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