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