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