xref: /haiku/src/apps/showimage/ShowImageView.cpp (revision a7dde370f552f5376edbf25046ec9cf2ba8bbd1a)
1 /*
2  * Copyright 2003-2010, 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  */
16 
17 
18 #include "ShowImageView.h"
19 
20 #include <math.h>
21 #include <new>
22 #include <stdio.h>
23 
24 #include <Alert.h>
25 #include <Application.h>
26 #include <Bitmap.h>
27 #include <BitmapStream.h>
28 #include <Catalog.h>
29 #include <Clipboard.h>
30 #include <Debug.h>
31 #include <Directory.h>
32 #include <Entry.h>
33 #include <File.h>
34 #include <Locale.h>
35 #include <MenuBar.h>
36 #include <MenuItem.h>
37 #include <Message.h>
38 #include <NodeInfo.h>
39 #include <Path.h>
40 #include <PopUpMenu.h>
41 #include <Rect.h>
42 #include <Region.h>
43 #include <Roster.h>
44 #include <Screen.h>
45 #include <ScrollBar.h>
46 #include <StopWatch.h>
47 #include <SupportDefs.h>
48 #include <TranslatorRoster.h>
49 
50 #include <tracker_private.h>
51 
52 #include "ProgressWindow.h"
53 #include "ShowImageApp.h"
54 #include "ShowImageConstants.h"
55 #include "ShowImageWindow.h"
56 
57 
58 // TODO: Remove this and use Tracker's Command.h once it is moved into the private headers
59 namespace BPrivate {
60 	const uint32 kMoveToTrash = 'Ttrs';
61 }
62 
63 using std::nothrow;
64 
65 
66 class PopUpMenu : public BPopUpMenu {
67 	public:
68 		PopUpMenu(const char* name, BMessenger target);
69 		virtual ~PopUpMenu();
70 
71 	private:
72 		BMessenger fTarget;
73 };
74 
75 
76 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
77 const rgb_color kBorderColor = { 0, 0, 0, 255 };
78 
79 enum ShowImageView::image_orientation
80 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations][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 static bool
98 entry_ref_is_file(const entry_ref *ref)
99 {
100 	BEntry entry(ref, true);
101 	if (entry.InitCheck() != B_OK)
102 		return false;
103 
104 	return entry.IsFile();
105 }
106 
107 
108 inline void
109 blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a)
110 {
111 	d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8;
112 	d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8;
113 	d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8;
114 }
115 
116 
117 BBitmap*
118 compose_checker_background(const BBitmap* bitmap)
119 {
120 	BBitmap* result = new (nothrow) BBitmap(bitmap);
121 	if (result && !result->IsValid()) {
122 		delete result;
123 		result = NULL;
124 	}
125 	if (!result)
126 		return NULL;
127 
128 	uint8* bits = (uint8*)result->Bits();
129 	uint32 bpr = result->BytesPerRow();
130 	uint32 width = result->Bounds().IntegerWidth() + 1;
131 	uint32 height = result->Bounds().IntegerHeight() + 1;
132 
133 	for (uint32 i = 0; i < height; i++) {
134 		uint8* p = bits;
135 		for (uint32 x = 0; x < width; x++) {
136 			uint8 alpha = p[3];
137 			if (alpha < 255) {
138 				p[3] = 255;
139 				alpha = 255 - alpha;
140 				if (x % 10 >= 5) {
141 					if (i % 10 >= 5) {
142 						blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
143 					} else {
144 						blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
145 					}
146 				} else {
147 					if (i % 10 >= 5) {
148 						blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
149 					} else {
150 						blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
151 					}
152 				}
153 			}
154 			p += 4;
155 		}
156 		bits += bpr;
157 	}
158 	return result;
159 }
160 
161 
162 //	#pragma mark -
163 
164 
165 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
166 	:
167 	BPopUpMenu(name, false, false),
168 	fTarget(target)
169 {
170 	SetAsyncAutoDestruct(true);
171 }
172 
173 
174 PopUpMenu::~PopUpMenu()
175 {
176 	fTarget.SendMessage(kMsgPopUpMenuClosed);
177 }
178 
179 
180 //	#pragma mark -
181 
182 
183 ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
184 		uint32 flags)
185 	:
186 	BView(rect, name, resizingMode, flags),
187 	fDocumentIndex(1),
188 	fDocumentCount(1),
189 	fBitmap(NULL),
190 	fDisplayBitmap(NULL),
191 	fSelectionBitmap(NULL),
192 
193 	fZoom(1.0),
194 
195 	fScaleBilinear(true),
196 
197 	fBitmapLocationInView(0.0, 0.0),
198 
199 	fShrinkToBounds(true),
200 	fStretchToBounds(false),
201 	fFitToBoundsZoom(1.0),
202 	fFullScreen(false),
203 	fScrollingBitmap(false),
204 	fCreatingSelection(false),
205 	fFirstPoint(0.0, 0.0),
206 	fSelectionMode(false),
207 	fAnimateSelection(true),
208 	fHasSelection(false),
209 	fSlideShow(false),
210 	fSlideShowDelay(3 * 10), // 3 seconds
211 	fSlideShowCountDown(0),
212 	fShowCaption(false),
213 	fShowingPopUpMenu(false),
214 	fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
215 	fIsActiveWin(true),
216 	fProgressWindow(NULL)
217 {
218 	ShowImageSettings* settings;
219 	settings = my_app->Settings();
220 	if (settings->Lock()) {
221 		fShrinkToBounds = settings->GetBool("ShrinksToBounds", fShrinkToBounds);
222 		fStretchToBounds = settings->GetBool("ZoomToBounds", fStretchToBounds);
223 		fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
224 		fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
225 		settings->Unlock();
226 	}
227 
228 	SetViewColor(B_TRANSPARENT_COLOR);
229 	SetHighColor(kBorderColor);
230 	SetLowColor(0, 0, 0);
231 }
232 
233 
234 ShowImageView::~ShowImageView()
235 {
236 	_DeleteBitmap();
237 }
238 
239 
240 void
241 ShowImageView::_AnimateSelection(bool enabled)
242 {
243 	fAnimateSelection = enabled;
244 }
245 
246 
247 void
248 ShowImageView::Pulse()
249 {
250 	// animate marching ants
251 	if (fHasSelection && fAnimateSelection && fIsActiveWin) {
252 		fSelectionBox.Animate();
253 		fSelectionBox.Draw(this, Bounds());
254 	}
255 	if (fSlideShow) {
256 		fSlideShowCountDown --;
257 		if (fSlideShowCountDown <= 0) {
258 			fSlideShowCountDown = fSlideShowDelay;
259 			if (!NextFile()) {
260 				_FirstFile();
261 			}
262 		}
263 	}
264 
265 	// Hide cursor in full screen mode
266 	if (fFullScreen && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) {
267 		if (fHideCursorCountDown <= 0)
268 			be_app->ObscureCursor();
269 		else
270 			fHideCursorCountDown--;
271 	}
272 }
273 
274 
275 bool
276 ShowImageView::_IsImage(const entry_ref *ref)
277 {
278 	if (ref == NULL || !entry_ref_is_file(ref))
279 		return false;
280 
281 	BFile file(ref, B_READ_ONLY);
282 	if (file.InitCheck() != B_OK)
283 		return false;
284 
285 	BTranslatorRoster *roster = BTranslatorRoster::Default();
286 	if (!roster)
287 		return false;
288 
289 	BMessage ioExtension;
290 	if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
291 		return false;
292 
293 	translator_info info;
294 	memset(&info, 0, sizeof(translator_info));
295 	if (roster->Identify(&file, &ioExtension, &info, 0, NULL,
296 		B_TRANSLATOR_BITMAP) != B_OK)
297 		return false;
298 
299 	return true;
300 }
301 
302 
303 void
304 ShowImageView::SetTrackerMessenger(const BMessenger& trackerMessenger)
305 {
306 	fTrackerMessenger = trackerMessenger;
307 }
308 
309 
310 void
311 ShowImageView::_SendMessageToWindow(BMessage *message)
312 {
313 	BMessenger msgr(Window());
314 	msgr.SendMessage(message);
315 }
316 
317 
318 void
319 ShowImageView::_SendMessageToWindow(uint32 code)
320 {
321 	BMessage message(code);
322 	_SendMessageToWindow(&message);
323 }
324 
325 
326 //! send message to parent about new image
327 void
328 ShowImageView::_Notify()
329 {
330 	BMessage msg(MSG_UPDATE_STATUS);
331 
332 	msg.AddString("status", fImageType.String());
333 	msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
334 	msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);
335 
336 	msg.AddInt32("colors", fBitmap->ColorSpace());
337 	_SendMessageToWindow(&msg);
338 
339 	FixupScrollBars();
340 	Invalidate();
341 }
342 
343 
344 void
345 ShowImageView::_UpdateStatusText()
346 {
347 	BMessage msg(MSG_UPDATE_STATUS_TEXT);
348 	BString status_to_send = fImageType;
349 
350 	if (fHasSelection) {
351 		char size[50];
352 		sprintf(size, " (%.0fx%.0f)",
353 			fSelectionBox.Bounds().Width() + 1.0,
354 			fSelectionBox.Bounds().Height() + 1.0);
355 		status_to_send << size;
356 	}
357 
358 	msg.AddString("status", status_to_send.String());
359 	_SendMessageToWindow(&msg);
360 }
361 
362 
363 void
364 ShowImageView::_DeleteBitmap()
365 {
366 	_DeleteSelectionBitmap();
367 
368 	if (fDisplayBitmap != fBitmap)
369 		delete fDisplayBitmap;
370 	fDisplayBitmap = NULL;
371 
372 	delete fBitmap;
373 	fBitmap = NULL;
374 }
375 
376 
377 void
378 ShowImageView::_DeleteSelectionBitmap()
379 {
380 	delete fSelectionBitmap;
381 	fSelectionBitmap = NULL;
382 }
383 
384 
385 status_t
386 ShowImageView::SetImage(const entry_ref* ref)
387 {
388 	// If no file was specified, load the specified page of
389 	// the current file.
390 	if (ref == NULL)
391 		ref = &fCurrentRef;
392 
393 	BTranslatorRoster *roster = BTranslatorRoster::Default();
394 	if (!roster)
395 		return B_ERROR;
396 
397 	if (!entry_ref_is_file(ref))
398 		return B_ERROR;
399 
400 	BFile file(ref, B_READ_ONLY);
401 	translator_info info;
402 	memset(&info, 0, sizeof(translator_info));
403 	BMessage ioExtension;
404 	if (ref != &fCurrentRef) {
405 		// if new image, reset to first document
406 		fDocumentIndex = 1;
407 	}
408 
409 	if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
410 		return B_ERROR;
411 
412 	BMessage progress(kMsgProgressStatusUpdate);
413 	if (ioExtension.AddMessenger("/progressMonitor", fProgressWindow) == B_OK
414 		&& ioExtension.AddMessage("/progressMessage", &progress) == B_OK)
415 		fProgressWindow->Start();
416 
417 	// Translate image data and create a new ShowImage window
418 
419 	BBitmapStream outstream;
420 
421 	status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
422 		B_TRANSLATOR_BITMAP);
423 	if (status == B_OK) {
424 		status = roster->Translate(&file, &info, &ioExtension, &outstream,
425 			B_TRANSLATOR_BITMAP);
426 	}
427 
428 	fProgressWindow->Stop();
429 
430 	if (status != B_OK)
431 		return status;
432 
433 	BBitmap *newBitmap = NULL;
434 	if (outstream.DetachBitmap(&newBitmap) != B_OK)
435 		return B_ERROR;
436 
437 	// Now that I've successfully loaded the new bitmap,
438 	// I can be sure it is safe to delete the old one,
439 	// and clear everything
440 	fUndo.Clear();
441 	_SetHasSelection(false);
442 	fCreatingSelection = false;
443 	_DeleteBitmap();
444 	fBitmap = newBitmap;
445 	fCurrentRef = *ref;
446 
447 	// prepare the display bitmap
448 	if (fBitmap->ColorSpace() == B_RGBA32)
449 		fDisplayBitmap = compose_checker_background(fBitmap);
450 
451 	if (!fDisplayBitmap)
452 		fDisplayBitmap = fBitmap;
453 
454 	// restore orientation
455 	int32 orientation;
456 	fImageOrientation = k0;
457 	if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
458 			&orientation, sizeof(orientation)) == sizeof(orientation)) {
459 		orientation &= 255;
460 		switch (orientation) {
461 			case k0:
462 				break;
463 			case k90:
464 				_DoImageOperation(ImageProcessor::kRotateClockwise, true);
465 				break;
466 			case k180:
467 				_DoImageOperation(ImageProcessor::kRotateClockwise, true);
468 				_DoImageOperation(ImageProcessor::kRotateClockwise, true);
469 				break;
470 			case k270:
471 				_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
472 				break;
473 			case k0V:
474 				_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
475 				break;
476 			case k90V:
477 				_DoImageOperation(ImageProcessor::kRotateClockwise, true);
478 				_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
479 				break;
480 			case k0H:
481 				_DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true);
482 				break;
483 			case k270V:
484 				_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
485 				_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
486 				break;
487 		}
488 	}
489 
490 	// get the number of documents (pages) if it has been supplied
491 	int32 documentCount = 0;
492 	if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
493 		&& documentCount > 0)
494 		fDocumentCount = documentCount;
495 	else
496 		fDocumentCount = 1;
497 
498 	fImageType = info.name;
499 	fImageMime = info.MIME;
500 
501 	GetPath(&fCaption);
502 	if (fDocumentCount > 1)
503 		fCaption << ", " << fDocumentIndex << "/" << fDocumentCount;
504 
505 	fCaption << ", " << fImageType;
506 
507 	fFitToBoundsZoom = _FitToBoundsZoom();
508 	ResetZoom();
509 
510 	be_roster->AddToRecentDocuments(&fCurrentRef, kApplicationSignature);
511 
512 	_Notify();
513 	return B_OK;
514 }
515 
516 
517 BPoint
518 ShowImageView::ImageToView(BPoint p) const
519 {
520 	p.x = floorf(fZoom * p.x + fBitmapLocationInView.x);
521 	p.y = floorf(fZoom * p.y + fBitmapLocationInView.y);
522 	return p;
523 }
524 
525 
526 BPoint
527 ShowImageView::ViewToImage(BPoint p) const
528 {
529 	p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom);
530 	p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom);
531 	return p;
532 }
533 
534 
535 BRect
536 ShowImageView::ImageToView(BRect r) const
537 {
538 	BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
539 	BPoint rightBottom(r.right, r.bottom);
540 	rightBottom += BPoint(1, 1);
541 	rightBottom = ImageToView(rightBottom);
542 	rightBottom -= BPoint(1, 1);
543 	return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
544 }
545 
546 
547 void
548 ShowImageView::ConstrainToImage(BPoint& point) const
549 {
550 	point.ConstrainTo(fBitmap->Bounds());
551 }
552 
553 
554 void
555 ShowImageView::ConstrainToImage(BRect& rect) const
556 {
557 	rect = rect & fBitmap->Bounds();
558 }
559 
560 
561 void
562 ShowImageView::SetShowCaption(bool show)
563 {
564 	if (fShowCaption != show) {
565 		fShowCaption = show;
566 		_UpdateCaption();
567 	}
568 }
569 
570 
571 void
572 ShowImageView::SetShrinkToBounds(bool enable)
573 {
574 	if (fShrinkToBounds != enable) {
575 		_SettingsSetBool("ShrinksToBounds", enable);
576 		fShrinkToBounds = enable;
577 		if (enable)
578 			SetZoom(fFitToBoundsZoom);
579 	}
580 }
581 
582 
583 void
584 ShowImageView::SetStretchToBounds(bool enable)
585 {
586 	if (fStretchToBounds != enable) {
587 		_SettingsSetBool("ZoomToBounds", enable);
588 		fStretchToBounds = enable;
589 		if (enable)
590 			SetZoom(fFitToBoundsZoom);
591 	}
592 }
593 
594 
595 void
596 ShowImageView::SetFullScreen(bool fullScreen)
597 {
598 	fFullScreen = fullScreen;
599 }
600 
601 
602 BBitmap *
603 ShowImageView::GetBitmap()
604 {
605 	return fBitmap;
606 }
607 
608 
609 void
610 ShowImageView::GetName(BString* outName)
611 {
612 	BEntry entry(&fCurrentRef);
613 	char name[B_FILE_NAME_LENGTH];
614 	if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK)
615 		outName->SetTo("");
616 	else
617 		outName->SetTo(name);
618 }
619 
620 
621 void
622 ShowImageView::GetPath(BString *outPath)
623 {
624 	BEntry entry(&fCurrentRef);
625 	BPath path;
626 	if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK)
627 		outPath->SetTo("");
628 	else
629 		outPath->SetTo(path.Path());
630 }
631 
632 
633 void
634 ShowImageView::SetScaleBilinear(bool enabled)
635 {
636 	if (fScaleBilinear != enabled) {
637 		_SettingsSetBool("ScaleBilinear", enabled);
638 		fScaleBilinear = enabled;
639 		Invalidate();
640 	}
641 }
642 
643 
644 void
645 ShowImageView::AttachedToWindow()
646 {
647 	ResetZoom();
648 	fUndo.SetWindow(Window());
649 	FixupScrollBars();
650 
651 	fProgressWindow = new ProgressWindow(Window());
652 }
653 
654 
655 void
656 ShowImageView::DetachedFromWindow()
657 {
658 	fProgressWindow->Lock();
659 	fProgressWindow->Quit();
660 }
661 
662 
663 bool
664 ShowImageView::_ShouldShrink() const
665 {
666 	return fShrinkToBounds && fBitmap->Bounds().Width() > Bounds().Width()
667 		&& fBitmap->Bounds().Height() > Bounds().Height();
668 }
669 
670 
671 bool
672 ShowImageView::_ShouldStretch() const
673 {
674 	return fStretchToBounds && fBitmap->Bounds().Width() < Bounds().Width()
675 		&& fBitmap->Bounds().Height() < Bounds().Height();
676 }
677 
678 
679 float
680 ShowImageView::_FitToBoundsZoom() const
681 {
682 	// the width/height of the bitmap (in pixels)
683 	float bitmapWidth = fBitmap->Bounds().Width() + 1;
684 	float bitmapHeight = fBitmap->Bounds().Height() + 1;
685 
686 	// the available width/height for layouting the bitmap (in pixels)
687 	float width = Bounds().Width() + 1;
688 	float height = Bounds().Height() + 1;
689 
690 	float zoom = width / bitmapWidth;
691 
692 	if (zoom * bitmapHeight <= height)
693 		return zoom;
694 
695 	return height / bitmapHeight;
696 }
697 
698 
699 BRect
700 ShowImageView::_AlignBitmap()
701 {
702 	BRect rect(fBitmap->Bounds());
703 
704 	// the width/height of the bitmap (in pixels)
705 	float bitmapWidth = rect.Width() + 1;
706 	float bitmapHeight = rect.Height() + 1;
707 
708 	// the available width/height for layouting the bitmap (in pixels)
709 	float width = Bounds().Width() + 1;
710 	float height = Bounds().Height() + 1;
711 
712 	if (width == 0 || height == 0)
713 		return rect;
714 
715 	// zoom image
716 	rect.right = floorf(bitmapWidth * fZoom) - 1;
717 	rect.bottom = floorf(bitmapHeight * fZoom) - 1;
718 
719 	// update the bitmap size after the zoom
720 	bitmapWidth = rect.Width() + 1.0;
721 	bitmapHeight = rect.Height() + 1.0;
722 
723 	// always align in the center if the bitmap is smaller than the window
724 	if (width > bitmapWidth)
725 		rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);
726 
727 	if (height > bitmapHeight)
728 		rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));
729 
730 	return rect;
731 }
732 
733 
734 void
735 ShowImageView::_DrawBackground(BRect border)
736 {
737 	BRect bounds(Bounds());
738 	// top
739 	FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW);
740 	// left
741 	FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW);
742 	// right
743 	FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
744 	// bottom
745 	FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW);
746 }
747 
748 
749 void
750 ShowImageView::_LayoutCaption(BFont &font, BPoint &pos, BRect &rect)
751 {
752 	font_height fontHeight;
753 	float width, height;
754 	BRect bounds(Bounds());
755 	font = be_plain_font;
756 	width = font.StringWidth(fCaption.String());
757 	font.GetHeight(&fontHeight);
758 	height = fontHeight.ascent + fontHeight.descent;
759 	// center text horizontally
760 	pos.x = (bounds.left + bounds.right - width) / 2;
761 	// flush bottom
762 	pos.y = bounds.bottom - fontHeight.descent - 7;
763 
764 	// background rectangle
765 	rect.Set(0, 0, width + 4, height + 4);
766 	rect.OffsetTo(pos);
767 	rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
768 }
769 
770 
771 void
772 ShowImageView::_DrawCaption()
773 {
774 	BFont font;
775 	BPoint position;
776 	BRect rect;
777 	_LayoutCaption(font, position, rect);
778 
779 	PushState();
780 
781 	// draw background
782 	SetDrawingMode(B_OP_ALPHA);
783 	SetHighColor(255, 255, 255, 160);
784 	FillRect(rect);
785 
786 	// draw text
787 	SetDrawingMode(B_OP_OVER);
788 	SetFont(&font);
789 	SetLowColor(B_TRANSPARENT_COLOR);
790 	SetHighColor(0, 0, 0);
791 	DrawString(fCaption.String(), position);
792 
793 	PopState();
794 }
795 
796 
797 void
798 ShowImageView::_UpdateCaption()
799 {
800 	BFont font;
801 	BPoint pos;
802 	BRect rect;
803 	_LayoutCaption(font, pos, rect);
804 
805 	// draw over portion of image where caption is located
806 	BRegion clip(rect);
807 	PushState();
808 	ConstrainClippingRegion(&clip);
809 	Draw(rect);
810 	PopState();
811 }
812 
813 
814 void
815 ShowImageView::_DrawImage(BRect rect)
816 {
817 	// TODO: fix composing of fBitmap with other bitmaps
818 	// with regard to alpha channel
819 	if (!fDisplayBitmap)
820 		fDisplayBitmap = fBitmap;
821 
822 	uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0;
823 	DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options);
824 }
825 
826 
827 void
828 ShowImageView::Draw(BRect updateRect)
829 {
830 	if (fBitmap == NULL)
831 		return;
832 
833 	if (IsPrinting()) {
834 		DrawBitmap(fBitmap);
835 		return;
836 	}
837 
838 	BRect rect = _AlignBitmap();
839 	fBitmapLocationInView.x = floorf(rect.left);
840 	fBitmapLocationInView.y = floorf(rect.top);
841 
842 	_DrawBackground(rect);
843 	_DrawImage(rect);
844 
845 	if (fShowCaption)
846 		_DrawCaption();
847 
848 	if (fHasSelection) {
849 		if (fSelectionBitmap != NULL) {
850 			BRect srcRect;
851 			BRect dstRect;
852 			_GetSelectionMergeRects(srcRect, dstRect);
853 			dstRect = ImageToView(dstRect);
854 			DrawBitmap(fSelectionBitmap, srcRect, dstRect);
855 		}
856 		fSelectionBox.Draw(this, updateRect);
857 	}
858 }
859 
860 
861 void
862 ShowImageView::FrameResized(float /*width*/, float /*height*/)
863 {
864 	fFitToBoundsZoom = _FitToBoundsZoom();
865 	SetZoom(_ShouldStretch() ? fFitToBoundsZoom : fZoom);
866 }
867 
868 
869 BBitmap*
870 ShowImageView::_CopySelection(uchar alpha, bool imageSize)
871 {
872 	bool hasAlpha = alpha != 255;
873 
874 	if (!fHasSelection)
875 		return NULL;
876 
877 	BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN);
878 	if (!imageSize) {
879 		// scale image to view size
880 		rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
881 		rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
882 	}
883 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
884 	BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
885 		: fBitmap->ColorSpace(), true);
886 	if (bitmap == NULL || !bitmap->IsValid()) {
887 		delete bitmap;
888 		return NULL;
889 	}
890 
891 	if (bitmap->Lock()) {
892 		bitmap->AddChild(&view);
893 #ifdef __HAIKU__
894 		// On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
895 		// Don't know if it's better to fix it or not (stippi).
896 		if (hasAlpha) {
897 			view.SetHighColor(0, 0, 0, 0);
898 			view.FillRect(view.Bounds());
899 			view.SetDrawingMode(B_OP_ALPHA);
900 			view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
901 			view.SetHighColor(0, 0, 0, alpha);
902 		}
903 		if (fSelectionBitmap) {
904 			view.DrawBitmap(fSelectionBitmap,
905 				fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
906 		} else
907 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
908 #else
909 		if (fSelectionBitmap) {
910 			view.DrawBitmap(fSelectionBitmap,
911 				fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
912 		} else
913 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
914 		if (hasAlpha) {
915 			view.SetDrawingMode(B_OP_SUBTRACT);
916 			view.SetHighColor(0, 0, 0, 255 - alpha);
917 			view.FillRect(rect, B_SOLID_HIGH);
918 		}
919 #endif
920 		view.Sync();
921 		bitmap->RemoveChild(&view);
922 		bitmap->Unlock();
923 	}
924 
925 	return bitmap;
926 }
927 
928 
929 bool
930 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
931 {
932 	BTranslatorRoster *roster = BTranslatorRoster::Default();
933 	if (roster == NULL)
934 		return false;
935 
936 	// add the current image mime first, will make it the preferred format on
937 	// left mouse drag
938 	msg->AddString("be:types", fImageMime);
939 	msg->AddString("be:filetypes", fImageMime);
940 	msg->AddString("be:type_descriptions", fImageType);
941 
942 	bool foundOther = false;
943 	bool foundCurrent = false;
944 
945 	int32 infoCount;
946 	translator_info* info;
947 	BBitmapStream stream(bitmap);
948 	if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
949 		for (int32 i = 0; i < infoCount; i++) {
950 			const translation_format* formats;
951 			int32 count;
952 			roster->GetOutputFormats(info[i].translator, &formats, &count);
953 			for (int32 j = 0; j < count; j++) {
954 				if (fImageMime == formats[j].MIME) {
955 					foundCurrent = true;
956 				} else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
957 					foundOther = true;
958 					// needed to send data in message
959 					msg->AddString("be:types", formats[j].MIME);
960 					// needed to pass data via file
961 					msg->AddString("be:filetypes", formats[j].MIME);
962 					msg->AddString("be:type_descriptions", formats[j].name);
963 				}
964 			}
965 		}
966 	}
967 	stream.DetachBitmap(&bitmap);
968 
969 	if (!foundCurrent) {
970 		msg->RemoveData("be:types", 0);
971 		msg->RemoveData("be:filetypes", 0);
972 		msg->RemoveData("be:type_descriptions", 0);
973 	}
974 
975 	return foundOther || foundCurrent;
976 }
977 
978 
979 void
980 ShowImageView::_BeginDrag(BPoint sourcePoint)
981 {
982 	BBitmap* bitmap = _CopySelection(128, false);
983 	if (bitmap == NULL)
984 		return;
985 
986 	SetMouseEventMask(B_POINTER_EVENTS);
987 
988 	// fill the drag message
989 	BMessage drag(B_SIMPLE_DATA);
990 	drag.AddInt32("be:actions", B_COPY_TARGET);
991 	drag.AddString("be:clip_name", "Bitmap Clip");
992 	// ShowImage specific fields
993 	drag.AddPoint("be:_source_point", sourcePoint);
994 	drag.AddRect("be:_frame", fSelectionBox.Bounds());
995 	if (_AddSupportedTypes(&drag, bitmap)) {
996 		// we also support "Passing Data via File" protocol
997 		drag.AddString("be:types", B_FILE_MIME_TYPE);
998 		// avoid flickering of dragged bitmap caused by drawing into the window
999 		_AnimateSelection(false);
1000 		// only use a transparent bitmap on selections less than 400x400
1001 		// (taking into account zooming)
1002 		BRect selectionRect = fSelectionBox.Bounds();
1003 		if (selectionRect.Width() * fZoom < 400.0
1004 			&& selectionRect.Height() * fZoom < 400.0) {
1005 			sourcePoint -= selectionRect.LeftTop();
1006 			sourcePoint.x *= fZoom;
1007 			sourcePoint.y *= fZoom;
1008 			// DragMessage takes ownership of bitmap
1009 			DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
1010 			bitmap = NULL;
1011 		} else {
1012 			delete bitmap;
1013 			// Offset and scale the rect
1014 			BRect rect(selectionRect);
1015 			rect = ImageToView(rect);
1016 			rect.InsetBy(-1, -1);
1017 			DragMessage(&drag, rect);
1018 		}
1019 	}
1020 }
1021 
1022 
1023 bool
1024 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type,
1025 	translation_format* format)
1026 {
1027 	bool found = false;
1028 
1029 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1030 	if (roster == NULL)
1031 		return false;
1032 
1033 	BBitmapStream stream(bitmap);
1034 
1035 	translator_info *outInfo;
1036 	int32 outNumInfo;
1037 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
1038 		for (int32 i = 0; i < outNumInfo; i++) {
1039 			const translation_format *fmts;
1040 			int32 num_fmts;
1041 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
1042 			for (int32 j = 0; j < num_fmts; j++) {
1043 				if (strcmp(fmts[j].MIME, type) == 0) {
1044 					*format = fmts[j];
1045 					found = true;
1046 					break;
1047 				}
1048 			}
1049 		}
1050 	}
1051 	stream.DetachBitmap(&bitmap);
1052 	return found;
1053 }
1054 
1055 
1056 #undef B_TRANSLATE_CONTEXT
1057 #define B_TRANSLATE_CONTEXT "SaveToFile"
1058 
1059 
1060 void
1061 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
1062 	const translation_format* format)
1063 {
1064 	if (bitmap == NULL) {
1065 		// If no bitmap is supplied, write out the whole image
1066 		bitmap = fBitmap;
1067 	}
1068 
1069 	BBitmapStream stream(bitmap);
1070 
1071 	bool loop = true;
1072 	while (loop) {
1073 		BTranslatorRoster *roster = BTranslatorRoster::Default();
1074 		if (!roster)
1075 			break;
1076 		// write data
1077 		BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
1078 		if (file.InitCheck() != B_OK)
1079 			break;
1080 		if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
1081 			break;
1082 		// set mime type
1083 		BNodeInfo info(&file);
1084 		if (info.InitCheck() == B_OK)
1085 			info.SetType(format->MIME);
1086 
1087 		loop = false;
1088 			// break out of loop gracefully (indicates no errors)
1089 	}
1090 	if (loop) {
1091 		// If loop terminated because of a break, there was an error
1092 		char buffer[512];
1093 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not "
1094 			"be written."), name);
1095 		BAlert *palert = new BAlert("", buffer, B_TRANSLATE("OK"));
1096 		palert->Go();
1097 	}
1098 
1099 	stream.DetachBitmap(&bitmap);
1100 		// Don't allow the bitmap to be deleted, this is
1101 		// especially important when using fBitmap as the bitmap
1102 }
1103 
1104 
1105 void
1106 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format)
1107 {
1108 	BMessage reply(B_MIME_DATA);
1109 	BBitmapStream stream(bitmap); // destructor deletes bitmap
1110 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1111 	BMallocIO memStream;
1112 	if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
1113 		reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength());
1114 		msg->SendReply(&reply);
1115 	}
1116 }
1117 
1118 
1119 void
1120 ShowImageView::_HandleDrop(BMessage* msg)
1121 {
1122 	entry_ref dirRef;
1123 	BString name, type;
1124 	bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
1125 		&& msg->FindRef("directory", &dirRef) == B_OK
1126 		&& msg->FindString("name", &name) == B_OK;
1127 
1128 	bool sendInMessage = !saveToFile
1129 		&& msg->FindString("be:types", &type) == B_OK;
1130 
1131 	BBitmap* bitmap = _CopySelection();
1132 	if (bitmap == NULL)
1133 		return;
1134 
1135 	translation_format format;
1136 	if (!_OutputFormatForType(bitmap, type.String(), &format)) {
1137 		delete bitmap;
1138 		return;
1139 	}
1140 
1141 	if (saveToFile) {
1142 		BDirectory dir(&dirRef);
1143 		SaveToFile(&dir, name.String(), bitmap, &format);
1144 		delete bitmap;
1145 	} else if (sendInMessage) {
1146 		_SendInMessage(msg, bitmap, &format);
1147 	} else {
1148 		delete bitmap;
1149 	}
1150 }
1151 
1152 
1153 void
1154 ShowImageView::_ScrollBitmap(BPoint point)
1155 {
1156 	point = ConvertToScreen(point);
1157 	BPoint delta = fFirstPoint - point;
1158 	fFirstPoint = point;
1159 	_ScrollRestrictedBy(delta.x, delta.y);
1160 }
1161 
1162 
1163 void
1164 ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect,
1165 	BRect& dstRect)
1166 {
1167 	// Constrain dstRect to target image size and apply the same edge offsets
1168 	// to the srcRect.
1169 
1170 	dstRect = selection;
1171 
1172 	BRect clippedDstRect(dstRect);
1173 	ConstrainToImage(clippedDstRect);
1174 
1175 	srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN);
1176 
1177 	srcRect.left += clippedDstRect.left - dstRect.left;
1178 	srcRect.top += clippedDstRect.top - dstRect.top;
1179 	srcRect.right += clippedDstRect.right - dstRect.right;
1180 	srcRect.bottom += clippedDstRect.bottom - dstRect.bottom;
1181 
1182 	dstRect = clippedDstRect;
1183 }
1184 
1185 
1186 void
1187 ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
1188 {
1189 	_GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
1190 }
1191 
1192 
1193 void
1194 ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection)
1195 {
1196 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1197 	BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(),
1198 		fBitmap->ColorSpace(), true);
1199 	if (bitmap == NULL || !bitmap->IsValid()) {
1200 		delete bitmap;
1201 		return;
1202 	}
1203 
1204 	if (bitmap->Lock()) {
1205 		bitmap->AddChild(&view);
1206 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1207 		BRect srcRect;
1208 		BRect dstRect;
1209 		_GetMergeRects(merge, selection, srcRect, dstRect);
1210 		view.DrawBitmap(merge, srcRect, dstRect);
1211 
1212 		view.Sync();
1213 		bitmap->RemoveChild(&view);
1214 		bitmap->Unlock();
1215 
1216 		_DeleteBitmap();
1217 		fBitmap = bitmap;
1218 
1219 		_SendMessageToWindow(MSG_MODIFIED);
1220 	} else
1221 		delete bitmap;
1222 }
1223 
1224 
1225 void
1226 ShowImageView::MouseDown(BPoint position)
1227 {
1228 	MakeFocus(true);
1229 
1230 	BPoint point = ViewToImage(position);
1231 	uint32 buttons = 0;
1232 	if (Window() != NULL && Window()->CurrentMessage() != NULL)
1233 		buttons = Window()->CurrentMessage()->FindInt32("buttons");
1234 
1235 	if (fHasSelection && fSelectionBox.Bounds().Contains(point)
1236 		&& (buttons
1237 				& (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
1238 		if (!fSelectionBitmap)
1239 			fSelectionBitmap = _CopySelection();
1240 
1241 		_BeginDrag(point);
1242 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON
1243 			&& (fSelectionMode
1244 				|| (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) {
1245 		// begin new selection
1246 		_SetHasSelection(true);
1247 		fCreatingSelection = true;
1248 		SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1249 		ConstrainToImage(point);
1250 		fFirstPoint = point;
1251 		fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1252 		fSelectionBox.SetBounds(this, fCopyFromRect);
1253 		Invalidate();
1254 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1255 		_ShowPopUpMenu(ConvertToScreen(position));
1256 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON
1257 		|| buttons == B_TERTIARY_MOUSE_BUTTON) {
1258 		// move image in window
1259 		SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1260 		fScrollingBitmap = true;
1261 		fFirstPoint = ConvertToScreen(position);
1262 	}
1263 }
1264 
1265 
1266 void
1267 ShowImageView::_UpdateSelectionRect(BPoint point, bool final)
1268 {
1269 	BRect oldSelection = fCopyFromRect;
1270 	point = ViewToImage(point);
1271 	ConstrainToImage(point);
1272 	fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
1273 	fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
1274 	fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
1275 	fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
1276 	fSelectionBox.SetBounds(this, fCopyFromRect);
1277 
1278 	if (final) {
1279 		// selection must be at least 2 pixels wide or 2 pixels tall
1280 		if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
1281 			_SetHasSelection(false);
1282 	} else
1283 		_UpdateStatusText();
1284 
1285 	if (oldSelection != fCopyFromRect || !fHasSelection) {
1286 		BRect updateRect;
1287 		updateRect = oldSelection | fCopyFromRect;
1288 		updateRect = ImageToView(updateRect);
1289 		updateRect.InsetBy(-1, -1);
1290 		Invalidate(updateRect);
1291 	}
1292 }
1293 
1294 
1295 void
1296 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message)
1297 {
1298 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1299 	if (fCreatingSelection)
1300 		_UpdateSelectionRect(point, false);
1301 	else if (fScrollingBitmap)
1302 		_ScrollBitmap(point);
1303 }
1304 
1305 
1306 void
1307 ShowImageView::MouseUp(BPoint point)
1308 {
1309 	if (fCreatingSelection) {
1310 		_UpdateSelectionRect(point, true);
1311 		fCreatingSelection = false;
1312 	} else if (fScrollingBitmap) {
1313 		_ScrollBitmap(point);
1314 		fScrollingBitmap = false;
1315 	}
1316 	_AnimateSelection(true);
1317 }
1318 
1319 
1320 float
1321 ShowImageView::_LimitToRange(float v, orientation o, bool absolute)
1322 {
1323 	BScrollBar* psb = ScrollBar(o);
1324 	if (psb) {
1325 		float min, max, pos;
1326 		pos = v;
1327 		if (!absolute)
1328 			pos += psb->Value();
1329 
1330 		psb->GetRange(&min, &max);
1331 		if (pos < min)
1332 			pos = min;
1333 		else if (pos > max)
1334 			pos = max;
1335 
1336 		v = pos;
1337 		if (!absolute)
1338 			v -= psb->Value();
1339 	}
1340 	return v;
1341 }
1342 
1343 
1344 void
1345 ShowImageView::_ScrollRestricted(float x, float y, bool absolute)
1346 {
1347 	if (x != 0)
1348 		x = _LimitToRange(x, B_HORIZONTAL, absolute);
1349 
1350 	if (y != 0)
1351 		y = _LimitToRange(y, B_VERTICAL, absolute);
1352 
1353 	// hide the caption when using mouse wheel
1354 	// in full screen mode
1355 	// to prevent the caption from dirtying up the image
1356 	// during scrolling.
1357 	bool caption = fShowCaption;
1358 	if (caption) {
1359 		fShowCaption = false;
1360 		_UpdateCaption();
1361 	}
1362 
1363 	ScrollBy(x, y);
1364 
1365 	if (caption) {
1366 		// show the caption again
1367 		fShowCaption = true;
1368 		_UpdateCaption();
1369 	}
1370 }
1371 
1372 
1373 // XXX method is not unused
1374 void
1375 ShowImageView::_ScrollRestrictedTo(float x, float y)
1376 {
1377 	_ScrollRestricted(x, y, true);
1378 }
1379 
1380 
1381 void
1382 ShowImageView::_ScrollRestrictedBy(float x, float y)
1383 {
1384 	_ScrollRestricted(x, y, false);
1385 }
1386 
1387 
1388 void
1389 ShowImageView::KeyDown(const char* bytes, int32 numBytes)
1390 {
1391 	if (numBytes != 1) {
1392 		BView::KeyDown(bytes, numBytes);
1393 		return;
1394 	}
1395 
1396 	bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0;
1397 
1398 	switch (*bytes) {
1399 		case B_DOWN_ARROW:
1400 			if (shiftKeyDown)
1401 				_ScrollRestrictedBy(0, 10);
1402 			else
1403 				_SendMessageToWindow(MSG_FILE_NEXT);
1404 			break;
1405 		case B_RIGHT_ARROW:
1406 			if (shiftKeyDown)
1407 				_ScrollRestrictedBy(10, 0);
1408 			else
1409 				_SendMessageToWindow(MSG_FILE_NEXT);
1410 			break;
1411 		case B_UP_ARROW:
1412 			if (shiftKeyDown)
1413 				_ScrollRestrictedBy(0, -10);
1414 			else
1415 				_SendMessageToWindow(MSG_FILE_PREV);
1416 			break;
1417 		case B_LEFT_ARROW:
1418 			if (shiftKeyDown)
1419 				_ScrollRestrictedBy(-10, 0);
1420 			else
1421 				_SendMessageToWindow(MSG_FILE_PREV);
1422 			break;
1423 		case B_BACKSPACE:
1424 			_SendMessageToWindow(MSG_FILE_PREV);
1425 			break;
1426 		case B_HOME:
1427 			break;
1428 		case B_END:
1429 			break;
1430 		case B_SPACE:
1431 			_ToggleSlideShow();
1432 			break;
1433 		case B_ESCAPE:
1434 			// stop slide show
1435 			if (fSlideShow)
1436 				_ToggleSlideShow();
1437 
1438 			_ExitFullScreen();
1439 
1440 			ClearSelection();
1441 			break;
1442 		case B_DELETE:
1443 		{
1444 			// Move image to Trash
1445 			BMessage trash(BPrivate::kMoveToTrash);
1446 			trash.AddRef("refs", &fCurrentRef);
1447 			// We create our own messenger because the member fTrackerMessenger
1448 			// could be invalid
1449 			BMessenger tracker(kTrackerSignature);
1450 			if (tracker.SendMessage(&trash) == B_OK)
1451 				if (!NextFile()) {
1452 					// This is the last (or only file) in this directory,
1453 					// close the window
1454 					_SendMessageToWindow(B_QUIT_REQUESTED);
1455 				}
1456 			break;
1457 		}
1458 		case '+':
1459 		case '=':
1460 			ZoomIn();
1461 			break;
1462 		case '-':
1463 			ZoomOut();
1464 			break;
1465 		case '[':
1466 			Rotate(270);
1467 			break;
1468 		case ']':
1469 			Rotate(90);
1470 			break;
1471 	}
1472 }
1473 
1474 
1475 void
1476 ShowImageView::_MouseWheelChanged(BMessage *msg)
1477 {
1478 	// The BeOS driver does not currently support
1479 	// X wheel scrolling, therefore, dx is zero.
1480 	// |dy| is the number of notches scrolled up or down.
1481 	// When the wheel is scrolled down (towards the user) dy > 0
1482 	// When the wheel is scrolled up (away from the user) dy < 0
1483 	const float kscrollBy = 40;
1484 	float dy, dx;
1485 	float x, y;
1486 	x = 0; y = 0;
1487 	if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK)
1488 		x = dx * kscrollBy;
1489 	if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK)
1490 		y = dy * kscrollBy;
1491 
1492 	if ((modifiers() & B_SHIFT_KEY) != 0)
1493 		_ScrollRestrictedBy(x, y);
1494 	else if ((modifiers() & B_COMMAND_KEY) != 0)
1495 		_ScrollRestrictedBy(y, x);
1496 	else {
1497 		// Zoom in spot
1498 		BPoint where;
1499 		uint32 buttons;
1500 		GetMouse(&where, &buttons);
1501 
1502 		if (dy < 0)
1503 			ZoomIn(where);
1504 		else if (dy > 0)
1505 			ZoomOut(where);
1506 	}
1507 }
1508 
1509 
1510 void
1511 ShowImageView::_ShowPopUpMenu(BPoint screen)
1512 {
1513 	if (!fShowingPopUpMenu) {
1514 	  PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
1515 
1516 	  ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
1517 	  if (showImage)
1518 		  showImage->BuildContextMenu(menu);
1519 
1520 	  screen += BPoint(2, 2);
1521 	  menu->Go(screen, true, true, true);
1522 	  fShowingPopUpMenu = true;
1523 	}
1524 }
1525 
1526 
1527 void
1528 ShowImageView::_SettingsSetBool(const char* name, bool value)
1529 {
1530 	ShowImageSettings* settings;
1531 	settings = my_app->Settings();
1532 	if (settings->Lock()) {
1533 		settings->SetBool(name, value);
1534 		settings->Unlock();
1535 	}
1536 }
1537 
1538 
1539 void
1540 ShowImageView::MessageReceived(BMessage* message)
1541 {
1542 	switch (message->what) {
1543 		case B_SIMPLE_DATA:
1544 			if (message->WasDropped()) {
1545 				uint32 type;
1546 				int32 count;
1547 				status_t ret = message->GetInfo("refs", &type, &count);
1548 				if (ret == B_OK && type == B_REF_TYPE) {
1549 					// If file was dropped, open it as the selection
1550 					entry_ref ref;
1551 					if (message->FindRef("refs", 0, &ref) == B_OK)
1552 						SetImage(&ref);
1553 				}
1554 			}
1555 			break;
1556 
1557 		case B_COPY_TARGET:
1558 			_HandleDrop(message);
1559 			break;
1560 		case B_MOUSE_WHEEL_CHANGED:
1561 			_MouseWheelChanged(message);
1562 			break;
1563 
1564 		case kMsgPopUpMenuClosed:
1565 			fShowingPopUpMenu = false;
1566 			break;
1567 
1568 		default:
1569 			BView::MessageReceived(message);
1570 			break;
1571 	}
1572 }
1573 
1574 
1575 void
1576 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength)
1577 {
1578 	float prop, range;
1579 	BScrollBar *psb;
1580 
1581 	psb = ScrollBar(o);
1582 	if (psb) {
1583 		range = bitmapLength - viewLength;
1584 		if (range < 0.0) {
1585 			range = 0.0;
1586 		}
1587 		prop = viewLength / bitmapLength;
1588 		if (prop > 1.0) {
1589 			prop = 1.0;
1590 		}
1591 		psb->SetRange(0, range);
1592 		psb->SetProportion(prop);
1593 		psb->SetSteps(10, 100);
1594 	}
1595 }
1596 
1597 
1598 void
1599 ShowImageView::FixupScrollBars()
1600 {
1601 	BRect viewRect = Bounds();
1602 	BRect bitmapRect;
1603 	if (fBitmap != NULL) {
1604 		bitmapRect = _AlignBitmap();
1605 		bitmapRect.OffsetTo(0, 0);
1606 	}
1607 
1608 	FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width());
1609 	FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height());
1610 }
1611 
1612 
1613 int32
1614 ShowImageView::CurrentPage()
1615 {
1616 	return fDocumentIndex;
1617 }
1618 
1619 
1620 int32
1621 ShowImageView::PageCount()
1622 {
1623 	return fDocumentCount;
1624 }
1625 
1626 
1627 void
1628 ShowImageView::SetSelectionMode(bool selectionMode)
1629 {
1630 	// The mode only has an effect in MouseDown()
1631 	fSelectionMode = selectionMode;
1632 }
1633 
1634 
1635 void
1636 ShowImageView::Undo()
1637 {
1638 	int32 undoType = fUndo.GetType();
1639 	if (undoType != UNDO_UNDO && undoType != UNDO_REDO)
1640 		return;
1641 
1642 	// backup current selection
1643 	BRect undoneSelRect;
1644 	BBitmap *undoneSelection;
1645 	undoneSelRect = fSelectionBox.Bounds();
1646 	undoneSelection = _CopySelection();
1647 
1648 	if (undoType == UNDO_UNDO) {
1649 		BBitmap *undoRestore;
1650 		undoRestore = fUndo.GetRestoreBitmap();
1651 		if (undoRestore)
1652 			_MergeWithBitmap(undoRestore, fUndo.GetRect());
1653 	}
1654 
1655 	// restore previous image/selection
1656 	BBitmap *undoSelection;
1657 	undoSelection = fUndo.GetSelectionBitmap();
1658 		// NOTE: ShowImageView is responsible for deleting this bitmap
1659 		// (Which it will, as it would with a fSelectionBitmap that it allocated itself)
1660 	if (!undoSelection)
1661 		_SetHasSelection(false);
1662 	else {
1663 		fCopyFromRect = BRect();
1664 		fSelectionBox.SetBounds(this, fUndo.GetRect());
1665 		_SetHasSelection(true);
1666 		fSelectionBitmap = undoSelection;
1667 	}
1668 
1669 	fUndo.Undo(undoneSelRect, NULL, undoneSelection);
1670 
1671 	Invalidate();
1672 }
1673 
1674 
1675 void
1676 ShowImageView::SelectAll()
1677 {
1678 	_SetHasSelection(true);
1679 	fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
1680 		fBitmap->Bounds().Height());
1681 	fSelectionBox.SetBounds(this, fCopyFromRect);
1682 	Invalidate();
1683 }
1684 
1685 
1686 void
1687 ShowImageView::ClearSelection()
1688 {
1689 	if (!fHasSelection)
1690 		return;
1691 
1692 	_SetHasSelection(false);
1693 	Invalidate();
1694 }
1695 
1696 
1697 void
1698 ShowImageView::_SetHasSelection(bool hasSelection)
1699 {
1700 	_DeleteSelectionBitmap();
1701 	fHasSelection = hasSelection;
1702 
1703 	_UpdateStatusText();
1704 
1705 	BMessage msg(MSG_SELECTION);
1706 	msg.AddBool("has_selection", fHasSelection);
1707 	_SendMessageToWindow(&msg);
1708 }
1709 
1710 
1711 void
1712 ShowImageView::CopySelectionToClipboard()
1713 {
1714 	if (!fHasSelection || !be_clipboard->Lock())
1715 		return;
1716 
1717 	be_clipboard->Clear();
1718 
1719 	BMessage* data = be_clipboard->Data();
1720 	if (data != NULL) {
1721 		BBitmap* bitmap = _CopySelection();
1722 		if (bitmap != NULL) {
1723 			BMessage bitmapArchive;
1724 			bitmap->Archive(&bitmapArchive);
1725 			// NOTE: Possibly "image/x-be-bitmap" is more correct.
1726 			// This works with WonderBrush, though, which in turn had been
1727 			// tested with other apps.
1728 			data->AddMessage("image/bitmap", &bitmapArchive);
1729 			data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop());
1730 
1731 			delete bitmap;
1732 
1733 			be_clipboard->Commit();
1734 		}
1735 	}
1736 	be_clipboard->Unlock();
1737 }
1738 
1739 
1740 void
1741 ShowImageView::FirstPage()
1742 {
1743 	if (fDocumentIndex != 1) {
1744 		fDocumentIndex = 1;
1745 		SetImage(NULL);
1746 	}
1747 }
1748 
1749 
1750 void
1751 ShowImageView::LastPage()
1752 {
1753 	if (fDocumentIndex != fDocumentCount) {
1754 		fDocumentIndex = fDocumentCount;
1755 		SetImage(NULL);
1756 	}
1757 }
1758 
1759 
1760 void
1761 ShowImageView::NextPage()
1762 {
1763 	if (fDocumentIndex < fDocumentCount) {
1764 		fDocumentIndex++;
1765 		SetImage(NULL);
1766 	}
1767 }
1768 
1769 
1770 void
1771 ShowImageView::PrevPage()
1772 {
1773 	if (fDocumentIndex > 1) {
1774 		fDocumentIndex--;
1775 		SetImage(NULL);
1776 	}
1777 }
1778 
1779 
1780 int
1781 ShowImageView::_CompareEntries(const void* a, const void* b)
1782 {
1783 	entry_ref *r1, *r2;
1784 	r1 = *(entry_ref**)a;
1785 	r2 = *(entry_ref**)b;
1786 	return strcasecmp(r1->name, r2->name);
1787 }
1788 
1789 
1790 void
1791 ShowImageView::GoToPage(int32 page)
1792 {
1793 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
1794 		fDocumentIndex = page;
1795 		SetImage(NULL);
1796 	}
1797 }
1798 
1799 
1800 void
1801 ShowImageView::_FreeEntries(BList* entries)
1802 {
1803 	const int32 n = entries->CountItems();
1804 	for (int32 i = 0; i < n; i ++) {
1805 		entry_ref* ref = (entry_ref*)entries->ItemAt(i);
1806 		delete ref;
1807 	}
1808 	entries->MakeEmpty();
1809 }
1810 
1811 
1812 void
1813 ShowImageView::_SetTrackerSelectionToCurrent()
1814 {
1815 	BMessage setsel(B_SET_PROPERTY);
1816 	setsel.AddSpecifier("Selection");
1817 	setsel.AddRef("data", &fCurrentRef);
1818 	fTrackerMessenger.SendMessage(&setsel);
1819 }
1820 
1821 
1822 bool
1823 ShowImageView::_FindNextImage(entry_ref *in_current, entry_ref *ref, bool next,
1824 	bool rewind)
1825 {
1826 	// Based on GetTrackerWindowFile function from BeMail
1827 	if (!fTrackerMessenger.IsValid())
1828 		return false;
1829 
1830 	//
1831 	//	Ask the Tracker what the next/prev file in the window is.
1832 	//	Continue asking for the next reference until a valid
1833 	//	image is found.
1834 	//
1835 	entry_ref nextRef = *in_current;
1836 	bool foundRef = false;
1837 	while (!foundRef)
1838 	{
1839 		BMessage request(B_GET_PROPERTY);
1840 		BMessage spc;
1841 		if (rewind)
1842 			spc.what = B_DIRECT_SPECIFIER;
1843 		else if (next)
1844 			spc.what = 'snxt';
1845 		else
1846 			spc.what = 'sprv';
1847 		spc.AddString("property", "Entry");
1848 		if (rewind)
1849 			// if rewinding, ask for the ref to the
1850 			// first item in the directory
1851 			spc.AddInt32("data", 0);
1852 		else
1853 			spc.AddRef("data", &nextRef);
1854 		request.AddSpecifier(&spc);
1855 
1856 		BMessage reply;
1857 		if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
1858 			return false;
1859 		if (reply.FindRef("result", &nextRef) != B_OK)
1860 			return false;
1861 
1862 		if (_IsImage(&nextRef))
1863 			foundRef = true;
1864 
1865 		rewind = false;
1866 			// stop asking for the first ref in the directory
1867 	}
1868 
1869 	*ref = nextRef;
1870 	return foundRef;
1871 }
1872 
1873 bool
1874 ShowImageView::_ShowNextImage(bool next, bool rewind)
1875 {
1876 	entry_ref curRef = fCurrentRef;
1877 	entry_ref imgRef;
1878 	bool found = _FindNextImage(&curRef, &imgRef, next, rewind);
1879 	if (found) {
1880 		// Keep trying to load images until:
1881 		// 1. The image loads successfully
1882 		// 2. The last file in the directory is found (for find next or find first)
1883 		// 3. The first file in the directory is found (for find prev)
1884 		// 4. The call to _FindNextImage fails for any other reason
1885 		while (SetImage(&imgRef) != B_OK) {
1886 			curRef = imgRef;
1887 			found = _FindNextImage(&curRef, &imgRef, next, false);
1888 			if (!found)
1889 				return false;
1890 		}
1891 		_SetTrackerSelectionToCurrent();
1892 		return true;
1893 	}
1894 	return false;
1895 }
1896 
1897 
1898 bool
1899 ShowImageView::NextFile()
1900 {
1901 	return _ShowNextImage(true, false);
1902 }
1903 
1904 
1905 bool
1906 ShowImageView::PrevFile()
1907 {
1908 	return _ShowNextImage(false, false);
1909 }
1910 
1911 
1912 bool
1913 ShowImageView::HasNextFile()
1914 {
1915 	entry_ref ref;
1916 	return _FindNextImage(&fCurrentRef, &ref, true, false);
1917 }
1918 
1919 
1920 bool
1921 ShowImageView::HasPrevFile()
1922 {
1923 	entry_ref ref;
1924 	return _FindNextImage(&fCurrentRef, &ref, false, false);
1925 }
1926 
1927 
1928 bool
1929 ShowImageView::_FirstFile()
1930 {
1931 	return _ShowNextImage(true, true);
1932 }
1933 
1934 
1935 void
1936 ShowImageView::SetZoom(float zoom, BPoint where)
1937 {
1938 	if (zoom > 32)
1939 		zoom = 32;
1940 	if (zoom < fFitToBoundsZoom / 2)
1941 		zoom = fFitToBoundsZoom / 2;
1942 
1943 	if (zoom == fZoom) {
1944 		// window size might have changed
1945 		FixupScrollBars();
1946 		return;
1947 	}
1948 
1949 	// Invalidate before scrolling, as that prevents the app_server
1950 	// to do the scrolling server side
1951 	Invalidate();
1952 
1953 	// zoom to center if not otherwise specified
1954 	BPoint offset;
1955 	if (where.x == -1) {
1956 		where.Set(Bounds().Width() / 2, Bounds().Height() / 2);
1957 		offset = where;
1958 		where += Bounds().LeftTop();
1959 	} else
1960 		offset = where - Bounds().LeftTop();
1961 
1962 	float oldZoom = fZoom;
1963 	fZoom = zoom;
1964 
1965 	FixupScrollBars();
1966 
1967 	if (fBitmap != NULL) {
1968 		offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x;
1969 		offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y;
1970 		ScrollTo(offset);
1971 	}
1972 }
1973 
1974 
1975 void
1976 ShowImageView::ZoomIn(BPoint where)
1977 {
1978 	// snap zoom to "fit to bounds", and "original size"
1979 	float zoom = fZoom * 1.2;
1980 	float zoomSnap = fZoom * 1.25;
1981 	if (fZoom < fFitToBoundsZoom && zoomSnap > fFitToBoundsZoom)
1982 		zoom = fFitToBoundsZoom;
1983 	if (fZoom < 1.0 && zoomSnap > 1.0)
1984 		zoom = 1.0;
1985 
1986 	SetZoom(zoom, where);
1987 }
1988 
1989 
1990 void
1991 ShowImageView::ZoomOut(BPoint where)
1992 {
1993 	// snap zoom to "fit to bounds", and "original size"
1994 	float zoom = fZoom / 1.2;
1995 	float zoomSnap = fZoom / 1.25;
1996 	if (fZoom > fFitToBoundsZoom && zoomSnap < fFitToBoundsZoom)
1997 		zoom = fFitToBoundsZoom;
1998 	if (fZoom > 1.0 && zoomSnap < 1.0)
1999 		zoom = 1.0;
2000 
2001 	SetZoom(zoom, where);
2002 }
2003 
2004 
2005 /*!	Resets the zoom to what it should be when opening an image, depending
2006 	on the current settings.
2007 */
2008 void
2009 ShowImageView::ResetZoom()
2010 {
2011 	if (fBitmap == NULL)
2012 		return;
2013 
2014 	fFitToBoundsZoom = _FitToBoundsZoom();
2015 
2016 	if (_ShouldShrink() || _ShouldStretch())
2017 		SetZoom(fFitToBoundsZoom);
2018 	else
2019 		SetZoom(1.0);
2020 }
2021 
2022 
2023 void
2024 ShowImageView::SetSlideShowDelay(float seconds)
2025 {
2026 	ShowImageSettings* settings;
2027 	int32 delay = (int)(seconds * 10.0);
2028 	if (fSlideShowDelay != delay) {
2029 		// update counter
2030 		fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown);
2031 		if (fSlideShowCountDown <= 0) {
2032 			// show next image on next Pulse()
2033 			fSlideShowCountDown = 1;
2034 		}
2035 		fSlideShowDelay = delay;
2036 		settings = my_app->Settings();
2037 		if (settings->Lock()) {
2038 			settings->SetInt32("SlideShowDelay", fSlideShowDelay);
2039 			settings->Unlock();
2040 		}
2041 	}
2042 }
2043 
2044 
2045 void
2046 ShowImageView::StartSlideShow()
2047 {
2048 	fSlideShow = true;
2049 	fSlideShowCountDown = fSlideShowDelay;
2050 }
2051 
2052 
2053 void
2054 ShowImageView::StopSlideShow()
2055 {
2056 	fSlideShow = false;
2057 }
2058 
2059 
2060 void
2061 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet)
2062 {
2063 	BMessenger msgr;
2064 	ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
2065 	imageProcessor.Start(false);
2066 	BBitmap* bm = imageProcessor.DetachBitmap();
2067 	if (bm == NULL) {
2068 		// operation failed
2069 		return;
2070 	}
2071 
2072 	// update orientation state
2073 	if (op != ImageProcessor::kInvert) {
2074 		// Note: If one of these fails, check its definition in class ImageProcessor.
2075 //		ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations);
2076 //		ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations);
2077 //		ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations);
2078 //		ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations);
2079 		fImageOrientation = fTransformation[op][fImageOrientation];
2080 	}
2081 
2082 	if (!quiet) {
2083 		// write orientation state
2084 		BNode node(&fCurrentRef);
2085 		int32 orientation = fImageOrientation;
2086 		if (orientation != k0) {
2087 			node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
2088 				&orientation, sizeof(orientation));
2089 		} else {
2090 			node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
2091 		}
2092 	}
2093 
2094 	// set new bitmap
2095 	_DeleteBitmap();
2096 	fBitmap = bm;
2097 
2098 	if (!quiet) {
2099 		// remove selection
2100 		_SetHasSelection(false);
2101 		_Notify();
2102 	}
2103 }
2104 
2105 
2106 //! image operation initiated by user
2107 void
2108 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
2109 {
2110 	fUndo.Clear();
2111 	_DoImageOperation(op, quiet);
2112 }
2113 
2114 
2115 void
2116 ShowImageView::Rotate(int degree)
2117 {
2118 	if (degree == 90) {
2119 		_UserDoImageOperation(ImageProcessor::kRotateClockwise);
2120 	} else if (degree == 270) {
2121 		_UserDoImageOperation(ImageProcessor::kRotateCounterClockwise);
2122 	}
2123 }
2124 
2125 
2126 void
2127 ShowImageView::Flip(bool vertical)
2128 {
2129 	if (vertical) {
2130 		_UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
2131 	} else {
2132 		_UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
2133 	}
2134 }
2135 
2136 
2137 void
2138 ShowImageView::ResizeImage(int w, int h)
2139 {
2140 	if (fBitmap == NULL || w < 1 || h < 1)
2141 		return;
2142 
2143 	Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false);
2144 	scaler.Start(false);
2145 	BBitmap* scaled = scaler.DetachBitmap();
2146 	if (scaled == NULL) {
2147 		// operation failed
2148 		return;
2149 	}
2150 
2151 	// remove selection
2152 	_SetHasSelection(false);
2153 	fUndo.Clear();
2154 	_DeleteBitmap();
2155 	fBitmap = scaled;
2156 
2157 	_SendMessageToWindow(MSG_MODIFIED);
2158 
2159 	_Notify();
2160 }
2161 
2162 
2163 void
2164 ShowImageView::_SetIcon(bool clear, icon_size which)
2165 {
2166 	int32 size;
2167 	switch (which) {
2168 		case B_MINI_ICON: size = 16;
2169 			break;
2170 		case B_LARGE_ICON: size = 32;
2171 			break;
2172 		default:
2173 			return;
2174 	}
2175 
2176 	BRect rect(fBitmap->Bounds());
2177 	float s;
2178 	s = size / (rect.Width()+1.0);
2179 
2180 	if (s * (rect.Height()+1.0) <= size) {
2181 		rect.right = size-1;
2182 		rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
2183 		// center vertically
2184 		rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
2185 	} else {
2186 		s = size / (rect.Height()+1.0);
2187 		rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
2188 		rect.bottom = size-1;
2189 		// center horizontally
2190 		rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
2191 	}
2192 
2193 	// scale bitmap to thumbnail size
2194 	BMessenger msgr;
2195 	Scaler scaler(fBitmap, rect, msgr, 0, true);
2196 	BBitmap* thumbnail = scaler.GetBitmap();
2197 	scaler.Start(false);
2198 	ASSERT(thumbnail->ColorSpace() == B_CMAP8);
2199 	// create icon from thumbnail
2200 	BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8);
2201 	memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
2202 	BScreen screen;
2203 	const uchar* src = (uchar*)thumbnail->Bits();
2204 	uchar* dest = (uchar*)icon.Bits();
2205 	const int32 srcBPR = thumbnail->BytesPerRow();
2206 	const int32 destBPR = icon.BytesPerRow();
2207 	const int32 dx = (int32)rect.left;
2208 	const int32 dy = (int32)rect.top;
2209 
2210 	for (int32 y = 0; y <= rect.IntegerHeight(); y ++) {
2211 		for (int32 x = 0; x <= rect.IntegerWidth(); x ++) {
2212 			const uchar* s = src + y * srcBPR + x;
2213 			uchar* d = dest + (y+dy) * destBPR + (x+dx);
2214 			*d = *s;
2215 		}
2216 	}
2217 
2218 	// set icon
2219 	BNode node(&fCurrentRef);
2220 	BNodeInfo info(&node);
2221 	info.SetIcon(clear ? NULL : &icon, which);
2222 }
2223 
2224 
2225 void
2226 ShowImageView::SetIcon(bool clear)
2227 {
2228 	_SetIcon(clear, B_MINI_ICON);
2229 	_SetIcon(clear, B_LARGE_ICON);
2230 }
2231 
2232 
2233 void
2234 ShowImageView::_ToggleSlideShow()
2235 {
2236 	_SendMessageToWindow(MSG_SLIDE_SHOW);
2237 }
2238 
2239 
2240 void
2241 ShowImageView::_ExitFullScreen()
2242 {
2243 	be_app->ShowCursor();
2244 	_SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
2245 }
2246 
2247 
2248 void
2249 ShowImageView::WindowActivated(bool active)
2250 {
2251 	fIsActiveWin = active;
2252 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
2253 }
2254 
2255