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