xref: /haiku/src/apps/resedit/BitmapView.cpp (revision bab64f65bb775dc23060e276f1f1c4498ab7af6c)
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 
17 enum
18 {
19 	CLIP_NONE = 0,
20 	CLIP_BEOS = 1,
21 	CLIP_SHOWIMAGE = 2,
22 	CLIP_PRODUCTIVE = 3
23 };
24 
25 
26 inline void SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a = 255);
27 
28 
29 void
SetRGBColor(rgb_color * col,uint8 r,uint8 g,uint8 b,uint8 a)30 SetRGBColor(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 
BitmapView(BRect frame,const char * name,BMessage * mod,BBitmap * bitmap,const char * label,border_style borderstyle,int32 resize,int32 flags)41 BitmapView::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 
~BitmapView(void)100 BitmapView::~BitmapView(void)
101 {
102 	delete fPopUpMenu;
103 }
104 
105 
106 void
AttachedToWindow(void)107 BitmapView::AttachedToWindow(void)
108 {
109 	SetTarget((BHandler*)Window());
110 	fPopUpMenu->SetTargetForItems(this);
111 }
112 
113 
114 void
SetBitmap(BBitmap * bitmap)115 BitmapView::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 
133 void
SetEnabled(bool value)134 BitmapView::SetEnabled(bool value)
135 {
136 	if (fEnabled != value) {
137 		fEnabled = value;
138 		Invalidate();
139 	}
140 }
141 
142 
143 /*
144 void
145 BitmapView::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 
158 void
SetStyle(border_style style)159 BitmapView::SetStyle(border_style style)
160 {
161 	if (fBorderStyle != style) {
162 		fBorderStyle = style;
163 
164 		CalculateBitmapRect();
165 		if (!IsHidden())
166 			Invalidate();
167 	}
168 }
169 
170 
171 void
SetFixedSize(bool isfixed)172 BitmapView::SetFixedSize(bool isfixed)
173 {
174 	if (fFixedSize != isfixed) {
175 		fFixedSize = isfixed;
176 
177 		CalculateBitmapRect();
178 		if (!IsHidden())
179 			Invalidate();
180 	}
181 }
182 
183 
184 void
MessageReceived(BMessage * msg)185 BitmapView::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 
278 void
Draw(BRect rect)279 BitmapView::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 
358 void
MouseDown(BPoint pt)359 BitmapView::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 
386 void
FrameResized(float w,float h)387 BitmapView::FrameResized(float w, float h)
388 {
389 	CalculateBitmapRect();
390 }
391 
392 
393 void
CalculateBitmapRect(void)394 BitmapView::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 
408 void
SetAcceptDrops(bool accept)409 BitmapView::SetAcceptDrops(bool accept)
410 {
411 	fAcceptDrops = accept;
412 }
413 
414 
415 void
SetAcceptPaste(bool accept)416 BitmapView::SetAcceptPaste(bool accept)
417 {
418 	fAcceptPaste = accept;
419 }
420 
421 
422 void
SetConstrainDrops(bool value)423 BitmapView::SetConstrainDrops(bool value)
424 {
425 	fConstrainDrops = value;
426 }
427 
428 
429 void
MaxBitmapSize(float * width,float * height) const430 BitmapView::MaxBitmapSize(float *width, float *height) const
431 {
432 	*width = fMaxWidth;
433 	*height = fMaxHeight;
434 }
435 
436 
437 void
SetMaxBitmapSize(const float & width,const float & height)438 BitmapView::SetMaxBitmapSize(const float &width, const float &height)
439 {
440 	fMaxWidth = width;
441 	fMaxHeight = height;
442 
443 	ConstrainBitmap();
444 }
445 
446 
447 void
SetBitmapRemovable(bool isremovable)448 BitmapView::SetBitmapRemovable(bool isremovable)
449 {
450 	fRemovableBitmap = isremovable;
451 }
452 
453 
454 void
ConstrainBitmap(void)455 BitmapView::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 
476 bool
ClipboardHasBitmap(void)477 BitmapView::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 
511 BBitmap *
BitmapFromClipboard(void)512 BitmapView::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 
627 BRect
ScaleRectToFit(const BRect & from,const BRect & to)628 ScaleRectToFit(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 
670 void
RemoveBitmap(void)671 BitmapView::RemoveBitmap(void)
672 {
673 	SetBitmap(NULL);
674 }
675 
676 
677 void
PasteBitmap(void)678 BitmapView::PasteBitmap(void)
679 {
680 	BBitmap *bmp = BitmapFromClipboard();
681 	if (bmp)
682 		SetBitmap(bmp);
683 
684 	if (fConstrainDrops)
685 		ConstrainBitmap();
686 }
687