/* * 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 */ #include "ShowImageWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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); 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; 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" 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("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); // 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); _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_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() && fNavigator.PreviousFile()) _LoadImage(false); 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; 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 int32 outTranslator; uint32 outType; if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK || message->FindInt32(kTypeField, reinterpret_cast(&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(&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; }