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