1 /* 2 * Copyright 2003-2010, 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 */ 15 16 17 #include "ShowImageWindow.h" 18 19 #include <new> 20 #include <stdio.h> 21 #include <stdlib.h> 22 23 #include <Alert.h> 24 #include <Application.h> 25 #include <Bitmap.h> 26 #include <BitmapStream.h> 27 #include <Catalog.h> 28 #include <Clipboard.h> 29 #include <Entry.h> 30 #include <File.h> 31 #include <FilePanel.h> 32 #include <Locale.h> 33 #include <Menu.h> 34 #include <MenuBar.h> 35 #include <MenuItem.h> 36 #include <Path.h> 37 #include <PrintJob.h> 38 #include <RecentItems.h> 39 #include <Roster.h> 40 #include <Screen.h> 41 #include <ScrollView.h> 42 #include <String.h> 43 #include <SupportDefs.h> 44 #include <TranslationDefs.h> 45 #include <TranslationUtils.h> 46 #include <TranslatorRoster.h> 47 48 #include "ImageCache.h" 49 #include "ProgressWindow.h" 50 #include "ShowImageApp.h" 51 #include "ShowImageConstants.h" 52 #include "ShowImageStatusView.h" 53 #include "ShowImageView.h" 54 55 56 // BMessage field names used in Save messages 57 const char* kTypeField = "be:type"; 58 const char* kTranslatorField = "be:translator"; 59 60 61 // message constants 62 enum { 63 MSG_CAPTURE_MOUSE = 'mCPM', 64 MSG_CHANGE_FOCUS = 'mCFS', 65 MSG_WINDOW_QUIT = 'mWQT', 66 MSG_OUTPUT_TYPE = 'BTMN', 67 MSG_SAVE_PANEL = 'mFSP', 68 MSG_CLEAR_SELECT = 'mCSL', 69 MSG_SELECT_ALL = 'mSAL', 70 MSG_SELECTION_MODE = 'mSLM', 71 MSG_PAGE_FIRST = 'mPGF', 72 MSG_PAGE_LAST = 'mPGL', 73 MSG_PAGE_NEXT = 'mPGN', 74 MSG_PAGE_PREV = 'mPGP', 75 MSG_GOTO_PAGE = 'mGTP', 76 MSG_ZOOM_IN = 'mZIN', 77 MSG_ZOOM_OUT = 'mZOU', 78 MSG_SCALE_BILINEAR = 'mSBL', 79 MSG_DESKTOP_BACKGROUND = 'mDBG', 80 MSG_ROTATE_90 = 'mR90', 81 MSG_ROTATE_270 = 'mR27', 82 MSG_FLIP_LEFT_TO_RIGHT = 'mFLR', 83 MSG_FLIP_TOP_TO_BOTTOM = 'mFTB', 84 MSG_SLIDE_SHOW_DELAY = 'mSSD', 85 MSG_FULL_SCREEN = 'mFSC', 86 MSG_SHOW_CAPTION = 'mSCP', 87 MSG_PAGE_SETUP = 'mPSU', 88 MSG_PREPARE_PRINT = 'mPPT', 89 kMsgFitToWindow = 'mFtW', 90 kMsgOriginalSize = 'mOSZ', 91 kMsgStretchToWindow = 'mStW' 92 }; 93 94 95 // This is temporary solution for building BString with printf like format. 96 // will be removed in the future. 97 static void 98 bs_printf(BString* string, const char* format, ...) 99 { 100 va_list ap; 101 char* buf; 102 103 va_start(ap, format); 104 vasprintf(&buf, format, ap); 105 string->SetTo(buf); 106 free(buf); 107 va_end(ap); 108 } 109 110 111 // #pragma mark -- ShowImageWindow 112 113 114 ShowImageWindow::ShowImageWindow(const entry_ref& ref, 115 const BMessenger& trackerMessenger) 116 : 117 BWindow(BRect(5, 24, 250, 100), "", B_DOCUMENT_WINDOW, 0), 118 fNavigator(ref, trackerMessenger), 119 fSavePanel(NULL), 120 fBar(NULL), 121 fBrowseMenu(NULL), 122 fGoToPageMenu(NULL), 123 fSlideShowDelay(NULL), 124 fImageView(NULL), 125 fStatusView(NULL), 126 fProgressWindow(new ProgressWindow()), 127 fModified(false), 128 fFullScreen(false), 129 fShowCaption(true), 130 fPrintSettings(NULL) 131 { 132 _ApplySettings(); 133 134 // create menu bar 135 fBar = new BMenuBar(BRect(0, 0, Bounds().right, 1), "menu_bar"); 136 _AddMenus(fBar); 137 AddChild(fBar); 138 139 BRect viewFrame = Bounds(); 140 viewFrame.top = fBar->Bounds().Height() + 1; 141 viewFrame.right -= B_V_SCROLL_BAR_WIDTH; 142 viewFrame.bottom -= B_H_SCROLL_BAR_HEIGHT; 143 144 // create the image view 145 fImageView = new ShowImageView(viewFrame, "image_view", B_FOLLOW_ALL, 146 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_PULSE_NEEDED 147 | B_FRAME_EVENTS); 148 // wrap a scroll view around the view 149 BScrollView* scrollView = new BScrollView("image_scroller", fImageView, 150 B_FOLLOW_ALL, 0, false, false, B_PLAIN_BORDER); 151 AddChild(scrollView); 152 153 const int32 kstatusWidth = 190; 154 BRect rect; 155 rect = Bounds(); 156 rect.top = viewFrame.bottom + 1; 157 rect.left = viewFrame.left + kstatusWidth; 158 rect.right = viewFrame.right + 1; 159 rect.bottom += 1; 160 BScrollBar* horizontalScrollBar = new BScrollBar(rect, "hscroll", 161 fImageView, 0, 150, B_HORIZONTAL); 162 AddChild(horizontalScrollBar); 163 164 rect.left = 0; 165 rect.right = kstatusWidth - 1; 166 rect.bottom -= 1; 167 fStatusView = new ShowImageStatusView(rect, "status_view", B_FOLLOW_BOTTOM, 168 B_WILL_DRAW); 169 AddChild(fStatusView); 170 171 rect = Bounds(); 172 rect.top = viewFrame.top - 1; 173 rect.left = viewFrame.right + 1; 174 rect.bottom = viewFrame.bottom + 1; 175 rect.right += 1; 176 BScrollBar* verticalScrollBar = new BScrollBar(rect, "vscroll", fImageView, 177 0, 150, B_VERTICAL); 178 AddChild(verticalScrollBar); 179 180 SetSizeLimits(250, 100000, 100, 100000); 181 182 // finish creating the window 183 if (_LoadImage() != B_OK) { 184 _LoadError(ref); 185 Quit(); 186 return; 187 } 188 189 // add View menu here so it can access ShowImageView methods 190 BMenu* menu = new BMenu(B_TRANSLATE_WITH_CONTEXT("View", "Menus")); 191 _BuildViewMenu(menu, false); 192 fBar->AddItem(menu); 193 194 SetPulseRate(100000); 195 // every 1/10 second; ShowImageView needs it for marching ants 196 197 _MarkMenuItem(menu, MSG_SELECTION_MODE, 198 fImageView->IsSelectionModeEnabled()); 199 200 // Tell application object to query the clipboard 201 // and tell this window if it contains interesting data or not 202 be_app_messenger.SendMessage(B_CLIPBOARD_CHANGED); 203 204 // The window will be shown on screen automatically 205 Run(); 206 } 207 208 209 ShowImageWindow::~ShowImageWindow() 210 { 211 fProgressWindow->Lock(); 212 fProgressWindow->Quit(); 213 } 214 215 216 void 217 ShowImageWindow::BuildContextMenu(BMenu* menu) 218 { 219 _BuildViewMenu(menu, true); 220 } 221 222 223 #undef B_TRANSLATE_CONTEXT 224 #define B_TRANSLATE_CONTEXT "Menus" 225 226 227 void 228 ShowImageWindow::_BuildViewMenu(BMenu* menu, bool popupMenu) 229 { 230 _AddItemMenu(menu, B_TRANSLATE("Slide show"), MSG_SLIDE_SHOW, 0, 0, this); 231 _MarkMenuItem(menu, MSG_SLIDE_SHOW, fImageView->SlideShowStarted()); 232 BMenu* delayMenu = new BMenu(B_TRANSLATE("Slide delay")); 233 if (fSlideShowDelay == NULL) 234 fSlideShowDelay = delayMenu; 235 236 delayMenu->SetRadioMode(true); 237 // Note: ShowImage loads images in window thread so it becomes unresponsive 238 // if slide show delay is too short! (Especially if loading the image 239 // takes as long as or longer than the slide show delay). Should load 240 // in background thread! 241 _AddDelayItem(delayMenu, B_TRANSLATE("3 seconds"), 3); 242 _AddDelayItem(delayMenu, B_TRANSLATE("4 seconds"), 4); 243 _AddDelayItem(delayMenu, B_TRANSLATE("5 seconds"), 5); 244 _AddDelayItem(delayMenu, B_TRANSLATE("6 seconds"), 6); 245 _AddDelayItem(delayMenu, B_TRANSLATE("7 seconds"), 7); 246 _AddDelayItem(delayMenu, B_TRANSLATE("8 seconds"), 8); 247 _AddDelayItem(delayMenu, B_TRANSLATE("9 seconds"), 9); 248 _AddDelayItem(delayMenu, B_TRANSLATE("10 seconds"), 10); 249 _AddDelayItem(delayMenu, B_TRANSLATE("20 seconds"), 20); 250 menu->AddItem(delayMenu); 251 252 menu->AddSeparatorItem(); 253 254 _AddItemMenu(menu, B_TRANSLATE("Original size"), 255 kMsgOriginalSize, '1', 0, this); 256 _AddItemMenu(menu, B_TRANSLATE("Fit to window"), 257 kMsgFitToWindow, '0', 0, this); 258 _AddItemMenu(menu, B_TRANSLATE("Zoom in"), MSG_ZOOM_IN, '+', 0, this); 259 _AddItemMenu(menu, B_TRANSLATE("Zoom out"), MSG_ZOOM_OUT, '-', 0, this); 260 261 menu->AddSeparatorItem(); 262 263 if (!popupMenu || fFullScreen) { 264 _AddItemMenu(menu, B_TRANSLATE("High-quality zooming"), 265 MSG_SCALE_BILINEAR, 0, 0, this); 266 _AddItemMenu(menu, B_TRANSLATE("Stretch to window"), 267 kMsgStretchToWindow, 0, 0, this); 268 269 menu->AddSeparatorItem(); 270 } 271 272 _AddItemMenu(menu, B_TRANSLATE("Full screen"), 273 MSG_FULL_SCREEN, B_ENTER, 0, this); 274 _MarkMenuItem(menu, MSG_FULL_SCREEN, fFullScreen); 275 276 _AddItemMenu(menu, B_TRANSLATE("Show caption in full screen mode"), 277 MSG_SHOW_CAPTION, 0, 0, this); 278 _MarkMenuItem(menu, MSG_SHOW_CAPTION, fShowCaption); 279 280 _MarkMenuItem(menu, MSG_SCALE_BILINEAR, fImageView->ScaleBilinear()); 281 _MarkMenuItem(menu, kMsgStretchToWindow, fImageView->StretchesToBounds()); 282 283 if (popupMenu) { 284 menu->AddSeparatorItem(); 285 _AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS), 286 MSG_DESKTOP_BACKGROUND, 0, 0, this); 287 } 288 } 289 290 291 void 292 ShowImageWindow::_AddMenus(BMenuBar* bar) 293 { 294 BMenu* menu = new BMenu(B_TRANSLATE("File")); 295 296 // Add recent files to "Open File" entry as sub-menu. 297 BMenuItem* item = new BMenuItem(BRecentFilesList::NewFileListMenu( 298 B_TRANSLATE("Open"B_UTF8_ELLIPSIS), NULL, NULL, be_app, 10, true, 299 NULL, kApplicationSignature), new BMessage(MSG_FILE_OPEN)); 300 item->SetShortcut('O', 0); 301 item->SetTarget(be_app); 302 menu->AddItem(item); 303 menu->AddSeparatorItem(); 304 305 BMenu* menuSaveAs = new BMenu(B_TRANSLATE("Save as" B_UTF8_ELLIPSIS), 306 B_ITEMS_IN_COLUMN); 307 BTranslationUtils::AddTranslationItems(menuSaveAs, B_TRANSLATOR_BITMAP); 308 // Fill Save As submenu with all types that can be converted 309 // to from the Be bitmap image format 310 menu->AddItem(menuSaveAs); 311 _AddItemMenu(menu, B_TRANSLATE("Close"), B_QUIT_REQUESTED, 'W', 0, this); 312 menu->AddSeparatorItem(); 313 _AddItemMenu(menu, B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 314 MSG_PAGE_SETUP, 0, 0, this); 315 _AddItemMenu(menu, B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 316 MSG_PREPARE_PRINT, 'P', 0, this); 317 menu->AddSeparatorItem(); 318 _AddItemMenu(menu, B_TRANSLATE("About ShowImage" B_UTF8_ELLIPSIS), 319 B_ABOUT_REQUESTED, 0, 0, be_app); 320 menu->AddSeparatorItem(); 321 _AddItemMenu(menu, B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q', 0, be_app); 322 bar->AddItem(menu); 323 324 menu = new BMenu(B_TRANSLATE("Edit")); 325 _AddItemMenu(menu, B_TRANSLATE("Undo"), B_UNDO, 'Z', 0, this, false); 326 menu->AddSeparatorItem(); 327 _AddItemMenu(menu, B_TRANSLATE("Copy"), B_COPY, 'C', 0, this, false); 328 menu->AddSeparatorItem(); 329 _AddItemMenu(menu, B_TRANSLATE("Selection Mode"), MSG_SELECTION_MODE, 0, 0, 330 this); 331 _AddItemMenu(menu, B_TRANSLATE("Clear selection"), 332 MSG_CLEAR_SELECT, 0, 0, this, false); 333 _AddItemMenu(menu, B_TRANSLATE("Select all"), 334 MSG_SELECT_ALL, 'A', 0, this); 335 bar->AddItem(menu); 336 337 menu = fBrowseMenu = new BMenu(B_TRANSLATE("Browse")); 338 _AddItemMenu(menu, B_TRANSLATE("First page"), 339 MSG_PAGE_FIRST, B_LEFT_ARROW, B_SHIFT_KEY, this); 340 _AddItemMenu(menu, B_TRANSLATE("Last page"), 341 MSG_PAGE_LAST, B_RIGHT_ARROW, B_SHIFT_KEY, this); 342 _AddItemMenu(menu, B_TRANSLATE("Previous page"), 343 MSG_PAGE_PREV, B_LEFT_ARROW, 0, this); 344 _AddItemMenu(menu, B_TRANSLATE("Next page"), 345 MSG_PAGE_NEXT, B_RIGHT_ARROW, 0, this); 346 fGoToPageMenu = new BMenu(B_TRANSLATE("Go to page")); 347 fGoToPageMenu->SetRadioMode(true); 348 menu->AddItem(fGoToPageMenu); 349 menu->AddSeparatorItem(); 350 _AddItemMenu(menu, B_TRANSLATE("Previous file"), 351 MSG_FILE_PREV, B_UP_ARROW, 0, this); 352 _AddItemMenu(menu, B_TRANSLATE("Next file"), 353 MSG_FILE_NEXT, B_DOWN_ARROW, 0, this); 354 bar->AddItem(menu); 355 356 menu = new BMenu(B_TRANSLATE("Image")); 357 _AddItemMenu(menu, B_TRANSLATE("Rotate clockwise"), 358 MSG_ROTATE_90, 'R', 0, this); 359 _AddItemMenu(menu, B_TRANSLATE("Rotate counterclockwise"), 360 MSG_ROTATE_270, 'R', B_SHIFT_KEY, this); 361 menu->AddSeparatorItem(); 362 _AddItemMenu(menu, B_TRANSLATE("Flip left to right"), 363 MSG_FLIP_LEFT_TO_RIGHT, 0, 0, this); 364 _AddItemMenu(menu, B_TRANSLATE("Flip top to bottom"), 365 MSG_FLIP_TOP_TO_BOTTOM, 0, 0, this); 366 menu->AddSeparatorItem(); 367 _AddItemMenu(menu, B_TRANSLATE("Use as background" B_UTF8_ELLIPSIS), 368 MSG_DESKTOP_BACKGROUND, 0, 0, this); 369 370 bar->AddItem(menu); 371 } 372 373 374 BMenuItem* 375 ShowImageWindow::_AddItemMenu(BMenu* menu, const char* label, uint32 what, 376 char shortcut, uint32 modifier, const BHandler* target, bool enabled) 377 { 378 BMenuItem* item = new BMenuItem(label, new BMessage(what), shortcut, 379 modifier); 380 menu->AddItem(item); 381 382 item->SetTarget(target); 383 item->SetEnabled(enabled); 384 385 return item; 386 } 387 388 389 BMenuItem* 390 ShowImageWindow::_AddDelayItem(BMenu* menu, const char* label, float value) 391 { 392 BMessage* message = new BMessage(MSG_SLIDE_SHOW_DELAY); 393 message->AddFloat("value", value); 394 395 BMenuItem* item = new BMenuItem(label, message, 0); 396 item->SetTarget(this); 397 398 bool marked = fImageView->GetSlideShowDelay() == value; 399 if (marked) 400 item->SetMarked(true); 401 402 menu->AddItem(item); 403 return item; 404 } 405 406 407 void 408 ShowImageWindow::_ResizeWindowToImage() 409 { 410 BBitmap* bitmap = fImageView->Bitmap(); 411 BScreen screen; 412 if (bitmap == NULL || !screen.IsValid()) 413 return; 414 415 // TODO: use View::GetPreferredSize() instead? 416 BRect r(bitmap->Bounds()); 417 float width = r.Width() + B_V_SCROLL_BAR_WIDTH; 418 float height = r.Height() + 1 + fBar->Frame().Height() 419 + B_H_SCROLL_BAR_HEIGHT; 420 421 BRect frame = screen.Frame(); 422 const float windowBorder = 5; 423 // dimensions so that window does not reach outside of screen 424 float maxWidth = frame.Width() + 1 - windowBorder - Frame().left; 425 float maxHeight = frame.Height() + 1 - windowBorder - Frame().top; 426 427 // We have to check size limits manually, otherwise 428 // menu bar will be too short for small images. 429 430 float minW, maxW, minH, maxH; 431 GetSizeLimits(&minW, &maxW, &minH, &maxH); 432 if (maxWidth > maxW) 433 maxWidth = maxW; 434 if (maxHeight > maxH) 435 maxHeight = maxH; 436 if (width < minW) 437 width = minW; 438 if (height < minH) 439 height = minH; 440 441 if (width > maxWidth) 442 width = maxWidth; 443 if (height > maxHeight) 444 height = maxHeight; 445 446 ResizeTo(width, height); 447 } 448 449 450 bool 451 ShowImageWindow::_ToggleMenuItem(uint32 what) 452 { 453 bool marked = false; 454 BMenuItem* item = fBar->FindItem(what); 455 if (item != NULL) { 456 marked = !item->IsMarked(); 457 item->SetMarked(marked); 458 } 459 return marked; 460 } 461 462 463 void 464 ShowImageWindow::_EnableMenuItem(BMenu* menu, uint32 what, bool enable) 465 { 466 BMenuItem* item = menu->FindItem(what); 467 if (item && item->IsEnabled() != enable) 468 item->SetEnabled(enable); 469 } 470 471 472 void 473 ShowImageWindow::_MarkMenuItem(BMenu* menu, uint32 what, bool marked) 474 { 475 BMenuItem* item = menu->FindItem(what); 476 if (item && item->IsMarked() != marked) 477 item->SetMarked(marked); 478 } 479 480 481 void 482 ShowImageWindow::_MarkSlideShowDelay(float value) 483 { 484 const int32 n = fSlideShowDelay->CountItems(); 485 float v; 486 for (int32 i = 0; i < n; i ++) { 487 BMenuItem* item = fSlideShowDelay->ItemAt(i); 488 if (item) { 489 if (item->Message()->FindFloat("value", &v) == B_OK && v == value) { 490 if (!item->IsMarked()) 491 item->SetMarked(true); 492 return; 493 } 494 } 495 } 496 } 497 498 499 void 500 ShowImageWindow::Zoom(BPoint origin, float width, float height) 501 { 502 _ToggleFullScreen(); 503 } 504 505 506 void 507 ShowImageWindow::MessageReceived(BMessage* message) 508 { 509 if (message->WasDropped()) { 510 uint32 type; 511 int32 count; 512 status_t status = message->GetInfo("refs", &type, &count); 513 if (status == B_OK && type == B_REF_TYPE) { 514 message->what = B_REFS_RECEIVED; 515 be_app->PostMessage(message); 516 } 517 } 518 519 switch (message->what) { 520 case kMsgImageCacheImageLoaded: 521 { 522 fProgressWindow->Stop(); 523 524 BitmapOwner* bitmapOwner = NULL; 525 message->FindPointer("bitmapOwner", (void**)&bitmapOwner); 526 527 bool first = fImageView->Bitmap() == NULL; 528 entry_ref ref; 529 message->FindRef("ref", &ref); 530 if (!first && ref != fNavigator.CurrentRef()) { 531 // ignore older images 532 if (bitmapOwner != NULL) 533 bitmapOwner->ReleaseReference(); 534 break; 535 } 536 537 status_t status = fImageView->SetImage(message); 538 if (status != B_OK) { 539 if (bitmapOwner != NULL) 540 bitmapOwner->ReleaseReference(); 541 542 _LoadError(ref); 543 544 // quit if file could not be opened 545 if (first) 546 Quit(); 547 break; 548 } 549 550 fImageType = message->FindString("type"); 551 fNavigator.SetTo(ref, message->FindInt32("page"), 552 message->FindInt32("pageCount")); 553 554 if (first || (!fImageView->StretchesToBounds() && !fFullScreen)) { 555 _ResizeWindowToImage(); 556 fImageView->FitToBounds(); 557 } 558 if (first) { 559 fImageView->MakeFocus(true); 560 // to receive key messages 561 Show(); 562 } 563 break; 564 } 565 566 case kMsgImageCacheProgressUpdate: 567 { 568 entry_ref ref; 569 if (message->FindRef("ref", &ref) == B_OK 570 && ref == fNavigator.CurrentRef()) { 571 message->what = kMsgProgressUpdate; 572 fProgressWindow->PostMessage(message); 573 } 574 break; 575 } 576 577 case MSG_MODIFIED: 578 // If image has been modified due to a Cut or Paste 579 fModified = true; 580 break; 581 582 case MSG_OUTPUT_TYPE: 583 // User clicked Save As then choose an output format 584 if (!fSavePanel) 585 // If user doesn't already have a save panel open 586 _SaveAs(message); 587 break; 588 589 case MSG_SAVE_PANEL: 590 // User specified where to save the output image 591 _SaveToFile(message); 592 break; 593 594 case B_CANCEL: 595 delete fSavePanel; 596 fSavePanel = NULL; 597 break; 598 599 case MSG_UPDATE_STATUS: 600 { 601 int32 pages = fNavigator.PageCount(); 602 int32 currentPage = fNavigator.CurrentPage(); 603 604 bool enable = pages > 1 ? true : false; 605 _EnableMenuItem(fBar, MSG_PAGE_FIRST, enable); 606 _EnableMenuItem(fBar, MSG_PAGE_LAST, enable); 607 _EnableMenuItem(fBar, MSG_PAGE_NEXT, enable); 608 _EnableMenuItem(fBar, MSG_PAGE_PREV, enable); 609 fGoToPageMenu->SetEnabled(enable); 610 611 _EnableMenuItem(fBar, MSG_FILE_NEXT, fNavigator.HasNextFile()); 612 _EnableMenuItem(fBar, MSG_FILE_PREV, fNavigator.HasPreviousFile()); 613 614 if (fGoToPageMenu->CountItems() != pages) { 615 // Only rebuild the submenu if the number of 616 // pages is different 617 618 while (fGoToPageMenu->CountItems() > 0) { 619 // Remove all page numbers 620 delete fGoToPageMenu->RemoveItem(0L); 621 } 622 623 for (int32 i = 1; i <= pages; i++) { 624 // Fill Go To page submenu with an entry for each page 625 BMessage* goTo = new BMessage(MSG_GOTO_PAGE); 626 goTo->AddInt32("page", i); 627 628 char shortcut = 0; 629 if (i < 10) 630 shortcut = '0' + i; 631 632 BString strCaption; 633 strCaption << i; 634 635 BMenuItem* item = new BMenuItem(strCaption.String(), goTo, 636 B_SHIFT_KEY, shortcut); 637 if (currentPage == i) 638 item->SetMarked(true); 639 fGoToPageMenu->AddItem(item); 640 } 641 } else { 642 // Make sure the correct page is marked 643 BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1); 644 if (currentItem != NULL && !currentItem->IsMarked()) 645 currentItem->SetMarked(true); 646 } 647 648 _UpdateStatusText(message); 649 650 BPath path(fImageView->Image()); 651 SetTitle(path.Path()); 652 break; 653 } 654 655 case MSG_UPDATE_STATUS_TEXT: 656 { 657 _UpdateStatusText(message); 658 break; 659 } 660 661 case MSG_SELECTION: 662 { 663 // The view sends this message when a selection is 664 // made or the selection is cleared so that the window 665 // can update the state of the appropriate menu items 666 bool enable; 667 if (message->FindBool("has_selection", &enable) == B_OK) { 668 _EnableMenuItem(fBar, B_COPY, enable); 669 _EnableMenuItem(fBar, MSG_CLEAR_SELECT, enable); 670 } 671 break; 672 } 673 674 case MSG_UNDO_STATE: 675 { 676 bool enable; 677 if (message->FindBool("can_undo", &enable) == B_OK) 678 _EnableMenuItem(fBar, B_UNDO, enable); 679 break; 680 } 681 682 case B_UNDO: 683 fImageView->Undo(); 684 break; 685 686 case B_COPY: 687 fImageView->CopySelectionToClipboard(); 688 break; 689 690 case MSG_SELECTION_MODE: 691 fImageView->SetSelectionMode(_ToggleMenuItem(MSG_SELECTION_MODE)); 692 break; 693 694 case MSG_CLEAR_SELECT: 695 fImageView->ClearSelection(); 696 break; 697 698 case MSG_SELECT_ALL: 699 fImageView->SelectAll(); 700 break; 701 702 case MSG_PAGE_FIRST: 703 if (_ClosePrompt() && fNavigator.FirstPage()) 704 _LoadImage(); 705 break; 706 707 case MSG_PAGE_LAST: 708 if (_ClosePrompt() && fNavigator.LastPage()) 709 _LoadImage(); 710 break; 711 712 case MSG_PAGE_NEXT: 713 if (_ClosePrompt() && fNavigator.NextPage()) 714 _LoadImage(); 715 break; 716 717 case MSG_PAGE_PREV: 718 if (_ClosePrompt() && fNavigator.PreviousPage()) 719 _LoadImage(); 720 break; 721 722 case MSG_GOTO_PAGE: 723 { 724 if (!_ClosePrompt()) 725 break; 726 727 int32 newPage; 728 if (message->FindInt32("page", &newPage) != B_OK) 729 break; 730 731 int32 currentPage = fNavigator.CurrentPage(); 732 int32 pages = fNavigator.PageCount(); 733 734 // TODO: use radio mode instead! 735 if (newPage > 0 && newPage <= pages) { 736 BMenuItem* currentItem = fGoToPageMenu->ItemAt(currentPage - 1); 737 BMenuItem* newItem = fGoToPageMenu->ItemAt(newPage - 1); 738 if (currentItem != NULL && newItem != NULL) { 739 currentItem->SetMarked(false); 740 newItem->SetMarked(true); 741 if (fNavigator.GoToPage(newPage)) 742 _LoadImage(); 743 } 744 } 745 break; 746 } 747 748 case kMsgFitToWindow: 749 fImageView->FitToBounds(); 750 break; 751 752 case kMsgStretchToWindow: 753 fImageView->SetStretchToBounds( 754 _ToggleMenuItem(kMsgStretchToWindow)); 755 break; 756 757 case MSG_FILE_PREV: 758 if (_ClosePrompt() && fNavigator.PreviousFile()) 759 _LoadImage(false); 760 break; 761 762 case MSG_FILE_NEXT: 763 if (_ClosePrompt() && fNavigator.NextFile()) 764 _LoadImage(); 765 break; 766 767 case kMsgDeleteCurrentFile: 768 { 769 if (fNavigator.MoveFileToTrash()) 770 _LoadImage(); 771 else 772 PostMessage(B_QUIT_REQUESTED); 773 break; 774 } 775 776 case MSG_ROTATE_90: 777 fImageView->Rotate(90); 778 break; 779 780 case MSG_ROTATE_270: 781 fImageView->Rotate(270); 782 break; 783 784 case MSG_FLIP_LEFT_TO_RIGHT: 785 fImageView->Flip(true); 786 break; 787 788 case MSG_FLIP_TOP_TO_BOTTOM: 789 fImageView->Flip(false); 790 break; 791 792 case MSG_SLIDE_SHOW: 793 { 794 BMenuItem* item = fBar->FindItem(message->what); 795 if (!item) 796 break; 797 if (item->IsMarked()) { 798 item->SetMarked(false); 799 fImageView->StopSlideShow(); 800 } else if (_ClosePrompt()) { 801 item->SetMarked(true); 802 fImageView->StartSlideShow(); 803 } 804 break; 805 } 806 807 case MSG_SLIDE_SHOW_DELAY: 808 { 809 float value; 810 if (message->FindFloat("value", &value) == B_OK) { 811 fImageView->SetSlideShowDelay(value); 812 // in case message is sent from popup menu 813 _MarkSlideShowDelay(value); 814 } 815 break; 816 } 817 818 case MSG_FULL_SCREEN: 819 _ToggleFullScreen(); 820 break; 821 822 case MSG_EXIT_FULL_SCREEN: 823 if (fFullScreen) 824 _ToggleFullScreen(); 825 break; 826 827 case MSG_SHOW_CAPTION: { 828 fShowCaption = _ToggleMenuItem(message->what); 829 ShowImageSettings* settings = my_app->Settings(); 830 831 if (settings->Lock()) { 832 settings->SetBool("ShowCaption", fShowCaption); 833 settings->Unlock(); 834 } 835 if (fFullScreen) 836 fImageView->SetShowCaption(fShowCaption); 837 } break; 838 839 case MSG_PAGE_SETUP: 840 _PageSetup(); 841 break; 842 843 case MSG_PREPARE_PRINT: 844 _PrepareForPrint(); 845 break; 846 847 case MSG_PRINT: 848 _Print(message); 849 break; 850 851 case MSG_ZOOM_IN: 852 fImageView->ZoomIn(); 853 break; 854 855 case MSG_ZOOM_OUT: 856 fImageView->ZoomOut(); 857 break; 858 859 case kMsgOriginalSize: 860 fImageView->SetZoom(1.0); 861 break; 862 863 case MSG_SCALE_BILINEAR: 864 fImageView->SetScaleBilinear(_ToggleMenuItem(message->what)); 865 break; 866 867 case MSG_DESKTOP_BACKGROUND: 868 { 869 BMessage backgroundsMessage(B_REFS_RECEIVED); 870 backgroundsMessage.AddRef("refs", fImageView->Image()); 871 // This is used in the Backgrounds code for scaled placement 872 backgroundsMessage.AddInt32("placement", 'scpl'); 873 be_roster->Launch("application/x-vnd.haiku-backgrounds", &backgroundsMessage); 874 break; 875 } 876 877 default: 878 BWindow::MessageReceived(message); 879 break; 880 } 881 } 882 883 884 void 885 ShowImageWindow::_UpdateStatusText(const BMessage* message) 886 { 887 BString status; 888 if (fImageView->Bitmap() != NULL) { 889 BRect bounds = fImageView->Bitmap()->Bounds(); 890 status << bounds.IntegerWidth() + 1 891 << "x" << bounds.IntegerHeight() + 1 << ", " << fImageType; 892 } 893 894 BString text; 895 if (message != NULL && message->FindString("status", &text) == B_OK 896 && text.Length() > 0) { 897 status << ", " << text; 898 } 899 900 fStatusView->Update(fNavigator.CurrentRef(), status); 901 } 902 903 904 void 905 ShowImageWindow::_LoadError(const entry_ref& ref) 906 { 907 // TODO: give a better error message! 908 BAlert* alert = new BAlert(B_TRANSLATE("ShowImage"), 909 B_TRANSLATE_WITH_CONTEXT("Could not load image! Either the " 910 "file or an image translator for it does not exist.", 911 "LoadAlerts"), 912 B_TRANSLATE_WITH_CONTEXT("OK", "Alerts"), NULL, NULL, 913 B_WIDTH_AS_USUAL, B_INFO_ALERT); 914 alert->Go(); 915 } 916 917 918 void 919 ShowImageWindow::_SaveAs(BMessage* message) 920 { 921 // Read the translator and output type the user chose 922 translator_id outTranslator; 923 uint32 outType; 924 if (message->FindInt32(kTranslatorField, 925 reinterpret_cast<int32 *>(&outTranslator)) != B_OK 926 || message->FindInt32(kTypeField, 927 reinterpret_cast<int32 *>(&outType)) != B_OK) 928 return; 929 930 // Add the chosen translator and output type to the 931 // message that the save panel will send back 932 BMessage panelMsg(MSG_SAVE_PANEL); 933 panelMsg.AddInt32(kTranslatorField, outTranslator); 934 panelMsg.AddInt32(kTypeField, outType); 935 936 // Create save panel and show it 937 BMessenger target(this); 938 fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL, 939 &target, NULL, 0, false, &panelMsg); 940 if (!fSavePanel) 941 return; 942 943 fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE); 944 fSavePanel->Show(); 945 } 946 947 948 void 949 ShowImageWindow::_SaveToFile(BMessage* message) 950 { 951 // Read in where the file should be saved 952 entry_ref dirRef; 953 if (message->FindRef("directory", &dirRef) != B_OK) 954 return; 955 956 const char* filename; 957 if (message->FindString("name", &filename) != B_OK) 958 return; 959 960 // Read in the translator and type to be used 961 // to save the output image 962 translator_id outTranslator; 963 uint32 outType; 964 if (message->FindInt32(kTranslatorField, 965 reinterpret_cast<int32 *>(&outTranslator)) != B_OK 966 || message->FindInt32(kTypeField, 967 reinterpret_cast<int32 *>(&outType)) != B_OK) 968 return; 969 970 // Find the translator_format information needed to 971 // write a MIME attribute for the image file 972 BTranslatorRoster* roster = BTranslatorRoster::Default(); 973 const translation_format* outFormat = NULL; 974 int32 outCount = 0; 975 if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK 976 || outCount < 1) 977 return; 978 979 int32 i; 980 for (i = 0; i < outCount; i++) { 981 if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type == outType) 982 break; 983 } 984 if (i == outCount) 985 return; 986 987 // Write out the image file 988 BDirectory dir(&dirRef); 989 fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]); 990 } 991 992 993 #undef B_TRANSLATE_CONTEXT 994 #define B_TRANSLATE_CONTEXT "ClosePrompt" 995 996 997 bool 998 ShowImageWindow::_ClosePrompt() 999 { 1000 if (!fModified) 1001 return true; 1002 1003 int32 count = fNavigator.PageCount(); 1004 int32 page = fNavigator.CurrentPage(); 1005 BString prompt; 1006 1007 if (count > 1) { 1008 bs_printf(&prompt, 1009 B_TRANSLATE("The document '%s' (page %d) has been changed. Do you " 1010 "want to close the document?"), 1011 fImageView->Image()->name, page); 1012 } else { 1013 bs_printf(&prompt, 1014 B_TRANSLATE("The document '%s' has been changed. Do you want to " 1015 "close the document?"), 1016 fImageView->Image()->name); 1017 } 1018 1019 BAlert* alert = new BAlert(B_TRANSLATE("Close document"), prompt.String(), 1020 B_TRANSLATE("Cancel"), B_TRANSLATE("Close")); 1021 if (alert->Go() == 0) { 1022 // Cancel 1023 return false; 1024 } 1025 1026 // Close 1027 fModified = false; 1028 return true; 1029 } 1030 1031 1032 status_t 1033 ShowImageWindow::_LoadImage(bool forward) 1034 { 1035 BMessenger us(this); 1036 status_t status = ImageCache::Default().RetrieveImage( 1037 fNavigator.CurrentRef(), fNavigator.CurrentPage(), &us); 1038 if (status != B_OK) 1039 return status; 1040 1041 fProgressWindow->Start(this); 1042 1043 // Preload previous/next images - two in the navigation direction, one 1044 // in the opposite direction. 1045 1046 entry_ref nextRef = fNavigator.CurrentRef(); 1047 if (_PreloadImage(forward, nextRef)) 1048 _PreloadImage(forward, nextRef); 1049 1050 entry_ref previousRef = fNavigator.CurrentRef(); 1051 _PreloadImage(!forward, previousRef); 1052 1053 return B_OK; 1054 } 1055 1056 1057 bool 1058 ShowImageWindow::_PreloadImage(bool forward, entry_ref& ref) 1059 { 1060 entry_ref currentRef = ref; 1061 if ((forward && !fNavigator.GetNextFile(currentRef, ref)) 1062 || (!forward && !fNavigator.GetPreviousFile(currentRef, ref))) 1063 return false; 1064 1065 return ImageCache::Default().RetrieveImage(ref) == B_OK; 1066 } 1067 1068 1069 void 1070 ShowImageWindow::_ToggleFullScreen() 1071 { 1072 BRect frame; 1073 fFullScreen = !fFullScreen; 1074 if (fFullScreen) { 1075 BScreen screen; 1076 fWindowFrame = Frame(); 1077 frame = screen.Frame(); 1078 frame.top -= fBar->Bounds().Height()+1; 1079 frame.right += B_V_SCROLL_BAR_WIDTH; 1080 frame.bottom += B_H_SCROLL_BAR_HEIGHT; 1081 frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView 1082 1083 SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE); 1084 1085 Activate(); 1086 // make the window frontmost 1087 } else { 1088 frame = fWindowFrame; 1089 1090 SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); 1091 } 1092 1093 MoveTo(frame.left, frame.top); 1094 ResizeTo(frame.Width(), frame.Height()); 1095 1096 fImageView->SetHideIdlingCursor(fFullScreen); 1097 fImageView->SetShowCaption(fFullScreen && fShowCaption); 1098 fImageView->FitToBounds(); 1099 } 1100 1101 1102 void 1103 ShowImageWindow::_ApplySettings() 1104 { 1105 ShowImageSettings* settings = my_app->Settings(); 1106 1107 if (settings->Lock()) { 1108 fShowCaption = settings->GetBool("ShowCaption", fShowCaption); 1109 fPrintOptions.SetBounds(BRect(0, 0, 1023, 767)); 1110 1111 int32 op = settings->GetInt32("PO:Option", fPrintOptions.Option()); 1112 fPrintOptions.SetOption((enum PrintOptions::Option)op); 1113 1114 float f = settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1115 fPrintOptions.SetZoomFactor(f); 1116 1117 f = settings->GetFloat("PO:DPI", fPrintOptions.DPI()); 1118 fPrintOptions.SetDPI(f); 1119 1120 f = settings->GetFloat("PO:Width", fPrintOptions.Width()); 1121 fPrintOptions.SetWidth(f); 1122 1123 f = settings->GetFloat("PO:Height", fPrintOptions.Height()); 1124 fPrintOptions.SetHeight(f); 1125 1126 settings->Unlock(); 1127 } 1128 } 1129 1130 1131 void 1132 ShowImageWindow::_SavePrintOptions() 1133 { 1134 ShowImageSettings* settings = my_app->Settings(); 1135 1136 if (settings->Lock()) { 1137 settings->SetInt32("PO:Option", fPrintOptions.Option()); 1138 settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1139 settings->SetFloat("PO:DPI", fPrintOptions.DPI()); 1140 settings->SetFloat("PO:Width", fPrintOptions.Width()); 1141 settings->SetFloat("PO:Height", fPrintOptions.Height()); 1142 settings->Unlock(); 1143 } 1144 } 1145 1146 1147 bool 1148 ShowImageWindow::_PageSetup() 1149 { 1150 BPrintJob printJob(fImageView->Image()->name); 1151 if (fPrintSettings != NULL) 1152 printJob.SetSettings(new BMessage(*fPrintSettings)); 1153 1154 status_t status = printJob.ConfigPage(); 1155 if (status == B_OK) { 1156 delete fPrintSettings; 1157 fPrintSettings = printJob.Settings(); 1158 } 1159 1160 return status == B_OK; 1161 } 1162 1163 1164 void 1165 ShowImageWindow::_PrepareForPrint() 1166 { 1167 if (fPrintSettings == NULL) { 1168 BPrintJob printJob(fImageView->Image()->name); 1169 if (printJob.ConfigJob() == B_OK) 1170 fPrintSettings = printJob.Settings(); 1171 } 1172 1173 fPrintOptions.SetBounds(fImageView->Bitmap()->Bounds()); 1174 fPrintOptions.SetWidth(fImageView->Bitmap()->Bounds().Width() + 1); 1175 1176 new PrintOptionsWindow(BPoint(Frame().left + 30, Frame().top + 50), 1177 &fPrintOptions, this); 1178 } 1179 1180 1181 void 1182 ShowImageWindow::_Print(BMessage* msg) 1183 { 1184 status_t st; 1185 if (msg->FindInt32("status", &st) != B_OK || st != B_OK) 1186 return; 1187 1188 _SavePrintOptions(); 1189 1190 BPrintJob printJob(fImageView->Image()->name); 1191 if (fPrintSettings) 1192 printJob.SetSettings(new BMessage(*fPrintSettings)); 1193 1194 if (printJob.ConfigJob() == B_OK) { 1195 delete fPrintSettings; 1196 fPrintSettings = printJob.Settings(); 1197 1198 // first/lastPage is unused for now 1199 int32 firstPage = printJob.FirstPage(); 1200 int32 lastPage = printJob.LastPage(); 1201 BRect printableRect = printJob.PrintableRect(); 1202 1203 if (firstPage < 1) 1204 firstPage = 1; 1205 if (lastPage < firstPage) 1206 lastPage = firstPage; 1207 1208 BBitmap* bitmap = fImageView->Bitmap(); 1209 float imageWidth = bitmap->Bounds().Width() + 1.0; 1210 float imageHeight = bitmap->Bounds().Height() + 1.0; 1211 1212 float width; 1213 switch (fPrintOptions.Option()) { 1214 case PrintOptions::kFitToPage: { 1215 float w1 = printableRect.Width()+1; 1216 float w2 = imageWidth * (printableRect.Height() + 1) 1217 / imageHeight; 1218 if (w2 < w1) 1219 width = w2; 1220 else 1221 width = w1; 1222 } break; 1223 case PrintOptions::kZoomFactor: 1224 width = imageWidth * fPrintOptions.ZoomFactor(); 1225 break; 1226 case PrintOptions::kDPI: 1227 width = imageWidth * 72.0 / fPrintOptions.DPI(); 1228 break; 1229 case PrintOptions::kWidth: 1230 case PrintOptions::kHeight: 1231 width = fPrintOptions.Width(); 1232 break; 1233 1234 default: 1235 // keep compiler silent; should not reach here 1236 width = imageWidth; 1237 } 1238 1239 // TODO: eventually print large images on several pages 1240 printJob.BeginJob(); 1241 fImageView->SetScale(width / imageWidth); 1242 // coordinates are relative to printable rectangle 1243 BRect bounds(bitmap->Bounds()); 1244 printJob.DrawView(fImageView, bounds, BPoint(0, 0)); 1245 fImageView->SetScale(1.0); 1246 printJob.SpoolPage(); 1247 printJob.CommitJob(); 1248 } 1249 } 1250 1251 1252 bool 1253 ShowImageWindow::QuitRequested() 1254 { 1255 if (fSavePanel) { 1256 // Don't allow this window to be closed if a save panel is open 1257 return false; 1258 } 1259 1260 return _ClosePrompt(); 1261 } 1262