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