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