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