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