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