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 597 AddToRecentDocuments(); 598 599 Notify(); 600 return B_OK; 601 } 602 603 604 status_t 605 ShowImageView::SetSelection(const entry_ref *ref, BPoint point) 606 { 607 BTranslatorRoster *roster = BTranslatorRoster::Default(); 608 if (!roster) 609 return B_ERROR; 610 611 BFile file(ref, B_READ_ONLY); 612 translator_info info; 613 memset(&info, 0, sizeof(translator_info)); 614 if (roster->Identify(&file, NULL, &info, 0, NULL, 615 B_TRANSLATOR_BITMAP) != B_OK) 616 return B_ERROR; 617 618 // Translate image data and create a new ShowImage window 619 BBitmapStream outstream; 620 if (roster->Translate(&file, &info, NULL, &outstream, 621 B_TRANSLATOR_BITMAP) != B_OK) 622 return B_ERROR; 623 624 BBitmap *newBitmap = NULL; 625 if (outstream.DetachBitmap(&newBitmap) != B_OK) 626 return B_ERROR; 627 628 return PasteBitmap(newBitmap, point); 629 } 630 631 632 void 633 ShowImageView::SetDither(bool dither) 634 { 635 if (fDither != dither) { 636 SettingsSetBool("Dither", dither); 637 fDither = dither; 638 Invalidate(); 639 } 640 } 641 642 643 void 644 ShowImageView::SetShowCaption(bool show) 645 { 646 if (fShowCaption != show) { 647 fShowCaption = show; 648 UpdateCaption(); 649 } 650 } 651 652 653 void 654 ShowImageView::SetShrinkToBounds(bool enable) 655 { 656 if (fShrinkToBounds != enable) { 657 SettingsSetBool("ShrinkToBounds", enable); 658 fShrinkToBounds = enable; 659 FixupScrollBars(); 660 Invalidate(); 661 } 662 } 663 664 665 void 666 ShowImageView::SetZoomToBounds(bool enable) 667 { 668 if (fZoomToBounds != enable) { 669 SettingsSetBool("ZoomToBounds", enable); 670 fZoomToBounds = enable; 671 FixupScrollBars(); 672 Invalidate(); 673 } 674 } 675 676 677 void 678 ShowImageView::SetFullScreen(bool fullScreen) 679 { 680 if (fFullScreen != fullScreen) { 681 fFullScreen = fullScreen; 682 if (fFullScreen) { 683 SetLowColor(0, 0, 0, 255); 684 } else 685 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 686 } 687 } 688 689 690 BBitmap * 691 ShowImageView::GetBitmap() 692 { 693 return fBitmap; 694 } 695 696 697 void 698 ShowImageView::GetName(BString* outName) 699 { 700 BEntry entry(&fCurrentRef); 701 char name[B_FILE_NAME_LENGTH]; 702 if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK) 703 outName->SetTo(""); 704 else 705 outName->SetTo(name); 706 } 707 708 709 void 710 ShowImageView::GetPath(BString *outPath) 711 { 712 BEntry entry(&fCurrentRef); 713 BPath path; 714 if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK) 715 outPath->SetTo(""); 716 else 717 outPath->SetTo(path.Path()); 718 } 719 720 721 void 722 ShowImageView::SetScaleBilinear(bool enabled) 723 { 724 if (fScaleBilinear != enabled) { 725 SettingsSetBool("ScaleBilinear", enabled); 726 fScaleBilinear = enabled; 727 Invalidate(); 728 } 729 } 730 731 732 void 733 ShowImageView::AttachedToWindow() 734 { 735 fUndo.SetWindow(Window()); 736 FixupScrollBars(); 737 738 fProgressWindow = new ProgressWindow(Window()); 739 } 740 741 742 void 743 ShowImageView::DetachedFromWindow() 744 { 745 fProgressWindow->Lock(); 746 fProgressWindow->Quit(); 747 } 748 749 750 BRect 751 ShowImageView::AlignBitmap() 752 { 753 BRect rect(fBitmap->Bounds()); 754 755 // the width/height of the bitmap (in pixels) 756 float bitmapWidth = rect.Width() + 1.0; 757 float bitmapHeight = rect.Height() + 1.0; 758 759 // the available width/height for layouting the bitmap (in pixels) 760 float width = Bounds().Width() - 2 * PEN_SIZE + 1.0; 761 float height = Bounds().Height() - 2 * PEN_SIZE + 1.0; 762 763 if (width == 0 || height == 0) 764 return rect; 765 766 fShrinkOrZoomToBounds = (fShrinkToBounds && 767 (bitmapWidth >= width || bitmapHeight >= height)) || 768 (fZoomToBounds && (bitmapWidth < width && bitmapHeight < height)); 769 if (fShrinkOrZoomToBounds) { 770 float s = width / bitmapWidth; 771 772 if (s * bitmapHeight <= height) { 773 rect.right = width - 1; 774 rect.bottom = static_cast<int>(s * bitmapHeight) - 1; 775 // center vertically 776 rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2)); 777 } else { 778 s = height / bitmapHeight; 779 rect.right = static_cast<int>(s * bitmapWidth) - 1; 780 rect.bottom = height - 1; 781 // center horizontally 782 rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0); 783 } 784 } else { 785 // zoom image 786 rect.right = floorf(bitmapWidth * fZoom) - 1; 787 rect.bottom = floorf(bitmapHeight * fZoom) - 1; 788 789 // update the bitmap size after the zoom 790 bitmapWidth = rect.Width() + 1.0; 791 bitmapHeight = rect.Height() + 1.0; 792 793 // always align in the center 794 if (width > bitmapWidth) 795 rect.OffsetBy((width - bitmapWidth) / 2.0, 0); 796 797 if (height > bitmapHeight) 798 rect.OffsetBy(0, (height - bitmapHeight) / 2.0); 799 } 800 rect.OffsetBy(PEN_SIZE, PEN_SIZE); 801 return rect; 802 } 803 804 805 void 806 ShowImageView::Setup(BRect rect) 807 { 808 fLeft = floorf(rect.left); 809 fTop = floorf(rect.top); 810 fZoom = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0); 811 } 812 813 814 BPoint 815 ShowImageView::ImageToView(BPoint p) const 816 { 817 p.x = floorf(fZoom * p.x + fLeft); 818 p.y = floorf(fZoom * p.y + fTop); 819 return p; 820 } 821 822 823 BPoint 824 ShowImageView::ViewToImage(BPoint p) const 825 { 826 p.x = floorf((p.x - fLeft) / fZoom); 827 p.y = floorf((p.y - fTop) / fZoom); 828 return p; 829 } 830 831 832 BRect 833 ShowImageView::ImageToView(BRect r) const 834 { 835 BPoint leftTop(ImageToView(BPoint(r.left, r.top))); 836 BPoint rightBottom(r.right, r.bottom); 837 rightBottom += BPoint(1, 1); 838 rightBottom = ImageToView(rightBottom); 839 rightBottom -= BPoint(1, 1); 840 return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); 841 } 842 843 844 void 845 ShowImageView::DrawBorder(BRect border) 846 { 847 BRect bounds(Bounds()); 848 // top 849 FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW); 850 // left 851 FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW); 852 // right 853 FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW); 854 // bottom 855 FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW); 856 } 857 858 859 void 860 ShowImageView::LayoutCaption(BFont &font, BPoint &pos, BRect &rect) 861 { 862 font_height fontHeight; 863 float width, height; 864 BRect bounds(Bounds()); 865 font = be_plain_font; 866 width = font.StringWidth(fCaption.String()); 867 font.GetHeight(&fontHeight); 868 height = fontHeight.ascent + fontHeight.descent; 869 // center text horizontally 870 pos.x = (bounds.left + bounds.right - width) / 2; 871 // flush bottom 872 pos.y = bounds.bottom - fontHeight.descent - 7; 873 874 // background rectangle 875 rect.Set(0, 0, width + 4, height + 4); 876 rect.OffsetTo(pos); 877 rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border 878 } 879 880 881 void 882 ShowImageView::DrawCaption() 883 { 884 BFont font; 885 BPoint position; 886 BRect rect; 887 LayoutCaption(font, position, rect); 888 889 PushState(); 890 891 // draw background 892 SetDrawingMode(B_OP_ALPHA); 893 SetHighColor(255, 255, 255, 160); 894 FillRect(rect); 895 896 // draw text 897 SetDrawingMode(B_OP_OVER); 898 SetFont(&font); 899 SetLowColor(B_TRANSPARENT_COLOR); 900 SetHighColor(0, 0, 0); 901 DrawString(fCaption.String(), position); 902 903 PopState(); 904 } 905 906 907 void 908 ShowImageView::UpdateCaption() 909 { 910 BFont font; 911 BPoint pos; 912 BRect rect; 913 LayoutCaption(font, pos, rect); 914 915 // draw over portion of image where caption is located 916 BRegion clip(rect); 917 PushState(); 918 ConstrainClippingRegion(&clip); 919 Draw(rect); 920 PopState(); 921 } 922 923 924 Scaler* 925 ShowImageView::GetScaler(BRect rect) 926 { 927 if (fScaler == NULL || !fScaler->Matches(rect, fDither)) { 928 DeleteScaler(); 929 BMessenger msgr(this, Window()); 930 fScaler = new Scaler(fDisplayBitmap, rect, msgr, MSG_INVALIDATE, fDither); 931 fScaler->Start(); 932 } 933 return fScaler; 934 } 935 936 937 void 938 ShowImageView::DrawImage(BRect rect) 939 { 940 if (fScaleBilinear || fDither) { 941 #if DELAYED_SCALING 942 Scaler* scaler = fScaler; 943 if (scaler != NULL && !scaler->Matches(rect, fDither)) { 944 DeleteScaler(); scaler = NULL; 945 } 946 #else 947 Scaler* scaler = GetScaler(rect); 948 #endif 949 if (scaler != NULL && !scaler->IsRunning()) { 950 BBitmap* bitmap = scaler->GetBitmap(); 951 952 if (bitmap) { 953 DrawBitmap(bitmap, BPoint(rect.left, rect.top)); 954 return; 955 } 956 } 957 } 958 // TODO: fix composing of fBitmap with other bitmaps 959 // with regard to alpha channel 960 if (!fDisplayBitmap) 961 fDisplayBitmap = fBitmap; 962 963 DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect); 964 } 965 966 967 void 968 ShowImageView::Draw(BRect updateRect) 969 { 970 if (fBitmap == NULL) 971 return; 972 973 if (IsPrinting()) { 974 DrawBitmap(fBitmap); 975 return; 976 } 977 978 BRect rect = AlignBitmap(); 979 Setup(rect); 980 981 BRect border(rect); 982 border.InsetBy(-PEN_SIZE, -PEN_SIZE); 983 984 DrawBorder(border); 985 986 // Draw black rectangle around image 987 StrokeRect(border); 988 989 // Draw image 990 DrawImage(rect); 991 992 if (fShowCaption) 993 DrawCaption(); 994 995 if (HasSelection()) { 996 if (fSelBitmap) { 997 BRect srcBits, destRect; 998 GetSelMergeRects(srcBits, destRect); 999 destRect = ImageToView(destRect); 1000 DrawBitmap(fSelBitmap, srcBits, destRect); 1001 } 1002 DrawSelectionBox(); 1003 } 1004 } 1005 1006 1007 void 1008 ShowImageView::DrawSelectionBox() 1009 { 1010 BRect r(fSelectionRect); 1011 ConstrainToImage(r); 1012 r = ImageToView(r); 1013 // draw selection box *around* selection 1014 r.InsetBy(-1, -1); 1015 PushState(); 1016 rgb_color white = {255, 255, 255}; 1017 SetLowColor(white); 1018 StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft); 1019 StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp); 1020 StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight); 1021 StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown); 1022 PopState(); 1023 } 1024 1025 1026 void 1027 ShowImageView::FrameResized(float /* width */, float /* height */) 1028 { 1029 FixupScrollBars(); 1030 } 1031 1032 1033 void 1034 ShowImageView::ConstrainToImage(BPoint &point) 1035 { 1036 point.ConstrainTo(fBitmap->Bounds()); 1037 } 1038 1039 1040 void 1041 ShowImageView::ConstrainToImage(BRect &rect) 1042 { 1043 rect = rect & fBitmap->Bounds(); 1044 } 1045 1046 1047 BBitmap* 1048 ShowImageView::CopyFromRect(BRect srcRect) 1049 { 1050 BRect rect(0, 0, srcRect.Width(), srcRect.Height()); 1051 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1052 BBitmap *bitmap = new(nothrow) BBitmap(rect, fBitmap->ColorSpace(), true); 1053 if (bitmap == NULL || !bitmap->IsValid()) { 1054 delete bitmap; 1055 return NULL; 1056 } 1057 1058 if (bitmap->Lock()) { 1059 bitmap->AddChild(&view); 1060 view.DrawBitmap(fBitmap, srcRect, rect); 1061 view.Sync(); 1062 bitmap->RemoveChild(&view); 1063 bitmap->Unlock(); 1064 } 1065 1066 return bitmap; 1067 } 1068 1069 1070 BBitmap* 1071 ShowImageView::CopySelection(uchar alpha, bool imageSize) 1072 { 1073 bool hasAlpha = alpha != 255; 1074 1075 if (!HasSelection()) 1076 return NULL; 1077 1078 BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height()); 1079 if (!imageSize) { 1080 // scale image to view size 1081 rect.right = floorf((rect.right + 1.0) * fZoom - 1.0); 1082 rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0); 1083 } 1084 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1085 BBitmap *bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true); 1086 if (bitmap == NULL || !bitmap->IsValid()) { 1087 delete bitmap; 1088 return NULL; 1089 } 1090 1091 if (bitmap->Lock()) { 1092 bitmap->AddChild(&view); 1093 if (fSelBitmap) 1094 view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect); 1095 else 1096 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 1097 if (hasAlpha) { 1098 view.SetDrawingMode(B_OP_SUBTRACT); 1099 view.SetHighColor(0, 0, 0, 255-alpha); 1100 view.FillRect(rect, B_SOLID_HIGH); 1101 } 1102 view.Sync(); 1103 bitmap->RemoveChild(&view); 1104 bitmap->Unlock(); 1105 } 1106 1107 return bitmap; 1108 } 1109 1110 1111 bool 1112 ShowImageView::AddSupportedTypes(BMessage* msg, BBitmap* bitmap) 1113 { 1114 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1115 if (roster == NULL) 1116 return false; 1117 1118 BBitmapStream stream(bitmap); 1119 1120 translator_info *outInfo; 1121 bool found = false; 1122 int32 outNumInfo; 1123 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 1124 for (int32 i = 0; i < outNumInfo; i++) { 1125 const translation_format *fmts; 1126 int32 num_fmts; 1127 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 1128 for (int32 j = 0; j < num_fmts; j++) { 1129 if (strcmp(fmts[j].MIME, "image/x-be-bitmap") != 0) { 1130 // needed to send data in message 1131 msg->AddString("be:types", fmts[j].MIME); 1132 // needed to pass data via file 1133 msg->AddString("be:filetypes", fmts[j].MIME); 1134 msg->AddString("be:type_descriptions", fmts[j].name); 1135 } 1136 found = true; 1137 } 1138 } 1139 } 1140 stream.DetachBitmap(&bitmap); 1141 1142 return found; 1143 } 1144 1145 1146 void 1147 ShowImageView::BeginDrag(BPoint sourcePoint) 1148 { 1149 BBitmap* bitmap = CopySelection(128, false); 1150 if (bitmap == NULL) 1151 return; 1152 1153 SetMouseEventMask(B_POINTER_EVENTS); 1154 1155 // fill the drag message 1156 BMessage drag(B_SIMPLE_DATA); 1157 drag.AddInt32("be:actions", B_COPY_TARGET); 1158 drag.AddString("be:clip_name", "Bitmap Clip"); 1159 // ShowImage specific fields 1160 drag.AddPoint("be:_source_point", sourcePoint); 1161 drag.AddRect("be:_frame", fSelectionRect); 1162 if (AddSupportedTypes(&drag, bitmap)) { 1163 // we also support "Passing Data via File" protocol 1164 drag.AddString("be:types", B_FILE_MIME_TYPE); 1165 // avoid flickering of dragged bitmap caused by drawing into the window 1166 AnimateSelection(false); 1167 // only use a transparent bitmap on selections less than 400x400 (taking into account zooming) 1168 if ((fSelectionRect.Width() * fZoom) < 400.0 && (fSelectionRect.Height() * fZoom) < 400.0) 1169 { 1170 sourcePoint -= fSelectionRect.LeftTop(); 1171 sourcePoint.x *= fZoom; 1172 sourcePoint.y *= fZoom; 1173 // DragMessage takes ownership of bitmap 1174 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint); 1175 bitmap = NULL; 1176 } 1177 else 1178 { 1179 delete bitmap; 1180 // Offset and scale the rect 1181 BRect rect(fSelectionRect); 1182 rect = ImageToView(rect); 1183 rect.InsetBy(-1, -1); 1184 DragMessage(&drag, rect); 1185 } 1186 } 1187 } 1188 1189 1190 bool 1191 ShowImageView::OutputFormatForType(BBitmap* bitmap, const char* type, 1192 translation_format* format) 1193 { 1194 bool found = false; 1195 1196 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1197 if (roster == NULL) 1198 return false; 1199 1200 BBitmapStream stream(bitmap); 1201 1202 translator_info *outInfo; 1203 int32 outNumInfo; 1204 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 1205 for (int32 i = 0; i < outNumInfo; i++) { 1206 const translation_format *fmts; 1207 int32 num_fmts; 1208 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 1209 for (int32 j = 0; j < num_fmts; j++) { 1210 if (strcmp(fmts[j].MIME, type) == 0) { 1211 *format = fmts[j]; 1212 found = true; 1213 break; 1214 } 1215 } 1216 } 1217 } 1218 stream.DetachBitmap(&bitmap); 1219 return found; 1220 } 1221 1222 1223 void 1224 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, 1225 const translation_format* format) 1226 { 1227 if (!bitmap) { 1228 // If no bitmap is supplied, write out the whole image 1229 bitmap = fBitmap; 1230 } 1231 1232 BBitmapStream stream(bitmap); 1233 1234 bool loop = true; 1235 while (loop) { 1236 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1237 if (!roster) 1238 break; 1239 // write data 1240 BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 1241 if (file.InitCheck() != B_OK) 1242 break; 1243 if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK) 1244 break; 1245 // set mime type 1246 BNodeInfo info(&file); 1247 if (info.InitCheck() == B_OK) 1248 info.SetType(format->MIME); 1249 1250 loop = false; 1251 // break out of loop gracefully (indicates no errors) 1252 } 1253 if (loop) { 1254 // If loop terminated because of a break, there was an error 1255 BString errText; 1256 errText << "Sorry, the file '" << name << "' could not be written."; 1257 BAlert *palert = new BAlert(NULL, errText.String(), "Ok"); 1258 palert->Go(); 1259 } 1260 1261 stream.DetachBitmap(&bitmap); 1262 // Don't allow the bitmap to be deleted, this is 1263 // especially important when using fBitmap as the bitmap 1264 } 1265 1266 1267 void 1268 ShowImageView::SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format) 1269 { 1270 BMessage reply(B_MIME_DATA); 1271 BBitmapStream stream(bitmap); // destructor deletes bitmap 1272 BTranslatorRoster *roster = BTranslatorRoster::Default(); 1273 BMallocIO memStream; 1274 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) { 1275 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength()); 1276 msg->SendReply(&reply); 1277 } 1278 } 1279 1280 1281 void 1282 ShowImageView::HandleDrop(BMessage* msg) 1283 { 1284 BMessage data(B_MIME_DATA); 1285 entry_ref dirRef; 1286 BString name, type; 1287 bool saveToFile; 1288 bool sendInMessage; 1289 BBitmap *bitmap; 1290 1291 saveToFile = msg->FindString("be:filetypes", &type) == B_OK 1292 && msg->FindRef("directory", &dirRef) == B_OK 1293 && msg->FindString("name", &name) == B_OK; 1294 1295 sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK; 1296 1297 bitmap = CopySelection(); 1298 if (bitmap == NULL) 1299 return; 1300 1301 translation_format format; 1302 if (!OutputFormatForType(bitmap, type.String(), &format)) { 1303 delete bitmap; 1304 return; 1305 } 1306 1307 if (saveToFile) { 1308 BDirectory dir(&dirRef); 1309 SaveToFile(&dir, name.String(), bitmap, &format); 1310 delete bitmap; 1311 } else if (sendInMessage) { 1312 SendInMessage(msg, bitmap, &format); 1313 } else { 1314 delete bitmap; 1315 } 1316 } 1317 1318 1319 void 1320 ShowImageView::MoveImage() 1321 { 1322 BPoint point, delta; 1323 uint32 buttons; 1324 // get CURRENT position 1325 GetMouse(&point, &buttons); 1326 point = ConvertToScreen(point); 1327 delta = fFirstPoint - point; 1328 fFirstPoint = point; 1329 ScrollRestrictedBy(delta.x, delta.y); 1330 1331 // in case we miss MouseUp 1332 if ((GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0) 1333 fMovesImage = false; 1334 } 1335 1336 1337 uint32 1338 ShowImageView::GetMouseButtons() 1339 { 1340 uint32 buttons; 1341 BPoint point; 1342 GetMouse(&point, &buttons); 1343 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1344 if ((modifiers() & B_CONTROL_KEY) != 0) { 1345 buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button 1346 } else if ((modifiers() & B_SHIFT_KEY) != 0) { 1347 buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button 1348 } 1349 } 1350 return buttons; 1351 } 1352 1353 1354 void 1355 ShowImageView::GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits, 1356 BRect &destRect) 1357 { 1358 destRect = selection; 1359 ConstrainToImage(destRect); 1360 1361 srcBits = selection; 1362 if (srcBits.left < 0) 1363 srcBits.left = -(srcBits.left); 1364 else 1365 srcBits.left = 0; 1366 1367 if (srcBits.top < 0) 1368 srcBits.top = -(srcBits.top); 1369 else 1370 srcBits.top = 0; 1371 1372 if (srcBits.right > fBitmap->Bounds().right) 1373 srcBits.right = srcBits.left + destRect.Width(); 1374 else 1375 srcBits.right = merge->Bounds().right; 1376 1377 if (srcBits.bottom > fBitmap->Bounds().bottom) 1378 srcBits.bottom = srcBits.top + destRect.Height(); 1379 else 1380 srcBits.bottom = merge->Bounds().bottom; 1381 } 1382 1383 1384 void 1385 ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect) 1386 { 1387 GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect); 1388 } 1389 1390 1391 void 1392 ShowImageView::MergeWithBitmap(BBitmap *merge, BRect selection) 1393 { 1394 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1395 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1396 if (bitmap == NULL || !bitmap->IsValid()) { 1397 delete bitmap; 1398 return; 1399 } 1400 1401 if (bitmap->Lock()) { 1402 bitmap->AddChild(&view); 1403 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1404 BRect srcBits, destRect; 1405 GetMergeRects(merge, selection, srcBits, destRect); 1406 view.DrawBitmap(merge, srcBits, destRect); 1407 1408 view.Sync(); 1409 bitmap->RemoveChild(&view); 1410 bitmap->Unlock(); 1411 1412 DeleteBitmap(); 1413 fBitmap = bitmap; 1414 1415 SendMessageToWindow(MSG_MODIFIED); 1416 } else 1417 delete bitmap; 1418 } 1419 1420 1421 void 1422 ShowImageView::MergeSelection() 1423 { 1424 if (!HasSelection()) 1425 return; 1426 1427 if (!fSelBitmap) { 1428 // Even though the merge will not change 1429 // the background image, I still need to save 1430 // some undo information here 1431 fUndo.SetTo(fSelectionRect, NULL, CopySelection()); 1432 return; 1433 } 1434 1435 // Merge selection with background 1436 fUndo.SetTo(fSelectionRect, CopyFromRect(fSelectionRect), CopySelection()); 1437 MergeWithBitmap(fSelBitmap, fSelectionRect); 1438 } 1439 1440 1441 void 1442 ShowImageView::MouseDown(BPoint position) 1443 { 1444 BPoint point; 1445 uint32 buttons; 1446 MakeFocus(true); 1447 1448 point = ViewToImage(position); 1449 buttons = GetMouseButtons(); 1450 1451 if (HasSelection() && fSelectionRect.Contains(point) 1452 && (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) { 1453 if (!fSelBitmap) 1454 fSelBitmap = CopySelection(); 1455 1456 BPoint sourcePoint = point; 1457 BeginDrag(sourcePoint); 1458 1459 while (buttons) { 1460 // Keep reading mouse movement until 1461 // the user lets up on all mouse buttons 1462 GetMouse(&point, &buttons); 1463 snooze(25 * 1000); 1464 // sleep for 25 milliseconds to minimize CPU usage during loop 1465 } 1466 1467 if (Bounds().Contains(point)) { 1468 // If selection stayed inside this view 1469 // (Some of the selection may be in the border area, which can be OK) 1470 BPoint last, diff; 1471 last = ViewToImage(point); 1472 diff = last - sourcePoint; 1473 1474 BRect newSelection = fSelectionRect; 1475 newSelection.OffsetBy(diff); 1476 1477 if (fBitmap->Bounds().Intersects(newSelection)) { 1478 // Do not accept the new selection box location 1479 // if it does not intersect with the bitmap rectangle 1480 fSelectionRect = newSelection; 1481 Invalidate(); 1482 } 1483 } 1484 1485 AnimateSelection(true); 1486 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1487 MergeSelection(); 1488 // If there is an existing selection, 1489 // Make it part of the background image 1490 1491 // begin new selection 1492 SetHasSelection(true); 1493 fMakesSelection = true; 1494 SetMouseEventMask(B_POINTER_EVENTS); 1495 ConstrainToImage(point); 1496 fFirstPoint = point; 1497 fCopyFromRect.Set(point.x, point.y, point.x, point.y); 1498 fSelectionRect = fCopyFromRect; 1499 Invalidate(); 1500 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1501 ShowPopUpMenu(ConvertToScreen(position)); 1502 } else if (buttons == B_TERTIARY_MOUSE_BUTTON) { 1503 // move image in window 1504 SetMouseEventMask(B_POINTER_EVENTS); 1505 fMovesImage = true; 1506 fFirstPoint = ConvertToScreen(position); 1507 } 1508 } 1509 1510 1511 void 1512 ShowImageView::UpdateSelectionRect(BPoint point, bool final) 1513 { 1514 BRect oldSelection = fCopyFromRect; 1515 point = ViewToImage(point); 1516 ConstrainToImage(point); 1517 fCopyFromRect.left = min_c(fFirstPoint.x, point.x); 1518 fCopyFromRect.right = max_c(fFirstPoint.x, point.x); 1519 fCopyFromRect.top = min_c(fFirstPoint.y, point.y); 1520 fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y); 1521 fSelectionRect = fCopyFromRect; 1522 1523 if (final) { 1524 // selection must be at least 2 pixels wide or 2 pixels tall 1525 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0) 1526 SetHasSelection(false); 1527 } else 1528 UpdateStatusText(); 1529 1530 if (oldSelection != fCopyFromRect || !HasSelection()) { 1531 BRect updateRect; 1532 updateRect = oldSelection | fCopyFromRect; 1533 updateRect = ImageToView(updateRect); 1534 updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE); 1535 Invalidate(updateRect); 1536 } 1537 } 1538 1539 1540 void 1541 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message) 1542 { 1543 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 1544 if (fMakesSelection) { 1545 UpdateSelectionRect(point, false); 1546 } else if (fMovesImage) { 1547 MoveImage(); 1548 } 1549 } 1550 1551 1552 void 1553 ShowImageView::MouseUp(BPoint point) 1554 { 1555 if (fMakesSelection) { 1556 UpdateSelectionRect(point, true); 1557 fMakesSelection = false; 1558 } else if (fMovesImage) { 1559 MoveImage(); 1560 fMovesImage = false; 1561 } 1562 AnimateSelection(true); 1563 } 1564 1565 1566 float 1567 ShowImageView::LimitToRange(float v, orientation o, bool absolute) 1568 { 1569 BScrollBar* psb = ScrollBar(o); 1570 if (psb) { 1571 float min, max, pos; 1572 pos = v; 1573 if (!absolute) 1574 pos += psb->Value(); 1575 1576 psb->GetRange(&min, &max); 1577 if (pos < min) 1578 pos = min; 1579 else if (pos > max) 1580 pos = max; 1581 1582 v = pos; 1583 if (!absolute) 1584 v -= psb->Value(); 1585 } 1586 return v; 1587 } 1588 1589 1590 void 1591 ShowImageView::ScrollRestricted(float x, float y, bool absolute) 1592 { 1593 if (x != 0) 1594 x = LimitToRange(x, B_HORIZONTAL, absolute); 1595 1596 if (y != 0) 1597 y = LimitToRange(y, B_VERTICAL, absolute); 1598 1599 // hide the caption when using mouse wheel 1600 // in full screen mode 1601 // to prevent the caption from dirtying up the image 1602 // during scrolling. 1603 bool caption = fShowCaption; 1604 if (caption) { 1605 fShowCaption = false; 1606 UpdateCaption(); 1607 } 1608 1609 ScrollBy(x, y); 1610 1611 if (caption) { 1612 // show the caption again 1613 fShowCaption = true; 1614 UpdateCaption(); 1615 } 1616 } 1617 1618 1619 // XXX method is not unused 1620 void 1621 ShowImageView::ScrollRestrictedTo(float x, float y) 1622 { 1623 ScrollRestricted(x, y, true); 1624 } 1625 1626 1627 void 1628 ShowImageView::ScrollRestrictedBy(float x, float y) 1629 { 1630 ScrollRestricted(x, y, false); 1631 } 1632 1633 1634 void 1635 ShowImageView::KeyDown(const char* bytes, int32 numBytes) 1636 { 1637 if (numBytes != 1) { 1638 BView::KeyDown(bytes, numBytes); 1639 return; 1640 } 1641 1642 switch (*bytes) { 1643 case B_DOWN_ARROW: 1644 ScrollRestrictedBy(0, 10); 1645 break; 1646 case B_UP_ARROW: 1647 ScrollRestrictedBy(0, -10); 1648 break; 1649 case B_LEFT_ARROW: 1650 ScrollRestrictedBy(-10, 0); 1651 break; 1652 case B_RIGHT_ARROW: 1653 ScrollRestrictedBy(10, 0); 1654 break; 1655 case B_ENTER: 1656 SendMessageToWindow(MSG_FILE_NEXT); 1657 break; 1658 case B_BACKSPACE: 1659 SendMessageToWindow(MSG_FILE_PREV); 1660 break; 1661 case B_HOME: 1662 break; 1663 case B_END: 1664 break; 1665 case B_SPACE: 1666 ToggleSlideShow(); 1667 break; 1668 case B_ESCAPE: 1669 // stop slide show 1670 if (fSlideShow) 1671 ToggleSlideShow(); 1672 1673 ExitFullScreen(); 1674 1675 ClearSelection(); 1676 break; 1677 case B_DELETE: 1678 { 1679 // Move image to Trash 1680 BMessage trash(BPrivate::kMoveToTrash); 1681 trash.AddRef("refs", &fCurrentRef); 1682 // We create our own messenger because the member fTrackerMessenger 1683 // could be invalid 1684 BMessenger tracker(kTrackerSignature); 1685 if (tracker.SendMessage(&trash) == B_OK) 1686 if (!NextFile()) { 1687 // This is the last (or only file) in this directory, 1688 // close the window 1689 SendMessageToWindow(B_QUIT_REQUESTED); 1690 } 1691 break; 1692 } 1693 case '+': 1694 case '=': 1695 ZoomIn(); 1696 break; 1697 case '-': 1698 ZoomOut(); 1699 break; 1700 } 1701 } 1702 1703 1704 void 1705 ShowImageView::MouseWheelChanged(BMessage *msg) 1706 { 1707 // The BeOS driver does not currently support 1708 // X wheel scrolling, therefore, dx is zero. 1709 // |dy| is the number of notches scrolled up or down. 1710 // When the wheel is scrolled down (towards the user) dy > 0 1711 // When the wheel is scrolled up (away from the user) dy < 0 1712 const float kscrollBy = 40; 1713 float dy, dx; 1714 float x, y; 1715 x = 0; y = 0; 1716 if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) 1717 x = dx * kscrollBy; 1718 if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) 1719 y = dy * kscrollBy; 1720 1721 ScrollRestrictedBy(x, y); 1722 } 1723 1724 1725 void 1726 ShowImageView::ShowPopUpMenu(BPoint screen) 1727 { 1728 BPopUpMenu* menu = new PopUpMenu("PopUpMenu", this); 1729 1730 ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window()); 1731 if (showImage) 1732 showImage->BuildContextMenu(menu); 1733 1734 screen -= BPoint(10, 10); 1735 menu->Go(screen, true, false, true); 1736 fShowingPopUpMenu = true; 1737 } 1738 1739 1740 void 1741 ShowImageView::SettingsSetBool(const char* name, bool value) 1742 { 1743 ShowImageSettings* settings; 1744 settings = my_app->Settings(); 1745 if (settings->Lock()) { 1746 settings->SetBool(name, value); 1747 settings->Unlock(); 1748 } 1749 } 1750 1751 1752 void 1753 ShowImageView::MessageReceived(BMessage *message) 1754 { 1755 switch (message->what) { 1756 case MSG_SELECTION_BITMAP: 1757 { 1758 // In response to a B_SIMPLE_DATA message, a view will 1759 // send this message and expect a reply with a pointer to 1760 // the currently selected bitmap clip. Although this view 1761 // allocates the BBitmap * sent in the reply, it is only 1762 // to be used and deleted by the view that is being replied to. 1763 BMessage msg; 1764 msg.AddPointer("be:_bitmap_ptr", CopySelection()); 1765 message->SendReply(&msg); 1766 break; 1767 } 1768 1769 case B_SIMPLE_DATA: 1770 if (message->WasDropped()) { 1771 uint32 type; 1772 int32 count; 1773 status_t ret = message->GetInfo("refs", &type, &count); 1774 if (ret == B_OK && type == B_REF_TYPE) { 1775 // If file was dropped, open it as the selection 1776 entry_ref ref; 1777 if (message->FindRef("refs", 0, &ref) == B_OK) { 1778 BPoint point = message->DropPoint(); 1779 point = ConvertFromScreen(point); 1780 point = ViewToImage(point); 1781 SetSelection(&ref, point); 1782 } 1783 } else { 1784 // If a user drags a clip from another ShowImage window, 1785 // request a BBitmap pointer to that clip, allocated by the 1786 // other view, for use solely by this view, so that it can 1787 // be dropped/pasted onto this view. 1788 BMessenger retMsgr, localMsgr(this); 1789 retMsgr = message->ReturnAddress(); 1790 if (retMsgr != localMsgr) { 1791 BMessage msgReply; 1792 retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply); 1793 BBitmap *bitmap = NULL; 1794 if (msgReply.FindPointer("be:_bitmap_ptr", 1795 reinterpret_cast<void **>(&bitmap)) == B_OK) { 1796 BRect sourceRect; 1797 BPoint point, sourcePoint; 1798 message->FindPoint("be:_source_point", &sourcePoint); 1799 message->FindRect("be:_frame", &sourceRect); 1800 point = message->DropPoint(); 1801 point.Set(point.x - (sourcePoint.x - sourceRect.left), 1802 point.y - (sourcePoint.y - sourceRect.top)); 1803 // adjust drop point before scaling is factored in 1804 point = ConvertFromScreen(point); 1805 point = ViewToImage(point); 1806 1807 PasteBitmap(bitmap, point); 1808 } 1809 } 1810 } 1811 } 1812 break; 1813 1814 case B_COPY_TARGET: 1815 HandleDrop(message); 1816 break; 1817 case B_MOUSE_WHEEL_CHANGED: 1818 MouseWheelChanged(message); 1819 break; 1820 case MSG_INVALIDATE: 1821 Invalidate(); 1822 break; 1823 1824 case kMsgPopUpMenuClosed: 1825 fShowingPopUpMenu = false; 1826 break; 1827 1828 default: 1829 BView::MessageReceived(message); 1830 break; 1831 } 1832 } 1833 1834 1835 void 1836 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) 1837 { 1838 float prop, range; 1839 BScrollBar *psb; 1840 1841 psb = ScrollBar(o); 1842 if (psb) { 1843 range = bitmapLength - viewLength; 1844 if (range < 0.0) { 1845 range = 0.0; 1846 } 1847 prop = viewLength / bitmapLength; 1848 if (prop > 1.0) { 1849 prop = 1.0; 1850 } 1851 psb->SetRange(0, range); 1852 psb->SetProportion(prop); 1853 psb->SetSteps(10, 100); 1854 } 1855 } 1856 1857 1858 void 1859 ShowImageView::FixupScrollBars() 1860 { 1861 BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0); 1862 if (fBitmap) { 1863 rctbitmap = AlignBitmap(); 1864 rctbitmap.OffsetTo(0, 0); 1865 } 1866 1867 FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width()); 1868 FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height()); 1869 } 1870 1871 1872 int32 1873 ShowImageView::CurrentPage() 1874 { 1875 return fDocumentIndex; 1876 } 1877 1878 1879 int32 1880 ShowImageView::PageCount() 1881 { 1882 return fDocumentCount; 1883 } 1884 1885 1886 void 1887 ShowImageView::Undo() 1888 { 1889 int32 undoType = fUndo.GetType(); 1890 if (undoType != UNDO_UNDO && undoType != UNDO_REDO) 1891 return; 1892 1893 // backup current selection 1894 BRect undoneSelRect; 1895 BBitmap *undoneSelection; 1896 undoneSelRect = fSelectionRect; 1897 undoneSelection = CopySelection(); 1898 1899 if (undoType == UNDO_UNDO) { 1900 BBitmap *undoRestore; 1901 undoRestore = fUndo.GetRestoreBitmap(); 1902 if (undoRestore) 1903 MergeWithBitmap(undoRestore, fUndo.GetRect()); 1904 } 1905 1906 // restore previous image/selection 1907 BBitmap *undoSelection; 1908 undoSelection = fUndo.GetSelectionBitmap(); 1909 // NOTE: ShowImageView is responsible for deleting this bitmap 1910 // (Which it will, as it would with a fSelBitmap that it allocated itself) 1911 if (!undoSelection) 1912 SetHasSelection(false); 1913 else { 1914 fCopyFromRect = BRect(); 1915 fSelectionRect = fUndo.GetRect(); 1916 SetHasSelection(true); 1917 fSelBitmap = undoSelection; 1918 } 1919 1920 fUndo.Undo(undoneSelRect, NULL, undoneSelection); 1921 1922 Invalidate(); 1923 } 1924 1925 1926 void 1927 ShowImageView::AddWhiteRect(BRect &rect) 1928 { 1929 // Paint white rectangle, using rect, into the background image 1930 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1931 BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1932 if (bitmap == NULL || !bitmap->IsValid()) { 1933 delete bitmap; 1934 return; 1935 } 1936 1937 if (bitmap->Lock()) { 1938 bitmap->AddChild(&view); 1939 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1940 1941 view.FillRect(rect, B_SOLID_LOW); 1942 // draw white rect 1943 1944 view.Sync(); 1945 bitmap->RemoveChild(&view); 1946 bitmap->Unlock(); 1947 1948 DeleteBitmap(); 1949 fBitmap = bitmap; 1950 1951 SendMessageToWindow(MSG_MODIFIED); 1952 } else 1953 delete bitmap; 1954 } 1955 1956 1957 void 1958 ShowImageView::RemoveSelection(bool toClipboard) 1959 { 1960 if (!HasSelection()) 1961 return; 1962 1963 BRect rect = fSelectionRect; 1964 bool cutBackground = (fSelBitmap) ? false : true; 1965 BBitmap *selection, *restore = NULL; 1966 selection = CopySelection(); 1967 1968 if (toClipboard) 1969 CopySelectionToClipboard(); 1970 1971 SetHasSelection(false); 1972 1973 if (cutBackground) { 1974 // If the user hasn't dragged the selection, 1975 // paint a white rectangle where the selection was 1976 restore = CopyFromRect(rect); 1977 AddWhiteRect(rect); 1978 } 1979 1980 fUndo.SetTo(rect, restore, selection); 1981 Invalidate(); 1982 } 1983 1984 1985 void 1986 ShowImageView::Cut() 1987 { 1988 // Copy the selection to the clipboard, 1989 // then remove it 1990 RemoveSelection(true); 1991 } 1992 1993 1994 status_t 1995 ShowImageView::PasteBitmap(BBitmap *bitmap, BPoint point) 1996 { 1997 if (bitmap && bitmap->IsValid()) { 1998 MergeSelection(); 1999 2000 fCopyFromRect = BRect(); 2001 fSelectionRect = bitmap->Bounds(); 2002 SetHasSelection(true); 2003 fSelBitmap = bitmap; 2004 2005 BRect offsetRect = fSelectionRect; 2006 offsetRect.OffsetBy(point); 2007 if (fBitmap->Bounds().Intersects(offsetRect)) 2008 // Move the selection rectangle to desired origin, 2009 // but only if the resulting selection rectangle 2010 // intersects with the background bitmap rectangle 2011 fSelectionRect = offsetRect; 2012 2013 Invalidate(); 2014 2015 return B_OK; 2016 } 2017 2018 return B_ERROR; 2019 } 2020 2021 2022 void 2023 ShowImageView::Paste() 2024 { 2025 if (be_clipboard->Lock()) { 2026 BMessage *pclip; 2027 if ((pclip = be_clipboard->Data()) != NULL) { 2028 BPoint point(0, 0); 2029 pclip->FindPoint("be:location", &point); 2030 BBitmap *pbits; 2031 pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip)); 2032 PasteBitmap(pbits, point); 2033 } 2034 2035 be_clipboard->Unlock(); 2036 } 2037 } 2038 2039 2040 void 2041 ShowImageView::SelectAll() 2042 { 2043 SetHasSelection(true); 2044 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()); 2045 fSelectionRect = fCopyFromRect; 2046 Invalidate(); 2047 } 2048 2049 2050 void 2051 ShowImageView::ClearSelection() 2052 { 2053 // Remove the selection, 2054 // DON'T copy it to the clipboard 2055 RemoveSelection(false); 2056 } 2057 2058 2059 void 2060 ShowImageView::SetHasSelection(bool bHasSelection) 2061 { 2062 DeleteSelBitmap(); 2063 fHasSelection = bHasSelection; 2064 2065 UpdateStatusText(); 2066 2067 BMessage msg(MSG_SELECTION); 2068 msg.AddBool("has_selection", fHasSelection); 2069 SendMessageToWindow(&msg); 2070 } 2071 2072 2073 void 2074 ShowImageView::CopySelectionToClipboard() 2075 { 2076 if (HasSelection() && be_clipboard->Lock()) { 2077 be_clipboard->Clear(); 2078 BMessage *clip = NULL; 2079 if ((clip = be_clipboard->Data()) != NULL) { 2080 BMessage data; 2081 BBitmap* bitmap = CopySelection(); 2082 if (bitmap != NULL) { 2083 #if 0 2084 // According to BeBook and Becasso, Gobe Productive do the following. 2085 // Paste works in Productive, but not in Becasso and original ShowImage. 2086 BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused 2087 bitmap->Archive(&msg); 2088 clip->AddMessage("image/x-be-bitmap", &msg); 2089 #else 2090 // original ShowImage performs this. Paste works with original ShowImage. 2091 bitmap->Archive(clip); 2092 // original ShowImage uses be:location for insertion point 2093 clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top)); 2094 #endif 2095 delete bitmap; 2096 be_clipboard->Commit(); 2097 } 2098 } 2099 be_clipboard->Unlock(); 2100 } 2101 } 2102 2103 2104 void 2105 ShowImageView::FirstPage() 2106 { 2107 if (fDocumentIndex != 1) { 2108 fDocumentIndex = 1; 2109 SetImage(NULL); 2110 } 2111 } 2112 2113 2114 void 2115 ShowImageView::LastPage() 2116 { 2117 if (fDocumentIndex != fDocumentCount) { 2118 fDocumentIndex = fDocumentCount; 2119 SetImage(NULL); 2120 } 2121 } 2122 2123 2124 void 2125 ShowImageView::NextPage() 2126 { 2127 if (fDocumentIndex < fDocumentCount) { 2128 fDocumentIndex++; 2129 SetImage(NULL); 2130 } 2131 } 2132 2133 2134 void 2135 ShowImageView::PrevPage() 2136 { 2137 if (fDocumentIndex > 1) { 2138 fDocumentIndex--; 2139 SetImage(NULL); 2140 } 2141 } 2142 2143 2144 int 2145 ShowImageView::CompareEntries(const void* a, const void* b) 2146 { 2147 entry_ref *r1, *r2; 2148 r1 = *(entry_ref**)a; 2149 r2 = *(entry_ref**)b; 2150 return strcasecmp(r1->name, r2->name); 2151 } 2152 2153 2154 void 2155 ShowImageView::GoToPage(int32 page) 2156 { 2157 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 2158 fDocumentIndex = page; 2159 SetImage(NULL); 2160 } 2161 } 2162 2163 2164 void 2165 ShowImageView::FreeEntries(BList* entries) 2166 { 2167 const int32 n = entries->CountItems(); 2168 for (int32 i = 0; i < n; i ++) { 2169 entry_ref* ref = (entry_ref*)entries->ItemAt(i); 2170 delete ref; 2171 } 2172 entries->MakeEmpty(); 2173 } 2174 2175 2176 void 2177 ShowImageView::SetTrackerSelectionToCurrent() 2178 { 2179 BMessage setsel(B_SET_PROPERTY); 2180 setsel.AddSpecifier("Selection"); 2181 setsel.AddRef("data", &fCurrentRef); 2182 fTrackerMessenger.SendMessage(&setsel); 2183 } 2184 2185 2186 bool 2187 ShowImageView::FindNextImageByDir(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind) 2188 { 2189 ASSERT(next || !rewind); 2190 BEntry curImage(in_current); 2191 entry_ref entry, *ref; 2192 BDirectory parent; 2193 BList entries; 2194 bool found = false; 2195 int32 cur; 2196 2197 if (curImage.GetParent(&parent) != B_OK) { 2198 return false; 2199 } 2200 2201 // insert current ref, so we can find it easily after sorting 2202 entries.AddItem(in_current); 2203 2204 while (parent.GetNextRef(&entry) == B_OK) { 2205 if (entry != *in_current) { 2206 entries.AddItem(new entry_ref(entry)); 2207 } 2208 } 2209 2210 entries.SortItems(CompareEntries); 2211 2212 cur = entries.IndexOf(in_current); 2213 ASSERT(cur >= 0); 2214 2215 // remove it so FreeEntries() does not delete it 2216 entries.RemoveItem(in_current); 2217 2218 if (next) { 2219 // find the next image in the list 2220 if (rewind) cur = 0; // start with first 2221 for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) { 2222 if (IsImage(ref)) { 2223 found = true; 2224 *out_image = (const entry_ref)*ref; 2225 break; 2226 } 2227 } 2228 } else { 2229 // find the previous image in the list 2230 cur --; 2231 for (; cur >= 0; cur --) { 2232 ref = (entry_ref*)entries.ItemAt(cur); 2233 if (IsImage(ref)) { 2234 found = true; 2235 *out_image = (const entry_ref)*ref; 2236 break; 2237 } 2238 } 2239 } 2240 2241 FreeEntries(&entries); 2242 return found; 2243 } 2244 2245 bool 2246 ShowImageView::FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, bool rewind) 2247 { 2248 // Based on similar function from BeMail! 2249 if (!fTrackerMessenger.IsValid()) 2250 // If tracker scripting is not available, 2251 // fall back on directory searching code 2252 return FindNextImageByDir(in_current, ref, next, rewind); 2253 2254 // 2255 // Ask the Tracker what the next/prev file in the window is. 2256 // Continue asking for the next reference until a valid 2257 // image is found. 2258 // 2259 entry_ref nextRef = *in_current; 2260 bool foundRef = false; 2261 while (!foundRef) 2262 { 2263 BMessage request(B_GET_PROPERTY); 2264 BMessage spc; 2265 if (rewind) 2266 spc.what = B_DIRECT_SPECIFIER; 2267 else if (next) 2268 spc.what = 'snxt'; 2269 else 2270 spc.what = 'sprv'; 2271 spc.AddString("property", "Entry"); 2272 if (rewind) 2273 // if rewinding, ask for the ref to the 2274 // first item in the directory 2275 spc.AddInt32("data", 0); 2276 else 2277 spc.AddRef("data", &nextRef); 2278 request.AddSpecifier(&spc); 2279 2280 BMessage reply; 2281 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 2282 return FindNextImageByDir(in_current, ref, next, rewind);; 2283 if (reply.FindRef("result", &nextRef) != B_OK) 2284 return FindNextImageByDir(in_current, ref, next, rewind);; 2285 2286 if (IsImage(&nextRef)) 2287 foundRef = true; 2288 2289 rewind = false; 2290 // stop asking for the first ref in the directory 2291 } 2292 2293 *ref = nextRef; 2294 return foundRef; 2295 } 2296 2297 bool 2298 ShowImageView::ShowNextImage(bool next, bool rewind) 2299 { 2300 entry_ref curRef = fCurrentRef; 2301 entry_ref imgRef; 2302 bool found = FindNextImage(&curRef, &imgRef, next, rewind); 2303 if (found) { 2304 // Keep trying to load images until: 2305 // 1. The image loads successfully 2306 // 2. The last file in the directory is found (for find next or find first) 2307 // 3. The first file in the directory is found (for find prev) 2308 // 4. The call to FindNextImage fails for any other reason 2309 while (SetImage(&imgRef) != B_OK) { 2310 curRef = imgRef; 2311 found = FindNextImage(&curRef, &imgRef, next, false); 2312 if (!found) 2313 return false; 2314 } 2315 SetTrackerSelectionToCurrent(); 2316 return true; 2317 } 2318 return false; 2319 } 2320 2321 2322 bool 2323 ShowImageView::NextFile() 2324 { 2325 return ShowNextImage(true, false); 2326 } 2327 2328 2329 bool 2330 ShowImageView::PrevFile() 2331 { 2332 return ShowNextImage(false, false); 2333 } 2334 2335 2336 bool 2337 ShowImageView::HasNextFile() 2338 { 2339 entry_ref ref; 2340 return FindNextImage(&fCurrentRef, &ref, true, false); 2341 } 2342 2343 2344 bool 2345 ShowImageView::HasPrevFile() 2346 { 2347 entry_ref ref; 2348 return FindNextImage(&fCurrentRef, &ref, false, false); 2349 } 2350 2351 2352 bool 2353 ShowImageView::FirstFile() 2354 { 2355 return ShowNextImage(true, true); 2356 } 2357 2358 2359 void 2360 ShowImageView::SetZoom(float zoom) 2361 { 2362 if ((fScaleBilinear || fDither) && fZoom != zoom) { 2363 DeleteScaler(); 2364 } 2365 fZoom = zoom; 2366 FixupScrollBars(); 2367 Invalidate(); 2368 } 2369 2370 2371 void 2372 ShowImageView::ZoomIn() 2373 { 2374 if (fZoom < 16) 2375 SetZoom(fZoom + 0.25); 2376 } 2377 2378 2379 void 2380 ShowImageView::ZoomOut() 2381 { 2382 if (fZoom > 0.25) 2383 SetZoom(fZoom - 0.25); 2384 } 2385 2386 2387 void 2388 ShowImageView::SetSlideShowDelay(float seconds) 2389 { 2390 ShowImageSettings* settings; 2391 int32 delay = (int)(seconds * 10.0); 2392 if (fSlideShowDelay != delay) { 2393 // update counter 2394 fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown); 2395 if (fSlideShowCountDown <= 0) { 2396 // show next image on next Pulse() 2397 fSlideShowCountDown = 1; 2398 } 2399 fSlideShowDelay = delay; 2400 settings = my_app->Settings(); 2401 if (settings->Lock()) { 2402 settings->SetInt32("SlideShowDelay", fSlideShowDelay); 2403 settings->Unlock(); 2404 } 2405 } 2406 } 2407 2408 2409 void 2410 ShowImageView::StartSlideShow() 2411 { 2412 fSlideShow = true; fSlideShowCountDown = fSlideShowDelay; 2413 } 2414 2415 2416 void 2417 ShowImageView::StopSlideShow() 2418 { 2419 fSlideShow = false; 2420 } 2421 2422 2423 void 2424 ShowImageView::DoImageOperation(ImageProcessor::operation op, bool quiet) 2425 { 2426 BMessenger msgr; 2427 ImageProcessor imageProcessor(op, fBitmap, msgr, 0); 2428 imageProcessor.Start(false); 2429 BBitmap* bm = imageProcessor.DetachBitmap(); 2430 if (bm == NULL) { 2431 // operation failed 2432 return; 2433 } 2434 2435 // update orientation state 2436 if (op != ImageProcessor::kInvert) { 2437 // Note: If one of these fails, check its definition in class ImageProcessor. 2438 ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations); 2439 ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations); 2440 ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations); 2441 ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations); 2442 fImageOrientation = fTransformation[op][fImageOrientation]; 2443 } else { 2444 fInverted = !fInverted; 2445 } 2446 2447 if (!quiet) { 2448 // write orientation state 2449 BNode node(&fCurrentRef); 2450 int32 orientation = fImageOrientation; 2451 if (fInverted) orientation += 256; 2452 if (orientation != k0) { 2453 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation)); 2454 } else { 2455 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE); 2456 } 2457 } 2458 2459 // set new bitmap 2460 DeleteBitmap(); 2461 fBitmap = bm; 2462 2463 if (!quiet) { 2464 // remove selection 2465 SetHasSelection(false); 2466 Notify(); 2467 } 2468 } 2469 2470 2471 //! image operation initiated by user 2472 void 2473 ShowImageView::UserDoImageOperation(ImageProcessor::operation op, bool quiet) 2474 { 2475 fUndo.Clear(); 2476 DoImageOperation(op, quiet); 2477 } 2478 2479 2480 void 2481 ShowImageView::Rotate(int degree) 2482 { 2483 if (degree == 90) { 2484 UserDoImageOperation(ImageProcessor::kRotateClockwise); 2485 } else if (degree == 270) { 2486 UserDoImageOperation(ImageProcessor::kRotateCounterClockwise); 2487 } 2488 } 2489 2490 2491 void 2492 ShowImageView::Flip(bool vertical) 2493 { 2494 if (vertical) { 2495 UserDoImageOperation(ImageProcessor::kFlipLeftToRight); 2496 } else { 2497 UserDoImageOperation(ImageProcessor::kFlipTopToBottom); 2498 } 2499 } 2500 2501 2502 void 2503 ShowImageView::Invert() 2504 { 2505 if (fBitmap->ColorSpace() != B_CMAP8) { 2506 // Only allow an invert operation if the 2507 // bitmap color space is supported by the 2508 // invert algorithm 2509 UserDoImageOperation(ImageProcessor::kInvert); 2510 } 2511 } 2512 2513 void 2514 ShowImageView::ResizeImage(int w, int h) 2515 { 2516 if (fBitmap == NULL || w < 1 || h < 1) 2517 return; 2518 2519 Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false); 2520 scaler.Start(false); 2521 BBitmap* scaled = scaler.DetachBitmap(); 2522 if (scaled == NULL) { 2523 // operation failed 2524 return; 2525 } 2526 2527 // remove selection 2528 SetHasSelection(false); 2529 fUndo.Clear(); 2530 DeleteBitmap(); 2531 fBitmap = scaled; 2532 2533 SendMessageToWindow(MSG_MODIFIED); 2534 2535 Notify(); 2536 } 2537 2538 void 2539 ShowImageView::SetIcon(bool clear, icon_size which) 2540 { 2541 int32 size; 2542 switch (which) { 2543 case B_MINI_ICON: size = 16; 2544 break; 2545 case B_LARGE_ICON: size = 32; 2546 break; 2547 default: 2548 return; 2549 } 2550 2551 BRect rect(fBitmap->Bounds()); 2552 float s; 2553 s = size / (rect.Width()+1.0); 2554 2555 if (s * (rect.Height()+1.0) <= size) { 2556 rect.right = size-1; 2557 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 2558 // center vertically 2559 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2); 2560 } else { 2561 s = size / (rect.Height()+1.0); 2562 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 2563 rect.bottom = size-1; 2564 // center horizontally 2565 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0); 2566 } 2567 2568 // scale bitmap to thumbnail size 2569 BMessenger msgr; 2570 Scaler scaler(fBitmap, rect, msgr, 0, true); 2571 BBitmap* thumbnail = scaler.GetBitmap(); 2572 scaler.Start(false); 2573 ASSERT(thumbnail->ColorSpace() == B_CMAP8); 2574 // create icon from thumbnail 2575 BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8); 2576 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength()); 2577 BScreen screen; 2578 const uchar* src = (uchar*)thumbnail->Bits(); 2579 uchar* dest = (uchar*)icon.Bits(); 2580 const int32 srcBPR = thumbnail->BytesPerRow(); 2581 const int32 destBPR = icon.BytesPerRow(); 2582 const int32 dx = (int32)rect.left; 2583 const int32 dy = (int32)rect.top; 2584 2585 for (int32 y = 0; y <= rect.IntegerHeight(); y ++) { 2586 for (int32 x = 0; x <= rect.IntegerWidth(); x ++) { 2587 const uchar* s = src + y * srcBPR + x; 2588 uchar* d = dest + (y+dy) * destBPR + (x+dx); 2589 *d = *s; 2590 } 2591 } 2592 2593 // set icon 2594 BNode node(&fCurrentRef); 2595 BNodeInfo info(&node); 2596 info.SetIcon(clear ? NULL : &icon, which); 2597 } 2598 2599 2600 void 2601 ShowImageView::SetIcon(bool clear) 2602 { 2603 SetIcon(clear, B_MINI_ICON); 2604 SetIcon(clear, B_LARGE_ICON); 2605 } 2606 2607 2608 void 2609 ShowImageView::ToggleSlideShow() 2610 { 2611 SendMessageToWindow(MSG_SLIDE_SHOW); 2612 } 2613 2614 2615 void 2616 ShowImageView::ExitFullScreen() 2617 { 2618 be_app->ShowCursor(); 2619 SendMessageToWindow(MSG_EXIT_FULL_SCREEN); 2620 } 2621 2622 2623 void 2624 ShowImageView::WindowActivated(bool active) 2625 { 2626 fIsActiveWin = active; 2627 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME; 2628 } 2629 2630