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