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