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