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