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