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