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