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