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