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