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