xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 850f2d1e58cc443f77353c7fc0ce0c158c1fd328)
1 /*
2  * Copyright 2003-2014, 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 <Button.h>
29 #include <Catalog.h>
30 #include <Clipboard.h>
31 #include <Entry.h>
32 #include <File.h>
33 #include <FilePanel.h>
34 #include <Locale.h>
35 #include <Menu.h>
36 #include <MenuBar.h>
37 #include <MenuItem.h>
38 #include <MessageRunner.h>
39 #include <Path.h>
40 #include <PrintJob.h>
41 #include <RecentItems.h>
42 #include <Roster.h>
43 #include <Screen.h>
44 #include <ScrollView.h>
45 #include <String.h>
46 #include <SupportDefs.h>
47 #include <TranslationDefs.h>
48 #include <TranslationUtils.h>
49 #include <TranslatorRoster.h>
50 
51 #include "ImageCache.h"
52 #include "ProgressWindow.h"
53 #include "ShowImageApp.h"
54 #include "ShowImageConstants.h"
55 #include "ShowImageStatusView.h"
56 #include "ShowImageView.h"
57 #include "ToolBarIcons.h"
58 #include "ToolBarView.h"
59 
60 
61 // BMessage field names used in Save messages
62 const char* kTypeField = "be:type";
63 const char* kTranslatorField = "be:translator";
64 
65 const bigtime_t kDefaultSlideShowDelay = 3000000;
66 	// 3 seconds
67 
68 
69 // message constants
70 enum {
71 	MSG_CAPTURE_MOUSE			= 'mCPM',
72 	MSG_CHANGE_FOCUS			= 'mCFS',
73 	MSG_WINDOW_QUIT				= 'mWQT',
74 	MSG_OUTPUT_TYPE				= 'BTMN',
75 	MSG_SAVE_PANEL				= 'mFSP',
76 	MSG_CLEAR_SELECT			= 'mCSL',
77 	MSG_SELECT_ALL				= 'mSAL',
78 	MSG_SELECTION_MODE			= 'mSLM',
79 	MSG_PAGE_FIRST				= 'mPGF',
80 	MSG_PAGE_LAST				= 'mPGL',
81 	MSG_PAGE_NEXT				= 'mPGN',
82 	MSG_PAGE_PREV				= 'mPGP',
83 	MSG_GOTO_PAGE				= 'mGTP',
84 	MSG_ZOOM_IN					= 'mZIN',
85 	MSG_ZOOM_OUT				= 'mZOU',
86 	MSG_SCALE_BILINEAR			= 'mSBL',
87 	MSG_DESKTOP_BACKGROUND		= 'mDBG',
88 	MSG_ROTATE_90				= 'mR90',
89 	MSG_ROTATE_270				= 'mR27',
90 	MSG_FLIP_LEFT_TO_RIGHT		= 'mFLR',
91 	MSG_FLIP_TOP_TO_BOTTOM		= 'mFTB',
92 	MSG_SLIDE_SHOW_DELAY		= 'mSSD',
93 	MSG_SHOW_CAPTION			= 'mSCP',
94 	MSG_PAGE_SETUP				= 'mPSU',
95 	MSG_PREPARE_PRINT			= 'mPPT',
96 	MSG_SET_RATING				= 'mSRT',
97 	kMsgFitToWindow				= 'mFtW',
98 	kMsgOriginalSize			= 'mOSZ',
99 	kMsgStretchToWindow			= 'mStW',
100 	kMsgNextSlide				= 'mNxS',
101 	kMsgToggleToolBar			= 'mTTB',
102 	kMsgSlideToolBar			= 'mSTB',
103 	kMsgFinishSlidingToolBar	= 'mFST'
104 };
105 
106 
107 // This is temporary solution for building BString with printf like format.
108 // will be removed in the future.
109 static void
110 bs_printf(BString* string, const char* format, ...)
111 {
112 	va_list ap;
113 	char* buf;
114 
115 	va_start(ap, format);
116 	vasprintf(&buf, format, ap);
117 	string->SetTo(buf);
118 	free(buf);
119 	va_end(ap);
120 }
121 
122 
123 //	#pragma mark -- ShowImageWindow
124 
125 
126 #undef B_TRANSLATION_CONTEXT
127 #define B_TRANSLATION_CONTEXT "Menus"
128 
129 
130 ShowImageWindow::ShowImageWindow(BRect frame, const entry_ref& ref,
131 	const BMessenger& trackerMessenger)
132 	:
133 	BWindow(frame, "", B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
134 	fNavigator(ref, trackerMessenger),
135 	fSavePanel(NULL),
136 	fBar(NULL),
137 	fBrowseMenu(NULL),
138 	fGoToPageMenu(NULL),
139 	fSlideShowDelayMenu(NULL),
140 	fToolBarView(NULL),
141 	fImageView(NULL),
142 	fStatusView(NULL),
143 	fProgressWindow(new ProgressWindow()),
144 	fModified(false),
145 	fFullScreen(false),
146 	fShowCaption(true),
147 	fShowToolBar(true),
148 	fPrintSettings(NULL),
149 	fSlideShowRunner(NULL),
150 	fSlideShowDelay(kDefaultSlideShowDelay)
151 {
152 	_ApplySettings();
153 
154 	SetLayout(new BGroupLayout(B_VERTICAL, 0));
155 
156 	// create menu bar
157 	fBar = new BMenuBar("menu_bar");
158 	_AddMenus(fBar);
159 	float menuBarMinWidth = fBar->MinSize().width;
160 	AddChild(fBar);
161 
162 	// Add a content view so the tool bar can be moved outside of the
163 	// visible portion without colliding with the menu bar.
164 
165 	BView* contentView = new BView(BRect(), "content", B_FOLLOW_NONE, 0);
166 	contentView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
167 	contentView->SetExplicitMinSize(BSize(250, 100));
168 	AddChild(contentView);
169 
170 	// Create the tool bar
171 	BRect viewFrame = contentView->Bounds();
172 	viewFrame.right -= B_V_SCROLL_BAR_WIDTH;
173 	fToolBarView = new ToolBarView(viewFrame);
174 
175 	// Add the tool icons.
176 
177 //	fToolBarView->AddAction(MSG_FILE_OPEN, be_app,
178 //		tool_bar_icon(kIconDocumentOpen), B_TRANSLATE("Open" B_UTF8_ELLIPSIS));
179 	fToolBarView->AddAction(MSG_FILE_PREV, this,
180 		tool_bar_icon(kIconGoPrevious), B_TRANSLATE("Previous file"), false);
181 	fToolBarView->AddAction(MSG_FILE_NEXT, this, tool_bar_icon(kIconGoNext),
182 		B_TRANSLATE("Next file"), false);
183 	BMessage* fullScreenSlideShow = new BMessage(MSG_SLIDE_SHOW);
184 	fullScreenSlideShow->AddBool("full screen", true);
185 	fToolBarView->AddAction(fullScreenSlideShow, this,
186 		tool_bar_icon(kIconMediaMovieLibrary), B_TRANSLATE("Slide show"),
187 		false);
188 	fToolBarView->AddSeparator();
189 	fToolBarView->AddAction(MSG_SELECTION_MODE, this,
190 		tool_bar_icon(kIconDrawRectangularSelection),
191 		B_TRANSLATE("Selection mode"), false);
192 	fToolBarView->AddSeparator();
193 	fToolBarView->AddAction(kMsgOriginalSize, this,
194 		tool_bar_icon(kIconZoomOriginal), B_TRANSLATE("Original size"), true);
195 	fToolBarView->AddAction(kMsgFitToWindow, this,
196 		tool_bar_icon(kIconZoomFitBest), B_TRANSLATE("Fit to window"), false);
197 	fToolBarView->AddAction(MSG_ZOOM_IN, this, tool_bar_icon(kIconZoomIn),
198 		B_TRANSLATE("Zoom in"), false);
199 	fToolBarView->AddAction(MSG_ZOOM_OUT, this, tool_bar_icon(kIconZoomOut),
200 		B_TRANSLATE("Zoom out"), false);
201 	fToolBarView->AddGlue();
202 	fToolBarView->AddAction(MSG_FULL_SCREEN, this,
203 		tool_bar_icon(kIconViewWindowed), B_TRANSLATE("Leave full screen"),
204 		false);
205 	fToolBarView->SetActionVisible(MSG_FULL_SCREEN, false);
206 
207 	fToolBarView->ResizeTo(viewFrame.Width(), fToolBarView->MinSize().height);
208 
209 	contentView->AddChild(fToolBarView);
210 
211 	if (fShowToolBar)
212 		viewFrame.top = fToolBarView->Frame().bottom + 1;
213 	else
214 		fToolBarView->Hide();
215 
216 	fToolBarVisible = fShowToolBar;
217 
218 	viewFrame.bottom = contentView->Bounds().bottom;
219 	viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
220 
221 	// create the image view
222 	fImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL,
223 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED
224 			| B_FRAME_EVENTS);
225 	// wrap a scroll view around the view
226 	fScrollView = new BScrollView("image_scroller", fImageView,
227 		B_FOLLOW_ALL, 0, false, false, B_PLAIN_BORDER);
228 	contentView->AddChild(fScrollView);
229 
230 	const int32 kstatusWidth = 190;
231 	BRect rect;
232 	rect = contentView->Bounds();
233 	rect.top = viewFrame.bottom + 1;
234 	rect.left = viewFrame.left + kstatusWidth;
235 	rect.right = viewFrame.right + 1;
236 	rect.bottom += 1;
237 	BScrollBar* horizontalScrollBar = new BScrollBar(rect, "hscroll",
238 		fImageView, 0, 150, B_HORIZONTAL);
239 	contentView->AddChild(horizontalScrollBar);
240 
241 	rect.left = 0;
242 	rect.right = kstatusWidth - 1;
243 	rect.bottom -= 1;
244 	fStatusView = new ShowImageStatusView(rect, "status_view", B_FOLLOW_BOTTOM,
245 		B_WILL_DRAW);
246 	contentView->AddChild(fStatusView);
247 
248 	rect = contentView->Bounds();
249 	rect.top = viewFrame.top - 1;
250 	rect.left = viewFrame.right + 1;
251 	rect.bottom = viewFrame.bottom + 1;
252 	rect.right += 1;
253 	fVerticalScrollBar = new BScrollBar(rect, "vscroll", fImageView,
254 		0, 150, B_VERTICAL);
255 	contentView->AddChild(fVerticalScrollBar);
256 
257 	// Update minimum window size
258 	float toolBarMinWidth = fToolBarView->MinSize().width;
259 	SetSizeLimits(std::max(menuBarMinWidth, toolBarMinWidth), 100000, 100,
260 		100000);
261 
262 	// finish creating the window
263 	if (_LoadImage() != B_OK) {
264 		_LoadError(ref);
265 		Quit();
266 		return;
267 	}
268 
269 	// add View menu here so it can access ShowImageView methods
270 	BMenu* menu = new BMenu(B_TRANSLATE_CONTEXT("View", "Menus"));
271 	_BuildViewMenu(menu, false);
272 	fBar->AddItem(menu);
273 
274 	fBar->AddItem(_BuildRatingMenu());
275 
276 	SetPulseRate(100000);
277 		// every 1/10 second; ShowImageView needs it for marching ants
278 
279 	_MarkMenuItem(menu, MSG_SELECTION_MODE,
280 		fImageView->IsSelectionModeEnabled());
281 
282 	// Tell application object to query the clipboard
283 	// and tell this window if it contains interesting data or not
284 	be_app_messenger.SendMessage(B_CLIPBOARD_CHANGED);
285 
286 	// The window will be shown on screen automatically
287 	Run();
288 }
289 
290 
291 ShowImageWindow::~ShowImageWindow()
292 {
293 	fProgressWindow->Lock();
294 	fProgressWindow->Quit();
295 
296 	_StopSlideShow();
297 }
298 
299 
300 void
301 ShowImageWindow::BuildContextMenu(BMenu* menu)
302 {
303 	_BuildViewMenu(menu, true);
304 }
305 
306 
307 void
308 ShowImageWindow::_BuildViewMenu(BMenu* menu, bool popupMenu)
309 {
310 	_AddItemMenu(menu, B_TRANSLATE("Slide show"), MSG_SLIDE_SHOW, 0, 0, this);
311 	_MarkMenuItem(menu, MSG_SLIDE_SHOW, fSlideShowRunner != NULL);
312 	BMenu* delayMenu = new BMenu(B_TRANSLATE("Slide delay"));
313 	if (fSlideShowDelayMenu == NULL)
314 		fSlideShowDelayMenu = delayMenu;
315 
316 	delayMenu->SetRadioMode(true);
317 
318 	int32 kDelays[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 20};
319 	for (uint32 i = 0; i < sizeof(kDelays) / sizeof(kDelays[0]); i++) {
320 		BString text(B_TRANSLATE_COMMENT("%SECONDS seconds",
321 			"Don't translate %SECONDS"));
322 		char seconds[32];
323 		snprintf(seconds, sizeof(seconds), "%" B_PRIi32, kDelays[i]);
324 		text.ReplaceFirst("%SECONDS", seconds);
325 
326 		_AddDelayItem(delayMenu, text.String(), kDelays[i] * 1000000LL);
327 	}
328 	menu->AddItem(delayMenu);
329 
330 	menu->AddSeparatorItem();
331 
332 	_AddItemMenu(menu, B_TRANSLATE("Original size"),
333 		kMsgOriginalSize, '1', 0, this);
334 	_AddItemMenu(menu, B_TRANSLATE("Fit to window"),
335 		kMsgFitToWindow, '0', 0, this);
336 	_AddItemMenu(menu, B_TRANSLATE("Zoom in"), MSG_ZOOM_IN, '+', 0, this);
337 	_AddItemMenu(menu, B_TRANSLATE("Zoom out"), MSG_ZOOM_OUT, '-', 0, this);
338 
339 	menu->AddSeparatorItem();
340 
341 	if (!popupMenu || fFullScreen) {
342 		_AddItemMenu(menu, B_TRANSLATE("High-quality zooming"),
343 			MSG_SCALE_BILINEAR, 0, 0, this);
344 		_AddItemMenu(menu, B_TRANSLATE("Stretch to window"),
345 			kMsgStretchToWindow, 0, 0, this);
346 
347 		menu->AddSeparatorItem();
348 	}
349 
350 	_AddItemMenu(menu, B_TRANSLATE("Full screen"),
351 		MSG_FULL_SCREEN, B_ENTER, 0, this);
352 	_MarkMenuItem(menu, MSG_FULL_SCREEN, fFullScreen);
353 
354 	_AddItemMenu(menu, B_TRANSLATE("Show caption in full screen mode"),
355 		MSG_SHOW_CAPTION, 0, 0, this);
356 	_MarkMenuItem(menu, MSG_SHOW_CAPTION, fShowCaption);
357 
358 	_MarkMenuItem(menu, MSG_SCALE_BILINEAR, fImageView->ScaleBilinear());
359 	_MarkMenuItem(menu, kMsgStretchToWindow, fImageView->StretchesToBounds());
360 
361 	if (!popupMenu) {
362 		_AddItemMenu(menu, B_TRANSLATE("Show tool bar"), kMsgToggleToolBar,
363 			'B', 0, this);
364 		_MarkMenuItem(menu, kMsgToggleToolBar,
365 			!fToolBarView->IsHidden(fToolBarView));
366 	}
367 
368 	if (popupMenu) {
369 		menu->AddSeparatorItem();
370 		_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
371 			MSG_DESKTOP_BACKGROUND, 0, 0, this);
372 	}
373 }
374 
375 
376 BMenu*
377 ShowImageWindow::_BuildRatingMenu()
378 {
379 	fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
380 	for (int32 i = 1; i <= 10; i++) {
381 		BString label;
382 		label << i;
383 		BMessage* message = new BMessage(MSG_SET_RATING);
384 		message->AddInt32("rating", i);
385 		fRatingMenu->AddItem(new BMenuItem(label.String(), message));
386 	}
387 	// NOTE: We may want to encapsulate the Rating menu within a more
388 	// general "Attributes" menu.
389 	return fRatingMenu;
390 }
391 
392 
393 void
394 ShowImageWindow::_AddMenus(BMenuBar* bar)
395 {
396 	BMenu* menu = new BMenu(B_TRANSLATE("File"));
397 
398 	// Add recent files to "Open File" entry as sub-menu.
399 	BMenuItem* item = new BMenuItem(BRecentFilesList::NewFileListMenu(
400 		B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, be_app, 10, true,
401 		NULL, kApplicationSignature), new BMessage(MSG_FILE_OPEN));
402 	item->SetShortcut('O', 0);
403 	item->SetTarget(be_app);
404 	menu->AddItem(item);
405 	menu->AddSeparatorItem();
406 
407 	BMenu* menuSaveAs = new BMenu(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
408 		B_ITEMS_IN_COLUMN);
409 	BTranslationUtils::AddTranslationItems(menuSaveAs, B_TRANSLATOR_BITMAP);
410 		// Fill Save As submenu with all types that can be converted
411 		// to from the Be bitmap image format
412 	menu->AddItem(menuSaveAs);
413 	_AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this);
414 	_AddItemMenu(menu, B_TRANSLATE("Move to Trash"), kMsgDeleteCurrentFile, 'T', 0, this);
415 	menu->AddSeparatorItem();
416 	_AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
417 		MSG_PAGE_SETUP, 0, 0, this);
418 	_AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
419 		MSG_PREPARE_PRINT, 'P', 0, this);
420 	menu->AddSeparatorItem();
421 	_AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app);
422 	bar->AddItem(menu);
423 
424 	menu = new BMenu(B_TRANSLATE("Edit"));
425 	_AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false);
426 	menu->AddSeparatorItem();
427 	_AddItemMenu(menu, B_TRANSLATE("Selection mode"), MSG_SELECTION_MODE, 0, 0,
428 		this);
429 	_AddItemMenu(menu, B_TRANSLATE("Clear selection"),
430 		MSG_CLEAR_SELECT, 0, 0, this, false);
431 	_AddItemMenu(menu, B_TRANSLATE("Select all"),
432 		MSG_SELECT_ALL, 'A', 0, this);
433 	bar->AddItem(menu);
434 
435 	menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse"));
436 	_AddItemMenu(menu, B_TRANSLATE("First page"),
437 		MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this);
438 	_AddItemMenu(menu, B_TRANSLATE("Last page"),
439 		MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this);
440 	_AddItemMenu(menu, B_TRANSLATE("Previous page"),
441 		MSG_PAGE_PREV, B_LEFT_ARROW, 0, this);
442 	_AddItemMenu(menu, B_TRANSLATE("Next page"),
443 		MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this);
444 	fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page"));
445 	fGoToPageMenu->SetRadioMode(true);
446 	menu->AddItem(fGoToPageMenu);
447 	menu->AddSeparatorItem();
448 	_AddItemMenu(menu, B_TRANSLATE("Previous file"),
449 		MSG_FILE_PREV, B_UP_ARROW, 0, this);
450 	_AddItemMenu(menu, B_TRANSLATE("Next file"),
451 		MSG_FILE_NEXT, B_DOWN_ARROW, 0, this);
452 	bar->AddItem(menu);
453 
454 	menu = new BMenu(B_TRANSLATE("Image"));
455 	_AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"),
456 		MSG_ROTATE_90, 'R', 0, this);
457 	_AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"),
458 		MSG_ROTATE_270, 'R', B_SHIFT_KEY, this);
459 	menu->AddSeparatorItem();
460 	_AddItemMenu(menu, B_TRANSLATE("Flip left to right"),
461 		MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this);
462 	_AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"),
463 		MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this);
464 	menu->AddSeparatorItem();
465 	_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
466 		MSG_DESKTOP_BACKGROUND, 0, 0, this);
467 
468 	bar->AddItem(menu);
469 }
470 
471 
472 BMenuItem*
473 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what,
474 	char shortcut, uint32 modifier, const BHandler* target, bool enabled)
475 {
476 	BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut,
477 		modifier);
478 	menu->AddItem(item);
479 
480 	item->SetTarget(target);
481 	item->SetEnabled(enabled);
482 
483 	return item;
484 }
485 
486 
487 BMenuItem*
488 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, bigtime_t delay)
489 {
490 	BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY);
491 	message->AddInt64("delay", delay);
492 
493 	BMenuItem* item = new BMenuItem(label, message, 0);
494 	item->SetTarget(this);
495 
496 	if (delay == fSlideShowDelay)
497 		item->SetMarked(true);
498 
499 	menu->AddItem(item);
500 	return item;
501 }
502 
503 
504 void
505 ShowImageWindow::_ResizeWindowToImage()
506 {
507 	BBitmap* bitmap = fImageView->Bitmap();
508 	BScreen screen;
509 	if (bitmap == NULL || !screen.IsValid())
510 		return;
511 
512 	// TODO: use View::GetPreferredSize() instead?
513 	BRect r(bitmap->Bounds());
514 	float width = r.Width() + B_V_SCROLL_BAR_WIDTH;
515 	float height = r.Height() + 1 + fBar->Frame().Height()
516 		+ B_H_SCROLL_BAR_HEIGHT;
517 
518 	BRect frame = screen.Frame();
519 	const float windowBorder = 5;
520 	// dimensions so that window does not reach outside of screen
521 	float maxWidth = frame.Width() + 1 - windowBorder - Frame().left;
522 	float maxHeight = frame.Height() + 1 - windowBorder - Frame().top;
523 
524 	// We have to check size limits manually, otherwise
525 	// menu bar will be too short for small images.
526 
527 	float minW, maxW, minH, maxH;
528 	GetSizeLimits(&minW, &maxW, &minH, &maxH);
529 	if (maxWidth > maxW)
530 		maxWidth = maxW;
531 	if (maxHeight > maxH)
532 		maxHeight = maxH;
533 	if (width < minW)
534 		width = minW;
535 	if (height < minH)
536 		height = minH;
537 
538 	if (width > maxWidth)
539 		width = maxWidth;
540 	if (height > maxHeight)
541 		height = maxHeight;
542 
543 	ResizeTo(width, height);
544 }
545 
546 
547 bool
548 ShowImageWindow::_ToggleMenuItem(uint32 what)
549 {
550 	bool marked = false;
551 	BMenuItem* item = fBar->FindItem(what);
552 	if (item != NULL) {
553 		marked = !item->IsMarked();
554 		item->SetMarked(marked);
555 	}
556 	fToolBarView->SetActionPressed(what, marked);
557 	return marked;
558 }
559 
560 
561 void
562 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable)
563 {
564 	BMenuItem* item = menu->FindItem(what);
565 	if (item && item->IsEnabled() != enable)
566 		item->SetEnabled(enable);
567 	fToolBarView->SetActionEnabled(what, enable);
568 }
569 
570 
571 void
572 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked)
573 {
574 	BMenuItem* item = menu->FindItem(what);
575 	if (item && item->IsMarked() != marked)
576 		item->SetMarked(marked);
577 	fToolBarView->SetActionPressed(what, marked);
578 }
579 
580 
581 void
582 ShowImageWindow::_MarkSlideShowDelay(bigtime_t delay)
583 {
584 	const int32 count = fSlideShowDelayMenu->CountItems();
585 	for (int32 i = 0; i < count; i ++) {
586 		BMenuItem* item = fSlideShowDelayMenu->ItemAt(i);
587 		if (item != NULL) {
588 			bigtime_t itemDelay;
589 			if (item->Message()->FindInt64("delay", &itemDelay) == B_OK
590 				&& itemDelay == delay) {
591 				item->SetMarked(true);
592 				return;
593 			}
594 		}
595 	}
596 }
597 
598 
599 void
600 ShowImageWindow::Zoom(BPoint origin, float width, float height)
601 {
602 	_ToggleFullScreen();
603 }
604 
605 
606 void
607 ShowImageWindow::MessageReceived(BMessage* message)
608 {
609 	if (message->WasDropped()) {
610 		uint32 type;
611 		int32 count;
612 		status_t status = message->GetInfo("refs", &type, &count);
613 		if (status == B_OK && type == B_REF_TYPE) {
614 			message->what = B_REFS_RECEIVED;
615 			be_app->PostMessage(message);
616 		}
617 	}
618 
619 	switch (message->what) {
620 		case kMsgImageCacheImageLoaded:
621 		{
622 			fProgressWindow->Stop();
623 
624 			BitmapOwner* bitmapOwner = NULL;
625 			message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
626 
627 			bool first = fImageView->Bitmap() == NULL;
628 			entry_ref ref;
629 			message->FindRef("ref", &ref);
630 			if (!first && ref != fNavigator.CurrentRef()) {
631 				// ignore older images
632 				if (bitmapOwner != NULL)
633 					bitmapOwner->ReleaseReference();
634 				break;
635 			}
636 
637 			status_t status = fImageView->SetImage(message);
638 			if (status != B_OK) {
639 				if (bitmapOwner != NULL)
640 					bitmapOwner->ReleaseReference();
641 
642 				_LoadError(ref);
643 
644 				// quit if file could not be opened
645 				if (first)
646 					Quit();
647 				break;
648 			}
649 
650 			fImageType = message->FindString("type");
651 			fNavigator.SetTo(ref, message->FindInt32("page"),
652 				message->FindInt32("pageCount"));
653 
654 			fImageView->FitToBounds();
655 			if (first) {
656 				fImageView->MakeFocus(true);
657 					// to receive key messages
658 				Show();
659 			}
660 			_UpdateRatingMenu();
661 			break;
662 		}
663 
664 		case kMsgImageCacheProgressUpdate:
665 		{
666 			entry_ref ref;
667 			if (message->FindRef("ref", &ref) == B_OK
668 				&& ref == fNavigator.CurrentRef()) {
669 				message->what = kMsgProgressUpdate;
670 				fProgressWindow->PostMessage(message);
671 			}
672 			break;
673 		}
674 
675 		case MSG_MODIFIED:
676 			// If image has been modified due to a Cut or Paste
677 			fModified = true;
678 			break;
679 
680 		case MSG_OUTPUT_TYPE:
681 			// User clicked Save As then choose an output format
682 			if (!fSavePanel)
683 				// If user doesn't already have a save panel open
684 				_SaveAs(message);
685 			break;
686 
687 		case MSG_SAVE_PANEL:
688 			// User specified where to save the output image
689 			_SaveToFile(message);
690 			break;
691 
692 		case B_CANCEL:
693 			delete fSavePanel;
694 			fSavePanel = NULL;
695 			break;
696 
697 		case MSG_UPDATE_STATUS:
698 		{
699 			int32 pages = fNavigator.PageCount();
700 			int32 currentPage = fNavigator.CurrentPage();
701 
702 			bool enable = pages > 1 ? true : false;
703 			_EnableMenuItem(fBar, MSG_PAGE_FIRST, enable);
704 			_EnableMenuItem(fBar, MSG_PAGE_LAST, enable);
705 			_EnableMenuItem(fBar, MSG_PAGE_NEXT, enable);
706 			_EnableMenuItem(fBar, MSG_PAGE_PREV, enable);
707 			fGoToPageMenu->SetEnabled(enable);
708 
709 			_EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile());
710 			_EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile());
711 
712 			if (fGoToPageMenu->CountItems() != pages) {
713 				// Only rebuild the submenu if the number of
714 				// pages is different
715 
716 				while (fGoToPageMenu->CountItems() > 0) {
717 					// Remove all page numbers
718 					delete fGoToPageMenu->RemoveItem((int32)0);
719 				}
720 
721 				for (int32 i = 1; i <= pages; i++) {
722 					// Fill Go To page submenu with an entry for each page
723 					BMessage* goTo = new BMessage(MSG_GOTO_PAGE);
724 					goTo->AddInt32("page", i);
725 
726 					char shortcut = 0;
727 					if (i < 10)
728 						shortcut = '0' + i;
729 
730 					BString strCaption;
731 					strCaption << i;
732 
733 					BMenuItem* item = new BMenuItem(strCaption.String(), goTo,
734 						shortcut, B_SHIFT_KEY);
735 					if (currentPage == i)
736 						item->SetMarked(true);
737 					fGoToPageMenu->AddItem(item);
738 				}
739 			} else {
740 				// Make sure the correct page is marked
741 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
742 				if (currentItem != NULL && !currentItem->IsMarked())
743 					currentItem->SetMarked(true);
744 			}
745 
746 			_UpdateStatusText(message);
747 
748 			BPath path(fImageView->Image());
749 			SetTitle(path.Path());
750 			break;
751 		}
752 
753 		case MSG_UPDATE_STATUS_TEXT:
754 		{
755 			_UpdateStatusText(message);
756 			break;
757 		}
758 
759 		case MSG_SELECTION:
760 		{
761 			// The view sends this message when a selection is
762 			// made or the selection is cleared so that the window
763 			// can update the state of the appropriate menu items
764 			bool enable;
765 			if (message->FindBool("has_selection", &enable) == B_OK) {
766 				_EnableMenuItem(fBar, B_COPY, enable);
767 				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable);
768 			}
769 			break;
770 		}
771 
772 		case B_COPY:
773 			fImageView->CopySelectionToClipboard();
774 			break;
775 
776 		case MSG_SELECTION_MODE:
777 		{
778 			bool selectionMode = _ToggleMenuItem(MSG_SELECTION_MODE);
779 			fImageView->SetSelectionMode(selectionMode);
780 			if (!selectionMode)
781 				fImageView->ClearSelection();
782 			break;
783 		}
784 
785 		case MSG_CLEAR_SELECT:
786 			fImageView->ClearSelection();
787 			break;
788 
789 		case MSG_SELECT_ALL:
790 			fImageView->SelectAll();
791 			break;
792 
793 		case MSG_PAGE_FIRST:
794 			if (_ClosePrompt() && fNavigator.FirstPage())
795 				_LoadImage();
796 			break;
797 
798 		case MSG_PAGE_LAST:
799 			if (_ClosePrompt() && fNavigator.LastPage())
800 				_LoadImage();
801 			break;
802 
803 		case MSG_PAGE_NEXT:
804 			if (_ClosePrompt() && fNavigator.NextPage())
805 				_LoadImage();
806 			break;
807 
808 		case MSG_PAGE_PREV:
809 			if (_ClosePrompt() && fNavigator.PreviousPage())
810 				_LoadImage();
811 			break;
812 
813 		case MSG_GOTO_PAGE:
814 		{
815 			if (!_ClosePrompt())
816 				break;
817 
818 			int32 newPage;
819 			if (message->FindInt32("page", &newPage) != B_OK)
820 				break;
821 
822 			int32 currentPage = fNavigator.CurrentPage();
823 			int32 pages = fNavigator.PageCount();
824 
825 			// TODO: use radio mode instead!
826 			if (newPage > 0 && newPage <= pages) {
827 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
828 				BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1);
829 				if (currentItem != NULL && newItem != NULL) {
830 					currentItem->SetMarked(false);
831 					newItem->SetMarked(true);
832 					if (fNavigator.GoToPage(newPage))
833 						_LoadImage();
834 				}
835 			}
836 			break;
837 		}
838 
839 		case kMsgFitToWindow:
840 			fImageView->FitToBounds();
841 			break;
842 
843 		case kMsgStretchToWindow:
844 			fImageView->SetStretchToBounds(
845 				_ToggleMenuItem(kMsgStretchToWindow));
846 			break;
847 
848 		case MSG_FILE_PREV:
849 			if (_ClosePrompt() && fNavigator.PreviousFile())
850 				_LoadImage(false);
851 			break;
852 
853 		case MSG_FILE_NEXT:
854 		case kMsgNextSlide:
855 			if (_ClosePrompt() && fNavigator.NextFile())
856 				_LoadImage();
857 			break;
858 
859 		case kMsgDeleteCurrentFile:
860 		{
861 			if (fNavigator.MoveFileToTrash())
862 				_LoadImage();
863 			else
864 				PostMessage(B_QUIT_REQUESTED);
865 			break;
866 		}
867 
868 		case MSG_ROTATE_90:
869 			fImageView->Rotate(90);
870 			break;
871 
872 		case MSG_ROTATE_270:
873 			fImageView->Rotate(270);
874 			break;
875 
876 		case MSG_FLIP_LEFT_TO_RIGHT:
877 			fImageView->Flip(true);
878 			break;
879 
880 		case MSG_FLIP_TOP_TO_BOTTOM:
881 			fImageView->Flip(false);
882 			break;
883 
884 		case MSG_SLIDE_SHOW:
885 		{
886 			bool fullScreen = false;
887 			message->FindBool("full screen", &fullScreen);
888 
889 			BMenuItem* item = fBar->FindItem(message->what);
890 			if (item == NULL)
891 				break;
892 
893 			if (item->IsMarked()) {
894 				item->SetMarked(false);
895 				_StopSlideShow();
896 				fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, false);
897 			} else if (_ClosePrompt()) {
898 				item->SetMarked(true);
899 				if (!fFullScreen && fullScreen)
900 					_ToggleFullScreen();
901 				_StartSlideShow();
902 				fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, true);
903 			}
904 			break;
905 		}
906 
907 		case kMsgStopSlideShow:
908 		{
909 			BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW);
910 			if (item != NULL)
911 				item->SetMarked(false);
912 
913 			_StopSlideShow();
914 			fToolBarView->SetActionPressed(MSG_SLIDE_SHOW, false);
915 			break;
916 		}
917 
918 		case MSG_SLIDE_SHOW_DELAY:
919 		{
920 			bigtime_t delay;
921 			if (message->FindInt64("delay", &delay) == B_OK) {
922 				_SetSlideShowDelay(delay);
923 				// in case message is sent from popup menu
924 				_MarkSlideShowDelay(delay);
925 			}
926 			break;
927 		}
928 
929 		case MSG_FULL_SCREEN:
930 			_ToggleFullScreen();
931 			break;
932 
933 		case MSG_EXIT_FULL_SCREEN:
934 			if (fFullScreen)
935 				_ToggleFullScreen();
936 			break;
937 
938 		case MSG_SHOW_CAPTION:
939 		{
940 			fShowCaption = _ToggleMenuItem(message->what);
941 			ShowImageSettings* settings = my_app->Settings();
942 
943 			if (settings->Lock()) {
944 				settings->SetBool("ShowCaption", fShowCaption);
945 				settings->Unlock();
946 			}
947 			if (fFullScreen)
948 				fImageView->SetShowCaption(fShowCaption);
949 		}	break;
950 
951 		case MSG_PAGE_SETUP:
952 			_PageSetup();
953 			break;
954 
955 		case MSG_PREPARE_PRINT:
956 			_PrepareForPrint();
957 			break;
958 
959 		case MSG_PRINT:
960 			_Print(message);
961 			break;
962 
963 		case MSG_ZOOM_IN:
964 			fImageView->ZoomIn();
965 			break;
966 
967 		case MSG_ZOOM_OUT:
968 			fImageView->ZoomOut();
969 			break;
970 
971 		case kMsgOriginalSize:
972 			if (message->FindInt32("behavior") == BButton::B_TOGGLE_BEHAVIOR) {
973 				bool force = (message->FindInt32("be:value") == B_CONTROL_ON);
974 				fImageView->ForceOriginalSize(force);
975 				if (!force)
976 					break;
977 			}
978 			fImageView->SetZoom(1.0);
979 			break;
980 
981 		case MSG_SCALE_BILINEAR:
982 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
983 			break;
984 
985 		case MSG_DESKTOP_BACKGROUND:
986 		{
987 			BMessage backgroundsMessage(B_REFS_RECEIVED);
988 			backgroundsMessage.AddRef("refs", fImageView->Image());
989 			// This is used in the Backgrounds code for scaled placement
990 			backgroundsMessage.AddInt32("placement", 'scpl');
991 			be_roster->Launch("application/x-vnd.haiku-backgrounds",
992 				&backgroundsMessage);
993 			break;
994 		}
995 
996 		case MSG_SET_RATING:
997 		{
998 			int32 rating;
999 			if (message->FindInt32("rating", &rating) != B_OK)
1000 				break;
1001 			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
1002 			if (file.InitCheck() != B_OK)
1003 				break;
1004 			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
1005 				sizeof(rating));
1006 			_UpdateRatingMenu();
1007 			break;
1008 		}
1009 
1010 		case kMsgToggleToolBar:
1011 		{
1012 			fShowToolBar = _ToggleMenuItem(message->what);
1013 			_SetToolBarVisible(fShowToolBar, true);
1014 
1015 			ShowImageSettings* settings = my_app->Settings();
1016 
1017 			if (settings->Lock()) {
1018 				settings->SetBool("ShowToolBar", fShowToolBar);
1019 				settings->Unlock();
1020 			}
1021 			break;
1022 		}
1023 		case kShowToolBarIfEnabled:
1024 		{
1025 			bool show;
1026 			if (message->FindBool("show", &show) != B_OK)
1027 				break;
1028 			_SetToolBarVisible(fShowToolBar && show, true);
1029 			break;
1030 		}
1031 		case kMsgSlideToolBar:
1032 		{
1033 			float offset;
1034 			if (message->FindFloat("offset", &offset) == B_OK) {
1035 				fToolBarView->MoveBy(0, offset);
1036 				fScrollView->ResizeBy(0, -offset);
1037 				fScrollView->MoveBy(0, offset);
1038 				fVerticalScrollBar->ResizeBy(0, -offset);
1039 				fVerticalScrollBar->MoveBy(0, offset);
1040 				UpdateIfNeeded();
1041 				snooze(15000);
1042 			}
1043 			break;
1044 		}
1045 		case kMsgFinishSlidingToolBar:
1046 		{
1047 			float offset;
1048 			bool show;
1049 			if (message->FindFloat("offset", &offset) == B_OK
1050 				&& message->FindBool("show", &show) == B_OK) {
1051 				// Compensate rounding errors with the final placement
1052 				fToolBarView->MoveTo(fToolBarView->Frame().left, offset);
1053 				if (!show)
1054 					fToolBarView->Hide();
1055 				BRect frame = fToolBarView->Parent()->Bounds();
1056 				frame.top = fToolBarView->Frame().bottom + 1;
1057 				fScrollView->MoveTo(fScrollView->Frame().left, frame.top);
1058 				fScrollView->ResizeTo(fScrollView->Bounds().Width(),
1059 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1060 				fVerticalScrollBar->MoveTo(
1061 					frame.right - B_V_SCROLL_BAR_WIDTH + 1, frame.top);
1062 				fVerticalScrollBar->ResizeTo(
1063 					fVerticalScrollBar->Bounds().Width(),
1064 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1065 			}
1066 			break;
1067 		}
1068 
1069 		default:
1070 			BWindow::MessageReceived(message);
1071 			break;
1072 	}
1073 }
1074 
1075 
1076 void
1077 ShowImageWindow::_UpdateStatusText(const BMessage* message)
1078 {
1079 	BString status;
1080 	if (fImageView->Bitmap() != NULL) {
1081 		BRect bounds = fImageView->Bitmap()->Bounds();
1082 		status << bounds.IntegerWidth() + 1
1083 			<< "x" << bounds.IntegerHeight() + 1 << ", " << fImageType;
1084 	}
1085 
1086 	BString text;
1087 	if (message != NULL && message->FindString("status", &text) == B_OK
1088 		&& text.Length() > 0) {
1089 		status << ", " << text;
1090 	}
1091 
1092 	fStatusView->Update(fNavigator.CurrentRef(), status);
1093 }
1094 
1095 
1096 void
1097 ShowImageWindow::_LoadError(const entry_ref& ref)
1098 {
1099 	// TODO: give a better error message!
1100 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1101 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1102 			"file or an image translator for it does not exist.",
1103 			"LoadAlerts"),
1104 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1105 		B_WIDTH_AS_USUAL, B_STOP_ALERT);
1106 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1107 	alert->Go();
1108 }
1109 
1110 
1111 void
1112 ShowImageWindow::_SaveAs(BMessage* message)
1113 {
1114 	// Read the translator and output type the user chose
1115 	translator_id outTranslator;
1116 	uint32 outType;
1117 	if (message->FindInt32(kTranslatorField,
1118 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1119 		|| message->FindInt32(kTypeField,
1120 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1121 		return;
1122 
1123 	// Add the chosen translator and output type to the
1124 	// message that the save panel will send back
1125 	BMessage panelMsg(MSG_SAVE_PANEL);
1126 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1127 	panelMsg.AddInt32(kTypeField, outType);
1128 
1129 	// Create save panel and show it
1130 	BMessenger target(this);
1131 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1132 		&target, NULL, 0, false, &panelMsg);
1133 	if (!fSavePanel)
1134 		return;
1135 
1136 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1137 	fSavePanel->Show();
1138 }
1139 
1140 
1141 void
1142 ShowImageWindow::_SaveToFile(BMessage* message)
1143 {
1144 	// Read in where the file should be saved
1145 	entry_ref dirRef;
1146 	if (message->FindRef("directory", &dirRef) != B_OK)
1147 		return;
1148 
1149 	const char* filename;
1150 	if (message->FindString("name", &filename) != B_OK)
1151 		return;
1152 
1153 	// Read in the translator and type to be used
1154 	// to save the output image
1155 	translator_id outTranslator;
1156 	uint32 outType;
1157 	if (message->FindInt32(kTranslatorField,
1158 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1159 		|| message->FindInt32(kTypeField,
1160 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1161 		return;
1162 
1163 	// Find the translator_format information needed to
1164 	// write a MIME attribute for the image file
1165 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1166 	const translation_format* outFormat = NULL;
1167 	int32 outCount = 0;
1168 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1169 		|| outCount < 1)
1170 		return;
1171 
1172 	int32 i;
1173 	for (i = 0; i < outCount; i++) {
1174 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
1175 				== outType)
1176 			break;
1177 	}
1178 	if (i == outCount)
1179 		return;
1180 
1181 	// Write out the image file
1182 	BDirectory dir(&dirRef);
1183 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1184 }
1185 
1186 
1187 #undef B_TRANSLATION_CONTEXT
1188 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1189 
1190 
1191 bool
1192 ShowImageWindow::_ClosePrompt()
1193 {
1194 	if (!fModified)
1195 		return true;
1196 
1197 	int32 count = fNavigator.PageCount();
1198 	int32 page = fNavigator.CurrentPage();
1199 	BString prompt;
1200 
1201 	if (count > 1) {
1202 		bs_printf(&prompt,
1203 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1204 				"want to close the document?"),
1205 			fImageView->Image()->name, page);
1206 	} else {
1207 		bs_printf(&prompt,
1208 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1209 				"close the document?"),
1210 			fImageView->Image()->name);
1211 	}
1212 
1213 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1214 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1215 	alert->SetShortcut(0, B_ESCAPE);
1216 
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 == fToolBarVisible)
1518 		return;
1519 
1520 	fToolBarVisible = visible;
1521 	float diff = fToolBarView->Bounds().Height() + 2;
1522 	if (!visible)
1523 		diff = -diff;
1524 	else
1525 		fToolBarView->Show();
1526 
1527 	if (animate) {
1528 		// Slide the controls into view. We do this with messages in order
1529 		// not to block the window thread.
1530 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1531 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
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", visible ? 0 : 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