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