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 if (fSelectionRect.Height() > 0.0 && fSelectionRect.Width() > 0.0) { 1025 BRect r(fSelectionRect); 1026 _ConstrainToImage(r); 1027 r = _ImageToView(r); 1028 // draw selection box *around* selection 1029 r.InsetBy(-1, -1); 1030 PushState(); 1031 rgb_color white = {255, 255, 255}; 1032 SetLowColor(white); 1033 StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft); 1034 StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp); 1035 StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight); 1036 StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown); 1037 PopState(); 1038 } 1039 } 1040 1041 1042 void 1043 ShowImageView::FrameResized(float /* width */, float /* height */) 1044 { 1045 FixupScrollBars(); 1046 } 1047 1048 1049 void 1050 ShowImageView::_ConstrainToImage(BPoint &point) 1051 { 1052 point.ConstrainTo(fBitmap->Bounds()); 1053 } 1054 1055 1056 void 1057 ShowImageView::_ConstrainToImage(BRect &rect) 1058 { 1059 rect = rect & fBitmap->Bounds(); 1060 } 1061 1062 1063 BBitmap* 1064 ShowImageView::_CopyFromRect(BRect srcRect) 1065 { 1066 BRect rect(0, 0, srcRect.Width(), srcRect.Height()); 1067 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1068 BBitmap *bitmap = new(nothrow) BBitmap(rect, fBitmap->ColorSpace(), true); 1069 if (bitmap == NULL || !bitmap->IsValid()) { 1070 delete bitmap; 1071 return NULL; 1072 } 1073 1074 if (bitmap->Lock()) { 1075 bitmap->AddChild(&view); 1076 view.DrawBitmap(fBitmap, srcRect, rect); 1077 view.Sync(); 1078 bitmap->RemoveChild(&view); 1079 bitmap->Unlock(); 1080 } 1081 1082 return bitmap; 1083 } 1084 1085 1086 BBitmap* 1087 ShowImageView::_CopySelection(uchar alpha, bool imageSize) 1088 { 1089 bool hasAlpha = alpha != 255; 1090 1091 if (!_HasSelection()) 1092 return NULL; 1093 1094 BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height()); 1095 if (!imageSize) { 1096 // scale image to view size 1097 rect.right = floorf((rect.right + 1.0) * fZoom - 1.0); 1098 rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0); 1099 } 1100 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1101 BBitmap *bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true); 1102 if (bitmap == NULL || !bitmap->IsValid()) { 1103 delete bitmap; 1104 return NULL; 1105 } 1106 1107 if (bitmap->Lock()) { 1108 bitmap->AddChild(&view); 1109 if (fSelBitmap) 1110 view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect); 1111 else 1112 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 1113 if (hasAlpha) { 1114 view.SetDrawingMode(B_OP_SUBTRACT); 1115 view.SetHighColor(0, 0, 0, 255-alpha); 1116 view.FillRect(rect, B_SOLID_HIGH); 1117 } 1118 view.Sync(); 1119 bitmap->RemoveChild(&view); 1120 bitmap->Unlock(); 1121 } 1122 1123 return bitmap; 1124 } 1125 1126 1127 bool 1128 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap) 1129 { 1130 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1131 if (roster == NULL) 1132 return false; 1133 1134 // add the current image mime first, will make it the preferred format on 1135 // left mouse drag 1136 msg->AddString("be:types", fImageMime); 1137 msg->AddString("be:filetypes", fImageMime); 1138 msg->AddString("be:type_descriptions", fImageType); 1139 1140 bool foundOther = false; 1141 bool foundCurrent = false; 1142 1143 int32 infoCount; 1144 translator_info* info; 1145 BBitmapStream stream(bitmap); 1146 if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) { 1147 for (int32 i = 0; i < infoCount; i++) { 1148 const translation_format* formats; 1149 int32 count; 1150 roster->GetOutputFormats(info[i].translator, &formats, &count); 1151 for (int32 j = 0; j < count; j++) { 1152 if (fImageMime == formats[j].MIME) { 1153 foundCurrent = true; 1154 } else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) { 1155 foundOther = true; 1156 // needed to send data in message 1157 msg->AddString("be:types", formats[j].MIME); 1158 // needed to pass data via file 1159 msg->AddString("be:filetypes", formats[j].MIME); 1160 msg->AddString("be:type_descriptions", formats[j].name); 1161 } 1162 } 1163 } 1164 } 1165 stream.DetachBitmap(&bitmap); 1166 1167 if (!foundCurrent) { 1168 msg->RemoveData("be:types", 0); 1169 msg->RemoveData("be:filetypes", 0); 1170 msg->RemoveData("be:type_descriptions", 0); 1171 } 1172 1173 return foundOther || foundCurrent; 1174 } 1175 1176 1177 void 1178 ShowImageView::_BeginDrag(BPoint sourcePoint) 1179 { 1180 BBitmap* bitmap = _CopySelection(128, false); 1181 if (bitmap == NULL) 1182 return; 1183 1184 SetMouseEventMask(B_POINTER_EVENTS); 1185 1186 // fill the drag message 1187 BMessage drag(B_SIMPLE_DATA); 1188 drag.AddInt32("be:actions", B_COPY_TARGET); 1189 drag.AddString("be:clip_name", "Bitmap Clip"); 1190 // ShowImage specific fields 1191 drag.AddPoint("be:_source_point", sourcePoint); 1192 drag.AddRect("be:_frame", fSelectionRect); 1193 if (_AddSupportedTypes(&drag, bitmap)) { 1194 // we also support "Passing Data via File" protocol 1195 drag.AddString("be:types", B_FILE_MIME_TYPE); 1196 // avoid flickering of dragged bitmap caused by drawing into the window 1197 _AnimateSelection(false); 1198 // only use a transparent bitmap on selections less than 400x400 1199 // (taking into account zooming) 1200 if ((fSelectionRect.Width() * fZoom) < 400.0 1201 && (fSelectionRect.Height() * fZoom) < 400.0) { 1202 sourcePoint -= fSelectionRect.LeftTop(); 1203 sourcePoint.x *= fZoom; 1204 sourcePoint.y *= fZoom; 1205 // DragMessage takes ownership of bitmap 1206 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint); 1207 bitmap = NULL; 1208 } else { 1209 delete bitmap; 1210 // Offset and scale the rect 1211 BRect rect(fSelectionRect); 1212 rect = _ImageToView(rect); 1213 rect.InsetBy(-1, -1); 1214 DragMessage(&drag, rect); 1215 } 1216 } 1217 } 1218 1219 1220 bool 1221 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type, 1222 translation_format* format) 1223 { 1224 bool found = false; 1225 1226 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1227 if (roster == NULL) 1228 return false; 1229 1230 BBitmapStream stream(bitmap); 1231 1232 translator_info *outInfo; 1233 int32 outNumInfo; 1234 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 1235 for (int32 i = 0; i < outNumInfo; i++) { 1236 const translation_format *fmts; 1237 int32 num_fmts; 1238 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 1239 for (int32 j = 0; j < num_fmts; j++) { 1240 if (strcmp(fmts[j].MIME, type) == 0) { 1241 *format = fmts[j]; 1242 found = true; 1243 break; 1244 } 1245 } 1246 } 1247 } 1248 stream.DetachBitmap(&bitmap); 1249 return found; 1250 } 1251 1252 1253 #undef B_TRANSLATE_CONTEXT 1254 #define B_TRANSLATE_CONTEXT "SaveToFile" 1255 1256 1257 void 1258 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, 1259 const translation_format* format) 1260 { 1261 if (bitmap == NULL) { 1262 // If no bitmap is supplied, write out the whole image 1263 bitmap = fBitmap; 1264 } 1265 1266 BBitmapStream stream(bitmap); 1267 1268 bool loop = true; 1269 while (loop) { 1270 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1271 if (!roster) 1272 break; 1273 // write data 1274 BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 1275 if (file.InitCheck() != B_OK) 1276 break; 1277 if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK) 1278 break; 1279 // set mime type 1280 BNodeInfo info(&file); 1281 if (info.InitCheck() == B_OK) 1282 info.SetType(format->MIME); 1283 1284 loop = false; 1285 // break out of loop gracefully (indicates no errors) 1286 } 1287 if (loop) { 1288 // If loop terminated because of a break, there was an error 1289 char buffer[512]; 1290 snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not " 1291 "be written."), name); 1292 BAlert *palert = new BAlert("", buffer, B_TRANSLATE("OK")); 1293 palert->Go(); 1294 } 1295 1296 stream.DetachBitmap(&bitmap); 1297 // Don't allow the bitmap to be deleted, this is 1298 // especially important when using fBitmap as the bitmap 1299 } 1300 1301 1302 void 1303 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format) 1304 { 1305 BMessage reply(B_MIME_DATA); 1306 BBitmapStream stream(bitmap); // destructor deletes bitmap 1307 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1308 BMallocIO memStream; 1309 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) { 1310 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength()); 1311 msg->SendReply(&reply); 1312 } 1313 } 1314 1315 1316 void 1317 ShowImageView::_HandleDrop(BMessage* msg) 1318 { 1319 entry_ref dirRef; 1320 BString name, type; 1321 bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK 1322 && msg->FindRef("directory", &dirRef) == B_OK 1323 && msg->FindString("name", &name) == B_OK; 1324 1325 bool sendInMessage = !saveToFile 1326 && msg->FindString("be:types", &type) == B_OK; 1327 1328 BBitmap* bitmap = _CopySelection(); 1329 if (bitmap == NULL) 1330 return; 1331 1332 translation_format format; 1333 if (!_OutputFormatForType(bitmap, type.String(), &format)) { 1334 delete bitmap; 1335 return; 1336 } 1337 1338 if (saveToFile) { 1339 BDirectory dir(&dirRef); 1340 SaveToFile(&dir, name.String(), bitmap, &format); 1341 delete bitmap; 1342 } else if (sendInMessage) { 1343 _SendInMessage(msg, bitmap, &format); 1344 } else { 1345 delete bitmap; 1346 } 1347 } 1348 1349 1350 void 1351 ShowImageView::_MoveImage() 1352 { 1353 BPoint point, delta; 1354 uint32 buttons; 1355 // get CURRENT position 1356 GetMouse(&point, &buttons); 1357 point = ConvertToScreen(point); 1358 delta = fFirstPoint - point; 1359 fFirstPoint = point; 1360 _ScrollRestrictedBy(delta.x, delta.y); 1361 1362 // in case we miss MouseUp 1363 if ((_GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0) 1364 fMovesImage = false; 1365 } 1366 1367 1368 uint32 1369 ShowImageView::_GetMouseButtons() 1370 { 1371 uint32 buttons; 1372 BPoint point; 1373 GetMouse(&point, &buttons); 1374 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1375 if ((modifiers() & B_CONTROL_KEY) != 0) { 1376 buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button 1377 } else if ((modifiers() & B_SHIFT_KEY) != 0) { 1378 buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button 1379 } 1380 } 1381 return buttons; 1382 } 1383 1384 1385 void 1386 ShowImageView::_GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits, 1387 BRect &destRect) 1388 { 1389 destRect = selection; 1390 _ConstrainToImage(destRect); 1391 1392 srcBits = selection; 1393 if (srcBits.left < 0) 1394 srcBits.left = -(srcBits.left); 1395 else 1396 srcBits.left = 0; 1397 1398 if (srcBits.top < 0) 1399 srcBits.top = -(srcBits.top); 1400 else 1401 srcBits.top = 0; 1402 1403 if (srcBits.right > fBitmap->Bounds().right) 1404 srcBits.right = srcBits.left + destRect.Width(); 1405 else 1406 srcBits.right = merge->Bounds().right; 1407 1408 if (srcBits.bottom > fBitmap->Bounds().bottom) 1409 srcBits.bottom = srcBits.top + destRect.Height(); 1410 else 1411 srcBits.bottom = merge->Bounds().bottom; 1412 } 1413 1414 1415 void 1416 ShowImageView::_GetSelMergeRects(BRect &srcBits, BRect &destRect) 1417 { 1418 _GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect); 1419 } 1420 1421 1422 void 1423 ShowImageView::_MergeWithBitmap(BBitmap *merge, BRect selection) 1424 { 1425 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1426 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1427 if (bitmap == NULL || !bitmap->IsValid()) { 1428 delete bitmap; 1429 return; 1430 } 1431 1432 if (bitmap->Lock()) { 1433 bitmap->AddChild(&view); 1434 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1435 BRect srcBits, destRect; 1436 _GetMergeRects(merge, selection, srcBits, destRect); 1437 view.DrawBitmap(merge, srcBits, destRect); 1438 1439 view.Sync(); 1440 bitmap->RemoveChild(&view); 1441 bitmap->Unlock(); 1442 1443 _DeleteBitmap(); 1444 fBitmap = bitmap; 1445 1446 _SendMessageToWindow(MSG_MODIFIED); 1447 } else 1448 delete bitmap; 1449 } 1450 1451 1452 void 1453 ShowImageView::_MergeSelection() 1454 { 1455 if (!_HasSelection()) 1456 return; 1457 1458 if (!fSelBitmap) { 1459 // Even though the merge will not change 1460 // the background image, I still need to save 1461 // some undo information here 1462 fUndo.SetTo(fSelectionRect, NULL, _CopySelection()); 1463 return; 1464 } 1465 1466 // Merge selection with background 1467 fUndo.SetTo(fSelectionRect, _CopyFromRect(fSelectionRect), _CopySelection()); 1468 _MergeWithBitmap(fSelBitmap, fSelectionRect); 1469 } 1470 1471 1472 void 1473 ShowImageView::MouseDown(BPoint position) 1474 { 1475 BPoint point; 1476 uint32 buttons; 1477 MakeFocus(true); 1478 1479 point = _ViewToImage(position); 1480 buttons = _GetMouseButtons(); 1481 1482 if (_HasSelection() && fSelectionRect.Contains(point) 1483 && (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) { 1484 if (!fSelBitmap) 1485 fSelBitmap = _CopySelection(); 1486 1487 BPoint sourcePoint = point; 1488 _BeginDrag(sourcePoint); 1489 1490 while (buttons) { 1491 // Keep reading mouse movement until 1492 // the user lets up on all mouse buttons 1493 GetMouse(&point, &buttons); 1494 snooze(25 * 1000); 1495 // sleep for 25 milliseconds to minimize CPU usage during loop 1496 } 1497 1498 if (Bounds().Contains(point)) { 1499 // If selection stayed inside this view 1500 // (Some of the selection may be in the border area, which can be OK) 1501 BPoint last, diff; 1502 last = _ViewToImage(point); 1503 diff = last - sourcePoint; 1504 1505 BRect newSelection = fSelectionRect; 1506 newSelection.OffsetBy(diff); 1507 1508 if (fBitmap->Bounds().Intersects(newSelection)) { 1509 // Do not accept the new selection box location 1510 // if it does not intersect with the bitmap rectangle 1511 fSelectionRect = newSelection; 1512 Invalidate(); 1513 } 1514 } 1515 1516 _AnimateSelection(true); 1517 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1518 _MergeSelection(); 1519 // If there is an existing selection, 1520 // Make it part of the background image 1521 1522 // begin new selection 1523 _SetHasSelection(true); 1524 fMakesSelection = true; 1525 SetMouseEventMask(B_POINTER_EVENTS); 1526 _ConstrainToImage(point); 1527 fFirstPoint = point; 1528 fCopyFromRect.Set(point.x, point.y, point.x, point.y); 1529 fSelectionRect = fCopyFromRect; 1530 Invalidate(); 1531 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1532 _ShowPopUpMenu(ConvertToScreen(position)); 1533 } else if (buttons == B_TERTIARY_MOUSE_BUTTON) { 1534 // move image in window 1535 SetMouseEventMask(B_POINTER_EVENTS); 1536 fMovesImage = true; 1537 fFirstPoint = ConvertToScreen(position); 1538 } 1539 } 1540 1541 1542 void 1543 ShowImageView::_UpdateSelectionRect(BPoint point, bool final) 1544 { 1545 BRect oldSelection = fCopyFromRect; 1546 point = _ViewToImage(point); 1547 _ConstrainToImage(point); 1548 fCopyFromRect.left = min_c(fFirstPoint.x, point.x); 1549 fCopyFromRect.right = max_c(fFirstPoint.x, point.x); 1550 fCopyFromRect.top = min_c(fFirstPoint.y, point.y); 1551 fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y); 1552 fSelectionRect = fCopyFromRect; 1553 1554 if (final) { 1555 // selection must be at least 2 pixels wide or 2 pixels tall 1556 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0) 1557 _SetHasSelection(false); 1558 } else 1559 _UpdateStatusText(); 1560 1561 if (oldSelection != fCopyFromRect || !_HasSelection()) { 1562 BRect updateRect; 1563 updateRect = oldSelection | fCopyFromRect; 1564 updateRect = _ImageToView(updateRect); 1565 updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE); 1566 Invalidate(updateRect); 1567 } 1568 } 1569 1570 1571 void 1572 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message) 1573 { 1574 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 1575 if (fMakesSelection) { 1576 _UpdateSelectionRect(point, false); 1577 } else if (fMovesImage) { 1578 _MoveImage(); 1579 } 1580 } 1581 1582 1583 void 1584 ShowImageView::MouseUp(BPoint point) 1585 { 1586 if (fMakesSelection) { 1587 _UpdateSelectionRect(point, true); 1588 fMakesSelection = false; 1589 } else if (fMovesImage) { 1590 _MoveImage(); 1591 fMovesImage = false; 1592 } 1593 _AnimateSelection(true); 1594 } 1595 1596 1597 float 1598 ShowImageView::_LimitToRange(float v, orientation o, bool absolute) 1599 { 1600 BScrollBar* psb = ScrollBar(o); 1601 if (psb) { 1602 float min, max, pos; 1603 pos = v; 1604 if (!absolute) 1605 pos += psb->Value(); 1606 1607 psb->GetRange(&min, &max); 1608 if (pos < min) 1609 pos = min; 1610 else if (pos > max) 1611 pos = max; 1612 1613 v = pos; 1614 if (!absolute) 1615 v -= psb->Value(); 1616 } 1617 return v; 1618 } 1619 1620 1621 void 1622 ShowImageView::_ScrollRestricted(float x, float y, bool absolute) 1623 { 1624 if (x != 0) 1625 x = _LimitToRange(x, B_HORIZONTAL, absolute); 1626 1627 if (y != 0) 1628 y = _LimitToRange(y, B_VERTICAL, absolute); 1629 1630 // hide the caption when using mouse wheel 1631 // in full screen mode 1632 // to prevent the caption from dirtying up the image 1633 // during scrolling. 1634 bool caption = fShowCaption; 1635 if (caption) { 1636 fShowCaption = false; 1637 _UpdateCaption(); 1638 } 1639 1640 ScrollBy(x, y); 1641 1642 if (caption) { 1643 // show the caption again 1644 fShowCaption = true; 1645 _UpdateCaption(); 1646 } 1647 } 1648 1649 1650 // XXX method is not unused 1651 void 1652 ShowImageView::_ScrollRestrictedTo(float x, float y) 1653 { 1654 _ScrollRestricted(x, y, true); 1655 } 1656 1657 1658 void 1659 ShowImageView::_ScrollRestrictedBy(float x, float y) 1660 { 1661 _ScrollRestricted(x, y, false); 1662 } 1663 1664 1665 void 1666 ShowImageView::KeyDown(const char* bytes, int32 numBytes) 1667 { 1668 if (numBytes != 1) { 1669 BView::KeyDown(bytes, numBytes); 1670 return; 1671 } 1672 1673 bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0; 1674 1675 switch (*bytes) { 1676 case B_DOWN_ARROW: 1677 if (shiftKeyDown) 1678 _ScrollRestrictedBy(0, 10); 1679 else 1680 _SendMessageToWindow(MSG_FILE_NEXT); 1681 break; 1682 case B_RIGHT_ARROW: 1683 if (shiftKeyDown) 1684 _ScrollRestrictedBy(10, 0); 1685 else 1686 _SendMessageToWindow(MSG_FILE_NEXT); 1687 break; 1688 case B_UP_ARROW: 1689 if (shiftKeyDown) 1690 _ScrollRestrictedBy(0, -10); 1691 else 1692 _SendMessageToWindow(MSG_FILE_PREV); 1693 break; 1694 case B_LEFT_ARROW: 1695 if (shiftKeyDown) 1696 _ScrollRestrictedBy(-10, 0); 1697 else 1698 _SendMessageToWindow(MSG_FILE_PREV); 1699 break; 1700 case B_BACKSPACE: 1701 _SendMessageToWindow(MSG_FILE_PREV); 1702 break; 1703 case B_HOME: 1704 break; 1705 case B_END: 1706 break; 1707 case B_SPACE: 1708 _ToggleSlideShow(); 1709 break; 1710 case B_ESCAPE: 1711 // stop slide show 1712 if (fSlideShow) 1713 _ToggleSlideShow(); 1714 1715 _ExitFullScreen(); 1716 1717 ClearSelection(); 1718 break; 1719 case B_DELETE: 1720 { 1721 // Move image to Trash 1722 BMessage trash(BPrivate::kMoveToTrash); 1723 trash.AddRef("refs", &fCurrentRef); 1724 // We create our own messenger because the member fTrackerMessenger 1725 // could be invalid 1726 BMessenger tracker(kTrackerSignature); 1727 if (tracker.SendMessage(&trash) == B_OK) 1728 if (!NextFile()) { 1729 // This is the last (or only file) in this directory, 1730 // close the window 1731 _SendMessageToWindow(B_QUIT_REQUESTED); 1732 } 1733 break; 1734 } 1735 case '+': 1736 case '=': 1737 ZoomIn(); 1738 break; 1739 case '-': 1740 ZoomOut(); 1741 break; 1742 case '[': 1743 Rotate(270); 1744 break; 1745 case ']': 1746 Rotate(90); 1747 break; 1748 } 1749 } 1750 1751 1752 void 1753 ShowImageView::_MouseWheelChanged(BMessage *msg) 1754 { 1755 // The BeOS driver does not currently support 1756 // X wheel scrolling, therefore, dx is zero. 1757 // |dy| is the number of notches scrolled up or down. 1758 // When the wheel is scrolled down (towards the user) dy > 0 1759 // When the wheel is scrolled up (away from the user) dy < 0 1760 const float kscrollBy = 40; 1761 float dy, dx; 1762 float x, y; 1763 x = 0; y = 0; 1764 if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) 1765 x = dx * kscrollBy; 1766 if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) 1767 y = dy * kscrollBy; 1768 1769 if (modifiers() & B_SHIFT_KEY) 1770 _ScrollRestrictedBy(x, y); 1771 else if (modifiers() & B_COMMAND_KEY) 1772 _ScrollRestrictedBy(y, x); 1773 else { 1774 if (dy < 0) 1775 ZoomIn(); 1776 else if (dy > 0) 1777 ZoomOut(); 1778 } 1779 } 1780 1781 1782 void 1783 ShowImageView::_ShowPopUpMenu(BPoint screen) 1784 { 1785 if (!fShowingPopUpMenu) { 1786 PopUpMenu* menu = new PopUpMenu("PopUpMenu", this); 1787 1788 ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window()); 1789 if (showImage) 1790 showImage->BuildContextMenu(menu); 1791 1792 screen += BPoint(2, 2); 1793 menu->Go(screen, true, true, true); 1794 fShowingPopUpMenu = true; 1795 } 1796 } 1797 1798 1799 void 1800 ShowImageView::_SettingsSetBool(const char* name, bool value) 1801 { 1802 ShowImageSettings* settings; 1803 settings = my_app->Settings(); 1804 if (settings->Lock()) { 1805 settings->SetBool(name, value); 1806 settings->Unlock(); 1807 } 1808 } 1809 1810 1811 void 1812 ShowImageView::MessageReceived(BMessage *message) 1813 { 1814 switch (message->what) { 1815 case MSG_SELECTION_BITMAP: 1816 { 1817 // In response to a B_SIMPLE_DATA message, a view will 1818 // send this message and expect a reply with a pointer to 1819 // the currently selected bitmap clip. Although this view 1820 // allocates the BBitmap * sent in the reply, it is only 1821 // to be used and deleted by the view that is being replied to. 1822 BMessage msg; 1823 msg.AddPointer("be:_bitmap_ptr", _CopySelection()); 1824 message->SendReply(&msg); 1825 break; 1826 } 1827 1828 case B_SIMPLE_DATA: 1829 if (message->WasDropped()) { 1830 uint32 type; 1831 int32 count; 1832 status_t ret = message->GetInfo("refs", &type, &count); 1833 if (ret == B_OK && type == B_REF_TYPE) { 1834 // If file was dropped, open it as the selection 1835 entry_ref ref; 1836 if (message->FindRef("refs", 0, &ref) == B_OK) { 1837 BPoint point = message->DropPoint(); 1838 point = ConvertFromScreen(point); 1839 point = _ViewToImage(point); 1840 _SetSelection(&ref, point); 1841 } 1842 } else { 1843 // If a user drags a clip from another ShowImage window, 1844 // request a BBitmap pointer to that clip, allocated by the 1845 // other view, for use solely by this view, so that it can 1846 // be dropped/pasted onto this view. 1847 BMessenger retMsgr, localMsgr(this); 1848 retMsgr = message->ReturnAddress(); 1849 if (retMsgr != localMsgr) { 1850 BMessage msgReply; 1851 retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply); 1852 BBitmap *bitmap = NULL; 1853 if (msgReply.FindPointer("be:_bitmap_ptr", 1854 reinterpret_cast<void **>(&bitmap)) == B_OK) { 1855 BRect sourceRect; 1856 BPoint point, sourcePoint; 1857 message->FindPoint("be:_source_point", &sourcePoint); 1858 message->FindRect("be:_frame", &sourceRect); 1859 point = message->DropPoint(); 1860 point.Set(point.x - (sourcePoint.x - sourceRect.left), 1861 point.y - (sourcePoint.y - sourceRect.top)); 1862 // adjust drop point before scaling is factored in 1863 point = ConvertFromScreen(point); 1864 point = _ViewToImage(point); 1865 1866 _PasteBitmap(bitmap, point); 1867 } 1868 } 1869 } 1870 } 1871 break; 1872 1873 case B_COPY_TARGET: 1874 _HandleDrop(message); 1875 break; 1876 case B_MOUSE_WHEEL_CHANGED: 1877 _MouseWheelChanged(message); 1878 break; 1879 case MSG_INVALIDATE: 1880 Invalidate(); 1881 break; 1882 1883 case kMsgPopUpMenuClosed: 1884 fShowingPopUpMenu = false; 1885 break; 1886 1887 default: 1888 BView::MessageReceived(message); 1889 break; 1890 } 1891 } 1892 1893 1894 void 1895 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) 1896 { 1897 float prop, range; 1898 BScrollBar *psb; 1899 1900 psb = ScrollBar(o); 1901 if (psb) { 1902 range = bitmapLength - viewLength; 1903 if (range < 0.0) { 1904 range = 0.0; 1905 } 1906 prop = viewLength / bitmapLength; 1907 if (prop > 1.0) { 1908 prop = 1.0; 1909 } 1910 psb->SetRange(0, range); 1911 psb->SetProportion(prop); 1912 psb->SetSteps(10, 100); 1913 } 1914 } 1915 1916 1917 void 1918 ShowImageView::FixupScrollBars() 1919 { 1920 BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0); 1921 if (fBitmap) { 1922 rctbitmap = _AlignBitmap(); 1923 rctbitmap.OffsetTo(0, 0); 1924 } 1925 1926 FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width()); 1927 FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height()); 1928 } 1929 1930 1931 int32 1932 ShowImageView::CurrentPage() 1933 { 1934 return fDocumentIndex; 1935 } 1936 1937 1938 int32 1939 ShowImageView::PageCount() 1940 { 1941 return fDocumentCount; 1942 } 1943 1944 1945 void 1946 ShowImageView::Undo() 1947 { 1948 int32 undoType = fUndo.GetType(); 1949 if (undoType != UNDO_UNDO && undoType != UNDO_REDO) 1950 return; 1951 1952 // backup current selection 1953 BRect undoneSelRect; 1954 BBitmap *undoneSelection; 1955 undoneSelRect = fSelectionRect; 1956 undoneSelection = _CopySelection(); 1957 1958 if (undoType == UNDO_UNDO) { 1959 BBitmap *undoRestore; 1960 undoRestore = fUndo.GetRestoreBitmap(); 1961 if (undoRestore) 1962 _MergeWithBitmap(undoRestore, fUndo.GetRect()); 1963 } 1964 1965 // restore previous image/selection 1966 BBitmap *undoSelection; 1967 undoSelection = fUndo.GetSelectionBitmap(); 1968 // NOTE: ShowImageView is responsible for deleting this bitmap 1969 // (Which it will, as it would with a fSelBitmap that it allocated itself) 1970 if (!undoSelection) 1971 _SetHasSelection(false); 1972 else { 1973 fCopyFromRect = BRect(); 1974 fSelectionRect = fUndo.GetRect(); 1975 _SetHasSelection(true); 1976 fSelBitmap = undoSelection; 1977 } 1978 1979 fUndo.Undo(undoneSelRect, NULL, undoneSelection); 1980 1981 Invalidate(); 1982 } 1983 1984 1985 void 1986 ShowImageView::_AddWhiteRect(BRect &rect) 1987 { 1988 // Paint white rectangle, using rect, into the background image 1989 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1990 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1991 if (bitmap == NULL || !bitmap->IsValid()) { 1992 delete bitmap; 1993 return; 1994 } 1995 1996 if (bitmap->Lock()) { 1997 bitmap->AddChild(&view); 1998 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1999 2000 view.FillRect(rect, B_SOLID_LOW); 2001 // draw white rect 2002 2003 view.Sync(); 2004 bitmap->RemoveChild(&view); 2005 bitmap->Unlock(); 2006 2007 _DeleteBitmap(); 2008 fBitmap = bitmap; 2009 2010 _SendMessageToWindow(MSG_MODIFIED); 2011 } else 2012 delete bitmap; 2013 } 2014 2015 2016 void 2017 ShowImageView::_RemoveSelection(bool toClipboard, bool neverCutBackground) 2018 { 2019 if (!_HasSelection()) 2020 return; 2021 2022 BRect rect = fSelectionRect; 2023 bool cutBackground = (fSelBitmap) ? false : true; 2024 BBitmap *selection, *restore = NULL; 2025 selection = _CopySelection(); 2026 2027 if (toClipboard) 2028 CopySelectionToClipboard(); 2029 2030 _SetHasSelection(false); 2031 2032 if (!neverCutBackground && cutBackground) { 2033 // If the user hasn't dragged the selection, 2034 // paint a white rectangle where the selection was 2035 restore = _CopyFromRect(rect); 2036 _AddWhiteRect(rect); 2037 } 2038 2039 fUndo.SetTo(rect, restore, selection); 2040 Invalidate(); 2041 } 2042 2043 2044 void 2045 ShowImageView::Cut() 2046 { 2047 // Copy the selection to the clipboard, 2048 // then remove it 2049 _RemoveSelection(true); 2050 } 2051 2052 2053 status_t 2054 ShowImageView::_PasteBitmap(BBitmap *bitmap, BPoint point) 2055 { 2056 if (bitmap && bitmap->IsValid()) { 2057 _MergeSelection(); 2058 2059 fCopyFromRect = BRect(); 2060 fSelectionRect = bitmap->Bounds(); 2061 _SetHasSelection(true); 2062 fSelBitmap = bitmap; 2063 2064 BRect offsetRect = fSelectionRect; 2065 offsetRect.OffsetBy(point); 2066 if (fBitmap->Bounds().Intersects(offsetRect)) 2067 // Move the selection rectangle to desired origin, 2068 // but only if the resulting selection rectangle 2069 // intersects with the background bitmap rectangle 2070 fSelectionRect = offsetRect; 2071 2072 Invalidate(); 2073 2074 return B_OK; 2075 } 2076 2077 return B_ERROR; 2078 } 2079 2080 2081 void 2082 ShowImageView::Paste() 2083 { 2084 if (be_clipboard->Lock()) { 2085 BMessage *pclip; 2086 if ((pclip = be_clipboard->Data()) != NULL) { 2087 BPoint point(0, 0); 2088 pclip->FindPoint("be:location", &point); 2089 BBitmap *pbits; 2090 pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip)); 2091 _PasteBitmap(pbits, point); 2092 } 2093 2094 be_clipboard->Unlock(); 2095 } 2096 } 2097 2098 2099 void 2100 ShowImageView::SelectAll() 2101 { 2102 _SetHasSelection(true); 2103 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()); 2104 fSelectionRect = fCopyFromRect; 2105 Invalidate(); 2106 } 2107 2108 2109 void 2110 ShowImageView::ClearSelection() 2111 { 2112 // Remove the selection, 2113 // DON'T copy it to the clipboard 2114 // or white out the selection 2115 _RemoveSelection(false, true); 2116 } 2117 2118 2119 void 2120 ShowImageView::_SetHasSelection(bool bHasSelection) 2121 { 2122 _DeleteSelBitmap(); 2123 fHasSelection = bHasSelection; 2124 2125 _UpdateStatusText(); 2126 2127 BMessage msg(MSG_SELECTION); 2128 msg.AddBool("has_selection", fHasSelection); 2129 _SendMessageToWindow(&msg); 2130 } 2131 2132 2133 void 2134 ShowImageView::CopySelectionToClipboard() 2135 { 2136 if (_HasSelection() && be_clipboard->Lock()) { 2137 be_clipboard->Clear(); 2138 BMessage *clip = NULL; 2139 if ((clip = be_clipboard->Data()) != NULL) { 2140 BMessage data; 2141 BBitmap* bitmap = _CopySelection(); 2142 if (bitmap != NULL) { 2143 #if 0 2144 // According to BeBook and Becasso, Gobe Productive do the following. 2145 // Paste works in Productive, but not in Becasso and original ShowImage. 2146 BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused 2147 bitmap->Archive(&msg); 2148 clip->AddMessage("image/x-be-bitmap", &msg); 2149 #else 2150 // original ShowImage performs this. Paste works with original ShowImage. 2151 bitmap->Archive(clip); 2152 // original ShowImage uses be:location for insertion point 2153 clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top)); 2154 #endif 2155 delete bitmap; 2156 be_clipboard->Commit(); 2157 } 2158 } 2159 be_clipboard->Unlock(); 2160 } 2161 } 2162 2163 2164 void 2165 ShowImageView::FirstPage() 2166 { 2167 if (fDocumentIndex != 1) { 2168 fDocumentIndex = 1; 2169 SetImage(NULL); 2170 } 2171 } 2172 2173 2174 void 2175 ShowImageView::LastPage() 2176 { 2177 if (fDocumentIndex != fDocumentCount) { 2178 fDocumentIndex = fDocumentCount; 2179 SetImage(NULL); 2180 } 2181 } 2182 2183 2184 void 2185 ShowImageView::NextPage() 2186 { 2187 if (fDocumentIndex < fDocumentCount) { 2188 fDocumentIndex++; 2189 SetImage(NULL); 2190 } 2191 } 2192 2193 2194 void 2195 ShowImageView::PrevPage() 2196 { 2197 if (fDocumentIndex > 1) { 2198 fDocumentIndex--; 2199 SetImage(NULL); 2200 } 2201 } 2202 2203 2204 int 2205 ShowImageView::_CompareEntries(const void* a, const void* b) 2206 { 2207 entry_ref *r1, *r2; 2208 r1 = *(entry_ref**)a; 2209 r2 = *(entry_ref**)b; 2210 return strcasecmp(r1->name, r2->name); 2211 } 2212 2213 2214 void 2215 ShowImageView::GoToPage(int32 page) 2216 { 2217 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 2218 fDocumentIndex = page; 2219 SetImage(NULL); 2220 } 2221 } 2222 2223 2224 void 2225 ShowImageView::_FreeEntries(BList* entries) 2226 { 2227 const int32 n = entries->CountItems(); 2228 for (int32 i = 0; i < n; i ++) { 2229 entry_ref* ref = (entry_ref*)entries->ItemAt(i); 2230 delete ref; 2231 } 2232 entries->MakeEmpty(); 2233 } 2234 2235 2236 void 2237 ShowImageView::_SetTrackerSelectionToCurrent() 2238 { 2239 BMessage setsel(B_SET_PROPERTY); 2240 setsel.AddSpecifier("Selection"); 2241 setsel.AddRef("data", &fCurrentRef); 2242 fTrackerMessenger.SendMessage(&setsel); 2243 } 2244 2245 2246 bool 2247 ShowImageView::_FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, 2248 bool rewind) 2249 { 2250 // Based on GetTrackerWindowFile function from BeMail 2251 if (!fTrackerMessenger.IsValid()) 2252 return false; 2253 2254 // 2255 // Ask the Tracker what the next/prev file in the window is. 2256 // Continue asking for the next reference until a valid 2257 // image is found. 2258 // 2259 entry_ref nextRef = *in_current; 2260 bool foundRef = false; 2261 while (!foundRef) 2262 { 2263 BMessage request(B_GET_PROPERTY); 2264 BMessage spc; 2265 if (rewind) 2266 spc.what = B_DIRECT_SPECIFIER; 2267 else if (next) 2268 spc.what = 'snxt'; 2269 else 2270 spc.what = 'sprv'; 2271 spc.AddString("property", "Entry"); 2272 if (rewind) 2273 // if rewinding, ask for the ref to the 2274 // first item in the directory 2275 spc.AddInt32("data", 0); 2276 else 2277 spc.AddRef("data", &nextRef); 2278 request.AddSpecifier(&spc); 2279 2280 BMessage reply; 2281 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 2282 return false; 2283 if (reply.FindRef("result", &nextRef) != B_OK) 2284 return false; 2285 2286 if (_IsImage(&nextRef)) 2287 foundRef = true; 2288 2289 rewind = false; 2290 // stop asking for the first ref in the directory 2291 } 2292 2293 *ref = nextRef; 2294 return foundRef; 2295 } 2296 2297 bool 2298 ShowImageView::_ShowNextImage(bool next, bool rewind) 2299 { 2300 entry_ref curRef = fCurrentRef; 2301 entry_ref imgRef; 2302 bool found = _FindNextImage(&curRef, &imgRef, next, rewind); 2303 if (found) { 2304 // Keep trying to load images until: 2305 // 1. The image loads successfully 2306 // 2. The last file in the directory is found (for find next or find first) 2307 // 3. The first file in the directory is found (for find prev) 2308 // 4. The call to _FindNextImage fails for any other reason 2309 while (SetImage(&imgRef) != B_OK) { 2310 curRef = imgRef; 2311 found = _FindNextImage(&curRef, &imgRef, next, false); 2312 if (!found) 2313 return false; 2314 } 2315 _SetTrackerSelectionToCurrent(); 2316 return true; 2317 } 2318 return false; 2319 } 2320 2321 2322 bool 2323 ShowImageView::NextFile() 2324 { 2325 return _ShowNextImage(true, false); 2326 } 2327 2328 2329 bool 2330 ShowImageView::PrevFile() 2331 { 2332 return _ShowNextImage(false, false); 2333 } 2334 2335 2336 bool 2337 ShowImageView::HasNextFile() 2338 { 2339 entry_ref ref; 2340 return _FindNextImage(&fCurrentRef, &ref, true, false); 2341 } 2342 2343 2344 bool 2345 ShowImageView::HasPrevFile() 2346 { 2347 entry_ref ref; 2348 return _FindNextImage(&fCurrentRef, &ref, false, false); 2349 } 2350 2351 2352 bool 2353 ShowImageView::_FirstFile() 2354 { 2355 return _ShowNextImage(true, true); 2356 } 2357 2358 2359 void 2360 ShowImageView::SetZoom(float zoom) 2361 { 2362 if ((fScaleBilinear || fDither) && fZoom != zoom) { 2363 _DeleteScaler(); 2364 } 2365 fZoom = zoom; 2366 FixupScrollBars(); 2367 Invalidate(); 2368 } 2369 2370 2371 void 2372 ShowImageView::ZoomIn() 2373 { 2374 if (fZoom < 16) 2375 SetZoom(fZoom + 0.25); 2376 } 2377 2378 2379 void 2380 ShowImageView::ZoomOut() 2381 { 2382 if (fZoom > 0.25) 2383 SetZoom(fZoom - 0.25); 2384 } 2385 2386 2387 void 2388 ShowImageView::SetSlideShowDelay(float seconds) 2389 { 2390 ShowImageSettings* settings; 2391 int32 delay = (int)(seconds * 10.0); 2392 if (fSlideShowDelay != delay) { 2393 // update counter 2394 fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown); 2395 if (fSlideShowCountDown <= 0) { 2396 // show next image on next Pulse() 2397 fSlideShowCountDown = 1; 2398 } 2399 fSlideShowDelay = delay; 2400 settings = my_app->Settings(); 2401 if (settings->Lock()) { 2402 settings->SetInt32("SlideShowDelay", fSlideShowDelay); 2403 settings->Unlock(); 2404 } 2405 } 2406 } 2407 2408 2409 void 2410 ShowImageView::StartSlideShow() 2411 { 2412 fSlideShow = true; fSlideShowCountDown = fSlideShowDelay; 2413 } 2414 2415 2416 void 2417 ShowImageView::StopSlideShow() 2418 { 2419 fSlideShow = false; 2420 } 2421 2422 2423 void 2424 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet) 2425 { 2426 BMessenger msgr; 2427 ImageProcessor imageProcessor(op, fBitmap, msgr, 0); 2428 imageProcessor.Start(false); 2429 BBitmap* bm = imageProcessor.DetachBitmap(); 2430 if (bm == NULL) { 2431 // operation failed 2432 return; 2433 } 2434 2435 // update orientation state 2436 if (op != ImageProcessor::kInvert) { 2437 // Note: If one of these fails, check its definition in class ImageProcessor. 2438 ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations); 2439 ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations); 2440 ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations); 2441 ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations); 2442 fImageOrientation = fTransformation[op][fImageOrientation]; 2443 } else { 2444 fInverted = !fInverted; 2445 } 2446 2447 if (!quiet) { 2448 // write orientation state 2449 BNode node(&fCurrentRef); 2450 int32 orientation = fImageOrientation; 2451 if (fInverted) orientation += 256; 2452 if (orientation != k0) { 2453 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, 2454 &orientation, sizeof(orientation)); 2455 } else { 2456 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE); 2457 } 2458 } 2459 2460 // set new bitmap 2461 _DeleteBitmap(); 2462 fBitmap = bm; 2463 2464 if (!quiet) { 2465 // remove selection 2466 _SetHasSelection(false); 2467 _Notify(); 2468 } 2469 } 2470 2471 2472 //! image operation initiated by user 2473 void 2474 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet) 2475 { 2476 fUndo.Clear(); 2477 _DoImageOperation(op, quiet); 2478 } 2479 2480 2481 void 2482 ShowImageView::Rotate(int degree) 2483 { 2484 if (degree == 90) { 2485 _UserDoImageOperation(ImageProcessor::kRotateClockwise); 2486 } else if (degree == 270) { 2487 _UserDoImageOperation(ImageProcessor::kRotateCounterClockwise); 2488 } 2489 } 2490 2491 2492 void 2493 ShowImageView::Flip(bool vertical) 2494 { 2495 if (vertical) { 2496 _UserDoImageOperation(ImageProcessor::kFlipLeftToRight); 2497 } else { 2498 _UserDoImageOperation(ImageProcessor::kFlipTopToBottom); 2499 } 2500 } 2501 2502 2503 void 2504 ShowImageView::Invert() 2505 { 2506 if (fBitmap->ColorSpace() != B_CMAP8) { 2507 // Only allow an invert operation if the 2508 // bitmap color space is supported by the 2509 // invert algorithm 2510 _UserDoImageOperation(ImageProcessor::kInvert); 2511 } 2512 } 2513 2514 void 2515 ShowImageView::ResizeImage(int w, int h) 2516 { 2517 if (fBitmap == NULL || w < 1 || h < 1) 2518 return; 2519 2520 Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false); 2521 scaler.Start(false); 2522 BBitmap* scaled = scaler.DetachBitmap(); 2523 if (scaled == NULL) { 2524 // operation failed 2525 return; 2526 } 2527 2528 // remove selection 2529 _SetHasSelection(false); 2530 fUndo.Clear(); 2531 _DeleteBitmap(); 2532 fBitmap = scaled; 2533 2534 _SendMessageToWindow(MSG_MODIFIED); 2535 2536 _Notify(); 2537 } 2538 2539 void 2540 ShowImageView::_SetIcon(bool clear, icon_size which) 2541 { 2542 int32 size; 2543 switch (which) { 2544 case B_MINI_ICON: size = 16; 2545 break; 2546 case B_LARGE_ICON: size = 32; 2547 break; 2548 default: 2549 return; 2550 } 2551 2552 BRect rect(fBitmap->Bounds()); 2553 float s; 2554 s = size / (rect.Width()+1.0); 2555 2556 if (s * (rect.Height()+1.0) <= size) { 2557 rect.right = size-1; 2558 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 2559 // center vertically 2560 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2); 2561 } else { 2562 s = size / (rect.Height()+1.0); 2563 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 2564 rect.bottom = size-1; 2565 // center horizontally 2566 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0); 2567 } 2568 2569 // scale bitmap to thumbnail size 2570 BMessenger msgr; 2571 Scaler scaler(fBitmap, rect, msgr, 0, true); 2572 BBitmap* thumbnail = scaler.GetBitmap(); 2573 scaler.Start(false); 2574 ASSERT(thumbnail->ColorSpace() == B_CMAP8); 2575 // create icon from thumbnail 2576 BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8); 2577 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength()); 2578 BScreen screen; 2579 const uchar* src = (uchar*)thumbnail->Bits(); 2580 uchar* dest = (uchar*)icon.Bits(); 2581 const int32 srcBPR = thumbnail->BytesPerRow(); 2582 const int32 destBPR = icon.BytesPerRow(); 2583 const int32 dx = (int32)rect.left; 2584 const int32 dy = (int32)rect.top; 2585 2586 for (int32 y = 0; y <= rect.IntegerHeight(); y ++) { 2587 for (int32 x = 0; x <= rect.IntegerWidth(); x ++) { 2588 const uchar* s = src + y * srcBPR + x; 2589 uchar* d = dest + (y+dy) * destBPR + (x+dx); 2590 *d = *s; 2591 } 2592 } 2593 2594 // set icon 2595 BNode node(&fCurrentRef); 2596 BNodeInfo info(&node); 2597 info.SetIcon(clear ? NULL : &icon, which); 2598 } 2599 2600 2601 void 2602 ShowImageView::SetIcon(bool clear) 2603 { 2604 _SetIcon(clear, B_MINI_ICON); 2605 _SetIcon(clear, B_LARGE_ICON); 2606 } 2607 2608 2609 void 2610 ShowImageView::_ToggleSlideShow() 2611 { 2612 _SendMessageToWindow(MSG_SLIDE_SHOW); 2613 } 2614 2615 2616 void 2617 ShowImageView::_ExitFullScreen() 2618 { 2619 be_app->ShowCursor(); 2620 _SendMessageToWindow(MSG_EXIT_FULL_SCREEN); 2621 } 2622 2623 2624 void 2625 ShowImageView::WindowActivated(bool active) 2626 { 2627 fIsActiveWin = active; 2628 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 2629 } 2630 2631