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