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