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