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