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