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