1 /*
2 * Copyright 2003-2011, 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 * Stephan Aßmus <superstippi@gmx.de>
15 * Axel Dörfler, axeld@pinc-software.de
16 */
17
18
19 #include "ShowImageView.h"
20
21 #include <math.h>
22 #include <new>
23 #include <stdio.h>
24
25 #include <Alert.h>
26 #include <Application.h>
27 #include <Bitmap.h>
28 #include <BitmapStream.h>
29 #include <Catalog.h>
30 #include <Clipboard.h>
31 #include <ControlLook.h>
32 #include <Cursor.h>
33 #include <Debug.h>
34 #include <Directory.h>
35 #include <Entry.h>
36 #include <File.h>
37 #include <Locale.h>
38 #include <MenuBar.h>
39 #include <MenuItem.h>
40 #include <Message.h>
41 #include <NodeInfo.h>
42 #include <Path.h>
43 #include <PopUpMenu.h>
44 #include <Rect.h>
45 #include <Region.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 #include <ScrollBar.h>
49 #include <StopWatch.h>
50 #include <SupportDefs.h>
51 #include <TranslatorRoster.h>
52 #include <WindowScreen.h>
53
54 #include <tracker_private.h>
55
56 #include "ImageCache.h"
57 #include "ShowImageApp.h"
58 #include "ShowImageWindow.h"
59
60
61 using std::nothrow;
62
63
64 class PopUpMenu : public BPopUpMenu {
65 public:
66 PopUpMenu(const char* name, BMessenger target);
67 virtual ~PopUpMenu();
68
69 private:
70 BMessenger fTarget;
71 };
72
73
74 // the delay time for hiding the cursor in 1/10 seconds (the pulse rate)
75 #define HIDE_CURSOR_DELAY_TIME 20
76 #define STICKY_ZOOM_DELAY_TIME 5
77 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
78
79
80 const rgb_color kBorderColor = { 0, 0, 0, 255 };
81
82 enum ShowImageView::image_orientation
83 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations]
84 [kNumberOfOrientations] = {
85 // rotate 90°
86 {k90, k180, k270, k0, k270V, k0V, k90V, k0H},
87 // rotate -90°
88 {k270, k0, k90, k180, k90V, k0H, k270V, k0V},
89 // mirror vertical
90 {k0H, k270V, k0V, k90V, k180, k270, k0, k90},
91 // mirror horizontal
92 {k0V, k90V, k0H, k270V, k0, k90, k180, k270}
93 };
94
95 const rgb_color kAlphaLow = (rgb_color) { 0xbb, 0xbb, 0xbb, 0xff };
96 const rgb_color kAlphaHigh = (rgb_color) { 0xe0, 0xe0, 0xe0, 0xff };
97
98 const uint32 kMsgPopUpMenuClosed = 'pmcl';
99
100
101 inline void
blend_colors(uint8 * d,uint8 r,uint8 g,uint8 b,uint8 a)102 blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a)
103 {
104 d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8;
105 d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8;
106 d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8;
107 }
108
109
110 BBitmap*
compose_checker_background(const BBitmap * bitmap)111 compose_checker_background(const BBitmap* bitmap)
112 {
113 BBitmap* result = new (nothrow) BBitmap(bitmap);
114 if (result && !result->IsValid()) {
115 delete result;
116 result = NULL;
117 }
118 if (!result)
119 return NULL;
120
121 uint8* bits = (uint8*)result->Bits();
122 uint32 bpr = result->BytesPerRow();
123 uint32 width = result->Bounds().IntegerWidth() + 1;
124 uint32 height = result->Bounds().IntegerHeight() + 1;
125
126 for (uint32 i = 0; i < height; i++) {
127 uint8* p = bits;
128 for (uint32 x = 0; x < width; x++) {
129 uint8 alpha = p[3];
130 if (alpha < 255) {
131 p[3] = 255;
132 alpha = 255 - alpha;
133 if (x % 10 >= 5) {
134 if (i % 10 >= 5)
135 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
136 else
137 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
138
139 } else {
140 if (i % 10 >= 5)
141 blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
142 else
143 blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
144 }
145 }
146 p += 4;
147 }
148 bits += bpr;
149 }
150 return result;
151 }
152
153
154 // #pragma mark -
155
156
PopUpMenu(const char * name,BMessenger target)157 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
158 :
159 BPopUpMenu(name, false, false),
160 fTarget(target)
161 {
162 SetAsyncAutoDestruct(true);
163 }
164
165
~PopUpMenu()166 PopUpMenu::~PopUpMenu()
167 {
168 fTarget.SendMessage(kMsgPopUpMenuClosed);
169 }
170
171
172 // #pragma mark -
173
174
ShowImageView(const char * name,uint32 flags)175 ShowImageView::ShowImageView(const char* name, uint32 flags)
176 :
177 BView(name, flags),
178 fBitmapOwner(NULL),
179 fBitmap(NULL),
180 fDisplayBitmap(NULL),
181 fSelectionBitmap(NULL),
182
183 fZoom(1.0),
184
185 fScaleBilinear(true),
186
187 fBitmapLocationInView(0.0, 0.0),
188
189 fStretchToBounds(false),
190 fForceOriginalSize(false),
191 fHideCursor(false),
192 fScrollingBitmap(false),
193 fCreatingSelection(false),
194 fFirstPoint(0.0, 0.0),
195 fSelectionMode(false),
196 fAnimateSelection(true),
197 fHasSelection(false),
198 fShowCaption(false),
199 fShowingPopUpMenu(false),
200 fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
201 fStickyZoomCountDown(0),
202 fIsActiveWin(true),
203 fDefaultCursor(NULL),
204 fGrabCursor(NULL)
205 {
206 ShowImageSettings* settings = my_app->Settings();
207 if (settings->Lock()) {
208 fStretchToBounds = settings->GetBool("StretchToBounds",
209 fStretchToBounds);
210 fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
211 settings->Unlock();
212 }
213
214 fDefaultCursor = new BCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
215 fGrabCursor = new BCursor(B_CURSOR_ID_GRABBING);
216
217 SetViewColor(B_TRANSPARENT_COLOR);
218 SetHighColor(kBorderColor);
219 SetLowColor(0, 0, 0);
220 }
221
222
~ShowImageView()223 ShowImageView::~ShowImageView()
224 {
225 _DeleteBitmap();
226
227 delete fDefaultCursor;
228 delete fGrabCursor;
229 }
230
231
232 void
_AnimateSelection(bool enabled)233 ShowImageView::_AnimateSelection(bool enabled)
234 {
235 fAnimateSelection = enabled;
236 }
237
238
239 void
Pulse()240 ShowImageView::Pulse()
241 {
242 // animate marching ants
243 if (fHasSelection && fAnimateSelection && fIsActiveWin) {
244 fSelectionBox.Animate();
245 fSelectionBox.Draw(this, Bounds());
246 }
247
248 if (fHideCursor && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) {
249 if (fHideCursorCountDown == 0) {
250 // Go negative so this isn't triggered again
251 fHideCursorCountDown--;
252
253 BPoint mousePos;
254 uint32 buttons;
255 GetMouse(&mousePos, &buttons, false);
256 if (Bounds().Contains(mousePos)) {
257 be_app->ObscureCursor();
258
259 // Set current mouse coordinates to avoid the screen saver kicking in
260 ConvertToScreen(&mousePos);
261 set_mouse_position((int32)mousePos.x, (int32)mousePos.y);
262 }
263 } else if (fHideCursorCountDown > 0)
264 fHideCursorCountDown--;
265 }
266
267 if (fStickyZoomCountDown > 0)
268 fStickyZoomCountDown--;
269
270 }
271
272
273 void
_SendMessageToWindow(BMessage * message)274 ShowImageView::_SendMessageToWindow(BMessage* message)
275 {
276 BMessenger target(Window());
277 target.SendMessage(message);
278 }
279
280
281 void
_SendMessageToWindow(uint32 code)282 ShowImageView::_SendMessageToWindow(uint32 code)
283 {
284 BMessage message(code);
285 _SendMessageToWindow(&message);
286 }
287
288
289 //! send message to parent about new image
290 void
_Notify()291 ShowImageView::_Notify()
292 {
293 BMessage msg(MSG_UPDATE_STATUS);
294
295 msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
296 msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);
297
298 msg.AddInt32("colors", fBitmap->ColorSpace());
299 _SendMessageToWindow(&msg);
300
301 FixupScrollBars();
302 Invalidate();
303 }
304
305
306 void
_UpdateStatusText()307 ShowImageView::_UpdateStatusText()
308 {
309 BMessage msg(MSG_UPDATE_STATUS_TEXT);
310
311 if (fHasSelection) {
312 char size[50];
313 snprintf(size, sizeof(size), "(%.0fx%.0f)",
314 fSelectionBox.Bounds().Width() + 1.0,
315 fSelectionBox.Bounds().Height() + 1.0);
316
317 msg.AddString("status", size);
318 }
319
320 _SendMessageToWindow(&msg);
321 }
322
323
324 void
_DeleteBitmap()325 ShowImageView::_DeleteBitmap()
326 {
327 _DeleteSelectionBitmap();
328
329 if (fDisplayBitmap != fBitmap)
330 delete fDisplayBitmap;
331 fDisplayBitmap = NULL;
332
333 if (fBitmapOwner != NULL)
334 fBitmapOwner->ReleaseReference();
335 else
336 delete fBitmap;
337
338 fBitmapOwner = NULL;
339 fBitmap = NULL;
340 }
341
342
343 void
_DeleteSelectionBitmap()344 ShowImageView::_DeleteSelectionBitmap()
345 {
346 delete fSelectionBitmap;
347 fSelectionBitmap = NULL;
348 }
349
350
351 status_t
SetImage(const BMessage * message)352 ShowImageView::SetImage(const BMessage* message)
353 {
354 BBitmap* bitmap;
355 entry_ref ref;
356 if (message->FindPointer("bitmap", (void**)&bitmap) != B_OK
357 || message->FindRef("ref", &ref) != B_OK || bitmap == NULL)
358 return B_ERROR;
359
360 BitmapOwner* bitmapOwner;
361 message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
362
363 status_t status = SetImage(&ref, bitmap, bitmapOwner);
364 if (status == B_OK) {
365 fFormatDescription = message->FindString("type");
366 fMimeType = message->FindString("mime");
367 }
368
369 return status;
370 }
371
372
373 status_t
SetImage(const entry_ref * ref,BBitmap * bitmap,BitmapOwner * bitmapOwner)374 ShowImageView::SetImage(const entry_ref* ref, BBitmap* bitmap,
375 BitmapOwner* bitmapOwner)
376 {
377 // Delete the old one, and clear everything
378 _SetHasSelection(false);
379 fCreatingSelection = false;
380 _DeleteBitmap();
381
382 fBitmap = bitmap;
383 fBitmapOwner = bitmapOwner;
384 if (ref == NULL)
385 fCurrentRef.device = -1;
386 else
387 fCurrentRef = *ref;
388
389 if (fBitmap != NULL) {
390 // prepare the display bitmap
391 if (fBitmap->ColorSpace() == B_RGBA32)
392 fDisplayBitmap = compose_checker_background(fBitmap);
393 if (fDisplayBitmap == NULL)
394 fDisplayBitmap = fBitmap;
395
396 BNode node(ref);
397
398 // restore orientation
399 int32 orientation;
400 fImageOrientation = k0;
401 if (node.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
402 &orientation, sizeof(orientation)) == sizeof(orientation)) {
403 orientation &= 255;
404 switch (orientation) {
405 case k0:
406 break;
407 case k90:
408 _DoImageOperation(ImageProcessor::kRotateClockwise, true);
409 break;
410 case k180:
411 _DoImageOperation(ImageProcessor::kRotateClockwise, true);
412 _DoImageOperation(ImageProcessor::kRotateClockwise, true);
413 break;
414 case k270:
415 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
416 break;
417 case k0V:
418 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
419 break;
420 case k90V:
421 _DoImageOperation(ImageProcessor::kRotateClockwise, true);
422 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
423 break;
424 case k0H:
425 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true);
426 break;
427 case k270V:
428 _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
429 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
430 break;
431 }
432 }
433 }
434
435 BPath path(ref);
436 fCaption = path.Path();
437 fFormatDescription = "Bitmap";
438 fMimeType = "image/x-be-bitmap";
439
440 be_roster->AddToRecentDocuments(ref, kApplicationSignature);
441
442 FitToBounds();
443 _Notify();
444 return B_OK;
445 }
446
447
448 BPoint
ImageToView(BPoint p) const449 ShowImageView::ImageToView(BPoint p) const
450 {
451 p.x = floorf(fZoom * p.x + fBitmapLocationInView.x);
452 p.y = floorf(fZoom * p.y + fBitmapLocationInView.y);
453 return p;
454 }
455
456
457 BPoint
ViewToImage(BPoint p) const458 ShowImageView::ViewToImage(BPoint p) const
459 {
460 p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom);
461 p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom);
462 return p;
463 }
464
465
466 BRect
ImageToView(BRect r) const467 ShowImageView::ImageToView(BRect r) const
468 {
469 BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
470 BPoint rightBottom(r.right, r.bottom);
471 rightBottom += BPoint(1, 1);
472 rightBottom = ImageToView(rightBottom);
473 rightBottom -= BPoint(1, 1);
474 return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
475 }
476
477
478 void
ConstrainToImage(BPoint & point) const479 ShowImageView::ConstrainToImage(BPoint& point) const
480 {
481 point.ConstrainTo(fBitmap->Bounds());
482 }
483
484
485 void
ConstrainToImage(BRect & rect) const486 ShowImageView::ConstrainToImage(BRect& rect) const
487 {
488 rect = rect & fBitmap->Bounds();
489 }
490
491
492 void
SetShowCaption(bool show)493 ShowImageView::SetShowCaption(bool show)
494 {
495 if (fShowCaption != show) {
496 fShowCaption = show;
497 _UpdateCaption();
498 }
499 }
500
501
502 void
SetStretchToBounds(bool enable)503 ShowImageView::SetStretchToBounds(bool enable)
504 {
505 if (fStretchToBounds != enable) {
506 _SettingsSetBool("StretchToBounds", enable);
507 fStretchToBounds = enable;
508 if (enable || fZoom > 1.0)
509 FitToBounds();
510 }
511 }
512
513
514 void
SetHideIdlingCursor(bool hide)515 ShowImageView::SetHideIdlingCursor(bool hide)
516 {
517 fHideCursor = hide;
518 }
519
520
521 BBitmap*
Bitmap()522 ShowImageView::Bitmap()
523 {
524 return fBitmap;
525 }
526
527
528 void
SetScaleBilinear(bool enabled)529 ShowImageView::SetScaleBilinear(bool enabled)
530 {
531 if (fScaleBilinear != enabled) {
532 _SettingsSetBool("ScaleBilinear", enabled);
533 fScaleBilinear = enabled;
534 Invalidate();
535 }
536 }
537
538
539 void
AttachedToWindow()540 ShowImageView::AttachedToWindow()
541 {
542 FitToBounds();
543 FixupScrollBars();
544 }
545
546
547 void
FrameResized(float width,float height)548 ShowImageView::FrameResized(float width, float height)
549 {
550 FixupScrollBars();
551 }
552
553
554 float
_FitToBoundsZoom() const555 ShowImageView::_FitToBoundsZoom() const
556 {
557 if (fBitmap == NULL)
558 return 1.0f;
559
560 // the width/height of the bitmap (in pixels)
561 float bitmapWidth = fBitmap->Bounds().Width() + 1;
562 float bitmapHeight = fBitmap->Bounds().Height() + 1;
563
564 // the available width/height for layouting the bitmap (in pixels)
565 float width = Bounds().Width() + 1;
566 float height = Bounds().Height() + 1;
567
568 float zoom = width / bitmapWidth;
569
570 if (zoom * bitmapHeight <= height)
571 return zoom;
572
573 return height / bitmapHeight;
574 }
575
576
577 BRect
_AlignBitmap()578 ShowImageView::_AlignBitmap()
579 {
580 BRect rect(fBitmap->Bounds());
581
582 // the width/height of the bitmap (in pixels)
583 float bitmapWidth = rect.Width() + 1;
584 float bitmapHeight = rect.Height() + 1;
585
586 // the available width/height for layouting the bitmap (in pixels)
587 float width = Bounds().Width() + 1;
588 float height = Bounds().Height() + 1;
589
590 if (width == 0 || height == 0)
591 return rect;
592
593 // zoom image
594 rect.right = floorf(bitmapWidth * fZoom) - 1;
595 rect.bottom = floorf(bitmapHeight * fZoom) - 1;
596
597 // update the bitmap size after the zoom
598 bitmapWidth = rect.Width() + 1.0;
599 bitmapHeight = rect.Height() + 1.0;
600
601 // always align in the center if the bitmap is smaller than the window
602 if (width > bitmapWidth)
603 rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);
604
605 if (height > bitmapHeight)
606 rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));
607
608 return rect;
609 }
610
611
612 void
_DrawBackground(BRect border)613 ShowImageView::_DrawBackground(BRect border)
614 {
615 BRect bounds(Bounds());
616 // top
617 FillRect(BRect(0, 0, bounds.right, border.top - 1), B_SOLID_LOW);
618 // left
619 FillRect(BRect(0, border.top, border.left - 1, border.bottom), B_SOLID_LOW);
620 // right
621 FillRect(BRect(border.right + 1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
622 // bottom
623 FillRect(BRect(0, border.bottom + 1, bounds.right, bounds.bottom), B_SOLID_LOW);
624 }
625
626
627 void
_LayoutCaption(BFont & font,BPoint & pos,BRect & rect)628 ShowImageView::_LayoutCaption(BFont& font, BPoint& pos, BRect& rect)
629 {
630 font_height fontHeight;
631 float width, height;
632 BRect bounds(Bounds());
633 font = be_plain_font;
634 width = font.StringWidth(fCaption.String());
635 font.GetHeight(&fontHeight);
636 height = fontHeight.ascent + fontHeight.descent;
637 // center text horizontally
638 pos.x = (bounds.left + bounds.right - width) / 2;
639 // flush bottom
640 pos.y = bounds.bottom - fontHeight.descent - 7;
641
642 // background rectangle
643 rect.Set(0, 0, width + 4, height + 4);
644 rect.OffsetTo(pos);
645 rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
646 }
647
648
649 void
_DrawCaption()650 ShowImageView::_DrawCaption()
651 {
652 BFont font;
653 BPoint position;
654 BRect rect;
655 _LayoutCaption(font, position, rect);
656
657 PushState();
658
659 // draw background
660 SetDrawingMode(B_OP_ALPHA);
661 SetHighColor(255, 255, 255, 160);
662 FillRect(rect);
663
664 // draw text
665 SetDrawingMode(B_OP_OVER);
666 SetFont(&font);
667 SetLowColor(B_TRANSPARENT_COLOR);
668 SetHighColor(0, 0, 0);
669 DrawString(fCaption.String(), position);
670
671 PopState();
672 }
673
674
675 void
_UpdateCaption()676 ShowImageView::_UpdateCaption()
677 {
678 BFont font;
679 BPoint pos;
680 BRect rect;
681 _LayoutCaption(font, pos, rect);
682
683 // draw over portion of image where caption is located
684 BRegion clip(rect);
685 PushState();
686 ConstrainClippingRegion(&clip);
687 Draw(rect);
688 PopState();
689 }
690
691
692 void
_DrawImage(BRect rect)693 ShowImageView::_DrawImage(BRect rect)
694 {
695 // TODO: fix composing of fBitmap with other bitmaps
696 // with regard to alpha channel
697 if (!fDisplayBitmap)
698 fDisplayBitmap = fBitmap;
699
700 uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0;
701 DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options);
702 }
703
704
705 void
Draw(BRect updateRect)706 ShowImageView::Draw(BRect updateRect)
707 {
708 if (fBitmap == NULL)
709 return;
710
711 if (IsPrinting()) {
712 DrawBitmap(fBitmap);
713 return;
714 }
715
716 BRect rect = _AlignBitmap();
717 fBitmapLocationInView.x = floorf(rect.left);
718 fBitmapLocationInView.y = floorf(rect.top);
719
720 _DrawBackground(rect);
721 _DrawImage(rect);
722
723 if (fShowCaption)
724 _DrawCaption();
725
726 if (fHasSelection) {
727 if (fSelectionBitmap != NULL) {
728 BRect srcRect;
729 BRect dstRect;
730 _GetSelectionMergeRects(srcRect, dstRect);
731 dstRect = ImageToView(dstRect);
732 DrawBitmap(fSelectionBitmap, srcRect, dstRect);
733 }
734 fSelectionBox.Draw(this, updateRect);
735 }
736 }
737
738
739 BBitmap*
_CopySelection(uchar alpha,bool imageSize)740 ShowImageView::_CopySelection(uchar alpha, bool imageSize)
741 {
742 bool hasAlpha = alpha != 255;
743
744 if (!fHasSelection)
745 return NULL;
746
747 BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN);
748 if (!imageSize) {
749 // scale image to view size
750 rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
751 rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
752 }
753 BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
754 BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
755 : fBitmap->ColorSpace(), true);
756 if (bitmap == NULL || !bitmap->IsValid()) {
757 delete bitmap;
758 return NULL;
759 }
760
761 if (bitmap->Lock()) {
762 bitmap->AddChild(&view);
763 #ifdef __HAIKU__
764 // On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
765 // Don't know if it's better to fix it or not (stippi).
766 if (hasAlpha) {
767 view.SetHighColor(0, 0, 0, 0);
768 view.FillRect(view.Bounds());
769 view.SetDrawingMode(B_OP_ALPHA);
770 view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
771 view.SetHighColor(0, 0, 0, alpha);
772 }
773 if (fSelectionBitmap) {
774 view.DrawBitmap(fSelectionBitmap,
775 fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
776 } else
777 view.DrawBitmap(fBitmap, fCopyFromRect, rect);
778 #else
779 if (fSelectionBitmap) {
780 view.DrawBitmap(fSelectionBitmap,
781 fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
782 } else
783 view.DrawBitmap(fBitmap, fCopyFromRect, rect);
784 if (hasAlpha) {
785 view.SetDrawingMode(B_OP_SUBTRACT);
786 view.SetHighColor(0, 0, 0, 255 - alpha);
787 view.FillRect(rect, B_SOLID_HIGH);
788 }
789 #endif
790 view.Sync();
791 bitmap->RemoveChild(&view);
792 bitmap->Unlock();
793 }
794
795 return bitmap;
796 }
797
798
799 bool
_AddSupportedTypes(BMessage * msg,BBitmap * bitmap)800 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
801 {
802 BTranslatorRoster* roster = BTranslatorRoster::Default();
803 if (roster == NULL)
804 return false;
805
806 // add the current image mime first, will make it the preferred format on
807 // left mouse drag
808 msg->AddString("be:types", fMimeType);
809 msg->AddString("be:filetypes", fMimeType);
810 msg->AddString("be:type_descriptions", fFormatDescription);
811
812 bool foundOther = false;
813 bool foundCurrent = false;
814
815 int32 infoCount;
816 translator_info* info;
817 BBitmapStream stream(bitmap);
818 if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
819 for (int32 i = 0; i < infoCount; i++) {
820 const translation_format* formats;
821 int32 count;
822 roster->GetOutputFormats(info[i].translator, &formats, &count);
823 for (int32 j = 0; j < count; j++) {
824 if (fMimeType == formats[j].MIME)
825 foundCurrent = true;
826 else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
827 foundOther = true;
828 // needed to send data in message
829 msg->AddString("be:types", formats[j].MIME);
830 // needed to pass data via file
831 msg->AddString("be:filetypes", formats[j].MIME);
832 msg->AddString("be:type_descriptions", formats[j].name);
833 }
834 }
835 }
836 }
837 stream.DetachBitmap(&bitmap);
838
839 if (!foundCurrent) {
840 msg->RemoveData("be:types", 0);
841 msg->RemoveData("be:filetypes", 0);
842 msg->RemoveData("be:type_descriptions", 0);
843 }
844
845 return foundOther || foundCurrent;
846 }
847
848
849 void
_BeginDrag(BPoint sourcePoint)850 ShowImageView::_BeginDrag(BPoint sourcePoint)
851 {
852 BBitmap* bitmap = _CopySelection(128, false);
853 if (bitmap == NULL)
854 return;
855
856 SetMouseEventMask(B_POINTER_EVENTS);
857
858 // fill the drag message
859 BMessage drag(B_SIMPLE_DATA);
860 drag.AddInt32("be:actions", B_COPY_TARGET);
861 drag.AddString("be:clip_name", "Bitmap Clip");
862 // ShowImage specific fields
863 drag.AddPoint("be:_source_point", sourcePoint);
864 drag.AddRect("be:_frame", fSelectionBox.Bounds());
865 if (_AddSupportedTypes(&drag, bitmap)) {
866 // we also support "Passing Data via File" protocol
867 drag.AddString("be:types", B_FILE_MIME_TYPE);
868 // avoid flickering of dragged bitmap caused by drawing into the window
869 _AnimateSelection(false);
870 // only use a transparent bitmap on selections less than 400x400
871 // (taking into account zooming)
872 BRect selectionRect = fSelectionBox.Bounds();
873 if (selectionRect.Width() * fZoom < 400.0
874 && selectionRect.Height() * fZoom < 400.0) {
875 sourcePoint -= selectionRect.LeftTop();
876 sourcePoint.x *= fZoom;
877 sourcePoint.y *= fZoom;
878 // DragMessage takes ownership of bitmap
879 DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
880 bitmap = NULL;
881 } else {
882 delete bitmap;
883 // Offset and scale the rect
884 BRect rect(selectionRect);
885 rect = ImageToView(rect);
886 rect.InsetBy(-1, -1);
887 DragMessage(&drag, rect);
888 }
889 }
890 }
891
892
893 bool
_OutputFormatForType(BBitmap * bitmap,const char * type,translation_format * format)894 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type,
895 translation_format* format)
896 {
897 bool found = false;
898
899 BTranslatorRoster* roster = BTranslatorRoster::Default();
900 if (roster == NULL)
901 return false;
902
903 BBitmapStream stream(bitmap);
904
905 translator_info* outInfo;
906 int32 outNumInfo;
907 if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
908 for (int32 i = 0; i < outNumInfo; i++) {
909 const translation_format* formats;
910 int32 formatCount;
911 roster->GetOutputFormats(outInfo[i].translator, &formats,
912 &formatCount);
913 for (int32 j = 0; j < formatCount; j++) {
914 if (strcmp(formats[j].MIME, type) == 0) {
915 *format = formats[j];
916 found = true;
917 break;
918 }
919 }
920 }
921 }
922 stream.DetachBitmap(&bitmap);
923 return found;
924 }
925
926
927 #undef B_TRANSLATION_CONTEXT
928 #define B_TRANSLATION_CONTEXT "SaveToFile"
929
930
931 void
SaveToFile(BDirectory * dir,const char * name,BBitmap * bitmap,const translation_format * format)932 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
933 const translation_format* format)
934 {
935 if (bitmap == NULL) {
936 // If no bitmap is supplied, write out the whole image
937 bitmap = fBitmap;
938 }
939
940 BBitmapStream stream(bitmap);
941
942 bool loop = true;
943 while (loop) {
944 BTranslatorRoster* roster = BTranslatorRoster::Default();
945 if (!roster)
946 break;
947 // write data
948 BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
949 if (file.InitCheck() != B_OK)
950 break;
951 if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
952 break;
953 // set mime type
954 BNodeInfo info(&file);
955 if (info.InitCheck() == B_OK)
956 info.SetType(format->MIME);
957
958 loop = false;
959 // break out of loop gracefully (indicates no errors)
960 }
961 if (loop) {
962 // If loop terminated because of a break, there was an error
963 char buffer[512];
964 snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not "
965 "be written."), name);
966 BAlert* palert = new BAlert("", buffer, B_TRANSLATE("OK"));
967 palert->SetFlags(palert->Flags() | B_CLOSE_ON_ESCAPE);
968 palert->Go();
969 }
970
971 stream.DetachBitmap(&bitmap);
972 // Don't allow the bitmap to be deleted, this is
973 // especially important when using fBitmap as the bitmap
974 }
975
976
977 void
_SendInMessage(BMessage * msg,BBitmap * bitmap,translation_format * format)978 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap,
979 translation_format* format)
980 {
981 BMessage reply(B_MIME_DATA);
982 BBitmapStream stream(bitmap); // destructor deletes bitmap
983 BTranslatorRoster* roster = BTranslatorRoster::Default();
984 BMallocIO memStream;
985 if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
986 reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(),
987 memStream.BufferLength());
988 msg->SendReply(&reply);
989 }
990 }
991
992
993 void
_HandleDrop(BMessage * msg)994 ShowImageView::_HandleDrop(BMessage* msg)
995 {
996 entry_ref dirRef;
997 BString name, type;
998 bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
999 && msg->FindRef("directory", &dirRef) == B_OK
1000 && msg->FindString("name", &name) == B_OK;
1001
1002 bool sendInMessage = !saveToFile
1003 && msg->FindString("be:types", &type) == B_OK;
1004
1005 BBitmap* bitmap = _CopySelection();
1006 if (bitmap == NULL)
1007 return;
1008
1009 translation_format format;
1010 if (!_OutputFormatForType(bitmap, type.String(), &format)) {
1011 delete bitmap;
1012 return;
1013 }
1014
1015 if (saveToFile) {
1016 BDirectory dir(&dirRef);
1017 SaveToFile(&dir, name.String(), bitmap, &format);
1018 delete bitmap;
1019 } else if (sendInMessage) {
1020 _SendInMessage(msg, bitmap, &format);
1021 } else {
1022 delete bitmap;
1023 }
1024 }
1025
1026
1027 void
_ScrollBitmap(BPoint point)1028 ShowImageView::_ScrollBitmap(BPoint point)
1029 {
1030 point = ConvertToScreen(point);
1031 BPoint delta = fFirstPoint - point;
1032 fFirstPoint = point;
1033 _ScrollRestrictedBy(delta.x, delta.y);
1034 }
1035
1036
1037 void
_GetMergeRects(BBitmap * merge,BRect selection,BRect & srcRect,BRect & dstRect)1038 ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect,
1039 BRect& dstRect)
1040 {
1041 // Constrain dstRect to target image size and apply the same edge offsets
1042 // to the srcRect.
1043
1044 dstRect = selection;
1045
1046 BRect clippedDstRect(dstRect);
1047 ConstrainToImage(clippedDstRect);
1048
1049 srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN);
1050
1051 srcRect.left += clippedDstRect.left - dstRect.left;
1052 srcRect.top += clippedDstRect.top - dstRect.top;
1053 srcRect.right += clippedDstRect.right - dstRect.right;
1054 srcRect.bottom += clippedDstRect.bottom - dstRect.bottom;
1055
1056 dstRect = clippedDstRect;
1057 }
1058
1059
1060 void
_GetSelectionMergeRects(BRect & srcRect,BRect & dstRect)1061 ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
1062 {
1063 _GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
1064 }
1065
1066
1067 void
_MergeWithBitmap(BBitmap * merge,BRect selection)1068 ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection)
1069 {
1070 BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1071 BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(),
1072 fBitmap->ColorSpace(), true);
1073 if (bitmap == NULL || !bitmap->IsValid()) {
1074 delete bitmap;
1075 return;
1076 }
1077
1078 if (bitmap->Lock()) {
1079 bitmap->AddChild(&view);
1080 view.DrawBitmap(fBitmap, fBitmap->Bounds());
1081 BRect srcRect;
1082 BRect dstRect;
1083 _GetMergeRects(merge, selection, srcRect, dstRect);
1084 view.DrawBitmap(merge, srcRect, dstRect);
1085
1086 view.Sync();
1087 bitmap->RemoveChild(&view);
1088 bitmap->Unlock();
1089
1090 _DeleteBitmap();
1091 fBitmap = bitmap;
1092
1093 _SendMessageToWindow(MSG_MODIFIED);
1094 } else
1095 delete bitmap;
1096 }
1097
1098
1099 void
MouseDown(BPoint position)1100 ShowImageView::MouseDown(BPoint position)
1101 {
1102 MakeFocus(true);
1103
1104 BPoint point = ViewToImage(position);
1105 int32 clickCount = 0;
1106 uint32 buttons = 0;
1107 if (Window() != NULL && Window()->CurrentMessage() != NULL) {
1108 clickCount = Window()->CurrentMessage()->FindInt32("clicks");
1109 buttons = Window()->CurrentMessage()->FindInt32("buttons");
1110 }
1111
1112 // Using clickCount >= 2 and the modulo 2 accounts for quickly repeated
1113 // double-clicks
1114 if (buttons == B_PRIMARY_MOUSE_BUTTON && clickCount >= 2 &&
1115 clickCount % 2 == 0) {
1116 Window()->PostMessage(MSG_FULL_SCREEN);
1117 return;
1118 }
1119
1120 if (fHasSelection && fSelectionBox.Bounds().Contains(point)
1121 && (buttons
1122 & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
1123 if (!fSelectionBitmap)
1124 fSelectionBitmap = _CopySelection();
1125
1126 _BeginDrag(point);
1127 } else if (buttons == B_PRIMARY_MOUSE_BUTTON
1128 && (fSelectionMode
1129 || (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) {
1130 // begin new selection
1131 _SetHasSelection(true);
1132 fCreatingSelection = true;
1133 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1134 ConstrainToImage(point);
1135 fFirstPoint = point;
1136 fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1137 fSelectionBox.SetBounds(this, fCopyFromRect);
1138 Invalidate();
1139 } else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1140 _ShowPopUpMenu(ConvertToScreen(position));
1141 } else if (buttons == B_PRIMARY_MOUSE_BUTTON
1142 || buttons == B_TERTIARY_MOUSE_BUTTON) {
1143 // move image in window
1144 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1145 fScrollingBitmap = true;
1146 fFirstPoint = ConvertToScreen(position);
1147 be_app->SetCursor(fGrabCursor);
1148 }
1149 }
1150
1151
1152 void
_UpdateSelectionRect(BPoint point,bool final)1153 ShowImageView::_UpdateSelectionRect(BPoint point, bool final)
1154 {
1155 BRect oldSelection = fCopyFromRect;
1156 point = ViewToImage(point);
1157 ConstrainToImage(point);
1158 fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
1159 fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
1160 fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
1161 fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
1162 fSelectionBox.SetBounds(this, fCopyFromRect);
1163
1164 if (final) {
1165 // selection must be at least 2 pixels wide or 2 pixels tall
1166 if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
1167 _SetHasSelection(false);
1168 } else
1169 _UpdateStatusText();
1170
1171 if (oldSelection != fCopyFromRect || !fHasSelection) {
1172 BRect updateRect;
1173 updateRect = oldSelection | fCopyFromRect;
1174 updateRect = ImageToView(updateRect);
1175 updateRect.InsetBy(-1, -1);
1176 Invalidate(updateRect);
1177 }
1178 }
1179
1180
1181 void
MouseMoved(BPoint point,uint32 state,const BMessage * message)1182 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message)
1183 {
1184 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1185 if (fHideCursor) {
1186 // Show toolbar when mouse hits top 15 pixels, hide otherwise
1187 _ShowToolBarIfEnabled(ConvertToScreen(point).y <= 15);
1188 }
1189 if (fCreatingSelection)
1190 _UpdateSelectionRect(point, false);
1191 else if (fScrollingBitmap)
1192 _ScrollBitmap(point);
1193 }
1194
1195
1196 void
MouseUp(BPoint point)1197 ShowImageView::MouseUp(BPoint point)
1198 {
1199 if (fCreatingSelection) {
1200 _UpdateSelectionRect(point, true);
1201 fCreatingSelection = false;
1202 } else if (fScrollingBitmap) {
1203 _ScrollBitmap(point);
1204 fScrollingBitmap = false;
1205 be_app->SetCursor(fDefaultCursor);
1206 }
1207 _AnimateSelection(true);
1208 }
1209
1210
1211 float
_LimitToRange(float v,orientation o,bool absolute)1212 ShowImageView::_LimitToRange(float v, orientation o, bool absolute)
1213 {
1214 BScrollBar* psb = ScrollBar(o);
1215 if (psb) {
1216 float min, max, pos;
1217 pos = v;
1218 if (!absolute)
1219 pos += psb->Value();
1220
1221 psb->GetRange(&min, &max);
1222 if (pos < min)
1223 pos = min;
1224 else if (pos > max)
1225 pos = max;
1226
1227 v = pos;
1228 if (!absolute)
1229 v -= psb->Value();
1230 }
1231 return v;
1232 }
1233
1234
1235 void
_ScrollRestricted(float x,float y,bool absolute)1236 ShowImageView::_ScrollRestricted(float x, float y, bool absolute)
1237 {
1238 if (x != 0)
1239 x = _LimitToRange(x, B_HORIZONTAL, absolute);
1240
1241 if (y != 0)
1242 y = _LimitToRange(y, B_VERTICAL, absolute);
1243
1244 // We invalidate before we scroll to avoid the caption messing up the
1245 // image, and to prevent it from flickering
1246 if (fShowCaption)
1247 Invalidate();
1248
1249 ScrollBy(x, y);
1250 }
1251
1252
1253 // XXX method is not unused
1254 void
_ScrollRestrictedTo(float x,float y)1255 ShowImageView::_ScrollRestrictedTo(float x, float y)
1256 {
1257 _ScrollRestricted(x, y, true);
1258 }
1259
1260
1261 void
_ScrollRestrictedBy(float x,float y)1262 ShowImageView::_ScrollRestrictedBy(float x, float y)
1263 {
1264 _ScrollRestricted(x, y, false);
1265 }
1266
1267
1268 void
KeyDown(const char * bytes,int32 numBytes)1269 ShowImageView::KeyDown(const char* bytes, int32 numBytes)
1270 {
1271 if (numBytes != 1) {
1272 BView::KeyDown(bytes, numBytes);
1273 return;
1274 }
1275
1276 bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0;
1277
1278 switch (*bytes) {
1279 case B_DOWN_ARROW:
1280 if (shiftKeyDown)
1281 _ScrollRestrictedBy(0, 10);
1282 else
1283 _SendMessageToWindow(MSG_FILE_NEXT);
1284 break;
1285 case B_RIGHT_ARROW:
1286 if (shiftKeyDown)
1287 _ScrollRestrictedBy(10, 0);
1288 else
1289 _SendMessageToWindow(MSG_FILE_NEXT);
1290 break;
1291 case B_UP_ARROW:
1292 if (shiftKeyDown)
1293 _ScrollRestrictedBy(0, -10);
1294 else
1295 _SendMessageToWindow(MSG_FILE_PREV);
1296 break;
1297 case B_LEFT_ARROW:
1298 if (shiftKeyDown)
1299 _ScrollRestrictedBy(-10, 0);
1300 else
1301 _SendMessageToWindow(MSG_FILE_PREV);
1302 break;
1303 case B_BACKSPACE:
1304 _SendMessageToWindow(MSG_FILE_PREV);
1305 break;
1306 case B_HOME:
1307 break;
1308 case B_END:
1309 break;
1310 case B_SPACE:
1311 _ToggleSlideShow();
1312 break;
1313 case B_ESCAPE:
1314 // stop slide show
1315 _StopSlideShow();
1316 _ExitFullScreen();
1317
1318 ClearSelection();
1319 break;
1320 case B_DELETE:
1321 if (fHasSelection)
1322 ClearSelection();
1323 else
1324 _SendMessageToWindow(kMsgDeleteCurrentFile);
1325 break;
1326 case '0':
1327 FitToBounds();
1328 break;
1329 case '1':
1330 SetZoom(1.0f);
1331 break;
1332 case '+':
1333 case '=':
1334 ZoomIn();
1335 break;
1336 case '-':
1337 ZoomOut();
1338 break;
1339 case '[':
1340 Rotate(270);
1341 break;
1342 case ']':
1343 Rotate(90);
1344 break;
1345 }
1346 }
1347
1348
1349 void
_MouseWheelChanged(BMessage * message)1350 ShowImageView::_MouseWheelChanged(BMessage* message)
1351 {
1352 // The BeOS driver does not currently support
1353 // X wheel scrolling, therefore, deltaX is zero.
1354 // |deltaY| is the number of notches scrolled up or down.
1355 // When the wheel is scrolled down (towards the user) deltaY > 0
1356 // When the wheel is scrolled up (away from the user) deltaY < 0
1357 const float kscrollBy = 40;
1358 float deltaY;
1359 float deltaX;
1360 float x = 0;
1361 float y = 0;
1362
1363 if (message->FindFloat("be:wheel_delta_x", &deltaX) == B_OK)
1364 x = deltaX * kscrollBy;
1365
1366 if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK)
1367 y = deltaY * kscrollBy;
1368
1369 if ((modifiers() & B_SHIFT_KEY) != 0) {
1370 // scroll up and down
1371 _ScrollRestrictedBy(x, y);
1372 } else if ((modifiers() & B_CONTROL_KEY) != 0) {
1373 // scroll left and right
1374 _ScrollRestrictedBy(y, x);
1375 } else {
1376 // zoom at location
1377 BPoint where;
1378 uint32 buttons;
1379 GetMouse(&where, &buttons);
1380
1381 if (fStickyZoomCountDown <= 0) {
1382 if (deltaY < 0)
1383 ZoomIn(where);
1384 else if (deltaY > 0)
1385 ZoomOut(where);
1386
1387 if (fZoom == 1.0)
1388 fStickyZoomCountDown = STICKY_ZOOM_DELAY_TIME;
1389 }
1390
1391 }
1392 }
1393
1394
1395 void
_ShowPopUpMenu(BPoint screen)1396 ShowImageView::_ShowPopUpMenu(BPoint screen)
1397 {
1398 if (!fShowingPopUpMenu) {
1399 PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
1400
1401 ShowImageWindow* window = dynamic_cast<ShowImageWindow*>(Window());
1402 if (window != NULL)
1403 window->BuildContextMenu(menu);
1404
1405 menu->Go(screen, true, true, true);
1406 fShowingPopUpMenu = true;
1407 }
1408 }
1409
1410
1411 void
_SettingsSetBool(const char * name,bool value)1412 ShowImageView::_SettingsSetBool(const char* name, bool value)
1413 {
1414 ShowImageSettings* settings;
1415 settings = my_app->Settings();
1416 if (settings->Lock()) {
1417 settings->SetBool(name, value);
1418 settings->Unlock();
1419 }
1420 }
1421
1422
1423 void
MessageReceived(BMessage * message)1424 ShowImageView::MessageReceived(BMessage* message)
1425 {
1426 switch (message->what) {
1427 case B_COPY_TARGET:
1428 _HandleDrop(message);
1429 break;
1430
1431 case B_MOUSE_WHEEL_CHANGED:
1432 _MouseWheelChanged(message);
1433 break;
1434
1435 case kMsgPopUpMenuClosed:
1436 fShowingPopUpMenu = false;
1437 break;
1438
1439 default:
1440 BView::MessageReceived(message);
1441 break;
1442 }
1443 }
1444
1445
1446 void
FixupScrollBar(orientation o,float bitmapLength,float viewLength)1447 ShowImageView::FixupScrollBar(orientation o, float bitmapLength,
1448 float viewLength)
1449 {
1450 float prop, range;
1451 BScrollBar* psb;
1452
1453 psb = ScrollBar(o);
1454 if (psb) {
1455 range = bitmapLength - viewLength;
1456 if (range < 0.0)
1457 range = 0.0;
1458
1459 prop = viewLength / bitmapLength;
1460 if (prop > 1.0)
1461 prop = 1.0;
1462
1463 psb->SetRange(0, range);
1464 psb->SetProportion(prop);
1465 psb->SetSteps(10, 100);
1466 }
1467 }
1468
1469
1470 void
FixupScrollBars()1471 ShowImageView::FixupScrollBars()
1472 {
1473 BRect viewRect = Bounds();
1474 BRect bitmapRect;
1475 if (fBitmap != NULL) {
1476 bitmapRect = _AlignBitmap();
1477 bitmapRect.OffsetTo(0, 0);
1478 }
1479
1480 FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width());
1481 FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height());
1482 }
1483
1484
1485 void
SetSelectionMode(bool selectionMode)1486 ShowImageView::SetSelectionMode(bool selectionMode)
1487 {
1488 // The mode only has an effect in MouseDown()
1489 fSelectionMode = selectionMode;
1490 }
1491
1492
1493 void
SelectAll()1494 ShowImageView::SelectAll()
1495 {
1496 fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
1497 fBitmap->Bounds().Height());
1498 fSelectionBox.SetBounds(this, fCopyFromRect);
1499 _SetHasSelection(true);
1500 Invalidate();
1501 }
1502
1503
1504 void
ClearSelection()1505 ShowImageView::ClearSelection()
1506 {
1507 if (!fHasSelection)
1508 return;
1509
1510 _SetHasSelection(false);
1511 Invalidate();
1512 }
1513
1514
1515 void
_SetHasSelection(bool hasSelection)1516 ShowImageView::_SetHasSelection(bool hasSelection)
1517 {
1518 _DeleteSelectionBitmap();
1519 fHasSelection = hasSelection;
1520
1521 _UpdateStatusText();
1522
1523 BMessage msg(MSG_SELECTION);
1524 msg.AddBool("has_selection", fHasSelection);
1525 _SendMessageToWindow(&msg);
1526 }
1527
1528
1529 void
CopySelectionToClipboard()1530 ShowImageView::CopySelectionToClipboard()
1531 {
1532 if (!fHasSelection || !be_clipboard->Lock())
1533 return;
1534
1535 be_clipboard->Clear();
1536
1537 BMessage* data = be_clipboard->Data();
1538 if (data != NULL) {
1539 BBitmap* bitmap = _CopySelection();
1540 if (bitmap != NULL) {
1541 BMessage bitmapArchive;
1542 bitmap->Archive(&bitmapArchive);
1543 // NOTE: Possibly "image/x-be-bitmap" is more correct.
1544 // This works with WonderBrush, though, which in turn had been
1545 // tested with other apps.
1546 data->AddMessage("image/bitmap", &bitmapArchive);
1547 data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop());
1548
1549 delete bitmap;
1550
1551 be_clipboard->Commit();
1552 }
1553 }
1554 be_clipboard->Unlock();
1555 }
1556
1557
1558 void
SetZoom(float zoom,BPoint where)1559 ShowImageView::SetZoom(float zoom, BPoint where)
1560 {
1561 float fitToBoundsZoom = _FitToBoundsZoom();
1562 if (zoom > 32)
1563 zoom = 32;
1564 if (zoom < fitToBoundsZoom / 2 && zoom < 0.25)
1565 zoom = min_c(fitToBoundsZoom / 2, 0.25);
1566
1567 if (zoom == fZoom) {
1568 // window size might have changed
1569 FixupScrollBars();
1570 return;
1571 }
1572
1573 // Invalidate before scrolling, as that prevents the app_server
1574 // to do the scrolling server side
1575 Invalidate();
1576
1577 // zoom to center if not otherwise specified
1578 BPoint offset;
1579 if (where.x == -1) {
1580 where.Set(Bounds().Width() / 2, Bounds().Height() / 2);
1581 offset = where;
1582 where += Bounds().LeftTop();
1583 } else
1584 offset = where - Bounds().LeftTop();
1585
1586 float oldZoom = fZoom;
1587 fZoom = zoom;
1588
1589 FixupScrollBars();
1590
1591 if (fBitmap != NULL) {
1592 offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x;
1593 offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y;
1594 ScrollTo(offset);
1595 }
1596
1597 BMessage message(MSG_UPDATE_STATUS_ZOOM);
1598 message.AddFloat("zoom", fZoom);
1599 _SendMessageToWindow(&message);
1600 }
1601
1602
1603 void
ZoomIn(BPoint where)1604 ShowImageView::ZoomIn(BPoint where)
1605 {
1606 // snap zoom to "fit to bounds", and "original size"
1607 float zoom = fZoom * 1.2;
1608 float zoomSnap = fZoom * 1.25;
1609 float fitToBoundsZoom = _FitToBoundsZoom();
1610 if (fZoom < fitToBoundsZoom - 0.001 && zoomSnap > fitToBoundsZoom)
1611 zoom = fitToBoundsZoom;
1612 if (fZoom < 1.0 && zoomSnap > 1.0)
1613 zoom = 1.0;
1614
1615 SetZoom(zoom, where);
1616 }
1617
1618
1619 void
ZoomOut(BPoint where)1620 ShowImageView::ZoomOut(BPoint where)
1621 {
1622 // snap zoom to "fit to bounds", and "original size"
1623 float zoom = fZoom / 1.2;
1624 float zoomSnap = fZoom / 1.25;
1625 float fitToBoundsZoom = _FitToBoundsZoom();
1626 if (fZoom > fitToBoundsZoom + 0.001 && zoomSnap < fitToBoundsZoom)
1627 zoom = fitToBoundsZoom;
1628 if (fZoom > 1.0 && zoomSnap < 1.0)
1629 zoom = 1.0;
1630
1631 SetZoom(zoom, where);
1632 }
1633
1634
1635 /*! Fits the image to the view bounds.
1636 */
1637 void
FitToBounds()1638 ShowImageView::FitToBounds()
1639 {
1640 if (fBitmap == NULL)
1641 return;
1642
1643 float fitToBoundsZoom = _FitToBoundsZoom();
1644 if ((!fStretchToBounds && fitToBoundsZoom > 1.0f) || fForceOriginalSize)
1645 SetZoom(1.0f);
1646 else
1647 SetZoom(fitToBoundsZoom);
1648
1649 FixupScrollBars();
1650 }
1651
1652
1653 void
_DoImageOperation(ImageProcessor::operation op,bool quiet)1654 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet)
1655 {
1656 BMessenger msgr;
1657 ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
1658 imageProcessor.Start(false);
1659 BBitmap* bm = imageProcessor.DetachBitmap();
1660 if (bm == NULL) {
1661 // operation failed
1662 return;
1663 }
1664
1665 // update orientation state
1666 if (op != ImageProcessor::kInvert) {
1667 // Note: If one of these fails, check its definition in class ImageProcessor.
1668 // ASSERT(ImageProcessor::kRotateClockwise <
1669 // ImageProcessor::kNumberOfAffineTransformations);
1670 // ASSERT(ImageProcessor::kRotateCounterClockwise <
1671 // ImageProcessor::kNumberOfAffineTransformations);
1672 // ASSERT(ImageProcessor::kFlipLeftToRight <
1673 // ImageProcessor::kNumberOfAffineTransformations);
1674 // ASSERT(ImageProcessor::kFlipTopToBottom <
1675 // ImageProcessor::kNumberOfAffineTransformations);
1676 fImageOrientation = fTransformation[op][fImageOrientation];
1677 }
1678
1679 if (!quiet) {
1680 // write orientation state
1681 BNode node(&fCurrentRef);
1682 int32 orientation = fImageOrientation;
1683 if (orientation != k0) {
1684 node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
1685 &orientation, sizeof(orientation));
1686 } else
1687 node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
1688 }
1689
1690 // set new bitmap
1691 _DeleteBitmap();
1692 fBitmap = bm;
1693
1694 if (fBitmap->ColorSpace() == B_RGBA32)
1695 fDisplayBitmap = compose_checker_background(fBitmap);
1696
1697 if (!quiet) {
1698 // remove selection
1699 _SetHasSelection(false);
1700 _Notify();
1701 }
1702 }
1703
1704
1705 //! Image operation initiated by user
1706 void
_UserDoImageOperation(ImageProcessor::operation op,bool quiet)1707 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
1708 {
1709 _DoImageOperation(op, quiet);
1710 }
1711
1712
1713 void
Rotate(int degree)1714 ShowImageView::Rotate(int degree)
1715 {
1716 _UserDoImageOperation(degree == 90 ? ImageProcessor::kRotateClockwise
1717 : ImageProcessor::kRotateCounterClockwise);
1718
1719 FitToBounds();
1720 }
1721
1722
1723 void
Flip(bool vertical)1724 ShowImageView::Flip(bool vertical)
1725 {
1726 if (vertical)
1727 _UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
1728 else
1729 _UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
1730 }
1731
1732
1733 void
ResizeImage(int w,int h)1734 ShowImageView::ResizeImage(int w, int h)
1735 {
1736 if (fBitmap == NULL || w < 1 || h < 1)
1737 return;
1738
1739 Scaler scaler(fBitmap, BRect(0, 0, w - 1, h - 1), BMessenger(), 0, false);
1740 scaler.Start(false);
1741 BBitmap* scaled = scaler.DetachBitmap();
1742 if (scaled == NULL) {
1743 // operation failed
1744 return;
1745 }
1746
1747 // remove selection
1748 _SetHasSelection(false);
1749 _DeleteBitmap();
1750 fBitmap = scaled;
1751
1752 _SendMessageToWindow(MSG_MODIFIED);
1753
1754 _Notify();
1755 }
1756
1757
1758 void
_SetIcon(bool clear,icon_size which)1759 ShowImageView::_SetIcon(bool clear, icon_size which)
1760 {
1761 const int32 size = be_control_look->ComposeIconSize(which).IntegerWidth() + 1;
1762
1763 BRect rect(fBitmap->Bounds());
1764 float s;
1765 s = size / (rect.Width() + 1.0);
1766
1767 if (s * (rect.Height() + 1.0) <= size) {
1768 rect.right = size - 1;
1769 rect.bottom = static_cast<int>(s * (rect.Height() + 1.0)) - 1;
1770 // center vertically
1771 rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
1772 } else {
1773 s = size / (rect.Height() + 1.0);
1774 rect.right = static_cast<int>(s * (rect.Width() + 1.0)) - 1;
1775 rect.bottom = size - 1;
1776 // center horizontally
1777 rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
1778 }
1779
1780 // scale bitmap to thumbnail size
1781 BMessenger msgr;
1782 Scaler scaler(fBitmap, rect, msgr, 0, true);
1783 BBitmap* thumbnail = scaler.GetBitmap();
1784 scaler.Start(false);
1785 ASSERT(thumbnail->ColorSpace() == B_CMAP8);
1786 // create icon from thumbnail
1787 BBitmap icon(BRect(0, 0, size - 1, size - 1), B_CMAP8);
1788 memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
1789 BScreen screen;
1790 const uchar* src = (uchar*)thumbnail->Bits();
1791 uchar* dest = (uchar*)icon.Bits();
1792 const int32 srcBPR = thumbnail->BytesPerRow();
1793 const int32 destBPR = icon.BytesPerRow();
1794 const int32 deltaX = (int32)rect.left;
1795 const int32 deltaY = (int32)rect.top;
1796
1797 for (int32 y = 0; y <= rect.IntegerHeight(); y++) {
1798 for (int32 x = 0; x <= rect.IntegerWidth(); x++) {
1799 const uchar* s = src + y * srcBPR + x;
1800 uchar* d = dest + (y + deltaY) * destBPR + (x + deltaX);
1801 *d = *s;
1802 }
1803 }
1804
1805 // set icon
1806 BNode node(&fCurrentRef);
1807 BNodeInfo info(&node);
1808 info.SetIcon(clear ? NULL : &icon, which);
1809 }
1810
1811
1812 void
SetIcon(bool clear)1813 ShowImageView::SetIcon(bool clear)
1814 {
1815 _SetIcon(clear, B_MINI_ICON);
1816 _SetIcon(clear, B_LARGE_ICON);
1817 }
1818
1819
1820 void
_ToggleSlideShow()1821 ShowImageView::_ToggleSlideShow()
1822 {
1823 _SendMessageToWindow(MSG_SLIDE_SHOW);
1824 }
1825
1826
1827 void
_StopSlideShow()1828 ShowImageView::_StopSlideShow()
1829 {
1830 _SendMessageToWindow(kMsgStopSlideShow);
1831 }
1832
1833
1834 void
_ExitFullScreen()1835 ShowImageView::_ExitFullScreen()
1836 {
1837 be_app->ShowCursor();
1838 _SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
1839 }
1840
1841
1842 void
_ShowToolBarIfEnabled(bool show)1843 ShowImageView::_ShowToolBarIfEnabled(bool show)
1844 {
1845 BMessage message(kShowToolBarIfEnabled);
1846 message.AddBool("show", show);
1847 Window()->PostMessage(&message);
1848 }
1849
1850
1851 void
WindowActivated(bool active)1852 ShowImageView::WindowActivated(bool active)
1853 {
1854 fIsActiveWin = active;
1855 fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1856 }
1857
1858