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