vfs: check userland buffers before reading them.
[haiku.git] / src / apps / people / PictureView.cpp
blob837bd7df9953823dc4829233aedb14f4f75b30ee
1 /*
2 * Copyright 2011, Haiku.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Philippe Houdoin
7 */
10 #include "PictureView.h"
12 #include <math.h>
13 #include <new>
14 #include <stdio.h>
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>
37 #include "PeopleApp.h" // for B_PERSON_MIMETYPE
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "People"
44 const uint32 kMsgPopUpMenuClosed = 'pmcl';
46 class PopUpMenu : public BPopUpMenu {
47 public:
48 PopUpMenu(const char* name, BMessenger target);
49 virtual ~PopUpMenu();
51 private:
52 BMessenger fTarget;
56 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
58 BPopUpMenu(name, false, false), fTarget(target)
60 SetAsyncAutoDestruct(true);
64 PopUpMenu::~PopUpMenu()
66 fTarget.SendMessage(kMsgPopUpMenuClosed);
70 // #pragma mark -
72 using std::nothrow;
75 const float kPictureMargin = 6.0;
77 PictureView::PictureView(float width, float height, const entry_ref* ref)
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))
88 SetViewColor(255, 255, 255);
90 SetToolTip(B_TRANSLATE(
91 "Drop an image here,\n"
92 "or use the contextual menu."));
94 BSize size(width + 2 * kPictureMargin, height + 2 * kPictureMargin);
95 SetExplicitMinSize(size);
96 SetExplicitMaxSize(size);
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;
113 Update(ref);
117 PictureView::~PictureView()
119 delete fDefaultPicture;
120 delete fPicture;
121 if (fOriginalPicture != fPicture)
122 delete fOriginalPicture;
124 delete fOpenPanel;
128 bool
129 PictureView::HasChanged()
131 return fPicture != fOriginalPicture;
135 void
136 PictureView::Revert()
138 if (!HasChanged())
139 return;
141 _SetPicture(fOriginalPicture);
145 void
146 PictureView::Update()
148 if (fOriginalPicture != fPicture) {
149 delete fOriginalPicture;
150 fOriginalPicture = fPicture;
155 void
156 PictureView::Update(const entry_ref* ref)
158 // Don't update when user has modified the picture
159 if (HasChanged())
160 return;
162 if (_LoadPicture(ref) == B_OK) {
163 delete fOriginalPicture;
164 fOriginalPicture = fPicture;
169 BBitmap*
170 PictureView::Bitmap()
172 return fPicture;
176 uint32
177 PictureView::SuggestedType()
179 return fPictureType;
183 const char*
184 PictureView::SuggestedMIMEType()
186 if (fPictureMIMEType == "")
187 return NULL;
189 return fPictureMIMEType.String();
193 void
194 PictureView::MessageReceived(BMessage* message)
196 switch (message->what) {
197 case B_REFS_RECEIVED:
198 case B_SIMPLE_DATA:
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;
209 case B_MIME_DATA:
210 // TODO
211 break;
213 case B_COPY_TARGET:
214 _HandleDrop(message);
215 break;
217 case B_PASTE:
219 if (be_clipboard->Lock() != B_OK)
220 break;
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);
229 be_clipboard->Unlock();
230 break;
233 case B_DELETE:
234 case B_TRASH_TARGET:
235 _SetPicture(NULL);
236 break;
238 case kMsgLoadImage:
239 fOpenPanel->SetTarget(BMessenger(this));
240 fOpenPanel->Show();
241 break;
243 case kMsgPopUpMenuClosed:
244 fShowingPopUpMenu = false;
245 break;
247 default:
248 BView::MessageReceived(message);
249 break;
254 void
255 PictureView::Draw(BRect updateRect)
257 BRect rect = Bounds();
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);
267 if (fFocusChanging) {
268 // focus frame is already redraw, stop here
269 return;
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();
283 fPictureRect = BLayoutUtils::AlignInFrame(frame, size,
284 BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
286 SetDrawingMode(B_OP_ALPHA);
287 if (picture == fDefaultPicture) {
288 SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
289 SetHighColor(0, 0, 0, 24);
292 DrawBitmapAsync(picture, srcRect, fPictureRect,
293 B_FILTER_BITMAP_BILINEAR);
295 SetDrawingMode(B_OP_OVER);
300 void
301 PictureView::WindowActivated(bool active)
303 BView::WindowActivated(active);
305 if (IsFocus())
306 Invalidate();
310 void
311 PictureView::MakeFocus(bool focused)
313 if (focused == IsFocus())
314 return;
316 BView::MakeFocus(focused);
318 if (Window()) {
319 fFocusChanging = true;
320 Invalidate();
321 Flush();
322 fFocusChanging = false;
327 void
328 PictureView::MouseDown(BPoint position)
330 MakeFocus(true);
332 uint32 buttons = 0;
333 if (Window() != NULL && Window()->CurrentMessage() != NULL)
334 buttons = Window()->CurrentMessage()->FindInt32("buttons");
336 if (fPicture != NULL && fPictureRect.Contains(position)
337 && (buttons
338 & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
340 _BeginDrag(position);
342 } else if (buttons == B_SECONDARY_MOUSE_BUTTON)
343 _ShowPopUpMenu(ConvertToScreen(position));
347 void
348 PictureView::KeyDown(const char* bytes, int32 numBytes)
350 if (numBytes != 1) {
351 BView::KeyDown(bytes, numBytes);
352 return;
355 switch (*bytes) {
356 case B_DELETE:
357 _SetPicture(NULL);
358 break;
360 default:
361 BView::KeyDown(bytes, numBytes);
362 break;
367 // #pragma mark -
370 void
371 PictureView::_ShowPopUpMenu(BPoint screen)
373 if (fShowingPopUpMenu)
374 return;
376 PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
378 BMenuItem* item = new BMenuItem(B_TRANSLATE("Load image" B_UTF8_ELLIPSIS),
379 new BMessage(kMsgLoadImage));
380 menu->AddItem(item);
382 item = new BMenuItem(B_TRANSLATE("Remove image"), new BMessage(B_DELETE));
383 item->SetEnabled(fPicture != NULL);
384 menu->AddItem(item);
386 menu->SetTargetForItems(this);
387 menu->Go(screen, true, true, true);
388 fShowingPopUpMenu = true;
392 BBitmap*
393 PictureView::_CopyPicture(uint8 alpha)
395 bool hasAlpha = alpha != 255;
397 if (!fPicture)
398 return NULL;
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;
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);
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();
425 return bitmap;
429 void
430 PictureView::_BeginDrag(BPoint sourcePoint)
432 BBitmap* bitmap = _CopyPicture(128);
433 if (bitmap == NULL)
434 return;
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);
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());
447 BTranslatorRoster* roster = BTranslatorRoster::Default();
448 if (roster == NULL) {
449 delete bitmap;
450 return;
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);
472 stream.DetachBitmap(&bitmap);
474 // we also support "Passing Data via File" protocol
475 drag.AddString("be:types", B_FILE_MIME_TYPE);
477 sourcePoint -= fPictureRect.LeftTop();
479 SetMouseEventMask(B_POINTER_EVENTS);
481 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
482 bitmap = NULL;
486 void
487 PictureView::_HandleDrop(BMessage* msg)
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;
495 bool sendInMessage = !saveToFile
496 && msg->FindString("be:types", &type) == B_OK;
498 if (!sendInMessage && !saveToFile)
499 return;
501 BBitmap* bitmap = fPicture;
502 if (bitmap == NULL)
503 return;
505 BTranslatorRoster* roster = BTranslatorRoster::Default();
506 if (roster == NULL)
507 return;
509 BBitmapStream stream(bitmap);
511 // find translation format we're asked for
512 translator_info* outInfo;
513 int32 outNumInfo;
514 bool found = false;
515 translation_format format;
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;
533 if (!found) {
534 stream.DetachBitmap(&bitmap);
535 return;
538 if (sendInMessage) {
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);
549 } else {
551 BDirectory dir(&dirRef);
552 BFile file(&dir, name.String(), B_WRITE_ONLY | B_CREATE_FILE
553 | B_ERASE_FILE);
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();
572 // Detach, as we don't want our fPicture to be deleted
573 stream.DetachBitmap(&bitmap);
577 status_t
578 PictureView::_LoadPicture(const entry_ref* ref)
580 BFile file;
581 status_t status = file.SetTo(ref, B_READ_ONLY);
582 if (status != B_OK)
583 return status;
585 off_t fileSize;
586 status = file.GetSize(&fileSize);
587 if (status != B_OK)
588 return status;
590 // Check that we've at least some data to translate...
591 if (fileSize < 1)
592 return B_OK;
594 translator_info info;
595 memset(&info, 0, sizeof(translator_info));
596 BMessage ioExtension;
598 BTranslatorRoster* roster = BTranslatorRoster::Default();
599 if (roster == NULL)
600 return B_ERROR;
602 status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
603 B_TRANSLATOR_BITMAP);
605 BBitmapStream stream;
607 if (status == B_OK) {
608 status = roster->Translate(&file, &info, &ioExtension, &stream,
609 B_TRANSLATOR_BITMAP);
611 if (status != B_OK)
612 return status;
614 BBitmap* picture = NULL;
615 if (stream.DetachBitmap(&picture) != B_OK
616 || picture == NULL)
617 return B_ERROR;
619 // Remember image format so we could store using the same
620 fPictureMIMEType = info.MIME;
621 fPictureType = info.type;
623 _SetPicture(picture);
624 return B_OK;
628 void
629 PictureView::_SetPicture(BBitmap* picture)
631 if (fPicture != fOriginalPicture)
632 delete fPicture;
634 fPicture = picture;
636 if (picture == NULL) {
637 fPictureType = 0;
638 fPictureMIMEType = "";
641 Invalidate();