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