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 AutoLock<BLooper> _(parentMenu->Looper()); 2991 parentMenu->GetFont(&font); 2992 } 2993 2994 BMenu* menu = item->Submenu(); 2995 if (menu == NULL) 2996 return; 2997 2998 menu->SetFont(&font); 2999 3000 // found add-ons menu, empty it first 3001 for (;;) { 3002 item = menu->RemoveItem((int32)0); 3003 if (!item) 3004 break; 3005 delete item; 3006 } 3007 3008 BObjectList<BMenuItem> primaryList; 3009 BObjectList<BMenuItem> secondaryList; 3010 BStringList mimeTypes(10); 3011 BuildMimeTypeList(mimeTypes); 3012 3013 AddOneAddOnParams params; 3014 params.primaryList = &primaryList; 3015 params.secondaryList = &secondaryList; 3016 3017 // build a list of the MIME types of the selected items 3018 3019 EachAddOn(AddOneAddOn, ¶ms, mimeTypes, parentMenu); 3020 3021 primaryList.SortItems(CompareLabels); 3022 secondaryList.SortItems(CompareLabels); 3023 3024 int32 count = primaryList.CountItems(); 3025 for (int32 index = 0; index < count; index++) 3026 menu->AddItem(primaryList.ItemAt(index)); 3027 3028 if (count > 0) 3029 menu->AddSeparatorItem(); 3030 3031 count = secondaryList.CountItems(); 3032 for (int32 index = 0; index < count; index++) 3033 menu->AddItem(secondaryList.ItemAt(index)); 3034 3035 menu->SetTargetForItems(this); 3036 } 3037 3038 3039 void 3040 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context) 3041 { 3042 const int32 selectCount = PoseView()->CountSelected(); 3043 const int32 poseCount = PoseView()->CountItems(); 3044 3045 if (context == kMenuBarContext) { 3046 EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0); 3047 EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0); 3048 EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0); 3049 EnableNamedMenuItem(menu, kDelete, 3050 PoseView()->CanMoveToTrashOrDuplicate()); 3051 } 3052 3053 if (context == kMenuBarContext || context == kPosePopUpContext) { 3054 SetupEditQueryItem(menu); 3055 3056 EnableNamedMenuItem(menu, kEditItem, PoseView()->CanEditName()); 3057 EnableNamedMenuItem(menu, kMoveToTrash, 3058 PoseView()->CanMoveToTrashOrDuplicate()); 3059 EnableNamedMenuItem(menu, kDuplicateSelection, 3060 PoseView()->CanMoveToTrashOrDuplicate()); 3061 3062 SetCutItem(menu); 3063 SetCopyItem(menu); 3064 SetPasteItem(menu); 3065 } 3066 3067 if (context == kMenuBarContext || context == kWindowPopUpContext) { 3068 uint32 viewMode = PoseView()->ViewMode(); 3069 3070 BMenu* iconSizeMenu = NULL; 3071 if (BMenuItem* item = menu->FindItem(kIconMode)) 3072 iconSizeMenu = item->Submenu(); 3073 3074 if (iconSizeMenu != NULL) { 3075 if (viewMode == kIconMode) { 3076 int32 iconSize = PoseView()->UnscaledIconSizeInt(); 3077 BMenuItem* item = iconSizeMenu->ItemAt(0); 3078 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; 3079 i++) { 3080 BMessage* message = item->Message(); 3081 if (message == NULL) { 3082 item->SetMarked(false); 3083 continue; 3084 } 3085 int32 size; 3086 if (message->FindInt32("size", &size) != B_OK) 3087 size = -1; 3088 item->SetMarked(iconSize == size); 3089 } 3090 } else { 3091 BMenuItem* item; 3092 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++) 3093 item->SetMarked(false); 3094 } 3095 } 3096 3097 MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode); 3098 MarkNamedMenuItem(menu, kListMode, viewMode == kListMode); 3099 MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode); 3100 3101 SetCloseItem(menu); 3102 SetArrangeMenu(menu); 3103 SetPasteItem(menu); 3104 3105 BEntry entry(TargetModel()->EntryRef()); 3106 BDirectory parent; 3107 bool parentIsRoot = (entry.GetParent(&parent) == B_OK 3108 && parent.GetEntry(&entry) == B_OK 3109 && FSIsRootDir(&entry)); 3110 3111 EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop() 3112 && !TargetModel()->IsRoot() 3113 && (!parentIsRoot 3114 || TrackerSettings().SingleWindowBrowse() 3115 || TrackerSettings().ShowDisksIcon() 3116 || (modifiers() & B_CONTROL_KEY) != 0)); 3117 3118 EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0); 3119 EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0); 3120 3121 BMenuItem* item = menu->FindItem(B_TRANSLATE("New")); 3122 if (item != NULL) { 3123 TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>( 3124 item->Submenu()); 3125 if (templatesMenu != NULL) 3126 templatesMenu->UpdateMenuState(); 3127 } 3128 } 3129 3130 BuildAddOnsMenu(menu); 3131 } 3132 3133 3134 BMessage* 3135 BContainerWindow::AddOnMessage(int32 what) 3136 { 3137 BMessage* message = new BMessage(what); 3138 3139 // add selected refs to message 3140 BObjectList<BPose>* selectionList = PoseView()->SelectionList(); 3141 3142 int32 index = 0; 3143 BPose* pose; 3144 while ((pose = selectionList->ItemAt(index++)) != NULL) 3145 message->AddRef("refs", pose->TargetModel()->EntryRef()); 3146 3147 message->AddRef("dir_ref", TargetModel()->EntryRef()); 3148 message->AddMessenger("TrackerViewToken", BMessenger(PoseView())); 3149 3150 return message; 3151 } 3152 3153 3154 void 3155 BContainerWindow::LoadAddOn(BMessage* message) 3156 { 3157 UpdateIfNeeded(); 3158 3159 entry_ref addOnRef; 3160 status_t result = message->FindRef("refs", &addOnRef); 3161 if (result != B_OK) { 3162 BString buffer(B_TRANSLATE("Error %error loading add-On %name.")); 3163 buffer.ReplaceFirst("%error", strerror(result)); 3164 buffer.ReplaceFirst("%name", addOnRef.name); 3165 3166 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 3167 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3168 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3169 alert->Go(); 3170 return; 3171 } 3172 3173 // add selected refs to message 3174 BMessage* refs = AddOnMessage(B_REFS_RECEIVED); 3175 3176 LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs, 3177 addOnRef, *TargetModel()->EntryRef()); 3178 } 3179 3180 3181 // #pragma mark - BContainerWindow private methods 3182 3183 3184 void 3185 BContainerWindow::_UpdateSelectionMIMEInfo() 3186 { 3187 BPose* pose; 3188 int32 index = 0; 3189 while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) { 3190 BString mimeType(pose->TargetModel()->MimeType()); 3191 if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3192 pose->TargetModel()->Mimeset(true); 3193 if (pose->TargetModel()->IsSymLink()) { 3194 Model* resolved = new Model(pose->TargetModel()->EntryRef(), 3195 true, true); 3196 if (resolved->InitCheck() == B_OK) { 3197 mimeType.SetTo(resolved->MimeType()); 3198 if (!mimeType.Length() 3199 || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3200 resolved->Mimeset(true); 3201 } 3202 } 3203 delete resolved; 3204 } 3205 } 3206 } 3207 } 3208 3209 3210 void 3211 BContainerWindow::_AddFolderIcon() 3212 { 3213 if (fMenuBar == NULL) { 3214 // We don't want to add the icon if there's no menubar 3215 return; 3216 } 3217 3218 float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1, 3219 iconSize = fMenuBar->Bounds().Height() - 2; 3220 if (iconSize < baseIconSize) 3221 iconSize = baseIconSize; 3222 3223 fDraggableIcon = new(std::nothrow) 3224 DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1)); 3225 if (fDraggableIcon != NULL) { 3226 fMenuContainer->GroupLayout()->AddView(fDraggableIcon); 3227 fMenuBar->SetBorders( 3228 BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER); 3229 } 3230 } 3231 3232 3233 void 3234 BContainerWindow::_PassMessageToAddOn(BMessage* message) 3235 { 3236 LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY, 3237 &RunAddOnMessageThread, new BMessage(*message), (void*)NULL); 3238 } 3239 3240 3241 BMenuItem* 3242 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3243 int32 type, float width, int32 align, bool editable, bool statField) 3244 { 3245 return NewAttributeMenuItem(label, name, type, NULL, width, align, 3246 editable, statField); 3247 } 3248 3249 3250 BMenuItem* 3251 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3252 int32 type, const char* displayAs, float width, int32 align, 3253 bool editable, bool statField) 3254 { 3255 BMessage* message = new BMessage(kAttributeItem); 3256 message->AddString("attr_name", name); 3257 message->AddInt32("attr_type", type); 3258 message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type)); 3259 message->AddFloat("attr_width", width); 3260 message->AddInt32("attr_align", align); 3261 if (displayAs != NULL) 3262 message->AddString("attr_display_as", displayAs); 3263 message->AddBool("attr_editable", editable); 3264 message->AddBool("attr_statfield", statField); 3265 3266 BMenuItem* menuItem = new BMenuItem(label, message); 3267 menuItem->SetTarget(PoseView()); 3268 3269 return menuItem; 3270 } 3271 3272 3273 void 3274 BContainerWindow::NewAttributesMenu(BMenu* menu) 3275 { 3276 ASSERT(PoseView()); 3277 3278 BMenuItem* item; 3279 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"), 3280 new BMessage(kCopyAttributes))); 3281 item->SetTarget(PoseView()); 3282 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"), 3283 new BMessage(kPasteAttributes))); 3284 item->SetTarget(PoseView()); 3285 menu->AddSeparatorItem(); 3286 3287 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"), 3288 kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3289 3290 if (gLocalizedNamePreferred) { 3291 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"), 3292 kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3293 } 3294 3295 menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize, 3296 B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true)); 3297 3298 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"), 3299 kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3300 3301 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"), 3302 kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3303 3304 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"), 3305 kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false)); 3306 3307 if (IsTrash() || InTrash()) { 3308 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"), 3309 kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false, 3310 false)); 3311 } else { 3312 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath, 3313 B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false)); 3314 } 3315 3316 #ifdef OWNER_GROUP_ATTRIBUTES 3317 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner, 3318 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3319 3320 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup, 3321 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3322 #endif 3323 3324 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"), 3325 kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true)); 3326 } 3327 3328 3329 void 3330 BContainerWindow::ShowAttributesMenu() 3331 { 3332 ASSERT(fAttrMenu); 3333 fMenuBar->AddItem(fAttrMenu); 3334 } 3335 3336 3337 void 3338 BContainerWindow::HideAttributesMenu() 3339 { 3340 ASSERT(fAttrMenu); 3341 fMenuBar->RemoveItem(fAttrMenu); 3342 } 3343 3344 3345 void 3346 BContainerWindow::MarkAttributesMenu() 3347 { 3348 MarkAttributesMenu(fAttrMenu); 3349 } 3350 3351 3352 void 3353 BContainerWindow::MarkAttributesMenu(BMenu* menu) 3354 { 3355 if (menu == NULL) 3356 return; 3357 3358 int32 count = menu->CountItems(); 3359 for (int32 index = 0; index < count; index++) { 3360 BMenuItem* item = menu->ItemAt(index); 3361 int32 attrHash; 3362 if (item->Message() != NULL) { 3363 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) 3364 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); 3365 else 3366 item->SetMarked(false); 3367 } 3368 3369 BMenu* submenu = item->Submenu(); 3370 if (submenu != NULL) { 3371 int32 count2 = submenu->CountItems(); 3372 for (int32 subindex = 0; subindex < count2; subindex++) { 3373 item = submenu->ItemAt(subindex); 3374 if (item->Message() != NULL) { 3375 if (item->Message()->FindInt32("attr_hash", &attrHash) 3376 == B_OK) { 3377 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) 3378 != 0); 3379 } else 3380 item->SetMarked(false); 3381 } 3382 } 3383 } 3384 } 3385 } 3386 3387 3388 void 3389 BContainerWindow::MarkArrangeByMenu(BMenu* menu) 3390 { 3391 if (menu == NULL) 3392 return; 3393 3394 int32 count = menu->CountItems(); 3395 for (int32 index = 0; index < count; index++) { 3396 BMenuItem* item = menu->ItemAt(index); 3397 if (item->Message() != NULL) { 3398 uint32 attrHash; 3399 if (item->Message()->FindInt32("attr_hash", 3400 (int32*)&attrHash) == B_OK) { 3401 item->SetMarked(PoseView()->PrimarySort() == attrHash); 3402 } else if (item->Command() == kArrangeReverseOrder) 3403 item->SetMarked(PoseView()->ReverseSort()); 3404 } 3405 } 3406 } 3407 3408 3409 void 3410 BContainerWindow::AddMimeTypesToMenu() 3411 { 3412 AddMimeTypesToMenu(fAttrMenu); 3413 } 3414 3415 3416 // Adds a menu for a specific MIME type if it doesn't exist already. 3417 // Returns the menu, if it existed or not. 3418 BMenu* 3419 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType, 3420 BMenu* menu, int32 start) 3421 { 3422 AutoLock<BLooper> _(menu->Looper()); 3423 3424 if (!mimeType.IsValid()) 3425 return NULL; 3426 3427 // Check if we already have an entry for this MIME type in the menu. 3428 for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) { 3429 BMessage* message = item->Message(); 3430 if (message == NULL) 3431 continue; 3432 3433 const char* type; 3434 if (message->FindString("mimetype", &type) == B_OK 3435 && !strcmp(mimeType.Type(), type)) { 3436 return item->Submenu(); 3437 } 3438 } 3439 3440 BMessage attrInfo; 3441 char description[B_MIME_TYPE_LENGTH]; 3442 const char* label = mimeType.Type(); 3443 3444 if (!mimeType.IsInstalled()) 3445 return NULL; 3446 3447 // only add things to menu which have "user-visible" data 3448 if (mimeType.GetAttrInfo(&attrInfo) != B_OK) 3449 return NULL; 3450 3451 if (mimeType.GetShortDescription(description) == B_OK && description[0]) 3452 label = description; 3453 3454 // go through each field in meta mime and add it to a menu 3455 BMenu* mimeMenu = NULL; 3456 if (isSuperType) { 3457 // If it is a supertype, we create the menu anyway as it may have 3458 // submenus later on. 3459 mimeMenu = new BMenu(label); 3460 BFont font; 3461 menu->GetFont(&font); 3462 mimeMenu->SetFont(&font); 3463 } 3464 3465 int32 index = -1; 3466 const char* publicName; 3467 while (attrInfo.FindString("attr:public_name", ++index, &publicName) 3468 == B_OK) { 3469 if (!attrInfo.FindBool("attr:viewable", index)) { 3470 // don't add if attribute not viewable 3471 continue; 3472 } 3473 3474 int32 type; 3475 int32 align; 3476 int32 width; 3477 bool editable; 3478 const char* attrName; 3479 if (attrInfo.FindString("attr:name", index, &attrName) != B_OK 3480 || attrInfo.FindInt32("attr:type", index, &type) != B_OK 3481 || attrInfo.FindBool("attr:editable", index, &editable) != B_OK 3482 || attrInfo.FindInt32("attr:width", index, &width) != B_OK 3483 || attrInfo.FindInt32("attr:alignment", index, &align) != B_OK) 3484 continue; 3485 3486 BString displayAs; 3487 attrInfo.FindString("attr:display_as", index, &displayAs); 3488 3489 if (mimeMenu == NULL) { 3490 // do a lazy allocation of the menu 3491 mimeMenu = new BMenu(label); 3492 BFont font; 3493 menu->GetFont(&font); 3494 mimeMenu->SetFont(&font); 3495 } 3496 mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type, 3497 displayAs.String(), width, align, editable, false)); 3498 } 3499 3500 if (mimeMenu == NULL) 3501 return NULL; 3502 3503 BMessage* message = new BMessage(kMIMETypeItem); 3504 message->AddString("mimetype", mimeType.Type()); 3505 menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type())); 3506 3507 return mimeMenu; 3508 } 3509 3510 3511 void 3512 BContainerWindow::AddMimeTypesToMenu(BMenu* menu) 3513 { 3514 if (menu == NULL) 3515 return; 3516 3517 // Remove old mime type menus 3518 int32 start = menu->CountItems(); 3519 while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) { 3520 delete menu->RemoveItem(start - 1); 3521 start--; 3522 } 3523 3524 // Add a separator item if there is none yet 3525 if (start > 0 3526 && dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL) 3527 menu->AddSeparatorItem(); 3528 3529 // Add MIME type in case we're a default query type window 3530 BPath path; 3531 if (TargetModel() != NULL) { 3532 TargetModel()->GetPath(&path); 3533 if (path.InitCheck() == B_OK 3534 && strstr(path.Path(), "/" kQueryTemplates "/") != NULL) { 3535 // demangle MIME type name 3536 BString name(TargetModel()->Name()); 3537 name.ReplaceFirst('_', '/'); 3538 3539 PoseView()->AddMimeType(name.String()); 3540 } 3541 } 3542 3543 // Add MIME type menus 3544 3545 int32 typeCount = PoseView()->CountMimeTypes(); 3546 3547 for (int32 index = 0; index < typeCount; index++) { 3548 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3549 if (mimeType.InitCheck() == B_OK) { 3550 BMimeType superType; 3551 mimeType.GetSupertype(&superType); 3552 if (superType.InitCheck() == B_OK) { 3553 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3554 if (superMenu != NULL) { 3555 // We have a supertype menu. 3556 AddMimeMenu(mimeType, false, superMenu, 0); 3557 } 3558 } 3559 } 3560 } 3561 3562 // remove empty super menus, promote sub-types if needed 3563 3564 for (int32 index = 0; index < typeCount; index++) { 3565 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3566 BMimeType superType; 3567 mimeType.GetSupertype(&superType); 3568 3569 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3570 if (superMenu == NULL) 3571 continue; 3572 3573 int32 itemsFound = 0; 3574 int32 menusFound = 0; 3575 for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) { 3576 if (item->Submenu() != NULL) 3577 menusFound++; 3578 else 3579 itemsFound++; 3580 } 3581 3582 if (itemsFound == 0) { 3583 if (menusFound != 0) { 3584 // promote types to the top level 3585 while (BMenuItem* item = superMenu->RemoveItem((int32)0)) { 3586 menu->AddItem(item); 3587 } 3588 } 3589 3590 menu->RemoveItem(superMenu->Superitem()); 3591 delete superMenu->Superitem(); 3592 } 3593 } 3594 3595 // remove separator if it's the only item in menu 3596 BMenuItem* item = menu->ItemAt(menu->CountItems() - 1); 3597 if (dynamic_cast<BSeparatorItem*>(item) != NULL) { 3598 menu->RemoveItem(item); 3599 delete item; 3600 } 3601 3602 MarkAttributesMenu(menu); 3603 } 3604 3605 3606 BHandler* 3607 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index, 3608 BMessage* specifier, int32 form, const char* property) 3609 { 3610 if (strcmp(property, "Poses") == 0) { 3611 // PRINT(("BContainerWindow::ResolveSpecifier %s\n", property)); 3612 message->PopSpecifier(); 3613 return PoseView(); 3614 } 3615 3616 return _inherited::ResolveSpecifier(message, index, specifier, 3617 form, property); 3618 } 3619 3620 3621 PiggybackTaskLoop* 3622 BContainerWindow::DelayedTaskLoop() 3623 { 3624 if (!fTaskLoop) 3625 fTaskLoop = new PiggybackTaskLoop; 3626 3627 return fTaskLoop; 3628 } 3629 3630 3631 bool 3632 BContainerWindow::NeedsDefaultStateSetup() 3633 { 3634 if (TargetModel() == NULL) 3635 return false; 3636 3637 if (TargetModel()->IsRoot()) { 3638 // don't try to set up anything if we are root 3639 return false; 3640 } 3641 3642 WindowStateNodeOpener opener(this, false); 3643 if (opener.StreamNode() == NULL) { 3644 // can't read state, give up 3645 return false; 3646 } 3647 3648 return !NodeHasSavedState(opener.Node()); 3649 } 3650 3651 3652 bool 3653 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result, 3654 bool createNew, bool createFolder) 3655 { 3656 //PRINT(("looking for default state in tracker settings dir\n")); 3657 BPath settingsPath; 3658 if (FSFindTrackerSettingsDir(&settingsPath) != B_OK) 3659 return false; 3660 3661 BDirectory dir(settingsPath.Path()); 3662 3663 BPath path(settingsPath); 3664 path.Append(name); 3665 if (!BEntry(path.Path()).Exists()) { 3666 if (!createNew) 3667 return false; 3668 3669 BPath tmpPath(settingsPath); 3670 for (;;) { 3671 // deal with several levels of folders 3672 const char* nextSlash = strchr(name, '/'); 3673 if (!nextSlash) 3674 break; 3675 3676 BString tmp; 3677 tmp.SetTo(name, nextSlash - name); 3678 tmpPath.Append(tmp.String()); 3679 3680 mkdir(tmpPath.Path(), 0777); 3681 3682 name = nextSlash + 1; 3683 if (!name[0]) { 3684 // can't deal with a slash at end 3685 return false; 3686 } 3687 } 3688 3689 if (createFolder) { 3690 if (mkdir(path.Path(), 0777) < 0) 3691 return false; 3692 } else { 3693 BFile file; 3694 if (dir.CreateFile(name, &file) != B_OK) 3695 return false; 3696 } 3697 } 3698 3699 //PRINT(("using default state from %s\n", path.Path())); 3700 result->SetTo(path.Path()); 3701 return result->InitCheck() == B_OK; 3702 } 3703 3704 3705 void 3706 BContainerWindow::SetupDefaultState() 3707 { 3708 BNode defaultingNode; 3709 // this is where we'll ulitimately get the state from 3710 bool gotDefaultingNode = 0; 3711 bool shouldStagger = false; 3712 3713 ASSERT(TargetModel() != NULL); 3714 3715 PRINT(("folder %s does not have any saved state\n", TargetModel()->Name())); 3716 3717 WindowStateNodeOpener opener(this, true); 3718 // this is our destination node, whatever it is for this window 3719 if (opener.StreamNode() == NULL) 3720 return; 3721 3722 if (!TargetModel()->IsRoot()) { 3723 BDirectory deskDir; 3724 FSGetDeskDir(&deskDir); 3725 3726 // try copying state from our parent directory, unless it is the 3727 // desktop folder 3728 BEntry entry(TargetModel()->EntryRef()); 3729 BNode parent; 3730 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK 3731 && parent != deskDir) { 3732 PRINT(("looking at parent for state\n")); 3733 if (NodeHasSavedState(&parent)) { 3734 PRINT(("got state from parent\n")); 3735 defaultingNode = parent; 3736 gotDefaultingNode = true; 3737 // when getting state from parent, stagger the window 3738 shouldStagger = true; 3739 } 3740 } 3741 } 3742 3743 if (!gotDefaultingNode 3744 // parent didn't have any state, use the template directory from 3745 // tracker settings folder for what our state should be 3746 // For simplicity we are not picking up the most recent 3747 // changes that didn't get committed if home is still open in 3748 // a window, that's probably not a problem; would be OK if state 3749 // got committed after every change 3750 && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode, 3751 true)) { 3752 return; 3753 } 3754 3755 if (fIsDesktop) { 3756 // don't copy over the attributes if we are the Desktop 3757 return; 3758 } 3759 3760 // copy over the attributes 3761 3762 // set up a filter of the attributes we want copied 3763 const char* allowAttrs[] = { 3764 kAttrWindowFrame, 3765 kAttrWindowWorkspace, 3766 kAttrViewState, 3767 kAttrViewStateForeign, 3768 kAttrColumns, 3769 kAttrColumnsForeign, 3770 0 3771 }; 3772 3773 // copy over attributes that apply; transform them properly, stripping 3774 // parts that do not apply, adding a window stagger, etc. 3775 3776 StaggerOneParams params; 3777 params.rectFromParent = shouldStagger; 3778 SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame, 3779 OffsetFrameOne, ¶ms); 3780 SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState, 3781 ClearViewOriginOne, ¶ms); 3782 3783 // do it 3784 AttributeStreamMemoryNode memoryNode; 3785 NamesToAcceptAttrFilter filter(allowAttrs); 3786 AttributeStreamFileNode fileNode(&defaultingNode); 3787 3788 *opener.StreamNode() << scrollOriginCleaner << frameOffsetter 3789 << memoryNode << filter << fileNode; 3790 } 3791 3792 3793 void 3794 BContainerWindow::RestoreWindowState(AttributeStreamNode* node) 3795 { 3796 if (node == NULL || fIsDesktop) { 3797 // don't restore any window state if we are the Desktop 3798 return; 3799 } 3800 3801 const char* rectAttributeName; 3802 const char* workspaceAttributeName; 3803 if (TargetModel()->IsRoot()) { 3804 rectAttributeName = kAttrDisksFrame; 3805 workspaceAttributeName = kAttrDisksWorkspace; 3806 } else { 3807 rectAttributeName = kAttrWindowFrame; 3808 workspaceAttributeName = kAttrWindowWorkspace; 3809 } 3810 3811 BRect frame(Frame()); 3812 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 3813 == sizeof(BRect)) { 3814 const float scalingFactor = be_plain_font->Size() / 12.0f; 3815 frame.left *= scalingFactor; 3816 frame.top *= scalingFactor; 3817 frame.right *= scalingFactor; 3818 frame.bottom *= scalingFactor; 3819 3820 MoveTo(frame.LeftTop()); 3821 ResizeTo(frame.Width(), frame.Height()); 3822 } else 3823 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy); 3824 3825 fPreviousBounds = Bounds(); 3826 3827 uint32 workspace; 3828 if (((fOpenFlags & kRestoreWorkspace) != 0) 3829 && node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 3830 &workspace) == sizeof(uint32)) 3831 SetWorkspaces(workspace); 3832 3833 if ((fOpenFlags & kIsHidden) != 0) 3834 Minimize(true); 3835 3836 // restore window decor settings 3837 int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE); 3838 if (size > 0) { 3839 char buffer[size]; 3840 if (((fOpenFlags & kRestoreDecor) != 0) 3841 && node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer) 3842 == size) { 3843 BMessage decorSettings; 3844 if (decorSettings.Unflatten(buffer) == B_OK) 3845 SetDecoratorSettings(decorSettings); 3846 } 3847 } 3848 } 3849 3850 3851 void 3852 BContainerWindow::RestoreWindowState(const BMessage& message) 3853 { 3854 if (fIsDesktop) { 3855 // don't restore any window state if we are the Desktop 3856 return; 3857 } 3858 3859 const char* rectAttributeName; 3860 const char* workspaceAttributeName; 3861 if (TargetModel()->IsRoot()) { 3862 rectAttributeName = kAttrDisksFrame; 3863 workspaceAttributeName = kAttrDisksWorkspace; 3864 } else { 3865 rectAttributeName = kAttrWindowFrame; 3866 workspaceAttributeName = kAttrWindowWorkspace; 3867 } 3868 3869 BRect frame(Frame()); 3870 if (message.FindRect(rectAttributeName, &frame) == B_OK) { 3871 const float scalingFactor = be_plain_font->Size() / 12.0f; 3872 frame.left *= scalingFactor; 3873 frame.top *= scalingFactor; 3874 frame.right *= scalingFactor; 3875 frame.bottom *= scalingFactor; 3876 3877 MoveTo(frame.LeftTop()); 3878 ResizeTo(frame.Width(), frame.Height()); 3879 } else 3880 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy); 3881 3882 uint32 workspace; 3883 if (((fOpenFlags & kRestoreWorkspace) != 0) 3884 && message.FindInt32(workspaceAttributeName, 3885 (int32*)&workspace) == B_OK) { 3886 SetWorkspaces(workspace); 3887 } 3888 3889 if ((fOpenFlags & kIsHidden) != 0) 3890 Minimize(true); 3891 3892 // restore window decor settings 3893 BMessage decorSettings; 3894 if (((fOpenFlags & kRestoreDecor) != 0) 3895 && message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) { 3896 SetDecoratorSettings(decorSettings); 3897 } 3898 3899 fStateNeedsSaving = false; 3900 // Undo the effect of the above MoveTo and ResizeTo calls 3901 } 3902 3903 3904 void 3905 BContainerWindow::SaveWindowState(AttributeStreamNode* node) 3906 { 3907 if (fIsDesktop) { 3908 // don't save window state if we are the Desktop 3909 return; 3910 } 3911 3912 ASSERT(node != NULL); 3913 3914 const char* rectAttributeName; 3915 const char* workspaceAttributeName; 3916 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 3917 rectAttributeName = kAttrDisksFrame; 3918 workspaceAttributeName = kAttrDisksWorkspace; 3919 } else { 3920 rectAttributeName = kAttrWindowFrame; 3921 workspaceAttributeName = kAttrWindowWorkspace; 3922 } 3923 3924 // node is null if it already got deleted 3925 BRect frame(Frame()); 3926 const float scalingFactor = be_plain_font->Size() / 12.0f; 3927 frame.left /= scalingFactor; 3928 frame.top /= scalingFactor; 3929 frame.right /= scalingFactor; 3930 frame.bottom /= scalingFactor; 3931 node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame); 3932 3933 uint32 workspaces = Workspaces(); 3934 node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 3935 &workspaces); 3936 3937 BMessage decorSettings; 3938 if (GetDecoratorSettings(&decorSettings) == B_OK) { 3939 int32 size = decorSettings.FlattenedSize(); 3940 char buffer[size]; 3941 if (decorSettings.Flatten(buffer, size) == B_OK) { 3942 node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer); 3943 } 3944 } 3945 } 3946 3947 3948 void 3949 BContainerWindow::SaveWindowState(BMessage& message) const 3950 { 3951 const char* rectAttributeName; 3952 const char* workspaceAttributeName; 3953 3954 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 3955 rectAttributeName = kAttrDisksFrame; 3956 workspaceAttributeName = kAttrDisksWorkspace; 3957 } else { 3958 rectAttributeName = kAttrWindowFrame; 3959 workspaceAttributeName = kAttrWindowWorkspace; 3960 } 3961 3962 // node is null if it already got deleted 3963 BRect frame(Frame()); 3964 const float scalingFactor = be_plain_font->Size() / 12.0f; 3965 frame.left /= scalingFactor; 3966 frame.top /= scalingFactor; 3967 frame.right /= scalingFactor; 3968 frame.bottom /= scalingFactor; 3969 message.AddRect(rectAttributeName, frame); 3970 message.AddInt32(workspaceAttributeName, (int32)Workspaces()); 3971 3972 BMessage decorSettings; 3973 if (GetDecoratorSettings(&decorSettings) == B_OK) { 3974 message.AddMessage(kAttrWindowDecor, &decorSettings); 3975 } 3976 } 3977 3978 3979 status_t 3980 BContainerWindow::DragStart(const BMessage* dragMessage) 3981 { 3982 if (dragMessage == NULL) 3983 return B_ERROR; 3984 3985 // if already dragging, or 3986 // if all the refs match 3987 if (Dragging() 3988 && SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) { 3989 return B_OK; 3990 } 3991 3992 // cache the current drag message 3993 // build a list of the mimetypes in the message 3994 SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage, 3995 &fCachedTypesList); 3996 3997 fWaitingForRefs = true; 3998 3999 return B_OK; 4000 } 4001 4002 4003 void 4004 BContainerWindow::DragStop() 4005 { 4006 delete fDragMessage; 4007 fDragMessage = NULL; 4008 4009 delete fCachedTypesList; 4010 fCachedTypesList = NULL; 4011 4012 fWaitingForRefs = false; 4013 } 4014 4015 4016 void 4017 BContainerWindow::ShowSelectionWindow() 4018 { 4019 if (fSelectionWindow == NULL) { 4020 fSelectionWindow = new SelectionWindow(this); 4021 fSelectionWindow->Show(); 4022 } else if (fSelectionWindow->Lock()) { 4023 // The window is already there, just bring it close 4024 fSelectionWindow->MoveCloseToMouse(); 4025 if (fSelectionWindow->IsHidden()) 4026 fSelectionWindow->Show(); 4027 4028 fSelectionWindow->Unlock(); 4029 } 4030 } 4031 4032 4033 void 4034 BContainerWindow::ShowNavigator(bool show) 4035 { 4036 if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory() 4037 || PoseView()->IsFilePanel()) { 4038 return; 4039 } 4040 4041 if (show) { 4042 if (Navigator() && !Navigator()->IsHidden()) 4043 return; 4044 4045 if (Navigator() == NULL) { 4046 fNavigator = new BNavigator(TargetModel()); 4047 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2); 4048 } 4049 4050 if (Navigator()->IsHidden()) 4051 Navigator()->Show(); 4052 4053 if (PoseView()->VScrollBar()) 4054 PoseView()->UpdateScrollRange(); 4055 } else { 4056 if (!Navigator() || Navigator()->IsHidden()) 4057 return; 4058 4059 if (PoseView()->VScrollBar()) 4060 PoseView()->UpdateScrollRange(); 4061 4062 fNavigator->Hide(); 4063 } 4064 } 4065 4066 4067 void 4068 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled) 4069 { 4070 if (PoseView()->IsDesktopWindow()) 4071 return; 4072 4073 if (enabled) { 4074 if (!Navigator()) 4075 return; 4076 4077 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4078 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4079 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4080 4081 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, 4082 new BMessage(kNavigatorCommandBackward), Navigator()); 4083 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, 4084 new BMessage(kNavigatorCommandForward), Navigator()); 4085 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4086 new BMessage(kNavigatorCommandUp), Navigator()); 4087 4088 AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4089 new BMessage(kNavigatorCommandBackward), Navigator()); 4090 AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4091 new BMessage(kNavigatorCommandForward), Navigator()); 4092 AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4093 new BMessage(kNavigatorCommandUp), Navigator()); 4094 AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4095 new BMessage(kOpenSelection), PoseView()); 4096 AddShortcut('L', B_COMMAND_KEY, 4097 new BMessage(kNavigatorCommandSetFocus), Navigator()); 4098 4099 } else { 4100 RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 4101 RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 4102 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4103 // This is added again, below, with a new meaning. 4104 4105 RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4106 RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4107 RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4108 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4109 // This also changes meaning, added again below. 4110 4111 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4112 new BMessage(kOpenSelection), PoseView()); 4113 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4114 new BMessage(kOpenParentDir), PoseView()); 4115 // We change the meaning from kNavigatorCommandUp 4116 // to kOpenParentDir. 4117 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4118 new BMessage(kOpenParentDir), PoseView()); 4119 // command + option results in closing the parent window 4120 RemoveShortcut('L', B_COMMAND_KEY); 4121 } 4122 } 4123 4124 4125 void 4126 BContainerWindow::SetPathWatchingEnabled(bool enable) 4127 { 4128 if (IsPathWatchingEnabled()) { 4129 stop_watching(this); 4130 fIsWatchingPath = false; 4131 } 4132 4133 if (enable) { 4134 if (TargetModel() != NULL) { 4135 BEntry entry; 4136 4137 TargetModel()->GetEntry(&entry); 4138 status_t err; 4139 do { 4140 err = entry.GetParent(&entry); 4141 if (err != B_OK) 4142 break; 4143 4144 char name[B_FILE_NAME_LENGTH]; 4145 entry.GetName(name); 4146 if (strcmp(name, "/") == 0) 4147 break; 4148 4149 node_ref ref; 4150 entry.GetNodeRef(&ref); 4151 watch_node(&ref, B_WATCH_NAME, this); 4152 } while (err == B_OK); 4153 4154 fIsWatchingPath = err == B_OK; 4155 } else 4156 fIsWatchingPath = false; 4157 } 4158 } 4159 4160 4161 void 4162 BContainerWindow::PulseTaskLoop() 4163 { 4164 if (fTaskLoop) 4165 fTaskLoop->PulseMe(); 4166 } 4167 4168 4169 void 4170 BContainerWindow::PopulateArrangeByMenu(BMenu* menu) 4171 { 4172 if (!fAttrMenu || !menu) 4173 return; 4174 // empty fArrangeByMenu... 4175 BMenuItem* item; 4176 while ((item = menu->RemoveItem((int32)0)) != NULL) 4177 delete item; 4178 4179 int32 itemCount = fAttrMenu->CountItems(); 4180 for (int32 i = 0; i < itemCount; i++) { 4181 item = fAttrMenu->ItemAt(i); 4182 if (item->Command() == kAttributeItem) { 4183 BMessage* message = new BMessage(*(item->Message())); 4184 message->what = kArrangeBy; 4185 BMenuItem* newItem = new BMenuItem(item->Label(), message); 4186 newItem->SetTarget(PoseView()); 4187 menu->AddItem(newItem); 4188 } 4189 } 4190 4191 menu->AddSeparatorItem(); 4192 4193 item = new BMenuItem(B_TRANSLATE("Reverse order"), 4194 new BMessage(kArrangeReverseOrder)); 4195 4196 item->SetTarget(PoseView()); 4197 menu->AddItem(item); 4198 4199 menu->AddSeparatorItem(); 4200 4201 item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K'); 4202 item->SetTarget(PoseView()); 4203 menu->AddItem(item); 4204 } 4205 4206 4207 // #pragma mark - WindowStateNodeOpener 4208 4209 4210 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window, 4211 bool forWriting) 4212 : 4213 fModelOpener(NULL), 4214 fNode(NULL), 4215 fStreamNode(NULL) 4216 { 4217 if (window->TargetModel() && window->TargetModel()->IsRoot()) { 4218 BDirectory dir; 4219 if (FSGetDeskDir(&dir) == B_OK) { 4220 fNode = new BDirectory(dir); 4221 fStreamNode = new AttributeStreamFileNode(fNode); 4222 } 4223 } else if (window->TargetModel()){ 4224 fModelOpener = new ModelNodeLazyOpener(window->TargetModel(), 4225 forWriting, false); 4226 if (fModelOpener->IsOpen(forWriting)) { 4227 fStreamNode = new AttributeStreamFileNode( 4228 fModelOpener->TargetModel()->Node()); 4229 } 4230 } 4231 } 4232 4233 WindowStateNodeOpener::~WindowStateNodeOpener() 4234 { 4235 delete fModelOpener; 4236 delete fNode; 4237 delete fStreamNode; 4238 } 4239 4240 4241 void 4242 WindowStateNodeOpener::SetTo(const BDirectory* node) 4243 { 4244 delete fModelOpener; 4245 delete fNode; 4246 delete fStreamNode; 4247 4248 fModelOpener = NULL; 4249 fNode = new BDirectory(*node); 4250 fStreamNode = new AttributeStreamFileNode(fNode); 4251 } 4252 4253 4254 void 4255 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting) 4256 { 4257 delete fModelOpener; 4258 delete fNode; 4259 delete fStreamNode; 4260 4261 fModelOpener = NULL; 4262 fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY)); 4263 fStreamNode = new AttributeStreamFileNode(fNode); 4264 } 4265 4266 4267 void 4268 WindowStateNodeOpener::SetTo(Model* model, bool forWriting) 4269 { 4270 delete fModelOpener; 4271 delete fNode; 4272 delete fStreamNode; 4273 4274 fNode = NULL; 4275 fStreamNode = NULL; 4276 fModelOpener = new ModelNodeLazyOpener(model, forWriting, false); 4277 if (fModelOpener->IsOpen(forWriting)) { 4278 fStreamNode = new AttributeStreamFileNode( 4279 fModelOpener->TargetModel()->Node()); 4280 } 4281 } 4282 4283 4284 AttributeStreamNode* 4285 WindowStateNodeOpener::StreamNode() const 4286 { 4287 return fStreamNode; 4288 } 4289 4290 4291 BNode* 4292 WindowStateNodeOpener::Node() const 4293 { 4294 if (!fStreamNode) 4295 return NULL; 4296 4297 if (fNode) 4298 return fNode; 4299 4300 return fModelOpener->TargetModel()->Node(); 4301 } 4302 4303 4304 // #pragma mark - BorderedView 4305 4306 4307 BorderedView::BorderedView() 4308 : 4309 BGroupView(B_VERTICAL, 0), 4310 fEnableBorderHighlight(true) 4311 { 4312 GroupLayout()->SetInsets(1); 4313 } 4314 4315 4316 void 4317 BorderedView::WindowActivated(bool active) 4318 { 4319 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4320 if (window == NULL) 4321 return; 4322 4323 if (window->PoseView()->IsFocus()) 4324 PoseViewFocused(active); // Update border color 4325 } 4326 4327 4328 void BorderedView::EnableBorderHighlight(bool enable) 4329 { 4330 fEnableBorderHighlight = enable; 4331 PoseViewFocused(false); 4332 } 4333 4334 4335 void 4336 BorderedView::PoseViewFocused(bool focused) 4337 { 4338 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4339 if (window == NULL) 4340 return; 4341 4342 color_which base = B_DOCUMENT_BACKGROUND_COLOR; 4343 float tint = B_DARKEN_2_TINT; 4344 if (focused && window->IsActive() && fEnableBorderHighlight) { 4345 base = B_KEYBOARD_NAVIGATION_COLOR; 4346 tint = B_NO_TINT; 4347 } 4348 4349 BScrollBar* hScrollBar = window->PoseView()->HScrollBar(); 4350 if (hScrollBar != NULL) 4351 hScrollBar->SetBorderHighlighted(focused); 4352 4353 BScrollBar* vScrollBar = window->PoseView()->VScrollBar(); 4354 if (vScrollBar != NULL) 4355 vScrollBar->SetBorderHighlighted(focused); 4356 4357 SetViewUIColor(base, tint); 4358 Invalidate(); 4359 } 4360 4361 4362 void 4363 BorderedView::Pulse() 4364 { 4365 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4366 if (window != NULL) 4367 window->PulseTaskLoop(); 4368 } 4369