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.
8 * Fernando Francisco de Oliveira
14 * Stephan Aßmus <superstippi@gmx.de>
15 * Axel Dörfler, axeld@pinc-software.de
19 #include "ShowImageView.h"
26 #include <Application.h>
28 #include <BitmapStream.h>
30 #include <Clipboard.h>
33 #include <Directory.h>
42 #include <PopUpMenu.h>
47 #include <ScrollBar.h>
48 #include <StopWatch.h>
49 #include <SupportDefs.h>
50 #include <TranslatorRoster.h>
52 #include <tracker_private.h>
54 #include "ImageCache.h"
55 #include "ShowImageApp.h"
56 #include "ShowImageWindow.h"
62 class PopUpMenu
: public BPopUpMenu
{
64 PopUpMenu(const char* name
, BMessenger target
);
72 // the delay time for hiding the cursor in 1/10 seconds (the pulse rate)
73 #define HIDE_CURSOR_DELAY_TIME 20
74 #define STICKY_ZOOM_DELAY_TIME 5
75 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
78 const rgb_color kBorderColor
= { 0, 0, 0, 255 };
80 enum ShowImageView::image_orientation
81 ShowImageView::fTransformation
[ImageProcessor::kNumberOfAffineTransformations
]
82 [kNumberOfOrientations
] = {
84 {k90
, k180
, k270
, k0
, k270V
, k0V
, k90V
, k0H
},
86 {k270
, k0
, k90
, k180
, k90V
, k0H
, k270V
, k0V
},
88 {k0H
, k270V
, k0V
, k90V
, k180
, k270
, k0
, k90
},
90 {k0V
, k90V
, k0H
, k270V
, k0
, k90
, k180
, k270
}
93 const rgb_color kAlphaLow
= (rgb_color
) { 0xbb, 0xbb, 0xbb, 0xff };
94 const rgb_color kAlphaHigh
= (rgb_color
) { 0xe0, 0xe0, 0xe0, 0xff };
96 const uint32 kMsgPopUpMenuClosed
= 'pmcl';
100 blend_colors(uint8
* d
, uint8 r
, uint8 g
, uint8 b
, uint8 a
)
102 d
[0] = ((b
- d
[0]) * a
+ (d
[0] << 8)) >> 8;
103 d
[1] = ((g
- d
[1]) * a
+ (d
[1] << 8)) >> 8;
104 d
[2] = ((r
- d
[2]) * a
+ (d
[2] << 8)) >> 8;
109 compose_checker_background(const BBitmap
* bitmap
)
111 BBitmap
* result
= new (nothrow
) BBitmap(bitmap
);
112 if (result
&& !result
->IsValid()) {
119 uint8
* bits
= (uint8
*)result
->Bits();
120 uint32 bpr
= result
->BytesPerRow();
121 uint32 width
= result
->Bounds().IntegerWidth() + 1;
122 uint32 height
= result
->Bounds().IntegerHeight() + 1;
124 for (uint32 i
= 0; i
< height
; i
++) {
126 for (uint32 x
= 0; x
< width
; x
++) {
133 blend_colors(p
, kAlphaLow
.red
, kAlphaLow
.green
, kAlphaLow
.blue
, alpha
);
135 blend_colors(p
, kAlphaHigh
.red
, kAlphaHigh
.green
, kAlphaHigh
.blue
, alpha
);
139 blend_colors(p
, kAlphaHigh
.red
, kAlphaHigh
.green
, kAlphaHigh
.blue
, alpha
);
141 blend_colors(p
, kAlphaLow
.red
, kAlphaLow
.green
, kAlphaLow
.blue
, alpha
);
155 PopUpMenu::PopUpMenu(const char* name
, BMessenger target
)
157 BPopUpMenu(name
, false, false),
160 SetAsyncAutoDestruct(true);
164 PopUpMenu::~PopUpMenu()
166 fTarget
.SendMessage(kMsgPopUpMenuClosed
);
173 ShowImageView::ShowImageView(BRect rect
, const char* name
, uint32 resizingMode
,
176 BView(rect
, name
, resizingMode
, flags
),
179 fDisplayBitmap(NULL
),
180 fSelectionBitmap(NULL
),
184 fScaleBilinear(true),
186 fBitmapLocationInView(0.0, 0.0),
188 fStretchToBounds(false),
189 fForceOriginalSize(false),
191 fScrollingBitmap(false),
192 fCreatingSelection(false),
193 fFirstPoint(0.0, 0.0),
194 fSelectionMode(false),
195 fAnimateSelection(true),
196 fHasSelection(false),
198 fShowingPopUpMenu(false),
199 fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME
),
200 fStickyZoomCountDown(0),
202 fDefaultCursor(NULL
),
205 ShowImageSettings
* settings
= my_app
->Settings();
206 if (settings
->Lock()) {
207 fStretchToBounds
= settings
->GetBool("StretchToBounds",
209 fScaleBilinear
= settings
->GetBool("ScaleBilinear", fScaleBilinear
);
213 fDefaultCursor
= new BCursor(B_CURSOR_ID_SYSTEM_DEFAULT
);
214 fGrabCursor
= new BCursor(B_CURSOR_ID_GRABBING
);
216 SetViewColor(B_TRANSPARENT_COLOR
);
217 SetHighColor(kBorderColor
);
218 SetLowColor(0, 0, 0);
222 ShowImageView::~ShowImageView()
226 delete fDefaultCursor
;
232 ShowImageView::_AnimateSelection(bool enabled
)
234 fAnimateSelection
= enabled
;
239 ShowImageView::Pulse()
241 // animate marching ants
242 if (fHasSelection
&& fAnimateSelection
&& fIsActiveWin
) {
243 fSelectionBox
.Animate();
244 fSelectionBox
.Draw(this, Bounds());
247 if (fHideCursor
&& !fHasSelection
&& !fShowingPopUpMenu
&& fIsActiveWin
) {
248 if (fHideCursorCountDown
== 0) {
249 // Go negative so this isn't triggered again
250 fHideCursorCountDown
--;
254 GetMouse(&mousePos
, &buttons
, false);
255 if (Bounds().Contains(mousePos
))
256 be_app
->ObscureCursor();
257 } else if (fHideCursorCountDown
> 0)
258 fHideCursorCountDown
--;
261 if (fStickyZoomCountDown
> 0)
262 fStickyZoomCountDown
--;
268 ShowImageView::_SendMessageToWindow(BMessage
* message
)
270 BMessenger
target(Window());
271 target
.SendMessage(message
);
276 ShowImageView::_SendMessageToWindow(uint32 code
)
278 BMessage
message(code
);
279 _SendMessageToWindow(&message
);
283 //! send message to parent about new image
285 ShowImageView::_Notify()
287 BMessage
msg(MSG_UPDATE_STATUS
);
289 msg
.AddInt32("width", fBitmap
->Bounds().IntegerWidth() + 1);
290 msg
.AddInt32("height", fBitmap
->Bounds().IntegerHeight() + 1);
292 msg
.AddInt32("colors", fBitmap
->ColorSpace());
293 _SendMessageToWindow(&msg
);
301 ShowImageView::_UpdateStatusText()
303 BMessage
msg(MSG_UPDATE_STATUS_TEXT
);
307 snprintf(size
, sizeof(size
), "(%.0fx%.0f)",
308 fSelectionBox
.Bounds().Width() + 1.0,
309 fSelectionBox
.Bounds().Height() + 1.0);
311 msg
.AddString("status", size
);
314 _SendMessageToWindow(&msg
);
319 ShowImageView::_DeleteBitmap()
321 _DeleteSelectionBitmap();
323 if (fDisplayBitmap
!= fBitmap
)
324 delete fDisplayBitmap
;
325 fDisplayBitmap
= NULL
;
327 if (fBitmapOwner
!= NULL
)
328 fBitmapOwner
->ReleaseReference();
338 ShowImageView::_DeleteSelectionBitmap()
340 delete fSelectionBitmap
;
341 fSelectionBitmap
= NULL
;
346 ShowImageView::SetImage(const BMessage
* message
)
350 if (message
->FindPointer("bitmap", (void**)&bitmap
) != B_OK
351 || message
->FindRef("ref", &ref
) != B_OK
|| bitmap
== NULL
)
354 BitmapOwner
* bitmapOwner
;
355 message
->FindPointer("bitmapOwner", (void**)&bitmapOwner
);
357 status_t status
= SetImage(&ref
, bitmap
, bitmapOwner
);
358 if (status
== B_OK
) {
359 fFormatDescription
= message
->FindString("type");
360 fMimeType
= message
->FindString("mime");
368 ShowImageView::SetImage(const entry_ref
* ref
, BBitmap
* bitmap
,
369 BitmapOwner
* bitmapOwner
)
371 // Delete the old one, and clear everything
372 _SetHasSelection(false);
373 fCreatingSelection
= false;
377 fBitmapOwner
= bitmapOwner
;
379 fCurrentRef
.device
= -1;
383 if (fBitmap
!= NULL
) {
384 // prepare the display bitmap
385 if (fBitmap
->ColorSpace() == B_RGBA32
)
386 fDisplayBitmap
= compose_checker_background(fBitmap
);
387 if (fDisplayBitmap
== NULL
)
388 fDisplayBitmap
= fBitmap
;
392 // restore orientation
394 fImageOrientation
= k0
;
395 if (node
.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE
, B_INT32_TYPE
, 0,
396 &orientation
, sizeof(orientation
)) == sizeof(orientation
)) {
398 switch (orientation
) {
402 _DoImageOperation(ImageProcessor::kRotateClockwise
, true);
405 _DoImageOperation(ImageProcessor::kRotateClockwise
, true);
406 _DoImageOperation(ImageProcessor::kRotateClockwise
, true);
409 _DoImageOperation(ImageProcessor::kRotateCounterClockwise
, true);
412 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom
, true);
415 _DoImageOperation(ImageProcessor::kRotateClockwise
, true);
416 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom
, true);
419 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight
, true);
422 _DoImageOperation(ImageProcessor::kRotateCounterClockwise
, true);
423 _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom
, true);
430 fCaption
= path
.Path();
431 fFormatDescription
= "Bitmap";
432 fMimeType
= "image/x-be-bitmap";
434 be_roster
->AddToRecentDocuments(ref
, kApplicationSignature
);
443 ShowImageView::ImageToView(BPoint p
) const
445 p
.x
= floorf(fZoom
* p
.x
+ fBitmapLocationInView
.x
);
446 p
.y
= floorf(fZoom
* p
.y
+ fBitmapLocationInView
.y
);
452 ShowImageView::ViewToImage(BPoint p
) const
454 p
.x
= floorf((p
.x
- fBitmapLocationInView
.x
) / fZoom
);
455 p
.y
= floorf((p
.y
- fBitmapLocationInView
.y
) / fZoom
);
461 ShowImageView::ImageToView(BRect r
) const
463 BPoint
leftTop(ImageToView(BPoint(r
.left
, r
.top
)));
464 BPoint
rightBottom(r
.right
, r
.bottom
);
465 rightBottom
+= BPoint(1, 1);
466 rightBottom
= ImageToView(rightBottom
);
467 rightBottom
-= BPoint(1, 1);
468 return BRect(leftTop
.x
, leftTop
.y
, rightBottom
.x
, rightBottom
.y
);
473 ShowImageView::ConstrainToImage(BPoint
& point
) const
475 point
.ConstrainTo(fBitmap
->Bounds());
480 ShowImageView::ConstrainToImage(BRect
& rect
) const
482 rect
= rect
& fBitmap
->Bounds();
487 ShowImageView::SetShowCaption(bool show
)
489 if (fShowCaption
!= show
) {
497 ShowImageView::SetStretchToBounds(bool enable
)
499 if (fStretchToBounds
!= enable
) {
500 _SettingsSetBool("StretchToBounds", enable
);
501 fStretchToBounds
= enable
;
502 if (enable
|| fZoom
> 1.0)
509 ShowImageView::SetHideIdlingCursor(bool hide
)
516 ShowImageView::Bitmap()
523 ShowImageView::SetScaleBilinear(bool enabled
)
525 if (fScaleBilinear
!= enabled
) {
526 _SettingsSetBool("ScaleBilinear", enabled
);
527 fScaleBilinear
= enabled
;
534 ShowImageView::AttachedToWindow()
542 ShowImageView::FrameResized(float width
, float height
)
549 ShowImageView::_FitToBoundsZoom() const
554 // the width/height of the bitmap (in pixels)
555 float bitmapWidth
= fBitmap
->Bounds().Width() + 1;
556 float bitmapHeight
= fBitmap
->Bounds().Height() + 1;
558 // the available width/height for layouting the bitmap (in pixels)
559 float width
= Bounds().Width() + 1;
560 float height
= Bounds().Height() + 1;
562 float zoom
= width
/ bitmapWidth
;
564 if (zoom
* bitmapHeight
<= height
)
567 return height
/ bitmapHeight
;
572 ShowImageView::_AlignBitmap()
574 BRect
rect(fBitmap
->Bounds());
576 // the width/height of the bitmap (in pixels)
577 float bitmapWidth
= rect
.Width() + 1;
578 float bitmapHeight
= rect
.Height() + 1;
580 // the available width/height for layouting the bitmap (in pixels)
581 float width
= Bounds().Width() + 1;
582 float height
= Bounds().Height() + 1;
584 if (width
== 0 || height
== 0)
588 rect
.right
= floorf(bitmapWidth
* fZoom
) - 1;
589 rect
.bottom
= floorf(bitmapHeight
* fZoom
) - 1;
591 // update the bitmap size after the zoom
592 bitmapWidth
= rect
.Width() + 1.0;
593 bitmapHeight
= rect
.Height() + 1.0;
595 // always align in the center if the bitmap is smaller than the window
596 if (width
> bitmapWidth
)
597 rect
.OffsetBy(floorf((width
- bitmapWidth
) / 2.0), 0);
599 if (height
> bitmapHeight
)
600 rect
.OffsetBy(0, floorf((height
- bitmapHeight
) / 2.0));
607 ShowImageView::_DrawBackground(BRect border
)
609 BRect
bounds(Bounds());
611 FillRect(BRect(0, 0, bounds
.right
, border
.top
- 1), B_SOLID_LOW
);
613 FillRect(BRect(0, border
.top
, border
.left
- 1, border
.bottom
), B_SOLID_LOW
);
615 FillRect(BRect(border
.right
+ 1, border
.top
, bounds
.right
, border
.bottom
), B_SOLID_LOW
);
617 FillRect(BRect(0, border
.bottom
+ 1, bounds
.right
, bounds
.bottom
), B_SOLID_LOW
);
622 ShowImageView::_LayoutCaption(BFont
& font
, BPoint
& pos
, BRect
& rect
)
624 font_height fontHeight
;
626 BRect
bounds(Bounds());
627 font
= be_plain_font
;
628 width
= font
.StringWidth(fCaption
.String());
629 font
.GetHeight(&fontHeight
);
630 height
= fontHeight
.ascent
+ fontHeight
.descent
;
631 // center text horizontally
632 pos
.x
= (bounds
.left
+ bounds
.right
- width
) / 2;
634 pos
.y
= bounds
.bottom
- fontHeight
.descent
- 7;
636 // background rectangle
637 rect
.Set(0, 0, width
+ 4, height
+ 4);
639 rect
.OffsetBy(-2, -2 - fontHeight
.ascent
); // -2 for border
644 ShowImageView::_DrawCaption()
649 _LayoutCaption(font
, position
, rect
);
654 SetDrawingMode(B_OP_ALPHA
);
655 SetHighColor(255, 255, 255, 160);
659 SetDrawingMode(B_OP_OVER
);
661 SetLowColor(B_TRANSPARENT_COLOR
);
662 SetHighColor(0, 0, 0);
663 DrawString(fCaption
.String(), position
);
670 ShowImageView::_UpdateCaption()
675 _LayoutCaption(font
, pos
, rect
);
677 // draw over portion of image where caption is located
680 ConstrainClippingRegion(&clip
);
687 ShowImageView::_DrawImage(BRect rect
)
689 // TODO: fix composing of fBitmap with other bitmaps
690 // with regard to alpha channel
692 fDisplayBitmap
= fBitmap
;
694 uint32 options
= fScaleBilinear
? B_FILTER_BITMAP_BILINEAR
: 0;
695 DrawBitmap(fDisplayBitmap
, fDisplayBitmap
->Bounds(), rect
, options
);
700 ShowImageView::Draw(BRect updateRect
)
710 BRect rect
= _AlignBitmap();
711 fBitmapLocationInView
.x
= floorf(rect
.left
);
712 fBitmapLocationInView
.y
= floorf(rect
.top
);
714 _DrawBackground(rect
);
721 if (fSelectionBitmap
!= NULL
) {
724 _GetSelectionMergeRects(srcRect
, dstRect
);
725 dstRect
= ImageToView(dstRect
);
726 DrawBitmap(fSelectionBitmap
, srcRect
, dstRect
);
728 fSelectionBox
.Draw(this, updateRect
);
734 ShowImageView::_CopySelection(uchar alpha
, bool imageSize
)
736 bool hasAlpha
= alpha
!= 255;
741 BRect rect
= fSelectionBox
.Bounds().OffsetToCopy(B_ORIGIN
);
743 // scale image to view size
744 rect
.right
= floorf((rect
.right
+ 1.0) * fZoom
- 1.0);
745 rect
.bottom
= floorf((rect
.bottom
+ 1.0) * fZoom
- 1.0);
747 BView
view(rect
, NULL
, B_FOLLOW_NONE
, B_WILL_DRAW
);
748 BBitmap
* bitmap
= new(nothrow
) BBitmap(rect
, hasAlpha
? B_RGBA32
749 : fBitmap
->ColorSpace(), true);
750 if (bitmap
== NULL
|| !bitmap
->IsValid()) {
755 if (bitmap
->Lock()) {
756 bitmap
->AddChild(&view
);
758 // On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
759 // Don't know if it's better to fix it or not (stippi).
761 view
.SetHighColor(0, 0, 0, 0);
762 view
.FillRect(view
.Bounds());
763 view
.SetDrawingMode(B_OP_ALPHA
);
764 view
.SetBlendingMode(B_CONSTANT_ALPHA
, B_ALPHA_COMPOSITE
);
765 view
.SetHighColor(0, 0, 0, alpha
);
767 if (fSelectionBitmap
) {
768 view
.DrawBitmap(fSelectionBitmap
,
769 fSelectionBitmap
->Bounds().OffsetToCopy(B_ORIGIN
), rect
);
771 view
.DrawBitmap(fBitmap
, fCopyFromRect
, rect
);
773 if (fSelectionBitmap
) {
774 view
.DrawBitmap(fSelectionBitmap
,
775 fSelectionBitmap
->Bounds().OffsetToCopy(B_ORIGIN
), rect
);
777 view
.DrawBitmap(fBitmap
, fCopyFromRect
, rect
);
779 view
.SetDrawingMode(B_OP_SUBTRACT
);
780 view
.SetHighColor(0, 0, 0, 255 - alpha
);
781 view
.FillRect(rect
, B_SOLID_HIGH
);
785 bitmap
->RemoveChild(&view
);
794 ShowImageView::_AddSupportedTypes(BMessage
* msg
, BBitmap
* bitmap
)
796 BTranslatorRoster
* roster
= BTranslatorRoster::Default();
800 // add the current image mime first, will make it the preferred format on
802 msg
->AddString("be:types", fMimeType
);
803 msg
->AddString("be:filetypes", fMimeType
);
804 msg
->AddString("be:type_descriptions", fFormatDescription
);
806 bool foundOther
= false;
807 bool foundCurrent
= false;
810 translator_info
* info
;
811 BBitmapStream
stream(bitmap
);
812 if (roster
->GetTranslators(&stream
, NULL
, &info
, &infoCount
) == B_OK
) {
813 for (int32 i
= 0; i
< infoCount
; i
++) {
814 const translation_format
* formats
;
816 roster
->GetOutputFormats(info
[i
].translator
, &formats
, &count
);
817 for (int32 j
= 0; j
< count
; j
++) {
818 if (fMimeType
== formats
[j
].MIME
)
820 else if (strcmp(formats
[j
].MIME
, "image/x-be-bitmap") != 0) {
822 // needed to send data in message
823 msg
->AddString("be:types", formats
[j
].MIME
);
824 // needed to pass data via file
825 msg
->AddString("be:filetypes", formats
[j
].MIME
);
826 msg
->AddString("be:type_descriptions", formats
[j
].name
);
831 stream
.DetachBitmap(&bitmap
);
834 msg
->RemoveData("be:types", 0);
835 msg
->RemoveData("be:filetypes", 0);
836 msg
->RemoveData("be:type_descriptions", 0);
839 return foundOther
|| foundCurrent
;
844 ShowImageView::_BeginDrag(BPoint sourcePoint
)
846 BBitmap
* bitmap
= _CopySelection(128, false);
850 SetMouseEventMask(B_POINTER_EVENTS
);
852 // fill the drag message
853 BMessage
drag(B_SIMPLE_DATA
);
854 drag
.AddInt32("be:actions", B_COPY_TARGET
);
855 drag
.AddString("be:clip_name", "Bitmap Clip");
856 // ShowImage specific fields
857 drag
.AddPoint("be:_source_point", sourcePoint
);
858 drag
.AddRect("be:_frame", fSelectionBox
.Bounds());
859 if (_AddSupportedTypes(&drag
, bitmap
)) {
860 // we also support "Passing Data via File" protocol
861 drag
.AddString("be:types", B_FILE_MIME_TYPE
);
862 // avoid flickering of dragged bitmap caused by drawing into the window
863 _AnimateSelection(false);
864 // only use a transparent bitmap on selections less than 400x400
865 // (taking into account zooming)
866 BRect selectionRect
= fSelectionBox
.Bounds();
867 if (selectionRect
.Width() * fZoom
< 400.0
868 && selectionRect
.Height() * fZoom
< 400.0) {
869 sourcePoint
-= selectionRect
.LeftTop();
870 sourcePoint
.x
*= fZoom
;
871 sourcePoint
.y
*= fZoom
;
872 // DragMessage takes ownership of bitmap
873 DragMessage(&drag
, bitmap
, B_OP_ALPHA
, sourcePoint
);
877 // Offset and scale the rect
878 BRect
rect(selectionRect
);
879 rect
= ImageToView(rect
);
880 rect
.InsetBy(-1, -1);
881 DragMessage(&drag
, rect
);
888 ShowImageView::_OutputFormatForType(BBitmap
* bitmap
, const char* type
,
889 translation_format
* format
)
893 BTranslatorRoster
* roster
= BTranslatorRoster::Default();
897 BBitmapStream
stream(bitmap
);
899 translator_info
* outInfo
;
901 if (roster
->GetTranslators(&stream
, NULL
, &outInfo
, &outNumInfo
) == B_OK
) {
902 for (int32 i
= 0; i
< outNumInfo
; i
++) {
903 const translation_format
* formats
;
905 roster
->GetOutputFormats(outInfo
[i
].translator
, &formats
,
907 for (int32 j
= 0; j
< formatCount
; j
++) {
908 if (strcmp(formats
[j
].MIME
, type
) == 0) {
909 *format
= formats
[j
];
916 stream
.DetachBitmap(&bitmap
);
921 #undef B_TRANSLATION_CONTEXT
922 #define B_TRANSLATION_CONTEXT "SaveToFile"
926 ShowImageView::SaveToFile(BDirectory
* dir
, const char* name
, BBitmap
* bitmap
,
927 const translation_format
* format
)
929 if (bitmap
== NULL
) {
930 // If no bitmap is supplied, write out the whole image
934 BBitmapStream
stream(bitmap
);
938 BTranslatorRoster
* roster
= BTranslatorRoster::Default();
942 BFile
file(dir
, name
, B_WRITE_ONLY
| B_CREATE_FILE
| B_ERASE_FILE
);
943 if (file
.InitCheck() != B_OK
)
945 if (roster
->Translate(&stream
, NULL
, NULL
, &file
, format
->type
) < B_OK
)
948 BNodeInfo
info(&file
);
949 if (info
.InitCheck() == B_OK
)
950 info
.SetType(format
->MIME
);
953 // break out of loop gracefully (indicates no errors)
956 // If loop terminated because of a break, there was an error
958 snprintf(buffer
, sizeof(buffer
), B_TRANSLATE("The file '%s' could not "
959 "be written."), name
);
960 BAlert
* palert
= new BAlert("", buffer
, B_TRANSLATE("OK"));
961 palert
->SetFlags(palert
->Flags() | B_CLOSE_ON_ESCAPE
);
965 stream
.DetachBitmap(&bitmap
);
966 // Don't allow the bitmap to be deleted, this is
967 // especially important when using fBitmap as the bitmap
972 ShowImageView::_SendInMessage(BMessage
* msg
, BBitmap
* bitmap
,
973 translation_format
* format
)
975 BMessage
reply(B_MIME_DATA
);
976 BBitmapStream
stream(bitmap
); // destructor deletes bitmap
977 BTranslatorRoster
* roster
= BTranslatorRoster::Default();
979 if (roster
->Translate(&stream
, NULL
, NULL
, &memStream
, format
->type
) == B_OK
) {
980 reply
.AddData(format
->MIME
, B_MIME_TYPE
, memStream
.Buffer(),
981 memStream
.BufferLength());
982 msg
->SendReply(&reply
);
988 ShowImageView::_HandleDrop(BMessage
* msg
)
992 bool saveToFile
= msg
->FindString("be:filetypes", &type
) == B_OK
993 && msg
->FindRef("directory", &dirRef
) == B_OK
994 && msg
->FindString("name", &name
) == B_OK
;
996 bool sendInMessage
= !saveToFile
997 && msg
->FindString("be:types", &type
) == B_OK
;
999 BBitmap
* bitmap
= _CopySelection();
1003 translation_format format
;
1004 if (!_OutputFormatForType(bitmap
, type
.String(), &format
)) {
1010 BDirectory
dir(&dirRef
);
1011 SaveToFile(&dir
, name
.String(), bitmap
, &format
);
1013 } else if (sendInMessage
) {
1014 _SendInMessage(msg
, bitmap
, &format
);
1022 ShowImageView::_ScrollBitmap(BPoint point
)
1024 point
= ConvertToScreen(point
);
1025 BPoint delta
= fFirstPoint
- point
;
1026 fFirstPoint
= point
;
1027 _ScrollRestrictedBy(delta
.x
, delta
.y
);
1032 ShowImageView::_GetMergeRects(BBitmap
* merge
, BRect selection
, BRect
& srcRect
,
1035 // Constrain dstRect to target image size and apply the same edge offsets
1038 dstRect
= selection
;
1040 BRect
clippedDstRect(dstRect
);
1041 ConstrainToImage(clippedDstRect
);
1043 srcRect
= merge
->Bounds().OffsetToCopy(B_ORIGIN
);
1045 srcRect
.left
+= clippedDstRect
.left
- dstRect
.left
;
1046 srcRect
.top
+= clippedDstRect
.top
- dstRect
.top
;
1047 srcRect
.right
+= clippedDstRect
.right
- dstRect
.right
;
1048 srcRect
.bottom
+= clippedDstRect
.bottom
- dstRect
.bottom
;
1050 dstRect
= clippedDstRect
;
1055 ShowImageView::_GetSelectionMergeRects(BRect
& srcRect
, BRect
& dstRect
)
1057 _GetMergeRects(fSelectionBitmap
, fSelectionBox
.Bounds(), srcRect
, dstRect
);
1062 ShowImageView::_MergeWithBitmap(BBitmap
* merge
, BRect selection
)
1064 BView
view(fBitmap
->Bounds(), NULL
, B_FOLLOW_NONE
, B_WILL_DRAW
);
1065 BBitmap
* bitmap
= new(nothrow
) BBitmap(fBitmap
->Bounds(),
1066 fBitmap
->ColorSpace(), true);
1067 if (bitmap
== NULL
|| !bitmap
->IsValid()) {
1072 if (bitmap
->Lock()) {
1073 bitmap
->AddChild(&view
);
1074 view
.DrawBitmap(fBitmap
, fBitmap
->Bounds());
1077 _GetMergeRects(merge
, selection
, srcRect
, dstRect
);
1078 view
.DrawBitmap(merge
, srcRect
, dstRect
);
1081 bitmap
->RemoveChild(&view
);
1087 _SendMessageToWindow(MSG_MODIFIED
);
1094 ShowImageView::MouseDown(BPoint position
)
1098 BPoint point
= ViewToImage(position
);
1099 int32 clickCount
= 0;
1101 if (Window() != NULL
&& Window()->CurrentMessage() != NULL
) {
1102 clickCount
= Window()->CurrentMessage()->FindInt32("clicks");
1103 buttons
= Window()->CurrentMessage()->FindInt32("buttons");
1106 // Using clickCount >= 2 and the modulo 2 accounts for quickly repeated
1108 if (buttons
== B_PRIMARY_MOUSE_BUTTON
&& clickCount
>= 2 &&
1109 clickCount
% 2 == 0) {
1110 Window()->PostMessage(MSG_FULL_SCREEN
);
1114 if (fHasSelection
&& fSelectionBox
.Bounds().Contains(point
)
1116 & (B_PRIMARY_MOUSE_BUTTON
| B_SECONDARY_MOUSE_BUTTON
)) != 0) {
1117 if (!fSelectionBitmap
)
1118 fSelectionBitmap
= _CopySelection();
1121 } else if (buttons
== B_PRIMARY_MOUSE_BUTTON
1123 || (modifiers() & (B_COMMAND_KEY
| B_CONTROL_KEY
)) != 0)) {
1124 // begin new selection
1125 _SetHasSelection(true);
1126 fCreatingSelection
= true;
1127 SetMouseEventMask(B_POINTER_EVENTS
, B_NO_POINTER_HISTORY
);
1128 ConstrainToImage(point
);
1129 fFirstPoint
= point
;
1130 fCopyFromRect
.Set(point
.x
, point
.y
, point
.x
, point
.y
);
1131 fSelectionBox
.SetBounds(this, fCopyFromRect
);
1133 } else if (buttons
== B_SECONDARY_MOUSE_BUTTON
) {
1134 _ShowPopUpMenu(ConvertToScreen(position
));
1135 } else if (buttons
== B_PRIMARY_MOUSE_BUTTON
1136 || buttons
== B_TERTIARY_MOUSE_BUTTON
) {
1137 // move image in window
1138 SetMouseEventMask(B_POINTER_EVENTS
, B_NO_POINTER_HISTORY
);
1139 fScrollingBitmap
= true;
1140 fFirstPoint
= ConvertToScreen(position
);
1141 be_app
->SetCursor(fGrabCursor
);
1147 ShowImageView::_UpdateSelectionRect(BPoint point
, bool final
)
1149 BRect oldSelection
= fCopyFromRect
;
1150 point
= ViewToImage(point
);
1151 ConstrainToImage(point
);
1152 fCopyFromRect
.left
= min_c(fFirstPoint
.x
, point
.x
);
1153 fCopyFromRect
.right
= max_c(fFirstPoint
.x
, point
.x
);
1154 fCopyFromRect
.top
= min_c(fFirstPoint
.y
, point
.y
);
1155 fCopyFromRect
.bottom
= max_c(fFirstPoint
.y
, point
.y
);
1156 fSelectionBox
.SetBounds(this, fCopyFromRect
);
1159 // selection must be at least 2 pixels wide or 2 pixels tall
1160 if (fCopyFromRect
.Width() < 1.0 && fCopyFromRect
.Height() < 1.0)
1161 _SetHasSelection(false);
1163 _UpdateStatusText();
1165 if (oldSelection
!= fCopyFromRect
|| !fHasSelection
) {
1167 updateRect
= oldSelection
| fCopyFromRect
;
1168 updateRect
= ImageToView(updateRect
);
1169 updateRect
.InsetBy(-1, -1);
1170 Invalidate(updateRect
);
1176 ShowImageView::MouseMoved(BPoint point
, uint32 state
, const BMessage
* message
)
1178 fHideCursorCountDown
= HIDE_CURSOR_DELAY_TIME
;
1180 // Show toolbar when mouse hits top 15 pixels, hide otherwise
1181 _ShowToolBarIfEnabled(ConvertToScreen(point
).y
<= 15);
1183 if (fCreatingSelection
)
1184 _UpdateSelectionRect(point
, false);
1185 else if (fScrollingBitmap
)
1186 _ScrollBitmap(point
);
1191 ShowImageView::MouseUp(BPoint point
)
1193 if (fCreatingSelection
) {
1194 _UpdateSelectionRect(point
, true);
1195 fCreatingSelection
= false;
1196 } else if (fScrollingBitmap
) {
1197 _ScrollBitmap(point
);
1198 fScrollingBitmap
= false;
1199 be_app
->SetCursor(fDefaultCursor
);
1201 _AnimateSelection(true);
1206 ShowImageView::_LimitToRange(float v
, orientation o
, bool absolute
)
1208 BScrollBar
* psb
= ScrollBar(o
);
1210 float min
, max
, pos
;
1213 pos
+= psb
->Value();
1215 psb
->GetRange(&min
, &max
);
1230 ShowImageView::_ScrollRestricted(float x
, float y
, bool absolute
)
1233 x
= _LimitToRange(x
, B_HORIZONTAL
, absolute
);
1236 y
= _LimitToRange(y
, B_VERTICAL
, absolute
);
1238 // We invalidate before we scroll to avoid the caption messing up the
1239 // image, and to prevent it from flickering
1247 // XXX method is not unused
1249 ShowImageView::_ScrollRestrictedTo(float x
, float y
)
1251 _ScrollRestricted(x
, y
, true);
1256 ShowImageView::_ScrollRestrictedBy(float x
, float y
)
1258 _ScrollRestricted(x
, y
, false);
1263 ShowImageView::KeyDown(const char* bytes
, int32 numBytes
)
1265 if (numBytes
!= 1) {
1266 BView::KeyDown(bytes
, numBytes
);
1270 bool shiftKeyDown
= (modifiers() & B_SHIFT_KEY
) != 0;
1275 _ScrollRestrictedBy(0, 10);
1277 _SendMessageToWindow(MSG_FILE_NEXT
);
1281 _ScrollRestrictedBy(10, 0);
1283 _SendMessageToWindow(MSG_FILE_NEXT
);
1287 _ScrollRestrictedBy(0, -10);
1289 _SendMessageToWindow(MSG_FILE_PREV
);
1293 _ScrollRestrictedBy(-10, 0);
1295 _SendMessageToWindow(MSG_FILE_PREV
);
1298 _SendMessageToWindow(MSG_FILE_PREV
);
1318 _SendMessageToWindow(kMsgDeleteCurrentFile
);
1344 ShowImageView::_MouseWheelChanged(BMessage
* message
)
1346 // The BeOS driver does not currently support
1347 // X wheel scrolling, therefore, deltaX is zero.
1348 // |deltaY| is the number of notches scrolled up or down.
1349 // When the wheel is scrolled down (towards the user) deltaY > 0
1350 // When the wheel is scrolled up (away from the user) deltaY < 0
1351 const float kscrollBy
= 40;
1357 if (message
->FindFloat("be:wheel_delta_x", &deltaX
) == B_OK
)
1358 x
= deltaX
* kscrollBy
;
1360 if (message
->FindFloat("be:wheel_delta_y", &deltaY
) == B_OK
)
1361 y
= deltaY
* kscrollBy
;
1363 if ((modifiers() & B_SHIFT_KEY
) != 0) {
1364 // scroll up and down
1365 _ScrollRestrictedBy(x
, y
);
1366 } else if ((modifiers() & B_CONTROL_KEY
) != 0) {
1367 // scroll left and right
1368 _ScrollRestrictedBy(y
, x
);
1373 GetMouse(&where
, &buttons
);
1375 if (fStickyZoomCountDown
<= 0) {
1378 else if (deltaY
> 0)
1382 fStickyZoomCountDown
= STICKY_ZOOM_DELAY_TIME
;
1390 ShowImageView::_ShowPopUpMenu(BPoint screen
)
1392 if (!fShowingPopUpMenu
) {
1393 PopUpMenu
* menu
= new PopUpMenu("PopUpMenu", this);
1395 ShowImageWindow
* window
= dynamic_cast<ShowImageWindow
*>(Window());
1397 window
->BuildContextMenu(menu
);
1399 menu
->Go(screen
, true, true, true);
1400 fShowingPopUpMenu
= true;
1406 ShowImageView::_SettingsSetBool(const char* name
, bool value
)
1408 ShowImageSettings
* settings
;
1409 settings
= my_app
->Settings();
1410 if (settings
->Lock()) {
1411 settings
->SetBool(name
, value
);
1418 ShowImageView::MessageReceived(BMessage
* message
)
1420 switch (message
->what
) {
1422 _HandleDrop(message
);
1425 case B_MOUSE_WHEEL_CHANGED
:
1426 _MouseWheelChanged(message
);
1429 case kMsgPopUpMenuClosed
:
1430 fShowingPopUpMenu
= false;
1434 BView::MessageReceived(message
);
1441 ShowImageView::FixupScrollBar(orientation o
, float bitmapLength
,
1449 range
= bitmapLength
- viewLength
;
1453 prop
= viewLength
/ bitmapLength
;
1457 psb
->SetRange(0, range
);
1458 psb
->SetProportion(prop
);
1459 psb
->SetSteps(10, 100);
1465 ShowImageView::FixupScrollBars()
1467 BRect viewRect
= Bounds();
1469 if (fBitmap
!= NULL
) {
1470 bitmapRect
= _AlignBitmap();
1471 bitmapRect
.OffsetTo(0, 0);
1474 FixupScrollBar(B_HORIZONTAL
, bitmapRect
.Width(), viewRect
.Width());
1475 FixupScrollBar(B_VERTICAL
, bitmapRect
.Height(), viewRect
.Height());
1480 ShowImageView::SetSelectionMode(bool selectionMode
)
1482 // The mode only has an effect in MouseDown()
1483 fSelectionMode
= selectionMode
;
1488 ShowImageView::SelectAll()
1490 fCopyFromRect
.Set(0, 0, fBitmap
->Bounds().Width(),
1491 fBitmap
->Bounds().Height());
1492 fSelectionBox
.SetBounds(this, fCopyFromRect
);
1493 _SetHasSelection(true);
1499 ShowImageView::ClearSelection()
1504 _SetHasSelection(false);
1510 ShowImageView::_SetHasSelection(bool hasSelection
)
1512 _DeleteSelectionBitmap();
1513 fHasSelection
= hasSelection
;
1515 _UpdateStatusText();
1517 BMessage
msg(MSG_SELECTION
);
1518 msg
.AddBool("has_selection", fHasSelection
);
1519 _SendMessageToWindow(&msg
);
1524 ShowImageView::CopySelectionToClipboard()
1526 if (!fHasSelection
|| !be_clipboard
->Lock())
1529 be_clipboard
->Clear();
1531 BMessage
* data
= be_clipboard
->Data();
1533 BBitmap
* bitmap
= _CopySelection();
1534 if (bitmap
!= NULL
) {
1535 BMessage bitmapArchive
;
1536 bitmap
->Archive(&bitmapArchive
);
1537 // NOTE: Possibly "image/x-be-bitmap" is more correct.
1538 // This works with WonderBrush, though, which in turn had been
1539 // tested with other apps.
1540 data
->AddMessage("image/bitmap", &bitmapArchive
);
1541 data
->AddPoint("be:location", fSelectionBox
.Bounds().LeftTop());
1545 be_clipboard
->Commit();
1548 be_clipboard
->Unlock();
1553 ShowImageView::SetZoom(float zoom
, BPoint where
)
1555 float fitToBoundsZoom
= _FitToBoundsZoom();
1558 if (zoom
< fitToBoundsZoom
/ 2 && zoom
< 0.25)
1559 zoom
= min_c(fitToBoundsZoom
/ 2, 0.25);
1561 if (zoom
== fZoom
) {
1562 // window size might have changed
1567 // Invalidate before scrolling, as that prevents the app_server
1568 // to do the scrolling server side
1571 // zoom to center if not otherwise specified
1573 if (where
.x
== -1) {
1574 where
.Set(Bounds().Width() / 2, Bounds().Height() / 2);
1576 where
+= Bounds().LeftTop();
1578 offset
= where
- Bounds().LeftTop();
1580 float oldZoom
= fZoom
;
1585 if (fBitmap
!= NULL
) {
1586 offset
.x
= (int)(where
.x
* fZoom
/ oldZoom
+ 0.5) - offset
.x
;
1587 offset
.y
= (int)(where
.y
* fZoom
/ oldZoom
+ 0.5) - offset
.y
;
1591 BMessage
message(MSG_UPDATE_STATUS_ZOOM
);
1592 message
.AddFloat("zoom", fZoom
);
1593 _SendMessageToWindow(&message
);
1598 ShowImageView::ZoomIn(BPoint where
)
1600 // snap zoom to "fit to bounds", and "original size"
1601 float zoom
= fZoom
* 1.2;
1602 float zoomSnap
= fZoom
* 1.25;
1603 float fitToBoundsZoom
= _FitToBoundsZoom();
1604 if (fZoom
< fitToBoundsZoom
- 0.001 && zoomSnap
> fitToBoundsZoom
)
1605 zoom
= fitToBoundsZoom
;
1606 if (fZoom
< 1.0 && zoomSnap
> 1.0)
1609 SetZoom(zoom
, where
);
1614 ShowImageView::ZoomOut(BPoint where
)
1616 // snap zoom to "fit to bounds", and "original size"
1617 float zoom
= fZoom
/ 1.2;
1618 float zoomSnap
= fZoom
/ 1.25;
1619 float fitToBoundsZoom
= _FitToBoundsZoom();
1620 if (fZoom
> fitToBoundsZoom
+ 0.001 && zoomSnap
< fitToBoundsZoom
)
1621 zoom
= fitToBoundsZoom
;
1622 if (fZoom
> 1.0 && zoomSnap
< 1.0)
1625 SetZoom(zoom
, where
);
1629 /*! Fits the image to the view bounds.
1632 ShowImageView::FitToBounds()
1634 if (fBitmap
== NULL
)
1637 float fitToBoundsZoom
= _FitToBoundsZoom();
1638 if ((!fStretchToBounds
&& fitToBoundsZoom
> 1.0f
) || fForceOriginalSize
)
1641 SetZoom(fitToBoundsZoom
);
1648 ShowImageView::_DoImageOperation(ImageProcessor::operation op
, bool quiet
)
1651 ImageProcessor
imageProcessor(op
, fBitmap
, msgr
, 0);
1652 imageProcessor
.Start(false);
1653 BBitmap
* bm
= imageProcessor
.DetachBitmap();
1659 // update orientation state
1660 if (op
!= ImageProcessor::kInvert
) {
1661 // Note: If one of these fails, check its definition in class ImageProcessor.
1662 // ASSERT(ImageProcessor::kRotateClockwise <
1663 // ImageProcessor::kNumberOfAffineTransformations);
1664 // ASSERT(ImageProcessor::kRotateCounterClockwise <
1665 // ImageProcessor::kNumberOfAffineTransformations);
1666 // ASSERT(ImageProcessor::kFlipLeftToRight <
1667 // ImageProcessor::kNumberOfAffineTransformations);
1668 // ASSERT(ImageProcessor::kFlipTopToBottom <
1669 // ImageProcessor::kNumberOfAffineTransformations);
1670 fImageOrientation
= fTransformation
[op
][fImageOrientation
];
1674 // write orientation state
1675 BNode
node(&fCurrentRef
);
1676 int32 orientation
= fImageOrientation
;
1677 if (orientation
!= k0
) {
1678 node
.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE
, B_INT32_TYPE
, 0,
1679 &orientation
, sizeof(orientation
));
1681 node
.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE
);
1688 if (fBitmap
->ColorSpace() == B_RGBA32
)
1689 fDisplayBitmap
= compose_checker_background(fBitmap
);
1693 _SetHasSelection(false);
1699 //! Image operation initiated by user
1701 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op
, bool quiet
)
1703 _DoImageOperation(op
, quiet
);
1708 ShowImageView::Rotate(int degree
)
1710 _UserDoImageOperation(degree
== 90 ? ImageProcessor::kRotateClockwise
1711 : ImageProcessor::kRotateCounterClockwise
);
1718 ShowImageView::Flip(bool vertical
)
1721 _UserDoImageOperation(ImageProcessor::kFlipLeftToRight
);
1723 _UserDoImageOperation(ImageProcessor::kFlipTopToBottom
);
1728 ShowImageView::ResizeImage(int w
, int h
)
1730 if (fBitmap
== NULL
|| w
< 1 || h
< 1)
1733 Scaler
scaler(fBitmap
, BRect(0, 0, w
- 1, h
- 1), BMessenger(), 0, false);
1734 scaler
.Start(false);
1735 BBitmap
* scaled
= scaler
.DetachBitmap();
1736 if (scaled
== NULL
) {
1742 _SetHasSelection(false);
1746 _SendMessageToWindow(MSG_MODIFIED
);
1753 ShowImageView::_SetIcon(bool clear
, icon_size which
)
1757 case B_MINI_ICON
: size
= 16;
1759 case B_LARGE_ICON
: size
= 32;
1765 BRect
rect(fBitmap
->Bounds());
1767 s
= size
/ (rect
.Width() + 1.0);
1769 if (s
* (rect
.Height() + 1.0) <= size
) {
1770 rect
.right
= size
- 1;
1771 rect
.bottom
= static_cast<int>(s
* (rect
.Height() + 1.0)) - 1;
1772 // center vertically
1773 rect
.OffsetBy(0, (size
- rect
.IntegerHeight()) / 2);
1775 s
= size
/ (rect
.Height() + 1.0);
1776 rect
.right
= static_cast<int>(s
* (rect
.Width() + 1.0)) - 1;
1777 rect
.bottom
= size
- 1;
1778 // center horizontally
1779 rect
.OffsetBy((size
- rect
.IntegerWidth()) / 2, 0);
1782 // scale bitmap to thumbnail size
1784 Scaler
scaler(fBitmap
, rect
, msgr
, 0, true);
1785 BBitmap
* thumbnail
= scaler
.GetBitmap();
1786 scaler
.Start(false);
1787 ASSERT(thumbnail
->ColorSpace() == B_CMAP8
);
1788 // create icon from thumbnail
1789 BBitmap
icon(BRect(0, 0, size
- 1, size
- 1), B_CMAP8
);
1790 memset(icon
.Bits(), B_TRANSPARENT_MAGIC_CMAP8
, icon
.BitsLength());
1792 const uchar
* src
= (uchar
*)thumbnail
->Bits();
1793 uchar
* dest
= (uchar
*)icon
.Bits();
1794 const int32 srcBPR
= thumbnail
->BytesPerRow();
1795 const int32 destBPR
= icon
.BytesPerRow();
1796 const int32 deltaX
= (int32
)rect
.left
;
1797 const int32 deltaY
= (int32
)rect
.top
;
1799 for (int32 y
= 0; y
<= rect
.IntegerHeight(); y
++) {
1800 for (int32 x
= 0; x
<= rect
.IntegerWidth(); x
++) {
1801 const uchar
* s
= src
+ y
* srcBPR
+ x
;
1802 uchar
* d
= dest
+ (y
+ deltaY
) * destBPR
+ (x
+ deltaX
);
1808 BNode
node(&fCurrentRef
);
1809 BNodeInfo
info(&node
);
1810 info
.SetIcon(clear
? NULL
: &icon
, which
);
1815 ShowImageView::SetIcon(bool clear
)
1817 _SetIcon(clear
, B_MINI_ICON
);
1818 _SetIcon(clear
, B_LARGE_ICON
);
1823 ShowImageView::_ToggleSlideShow()
1825 _SendMessageToWindow(MSG_SLIDE_SHOW
);
1830 ShowImageView::_StopSlideShow()
1832 _SendMessageToWindow(kMsgStopSlideShow
);
1837 ShowImageView::_ExitFullScreen()
1839 be_app
->ShowCursor();
1840 _SendMessageToWindow(MSG_EXIT_FULL_SCREEN
);
1845 ShowImageView::_ShowToolBarIfEnabled(bool show
)
1847 BMessage
message(kShowToolBarIfEnabled
);
1848 message
.AddBool("show", show
);
1849 Window()->PostMessage(&message
);
1854 ShowImageView::WindowActivated(bool active
)
1856 fIsActiveWin
= active
;
1857 fHideCursorCountDown
= HIDE_CURSOR_DELAY_TIME
;