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