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