1 /* 2 * Copyright 2003-2010, Haiku, Inc. All Rights Reserved. 3 * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd. 4 * Copyright 2006 Bernd Korz. All Rights Reserved 5 * Distributed under the terms of the MIT License. 6 * 7 * Authors: 8 * Fernando Francisco de Oliveira 9 * Michael Wilber 10 * Michael Pfeiffer 11 * Ryan Leavengood 12 * yellowTAB GmbH 13 * Bernd Korz 14 * Stephan Aßmus <superstippi@gmx.de> 15 */ 16 17 18 #include "ShowImageView.h" 19 20 #include <math.h> 21 #include <new> 22 #include <stdio.h> 23 24 #include <Alert.h> 25 #include <Application.h> 26 #include <Bitmap.h> 27 #include <BitmapStream.h> 28 #include <Catalog.h> 29 #include <Clipboard.h> 30 #include <Debug.h> 31 #include <Directory.h> 32 #include <Entry.h> 33 #include <File.h> 34 #include <Locale.h> 35 #include <MenuBar.h> 36 #include <MenuItem.h> 37 #include <Message.h> 38 #include <NodeInfo.h> 39 #include <Path.h> 40 #include <PopUpMenu.h> 41 #include <Rect.h> 42 #include <Region.h> 43 #include <Roster.h> 44 #include <Screen.h> 45 #include <ScrollBar.h> 46 #include <StopWatch.h> 47 #include <SupportDefs.h> 48 #include <TranslatorRoster.h> 49 50 #include <tracker_private.h> 51 52 #include "ProgressWindow.h" 53 #include "ShowImageApp.h" 54 #include "ShowImageConstants.h" 55 #include "ShowImageWindow.h" 56 57 58 // TODO: Remove this and use Tracker's Command.h once it is moved into the private headers 59 namespace BPrivate { 60 const uint32 kMoveToTrash = 'Ttrs'; 61 } 62 63 using std::nothrow; 64 65 66 class PopUpMenu : public BPopUpMenu { 67 public: 68 PopUpMenu(const char* name, BMessenger target); 69 virtual ~PopUpMenu(); 70 71 private: 72 BMessenger fTarget; 73 }; 74 75 76 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation" 77 const rgb_color kBorderColor = { 0, 0, 0, 255 }; 78 79 enum ShowImageView::image_orientation 80 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations] = { 81 // rotate 90° 82 {k90, k180, k270, k0, k270V, k0V, k90V, k0H}, 83 // rotate -90° 84 {k270, k0, k90, k180, k90V, k0H, k270V, k0V}, 85 // mirror vertical 86 {k0H, k270V, k0V, k90V, k180, k270, k0, k90}, 87 // mirror horizontal 88 {k0V, k90V, k0H, k270V, k0, k90, k180, k270} 89 }; 90 91 const rgb_color kAlphaLow = (rgb_color){ 0xbb, 0xbb, 0xbb, 0xff }; 92 const rgb_color kAlphaHigh = (rgb_color){ 0xe0, 0xe0, 0xe0, 0xff }; 93 94 const uint32 kMsgPopUpMenuClosed = 'pmcl'; 95 96 97 static bool 98 entry_ref_is_file(const entry_ref *ref) 99 { 100 BEntry entry(ref, true); 101 if (entry.InitCheck() != B_OK) 102 return false; 103 104 return entry.IsFile(); 105 } 106 107 108 inline void 109 blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a) 110 { 111 d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8; 112 d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8; 113 d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8; 114 } 115 116 117 BBitmap* 118 compose_checker_background(const BBitmap* bitmap) 119 { 120 BBitmap* result = new (nothrow) BBitmap(bitmap); 121 if (result && !result->IsValid()) { 122 delete result; 123 result = NULL; 124 } 125 if (!result) 126 return NULL; 127 128 uint8* bits = (uint8*)result->Bits(); 129 uint32 bpr = result->BytesPerRow(); 130 uint32 width = result->Bounds().IntegerWidth() + 1; 131 uint32 height = result->Bounds().IntegerHeight() + 1; 132 133 for (uint32 i = 0; i < height; i++) { 134 uint8* p = bits; 135 for (uint32 x = 0; x < width; x++) { 136 uint8 alpha = p[3]; 137 if (alpha < 255) { 138 p[3] = 255; 139 alpha = 255 - alpha; 140 if (x % 10 >= 5) { 141 if (i % 10 >= 5) { 142 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha); 143 } else { 144 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha); 145 } 146 } else { 147 if (i % 10 >= 5) { 148 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha); 149 } else { 150 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha); 151 } 152 } 153 } 154 p += 4; 155 } 156 bits += bpr; 157 } 158 return result; 159 } 160 161 162 // #pragma mark - 163 164 165 PopUpMenu::PopUpMenu(const char* name, BMessenger target) 166 : 167 BPopUpMenu(name, false, false), 168 fTarget(target) 169 { 170 SetAsyncAutoDestruct(true); 171 } 172 173 174 PopUpMenu::~PopUpMenu() 175 { 176 fTarget.SendMessage(kMsgPopUpMenuClosed); 177 } 178 179 180 // #pragma mark - 181 182 183 ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode, 184 uint32 flags) 185 : 186 BView(rect, name, resizingMode, flags), 187 fDocumentIndex(1), 188 fDocumentCount(1), 189 fBitmap(NULL), 190 fDisplayBitmap(NULL), 191 fSelectionBitmap(NULL), 192 193 fZoom(1.0), 194 195 fScaleBilinear(true), 196 197 fBitmapLocationInView(0.0, 0.0), 198 199 fShrinkToBounds(true), 200 fStretchToBounds(false), 201 fFitToBoundsZoom(1.0), 202 fFullScreen(false), 203 fScrollingBitmap(false), 204 fCreatingSelection(false), 205 fFirstPoint(0.0, 0.0), 206 fSelectionMode(false), 207 fAnimateSelection(true), 208 fHasSelection(false), 209 fSlideShow(false), 210 fSlideShowDelay(3 * 10), // 3 seconds 211 fSlideShowCountDown(0), 212 fShowCaption(false), 213 fShowingPopUpMenu(false), 214 fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME), 215 fIsActiveWin(true), 216 fProgressWindow(NULL) 217 { 218 ShowImageSettings* settings; 219 settings = my_app->Settings(); 220 if (settings->Lock()) { 221 fShrinkToBounds = settings->GetBool("ShrinksToBounds", fShrinkToBounds); 222 fStretchToBounds = settings->GetBool("ZoomToBounds", fStretchToBounds); 223 fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay); 224 fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear); 225 settings->Unlock(); 226 } 227 228 SetViewColor(B_TRANSPARENT_COLOR); 229 SetHighColor(kBorderColor); 230 SetLowColor(0, 0, 0); 231 } 232 233 234 ShowImageView::~ShowImageView() 235 { 236 _DeleteBitmap(); 237 } 238 239 240 void 241 ShowImageView::_AnimateSelection(bool enabled) 242 { 243 fAnimateSelection = enabled; 244 } 245 246 247 void 248 ShowImageView::Pulse() 249 { 250 // animate marching ants 251 if (fHasSelection && fAnimateSelection && fIsActiveWin) { 252 fSelectionBox.Animate(); 253 fSelectionBox.Draw(this, Bounds()); 254 } 255 if (fSlideShow) { 256 fSlideShowCountDown --; 257 if (fSlideShowCountDown <= 0) { 258 fSlideShowCountDown = fSlideShowDelay; 259 if (!NextFile()) { 260 _FirstFile(); 261 } 262 } 263 } 264 265 // Hide cursor in full screen mode 266 if (fFullScreen && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) { 267 if (fHideCursorCountDown <= 0) 268 be_app->ObscureCursor(); 269 else 270 fHideCursorCountDown--; 271 } 272 } 273 274 275 bool 276 ShowImageView::_IsImage(const entry_ref *ref) 277 { 278 if (ref == NULL || !entry_ref_is_file(ref)) 279 return false; 280 281 BFile file(ref, B_READ_ONLY); 282 if (file.InitCheck() != B_OK) 283 return false; 284 285 BTranslatorRoster *roster = BTranslatorRoster::Default(); 286 if (!roster) 287 return false; 288 289 BMessage ioExtension; 290 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 291 return false; 292 293 translator_info info; 294 memset(&info, 0, sizeof(translator_info)); 295 if (roster->Identify(&file, &ioExtension, &info, 0, NULL, 296 B_TRANSLATOR_BITMAP) != B_OK) 297 return false; 298 299 return true; 300 } 301 302 303 void 304 ShowImageView::SetTrackerMessenger(const BMessenger& trackerMessenger) 305 { 306 fTrackerMessenger = trackerMessenger; 307 } 308 309 310 void 311 ShowImageView::_SendMessageToWindow(BMessage *message) 312 { 313 BMessenger msgr(Window()); 314 msgr.SendMessage(message); 315 } 316 317 318 void 319 ShowImageView::_SendMessageToWindow(uint32 code) 320 { 321 BMessage message(code); 322 _SendMessageToWindow(&message); 323 } 324 325 326 //! send message to parent about new image 327 void 328 ShowImageView::_Notify() 329 { 330 BMessage msg(MSG_UPDATE_STATUS); 331 332 msg.AddString("status", fImageType.String()); 333 msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1); 334 msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1); 335 336 msg.AddInt32("colors", fBitmap->ColorSpace()); 337 _SendMessageToWindow(&msg); 338 339 FixupScrollBars(); 340 Invalidate(); 341 } 342 343 344 void 345 ShowImageView::_UpdateStatusText() 346 { 347 BMessage msg(MSG_UPDATE_STATUS_TEXT); 348 BString status_to_send = fImageType; 349 350 if (fHasSelection) { 351 char size[50]; 352 sprintf(size, " (%.0fx%.0f)", 353 fSelectionBox.Bounds().Width() + 1.0, 354 fSelectionBox.Bounds().Height() + 1.0); 355 status_to_send << size; 356 } 357 358 msg.AddString("status", status_to_send.String()); 359 _SendMessageToWindow(&msg); 360 } 361 362 363 void 364 ShowImageView::_DeleteBitmap() 365 { 366 _DeleteSelectionBitmap(); 367 368 if (fDisplayBitmap != fBitmap) 369 delete fDisplayBitmap; 370 fDisplayBitmap = NULL; 371 372 delete fBitmap; 373 fBitmap = NULL; 374 } 375 376 377 void 378 ShowImageView::_DeleteSelectionBitmap() 379 { 380 delete fSelectionBitmap; 381 fSelectionBitmap = NULL; 382 } 383 384 385 status_t 386 ShowImageView::SetImage(const entry_ref* ref) 387 { 388 // If no file was specified, load the specified page of 389 // the current file. 390 if (ref == NULL) 391 ref = &fCurrentRef; 392 393 BTranslatorRoster *roster = BTranslatorRoster::Default(); 394 if (!roster) 395 return B_ERROR; 396 397 if (!entry_ref_is_file(ref)) 398 return B_ERROR; 399 400 BFile file(ref, B_READ_ONLY); 401 translator_info info; 402 memset(&info, 0, sizeof(translator_info)); 403 BMessage ioExtension; 404 if (ref != &fCurrentRef) { 405 // if new image, reset to first document 406 fDocumentIndex = 1; 407 } 408 409 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 410 return B_ERROR; 411 412 BMessage progress(kMsgProgressStatusUpdate); 413 if (ioExtension.AddMessenger("/progressMonitor", fProgressWindow) == B_OK 414 && ioExtension.AddMessage("/progressMessage", &progress) == B_OK) 415 fProgressWindow->Start(); 416 417 // Translate image data and create a new ShowImage window 418 419 BBitmapStream outstream; 420 421 status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL, 422 B_TRANSLATOR_BITMAP); 423 if (status == B_OK) { 424 status = roster->Translate(&file, &info, &ioExtension, &outstream, 425 B_TRANSLATOR_BITMAP); 426 } 427 428 fProgressWindow->Stop(); 429 430 if (status != B_OK) 431 return status; 432 433 BBitmap *newBitmap = NULL; 434 if (outstream.DetachBitmap(&newBitmap) != B_OK) 435 return B_ERROR; 436 437 // Now that I've successfully loaded the new bitmap, 438 // I can be sure it is safe to delete the old one, 439 // and clear everything 440 fUndo.Clear(); 441 _SetHasSelection(false); 442 fCreatingSelection = false; 443 _DeleteBitmap(); 444 fBitmap = newBitmap; 445 fCurrentRef = *ref; 446 447 // prepare the display bitmap 448 if (fBitmap->ColorSpace() == B_RGBA32) 449 fDisplayBitmap = compose_checker_background(fBitmap); 450 451 if (!fDisplayBitmap) 452 fDisplayBitmap = fBitmap; 453 454 // restore orientation 455 int32 orientation; 456 fImageOrientation = k0; 457 if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, 458 &orientation, sizeof(orientation)) == sizeof(orientation)) { 459 orientation &= 255; 460 switch (orientation) { 461 case k0: 462 break; 463 case k90: 464 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 465 break; 466 case k180: 467 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 468 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 469 break; 470 case k270: 471 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true); 472 break; 473 case k0V: 474 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 475 break; 476 case k90V: 477 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 478 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 479 break; 480 case k0H: 481 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true); 482 break; 483 case k270V: 484 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true); 485 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 486 break; 487 } 488 } 489 490 // get the number of documents (pages) if it has been supplied 491 int32 documentCount = 0; 492 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK 493 && documentCount > 0) 494 fDocumentCount = documentCount; 495 else 496 fDocumentCount = 1; 497 498 fImageType = info.name; 499 fImageMime = info.MIME; 500 501 GetPath(&fCaption); 502 if (fDocumentCount > 1) 503 fCaption << ", " << fDocumentIndex << "/" << fDocumentCount; 504 505 fCaption << ", " << fImageType; 506 507 fFitToBoundsZoom = _FitToBoundsZoom(); 508 ResetZoom(); 509 510 be_roster->AddToRecentDocuments(&fCurrentRef, kApplicationSignature); 511 512 _Notify(); 513 return B_OK; 514 } 515 516 517 BPoint 518 ShowImageView::ImageToView(BPoint p) const 519 { 520 p.x = floorf(fZoom * p.x + fBitmapLocationInView.x); 521 p.y = floorf(fZoom * p.y + fBitmapLocationInView.y); 522 return p; 523 } 524 525 526 BPoint 527 ShowImageView::ViewToImage(BPoint p) const 528 { 529 p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom); 530 p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom); 531 return p; 532 } 533 534 535 BRect 536 ShowImageView::ImageToView(BRect r) const 537 { 538 BPoint leftTop(ImageToView(BPoint(r.left, r.top))); 539 BPoint rightBottom(r.right, r.bottom); 540 rightBottom += BPoint(1, 1); 541 rightBottom = ImageToView(rightBottom); 542 rightBottom -= BPoint(1, 1); 543 return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); 544 } 545 546 547 void 548 ShowImageView::ConstrainToImage(BPoint& point) const 549 { 550 point.ConstrainTo(fBitmap->Bounds()); 551 } 552 553 554 void 555 ShowImageView::ConstrainToImage(BRect& rect) const 556 { 557 rect = rect & fBitmap->Bounds(); 558 } 559 560 561 void 562 ShowImageView::SetShowCaption(bool show) 563 { 564 if (fShowCaption != show) { 565 fShowCaption = show; 566 _UpdateCaption(); 567 } 568 } 569 570 571 void 572 ShowImageView::SetShrinkToBounds(bool enable) 573 { 574 if (fShrinkToBounds != enable) { 575 _SettingsSetBool("ShrinksToBounds", enable); 576 fShrinkToBounds = enable; 577 if (enable) 578 SetZoom(fFitToBoundsZoom); 579 } 580 } 581 582 583 void 584 ShowImageView::SetStretchToBounds(bool enable) 585 { 586 if (fStretchToBounds != enable) { 587 _SettingsSetBool("ZoomToBounds", enable); 588 fStretchToBounds = enable; 589 if (enable) 590 SetZoom(fFitToBoundsZoom); 591 } 592 } 593 594 595 void 596 ShowImageView::SetFullScreen(bool fullScreen) 597 { 598 fFullScreen = fullScreen; 599 } 600 601 602 BBitmap * 603 ShowImageView::GetBitmap() 604 { 605 return fBitmap; 606 } 607 608 609 void 610 ShowImageView::GetName(BString* outName) 611 { 612 BEntry entry(&fCurrentRef); 613 char name[B_FILE_NAME_LENGTH]; 614 if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK) 615 outName->SetTo(""); 616 else 617 outName->SetTo(name); 618 } 619 620 621 void 622 ShowImageView::GetPath(BString *outPath) 623 { 624 BEntry entry(&fCurrentRef); 625 BPath path; 626 if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK) 627 outPath->SetTo(""); 628 else 629 outPath->SetTo(path.Path()); 630 } 631 632 633 void 634 ShowImageView::SetScaleBilinear(bool enabled) 635 { 636 if (fScaleBilinear != enabled) { 637 _SettingsSetBool("ScaleBilinear", enabled); 638 fScaleBilinear = enabled; 639 Invalidate(); 640 } 641 } 642 643 644 void 645 ShowImageView::AttachedToWindow() 646 { 647 ResetZoom(); 648 fUndo.SetWindow(Window()); 649 FixupScrollBars(); 650 651 fProgressWindow = new ProgressWindow(Window()); 652 } 653 654 655 void 656 ShowImageView::DetachedFromWindow() 657 { 658 fProgressWindow->Lock(); 659 fProgressWindow->Quit(); 660 } 661 662 663 bool 664 ShowImageView::_ShouldShrink() const 665 { 666 return fShrinkToBounds && fBitmap->Bounds().Width() > Bounds().Width() 667 && fBitmap->Bounds().Height() > Bounds().Height(); 668 } 669 670 671 bool 672 ShowImageView::_ShouldStretch() const 673 { 674 return fStretchToBounds && fBitmap->Bounds().Width() < Bounds().Width() 675 && fBitmap->Bounds().Height() < Bounds().Height(); 676 } 677 678 679 float 680 ShowImageView::_FitToBoundsZoom() const 681 { 682 // the width/height of the bitmap (in pixels) 683 float bitmapWidth = fBitmap->Bounds().Width() + 1; 684 float bitmapHeight = fBitmap->Bounds().Height() + 1; 685 686 // the available width/height for layouting the bitmap (in pixels) 687 float width = Bounds().Width() + 1; 688 float height = Bounds().Height() + 1; 689 690 float zoom = width / bitmapWidth; 691 692 if (zoom * bitmapHeight <= height) 693 return zoom; 694 695 return height / bitmapHeight; 696 } 697 698 699 BRect 700 ShowImageView::_AlignBitmap() 701 { 702 BRect rect(fBitmap->Bounds()); 703 704 // the width/height of the bitmap (in pixels) 705 float bitmapWidth = rect.Width() + 1; 706 float bitmapHeight = rect.Height() + 1; 707 708 // the available width/height for layouting the bitmap (in pixels) 709 float width = Bounds().Width() + 1; 710 float height = Bounds().Height() + 1; 711 712 if (width == 0 || height == 0) 713 return rect; 714 715 // zoom image 716 rect.right = floorf(bitmapWidth * fZoom) - 1; 717 rect.bottom = floorf(bitmapHeight * fZoom) - 1; 718 719 // update the bitmap size after the zoom 720 bitmapWidth = rect.Width() + 1.0; 721 bitmapHeight = rect.Height() + 1.0; 722 723 // always align in the center if the bitmap is smaller than the window 724 if (width > bitmapWidth) 725 rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0); 726 727 if (height > bitmapHeight) 728 rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0)); 729 730 return rect; 731 } 732 733 734 void 735 ShowImageView::_DrawBackground(BRect border) 736 { 737 BRect bounds(Bounds()); 738 // top 739 FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW); 740 // left 741 FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW); 742 // right 743 FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW); 744 // bottom 745 FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW); 746 } 747 748 749 void 750 ShowImageView::_LayoutCaption(BFont &font, BPoint &pos, BRect &rect) 751 { 752 font_height fontHeight; 753 float width, height; 754 BRect bounds(Bounds()); 755 font = be_plain_font; 756 width = font.StringWidth(fCaption.String()); 757 font.GetHeight(&fontHeight); 758 height = fontHeight.ascent + fontHeight.descent; 759 // center text horizontally 760 pos.x = (bounds.left + bounds.right - width) / 2; 761 // flush bottom 762 pos.y = bounds.bottom - fontHeight.descent - 7; 763 764 // background rectangle 765 rect.Set(0, 0, width + 4, height + 4); 766 rect.OffsetTo(pos); 767 rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border 768 } 769 770 771 void 772 ShowImageView::_DrawCaption() 773 { 774 BFont font; 775 BPoint position; 776 BRect rect; 777 _LayoutCaption(font, position, rect); 778 779 PushState(); 780 781 // draw background 782 SetDrawingMode(B_OP_ALPHA); 783 SetHighColor(255, 255, 255, 160); 784 FillRect(rect); 785 786 // draw text 787 SetDrawingMode(B_OP_OVER); 788 SetFont(&font); 789 SetLowColor(B_TRANSPARENT_COLOR); 790 SetHighColor(0, 0, 0); 791 DrawString(fCaption.String(), position); 792 793 PopState(); 794 } 795 796 797 void 798 ShowImageView::_UpdateCaption() 799 { 800 BFont font; 801 BPoint pos; 802 BRect rect; 803 _LayoutCaption(font, pos, rect); 804 805 // draw over portion of image where caption is located 806 BRegion clip(rect); 807 PushState(); 808 ConstrainClippingRegion(&clip); 809 Draw(rect); 810 PopState(); 811 } 812 813 814 void 815 ShowImageView::_DrawImage(BRect rect) 816 { 817 // TODO: fix composing of fBitmap with other bitmaps 818 // with regard to alpha channel 819 if (!fDisplayBitmap) 820 fDisplayBitmap = fBitmap; 821 822 uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0; 823 DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options); 824 } 825 826 827 void 828 ShowImageView::Draw(BRect updateRect) 829 { 830 if (fBitmap == NULL) 831 return; 832 833 if (IsPrinting()) { 834 DrawBitmap(fBitmap); 835 return; 836 } 837 838 BRect rect = _AlignBitmap(); 839 fBitmapLocationInView.x = floorf(rect.left); 840 fBitmapLocationInView.y = floorf(rect.top); 841 842 _DrawBackground(rect); 843 _DrawImage(rect); 844 845 if (fShowCaption) 846 _DrawCaption(); 847 848 if (fHasSelection) { 849 if (fSelectionBitmap != NULL) { 850 BRect srcRect; 851 BRect dstRect; 852 _GetSelectionMergeRects(srcRect, dstRect); 853 dstRect = ImageToView(dstRect); 854 DrawBitmap(fSelectionBitmap, srcRect, dstRect); 855 } 856 fSelectionBox.Draw(this, updateRect); 857 } 858 } 859 860 861 void 862 ShowImageView::FrameResized(float /*width*/, float /*height*/) 863 { 864 fFitToBoundsZoom = _FitToBoundsZoom(); 865 SetZoom(_ShouldStretch() ? fFitToBoundsZoom : fZoom); 866 } 867 868 869 BBitmap* 870 ShowImageView::_CopySelection(uchar alpha, bool imageSize) 871 { 872 bool hasAlpha = alpha != 255; 873 874 if (!fHasSelection) 875 return NULL; 876 877 BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN); 878 if (!imageSize) { 879 // scale image to view size 880 rect.right = floorf((rect.right + 1.0) * fZoom - 1.0); 881 rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0); 882 } 883 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 884 BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32 885 : fBitmap->ColorSpace(), true); 886 if (bitmap == NULL || !bitmap->IsValid()) { 887 delete bitmap; 888 return NULL; 889 } 890 891 if (bitmap->Lock()) { 892 bitmap->AddChild(&view); 893 #ifdef __HAIKU__ 894 // On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS. 895 // Don't know if it's better to fix it or not (stippi). 896 if (hasAlpha) { 897 view.SetHighColor(0, 0, 0, 0); 898 view.FillRect(view.Bounds()); 899 view.SetDrawingMode(B_OP_ALPHA); 900 view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 901 view.SetHighColor(0, 0, 0, alpha); 902 } 903 if (fSelectionBitmap) { 904 view.DrawBitmap(fSelectionBitmap, 905 fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect); 906 } else 907 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 908 #else 909 if (fSelectionBitmap) { 910 view.DrawBitmap(fSelectionBitmap, 911 fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect); 912 } else 913 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 914 if (hasAlpha) { 915 view.SetDrawingMode(B_OP_SUBTRACT); 916 view.SetHighColor(0, 0, 0, 255 - alpha); 917 view.FillRect(rect, B_SOLID_HIGH); 918 } 919 #endif 920 view.Sync(); 921 bitmap->RemoveChild(&view); 922 bitmap->Unlock(); 923 } 924 925 return bitmap; 926 } 927 928 929 bool 930 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap) 931 { 932 BTranslatorRoster *roster = BTranslatorRoster::Default(); 933 if (roster == NULL) 934 return false; 935 936 // add the current image mime first, will make it the preferred format on 937 // left mouse drag 938 msg->AddString("be:types", fImageMime); 939 msg->AddString("be:filetypes", fImageMime); 940 msg->AddString("be:type_descriptions", fImageType); 941 942 bool foundOther = false; 943 bool foundCurrent = false; 944 945 int32 infoCount; 946 translator_info* info; 947 BBitmapStream stream(bitmap); 948 if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) { 949 for (int32 i = 0; i < infoCount; i++) { 950 const translation_format* formats; 951 int32 count; 952 roster->GetOutputFormats(info[i].translator, &formats, &count); 953 for (int32 j = 0; j < count; j++) { 954 if (fImageMime == formats[j].MIME) { 955 foundCurrent = true; 956 } else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) { 957 foundOther = true; 958 // needed to send data in message 959 msg->AddString("be:types", formats[j].MIME); 960 // needed to pass data via file 961 msg->AddString("be:filetypes", formats[j].MIME); 962 msg->AddString("be:type_descriptions", formats[j].name); 963 } 964 } 965 } 966 } 967 stream.DetachBitmap(&bitmap); 968 969 if (!foundCurrent) { 970 msg->RemoveData("be:types", 0); 971 msg->RemoveData("be:filetypes", 0); 972 msg->RemoveData("be:type_descriptions", 0); 973 } 974 975 return foundOther || foundCurrent; 976 } 977 978 979 void 980 ShowImageView::_BeginDrag(BPoint sourcePoint) 981 { 982 BBitmap* bitmap = _CopySelection(128, false); 983 if (bitmap == NULL) 984 return; 985 986 SetMouseEventMask(B_POINTER_EVENTS); 987 988 // fill the drag message 989 BMessage drag(B_SIMPLE_DATA); 990 drag.AddInt32("be:actions", B_COPY_TARGET); 991 drag.AddString("be:clip_name", "Bitmap Clip"); 992 // ShowImage specific fields 993 drag.AddPoint("be:_source_point", sourcePoint); 994 drag.AddRect("be:_frame", fSelectionBox.Bounds()); 995 if (_AddSupportedTypes(&drag, bitmap)) { 996 // we also support "Passing Data via File" protocol 997 drag.AddString("be:types", B_FILE_MIME_TYPE); 998 // avoid flickering of dragged bitmap caused by drawing into the window 999 _AnimateSelection(false); 1000 // only use a transparent bitmap on selections less than 400x400 1001 // (taking into account zooming) 1002 BRect selectionRect = fSelectionBox.Bounds(); 1003 if (selectionRect.Width() * fZoom < 400.0 1004 && selectionRect.Height() * fZoom < 400.0) { 1005 sourcePoint -= selectionRect.LeftTop(); 1006 sourcePoint.x *= fZoom; 1007 sourcePoint.y *= fZoom; 1008 // DragMessage takes ownership of bitmap 1009 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint); 1010 bitmap = NULL; 1011 } else { 1012 delete bitmap; 1013 // Offset and scale the rect 1014 BRect rect(selectionRect); 1015 rect = ImageToView(rect); 1016 rect.InsetBy(-1, -1); 1017 DragMessage(&drag, rect); 1018 } 1019 } 1020 } 1021 1022 1023 bool 1024 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type, 1025 translation_format* format) 1026 { 1027 bool found = false; 1028 1029 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1030 if (roster == NULL) 1031 return false; 1032 1033 BBitmapStream stream(bitmap); 1034 1035 translator_info *outInfo; 1036 int32 outNumInfo; 1037 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 1038 for (int32 i = 0; i < outNumInfo; i++) { 1039 const translation_format *fmts; 1040 int32 num_fmts; 1041 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 1042 for (int32 j = 0; j < num_fmts; j++) { 1043 if (strcmp(fmts[j].MIME, type) == 0) { 1044 *format = fmts[j]; 1045 found = true; 1046 break; 1047 } 1048 } 1049 } 1050 } 1051 stream.DetachBitmap(&bitmap); 1052 return found; 1053 } 1054 1055 1056 #undef B_TRANSLATE_CONTEXT 1057 #define B_TRANSLATE_CONTEXT "SaveToFile" 1058 1059 1060 void 1061 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, 1062 const translation_format* format) 1063 { 1064 if (bitmap == NULL) { 1065 // If no bitmap is supplied, write out the whole image 1066 bitmap = fBitmap; 1067 } 1068 1069 BBitmapStream stream(bitmap); 1070 1071 bool loop = true; 1072 while (loop) { 1073 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1074 if (!roster) 1075 break; 1076 // write data 1077 BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 1078 if (file.InitCheck() != B_OK) 1079 break; 1080 if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK) 1081 break; 1082 // set mime type 1083 BNodeInfo info(&file); 1084 if (info.InitCheck() == B_OK) 1085 info.SetType(format->MIME); 1086 1087 loop = false; 1088 // break out of loop gracefully (indicates no errors) 1089 } 1090 if (loop) { 1091 // If loop terminated because of a break, there was an error 1092 char buffer[512]; 1093 snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not " 1094 "be written."), name); 1095 BAlert *palert = new BAlert("", buffer, B_TRANSLATE("OK")); 1096 palert->Go(); 1097 } 1098 1099 stream.DetachBitmap(&bitmap); 1100 // Don't allow the bitmap to be deleted, this is 1101 // especially important when using fBitmap as the bitmap 1102 } 1103 1104 1105 void 1106 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format) 1107 { 1108 BMessage reply(B_MIME_DATA); 1109 BBitmapStream stream(bitmap); // destructor deletes bitmap 1110 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1111 BMallocIO memStream; 1112 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) { 1113 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength()); 1114 msg->SendReply(&reply); 1115 } 1116 } 1117 1118 1119 void 1120 ShowImageView::_HandleDrop(BMessage* msg) 1121 { 1122 entry_ref dirRef; 1123 BString name, type; 1124 bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK 1125 && msg->FindRef("directory", &dirRef) == B_OK 1126 && msg->FindString("name", &name) == B_OK; 1127 1128 bool sendInMessage = !saveToFile 1129 && msg->FindString("be:types", &type) == B_OK; 1130 1131 BBitmap* bitmap = _CopySelection(); 1132 if (bitmap == NULL) 1133 return; 1134 1135 translation_format format; 1136 if (!_OutputFormatForType(bitmap, type.String(), &format)) { 1137 delete bitmap; 1138 return; 1139 } 1140 1141 if (saveToFile) { 1142 BDirectory dir(&dirRef); 1143 SaveToFile(&dir, name.String(), bitmap, &format); 1144 delete bitmap; 1145 } else if (sendInMessage) { 1146 _SendInMessage(msg, bitmap, &format); 1147 } else { 1148 delete bitmap; 1149 } 1150 } 1151 1152 1153 void 1154 ShowImageView::_ScrollBitmap(BPoint point) 1155 { 1156 point = ConvertToScreen(point); 1157 BPoint delta = fFirstPoint - point; 1158 fFirstPoint = point; 1159 _ScrollRestrictedBy(delta.x, delta.y); 1160 } 1161 1162 1163 void 1164 ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect, 1165 BRect& dstRect) 1166 { 1167 // Constrain dstRect to target image size and apply the same edge offsets 1168 // to the srcRect. 1169 1170 dstRect = selection; 1171 1172 BRect clippedDstRect(dstRect); 1173 ConstrainToImage(clippedDstRect); 1174 1175 srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN); 1176 1177 srcRect.left += clippedDstRect.left - dstRect.left; 1178 srcRect.top += clippedDstRect.top - dstRect.top; 1179 srcRect.right += clippedDstRect.right - dstRect.right; 1180 srcRect.bottom += clippedDstRect.bottom - dstRect.bottom; 1181 1182 dstRect = clippedDstRect; 1183 } 1184 1185 1186 void 1187 ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect) 1188 { 1189 _GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect); 1190 } 1191 1192 1193 void 1194 ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection) 1195 { 1196 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1197 BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), 1198 fBitmap->ColorSpace(), true); 1199 if (bitmap == NULL || !bitmap->IsValid()) { 1200 delete bitmap; 1201 return; 1202 } 1203 1204 if (bitmap->Lock()) { 1205 bitmap->AddChild(&view); 1206 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1207 BRect srcRect; 1208 BRect dstRect; 1209 _GetMergeRects(merge, selection, srcRect, dstRect); 1210 view.DrawBitmap(merge, srcRect, dstRect); 1211 1212 view.Sync(); 1213 bitmap->RemoveChild(&view); 1214 bitmap->Unlock(); 1215 1216 _DeleteBitmap(); 1217 fBitmap = bitmap; 1218 1219 _SendMessageToWindow(MSG_MODIFIED); 1220 } else 1221 delete bitmap; 1222 } 1223 1224 1225 void 1226 ShowImageView::MouseDown(BPoint position) 1227 { 1228 MakeFocus(true); 1229 1230 BPoint point = ViewToImage(position); 1231 uint32 buttons = 0; 1232 if (Window() != NULL && Window()->CurrentMessage() != NULL) 1233 buttons = Window()->CurrentMessage()->FindInt32("buttons"); 1234 1235 if (fHasSelection && fSelectionBox.Bounds().Contains(point) 1236 && (buttons 1237 & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) { 1238 if (!fSelectionBitmap) 1239 fSelectionBitmap = _CopySelection(); 1240 1241 _BeginDrag(point); 1242 } else if (buttons == B_PRIMARY_MOUSE_BUTTON 1243 && (fSelectionMode 1244 || (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) { 1245 // begin new selection 1246 _SetHasSelection(true); 1247 fCreatingSelection = true; 1248 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 1249 ConstrainToImage(point); 1250 fFirstPoint = point; 1251 fCopyFromRect.Set(point.x, point.y, point.x, point.y); 1252 fSelectionBox.SetBounds(this, fCopyFromRect); 1253 Invalidate(); 1254 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1255 _ShowPopUpMenu(ConvertToScreen(position)); 1256 } else if (buttons == B_PRIMARY_MOUSE_BUTTON 1257 || buttons == B_TERTIARY_MOUSE_BUTTON) { 1258 // move image in window 1259 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 1260 fScrollingBitmap = true; 1261 fFirstPoint = ConvertToScreen(position); 1262 } 1263 } 1264 1265 1266 void 1267 ShowImageView::_UpdateSelectionRect(BPoint point, bool final) 1268 { 1269 BRect oldSelection = fCopyFromRect; 1270 point = ViewToImage(point); 1271 ConstrainToImage(point); 1272 fCopyFromRect.left = min_c(fFirstPoint.x, point.x); 1273 fCopyFromRect.right = max_c(fFirstPoint.x, point.x); 1274 fCopyFromRect.top = min_c(fFirstPoint.y, point.y); 1275 fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y); 1276 fSelectionBox.SetBounds(this, fCopyFromRect); 1277 1278 if (final) { 1279 // selection must be at least 2 pixels wide or 2 pixels tall 1280 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0) 1281 _SetHasSelection(false); 1282 } else 1283 _UpdateStatusText(); 1284 1285 if (oldSelection != fCopyFromRect || !fHasSelection) { 1286 BRect updateRect; 1287 updateRect = oldSelection | fCopyFromRect; 1288 updateRect = ImageToView(updateRect); 1289 updateRect.InsetBy(-1, -1); 1290 Invalidate(updateRect); 1291 } 1292 } 1293 1294 1295 void 1296 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message) 1297 { 1298 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 1299 if (fCreatingSelection) 1300 _UpdateSelectionRect(point, false); 1301 else if (fScrollingBitmap) 1302 _ScrollBitmap(point); 1303 } 1304 1305 1306 void 1307 ShowImageView::MouseUp(BPoint point) 1308 { 1309 if (fCreatingSelection) { 1310 _UpdateSelectionRect(point, true); 1311 fCreatingSelection = false; 1312 } else if (fScrollingBitmap) { 1313 _ScrollBitmap(point); 1314 fScrollingBitmap = false; 1315 } 1316 _AnimateSelection(true); 1317 } 1318 1319 1320 float 1321 ShowImageView::_LimitToRange(float v, orientation o, bool absolute) 1322 { 1323 BScrollBar* psb = ScrollBar(o); 1324 if (psb) { 1325 float min, max, pos; 1326 pos = v; 1327 if (!absolute) 1328 pos += psb->Value(); 1329 1330 psb->GetRange(&min, &max); 1331 if (pos < min) 1332 pos = min; 1333 else if (pos > max) 1334 pos = max; 1335 1336 v = pos; 1337 if (!absolute) 1338 v -= psb->Value(); 1339 } 1340 return v; 1341 } 1342 1343 1344 void 1345 ShowImageView::_ScrollRestricted(float x, float y, bool absolute) 1346 { 1347 if (x != 0) 1348 x = _LimitToRange(x, B_HORIZONTAL, absolute); 1349 1350 if (y != 0) 1351 y = _LimitToRange(y, B_VERTICAL, absolute); 1352 1353 // hide the caption when using mouse wheel 1354 // in full screen mode 1355 // to prevent the caption from dirtying up the image 1356 // during scrolling. 1357 bool caption = fShowCaption; 1358 if (caption) { 1359 fShowCaption = false; 1360 _UpdateCaption(); 1361 } 1362 1363 ScrollBy(x, y); 1364 1365 if (caption) { 1366 // show the caption again 1367 fShowCaption = true; 1368 _UpdateCaption(); 1369 } 1370 } 1371 1372 1373 // XXX method is not unused 1374 void 1375 ShowImageView::_ScrollRestrictedTo(float x, float y) 1376 { 1377 _ScrollRestricted(x, y, true); 1378 } 1379 1380 1381 void 1382 ShowImageView::_ScrollRestrictedBy(float x, float y) 1383 { 1384 _ScrollRestricted(x, y, false); 1385 } 1386 1387 1388 void 1389 ShowImageView::KeyDown(const char* bytes, int32 numBytes) 1390 { 1391 if (numBytes != 1) { 1392 BView::KeyDown(bytes, numBytes); 1393 return; 1394 } 1395 1396 bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0; 1397 1398 switch (*bytes) { 1399 case B_DOWN_ARROW: 1400 if (shiftKeyDown) 1401 _ScrollRestrictedBy(0, 10); 1402 else 1403 _SendMessageToWindow(MSG_FILE_NEXT); 1404 break; 1405 case B_RIGHT_ARROW: 1406 if (shiftKeyDown) 1407 _ScrollRestrictedBy(10, 0); 1408 else 1409 _SendMessageToWindow(MSG_FILE_NEXT); 1410 break; 1411 case B_UP_ARROW: 1412 if (shiftKeyDown) 1413 _ScrollRestrictedBy(0, -10); 1414 else 1415 _SendMessageToWindow(MSG_FILE_PREV); 1416 break; 1417 case B_LEFT_ARROW: 1418 if (shiftKeyDown) 1419 _ScrollRestrictedBy(-10, 0); 1420 else 1421 _SendMessageToWindow(MSG_FILE_PREV); 1422 break; 1423 case B_BACKSPACE: 1424 _SendMessageToWindow(MSG_FILE_PREV); 1425 break; 1426 case B_HOME: 1427 break; 1428 case B_END: 1429 break; 1430 case B_SPACE: 1431 _ToggleSlideShow(); 1432 break; 1433 case B_ESCAPE: 1434 // stop slide show 1435 if (fSlideShow) 1436 _ToggleSlideShow(); 1437 1438 _ExitFullScreen(); 1439 1440 ClearSelection(); 1441 break; 1442 case B_DELETE: 1443 { 1444 // Move image to Trash 1445 BMessage trash(BPrivate::kMoveToTrash); 1446 trash.AddRef("refs", &fCurrentRef); 1447 // We create our own messenger because the member fTrackerMessenger 1448 // could be invalid 1449 BMessenger tracker(kTrackerSignature); 1450 if (tracker.SendMessage(&trash) == B_OK) 1451 if (!NextFile()) { 1452 // This is the last (or only file) in this directory, 1453 // close the window 1454 _SendMessageToWindow(B_QUIT_REQUESTED); 1455 } 1456 break; 1457 } 1458 case '+': 1459 case '=': 1460 ZoomIn(); 1461 break; 1462 case '-': 1463 ZoomOut(); 1464 break; 1465 case '[': 1466 Rotate(270); 1467 break; 1468 case ']': 1469 Rotate(90); 1470 break; 1471 } 1472 } 1473 1474 1475 void 1476 ShowImageView::_MouseWheelChanged(BMessage *msg) 1477 { 1478 // The BeOS driver does not currently support 1479 // X wheel scrolling, therefore, dx is zero. 1480 // |dy| is the number of notches scrolled up or down. 1481 // When the wheel is scrolled down (towards the user) dy > 0 1482 // When the wheel is scrolled up (away from the user) dy < 0 1483 const float kscrollBy = 40; 1484 float dy, dx; 1485 float x, y; 1486 x = 0; y = 0; 1487 if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) 1488 x = dx * kscrollBy; 1489 if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) 1490 y = dy * kscrollBy; 1491 1492 if ((modifiers() & B_SHIFT_KEY) != 0) 1493 _ScrollRestrictedBy(x, y); 1494 else if ((modifiers() & B_COMMAND_KEY) != 0) 1495 _ScrollRestrictedBy(y, x); 1496 else { 1497 // Zoom in spot 1498 BPoint where; 1499 uint32 buttons; 1500 GetMouse(&where, &buttons); 1501 1502 if (dy < 0) 1503 ZoomIn(where); 1504 else if (dy > 0) 1505 ZoomOut(where); 1506 } 1507 } 1508 1509 1510 void 1511 ShowImageView::_ShowPopUpMenu(BPoint screen) 1512 { 1513 if (!fShowingPopUpMenu) { 1514 PopUpMenu* menu = new PopUpMenu("PopUpMenu", this); 1515 1516 ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window()); 1517 if (showImage) 1518 showImage->BuildContextMenu(menu); 1519 1520 screen += BPoint(2, 2); 1521 menu->Go(screen, true, true, true); 1522 fShowingPopUpMenu = true; 1523 } 1524 } 1525 1526 1527 void 1528 ShowImageView::_SettingsSetBool(const char* name, bool value) 1529 { 1530 ShowImageSettings* settings; 1531 settings = my_app->Settings(); 1532 if (settings->Lock()) { 1533 settings->SetBool(name, value); 1534 settings->Unlock(); 1535 } 1536 } 1537 1538 1539 void 1540 ShowImageView::MessageReceived(BMessage* message) 1541 { 1542 switch (message->what) { 1543 case B_SIMPLE_DATA: 1544 if (message->WasDropped()) { 1545 uint32 type; 1546 int32 count; 1547 status_t ret = message->GetInfo("refs", &type, &count); 1548 if (ret == B_OK && type == B_REF_TYPE) { 1549 // If file was dropped, open it as the selection 1550 entry_ref ref; 1551 if (message->FindRef("refs", 0, &ref) == B_OK) 1552 SetImage(&ref); 1553 } 1554 } 1555 break; 1556 1557 case B_COPY_TARGET: 1558 _HandleDrop(message); 1559 break; 1560 case B_MOUSE_WHEEL_CHANGED: 1561 _MouseWheelChanged(message); 1562 break; 1563 1564 case kMsgPopUpMenuClosed: 1565 fShowingPopUpMenu = false; 1566 break; 1567 1568 default: 1569 BView::MessageReceived(message); 1570 break; 1571 } 1572 } 1573 1574 1575 void 1576 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) 1577 { 1578 float prop, range; 1579 BScrollBar *psb; 1580 1581 psb = ScrollBar(o); 1582 if (psb) { 1583 range = bitmapLength - viewLength; 1584 if (range < 0.0) { 1585 range = 0.0; 1586 } 1587 prop = viewLength / bitmapLength; 1588 if (prop > 1.0) { 1589 prop = 1.0; 1590 } 1591 psb->SetRange(0, range); 1592 psb->SetProportion(prop); 1593 psb->SetSteps(10, 100); 1594 } 1595 } 1596 1597 1598 void 1599 ShowImageView::FixupScrollBars() 1600 { 1601 BRect viewRect = Bounds(); 1602 BRect bitmapRect; 1603 if (fBitmap != NULL) { 1604 bitmapRect = _AlignBitmap(); 1605 bitmapRect.OffsetTo(0, 0); 1606 } 1607 1608 FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width()); 1609 FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height()); 1610 } 1611 1612 1613 int32 1614 ShowImageView::CurrentPage() 1615 { 1616 return fDocumentIndex; 1617 } 1618 1619 1620 int32 1621 ShowImageView::PageCount() 1622 { 1623 return fDocumentCount; 1624 } 1625 1626 1627 void 1628 ShowImageView::SetSelectionMode(bool selectionMode) 1629 { 1630 // The mode only has an effect in MouseDown() 1631 fSelectionMode = selectionMode; 1632 } 1633 1634 1635 void 1636 ShowImageView::Undo() 1637 { 1638 int32 undoType = fUndo.GetType(); 1639 if (undoType != UNDO_UNDO && undoType != UNDO_REDO) 1640 return; 1641 1642 // backup current selection 1643 BRect undoneSelRect; 1644 BBitmap *undoneSelection; 1645 undoneSelRect = fSelectionBox.Bounds(); 1646 undoneSelection = _CopySelection(); 1647 1648 if (undoType == UNDO_UNDO) { 1649 BBitmap *undoRestore; 1650 undoRestore = fUndo.GetRestoreBitmap(); 1651 if (undoRestore) 1652 _MergeWithBitmap(undoRestore, fUndo.GetRect()); 1653 } 1654 1655 // restore previous image/selection 1656 BBitmap *undoSelection; 1657 undoSelection = fUndo.GetSelectionBitmap(); 1658 // NOTE: ShowImageView is responsible for deleting this bitmap 1659 // (Which it will, as it would with a fSelectionBitmap that it allocated itself) 1660 if (!undoSelection) 1661 _SetHasSelection(false); 1662 else { 1663 fCopyFromRect = BRect(); 1664 fSelectionBox.SetBounds(this, fUndo.GetRect()); 1665 _SetHasSelection(true); 1666 fSelectionBitmap = undoSelection; 1667 } 1668 1669 fUndo.Undo(undoneSelRect, NULL, undoneSelection); 1670 1671 Invalidate(); 1672 } 1673 1674 1675 void 1676 ShowImageView::SelectAll() 1677 { 1678 _SetHasSelection(true); 1679 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), 1680 fBitmap->Bounds().Height()); 1681 fSelectionBox.SetBounds(this, fCopyFromRect); 1682 Invalidate(); 1683 } 1684 1685 1686 void 1687 ShowImageView::ClearSelection() 1688 { 1689 if (!fHasSelection) 1690 return; 1691 1692 _SetHasSelection(false); 1693 Invalidate(); 1694 } 1695 1696 1697 void 1698 ShowImageView::_SetHasSelection(bool hasSelection) 1699 { 1700 _DeleteSelectionBitmap(); 1701 fHasSelection = hasSelection; 1702 1703 _UpdateStatusText(); 1704 1705 BMessage msg(MSG_SELECTION); 1706 msg.AddBool("has_selection", fHasSelection); 1707 _SendMessageToWindow(&msg); 1708 } 1709 1710 1711 void 1712 ShowImageView::CopySelectionToClipboard() 1713 { 1714 if (!fHasSelection || !be_clipboard->Lock()) 1715 return; 1716 1717 be_clipboard->Clear(); 1718 1719 BMessage* data = be_clipboard->Data(); 1720 if (data != NULL) { 1721 BBitmap* bitmap = _CopySelection(); 1722 if (bitmap != NULL) { 1723 BMessage bitmapArchive; 1724 bitmap->Archive(&bitmapArchive); 1725 // NOTE: Possibly "image/x-be-bitmap" is more correct. 1726 // This works with WonderBrush, though, which in turn had been 1727 // tested with other apps. 1728 data->AddMessage("image/bitmap", &bitmapArchive); 1729 data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop()); 1730 1731 delete bitmap; 1732 1733 be_clipboard->Commit(); 1734 } 1735 } 1736 be_clipboard->Unlock(); 1737 } 1738 1739 1740 void 1741 ShowImageView::FirstPage() 1742 { 1743 if (fDocumentIndex != 1) { 1744 fDocumentIndex = 1; 1745 SetImage(NULL); 1746 } 1747 } 1748 1749 1750 void 1751 ShowImageView::LastPage() 1752 { 1753 if (fDocumentIndex != fDocumentCount) { 1754 fDocumentIndex = fDocumentCount; 1755 SetImage(NULL); 1756 } 1757 } 1758 1759 1760 void 1761 ShowImageView::NextPage() 1762 { 1763 if (fDocumentIndex < fDocumentCount) { 1764 fDocumentIndex++; 1765 SetImage(NULL); 1766 } 1767 } 1768 1769 1770 void 1771 ShowImageView::PrevPage() 1772 { 1773 if (fDocumentIndex > 1) { 1774 fDocumentIndex--; 1775 SetImage(NULL); 1776 } 1777 } 1778 1779 1780 int 1781 ShowImageView::_CompareEntries(const void* a, const void* b) 1782 { 1783 entry_ref *r1, *r2; 1784 r1 = *(entry_ref**)a; 1785 r2 = *(entry_ref**)b; 1786 return strcasecmp(r1->name, r2->name); 1787 } 1788 1789 1790 void 1791 ShowImageView::GoToPage(int32 page) 1792 { 1793 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 1794 fDocumentIndex = page; 1795 SetImage(NULL); 1796 } 1797 } 1798 1799 1800 void 1801 ShowImageView::_FreeEntries(BList* entries) 1802 { 1803 const int32 n = entries->CountItems(); 1804 for (int32 i = 0; i < n; i ++) { 1805 entry_ref* ref = (entry_ref*)entries->ItemAt(i); 1806 delete ref; 1807 } 1808 entries->MakeEmpty(); 1809 } 1810 1811 1812 void 1813 ShowImageView::_SetTrackerSelectionToCurrent() 1814 { 1815 BMessage setsel(B_SET_PROPERTY); 1816 setsel.AddSpecifier("Selection"); 1817 setsel.AddRef("data", &fCurrentRef); 1818 fTrackerMessenger.SendMessage(&setsel); 1819 } 1820 1821 1822 bool 1823 ShowImageView::_FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, 1824 bool rewind) 1825 { 1826 // Based on GetTrackerWindowFile function from BeMail 1827 if (!fTrackerMessenger.IsValid()) 1828 return false; 1829 1830 // 1831 // Ask the Tracker what the next/prev file in the window is. 1832 // Continue asking for the next reference until a valid 1833 // image is found. 1834 // 1835 entry_ref nextRef = *in_current; 1836 bool foundRef = false; 1837 while (!foundRef) 1838 { 1839 BMessage request(B_GET_PROPERTY); 1840 BMessage spc; 1841 if (rewind) 1842 spc.what = B_DIRECT_SPECIFIER; 1843 else if (next) 1844 spc.what = 'snxt'; 1845 else 1846 spc.what = 'sprv'; 1847 spc.AddString("property", "Entry"); 1848 if (rewind) 1849 // if rewinding, ask for the ref to the 1850 // first item in the directory 1851 spc.AddInt32("data", 0); 1852 else 1853 spc.AddRef("data", &nextRef); 1854 request.AddSpecifier(&spc); 1855 1856 BMessage reply; 1857 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 1858 return false; 1859 if (reply.FindRef("result", &nextRef) != B_OK) 1860 return false; 1861 1862 if (_IsImage(&nextRef)) 1863 foundRef = true; 1864 1865 rewind = false; 1866 // stop asking for the first ref in the directory 1867 } 1868 1869 *ref = nextRef; 1870 return foundRef; 1871 } 1872 1873 bool 1874 ShowImageView::_ShowNextImage(bool next, bool rewind) 1875 { 1876 entry_ref curRef = fCurrentRef; 1877 entry_ref imgRef; 1878 bool found = _FindNextImage(&curRef, &imgRef, next, rewind); 1879 if (found) { 1880 // Keep trying to load images until: 1881 // 1. The image loads successfully 1882 // 2. The last file in the directory is found (for find next or find first) 1883 // 3. The first file in the directory is found (for find prev) 1884 // 4. The call to _FindNextImage fails for any other reason 1885 while (SetImage(&imgRef) != B_OK) { 1886 curRef = imgRef; 1887 found = _FindNextImage(&curRef, &imgRef, next, false); 1888 if (!found) 1889 return false; 1890 } 1891 _SetTrackerSelectionToCurrent(); 1892 return true; 1893 } 1894 return false; 1895 } 1896 1897 1898 bool 1899 ShowImageView::NextFile() 1900 { 1901 return _ShowNextImage(true, false); 1902 } 1903 1904 1905 bool 1906 ShowImageView::PrevFile() 1907 { 1908 return _ShowNextImage(false, false); 1909 } 1910 1911 1912 bool 1913 ShowImageView::HasNextFile() 1914 { 1915 entry_ref ref; 1916 return _FindNextImage(&fCurrentRef, &ref, true, false); 1917 } 1918 1919 1920 bool 1921 ShowImageView::HasPrevFile() 1922 { 1923 entry_ref ref; 1924 return _FindNextImage(&fCurrentRef, &ref, false, false); 1925 } 1926 1927 1928 bool 1929 ShowImageView::_FirstFile() 1930 { 1931 return _ShowNextImage(true, true); 1932 } 1933 1934 1935 void 1936 ShowImageView::SetZoom(float zoom, BPoint where) 1937 { 1938 if (zoom > 32) 1939 zoom = 32; 1940 if (zoom < fFitToBoundsZoom / 2) 1941 zoom = fFitToBoundsZoom / 2; 1942 1943 if (zoom == fZoom) { 1944 // window size might have changed 1945 FixupScrollBars(); 1946 return; 1947 } 1948 1949 // Invalidate before scrolling, as that prevents the app_server 1950 // to do the scrolling server side 1951 Invalidate(); 1952 1953 // zoom to center if not otherwise specified 1954 BPoint offset; 1955 if (where.x == -1) { 1956 where.Set(Bounds().Width() / 2, Bounds().Height() / 2); 1957 offset = where; 1958 where += Bounds().LeftTop(); 1959 } else 1960 offset = where - Bounds().LeftTop(); 1961 1962 float oldZoom = fZoom; 1963 fZoom = zoom; 1964 1965 FixupScrollBars(); 1966 1967 if (fBitmap != NULL) { 1968 offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x; 1969 offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y; 1970 ScrollTo(offset); 1971 } 1972 } 1973 1974 1975 void 1976 ShowImageView::ZoomIn(BPoint where) 1977 { 1978 // snap zoom to "fit to bounds", and "original size" 1979 float zoom = fZoom * 1.2; 1980 float zoomSnap = fZoom * 1.25; 1981 if (fZoom < fFitToBoundsZoom && zoomSnap > fFitToBoundsZoom) 1982 zoom = fFitToBoundsZoom; 1983 if (fZoom < 1.0 && zoomSnap > 1.0) 1984 zoom = 1.0; 1985 1986 SetZoom(zoom, where); 1987 } 1988 1989 1990 void 1991 ShowImageView::ZoomOut(BPoint where) 1992 { 1993 // snap zoom to "fit to bounds", and "original size" 1994 float zoom = fZoom / 1.2; 1995 float zoomSnap = fZoom / 1.25; 1996 if (fZoom > fFitToBoundsZoom && zoomSnap < fFitToBoundsZoom) 1997 zoom = fFitToBoundsZoom; 1998 if (fZoom > 1.0 && zoomSnap < 1.0) 1999 zoom = 1.0; 2000 2001 SetZoom(zoom, where); 2002 } 2003 2004 2005 /*! Resets the zoom to what it should be when opening an image, depending 2006 on the current settings. 2007 */ 2008 void 2009 ShowImageView::ResetZoom() 2010 { 2011 if (fBitmap == NULL) 2012 return; 2013 2014 fFitToBoundsZoom = _FitToBoundsZoom(); 2015 2016 if (_ShouldShrink() || _ShouldStretch()) 2017 SetZoom(fFitToBoundsZoom); 2018 else 2019 SetZoom(1.0); 2020 } 2021 2022 2023 void 2024 ShowImageView::SetSlideShowDelay(float seconds) 2025 { 2026 ShowImageSettings* settings; 2027 int32 delay = (int)(seconds * 10.0); 2028 if (fSlideShowDelay != delay) { 2029 // update counter 2030 fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown); 2031 if (fSlideShowCountDown <= 0) { 2032 // show next image on next Pulse() 2033 fSlideShowCountDown = 1; 2034 } 2035 fSlideShowDelay = delay; 2036 settings = my_app->Settings(); 2037 if (settings->Lock()) { 2038 settings->SetInt32("SlideShowDelay", fSlideShowDelay); 2039 settings->Unlock(); 2040 } 2041 } 2042 } 2043 2044 2045 void 2046 ShowImageView::StartSlideShow() 2047 { 2048 fSlideShow = true; 2049 fSlideShowCountDown = fSlideShowDelay; 2050 } 2051 2052 2053 void 2054 ShowImageView::StopSlideShow() 2055 { 2056 fSlideShow = false; 2057 } 2058 2059 2060 void 2061 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet) 2062 { 2063 BMessenger msgr; 2064 ImageProcessor imageProcessor(op, fBitmap, msgr, 0); 2065 imageProcessor.Start(false); 2066 BBitmap* bm = imageProcessor.DetachBitmap(); 2067 if (bm == NULL) { 2068 // operation failed 2069 return; 2070 } 2071 2072 // update orientation state 2073 if (op != ImageProcessor::kInvert) { 2074 // Note: If one of these fails, check its definition in class ImageProcessor. 2075 // ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations); 2076 // ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations); 2077 // ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations); 2078 // ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations); 2079 fImageOrientation = fTransformation[op][fImageOrientation]; 2080 } 2081 2082 if (!quiet) { 2083 // write orientation state 2084 BNode node(&fCurrentRef); 2085 int32 orientation = fImageOrientation; 2086 if (orientation != k0) { 2087 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, 2088 &orientation, sizeof(orientation)); 2089 } else { 2090 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE); 2091 } 2092 } 2093 2094 // set new bitmap 2095 _DeleteBitmap(); 2096 fBitmap = bm; 2097 2098 if (!quiet) { 2099 // remove selection 2100 _SetHasSelection(false); 2101 _Notify(); 2102 } 2103 } 2104 2105 2106 //! image operation initiated by user 2107 void 2108 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet) 2109 { 2110 fUndo.Clear(); 2111 _DoImageOperation(op, quiet); 2112 } 2113 2114 2115 void 2116 ShowImageView::Rotate(int degree) 2117 { 2118 if (degree == 90) { 2119 _UserDoImageOperation(ImageProcessor::kRotateClockwise); 2120 } else if (degree == 270) { 2121 _UserDoImageOperation(ImageProcessor::kRotateCounterClockwise); 2122 } 2123 } 2124 2125 2126 void 2127 ShowImageView::Flip(bool vertical) 2128 { 2129 if (vertical) { 2130 _UserDoImageOperation(ImageProcessor::kFlipLeftToRight); 2131 } else { 2132 _UserDoImageOperation(ImageProcessor::kFlipTopToBottom); 2133 } 2134 } 2135 2136 2137 void 2138 ShowImageView::ResizeImage(int w, int h) 2139 { 2140 if (fBitmap == NULL || w < 1 || h < 1) 2141 return; 2142 2143 Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false); 2144 scaler.Start(false); 2145 BBitmap* scaled = scaler.DetachBitmap(); 2146 if (scaled == NULL) { 2147 // operation failed 2148 return; 2149 } 2150 2151 // remove selection 2152 _SetHasSelection(false); 2153 fUndo.Clear(); 2154 _DeleteBitmap(); 2155 fBitmap = scaled; 2156 2157 _SendMessageToWindow(MSG_MODIFIED); 2158 2159 _Notify(); 2160 } 2161 2162 2163 void 2164 ShowImageView::_SetIcon(bool clear, icon_size which) 2165 { 2166 int32 size; 2167 switch (which) { 2168 case B_MINI_ICON: size = 16; 2169 break; 2170 case B_LARGE_ICON: size = 32; 2171 break; 2172 default: 2173 return; 2174 } 2175 2176 BRect rect(fBitmap->Bounds()); 2177 float s; 2178 s = size / (rect.Width()+1.0); 2179 2180 if (s * (rect.Height()+1.0) <= size) { 2181 rect.right = size-1; 2182 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 2183 // center vertically 2184 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2); 2185 } else { 2186 s = size / (rect.Height()+1.0); 2187 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 2188 rect.bottom = size-1; 2189 // center horizontally 2190 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0); 2191 } 2192 2193 // scale bitmap to thumbnail size 2194 BMessenger msgr; 2195 Scaler scaler(fBitmap, rect, msgr, 0, true); 2196 BBitmap* thumbnail = scaler.GetBitmap(); 2197 scaler.Start(false); 2198 ASSERT(thumbnail->ColorSpace() == B_CMAP8); 2199 // create icon from thumbnail 2200 BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8); 2201 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength()); 2202 BScreen screen; 2203 const uchar* src = (uchar*)thumbnail->Bits(); 2204 uchar* dest = (uchar*)icon.Bits(); 2205 const int32 srcBPR = thumbnail->BytesPerRow(); 2206 const int32 destBPR = icon.BytesPerRow(); 2207 const int32 dx = (int32)rect.left; 2208 const int32 dy = (int32)rect.top; 2209 2210 for (int32 y = 0; y <= rect.IntegerHeight(); y ++) { 2211 for (int32 x = 0; x <= rect.IntegerWidth(); x ++) { 2212 const uchar* s = src + y * srcBPR + x; 2213 uchar* d = dest + (y+dy) * destBPR + (x+dx); 2214 *d = *s; 2215 } 2216 } 2217 2218 // set icon 2219 BNode node(&fCurrentRef); 2220 BNodeInfo info(&node); 2221 info.SetIcon(clear ? NULL : &icon, which); 2222 } 2223 2224 2225 void 2226 ShowImageView::SetIcon(bool clear) 2227 { 2228 _SetIcon(clear, B_MINI_ICON); 2229 _SetIcon(clear, B_LARGE_ICON); 2230 } 2231 2232 2233 void 2234 ShowImageView::_ToggleSlideShow() 2235 { 2236 _SendMessageToWindow(MSG_SLIDE_SHOW); 2237 } 2238 2239 2240 void 2241 ShowImageView::_ExitFullScreen() 2242 { 2243 be_app->ShowCursor(); 2244 _SendMessageToWindow(MSG_EXIT_FULL_SCREEN); 2245 } 2246 2247 2248 void 2249 ShowImageView::WindowActivated(bool active) 2250 { 2251 fIsActiveWin = active; 2252 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 2253 } 2254 2255