xref: /haiku/src/apps/showimage/ShowImageView.cpp (revision 6c4a44e36ba846c54467103f884d65dfa13e7fcb)
1 /*
2  * Copyright 2003-2011, Haiku, Inc. All Rights Reserved.
3  * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
4  * Copyright 2006 Bernd Korz. All Rights Reserved
5  * Distributed under the terms of the MIT License.
6  *
7  * Authors:
8  *		Fernando Francisco de Oliveira
9  *		Michael Wilber
10  *		Michael Pfeiffer
11  *		Ryan Leavengood
12  *		yellowTAB GmbH
13  *		Bernd Korz
14  *		Stephan Aßmus <superstippi@gmx.de>
15  *		Axel Dörfler, axeld@pinc-software.de
16  */
17 
18 
19 #include "ShowImageView.h"
20 
21 #include <math.h>
22 #include <new>
23 #include <stdio.h>
24 
25 #include <Alert.h>
26 #include <Application.h>
27 #include <Bitmap.h>
28 #include <BitmapStream.h>
29 #include <Catalog.h>
30 #include <Clipboard.h>
31 #include <Cursor.h>
32 #include <Debug.h>
33 #include <Directory.h>
34 #include <Entry.h>
35 #include <File.h>
36 #include <Locale.h>
37 #include <MenuBar.h>
38 #include <MenuItem.h>
39 #include <Message.h>
40 #include <NodeInfo.h>
41 #include <Path.h>
42 #include <PopUpMenu.h>
43 #include <Rect.h>
44 #include <Region.h>
45 #include <Roster.h>
46 #include <Screen.h>
47 #include <ScrollBar.h>
48 #include <StopWatch.h>
49 #include <SupportDefs.h>
50 #include <TranslatorRoster.h>
51 
52 #include <tracker_private.h>
53 
54 #include "ImageCache.h"
55 #include "ShowImageApp.h"
56 #include "ShowImageWindow.h"
57 
58 
59 using std::nothrow;
60 
61 
62 class PopUpMenu : public BPopUpMenu {
63 	public:
64 		PopUpMenu(const char* name, BMessenger target);
65 		virtual ~PopUpMenu();
66 
67 	private:
68 		BMessenger fTarget;
69 };
70 
71 
72 // the delay time for hiding the cursor in 1/10 seconds (the pulse rate)
73 #define HIDE_CURSOR_DELAY_TIME 20
74 #define STICKY_ZOOM_DELAY_TIME 5
75 #define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
76 
77 
78 const rgb_color kBorderColor = { 0, 0, 0, 255 };
79 
80 enum ShowImageView::image_orientation
81 ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations]
82 		[kNumberOfOrientations] = {
83 	// rotate 90°
84 	{k90, k180, k270, k0, k270V, k0V, k90V, k0H},
85 	// rotate -90°
86 	{k270, k0, k90, k180, k90V, k0H, k270V, k0V},
87 	// mirror vertical
88 	{k0H, k270V, k0V, k90V, k180, k270, k0, k90},
89 	// mirror horizontal
90 	{k0V, k90V, k0H, k270V, k0, k90, k180, k270}
91 };
92 
93 const rgb_color kAlphaLow = (rgb_color) { 0xbb, 0xbb, 0xbb, 0xff };
94 const rgb_color kAlphaHigh = (rgb_color) { 0xe0, 0xe0, 0xe0, 0xff };
95 
96 const uint32 kMsgPopUpMenuClosed = 'pmcl';
97 
98 
99 inline void
100 blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a)
101 {
102 	d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8;
103 	d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8;
104 	d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8;
105 }
106 
107 
108 BBitmap*
109 compose_checker_background(const BBitmap* bitmap)
110 {
111 	BBitmap* result = new (nothrow) BBitmap(bitmap);
112 	if (result && !result->IsValid()) {
113 		delete result;
114 		result = NULL;
115 	}
116 	if (!result)
117 		return NULL;
118 
119 	uint8* bits = (uint8*)result->Bits();
120 	uint32 bpr = result->BytesPerRow();
121 	uint32 width = result->Bounds().IntegerWidth() + 1;
122 	uint32 height = result->Bounds().IntegerHeight() + 1;
123 
124 	for (uint32 i = 0; i < height; i++) {
125 		uint8* p = bits;
126 		for (uint32 x = 0; x < width; x++) {
127 			uint8 alpha = p[3];
128 			if (alpha < 255) {
129 				p[3] = 255;
130 				alpha = 255 - alpha;
131 				if (x % 10 >= 5) {
132 					if (i % 10 >= 5)
133 						blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
134 					else
135 						blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
136 
137 				} else {
138 					if (i % 10 >= 5)
139 						blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
140 					else
141 						blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
142 				}
143 			}
144 			p += 4;
145 		}
146 		bits += bpr;
147 	}
148 	return result;
149 }
150 
151 
152 //	#pragma mark -
153 
154 
155 PopUpMenu::PopUpMenu(const char* name, BMessenger target)
156 	:
157 	BPopUpMenu(name, false, false),
158 	fTarget(target)
159 {
160 	SetAsyncAutoDestruct(true);
161 }
162 
163 
164 PopUpMenu::~PopUpMenu()
165 {
166 	fTarget.SendMessage(kMsgPopUpMenuClosed);
167 }
168 
169 
170 //	#pragma mark -
171 
172 
173 ShowImageView::ShowImageView(BRect rect, const char* name, uint32 resizingMode,
174 		uint32 flags)
175 	:
176 	BView(rect, name, resizingMode, flags),
177 	fBitmapOwner(NULL),
178 	fBitmap(NULL),
179 	fDisplayBitmap(NULL),
180 	fSelectionBitmap(NULL),
181 
182 	fZoom(1.0),
183 
184 	fScaleBilinear(true),
185 
186 	fBitmapLocationInView(0.0, 0.0),
187 
188 	fStretchToBounds(false),
189 	fHideCursor(false),
190 	fScrollingBitmap(false),
191 	fCreatingSelection(false),
192 	fFirstPoint(0.0, 0.0),
193 	fSelectionMode(false),
194 	fAnimateSelection(true),
195 	fHasSelection(false),
196 	fShowCaption(false),
197 	fShowingPopUpMenu(false),
198 	fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
199 	fStickyZoomCountDown(0),
200 	fIsActiveWin(true),
201 	fDefaultCursor(NULL),
202 	fGrabCursor(NULL)
203 {
204 	ShowImageSettings* settings = my_app->Settings();
205 	if (settings->Lock()) {
206 		fStretchToBounds = settings->GetBool("StretchToBounds",
207 			fStretchToBounds);
208 		fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
209 		settings->Unlock();
210 	}
211 
212 	fDefaultCursor = new BCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
213 	fGrabCursor = new BCursor(B_CURSOR_ID_GRABBING);
214 
215 	SetViewColor(B_TRANSPARENT_COLOR);
216 	SetHighColor(kBorderColor);
217 	SetLowColor(0, 0, 0);
218 }
219 
220 
221 ShowImageView::~ShowImageView()
222 {
223 	_DeleteBitmap();
224 
225 	delete fDefaultCursor;
226 	delete fGrabCursor;
227 }
228 
229 
230 void
231 ShowImageView::_AnimateSelection(bool enabled)
232 {
233 	fAnimateSelection = enabled;
234 }
235 
236 
237 void
238 ShowImageView::Pulse()
239 {
240 	// animate marching ants
241 	if (fHasSelection && fAnimateSelection && fIsActiveWin) {
242 		fSelectionBox.Animate();
243 		fSelectionBox.Draw(this, Bounds());
244 	}
245 
246 	if (fHideCursor && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) {
247 		if (fHideCursorCountDown <= 0) {
248 			BPoint mousePos;
249 			uint32 buttons;
250 			GetMouse(&mousePos, &buttons, false);
251 			if (Bounds().Contains(mousePos))
252 				be_app->ObscureCursor();
253 		} else
254 			fHideCursorCountDown--;
255 	}
256 
257 	if (fStickyZoomCountDown > 0)
258 		fStickyZoomCountDown--;
259 
260 }
261 
262 
263 void
264 ShowImageView::_SendMessageToWindow(BMessage* message)
265 {
266 	BMessenger target(Window());
267 	target.SendMessage(message);
268 }
269 
270 
271 void
272 ShowImageView::_SendMessageToWindow(uint32 code)
273 {
274 	BMessage message(code);
275 	_SendMessageToWindow(&message);
276 }
277 
278 
279 //! send message to parent about new image
280 void
281 ShowImageView::_Notify()
282 {
283 	BMessage msg(MSG_UPDATE_STATUS);
284 
285 	msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
286 	msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);
287 
288 	msg.AddInt32("colors", fBitmap->ColorSpace());
289 	_SendMessageToWindow(&msg);
290 
291 	FixupScrollBars();
292 	Invalidate();
293 }
294 
295 
296 void
297 ShowImageView::_UpdateStatusText()
298 {
299 	BMessage msg(MSG_UPDATE_STATUS_TEXT);
300 
301 	if (fHasSelection) {
302 		char size[50];
303 		snprintf(size, sizeof(size), "(%.0fx%.0f)",
304 			fSelectionBox.Bounds().Width() + 1.0,
305 			fSelectionBox.Bounds().Height() + 1.0);
306 
307 		msg.AddString("status", size);
308 	}
309 
310 	_SendMessageToWindow(&msg);
311 }
312 
313 
314 void
315 ShowImageView::_DeleteBitmap()
316 {
317 	_DeleteSelectionBitmap();
318 
319 	if (fDisplayBitmap != fBitmap)
320 		delete fDisplayBitmap;
321 	fDisplayBitmap = NULL;
322 
323 	if (fBitmapOwner != NULL)
324 		fBitmapOwner->ReleaseReference();
325 	else
326 		delete fBitmap;
327 
328 	fBitmapOwner = NULL;
329 	fBitmap = NULL;
330 }
331 
332 
333 void
334 ShowImageView::_DeleteSelectionBitmap()
335 {
336 	delete fSelectionBitmap;
337 	fSelectionBitmap = NULL;
338 }
339 
340 
341 status_t
342 ShowImageView::SetImage(const BMessage* message)
343 {
344 	BBitmap* bitmap;
345 	entry_ref ref;
346 	if (message->FindPointer("bitmap", (void**)&bitmap) != B_OK
347 		|| message->FindRef("ref", &ref) != B_OK || bitmap == NULL)
348 		return B_ERROR;
349 
350 	status_t status = SetImage(&ref, bitmap);
351 	if (status == B_OK) {
352 		fFormatDescription = message->FindString("type");
353 		fMimeType = message->FindString("mime");
354 
355 		message->FindPointer("bitmapOwner", (void**)&fBitmapOwner);
356 	}
357 
358 	return status;
359 }
360 
361 
362 status_t
363 ShowImageView::SetImage(const entry_ref* ref, BBitmap* bitmap)
364 {
365 	// Delete the old one, and clear everything
366 	fUndo.Clear();
367 	_SetHasSelection(false);
368 	fCreatingSelection = false;
369 	_DeleteBitmap();
370 
371 	fBitmap = bitmap;
372 	if (ref == NULL)
373 		fCurrentRef.device = -1;
374 	else
375 		fCurrentRef = *ref;
376 
377 	if (fBitmap != NULL) {
378 		// prepare the display bitmap
379 		if (fBitmap->ColorSpace() == B_RGBA32)
380 			fDisplayBitmap = compose_checker_background(fBitmap);
381 		if (fDisplayBitmap == NULL)
382 			fDisplayBitmap = fBitmap;
383 
384 		BNode node(ref);
385 
386 		// restore orientation
387 		int32 orientation;
388 		fImageOrientation = k0;
389 		if (node.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
390 				&orientation, sizeof(orientation)) == sizeof(orientation)) {
391 			orientation &= 255;
392 			switch (orientation) {
393 				case k0:
394 					break;
395 				case k90:
396 					_DoImageOperation(ImageProcessor::kRotateClockwise, true);
397 					break;
398 				case k180:
399 					_DoImageOperation(ImageProcessor::kRotateClockwise, true);
400 					_DoImageOperation(ImageProcessor::kRotateClockwise, true);
401 					break;
402 				case k270:
403 					_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
404 					break;
405 				case k0V:
406 					_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
407 					break;
408 				case k90V:
409 					_DoImageOperation(ImageProcessor::kRotateClockwise, true);
410 					_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
411 					break;
412 				case k0H:
413 					_DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true);
414 					break;
415 				case k270V:
416 					_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
417 					_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
418 					break;
419 			}
420 		}
421 	}
422 
423 	BPath path(ref);
424 	fCaption = path.Path();
425 	fFormatDescription = "Bitmap";
426 	fMimeType = "image/x-be-bitmap";
427 
428 	be_roster->AddToRecentDocuments(ref, kApplicationSignature);
429 
430 	FitToBounds();
431 	_Notify();
432 	return B_OK;
433 }
434 
435 
436 BPoint
437 ShowImageView::ImageToView(BPoint p) const
438 {
439 	p.x = floorf(fZoom * p.x + fBitmapLocationInView.x);
440 	p.y = floorf(fZoom * p.y + fBitmapLocationInView.y);
441 	return p;
442 }
443 
444 
445 BPoint
446 ShowImageView::ViewToImage(BPoint p) const
447 {
448 	p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom);
449 	p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom);
450 	return p;
451 }
452 
453 
454 BRect
455 ShowImageView::ImageToView(BRect r) const
456 {
457 	BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
458 	BPoint rightBottom(r.right, r.bottom);
459 	rightBottom += BPoint(1, 1);
460 	rightBottom = ImageToView(rightBottom);
461 	rightBottom -= BPoint(1, 1);
462 	return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
463 }
464 
465 
466 void
467 ShowImageView::ConstrainToImage(BPoint& point) const
468 {
469 	point.ConstrainTo(fBitmap->Bounds());
470 }
471 
472 
473 void
474 ShowImageView::ConstrainToImage(BRect& rect) const
475 {
476 	rect = rect & fBitmap->Bounds();
477 }
478 
479 
480 void
481 ShowImageView::SetShowCaption(bool show)
482 {
483 	if (fShowCaption != show) {
484 		fShowCaption = show;
485 		_UpdateCaption();
486 	}
487 }
488 
489 
490 void
491 ShowImageView::SetStretchToBounds(bool enable)
492 {
493 	if (fStretchToBounds != enable) {
494 		_SettingsSetBool("StretchToBounds", enable);
495 		fStretchToBounds = enable;
496 		if (enable || fZoom > 1.0)
497 			FitToBounds();
498 	}
499 }
500 
501 
502 void
503 ShowImageView::SetHideIdlingCursor(bool hide)
504 {
505 	fHideCursor = hide;
506 }
507 
508 
509 BBitmap*
510 ShowImageView::Bitmap()
511 {
512 	return fBitmap;
513 }
514 
515 
516 void
517 ShowImageView::SetScaleBilinear(bool enabled)
518 {
519 	if (fScaleBilinear != enabled) {
520 		_SettingsSetBool("ScaleBilinear", enabled);
521 		fScaleBilinear = enabled;
522 		Invalidate();
523 	}
524 }
525 
526 
527 void
528 ShowImageView::AttachedToWindow()
529 {
530 	FitToBounds();
531 	fUndo.SetWindow(Window());
532 	FixupScrollBars();
533 }
534 
535 
536 void
537 ShowImageView::FrameResized(float width, float height)
538 {
539 	FixupScrollBars();
540 }
541 
542 
543 float
544 ShowImageView::_FitToBoundsZoom() const
545 {
546 	if (fBitmap == NULL)
547 		return 1.0f;
548 
549 	// the width/height of the bitmap (in pixels)
550 	float bitmapWidth = fBitmap->Bounds().Width() + 1;
551 	float bitmapHeight = fBitmap->Bounds().Height() + 1;
552 
553 	// the available width/height for layouting the bitmap (in pixels)
554 	float width = Bounds().Width() + 1;
555 	float height = Bounds().Height() + 1;
556 
557 	float zoom = width / bitmapWidth;
558 
559 	if (zoom * bitmapHeight <= height)
560 		return zoom;
561 
562 	return height / bitmapHeight;
563 }
564 
565 
566 BRect
567 ShowImageView::_AlignBitmap()
568 {
569 	BRect rect(fBitmap->Bounds());
570 
571 	// the width/height of the bitmap (in pixels)
572 	float bitmapWidth = rect.Width() + 1;
573 	float bitmapHeight = rect.Height() + 1;
574 
575 	// the available width/height for layouting the bitmap (in pixels)
576 	float width = Bounds().Width() + 1;
577 	float height = Bounds().Height() + 1;
578 
579 	if (width == 0 || height == 0)
580 		return rect;
581 
582 	// zoom image
583 	rect.right = floorf(bitmapWidth * fZoom) - 1;
584 	rect.bottom = floorf(bitmapHeight * fZoom) - 1;
585 
586 	// update the bitmap size after the zoom
587 	bitmapWidth = rect.Width() + 1.0;
588 	bitmapHeight = rect.Height() + 1.0;
589 
590 	// always align in the center if the bitmap is smaller than the window
591 	if (width > bitmapWidth)
592 		rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);
593 
594 	if (height > bitmapHeight)
595 		rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));
596 
597 	return rect;
598 }
599 
600 
601 void
602 ShowImageView::_DrawBackground(BRect border)
603 {
604 	BRect bounds(Bounds());
605 	// top
606 	FillRect(BRect(0, 0, bounds.right, border.top - 1), B_SOLID_LOW);
607 	// left
608 	FillRect(BRect(0, border.top, border.left - 1, border.bottom), B_SOLID_LOW);
609 	// right
610 	FillRect(BRect(border.right + 1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
611 	// bottom
612 	FillRect(BRect(0, border.bottom + 1, bounds.right, bounds.bottom), B_SOLID_LOW);
613 }
614 
615 
616 void
617 ShowImageView::_LayoutCaption(BFont& font, BPoint& pos, BRect& rect)
618 {
619 	font_height fontHeight;
620 	float width, height;
621 	BRect bounds(Bounds());
622 	font = be_plain_font;
623 	width = font.StringWidth(fCaption.String());
624 	font.GetHeight(&fontHeight);
625 	height = fontHeight.ascent + fontHeight.descent;
626 	// center text horizontally
627 	pos.x = (bounds.left + bounds.right - width) / 2;
628 	// flush bottom
629 	pos.y = bounds.bottom - fontHeight.descent - 7;
630 
631 	// background rectangle
632 	rect.Set(0, 0, width + 4, height + 4);
633 	rect.OffsetTo(pos);
634 	rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
635 }
636 
637 
638 void
639 ShowImageView::_DrawCaption()
640 {
641 	BFont font;
642 	BPoint position;
643 	BRect rect;
644 	_LayoutCaption(font, position, rect);
645 
646 	PushState();
647 
648 	// draw background
649 	SetDrawingMode(B_OP_ALPHA);
650 	SetHighColor(255, 255, 255, 160);
651 	FillRect(rect);
652 
653 	// draw text
654 	SetDrawingMode(B_OP_OVER);
655 	SetFont(&font);
656 	SetLowColor(B_TRANSPARENT_COLOR);
657 	SetHighColor(0, 0, 0);
658 	DrawString(fCaption.String(), position);
659 
660 	PopState();
661 }
662 
663 
664 void
665 ShowImageView::_UpdateCaption()
666 {
667 	BFont font;
668 	BPoint pos;
669 	BRect rect;
670 	_LayoutCaption(font, pos, rect);
671 
672 	// draw over portion of image where caption is located
673 	BRegion clip(rect);
674 	PushState();
675 	ConstrainClippingRegion(&clip);
676 	Draw(rect);
677 	PopState();
678 }
679 
680 
681 void
682 ShowImageView::_DrawImage(BRect rect)
683 {
684 	// TODO: fix composing of fBitmap with other bitmaps
685 	// with regard to alpha channel
686 	if (!fDisplayBitmap)
687 		fDisplayBitmap = fBitmap;
688 
689 	uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0;
690 	DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options);
691 }
692 
693 
694 void
695 ShowImageView::Draw(BRect updateRect)
696 {
697 	if (fBitmap == NULL)
698 		return;
699 
700 	if (IsPrinting()) {
701 		DrawBitmap(fBitmap);
702 		return;
703 	}
704 
705 	BRect rect = _AlignBitmap();
706 	fBitmapLocationInView.x = floorf(rect.left);
707 	fBitmapLocationInView.y = floorf(rect.top);
708 
709 	_DrawBackground(rect);
710 	_DrawImage(rect);
711 
712 	if (fShowCaption)
713 		_DrawCaption();
714 
715 	if (fHasSelection) {
716 		if (fSelectionBitmap != NULL) {
717 			BRect srcRect;
718 			BRect dstRect;
719 			_GetSelectionMergeRects(srcRect, dstRect);
720 			dstRect = ImageToView(dstRect);
721 			DrawBitmap(fSelectionBitmap, srcRect, dstRect);
722 		}
723 		fSelectionBox.Draw(this, updateRect);
724 	}
725 }
726 
727 
728 BBitmap*
729 ShowImageView::_CopySelection(uchar alpha, bool imageSize)
730 {
731 	bool hasAlpha = alpha != 255;
732 
733 	if (!fHasSelection)
734 		return NULL;
735 
736 	BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN);
737 	if (!imageSize) {
738 		// scale image to view size
739 		rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
740 		rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
741 	}
742 	BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
743 	BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
744 		: fBitmap->ColorSpace(), true);
745 	if (bitmap == NULL || !bitmap->IsValid()) {
746 		delete bitmap;
747 		return NULL;
748 	}
749 
750 	if (bitmap->Lock()) {
751 		bitmap->AddChild(&view);
752 #ifdef __HAIKU__
753 		// On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
754 		// Don't know if it's better to fix it or not (stippi).
755 		if (hasAlpha) {
756 			view.SetHighColor(0, 0, 0, 0);
757 			view.FillRect(view.Bounds());
758 			view.SetDrawingMode(B_OP_ALPHA);
759 			view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
760 			view.SetHighColor(0, 0, 0, alpha);
761 		}
762 		if (fSelectionBitmap) {
763 			view.DrawBitmap(fSelectionBitmap,
764 				fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
765 		} else
766 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
767 #else
768 		if (fSelectionBitmap) {
769 			view.DrawBitmap(fSelectionBitmap,
770 				fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
771 		} else
772 			view.DrawBitmap(fBitmap, fCopyFromRect, rect);
773 		if (hasAlpha) {
774 			view.SetDrawingMode(B_OP_SUBTRACT);
775 			view.SetHighColor(0, 0, 0, 255 - alpha);
776 			view.FillRect(rect, B_SOLID_HIGH);
777 		}
778 #endif
779 		view.Sync();
780 		bitmap->RemoveChild(&view);
781 		bitmap->Unlock();
782 	}
783 
784 	return bitmap;
785 }
786 
787 
788 bool
789 ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
790 {
791 	BTranslatorRoster* roster = BTranslatorRoster::Default();
792 	if (roster == NULL)
793 		return false;
794 
795 	// add the current image mime first, will make it the preferred format on
796 	// left mouse drag
797 	msg->AddString("be:types", fMimeType);
798 	msg->AddString("be:filetypes", fMimeType);
799 	msg->AddString("be:type_descriptions", fFormatDescription);
800 
801 	bool foundOther = false;
802 	bool foundCurrent = false;
803 
804 	int32 infoCount;
805 	translator_info* info;
806 	BBitmapStream stream(bitmap);
807 	if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
808 		for (int32 i = 0; i < infoCount; i++) {
809 			const translation_format* formats;
810 			int32 count;
811 			roster->GetOutputFormats(info[i].translator, &formats, &count);
812 			for (int32 j = 0; j < count; j++) {
813 				if (fMimeType == formats[j].MIME)
814 					foundCurrent = true;
815 				else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
816 					foundOther = true;
817 					// needed to send data in message
818 					msg->AddString("be:types", formats[j].MIME);
819 					// needed to pass data via file
820 					msg->AddString("be:filetypes", formats[j].MIME);
821 					msg->AddString("be:type_descriptions", formats[j].name);
822 				}
823 			}
824 		}
825 	}
826 	stream.DetachBitmap(&bitmap);
827 
828 	if (!foundCurrent) {
829 		msg->RemoveData("be:types", 0);
830 		msg->RemoveData("be:filetypes", 0);
831 		msg->RemoveData("be:type_descriptions", 0);
832 	}
833 
834 	return foundOther || foundCurrent;
835 }
836 
837 
838 void
839 ShowImageView::_BeginDrag(BPoint sourcePoint)
840 {
841 	BBitmap* bitmap = _CopySelection(128, false);
842 	if (bitmap == NULL)
843 		return;
844 
845 	SetMouseEventMask(B_POINTER_EVENTS);
846 
847 	// fill the drag message
848 	BMessage drag(B_SIMPLE_DATA);
849 	drag.AddInt32("be:actions", B_COPY_TARGET);
850 	drag.AddString("be:clip_name", "Bitmap Clip");
851 	// ShowImage specific fields
852 	drag.AddPoint("be:_source_point", sourcePoint);
853 	drag.AddRect("be:_frame", fSelectionBox.Bounds());
854 	if (_AddSupportedTypes(&drag, bitmap)) {
855 		// we also support "Passing Data via File" protocol
856 		drag.AddString("be:types", B_FILE_MIME_TYPE);
857 		// avoid flickering of dragged bitmap caused by drawing into the window
858 		_AnimateSelection(false);
859 		// only use a transparent bitmap on selections less than 400x400
860 		// (taking into account zooming)
861 		BRect selectionRect = fSelectionBox.Bounds();
862 		if (selectionRect.Width() * fZoom < 400.0
863 			&& selectionRect.Height() * fZoom < 400.0) {
864 			sourcePoint -= selectionRect.LeftTop();
865 			sourcePoint.x *= fZoom;
866 			sourcePoint.y *= fZoom;
867 			// DragMessage takes ownership of bitmap
868 			DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
869 			bitmap = NULL;
870 		} else {
871 			delete bitmap;
872 			// Offset and scale the rect
873 			BRect rect(selectionRect);
874 			rect = ImageToView(rect);
875 			rect.InsetBy(-1, -1);
876 			DragMessage(&drag, rect);
877 		}
878 	}
879 }
880 
881 
882 bool
883 ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type,
884 	translation_format* format)
885 {
886 	bool found = false;
887 
888 	BTranslatorRoster* roster = BTranslatorRoster::Default();
889 	if (roster == NULL)
890 		return false;
891 
892 	BBitmapStream stream(bitmap);
893 
894 	translator_info* outInfo;
895 	int32 outNumInfo;
896 	if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
897 		for (int32 i = 0; i < outNumInfo; i++) {
898 			const translation_format* formats;
899 			int32 formatCount;
900 			roster->GetOutputFormats(outInfo[i].translator, &formats,
901 				&formatCount);
902 			for (int32 j = 0; j < formatCount; j++) {
903 				if (strcmp(formats[j].MIME, type) == 0) {
904 					*format = formats[j];
905 					found = true;
906 					break;
907 				}
908 			}
909 		}
910 	}
911 	stream.DetachBitmap(&bitmap);
912 	return found;
913 }
914 
915 
916 #undef B_TRANSLATION_CONTEXT
917 #define B_TRANSLATION_CONTEXT "SaveToFile"
918 
919 
920 void
921 ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
922 	const translation_format* format)
923 {
924 	if (bitmap == NULL) {
925 		// If no bitmap is supplied, write out the whole image
926 		bitmap = fBitmap;
927 	}
928 
929 	BBitmapStream stream(bitmap);
930 
931 	bool loop = true;
932 	while (loop) {
933 		BTranslatorRoster* roster = BTranslatorRoster::Default();
934 		if (!roster)
935 			break;
936 		// write data
937 		BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
938 		if (file.InitCheck() != B_OK)
939 			break;
940 		if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
941 			break;
942 		// set mime type
943 		BNodeInfo info(&file);
944 		if (info.InitCheck() == B_OK)
945 			info.SetType(format->MIME);
946 
947 		loop = false;
948 			// break out of loop gracefully (indicates no errors)
949 	}
950 	if (loop) {
951 		// If loop terminated because of a break, there was an error
952 		char buffer[512];
953 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not "
954 			"be written."), name);
955 		BAlert* palert = new BAlert("", buffer, B_TRANSLATE("OK"));
956 		palert->SetFlags(palert->Flags() | B_CLOSE_ON_ESCAPE);
957 		palert->Go();
958 	}
959 
960 	stream.DetachBitmap(&bitmap);
961 		// Don't allow the bitmap to be deleted, this is
962 		// especially important when using fBitmap as the bitmap
963 }
964 
965 
966 void
967 ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap,
968 	translation_format* format)
969 {
970 	BMessage reply(B_MIME_DATA);
971 	BBitmapStream stream(bitmap); // destructor deletes bitmap
972 	BTranslatorRoster* roster = BTranslatorRoster::Default();
973 	BMallocIO memStream;
974 	if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
975 		reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(),
976 			memStream.BufferLength());
977 		msg->SendReply(&reply);
978 	}
979 }
980 
981 
982 void
983 ShowImageView::_HandleDrop(BMessage* msg)
984 {
985 	entry_ref dirRef;
986 	BString name, type;
987 	bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
988 		&& msg->FindRef("directory", &dirRef) == B_OK
989 		&& msg->FindString("name", &name) == B_OK;
990 
991 	bool sendInMessage = !saveToFile
992 		&& msg->FindString("be:types", &type) == B_OK;
993 
994 	BBitmap* bitmap = _CopySelection();
995 	if (bitmap == NULL)
996 		return;
997 
998 	translation_format format;
999 	if (!_OutputFormatForType(bitmap, type.String(), &format)) {
1000 		delete bitmap;
1001 		return;
1002 	}
1003 
1004 	if (saveToFile) {
1005 		BDirectory dir(&dirRef);
1006 		SaveToFile(&dir, name.String(), bitmap, &format);
1007 		delete bitmap;
1008 	} else if (sendInMessage) {
1009 		_SendInMessage(msg, bitmap, &format);
1010 	} else {
1011 		delete bitmap;
1012 	}
1013 }
1014 
1015 
1016 void
1017 ShowImageView::_ScrollBitmap(BPoint point)
1018 {
1019 	point = ConvertToScreen(point);
1020 	BPoint delta = fFirstPoint - point;
1021 	fFirstPoint = point;
1022 	_ScrollRestrictedBy(delta.x, delta.y);
1023 }
1024 
1025 
1026 void
1027 ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect,
1028 	BRect& dstRect)
1029 {
1030 	// Constrain dstRect to target image size and apply the same edge offsets
1031 	// to the srcRect.
1032 
1033 	dstRect = selection;
1034 
1035 	BRect clippedDstRect(dstRect);
1036 	ConstrainToImage(clippedDstRect);
1037 
1038 	srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN);
1039 
1040 	srcRect.left += clippedDstRect.left - dstRect.left;
1041 	srcRect.top += clippedDstRect.top - dstRect.top;
1042 	srcRect.right += clippedDstRect.right - dstRect.right;
1043 	srcRect.bottom += clippedDstRect.bottom - dstRect.bottom;
1044 
1045 	dstRect = clippedDstRect;
1046 }
1047 
1048 
1049 void
1050 ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
1051 {
1052 	_GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
1053 }
1054 
1055 
1056 void
1057 ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection)
1058 {
1059 	BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
1060 	BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(),
1061 		fBitmap->ColorSpace(), true);
1062 	if (bitmap == NULL || !bitmap->IsValid()) {
1063 		delete bitmap;
1064 		return;
1065 	}
1066 
1067 	if (bitmap->Lock()) {
1068 		bitmap->AddChild(&view);
1069 		view.DrawBitmap(fBitmap, fBitmap->Bounds());
1070 		BRect srcRect;
1071 		BRect dstRect;
1072 		_GetMergeRects(merge, selection, srcRect, dstRect);
1073 		view.DrawBitmap(merge, srcRect, dstRect);
1074 
1075 		view.Sync();
1076 		bitmap->RemoveChild(&view);
1077 		bitmap->Unlock();
1078 
1079 		_DeleteBitmap();
1080 		fBitmap = bitmap;
1081 
1082 		_SendMessageToWindow(MSG_MODIFIED);
1083 	} else
1084 		delete bitmap;
1085 }
1086 
1087 
1088 void
1089 ShowImageView::MouseDown(BPoint position)
1090 {
1091 	MakeFocus(true);
1092 
1093 	BPoint point = ViewToImage(position);
1094 	int32 clickCount = 0;
1095 	uint32 buttons = 0;
1096 	if (Window() != NULL && Window()->CurrentMessage() != NULL) {
1097 		clickCount = Window()->CurrentMessage()->FindInt32("clicks");
1098 		buttons = Window()->CurrentMessage()->FindInt32("buttons");
1099 	}
1100 
1101 	// Using clickCount >= 2 and the modulo 2 accounts for quickly repeated
1102 	// double-clicks
1103 	if (buttons == B_PRIMARY_MOUSE_BUTTON && clickCount >= 2 &&
1104 			clickCount % 2 == 0) {
1105 		Window()->PostMessage(MSG_FULL_SCREEN);
1106 		return;
1107 	}
1108 
1109 	if (fHasSelection && fSelectionBox.Bounds().Contains(point)
1110 		&& (buttons
1111 				& (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
1112 		if (!fSelectionBitmap)
1113 			fSelectionBitmap = _CopySelection();
1114 
1115 		_BeginDrag(point);
1116 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON
1117 			&& (fSelectionMode
1118 				|| (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) {
1119 		// begin new selection
1120 		_SetHasSelection(true);
1121 		fCreatingSelection = true;
1122 		SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1123 		ConstrainToImage(point);
1124 		fFirstPoint = point;
1125 		fCopyFromRect.Set(point.x, point.y, point.x, point.y);
1126 		fSelectionBox.SetBounds(this, fCopyFromRect);
1127 		Invalidate();
1128 	} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
1129 		_ShowPopUpMenu(ConvertToScreen(position));
1130 	} else if (buttons == B_PRIMARY_MOUSE_BUTTON
1131 		|| buttons == B_TERTIARY_MOUSE_BUTTON) {
1132 		// move image in window
1133 		SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
1134 		fScrollingBitmap = true;
1135 		fFirstPoint = ConvertToScreen(position);
1136 		be_app->SetCursor(fGrabCursor);
1137 	}
1138 }
1139 
1140 
1141 void
1142 ShowImageView::_UpdateSelectionRect(BPoint point, bool final)
1143 {
1144 	BRect oldSelection = fCopyFromRect;
1145 	point = ViewToImage(point);
1146 	ConstrainToImage(point);
1147 	fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
1148 	fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
1149 	fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
1150 	fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
1151 	fSelectionBox.SetBounds(this, fCopyFromRect);
1152 
1153 	if (final) {
1154 		// selection must be at least 2 pixels wide or 2 pixels tall
1155 		if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
1156 			_SetHasSelection(false);
1157 	} else
1158 		_UpdateStatusText();
1159 
1160 	if (oldSelection != fCopyFromRect || !fHasSelection) {
1161 		BRect updateRect;
1162 		updateRect = oldSelection | fCopyFromRect;
1163 		updateRect = ImageToView(updateRect);
1164 		updateRect.InsetBy(-1, -1);
1165 		Invalidate(updateRect);
1166 	}
1167 }
1168 
1169 
1170 void
1171 ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message)
1172 {
1173 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1174 	if (fHideCursor) {
1175 		// Show toolbar when mouse hits top 15 pixels, hide otherwise
1176 		_ShowToolBarIfEnabled(ConvertToScreen(point).y <= 15);
1177 	}
1178 	if (fCreatingSelection)
1179 		_UpdateSelectionRect(point, false);
1180 	else if (fScrollingBitmap)
1181 		_ScrollBitmap(point);
1182 }
1183 
1184 
1185 void
1186 ShowImageView::MouseUp(BPoint point)
1187 {
1188 	if (fCreatingSelection) {
1189 		_UpdateSelectionRect(point, true);
1190 		fCreatingSelection = false;
1191 	} else if (fScrollingBitmap) {
1192 		_ScrollBitmap(point);
1193 		fScrollingBitmap = false;
1194 		be_app->SetCursor(fDefaultCursor);
1195 	}
1196 	_AnimateSelection(true);
1197 }
1198 
1199 
1200 float
1201 ShowImageView::_LimitToRange(float v, orientation o, bool absolute)
1202 {
1203 	BScrollBar* psb = ScrollBar(o);
1204 	if (psb) {
1205 		float min, max, pos;
1206 		pos = v;
1207 		if (!absolute)
1208 			pos += psb->Value();
1209 
1210 		psb->GetRange(&min, &max);
1211 		if (pos < min)
1212 			pos = min;
1213 		else if (pos > max)
1214 			pos = max;
1215 
1216 		v = pos;
1217 		if (!absolute)
1218 			v -= psb->Value();
1219 	}
1220 	return v;
1221 }
1222 
1223 
1224 void
1225 ShowImageView::_ScrollRestricted(float x, float y, bool absolute)
1226 {
1227 	if (x != 0)
1228 		x = _LimitToRange(x, B_HORIZONTAL, absolute);
1229 
1230 	if (y != 0)
1231 		y = _LimitToRange(y, B_VERTICAL, absolute);
1232 
1233 	// We invalidate before we scroll to avoid the caption messing up the
1234 	// image, and to prevent it from flickering
1235 	if (fShowCaption)
1236 		Invalidate();
1237 
1238 	ScrollBy(x, y);
1239 }
1240 
1241 
1242 // XXX method is not unused
1243 void
1244 ShowImageView::_ScrollRestrictedTo(float x, float y)
1245 {
1246 	_ScrollRestricted(x, y, true);
1247 }
1248 
1249 
1250 void
1251 ShowImageView::_ScrollRestrictedBy(float x, float y)
1252 {
1253 	_ScrollRestricted(x, y, false);
1254 }
1255 
1256 
1257 void
1258 ShowImageView::KeyDown(const char* bytes, int32 numBytes)
1259 {
1260 	if (numBytes != 1) {
1261 		BView::KeyDown(bytes, numBytes);
1262 		return;
1263 	}
1264 
1265 	bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0;
1266 
1267 	switch (*bytes) {
1268 		case B_DOWN_ARROW:
1269 			if (shiftKeyDown)
1270 				_ScrollRestrictedBy(0, 10);
1271 			else
1272 				_SendMessageToWindow(MSG_FILE_NEXT);
1273 			break;
1274 		case B_RIGHT_ARROW:
1275 			if (shiftKeyDown)
1276 				_ScrollRestrictedBy(10, 0);
1277 			else
1278 				_SendMessageToWindow(MSG_FILE_NEXT);
1279 			break;
1280 		case B_UP_ARROW:
1281 			if (shiftKeyDown)
1282 				_ScrollRestrictedBy(0, -10);
1283 			else
1284 				_SendMessageToWindow(MSG_FILE_PREV);
1285 			break;
1286 		case B_LEFT_ARROW:
1287 			if (shiftKeyDown)
1288 				_ScrollRestrictedBy(-10, 0);
1289 			else
1290 				_SendMessageToWindow(MSG_FILE_PREV);
1291 			break;
1292 		case B_BACKSPACE:
1293 			_SendMessageToWindow(MSG_FILE_PREV);
1294 			break;
1295 		case B_HOME:
1296 			break;
1297 		case B_END:
1298 			break;
1299 		case B_SPACE:
1300 			_ToggleSlideShow();
1301 			break;
1302 		case B_ESCAPE:
1303 			// stop slide show
1304 			_StopSlideShow();
1305 			_ExitFullScreen();
1306 
1307 			ClearSelection();
1308 			break;
1309 		case B_DELETE:
1310 			if (fHasSelection)
1311 				ClearSelection();
1312 			else
1313 				_SendMessageToWindow(kMsgDeleteCurrentFile);
1314 			break;
1315 		case '0':
1316 			FitToBounds();
1317 			break;
1318 		case '1':
1319 			SetZoom(1.0f);
1320 			break;
1321 		case '+':
1322 		case '=':
1323 			ZoomIn();
1324 			break;
1325 		case '-':
1326 			ZoomOut();
1327 			break;
1328 		case '[':
1329 			Rotate(270);
1330 			break;
1331 		case ']':
1332 			Rotate(90);
1333 			break;
1334 	}
1335 }
1336 
1337 
1338 void
1339 ShowImageView::_MouseWheelChanged(BMessage* msg)
1340 {
1341 	// The BeOS driver does not currently support
1342 	// X wheel scrolling, therefore, dx is zero.
1343 	// |dy| is the number of notches scrolled up or down.
1344 	// When the wheel is scrolled down (towards the user) dy > 0
1345 	// When the wheel is scrolled up (away from the user) dy < 0
1346 	const float kscrollBy = 40;
1347 	float dy, dx;
1348 	float x, y;
1349 	x = 0; y = 0;
1350 	if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK)
1351 		x = dx * kscrollBy;
1352 	if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK)
1353 		y = dy * kscrollBy;
1354 
1355 	if ((modifiers() & B_SHIFT_KEY) != 0)
1356 		_ScrollRestrictedBy(x, y);
1357 	else if ((modifiers() & B_COMMAND_KEY) != 0)
1358 		_ScrollRestrictedBy(y, x);
1359 	else {
1360 		// Zoom in spot
1361 		BPoint where;
1362 		uint32 buttons;
1363 		GetMouse(&where, &buttons);
1364 
1365 		if (fStickyZoomCountDown <= 0) {
1366 			if (dy < 0)
1367 				ZoomIn(where);
1368 			else if (dy > 0)
1369 				ZoomOut(where);
1370 
1371 			if (fZoom == 1.0)
1372 				fStickyZoomCountDown = STICKY_ZOOM_DELAY_TIME;
1373 		}
1374 
1375 	}
1376 }
1377 
1378 
1379 void
1380 ShowImageView::_ShowPopUpMenu(BPoint screen)
1381 {
1382 	if (!fShowingPopUpMenu) {
1383 		PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
1384 
1385 		ShowImageWindow* window = dynamic_cast<ShowImageWindow*>(Window());
1386 		if (window != NULL)
1387 			window->BuildContextMenu(menu);
1388 
1389 		menu->Go(screen, true, true, true);
1390 		fShowingPopUpMenu = true;
1391 	}
1392 }
1393 
1394 
1395 void
1396 ShowImageView::_SettingsSetBool(const char* name, bool value)
1397 {
1398 	ShowImageSettings* settings;
1399 	settings = my_app->Settings();
1400 	if (settings->Lock()) {
1401 		settings->SetBool(name, value);
1402 		settings->Unlock();
1403 	}
1404 }
1405 
1406 
1407 void
1408 ShowImageView::MessageReceived(BMessage* message)
1409 {
1410 	switch (message->what) {
1411 		case B_COPY_TARGET:
1412 			_HandleDrop(message);
1413 			break;
1414 		case B_MOUSE_WHEEL_CHANGED:
1415 			_MouseWheelChanged(message);
1416 			break;
1417 
1418 		case kMsgPopUpMenuClosed:
1419 			fShowingPopUpMenu = false;
1420 			break;
1421 
1422 		default:
1423 			BView::MessageReceived(message);
1424 			break;
1425 	}
1426 }
1427 
1428 
1429 void
1430 ShowImageView::FixupScrollBar(orientation o, float bitmapLength,
1431 	float viewLength)
1432 {
1433 	float prop, range;
1434 	BScrollBar* psb;
1435 
1436 	psb = ScrollBar(o);
1437 	if (psb) {
1438 		range = bitmapLength - viewLength;
1439 		if (range < 0.0)
1440 			range = 0.0;
1441 
1442 		prop = viewLength / bitmapLength;
1443 		if (prop > 1.0)
1444 			prop = 1.0;
1445 
1446 		psb->SetRange(0, range);
1447 		psb->SetProportion(prop);
1448 		psb->SetSteps(10, 100);
1449 	}
1450 }
1451 
1452 
1453 void
1454 ShowImageView::FixupScrollBars()
1455 {
1456 	BRect viewRect = Bounds();
1457 	BRect bitmapRect;
1458 	if (fBitmap != NULL) {
1459 		bitmapRect = _AlignBitmap();
1460 		bitmapRect.OffsetTo(0, 0);
1461 	}
1462 
1463 	FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width());
1464 	FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height());
1465 }
1466 
1467 
1468 void
1469 ShowImageView::SetSelectionMode(bool selectionMode)
1470 {
1471 	// The mode only has an effect in MouseDown()
1472 	fSelectionMode = selectionMode;
1473 }
1474 
1475 
1476 void
1477 ShowImageView::Undo()
1478 {
1479 	int32 undoType = fUndo.GetType();
1480 	if (undoType != UNDO_UNDO && undoType != UNDO_REDO)
1481 		return;
1482 
1483 	// backup current selection
1484 	BRect undoneSelRect;
1485 	BBitmap* undoneSelection;
1486 	undoneSelRect = fSelectionBox.Bounds();
1487 	undoneSelection = _CopySelection();
1488 
1489 	if (undoType == UNDO_UNDO) {
1490 		BBitmap* undoRestore;
1491 		undoRestore = fUndo.GetRestoreBitmap();
1492 		if (undoRestore)
1493 			_MergeWithBitmap(undoRestore, fUndo.GetRect());
1494 	}
1495 
1496 	// restore previous image/selection
1497 	BBitmap* undoSelection;
1498 	undoSelection = fUndo.GetSelectionBitmap();
1499 		// NOTE: ShowImageView is responsible for deleting this bitmap
1500 		// (Which it will, as it would with a fSelectionBitmap that it
1501 		// allocated itself)
1502 	if (!undoSelection)
1503 		_SetHasSelection(false);
1504 	else {
1505 		fCopyFromRect = BRect();
1506 		fSelectionBox.SetBounds(this, fUndo.GetRect());
1507 		_SetHasSelection(true);
1508 		fSelectionBitmap = undoSelection;
1509 	}
1510 
1511 	fUndo.Undo(undoneSelRect, NULL, undoneSelection);
1512 
1513 	Invalidate();
1514 }
1515 
1516 
1517 void
1518 ShowImageView::SelectAll()
1519 {
1520 	fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
1521 		fBitmap->Bounds().Height());
1522 	fSelectionBox.SetBounds(this, fCopyFromRect);
1523 	_SetHasSelection(true);
1524 	Invalidate();
1525 }
1526 
1527 
1528 void
1529 ShowImageView::ClearSelection()
1530 {
1531 	if (!fHasSelection)
1532 		return;
1533 
1534 	_SetHasSelection(false);
1535 	Invalidate();
1536 }
1537 
1538 
1539 void
1540 ShowImageView::_SetHasSelection(bool hasSelection)
1541 {
1542 	_DeleteSelectionBitmap();
1543 	fHasSelection = hasSelection;
1544 
1545 	_UpdateStatusText();
1546 
1547 	BMessage msg(MSG_SELECTION);
1548 	msg.AddBool("has_selection", fHasSelection);
1549 	_SendMessageToWindow(&msg);
1550 }
1551 
1552 
1553 void
1554 ShowImageView::CopySelectionToClipboard()
1555 {
1556 	if (!fHasSelection || !be_clipboard->Lock())
1557 		return;
1558 
1559 	be_clipboard->Clear();
1560 
1561 	BMessage* data = be_clipboard->Data();
1562 	if (data != NULL) {
1563 		BBitmap* bitmap = _CopySelection();
1564 		if (bitmap != NULL) {
1565 			BMessage bitmapArchive;
1566 			bitmap->Archive(&bitmapArchive);
1567 			// NOTE: Possibly "image/x-be-bitmap" is more correct.
1568 			// This works with WonderBrush, though, which in turn had been
1569 			// tested with other apps.
1570 			data->AddMessage("image/bitmap", &bitmapArchive);
1571 			data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop());
1572 
1573 			delete bitmap;
1574 
1575 			be_clipboard->Commit();
1576 		}
1577 	}
1578 	be_clipboard->Unlock();
1579 }
1580 
1581 
1582 void
1583 ShowImageView::SetZoom(float zoom, BPoint where)
1584 {
1585 	float fitToBoundsZoom = _FitToBoundsZoom();
1586 	if (zoom > 32)
1587 		zoom = 32;
1588 	if (zoom < fitToBoundsZoom / 2 && zoom < 0.25)
1589 		zoom = min_c(fitToBoundsZoom / 2, 0.25);
1590 
1591 	if (zoom == fZoom) {
1592 		// window size might have changed
1593 		FixupScrollBars();
1594 		return;
1595 	}
1596 
1597 	// Invalidate before scrolling, as that prevents the app_server
1598 	// to do the scrolling server side
1599 	Invalidate();
1600 
1601 	// zoom to center if not otherwise specified
1602 	BPoint offset;
1603 	if (where.x == -1) {
1604 		where.Set(Bounds().Width() / 2, Bounds().Height() / 2);
1605 		offset = where;
1606 		where += Bounds().LeftTop();
1607 	} else
1608 		offset = where - Bounds().LeftTop();
1609 
1610 	float oldZoom = fZoom;
1611 	fZoom = zoom;
1612 
1613 	FixupScrollBars();
1614 
1615 	if (fBitmap != NULL) {
1616 		offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x;
1617 		offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y;
1618 		ScrollTo(offset);
1619 	}
1620 }
1621 
1622 
1623 void
1624 ShowImageView::ZoomIn(BPoint where)
1625 {
1626 	// snap zoom to "fit to bounds", and "original size"
1627 	float zoom = fZoom * 1.2;
1628 	float zoomSnap = fZoom * 1.25;
1629 	float fitToBoundsZoom = _FitToBoundsZoom();
1630 	if (fZoom < fitToBoundsZoom - 0.001 && zoomSnap > fitToBoundsZoom)
1631 		zoom = fitToBoundsZoom;
1632 	if (fZoom < 1.0 && zoomSnap > 1.0)
1633 		zoom = 1.0;
1634 
1635 	SetZoom(zoom, where);
1636 }
1637 
1638 
1639 void
1640 ShowImageView::ZoomOut(BPoint where)
1641 {
1642 	// snap zoom to "fit to bounds", and "original size"
1643 	float zoom = fZoom / 1.2;
1644 	float zoomSnap = fZoom / 1.25;
1645 	float fitToBoundsZoom = _FitToBoundsZoom();
1646 	if (fZoom > fitToBoundsZoom + 0.001 && zoomSnap < fitToBoundsZoom)
1647 		zoom = fitToBoundsZoom;
1648 	if (fZoom > 1.0 && zoomSnap < 1.0)
1649 		zoom = 1.0;
1650 
1651 	SetZoom(zoom, where);
1652 }
1653 
1654 
1655 /*!	Fits the image to the view bounds.
1656 */
1657 void
1658 ShowImageView::FitToBounds()
1659 {
1660 	if (fBitmap == NULL)
1661 		return;
1662 
1663 	float fitToBoundsZoom = _FitToBoundsZoom();
1664 	if (!fStretchToBounds && fitToBoundsZoom > 1.0f)
1665 		SetZoom(1.0f);
1666 	else
1667 		SetZoom(fitToBoundsZoom);
1668 
1669 	FixupScrollBars();
1670 }
1671 
1672 
1673 void
1674 ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet)
1675 {
1676 	BMessenger msgr;
1677 	ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
1678 	imageProcessor.Start(false);
1679 	BBitmap* bm = imageProcessor.DetachBitmap();
1680 	if (bm == NULL) {
1681 		// operation failed
1682 		return;
1683 	}
1684 
1685 	// update orientation state
1686 	if (op != ImageProcessor::kInvert) {
1687 		// Note: If one of these fails, check its definition in class ImageProcessor.
1688 //		ASSERT(ImageProcessor::kRotateClockwise <
1689 //			ImageProcessor::kNumberOfAffineTransformations);
1690 //		ASSERT(ImageProcessor::kRotateCounterClockwise <
1691 //			ImageProcessor::kNumberOfAffineTransformations);
1692 //		ASSERT(ImageProcessor::kFlipLeftToRight <
1693 //			ImageProcessor::kNumberOfAffineTransformations);
1694 //		ASSERT(ImageProcessor::kFlipTopToBottom <
1695 //			ImageProcessor::kNumberOfAffineTransformations);
1696 		fImageOrientation = fTransformation[op][fImageOrientation];
1697 	}
1698 
1699 	if (!quiet) {
1700 		// write orientation state
1701 		BNode node(&fCurrentRef);
1702 		int32 orientation = fImageOrientation;
1703 		if (orientation != k0) {
1704 			node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
1705 				&orientation, sizeof(orientation));
1706 		} else
1707 			node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
1708 	}
1709 
1710 	// set new bitmap
1711 	_DeleteBitmap();
1712 	fBitmap = bm;
1713 
1714 	if (!quiet) {
1715 		// remove selection
1716 		_SetHasSelection(false);
1717 		_Notify();
1718 	}
1719 }
1720 
1721 
1722 //! Image operation initiated by user
1723 void
1724 ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
1725 {
1726 	fUndo.Clear();
1727 	_DoImageOperation(op, quiet);
1728 }
1729 
1730 
1731 void
1732 ShowImageView::Rotate(int degree)
1733 {
1734 	_UserDoImageOperation(degree == 90 ? ImageProcessor::kRotateClockwise
1735 		: ImageProcessor::kRotateCounterClockwise);
1736 
1737 	FitToBounds();
1738 }
1739 
1740 
1741 void
1742 ShowImageView::Flip(bool vertical)
1743 {
1744 	if (vertical)
1745 		_UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
1746 	else
1747 		_UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
1748 }
1749 
1750 
1751 void
1752 ShowImageView::ResizeImage(int w, int h)
1753 {
1754 	if (fBitmap == NULL || w < 1 || h < 1)
1755 		return;
1756 
1757 	Scaler scaler(fBitmap, BRect(0, 0, w - 1, h - 1), BMessenger(), 0, false);
1758 	scaler.Start(false);
1759 	BBitmap* scaled = scaler.DetachBitmap();
1760 	if (scaled == NULL) {
1761 		// operation failed
1762 		return;
1763 	}
1764 
1765 	// remove selection
1766 	_SetHasSelection(false);
1767 	fUndo.Clear();
1768 	_DeleteBitmap();
1769 	fBitmap = scaled;
1770 
1771 	_SendMessageToWindow(MSG_MODIFIED);
1772 
1773 	_Notify();
1774 }
1775 
1776 
1777 void
1778 ShowImageView::_SetIcon(bool clear, icon_size which)
1779 {
1780 	int32 size;
1781 	switch (which) {
1782 		case B_MINI_ICON: size = 16;
1783 			break;
1784 		case B_LARGE_ICON: size = 32;
1785 			break;
1786 		default:
1787 			return;
1788 	}
1789 
1790 	BRect rect(fBitmap->Bounds());
1791 	float s;
1792 	s = size / (rect.Width() + 1.0);
1793 
1794 	if (s * (rect.Height() + 1.0) <= size) {
1795 		rect.right = size - 1;
1796 		rect.bottom = static_cast<int>(s * (rect.Height() + 1.0)) - 1;
1797 		// center vertically
1798 		rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
1799 	} else {
1800 		s = size / (rect.Height() + 1.0);
1801 		rect.right = static_cast<int>(s * (rect.Width() + 1.0)) - 1;
1802 		rect.bottom = size - 1;
1803 		// center horizontally
1804 		rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
1805 	}
1806 
1807 	// scale bitmap to thumbnail size
1808 	BMessenger msgr;
1809 	Scaler scaler(fBitmap, rect, msgr, 0, true);
1810 	BBitmap* thumbnail = scaler.GetBitmap();
1811 	scaler.Start(false);
1812 	ASSERT(thumbnail->ColorSpace() == B_CMAP8);
1813 	// create icon from thumbnail
1814 	BBitmap icon(BRect(0, 0, size - 1, size - 1), B_CMAP8);
1815 	memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
1816 	BScreen screen;
1817 	const uchar* src = (uchar*)thumbnail->Bits();
1818 	uchar* dest = (uchar*)icon.Bits();
1819 	const int32 srcBPR = thumbnail->BytesPerRow();
1820 	const int32 destBPR = icon.BytesPerRow();
1821 	const int32 dx = (int32)rect.left;
1822 	const int32 dy = (int32)rect.top;
1823 
1824 	for (int32 y = 0; y <= rect.IntegerHeight(); y++) {
1825 		for (int32 x = 0; x <= rect.IntegerWidth(); x++) {
1826 			const uchar* s = src + y * srcBPR + x;
1827 			uchar* d = dest + (y + dy) * destBPR + (x + dx);
1828 			*d = *s;
1829 		}
1830 	}
1831 
1832 	// set icon
1833 	BNode node(&fCurrentRef);
1834 	BNodeInfo info(&node);
1835 	info.SetIcon(clear ? NULL : &icon, which);
1836 }
1837 
1838 
1839 void
1840 ShowImageView::SetIcon(bool clear)
1841 {
1842 	_SetIcon(clear, B_MINI_ICON);
1843 	_SetIcon(clear, B_LARGE_ICON);
1844 }
1845 
1846 
1847 void
1848 ShowImageView::_ToggleSlideShow()
1849 {
1850 	_SendMessageToWindow(MSG_SLIDE_SHOW);
1851 }
1852 
1853 
1854 void
1855 ShowImageView::_StopSlideShow()
1856 {
1857 	_SendMessageToWindow(kMsgStopSlideShow);
1858 }
1859 
1860 
1861 void
1862 ShowImageView::_ExitFullScreen()
1863 {
1864 	be_app->ShowCursor();
1865 	_SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
1866 }
1867 
1868 
1869 void
1870 ShowImageView::_ShowToolBarIfEnabled(bool show)
1871 {
1872 	BMessage message(kShowToolBarIfEnabled);
1873 	message.AddBool("show", show);
1874 	Window()->PostMessage(&message);
1875 }
1876 
1877 
1878 void
1879 ShowImageView::WindowActivated(bool active)
1880 {
1881 	fIsActiveWin = active;
1882 	fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
1883 }
1884 
1885