1 /*
2 * Copyright 2011, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Philippe Houdoin
7 */
8
9
10 #include "PictureView.h"
11
12 #include <math.h>
13 #include <new>
14 #include <stdio.h>
15
16 #include <Alert.h>
17 #include <Bitmap.h>
18 #include <BitmapStream.h>
19 #include <Catalog.h>
20 #include <Clipboard.h>
21 #include <Directory.h>
22 #include <File.h>
23 #include <FilePanel.h>
24 #include <IconUtils.h>
25 #include <LayoutUtils.h>
26 #include <PopUpMenu.h>
27 #include <DataIO.h>
28 #include <MenuItem.h>
29 #include <Messenger.h>
30 #include <MimeType.h>
31 #include <NodeInfo.h>
32 #include <String.h>
33 #include <TranslatorRoster.h>
34 #include <TranslationUtils.h>
35 #include <Window.h>
36
37 #include "PeopleApp.h" // for B_PERSON_MIMETYPE
38
39
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "People"
42
43
44 const uint32 kMsgPopUpMenuClosed = 'pmcl';
45
46 class PopUpMenu : public BPopUpMenu {
47 public:
48 PopUpMenu(const char* name, BMessenger target);
49 virtual ~PopUpMenu();
50
51 private:
52 BMessenger fTarget;
53 };
54
55
PopUpMenu(const char * name,BMessenger target)56 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
57 :
58 BPopUpMenu(name, false, false), fTarget(target)
59 {
60 SetAsyncAutoDestruct(true);
61 }
62
63
~PopUpMenu()64 PopUpMenu::~PopUpMenu()
65 {
66 fTarget.SendMessage(kMsgPopUpMenuClosed);
67 }
68
69
70 // #pragma mark -
71
72 using std::nothrow;
73
74
75 const float kPictureMargin = 6.0;
76
PictureView(float width,float height,const entry_ref * ref)77 PictureView::PictureView(float width, float height, const entry_ref* ref)
78 :
79 BView("pictureview", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE),
80 fPicture(NULL),
81 fOriginalPicture(NULL),
82 fDefaultPicture(NULL),
83 fShowingPopUpMenu(false),
84 fPictureType(0),
85 fFocusChanging(false),
86 fOpenPanel(new BFilePanel(B_OPEN_PANEL))
87 {
88 SetViewColor(255, 255, 255);
89
90 SetToolTip(B_TRANSLATE(
91 "Drop an image here,\n"
92 "or use the contextual menu."));
93
94 BSize size(width + 2 * kPictureMargin, height + 2 * kPictureMargin);
95 SetExplicitMinSize(size);
96 SetExplicitMaxSize(size);
97
98 BMimeType mime(B_PERSON_MIMETYPE);
99 uint8* iconData;
100 size_t iconDataSize;
101 if (mime.GetIcon(&iconData, &iconDataSize) == B_OK) {
102 float size = width < height ? width : height;
103 fDefaultPicture = new BBitmap(BRect(0, 0, size, size),
104 B_RGB32);
105 if (fDefaultPicture->InitCheck() != B_OK
106 || BIconUtils::GetVectorIcon(iconData, iconDataSize,
107 fDefaultPicture) != B_OK) {
108 delete fDefaultPicture;
109 fDefaultPicture = NULL;
110 }
111 }
112
113 Update(ref);
114 }
115
116
~PictureView()117 PictureView::~PictureView()
118 {
119 delete fDefaultPicture;
120 delete fPicture;
121 if (fOriginalPicture != fPicture)
122 delete fOriginalPicture;
123
124 delete fOpenPanel;
125 }
126
127
128 bool
HasChanged()129 PictureView::HasChanged()
130 {
131 return fPicture != fOriginalPicture;
132 }
133
134
135 void
Revert()136 PictureView::Revert()
137 {
138 if (!HasChanged())
139 return;
140
141 _SetPicture(fOriginalPicture);
142 }
143
144
145 void
Update()146 PictureView::Update()
147 {
148 if (fOriginalPicture != fPicture) {
149 delete fOriginalPicture;
150 fOriginalPicture = fPicture;
151 }
152 }
153
154
155 void
Update(const entry_ref * ref)156 PictureView::Update(const entry_ref* ref)
157 {
158 // Don't update when user has modified the picture
159 if (HasChanged())
160 return;
161
162 if (_LoadPicture(ref) == B_OK) {
163 delete fOriginalPicture;
164 fOriginalPicture = fPicture;
165 }
166 }
167
168
169 BBitmap*
Bitmap()170 PictureView::Bitmap()
171 {
172 return fPicture;
173 }
174
175
176 uint32
SuggestedType()177 PictureView::SuggestedType()
178 {
179 return fPictureType;
180 }
181
182
183 const char*
SuggestedMIMEType()184 PictureView::SuggestedMIMEType()
185 {
186 if (fPictureMIMEType == "")
187 return NULL;
188
189 return fPictureMIMEType.String();
190 }
191
192
193 void
MessageReceived(BMessage * message)194 PictureView::MessageReceived(BMessage* message)
195 {
196 switch (message->what) {
197 case B_REFS_RECEIVED:
198 case B_SIMPLE_DATA:
199 {
200 entry_ref ref;
201 if (message->FindRef("refs", &ref) == B_OK
202 && _LoadPicture(&ref) == B_OK)
203 MakeFocus(true);
204 else
205 _HandleDrop(message);
206 break;
207 }
208
209 case B_MIME_DATA:
210 // TODO
211 break;
212
213 case B_COPY_TARGET:
214 _HandleDrop(message);
215 break;
216
217 case B_PASTE:
218 {
219 if (be_clipboard->Lock() != B_OK)
220 break;
221
222 BMessage* data = be_clipboard->Data();
223 BMessage archivedBitmap;
224 if (data->FindMessage("image/bitmap", &archivedBitmap) == B_OK) {
225 BBitmap* picture = new(std::nothrow) BBitmap(&archivedBitmap);
226 _SetPicture(picture);
227 }
228
229 be_clipboard->Unlock();
230 break;
231 }
232
233 case B_DELETE:
234 case B_TRASH_TARGET:
235 _SetPicture(NULL);
236 break;
237
238 case kMsgLoadImage:
239 fOpenPanel->SetTarget(BMessenger(this));
240 fOpenPanel->Show();
241 break;
242
243 case kMsgPopUpMenuClosed:
244 fShowingPopUpMenu = false;
245 break;
246
247 default:
248 BView::MessageReceived(message);
249 break;
250 }
251 }
252
253
254 void
Draw(BRect updateRect)255 PictureView::Draw(BRect updateRect)
256 {
257 BRect rect = Bounds();
258
259 // Draw the outer frame
260 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
261 if (IsFocus() && Window() && Window()->IsActive())
262 SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
263 else
264 SetHighColor(tint_color(base, B_DARKEN_3_TINT));
265 StrokeRect(rect);
266
267 if (fFocusChanging) {
268 // focus frame is already redraw, stop here
269 return;
270 }
271
272 BBitmap* picture = fPicture ? fPicture : fDefaultPicture;
273 if (picture != NULL) {
274 // scale to fit and center picture in frame
275 BRect frame = rect.InsetByCopy(kPictureMargin, kPictureMargin);
276 BRect srcRect = picture->Bounds();
277 BSize size = frame.Size();
278 float pictureAspect = srcRect.Height() / srcRect.Width();
279 float frameAspect = size.height / size.width;
280
281 if (pictureAspect > frameAspect)
282 size.width = srcRect.Width() * size.height / srcRect.Height();
283 else if (pictureAspect < frameAspect)
284 size.height = srcRect.Height() * size.width / srcRect.Width();
285
286 fPictureRect = BLayoutUtils::AlignInFrame(frame, size,
287 BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
288
289 SetDrawingMode(B_OP_ALPHA);
290 if (picture == fDefaultPicture) {
291 SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
292 SetHighColor(0, 0, 0, 24);
293 }
294
295 DrawBitmapAsync(picture, srcRect, fPictureRect,
296 B_FILTER_BITMAP_BILINEAR);
297
298 SetDrawingMode(B_OP_OVER);
299 }
300 }
301
302
303 void
WindowActivated(bool active)304 PictureView::WindowActivated(bool active)
305 {
306 BView::WindowActivated(active);
307
308 if (IsFocus())
309 Invalidate();
310 }
311
312
313 void
MakeFocus(bool focused)314 PictureView::MakeFocus(bool focused)
315 {
316 if (focused == IsFocus())
317 return;
318
319 BView::MakeFocus(focused);
320
321 if (Window()) {
322 fFocusChanging = true;
323 Invalidate();
324 Flush();
325 fFocusChanging = false;
326 }
327 }
328
329
330 void
MouseDown(BPoint position)331 PictureView::MouseDown(BPoint position)
332 {
333 MakeFocus(true);
334
335 uint32 buttons = 0;
336 if (Window() != NULL && Window()->CurrentMessage() != NULL)
337 buttons = Window()->CurrentMessage()->FindInt32("buttons");
338
339 if (fPicture != NULL && fPictureRect.Contains(position)
340 && (buttons
341 & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
342
343 _BeginDrag(position);
344
345 } else if (buttons == B_SECONDARY_MOUSE_BUTTON)
346 _ShowPopUpMenu(ConvertToScreen(position));
347 }
348
349
350 void
KeyDown(const char * bytes,int32 numBytes)351 PictureView::KeyDown(const char* bytes, int32 numBytes)
352 {
353 if (numBytes != 1) {
354 BView::KeyDown(bytes, numBytes);
355 return;
356 }
357
358 switch (*bytes) {
359 case B_DELETE:
360 _SetPicture(NULL);
361 break;
362
363 default:
364 BView::KeyDown(bytes, numBytes);
365 break;
366 }
367 }
368
369
370 // #pragma mark -
371
372
373 void
_ShowPopUpMenu(BPoint screen)374 PictureView::_ShowPopUpMenu(BPoint screen)
375 {
376 if (fShowingPopUpMenu)
377 return;
378
379 PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
380
381 BMenuItem* item = new BMenuItem(B_TRANSLATE("Load image" B_UTF8_ELLIPSIS),
382 new BMessage(kMsgLoadImage));
383 menu->AddItem(item);
384
385 item = new BMenuItem(B_TRANSLATE("Remove image"), new BMessage(B_DELETE));
386 item->SetEnabled(fPicture != NULL);
387 menu->AddItem(item);
388
389 menu->SetTargetForItems(this);
390 menu->Go(screen, true, true, true);
391 fShowingPopUpMenu = true;
392 }
393
394
395 BBitmap*
_CopyPicture(uint8 alpha)396 PictureView::_CopyPicture(uint8 alpha)
397 {
398 bool hasAlpha = alpha != 255;
399
400 if (!fPicture)
401 return NULL;
402
403 BRect rect = fPictureRect.OffsetToCopy(B_ORIGIN);
404 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
405 BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
406 : fPicture->ColorSpace(), true);
407 if (bitmap == NULL || !bitmap->IsValid()) {
408 delete bitmap;
409 return NULL;
410 }
411
412 if (bitmap->Lock()) {
413 bitmap->AddChild(&view);
414 if (hasAlpha) {
415 view.SetHighColor(0, 0, 0, 0);
416 view.FillRect(rect);
417 view.SetDrawingMode(B_OP_ALPHA);
418 view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
419 view.SetHighColor(0, 0, 0, alpha);
420 }
421 view.DrawBitmap(fPicture, fPicture->Bounds().OffsetToCopy(B_ORIGIN),
422 rect, B_FILTER_BITMAP_BILINEAR);
423 view.Sync();
424 bitmap->RemoveChild(&view);
425 bitmap->Unlock();
426 }
427
428 return bitmap;
429 }
430
431
432 void
_BeginDrag(BPoint sourcePoint)433 PictureView::_BeginDrag(BPoint sourcePoint)
434 {
435 BBitmap* bitmap = _CopyPicture(128);
436 if (bitmap == NULL)
437 return;
438
439 // fill the drag message
440 BMessage drag(B_SIMPLE_DATA);
441 drag.AddInt32("be:actions", B_COPY_TARGET);
442 drag.AddInt32("be:actions", B_TRASH_TARGET);
443
444 // name the clip after person name, if any
445 BString name = B_TRANSLATE("%name% picture");
446 name.ReplaceFirst("%name%", Window() ? Window()->Title() :
447 B_TRANSLATE("Unnamed person"));
448 drag.AddString("be:clip_name", name.String());
449
450 BTranslatorRoster* roster = BTranslatorRoster::Default();
451 if (roster == NULL) {
452 delete bitmap;
453 return;
454 }
455
456 int32 infoCount;
457 translator_info* info;
458 BBitmapStream stream(bitmap);
459 if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
460 for (int32 i = 0; i < infoCount; i++) {
461 const translation_format* formats;
462 int32 count;
463 roster->GetOutputFormats(info[i].translator, &formats, &count);
464 for (int32 j = 0; j < count; j++) {
465 if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
466 // needed to send data in message
467 drag.AddString("be:types", formats[j].MIME);
468 // needed to pass data via file
469 drag.AddString("be:filetypes", formats[j].MIME);
470 drag.AddString("be:type_descriptions", formats[j].name);
471 }
472 }
473 }
474 }
475 stream.DetachBitmap(&bitmap);
476
477 // we also support "Passing Data via File" protocol
478 drag.AddString("be:types", B_FILE_MIME_TYPE);
479
480 sourcePoint -= fPictureRect.LeftTop();
481
482 SetMouseEventMask(B_POINTER_EVENTS);
483
484 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
485 bitmap = NULL;
486 }
487
488
489 void
_HandleDrop(BMessage * msg)490 PictureView::_HandleDrop(BMessage* msg)
491 {
492 entry_ref dirRef;
493 BString name, type;
494 bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
495 && msg->FindRef("directory", &dirRef) == B_OK
496 && msg->FindString("name", &name) == B_OK;
497
498 bool sendInMessage = !saveToFile
499 && msg->FindString("be:types", &type) == B_OK;
500
501 if (!sendInMessage && !saveToFile)
502 return;
503
504 BBitmap* bitmap = fPicture;
505 if (bitmap == NULL)
506 return;
507
508 BTranslatorRoster* roster = BTranslatorRoster::Default();
509 if (roster == NULL)
510 return;
511
512 BBitmapStream stream(bitmap);
513
514 // find translation format we're asked for
515 translator_info* outInfo;
516 int32 outNumInfo;
517 bool found = false;
518 translation_format format;
519
520 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
521 for (int32 i = 0; i < outNumInfo; i++) {
522 const translation_format* formats;
523 int32 formatCount;
524 roster->GetOutputFormats(outInfo[i].translator, &formats,
525 &formatCount);
526 for (int32 j = 0; j < formatCount; j++) {
527 if (strcmp(formats[j].MIME, type.String()) == 0) {
528 format = formats[j];
529 found = true;
530 break;
531 }
532 }
533 }
534 }
535
536 if (!found) {
537 stream.DetachBitmap(&bitmap);
538 return;
539 }
540
541 if (sendInMessage) {
542
543 BMessage reply(B_MIME_DATA);
544 BMallocIO memStream;
545 if (roster->Translate(&stream, NULL, NULL, &memStream,
546 format.type) == B_OK) {
547 reply.AddData(format.MIME, B_MIME_TYPE, memStream.Buffer(),
548 memStream.BufferLength());
549 msg->SendReply(&reply);
550 }
551
552 } else {
553
554 BDirectory dir(&dirRef);
555 BFile file(&dir, name.String(), B_WRITE_ONLY | B_CREATE_FILE
556 | B_ERASE_FILE);
557
558 if (file.InitCheck() == B_OK
559 && roster->Translate(&stream, NULL, NULL, &file,
560 format.type) == B_OK) {
561 BNodeInfo nodeInfo(&file);
562 if (nodeInfo.InitCheck() == B_OK)
563 nodeInfo.SetType(type.String());
564 } else {
565 BString text = B_TRANSLATE("The file '%name%' could not "
566 "be written.");
567 text.ReplaceFirst("%name%", name);
568 BAlert* alert = new BAlert(B_TRANSLATE("Error"), text.String(),
569 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
570 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
571 alert->Go();
572 }
573 }
574
575 // Detach, as we don't want our fPicture to be deleted
576 stream.DetachBitmap(&bitmap);
577 }
578
579
580 status_t
_LoadPicture(const entry_ref * ref)581 PictureView::_LoadPicture(const entry_ref* ref)
582 {
583 BFile file;
584 status_t status = file.SetTo(ref, B_READ_ONLY);
585 if (status != B_OK)
586 return status;
587
588 off_t fileSize;
589 status = file.GetSize(&fileSize);
590 if (status != B_OK)
591 return status;
592
593 // Check that we've at least some data to translate...
594 if (fileSize < 1)
595 return B_OK;
596
597 translator_info info;
598 memset(&info, 0, sizeof(translator_info));
599 BMessage ioExtension;
600
601 BTranslatorRoster* roster = BTranslatorRoster::Default();
602 if (roster == NULL)
603 return B_ERROR;
604
605 status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
606 B_TRANSLATOR_BITMAP);
607
608 BBitmapStream stream;
609
610 if (status == B_OK) {
611 status = roster->Translate(&file, &info, &ioExtension, &stream,
612 B_TRANSLATOR_BITMAP);
613 }
614 if (status != B_OK)
615 return status;
616
617 BBitmap* picture = NULL;
618 if (stream.DetachBitmap(&picture) != B_OK
619 || picture == NULL)
620 return B_ERROR;
621
622 // Remember image format so we could store using the same
623 fPictureMIMEType = info.MIME;
624 fPictureType = info.type;
625
626 _SetPicture(picture);
627 return B_OK;
628 }
629
630
631 void
_SetPicture(BBitmap * picture)632 PictureView::_SetPicture(BBitmap* picture)
633 {
634 if (fPicture != fOriginalPicture)
635 delete fPicture;
636
637 fPicture = picture;
638
639 if (picture == NULL) {
640 fPictureType = 0;
641 fPictureMIMEType = "";
642 }
643
644 Invalidate();
645 }
646
647
648