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