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