xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 1a76488fc88584bf66b9751d7fb9b6527ac20d87)
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 -= be_control_look->GetScrollBarWidth(B_VERTICAL);
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 -= be_control_look->GetScrollBarWidth(B_HORIZONTAL);
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() + be_control_look->GetScrollBarWidth(B_VERTICAL);
498 	float height = r.Height() + 1 + fBar->Frame().Height()
499 		+ be_control_look->GetScrollBarWidth(B_HORIZONTAL);
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()) {
850 				if (!fNavigator.NextFile()) {
851 					// Wrap back around
852 					fNavigator.FirstFile();
853 				}
854 				_LoadImage();
855 			}
856 			break;
857 
858 		case kMsgDeleteCurrentFile:
859 		{
860 			if (fNavigator.MoveFileToTrash())
861 				_LoadImage();
862 			else
863 				PostMessage(B_QUIT_REQUESTED);
864 			break;
865 		}
866 
867 		case MSG_ROTATE_90:
868 			fImageView->Rotate(90);
869 			break;
870 
871 		case MSG_ROTATE_270:
872 			fImageView->Rotate(270);
873 			break;
874 
875 		case MSG_FLIP_LEFT_TO_RIGHT:
876 			fImageView->Flip(true);
877 			break;
878 
879 		case MSG_FLIP_TOP_TO_BOTTOM:
880 			fImageView->Flip(false);
881 			break;
882 
883 		case MSG_GET_INFO:
884 			_GetFileInfo(fNavigator.CurrentRef());
885 			break;
886 
887 		case MSG_SLIDE_SHOW:
888 		{
889 			bool fullScreen = false;
890 			message->FindBool("full screen", &fullScreen);
891 
892 			BMenuItem* item = fBar->FindItem(message->what);
893 			if (item == NULL)
894 				break;
895 
896 			if (item->IsMarked()) {
897 				item->SetMarked(false);
898 				_StopSlideShow();
899 				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
900 			} else if (_ClosePrompt()) {
901 				item->SetMarked(true);
902 				if (!fFullScreen && fullScreen)
903 					_ToggleFullScreen();
904 				_StartSlideShow();
905 				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, true);
906 			}
907 			break;
908 		}
909 
910 		case kMsgStopSlideShow:
911 		{
912 			BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW);
913 			if (item != NULL)
914 				item->SetMarked(false);
915 
916 			_StopSlideShow();
917 			fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
918 			break;
919 		}
920 
921 		case MSG_SLIDE_SHOW_DELAY:
922 		{
923 			bigtime_t delay;
924 			if (message->FindInt64("delay", &delay) == B_OK) {
925 				_SetSlideShowDelay(delay);
926 				// in case message is sent from popup menu
927 				_MarkSlideShowDelay(delay);
928 			}
929 			break;
930 		}
931 
932 		case MSG_FULL_SCREEN:
933 			_ToggleFullScreen();
934 			break;
935 
936 		case MSG_EXIT_FULL_SCREEN:
937 			if (fFullScreen)
938 				_ToggleFullScreen();
939 			break;
940 
941 		case MSG_SHOW_CAPTION:
942 		{
943 			fShowCaption = _ToggleMenuItem(message->what);
944 			ShowImageSettings* settings = my_app->Settings();
945 
946 			if (settings->Lock()) {
947 				settings->SetBool("ShowCaption", fShowCaption);
948 				settings->Unlock();
949 			}
950 			if (fFullScreen)
951 				fImageView->SetShowCaption(fShowCaption);
952 		}	break;
953 
954 		case MSG_PAGE_SETUP:
955 			_PageSetup();
956 			break;
957 
958 		case MSG_PREPARE_PRINT:
959 			_PrepareForPrint();
960 			break;
961 
962 		case MSG_PRINT:
963 			_Print(message);
964 			break;
965 
966 		case MSG_ZOOM_IN:
967 			fImageView->ZoomIn();
968 			break;
969 
970 		case MSG_ZOOM_OUT:
971 			fImageView->ZoomOut();
972 			break;
973 
974 		case MSG_UPDATE_STATUS_ZOOM:
975 			fStatusView->SetZoom(fImageView->Zoom());
976 			break;
977 
978 		case kMsgOriginalSize:
979 			if (message->FindInt32("behavior") == BButton::B_TOGGLE_BEHAVIOR) {
980 				bool force = (message->FindInt32("be:value") == B_CONTROL_ON);
981 				fImageView->ForceOriginalSize(force);
982 				if (!force)
983 					break;
984 			}
985 			fImageView->SetZoom(1.0);
986 			break;
987 
988 		case MSG_SCALE_BILINEAR:
989 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
990 			break;
991 
992 		case MSG_DESKTOP_BACKGROUND:
993 		{
994 			BMessage backgroundsMessage(B_REFS_RECEIVED);
995 			backgroundsMessage.AddRef("refs", fImageView->Image());
996 			// This is used in the Backgrounds code for scaled placement
997 			backgroundsMessage.AddInt32("placement", 'scpl');
998 			be_roster->Launch("application/x-vnd.haiku-backgrounds",
999 				&backgroundsMessage);
1000 			break;
1001 		}
1002 
1003 		case MSG_SET_RATING:
1004 		{
1005 			int32 rating;
1006 			if (message->FindInt32("rating", &rating) != B_OK)
1007 				break;
1008 			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
1009 			if (file.InitCheck() != B_OK)
1010 				break;
1011 			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
1012 				sizeof(rating));
1013 			_UpdateRatingMenu();
1014 			break;
1015 		}
1016 
1017 		case kMsgToggleToolBar:
1018 		{
1019 			fShowToolBar = _ToggleMenuItem(message->what);
1020 			_SetToolBarVisible(fShowToolBar, true);
1021 
1022 			ShowImageSettings* settings = my_app->Settings();
1023 
1024 			if (settings->Lock()) {
1025 				settings->SetBool("ShowToolBar", fShowToolBar);
1026 				settings->Unlock();
1027 			}
1028 			break;
1029 		}
1030 		case kShowToolBarIfEnabled:
1031 		{
1032 			bool show;
1033 			if (message->FindBool("show", &show) != B_OK)
1034 				break;
1035 			_SetToolBarVisible(fShowToolBar && show, true);
1036 			break;
1037 		}
1038 		case kMsgSlideToolBar:
1039 		{
1040 			float offset;
1041 			if (message->FindFloat("offset", &offset) == B_OK) {
1042 				fToolBar->MoveBy(0, offset);
1043 				fScrollView->ResizeBy(0, -offset);
1044 				fScrollView->MoveBy(0, offset);
1045 				UpdateIfNeeded();
1046 				snooze(15000);
1047 			}
1048 			break;
1049 		}
1050 		case kMsgFinishSlidingToolBar:
1051 		{
1052 			float offset;
1053 			bool show;
1054 			if (message->FindFloat("offset", &offset) == B_OK
1055 				&& message->FindBool("show", &show) == B_OK) {
1056 				// Compensate rounding errors with the final placement
1057 				fToolBar->MoveTo(fToolBar->Frame().left, offset);
1058 				if (!show)
1059 					fToolBar->Hide();
1060 				BRect frame = fToolBar->Parent()->Bounds();
1061 				frame.top = fToolBar->Frame().bottom + 1;
1062 				fScrollView->MoveTo(fScrollView->Frame().left, frame.top);
1063 				fScrollView->ResizeTo(fScrollView->Bounds().Width(),
1064 					frame.Height() + 1);
1065 			}
1066 			break;
1067 		}
1068 
1069 		default:
1070 			BWindow::MessageReceived(message);
1071 			break;
1072 	}
1073 }
1074 
1075 
1076 void
1077 ShowImageWindow::_GetFileInfo(const entry_ref& ref)
1078 {
1079 	BMessage message('Tinf');
1080 	BMessenger tracker("application/x-vnd.Be-TRAK");
1081 	message.AddRef("refs", &ref);
1082 	tracker.SendMessage(&message);
1083 }
1084 
1085 
1086 void
1087 ShowImageWindow::_UpdateStatusText(const BMessage* message)
1088 {
1089 	BString frameText;
1090 	if (fImageView->Bitmap() != NULL) {
1091 		BRect bounds = fImageView->Bitmap()->Bounds();
1092 		frameText << bounds.IntegerWidth() + 1
1093 			<< "x" << bounds.IntegerHeight() + 1;
1094 	}
1095 	BString pages;
1096 	if (fNavigator.PageCount() > 1)
1097 		pages << fNavigator.CurrentPage() << "/" << fNavigator.PageCount();
1098 	fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType,
1099 		fImageView->Zoom());
1100 }
1101 
1102 
1103 void
1104 ShowImageWindow::_LoadError(const entry_ref& ref)
1105 {
1106 	// TODO: give a better error message!
1107 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1108 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1109 			"file or an image translator for it does not exist.",
1110 			"LoadAlerts"),
1111 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1112 		B_WIDTH_AS_USUAL, B_STOP_ALERT);
1113 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1114 	alert->Go();
1115 }
1116 
1117 
1118 void
1119 ShowImageWindow::_SaveAs(BMessage* message)
1120 {
1121 	// Read the translator and output type the user chose
1122 	translator_id outTranslator;
1123 	uint32 outType;
1124 	if (message->FindInt32(kTranslatorField,
1125 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1126 		|| message->FindInt32(kTypeField,
1127 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1128 		return;
1129 
1130 	// Add the chosen translator and output type to the
1131 	// message that the save panel will send back
1132 	BMessage panelMsg(MSG_SAVE_PANEL);
1133 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1134 	panelMsg.AddInt32(kTypeField, outType);
1135 
1136 	// Create save panel and show it
1137 	BMessenger target(this);
1138 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1139 		&target, NULL, 0, false, &panelMsg);
1140 	if (!fSavePanel)
1141 		return;
1142 
1143 	// Retrieve save directory from settings;
1144 	ShowImageSettings* settings = my_app->Settings();
1145 	if (settings->Lock()) {
1146 		fSavePanel->SetPanelDirectory(
1147 			settings->GetString("SaveDirectory", NULL));
1148 		settings->Unlock();
1149 	}
1150 
1151 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1152 	fSavePanel->Show();
1153 }
1154 
1155 
1156 void
1157 ShowImageWindow::_SaveToFile(BMessage* message)
1158 {
1159 	// Read in where the file should be saved
1160 	entry_ref dirRef;
1161 	if (message->FindRef("directory", &dirRef) != B_OK)
1162 		return;
1163 
1164 	const char* filename;
1165 	if (message->FindString("name", &filename) != B_OK)
1166 		return;
1167 
1168 	// Read in the translator and type to be used
1169 	// to save the output image
1170 	translator_id outTranslator;
1171 	uint32 outType;
1172 	if (message->FindInt32(kTranslatorField,
1173 			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
1174 		|| message->FindInt32(kTypeField,
1175 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1176 		return;
1177 
1178 	// Find the translator_format information needed to
1179 	// write a MIME attribute for the image file
1180 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1181 	const translation_format* outFormat = NULL;
1182 	int32 outCount = 0;
1183 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1184 		|| outCount < 1)
1185 		return;
1186 
1187 	int32 i;
1188 	for (i = 0; i < outCount; i++) {
1189 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
1190 				== outType)
1191 			break;
1192 	}
1193 	if (i == outCount)
1194 		return;
1195 
1196 	// Write out the image file
1197 	BDirectory dir(&dirRef);
1198 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1199 
1200 	// Store Save directory in settings;
1201 	ShowImageSettings* settings = my_app->Settings();
1202 	if (settings->Lock()) {
1203 		BPath path(&dirRef);
1204 		settings->SetString("SaveDirectory", path.Path());
1205 		settings->Unlock();
1206 	}
1207 }
1208 
1209 
1210 #undef B_TRANSLATION_CONTEXT
1211 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1212 
1213 
1214 bool
1215 ShowImageWindow::_ClosePrompt()
1216 {
1217 	if (!fModified)
1218 		return true;
1219 
1220 	int32 count = fNavigator.PageCount();
1221 	int32 page = fNavigator.CurrentPage();
1222 	BString prompt;
1223 
1224 	if (count > 1) {
1225 		bs_printf(&prompt,
1226 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1227 				"want to close the document?"),
1228 			fImageView->Image()->name, page);
1229 	} else {
1230 		bs_printf(&prompt,
1231 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1232 				"close the document?"),
1233 			fImageView->Image()->name);
1234 	}
1235 
1236 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1237 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1238 	alert->SetShortcut(0, B_ESCAPE);
1239 
1240 	if (alert->Go() == 0) {
1241 		// Cancel
1242 		return false;
1243 	}
1244 
1245 	// Close
1246 	fModified = false;
1247 	return true;
1248 }
1249 
1250 
1251 status_t
1252 ShowImageWindow::_LoadImage(bool forward)
1253 {
1254 	// If the user triggered a _LoadImage while in a slide show,
1255 	// make sure the new image is shown for the set delay:
1256 	_ResetSlideShowDelay();
1257 
1258 	BMessenger us(this);
1259 	status_t status = my_app->DefaultCache().RetrieveImage(
1260 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1261 	if (status != B_OK)
1262 		return status;
1263 
1264 	fProgressWindow->Start(this);
1265 
1266 	// Preload previous/next images - two in the navigation direction, one
1267 	// in the opposite direction.
1268 
1269 	entry_ref nextRef = fNavigator.CurrentRef();
1270 	if (_PreloadImage(forward, nextRef))
1271 		_PreloadImage(forward, nextRef);
1272 
1273 	entry_ref previousRef = fNavigator.CurrentRef();
1274 	_PreloadImage(!forward, previousRef);
1275 
1276 	return B_OK;
1277 }
1278 
1279 
1280 bool
1281 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1282 {
1283 	entry_ref currentRef = ref;
1284 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1285 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1286 		return false;
1287 
1288 	return my_app->DefaultCache().RetrieveImage(ref) == B_OK;
1289 }
1290 
1291 
1292 void
1293 ShowImageWindow::_ToggleFullScreen()
1294 {
1295 	BRect frame;
1296 	fFullScreen = !fFullScreen;
1297 	if (fFullScreen) {
1298 		BScreen screen;
1299 		fWindowFrame = Frame();
1300 		frame = screen.Frame();
1301 		frame.top -= fBar->Bounds().Height() + 1;
1302 		frame.right += be_control_look->GetScrollBarWidth(B_VERTICAL);
1303 		frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL);
1304 
1305 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1306 
1307 		Activate();
1308 			// make the window frontmost
1309 	} else {
1310 		frame = fWindowFrame;
1311 
1312 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1313 	}
1314 
1315 	fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
1316 	_SetToolBarVisible(!fFullScreen && fShowToolBar);
1317 	_SetToolBarBorder(!fFullScreen);
1318 
1319 	MoveTo(frame.left, frame.top);
1320 	ResizeTo(frame.Width(), frame.Height());
1321 
1322 	fImageView->SetHideIdlingCursor(fFullScreen);
1323 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1324 
1325 	Layout(false);
1326 		// We need to manually relayout here, as the views are layouted
1327 		// asynchronously, and FitToBounds() would still have the wrong size
1328 	fImageView->FitToBounds();
1329 }
1330 
1331 
1332 void
1333 ShowImageWindow::_ApplySettings()
1334 {
1335 	ShowImageSettings* settings = my_app->Settings();
1336 
1337 	if (settings->Lock()) {
1338 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1339 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1340 
1341 		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
1342 
1343 		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
1344 			"PO:Option", fPrintOptions.Option()));
1345 		fPrintOptions.SetZoomFactor(
1346 			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
1347 		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
1348 		fPrintOptions.SetWidth(
1349 			settings->GetFloat("PO:Width", fPrintOptions.Width()));
1350 		fPrintOptions.SetHeight(
1351 			settings->GetFloat("PO:Height", fPrintOptions.Height()));
1352 
1353 		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
1354 
1355 		settings->Unlock();
1356 	}
1357 }
1358 
1359 
1360 void
1361 ShowImageWindow::_SavePrintOptions()
1362 {
1363 	ShowImageSettings* settings = my_app->Settings();
1364 
1365 	if (settings->Lock()) {
1366 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1367 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1368 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1369 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1370 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1371 		settings->Unlock();
1372 	}
1373 }
1374 
1375 
1376 bool
1377 ShowImageWindow::_PageSetup()
1378 {
1379 	BPrintJob printJob(fImageView->Image()->name);
1380 	if (fPrintSettings != NULL)
1381 		printJob.SetSettings(new BMessage(*fPrintSettings));
1382 
1383 	status_t status = printJob.ConfigPage();
1384 	if (status == B_OK) {
1385 		delete fPrintSettings;
1386 		fPrintSettings = printJob.Settings();
1387 	}
1388 
1389 	return status == B_OK;
1390 }
1391 
1392 
1393 void
1394 ShowImageWindow::_PrepareForPrint()
1395 {
1396 	if (fPrintSettings == NULL) {
1397 		BPrintJob printJob(fImageView->Image()->name);
1398 		if (printJob.ConfigJob() == B_OK)
1399 			fPrintSettings = printJob.Settings();
1400 	}
1401 
1402 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1403 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1404 
1405 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1406 		&fPrintOptions, this);
1407 }
1408 
1409 
1410 void
1411 ShowImageWindow::_Print(BMessage* msg)
1412 {
1413 	status_t st;
1414 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1415 		return;
1416 
1417 	_SavePrintOptions();
1418 
1419 	BPrintJob printJob(fImageView->Image()->name);
1420 	if (fPrintSettings)
1421 		printJob.SetSettings(new BMessage(*fPrintSettings));
1422 
1423 	if (printJob.ConfigJob() == B_OK) {
1424 		delete fPrintSettings;
1425 		fPrintSettings = printJob.Settings();
1426 
1427 		// first/lastPage is unused for now
1428 		int32 firstPage = printJob.FirstPage();
1429 		int32 lastPage = printJob.LastPage();
1430 		BRect printableRect = printJob.PrintableRect();
1431 
1432 		if (firstPage < 1)
1433 			firstPage = 1;
1434 		if (lastPage < firstPage)
1435 			lastPage = firstPage;
1436 
1437 		BBitmap* bitmap = fImageView->Bitmap();
1438 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1439 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1440 
1441 		float width;
1442 		switch (fPrintOptions.Option()) {
1443 			case PrintOptions::kFitToPage: {
1444 				float w1 = printableRect.Width() + 1;
1445 				float w2 = imageWidth * (printableRect.Height() + 1)
1446 					/ imageHeight;
1447 				if (w2 < w1)
1448 					width = w2;
1449 				else
1450 					width = w1;
1451 			}	break;
1452 			case PrintOptions::kZoomFactor:
1453 				width = imageWidth * fPrintOptions.ZoomFactor();
1454 				break;
1455 			case PrintOptions::kDPI:
1456 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1457 				break;
1458 			case PrintOptions::kWidth:
1459 			case PrintOptions::kHeight:
1460 				width = fPrintOptions.Width();
1461 				break;
1462 
1463 			default:
1464 				// keep compiler silent; should not reach here
1465 				width = imageWidth;
1466 		}
1467 
1468 		// TODO: eventually print large images on several pages
1469 		printJob.BeginJob();
1470 		fImageView->SetScale(width / imageWidth);
1471 		// coordinates are relative to printable rectangle
1472 		BRect bounds(bitmap->Bounds());
1473 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1474 		fImageView->SetScale(1.0);
1475 		printJob.SpoolPage();
1476 		printJob.CommitJob();
1477 	}
1478 }
1479 
1480 
1481 void
1482 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
1483 {
1484 	if (fSlideShowDelay == delay)
1485 		return;
1486 
1487 	fSlideShowDelay = delay;
1488 
1489 	ShowImageSettings* settings = my_app->Settings();
1490 	if (settings->Lock()) {
1491 		settings->SetTime("SlideShowDelay", fSlideShowDelay);
1492 		settings->Unlock();
1493 	}
1494 
1495 	if (fSlideShowRunner != NULL)
1496 		_StartSlideShow();
1497 }
1498 
1499 
1500 void
1501 ShowImageWindow::_StartSlideShow()
1502 {
1503 	_StopSlideShow();
1504 
1505 	BMessage nextSlide(kMsgNextSlide);
1506 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1507 }
1508 
1509 
1510 void
1511 ShowImageWindow::_StopSlideShow()
1512 {
1513 	if (fSlideShowRunner != NULL) {
1514 		delete fSlideShowRunner;
1515 		fSlideShowRunner = NULL;
1516 	}
1517 }
1518 
1519 
1520 void
1521 ShowImageWindow::_ResetSlideShowDelay()
1522 {
1523 	if (fSlideShowRunner != NULL)
1524 		fSlideShowRunner->SetInterval(fSlideShowDelay);
1525 }
1526 
1527 
1528 void
1529 ShowImageWindow::_UpdateRatingMenu()
1530 {
1531 	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
1532 	if (file.InitCheck() != B_OK)
1533 		return;
1534 	int32 rating;
1535 	ssize_t size = sizeof(rating);
1536 	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
1537 		rating = 0;
1538 	// TODO: Finding the correct item could be more robust, like by looking
1539 	// at the message of each item.
1540 	for (int32 i = 1; i <= 10; i++) {
1541 		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
1542 		if (item == NULL)
1543 			break;
1544 		item->SetMarked(i == rating);
1545 	}
1546 }
1547 
1548 
1549 void
1550 ShowImageWindow::_SaveWidthAndHeight()
1551 {
1552 	if (fNavigator.CurrentPage() != 1)
1553 		return;
1554 
1555 	if (fImageView->Bitmap() == NULL)
1556 		return;
1557 
1558 	BRect bounds = fImageView->Bitmap()->Bounds();
1559 	int32 width = bounds.IntegerWidth() + 1;
1560 	int32 height = bounds.IntegerHeight() + 1;
1561 
1562 	BNode node(&fNavigator.CurrentRef());
1563 	if (node.InitCheck() != B_OK)
1564 		return;
1565 
1566 	const char* kWidthAttrName = "Media:Width";
1567 	const char* kHeightAttrName = "Media:Height";
1568 
1569 	int32 widthAttr;
1570 	ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0,
1571 		&widthAttr, sizeof(widthAttr));
1572 	if (attrSize <= 0 || widthAttr != width)
1573 		node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width));
1574 
1575 	int32 heightAttr;
1576 	attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0,
1577 		&heightAttr, sizeof(heightAttr));
1578 	if (attrSize <= 0 || heightAttr != height)
1579 		node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height));
1580 }
1581 
1582 
1583 void
1584 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
1585 {
1586 	if (visible == fToolBarVisible)
1587 		return;
1588 
1589 	fToolBarVisible = visible;
1590 	float diff = fToolBar->Bounds().Height() + 2;
1591 	if (!visible)
1592 		diff = -diff;
1593 	else
1594 		fToolBar->Show();
1595 
1596 	if (animate) {
1597 		// Slide the controls into view. We do this with messages in order
1598 		// not to block the window thread.
1599 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1600 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
1601 		for (int32 i = 0; i < steps; i++) {
1602 			BMessage message(kMsgSlideToolBar);
1603 			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
1604 			PostMessage(&message, this);
1605 		}
1606 		BMessage finalMessage(kMsgFinishSlidingToolBar);
1607 		finalMessage.AddFloat("offset", visible ? 0 : diff);
1608 		finalMessage.AddBool("show", visible);
1609 		PostMessage(&finalMessage, this);
1610 	} else {
1611 		fScrollView->ResizeBy(0, -diff);
1612 		fScrollView->MoveBy(0, diff);
1613 		fToolBar->MoveBy(0, diff);
1614 		if (!visible)
1615 			fToolBar->Hide();
1616 	}
1617 }
1618 
1619 
1620 void
1621 ShowImageWindow::_SetToolBarBorder(bool visible)
1622 {
1623 	float inset = visible
1624 		? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0;
1625 
1626 	fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0);
1627 }
1628 
1629 
1630 bool
1631 ShowImageWindow::QuitRequested()
1632 {
1633 	if (fSavePanel) {
1634 		// Don't allow this window to be closed if a save panel is open
1635 		return false;
1636 	}
1637 
1638 	if (!_ClosePrompt())
1639 		return false;
1640 
1641 	ShowImageSettings* settings = my_app->Settings();
1642 	if (settings->Lock()) {
1643 		if (fFullScreen)
1644 			settings->SetRect("WindowFrame", fWindowFrame);
1645 		else
1646 			settings->SetRect("WindowFrame", Frame());
1647 		settings->Unlock();
1648 	}
1649 
1650 	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
1651 
1652 	return true;
1653 }
1654