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