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