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