1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "ContainerWindow.h" 37 38 #include <Alert.h> 39 #include <Application.h> 40 #include <AppFileInfo.h> 41 #include <Catalog.h> 42 #include <ControlLook.h> 43 #include <Debug.h> 44 #include <Directory.h> 45 #include <Entry.h> 46 #include <FindDirectory.h> 47 #include <GridView.h> 48 #include <GroupLayout.h> 49 #include <Keymap.h> 50 #include <Locale.h> 51 #include <MenuItem.h> 52 #include <MenuBar.h> 53 #include <NodeMonitor.h> 54 #include <Path.h> 55 #include <PopUpMenu.h> 56 #include <Roster.h> 57 #include <Screen.h> 58 #include <UnicodeChar.h> 59 #include <Volume.h> 60 #include <VolumeRoster.h> 61 #include <WindowPrivate.h> 62 63 #include <fs_attr.h> 64 #include <image.h> 65 #include <strings.h> 66 #include <stdlib.h> 67 68 #include "Attributes.h" 69 #include "AttributeStream.h" 70 #include "AutoDeleter.h" 71 #include "AutoLock.h" 72 #include "BackgroundImage.h" 73 #include "Commands.h" 74 #include "CountView.h" 75 #include "DeskWindow.h" 76 #include "FavoritesMenu.h" 77 #include "FindPanel.h" 78 #include "FSClipboard.h" 79 #include "FSUndoRedo.h" 80 #include "FSUtils.h" 81 #include "IconMenuItem.h" 82 #include "OpenWithWindow.h" 83 #include "MimeTypes.h" 84 #include "MountMenu.h" 85 #include "Navigator.h" 86 #include "NavMenu.h" 87 #include "PoseView.h" 88 #include "QueryContainerWindow.h" 89 #include "SelectionWindow.h" 90 #include "TitleView.h" 91 #include "Tracker.h" 92 #include "TrackerSettings.h" 93 #include "Thread.h" 94 #include "TemplatesMenu.h" 95 96 97 #undef B_TRANSLATION_CONTEXT 98 #define B_TRANSLATION_CONTEXT "ContainerWindow" 99 100 101 #ifdef _IMPEXP_BE 102 _IMPEXP_BE 103 #endif 104 void do_minimize_team(BRect zoomRect, team_id team, bool zoom); 105 106 107 // Amount you have to move the mouse before a drag starts 108 const float kDragSlop = 3.0f; 109 110 111 namespace BPrivate { 112 113 class DraggableContainerIcon : public BView { 114 public: 115 DraggableContainerIcon(BSize iconSize); 116 117 virtual void MouseDown(BPoint where); 118 virtual void MouseUp(BPoint); 119 virtual void MouseMoved(BPoint where, uint32, const BMessage*); 120 virtual void Draw(BRect updateRect); 121 122 private: 123 BSize fIconSize; 124 uint32 fDragButton; 125 BPoint fClickPoint; 126 bool fDragStarted; 127 }; 128 129 } // namespace BPrivate 130 131 132 struct AddOneAddonParams { 133 BObjectList<BMenuItem>* primaryList; 134 BObjectList<BMenuItem>* secondaryList; 135 }; 136 137 struct StaggerOneParams { 138 bool rectFromParent; 139 }; 140 141 142 BRect BContainerWindow::sNewWindRect; 143 static int32 sWindowStaggerBy; 144 145 LockingList<AddonShortcut>* BContainerWindow::fAddonsList 146 = new LockingList<struct AddonShortcut>(10, true); 147 148 149 namespace BPrivate { 150 151 filter_result 152 ActivateWindowFilter(BMessage*, BHandler** target, BMessageFilter*) 153 { 154 BView* view = dynamic_cast<BView*>(*target); 155 156 // activate the window if no PoseView or DraggableContainerIcon had been 157 // pressed (those will activate the window themselves, if necessary) 158 if (view != NULL 159 && dynamic_cast<BPoseView*>(view) == NULL 160 && dynamic_cast<DraggableContainerIcon*>(view) == NULL 161 && view->Window() != NULL) { 162 view->Window()->Activate(true); 163 } 164 165 return B_DISPATCH_MESSAGE; 166 } 167 168 169 int 170 CompareLabels(const BMenuItem* item1, const BMenuItem* item2) 171 { 172 return strcasecmp(item1->Label(), item2->Label()); 173 } 174 175 } // namespace BPrivate 176 177 178 static int32 179 AddOnMenuGenerate(const entry_ref* addonRef, BMenu* menu, 180 BContainerWindow* window) 181 { 182 BEntry entry(addonRef); 183 BPath path; 184 status_t result = entry.InitCheck(); 185 if (result != B_OK) 186 return result; 187 188 result = entry.GetPath(&path); 189 if (result != B_OK) 190 return result; 191 192 image_id addonImage = load_add_on(path.Path()); 193 if (addonImage < 0) 194 return addonImage; 195 196 void (*populateMenu)(BMessage*, BMenu*, BHandler*); 197 result = get_image_symbol(addonImage, "populate_menu", 2, 198 (void**)&populateMenu); 199 if (result < 0) { 200 PRINT(("Couldn't find populate_menu\n")); 201 unload_add_on(addonImage); 202 return result; 203 } 204 205 BMessage* message = window->AddOnMessage(B_TRACKER_ADDON_MESSAGE); 206 message->AddRef("addon_ref", addonRef); 207 208 // call add-on code 209 (*populateMenu)(message, menu, window->PoseView()); 210 211 unload_add_on(addonImage); 212 return B_OK; 213 } 214 215 216 static status_t 217 RunAddOnMessageThread(BMessage *message, void *) 218 { 219 entry_ref addonRef; 220 BEntry entry; 221 BPath path; 222 status_t result = message->FindRef("addon_ref", &addonRef); 223 image_id addonImage; 224 225 if (result != B_OK) 226 goto end; 227 228 entry = BEntry(&addonRef); 229 result = entry.InitCheck(); 230 if (result != B_OK) 231 goto end; 232 233 result = entry.GetPath(&path); 234 if (result != B_OK) 235 goto end; 236 237 addonImage = load_add_on(path.Path()); 238 if (addonImage < 0) { 239 result = addonImage; 240 goto end; 241 } 242 void (*messageReceived)(BMessage*); 243 result = get_image_symbol(addonImage, "message_received", 2, 244 (void**)&messageReceived); 245 246 if (result < 0) { 247 PRINT(("Couldn't find message_received\n")); 248 unload_add_on(addonImage); 249 goto end; 250 } 251 // call add-on code 252 (*messageReceived)(message); 253 unload_add_on(addonImage); 254 return B_OK; 255 256 end: 257 BString buffer(B_TRANSLATE("Error %error loading add-On %name.")); 258 buffer.ReplaceFirst("%error", strerror(result)); 259 buffer.ReplaceFirst("%name", addonRef.name); 260 261 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 262 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 263 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 264 alert->Go(); 265 266 return result; 267 } 268 269 270 static bool 271 AddOneAddon(const Model* model, const char* name, uint32 shortcut, 272 uint32 modifiers, bool primary, void* context, 273 BContainerWindow* window, BMenu* menu) 274 { 275 AddOneAddonParams* params = (AddOneAddonParams*)context; 276 277 BMessage* message = new BMessage(kLoadAddOn); 278 message->AddRef("refs", model->EntryRef()); 279 280 ModelMenuItem* item = new ModelMenuItem(model, name, message, 281 (char)shortcut, modifiers); 282 283 const entry_ref* addonRef = model->EntryRef(); 284 AddOnMenuGenerate(addonRef, menu, window); 285 286 if (primary) 287 params->primaryList->AddItem(item); 288 else 289 params->secondaryList->AddItem(item); 290 291 return false; 292 } 293 294 295 static int32 296 AddOnThread(BMessage* refsMessage, entry_ref addonRef, entry_ref directoryRef) 297 { 298 ObjectDeleter<BMessage> _(refsMessage); 299 300 BEntry entry(&addonRef); 301 BPath path; 302 status_t result = entry.InitCheck(); 303 if (result == B_OK) 304 result = entry.GetPath(&path); 305 306 if (result == B_OK) { 307 image_id addonImage = load_add_on(path.Path()); 308 if (addonImage >= 0) { 309 void (*processRefs)(entry_ref, BMessage*, void*); 310 result = get_image_symbol(addonImage, "process_refs", 2, 311 (void**)&processRefs); 312 313 if (result >= 0) { 314 // call add-on code 315 (*processRefs)(directoryRef, refsMessage, NULL); 316 317 unload_add_on(addonImage); 318 return B_OK; 319 } else 320 PRINT(("couldn't find process_refs\n")); 321 322 unload_add_on(addonImage); 323 } else 324 result = addonImage; 325 } 326 327 BString buffer(B_TRANSLATE("Error %error loading Add-On %name.")); 328 buffer.ReplaceFirst("%error", strerror(result)); 329 buffer.ReplaceFirst("%name", addonRef.name); 330 331 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 332 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 333 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 334 alert->Go(); 335 336 return result; 337 } 338 339 340 static bool 341 NodeHasSavedState(const BNode* node) 342 { 343 attr_info info; 344 return node->GetAttrInfo(kAttrWindowFrame, &info) == B_OK; 345 } 346 347 348 static bool 349 OffsetFrameOne(const char* DEBUG_ONLY(name), uint32, off_t, void* castToRect, 350 void* castToParams) 351 { 352 ASSERT(strcmp(name, kAttrWindowFrame) == 0); 353 StaggerOneParams* params = (StaggerOneParams*)castToParams; 354 355 if (!params->rectFromParent) 356 return false; 357 358 if (!castToRect) 359 return false; 360 361 ((BRect*)castToRect)->OffsetBy(sWindowStaggerBy, sWindowStaggerBy); 362 363 return true; 364 } 365 366 367 static void 368 AddMimeTypeString(BStringList& list, Model* model) 369 { 370 if (model == NULL) 371 return; 372 373 const char* modelMimeType = model->MimeType(); 374 if (modelMimeType == NULL || *modelMimeType == '\0') 375 return; 376 377 BString type = BString(modelMimeType); 378 if (list.HasString(type, true)) 379 return; 380 381 list.Add(type); 382 } 383 384 385 // #pragma mark - DraggableContainerIcon 386 387 388 DraggableContainerIcon::DraggableContainerIcon(BSize iconSize) 389 : 390 BView("DraggableContainerIcon", B_WILL_DRAW), 391 fIconSize(iconSize), 392 fDragButton(0), 393 fDragStarted(false) 394 { 395 SetExplicitMinSize(BSize(iconSize.Width() + 5, iconSize.Height())); 396 SetExplicitMaxSize(BSize(iconSize.Width() + 5, B_SIZE_UNSET)); 397 } 398 399 400 void 401 DraggableContainerIcon::MouseDown(BPoint where) 402 { 403 // we only like container windows 404 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 405 ThrowOnAssert(window != NULL); 406 407 // we don't like the Trash icon (because it cannot be moved) 408 if (window->IsTrash() || window->IsPrintersDir()) 409 return; 410 411 uint32 buttons; 412 window->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 413 414 if (IconCache::sIconCache->IconHitTest(where, window->TargetModel(), 415 kNormalIcon, fIconSize)) { 416 // The click hit the icon, initiate a drag 417 fDragButton = buttons 418 & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON); 419 fDragStarted = false; 420 fClickPoint = where; 421 } else 422 fDragButton = 0; 423 424 if (!fDragButton) 425 Window()->Activate(true); 426 } 427 428 429 void 430 DraggableContainerIcon::MouseUp(BPoint) 431 { 432 if (!fDragStarted) 433 Window()->Activate(true); 434 435 fDragButton = 0; 436 fDragStarted = false; 437 } 438 439 440 void 441 DraggableContainerIcon::MouseMoved(BPoint where, uint32, const BMessage*) 442 { 443 if (fDragButton == 0 || fDragStarted 444 || (abs((int32)(where.x - fClickPoint.x)) <= kDragSlop 445 && abs((int32)(where.y - fClickPoint.y)) <= kDragSlop)) 446 return; 447 448 BContainerWindow* window = static_cast<BContainerWindow*>(Window()); 449 // we can only get here in a BContainerWindow 450 Model* model = window->TargetModel(); 451 452 // Find the required height 453 BFont font; 454 GetFont(&font); 455 456 font_height fontHeight; 457 font.GetHeight(&fontHeight); 458 float height = ceilf(fontHeight.ascent + fontHeight.descent 459 + fontHeight.leading + 2 + Bounds().Height() + 8); 460 461 BRect rect(0, 0, std::max(Bounds().Width(), 462 font.StringWidth(model->Name()) + 4), height); 463 BBitmap* dragBitmap = new BBitmap(rect, B_RGBA32, true); 464 465 dragBitmap->Lock(); 466 BView* view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0); 467 dragBitmap->AddChild(view); 468 view->SetOrigin(0, 0); 469 BRect clipRect(view->Bounds()); 470 BRegion newClip; 471 newClip.Set(clipRect); 472 view->ConstrainClippingRegion(&newClip); 473 474 // Transparent draw magic 475 view->SetHighColor(0, 0, 0, 0); 476 view->FillRect(view->Bounds()); 477 view->SetDrawingMode(B_OP_ALPHA); 478 479 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 480 textColor.alpha = 128; 481 // set the level of transparency by value 482 view->SetHighColor(textColor); 483 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 484 485 // Draw the icon 486 float hIconOffset = (rect.Width() - Bounds().Width()) / 2; 487 IconCache::sIconCache->Draw(model, view, BPoint(hIconOffset, 0), 488 kNormalIcon, fIconSize, true); 489 490 // See if we need to truncate the string 491 BString nameString = model->Name(); 492 if (view->StringWidth(model->Name()) > rect.Width()) { 493 view->TruncateString(&nameString, B_TRUNCATE_MIDDLE, 494 rect.Width() - 5); 495 } 496 497 // Draw the label 498 float leftText = (view->StringWidth(nameString.String()) 499 - Bounds().Width()) / 2; 500 view->MovePenTo(BPoint(hIconOffset - leftText + 2, Bounds().Height() 501 + (fontHeight.ascent + 2))); 502 view->DrawString(nameString.String()); 503 504 view->Sync(); 505 dragBitmap->Unlock(); 506 507 BMessage message(B_SIMPLE_DATA); 508 message.AddRef("refs", model->EntryRef()); 509 message.AddPoint("click_pt", fClickPoint); 510 511 BPoint tmpLoc; 512 uint32 button; 513 GetMouse(&tmpLoc, &button); 514 if (button) 515 message.AddInt32("buttons", (int32)button); 516 517 if ((button & B_PRIMARY_MOUSE_BUTTON) != 0) { 518 // add an action specifier to the message, so that it is not copied 519 message.AddInt32("be:actions", (modifiers() & B_OPTION_KEY) != 0 520 ? B_COPY_TARGET : B_MOVE_TARGET); 521 } 522 523 fDragStarted = true; 524 fDragButton = 0; 525 526 DragMessage(&message, dragBitmap, B_OP_ALPHA, 527 BPoint(fClickPoint.x + hIconOffset, fClickPoint.y), this); 528 } 529 530 531 void 532 DraggableContainerIcon::Draw(BRect updateRect) 533 { 534 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 535 ThrowOnAssert(window != NULL); 536 537 BRect rect(Bounds()); 538 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR); 539 be_control_look->DrawBorder(this, rect, updateRect, base, B_PLAIN_BORDER, 540 0, BControlLook::B_BOTTOM_BORDER); 541 be_control_look->DrawMenuBarBackground(this, rect, updateRect, base, 0, 542 BControlLook::B_ALL_BORDERS & ~BControlLook::B_LEFT_BORDER); 543 544 // Draw the icon, straddling the border 545 SetDrawingMode(B_OP_ALPHA); 546 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 547 float iconOffsetX = (Bounds().Width() - fIconSize.Width()) / 2; 548 float iconOffsetY = (Bounds().Height() - fIconSize.Height()) / 2; 549 IconCache::sIconCache->Draw(window->TargetModel(), this, 550 BPoint(iconOffsetX, iconOffsetY), kNormalIcon, fIconSize, true); 551 } 552 553 554 // #pragma mark - BContainerWindow 555 556 557 BContainerWindow::BContainerWindow(LockingList<BWindow>* list, 558 uint32 containerWindowFlags, window_look look, window_feel feel, 559 uint32 flags, uint32 workspace, bool useLayouts, bool isDeskWindow) 560 : 561 BWindow(InitialWindowRect(feel), "TrackerWindow", look, feel, flags, 562 workspace), 563 fUseLayouts(useLayouts), 564 fMenuContainer(NULL), 565 fPoseContainer(NULL), 566 fBorderedView(NULL), 567 fVScrollBarContainer(NULL), 568 fCountContainer(NULL), 569 fContextMenu(NULL), 570 fFileContextMenu(NULL), 571 fWindowContextMenu(NULL), 572 fDropContextMenu(NULL), 573 fVolumeContextMenu(NULL), 574 fTrashContextMenu(NULL), 575 fDragContextMenu(NULL), 576 fMoveToItem(NULL), 577 fCopyToItem(NULL), 578 fCreateLinkItem(NULL), 579 fOpenWithItem(NULL), 580 fNavigationItem(NULL), 581 fMenuBar(NULL), 582 fDraggableIcon(NULL), 583 fNavigator(NULL), 584 fPoseView(NULL), 585 fWindowList(list), 586 fAttrMenu(NULL), 587 fWindowMenu(NULL), 588 fFileMenu(NULL), 589 fArrangeByMenu(NULL), 590 fSelectionWindow(NULL), 591 fTaskLoop(NULL), 592 fStateNeedsSaving(false), 593 fIsTrash(false), 594 fInTrash(false), 595 fIsPrinters(false), 596 fIsDesktop(isDeskWindow), 597 fContainerWindowFlags(containerWindowFlags), 598 fBackgroundImage(NULL), 599 fSavedZoomRect(0, 0, -1, -1), 600 fDragMessage(NULL), 601 fCachedTypesList(NULL), 602 fSaveStateIsEnabled(true), 603 fIsWatchingPath(false) 604 { 605 InitIconPreloader(); 606 607 if (list != NULL) { 608 ASSERT(list->IsLocked()); 609 list->AddItem(this); 610 } 611 612 if (useLayouts) { 613 SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS); 614 615 fRootLayout = new BGroupLayout(B_VERTICAL, 0); 616 fRootLayout->SetInsets(0); 617 SetLayout(fRootLayout); 618 fRootLayout->Owner()->AdoptSystemColors(); 619 620 if (!fIsDesktop) { 621 fMenuContainer = new BGroupView(B_HORIZONTAL, 0); 622 fRootLayout->AddView(fMenuContainer); 623 624 fPoseContainer = new BGridView(0.0, 0.0); 625 fRootLayout->AddView(fPoseContainer); 626 627 fBorderedView = new BorderedView; 628 fPoseContainer->GridLayout()->AddView(fBorderedView, 0, 1); 629 630 fCountContainer = new BGroupView(B_HORIZONTAL, 0); 631 fPoseContainer->GridLayout()->AddView(fCountContainer, 0, 2); 632 } 633 } 634 635 AddCommonFilter(new BMessageFilter(B_MOUSE_DOWN, ActivateWindowFilter)); 636 637 Run(); 638 639 // watch out for settings changes 640 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 641 if (tracker != NULL && tracker->Lock()) { 642 tracker->StartWatching(this, kWindowsShowFullPathChanged); 643 tracker->StartWatching(this, kSingleWindowBrowseChanged); 644 tracker->StartWatching(this, kShowNavigatorChanged); 645 tracker->StartWatching(this, kDontMoveFilesToTrashChanged); 646 tracker->Unlock(); 647 } 648 649 // ToDo: remove me once we have undo/redo menu items 650 // (that is, move them to AddShortcuts()) 651 AddShortcut('Z', B_COMMAND_KEY, new BMessage(B_UNDO), this); 652 AddShortcut('Z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO), this); 653 } 654 655 656 BContainerWindow::~BContainerWindow() 657 { 658 ASSERT(IsLocked()); 659 660 // stop the watchers 661 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 662 if (tracker != NULL && tracker->Lock()) { 663 tracker->StopWatching(this, kWindowsShowFullPathChanged); 664 tracker->StopWatching(this, kSingleWindowBrowseChanged); 665 tracker->StopWatching(this, kShowNavigatorChanged); 666 tracker->StopWatching(this, kDontMoveFilesToTrashChanged); 667 tracker->Unlock(); 668 } 669 670 delete fTaskLoop; 671 delete fBackgroundImage; 672 delete fDragMessage; 673 delete fCachedTypesList; 674 675 if (fSelectionWindow != NULL && fSelectionWindow->Lock()) 676 fSelectionWindow->Quit(); 677 } 678 679 680 BRect 681 BContainerWindow::InitialWindowRect(window_feel feel) 682 { 683 if (!sNewWindRect.IsValid()) { 684 const float labelSpacing = be_control_look->DefaultLabelSpacing(); 685 // approximately (85, 50, 548, 280) with default spacing 686 sNewWindRect = BRect(labelSpacing * 14, labelSpacing * 8, 687 labelSpacing * 91, labelSpacing * 46); 688 sWindowStaggerBy = (int32)(labelSpacing * 3.0f); 689 } 690 691 if (feel != kDesktopWindowFeel) 692 return sNewWindRect; 693 694 // do not offset desktop window 695 BRect result = sNewWindRect; 696 result.OffsetTo(0, 0); 697 return result; 698 } 699 700 701 void 702 BContainerWindow::Minimize(bool minimize) 703 { 704 if (minimize && (modifiers() & B_OPTION_KEY) != 0) 705 do_minimize_team(BRect(0, 0, 0, 0), be_app->Team(), true); 706 else 707 _inherited::Minimize(minimize); 708 } 709 710 711 bool 712 BContainerWindow::QuitRequested() 713 { 714 // this is a response to the DeskBar sending us a B_QUIT, when it really 715 // means to say close all your windows. It might be better to have it 716 // send a kCloseAllWindows message and have windowless apps stay running, 717 // which is what we will do for the Tracker 718 if (CurrentMessage() != NULL 719 && ((CurrentMessage()->FindInt32("modifiers") & B_CONTROL_KEY)) != 0) { 720 be_app->PostMessage(kCloseAllWindows); 721 } 722 723 Hide(); 724 // this will close the window instantly, even if 725 // the file system is very busy right now 726 return true; 727 } 728 729 730 void 731 BContainerWindow::Quit() 732 { 733 // get rid of context menus 734 if (fNavigationItem) { 735 BMenu* menu = fNavigationItem->Menu(); 736 if (menu != NULL) 737 menu->RemoveItem(fNavigationItem); 738 739 delete fNavigationItem; 740 fNavigationItem = NULL; 741 } 742 743 if (fOpenWithItem != NULL && fOpenWithItem->Menu() == NULL) { 744 delete fOpenWithItem; 745 fOpenWithItem = NULL; 746 } 747 748 if (fMoveToItem != NULL && fMoveToItem->Menu() == NULL) { 749 delete fMoveToItem; 750 fMoveToItem = NULL; 751 } 752 753 if (fCopyToItem != NULL && fCopyToItem->Menu() == NULL) { 754 delete fCopyToItem; 755 fCopyToItem = NULL; 756 } 757 758 if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() == NULL) { 759 delete fCreateLinkItem; 760 fCreateLinkItem = NULL; 761 } 762 763 if (fAttrMenu != NULL && fAttrMenu->Supermenu() == NULL) { 764 delete fAttrMenu; 765 fAttrMenu = NULL; 766 } 767 768 delete fFileContextMenu; 769 fFileContextMenu = NULL; 770 771 delete fWindowContextMenu; 772 fWindowContextMenu = NULL; 773 774 delete fDropContextMenu; 775 fDropContextMenu = NULL; 776 777 delete fVolumeContextMenu; 778 fVolumeContextMenu = NULL; 779 780 delete fDragContextMenu; 781 fDragContextMenu = NULL; 782 783 delete fTrashContextMenu; 784 fTrashContextMenu = NULL; 785 786 int32 windowCount = 0; 787 788 // This is a deadlock code sequence - need to change this 789 // to acquire the window list while this container window is unlocked 790 if (fWindowList != NULL) { 791 AutoLock<LockingList<BWindow> > lock(fWindowList); 792 if (lock.IsLocked()) { 793 fWindowList->RemoveItem(this, false); 794 windowCount = fWindowList->CountItems(); 795 } 796 } 797 798 if (StateNeedsSaving()) 799 SaveState(); 800 801 if (fWindowList != NULL && windowCount == 0) 802 be_app->PostMessage(B_QUIT_REQUESTED); 803 804 _inherited::Quit(); 805 } 806 807 808 BPoseView* 809 BContainerWindow::NewPoseView(Model* model, uint32 viewMode) 810 { 811 return new BPoseView(model, viewMode); 812 } 813 814 815 void 816 BContainerWindow::UpdateIfTrash(Model* model) 817 { 818 BEntry entry(model->EntryRef()); 819 820 if (entry.InitCheck() == B_OK) { 821 fIsTrash = model->IsTrash(); 822 fInTrash = FSInTrashDir(model->EntryRef()); 823 fIsPrinters = FSIsPrintersDir(&entry); 824 } 825 } 826 827 828 void 829 BContainerWindow::CreatePoseView(Model* model) 830 { 831 UpdateIfTrash(model); 832 833 fPoseView = NewPoseView(model, kListMode); 834 fBorderedView->GroupLayout()->AddView(fPoseView); 835 fBorderedView->GroupLayout()->SetInsets(1, 0, 1, 1); 836 fBorderedView->EnableBorderHighlight(false); 837 838 TrackerSettings settings; 839 if (settings.SingleWindowBrowse() && model->IsDirectory() 840 && !fPoseView->IsFilePanel()) { 841 fNavigator = new BNavigator(model); 842 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2); 843 if (!settings.ShowNavigator()) 844 fNavigator->Hide(); 845 } 846 847 SetPathWatchingEnabled(settings.ShowNavigator() 848 || settings.ShowFullPathInTitleBar()); 849 } 850 851 852 void 853 BContainerWindow::AddContextMenus() 854 { 855 // create context sensitive menus 856 fFileContextMenu = new BPopUpMenu("FileContext", false, false); 857 AddFileContextMenus(fFileContextMenu); 858 859 fVolumeContextMenu = new BPopUpMenu("VolumeContext", false, false); 860 AddVolumeContextMenus(fVolumeContextMenu); 861 862 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); 863 AddWindowContextMenus(fWindowContextMenu); 864 865 fDropContextMenu = new BPopUpMenu("DropContext", false, false); 866 AddDropContextMenus(fDropContextMenu); 867 868 fDragContextMenu = new BPopUpNavMenu("DragContext"); 869 // will get added and built dynamically in ShowContextMenu 870 871 fTrashContextMenu = new BPopUpMenu("TrashContext", false, false); 872 AddTrashContextMenus(fTrashContextMenu); 873 } 874 875 876 void 877 BContainerWindow::RepopulateMenus() 878 { 879 // Avoid these menus to be destroyed: 880 if (fMoveToItem != NULL && fMoveToItem->Menu() != NULL) 881 fMoveToItem->Menu()->RemoveItem(fMoveToItem); 882 883 if (fCopyToItem != NULL && fCopyToItem->Menu() != NULL) 884 fCopyToItem->Menu()->RemoveItem(fCopyToItem); 885 886 if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() != NULL) 887 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); 888 889 if (fOpenWithItem != NULL && fOpenWithItem->Menu() != NULL) { 890 fOpenWithItem->Menu()->RemoveItem(fOpenWithItem); 891 delete fOpenWithItem; 892 fOpenWithItem = NULL; 893 } 894 895 if (fNavigationItem != NULL) { 896 BMenu* menu = fNavigationItem->Menu(); 897 if (menu != NULL) { 898 menu->RemoveItem(fNavigationItem); 899 BMenuItem* item = menu->RemoveItem((int32)0); 900 ASSERT(item != fNavigationItem); 901 delete item; 902 } 903 } 904 905 delete fFileContextMenu; 906 fFileContextMenu = new BPopUpMenu("FileContext", false, false); 907 fFileContextMenu->SetFont(be_plain_font); 908 AddFileContextMenus(fFileContextMenu); 909 910 delete fWindowContextMenu; 911 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); 912 fWindowContextMenu->SetFont(be_plain_font); 913 AddWindowContextMenus(fWindowContextMenu); 914 915 if (fMenuBar != NULL) { 916 fMenuBar->RemoveItem(fFileMenu); 917 delete fFileMenu; 918 fFileMenu = new BMenu(B_TRANSLATE("File")); 919 AddFileMenu(fFileMenu); 920 fMenuBar->AddItem(fFileMenu); 921 922 fMenuBar->RemoveItem(fWindowMenu); 923 delete fWindowMenu; 924 fWindowMenu = new BMenu(B_TRANSLATE("Window")); 925 fMenuBar->AddItem(fWindowMenu); 926 AddWindowMenu(fWindowMenu); 927 928 // just create the attribute, decide to add it later 929 fMenuBar->RemoveItem(fAttrMenu); 930 delete fAttrMenu; 931 fAttrMenu = new BMenu(B_TRANSLATE("Attributes")); 932 NewAttributeMenu(fAttrMenu); 933 if (PoseView()->ViewMode() == kListMode) 934 ShowAttributeMenu(); 935 936 PopulateArrangeByMenu(fArrangeByMenu); 937 938 int32 selectCount = PoseView()->CountSelected(); 939 940 SetupOpenWithMenu(fFileMenu); 941 SetupMoveCopyMenus(selectCount ? PoseView()->SelectionList() 942 ->FirstItem()->TargetModel()->EntryRef() : NULL, 943 fFileMenu); 944 } 945 } 946 947 948 void 949 BContainerWindow::Init(const BMessage* message) 950 { 951 BEntry entry; 952 953 ASSERT(fPoseView != NULL); 954 if (fPoseView == NULL) 955 return; 956 957 // deal with new unconfigured folders 958 if (NeedsDefaultStateSetup()) 959 SetUpDefaultState(); 960 961 if (ShouldAddScrollBars()) 962 fPoseView->AddScrollBars(); 963 964 fMoveToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Move to"), 965 kMoveSelectionTo, this)); 966 fCopyToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Copy to"), 967 kCopySelectionTo, this)); 968 fCreateLinkItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Create link"), 969 kCreateLink, this), new BMessage(kCreateLink)); 970 971 TrackerSettings settings; 972 973 if (ShouldAddMenus()) { 974 fMenuBar = new BMenuBar("MenuBar"); 975 fMenuContainer->GroupLayout()->AddView(fMenuBar); 976 AddMenus(); 977 978 if (!TargetModel()->IsRoot() && !IsTrash()) 979 _AddFolderIcon(); 980 } else { 981 // add equivalents of the menu shortcuts to the menuless 982 // desktop window 983 AddShortcuts(); 984 } 985 986 AddContextMenus(); 987 AddShortcut('T', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kDelete), 988 PoseView()); 989 AddShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCleanupAll), 990 PoseView()); 991 AddShortcut('Q', B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY 992 | B_CONTROL_KEY, new BMessage(kQuitTracker)); 993 994 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenSelection), 995 PoseView()); 996 997 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); 998 999 #if DEBUG 1000 // add some debugging shortcuts 1001 AddShortcut('D', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dbug'), 1002 PoseView()); 1003 AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpcc'), 1004 PoseView()); 1005 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpfl'), 1006 PoseView()); 1007 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY, 1008 new BMessage('dpfL'), PoseView()); 1009 #endif 1010 1011 BKeymap keymap; 1012 if (keymap.SetToCurrent() == B_OK) { 1013 BObjectList<const char> unmodified(3, true); 1014 if (keymap.GetModifiedCharacters("+", B_SHIFT_KEY, 0, &unmodified) 1015 == B_OK) { 1016 int32 count = unmodified.CountItems(); 1017 for (int32 i = 0; i < count; i++) { 1018 uint32 key = BUnicodeChar::FromUTF8(unmodified.ItemAt(i)); 1019 if (!HasShortcut(key, 0)) { 1020 // Add semantic zoom in shortcut, bug #6692 1021 BMessage* increaseSize = new BMessage(kIconMode); 1022 increaseSize->AddInt32("scale", 1); 1023 AddShortcut(key, B_COMMAND_KEY, increaseSize, PoseView()); 1024 } 1025 } 1026 } 1027 unmodified.MakeEmpty(); 1028 } 1029 1030 if (message != NULL) 1031 RestoreState(*message); 1032 else 1033 RestoreState(); 1034 1035 if (ShouldAddMenus() && PoseView()->ViewMode() == kListMode) { 1036 // for now only show attributes in list view 1037 // eventually enable attribute menu to allow users to select 1038 // using different attributes as titles in icon view modes 1039 ShowAttributeMenu(); 1040 } 1041 MarkAttributeMenu(fAttrMenu); 1042 CheckScreenIntersect(); 1043 1044 if (fBackgroundImage != NULL && !fIsDesktop 1045 && PoseView()->ViewMode() != kListMode) { 1046 fBackgroundImage->Show(PoseView(), current_workspace()); 1047 } 1048 1049 Show(); 1050 1051 // done showing, turn the B_NO_WORKSPACE_ACTIVATION flag off; 1052 // it was on to prevent workspace jerking during boot 1053 SetFlags(Flags() & ~B_NO_WORKSPACE_ACTIVATION); 1054 } 1055 1056 1057 void 1058 BContainerWindow::InitLayout() 1059 { 1060 fBorderedView->GroupLayout()->AddView(0, fPoseView->TitleView()); 1061 1062 fCountContainer->GroupLayout()->AddView(fPoseView->CountView(), 0.25f); 1063 1064 bool forFilePanel = PoseView()->IsFilePanel(); 1065 if (!forFilePanel) { 1066 // Eliminate the extra borders 1067 fPoseContainer->GridLayout()->SetInsets(-1, 0, -1, -1); 1068 fCountContainer->GroupLayout()->SetInsets(0, -1, 0, 0); 1069 } 1070 1071 if (fPoseView->VScrollBar() != NULL) { 1072 fVScrollBarContainer = new BGroupView(B_VERTICAL, 0); 1073 fVScrollBarContainer->GroupLayout()->AddView(fPoseView->VScrollBar()); 1074 fVScrollBarContainer->GroupLayout()->SetInsets(-1, forFilePanel ? 0 : -1, 1075 0, 0); 1076 fPoseContainer->GridLayout()->AddView(fVScrollBarContainer, 1, 1); 1077 } 1078 if (fPoseView->HScrollBar() != NULL) { 1079 BGroupView* hScrollBarContainer = new BGroupView(B_VERTICAL, 0); 1080 hScrollBarContainer->GroupLayout()->AddView(fPoseView->HScrollBar()); 1081 hScrollBarContainer->GroupLayout()->SetInsets(0, -1, 0, 1082 forFilePanel ? 0 : -1); 1083 fCountContainer->GroupLayout()->AddView(hScrollBarContainer); 1084 1085 BSize size = fPoseView->HScrollBar()->MinSize(); 1086 if (forFilePanel) { 1087 // Count view height is 1px smaller than scroll bar because it has 1088 // no upper border. 1089 size.height -= 1; 1090 } 1091 fPoseView->CountView()->SetExplicitMinSize(size); 1092 } 1093 } 1094 1095 1096 void 1097 BContainerWindow::RestoreState() 1098 { 1099 UpdateTitle(); 1100 1101 WindowStateNodeOpener opener(this, false); 1102 RestoreWindowState(opener.StreamNode()); 1103 fPoseView->Init(opener.StreamNode()); 1104 1105 RestoreStateCommon(); 1106 } 1107 1108 1109 void 1110 BContainerWindow::RestoreState(const BMessage &message) 1111 { 1112 UpdateTitle(); 1113 1114 RestoreWindowState(message); 1115 fPoseView->Init(message); 1116 1117 RestoreStateCommon(); 1118 } 1119 1120 1121 void 1122 BContainerWindow::RestoreStateCommon() 1123 { 1124 if (!fIsDesktop && fUseLayouts) 1125 InitLayout(); 1126 1127 if (BootedInSafeMode()) 1128 // don't pick up backgrounds in safe mode 1129 return; 1130 1131 WindowStateNodeOpener opener(this, false); 1132 1133 if (!TargetModel()->IsRoot() && opener.Node() != NULL) { 1134 // don't pick up background image for root disks 1135 // to do this, would have to have a unique attribute for the 1136 // disks window that doesn't collide with the desktop 1137 // for R4 this was not done to make things simpler 1138 // the default image will still work though 1139 fBackgroundImage = BackgroundImage::GetBackgroundImage( 1140 opener.Node(), fIsDesktop); 1141 // look for background image info in the window's node 1142 } 1143 1144 BNode defaultingNode; 1145 if (fBackgroundImage == NULL && !fIsDesktop 1146 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) { 1147 // look for background image info in the source for defaults 1148 fBackgroundImage = BackgroundImage::GetBackgroundImage(&defaultingNode, 1149 fIsDesktop); 1150 } 1151 } 1152 1153 1154 void 1155 BContainerWindow::UpdateTitle() 1156 { 1157 // set title to full path, if necessary 1158 if (TrackerSettings().ShowFullPathInTitleBar()) { 1159 // use the Entry's full path 1160 BPath path; 1161 TargetModel()->GetPath(&path); 1162 SetTitle(path.Path()); 1163 } else { 1164 // use the default look 1165 SetTitle(TargetModel()->Name()); 1166 } 1167 1168 if (Navigator() != NULL) { 1169 Navigator()->UpdateLocation(PoseView()->TargetModel(), 1170 kActionUpdatePath); 1171 } 1172 } 1173 1174 1175 void 1176 BContainerWindow::UpdateBackgroundImage() 1177 { 1178 if (BootedInSafeMode()) 1179 return; 1180 1181 WindowStateNodeOpener opener(this, false); 1182 1183 if (!TargetModel()->IsRoot() && opener.Node() != NULL) { 1184 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, 1185 opener.Node(), fIsDesktop, PoseView()); 1186 } 1187 1188 // look for background image info in the window's node 1189 BNode defaultingNode; 1190 if (!fBackgroundImage && !fIsDesktop 1191 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) { 1192 // look for background image info in the source for defaults 1193 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, 1194 &defaultingNode, fIsDesktop, PoseView()); 1195 } 1196 } 1197 1198 1199 void 1200 BContainerWindow::FrameResized(float, float) 1201 { 1202 if (PoseView() != NULL && !fIsDesktop) { 1203 BRect extent = PoseView()->Extent(); 1204 float offsetX = extent.left - PoseView()->Bounds().left; 1205 float offsetY = extent.top - PoseView()->Bounds().top; 1206 1207 // scroll when the size augmented, there is a negative offset 1208 // and we have resized over the bottom right corner of the extent 1209 BPoint scroll(B_ORIGIN); 1210 if (offsetX < 0 && PoseView()->Bounds().right > extent.right 1211 && Bounds().Width() > fPreviousBounds.Width()) { 1212 scroll.x = std::max(fPreviousBounds.Width() - Bounds().Width(), 1213 offsetX); 1214 } 1215 1216 if (offsetY < 0 && PoseView()->Bounds().bottom > extent.bottom 1217 && Bounds().Height() > fPreviousBounds.Height()) { 1218 scroll.y = std::max(fPreviousBounds.Height() - Bounds().Height(), 1219 offsetY); 1220 } 1221 1222 if (scroll != B_ORIGIN) 1223 PoseView()->ScrollBy(scroll.x, scroll.y); 1224 1225 PoseView()->UpdateScrollRange(); 1226 PoseView()->ResetPosePlacementHint(); 1227 } 1228 1229 fPreviousBounds = Bounds(); 1230 if (IsActive()) 1231 fStateNeedsSaving = true; 1232 } 1233 1234 1235 void 1236 BContainerWindow::FrameMoved(BPoint) 1237 { 1238 if (IsActive()) 1239 fStateNeedsSaving = true; 1240 } 1241 1242 1243 void 1244 BContainerWindow::WorkspacesChanged(uint32, uint32) 1245 { 1246 if (IsActive()) 1247 fStateNeedsSaving = true; 1248 } 1249 1250 1251 void 1252 BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode) 1253 { 1254 if (fBackgroundImage == NULL) 1255 return; 1256 1257 if (newMode == kListMode) 1258 fBackgroundImage->Remove(); 1259 else if (oldMode == kListMode) 1260 fBackgroundImage->Show(PoseView(), current_workspace()); 1261 } 1262 1263 1264 void 1265 BContainerWindow::CheckScreenIntersect() 1266 { 1267 BScreen screen(this); 1268 BRect screenFrame(screen.Frame()); 1269 BRect frame(Frame()); 1270 1271 if (sNewWindRect.bottom > screenFrame.bottom) 1272 sNewWindRect.OffsetTo(85, 50); 1273 1274 if (sNewWindRect.right > screenFrame.right) 1275 sNewWindRect.OffsetTo(85, 50); 1276 1277 if (!frame.Intersects(screenFrame)) 1278 MoveTo(sNewWindRect.LeftTop()); 1279 } 1280 1281 1282 void 1283 BContainerWindow::SaveState(bool hide) 1284 { 1285 if (SaveStateIsEnabled()) { 1286 WindowStateNodeOpener opener(this, true); 1287 if (opener.StreamNode() != NULL) 1288 SaveWindowState(opener.StreamNode()); 1289 1290 if (hide) 1291 Hide(); 1292 1293 if (opener.StreamNode()) 1294 fPoseView->SaveState(opener.StreamNode()); 1295 1296 fStateNeedsSaving = false; 1297 } 1298 } 1299 1300 1301 void 1302 BContainerWindow::SaveState(BMessage& message) const 1303 { 1304 if (SaveStateIsEnabled()) { 1305 SaveWindowState(message); 1306 fPoseView->SaveState(message); 1307 } 1308 } 1309 1310 1311 bool 1312 BContainerWindow::StateNeedsSaving() const 1313 { 1314 return fPoseView != NULL && (fStateNeedsSaving || fPoseView->StateNeedsSaving()); 1315 } 1316 1317 1318 status_t 1319 BContainerWindow::GetLayoutState(BNode* node, BMessage* message) 1320 { 1321 if (node == NULL || message == NULL) 1322 return B_BAD_VALUE; 1323 1324 status_t result = node->InitCheck(); 1325 if (result != B_OK) 1326 return result; 1327 1328 // ToDo: get rid of this, use AttrStream instead 1329 node->RewindAttrs(); 1330 char attrName[256]; 1331 while (node->GetNextAttrName(attrName) == B_OK) { 1332 attr_info info; 1333 if (node->GetAttrInfo(attrName, &info) != B_OK) 1334 continue; 1335 1336 // filter out attributes that are not related to window position 1337 // and column resizing 1338 // more can be added as needed 1339 if (strcmp(attrName, kAttrWindowFrame) != 0 1340 && strcmp(attrName, kAttrColumns) != 0 1341 && strcmp(attrName, kAttrViewState) != 0 1342 && strcmp(attrName, kAttrColumnsForeign) != 0 1343 && strcmp(attrName, kAttrViewStateForeign) != 0) { 1344 continue; 1345 } 1346 1347 char* buffer = new char[info.size]; 1348 if (node->ReadAttr(attrName, info.type, 0, buffer, 1349 (size_t)info.size) == info.size) { 1350 message->AddData(attrName, info.type, buffer, (ssize_t)info.size); 1351 } 1352 delete[] buffer; 1353 } 1354 1355 return B_OK; 1356 } 1357 1358 1359 status_t 1360 BContainerWindow::SetLayoutState(BNode* node, const BMessage* message) 1361 { 1362 status_t result = node->InitCheck(); 1363 if (result != B_OK) 1364 return result; 1365 1366 for (int32 globalIndex = 0; ;) { 1367 #if B_BEOS_VERSION_DANO 1368 const char* name; 1369 #else 1370 char* name; 1371 #endif 1372 type_code type; 1373 int32 count; 1374 status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name, 1375 &type, &count); 1376 if (result != B_OK) 1377 break; 1378 1379 for (int32 index = 0; index < count; index++) { 1380 const void* buffer; 1381 ssize_t size; 1382 result = message->FindData(name, type, index, &buffer, &size); 1383 if (result != B_OK) { 1384 PRINT(("error reading %s \n", name)); 1385 return result; 1386 } 1387 1388 if (node->WriteAttr(name, type, 0, buffer, 1389 (size_t)size) != size) { 1390 PRINT(("error writing %s \n", name)); 1391 return result; 1392 } 1393 globalIndex++; 1394 } 1395 } 1396 1397 return B_OK; 1398 } 1399 1400 1401 bool 1402 BContainerWindow::ShouldAddMenus() const 1403 { 1404 return true; 1405 } 1406 1407 1408 bool 1409 BContainerWindow::ShouldAddScrollBars() const 1410 { 1411 return true; 1412 } 1413 1414 1415 Model* 1416 BContainerWindow::TargetModel() const 1417 { 1418 return fPoseView->TargetModel(); 1419 } 1420 1421 1422 void 1423 BContainerWindow::SelectionChanged() 1424 { 1425 } 1426 1427 1428 void 1429 BContainerWindow::Zoom(BPoint, float, float) 1430 { 1431 BRect oldZoomRect(fSavedZoomRect); 1432 fSavedZoomRect = Frame(); 1433 ResizeToFit(); 1434 1435 if (fSavedZoomRect == Frame() && oldZoomRect.IsValid()) 1436 ResizeTo(oldZoomRect.Width(), oldZoomRect.Height()); 1437 } 1438 1439 1440 void 1441 BContainerWindow::ResizeToFit() 1442 { 1443 BScreen screen(this); 1444 BRect screenFrame(screen.Frame()); 1445 1446 screenFrame.InsetBy(5, 5); 1447 BMessage decoratorSettings; 1448 GetDecoratorSettings(&decoratorSettings); 1449 1450 float tabHeight = 15; 1451 BRect tabRect; 1452 if (decoratorSettings.FindRect("tab frame", &tabRect) == B_OK) 1453 tabHeight = tabRect.Height(); 1454 screenFrame.top += tabHeight; 1455 1456 BRect frame(Frame()); 1457 1458 float widthDiff = frame.Width() - PoseView()->Frame().Width(); 1459 float heightDiff = frame.Height() - PoseView()->Frame().Height(); 1460 1461 // move frame left top on screen 1462 BPoint leftTop(frame.LeftTop()); 1463 leftTop.ConstrainTo(screenFrame); 1464 frame.OffsetTo(leftTop); 1465 1466 // resize to extent size 1467 BRect extent(PoseView()->Extent()); 1468 frame.right = frame.left + extent.Width() + widthDiff; 1469 frame.bottom = frame.top + extent.Height() + heightDiff; 1470 1471 // make sure entire window fits on screen 1472 frame = frame & screenFrame; 1473 1474 ResizeTo(frame.Width(), frame.Height()); 1475 MoveTo(frame.LeftTop()); 1476 PoseView()->DisableScrollBars(); 1477 1478 // scroll if there is an offset 1479 PoseView()->ScrollBy( 1480 extent.left - PoseView()->Bounds().left, 1481 extent.top - PoseView()->Bounds().top); 1482 1483 PoseView()->UpdateScrollRange(); 1484 PoseView()->EnableScrollBars(); 1485 } 1486 1487 1488 void 1489 BContainerWindow::MessageReceived(BMessage* message) 1490 { 1491 switch (message->what) { 1492 case B_CUT: 1493 case B_COPY: 1494 case B_PASTE: 1495 case B_SELECT_ALL: 1496 { 1497 BView* view = CurrentFocus(); 1498 if (dynamic_cast<BTextView*>(view) == NULL) { 1499 // The selected item is not a BTextView, so forward the 1500 // message to the PoseView. 1501 if (fPoseView != NULL) 1502 PostMessage(message, fPoseView); 1503 } else { 1504 // Since we catch the generic clipboard shortcuts in a way that 1505 // means the BTextView will never get them, we must 1506 // manually forward them ourselves. 1507 // 1508 // However, we have to take care to not forward the custom 1509 // clipboard messages, else we would wind up in infinite 1510 // recursion. 1511 PostMessage(message, view); 1512 } 1513 break; 1514 } 1515 1516 case kCutMoreSelectionToClipboard: 1517 case kCopyMoreSelectionToClipboard: 1518 case kPasteLinksFromClipboard: 1519 if (fPoseView != NULL) 1520 PostMessage(message, fPoseView); 1521 break; 1522 1523 case B_UNDO: { 1524 BView* view = CurrentFocus(); 1525 if (dynamic_cast<BTextView*>(view) == NULL) { 1526 FSUndo(); 1527 } else { 1528 view->MessageReceived(message); 1529 } 1530 break; 1531 } 1532 1533 case B_REDO: { 1534 BView* view = CurrentFocus(); 1535 if (dynamic_cast<BTextView*>(view) == NULL) { 1536 FSRedo(); 1537 } else { 1538 view->MessageReceived(message); 1539 } 1540 break; 1541 } 1542 1543 case kNewFolder: 1544 PostMessage(message, PoseView()); 1545 break; 1546 1547 case kRestoreState: 1548 if (message->HasMessage("state")) { 1549 BMessage state; 1550 message->FindMessage("state", &state); 1551 Init(&state); 1552 } else 1553 Init(); 1554 break; 1555 1556 case kResizeToFit: 1557 ResizeToFit(); 1558 break; 1559 1560 case kLoadAddOn: 1561 LoadAddOn(message); 1562 break; 1563 1564 case kCopySelectionTo: 1565 { 1566 entry_ref ref; 1567 if (message->FindRef("refs", &ref) != B_OK) 1568 break; 1569 1570 BRoster().AddToRecentFolders(&ref); 1571 1572 Model model(&ref); 1573 if (model.InitCheck() != B_OK) 1574 break; 1575 1576 if (*model.NodeRef() == *TargetModel()->NodeRef()) 1577 PoseView()->DuplicateSelection(); 1578 else 1579 PoseView()->MoveSelectionInto(&model, this, true); 1580 break; 1581 } 1582 1583 case kMoveSelectionTo: 1584 { 1585 entry_ref ref; 1586 if (message->FindRef("refs", &ref) != B_OK) 1587 break; 1588 1589 BRoster().AddToRecentFolders(&ref); 1590 1591 Model model(&ref); 1592 if (model.InitCheck() != B_OK) 1593 break; 1594 1595 PoseView()->MoveSelectionInto(&model, this, false, true); 1596 break; 1597 } 1598 1599 case kCreateLink: 1600 case kCreateRelativeLink: 1601 { 1602 entry_ref ref; 1603 if (message->FindRef("refs", &ref) == B_OK) { 1604 BRoster().AddToRecentFolders(&ref); 1605 1606 Model model(&ref); 1607 if (model.InitCheck() != B_OK) 1608 break; 1609 1610 PoseView()->MoveSelectionInto(&model, this, false, false, 1611 message->what == kCreateLink, 1612 message->what == kCreateRelativeLink); 1613 } else if (!TargetModel()->IsQuery() 1614 && !TargetModel()->IsVirtualDirectory()) { 1615 // no destination specified, create link in same dir as item 1616 PoseView()->MoveSelectionInto(TargetModel(), this, false, false, 1617 message->what == kCreateLink, 1618 message->what == kCreateRelativeLink); 1619 } 1620 break; 1621 } 1622 1623 case kShowSelectionWindow: 1624 ShowSelectionWindow(); 1625 break; 1626 1627 case kSelectMatchingEntries: 1628 PoseView()->SelectMatchingEntries(message); 1629 break; 1630 1631 case kFindButton: 1632 (new FindWindow())->Show(); 1633 break; 1634 1635 case kQuitTracker: 1636 be_app->PostMessage(B_QUIT_REQUESTED); 1637 break; 1638 1639 case kRestoreBackgroundImage: 1640 UpdateBackgroundImage(); 1641 break; 1642 1643 case kSwitchDirectory: 1644 { 1645 entry_ref ref; 1646 if (message->FindRef("refs", &ref) != B_OK) 1647 break; 1648 1649 BEntry entry; 1650 if (entry.SetTo(&ref) != B_OK) 1651 break; 1652 1653 if (StateNeedsSaving()) 1654 SaveState(false); 1655 1656 bool wasInTrash = IsTrash() || InTrash(); 1657 bool isRoot = PoseView()->TargetModel()->IsRoot(); 1658 1659 // Switch dir and apply new state 1660 WindowStateNodeOpener opener(this, false); 1661 opener.SetTo(&entry, false); 1662 1663 // Update PoseView 1664 PoseView()->SwitchDir(&ref, opener.StreamNode()); 1665 1666 fIsTrash = FSIsTrashDir(&entry); 1667 fInTrash = FSInTrashDir(&ref); 1668 1669 if (wasInTrash ^ (IsTrash() || InTrash()) 1670 || isRoot != PoseView()->TargetModel()->IsRoot()) { 1671 RepopulateMenus(); 1672 } 1673 1674 if (Navigator() != NULL) { 1675 // update Navigation bar 1676 int32 action = kActionSet; 1677 if (message->FindInt32("action", &action) != B_OK) { 1678 // Design problem? Why does FindInt32 touch 1679 // 'action' at all if he can't find it?? 1680 action = kActionSet; 1681 } 1682 Navigator()->UpdateLocation(PoseView()->TargetModel(), 1683 action); 1684 } 1685 1686 TrackerSettings settings; 1687 if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar()) 1688 SetPathWatchingEnabled(true); 1689 1690 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); 1691 1692 // Update draggable folder icon 1693 if (fMenuBar != NULL) { 1694 if (!TargetModel()->IsRoot() && !IsTrash()) { 1695 // Folder icon should be visible, but in single 1696 // window navigation, it might not be. 1697 if (fDraggableIcon != NULL) { 1698 IconCache::sIconCache->IconChanged(TargetModel()); 1699 if (fDraggableIcon->IsHidden()) 1700 fDraggableIcon->Show(); 1701 fDraggableIcon->Invalidate(); 1702 } else 1703 _AddFolderIcon(); 1704 } else if (fDraggableIcon != NULL) 1705 fDraggableIcon->Hide(); 1706 } 1707 1708 // Update window title 1709 UpdateTitle(); 1710 break; 1711 } 1712 1713 case B_REFS_RECEIVED: 1714 if (Dragging()) { 1715 // ref in this message is the target, 1716 // the end point of the drag 1717 1718 entry_ref ref; 1719 if (message->FindRef("refs", &ref) == B_OK) { 1720 fWaitingForRefs = false; 1721 BEntry entry(&ref, true); 1722 // don't copy to printers dir 1723 if (!FSIsPrintersDir(&entry)) { 1724 if (entry.InitCheck() == B_OK 1725 && entry.IsDirectory()) { 1726 Model targetModel(&entry, true, false); 1727 BPoint dropPoint; 1728 uint32 buttons; 1729 PoseView()->GetMouse(&dropPoint, &buttons, true); 1730 PoseView()->HandleDropCommon(fDragMessage, 1731 &targetModel, NULL, PoseView(), dropPoint); 1732 } 1733 } 1734 } 1735 DragStop(); 1736 } 1737 break; 1738 1739 case B_TRACKER_ADDON_MESSAGE: 1740 { 1741 _PassMessageToAddOn(message); 1742 break; 1743 } 1744 1745 case B_OBSERVER_NOTICE_CHANGE: 1746 { 1747 int32 observerWhat; 1748 if (message->FindInt32("be:observe_change_what", &observerWhat) 1749 == B_OK) { 1750 TrackerSettings settings; 1751 switch (observerWhat) { 1752 case kWindowsShowFullPathChanged: 1753 UpdateTitle(); 1754 if (!IsPathWatchingEnabled() 1755 && settings.ShowFullPathInTitleBar()) { 1756 SetPathWatchingEnabled(true); 1757 } 1758 if (IsPathWatchingEnabled() 1759 && !(settings.ShowNavigator() 1760 || settings.ShowFullPathInTitleBar())) { 1761 SetPathWatchingEnabled(false); 1762 } 1763 break; 1764 1765 case kSingleWindowBrowseChanged: 1766 if (settings.SingleWindowBrowse() 1767 && !Navigator() 1768 && TargetModel()->IsDirectory() 1769 && !PoseView()->IsFilePanel() 1770 && !PoseView()->IsDesktopWindow()) { 1771 fNavigator = new BNavigator(TargetModel()); 1772 fPoseContainer->GridLayout()->AddView(fNavigator, 1773 0, 0, 2); 1774 fNavigator->Hide(); 1775 SetPathWatchingEnabled(settings.ShowNavigator() 1776 || settings.ShowFullPathInTitleBar()); 1777 } 1778 1779 if (!settings.SingleWindowBrowse() 1780 && !fIsDesktop && TargetModel()->IsDesktop()) { 1781 // Close the "Desktop" window, but not the Desktop 1782 this->Quit(); 1783 } 1784 1785 SetSingleWindowBrowseShortcuts( 1786 settings.SingleWindowBrowse()); 1787 break; 1788 1789 case kShowNavigatorChanged: 1790 ShowNavigator(settings.ShowNavigator()); 1791 if (!IsPathWatchingEnabled() 1792 && settings.ShowNavigator()) { 1793 SetPathWatchingEnabled(true); 1794 } 1795 if (IsPathWatchingEnabled() 1796 && !(settings.ShowNavigator() 1797 || settings.ShowFullPathInTitleBar())) { 1798 SetPathWatchingEnabled(false); 1799 } 1800 SetSingleWindowBrowseShortcuts( 1801 settings.SingleWindowBrowse()); 1802 break; 1803 1804 case kDontMoveFilesToTrashChanged: 1805 { 1806 bool dontMoveToTrash 1807 = settings.DontMoveFilesToTrash(); 1808 1809 BMenuItem* item 1810 = fFileContextMenu->FindItem(kMoveToTrash); 1811 if (item != NULL) { 1812 item->SetLabel(dontMoveToTrash 1813 ? B_TRANSLATE("Delete") 1814 : B_TRANSLATE("Move to Trash")); 1815 } 1816 // Deskbar doesn't have a menu bar, so check if 1817 // there is fMenuBar 1818 if (fMenuBar != NULL && fFileMenu != NULL) { 1819 item = fFileMenu->FindItem(kMoveToTrash); 1820 if (item != NULL) { 1821 item->SetLabel(dontMoveToTrash 1822 ? B_TRANSLATE("Delete") 1823 : B_TRANSLATE("Move to Trash")); 1824 } 1825 } 1826 UpdateIfNeeded(); 1827 break; 1828 } 1829 1830 default: 1831 _inherited::MessageReceived(message); 1832 break; 1833 } 1834 } 1835 break; 1836 } 1837 1838 case B_NODE_MONITOR: 1839 UpdateTitle(); 1840 break; 1841 1842 default: 1843 _inherited::MessageReceived(message); 1844 break; 1845 } 1846 } 1847 1848 1849 void 1850 BContainerWindow::SetCutItem(BMenu* menu) 1851 { 1852 BMenuItem* item; 1853 if ((item = menu->FindItem(B_CUT)) == NULL 1854 && (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) { 1855 return; 1856 } 1857 1858 if (PoseView() != CurrentFocus()) 1859 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL); 1860 else { 1861 if (TargetModel()->IsRoot() || TargetModel()->IsTrash() 1862 || TargetModel()->IsVirtualDirectory()) { 1863 // cannot cut files in root, trash or in a virtual directory 1864 item->SetEnabled(false); 1865 } else { 1866 item->SetEnabled(PoseView()->CountSelected() > 0 1867 && !PoseView()->TargetVolumeIsReadOnly()); 1868 } 1869 } 1870 1871 if ((modifiers() & B_SHIFT_KEY) != 0) { 1872 item->SetLabel(B_TRANSLATE("Cut more")); 1873 item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY); 1874 item->SetMessage(new BMessage(kCutMoreSelectionToClipboard)); 1875 } else { 1876 item->SetLabel(B_TRANSLATE("Cut")); 1877 item->SetShortcut('X', B_COMMAND_KEY); 1878 item->SetMessage(new BMessage(B_CUT)); 1879 } 1880 } 1881 1882 1883 void 1884 BContainerWindow::SetCopyItem(BMenu* menu) 1885 { 1886 BMenuItem* item; 1887 if ((item = menu->FindItem(B_COPY)) == NULL 1888 && (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) { 1889 return; 1890 } 1891 1892 if (PoseView() != CurrentFocus()) 1893 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL); 1894 else 1895 item->SetEnabled(PoseView()->CountSelected() > 0); 1896 1897 if ((modifiers() & B_SHIFT_KEY) != 0) { 1898 item->SetLabel(B_TRANSLATE("Copy more")); 1899 item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY); 1900 item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard)); 1901 } else { 1902 item->SetLabel(B_TRANSLATE("Copy")); 1903 item->SetShortcut('C', B_COMMAND_KEY); 1904 item->SetMessage(new BMessage(B_COPY)); 1905 } 1906 } 1907 1908 1909 void 1910 BContainerWindow::SetPasteItem(BMenu* menu) 1911 { 1912 BMenuItem* item; 1913 if ((item = menu->FindItem(B_PASTE)) == NULL 1914 && (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) { 1915 return; 1916 } 1917 1918 if (PoseView() != CurrentFocus()) 1919 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL); 1920 else { 1921 item->SetEnabled(FSClipboardHasRefs() 1922 && !PoseView()->TargetVolumeIsReadOnly()); 1923 } 1924 1925 if ((modifiers() & B_SHIFT_KEY) != 0) { 1926 item->SetLabel(B_TRANSLATE("Paste links")); 1927 item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY); 1928 item->SetMessage(new BMessage(kPasteLinksFromClipboard)); 1929 } else { 1930 item->SetLabel(B_TRANSLATE("Paste")); 1931 item->SetShortcut('V', B_COMMAND_KEY); 1932 item->SetMessage(new BMessage(B_PASTE)); 1933 } 1934 } 1935 1936 1937 void 1938 BContainerWindow::SetArrangeMenu(BMenu* menu) 1939 { 1940 BMenuItem* item; 1941 if ((item = menu->FindItem(kCleanup)) == NULL 1942 && (item = menu->FindItem(kCleanupAll)) == NULL) { 1943 return; 1944 } 1945 1946 item->Menu()->SetEnabled(PoseView()->CountItems() > 0 1947 && (PoseView()->ViewMode() != kListMode)); 1948 1949 BMenu* arrangeMenu; 1950 1951 if ((modifiers() & B_SHIFT_KEY) != 0) { 1952 item->SetLabel(B_TRANSLATE("Clean up all")); 1953 item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY); 1954 item->SetMessage(new BMessage(kCleanupAll)); 1955 arrangeMenu = item->Menu(); 1956 } else { 1957 item->SetLabel(B_TRANSLATE("Clean up")); 1958 item->SetShortcut('K', B_COMMAND_KEY); 1959 item->SetMessage(new BMessage(kCleanup)); 1960 arrangeMenu = item->Menu(); 1961 } 1962 1963 MarkArrangeByMenu(arrangeMenu); 1964 } 1965 1966 1967 void 1968 BContainerWindow::SetCloseItem(BMenu* menu) 1969 { 1970 BMenuItem* item; 1971 if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL 1972 && (item = menu->FindItem(kCloseAllWindows)) == NULL) { 1973 return; 1974 } 1975 1976 if ((modifiers() & B_SHIFT_KEY) != 0) { 1977 item->SetLabel(B_TRANSLATE("Close all")); 1978 item->SetShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY); 1979 item->SetTarget(be_app); 1980 item->SetMessage(new BMessage(kCloseAllWindows)); 1981 } else { 1982 item->SetLabel(B_TRANSLATE("Close")); 1983 item->SetShortcut('W', B_COMMAND_KEY); 1984 item->SetTarget(this); 1985 item->SetMessage(new BMessage(B_QUIT_REQUESTED)); 1986 } 1987 } 1988 1989 1990 bool 1991 BContainerWindow::IsShowing(const node_ref* node) const 1992 { 1993 return PoseView()->Represents(node); 1994 } 1995 1996 1997 bool 1998 BContainerWindow::IsShowing(const entry_ref* entry) const 1999 { 2000 return PoseView()->Represents(entry); 2001 } 2002 2003 2004 void 2005 BContainerWindow::AddMenus() 2006 { 2007 fFileMenu = new BMenu(B_TRANSLATE("File")); 2008 AddFileMenu(fFileMenu); 2009 fMenuBar->AddItem(fFileMenu); 2010 fWindowMenu = new BMenu(B_TRANSLATE("Window")); 2011 fMenuBar->AddItem(fWindowMenu); 2012 AddWindowMenu(fWindowMenu); 2013 // just create the attribute, decide to add it later 2014 fAttrMenu = new BMenu(B_TRANSLATE("Attributes")); 2015 NewAttributeMenu(fAttrMenu); 2016 PopulateArrangeByMenu(fArrangeByMenu); 2017 } 2018 2019 2020 void 2021 BContainerWindow::AddFileMenu(BMenu* menu) 2022 { 2023 BMenuItem* item; 2024 2025 if (!PoseView()->IsFilePanel()) { 2026 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 2027 new BMessage(kFindButton), 'F')); 2028 } 2029 2030 if (!TargetModel()->IsQuery() && !TargetModel()->IsVirtualDirectory() 2031 && !IsTrash() && !IsPrintersDir() && !TargetModel()->IsRoot()) { 2032 if (!PoseView()->IsFilePanel()) { 2033 TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(), 2034 B_TRANSLATE("New")); 2035 menu->AddItem(templatesMenu); 2036 templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly()); 2037 templatesMenu->SetTargetForItems(PoseView()); 2038 } else { 2039 item = new BMenuItem(B_TRANSLATE("New folder"), 2040 new BMessage(kNewFolder), 'N'); 2041 item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly()); 2042 menu->AddItem(item); 2043 } 2044 } 2045 menu->AddSeparatorItem(); 2046 2047 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 2048 new BMessage(kOpenSelection), 'O')); 2049 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 2050 new BMessage(kGetInfo), 'I')); 2051 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 2052 new BMessage(kEditItem), 'E')); 2053 2054 if (IsTrash() || InTrash()) { 2055 menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"), 2056 new BMessage(kRestoreFromTrash))); 2057 if (IsTrash()) { 2058 // add as first item in menu 2059 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"), 2060 new BMessage(kEmptyTrash)), 0); 2061 menu->AddItem(new BSeparatorItem(), 1); 2062 } 2063 } else if (IsPrintersDir()) { 2064 menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS), 2065 new BMessage(kAddPrinter), 'N'), 0); 2066 menu->AddItem(new BSeparatorItem(), 1); 2067 menu->AddItem(new BMenuItem(B_TRANSLATE("Make active printer"), 2068 new BMessage(kMakeActivePrinter))); 2069 } else if (TargetModel()->IsRoot()) { 2070 item = new BMenuItem(B_TRANSLATE("Unmount"), 2071 new BMessage(kUnmountVolume), 'U'); 2072 item->SetEnabled(false); 2073 menu->AddItem(item); 2074 menu->AddItem(new BMenuItem( 2075 B_TRANSLATE("Mount settings" B_UTF8_ELLIPSIS), 2076 new BMessage(kRunAutomounterSettings))); 2077 } else { 2078 item = new BMenuItem(B_TRANSLATE("Duplicate"), 2079 new BMessage(kDuplicateSelection), 'D'); 2080 item->SetEnabled(PoseView()->CountSelected() > 0 2081 && !PoseView()->SelectedVolumeIsReadOnly()); 2082 menu->AddItem(item); 2083 2084 item = new BMenuItem(TrackerSettings().DontMoveFilesToTrash() 2085 ? B_TRANSLATE("Delete") : B_TRANSLATE("Move to Trash"), 2086 new BMessage(kMoveToTrash), 'T'); 2087 item->SetEnabled(PoseView()->CountSelected() > 0 2088 && !PoseView()->SelectedVolumeIsReadOnly()); 2089 menu->AddItem(item); 2090 2091 menu->AddSeparatorItem(); 2092 2093 // The "Move To", "Copy To", "Create Link" menus are inserted 2094 // at this place, have a look at: 2095 // BContainerWindow::SetupMoveCopyMenus() 2096 } 2097 2098 BMenuItem* cutItem = NULL; 2099 BMenuItem* copyItem = NULL; 2100 BMenuItem* pasteItem = NULL; 2101 if (!IsPrintersDir()) { 2102 menu->AddSeparatorItem(); 2103 2104 if (!TargetModel()->IsRoot()) { 2105 cutItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Cut"), 2106 new BMessage(B_CUT), 'X'); 2107 menu->AddItem(cutItem); 2108 copyItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Copy"), 2109 new BMessage(B_COPY), 'C'); 2110 menu->AddItem(copyItem); 2111 pasteItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Paste"), 2112 new BMessage(B_PASTE), 'V'); 2113 menu->AddItem(pasteItem); 2114 menu->AddSeparatorItem(); 2115 2116 menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), 2117 new BMessage(kIdentifyEntry))); 2118 } 2119 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons")); 2120 addOnMenuItem->SetFont(be_plain_font); 2121 menu->AddItem(addOnMenuItem); 2122 } 2123 2124 menu->SetTargetForItems(PoseView()); 2125 if (cutItem != NULL) 2126 cutItem->SetTarget(this); 2127 2128 if (copyItem != NULL) 2129 copyItem->SetTarget(this); 2130 2131 if (pasteItem != NULL) 2132 pasteItem->SetTarget(this); 2133 } 2134 2135 2136 void 2137 BContainerWindow::AddWindowMenu(BMenu* menu) 2138 { 2139 BMenuItem* item; 2140 2141 BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view")); 2142 2143 static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 }; 2144 BMessage* message; 2145 2146 for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) { 2147 uint32 iconSize = kIconSizes[i]; 2148 message = new BMessage(kIconMode); 2149 message->AddInt32("size", iconSize); 2150 BString label; 2151 label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32, 2152 "The '×' is the Unicode multiplication sign U+00D7"), 2153 iconSize, iconSize); 2154 item = new BMenuItem(label, message); 2155 item->SetTarget(PoseView()); 2156 iconSizeMenu->AddItem(item); 2157 } 2158 2159 iconSizeMenu->AddSeparatorItem(); 2160 2161 message = new BMessage(kIconMode); 2162 message->AddInt32("scale", 0); 2163 item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-'); 2164 item->SetTarget(PoseView()); 2165 iconSizeMenu->AddItem(item); 2166 2167 message = new BMessage(kIconMode); 2168 message->AddInt32("scale", 1); 2169 item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+'); 2170 item->SetTarget(PoseView()); 2171 iconSizeMenu->AddItem(item); 2172 2173 // A sub menu where the super item can be invoked. 2174 menu->AddItem(iconSizeMenu); 2175 iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY); 2176 iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode)); 2177 iconSizeMenu->Superitem()->SetTarget(PoseView()); 2178 2179 item = new BMenuItem(B_TRANSLATE("Mini icon view"), 2180 new BMessage(kMiniIconMode), '2'); 2181 item->SetTarget(PoseView()); 2182 menu->AddItem(item); 2183 2184 item = new BMenuItem(B_TRANSLATE("List view"), 2185 new BMessage(kListMode), '3'); 2186 item->SetTarget(PoseView()); 2187 menu->AddItem(item); 2188 2189 menu->AddSeparatorItem(); 2190 2191 item = new BMenuItem(B_TRANSLATE("Resize to fit"), 2192 new BMessage(kResizeToFit), 'Y'); 2193 item->SetTarget(this); 2194 menu->AddItem(item); 2195 2196 fArrangeByMenu = new BMenu(B_TRANSLATE("Arrange by")); 2197 menu->AddItem(fArrangeByMenu); 2198 2199 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 2200 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); 2201 item->SetTarget(PoseView()); 2202 menu->AddItem(item); 2203 2204 item = new BMenuItem(B_TRANSLATE("Select all"), 2205 new BMessage(B_SELECT_ALL), 'A'); 2206 item->SetTarget(this); 2207 menu->AddItem(item); 2208 2209 item = new BMenuItem(B_TRANSLATE("Invert selection"), 2210 new BMessage(kInvertSelection), 'S'); 2211 item->SetTarget(PoseView()); 2212 menu->AddItem(item); 2213 2214 if (!IsTrash()) { 2215 item = new BMenuItem(B_TRANSLATE("Open parent"), 2216 new BMessage(kOpenParentDir), B_UP_ARROW); 2217 item->SetTarget(PoseView()); 2218 menu->AddItem(item); 2219 } 2220 2221 item = new BMenuItem(B_TRANSLATE("Close"), 2222 new BMessage(B_QUIT_REQUESTED), 'W'); 2223 item->SetTarget(this); 2224 menu->AddItem(item); 2225 2226 item = new BMenuItem(B_TRANSLATE("Close all in workspace"), 2227 new BMessage(kCloseAllInWorkspace), 'Q'); 2228 item->SetTarget(be_app); 2229 menu->AddItem(item); 2230 2231 menu->AddSeparatorItem(); 2232 2233 item = new BMenuItem(B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS), 2234 new BMessage(kShowSettingsWindow), ','); 2235 item->SetTarget(be_app); 2236 menu->AddItem(item); 2237 } 2238 2239 2240 void 2241 BContainerWindow::AddShortcuts() 2242 { 2243 // add equivalents of the menu shortcuts to the menuless desktop window 2244 ASSERT(!IsTrash()); 2245 ASSERT(!PoseView()->IsFilePanel()); 2246 ASSERT(!TargetModel()->IsQuery()); 2247 ASSERT(!TargetModel()->IsVirtualDirectory()); 2248 2249 AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY, 2250 new BMessage(kCutMoreSelectionToClipboard), this); 2251 AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY, 2252 new BMessage(kCopyMoreSelectionToClipboard), this); 2253 AddShortcut('F', B_COMMAND_KEY, 2254 new BMessage(kFindButton), PoseView()); 2255 AddShortcut('N', B_COMMAND_KEY, 2256 new BMessage(kNewFolder), PoseView()); 2257 AddShortcut('O', B_COMMAND_KEY, 2258 new BMessage(kOpenSelection), PoseView()); 2259 AddShortcut('I', B_COMMAND_KEY, 2260 new BMessage(kGetInfo), PoseView()); 2261 AddShortcut('E', B_COMMAND_KEY, 2262 new BMessage(kEditItem), PoseView()); 2263 AddShortcut('D', B_COMMAND_KEY, 2264 new BMessage(kDuplicateSelection), PoseView()); 2265 AddShortcut('T', B_COMMAND_KEY, 2266 new BMessage(kMoveToTrash), PoseView()); 2267 AddShortcut('K', B_COMMAND_KEY, 2268 new BMessage(kCleanup), PoseView()); 2269 AddShortcut('A', B_COMMAND_KEY, 2270 new BMessage(B_SELECT_ALL), PoseView()); 2271 AddShortcut('S', B_COMMAND_KEY, 2272 new BMessage(kInvertSelection), PoseView()); 2273 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, 2274 new BMessage(kShowSelectionWindow), PoseView()); 2275 AddShortcut('G', B_COMMAND_KEY, 2276 new BMessage(kEditQuery), PoseView()); 2277 // it is ok to add a global Edit query shortcut here, PoseView will 2278 // filter out cases where selected pose is not a query 2279 AddShortcut('U', B_COMMAND_KEY, 2280 new BMessage(kUnmountVolume), PoseView()); 2281 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 2282 new BMessage(kOpenParentDir), PoseView()); 2283 AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY, 2284 new BMessage(kOpenSelectionWith), PoseView()); 2285 2286 BMessage* decreaseSize = new BMessage(kIconMode); 2287 decreaseSize->AddInt32("scale", 0); 2288 AddShortcut('-', B_COMMAND_KEY, decreaseSize, PoseView()); 2289 2290 BMessage* increaseSize = new BMessage(kIconMode); 2291 increaseSize->AddInt32("scale", 1); 2292 AddShortcut('+', B_COMMAND_KEY, increaseSize, PoseView()); 2293 } 2294 2295 2296 void 2297 BContainerWindow::MenusBeginning() 2298 { 2299 if (fMenuBar == NULL) 2300 return; 2301 2302 if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) { 2303 // don't commit active pose if only a keyboard shortcut is 2304 // invoked - this would prevent Cut/Copy/Paste from working 2305 PoseView()->CommitActivePose(); 2306 } 2307 2308 // File menu 2309 int32 selectCount = PoseView()->SelectionList()->CountItems(); 2310 2311 SetupOpenWithMenu(fFileMenu); 2312 SetupMoveCopyMenus(selectCount 2313 ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() 2314 : NULL, fFileMenu); 2315 2316 if (TargetModel()->IsRoot()) { 2317 BVolume boot; 2318 BVolumeRoster().GetBootVolume(&boot); 2319 2320 bool ejectableVolumeSelected = false; 2321 for (int32 index = 0; index < selectCount; index++) { 2322 Model* model 2323 = PoseView()->SelectionList()->ItemAt(index)->TargetModel(); 2324 if (model->IsVolume()) { 2325 BVolume volume; 2326 volume.SetTo(model->NodeRef()->device); 2327 if (volume != boot) { 2328 ejectableVolumeSelected = true; 2329 break; 2330 } 2331 } 2332 } 2333 BMenuItem* item = fMenuBar->FindItem(kUnmountVolume); 2334 if (item != NULL) 2335 item->SetEnabled(ejectableVolumeSelected); 2336 } 2337 2338 UpdateMenu(fMenuBar, kMenuBarContext); 2339 2340 AddMimeTypesToMenu(fAttrMenu); 2341 2342 if (IsPrintersDir()) { 2343 EnableNamedMenuItem(fFileMenu, B_TRANSLATE("Make active printer"), 2344 selectCount == 1); 2345 } 2346 } 2347 2348 2349 void 2350 BContainerWindow::MenusEnded() 2351 { 2352 // when we're done we want to clear nav menus for next time 2353 DeleteSubmenu(fNavigationItem); 2354 DeleteSubmenu(fMoveToItem); 2355 DeleteSubmenu(fCopyToItem); 2356 DeleteSubmenu(fCreateLinkItem); 2357 DeleteSubmenu(fOpenWithItem); 2358 } 2359 2360 2361 void 2362 BContainerWindow::SetupNavigationMenu(const entry_ref* ref, BMenu* parent) 2363 { 2364 // start by removing nav item (and separator) from old menu 2365 if (fNavigationItem != NULL) { 2366 BMenu* menu = fNavigationItem->Menu(); 2367 if (menu != NULL) { 2368 menu->RemoveItem(fNavigationItem); 2369 BMenuItem* item = menu->RemoveItem((int32)0); 2370 ASSERT(item != fNavigationItem); 2371 delete item; 2372 } 2373 } 2374 2375 // if we weren't passed a ref then we're navigating this window 2376 if (ref == NULL) 2377 ref = TargetModel()->EntryRef(); 2378 2379 BEntry entry; 2380 if (entry.SetTo(ref) != B_OK) 2381 return; 2382 2383 // only navigate directories and queries (check for symlink here) 2384 Model model(&entry); 2385 entry_ref resolvedRef; 2386 2387 if (model.InitCheck() != B_OK 2388 || (!model.IsContainer() && !model.IsSymLink())) { 2389 return; 2390 } 2391 2392 if (model.IsSymLink()) { 2393 if (entry.SetTo(model.EntryRef(), true) != B_OK) 2394 return; 2395 2396 Model resolvedModel(&entry); 2397 if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer()) 2398 return; 2399 2400 entry.GetRef(&resolvedRef); 2401 ref = &resolvedRef; 2402 } 2403 2404 if (fNavigationItem == NULL) { 2405 fNavigationItem = new ModelMenuItem(&model, 2406 new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this)); 2407 } 2408 2409 // setup a navigation menu item which will dynamically load items 2410 // as menu items are traversed 2411 BNavMenu* navMenu = dynamic_cast<BNavMenu*>(fNavigationItem->Submenu()); 2412 navMenu->SetNavDir(ref); 2413 fNavigationItem->SetLabel(model.Name()); 2414 fNavigationItem->SetEntry(&entry); 2415 2416 parent->AddItem(fNavigationItem, 0); 2417 parent->AddItem(new BSeparatorItem(), 1); 2418 2419 BMessage* message = new BMessage(B_REFS_RECEIVED); 2420 message->AddRef("refs", ref); 2421 fNavigationItem->SetMessage(message); 2422 fNavigationItem->SetTarget(be_app); 2423 2424 if (!Dragging()) 2425 parent->SetTrackingHook(NULL, NULL); 2426 } 2427 2428 2429 void 2430 BContainerWindow::SetUpEditQueryItem(BMenu* menu) 2431 { 2432 ASSERT(menu); 2433 // File menu 2434 int32 selectCount = PoseView()->CountSelected(); 2435 2436 // add Edit query if appropriate 2437 bool queryInSelection = false; 2438 if (selectCount && selectCount < 100) { 2439 // only do this for a limited number of selected poses 2440 2441 // if any queries selected, add an edit query menu item 2442 for (int32 index = 0; index < selectCount; index++) { 2443 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 2444 Model model(pose->TargetModel()->EntryRef(), true); 2445 if (model.InitCheck() != B_OK) 2446 continue; 2447 2448 if (model.IsQuery() || model.IsQueryTemplate()) { 2449 queryInSelection = true; 2450 break; 2451 } 2452 } 2453 } 2454 2455 bool poseViewIsQuery = TargetModel()->IsQuery(); 2456 // if the view is a query pose view, add edit query menu item 2457 2458 BMenuItem* item = menu->FindItem(kEditQuery); 2459 if (!poseViewIsQuery && !queryInSelection && item != NULL) 2460 item->Menu()->RemoveItem(item); 2461 else if ((poseViewIsQuery || queryInSelection) && item == NULL) { 2462 // add edit query item after Open 2463 item = menu->FindItem(kOpenSelection); 2464 if (item) { 2465 int32 itemIndex = item->Menu()->IndexOf(item); 2466 BMenuItem* query = new BMenuItem(B_TRANSLATE("Edit query"), 2467 new BMessage(kEditQuery), 'G'); 2468 item->Menu()->AddItem(query, itemIndex + 1); 2469 query->SetTarget(PoseView()); 2470 } 2471 } 2472 } 2473 2474 2475 void 2476 BContainerWindow::SetupOpenWithMenu(BMenu* parent) 2477 { 2478 // start by removing nav item (and separator) from old menu 2479 if (fOpenWithItem) { 2480 BMenu* menu = fOpenWithItem->Menu(); 2481 if (menu != NULL) 2482 menu->RemoveItem(fOpenWithItem); 2483 2484 delete fOpenWithItem; 2485 fOpenWithItem = 0; 2486 } 2487 2488 int32 selectCount = PoseView()->CountSelected(); 2489 if (selectCount <= 0) { 2490 // no selection, nothing to open 2491 return; 2492 } 2493 2494 if (TargetModel()->IsRoot()) { 2495 // don't add ourselves if we are root 2496 return; 2497 } 2498 2499 // ToDo: 2500 // check if only item in selection list is the root 2501 // and do not add if true 2502 2503 // add after "Open" 2504 BMenuItem* item = parent->FindItem(kOpenSelection); 2505 2506 // build a list of all refs to open 2507 BMessage message(B_REFS_RECEIVED); 2508 for (int32 index = 0; index < selectCount; index++) { 2509 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 2510 message.AddRef("refs", pose->TargetModel()->EntryRef()); 2511 } 2512 2513 // add Tracker token so that refs received recipients can script us 2514 message.AddMessenger("TrackerViewToken", BMessenger(PoseView())); 2515 2516 int32 index = item->Menu()->IndexOf(item); 2517 fOpenWithItem = new BMenuItem( 2518 new OpenWithMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS), 2519 &message, this, be_app), new BMessage(kOpenSelectionWith)); 2520 fOpenWithItem->SetTarget(PoseView()); 2521 fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY); 2522 2523 item->Menu()->AddItem(fOpenWithItem, index + 1); 2524 } 2525 2526 2527 void 2528 BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu* navMenu, uint32 what, 2529 const entry_ref* ref, bool addLocalOnly) 2530 { 2531 BVolume volume; 2532 BVolumeRoster volumeRoster; 2533 BDirectory directory; 2534 BEntry entry; 2535 BPath path; 2536 Model model; 2537 dev_t device = ref->device; 2538 2539 int32 volumeCount = 0; 2540 2541 navMenu->RemoveItems(0, navMenu->CountItems(), true); 2542 2543 // count persistent writable volumes 2544 volumeRoster.Rewind(); 2545 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2546 if (!volume.IsReadOnly() && volume.IsPersistent()) 2547 volumeCount++; 2548 } 2549 2550 // add the current folder 2551 if (entry.SetTo(ref) == B_OK 2552 && entry.GetParent(&entry) == B_OK 2553 && model.SetTo(&entry) == B_OK) { 2554 BNavMenu* menu = new BNavMenu(B_TRANSLATE("Current folder"), what, 2555 this); 2556 menu->SetNavDir(model.EntryRef()); 2557 menu->SetShowParent(true); 2558 2559 BMenuItem* item = new SpecialModelMenuItem(&model, menu); 2560 item->SetMessage(new BMessage((uint32)what)); 2561 2562 navMenu->AddItem(item); 2563 } 2564 2565 // add the recent folder menu 2566 // the "Tracker" settings directory is only used to get its icon 2567 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 2568 path.Append("Tracker"); 2569 if (entry.SetTo(path.Path()) == B_OK 2570 && model.SetTo(&entry) == B_OK) { 2571 BMenu* menu = new RecentsMenu(B_TRANSLATE("Recent folders"), 2572 kRecentFolders, what, this); 2573 2574 BMenuItem* item = new SpecialModelMenuItem(&model, menu); 2575 item->SetMessage(new BMessage((uint32)what)); 2576 2577 navMenu->AddItem(item); 2578 } 2579 } 2580 2581 // add Desktop 2582 FSGetBootDeskDir(&directory); 2583 if (directory.InitCheck() == B_OK && directory.GetEntry(&entry) == B_OK 2584 && model.SetTo(&entry) == B_OK) { 2585 navMenu->AddNavDir(&model, what, this, true); 2586 // ask NavMenu to populate submenu for us 2587 } 2588 2589 // add the home dir 2590 if (find_directory(B_USER_DIRECTORY, &path) == B_OK 2591 && entry.SetTo(path.Path()) == B_OK && model.SetTo(&entry) == B_OK) { 2592 navMenu->AddNavDir(&model, what, this, true); 2593 } 2594 2595 navMenu->AddSeparatorItem(); 2596 2597 // either add all mounted volumes (for copy), or all the top-level 2598 // directories from the same device (for move) 2599 // ToDo: can be changed if cross-device moves are implemented 2600 2601 if (addLocalOnly || volumeCount < 2) { 2602 // add volume this item lives on 2603 if (volume.SetTo(device) == B_OK 2604 && volume.GetRootDirectory(&directory) == B_OK 2605 && directory.GetEntry(&entry) == B_OK 2606 && model.SetTo(&entry) == B_OK) { 2607 navMenu->AddNavDir(&model, what, this, false); 2608 // do not have submenu populated 2609 2610 navMenu->SetNavDir(model.EntryRef()); 2611 } 2612 } else { 2613 // add all persistent writable volumes 2614 volumeRoster.Rewind(); 2615 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2616 if (volume.IsReadOnly() || !volume.IsPersistent()) 2617 continue; 2618 2619 // add root dir 2620 if (volume.GetRootDirectory(&directory) == B_OK 2621 && directory.GetEntry(&entry) == B_OK 2622 && model.SetTo(&entry) == B_OK) { 2623 navMenu->AddNavDir(&model, what, this, true); 2624 // ask NavMenu to populate submenu for us 2625 } 2626 } 2627 } 2628 } 2629 2630 2631 void 2632 BContainerWindow::SetupMoveCopyMenus(const entry_ref* item_ref, BMenu* parent) 2633 { 2634 if (IsTrash() || InTrash() || IsPrintersDir() || fMoveToItem == NULL 2635 || fCopyToItem == NULL || fCreateLinkItem == NULL 2636 || TargetModel()->IsRoot()) { 2637 return; 2638 } 2639 2640 // re-parent items to this menu since they're shared 2641 BMenuItem* trash = parent->FindItem(kMoveToTrash); 2642 int32 index = trash != NULL ? parent->IndexOf(trash) + 2 : 0; 2643 2644 if (fMoveToItem->Menu() != parent) { 2645 if (fMoveToItem->Menu() != NULL) 2646 fMoveToItem->Menu()->RemoveItem(fMoveToItem); 2647 2648 parent->AddItem(fMoveToItem, index++); 2649 } 2650 2651 if (fCopyToItem->Menu() != parent) { 2652 if (fCopyToItem->Menu() != NULL) 2653 fCopyToItem->Menu()->RemoveItem(fCopyToItem); 2654 2655 parent->AddItem(fCopyToItem, index++); 2656 } 2657 2658 if (fCreateLinkItem->Menu() != parent) { 2659 if (fCreateLinkItem->Menu() != NULL) 2660 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); 2661 2662 parent->AddItem(fCreateLinkItem, index); 2663 } 2664 2665 // Set the "Create Link" item label here so it 2666 // appears correctly when menus are disabled, too. 2667 if ((modifiers() & B_SHIFT_KEY) != 0) 2668 fCreateLinkItem->SetLabel(B_TRANSLATE("Create relative link")); 2669 else 2670 fCreateLinkItem->SetLabel(B_TRANSLATE("Create link")); 2671 2672 // only enable once the menus are built 2673 fMoveToItem->SetEnabled(false); 2674 fCopyToItem->SetEnabled(false); 2675 fCreateLinkItem->SetEnabled(false); 2676 2677 // get ref for item which is selected 2678 BEntry entry; 2679 if (entry.SetTo(item_ref) != B_OK) 2680 return; 2681 2682 Model tempModel(&entry); 2683 if (tempModel.InitCheck() != B_OK) 2684 return; 2685 2686 if (tempModel.IsRoot() || tempModel.IsVolume()) 2687 return; 2688 2689 // configure "Move to" menu item 2690 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fMoveToItem->Submenu()), 2691 kMoveSelectionTo, item_ref, true); 2692 2693 // configure "Copy to" menu item 2694 // add all mounted volumes (except the one this item lives on) 2695 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fCopyToItem->Submenu()), 2696 kCopySelectionTo, item_ref, false); 2697 2698 // Set "Create Link" menu item message and 2699 // add all mounted volumes (except the one this item lives on) 2700 if ((modifiers() & B_SHIFT_KEY) != 0) { 2701 fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink)); 2702 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*> 2703 (fCreateLinkItem->Submenu()), 2704 kCreateRelativeLink, item_ref, false); 2705 } else { 2706 fCreateLinkItem->SetMessage(new BMessage(kCreateLink)); 2707 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*> 2708 (fCreateLinkItem->Submenu()), 2709 kCreateLink, item_ref, false); 2710 } 2711 2712 fMoveToItem->SetEnabled(PoseView()->CountSelected() > 0 2713 && !PoseView()->TargetVolumeIsReadOnly()); 2714 fCopyToItem->SetEnabled(PoseView()->CountSelected() > 0); 2715 fCreateLinkItem->SetEnabled(PoseView()->CountSelected() > 0); 2716 2717 // Set the "Identify" item label 2718 BMenuItem* identifyItem = parent->FindItem(kIdentifyEntry); 2719 if (identifyItem != NULL) { 2720 if ((modifiers() & B_SHIFT_KEY) != 0) { 2721 identifyItem->SetLabel(B_TRANSLATE("Force identify")); 2722 identifyItem->Message()->ReplaceBool("force", true); 2723 } else { 2724 identifyItem->SetLabel(B_TRANSLATE("Identify")); 2725 identifyItem->Message()->ReplaceBool("force", false); 2726 } 2727 } 2728 } 2729 2730 2731 uint32 2732 BContainerWindow::ShowDropContextMenu(BPoint where, BPoseView* source) 2733 { 2734 BPoint global(where); 2735 2736 PoseView()->ConvertToScreen(&global); 2737 PoseView()->CommitActivePose(); 2738 2739 // Change the "Create Link" item - allow user to 2740 // create relative links with the Shift key down. 2741 BMenuItem* item = fDropContextMenu->FindItem(kCreateLink); 2742 if (item == NULL) 2743 item = fDropContextMenu->FindItem(kCreateRelativeLink); 2744 2745 if (item != NULL && (modifiers() & B_SHIFT_KEY) != 0) { 2746 item->SetLabel(B_TRANSLATE("Create relative link here")); 2747 item->SetMessage(new BMessage(kCreateRelativeLink)); 2748 } else if (item != NULL) { 2749 item->SetLabel(B_TRANSLATE("Create link here")); 2750 item->SetMessage(new BMessage(kCreateLink)); 2751 } 2752 2753 int32 itemCount = fDropContextMenu->CountItems(); 2754 for(int32 i = 0; i < itemCount - 2; i++) { 2755 // separator item and Cancel item are skipped 2756 item = fDropContextMenu->ItemAt(i); 2757 if (item == NULL) 2758 break; 2759 2760 if (item->Command() == kMoveSelectionTo && source != NULL) { 2761 item->SetEnabled(!source->TargetVolumeIsReadOnly() 2762 && !PoseView()->TargetVolumeIsReadOnly()); 2763 } else 2764 item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly()); 2765 } 2766 2767 item = fDropContextMenu->Go(global, true, true); 2768 if (item != NULL) 2769 return item->Command(); 2770 2771 return 0; 2772 } 2773 2774 2775 void 2776 BContainerWindow::ShowContextMenu(BPoint where, const entry_ref* ref) 2777 { 2778 ASSERT(IsLocked()); 2779 BPoint global(where); 2780 PoseView()->ConvertToScreen(&global); 2781 PoseView()->CommitActivePose(); 2782 2783 if (ref != NULL) { 2784 // clicked on a pose, show file or volume context menu 2785 Model model(ref); 2786 2787 if (model.IsTrash()) { 2788 if (fTrashContextMenu->Window() || Dragging()) 2789 return; 2790 2791 DeleteSubmenu(fNavigationItem); 2792 2793 // selected item was trash, show the trash context menu instead 2794 2795 EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash, 2796 static_cast<TTracker*>(be_app)->TrashFull()); 2797 2798 SetupNavigationMenu(ref, fTrashContextMenu); 2799 2800 fContextMenu = fTrashContextMenu; 2801 } else { 2802 bool showAsVolume = false; 2803 bool isFilePanel = PoseView()->IsFilePanel(); 2804 2805 if (Dragging()) { 2806 fContextMenu = NULL; 2807 2808 BEntry entry; 2809 model.GetEntry(&entry); 2810 2811 // only show for directories (directory, volume, root) 2812 // 2813 // don't show a popup for the trash or printers 2814 // trash is handled in DeskWindow 2815 // 2816 // since this menu is opened asynchronously 2817 // we need to make sure we don't open it more 2818 // than once, the IsShowing flag is set in 2819 // SlowContextPopup::AttachedToWindow and 2820 // reset in DetachedFromWindow 2821 // see the notes in SlowContextPopup::AttachedToWindow 2822 2823 if (!FSIsPrintersDir(&entry) 2824 && !fDragContextMenu->IsShowing()) { 2825 //printf("ShowContextMenu - target is %s %i\n", 2826 // ref->name, IsShowing(ref)); 2827 fDragContextMenu->ClearMenu(); 2828 2829 // in case the ref is a symlink, resolve it 2830 // only pop open for directories 2831 BEntry resolvedEntry(ref, true); 2832 if (!resolvedEntry.IsDirectory()) 2833 return; 2834 2835 entry_ref resolvedRef; 2836 resolvedEntry.GetRef(&resolvedRef); 2837 2838 // use the resolved ref for the menu 2839 fDragContextMenu->SetNavDir(&resolvedRef); 2840 fDragContextMenu->SetTypesList(fCachedTypesList); 2841 fDragContextMenu->SetTarget(BMessenger(this)); 2842 BPoseView* poseView = PoseView(); 2843 if (poseView != NULL) { 2844 BMessenger target(poseView); 2845 fDragContextMenu->InitTrackingHook( 2846 &BPoseView::MenuTrackingHook, &target, 2847 fDragMessage); 2848 } 2849 2850 // this is now asynchronous so that we don't 2851 // deadlock in Window::Quit, 2852 fDragContextMenu->Go(global); 2853 } 2854 2855 return; 2856 } else if (TargetModel()->IsRoot() || model.IsVolume()) { 2857 fContextMenu = fVolumeContextMenu; 2858 showAsVolume = true; 2859 } else 2860 fContextMenu = fFileContextMenu; 2861 2862 if (fContextMenu == NULL) 2863 return; 2864 2865 // clean up items from last context menu 2866 MenusEnded(); 2867 2868 if (fContextMenu == fFileContextMenu) { 2869 // Add all mounted volumes (except the one this item lives on.) 2870 BNavMenu* navMenu = dynamic_cast<BNavMenu*>( 2871 fCreateLinkItem->Submenu()); 2872 PopulateMoveCopyNavMenu(navMenu, 2873 fCreateLinkItem->Message()->what, ref, false); 2874 } else if (showAsVolume) { 2875 // non-volume enable/disable copy, move, identify 2876 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, 2877 false); 2878 EnableNamedMenuItem(fContextMenu, kMoveToTrash, false); 2879 EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false); 2880 2881 // volume model, enable/disable the Unmount item 2882 bool ejectableVolumeSelected = false; 2883 2884 BVolume boot; 2885 BVolumeRoster().GetBootVolume(&boot); 2886 BVolume volume; 2887 volume.SetTo(model.NodeRef()->device); 2888 if (volume != boot) 2889 ejectableVolumeSelected = true; 2890 2891 EnableNamedMenuItem(fContextMenu, 2892 B_TRANSLATE("Unmount"), ejectableVolumeSelected); 2893 } 2894 2895 SetupNavigationMenu(ref, fContextMenu); 2896 if (!showAsVolume && !isFilePanel) { 2897 SetupMoveCopyMenus(ref, fContextMenu); 2898 SetupOpenWithMenu(fContextMenu); 2899 } 2900 2901 UpdateMenu(fContextMenu, kPosePopUpContext); 2902 } 2903 } else if (fWindowContextMenu != NULL) { 2904 // Repopulate desktop menu if IsDesktop 2905 if (fIsDesktop) 2906 RepopulateMenus(); 2907 2908 MenusEnded(); 2909 2910 // clicked on a window, show window context menu 2911 2912 SetupNavigationMenu(ref, fWindowContextMenu); 2913 UpdateMenu(fWindowContextMenu, kWindowPopUpContext); 2914 2915 fContextMenu = fWindowContextMenu; 2916 } 2917 2918 // context menu invalid or popup window is already open 2919 if (fContextMenu == NULL || fContextMenu->Window() != NULL) 2920 return; 2921 2922 fContextMenu->Go(global, true, true, true); 2923 fContextMenu = NULL; 2924 } 2925 2926 2927 void 2928 BContainerWindow::AddFileContextMenus(BMenu* menu) 2929 { 2930 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 2931 new BMessage(kOpenSelection), 'O')); 2932 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 2933 new BMessage(kGetInfo), 'I')); 2934 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 2935 new BMessage(kEditItem), 'E')); 2936 2937 if (!IsTrash() && !InTrash() && !IsPrintersDir()) { 2938 menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"), 2939 new BMessage(kDuplicateSelection), 'D')); 2940 } 2941 2942 if (!IsTrash() && !InTrash()) { 2943 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() 2944 ? B_TRANSLATE("Delete") : B_TRANSLATE("Move to Trash"), 2945 new BMessage(kMoveToTrash), 'T')); 2946 if (!IsPrintersDir()) { 2947 // add separator for copy to/move to items (navigation items) 2948 menu->AddSeparatorItem(); 2949 } 2950 } else { 2951 menu->AddItem(new BMenuItem(B_TRANSLATE("Delete"), 2952 new BMessage(kDelete), 0)); 2953 menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"), 2954 new BMessage(kRestoreFromTrash), 0)); 2955 } 2956 2957 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 2958 menu->AddSeparatorItem(); 2959 BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"), 2960 new BMessage(B_CUT), 'X'); 2961 menu->AddItem(cutItem); 2962 BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"), 2963 new BMessage(B_COPY), 'C'); 2964 menu->AddItem(copyItem); 2965 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 2966 new BMessage(B_PASTE), 'V'); 2967 menu->AddItem(pasteItem); 2968 #endif 2969 menu->AddSeparatorItem(); 2970 2971 BMessage* message = new BMessage(kIdentifyEntry); 2972 message->AddBool("force", false); 2973 menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), message)); 2974 2975 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons")); 2976 addOnMenuItem->SetFont(be_plain_font); 2977 menu->AddItem(addOnMenuItem); 2978 2979 // set targets as needed 2980 menu->SetTargetForItems(PoseView()); 2981 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 2982 cutItem->SetTarget(this); 2983 copyItem->SetTarget(this); 2984 pasteItem->SetTarget(this); 2985 #endif 2986 } 2987 2988 2989 void 2990 BContainerWindow::AddVolumeContextMenus(BMenu* menu) 2991 { 2992 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 2993 new BMessage(kOpenSelection), 'O')); 2994 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 2995 new BMessage(kGetInfo), 'I')); 2996 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 2997 new BMessage(kEditItem), 'E')); 2998 2999 menu->AddSeparatorItem(); 3000 menu->AddItem(new MountMenu(B_TRANSLATE("Mount"))); 3001 3002 BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"), 3003 new BMessage(kUnmountVolume), 'U'); 3004 item->SetEnabled(false); 3005 menu->AddItem(item); 3006 menu->AddSeparatorItem(); 3007 3008 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 3009 menu->AddItem(new BMenuItem(B_TRANSLATE("Paste"), 3010 new BMessage(B_PASTE), 'V')); 3011 menu->AddSeparatorItem(); 3012 #endif 3013 3014 menu->AddItem(new BMenu(B_TRANSLATE("Add-ons"))); 3015 3016 menu->SetTargetForItems(PoseView()); 3017 } 3018 3019 3020 void 3021 BContainerWindow::AddWindowContextMenus(BMenu* menu) 3022 { 3023 // create context sensitive menu for empty area of window 3024 // since we check view mode before display, this should be a radio 3025 // mode menu 3026 3027 Model* targetModel = TargetModel(); 3028 ASSERT(targetModel != NULL); 3029 3030 bool needSeparator = true; 3031 if (IsTrash()) { 3032 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"), 3033 new BMessage(kEmptyTrash))); 3034 } else if (IsPrintersDir()) { 3035 menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS), 3036 new BMessage(kAddPrinter), 'N')); 3037 } else if (InTrash() || targetModel->IsRoot()) { 3038 needSeparator = false; 3039 } else { 3040 if (!PoseView()->IsFilePanel()) { 3041 TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(), 3042 B_TRANSLATE("New")); 3043 menu->AddItem(templatesMenu); 3044 templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly()); 3045 templatesMenu->SetTargetForItems(PoseView()); 3046 templatesMenu->SetFont(be_plain_font); 3047 } else { 3048 BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"), 3049 new BMessage(kNewFolder), 'N'); 3050 item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly()); 3051 menu->AddItem(item); 3052 } 3053 } 3054 3055 if (needSeparator) 3056 menu->AddSeparatorItem(); 3057 3058 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 3059 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 3060 new BMessage(B_PASTE), 'V'); 3061 pasteItem->SetEnabled(FSClipboardHasRefs() 3062 && !PoseView()->TargetVolumeIsReadOnly()); 3063 menu->AddItem(pasteItem); 3064 menu->AddSeparatorItem(); 3065 #endif 3066 3067 BMenu* arrangeBy = new BMenu(B_TRANSLATE("Arrange by")); 3068 PopulateArrangeByMenu(arrangeBy); 3069 menu->AddItem(arrangeBy); 3070 3071 menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 3072 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); 3073 menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"), 3074 new BMessage(B_SELECT_ALL), 'A')); 3075 if (!IsTrash()) { 3076 menu->AddItem(new BMenuItem(B_TRANSLATE("Open parent"), 3077 new BMessage(kOpenParentDir), B_UP_ARROW)); 3078 } 3079 3080 if (targetModel->IsRoot()) { 3081 menu->AddSeparatorItem(); 3082 menu->AddItem(new MountMenu(B_TRANSLATE("Mount"))); 3083 } 3084 3085 menu->AddSeparatorItem(); 3086 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons")); 3087 addOnMenuItem->SetFont(be_plain_font); 3088 menu->AddItem(addOnMenuItem); 3089 3090 #if DEBUG 3091 menu->AddSeparatorItem(); 3092 BMenuItem* testing = new BMenuItem("Test icon cache", 3093 new BMessage(kTestIconCache)); 3094 menu->AddItem(testing); 3095 #endif 3096 3097 // target items as needed 3098 menu->SetTargetForItems(PoseView()); 3099 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 3100 pasteItem->SetTarget(this); 3101 #endif 3102 } 3103 3104 3105 void 3106 BContainerWindow::AddDropContextMenus(BMenu* menu) 3107 { 3108 menu->AddItem(new BMenuItem(B_TRANSLATE("Move here"), 3109 new BMessage(kMoveSelectionTo))); 3110 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy here"), 3111 new BMessage(kCopySelectionTo))); 3112 menu->AddItem(new BMenuItem(B_TRANSLATE("Create link here"), 3113 new BMessage(kCreateLink))); 3114 menu->AddSeparatorItem(); 3115 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 3116 new BMessage(kCancelButton))); 3117 } 3118 3119 3120 void 3121 BContainerWindow::AddTrashContextMenus(BMenu* menu) 3122 { 3123 // setup special trash context menu 3124 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"), 3125 new BMessage(kEmptyTrash))); 3126 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 3127 new BMessage(kOpenSelection), 'O')); 3128 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 3129 new BMessage(kGetInfo), 'I')); 3130 menu->SetTargetForItems(PoseView()); 3131 } 3132 3133 3134 void 3135 BContainerWindow::EachAddon(bool (*eachAddon)(const Model*, const char*, 3136 uint32 shortcut, uint32 modifiers, bool primary, void* context, 3137 BContainerWindow* window, BMenu* menu), 3138 void* passThru, BStringList& mimeTypes, BMenu* menu) 3139 { 3140 AutoLock<LockingList<AddonShortcut> > lock(fAddonsList); 3141 if (lock.IsLocked()) { 3142 for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) { 3143 struct AddonShortcut* item = fAddonsList->ItemAt(i); 3144 bool primary = false; 3145 3146 if (mimeTypes.CountStrings() > 0) { 3147 BFile file(item->model->EntryRef(), B_READ_ONLY); 3148 if (file.InitCheck() == B_OK) { 3149 BAppFileInfo info(&file); 3150 if (info.InitCheck() == B_OK) { 3151 bool secondary = true; 3152 3153 // does this add-on has types set at all? 3154 BMessage message; 3155 if (info.GetSupportedTypes(&message) == B_OK) { 3156 type_code typeCode; 3157 int32 count; 3158 if (message.GetInfo("types", &typeCode, 3159 &count) == B_OK) { 3160 secondary = false; 3161 } 3162 } 3163 3164 // check all supported types if it has some set 3165 if (!secondary) { 3166 for (int32 i = mimeTypes.CountStrings(); 3167 !primary && i-- > 0;) { 3168 BString type = mimeTypes.StringAt(i); 3169 if (info.IsSupportedType(type.String())) { 3170 BMimeType mimeType(type.String()); 3171 if (info.Supports(&mimeType)) 3172 primary = true; 3173 else 3174 secondary = true; 3175 } 3176 } 3177 } 3178 3179 if (!secondary && !primary) 3180 continue; 3181 } 3182 } 3183 } 3184 ((eachAddon)(item->model, item->model->Name(), item->key, 3185 item->modifiers, primary, passThru, this, menu)); 3186 } 3187 } 3188 } 3189 3190 3191 void 3192 BContainerWindow::BuildMimeTypeList(BStringList& mimeTypes) 3193 { 3194 int32 selectCount = PoseView()->CountSelected(); 3195 if (selectCount <= 0) { 3196 // just add the type of the current directory 3197 AddMimeTypeString(mimeTypes, TargetModel()); 3198 } else { 3199 _UpdateSelectionMIMEInfo(); 3200 for (int32 index = 0; index < selectCount; index++) { 3201 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 3202 AddMimeTypeString(mimeTypes, pose->TargetModel()); 3203 // If it's a symlink, resolves it and add the Target's MimeType 3204 if (pose->TargetModel()->IsSymLink()) { 3205 Model* resolved = new Model( 3206 pose->TargetModel()->EntryRef(), true, true); 3207 if (resolved->InitCheck() == B_OK) 3208 AddMimeTypeString(mimeTypes, resolved); 3209 3210 delete resolved; 3211 } 3212 } 3213 } 3214 } 3215 3216 3217 void 3218 BContainerWindow::BuildAddOnMenu(BMenu* parentMenu) 3219 { 3220 BMenuItem* item = parentMenu->FindItem(B_TRANSLATE("Add-ons")); 3221 if (parentMenu->IndexOf(item) == 0) { 3222 // the folder of the context menu seems to be named "Add-Ons" 3223 // so we just take the last menu item, which is correct if not 3224 // build with debug option 3225 item = parentMenu->ItemAt(parentMenu->CountItems() - 1); 3226 } 3227 if (item == NULL) 3228 return; 3229 3230 BFont font; 3231 parentMenu->GetFont(&font); 3232 3233 BMenu* menu = item->Submenu(); 3234 if (menu == NULL) 3235 return; 3236 3237 menu->SetFont(&font); 3238 3239 // found the addons menu, empty it first 3240 for (;;) { 3241 item = menu->RemoveItem((int32)0); 3242 if (!item) 3243 break; 3244 delete item; 3245 } 3246 3247 BObjectList<BMenuItem> primaryList; 3248 BObjectList<BMenuItem> secondaryList; 3249 BStringList mimeTypes(10); 3250 BuildMimeTypeList(mimeTypes); 3251 3252 AddOneAddonParams params; 3253 params.primaryList = &primaryList; 3254 params.secondaryList = &secondaryList; 3255 3256 // build a list of the MIME types of the selected items 3257 3258 EachAddon(AddOneAddon, ¶ms, mimeTypes, parentMenu); 3259 3260 primaryList.SortItems(CompareLabels); 3261 secondaryList.SortItems(CompareLabels); 3262 3263 int32 count = primaryList.CountItems(); 3264 for (int32 index = 0; index < count; index++) 3265 menu->AddItem(primaryList.ItemAt(index)); 3266 3267 if (count > 0) 3268 menu->AddSeparatorItem(); 3269 3270 count = secondaryList.CountItems(); 3271 for (int32 index = 0; index < count; index++) 3272 menu->AddItem(secondaryList.ItemAt(index)); 3273 3274 menu->SetTargetForItems(this); 3275 } 3276 3277 3278 void 3279 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context) 3280 { 3281 const int32 selectCount = PoseView()->CountSelected(); 3282 const int32 poseCount = PoseView()->CountItems(); 3283 3284 if (context == kMenuBarContext) { 3285 EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0); 3286 EnableNamedMenuItem(menu, kGetInfo, selectCount > 0); 3287 EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0); 3288 EnableNamedMenuItem(menu, kRestoreFromTrash, 3289 !PoseView()->TargetVolumeIsReadOnly()); 3290 EnableNamedMenuItem(menu, kDelete, selectCount > 0 3291 && !PoseView()->SelectedVolumeIsReadOnly()); 3292 } 3293 3294 if (context == kMenuBarContext || context == kPosePopUpContext) { 3295 SetUpEditQueryItem(menu); 3296 3297 Model* selected = selectCount <= 0 ? NULL 3298 : PoseView()->SelectionList()->FirstItem()->TargetModel(); 3299 EnableNamedMenuItem(menu, kEditItem, !PoseView()->ActivePose() 3300 && selectCount == 1 && selected != NULL && !selected->IsDesktop() 3301 && !selected->IsRoot() && !selected->IsTrash()); 3302 3303 EnableNamedMenuItem(menu, kMoveToTrash, selectCount > 0 3304 && !PoseView()->SelectedVolumeIsReadOnly()); 3305 EnableNamedMenuItem(menu, kDuplicateSelection, selectCount > 0 3306 && !PoseView()->SelectedVolumeIsReadOnly()); 3307 3308 SetCutItem(menu); 3309 SetCopyItem(menu); 3310 SetPasteItem(menu); 3311 } 3312 3313 if (context == kMenuBarContext || context == kWindowPopUpContext) { 3314 uint32 viewMode = PoseView()->ViewMode(); 3315 3316 BMenu* iconSizeMenu = NULL; 3317 if (BMenuItem* item = menu->FindItem(kIconMode)) 3318 iconSizeMenu = item->Submenu(); 3319 3320 if (iconSizeMenu != NULL) { 3321 if (viewMode == kIconMode) { 3322 int32 iconSize = PoseView()->UnscaledIconSizeInt(); 3323 BMenuItem* item = iconSizeMenu->ItemAt(0); 3324 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; 3325 i++) { 3326 BMessage* message = item->Message(); 3327 if (message == NULL) { 3328 item->SetMarked(false); 3329 continue; 3330 } 3331 int32 size; 3332 if (message->FindInt32("size", &size) != B_OK) 3333 size = -1; 3334 item->SetMarked(iconSize == size); 3335 } 3336 } else { 3337 BMenuItem* item; 3338 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++) 3339 item->SetMarked(false); 3340 } 3341 } 3342 3343 MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode); 3344 MarkNamedMenuItem(menu, kListMode, viewMode == kListMode); 3345 MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode); 3346 3347 SetCloseItem(menu); 3348 SetArrangeMenu(menu); 3349 SetPasteItem(menu); 3350 3351 BEntry entry(TargetModel()->EntryRef()); 3352 BDirectory parent; 3353 entry_ref ref; 3354 BEntry root("/"); 3355 3356 bool parentIsRoot = (entry.GetParent(&parent) == B_OK 3357 && parent.GetEntry(&entry) == B_OK 3358 && entry.GetRef(&ref) == B_OK 3359 && entry == root); 3360 3361 EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop() 3362 && !TargetModel()->IsRoot() 3363 && (!parentIsRoot 3364 || TrackerSettings().SingleWindowBrowse() 3365 || TrackerSettings().ShowDisksIcon() 3366 || (modifiers() & B_CONTROL_KEY) != 0)); 3367 3368 EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0); 3369 EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0); 3370 3371 BMenuItem* item = menu->FindItem(B_TRANSLATE("New")); 3372 if (item != NULL) { 3373 TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>( 3374 item->Submenu()); 3375 if (templatesMenu != NULL) 3376 templatesMenu->UpdateMenuState(); 3377 } 3378 } 3379 3380 BuildAddOnMenu(menu); 3381 } 3382 3383 3384 BMessage* 3385 BContainerWindow::AddOnMessage(int32 what) 3386 { 3387 BMessage* message = new BMessage(what); 3388 3389 // add selected refs to message 3390 BObjectList<BPose>* selectionList = PoseView()->SelectionList(); 3391 3392 int32 index = 0; 3393 BPose* pose; 3394 while ((pose = selectionList->ItemAt(index++)) != NULL) 3395 message->AddRef("refs", pose->TargetModel()->EntryRef()); 3396 3397 message->AddRef("dir_ref", TargetModel()->EntryRef()); 3398 message->AddMessenger("TrackerViewToken", BMessenger(PoseView())); 3399 3400 return message; 3401 } 3402 3403 3404 void 3405 BContainerWindow::LoadAddOn(BMessage* message) 3406 { 3407 UpdateIfNeeded(); 3408 3409 entry_ref addonRef; 3410 status_t result = message->FindRef("refs", &addonRef); 3411 if (result != B_OK) { 3412 BString buffer(B_TRANSLATE("Error %error loading add-On %name.")); 3413 buffer.ReplaceFirst("%error", strerror(result)); 3414 buffer.ReplaceFirst("%name", addonRef.name); 3415 3416 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 3417 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3418 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3419 alert->Go(); 3420 return; 3421 } 3422 3423 // add selected refs to message 3424 BMessage* refs = AddOnMessage(B_REFS_RECEIVED); 3425 3426 LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs, 3427 addonRef, *TargetModel()->EntryRef()); 3428 } 3429 3430 3431 // #pragma mark - BContainerWindow private methods 3432 3433 3434 void 3435 BContainerWindow::_UpdateSelectionMIMEInfo() 3436 { 3437 BPose* pose; 3438 int32 index = 0; 3439 while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) { 3440 BString mimeType(pose->TargetModel()->MimeType()); 3441 if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3442 pose->TargetModel()->Mimeset(true); 3443 if (pose->TargetModel()->IsSymLink()) { 3444 Model* resolved = new Model(pose->TargetModel()->EntryRef(), 3445 true, true); 3446 if (resolved->InitCheck() == B_OK) { 3447 mimeType.SetTo(resolved->MimeType()); 3448 if (!mimeType.Length() 3449 || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3450 resolved->Mimeset(true); 3451 } 3452 } 3453 delete resolved; 3454 } 3455 } 3456 } 3457 } 3458 3459 3460 void 3461 BContainerWindow::_AddFolderIcon() 3462 { 3463 if (fMenuBar == NULL) { 3464 // We don't want to add the icon if there's no menubar 3465 return; 3466 } 3467 3468 float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1, 3469 iconSize = fMenuBar->Bounds().Height() - 2; 3470 if (iconSize < baseIconSize) 3471 iconSize = baseIconSize; 3472 3473 fDraggableIcon = new(std::nothrow) 3474 DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1)); 3475 if (fDraggableIcon != NULL) { 3476 fMenuContainer->GroupLayout()->AddView(fDraggableIcon); 3477 fMenuBar->SetBorders( 3478 BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER); 3479 } 3480 } 3481 3482 3483 void 3484 BContainerWindow::_PassMessageToAddOn(BMessage* message) 3485 { 3486 LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY, 3487 &RunAddOnMessageThread, new BMessage(*message), (void*)NULL); 3488 } 3489 3490 3491 BMenuItem* 3492 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3493 int32 type, float width, int32 align, bool editable, bool statField) 3494 { 3495 return NewAttributeMenuItem(label, name, type, NULL, width, align, 3496 editable, statField); 3497 } 3498 3499 3500 BMenuItem* 3501 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3502 int32 type, const char* displayAs, float width, int32 align, 3503 bool editable, bool statField) 3504 { 3505 BMessage* message = new BMessage(kAttributeItem); 3506 message->AddString("attr_name", name); 3507 message->AddInt32("attr_type", type); 3508 message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type)); 3509 message->AddFloat("attr_width", width); 3510 message->AddInt32("attr_align", align); 3511 if (displayAs != NULL) 3512 message->AddString("attr_display_as", displayAs); 3513 message->AddBool("attr_editable", editable); 3514 message->AddBool("attr_statfield", statField); 3515 3516 BMenuItem* menuItem = new BMenuItem(label, message); 3517 menuItem->SetTarget(PoseView()); 3518 3519 return menuItem; 3520 } 3521 3522 3523 void 3524 BContainerWindow::NewAttributeMenu(BMenu* menu) 3525 { 3526 ASSERT(PoseView()); 3527 3528 BMenuItem* item; 3529 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"), 3530 new BMessage(kCopyAttributes))); 3531 item->SetTarget(PoseView()); 3532 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"), 3533 new BMessage(kPasteAttributes))); 3534 item->SetTarget(PoseView()); 3535 menu->AddSeparatorItem(); 3536 3537 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"), 3538 kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3539 3540 if (gLocalizedNamePreferred) { 3541 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"), 3542 kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3543 } 3544 3545 menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize, 3546 B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true)); 3547 3548 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"), 3549 kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3550 3551 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"), 3552 kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3553 3554 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"), 3555 kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false)); 3556 3557 if (IsTrash() || InTrash()) { 3558 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"), 3559 kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false, 3560 false)); 3561 } else { 3562 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath, 3563 B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false)); 3564 } 3565 3566 #ifdef OWNER_GROUP_ATTRIBUTES 3567 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner, 3568 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3569 3570 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup, 3571 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3572 #endif 3573 3574 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"), 3575 kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true)); 3576 } 3577 3578 3579 void 3580 BContainerWindow::ShowAttributeMenu() 3581 { 3582 ASSERT(fAttrMenu); 3583 fMenuBar->AddItem(fAttrMenu); 3584 } 3585 3586 3587 void 3588 BContainerWindow::HideAttributeMenu() 3589 { 3590 ASSERT(fAttrMenu); 3591 fMenuBar->RemoveItem(fAttrMenu); 3592 } 3593 3594 3595 void 3596 BContainerWindow::MarkAttributeMenu() 3597 { 3598 MarkAttributeMenu(fAttrMenu); 3599 } 3600 3601 3602 void 3603 BContainerWindow::MarkAttributeMenu(BMenu* menu) 3604 { 3605 if (menu == NULL) 3606 return; 3607 3608 int32 count = menu->CountItems(); 3609 for (int32 index = 0; index < count; index++) { 3610 BMenuItem* item = menu->ItemAt(index); 3611 int32 attrHash; 3612 if (item->Message() != NULL) { 3613 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) 3614 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); 3615 else 3616 item->SetMarked(false); 3617 } 3618 3619 BMenu* submenu = item->Submenu(); 3620 if (submenu != NULL) { 3621 int32 count2 = submenu->CountItems(); 3622 for (int32 subindex = 0; subindex < count2; subindex++) { 3623 item = submenu->ItemAt(subindex); 3624 if (item->Message() != NULL) { 3625 if (item->Message()->FindInt32("attr_hash", &attrHash) 3626 == B_OK) { 3627 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) 3628 != 0); 3629 } else 3630 item->SetMarked(false); 3631 } 3632 } 3633 } 3634 } 3635 } 3636 3637 3638 void 3639 BContainerWindow::MarkArrangeByMenu(BMenu* menu) 3640 { 3641 if (menu == NULL) 3642 return; 3643 3644 int32 count = menu->CountItems(); 3645 for (int32 index = 0; index < count; index++) { 3646 BMenuItem* item = menu->ItemAt(index); 3647 if (item->Message() != NULL) { 3648 uint32 attrHash; 3649 if (item->Message()->FindInt32("attr_hash", 3650 (int32*)&attrHash) == B_OK) { 3651 item->SetMarked(PoseView()->PrimarySort() == attrHash); 3652 } else if (item->Command() == kArrangeReverseOrder) 3653 item->SetMarked(PoseView()->ReverseSort()); 3654 } 3655 } 3656 } 3657 3658 3659 void 3660 BContainerWindow::AddMimeTypesToMenu() 3661 { 3662 AddMimeTypesToMenu(fAttrMenu); 3663 } 3664 3665 3666 // Adds a menu for a specific MIME type if it doesn't exist already. 3667 // Returns the menu, if it existed or not. 3668 BMenu* 3669 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType, 3670 BMenu* menu, int32 start) 3671 { 3672 AutoLock<BLooper> _(menu->Looper()); 3673 3674 if (!mimeType.IsValid()) 3675 return NULL; 3676 3677 // Check if we already have an entry for this MIME type in the menu. 3678 for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) { 3679 BMessage* message = item->Message(); 3680 if (message == NULL) 3681 continue; 3682 3683 const char* type; 3684 if (message->FindString("mimetype", &type) == B_OK 3685 && !strcmp(mimeType.Type(), type)) { 3686 return item->Submenu(); 3687 } 3688 } 3689 3690 BMessage attrInfo; 3691 char description[B_MIME_TYPE_LENGTH]; 3692 const char* label = mimeType.Type(); 3693 3694 if (!mimeType.IsInstalled()) 3695 return NULL; 3696 3697 // only add things to menu which have "user-visible" data 3698 if (mimeType.GetAttrInfo(&attrInfo) != B_OK) 3699 return NULL; 3700 3701 if (mimeType.GetShortDescription(description) == B_OK && description[0]) 3702 label = description; 3703 3704 // go through each field in meta mime and add it to a menu 3705 BMenu* mimeMenu = NULL; 3706 if (isSuperType) { 3707 // If it is a supertype, we create the menu anyway as it may have 3708 // submenus later on. 3709 mimeMenu = new BMenu(label); 3710 BFont font; 3711 menu->GetFont(&font); 3712 mimeMenu->SetFont(&font); 3713 } 3714 3715 int32 index = -1; 3716 const char* publicName; 3717 while (attrInfo.FindString("attr:public_name", ++index, &publicName) 3718 == B_OK) { 3719 if (!attrInfo.FindBool("attr:viewable", index)) { 3720 // don't add if attribute not viewable 3721 continue; 3722 } 3723 3724 int32 type; 3725 int32 align; 3726 int32 width; 3727 bool editable; 3728 const char* attrName; 3729 if (attrInfo.FindString("attr:name", index, &attrName) != B_OK 3730 || attrInfo.FindInt32("attr:type", index, &type) != B_OK 3731 || attrInfo.FindBool("attr:editable", index, &editable) != B_OK 3732 || attrInfo.FindInt32("attr:width", index, &width) != B_OK 3733 || attrInfo.FindInt32("attr:alignment", index, &align) != B_OK) 3734 continue; 3735 3736 BString displayAs; 3737 attrInfo.FindString("attr:display_as", index, &displayAs); 3738 3739 if (mimeMenu == NULL) { 3740 // do a lazy allocation of the menu 3741 mimeMenu = new BMenu(label); 3742 BFont font; 3743 menu->GetFont(&font); 3744 mimeMenu->SetFont(&font); 3745 } 3746 mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type, 3747 displayAs.String(), width, align, editable, false)); 3748 } 3749 3750 if (mimeMenu == NULL) 3751 return NULL; 3752 3753 BMessage* message = new BMessage(kMIMETypeItem); 3754 message->AddString("mimetype", mimeType.Type()); 3755 menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type())); 3756 3757 return mimeMenu; 3758 } 3759 3760 3761 void 3762 BContainerWindow::AddMimeTypesToMenu(BMenu* menu) 3763 { 3764 if (menu == NULL) 3765 return; 3766 3767 // Remove old mime type menus 3768 int32 start = menu->CountItems(); 3769 while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) { 3770 delete menu->RemoveItem(start - 1); 3771 start--; 3772 } 3773 3774 // Add a separator item if there is none yet 3775 if (start > 0 3776 && dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL) 3777 menu->AddSeparatorItem(); 3778 3779 // Add MIME type in case we're a default query type window 3780 BPath path; 3781 if (TargetModel() != NULL) { 3782 TargetModel()->GetPath(&path); 3783 if (path.InitCheck() == B_OK 3784 && strstr(path.Path(), "/" kQueryTemplates "/") != NULL) { 3785 // demangle MIME type name 3786 BString name(TargetModel()->Name()); 3787 name.ReplaceFirst('_', '/'); 3788 3789 PoseView()->AddMimeType(name.String()); 3790 } 3791 } 3792 3793 // Add MIME type menus 3794 3795 int32 typeCount = PoseView()->CountMimeTypes(); 3796 3797 for (int32 index = 0; index < typeCount; index++) { 3798 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3799 if (mimeType.InitCheck() == B_OK) { 3800 BMimeType superType; 3801 mimeType.GetSupertype(&superType); 3802 if (superType.InitCheck() == B_OK) { 3803 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3804 if (superMenu != NULL) { 3805 // We have a supertype menu. 3806 AddMimeMenu(mimeType, false, superMenu, 0); 3807 } 3808 } 3809 } 3810 } 3811 3812 // remove empty super menus, promote sub-types if needed 3813 3814 for (int32 index = 0; index < typeCount; index++) { 3815 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3816 BMimeType superType; 3817 mimeType.GetSupertype(&superType); 3818 3819 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3820 if (superMenu == NULL) 3821 continue; 3822 3823 int32 itemsFound = 0; 3824 int32 menusFound = 0; 3825 for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) { 3826 if (item->Submenu() != NULL) 3827 menusFound++; 3828 else 3829 itemsFound++; 3830 } 3831 3832 if (itemsFound == 0) { 3833 if (menusFound != 0) { 3834 // promote types to the top level 3835 while (BMenuItem* item = superMenu->RemoveItem((int32)0)) { 3836 menu->AddItem(item); 3837 } 3838 } 3839 3840 menu->RemoveItem(superMenu->Superitem()); 3841 delete superMenu->Superitem(); 3842 } 3843 } 3844 3845 // remove separator if it's the only item in menu 3846 BMenuItem* item = menu->ItemAt(menu->CountItems() - 1); 3847 if (dynamic_cast<BSeparatorItem*>(item) != NULL) { 3848 menu->RemoveItem(item); 3849 delete item; 3850 } 3851 3852 MarkAttributeMenu(menu); 3853 } 3854 3855 3856 BHandler* 3857 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index, 3858 BMessage* specifier, int32 form, const char* property) 3859 { 3860 if (strcmp(property, "Poses") == 0) { 3861 // PRINT(("BContainerWindow::ResolveSpecifier %s\n", property)); 3862 message->PopSpecifier(); 3863 return PoseView(); 3864 } 3865 3866 return _inherited::ResolveSpecifier(message, index, specifier, 3867 form, property); 3868 } 3869 3870 3871 PiggybackTaskLoop* 3872 BContainerWindow::DelayedTaskLoop() 3873 { 3874 if (!fTaskLoop) 3875 fTaskLoop = new PiggybackTaskLoop; 3876 3877 return fTaskLoop; 3878 } 3879 3880 3881 bool 3882 BContainerWindow::NeedsDefaultStateSetup() 3883 { 3884 if (TargetModel() == NULL) 3885 return false; 3886 3887 if (TargetModel()->IsRoot()) { 3888 // don't try to set up anything if we are root 3889 return false; 3890 } 3891 3892 WindowStateNodeOpener opener(this, false); 3893 if (opener.StreamNode() == NULL) { 3894 // can't read state, give up 3895 return false; 3896 } 3897 3898 return !NodeHasSavedState(opener.Node()); 3899 } 3900 3901 3902 bool 3903 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result, 3904 bool createNew, bool createFolder) 3905 { 3906 //PRINT(("looking for default state in tracker settings dir\n")); 3907 BPath settingsPath; 3908 if (FSFindTrackerSettingsDir(&settingsPath) != B_OK) 3909 return false; 3910 3911 BDirectory dir(settingsPath.Path()); 3912 3913 BPath path(settingsPath); 3914 path.Append(name); 3915 if (!BEntry(path.Path()).Exists()) { 3916 if (!createNew) 3917 return false; 3918 3919 BPath tmpPath(settingsPath); 3920 for (;;) { 3921 // deal with several levels of folders 3922 const char* nextSlash = strchr(name, '/'); 3923 if (!nextSlash) 3924 break; 3925 3926 BString tmp; 3927 tmp.SetTo(name, nextSlash - name); 3928 tmpPath.Append(tmp.String()); 3929 3930 mkdir(tmpPath.Path(), 0777); 3931 3932 name = nextSlash + 1; 3933 if (!name[0]) { 3934 // can't deal with a slash at end 3935 return false; 3936 } 3937 } 3938 3939 if (createFolder) { 3940 if (mkdir(path.Path(), 0777) < 0) 3941 return false; 3942 } else { 3943 BFile file; 3944 if (dir.CreateFile(name, &file) != B_OK) 3945 return false; 3946 } 3947 } 3948 3949 //PRINT(("using default state from %s\n", path.Path())); 3950 result->SetTo(path.Path()); 3951 return result->InitCheck() == B_OK; 3952 } 3953 3954 3955 void 3956 BContainerWindow::SetUpDefaultState() 3957 { 3958 BNode defaultingNode; 3959 // this is where we'll ulitimately get the state from 3960 bool gotDefaultingNode = 0; 3961 bool shouldStagger = false; 3962 3963 ASSERT(TargetModel() != NULL); 3964 3965 PRINT(("folder %s does not have any saved state\n", TargetModel()->Name())); 3966 3967 WindowStateNodeOpener opener(this, true); 3968 // this is our destination node, whatever it is for this window 3969 if (opener.StreamNode() == NULL) 3970 return; 3971 3972 if (!TargetModel()->IsRoot()) { 3973 BDirectory deskDir; 3974 FSGetDeskDir(&deskDir); 3975 3976 // try copying state from our parent directory, unless it is the 3977 // desktop folder 3978 BEntry entry(TargetModel()->EntryRef()); 3979 BNode parent; 3980 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK 3981 && parent != deskDir) { 3982 PRINT(("looking at parent for state\n")); 3983 if (NodeHasSavedState(&parent)) { 3984 PRINT(("got state from parent\n")); 3985 defaultingNode = parent; 3986 gotDefaultingNode = true; 3987 // when getting state from parent, stagger the window 3988 shouldStagger = true; 3989 } 3990 } 3991 } 3992 3993 if (!gotDefaultingNode 3994 // parent didn't have any state, use the template directory from 3995 // tracker settings folder for what our state should be 3996 // For simplicity we are not picking up the most recent 3997 // changes that didn't get committed if home is still open in 3998 // a window, that's probably not a problem; would be OK if state 3999 // got committed after every change 4000 && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode, 4001 true)) { 4002 return; 4003 } 4004 4005 if (fIsDesktop) { 4006 // don't copy over the attributes if we are the Desktop 4007 return; 4008 } 4009 4010 // copy over the attributes 4011 4012 // set up a filter of the attributes we want copied 4013 const char* allowAttrs[] = { 4014 kAttrWindowFrame, 4015 kAttrWindowWorkspace, 4016 kAttrViewState, 4017 kAttrViewStateForeign, 4018 kAttrColumns, 4019 kAttrColumnsForeign, 4020 0 4021 }; 4022 4023 // copy over attributes that apply; transform them properly, stripping 4024 // parts that do not apply, adding a window stagger, etc. 4025 4026 StaggerOneParams params; 4027 params.rectFromParent = shouldStagger; 4028 SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame, 4029 OffsetFrameOne, ¶ms); 4030 SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState, 4031 ClearViewOriginOne, ¶ms); 4032 4033 // do it 4034 AttributeStreamMemoryNode memoryNode; 4035 NamesToAcceptAttrFilter filter(allowAttrs); 4036 AttributeStreamFileNode fileNode(&defaultingNode); 4037 4038 *opener.StreamNode() << scrollOriginCleaner << frameOffsetter 4039 << memoryNode << filter << fileNode; 4040 } 4041 4042 4043 void 4044 BContainerWindow::RestoreWindowState(AttributeStreamNode* node) 4045 { 4046 if (node == NULL || fIsDesktop) { 4047 // don't restore any window state if we are the Desktop 4048 return; 4049 } 4050 4051 const char* rectAttributeName; 4052 const char* workspaceAttributeName; 4053 if (TargetModel()->IsRoot()) { 4054 rectAttributeName = kAttrDisksFrame; 4055 workspaceAttributeName = kAttrDisksWorkspace; 4056 } else { 4057 rectAttributeName = kAttrWindowFrame; 4058 workspaceAttributeName = kAttrWindowWorkspace; 4059 } 4060 4061 BRect frame(Frame()); 4062 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 4063 == sizeof(BRect)) { 4064 const float scalingFactor = be_plain_font->Size() / 12.0f; 4065 frame.left *= scalingFactor; 4066 frame.top *= scalingFactor; 4067 frame.right *= scalingFactor; 4068 frame.bottom *= scalingFactor; 4069 4070 MoveTo(frame.LeftTop()); 4071 ResizeTo(frame.Width(), frame.Height()); 4072 } else 4073 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy); 4074 4075 fPreviousBounds = Bounds(); 4076 4077 uint32 workspace; 4078 if (((fContainerWindowFlags & kRestoreWorkspace) != 0) 4079 && node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 4080 &workspace) == sizeof(uint32)) 4081 SetWorkspaces(workspace); 4082 4083 if ((fContainerWindowFlags & kIsHidden) != 0) 4084 Minimize(true); 4085 4086 // restore window decor settings 4087 int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE); 4088 if (size > 0) { 4089 char buffer[size]; 4090 if (((fContainerWindowFlags & kRestoreDecor) != 0) 4091 && node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer) 4092 == size) { 4093 BMessage decorSettings; 4094 if (decorSettings.Unflatten(buffer) == B_OK) 4095 SetDecoratorSettings(decorSettings); 4096 } 4097 } 4098 } 4099 4100 4101 void 4102 BContainerWindow::RestoreWindowState(const BMessage& message) 4103 { 4104 if (fIsDesktop) { 4105 // don't restore any window state if we are the Desktop 4106 return; 4107 } 4108 4109 const char* rectAttributeName; 4110 const char* workspaceAttributeName; 4111 if (TargetModel()->IsRoot()) { 4112 rectAttributeName = kAttrDisksFrame; 4113 workspaceAttributeName = kAttrDisksWorkspace; 4114 } else { 4115 rectAttributeName = kAttrWindowFrame; 4116 workspaceAttributeName = kAttrWindowWorkspace; 4117 } 4118 4119 BRect frame(Frame()); 4120 if (message.FindRect(rectAttributeName, &frame) == B_OK) { 4121 const float scalingFactor = be_plain_font->Size() / 12.0f; 4122 frame.left *= scalingFactor; 4123 frame.top *= scalingFactor; 4124 frame.right *= scalingFactor; 4125 frame.bottom *= scalingFactor; 4126 4127 MoveTo(frame.LeftTop()); 4128 ResizeTo(frame.Width(), frame.Height()); 4129 } else 4130 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy); 4131 4132 uint32 workspace; 4133 if ((fContainerWindowFlags & kRestoreWorkspace) 4134 && message.FindInt32(workspaceAttributeName, 4135 (int32*)&workspace) == B_OK) { 4136 SetWorkspaces(workspace); 4137 } 4138 4139 if (fContainerWindowFlags & kIsHidden) 4140 Minimize(true); 4141 4142 // restore window decor settings 4143 BMessage decorSettings; 4144 if ((fContainerWindowFlags & kRestoreDecor) 4145 && message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) { 4146 SetDecoratorSettings(decorSettings); 4147 } 4148 4149 fStateNeedsSaving = false; 4150 // Undo the effect of the above MoveTo and ResizeTo calls 4151 } 4152 4153 4154 void 4155 BContainerWindow::SaveWindowState(AttributeStreamNode* node) 4156 { 4157 if (fIsDesktop) { 4158 // don't save window state if we are the Desktop 4159 return; 4160 } 4161 4162 ASSERT(node != NULL); 4163 4164 const char* rectAttributeName; 4165 const char* workspaceAttributeName; 4166 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 4167 rectAttributeName = kAttrDisksFrame; 4168 workspaceAttributeName = kAttrDisksWorkspace; 4169 } else { 4170 rectAttributeName = kAttrWindowFrame; 4171 workspaceAttributeName = kAttrWindowWorkspace; 4172 } 4173 4174 // node is null if it already got deleted 4175 BRect frame(Frame()); 4176 const float scalingFactor = be_plain_font->Size() / 12.0f; 4177 frame.left /= scalingFactor; 4178 frame.top /= scalingFactor; 4179 frame.right /= scalingFactor; 4180 frame.bottom /= scalingFactor; 4181 node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame); 4182 4183 uint32 workspaces = Workspaces(); 4184 node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 4185 &workspaces); 4186 4187 BMessage decorSettings; 4188 if (GetDecoratorSettings(&decorSettings) == B_OK) { 4189 int32 size = decorSettings.FlattenedSize(); 4190 char buffer[size]; 4191 if (decorSettings.Flatten(buffer, size) == B_OK) { 4192 node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer); 4193 } 4194 } 4195 } 4196 4197 4198 void 4199 BContainerWindow::SaveWindowState(BMessage& message) const 4200 { 4201 const char* rectAttributeName; 4202 const char* workspaceAttributeName; 4203 4204 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 4205 rectAttributeName = kAttrDisksFrame; 4206 workspaceAttributeName = kAttrDisksWorkspace; 4207 } else { 4208 rectAttributeName = kAttrWindowFrame; 4209 workspaceAttributeName = kAttrWindowWorkspace; 4210 } 4211 4212 // node is null if it already got deleted 4213 BRect frame(Frame()); 4214 const float scalingFactor = be_plain_font->Size() / 12.0f; 4215 frame.left /= scalingFactor; 4216 frame.top /= scalingFactor; 4217 frame.right /= scalingFactor; 4218 frame.bottom /= scalingFactor; 4219 message.AddRect(rectAttributeName, frame); 4220 message.AddInt32(workspaceAttributeName, (int32)Workspaces()); 4221 4222 BMessage decorSettings; 4223 if (GetDecoratorSettings(&decorSettings) == B_OK) { 4224 message.AddMessage(kAttrWindowDecor, &decorSettings); 4225 } 4226 } 4227 4228 4229 status_t 4230 BContainerWindow::DragStart(const BMessage* dragMessage) 4231 { 4232 if (dragMessage == NULL) 4233 return B_ERROR; 4234 4235 // if already dragging, or 4236 // if all the refs match 4237 if (Dragging() 4238 && SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) { 4239 return B_OK; 4240 } 4241 4242 // cache the current drag message 4243 // build a list of the mimetypes in the message 4244 SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage, 4245 &fCachedTypesList); 4246 4247 fWaitingForRefs = true; 4248 4249 return B_OK; 4250 } 4251 4252 4253 void 4254 BContainerWindow::DragStop() 4255 { 4256 delete fDragMessage; 4257 fDragMessage = NULL; 4258 4259 delete fCachedTypesList; 4260 fCachedTypesList = NULL; 4261 4262 fWaitingForRefs = false; 4263 } 4264 4265 4266 void 4267 BContainerWindow::ShowSelectionWindow() 4268 { 4269 if (fSelectionWindow == NULL) { 4270 fSelectionWindow = new SelectionWindow(this); 4271 fSelectionWindow->Show(); 4272 } else if (fSelectionWindow->Lock()) { 4273 // The window is already there, just bring it close 4274 fSelectionWindow->MoveCloseToMouse(); 4275 if (fSelectionWindow->IsHidden()) 4276 fSelectionWindow->Show(); 4277 4278 fSelectionWindow->Unlock(); 4279 } 4280 } 4281 4282 4283 void 4284 BContainerWindow::ShowNavigator(bool show) 4285 { 4286 if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory() 4287 || fPoseView->IsFilePanel()) { 4288 return; 4289 } 4290 4291 if (show) { 4292 if (Navigator() && !Navigator()->IsHidden()) 4293 return; 4294 4295 if (Navigator() == NULL) { 4296 fNavigator = new BNavigator(TargetModel()); 4297 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2); 4298 } 4299 4300 if (Navigator()->IsHidden()) 4301 Navigator()->Show(); 4302 4303 if (PoseView()->VScrollBar()) 4304 PoseView()->UpdateScrollRange(); 4305 } else { 4306 if (!Navigator() || Navigator()->IsHidden()) 4307 return; 4308 4309 if (PoseView()->VScrollBar()) 4310 PoseView()->UpdateScrollRange(); 4311 4312 fNavigator->Hide(); 4313 } 4314 } 4315 4316 4317 void 4318 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled) 4319 { 4320 if (PoseView()->IsDesktopWindow()) 4321 return; 4322 4323 if (enabled) { 4324 if (!Navigator()) 4325 return; 4326 4327 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4328 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4329 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4330 4331 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, 4332 new BMessage(kNavigatorCommandBackward), Navigator()); 4333 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, 4334 new BMessage(kNavigatorCommandForward), Navigator()); 4335 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4336 new BMessage(kNavigatorCommandUp), Navigator()); 4337 4338 AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4339 new BMessage(kNavigatorCommandBackward), Navigator()); 4340 AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4341 new BMessage(kNavigatorCommandForward), Navigator()); 4342 AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4343 new BMessage(kNavigatorCommandUp), Navigator()); 4344 AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4345 new BMessage(kOpenSelection), PoseView()); 4346 AddShortcut('L', B_COMMAND_KEY, 4347 new BMessage(kNavigatorCommandSetFocus), Navigator()); 4348 4349 } else { 4350 RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 4351 RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 4352 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4353 // This is added again, below, with a new meaning. 4354 4355 RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4356 RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4357 RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4358 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4359 // This also changes meaning, added again below. 4360 4361 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4362 new BMessage(kOpenSelection), PoseView()); 4363 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4364 new BMessage(kOpenParentDir), PoseView()); 4365 // We change the meaning from kNavigatorCommandUp 4366 // to kOpenParentDir. 4367 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4368 new BMessage(kOpenParentDir), PoseView()); 4369 // command + option results in closing the parent window 4370 RemoveShortcut('L', B_COMMAND_KEY); 4371 } 4372 } 4373 4374 4375 void 4376 BContainerWindow::SetPathWatchingEnabled(bool enable) 4377 { 4378 if (IsPathWatchingEnabled()) { 4379 stop_watching(this); 4380 fIsWatchingPath = false; 4381 } 4382 4383 if (enable) { 4384 if (TargetModel() != NULL) { 4385 BEntry entry; 4386 4387 TargetModel()->GetEntry(&entry); 4388 status_t err; 4389 do { 4390 err = entry.GetParent(&entry); 4391 if (err != B_OK) 4392 break; 4393 4394 char name[B_FILE_NAME_LENGTH]; 4395 entry.GetName(name); 4396 if (strcmp(name, "/") == 0) 4397 break; 4398 4399 node_ref ref; 4400 entry.GetNodeRef(&ref); 4401 watch_node(&ref, B_WATCH_NAME, this); 4402 } while (err == B_OK); 4403 4404 fIsWatchingPath = err == B_OK; 4405 } else 4406 fIsWatchingPath = false; 4407 } 4408 } 4409 4410 4411 void 4412 BContainerWindow::PulseTaskLoop() 4413 { 4414 if (fTaskLoop) 4415 fTaskLoop->PulseMe(); 4416 } 4417 4418 4419 void 4420 BContainerWindow::PopulateArrangeByMenu(BMenu* menu) 4421 { 4422 if (!fAttrMenu || !menu) 4423 return; 4424 // empty fArrangeByMenu... 4425 BMenuItem* item; 4426 while ((item = menu->RemoveItem((int32)0)) != NULL) 4427 delete item; 4428 4429 int32 itemCount = fAttrMenu->CountItems(); 4430 for (int32 i = 0; i < itemCount; i++) { 4431 item = fAttrMenu->ItemAt(i); 4432 if (item->Command() == kAttributeItem) { 4433 BMessage* message = new BMessage(*(item->Message())); 4434 message->what = kArrangeBy; 4435 BMenuItem* newItem = new BMenuItem(item->Label(), message); 4436 newItem->SetTarget(PoseView()); 4437 menu->AddItem(newItem); 4438 } 4439 } 4440 4441 menu->AddSeparatorItem(); 4442 4443 item = new BMenuItem(B_TRANSLATE("Reverse order"), 4444 new BMessage(kArrangeReverseOrder)); 4445 4446 item->SetTarget(PoseView()); 4447 menu->AddItem(item); 4448 4449 menu->AddSeparatorItem(); 4450 4451 item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K'); 4452 item->SetTarget(PoseView()); 4453 menu->AddItem(item); 4454 } 4455 4456 4457 // #pragma mark - WindowStateNodeOpener 4458 4459 4460 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window, 4461 bool forWriting) 4462 : 4463 fModelOpener(NULL), 4464 fNode(NULL), 4465 fStreamNode(NULL) 4466 { 4467 if (window->TargetModel() && window->TargetModel()->IsRoot()) { 4468 BDirectory dir; 4469 if (FSGetDeskDir(&dir) == B_OK) { 4470 fNode = new BDirectory(dir); 4471 fStreamNode = new AttributeStreamFileNode(fNode); 4472 } 4473 } else if (window->TargetModel()){ 4474 fModelOpener = new ModelNodeLazyOpener(window->TargetModel(), 4475 forWriting, false); 4476 if (fModelOpener->IsOpen(forWriting)) { 4477 fStreamNode = new AttributeStreamFileNode( 4478 fModelOpener->TargetModel()->Node()); 4479 } 4480 } 4481 } 4482 4483 WindowStateNodeOpener::~WindowStateNodeOpener() 4484 { 4485 delete fModelOpener; 4486 delete fNode; 4487 delete fStreamNode; 4488 } 4489 4490 4491 void 4492 WindowStateNodeOpener::SetTo(const BDirectory* node) 4493 { 4494 delete fModelOpener; 4495 delete fNode; 4496 delete fStreamNode; 4497 4498 fModelOpener = NULL; 4499 fNode = new BDirectory(*node); 4500 fStreamNode = new AttributeStreamFileNode(fNode); 4501 } 4502 4503 4504 void 4505 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting) 4506 { 4507 delete fModelOpener; 4508 delete fNode; 4509 delete fStreamNode; 4510 4511 fModelOpener = NULL; 4512 fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY)); 4513 fStreamNode = new AttributeStreamFileNode(fNode); 4514 } 4515 4516 4517 void 4518 WindowStateNodeOpener::SetTo(Model* model, bool forWriting) 4519 { 4520 delete fModelOpener; 4521 delete fNode; 4522 delete fStreamNode; 4523 4524 fNode = NULL; 4525 fStreamNode = NULL; 4526 fModelOpener = new ModelNodeLazyOpener(model, forWriting, false); 4527 if (fModelOpener->IsOpen(forWriting)) { 4528 fStreamNode = new AttributeStreamFileNode( 4529 fModelOpener->TargetModel()->Node()); 4530 } 4531 } 4532 4533 4534 AttributeStreamNode* 4535 WindowStateNodeOpener::StreamNode() const 4536 { 4537 return fStreamNode; 4538 } 4539 4540 4541 BNode* 4542 WindowStateNodeOpener::Node() const 4543 { 4544 if (!fStreamNode) 4545 return NULL; 4546 4547 if (fNode) 4548 return fNode; 4549 4550 return fModelOpener->TargetModel()->Node(); 4551 } 4552 4553 4554 // #pragma mark - BorderedView 4555 4556 4557 BorderedView::BorderedView() 4558 : 4559 BGroupView(B_VERTICAL, 0), 4560 fEnableBorderHighlight(true) 4561 { 4562 GroupLayout()->SetInsets(1); 4563 } 4564 4565 4566 void 4567 BorderedView::WindowActivated(bool active) 4568 { 4569 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4570 if (window == NULL) 4571 return; 4572 4573 if (window->PoseView()->IsFocus()) 4574 PoseViewFocused(active); // Update border color 4575 } 4576 4577 4578 void BorderedView::EnableBorderHighlight(bool enable) 4579 { 4580 fEnableBorderHighlight = enable; 4581 PoseViewFocused(false); 4582 } 4583 4584 4585 void 4586 BorderedView::PoseViewFocused(bool focused) 4587 { 4588 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4589 if (window == NULL) 4590 return; 4591 4592 color_which base = B_DOCUMENT_BACKGROUND_COLOR; 4593 float tint = B_DARKEN_2_TINT; 4594 if (focused && window->IsActive() && fEnableBorderHighlight) { 4595 base = B_KEYBOARD_NAVIGATION_COLOR; 4596 tint = B_NO_TINT; 4597 } 4598 4599 BScrollBar* hScrollBar = window->PoseView()->HScrollBar(); 4600 if (hScrollBar != NULL) 4601 hScrollBar->SetBorderHighlighted(focused); 4602 4603 BScrollBar* vScrollBar = window->PoseView()->VScrollBar(); 4604 if (vScrollBar != NULL) 4605 vScrollBar->SetBorderHighlighted(focused); 4606 4607 SetViewUIColor(base, tint); 4608 Invalidate(); 4609 } 4610 4611 4612 void 4613 BorderedView::Pulse() 4614 { 4615 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4616 if (window != NULL) 4617 window->PulseTaskLoop(); 4618 } 4619