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"), MSG_GET_INFO, 'I', 0, this); 448 menu->AddSeparatorItem(); 449 _AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 450 MSG_PAGE_SETUP, 0, 0, this); 451 _AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 452 MSG_PREPARE_PRINT, 'P', 0, this); 453 menu->AddSeparatorItem(); 454 _AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this); 455 _AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app); 456 bar->AddItem(menu); 457 458 menu = new BMenu(B_TRANSLATE("Edit")); 459 _AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false); 460 menu->AddSeparatorItem(); 461 _AddItemMenu(menu, B_TRANSLATE("Selection mode"), MSG_SELECTION_MODE, 0, 0, 462 this); 463 _AddItemMenu(menu, B_TRANSLATE("Clear selection"), 464 MSG_CLEAR_SELECT, 0, 0, this, false); 465 _AddItemMenu(menu, B_TRANSLATE("Select all"), 466 MSG_SELECT_ALL, 'A', 0, this); 467 bar->AddItem(menu); 468 469 menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse")); 470 _AddItemMenu(menu, B_TRANSLATE("First page"), 471 MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this); 472 _AddItemMenu(menu, B_TRANSLATE("Last page"), 473 MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this); 474 _AddItemMenu(menu, B_TRANSLATE("Previous page"), 475 MSG_PAGE_PREV, B_LEFT_ARROW, 0, this); 476 _AddItemMenu(menu, B_TRANSLATE("Next page"), 477 MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this); 478 fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page")); 479 fGoToPageMenu->SetRadioMode(true); 480 menu->AddItem(fGoToPageMenu); 481 menu->AddSeparatorItem(); 482 _AddItemMenu(menu, B_TRANSLATE("Previous file"), 483 MSG_FILE_PREV, B_UP_ARROW, 0, this); 484 _AddItemMenu(menu, B_TRANSLATE("Next file"), 485 MSG_FILE_NEXT, B_DOWN_ARROW, 0, this); 486 bar->AddItem(menu); 487 488 menu = new BMenu(B_TRANSLATE("Image")); 489 _AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"), 490 MSG_ROTATE_90, 'R', 0, this); 491 _AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"), 492 MSG_ROTATE_270, 'R', B_SHIFT_KEY, this); 493 menu->AddSeparatorItem(); 494 _AddItemMenu(menu, B_TRANSLATE("Flip left to right"), 495 MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this); 496 _AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"), 497 MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this); 498 499 bar->AddItem(menu); 500 } 501 502 503 BMenuItem* 504 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what, 505 char shortcut, uint32 modifier, const BHandler* target, bool enabled) 506 { 507 BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut, 508 modifier); 509 menu->AddItem(item); 510 511 item->SetTarget(target); 512 item->SetEnabled(enabled); 513 514 return item; 515 } 516 517 518 BMenuItem* 519 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, bigtime_t delay) 520 { 521 BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY); 522 message->AddInt64("delay", delay); 523 524 BMenuItem* item = new BMenuItem(label, message, 0); 525 item->SetTarget(this); 526 527 if (delay == fSlideShowDelay) 528 item->SetMarked(true); 529 530 menu->AddItem(item); 531 return item; 532 } 533 534 535 void 536 ShowImageWindow::_ResizeWindowToImage() 537 { 538 BBitmap* bitmap = fImageView->Bitmap(); 539 BScreen screen; 540 if (bitmap == NULL || !screen.IsValid()) 541 return; 542 543 // TODO: use View::GetPreferredSize() instead? 544 BRect r(bitmap->Bounds()); 545 float width = r.Width() + be_control_look->GetScrollBarWidth(B_VERTICAL); 546 float height = r.Height() + 1 + fBar->Frame().Height() 547 + be_control_look->GetScrollBarWidth(B_HORIZONTAL); 548 549 BRect frame = screen.Frame(); 550 const float windowBorder = 5; 551 // dimensions so that window does not reach outside of screen 552 float maxWidth = frame.Width() + 1 - windowBorder - Frame().left; 553 float maxHeight = frame.Height() + 1 - windowBorder - Frame().top; 554 555 // We have to check size limits manually, otherwise 556 // menu bar will be too short for small images. 557 558 float minW, maxW, minH, maxH; 559 GetSizeLimits(&minW, &maxW, &minH, &maxH); 560 if (maxWidth > maxW) 561 maxWidth = maxW; 562 if (maxHeight > maxH) 563 maxHeight = maxH; 564 if (width < minW) 565 width = minW; 566 if (height < minH) 567 height = minH; 568 569 if (width > maxWidth) 570 width = maxWidth; 571 if (height > maxHeight) 572 height = maxHeight; 573 574 ResizeTo(width, height); 575 } 576 577 578 bool 579 ShowImageWindow::_ToggleMenuItem(uint32 what) 580 { 581 bool marked = false; 582 BMenuItem* item = fBar->FindItem(what); 583 if (item != NULL) { 584 marked = !item->IsMarked(); 585 item->SetMarked(marked); 586 } 587 fToolBar->SetActionPressed(what, marked); 588 return marked; 589 } 590 591 592 void 593 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable) 594 { 595 BMenuItem* item = menu->FindItem(what); 596 if (item && item->IsEnabled() != enable) 597 item->SetEnabled(enable); 598 fToolBar->SetActionEnabled(what, enable); 599 } 600 601 602 void 603 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked) 604 { 605 BMenuItem* item = menu->FindItem(what); 606 if (item && item->IsMarked() != marked) 607 item->SetMarked(marked); 608 fToolBar->SetActionPressed(what, marked); 609 } 610 611 612 void 613 ShowImageWindow::_MarkSlideShowDelay(bigtime_t delay) 614 { 615 const int32 count = fSlideShowDelayMenu->CountItems(); 616 for (int32 i = 0; i < count; i ++) { 617 BMenuItem* item = fSlideShowDelayMenu->ItemAt(i); 618 if (item != NULL) { 619 bigtime_t itemDelay; 620 if (item->Message()->FindInt64("delay", &itemDelay) == B_OK 621 && itemDelay == delay) { 622 item->SetMarked(true); 623 return; 624 } 625 } 626 } 627 } 628 629 630 void 631 ShowImageWindow::Zoom(BPoint origin, float width, float height) 632 { 633 _ToggleFullScreen(); 634 } 635 636 637 void 638 ShowImageWindow::MessageReceived(BMessage* message) 639 { 640 if (message->WasDropped()) { 641 uint32 type; 642 int32 count; 643 status_t status = message->GetInfo("refs", &type, &count); 644 if (status == B_OK && type == B_REF_TYPE) { 645 message->what = B_REFS_RECEIVED; 646 be_app->PostMessage(message); 647 } 648 } 649 650 switch (message->what) { 651 case kMsgImageCacheImageLoaded: 652 { 653 fProgressWindow->Stop(); 654 655 BitmapOwner* bitmapOwner = NULL; 656 message->FindPointer("bitmapOwner", (void**)&bitmapOwner); 657 658 bool first = fImageView->Bitmap() == NULL; 659 entry_ref ref; 660 message->FindRef("ref", &ref); 661 if (!first && ref != fNavigator.CurrentRef()) { 662 // ignore older images 663 if (bitmapOwner != NULL) 664 bitmapOwner->ReleaseReference(); 665 break; 666 } 667 668 int32 page = message->FindInt32("page"); 669 int32 pageCount = message->FindInt32("pageCount"); 670 if (!first && page != fNavigator.CurrentPage()) { 671 // ignore older pages 672 if (bitmapOwner != NULL) 673 bitmapOwner->ReleaseReference(); 674 break; 675 } 676 677 status_t status = fImageView->SetImage(message); 678 if (status != B_OK) { 679 if (bitmapOwner != NULL) 680 bitmapOwner->ReleaseReference(); 681 682 _LoadError(ref); 683 684 // quit if file could not be opened 685 if (first) 686 Quit(); 687 break; 688 } 689 690 fImageType = message->FindString("type"); 691 fNavigator.SetTo(ref, page, pageCount); 692 693 fImageView->FitToBounds(); 694 if (first) { 695 fImageView->MakeFocus(true); 696 // to receive key messages 697 Show(); 698 } 699 700 fMimeType = new BMimeType(message->FindString("mime")); 701 _UpdateOpenWithMenu(fOpenWithMenu); 702 _UpdateRatingMenu(); 703 // Set width and height attributes of the currently showed file. 704 // This should only be a temporary solution. 705 _SaveWidthAndHeight(); 706 break; 707 } 708 709 case kMsgImageCacheProgressUpdate: 710 { 711 entry_ref ref; 712 if (message->FindRef("ref", &ref) == B_OK 713 && ref == fNavigator.CurrentRef()) { 714 message->what = kMsgProgressUpdate; 715 fProgressWindow->PostMessage(message); 716 } 717 break; 718 } 719 720 case MSG_MODIFIED: 721 // If image has been modified due to a Cut or Paste 722 fModified = true; 723 break; 724 725 case MSG_OUTPUT_TYPE: 726 // User clicked Save As then choose an output format 727 if (!fSavePanel) 728 // If user doesn't already have a save panel open 729 _SaveAs(message); 730 break; 731 732 case MSG_SAVE_PANEL: 733 // User specified where to save the output image 734 _SaveToFile(message); 735 break; 736 737 case B_CANCEL: 738 delete fSavePanel; 739 fSavePanel = NULL; 740 break; 741 742 case MSG_UPDATE_STATUS: 743 { 744 int32 pages = fNavigator.PageCount(); 745 int32 currentPage = fNavigator.CurrentPage(); 746 747 _EnableMenuItem(fBar, MSG_PAGE_FIRST, 748 fNavigator.HasPreviousPage()); 749 _EnableMenuItem(fBar, MSG_PAGE_LAST, fNavigator.HasNextPage()); 750 _EnableMenuItem(fBar, MSG_PAGE_NEXT, fNavigator.HasNextPage()); 751 _EnableMenuItem(fBar, MSG_PAGE_PREV, fNavigator.HasPreviousPage()); 752 fGoToPageMenu->SetEnabled(pages > 1); 753 754 _EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile()); 755 _EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile()); 756 757 if (fGoToPageMenu->CountItems() != pages) { 758 // Only rebuild the submenu if the number of 759 // pages is different 760 761 while (fGoToPageMenu->CountItems() > 0) { 762 // Remove all page numbers 763 delete fGoToPageMenu->RemoveItem((int32)0); 764 } 765 766 for (int32 i = 1; i <= pages; i++) { 767 // Fill Go To page submenu with an entry for each page 768 BMessage* goTo = new BMessage(MSG_GOTO_PAGE); 769 goTo->AddInt32("page", i); 770 771 char shortcut = 0; 772 if (i < 10) 773 shortcut = '0' + i; 774 775 BString strCaption; 776 strCaption << i; 777 778 BMenuItem* item = new BMenuItem(strCaption.String(), goTo, 779 shortcut, B_SHIFT_KEY); 780 if (currentPage == i) 781 item->SetMarked(true); 782 fGoToPageMenu->AddItem(item); 783 } 784 } else { 785 // Make sure the correct page is marked 786 BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1); 787 if (currentItem != NULL && !currentItem->IsMarked()) 788 currentItem->SetMarked(true); 789 } 790 791 _UpdateStatusText(message); 792 793 BPath path(fImageView->Image()); 794 SetTitle(path.Path()); 795 break; 796 } 797 798 case MSG_UPDATE_STATUS_TEXT: 799 { 800 _UpdateStatusText(message); 801 break; 802 } 803 804 case MSG_OPEN_WITH: 805 { 806 BString appSig = ""; 807 message->FindString("signature", &appSig); 808 entry_ref ref = fNavigator.CurrentRef(); 809 BMessage openMsg(B_REFS_RECEIVED); 810 openMsg.AddRef("refs", &ref); 811 be_roster->Launch(appSig.String(), &openMsg); 812 break; 813 } 814 815 case MSG_SELECTION: 816 { 817 // The view sends this message when a selection is 818 // made or the selection is cleared so that the window 819 // can update the state of the appropriate menu items 820 bool enable; 821 if (message->FindBool("has_selection", &enable) == B_OK) { 822 _EnableMenuItem(fBar, B_COPY, enable); 823 _EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable); 824 } 825 break; 826 } 827 828 case B_COPY: 829 fImageView->CopySelectionToClipboard(); 830 break; 831 832 case MSG_SELECTION_MODE: 833 { 834 bool selectionMode = _ToggleMenuItem(MSG_SELECTION_MODE); 835 fImageView->SetSelectionMode(selectionMode); 836 if (!selectionMode) 837 fImageView->ClearSelection(); 838 break; 839 } 840 841 case MSG_CLEAR_SELECT: 842 fImageView->ClearSelection(); 843 break; 844 845 case MSG_SELECT_ALL: 846 fImageView->SelectAll(); 847 break; 848 849 case MSG_PAGE_FIRST: 850 if (_ClosePrompt() && fNavigator.FirstPage()) 851 _LoadImage(); 852 break; 853 854 case MSG_PAGE_LAST: 855 if (_ClosePrompt() && fNavigator.LastPage()) 856 _LoadImage(); 857 break; 858 859 case MSG_PAGE_NEXT: 860 if (_ClosePrompt() && fNavigator.NextPage()) 861 _LoadImage(); 862 break; 863 864 case MSG_PAGE_PREV: 865 if (_ClosePrompt() && fNavigator.PreviousPage()) 866 _LoadImage(); 867 break; 868 869 case MSG_GOTO_PAGE: 870 { 871 if (!_ClosePrompt()) 872 break; 873 874 int32 newPage; 875 if (message->FindInt32("page", &newPage) != B_OK) 876 break; 877 878 int32 currentPage = fNavigator.CurrentPage(); 879 int32 pages = fNavigator.PageCount(); 880 881 // TODO: use radio mode instead! 882 if (newPage > 0 && newPage <= pages) { 883 BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1); 884 BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1); 885 if (currentItem != NULL && newItem != NULL) { 886 currentItem->SetMarked(false); 887 newItem->SetMarked(true); 888 if (fNavigator.GoToPage(newPage)) 889 _LoadImage(); 890 } 891 } 892 break; 893 } 894 895 case kMsgFitToWindow: 896 fImageView->FitToBounds(); 897 break; 898 899 case kMsgStretchToWindow: 900 fImageView->SetStretchToBounds( 901 _ToggleMenuItem(kMsgStretchToWindow)); 902 break; 903 904 case MSG_FILE_PREV: 905 if (_ClosePrompt() && fNavigator.PreviousFile()) 906 _LoadImage(false); 907 break; 908 909 case MSG_FILE_NEXT: 910 case kMsgNextSlide: 911 if (_ClosePrompt()) { 912 if (!fNavigator.NextFile()) { 913 // Wrap back around 914 fNavigator.FirstFile(); 915 } 916 _LoadImage(); 917 } 918 break; 919 920 case kMsgDeleteCurrentFile: 921 { 922 if (fNavigator.MoveFileToTrash()) 923 _LoadImage(); 924 else 925 PostMessage(B_QUIT_REQUESTED); 926 break; 927 } 928 929 case MSG_ROTATE_90: 930 fImageView->Rotate(90); 931 break; 932 933 case MSG_ROTATE_270: 934 fImageView->Rotate(270); 935 break; 936 937 case MSG_FLIP_LEFT_TO_RIGHT: 938 fImageView->Flip(true); 939 break; 940 941 case MSG_FLIP_TOP_TO_BOTTOM: 942 fImageView->Flip(false); 943 break; 944 945 case MSG_GET_INFO: 946 _GetFileInfo(fNavigator.CurrentRef()); 947 break; 948 949 case MSG_SLIDE_SHOW: 950 { 951 bool fullScreen = false; 952 message->FindBool("full screen", &fullScreen); 953 954 BMenuItem* item = fBar->FindItem(message->what); 955 if (item == NULL) 956 break; 957 958 if (item->IsMarked()) { 959 item->SetMarked(false); 960 _StopSlideShow(); 961 fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false); 962 } else if (_ClosePrompt()) { 963 item->SetMarked(true); 964 if (!fFullScreen && fullScreen) 965 _ToggleFullScreen(); 966 _StartSlideShow(); 967 fToolBar->SetActionPressed(MSG_SLIDE_SHOW, true); 968 } 969 break; 970 } 971 972 case kMsgStopSlideShow: 973 { 974 BMenuItem* item = fBar->FindItem(MSG_SLIDE_SHOW); 975 if (item != NULL) 976 item->SetMarked(false); 977 978 _StopSlideShow(); 979 fToolBar->SetActionPressed(MSG_SLIDE_SHOW, false); 980 break; 981 } 982 983 case MSG_SLIDE_SHOW_DELAY: 984 { 985 bigtime_t delay; 986 if (message->FindInt64("delay", &delay) == B_OK) { 987 _SetSlideShowDelay(delay); 988 // in case message is sent from popup menu 989 _MarkSlideShowDelay(delay); 990 } 991 break; 992 } 993 994 case MSG_FULL_SCREEN: 995 _ToggleFullScreen(); 996 break; 997 998 case MSG_EXIT_FULL_SCREEN: 999 if (fFullScreen) 1000 _ToggleFullScreen(); 1001 break; 1002 1003 case MSG_SHOW_CAPTION: 1004 { 1005 fShowCaption = _ToggleMenuItem(message->what); 1006 ShowImageSettings* settings = my_app->Settings(); 1007 1008 if (settings->Lock()) { 1009 settings->SetBool("ShowCaption", fShowCaption); 1010 settings->Unlock(); 1011 } 1012 if (fFullScreen) 1013 fImageView->SetShowCaption(fShowCaption); 1014 } break; 1015 1016 case MSG_PAGE_SETUP: 1017 _PageSetup(); 1018 break; 1019 1020 case MSG_PREPARE_PRINT: 1021 _PrepareForPrint(); 1022 break; 1023 1024 case MSG_PRINT: 1025 _Print(message); 1026 break; 1027 1028 case MSG_ZOOM_IN: 1029 fImageView->ZoomIn(); 1030 break; 1031 1032 case MSG_ZOOM_OUT: 1033 fImageView->ZoomOut(); 1034 break; 1035 1036 case MSG_UPDATE_STATUS_ZOOM: 1037 fStatusView->SetZoom(fImageView->Zoom()); 1038 break; 1039 1040 case kMsgOriginalSize: 1041 if (message->FindInt32("behavior") == BButton::B_TOGGLE_BEHAVIOR) { 1042 bool force = (message->FindInt32("be:value") == B_CONTROL_ON); 1043 fImageView->ForceOriginalSize(force); 1044 if (!force) 1045 break; 1046 } 1047 fImageView->SetZoom(1.0); 1048 break; 1049 1050 case MSG_SCALE_BILINEAR: 1051 fImageView->SetScaleBilinear(_ToggleMenuItem(message->what)); 1052 break; 1053 1054 case MSG_DESKTOP_BACKGROUND: 1055 { 1056 BMessage backgroundsMessage(B_REFS_RECEIVED); 1057 backgroundsMessage.AddRef("refs", fImageView->Image()); 1058 // This is used in the Backgrounds code for scaled placement 1059 backgroundsMessage.AddInt32("placement", 'scpl'); 1060 be_roster->Launch("application/x-vnd.haiku-backgrounds", 1061 &backgroundsMessage); 1062 break; 1063 } 1064 1065 case MSG_SET_RATING: 1066 { 1067 int32 rating; 1068 if (message->FindInt32("rating", &rating) != B_OK) 1069 break; 1070 BFile file(&fNavigator.CurrentRef(), B_WRITE_ONLY); 1071 if (file.InitCheck() != B_OK) 1072 break; 1073 file.WriteAttr("Media:Rating", B_INT32_TYPE, 0, &rating, 1074 sizeof(rating)); 1075 _UpdateRatingMenu(); 1076 break; 1077 } 1078 1079 case kMsgToggleToolBar: 1080 { 1081 fShowToolBar = _ToggleMenuItem(message->what); 1082 _SetToolBarVisible(fShowToolBar, true); 1083 1084 ShowImageSettings* settings = my_app->Settings(); 1085 1086 if (settings->Lock()) { 1087 settings->SetBool("ShowToolBar", fShowToolBar); 1088 settings->Unlock(); 1089 } 1090 break; 1091 } 1092 case kShowToolBarIfEnabled: 1093 { 1094 bool show; 1095 if (message->FindBool("show", &show) != B_OK) 1096 break; 1097 _SetToolBarVisible(fShowToolBar && show, true); 1098 break; 1099 } 1100 case kMsgSlideToolBar: 1101 { 1102 float offset; 1103 if (message->FindFloat("offset", &offset) == B_OK) { 1104 fToolBar->MoveBy(0, offset); 1105 fScrollArea->ResizeBy(0, -offset); 1106 fScrollArea->MoveBy(0, offset); 1107 UpdateIfNeeded(); 1108 snooze(15000); 1109 } 1110 break; 1111 } 1112 case kMsgFinishSlidingToolBar: 1113 { 1114 float offset; 1115 bool show; 1116 if (message->FindFloat("offset", &offset) == B_OK 1117 && message->FindBool("show", &show) == B_OK) { 1118 // Compensate rounding errors with the final placement 1119 fToolBar->MoveTo(fToolBar->Frame().left, offset); 1120 if (!show) 1121 fToolBar->Hide(); 1122 BRect frame = fToolBar->Parent()->Bounds(); 1123 frame.top = fToolBar->Frame().bottom + 1; 1124 fScrollArea->MoveTo(fScrollArea->Frame().left, frame.top); 1125 fScrollArea->ResizeTo(fScrollArea->Bounds().Width(), 1126 frame.Height() + 1); 1127 } 1128 break; 1129 } 1130 1131 default: 1132 BWindow::MessageReceived(message); 1133 break; 1134 } 1135 } 1136 1137 1138 void 1139 ShowImageWindow::_GetFileInfo(const entry_ref& ref) 1140 { 1141 BMessage message('Tinf'); 1142 BMessenger tracker("application/x-vnd.Be-TRAK"); 1143 message.AddRef("refs", &ref); 1144 tracker.SendMessage(&message); 1145 } 1146 1147 1148 void 1149 ShowImageWindow::_UpdateStatusText(const BMessage* message) 1150 { 1151 BString frameText, height, width; 1152 if (fImageView->Bitmap() != NULL) { 1153 BRect bounds = fImageView->Bitmap()->Bounds(); 1154 fNumberFormat.Format(width, bounds.IntegerWidth() + 1); 1155 fNumberFormat.Format(height, bounds.IntegerHeight() + 1); 1156 frameText.SetToFormat("%s × %s", width.String(), height.String()); 1157 } 1158 1159 BString currentPage, pageCount, pages; 1160 if (fNavigator.PageCount() > 1) { 1161 fNumberFormat.Format(currentPage, fNavigator.CurrentPage()); 1162 fNumberFormat.Format(pageCount, fNavigator.PageCount()); 1163 pages.SetToFormat("%s / %s", currentPage.String(), pageCount.String()); 1164 } 1165 1166 fStatusView->Update(fNavigator.CurrentRef(), frameText, pages, fImageType, 1167 fImageView->Zoom()); 1168 } 1169 1170 1171 void 1172 ShowImageWindow::_LoadError(const entry_ref& ref) 1173 { 1174 // TODO: give a better error message! 1175 BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("ShowImage"), 1176 B_TRANSLATE_CONTEXT("Could not load image! Either the " 1177 "file or an image translator for it does not exist.", 1178 "LoadAlerts"), 1179 B_TRANSLATE_CONTEXT("OK", "Alerts"), NULL, NULL, 1180 B_WIDTH_AS_USUAL, B_STOP_ALERT); 1181 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1182 alert->Go(); 1183 } 1184 1185 1186 void 1187 ShowImageWindow::_SaveAs(BMessage* message) 1188 { 1189 // Read the translator and output type the user chose 1190 int32 outTranslator; 1191 uint32 outType; 1192 if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK 1193 || message->FindInt32(kTypeField, 1194 reinterpret_cast<int32 *>(&outType)) != B_OK) 1195 return; 1196 1197 // Add the chosen translator and output type to the 1198 // message that the save panel will send back 1199 BMessage panelMsg(MSG_SAVE_PANEL); 1200 panelMsg.AddInt32(kTranslatorField, outTranslator); 1201 panelMsg.AddInt32(kTypeField, outType); 1202 1203 // Create save panel and show it 1204 BMessenger target(this); 1205 fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL, 1206 &target, NULL, 0, false, &panelMsg); 1207 1208 if (!fSavePanel) 1209 return; 1210 1211 // Retrieve save directory from settings; 1212 ShowImageSettings* settings = my_app->Settings(); 1213 if (settings->Lock()) { 1214 fSavePanel->SetPanelDirectory( 1215 settings->GetString("SaveDirectory", NULL)); 1216 settings->Unlock(); 1217 } 1218 1219 // Prefill current image's file name in save dialog 1220 BEntry entry = fImageView->Image(); 1221 BPath path(&entry); 1222 const char* filename = path.Leaf(); 1223 fSavePanel->SetSaveText(filename); 1224 1225 fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE); 1226 fSavePanel->Show(); 1227 } 1228 1229 1230 void 1231 ShowImageWindow::_SaveToFile(BMessage* message) 1232 { 1233 // Read in where the file should be saved 1234 entry_ref dirRef; 1235 if (message->FindRef("directory", &dirRef) != B_OK) 1236 return; 1237 1238 const char* filename; 1239 if (message->FindString("name", &filename) != B_OK) 1240 return; 1241 1242 // Read in the translator and type to be used 1243 // to save the output image 1244 int32 outTranslator; 1245 uint32 outType; 1246 if (message->FindInt32(kTranslatorField, &outTranslator) != B_OK 1247 || message->FindInt32(kTypeField, 1248 reinterpret_cast<int32 *>(&outType)) != B_OK) 1249 return; 1250 1251 // Find the translator_format information needed to 1252 // write a MIME attribute for the image file 1253 BTranslatorRoster* roster = BTranslatorRoster::Default(); 1254 const translation_format* outFormat = NULL; 1255 int32 outCount = 0; 1256 if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK 1257 || outCount < 1) 1258 return; 1259 1260 int32 i; 1261 for (i = 0; i < outCount; i++) { 1262 if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type 1263 == outType) 1264 break; 1265 } 1266 if (i == outCount) 1267 return; 1268 1269 // Write out the image file 1270 BDirectory dir(&dirRef); 1271 fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]); 1272 1273 // Store Save directory in settings; 1274 ShowImageSettings* settings = my_app->Settings(); 1275 if (settings->Lock()) { 1276 BPath path(&dirRef); 1277 settings->SetString("SaveDirectory", path.Path()); 1278 settings->Unlock(); 1279 } 1280 } 1281 1282 1283 #undef B_TRANSLATION_CONTEXT 1284 #define B_TRANSLATION_CONTEXT "ClosePrompt" 1285 1286 1287 bool 1288 ShowImageWindow::_ClosePrompt() 1289 { 1290 if (!fModified) 1291 return true; 1292 1293 int32 count = fNavigator.PageCount(); 1294 int32 page = fNavigator.CurrentPage(); 1295 BString prompt; 1296 1297 if (count > 1) { 1298 bs_printf(&prompt, 1299 B_TRANSLATE("The document '%s' (page %d) has been changed. Do you " 1300 "want to close the document?"), 1301 fImageView->Image()->name, page); 1302 } else { 1303 bs_printf(&prompt, 1304 B_TRANSLATE("The document '%s' has been changed. Do you want to " 1305 "close the document?"), 1306 fImageView->Image()->name); 1307 } 1308 1309 BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(), 1310 B_TRANSLATE("Cancel"), B_TRANSLATE("Close")); 1311 alert->SetShortcut(0, B_ESCAPE); 1312 1313 if (alert->Go() == 0) { 1314 // Cancel 1315 return false; 1316 } 1317 1318 // Close 1319 fModified = false; 1320 return true; 1321 } 1322 1323 1324 status_t 1325 ShowImageWindow::_LoadImage(bool forward) 1326 { 1327 // If the user triggered a _LoadImage while in a slide show, 1328 // make sure the new image is shown for the set delay: 1329 _ResetSlideShowDelay(); 1330 1331 BMessenger us(this); 1332 status_t status = my_app->DefaultCache().RetrieveImage( 1333 fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us); 1334 if (status != B_OK) 1335 return status; 1336 1337 fProgressWindow->Start(this); 1338 1339 // Preload previous/next images - two in the navigation direction, one 1340 // in the opposite direction. 1341 1342 entry_ref nextRef = fNavigator.CurrentRef(); 1343 if (_PreloadImage(forward, nextRef)) 1344 _PreloadImage(forward, nextRef); 1345 1346 entry_ref previousRef = fNavigator.CurrentRef(); 1347 _PreloadImage(!forward, previousRef); 1348 1349 return B_OK; 1350 } 1351 1352 1353 bool 1354 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref) 1355 { 1356 entry_ref currentRef = ref; 1357 if ((forward && !fNavigator.GetNextFile(currentRef, ref)) 1358 || (!forward && !fNavigator.GetPreviousFile(currentRef, ref))) 1359 return false; 1360 1361 return my_app->DefaultCache().RetrieveImage(ref) == B_OK; 1362 } 1363 1364 1365 void 1366 ShowImageWindow::_ToggleFullScreen() 1367 { 1368 BRect frame; 1369 fFullScreen = !fFullScreen; 1370 if (fFullScreen) { 1371 BScreen screen; 1372 fWindowFrame = Frame(); 1373 frame = screen.Frame(); 1374 frame.top -= fBar->Bounds().Height() + 1; 1375 frame.right += be_control_look->GetScrollBarWidth(B_VERTICAL); 1376 frame.bottom += be_control_look->GetScrollBarWidth(B_HORIZONTAL); 1377 1378 SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE); 1379 1380 Activate(); 1381 // make the window frontmost 1382 } else { 1383 frame = fWindowFrame; 1384 1385 SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); 1386 } 1387 1388 fToolBar->SetActionVisible(MSG_FULL_SCREEN, fFullScreen); 1389 _SetToolBarVisible(!fFullScreen && fShowToolBar); 1390 _SetToolBarBorder(!fFullScreen); 1391 1392 MoveTo(frame.left, frame.top); 1393 ResizeTo(frame.Width(), frame.Height()); 1394 1395 fImageView->SetHideIdlingCursor(fFullScreen); 1396 fImageView->SetShowCaption(fFullScreen && fShowCaption); 1397 1398 Layout(false); 1399 // We need to manually relayout here, as the views are layouted 1400 // asynchronously, and FitToBounds() would still have the wrong size 1401 fImageView->FitToBounds(); 1402 } 1403 1404 1405 void 1406 ShowImageWindow::_ApplySettings() 1407 { 1408 ShowImageSettings* settings = my_app->Settings(); 1409 1410 if (settings->Lock()) { 1411 fShowCaption = settings->GetBool("ShowCaption", fShowCaption); 1412 fPrintOptions.SetBounds(BRect(0, 0, 1023, 767)); 1413 1414 fSlideShowDelay = settings->GetTime("SlideShowDelay", fSlideShowDelay); 1415 1416 fPrintOptions.SetOption((enum PrintOptions::Option)settings->GetInt32( 1417 "PO:Option", fPrintOptions.Option())); 1418 fPrintOptions.SetZoomFactor( 1419 settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor())); 1420 fPrintOptions.SetDPI(settings->GetFloat("PO:DPI", fPrintOptions.DPI())); 1421 fPrintOptions.SetWidth( 1422 settings->GetFloat("PO:Width", fPrintOptions.Width())); 1423 fPrintOptions.SetHeight( 1424 settings->GetFloat("PO:Height", fPrintOptions.Height())); 1425 1426 fShowToolBar = settings->GetBool("ShowToolBar", fShowToolBar); 1427 1428 settings->Unlock(); 1429 } 1430 } 1431 1432 1433 void 1434 ShowImageWindow::_SavePrintOptions() 1435 { 1436 ShowImageSettings* settings = my_app->Settings(); 1437 1438 if (settings->Lock()) { 1439 settings->SetInt32("PO:Option", fPrintOptions.Option()); 1440 settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1441 settings->SetFloat("PO:DPI", fPrintOptions.DPI()); 1442 settings->SetFloat("PO:Width", fPrintOptions.Width()); 1443 settings->SetFloat("PO:Height", fPrintOptions.Height()); 1444 settings->Unlock(); 1445 } 1446 } 1447 1448 1449 bool 1450 ShowImageWindow::_PageSetup() 1451 { 1452 BPrintJob printJob(fImageView->Image()->name); 1453 if (fPrintSettings != NULL) 1454 printJob.SetSettings(new BMessage(*fPrintSettings)); 1455 1456 status_t status = printJob.ConfigPage(); 1457 if (status == B_OK) { 1458 delete fPrintSettings; 1459 fPrintSettings = printJob.Settings(); 1460 } 1461 1462 return status == B_OK; 1463 } 1464 1465 1466 void 1467 ShowImageWindow::_PrepareForPrint() 1468 { 1469 if (fPrintSettings == NULL) { 1470 BPrintJob printJob(fImageView->Image()->name); 1471 if (printJob.ConfigJob() == B_OK) 1472 fPrintSettings = printJob.Settings(); 1473 } 1474 1475 fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds()); 1476 fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1); 1477 1478 new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50), 1479 &fPrintOptions, this); 1480 } 1481 1482 1483 void 1484 ShowImageWindow::_Print(BMessage* msg) 1485 { 1486 status_t st; 1487 if (msg->FindInt32("status", &st) != B_OK || st != B_OK) 1488 return; 1489 1490 _SavePrintOptions(); 1491 1492 BPrintJob printJob(fImageView->Image()->name); 1493 if (fPrintSettings) 1494 printJob.SetSettings(new BMessage(*fPrintSettings)); 1495 1496 if (printJob.ConfigJob() == B_OK) { 1497 delete fPrintSettings; 1498 fPrintSettings = printJob.Settings(); 1499 1500 // first/lastPage is unused for now 1501 int32 firstPage = printJob.FirstPage(); 1502 int32 lastPage = printJob.LastPage(); 1503 BRect printableRect = printJob.PrintableRect(); 1504 1505 if (firstPage < 1) 1506 firstPage = 1; 1507 if (lastPage < firstPage) 1508 lastPage = firstPage; 1509 1510 BBitmap* bitmap = fImageView->Bitmap(); 1511 float imageWidth = bitmap->Bounds().Width() + 1.0; 1512 float imageHeight = bitmap->Bounds().Height() + 1.0; 1513 1514 float width; 1515 switch (fPrintOptions.Option()) { 1516 case PrintOptions::kFitToPage: { 1517 float w1 = printableRect.Width() + 1; 1518 float w2 = imageWidth * (printableRect.Height() + 1) 1519 / imageHeight; 1520 if (w2 < w1) 1521 width = w2; 1522 else 1523 width = w1; 1524 } break; 1525 case PrintOptions::kZoomFactor: 1526 width = imageWidth * fPrintOptions.ZoomFactor(); 1527 break; 1528 case PrintOptions::kDPI: 1529 width = imageWidth * 72.0 / fPrintOptions.DPI(); 1530 break; 1531 case PrintOptions::kWidth: 1532 case PrintOptions::kHeight: 1533 width = fPrintOptions.Width(); 1534 break; 1535 1536 default: 1537 // keep compiler silent; should not reach here 1538 width = imageWidth; 1539 } 1540 1541 // TODO: eventually print large images on several pages 1542 printJob.BeginJob(); 1543 fImageView->SetScale(width / imageWidth); 1544 // coordinates are relative to printable rectangle 1545 BRect bounds(bitmap->Bounds()); 1546 printJob.DrawView(fImageView, bounds, BPoint(0, 0)); 1547 fImageView->SetScale(1.0); 1548 printJob.SpoolPage(); 1549 printJob.CommitJob(); 1550 } 1551 } 1552 1553 1554 void 1555 ShowImageWindow::_SetSlideShowDelay(bigtime_t delay) 1556 { 1557 if (fSlideShowDelay == delay) 1558 return; 1559 1560 fSlideShowDelay = delay; 1561 1562 ShowImageSettings* settings = my_app->Settings(); 1563 if (settings->Lock()) { 1564 settings->SetTime("SlideShowDelay", fSlideShowDelay); 1565 settings->Unlock(); 1566 } 1567 1568 if (fSlideShowRunner != NULL) 1569 _StartSlideShow(); 1570 } 1571 1572 1573 void 1574 ShowImageWindow::_StartSlideShow() 1575 { 1576 _StopSlideShow(); 1577 1578 BMessage nextSlide(kMsgNextSlide); 1579 fSlideShowRunner = new BMessageRunner(this, &nextSlide, fSlideShowDelay); 1580 } 1581 1582 1583 void 1584 ShowImageWindow::_StopSlideShow() 1585 { 1586 if (fSlideShowRunner != NULL) { 1587 delete fSlideShowRunner; 1588 fSlideShowRunner = NULL; 1589 } 1590 } 1591 1592 1593 void 1594 ShowImageWindow::_ResetSlideShowDelay() 1595 { 1596 if (fSlideShowRunner != NULL) 1597 fSlideShowRunner->SetInterval(fSlideShowDelay); 1598 } 1599 1600 1601 void 1602 ShowImageWindow::_UpdateRatingMenu() 1603 { 1604 BFile file(&fNavigator.CurrentRef(), B_READ_ONLY); 1605 if (file.InitCheck() != B_OK) 1606 return; 1607 int32 rating; 1608 ssize_t size = sizeof(rating); 1609 if (file.ReadAttr("Media:Rating", B_INT32_TYPE, 0, &rating, size) != size) 1610 rating = 0; 1611 // TODO: Finding the correct item could be more robust, like by looking 1612 // at the message of each item. 1613 for (int32 i = 1; i <= 10; i++) { 1614 BMenuItem* item = fRatingMenu->ItemAt(i - 1); 1615 if (item == NULL) 1616 break; 1617 item->SetMarked(i == rating); 1618 } 1619 fResetRatingItem->SetEnabled(rating > 0); 1620 } 1621 1622 1623 void 1624 ShowImageWindow::_SaveWidthAndHeight() 1625 { 1626 if (fNavigator.CurrentPage() != 1) 1627 return; 1628 1629 if (fImageView->Bitmap() == NULL) 1630 return; 1631 1632 BRect bounds = fImageView->Bitmap()->Bounds(); 1633 int32 width = bounds.IntegerWidth() + 1; 1634 int32 height = bounds.IntegerHeight() + 1; 1635 1636 BNode node(&fNavigator.CurrentRef()); 1637 if (node.InitCheck() != B_OK) 1638 return; 1639 1640 const char* kWidthAttrName = "Media:Width"; 1641 const char* kHeightAttrName = "Media:Height"; 1642 1643 int32 widthAttr; 1644 ssize_t attrSize = node.ReadAttr(kWidthAttrName, B_INT32_TYPE, 0, 1645 &widthAttr, sizeof(widthAttr)); 1646 if (attrSize <= 0 || widthAttr != width) 1647 node.WriteAttr(kWidthAttrName, B_INT32_TYPE, 0, &width, sizeof(width)); 1648 1649 int32 heightAttr; 1650 attrSize = node.ReadAttr(kHeightAttrName, B_INT32_TYPE, 0, 1651 &heightAttr, sizeof(heightAttr)); 1652 if (attrSize <= 0 || heightAttr != height) 1653 node.WriteAttr(kHeightAttrName, B_INT32_TYPE, 0, &height, sizeof(height)); 1654 } 1655 1656 1657 void 1658 ShowImageWindow::_SetToolBarVisible(bool visible, bool animate) 1659 { 1660 if (visible == fToolBarVisible) 1661 return; 1662 1663 fToolBarVisible = visible; 1664 float diff = fToolBar->Bounds().Height() + 2; 1665 if (!visible) 1666 diff = -diff; 1667 else 1668 fToolBar->Show(); 1669 1670 if (animate) { 1671 // Slide the controls into view. We do this with messages in order 1672 // not to block the window thread. 1673 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 }; 1674 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float); 1675 for (int32 i = 0; i < steps; i++) { 1676 BMessage message(kMsgSlideToolBar); 1677 message.AddFloat("offset", floorf(diff * kAnimationOffsets[i])); 1678 PostMessage(&message, this); 1679 } 1680 BMessage finalMessage(kMsgFinishSlidingToolBar); 1681 finalMessage.AddFloat("offset", visible ? 0 : diff); 1682 finalMessage.AddBool("show", visible); 1683 PostMessage(&finalMessage, this); 1684 } else { 1685 fScrollArea->ResizeBy(0, -diff); 1686 fScrollArea->MoveBy(0, diff); 1687 fToolBar->MoveBy(0, diff); 1688 if (!visible) 1689 fToolBar->Hide(); 1690 } 1691 } 1692 1693 1694 void 1695 ShowImageWindow::_SetToolBarBorder(bool visible) 1696 { 1697 float inset = visible 1698 ? ceilf(be_control_look->DefaultItemSpacing() / 2) : 0; 1699 1700 fToolBar->GroupLayout()->SetInsets(inset, 0, inset, 0); 1701 } 1702 1703 1704 bool 1705 ShowImageWindow::QuitRequested() 1706 { 1707 if (fSavePanel) { 1708 // Don't allow this window to be closed if a save panel is open 1709 return false; 1710 } 1711 1712 if (!_ClosePrompt()) 1713 return false; 1714 1715 ShowImageSettings* settings = my_app->Settings(); 1716 if (settings->Lock()) { 1717 if (fFullScreen) 1718 settings->SetRect("WindowFrame", fWindowFrame); 1719 else 1720 settings->SetRect("WindowFrame", Frame()); 1721 settings->Unlock(); 1722 } 1723 1724 be_app->PostMessage(MSG_WINDOW_HAS_QUIT); 1725 1726 return true; 1727 } 1728