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