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