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