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