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