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