xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision e688bf23d48bfd1216a0cacbdbda5e35a1bcd779)
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",
994 				&backgroundsMessage);
995 			break;
996 		}
997 
998 		case MSG_SET_RATING:
999 		{
1000 			int32 rating;
1001 			if (message->FindInt32("rating", &rating) != B_OK)
1002 				break;
1003 			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
1004 			if (file.InitCheck() != B_OK)
1005 				break;
1006 			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
1007 				sizeof(rating));
1008 			_UpdateRatingMenu();
1009 			break;
1010 		}
1011 
1012 		case kMsgToggleToolBar:
1013 		{
1014 			fShowToolBar = _ToggleMenuItem(message->what);
1015 			_SetToolBarVisible(fShowToolBar, true);
1016 
1017 			ShowImageSettings* settings = my_app->Settings();
1018 
1019 			if (settings->Lock()) {
1020 				settings->SetBool("ShowToolBar", fShowToolBar);
1021 				settings->Unlock();
1022 			}
1023 			break;
1024 		}
1025 		case kShowToolBarIfEnabled:
1026 		{
1027 			bool show;
1028 			if (message->FindBool("show", &show) != B_OK)
1029 				break;
1030 			_SetToolBarVisible(fShowToolBar && show, true);
1031 			break;
1032 		}
1033 		case kMsgSlideToolBar:
1034 		{
1035 			float offset;
1036 			if (message->FindFloat("offset", &offset) == B_OK) {
1037 				fToolBarView->MoveBy(0, offset);
1038 				fScrollView->ResizeBy(0, -offset);
1039 				fScrollView->MoveBy(0, offset);
1040 				fVerticalScrollBar->ResizeBy(0, -offset);
1041 				fVerticalScrollBar->MoveBy(0, offset);
1042 				UpdateIfNeeded();
1043 				snooze(15000);
1044 			}
1045 			break;
1046 		}
1047 		case kMsgFinishSlidingToolBar:
1048 		{
1049 			float offset;
1050 			bool show;
1051 			if (message->FindFloat("offset", &offset) == B_OK
1052 				&& message->FindBool("show", &show) == B_OK) {
1053 				// Compensate rounding errors with the final placement
1054 				if (show)
1055 					fToolBarView->MoveTo(fToolBarView->Frame().left, 0);
1056 				else {
1057 					fToolBarView->MoveTo(fToolBarView->Frame().left, offset);
1058 					fToolBarView->Hide();
1059 				}
1060 				BRect frame = fToolBarView->Parent()->Bounds();
1061 				frame.top = fToolBarView->Frame().bottom + 1;
1062 				fScrollView->MoveTo(fScrollView->Frame().left, frame.top);
1063 				fScrollView->ResizeTo(fScrollView->Bounds().Width(),
1064 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1065 				fVerticalScrollBar->MoveTo(
1066 					frame.right - B_V_SCROLL_BAR_WIDTH + 1, frame.top);
1067 				fVerticalScrollBar->ResizeTo(
1068 					fVerticalScrollBar->Bounds().Width(),
1069 					frame.Height() - B_H_SCROLL_BAR_HEIGHT + 1);
1070 			}
1071 			break;
1072 		}
1073 
1074 		default:
1075 			BWindow::MessageReceived(message);
1076 			break;
1077 	}
1078 }
1079 
1080 
1081 void
1082 ShowImageWindow::_UpdateStatusText(const BMessage* message)
1083 {
1084 	BString status;
1085 	if (fImageView->Bitmap() != NULL) {
1086 		BRect bounds = fImageView->Bitmap()->Bounds();
1087 		status << bounds.IntegerWidth() + 1
1088 			<< "x" << bounds.IntegerHeight() + 1 << ", " << fImageType;
1089 	}
1090 
1091 	BString text;
1092 	if (message != NULL && message->FindString("status", &text) == B_OK
1093 		&& text.Length() > 0) {
1094 		status << ", " << text;
1095 	}
1096 
1097 	fStatusView->Update(fNavigator.CurrentRef(), status);
1098 }
1099 
1100 
1101 void
1102 ShowImageWindow::_LoadError(const entry_ref& ref)
1103 {
1104 	// TODO: give a better error message!
1105 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1106 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1107 			"file or an image translator for it does not exist.",
1108 			"LoadAlerts"),
1109 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1110 		B_WIDTH_AS_USUAL, B_INFO_ALERT);
1111 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1112 	alert->Go();
1113 }
1114 
1115 
1116 void
1117 ShowImageWindow::_SaveAs(BMessage* message)
1118 {
1119 	// Read the translator and output type the user chose
1120 	translator_id outTranslator;
1121 	uint32 outType;
1122 	if (message->FindInt32(kTranslatorField,
1123 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1124 		|| message->FindInt32(kTypeField,
1125 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1126 		return;
1127 
1128 	// Add the chosen translator and output type to the
1129 	// message that the save panel will send back
1130 	BMessage panelMsg(MSG_SAVE_PANEL);
1131 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1132 	panelMsg.AddInt32(kTypeField, outType);
1133 
1134 	// Create save panel and show it
1135 	BMessenger target(this);
1136 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1137 		&target, NULL, 0, false, &panelMsg);
1138 	if (!fSavePanel)
1139 		return;
1140 
1141 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1142 	fSavePanel->Show();
1143 }
1144 
1145 
1146 void
1147 ShowImageWindow::_SaveToFile(BMessage* message)
1148 {
1149 	// Read in where the file should be saved
1150 	entry_ref dirRef;
1151 	if (message->FindRef("directory", &dirRef) != B_OK)
1152 		return;
1153 
1154 	const char* filename;
1155 	if (message->FindString("name", &filename) != B_OK)
1156 		return;
1157 
1158 	// Read in the translator and type to be used
1159 	// to save the output image
1160 	translator_id outTranslator;
1161 	uint32 outType;
1162 	if (message->FindInt32(kTranslatorField,
1163 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1164 		|| message->FindInt32(kTypeField,
1165 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1166 		return;
1167 
1168 	// Find the translator_format information needed to
1169 	// write a MIME attribute for the image file
1170 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1171 	const translation_format* outFormat = NULL;
1172 	int32 outCount = 0;
1173 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1174 		|| outCount < 1)
1175 		return;
1176 
1177 	int32 i;
1178 	for (i = 0; i < outCount; i++) {
1179 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
1180 				== outType)
1181 			break;
1182 	}
1183 	if (i == outCount)
1184 		return;
1185 
1186 	// Write out the image file
1187 	BDirectory dir(&dirRef);
1188 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1189 }
1190 
1191 
1192 #undef B_TRANSLATION_CONTEXT
1193 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1194 
1195 
1196 bool
1197 ShowImageWindow::_ClosePrompt()
1198 {
1199 	if (!fModified)
1200 		return true;
1201 
1202 	int32 count = fNavigator.PageCount();
1203 	int32 page = fNavigator.CurrentPage();
1204 	BString prompt;
1205 
1206 	if (count > 1) {
1207 		bs_printf(&prompt,
1208 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1209 				"want to close the document?"),
1210 			fImageView->Image()->name, page);
1211 	} else {
1212 		bs_printf(&prompt,
1213 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1214 				"close the document?"),
1215 			fImageView->Image()->name);
1216 	}
1217 
1218 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1219 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1220 	alert->SetShortcut(0, B_ESCAPE);
1221 
1222 	if (alert->Go() == 0) {
1223 		// Cancel
1224 		return false;
1225 	}
1226 
1227 	// Close
1228 	fModified = false;
1229 	return true;
1230 }
1231 
1232 
1233 status_t
1234 ShowImageWindow::_LoadImage(bool forward)
1235 {
1236 	BMessenger us(this);
1237 	status_t status = ImageCache::Default().RetrieveImage(
1238 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1239 	if (status != B_OK)
1240 		return status;
1241 
1242 	fProgressWindow->Start(this);
1243 
1244 	// Preload previous/next images - two in the navigation direction, one
1245 	// in the opposite direction.
1246 
1247 	entry_ref nextRef = fNavigator.CurrentRef();
1248 	if (_PreloadImage(forward, nextRef))
1249 		_PreloadImage(forward, nextRef);
1250 
1251 	entry_ref previousRef = fNavigator.CurrentRef();
1252 	_PreloadImage(!forward, previousRef);
1253 
1254 	return B_OK;
1255 }
1256 
1257 
1258 bool
1259 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1260 {
1261 	entry_ref currentRef = ref;
1262 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1263 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1264 		return false;
1265 
1266 	return ImageCache::Default().RetrieveImage(ref) == B_OK;
1267 }
1268 
1269 
1270 void
1271 ShowImageWindow::_ToggleFullScreen()
1272 {
1273 	BRect frame;
1274 	fFullScreen = !fFullScreen;
1275 	if (fFullScreen) {
1276 		BScreen screen;
1277 		fWindowFrame = Frame();
1278 		frame = screen.Frame();
1279 		frame.top -= fBar->Bounds().Height() + 1;
1280 		frame.right += B_V_SCROLL_BAR_WIDTH;
1281 		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
1282 		frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView
1283 
1284 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1285 
1286 		Activate();
1287 			// make the window frontmost
1288 	} else {
1289 		frame = fWindowFrame;
1290 
1291 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1292 	}
1293 
1294 	fToolBarView->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
1295 	_SetToolBarVisible(!fFullScreen && fShowToolBar);
1296 
1297 	MoveTo(frame.left, frame.top);
1298 	ResizeTo(frame.Width(), frame.Height());
1299 
1300 	fImageView->SetHideIdlingCursor(fFullScreen);
1301 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1302 
1303 	Layout(false);
1304 		// We need to manually relayout here, as the views are layouted
1305 		// asynchronously, and FitToBounds() would still have the wrong size
1306 	fImageView->FitToBounds();
1307 }
1308 
1309 
1310 void
1311 ShowImageWindow::_ApplySettings()
1312 {
1313 	ShowImageSettings* settings = my_app->Settings();
1314 
1315 	if (settings->Lock()) {
1316 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1317 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1318 
1319 		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
1320 
1321 		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
1322 			"PO:Option", fPrintOptions.Option()));
1323 		fPrintOptions.SetZoomFactor(
1324 			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
1325 		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
1326 		fPrintOptions.SetWidth(
1327 			settings->GetFloat("PO:Width", fPrintOptions.Width()));
1328 		fPrintOptions.SetHeight(
1329 			settings->GetFloat("PO:Height", fPrintOptions.Height()));
1330 
1331 		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
1332 
1333 		settings->Unlock();
1334 	}
1335 }
1336 
1337 
1338 void
1339 ShowImageWindow::_SavePrintOptions()
1340 {
1341 	ShowImageSettings* settings = my_app->Settings();
1342 
1343 	if (settings->Lock()) {
1344 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1345 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1346 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1347 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1348 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1349 		settings->Unlock();
1350 	}
1351 }
1352 
1353 
1354 bool
1355 ShowImageWindow::_PageSetup()
1356 {
1357 	BPrintJob printJob(fImageView->Image()->name);
1358 	if (fPrintSettings != NULL)
1359 		printJob.SetSettings(new BMessage(*fPrintSettings));
1360 
1361 	status_t status = printJob.ConfigPage();
1362 	if (status == B_OK) {
1363 		delete fPrintSettings;
1364 		fPrintSettings = printJob.Settings();
1365 	}
1366 
1367 	return status == B_OK;
1368 }
1369 
1370 
1371 void
1372 ShowImageWindow::_PrepareForPrint()
1373 {
1374 	if (fPrintSettings == NULL) {
1375 		BPrintJob printJob(fImageView->Image()->name);
1376 		if (printJob.ConfigJob() == B_OK)
1377 			fPrintSettings = printJob.Settings();
1378 	}
1379 
1380 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1381 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1382 
1383 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1384 		&fPrintOptions, this);
1385 }
1386 
1387 
1388 void
1389 ShowImageWindow::_Print(BMessage* msg)
1390 {
1391 	status_t st;
1392 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1393 		return;
1394 
1395 	_SavePrintOptions();
1396 
1397 	BPrintJob printJob(fImageView->Image()->name);
1398 	if (fPrintSettings)
1399 		printJob.SetSettings(new BMessage(*fPrintSettings));
1400 
1401 	if (printJob.ConfigJob() == B_OK) {
1402 		delete fPrintSettings;
1403 		fPrintSettings = printJob.Settings();
1404 
1405 		// first/lastPage is unused for now
1406 		int32 firstPage = printJob.FirstPage();
1407 		int32 lastPage = printJob.LastPage();
1408 		BRect printableRect = printJob.PrintableRect();
1409 
1410 		if (firstPage < 1)
1411 			firstPage = 1;
1412 		if (lastPage < firstPage)
1413 			lastPage = firstPage;
1414 
1415 		BBitmap* bitmap = fImageView->Bitmap();
1416 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1417 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1418 
1419 		float width;
1420 		switch (fPrintOptions.Option()) {
1421 			case PrintOptions::kFitToPage: {
1422 				float w1 = printableRect.Width() + 1;
1423 				float w2 = imageWidth * (printableRect.Height() + 1)
1424 					/ imageHeight;
1425 				if (w2 < w1)
1426 					width = w2;
1427 				else
1428 					width = w1;
1429 			}	break;
1430 			case PrintOptions::kZoomFactor:
1431 				width = imageWidth * fPrintOptions.ZoomFactor();
1432 				break;
1433 			case PrintOptions::kDPI:
1434 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1435 				break;
1436 			case PrintOptions::kWidth:
1437 			case PrintOptions::kHeight:
1438 				width = fPrintOptions.Width();
1439 				break;
1440 
1441 			default:
1442 				// keep compiler silent; should not reach here
1443 				width = imageWidth;
1444 		}
1445 
1446 		// TODO: eventually print large images on several pages
1447 		printJob.BeginJob();
1448 		fImageView->SetScale(width / imageWidth);
1449 		// coordinates are relative to printable rectangle
1450 		BRect bounds(bitmap->Bounds());
1451 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1452 		fImageView->SetScale(1.0);
1453 		printJob.SpoolPage();
1454 		printJob.CommitJob();
1455 	}
1456 }
1457 
1458 
1459 void
1460 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
1461 {
1462 	if (fSlideShowDelay == delay)
1463 		return;
1464 
1465 	fSlideShowDelay = delay;
1466 
1467 	ShowImageSettings* settings = my_app->Settings();
1468 	if (settings->Lock()) {
1469 		settings->SetTime("SlideShowDelay", fSlideShowDelay);
1470 		settings->Unlock();
1471 	}
1472 
1473 	if (fSlideShowRunner != NULL)
1474 		_StartSlideShow();
1475 }
1476 
1477 
1478 void
1479 ShowImageWindow::_StartSlideShow()
1480 {
1481 	_StopSlideShow();
1482 
1483 	BMessage nextSlide(kMsgNextSlide);
1484 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1485 }
1486 
1487 
1488 void
1489 ShowImageWindow::_StopSlideShow()
1490 {
1491 	if (fSlideShowRunner != NULL) {
1492 		delete fSlideShowRunner;
1493 		fSlideShowRunner = NULL;
1494 	}
1495 }
1496 
1497 
1498 void
1499 ShowImageWindow::_UpdateRatingMenu()
1500 {
1501 	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
1502 	if (file.InitCheck() != B_OK)
1503 		return;
1504 	int32 rating;
1505 	ssize_t size = sizeof(rating);
1506 	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
1507 		rating = 0;
1508 	// TODO: Finding the correct item could be more robust, like by looking
1509 	// at the message of each item.
1510 	for (int32 i = 1; i <= 10; i++) {
1511 		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
1512 		if (item == NULL)
1513 			break;
1514 		item->SetMarked(i == rating);
1515 	}
1516 }
1517 
1518 
1519 void
1520 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
1521 {
1522 	if (visible == !fToolBarView->IsHidden())
1523 		return;
1524 
1525 	float diff = fToolBarView->Bounds().Height() + 2;
1526 	if (!visible)
1527 		diff = -diff;
1528 	else
1529 		fToolBarView->Show();
1530 
1531 	if (animate) {
1532 		// Slide the controls into view. We do this with messages in order
1533 		// not to block the window thread.
1534 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1535 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
1536 		float originalY = fToolBarView->Frame().top;
1537 		for (int32 i = 0; i < steps; i++) {
1538 			BMessage message(kMsgSlideToolBar);
1539 			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
1540 			PostMessage(&message, this);
1541 		}
1542 		BMessage finalMessage(kMsgFinishSlidingToolBar);
1543 		finalMessage.AddFloat("offset", originalY + diff);
1544 		finalMessage.AddBool("show", visible);
1545 		PostMessage(&finalMessage, this);
1546 	} else {
1547 		fScrollView->ResizeBy(0, -diff);
1548 		fScrollView->MoveBy(0, diff);
1549 		fVerticalScrollBar->ResizeBy(0, -diff);
1550 		fVerticalScrollBar->MoveBy(0, diff);
1551 		fToolBarView->MoveBy(0, diff);
1552 		if (!visible)
1553 			fToolBarView->Hide();
1554 	}
1555 }
1556 
1557 
1558 bool
1559 ShowImageWindow::QuitRequested()
1560 {
1561 	if (fSavePanel) {
1562 		// Don't allow this window to be closed if a save panel is open
1563 		return false;
1564 	}
1565 
1566 	if (!_ClosePrompt())
1567 		return false;
1568 
1569 	ShowImageSettings* settings = my_app->Settings();
1570 	if (settings->Lock()) {
1571 		if (fFullScreen)
1572 			settings->SetRect("WindowFrame", fWindowFrame);
1573 		else
1574 			settings->SetRect("WindowFrame", Frame());
1575 		settings->Unlock();
1576 	}
1577 
1578 	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
1579 
1580 	return true;
1581 }
1582