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