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
61using std::nothrow;
62
63
64class 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
80const rgb_color kBorderColor = { 0, 0, 0, 255 };
81
82enum ShowImageView::image_orientation
83ShowImageView::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
95const rgb_color kAlphaLow = (rgb_color) { 0xbb, 0xbb, 0xbb, 0xff };
96const rgb_color kAlphaHigh = (rgb_color) { 0xe0, 0xe0, 0xe0, 0xff };
97
98const uint32 kMsgPopUpMenuClosed = 'pmcl';
99
100
101inline void
102blend_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
110BBitmap*
111compose_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
157PopUpMenu::PopUpMenu(const char* name, BMessenger target)
158	:
159	BPopUpMenu(name, false, false),
160	fTarget(target)
161{
162	SetAsyncAutoDestruct(true);
163}
164
165
166PopUpMenu::~PopUpMenu()
167{
168	fTarget.SendMessage(kMsgPopUpMenuClosed);
169}
170
171
172//	#pragma mark -
173
174
175ShowImageView::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
223ShowImageView::~ShowImageView()
224{
225	_DeleteBitmap();
226
227	delete fDefaultCursor;
228	delete fGrabCursor;
229}
230
231
232void
233ShowImageView::_AnimateSelection(bool enabled)
234{
235	fAnimateSelection = enabled;
236}
237
238
239void
240ShowImageView::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
273void
274ShowImageView::_SendMessageToWindow(BMessage* message)
275{
276	BMessenger target(Window());
277	target.SendMessage(message);
278}
279
280
281void
282ShowImageView::_SendMessageToWindow(uint32 code)
283{
284	BMessage message(code);
285	_SendMessageToWindow(&message);
286}
287
288
289//! send message to parent about new image
290void
291ShowImageView::_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
306void
307ShowImageView::_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
324void
325ShowImageView::_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
343void
344ShowImageView::_DeleteSelectionBitmap()
345{
346	delete fSelectionBitmap;
347	fSelectionBitmap = NULL;
348}
349
350
351status_t
352ShowImageView::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
373status_t
374ShowImageView::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
448BPoint
449ShowImageView::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
457BPoint
458ShowImageView::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
466BRect
467ShowImageView::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
478void
479ShowImageView::ConstrainToImage(BPoint& point) const
480{
481	point.ConstrainTo(fBitmap->Bounds());
482}
483
484
485void
486ShowImageView::ConstrainToImage(BRect& rect) const
487{
488	rect = rect & fBitmap->Bounds();
489}
490
491
492void
493ShowImageView::SetShowCaption(bool show)
494{
495	if (fShowCaption != show) {
496		fShowCaption = show;
497		_UpdateCaption();
498	}
499}
500
501
502void
503ShowImageView::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
514void
515ShowImageView::SetHideIdlingCursor(bool hide)
516{
517	fHideCursor = hide;
518}
519
520
521BBitmap*
522ShowImageView::Bitmap()
523{
524	return fBitmap;
525}
526
527
528void
529ShowImageView::SetScaleBilinear(bool enabled)
530{
531	if (fScaleBilinear != enabled) {
532		_SettingsSetBool("ScaleBilinear", enabled);
533		fScaleBilinear = enabled;
534		Invalidate();
535	}
536}
537
538
539void
540ShowImageView::AttachedToWindow()
541{
542	FitToBounds();
543	FixupScrollBars();
544}
545
546
547void
548ShowImageView::FrameResized(float width, float height)
549{
550	FixupScrollBars();
551}
552
553
554float
555ShowImageView::_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
577BRect
578ShowImageView::_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
612void
613ShowImageView::_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
627void
628ShowImageView::_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
649void
650ShowImageView::_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
675void
676ShowImageView::_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
692void
693ShowImageView::_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
705void
706ShowImageView::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
739BBitmap*
740ShowImageView::_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
799bool
800ShowImageView::_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
849void
850ShowImageView::_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
893bool
894ShowImageView::_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
931void
932ShowImageView::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
977void
978ShowImageView::_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
993void
994ShowImageView::_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
1027void
1028ShowImageView::_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
1037void
1038ShowImageView::_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
1060void
1061ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
1062{
1063	_GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
1064}
1065
1066
1067void
1068ShowImageView::_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
1099void
1100ShowImageView::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
1152void
1153ShowImageView::_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
1181void
1182ShowImageView::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
1196void
1197ShowImageView::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
1211float
1212ShowImageView::_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
1235void
1236ShowImageView::_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
1254void
1255ShowImageView::_ScrollRestrictedTo(float x, float y)
1256{
1257	_ScrollRestricted(x, y, true);
1258}
1259
1260
1261void
1262ShowImageView::_ScrollRestrictedBy(float x, float y)
1263{
1264	_ScrollRestricted(x, y, false);
1265}
1266
1267
1268void
1269ShowImageView::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
1349void
1350ShowImageView::_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
1395void
1396ShowImageView::_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
1411void
1412ShowImageView::_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
1423void
1424ShowImageView::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
1446void
1447ShowImageView::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
1470void
1471ShowImageView::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
1485void
1486ShowImageView::SetSelectionMode(bool selectionMode)
1487{
1488	// The mode only has an effect in MouseDown()
1489	fSelectionMode = selectionMode;
1490}
1491
1492
1493void
1494ShowImageView::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
1504void
1505ShowImageView::ClearSelection()
1506{
1507	if (!fHasSelection)
1508		return;
1509
1510	_SetHasSelection(false);
1511	Invalidate();
1512}
1513
1514
1515void
1516ShowImageView::_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
1529void
1530ShowImageView::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
1558void
1559ShowImageView::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
1603void
1604ShowImageView::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
1619void
1620ShowImageView::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*/
1637void
1638ShowImageView::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
1653void
1654ShowImageView::_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
1706void
1707ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
1708{
1709	_DoImageOperation(op, quiet);
1710}
1711
1712
1713void
1714ShowImageView::Rotate(int degree)
1715{
1716	_UserDoImageOperation(degree == 90 ? ImageProcessor::kRotateClockwise
1717		: ImageProcessor::kRotateCounterClockwise);
1718
1719	FitToBounds();
1720}
1721
1722
1723void
1724ShowImageView::Flip(bool vertical)
1725{
1726	if (vertical)
1727		_UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
1728	else
1729		_UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
1730}
1731
1732
1733void
1734ShowImageView::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
1758void
1759ShowImageView::_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
1812void
1813ShowImageView::SetIcon(bool clear)
1814{
1815	_SetIcon(clear, B_MINI_ICON);
1816	_SetIcon(clear, B_LARGE_ICON);
1817}
1818
1819
1820void
1821ShowImageView::_ToggleSlideShow()
1822{
1823	_SendMessageToWindow(MSG_SLIDE_SHOW);
1824}
1825
1826
1827void
1828ShowImageView::_StopSlideShow()
1829{
1830	_SendMessageToWindow(kMsgStopSlideShow);
1831}
1832
1833
1834void
1835ShowImageView::_ExitFullScreen()
1836{
1837	be_app->ShowCursor();
1838	_SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
1839}
1840
1841
1842void
1843ShowImageView::_ShowToolBarIfEnabled(bool show)
1844{
1845	BMessage message(kShowToolBarIfEnabled);
1846	message.AddBool("show", show);
1847	Window()->PostMessage(&message);
1848}
1849
1850
1851void
1852ShowImageView::WindowActivated(bool active)
1853{
1854	fIsActiveWin = active;
1855	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1856}
1857
1858