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