xref: /haiku/src/apps/people/PictureView.cpp (revision db6fcb750a1afb5fdc752322972adf6044d3b4c4)
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