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