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 BMenu* listViewMenu = new BMenu(B_TRANSLATE("List view")); 2146 2147 message = new BMessage(kListMode); 2148 message->AddInt32("icon_size", B_MINI_ICON); 2149 item = new BMenuItem(listViewMenu, message); 2150 item->SetShortcut('3', B_COMMAND_KEY); 2151 item->SetTarget(PoseView()); 2152 menu->AddItem(item); 2153 2154 message = new BMessage(kListMode); 2155 message->AddInt32("icon_size", B_MINI_ICON); 2156 item = new BMenuItem(B_TRANSLATE("Mini"), message); 2157 item->SetTarget(PoseView()); 2158 listViewMenu->AddItem(item); 2159 2160 message = new BMessage(kListMode); 2161 message->AddInt32("icon_size", B_LARGE_ICON); 2162 item = new BMenuItem(B_TRANSLATE("Large"), message); 2163 item->SetTarget(PoseView()); 2164 listViewMenu->AddItem(item); 2165 2166 listViewMenu->SetTargetForItems(PoseView()); 2167 2168 menu->AddSeparatorItem(); 2169 2170 item = new BMenuItem(B_TRANSLATE("Resize to fit"), 2171 new BMessage(kResizeToFit), 'Y'); 2172 item->SetTarget(this); 2173 menu->AddItem(item); 2174 2175 fArrangeByMenu = new BMenu(B_TRANSLATE("Arrange by")); 2176 menu->AddItem(fArrangeByMenu); 2177 2178 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 2179 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); 2180 item->SetTarget(PoseView()); 2181 menu->AddItem(item); 2182 2183 item = new BMenuItem(B_TRANSLATE("Select all"), 2184 new BMessage(B_SELECT_ALL), 'A'); 2185 item->SetTarget(PoseView()); 2186 menu->AddItem(item); 2187 2188 item = new BMenuItem(B_TRANSLATE("Invert selection"), 2189 new BMessage(kInvertSelection), 'S'); 2190 item->SetTarget(PoseView()); 2191 menu->AddItem(item); 2192 2193 if (!IsTrash()) { 2194 item = new BMenuItem(B_TRANSLATE("Open parent"), 2195 new BMessage(kOpenParentDir), B_UP_ARROW); 2196 item->SetTarget(PoseView()); 2197 menu->AddItem(item); 2198 } 2199 2200 item = new BMenuItem(B_TRANSLATE("Close"), 2201 new BMessage(B_QUIT_REQUESTED), 'W'); 2202 item->SetTarget(this); 2203 menu->AddItem(item); 2204 2205 item = new BMenuItem(B_TRANSLATE("Close all in workspace"), 2206 new BMessage(kCloseAllInWorkspace), 'Q'); 2207 item->SetTarget(be_app); 2208 menu->AddItem(item); 2209 2210 menu->AddSeparatorItem(); 2211 2212 item = new BMenuItem(B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS), 2213 new BMessage(kShowSettingsWindow)); 2214 item->SetTarget(be_app); 2215 menu->AddItem(item); 2216 } 2217 2218 2219 void 2220 BContainerWindow::AddShortcuts() 2221 { 2222 // add equivalents of the menu shortcuts to the menuless desktop window 2223 ASSERT(!IsTrash()); 2224 ASSERT(!PoseView()->IsFilePanel()); 2225 ASSERT(!TargetModel()->IsQuery()); 2226 ASSERT(!TargetModel()->IsVirtualDirectory()); 2227 2228 AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY, 2229 new BMessage(kCutMoreSelectionToClipboard), this); 2230 AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY, 2231 new BMessage(kCopyMoreSelectionToClipboard), this); 2232 AddShortcut('F', B_COMMAND_KEY, 2233 new BMessage(kFindButton), PoseView()); 2234 AddShortcut('N', B_COMMAND_KEY, 2235 new BMessage(kNewFolder), PoseView()); 2236 AddShortcut('O', B_COMMAND_KEY, 2237 new BMessage(kOpenSelection), PoseView()); 2238 AddShortcut('I', B_COMMAND_KEY, 2239 new BMessage(kGetInfo), PoseView()); 2240 AddShortcut('E', B_COMMAND_KEY, 2241 new BMessage(kEditItem), PoseView()); 2242 AddShortcut('D', B_COMMAND_KEY, 2243 new BMessage(kDuplicateSelection), PoseView()); 2244 AddShortcut('T', B_COMMAND_KEY, 2245 new BMessage(kMoveToTrash), PoseView()); 2246 AddShortcut('K', B_COMMAND_KEY, 2247 new BMessage(kCleanup), PoseView()); 2248 AddShortcut('A', B_COMMAND_KEY, 2249 new BMessage(B_SELECT_ALL), PoseView()); 2250 AddShortcut('S', B_COMMAND_KEY, 2251 new BMessage(kInvertSelection), PoseView()); 2252 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, 2253 new BMessage(kShowSelectionWindow), PoseView()); 2254 AddShortcut('G', B_COMMAND_KEY, 2255 new BMessage(kEditQuery), PoseView()); 2256 // it is ok to add a global Edit query shortcut here, PoseView will 2257 // filter out cases where selected pose is not a query 2258 AddShortcut('U', B_COMMAND_KEY, 2259 new BMessage(kUnmountVolume), PoseView()); 2260 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 2261 new BMessage(kOpenParentDir), PoseView()); 2262 AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY, 2263 new BMessage(kOpenSelectionWith), PoseView()); 2264 2265 BMessage* decreaseSize = new BMessage(kIconMode); 2266 decreaseSize->AddInt32("scale", 0); 2267 AddShortcut('-', B_COMMAND_KEY, decreaseSize, PoseView()); 2268 2269 BMessage* increaseSize = new BMessage(kIconMode); 2270 increaseSize->AddInt32("scale", 1); 2271 AddShortcut('+', B_COMMAND_KEY, increaseSize, PoseView()); 2272 } 2273 2274 2275 void 2276 BContainerWindow::MenusBeginning() 2277 { 2278 if (fMenuBar == NULL) 2279 return; 2280 2281 if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) { 2282 // don't commit active pose if only a keyboard shortcut is 2283 // invoked - this would prevent Cut/Copy/Paste from working 2284 fPoseView->CommitActivePose(); 2285 } 2286 2287 // File menu 2288 int32 selectCount = PoseView()->SelectionList()->CountItems(); 2289 2290 SetupOpenWithMenu(fFileMenu); 2291 SetupMoveCopyMenus(selectCount 2292 ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() 2293 : NULL, fFileMenu); 2294 2295 if (TargetModel()->IsRoot()) { 2296 BVolume boot; 2297 BVolumeRoster().GetBootVolume(&boot); 2298 2299 bool ejectableVolumeSelected = false; 2300 2301 int32 count = PoseView()->SelectionList()->CountItems(); 2302 for (int32 index = 0; index < count; index++) { 2303 Model* model 2304 = PoseView()->SelectionList()->ItemAt(index)->TargetModel(); 2305 if (model->IsVolume()) { 2306 BVolume volume; 2307 volume.SetTo(model->NodeRef()->device); 2308 if (volume != boot) { 2309 ejectableVolumeSelected = true; 2310 break; 2311 } 2312 } 2313 } 2314 BMenuItem* item = fMenuBar->FindItem(kUnmountVolume); 2315 if (item != NULL) 2316 item->SetEnabled(ejectableVolumeSelected); 2317 } 2318 2319 UpdateMenu(fMenuBar, kMenuBarContext); 2320 2321 AddMimeTypesToMenu(fAttrMenu); 2322 2323 if (IsPrintersDir()) { 2324 EnableNamedMenuItem(fFileMenu, B_TRANSLATE("Make active printer"), 2325 selectCount == 1); 2326 } 2327 } 2328 2329 2330 void 2331 BContainerWindow::MenusEnded() 2332 { 2333 // when we're done we want to clear nav menus for next time 2334 DeleteSubmenu(fNavigationItem); 2335 DeleteSubmenu(fMoveToItem); 2336 DeleteSubmenu(fCopyToItem); 2337 DeleteSubmenu(fCreateLinkItem); 2338 DeleteSubmenu(fOpenWithItem); 2339 } 2340 2341 2342 void 2343 BContainerWindow::SetupNavigationMenu(const entry_ref* ref, BMenu* parent) 2344 { 2345 // start by removing nav item (and separator) from old menu 2346 if (fNavigationItem != NULL) { 2347 BMenu* menu = fNavigationItem->Menu(); 2348 if (menu != NULL) { 2349 menu->RemoveItem(fNavigationItem); 2350 BMenuItem* item = menu->RemoveItem((int32)0); 2351 ASSERT(item != fNavigationItem); 2352 delete item; 2353 } 2354 } 2355 2356 // if we weren't passed a ref then we're navigating this window 2357 if (ref == NULL) 2358 ref = TargetModel()->EntryRef(); 2359 2360 BEntry entry; 2361 if (entry.SetTo(ref) != B_OK) 2362 return; 2363 2364 // only navigate directories and queries (check for symlink here) 2365 Model model(&entry); 2366 entry_ref resolvedRef; 2367 2368 if (model.InitCheck() != B_OK 2369 || (!model.IsContainer() && !model.IsSymLink())) { 2370 return; 2371 } 2372 2373 if (model.IsSymLink()) { 2374 if (entry.SetTo(model.EntryRef(), true) != B_OK) 2375 return; 2376 2377 Model resolvedModel(&entry); 2378 if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer()) 2379 return; 2380 2381 entry.GetRef(&resolvedRef); 2382 ref = &resolvedRef; 2383 } 2384 2385 if (fNavigationItem == NULL) { 2386 fNavigationItem = new ModelMenuItem(&model, 2387 new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this)); 2388 } 2389 2390 // setup a navigation menu item which will dynamically load items 2391 // as menu items are traversed 2392 BNavMenu* navMenu = dynamic_cast<BNavMenu*>(fNavigationItem->Submenu()); 2393 navMenu->SetNavDir(ref); 2394 fNavigationItem->SetLabel(model.Name()); 2395 fNavigationItem->SetEntry(&entry); 2396 2397 parent->AddItem(fNavigationItem, 0); 2398 parent->AddItem(new BSeparatorItem(), 1); 2399 2400 BMessage* message = new BMessage(B_REFS_RECEIVED); 2401 message->AddRef("refs", ref); 2402 fNavigationItem->SetMessage(message); 2403 fNavigationItem->SetTarget(be_app); 2404 2405 if (!Dragging()) 2406 parent->SetTrackingHook(NULL, NULL); 2407 } 2408 2409 2410 void 2411 BContainerWindow::SetUpEditQueryItem(BMenu* menu) 2412 { 2413 ASSERT(menu); 2414 // File menu 2415 int32 selectCount = PoseView()->SelectionList()->CountItems(); 2416 2417 // add Edit query if appropriate 2418 bool queryInSelection = false; 2419 if (selectCount && selectCount < 100) { 2420 // only do this for a limited number of selected poses 2421 2422 // if any queries selected, add an edit query menu item 2423 for (int32 index = 0; index < selectCount; index++) { 2424 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 2425 Model model(pose->TargetModel()->EntryRef(), true); 2426 if (model.InitCheck() != B_OK) 2427 continue; 2428 2429 if (model.IsQuery() || model.IsQueryTemplate()) { 2430 queryInSelection = true; 2431 break; 2432 } 2433 } 2434 } 2435 2436 bool poseViewIsQuery = TargetModel()->IsQuery(); 2437 // if the view is a query pose view, add edit query menu item 2438 2439 BMenuItem* item = menu->FindItem(kEditQuery); 2440 if (!poseViewIsQuery && !queryInSelection && item != NULL) 2441 item->Menu()->RemoveItem(item); 2442 else if ((poseViewIsQuery || queryInSelection) && item == NULL) { 2443 // add edit query item after Open 2444 item = menu->FindItem(kOpenSelection); 2445 if (item) { 2446 int32 itemIndex = item->Menu()->IndexOf(item); 2447 BMenuItem* query = new BMenuItem(B_TRANSLATE("Edit query"), 2448 new BMessage(kEditQuery), 'G'); 2449 item->Menu()->AddItem(query, itemIndex + 1); 2450 query->SetTarget(PoseView()); 2451 } 2452 } 2453 } 2454 2455 2456 void 2457 BContainerWindow::SetupOpenWithMenu(BMenu* parent) 2458 { 2459 // start by removing nav item (and separator) from old menu 2460 if (fOpenWithItem) { 2461 BMenu* menu = fOpenWithItem->Menu(); 2462 if (menu != NULL) 2463 menu->RemoveItem(fOpenWithItem); 2464 2465 delete fOpenWithItem; 2466 fOpenWithItem = 0; 2467 } 2468 2469 if (PoseView()->SelectionList()->CountItems() == 0) { 2470 // no selection, nothing to open 2471 return; 2472 } 2473 2474 if (TargetModel()->IsRoot()) { 2475 // don't add ourselves if we are root 2476 return; 2477 } 2478 2479 // ToDo: 2480 // check if only item in selection list is the root 2481 // and do not add if true 2482 2483 // add after "Open" 2484 BMenuItem* item = parent->FindItem(kOpenSelection); 2485 2486 int32 count = PoseView()->SelectionList()->CountItems(); 2487 if (count == 0) 2488 return; 2489 2490 // build a list of all refs to open 2491 BMessage message(B_REFS_RECEIVED); 2492 for (int32 index = 0; index < count; index++) { 2493 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 2494 message.AddRef("refs", pose->TargetModel()->EntryRef()); 2495 } 2496 2497 // add Tracker token so that refs received recipients can script us 2498 message.AddMessenger("TrackerViewToken", BMessenger(PoseView())); 2499 2500 int32 index = item->Menu()->IndexOf(item); 2501 fOpenWithItem = new BMenuItem( 2502 new OpenWithMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS), 2503 &message, this, be_app), new BMessage(kOpenSelectionWith)); 2504 fOpenWithItem->SetTarget(PoseView()); 2505 fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY); 2506 2507 item->Menu()->AddItem(fOpenWithItem, index + 1); 2508 } 2509 2510 2511 void 2512 BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu* navMenu, uint32 what, 2513 const entry_ref* ref, bool addLocalOnly) 2514 { 2515 BVolume volume; 2516 BVolumeRoster volumeRoster; 2517 BDirectory directory; 2518 BEntry entry; 2519 BPath path; 2520 Model model; 2521 dev_t device = ref->device; 2522 2523 int32 volumeCount = 0; 2524 2525 navMenu->RemoveItems(0, navMenu->CountItems(), true); 2526 2527 // count persistent writable volumes 2528 volumeRoster.Rewind(); 2529 while (volumeRoster.GetNextVolume(&volume) == B_OK) 2530 if (!volume.IsReadOnly() && volume.IsPersistent()) 2531 volumeCount++; 2532 2533 // add the current folder 2534 if (entry.SetTo(ref) == B_OK 2535 && entry.GetParent(&entry) == B_OK 2536 && model.SetTo(&entry) == B_OK) { 2537 BNavMenu* menu = new BNavMenu(B_TRANSLATE("Current folder"), what, 2538 this); 2539 menu->SetNavDir(model.EntryRef()); 2540 menu->SetShowParent(true); 2541 2542 BMenuItem* item = new SpecialModelMenuItem(&model,menu); 2543 item->SetMessage(new BMessage((uint32)what)); 2544 2545 navMenu->AddItem(item); 2546 } 2547 2548 // add the recent folder menu 2549 // the "Tracker" settings directory is only used to get its icon 2550 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 2551 path.Append("Tracker"); 2552 if (entry.SetTo(path.Path()) == B_OK 2553 && model.SetTo(&entry) == B_OK) { 2554 BMenu* menu = new RecentsMenu(B_TRANSLATE("Recent folders"), 2555 kRecentFolders, what, this); 2556 2557 BMenuItem* item = new SpecialModelMenuItem(&model,menu); 2558 item->SetMessage(new BMessage((uint32)what)); 2559 2560 navMenu->AddItem(item); 2561 } 2562 } 2563 2564 // add Desktop 2565 FSGetBootDeskDir(&directory); 2566 if (directory.InitCheck() == B_OK 2567 && directory.GetEntry(&entry) == B_OK 2568 && model.SetTo(&entry) == B_OK) 2569 navMenu->AddNavDir(&model, what, this, true); 2570 // ask NavMenu to populate submenu for us 2571 2572 // add the home dir 2573 if (find_directory(B_USER_DIRECTORY, &path) == B_OK 2574 && entry.SetTo(path.Path()) == B_OK 2575 && model.SetTo(&entry) == B_OK) 2576 navMenu->AddNavDir(&model, what, this, true); 2577 2578 navMenu->AddSeparatorItem(); 2579 2580 // either add all mounted volumes (for copy), or all the top-level 2581 // directories from the same device (for move) 2582 // ToDo: can be changed if cross-device moves are implemented 2583 2584 if (addLocalOnly || volumeCount < 2) { 2585 // add volume this item lives on 2586 if (volume.SetTo(device) == B_OK 2587 && volume.GetRootDirectory(&directory) == B_OK 2588 && directory.GetEntry(&entry) == B_OK 2589 && model.SetTo(&entry) == B_OK) { 2590 navMenu->AddNavDir(&model, what, this, false); 2591 // do not have submenu populated 2592 2593 navMenu->SetNavDir(model.EntryRef()); 2594 } 2595 } else { 2596 // add all persistent writable volumes 2597 volumeRoster.Rewind(); 2598 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2599 if (volume.IsReadOnly() || !volume.IsPersistent()) 2600 continue; 2601 2602 // add root dir 2603 if (volume.GetRootDirectory(&directory) == B_OK 2604 && directory.GetEntry(&entry) == B_OK 2605 && model.SetTo(&entry) == B_OK) { 2606 navMenu->AddNavDir(&model, what, this, true); 2607 // ask NavMenu to populate submenu for us 2608 } 2609 } 2610 } 2611 } 2612 2613 2614 void 2615 BContainerWindow::SetupMoveCopyMenus(const entry_ref* item_ref, BMenu* parent) 2616 { 2617 if (IsTrash() || InTrash() || IsPrintersDir() || !fMoveToItem 2618 || !fCopyToItem || !fCreateLinkItem || TargetModel()->IsRoot()) { 2619 return; 2620 } 2621 2622 // Grab the modifiers state since we use it twice 2623 uint32 modifierKeys = modifiers(); 2624 2625 // re-parent items to this menu since they're shared 2626 int32 index; 2627 BMenuItem* trash = parent->FindItem(kMoveToTrash); 2628 if (trash) 2629 index = parent->IndexOf(trash) + 2; 2630 else 2631 index = 0; 2632 2633 if (fMoveToItem->Menu() != parent) { 2634 if (fMoveToItem->Menu()) 2635 fMoveToItem->Menu()->RemoveItem(fMoveToItem); 2636 2637 parent->AddItem(fMoveToItem, index++); 2638 } 2639 2640 if (fCopyToItem->Menu() != parent) { 2641 if (fCopyToItem->Menu()) 2642 fCopyToItem->Menu()->RemoveItem(fCopyToItem); 2643 2644 parent->AddItem(fCopyToItem, index++); 2645 } 2646 2647 if (fCreateLinkItem->Menu() != parent) { 2648 if (fCreateLinkItem->Menu()) 2649 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); 2650 2651 parent->AddItem(fCreateLinkItem, index); 2652 } 2653 2654 // Set the "Create Link" item label here so it 2655 // appears correctly when menus are disabled, too. 2656 if (modifierKeys & B_SHIFT_KEY) 2657 fCreateLinkItem->SetLabel(B_TRANSLATE("Create relative link")); 2658 else 2659 fCreateLinkItem->SetLabel(B_TRANSLATE("Create link")); 2660 2661 // only enable once the menus are built 2662 fMoveToItem->SetEnabled(false); 2663 fCopyToItem->SetEnabled(false); 2664 fCreateLinkItem->SetEnabled(false); 2665 2666 // get ref for item which is selected 2667 BEntry entry; 2668 if (entry.SetTo(item_ref) != B_OK) 2669 return; 2670 2671 Model tempModel(&entry); 2672 if (tempModel.InitCheck() != B_OK) 2673 return; 2674 2675 if (tempModel.IsRoot() || tempModel.IsVolume()) 2676 return; 2677 2678 // configure "Move to" menu item 2679 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fMoveToItem->Submenu()), 2680 kMoveSelectionTo, item_ref, true); 2681 2682 // configure "Copy to" menu item 2683 // add all mounted volumes (except the one this item lives on) 2684 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fCopyToItem->Submenu()), 2685 kCopySelectionTo, item_ref, false); 2686 2687 // Set "Create Link" menu item message and 2688 // add all mounted volumes (except the one this item lives on) 2689 if (modifierKeys & B_SHIFT_KEY) { 2690 fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink)); 2691 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*> 2692 (fCreateLinkItem->Submenu()), 2693 kCreateRelativeLink, item_ref, false); 2694 } else { 2695 fCreateLinkItem->SetMessage(new BMessage(kCreateLink)); 2696 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*> 2697 (fCreateLinkItem->Submenu()), 2698 kCreateLink, item_ref, false); 2699 } 2700 2701 fMoveToItem->SetEnabled(true); 2702 fCopyToItem->SetEnabled(true); 2703 fCreateLinkItem->SetEnabled(true); 2704 2705 // Set the "Identify" item label 2706 BMenuItem* identifyItem = parent->FindItem(kIdentifyEntry); 2707 if (identifyItem != NULL) { 2708 if (modifierKeys & B_SHIFT_KEY) { 2709 identifyItem->SetLabel(B_TRANSLATE("Force identify")); 2710 identifyItem->Message()->ReplaceBool("force", true); 2711 } else { 2712 identifyItem->SetLabel(B_TRANSLATE("Identify")); 2713 identifyItem->Message()->ReplaceBool("force", false); 2714 } 2715 } 2716 } 2717 2718 2719 uint32 2720 BContainerWindow::ShowDropContextMenu(BPoint loc) 2721 { 2722 BPoint global(loc); 2723 2724 PoseView()->ConvertToScreen(&global); 2725 PoseView()->CommitActivePose(); 2726 2727 // Change the "Create Link" item - allow user to 2728 // create relative links with the Shift key down. 2729 BMenuItem* item = fDropContextMenu->FindItem(kCreateLink); 2730 if (item == NULL) 2731 item = fDropContextMenu->FindItem(kCreateRelativeLink); 2732 if (item && (modifiers() & B_SHIFT_KEY)) { 2733 item->SetLabel(B_TRANSLATE("Create relative link here")); 2734 item->SetMessage(new BMessage(kCreateRelativeLink)); 2735 } else if (item) { 2736 item->SetLabel(B_TRANSLATE("Create link here")); 2737 item->SetMessage(new BMessage(kCreateLink)); 2738 } 2739 2740 item = fDropContextMenu->Go(global, true, true); 2741 if (item) 2742 return item->Command(); 2743 2744 return 0; 2745 } 2746 2747 2748 void 2749 BContainerWindow::ShowContextMenu(BPoint loc, const entry_ref* ref, BView*) 2750 { 2751 ASSERT(IsLocked()); 2752 BPoint global(loc); 2753 PoseView()->ConvertToScreen(&global); 2754 PoseView()->CommitActivePose(); 2755 2756 if (ref != NULL) { 2757 // clicked on a pose, show file or volume context menu 2758 Model model(ref); 2759 2760 if (model.IsTrash()) { 2761 if (fTrashContextMenu->Window() || Dragging()) 2762 return; 2763 2764 DeleteSubmenu(fNavigationItem); 2765 2766 // selected item was trash, show the trash context menu instead 2767 2768 EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash, 2769 static_cast<TTracker*>(be_app)->TrashFull()); 2770 2771 SetupNavigationMenu(ref, fTrashContextMenu); 2772 fTrashContextMenu->Go(global, true, true, true); 2773 } else { 2774 bool showAsVolume = false; 2775 bool filePanel = PoseView()->IsFilePanel(); 2776 2777 if (Dragging()) { 2778 fContextMenu = NULL; 2779 2780 BEntry entry; 2781 model.GetEntry(&entry); 2782 2783 // only show for directories (directory, volume, root) 2784 // 2785 // don't show a popup for the trash or printers 2786 // trash is handled in DeskWindow 2787 // 2788 // since this menu is opened asynchronously 2789 // we need to make sure we don't open it more 2790 // than once, the IsShowing flag is set in 2791 // SlowContextPopup::AttachedToWindow and 2792 // reset in DetachedFromWindow 2793 // see the notes in SlowContextPopup::AttachedToWindow 2794 2795 if (!FSIsPrintersDir(&entry) 2796 && !fDragContextMenu->IsShowing()) { 2797 //printf("ShowContextMenu - target is %s %i\n", 2798 // ref->name, IsShowing(ref)); 2799 fDragContextMenu->ClearMenu(); 2800 2801 // in case the ref is a symlink, resolve it 2802 // only pop open for directories 2803 BEntry resolvedEntry(ref, true); 2804 if (!resolvedEntry.IsDirectory()) 2805 return; 2806 2807 entry_ref resolvedRef; 2808 resolvedEntry.GetRef(&resolvedRef); 2809 2810 // use the resolved ref for the menu 2811 fDragContextMenu->SetNavDir(&resolvedRef); 2812 fDragContextMenu->SetTypesList(fCachedTypesList); 2813 fDragContextMenu->SetTarget(BMessenger(this)); 2814 BPoseView* poseView = PoseView(); 2815 if (poseView != NULL) { 2816 BMessenger target(poseView); 2817 fDragContextMenu->InitTrackingHook( 2818 &BPoseView::MenuTrackingHook, &target, 2819 fDragMessage); 2820 } 2821 2822 // this is now asynchronous so that we don't 2823 // deadlock in Window::Quit, 2824 fDragContextMenu->Go(global, true, false, true); 2825 } 2826 2827 return; 2828 } else if (TargetModel()->IsRoot() || model.IsVolume()) { 2829 fContextMenu = fVolumeContextMenu; 2830 showAsVolume = true; 2831 } else 2832 fContextMenu = fFileContextMenu; 2833 2834 // clean up items from last context menu 2835 2836 if (fContextMenu != NULL) { 2837 if (fContextMenu->Window()) 2838 return; 2839 else 2840 MenusEnded(); 2841 2842 if (model.InitCheck() == B_OK) { // ??? Do I need this ??? 2843 if (showAsVolume) { 2844 // non-volume enable/disable copy, move, identify 2845 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, 2846 false); 2847 EnableNamedMenuItem(fContextMenu, kMoveToTrash, false); 2848 EnableNamedMenuItem(fContextMenu, kIdentifyEntry, 2849 false); 2850 2851 // volume model, enable/disable the Unmount item 2852 bool ejectableVolumeSelected = false; 2853 2854 BVolume boot; 2855 BVolumeRoster().GetBootVolume(&boot); 2856 BVolume volume; 2857 volume.SetTo(model.NodeRef()->device); 2858 if (volume != boot) 2859 ejectableVolumeSelected = true; 2860 2861 EnableNamedMenuItem(fContextMenu, 2862 B_TRANSLATE("Unmount"), ejectableVolumeSelected); 2863 } 2864 } 2865 2866 SetupNavigationMenu(ref, fContextMenu); 2867 if (!showAsVolume && !filePanel) { 2868 SetupMoveCopyMenus(ref, fContextMenu); 2869 SetupOpenWithMenu(fContextMenu); 2870 } 2871 2872 UpdateMenu(fContextMenu, kPosePopUpContext); 2873 2874 fContextMenu->Go(global, true, true, true); 2875 } 2876 } 2877 } else if (fWindowContextMenu != NULL) { 2878 if (fWindowContextMenu->Window()) 2879 return; 2880 2881 // Repopulate desktop menu if IsDesktop 2882 if (fIsDesktop) 2883 RepopulateMenus(); 2884 2885 MenusEnded(); 2886 2887 // clicked on a window, show window context menu 2888 2889 SetupNavigationMenu(ref, fWindowContextMenu); 2890 UpdateMenu(fWindowContextMenu, kWindowPopUpContext); 2891 2892 fWindowContextMenu->Go(global, true, true, true); 2893 } 2894 2895 fContextMenu = NULL; 2896 } 2897 2898 2899 void 2900 BContainerWindow::AddFileContextMenus(BMenu* menu) 2901 { 2902 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 2903 new BMessage(kOpenSelection), 'O')); 2904 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 2905 new BMessage(kGetInfo), 'I')); 2906 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 2907 new BMessage(kEditItem), 'E')); 2908 2909 if (!IsTrash() && !InTrash() && !IsPrintersDir()) { 2910 menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"), 2911 new BMessage(kDuplicateSelection), 'D')); 2912 } 2913 2914 if (!IsTrash() && !InTrash()) { 2915 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() 2916 ? B_TRANSLATE("Delete") : B_TRANSLATE("Move to Trash"), 2917 new BMessage(kMoveToTrash), 'T')); 2918 if (!IsPrintersDir()) { 2919 // add separator for copy to/move to items (navigation items) 2920 menu->AddSeparatorItem(); 2921 } 2922 } else { 2923 menu->AddItem(new BMenuItem(B_TRANSLATE("Delete"), 2924 new BMessage(kDelete), 0)); 2925 menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"), 2926 new BMessage(kRestoreFromTrash), 0)); 2927 } 2928 2929 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 2930 menu->AddSeparatorItem(); 2931 BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"), 2932 new BMessage(B_CUT), 'X'); 2933 menu->AddItem(cutItem); 2934 BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"), 2935 new BMessage(B_COPY), 'C'); 2936 menu->AddItem(copyItem); 2937 #endif 2938 2939 menu->AddSeparatorItem(); 2940 BMessage* message = new BMessage(kIdentifyEntry); 2941 message->AddBool("force", false); 2942 menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), message)); 2943 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons")); 2944 addOnMenuItem->SetFont(be_plain_font); 2945 menu->AddItem(addOnMenuItem); 2946 2947 // set targets as needed 2948 menu->SetTargetForItems(PoseView()); 2949 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 2950 cutItem->SetTarget(this); 2951 copyItem->SetTarget(this); 2952 #endif 2953 } 2954 2955 2956 void 2957 BContainerWindow::AddVolumeContextMenus(BMenu* menu) 2958 { 2959 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 2960 new BMessage(kOpenSelection), 'O')); 2961 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 2962 new BMessage(kGetInfo), 'I')); 2963 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 2964 new BMessage(kEditItem), 'E')); 2965 2966 menu->AddSeparatorItem(); 2967 menu->AddItem(new MountMenu(B_TRANSLATE("Mount"))); 2968 2969 BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"), 2970 new BMessage(kUnmountVolume), 'U'); 2971 item->SetEnabled(false); 2972 menu->AddItem(item); 2973 2974 menu->AddSeparatorItem(); 2975 menu->AddItem(new BMenu(B_TRANSLATE("Add-ons"))); 2976 2977 menu->SetTargetForItems(PoseView()); 2978 } 2979 2980 2981 void 2982 BContainerWindow::AddWindowContextMenus(BMenu* menu) 2983 { 2984 // create context sensitive menu for empty area of window 2985 // since we check view mode before display, this should be a radio 2986 // mode menu 2987 2988 Model* targetModel = TargetModel(); 2989 ASSERT(targetModel != NULL); 2990 2991 bool needSeparator = true; 2992 if (IsTrash()) { 2993 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"), 2994 new BMessage(kEmptyTrash))); 2995 } else if (IsPrintersDir()) { 2996 menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS), 2997 new BMessage(kAddPrinter), 'N')); 2998 } else if (InTrash() || targetModel->IsRoot()) { 2999 needSeparator = false; 3000 } else { 3001 TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(), 3002 B_TRANSLATE("New")); 3003 menu->AddItem(templatesMenu); 3004 templatesMenu->SetTargetForItems(PoseView()); 3005 templatesMenu->SetFont(be_plain_font); 3006 } 3007 3008 if (needSeparator) 3009 menu->AddSeparatorItem(); 3010 3011 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 3012 BMenuItem* pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'); 3013 menu->AddItem(pasteItem); 3014 menu->AddSeparatorItem(); 3015 #endif 3016 3017 BMenu* arrangeBy = new BMenu(B_TRANSLATE("Arrange by")); 3018 PopulateArrangeByMenu(arrangeBy); 3019 menu->AddItem(arrangeBy); 3020 3021 menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 3022 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); 3023 menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"), 3024 new BMessage(B_SELECT_ALL), 'A')); 3025 if (!IsTrash()) { 3026 menu->AddItem(new BMenuItem(B_TRANSLATE("Open parent"), 3027 new BMessage(kOpenParentDir), B_UP_ARROW)); 3028 } 3029 3030 if (targetModel->IsRoot()) { 3031 menu->AddSeparatorItem(); 3032 menu->AddItem(new MountMenu(B_TRANSLATE("Mount"))); 3033 } 3034 3035 menu->AddSeparatorItem(); 3036 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons")); 3037 addOnMenuItem->SetFont(be_plain_font); 3038 menu->AddItem(addOnMenuItem); 3039 3040 #if DEBUG 3041 menu->AddSeparatorItem(); 3042 BMenuItem* testing = new BMenuItem("Test icon cache", 3043 new BMessage(kTestIconCache)); 3044 menu->AddItem(testing); 3045 #endif 3046 3047 // target items as needed 3048 menu->SetTargetForItems(PoseView()); 3049 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 3050 pasteItem->SetTarget(this); 3051 #endif 3052 } 3053 3054 3055 void 3056 BContainerWindow::AddDropContextMenus(BMenu* menu) 3057 { 3058 menu->AddItem(new BMenuItem(B_TRANSLATE("Create link here"), 3059 new BMessage(kCreateLink))); 3060 menu->AddItem(new BMenuItem(B_TRANSLATE("Move here"), 3061 new BMessage(kMoveSelectionTo))); 3062 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy here"), 3063 new BMessage(kCopySelectionTo))); 3064 menu->AddSeparatorItem(); 3065 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 3066 new BMessage(kCancelButton))); 3067 } 3068 3069 3070 void 3071 BContainerWindow::AddTrashContextMenus(BMenu* menu) 3072 { 3073 // setup special trash context menu 3074 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"), 3075 new BMessage(kEmptyTrash))); 3076 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 3077 new BMessage(kOpenSelection), 'O')); 3078 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 3079 new BMessage(kGetInfo), 'I')); 3080 menu->SetTargetForItems(PoseView()); 3081 } 3082 3083 3084 void 3085 BContainerWindow::EachAddon(bool (*eachAddon)(const Model*, const char*, 3086 uint32 shortcut, uint32 modifiers, bool primary, void* context, 3087 BContainerWindow* window, BMenu* menu), 3088 void* passThru, BStringList& mimeTypes, BMenu* menu) 3089 { 3090 AutoLock<LockingList<AddonShortcut> > lock(fAddonsList); 3091 if (lock.IsLocked()) { 3092 for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) { 3093 struct AddonShortcut* item = fAddonsList->ItemAt(i); 3094 bool primary = false; 3095 3096 if (mimeTypes.CountStrings() > 0) { 3097 BFile file(item->model->EntryRef(), B_READ_ONLY); 3098 if (file.InitCheck() == B_OK) { 3099 BAppFileInfo info(&file); 3100 if (info.InitCheck() == B_OK) { 3101 bool secondary = true; 3102 3103 // does this add-on has types set at all? 3104 BMessage message; 3105 if (info.GetSupportedTypes(&message) == B_OK) { 3106 type_code typeCode; 3107 int32 count; 3108 if (message.GetInfo("types", &typeCode, 3109 &count) == B_OK) { 3110 secondary = false; 3111 } 3112 } 3113 3114 // check all supported types if it has some set 3115 if (!secondary) { 3116 for (int32 i = mimeTypes.CountStrings(); 3117 !primary && i-- > 0;) { 3118 BString type = mimeTypes.StringAt(i); 3119 if (info.IsSupportedType(type.String())) { 3120 BMimeType mimeType(type.String()); 3121 if (info.Supports(&mimeType)) 3122 primary = true; 3123 else 3124 secondary = true; 3125 } 3126 } 3127 } 3128 3129 if (!secondary && !primary) 3130 continue; 3131 } 3132 } 3133 } 3134 ((eachAddon)(item->model, item->model->Name(), item->key, 3135 item->modifiers, primary, passThru, this, menu)); 3136 } 3137 } 3138 } 3139 3140 3141 void 3142 BContainerWindow::BuildMimeTypeList(BStringList& mimeTypes) 3143 { 3144 int32 count = PoseView()->SelectionList()->CountItems(); 3145 if (count <= 0) { 3146 // just add the type of the current directory 3147 AddMimeTypeString(mimeTypes, TargetModel()); 3148 } else { 3149 _UpdateSelectionMIMEInfo(); 3150 for (int32 index = 0; index < count; index++) { 3151 BPose* pose = PoseView()->SelectionList()->ItemAt(index); 3152 AddMimeTypeString(mimeTypes, pose->TargetModel()); 3153 // If it's a symlink, resolves it and add the Target's MimeType 3154 if (pose->TargetModel()->IsSymLink()) { 3155 Model* resolved = new Model( 3156 pose->TargetModel()->EntryRef(), true, true); 3157 if (resolved->InitCheck() == B_OK) 3158 AddMimeTypeString(mimeTypes, resolved); 3159 3160 delete resolved; 3161 } 3162 } 3163 } 3164 } 3165 3166 3167 void 3168 BContainerWindow::BuildAddOnMenu(BMenu* parentMenu) 3169 { 3170 BMenuItem* item = parentMenu->FindItem(B_TRANSLATE("Add-ons")); 3171 if (parentMenu->IndexOf(item) == 0) { 3172 // the folder of the context menu seems to be named "Add-Ons" 3173 // so we just take the last menu item, which is correct if not 3174 // build with debug option 3175 item = parentMenu->ItemAt(parentMenu->CountItems() - 1); 3176 } 3177 if (item == NULL) 3178 return; 3179 3180 BFont font; 3181 parentMenu->GetFont(&font); 3182 3183 BMenu* menu = item->Submenu(); 3184 if (menu == NULL) 3185 return; 3186 3187 menu->SetFont(&font); 3188 3189 // found the addons menu, empty it first 3190 for (;;) { 3191 item = menu->RemoveItem((int32)0); 3192 if (!item) 3193 break; 3194 delete item; 3195 } 3196 3197 BObjectList<BMenuItem> primaryList; 3198 BObjectList<BMenuItem> secondaryList; 3199 BStringList mimeTypes(10); 3200 BuildMimeTypeList(mimeTypes); 3201 3202 AddOneAddonParams params; 3203 params.primaryList = &primaryList; 3204 params.secondaryList = &secondaryList; 3205 3206 // build a list of the MIME types of the selected items 3207 3208 EachAddon(AddOneAddon, ¶ms, mimeTypes, parentMenu); 3209 3210 primaryList.SortItems(CompareLabels); 3211 secondaryList.SortItems(CompareLabels); 3212 3213 int32 count = primaryList.CountItems(); 3214 for (int32 index = 0; index < count; index++) 3215 menu->AddItem(primaryList.ItemAt(index)); 3216 3217 if (count > 0) 3218 menu->AddSeparatorItem(); 3219 3220 count = secondaryList.CountItems(); 3221 for (int32 index = 0; index < count; index++) 3222 menu->AddItem(secondaryList.ItemAt(index)); 3223 3224 menu->SetTargetForItems(this); 3225 } 3226 3227 3228 void 3229 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context) 3230 { 3231 const int32 selectCount = PoseView()->SelectionList()->CountItems(); 3232 const int32 count = PoseView()->CountItems(); 3233 3234 if (context == kMenuBarContext) { 3235 EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0); 3236 EnableNamedMenuItem(menu, kGetInfo, selectCount > 0); 3237 EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0); 3238 EnableNamedMenuItem(menu, kMoveToTrash, selectCount > 0); 3239 EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0); 3240 EnableNamedMenuItem(menu, kDelete, selectCount > 0); 3241 EnableNamedMenuItem(menu, kDuplicateSelection, selectCount > 0); 3242 } 3243 3244 Model* selectedModel = NULL; 3245 if (selectCount == 1) { 3246 selectedModel = PoseView()->SelectionList()->FirstItem()-> 3247 TargetModel(); 3248 } 3249 3250 if (context == kMenuBarContext || context == kPosePopUpContext) { 3251 SetUpEditQueryItem(menu); 3252 EnableNamedMenuItem(menu, kEditItem, selectCount == 1 3253 && (context == kPosePopUpContext || !PoseView()->ActivePose()) 3254 && selectedModel != NULL 3255 && !selectedModel->IsDesktop() 3256 && !selectedModel->IsRoot() 3257 && !selectedModel->IsTrash() 3258 && !selectedModel->HasLocalizedName()); 3259 SetCutItem(menu); 3260 SetCopyItem(menu); 3261 SetPasteItem(menu); 3262 } 3263 3264 if (context == kMenuBarContext || context == kWindowPopUpContext) { 3265 uint32 viewMode = PoseView()->ViewMode(); 3266 3267 BMenu* iconSizeMenu = NULL; 3268 if (BMenuItem* item = menu->FindItem(kIconMode)) 3269 iconSizeMenu = item->Submenu(); 3270 3271 if (iconSizeMenu != NULL) { 3272 if (viewMode == kIconMode) { 3273 int32 iconSize = PoseView()->IconSizeInt(); 3274 BMenuItem* item = iconSizeMenu->ItemAt(0); 3275 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; 3276 i++) { 3277 BMessage* message = item->Message(); 3278 if (message == NULL) { 3279 item->SetMarked(false); 3280 continue; 3281 } 3282 int32 size; 3283 if (message->FindInt32("size", &size) != B_OK) 3284 size = -1; 3285 item->SetMarked(iconSize == size); 3286 } 3287 } else { 3288 BMenuItem* item; 3289 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++) 3290 item->SetMarked(false); 3291 } 3292 } 3293 3294 BMenu* listSizeMenu = NULL; 3295 if (BMenuItem* item = menu->FindItem(kListMode)) 3296 listSizeMenu = item->Submenu(); 3297 3298 if (listSizeMenu != NULL) { 3299 if (viewMode == kListMode) { 3300 int32 iconSize = PoseView()->IconSizeInt(); 3301 BMenuItem* item = listSizeMenu->ItemAt(0); 3302 for (int32 i = 0; (item = listSizeMenu->ItemAt(i)) != NULL; 3303 i++) { 3304 BMessage* message = item->Message(); 3305 if (message == NULL) { 3306 item->SetMarked(false); 3307 continue; 3308 } 3309 int32 size; 3310 if (message->FindInt32("icon_size", &size) != B_OK) 3311 size = -1; 3312 item->SetMarked(iconSize == size); 3313 } 3314 } else { 3315 BMenuItem* item; 3316 for (int32 i = 0; (item = listSizeMenu->ItemAt(i)) != NULL; i++) 3317 item->SetMarked(false); 3318 } 3319 } 3320 3321 MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode); 3322 MarkNamedMenuItem(menu, kListMode, viewMode == kListMode); 3323 MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode); 3324 3325 SetCloseItem(menu); 3326 SetArrangeMenu(menu); 3327 SetPasteItem(menu); 3328 3329 BEntry entry(TargetModel()->EntryRef()); 3330 BDirectory parent; 3331 entry_ref ref; 3332 BEntry root("/"); 3333 3334 bool parentIsRoot = (entry.GetParent(&parent) == B_OK 3335 && parent.GetEntry(&entry) == B_OK 3336 && entry.GetRef(&ref) == B_OK 3337 && entry == root); 3338 3339 EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop() 3340 && !TargetModel()->IsRoot() 3341 && (!parentIsRoot 3342 || TrackerSettings().SingleWindowBrowse() 3343 || TrackerSettings().ShowDisksIcon() 3344 || (modifiers() & B_CONTROL_KEY) != 0)); 3345 3346 EnableNamedMenuItem(menu, kEmptyTrash, count > 0); 3347 EnableNamedMenuItem(menu, B_SELECT_ALL, count > 0); 3348 3349 BMenuItem* item = menu->FindItem(B_TRANSLATE("New")); 3350 if (item != NULL) { 3351 TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>( 3352 item->Submenu()); 3353 if (templatesMenu != NULL) 3354 templatesMenu->UpdateMenuState(); 3355 } 3356 } 3357 3358 BuildAddOnMenu(menu); 3359 } 3360 3361 3362 BMessage* 3363 BContainerWindow::AddOnMessage(int32 what) 3364 { 3365 BMessage* message = new BMessage(what); 3366 3367 // add selected refs to message 3368 BObjectList<BPose>* selectionList = PoseView()->SelectionList(); 3369 3370 int32 index = 0; 3371 BPose* pose; 3372 while ((pose = selectionList->ItemAt(index++)) != NULL) 3373 message->AddRef("refs", pose->TargetModel()->EntryRef()); 3374 3375 message->AddRef("dir_ref", TargetModel()->EntryRef()); 3376 message->AddMessenger("TrackerViewToken", BMessenger(PoseView())); 3377 3378 return message; 3379 } 3380 3381 3382 void 3383 BContainerWindow::LoadAddOn(BMessage* message) 3384 { 3385 UpdateIfNeeded(); 3386 3387 entry_ref addonRef; 3388 status_t result = message->FindRef("refs", &addonRef); 3389 if (result != B_OK) { 3390 BString buffer(B_TRANSLATE("Error %error loading add-On %name.")); 3391 buffer.ReplaceFirst("%error", strerror(result)); 3392 buffer.ReplaceFirst("%name", addonRef.name); 3393 3394 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"), 3395 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 3396 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 3397 alert->Go(); 3398 return; 3399 } 3400 3401 // add selected refs to message 3402 BMessage* refs = AddOnMessage(B_REFS_RECEIVED); 3403 3404 LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs, 3405 addonRef, *TargetModel()->EntryRef()); 3406 } 3407 3408 3409 void 3410 BContainerWindow::_UpdateSelectionMIMEInfo() 3411 { 3412 BPose* pose; 3413 int32 index = 0; 3414 while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) { 3415 BString mimeType(pose->TargetModel()->MimeType()); 3416 if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3417 pose->TargetModel()->Mimeset(true); 3418 if (pose->TargetModel()->IsSymLink()) { 3419 Model* resolved = new Model(pose->TargetModel()->EntryRef(), 3420 true, true); 3421 if (resolved->InitCheck() == B_OK) { 3422 mimeType.SetTo(resolved->MimeType()); 3423 if (!mimeType.Length() 3424 || mimeType.ICompare(B_FILE_MIMETYPE) == 0) { 3425 resolved->Mimeset(true); 3426 } 3427 } 3428 delete resolved; 3429 } 3430 } 3431 } 3432 } 3433 3434 3435 void 3436 BContainerWindow::_AddFolderIcon() 3437 { 3438 if (fMenuBar == NULL) { 3439 // We don't want to add the icon if there's no menubar 3440 return; 3441 } 3442 3443 float iconSize = fMenuBar->Bounds().Height() - 2; 3444 if (iconSize < 16) 3445 iconSize = 16; 3446 3447 fDraggableIcon = new(std::nothrow) DraggableContainerIcon(); 3448 if (fDraggableIcon != NULL) { 3449 BLayoutItem* item = fMenuContainer->GroupLayout()->AddView( 3450 fDraggableIcon); 3451 item->SetExplicitMinSize(BSize(iconSize + 5, iconSize)); 3452 item->SetExplicitMaxSize(BSize(iconSize + 5, item->MaxSize().Height())); 3453 3454 fMenuBar->SetBorders( 3455 BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER); 3456 } 3457 } 3458 3459 3460 void 3461 BContainerWindow::_PassMessageToAddOn(BMessage* message) 3462 { 3463 LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY, 3464 &RunAddOnMessageThread, new BMessage(*message), (void*)NULL); 3465 } 3466 3467 3468 BMenuItem* 3469 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3470 int32 type, float width, int32 align, bool editable, bool statField) 3471 { 3472 return NewAttributeMenuItem(label, name, type, NULL, width, align, 3473 editable, statField); 3474 } 3475 3476 3477 BMenuItem* 3478 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name, 3479 int32 type, const char* displayAs, float width, int32 align, 3480 bool editable, bool statField) 3481 { 3482 BMessage* message = new BMessage(kAttributeItem); 3483 message->AddString("attr_name", name); 3484 message->AddInt32("attr_type", type); 3485 message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type)); 3486 message->AddFloat("attr_width", width); 3487 message->AddInt32("attr_align", align); 3488 if (displayAs != NULL) 3489 message->AddString("attr_display_as", displayAs); 3490 message->AddBool("attr_editable", editable); 3491 message->AddBool("attr_statfield", statField); 3492 3493 BMenuItem* menuItem = new BMenuItem(label, message); 3494 menuItem->SetTarget(PoseView()); 3495 3496 return menuItem; 3497 } 3498 3499 3500 void 3501 BContainerWindow::NewAttributeMenu(BMenu* menu) 3502 { 3503 ASSERT(PoseView()); 3504 3505 BMenuItem* item; 3506 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"), 3507 new BMessage(kCopyAttributes))); 3508 item->SetTarget(PoseView()); 3509 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"), 3510 new BMessage(kPasteAttributes))); 3511 item->SetTarget(PoseView()); 3512 menu->AddSeparatorItem(); 3513 3514 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"), 3515 kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3516 3517 if (gLocalizedNamePreferred) { 3518 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"), 3519 kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true)); 3520 } 3521 3522 menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize, 3523 B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true)); 3524 3525 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"), 3526 kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3527 3528 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"), 3529 kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true)); 3530 3531 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"), 3532 kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false)); 3533 3534 if (IsTrash() || InTrash()) { 3535 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"), 3536 kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false, 3537 false)); 3538 } else { 3539 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath, 3540 B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false)); 3541 } 3542 3543 #ifdef OWNER_GROUP_ATTRIBUTES 3544 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner, 3545 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3546 3547 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup, 3548 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true)); 3549 #endif 3550 3551 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"), 3552 kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true)); 3553 } 3554 3555 3556 void 3557 BContainerWindow::ShowAttributeMenu() 3558 { 3559 ASSERT(fAttrMenu); 3560 fMenuBar->AddItem(fAttrMenu); 3561 } 3562 3563 3564 void 3565 BContainerWindow::HideAttributeMenu() 3566 { 3567 ASSERT(fAttrMenu); 3568 fMenuBar->RemoveItem(fAttrMenu); 3569 } 3570 3571 3572 void 3573 BContainerWindow::MarkAttributeMenu() 3574 { 3575 MarkAttributeMenu(fAttrMenu); 3576 } 3577 3578 3579 void 3580 BContainerWindow::MarkAttributeMenu(BMenu* menu) 3581 { 3582 if (!menu) 3583 return; 3584 3585 int32 count = menu->CountItems(); 3586 for (int32 index = 0; index < count; index++) { 3587 BMenuItem* item = menu->ItemAt(index); 3588 int32 attrHash; 3589 if (item->Message()) { 3590 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) 3591 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); 3592 else 3593 item->SetMarked(false); 3594 } 3595 3596 BMenu* submenu = item->Submenu(); 3597 if (submenu) { 3598 int32 count2 = submenu->CountItems(); 3599 for (int32 subindex = 0; subindex < count2; subindex++) { 3600 item = submenu->ItemAt(subindex); 3601 if (item->Message()) { 3602 if (item->Message()->FindInt32("attr_hash", &attrHash) 3603 == B_OK) { 3604 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) 3605 != 0); 3606 } else 3607 item->SetMarked(false); 3608 } 3609 } 3610 } 3611 } 3612 } 3613 3614 3615 void 3616 BContainerWindow::MarkArrangeByMenu(BMenu* menu) 3617 { 3618 if (!menu) 3619 return; 3620 3621 int32 count = menu->CountItems(); 3622 for (int32 index = 0; index < count; index++) { 3623 BMenuItem* item = menu->ItemAt(index); 3624 if (item->Message()) { 3625 uint32 attrHash; 3626 if (item->Message()->FindInt32("attr_hash", 3627 (int32*)&attrHash) == B_OK) { 3628 item->SetMarked(PoseView()->PrimarySort() == attrHash); 3629 } else if (item->Command() == kArrangeReverseOrder) 3630 item->SetMarked(PoseView()->ReverseSort()); 3631 } 3632 } 3633 } 3634 3635 3636 void 3637 BContainerWindow::AddMimeTypesToMenu() 3638 { 3639 AddMimeTypesToMenu(fAttrMenu); 3640 } 3641 3642 3643 // Adds a menu for a specific MIME type if it doesn't exist already. 3644 // Returns the menu, if it existed or not. 3645 BMenu* 3646 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType, 3647 BMenu* menu, int32 start) 3648 { 3649 AutoLock<BLooper> _(menu->Looper()); 3650 3651 if (!mimeType.IsValid()) 3652 return NULL; 3653 3654 // Check if we already have an entry for this MIME type in the menu. 3655 for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) { 3656 BMessage* message = item->Message(); 3657 if (message == NULL) 3658 continue; 3659 3660 const char* type; 3661 if (message->FindString("mimetype", &type) == B_OK 3662 && !strcmp(mimeType.Type(), type)) { 3663 return item->Submenu(); 3664 } 3665 } 3666 3667 BMessage attrInfo; 3668 char description[B_MIME_TYPE_LENGTH]; 3669 const char* label = mimeType.Type(); 3670 3671 if (!mimeType.IsInstalled()) 3672 return NULL; 3673 3674 // only add things to menu which have "user-visible" data 3675 if (mimeType.GetAttrInfo(&attrInfo) != B_OK) 3676 return NULL; 3677 3678 if (mimeType.GetShortDescription(description) == B_OK && description[0]) 3679 label = description; 3680 3681 // go through each field in meta mime and add it to a menu 3682 BMenu* mimeMenu = NULL; 3683 if (isSuperType) { 3684 // If it is a supertype, we create the menu anyway as it may have 3685 // submenus later on. 3686 mimeMenu = new BMenu(label); 3687 BFont font; 3688 menu->GetFont(&font); 3689 mimeMenu->SetFont(&font); 3690 } 3691 3692 int32 index = -1; 3693 const char* publicName; 3694 while (attrInfo.FindString("attr:public_name", ++index, &publicName) 3695 == B_OK) { 3696 if (!attrInfo.FindBool("attr:viewable", index)) { 3697 // don't add if attribute not viewable 3698 continue; 3699 } 3700 3701 int32 type; 3702 int32 align; 3703 int32 width; 3704 bool editable; 3705 const char* attrName; 3706 if (attrInfo.FindString("attr:name", index, &attrName) != B_OK 3707 || attrInfo.FindInt32("attr:type", index, &type) != B_OK 3708 || attrInfo.FindBool("attr:editable", index, &editable) != B_OK 3709 || attrInfo.FindInt32("attr:width", index, &width) != B_OK 3710 || attrInfo.FindInt32("attr:alignment", index, &align) != B_OK) 3711 continue; 3712 3713 BString displayAs; 3714 attrInfo.FindString("attr:display_as", index, &displayAs); 3715 3716 if (mimeMenu == NULL) { 3717 // do a lazy allocation of the menu 3718 mimeMenu = new BMenu(label); 3719 BFont font; 3720 menu->GetFont(&font); 3721 mimeMenu->SetFont(&font); 3722 } 3723 mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type, 3724 displayAs.String(), width, align, editable, false)); 3725 } 3726 3727 if (mimeMenu == NULL) 3728 return NULL; 3729 3730 BMessage* message = new BMessage(kMIMETypeItem); 3731 message->AddString("mimetype", mimeType.Type()); 3732 menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type())); 3733 3734 return mimeMenu; 3735 } 3736 3737 3738 void 3739 BContainerWindow::AddMimeTypesToMenu(BMenu* menu) 3740 { 3741 if (!menu) 3742 return; 3743 3744 // Remove old mime type menus 3745 int32 start = menu->CountItems(); 3746 while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) { 3747 delete menu->RemoveItem(start - 1); 3748 start--; 3749 } 3750 3751 // Add a separator item if there is none yet 3752 if (start > 0 3753 && dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL) 3754 menu->AddSeparatorItem(); 3755 3756 // Add MIME type in case we're a default query type window 3757 BPath path; 3758 if (TargetModel() != NULL) { 3759 TargetModel()->GetPath(&path); 3760 if (path.InitCheck() == B_OK 3761 && strstr(path.Path(), "/" kQueryTemplates "/") != NULL) { 3762 // demangle MIME type name 3763 BString name(TargetModel()->Name()); 3764 name.ReplaceFirst('_', '/'); 3765 3766 PoseView()->AddMimeType(name.String()); 3767 } 3768 } 3769 3770 // Add MIME type menus 3771 3772 int32 typeCount = PoseView()->CountMimeTypes(); 3773 3774 for (int32 index = 0; index < typeCount; index++) { 3775 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3776 if (mimeType.InitCheck() == B_OK) { 3777 BMimeType superType; 3778 mimeType.GetSupertype(&superType); 3779 if (superType.InitCheck() == B_OK) { 3780 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3781 if (superMenu != NULL) { 3782 // We have a supertype menu. 3783 AddMimeMenu(mimeType, false, superMenu, 0); 3784 } 3785 } 3786 } 3787 } 3788 3789 // remove empty super menus, promote sub-types if needed 3790 3791 for (int32 index = 0; index < typeCount; index++) { 3792 BMimeType mimeType(PoseView()->MimeTypeAt(index)); 3793 BMimeType superType; 3794 mimeType.GetSupertype(&superType); 3795 3796 BMenu* superMenu = AddMimeMenu(superType, true, menu, start); 3797 if (superMenu == NULL) 3798 continue; 3799 3800 int32 itemsFound = 0; 3801 int32 menusFound = 0; 3802 for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) { 3803 if (item->Submenu() != NULL) 3804 menusFound++; 3805 else 3806 itemsFound++; 3807 } 3808 3809 if (itemsFound == 0) { 3810 if (menusFound != 0) { 3811 // promote types to the top level 3812 while (BMenuItem* item = superMenu->RemoveItem((int32)0)) { 3813 menu->AddItem(item); 3814 } 3815 } 3816 3817 menu->RemoveItem(superMenu->Superitem()); 3818 delete superMenu->Superitem(); 3819 } 3820 } 3821 3822 // remove separator if it's the only item in menu 3823 BMenuItem* item = menu->ItemAt(menu->CountItems() - 1); 3824 if (dynamic_cast<BSeparatorItem*>(item) != NULL) { 3825 menu->RemoveItem(item); 3826 delete item; 3827 } 3828 3829 MarkAttributeMenu(menu); 3830 } 3831 3832 3833 BHandler* 3834 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index, 3835 BMessage* specifier, int32 form, const char* property) 3836 { 3837 if (strcmp(property, "Poses") == 0) { 3838 // PRINT(("BContainerWindow::ResolveSpecifier %s\n", property)); 3839 message->PopSpecifier(); 3840 return PoseView(); 3841 } 3842 3843 return _inherited::ResolveSpecifier(message, index, specifier, 3844 form, property); 3845 } 3846 3847 3848 PiggybackTaskLoop* 3849 BContainerWindow::DelayedTaskLoop() 3850 { 3851 if (!fTaskLoop) 3852 fTaskLoop = new PiggybackTaskLoop; 3853 3854 return fTaskLoop; 3855 } 3856 3857 3858 bool 3859 BContainerWindow::NeedsDefaultStateSetup() 3860 { 3861 if (TargetModel() == NULL) 3862 return false; 3863 3864 if (TargetModel()->IsRoot()) { 3865 // don't try to set up anything if we are root 3866 return false; 3867 } 3868 3869 WindowStateNodeOpener opener(this, false); 3870 if (opener.StreamNode() == NULL) { 3871 // can't read state, give up 3872 return false; 3873 } 3874 3875 return !NodeHasSavedState(opener.Node()); 3876 } 3877 3878 3879 bool 3880 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result, 3881 bool createNew, bool createFolder) 3882 { 3883 //PRINT(("looking for default state in tracker settings dir\n")); 3884 BPath settingsPath; 3885 if (FSFindTrackerSettingsDir(&settingsPath) != B_OK) 3886 return false; 3887 3888 BDirectory dir(settingsPath.Path()); 3889 3890 BPath path(settingsPath); 3891 path.Append(name); 3892 if (!BEntry(path.Path()).Exists()) { 3893 if (!createNew) 3894 return false; 3895 3896 BPath tmpPath(settingsPath); 3897 for (;;) { 3898 // deal with several levels of folders 3899 const char* nextSlash = strchr(name, '/'); 3900 if (!nextSlash) 3901 break; 3902 3903 BString tmp; 3904 tmp.SetTo(name, nextSlash - name); 3905 tmpPath.Append(tmp.String()); 3906 3907 mkdir(tmpPath.Path(), 0777); 3908 3909 name = nextSlash + 1; 3910 if (!name[0]) { 3911 // can't deal with a slash at end 3912 return false; 3913 } 3914 } 3915 3916 if (createFolder) { 3917 if (mkdir(path.Path(), 0777) < 0) 3918 return false; 3919 } else { 3920 BFile file; 3921 if (dir.CreateFile(name, &file) != B_OK) 3922 return false; 3923 } 3924 } 3925 3926 //PRINT(("using default state from %s\n", path.Path())); 3927 result->SetTo(path.Path()); 3928 return result->InitCheck() == B_OK; 3929 } 3930 3931 3932 void 3933 BContainerWindow::SetUpDefaultState() 3934 { 3935 BNode defaultingNode; 3936 // this is where we'll ulitimately get the state from 3937 bool gotDefaultingNode = 0; 3938 bool shouldStagger = false; 3939 3940 ASSERT(TargetModel() != NULL); 3941 3942 PRINT(("folder %s does not have any saved state\n", TargetModel()->Name())); 3943 3944 WindowStateNodeOpener opener(this, true); 3945 // this is our destination node, whatever it is for this window 3946 if (opener.StreamNode() == NULL) 3947 return; 3948 3949 if (!TargetModel()->IsRoot()) { 3950 BDirectory deskDir; 3951 FSGetDeskDir(&deskDir); 3952 3953 // try copying state from our parent directory, unless it is the 3954 // desktop folder 3955 BEntry entry(TargetModel()->EntryRef()); 3956 BNode parent; 3957 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK 3958 && parent != deskDir) { 3959 PRINT(("looking at parent for state\n")); 3960 if (NodeHasSavedState(&parent)) { 3961 PRINT(("got state from parent\n")); 3962 defaultingNode = parent; 3963 gotDefaultingNode = true; 3964 // when getting state from parent, stagger the window 3965 shouldStagger = true; 3966 } 3967 } 3968 } 3969 3970 if (!gotDefaultingNode 3971 // parent didn't have any state, use the template directory from 3972 // tracker settings folder for what our state should be 3973 // For simplicity we are not picking up the most recent 3974 // changes that didn't get committed if home is still open in 3975 // a window, that's probably not a problem; would be OK if state 3976 // got committed after every change 3977 && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode, 3978 true)) { 3979 return; 3980 } 3981 3982 if (fIsDesktop) { 3983 // don't copy over the attributes if we are the Desktop 3984 return; 3985 } 3986 3987 // copy over the attributes 3988 3989 // set up a filter of the attributes we want copied 3990 const char* allowAttrs[] = { 3991 kAttrWindowFrame, 3992 kAttrWindowWorkspace, 3993 kAttrViewState, 3994 kAttrViewStateForeign, 3995 kAttrColumns, 3996 kAttrColumnsForeign, 3997 0 3998 }; 3999 4000 // copy over attributes that apply; transform them properly, stripping 4001 // parts that do not apply, adding a window stagger, etc. 4002 4003 StaggerOneParams params; 4004 params.rectFromParent = shouldStagger; 4005 SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame, 4006 OffsetFrameOne, ¶ms); 4007 SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState, 4008 ClearViewOriginOne, ¶ms); 4009 4010 // do it 4011 AttributeStreamMemoryNode memoryNode; 4012 NamesToAcceptAttrFilter filter(allowAttrs); 4013 AttributeStreamFileNode fileNode(&defaultingNode); 4014 4015 *opener.StreamNode() << scrollOriginCleaner << frameOffsetter 4016 << memoryNode << filter << fileNode; 4017 } 4018 4019 4020 void 4021 BContainerWindow::RestoreWindowState(AttributeStreamNode* node) 4022 { 4023 if (node == NULL || fIsDesktop) { 4024 // don't restore any window state if we are the Desktop 4025 return; 4026 } 4027 4028 const char* rectAttributeName; 4029 const char* workspaceAttributeName; 4030 if (TargetModel()->IsRoot()) { 4031 rectAttributeName = kAttrDisksFrame; 4032 workspaceAttributeName = kAttrDisksWorkspace; 4033 } else { 4034 rectAttributeName = kAttrWindowFrame; 4035 workspaceAttributeName = kAttrWindowWorkspace; 4036 } 4037 4038 BRect frame(Frame()); 4039 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 4040 == sizeof(BRect)) { 4041 MoveTo(frame.LeftTop()); 4042 ResizeTo(frame.Width(), frame.Height()); 4043 } else 4044 sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); 4045 4046 fPreviousBounds = Bounds(); 4047 4048 uint32 workspace; 4049 if (((fContainerWindowFlags & kRestoreWorkspace) != 0) 4050 && node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 4051 &workspace) == sizeof(uint32)) 4052 SetWorkspaces(workspace); 4053 4054 if ((fContainerWindowFlags & kIsHidden) != 0) 4055 Minimize(true); 4056 4057 // restore window decor settings 4058 int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE); 4059 if (size > 0) { 4060 char buffer[size]; 4061 if (((fContainerWindowFlags & kRestoreDecor) != 0) 4062 && node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer) 4063 == size) { 4064 BMessage decorSettings; 4065 if (decorSettings.Unflatten(buffer) == B_OK) 4066 SetDecoratorSettings(decorSettings); 4067 } 4068 } 4069 } 4070 4071 4072 void 4073 BContainerWindow::RestoreWindowState(const BMessage& message) 4074 { 4075 if (fIsDesktop) { 4076 // don't restore any window state if we are the Desktop 4077 return; 4078 } 4079 4080 const char* rectAttributeName; 4081 const char* workspaceAttributeName; 4082 if (TargetModel()->IsRoot()) { 4083 rectAttributeName = kAttrDisksFrame; 4084 workspaceAttributeName = kAttrDisksWorkspace; 4085 } else { 4086 rectAttributeName = kAttrWindowFrame; 4087 workspaceAttributeName = kAttrWindowWorkspace; 4088 } 4089 4090 BRect frame(Frame()); 4091 if (message.FindRect(rectAttributeName, &frame) == B_OK) { 4092 MoveTo(frame.LeftTop()); 4093 ResizeTo(frame.Width(), frame.Height()); 4094 } else 4095 sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); 4096 4097 uint32 workspace; 4098 if ((fContainerWindowFlags & kRestoreWorkspace) 4099 && message.FindInt32(workspaceAttributeName, 4100 (int32*)&workspace) == B_OK) { 4101 SetWorkspaces(workspace); 4102 } 4103 4104 if (fContainerWindowFlags & kIsHidden) 4105 Minimize(true); 4106 4107 // restore window decor settings 4108 BMessage decorSettings; 4109 if ((fContainerWindowFlags & kRestoreDecor) 4110 && message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) { 4111 SetDecoratorSettings(decorSettings); 4112 } 4113 } 4114 4115 4116 void 4117 BContainerWindow::SaveWindowState(AttributeStreamNode* node) 4118 { 4119 if (fIsDesktop) { 4120 // don't save window state if we are the Desktop 4121 return; 4122 } 4123 4124 ASSERT(node != NULL); 4125 4126 const char* rectAttributeName; 4127 const char* workspaceAttributeName; 4128 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 4129 rectAttributeName = kAttrDisksFrame; 4130 workspaceAttributeName = kAttrDisksWorkspace; 4131 } else { 4132 rectAttributeName = kAttrWindowFrame; 4133 workspaceAttributeName = kAttrWindowWorkspace; 4134 } 4135 4136 // node is null if it already got deleted 4137 BRect frame(Frame()); 4138 node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame); 4139 4140 uint32 workspaces = Workspaces(); 4141 node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 4142 &workspaces); 4143 4144 BMessage decorSettings; 4145 if (GetDecoratorSettings(&decorSettings) == B_OK) { 4146 int32 size = decorSettings.FlattenedSize(); 4147 char buffer[size]; 4148 if (decorSettings.Flatten(buffer, size) == B_OK) { 4149 node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer); 4150 } 4151 } 4152 } 4153 4154 4155 void 4156 BContainerWindow::SaveWindowState(BMessage& message) const 4157 { 4158 const char* rectAttributeName; 4159 const char* workspaceAttributeName; 4160 4161 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 4162 rectAttributeName = kAttrDisksFrame; 4163 workspaceAttributeName = kAttrDisksWorkspace; 4164 } else { 4165 rectAttributeName = kAttrWindowFrame; 4166 workspaceAttributeName = kAttrWindowWorkspace; 4167 } 4168 4169 // node is null if it already got deleted 4170 BRect frame(Frame()); 4171 message.AddRect(rectAttributeName, frame); 4172 message.AddInt32(workspaceAttributeName, (int32)Workspaces()); 4173 4174 BMessage decorSettings; 4175 if (GetDecoratorSettings(&decorSettings) == B_OK) { 4176 message.AddMessage(kAttrWindowDecor, &decorSettings); 4177 } 4178 } 4179 4180 4181 status_t 4182 BContainerWindow::DragStart(const BMessage* dragMessage) 4183 { 4184 if (dragMessage == NULL) 4185 return B_ERROR; 4186 4187 // if already dragging, or 4188 // if all the refs match 4189 if (Dragging() 4190 && SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) { 4191 return B_OK; 4192 } 4193 4194 // cache the current drag message 4195 // build a list of the mimetypes in the message 4196 SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage, 4197 &fCachedTypesList); 4198 4199 fWaitingForRefs = true; 4200 4201 return B_OK; 4202 } 4203 4204 4205 void 4206 BContainerWindow::DragStop() 4207 { 4208 delete fDragMessage; 4209 fDragMessage = NULL; 4210 4211 delete fCachedTypesList; 4212 fCachedTypesList = NULL; 4213 4214 fWaitingForRefs = false; 4215 } 4216 4217 4218 void 4219 BContainerWindow::ShowSelectionWindow() 4220 { 4221 if (fSelectionWindow == NULL) { 4222 fSelectionWindow = new SelectionWindow(this); 4223 fSelectionWindow->Show(); 4224 } else if (fSelectionWindow->Lock()) { 4225 // The window is already there, just bring it close 4226 fSelectionWindow->MoveCloseToMouse(); 4227 if (fSelectionWindow->IsHidden()) 4228 fSelectionWindow->Show(); 4229 4230 fSelectionWindow->Unlock(); 4231 } 4232 } 4233 4234 4235 void 4236 BContainerWindow::ShowNavigator(bool show) 4237 { 4238 if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory() 4239 || fPoseView->IsFilePanel()) { 4240 return; 4241 } 4242 4243 if (show) { 4244 if (Navigator() && !Navigator()->IsHidden()) 4245 return; 4246 4247 if (Navigator() == NULL) { 4248 fNavigator = new BNavigator(TargetModel()); 4249 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2); 4250 } 4251 4252 if (Navigator()->IsHidden()) 4253 Navigator()->Show(); 4254 4255 if (PoseView()->VScrollBar()) 4256 PoseView()->UpdateScrollRange(); 4257 } else { 4258 if (!Navigator() || Navigator()->IsHidden()) 4259 return; 4260 4261 if (PoseView()->VScrollBar()) 4262 PoseView()->UpdateScrollRange(); 4263 4264 fNavigator->Hide(); 4265 } 4266 } 4267 4268 4269 void 4270 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled) 4271 { 4272 if (PoseView()->IsDesktopWindow()) 4273 return; 4274 4275 if (enabled) { 4276 if (!Navigator()) 4277 return; 4278 4279 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4280 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4281 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4282 4283 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, 4284 new BMessage(kNavigatorCommandBackward), Navigator()); 4285 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, 4286 new BMessage(kNavigatorCommandForward), Navigator()); 4287 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4288 new BMessage(kNavigatorCommandUp), Navigator()); 4289 4290 AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4291 new BMessage(kNavigatorCommandBackward), Navigator()); 4292 AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4293 new BMessage(kNavigatorCommandForward), Navigator()); 4294 AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4295 new BMessage(kNavigatorCommandUp), Navigator()); 4296 AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 4297 new BMessage(kOpenSelection), PoseView()); 4298 AddShortcut('L', B_COMMAND_KEY, 4299 new BMessage(kNavigatorCommandSetFocus), Navigator()); 4300 4301 } else { 4302 RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 4303 RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 4304 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 4305 // This is added again, below, with a new meaning. 4306 4307 RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4308 RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4309 RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 4310 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 4311 // This also changes meaning, added again below. 4312 4313 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4314 new BMessage(kOpenSelection), PoseView()); 4315 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 4316 new BMessage(kOpenParentDir), PoseView()); 4317 // We change the meaning from kNavigatorCommandUp 4318 // to kOpenParentDir. 4319 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 4320 new BMessage(kOpenParentDir), PoseView()); 4321 // command + option results in closing the parent window 4322 RemoveShortcut('L', B_COMMAND_KEY); 4323 } 4324 } 4325 4326 4327 void 4328 BContainerWindow::SetPathWatchingEnabled(bool enable) 4329 { 4330 if (IsPathWatchingEnabled()) { 4331 stop_watching(this); 4332 fIsWatchingPath = false; 4333 } 4334 4335 if (enable) { 4336 if (TargetModel() != NULL) { 4337 BEntry entry; 4338 4339 TargetModel()->GetEntry(&entry); 4340 status_t err; 4341 do { 4342 err = entry.GetParent(&entry); 4343 if (err != B_OK) 4344 break; 4345 4346 char name[B_FILE_NAME_LENGTH]; 4347 entry.GetName(name); 4348 if (strcmp(name, "/") == 0) 4349 break; 4350 4351 node_ref ref; 4352 entry.GetNodeRef(&ref); 4353 watch_node(&ref, B_WATCH_NAME, this); 4354 } while (err == B_OK); 4355 4356 fIsWatchingPath = err == B_OK; 4357 } else 4358 fIsWatchingPath = false; 4359 } 4360 } 4361 4362 4363 void 4364 BContainerWindow::PulseTaskLoop() 4365 { 4366 if (fTaskLoop) 4367 fTaskLoop->PulseMe(); 4368 } 4369 4370 4371 void 4372 BContainerWindow::PopulateArrangeByMenu(BMenu* menu) 4373 { 4374 if (!fAttrMenu || !menu) 4375 return; 4376 // empty fArrangeByMenu... 4377 BMenuItem* item; 4378 while ((item = menu->RemoveItem((int32)0)) != NULL) 4379 delete item; 4380 4381 int32 itemCount = fAttrMenu->CountItems(); 4382 for (int32 i = 0; i < itemCount; i++) { 4383 item = fAttrMenu->ItemAt(i); 4384 if (item->Command() == kAttributeItem) { 4385 BMessage* message = new BMessage(*(item->Message())); 4386 message->what = kArrangeBy; 4387 BMenuItem* newItem = new BMenuItem(item->Label(), message); 4388 newItem->SetTarget(PoseView()); 4389 menu->AddItem(newItem); 4390 } 4391 } 4392 4393 menu->AddSeparatorItem(); 4394 4395 item = new BMenuItem(B_TRANSLATE("Reverse order"), 4396 new BMessage(kArrangeReverseOrder)); 4397 4398 item->SetTarget(PoseView()); 4399 menu->AddItem(item); 4400 menu->AddSeparatorItem(); 4401 4402 4403 item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 4404 'K'); 4405 item->SetTarget(PoseView()); 4406 menu->AddItem(item); 4407 } 4408 4409 4410 // #pragma mark - WindowStateNodeOpener 4411 4412 4413 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window, 4414 bool forWriting) 4415 : 4416 fModelOpener(NULL), 4417 fNode(NULL), 4418 fStreamNode(NULL) 4419 { 4420 if (window->TargetModel() && window->TargetModel()->IsRoot()) { 4421 BDirectory dir; 4422 if (FSGetDeskDir(&dir) == B_OK) { 4423 fNode = new BDirectory(dir); 4424 fStreamNode = new AttributeStreamFileNode(fNode); 4425 } 4426 } else if (window->TargetModel()){ 4427 fModelOpener = new ModelNodeLazyOpener(window->TargetModel(), 4428 forWriting, false); 4429 if (fModelOpener->IsOpen(forWriting)) { 4430 fStreamNode = new AttributeStreamFileNode( 4431 fModelOpener->TargetModel()->Node()); 4432 } 4433 } 4434 } 4435 4436 WindowStateNodeOpener::~WindowStateNodeOpener() 4437 { 4438 delete fModelOpener; 4439 delete fNode; 4440 delete fStreamNode; 4441 } 4442 4443 4444 void 4445 WindowStateNodeOpener::SetTo(const BDirectory* node) 4446 { 4447 delete fModelOpener; 4448 delete fNode; 4449 delete fStreamNode; 4450 4451 fModelOpener = NULL; 4452 fNode = new BDirectory(*node); 4453 fStreamNode = new AttributeStreamFileNode(fNode); 4454 } 4455 4456 4457 void 4458 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting) 4459 { 4460 delete fModelOpener; 4461 delete fNode; 4462 delete fStreamNode; 4463 4464 fModelOpener = NULL; 4465 fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY)); 4466 fStreamNode = new AttributeStreamFileNode(fNode); 4467 } 4468 4469 4470 void 4471 WindowStateNodeOpener::SetTo(Model* model, bool forWriting) 4472 { 4473 delete fModelOpener; 4474 delete fNode; 4475 delete fStreamNode; 4476 4477 fNode = NULL; 4478 fStreamNode = NULL; 4479 fModelOpener = new ModelNodeLazyOpener(model, forWriting, false); 4480 if (fModelOpener->IsOpen(forWriting)) { 4481 fStreamNode = new AttributeStreamFileNode( 4482 fModelOpener->TargetModel()->Node()); 4483 } 4484 } 4485 4486 4487 AttributeStreamNode* 4488 WindowStateNodeOpener::StreamNode() const 4489 { 4490 return fStreamNode; 4491 } 4492 4493 4494 BNode* 4495 WindowStateNodeOpener::Node() const 4496 { 4497 if (!fStreamNode) 4498 return NULL; 4499 4500 if (fNode) 4501 return fNode; 4502 4503 return fModelOpener->TargetModel()->Node(); 4504 } 4505 4506 4507 // #pragma mark - BorderedView 4508 4509 4510 BorderedView::BorderedView() 4511 : 4512 BGroupView(B_VERTICAL, 0), 4513 fEnableBorderHighlight(true) 4514 { 4515 GroupLayout()->SetInsets(1); 4516 } 4517 4518 4519 void 4520 BorderedView::WindowActivated(bool active) 4521 { 4522 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4523 if (window == NULL) 4524 return; 4525 4526 if (window->PoseView()->IsFocus()) 4527 PoseViewFocused(active); // Update border color 4528 } 4529 4530 4531 void BorderedView::EnableBorderHighlight(bool enable) 4532 { 4533 fEnableBorderHighlight = enable; 4534 PoseViewFocused(false); 4535 } 4536 4537 4538 void 4539 BorderedView::PoseViewFocused(bool focused) 4540 { 4541 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4542 if (window == NULL) 4543 return; 4544 4545 color_which base = B_DOCUMENT_BACKGROUND_COLOR; 4546 float tint = B_DARKEN_2_TINT; 4547 if (focused && window->IsActive() && fEnableBorderHighlight) { 4548 base = B_KEYBOARD_NAVIGATION_COLOR; 4549 tint = B_NO_TINT; 4550 } 4551 4552 BScrollBar* hScrollBar = window->PoseView()->HScrollBar(); 4553 if (hScrollBar != NULL) 4554 hScrollBar->SetBorderHighlighted(focused); 4555 4556 BScrollBar* vScrollBar = window->PoseView()->VScrollBar(); 4557 if (vScrollBar != NULL) 4558 vScrollBar->SetBorderHighlighted(focused); 4559 4560 SetViewUIColor(base, tint); 4561 Invalidate(); 4562 } 4563 4564 4565 void 4566 BorderedView::Pulse() 4567 { 4568 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4569 if (window != NULL) 4570 window->PulseTaskLoop(); 4571 } 4572