1 /* 2 * Copyright 2003-2009, 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 */ 15 16 #include "ShowImageView.h" 17 18 #include <math.h> 19 #include <new> 20 #include <stdio.h> 21 22 #include <Alert.h> 23 #include <Application.h> 24 #include <Bitmap.h> 25 #include <BitmapStream.h> 26 #include <Clipboard.h> 27 #include <Debug.h> 28 #include <Directory.h> 29 #include <Entry.h> 30 #include <File.h> 31 #include <MenuBar.h> 32 #include <MenuItem.h> 33 #include <Message.h> 34 #include <NodeInfo.h> 35 #include <Path.h> 36 #include <PopUpMenu.h> 37 #include <Rect.h> 38 #include <Region.h> 39 #include <Roster.h> 40 #include <Screen.h> 41 #include <ScrollBar.h> 42 #include <StopWatch.h> 43 #include <SupportDefs.h> 44 #include <TranslatorRoster.h> 45 46 #include <tracker_private.h> 47 48 #include "ProgressWindow.h" 49 #include "ShowImageApp.h" 50 #include "ShowImageConstants.h" 51 #include "ShowImageWindow.h" 52 53 54 // TODO: Remove this and use Tracker's Command.h once it is moved into the private headers 55 namespace BPrivate { 56 const uint32 kMoveToTrash = 'Ttrs'; 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 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation" 73 const rgb_color kBorderColor = { 0, 0, 0, 255 }; 74 75 enum ShowImageView::image_orientation 76 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations] = { 77 // rotate 90° 78 {k90, k180, k270, k0, k270V, k0V, k90V, k0H}, 79 // rotate -90° 80 {k270, k0, k90, k180, k90V, k0H, k270V, k0V}, 81 // mirror vertical 82 {k0H, k270V, k0V, k90V, k180, k270, k0, k90}, 83 // mirror horizontal 84 {k0V, k90V, k0H, k270V, k0, k90, k180, k270} 85 }; 86 87 const rgb_color kAlphaLow = (rgb_color){ 0xbb, 0xbb, 0xbb, 0xff }; 88 const rgb_color kAlphaHigh = (rgb_color){ 0xe0, 0xe0, 0xe0, 0xff }; 89 90 const uint32 kMsgPopUpMenuClosed = 'pmcl'; 91 92 93 static bool 94 entry_ref_is_file(const entry_ref *ref) 95 { 96 BEntry entry(ref, true); 97 if (entry.InitCheck() != B_OK) 98 return false; 99 100 return entry.IsFile(); 101 } 102 103 104 inline void 105 blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a) 106 { 107 d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8; 108 d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8; 109 d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8; 110 } 111 112 113 BBitmap* 114 compose_checker_background(const BBitmap* bitmap) 115 { 116 BBitmap* result = new (nothrow) BBitmap(bitmap); 117 if (result && !result->IsValid()) { 118 delete result; 119 result = NULL; 120 } 121 if (!result) 122 return NULL; 123 124 uint8* bits = (uint8*)result->Bits(); 125 uint32 bpr = result->BytesPerRow(); 126 uint32 width = result->Bounds().IntegerWidth() + 1; 127 uint32 height = result->Bounds().IntegerHeight() + 1; 128 129 for (uint32 i = 0; i < height; i++) { 130 uint8* p = bits; 131 for (uint32 x = 0; x < width; x++) { 132 uint8 alpha = p[3]; 133 if (alpha < 255) { 134 p[3] = 255; 135 alpha = 255 - alpha; 136 if (x % 10 >= 5) { 137 if (i % 10 >= 5) { 138 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha); 139 } else { 140 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha); 141 } 142 } else { 143 if (i % 10 >= 5) { 144 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha); 145 } else { 146 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha); 147 } 148 } 149 } 150 p += 4; 151 } 152 bits += bpr; 153 } 154 return result; 155 } 156 157 158 // #pragma mark - 159 160 161 PopUpMenu::PopUpMenu(const char* name, BMessenger target) 162 : 163 BPopUpMenu(name, false, false), 164 fTarget(target) 165 { 166 SetAsyncAutoDestruct(true); 167 } 168 169 170 PopUpMenu::~PopUpMenu() 171 { 172 fTarget.SendMessage(kMsgPopUpMenuClosed); 173 } 174 175 176 // #pragma mark - 177 178 179 ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode, 180 uint32 flags) 181 : 182 BView(rect, name, resizingMode, flags), 183 fDither(BScreen().ColorSpace() == B_CMAP8), 184 fDocumentIndex(1), 185 fDocumentCount(1), 186 fBitmap(NULL), 187 fDisplayBitmap(NULL), 188 fSelBitmap(NULL), 189 fZoom(1.0), 190 fScaleBilinear(true), 191 fScaler(NULL), 192 fShrinkToBounds(false), 193 fZoomToBounds(false), 194 fShrinkOrZoomToBounds(false), 195 fFullScreen(false), 196 fLeft(0.0), 197 fTop(0.0), 198 fMovesImage(false), 199 fMakesSelection(false), 200 fFirstPoint(0.0, 0.0), 201 fAnimateSelection(true), 202 fHasSelection(false), 203 fSlideShow(false), 204 fSlideShowDelay(3 * 10), // 3 seconds 205 fSlideShowCountDown(0), 206 #if DELAYED_SCALING 207 fScalingCountDown(SCALING_DELAY_TIME), 208 #endif 209 fShowCaption(false), 210 fInverted(false), 211 fShowingPopUpMenu(false), 212 fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME), 213 fIsActiveWin(true), 214 fProgressWindow(NULL) 215 { 216 _InitPatterns(); 217 218 ShowImageSettings* settings; 219 settings = my_app->Settings(); 220 if (settings->Lock()) { 221 fDither = settings->GetBool("Dither", fDither); 222 fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds); 223 fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds); 224 fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay); 225 fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear); 226 settings->Unlock(); 227 } 228 229 SetViewColor(B_TRANSPARENT_COLOR); 230 SetHighColor(kBorderColor); 231 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 232 SetPenSize(PEN_SIZE); 233 } 234 235 236 ShowImageView::~ShowImageView() 237 { 238 _DeleteBitmap(); 239 } 240 241 242 //! Use patterns to simulate marching ants for selection 243 void 244 ShowImageView::_InitPatterns() 245 { 246 uchar p; 247 uchar p1 = 0x33; 248 uchar p2 = 0xCC; 249 for (int i = 0; i <= 7; i ++) { 250 fPatternLeft.data[i] = p1; 251 fPatternRight.data[i] = p2; 252 if ((i / 2) % 2 == 0) { 253 p = 255; 254 } else { 255 p = 0; 256 } 257 fPatternUp.data[i] = p; 258 fPatternDown.data[i] = ~p; 259 } 260 } 261 262 263 void 264 ShowImageView::_RotatePatterns() 265 { 266 int i; 267 uchar p; 268 bool set; 269 270 // rotate up 271 p = fPatternUp.data[0]; 272 for (i = 0; i <= 6; i ++) { 273 fPatternUp.data[i] = fPatternUp.data[i+1]; 274 } 275 fPatternUp.data[7] = p; 276 277 // rotate down 278 p = fPatternDown.data[7]; 279 for (i = 7; i >= 1; i --) { 280 fPatternDown.data[i] = fPatternDown.data[i-1]; 281 } 282 fPatternDown.data[0] = p; 283 284 // rotate to left 285 p = fPatternLeft.data[0]; 286 set = (p & 0x80) != 0; 287 p <<= 1; 288 p &= 0xfe; 289 if (set) p |= 1; 290 memset(fPatternLeft.data, p, 8); 291 292 // rotate to right 293 p = fPatternRight.data[0]; 294 set = (p & 1) != 0; 295 p >>= 1; 296 if (set) p |= 0x80; 297 memset(fPatternRight.data, p, 8); 298 } 299 300 301 void 302 ShowImageView::_AnimateSelection(bool enabled) 303 { 304 fAnimateSelection = enabled; 305 } 306 307 308 void 309 ShowImageView::Pulse() 310 { 311 // animate marching ants 312 if (_HasSelection() && fAnimateSelection && fIsActiveWin) { 313 _RotatePatterns(); 314 _DrawSelectionBox(); 315 } 316 if (fSlideShow) { 317 fSlideShowCountDown --; 318 if (fSlideShowCountDown <= 0) { 319 fSlideShowCountDown = fSlideShowDelay; 320 if (!NextFile()) { 321 _FirstFile(); 322 } 323 } 324 } 325 326 // Hide cursor in full screen mode 327 if (fFullScreen && !_HasSelection() && !fShowingPopUpMenu && fIsActiveWin) { 328 if (fHideCursorCountDown <= 0) 329 be_app->ObscureCursor(); 330 else 331 fHideCursorCountDown--; 332 } 333 334 #if DELAYED_SCALING 335 if (fBitmap && (fScaleBilinear || fDither) && fScalingCountDown > 0) { 336 if (fScalingCountDown == 1) { 337 fScalingCountDown = 0; 338 _GetScaler(_AlignBitmap()); 339 } else { 340 fScalingCountDown --; 341 } 342 } 343 #endif 344 } 345 346 347 bool 348 ShowImageView::_IsImage(const entry_ref *ref) 349 { 350 if (ref == NULL || !entry_ref_is_file(ref)) 351 return false; 352 353 BFile file(ref, B_READ_ONLY); 354 if (file.InitCheck() != B_OK) 355 return false; 356 357 BTranslatorRoster *roster = BTranslatorRoster::Default(); 358 if (!roster) 359 return false; 360 361 BMessage ioExtension; 362 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 363 return false; 364 365 translator_info info; 366 memset(&info, 0, sizeof(translator_info)); 367 if (roster->Identify(&file, &ioExtension, &info, 0, NULL, 368 B_TRANSLATOR_BITMAP) != B_OK) 369 return false; 370 371 return true; 372 } 373 374 375 void 376 ShowImageView::SetTrackerMessenger(const BMessenger& trackerMessenger) 377 { 378 fTrackerMessenger = trackerMessenger; 379 } 380 381 382 void 383 ShowImageView::_SendMessageToWindow(BMessage *message) 384 { 385 BMessenger msgr(Window()); 386 msgr.SendMessage(message); 387 } 388 389 390 void 391 ShowImageView::_SendMessageToWindow(uint32 code) 392 { 393 BMessage message(code); 394 _SendMessageToWindow(&message); 395 } 396 397 398 //! send message to parent about new image 399 void 400 ShowImageView::_Notify() 401 { 402 BMessage msg(MSG_UPDATE_STATUS); 403 404 msg.AddString("status", fImageType.String()); 405 msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1); 406 msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1); 407 408 msg.AddInt32("colors", fBitmap->ColorSpace()); 409 _SendMessageToWindow(&msg); 410 411 FixupScrollBars(); 412 Invalidate(); 413 } 414 415 416 void 417 ShowImageView::_UpdateStatusText() 418 { 419 BMessage msg(MSG_UPDATE_STATUS_TEXT); 420 BString status_to_send = fImageType; 421 422 if (fHasSelection) { 423 char size[50]; 424 sprintf(size, " (%.0fx%.0f)", 425 fSelectionRect.Width()+1.0, 426 fSelectionRect.Height()+1.0); 427 status_to_send << size; 428 } 429 430 msg.AddString("status", status_to_send.String()); 431 _SendMessageToWindow(&msg); 432 } 433 434 435 void 436 ShowImageView::_AddToRecentDocuments() 437 { 438 be_roster->AddToRecentDocuments(&fCurrentRef, kApplicationSignature); 439 } 440 441 442 void 443 ShowImageView::_DeleteScaler() 444 { 445 if (fScaler) { 446 fScaler->Stop(); 447 delete fScaler; 448 fScaler = NULL; 449 } 450 #if DELAYED_SCALING 451 fScalingCountDown = SCALING_DELAY_TIME; 452 #endif 453 } 454 455 456 void 457 ShowImageView::_DeleteBitmap() 458 { 459 _DeleteScaler(); 460 _DeleteSelBitmap(); 461 462 if (fDisplayBitmap != fBitmap) 463 delete fDisplayBitmap; 464 fDisplayBitmap = NULL; 465 466 delete fBitmap; 467 fBitmap = NULL; 468 } 469 470 471 void 472 ShowImageView::_DeleteSelBitmap() 473 { 474 delete fSelBitmap; 475 fSelBitmap = NULL; 476 } 477 478 479 status_t 480 ShowImageView::SetImage(const entry_ref *ref) 481 { 482 // If no file was specified, load the specified page of 483 // the current file. 484 if (ref == NULL) 485 ref = &fCurrentRef; 486 487 BTranslatorRoster *roster = BTranslatorRoster::Default(); 488 if (!roster) 489 return B_ERROR; 490 491 if (!entry_ref_is_file(ref)) 492 return B_ERROR; 493 494 BFile file(ref, B_READ_ONLY); 495 translator_info info; 496 memset(&info, 0, sizeof(translator_info)); 497 BMessage ioExtension; 498 if (ref != &fCurrentRef) { 499 // if new image, reset to first document 500 fDocumentIndex = 1; 501 } 502 503 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 504 return B_ERROR; 505 506 BMessage progress(kMsgProgressStatusUpdate); 507 if (ioExtension.AddMessenger("/progressMonitor", fProgressWindow) == B_OK 508 && ioExtension.AddMessage("/progressMessage", &progress) == B_OK) 509 fProgressWindow->Start(); 510 511 // Translate image data and create a new ShowImage window 512 513 BBitmapStream outstream; 514 515 status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL, 516 B_TRANSLATOR_BITMAP); 517 if (status == B_OK) { 518 status = roster->Translate(&file, &info, &ioExtension, &outstream, 519 B_TRANSLATOR_BITMAP); 520 } 521 522 fProgressWindow->Stop(); 523 524 if (status != B_OK) 525 return status; 526 527 BBitmap *newBitmap = NULL; 528 if (outstream.DetachBitmap(&newBitmap) != B_OK) 529 return B_ERROR; 530 531 // Now that I've successfully loaded the new bitmap, 532 // I can be sure it is safe to delete the old one, 533 // and clear everything 534 fUndo.Clear(); 535 _SetHasSelection(false); 536 fMakesSelection = false; 537 _DeleteBitmap(); 538 fBitmap = newBitmap; 539 fDisplayBitmap = NULL; 540 newBitmap = NULL; 541 fCurrentRef = *ref; 542 543 // prepare the display bitmap 544 if (fBitmap->ColorSpace() == B_RGBA32) 545 fDisplayBitmap = compose_checker_background(fBitmap); 546 547 if (!fDisplayBitmap) 548 fDisplayBitmap = fBitmap; 549 550 // restore orientation 551 int32 orientation; 552 fImageOrientation = k0; 553 fInverted = false; 554 if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, 555 &orientation, sizeof(orientation)) == sizeof(orientation)) { 556 if (orientation & 256) 557 _DoImageOperation(ImageProcessor::ImageProcessor::kInvert, true); 558 559 orientation &= 255; 560 switch (orientation) { 561 case k0: 562 break; 563 case k90: 564 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 565 break; 566 case k180: 567 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 568 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 569 break; 570 case k270: 571 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true); 572 break; 573 case k0V: 574 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 575 break; 576 case k90V: 577 _DoImageOperation(ImageProcessor::kRotateClockwise, true); 578 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 579 break; 580 case k0H: 581 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true); 582 break; 583 case k270V: 584 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true); 585 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true); 586 break; 587 } 588 } 589 590 // get the number of documents (pages) if it has been supplied 591 int32 documentCount = 0; 592 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK 593 && documentCount > 0) 594 fDocumentCount = documentCount; 595 else 596 fDocumentCount = 1; 597 598 fImageType = info.name; 599 fImageMime = info.MIME; 600 601 GetPath(&fCaption); 602 if (fDocumentCount > 1) 603 fCaption << ", " << fDocumentIndex << "/" << fDocumentCount; 604 605 fCaption << ", " << fImageType; 606 fZoom = 1.0; 607 608 _AddToRecentDocuments(); 609 610 _Notify(); 611 return B_OK; 612 } 613 614 615 status_t 616 ShowImageView::_SetSelection(const entry_ref *ref, BPoint point) 617 { 618 BTranslatorRoster *roster = BTranslatorRoster::Default(); 619 if (!roster) 620 return B_ERROR; 621 622 BFile file(ref, B_READ_ONLY); 623 translator_info info; 624 memset(&info, 0, sizeof(translator_info)); 625 if (roster->Identify(&file, NULL, &info, 0, NULL, 626 B_TRANSLATOR_BITMAP) != B_OK) 627 return B_ERROR; 628 629 // Translate image data and create a new ShowImage window 630 BBitmapStream outstream; 631 if (roster->Translate(&file, &info, NULL, &outstream, 632 B_TRANSLATOR_BITMAP) != B_OK) 633 return B_ERROR; 634 635 BBitmap *newBitmap = NULL; 636 if (outstream.DetachBitmap(&newBitmap) != B_OK) 637 return B_ERROR; 638 639 return _PasteBitmap(newBitmap, point); 640 } 641 642 643 void 644 ShowImageView::SetDither(bool dither) 645 { 646 if (fDither != dither) { 647 _SettingsSetBool("Dither", dither); 648 fDither = dither; 649 Invalidate(); 650 } 651 } 652 653 654 void 655 ShowImageView::SetShowCaption(bool show) 656 { 657 if (fShowCaption != show) { 658 fShowCaption = show; 659 _UpdateCaption(); 660 } 661 } 662 663 664 void 665 ShowImageView::SetShrinkToBounds(bool enable) 666 { 667 if (fShrinkToBounds != enable) { 668 _SettingsSetBool("ShrinkToBounds", enable); 669 fShrinkToBounds = enable; 670 FixupScrollBars(); 671 Invalidate(); 672 } 673 } 674 675 676 void 677 ShowImageView::SetZoomToBounds(bool enable) 678 { 679 if (fZoomToBounds != enable) { 680 _SettingsSetBool("ZoomToBounds", enable); 681 fZoomToBounds = enable; 682 FixupScrollBars(); 683 Invalidate(); 684 } 685 } 686 687 688 void 689 ShowImageView::SetFullScreen(bool fullScreen) 690 { 691 if (fFullScreen != fullScreen) { 692 fFullScreen = fullScreen; 693 if (fFullScreen) { 694 SetLowColor(0, 0, 0, 255); 695 } else 696 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 697 } 698 } 699 700 701 BBitmap * 702 ShowImageView::GetBitmap() 703 { 704 return fBitmap; 705 } 706 707 708 void 709 ShowImageView::GetName(BString* outName) 710 { 711 BEntry entry(&fCurrentRef); 712 char name[B_FILE_NAME_LENGTH]; 713 if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK) 714 outName->SetTo(""); 715 else 716 outName->SetTo(name); 717 } 718 719 720 void 721 ShowImageView::GetPath(BString *outPath) 722 { 723 BEntry entry(&fCurrentRef); 724 BPath path; 725 if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK) 726 outPath->SetTo(""); 727 else 728 outPath->SetTo(path.Path()); 729 } 730 731 732 void 733 ShowImageView::SetScaleBilinear(bool enabled) 734 { 735 if (fScaleBilinear != enabled) { 736 _SettingsSetBool("ScaleBilinear", enabled); 737 fScaleBilinear = enabled; 738 Invalidate(); 739 } 740 } 741 742 743 void 744 ShowImageView::AttachedToWindow() 745 { 746 fUndo.SetWindow(Window()); 747 FixupScrollBars(); 748 749 fProgressWindow = new ProgressWindow(Window()); 750 } 751 752 753 void 754 ShowImageView::DetachedFromWindow() 755 { 756 fProgressWindow->Lock(); 757 fProgressWindow->Quit(); 758 } 759 760 761 BRect 762 ShowImageView::_AlignBitmap() 763 { 764 BRect rect(fBitmap->Bounds()); 765 766 // the width/height of the bitmap (in pixels) 767 float bitmapWidth = rect.Width() + 1.0; 768 float bitmapHeight = rect.Height() + 1.0; 769 770 // the available width/height for layouting the bitmap (in pixels) 771 float width = Bounds().Width() - 2 * PEN_SIZE + 1.0; 772 float height = Bounds().Height() - 2 * PEN_SIZE + 1.0; 773 774 if (width == 0 || height == 0) 775 return rect; 776 777 fShrinkOrZoomToBounds = (fShrinkToBounds && 778 (bitmapWidth >= width || bitmapHeight >= height)) || 779 (fZoomToBounds && (bitmapWidth < width && bitmapHeight < height)); 780 if (fShrinkOrZoomToBounds) { 781 float s = width / bitmapWidth; 782 783 if (s * bitmapHeight <= height) { 784 rect.right = width - 1; 785 rect.bottom = static_cast<int>(s * bitmapHeight) - 1; 786 // center vertically 787 rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2)); 788 } else { 789 s = height / bitmapHeight; 790 rect.right = static_cast<int>(s * bitmapWidth) - 1; 791 rect.bottom = height - 1; 792 // center horizontally 793 rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0); 794 } 795 } else { 796 // zoom image 797 rect.right = floorf(bitmapWidth * fZoom) - 1; 798 rect.bottom = floorf(bitmapHeight * fZoom) - 1; 799 800 // update the bitmap size after the zoom 801 bitmapWidth = rect.Width() + 1.0; 802 bitmapHeight = rect.Height() + 1.0; 803 804 // always align in the center 805 if (width > bitmapWidth) 806 rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0); 807 808 if (height > bitmapHeight) 809 rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0)); 810 } 811 rect.OffsetBy(PEN_SIZE, PEN_SIZE); 812 return rect; 813 } 814 815 816 void 817 ShowImageView::_Setup(BRect rect) 818 { 819 fLeft = floorf(rect.left); 820 fTop = floorf(rect.top); 821 fZoom = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0); 822 } 823 824 825 BPoint 826 ShowImageView::_ImageToView(BPoint p) const 827 { 828 p.x = floorf(fZoom * p.x + fLeft); 829 p.y = floorf(fZoom * p.y + fTop); 830 return p; 831 } 832 833 834 BPoint 835 ShowImageView::_ViewToImage(BPoint p) const 836 { 837 p.x = floorf((p.x - fLeft) / fZoom); 838 p.y = floorf((p.y - fTop) / fZoom); 839 return p; 840 } 841 842 843 BRect 844 ShowImageView::_ImageToView(BRect r) const 845 { 846 BPoint leftTop(_ImageToView(BPoint(r.left, r.top))); 847 BPoint rightBottom(r.right, r.bottom); 848 rightBottom += BPoint(1, 1); 849 rightBottom = _ImageToView(rightBottom); 850 rightBottom -= BPoint(1, 1); 851 return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); 852 } 853 854 855 void 856 ShowImageView::_DrawBorder(BRect border) 857 { 858 BRect bounds(Bounds()); 859 // top 860 FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW); 861 // left 862 FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW); 863 // right 864 FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW); 865 // bottom 866 FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW); 867 } 868 869 870 void 871 ShowImageView::_LayoutCaption(BFont &font, BPoint &pos, BRect &rect) 872 { 873 font_height fontHeight; 874 float width, height; 875 BRect bounds(Bounds()); 876 font = be_plain_font; 877 width = font.StringWidth(fCaption.String()); 878 font.GetHeight(&fontHeight); 879 height = fontHeight.ascent + fontHeight.descent; 880 // center text horizontally 881 pos.x = (bounds.left + bounds.right - width) / 2; 882 // flush bottom 883 pos.y = bounds.bottom - fontHeight.descent - 7; 884 885 // background rectangle 886 rect.Set(0, 0, width + 4, height + 4); 887 rect.OffsetTo(pos); 888 rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border 889 } 890 891 892 void 893 ShowImageView::_DrawCaption() 894 { 895 BFont font; 896 BPoint position; 897 BRect rect; 898 _LayoutCaption(font, position, rect); 899 900 PushState(); 901 902 // draw background 903 SetDrawingMode(B_OP_ALPHA); 904 SetHighColor(255, 255, 255, 160); 905 FillRect(rect); 906 907 // draw text 908 SetDrawingMode(B_OP_OVER); 909 SetFont(&font); 910 SetLowColor(B_TRANSPARENT_COLOR); 911 SetHighColor(0, 0, 0); 912 DrawString(fCaption.String(), position); 913 914 PopState(); 915 } 916 917 918 void 919 ShowImageView::_UpdateCaption() 920 { 921 BFont font; 922 BPoint pos; 923 BRect rect; 924 _LayoutCaption(font, pos, rect); 925 926 // draw over portion of image where caption is located 927 BRegion clip(rect); 928 PushState(); 929 ConstrainClippingRegion(&clip); 930 Draw(rect); 931 PopState(); 932 } 933 934 935 Scaler* 936 ShowImageView::_GetScaler(BRect rect) 937 { 938 if (fScaler == NULL || !fScaler->Matches(rect, fDither)) { 939 _DeleteScaler(); 940 BMessenger msgr(this, Window()); 941 fScaler = new Scaler(fDisplayBitmap, rect, msgr, MSG_INVALIDATE, fDither); 942 fScaler->Start(); 943 } 944 return fScaler; 945 } 946 947 948 void 949 ShowImageView::_DrawImage(BRect rect) 950 { 951 if (fScaleBilinear || fDither) { 952 #if DELAYED_SCALING 953 Scaler* scaler = fScaler; 954 if (scaler != NULL && !scaler->Matches(rect, fDither)) { 955 _DeleteScaler(); scaler = NULL; 956 } 957 #else 958 Scaler* scaler = _GetScaler(rect); 959 #endif 960 if (scaler != NULL && !scaler->IsRunning()) { 961 BBitmap* bitmap = scaler->GetBitmap(); 962 963 if (bitmap) { 964 DrawBitmap(bitmap, BPoint(rect.left, rect.top)); 965 return; 966 } 967 } 968 } 969 // TODO: fix composing of fBitmap with other bitmaps 970 // with regard to alpha channel 971 if (!fDisplayBitmap) 972 fDisplayBitmap = fBitmap; 973 974 DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect); 975 } 976 977 978 void 979 ShowImageView::Draw(BRect updateRect) 980 { 981 if (fBitmap == NULL) 982 return; 983 984 if (IsPrinting()) { 985 DrawBitmap(fBitmap); 986 return; 987 } 988 989 BRect rect = _AlignBitmap(); 990 _Setup(rect); 991 992 BRect border(rect); 993 border.InsetBy(-PEN_SIZE, -PEN_SIZE); 994 995 _DrawBorder(border); 996 997 // Draw black rectangle around image 998 StrokeRect(border); 999 1000 // Draw image 1001 _DrawImage(rect); 1002 1003 if (fShowCaption) 1004 _DrawCaption(); 1005 1006 if (_HasSelection()) { 1007 if (fSelBitmap) { 1008 BRect srcBits, destRect; 1009 _GetSelMergeRects(srcBits, destRect); 1010 destRect = _ImageToView(destRect); 1011 DrawBitmap(fSelBitmap, srcBits, destRect); 1012 } 1013 _DrawSelectionBox(); 1014 } 1015 } 1016 1017 1018 void 1019 ShowImageView::_DrawSelectionBox() 1020 { 1021 BRect r(fSelectionRect); 1022 _ConstrainToImage(r); 1023 r = _ImageToView(r); 1024 // draw selection box *around* selection 1025 r.InsetBy(-1, -1); 1026 PushState(); 1027 rgb_color white = {255, 255, 255}; 1028 SetLowColor(white); 1029 StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft); 1030 StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp); 1031 StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight); 1032 StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown); 1033 PopState(); 1034 } 1035 1036 1037 void 1038 ShowImageView::FrameResized(float /* width */, float /* height */) 1039 { 1040 FixupScrollBars(); 1041 } 1042 1043 1044 void 1045 ShowImageView::_ConstrainToImage(BPoint &point) 1046 { 1047 point.ConstrainTo(fBitmap->Bounds()); 1048 } 1049 1050 1051 void 1052 ShowImageView::_ConstrainToImage(BRect &rect) 1053 { 1054 rect = rect & fBitmap->Bounds(); 1055 } 1056 1057 1058 BBitmap* 1059 ShowImageView::_CopyFromRect(BRect srcRect) 1060 { 1061 BRect rect(0, 0, srcRect.Width(), srcRect.Height()); 1062 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1063 BBitmap *bitmap = new(nothrow) BBitmap(rect, fBitmap->ColorSpace(), true); 1064 if (bitmap == NULL || !bitmap->IsValid()) { 1065 delete bitmap; 1066 return NULL; 1067 } 1068 1069 if (bitmap->Lock()) { 1070 bitmap->AddChild(&view); 1071 view.DrawBitmap(fBitmap, srcRect, rect); 1072 view.Sync(); 1073 bitmap->RemoveChild(&view); 1074 bitmap->Unlock(); 1075 } 1076 1077 return bitmap; 1078 } 1079 1080 1081 BBitmap* 1082 ShowImageView::_CopySelection(uchar alpha, bool imageSize) 1083 { 1084 bool hasAlpha = alpha != 255; 1085 1086 if (!_HasSelection()) 1087 return NULL; 1088 1089 BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height()); 1090 if (!imageSize) { 1091 // scale image to view size 1092 rect.right = floorf((rect.right + 1.0) * fZoom - 1.0); 1093 rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0); 1094 } 1095 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1096 BBitmap *bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true); 1097 if (bitmap == NULL || !bitmap->IsValid()) { 1098 delete bitmap; 1099 return NULL; 1100 } 1101 1102 if (bitmap->Lock()) { 1103 bitmap->AddChild(&view); 1104 if (fSelBitmap) 1105 view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect); 1106 else 1107 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 1108 if (hasAlpha) { 1109 view.SetDrawingMode(B_OP_SUBTRACT); 1110 view.SetHighColor(0, 0, 0, 255-alpha); 1111 view.FillRect(rect, B_SOLID_HIGH); 1112 } 1113 view.Sync(); 1114 bitmap->RemoveChild(&view); 1115 bitmap->Unlock(); 1116 } 1117 1118 return bitmap; 1119 } 1120 1121 1122 bool 1123 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap) 1124 { 1125 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1126 if (roster == NULL) 1127 return false; 1128 1129 // add the current image mime first, will make it the preferred format on 1130 // left mouse drag 1131 msg->AddString("be:types", fImageMime); 1132 msg->AddString("be:filetypes", fImageMime); 1133 msg->AddString("be:type_descriptions", fImageType); 1134 1135 bool foundOther = false; 1136 bool foundCurrent = false; 1137 1138 int32 infoCount; 1139 translator_info* info; 1140 BBitmapStream stream(bitmap); 1141 if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) { 1142 for (int32 i = 0; i < infoCount; i++) { 1143 const translation_format* formats; 1144 int32 count; 1145 roster->GetOutputFormats(info[i].translator, &formats, &count); 1146 for (int32 j = 0; j < count; j++) { 1147 if (fImageMime == formats[j].MIME) { 1148 foundCurrent = true; 1149 } else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) { 1150 foundOther = true; 1151 // needed to send data in message 1152 msg->AddString("be:types", formats[j].MIME); 1153 // needed to pass data via file 1154 msg->AddString("be:filetypes", formats[j].MIME); 1155 msg->AddString("be:type_descriptions", formats[j].name); 1156 } 1157 } 1158 } 1159 } 1160 stream.DetachBitmap(&bitmap); 1161 1162 if (!foundCurrent) { 1163 msg->RemoveData("be:types", 0); 1164 msg->RemoveData("be:filetypes", 0); 1165 msg->RemoveData("be:type_descriptions", 0); 1166 } 1167 1168 return foundOther || foundCurrent; 1169 } 1170 1171 1172 void 1173 ShowImageView::_BeginDrag(BPoint sourcePoint) 1174 { 1175 BBitmap* bitmap = _CopySelection(128, false); 1176 if (bitmap == NULL) 1177 return; 1178 1179 SetMouseEventMask(B_POINTER_EVENTS); 1180 1181 // fill the drag message 1182 BMessage drag(B_SIMPLE_DATA); 1183 drag.AddInt32("be:actions", B_COPY_TARGET); 1184 drag.AddString("be:clip_name", "Bitmap Clip"); 1185 // ShowImage specific fields 1186 drag.AddPoint("be:_source_point", sourcePoint); 1187 drag.AddRect("be:_frame", fSelectionRect); 1188 if (_AddSupportedTypes(&drag, bitmap)) { 1189 // we also support "Passing Data via File" protocol 1190 drag.AddString("be:types", B_FILE_MIME_TYPE); 1191 // avoid flickering of dragged bitmap caused by drawing into the window 1192 _AnimateSelection(false); 1193 // only use a transparent bitmap on selections less than 400x400 1194 // (taking into account zooming) 1195 if ((fSelectionRect.Width() * fZoom) < 400.0 1196 && (fSelectionRect.Height() * fZoom) < 400.0) { 1197 sourcePoint -= fSelectionRect.LeftTop(); 1198 sourcePoint.x *= fZoom; 1199 sourcePoint.y *= fZoom; 1200 // DragMessage takes ownership of bitmap 1201 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint); 1202 bitmap = NULL; 1203 } else { 1204 delete bitmap; 1205 // Offset and scale the rect 1206 BRect rect(fSelectionRect); 1207 rect = _ImageToView(rect); 1208 rect.InsetBy(-1, -1); 1209 DragMessage(&drag, rect); 1210 } 1211 } 1212 } 1213 1214 1215 bool 1216 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type, 1217 translation_format* format) 1218 { 1219 bool found = false; 1220 1221 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1222 if (roster == NULL) 1223 return false; 1224 1225 BBitmapStream stream(bitmap); 1226 1227 translator_info *outInfo; 1228 int32 outNumInfo; 1229 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 1230 for (int32 i = 0; i < outNumInfo; i++) { 1231 const translation_format *fmts; 1232 int32 num_fmts; 1233 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 1234 for (int32 j = 0; j < num_fmts; j++) { 1235 if (strcmp(fmts[j].MIME, type) == 0) { 1236 *format = fmts[j]; 1237 found = true; 1238 break; 1239 } 1240 } 1241 } 1242 } 1243 stream.DetachBitmap(&bitmap); 1244 return found; 1245 } 1246 1247 1248 void 1249 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, 1250 const translation_format* format) 1251 { 1252 if (!bitmap) { 1253 // If no bitmap is supplied, write out the whole image 1254 bitmap = fBitmap; 1255 } 1256 1257 BBitmapStream stream(bitmap); 1258 1259 bool loop = true; 1260 while (loop) { 1261 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1262 if (!roster) 1263 break; 1264 // write data 1265 BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 1266 if (file.InitCheck() != B_OK) 1267 break; 1268 if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK) 1269 break; 1270 // set mime type 1271 BNodeInfo info(&file); 1272 if (info.InitCheck() == B_OK) 1273 info.SetType(format->MIME); 1274 1275 loop = false; 1276 // break out of loop gracefully (indicates no errors) 1277 } 1278 if (loop) { 1279 // If loop terminated because of a break, there was an error 1280 BString errText; 1281 errText << "Sorry, the file '" << name << "' could not be written."; 1282 BAlert *palert = new BAlert(NULL, errText.String(), "OK"); 1283 palert->Go(); 1284 } 1285 1286 stream.DetachBitmap(&bitmap); 1287 // Don't allow the bitmap to be deleted, this is 1288 // especially important when using fBitmap as the bitmap 1289 } 1290 1291 1292 void 1293 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format) 1294 { 1295 BMessage reply(B_MIME_DATA); 1296 BBitmapStream stream(bitmap); // destructor deletes bitmap 1297 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1298 BMallocIO memStream; 1299 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) { 1300 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength()); 1301 msg->SendReply(&reply); 1302 } 1303 } 1304 1305 1306 void 1307 ShowImageView::_HandleDrop(BMessage* msg) 1308 { 1309 entry_ref dirRef; 1310 BString name, type; 1311 bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK 1312 && msg->FindRef("directory", &dirRef) == B_OK 1313 && msg->FindString("name", &name) == B_OK; 1314 1315 bool sendInMessage = !saveToFile 1316 && msg->FindString("be:types", &type) == B_OK; 1317 1318 BBitmap* bitmap = _CopySelection(); 1319 if (bitmap == NULL) 1320 return; 1321 1322 translation_format format; 1323 if (!_OutputFormatForType(bitmap, type.String(), &format)) { 1324 delete bitmap; 1325 return; 1326 } 1327 1328 if (saveToFile) { 1329 BDirectory dir(&dirRef); 1330 SaveToFile(&dir, name.String(), bitmap, &format); 1331 delete bitmap; 1332 } else if (sendInMessage) { 1333 _SendInMessage(msg, bitmap, &format); 1334 } else { 1335 delete bitmap; 1336 } 1337 } 1338 1339 1340 void 1341 ShowImageView::_MoveImage() 1342 { 1343 BPoint point, delta; 1344 uint32 buttons; 1345 // get CURRENT position 1346 GetMouse(&point, &buttons); 1347 point = ConvertToScreen(point); 1348 delta = fFirstPoint - point; 1349 fFirstPoint = point; 1350 _ScrollRestrictedBy(delta.x, delta.y); 1351 1352 // in case we miss MouseUp 1353 if ((_GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0) 1354 fMovesImage = false; 1355 } 1356 1357 1358 uint32 1359 ShowImageView::_GetMouseButtons() 1360 { 1361 uint32 buttons; 1362 BPoint point; 1363 GetMouse(&point, &buttons); 1364 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1365 if ((modifiers() & B_CONTROL_KEY) != 0) { 1366 buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button 1367 } else if ((modifiers() & B_SHIFT_KEY) != 0) { 1368 buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button 1369 } 1370 } 1371 return buttons; 1372 } 1373 1374 1375 void 1376 ShowImageView::_GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits, 1377 BRect &destRect) 1378 { 1379 destRect = selection; 1380 _ConstrainToImage(destRect); 1381 1382 srcBits = selection; 1383 if (srcBits.left < 0) 1384 srcBits.left = -(srcBits.left); 1385 else 1386 srcBits.left = 0; 1387 1388 if (srcBits.top < 0) 1389 srcBits.top = -(srcBits.top); 1390 else 1391 srcBits.top = 0; 1392 1393 if (srcBits.right > fBitmap->Bounds().right) 1394 srcBits.right = srcBits.left + destRect.Width(); 1395 else 1396 srcBits.right = merge->Bounds().right; 1397 1398 if (srcBits.bottom > fBitmap->Bounds().bottom) 1399 srcBits.bottom = srcBits.top + destRect.Height(); 1400 else 1401 srcBits.bottom = merge->Bounds().bottom; 1402 } 1403 1404 1405 void 1406 ShowImageView::_GetSelMergeRects(BRect &srcBits, BRect &destRect) 1407 { 1408 _GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect); 1409 } 1410 1411 1412 void 1413 ShowImageView::_MergeWithBitmap(BBitmap *merge, BRect selection) 1414 { 1415 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1416 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1417 if (bitmap == NULL || !bitmap->IsValid()) { 1418 delete bitmap; 1419 return; 1420 } 1421 1422 if (bitmap->Lock()) { 1423 bitmap->AddChild(&view); 1424 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1425 BRect srcBits, destRect; 1426 _GetMergeRects(merge, selection, srcBits, destRect); 1427 view.DrawBitmap(merge, srcBits, destRect); 1428 1429 view.Sync(); 1430 bitmap->RemoveChild(&view); 1431 bitmap->Unlock(); 1432 1433 _DeleteBitmap(); 1434 fBitmap = bitmap; 1435 1436 _SendMessageToWindow(MSG_MODIFIED); 1437 } else 1438 delete bitmap; 1439 } 1440 1441 1442 void 1443 ShowImageView::_MergeSelection() 1444 { 1445 if (!_HasSelection()) 1446 return; 1447 1448 if (!fSelBitmap) { 1449 // Even though the merge will not change 1450 // the background image, I still need to save 1451 // some undo information here 1452 fUndo.SetTo(fSelectionRect, NULL, _CopySelection()); 1453 return; 1454 } 1455 1456 // Merge selection with background 1457 fUndo.SetTo(fSelectionRect, _CopyFromRect(fSelectionRect), _CopySelection()); 1458 _MergeWithBitmap(fSelBitmap, fSelectionRect); 1459 } 1460 1461 1462 void 1463 ShowImageView::MouseDown(BPoint position) 1464 { 1465 BPoint point; 1466 uint32 buttons; 1467 MakeFocus(true); 1468 1469 point = _ViewToImage(position); 1470 buttons = _GetMouseButtons(); 1471 1472 if (_HasSelection() && fSelectionRect.Contains(point) 1473 && (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) { 1474 if (!fSelBitmap) 1475 fSelBitmap = _CopySelection(); 1476 1477 BPoint sourcePoint = point; 1478 _BeginDrag(sourcePoint); 1479 1480 while (buttons) { 1481 // Keep reading mouse movement until 1482 // the user lets up on all mouse buttons 1483 GetMouse(&point, &buttons); 1484 snooze(25 * 1000); 1485 // sleep for 25 milliseconds to minimize CPU usage during loop 1486 } 1487 1488 if (Bounds().Contains(point)) { 1489 // If selection stayed inside this view 1490 // (Some of the selection may be in the border area, which can be OK) 1491 BPoint last, diff; 1492 last = _ViewToImage(point); 1493 diff = last - sourcePoint; 1494 1495 BRect newSelection = fSelectionRect; 1496 newSelection.OffsetBy(diff); 1497 1498 if (fBitmap->Bounds().Intersects(newSelection)) { 1499 // Do not accept the new selection box location 1500 // if it does not intersect with the bitmap rectangle 1501 fSelectionRect = newSelection; 1502 Invalidate(); 1503 } 1504 } 1505 1506 _AnimateSelection(true); 1507 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1508 _MergeSelection(); 1509 // If there is an existing selection, 1510 // Make it part of the background image 1511 1512 // begin new selection 1513 _SetHasSelection(true); 1514 fMakesSelection = true; 1515 SetMouseEventMask(B_POINTER_EVENTS); 1516 _ConstrainToImage(point); 1517 fFirstPoint = point; 1518 fCopyFromRect.Set(point.x, point.y, point.x, point.y); 1519 fSelectionRect = fCopyFromRect; 1520 Invalidate(); 1521 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1522 _ShowPopUpMenu(ConvertToScreen(position)); 1523 } else if (buttons == B_TERTIARY_MOUSE_BUTTON) { 1524 // move image in window 1525 SetMouseEventMask(B_POINTER_EVENTS); 1526 fMovesImage = true; 1527 fFirstPoint = ConvertToScreen(position); 1528 } 1529 } 1530 1531 1532 void 1533 ShowImageView::_UpdateSelectionRect(BPoint point, bool final) 1534 { 1535 BRect oldSelection = fCopyFromRect; 1536 point = _ViewToImage(point); 1537 _ConstrainToImage(point); 1538 fCopyFromRect.left = min_c(fFirstPoint.x, point.x); 1539 fCopyFromRect.right = max_c(fFirstPoint.x, point.x); 1540 fCopyFromRect.top = min_c(fFirstPoint.y, point.y); 1541 fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y); 1542 fSelectionRect = fCopyFromRect; 1543 1544 if (final) { 1545 // selection must be at least 2 pixels wide or 2 pixels tall 1546 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0) 1547 _SetHasSelection(false); 1548 } else 1549 _UpdateStatusText(); 1550 1551 if (oldSelection != fCopyFromRect || !_HasSelection()) { 1552 BRect updateRect; 1553 updateRect = oldSelection | fCopyFromRect; 1554 updateRect = _ImageToView(updateRect); 1555 updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE); 1556 Invalidate(updateRect); 1557 } 1558 } 1559 1560 1561 void 1562 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message) 1563 { 1564 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 1565 if (fMakesSelection) { 1566 _UpdateSelectionRect(point, false); 1567 } else if (fMovesImage) { 1568 _MoveImage(); 1569 } 1570 } 1571 1572 1573 void 1574 ShowImageView::MouseUp(BPoint point) 1575 { 1576 if (fMakesSelection) { 1577 _UpdateSelectionRect(point, true); 1578 fMakesSelection = false; 1579 } else if (fMovesImage) { 1580 _MoveImage(); 1581 fMovesImage = false; 1582 } 1583 _AnimateSelection(true); 1584 } 1585 1586 1587 float 1588 ShowImageView::_LimitToRange(float v, orientation o, bool absolute) 1589 { 1590 BScrollBar* psb = ScrollBar(o); 1591 if (psb) { 1592 float min, max, pos; 1593 pos = v; 1594 if (!absolute) 1595 pos += psb->Value(); 1596 1597 psb->GetRange(&min, &max); 1598 if (pos < min) 1599 pos = min; 1600 else if (pos > max) 1601 pos = max; 1602 1603 v = pos; 1604 if (!absolute) 1605 v -= psb->Value(); 1606 } 1607 return v; 1608 } 1609 1610 1611 void 1612 ShowImageView::_ScrollRestricted(float x, float y, bool absolute) 1613 { 1614 if (x != 0) 1615 x = _LimitToRange(x, B_HORIZONTAL, absolute); 1616 1617 if (y != 0) 1618 y = _LimitToRange(y, B_VERTICAL, absolute); 1619 1620 // hide the caption when using mouse wheel 1621 // in full screen mode 1622 // to prevent the caption from dirtying up the image 1623 // during scrolling. 1624 bool caption = fShowCaption; 1625 if (caption) { 1626 fShowCaption = false; 1627 _UpdateCaption(); 1628 } 1629 1630 ScrollBy(x, y); 1631 1632 if (caption) { 1633 // show the caption again 1634 fShowCaption = true; 1635 _UpdateCaption(); 1636 } 1637 } 1638 1639 1640 // XXX method is not unused 1641 void 1642 ShowImageView::_ScrollRestrictedTo(float x, float y) 1643 { 1644 _ScrollRestricted(x, y, true); 1645 } 1646 1647 1648 void 1649 ShowImageView::_ScrollRestrictedBy(float x, float y) 1650 { 1651 _ScrollRestricted(x, y, false); 1652 } 1653 1654 1655 void 1656 ShowImageView::KeyDown(const char* bytes, int32 numBytes) 1657 { 1658 if (numBytes != 1) { 1659 BView::KeyDown(bytes, numBytes); 1660 return; 1661 } 1662 1663 bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0; 1664 1665 switch (*bytes) { 1666 case B_DOWN_ARROW: 1667 if (shiftKeyDown) 1668 _ScrollRestrictedBy(0, 10); 1669 else 1670 _SendMessageToWindow(MSG_FILE_NEXT); 1671 break; 1672 case B_RIGHT_ARROW: 1673 if (shiftKeyDown) 1674 _ScrollRestrictedBy(10, 0); 1675 else 1676 _SendMessageToWindow(MSG_FILE_NEXT); 1677 break; 1678 case B_UP_ARROW: 1679 if (shiftKeyDown) 1680 _ScrollRestrictedBy(0, -10); 1681 else 1682 _SendMessageToWindow(MSG_FILE_PREV); 1683 break; 1684 case B_LEFT_ARROW: 1685 if (shiftKeyDown) 1686 _ScrollRestrictedBy(-10, 0); 1687 else 1688 _SendMessageToWindow(MSG_FILE_PREV); 1689 break; 1690 case B_BACKSPACE: 1691 _SendMessageToWindow(MSG_FILE_PREV); 1692 break; 1693 case B_HOME: 1694 break; 1695 case B_END: 1696 break; 1697 case B_SPACE: 1698 _ToggleSlideShow(); 1699 break; 1700 case B_ESCAPE: 1701 // stop slide show 1702 if (fSlideShow) 1703 _ToggleSlideShow(); 1704 1705 _ExitFullScreen(); 1706 1707 ClearSelection(); 1708 break; 1709 case B_DELETE: 1710 { 1711 // Move image to Trash 1712 BMessage trash(BPrivate::kMoveToTrash); 1713 trash.AddRef("refs", &fCurrentRef); 1714 // We create our own messenger because the member fTrackerMessenger 1715 // could be invalid 1716 BMessenger tracker(kTrackerSignature); 1717 if (tracker.SendMessage(&trash) == B_OK) 1718 if (!NextFile()) { 1719 // This is the last (or only file) in this directory, 1720 // close the window 1721 _SendMessageToWindow(B_QUIT_REQUESTED); 1722 } 1723 break; 1724 } 1725 case '+': 1726 case '=': 1727 ZoomIn(); 1728 break; 1729 case '-': 1730 ZoomOut(); 1731 break; 1732 case '[': 1733 Rotate(270); 1734 break; 1735 case ']': 1736 Rotate(90); 1737 break; 1738 } 1739 } 1740 1741 1742 void 1743 ShowImageView::_MouseWheelChanged(BMessage *msg) 1744 { 1745 // The BeOS driver does not currently support 1746 // X wheel scrolling, therefore, dx is zero. 1747 // |dy| is the number of notches scrolled up or down. 1748 // When the wheel is scrolled down (towards the user) dy > 0 1749 // When the wheel is scrolled up (away from the user) dy < 0 1750 const float kscrollBy = 40; 1751 float dy, dx; 1752 float x, y; 1753 x = 0; y = 0; 1754 if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) 1755 x = dx * kscrollBy; 1756 if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) 1757 y = dy * kscrollBy; 1758 1759 if (modifiers() & B_SHIFT_KEY) 1760 _ScrollRestrictedBy(x, y); 1761 else if (modifiers() & B_COMMAND_KEY) 1762 _ScrollRestrictedBy(y, x); 1763 else { 1764 if (dy < 0) 1765 ZoomIn(); 1766 else if (dy > 0) 1767 ZoomOut(); 1768 } 1769 } 1770 1771 1772 void 1773 ShowImageView::_ShowPopUpMenu(BPoint screen) 1774 { 1775 BPopUpMenu* menu = new PopUpMenu("PopUpMenu", this); 1776 1777 ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window()); 1778 if (showImage) 1779 showImage->BuildContextMenu(menu); 1780 1781 screen -= BPoint(10, 10); 1782 menu->Go(screen, true, false, true); 1783 fShowingPopUpMenu = true; 1784 } 1785 1786 1787 void 1788 ShowImageView::_SettingsSetBool(const char* name, bool value) 1789 { 1790 ShowImageSettings* settings; 1791 settings = my_app->Settings(); 1792 if (settings->Lock()) { 1793 settings->SetBool(name, value); 1794 settings->Unlock(); 1795 } 1796 } 1797 1798 1799 void 1800 ShowImageView::MessageReceived(BMessage *message) 1801 { 1802 switch (message->what) { 1803 case MSG_SELECTION_BITMAP: 1804 { 1805 // In response to a B_SIMPLE_DATA message, a view will 1806 // send this message and expect a reply with a pointer to 1807 // the currently selected bitmap clip. Although this view 1808 // allocates the BBitmap * sent in the reply, it is only 1809 // to be used and deleted by the view that is being replied to. 1810 BMessage msg; 1811 msg.AddPointer("be:_bitmap_ptr", _CopySelection()); 1812 message->SendReply(&msg); 1813 break; 1814 } 1815 1816 case B_SIMPLE_DATA: 1817 if (message->WasDropped()) { 1818 uint32 type; 1819 int32 count; 1820 status_t ret = message->GetInfo("refs", &type, &count); 1821 if (ret == B_OK && type == B_REF_TYPE) { 1822 // If file was dropped, open it as the selection 1823 entry_ref ref; 1824 if (message->FindRef("refs", 0, &ref) == B_OK) { 1825 BPoint point = message->DropPoint(); 1826 point = ConvertFromScreen(point); 1827 point = _ViewToImage(point); 1828 _SetSelection(&ref, point); 1829 } 1830 } else { 1831 // If a user drags a clip from another ShowImage window, 1832 // request a BBitmap pointer to that clip, allocated by the 1833 // other view, for use solely by this view, so that it can 1834 // be dropped/pasted onto this view. 1835 BMessenger retMsgr, localMsgr(this); 1836 retMsgr = message->ReturnAddress(); 1837 if (retMsgr != localMsgr) { 1838 BMessage msgReply; 1839 retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply); 1840 BBitmap *bitmap = NULL; 1841 if (msgReply.FindPointer("be:_bitmap_ptr", 1842 reinterpret_cast<void **>(&bitmap)) == B_OK) { 1843 BRect sourceRect; 1844 BPoint point, sourcePoint; 1845 message->FindPoint("be:_source_point", &sourcePoint); 1846 message->FindRect("be:_frame", &sourceRect); 1847 point = message->DropPoint(); 1848 point.Set(point.x - (sourcePoint.x - sourceRect.left), 1849 point.y - (sourcePoint.y - sourceRect.top)); 1850 // adjust drop point before scaling is factored in 1851 point = ConvertFromScreen(point); 1852 point = _ViewToImage(point); 1853 1854 _PasteBitmap(bitmap, point); 1855 } 1856 } 1857 } 1858 } 1859 break; 1860 1861 case B_COPY_TARGET: 1862 _HandleDrop(message); 1863 break; 1864 case B_MOUSE_WHEEL_CHANGED: 1865 _MouseWheelChanged(message); 1866 break; 1867 case MSG_INVALIDATE: 1868 Invalidate(); 1869 break; 1870 1871 case kMsgPopUpMenuClosed: 1872 fShowingPopUpMenu = false; 1873 break; 1874 1875 default: 1876 BView::MessageReceived(message); 1877 break; 1878 } 1879 } 1880 1881 1882 void 1883 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) 1884 { 1885 float prop, range; 1886 BScrollBar *psb; 1887 1888 psb = ScrollBar(o); 1889 if (psb) { 1890 range = bitmapLength - viewLength; 1891 if (range < 0.0) { 1892 range = 0.0; 1893 } 1894 prop = viewLength / bitmapLength; 1895 if (prop > 1.0) { 1896 prop = 1.0; 1897 } 1898 psb->SetRange(0, range); 1899 psb->SetProportion(prop); 1900 psb->SetSteps(10, 100); 1901 } 1902 } 1903 1904 1905 void 1906 ShowImageView::FixupScrollBars() 1907 { 1908 BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0); 1909 if (fBitmap) { 1910 rctbitmap = _AlignBitmap(); 1911 rctbitmap.OffsetTo(0, 0); 1912 } 1913 1914 FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width()); 1915 FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height()); 1916 } 1917 1918 1919 int32 1920 ShowImageView::CurrentPage() 1921 { 1922 return fDocumentIndex; 1923 } 1924 1925 1926 int32 1927 ShowImageView::PageCount() 1928 { 1929 return fDocumentCount; 1930 } 1931 1932 1933 void 1934 ShowImageView::Undo() 1935 { 1936 int32 undoType = fUndo.GetType(); 1937 if (undoType != UNDO_UNDO && undoType != UNDO_REDO) 1938 return; 1939 1940 // backup current selection 1941 BRect undoneSelRect; 1942 BBitmap *undoneSelection; 1943 undoneSelRect = fSelectionRect; 1944 undoneSelection = _CopySelection(); 1945 1946 if (undoType == UNDO_UNDO) { 1947 BBitmap *undoRestore; 1948 undoRestore = fUndo.GetRestoreBitmap(); 1949 if (undoRestore) 1950 _MergeWithBitmap(undoRestore, fUndo.GetRect()); 1951 } 1952 1953 // restore previous image/selection 1954 BBitmap *undoSelection; 1955 undoSelection = fUndo.GetSelectionBitmap(); 1956 // NOTE: ShowImageView is responsible for deleting this bitmap 1957 // (Which it will, as it would with a fSelBitmap that it allocated itself) 1958 if (!undoSelection) 1959 _SetHasSelection(false); 1960 else { 1961 fCopyFromRect = BRect(); 1962 fSelectionRect = fUndo.GetRect(); 1963 _SetHasSelection(true); 1964 fSelBitmap = undoSelection; 1965 } 1966 1967 fUndo.Undo(undoneSelRect, NULL, undoneSelection); 1968 1969 Invalidate(); 1970 } 1971 1972 1973 void 1974 ShowImageView::_AddWhiteRect(BRect &rect) 1975 { 1976 // Paint white rectangle, using rect, into the background image 1977 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1978 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1979 if (bitmap == NULL || !bitmap->IsValid()) { 1980 delete bitmap; 1981 return; 1982 } 1983 1984 if (bitmap->Lock()) { 1985 bitmap->AddChild(&view); 1986 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1987 1988 view.FillRect(rect, B_SOLID_LOW); 1989 // draw white rect 1990 1991 view.Sync(); 1992 bitmap->RemoveChild(&view); 1993 bitmap->Unlock(); 1994 1995 _DeleteBitmap(); 1996 fBitmap = bitmap; 1997 1998 _SendMessageToWindow(MSG_MODIFIED); 1999 } else 2000 delete bitmap; 2001 } 2002 2003 2004 void 2005 ShowImageView::_RemoveSelection(bool toClipboard) 2006 { 2007 if (!_HasSelection()) 2008 return; 2009 2010 BRect rect = fSelectionRect; 2011 bool cutBackground = (fSelBitmap) ? false : true; 2012 BBitmap *selection, *restore = NULL; 2013 selection = _CopySelection(); 2014 2015 if (toClipboard) 2016 CopySelectionToClipboard(); 2017 2018 _SetHasSelection(false); 2019 2020 if (cutBackground) { 2021 // If the user hasn't dragged the selection, 2022 // paint a white rectangle where the selection was 2023 restore = _CopyFromRect(rect); 2024 _AddWhiteRect(rect); 2025 } 2026 2027 fUndo.SetTo(rect, restore, selection); 2028 Invalidate(); 2029 } 2030 2031 2032 void 2033 ShowImageView::Cut() 2034 { 2035 // Copy the selection to the clipboard, 2036 // then remove it 2037 _RemoveSelection(true); 2038 } 2039 2040 2041 status_t 2042 ShowImageView::_PasteBitmap(BBitmap *bitmap, BPoint point) 2043 { 2044 if (bitmap && bitmap->IsValid()) { 2045 _MergeSelection(); 2046 2047 fCopyFromRect = BRect(); 2048 fSelectionRect = bitmap->Bounds(); 2049 _SetHasSelection(true); 2050 fSelBitmap = bitmap; 2051 2052 BRect offsetRect = fSelectionRect; 2053 offsetRect.OffsetBy(point); 2054 if (fBitmap->Bounds().Intersects(offsetRect)) 2055 // Move the selection rectangle to desired origin, 2056 // but only if the resulting selection rectangle 2057 // intersects with the background bitmap rectangle 2058 fSelectionRect = offsetRect; 2059 2060 Invalidate(); 2061 2062 return B_OK; 2063 } 2064 2065 return B_ERROR; 2066 } 2067 2068 2069 void 2070 ShowImageView::Paste() 2071 { 2072 if (be_clipboard->Lock()) { 2073 BMessage *pclip; 2074 if ((pclip = be_clipboard->Data()) != NULL) { 2075 BPoint point(0, 0); 2076 pclip->FindPoint("be:location", &point); 2077 BBitmap *pbits; 2078 pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip)); 2079 _PasteBitmap(pbits, point); 2080 } 2081 2082 be_clipboard->Unlock(); 2083 } 2084 } 2085 2086 2087 void 2088 ShowImageView::SelectAll() 2089 { 2090 _SetHasSelection(true); 2091 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()); 2092 fSelectionRect = fCopyFromRect; 2093 Invalidate(); 2094 } 2095 2096 2097 void 2098 ShowImageView::ClearSelection() 2099 { 2100 // Remove the selection, 2101 // DON'T copy it to the clipboard 2102 _RemoveSelection(false); 2103 } 2104 2105 2106 void 2107 ShowImageView::_SetHasSelection(bool bHasSelection) 2108 { 2109 _DeleteSelBitmap(); 2110 fHasSelection = bHasSelection; 2111 2112 _UpdateStatusText(); 2113 2114 BMessage msg(MSG_SELECTION); 2115 msg.AddBool("has_selection", fHasSelection); 2116 _SendMessageToWindow(&msg); 2117 } 2118 2119 2120 void 2121 ShowImageView::CopySelectionToClipboard() 2122 { 2123 if (_HasSelection() && be_clipboard->Lock()) { 2124 be_clipboard->Clear(); 2125 BMessage *clip = NULL; 2126 if ((clip = be_clipboard->Data()) != NULL) { 2127 BMessage data; 2128 BBitmap* bitmap = _CopySelection(); 2129 if (bitmap != NULL) { 2130 #if 0 2131 // According to BeBook and Becasso, Gobe Productive do the following. 2132 // Paste works in Productive, but not in Becasso and original ShowImage. 2133 BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused 2134 bitmap->Archive(&msg); 2135 clip->AddMessage("image/x-be-bitmap", &msg); 2136 #else 2137 // original ShowImage performs this. Paste works with original ShowImage. 2138 bitmap->Archive(clip); 2139 // original ShowImage uses be:location for insertion point 2140 clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top)); 2141 #endif 2142 delete bitmap; 2143 be_clipboard->Commit(); 2144 } 2145 } 2146 be_clipboard->Unlock(); 2147 } 2148 } 2149 2150 2151 void 2152 ShowImageView::FirstPage() 2153 { 2154 if (fDocumentIndex != 1) { 2155 fDocumentIndex = 1; 2156 SetImage(NULL); 2157 } 2158 } 2159 2160 2161 void 2162 ShowImageView::LastPage() 2163 { 2164 if (fDocumentIndex != fDocumentCount) { 2165 fDocumentIndex = fDocumentCount; 2166 SetImage(NULL); 2167 } 2168 } 2169 2170 2171 void 2172 ShowImageView::NextPage() 2173 { 2174 if (fDocumentIndex < fDocumentCount) { 2175 fDocumentIndex++; 2176 SetImage(NULL); 2177 } 2178 } 2179 2180 2181 void 2182 ShowImageView::PrevPage() 2183 { 2184 if (fDocumentIndex > 1) { 2185 fDocumentIndex--; 2186 SetImage(NULL); 2187 } 2188 } 2189 2190 2191 int 2192 ShowImageView::_CompareEntries(const void* a, const void* b) 2193 { 2194 entry_ref *r1, *r2; 2195 r1 = *(entry_ref**)a; 2196 r2 = *(entry_ref**)b; 2197 return strcasecmp(r1->name, r2->name); 2198 } 2199 2200 2201 void 2202 ShowImageView::GoToPage(int32 page) 2203 { 2204 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 2205 fDocumentIndex = page; 2206 SetImage(NULL); 2207 } 2208 } 2209 2210 2211 void 2212 ShowImageView::_FreeEntries(BList* entries) 2213 { 2214 const int32 n = entries->CountItems(); 2215 for (int32 i = 0; i < n; i ++) { 2216 entry_ref* ref = (entry_ref*)entries->ItemAt(i); 2217 delete ref; 2218 } 2219 entries->MakeEmpty(); 2220 } 2221 2222 2223 void 2224 ShowImageView::_SetTrackerSelectionToCurrent() 2225 { 2226 BMessage setsel(B_SET_PROPERTY); 2227 setsel.AddSpecifier("Selection"); 2228 setsel.AddRef("data", &fCurrentRef); 2229 fTrackerMessenger.SendMessage(&setsel); 2230 } 2231 2232 2233 bool 2234 ShowImageView::_FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, 2235 bool rewind) 2236 { 2237 // Based on GetTrackerWindowFile function from BeMail 2238 if (!fTrackerMessenger.IsValid()) 2239 return false; 2240 2241 // 2242 // Ask the Tracker what the next/prev file in the window is. 2243 // Continue asking for the next reference until a valid 2244 // image is found. 2245 // 2246 entry_ref nextRef = *in_current; 2247 bool foundRef = false; 2248 while (!foundRef) 2249 { 2250 BMessage request(B_GET_PROPERTY); 2251 BMessage spc; 2252 if (rewind) 2253 spc.what = B_DIRECT_SPECIFIER; 2254 else if (next) 2255 spc.what = 'snxt'; 2256 else 2257 spc.what = 'sprv'; 2258 spc.AddString("property", "Entry"); 2259 if (rewind) 2260 // if rewinding, ask for the ref to the 2261 // first item in the directory 2262 spc.AddInt32("data", 0); 2263 else 2264 spc.AddRef("data", &nextRef); 2265 request.AddSpecifier(&spc); 2266 2267 BMessage reply; 2268 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 2269 return false; 2270 if (reply.FindRef("result", &nextRef) != B_OK) 2271 return false; 2272 2273 if (_IsImage(&nextRef)) 2274 foundRef = true; 2275 2276 rewind = false; 2277 // stop asking for the first ref in the directory 2278 } 2279 2280 *ref = nextRef; 2281 return foundRef; 2282 } 2283 2284 bool 2285 ShowImageView::_ShowNextImage(bool next, bool rewind) 2286 { 2287 entry_ref curRef = fCurrentRef; 2288 entry_ref imgRef; 2289 bool found = _FindNextImage(&curRef, &imgRef, next, rewind); 2290 if (found) { 2291 // Keep trying to load images until: 2292 // 1. The image loads successfully 2293 // 2. The last file in the directory is found (for find next or find first) 2294 // 3. The first file in the directory is found (for find prev) 2295 // 4. The call to _FindNextImage fails for any other reason 2296 while (SetImage(&imgRef) != B_OK) { 2297 curRef = imgRef; 2298 found = _FindNextImage(&curRef, &imgRef, next, false); 2299 if (!found) 2300 return false; 2301 } 2302 _SetTrackerSelectionToCurrent(); 2303 return true; 2304 } 2305 return false; 2306 } 2307 2308 2309 bool 2310 ShowImageView::NextFile() 2311 { 2312 return _ShowNextImage(true, false); 2313 } 2314 2315 2316 bool 2317 ShowImageView::PrevFile() 2318 { 2319 return _ShowNextImage(false, false); 2320 } 2321 2322 2323 bool 2324 ShowImageView::HasNextFile() 2325 { 2326 entry_ref ref; 2327 return _FindNextImage(&fCurrentRef, &ref, true, false); 2328 } 2329 2330 2331 bool 2332 ShowImageView::HasPrevFile() 2333 { 2334 entry_ref ref; 2335 return _FindNextImage(&fCurrentRef, &ref, false, false); 2336 } 2337 2338 2339 bool 2340 ShowImageView::_FirstFile() 2341 { 2342 return _ShowNextImage(true, true); 2343 } 2344 2345 2346 void 2347 ShowImageView::SetZoom(float zoom) 2348 { 2349 if ((fScaleBilinear || fDither) && fZoom != zoom) { 2350 _DeleteScaler(); 2351 } 2352 fZoom = zoom; 2353 FixupScrollBars(); 2354 Invalidate(); 2355 } 2356 2357 2358 void 2359 ShowImageView::ZoomIn() 2360 { 2361 if (fZoom < 16) 2362 SetZoom(fZoom + 0.25); 2363 } 2364 2365 2366 void 2367 ShowImageView::ZoomOut() 2368 { 2369 if (fZoom > 0.25) 2370 SetZoom(fZoom - 0.25); 2371 } 2372 2373 2374 void 2375 ShowImageView::SetSlideShowDelay(float seconds) 2376 { 2377 ShowImageSettings* settings; 2378 int32 delay = (int)(seconds * 10.0); 2379 if (fSlideShowDelay != delay) { 2380 // update counter 2381 fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown); 2382 if (fSlideShowCountDown <= 0) { 2383 // show next image on next Pulse() 2384 fSlideShowCountDown = 1; 2385 } 2386 fSlideShowDelay = delay; 2387 settings = my_app->Settings(); 2388 if (settings->Lock()) { 2389 settings->SetInt32("SlideShowDelay", fSlideShowDelay); 2390 settings->Unlock(); 2391 } 2392 } 2393 } 2394 2395 2396 void 2397 ShowImageView::StartSlideShow() 2398 { 2399 fSlideShow = true; fSlideShowCountDown = fSlideShowDelay; 2400 } 2401 2402 2403 void 2404 ShowImageView::StopSlideShow() 2405 { 2406 fSlideShow = false; 2407 } 2408 2409 2410 void 2411 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet) 2412 { 2413 BMessenger msgr; 2414 ImageProcessor imageProcessor(op, fBitmap, msgr, 0); 2415 imageProcessor.Start(false); 2416 BBitmap* bm = imageProcessor.DetachBitmap(); 2417 if (bm == NULL) { 2418 // operation failed 2419 return; 2420 } 2421 2422 // update orientation state 2423 if (op != ImageProcessor::kInvert) { 2424 // Note: If one of these fails, check its definition in class ImageProcessor. 2425 ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations); 2426 ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations); 2427 ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations); 2428 ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations); 2429 fImageOrientation = fTransformation[op][fImageOrientation]; 2430 } else { 2431 fInverted = !fInverted; 2432 } 2433 2434 if (!quiet) { 2435 // write orientation state 2436 BNode node(&fCurrentRef); 2437 int32 orientation = fImageOrientation; 2438 if (fInverted) orientation += 256; 2439 if (orientation != k0) { 2440 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation)); 2441 } else { 2442 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE); 2443 } 2444 } 2445 2446 // set new bitmap 2447 _DeleteBitmap(); 2448 fBitmap = bm; 2449 2450 if (!quiet) { 2451 // remove selection 2452 _SetHasSelection(false); 2453 _Notify(); 2454 } 2455 } 2456 2457 2458 //! image operation initiated by user 2459 void 2460 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet) 2461 { 2462 fUndo.Clear(); 2463 _DoImageOperation(op, quiet); 2464 } 2465 2466 2467 void 2468 ShowImageView::Rotate(int degree) 2469 { 2470 if (degree == 90) { 2471 _UserDoImageOperation(ImageProcessor::kRotateClockwise); 2472 } else if (degree == 270) { 2473 _UserDoImageOperation(ImageProcessor::kRotateCounterClockwise); 2474 } 2475 } 2476 2477 2478 void 2479 ShowImageView::Flip(bool vertical) 2480 { 2481 if (vertical) { 2482 _UserDoImageOperation(ImageProcessor::kFlipLeftToRight); 2483 } else { 2484 _UserDoImageOperation(ImageProcessor::kFlipTopToBottom); 2485 } 2486 } 2487 2488 2489 void 2490 ShowImageView::Invert() 2491 { 2492 if (fBitmap->ColorSpace() != B_CMAP8) { 2493 // Only allow an invert operation if the 2494 // bitmap color space is supported by the 2495 // invert algorithm 2496 _UserDoImageOperation(ImageProcessor::kInvert); 2497 } 2498 } 2499 2500 void 2501 ShowImageView::ResizeImage(int w, int h) 2502 { 2503 if (fBitmap == NULL || w < 1 || h < 1) 2504 return; 2505 2506 Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false); 2507 scaler.Start(false); 2508 BBitmap* scaled = scaler.DetachBitmap(); 2509 if (scaled == NULL) { 2510 // operation failed 2511 return; 2512 } 2513 2514 // remove selection 2515 _SetHasSelection(false); 2516 fUndo.Clear(); 2517 _DeleteBitmap(); 2518 fBitmap = scaled; 2519 2520 _SendMessageToWindow(MSG_MODIFIED); 2521 2522 _Notify(); 2523 } 2524 2525 void 2526 ShowImageView::_SetIcon(bool clear, icon_size which) 2527 { 2528 int32 size; 2529 switch (which) { 2530 case B_MINI_ICON: size = 16; 2531 break; 2532 case B_LARGE_ICON: size = 32; 2533 break; 2534 default: 2535 return; 2536 } 2537 2538 BRect rect(fBitmap->Bounds()); 2539 float s; 2540 s = size / (rect.Width()+1.0); 2541 2542 if (s * (rect.Height()+1.0) <= size) { 2543 rect.right = size-1; 2544 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 2545 // center vertically 2546 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2); 2547 } else { 2548 s = size / (rect.Height()+1.0); 2549 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 2550 rect.bottom = size-1; 2551 // center horizontally 2552 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0); 2553 } 2554 2555 // scale bitmap to thumbnail size 2556 BMessenger msgr; 2557 Scaler scaler(fBitmap, rect, msgr, 0, true); 2558 BBitmap* thumbnail = scaler.GetBitmap(); 2559 scaler.Start(false); 2560 ASSERT(thumbnail->ColorSpace() == B_CMAP8); 2561 // create icon from thumbnail 2562 BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8); 2563 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength()); 2564 BScreen screen; 2565 const uchar* src = (uchar*)thumbnail->Bits(); 2566 uchar* dest = (uchar*)icon.Bits(); 2567 const int32 srcBPR = thumbnail->BytesPerRow(); 2568 const int32 destBPR = icon.BytesPerRow(); 2569 const int32 dx = (int32)rect.left; 2570 const int32 dy = (int32)rect.top; 2571 2572 for (int32 y = 0; y <= rect.IntegerHeight(); y ++) { 2573 for (int32 x = 0; x <= rect.IntegerWidth(); x ++) { 2574 const uchar* s = src + y * srcBPR + x; 2575 uchar* d = dest + (y+dy) * destBPR + (x+dx); 2576 *d = *s; 2577 } 2578 } 2579 2580 // set icon 2581 BNode node(&fCurrentRef); 2582 BNodeInfo info(&node); 2583 info.SetIcon(clear ? NULL : &icon, which); 2584 } 2585 2586 2587 void 2588 ShowImageView::SetIcon(bool clear) 2589 { 2590 _SetIcon(clear, B_MINI_ICON); 2591 _SetIcon(clear, B_LARGE_ICON); 2592 } 2593 2594 2595 void 2596 ShowImageView::_ToggleSlideShow() 2597 { 2598 _SendMessageToWindow(MSG_SLIDE_SHOW); 2599 } 2600 2601 2602 void 2603 ShowImageView::_ExitFullScreen() 2604 { 2605 be_app->ShowCursor(); 2606 _SendMessageToWindow(MSG_EXIT_FULL_SCREEN); 2607 } 2608 2609 2610 void 2611 ShowImageView::WindowActivated(bool active) 2612 { 2613 fIsActiveWin = active; 2614 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 2615 } 2616 2617