1 /*****************************************************************************/ 2 // ShowImageView 3 // Written by Fernando Francisco de Oliveira, Michael Wilber, Michael Pfeiffer 4 // 5 // ShowImageView.cpp 6 // 7 // 8 // Copyright (c) 2003 OpenBeOS Project 9 // 10 // Permission is hereby granted, free of charge, to any person obtaining a 11 // copy of this software and associated documentation files (the "Software"), 12 // to deal in the Software without restriction, including without limitation 13 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 // and/or sell copies of the Software, and to permit persons to whom the 15 // Software is furnished to do so, subject to the following conditions: 16 // 17 // The above copyright notice and this permission notice shall be included 18 // in all copies or substantial portions of the Software. 19 // 20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 23 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 // DEALINGS IN THE SOFTWARE. 27 /*****************************************************************************/ 28 29 #include <stdio.h> 30 #include <math.h> 31 #include <Debug.h> 32 #include <Message.h> 33 #include <ScrollBar.h> 34 #include <StopWatch.h> 35 #include <Alert.h> 36 #include <MenuBar.h> 37 #include <MenuItem.h> 38 #include <File.h> 39 #include <Bitmap.h> 40 #include <TranslatorRoster.h> 41 #include <BitmapStream.h> 42 #include <Rect.h> 43 #include <SupportDefs.h> 44 #include <Directory.h> 45 #include <Application.h> 46 #include <Roster.h> 47 #include <NodeInfo.h> 48 #include <Clipboard.h> 49 #include <Path.h> 50 #include <PopUpMenu.h> 51 #include <Region.h> 52 #include <Screen.h> 53 54 55 #include "ShowImageApp.h" 56 #include "ShowImageConstants.h" 57 #include "ShowImageView.h" 58 #include "ShowImageWindow.h" 59 60 #ifndef min 61 #define min(a,b) ((a)>(b)?(b):(a)) 62 #endif 63 #ifndef max 64 #define max(a,b) ((a)>(b)?(a):(b)) 65 #endif 66 67 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation" 68 #define BORDER_WIDTH 16 69 #define BORDER_HEIGHT 16 70 #define PEN_SIZE 1.0f 71 const rgb_color kBorderColor = { 0, 0, 0, 255 }; 72 73 enum ShowImageView::image_orientation 74 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations] = 75 { 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 87 // use patterns to simulate marching ants for selection 88 void 89 ShowImageView::InitPatterns() 90 { 91 uchar p; 92 uchar p1 = 0x33; 93 uchar p2 = 0xCC; 94 for (int i = 0; i <= 7; i ++) { 95 fPatternLeft.data[i] = p1; 96 fPatternRight.data[i] = p2; 97 if ((i / 2) % 2 == 0) { 98 p = 255; 99 } else { 100 p = 0; 101 } 102 fPatternUp.data[i] = p; 103 fPatternDown.data[i] = ~p; 104 } 105 } 106 107 void 108 ShowImageView::RotatePatterns() 109 { 110 int i; 111 uchar p; 112 bool set; 113 114 // rotate up 115 p = fPatternUp.data[0]; 116 for (i = 0; i <= 6; i ++) { 117 fPatternUp.data[i] = fPatternUp.data[i+1]; 118 } 119 fPatternUp.data[7] = p; 120 121 // rotate down 122 p = fPatternDown.data[7]; 123 for (i = 7; i >= 1; i --) { 124 fPatternDown.data[i] = fPatternDown.data[i-1]; 125 } 126 fPatternDown.data[0] = p; 127 128 // rotate to left 129 p = fPatternLeft.data[0]; 130 set = (p & 0x80) != 0; 131 p <<= 1; 132 p &= 0xfe; 133 if (set) p |= 1; 134 memset(fPatternLeft.data, p, 8); 135 136 // rotate to right 137 p = fPatternRight.data[0]; 138 set = (p & 1) != 0; 139 p >>= 1; 140 if (set) p |= 0x80; 141 memset(fPatternRight.data, p, 8); 142 } 143 144 void 145 ShowImageView::AnimateSelection(bool a) 146 { 147 fAnimateSelection = a; 148 } 149 150 void 151 ShowImageView::Pulse() 152 { 153 // animate marching ants 154 if (HasSelection() && fAnimateSelection && Window()->IsActive()) { 155 RotatePatterns(); 156 DrawSelectionBox(); 157 } 158 if (fSlideShow) { 159 fSlideShowCountDown --; 160 if (fSlideShowCountDown <= 0) { 161 fSlideShowCountDown = fSlideShowDelay; 162 if (!NextFile()) { 163 FirstFile(); 164 } 165 } 166 } 167 #if DELAYED_SCALING 168 if (fBitmap && (fScaleBilinear || fDither) && fScalingCountDown > 0) { 169 fScalingCountDown --; 170 if (fScalingCountDown == 0) { 171 GetScaler(AlignBitmap()); 172 } 173 } 174 #endif 175 } 176 177 ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode, 178 uint32 flags) 179 : BView(rect, name, resizingMode, flags) 180 { 181 ShowImageSettings* settings; 182 settings = my_app->Settings(); 183 184 InitPatterns(); 185 fDither = false; 186 fBitmap = NULL; 187 fSelBitmap = NULL; 188 fDocumentIndex = 1; 189 fDocumentCount = 1; 190 fAnimateSelection = true; 191 fHasSelection = false; 192 fShrinkToBounds = false; 193 fZoomToBounds = false; 194 fHasBorder = true; 195 fHAlignment = B_ALIGN_LEFT; 196 fVAlignment = B_ALIGN_TOP; 197 fSlideShow = false; 198 fSlideShowDelay = 3 * 10; // 3 seconds 199 fShowCaption = false; 200 fZoom = 1.0; 201 fMovesImage = false; 202 fScaleBilinear = false; 203 fScaler = NULL; 204 #if DELAYED_SCALING 205 fScalingCountDown = 10; 206 #endif 207 208 if (settings->Lock()) { 209 fDither = settings->GetBool("Dither", fDither); 210 fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds); 211 fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds); 212 fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay); 213 fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear); 214 settings->Unlock(); 215 } 216 217 SetViewColor(B_TRANSPARENT_COLOR); 218 SetHighColor(kBorderColor); 219 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 220 SetPenSize(PEN_SIZE); 221 } 222 223 ShowImageView::~ShowImageView() 224 { 225 DeleteBitmap(); 226 } 227 228 bool 229 ShowImageView::IsImage(const entry_ref *pref) 230 { 231 BFile file(pref, B_READ_ONLY); 232 translator_info info; 233 memset(&info, 0, sizeof(translator_info)); 234 BMessage ioExtension; 235 236 BTranslatorRoster *proster = BTranslatorRoster::Default(); 237 if (!proster) 238 return false; 239 240 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 241 return false; 242 243 if (proster->Identify(&file, &ioExtension, &info, 0, NULL, 244 B_TRANSLATOR_BITMAP) != B_OK) 245 return false; 246 247 return true; 248 } 249 250 // send message to parent about new image 251 void 252 ShowImageView::Notify(const char* status) 253 { 254 BMessage msg(MSG_UPDATE_STATUS); 255 if (status != NULL) { 256 msg.AddString("status", status); 257 } 258 msg.AddInt32("colors", fBitmap->ColorSpace()); 259 BMessenger msgr(Window()); 260 msgr.SendMessage(&msg); 261 262 FixupScrollBars(); 263 Invalidate(); 264 } 265 266 void 267 ShowImageView::AddToRecentDocuments() 268 { 269 be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG); 270 } 271 272 void 273 ShowImageView::DeleteScaler() 274 { 275 if (fScaler) { 276 fScaler->Stop(); 277 delete fScaler; 278 fScaler = NULL; 279 } 280 #if DELAYED_SCALING 281 fScalingCountDown = 3; // delay for 3/10 seconds 282 #endif 283 } 284 285 void 286 ShowImageView::DeleteBitmap() 287 { 288 DeleteScaler(); 289 DeleteSelBitmap(); 290 delete fBitmap; 291 fBitmap = NULL; 292 } 293 294 void ShowImageView::DeleteSelBitmap() 295 { 296 delete fSelBitmap; 297 fSelBitmap = NULL; 298 } 299 300 void 301 ShowImageView::SetImage(const entry_ref *pref) 302 { 303 fUndo.Clear(); 304 DeleteBitmap(); 305 SetHasSelection(false); 306 fMakesSelection = false; 307 308 entry_ref ref; 309 if (!pref) 310 ref = fCurrentRef; 311 else 312 ref = *pref; 313 314 BTranslatorRoster *proster = BTranslatorRoster::Default(); 315 if (!proster) 316 return; 317 BFile file(&ref, B_READ_ONLY); 318 translator_info info; 319 memset(&info, 0, sizeof(translator_info)); 320 BMessage ioExtension; 321 if (ref != fCurrentRef) 322 // if new image, reset to first document 323 fDocumentIndex = 1; 324 if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) 325 return; 326 if (proster->Identify(&file, &ioExtension, &info, 0, NULL, 327 B_TRANSLATOR_BITMAP) != B_OK) 328 return; 329 330 // Translate image data and create a new ShowImage window 331 BBitmapStream outstream; 332 if (proster->Translate(&file, &info, &ioExtension, &outstream, 333 B_TRANSLATOR_BITMAP) != B_OK) 334 return; 335 if (outstream.DetachBitmap(&fBitmap) != B_OK) 336 return; 337 fCurrentRef = ref; 338 339 // restore orientation 340 int32 orientation; 341 fImageOrientation = k0; 342 fInverted = false; 343 if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation)) == sizeof(orientation)) { 344 if (orientation & 256) { 345 DoImageOperation(ImageProcessor::ImageProcessor::kInvert, true); 346 } 347 orientation &= 255; 348 switch (orientation) { 349 case k0: 350 break; 351 case k90: 352 DoImageOperation(ImageProcessor::kRotateClockwise, true); 353 break; 354 case k180: 355 DoImageOperation(ImageProcessor::kRotateClockwise, true); 356 DoImageOperation(ImageProcessor::kRotateClockwise, true); 357 break; 358 case k270: 359 DoImageOperation(ImageProcessor::kRotateAntiClockwise, true); 360 break; 361 case k0V: 362 DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true); 363 break; 364 case k90V: 365 DoImageOperation(ImageProcessor::kRotateClockwise, true); 366 DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true); 367 break; 368 case k0H: 369 DoImageOperation(ImageProcessor::ImageProcessor::kMirrorVertical, true); 370 break; 371 case k270V: 372 DoImageOperation(ImageProcessor::kRotateAntiClockwise, true); 373 DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true); 374 break; 375 } 376 } 377 378 // get the number of documents (pages) if it has been supplied 379 int32 documentCount = 0; 380 if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK && 381 documentCount > 0) 382 fDocumentCount = documentCount; 383 else 384 fDocumentCount = 1; 385 386 GetPath(&fCaption); 387 if (fDocumentCount > 1) { 388 fCaption << ", " << fDocumentIndex << "/" << fDocumentCount; 389 } 390 fCaption << ", " << info.name; 391 392 AddToRecentDocuments(); 393 394 Notify(info.name); 395 } 396 397 void 398 ShowImageView::SetDither(bool dither) 399 { 400 if (fDither != dither) { 401 SettingsSetBool("Dither", dither); 402 fDither = dither; 403 Invalidate(); 404 } 405 } 406 407 void 408 ShowImageView::SetShowCaption(bool show) 409 { 410 if (fShowCaption != show) { 411 fShowCaption = show; 412 UpdateCaption(); 413 } 414 } 415 416 void 417 ShowImageView::SetShrinkToBounds(bool enable) 418 { 419 if (fShrinkToBounds != enable) { 420 SettingsSetBool("ShrinkToBounds", enable); 421 fShrinkToBounds = enable; 422 FixupScrollBars(); 423 Invalidate(); 424 } 425 } 426 427 void 428 ShowImageView::SetZoomToBounds(bool enable) 429 { 430 if (fZoomToBounds != enable) { 431 SettingsSetBool("ZoomToBounds", enable); 432 fZoomToBounds = enable; 433 FixupScrollBars(); 434 Invalidate(); 435 } 436 } 437 438 void 439 ShowImageView::SetBorder(bool hasBorder) 440 { 441 if (fHasBorder != hasBorder) { 442 fHasBorder = hasBorder; 443 FixupScrollBars(); 444 Invalidate(); 445 } 446 } 447 448 void 449 ShowImageView::SetAlignment(alignment horizontal, vertical_alignment vertical) 450 { 451 bool hasChanged; 452 hasChanged = fHAlignment != horizontal || fVAlignment != vertical; 453 if (hasChanged) { 454 fHAlignment = horizontal; 455 fVAlignment = vertical; 456 FixupScrollBars(); 457 Invalidate(); 458 } 459 } 460 461 BBitmap * 462 ShowImageView::GetBitmap() 463 { 464 return fBitmap; 465 } 466 467 void 468 ShowImageView::GetName(BString *name) 469 { 470 *name = ""; 471 BEntry entry(&fCurrentRef); 472 if (entry.InitCheck() == B_OK) { 473 char n[B_FILE_NAME_LENGTH]; 474 if (entry.GetName(n) == B_OK) { 475 name->SetTo(n); 476 } 477 } 478 } 479 480 void 481 ShowImageView::GetPath(BString *name) 482 { 483 *name = ""; 484 BEntry entry(&fCurrentRef); 485 if (entry.InitCheck() == B_OK) { 486 BPath path; 487 entry.GetPath(&path); 488 if (path.InitCheck() == B_OK) { 489 name->SetTo(path.Path()); 490 } 491 } 492 } 493 494 void 495 ShowImageView::FlushToLeftTop() 496 { 497 BRect rect = AlignBitmap(); 498 BPoint p(rect.left, rect.top); 499 ScrollTo(p); 500 } 501 502 void 503 ShowImageView::SetScaleBilinear(bool s) 504 { 505 if (fScaleBilinear != s) { 506 SettingsSetBool("ScaleBilinear", s); 507 fScaleBilinear = s; Invalidate(); 508 } 509 } 510 511 void 512 ShowImageView::AttachedToWindow() 513 { 514 fUndo.SetWindow(Window()); 515 FixupScrollBars(); 516 } 517 518 BRect 519 ShowImageView::AlignBitmap() 520 { 521 BRect rect(fBitmap->Bounds()); 522 float width, height; 523 width = Bounds().Width()-2*PEN_SIZE+1; 524 height = Bounds().Height()-2*PEN_SIZE+1; 525 if (width == 0 || height == 0) return rect; 526 fShrinkOrZoomToBounds = fShrinkToBounds && (rect.Width() >= Bounds().Width() || rect.Height() >= Bounds().Height()) || 527 fZoomToBounds && rect.Width() < Bounds().Width() && rect.Height() < Bounds().Height(); 528 if (fShrinkOrZoomToBounds) { 529 float s; 530 s = width / (rect.Width()+1.0); 531 532 if (s * (rect.Height()+1.0) <= height) { 533 rect.right = width-1; 534 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 535 // center vertically 536 rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2)); 537 } else { 538 s = height / (rect.Height()+1.0); 539 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 540 rect.bottom = height-1; 541 // center horizontally 542 rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0); 543 } 544 } else { 545 // zoom image 546 rect.right = static_cast<int>((rect.right+1.0)*fZoom)-1; 547 rect.bottom = static_cast<int>((rect.bottom+1.0)*fZoom)-1; 548 // align 549 switch (fHAlignment) { 550 case B_ALIGN_CENTER: 551 if (width > rect.Width()) { 552 rect.OffsetBy((width - rect.Width()) / 2.0, 0); 553 break; 554 } 555 // fall through 556 default: 557 case B_ALIGN_LEFT: 558 if (fHasBorder) { 559 rect.OffsetBy(BORDER_WIDTH, 0); 560 } 561 break; 562 } 563 switch (fVAlignment) { 564 case B_ALIGN_MIDDLE: 565 if (height > rect.Height()) { 566 rect.OffsetBy(0, (height - rect.Height()) / 2.0); 567 break; 568 } 569 // fall through 570 default: 571 case B_ALIGN_TOP: 572 if (fHasBorder) { 573 rect.OffsetBy(0, BORDER_WIDTH); 574 } 575 break; 576 } 577 } 578 rect.OffsetBy(PEN_SIZE, PEN_SIZE); 579 return rect; 580 } 581 582 void 583 ShowImageView::Setup(BRect rect) 584 { 585 fLeft = floorf(rect.left); 586 fTop = floorf(rect.top); 587 fScaleX = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0); 588 fScaleY = (rect.Height()+1.0) / (fBitmap->Bounds().Height()+1.0); 589 } 590 591 BPoint 592 ShowImageView::ImageToView(BPoint p) const 593 { 594 p.x = floorf(fScaleX * p.x + fLeft); 595 p.y = floorf(fScaleY * p.y + fTop); 596 return p; 597 } 598 599 BPoint 600 ShowImageView::ViewToImage(BPoint p) const 601 { 602 p.x = floorf((p.x - fLeft) / fScaleX); 603 p.y = floorf((p.y - fTop) / fScaleY); 604 return p; 605 } 606 607 BRect 608 ShowImageView::ImageToView(BRect r) const 609 { 610 BPoint leftTop(ImageToView(BPoint(r.left, r.top))); 611 BPoint rightBottom(r.right, r.bottom); 612 rightBottom += BPoint(1, 1); 613 rightBottom = ImageToView(rightBottom); 614 rightBottom -= BPoint(1, 1); 615 return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y); 616 } 617 618 void 619 ShowImageView::DrawBorder(BRect border) 620 { 621 BRect bounds(Bounds()); 622 // top 623 FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW); 624 // left 625 FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW); 626 // right 627 FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW); 628 // bottom 629 FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW); 630 } 631 632 void 633 ShowImageView::LayoutCaption(BFont &font, BPoint &pos, BRect &rect) 634 { 635 font_height fontHeight; 636 float width, height; 637 BRect bounds(Bounds()); 638 font = be_plain_font; 639 width = font.StringWidth(fCaption.String()) + 1; // 1 for text shadow 640 font.GetHeight(&fontHeight); 641 height = fontHeight.ascent + fontHeight.descent; 642 // center text horizontally 643 pos.x = (bounds.left + bounds.right - width)/2; 644 // flush bottom 645 pos.y = bounds.bottom - fontHeight.descent - 5; 646 647 // background rectangle 648 rect.Set(0, 0, (width-1)+2, (height-1)+2+1); // 2 for border and 1 for text shadow 649 rect.OffsetTo(pos); 650 rect.OffsetBy(-1, -1-fontHeight.ascent); // -1 for border 651 } 652 653 void 654 ShowImageView::DrawCaption() 655 { 656 BFont font; 657 BPoint pos; 658 BRect rect; 659 LayoutCaption(font, pos, rect); 660 661 PushState(); 662 // draw background 663 SetDrawingMode(B_OP_ALPHA); 664 SetHighColor(0, 0, 255, 128); 665 FillRect(rect); 666 // draw text 667 SetDrawingMode(B_OP_OVER); 668 SetFont(&font); 669 SetLowColor(B_TRANSPARENT_COLOR); 670 // text shadow 671 pos += BPoint(1, 1); 672 SetHighColor(0, 0, 0); 673 SetPenSize(1); 674 DrawString(fCaption.String(), pos); 675 // text 676 pos -= BPoint(1, 1); 677 SetHighColor(255, 255, 0); 678 DrawString(fCaption.String(), pos); 679 PopState(); 680 } 681 682 void 683 ShowImageView::UpdateCaption() 684 { 685 BFont font; 686 BPoint pos; 687 BRect rect; 688 LayoutCaption(font, pos, rect); 689 690 // draw over portion of image where caption is located 691 BRegion clip(rect); 692 PushState(); 693 ConstrainClippingRegion(&clip); 694 Draw(rect); 695 PopState(); 696 } 697 698 Scaler* 699 ShowImageView::GetScaler(BRect rect) 700 { 701 if (fScaler == NULL || !fScaler->Matches(rect, fDither)) { 702 DeleteScaler(); 703 BMessenger msgr(this, Window()); 704 fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE, fDither); 705 fScaler->Start(); 706 } 707 return fScaler; 708 } 709 710 void 711 ShowImageView::DrawImage(BRect rect) 712 { 713 if (fScaleBilinear || fDither) { 714 #if DELAYED_SCALING 715 Scaler* scaler = fScaler; 716 if (scaler != NULL && !scaler->Matches(rect, fDither)) { 717 DeleteScaler(); scaler = NULL; 718 } 719 #else 720 Scaler* scaler = GetScaler(rect); 721 #endif 722 if (scaler != NULL && !scaler->IsRunning()) { 723 BBitmap* bitmap = scaler->GetBitmap(); 724 if (bitmap) { 725 DrawBitmap(bitmap, BPoint(rect.left, rect.top)); 726 return; 727 } 728 } 729 } 730 DrawBitmap(fBitmap, fBitmap->Bounds(), rect); 731 } 732 733 void 734 ShowImageView::Draw(BRect updateRect) 735 { 736 if (fBitmap) { 737 if (!IsPrinting()) { 738 BRect rect = AlignBitmap(); 739 Setup(rect); 740 741 BRect border(rect); 742 border.InsetBy(-PEN_SIZE, -PEN_SIZE); 743 744 DrawBorder(border); 745 746 // Draw black rectangle around image 747 StrokeRect(border); 748 749 // Draw image 750 DrawImage(rect); 751 752 if (fShowCaption) { 753 // fShowCaption is set to false by ScrollRestricted() 754 // to prevent the caption from dirtying up the image 755 // during scrolling. 756 DrawCaption(); 757 } 758 759 if (HasSelection()) { 760 if (fSelBitmap) { 761 BRect srcBits, destRect; 762 GetSelMergeRects(srcBits, destRect); 763 destRect = ImageToView(destRect); 764 DrawBitmap(fSelBitmap, srcBits, destRect); 765 } 766 DrawSelectionBox(); 767 } 768 } else { 769 DrawBitmap(fBitmap); 770 } 771 } 772 } 773 774 void 775 ShowImageView::DrawSelectionBox() 776 { 777 BRect r(fSelectionRect); 778 ConstrainToImage(r); 779 r = ImageToView(r); 780 // draw selection box *around* selection 781 r.InsetBy(-1, -1); 782 PushState(); 783 rgb_color white = {255, 255, 255}; 784 SetLowColor(white); 785 StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft); 786 StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp); 787 StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight); 788 StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown); 789 PopState(); 790 } 791 792 void 793 ShowImageView::FrameResized(float /* width */, float /* height */) 794 { 795 FixupScrollBars(); 796 } 797 798 void 799 ShowImageView::ConstrainToImage(BPoint &point) 800 { 801 point.ConstrainTo(fBitmap->Bounds()); 802 } 803 804 void 805 ShowImageView::ConstrainToImage(BRect &rect) 806 { 807 BRect bounds = fBitmap->Bounds(); 808 BPoint leftTop, rightBottom; 809 810 leftTop = rect.LeftTop(); 811 leftTop.ConstrainTo(bounds); 812 813 rightBottom = rect.RightBottom(); 814 rightBottom.ConstrainTo(bounds); 815 816 rect.SetLeftTop(leftTop); 817 rect.SetRightBottom(rightBottom); 818 } 819 820 BBitmap* 821 ShowImageView::CopyFromRect(BRect srcRect) 822 { 823 BRect rect(0, 0, srcRect.Width(), srcRect.Height()); 824 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 825 BBitmap *bitmap = new BBitmap(rect, fBitmap->ColorSpace(), true); 826 if (bitmap == NULL) return NULL; 827 828 if (bitmap->Lock()) { 829 bitmap->AddChild(&view); 830 view.DrawBitmap(fBitmap, srcRect, rect); 831 view.Sync(); 832 bitmap->RemoveChild(&view); 833 bitmap->Unlock(); 834 } 835 836 return bitmap; 837 } 838 839 BBitmap* 840 ShowImageView::CopySelection(uchar alpha, bool imageSize) 841 { 842 bool hasAlpha = alpha != 255; 843 844 if (!HasSelection()) return NULL; 845 846 BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height()); 847 if (!imageSize) { 848 // scale image to view size 849 rect.right = floorf((rect.right + 1.0) * fScaleX - 1.0); 850 rect.bottom = floorf((rect.bottom + 1.0) * fScaleY - 1.0); 851 } 852 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW); 853 BBitmap *bitmap = new BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true); 854 if (bitmap == NULL) return NULL; 855 856 if (bitmap->Lock()) { 857 bitmap->AddChild(&view); 858 if (fSelBitmap) 859 view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect); 860 else 861 view.DrawBitmap(fBitmap, fCopyFromRect, rect); 862 if (hasAlpha) { 863 view.SetDrawingMode(B_OP_SUBTRACT); 864 view.SetHighColor(0, 0, 0, 255-alpha); 865 view.FillRect(rect, B_SOLID_HIGH); 866 } 867 view.Sync(); 868 bitmap->RemoveChild(&view); 869 bitmap->Unlock(); 870 } 871 872 return bitmap; 873 } 874 875 bool 876 ShowImageView::AddSupportedTypes(BMessage* msg, BBitmap* bitmap) 877 { 878 bool found = false; 879 BTranslatorRoster *roster = BTranslatorRoster::Default(); 880 if (roster == NULL) return false; 881 882 BBitmapStream stream(bitmap); 883 884 translator_info *outInfo; 885 int32 outNumInfo; 886 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 887 for (int32 i = 0; i < outNumInfo; i++) { 888 const translation_format *fmts; 889 int32 num_fmts; 890 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 891 for (int32 j = 0; j < num_fmts; j++) { 892 if (strcmp(fmts[j].MIME, "image/x-be-bitmap") != 0) { 893 // needed to send data in message 894 msg->AddString("be:types", fmts[j].MIME); 895 // needed to pass data via file 896 msg->AddString("be:filetypes", fmts[j].MIME); 897 msg->AddString("be:type_descriptions", fmts[j].name); 898 } 899 found = true; 900 } 901 } 902 } 903 stream.DetachBitmap(&bitmap); 904 return found; 905 } 906 907 void 908 ShowImageView::BeginDrag(BPoint sourcePoint) 909 { 910 BBitmap* bitmap = CopySelection(128, false); 911 if (bitmap == NULL) return; 912 913 SetMouseEventMask(B_POINTER_EVENTS); 914 BPoint leftTop(fSelectionRect.left, fSelectionRect.top); 915 916 // fill the drag message 917 BMessage drag(B_SIMPLE_DATA); 918 drag.AddInt32("be:actions", B_COPY_TARGET); 919 drag.AddString("be:clip_name", "Bitmap Clip"); 920 // ShowImage specific fields 921 drag.AddPoint("be:_source_point", sourcePoint); 922 drag.AddRect("be:_frame", fSelectionRect); 923 if (AddSupportedTypes(&drag, bitmap)) { 924 // we also support "Passing Data via File" protocol 925 drag.AddString("be:types", B_FILE_MIME_TYPE); 926 // avoid flickering of dragged bitmap caused by drawing into the window 927 AnimateSelection(false); 928 sourcePoint -= leftTop; 929 sourcePoint.x *= fScaleX; 930 sourcePoint.y *= fScaleY; 931 // DragMessage takes ownership of bitmap 932 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint); 933 bitmap = NULL; 934 } 935 } 936 937 bool 938 ShowImageView::OutputFormatForType(BBitmap* bitmap, const char* type, translation_format* format) 939 { 940 bool found = false; 941 942 BTranslatorRoster *roster = BTranslatorRoster::Default(); 943 if (roster == NULL) return false; 944 945 BBitmapStream stream(bitmap); 946 947 translator_info *outInfo; 948 int32 outNumInfo; 949 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) { 950 for (int32 i = 0; i < outNumInfo; i++) { 951 const translation_format *fmts; 952 int32 num_fmts; 953 roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts); 954 for (int32 j = 0; j < num_fmts; j++) { 955 if (strcmp(fmts[j].MIME, type) == 0) { 956 *format = fmts[j]; 957 found = true; 958 break; 959 } 960 } 961 } 962 } 963 stream.DetachBitmap(&bitmap); 964 return found; 965 } 966 967 void 968 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, translation_format* format) 969 { 970 BTranslatorRoster *roster = BTranslatorRoster::Default(); 971 BBitmapStream stream(bitmap); // destructor deletes bitmap 972 // write data 973 BFile file(dir, name, B_WRITE_ONLY); 974 roster->Translate(&stream, NULL, NULL, &file, format->type); 975 // set mime type 976 BNodeInfo info(&file); 977 if (info.InitCheck() == B_OK) { 978 info.SetType(format->MIME); 979 } 980 } 981 982 void 983 ShowImageView::SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format) 984 { 985 BMessage reply(B_MIME_DATA); 986 BBitmapStream stream(bitmap); // destructor deletes bitmap 987 BTranslatorRoster *roster = BTranslatorRoster::Default(); 988 BMallocIO memStream; 989 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) { 990 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength()); 991 msg->SendReply(&reply); 992 } 993 } 994 995 void 996 ShowImageView::HandleDrop(BMessage* msg) 997 { 998 BMessage data(B_MIME_DATA); 999 entry_ref dirRef; 1000 BString name, type; 1001 bool saveToFile; 1002 bool sendInMessage; 1003 BBitmap *bitmap; 1004 1005 saveToFile = msg->FindString("be:filetypes", &type) == B_OK && 1006 msg->FindRef("directory", &dirRef) == B_OK && 1007 msg->FindString("name", &name) == B_OK; 1008 1009 sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK; 1010 1011 bitmap = CopySelection(); 1012 if (bitmap == NULL) return; 1013 1014 translation_format format; 1015 if (!OutputFormatForType(bitmap, type.String(), &format)) { 1016 delete bitmap; 1017 return; 1018 } 1019 1020 if (saveToFile) { 1021 BDirectory dir(&dirRef); 1022 SaveToFile(&dir, name.String(), bitmap, &format); 1023 } else if (sendInMessage) { 1024 SendInMessage(msg, bitmap, &format); 1025 } else { 1026 delete bitmap; 1027 } 1028 } 1029 1030 void 1031 ShowImageView::MoveImage() 1032 { 1033 BPoint point, delta; 1034 uint32 buttons; 1035 // get CURRENT position 1036 GetMouse(&point, &buttons); 1037 point = ConvertToScreen(point); 1038 delta = fFirstPoint - point; 1039 fFirstPoint = point; 1040 ScrollRestrictedBy(delta.x, delta.y); 1041 // in case we miss MouseUp 1042 if ((GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0) 1043 fMovesImage = false; 1044 } 1045 1046 uint32 1047 ShowImageView::GetMouseButtons() 1048 { 1049 uint32 buttons; 1050 BPoint point; 1051 GetMouse(&point, &buttons); 1052 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1053 if ((modifiers() & B_CONTROL_KEY) != 0) { 1054 buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button 1055 } else if ((modifiers() & B_SHIFT_KEY) != 0) { 1056 buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button 1057 } 1058 } 1059 return buttons; 1060 } 1061 1062 void 1063 ShowImageView::GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits, BRect &destRect) 1064 { 1065 destRect = selection; 1066 ConstrainToImage(destRect); 1067 1068 srcBits = selection; 1069 if (srcBits.left < 0) 1070 srcBits.left = -(srcBits.left); 1071 else 1072 srcBits.left = 0; 1073 if (srcBits.top < 0) 1074 srcBits.top = -(srcBits.top); 1075 else 1076 srcBits.top = 0; 1077 if (srcBits.right > fBitmap->Bounds().right) 1078 srcBits.right = srcBits.left + destRect.Width(); 1079 else 1080 srcBits.right = merge->Bounds().right; 1081 if (srcBits.bottom > fBitmap->Bounds().bottom) 1082 srcBits.bottom = srcBits.top + destRect.Height(); 1083 else 1084 srcBits.bottom = merge->Bounds().bottom; 1085 } 1086 1087 void 1088 ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect) 1089 { 1090 GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect); 1091 } 1092 1093 void 1094 ShowImageView::MergeWithBitmap(BBitmap *merge, BRect selection) 1095 { 1096 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1097 BBitmap *bitmap = new BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1098 if (bitmap == NULL) 1099 return; 1100 1101 if (bitmap->Lock()) { 1102 bitmap->AddChild(&view); 1103 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1104 BRect srcBits, destRect; 1105 GetMergeRects(merge, selection, srcBits, destRect); 1106 view.DrawBitmap(merge, srcBits, destRect); 1107 1108 view.Sync(); 1109 bitmap->RemoveChild(&view); 1110 bitmap->Unlock(); 1111 1112 DeleteBitmap(); 1113 fBitmap = bitmap; 1114 1115 BMessenger msgr(Window()); 1116 msgr.SendMessage(MSG_MODIFIED); 1117 } else 1118 delete bitmap; 1119 } 1120 1121 void 1122 ShowImageView::MergeSelection() 1123 { 1124 if (!HasSelection()) 1125 return; 1126 1127 if (!fSelBitmap) { 1128 // Even though the merge will not change 1129 // the background image, I still need to save 1130 // some undo information here 1131 fUndo.SetTo(fSelectionRect, NULL, CopySelection()); 1132 return; 1133 } 1134 1135 // Merge selection with background 1136 fUndo.SetTo(fSelectionRect, CopyFromRect(fSelectionRect), CopySelection()); 1137 MergeWithBitmap(fSelBitmap, fSelectionRect); 1138 } 1139 1140 void 1141 ShowImageView::MouseDown(BPoint position) 1142 { 1143 BPoint point; 1144 uint32 buttons; 1145 MakeFocus(true); 1146 1147 point = ViewToImage(position); 1148 buttons = GetMouseButtons(); 1149 1150 if (HasSelection() && fSelectionRect.Contains(point) && 1151 (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) { 1152 if (!fSelBitmap) { 1153 fSelBitmap = CopySelection(); 1154 } 1155 BPoint sourcePoint = point; 1156 BeginDrag(sourcePoint); 1157 1158 while (buttons) { 1159 // Keep reading mouse movement until 1160 // the user lets up on all mouse buttons 1161 GetMouse(&point, &buttons); 1162 snooze(25 * 1000); 1163 // sleep for 25 milliseconds to minimize CPU usage during loop 1164 } 1165 1166 if (Bounds().Contains(point)) { 1167 // If selection stayed inside this view 1168 // (Some of the selection may be in the border area, which can be OK) 1169 BPoint last, diff; 1170 last = ViewToImage(point); 1171 diff = last - sourcePoint; 1172 1173 BRect newSelection = fSelectionRect; 1174 newSelection.OffsetBy(diff); 1175 1176 if (fBitmap->Bounds().Intersects(newSelection)) { 1177 // Do not accept the new selection box location 1178 // if it does not intersect with the bitmap rectangle 1179 fSelectionRect = newSelection; 1180 Invalidate(); 1181 } 1182 } 1183 1184 AnimateSelection(true); 1185 1186 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 1187 MergeSelection(); 1188 // If there is an existing selection, 1189 // Make it part of the background image 1190 1191 // begin new selection 1192 SetHasSelection(true); 1193 fMakesSelection = true; 1194 SetMouseEventMask(B_POINTER_EVENTS); 1195 ConstrainToImage(point); 1196 fFirstPoint = point; 1197 fCopyFromRect.Set(point.x, point.y, point.x, point.y); 1198 fSelectionRect = fCopyFromRect; 1199 Invalidate(); 1200 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1201 ShowPopUpMenu(ConvertToScreen(position)); 1202 } else if (buttons == B_TERTIARY_MOUSE_BUTTON) { 1203 // move image in window 1204 SetMouseEventMask(B_POINTER_EVENTS); 1205 fMovesImage = true; 1206 fFirstPoint = ConvertToScreen(position); 1207 } 1208 } 1209 1210 void 1211 ShowImageView::UpdateSelectionRect(BPoint point, bool final) 1212 { 1213 BRect oldSelection = fCopyFromRect; 1214 point = ViewToImage(point); 1215 ConstrainToImage(point); 1216 fCopyFromRect.left = min(fFirstPoint.x, point.x); 1217 fCopyFromRect.right = max(fFirstPoint.x, point.x); 1218 fCopyFromRect.top = min(fFirstPoint.y, point.y); 1219 fCopyFromRect.bottom = max(fFirstPoint.y, point.y); 1220 fSelectionRect = fCopyFromRect; 1221 if (final) { 1222 // selection must be at least 2 pixels wide or 2 pixels tall 1223 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0) 1224 SetHasSelection(false); 1225 } 1226 if (oldSelection != fCopyFromRect || !HasSelection()) { 1227 BRect updateRect; 1228 updateRect = oldSelection | fCopyFromRect; 1229 updateRect = ImageToView(updateRect); 1230 updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE); 1231 Invalidate(updateRect); 1232 } 1233 } 1234 1235 void 1236 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *pmsg) 1237 { 1238 if (fMakesSelection) { 1239 UpdateSelectionRect(point, false); 1240 } else if (fMovesImage) { 1241 MoveImage(); 1242 } 1243 } 1244 1245 void 1246 ShowImageView::MouseUp(BPoint point) 1247 { 1248 if (fMakesSelection) { 1249 UpdateSelectionRect(point, true); 1250 fMakesSelection = false; 1251 } else if (fMovesImage) { 1252 MoveImage(); 1253 if (fMovesImage) 1254 fMovesImage = false; 1255 } 1256 AnimateSelection(true); 1257 } 1258 1259 float 1260 ShowImageView::LimitToRange(float v, orientation o, bool absolute) 1261 { 1262 BScrollBar* psb = ScrollBar(o); 1263 if (psb) { 1264 float min, max, pos; 1265 pos = v; 1266 if (!absolute) { 1267 pos += psb->Value(); 1268 } 1269 psb->GetRange(&min, &max); 1270 if (pos < min) { 1271 pos = min; 1272 } else if (pos > max) { 1273 pos = max; 1274 } 1275 v = pos; 1276 if (!absolute) { 1277 v -= psb->Value(); 1278 } 1279 } 1280 return v; 1281 } 1282 1283 void 1284 ShowImageView::ScrollRestricted(float x, float y, bool absolute) 1285 { 1286 if (x != 0) { 1287 x = LimitToRange(x, B_HORIZONTAL, absolute); 1288 } 1289 1290 if (y != 0) { 1291 y = LimitToRange(y, B_VERTICAL, absolute); 1292 } 1293 1294 // hide the caption when using mouse wheel 1295 // in full screen mode 1296 bool caption = fShowCaption; 1297 if (caption) { 1298 fShowCaption = false; 1299 UpdateCaption(); 1300 } 1301 1302 ScrollBy(x, y); 1303 1304 if (caption) { 1305 // show the caption again 1306 fShowCaption = true; 1307 UpdateCaption(); 1308 } 1309 } 1310 1311 // XXX method is not unused 1312 void 1313 ShowImageView::ScrollRestrictedTo(float x, float y) 1314 { 1315 ScrollRestricted(x, y, true); 1316 } 1317 1318 void 1319 ShowImageView::ScrollRestrictedBy(float x, float y) 1320 { 1321 ScrollRestricted(x, y, false); 1322 } 1323 1324 void 1325 ShowImageView::KeyDown (const char * bytes, int32 numBytes) 1326 { 1327 if (numBytes == 1) { 1328 switch (*bytes) { 1329 case B_DOWN_ARROW: 1330 ScrollRestrictedBy(0, 10); 1331 break; 1332 case B_UP_ARROW: 1333 ScrollRestrictedBy(0, -10); 1334 break; 1335 case B_LEFT_ARROW: 1336 ScrollRestrictedBy(-10, 0); 1337 break; 1338 case B_RIGHT_ARROW: 1339 ScrollRestrictedBy(10, 0); 1340 break; 1341 case B_SPACE: 1342 case B_ENTER: 1343 NextFile(); 1344 break; 1345 case B_BACKSPACE: 1346 PrevFile(); 1347 break; 1348 case B_HOME: 1349 break; 1350 case B_END: 1351 break; 1352 case B_ESCAPE: 1353 if (fSlideShow) { 1354 BMessenger msgr(Window()); 1355 msgr.SendMessage(MSG_SLIDE_SHOW); 1356 } 1357 ClearSelection(); 1358 break; 1359 } 1360 } 1361 } 1362 1363 void 1364 ShowImageView::MouseWheelChanged(BMessage *msg) 1365 { 1366 // The BeOS driver does not currently support 1367 // X wheel scrolling, therefore, dx is zero. 1368 // |dy| is the number of notches scrolled up or down. 1369 // When the wheel is scrolled down (towards the user) dy > 0 1370 // When the wheel is scrolled up (away from the user) dy < 0 1371 const float kscrollBy = 40; 1372 float dy, dx; 1373 float x, y; 1374 x = 0; y = 0; 1375 if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) { 1376 x = dx * kscrollBy; 1377 } 1378 if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) { 1379 y = dy * kscrollBy; 1380 } 1381 1382 ScrollRestrictedBy(x, y); 1383 } 1384 1385 void 1386 ShowImageView::ShowPopUpMenu(BPoint screen) 1387 { 1388 BPopUpMenu* menu = new BPopUpMenu("PopUpMenu"); 1389 menu->SetAsyncAutoDestruct(true); 1390 menu->SetRadioMode(false); 1391 1392 ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window()); 1393 if (showImage) { 1394 showImage->BuildViewMenu(menu); 1395 } 1396 menu->AddSeparatorItem(); 1397 menu->AddItem(new BMenuItem("Cancel", 0, 0)); 1398 1399 screen -= BPoint(10, 10); 1400 menu->Go(screen, true, false, true); 1401 } 1402 1403 void 1404 ShowImageView::SettingsSetBool(const char* name, bool value) 1405 { 1406 ShowImageSettings* settings; 1407 settings = my_app->Settings(); 1408 if (settings->Lock()) { 1409 settings->SetBool(name, value); 1410 settings->Unlock(); 1411 } 1412 } 1413 1414 void 1415 ShowImageView::MessageReceived(BMessage *pmsg) 1416 { 1417 switch (pmsg->what) { 1418 case MSG_SELECTION_BITMAP: 1419 { 1420 // In response to a B_SIMPLE_DATA message, a view will 1421 // send this message and expect a reply with a pointer to 1422 // the currently selected bitmap clip. Although this view 1423 // allocates the BBitmap * sent in the reply, it is only 1424 // to be used and deleted by the view that is being replied to. 1425 BMessage msg; 1426 msg.AddPointer("be:_bitmap_ptr", CopySelection()); 1427 pmsg->SendReply(&msg); 1428 break; 1429 } 1430 1431 case B_SIMPLE_DATA: 1432 // If a user drags a clip from another ShowImage window, 1433 // request a BBitmap pointer to that clip, allocated by the 1434 // other view, for use solely by this view, so that it can 1435 // be dropped/pasted onto this view. 1436 if (pmsg->WasDropped()) { 1437 BMessenger retMsgr, localMsgr(this); 1438 retMsgr = pmsg->ReturnAddress(); 1439 if (retMsgr != localMsgr) { 1440 BMessage msgReply; 1441 retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply); 1442 BBitmap *bitmap = NULL; 1443 if (msgReply.FindPointer("be:_bitmap_ptr", 1444 reinterpret_cast<void **>(&bitmap)) == B_OK) { 1445 BRect sourceRect; 1446 BPoint point, sourcePoint; 1447 pmsg->FindPoint("be:_source_point", &sourcePoint); 1448 pmsg->FindRect("be:_frame", &sourceRect); 1449 point = pmsg->DropPoint(); 1450 point.Set(point.x - (sourcePoint.x - sourceRect.left), 1451 point.y - (sourcePoint.y - sourceRect.top)); 1452 // adjust drop point before scaling is factored in 1453 point = ConvertFromScreen(point); 1454 point = ViewToImage(point); 1455 1456 PasteBitmap(bitmap, point); 1457 } 1458 1459 } 1460 } 1461 break; 1462 1463 case B_COPY_TARGET: 1464 HandleDrop(pmsg); 1465 break; 1466 case B_MOUSE_WHEEL_CHANGED: 1467 MouseWheelChanged(pmsg); 1468 break; 1469 case MSG_INVALIDATE: 1470 Invalidate(); 1471 break; 1472 default: 1473 BView::MessageReceived(pmsg); 1474 break; 1475 } 1476 } 1477 1478 void 1479 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) 1480 { 1481 float prop, range; 1482 BScrollBar *psb; 1483 1484 psb = ScrollBar(o); 1485 if (psb) { 1486 if (fHasBorder && !fShrinkOrZoomToBounds) { 1487 bitmapLength += BORDER_WIDTH*2; 1488 } 1489 range = bitmapLength - viewLength; 1490 if (range < 0.0) { 1491 range = 0.0; 1492 } 1493 prop = viewLength / bitmapLength; 1494 if (prop > 1.0) { 1495 prop = 1.0; 1496 } 1497 psb->SetRange(0, range); 1498 psb->SetProportion(prop); 1499 psb->SetSteps(10, 100); 1500 } 1501 } 1502 1503 void 1504 ShowImageView::FixupScrollBars() 1505 { 1506 BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0); 1507 if (fBitmap) { 1508 BRect rect(AlignBitmap()); 1509 rctbitmap.Set(0, 0, rect.Width(), rect.Height()); 1510 } 1511 1512 FixupScrollBar(B_HORIZONTAL, rctbitmap.Width(), rctview.Width()); 1513 FixupScrollBar(B_VERTICAL, rctbitmap.Height(), rctview.Height()); 1514 } 1515 1516 int32 1517 ShowImageView::CurrentPage() 1518 { 1519 return fDocumentIndex; 1520 } 1521 1522 int32 1523 ShowImageView::PageCount() 1524 { 1525 return fDocumentCount; 1526 } 1527 1528 void 1529 ShowImageView::Undo() 1530 { 1531 int32 undoType = fUndo.GetType(); 1532 if (undoType != UNDO_UNDO && undoType != UNDO_REDO) 1533 return; 1534 1535 // backup current selection 1536 BRect undoneSelRect; 1537 BBitmap *undoneSelection; 1538 undoneSelRect = fSelectionRect; 1539 undoneSelection = CopySelection(); 1540 1541 if (undoType == UNDO_UNDO) { 1542 BBitmap *undoRestore; 1543 undoRestore = fUndo.GetRestoreBitmap(); 1544 if (undoRestore) 1545 MergeWithBitmap(undoRestore, fUndo.GetRect()); 1546 } 1547 1548 // restore previous image/selection 1549 BBitmap *undoSelection; 1550 undoSelection = fUndo.GetSelectionBitmap(); 1551 // NOTE: ShowImageView is responsible for deleting this bitmap 1552 // (Which it will, as it would with a fSelBitmap that it allocated itself) 1553 if (!undoSelection) 1554 SetHasSelection(false); 1555 else { 1556 SetHasSelection(true); 1557 fCopyFromRect = BRect(); 1558 fSelectionRect = fUndo.GetRect(); 1559 fSelBitmap = undoSelection; 1560 } 1561 1562 fUndo.Undo(undoneSelRect, NULL, undoneSelection); 1563 1564 Invalidate(); 1565 } 1566 1567 void 1568 ShowImageView::AddWhiteRect(BRect &rect) 1569 { 1570 // Paint white rectangle, using rect, into the background image 1571 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW); 1572 BBitmap *bitmap = new BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true); 1573 if (bitmap == NULL) 1574 return; 1575 1576 if (bitmap->Lock()) { 1577 bitmap->AddChild(&view); 1578 view.DrawBitmap(fBitmap, fBitmap->Bounds()); 1579 1580 view.FillRect(rect, B_SOLID_LOW); 1581 // draw white rect 1582 1583 view.Sync(); 1584 bitmap->RemoveChild(&view); 1585 bitmap->Unlock(); 1586 1587 DeleteBitmap(); 1588 fBitmap = bitmap; 1589 1590 BMessenger msgr(Window()); 1591 msgr.SendMessage(MSG_MODIFIED); 1592 } else 1593 delete bitmap; 1594 } 1595 1596 void 1597 ShowImageView::RemoveSelection(bool bToClipboard) 1598 { 1599 if (HasSelection()) { 1600 BRect rect = fSelectionRect; 1601 bool bCutBackground = (fSelBitmap) ? false : true; 1602 BBitmap *selection, *restore = NULL; 1603 selection = CopySelection(); 1604 1605 if (bToClipboard) 1606 CopySelectionToClipboard(); 1607 SetHasSelection(false); 1608 1609 if (bCutBackground) { 1610 // If the user hasn't dragged the selection, 1611 // paint a white rectangle where the selection was 1612 restore = CopyFromRect(rect); 1613 AddWhiteRect(rect); 1614 } 1615 1616 fUndo.SetTo(rect, restore, selection); 1617 Invalidate(); 1618 } 1619 } 1620 1621 void 1622 ShowImageView::Cut() 1623 { 1624 // Copy the selection to the clipboard, 1625 // then remove it 1626 RemoveSelection(true); 1627 } 1628 1629 void 1630 ShowImageView::PasteBitmap(BBitmap *bitmap, BPoint point) 1631 { 1632 if (bitmap && bitmap->IsValid()) { 1633 MergeSelection(); 1634 1635 SetHasSelection(true); 1636 fSelBitmap = bitmap; 1637 fCopyFromRect = BRect(); 1638 fSelectionRect = bitmap->Bounds(); 1639 1640 BRect offsetRect = fSelectionRect; 1641 offsetRect.OffsetBy(point); 1642 if (fBitmap->Bounds().Intersects(offsetRect)) 1643 // Move the selection rectangle to desired origin, 1644 // but only if the resulting selection rectangle 1645 // intersects with the background bitmap rectangle 1646 fSelectionRect = offsetRect; 1647 1648 Invalidate(); 1649 } 1650 } 1651 1652 void 1653 ShowImageView::Paste() 1654 { 1655 if (be_clipboard->Lock()) { 1656 BMessage *pclip; 1657 if ((pclip = be_clipboard->Data()) != NULL) { 1658 BPoint point(0, 0); 1659 pclip->FindPoint("be:location", &point); 1660 BBitmap *pbits; 1661 pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip)); 1662 PasteBitmap(pbits, point); 1663 } 1664 1665 be_clipboard->Unlock(); 1666 } 1667 } 1668 1669 void 1670 ShowImageView::SelectAll() 1671 { 1672 SetHasSelection(true); 1673 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()); 1674 fSelectionRect = fCopyFromRect; 1675 Invalidate(); 1676 } 1677 1678 void 1679 ShowImageView::ClearSelection() 1680 { 1681 // Remove the selection, 1682 // DON'T copy it to the clipboard 1683 RemoveSelection(false); 1684 } 1685 1686 void 1687 ShowImageView::SetHasSelection(bool bHasSelection) 1688 { 1689 DeleteSelBitmap(); 1690 fHasSelection = bHasSelection; 1691 1692 BMessage msg(MSG_SELECTION); 1693 msg.AddBool("has_selection", fHasSelection); 1694 BMessenger msgr(Window()); 1695 msgr.SendMessage(&msg); 1696 } 1697 1698 void 1699 ShowImageView::CopySelectionToClipboard() 1700 { 1701 if (HasSelection() && be_clipboard->Lock()) { 1702 be_clipboard->Clear(); 1703 BMessage *clip = NULL; 1704 if ((clip = be_clipboard->Data()) != NULL) { 1705 BMessage data; 1706 BBitmap* bitmap = CopySelection(); 1707 if (bitmap != NULL) { 1708 #if 0 1709 // According to BeBook and Becasso, Gobe Productive do the following. 1710 // Paste works in Productive, but not in Becasso and original ShowImage. 1711 BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused 1712 bitmap->Archive(&msg); 1713 clip->AddMessage("image/x-be-bitmap", &msg); 1714 #else 1715 // original ShowImage performs this. Paste works with original ShowImage. 1716 bitmap->Archive(clip); 1717 // original ShowImage uses be:location for insertion point 1718 clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top)); 1719 #endif 1720 delete bitmap; 1721 be_clipboard->Commit(); 1722 } 1723 } 1724 be_clipboard->Unlock(); 1725 } 1726 } 1727 1728 void 1729 ShowImageView::FirstPage() 1730 { 1731 if (fDocumentIndex != 1) { 1732 fDocumentIndex = 1; 1733 SetImage(NULL); 1734 } 1735 } 1736 1737 void 1738 ShowImageView::LastPage() 1739 { 1740 if (fDocumentIndex != fDocumentCount) { 1741 fDocumentIndex = fDocumentCount; 1742 SetImage(NULL); 1743 } 1744 } 1745 1746 void 1747 ShowImageView::NextPage() 1748 { 1749 if (fDocumentIndex < fDocumentCount) { 1750 fDocumentIndex++; 1751 SetImage(NULL); 1752 } 1753 } 1754 1755 void 1756 ShowImageView::PrevPage() 1757 { 1758 if (fDocumentIndex > 1) { 1759 fDocumentIndex--; 1760 SetImage(NULL); 1761 } 1762 } 1763 1764 int 1765 ShowImageView::CompareEntries(const void* a, const void* b) 1766 { 1767 entry_ref *r1, *r2; 1768 r1 = *(entry_ref**)a; 1769 r2 = *(entry_ref**)b; 1770 return strcasecmp(r1->name, r2->name); 1771 } 1772 1773 void 1774 ShowImageView::GoToPage(int32 page) 1775 { 1776 if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) { 1777 fDocumentIndex = page; 1778 SetImage(NULL); 1779 } 1780 } 1781 1782 void 1783 ShowImageView::FreeEntries(BList* entries) 1784 { 1785 const int32 n = entries->CountItems(); 1786 for (int32 i = 0; i < n; i ++) { 1787 entry_ref* ref = (entry_ref*)entries->ItemAt(i); 1788 delete ref; 1789 } 1790 entries->MakeEmpty(); 1791 } 1792 1793 bool 1794 ShowImageView::FindNextImage(entry_ref* image, bool next, bool rewind) 1795 { 1796 ASSERT(next || !rewind); 1797 BEntry curImage(&fCurrentRef); 1798 entry_ref entry, *ref; 1799 BDirectory parent; 1800 BList entries; 1801 bool found = false; 1802 int32 cur; 1803 1804 if (curImage.GetParent(&parent) != B_OK) 1805 return false; 1806 1807 while (parent.GetNextRef(&entry) == B_OK) { 1808 if (entry != fCurrentRef) { 1809 entries.AddItem(new entry_ref(entry)); 1810 } else { 1811 // insert current ref, so we can find it easily after sorting 1812 entries.AddItem(&fCurrentRef); 1813 } 1814 } 1815 1816 entries.SortItems(CompareEntries); 1817 1818 cur = entries.IndexOf(&fCurrentRef); 1819 ASSERT(cur >= 0); 1820 1821 // remove it so FreeEntries() does not delete it 1822 entries.RemoveItem(&fCurrentRef); 1823 1824 if (next) { 1825 // find the next image in the list 1826 if (rewind) cur = 0; // start with first 1827 for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) { 1828 if (IsImage(ref)) { 1829 found = true; 1830 *image = (const entry_ref)*ref; 1831 break; 1832 } 1833 } 1834 } else { 1835 // find the previous image in the list 1836 cur --; 1837 for (; cur >= 0; cur --) { 1838 ref = (entry_ref*)entries.ItemAt(cur); 1839 if (IsImage(ref)) { 1840 found = true; 1841 *image = (const entry_ref)*ref; 1842 break; 1843 } 1844 } 1845 } 1846 1847 FreeEntries(&entries); 1848 return found; 1849 } 1850 1851 bool 1852 ShowImageView::ShowNextImage(bool next, bool rewind) 1853 { 1854 entry_ref ref; 1855 1856 if (FindNextImage(&ref, next, rewind)) { 1857 SetImage(&ref); 1858 return true; 1859 } 1860 return false; 1861 } 1862 1863 bool 1864 ShowImageView::NextFile() 1865 { 1866 return ShowNextImage(true, false); 1867 } 1868 1869 bool 1870 ShowImageView::PrevFile() 1871 { 1872 return ShowNextImage(false, false); 1873 } 1874 1875 bool 1876 ShowImageView::FirstFile() 1877 { 1878 return ShowNextImage(true, true); 1879 } 1880 1881 void 1882 ShowImageView::SetZoom(float zoom) 1883 { 1884 if ((fScaleBilinear || fDither) && fZoom != zoom) { 1885 DeleteScaler(); 1886 } 1887 fZoom = zoom; 1888 FixupScrollBars(); 1889 Invalidate(); 1890 } 1891 1892 void 1893 ShowImageView::ZoomIn() 1894 { 1895 if (fZoom < 16) { 1896 SetZoom(fZoom + 0.25); 1897 } 1898 } 1899 1900 void 1901 ShowImageView::ZoomOut() 1902 { 1903 if (fZoom > 0.25) { 1904 SetZoom(fZoom - 0.25); 1905 } 1906 } 1907 1908 void 1909 ShowImageView::SetSlideShowDelay(float seconds) 1910 { 1911 ShowImageSettings* settings; 1912 int32 delay = (int)(seconds * 10.0); 1913 if (fSlideShowDelay != delay) { 1914 // update counter 1915 fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown); 1916 if (fSlideShowCountDown <= 0) { 1917 // show next image on next Pulse() 1918 fSlideShowCountDown = 1; 1919 } 1920 fSlideShowDelay = delay; 1921 settings = my_app->Settings(); 1922 if (settings->Lock()) { 1923 settings->SetInt32("SlideShowDelay", fSlideShowDelay); 1924 settings->Unlock(); 1925 } 1926 } 1927 } 1928 1929 void 1930 ShowImageView::StartSlideShow() 1931 { 1932 fSlideShow = true; fSlideShowCountDown = fSlideShowDelay; 1933 } 1934 1935 void 1936 ShowImageView::StopSlideShow() 1937 { 1938 fSlideShow = false; 1939 } 1940 1941 void 1942 ShowImageView::DoImageOperation(ImageProcessor::operation op, bool quiet) 1943 { 1944 BMessenger msgr; 1945 ImageProcessor imageProcessor(op, fBitmap, msgr, 0); 1946 imageProcessor.Start(false); 1947 BBitmap* bm = imageProcessor.DetachBitmap(); 1948 if (bm == NULL) { 1949 // operation failed 1950 return; 1951 } 1952 1953 // update orientation state 1954 if (op != ImageProcessor::kInvert) { 1955 // Note: If one of these fails, check its definition in class ImageProcessor. 1956 ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations); 1957 ASSERT(ImageProcessor::kRotateAntiClockwise < ImageProcessor::kNumberOfAffineTransformations); 1958 ASSERT(ImageProcessor::kMirrorVertical < ImageProcessor::kNumberOfAffineTransformations); 1959 ASSERT(ImageProcessor::kMirrorHorizontal < ImageProcessor::kNumberOfAffineTransformations); 1960 fImageOrientation = fTransformation[op][fImageOrientation]; 1961 } else { 1962 fInverted = !fInverted; 1963 } 1964 1965 if (!quiet) { 1966 // write orientation state 1967 BNode node(&fCurrentRef); 1968 int32 orientation = fImageOrientation; 1969 if (fInverted) orientation += 256; 1970 if (orientation != k0) { 1971 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation)); 1972 } else { 1973 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE); 1974 } 1975 } 1976 1977 // set new bitmap 1978 DeleteBitmap(); 1979 fBitmap = bm; 1980 1981 if (!quiet) { 1982 // remove selection 1983 SetHasSelection(false); 1984 Notify(NULL); 1985 } 1986 } 1987 1988 void 1989 ShowImageView::Rotate(int degree) 1990 { 1991 if (degree == 90) { 1992 fUndo.Clear(); 1993 DoImageOperation(ImageProcessor::kRotateClockwise); 1994 } else if (degree == 270) { 1995 fUndo.Clear(); 1996 DoImageOperation(ImageProcessor::kRotateAntiClockwise); 1997 } 1998 } 1999 2000 void 2001 ShowImageView::Mirror(bool vertical) 2002 { 2003 fUndo.Clear(); 2004 if (vertical) { 2005 DoImageOperation(ImageProcessor::kMirrorVertical); 2006 } else { 2007 DoImageOperation(ImageProcessor::kMirrorHorizontal); 2008 } 2009 } 2010 2011 void 2012 ShowImageView::Invert() 2013 { 2014 if (fBitmap->ColorSpace() != B_CMAP8) { 2015 // Only allow an invert operation if the 2016 // bitmap color space is supported by the 2017 // invert algorithm 2018 fUndo.Clear(); 2019 DoImageOperation(ImageProcessor::kInvert); 2020 } 2021 } 2022 2023 void 2024 ShowImageView::SetIcon(bool clear, icon_size which) 2025 { 2026 int32 size; 2027 switch (which) { 2028 case B_MINI_ICON: size = 16; 2029 break; 2030 case B_LARGE_ICON: size = 32; 2031 break; 2032 default: 2033 return; 2034 } 2035 2036 BRect rect(fBitmap->Bounds()); 2037 float s; 2038 s = size / (rect.Width()+1.0); 2039 2040 if (s * (rect.Height()+1.0) <= size) { 2041 rect.right = size-1; 2042 rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1; 2043 // center vertically 2044 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2); 2045 } else { 2046 s = size / (rect.Height()+1.0); 2047 rect.right = static_cast<int>(s * (rect.Width()+1.0))-1; 2048 rect.bottom = size-1; 2049 // center horizontally 2050 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0); 2051 } 2052 2053 // scale bitmap to thumbnail size 2054 BMessenger msgr; 2055 Scaler scaler(fBitmap, rect, msgr, 0, true); 2056 BBitmap* thumbnail = scaler.GetBitmap(); 2057 scaler.Start(false); 2058 ASSERT(thumbnail->ColorSpace() == B_CMAP8); 2059 // create icon from thumbnail 2060 BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8); 2061 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength()); 2062 BScreen screen; 2063 const uchar* src = (uchar*)thumbnail->Bits(); 2064 uchar* dest = (uchar*)icon.Bits(); 2065 const int32 srcBPR = thumbnail->BytesPerRow(); 2066 const int32 destBPR = icon.BytesPerRow(); 2067 const int32 dx = (int32)rect.left; 2068 const int32 dy = (int32)rect.top; 2069 2070 for (int32 y = 0; y <= rect.IntegerHeight(); y ++) { 2071 for (int32 x = 0; x <= rect.IntegerWidth(); x ++) { 2072 const uchar* s = src + y * srcBPR + x; 2073 uchar* d = dest + (y+dy) * destBPR + (x+dx); 2074 *d = *s; 2075 } 2076 } 2077 2078 // set icon 2079 BNode node(&fCurrentRef); 2080 BNodeInfo info(&node); 2081 info.SetIcon(clear ? NULL : &icon, which); 2082 } 2083 2084 void 2085 ShowImageView::SetIcon(bool clear) 2086 { 2087 SetIcon(clear, B_MINI_ICON); 2088 SetIcon(clear, B_LARGE_ICON); 2089 } 2090