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