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 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; 1153 if (fImageView->Bitmap() != NULL) { 1154 BRect bounds = fImageView->Bitmap()->Bounds(); 1155 frameText << bounds.IntegerWidth() + 1 1156 << "x" << bounds.IntegerHeight() + 1; 1157 } 1158 BString pages; 1159 if (fNavigator.PageCount() > 1) 1160 pages << fNavigator.CurrentPage() << "/" << fNavigator.PageCount(); 1161 fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType, 1162 fImageView->Zoom()); 1163 } 1164 1165 1166 void 1167 ShowImageWindow::_LoadError(const entry_ref& ref) 1168 { 1169 // TODO: give a better error message! 1170 BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"), 1171 B_TRANSLATE_CONTEXT("Could not load image! Either the " 1172 "file or an image translator for it does not exist.", 1173 "LoadAlerts"), 1174 B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL, 1175 B_WIDTH_AS_USUAL, B_STOP_ALERT); 1176 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1177 alert->Go(); 1178 } 1179 1180 1181 void 1182 ShowImageWindow::_SaveAs(BMessage* message) 1183 { 1184 // Read the translator and output type the user chose 1185 int32 outTranslator; 1186 uint32 outType; 1187 if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK 1188 || message->FindInt32(kTypeField, 1189 reinterpret_cast<int32 *>(&outType)) != B_OK) 1190 return; 1191 1192 // Add the chosen translator and output type to the 1193 // message that the save panel will send back 1194 BMessage panelMsg(MSG_SAVE_PANEL); 1195 panelMsg.AddInt32(kTranslatorField, outTranslator); 1196 panelMsg.AddInt32(kTypeField, outType); 1197 1198 // Create save panel and show it 1199 BMessenger target(this); 1200 fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL, 1201 &target, NULL, 0, false, &panelMsg); 1202 1203 if (!fSavePanel) 1204 return; 1205 1206 // Retrieve save directory from settings; 1207 ShowImageSettings* settings = my_app->Settings(); 1208 if (settings->Lock()) { 1209 fSavePanel->SetPanelDirectory( 1210 settings->GetString("SaveDirectory", NULL)); 1211 settings->Unlock(); 1212 } 1213 1214 // Prefill current image's file name in save dialog 1215 BEntry entry = fImageView->Image(); 1216 BPath path(&entry); 1217 const char* filename = path.Leaf(); 1218 fSavePanel->SetSaveText(filename); 1219 1220 fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE); 1221 fSavePanel->Show(); 1222 } 1223 1224 1225 void 1226 ShowImageWindow::_SaveToFile(BMessage* message) 1227 { 1228 // Read in where the file should be saved 1229 entry_ref dirRef; 1230 if (message->FindRef("directory", &dirRef) != B_OK) 1231 return; 1232 1233 const char* filename; 1234 if (message->FindString("name", &filename) != B_OK) 1235 return; 1236 1237 // Read in the translator and type to be used 1238 // to save the output image 1239 int32 outTranslator; 1240 uint32 outType; 1241 if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK 1242 || message->FindInt32(kTypeField, 1243 reinterpret_cast<int32 *>(&outType)) != B_OK) 1244 return; 1245 1246 // Find the translator_format information needed to 1247 // write a MIME attribute for the image file 1248 BTranslatorRoster* roster = BTranslatorRoster::Default(); 1249 const translation_format* outFormat = NULL; 1250 int32 outCount = 0; 1251 if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK 1252 || outCount < 1) 1253 return; 1254 1255 int32 i; 1256 for (i = 0; i < outCount; i++) { 1257 if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type 1258 == outType) 1259 break; 1260 } 1261 if (i == outCount) 1262 return; 1263 1264 // Write out the image file 1265 BDirectory dir(&dirRef); 1266 fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]); 1267 1268 // Store Save directory in settings; 1269 ShowImageSettings* settings = my_app->Settings(); 1270 if (settings->Lock()) { 1271 BPath path(&dirRef); 1272 settings->SetString("SaveDirectory", path.Path()); 1273 settings->Unlock(); 1274 } 1275 } 1276 1277 1278 #undef B_TRANSLATION_CONTEXT 1279 #define B_TRANSLATION_CONTEXT "ClosePrompt" 1280 1281 1282 bool 1283 ShowImageWindow::_ClosePrompt() 1284 { 1285 if (!fModified) 1286 return true; 1287 1288 int32 count = fNavigator.PageCount(); 1289 int32 page = fNavigator.CurrentPage(); 1290 BString prompt; 1291 1292 if (count > 1) { 1293 bs_printf(&prompt, 1294 B_TRANSLATE("The document '%s' (page %d) has been changed. Do you " 1295 "want to close the document?"), 1296 fImageView->Image()->name, page); 1297 } else { 1298 bs_printf(&prompt, 1299 B_TRANSLATE("The document '%s' has been changed. Do you want to " 1300 "close the document?"), 1301 fImageView->Image()->name); 1302 } 1303 1304 BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(), 1305 B_TRANSLATE("Cancel"), B_TRANSLATE("Close")); 1306 alert->SetShortcut(0, B_ESCAPE); 1307 1308 if (alert->Go() == 0) { 1309 // Cancel 1310 return false; 1311 } 1312 1313 // Close 1314 fModified = false; 1315 return true; 1316 } 1317 1318 1319 status_t 1320 ShowImageWindow::_LoadImage(bool forward) 1321 { 1322 // If the user triggered a _LoadImage while in a slide show, 1323 // make sure the new image is shown for the set delay: 1324 _ResetSlideShowDelay(); 1325 1326 BMessenger us(this); 1327 status_t status = my_app->DefaultCache().RetrieveImage( 1328 fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us); 1329 if (status != B_OK) 1330 return status; 1331 1332 fProgressWindow->Start(this); 1333 1334 // Preload previous/next images - two in the navigation direction, one 1335 // in the opposite direction. 1336 1337 entry_ref nextRef = fNavigator.CurrentRef(); 1338 if (_PreloadImage(forward, nextRef)) 1339 _PreloadImage(forward, nextRef); 1340 1341 entry_ref previousRef = fNavigator.CurrentRef(); 1342 _PreloadImage(!forward, previousRef); 1343 1344 return B_OK; 1345 } 1346 1347 1348 bool 1349 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref) 1350 { 1351 entry_ref currentRef = ref; 1352 if ((forward && !fNavigator.GetNextFile(currentRef, ref)) 1353 || (!forward && !fNavigator.GetPreviousFile(currentRef, ref))) 1354 return false; 1355 1356 return my_app->DefaultCache().RetrieveImage(ref) == B_OK; 1357 } 1358 1359 1360 void 1361 ShowImageWindow::_ToggleFullScreen() 1362 { 1363 BRect frame; 1364 fFullScreen = !fFullScreen; 1365 if (fFullScreen) { 1366 BScreen screen; 1367 fWindowFrame = Frame(); 1368 frame = screen.Frame(); 1369 frame.top -= fBar->Bounds().Height() + 1; 1370 frame.right += be_control_look->GetScrollBarWidth(B_VERTICAL); 1371 frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL); 1372 1373 SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE); 1374 1375 Activate(); 1376 // make the window frontmost 1377 } else { 1378 frame = fWindowFrame; 1379 1380 SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); 1381 } 1382 1383 fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen); 1384 _SetToolBarVisible(!fFullScreen && fShowToolBar); 1385 _SetToolBarBorder(!fFullScreen); 1386 1387 MoveTo(frame.left, frame.top); 1388 ResizeTo(frame.Width(), frame.Height()); 1389 1390 fImageView->SetHideIdlingCursor(fFullScreen); 1391 fImageView->SetShowCaption(fFullScreen && fShowCaption); 1392 1393 Layout(false); 1394 // We need to manually relayout here, as the views are layouted 1395 // asynchronously, and FitToBounds() would still have the wrong size 1396 fImageView->FitToBounds(); 1397 } 1398 1399 1400 void 1401 ShowImageWindow::_ApplySettings() 1402 { 1403 ShowImageSettings* settings = my_app->Settings(); 1404 1405 if (settings->Lock()) { 1406 fShowCaption = settings->GetBool("ShowCaption", fShowCaption); 1407 fPrintOptions.SetBounds(BRect(0, 0, 1023, 767)); 1408 1409 fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay); 1410 1411 fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32( 1412 "PO:Option", fPrintOptions.Option())); 1413 fPrintOptions.SetZoomFactor( 1414 settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor())); 1415 fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI())); 1416 fPrintOptions.SetWidth( 1417 settings->GetFloat("PO:Width", fPrintOptions.Width())); 1418 fPrintOptions.SetHeight( 1419 settings->GetFloat("PO:Height", fPrintOptions.Height())); 1420 1421 fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar); 1422 1423 settings->Unlock(); 1424 } 1425 } 1426 1427 1428 void 1429 ShowImageWindow::_SavePrintOptions() 1430 { 1431 ShowImageSettings* settings = my_app->Settings(); 1432 1433 if (settings->Lock()) { 1434 settings->SetInt32("PO:Option", fPrintOptions.Option()); 1435 settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1436 settings->SetFloat("PO:DPI", fPrintOptions.DPI()); 1437 settings->SetFloat("PO:Width", fPrintOptions.Width()); 1438 settings->SetFloat("PO:Height", fPrintOptions.Height()); 1439 settings->Unlock(); 1440 } 1441 } 1442 1443 1444 bool 1445 ShowImageWindow::_PageSetup() 1446 { 1447 BPrintJob printJob(fImageView->Image()->name); 1448 if (fPrintSettings != NULL) 1449 printJob.SetSettings(new BMessage(*fPrintSettings)); 1450 1451 status_t status = printJob.ConfigPage(); 1452 if (status == B_OK) { 1453 delete fPrintSettings; 1454 fPrintSettings = printJob.Settings(); 1455 } 1456 1457 return status == B_OK; 1458 } 1459 1460 1461 void 1462 ShowImageWindow::_PrepareForPrint() 1463 { 1464 if (fPrintSettings == NULL) { 1465 BPrintJob printJob(fImageView->Image()->name); 1466 if (printJob.ConfigJob() == B_OK) 1467 fPrintSettings = printJob.Settings(); 1468 } 1469 1470 fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds()); 1471 fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1); 1472 1473 new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50), 1474 &fPrintOptions, this); 1475 } 1476 1477 1478 void 1479 ShowImageWindow::_Print(BMessage* msg) 1480 { 1481 status_t st; 1482 if (msg->FindInt32("status", &st) != B_OK || st != B_OK) 1483 return; 1484 1485 _SavePrintOptions(); 1486 1487 BPrintJob printJob(fImageView->Image()->name); 1488 if (fPrintSettings) 1489 printJob.SetSettings(new BMessage(*fPrintSettings)); 1490 1491 if (printJob.ConfigJob() == B_OK) { 1492 delete fPrintSettings; 1493 fPrintSettings = printJob.Settings(); 1494 1495 // first/lastPage is unused for now 1496 int32 firstPage = printJob.FirstPage(); 1497 int32 lastPage = printJob.LastPage(); 1498 BRect printableRect = printJob.PrintableRect(); 1499 1500 if (firstPage < 1) 1501 firstPage = 1; 1502 if (lastPage < firstPage) 1503 lastPage = firstPage; 1504 1505 BBitmap* bitmap = fImageView->Bitmap(); 1506 float imageWidth = bitmap->Bounds().Width() + 1.0; 1507 float imageHeight = bitmap->Bounds().Height() + 1.0; 1508 1509 float width; 1510 switch (fPrintOptions.Option()) { 1511 case PrintOptions::kFitToPage: { 1512 float w1 = printableRect.Width() + 1; 1513 float w2 = imageWidth * (printableRect.Height() + 1) 1514 / imageHeight; 1515 if (w2 < w1) 1516 width = w2; 1517 else 1518 width = w1; 1519 } break; 1520 case PrintOptions::kZoomFactor: 1521 width = imageWidth * fPrintOptions.ZoomFactor(); 1522 break; 1523 case PrintOptions::kDPI: 1524 width = imageWidth * 72.0 / fPrintOptions.DPI(); 1525 break; 1526 case PrintOptions::kWidth: 1527 case PrintOptions::kHeight: 1528 width = fPrintOptions.Width(); 1529 break; 1530 1531 default: 1532 // keep compiler silent; should not reach here 1533 width = imageWidth; 1534 } 1535 1536 // TODO: eventually print large images on several pages 1537 printJob.BeginJob(); 1538 fImageView->SetScale(width / imageWidth); 1539 // coordinates are relative to printable rectangle 1540 BRect bounds(bitmap->Bounds()); 1541 printJob.DrawView(fImageView, bounds, BPoint(0, 0)); 1542 fImageView->SetScale(1.0); 1543 printJob.SpoolPage(); 1544 printJob.CommitJob(); 1545 } 1546 } 1547 1548 1549 void 1550 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay) 1551 { 1552 if (fSlideShowDelay == delay) 1553 return; 1554 1555 fSlideShowDelay = delay; 1556 1557 ShowImageSettings* settings = my_app->Settings(); 1558 if (settings->Lock()) { 1559 settings->SetTime("SlideShowDelay", fSlideShowDelay); 1560 settings->Unlock(); 1561 } 1562 1563 if (fSlideShowRunner != NULL) 1564 _StartSlideShow(); 1565 } 1566 1567 1568 void 1569 ShowImageWindow::_StartSlideShow() 1570 { 1571 _StopSlideShow(); 1572 1573 BMessage nextSlide(kMsgNextSlide); 1574 fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay); 1575 } 1576 1577 1578 void 1579 ShowImageWindow::_StopSlideShow() 1580 { 1581 if (fSlideShowRunner != NULL) { 1582 delete fSlideShowRunner; 1583 fSlideShowRunner = NULL; 1584 } 1585 } 1586 1587 1588 void 1589 ShowImageWindow::_ResetSlideShowDelay() 1590 { 1591 if (fSlideShowRunner != NULL) 1592 fSlideShowRunner->SetInterval(fSlideShowDelay); 1593 } 1594 1595 1596 void 1597 ShowImageWindow::_UpdateRatingMenu() 1598 { 1599 BFile file(&fNavigator.CurrentRef(), B_READ_ONLY); 1600 if (file.InitCheck() != B_OK) 1601 return; 1602 int32 rating; 1603 ssize_t size = sizeof(rating); 1604 if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size) 1605 rating = 0; 1606 // TODO: Finding the correct item could be more robust, like by looking 1607 // at the message of each item. 1608 for (int32 i = 1; i <= 10; i++) { 1609 BMenuItem* item = fRatingMenu->ItemAt(i - 1); 1610 if (item == NULL) 1611 break; 1612 item->SetMarked(i == rating); 1613 } 1614 fResetRatingItem->SetEnabled(rating > 0); 1615 } 1616 1617 1618 void 1619 ShowImageWindow::_SaveWidthAndHeight() 1620 { 1621 if (fNavigator.CurrentPage() != 1) 1622 return; 1623 1624 if (fImageView->Bitmap() == NULL) 1625 return; 1626 1627 BRect bounds = fImageView->Bitmap()->Bounds(); 1628 int32 width = bounds.IntegerWidth() + 1; 1629 int32 height = bounds.IntegerHeight() + 1; 1630 1631 BNode node(&fNavigator.CurrentRef()); 1632 if (node.InitCheck() != B_OK) 1633 return; 1634 1635 const char* kWidthAttrName = "Media:Width"; 1636 const char* kHeightAttrName = "Media:Height"; 1637 1638 int32 widthAttr; 1639 ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0, 1640 &widthAttr, sizeof(widthAttr)); 1641 if (attrSize <= 0 || widthAttr != width) 1642 node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width)); 1643 1644 int32 heightAttr; 1645 attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0, 1646 &heightAttr, sizeof(heightAttr)); 1647 if (attrSize <= 0 || heightAttr != height) 1648 node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height)); 1649 } 1650 1651 1652 void 1653 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate) 1654 { 1655 if (visible == fToolBarVisible) 1656 return; 1657 1658 fToolBarVisible = visible; 1659 float diff = fToolBar->Bounds().Height() + 2; 1660 if (!visible) 1661 diff = -diff; 1662 else 1663 fToolBar->Show(); 1664 1665 if (animate) { 1666 // Slide the controls into view. We do this with messages in order 1667 // not to block the window thread. 1668 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 }; 1669 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float); 1670 for (int32 i = 0; i < steps; i++) { 1671 BMessage message(kMsgSlideToolBar); 1672 message.AddFloat("offset", floorf(diff * kAnimationOffsets[i])); 1673 PostMessage(&message, this); 1674 } 1675 BMessage finalMessage(kMsgFinishSlidingToolBar); 1676 finalMessage.AddFloat("offset", visible ? 0 : diff); 1677 finalMessage.AddBool("show", visible); 1678 PostMessage(&finalMessage, this); 1679 } else { 1680 fScrollArea->ResizeBy(0, -diff); 1681 fScrollArea->MoveBy(0, diff); 1682 fToolBar->MoveBy(0, diff); 1683 if (!visible) 1684 fToolBar->Hide(); 1685 } 1686 } 1687 1688 1689 void 1690 ShowImageWindow::_SetToolBarBorder(bool visible) 1691 { 1692 float inset = visible 1693 ? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0; 1694 1695 fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0); 1696 } 1697 1698 1699 bool 1700 ShowImageWindow::QuitRequested() 1701 { 1702 if (fSavePanel) { 1703 // Don't allow this window to be closed if a save panel is open 1704 return false; 1705 } 1706 1707 if (!_ClosePrompt()) 1708 return false; 1709 1710 ShowImageSettings* settings = my_app->Settings(); 1711 if (settings->Lock()) { 1712 if (fFullScreen) 1713 settings->SetRect("WindowFrame", fWindowFrame); 1714 else 1715 settings->SetRect("WindowFrame", Frame()); 1716 settings->Unlock(); 1717 } 1718 1719 be_app->PostMessage(MSG_WINDOW_HAS_QUIT); 1720 1721 return true; 1722 } 1723