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