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