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