/*
 * Copyright 2003-2014, Haiku, Inc. All Rights Reserved.
 * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
 * Copyright 2006 Bernd Korz. All Rights Reserved
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Fernando Francisco de Oliveira
 *		Michael Wilber
 *		Michael Pfeiffer
 *		yellowTAB GmbH
 *		Bernd Korz
 *		Axel Dörfler, axeld@pinc-software.de
 *		Stephan Aßmus <superstippi@gmx.de>
 */
 
 
#include "ShowImageWindow.h"
 
#include <new>
#include <stdio.h>
#include <stdlib.h>
 
#include <Alert.h>
#include <Application.h>
#include <Bitmap.h>
#include <BitmapStream.h>
#include <Button.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <DurationFormat.h>
#include <Entry.h>
#include <File.h>
#include <FilePanel.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <Path.h>
#include <PrintJob.h>
#include <RecentItems.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <String.h>
#include <SupportDefs.h>
#include <TranslationDefs.h>
#include <TranslationUtils.h>
#include <TranslatorRoster.h>
 
#include "ImageCache.h"
#include "ProgressWindow.h"
#include "ShowImageApp.h"
#include "ShowImageConstants.h"
#include "ShowImageStatusView.h"
#include "ShowImageView.h"
#include "ToolBarIcons.h"
 
 
// BMessage field names used in Save messages
const char* kTypeField = "be:type";
const char* kTranslatorField = "be:translator";
 
const bigtime_t kDefaultSlideShowDelay = 3000000;
	// 3 seconds
 
 
