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