xref: /haiku/src/apps/resedit/BitmapView.cpp (revision 97901ec593ec4dd50ac115c1c35a6d72f6e489a5)
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
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 
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 	SetViewColor(ui_color(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 
100 BitmapView::~BitmapView(void)
101 {
102 	delete fPopUpMenu;
103 }
104 
105 
106 void
107 BitmapView::AttachedToWindow(void)
108 {
109 	SetTarget((BHandler*)Window());
110 	fPopUpMenu->SetTargetForItems(this);
111 }
112 
113 
114 void
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
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
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
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
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("Mr. Peeps!", "This cannot be undone. Remove the image?",
254 										"Remove", "Cancel");
255 			int32 value = alert->Go();
256 			if (value == 0) {
257 				SetBitmap(NULL);
258 
259 				if (Target()) {
260 					BMessenger msgr(Target());
261 
262 					msgr.SendMessage(new BMessage(M_BITMAP_REMOVED));
263 					return;
264 				}
265 			}
266 		}
267 		case M_PASTE_IMAGE:
268 		{
269 			PasteBitmap();
270 			Invoke();
271 		}
272 	}
273 	BView::MessageReceived(msg);
274 }
275 
276 
277 void
278 BitmapView::Draw(BRect rect)
279 {
280 	if (fBitmap)
281 		DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect);
282 	else {
283 		SetHighColor(0, 0, 0, 80);
284 		SetDrawingMode(B_OP_ALPHA);
285 		DrawString("Drop", fNoPhotoOffsets[0]);
286 		DrawString("a", fNoPhotoOffsets[1]);
287 		DrawString("Photo", fNoPhotoOffsets[2]);
288 		DrawString("Here", fNoPhotoOffsets[3]);
289 		SetDrawingMode(B_OP_COPY);
290 	}
291 
292 	if (fBorderStyle == B_FANCY_BORDER) {
293 		rgb_color base= { 216, 216, 216, 255 };
294 		rgb_color work;
295 
296 		SetHighColor(base);
297 		StrokeRect(Bounds().InsetByCopy(2, 2));
298 
299 		BeginLineArray(12);
300 
301 		BRect r(Bounds());
302 
303 		work = tint_color(base, B_DARKEN_2_TINT);
304 		AddLine(r.LeftTop(), r.RightTop(), work);
305 		AddLine(r.LeftTop(), r.LeftBottom(), work);
306 		r.left++;
307 
308 		work = tint_color(base, B_DARKEN_4_TINT);
309 		AddLine(r.RightTop(), r.RightBottom(), work);
310 		AddLine(r.LeftBottom(), r.RightBottom(), work);
311 
312 		r.right--;
313 		r.top++;
314 		r.bottom--;
315 
316 
317 		work = tint_color(base, B_LIGHTEN_MAX_TINT);
318 		AddLine(r.LeftTop(), r.RightTop(), work);
319 		AddLine(r.LeftTop(), r.LeftBottom(), work);
320 		r.left++;
321 
322 		work = tint_color(base, B_DARKEN_3_TINT);
323 		AddLine(r.RightTop(), r.RightBottom(), work);
324 		AddLine(r.LeftBottom(), r.RightBottom(), work);
325 
326 		// this rect handled by the above StrokeRect, so inset a total of 2 pixels
327 		r.left++;
328 		r.right -= 2;
329 		r.top += 2;
330 		r.bottom -= 2;
331 
332 
333 		work = tint_color(base, B_DARKEN_3_TINT);
334 		AddLine(r.LeftTop(), r.RightTop(), work);
335 		AddLine(r.LeftTop(), r.LeftBottom(), work);
336 		r.left++;
337 
338 		work = tint_color(base, B_LIGHTEN_MAX_TINT);
339 		AddLine(r.RightTop(), r.RightBottom(), work);
340 		AddLine(r.LeftBottom(), r.RightBottom(), work);
341 
342 		r.right--;
343 		r.top++;
344 		r.bottom--;
345 		EndLineArray();
346 
347 		SetHighColor(tint_color(base, B_DARKEN_2_TINT));
348 		StrokeRect(r);
349 	} else {
350 		// Plain border
351 		SetHighColor(0, 0, 0);
352 		StrokeRect(fBitmapRect);
353 	}
354 }
355 
356 
357 void
358 BitmapView::MouseDown(BPoint pt)
359 {
360 	BPoint mousept;
361 	uint32 buttons;
362 
363 	GetMouse(&mousept, &buttons);
364 	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
365 		ConvertToScreen(&mousept);
366 
367 		mousept.x= (mousept.x>5) ? mousept.x-5 : 0;
368 		mousept.y= (mousept.y>5) ? mousept.y-5 : 0;
369 
370 		if (AcceptsPaste() && ClipboardHasBitmap())
371 			fPasteItem->SetEnabled(true);
372 		else
373 			fPasteItem->SetEnabled(false);
374 
375 		if (fRemovableBitmap && fBitmap)
376 			fRemoveItem->SetEnabled(true);
377 		else
378 			fRemoveItem->SetEnabled(false);
379 
380 		fPopUpMenu->Go(mousept, true, true, true);
381 	}
382 }
383 
384 
385 void
386 BitmapView::FrameResized(float w, float h)
387 {
388 	CalculateBitmapRect();
389 }
390 
391 
392 void
393 BitmapView::CalculateBitmapRect(void)
394 {
395 	if (!fBitmap || fFixedSize) {
396 		fBitmapRect = Bounds().InsetByCopy(1, 1);
397 		return;
398 	}
399 
400 	uint8 borderwidth = (fBorderStyle == B_FANCY_BORDER) ? 5 : 1;
401 
402 	BRect r(Bounds());
403 	fBitmapRect= ScaleRectToFit(fBitmap->Bounds(), r.InsetByCopy(borderwidth, borderwidth));
404 }
405 
406 
407 void
408 BitmapView::SetAcceptDrops(bool accept)
409 {
410 	fAcceptDrops = accept;
411 }
412 
413 
414 void
415 BitmapView::SetAcceptPaste(bool accept)
416 {
417 	fAcceptPaste = accept;
418 }
419 
420 
421 void
422 BitmapView::SetConstrainDrops(bool value)
423 {
424 	fConstrainDrops = value;
425 }
426 
427 
428 void
429 BitmapView::MaxBitmapSize(float *width, float *height) const
430 {
431 	*width = fMaxWidth;
432 	*height = fMaxHeight;
433 }
434 
435 
436 void
437 BitmapView::SetMaxBitmapSize(const float &width, const float &height)
438 {
439 	fMaxWidth = width;
440 	fMaxHeight = height;
441 
442 	ConstrainBitmap();
443 }
444 
445 
446 void
447 BitmapView::SetBitmapRemovable(bool isremovable)
448 {
449 	fRemovableBitmap = isremovable;
450 }
451 
452 
453 void
454 BitmapView::ConstrainBitmap(void)
455 {
456 	if (!fBitmap || fMaxWidth < 1 || fMaxHeight < 1)
457 		return;
458 
459 	BRect r = ScaleRectToFit(fBitmap->Bounds(), BRect(0, 0, fMaxWidth - 1, fMaxHeight - 1));
460 	r.OffsetTo(0, 0);
461 
462 	BBitmap *scaled = new BBitmap(r, fBitmap->ColorSpace(), true);
463 	BView *view = new BView(r, "drawview", 0, 0);
464 
465 	scaled->Lock();
466 	scaled->AddChild(view);
467 	view->DrawBitmap(fBitmap, fBitmap->Bounds(), scaled->Bounds());
468 	scaled->Unlock();
469 
470 	delete fBitmap;
471 	fBitmap = new BBitmap(scaled, false);
472 }
473 
474 
475 bool
476 BitmapView::ClipboardHasBitmap(void)
477 {
478 	BMessage *clip = NULL, flattened;
479 	uint8 clipval = CLIP_NONE;
480 	bool returnval;
481 
482 	if (be_clipboard->Lock()) {
483 		clip = be_clipboard->Data();
484 		if (!clip->IsEmpty()) {
485 			returnval = (clip->FindMessage("image/bitmap", &flattened) == B_OK);
486 			if (returnval)
487 				clipval = CLIP_BEOS;
488 			else {
489 				BString string;
490 				returnval = (clip->FindString("class", &string) == B_OK && string == "BBitmap");
491 
492 				// Try method Gobe Productive uses if that, too, didn't work
493 				if (returnval)
494 					clipval = CLIP_SHOWIMAGE;
495 				else {
496 					returnval = (clip->FindMessage("image/x-vnd.Be-bitmap", &flattened) == B_OK);
497 					if (returnval)
498 						clipval = CLIP_SHOWIMAGE;
499 					else
500 						clipval = CLIP_NONE;
501 				}
502 			}
503 		}
504 		be_clipboard->Unlock();
505 	}
506 	return (clipval != CLIP_NONE)?true:false;
507 }
508 
509 
510 BBitmap *
511 BitmapView::BitmapFromClipboard(void)
512 {
513 	BMessage *clip = NULL, flattened;
514 	BBitmap *bitmap;
515 
516 	if (!be_clipboard->Lock())
517 		return NULL;
518 
519 	clip = be_clipboard->Data();
520 	if (!clip)
521 		return NULL;
522 
523 	uint8 clipval = CLIP_NONE;
524 
525 	// Try ArtPaint-style storage
526 	status_t status = clip->FindMessage("image/bitmap", &flattened);
527 
528 	// If that didn't work, try ShowImage-style
529 	if (status != B_OK) {
530 		BString string;
531 		status = clip->FindString("class", &string);
532 
533 		// Try method Gobe Productive uses if that, too, didn't work
534 		if (status == B_OK && string == "BBitmap")
535 			clipval = CLIP_SHOWIMAGE;
536 		else {
537 			status = clip->FindMessage("image/x-vnd.Be-bitmap", &flattened);
538 			if (status == B_OK)
539 				clipval = CLIP_PRODUCTIVE;
540 			else
541 				clipval = CLIP_NONE;
542 		}
543 	}
544 	else
545 		clipval = CLIP_BEOS;
546 
547 	be_clipboard->Unlock();
548 
549 	switch (clipval) {
550 		case CLIP_SHOWIMAGE: {
551 			// Showimage does it a slightly different way -- it dumps the BBitmap
552 			// data directly to the clipboard message instead of packaging it in
553 			// a bitmap like everyone else.
554 
555 			if (!be_clipboard->Lock())
556 				return NULL;
557 
558 			BMessage datamsg(*be_clipboard->Data());
559 
560 			be_clipboard->Unlock();
561 
562 			const void *buffer;
563 			int32 bufferLength;
564 
565 			BRect frame;
566 			color_space cspace = B_NO_COLOR_SPACE;
567 
568 			status = datamsg.FindRect("_frame", &frame);
569 			if (status != B_OK)
570 				return NULL;
571 
572 			status = datamsg.FindInt32("_cspace", (int32)cspace);
573 			if (status != B_OK)
574 				return NULL;
575 			cspace = B_RGBA32;
576 			bitmap = new BBitmap(frame, cspace, true);
577 
578 			status = datamsg.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength);
579 			if (status != B_OK) {
580 				delete bitmap;
581 				return NULL;
582 			}
583 
584 			memcpy(bitmap->Bits(), buffer, bufferLength);
585 			return bitmap;
586 		}
587 		case CLIP_PRODUCTIVE:
588 		// Productive doesn't name the packaged BBitmap data message the same, but
589 		// uses exactly the same data format.
590 
591 		case CLIP_BEOS: {
592 			const void *buffer;
593 			int32 bufferLength;
594 
595 			BRect frame;
596 			color_space cspace = B_NO_COLOR_SPACE;
597 
598 			status = flattened.FindRect("_frame", &frame);
599 			if (status != B_OK)
600 				return NULL;
601 
602 			status = flattened.FindInt32("_cspace", (int32)cspace);
603 			if (status != B_OK)
604 				return NULL;
605 			cspace = B_RGBA32;
606 			bitmap = new BBitmap(frame, cspace, true);
607 
608 			status = flattened.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength);
609 			if (status != B_OK) {
610 				delete bitmap;
611 				return NULL;
612 			}
613 
614 			memcpy(bitmap->Bits(), buffer, bufferLength);
615 			return bitmap;
616 		}
617 		default:
618 			return NULL;
619 	}
620 
621 	// shut the compiler up
622 	return NULL;
623 }
624 
625 
626 BRect
627 ScaleRectToFit(const BRect &from, const BRect &to)
628 {
629 	// Dynamic sizing algorithm
630 	// 1) Check to see if either dimension is bigger than the view's display area
631 	// 2) If smaller along both axes, make bitmap rect centered and return
632 	// 3) Check to see if scaling is to be horizontal or vertical on basis of longer axis
633 	// 4) Calculate scaling factor
634 	// 5) Scale both axes down by scaling factor, accounting for border width
635 	// 6) Center the rectangle in the direction of the smaller axis
636 
637 	if (!to.IsValid())
638 		return from;
639 	if (!from.IsValid())
640 		return to;
641 
642 	BRect r(to);
643 
644 	if ((from.Width() <= r.Width()) && (from.Height() <= r.Height())) {
645 		// Smaller than view, so just center and return
646 		r = from;
647 		r.OffsetBy((to.Width() - r.Width()) / 2, (to.Height() - r.Height()) / 2);
648 		return r;
649 	}
650 
651 	float multiplier = from.Width()/from.Height();
652 	if (multiplier > 1)	{
653 		// Landscape orientation
654 
655 		// Scale rectangle to bounds width and center height
656 		r.bottom = r.top + (r.Width() / multiplier);
657 		r.OffsetBy(0, (to.Height() - r.Height()) / 2);
658 	} else {
659 		// Portrait orientation
660 
661 		// Scale rectangle to bounds height and center width
662 		r.right = r.left + (r.Height() * multiplier);
663 		r.OffsetBy((to.Width() - r.Width()) / 2, 0);
664 	}
665 	return r;
666 }
667 
668 
669 void
670 BitmapView::RemoveBitmap(void)
671 {
672 	SetBitmap(NULL);
673 }
674 
675 
676 void
677 BitmapView::PasteBitmap(void)
678 {
679 	BBitmap *bmp = BitmapFromClipboard();
680 	if (bmp)
681 		SetBitmap(bmp);
682 
683 	if (fConstrainDrops)
684 		ConstrainBitmap();
685 }
686