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