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