xref: /haiku/src/apps/showimage/ShowImageWindow.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
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"), MSG_GET_INFO, 'I', 0, this);
448 	menu->AddSeparatorItem();
449 	_AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
450 		MSG_PAGE_SETUP, 0, 0, this);
451 	_AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
452 		MSG_PREPARE_PRINT, 'P', 0, this);
453 	menu->AddSeparatorItem();
454 	_AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this);
455 	_AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app);
456 	bar->AddItem(menu);
457 
458 	menu = new BMenu(B_TRANSLATE("Edit"));
459 	_AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false);
460 	menu->AddSeparatorItem();
461 	_AddItemMenu(menu, B_TRANSLATE("Selection mode"), MSG_SELECTION_MODE, 0, 0,
462 		this);
463 	_AddItemMenu(menu, B_TRANSLATE("Clear selection"),
464 		MSG_CLEAR_SELECT, 0, 0, this, false);
465 	_AddItemMenu(menu, B_TRANSLATE("Select all"),
466 		MSG_SELECT_ALL, 'A', 0, this);
467 	bar->AddItem(menu);
468 
469 	menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse"));
470 	_AddItemMenu(menu, B_TRANSLATE("First page"),
471 		MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this);
472 	_AddItemMenu(menu, B_TRANSLATE("Last page"),
473 		MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this);
474 	_AddItemMenu(menu, B_TRANSLATE("Previous page"),
475 		MSG_PAGE_PREV, B_LEFT_ARROW, 0, this);
476 	_AddItemMenu(menu, B_TRANSLATE("Next page"),
477 		MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this);
478 	fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page"));
479 	fGoToPageMenu->SetRadioMode(true);
480 	menu->AddItem(fGoToPageMenu);
481 	menu->AddSeparatorItem();
482 	_AddItemMenu(menu, B_TRANSLATE("Previous file"),
483 		MSG_FILE_PREV, B_UP_ARROW, 0, this);
484 	_AddItemMenu(menu, B_TRANSLATE("Next file"),
485 		MSG_FILE_NEXT, B_DOWN_ARROW, 0, this);
486 	bar->AddItem(menu);
487 
488 	menu = new BMenu(B_TRANSLATE("Image"));
489 	_AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"),
490 		MSG_ROTATE_90, 'R', 0, this);
491 	_AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"),
492 		MSG_ROTATE_270, 'R', B_SHIFT_KEY, this);
493 	menu->AddSeparatorItem();
494 	_AddItemMenu(menu, B_TRANSLATE("Flip left to right"),
495 		MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this);
496 	_AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"),
497 		MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this);
498 
499 	bar->AddItem(menu);
500 }
501 
502 
503 BMenuItem*
504 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what,
505 	char shortcut, uint32 modifier, const BHandler* target, bool enabled)
506 {
507 	BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut,
508 		modifier);
509 	menu->AddItem(item);
510 
511 	item->SetTarget(target);
512 	item->SetEnabled(enabled);
513 
514 	return item;
515 }
516 
517 
518 BMenuItem*
519 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, bigtime_t delay)
520 {
521 	BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY);
522 	message->AddInt64("delay", delay);
523 
524 	BMenuItem* item = new BMenuItem(label, message, 0);
525 	item->SetTarget(this);
526 
527 	if (delay == fSlideShowDelay)
528 		item->SetMarked(true);
529 
530 	menu->AddItem(item);
531 	return item;
532 }
533 
534 
535 void
536 ShowImageWindow::_ResizeWindowToImage()
537 {
538 	BBitmap* bitmap = fImageView->Bitmap();
539 	BScreen screen;
540 	if (bitmap == NULL || !screen.IsValid())
541 		return;
542 
543 	// TODO: use View::GetPreferredSize() instead?
544 	BRect r(bitmap->Bounds());
545 	float width = r.Width() + be_control_look->GetScrollBarWidth(B_VERTICAL);
546 	float height = r.Height() + 1 + fBar->Frame().Height()
547 		+ be_control_look->GetScrollBarWidth(B_HORIZONTAL);
548 
549 	BRect frame = screen.Frame();
550 	const float windowBorder = 5;
551 	// dimensions so that window does not reach outside of screen
552 	float maxWidth = frame.Width() + 1 - windowBorder - Frame().left;
553 	float maxHeight = frame.Height() + 1 - windowBorder - Frame().top;
554 
555 	// We have to check size limits manually, otherwise
556 	// menu bar will be too short for small images.
557 
558 	float minW, maxW, minH, maxH;
559 	GetSizeLimits(&minW, &maxW, &minH, &maxH);
560 	if (maxWidth > maxW)
561 		maxWidth = maxW;
562 	if (maxHeight > maxH)
563 		maxHeight = maxH;
564 	if (width < minW)
565 		width = minW;
566 	if (height < minH)
567 		height = minH;
568 
569 	if (width > maxWidth)
570 		width = maxWidth;
571 	if (height > maxHeight)
572 		height = maxHeight;
573 
574 	ResizeTo(width, height);
575 }
576 
577 
578 bool
579 ShowImageWindow::_ToggleMenuItem(uint32 what)
580 {
581 	bool marked = false;
582 	BMenuItem* item = fBar->FindItem(what);
583 	if (item != NULL) {
584 		marked = !item->IsMarked();
585 		item->SetMarked(marked);
586 	}
587 	fToolBar->SetActionPressed(what, marked);
588 	return marked;
589 }
590 
591 
592 void
593 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable)
594 {
595 	BMenuItem* item = menu->FindItem(what);
596 	if (item && item->IsEnabled() != enable)
597 		item->SetEnabled(enable);
598 	fToolBar->SetActionEnabled(what, enable);
599 }
600 
601 
602 void
603 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked)
604 {
605 	BMenuItem* item = menu->FindItem(what);
606 	if (item && item->IsMarked() != marked)
607 		item->SetMarked(marked);
608 	fToolBar->SetActionPressed(what, marked);
609 }
610 
611 
612 void
613 ShowImageWindow::_MarkSlideShowDelay(bigtime_t delay)
614 {
615 	const int32 count = fSlideShowDelayMenu->CountItems();
616 	for (int32 i = 0; i < count; i ++) {
617 		BMenuItem* item = fSlideShowDelayMenu->ItemAt(i);
618 		if (item != NULL) {
619 			bigtime_t itemDelay;
620 			if (item->Message()->FindInt64("delay", &itemDelay) == B_OK
621 				&& itemDelay == delay) {
622 				item->SetMarked(true);
623 				return;
624 			}
625 		}
626 	}
627 }
628 
629 
630 void
631 ShowImageWindow::Zoom(BPoint origin, float width, float height)
632 {
633 	_ToggleFullScreen();
634 }
635 
636 
637 void
638 ShowImageWindow::MessageReceived(BMessage* message)
639 {
640 	if (message->WasDropped()) {
641 		uint32 type;
642 		int32 count;
643 		status_t status = message->GetInfo("refs", &type, &count);
644 		if (status == B_OK && type == B_REF_TYPE) {
645 			message->what = B_REFS_RECEIVED;
646 			be_app->PostMessage(message);
647 		}
648 	}
649 
650 	switch (message->what) {
651 		case kMsgImageCacheImageLoaded:
652 		{
653 			fProgressWindow->Stop();
654 
655 			BitmapOwner* bitmapOwner = NULL;
656 			message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
657 
658 			bool first = fImageView->Bitmap() == NULL;
659 			entry_ref ref;
660 			message->FindRef("ref", &ref);
661 			if (!first && ref != fNavigator.CurrentRef()) {
662 				// ignore older images
663 				if (bitmapOwner != NULL)
664 					bitmapOwner->ReleaseReference();
665 				break;
666 			}
667 
668 			int32 page = message->FindInt32("page");
669 			int32 pageCount = message->FindInt32("pageCount");
670 			if (!first && page != fNavigator.CurrentPage()) {
671 				// ignore older pages
672 				if (bitmapOwner != NULL)
673 					bitmapOwner->ReleaseReference();
674 				break;
675 			}
676 
677 			status_t status = fImageView->SetImage(message);
678 			if (status != B_OK) {
679 				if (bitmapOwner != NULL)
680 					bitmapOwner->ReleaseReference();
681 
682 				_LoadError(ref);
683 
684 				// quit if file could not be opened
685 				if (first)
686 					Quit();
687 				break;
688 			}
689 
690 			fImageType = message->FindString("type");
691 			fNavigator.SetTo(ref, page, pageCount);
692 
693 			fImageView->FitToBounds();
694 			if (first) {
695 				fImageView->MakeFocus(true);
696 					// to receive key messages
697 				Show();
698 			}
699 
700 			fMimeType = new BMimeType(message->FindString("mime"));
701 			_UpdateOpenWithMenu(fOpenWithMenu);
702 			_UpdateRatingMenu();
703 			// Set width and height attributes of the currently showed file.
704 			// This should only be a temporary solution.
705 			_SaveWidthAndHeight();
706 			break;
707 		}
708 
709 		case kMsgImageCacheProgressUpdate:
710 		{
711 			entry_ref ref;
712 			if (message->FindRef("ref", &ref) == B_OK
713 				&& ref == fNavigator.CurrentRef()) {
714 				message->what = kMsgProgressUpdate;
715 				fProgressWindow->PostMessage(message);
716 			}
717 			break;
718 		}
719 
720 		case MSG_MODIFIED:
721 			// If image has been modified due to a Cut or Paste
722 			fModified = true;
723 			break;
724 
725 		case MSG_OUTPUT_TYPE:
726 			// User clicked Save As then choose an output format
727 			if (!fSavePanel)
728 				// If user doesn't already have a save panel open
729 				_SaveAs(message);
730 			break;
731 
732 		case MSG_SAVE_PANEL:
733 			// User specified where to save the output image
734 			_SaveToFile(message);
735 			break;
736 
737 		case B_CANCEL:
738 			delete fSavePanel;
739 			fSavePanel = NULL;
740 			break;
741 
742 		case MSG_UPDATE_STATUS:
743 		{
744 			int32 pages = fNavigator.PageCount();
745 			int32 currentPage = fNavigator.CurrentPage();
746 
747 			_EnableMenuItem(fBar, MSG_PAGE_FIRST,
748 				fNavigator.HasPreviousPage());
749 			_EnableMenuItem(fBar, MSG_PAGE_LAST, fNavigator.HasNextPage());
750 			_EnableMenuItem(fBar, MSG_PAGE_NEXT, fNavigator.HasNextPage());
751 			_EnableMenuItem(fBar, MSG_PAGE_PREV, fNavigator.HasPreviousPage());
752 			fGoToPageMenu->SetEnabled(pages > 1);
753 
754 			_EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile());
755 			_EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile());
756 
757 			if (fGoToPageMenu->CountItems() != pages) {
758 				// Only rebuild the submenu if the number of
759 				// pages is different
760 
761 				while (fGoToPageMenu->CountItems() > 0) {
762 					// Remove all page numbers
763 					delete fGoToPageMenu->RemoveItem((int32)0);
764 				}
765 
766 				for (int32 i = 1; i <= pages; i++) {
767 					// Fill Go To page submenu with an entry for each page
768 					BMessage* goTo = new BMessage(MSG_GOTO_PAGE);
769 					goTo->AddInt32("page", i);
770 
771 					char shortcut = 0;
772 					if (i < 10)
773 						shortcut = '0' + i;
774 
775 					BString strCaption;
776 					strCaption << i;
777 
778 					BMenuItem* item = new BMenuItem(strCaption.String(), goTo,
779 						shortcut, B_SHIFT_KEY);
780 					if (currentPage == i)
781 						item->SetMarked(true);
782 					fGoToPageMenu->AddItem(item);
783 				}
784 			} else {
785 				// Make sure the correct page is marked
786 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
787 				if (currentItem != NULL && !currentItem->IsMarked())
788 					currentItem->SetMarked(true);
789 			}
790 
791 			_UpdateStatusText(message);
792 
793 			BPath path(fImageView->Image());
794 			SetTitle(path.Path());
795 			break;
796 		}
797 
798 		case MSG_UPDATE_STATUS_TEXT:
799 		{
800 			_UpdateStatusText(message);
801 			break;
802 		}
803 
804 		case MSG_OPEN_WITH:
805 		{
806 			BString appSig = "";
807 			message->FindString("signature", &appSig);
808 			entry_ref ref = fNavigator.CurrentRef();
809 			BMessage openMsg(B_REFS_RECEIVED);
810 			openMsg.AddRef("refs", &ref);
811 			be_roster->Launch(appSig.String(), &openMsg);
812 			break;
813 		}
814 
815 		case MSG_SELECTION:
816 		{
817 			// The view sends this message when a selection is
818 			// made or the selection is cleared so that the window
819 			// can update the state of the appropriate menu items
820 			bool enable;
821 			if (message->FindBool("has_selection", &enable) == B_OK) {
822 				_EnableMenuItem(fBar, B_COPY, enable);
823 				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable);
824 			}
825 			break;
826 		}
827 
828 		case B_COPY:
829 			fImageView->CopySelectionToClipboard();
830 			break;
831 
832 		case MSG_SELECTION_MODE:
833 		{
834 			bool selectionMode = _ToggleMenuItem(MSG_SELECTION_MODE);
835 			fImageView->SetSelectionMode(selectionMode);
836 			if (!selectionMode)
837 				fImageView->ClearSelection();
838 			break;
839 		}
840 
841 		case MSG_CLEAR_SELECT:
842 			fImageView->ClearSelection();
843 			break;
844 
845 		case MSG_SELECT_ALL:
846 			fImageView->SelectAll();
847 			break;
848 
849 		case MSG_PAGE_FIRST:
850 			if (_ClosePrompt() && fNavigator.FirstPage())
851 				_LoadImage();
852 			break;
853 
854 		case MSG_PAGE_LAST:
855 			if (_ClosePrompt() && fNavigator.LastPage())
856 				_LoadImage();
857 			break;
858 
859 		case MSG_PAGE_NEXT:
860 			if (_ClosePrompt() && fNavigator.NextPage())
861 				_LoadImage();
862 			break;
863 
864 		case MSG_PAGE_PREV:
865 			if (_ClosePrompt() && fNavigator.PreviousPage())
866 				_LoadImage();
867 			break;
868 
869 		case MSG_GOTO_PAGE:
870 		{
871 			if (!_ClosePrompt())
872 				break;
873 
874 			int32 newPage;
875 			if (message->FindInt32("page", &newPage) != B_OK)
876 				break;
877 
878 			int32 currentPage = fNavigator.CurrentPage();
879 			int32 pages = fNavigator.PageCount();
880 
881 			// TODO: use radio mode instead!
882 			if (newPage > 0 && newPage <= pages) {
883 				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
884 				BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1);
885 				if (currentItem != NULL && newItem != NULL) {
886 					currentItem->SetMarked(false);
887 					newItem->SetMarked(true);
888 					if (fNavigator.GoToPage(newPage))
889 						_LoadImage();
890 				}
891 			}
892 			break;
893 		}
894 
895 		case kMsgFitToWindow:
896 			fImageView->FitToBounds();
897 			break;
898 
899 		case kMsgStretchToWindow:
900 			fImageView->SetStretchToBounds(
901 				_ToggleMenuItem(kMsgStretchToWindow));
902 			break;
903 
904 		case MSG_FILE_PREV:
905 			if (_ClosePrompt() && fNavigator.PreviousFile())
906 				_LoadImage(false);
907 			break;
908 
909 		case MSG_FILE_NEXT:
910 		case kMsgNextSlide:
911 			if (_ClosePrompt()) {
912 				if (!fNavigator.NextFile()) {
913 					// Wrap back around
914 					fNavigator.FirstFile();
915 				}
916 				_LoadImage();
917 			}
918 			break;
919 
920 		case kMsgDeleteCurrentFile:
921 		{
922 			if (fNavigator.MoveFileToTrash())
923 				_LoadImage();
924 			else
925 				PostMessage(B_QUIT_REQUESTED);
926 			break;
927 		}
928 
929 		case MSG_ROTATE_90:
930 			fImageView->Rotate(90);
931 			break;
932 
933 		case MSG_ROTATE_270:
934 			fImageView->Rotate(270);
935 			break;
936 
937 		case MSG_FLIP_LEFT_TO_RIGHT:
938 			fImageView->Flip(true);
939 			break;
940 
941 		case MSG_FLIP_TOP_TO_BOTTOM:
942 			fImageView->Flip(false);
943 			break;
944 
945 		case MSG_GET_INFO:
946 			_GetFileInfo(fNavigator.CurrentRef());
947 			break;
948 
949 		case MSG_SLIDE_SHOW:
950 		{
951 			bool fullScreen = false;
952 			message->FindBool("full screen", &fullScreen);
953 
954 			BMenuItem* item = fBar->FindItem(message->what);
955 			if (item == NULL)
956 				break;
957 
958 			if (item->IsMarked()) {
959 				item->SetMarked(false);
960 				_StopSlideShow();
961 				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
962 			} else if (_ClosePrompt()) {
963 				item->SetMarked(true);
964 				if (!fFullScreen && fullScreen)
965 					_ToggleFullScreen();
966 				_StartSlideShow();
967 				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, true);
968 			}
969 			break;
970 		}
971 
972 		case kMsgStopSlideShow:
973 		{
974 			BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW);
975 			if (item != NULL)
976 				item->SetMarked(false);
977 
978 			_StopSlideShow();
979 			fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
980 			break;
981 		}
982 
983 		case MSG_SLIDE_SHOW_DELAY:
984 		{
985 			bigtime_t delay;
986 			if (message->FindInt64("delay", &delay) == B_OK) {
987 				_SetSlideShowDelay(delay);
988 				// in case message is sent from popup menu
989 				_MarkSlideShowDelay(delay);
990 			}
991 			break;
992 		}
993 
994 		case MSG_FULL_SCREEN:
995 			_ToggleFullScreen();
996 			break;
997 
998 		case MSG_EXIT_FULL_SCREEN:
999 			if (fFullScreen)
1000 				_ToggleFullScreen();
1001 			break;
1002 
1003 		case MSG_SHOW_CAPTION:
1004 		{
1005 			fShowCaption = _ToggleMenuItem(message->what);
1006 			ShowImageSettings* settings = my_app->Settings();
1007 
1008 			if (settings->Lock()) {
1009 				settings->SetBool("ShowCaption", fShowCaption);
1010 				settings->Unlock();
1011 			}
1012 			if (fFullScreen)
1013 				fImageView->SetShowCaption(fShowCaption);
1014 		}	break;
1015 
1016 		case MSG_PAGE_SETUP:
1017 			_PageSetup();
1018 			break;
1019 
1020 		case MSG_PREPARE_PRINT:
1021 			_PrepareForPrint();
1022 			break;
1023 
1024 		case MSG_PRINT:
1025 			_Print(message);
1026 			break;
1027 
1028 		case MSG_ZOOM_IN:
1029 			fImageView->ZoomIn();
1030 			break;
1031 
1032 		case MSG_ZOOM_OUT:
1033 			fImageView->ZoomOut();
1034 			break;
1035 
1036 		case MSG_UPDATE_STATUS_ZOOM:
1037 			fStatusView->SetZoom(fImageView->Zoom());
1038 			break;
1039 
1040 		case kMsgOriginalSize:
1041 			if (message->FindInt32("behavior") == BButton::B_TOGGLE_BEHAVIOR) {
1042 				bool force = (message->FindInt32("be:value") == B_CONTROL_ON);
1043 				fImageView->ForceOriginalSize(force);
1044 				if (!force)
1045 					break;
1046 			}
1047 			fImageView->SetZoom(1.0);
1048 			break;
1049 
1050 		case MSG_SCALE_BILINEAR:
1051 			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
1052 			break;
1053 
1054 		case MSG_DESKTOP_BACKGROUND:
1055 		{
1056 			BMessage backgroundsMessage(B_REFS_RECEIVED);
1057 			backgroundsMessage.AddRef("refs", fImageView->Image());
1058 			// This is used in the Backgrounds code for scaled placement
1059 			backgroundsMessage.AddInt32("placement", 'scpl');
1060 			be_roster->Launch("application/x-vnd.haiku-backgrounds",
1061 				&backgroundsMessage);
1062 			break;
1063 		}
1064 
1065 		case MSG_SET_RATING:
1066 		{
1067 			int32 rating;
1068 			if (message->FindInt32("rating", &rating) != B_OK)
1069 				break;
1070 			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
1071 			if (file.InitCheck() != B_OK)
1072 				break;
1073 			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
1074 				sizeof(rating));
1075 			_UpdateRatingMenu();
1076 			break;
1077 		}
1078 
1079 		case kMsgToggleToolBar:
1080 		{
1081 			fShowToolBar = _ToggleMenuItem(message->what);
1082 			_SetToolBarVisible(fShowToolBar, true);
1083 
1084 			ShowImageSettings* settings = my_app->Settings();
1085 
1086 			if (settings->Lock()) {
1087 				settings->SetBool("ShowToolBar", fShowToolBar);
1088 				settings->Unlock();
1089 			}
1090 			break;
1091 		}
1092 		case kShowToolBarIfEnabled:
1093 		{
1094 			bool show;
1095 			if (message->FindBool("show", &show) != B_OK)
1096 				break;
1097 			_SetToolBarVisible(fShowToolBar && show, true);
1098 			break;
1099 		}
1100 		case kMsgSlideToolBar:
1101 		{
1102 			float offset;
1103 			if (message->FindFloat("offset", &offset) == B_OK) {
1104 				fToolBar->MoveBy(0, offset);
1105 				fScrollArea->ResizeBy(0, -offset);
1106 				fScrollArea->MoveBy(0, offset);
1107 				UpdateIfNeeded();
1108 				snooze(15000);
1109 			}
1110 			break;
1111 		}
1112 		case kMsgFinishSlidingToolBar:
1113 		{
1114 			float offset;
1115 			bool show;
1116 			if (message->FindFloat("offset", &offset) == B_OK
1117 				&& message->FindBool("show", &show) == B_OK) {
1118 				// Compensate rounding errors with the final placement
1119 				fToolBar->MoveTo(fToolBar->Frame().left, offset);
1120 				if (!show)
1121 					fToolBar->Hide();
1122 				BRect frame = fToolBar->Parent()->Bounds();
1123 				frame.top = fToolBar->Frame().bottom + 1;
1124 				fScrollArea->MoveTo(fScrollArea->Frame().left, frame.top);
1125 				fScrollArea->ResizeTo(fScrollArea->Bounds().Width(),
1126 					frame.Height() + 1);
1127 			}
1128 			break;
1129 		}
1130 
1131 		default:
1132 			BWindow::MessageReceived(message);
1133 			break;
1134 	}
1135 }
1136 
1137 
1138 void
1139 ShowImageWindow::_GetFileInfo(const entry_ref& ref)
1140 {
1141 	BMessage message('Tinf');
1142 	BMessenger tracker("application/x-vnd.Be-TRAK");
1143 	message.AddRef("refs", &ref);
1144 	tracker.SendMessage(&message);
1145 }
1146 
1147 
1148 void
1149 ShowImageWindow::_UpdateStatusText(const BMessage* message)
1150 {
1151 	BString frameText, height, width;
1152 	if (fImageView->Bitmap() != NULL) {
1153 		BRect bounds = fImageView->Bitmap()->Bounds();
1154 		fNumberFormat.Format(width, bounds.IntegerWidth() + 1);
1155 		fNumberFormat.Format(height, bounds.IntegerHeight() + 1);
1156 		frameText.SetToFormat("%s × %s", width.String(), height.String());
1157 	}
1158 
1159 	BString currentPage, pageCount, pages;
1160 	if (fNavigator.PageCount() > 1) {
1161 		fNumberFormat.Format(currentPage, fNavigator.CurrentPage());
1162 		fNumberFormat.Format(pageCount, fNavigator.PageCount());
1163 		pages.SetToFormat("%s / %s", currentPage.String(), pageCount.String());
1164 	}
1165 
1166 	fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType,
1167 		fImageView->Zoom());
1168 }
1169 
1170 
1171 void
1172 ShowImageWindow::_LoadError(const entry_ref& ref)
1173 {
1174 	// TODO: give a better error message!
1175 	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
1176 		B_TRANSLATE_CONTEXT("Could not load image! Either the "
1177 			"file or an image translator for it does not exist.",
1178 			"LoadAlerts"),
1179 		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
1180 		B_WIDTH_AS_USUAL, B_STOP_ALERT);
1181 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1182 	alert->Go();
1183 }
1184 
1185 
1186 void
1187 ShowImageWindow::_SaveAs(BMessage* message)
1188 {
1189 	// Read the translator and output type the user chose
1190 	int32 outTranslator;
1191 	uint32 outType;
1192 	if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK
1193 		|| message->FindInt32(kTypeField,
1194 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1195 		return;
1196 
1197 	// Add the chosen translator and output type to the
1198 	// message that the save panel will send back
1199 	BMessage panelMsg(MSG_SAVE_PANEL);
1200 	panelMsg.AddInt32(kTranslatorField, outTranslator);
1201 	panelMsg.AddInt32(kTypeField, outType);
1202 
1203 	// Create save panel and show it
1204 	BMessenger target(this);
1205 	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
1206 		&target, NULL, 0, false, &panelMsg);
1207 
1208 	if (!fSavePanel)
1209 		return;
1210 
1211 	// Retrieve save directory from settings;
1212 	ShowImageSettings* settings = my_app->Settings();
1213 	if (settings->Lock()) {
1214 		fSavePanel->SetPanelDirectory(
1215 			settings->GetString("SaveDirectory", NULL));
1216 		settings->Unlock();
1217 	}
1218 
1219 	// Prefill current image's file name in save dialog
1220 	BEntry entry = fImageView->Image();
1221 	BPath path(&entry);
1222 	const char* filename = path.Leaf();
1223 	fSavePanel->SetSaveText(filename);
1224 
1225 	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
1226 	fSavePanel->Show();
1227 }
1228 
1229 
1230 void
1231 ShowImageWindow::_SaveToFile(BMessage* message)
1232 {
1233 	// Read in where the file should be saved
1234 	entry_ref dirRef;
1235 	if (message->FindRef("directory", &dirRef) != B_OK)
1236 		return;
1237 
1238 	const char* filename;
1239 	if (message->FindString("name", &filename) != B_OK)
1240 		return;
1241 
1242 	// Read in the translator and type to be used
1243 	// to save the output image
1244 	int32 outTranslator;
1245 	uint32 outType;
1246 	if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK
1247 		|| message->FindInt32(kTypeField,
1248 			reinterpret_cast<int32 *>(&outType)) != B_OK)
1249 		return;
1250 
1251 	// Find the translator_format information needed to
1252 	// write a MIME attribute for the image file
1253 	BTranslatorRoster* roster = BTranslatorRoster::Default();
1254 	const translation_format* outFormat = NULL;
1255 	int32 outCount = 0;
1256 	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
1257 		|| outCount < 1)
1258 		return;
1259 
1260 	int32 i;
1261 	for (i = 0; i < outCount; i++) {
1262 		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
1263 				== outType)
1264 			break;
1265 	}
1266 	if (i == outCount)
1267 		return;
1268 
1269 	// Write out the image file
1270 	BDirectory dir(&dirRef);
1271 	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
1272 
1273 	// Store Save directory in settings;
1274 	ShowImageSettings* settings = my_app->Settings();
1275 	if (settings->Lock()) {
1276 		BPath path(&dirRef);
1277 		settings->SetString("SaveDirectory", path.Path());
1278 		settings->Unlock();
1279 	}
1280 }
1281 
1282 
1283 #undef B_TRANSLATION_CONTEXT
1284 #define B_TRANSLATION_CONTEXT "ClosePrompt"
1285 
1286 
1287 bool
1288 ShowImageWindow::_ClosePrompt()
1289 {
1290 	if (!fModified)
1291 		return true;
1292 
1293 	int32 count = fNavigator.PageCount();
1294 	int32 page = fNavigator.CurrentPage();
1295 	BString prompt;
1296 
1297 	if (count > 1) {
1298 		bs_printf(&prompt,
1299 			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
1300 				"want to close the document?"),
1301 			fImageView->Image()->name, page);
1302 	} else {
1303 		bs_printf(&prompt,
1304 			B_TRANSLATE("The document '%s' has been changed. Do you want to "
1305 				"close the document?"),
1306 			fImageView->Image()->name);
1307 	}
1308 
1309 	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
1310 		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
1311 	alert->SetShortcut(0, B_ESCAPE);
1312 
1313 	if (alert->Go() == 0) {
1314 		// Cancel
1315 		return false;
1316 	}
1317 
1318 	// Close
1319 	fModified = false;
1320 	return true;
1321 }
1322 
1323 
1324 status_t
1325 ShowImageWindow::_LoadImage(bool forward)
1326 {
1327 	// If the user triggered a _LoadImage while in a slide show,
1328 	// make sure the new image is shown for the set delay:
1329 	_ResetSlideShowDelay();
1330 
1331 	BMessenger us(this);
1332 	status_t status = my_app->DefaultCache().RetrieveImage(
1333 		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
1334 	if (status != B_OK)
1335 		return status;
1336 
1337 	fProgressWindow->Start(this);
1338 
1339 	// Preload previous/next images - two in the navigation direction, one
1340 	// in the opposite direction.
1341 
1342 	entry_ref nextRef = fNavigator.CurrentRef();
1343 	if (_PreloadImage(forward, nextRef))
1344 		_PreloadImage(forward, nextRef);
1345 
1346 	entry_ref previousRef = fNavigator.CurrentRef();
1347 	_PreloadImage(!forward, previousRef);
1348 
1349 	return B_OK;
1350 }
1351 
1352 
1353 bool
1354 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
1355 {
1356 	entry_ref currentRef = ref;
1357 	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
1358 		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
1359 		return false;
1360 
1361 	return my_app->DefaultCache().RetrieveImage(ref) == B_OK;
1362 }
1363 
1364 
1365 void
1366 ShowImageWindow::_ToggleFullScreen()
1367 {
1368 	BRect frame;
1369 	fFullScreen = !fFullScreen;
1370 	if (fFullScreen) {
1371 		BScreen screen;
1372 		fWindowFrame = Frame();
1373 		frame = screen.Frame();
1374 		frame.top -= fBar->Bounds().Height() + 1;
1375 		frame.right += be_control_look->GetScrollBarWidth(B_VERTICAL);
1376 		frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL);
1377 
1378 		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
1379 
1380 		Activate();
1381 			// make the window frontmost
1382 	} else {
1383 		frame = fWindowFrame;
1384 
1385 		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
1386 	}
1387 
1388 	fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
1389 	_SetToolBarVisible(!fFullScreen && fShowToolBar);
1390 	_SetToolBarBorder(!fFullScreen);
1391 
1392 	MoveTo(frame.left, frame.top);
1393 	ResizeTo(frame.Width(), frame.Height());
1394 
1395 	fImageView->SetHideIdlingCursor(fFullScreen);
1396 	fImageView->SetShowCaption(fFullScreen && fShowCaption);
1397 
1398 	Layout(false);
1399 		// We need to manually relayout here, as the views are layouted
1400 		// asynchronously, and FitToBounds() would still have the wrong size
1401 	fImageView->FitToBounds();
1402 }
1403 
1404 
1405 void
1406 ShowImageWindow::_ApplySettings()
1407 {
1408 	ShowImageSettings* settings = my_app->Settings();
1409 
1410 	if (settings->Lock()) {
1411 		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
1412 		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
1413 
1414 		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
1415 
1416 		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
1417 			"PO:Option", fPrintOptions.Option()));
1418 		fPrintOptions.SetZoomFactor(
1419 			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
1420 		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
1421 		fPrintOptions.SetWidth(
1422 			settings->GetFloat("PO:Width", fPrintOptions.Width()));
1423 		fPrintOptions.SetHeight(
1424 			settings->GetFloat("PO:Height", fPrintOptions.Height()));
1425 
1426 		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
1427 
1428 		settings->Unlock();
1429 	}
1430 }
1431 
1432 
1433 void
1434 ShowImageWindow::_SavePrintOptions()
1435 {
1436 	ShowImageSettings* settings = my_app->Settings();
1437 
1438 	if (settings->Lock()) {
1439 		settings->SetInt32("PO:Option", fPrintOptions.Option());
1440 		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
1441 		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
1442 		settings->SetFloat("PO:Width", fPrintOptions.Width());
1443 		settings->SetFloat("PO:Height", fPrintOptions.Height());
1444 		settings->Unlock();
1445 	}
1446 }
1447 
1448 
1449 bool
1450 ShowImageWindow::_PageSetup()
1451 {
1452 	BPrintJob printJob(fImageView->Image()->name);
1453 	if (fPrintSettings != NULL)
1454 		printJob.SetSettings(new BMessage(*fPrintSettings));
1455 
1456 	status_t status = printJob.ConfigPage();
1457 	if (status == B_OK) {
1458 		delete fPrintSettings;
1459 		fPrintSettings = printJob.Settings();
1460 	}
1461 
1462 	return status == B_OK;
1463 }
1464 
1465 
1466 void
1467 ShowImageWindow::_PrepareForPrint()
1468 {
1469 	if (fPrintSettings == NULL) {
1470 		BPrintJob printJob(fImageView->Image()->name);
1471 		if (printJob.ConfigJob() == B_OK)
1472 			fPrintSettings = printJob.Settings();
1473 	}
1474 
1475 	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
1476 	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
1477 
1478 	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
1479 		&fPrintOptions, this);
1480 }
1481 
1482 
1483 void
1484 ShowImageWindow::_Print(BMessage* msg)
1485 {
1486 	status_t st;
1487 	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
1488 		return;
1489 
1490 	_SavePrintOptions();
1491 
1492 	BPrintJob printJob(fImageView->Image()->name);
1493 	if (fPrintSettings)
1494 		printJob.SetSettings(new BMessage(*fPrintSettings));
1495 
1496 	if (printJob.ConfigJob() == B_OK) {
1497 		delete fPrintSettings;
1498 		fPrintSettings = printJob.Settings();
1499 
1500 		// first/lastPage is unused for now
1501 		int32 firstPage = printJob.FirstPage();
1502 		int32 lastPage = printJob.LastPage();
1503 		BRect printableRect = printJob.PrintableRect();
1504 
1505 		if (firstPage < 1)
1506 			firstPage = 1;
1507 		if (lastPage < firstPage)
1508 			lastPage = firstPage;
1509 
1510 		BBitmap* bitmap = fImageView->Bitmap();
1511 		float imageWidth = bitmap->Bounds().Width() + 1.0;
1512 		float imageHeight = bitmap->Bounds().Height() + 1.0;
1513 
1514 		float width;
1515 		switch (fPrintOptions.Option()) {
1516 			case PrintOptions::kFitToPage: {
1517 				float w1 = printableRect.Width() + 1;
1518 				float w2 = imageWidth * (printableRect.Height() + 1)
1519 					/ imageHeight;
1520 				if (w2 < w1)
1521 					width = w2;
1522 				else
1523 					width = w1;
1524 			}	break;
1525 			case PrintOptions::kZoomFactor:
1526 				width = imageWidth * fPrintOptions.ZoomFactor();
1527 				break;
1528 			case PrintOptions::kDPI:
1529 				width = imageWidth * 72.0 / fPrintOptions.DPI();
1530 				break;
1531 			case PrintOptions::kWidth:
1532 			case PrintOptions::kHeight:
1533 				width = fPrintOptions.Width();
1534 				break;
1535 
1536 			default:
1537 				// keep compiler silent; should not reach here
1538 				width = imageWidth;
1539 		}
1540 
1541 		// TODO: eventually print large images on several pages
1542 		printJob.BeginJob();
1543 		fImageView->SetScale(width / imageWidth);
1544 		// coordinates are relative to printable rectangle
1545 		BRect bounds(bitmap->Bounds());
1546 		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
1547 		fImageView->SetScale(1.0);
1548 		printJob.SpoolPage();
1549 		printJob.CommitJob();
1550 	}
1551 }
1552 
1553 
1554 void
1555 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
1556 {
1557 	if (fSlideShowDelay == delay)
1558 		return;
1559 
1560 	fSlideShowDelay = delay;
1561 
1562 	ShowImageSettings* settings = my_app->Settings();
1563 	if (settings->Lock()) {
1564 		settings->SetTime("SlideShowDelay", fSlideShowDelay);
1565 		settings->Unlock();
1566 	}
1567 
1568 	if (fSlideShowRunner != NULL)
1569 		_StartSlideShow();
1570 }
1571 
1572 
1573 void
1574 ShowImageWindow::_StartSlideShow()
1575 {
1576 	_StopSlideShow();
1577 
1578 	BMessage nextSlide(kMsgNextSlide);
1579 	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
1580 }
1581 
1582 
1583 void
1584 ShowImageWindow::_StopSlideShow()
1585 {
1586 	if (fSlideShowRunner != NULL) {
1587 		delete fSlideShowRunner;
1588 		fSlideShowRunner = NULL;
1589 	}
1590 }
1591 
1592 
1593 void
1594 ShowImageWindow::_ResetSlideShowDelay()
1595 {
1596 	if (fSlideShowRunner != NULL)
1597 		fSlideShowRunner->SetInterval(fSlideShowDelay);
1598 }
1599 
1600 
1601 void
1602 ShowImageWindow::_UpdateRatingMenu()
1603 {
1604 	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
1605 	if (file.InitCheck() != B_OK)
1606 		return;
1607 	int32 rating;
1608 	ssize_t size = sizeof(rating);
1609 	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
1610 		rating = 0;
1611 	// TODO: Finding the correct item could be more robust, like by looking
1612 	// at the message of each item.
1613 	for (int32 i = 1; i <= 10; i++) {
1614 		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
1615 		if (item == NULL)
1616 			break;
1617 		item->SetMarked(i == rating);
1618 	}
1619 	fResetRatingItem->SetEnabled(rating > 0);
1620 }
1621 
1622 
1623 void
1624 ShowImageWindow::_SaveWidthAndHeight()
1625 {
1626 	if (fNavigator.CurrentPage() != 1)
1627 		return;
1628 
1629 	if (fImageView->Bitmap() == NULL)
1630 		return;
1631 
1632 	BRect bounds = fImageView->Bitmap()->Bounds();
1633 	int32 width = bounds.IntegerWidth() + 1;
1634 	int32 height = bounds.IntegerHeight() + 1;
1635 
1636 	BNode node(&fNavigator.CurrentRef());
1637 	if (node.InitCheck() != B_OK)
1638 		return;
1639 
1640 	const char* kWidthAttrName = "Media:Width";
1641 	const char* kHeightAttrName = "Media:Height";
1642 
1643 	int32 widthAttr;
1644 	ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0,
1645 		&widthAttr, sizeof(widthAttr));
1646 	if (attrSize <= 0 || widthAttr != width)
1647 		node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width));
1648 
1649 	int32 heightAttr;
1650 	attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0,
1651 		&heightAttr, sizeof(heightAttr));
1652 	if (attrSize <= 0 || heightAttr != height)
1653 		node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height));
1654 }
1655 
1656 
1657 void
1658 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
1659 {
1660 	if (visible == fToolBarVisible)
1661 		return;
1662 
1663 	fToolBarVisible = visible;
1664 	float diff = fToolBar->Bounds().Height() + 2;
1665 	if (!visible)
1666 		diff = -diff;
1667 	else
1668 		fToolBar->Show();
1669 
1670 	if (animate) {
1671 		// Slide the controls into view. We do this with messages in order
1672 		// not to block the window thread.
1673 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
1674 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
1675 		for (int32 i = 0; i < steps; i++) {
1676 			BMessage message(kMsgSlideToolBar);
1677 			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
1678 			PostMessage(&message, this);
1679 		}
1680 		BMessage finalMessage(kMsgFinishSlidingToolBar);
1681 		finalMessage.AddFloat("offset", visible ? 0 : diff);
1682 		finalMessage.AddBool("show", visible);
1683 		PostMessage(&finalMessage, this);
1684 	} else {
1685 		fScrollArea->ResizeBy(0, -diff);
1686 		fScrollArea->MoveBy(0, diff);
1687 		fToolBar->MoveBy(0, diff);
1688 		if (!visible)
1689 			fToolBar->Hide();
1690 	}
1691 }
1692 
1693 
1694 void
1695 ShowImageWindow::_SetToolBarBorder(bool visible)
1696 {
1697 	float inset = visible
1698 		? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0;
1699 
1700 	fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0);
1701 }
1702 
1703 
1704 bool
1705 ShowImageWindow::QuitRequested()
1706 {
1707 	if (fSavePanel) {
1708 		// Don't allow this window to be closed if a save panel is open
1709 		return false;
1710 	}
1711 
1712 	if (!_ClosePrompt())
1713 		return false;
1714 
1715 	ShowImageSettings* settings = my_app->Settings();
1716 	if (settings->Lock()) {
1717 		if (fFullScreen)
1718 			settings->SetRect("WindowFrame", fWindowFrame);
1719 		else
1720 			settings->SetRect("WindowFrame", Frame());
1721 		settings->Unlock();
1722 	}
1723 
1724 	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
1725 
1726 	return true;
1727 }
1728