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