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