xref: /haiku/src/apps/showimage/ShowImageView.cpp (revision d5cd5d63ff0ad395989db6cf4841a64d5b545d1d)
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 <Debug.h>
31 #include <Message.h>
32 #include <ScrollBar.h>
33 #include <StopWatch.h>
34 #include <Alert.h>
35 #include <MenuBar.h>
36 #include <MenuItem.h>
37 #include <File.h>
38 #include <Bitmap.h>
39 #include <TranslatorRoster.h>
40 #include <BitmapStream.h>
41 #include <Rect.h>
42 #include <SupportDefs.h>
43 #include <Directory.h>
44 #include <Application.h>
45 #include <Roster.h>
46 #include <NodeInfo.h>
47 #include <Clipboard.h>
48 #include <Path.h>
49 #include <PopUpMenu.h>
50 #include <Region.h>
51 
52 
53 #include "ShowImageApp.h"
54 #include "ShowImageConstants.h"
55 #include "ShowImageView.h"
56 #include "ShowImageWindow.h"
57 
58 #ifndef min
59 #define min(a,b) ((a)>(b)?(b):(a))
60 #endif
61 #ifndef max
62 #define max(a,b) ((a)>(b)?(a):(b))
63 #endif
64 
65 #define BORDER_WIDTH 16
66 #define BORDER_HEIGHT 16
67 #define PEN_SIZE 1.0f
68 const rgb_color kborderColor = { 0, 0, 0, 255 };
69 
70 // use patterns to simulate marching ants for selection
71 void
72 ShowImageView::InitPatterns()
73 {
74 	uchar p;
75 	uchar p1 = 0x33;
76 	uchar p2 = 0xCC;
77 	for (int i = 0; i <= 7; i ++) {
78 		fPatternLeft.data[i] = p1;
79 		fPatternRight.data[i] = p2;
80 		if ((i / 2) % 2 == 0) {
81 			p = 255;
82 		} else {
83 			p = 0;
84 		}
85 		fPatternUp.data[i] = p;
86 		fPatternDown.data[i] = ~p;
87 	}
88 }
89 
90 void
91 ShowImageView::RotatePatterns()
92 {
93 	int i;
94 	uchar p;
95 	bool set;
96 
97 	// rotate up
98 	p = fPatternUp.data[0];
99 	for (i = 0; i <= 6; i ++) {
100 		fPatternUp.data[i] = fPatternUp.data[i+1];
101 	}
102 	fPatternUp.data[7] = p;
103 
104 	// rotate down
105 	p = fPatternDown.data[7];
106 	for (i = 7; i >= 1; i --) {
107 		fPatternDown.data[i] = fPatternDown.data[i-1];
108 	}
109 	fPatternDown.data[0] = p;
110 
111 	// rotate to left
112 	p = fPatternLeft.data[0];
113 	set = (p & 0x80) != 0;
114 	p <<= 1;
115 	p &= 0xfe;
116 	if (set) p |= 1;
117 	memset(fPatternLeft.data, p, 8);
118 
119 	// rotate to right
120 	p = fPatternRight.data[0];
121 	set = (p & 1) != 0;
122 	p >>= 1;
123 	if (set) p |= 0x80;
124 	memset(fPatternRight.data, p, 8);
125 }
126 
127 void
128 ShowImageView::AnimateSelection(bool a)
129 {
130 	fAnimateSelection = a;
131 }
132 
133 void
134 ShowImageView::Pulse()
135 {
136 	// animate marching ants
137 	if (HasSelection() && fAnimateSelection && Window()->IsActive()) {
138 		RotatePatterns();
139 		DrawSelectionBox(fSelectionRect);
140 	}
141 	if (fSlideShow) {
142 		fSlideShowCountDown --;
143 		if (fSlideShowCountDown <= 0) {
144 			fSlideShowCountDown = fSlideShowDelay;
145 			if (!NextFile()) {
146 				FirstFile();
147 			}
148 		}
149 	}
150 }
151 
152 ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
153 	uint32 flags)
154 	: BView(rect, name, resizingMode, flags)
155 {
156 	ShowImageSettings* settings;
157 	settings = my_app->Settings();
158 
159 	InitPatterns();
160 	fBitmap = NULL;
161 	fSelBitmap = NULL;
162 	fDocumentIndex = 1;
163 	fDocumentCount = 1;
164 	fAnimateSelection = true;
165 	fbHasSelection = false;
166 	fShrinkToBounds = false;
167 	fZoomToBounds = false;
168 	fHasBorder = true;
169 	fHAlignment = B_ALIGN_LEFT;
170 	fVAlignment = B_ALIGN_TOP;
171 	fSlideShow = false;
172 	fSlideShowDelay = 3 * 10; // 3 seconds
173 	fShowCaption = false;
174 	fZoom = 1.0;
175 	fMovesImage = false;
176 	fScaleBilinear = false;
177 	fScaler = NULL;
178 
179 	if (settings->Lock()) {
180 		fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds);
181 		fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds);
182 		fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
183 		fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
184 		settings->Unlock();
185 	}
186 
187 	SetViewColor(B_TRANSPARENT_COLOR);
188 	SetHighColor(kborderColor);
189 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
190 	SetPenSize(PEN_SIZE);
191 }
192 
193 ShowImageView::~ShowImageView()
194 {
195 	DeleteBitmap();
196 }
197 
198 bool
199 ShowImageView::IsImage(const entry_ref *pref)
200 {
201 	BFile file(pref, B_READ_ONLY);
202 	translator_info info;
203 	memset(&info, 0, sizeof(translator_info));
204 	BMessage ioExtension;
205 
206 	BTranslatorRoster *proster = BTranslatorRoster::Default();
207 	if (!proster)
208 		return false;
209 
210 	if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
211 		return false;
212 
213 	if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
214 		B_TRANSLATOR_BITMAP) != B_OK)
215 		return false;
216 
217 	return true;
218 }
219 
220 // send message to parent about new image
221 void
222 ShowImageView::Notify(const char* status)
223 {
224 	BMessage msg(MSG_UPDATE_STATUS);
225 	if (status != NULL) {
226 		msg.AddString("status", status);
227 	}
228 	BMessenger msgr(Window());
229 	msgr.SendMessage(&msg);
230 
231 	FixupScrollBars();
232 	Invalidate();
233 }
234 
235 void
236 ShowImageView::AddToRecentDocuments()
237 {
238 	be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG);
239 	be_app_messenger.SendMessage(MSG_UPDATE_RECENT_DOCUMENTS);
240 }
241 
242 void
243 ShowImageView::DeleteScaler()
244 {
245 	if (fScaler) {
246 		fScaler->Stop();
247 		delete fScaler;
248 		fScaler = NULL;
249 	}
250 }
251 
252 void
253 ShowImageView::DeleteBitmap()
254 {
255 	DeleteScaler();
256 	DeleteSelBitmap();
257 	delete fBitmap;
258 	fBitmap = NULL;
259 }
260 
261 void ShowImageView::DeleteSelBitmap()
262 {
263 	delete fSelBitmap;
264 	fSelBitmap = NULL;
265 }
266 
267 void
268 ShowImageView::SetImage(const entry_ref *pref)
269 {
270 	DeleteBitmap();
271 	SetHasSelection(false);
272 	fMakesSelection = false;
273 
274 	entry_ref ref;
275 	if (!pref)
276 		ref = fCurrentRef;
277 	else
278 		ref = *pref;
279 
280 	BTranslatorRoster *proster = BTranslatorRoster::Default();
281 	if (!proster)
282 		return;
283 	BFile file(&ref, B_READ_ONLY);
284 	translator_info info;
285 	memset(&info, 0, sizeof(translator_info));
286 	BMessage ioExtension;
287 	if (ref != fCurrentRef)
288 		// if new image, reset to first document
289 		fDocumentIndex = 1;
290 	if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
291 		return;
292 	if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
293 		B_TRANSLATOR_BITMAP) != B_OK)
294 		return;
295 
296 	// Translate image data and create a new ShowImage window
297 	BBitmapStream outstream;
298 	if (proster->Translate(&file, &info, &ioExtension, &outstream,
299 		B_TRANSLATOR_BITMAP) != B_OK)
300 		return;
301 	if (outstream.DetachBitmap(&fBitmap) != B_OK)
302 		return;
303 	fCurrentRef = ref;
304 
305 	// get the number of documents (pages) if it has been supplied
306 	int32 documentCount = 0;
307 	if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
308 		documentCount > 0)
309 		fDocumentCount = documentCount;
310 	else
311 		fDocumentCount = 1;
312 
313 	GetPath(&fCaption);
314 	if (fDocumentCount > 1) {
315 		fCaption << ", " << fDocumentIndex << "/" << fDocumentCount;
316 	}
317 	fCaption << ", " << info.name;
318 
319 	AddToRecentDocuments();
320 
321 	Notify(info.name);
322 }
323 
324 void
325 ShowImageView::SetShowCaption(bool show)
326 {
327 	if (fShowCaption != show) {
328 		fShowCaption = show;
329 		UpdateCaption();
330 	}
331 }
332 
333 void
334 ShowImageView::SetShrinkToBounds(bool enable)
335 {
336 	if (fShrinkToBounds != enable) {
337 		SettingsSetBool("ShrinkToBounds", enable);
338 		fShrinkToBounds = enable;
339 		FixupScrollBars();
340 		Invalidate();
341 	}
342 }
343 
344 void
345 ShowImageView::SetZoomToBounds(bool enable)
346 {
347 	if (fZoomToBounds != enable) {
348 		SettingsSetBool("ZoomToBounds", enable);
349 		fZoomToBounds = enable;
350 		FixupScrollBars();
351 		Invalidate();
352 	}
353 }
354 
355 void
356 ShowImageView::SetBorder(bool hasBorder)
357 {
358 	if (fHasBorder != hasBorder) {
359 		fHasBorder = hasBorder;
360 		FixupScrollBars();
361 		Invalidate();
362 	}
363 }
364 
365 void
366 ShowImageView::SetAlignment(alignment horizontal, vertical_alignment vertical)
367 {
368 	bool hasChanged;
369 	hasChanged = fHAlignment != horizontal || fVAlignment != vertical;
370 	if (hasChanged) {
371 		fHAlignment = horizontal;
372 		fVAlignment = vertical;
373 		FixupScrollBars();
374 		Invalidate();
375 	}
376 }
377 
378 BBitmap *
379 ShowImageView::GetBitmap()
380 {
381 	return fBitmap;
382 }
383 
384 void
385 ShowImageView::GetName(BString *name)
386 {
387 	*name = "";
388 	BEntry entry(&fCurrentRef);
389 	if (entry.InitCheck() == B_OK) {
390 		char n[B_FILE_NAME_LENGTH];
391 		if (entry.GetName(n) == B_OK) {
392 			name->SetTo(n);
393 		}
394 	}
395 }
396 
397 void
398 ShowImageView::GetPath(BString *name)
399 {
400 	*name = "";
401 	BEntry entry(&fCurrentRef);
402 	if (entry.InitCheck() == B_OK) {
403 		BPath path;
404 		entry.GetPath(&path);
405 		if (path.InitCheck() == B_OK) {
406 			name->SetTo(path.Path());
407 		}
408 	}
409 }
410 
411 void
412 ShowImageView::FlushToLeftTop()
413 {
414 	BRect rect = AlignBitmap();
415 	BPoint p(rect.left, rect.top);
416 	ScrollTo(p);
417 }
418 
419 void
420 ShowImageView::SetScaleBilinear(bool s)
421 {
422 	if (fScaleBilinear != s) {
423 		SettingsSetBool("ScaleBilinear", s);
424 		fScaleBilinear = s; Invalidate();
425 	}
426 }
427 
428 void
429 ShowImageView::AttachedToWindow()
430 {
431 	FixupScrollBars();
432 }
433 
434 BRect
435 ShowImageView::AlignBitmap()
436 {
437 	BRect rect(fBitmap->Bounds());
438 	float width, height;
439 	width = Bounds().Width()-2*PEN_SIZE+1;
440 	height = Bounds().Height()-2*PEN_SIZE+1;
441 	if (width == 0 || height == 0) return rect;
442 	fShrinkOrZoomToBounds = fShrinkToBounds && (rect.Width() >= Bounds().Width() || rect.Height() >= Bounds().Height()) ||
443 		fZoomToBounds && rect.Width() < Bounds().Width() && rect.Height() < Bounds().Height();
444 	if (fShrinkOrZoomToBounds) {
445 		float s;
446 		s = width / (rect.Width()+1.0);
447 
448 		if (s * (rect.Height()+1.0) <= height) {
449 			rect.right = width-1;
450 			rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
451 			// center vertically
452 			rect.OffsetBy(0, (height - rect.Height()) / 2);
453 		} else {
454 			s = height / (rect.Height()+1.0);
455 			rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
456 			rect.bottom = height-1;
457 			// center horizontally
458 			rect.OffsetBy((width - rect.Width()) / 2, 0);
459 		}
460 	} else {
461 		// zoom image
462 		rect.right = static_cast<int>((rect.right+1.0)*fZoom)-1;
463 		rect.bottom = static_cast<int>((rect.bottom+1.0)*fZoom)-1;
464 		// align
465 		switch (fHAlignment) {
466 			case B_ALIGN_CENTER:
467 				if (width > rect.Width()) {
468 					rect.OffsetBy((width - rect.Width()) / 2.0, 0);
469 					break;
470 				}
471 				// fall through
472 			default:
473 			case B_ALIGN_LEFT:
474 				if (fHasBorder) {
475 					rect.OffsetBy(BORDER_WIDTH, 0);
476 				}
477 				break;
478 		}
479 		switch (fVAlignment) {
480 			case B_ALIGN_MIDDLE:
481 				if (height > rect.Height()) {
482 					rect.OffsetBy(0, (height - rect.Height()) / 2.0);
483 				break;
484 				}
485 				// fall through
486 			default:
487 			case B_ALIGN_TOP:
488 				if (fHasBorder) {
489 					rect.OffsetBy(0, BORDER_WIDTH);
490 				}
491 				break;
492 		}
493 	}
494 	rect.OffsetBy(PEN_SIZE, PEN_SIZE);
495 	return rect;
496 }
497 
498 void
499 ShowImageView::Setup(BRect rect)
500 {
501 	fLeft = rect.left;
502 	fTop = rect.top;
503 	fScaleX = rect.Width() / fBitmap->Bounds().Width();
504 	fScaleY = rect.Height() / fBitmap->Bounds().Height();
505 }
506 
507 BPoint
508 ShowImageView::ImageToView(BPoint p) const
509 {
510 	p.x = fScaleX * p.x + fLeft;
511 	p.y = fScaleY * p.y + fTop;
512 	return p;
513 }
514 
515 BPoint
516 ShowImageView::ViewToImage(BPoint p) const
517 {
518 	p.x = (p.x - fLeft) / fScaleX;
519 	p.y = (p.y - fTop) / fScaleY;
520 	return p;
521 }
522 
523 BRect
524 ShowImageView::ImageToView(BRect r) const
525 {
526 	BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
527 	BPoint rightBottom(ImageToView(BPoint(r.right, r.bottom)));
528 	return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
529 }
530 
531 void
532 ShowImageView::DrawBorder(BRect border)
533 {
534 	BRect bounds(Bounds());
535 	// top
536 	FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW);
537 	// left
538 	FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW);
539 	// right
540 	FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
541 	// bottom
542 	FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW);
543 }
544 
545 void
546 ShowImageView::LayoutCaption(BFont &font, BPoint &pos, BRect &rect)
547 {
548 	font_height fontHeight;
549 	float width, height;
550 	BRect bounds(Bounds());
551 	font = be_plain_font;
552 	width = font.StringWidth(fCaption.String()) + 1; // 1 for text shadow
553 	font.GetHeight(&fontHeight);
554 	height = fontHeight.ascent + fontHeight.descent;
555 	// center text horizontally
556 	pos.x = (bounds.left + bounds.right - width)/2;
557 	// flush bottom
558 	pos.y = bounds.bottom - fontHeight.descent - 5;
559 
560 	// background rectangle
561 	rect.Set(0, 0, (width-1)+2, (height-1)+2+1); // 2 for border and 1 for text shadow
562 	rect.OffsetTo(pos);
563 	rect.OffsetBy(-1, -1-fontHeight.ascent); // -1 for border
564 }
565 
566 void
567 ShowImageView::DrawCaption()
568 {
569 	BFont font;
570 	BPoint pos;
571 	BRect rect;
572 	LayoutCaption(font, pos, rect);
573 
574 	PushState();
575 	// draw background
576 	SetDrawingMode(B_OP_ALPHA);
577 	SetHighColor(0, 0, 255, 128);
578 	FillRect(rect);
579 	// draw text
580 	SetDrawingMode(B_OP_OVER);
581 	SetFont(&font);
582 	SetLowColor(B_TRANSPARENT_COLOR);
583 	// text shadow
584 	pos += BPoint(1, 1);
585 	SetHighColor(0, 0, 0);
586 	SetPenSize(1);
587 	DrawString(fCaption.String(), pos);
588 	// text
589 	pos -= BPoint(1, 1);
590 	SetHighColor(255, 255, 0);
591 	DrawString(fCaption.String(), pos);
592 	PopState();
593 }
594 
595 void
596 ShowImageView::UpdateCaption()
597 {
598 	BFont font;
599 	BPoint pos;
600 	BRect rect;
601 	LayoutCaption(font, pos, rect);
602 
603 	// draw over portion of image where caption is located
604 	BRegion clip(rect);
605 	PushState();
606 	ConstrainClippingRegion(&clip);
607 	Draw(rect);
608 	PopState();
609 }
610 
611 Scaler*
612 ShowImageView::GetScaler(BRect rect)
613 {
614 	if (fScaler == NULL || !fScaler->Matches(rect)) {
615 		DeleteScaler();
616 		BMessenger msgr(this, Window());
617 		fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE);
618 		fScaler->Start();
619 	}
620 	return fScaler;
621 }
622 
623 void
624 ShowImageView::DrawImage(BRect rect)
625 {
626 	if (fScaleBilinear) {
627 		Scaler* scaler = GetScaler(rect);
628 		if (scaler != NULL && scaler->GetBitmap() != NULL && !scaler->IsRunning()) {
629 			BBitmap* bitmap = scaler->GetBitmap();
630 			DrawBitmap(bitmap, BPoint(rect.left, rect.top));
631 			return;
632 		}
633 	}
634 	DrawBitmap(fBitmap, fBitmap->Bounds(), rect);
635 }
636 
637 void
638 ShowImageView::Draw(BRect updateRect)
639 {
640 	if (fBitmap) {
641 		if (!IsPrinting()) {
642 			BRect rect = AlignBitmap();
643 			Setup(rect);
644 
645 			BRect border(rect);
646 			border.InsetBy(-PEN_SIZE, -PEN_SIZE);
647 
648 			DrawBorder(border);
649 
650 			// Draw black rectangle around image
651 			StrokeRect(border);
652 
653 			// Draw image
654 			DrawImage(rect);
655 
656 			if (fShowCaption) {
657 				// fShowCaption is set to false by ScrollRestricted()
658 				// to prevent the caption from dirtying up the image
659 				// during scrolling.
660 				DrawCaption();
661 			}
662 
663 			if (HasSelection()) {
664 				if (fSelBitmap) {
665 					BRect srcBits, destRect;
666 					GetSelMergeRects(srcBits, destRect);
667 					destRect = ImageToView(destRect);
668 					DrawBitmap(fSelBitmap, srcBits, destRect);
669 				}
670 				DrawSelectionBox(fSelectionRect);
671 			}
672 		} else {
673 			DrawBitmap(fBitmap);
674 		}
675 	}
676 }
677 
678 void
679 ShowImageView::DrawSelectionBox(BRect &rect)
680 {
681 	BRect r = rect;
682 	ConstrainToImage(r);
683 
684 	PushState();
685 	rgb_color white = {255, 255, 255};
686 	SetLowColor(white);
687 	r = ImageToView(r);
688 	StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft);
689 	StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp);
690 	StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight);
691 	StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown);
692 	PopState();
693 }
694 
695 void
696 ShowImageView::FrameResized(float /* width */, float /* height */)
697 {
698 	FixupScrollBars();
699 }
700 
701 void
702 ShowImageView::ConstrainToImage(BPoint &point)
703 {
704 	point.ConstrainTo(fBitmap->Bounds());
705 }
706 
707 void
708 ShowImageView::ConstrainToImage(BRect &rect)
709 {
710 	BRect bounds = fBitmap->Bounds();
711 	BPoint leftTop, rightBottom;
712 
713 	leftTop = rect.LeftTop();
714 	leftTop.ConstrainTo(bounds);
715 
716 	rightBottom = rect.RightBottom();
717 	rightBottom.ConstrainTo(bounds);
718 
719 	rect.SetLeftTop(leftTop);
720 	rect.SetRightBottom(rightBottom);
721 }
722 
723 BBitmap*
724 ShowImageView::CopySelection(uchar alpha)
725 {
726 	bool hasAlpha = alpha != 255;
727 
728 	if (!HasSelection()) return NULL;
729 
730 	BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height());
731 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
732 	BBitmap *bitmap = new BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true);
733 	if (bitmap == NULL) return NULL;
734 
735 	if (bitmap->Lock()) {
736 		bitmap->AddChild(&view);
737 		if (fSelBitmap)
738 			view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), rect);
739 		else
740 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
741 		if (hasAlpha) {
742 			view.SetDrawingMode(B_OP_SUBTRACT);
743 			view.SetHighColor(0, 0, 0, 255-alpha);
744 			view.FillRect(rect, B_SOLID_HIGH);
745 		}
746 		view.Sync();
747 		bitmap->RemoveChild(&view);
748 		bitmap->Unlock();
749 	}
750 
751 	return bitmap;
752 }
753 
754 bool
755 ShowImageView::AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
756 {
757 	bool found = false;
758 	BTranslatorRoster *roster = BTranslatorRoster::Default();
759 	if (roster == NULL) return false;
760 
761 	BBitmapStream stream(bitmap);
762 
763 	translator_info *outInfo;
764 	int32 outNumInfo;
765 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
766 		for (int32 i = 0; i < outNumInfo; i++) {
767 			const translation_format *fmts;
768 			int32 num_fmts;
769 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
770 			for (int32 j = 0; j < num_fmts; j++) {
771 				if (strcmp(fmts[j].MIME, "image/x-be-bitmap") != 0) {
772 					// needed to send data in message
773  					msg->AddString("be:types", fmts[j].MIME);
774  					// needed to pass data via file
775 					msg->AddString("be:filetypes", fmts[j].MIME);
776 					msg->AddString("be:type_descriptions", fmts[j].name);
777 				}
778 				found = true;
779 			}
780 		}
781 	}
782 	stream.DetachBitmap(&bitmap);
783 	return found;
784 }
785 
786 void
787 ShowImageView::BeginDrag(BPoint sourcePoint)
788 {
789 	BBitmap* bitmap = CopySelection(128);
790 	if (bitmap == NULL) return;
791 
792 	SetMouseEventMask(B_POINTER_EVENTS);
793 	BPoint leftTop(fSelectionRect.left, fSelectionRect.top);
794 
795 	// fill the drag message
796 	BMessage drag(B_SIMPLE_DATA);
797 	drag.AddInt32("be:actions", B_COPY_TARGET);
798 	drag.AddString("be:clip_name", "Bitmap Clip");
799 	// XXX undocumented fields
800 	drag.AddPoint("be:_source_point", sourcePoint);
801 	drag.AddRect("be:_frame", fSelectionRect);
802 	// XXX meaning unknown???
803 	// drag.AddInt32("be:_format", e.g.: B_TGA_FORMAT);
804 	// drag.AddInt32("be_translator", translator_id);
805 	// drag.AddPointer("be:_bitmap_ptr, ?);
806 	if (AddSupportedTypes(&drag, bitmap)) {
807 		// we also support "Passing Data via File" protocol
808 		drag.AddString("be:types", B_FILE_MIME_TYPE);
809 		// avoid flickering of dragged bitmap caused by drawing into the window
810 		AnimateSelection(false);
811 		// DragMessage takes ownership of bitmap
812 		DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint - leftTop);
813 	}
814 }
815 
816 bool
817 ShowImageView::OutputFormatForType(BBitmap* bitmap, const char* type, translation_format* format)
818 {
819 	bool found = false;
820 
821 	BTranslatorRoster *roster = BTranslatorRoster::Default();
822 	if (roster == NULL) return false;
823 
824 	BBitmapStream stream(bitmap);
825 
826 	translator_info *outInfo;
827 	int32 outNumInfo;
828 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
829 		for (int32 i = 0; i < outNumInfo; i++) {
830 			const translation_format *fmts;
831 			int32 num_fmts;
832 			roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
833 			for (int32 j = 0; j < num_fmts; j++) {
834 				if (strcmp(fmts[j].MIME, type) == 0) {
835 					*format = fmts[j];
836 					found = true;
837 					break;
838 				}
839 			}
840 		}
841 	}
842 	stream.DetachBitmap(&bitmap);
843 	return found;
844 }
845 
846 void
847 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, translation_format* format)
848 {
849 	BTranslatorRoster *roster = BTranslatorRoster::Default();
850 	BBitmapStream stream(bitmap); // destructor deletes bitmap
851 	// write data
852 	BFile file(dir, name, B_WRITE_ONLY);
853 	roster->Translate(&stream, NULL, NULL, &file, format->type);
854 	// set mime type
855 	BNodeInfo info(&file);
856 	if (info.InitCheck() == B_OK) {
857 		info.SetType(format->MIME);
858 	}
859 }
860 
861 void
862 ShowImageView::SendInMessage(BMessage* msg, BBitmap* bitmap, translation_format* format)
863 {
864 	BMessage reply(B_MIME_DATA);
865 	BBitmapStream stream(bitmap); // destructor deletes bitmap
866 	BTranslatorRoster *roster = BTranslatorRoster::Default();
867 	BMallocIO memStream;
868 	if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
869 		reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(), memStream.BufferLength());
870 		msg->SendReply(&reply);
871 	}
872 }
873 
874 void
875 ShowImageView::HandleDrop(BMessage* msg)
876 {
877 	BMessage data(B_MIME_DATA);
878 	entry_ref dirRef;
879 	BString name, type;
880 	bool saveToFile;
881 	bool sendInMessage;
882 	BBitmap *bitmap;
883 
884 	saveToFile = msg->FindString("be:filetypes", &type) == B_OK &&
885 				 msg->FindRef("directory", &dirRef) == B_OK &&
886 				 msg->FindString("name", &name) == B_OK;
887 
888 	sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK;
889 
890 	bitmap = CopySelection();
891 	if (bitmap == NULL) return;
892 
893 	translation_format format;
894 	if (!OutputFormatForType(bitmap, type.String(), &format)) {
895 		delete bitmap;
896 		return;
897 	}
898 
899 	if (saveToFile) {
900 		BDirectory dir(&dirRef);
901 		SaveToFile(&dir, name.String(), bitmap, &format);
902 	} else if (sendInMessage) {
903 		SendInMessage(msg, bitmap, &format);
904 	} else {
905 		delete bitmap;
906 	}
907 }
908 
909 void
910 ShowImageView::MoveImage()
911 {
912 	BPoint point, delta;
913 	uint32 buttons;
914 	// get CURRENT position
915 	GetMouse(&point, &buttons);
916 	point = ConvertToScreen(point);
917 	delta = fFirstPoint - point;
918 	fFirstPoint = point;
919 	ScrollRestrictedBy(delta.x, delta.y);
920 	// in case we miss MouseUp
921 	if ((GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0) {
922 		fMovesImage = false;
923 		Invalidate();
924 	}
925 }
926 
927 uint32
928 ShowImageView::GetMouseButtons()
929 {
930 	uint32 buttons;
931 	BPoint point;
932 	GetMouse(&point, &buttons);
933 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
934 		if ((modifiers() & B_CONTROL_KEY) != 0) {
935 			buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button
936 		} else if ((modifiers() & B_SHIFT_KEY) != 0) {
937 			buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button
938 		}
939 	}
940 	return buttons;
941 }
942 
943 void
944 ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect)
945 {
946 	destRect = fSelectionRect;
947 	ConstrainToImage(destRect);
948 
949 	srcBits = fSelectionRect;
950 	if (srcBits.left < 0)
951 		srcBits.left = -(srcBits.left);
952 	else
953 		srcBits.left = 0;
954 	if (srcBits.top < 0)
955 		srcBits.top = -(srcBits.top);
956 	else
957 		srcBits.top = 0;
958 	if (srcBits.right > fBitmap->Bounds().right)
959 		srcBits.right = srcBits.left + destRect.Width();
960 	else
961 		srcBits.right = fSelBitmap->Bounds().right;
962 	if (srcBits.bottom > fBitmap->Bounds().bottom)
963 		srcBits.bottom = srcBits.top + destRect.Height();
964 	else
965 		srcBits.bottom = fSelBitmap->Bounds().bottom;
966 }
967 
968 void
969 ShowImageView::MergeSelection()
970 {
971 	if (!HasSelection() || !fSelBitmap)
972 		return;
973 
974 	// Merge selection with background
975 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
976 	BBitmap *bitmap = new BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
977 	if (bitmap == NULL)
978 		return;
979 
980 	if (bitmap->Lock()) {
981 		bitmap->AddChild(&view);
982 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
983 
984 		BRect srcBits, destRect;
985 		GetSelMergeRects(srcBits, destRect);
986 		view.DrawBitmap(fSelBitmap, srcBits, destRect);
987 
988 		view.Sync();
989 		bitmap->RemoveChild(&view);
990 		bitmap->Unlock();
991 
992 		DeleteBitmap();
993 		fBitmap = bitmap;
994 	} else
995 		delete bitmap;
996 }
997 
998 void
999 ShowImageView::MouseDown(BPoint position)
1000 {
1001 	BPoint point;
1002 	uint32 buttons;
1003 	MakeFocus(true);
1004 
1005 	point = ViewToImage(position);
1006 	buttons = GetMouseButtons();
1007 
1008 	if (HasSelection() && fSelectionRect.Contains(point) &&
1009 		(buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) {
1010 		if (!fSelBitmap) {
1011 			fSelBitmap = CopySelection();
1012 		}
1013 		BPoint sourcePoint = point;
1014 		BeginDrag(sourcePoint);
1015 
1016 		while (buttons) {
1017 			// Keep reading mouse movement until
1018 			// the user lets up on all mouse buttons
1019 			GetMouse(&point, &buttons);
1020 			snooze(25 * 1000);
1021 				// sleep for 25 milliseconds to minimize CPU usage during loop
1022 		}
1023 
1024 		if (Bounds().Contains(point)) {
1025 			// If selection stayed inside this view
1026 			// (Some of the selection may be in the border area, which can be OK)
1027 			BPoint last, diff;
1028 			last = ViewToImage(point);
1029 			diff = last - sourcePoint;
1030 
1031 			BRect newSelection = fSelectionRect;
1032 			newSelection.OffsetBy(diff);
1033 
1034 			if (fBitmap->Bounds().Intersects(newSelection)) {
1035 				// Do not accept the new selection box location
1036 				// if it does not intersect with the bitmap rectangle
1037 				fSelectionRect = newSelection;
1038 				Invalidate();
1039 			}
1040 		}
1041 
1042 		AnimateSelection(true);
1043 
1044 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1045 		MergeSelection();
1046 			// If there is an existing selection,
1047 			// Make it part of the background image
1048 
1049 		// begin new selection
1050 		SetHasSelection(true);
1051 		fMakesSelection = true;
1052 		SetMouseEventMask(B_POINTER_EVENTS);
1053 		ConstrainToImage(point);
1054 		fFirstPoint = point;
1055 		fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1056 		fSelectionRect = fCopyFromRect;
1057 		Invalidate();
1058 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1059 		ShowPopUpMenu(ConvertToScreen(position));
1060 	} else if (buttons == B_TERTIARY_MOUSE_BUTTON) {
1061 		// move image in window
1062 		SetMouseEventMask(B_POINTER_EVENTS);
1063 		fMovesImage = true;
1064 		fFirstPoint = ConvertToScreen(position);
1065 		Invalidate();
1066 	}
1067 }
1068 
1069 void
1070 ShowImageView::UpdateSelectionRect(BPoint point, bool final) {
1071 	BRect oldSelection = fCopyFromRect;
1072 	point = ViewToImage(point);
1073 	ConstrainToImage(point);
1074 	fCopyFromRect.left = min(fFirstPoint.x, point.x);
1075 	fCopyFromRect.right = max(fFirstPoint.x, point.x);
1076 	fCopyFromRect.top = min(fFirstPoint.y, point.y);
1077 	fCopyFromRect.bottom = max(fFirstPoint.y, point.y);
1078 	fSelectionRect = fCopyFromRect;
1079 	if (final) {
1080 		// selection must contain a few pixels
1081 		if (fCopyFromRect.Width() * fCopyFromRect.Height() <= 1) {
1082 			SetHasSelection(false);
1083 		}
1084 	}
1085 	if (oldSelection != fCopyFromRect || !HasSelection()) {
1086 		BRect updateRect;
1087 		updateRect = oldSelection | fCopyFromRect;
1088 		updateRect = ImageToView(updateRect);
1089 		updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE);
1090 		Invalidate(updateRect);
1091 	}
1092 }
1093 
1094 void
1095 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *pmsg)
1096 {
1097 	if (fMakesSelection) {
1098 		UpdateSelectionRect(point, false);
1099 	} else if (fMovesImage) {
1100 		MoveImage();
1101 	}
1102 }
1103 
1104 void
1105 ShowImageView::MouseUp(BPoint point)
1106 {
1107 	if (fMakesSelection) {
1108 		UpdateSelectionRect(point, true);
1109 		fMakesSelection = false;
1110 	} else if (fMovesImage) {
1111 		MoveImage();
1112 		if (fMovesImage) {
1113 			fMovesImage = false;
1114 			Invalidate();
1115 		}
1116 	}
1117 	AnimateSelection(true);
1118 }
1119 
1120 float
1121 ShowImageView::LimitToRange(float v, orientation o, bool absolute)
1122 {
1123 	BScrollBar* psb = ScrollBar(o);
1124 	if (psb) {
1125 		float min, max, pos;
1126 		pos = v;
1127 		if (!absolute) {
1128 			pos += psb->Value();
1129 		}
1130 		psb->GetRange(&min, &max);
1131 		if (pos < min) {
1132 			pos = min;
1133 		} else if (pos > max) {
1134 			pos = max;
1135 		}
1136 		v = pos;
1137 		if (!absolute) {
1138 			v -= psb->Value();
1139 		}
1140 	}
1141 	return v;
1142 }
1143 
1144 void
1145 ShowImageView::ScrollRestricted(float x, float y, bool absolute)
1146 {
1147 	if (x != 0) {
1148 		x = LimitToRange(x, B_HORIZONTAL, absolute);
1149 	}
1150 
1151 	if (y != 0) {
1152 		y = LimitToRange(y, B_VERTICAL, absolute);
1153 	}
1154 
1155 	// hide the caption when using mouse wheel
1156 	// in full screen mode
1157 	bool caption = fShowCaption;
1158 	if (caption) {
1159 		fShowCaption = false;
1160 		UpdateCaption();
1161 	}
1162 
1163 	ScrollBy(x, y);
1164 
1165 	if (caption) {
1166 		// show the caption again
1167 		fShowCaption = true;
1168 		UpdateCaption();
1169 	}
1170 }
1171 
1172 // XXX method is not unused
1173 void
1174 ShowImageView::ScrollRestrictedTo(float x, float y)
1175 {
1176 	ScrollRestricted(x, y, true);
1177 }
1178 
1179 void
1180 ShowImageView::ScrollRestrictedBy(float x, float y)
1181 {
1182 	ScrollRestricted(x, y, false);
1183 }
1184 
1185 void
1186 ShowImageView::KeyDown (const char * bytes, int32 numBytes)
1187 {
1188 	if (numBytes == 1) {
1189 		switch (*bytes) {
1190 			case B_DOWN_ARROW:
1191 				ScrollRestrictedBy(0, 10);
1192 				break;
1193 			case B_UP_ARROW:
1194 				ScrollRestrictedBy(0, -10);
1195 				break;
1196 			case B_LEFT_ARROW:
1197 				ScrollRestrictedBy(-10, 0);
1198 				break;
1199 			case B_RIGHT_ARROW:
1200 				ScrollRestrictedBy(10, 0);
1201 				break;
1202 			case B_SPACE:
1203 			case B_ENTER:
1204 				NextFile();
1205 				break;
1206 			case B_BACKSPACE:
1207 				PrevFile();
1208 				break;
1209 			case B_HOME:
1210 				break;
1211 			case B_END:
1212 				break;
1213 			case B_ESCAPE:
1214 				if (fSlideShow) {
1215 					BMessenger msgr(Window());
1216 					msgr.SendMessage(MSG_SLIDE_SHOW);
1217 				}
1218 				break;
1219 		}
1220 	}
1221 }
1222 
1223 void
1224 ShowImageView::MouseWheelChanged(BMessage *msg)
1225 {
1226 	// The BeOS driver does not currently support
1227 	// X wheel scrolling, therefore, dx is zero.
1228 	// |dy| is the number of notches scrolled up or down.
1229 	// When the wheel is scrolled down (towards the user) dy > 0
1230 	// When the wheel is scrolled up (away from the user) dy < 0
1231 	const float kscrollBy = 40;
1232 	float dy, dx;
1233 	float x, y;
1234 	x = 0; y = 0;
1235 	if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK) {
1236 		x = dx * kscrollBy;
1237 	}
1238 	if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK) {
1239 		y = dy * kscrollBy;
1240 	}
1241 
1242 	ScrollRestrictedBy(x, y);
1243 }
1244 
1245 void
1246 ShowImageView::ShowPopUpMenu(BPoint screen)
1247 {
1248 	BPopUpMenu* menu = new BPopUpMenu("PopUpMenu");
1249 	menu->SetAsyncAutoDestruct(true);
1250 	menu->SetRadioMode(false);
1251 
1252 	ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
1253 	if (showImage) {
1254 		showImage->BuildViewMenu(menu);
1255 	}
1256 	menu->AddSeparatorItem();
1257 	menu->AddItem(new BMenuItem("Cancel", 0, 0));
1258 
1259 	screen -= BPoint(10, 10);
1260 	menu->Go(screen, true, false, true);
1261 }
1262 
1263 void
1264 ShowImageView::SettingsSetBool(const char* name, bool value)
1265 {
1266 	ShowImageSettings* settings;
1267 	settings = my_app->Settings();
1268 	if (settings->Lock()) {
1269 		settings->SetBool(name, value);
1270 		settings->Unlock();
1271 	}
1272 }
1273 
1274 void
1275 ShowImageView::MessageReceived(BMessage *pmsg)
1276 {
1277 	switch (pmsg->what) {
1278 		case B_COPY_TARGET:
1279 			HandleDrop(pmsg);
1280 			break;
1281 		case B_MOUSE_WHEEL_CHANGED:
1282 			MouseWheelChanged(pmsg);
1283 			break;
1284 		case MSG_INVALIDATE:
1285 			Invalidate();
1286 			break;
1287 		default:
1288 			BView::MessageReceived(pmsg);
1289 			break;
1290 	}
1291 }
1292 
1293 void
1294 ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength)
1295 {
1296 	float prop, range;
1297 	BScrollBar *psb;
1298 
1299 	psb = ScrollBar(o);
1300 	if (psb) {
1301 		if (fHasBorder && !fShrinkOrZoomToBounds) {
1302 			bitmapLength += BORDER_WIDTH*2;
1303 		}
1304 		range = bitmapLength - viewLength;
1305 		if (range < 0.0) {
1306 			range = 0.0;
1307 		}
1308 		prop = viewLength / bitmapLength;
1309 		if (prop > 1.0) {
1310 			prop = 1.0;
1311 		}
1312 		psb->SetRange(0, range);
1313 		psb->SetProportion(prop);
1314 		psb->SetSteps(10, 100);
1315 	}
1316 }
1317 
1318 void
1319 ShowImageView::FixupScrollBars()
1320 {
1321 	BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
1322 	if (fBitmap) {
1323 		BRect rect(AlignBitmap());
1324 		rctbitmap.Set(0, 0, rect.Width(), rect.Height());
1325 	}
1326 
1327 	FixupScrollBar(B_HORIZONTAL, rctbitmap.Width(), rctview.Width());
1328 	FixupScrollBar(B_VERTICAL, rctbitmap.Height(), rctview.Height());
1329 }
1330 
1331 int32
1332 ShowImageView::CurrentPage()
1333 {
1334 	return fDocumentIndex;
1335 }
1336 
1337 int32
1338 ShowImageView::PageCount()
1339 {
1340 	return fDocumentCount;
1341 }
1342 
1343 void
1344 ShowImageView::Cut()
1345 {
1346 	CopySelectionToClipboard();
1347 	ClearSelection();
1348 }
1349 
1350 void
1351 ShowImageView::Paste()
1352 {
1353 	if (be_clipboard->Lock()) {
1354 		BMessage *pclip;
1355 		if ((pclip = be_clipboard->Data()) != NULL) {
1356 			BBitmap *pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip));
1357 			if (pbits) {
1358 				MergeSelection();
1359 
1360 				SetHasSelection(true);
1361 				fSelBitmap = pbits;
1362 				fCopyFromRect = BRect();
1363 				fSelectionRect = pbits->Bounds();
1364 
1365 				BPoint point;
1366 				if (pclip->FindPoint("be:location", &point) == B_OK &&
1367 					fBitmap->Bounds().Contains(point))
1368 					// Set the selection rectangle to the same location it was
1369 					// copied from, but only if the background bitmap is large enough
1370 					// to contain that point
1371 					fSelectionRect.OffsetBy(point);
1372 
1373 				Invalidate();
1374 			}
1375 		}
1376 
1377 		be_clipboard->Unlock();
1378 	}
1379 }
1380 
1381 void
1382 ShowImageView::SelectAll()
1383 {
1384 	SetHasSelection(true);
1385 	fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height());
1386 	fSelectionRect = fCopyFromRect;
1387 	Invalidate();
1388 }
1389 
1390 void
1391 ShowImageView::ClearSelection()
1392 {
1393 	if (HasSelection()) {
1394 		SetHasSelection(false);
1395 		Invalidate();
1396 	}
1397 }
1398 
1399 void
1400 ShowImageView::SetHasSelection(bool bHasSelection)
1401 {
1402 	DeleteSelBitmap();
1403 	fbHasSelection = bHasSelection;
1404 
1405 	BMessage msg(MSG_SELECTION);
1406 	msg.AddBool("has_selection", fbHasSelection);
1407 	BMessenger msgr(Window());
1408 	msgr.SendMessage(&msg);
1409 }
1410 
1411 void
1412 ShowImageView::CopySelectionToClipboard()
1413 {
1414 	if (HasSelection() && be_clipboard->Lock()) {
1415 		be_clipboard->Clear();
1416 		BMessage *clip = NULL;
1417 		if ((clip = be_clipboard->Data()) != NULL) {
1418 			BMessage data;
1419 			BBitmap* bitmap = CopySelection();
1420 			if (bitmap != NULL) {
1421 				#if 0
1422 				// According to BeBook and Becasso, Gobe Productive do the following.
1423 				// Paste works in Productive, but not in Becasso and original ShowImage.
1424 				BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused
1425 				bitmap->Archive(&msg);
1426 				clip->AddMessage("image/x-be-bitmap", &msg);
1427 				#else
1428 				// original ShowImage performs this. Paste works with original ShowImage.
1429 				bitmap->Archive(clip);
1430 				// original ShowImage uses be:location for insertion point
1431 				clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top));
1432 				#endif
1433 				delete bitmap;
1434 				be_clipboard->Commit();
1435 			}
1436 		}
1437 		be_clipboard->Unlock();
1438 	}
1439 }
1440 
1441 void
1442 ShowImageView::FirstPage()
1443 {
1444 	if (fDocumentIndex != 1) {
1445 		fDocumentIndex = 1;
1446 		SetImage(NULL);
1447 	}
1448 }
1449 
1450 void
1451 ShowImageView::LastPage()
1452 {
1453 	if (fDocumentIndex != fDocumentCount) {
1454 		fDocumentIndex = fDocumentCount;
1455 		SetImage(NULL);
1456 	}
1457 }
1458 
1459 void
1460 ShowImageView::NextPage()
1461 {
1462 	if (fDocumentIndex < fDocumentCount) {
1463 		fDocumentIndex++;
1464 		SetImage(NULL);
1465 	}
1466 }
1467 
1468 void
1469 ShowImageView::PrevPage()
1470 {
1471 	if (fDocumentIndex > 1) {
1472 		fDocumentIndex--;
1473 		SetImage(NULL);
1474 	}
1475 }
1476 
1477 int
1478 ShowImageView::CompareEntries(const void* a, const void* b)
1479 {
1480 	entry_ref *r1, *r2;
1481 	r1 = *(entry_ref**)a;
1482 	r2 = *(entry_ref**)b;
1483 	return strcasecmp(r1->name, r2->name);
1484 }
1485 
1486 void
1487 ShowImageView::GoToPage(int32 page)
1488 {
1489 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
1490 		fDocumentIndex = page;
1491 		SetImage(NULL);
1492 	}
1493 }
1494 
1495 void
1496 ShowImageView::FreeEntries(BList* entries)
1497 {
1498 	const int32 n = entries->CountItems();
1499 	for (int32 i = 0; i < n; i ++) {
1500 		entry_ref* ref = (entry_ref*)entries->ItemAt(i);
1501 		delete ref;
1502 	}
1503 	entries->MakeEmpty();
1504 }
1505 
1506 bool
1507 ShowImageView::FindNextImage(entry_ref* image, bool next, bool rewind)
1508 {
1509 	ASSERT(next || !rewind);
1510 	BEntry curImage(&fCurrentRef);
1511 	entry_ref entry, *ref;
1512 	BDirectory parent;
1513 	BList entries;
1514 	bool found = false;
1515 	int32 cur;
1516 
1517 	if (curImage.GetParent(&parent) != B_OK)
1518 		return false;
1519 
1520 	while (parent.GetNextRef(&entry) == B_OK) {
1521 		if (entry != fCurrentRef) {
1522 			entries.AddItem(new entry_ref(entry));
1523 		} else {
1524 			// insert current ref, so we can find it easily after sorting
1525 			entries.AddItem(&fCurrentRef);
1526 		}
1527 	}
1528 
1529 	entries.SortItems(CompareEntries);
1530 
1531 	cur = entries.IndexOf(&fCurrentRef);
1532 	ASSERT(cur >= 0);
1533 
1534 	// remove it so FreeEntries() does not delete it
1535 	entries.RemoveItem(&fCurrentRef);
1536 
1537 	if (next) {
1538 		// find the next image in the list
1539 		if (rewind) cur = 0; // start with first
1540 		for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) {
1541 			if (IsImage(ref)) {
1542 				found = true;
1543 				*image = (const entry_ref)*ref;
1544 				break;
1545 			}
1546 		}
1547 	} else {
1548 		// find the previous image in the list
1549 		cur --;
1550 		for (; cur >= 0; cur --) {
1551 			ref = (entry_ref*)entries.ItemAt(cur);
1552 			if (IsImage(ref)) {
1553 				found = true;
1554 				*image = (const entry_ref)*ref;
1555 				break;
1556 			}
1557 		}
1558 	}
1559 
1560 	FreeEntries(&entries);
1561 	return found;
1562 }
1563 
1564 bool
1565 ShowImageView::ShowNextImage(bool next, bool rewind)
1566 {
1567 	entry_ref ref;
1568 
1569 	if (FindNextImage(&ref, next, rewind)) {
1570 		SetImage(&ref);
1571 		return true;
1572 	}
1573 	return false;
1574 }
1575 
1576 bool
1577 ShowImageView::NextFile()
1578 {
1579 	return ShowNextImage(true, false);
1580 }
1581 
1582 bool
1583 ShowImageView::PrevFile()
1584 {
1585 	return ShowNextImage(false, false);
1586 }
1587 
1588 bool
1589 ShowImageView::FirstFile()
1590 {
1591 	return ShowNextImage(true, true);
1592 }
1593 
1594 void
1595 ShowImageView::SetZoom(float zoom)
1596 {
1597 	if (fScaleBilinear && fZoom != zoom) {
1598 		DeleteScaler();
1599 	}
1600 	fZoom = zoom;
1601 	FixupScrollBars();
1602 	Invalidate();
1603 }
1604 
1605 void
1606 ShowImageView::ZoomIn()
1607 {
1608 	SetZoom(fZoom + 0.25);
1609 }
1610 
1611 void
1612 ShowImageView::ZoomOut()
1613 {
1614 	if (fZoom > 0.25) {
1615 		SetZoom(fZoom - 0.25);
1616 	}
1617 }
1618 
1619 void
1620 ShowImageView::SetSlideShowDelay(float seconds)
1621 {
1622 	ShowImageSettings* settings;
1623 	fSlideShowDelay = (int)(seconds * 10.0);
1624 	settings = my_app->Settings();
1625 	if (settings->Lock()) {
1626 		settings->SetInt32("SlideShowDelay", fSlideShowDelay);
1627 		settings->Unlock();
1628 	}
1629 }
1630 
1631 void
1632 ShowImageView::StartSlideShow()
1633 {
1634 	fSlideShow = true; fSlideShowCountDown = fSlideShowDelay;
1635 }
1636 
1637 void
1638 ShowImageView::StopSlideShow()
1639 {
1640 	fSlideShow = false;
1641 }
1642 
1643 int32
1644 ShowImageView::BytesPerPixel(color_space cs) const
1645 {
1646 	switch (cs) {
1647 		case B_RGB32:      // fall through
1648 		case B_RGB32_BIG:  // fall through
1649 		case B_RGBA32:     // fall through
1650 		case B_RGBA32_BIG: return 4;
1651 
1652 		case B_RGB24_BIG:  // fall through
1653 		case B_RGB24:      return 3;
1654 
1655 		case B_RGB16:      // fall through
1656 		case B_RGB16_BIG:  // fall through
1657 		case B_RGB15:      // fall through
1658 		case B_RGB15_BIG:  // fall through
1659 		case B_RGBA15:     // fall through
1660 		case B_RGBA15_BIG: return 2;
1661 
1662 		case B_GRAY8:      // fall through
1663 		case B_CMAP8:      return 1;
1664 		case B_GRAY1:      return 0;
1665 		default: return -1;
1666 	}
1667 }
1668 
1669 void
1670 ShowImageView::CopyPixel(uchar* dest, int32 destX, int32 destY, int32 destBPR, uchar* src, int32 x, int32 y, int32 bpr, int32 bpp)
1671 {
1672 	dest += destBPR * destY + destX * bpp;
1673 	src += bpr * y + x * bpp;
1674 	memcpy(dest, src, bpp);
1675 }
1676 
1677 // FIXME: In color space with alpha channel the alpha value should not be inverted!
1678 // This could be a problem when image is saved and opened in another application.
1679 // Note: For B_CMAP8 InvertPixel inverts the color index not the color value!
1680 void
1681 ShowImageView::InvertPixel(int32 x, int32 y, uchar* dest, int32 destBPR, uchar* src, int32 bpr, int32 bpp)
1682 {
1683 	dest += destBPR * y + x * bpp;
1684 	src += bpr * y + x * bpp;
1685 	for (; bpp > 0; bpp --, dest ++, src ++) {
1686 		*dest = ~*src;
1687 	}
1688 }
1689 
1690 // DoImageOperation supports only color spaces with bytes per pixel >= 1
1691 // See above for limitations about kInvert
1692 void
1693 ShowImageView::DoImageOperation(image_operation op)
1694 {
1695 	color_space cs;
1696 	int32 bpp;
1697 	BBitmap* bm;
1698 	int32 width, height;
1699 	uchar* src;
1700 	uchar* dest;
1701 	int32 bpr, destBPR;
1702 	int32 x, y, destX, destY;
1703 	BRect rect;
1704 
1705 	if (fBitmap == NULL) return;
1706 
1707 	cs = fBitmap->ColorSpace();
1708 	bpp = BytesPerPixel(cs);
1709 	if (bpp < 1) return;
1710 
1711 	width = fBitmap->Bounds().IntegerWidth();
1712 	height = fBitmap->Bounds().IntegerHeight();
1713 
1714 	if (op == kRotateClockwise || op == kRotateAntiClockwise) {
1715 		rect.Set(0, 0, height, width);
1716 	} else {
1717 		rect.Set(0, 0, width, height);
1718 	}
1719 
1720 	bm = new BBitmap(rect, cs);
1721 	if (bm == NULL) return;
1722 
1723 	src = (uchar*)fBitmap->Bits();
1724 	dest = (uchar*)bm->Bits();
1725 	bpr = fBitmap->BytesPerRow();
1726 	destBPR = bm->BytesPerRow();
1727 
1728 	switch (op) {
1729 		case kRotateClockwise:
1730 			for (y = 0; y <= height; y ++) {
1731 				for (x = 0; x <= width; x ++) {
1732 					destX = height - y;
1733 					destY = x;
1734 					CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
1735 				}
1736 			}
1737 			break;
1738 		case kRotateAntiClockwise:
1739 			for (y = 0; y <= height; y ++) {
1740 				for (x = 0; x <= width; x ++) {
1741 					destX = y;
1742 					destY = width - x;
1743 					CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
1744 				}
1745 			}
1746 			break;
1747 		case kMirrorHorizontal:
1748 			for (y = 0; y <= height; y ++) {
1749 				for (x = 0; x <= width; x ++) {
1750 					destX = x;
1751 					destY = height - y;
1752 					CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
1753 				}
1754 			}
1755 			break;
1756 		case kMirrorVertical:
1757 			for (y = 0; y <= height; y ++) {
1758 				for (x = 0; x <= width; x ++) {
1759 					destX = width - x;
1760 					destY = y;
1761 					CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
1762 				}
1763 			}
1764 			break;
1765 		case kInvert:
1766 			for (y = 0; y <= height; y ++) {
1767 				for (x = 0; x <= width; x ++) {
1768 					InvertPixel(x, y, dest, destBPR, src, bpr, bpp);
1769 				}
1770 			}
1771 			break;
1772 	}
1773 
1774 	// set new bitmap
1775 	DeleteBitmap();
1776 	fBitmap = bm;
1777 	// remove selection
1778 	SetHasSelection(false);
1779 	Notify(NULL);
1780 }
1781 
1782 void
1783 ShowImageView::Rotate(int degree)
1784 {
1785 	if (degree == 90) {
1786 		DoImageOperation(kRotateClockwise);
1787 	} else if (degree == 270) {
1788 		DoImageOperation(kRotateAntiClockwise);
1789 	}
1790 }
1791 
1792 void
1793 ShowImageView::Mirror(bool vertical)
1794 {
1795 	if (vertical) {
1796 		DoImageOperation(kMirrorVertical);
1797 	} else {
1798 		DoImageOperation(kMirrorHorizontal);
1799 	}
1800 }
1801 
1802 void
1803 ShowImageView::Invert()
1804 {
1805 	DoImageOperation(kInvert);
1806 }
1807