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