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