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