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