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