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