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 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 304 PictureView::WindowActivated(bool active) 305 { 306 BView::WindowActivated(active); 307 308 if (IsFocus()) 309 Invalidate(); 310 } 311 312 313 void 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 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 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 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* 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 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 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 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 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