1#include "BitmapView.h"
2#include <Alert.h>
3#include <BitmapStream.h>
4#include <Clipboard.h>
5#include <Font.h>
6#include <MenuItem.h>
7#include <Entry.h>
8#include <TranslationUtils.h>
9#include <TranslatorRoster.h>
10#include <TranslatorFormats.h>
11
12// TODO: Add support for labels
13
14#define M_REMOVE_IMAGE 'mrmi'
15#define M_PASTE_IMAGE 'mpsi'
16
17enum
18{
19	CLIP_NONE = 0,
20	CLIP_BEOS = 1,
21	CLIP_SHOWIMAGE = 2,
22	CLIP_PRODUCTIVE = 3
23};
24
25
26inline void SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a = 255);
27
28
29void
30SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a)
31{
32	if (col) {
33		col->red = r;
34		col->green = g;
35		col->blue = b;
36		col->alpha = a;
37	}
38}
39
40
41BitmapView::BitmapView(BRect frame, const char *name, BMessage *mod, BBitmap *bitmap,
42						const char *label, border_style borderstyle, int32 resize, int32 flags)
43  :	BView(frame, name, resize, flags)
44{
45	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
46
47	if (bitmap && bitmap->IsValid())
48		fBitmap = bitmap;
49	else
50		fBitmap = NULL;
51
52	if (mod)
53		SetMessage(mod);
54
55	fLabel = label;
56	fBorderStyle = borderstyle;
57	fFixedSize = false;
58	fEnabled = true;
59	fRemovableBitmap = false;
60	fAcceptDrops = true;
61	fAcceptPaste = true;
62	fConstrainDrops = true;
63	fMaxWidth = 100;
64	fMaxHeight = 100;
65
66	fPopUpMenu = new BPopUpMenu("deletepopup", false, false);
67	fPopUpMenu->AddItem(new BMenuItem("Close This Menu", new BMessage(B_CANCEL)));
68	fPopUpMenu->AddSeparatorItem();
69
70	fPasteItem = new BMenuItem("Paste Photo from Clipboard", new BMessage(M_PASTE_IMAGE));
71	fPopUpMenu->AddItem(fPasteItem);
72
73	fPopUpMenu->AddSeparatorItem();
74
75	fRemoveItem = new BMenuItem("Remove Photo", new BMessage(M_REMOVE_IMAGE));
76	fPopUpMenu->AddItem(fRemoveItem);
77
78	CalculateBitmapRect();
79
80	// Calculate the offsets for each of the words -- the phrase will be center justified
81	fNoPhotoWidths[0] = StringWidth("Drop");
82	fNoPhotoWidths[1] = StringWidth("a");
83	fNoPhotoWidths[2] = StringWidth("Photo");
84	fNoPhotoWidths[3] = StringWidth("Here");
85
86	font_height fh;
87	GetFontHeight(&fh);
88	float totalheight = fh.ascent + fh.descent + fh.leading;
89	float yoffset = (Bounds().Height() - 10 - (totalheight * 4)) / 2;
90	fNoPhotoOffsets[0].Set((Bounds().Width() - fNoPhotoWidths[0]) / 2, totalheight + yoffset);
91	fNoPhotoOffsets[1].Set((Bounds().Width() - fNoPhotoWidths[1]) / 2,
92							fNoPhotoOffsets[0].y + totalheight);
93	fNoPhotoOffsets[2].Set((Bounds().Width() - fNoPhotoWidths[2]) / 2,
94							fNoPhotoOffsets[1].y + totalheight);
95	fNoPhotoOffsets[3].Set((Bounds().Width() - fNoPhotoWidths[3]) / 2,
96							fNoPhotoOffsets[2].y + totalheight);
97}
98
99
100BitmapView::~BitmapView(void)
101{
102	delete fPopUpMenu;
103}
104
105
106void
107BitmapView::AttachedToWindow(void)
108{
109	SetTarget((BHandler*)Window());
110	fPopUpMenu->SetTargetForItems(this);
111}
112
113
114void
115BitmapView::SetBitmap(BBitmap *bitmap)
116{
117	if (bitmap && bitmap->IsValid()) {
118		if (fBitmap == bitmap)
119			return;
120		fBitmap = bitmap;
121	} else {
122		if (!fBitmap)
123			return;
124		fBitmap = NULL;
125	}
126
127	CalculateBitmapRect();
128	if (!IsHidden())
129		Invalidate();
130}
131
132
133void
134BitmapView::SetEnabled(bool value)
135{
136	if (fEnabled != value) {
137		fEnabled = value;
138		Invalidate();
139	}
140}
141
142
143/*
144void
145BitmapView::SetLabel(const char *label)
146{
147	if (fLabel.Compare(label) != 0)	{
148		fLabel = label;
149
150		CalculateBitmapRect();
151		if (!IsHidden())
152			Invalidate();
153	}
154}
155*/
156
157
158void
159BitmapView::SetStyle(border_style style)
160{
161	if (fBorderStyle != style) {
162		fBorderStyle = style;
163
164		CalculateBitmapRect();
165		if (!IsHidden())
166			Invalidate();
167	}
168}
169
170
171void
172BitmapView::SetFixedSize(bool isfixed)
173{
174	if (fFixedSize != isfixed) {
175		fFixedSize = isfixed;
176
177		CalculateBitmapRect();
178		if (!IsHidden())
179			Invalidate();
180	}
181}
182
183
184void
185BitmapView::MessageReceived(BMessage *msg)
186{
187	if (msg->WasDropped() && AcceptsDrops()) {
188		// We'll handle two types of drops: those from Tracker and those from ShowImage
189		if (msg->what == B_SIMPLE_DATA) {
190			int32 actions;
191			if (msg->FindInt32("be:actions", &actions) == B_OK) {
192				// ShowImage drop. This is a negotiated drag&drop, so send a reply
193				BMessage reply(B_COPY_TARGET), response;
194				reply.AddString("be:types", "image/jpeg");
195				reply.AddString("be:types", "image/png");
196
197				msg->SendReply(&reply, &response);
198
199				// now, we've gotten the response
200				if (response.what == B_MIME_DATA) {
201					// Obtain and translate the received data
202					uint8 *imagedata;
203					ssize_t datasize;
204
205					// Try JPEG first
206					if (response.FindData("image/jpeg", B_MIME_DATA,
207						(const void **)&imagedata, &datasize) != B_OK) {
208						// Try PNG next and piddle out if unsuccessful
209						if (response.FindData("image/png", B_PNG_FORMAT,
210							(const void **)&imagedata, &datasize) != B_OK)
211							return;
212					}
213
214					// Set up to decode into memory
215					BMemoryIO memio(imagedata, datasize);
216					BTranslatorRoster *roster = BTranslatorRoster::Default();
217					BBitmapStream bstream;
218
219					if (roster->Translate(&memio, NULL, NULL, &bstream, B_TRANSLATOR_BITMAP) == B_OK)
220					{
221						BBitmap *bmp;
222						if (bstream.DetachBitmap(&bmp) != B_OK)
223							return;
224						SetBitmap(bmp);
225
226						if (fConstrainDrops)
227							ConstrainBitmap();
228						Invoke();
229					}
230				}
231				return;
232			}
233
234			entry_ref ref;
235			if (msg->FindRef("refs", &ref) == B_OK) {
236				// Tracker drop
237				BBitmap *bmp = BTranslationUtils::GetBitmap(&ref);
238				if (bmp) {
239					SetBitmap(bmp);
240
241					if (fConstrainDrops)
242						ConstrainBitmap();
243					Invoke();
244				}
245			}
246		}
247		return;
248	}
249
250	switch (msg->what)
251	{
252		case M_REMOVE_IMAGE: {
253			BAlert *alert = new BAlert("ResEdit", "This cannot be undone. "
254				"Remove the image?", "Remove", "Cancel");
255			alert->SetShortcut(1, B_ESCAPE);
256			int32 value = alert->Go();
257			if (value == 0) {
258				SetBitmap(NULL);
259
260				if (Target()) {
261					BMessenger msgr(Target());
262
263					msgr.SendMessage(new BMessage(M_BITMAP_REMOVED));
264					return;
265				}
266			}
267		}
268		case M_PASTE_IMAGE:
269		{
270			PasteBitmap();
271			Invoke();
272		}
273	}
274	BView::MessageReceived(msg);
275}
276
277
278void
279BitmapView::Draw(BRect rect)
280{
281	if (fBitmap)
282		DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect);
283	else {
284		SetHighColor(0, 0, 0, 80);
285		SetDrawingMode(B_OP_ALPHA);
286		DrawString("Drop", fNoPhotoOffsets[0]);
287		DrawString("a", fNoPhotoOffsets[1]);
288		DrawString("Photo", fNoPhotoOffsets[2]);
289		DrawString("Here", fNoPhotoOffsets[3]);
290		SetDrawingMode(B_OP_COPY);
291	}
292
293	if (fBorderStyle == B_FANCY_BORDER) {
294		rgb_color base= { 216, 216, 216, 255 };
295		rgb_color work;
296
297		SetHighColor(base);
298		StrokeRect(Bounds().InsetByCopy(2, 2));
299
300		BeginLineArray(12);
301
302		BRect r(Bounds());
303
304		work = tint_color(base, B_DARKEN_2_TINT);
305		AddLine(r.LeftTop(), r.RightTop(), work);
306		AddLine(r.LeftTop(), r.LeftBottom(), work);
307		r.left++;
308
309		work = tint_color(base, B_DARKEN_4_TINT);
310		AddLine(r.RightTop(), r.RightBottom(), work);
311		AddLine(r.LeftBottom(), r.RightBottom(), work);
312
313		r.right--;
314		r.top++;
315		r.bottom--;
316
317
318		work = tint_color(base, B_LIGHTEN_MAX_TINT);
319		AddLine(r.LeftTop(), r.RightTop(), work);
320		AddLine(r.LeftTop(), r.LeftBottom(), work);
321		r.left++;
322
323		work = tint_color(base, B_DARKEN_3_TINT);
324		AddLine(r.RightTop(), r.RightBottom(), work);
325		AddLine(r.LeftBottom(), r.RightBottom(), work);
326
327		// this rect handled by the above StrokeRect, so inset a total of 2 pixels
328		r.left++;
329		r.right -= 2;
330		r.top += 2;
331		r.bottom -= 2;
332
333
334		work = tint_color(base, B_DARKEN_3_TINT);
335		AddLine(r.LeftTop(), r.RightTop(), work);
336		AddLine(r.LeftTop(), r.LeftBottom(), work);
337		r.left++;
338
339		work = tint_color(base, B_LIGHTEN_MAX_TINT);
340		AddLine(r.RightTop(), r.RightBottom(), work);
341		AddLine(r.LeftBottom(), r.RightBottom(), work);
342
343		r.right--;
344		r.top++;
345		r.bottom--;
346		EndLineArray();
347
348		SetHighColor(tint_color(base, B_DARKEN_2_TINT));
349		StrokeRect(r);
350	} else {
351		// Plain border
352		SetHighColor(0, 0, 0);
353		StrokeRect(fBitmapRect);
354	}
355}
356
357
358void
359BitmapView::MouseDown(BPoint pt)
360{
361	BPoint mousept;
362	uint32 buttons;
363
364	GetMouse(&mousept, &buttons);
365	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
366		ConvertToScreen(&mousept);
367
368		mousept.x= (mousept.x>5) ? mousept.x-5 : 0;
369		mousept.y= (mousept.y>5) ? mousept.y-5 : 0;
370
371		if (AcceptsPaste() && ClipboardHasBitmap())
372			fPasteItem->SetEnabled(true);
373		else
374			fPasteItem->SetEnabled(false);
375
376		if (fRemovableBitmap && fBitmap)
377			fRemoveItem->SetEnabled(true);
378		else
379			fRemoveItem->SetEnabled(false);
380
381		fPopUpMenu->Go(mousept, true, true, true);
382	}
383}
384
385
386void
387BitmapView::FrameResized(float w, float h)
388{
389	CalculateBitmapRect();
390}
391
392
393void
394BitmapView::CalculateBitmapRect(void)
395{
396	if (!fBitmap || fFixedSize) {
397		fBitmapRect = Bounds().InsetByCopy(1, 1);
398		return;
399	}
400
401	uint8 borderwidth = (fBorderStyle == B_FANCY_BORDER) ? 5 : 1;
402
403	BRect r(Bounds());
404	fBitmapRect= ScaleRectToFit(fBitmap->Bounds(), r.InsetByCopy(borderwidth, borderwidth));
405}
406
407
408void
409BitmapView::SetAcceptDrops(bool accept)
410{
411	fAcceptDrops = accept;
412}
413
414
415void
416BitmapView::SetAcceptPaste(bool accept)
417{
418	fAcceptPaste = accept;
419}
420
421
422void
423BitmapView::SetConstrainDrops(bool value)
424{
425	fConstrainDrops = value;
426}
427
428
429void
430BitmapView::MaxBitmapSize(float *width, float *height) const
431{
432	*width = fMaxWidth;
433	*height = fMaxHeight;
434}
435
436
437void
438BitmapView::SetMaxBitmapSize(const float &width, const float &height)
439{
440	fMaxWidth = width;
441	fMaxHeight = height;
442
443	ConstrainBitmap();
444}
445
446
447void
448BitmapView::SetBitmapRemovable(bool isremovable)
449{
450	fRemovableBitmap = isremovable;
451}
452
453
454void
455BitmapView::ConstrainBitmap(void)
456{
457	if (!fBitmap || fMaxWidth < 1 || fMaxHeight < 1)
458		return;
459
460	BRect r = ScaleRectToFit(fBitmap->Bounds(), BRect(0, 0, fMaxWidth - 1, fMaxHeight - 1));
461	r.OffsetTo(0, 0);
462
463	BBitmap *scaled = new BBitmap(r, fBitmap->ColorSpace(), true);
464	BView *view = new BView(r, "drawview", 0, 0);
465
466	scaled->Lock();
467	scaled->AddChild(view);
468	view->DrawBitmap(fBitmap, fBitmap->Bounds(), scaled->Bounds());
469	scaled->Unlock();
470
471	delete fBitmap;
472	fBitmap = new BBitmap(scaled, false);
473}
474
475
476bool
477BitmapView::ClipboardHasBitmap(void)
478{
479	BMessage *clip = NULL, flattened;
480	uint8 clipval = CLIP_NONE;
481	bool returnval;
482
483	if (be_clipboard->Lock()) {
484		clip = be_clipboard->Data();
485		if (!clip->IsEmpty()) {
486			returnval = (clip->FindMessage("image/bitmap", &flattened) == B_OK);
487			if (returnval)
488				clipval = CLIP_BEOS;
489			else {
490				BString string;
491				returnval = (clip->FindString("class", &string) == B_OK && string == "BBitmap");
492
493				// Try method Gobe Productive uses if that, too, didn't work
494				if (returnval)
495					clipval = CLIP_SHOWIMAGE;
496				else {
497					returnval = (clip->FindMessage("image/x-vnd.Be-bitmap", &flattened) == B_OK);
498					if (returnval)
499						clipval = CLIP_SHOWIMAGE;
500					else
501						clipval = CLIP_NONE;
502				}
503			}
504		}
505		be_clipboard->Unlock();
506	}
507	return (clipval != CLIP_NONE)?true:false;
508}
509
510
511BBitmap *
512BitmapView::BitmapFromClipboard(void)
513{
514	BMessage *clip = NULL, flattened;
515	BBitmap *bitmap;
516
517	if (!be_clipboard->Lock())
518		return NULL;
519
520	clip = be_clipboard->Data();
521	if (!clip)
522		return NULL;
523
524	uint8 clipval = CLIP_NONE;
525
526	// Try ArtPaint-style storage
527	status_t status = clip->FindMessage("image/bitmap", &flattened);
528
529	// If that didn't work, try ShowImage-style
530	if (status != B_OK) {
531		BString string;
532		status = clip->FindString("class", &string);
533
534		// Try method Gobe Productive uses if that, too, didn't work
535		if (status == B_OK && string == "BBitmap")
536			clipval = CLIP_SHOWIMAGE;
537		else {
538			status = clip->FindMessage("image/x-vnd.Be-bitmap", &flattened);
539			if (status == B_OK)
540				clipval = CLIP_PRODUCTIVE;
541			else
542				clipval = CLIP_NONE;
543		}
544	}
545	else
546		clipval = CLIP_BEOS;
547
548	be_clipboard->Unlock();
549
550	switch (clipval) {
551		case CLIP_SHOWIMAGE: {
552			// Showimage does it a slightly different way -- it dumps the BBitmap
553			// data directly to the clipboard message instead of packaging it in
554			// a bitmap like everyone else.
555
556			if (!be_clipboard->Lock())
557				return NULL;
558
559			BMessage datamsg(*be_clipboard->Data());
560
561			be_clipboard->Unlock();
562
563			const void *buffer;
564			ssize_t bufferLength;
565
566			BRect frame;
567			color_space cspace = B_NO_COLOR_SPACE;
568
569			status = datamsg.FindRect("_frame", &frame);
570			if (status != B_OK)
571				return NULL;
572
573			status = datamsg.FindInt32("_cspace", (int32)cspace);
574			if (status != B_OK)
575				return NULL;
576			cspace = B_RGBA32;
577			bitmap = new BBitmap(frame, cspace, true);
578
579			status = datamsg.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength);
580			if (status != B_OK) {
581				delete bitmap;
582				return NULL;
583			}
584
585			memcpy(bitmap->Bits(), buffer, bufferLength);
586			return bitmap;
587		}
588		case CLIP_PRODUCTIVE:
589		// Productive doesn't name the packaged BBitmap data message the same, but
590		// uses exactly the same data format.
591
592		case CLIP_BEOS: {
593			const void *buffer;
594			ssize_t bufferLength;
595
596			BRect frame;
597			color_space cspace = B_NO_COLOR_SPACE;
598
599			status = flattened.FindRect("_frame", &frame);
600			if (status != B_OK)
601				return NULL;
602
603			status = flattened.FindInt32("_cspace", (int32)cspace);
604			if (status != B_OK)
605				return NULL;
606			cspace = B_RGBA32;
607			bitmap = new BBitmap(frame, cspace, true);
608
609			status = flattened.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength);
610			if (status != B_OK) {
611				delete bitmap;
612				return NULL;
613			}
614
615			memcpy(bitmap->Bits(), buffer, bufferLength);
616			return bitmap;
617		}
618		default:
619			return NULL;
620	}
621
622	// shut the compiler up
623	return NULL;
624}
625
626
627BRect
628ScaleRectToFit(const BRect &from, const BRect &to)
629{
630	// Dynamic sizing algorithm
631	// 1) Check to see if either dimension is bigger than the view's display area
632	// 2) If smaller along both axes, make bitmap rect centered and return
633	// 3) Check to see if scaling is to be horizontal or vertical on basis of longer axis
634	// 4) Calculate scaling factor
635	// 5) Scale both axes down by scaling factor, accounting for border width
636	// 6) Center the rectangle in the direction of the smaller axis
637
638	if (!to.IsValid())
639		return from;
640	if (!from.IsValid())
641		return to;
642
643	BRect r(to);
644
645	if ((from.Width() <= r.Width()) && (from.Height() <= r.Height())) {
646		// Smaller than view, so just center and return
647		r = from;
648		r.OffsetBy((to.Width() - r.Width()) / 2, (to.Height() - r.Height()) / 2);
649		return r;
650	}
651
652	float multiplier = from.Width()/from.Height();
653	if (multiplier > 1)	{
654		// Landscape orientation
655
656		// Scale rectangle to bounds width and center height
657		r.bottom = r.top + (r.Width() / multiplier);
658		r.OffsetBy(0, (to.Height() - r.Height()) / 2);
659	} else {
660		// Portrait orientation
661
662		// Scale rectangle to bounds height and center width
663		r.right = r.left + (r.Height() * multiplier);
664		r.OffsetBy((to.Width() - r.Width()) / 2, 0);
665	}
666	return r;
667}
668
669
670void
671BitmapView::RemoveBitmap(void)
672{
673	SetBitmap(NULL);
674}
675
676
677void
678BitmapView::PasteBitmap(void)
679{
680	BBitmap *bmp = BitmapFromClipboard();
681	if (bmp)
682		SetBitmap(bmp);
683
684	if (fConstrainDrops)
685		ConstrainBitmap();
686}
687