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 #include <string.h> 36 #include <stdlib.h> 37 #include <image.h> 38 39 #include <Alert.h> 40 #include <Application.h> 41 #include <AppFileInfo.h> 42 #include <Debug.h> 43 #include <Directory.h> 44 #include <Entry.h> 45 #include <FindDirectory.h> 46 #include <InterfaceDefs.h> 47 #include <MenuItem.h> 48 #include <MenuBar.h> 49 #include <NodeMonitor.h> 50 #include <Path.h> 51 #include <PopUpMenu.h> 52 #include <Screen.h> 53 #include <Volume.h> 54 #include <VolumeRoster.h> 55 #include <Roster.h> 56 57 #include <fs_attr.h> 58 59 #include <memory> 60 61 #include "Attributes.h" 62 #include "AttributeStream.h" 63 #include "AutoLock.h" 64 #include "BackgroundImage.h" 65 #include "Commands.h" 66 #include "ContainerWindow.h" 67 #include "DeskWindow.h" 68 #include "FavoritesMenu.h" 69 #include "FindPanel.h" 70 #include "FSClipboard.h" 71 #include "FSUndoRedo.h" 72 #include "FSUtils.h" 73 #include "IconMenuItem.h" 74 #include "OpenWithWindow.h" 75 #include "MimeTypes.h" 76 #include "Model.h" 77 #include "MountMenu.h" 78 #include "Navigator.h" 79 #include "NavMenu.h" 80 #include "PoseView.h" 81 #include "SelectionWindow.h" 82 #include "TitleView.h" 83 #include "Tracker.h" 84 #include "TrackerSettings.h" 85 #include "Thread.h" 86 #include "TemplatesMenu.h" 87 88 89 const uint32 kRedo = 'REDO'; 90 // this is the same as B_REDO in Dano/Zeta/OpenBeOS 91 92 93 #if !B_BEOS_VERSION_DANO 94 _IMPEXP_BE 95 #endif 96 void do_minimize_team(BRect zoomRect, team_id team, bool zoom); 97 98 // Amount you have to move the mouse before a drag starts 99 const float kDragSlop = 3.0f; 100 101 namespace BPrivate { 102 103 const char *kAddOnsMenuName = "Add-Ons"; 104 105 class DraggableContainerIcon : public BView { 106 public: 107 DraggableContainerIcon(BRect rect, const char *name, uint32 resizeMask); 108 109 virtual void AttachedToWindow(); 110 virtual void MouseDown(BPoint where); 111 virtual void MouseUp(BPoint where); 112 virtual void MouseMoved(BPoint point, uint32 /*transit*/, const BMessage *message); 113 virtual void Draw(BRect updateRect); 114 115 private: 116 uint32 fDragButton; 117 BPoint fClickPoint; 118 bool fDragStarted; 119 }; 120 121 } // namespace BPrivate 122 123 struct AddOneAddonParams { 124 BObjectList<BMenuItem> *primaryList; 125 BObjectList<BMenuItem> *secondaryList; 126 }; 127 128 struct StaggerOneParams { 129 bool rectFromParent; 130 }; 131 132 const int32 kContainerWidthMinLimit = 120; 133 const int32 kContainerWindowHeightLimit = 85; 134 135 const int32 kWindowStaggerBy = 17; 136 137 BRect BContainerWindow::sNewWindRect(85, 50, 415, 280); 138 139 140 namespace BPrivate { 141 142 filter_result 143 ActivateWindowFilter(BMessage *, BHandler **target, BMessageFilter *) 144 { 145 BView *view = dynamic_cast<BView*>(*target); 146 147 // activate the window if no PoseView or DraggableContainerIcon had been pressed 148 // (those will activate the window themselves, if necessary) 149 if (view 150 && !dynamic_cast<BPoseView*>(view) 151 && !dynamic_cast<DraggableContainerIcon*>(view) 152 && view->Window()) 153 view->Window()->Activate(true); 154 155 return B_DISPATCH_MESSAGE; 156 } 157 158 159 static void 160 StripShortcut(const Model *model, char *result, uint32 &shortcut) 161 { 162 strcpy(result, model->Name()); 163 164 // check if there is a shortcut 165 uint32 length = strlen(result); 166 shortcut = '\0'; 167 if (result[length - 2] == '-') { 168 shortcut = result[length - 1]; 169 result[length - 2] = '\0'; 170 } 171 } 172 173 174 static const Model * 175 MatchOne(const Model *model, void *castToName) 176 { 177 char buffer[B_FILE_NAME_LENGTH]; 178 uint32 dummy; 179 StripShortcut(model, buffer, dummy); 180 181 if (strcmp(buffer, (const char *)castToName) == 0) 182 // found match, bail out 183 return model; 184 185 return 0; 186 } 187 188 189 int 190 CompareLabels(const BMenuItem *item1, const BMenuItem *item2) 191 { 192 return strcasecmp(item1->Label(), item2->Label()); 193 } 194 195 } // namespace BPrivate 196 197 198 static bool 199 AddOneAddon(const Model *model, const char *name, uint32 shortcut, bool primary, void *context) 200 { 201 AddOneAddonParams *params = (AddOneAddonParams *)context; 202 203 BMessage *message = new BMessage(kLoadAddOn); 204 message->AddRef("refs", model->EntryRef()); 205 206 ModelMenuItem *item = new ModelMenuItem(model, name, message, 207 (char)shortcut, B_OPTION_KEY); 208 209 if (primary) 210 params->primaryList->AddItem(item); 211 else 212 params->secondaryList->AddItem(item); 213 214 return false; 215 } 216 217 218 static int32 219 AddOnThread(BMessage *refsMessage, entry_ref addonRef, entry_ref dirRef) 220 { 221 std::auto_ptr<BMessage> refsMessagePtr(refsMessage); 222 223 BEntry entry(&addonRef); 224 BPath path; 225 status_t result = entry.InitCheck(); 226 if (result == B_OK) 227 result = entry.GetPath(&path); 228 229 if (result == B_OK) { 230 image_id addonImage = load_add_on(path.Path()); 231 if (addonImage >= 0) { 232 void (*processRefs)(entry_ref, BMessage *, void *); 233 result = get_image_symbol(addonImage, "process_refs", 2, (void **)&processRefs); 234 235 #ifndef __INTEL__ 236 if (result < 0) { 237 PRINT(("trying old legacy ppc signature\n")); 238 // try old-style addon signature 239 result = get_image_symbol(addonImage, 240 "process_refs__F9entry_refP8BMessagePv", 2, (void **)&processRefs); 241 } 242 #endif 243 244 if (result >= 0) { 245 246 // call add-on code 247 (*processRefs)(dirRef, refsMessagePtr.get(), 0); 248 249 unload_add_on(addonImage); 250 return B_OK; 251 } else 252 PRINT(("couldn't find process_refs\n")); 253 254 unload_add_on(addonImage); 255 } 256 } 257 258 char buffer[1024]; 259 sprintf(buffer, "Error %s loading Add-On %s.", strerror(result), addonRef.name); 260 261 BAlert *alert = new BAlert("", buffer, "Cancel", 0, 0, 262 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 263 alert->SetShortcut(0, B_ESCAPE); 264 alert->Go(); 265 266 return result; 267 } 268 269 270 static bool 271 NodeHasSavedState(const BNode *node) 272 { 273 attr_info info; 274 return node->GetAttrInfo(kAttrWindowFrame, &info) == B_OK; 275 } 276 277 278 static bool 279 OffsetFrameOne(const char *DEBUG_ONLY(name), uint32, off_t, void *castToRect, 280 void *castToParams) 281 { 282 ASSERT(strcmp(name, kAttrWindowFrame) == 0); 283 StaggerOneParams *params = (StaggerOneParams *)castToParams; 284 285 if (!params->rectFromParent) 286 return false; 287 288 if (!castToRect) 289 return false; 290 291 ((BRect *)castToRect)->OffsetBy(kWindowStaggerBy, kWindowStaggerBy); 292 return true; 293 } 294 295 296 static void 297 AddMimeTypeString(BObjectList<BString> &list, Model *model) 298 { 299 BString *mimeType = new BString(model->MimeType()); 300 if (!mimeType->Length() || !mimeType->ICompare(B_FILE_MIMETYPE)) { 301 // if model is of unknown type, try mimeseting it first 302 model->Mimeset(true); 303 mimeType->SetTo(model->MimeType()); 304 } 305 306 if (mimeType->Length()) { 307 // only add the type if it's not already there 308 for (int32 i = list.CountItems(); i-- > 0;) { 309 BString *string = list.ItemAt(i); 310 if (string != NULL && !string->ICompare(*mimeType)) 311 return; 312 } 313 list.AddItem(mimeType); 314 } 315 } 316 317 318 // #pragma mark - 319 320 321 DraggableContainerIcon::DraggableContainerIcon(BRect rect, const char *name, 322 uint32 resizeMask) 323 : BView(rect, name, resizeMask, B_WILL_DRAW), 324 fDragButton(0), 325 fDragStarted(false) 326 { 327 } 328 329 330 void 331 DraggableContainerIcon::AttachedToWindow() 332 { 333 //SetViewColor(B_TRANSPARENT_COLOR); 334 SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); 335 } 336 337 338 void 339 DraggableContainerIcon::MouseDown(BPoint point) 340 { 341 // we only like container windows 342 BContainerWindow *window = dynamic_cast<BContainerWindow *>(Window()); 343 if (window == NULL) 344 return; 345 346 // we don't like the Trash icon (because it cannot be moved) 347 if (window->IsTrash() || window->IsPrintersDir()) 348 return; 349 350 uint32 buttons; 351 window->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 352 353 if (IconCache::sIconCache->IconHitTest(point, window->TargetModel(), 354 kNormalIcon, B_MINI_ICON)) { 355 // The click hit the icon, initiate a drag 356 fDragButton = buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON); 357 fDragStarted = false; 358 fClickPoint = point; 359 } else 360 fDragButton = 0; 361 362 if (!fDragButton) 363 Window()->Activate(true); 364 } 365 366 367 void 368 DraggableContainerIcon::MouseUp(BPoint /*point*/) 369 { 370 if (!fDragStarted) 371 Window()->Activate(true); 372 373 fDragButton = 0; 374 fDragStarted = false; 375 } 376 377 378 void 379 DraggableContainerIcon::MouseMoved(BPoint point, uint32 /*transit*/, const BMessage */*message*/) 380 { 381 if (fDragButton == 0 || fDragStarted 382 || (abs((int32)(point.x - fClickPoint.x)) <= kDragSlop 383 && abs((int32)(point.y - fClickPoint.y)) <= kDragSlop)) 384 return; 385 386 BContainerWindow *window = static_cast<BContainerWindow *>(Window()); 387 // we can only get here in a BContainerWindow 388 Model *model = window->TargetModel(); 389 390 // Find the required height 391 BFont font; 392 GetFont(&font); 393 394 font_height fontHeight; 395 font.GetHeight(&fontHeight); 396 float height = fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2 397 + Bounds().Height() + 8; 398 399 BRect rect(0, 0, max_c(Bounds().Width(), font.StringWidth(model->Name()) + 4), height); 400 BBitmap *dragBitmap = new BBitmap(rect, B_RGBA32, true); 401 402 dragBitmap->Lock(); 403 BView *view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0); 404 dragBitmap->AddChild(view); 405 view->SetOrigin(0, 0); 406 BRect clipRect(view->Bounds()); 407 BRegion newClip; 408 newClip.Set(clipRect); 409 view->ConstrainClippingRegion(&newClip); 410 411 // Transparent draw magic 412 view->SetHighColor(0, 0, 0, 0); 413 view->FillRect(view->Bounds()); 414 view->SetDrawingMode(B_OP_ALPHA); 415 view->SetHighColor(0, 0, 0, 128); // set the level of transparency by value 416 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 417 418 // Draw the icon 419 float hIconOffset = (rect.Width() - Bounds().Width()) / 2; 420 IconCache::sIconCache->Draw(model, view, BPoint(hIconOffset, 0), 421 kNormalIcon, B_MINI_ICON, true); 422 423 // See if we need to truncate the string 424 BString nameString = model->Name(); 425 if (view->StringWidth(model->Name()) > rect.Width()) 426 view->TruncateString(&nameString, B_TRUNCATE_END, rect.Width() - 5); 427 428 // Draw the label 429 float leftText = (view->StringWidth(nameString.String()) - Bounds().Width()) / 2; 430 view->MovePenTo(BPoint(hIconOffset - leftText + 2, Bounds().Height() + (fontHeight.ascent + 2))); 431 view->DrawString(nameString.String()); 432 433 view->Sync(); 434 dragBitmap->Unlock(); 435 436 BMessage message(B_SIMPLE_DATA); 437 message.AddRef("refs", model->EntryRef()); 438 message.AddPoint("click_pt", fClickPoint); 439 440 BPoint tmpLoc; 441 uint32 button; 442 GetMouse(&tmpLoc, &button); 443 if (button) 444 message.AddInt32("buttons", (int32)button); 445 446 if (button & B_PRIMARY_MOUSE_BUTTON) { 447 // add an action specifier to the message, so that it is not copied 448 message.AddInt32("be:actions", 449 (modifiers() & B_OPTION_KEY) != 0 ? B_COPY_TARGET : B_MOVE_TARGET); 450 } 451 452 fDragStarted = true; 453 fDragButton = 0; 454 455 DragMessage(&message, dragBitmap, B_OP_ALPHA, 456 BPoint(fClickPoint.x + hIconOffset, fClickPoint.y), this); 457 } 458 459 460 void 461 DraggableContainerIcon::Draw(BRect /*updateRect*/) 462 { 463 BContainerWindow *window = dynamic_cast<BContainerWindow *>(Window()); 464 if (window == NULL) 465 return; 466 467 // Draw the icon, straddling the border 468 SetDrawingMode(B_OP_OVER); 469 float iconOffset = (Bounds().Width() - B_MINI_ICON) / 2; 470 IconCache::sIconCache->Draw(window->TargetModel(), this, 471 BPoint(iconOffset, iconOffset), kNormalIcon, B_MINI_ICON, true); 472 } 473 474 475 // #pragma mark - 476 477 478 BContainerWindow::BContainerWindow(LockingList<BWindow> *list, 479 uint32 containerWindowFlags, 480 window_look look, window_feel feel, uint32 flags, uint32 workspace) 481 : BWindow(InitialWindowRect(feel), "TrackerWindow", look, feel, flags, 482 workspace), 483 fFileContextMenu(NULL), 484 fWindowContextMenu(NULL), 485 fDropContextMenu(NULL), 486 fVolumeContextMenu(NULL), 487 fDragContextMenu(NULL), 488 fMoveToItem(NULL), 489 fCopyToItem(NULL), 490 fCreateLinkItem(NULL), 491 fOpenWithItem(NULL), 492 fNavigationItem(NULL), 493 fMenuBar(NULL), 494 fNavigator(NULL), 495 fPoseView(NULL), 496 fWindowList(list), 497 fAttrMenu(NULL), 498 fWindowMenu(NULL), 499 fFileMenu(NULL), 500 fSelectionWindow(NULL), 501 fTaskLoop(NULL), 502 fIsTrash(false), 503 fInTrash(false), 504 fIsPrinters(false), 505 fContainerWindowFlags(containerWindowFlags), 506 fBackgroundImage(NULL), 507 fSavedZoomRect(0, 0, -1, -1), 508 fContextMenu(NULL), 509 fDragMessage(NULL), 510 fCachedTypesList(NULL), 511 fStateNeedsSaving(false), 512 fSaveStateIsEnabled(true), 513 fIsWatchingPath(false) 514 { 515 InitIconPreloader(); 516 517 if (list) { 518 ASSERT(list->IsLocked()); 519 list->AddItem(this); 520 } 521 522 AddCommonFilter(new BMessageFilter(B_MOUSE_DOWN, ActivateWindowFilter)); 523 524 Run(); 525 526 // Watch out for settings changes: 527 if (TTracker *app = dynamic_cast<TTracker*>(be_app)) { 528 app->Lock(); 529 app->StartWatching(this, kWindowsShowFullPathChanged); 530 app->StartWatching(this, kSingleWindowBrowseChanged); 531 app->StartWatching(this, kShowNavigatorChanged); 532 app->StartWatching(this, kDontMoveFilesToTrashChanged); 533 app->Unlock(); 534 } 535 536 // ToDo: remove me once we have undo/redo menu items 537 // (that is, move them to AddShortcuts()) 538 AddShortcut('Z', B_COMMAND_KEY, new BMessage(B_UNDO), this); 539 AddShortcut('Z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kRedo), this); 540 } 541 542 543 BContainerWindow::~BContainerWindow() 544 { 545 ASSERT(IsLocked()); 546 547 // stop the watchers 548 if (TTracker *app = dynamic_cast<TTracker*>(be_app)) { 549 app->Lock(); 550 app->StopWatching(this, kWindowsShowFullPathChanged); 551 app->StopWatching(this, kSingleWindowBrowseChanged); 552 app->StopWatching(this, kShowNavigatorChanged); 553 app->StopWatching(this, kDontMoveFilesToTrashChanged); 554 app->Unlock(); 555 } 556 557 delete fTaskLoop; 558 delete fBackgroundImage; 559 delete fDragMessage; 560 delete fCachedTypesList; 561 562 if (fSelectionWindow && fSelectionWindow->Lock()) 563 fSelectionWindow->Quit(); 564 } 565 566 567 BRect 568 BContainerWindow::InitialWindowRect(window_feel feel) 569 { 570 if (feel != kPrivateDesktopWindowFeel) 571 return sNewWindRect; 572 573 // do not offset desktop window 574 BRect result = sNewWindRect; 575 result.OffsetTo(0, 0); 576 return result; 577 } 578 579 580 void 581 BContainerWindow::Minimize(bool minimize) 582 { 583 if (minimize && (modifiers() & B_OPTION_KEY) != 0) 584 do_minimize_team(BRect(0, 0, 0, 0), be_app->Team(), true); 585 else 586 _inherited::Minimize(minimize); 587 } 588 589 590 bool 591 BContainerWindow::QuitRequested() 592 { 593 // this is a response to the DeskBar sending us a B_QUIT, when it really 594 // means to say close all your windows. It might be better to have it 595 // send a kCloseAllWindows message and have windowless apps stay running, 596 // which is what we will do for the Tracker 597 if (CurrentMessage() 598 && (CurrentMessage()->FindInt32("modifiers") & B_CONTROL_KEY)) 599 be_app->PostMessage(kCloseAllWindows); 600 601 Hide(); 602 // this will close the window instantly, even if 603 // the file system is very busy right now 604 return true; 605 } 606 607 608 void 609 BContainerWindow::Quit() 610 { 611 // get rid of context menus 612 if (fNavigationItem) { 613 BMenu *menu = fNavigationItem->Menu(); 614 if (menu) 615 menu->RemoveItem(fNavigationItem); 616 delete fNavigationItem; 617 fNavigationItem = NULL; 618 } 619 620 if (fOpenWithItem && !fOpenWithItem->Menu()) { 621 delete fOpenWithItem; 622 fOpenWithItem = NULL; 623 } 624 625 if (fMoveToItem && !fMoveToItem->Menu()) { 626 delete fMoveToItem; 627 fMoveToItem = NULL; 628 } 629 630 if (fCopyToItem && !fCopyToItem->Menu()) { 631 delete fCopyToItem; 632 fCopyToItem = NULL; 633 } 634 635 if (fCreateLinkItem && !fCreateLinkItem->Menu()) { 636 delete fCreateLinkItem; 637 fCreateLinkItem = NULL; 638 } 639 640 if (fAttrMenu && !fAttrMenu->Supermenu()) { 641 delete fAttrMenu; 642 fAttrMenu = NULL; 643 } 644 645 delete fFileContextMenu; 646 fFileContextMenu = NULL; 647 delete fWindowContextMenu; 648 fWindowContextMenu = NULL; 649 delete fDropContextMenu; 650 fDropContextMenu = NULL; 651 delete fVolumeContextMenu; 652 fVolumeContextMenu = NULL; 653 delete fDragContextMenu; 654 fDragContextMenu = NULL; 655 656 int32 windowCount = 0; 657 658 // This is a deadlock code sequence - need to change this 659 // to acquire the window list while this container window is unlocked 660 if (fWindowList) { 661 AutoLock<LockingList<BWindow> > lock(fWindowList); 662 if (lock.IsLocked()) { 663 fWindowList->RemoveItem(this); 664 windowCount = fWindowList->CountItems(); 665 } 666 } 667 668 if (StateNeedsSaving()) 669 SaveState(); 670 671 if (fWindowList && windowCount == 0) 672 be_app->PostMessage(B_QUIT_REQUESTED); 673 674 _inherited::Quit(); 675 } 676 677 678 BPoseView * 679 BContainerWindow::NewPoseView(Model *model, BRect rect, uint32 viewMode) 680 { 681 return new BPoseView(model, rect, viewMode); 682 } 683 684 685 void 686 BContainerWindow::UpdateIfTrash(Model *model) 687 { 688 BEntry entry(model->EntryRef()); 689 690 if (entry.InitCheck() == B_OK) { 691 fIsTrash = FSIsTrashDir(&entry); 692 fInTrash = FSInTrashDir(model->EntryRef()); 693 fIsPrinters = FSIsPrintersDir(&entry); 694 } 695 } 696 697 698 void 699 BContainerWindow::CreatePoseView(Model *model) 700 { 701 UpdateIfTrash(model); 702 BRect rect(Bounds()); 703 704 TrackerSettings settings; 705 if (settings.SingleWindowBrowse() 706 && settings.ShowNavigator() 707 && model->IsDirectory()) 708 rect.top += BNavigator::CalcNavigatorHeight() + 1; 709 710 rect.right -= B_V_SCROLL_BAR_WIDTH; 711 rect.bottom -= B_H_SCROLL_BAR_HEIGHT; 712 fPoseView = NewPoseView(model, rect, kListMode); 713 AddChild(fPoseView); 714 715 if (settings.SingleWindowBrowse() 716 && model->IsDirectory() 717 && !fPoseView->IsFilePanel()) { 718 BRect rect(Bounds()); 719 rect.top = 0; 720 // The KeyMenuBar isn't attached yet, otherwise we'd use that to get the offset. 721 rect.bottom = BNavigator::CalcNavigatorHeight(); 722 fNavigator = new BNavigator(model, rect); 723 if (!settings.ShowNavigator()) 724 fNavigator->Hide(); 725 AddChild(fNavigator); 726 } 727 SetPathWatchingEnabled(settings.ShowNavigator() || settings.ShowFullPathInTitleBar()); 728 } 729 730 731 void 732 BContainerWindow::AddContextMenus() 733 { 734 // create context sensitive menus 735 fFileContextMenu = new BPopUpMenu("FileContext", false, false); 736 fFileContextMenu->SetFont(be_plain_font); 737 AddFileContextMenus(fFileContextMenu); 738 739 fVolumeContextMenu = new BPopUpMenu("VolumeContext", false, false); 740 fVolumeContextMenu->SetFont(be_plain_font); 741 AddVolumeContextMenus(fVolumeContextMenu); 742 743 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); 744 fWindowContextMenu->SetFont(be_plain_font); 745 AddWindowContextMenus(fWindowContextMenu); 746 747 fDropContextMenu = new BPopUpMenu("DropContext", false, false); 748 fDropContextMenu->SetFont(be_plain_font); 749 AddDropContextMenus(fDropContextMenu); 750 751 fDragContextMenu = new BSlowContextMenu("DragContext"); 752 // will get added and built dynamically in ShowContextMenu 753 } 754 755 756 void 757 BContainerWindow::RepopulateMenus() 758 { 759 // Avoid these menus to be destroyed: 760 if (fMoveToItem && fMoveToItem->Menu()) 761 fMoveToItem->Menu()->RemoveItem(fMoveToItem); 762 763 if (fCopyToItem && fCopyToItem->Menu()) 764 fCopyToItem->Menu()->RemoveItem(fCopyToItem); 765 766 if (fCreateLinkItem && fCreateLinkItem->Menu()) 767 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); 768 769 if (fOpenWithItem && fOpenWithItem->Menu()) { 770 fOpenWithItem->Menu()->RemoveItem(fOpenWithItem); 771 delete fOpenWithItem; 772 fOpenWithItem = NULL; 773 } 774 775 if (fNavigationItem) { 776 BMenu *menu = fNavigationItem->Menu(); 777 if (menu) { 778 menu->RemoveItem(fNavigationItem); 779 BMenuItem *item = menu->RemoveItem((int32)0); 780 ASSERT(item != fNavigationItem); 781 delete item; 782 } 783 } 784 785 delete fFileContextMenu; 786 fFileContextMenu = new BPopUpMenu("FileContext", false, false); 787 fFileContextMenu->SetFont(be_plain_font); 788 AddFileContextMenus(fFileContextMenu); 789 790 delete fWindowContextMenu; 791 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); 792 fWindowContextMenu->SetFont(be_plain_font); 793 AddWindowContextMenus(fWindowContextMenu); 794 795 fMenuBar->RemoveItem(fFileMenu); 796 delete fFileMenu; 797 fFileMenu = new BMenu("File"); 798 AddFileMenu(fFileMenu); 799 fMenuBar->AddItem(fFileMenu); 800 801 fMenuBar->RemoveItem(fWindowMenu); 802 delete fWindowMenu; 803 fWindowMenu = new BMenu("Window"); 804 fMenuBar->AddItem(fWindowMenu); 805 AddWindowMenu(fWindowMenu); 806 807 // just create the attribute, decide to add it later 808 fMenuBar->RemoveItem(fAttrMenu); 809 delete fAttrMenu; 810 fAttrMenu = new BMenu("Attributes"); 811 NewAttributeMenu(fAttrMenu); 812 if (PoseView()->ViewMode() == kListMode) 813 ShowAttributeMenu(); 814 815 int32 selectCount = PoseView()->SelectionList()->CountItems(); 816 817 SetupOpenWithMenu(fFileMenu); 818 SetupMoveCopyMenus(selectCount 819 ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() : NULL, 820 fFileMenu); 821 } 822 823 824 void 825 BContainerWindow::Init(const BMessage *message) 826 { 827 float y_delta; 828 BEntry entry; 829 830 ASSERT(fPoseView); 831 if (!fPoseView) 832 return; 833 834 // deal with new unconfigured folders 835 if (NeedsDefaultStateSetup()) 836 SetUpDefaultState(); 837 838 if (ShouldAddScrollBars()) 839 fPoseView->AddScrollBars(); 840 841 fMoveToItem = new BMenuItem(new BNavMenu("Move to", kMoveSelectionTo, this)); 842 fCopyToItem = new BMenuItem(new BNavMenu("Copy to", kCopySelectionTo, this)); 843 fCreateLinkItem = new BMenuItem(new BNavMenu("Create Link", kCreateLink, this), 844 new BMessage(kCreateLink)); 845 846 TrackerSettings settings; 847 848 if (ShouldAddMenus()) { 849 // add menu bar, menus and resize poseview to fit 850 fMenuBar = new BMenuBar(BRect(0, 0, Bounds().Width() + 1, 1), "MenuBar"); 851 fMenuBar->SetBorder(B_BORDER_FRAME); 852 AddMenus(); 853 AddChild(fMenuBar); 854 855 y_delta = KeyMenuBar()->Bounds().Height() + 1; 856 857 float navigatorDelta = 0; 858 859 if (Navigator() && settings.ShowNavigator()) { 860 Navigator()->MoveTo(BPoint(0, y_delta)); 861 navigatorDelta = BNavigator::CalcNavigatorHeight() + 1; 862 } 863 864 fPoseView->MoveTo(BPoint(0, navigatorDelta + y_delta)); 865 fPoseView->ResizeBy(0, -(y_delta)); 866 if (fPoseView->VScrollBar()) { 867 fPoseView->VScrollBar()->MoveBy(0, KeyMenuBar()->Bounds().Height() + 1); 868 fPoseView->VScrollBar()->ResizeBy(0, -(KeyMenuBar()->Bounds().Height() + 1)); 869 } 870 871 // add folder icon to menu bar 872 if (!TargetModel()->IsRoot() && !IsTrash() && !IsPrintersDir()) { 873 float iconSize = fMenuBar->Bounds().Height() - 2; 874 if (iconSize < 16) 875 iconSize = 16; 876 float iconPosY = 1 + (fMenuBar->Bounds().Height() - 2 - iconSize) / 2; 877 BView *icon = new DraggableContainerIcon(BRect(Bounds().Width() - 4 - iconSize + 1, 878 iconPosY, Bounds().Width() - 4, iconPosY + iconSize - 1), 879 "ThisContainer", B_FOLLOW_RIGHT); 880 fMenuBar->AddChild(icon); 881 } 882 } else { 883 // add equivalents of the menu shortcuts to the menuless desktop window 884 AddShortcuts(); 885 } 886 887 AddContextMenus(); 888 AddShortcut('T', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kDelete), PoseView()); 889 AddShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCleanupAll), 890 PoseView()); 891 AddShortcut('Q', B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY, 892 new BMessage(kQuitTracker)); 893 894 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenSelection), 895 PoseView()); 896 897 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); 898 899 #if DEBUG 900 // add some debugging shortcuts 901 AddShortcut('D', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dbug'), PoseView()); 902 AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpcc'), PoseView()); 903 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpfl'), PoseView()); 904 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY, 905 new BMessage('dpfL'), PoseView()); 906 #endif 907 908 if (message) 909 RestoreState(*message); 910 else 911 RestoreState(); 912 913 if (ShouldAddMenus() && PoseView()->ViewMode() == kListMode) { 914 // for now only show attributes in list view 915 // eventually enable attribute menu to allow users to select 916 // using different attributes as titles in icon view modes 917 ShowAttributeMenu(); 918 } 919 MarkAttributeMenu(fAttrMenu); 920 CheckScreenIntersect(); 921 922 if (fBackgroundImage && !dynamic_cast<BDeskWindow *>(this) 923 && PoseView()->ViewMode() != kListMode) 924 fBackgroundImage->Show(PoseView(), current_workspace()); 925 926 Show(); 927 928 // done showing, turn the B_NO_WORKSPACE_ACTIVATION flag off; 929 // it was on to prevent workspace jerking during boot 930 SetFlags(Flags() & ~B_NO_WORKSPACE_ACTIVATION); 931 } 932 933 934 void 935 BContainerWindow::RestoreState() 936 { 937 SetSizeLimits(kContainerWidthMinLimit, 10000, kContainerWindowHeightLimit, 10000); 938 939 UpdateTitle(); 940 941 WindowStateNodeOpener opener(this, false); 942 RestoreWindowState(opener.StreamNode()); 943 fPoseView->Init(opener.StreamNode()); 944 945 RestoreStateCommon(); 946 } 947 948 949 void 950 BContainerWindow::RestoreState(const BMessage &message) 951 { 952 SetSizeLimits(kContainerWidthMinLimit, 10000, kContainerWindowHeightLimit, 10000); 953 954 UpdateTitle(); 955 956 RestoreWindowState(message); 957 fPoseView->Init(message); 958 959 RestoreStateCommon(); 960 } 961 962 963 void 964 BContainerWindow::RestoreStateCommon() 965 { 966 if (BootedInSafeMode()) 967 // don't pick up backgrounds in safe mode 968 return; 969 970 WindowStateNodeOpener opener(this, false); 971 972 bool isDesktop = dynamic_cast<BDeskWindow *>(this); 973 if (!TargetModel()->IsRoot() && opener.Node()) 974 // don't pick up background image for root disks 975 // to do this, would have to have a unique attribute for the 976 // disks window that doesn't collide with the desktop 977 // for R4 this was not done to make things simpler 978 // the default image will still work though 979 fBackgroundImage = BackgroundImage::GetBackgroundImage( 980 opener.Node(), isDesktop); 981 // look for background image info in the window's node 982 983 BNode defaultingNode; 984 if (!fBackgroundImage && !isDesktop 985 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) 986 // look for background image info in the source for defaults 987 fBackgroundImage = BackgroundImage::GetBackgroundImage(&defaultingNode, isDesktop); 988 } 989 990 991 void 992 BContainerWindow::UpdateTitle() 993 { 994 // set title to full path, if necessary 995 if (TrackerSettings().ShowFullPathInTitleBar()) { 996 // use the Entry's full path 997 BPath path; 998 TargetModel()->GetPath(&path); 999 SetTitle(path.Path()); 1000 } else 1001 // use the default look 1002 SetTitle(TargetModel()->Name()); 1003 1004 if (Navigator()) 1005 Navigator()->UpdateLocation(PoseView()->TargetModel(), kActionUpdatePath); 1006 } 1007 1008 1009 void 1010 BContainerWindow::UpdateBackgroundImage() 1011 { 1012 if (BootedInSafeMode()) 1013 return; 1014 1015 bool isDesktop = dynamic_cast<BDeskWindow *>(this) != NULL; 1016 WindowStateNodeOpener opener(this, false); 1017 1018 if (!TargetModel()->IsRoot() && opener.Node()) 1019 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, 1020 opener.Node(), isDesktop, PoseView()); 1021 1022 // look for background image info in the window's node 1023 BNode defaultingNode; 1024 if (!fBackgroundImage && !isDesktop 1025 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) 1026 // look for background image info in the source for defaults 1027 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, 1028 &defaultingNode, isDesktop, PoseView()); 1029 } 1030 1031 1032 void 1033 BContainerWindow::FrameResized(float, float) 1034 { 1035 if (PoseView()) { 1036 PoseView()->UpdateScrollRange(); 1037 PoseView()->ResetPosePlacementHint(); 1038 } 1039 1040 fStateNeedsSaving = true; 1041 } 1042 1043 1044 void 1045 BContainerWindow::FrameMoved(BPoint) 1046 { 1047 fStateNeedsSaving = true; 1048 } 1049 1050 1051 void 1052 BContainerWindow::WorkspacesChanged(uint32, uint32) 1053 { 1054 fStateNeedsSaving = true; 1055 } 1056 1057 1058 void 1059 BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode) 1060 { 1061 if (!fBackgroundImage) 1062 return; 1063 1064 if (newMode == kListMode) 1065 fBackgroundImage->Remove(); 1066 else if (oldMode == kListMode) 1067 fBackgroundImage->Show(PoseView(), current_workspace()); 1068 } 1069 1070 1071 void 1072 BContainerWindow::CheckScreenIntersect() 1073 { 1074 BScreen screen(this); 1075 BRect screenFrame(screen.Frame()); 1076 BRect frame(Frame()); 1077 1078 if (sNewWindRect.bottom > screenFrame.bottom) 1079 sNewWindRect.OffsetTo(85, 50); 1080 1081 if (sNewWindRect.right > screenFrame.right) 1082 sNewWindRect.OffsetTo(85, 50); 1083 1084 if (!frame.Intersects(screenFrame)) 1085 MoveTo(sNewWindRect.LeftTop()); 1086 } 1087 1088 1089 void 1090 BContainerWindow::SaveState(bool hide) 1091 { 1092 if (SaveStateIsEnabled()) { 1093 WindowStateNodeOpener opener(this, true); 1094 if (opener.StreamNode()) 1095 SaveWindowState(opener.StreamNode()); 1096 if (hide) 1097 Hide(); 1098 if (opener.StreamNode()) 1099 fPoseView->SaveState(opener.StreamNode()); 1100 1101 fStateNeedsSaving = false; 1102 } 1103 } 1104 1105 1106 void 1107 BContainerWindow::SaveState(BMessage &message) const 1108 { 1109 if (SaveStateIsEnabled()) { 1110 SaveWindowState(message); 1111 fPoseView->SaveState(message); 1112 } 1113 } 1114 1115 1116 bool 1117 BContainerWindow::StateNeedsSaving() const 1118 { 1119 return fStateNeedsSaving || PoseView()->StateNeedsSaving(); 1120 } 1121 1122 1123 status_t 1124 BContainerWindow::GetLayoutState(BNode *node, BMessage *message) 1125 { 1126 // ToDo: 1127 // get rid of this, use AttrStream instead 1128 status_t result = node->InitCheck(); 1129 if (result != B_OK) 1130 return result; 1131 1132 node->RewindAttrs(); 1133 char attrName[256]; 1134 while (node->GetNextAttrName(attrName) == B_OK) { 1135 attr_info info; 1136 node->GetAttrInfo(attrName, &info); 1137 1138 // filter out attributes that are not related to window position 1139 // and column resizing 1140 // more can be added as needed 1141 if (strcmp(attrName, kAttrWindowFrame) != 0 1142 && strcmp(attrName, kAttrColumns) != 0 1143 && strcmp(attrName, kAttrViewState) != 0 1144 && strcmp(attrName, kAttrColumnsForeign) != 0 1145 && strcmp(attrName, kAttrViewStateForeign) != 0) 1146 continue; 1147 1148 char *buffer = new char[info.size]; 1149 if (node->ReadAttr(attrName, info.type, 0, buffer, (size_t)info.size) == info.size) 1150 message->AddData(attrName, info.type, buffer, (ssize_t)info.size); 1151 delete [] buffer; 1152 } 1153 return B_OK; 1154 } 1155 1156 1157 status_t 1158 BContainerWindow::SetLayoutState(BNode *node, const BMessage *message) 1159 { 1160 status_t result = node->InitCheck(); 1161 if (result != B_OK) 1162 return result; 1163 1164 for (int32 globalIndex = 0; ;) { 1165 #if B_BEOS_VERSION_DANO 1166 const char *name; 1167 #else 1168 char *name; 1169 #endif 1170 type_code type; 1171 int32 count; 1172 status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name, 1173 &type, &count); 1174 if (result != B_OK) 1175 break; 1176 1177 for (int32 index = 0; index < count; index++) { 1178 const void *buffer; 1179 int32 size; 1180 result = message->FindData(name, type, index, &buffer, &size); 1181 if (result != B_OK) { 1182 PRINT(("error reading %s \n", name)); 1183 return result; 1184 } 1185 1186 if (node->WriteAttr(name, type, 0, buffer, (size_t)size) != size) { 1187 PRINT(("error writing %s \n", name)); 1188 return result; 1189 } 1190 globalIndex++; 1191 } 1192 } 1193 return B_OK; 1194 } 1195 1196 1197 bool 1198 BContainerWindow::ShouldAddMenus() const 1199 { 1200 return true; 1201 } 1202 1203 1204 bool 1205 BContainerWindow::ShouldAddScrollBars() const 1206 { 1207 return true; 1208 } 1209 1210 1211 bool 1212 BContainerWindow::ShouldAddCountView() const 1213 { 1214 return true; 1215 } 1216 1217 1218 Model * 1219 BContainerWindow::TargetModel() const 1220 { 1221 return fPoseView->TargetModel(); 1222 } 1223 1224 1225 void 1226 BContainerWindow::SelectionChanged() 1227 { 1228 } 1229 1230 1231 void 1232 BContainerWindow::Zoom(BPoint, float, float) 1233 { 1234 BRect oldZoomRect(fSavedZoomRect); 1235 fSavedZoomRect = Frame(); 1236 ResizeToFit(); 1237 1238 if (fSavedZoomRect == Frame()) 1239 if (oldZoomRect.IsValid()) 1240 ResizeTo(oldZoomRect.Width(), oldZoomRect.Height()); 1241 } 1242 1243 1244 void 1245 BContainerWindow::ResizeToFit() 1246 { 1247 BScreen screen(this); 1248 BRect screenFrame(screen.Frame()); 1249 1250 screenFrame.InsetBy(5, 5); 1251 screenFrame.top += 15; // keeps title bar of window visible 1252 1253 BRect frame(Frame()); 1254 1255 // move frame left top on screen 1256 BPoint leftTop(frame.LeftTop()); 1257 leftTop.ConstrainTo(screenFrame); 1258 frame.OffsetTo(leftTop); 1259 1260 // resize to extent size 1261 float menuHeight; 1262 if (KeyMenuBar()) 1263 menuHeight = KeyMenuBar()->Bounds().Height(); 1264 else 1265 menuHeight = 0; 1266 1267 BRect extent(PoseView()->Extent()); 1268 frame.right = frame.left + extent.Width() + (float)B_V_SCROLL_BAR_WIDTH + 1.0f; 1269 frame.bottom = frame.top + extent.Height() + (float)B_H_SCROLL_BAR_HEIGHT + 1.0f 1270 + menuHeight; 1271 1272 if (PoseView()->ViewMode() == kListMode) 1273 frame.bottom += kTitleViewHeight + 1; // account for list titles 1274 1275 TrackerSettings settings; 1276 if (settings.SingleWindowBrowse() 1277 && settings.ShowNavigator() 1278 && Navigator()) 1279 frame.bottom += fNavigator->Bounds().bottom + 1; 1280 1281 // ToDo: 1282 // clean this up, move each special case to respective class 1283 1284 if (!TargetModel()) 1285 frame.bottom += 60; // Open with window 1286 else if (TargetModel()->IsQuery()) 1287 frame.bottom += 15; // account for query string 1288 1289 // make sure entire window fits on screen 1290 frame = frame & screenFrame; 1291 1292 ResizeTo(frame.Width(), frame.Height()); 1293 MoveTo(frame.LeftTop()); 1294 PoseView()->DisableScrollBars(); 1295 PoseView()->ScrollTo(extent.LeftTop()); 1296 PoseView()->UpdateScrollRange(); 1297 PoseView()->EnableScrollBars(); 1298 } 1299 1300 1301 void 1302 BContainerWindow::MessageReceived(BMessage *message) 1303 { 1304 switch (message->what) { 1305 case B_CUT: 1306 case B_COPY: 1307 case B_PASTE: 1308 case kCutMoreSelectionToClipboard: 1309 case kCopyMoreSelectionToClipboard: 1310 case kPasteLinksFromClipboard: 1311 { 1312 BView *view = CurrentFocus(); 1313 if (view->LockLooper()) { 1314 view->MessageReceived(message); 1315 view->UnlockLooper(); 1316 } 1317 break; 1318 } 1319 1320 case kNewFolder: 1321 PostMessage(message, PoseView()); 1322 break; 1323 1324 case kContextMenuDragNDrop: 1325 // 1326 // sent when the SlowContextPopup goes away 1327 // 1328 if (fWaitingForRefs && Dragging()) 1329 PostMessage(message, PoseView()); 1330 else 1331 fWaitingForRefs = false; 1332 break; 1333 1334 case kRestoreState: 1335 if (message->HasMessage("state")) { 1336 BMessage state; 1337 message->FindMessage("state", &state); 1338 Init(&state); 1339 } else 1340 Init(); 1341 break; 1342 1343 case kResizeToFit: 1344 ResizeToFit(); 1345 break; 1346 1347 case kLoadAddOn: 1348 LoadAddOn(message); 1349 break; 1350 1351 case kCopySelectionTo: 1352 { 1353 entry_ref ref; 1354 if (message->FindRef("refs", &ref) != B_OK) 1355 break; 1356 1357 BRoster().AddToRecentFolders(&ref); 1358 1359 Model model(&ref); 1360 if (model.InitCheck() != B_OK) 1361 break; 1362 1363 if (*model.NodeRef() == *TargetModel()->NodeRef()) 1364 PoseView()->DuplicateSelection(); 1365 else 1366 PoseView()->MoveSelectionInto(&model, this, true); 1367 1368 break; 1369 } 1370 case kMoveSelectionTo: 1371 { 1372 entry_ref ref; 1373 if (message->FindRef("refs", &ref) != B_OK) 1374 break; 1375 1376 BRoster().AddToRecentFolders(&ref); 1377 1378 Model model(&ref); 1379 if (model.InitCheck() != B_OK) 1380 break; 1381 1382 PoseView()->MoveSelectionInto(&model, this, false); 1383 break; 1384 } 1385 1386 case kCreateLink: 1387 case kCreateRelativeLink: 1388 { 1389 entry_ref ref; 1390 if (message->FindRef("refs", &ref) == B_OK) { 1391 BRoster().AddToRecentFolders(&ref); 1392 1393 Model model(&ref); 1394 if (model.InitCheck() != B_OK) 1395 break; 1396 1397 PoseView()->MoveSelectionInto(&model, this, false, true, 1398 message->what == kCreateRelativeLink); 1399 } else { 1400 // no destination specified, create link in same dir as item 1401 if (!TargetModel()->IsQuery()) 1402 PoseView()->MoveSelectionInto(TargetModel(), this, false, true, 1403 (message->what == kCreateRelativeLink)); 1404 } 1405 break; 1406 } 1407 1408 case kShowSelectionWindow: 1409 ShowSelectionWindow(); 1410 break; 1411 1412 case kSelectMatchingEntries: 1413 PoseView()->SelectMatchingEntries(message); 1414 break; 1415 1416 case kFindButton: 1417 (new FindWindow())->Show(); 1418 break; 1419 1420 case kQuitTracker: 1421 be_app->PostMessage(B_QUIT_REQUESTED); 1422 break; 1423 1424 case kRestoreBackgroundImage: 1425 UpdateBackgroundImage(); 1426 break; 1427 1428 case kSwitchDirectory: 1429 { 1430 entry_ref ref; 1431 if (message->FindRef("refs", &ref) == B_OK) { 1432 BEntry entry; 1433 if (entry.SetTo(&ref) == B_OK) { 1434 if (StateNeedsSaving()) 1435 SaveState(false); 1436 1437 bool wasInTrash = IsTrash() || InTrash(); 1438 bool isRoot = PoseView()->TargetModel()->IsRoot(); 1439 1440 // Switch dir and apply new state 1441 WindowStateNodeOpener opener(this, false); 1442 opener.SetTo(&entry, false); 1443 1444 // Update PoseView 1445 PoseView()->SwitchDir(&ref, opener.StreamNode()); 1446 1447 fIsTrash = FSIsTrashDir(&entry); 1448 fInTrash = FSInTrashDir(&ref); 1449 1450 if (wasInTrash ^ (IsTrash() || InTrash()) 1451 || isRoot != PoseView()->TargetModel()->IsRoot()) 1452 RepopulateMenus(); 1453 1454 // Update Navigation bar 1455 if (Navigator()) { 1456 int32 action = kActionSet; 1457 if (message->FindInt32("action", &action) != B_OK) 1458 // Design problem? Why does FindInt32 touch 1459 // 'action' at all if he can't find it?? 1460 action = kActionSet; 1461 1462 Navigator()->UpdateLocation(PoseView()->TargetModel(), action); 1463 } 1464 1465 TrackerSettings settings; 1466 if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar()) 1467 SetPathWatchingEnabled(true); 1468 1469 // Update window title 1470 UpdateTitle(); 1471 } 1472 } 1473 break; 1474 } 1475 1476 case B_REFS_RECEIVED: 1477 if (Dragging()) { 1478 // 1479 // ref in this message is the target, 1480 // the end point of the drag 1481 // 1482 entry_ref ref; 1483 if (message->FindRef("refs", &ref) == B_OK) { 1484 //printf("BContainerWindow::MessageReceived - refs received\n"); 1485 fWaitingForRefs = false; 1486 BEntry entry(&ref, true); 1487 // 1488 // don't copy to printers dir 1489 if (!FSIsPrintersDir(&entry)) { 1490 if (entry.InitCheck() == B_OK && entry.IsDirectory()) { 1491 // 1492 // build of list of entry_refs from the list 1493 // in the drag message 1494 entry_ref dragref; 1495 int32 index = 0; 1496 BObjectList<entry_ref> *list = new BObjectList<entry_ref>(0, true); 1497 while (fDragMessage->FindRef("refs", index++, &dragref) == B_OK) 1498 list->AddItem(new entry_ref(dragref)); 1499 // 1500 // compare the target and one of the drag items' parent 1501 // 1502 BEntry item(&dragref, true); 1503 BEntry itemparent; 1504 item.GetParent(&itemparent); 1505 entry_ref parentref; 1506 itemparent.GetRef(&parentref); 1507 1508 entry_ref targetref; 1509 entry.GetRef(&targetref); 1510 1511 // if they don't match, move/copy 1512 if (targetref != parentref) 1513 // copy drag contents to target ref in message 1514 FSMoveToFolder(list, new BEntry(entry), kMoveSelectionTo); 1515 1516 } else { 1517 // current message sent to apps is only B_REFS_RECEIVED 1518 fDragMessage->what = B_REFS_RECEIVED; 1519 FSLaunchItem(&ref, (const BMessage *)fDragMessage, true, true); 1520 } 1521 } 1522 } 1523 DragStop(); 1524 } 1525 break; 1526 1527 case B_OBSERVER_NOTICE_CHANGE: 1528 { 1529 int32 observerWhat; 1530 if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { 1531 TrackerSettings settings; 1532 switch (observerWhat) { 1533 case kWindowsShowFullPathChanged: 1534 UpdateTitle(); 1535 if (!IsPathWatchingEnabled() && settings.ShowFullPathInTitleBar()) 1536 SetPathWatchingEnabled(true); 1537 if (IsPathWatchingEnabled() && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) 1538 SetPathWatchingEnabled(false); 1539 break; 1540 1541 case kSingleWindowBrowseChanged: 1542 if (settings.SingleWindowBrowse() 1543 && !Navigator() 1544 && TargetModel()->IsDirectory() 1545 && !PoseView()->IsFilePanel() 1546 && !PoseView()->IsDesktopWindow()) { 1547 BRect rect(Bounds()); 1548 rect.top = KeyMenuBar()->Bounds().Height() + 1; 1549 rect.bottom = rect.top + BNavigator::CalcNavigatorHeight(); 1550 fNavigator = new BNavigator(TargetModel(), rect); 1551 fNavigator->Hide(); 1552 AddChild(fNavigator); 1553 SetPathWatchingEnabled(settings.ShowNavigator() || settings.ShowFullPathInTitleBar()); 1554 } 1555 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); 1556 break; 1557 1558 case kShowNavigatorChanged: 1559 ShowNavigator(settings.ShowNavigator()); 1560 if (!IsPathWatchingEnabled() && settings.ShowNavigator()) 1561 SetPathWatchingEnabled(true); 1562 if (IsPathWatchingEnabled() && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) 1563 SetPathWatchingEnabled(false); 1564 break; 1565 1566 case kDontMoveFilesToTrashChanged: 1567 { 1568 bool dontMoveToTrash = settings.DontMoveFilesToTrash(); 1569 1570 BMenuItem *item = fFileContextMenu->FindItem(kMoveToTrash); 1571 if (item) 1572 item->SetLabel(dontMoveToTrash ? "Delete" : "Move To Trash"); 1573 1574 // Deskbar doesn't have a menu bar, so check if there is fMenuBar 1575 if (fMenuBar && fFileMenu) { 1576 item = fFileMenu->FindItem(kMoveToTrash); 1577 if (item) 1578 item->SetLabel(dontMoveToTrash ? "Delete" : "Move To Trash"); 1579 } 1580 UpdateIfNeeded(); 1581 } 1582 break; 1583 1584 default: 1585 _inherited::MessageReceived(message); 1586 } 1587 } 1588 break; 1589 } 1590 1591 case B_NODE_MONITOR: 1592 UpdateTitle(); 1593 break; 1594 1595 case B_UNDO: 1596 FSUndo(); 1597 break; 1598 1599 //case B_REDO: /* only defined in Dano/Zeta/OpenBeOS */ 1600 case kRedo: 1601 FSRedo(); 1602 break; 1603 1604 default: 1605 _inherited::MessageReceived(message); 1606 } 1607 } 1608 1609 1610 void 1611 BContainerWindow::SetCutItem(BMenu *menu) 1612 { 1613 BMenuItem *item; 1614 if ((item = menu->FindItem(B_CUT)) == NULL 1615 && (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) 1616 return; 1617 1618 item->SetEnabled(PoseView()->SelectionList()->CountItems() > 0 1619 || PoseView() != CurrentFocus()); 1620 1621 if (modifiers() & B_SHIFT_KEY) { 1622 item->SetLabel("Cut more"); 1623 item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY); 1624 item->SetMessage(new BMessage(kCutMoreSelectionToClipboard)); 1625 } else { 1626 item->SetLabel("Cut"); 1627 item->SetShortcut('X', B_COMMAND_KEY); 1628 item->SetMessage(new BMessage(B_CUT)); 1629 } 1630 } 1631 1632 1633 void 1634 BContainerWindow::SetCopyItem(BMenu *menu) 1635 { 1636 BMenuItem *item; 1637 if ((item = menu->FindItem(B_COPY)) == NULL 1638 && (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) 1639 return; 1640 1641 item->SetEnabled(PoseView()->SelectionList()->CountItems() > 0 1642 || PoseView() != CurrentFocus()); 1643 1644 if (modifiers() & B_SHIFT_KEY) { 1645 item->SetLabel("Copy more"); 1646 item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY); 1647 item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard)); 1648 } else { 1649 item->SetLabel("Copy"); 1650 item->SetShortcut('C', B_COMMAND_KEY); 1651 item->SetMessage(new BMessage(B_COPY)); 1652 } 1653 } 1654 1655 1656 void 1657 BContainerWindow::SetPasteItem(BMenu *menu) 1658 { 1659 BMenuItem *item; 1660 if ((item = menu->FindItem(B_PASTE)) == NULL 1661 && (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) 1662 return; 1663 1664 item->SetEnabled(FSClipboardHasRefs() || PoseView() != CurrentFocus()); 1665 1666 if (modifiers() & B_SHIFT_KEY) { 1667 item->SetLabel("Paste links"); 1668 item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY); 1669 item->SetMessage(new BMessage(kPasteLinksFromClipboard)); 1670 } else { 1671 item->SetLabel("Paste"); 1672 item->SetShortcut('V', B_COMMAND_KEY); 1673 item->SetMessage(new BMessage(B_PASTE)); 1674 } 1675 } 1676 1677 1678 void 1679 BContainerWindow::SetCleanUpItem(BMenu *menu) 1680 { 1681 BMenuItem *item; 1682 if ((item = menu->FindItem(kCleanup)) == NULL 1683 && (item = menu->FindItem(kCleanupAll)) == NULL) 1684 return; 1685 1686 item->SetEnabled(PoseView()->CountItems() > 0 1687 && (PoseView()->ViewMode() != kListMode)); 1688 1689 if (modifiers() & B_SHIFT_KEY) { 1690 item->SetLabel("Clean Up All"); 1691 item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY); 1692 item->SetMessage(new BMessage(kCleanupAll)); 1693 } else { 1694 item->SetLabel("Clean Up"); 1695 item->SetShortcut('K', B_COMMAND_KEY); 1696 item->SetMessage(new BMessage(kCleanup)); 1697 } 1698 } 1699 1700 1701 void 1702 BContainerWindow::SetCloseItem(BMenu *menu) 1703 { 1704 BMenuItem *item; 1705 if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL 1706 && (item = menu->FindItem(kCloseAllWindows)) == NULL) 1707 return; 1708 1709 if (modifiers() & B_OPTION_KEY) { 1710 item->SetLabel("Close All"); 1711 item->SetShortcut('W', B_COMMAND_KEY | B_OPTION_KEY); 1712 item->SetTarget(be_app); 1713 item->SetMessage(new BMessage(kCloseAllWindows)); 1714 } else { 1715 item->SetLabel("Close"); 1716 item->SetShortcut('W', B_COMMAND_KEY); 1717 item->SetTarget(this); 1718 item->SetMessage(new BMessage(B_QUIT_REQUESTED)); 1719 } 1720 } 1721 1722 1723 bool 1724 BContainerWindow::IsShowing(const node_ref *node) const 1725 { 1726 return PoseView()->Represents(node); 1727 } 1728 1729 1730 bool 1731 BContainerWindow::IsShowing(const entry_ref *entry) const 1732 { 1733 return PoseView()->Represents(entry); 1734 } 1735 1736 1737 void 1738 BContainerWindow::AddMenus() 1739 { 1740 fFileMenu = new BMenu("File"); 1741 AddFileMenu(fFileMenu); 1742 fMenuBar->AddItem(fFileMenu); 1743 fWindowMenu = new BMenu("Window"); 1744 fMenuBar->AddItem(fWindowMenu); 1745 AddWindowMenu(fWindowMenu); 1746 // just create the attribute, decide to add it later 1747 fAttrMenu = new BMenu("Attributes"); 1748 NewAttributeMenu(fAttrMenu); 1749 } 1750 1751 1752 void 1753 BContainerWindow::AddFileMenu(BMenu *menu) 1754 { 1755 if (!PoseView()->IsFilePanel()) 1756 menu->AddItem(new BMenuItem("Find"B_UTF8_ELLIPSIS, 1757 new BMessage(kFindButton), 'F')); 1758 1759 if (!TargetModel()->IsQuery() && !IsTrash() && !IsPrintersDir()) { 1760 if (!PoseView()->IsFilePanel()) { 1761 TemplatesMenu *templateMenu = new TemplatesMenu(PoseView()); 1762 menu->AddItem(templateMenu); 1763 templateMenu->SetTargetForItems(PoseView()); 1764 } else 1765 menu->AddItem(new BMenuItem("New Folder", new BMessage(kNewFolder), 'N')); 1766 } 1767 menu->AddSeparatorItem(); 1768 1769 menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); 1770 menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); 1771 menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); 1772 1773 if (IsTrash() || InTrash()) { 1774 menu->AddItem(new BMenuItem("Delete", new BMessage(kDelete))); 1775 menu->AddItem(new BMenuItem("Restore", new BMessage(kRestoreFromTrash))); 1776 if (IsTrash()) { 1777 // add as first item in menu 1778 menu->AddItem(new BMenuItem("Empty Trash", new BMessage(kEmptyTrash)), 0); 1779 menu->AddItem(new BSeparatorItem(), 1); 1780 } 1781 } else if (IsPrintersDir()) { 1782 menu->AddItem(new BMenuItem("Add Printer"B_UTF8_ELLIPSIS, 1783 new BMessage(kAddPrinter), 'N'), 0); 1784 menu->AddItem(new BSeparatorItem(), 1); 1785 menu->AddItem(new BMenuItem("Make Active Printer", 1786 new BMessage(kMakeActivePrinter))); 1787 } else { 1788 menu->AddItem(new BMenuItem("Duplicate",new BMessage(kDuplicateSelection), 'D')); 1789 1790 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? 1791 "Delete" : "Move to Trash", 1792 new BMessage(kMoveToTrash), 'T')); 1793 1794 menu->AddSeparatorItem(); 1795 1796 // The "Move To", "Copy To", "Create Link" menus are inserted 1797 // at this place, have a look at: 1798 // BContainerWindow::SetupMoveCopyMenus() 1799 } 1800 1801 BMenuItem *cutItem = NULL, *copyItem = NULL, *pasteItem = NULL; 1802 if (!IsPrintersDir()) { 1803 menu->AddSeparatorItem(); 1804 1805 menu->AddItem(cutItem = new BMenuItem("Cut", new BMessage(B_CUT), 'X')); 1806 menu->AddItem(copyItem = new BMenuItem("Copy", new BMessage(B_COPY), 'C')); 1807 menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); 1808 1809 menu->AddSeparatorItem(); 1810 1811 menu->AddItem(new BMenuItem("Identify", new BMessage(kIdentifyEntry))); 1812 BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); 1813 addOnMenuItem->SetFont(be_plain_font); 1814 menu->AddItem(addOnMenuItem); 1815 } 1816 1817 menu->SetTargetForItems(PoseView()); 1818 if (cutItem) 1819 cutItem->SetTarget(this); 1820 if (copyItem) 1821 copyItem->SetTarget(this); 1822 if (pasteItem) 1823 pasteItem->SetTarget(this); 1824 } 1825 1826 1827 void 1828 BContainerWindow::AddWindowMenu(BMenu *menu) 1829 { 1830 BMenuItem *item; 1831 1832 item = new BMenuItem("Icon View", new BMessage(kIconMode), '1'); 1833 item->SetTarget(PoseView()); 1834 menu->AddItem(item); 1835 1836 item = new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode), '2'); 1837 item->SetTarget(PoseView()); 1838 menu->AddItem(item); 1839 1840 item = new BMenuItem("List View", new BMessage(kListMode), '3'); 1841 item->SetTarget(PoseView()); 1842 menu->AddItem(item); 1843 1844 menu->AddSeparatorItem(); 1845 1846 item = new BMenuItem("Resize to Fit", new BMessage(kResizeToFit), 'Y'); 1847 item->SetTarget(this); 1848 menu->AddItem(item); 1849 1850 item = new BMenuItem("Clean Up", new BMessage(kCleanup), 'K'); 1851 item->SetTarget(PoseView()); 1852 menu->AddItem(item); 1853 1854 item = new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), 1855 'A', B_SHIFT_KEY); 1856 item->SetTarget(PoseView()); 1857 menu->AddItem(item); 1858 1859 item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'); 1860 item->SetTarget(PoseView()); 1861 menu->AddItem(item); 1862 1863 item = new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S'); 1864 item->SetTarget(PoseView()); 1865 menu->AddItem(item); 1866 1867 if (!IsTrash()) { 1868 item = new BMenuItem("Open Parent", new BMessage(kOpenParentDir), 1869 B_UP_ARROW); 1870 item->SetTarget(PoseView()); 1871 menu->AddItem(item); 1872 } 1873 1874 item = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), 'W'); 1875 item->SetTarget(this); 1876 menu->AddItem(item); 1877 1878 menu->AddSeparatorItem(); 1879 1880 item = new BMenuItem("Settings"B_UTF8_ELLIPSIS, new BMessage(kShowSettingsWindow)); 1881 item->SetTarget(be_app); 1882 menu->AddItem(item); 1883 } 1884 1885 1886 void 1887 BContainerWindow::AddShortcuts() 1888 { 1889 // add equivalents of the menu shortcuts to the menuless desktop window 1890 ASSERT(!IsTrash()); 1891 ASSERT(!PoseView()->IsFilePanel()); 1892 ASSERT(!TargetModel()->IsQuery()); 1893 1894 AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCutMoreSelectionToClipboard), this); 1895 AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCopyMoreSelectionToClipboard), this); 1896 AddShortcut('F', B_COMMAND_KEY, new BMessage(kFindButton), PoseView()); 1897 AddShortcut('N', B_COMMAND_KEY, new BMessage(kNewFolder), PoseView()); 1898 AddShortcut('O', B_COMMAND_KEY, new BMessage(kOpenSelection), PoseView()); 1899 AddShortcut('I', B_COMMAND_KEY, new BMessage(kGetInfo), PoseView()); 1900 AddShortcut('E', B_COMMAND_KEY, new BMessage(kEditItem), PoseView()); 1901 AddShortcut('D', B_COMMAND_KEY, new BMessage(kDuplicateSelection), PoseView()); 1902 AddShortcut('T', B_COMMAND_KEY, new BMessage(kMoveToTrash), PoseView()); 1903 AddShortcut('K', B_COMMAND_KEY, new BMessage(kCleanup), PoseView()); 1904 AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), PoseView()); 1905 AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection), PoseView()); 1906 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kShowSelectionWindow), PoseView()); 1907 AddShortcut('G', B_COMMAND_KEY, new BMessage(kEditQuery), PoseView()); 1908 // it is ok to add a global Edit query shortcut here, PoseView will 1909 // filter out cases where selected pose is not a query 1910 AddShortcut('U', B_COMMAND_KEY, new BMessage(kUnmountVolume), PoseView()); 1911 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir), PoseView()); 1912 AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage(kOpenSelectionWith), 1913 PoseView()); 1914 } 1915 1916 1917 void 1918 BContainerWindow::MenusBeginning() 1919 { 1920 if (!fMenuBar) 1921 return; 1922 1923 if (CurrentMessage() && CurrentMessage()->what == B_MOUSE_DOWN) 1924 // don't commit active pose if only a keyboard shortcut is 1925 // invoked - this would prevent Cut/Copy/Paste from working 1926 fPoseView->CommitActivePose(); 1927 1928 // File menu 1929 int32 selectCount = PoseView()->SelectionList()->CountItems(); 1930 1931 SetupOpenWithMenu(fFileMenu); 1932 SetupMoveCopyMenus(selectCount 1933 ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() : NULL, fFileMenu); 1934 1935 UpdateMenu(fMenuBar, kMenuBarContext); 1936 1937 AddMimeTypesToMenu(fAttrMenu); 1938 1939 if (IsPrintersDir()) 1940 EnableNamedMenuItem(fFileMenu, "Make Active Printer", selectCount == 1); 1941 } 1942 1943 1944 void 1945 BContainerWindow::MenusEnded() 1946 { 1947 // when we're done we want to clear nav menus for next time 1948 DeleteSubmenu(fNavigationItem); 1949 DeleteSubmenu(fMoveToItem); 1950 DeleteSubmenu(fCopyToItem); 1951 DeleteSubmenu(fCreateLinkItem); 1952 DeleteSubmenu(fOpenWithItem); 1953 } 1954 1955 1956 void 1957 BContainerWindow::SetupNavigationMenu(const entry_ref *ref, BMenu *parent) 1958 { 1959 // start by removing nav item (and separator) from old menu 1960 if (fNavigationItem) { 1961 BMenu *menu = fNavigationItem->Menu(); 1962 if (menu) { 1963 menu->RemoveItem(fNavigationItem); 1964 BMenuItem *item = menu->RemoveItem((int32)0); 1965 ASSERT(item != fNavigationItem); 1966 delete item; 1967 } 1968 } 1969 1970 // if we weren't passed a ref then we're navigating this window 1971 if (!ref) 1972 ref = TargetModel()->EntryRef(); 1973 1974 BEntry entry; 1975 if (entry.SetTo(ref) != B_OK) 1976 return; 1977 1978 // only navigate directories and queries (check for symlink here) 1979 Model model(&entry); 1980 entry_ref resolvedRef; 1981 1982 if (model.InitCheck() != B_OK 1983 || (!model.IsContainer() && !model.IsSymLink())) 1984 return; 1985 1986 if (model.IsSymLink()) { 1987 if (entry.SetTo(model.EntryRef(), true) != B_OK) 1988 return; 1989 1990 Model resolvedModel(&entry); 1991 if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer()) 1992 return; 1993 1994 entry.GetRef(&resolvedRef); 1995 ref = &resolvedRef; 1996 } 1997 1998 if (!fNavigationItem) { 1999 fNavigationItem = new ModelMenuItem(&model, 2000 new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this)); 2001 } 2002 2003 // setup a navigation menu item which will dynamically load items 2004 // as menu items are traversed 2005 BNavMenu *navMenu = dynamic_cast<BNavMenu *>(fNavigationItem->Submenu()); 2006 navMenu->SetNavDir(ref); 2007 fNavigationItem->SetLabel(model.Name()); 2008 fNavigationItem->SetEntry(&entry); 2009 2010 parent->AddItem(fNavigationItem, 0); 2011 parent->AddItem(new BSeparatorItem(), 1); 2012 2013 BMessage *message = new BMessage(B_REFS_RECEIVED); 2014 message->AddRef("refs", ref); 2015 fNavigationItem->SetMessage(message); 2016 fNavigationItem->SetTarget(be_app); 2017 2018 if (!Dragging()) 2019 parent->SetTrackingHook(NULL, NULL); 2020 } 2021 2022 2023 void 2024 BContainerWindow::SetUpEditQueryItem(BMenu *menu) 2025 { 2026 ASSERT(menu); 2027 // File menu 2028 int32 selectCount = PoseView()->SelectionList()->CountItems(); 2029 2030 // add Edit query if appropriate 2031 bool queryInSelection = false; 2032 if (selectCount && selectCount < 100) { 2033 // only do this for a limited number of selected poses 2034 2035 // if any queries selected, add an edit query menu item 2036 for (int32 index = 0; index < selectCount; index++) { 2037 BPose *pose = PoseView()->SelectionList()->ItemAt(index); 2038 Model model(pose->TargetModel()->EntryRef(), true); 2039 if (model.InitCheck() != B_OK) 2040 continue; 2041 2042 if (model.IsQuery() || model.IsQueryTemplate()) { 2043 queryInSelection = true; 2044 break; 2045 } 2046 } 2047 } 2048 2049 bool poseViewIsQuery = TargetModel()->IsQuery(); 2050 // if the view is a query pose view, add edit query menu item 2051 2052 BMenuItem *item = menu->FindItem("Edit Query"); 2053 if (!poseViewIsQuery && !queryInSelection && item) 2054 item->Menu()->RemoveItem(item); 2055 2056 else if ((poseViewIsQuery || queryInSelection) && menu && !item) { 2057 2058 // add edit query item after Open 2059 item = menu->FindItem(kOpenSelection); 2060 if (item) { 2061 int32 itemIndex = item->Menu()->IndexOf(item); 2062 BMenuItem *query = new BMenuItem("Edit Query", new BMessage(kEditQuery), 'G'); 2063 item->Menu()->AddItem(query, itemIndex + 1); 2064 query->SetTarget(PoseView()); 2065 } 2066 } 2067 } 2068 2069 2070 void 2071 BContainerWindow::SetupOpenWithMenu(BMenu *parent) 2072 { 2073 // start by removing nav item (and separator) from old menu 2074 if (fOpenWithItem) { 2075 BMenu *menu = fOpenWithItem->Menu(); 2076 if (menu) 2077 menu->RemoveItem(fOpenWithItem); 2078 2079 delete fOpenWithItem; 2080 fOpenWithItem = 0; 2081 } 2082 2083 if (PoseView()->SelectionList()->CountItems() == 0) 2084 // no selection, nothing to open 2085 return; 2086 2087 if (TargetModel()->IsRoot()) 2088 // don't add ourselves if we are root 2089 return; 2090 2091 // ToDo: 2092 // check if only item in selection list is the root 2093 // and do not add if true 2094 2095 // add after "Open" 2096 BMenuItem *item = parent->FindItem(kOpenSelection); 2097 2098 int32 count = PoseView()->SelectionList()->CountItems(); 2099 if (!count) 2100 return; 2101 2102 // build a list of all refs to open 2103 BMessage message(B_REFS_RECEIVED); 2104 for (int32 index = 0; index < count; index++) { 2105 BPose *pose = PoseView()->SelectionList()->ItemAt(index); 2106 message.AddRef("refs", pose->TargetModel()->EntryRef()); 2107 } 2108 2109 // add Tracker token so that refs received recipients can script us 2110 message.AddMessenger("TrackerViewToken", BMessenger(PoseView())); 2111 2112 int32 index = item->Menu()->IndexOf(item); 2113 fOpenWithItem = new BMenuItem( 2114 new OpenWithMenu("Open With"B_UTF8_ELLIPSIS, &message, this, be_app), 2115 new BMessage(kOpenSelectionWith)); 2116 fOpenWithItem->SetTarget(PoseView()); 2117 fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY); 2118 2119 item->Menu()->AddItem(fOpenWithItem, index + 1); 2120 } 2121 2122 2123 void 2124 BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu *navMenu, uint32 what, 2125 const entry_ref *ref, bool addLocalOnly) 2126 { 2127 BVolume volume; 2128 BVolumeRoster volumeRoster; 2129 BDirectory directory; 2130 BEntry entry; 2131 BPath path; 2132 Model model; 2133 dev_t device = ref->device; 2134 2135 int32 volumeCount = 0; 2136 2137 // count persistent writable volumes 2138 volumeRoster.Rewind(); 2139 while (volumeRoster.GetNextVolume(&volume) == B_OK) 2140 if (!volume.IsReadOnly() && volume.IsPersistent()) 2141 volumeCount++; 2142 2143 // add the current folder 2144 if (entry.SetTo(ref) == B_OK 2145 && entry.GetParent(&entry) == B_OK 2146 && model.SetTo(&entry) == B_OK) { 2147 BNavMenu *menu = new BNavMenu("Current Folder",what,this); 2148 menu->SetNavDir(model.EntryRef()); 2149 menu->SetShowParent(true); 2150 2151 BMenuItem *item = new SpecialModelMenuItem(&model,menu); 2152 item->SetMessage(new BMessage((uint32)what)); 2153 2154 navMenu->AddItem(item); 2155 } 2156 2157 // add the recent folder menu 2158 // the "Tracker" settings directory is only used to get its icon 2159 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 2160 path.Append("Tracker"); 2161 if (entry.SetTo(path.Path()) == B_OK 2162 && model.SetTo(&entry) == B_OK) { 2163 BMenu *menu = new RecentsMenu("Recent Folders",kRecentFolders,what,this); 2164 2165 BMenuItem *item = new SpecialModelMenuItem(&model,menu); 2166 item->SetMessage(new BMessage((uint32)what)); 2167 2168 navMenu->AddItem(item); 2169 } 2170 } 2171 2172 // add Desktop 2173 FSGetBootDeskDir(&directory); 2174 if (directory.InitCheck() == B_OK 2175 && directory.GetEntry(&entry) == B_OK 2176 && model.SetTo(&entry) == B_OK) 2177 navMenu->AddNavDir(&model, what, this, true); 2178 // ask NavMenu to populate submenu for us 2179 2180 // add the home dir 2181 if (find_directory(B_USER_DIRECTORY, &path) == B_OK 2182 && entry.SetTo(path.Path()) == B_OK 2183 && model.SetTo(&entry) == B_OK) 2184 navMenu->AddNavDir(&model, what, this, true); 2185 2186 navMenu->AddSeparatorItem(); 2187 2188 // either add all mounted volumes (for copy), or all the top-level 2189 // directories from the same device (for move) 2190 // ToDo: can be changed if cross-device moves are implemented 2191 2192 if (addLocalOnly || volumeCount < 2) { 2193 // add volume this item lives on 2194 if (volume.SetTo(device) == B_OK 2195 && volume.GetRootDirectory(&directory) == B_OK 2196 && directory.GetEntry(&entry) == B_OK 2197 && model.SetTo(&entry) == B_OK) { 2198 navMenu->AddNavDir(&model, what, this, false); 2199 // do not have submenu populated 2200 2201 navMenu->SetNavDir(model.EntryRef()); 2202 } 2203 } else { 2204 // add all persistent writable volumes 2205 volumeRoster.Rewind(); 2206 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 2207 if (volume.IsReadOnly() || !volume.IsPersistent()) 2208 continue; 2209 2210 // add root dir 2211 if (volume.GetRootDirectory(&directory) == B_OK 2212 && directory.GetEntry(&entry) == B_OK 2213 && model.SetTo(&entry) == B_OK) 2214 navMenu->AddNavDir(&model, what, this, true); 2215 // ask NavMenu to populate submenu for us 2216 } 2217 } 2218 } 2219 2220 2221 void 2222 BContainerWindow::SetupMoveCopyMenus(const entry_ref *item_ref, BMenu *parent) 2223 { 2224 if (IsTrash() || InTrash() || IsPrintersDir() || !fMoveToItem || !fCopyToItem || !fCreateLinkItem) 2225 return; 2226 2227 // Grab the modifiers state since we use it twice 2228 uint32 modifierKeys = modifiers(); 2229 2230 // re-parent items to this menu since they're shared 2231 int32 index = parent->CountItems() - 7; 2232 if (index > 0 && dynamic_cast<BSeparatorItem *>(parent->ItemAt(index - 1)) == NULL) { 2233 // The items below the items to be added vary in number, so 2234 // this little "hack" makes sure they are always in place 2235 index++; 2236 } else 2237 index = 0; 2238 2239 if (fMoveToItem->Menu() != parent) { 2240 if (fMoveToItem->Menu()) 2241 fMoveToItem->Menu()->RemoveItem(fMoveToItem); 2242 2243 parent->AddItem(fMoveToItem, index++); 2244 } 2245 2246 if (fCopyToItem->Menu() != parent) { 2247 if (fCopyToItem->Menu()) 2248 fCopyToItem->Menu()->RemoveItem(fCopyToItem); 2249 2250 parent->AddItem(fCopyToItem, index++); 2251 } 2252 2253 if (fCreateLinkItem->Menu() != parent) { 2254 if (fCreateLinkItem->Menu()) 2255 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); 2256 2257 parent->AddItem(fCreateLinkItem, index); 2258 } 2259 2260 // Set the "Create Link" item label here so it 2261 // appears correctly when menus are disabled, too. 2262 if (modifierKeys & B_SHIFT_KEY) 2263 fCreateLinkItem->SetLabel("Create Relative Link"); 2264 else 2265 fCreateLinkItem->SetLabel("Create Link"); 2266 2267 // only enable once the menus are built 2268 fMoveToItem->SetEnabled(false); 2269 fCopyToItem->SetEnabled(false); 2270 fCreateLinkItem->SetEnabled(false); 2271 2272 // get ref for item which is selected 2273 BEntry entry; 2274 if (entry.SetTo(item_ref) != B_OK) 2275 return; 2276 2277 Model tempModel(&entry); 2278 if (tempModel.InitCheck() != B_OK) 2279 return; 2280 2281 if (tempModel.IsRoot() || tempModel.IsVolume()) 2282 return; 2283 2284 // configure "Move to" menu item 2285 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu *>(fMoveToItem->Submenu()), 2286 kMoveSelectionTo, item_ref, true); 2287 2288 // configure "Copy to" menu item 2289 // add all mounted volumes (except the one this item lives on) 2290 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu *>(fCopyToItem->Submenu()), 2291 kCopySelectionTo, item_ref, false); 2292 2293 // Set "Create Link" menu item message and 2294 // add all mounted volumes (except the one this item lives on) 2295 if (modifierKeys & B_SHIFT_KEY) { 2296 fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink)); 2297 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu *>(fCreateLinkItem->Submenu()), 2298 kCreateRelativeLink, item_ref, false); 2299 } else { 2300 fCreateLinkItem->SetMessage(new BMessage(kCreateLink)); 2301 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu *>(fCreateLinkItem->Submenu()), 2302 kCreateLink, item_ref, false); 2303 } 2304 2305 fMoveToItem->SetEnabled(true); 2306 fCopyToItem->SetEnabled(true); 2307 fCreateLinkItem->SetEnabled(true); 2308 } 2309 2310 2311 uint32 2312 BContainerWindow::ShowDropContextMenu(BPoint loc) 2313 { 2314 BPoint global(loc); 2315 2316 PoseView()->ConvertToScreen(&global); 2317 PoseView()->CommitActivePose(); 2318 BRect mouseRect(global.x, global.y, global.x, global.y); 2319 mouseRect.InsetBy(-5, -5); 2320 2321 // Change the "Create Link" item - allow user to 2322 // create relative links with the Shift key down. 2323 BMenuItem *item = fDropContextMenu->FindItem(kCreateLink); 2324 if (item == NULL) 2325 item = fDropContextMenu->FindItem(kCreateRelativeLink); 2326 if (item && (modifiers() & B_SHIFT_KEY)) { 2327 item->SetLabel("Create Relative Link Here"); 2328 item->SetMessage(new BMessage(kCreateRelativeLink)); 2329 } else if (item) { 2330 item->SetLabel("Create Link Here"); 2331 item->SetMessage(new BMessage(kCreateLink)); 2332 } 2333 2334 item = fDropContextMenu->Go(global, true, true, mouseRect); 2335 if (item) 2336 return item->Command(); 2337 2338 return 0; 2339 } 2340 2341 2342 void 2343 BContainerWindow::ShowContextMenu(BPoint loc, const entry_ref *ref, BView *) 2344 { 2345 ASSERT(IsLocked()); 2346 BPoint global(loc); 2347 PoseView()->ConvertToScreen(&global); 2348 PoseView()->CommitActivePose(); 2349 BRect mouseRect(global.x, global.y, global.x, global.y); 2350 mouseRect.InsetBy(-5, -5); 2351 2352 if (ref) { 2353 // clicked on a pose, show file or volume context menu 2354 Model model(ref); 2355 2356 bool showAsVolume = false; 2357 bool filePanel = PoseView()->IsFilePanel(); 2358 2359 if (Dragging()) { 2360 fContextMenu = NULL; 2361 2362 BEntry entry; 2363 model.GetEntry(&entry); 2364 // 2365 // only show for directories (directory, volume, root) 2366 // 2367 // don't show a popup for the trash or printers 2368 // trash is handled in DeskWindow 2369 // 2370 // since this menu is opened asynchronously 2371 // we need to make sure we don't open it more 2372 // than once, the IsShowing flag is set in 2373 // SlowContextPopup::AttachedToWindow and 2374 // reset in DetachedFromWindow 2375 // see the notes in SlowContextPopup::AttachedToWindow 2376 // 2377 if (!FSIsPrintersDir(&entry) && !fDragContextMenu->IsShowing()) { 2378 // printf("ShowContextMenu - target is %s %i\n", ref->name, IsShowing(ref)); 2379 fDragContextMenu->ClearMenu(); 2380 // 2381 // in case the ref is a symlink, resolve it 2382 // only pop open for directories 2383 BEntry resolvedEntry(ref, true); 2384 if (!resolvedEntry.IsDirectory()) 2385 return; 2386 2387 entry_ref resolvedRef; 2388 resolvedEntry.GetRef(&resolvedRef); 2389 2390 // use the resolved ref for the menu 2391 fDragContextMenu->SetNavDir(&resolvedRef); 2392 fDragContextMenu->SetTypesList(fCachedTypesList); 2393 fDragContextMenu->SetTarget(BMessenger(this)); 2394 BPoseView *poseView = PoseView(); 2395 if (poseView) { 2396 BMessenger tmpTarget(poseView); 2397 fDragContextMenu->InitTrackingHook( 2398 &BPoseView::MenuTrackingHook, &tmpTarget, fDragMessage); 2399 } 2400 2401 // this is now asynchronous so that we don't 2402 // deadlock in Window::Quit, 2403 fDragContextMenu->Go(global, true, false, true); 2404 } 2405 return; 2406 } else if (TargetModel()->IsRoot() || model.IsVolume()) { 2407 fContextMenu = fVolumeContextMenu; 2408 showAsVolume = true; 2409 } else 2410 fContextMenu = fFileContextMenu; 2411 2412 // clean up items from last context menu 2413 2414 if (fContextMenu) { 2415 if (fContextMenu->Window()) 2416 return; 2417 else 2418 MenusEnded(); 2419 2420 if (model.InitCheck() == B_OK) { // ??? Do I need this ??? 2421 if (showAsVolume) { 2422 // non-volume enable/disable copy, move, identify 2423 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, false); 2424 EnableNamedMenuItem(fContextMenu, kMoveToTrash, false); 2425 EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false); 2426 2427 // volume model, enable/disable the Unmount item 2428 bool ejectableVolumeSelected = false; 2429 2430 BVolume boot; 2431 BVolumeRoster().GetBootVolume(&boot); 2432 BVolume volume; 2433 volume.SetTo(model.NodeRef()->device); 2434 if (volume != boot) 2435 ejectableVolumeSelected = true; 2436 2437 EnableNamedMenuItem(fContextMenu, "Unmount", ejectableVolumeSelected); 2438 } 2439 } 2440 2441 SetupNavigationMenu(ref, fContextMenu); 2442 if (!showAsVolume && !filePanel) { 2443 SetupMoveCopyMenus(ref, fContextMenu); 2444 SetupOpenWithMenu(fContextMenu); 2445 } 2446 2447 UpdateMenu(fContextMenu, kPosePopUpContext); 2448 2449 fContextMenu->Go(global, true, false, mouseRect, true); 2450 } 2451 } else if (fWindowContextMenu) { 2452 if (fWindowContextMenu->Window()) 2453 return; 2454 2455 MenusEnded(); 2456 2457 // clicked on a window, show window context menu 2458 2459 SetupNavigationMenu(ref, fWindowContextMenu); 2460 UpdateMenu(fWindowContextMenu, kWindowPopUpContext); 2461 2462 fWindowContextMenu->Go(global, true, false, mouseRect, true); 2463 } 2464 fContextMenu = NULL; 2465 } 2466 2467 2468 void 2469 BContainerWindow::AddFileContextMenus(BMenu *menu) 2470 { 2471 menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); 2472 menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); 2473 menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); 2474 2475 if (!IsTrash() && !InTrash() && !IsPrintersDir()) 2476 menu->AddItem(new BMenuItem("Duplicate", 2477 new BMessage(kDuplicateSelection), 'D')); 2478 2479 if (!IsTrash() && !InTrash()) { 2480 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? 2481 "Delete" : "Move to Trash", 2482 new BMessage(kMoveToTrash), 'T')); 2483 2484 // add separator for copy to/move to items (navigation items) 2485 menu->AddSeparatorItem(); 2486 } else { 2487 menu->AddItem(new BMenuItem("Delete", new BMessage(kDelete), 0)); 2488 menu->AddItem(new BMenuItem("Restore", new BMessage(kRestoreFromTrash), 0)); 2489 } 2490 2491 menu->AddSeparatorItem(); 2492 BMenuItem *cutItem, *copyItem; 2493 menu->AddItem(cutItem = new BMenuItem("Cut", new BMessage(B_CUT), 'X')); 2494 menu->AddItem(copyItem = new BMenuItem("Copy", new BMessage(B_COPY), 'C')); 2495 2496 menu->AddSeparatorItem(); 2497 menu->AddItem(new BMenuItem("Identify", new BMessage(kIdentifyEntry))); 2498 BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); 2499 addOnMenuItem->SetFont(be_plain_font); 2500 menu->AddItem(addOnMenuItem); 2501 2502 // set targets as needed 2503 menu->SetTargetForItems(PoseView()); 2504 cutItem->SetTarget(this); 2505 copyItem->SetTarget(this); 2506 } 2507 2508 2509 void 2510 BContainerWindow::AddVolumeContextMenus(BMenu *menu) 2511 { 2512 menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); 2513 menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); 2514 menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); 2515 2516 menu->AddSeparatorItem(); 2517 menu->AddItem(new MountMenu("Mount")); 2518 2519 BMenuItem *item = new BMenuItem("Unmount", new BMessage(kUnmountVolume), 'U'); 2520 item->SetEnabled(false); 2521 menu->AddItem(item); 2522 2523 menu->AddSeparatorItem(); 2524 menu->AddItem(new BMenu(kAddOnsMenuName)); 2525 2526 menu->SetTargetForItems(PoseView()); 2527 } 2528 2529 2530 void 2531 BContainerWindow::AddWindowContextMenus(BMenu *menu) 2532 { 2533 // create context sensitive menu for empty area of window 2534 // since we check view mode before display, this should be a radio 2535 // mode menu 2536 2537 bool needSeparator = true; 2538 if (IsTrash()) 2539 menu->AddItem(new BMenuItem("Empty Trash", new BMessage(kEmptyTrash))); 2540 else if (IsPrintersDir()) 2541 menu->AddItem(new BMenuItem("Add Printer"B_UTF8_ELLIPSIS, new BMessage(kAddPrinter), 'N')); 2542 else if (InTrash()) 2543 needSeparator = false; 2544 else { 2545 TemplatesMenu *templateMenu = new TemplatesMenu(PoseView()); 2546 menu->AddItem(templateMenu); 2547 templateMenu->SetTargetForItems(PoseView()); 2548 templateMenu->SetFont(be_plain_font); 2549 } 2550 2551 if (needSeparator) 2552 menu->AddSeparatorItem(); 2553 2554 menu->AddItem(new BMenuItem("Icon View", new BMessage(kIconMode), '1')); 2555 menu->AddItem(new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode), '2')); 2556 menu->AddItem(new BMenuItem("List View", new BMessage(kListMode), '3')); 2557 menu->AddSeparatorItem(); 2558 BMenuItem *pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'); 2559 menu->AddItem(pasteItem); 2560 menu->AddSeparatorItem(); 2561 BMenuItem *resizeItem = new BMenuItem("Resize to Fit", 2562 new BMessage(kResizeToFit), 'Y'); 2563 menu->AddItem(resizeItem); 2564 menu->AddItem(new BMenuItem("Clean Up", new BMessage(kCleanup), 'K')); 2565 menu->AddItem(new BMenuItem("Select"B_UTF8_ELLIPSIS, 2566 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); 2567 menu->AddItem(new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A')); 2568 if (!IsTrash()) 2569 menu->AddItem(new BMenuItem("Open Parent", new BMessage(kOpenParentDir), 2570 B_UP_ARROW)); 2571 2572 BMenuItem *closeItem = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), 2573 'W'); 2574 menu->AddItem(closeItem); 2575 menu->AddSeparatorItem(); 2576 BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); 2577 addOnMenuItem->SetFont(be_plain_font); 2578 menu->AddItem(addOnMenuItem); 2579 2580 #if DEBUG 2581 menu->AddSeparatorItem(); 2582 BMenuItem *testing = new BMenuItem("Test Icon Cache", new BMessage(kTestIconCache)); 2583 menu->AddItem(testing); 2584 #endif 2585 2586 // target items as needed 2587 menu->SetTargetForItems(PoseView()); 2588 closeItem->SetTarget(this); 2589 resizeItem->SetTarget(this); 2590 pasteItem->SetTarget(this); 2591 } 2592 2593 2594 void 2595 BContainerWindow::AddDropContextMenus(BMenu *menu) 2596 { 2597 menu->AddItem(new BMenuItem("Create Link Here", new BMessage(kCreateLink))); 2598 menu->AddItem(new BMenuItem("Move Here", new BMessage(kMoveSelectionTo))); 2599 menu->AddItem(new BMenuItem("Copy Here", new BMessage(kCopySelectionTo))); 2600 menu->AddSeparatorItem(); 2601 menu->AddItem(new BMenuItem("Cancel", new BMessage(kCancelButton))); 2602 } 2603 2604 2605 void 2606 BContainerWindow::EachAddon(bool (*eachAddon)(const Model *, const char *, 2607 uint32 shortcut, bool primary, void *context), void *passThru) 2608 { 2609 BObjectList<Model> uniqueList(10, true); 2610 BPath path; 2611 bool bail = false; 2612 if (find_directory(B_BEOS_ADDONS_DIRECTORY, &path) == B_OK) 2613 bail = EachAddon(path, eachAddon, &uniqueList, passThru); 2614 2615 if (!bail && find_directory(B_USER_ADDONS_DIRECTORY, &path) == B_OK) 2616 bail = EachAddon(path, eachAddon, &uniqueList, passThru); 2617 2618 if (!bail && find_directory(B_COMMON_ADDONS_DIRECTORY, &path) == B_OK) 2619 EachAddon(path, eachAddon, &uniqueList, passThru); 2620 } 2621 2622 2623 bool 2624 BContainerWindow::EachAddon(BPath &path, bool (*eachAddon)(const Model *, 2625 const char *, uint32 shortcut, bool primary, void *), 2626 BObjectList<Model> *uniqueList, void *params) 2627 { 2628 path.Append("Tracker"); 2629 2630 BDirectory dir; 2631 BEntry entry; 2632 2633 if (dir.SetTo(path.Path()) != B_OK) 2634 return false; 2635 2636 // build a list of the MIME types of the selected items 2637 2638 BObjectList<BString> mimeTypes(10, true); 2639 2640 int32 count = PoseView()->SelectionList()->CountItems(); 2641 if (!count) { 2642 // just add the type of the current directory 2643 AddMimeTypeString(mimeTypes, TargetModel()); 2644 } else { 2645 for (int32 index = 0; index < count; index++) { 2646 BPose *pose = PoseView()->SelectionList()->ItemAt(index); 2647 AddMimeTypeString(mimeTypes, pose->TargetModel()); 2648 } 2649 } 2650 2651 dir.Rewind(); 2652 while (dir.GetNextEntry(&entry) == B_OK) { 2653 bool primary = false; 2654 2655 if (entry.IsSymLink()) { 2656 // resolve symlinks if needed 2657 entry_ref ref; 2658 entry.GetRef(&ref); 2659 entry.SetTo(&ref, true); 2660 } 2661 2662 Model *model = new Model(&entry); 2663 if (model->InitCheck() != B_OK || !model->IsExecutable()) { 2664 delete model; 2665 continue; 2666 } 2667 2668 // check if it supports at least one of the selected entries 2669 2670 if (mimeTypes.CountItems()) { 2671 BFile file(&entry, B_READ_ONLY); 2672 if (file.InitCheck() == B_OK) { 2673 BAppFileInfo info(&file); 2674 if (info.InitCheck() == B_OK) { 2675 bool secondary = true; 2676 2677 // does this add-on has types set at all? 2678 BMessage message; 2679 if (info.GetSupportedTypes(&message) == B_OK) { 2680 type_code type; 2681 int32 count; 2682 if (message.GetInfo("types", &type, &count) == B_OK) 2683 secondary = false; 2684 } 2685 2686 // check all supported types if it has some set 2687 if (!secondary) { 2688 for (int32 i = mimeTypes.CountItems(); !primary && i-- > 0;) { 2689 BString *type = mimeTypes.ItemAt(i); 2690 if (info.IsSupportedType(type->String())) { 2691 BMimeType mimeType(type->String()); 2692 if (info.Supports(&mimeType)) 2693 primary = true; 2694 else 2695 secondary = true; 2696 } 2697 } 2698 } 2699 2700 if (!secondary && !primary) { 2701 delete model; 2702 continue; 2703 } 2704 } 2705 } 2706 } 2707 2708 char name[B_FILE_NAME_LENGTH]; 2709 uint32 key; 2710 StripShortcut(model, name, key); 2711 2712 // do a uniqueness check 2713 if (uniqueList->EachElement(MatchOne, name)) { 2714 // found one already in the list 2715 delete model; 2716 continue; 2717 } 2718 uniqueList->AddItem(model); 2719 2720 if ((eachAddon)(model, name, key, primary, params)) 2721 return true; 2722 } 2723 return false; 2724 } 2725 2726 2727 void 2728 BContainerWindow::BuildAddOnMenu(BMenu *menu) 2729 { 2730 BMenuItem *item = menu->FindItem(kAddOnsMenuName); 2731 if (menu->IndexOf(item) == 0) { 2732 // the folder of the context menu seems to be named "Add-Ons" 2733 // so we just take the last menu item, which is correct if not 2734 // build with debug option 2735 item = menu->ItemAt(menu->CountItems() - 1); 2736 } 2737 if (item == NULL) 2738 return; 2739 2740 menu = item->Submenu(); 2741 if (!menu) 2742 return; 2743 2744 menu->SetFont(be_plain_font); 2745 2746 // found the addons menu, empty it first 2747 for (;;) { 2748 item = menu->RemoveItem(0L); 2749 if (!item) 2750 break; 2751 delete item; 2752 } 2753 2754 BObjectList<BMenuItem> primaryList; 2755 BObjectList<BMenuItem> secondaryList; 2756 2757 AddOneAddonParams params; 2758 params.primaryList = &primaryList; 2759 params.secondaryList = &secondaryList; 2760 2761 EachAddon(AddOneAddon, ¶ms); 2762 2763 primaryList.SortItems(CompareLabels); 2764 secondaryList.SortItems(CompareLabels); 2765 2766 int32 count = primaryList.CountItems(); 2767 for (int32 index = 0; index < count; index++) 2768 menu->AddItem(primaryList.ItemAt(index)); 2769 2770 if (count != 0) 2771 menu->AddSeparatorItem(); 2772 2773 count = secondaryList.CountItems(); 2774 for (int32 index = 0; index < count; index++) 2775 menu->AddItem(secondaryList.ItemAt(index)); 2776 2777 menu->SetTargetForItems(this); 2778 } 2779 2780 2781 void 2782 BContainerWindow::UpdateMenu(BMenu *menu, UpdateMenuContext context) 2783 { 2784 const int32 selectCount = PoseView()->SelectionList()->CountItems(); 2785 const int32 count = PoseView()->CountItems(); 2786 2787 if (context == kMenuBarContext) { 2788 EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0); 2789 EnableNamedMenuItem(menu, kGetInfo, selectCount > 0); 2790 EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0); 2791 EnableNamedMenuItem(menu, kMoveToTrash, selectCount > 0); 2792 EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0); 2793 EnableNamedMenuItem(menu, kDelete, selectCount > 0); 2794 EnableNamedMenuItem(menu, kDuplicateSelection, selectCount > 0); 2795 } 2796 2797 if (context == kMenuBarContext || context == kPosePopUpContext) { 2798 SetUpEditQueryItem(menu); 2799 EnableNamedMenuItem(menu, kEditItem, selectCount == 1 2800 && (context == kPosePopUpContext || !PoseView()->ActivePose())); 2801 SetCutItem(menu); 2802 SetCopyItem(menu); 2803 SetPasteItem(menu); 2804 } 2805 2806 if (context == kMenuBarContext || context == kWindowPopUpContext) { 2807 MarkNamedMenuItem(menu, kIconMode, PoseView()->ViewMode() == kIconMode); 2808 MarkNamedMenuItem(menu, kListMode, PoseView()->ViewMode() == kListMode); 2809 MarkNamedMenuItem(menu, kMiniIconMode, 2810 PoseView()->ViewMode() == kMiniIconMode); 2811 2812 SetCloseItem(menu); 2813 SetCleanUpItem(menu); 2814 SetPasteItem(menu); 2815 2816 EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsRoot()); 2817 EnableNamedMenuItem(menu, kEmptyTrash, count > 0); 2818 EnableNamedMenuItem(menu, B_SELECT_ALL, count > 0); 2819 2820 BMenuItem *item = menu->FindItem(kTemplatesMenuName); 2821 if (item) { 2822 TemplatesMenu *templateMenu = dynamic_cast<TemplatesMenu *>( 2823 item->Submenu()); 2824 if (templateMenu) 2825 templateMenu->UpdateMenuState(); 2826 } 2827 } 2828 2829 BuildAddOnMenu(menu); 2830 } 2831 2832 2833 void 2834 BContainerWindow::LoadAddOn(BMessage *message) 2835 { 2836 UpdateIfNeeded(); 2837 2838 entry_ref addonRef; 2839 status_t result = message->FindRef("refs", &addonRef); 2840 if (result != B_OK) { 2841 char buffer[1024]; 2842 sprintf(buffer, "Error %s loading Add-On %s.", strerror(result), addonRef.name); 2843 (new BAlert("", buffer, "Cancel", 0, 0, 2844 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 2845 return; 2846 } 2847 2848 // add selected refs to message 2849 BMessage *refs = new BMessage(B_REFS_RECEIVED); 2850 2851 BObjectList<BPose> *list = PoseView()->SelectionList(); 2852 2853 int32 index = 0; 2854 BPose *pose; 2855 while ((pose = list->ItemAt(index++)) != NULL) 2856 refs->AddRef("refs", pose->TargetModel()->EntryRef()); 2857 2858 refs->AddMessenger("TrackerViewToken", BMessenger(PoseView())); 2859 2860 LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs, addonRef, 2861 *TargetModel()->EntryRef()); 2862 } 2863 2864 2865 BMenuItem * 2866 BContainerWindow::NewAttributeMenuItem(const char *label, const char *attrName, 2867 int32 attrType, float attrWidth, int32 attrAlign, bool attrEditable, bool attrStatField) 2868 { 2869 BMessage *message = new BMessage(kAttributeItem); 2870 message->AddString("attr_name", attrName); 2871 message->AddInt32("attr_type", attrType); 2872 message->AddInt32("attr_hash", (int32)AttrHashString(attrName, (uint32)attrType)); 2873 message->AddFloat("attr_width", attrWidth); 2874 message->AddInt32("attr_align", attrAlign); 2875 message->AddBool("attr_editable", attrEditable); 2876 message->AddBool("attr_statfield", attrStatField); 2877 2878 BMenuItem *menuItem = new BMenuItem(label, message); 2879 menuItem->SetTarget(PoseView()); 2880 2881 return menuItem; 2882 } 2883 2884 2885 void 2886 BContainerWindow::NewAttributeMenu(BMenu *menu) 2887 { 2888 ASSERT(PoseView()); 2889 2890 BMenuItem *item; 2891 menu->AddItem(item = new BMenuItem("Copy Attributes", new BMessage(kCopyAttributes))); 2892 item->SetTarget(PoseView()); 2893 menu->AddItem(item = new BMenuItem("Paste Attributes", new BMessage(kPasteAttributes))); 2894 item->SetTarget(PoseView()); 2895 menu->AddSeparatorItem(); 2896 2897 menu->AddItem(NewAttributeMenuItem ("Name", kAttrStatName, B_STRING_TYPE, 2898 145, B_ALIGN_LEFT, true, true)); 2899 2900 menu->AddItem(NewAttributeMenuItem ("Size", kAttrStatSize, B_OFF_T_TYPE, 2901 80, B_ALIGN_RIGHT, false, true)); 2902 2903 menu->AddItem(NewAttributeMenuItem ("Modified", kAttrStatModified, B_TIME_TYPE, 2904 150, B_ALIGN_LEFT, false, true)); 2905 2906 menu->AddItem(NewAttributeMenuItem ("Created", kAttrStatCreated, B_TIME_TYPE, 2907 150, B_ALIGN_LEFT, false, true)); 2908 2909 menu->AddItem(NewAttributeMenuItem ("Kind", kAttrMIMEType, B_MIME_STRING_TYPE, 2910 145, B_ALIGN_LEFT, false, false)); 2911 2912 if (IsTrash() || InTrash()) 2913 menu->AddItem(NewAttributeMenuItem ("Original name", kAttrOriginalPath, B_STRING_TYPE, 2914 225, B_ALIGN_LEFT, false, false)); 2915 else 2916 menu->AddItem(NewAttributeMenuItem ("Path", kAttrPath, B_STRING_TYPE, 2917 225, B_ALIGN_LEFT, false, false)); 2918 2919 #ifdef OWNER_GROUP_ATTRIBUTES 2920 menu->AddItem(NewAttributeMenuItem ("Owner", kAttrStatOwner, B_STRING_TYPE, 2921 60, B_ALIGN_LEFT, false, true)); 2922 2923 menu->AddItem(NewAttributeMenuItem ("Group", kAttrStatGroup, B_STRING_TYPE, 2924 60, B_ALIGN_LEFT, false, true)); 2925 #endif 2926 2927 menu->AddItem(NewAttributeMenuItem ("Permissions", kAttrStatMode, B_STRING_TYPE, 2928 80, B_ALIGN_LEFT, false, true)); 2929 } 2930 2931 2932 void 2933 BContainerWindow::ShowAttributeMenu() 2934 { 2935 ASSERT(fAttrMenu); 2936 fMenuBar->AddItem(fAttrMenu); 2937 } 2938 2939 2940 void 2941 BContainerWindow::HideAttributeMenu() 2942 { 2943 ASSERT(fAttrMenu); 2944 fMenuBar->RemoveItem(fAttrMenu); 2945 } 2946 2947 2948 void 2949 BContainerWindow::MarkAttributeMenu() 2950 { 2951 MarkAttributeMenu(fAttrMenu); 2952 } 2953 2954 2955 void 2956 BContainerWindow::MarkAttributeMenu(BMenu *menu) 2957 { 2958 if (!menu) 2959 return; 2960 2961 int32 count = menu->CountItems(); 2962 for (int32 index = 0; index < count; index++) { 2963 BMenuItem *item = menu->ItemAt(index); 2964 int32 attrHash; 2965 if (item->Message()) 2966 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) 2967 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); 2968 else 2969 item->SetMarked(false); 2970 2971 BMenu *submenu = item->Submenu(); 2972 if (submenu) { 2973 int32 count2 = submenu->CountItems(); 2974 for (int32 subindex = 0; subindex < count2; subindex++) { 2975 item = submenu->ItemAt(subindex); 2976 if (item->Message()) 2977 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) 2978 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); 2979 else 2980 item->SetMarked(false); 2981 } 2982 } 2983 } 2984 } 2985 2986 2987 void 2988 BContainerWindow::AddMimeTypesToMenu() 2989 { 2990 AddMimeTypesToMenu(fAttrMenu); 2991 } 2992 2993 2994 void 2995 BContainerWindow::AddMimeTypesToMenu(BMenu *menu) 2996 { 2997 if (!menu) 2998 return; 2999 3000 // find start of mime types in menu 3001 int32 count = menu->CountItems(); 3002 int32 start; 3003 3004 for (start = 0; start < count; start++) { 3005 if (menu->ItemAt(start)->Submenu()) 3006 break; 3007 } 3008 3009 // Remove old mime menu: 3010 int32 removeIndex = count - 1; 3011 while (menu->ItemAt(removeIndex)->Submenu() != NULL) { 3012 delete menu->RemoveItem(removeIndex); 3013 removeIndex--; 3014 } 3015 3016 // Add a separator item if there is none yet 3017 if (dynamic_cast<BSeparatorItem *>(menu->ItemAt(removeIndex)) == NULL) 3018 menu->AddSeparatorItem(); 3019 3020 int32 typeCount = PoseView()->CountMimeTypes(); 3021 3022 for (int32 index = 0; index < typeCount; index++) { 3023 3024 bool shouldAdd = true; 3025 const char *signature = PoseView()->MimeTypeAt(index); 3026 3027 for (int32 subindex = start; subindex < count; subindex++) { 3028 BMenuItem *item = menu->ItemAt(subindex); 3029 if (!item) 3030 continue; 3031 BMessage *message = item->Message(); 3032 if (!message) 3033 continue; 3034 const char *str; 3035 if (message->FindString("mimetype", &str) == B_OK 3036 && strcmp(signature, str) == 0) { 3037 shouldAdd = false; 3038 break; 3039 } 3040 } 3041 3042 if (shouldAdd) { 3043 BMessage attr_msg; 3044 char desc[B_MIME_TYPE_LENGTH]; 3045 const char *nameToAdd = signature; 3046 3047 BMimeType mimetype(signature); 3048 3049 if (!mimetype.IsInstalled()) 3050 continue; 3051 3052 // only add things to menu which have "user-visible" data 3053 if (mimetype.GetAttrInfo(&attr_msg) != B_OK) 3054 continue; 3055 3056 if (mimetype.GetShortDescription(desc) == B_OK && desc[0]) 3057 nameToAdd = desc; 3058 3059 // go through each field in meta mime and add it to a menu 3060 BMenu *localMenu = 0; 3061 int32 index = -1; 3062 const char *str; 3063 3064 while (attr_msg.FindString("attr:public_name", ++index, &str) == B_OK) { 3065 if (!attr_msg.FindBool("attr:viewable", index)) 3066 // don't add if attribute not viewable 3067 continue; 3068 3069 int32 type; 3070 int32 align; 3071 int32 width; 3072 bool editable; 3073 3074 const char *attrName; 3075 3076 if (attr_msg.FindString("attr:name", index, &attrName) != B_OK) 3077 continue; 3078 3079 if (attr_msg.FindInt32("attr:type", index, &type) != B_OK) 3080 continue; 3081 3082 if (attr_msg.FindBool("attr:editable", index, &editable) != B_OK) 3083 continue; 3084 3085 if (attr_msg.FindInt32("attr:width", index, &width) != B_OK) 3086 continue; 3087 3088 if (attr_msg.FindInt32("attr:alignment", index, &align) != B_OK) 3089 continue; 3090 3091 if (!localMenu) { 3092 // do a lazy allocation of the menu 3093 localMenu = new BMenu(nameToAdd); 3094 BFont font; 3095 menu->GetFont(&font); 3096 localMenu->SetFont(&font); 3097 } 3098 localMenu->AddItem(NewAttributeMenuItem (str, attrName, type, 3099 width, align, editable, false)); 3100 } 3101 if (localMenu) { 3102 BMessage *message = new BMessage(kMIMETypeItem); 3103 message->AddString("mimetype", signature); 3104 menu->AddItem(new IconMenuItem(localMenu, message, signature, B_MINI_ICON)); 3105 } 3106 } 3107 } 3108 3109 // remove separator if it's the only item in menu 3110 BMenuItem *item = menu->ItemAt(menu->CountItems() - 1); 3111 if (dynamic_cast<BSeparatorItem *>(item) != NULL) { 3112 menu->RemoveItem(item); 3113 delete item; 3114 } 3115 3116 MarkAttributeMenu(menu); 3117 } 3118 3119 3120 BHandler * 3121 BContainerWindow::ResolveSpecifier(BMessage *message, int32 index, 3122 BMessage *specifier, int32 form, const char *property) 3123 { 3124 if (strcmp(property, "Poses") == 0) { 3125 // PRINT(("BContainerWindow::ResolveSpecifier %s\n", property)); 3126 message->PopSpecifier(); 3127 return PoseView(); 3128 } 3129 3130 return _inherited::ResolveSpecifier(message, index, specifier, 3131 form, property); 3132 } 3133 3134 3135 PiggybackTaskLoop * 3136 BContainerWindow::DelayedTaskLoop() 3137 { 3138 if (!fTaskLoop) 3139 fTaskLoop = new PiggybackTaskLoop; 3140 3141 return fTaskLoop; 3142 } 3143 3144 3145 bool 3146 BContainerWindow::NeedsDefaultStateSetup() 3147 { 3148 if (!TargetModel()) 3149 return false; 3150 3151 if (TargetModel()->IsRoot()) 3152 // don't try to set up anything if we are root 3153 return false; 3154 3155 WindowStateNodeOpener opener(this, false); 3156 if (!opener.StreamNode()) 3157 // can't read state, give up 3158 return false; 3159 3160 return !NodeHasSavedState(opener.Node()); 3161 } 3162 3163 3164 bool 3165 BContainerWindow::DefaultStateSourceNode(const char *name, BNode *result, 3166 bool createNew, bool createFolder) 3167 { 3168 // PRINT(("looking for default state in tracker settings dir\n")); 3169 BPath settingsPath; 3170 if (FSFindTrackerSettingsDir(&settingsPath) != B_OK) 3171 return false; 3172 3173 BDirectory dir(settingsPath.Path()); 3174 3175 BPath path(settingsPath); 3176 path.Append(name); 3177 if (!BEntry(path.Path()).Exists()) { 3178 3179 if (!createNew) 3180 return false; 3181 3182 BPath tmpPath(settingsPath); 3183 for (;;) { 3184 // deal with several levels of folders 3185 const char *nextSlash = strchr(name, '/'); 3186 if (!nextSlash) 3187 break; 3188 3189 BString tmp; 3190 tmp.SetTo(name, nextSlash - name); 3191 tmpPath.Append(tmp.String()); 3192 3193 3194 mkdir(tmpPath.Path(), 0777); 3195 3196 name = nextSlash + 1; 3197 if (!name[0]) { 3198 // can't deal with a slash at end 3199 3200 return false; 3201 } 3202 } 3203 3204 if (createFolder) { 3205 if (mkdir(path.Path(), 0777) < 0) 3206 return false; 3207 } else { 3208 BFile file; 3209 if (dir.CreateFile(name, &file) != B_OK) 3210 return false; 3211 } 3212 } 3213 3214 // PRINT(("using default state from %s\n", path.Path())); 3215 result->SetTo(path.Path()); 3216 return result->InitCheck() == B_OK; 3217 } 3218 3219 3220 void 3221 BContainerWindow::SetUpDefaultState() 3222 { 3223 BNode defaultingNode; 3224 // this is where we'll ulitimately get the state from 3225 bool gotDefaultingNode = 0; 3226 bool shouldStagger = false; 3227 3228 ASSERT(TargetModel()); 3229 3230 PRINT(("folder %s does not have any saved state\n", TargetModel()->Name())); 3231 3232 WindowStateNodeOpener opener(this, true); 3233 // this is our destination node, whatever it is for this window 3234 if (!opener.StreamNode()) 3235 return; 3236 3237 if (!TargetModel()->IsRoot()) { 3238 BDirectory desktop; 3239 FSGetDeskDir(&desktop, TargetModel()->EntryRef()->device); 3240 3241 // try copying state from our parent directory, unless it is the desktop folder 3242 BEntry entry(TargetModel()->EntryRef()); 3243 BDirectory parent; 3244 if (entry.GetParent(&parent) == B_OK && parent != desktop) { 3245 PRINT(("looking at parent for state\n")); 3246 if (NodeHasSavedState(&parent)) { 3247 PRINT(("got state from parent\n")); 3248 defaultingNode = parent; 3249 gotDefaultingNode = true; 3250 // when getting state from parent, stagger the window 3251 shouldStagger = true; 3252 } 3253 } 3254 } 3255 3256 if (!gotDefaultingNode 3257 // parent didn't have any state, use the template directory from 3258 // tracker settings folder for what our state should be 3259 // For simplicity we are not picking up the most recent 3260 // changes that didn't get committed if home is still open in 3261 // a window, that's probably not a problem; would be OK if state got committed 3262 // after every change 3263 && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode, true)) 3264 return; 3265 3266 // copy over the attributes 3267 3268 // set up a filter of the attributes we want copied 3269 const char *allowAttrs[] = { 3270 kAttrWindowFrame, 3271 kAttrWindowWorkspace, 3272 kAttrViewState, 3273 kAttrViewStateForeign, 3274 kAttrColumns, 3275 kAttrColumnsForeign, 3276 0 3277 }; 3278 3279 // copy over attributes that apply; transform them properly, stripping 3280 // parts that do not apply, adding a window stagger, etc. 3281 3282 StaggerOneParams params; 3283 params.rectFromParent = shouldStagger; 3284 SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame, OffsetFrameOne, ¶ms); 3285 SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState, 3286 ClearViewOriginOne, ¶ms); 3287 3288 // do it 3289 AttributeStreamMemoryNode memoryNode; 3290 NamesToAcceptAttrFilter filter(allowAttrs); 3291 AttributeStreamFileNode fileNode(&defaultingNode); 3292 3293 *opener.StreamNode() << scrollOriginCleaner << frameOffsetter 3294 << memoryNode << filter << fileNode; 3295 } 3296 3297 3298 void 3299 BContainerWindow::RestoreWindowState(AttributeStreamNode *node) 3300 { 3301 if (!node || dynamic_cast<BDeskWindow *>(this)) 3302 // don't restore any window state if we are a desktop window 3303 return; 3304 3305 const char *rectAttributeName; 3306 const char *workspaceAttributeName; 3307 if (TargetModel()->IsRoot()) { 3308 rectAttributeName = kAttrDisksFrame; 3309 workspaceAttributeName = kAttrDisksWorkspace; 3310 } else { 3311 rectAttributeName = kAttrWindowFrame; 3312 workspaceAttributeName = kAttrWindowWorkspace; 3313 } 3314 3315 BRect frame(Frame()); 3316 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) == sizeof(BRect)) { 3317 MoveTo(frame.LeftTop()); 3318 ResizeTo(frame.Width(), frame.Height()); 3319 } else 3320 sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); 3321 3322 uint32 workspace; 3323 if ((fContainerWindowFlags & kRestoreWorkspace) 3324 && node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), &workspace) == sizeof(uint32)) 3325 SetWorkspaces(workspace); 3326 3327 if (fContainerWindowFlags & kIsHidden) 3328 Minimize(true); 3329 3330 #if __HAIKU__ 3331 // restore window decor settings 3332 int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE); 3333 if (size > 0) { 3334 char buffer[size]; 3335 if ((fContainerWindowFlags & kRestoreDecor) 3336 && node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer) == size) { 3337 BMessage decorSettings; 3338 if (decorSettings.Unflatten(buffer) == B_OK) 3339 SetDecoratorSettings(decorSettings); 3340 } 3341 } 3342 #endif // __HAIKU__ 3343 } 3344 3345 3346 void 3347 BContainerWindow::RestoreWindowState(const BMessage &message) 3348 { 3349 if (dynamic_cast<BDeskWindow *>(this)) 3350 // don't restore any window state if we are a desktop window 3351 return; 3352 3353 const char *rectAttributeName; 3354 const char *workspaceAttributeName; 3355 if (TargetModel()->IsRoot()) { 3356 rectAttributeName = kAttrDisksFrame; 3357 workspaceAttributeName = kAttrDisksWorkspace; 3358 } else { 3359 rectAttributeName = kAttrWindowFrame; 3360 workspaceAttributeName = kAttrWindowWorkspace; 3361 } 3362 3363 BRect frame(Frame()); 3364 if (message.FindRect(rectAttributeName, &frame) == B_OK) { 3365 MoveTo(frame.LeftTop()); 3366 ResizeTo(frame.Width(), frame.Height()); 3367 } else 3368 sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); 3369 3370 uint32 workspace; 3371 if ((fContainerWindowFlags & kRestoreWorkspace) 3372 && message.FindInt32(workspaceAttributeName, (int32 *)&workspace) == B_OK) 3373 SetWorkspaces(workspace); 3374 3375 if (fContainerWindowFlags & kIsHidden) 3376 Minimize(true); 3377 3378 #if __HAIKU__ 3379 // restore window decor settings 3380 BMessage decorSettings; 3381 if ((fContainerWindowFlags & kRestoreDecor) 3382 && message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) { 3383 SetDecoratorSettings(decorSettings); 3384 } 3385 #endif // __HAIKU__ 3386 } 3387 3388 3389 void 3390 BContainerWindow::SaveWindowState(AttributeStreamNode *node) 3391 { 3392 ASSERT(node); 3393 const char *rectAttributeName; 3394 const char *workspaceAttributeName; 3395 if (TargetModel() && TargetModel()->IsRoot()) { 3396 rectAttributeName = kAttrDisksFrame; 3397 workspaceAttributeName = kAttrDisksWorkspace; 3398 } else { 3399 rectAttributeName = kAttrWindowFrame; 3400 workspaceAttributeName = kAttrWindowWorkspace; 3401 } 3402 3403 // node is null if it already got deleted 3404 BRect frame(Frame()); 3405 node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame); 3406 3407 uint32 workspaces = Workspaces(); 3408 node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), 3409 &workspaces); 3410 3411 #if __HAIKU__ 3412 BMessage decorSettings; 3413 if (GetDecoratorSettings(&decorSettings) == B_OK) { 3414 int32 size = decorSettings.FlattenedSize(); 3415 char buffer[size]; 3416 if (decorSettings.Flatten(buffer, size) == B_OK) { 3417 node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer); 3418 } 3419 } 3420 #endif // __HAIKU__ 3421 } 3422 3423 3424 void 3425 BContainerWindow::SaveWindowState(BMessage &message) const 3426 { 3427 const char *rectAttributeName; 3428 const char *workspaceAttributeName; 3429 3430 if (TargetModel() && TargetModel()->IsRoot()) { 3431 rectAttributeName = kAttrDisksFrame; 3432 workspaceAttributeName = kAttrDisksWorkspace; 3433 } else { 3434 rectAttributeName = kAttrWindowFrame; 3435 workspaceAttributeName = kAttrWindowWorkspace; 3436 } 3437 3438 // node is null if it already got deleted 3439 BRect frame(Frame()); 3440 message.AddRect(rectAttributeName, frame); 3441 message.AddInt32(workspaceAttributeName, (int32)Workspaces()); 3442 3443 #if __HAIKU__ 3444 BMessage decorSettings; 3445 if (GetDecoratorSettings(&decorSettings) == B_OK) { 3446 message.AddMessage(kAttrWindowDecor, &decorSettings); 3447 } 3448 #endif // __HAIKU__ 3449 } 3450 3451 3452 status_t 3453 BContainerWindow::DragStart(const BMessage *incoming) 3454 { 3455 if (!incoming) 3456 return B_ERROR; 3457 3458 // if already dragging, or 3459 // if all the refs match 3460 if (Dragging() && SpringLoadedFolderCompareMessages(incoming, fDragMessage)) 3461 return B_OK; 3462 3463 // cache the current drag message 3464 // build a list of the mimetypes in the message 3465 SpringLoadedFolderCacheDragData(incoming, &fDragMessage, &fCachedTypesList); 3466 3467 fWaitingForRefs = true; 3468 3469 return B_OK; 3470 } 3471 3472 3473 void 3474 BContainerWindow::DragStop() 3475 { 3476 delete fDragMessage; 3477 fDragMessage = NULL; 3478 3479 delete fCachedTypesList; 3480 fCachedTypesList = NULL; 3481 3482 fWaitingForRefs = false; 3483 } 3484 3485 3486 void 3487 BContainerWindow::ShowSelectionWindow() 3488 { 3489 if (fSelectionWindow == NULL) { 3490 fSelectionWindow = new SelectionWindow(this); 3491 fSelectionWindow->Show(); 3492 } else if (fSelectionWindow->Lock()) { 3493 if (fSelectionWindow->IsHidden()) { 3494 fSelectionWindow->MoveCloseToMouse(); 3495 fSelectionWindow->Show(); 3496 } 3497 fSelectionWindow->Unlock(); 3498 } 3499 } 3500 3501 3502 void 3503 BContainerWindow::ShowNavigator(bool show) 3504 { 3505 if (PoseView()->IsDesktopWindow()) 3506 return; 3507 3508 if (show) { 3509 if (Navigator() && !Navigator()->IsHidden()) 3510 return; 3511 3512 if (Navigator() == NULL) { 3513 BRect rect(Bounds()); 3514 rect.top = KeyMenuBar()->Bounds().Height() + 1; 3515 rect.bottom = rect.top + BNavigator::CalcNavigatorHeight(); 3516 fNavigator = new BNavigator(TargetModel(), rect); 3517 AddChild(fNavigator); 3518 } 3519 3520 if (Navigator()->IsHidden()) { 3521 if (Navigator()->Bounds().top == 0) 3522 Navigator()->MoveTo(0, KeyMenuBar()->Bounds().Height() + 1); 3523 // This is if the navigator was created with a .top = 0. 3524 Navigator()->Show(); 3525 } 3526 3527 float displacement = Navigator()->Frame().Height() + 1; 3528 3529 PoseView()->MoveBy(0, displacement); 3530 PoseView()->ResizeBy(0, -displacement); 3531 3532 if (PoseView()->VScrollBar()) { 3533 PoseView()->VScrollBar()->MoveBy(0, displacement); 3534 PoseView()->VScrollBar()->ResizeBy(0, -displacement); 3535 PoseView()->UpdateScrollRange(); 3536 } 3537 } else { 3538 if (!Navigator() || Navigator()->IsHidden()) 3539 return; 3540 3541 float displacement = Navigator()->Frame().Height() + 1; 3542 3543 PoseView()->ResizeBy(0, displacement); 3544 PoseView()->MoveBy(0, -displacement); 3545 3546 if (PoseView()->VScrollBar()) { 3547 PoseView()->VScrollBar()->ResizeBy(0, displacement); 3548 PoseView()->VScrollBar()->MoveBy(0, -displacement); 3549 PoseView()->UpdateScrollRange(); 3550 } 3551 3552 fNavigator->Hide(); 3553 } 3554 } 3555 3556 3557 void 3558 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled) 3559 { 3560 if (PoseView()->IsDesktopWindow()) 3561 return; 3562 3563 if (enabled) { 3564 if (!Navigator()) 3565 return; 3566 3567 RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 3568 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY); 3569 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY | B_CONTROL_KEY); 3570 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_CONTROL_KEY); 3571 3572 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, 3573 new BMessage(kNavigatorCommandBackward), Navigator()); 3574 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, 3575 new BMessage(kNavigatorCommandForward), Navigator()); 3576 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 3577 new BMessage(kNavigatorCommandUp), Navigator()); 3578 3579 AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 3580 new BMessage(kNavigatorCommandBackward), Navigator()); 3581 AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 3582 new BMessage(kNavigatorCommandForward), Navigator()); 3583 AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 3584 new BMessage(kNavigatorCommandUp), Navigator()); 3585 3586 } else { 3587 3588 RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); 3589 RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); 3590 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); 3591 // This is added again, below, with a new meaning. 3592 3593 RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 3594 RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 3595 RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY); 3596 // This also changes meaning, added again below. 3597 3598 AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY, 3599 new BMessage(kOpenSelection), PoseView()); 3600 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, 3601 new BMessage(kOpenParentDir), PoseView()); 3602 // We change the meaning from kNavigatorCommandUp to kOpenParentDir. 3603 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 3604 new BMessage(kOpenParentDir), PoseView()); 3605 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY | B_CONTROL_KEY, 3606 new BMessage(kOpenParentDir), PoseView()); 3607 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_CONTROL_KEY, 3608 new BMessage(kOpenParentDir), PoseView()); 3609 // the command option results in closing the parent window 3610 // the control is a secret backdoor to get at the Disks menu 3611 } 3612 } 3613 3614 3615 void 3616 BContainerWindow::SetPathWatchingEnabled(bool enable) 3617 { 3618 if (IsPathWatchingEnabled()) { 3619 stop_watching(this); 3620 fIsWatchingPath = false; 3621 } 3622 3623 if (enable) { 3624 if (TargetModel() != NULL) { 3625 BEntry entry; 3626 3627 TargetModel()->GetEntry(&entry); 3628 status_t err; 3629 do { 3630 err = entry.GetParent(&entry); 3631 if (err != B_OK) 3632 break; 3633 3634 char name[B_FILE_NAME_LENGTH]; 3635 entry.GetName(name); 3636 if (strcmp(name, "/") == 0) 3637 break; 3638 3639 node_ref ref; 3640 entry.GetNodeRef(&ref); 3641 watch_node(&ref, B_WATCH_NAME, this); 3642 } while (err == B_OK); 3643 3644 fIsWatchingPath = err == B_OK; 3645 } else 3646 fIsWatchingPath = false; 3647 } 3648 } 3649 3650 3651 void 3652 BContainerWindow::PulseTaskLoop() 3653 { 3654 if (fTaskLoop) 3655 fTaskLoop->PulseMe(); 3656 } 3657 3658 3659 // #pragma mark - 3660 3661 3662 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow *window, bool forWriting) 3663 : fModelOpener(NULL), 3664 fNode(NULL), 3665 fStreamNode(NULL) 3666 { 3667 if (window->TargetModel() && window->TargetModel()->IsRoot()) { 3668 BVolume bootVol; 3669 BVolumeRoster().GetBootVolume(&bootVol); 3670 BDirectory dir; 3671 if (FSGetDeskDir(&dir, bootVol.Device()) == B_OK) { 3672 fNode = new BDirectory(dir); 3673 fStreamNode = new AttributeStreamFileNode(fNode); 3674 } 3675 } else if (window->TargetModel()){ 3676 fModelOpener = new ModelNodeLazyOpener(window->TargetModel(), forWriting, false); 3677 if (fModelOpener->IsOpen(forWriting)) 3678 fStreamNode = new AttributeStreamFileNode(fModelOpener->TargetModel()->Node()); 3679 } 3680 } 3681 3682 WindowStateNodeOpener::~WindowStateNodeOpener() 3683 { 3684 delete fModelOpener; 3685 delete fNode; 3686 delete fStreamNode; 3687 } 3688 3689 3690 void 3691 WindowStateNodeOpener::SetTo(const BDirectory *node) 3692 { 3693 delete fModelOpener; 3694 delete fNode; 3695 delete fStreamNode; 3696 3697 fModelOpener = NULL; 3698 fNode = new BDirectory(*node); 3699 fStreamNode = new AttributeStreamFileNode(fNode); 3700 } 3701 3702 3703 void 3704 WindowStateNodeOpener::SetTo(const BEntry *entry, bool forWriting) 3705 { 3706 delete fModelOpener; 3707 delete fNode; 3708 delete fStreamNode; 3709 3710 fModelOpener = NULL; 3711 fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY)); 3712 fStreamNode = new AttributeStreamFileNode(fNode); 3713 } 3714 3715 3716 void 3717 WindowStateNodeOpener::SetTo(Model *model, bool forWriting) 3718 { 3719 delete fModelOpener; 3720 delete fNode; 3721 delete fStreamNode; 3722 3723 fNode = NULL; 3724 fStreamNode = NULL; 3725 fModelOpener = new ModelNodeLazyOpener(model, forWriting, false); 3726 if (fModelOpener->IsOpen(forWriting)) 3727 fStreamNode = new AttributeStreamFileNode(fModelOpener->TargetModel()->Node()); 3728 } 3729 3730 3731 AttributeStreamNode * 3732 WindowStateNodeOpener::StreamNode() const 3733 { 3734 return fStreamNode; 3735 } 3736 3737 3738 BNode * 3739 WindowStateNodeOpener::Node() const 3740 { 3741 if (!fStreamNode) 3742 return NULL; 3743 3744 if (fNode) 3745 return fNode; 3746 3747 return fModelOpener->TargetModel()->Node(); 3748 } 3749 3750 3751 // #pragma mark - 3752 3753 3754 BackgroundView::BackgroundView(BRect frame) 3755 : BView(frame, "", B_FOLLOW_ALL, 3756 B_FRAME_EVENTS | B_WILL_DRAW | B_PULSE_NEEDED) 3757 { 3758 } 3759 3760 3761 void 3762 BackgroundView::AttachedToWindow() 3763 { 3764 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 3765 } 3766 3767 3768 void 3769 BackgroundView::FrameResized(float, float) 3770 { 3771 Invalidate(); 3772 } 3773 3774 3775 void 3776 BackgroundView::PoseViewFocused(bool) 3777 { 3778 Invalidate(); 3779 } 3780 3781 3782 void 3783 BackgroundView::WindowActivated(bool) 3784 { 3785 Invalidate(); 3786 } 3787 3788 3789 void 3790 BackgroundView::Draw(BRect) 3791 { 3792 BContainerWindow *window = dynamic_cast<BContainerWindow *>(Window()); 3793 if (!window) 3794 return; 3795 3796 BRect frame(window->PoseView()->Frame()); 3797 3798 frame.InsetBy(-1, -1); 3799 frame.top -= kTitleViewHeight; 3800 frame.bottom += B_H_SCROLL_BAR_HEIGHT; 3801 frame.right += B_V_SCROLL_BAR_WIDTH; 3802 SetHighColor(100, 100, 100); 3803 StrokeRect(frame); 3804 3805 // draw the pose view focus 3806 if (window->IsActive() && window->PoseView()->IsFocus()) { 3807 frame.InsetBy(-2, -2); 3808 SetHighColor(keyboard_navigation_color()); 3809 StrokeRect(frame); 3810 } 3811 } 3812 3813 3814 void 3815 BackgroundView::Pulse() 3816 { 3817 BContainerWindow *window = dynamic_cast<BContainerWindow *>(Window()); 3818 if (window) 3819 window->PulseTaskLoop(); 3820 } 3821 3822