// message constants
enum {
	MSG_CAPTURE_MOUSE			= 'mCPM',
	MSG_CHANGE_FOCUS			= 'mCFS',
	MSG_WINDOW_QUIT				= 'mWQT',
	MSG_OUTPUT_TYPE				= 'BTMN',
	MSG_SAVE_PANEL				= 'mFSP',
	MSG_CLEAR_SELECT			= 'mCSL',
	MSG_SELECT_ALL				= 'mSAL',
	MSG_SELECTION_MODE			= 'mSLM',
	MSG_PAGE_FIRST				= 'mPGF',
	MSG_PAGE_LAST				= 'mPGL',
	MSG_PAGE_NEXT				= 'mPGN',
	MSG_PAGE_PREV				= 'mPGP',
	MSG_GOTO_PAGE				= 'mGTP',
	MSG_ZOOM_IN					= 'mZIN',
	MSG_ZOOM_OUT				= 'mZOU',
	MSG_SCALE_BILINEAR			= 'mSBL',
	MSG_DESKTOP_BACKGROUND		= 'mDBG',
	MSG_ROTATE_90				= 'mR90',
	MSG_ROTATE_270				= 'mR27',
	MSG_FLIP_LEFT_TO_RIGHT		= 'mFLR',
	MSG_FLIP_TOP_TO_BOTTOM		= 'mFTB',
	MSG_SLIDE_SHOW_DELAY		= 'mSSD',
	MSG_SHOW_CAPTION			= 'mSCP',
	MSG_PAGE_SETUP				= 'mPSU',
	MSG_PREPARE_PRINT			= 'mPPT',
	MSG_GET_INFO				= 'mGFI',
	MSG_SET_RATING				= 'mSRT',
	kMsgFitToWindow				= 'mFtW',
	kMsgOriginalSize			= 'mOSZ',
	kMsgStretchToWindow			= 'mStW',
	kMsgNextSlide				= 'mNxS',
	kMsgToggleToolBar			= 'mTTB',
	kMsgSlideToolBar			= 'mSTB',
	kMsgFinishSlidingToolBar	= 'mFST'
};
 
 
// This is temporary solution for building BString with printf like format.
// will be removed in the future.
static void
bs_printf(BString* string, const char* format, ...)
{
	va_list ap;
	char* buf;
 
	va_start(ap, format);
	vasprintf(&buf, format, ap);
	string->SetTo(buf);
	free(buf);
	va_end(ap);
}
 
 
//	#pragma mark -- ShowImageWindow
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Menus"
 
 
ShowImageWindow::ShowImageWindow(BRect frame, const entry_ref& ref,
	const BMessenger& trackerMessenger)
	:
	BWindow(frame, "", B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
	fNavigator(ref, trackerMessenger),
	fSavePanel(NULL),
	fBar(NULL),
	fBrowseMenu(NULL),
	fGoToPageMenu(NULL),
	fSlideShowDelayMenu(NULL),
	fToolBar(NULL),
	fImageView(NULL),
	fStatusView(NULL),
	fProgressWindow(new ProgressWindow()),
	fModified(false),
	fFullScreen(false),
	fShowCaption(true),
	fShowToolBar(true),
	fPrintSettings(NULL),
	fSlideShowRunner(NULL),
	fSlideShowDelay(kDefaultSlideShowDelay)
{
	_ApplySettings();
 
	SetLayout(new BGroupLayout(B_VERTICAL, 0));
 
	// create menu bar
	fBar = new BMenuBar("menu_bar");
	_AddMenus(fBar);
	float menuBarMinWidth = fBar->MinSize().width;
	AddChild(fBar);
 
	// Add a content view so the tool bar can be moved outside of the
	// visible portion without colliding with the menu bar.
 
	BView* contentView = new BView(BRect(), "content", B_FOLLOW_NONE, 0);
	contentView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	contentView->SetExplicitMinSize(BSize(250, 100));
	AddChild(contentView);
 
	// Create the tool bar
	BRect viewFrame = contentView->Bounds();
	viewFrame.right -= B_V_SCROLL_BAR_WIDTH;
	fToolBar = new BToolBar(viewFrame);
 
	// Add the tool icons.
 
//	fToolBar->AddAction(MSG_FILE_OPEN, be_app,
//		tool_bar_icon(kIconDocumentOpen), B_TRANSLATE("Open" B_UTF8_ELLIPSIS));
	fToolBar->AddAction(MSG_FILE_PREV, this,
		tool_bar_icon(kIconGoPrevious), B_TRANSLATE("Previous file"));
	fToolBar->AddAction(MSG_FILE_NEXT, this, tool_bar_icon(kIconGoNext),
		B_TRANSLATE("Next file"));
	BMessage* fullScreenSlideShow = new BMessage(MSG_SLIDE_SHOW);
	fullScreenSlideShow->AddBool("full screen", true);
	fToolBar->AddAction(fullScreenSlideShow, this,
		tool_bar_icon(kIconMediaMovieLibrary), B_TRANSLATE("Slide show"));
	fToolBar->AddSeparator();
	fToolBar->AddAction(MSG_SELECTION_MODE, this,
		tool_bar_icon(kIconDrawRectangularSelection),
		B_TRANSLATE("Selection mode"));
	fToolBar->AddSeparator();
	fToolBar->AddAction(kMsgOriginalSize, this,
		tool_bar_icon(kIconZoomOriginal), B_TRANSLATE("Original size"), NULL,
		true);
	fToolBar->AddAction(kMsgFitToWindow, this,
		tool_bar_icon(kIconZoomFitBest), B_TRANSLATE("Fit to window"));
	fToolBar->AddAction(MSG_ZOOM_IN, this, tool_bar_icon(kIconZoomIn),
		B_TRANSLATE("Zoom in"));
	fToolBar->AddAction(MSG_ZOOM_OUT, this, tool_bar_icon(kIconZoomOut),
		B_TRANSLATE("Zoom out"));
	fToolBar->AddSeparator();
	fToolBar->AddAction(MSG_PAGE_PREV, this, tool_bar_icon(kIconPagePrevious),
		B_TRANSLATE("Previous page"));
	fToolBar->AddAction(MSG_PAGE_NEXT, this, tool_bar_icon(kIconPageNext),
		B_TRANSLATE("Next page"));
	fToolBar->AddGlue();
	fToolBar->AddAction(MSG_FULL_SCREEN, this,
		tool_bar_icon(kIconViewWindowed), B_TRANSLATE("Leave full screen"));
	fToolBar->SetActionVisible(MSG_FULL_SCREEN, false);
 
	fToolBar->ResizeTo(viewFrame.Width(), fToolBar->MinSize().height);
 
	contentView->AddChild(fToolBar);
 
	if (fShowToolBar)
		viewFrame.top = fToolBar->Frame().bottom + 1;
	else
		fToolBar->Hide();
 
	fToolBarVisible = fShowToolBar;
 
	viewFrame.bottom = contentView->Bounds().bottom;
	viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT;
 
	// create the image view
	fImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL,
		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED
			| B_FRAME_EVENTS);
	// wrap a scroll view around the view
	fScrollView = new BScrollView("image_scroller", fImageView,
		B_FOLLOW_ALL, 0, true, true, B_PLAIN_BORDER);
	contentView->AddChild(fScrollView);
 
	fStatusView = new ShowImageStatusView(fScrollView);
	fScrollView->AddChild(fStatusView);
 
	// Update minimum window size
	float toolBarMinWidth = fToolBar->MinSize().width;
	SetSizeLimits(std::max(menuBarMinWidth, toolBarMinWidth), 100000, 100,
		100000);
 
	// finish creating the window
	if (_LoadImage() != B_OK) {
		_LoadError(ref);
		Quit();
		return;
	}
 
	// add View menu here so it can access ShowImageView methods
	BMenu* menu = new BMenu(B_TRANSLATE_CONTEXT("View", "Menus"));
	_BuildViewMenu(menu, false);
	fBar->AddItem(menu);
 
	fBar->AddItem(_BuildRatingMenu());
 
	SetPulseRate(100000);
		// every 1/10 second; ShowImageView needs it for marching ants
 
	_MarkMenuItem(menu, MSG_SELECTION_MODE,
		fImageView->IsSelectionModeEnabled());
 
	// Tell application object to query the clipboard
	// and tell this window if it contains interesting data or not
	be_app_messenger.SendMessage(B_CLIPBOARD_CHANGED);
 
	// The window will be shown on screen automatically
	Run();
}
 
 
ShowImageWindow::~ShowImageWindow()
{
	fProgressWindow->Lock();
	fProgressWindow->Quit();
 
	_StopSlideShow();
}
 
 
void
ShowImageWindow::BuildContextMenu(BMenu* menu)
{
	_BuildViewMenu(menu, true);
}
 
 
void
ShowImageWindow::_BuildViewMenu(BMenu* menu, bool popupMenu)
{
	_AddItemMenu(menu, B_TRANSLATE("Slide show"), MSG_SLIDE_SHOW, 0, 0, this);
	_MarkMenuItem(menu, MSG_SLIDE_SHOW, fSlideShowRunner != NULL);
	BMenu* delayMenu = new BMenu(B_TRANSLATE("Slide delay"));
	if (fSlideShowDelayMenu == NULL)
		fSlideShowDelayMenu = delayMenu;
 
	delayMenu->SetRadioMode(true);
 
	int32 kDelays[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 20};
	for (uint32 i = 0; i < sizeof(kDelays) / sizeof(kDelays[0]); i++) {
		BString text;
		BDurationFormat format;
		text.Truncate(0);
		format.Format(text, 0, kDelays[i] * 1000000LL);
 
		_AddDelayItem(delayMenu, text.String(), kDelays[i] * 1000000LL);
	}
	menu->AddItem(delayMenu);
 
	menu->AddSeparatorItem();
 
	_AddItemMenu(menu, B_TRANSLATE("Original size"),
		kMsgOriginalSize, '1', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Fit to window"),
		kMsgFitToWindow, '0', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Zoom in"), MSG_ZOOM_IN, '+', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Zoom out"), MSG_ZOOM_OUT, '-', 0, this);
 
	menu->AddSeparatorItem();
 
	if (!popupMenu || fFullScreen) {
		_AddItemMenu(menu, B_TRANSLATE("High-quality zooming"),
			MSG_SCALE_BILINEAR, 0, 0, this);
		_AddItemMenu(menu, B_TRANSLATE("Stretch to window"),
			kMsgStretchToWindow, 0, 0, this);
 
		menu->AddSeparatorItem();
	}
 
	_AddItemMenu(menu, B_TRANSLATE("Full screen"),
		MSG_FULL_SCREEN, B_ENTER, 0, this);
	_MarkMenuItem(menu, MSG_FULL_SCREEN, fFullScreen);
 
	_AddItemMenu(menu, B_TRANSLATE("Show caption in full screen mode"),
		MSG_SHOW_CAPTION, 0, 0, this);
	_MarkMenuItem(menu, MSG_SHOW_CAPTION, fShowCaption);
 
	_MarkMenuItem(menu, MSG_SCALE_BILINEAR, fImageView->ScaleBilinear());
	_MarkMenuItem(menu, kMsgStretchToWindow, fImageView->StretchesToBounds());
 
	if (!popupMenu) {
		_AddItemMenu(menu, B_TRANSLATE("Show tool bar"), kMsgToggleToolBar,
			'B', 0, this);
		_MarkMenuItem(menu, kMsgToggleToolBar,
			!fToolBar->IsHidden(fToolBar));
	}
 
	if (popupMenu) {
		menu->AddSeparatorItem();
		_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
			MSG_DESKTOP_BACKGROUND, 0, 0, this);
	}
}
 
 
BMenu*
ShowImageWindow::_BuildRatingMenu()
{
	fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
	for (int32 i = 1; i <= 10; i++) {
		BString label;
		label << i;
		BMessage* message = new BMessage(MSG_SET_RATING);
		message->AddInt32("rating", i);
		fRatingMenu->AddItem(new BMenuItem(label.String(), message));
	}
	// NOTE: We may want to encapsulate the Rating menu within a more
	// general "Attributes" menu.
	return fRatingMenu;
}
 
 
void
ShowImageWindow::_AddMenus(BMenuBar* bar)
{
	BMenu* menu = new BMenu(B_TRANSLATE("File"));
 
	// Add recent files to "Open File" entry as sub-menu.
	BMenuItem* item = new BMenuItem(BRecentFilesList::NewFileListMenu(
		B_TRANSLATE("Open" B_UTF8_ELLIPSIS), NULL, NULL, be_app, 10, true,
		NULL, kApplicationSignature), new BMessage(MSG_FILE_OPEN));
	item->SetShortcut('O', 0);
	item->SetTarget(be_app);
	menu->AddItem(item);
	menu->AddSeparatorItem();
 
	BMenu* menuSaveAs = new BMenu(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS),
		B_ITEMS_IN_COLUMN);
	BTranslationUtils::AddTranslationItems(menuSaveAs, B_TRANSLATOR_BITMAP);
		// Fill Save As submenu with all types that can be converted
		// to from the Be bitmap image format
	menu->AddItem(menuSaveAs);
	_AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Move to Trash"), kMsgDeleteCurrentFile, 'T', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Get info" B_UTF8_ELLIPSIS),
		MSG_GET_INFO, 'I', 0, this);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
		MSG_PAGE_SETUP, 0, 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS),
		MSG_PREPARE_PRINT, 'P', 0, this);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app);
	bar->AddItem(menu);
 
	menu = new BMenu(B_TRANSLATE("Edit"));
	_AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Selection mode"), MSG_SELECTION_MODE, 0, 0,
		this);
	_AddItemMenu(menu, B_TRANSLATE("Clear selection"),
		MSG_CLEAR_SELECT, 0, 0, this, false);
	_AddItemMenu(menu, B_TRANSLATE("Select all"),
		MSG_SELECT_ALL, 'A', 0, this);
	bar->AddItem(menu);
 
	menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse"));
	_AddItemMenu(menu, B_TRANSLATE("First page"),
		MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this);
	_AddItemMenu(menu, B_TRANSLATE("Last page"),
		MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this);
	_AddItemMenu(menu, B_TRANSLATE("Previous page"),
		MSG_PAGE_PREV, B_LEFT_ARROW, 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Next page"),
		MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this);
	fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page"));
	fGoToPageMenu->SetRadioMode(true);
	menu->AddItem(fGoToPageMenu);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Previous file"),
		MSG_FILE_PREV, B_UP_ARROW, 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Next file"),
		MSG_FILE_NEXT, B_DOWN_ARROW, 0, this);
	bar->AddItem(menu);
 
	menu = new BMenu(B_TRANSLATE("Image"));
	_AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"),
		MSG_ROTATE_90, 'R', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"),
		MSG_ROTATE_270, 'R', B_SHIFT_KEY, this);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Flip left to right"),
		MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"),
		MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this);
	menu->AddSeparatorItem();
	_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
		MSG_DESKTOP_BACKGROUND, 0, 0, this);
 
	bar->AddItem(menu);
}
 
 
BMenuItem*
ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what,
	char shortcut, uint32 modifier, const BHandler* target, bool enabled)
{
	BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut,
		modifier);
	menu->AddItem(item);
 
	item->SetTarget(target);
	item->SetEnabled(enabled);
 
	return item;
}
 
 
BMenuItem*
ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, bigtime_t delay)
{
	BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY);
	message->AddInt64("delay", delay);
 
	BMenuItem* item = new BMenuItem(label, message, 0);
	item->SetTarget(this);
 
	if (delay == fSlideShowDelay)
		item->SetMarked(true);
 
	menu->AddItem(item);
	return item;
}
 
 
void
ShowImageWindow::_ResizeWindowToImage()
{
	BBitmap* bitmap = fImageView->Bitmap();
	BScreen screen;
	if (bitmap == NULL || !screen.IsValid())
		return;
 
	// TODO: use View::GetPreferredSize() instead?
	BRect r(bitmap->Bounds());
	float width = r.Width() + B_V_SCROLL_BAR_WIDTH;
	float height = r.Height() + 1 + fBar->Frame().Height()
		+ B_H_SCROLL_BAR_HEIGHT;
 
	BRect frame = screen.Frame();
	const float windowBorder = 5;
	// dimensions so that window does not reach outside of screen
	float maxWidth = frame.Width() + 1 - windowBorder - Frame().left;
	float maxHeight = frame.Height() + 1 - windowBorder - Frame().top;
 
	// We have to check size limits manually, otherwise
	// menu bar will be too short for small images.
 
	float minW, maxW, minH, maxH;
	GetSizeLimits(&minW, &maxW, &minH, &maxH);
	if (maxWidth > maxW)
		maxWidth = maxW;
	if (maxHeight > maxH)
		maxHeight = maxH;
	if (width < minW)
		width = minW;
	if (height < minH)
		height = minH;
 
	if (width > maxWidth)
		width = maxWidth;
	if (height > maxHeight)
		height = maxHeight;
 
	ResizeTo(width, height);
}
 
 
bool
ShowImageWindow::_ToggleMenuItem(uint32 what)
{
	bool marked = false;
	BMenuItem* item = fBar->FindItem(what);
	if (item != NULL) {
		marked = !item->IsMarked();
		item->SetMarked(marked);
	}
	fToolBar->SetActionPressed(what, marked);
	return marked;
}
 
 
void
ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable)
{
	BMenuItem* item = menu->FindItem(what);
	if (item && item->IsEnabled() != enable)
		item->SetEnabled(enable);
	fToolBar->SetActionEnabled(what, enable);
}
 
 
void
ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked)
{
	BMenuItem* item = menu->FindItem(what);
	if (item && item->IsMarked() != marked)
		item->SetMarked(marked);
	fToolBar->SetActionPressed(what, marked);
}
 
 
void
ShowImageWindow::_MarkSlideShowDelay(bigtime_t delay)
{
	const int32 count = fSlideShowDelayMenu->CountItems();
	for (int32 i = 0; i < count; i ++) {
		BMenuItem* item = fSlideShowDelayMenu->ItemAt(i);
		if (item != NULL) {
			bigtime_t itemDelay;
			if (item->Message()->FindInt64("delay", &itemDelay) == B_OK
				&& itemDelay == delay) {
				item->SetMarked(true);
				return;
			}
		}
	}
}
 
 
void
ShowImageWindow::Zoom(BPoint origin, float width, float height)
{
	_ToggleFullScreen();
}
 
 
void
ShowImageWindow::MessageReceived(BMessage* message)
{
	if (message->WasDropped()) {
		uint32 type;
		int32 count;
		status_t status = message->GetInfo("refs", &type, &count);
		if (status == B_OK && type == B_REF_TYPE) {
			message->what = B_REFS_RECEIVED;
			be_app->PostMessage(message);
		}
	}
 
	switch (message->what) {
		case kMsgImageCacheImageLoaded:
		{
			fProgressWindow->Stop();
 
			BitmapOwner* bitmapOwner = NULL;
			message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
 
			bool first = fImageView->Bitmap() == NULL;
			entry_ref ref;
			message->FindRef("ref", &ref);
			if (!first && ref != fNavigator.CurrentRef()) {
				// ignore older images
				if (bitmapOwner != NULL)
					bitmapOwner->ReleaseReference();
				break;
			}
 
			int32 page = message->FindInt32("page");
			int32 pageCount = message->FindInt32("pageCount");
			if (!first && page != fNavigator.CurrentPage()) {
				// ignore older pages
				if (bitmapOwner != NULL)
					bitmapOwner->ReleaseReference();
				break;
			}
 
			status_t status = fImageView->SetImage(message);
			if (status != B_OK) {
				if (bitmapOwner != NULL)
					bitmapOwner->ReleaseReference();
 
				_LoadError(ref);
 
				// quit if file could not be opened
				if (first)
					Quit();
				break;
			}
 
			fImageType = message->FindString("type");
			fNavigator.SetTo(ref, page, pageCount);
 
			fImageView->FitToBounds();
			if (first) {
				fImageView->MakeFocus(true);
					// to receive key messages
				Show();
			}
			_UpdateRatingMenu();
			// Set width and height attributes of the currently showed file.
			// This should only be a temporary solution.
			_SaveWidthAndHeight();
			break;
		}
 
		case kMsgImageCacheProgressUpdate:
		{
			entry_ref ref;
			if (message->FindRef("ref", &ref) == B_OK
				&& ref == fNavigator.CurrentRef()) {
				message->what = kMsgProgressUpdate;
				fProgressWindow->PostMessage(message);
			}
			break;
		}
 
		case MSG_MODIFIED:
			// If image has been modified due to a Cut or Paste
			fModified = true;
			break;
 
		case MSG_OUTPUT_TYPE:
			// User clicked Save As then choose an output format
			if (!fSavePanel)
				// If user doesn't already have a save panel open
				_SaveAs(message);
			break;
 
		case MSG_SAVE_PANEL:
			// User specified where to save the output image
			_SaveToFile(message);
			break;
 
		case B_CANCEL:
			delete fSavePanel;
			fSavePanel = NULL;
			break;
 
		case MSG_UPDATE_STATUS:
		{
			int32 pages = fNavigator.PageCount();
			int32 currentPage = fNavigator.CurrentPage();
 
			_EnableMenuItem(fBar, MSG_PAGE_FIRST,
				fNavigator.HasPreviousPage());
			_EnableMenuItem(fBar, MSG_PAGE_LAST, fNavigator.HasNextPage());
			_EnableMenuItem(fBar, MSG_PAGE_NEXT, fNavigator.HasNextPage());
			_EnableMenuItem(fBar, MSG_PAGE_PREV, fNavigator.HasPreviousPage());
			fGoToPageMenu->SetEnabled(pages > 1);
 
			_EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile());
			_EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile());
 
			if (fGoToPageMenu->CountItems() != pages) {
				// Only rebuild the submenu if the number of
				// pages is different
 
				while (fGoToPageMenu->CountItems() > 0) {
					// Remove all page numbers
					delete fGoToPageMenu->RemoveItem((int32)0);
				}
 
				for (int32 i = 1; i <= pages; i++) {
					// Fill Go To page submenu with an entry for each page
					BMessage* goTo = new BMessage(MSG_GOTO_PAGE);
					goTo->AddInt32("page", i);
 
					char shortcut = 0;
					if (i < 10)
						shortcut = '0' + i;
 
					BString strCaption;
					strCaption << i;
 
					BMenuItem* item = new BMenuItem(strCaption.String(), goTo,
						shortcut, B_SHIFT_KEY);
					if (currentPage == i)
						item->SetMarked(true);
					fGoToPageMenu->AddItem(item);
				}
			} else {
				// Make sure the correct page is marked
				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
				if (currentItem != NULL && !currentItem->IsMarked())
					currentItem->SetMarked(true);
			}
 
			_UpdateStatusText(message);
 
			BPath path(fImageView->Image());
			SetTitle(path.Path());
			break;
		}
 
		case MSG_UPDATE_STATUS_TEXT:
		{
			_UpdateStatusText(message);
			break;
		}
 
		case MSG_SELECTION:
		{
			// The view sends this message when a selection is
			// made or the selection is cleared so that the window
			// can update the state of the appropriate menu items
			bool enable;
			if (message->FindBool("has_selection", &enable) == B_OK) {
				_EnableMenuItem(fBar, B_COPY, enable);
				_EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable);
			}
			break;
		}
 
		case B_COPY:
			fImageView->CopySelectionToClipboard();
			break;
 
		case MSG_SELECTION_MODE:
		{
			bool selectionMode = _ToggleMenuItem(MSG_SELECTION_MODE);
			fImageView->SetSelectionMode(selectionMode);
			if (!selectionMode)
				fImageView->ClearSelection();
			break;
		}
 
		case MSG_CLEAR_SELECT:
			fImageView->ClearSelection();
			break;
 
		case MSG_SELECT_ALL:
			fImageView->SelectAll();
			break;
 
		case MSG_PAGE_FIRST:
			if (_ClosePrompt() && fNavigator.FirstPage())
				_LoadImage();
			break;
 
		case MSG_PAGE_LAST:
			if (_ClosePrompt() && fNavigator.LastPage())
				_LoadImage();
			break;
 
		case MSG_PAGE_NEXT:
			if (_ClosePrompt() && fNavigator.NextPage())
				_LoadImage();
			break;
 
		case MSG_PAGE_PREV:
			if (_ClosePrompt() && fNavigator.PreviousPage())
				_LoadImage();
			break;
 
		case MSG_GOTO_PAGE:
		{
			if (!_ClosePrompt())
				break;
 
			int32 newPage;
			if (message->FindInt32("page", &newPage) != B_OK)
				break;
 
			int32 currentPage = fNavigator.CurrentPage();
			int32 pages = fNavigator.PageCount();
 
			// TODO: use radio mode instead!
			if (newPage > 0 && newPage <= pages) {
				BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1);
				BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1);
				if (currentItem != NULL && newItem != NULL) {
					currentItem->SetMarked(false);
					newItem->SetMarked(true);
					if (fNavigator.GoToPage(newPage))
						_LoadImage();
				}
			}
			break;
		}
 
		case kMsgFitToWindow:
			fImageView->FitToBounds();
			break;
 
		case kMsgStretchToWindow:
			fImageView->SetStretchToBounds(
				_ToggleMenuItem(kMsgStretchToWindow));
			break;
 
		case MSG_FILE_PREV:
			if (_ClosePrompt() && fNavigator.PreviousFile())
				_LoadImage(false);
			break;
 
		case MSG_FILE_NEXT:
		case kMsgNextSlide:
			if (_ClosePrompt() && fNavigator.NextFile())
				_LoadImage();
			break;
 
		case kMsgDeleteCurrentFile:
		{
			if (fNavigator.MoveFileToTrash())
				_LoadImage();
			else
				PostMessage(B_QUIT_REQUESTED);
			break;
		}
 
		case MSG_ROTATE_90:
			fImageView->Rotate(90);
			break;
 
		case MSG_ROTATE_270:
			fImageView->Rotate(270);
			break;
 
		case MSG_FLIP_LEFT_TO_RIGHT:
			fImageView->Flip(true);
			break;
 
		case MSG_FLIP_TOP_TO_BOTTOM:
			fImageView->Flip(false);
			break;
 
		case MSG_GET_INFO:
			_GetFileInfo(fNavigator.CurrentRef());
			break;
 
		case MSG_SLIDE_SHOW:
		{
			bool fullScreen = false;
			message->FindBool("full screen", &fullScreen);
 
			BMenuItem* item = fBar->FindItem(message->what);
			if (item == NULL)
				break;
 
			if (item->IsMarked()) {
				item->SetMarked(false);
				_StopSlideShow();
				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
			} else if (_ClosePrompt()) {
				item->SetMarked(true);
				if (!fFullScreen && fullScreen)
					_ToggleFullScreen();
				_StartSlideShow();
				fToolBar->SetActionPressed(MSG_SLIDE_SHOW, true);
			}
			break;
		}
 
		case kMsgStopSlideShow:
		{
			BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW);
			if (item != NULL)
				item->SetMarked(false);
 
			_StopSlideShow();
			fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false);
			break;
		}
 
		case MSG_SLIDE_SHOW_DELAY:
		{
			bigtime_t delay;
			if (message->FindInt64("delay", &delay) == B_OK) {
				_SetSlideShowDelay(delay);
				// in case message is sent from popup menu
				_MarkSlideShowDelay(delay);
			}
			break;
		}
 
		case MSG_FULL_SCREEN:
			_ToggleFullScreen();
			break;
 
		case MSG_EXIT_FULL_SCREEN:
			if (fFullScreen)
				_ToggleFullScreen();
			break;
 
		case MSG_SHOW_CAPTION:
		{
			fShowCaption = _ToggleMenuItem(message->what);
			ShowImageSettings* settings = my_app->Settings();
 
			if (settings->Lock()) {
				settings->SetBool("ShowCaption", fShowCaption);
				settings->Unlock();
			}
			if (fFullScreen)
				fImageView->SetShowCaption(fShowCaption);
		}	break;
 
		case MSG_PAGE_SETUP:
			_PageSetup();
			break;
 
		case MSG_PREPARE_PRINT:
			_PrepareForPrint();
			break;
 
		case MSG_PRINT:
			_Print(message);
			break;
 
		case MSG_ZOOM_IN:
			fImageView->ZoomIn();
			break;
 
		case MSG_ZOOM_OUT:
			fImageView->ZoomOut();
			break;
 
		case MSG_UPDATE_STATUS_ZOOM:
			fStatusView->SetZoom(fImageView->Zoom());
			break;
 
		case kMsgOriginalSize:
			if (message->FindInt32("behavior") == BButton::B_TOGGLE_BEHAVIOR) {
				bool force = (message->FindInt32("be:value") == B_CONTROL_ON);
				fImageView->ForceOriginalSize(force);
				if (!force)
					break;
			}
			fImageView->SetZoom(1.0);
			break;
 
		case MSG_SCALE_BILINEAR:
			fImageView->SetScaleBilinear(_ToggleMenuItem(message->what));
			break;
 
		case MSG_DESKTOP_BACKGROUND:
		{
			BMessage backgroundsMessage(B_REFS_RECEIVED);
			backgroundsMessage.AddRef("refs", fImageView->Image());
			// This is used in the Backgrounds code for scaled placement
			backgroundsMessage.AddInt32("placement", 'scpl');
			be_roster->Launch("application/x-vnd.haiku-backgrounds",
				&backgroundsMessage);
			break;
		}
 
		case MSG_SET_RATING:
		{
			int32 rating;
			if (message->FindInt32("rating", &rating) != B_OK)
				break;
			BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY);
			if (file.InitCheck() != B_OK)
				break;
			file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating,
				sizeof(rating));
			_UpdateRatingMenu();
			break;
		}
 
		case kMsgToggleToolBar:
		{
			fShowToolBar = _ToggleMenuItem(message->what);
			_SetToolBarVisible(fShowToolBar, true);
 
			ShowImageSettings* settings = my_app->Settings();
 
			if (settings->Lock()) {
				settings->SetBool("ShowToolBar", fShowToolBar);
				settings->Unlock();
			}
			break;
		}
		case kShowToolBarIfEnabled:
		{
			bool show;
			if (message->FindBool("show", &show) != B_OK)
				break;
			_SetToolBarVisible(fShowToolBar && show, true);
			break;
		}
		case kMsgSlideToolBar:
		{
			float offset;
			if (message->FindFloat("offset", &offset) == B_OK) {
				fToolBar->MoveBy(0, offset);
				fScrollView->ResizeBy(0, -offset);
				fScrollView->MoveBy(0, offset);
				UpdateIfNeeded();
				snooze(15000);
			}
			break;
		}
		case kMsgFinishSlidingToolBar:
		{
			float offset;
			bool show;
			if (message->FindFloat("offset", &offset) == B_OK
				&& message->FindBool("show", &show) == B_OK) {
				// Compensate rounding errors with the final placement
				fToolBar->MoveTo(fToolBar->Frame().left, offset);
				if (!show)
					fToolBar->Hide();
				BRect frame = fToolBar->Parent()->Bounds();
				frame.top = fToolBar->Frame().bottom + 1;
				fScrollView->MoveTo(fScrollView->Frame().left, frame.top);
				fScrollView->ResizeTo(fScrollView->Bounds().Width(),
					frame.Height() + 1);
			}
			break;
		}
 
		default:
			BWindow::MessageReceived(message);
			break;
	}
}
 
 
void
ShowImageWindow::_GetFileInfo(const entry_ref& ref)
{
	BMessage message('Tinf');
	BMessenger tracker("application/x-vnd.Be-TRAK");
	message.AddRef("refs", &ref);
	tracker.SendMessage(&message);
}
 
 
void
ShowImageWindow::_UpdateStatusText(const BMessage* message)
{
	BString frameText;
	if (fImageView->Bitmap() != NULL) {
		BRect bounds = fImageView->Bitmap()->Bounds();
		frameText << bounds.IntegerWidth() + 1
			<< "x" << bounds.IntegerHeight() + 1;
	}
	BString pages;
	if (fNavigator.PageCount() > 1)
		pages << fNavigator.CurrentPage() << "/" << fNavigator.PageCount();
	fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType,
		fImageView->Zoom());
}
 
 
void
ShowImageWindow::_LoadError(const entry_ref& ref)
{
	// TODO: give a better error message!
	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"),
		B_TRANSLATE_CONTEXT("Could not load image! Either the "
			"file or an image translator for it does not exist.",
			"LoadAlerts"),
		B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL,
		B_WIDTH_AS_USUAL, B_STOP_ALERT);
	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
	alert->Go();
}
 
 
void
ShowImageWindow::_SaveAs(BMessage* message)
{
	// Read the translator and output type the user chose
	translator_id outTranslator;
	uint32 outType;
	if (message->FindInt32(kTranslatorField,
			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
		|| message->FindInt32(kTypeField,
			reinterpret_cast<int32 *>(&outType)) != B_OK)
		return;
 
	// Add the chosen translator and output type to the
	// message that the save panel will send back
	BMessage panelMsg(MSG_SAVE_PANEL);
	panelMsg.AddInt32(kTranslatorField, outTranslator);
	panelMsg.AddInt32(kTypeField, outType);
 
	// Create save panel and show it
	BMessenger target(this);
	fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL,
		&target, NULL, 0, false, &panelMsg);
	if (!fSavePanel)
		return;
 
	// Retrieve save directory from settings;
	ShowImageSettings* settings = my_app->Settings();
	if (settings->Lock()) {
		fSavePanel->SetPanelDirectory(
			settings->GetString("SaveDirectory", NULL));
		settings->Unlock();
	}
 
	fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE);
	fSavePanel->Show();
}
 
 
void
ShowImageWindow::_SaveToFile(BMessage* message)
{
	// Read in where the file should be saved
	entry_ref dirRef;
	if (message->FindRef("directory", &dirRef) != B_OK)
		return;
 
	const char* filename;
	if (message->FindString("name", &filename) != B_OK)
		return;
 
	// Read in the translator and type to be used
	// to save the output image
	translator_id outTranslator;
	uint32 outType;
	if (message->FindInt32(kTranslatorField,
			reinterpret_cast<int32 *>(&outTranslator)) != B_OK
		|| message->FindInt32(kTypeField,
			reinterpret_cast<int32 *>(&outType)) != B_OK)
		return;
 
	// Find the translator_format information needed to
	// write a MIME attribute for the image file
	BTranslatorRoster* roster = BTranslatorRoster::Default();
	const translation_format* outFormat = NULL;
	int32 outCount = 0;
	if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK
		|| outCount < 1)
		return;
 
	int32 i;
	for (i = 0; i < outCount; i++) {
		if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type
				== outType)
			break;
	}
	if (i == outCount)
		return;
 
	// Write out the image file
	BDirectory dir(&dirRef);
	fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]);
 
	// Store Save directory in settings;
	ShowImageSettings* settings = my_app->Settings();
	if (settings->Lock()) {
		BPath path(&dirRef);
		settings->SetString("SaveDirectory", path.Path());
		settings->Unlock();
	}
}
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ClosePrompt"
 
 
bool
ShowImageWindow::_ClosePrompt()
{
	if (!fModified)
		return true;
 
	int32 count = fNavigator.PageCount();
	int32 page = fNavigator.CurrentPage();
	BString prompt;
 
	if (count > 1) {
		bs_printf(&prompt,
			B_TRANSLATE("The document '%s' (page %d) has been changed. Do you "
				"want to close the document?"),
			fImageView->Image()->name, page);
	} else {
		bs_printf(&prompt,
			B_TRANSLATE("The document '%s' has been changed. Do you want to "
				"close the document?"),
			fImageView->Image()->name);
	}
 
	BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(),
		B_TRANSLATE("Cancel"), B_TRANSLATE("Close"));
	alert->SetShortcut(0, B_ESCAPE);
 
	if (alert->Go() == 0) {
		// Cancel
		return false;
	}
 
	// Close
	fModified = false;
	return true;
}
 
 
status_t
ShowImageWindow::_LoadImage(bool forward)
{
	BMessenger us(this);
	status_t status = my_app->DefaultCache().RetrieveImage(
		fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us);
	if (status != B_OK)
		return status;
 
	fProgressWindow->Start(this);
 
	// Preload previous/next images - two in the navigation direction, one
	// in the opposite direction.
 
	entry_ref nextRef = fNavigator.CurrentRef();
	if (_PreloadImage(forward, nextRef))
		_PreloadImage(forward, nextRef);
 
	entry_ref previousRef = fNavigator.CurrentRef();
	_PreloadImage(!forward, previousRef);
 
	return B_OK;
}
 
 
bool
ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref)
{
	entry_ref currentRef = ref;
	if ((forward && !fNavigator.GetNextFile(currentRef, ref))
		|| (!forward && !fNavigator.GetPreviousFile(currentRef, ref)))
		return false;
 
	return my_app->DefaultCache().RetrieveImage(ref) == B_OK;
}
 
 
void
ShowImageWindow::_ToggleFullScreen()
{
	BRect frame;
	fFullScreen = !fFullScreen;
	if (fFullScreen) {
		BScreen screen;
		fWindowFrame = Frame();
		frame = screen.Frame();
		frame.top -= fBar->Bounds().Height() + 1;
		frame.right += B_V_SCROLL_BAR_WIDTH;
		frame.bottom += B_H_SCROLL_BAR_HEIGHT;
 
		SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE);
 
		Activate();
			// make the window frontmost
	} else {
		frame = fWindowFrame;
 
		SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
	}
 
	fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen);
	_SetToolBarVisible(!fFullScreen && fShowToolBar);
	_SetToolBarBorder(!fFullScreen);
 
	MoveTo(frame.left, frame.top);
	ResizeTo(frame.Width(), frame.Height());
 
	fImageView->SetHideIdlingCursor(fFullScreen);
	fImageView->SetShowCaption(fFullScreen && fShowCaption);
 
	Layout(false);
		// We need to manually relayout here, as the views are layouted
		// asynchronously, and FitToBounds() would still have the wrong size
	fImageView->FitToBounds();
}
 
 
void
ShowImageWindow::_ApplySettings()
{
	ShowImageSettings* settings = my_app->Settings();
 
	if (settings->Lock()) {
		fShowCaption = settings->GetBool("ShowCaption", fShowCaption);
		fPrintOptions.SetBounds(BRect(0, 0, 1023, 767));
 
		fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay);
 
		fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32(
			"PO:Option", fPrintOptions.Option()));
		fPrintOptions.SetZoomFactor(
			settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()));
		fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI()));
		fPrintOptions.SetWidth(
			settings->GetFloat("PO:Width", fPrintOptions.Width()));
		fPrintOptions.SetHeight(
			settings->GetFloat("PO:Height", fPrintOptions.Height()));
 
		fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar);
 
		settings->Unlock();
	}
}
 
 
void
ShowImageWindow::_SavePrintOptions()
{
	ShowImageSettings* settings = my_app->Settings();
 
	if (settings->Lock()) {
		settings->SetInt32("PO:Option", fPrintOptions.Option());
		settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor());
		settings->SetFloat("PO:DPI", fPrintOptions.DPI());
		settings->SetFloat("PO:Width", fPrintOptions.Width());
		settings->SetFloat("PO:Height", fPrintOptions.Height());
		settings->Unlock();
	}
}
 
 
bool
ShowImageWindow::_PageSetup()
{
	BPrintJob printJob(fImageView->Image()->name);
	if (fPrintSettings != NULL)
		printJob.SetSettings(new BMessage(*fPrintSettings));
 
	status_t status = printJob.ConfigPage();
	if (status == B_OK) {
		delete fPrintSettings;
		fPrintSettings = printJob.Settings();
	}
 
	return status == B_OK;
}
 
 
void
ShowImageWindow::_PrepareForPrint()
{
	if (fPrintSettings == NULL) {
		BPrintJob printJob(fImageView->Image()->name);
		if (printJob.ConfigJob() == B_OK)
			fPrintSettings = printJob.Settings();
	}
 
	fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds());
	fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1);
 
	new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50),
		&fPrintOptions, this);
}
 
 
void
ShowImageWindow::_Print(BMessage* msg)
{
	status_t st;
	if (msg->FindInt32("status", &st) != B_OK || st != B_OK)
		return;
 
	_SavePrintOptions();
 
	BPrintJob printJob(fImageView->Image()->name);
	if (fPrintSettings)
		printJob.SetSettings(new BMessage(*fPrintSettings));
 
	if (printJob.ConfigJob() == B_OK) {
		delete fPrintSettings;
		fPrintSettings = printJob.Settings();
 
		// first/lastPage is unused for now
		int32 firstPage = printJob.FirstPage();
		int32 lastPage = printJob.LastPage();
		BRect printableRect = printJob.PrintableRect();
 
		if (firstPage < 1)
			firstPage = 1;
		if (lastPage < firstPage)
			lastPage = firstPage;
 
		BBitmap* bitmap = fImageView->Bitmap();
		float imageWidth = bitmap->Bounds().Width() + 1.0;
		float imageHeight = bitmap->Bounds().Height() + 1.0;
 
		float width;
		switch (fPrintOptions.Option()) {
			case PrintOptions::kFitToPage: {
				float w1 = printableRect.Width() + 1;
				float w2 = imageWidth * (printableRect.Height() + 1)
					/ imageHeight;
				if (w2 < w1)
					width = w2;
				else
					width = w1;
			}	break;
			case PrintOptions::kZoomFactor:
				width = imageWidth * fPrintOptions.ZoomFactor();
				break;
			case PrintOptions::kDPI:
				width = imageWidth * 72.0 / fPrintOptions.DPI();
				break;
			case PrintOptions::kWidth:
			case PrintOptions::kHeight:
				width = fPrintOptions.Width();
				break;
 
			default:
				// keep compiler silent; should not reach here
				width = imageWidth;
		}
 
		// TODO: eventually print large images on several pages
		printJob.BeginJob();
		fImageView->SetScale(width / imageWidth);
		// coordinates are relative to printable rectangle
		BRect bounds(bitmap->Bounds());
		printJob.DrawView(fImageView, bounds, BPoint(0, 0));
		fImageView->SetScale(1.0);
		printJob.SpoolPage();
		printJob.CommitJob();
	}
}
 
 
void
ShowImageWindow::_SetSlideShowDelay(bigtime_t delay)
{
	if (fSlideShowDelay == delay)
		return;
 
	fSlideShowDelay = delay;
 
	ShowImageSettings* settings = my_app->Settings();
	if (settings->Lock()) {
		settings->SetTime("SlideShowDelay", fSlideShowDelay);
		settings->Unlock();
	}
 
	if (fSlideShowRunner != NULL)
		_StartSlideShow();
}
 
 
void
ShowImageWindow::_StartSlideShow()
{
	_StopSlideShow();
 
	BMessage nextSlide(kMsgNextSlide);
	fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay);
}
 
 
void
ShowImageWindow::_StopSlideShow()
{
	if (fSlideShowRunner != NULL) {
		delete fSlideShowRunner;
		fSlideShowRunner = NULL;
	}
}
 
 
void
ShowImageWindow::_UpdateRatingMenu()
{
	BFile file(&fNavigator.CurrentRef(), B_READ_ONLY);
	if (file.InitCheck() != B_OK)
		return;
	int32 rating;
	ssize_t size = sizeof(rating);
	if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size)
		rating = 0;
	// TODO: Finding the correct item could be more robust, like by looking
	// at the message of each item.
	for (int32 i = 1; i <= 10; i++) {
		BMenuItem* item = fRatingMenu->ItemAt(i - 1);
		if (item == NULL)
			break;
		item->SetMarked(i == rating);
	}
}
 
 
void
ShowImageWindow::_SaveWidthAndHeight()
{
	if (fNavigator.CurrentPage() != 1)
		return;
 
	if (fImageView->Bitmap() == NULL)
		return;
 
	BRect bounds = fImageView->Bitmap()->Bounds();
	int32 width = bounds.IntegerWidth() + 1;
	int32 height = bounds.IntegerHeight() + 1;
 
	BNode node(&fNavigator.CurrentRef());
	if (node.InitCheck() != B_OK)
		return;
 
	const char* kWidthAttrName = "Media:Width";
	const char* kHeightAttrName = "Media:Height";
 
	int32 widthAttr;
	ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0,
		&widthAttr, sizeof(widthAttr));
	if (attrSize <= 0 || widthAttr != width)
		node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width));
 
	int32 heightAttr;
	attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0,
		&heightAttr, sizeof(heightAttr));
	if (attrSize <= 0 || heightAttr != height)
		node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height));
}
 
 
void
ShowImageWindow::_SetToolBarVisible(bool visible, bool animate)
{
	if (visible == fToolBarVisible)
		return;
 
	fToolBarVisible = visible;
	float diff = fToolBar->Bounds().Height() + 2;
	if (!visible)
		diff = -diff;
	else
		fToolBar->Show();
 
	if (animate) {
		// Slide the controls into view. We do this with messages in order
		// not to block the window thread.
		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
		for (int32 i = 0; i < steps; i++) {
			BMessage message(kMsgSlideToolBar);
			message.AddFloat("offset", floorf(diff * kAnimationOffsets[i]));
			PostMessage(&message, this);
		}
		BMessage finalMessage(kMsgFinishSlidingToolBar);
		finalMessage.AddFloat("offset", visible ? 0 : diff);
		finalMessage.AddBool("show", visible);
		PostMessage(&finalMessage, this);
	} else {
		fScrollView->ResizeBy(0, -diff);
		fScrollView->MoveBy(0, diff);
		fToolBar->MoveBy(0, diff);
		if (!visible)
			fToolBar->Hide();
	}
}
 
 
void
ShowImageWindow::_SetToolBarBorder(bool visible)
{
	float inset = visible
		? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0;
 
	fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0);
}
 
 
bool
ShowImageWindow::QuitRequested()
{
	if (fSavePanel) {
		// Don't allow this window to be closed if a save panel is open
		return false;
	}
 
	if (!_ClosePrompt())
		return false;
 
	ShowImageSettings* settings = my_app->Settings();
	if (settings->Lock()) {
		if (fFullScreen)
			settings->SetRect("WindowFrame", fWindowFrame);
		else
			settings->SetRect("WindowFrame", Frame());
		settings->Unlock();
	}
 
	be_app->PostMessage(MSG_WINDOW_HAS_QUIT);
 
	return true;
}

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.