xref: /haiku/src/apps/showimage/ShowImageView.cpp (revision 0562493379cd52eb7103531f895f10bb8e77c085)
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, true);
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(floorf((width - bitmapWidth) / 2.0), 0);
797 
798 		if (height > bitmapHeight)
799 			rect.OffsetBy(0, floorf((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 			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 		} else {
1177 			delete bitmap;
1178 			// Offset and scale the rect
1179 			BRect rect(fSelectionRect);
1180 			rect = ImageToView(rect);
1181 			rect.InsetBy(-1, -1);
1182 			DragMessage(&drag, rect);
1183 		}
1184 	}
1185 }
1186 
1187 
1188 bool
1189 ShowImageView::OutputFormatForType(BBitmap* bitmap, const char* type,
1190 	translation_format* format)
1191 {
1192 	bool found = false;
1193 
1194 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1195 	if (roster == NULL)
1196 		return false;
1197 
1198 	BBitmapStream stream(bitmap);
1199 
1200 	translator_info *outInfo;
1201 	int32 outNumInfo;
1202 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
1203 		for (int32 i = 0; i < outNumInfo; i++) {
1204 			const translation_format *fmts;
1205 			int32 num_fmts;
1206 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
1207 			for (int32 j = 0; j < num_fmts; j++) {
1208 				if (strcmp(fmts[j].MIME, type) == 0) {
1209 					*format = fmts[j];
1210 					found = true;
1211 					break;
1212 				}
1213 			}
1214 		}
1215 	}
1216 	stream.DetachBitmap(&bitmap);
1217 	return found;
1218 }
1219 
1220 
1221 void
1222 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
1223 	const translation_format* format)
1224 {
1225 	if (!bitmap) {
1226 		// If no bitmap is supplied, write out the whole image
1227 		bitmap = fBitmap;
1228 	}
1229 
1230 	BBitmapStream stream(bitmap);
1231 
1232 	bool loop = true;
1233 	while (loop) {
1234 		BTranslatorRoster *roster = BTranslatorRoster::Default();
1235 		if (!roster)
1236 			break;
1237 		// write data
1238 		BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
1239 		if (file.InitCheck() != B_OK)
1240 			break;
1241 		if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
1242 			break;
1243 		// set mime type
1244 		BNodeInfo info(&file);
1245 		if (info.InitCheck() == B_OK)
1246 			info.SetType(format->MIME);
1247 
1248 		loop = false;
1249 			// break out of loop gracefully (indicates no errors)
1250 	}
1251 	if (loop) {
1252 		// If loop terminated because of a break, there was an error
1253 		BString errText;
1254 		errText << "Sorry, the file '" << name << "' could not be written.";
1255 		BAlert *palert = new BAlert(NULL, errText.String(), "Ok");
1256 		palert->Go();
1257 	}
1258 
1259 	stream.DetachBitmap(&bitmap);
1260 		// Don't allow the bitmap to be deleted, this is
1261 		// especially important when using fBitmap as the bitmap
1262 }
1263 
1264 
1265 void
1266 ShowImageView::SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format)
1267 {
1268 	BMessage reply(B_MIME_DATA);
1269 	BBitmapStream stream(bitmap); // destructor deletes bitmap
1270 	BTranslatorRoster *roster = BTranslatorRoster::Default();
1271 	BMallocIO memStream;
1272 	if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
1273 		reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength());
1274 		msg->SendReply(&reply);
1275 	}
1276 }
1277 
1278 
1279 void
1280 ShowImageView::HandleDrop(BMessage* msg)
1281 {
1282 	BMessage data(B_MIME_DATA);
1283 	entry_ref dirRef;
1284 	BString name, type;
1285 	bool saveToFile;
1286 	bool sendInMessage;
1287 	BBitmap *bitmap;
1288 
1289 	saveToFile = msg->FindString("be:filetypes", &type) == B_OK
1290 		&& msg->FindRef("directory", &dirRef) == B_OK
1291 		&& msg->FindString("name", &name) == B_OK;
1292 
1293 	sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK;
1294 
1295 	bitmap = CopySelection();
1296 	if (bitmap == NULL)
1297 		return;
1298 
1299 	translation_format format;
1300 	if (!OutputFormatForType(bitmap, type.String(), &format)) {
1301 		delete bitmap;
1302 		return;
1303 	}
1304 
1305 	if (saveToFile) {
1306 		BDirectory dir(&dirRef);
1307 		SaveToFile(&dir, name.String(), bitmap, &format);
1308 		delete bitmap;
1309 	} else if (sendInMessage) {
1310 		SendInMessage(msg, bitmap, &format);
1311 	} else {
1312 		delete bitmap;
1313 	}
1314 }
1315 
1316 
1317 void
1318 ShowImageView::MoveImage()
1319 {
1320 	BPoint point, delta;
1321 	uint32 buttons;
1322 	// get CURRENT position
1323 	GetMouse(&point, &buttons);
1324 	point = ConvertToScreen(point);
1325 	delta = fFirstPoint - point;
1326 	fFirstPoint = point;
1327 	ScrollRestrictedBy(delta.x, delta.y);
1328 
1329 	// in case we miss MouseUp
1330 	if ((GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0)
1331 		fMovesImage = false;
1332 }
1333 
1334 
1335 uint32
1336 ShowImageView::GetMouseButtons()
1337 {
1338 	uint32 buttons;
1339 	BPoint point;
1340 	GetMouse(&point, &buttons);
1341 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1342 		if ((modifiers() & B_CONTROL_KEY) != 0) {
1343 			buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button
1344 		} else if ((modifiers() & B_SHIFT_KEY) != 0) {
1345 			buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button
1346 		}
1347 	}
1348 	return buttons;
1349 }
1350 
1351 
1352 void
1353 ShowImageView::GetMergeRects(BBitmap *merge, BRect selection, BRect &srcBits,
1354 	BRect &destRect)
1355 {
1356 	destRect = selection;
1357 	ConstrainToImage(destRect);
1358 
1359 	srcBits = selection;
1360 	if (srcBits.left < 0)
1361 		srcBits.left = -(srcBits.left);
1362 	else
1363 		srcBits.left = 0;
1364 
1365 	if (srcBits.top < 0)
1366 		srcBits.top = -(srcBits.top);
1367 	else
1368 		srcBits.top = 0;
1369 
1370 	if (srcBits.right > fBitmap->Bounds().right)
1371 		srcBits.right = srcBits.left + destRect.Width();
1372 	else
1373 		srcBits.right = merge->Bounds().right;
1374 
1375 	if (srcBits.bottom > fBitmap->Bounds().bottom)
1376 		srcBits.bottom = srcBits.top + destRect.Height();
1377 	else
1378 		srcBits.bottom = merge->Bounds().bottom;
1379 }
1380 
1381 
1382 void
1383 ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect)
1384 {
1385 	GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect);
1386 }
1387 
1388 
1389 void
1390 ShowImageView::MergeWithBitmap(BBitmap *merge, BRect selection)
1391 {
1392 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1393 	BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
1394 	if (bitmap == NULL || !bitmap->IsValid()) {
1395 		delete bitmap;
1396 		return;
1397 	}
1398 
1399 	if (bitmap->Lock()) {
1400 		bitmap->AddChild(&view);
1401 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1402 		BRect srcBits, destRect;
1403 		GetMergeRects(merge, selection, srcBits, destRect);
1404 		view.DrawBitmap(merge, srcBits, destRect);
1405 
1406 		view.Sync();
1407 		bitmap->RemoveChild(&view);
1408 		bitmap->Unlock();
1409 
1410 		DeleteBitmap();
1411 		fBitmap = bitmap;
1412 
1413 		SendMessageToWindow(MSG_MODIFIED);
1414 	} else
1415 		delete bitmap;
1416 }
1417 
1418 
1419 void
1420 ShowImageView::MergeSelection()
1421 {
1422 	if (!HasSelection())
1423 		return;
1424 
1425 	if (!fSelBitmap) {
1426 		// Even though the merge will not change
1427 		// the background image, I still need to save
1428 		// some undo information here
1429 		fUndo.SetTo(fSelectionRect, NULL, CopySelection());
1430 		return;
1431 	}
1432 
1433 	// Merge selection with background
1434 	fUndo.SetTo(fSelectionRect, CopyFromRect(fSelectionRect), CopySelection());
1435 	MergeWithBitmap(fSelBitmap, fSelectionRect);
1436 }
1437 
1438 
1439 void
1440 ShowImageView::MouseDown(BPoint position)
1441 {
1442 	BPoint point;
1443 	uint32 buttons;
1444 	MakeFocus(true);
1445 
1446 	point = ViewToImage(position);
1447 	buttons = GetMouseButtons();
1448 
1449 	if (HasSelection() && fSelectionRect.Contains(point)
1450 		&& (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) {
1451 		if (!fSelBitmap)
1452 			fSelBitmap = CopySelection();
1453 
1454 		BPoint sourcePoint = point;
1455 		BeginDrag(sourcePoint);
1456 
1457 		while (buttons) {
1458 			// Keep reading mouse movement until
1459 			// the user lets up on all mouse buttons
1460 			GetMouse(&point, &buttons);
1461 			snooze(25 * 1000);
1462 				// sleep for 25 milliseconds to minimize CPU usage during loop
1463 		}
1464 
1465 		if (Bounds().Contains(point)) {
1466 			// If selection stayed inside this view
1467 			// (Some of the selection may be in the border area, which can be OK)
1468 			BPoint last, diff;
1469 			last = ViewToImage(point);
1470 			diff = last - sourcePoint;
1471 
1472 			BRect newSelection = fSelectionRect;
1473 			newSelection.OffsetBy(diff);
1474 
1475 			if (fBitmap->Bounds().Intersects(newSelection)) {
1476 				// Do not accept the new selection box location
1477 				// if it does not intersect with the bitmap rectangle
1478 				fSelectionRect = newSelection;
1479 				Invalidate();
1480 			}
1481 		}
1482 
1483 		AnimateSelection(true);
1484 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1485 		MergeSelection();
1486 			// If there is an existing selection,
1487 			// Make it part of the background image
1488 
1489 		// begin new selection
1490 		SetHasSelection(true);
1491 		fMakesSelection = true;
1492 		SetMouseEventMask(B_POINTER_EVENTS);
1493 		ConstrainToImage(point);
1494 		fFirstPoint = point;
1495 		fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1496 		fSelectionRect = fCopyFromRect;
1497 		Invalidate();
1498 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1499 		ShowPopUpMenu(ConvertToScreen(position));
1500 	} else if (buttons == B_TERTIARY_MOUSE_BUTTON) {
1501 		// move image in window
1502 		SetMouseEventMask(B_POINTER_EVENTS);
1503 		fMovesImage = true;
1504 		fFirstPoint = ConvertToScreen(position);
1505 	}
1506 }
1507 
1508 
1509 void
1510 ShowImageView::UpdateSelectionRect(BPoint point, bool final)
1511 {
1512 	BRect oldSelection = fCopyFromRect;
1513 	point = ViewToImage(point);
1514 	ConstrainToImage(point);
1515 	fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
1516 	fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
1517 	fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
1518 	fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
1519 	fSelectionRect = fCopyFromRect;
1520 
1521 	if (final) {
1522 		// selection must be at least 2 pixels wide or 2 pixels tall
1523 		if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
1524 			SetHasSelection(false);
1525 	} else
1526 		UpdateStatusText();
1527 
1528 	if (oldSelection != fCopyFromRect || !HasSelection()) {
1529 		BRect updateRect;
1530 		updateRect = oldSelection | fCopyFromRect;
1531 		updateRect = ImageToView(updateRect);
1532 		updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE);
1533 		Invalidate(updateRect);
1534 	}
1535 }
1536 
1537 
1538 void
1539 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message)
1540 {
1541 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1542 	if (fMakesSelection) {
1543 		UpdateSelectionRect(point, false);
1544 	} else if (fMovesImage) {
1545 		MoveImage();
1546 	}
1547 }
1548 
1549 
1550 void
1551 ShowImageView::MouseUp(BPoint point)
1552 {
1553 	if (fMakesSelection) {
1554 		UpdateSelectionRect(point, true);
1555 		fMakesSelection = false;
1556 	} else if (fMovesImage) {
1557 		MoveImage();
1558 		fMovesImage = false;
1559 	}
1560 	AnimateSelection(true);
1561 }
1562 
1563 
1564 float
1565 ShowImageView::LimitToRange(float v, orientation o, bool absolute)
1566 {
1567 	BScrollBar* psb = ScrollBar(o);
1568 	if (psb) {
1569 		float min, max, pos;
1570 		pos = v;
1571 		if (!absolute)
1572 			pos += psb->Value();
1573 
1574 		psb->GetRange(&min, &max);
1575 		if (pos < min)
1576 			pos = min;
1577 		else if (pos > max)
1578 			pos = max;
1579 
1580 		v = pos;
1581 		if (!absolute)
1582 			v -= psb->Value();
1583 	}
1584 	return v;
1585 }
1586 
1587 
1588 void
1589 ShowImageView::ScrollRestricted(float x, float y, bool absolute)
1590 {
1591 	if (x != 0)
1592 		x = LimitToRange(x, B_HORIZONTAL, absolute);
1593 
1594 	if (y != 0)
1595 		y = LimitToRange(y, B_VERTICAL, absolute);
1596 
1597 	// hide the caption when using mouse wheel
1598 	// in full screen mode
1599 	// to prevent the caption from dirtying up the image
1600 	// during scrolling.
1601 	bool caption = fShowCaption;
1602 	if (caption) {
1603 		fShowCaption = false;
1604 		UpdateCaption();
1605 	}
1606 
1607 	ScrollBy(x, y);
1608 
1609 	if (caption) {
1610 		// show the caption again
1611 		fShowCaption = true;
1612 		UpdateCaption();
1613 	}
1614 }
1615 
1616 
1617 // XXX method is not unused
1618 void
1619 ShowImageView::ScrollRestrictedTo(float x, float y)
1620 {
1621 	ScrollRestricted(x, y, true);
1622 }
1623 
1624 
1625 void
1626 ShowImageView::ScrollRestrictedBy(float x, float y)
1627 {
1628 	ScrollRestricted(x, y, false);
1629 }
1630 
1631 
1632 void
1633 ShowImageView::KeyDown(const char* bytes, int32 numBytes)
1634 {
1635 	if (numBytes != 1) {
1636 		BView::KeyDown(bytes, numBytes);
1637 		return;
1638 	}
1639 
1640 	switch (*bytes) {
1641 		case B_DOWN_ARROW:
1642 			ScrollRestrictedBy(0, 10);
1643 			break;
1644 		case B_UP_ARROW:
1645 			ScrollRestrictedBy(0, -10);
1646 			break;
1647 		case B_LEFT_ARROW:
1648 			ScrollRestrictedBy(-10, 0);
1649 			break;
1650 		case B_RIGHT_ARROW:
1651 			ScrollRestrictedBy(10, 0);
1652 			break;
1653 		case B_ENTER:
1654 			SendMessageToWindow(MSG_FILE_NEXT);
1655 			break;
1656 		case B_BACKSPACE:
1657 			SendMessageToWindow(MSG_FILE_PREV);
1658 			break;
1659 		case B_HOME:
1660 			break;
1661 		case B_END:
1662 			break;
1663 		case B_SPACE:
1664 			ToggleSlideShow();
1665 			break;
1666 		case B_ESCAPE:
1667 			// stop slide show
1668 			if (fSlideShow)
1669 				ToggleSlideShow();
1670 
1671 			ExitFullScreen();
1672 
1673 			ClearSelection();
1674 			break;
1675 		case B_DELETE:
1676 		{
1677 			// Move image to Trash
1678 			BMessage trash(BPrivate::kMoveToTrash);
1679 			trash.AddRef("refs", &fCurrentRef);
1680 			// We create our own messenger because the member fTrackerMessenger
1681 			// could be invalid
1682 			BMessenger tracker(kTrackerSignature);
1683 			if (tracker.SendMessage(&trash) == B_OK)
1684 				if (!NextFile()) {
1685 					// This is the last (or only file) in this directory,
1686 					// close the window
1687 					SendMessageToWindow(B_QUIT_REQUESTED);
1688 				}
1689 			break;
1690 		}
1691 		case '+':
1692 		case '=':
1693 			ZoomIn();
1694 			break;
1695 		case '-':
1696 			ZoomOut();
1697 			break;
1698 	}
1699 }
1700 
1701 
1702 void
1703 ShowImageView::MouseWheelChanged(BMessage *msg)
1704 {
1705 	// The BeOS driver does not currently support
1706 	// X wheel scrolling, therefore, dx is zero.
1707 	// |dy| is the number of notches scrolled up or down.
1708 	// When the wheel is scrolled down (towards the user) dy > 0
1709 	// When the wheel is scrolled up (away from the user) dy < 0
1710 	const float kscrollBy = 40;
1711 	float dy, dx;
1712 	float x, y;
1713 	x = 0; y = 0;
1714 	if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK)
1715 		x = dx * kscrollBy;
1716 	if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK)
1717 		y = dy * kscrollBy;
1718 
1719 	ScrollRestrictedBy(x, y);
1720 }
1721 
1722 
1723 void
1724 ShowImageView::ShowPopUpMenu(BPoint screen)
1725 {
1726 	BPopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
1727 
1728 	ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
1729 	if (showImage)
1730 		showImage->BuildContextMenu(menu);
1731 
1732 	screen -= BPoint(10, 10);
1733 	menu->Go(screen, true, false, true);
1734 	fShowingPopUpMenu = true;
1735 }
1736 
1737 
1738 void
1739 ShowImageView::SettingsSetBool(const char* name, bool value)
1740 {
1741 	ShowImageSettings* settings;
1742 	settings = my_app->Settings();
1743 	if (settings->Lock()) {
1744 		settings->SetBool(name, value);
1745 		settings->Unlock();
1746 	}
1747 }
1748 
1749 
1750 void
1751 ShowImageView::MessageReceived(BMessage *message)
1752 {
1753 	switch (message->what) {
1754 		case MSG_SELECTION_BITMAP:
1755 		{
1756 			// In response to a B_SIMPLE_DATA message, a view will
1757 			// send this message and expect a reply with a pointer to
1758 			// the currently selected bitmap clip. Although this view
1759 			// allocates the BBitmap * sent in the reply, it is only
1760 			// to be used and deleted by the view that is being replied to.
1761 			BMessage msg;
1762 			msg.AddPointer("be:_bitmap_ptr", CopySelection());
1763 			message->SendReply(&msg);
1764 			break;
1765 		}
1766 
1767 		case B_SIMPLE_DATA:
1768 			if (message->WasDropped()) {
1769 				uint32 type;
1770 				int32 count;
1771 				status_t ret = message->GetInfo("refs", &type, &count);
1772 				if (ret == B_OK && type == B_REF_TYPE) {
1773 					// If file was dropped, open it as the selection
1774 					entry_ref ref;
1775 					if (message->FindRef("refs", 0, &ref) == B_OK) {
1776 						BPoint point = message->DropPoint();
1777 						point = ConvertFromScreen(point);
1778 						point = ViewToImage(point);
1779 						SetSelection(&ref, point);
1780 					}
1781 				} else {
1782 					// If a user drags a clip from another ShowImage window,
1783 					// request a BBitmap pointer to that clip, allocated by the
1784 					// other view, for use solely by this view, so that it can
1785 					// be dropped/pasted onto this view.
1786 					BMessenger retMsgr, localMsgr(this);
1787 					retMsgr = message->ReturnAddress();
1788 					if (retMsgr != localMsgr) {
1789 						BMessage msgReply;
1790 						retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply);
1791 						BBitmap *bitmap = NULL;
1792 						if (msgReply.FindPointer("be:_bitmap_ptr",
1793 							reinterpret_cast<void **>(&bitmap)) == B_OK) {
1794 							BRect sourceRect;
1795 							BPoint point, sourcePoint;
1796 							message->FindPoint("be:_source_point", &sourcePoint);
1797 							message->FindRect("be:_frame", &sourceRect);
1798 							point = message->DropPoint();
1799 							point.Set(point.x - (sourcePoint.x - sourceRect.left),
1800 								point.y - (sourcePoint.y - sourceRect.top));
1801 								// adjust drop point before scaling is factored in
1802 							point = ConvertFromScreen(point);
1803 							point = ViewToImage(point);
1804 
1805 							PasteBitmap(bitmap, point);
1806 						}
1807 					}
1808 				}
1809 			}
1810 			break;
1811 
1812 		case B_COPY_TARGET:
1813 			HandleDrop(message);
1814 			break;
1815 		case B_MOUSE_WHEEL_CHANGED:
1816 			MouseWheelChanged(message);
1817 			break;
1818 		case MSG_INVALIDATE:
1819 			Invalidate();
1820 			break;
1821 
1822 		case kMsgPopUpMenuClosed:
1823 			fShowingPopUpMenu = false;
1824 			break;
1825 
1826 		default:
1827 			BView::MessageReceived(message);
1828 			break;
1829 	}
1830 }
1831 
1832 
1833 void
1834 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength)
1835 {
1836 	float prop, range;
1837 	BScrollBar *psb;
1838 
1839 	psb = ScrollBar(o);
1840 	if (psb) {
1841 		range = bitmapLength - viewLength;
1842 		if (range < 0.0) {
1843 			range = 0.0;
1844 		}
1845 		prop = viewLength / bitmapLength;
1846 		if (prop > 1.0) {
1847 			prop = 1.0;
1848 		}
1849 		psb->SetRange(0, range);
1850 		psb->SetProportion(prop);
1851 		psb->SetSteps(10, 100);
1852 	}
1853 }
1854 
1855 
1856 void
1857 ShowImageView::FixupScrollBars()
1858 {
1859 	BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
1860 	if (fBitmap) {
1861 		rctbitmap = AlignBitmap();
1862 		rctbitmap.OffsetTo(0, 0);
1863 	}
1864 
1865 	FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width());
1866 	FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height());
1867 }
1868 
1869 
1870 int32
1871 ShowImageView::CurrentPage()
1872 {
1873 	return fDocumentIndex;
1874 }
1875 
1876 
1877 int32
1878 ShowImageView::PageCount()
1879 {
1880 	return fDocumentCount;
1881 }
1882 
1883 
1884 void
1885 ShowImageView::Undo()
1886 {
1887 	int32 undoType = fUndo.GetType();
1888 	if (undoType != UNDO_UNDO && undoType != UNDO_REDO)
1889 		return;
1890 
1891 	// backup current selection
1892 	BRect undoneSelRect;
1893 	BBitmap *undoneSelection;
1894 	undoneSelRect = fSelectionRect;
1895 	undoneSelection = CopySelection();
1896 
1897 	if (undoType == UNDO_UNDO) {
1898 		BBitmap *undoRestore;
1899 		undoRestore = fUndo.GetRestoreBitmap();
1900 		if (undoRestore)
1901 			MergeWithBitmap(undoRestore, fUndo.GetRect());
1902 	}
1903 
1904 	// restore previous image/selection
1905 	BBitmap *undoSelection;
1906 	undoSelection = fUndo.GetSelectionBitmap();
1907 		// NOTE: ShowImageView is responsible for deleting this bitmap
1908 		// (Which it will, as it would with a fSelBitmap that it allocated itself)
1909 	if (!undoSelection)
1910 		SetHasSelection(false);
1911 	else {
1912 		fCopyFromRect = BRect();
1913 		fSelectionRect = fUndo.GetRect();
1914 		SetHasSelection(true);
1915 		fSelBitmap = undoSelection;
1916 	}
1917 
1918 	fUndo.Undo(undoneSelRect, NULL, undoneSelection);
1919 
1920 	Invalidate();
1921 }
1922 
1923 
1924 void
1925 ShowImageView::AddWhiteRect(BRect &rect)
1926 {
1927 	// Paint white rectangle, using rect, into the background image
1928 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1929 	BBitmap *bitmap = new(nothrow) BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
1930 	if (bitmap == NULL || !bitmap->IsValid()) {
1931 		delete bitmap;
1932 		return;
1933 	}
1934 
1935 	if (bitmap->Lock()) {
1936 		bitmap->AddChild(&view);
1937 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1938 
1939 		view.FillRect(rect, B_SOLID_LOW);
1940 			// draw white rect
1941 
1942 		view.Sync();
1943 		bitmap->RemoveChild(&view);
1944 		bitmap->Unlock();
1945 
1946 		DeleteBitmap();
1947 		fBitmap = bitmap;
1948 
1949 		SendMessageToWindow(MSG_MODIFIED);
1950 	} else
1951 		delete bitmap;
1952 }
1953 
1954 
1955 void
1956 ShowImageView::RemoveSelection(bool toClipboard)
1957 {
1958 	if (!HasSelection())
1959 		return;
1960 
1961 	BRect rect = fSelectionRect;
1962 	bool cutBackground = (fSelBitmap) ? false : true;
1963 	BBitmap *selection, *restore = NULL;
1964 	selection = CopySelection();
1965 
1966 	if (toClipboard)
1967 		CopySelectionToClipboard();
1968 
1969 	SetHasSelection(false);
1970 
1971 	if (cutBackground) {
1972 		// If the user hasn't dragged the selection,
1973 		// paint a white rectangle where the selection was
1974 		restore = CopyFromRect(rect);
1975 		AddWhiteRect(rect);
1976 	}
1977 
1978 	fUndo.SetTo(rect, restore, selection);
1979 	Invalidate();
1980 }
1981 
1982 
1983 void
1984 ShowImageView::Cut()
1985 {
1986 	// Copy the selection to the clipboard,
1987 	// then remove it
1988 	RemoveSelection(true);
1989 }
1990 
1991 
1992 status_t
1993 ShowImageView::PasteBitmap(BBitmap *bitmap, BPoint point)
1994 {
1995 	if (bitmap && bitmap->IsValid()) {
1996 		MergeSelection();
1997 
1998 		fCopyFromRect = BRect();
1999 		fSelectionRect = bitmap->Bounds();
2000 		SetHasSelection(true);
2001 		fSelBitmap = bitmap;
2002 
2003 		BRect offsetRect = fSelectionRect;
2004 		offsetRect.OffsetBy(point);
2005 		if (fBitmap->Bounds().Intersects(offsetRect))
2006 			// Move the selection rectangle to desired origin,
2007 			// but only if the resulting selection rectangle
2008 			// intersects with the background bitmap rectangle
2009 			fSelectionRect = offsetRect;
2010 
2011 		Invalidate();
2012 
2013 		return B_OK;
2014 	}
2015 
2016 	return B_ERROR;
2017 }
2018 
2019 
2020 void
2021 ShowImageView::Paste()
2022 {
2023 	if (be_clipboard->Lock()) {
2024 		BMessage *pclip;
2025 		if ((pclip = be_clipboard->Data()) != NULL) {
2026 			BPoint point(0, 0);
2027 			pclip->FindPoint("be:location", &point);
2028 			BBitmap *pbits;
2029 			pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip));
2030 			PasteBitmap(pbits, point);
2031 		}
2032 
2033 		be_clipboard->Unlock();
2034 	}
2035 }
2036 
2037 
2038 void
2039 ShowImageView::SelectAll()
2040 {
2041 	SetHasSelection(true);
2042 	fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height());
2043 	fSelectionRect = fCopyFromRect;
2044 	Invalidate();
2045 }
2046 
2047 
2048 void
2049 ShowImageView::ClearSelection()
2050 {
2051 	// Remove the selection,
2052 	// DON'T copy it to the clipboard
2053 	RemoveSelection(false);
2054 }
2055 
2056 
2057 void
2058 ShowImageView::SetHasSelection(bool bHasSelection)
2059 {
2060 	DeleteSelBitmap();
2061 	fHasSelection = bHasSelection;
2062 
2063 	UpdateStatusText();
2064 
2065 	BMessage msg(MSG_SELECTION);
2066 	msg.AddBool("has_selection", fHasSelection);
2067 	SendMessageToWindow(&msg);
2068 }
2069 
2070 
2071 void
2072 ShowImageView::CopySelectionToClipboard()
2073 {
2074 	if (HasSelection() && be_clipboard->Lock()) {
2075 		be_clipboard->Clear();
2076 		BMessage *clip = NULL;
2077 		if ((clip = be_clipboard->Data()) != NULL) {
2078 			BMessage data;
2079 			BBitmap* bitmap = CopySelection();
2080 			if (bitmap != NULL) {
2081 				#if 0
2082 				// According to BeBook and Becasso, Gobe Productive do the following.
2083 				// Paste works in Productive, but not in Becasso and original ShowImage.
2084 				BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused
2085 				bitmap->Archive(&msg);
2086 				clip->AddMessage("image/x-be-bitmap", &msg);
2087 				#else
2088 				// original ShowImage performs this. Paste works with original ShowImage.
2089 				bitmap->Archive(clip);
2090 				// original ShowImage uses be:location for insertion point
2091 				clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top));
2092 				#endif
2093 				delete bitmap;
2094 				be_clipboard->Commit();
2095 			}
2096 		}
2097 		be_clipboard->Unlock();
2098 	}
2099 }
2100 
2101 
2102 void
2103 ShowImageView::FirstPage()
2104 {
2105 	if (fDocumentIndex != 1) {
2106 		fDocumentIndex = 1;
2107 		SetImage(NULL);
2108 	}
2109 }
2110 
2111 
2112 void
2113 ShowImageView::LastPage()
2114 {
2115 	if (fDocumentIndex != fDocumentCount) {
2116 		fDocumentIndex = fDocumentCount;
2117 		SetImage(NULL);
2118 	}
2119 }
2120 
2121 
2122 void
2123 ShowImageView::NextPage()
2124 {
2125 	if (fDocumentIndex < fDocumentCount) {
2126 		fDocumentIndex++;
2127 		SetImage(NULL);
2128 	}
2129 }
2130 
2131 
2132 void
2133 ShowImageView::PrevPage()
2134 {
2135 	if (fDocumentIndex > 1) {
2136 		fDocumentIndex--;
2137 		SetImage(NULL);
2138 	}
2139 }
2140 
2141 
2142 int
2143 ShowImageView::CompareEntries(const void* a, const void* b)
2144 {
2145 	entry_ref *r1, *r2;
2146 	r1 = *(entry_ref**)a;
2147 	r2 = *(entry_ref**)b;
2148 	return strcasecmp(r1->name, r2->name);
2149 }
2150 
2151 
2152 void
2153 ShowImageView::GoToPage(int32 page)
2154 {
2155 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
2156 		fDocumentIndex = page;
2157 		SetImage(NULL);
2158 	}
2159 }
2160 
2161 
2162 void
2163 ShowImageView::FreeEntries(BList* entries)
2164 {
2165 	const int32 n = entries->CountItems();
2166 	for (int32 i = 0; i < n; i ++) {
2167 		entry_ref* ref = (entry_ref*)entries->ItemAt(i);
2168 		delete ref;
2169 	}
2170 	entries->MakeEmpty();
2171 }
2172 
2173 
2174 void
2175 ShowImageView::SetTrackerSelectionToCurrent()
2176 {
2177 	BMessage setsel(B_SET_PROPERTY);
2178 	setsel.AddSpecifier("Selection");
2179 	setsel.AddRef("data", &fCurrentRef);
2180 	fTrackerMessenger.SendMessage(&setsel);
2181 }
2182 
2183 
2184 bool
2185 ShowImageView::FindNextImageByDir(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind)
2186 {
2187 	ASSERT(next || !rewind);
2188 	BEntry curImage(in_current);
2189 	entry_ref entry, *ref;
2190 	BDirectory parent;
2191 	BList entries;
2192 	bool found = false;
2193 	int32 cur;
2194 
2195 	if (curImage.GetParent(&parent) != B_OK) {
2196 		return false;
2197 	}
2198 
2199 	// insert current ref, so we can find it easily after sorting
2200 	entries.AddItem(in_current);
2201 
2202 	while (parent.GetNextRef(&entry) == B_OK) {
2203 		if (entry != *in_current) {
2204 			entries.AddItem(new entry_ref(entry));
2205 		}
2206 	}
2207 
2208 	entries.SortItems(CompareEntries);
2209 
2210 	cur = entries.IndexOf(in_current);
2211 	ASSERT(cur >= 0);
2212 
2213 	// remove it so FreeEntries() does not delete it
2214 	entries.RemoveItem(in_current);
2215 
2216 	if (next) {
2217 		// find the next image in the list
2218 		if (rewind) cur = 0; // start with first
2219 		for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) {
2220 			if (IsImage(ref)) {
2221 				found = true;
2222 				*out_image = (const entry_ref)*ref;
2223 				break;
2224 			}
2225 		}
2226 	} else {
2227 		// find the previous image in the list
2228 		cur --;
2229 		for (; cur >= 0; cur --) {
2230 			ref = (entry_ref*)entries.ItemAt(cur);
2231 			if (IsImage(ref)) {
2232 				found = true;
2233 				*out_image = (const entry_ref)*ref;
2234 				break;
2235 			}
2236 		}
2237 	}
2238 
2239 	FreeEntries(&entries);
2240 	return found;
2241 }
2242 
2243 bool
2244 ShowImageView::FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, bool rewind)
2245 {
2246 	// Based on similar function from BeMail!
2247 	if (!fTrackerMessenger.IsValid())
2248 		// If tracker scripting is not available,
2249 		// fall back on directory searching code
2250 		return FindNextImageByDir(in_current, ref, next, rewind);
2251 
2252 	//
2253 	//	Ask the Tracker what the next/prev file in the window is.
2254 	//	Continue asking for the next reference until a valid
2255 	//	image is found.
2256 	//
2257 	entry_ref nextRef = *in_current;
2258 	bool foundRef = false;
2259 	while (!foundRef)
2260 	{
2261 		BMessage request(B_GET_PROPERTY);
2262 		BMessage spc;
2263 		if (rewind)
2264 			spc.what = B_DIRECT_SPECIFIER;
2265 		else if (next)
2266 			spc.what = 'snxt';
2267 		else
2268 			spc.what = 'sprv';
2269 		spc.AddString("property", "Entry");
2270 		if (rewind)
2271 			// if rewinding, ask for the ref to the
2272 			// first item in the directory
2273 			spc.AddInt32("data", 0);
2274 		else
2275 			spc.AddRef("data", &nextRef);
2276 		request.AddSpecifier(&spc);
2277 
2278 		BMessage reply;
2279 		if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
2280 			return FindNextImageByDir(in_current, ref, next, rewind);;
2281 		if (reply.FindRef("result", &nextRef) != B_OK)
2282 			return FindNextImageByDir(in_current, ref, next, rewind);;
2283 
2284 		if (IsImage(&nextRef))
2285 			foundRef = true;
2286 
2287 		rewind = false;
2288 			// stop asking for the first ref in the directory
2289 	}
2290 
2291 	*ref = nextRef;
2292 	return foundRef;
2293 }
2294 
2295 bool
2296 ShowImageView::ShowNextImage(bool next, bool rewind)
2297 {
2298 	entry_ref curRef = fCurrentRef;
2299 	entry_ref imgRef;
2300 	bool found = FindNextImage(&curRef, &imgRef, next, rewind);
2301 	if (found) {
2302 		// Keep trying to load images until:
2303 		// 1. The image loads successfully
2304 		// 2. The last file in the directory is found (for find next or find first)
2305 		// 3. The first file in the directory is found (for find prev)
2306 		// 4. The call to FindNextImage fails for any other reason
2307 		while (SetImage(&imgRef) != B_OK) {
2308 			curRef = imgRef;
2309 			found = FindNextImage(&curRef, &imgRef, next, false);
2310 			if (!found)
2311 				return false;
2312 		}
2313 		SetTrackerSelectionToCurrent();
2314 		return true;
2315 	}
2316 	return false;
2317 }
2318 
2319 
2320 bool
2321 ShowImageView::NextFile()
2322 {
2323 	return ShowNextImage(true, false);
2324 }
2325 
2326 
2327 bool
2328 ShowImageView::PrevFile()
2329 {
2330 	return ShowNextImage(false, false);
2331 }
2332 
2333 
2334 bool
2335 ShowImageView::HasNextFile()
2336 {
2337 	entry_ref ref;
2338 	return FindNextImage(&fCurrentRef, &ref, true, false);
2339 }
2340 
2341 
2342 bool
2343 ShowImageView::HasPrevFile()
2344 {
2345 	entry_ref ref;
2346 	return FindNextImage(&fCurrentRef, &ref, false, false);
2347 }
2348 
2349 
2350 bool
2351 ShowImageView::FirstFile()
2352 {
2353 	return ShowNextImage(true, true);
2354 }
2355 
2356 
2357 void
2358 ShowImageView::SetZoom(float zoom)
2359 {
2360 	if ((fScaleBilinear || fDither) && fZoom != zoom) {
2361 		DeleteScaler();
2362 	}
2363 	fZoom = zoom;
2364 	FixupScrollBars();
2365 	Invalidate();
2366 }
2367 
2368 
2369 void
2370 ShowImageView::ZoomIn()
2371 {
2372 	if (fZoom < 16)
2373 		SetZoom(fZoom + 0.25);
2374 }
2375 
2376 
2377 void
2378 ShowImageView::ZoomOut()
2379 {
2380 	if (fZoom > 0.25)
2381 		SetZoom(fZoom - 0.25);
2382 }
2383 
2384 
2385 void
2386 ShowImageView::SetSlideShowDelay(float seconds)
2387 {
2388 	ShowImageSettings* settings;
2389 	int32 delay = (int)(seconds * 10.0);
2390 	if (fSlideShowDelay != delay) {
2391 		// update counter
2392 		fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown);
2393 		if (fSlideShowCountDown <= 0) {
2394 			// show next image on next Pulse()
2395 			fSlideShowCountDown = 1;
2396 		}
2397 		fSlideShowDelay = delay;
2398 		settings = my_app->Settings();
2399 		if (settings->Lock()) {
2400 			settings->SetInt32("SlideShowDelay", fSlideShowDelay);
2401 			settings->Unlock();
2402 		}
2403 	}
2404 }
2405 
2406 
2407 void
2408 ShowImageView::StartSlideShow()
2409 {
2410 	fSlideShow = true; fSlideShowCountDown = fSlideShowDelay;
2411 }
2412 
2413 
2414 void
2415 ShowImageView::StopSlideShow()
2416 {
2417 	fSlideShow = false;
2418 }
2419 
2420 
2421 void
2422 ShowImageView::DoImageOperation(ImageProcessor::operation op, bool quiet)
2423 {
2424 	BMessenger msgr;
2425 	ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
2426 	imageProcessor.Start(false);
2427 	BBitmap* bm = imageProcessor.DetachBitmap();
2428 	if (bm == NULL) {
2429 		// operation failed
2430 		return;
2431 	}
2432 
2433 	// update orientation state
2434 	if (op != ImageProcessor::kInvert) {
2435 		// Note: If one of these fails, check its definition in class ImageProcessor.
2436 		ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations);
2437 		ASSERT(ImageProcessor::kRotateCounterClockwise < ImageProcessor::kNumberOfAffineTransformations);
2438 		ASSERT(ImageProcessor::kFlipLeftToRight < ImageProcessor::kNumberOfAffineTransformations);
2439 		ASSERT(ImageProcessor::kFlipTopToBottom < ImageProcessor::kNumberOfAffineTransformations);
2440 		fImageOrientation = fTransformation[op][fImageOrientation];
2441 	} else {
2442 		fInverted = !fInverted;
2443 	}
2444 
2445 	if (!quiet) {
2446 		// write orientation state
2447 		BNode node(&fCurrentRef);
2448 		int32 orientation = fImageOrientation;
2449 		if (fInverted) orientation += 256;
2450 		if (orientation != k0) {
2451 			node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation));
2452 		} else {
2453 			node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
2454 		}
2455 	}
2456 
2457 	// set new bitmap
2458 	DeleteBitmap();
2459 	fBitmap = bm;
2460 
2461 	if (!quiet) {
2462 		// remove selection
2463 		SetHasSelection(false);
2464 		Notify();
2465 	}
2466 }
2467 
2468 
2469 //! image operation initiated by user
2470 void
2471 ShowImageView::UserDoImageOperation(ImageProcessor::operation op, bool quiet)
2472 {
2473 	fUndo.Clear();
2474 	DoImageOperation(op, quiet);
2475 }
2476 
2477 
2478 void
2479 ShowImageView::Rotate(int degree)
2480 {
2481 	if (degree == 90) {
2482 		UserDoImageOperation(ImageProcessor::kRotateClockwise);
2483 	} else if (degree == 270) {
2484 		UserDoImageOperation(ImageProcessor::kRotateCounterClockwise);
2485 	}
2486 }
2487 
2488 
2489 void
2490 ShowImageView::Flip(bool vertical)
2491 {
2492 	if (vertical) {
2493 		UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
2494 	} else {
2495 		UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
2496 	}
2497 }
2498 
2499 
2500 void
2501 ShowImageView::Invert()
2502 {
2503 	if (fBitmap->ColorSpace() != B_CMAP8) {
2504 		// Only allow an invert operation if the
2505 		// bitmap color space is supported by the
2506 		// invert algorithm
2507 		UserDoImageOperation(ImageProcessor::kInvert);
2508 	}
2509 }
2510 
2511 void
2512 ShowImageView::ResizeImage(int w, int h)
2513 {
2514 	if (fBitmap == NULL || w < 1 || h < 1)
2515 		return;
2516 
2517 	Scaler scaler(fBitmap, BRect(0, 0, w-1, h-1), BMessenger(), 0, false);
2518 	scaler.Start(false);
2519 	BBitmap* scaled = scaler.DetachBitmap();
2520 	if (scaled == NULL) {
2521 		// operation failed
2522 		return;
2523 	}
2524 
2525 	// remove selection
2526 	SetHasSelection(false);
2527 	fUndo.Clear();
2528 	DeleteBitmap();
2529 	fBitmap = scaled;
2530 
2531 	SendMessageToWindow(MSG_MODIFIED);
2532 
2533 	Notify();
2534 }
2535 
2536 void
2537 ShowImageView::SetIcon(bool clear, icon_size which)
2538 {
2539 	int32 size;
2540 	switch (which) {
2541 		case B_MINI_ICON: size = 16;
2542 			break;
2543 		case B_LARGE_ICON: size = 32;
2544 			break;
2545 		default:
2546 			return;
2547 	}
2548 
2549 	BRect rect(fBitmap->Bounds());
2550 	float s;
2551 	s = size / (rect.Width()+1.0);
2552 
2553 	if (s * (rect.Height()+1.0) <= size) {
2554 		rect.right = size-1;
2555 		rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
2556 		// center vertically
2557 		rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
2558 	} else {
2559 		s = size / (rect.Height()+1.0);
2560 		rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
2561 		rect.bottom = size-1;
2562 		// center horizontally
2563 		rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
2564 	}
2565 
2566 	// scale bitmap to thumbnail size
2567 	BMessenger msgr;
2568 	Scaler scaler(fBitmap, rect, msgr, 0, true);
2569 	BBitmap* thumbnail = scaler.GetBitmap();
2570 	scaler.Start(false);
2571 	ASSERT(thumbnail->ColorSpace() == B_CMAP8);
2572 	// create icon from thumbnail
2573 	BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8);
2574 	memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
2575 	BScreen screen;
2576 	const uchar* src = (uchar*)thumbnail->Bits();
2577 	uchar* dest = (uchar*)icon.Bits();
2578 	const int32 srcBPR = thumbnail->BytesPerRow();
2579 	const int32 destBPR = icon.BytesPerRow();
2580 	const int32 dx = (int32)rect.left;
2581 	const int32 dy = (int32)rect.top;
2582 
2583 	for (int32 y = 0; y <= rect.IntegerHeight(); y ++) {
2584 		for (int32 x = 0; x <= rect.IntegerWidth(); x ++) {
2585 			const uchar* s = src + y * srcBPR + x;
2586 			uchar* d = dest + (y+dy) * destBPR + (x+dx);
2587 			*d = *s;
2588 		}
2589 	}
2590 
2591 	// set icon
2592 	BNode node(&fCurrentRef);
2593 	BNodeInfo info(&node);
2594 	info.SetIcon(clear ? NULL : &icon, which);
2595 }
2596 
2597 
2598 void
2599 ShowImageView::SetIcon(bool clear)
2600 {
2601 	SetIcon(clear, B_MINI_ICON);
2602 	SetIcon(clear, B_LARGE_ICON);
2603 }
2604 
2605 
2606 void
2607 ShowImageView::ToggleSlideShow()
2608 {
2609 	SendMessageToWindow(MSG_SLIDE_SHOW);
2610 }
2611 
2612 
2613 void
2614 ShowImageView::ExitFullScreen()
2615 {
2616 	be_app->ShowCursor();
2617 	SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
2618 }
2619 
2620 
2621 void
2622 ShowImageView::WindowActivated(bool active)
2623 {
2624 	fIsActiveWin = active;
2625 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
2626 }
2627 
2628