xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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
65 const char* kTypeField = "be:type";
66 const char* kTranslatorField = "be:translator";
67 
68 const bigtime_t kDefaultSlideShowDelay = 3000000;
69 	// 3 seconds
70 
71 
72 // message constants
73 enum {
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.
113 static void
114 bs_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 
134 ShowImageWindow::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 
307 ShowImageWindow::~ShowImageWindow()
308 {
309 	fProgressWindow->Lock();
310 	fProgressWindow->Quit();
311 
312 	_StopSlideShow();
313 }
314 
315 
316 void
317 ShowImageWindow::BuildContextMenu(BMenu* menu)
318 {
319 	_BuildViewMenu(menu, true);
320 }
321 
322 
323 void
324 ShowImageWindow::_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 
396 void
397 ShowImageWindow::_UpdateOpenWithMenu(BMenu* menu)
398 {
399 	update_supporting_apps_menu(menu, fMimeType, MSG_OPEN_WITH, this);
400 }
401 
402 
403 BMenu*
404 ShowImageWindow::_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 
419 void
420 ShowImageWindow::_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 
504 BMenuItem*
505 ShowImageWindow::_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 
519 BMenuItem*
520 ShowImageWindow::_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 
536 void
537 ShowImageWindow::_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 
579 bool
580 ShowImageWindow::_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 
593 void
594 ShowImageWindow::_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 
603 void
604 ShowImageWindow::_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 
613 void
614 ShowImageWindow::_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 
631 void
632 ShowImageWindow::Zoom(BPoint origin, float width, float height)
633 {
634 	_ToggleFullScreen();
635 }
636 
637 
638 void
639 ShowImageWindow::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 
1139 void
1140 ShowImageWindow::_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 
1149 void
1150 ShowImageWindow::_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 
1172 void
1173 ShowImageWindow::_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 
1187 void
1188 ShowImageWindow::_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 
1231 void
1232 ShowImageWindow::_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 
1288 bool
1289 ShowImageWindow::_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 
1325 status_t
1326 ShowImageWindow::_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 
1354 bool
1355 ShowImageWindow::_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 
1366 void
1367 ShowImageWindow::_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 
1406 void
1407 ShowImageWindow::_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 
1434 void
1435 ShowImageWindow::_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 
1450 bool
1451 ShowImageWindow::_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 
1467 void
1468 ShowImageWindow::_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 
1484 void
1485 ShowImageWindow::_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 
1555 void
1556 ShowImageWindow::_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 
1574 void
1575 ShowImageWindow::_StartSlideShow()
1576 {
1577 	_StopSlideShow();
1578 
1579 	BMessage nextSlide(kMsgNextSlide);
1580 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1581 }
1582 
1583 
1584 void
1585 ShowImageWindow::_StopSlideShow()
1586 {
1587 	if (fSlideShowRunner != NULL) {
1588 		delete fSlideShowRunner;
1589 		fSlideShowRunner = NULL;
1590 	}
1591 }
1592 
1593 
1594 void
1595 ShowImageWindow::_ResetSlideShowDelay()
1596 {
1597 	if (fSlideShowRunner != NULL)
1598 		fSlideShowRunner->SetInterval(fSlideShowDelay);
1599 }
1600 
1601 
1602 void
1603 ShowImageWindow::_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 
1624 void
1625 ShowImageWindow::_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 
1658 void
1659 ShowImageWindow::_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 
1695 void
1696 ShowImageWindow::_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 
1705 bool
1706 ShowImageWindow::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