xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 23d878482ed22e55dad6d1fca1df7bea42eb157c)
1 /*
2  * Copyright 2003-2011, Haiku, Inc. All Rights Reserved.
3  * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
4  * Copyright 2006 Bernd Korz. All Rights Reserved
5  * Distributed under the terms of the MIT License.
6  *
7  * Authors:
8  *		Fernando Francisco de Oliveira
9  *		Michael Wilber
10  *		Michael Pfeiffer
11  *		yellowTAB GmbH
12  *		Bernd Korz
13  *		Axel Dörfler, axeld@pinc-software.de
14  *		Stephan Aßmus <superstippi@gmx.de>
15  */
16 
17 
18 #include "ShowImageWindow.h"
19 
20 #include <new>
21 #include <stdio.h>
22 #include <stdlib.h>
23 
24 #include <Alert.h>
25 #include <Application.h>
26 #include <Bitmap.h>
27 #include <BitmapStream.h>
28 #include <Catalog.h>
29 #include <Clipboard.h>
30 #include <Entry.h>
31 #include <File.h>
32 #include <FilePanel.h>
33 #include <Locale.h>
34 #include <Menu.h>
35 #include <MenuBar.h>
36 #include <MenuItem.h>
37 #include <MessageRunner.h>
38 #include <Path.h>
39 #include <PrintJob.h>
40 #include <RecentItems.h>
41 #include <Roster.h>
42 #include <Screen.h>
43 #include <ScrollView.h>
44 #include <String.h>
45 #include <SupportDefs.h>
46 #include <TranslationDefs.h>
47 #include <TranslationUtils.h>
48 #include <TranslatorRoster.h>
49 
50 #include "ImageCache.h"
51 #include "ProgressWindow.h"
52 #include "ShowImageApp.h"
53 #include "ShowImageConstants.h"
54 #include "ShowImageStatusView.h"
55 #include "ShowImageView.h"
56 #include "ToolBarIcons.h"
57 #include "ToolBarView.h"
58 
59 
60 // BMessage field names used in Save messages
61 const char* kTypeField = "be:type";
62 const char* kTranslatorField = "be:translator";
63 
64 const bigtime_t kDefaultSlideShowDelay = 3000000;
65 	// 3 seconds
66 
67 
68 // message constants
69 enum {
70 	MSG_CAPTURE_MOUSE			= 'mCPM',
71 	MSG_CHANGE_FOCUS			= 'mCFS',
72 	MSG_WINDOW_QUIT				= 'mWQT',
73 	MSG_OUTPUT_TYPE				= 'BTMN',
74 	MSG_SAVE_PANEL				= 'mFSP',
75 	MSG_CLEAR_SELECT			= 'mCSL',
76 	MSG_SELECT_ALL				= 'mSAL',
77 	MSG_SELECTION_MODE			= 'mSLM',
78 	MSG_PAGE_FIRST				= 'mPGF',
79 	MSG_PAGE_LAST				= 'mPGL',
80 	MSG_PAGE_NEXT				= 'mPGN',
81 	MSG_PAGE_PREV				= 'mPGP',
82 	MSG_GOTO_PAGE				= 'mGTP',
83 	MSG_ZOOM_IN					= 'mZIN',
84 	MSG_ZOOM_OUT				= 'mZOU',
85 	MSG_SCALE_BILINEAR			= 'mSBL',
86 	MSG_DESKTOP_BACKGROUND		= 'mDBG',
87 	MSG_ROTATE_90				= 'mR90',
88 	MSG_ROTATE_270				= 'mR27',
89 	MSG_FLIP_LEFT_TO_RIGHT		= 'mFLR',
90 	MSG_FLIP_TOP_TO_BOTTOM		= 'mFTB',
91 	MSG_SLIDE_SHOW_DELAY		= 'mSSD',
92 	MSG_SHOW_CAPTION			= 'mSCP',
93 	MSG_PAGE_SETUP				= 'mPSU',
94 	MSG_PREPARE_PRINT			= 'mPPT',
95 	MSG_SET_RATING				= 'mSRT',
96 	kMsgFitToWindow				= 'mFtW',
97 	kMsgOriginalSize			= 'mOSZ',
98 	kMsgStretchToWindow			= 'mStW',
99 	kMsgNextSlide				= 'mNxS',
100 	kMsgToggleToolBar			= 'mTTB',
101 	kMsgSlideToolBar			= 'mSTB',
102 	kMsgFinishSlidingToolBar	= 'mFST'
103 };
104 
105 
106 // This is temporary solution for building BString with printf like format.
107 // will be removed in the future.
108 static void
109 bs_printf(BString* string, const char* format, ...)
110 {
111 	va_list ap;
112 	char* buf;
113 
114 	va_start(ap, format);
115 	vasprintf(&buf, format, ap);
116 	string->SetTo(buf);
117 	free(buf);
118 	va_end(ap);
119 }
120 
121 
122 //	#pragma mark -- ShowImageWindow
123 
124 
125 #undef B_TRANSLATION_CONTEXT
126 #define B_TRANSLATION_CONTEXT "Menus"
127 
128 
129 ShowImageWindow::ShowImageWindow(BRect frame, const entry_ref& ref,
130 	const BMessenger& trackerMessenger)
131 	:
132 	BWindow(frame, "", B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
133 	fNavigator(ref, trackerMessenger),
134 	fSavePanel(NULL),
135 	fBar(NULL),
136 	fBrowseMenu(NULL),
137 	fGoToPageMenu(NULL),
138 	fSlideShowDelayMenu(NULL),
139 	fToolBarView(NULL),
140 	fImageView(NULL),
141 	fStatusView(NULL),
142 	fProgressWindow(new ProgressWindow()),
143 	fModified(false),
144 	fFullScreen(false),
145 	fShowCaption(true),
146 	fShowToolBar(true),
147 	fPrintSettings(NULL),
148 	fSlideShowRunner(NULL),
149 	fSlideShowDelay(kDefaultSlideShowDelay)
150 {
151 	_ApplySettings();
152 
153 	SetLayout(new BGroupLayout(B_VERTICAL, 0));
154 
155 	// create menu bar
156 	fBar = new BMenuBar("menu_bar");
157 	_AddMenus(fBar);
158 	float menuBarMinWidth = fBar->MinSize().width;
159 	AddChild(fBar);
160 
161 	// Add a content view so the tool bar can be moved outside of the
162 	// visible portion without colliding with the menu bar.
163 
164 	BView* contentView = new BView(BRect(), "content", B_FOLLOW_NONE, 0);
165 	contentView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
166 	contentView->SetExplicitMinSize(BSize(250, 100));
167 	AddChild(contentView);
168 
169 	// Create the tool bar
170 	BRect viewFrame = contentView->Bounds();
171 	viewFrame.right -= B_V_SCROLL_BAR_WIDTH;
172 	fToolBarView = new ToolBarView(viewFrame);
173 
174 	// Add the tool icons.
175 
176 //	fToolBarView->AddAction(MSG_FILE_OPEN, be_app,
177 //		tool_bar_icon(kIconDocumentOpen), B_TRANSLATE("Open"B_UTF8_ELLIPSIS));
178 	fToolBarView->AddAction(MSG_FILE_PREV, this,
179 		tool_bar_icon(kIconGoPrevious), B_TRANSLATE("Previous file"));
180 	fToolBarView->AddAction(MSG_FILE_NEXT, this, tool_bar_icon(kIconGoNext),
181 		B_TRANSLATE("Next file"));
182 	BMessage* fullScreenSlideShow = new BMessage(MSG_SLIDE_SHOW);
183 	fullScreenSlideShow->AddBool("full screen", true);
184 	fToolBarView->AddAction(fullScreenSlideShow, this,
185 		tool_bar_icon(kIconMediaMovieLibrary), B_TRANSLATE("Slide show"));
186 	fToolBarView->AddSeparator();
187 	fToolBarView->AddAction(MSG_SELECTION_MODE, this,
188 		tool_bar_icon(kIconDrawRectangularSelection),
189 		B_TRANSLATE("Selection mode"));
190 	fToolBarView->AddSeparator();
191 	fToolBarView->AddAction(kMsgOriginalSize, this,
192 		tool_bar_icon(kIconZoomOriginal), B_TRANSLATE("Original size"));
193 	fToolBarView->AddAction(kMsgFitToWindow, this,
194 		tool_bar_icon(kIconZoomFitBest), B_TRANSLATE("Fit to window"));
195 	fToolBarView->AddAction(MSG_ZOOM_IN, this, tool_bar_icon(kIconZoomIn),
196 		B_TRANSLATE("Zoom in"));
197 	fToolBarView->AddAction(MSG_ZOOM_OUT, this, tool_bar_icon(kIconZoomOut),
198 		B_TRANSLATE("Zoom out"));
199 	fToolBarView->AddGlue();
200 	fToolBarView->AddAction(MSG_FULL_SCREEN, this,
201 		tool_bar_icon(kIconViewWindowed), B_TRANSLATE("Leave full screen"));
202 	fToolBarView->SetActionVisible(MSG_FULL_SCREEN, false);
203 
204 	fToolBarView->ResizeTo(viewFrame.Width(), fToolBarView->MinSize().height);
205 
206 	contentView->AddChild(fToolBarView);
207 
208 	if (fShowToolBar)
209 		viewFrame.top = fToolBarView->Frame().bottom + 1;
210 	else
211 		fToolBarView->Hide();
212 
213 	viewFrame.bottom = contentView->Bounds().bottom;
214 	viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
215 
216 	// create the image view
217 	fImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL,
218 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED
219 			| B_FRAME_EVENTS);
220 	// wrap a scroll view around the view
221 	fScrollView = new BScrollView("image_scroller", fImageView,
222 		B_FOLLOW_ALL, 0, false, false, B_PLAIN_BORDER);
223 	contentView->AddChild(fScrollView);
224 
225 	const int32 kstatusWidth = 190;
226 	BRect rect;
227 	rect = contentView->Bounds();
228 	rect.top = viewFrame.bottom + 1;
229 	rect.left = viewFrame.left + kstatusWidth;
230 	rect.right = viewFrame.right + 1;
231 	rect.bottom += 1;
232 	BScrollBar* horizontalScrollBar = new BScrollBar(rect, "hscroll",
233 		fImageView, 0, 150, B_HORIZONTAL);
234 	contentView->AddChild(horizontalScrollBar);
235 
236 	rect.left = 0;
237 	rect.right = kstatusWidth - 1;
238 	rect.bottom -= 1;
239 	fStatusView = new ShowImageStatusView(rect, "status_view", B_FOLLOW_BOTTOM,
240 		B_WILL_DRAW);
241 	contentView->AddChild(fStatusView);
242 
243 	rect = contentView->Bounds();
244 	rect.top = viewFrame.top - 1;
245 	rect.left = viewFrame.right + 1;
246 	rect.bottom = viewFrame.bottom + 1;
247 	rect.right += 1;
248 	fVerticalScrollBar = new BScrollBar(rect, "vscroll", fImageView,
249 		0, 150, B_VERTICAL);
250 	contentView->AddChild(fVerticalScrollBar);
251 
252 	// Update minimum window size
253 	float toolBarMinWidth = fToolBarView->MinSize().width;
254 	SetSizeLimits(std::max(menuBarMinWidth, toolBarMinWidth), 100000, 100,
255 		100000);
256 
257 	// finish creating the window
258 	if (_LoadImage() != B_OK) {
259 		_LoadError(ref);
260 		Quit();
261 		return;
262 	}
263 
264 	// add View menu here so it can access ShowImageView methods
265 	BMenu* menu = new BMenu(B_TRANSLATE_CONTEXT("View", "Menus"));
266 	_BuildViewMenu(menu, false);
267 	fBar->AddItem(menu);
268 
269 	fBar->AddItem(_BuildRatingMenu());
270 
271 	SetPulseRate(100000);
272 		// every 1/10 second; ShowImageView needs it for marching ants
273 
274 	_MarkMenuItem(menu, MSG_SELECTION_MODE,
275 		fImageView->IsSelectionModeEnabled());
276 
277 	// Tell application object to query the clipboard
278 	// and tell this window if it contains interesting data or not
279 	be_app_messenger.SendMessage(B_CLIPBOARD_CHANGED);
280 
281 	// The window will be shown on screen automatically
282 	Run();
283 }
284 
285 
286 ShowImageWindow::~ShowImageWindow()
287 {
288 	fProgressWindow->Lock();
289 	fProgressWindow->Quit();
290 
291 	_StopSlideShow();
292 }
293 
294 
295 void
296 ShowImageWindow::BuildContextMenu(BMenu* menu)
297 {
298 	_BuildViewMenu(menu, true);
299 }
300 
301 
302 void
303 ShowImageWindow::_BuildViewMenu(BMenu* menu, bool popupMenu)
304 {
305 	_AddItemMenu(menu, B_TRANSLATE("Slide show"), MSG_SLIDE_SHOW, 0, 0, this);
306 	_MarkMenuItem(menu, MSG_SLIDE_SHOW, fSlideShowRunner != NULL);
307 	BMenu* delayMenu = new BMenu(B_TRANSLATE("Slide delay"));
308 	if (fSlideShowDelayMenu == NULL)
309 		fSlideShowDelayMenu = delayMenu;
310 
311 	delayMenu->SetRadioMode(true);
312 
313 	int32 kDelays[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 20};
314 	for (uint32 i = 0; i < sizeof(kDelays) / sizeof(kDelays[0]); i++) {
315 		BString text(B_TRANSLATE_COMMENT("%SECONDS seconds",
316 			"Don't translate %SECONDS"));
317 		char seconds[32];
318 		snprintf(seconds, sizeof(seconds), "%" B_PRIi32, kDelays[i]);
319 		text.ReplaceFirst("%SECONDS", seconds);
320 
321 		_AddDelayItem(delayMenu, text.String(), kDelays[i] * 1000000LL);
322 	}
323 	menu->AddItem(delayMenu);
324 
325 	menu->AddSeparatorItem();
326 
327 	_AddItemMenu(menu, B_TRANSLATE("Original size"),
328 		kMsgOriginalSize, '1', 0, this);
329 	_AddItemMenu(menu, B_TRANSLATE("Fit to window"),
330 		kMsgFitToWindow, '0', 0, this);
331 	_AddItemMenu(menu, B_TRANSLATE("Zoom in"), MSG_ZOOM_IN, '+', 0, this);
332 	_AddItemMenu(menu, B_TRANSLATE("Zoom out"), MSG_ZOOM_OUT, '-', 0, this);
333 
334 	menu->AddSeparatorItem();
335 
336 	if (!popupMenu || fFullScreen) {
337 		_AddItemMenu(menu, B_TRANSLATE("High-quality zooming"),
338 			MSG_SCALE_BILINEAR, 0, 0, this);
339 		_AddItemMenu(menu, B_TRANSLATE("Stretch to window"),
340 			kMsgStretchToWindow, 0, 0, this);
341 
342 		menu->AddSeparatorItem();
343 	}
344 
345 	_AddItemMenu(menu, B_TRANSLATE("Full screen"),
346 		MSG_FULL_SCREEN, B_ENTER, 0, this);
347 	_MarkMenuItem(menu, MSG_FULL_SCREEN, fFullScreen);
348 
349 	_AddItemMenu(menu, B_TRANSLATE("Show caption in full screen mode"),
350 		MSG_SHOW_CAPTION, 0, 0, this);
351 	_MarkMenuItem(menu, MSG_SHOW_CAPTION, fShowCaption);
352 
353 	_MarkMenuItem(menu, MSG_SCALE_BILINEAR, fImageView->ScaleBilinear());
354 	_MarkMenuItem(menu, kMsgStretchToWindow, fImageView->StretchesToBounds());
355 
356 	if (!popupMenu) {
357 		_AddItemMenu(menu, B_TRANSLATE("Show tool bar"), kMsgToggleToolBar,
358 			'T', 0, this);
359 		_MarkMenuItem(menu, kMsgToggleToolBar,
360 			!fToolBarView->IsHidden(fToolBarView));
361 	}
362 
363 	if (popupMenu) {
364 		menu->AddSeparatorItem();
365 		_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
366 			MSG_DESKTOP_BACKGROUND, 0, 0, this);
367 	}
368 }
369 
370 
371 BMenu*
372 ShowImageWindow::_BuildRatingMenu()
373 {
374 	fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
375 	for (int32 i = 1; i <= 10; i++) {
376 		BString label;
377 		label << i;
378 		BMessage* message = new BMessage(MSG_SET_RATING);
379 		message->AddInt32("rating", i);
380 		fRatingMenu->AddItem(new BMenuItem(label.String(), message));
381 	}
382 	// NOTE: We may want to encapsulate the Rating menu within a more
383 	// general "Attributes" menu.
384 	return fRatingMenu;
385 }
386 
387 
388 void
389 ShowImageWindow::_AddMenus(BMenuBar* bar)
390 {
391 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
392 
393 	// Add recent files to "Open File" entry as sub-menu.
394 	BMenuItem* item = new BMenuItem(BRecentFilesList::NewFileListMenu(
395 		B_TRANSLATE("Open"B_UTF8_ELLIPSIS), NULL, NULL, be_app, 10, true,
396 		NULL, kApplicationSignature), new BMessage(MSG_FILE_OPEN));
397 	item->SetShortcut('O', 0);
398 	item->SetTarget(be_app);
399 	menu->AddItem(item);
400 	menu->AddSeparatorItem();
401 
402 	BMenu* menuSaveAs = new BMenu(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
403 		B_ITEMS_IN_COLUMN);
404 	BTranslationUtils::AddTranslationItems(menuSaveAs, B_TRANSLATOR_BITMAP);
405 		// Fill Save As submenu with all types that can be converted
406 		// to from the Be bitmap image format
407 	menu->AddItem(menuSaveAs);
408 	_AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this);
409 	menu->AddSeparatorItem();
410 	_AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
411 		MSG_PAGE_SETUP, 0, 0, this);
412 	_AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
413 		MSG_PREPARE_PRINT, 'P', 0, this);
414 	menu->AddSeparatorItem();
415 	_AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app);
416 	bar->AddItem(menu);
417 
418 	menu = new BMenu(B_TRANSLATE("Edit"));
419 	_AddItemMenu(menu, B_TRANSLATE("Undo"), B_UNDO, 'Z', 0, this, false);
420 	menu->AddSeparatorItem();
421 	_AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false);
422 	menu->AddSeparatorItem();
423 	_AddItemMenu(menu, B_TRANSLATE("Selection mode"), MSG_SELECTION_MODE, 0, 0,
424 		this);
425 	_AddItemMenu(menu, B_TRANSLATE("Clear selection"),
426 		MSG_CLEAR_SELECT, 0, 0, this, false);
427 	_AddItemMenu(menu, B_TRANSLATE("Select all"),
428 		MSG_SELECT_ALL, 'A', 0, this);
429 	bar->AddItem(menu);
430 
431 	menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse"));
432 	_AddItemMenu(menu, B_TRANSLATE("First page"),
433 		MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this);
434 	_AddItemMenu(menu, B_TRANSLATE("Last page"),
435 		MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this);
436 	_AddItemMenu(menu, B_TRANSLATE("Previous page"),
437 		MSG_PAGE_PREV, B_LEFT_ARROW, 0, this);
438 	_AddItemMenu(menu, B_TRANSLATE("Next page"),
439 		MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this);
440 	fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page"));
441 	fGoToPageMenu->SetRadioMode(true);
442 	menu->AddItem(fGoToPageMenu);
443 	menu->AddSeparatorItem();
444 	_AddItemMenu(menu, B_TRANSLATE("Previous file"),
445 		MSG_FILE_PREV, B_UP_ARROW, 0, this);
446 	_AddItemMenu(menu, B_TRANSLATE("Next file"),
447 		MSG_FILE_NEXT, B_DOWN_ARROW, 0, this);
448 	bar->AddItem(menu);
449 
450 	menu = new BMenu(B_TRANSLATE("Image"));
451 	_AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"),
452 		MSG_ROTATE_90, 'R', 0, this);
453 	_AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"),
454 		MSG_ROTATE_270, 'R', B_SHIFT_KEY, this);
455 	menu->AddSeparatorItem();
456 	_AddItemMenu(menu, B_TRANSLATE("Flip left to right"),
457 		MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this);
458 	_AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"),
459 		MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this);
460 	menu->AddSeparatorItem();
461 	_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
462 		MSG_DESKTOP_BACKGROUND, 0, 0, this);
463 
464 	bar->AddItem(menu);
465 }
466 
467 
468 BMenuItem*
469 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what,
470 	char shortcut, uint32 modifier, const BHandler* target, bool enabled)
471 {
472 	BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut,
473 		modifier);
474 	menu->AddItem(item);
475 
476 	item->SetTarget(target);
477 	item->SetEnabled(enabled);
478 
479 	return item;
480 }
481 
482 
483 BMenuItem*
484 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, bigtime_t delay)
485 {
486 	BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY);
487 	message->AddInt64("delay", delay);
488 
489 	BMenuItem* item = new BMenuItem(label, message, 0);
490 	item->SetTarget(this);
491 
492 	if (delay == fSlideShowDelay)
493 		item->SetMarked(true);
494 
495 	menu->AddItem(item);
496 	return item;
497 }
498 
499 
500 void
501 ShowImageWindow::_ResizeWindowToImage()
502 {
503 	BBitmap* bitmap = fImageView->Bitmap();
504 	BScreen screen;
505 	if (bitmap == NULL || !screen.IsValid())
506 		return;
507 
508 	// TODO: use View::GetPreferredSize() instead?
509 	BRect r(bitmap->Bounds());
510 	float width = r.Width() + B_V_SCROLL_BAR_WIDTH;
511 	float height = r.Height() + 1 + fBar->Frame().Height()
512 		+ B_H_SCROLL_BAR_HEIGHT;
513 
514 	BRect frame = screen.Frame();
515 	const float windowBorder = 5;
516 	// dimensions so that window does not reach outside of screen
517 	float maxWidth = frame.Width() + 1 - windowBorder - Frame().left;
518 	float maxHeight = frame.Height() + 1 - windowBorder - Frame().top;
519 
520 	// We have to check size limits manually, otherwise
521 	// menu bar will be too short for small images.
522 
523 	float minW, maxW, minH, maxH;
524 	GetSizeLimits(&minW, &maxW, &minH, &maxH);
525 	if (maxWidth > maxW)
526 		maxWidth = maxW;
527 	if (maxHeight > maxH)
528 		maxHeight = maxH;
529 	if (width < minW)
530 		width = minW;
531 	if (height < minH)
532 		height = minH;
533 
534 	if (width > maxWidth)
535 		width = maxWidth;
536 	if (height > maxHeight)
537 		height = maxHeight;
538 
539 	ResizeTo(width, height);
540 }
541 
542 
543 bool
544 ShowImageWindow::_ToggleMenuItem(uint32 what)
545 {
546 	bool marked = false;
547 	BMenuItem* item = fBar->FindItem(what);
548 	if (item != NULL) {
549 		marked = !item->IsMarked();
550 		item->SetMarked(marked);
551 	}
552 	fToolBarView->SetActionPressed(what, marked);
553 	return marked;
554 }
555 
556 
557 void
558 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable)
559 {
560 	BMenuItem* item = menu->FindItem(what);
561 	if (item && item->IsEnabled() != enable)
562 		item->SetEnabled(enable);
563 	fToolBarView->SetActionEnabled(what, enable);
564 }
565 
566 
567 void
568 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked)
569 {
570 	BMenuItem* item = menu->FindItem(what);
571 	if (item && item->IsMarked() != marked)
572 		item->SetMarked(marked);
573 	fToolBarView->SetActionPressed(what, marked);
574 }
575 
576 
577 void
578 ShowImageWindow::_MarkSlideShowDelay(bigtime_t delay)
579 {
580 	const int32 count = fSlideShowDelayMenu->CountItems();
581 	for (int32 i = 0; i < count; i ++) {
582 		BMenuItem* item = fSlideShowDelayMenu->ItemAt(i);
583 		if (item != NULL) {
584 			bigtime_t itemDelay;
585 			if (item->Message()->FindInt64("delay", &itemDelay) == B_OK
586 				&& itemDelay == delay) {
587 				item->SetMarked(true);
588 				return;
589 			}
590 		}
591 	}
592 }
593 
594 
595 void
596 ShowImageWindow::Zoom(BPoint origin, float width, float height)
597 {
598 	_ToggleFullScreen();
599 }
600 
601 
602 void
603 ShowImageWindow::MessageReceived(BMessage* message)
604 {
605 	if (message->WasDropped()) {
606 		uint32 type;
607 		int32 count;
608 		status_t status = message->GetInfo("refs", &type, &count);
609 		if (status == B_OK && type == B_REF_TYPE) {
610 			message->what = B_REFS_RECEIVED;
611 			be_app->PostMessage(message);
612 		}
613 	}
614 
615 	switch (message->what) {
616 		case kMsgImageCacheImageLoaded:
617 		{
618 			fProgressWindow->Stop();
619 
620 			BitmapOwner* bitmapOwner = NULL;
621 			message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
622 
623 			bool first = fImageView->Bitmap() == NULL;
624 			entry_ref ref;
625 			message->FindRef("ref", &ref);
626 			if (!first && ref != fNavigator.CurrentRef()) {
627 				// ignore older images
628 				if (bitmapOwner != NULL)
629 					bitmapOwner->ReleaseReference();
630 				break;
631 			}
632 
633 			status_t status = fImageView->SetImage(message);
634 			if (status != B_OK) {
635 				if (bitmapOwner != NULL)
636 					bitmapOwner->ReleaseReference();
637 
638 				_LoadError(ref);
639 
640 				// quit if file could not be opened
641 				if (first)
642 					Quit();
643 				break;
644 			}
645 
646 			fImageType = message->FindString("type");
647 			fNavigator.SetTo(ref, message->FindInt32("page"),
648 				message->FindInt32("pageCount"));
649 
650 			fImageView->FitToBounds();
651 			if (first) {
652 				fImageView->MakeFocus(true);
653 					// to receive key messages
654 				Show();
655 			}
656 			_UpdateRatingMenu();
657 			break;
658 		}
659 
660 		case kMsgImageCacheProgressUpdate:
661 		{
662 			entry_ref ref;
663 			if (message->FindRef("ref", &ref) == B_OK
664 				&& ref == fNavigator.CurrentRef()) {
665 				message->what = kMsgProgressUpdate;
666 				fProgressWindow->PostMessage(message);
667 			}
668 			break;
669 		}
670 
671 		case MSG_MODIFIED:
672 			// If image has been modified due to a Cut or Paste
673 			fModified = true;
674 			break;
675 
676 		case MSG_OUTPUT_TYPE:
677 			// User clicked Save As then choose an output format
678 			if (!fSavePanel)
679 				// If user doesn't already have a save panel open
680 				_SaveAs(message);
681 			break;
682 
683 		case MSG_SAVE_PANEL:
684 			// User specified where to save the output image
685 			_SaveToFile(message);
686 			break;
687 
688 		case B_CANCEL:
689 			delete fSavePanel;
690 			fSavePanel = NULL;
691 			break;
692 
693 		case MSG_UPDATE_STATUS:
694 		{
695 			int32 pages = fNavigator.PageCount();
696 			int32 currentPage = fNavigator.CurrentPage();
697 
698 			bool enable = pages > 1 ? true : false;
699 			_EnableMenuItem(fBar, MSG_PAGE_FIRST, enable);
700 			_EnableMenuItem(fBar, MSG_PAGE_LAST, enable);
701 			_EnableMenuItem(fBar, MSG_PAGE_NEXT, enable);
702 			_EnableMenuItem(fBar, MSG_PAGE_PREV, enable);
703 			fGoToPageMenu->SetEnabled(enable);
704 
705 			_EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile());
706 			_EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile());
707 
708 			if (fGoToPageMenu->CountItems() != pages) {
709 				// Only rebuild the submenu if the number of
710 				// pages is different
711 
712 				while (fGoToPageMenu->CountItems() > 0) {
713 					// Remove all page numbers
714 					delete fGoToPageMenu->RemoveItem(0L);
715 				}
716 
717 				for (int32 i = 1; i <= pages; i++) {
718 					// Fill Go To page submenu with an entry for each page
719 					BMessage* goTo = new BMessage(MSG_GOTO_PAGE);
720 					goTo->AddInt32("page", i);
721 
722 					char shortcut = 0;
723 					if (i < 10)
724 						shortcut = '0' + i;
725 
726 					BString strCaption;
727 					strCaption << i;
728 
729 					BMenuItem* item = new BMenuItem(strCaption.String(), goTo,
730 						B_SHIFT_KEY, shortcut);
731 					if (currentPage == i)
732 						item->SetMarked(true);
733 					fGoToPageMenu->AddItem(item);
734 				}
735 			} else {
736 				// Make sure the correct page is marked
737 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
738 				if (currentItem != NULL && !currentItem->IsMarked())
739 					currentItem->SetMarked(true);
740 			}
741 
742 			_UpdateStatusText(message);
743 
744 			BPath path(fImageView->Image());
745 			SetTitle(path.Path());
746 			break;
747 		}
748 
749 		case MSG_UPDATE_STATUS_TEXT:
750 		{
751 			_UpdateStatusText(message);
752 			break;
753 		}
754 
755 		case MSG_SELECTION:
756 		{
757 			// The view sends this message when a selection is
758 			// made or the selection is cleared so that the window
759 			// can update the state of the appropriate menu items
760 			bool enable;
761 			if (message->FindBool("has_selection", &enable) == B_OK) {
762 				_EnableMenuItem(fBar, B_COPY, enable);
763 				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable);
764 			}
765 			break;
766 		}
767 
768 		case MSG_UNDO_STATE:
769 		{
770 			bool enable;
771 			if (message->FindBool("can_undo", &enable) == B_OK)
772 				_EnableMenuItem(fBar, B_UNDO, enable);
773 			break;
774 		}
775 
776 		case B_UNDO:
777 			fImageView->Undo();
778 			break;
779 
780 		case B_COPY:
781 			fImageView->CopySelectionToClipboard();
782 			break;
783 
784 		case MSG_SELECTION_MODE:
785 		{
786 			bool selectionMode = _ToggleMenuItem(MSG_SELECTION_MODE);
787 			fImageView->SetSelectionMode(selectionMode);
788 			if (!selectionMode)
789 				fImageView->ClearSelection();
790 			break;
791 		}
792 
793 		case MSG_CLEAR_SELECT:
794 			fImageView->ClearSelection();
795 			break;
796 
797 		case MSG_SELECT_ALL:
798 			fImageView->SelectAll();
799 			break;
800 
801 		case MSG_PAGE_FIRST:
802 			if (_ClosePrompt() && fNavigator.FirstPage())
803 				_LoadImage();
804 			break;
805 
806 		case MSG_PAGE_LAST:
807 			if (_ClosePrompt() && fNavigator.LastPage())
808 				_LoadImage();
809 			break;
810 
811 		case MSG_PAGE_NEXT:
812 			if (_ClosePrompt() && fNavigator.NextPage())
813 				_LoadImage();
814 			break;
815 
816 		case MSG_PAGE_PREV:
817 			if (_ClosePrompt() && fNavigator.PreviousPage())
818 				_LoadImage();
819 			break;
820 
821 		case MSG_GOTO_PAGE:
822 		{
823 			if (!_ClosePrompt())
824 				break;
825 
826 			int32 newPage;
827 			if (message->FindInt32("page", &newPage) != B_OK)
828 				break;
829 
830 			int32 currentPage = fNavigator.CurrentPage();
831 			int32 pages = fNavigator.PageCount();
832 
833 			// TODO: use radio mode instead!
834 			if (newPage > 0 && newPage <= pages) {
835 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
836 				BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1);
837 				if (currentItem != NULL && newItem != NULL) {
838 					currentItem->SetMarked(false);
839 					newItem->SetMarked(true);
840 					if (fNavigator.GoToPage(newPage))
841 						_LoadImage();
842 				}
843 			}
844 			break;
845 		}
846 
847 		case kMsgFitToWindow:
848 			fImageView->FitToBounds();
849 			break;
850 
851 		case kMsgStretchToWindow:
852 			fImageView->SetStretchToBounds(
853 				_ToggleMenuItem(kMsgStretchToWindow));
854 			break;
855 
856 		case MSG_FILE_PREV:
857 			if (_ClosePrompt() && fNavigator.PreviousFile())
858 				_LoadImage(false);
859 			break;
860 
861 		case MSG_FILE_NEXT:
862 		case kMsgNextSlide:
863 			if (_ClosePrompt() && fNavigator.NextFile())
864 				_LoadImage();
865 			break;
866 
867 		case kMsgDeleteCurrentFile:
868 		{
869 			if (fNavigator.MoveFileToTrash())
870 				_LoadImage();
871 			else
872 				PostMessage(B_QUIT_REQUESTED);
873 			break;
874 		}
875 
876 		case MSG_ROTATE_90:
877 			fImageView->Rotate(90);
878 			break;
879 
880 		case MSG_ROTATE_270:
881 			fImageView->Rotate(270);
882 			break;
883 
884 		case MSG_FLIP_LEFT_TO_RIGHT:
885 			fImageView->Flip(true);
886 			break;
887 
888 		case MSG_FLIP_TOP_TO_BOTTOM:
889 			fImageView->Flip(false);
890 			break;
891 
892 		case MSG_SLIDE_SHOW:
893 		{
894 			bool fullScreen = false;
895 			message->FindBool("full screen", &fullScreen);
896 
897 			BMenuItem* item = fBar->FindItem(message->what);
898 			if (item == NULL)
899 				break;
900 
901 			if (item->IsMarked()) {
902 				item->SetMarked(false);
903 				_StopSlideShow();
904 				fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, false);
905 			} else if (_ClosePrompt()) {
906 				item->SetMarked(true);
907 				if (!fFullScreen && fullScreen)
908 					_ToggleFullScreen();
909 				_StartSlideShow();
910 				fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, true);
911 			}
912 			break;
913 		}
914 
915 		case kMsgStopSlideShow:
916 		{
917 			BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW);
918 			if (item != NULL)
919 				item->SetMarked(false);
920 
921 			_StopSlideShow();
922 			fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, false);
923 			break;
924 		}
925 
926 		case MSG_SLIDE_SHOW_DELAY:
927 		{
928 			bigtime_t delay;
929 			if (message->FindInt64("delay", &delay) == B_OK) {
930 				_SetSlideShowDelay(delay);
931 				// in case message is sent from popup menu
932 				_MarkSlideShowDelay(delay);
933 			}
934 			break;
935 		}
936 
937 		case MSG_FULL_SCREEN:
938 			_ToggleFullScreen();
939 			break;
940 
941 		case MSG_EXIT_FULL_SCREEN:
942 			if (fFullScreen)
943 				_ToggleFullScreen();
944 			break;
945 
946 		case MSG_SHOW_CAPTION:
947 		{
948 			fShowCaption = _ToggleMenuItem(message->what);
949 			ShowImageSettings* settings = my_app->Settings();
950 
951 			if (settings->Lock()) {
952 				settings->SetBool("ShowCaption", fShowCaption);
953 				settings->Unlock();
954 			}
955 			if (fFullScreen)
956 				fImageView->SetShowCaption(fShowCaption);
957 		}	break;
958 
959 		case MSG_PAGE_SETUP:
960 			_PageSetup();
961 			break;
962 
963 		case MSG_PREPARE_PRINT:
964 			_PrepareForPrint();
965 			break;
966 
967 		case MSG_PRINT:
968 			_Print(message);
969 			break;
970 
971 		case MSG_ZOOM_IN:
972 			fImageView->ZoomIn();
973 			break;
974 
975 		case MSG_ZOOM_OUT:
976 			fImageView->ZoomOut();
977 			break;
978 
979 		case kMsgOriginalSize:
980 			fImageView->SetZoom(1.0);
981 			break;
982 
983 		case MSG_SCALE_BILINEAR:
984 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
985 			break;
986 
987 		case MSG_DESKTOP_BACKGROUND:
988 		{
989 			BMessage backgroundsMessage(B_REFS_RECEIVED);
990 			backgroundsMessage.AddRef("refs", fImageView->Image());
991 			// This is used in the Backgrounds code for scaled placement
992 			backgroundsMessage.AddInt32("placement", 'scpl');
993 			be_roster->Launch("application/x-vnd.haiku-backgrounds", &backgroundsMessage);
994 			break;
995 		}
996 
997 		case MSG_SET_RATING:
998 		{
999 			int32 rating;
1000 			if (message->FindInt32("rating", &rating) != B_OK)
1001 				break;
1002 			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
1003 			if (file.InitCheck() != B_OK)
1004 				break;
1005 			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
1006 				sizeof(rating));
1007 			_UpdateRatingMenu();
1008 			break;
1009 		}
1010 
1011 		case kMsgToggleToolBar:
1012 		{
1013 			fShowToolBar = _ToggleMenuItem(message->what);
1014 			_SetToolBarVisible(fShowToolBar, true);
1015 
1016 			ShowImageSettings* settings = my_app->Settings();
1017 
1018 			if (settings->Lock()) {
1019 				settings->SetBool("ShowToolBar", fShowToolBar);
1020 				settings->Unlock();
1021 			}
1022 			break;
1023 		}
1024 		case kShowToolBarIfEnabled:
1025 		{
1026 			bool show;
1027 			if (message->FindBool("show", &show) != B_OK)
1028 				break;
1029 			_SetToolBarVisible(fShowToolBar && show, true);
1030 			break;
1031 		}
1032 		case kMsgSlideToolBar:
1033 		{
1034 			float offset;
1035 			if (message->FindFloat("offset", &offset) == B_OK) {
1036 				fToolBarView->MoveBy(0, offset);
1037 				fScrollView->ResizeBy(0, -offset);
1038 				fScrollView->MoveBy(0, offset);
1039 				fVerticalScrollBar->ResizeBy(0, -offset);
1040 				fVerticalScrollBar->MoveBy(0, offset);
1041 				UpdateIfNeeded();
1042 				snooze(15000);
1043 			}
1044 			break;
1045 		}
1046 		case kMsgFinishSlidingToolBar:
1047 		{
1048 			float offset;
1049 			bool show;
1050 			if (message->FindFloat("offset", &offset) == B_OK
1051 				&& message->FindBool("show", &show) == B_OK) {
1052 				// Compensate rounding errors with the final placement
1053 				if (show) {
1054 					fToolBarView->MoveTo(fToolBarView->Frame().left, 0);
1055 				} else {
1056 					fToolBarView->MoveTo(fToolBarView->Frame().left, offset);
1057 					fToolBarView->Hide();
1058 				}
1059 				BRect frame = fToolBarView->Parent()->Bounds();
1060 				frame.top = fToolBarView->Frame().bottom + 1;
1061 				fScrollView->MoveTo(0, frame.top);
1062 				fScrollView->ResizeTo(fScrollView->Bounds().Width(),
1063 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1064 				fVerticalScrollBar->MoveTo(
1065 					frame.right - B_V_SCROLL_BAR_WIDTH + 1, frame.top);
1066 				fVerticalScrollBar->ResizeTo(
1067 					fVerticalScrollBar->Bounds().Width(),
1068 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1069 			}
1070 			break;
1071 		}
1072 
1073 		default:
1074 			BWindow::MessageReceived(message);
1075 			break;
1076 	}
1077 }
1078 
1079 
1080 void
1081 ShowImageWindow::_UpdateStatusText(const BMessage* message)
1082 {
1083 	BString status;
1084 	if (fImageView->Bitmap() != NULL) {
1085 		BRect bounds = fImageView->Bitmap()->Bounds();
1086 		status << bounds.IntegerWidth() + 1
1087 			<< "x" << bounds.IntegerHeight() + 1 << ", " << fImageType;
1088 	}
1089 
1090 	BString text;
1091 	if (message != NULL && message->FindString("status", &text) == B_OK
1092 		&& text.Length() > 0) {
1093 		status << ", " << text;
1094 	}
1095 
1096 	fStatusView->Update(fNavigator.CurrentRef(), status);
1097 }
1098 
1099 
1100 void
1101 ShowImageWindow::_LoadError(const entry_ref& ref)
1102 {
1103 	// TODO: give a better error message!
1104 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1105 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1106 			"file or an image translator for it does not exist.",
1107 			"LoadAlerts"),
1108 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1109 		B_WIDTH_AS_USUAL, B_INFO_ALERT);
1110 	alert->Go();
1111 }
1112 
1113 
1114 void
1115 ShowImageWindow::_SaveAs(BMessage* message)
1116 {
1117 	// Read the translator and output type the user chose
1118 	translator_id outTranslator;
1119 	uint32 outType;
1120 	if (message->FindInt32(kTranslatorField,
1121 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1122 		|| message->FindInt32(kTypeField,
1123 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1124 		return;
1125 
1126 	// Add the chosen translator and output type to the
1127 	// message that the save panel will send back
1128 	BMessage panelMsg(MSG_SAVE_PANEL);
1129 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1130 	panelMsg.AddInt32(kTypeField, outType);
1131 
1132 	// Create save panel and show it
1133 	BMessenger target(this);
1134 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1135 		&target, NULL, 0, false, &panelMsg);
1136 	if (!fSavePanel)
1137 		return;
1138 
1139 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1140 	fSavePanel->Show();
1141 }
1142 
1143 
1144 void
1145 ShowImageWindow::_SaveToFile(BMessage* message)
1146 {
1147 	// Read in where the file should be saved
1148 	entry_ref dirRef;
1149 	if (message->FindRef("directory", &dirRef) != B_OK)
1150 		return;
1151 
1152 	const char* filename;
1153 	if (message->FindString("name", &filename) != B_OK)
1154 		return;
1155 
1156 	// Read in the translator and type to be used
1157 	// to save the output image
1158 	translator_id outTranslator;
1159 	uint32 outType;
1160 	if (message->FindInt32(kTranslatorField,
1161 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1162 		|| message->FindInt32(kTypeField,
1163 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1164 		return;
1165 
1166 	// Find the translator_format information needed to
1167 	// write a MIME attribute for the image file
1168 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1169 	const translation_format* outFormat = NULL;
1170 	int32 outCount = 0;
1171 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1172 		|| outCount < 1)
1173 		return;
1174 
1175 	int32 i;
1176 	for (i = 0; i < outCount; i++) {
1177 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type == outType)
1178 			break;
1179 	}
1180 	if (i == outCount)
1181 		return;
1182 
1183 	// Write out the image file
1184 	BDirectory dir(&dirRef);
1185 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1186 }
1187 
1188 
1189 #undef B_TRANSLATION_CONTEXT
1190 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1191 
1192 
1193 bool
1194 ShowImageWindow::_ClosePrompt()
1195 {
1196 	if (!fModified)
1197 		return true;
1198 
1199 	int32 count = fNavigator.PageCount();
1200 	int32 page = fNavigator.CurrentPage();
1201 	BString prompt;
1202 
1203 	if (count > 1) {
1204 		bs_printf(&prompt,
1205 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1206 				"want to close the document?"),
1207 			fImageView->Image()->name, page);
1208 	} else {
1209 		bs_printf(&prompt,
1210 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1211 				"close the document?"),
1212 			fImageView->Image()->name);
1213 	}
1214 
1215 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1216 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1217 	if (alert->Go() == 0) {
1218 		// Cancel
1219 		return false;
1220 	}
1221 
1222 	// Close
1223 	fModified = false;
1224 	return true;
1225 }
1226 
1227 
1228 status_t
1229 ShowImageWindow::_LoadImage(bool forward)
1230 {
1231 	BMessenger us(this);
1232 	status_t status = ImageCache::Default().RetrieveImage(
1233 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1234 	if (status != B_OK)
1235 		return status;
1236 
1237 	fProgressWindow->Start(this);
1238 
1239 	// Preload previous/next images - two in the navigation direction, one
1240 	// in the opposite direction.
1241 
1242 	entry_ref nextRef = fNavigator.CurrentRef();
1243 	if (_PreloadImage(forward, nextRef))
1244 		_PreloadImage(forward, nextRef);
1245 
1246 	entry_ref previousRef = fNavigator.CurrentRef();
1247 	_PreloadImage(!forward, previousRef);
1248 
1249 	return B_OK;
1250 }
1251 
1252 
1253 bool
1254 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1255 {
1256 	entry_ref currentRef = ref;
1257 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1258 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1259 		return false;
1260 
1261 	return ImageCache::Default().RetrieveImage(ref) == B_OK;
1262 }
1263 
1264 
1265 void
1266 ShowImageWindow::_ToggleFullScreen()
1267 {
1268 	BRect frame;
1269 	fFullScreen = !fFullScreen;
1270 	if (fFullScreen) {
1271 		BScreen screen;
1272 		fWindowFrame = Frame();
1273 		frame = screen.Frame();
1274 		frame.top -= fBar->Bounds().Height()+1;
1275 		frame.right += B_V_SCROLL_BAR_WIDTH;
1276 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
1277 		frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView
1278 
1279 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1280 
1281 		Activate();
1282 			// make the window frontmost
1283 	} else {
1284 		frame = fWindowFrame;
1285 
1286 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1287 	}
1288 
1289 	fToolBarView->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
1290 	_SetToolBarVisible(!fFullScreen && fShowToolBar);
1291 
1292 	MoveTo(frame.left, frame.top);
1293 	ResizeTo(frame.Width(), frame.Height());
1294 
1295 	fImageView->SetHideIdlingCursor(fFullScreen);
1296 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1297 
1298 	Layout(false);
1299 		// We need to manually relayout here, as the views are layouted
1300 		// asynchronously, and FitToBounds() would still have the wrong size
1301 	fImageView->FitToBounds();
1302 }
1303 
1304 
1305 void
1306 ShowImageWindow::_ApplySettings()
1307 {
1308 	ShowImageSettings* settings = my_app->Settings();
1309 
1310 	if (settings->Lock()) {
1311 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1312 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1313 
1314 		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
1315 
1316 		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
1317 			"PO:Option", fPrintOptions.Option()));
1318 		fPrintOptions.SetZoomFactor(
1319 			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
1320 		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
1321 		fPrintOptions.SetWidth(
1322 			settings->GetFloat("PO:Width", fPrintOptions.Width()));
1323 		fPrintOptions.SetHeight(
1324 			settings->GetFloat("PO:Height", fPrintOptions.Height()));
1325 
1326 		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
1327 
1328 		settings->Unlock();
1329 	}
1330 }
1331 
1332 
1333 void
1334 ShowImageWindow::_SavePrintOptions()
1335 {
1336 	ShowImageSettings* settings = my_app->Settings();
1337 
1338 	if (settings->Lock()) {
1339 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1340 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1341 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1342 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1343 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1344 		settings->Unlock();
1345 	}
1346 }
1347 
1348 
1349 bool
1350 ShowImageWindow::_PageSetup()
1351 {
1352 	BPrintJob printJob(fImageView->Image()->name);
1353 	if (fPrintSettings != NULL)
1354 		printJob.SetSettings(new BMessage(*fPrintSettings));
1355 
1356 	status_t status = printJob.ConfigPage();
1357 	if (status == B_OK) {
1358 		delete fPrintSettings;
1359 		fPrintSettings = printJob.Settings();
1360 	}
1361 
1362 	return status == B_OK;
1363 }
1364 
1365 
1366 void
1367 ShowImageWindow::_PrepareForPrint()
1368 {
1369 	if (fPrintSettings == NULL) {
1370 		BPrintJob printJob(fImageView->Image()->name);
1371 		if (printJob.ConfigJob() == B_OK)
1372 			fPrintSettings = printJob.Settings();
1373 	}
1374 
1375 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1376 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1377 
1378 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1379 		&fPrintOptions, this);
1380 }
1381 
1382 
1383 void
1384 ShowImageWindow::_Print(BMessage* msg)
1385 {
1386 	status_t st;
1387 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1388 		return;
1389 
1390 	_SavePrintOptions();
1391 
1392 	BPrintJob printJob(fImageView->Image()->name);
1393 	if (fPrintSettings)
1394 		printJob.SetSettings(new BMessage(*fPrintSettings));
1395 
1396 	if (printJob.ConfigJob() == B_OK) {
1397 		delete fPrintSettings;
1398 		fPrintSettings = printJob.Settings();
1399 
1400 		// first/lastPage is unused for now
1401 		int32 firstPage = printJob.FirstPage();
1402 		int32 lastPage = printJob.LastPage();
1403 		BRect printableRect = printJob.PrintableRect();
1404 
1405 		if (firstPage < 1)
1406 			firstPage = 1;
1407 		if (lastPage < firstPage)
1408 			lastPage = firstPage;
1409 
1410 		BBitmap* bitmap = fImageView->Bitmap();
1411 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1412 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1413 
1414 		float width;
1415 		switch (fPrintOptions.Option()) {
1416 			case PrintOptions::kFitToPage: {
1417 				float w1 = printableRect.Width()+1;
1418 				float w2 = imageWidth * (printableRect.Height() + 1)
1419 					/ imageHeight;
1420 				if (w2 < w1)
1421 					width = w2;
1422 				else
1423 					width = w1;
1424 			}	break;
1425 			case PrintOptions::kZoomFactor:
1426 				width = imageWidth * fPrintOptions.ZoomFactor();
1427 				break;
1428 			case PrintOptions::kDPI:
1429 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1430 				break;
1431 			case PrintOptions::kWidth:
1432 			case PrintOptions::kHeight:
1433 				width = fPrintOptions.Width();
1434 				break;
1435 
1436 			default:
1437 				// keep compiler silent; should not reach here
1438 				width = imageWidth;
1439 		}
1440 
1441 		// TODO: eventually print large images on several pages
1442 		printJob.BeginJob();
1443 		fImageView->SetScale(width / imageWidth);
1444 		// coordinates are relative to printable rectangle
1445 		BRect bounds(bitmap->Bounds());
1446 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1447 		fImageView->SetScale(1.0);
1448 		printJob.SpoolPage();
1449 		printJob.CommitJob();
1450 	}
1451 }
1452 
1453 
1454 void
1455 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
1456 {
1457 	if (fSlideShowDelay == delay)
1458 		return;
1459 
1460 	fSlideShowDelay = delay;
1461 
1462 	ShowImageSettings* settings = my_app->Settings();
1463 	if (settings->Lock()) {
1464 		settings->SetTime("SlideShowDelay", fSlideShowDelay);
1465 		settings->Unlock();
1466 	}
1467 
1468 	if (fSlideShowRunner != NULL)
1469 		_StartSlideShow();
1470 }
1471 
1472 
1473 void
1474 ShowImageWindow::_StartSlideShow()
1475 {
1476 	_StopSlideShow();
1477 
1478 	BMessage nextSlide(kMsgNextSlide);
1479 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1480 }
1481 
1482 
1483 void
1484 ShowImageWindow::_StopSlideShow()
1485 {
1486 	if (fSlideShowRunner != NULL) {
1487 		delete fSlideShowRunner;
1488 		fSlideShowRunner = NULL;
1489 	}
1490 }
1491 
1492 
1493 void
1494 ShowImageWindow::_UpdateRatingMenu()
1495 {
1496 	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
1497 	if (file.InitCheck() != B_OK)
1498 		return;
1499 	int32 rating;
1500 	ssize_t size = sizeof(rating);
1501 	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
1502 		rating = 0;
1503 	// TODO: Finding the correct item could be more robust, like by looking
1504 	// at the message of each item.
1505 	for (int32 i = 1; i <= 10; i++) {
1506 		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
1507 		if (item == NULL)
1508 			break;
1509 		item->SetMarked(i == rating);
1510 	}
1511 }
1512 
1513 
1514 void
1515 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
1516 {
1517 	if (visible == !fToolBarView->IsHidden())
1518 		return;
1519 
1520 	float diff = fToolBarView->Bounds().Height() + 2;
1521 	if (!visible)
1522 		diff = -diff;
1523 	else
1524 		fToolBarView->Show();
1525 
1526 	if (animate) {
1527 		// Slide the controls into view. We do this with messages in order
1528 		// not to block the window thread.
1529 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1530 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
1531 		float originalY = fToolBarView->Frame().top;
1532 		for (int32 i = 0; i < steps; i++) {
1533 			BMessage message(kMsgSlideToolBar);
1534 			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
1535 			PostMessage(&message, this);
1536 		}
1537 		BMessage finalMessage(kMsgFinishSlidingToolBar);
1538 		finalMessage.AddFloat("offset", originalY + diff);
1539 		finalMessage.AddBool("show", visible);
1540 		PostMessage(&finalMessage, this);
1541 	} else {
1542 		fScrollView->ResizeBy(0, -diff);
1543 		fScrollView->MoveBy(0, diff);
1544 		fVerticalScrollBar->ResizeBy(0, -diff);
1545 		fVerticalScrollBar->MoveBy(0, diff);
1546 		fToolBarView->MoveBy(0, diff);
1547 		if (!visible)
1548 			fToolBarView->Hide();
1549 	}
1550 }
1551 
1552 
1553 bool
1554 ShowImageWindow::QuitRequested()
1555 {
1556 	if (fSavePanel) {
1557 		// Don't allow this window to be closed if a save panel is open
1558 		return false;
1559 	}
1560 
1561 	if (!_ClosePrompt())
1562 		return false;
1563 
1564 	ShowImageSettings* settings = my_app->Settings();
1565 	if (settings->Lock()) {
1566 		if (fFullScreen)
1567 			settings->SetRect("WindowFrame", fWindowFrame);
1568 		else
1569 			settings->SetRect("WindowFrame", Frame());
1570 		settings->Unlock();
1571 	}
1572 
1573 	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
1574 
1575 	return true;
1576 }
1577