xref: /haiku/src/apps/people/PictureView.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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 
56 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
57 	:
58 	BPopUpMenu(name, false, false),	fTarget(target)
59 {
60 	SetAsyncAutoDestruct(true);
61 }
62 
63 
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 
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 
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
129 PictureView::HasChanged()
130 {
131 	return fPicture != fOriginalPicture;
132 }
133 
134 
135 void
136 PictureView::Revert()
137 {
138 	if (!HasChanged())
139 		return;
140 
141 	_SetPicture(fOriginalPicture);
142 }
143 
144 
145 void
146 PictureView::Update()
147 {
148 	if (fOriginalPicture != fPicture) {
149 		delete fOriginalPicture;
150 		fOriginalPicture = fPicture;
151 	}
152 }
153 
154 
155 void
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*
170 PictureView::Bitmap()
171 {
172 	return fPicture;
173 }
174 
175 
176 uint32
177 PictureView::SuggestedType()
178 {
179 	return fPictureType;
180 }
181 
182 
183 const char*
184 PictureView::SuggestedMIMEType()
185 {
186 	if (fPictureMIMEType == "")
187 		return NULL;
188 
189 	return fPictureMIMEType.String();
190 }
191 
192 
193 void
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
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 		if (srcRect.Width() > srcRect.Height())
279 			size.height = srcRect.Height() * size.width / srcRect.Width();
280 		else
281 			size.width = srcRect.Width() * size.height / srcRect.Height();
282 
283 		fPictureRect = BLayoutUtils::AlignInFrame(frame, size,
284 			BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
285 
286 		SetDrawingMode(B_OP_ALPHA);
287 		if (picture == fDefaultPicture) {
288 			SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
289 			SetHighColor(0, 0, 0, 24);
290 		}
291 
292  		DrawBitmapAsync(picture, srcRect, fPictureRect,
293  			B_FILTER_BITMAP_BILINEAR);
294 
295 		SetDrawingMode(B_OP_OVER);
296 	}
297 }
298 
299 
300 void
301 PictureView::WindowActivated(bool active)
302 {
303 	BView::WindowActivated(active);
304 
305 	if (IsFocus())
306 		Invalidate();
307 }
308 
309 
310 void
311 PictureView::MakeFocus(bool focused)
312 {
313 	if (focused == IsFocus())
314 		return;
315 
316 	BView::MakeFocus(focused);
317 
318 	if (Window()) {
319 		fFocusChanging = true;
320 		Invalidate();
321 		Flush();
322 		fFocusChanging = false;
323 	}
324 }
325 
326 
327 void
328 PictureView::MouseDown(BPoint position)
329 {
330 	MakeFocus(true);
331 
332 	uint32 buttons = 0;
333 	if (Window() != NULL && Window()->CurrentMessage() != NULL)
334 		buttons = Window()->CurrentMessage()->FindInt32("buttons");
335 
336 	if (fPicture != NULL && fPictureRect.Contains(position)
337 		&& (buttons
338 			& (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
339 
340 		_BeginDrag(position);
341 
342 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON)
343 		_ShowPopUpMenu(ConvertToScreen(position));
344 }
345 
346 
347 void
348 PictureView::KeyDown(const char* bytes, int32 numBytes)
349 {
350 	if (numBytes != 1) {
351 		BView::KeyDown(bytes, numBytes);
352 		return;
353 	}
354 
355 	switch (*bytes) {
356 		case B_DELETE:
357 			_SetPicture(NULL);
358 			break;
359 
360 		default:
361 			BView::KeyDown(bytes, numBytes);
362 			break;
363 	}
364 }
365 
366 
367 // #pragma mark -
368 
369 
370 void
371 PictureView::_ShowPopUpMenu(BPoint screen)
372 {
373 	if (fShowingPopUpMenu)
374 		return;
375 
376 	PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
377 
378 	BMenuItem* item = new BMenuItem(B_TRANSLATE("Load image" B_UTF8_ELLIPSIS),
379 		new BMessage(kMsgLoadImage));
380 	menu->AddItem(item);
381 
382 	item = new BMenuItem(B_TRANSLATE("Remove image"), new BMessage(B_DELETE));
383 	item->SetEnabled(fPicture != NULL);
384 	menu->AddItem(item);
385 
386 	menu->SetTargetForItems(this);
387 	menu->Go(screen, true, true, true);
388 	fShowingPopUpMenu = true;
389 }
390 
391 
392 BBitmap*
393 PictureView::_CopyPicture(uint8 alpha)
394 {
395 	bool hasAlpha = alpha != 255;
396 
397 	if (!fPicture)
398 		return NULL;
399 
400 	BRect rect = fPictureRect.OffsetToCopy(B_ORIGIN);
401 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
402 	BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
403 		: fPicture->ColorSpace(), true);
404 	if (bitmap == NULL || !bitmap->IsValid()) {
405 		delete bitmap;
406 		return NULL;
407 	}
408 
409 	if (bitmap->Lock()) {
410 		bitmap->AddChild(&view);
411 		if (hasAlpha) {
412 			view.SetHighColor(0, 0, 0, 0);
413 			view.FillRect(rect);
414 			view.SetDrawingMode(B_OP_ALPHA);
415 			view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
416 			view.SetHighColor(0, 0, 0, alpha);
417 		}
418 		view.DrawBitmap(fPicture, fPicture->Bounds().OffsetToCopy(B_ORIGIN),
419 			rect, B_FILTER_BITMAP_BILINEAR);
420 		view.Sync();
421 		bitmap->RemoveChild(&view);
422 		bitmap->Unlock();
423 	}
424 
425 	return bitmap;
426 }
427 
428 
429 void
430 PictureView::_BeginDrag(BPoint sourcePoint)
431 {
432 	BBitmap* bitmap = _CopyPicture(128);
433 	if (bitmap == NULL)
434 		return;
435 
436 	// fill the drag message
437 	BMessage drag(B_SIMPLE_DATA);
438 	drag.AddInt32("be:actions", B_COPY_TARGET);
439 	drag.AddInt32("be:actions", B_TRASH_TARGET);
440 
441 	// name the clip after person name, if any
442 	BString name = B_TRANSLATE("%name% picture");
443 	name.ReplaceFirst("%name%", Window() ? Window()->Title() :
444 		B_TRANSLATE("Unnamed person"));
445 	drag.AddString("be:clip_name", name.String());
446 
447 	BTranslatorRoster* roster = BTranslatorRoster::Default();
448 	if (roster == NULL) {
449 		delete bitmap;
450 		return;
451 	}
452 
453 	int32 infoCount;
454 	translator_info* info;
455 	BBitmapStream stream(bitmap);
456 	if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
457 		for (int32 i = 0; i < infoCount; i++) {
458 			const translation_format* formats;
459 			int32 count;
460 			roster->GetOutputFormats(info[i].translator, &formats, &count);
461 			for (int32 j = 0; j < count; j++) {
462 				if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
463 					// needed to send data in message
464 					drag.AddString("be:types", formats[j].MIME);
465 					// needed to pass data via file
466 					drag.AddString("be:filetypes", formats[j].MIME);
467 					drag.AddString("be:type_descriptions", formats[j].name);
468 				}
469 			}
470 		}
471 	}
472 	stream.DetachBitmap(&bitmap);
473 
474 	// we also support "Passing Data via File" protocol
475 	drag.AddString("be:types", B_FILE_MIME_TYPE);
476 
477 	sourcePoint -= fPictureRect.LeftTop();
478 
479 	SetMouseEventMask(B_POINTER_EVENTS);
480 
481 	DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
482 	bitmap = NULL;
483 }
484 
485 
486 void
487 PictureView::_HandleDrop(BMessage* msg)
488 {
489 	entry_ref dirRef;
490 	BString name, type;
491 	bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
492 		&& msg->FindRef("directory", &dirRef) == B_OK
493 		&& msg->FindString("name", &name) == B_OK;
494 
495 	bool sendInMessage = !saveToFile
496 		&& msg->FindString("be:types", &type) == B_OK;
497 
498 	if (!sendInMessage && !saveToFile)
499 		return;
500 
501 	BBitmap* bitmap = fPicture;
502 	if (bitmap == NULL)
503 		return;
504 
505 	BTranslatorRoster* roster = BTranslatorRoster::Default();
506 	if (roster == NULL)
507 		return;
508 
509 	BBitmapStream stream(bitmap);
510 
511 	// find translation format we're asked for
512 	translator_info* outInfo;
513 	int32 outNumInfo;
514 	bool found = false;
515 	translation_format format;
516 
517 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
518 		for (int32 i = 0; i < outNumInfo; i++) {
519 			const translation_format* formats;
520 			int32 formatCount;
521 			roster->GetOutputFormats(outInfo[i].translator, &formats,
522 					&formatCount);
523 			for (int32 j = 0; j < formatCount; j++) {
524 				if (strcmp(formats[j].MIME, type.String()) == 0) {
525 					format = formats[j];
526 					found = true;
527 					break;
528 				}
529 			}
530 		}
531 	}
532 
533 	if (!found) {
534 		stream.DetachBitmap(&bitmap);
535 		return;
536 	}
537 
538 	if (sendInMessage) {
539 
540 		BMessage reply(B_MIME_DATA);
541 		BMallocIO memStream;
542 		if (roster->Translate(&stream, NULL, NULL, &memStream,
543 			format.type) == B_OK) {
544 			reply.AddData(format.MIME, B_MIME_TYPE, memStream.Buffer(),
545 				memStream.BufferLength());
546 			msg->SendReply(&reply);
547 		}
548 
549 	} else {
550 
551 		BDirectory dir(&dirRef);
552 		BFile file(&dir, name.String(), B_WRITE_ONLY | B_CREATE_FILE
553 			| B_ERASE_FILE);
554 
555 		if (file.InitCheck() == B_OK
556 			&& roster->Translate(&stream, NULL, NULL, &file,
557 				format.type) == B_OK) {
558 			BNodeInfo nodeInfo(&file);
559 			if (nodeInfo.InitCheck() == B_OK)
560 				nodeInfo.SetType(type.String());
561 		} else {
562 			BString text = B_TRANSLATE("The file '%name%' could not "
563 				"be written.");
564 			text.ReplaceFirst("%name%", name);
565 			BAlert* alert = new BAlert(B_TRANSLATE("Error"), text.String(),
566 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
567 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
568 			alert->Go();
569 		}
570 	}
571 
572 	// Detach, as we don't want our fPicture to be deleted
573 	stream.DetachBitmap(&bitmap);
574 }
575 
576 
577 status_t
578 PictureView::_LoadPicture(const entry_ref* ref)
579 {
580 	BFile file;
581 	status_t status = file.SetTo(ref, B_READ_ONLY);
582 	if (status != B_OK)
583 		return status;
584 
585 	off_t fileSize;
586 	status = file.GetSize(&fileSize);
587 	if (status != B_OK)
588 		return status;
589 
590 	// Check that we've at least some data to translate...
591 	if (fileSize < 1)
592 		return B_OK;
593 
594 	translator_info info;
595 	memset(&info, 0, sizeof(translator_info));
596 	BMessage ioExtension;
597 
598 	BTranslatorRoster* roster = BTranslatorRoster::Default();
599 	if (roster == NULL)
600 		return B_ERROR;
601 
602 	status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
603 		B_TRANSLATOR_BITMAP);
604 
605 	BBitmapStream stream;
606 
607 	if (status == B_OK) {
608 		status = roster->Translate(&file, &info, &ioExtension, &stream,
609 			B_TRANSLATOR_BITMAP);
610 	}
611 	if (status != B_OK)
612 		return status;
613 
614 	BBitmap* picture = NULL;
615 	if (stream.DetachBitmap(&picture) != B_OK
616 		|| picture == NULL)
617 		return B_ERROR;
618 
619 	// Remember image format so we could store using the same
620 	fPictureMIMEType = info.MIME;
621 	fPictureType = info.type;
622 
623 	_SetPicture(picture);
624 	return B_OK;
625 }
626 
627 
628 void
629 PictureView::_SetPicture(BBitmap* picture)
630 {
631 	if (fPicture != fOriginalPicture)
632 		delete fPicture;
633 
634 	fPicture = picture;
635 
636 	if (picture == NULL) {
637 		fPictureType = 0;
638 		fPictureMIMEType = "";
639 	}
640 
641 	Invalidate();
642 }
643 
644 
645