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