xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
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 		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;
1153 	if (fImageView->Bitmap() != NULL) {
1154 		BRect bounds = fImageView->Bitmap()->Bounds();
1155 		frameText << bounds.IntegerWidth() + 1
1156 			<< "x" << bounds.IntegerHeight() + 1;
1157 	}
1158 	BString pages;
1159 	if (fNavigator.PageCount() > 1)
1160 		pages << fNavigator.CurrentPage() << "/" << fNavigator.PageCount();
1161 	fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType,
1162 		fImageView->Zoom());
1163 }
1164 
1165 
1166 void
1167 ShowImageWindow::_LoadError(const entry_ref& ref)
1168 {
1169 	// TODO: give a better error message!
1170 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1171 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1172 			"file or an image translator for it does not exist.",
1173 			"LoadAlerts"),
1174 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1175 		B_WIDTH_AS_USUAL, B_STOP_ALERT);
1176 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1177 	alert->Go();
1178 }
1179 
1180 
1181 void
1182 ShowImageWindow::_SaveAs(BMessage* message)
1183 {
1184 	// Read the translator and output type the user chose
1185 	int32 outTranslator;
1186 	uint32 outType;
1187 	if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK
1188 		|| message->FindInt32(kTypeField,
1189 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1190 		return;
1191 
1192 	// Add the chosen translator and output type to the
1193 	// message that the save panel will send back
1194 	BMessage panelMsg(MSG_SAVE_PANEL);
1195 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1196 	panelMsg.AddInt32(kTypeField, outType);
1197 
1198 	// Create save panel and show it
1199 	BMessenger target(this);
1200 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1201 		&target, NULL, 0, false, &panelMsg);
1202 
1203 	if (!fSavePanel)
1204 		return;
1205 
1206 	// Retrieve save directory from settings;
1207 	ShowImageSettings* settings = my_app->Settings();
1208 	if (settings->Lock()) {
1209 		fSavePanel->SetPanelDirectory(
1210 			settings->GetString("SaveDirectory", NULL));
1211 		settings->Unlock();
1212 	}
1213 
1214 	// Prefill current image's file name in save dialog
1215 	BEntry entry = fImageView->Image();
1216 	BPath path(&entry);
1217 	const char* filename = path.Leaf();
1218 	fSavePanel->SetSaveText(filename);
1219 
1220 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1221 	fSavePanel->Show();
1222 }
1223 
1224 
1225 void
1226 ShowImageWindow::_SaveToFile(BMessage* message)
1227 {
1228 	// Read in where the file should be saved
1229 	entry_ref dirRef;
1230 	if (message->FindRef("directory", &dirRef) != B_OK)
1231 		return;
1232 
1233 	const char* filename;
1234 	if (message->FindString("name", &filename) != B_OK)
1235 		return;
1236 
1237 	// Read in the translator and type to be used
1238 	// to save the output image
1239 	int32 outTranslator;
1240 	uint32 outType;
1241 	if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK
1242 		|| message->FindInt32(kTypeField,
1243 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1244 		return;
1245 
1246 	// Find the translator_format information needed to
1247 	// write a MIME attribute for the image file
1248 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1249 	const translation_format* outFormat = NULL;
1250 	int32 outCount = 0;
1251 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1252 		|| outCount < 1)
1253 		return;
1254 
1255 	int32 i;
1256 	for (i = 0; i < outCount; i++) {
1257 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
1258 				== outType)
1259 			break;
1260 	}
1261 	if (i == outCount)
1262 		return;
1263 
1264 	// Write out the image file
1265 	BDirectory dir(&dirRef);
1266 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1267 
1268 	// Store Save directory in settings;
1269 	ShowImageSettings* settings = my_app->Settings();
1270 	if (settings->Lock()) {
1271 		BPath path(&dirRef);
1272 		settings->SetString("SaveDirectory", path.Path());
1273 		settings->Unlock();
1274 	}
1275 }
1276 
1277 
1278 #undef B_TRANSLATION_CONTEXT
1279 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1280 
1281 
1282 bool
1283 ShowImageWindow::_ClosePrompt()
1284 {
1285 	if (!fModified)
1286 		return true;
1287 
1288 	int32 count = fNavigator.PageCount();
1289 	int32 page = fNavigator.CurrentPage();
1290 	BString prompt;
1291 
1292 	if (count > 1) {
1293 		bs_printf(&prompt,
1294 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1295 				"want to close the document?"),
1296 			fImageView->Image()->name, page);
1297 	} else {
1298 		bs_printf(&prompt,
1299 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1300 				"close the document?"),
1301 			fImageView->Image()->name);
1302 	}
1303 
1304 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1305 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1306 	alert->SetShortcut(0, B_ESCAPE);
1307 
1308 	if (alert->Go() == 0) {
1309 		// Cancel
1310 		return false;
1311 	}
1312 
1313 	// Close
1314 	fModified = false;
1315 	return true;
1316 }
1317 
1318 
1319 status_t
1320 ShowImageWindow::_LoadImage(bool forward)
1321 {
1322 	// If the user triggered a _LoadImage while in a slide show,
1323 	// make sure the new image is shown for the set delay:
1324 	_ResetSlideShowDelay();
1325 
1326 	BMessenger us(this);
1327 	status_t status = my_app->DefaultCache().RetrieveImage(
1328 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1329 	if (status != B_OK)
1330 		return status;
1331 
1332 	fProgressWindow->Start(this);
1333 
1334 	// Preload previous/next images - two in the navigation direction, one
1335 	// in the opposite direction.
1336 
1337 	entry_ref nextRef = fNavigator.CurrentRef();
1338 	if (_PreloadImage(forward, nextRef))
1339 		_PreloadImage(forward, nextRef);
1340 
1341 	entry_ref previousRef = fNavigator.CurrentRef();
1342 	_PreloadImage(!forward, previousRef);
1343 
1344 	return B_OK;
1345 }
1346 
1347 
1348 bool
1349 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1350 {
1351 	entry_ref currentRef = ref;
1352 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1353 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1354 		return false;
1355 
1356 	return my_app->DefaultCache().RetrieveImage(ref) == B_OK;
1357 }
1358 
1359 
1360 void
1361 ShowImageWindow::_ToggleFullScreen()
1362 {
1363 	BRect frame;
1364 	fFullScreen = !fFullScreen;
1365 	if (fFullScreen) {
1366 		BScreen screen;
1367 		fWindowFrame = Frame();
1368 		frame = screen.Frame();
1369 		frame.top -= fBar->Bounds().Height() + 1;
1370 		frame.right += be_control_look->GetScrollBarWidth(B_VERTICAL);
1371 		frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL);
1372 
1373 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1374 
1375 		Activate();
1376 			// make the window frontmost
1377 	} else {
1378 		frame = fWindowFrame;
1379 
1380 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1381 	}
1382 
1383 	fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
1384 	_SetToolBarVisible(!fFullScreen && fShowToolBar);
1385 	_SetToolBarBorder(!fFullScreen);
1386 
1387 	MoveTo(frame.left, frame.top);
1388 	ResizeTo(frame.Width(), frame.Height());
1389 
1390 	fImageView->SetHideIdlingCursor(fFullScreen);
1391 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1392 
1393 	Layout(false);
1394 		// We need to manually relayout here, as the views are layouted
1395 		// asynchronously, and FitToBounds() would still have the wrong size
1396 	fImageView->FitToBounds();
1397 }
1398 
1399 
1400 void
1401 ShowImageWindow::_ApplySettings()
1402 {
1403 	ShowImageSettings* settings = my_app->Settings();
1404 
1405 	if (settings->Lock()) {
1406 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1407 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1408 
1409 		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
1410 
1411 		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
1412 			"PO:Option", fPrintOptions.Option()));
1413 		fPrintOptions.SetZoomFactor(
1414 			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
1415 		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
1416 		fPrintOptions.SetWidth(
1417 			settings->GetFloat("PO:Width", fPrintOptions.Width()));
1418 		fPrintOptions.SetHeight(
1419 			settings->GetFloat("PO:Height", fPrintOptions.Height()));
1420 
1421 		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
1422 
1423 		settings->Unlock();
1424 	}
1425 }
1426 
1427 
1428 void
1429 ShowImageWindow::_SavePrintOptions()
1430 {
1431 	ShowImageSettings* settings = my_app->Settings();
1432 
1433 	if (settings->Lock()) {
1434 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1435 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1436 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1437 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1438 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1439 		settings->Unlock();
1440 	}
1441 }
1442 
1443 
1444 bool
1445 ShowImageWindow::_PageSetup()
1446 {
1447 	BPrintJob printJob(fImageView->Image()->name);
1448 	if (fPrintSettings != NULL)
1449 		printJob.SetSettings(new BMessage(*fPrintSettings));
1450 
1451 	status_t status = printJob.ConfigPage();
1452 	if (status == B_OK) {
1453 		delete fPrintSettings;
1454 		fPrintSettings = printJob.Settings();
1455 	}
1456 
1457 	return status == B_OK;
1458 }
1459 
1460 
1461 void
1462 ShowImageWindow::_PrepareForPrint()
1463 {
1464 	if (fPrintSettings == NULL) {
1465 		BPrintJob printJob(fImageView->Image()->name);
1466 		if (printJob.ConfigJob() == B_OK)
1467 			fPrintSettings = printJob.Settings();
1468 	}
1469 
1470 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1471 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1472 
1473 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1474 		&fPrintOptions, this);
1475 }
1476 
1477 
1478 void
1479 ShowImageWindow::_Print(BMessage* msg)
1480 {
1481 	status_t st;
1482 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1483 		return;
1484 
1485 	_SavePrintOptions();
1486 
1487 	BPrintJob printJob(fImageView->Image()->name);
1488 	if (fPrintSettings)
1489 		printJob.SetSettings(new BMessage(*fPrintSettings));
1490 
1491 	if (printJob.ConfigJob() == B_OK) {
1492 		delete fPrintSettings;
1493 		fPrintSettings = printJob.Settings();
1494 
1495 		// first/lastPage is unused for now
1496 		int32 firstPage = printJob.FirstPage();
1497 		int32 lastPage = printJob.LastPage();
1498 		BRect printableRect = printJob.PrintableRect();
1499 
1500 		if (firstPage < 1)
1501 			firstPage = 1;
1502 		if (lastPage < firstPage)
1503 			lastPage = firstPage;
1504 
1505 		BBitmap* bitmap = fImageView->Bitmap();
1506 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1507 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1508 
1509 		float width;
1510 		switch (fPrintOptions.Option()) {
1511 			case PrintOptions::kFitToPage: {
1512 				float w1 = printableRect.Width() + 1;
1513 				float w2 = imageWidth * (printableRect.Height() + 1)
1514 					/ imageHeight;
1515 				if (w2 < w1)
1516 					width = w2;
1517 				else
1518 					width = w1;
1519 			}	break;
1520 			case PrintOptions::kZoomFactor:
1521 				width = imageWidth * fPrintOptions.ZoomFactor();
1522 				break;
1523 			case PrintOptions::kDPI:
1524 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1525 				break;
1526 			case PrintOptions::kWidth:
1527 			case PrintOptions::kHeight:
1528 				width = fPrintOptions.Width();
1529 				break;
1530 
1531 			default:
1532 				// keep compiler silent; should not reach here
1533 				width = imageWidth;
1534 		}
1535 
1536 		// TODO: eventually print large images on several pages
1537 		printJob.BeginJob();
1538 		fImageView->SetScale(width / imageWidth);
1539 		// coordinates are relative to printable rectangle
1540 		BRect bounds(bitmap->Bounds());
1541 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1542 		fImageView->SetScale(1.0);
1543 		printJob.SpoolPage();
1544 		printJob.CommitJob();
1545 	}
1546 }
1547 
1548 
1549 void
1550 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
1551 {
1552 	if (fSlideShowDelay == delay)
1553 		return;
1554 
1555 	fSlideShowDelay = delay;
1556 
1557 	ShowImageSettings* settings = my_app->Settings();
1558 	if (settings->Lock()) {
1559 		settings->SetTime("SlideShowDelay", fSlideShowDelay);
1560 		settings->Unlock();
1561 	}
1562 
1563 	if (fSlideShowRunner != NULL)
1564 		_StartSlideShow();
1565 }
1566 
1567 
1568 void
1569 ShowImageWindow::_StartSlideShow()
1570 {
1571 	_StopSlideShow();
1572 
1573 	BMessage nextSlide(kMsgNextSlide);
1574 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1575 }
1576 
1577 
1578 void
1579 ShowImageWindow::_StopSlideShow()
1580 {
1581 	if (fSlideShowRunner != NULL) {
1582 		delete fSlideShowRunner;
1583 		fSlideShowRunner = NULL;
1584 	}
1585 }
1586 
1587 
1588 void
1589 ShowImageWindow::_ResetSlideShowDelay()
1590 {
1591 	if (fSlideShowRunner != NULL)
1592 		fSlideShowRunner->SetInterval(fSlideShowDelay);
1593 }
1594 
1595 
1596 void
1597 ShowImageWindow::_UpdateRatingMenu()
1598 {
1599 	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
1600 	if (file.InitCheck() != B_OK)
1601 		return;
1602 	int32 rating;
1603 	ssize_t size = sizeof(rating);
1604 	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
1605 		rating = 0;
1606 	// TODO: Finding the correct item could be more robust, like by looking
1607 	// at the message of each item.
1608 	for (int32 i = 1; i <= 10; i++) {
1609 		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
1610 		if (item == NULL)
1611 			break;
1612 		item->SetMarked(i == rating);
1613 	}
1614 	fResetRatingItem->SetEnabled(rating > 0);
1615 }
1616 
1617 
1618 void
1619 ShowImageWindow::_SaveWidthAndHeight()
1620 {
1621 	if (fNavigator.CurrentPage() != 1)
1622 		return;
1623 
1624 	if (fImageView->Bitmap() == NULL)
1625 		return;
1626 
1627 	BRect bounds = fImageView->Bitmap()->Bounds();
1628 	int32 width = bounds.IntegerWidth() + 1;
1629 	int32 height = bounds.IntegerHeight() + 1;
1630 
1631 	BNode node(&fNavigator.CurrentRef());
1632 	if (node.InitCheck() != B_OK)
1633 		return;
1634 
1635 	const char* kWidthAttrName = "Media:Width";
1636 	const char* kHeightAttrName = "Media:Height";
1637 
1638 	int32 widthAttr;
1639 	ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0,
1640 		&widthAttr, sizeof(widthAttr));
1641 	if (attrSize <= 0 || widthAttr != width)
1642 		node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width));
1643 
1644 	int32 heightAttr;
1645 	attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0,
1646 		&heightAttr, sizeof(heightAttr));
1647 	if (attrSize <= 0 || heightAttr != height)
1648 		node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height));
1649 }
1650 
1651 
1652 void
1653 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
1654 {
1655 	if (visible == fToolBarVisible)
1656 		return;
1657 
1658 	fToolBarVisible = visible;
1659 	float diff = fToolBar->Bounds().Height() + 2;
1660 	if (!visible)
1661 		diff = -diff;
1662 	else
1663 		fToolBar->Show();
1664 
1665 	if (animate) {
1666 		// Slide the controls into view. We do this with messages in order
1667 		// not to block the window thread.
1668 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1669 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
1670 		for (int32 i = 0; i < steps; i++) {
1671 			BMessage message(kMsgSlideToolBar);
1672 			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
1673 			PostMessage(&message, this);
1674 		}
1675 		BMessage finalMessage(kMsgFinishSlidingToolBar);
1676 		finalMessage.AddFloat("offset", visible ? 0 : diff);
1677 		finalMessage.AddBool("show", visible);
1678 		PostMessage(&finalMessage, this);
1679 	} else {
1680 		fScrollArea->ResizeBy(0, -diff);
1681 		fScrollArea->MoveBy(0, diff);
1682 		fToolBar->MoveBy(0, diff);
1683 		if (!visible)
1684 			fToolBar->Hide();
1685 	}
1686 }
1687 
1688 
1689 void
1690 ShowImageWindow::_SetToolBarBorder(bool visible)
1691 {
1692 	float inset = visible
1693 		? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0;
1694 
1695 	fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0);
1696 }
1697 
1698 
1699 bool
1700 ShowImageWindow::QuitRequested()
1701 {
1702 	if (fSavePanel) {
1703 		// Don't allow this window to be closed if a save panel is open
1704 		return false;
1705 	}
1706 
1707 	if (!_ClosePrompt())
1708 		return false;
1709 
1710 	ShowImageSettings* settings = my_app->Settings();
1711 	if (settings->Lock()) {
1712 		if (fFullScreen)
1713 			settings->SetRect("WindowFrame", fWindowFrame);
1714 		else
1715 			settings->SetRect("WindowFrame", Frame());
1716 		settings->Unlock();
1717 	}
1718 
1719 	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
1720 
1721 	return true;
1722 }
1723