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