⛏️ index : haiku.git

/*
 * 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 <GridLayout.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 <posix/locale.h>

#include "ImageCache.h"
#include "ProgressWindow.h"
#include "ShowImageApp.h"
#include "ShowImageConstants.h"
#include "ShowImageStatusView.h"
#include "ShowImageView.h"
#include "SupportingAppsMenu.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, 0),
	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();
	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;

	// create the scroll area
	fScrollArea = new BScrollView("image_scroller", NULL, 0,
		false, false, B_PLAIN_BORDER);
	BGridLayout* gridLayout = new BGridLayout(0, 0);
	fScrollArea->SetLayout(gridLayout);
	gridLayout->SetInsets(0, 1, -1, -1);

	fScrollArea->MoveTo(viewFrame.LeftTop());
	fScrollArea->ResizeTo(viewFrame.Size());
	fScrollArea->SetResizingMode(B_FOLLOW_ALL);
	contentView->AddChild(fScrollArea);

	// create the image view
	fImageView = new ShowImageView("image_view",
		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED
			| B_FRAME_EVENTS);
	fImageView->SetExplicitMinSize(BSize(0, 0));
	gridLayout->AddView(fImageView, 0, 0, 2, 1);

	// create the scroll bars (wrapped to avoid double borders)
	fVScrollBar = new BScrollBar(NULL, NULL, 0, 0, B_VERTICAL); {
		BGroupView* vScrollBarContainer = new BGroupView(B_VERTICAL, 0);
		vScrollBarContainer->GroupLayout()->AddView(fVScrollBar);
		vScrollBarContainer->GroupLayout()->SetInsets(0, -1, 0, -1);
		gridLayout->AddView(vScrollBarContainer, 2, 0);
	}

	fHScrollBar = new BScrollBar(NULL, NULL, 0, 0, B_HORIZONTAL); {
		BGroupView* hScrollBarContainer = new BGroupView(B_VERTICAL, 0);
		hScrollBarContainer->GroupLayout()->AddView(fHScrollBar);
		hScrollBarContainer->GroupLayout()->SetInsets(0, -1, -1, -1);
		gridLayout->AddView(hScrollBarContainer, 1, 1);
	}

	fVScrollBar->SetTarget(fImageView);
	fHScrollBar->SetTarget(fImageView);

	fStatusView = new ShowImageStatusView;
	gridLayout->AddView(fStatusView, 0, 1);

	// Update minimum window size
	float toolBarMinWidth = fToolBar->MinSize().width;
	SetSizeLimits(std::max(menuBarMinWidth, toolBarMinWidth), 100000,
		fBar->MinSize().height + gridLayout->MinSize().height, 100000);

	// finish creating the window
	status_t status = _LoadImage();
	if (status != B_OK) {
		_LoadError(ref, status);
		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);

	menu = new BMenu(B_TRANSLATE_CONTEXT("Attributes", "Menus"));
	menu->AddItem(_BuildRatingMenu());
	BMessage* message = new BMessage(MSG_SET_RATING);
	message->AddInt32("rating", 0);
	fResetRatingItem = new BMenuItem(B_TRANSLATE("Reset rating"), message);
	menu->AddItem(fResetRatingItem);
	fBar->AddItem(menu);

	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* openWithMenu = new BMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS));
		_UpdateOpenWithMenu(openWithMenu);
		BMenuItem* item = new BMenuItem(openWithMenu, NULL);
		menu->AddItem(item);
	}
}


void
ShowImageWindow::_UpdateOpenWithMenu(BMenu* menu)
{
	update_supporting_apps_menu(menu, fMimeType, MSG_OPEN_WITH, this);
}


BMenu*
ShowImageWindow::_BuildRatingMenu()
{
	fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
	for (int32 i = 1; i <= 10; i++) {
		BMessage* message = new BMessage(MSG_SET_RATING);
		BString label;
		fNumberFormat.Format(label, i);
		message->AddInt32("rating", i);
		fRatingMenu->AddItem(new BMenuItem(label.String(), message));
	}

	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();

	fOpenWithMenu = new BMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS));
	item = new BMenuItem(fOpenWithMenu, NULL);
	menu->AddItem(item);

	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("Move to Trash"), kMsgDeleteCurrentFile, 'T', 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS),
		MSG_DESKTOP_BACKGROUND, 0, 0, this);
	_AddItemMenu(menu, B_TRANSLATE("Get info"), 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("Close"), B_QUIT_REQUESTED, 'W', 0, this);
	_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);

	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() + be_control_look->GetScrollBarWidth(B_VERTICAL);
	float height = r.Height() + 1 + fBar->Frame().Height()
		+ be_control_look->GetScrollBarWidth(B_HORIZONTAL);

	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, status);

				// 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();
			}

			fMimeType = new BMimeType(message->FindString("mime"));
			_UpdateOpenWithMenu(fOpenWithMenu);
			_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);

			// Disable next/previous if this is the only image in the folder.
			if (!fNavigator.HasNextFile() && !fNavigator.HasPreviousFile()) {
				_EnableMenuItem(fBar, MSG_FILE_NEXT, false);
				_EnableMenuItem(fBar, MSG_FILE_PREV, false);
			}

			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_OPEN_WITH:
		{
			BString appSig = "";
			message->FindString("signature", &appSig);
			entry_ref ref = fNavigator.CurrentRef();
			BMessage openMsg(B_REFS_RECEIVED);
			openMsg.AddRef("refs", &ref);
			be_roster->Launch(appSig.String(), &openMsg);
			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()) {
				if (!fNavigator.PreviousFile()) {
					// Wrap to last file
					fNavigator.LastFile();
				}
				_LoadImage();
			}
			break;

		case MSG_FILE_NEXT:
		case kMsgNextSlide:
			if (_ClosePrompt()) {
				if (!fNavigator.NextFile()) {
					// Wrap back around
					fNavigator.FirstFile();
				}
				_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);
				fScrollArea->ResizeBy(0, -offset);
				fScrollArea->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;
				fScrollArea->MoveTo(fScrollArea->Frame().left, frame.top);
				fScrollArea->ResizeTo(fScrollArea->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, height, width;
	if (fImageView->Bitmap() != NULL) {
		BRect bounds = fImageView->Bitmap()->Bounds();
		fNumberFormat.Format(width, bounds.IntegerWidth() + 1);
		fNumberFormat.Format(height, bounds.IntegerHeight() + 1);
		frameText.SetToFormat("%s × %s", width.String(), height.String());
	}

	BString currentPage, pageCount, pages;
	if (fNavigator.PageCount() > 1) {
		fNumberFormat.Format(currentPage, fNavigator.CurrentPage());
		fNumberFormat.Format(pageCount, fNavigator.PageCount());
		pages.SetToFormat("%s / %s", currentPage.String(), pageCount.String());
	}

	fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType,
		fImageView->Zoom());
}


void
ShowImageWindow::_LoadError(const entry_ref& ref, status_t status)
{
	locale_t locale = newlocale(LC_ALL_MASK, "", 0);
	const char* errorMessage = strerror_l(status, locale);
	freelocale(locale);
	BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"), errorMessage,
		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
	int32 outTranslator;
	uint32 outType;
	if (message->FindInt32(kTranslatorField, &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();
	}

	// Prefill current image's file name in save dialog
	BEntry entry = fImageView->Image();
	BPath path(&entry);
	const char* filename = path.Leaf();
	fSavePanel->SetSaveText(filename);

	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
	int32 outTranslator;
	uint32 outType;
	if (message->FindInt32(kTranslatorField, &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)
{
	// If the user triggered a _LoadImage while in a slide show,
	// make sure the new image is shown for the set delay:
	_ResetSlideShowDelay();

	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 += be_control_look->GetScrollBarWidth(B_VERTICAL);
		frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL);

		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::_ResetSlideShowDelay()
{
	if (fSlideShowRunner != NULL)
		fSlideShowRunner->SetInterval(fSlideShowDelay);
}


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);
	}
	fResetRatingItem->SetEnabled(rating > 0);
}


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 {
		fScrollArea->ResizeBy(0, -diff);
		fScrollArea->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;
}