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