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