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