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, "3 seconds", 3); 262 _AddDelayItem(delayMenu, "4 seconds", 4); 263 _AddDelayItem(delayMenu, "5 seconds", 5); 264 _AddDelayItem(delayMenu, "6 seconds", 6); 265 _AddDelayItem(delayMenu, "7 seconds", 7); 266 _AddDelayItem(delayMenu, "8 seconds", 8); 267 _AddDelayItem(delayMenu, "9 seconds", 9); 268 _AddDelayItem(delayMenu, "10 seconds", 10); 269 _AddDelayItem(delayMenu, "20 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" B_UTF8_ELLIPSIS, 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" B_UTF8_ELLIPSIS, 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 && !fFullScreen) 636 WindowRedimension(fImageView->GetBitmap()); 637 } 638 639 fStatusView->SetText(status); 640 641 UpdateTitle(); 642 } break; 643 644 case MSG_UPDATE_STATUS_TEXT: { 645 BString status; 646 status << fWidth << "x" << fHeight; 647 BString str; 648 if (message->FindString("status", &str) == B_OK && str.Length() > 0) { 649 status << ", " << str; 650 fStatusView->SetText(status); 651 } 652 } break; 653 654 case MSG_SELECTION: { 655 // The view sends this message when a selection is 656 // made or the selection is cleared so that the window 657 // can update the state of the appropriate menu items 658 bool benable; 659 if (message->FindBool("has_selection", &benable) == B_OK) { 660 _EnableMenuItem(fBar, B_CUT, benable); 661 _EnableMenuItem(fBar, B_COPY, benable); 662 _EnableMenuItem(fBar, MSG_CLEAR_SELECT, benable); 663 } 664 } break; 665 666 case MSG_UNDO_STATE: { 667 bool benable; 668 if (message->FindBool("can_undo", &benable) == B_OK) 669 _EnableMenuItem(fBar, B_UNDO, benable); 670 } break; 671 672 case MSG_CLIPBOARD_CHANGED: { 673 // The app sends this message after it examines the clipboard in 674 // response to a B_CLIPBOARD_CHANGED message 675 bool bdata; 676 if (message->FindBool("data_available", &bdata) == B_OK) 677 _EnableMenuItem(fBar, B_PASTE, bdata); 678 } break; 679 680 case B_UNDO: 681 fImageView->Undo(); 682 break; 683 684 case B_CUT: 685 fImageView->Cut(); 686 break; 687 688 case B_COPY: 689 fImageView->CopySelectionToClipboard(); 690 break; 691 692 case B_PASTE: 693 fImageView->Paste(); 694 break; 695 696 case MSG_CLEAR_SELECT: 697 fImageView->ClearSelection(); 698 break; 699 700 case MSG_SELECT_ALL: 701 fImageView->SelectAll(); 702 break; 703 704 case MSG_PAGE_FIRST: 705 if (_ClosePrompt()) 706 fImageView->FirstPage(); 707 break; 708 709 case MSG_PAGE_LAST: 710 if (_ClosePrompt()) 711 fImageView->LastPage(); 712 break; 713 714 case MSG_PAGE_NEXT: 715 if (_ClosePrompt()) 716 fImageView->NextPage(); 717 break; 718 719 case MSG_PAGE_PREV: 720 if (_ClosePrompt()) 721 fImageView->PrevPage(); 722 break; 723 724 case MSG_GOTO_PAGE: { 725 if (!_ClosePrompt()) 726 break; 727 728 int32 newPage; 729 if (message->FindInt32("page", &newPage) != B_OK) 730 break; 731 732 int32 curPage = fImageView->CurrentPage(); 733 int32 pages = fImageView->PageCount(); 734 735 if (newPage > 0 && newPage <= pages) { 736 BMenuItem* pcurItem = fGoToPageMenu->ItemAt(curPage - 1); 737 BMenuItem* pnewItem = fGoToPageMenu->ItemAt(newPage - 1); 738 if (pcurItem && pnewItem) { 739 pcurItem->SetMarked(false); 740 pnewItem->SetMarked(true); 741 fImageView->GoToPage(newPage); 742 } 743 } 744 } break; 745 746 case MSG_DITHER_IMAGE: 747 fImageView->SetDither(_ToggleMenuItem(message->what)); 748 break; 749 750 case MSG_SHRINK_TO_WINDOW: 751 _ResizeToWindow(true, message->what); 752 break; 753 754 case MSG_ZOOM_TO_WINDOW: 755 _ResizeToWindow(false, message->what); 756 break; 757 758 case MSG_FILE_PREV: 759 if (_ClosePrompt()) 760 fImageView->PrevFile(); 761 break; 762 763 case MSG_FILE_NEXT: 764 if (_ClosePrompt()) 765 fImageView->NextFile(); 766 break; 767 768 case MSG_ROTATE_90: 769 fImageView->Rotate(90); 770 break; 771 772 case MSG_ROTATE_270: 773 fImageView->Rotate(270); 774 break; 775 776 case MSG_FLIP_LEFT_TO_RIGHT: 777 fImageView->Flip(true); 778 break; 779 780 case MSG_FLIP_TOP_TO_BOTTOM: 781 fImageView->Flip(false); 782 break; 783 784 case MSG_INVERT: 785 fImageView->Invert(); 786 break; 787 788 case MSG_SLIDE_SHOW: { 789 BMenuItem *item = fBar->FindItem(message->what); 790 if (!item) 791 break; 792 if (item->IsMarked()) { 793 item->SetMarked(false); 794 fResizeItem->SetEnabled(true); 795 fImageView->StopSlideShow(); 796 } else if (_ClosePrompt()) { 797 item->SetMarked(true); 798 fResizeItem->SetEnabled(false); 799 fImageView->StartSlideShow(); 800 } 801 } break; 802 803 case MSG_SLIDE_SHOW_DELAY: { 804 float value; 805 if (message->FindFloat("value", &value) == B_OK) { 806 fImageView->SetSlideShowDelay(value); 807 // in case message is sent from popup menu 808 _MarkSlideShowDelay(value); 809 } 810 } break; 811 812 case MSG_FULL_SCREEN: 813 _ToggleFullScreen(); 814 break; 815 816 case MSG_EXIT_FULL_SCREEN: 817 if (fFullScreen) 818 _ToggleFullScreen(); 819 break; 820 821 case MSG_SHOW_CAPTION: { 822 fShowCaption = _ToggleMenuItem(message->what); 823 ShowImageSettings* settings = my_app->Settings(); 824 825 if (settings->Lock()) { 826 settings->SetBool("ShowCaption", fShowCaption); 827 settings->Unlock(); 828 } 829 if (fFullScreen) 830 fImageView->SetShowCaption(fShowCaption); 831 } break; 832 833 case MSG_PAGE_SETUP: 834 _PageSetup(); 835 break; 836 837 case MSG_PREPARE_PRINT: 838 _PrepareForPrint(); 839 break; 840 841 case MSG_PRINT: 842 _Print(message); 843 break; 844 845 case MSG_ZOOM_IN: 846 fImageView->ZoomIn(); 847 break; 848 849 case MSG_ZOOM_OUT: 850 fImageView->ZoomOut(); 851 break; 852 853 case MSG_ORIGINAL_SIZE: 854 fImageView->SetZoom(1.0); 855 break; 856 857 case MSG_SCALE_BILINEAR: 858 fImageView->SetScaleBilinear(_ToggleMenuItem(message->what)); 859 break; 860 861 case MSG_OPEN_RESIZER_WINDOW: { 862 if (fImageView->GetBitmap() != NULL) { 863 BRect rect = fImageView->GetBitmap()->Bounds(); 864 _OpenResizerWindow(rect.IntegerWidth()+1, rect.IntegerHeight()+1); 865 } 866 } break; 867 868 case MSG_RESIZE: { 869 int w = message->FindInt32("w"); 870 int h = message->FindInt32("h"); 871 fImageView->ResizeImage(w, h); 872 } break; 873 874 case MSG_RESIZER_WINDOW_QUIT: 875 delete fResizerWindowMessenger; 876 fResizerWindowMessenger = NULL; 877 break; 878 879 case MSG_DESKTOP_BACKGROUND: { 880 BMessage message(B_REFS_RECEIVED); 881 message.AddRef("refs", fImageView->Image()); 882 // This is used in the Backgrounds code for scaled placement 883 message.AddInt32("placement", 'scpl'); 884 be_roster->Launch("application/x-vnd.haiku-backgrounds", &message); 885 } break; 886 887 default: 888 BWindow::MessageReceived(message); 889 break; 890 } 891 } 892 893 894 void 895 ShowImageWindow::_SaveAs(BMessage *message) 896 { 897 // Read the translator and output type the user chose 898 translator_id outTranslator; 899 uint32 outType; 900 if (message->FindInt32(kTranslatorField, 901 reinterpret_cast<int32 *>(&outTranslator)) != B_OK 902 || message->FindInt32(kTypeField, 903 reinterpret_cast<int32 *>(&outType)) != B_OK) 904 return; 905 906 // Add the chosen translator and output type to the 907 // message that the save panel will send back 908 BMessage panelMsg(MSG_SAVE_PANEL); 909 panelMsg.AddInt32(kTranslatorField, outTranslator); 910 panelMsg.AddInt32(kTypeField, outType); 911 912 // Create save panel and show it 913 BMessenger target(this); 914 fSavePanel = new (std::nothrow) BFilePanel(B_SAVE_PANEL, 915 &target, NULL, 0, false, &panelMsg); 916 if (!fSavePanel) 917 return; 918 919 fSavePanel->Window()->SetWorkspaces(B_CURRENT_WORKSPACE); 920 fSavePanel->Show(); 921 } 922 923 924 void 925 ShowImageWindow::_SaveToFile(BMessage *message) 926 { 927 // Read in where the file should be saved 928 entry_ref dirRef; 929 if (message->FindRef("directory", &dirRef) != B_OK) 930 return; 931 932 const char *filename; 933 if (message->FindString("name", &filename) != B_OK) 934 return; 935 936 // Read in the translator and type to be used 937 // to save the output image 938 translator_id outTranslator; 939 uint32 outType; 940 if (message->FindInt32(kTranslatorField, 941 reinterpret_cast<int32 *>(&outTranslator)) != B_OK 942 || message->FindInt32(kTypeField, 943 reinterpret_cast<int32 *>(&outType)) != B_OK) 944 return; 945 946 // Find the translator_format information needed to 947 // write a MIME attribute for the image file 948 BTranslatorRoster *roster = BTranslatorRoster::Default(); 949 const translation_format *outFormat = NULL; 950 int32 outCount = 0; 951 if (roster->GetOutputFormats(outTranslator, &outFormat, &outCount) != B_OK 952 || outCount < 1) 953 return; 954 955 int32 i; 956 for (i = 0; i < outCount; i++) { 957 if (outFormat[i].group == B_TRANSLATOR_BITMAP && outFormat[i].type == outType) 958 break; 959 } 960 if (i == outCount) 961 return; 962 963 // Write out the image file 964 BDirectory dir(&dirRef); 965 fImageView->SaveToFile(&dir, filename, NULL, &outFormat[i]); 966 } 967 968 969 bool 970 ShowImageWindow::_ClosePrompt() 971 { 972 if (!fModified) 973 return true; 974 975 int32 page, count; 976 count = fImageView->PageCount(); 977 page = fImageView->CurrentPage(); 978 BString prompt, name; 979 fImageView->GetName(&name); 980 prompt << "The document '" << name << "'"; 981 if (count > 1) 982 prompt << " (page " << page << ")"; 983 984 prompt << " has been changed. " 985 << "Do you want to close the document?"; 986 BAlert *pAlert = new BAlert("Close document", prompt.String(), 987 "Cancel", "Close"); 988 if (pAlert->Go() == 0) { 989 // Cancel 990 return false; 991 } else { 992 // Close 993 fModified = false; 994 return true; 995 } 996 } 997 998 999 void 1000 ShowImageWindow::_ToggleFullScreen() 1001 { 1002 BRect frame; 1003 fFullScreen = !fFullScreen; 1004 if (fFullScreen) { 1005 BScreen screen; 1006 fWindowFrame = Frame(); 1007 frame = screen.Frame(); 1008 frame.top -= fBar->Bounds().Height()+1; 1009 frame.right += B_V_SCROLL_BAR_WIDTH; 1010 frame.bottom += B_H_SCROLL_BAR_HEIGHT; 1011 frame.InsetBy(-1, -1); // PEN_SIZE in ShowImageView 1012 1013 SetFlags(Flags() | B_NOT_RESIZABLE | B_NOT_MOVABLE); 1014 1015 Activate(); 1016 // make the window frontmost 1017 } else { 1018 frame = fWindowFrame; 1019 1020 SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); 1021 } 1022 1023 fImageView->SetFullScreen(fFullScreen); 1024 fImageView->SetShowCaption(fFullScreen && fShowCaption); 1025 MoveTo(frame.left, frame.top); 1026 ResizeTo(frame.Width(), frame.Height()); 1027 } 1028 1029 1030 void 1031 ShowImageWindow::_LoadSettings() 1032 { 1033 ShowImageSettings* settings = my_app->Settings(); 1034 1035 if (settings->Lock()) { 1036 fShowCaption = settings->GetBool("ShowCaption", fShowCaption); 1037 fPrintOptions.SetBounds(BRect(0, 0, 1023, 767)); 1038 1039 int32 op = settings->GetInt32("PO:Option", fPrintOptions.Option()); 1040 fPrintOptions.SetOption((enum PrintOptions::Option)op); 1041 1042 float f = settings->GetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1043 fPrintOptions.SetZoomFactor(f); 1044 1045 f = settings->GetFloat("PO:DPI", fPrintOptions.DPI()); 1046 fPrintOptions.SetDPI(f); 1047 1048 f = settings->GetFloat("PO:Width", fPrintOptions.Width()); 1049 fPrintOptions.SetWidth(f); 1050 1051 f = settings->GetFloat("PO:Height", fPrintOptions.Height()); 1052 fPrintOptions.SetHeight(f); 1053 1054 settings->Unlock(); 1055 } 1056 } 1057 1058 1059 void 1060 ShowImageWindow::_SavePrintOptions() 1061 { 1062 ShowImageSettings* settings = my_app->Settings(); 1063 1064 if (settings->Lock()) { 1065 settings->SetInt32("PO:Option", fPrintOptions.Option()); 1066 settings->SetFloat("PO:ZoomFactor", fPrintOptions.ZoomFactor()); 1067 settings->SetFloat("PO:DPI", fPrintOptions.DPI()); 1068 settings->SetFloat("PO:Width", fPrintOptions.Width()); 1069 settings->SetFloat("PO:Height", fPrintOptions.Height()); 1070 settings->Unlock(); 1071 } 1072 } 1073 1074 1075 bool 1076 ShowImageWindow::_PageSetup() 1077 { 1078 BString name; 1079 fImageView->GetName(&name); 1080 BPrintJob printJob(name.String()); 1081 if (fPrintSettings != NULL) 1082 printJob.SetSettings(new BMessage(*fPrintSettings)); 1083 1084 status_t status = printJob.ConfigPage(); 1085 if (status == B_OK) { 1086 delete fPrintSettings; 1087 fPrintSettings = printJob.Settings(); 1088 } 1089 1090 return status == B_OK; 1091 } 1092 1093 1094 void 1095 ShowImageWindow::_PrepareForPrint() 1096 { 1097 if (fPrintSettings == NULL) { 1098 BString name; 1099 fImageView->GetName(&name); 1100 1101 BPrintJob printJob(""); 1102 if (printJob.ConfigJob() == B_OK) 1103 fPrintSettings = printJob.Settings(); 1104 } 1105 1106 fPrintOptions.SetBounds(fImageView->GetBitmap()->Bounds()); 1107 fPrintOptions.SetWidth(fImageView->GetBitmap()->Bounds().Width()+1); 1108 1109 new PrintOptionsWindow(BPoint(Frame().left+30, Frame().top+50), 1110 &fPrintOptions, this); 1111 } 1112 1113 1114 void 1115 ShowImageWindow::_Print(BMessage *msg) 1116 { 1117 status_t st; 1118 if (msg->FindInt32("status", &st) != B_OK || st != B_OK) 1119 return; 1120 1121 _SavePrintOptions(); 1122 1123 BString name; 1124 fImageView->GetName(&name); 1125 1126 BPrintJob printJob(name.String()); 1127 if (fPrintSettings) 1128 printJob.SetSettings(new BMessage(*fPrintSettings)); 1129 1130 if (printJob.ConfigJob() == B_OK) { 1131 delete fPrintSettings; 1132 fPrintSettings = printJob.Settings(); 1133 1134 // first/lastPage is unused for now 1135 int32 firstPage = printJob.FirstPage(); 1136 int32 lastPage = printJob.LastPage(); 1137 BRect printableRect = printJob.PrintableRect(); 1138 1139 if (firstPage < 1) 1140 firstPage = 1; 1141 if (lastPage < firstPage) 1142 lastPage = firstPage; 1143 1144 BBitmap* bitmap = fImageView->GetBitmap(); 1145 float imageWidth = bitmap->Bounds().Width() + 1.0; 1146 float imageHeight = bitmap->Bounds().Height() + 1.0; 1147 1148 float width; 1149 switch (fPrintOptions.Option()) { 1150 case PrintOptions::kFitToPage: { 1151 float w1 = printableRect.Width()+1; 1152 float w2 = imageWidth * (printableRect.Height() + 1) / imageHeight; 1153 if (w2 < w1) 1154 width = w2; 1155 else 1156 width = w1; 1157 } break; 1158 case PrintOptions::kZoomFactor: 1159 width = imageWidth * fPrintOptions.ZoomFactor(); 1160 break; 1161 case PrintOptions::kDPI: 1162 width = imageWidth * 72.0 / fPrintOptions.DPI(); 1163 break; 1164 case PrintOptions::kWidth: 1165 case PrintOptions::kHeight: 1166 width = fPrintOptions.Width(); 1167 break; 1168 1169 default: 1170 // keep compiler silent; should not reach here 1171 width = imageWidth; 1172 } 1173 1174 // TODO: eventually print large images on several pages 1175 printJob.BeginJob(); 1176 fImageView->SetScale(width / imageWidth); 1177 // coordinates are relative to printable rectangle 1178 BRect bounds(bitmap->Bounds()); 1179 printJob.DrawView(fImageView, bounds, BPoint(0, 0)); 1180 fImageView->SetScale(1.0); 1181 printJob.SpoolPage(); 1182 printJob.CommitJob(); 1183 } 1184 } 1185 1186 1187 void 1188 ShowImageWindow::_OpenResizerWindow(int32 width, int32 height) 1189 { 1190 if (fResizerWindowMessenger == NULL) { 1191 // open window if it is not already opened 1192 BWindow* window = new ResizerWindow(this, width, height); 1193 fResizerWindowMessenger = new BMessenger(window); 1194 window->Show(); 1195 } else { 1196 fResizerWindowMessenger->SendMessage(ResizerWindow::kActivateMsg); 1197 } 1198 } 1199 1200 1201 void 1202 ShowImageWindow::_UpdateResizerWindow(int32 width, int32 height) 1203 { 1204 if (fResizerWindowMessenger == NULL) 1205 return; 1206 1207 BMessage updateMsg(ResizerWindow::kUpdateMsg); 1208 updateMsg.AddInt32("width", width); 1209 updateMsg.AddInt32("height", height); 1210 fResizerWindowMessenger->SendMessage(&updateMsg); 1211 } 1212 1213 1214 void 1215 ShowImageWindow::_CloseResizerWindow() 1216 { 1217 if (fResizerWindowMessenger == NULL) 1218 return; 1219 1220 fResizerWindowMessenger->SendMessage(B_QUIT_REQUESTED); 1221 delete fResizerWindowMessenger; 1222 fResizerWindowMessenger = NULL; 1223 } 1224 1225 1226 bool 1227 ShowImageWindow::QuitRequested() 1228 { 1229 if (fSavePanel) { 1230 // Don't allow this window to be closed if a save panel is open 1231 return false; 1232 } 1233 1234 bool quit = _ClosePrompt(); 1235 1236 if (quit) { 1237 _CloseResizerWindow(); 1238 1239 // tell the app to forget about this window 1240 be_app->PostMessage(MSG_WINDOW_QUIT); 1241 } 1242 1243 return quit; 1244 } 1245 1246 1247 void 1248 ShowImageWindow::ScreenChanged(BRect frame, color_space mode) 1249 { 1250 fImageView->SetDither(mode == B_CMAP8); 1251 } 1252