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