xref: /haiku/src/apps/showimage/ShowImageView.cpp (revision 5115ca085884f7b604a3d607688f0ca20fb7cf57)
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 
597 	AddToRecentDocuments();
598 
599 	Notify();
600 	return B_OK;
601 }
602 
603 
604 status_t
605 ShowImageView::SetSelection(const entry_ref *ref, BPoint point)
606 {
607 	BTranslatorRoster *roster = BTranslatorRoster::Default();
608 	if (!roster)
609 		return B_ERROR;
610 
611 	BFile file(ref, B_READ_ONLY);
612 	translator_info info;
613 	memset(&info, 0, sizeof(translator_info));
614 	if (roster->Identify(&file, NULL, &info, 0, NULL,
615 			B_TRANSLATOR_BITMAP) != B_OK)
616 		return B_ERROR;
617 
618 	// Translate image data and create a new ShowImage window
619 	BBitmapStream outstream;
620 	if (roster->Translate(&file, &info, NULL, &outstream,
621 			B_TRANSLATOR_BITMAP) != B_OK)
622 		return B_ERROR;
623 
624 	BBitmap *newBitmap = NULL;
625 	if (outstream.DetachBitmap(&newBitmap) != B_OK)
626 		return B_ERROR;
627 
628 	return PasteBitmap(newBitmap, point);
629 }
630 
631 
632 void
633 ShowImageView::SetDither(bool dither)
634 {
635 	if (fDither != dither) {
636 		SettingsSetBool("Dither", dither);
637 		fDither = dither;
638 		Invalidate();
639 	}
640 }
641 
642 
643 void
644 ShowImageView::SetShowCaption(bool show)
645 {
646 	if (fShowCaption != show) {
647 		fShowCaption = show;
648 		UpdateCaption();
649 	}
650 }
651 
652 
653 void
654 ShowImageView::SetShrinkToBounds(bool enable)
655 {
656 	if (fShrinkToBounds != enable) {
657 		SettingsSetBool("ShrinkToBounds", enable);
658 		fShrinkToBounds = enable;
659 		FixupScrollBars();
660 		Invalidate();
661 	}
662 }
663 
664 
665 void
666 ShowImageView::SetZoomToBounds(bool enable)
667 {
668 	if (fZoomToBounds != enable) {
669 		SettingsSetBool("ZoomToBounds", enable);
670 		fZoomToBounds = enable;
671 		FixupScrollBars();
672 		Invalidate();
673 	}
674 }
675 
676 
677 void
678 ShowImageView::SetFullScreen(bool fullScreen)
679 {
680 	if (fFullScreen != fullScreen) {
681 		fFullScreen = fullScreen;
682 		if (fFullScreen) {
683 			SetLowColor(0, 0, 0, 255);
684 		} else
685 			SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
686 	}
687 }
688 
689 
690 BBitmap *
691 ShowImageView::GetBitmap()
692 {
693 	return fBitmap;
694 }
695 
696 
697 void
698 ShowImageView::GetName(BString* outName)
699 {
700 	BEntry entry(&fCurrentRef);
701 	char name[B_FILE_NAME_LENGTH];
702 	if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK)
703 		outName->SetTo("");
704 	else
705 		outName->SetTo(name);
706 }
707 
708 
709 void
710 ShowImageView::GetPath(BString *outPath)
711 {
712 	BEntry entry(&fCurrentRef);
713 	BPath path;
714 	if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK)
715 		outPath->SetTo("");
716 	else
717 		outPath->SetTo(path.Path());
718 }
719 
720 
721 void
722 ShowImageView::SetScaleBilinear(bool enabled)
723 {
724 	if (fScaleBilinear != enabled) {
725 		SettingsSetBool("ScaleBilinear", enabled);
726 		fScaleBilinear = enabled;
727 		Invalidate();
728 	}
729 }
730 
731 
732 void
733 ShowImageView::AttachedToWindow()
734 {
735 	fUndo.SetWindow(Window());
736 	FixupScrollBars();
737 
738 	fProgressWindow = new ProgressWindow(Window());
739 }
740 
741 
742 void
743 ShowImageView::DetachedFromWindow()
744 {
745 	fProgressWindow->Lock();
746 	fProgressWindow->Quit();
747 }
748 
749 
750 BRect
751 ShowImageView::AlignBitmap()
752 {
753 	BRect rect(fBitmap->Bounds());
754 
755 	// the width/height of the bitmap (in pixels)
756 	float bitmapWidth = rect.Width() + 1.0;
757 	float bitmapHeight = rect.Height() + 1.0;
758 
759 	// the available width/height for layouting the bitmap (in pixels)
760 	float width = Bounds().Width() - 2 * PEN_SIZE + 1.0;
761 	float height = Bounds().Height() - 2 * PEN_SIZE + 1.0;
762 
763 	if (width == 0 || height == 0)
764 		return rect;
765 
766 	fShrinkOrZoomToBounds = (fShrinkToBounds &&
767 		(bitmapWidth >= width || bitmapHeight >= height)) ||
768 		(fZoomToBounds && (bitmapWidth < width && bitmapHeight < height));
769 	if (fShrinkOrZoomToBounds) {
770 		float s = width / bitmapWidth;
771 
772 		if (s * bitmapHeight <= height) {
773 			rect.right = width - 1;
774 			rect.bottom = static_cast<int>(s * bitmapHeight) - 1;
775 			// center vertically
776 			rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2));
777 		} else {
778 			s = height / bitmapHeight;
779 			rect.right = static_cast<int>(s * bitmapWidth) - 1;
780 			rect.bottom = height - 1;
781 			// center horizontally
782 			rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0);
783 		}
784 	} else {
785 		// zoom image
786 		rect.right = floorf(bitmapWidth * fZoom) - 1;
787 		rect.bottom = floorf(bitmapHeight * fZoom) - 1;
788 
789 		// update the bitmap size after the zoom
790 		bitmapWidth = rect.Width() + 1.0;
791 		bitmapHeight = rect.Height() + 1.0;
792 
793 		// always align in the center
794 		if (width > bitmapWidth)
795 			rect.OffsetBy((width - bitmapWidth) / 2.0, 0);
796 
797 		if (height > bitmapHeight)
798 			rect.OffsetBy(0, (height - bitmapHeight) / 2.0);
799 	}
800 	rect.OffsetBy(PEN_SIZE, PEN_SIZE);
801 	return rect;
802 }
803 
804 
805 void
806 ShowImageView::Setup(BRect rect)
807 {
808 	fLeft = floorf(rect.left);
809 	fTop = floorf(rect.top);
810 	fZoom = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0);
811 }
812 
813 
814 BPoint
815 ShowImageView::ImageToView(BPoint p) const
816 {
817 	p.x = floorf(fZoom * p.x + fLeft);
818 	p.y = floorf(fZoom * p.y + fTop);
819 	return p;
820 }
821 
822 
823 BPoint
824 ShowImageView::ViewToImage(BPoint p) const
825 {
826 	p.x = floorf((p.x - fLeft) / fZoom);
827 	p.y = floorf((p.y - fTop) / fZoom);
828 	return p;
829 }
830 
831 
832 BRect
833 ShowImageView::ImageToView(BRect r) const
834 {
835 	BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
836 	BPoint rightBottom(r.right, r.bottom);
837 	rightBottom += BPoint(1, 1);
838 	rightBottom = ImageToView(rightBottom);
839 	rightBottom -= BPoint(1, 1);
840 	return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
841 }
842 
843 
844 void
845 ShowImageView::DrawBorder(BRect border)
846 {
847 	BRect bounds(Bounds());
848 	// top
849 	FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW);
850 	// left
851 	FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW);
852 	// right
853 	FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
854 	// bottom
855 	FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW);
856 }
857 
858 
859 void
860 ShowImageView::LayoutCaption(BFont &font, BPoint &pos, BRect &rect)
861 {
862 	font_height fontHeight;
863 	float width, height;
864 	BRect bounds(Bounds());
865 	font = be_plain_font;
866 	width = font.StringWidth(fCaption.String());
867 	font.GetHeight(&fontHeight);
868 	height = fontHeight.ascent + fontHeight.descent;
869 	// center text horizontally
870 	pos.x = (bounds.left + bounds.right - width) / 2;
871 	// flush bottom
872 	pos.y = bounds.bottom - fontHeight.descent - 7;
873 
874 	// background rectangle
875 	rect.Set(0, 0, width + 4, height + 4);
876 	rect.OffsetTo(pos);
877 	rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
878 }
879 
880 
881 void
882 ShowImageView::DrawCaption()
883 {
884 	BFont font;
885 	BPoint position;
886 	BRect rect;
887 	LayoutCaption(font, position, rect);
888 
889 	PushState();
890 
891 	// draw background
892 	SetDrawingMode(B_OP_ALPHA);
893 	SetHighColor(255, 255, 255, 160);
894 	FillRect(rect);
895 
896 	// draw text
897 	SetDrawingMode(B_OP_OVER);
898 	SetFont(&font);
899 	SetLowColor(B_TRANSPARENT_COLOR);
900 	SetHighColor(0, 0, 0);
901 	DrawString(fCaption.String(), position);
902 
903 	PopState();
904 }
905 
906 
907 void
908 ShowImageView::UpdateCaption()
909 {
910 	BFont font;
911 	BPoint pos;
912 	BRect rect;
913 	LayoutCaption(font, pos, rect);
914 
915 	// draw over portion of image where caption is located
916 	BRegion clip(rect);
917 	PushState();
918 	ConstrainClippingRegion(&clip);
919 	Draw(rect);
920 	PopState();
921 }
922 
923 
924 Scaler*
925 ShowImageView::GetScaler(BRect rect)
926 {
927 	if (fScaler == NULL || !fScaler->Matches(rect, fDither)) {
928 		DeleteScaler();
929 		BMessenger msgr(this, Window());
930 		fScaler = new Scaler(fDisplayBitmap, rect, msgr, MSG_INVALIDATE, fDither);
931 		fScaler->Start();
932 	}
933 	return fScaler;
934 }
935 
936 
937 void
938 ShowImageView::DrawImage(BRect rect)
939 {
940 	if (fScaleBilinear || fDither) {
941 #if DELAYED_SCALING
942 		Scaler* scaler = fScaler;
943 		if (scaler != NULL && !scaler->Matches(rect, fDither)) {
944 			DeleteScaler(); scaler = NULL;
945 		}
946 #else
947 		Scaler* scaler = GetScaler(rect);
948 #endif
949 		if (scaler != NULL && !scaler->IsRunning()) {
950 			BBitmap* bitmap = scaler->GetBitmap();
951 
952 			if (bitmap) {
953 				DrawBitmap(bitmap, BPoint(rect.left, rect.top));
954 				return;
955 			}
956 		}
957 	}
958 	// TODO: fix composing of fBitmap with other bitmaps
959 	// with regard to alpha channel
960 	if (!fDisplayBitmap)
961 		fDisplayBitmap = fBitmap;
962 
963 	DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect);
964 }
965 
966 
967 void
968 ShowImageView::Draw(BRect updateRect)
969 {
970 	if (fBitmap == NULL)
971 		return;
972 
973 	if (IsPrinting()) {
974 		DrawBitmap(fBitmap);
975 		return;
976 	}
977 
978 	BRect rect = AlignBitmap();
979 	Setup(rect);
980 
981 	BRect border(rect);
982 	border.InsetBy(-PEN_SIZE, -PEN_SIZE);
983 
984 	DrawBorder(border);
985 
986 	// Draw black rectangle around image
987 	StrokeRect(border);
988 
989 	// Draw image
990 	DrawImage(rect);
991 
992 	if (fShowCaption)
993 		DrawCaption();
994 
995 	if (HasSelection()) {
996 		if (fSelBitmap) {
997 			BRect srcBits, destRect;
998 			GetSelMergeRects(srcBits, destRect);
999 			destRect = ImageToView(destRect);
1000 			DrawBitmap(fSelBitmap, srcBits, destRect);
1001 		}
1002 		DrawSelectionBox();
1003 	}
1004 }
1005 
1006 
1007 void
1008 ShowImageView::DrawSelectionBox()
1009 {
1010 	BRect r(fSelectionRect);
1011 	ConstrainToImage(r);
1012 	r = ImageToView(r);
1013 	// draw selection box *around* selection
1014 	r.InsetBy(-1, -1);
1015 	PushState();
1016 	rgb_color white = {255, 255, 255};
1017 	SetLowColor(white);
1018 	StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft);
1019 	StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp);
1020 	StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight);
1021 	StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown);
1022 	PopState();
1023 }
1024 
1025 
1026 void
1027 ShowImageView::FrameResized(float /* width */, float /* height */)
1028 {
1029 	FixupScrollBars();
1030 }
1031 
1032 
1033 void
1034 ShowImageView::ConstrainToImage(BPoint &point)
1035 {
1036 	point.ConstrainTo(fBitmap->Bounds());
1037 }
1038 
1039 
1040 void
1041 ShowImageView::ConstrainToImage(BRect &rect)
1042 {
1043 	rect = rect & fBitmap->Bounds();
1044 }
1045 
1046 
1047 BBitmap*
1048 ShowImageView::CopyFromRect(BRect srcRect)
1049 {
1050 	BRect rect(0, 0, srcRect.Width(), srcRect.Height());
1051 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1052 	BBitmap *bitmap = new(nothrow) BBitmap(rect, fBitmap->ColorSpace(), true);
1053 	if (bitmap == NULL || !bitmap->IsValid()) {
1054 		delete bitmap;
1055 		return NULL;
1056 	}
1057 
1058 	if (bitmap->Lock()) {
1059 		bitmap->AddChild(&view);
1060 		view.DrawBitmap(fBitmap, srcRect, rect);
1061 		view.Sync();
1062 		bitmap->RemoveChild(&view);
1063 		bitmap->Unlock();
1064 	}
1065 
1066 	return bitmap;
1067 }
1068 
1069 
1070 BBitmap*
1071 ShowImageView::CopySelection(uchar alpha, bool imageSize)
1072 {
1073 	bool hasAlpha = alpha != 255;
1074 
1075 	if (!HasSelection())
1076 		return NULL;
1077 
1078 	BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height());
1079 	if (!imageSize) {
1080 		// scale image to view size
1081 		rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
1082 		rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
1083 	}
1084 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1085 	BBitmap *bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true);
1086 	if (bitmap == NULL || !bitmap->IsValid()) {
1087 		delete bitmap;
1088 		return NULL;
1089 	}
1090 
1091 	if (bitmap->Lock()) {
1092 		bitmap->AddChild(&view);
1093 		if (fSelBitmap)
1094 			view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect);
1095 		else
1096 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
1097 		if (hasAlpha) {
1098 			view.SetDrawingMode(B_OP_SUBTRACT);
1099 			view.SetHighColor(0, 0, 0, 255-alpha);
1100 			view.FillRect(rect, B_SOLID_HIGH);
1101 		}
1102 		view.Sync();
1103 		bitmap->RemoveChild(&view);
1104 		bitmap->Unlock();
1105 	}
1106 
1107 	return bitmap;
1108 }
1109 
1110 
1111 bool
1112 ShowImageView::AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
1113 {
1114 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1115 	if (roster == NULL)
1116 		return false;
1117 
1118 	BBitmapStream stream(bitmap);
1119 
1120 	translator_info *outInfo;
1121 	bool found = false;
1122 	int32 outNumInfo;
1123 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
1124 		for (int32 i = 0; i < outNumInfo; i++) {
1125 			const translation_format *fmts;
1126 			int32 num_fmts;
1127 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
1128 			for (int32 j = 0; j < num_fmts; j++) {
1129 				if (strcmp(fmts[j].MIME, "image/x-be-bitmap") != 0) {
1130 					// needed to send data in message
1131  					msg->AddString("be:types", fmts[j].MIME);
1132  					// needed to pass data via file
1133 					msg->AddString("be:filetypes", fmts[j].MIME);
1134 					msg->AddString("be:type_descriptions", fmts[j].name);
1135 				}
1136 				found = true;
1137 			}
1138 		}
1139 	}
1140 	stream.DetachBitmap(&bitmap);
1141 
1142 	return found;
1143 }
1144 
1145 
1146 void
1147 ShowImageView::BeginDrag(BPoint sourcePoint)
1148 {
1149 	BBitmap* bitmap = CopySelection(128, false);
1150 	if (bitmap == NULL)
1151 		return;
1152 
1153 	SetMouseEventMask(B_POINTER_EVENTS);
1154 
1155 	// fill the drag message
1156 	BMessage drag(B_SIMPLE_DATA);
1157 	drag.AddInt32("be:actions", B_COPY_TARGET);
1158 	drag.AddString("be:clip_name", "Bitmap Clip");
1159 	// ShowImage specific fields
1160 	drag.AddPoint("be:_source_point", sourcePoint);
1161 	drag.AddRect("be:_frame", fSelectionRect);
1162 	if (AddSupportedTypes(&drag, bitmap)) {
1163 		// we also support "Passing Data via File" protocol
1164 		drag.AddString("be:types", B_FILE_MIME_TYPE);
1165 		// avoid flickering of dragged bitmap caused by drawing into the window
1166 		AnimateSelection(false);
1167 		// only use a transparent bitmap on selections less than 400x400 (taking into account zooming)
1168 		if ((fSelectionRect.Width() * fZoom) < 400.0 && (fSelectionRect.Height() * fZoom) < 400.0)
1169 		{
1170 			sourcePoint -= fSelectionRect.LeftTop();
1171 			sourcePoint.x *= fZoom;
1172 			sourcePoint.y *= fZoom;
1173 			// DragMessage takes ownership of bitmap
1174 			DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
1175 			bitmap = NULL;
1176 		}
1177 		else
1178 		{
1179 			delete bitmap;
1180 			// Offset and scale the rect
1181 			BRect rect(fSelectionRect);
1182 			rect = ImageToView(rect);
1183 			rect.InsetBy(-1, -1);
1184 			DragMessage(&drag, rect);
1185 		}
1186 	}
1187 }
1188 
1189 
1190 bool
1191 ShowImageView::OutputFormatForType(BBitmap* bitmap, const char* type,
1192 	translation_format* format)
1193 {
1194 	bool found = false;
1195 
1196 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1197 	if (roster == NULL)
1198 		return false;
1199 
1200 	BBitmapStream stream(bitmap);
1201 
1202 	translator_info *outInfo;
1203 	int32 outNumInfo;
1204 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
1205 		for (int32 i = 0; i < outNumInfo; i++) {
1206 			const translation_format *fmts;
1207 			int32 num_fmts;
1208 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
1209 			for (int32 j = 0; j < num_fmts; j++) {
1210 				if (strcmp(fmts[j].MIME, type) == 0) {
1211 					*format = fmts[j];
1212 					found = true;
1213 					break;
1214 				}
1215 			}
1216 		}
1217 	}
1218 	stream.DetachBitmap(&bitmap);
1219 	return found;
1220 }
1221 
1222 
1223 void
1224 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
1225 	const translation_format* format)
1226 {
1227 	if (!bitmap) {
1228 		// If no bitmap is supplied, write out the whole image
1229 		bitmap = fBitmap;
1230 	}
1231 
1232 	BBitmapStream stream(bitmap);
1233 
1234 	bool loop = true;
1235 	while (loop) {
1236 		BTranslatorRoster *roster = BTranslatorRoster::Default();
1237 		if (!roster)
1238 			break;
1239 		// write data
1240 		BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
1241 		if (file.InitCheck() != B_OK)
1242 			break;
1243 		if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
1244 			break;
1245 		// set mime type
1246 		BNodeInfo info(&file);
1247 		if (info.InitCheck() == B_OK)
1248 			info.SetType(format->MIME);
1249 
1250 		loop = false;
1251 			// break out of loop gracefully (indicates no errors)
1252 	}
1253 	if (loop) {
1254 		// If loop terminated because of a break, there was an error
1255 		BString errText;
1256 		errText << "Sorry, the file '" << name << "' could not be written.";
1257 		BAlert *palert = new BAlert(NULL, errText.String(), "Ok");
1258 		palert->Go();
1259 	}
1260 
1261 	stream.DetachBitmap(&bitmap);
1262 		// Don't allow the bitmap to be deleted, this is
1263 		// especially important when using fBitmap as the bitmap
1264 }
1265 
1266 
1267 void
1268 ShowImageView::SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format)
1269 {
1270 	BMessage reply(B_MIME_DATA);
1271 	BBitmapStream stream(bitmap); // destructor deletes bitmap
1272 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1273 	BMallocIO memStream;
1274 	if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
1275 		reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength());
1276 		msg->SendReply(&reply);
1277 	}
1278 }
1279 
1280 
1281 void
1282 ShowImageView::HandleDrop(BMessage* msg)
1283 {
1284 	BMessage data(B_MIME_DATA);
1285 	entry_ref dirRef;
1286 	BString name, type;
1287 	bool saveToFile;
1288 	bool sendInMessage;
1289 	BBitmap *bitmap;
1290 
1291 	saveToFile = msg->FindString("be:filetypes", &type) == B_OK
1292 		&& msg->FindRef("directory", &dirRef) == B_OK
1293 		&& msg->FindString("name", &name) == B_OK;
1294 
1295 	sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK;
1296 
1297 	bitmap = CopySelection();
1298 	if (bitmap == NULL)
1299 		return;
1300 
1301 	translation_format format;
1302 	if (!OutputFormatForType(bitmap, type.String(), &format)) {
1303 		delete bitmap;
1304 		return;
1305 	}
1306 
1307 	if (saveToFile) {
1308 		BDirectory dir(&dirRef);
1309 		SaveToFile(&dir, name.String(), bitmap, &format);
1310 		delete bitmap;
1311 	} else if (sendInMessage) {
1312 		SendInMessage(msg, bitmap, &format);
1313 	} else {
1314 		delete bitmap;
1315 	}
1316 }
1317 
1318 
1319 void
1320 ShowImageView::MoveImage()
1321 {
1322 	BPoint point, delta;
1323 	uint32 buttons;
1324 	// get CURRENT position
1325 	GetMouse(&point, &buttons);
1326 	point = ConvertToScreen(point);
1327 	delta = fFirstPoint - point;
1328 	fFirstPoint = point;
1329 	ScrollRestrictedBy(delta.x, delta.y);
1330 
1331 	// in case we miss MouseUp
1332 	if ((GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0)
1333 		fMovesImage = false;
1334 }
1335 
1336 
1337 uint32
1338 ShowImageView::GetMouseButtons()
1339 {
1340 	uint32 buttons;
1341 	BPoint point;
1342 	GetMouse(&point, &buttons);
1343 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1344 		if ((modifiers() & B_CONTROL_KEY) != 0) {
1345 			buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button
1346 		} else if ((modifiers() & B_SHIFT_KEY) != 0) {
1347 			buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button
1348 		}
1349 	}
1350 	return buttons;
1351 }
1352 
1353 
1354 void
1355 ShowImageView::GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits,
1356 	BRect &destRect)
1357 {
1358 	destRect = selection;
1359 	ConstrainToImage(destRect);
1360 
1361 	srcBits = selection;
1362 	if (srcBits.left < 0)
1363 		srcBits.left = -(srcBits.left);
1364 	else
1365 		srcBits.left = 0;
1366 
1367 	if (srcBits.top < 0)
1368 		srcBits.top = -(srcBits.top);
1369 	else
1370 		srcBits.top = 0;
1371 
1372 	if (srcBits.right > fBitmap->Bounds().right)
1373 		srcBits.right = srcBits.left + destRect.Width();
1374 	else
1375 		srcBits.right = merge->Bounds().right;
1376 
1377 	if (srcBits.bottom > fBitmap->Bounds().bottom)
1378 		srcBits.bottom = srcBits.top + destRect.Height();
1379 	else
1380 		srcBits.bottom = merge->Bounds().bottom;
1381 }
1382 
1383 
1384 void
1385 ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect)
1386 {
1387 	GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect);
1388 }
1389 
1390 
1391 void
1392 ShowImageView::MergeWithBitmap(BBitmap *merge, BRect selection)
1393 {
1394 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1395 	BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
1396 	if (bitmap == NULL || !bitmap->IsValid()) {
1397 		delete bitmap;
1398 		return;
1399 	}
1400 
1401 	if (bitmap->Lock()) {
1402 		bitmap->AddChild(&view);
1403 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1404 		BRect srcBits, destRect;
1405 		GetMergeRects(merge, selection, srcBits, destRect);
1406 		view.DrawBitmap(merge, srcBits, destRect);
1407 
1408 		view.Sync();
1409 		bitmap->RemoveChild(&view);
1410 		bitmap->Unlock();
1411 
1412 		DeleteBitmap();
1413 		fBitmap = bitmap;
1414 
1415 		SendMessageToWindow(MSG_MODIFIED);
1416 	} else
1417 		delete bitmap;
1418 }
1419 
1420 
1421 void
1422 ShowImageView::MergeSelection()
1423 {
1424 	if (!HasSelection())
1425 		return;
1426 
1427 	if (!fSelBitmap) {
1428 		// Even though the merge will not change
1429 		// the background image, I still need to save
1430 		// some undo information here
1431 		fUndo.SetTo(fSelectionRect, NULL, CopySelection());
1432 		return;
1433 	}
1434 
1435 	// Merge selection with background
1436 	fUndo.SetTo(fSelectionRect, CopyFromRect(fSelectionRect), CopySelection());
1437 	MergeWithBitmap(fSelBitmap, fSelectionRect);
1438 }
1439 
1440 
1441 void
1442 ShowImageView::MouseDown(BPoint position)
1443 {
1444 	BPoint point;
1445 	uint32 buttons;
1446 	MakeFocus(true);
1447 
1448 	point = ViewToImage(position);
1449 	buttons = GetMouseButtons();
1450 
1451 	if (HasSelection() && fSelectionRect.Contains(point)
1452 		&& (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) {
1453 		if (!fSelBitmap)
1454 			fSelBitmap = CopySelection();
1455 
1456 		BPoint sourcePoint = point;
1457 		BeginDrag(sourcePoint);
1458 
1459 		while (buttons) {
1460 			// Keep reading mouse movement until
1461 			// the user lets up on all mouse buttons
1462 			GetMouse(&point, &buttons);
1463 			snooze(25 * 1000);
1464 				// sleep for 25 milliseconds to minimize CPU usage during loop
1465 		}
1466 
1467 		if (Bounds().Contains(point)) {
1468 			// If selection stayed inside this view
1469 			// (Some of the selection may be in the border area, which can be OK)
1470 			BPoint last, diff;
1471 			last = ViewToImage(point);
1472 			diff = last - sourcePoint;
1473 
1474 			BRect newSelection = fSelectionRect;
1475 			newSelection.OffsetBy(diff);
1476 
1477 			if (fBitmap->Bounds().Intersects(newSelection)) {
1478 				// Do not accept the new selection box location
1479 				// if it does not intersect with the bitmap rectangle
1480 				fSelectionRect = newSelection;
1481 				Invalidate();
1482 			}
1483 		}
1484 
1485 		AnimateSelection(true);
1486 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1487 		MergeSelection();
1488 			// If there is an existing selection,
1489 			// Make it part of the background image
1490 
1491 		// begin new selection
1492 		SetHasSelection(true);
1493 		fMakesSelection = true;
1494 		SetMouseEventMask(B_POINTER_EVENTS);
1495 		ConstrainToImage(point);
1496 		fFirstPoint = point;
1497 		fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1498 		fSelectionRect = fCopyFromRect;
1499 		Invalidate();
1500 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1501 		ShowPopUpMenu(ConvertToScreen(position));
1502 	} else if (buttons == B_TERTIARY_MOUSE_BUTTON) {
1503 		// move image in window
1504 		SetMouseEventMask(B_POINTER_EVENTS);
1505 		fMovesImage = true;
1506 		fFirstPoint = ConvertToScreen(position);
1507 	}
1508 }
1509 
1510 
1511 void
1512 ShowImageView::UpdateSelectionRect(BPoint point, bool final)
1513 {
1514 	BRect oldSelection = fCopyFromRect;
1515 	point = ViewToImage(point);
1516 	ConstrainToImage(point);
1517 	fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
1518 	fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
1519 	fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
1520 	fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
1521 	fSelectionRect = fCopyFromRect;
1522 
1523 	if (final) {
1524 		// selection must be at least 2 pixels wide or 2 pixels tall
1525 		if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
1526 			SetHasSelection(false);
1527 	} else
1528 		UpdateStatusText();
1529 
1530 	if (oldSelection != fCopyFromRect || !HasSelection()) {
1531 		BRect updateRect;
1532 		updateRect = oldSelection | fCopyFromRect;
1533 		updateRect = ImageToView(updateRect);
1534 		updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE);
1535 		Invalidate(updateRect);
1536 	}
1537 }
1538 
1539 
1540 void
1541 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message)
1542 {
1543 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1544 	if (fMakesSelection) {
1545 		UpdateSelectionRect(point, false);
1546 	} else if (fMovesImage) {
1547 		MoveImage();
1548 	}
1549 }
1550 
1551 
1552 void
1553 ShowImageView::MouseUp(BPoint point)
1554 {
1555 	if (fMakesSelection) {
1556 		UpdateSelectionRect(point, true);
1557 		fMakesSelection = false;
1558 	} else if (fMovesImage) {
1559 		MoveImage();
1560 		fMovesImage = false;
1561 	}
1562 	AnimateSelection(true);
1563 }
1564 
1565 
1566 float
1567 ShowImageView::LimitToRange(float v, orientation o, bool absolute)
1568 {
1569 	BScrollBar* psb = ScrollBar(o);
1570 	if (psb) {
1571 		float min, max, pos;
1572 		pos = v;
1573 		if (!absolute)
1574 			pos += psb->Value();
1575 
1576 		psb->GetRange(&min, &max);
1577 		if (pos < min)
1578 			pos = min;
1579 		else if (pos > max)
1580 			pos = max;
1581 
1582 		v = pos;
1583 		if (!absolute)
1584 			v -= psb->Value();
1585 	}
1586 	return v;
1587 }
1588 
1589 
1590 void
1591 ShowImageView::ScrollRestricted(float x, float y, bool absolute)
1592 {
1593 	if (x != 0)
1594 		x = LimitToRange(x, B_HORIZONTAL, absolute);
1595 
1596 	if (y != 0)
1597 		y = LimitToRange(y, B_VERTICAL, absolute);
1598 
1599 	// hide the caption when using mouse wheel
1600 	// in full screen mode
1601 	// to prevent the caption from dirtying up the image
1602 	// during scrolling.
1603 	bool caption = fShowCaption;
1604 	if (caption) {
1605 		fShowCaption = false;
1606 		UpdateCaption();
1607 	}
1608 
1609 	ScrollBy(x, y);
1610 
1611 	if (caption) {
1612 		// show the caption again
1613 		fShowCaption = true;
1614 		UpdateCaption();
1615 	}
1616 }
1617 
1618 
1619 // XXX method is not unused
1620 void
1621 ShowImageView::ScrollRestrictedTo(float x, float y)
1622 {
1623 	ScrollRestricted(x, y, true);
1624 }
1625 
1626 
1627 void
1628 ShowImageView::ScrollRestrictedBy(float x, float y)
1629 {
1630 	ScrollRestricted(x, y, false);
1631 }
1632 
1633 
1634 void
1635 ShowImageView::KeyDown(const char* bytes, int32 numBytes)
1636 {
1637 	if (numBytes != 1) {
1638 		BView::KeyDown(bytes, numBytes);
1639 		return;
1640 	}
1641 
1642 	switch (*bytes) {
1643 		case B_DOWN_ARROW:
1644 			ScrollRestrictedBy(0, 10);
1645 			break;
1646 		case B_UP_ARROW:
1647 			ScrollRestrictedBy(0, -10);
1648 			break;
1649 		case B_LEFT_ARROW:
1650 			ScrollRestrictedBy(-10, 0);
1651 			break;
1652 		case B_RIGHT_ARROW:
1653 			ScrollRestrictedBy(10, 0);
1654 			break;
1655 		case B_ENTER:
1656 			SendMessageToWindow(MSG_FILE_NEXT);
1657 			break;
1658 		case B_BACKSPACE:
1659 			SendMessageToWindow(MSG_FILE_PREV);
1660 			break;
1661 		case B_HOME:
1662 			break;
1663 		case B_END:
1664 			break;
1665 		case B_SPACE:
1666 			ToggleSlideShow();
1667 			break;
1668 		case B_ESCAPE:
1669 			// stop slide show
1670 			if (fSlideShow)
1671 				ToggleSlideShow();
1672 
1673 			ExitFullScreen();
1674 
1675 			ClearSelection();
1676 			break;
1677 		case B_DELETE:
1678 		{
1679 			// Move image to Trash
1680 			BMessage trash(BPrivate::kMoveToTrash);
1681 			trash.AddRef("refs", &fCurrentRef);
1682 			// We create our own messenger because the member fTrackerMessenger
1683 			// could be invalid
1684 			BMessenger tracker(kTrackerSignature);
1685 			if (tracker.SendMessage(&trash) == B_OK)
1686 				if (!NextFile()) {
1687 					// This is the last (or only file) in this directory,
1688 					// close the window
1689 					SendMessageToWindow(B_QUIT_REQUESTED);
1690 				}
1691 			break;
1692 		}
1693 		case '+':
1694 		case '=':
1695 			ZoomIn();
1696 			break;
1697 		case '-':
1698 			ZoomOut();
1699 			break;
1700 	}
1701 }
1702 
1703 
1704 void
1705 ShowImageView::MouseWheelChanged(BMessage *msg)
1706 {
1707 	// The BeOS driver does not currently support
1708 	// X wheel scrolling, therefore, dx is zero.
1709 	// |dy| is the number of notches scrolled up or down.
1710 	// When the wheel is scrolled down (towards the user) dy > 0
1711 	// When the wheel is scrolled up (away from the user) dy < 0
1712 	const float kscrollBy = 40;
1713 	float dy, dx;
1714 	float x, y;
1715 	x = 0; y = 0;
1716 	if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK)
1717 		x = dx * kscrollBy;
1718 	if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK)
1719 		y = dy * kscrollBy;
1720 
1721 	ScrollRestrictedBy(x, y);
1722 }
1723 
1724 
1725 void
1726 ShowImageView::ShowPopUpMenu(BPoint screen)
1727 {
1728 	BPopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
1729 
1730 	ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
1731 	if (showImage)
1732 		showImage->BuildContextMenu(menu);
1733 
1734 	screen -= BPoint(10, 10);
1735 	menu->Go(screen, true, false, true);
1736 	fShowingPopUpMenu = true;
1737 }
1738 
1739 
1740 void
1741 ShowImageView::SettingsSetBool(const char* name, bool value)
1742 {
1743 	ShowImageSettings* settings;
1744 	settings = my_app->Settings();
1745 	if (settings->Lock()) {
1746 		settings->SetBool(name, value);
1747 		settings->Unlock();
1748 	}
1749 }
1750 
1751 
1752 void
1753 ShowImageView::MessageReceived(BMessage *message)
1754 {
1755 	switch (message->what) {
1756 		case MSG_SELECTION_BITMAP:
1757 		{
1758 			// In response to a B_SIMPLE_DATA message, a view will
1759 			// send this message and expect a reply with a pointer to
1760 			// the currently selected bitmap clip. Although this view
1761 			// allocates the BBitmap * sent in the reply, it is only
1762 			// to be used and deleted by the view that is being replied to.
1763 			BMessage msg;
1764 			msg.AddPointer("be:_bitmap_ptr", CopySelection());
1765 			message->SendReply(&msg);
1766 			break;
1767 		}
1768 
1769 		case B_SIMPLE_DATA:
1770 			if (message->WasDropped()) {
1771 				uint32 type;
1772 				int32 count;
1773 				status_t ret = message->GetInfo("refs", &type, &count);
1774 				if (ret == B_OK && type == B_REF_TYPE) {
1775 					// If file was dropped, open it as the selection
1776 					entry_ref ref;
1777 					if (message->FindRef("refs", 0, &ref) == B_OK) {
1778 						BPoint point = message->DropPoint();
1779 						point = ConvertFromScreen(point);
1780 						point = ViewToImage(point);
1781 						SetSelection(&ref, point);
1782 					}
1783 				} else {
1784 					// If a user drags a clip from another ShowImage window,
1785 					// request a BBitmap pointer to that clip, allocated by the
1786 					// other view, for use solely by this view, so that it can
1787 					// be dropped/pasted onto this view.
1788 					BMessenger retMsgr, localMsgr(this);
1789 					retMsgr = message->ReturnAddress();
1790 					if (retMsgr != localMsgr) {
1791 						BMessage msgReply;
1792 						retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply);
1793 						BBitmap *bitmap = NULL;
1794 						if (msgReply.FindPointer("be:_bitmap_ptr",
1795 							reinterpret_cast<void **>(&bitmap)) == B_OK) {
1796 							BRect sourceRect;
1797 							BPoint point, sourcePoint;
1798 							message->FindPoint("be:_source_point", &sourcePoint);
1799 							message->FindRect("be:_frame", &sourceRect);
1800 							point = message->DropPoint();
1801 							point.Set(point.x - (sourcePoint.x - sourceRect.left),
1802 								point.y - (sourcePoint.y - sourceRect.top));
1803 								// adjust drop point before scaling is factored in
1804 							point = ConvertFromScreen(point);
1805 							point = ViewToImage(point);
1806 
1807 							PasteBitmap(bitmap, point);
1808 						}
1809 					}
1810 				}
1811 			}
1812 			break;
1813 
1814 		case B_COPY_TARGET:
1815 			HandleDrop(message);
1816 			break;
1817 		case B_MOUSE_WHEEL_CHANGED:
1818 			MouseWheelChanged(message);
1819 			break;
1820 		case MSG_INVALIDATE:
1821 			Invalidate();
1822 			break;
1823 
1824 		case kMsgPopUpMenuClosed:
1825 			fShowingPopUpMenu = false;
1826 			break;
1827 
1828 		default:
1829 			BView::MessageReceived(message);
1830 			break;
1831 	}
1832 }
1833 
1834 
1835 void
1836 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength)
1837 {
1838 	float prop, range;
1839 	BScrollBar *psb;
1840 
1841 	psb = ScrollBar(o);
1842 	if (psb) {
1843 		range = bitmapLength - viewLength;
1844 		if (range < 0.0) {
1845 			range = 0.0;
1846 		}
1847 		prop = viewLength / bitmapLength;
1848 		if (prop > 1.0) {
1849 			prop = 1.0;
1850 		}
1851 		psb->SetRange(0, range);
1852 		psb->SetProportion(prop);
1853 		psb->SetSteps(10, 100);
1854 	}
1855 }
1856 
1857 
1858 void
1859 ShowImageView::FixupScrollBars()
1860 {
1861 	BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
1862 	if (fBitmap) {
1863 		rctbitmap = AlignBitmap();
1864 		rctbitmap.OffsetTo(0, 0);
1865 	}
1866 
1867 	FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width());
1868 	FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height());
1869 }
1870 
1871 
1872 int32
1873 ShowImageView::CurrentPage()
1874 {
1875 	return fDocumentIndex;
1876 }
1877 
1878 
1879 int32
1880 ShowImageView::PageCount()
1881 {
1882 	return fDocumentCount;
1883 }
1884 
1885 
1886 void
1887 ShowImageView::Undo()
1888 {
1889 	int32 undoType = fUndo.GetType();
1890 	if (undoType != UNDO_UNDO && undoType != UNDO_REDO)
1891 		return;
1892 
1893 	// backup current selection
1894 	BRect undoneSelRect;
1895 	BBitmap *undoneSelection;
1896 	undoneSelRect = fSelectionRect;
1897 	undoneSelection = CopySelection();
1898 
1899 	if (undoType == UNDO_UNDO) {
1900 		BBitmap *undoRestore;
1901 		undoRestore = fUndo.GetRestoreBitmap();
1902 		if (undoRestore)
1903 			MergeWithBitmap(undoRestore, fUndo.GetRect());
1904 	}
1905 
1906 	// restore previous image/selection
1907 	BBitmap *undoSelection;
1908 	undoSelection = fUndo.GetSelectionBitmap();
1909 		// NOTE: ShowImageView is responsible for deleting this bitmap
1910 		// (Which it will, as it would with a fSelBitmap that it allocated itself)
1911 	if (!undoSelection)
1912 		SetHasSelection(false);
1913 	else {
1914 		fCopyFromRect = BRect();
1915 		fSelectionRect = fUndo.GetRect();
1916 		SetHasSelection(true);
1917 		fSelBitmap = undoSelection;
1918 	}
1919 
1920 	fUndo.Undo(undoneSelRect, NULL, undoneSelection);
1921 
1922 	Invalidate();
1923 }
1924 
1925 
1926 void
1927 ShowImageView::AddWhiteRect(BRect &rect)
1928 {
1929 	// Paint white rectangle, using rect, into the background image
1930 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1931 	BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
1932 	if (bitmap == NULL || !bitmap->IsValid()) {
1933 		delete bitmap;
1934 		return;
1935 	}
1936 
1937 	if (bitmap->Lock()) {
1938 		bitmap->AddChild(&view);
1939 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1940 
1941 		view.FillRect(rect, B_SOLID_LOW);
1942 			// draw white rect
1943 
1944 		view.Sync();
1945 		bitmap->RemoveChild(&view);
1946 		bitmap->Unlock();
1947 
1948 		DeleteBitmap();
1949 		fBitmap = bitmap;
1950 
1951 		SendMessageToWindow(MSG_MODIFIED);
1952 	} else
1953 		delete bitmap;
1954 }
1955 
1956 
1957 void
1958 ShowImageView::RemoveSelection(bool toClipboard)
1959 {
1960 	if (!HasSelection())
1961 		return;
1962 
1963 	BRect rect = fSelectionRect;
1964 	bool cutBackground = (fSelBitmap) ? false : true;
1965 	BBitmap *selection, *restore = NULL;
1966 	selection = CopySelection();
1967 
1968 	if (toClipboard)
1969 		CopySelectionToClipboard();
1970 
1971 	SetHasSelection(false);
1972 
1973 	if (cutBackground) {
1974 		// If the user hasn't dragged the selection,
1975 		// paint a white rectangle where the selection was
1976 		restore = CopyFromRect(rect);
1977 		AddWhiteRect(rect);
1978 	}
1979 
1980 	fUndo.SetTo(rect, restore, selection);
1981 	Invalidate();
1982 }
1983 
1984 
1985 void
1986 ShowImageView::Cut()
1987 {
1988 	// Copy the selection to the clipboard,
1989 	// then remove it
1990 	RemoveSelection(true);
1991 }
1992 
1993 
1994 status_t
1995 ShowImageView::PasteBitmap(BBitmap *bitmap, BPoint point)
1996 {
1997 	if (bitmap && bitmap->IsValid()) {
1998 		MergeSelection();
1999 
2000 		fCopyFromRect = BRect();
2001 		fSelectionRect = bitmap->Bounds();
2002 		SetHasSelection(true);
2003 		fSelBitmap = bitmap;
2004 
2005 		BRect offsetRect = fSelectionRect;
2006 		offsetRect.OffsetBy(point);
2007 		if (fBitmap->Bounds().Intersects(offsetRect))
2008 			// Move the selection rectangle to desired origin,
2009 			// but only if the resulting selection rectangle
2010 			// intersects with the background bitmap rectangle
2011 			fSelectionRect = offsetRect;
2012 
2013 		Invalidate();
2014 
2015 		return B_OK;
2016 	}
2017 
2018 	return B_ERROR;
2019 }
2020 
2021 
2022 void
2023 ShowImageView::Paste()
2024 {
2025 	if (be_clipboard->Lock()) {
2026 		BMessage *pclip;
2027 		if ((pclip = be_clipboard->Data()) != NULL) {
2028 			BPoint point(0, 0);
2029 			pclip->FindPoint("be:location", &point);
2030 			BBitmap *pbits;
2031 			pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip));
2032 			PasteBitmap(pbits, point);
2033 		}
2034 
2035 		be_clipboard->Unlock();
2036 	}
2037 }
2038 
2039 
2040 void
2041 ShowImageView::SelectAll()
2042 {
2043 	SetHasSelection(true);
2044 	fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height());
2045 	fSelectionRect = fCopyFromRect;
2046 	Invalidate();
2047 }
2048 
2049 
2050 void
2051 ShowImageView::ClearSelection()
2052 {
2053 	// Remove the selection,
2054 	// DON'T copy it to the clipboard
2055 	RemoveSelection(false);
2056 }
2057 
2058 
2059 void
2060 ShowImageView::SetHasSelection(bool bHasSelection)
2061 {
2062 	DeleteSelBitmap();
2063 	fHasSelection = bHasSelection;
2064 
2065 	UpdateStatusText();
2066 
2067 	BMessage msg(MSG_SELECTION);
2068 	msg.AddBool("has_selection", fHasSelection);
2069 	SendMessageToWindow(&msg);
2070 }
2071 
2072 
2073 void
2074 ShowImageView::CopySelectionToClipboard()
2075 {
2076 	if (HasSelection() && be_clipboard->Lock()) {
2077 		be_clipboard->Clear();
2078 		BMessage *clip = NULL;
2079 		if ((clip = be_clipboard->Data()) != NULL) {
2080 			BMessage data;
2081 			BBitmap* bitmap = CopySelection();
2082 			if (bitmap != NULL) {
2083 				#if 0
2084 				// According to BeBook and Becasso, Gobe Productive do the following.
2085 				// Paste works in Productive, but not in Becasso and original ShowImage.
2086 				BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused
2087 				bitmap->Archive(&msg);
2088 				clip->AddMessage("image/x-be-bitmap", &msg);
2089 				#else
2090 				// original ShowImage performs this. Paste works with original ShowImage.
2091 				bitmap->Archive(clip);
2092 				// original ShowImage uses be:location for insertion point
2093 				clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top));
2094 				#endif
2095 				delete bitmap;
2096 				be_clipboard->Commit();
2097 			}
2098 		}
2099 		be_clipboard->Unlock();
2100 	}
2101 }
2102 
2103 
2104 void
2105 ShowImageView::FirstPage()
2106 {
2107 	if (fDocumentIndex != 1) {
2108 		fDocumentIndex = 1;
2109 		SetImage(NULL);
2110 	}
2111 }
2112 
2113 
2114 void
2115 ShowImageView::LastPage()
2116 {
2117 	if (fDocumentIndex != fDocumentCount) {
2118 		fDocumentIndex = fDocumentCount;
2119 		SetImage(NULL);
2120 	}
2121 }
2122 
2123 
2124 void
2125 ShowImageView::NextPage()
2126 {
2127 	if (fDocumentIndex < fDocumentCount) {
2128 		fDocumentIndex++;
2129 		SetImage(NULL);
2130 	}
2131 }
2132 
2133 
2134 void
2135 ShowImageView::PrevPage()
2136 {
2137 	if (fDocumentIndex > 1) {
2138 		fDocumentIndex--;
2139 		SetImage(NULL);
2140 	}
2141 }
2142 
2143 
2144 int
2145 ShowImageView::CompareEntries(const void* a, const void* b)
2146 {
2147 	entry_ref *r1, *r2;
2148 	r1 = *(entry_ref**)a;
2149 	r2 = *(entry_ref**)b;
2150 	return strcasecmp(r1->name, r2->name);
2151 }
2152 
2153 
2154 void
2155 ShowImageView::GoToPage(int32 page)
2156 {
2157 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
2158 		fDocumentIndex = page;
2159 		SetImage(NULL);
2160 	}
2161 }
2162 
2163 
2164 void
2165 ShowImageView::FreeEntries(BList* entries)
2166 {
2167 	const int32 n = entries->CountItems();
2168 	for (int32 i = 0; i < n; i ++) {
2169 		entry_ref* ref = (entry_ref*)entries->ItemAt(i);
2170 		delete ref;
2171 	}
2172 	entries->MakeEmpty();
2173 }
2174 
2175 
2176 void
2177 ShowImageView::SetTrackerSelectionToCurrent()
2178 {
2179 	BMessage setsel(B_SET_PROPERTY);
2180 	setsel.AddSpecifier("Selection");
2181 	setsel.AddRef("data", &fCurrentRef);
2182 	fTrackerMessenger.SendMessage(&setsel);
2183 }
2184 
2185 
2186 bool
2187 ShowImageView::FindNextImageByDir(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind)
2188 {
2189 	ASSERT(next || !rewind);
2190 	BEntry curImage(in_current);
2191 	entry_ref entry, *ref;
2192 	BDirectory parent;
2193 	BList entries;
2194 	bool found = false;
2195 	int32 cur;
2196 
2197 	if (curImage.GetParent(&parent) != B_OK) {
2198 		return false;
2199 	}
2200 
2201 	// insert current ref, so we can find it easily after sorting
2202 	entries.AddItem(in_current);
2203 
2204 	while (parent.GetNextRef(&entry) == B_OK) {
2205 		if (entry != *in_current) {
2206 			entries.AddItem(new entry_ref(entry));
2207 		}
2208 	}
2209 
2210 	entries.SortItems(CompareEntries);
2211 
2212 	cur = entries.IndexOf(in_current);
2213 	ASSERT(cur >= 0);
2214 
2215 	// remove it so FreeEntries() does not delete it
2216 	entries.RemoveItem(in_current);
2217 
2218 	if (next) {
2219 		// find the next image in the list
2220 		if (rewind) cur = 0; // start with first
2221 		for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) {
2222 			if (IsImage(ref)) {
2223 				found = true;
2224 				*out_image = (const entry_ref)*ref;
2225 				break;
2226 			}
2227 		}
2228 	} else {
2229 		// find the previous image in the list
2230 		cur --;
2231 		for (; cur >= 0; cur --) {
2232 			ref = (entry_ref*)entries.ItemAt(cur);
2233 			if (IsImage(ref)) {
2234 				found = true;
2235 				*out_image = (const entry_ref)*ref;
2236 				break;
2237 			}
2238 		}
2239 	}
2240 
2241 	FreeEntries(&entries);
2242 	return found;
2243 }
2244 
2245 bool
2246 ShowImageView::FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, bool rewind)
2247 {
2248 	// Based on similar function from BeMail!
2249 	if (!fTrackerMessenger.IsValid())
2250 		// If tracker scripting is not available,
2251 		// fall back on directory searching code
2252 		return FindNextImageByDir(in_current, ref, next, rewind);
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 FindNextImageByDir(in_current, ref, next, rewind);;
2283 		if (reply.FindRef("result", &nextRef) != B_OK)
2284 			return FindNextImageByDir(in_current, ref, next, rewind);;
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, &orientation, sizeof(orientation));
2454 		} else {
2455 			node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
2456 		}
2457 	}
2458 
2459 	// set new bitmap
2460 	DeleteBitmap();
2461 	fBitmap = bm;
2462 
2463 	if (!quiet) {
2464 		// remove selection
2465 		SetHasSelection(false);
2466 		Notify();
2467 	}
2468 }
2469 
2470 
2471 //! image operation initiated by user
2472 void
2473 ShowImageView::UserDoImageOperation(ImageProcessor::operation op, bool quiet)
2474 {
2475 	fUndo.Clear();
2476 	DoImageOperation(op, quiet);
2477 }
2478 
2479 
2480 void
2481 ShowImageView::Rotate(int degree)
2482 {
2483 	if (degree == 90) {
2484 		UserDoImageOperation(ImageProcessor::kRotateClockwise);
2485 	} else if (degree == 270) {
2486 		UserDoImageOperation(ImageProcessor::kRotateCounterClockwise);
2487 	}
2488 }
2489 
2490 
2491 void
2492 ShowImageView::Flip(bool vertical)
2493 {
2494 	if (vertical) {
2495 		UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
2496 	} else {
2497 		UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
2498 	}
2499 }
2500 
2501 
2502 void
2503 ShowImageView::Invert()
2504 {
2505 	if (fBitmap->ColorSpace() != B_CMAP8) {
2506 		// Only allow an invert operation if the
2507 		// bitmap color space is supported by the
2508 		// invert algorithm
2509 		UserDoImageOperation(ImageProcessor::kInvert);
2510 	}
2511 }
2512 
2513 void
2514 ShowImageView::ResizeImage(int w, int h)
2515 {
2516 	if (fBitmap == NULL || w < 1 || h < 1)
2517 		return;
2518 
2519 	Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false);
2520 	scaler.Start(false);
2521 	BBitmap* scaled = scaler.DetachBitmap();
2522 	if (scaled == NULL) {
2523 		// operation failed
2524 		return;
2525 	}
2526 
2527 	// remove selection
2528 	SetHasSelection(false);
2529 	fUndo.Clear();
2530 	DeleteBitmap();
2531 	fBitmap = scaled;
2532 
2533 	SendMessageToWindow(MSG_MODIFIED);
2534 
2535 	Notify();
2536 }
2537 
2538 void
2539 ShowImageView::SetIcon(bool clear, icon_size which)
2540 {
2541 	int32 size;
2542 	switch (which) {
2543 		case B_MINI_ICON: size = 16;
2544 			break;
2545 		case B_LARGE_ICON: size = 32;
2546 			break;
2547 		default:
2548 			return;
2549 	}
2550 
2551 	BRect rect(fBitmap->Bounds());
2552 	float s;
2553 	s = size / (rect.Width()+1.0);
2554 
2555 	if (s * (rect.Height()+1.0) <= size) {
2556 		rect.right = size-1;
2557 		rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
2558 		// center vertically
2559 		rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
2560 	} else {
2561 		s = size / (rect.Height()+1.0);
2562 		rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
2563 		rect.bottom = size-1;
2564 		// center horizontally
2565 		rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
2566 	}
2567 
2568 	// scale bitmap to thumbnail size
2569 	BMessenger msgr;
2570 	Scaler scaler(fBitmap, rect, msgr, 0, true);
2571 	BBitmap* thumbnail = scaler.GetBitmap();
2572 	scaler.Start(false);
2573 	ASSERT(thumbnail->ColorSpace() == B_CMAP8);
2574 	// create icon from thumbnail
2575 	BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8);
2576 	memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
2577 	BScreen screen;
2578 	const uchar* src = (uchar*)thumbnail->Bits();
2579 	uchar* dest = (uchar*)icon.Bits();
2580 	const int32 srcBPR = thumbnail->BytesPerRow();
2581 	const int32 destBPR = icon.BytesPerRow();
2582 	const int32 dx = (int32)rect.left;
2583 	const int32 dy = (int32)rect.top;
2584 
2585 	for (int32 y = 0; y <= rect.IntegerHeight(); y ++) {
2586 		for (int32 x = 0; x <= rect.IntegerWidth(); x ++) {
2587 			const uchar* s = src + y * srcBPR + x;
2588 			uchar* d = dest + (y+dy) * destBPR + (x+dx);
2589 			*d = *s;
2590 		}
2591 	}
2592 
2593 	// set icon
2594 	BNode node(&fCurrentRef);
2595 	BNodeInfo info(&node);
2596 	info.SetIcon(clear ? NULL : &icon, which);
2597 }
2598 
2599 
2600 void
2601 ShowImageView::SetIcon(bool clear)
2602 {
2603 	SetIcon(clear, B_MINI_ICON);
2604 	SetIcon(clear, B_LARGE_ICON);
2605 }
2606 
2607 
2608 void
2609 ShowImageView::ToggleSlideShow()
2610 {
2611 	SendMessageToWindow(MSG_SLIDE_SHOW);
2612 }
2613 
2614 
2615 void
2616 ShowImageView::ExitFullScreen()
2617 {
2618 	be_app->ShowCursor();
2619 	SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
2620 }
2621 
2622 
2623 void
2624 ShowImageView::WindowActivated(bool active)
2625 {
2626 	fIsActiveWin = active;
2627 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
2628 }
2629 
2630