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 37 #include <Alert.h> 38 #include <Application.h> 39 #include <Button.h> 40 #include <Debug.h> 41 #include <Directory.h> 42 #include <FindDirectory.h> 43 #include <MenuBar.h> 44 #include <MenuField.h> 45 #include <MenuItem.h> 46 #include <MessageFilter.h> 47 #include <NodeInfo.h> 48 #include <NodeMonitor.h> 49 #include <Path.h> 50 #include <Roster.h> 51 #include <SymLink.h> 52 #include <ScrollView.h> 53 #include <String.h> 54 #include <StopWatch.h> 55 #include <TextControl.h> 56 #include <TextView.h> 57 #include <Volume.h> 58 #include <VolumeRoster.h> 59 60 #include "Attributes.h" 61 #include "AttributeStream.h" 62 #include "AutoLock.h" 63 #include "Commands.h" 64 #include "DesktopPoseView.h" 65 #include "DirMenu.h" 66 #include "FavoritesConfig.h" 67 #include "FavoritesMenu.h" 68 #include "FilePanelPriv.h" 69 #include "FSUtils.h" 70 #include "FSClipboard.h" 71 #include "IconMenuItem.h" 72 #include "MimeTypes.h" 73 #include "NavMenu.h" 74 #include "PoseView.h" 75 #include "Tracker.h" 76 #include "tracker_private.h" 77 78 79 const char *kDefaultFilePanelTemplate = "FilePanelSettings"; 80 81 82 static uint32 83 GetLinkFlavor(const Model *model, bool resolve = true) 84 { 85 if (model && model->IsSymLink()) { 86 if (!resolve) 87 return B_SYMLINK_NODE; 88 model = model->LinkTo(); 89 } 90 if (!model) 91 return 0; 92 93 if (model->IsDirectory()) 94 return B_DIRECTORY_NODE; 95 96 return B_FILE_NODE; 97 } 98 99 100 static filter_result 101 key_down_filter(BMessage *message, BHandler **, BMessageFilter *filter) 102 { 103 TFilePanel *panel = dynamic_cast<TFilePanel *>(filter->Looper()); 104 ASSERT(panel); 105 BPoseView *view = panel->PoseView(); 106 107 if (panel->TrackingMenu()) 108 return B_DISPATCH_MESSAGE; 109 110 uchar key; 111 if (message->FindInt8("byte", (int8 *)&key) != B_OK) 112 return B_DISPATCH_MESSAGE; 113 114 int32 modifier = 0; 115 message->FindInt32("modifiers", &modifier); 116 if (!modifier && key == B_ESCAPE) { 117 if (view->ActivePose()) 118 view->CommitActivePose(false); 119 else 120 filter->Looper()->PostMessage(kCancelButton); 121 return B_SKIP_MESSAGE; 122 } 123 124 if (key == B_RETURN && view->ActivePose()) { 125 view->CommitActivePose(); 126 return B_SKIP_MESSAGE; 127 } 128 129 return B_DISPATCH_MESSAGE; 130 } 131 132 133 // #pragma mark - 134 135 136 TFilePanel::TFilePanel(file_panel_mode mode, BMessenger *target, 137 const BEntry *startDir, uint32 nodeFlavors, bool multipleSelection, 138 BMessage *message, BRefFilter *filter, uint32 containerWindowFlags, 139 window_look look, window_feel feel, bool hideWhenDone) 140 : BContainerWindow(0, containerWindowFlags, look, feel, 0, B_CURRENT_WORKSPACE), 141 fDirMenu(NULL), 142 fDirMenuField(NULL), 143 fTextControl(NULL), 144 fClientObject(NULL), 145 fSelectionIterator(0), 146 fMessage(NULL), 147 fHideWhenDone(hideWhenDone), 148 fIsTrackingMenu(false), 149 fConfigWindow(NULL) 150 { 151 InitIconPreloader(); 152 153 fIsSavePanel = (mode == B_SAVE_PANEL); 154 155 BRect windRect(85, 50, 510, 296); 156 MoveTo(windRect.LeftTop()); 157 ResizeTo(windRect.Width(), windRect.Height()); 158 159 fNodeFlavors = (nodeFlavors == 0) ? B_FILE_NODE : nodeFlavors; 160 161 if (target) 162 fTarget = *target; 163 else 164 fTarget = BMessenger(be_app); 165 166 if (message) 167 SetMessage(message); 168 else if (fIsSavePanel) 169 fMessage = new BMessage(B_SAVE_REQUESTED); 170 else 171 fMessage = new BMessage(B_REFS_RECEIVED); 172 173 // check for legal starting directory 174 Model *model = new Model(); 175 bool useRoot = true; 176 177 if (startDir) { 178 if (model->SetTo(startDir) == B_OK && model->IsDirectory()) 179 useRoot = false; 180 else { 181 delete model; 182 model = new Model(); 183 } 184 } 185 186 if (useRoot) { 187 BPath path; 188 if (find_directory(B_USER_DIRECTORY, &path) == B_OK) { 189 BEntry entry(path.Path(), true); 190 if (entry.InitCheck() == B_OK && model->SetTo(&entry) == B_OK) 191 useRoot = false; 192 } 193 } 194 195 if (useRoot) { 196 BVolume volume; 197 BDirectory root; 198 BVolumeRoster volumeRoster; 199 volumeRoster.GetBootVolume(&volume); 200 volume.GetRootDirectory(&root); 201 202 BEntry entry; 203 root.GetEntry(&entry); 204 model->SetTo(&entry); 205 } 206 207 fTaskLoop = new PiggybackTaskLoop; 208 209 AutoLock<BWindow> lock(this); 210 CreatePoseView(model); 211 fPoseView->SetRefFilter(filter); 212 if (!fIsSavePanel) 213 fPoseView->SetMultipleSelection(multipleSelection); 214 215 fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE); 216 fPoseView->SetPoseEditing(false); 217 AddCommonFilter(new BMessageFilter(B_KEY_DOWN, key_down_filter)); 218 AddCommonFilter(new BMessageFilter(B_SIMPLE_DATA, TFilePanel::MessageDropFilter)); 219 AddCommonFilter(new BMessageFilter(B_NODE_MONITOR, TFilePanel::FSFilter)); 220 221 // inter-application observing 222 BMessenger tracker(kTrackerSignature); 223 BHandler::StartWatching(tracker, kDesktopFilePanelRootChanged); 224 225 Init(); 226 } 227 228 229 TFilePanel::~TFilePanel() 230 { 231 // regardless of the hide/close method 232 // always get rid of the config window 233 if (fConfigWindow) { 234 // moved from QuitRequested to ensure that 235 // if the config window is showing that 236 // it gets closed as well 237 fConfigWindow->Lock(); 238 fConfigWindow->Quit(); 239 } 240 241 BMessenger tracker(kTrackerSignature); 242 BHandler::StopWatching(tracker, kDesktopFilePanelRootChanged); 243 244 delete fMessage; 245 } 246 247 248 filter_result 249 TFilePanel::MessageDropFilter(BMessage *message, BHandler **, BMessageFilter *filter) 250 { 251 TFilePanel *panel = dynamic_cast<TFilePanel *>(filter->Looper()); 252 if (panel == NULL || !message->WasDropped()) 253 return B_SKIP_MESSAGE; 254 255 uint32 type; 256 int32 count; 257 if (message->GetInfo("refs", &type, &count) != B_OK) 258 return B_SKIP_MESSAGE; 259 260 if (count != 1) 261 return B_SKIP_MESSAGE; 262 263 entry_ref ref; 264 if (message->FindRef("refs", &ref) != B_OK) 265 return B_SKIP_MESSAGE; 266 267 BEntry entry(&ref); 268 if (entry.InitCheck() != B_OK) 269 return B_SKIP_MESSAGE; 270 271 // if the entry is a symlink 272 // resolve it and see if it is a directory 273 // pass it on if it is 274 if (entry.IsSymLink()) { 275 entry_ref resolvedRef; 276 277 entry.GetRef(&resolvedRef); 278 BEntry resolvedEntry(&resolvedRef, true); 279 280 if (resolvedEntry.IsDirectory()) { 281 // both entry and ref need to be the correct locations 282 // for the last setto 283 resolvedEntry.GetRef(&ref); 284 entry.SetTo(&ref); 285 } 286 } 287 288 // if not a directory, set to the parent, and select the child 289 if (!entry.IsDirectory()) { 290 node_ref child; 291 if (entry.GetNodeRef(&child) != B_OK) 292 return B_SKIP_MESSAGE; 293 294 BPath path(&entry); 295 296 if (entry.GetParent(&entry) != B_OK) 297 return B_SKIP_MESSAGE; 298 299 entry.GetRef(&ref); 300 301 panel->fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 302 (&TFilePanel::SelectChildInParent, panel, 303 const_cast<const entry_ref *>(&ref), 304 const_cast<const node_ref *>(&child)), 305 ref == *panel->TargetModel()->EntryRef() ? 0 : 100000, 200000, 5000000); 306 // if the target directory is already current, we won't 307 // delay the initial selection try 308 309 // also set the save name to the dragged in entry 310 if (panel->IsSavePanel()) 311 panel->SetSaveText(path.Leaf()); 312 } 313 314 panel->SetTo(&ref); 315 316 return B_SKIP_MESSAGE; 317 } 318 319 320 filter_result 321 TFilePanel::FSFilter(BMessage *message, BHandler **, BMessageFilter *filter) 322 { 323 switch (message->FindInt32("opcode")) { 324 case B_ENTRY_MOVED: 325 { 326 node_ref itemNode; 327 node_ref dirNode; 328 TFilePanel *panel = dynamic_cast<TFilePanel *>(filter->Looper()); 329 330 message->FindInt32("device", &dirNode.device); 331 itemNode.device = dirNode.device; 332 message->FindInt64("to directory", (int64 *)&dirNode.node); 333 message->FindInt64("node", (int64 *)&itemNode.node); 334 const char *name; 335 if (message->FindString("name", &name) != B_OK) 336 break; 337 338 // if current directory moved, update entry ref and menu 339 // but not wind title 340 if (*(panel->TargetModel()->NodeRef()) == itemNode) { 341 panel->TargetModel()->UpdateEntryRef(&dirNode, name); 342 panel->SetTo(panel->TargetModel()->EntryRef()); 343 return B_SKIP_MESSAGE; 344 } 345 break; 346 } 347 case B_ENTRY_REMOVED: 348 { 349 node_ref itemNode; 350 TFilePanel *panel = dynamic_cast<TFilePanel *>(filter->Looper()); 351 message->FindInt32("device", &itemNode.device); 352 message->FindInt64("node", (int64 *)&itemNode.node); 353 354 // if folder we're watching is deleted, switch to root 355 // or Desktop 356 if (*(panel->TargetModel()->NodeRef()) == itemNode) { 357 BVolumeRoster volumeRoster; 358 BVolume volume; 359 volumeRoster.GetBootVolume(&volume); 360 361 BDirectory root; 362 volume.GetRootDirectory(&root); 363 364 BEntry entry; 365 entry_ref ref; 366 root.GetEntry(&entry); 367 entry.GetRef(&ref); 368 369 panel->SwitchDirToDesktopIfNeeded(ref); 370 371 panel->SetTo(&ref); 372 return B_SKIP_MESSAGE; 373 } 374 } 375 break; 376 } 377 return B_DISPATCH_MESSAGE; 378 } 379 380 381 void 382 TFilePanel::DispatchMessage(BMessage *message, BHandler *handler) 383 { 384 _inherited::DispatchMessage(message, handler); 385 if (message->what == B_KEY_DOWN || message->what == B_MOUSE_DOWN) 386 AdjustButton(); 387 } 388 389 390 BFilePanelPoseView * 391 TFilePanel::PoseView() const 392 { 393 ASSERT(dynamic_cast<BFilePanelPoseView *>(fPoseView)); 394 return static_cast<BFilePanelPoseView *>(fPoseView); 395 } 396 397 398 bool 399 TFilePanel::QuitRequested() 400 { 401 // If we have a client object then this window will simply hide 402 // itself, to be closed later when the client object itself is 403 // destroyed. If we have no client then we must have been started 404 // from the "easy" functions which simply instantiate a TFilePanel 405 // and expect it to go away by itself 406 407 if (fClientObject) { 408 Hide(); 409 if (fClientObject) 410 fClientObject->WasHidden(); 411 412 BMessage message(*fMessage); 413 message.what = B_CANCEL; 414 message.AddInt32("old_what", (int32)fMessage->what); 415 message.AddPointer("source", fClientObject); 416 fTarget.SendMessage(&message); 417 return false; 418 } 419 420 return _inherited::QuitRequested(); 421 } 422 423 424 BRefFilter * 425 TFilePanel::Filter() const 426 { 427 return fPoseView->RefFilter(); 428 } 429 430 431 void 432 TFilePanel::SetTarget(BMessenger target) 433 { 434 fTarget = target; 435 } 436 437 438 void 439 TFilePanel::SetMessage(BMessage *message) 440 { 441 delete fMessage; 442 fMessage = new BMessage(*message); 443 } 444 445 446 void 447 TFilePanel::SetRefFilter(BRefFilter *filter) 448 { 449 if (!filter) 450 return; 451 452 fPoseView->SetRefFilter(filter); 453 fPoseView->CommitActivePose(); 454 fPoseView->Refresh(); 455 } 456 457 458 void 459 TFilePanel::SetTo(const entry_ref *ref) 460 { 461 if (!ref) 462 return; 463 464 entry_ref setToRef(*ref); 465 466 bool isDesktop = SwitchDirToDesktopIfNeeded(setToRef); 467 468 BEntry entry(&setToRef); 469 if (entry.InitCheck() != B_OK || !entry.IsDirectory()) 470 return; 471 472 SwitchDirMenuTo(&setToRef); 473 474 PoseView()->SetIsDesktop(isDesktop); 475 fPoseView->SwitchDir(&setToRef); 476 477 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 478 // our shortcut got possibly removed because the home 479 // menu item got removed - we shouldn't really have to do 480 // this - this is a workaround for a kit bug. 481 } 482 483 484 void 485 TFilePanel::Rewind() 486 { 487 fSelectionIterator = 0; 488 } 489 490 491 void 492 TFilePanel::SetClientObject(BFilePanel *panel) 493 { 494 fClientObject = panel; 495 } 496 497 498 void 499 TFilePanel::AdjustButton() 500 { 501 // adjust button state 502 BButton *button = dynamic_cast<BButton *>(FindView("default button")); 503 if (!button) 504 return; 505 506 BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("text view")); 507 BObjectList<BPose> *selectionList = fPoseView->SelectionList(); 508 const char *buttonText = fButtonText.String(); 509 bool enabled = false; 510 511 if (fIsSavePanel && textControl) { 512 enabled = textControl->Text()[0] != '\0'; 513 if (fPoseView->IsFocus()) { 514 fPoseView->ShowSelection(true); 515 if (selectionList->CountItems() == 1) { 516 Model *model = selectionList->FirstItem()->TargetModel(); 517 if (model->ResolveIfLink()->IsDirectory()) { 518 enabled = true; 519 buttonText = "Open"; 520 } else { 521 // insert the name of the selected model into the text field 522 textControl->SetText(model->Name()); 523 textControl->MakeFocus(true); 524 } 525 } 526 } else 527 fPoseView->ShowSelection(false); 528 } else { 529 int32 count = selectionList->CountItems(); 530 if (count) { 531 enabled = true; 532 533 // go through selection list looking at content 534 for (int32 index = 0; index < count; index++) { 535 Model *model = selectionList->ItemAt(index)->TargetModel(); 536 537 uint32 modelFlavor = GetLinkFlavor(model, false); 538 uint32 linkFlavor = GetLinkFlavor(model, true); 539 540 // if only one item is selected and we're not in dir 541 // selection mode then we don't disable button ever 542 if ((modelFlavor == B_DIRECTORY_NODE 543 || linkFlavor == B_DIRECTORY_NODE) 544 && count == 1) 545 break; 546 547 if ((fNodeFlavors & modelFlavor) == 0 548 && (fNodeFlavors & linkFlavor) == 0) { 549 enabled = false; 550 break; 551 } 552 } 553 } 554 } 555 556 button->SetLabel(buttonText); 557 button->SetEnabled(enabled); 558 } 559 560 561 void 562 TFilePanel::SelectionChanged() 563 { 564 AdjustButton(); 565 566 if (fClientObject) 567 fClientObject->SelectionChanged(); 568 } 569 570 571 status_t 572 TFilePanel::GetNextEntryRef(entry_ref *ref) 573 { 574 if (!ref) 575 return B_ERROR; 576 577 BPose *pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++); 578 if (!pose) 579 return B_ERROR; 580 581 *ref = *pose->TargetModel()->EntryRef(); 582 return B_OK; 583 } 584 585 586 BPoseView * 587 TFilePanel::NewPoseView(Model *model, BRect rect, uint32) 588 { 589 return new BFilePanelPoseView(model, rect); 590 } 591 592 593 void 594 TFilePanel::Init(const BMessage *) 595 { 596 BRect windRect(Bounds()); 597 AddChild(fBackView = new BackgroundView(windRect)); 598 599 // add poseview menu bar 600 fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar"); 601 fMenuBar->SetBorder(B_BORDER_FRAME); 602 fBackView->AddChild(fMenuBar); 603 604 AddMenus(); 605 AddContextMenus(); 606 607 FavoritesMenu *favorites = new FavoritesMenu("Favorites", 608 new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED), 609 BMessenger(this), IsSavePanel()); 610 favorites->AddItem(new BMenuItem("Add Current Folder", 611 new BMessage(kAddCurrentDir))); 612 favorites->AddItem(new BMenuItem("Configure Favorites"B_UTF8_ELLIPSIS, 613 new BMessage(kConfigShow))); 614 615 fMenuBar->AddItem(favorites); 616 617 // configure menus 618 BMenuItem *item = fMenuBar->FindItem("Window"); 619 if (item) { 620 fMenuBar->RemoveItem(item); 621 delete item; 622 } 623 624 item = fMenuBar->FindItem("File"); 625 if (item) { 626 BMenu *menu = item->Submenu(); 627 if (menu) { 628 item = menu->FindItem(kOpenSelection); 629 if (item && menu->RemoveItem(item)) 630 delete item; 631 632 item = menu->FindItem(kDuplicateSelection); 633 if (item && menu->RemoveItem(item)) 634 delete item; 635 636 // remove add-ons menu, identifier menu, separator 637 item = menu->FindItem(kAddOnsMenuName); 638 if (item) { 639 int32 index = menu->IndexOf(item); 640 delete menu->RemoveItem(index); 641 delete menu->RemoveItem(--index); 642 delete menu->RemoveItem(--index); 643 } 644 645 // remove separator 646 item = menu->FindItem(B_CUT); 647 if (item) { 648 item = menu->ItemAt(menu->IndexOf(item)-1); 649 if (item && menu->RemoveItem(item)) 650 delete item; 651 } 652 } 653 } 654 655 // add directory menu and menufield 656 fDirMenu = new BDirMenu(0, kSwitchDirectory, "refs"); 657 658 font_height ht; 659 be_plain_font->GetHeight(&ht); 660 float f_height = ht.ascent + ht.descent + ht.leading; 661 662 BRect rect; 663 rect.top = fMenuBar->Bounds().Height() + 2; 664 rect.left = windRect.left + 9; 665 rect.right = rect.left + 300; 666 rect.bottom = rect.top + (f_height > 22 ? f_height : 22); 667 668 fDirMenuField = new BMenuField(rect, "DirMenuField", "", fDirMenu); 669 fDirMenuField->MenuBar()->SetFont(be_plain_font); 670 fDirMenuField->SetDivider(0); 671 672 fDirMenuField->MenuBar()->RemoveItem((int32)0); 673 fDirMenu->SetMenuBar(fDirMenuField->MenuBar()); 674 // the above is a weird call from BDirMenu 675 // ToDo: clean up 676 677 BEntry entry(TargetModel()->EntryRef()); 678 if (entry.InitCheck() == B_OK) 679 fDirMenu->Populate(&entry, 0, true, true, false, true); 680 else 681 fDirMenu->Populate(0, 0, true, true, false, true); 682 683 fBackView->AddChild(fDirMenuField); 684 685 // add file name text view 686 if (fIsSavePanel) { 687 BRect rect(windRect); 688 rect.top = rect.bottom - 35; 689 rect.left = 9; 690 rect.right = rect.left + 170; 691 rect.bottom = rect.top + 13; 692 693 fTextControl = new BTextControl(rect, "text view", "save text", "", NULL, 694 B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 695 DisallowMetaKeys(fTextControl->TextView()); 696 DisallowFilenameKeys(fTextControl->TextView()); 697 fBackView->AddChild(fTextControl); 698 fTextControl->SetDivider(0.0f); 699 fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); 700 701 fButtonText = "Save"; 702 } else 703 fButtonText = "Open"; 704 705 rect = windRect; 706 rect.OffsetTo(10, fMenuBar->Bounds().Height() * 2 + 16); 707 rect.bottom = windRect.bottom - 60; 708 rect.right -= B_V_SCROLL_BAR_WIDTH + 20; 709 710 // re-parent the poseview to our backview 711 // ToDo: 712 // This is terrible, fix it up 713 PoseView()->RemoveSelf(); 714 if (fIsSavePanel) 715 fBackView->AddChild(PoseView(), fTextControl); 716 else 717 fBackView->AddChild(PoseView()); 718 719 PoseView()->MoveTo(rect.LeftTop()); 720 PoseView()->ResizeTo(rect.Width(), rect.Height()); 721 PoseView()->AddScrollBars(); 722 PoseView()->SetDragEnabled(false); 723 PoseView()->SetDropEnabled(false); 724 PoseView()->SetSelectionHandler(this); 725 PoseView()->SetSelectionChangedHook(true); 726 PoseView()->DisableSaveLocation(); 727 728 729 AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton)); 730 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 731 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir)); 732 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, new BMessage(kOpenDir)); 733 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir)); 734 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, new BMessage(kOpenParentDir)); 735 736 // New code to make buttons font sensitive 737 rect = windRect; 738 rect.top = rect.bottom - 35; 739 rect.bottom -= 10; 740 rect.right -= 25; 741 float default_width = be_plain_font->StringWidth(fButtonText.String()) + 20; 742 rect.left = (default_width > 75) ? (rect.right - default_width) : (rect.right - 75); 743 744 BButton *default_button = new BButton(rect, "default button", fButtonText.String(), 745 new BMessage(kDefaultButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 746 fBackView->AddChild(default_button); 747 748 rect.right = rect.left -= 10; 749 float cancel_width = be_plain_font->StringWidth("Cancel") + 20; 750 rect.left = (cancel_width > 75) ? (rect.right - cancel_width) : (rect.right - 75); 751 752 BButton *cancel_button = new BButton(rect, "cancel button", "Cancel", 753 new BMessage(kCancelButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 754 fBackView->AddChild(cancel_button); 755 756 if (!fIsSavePanel) 757 default_button->SetEnabled(false); 758 759 default_button->MakeDefault(true); 760 761 RestoreState(); 762 763 PoseView()->ScrollTo(B_ORIGIN); 764 PoseView()->UpdateScrollRange(); 765 PoseView()->ScrollTo(B_ORIGIN); 766 767 if (fTextControl) { 768 fTextControl->MakeFocus(); 769 fTextControl->TextView()->SelectAll(); 770 } else 771 PoseView()->MakeFocus(); 772 773 app_info info; 774 BString title; 775 if (be_app->GetAppInfo(&info) == B_OK) 776 title << info.ref.name << ": "; 777 778 title << fButtonText; // Open or Save 779 780 SetTitle(title.String()); 781 782 SetSizeLimits(360, 10000, 200, 10000); 783 } 784 785 786 void 787 TFilePanel::RestoreState() 788 { 789 BNode defaultingNode; 790 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, false)) { 791 AttributeStreamFileNode streamNodeSource(&defaultingNode); 792 RestoreWindowState(&streamNodeSource); 793 PoseView()->Init(&streamNodeSource); 794 } else { 795 RestoreWindowState(NULL); 796 PoseView()->Init(NULL); 797 } 798 } 799 800 801 void 802 TFilePanel::SaveState(bool) 803 { 804 BNode defaultingNode; 805 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 806 true, false)) { 807 AttributeStreamFileNode streamNodeDestination(&defaultingNode); 808 SaveWindowState(&streamNodeDestination); 809 PoseView()->SaveState(&streamNodeDestination); 810 } 811 } 812 813 814 void 815 TFilePanel::SaveState(BMessage &message) const 816 { 817 _inherited::SaveState(message); 818 } 819 820 821 void 822 TFilePanel::RestoreWindowState(AttributeStreamNode *node) 823 { 824 SetSizeLimits(360, 10000, 200, 10000); 825 if (!node) 826 return; 827 828 const char *rectAttributeName = kAttrWindowFrame; 829 BRect frame(Frame()); 830 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 831 == sizeof(BRect)) { 832 MoveTo(frame.LeftTop()); 833 ResizeTo(frame.Width(), frame.Height()); 834 } 835 } 836 837 838 void 839 TFilePanel::RestoreState(const BMessage &message) 840 { 841 _inherited::RestoreState(message); 842 } 843 844 845 void 846 TFilePanel::RestoreWindowState(const BMessage &message) 847 { 848 _inherited::RestoreWindowState(message); 849 } 850 851 852 void 853 TFilePanel::AddFileContextMenus(BMenu *menu) 854 { 855 menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); 856 menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); 857 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? 858 "Delete" : "Move to Trash", 859 new BMessage(kMoveToTrash), 'T')); 860 menu->AddSeparatorItem(); 861 menu->AddItem(new BMenuItem("Cut", new BMessage(B_CUT), 'X')); 862 menu->AddItem(new BMenuItem("Copy", new BMessage(B_COPY), 'C')); 863 // menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); 864 865 menu->SetTargetForItems(PoseView()); 866 } 867 868 869 void 870 TFilePanel::AddVolumeContextMenus(BMenu *menu) 871 { 872 menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); 873 menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); 874 menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); 875 menu->AddSeparatorItem(); 876 menu->AddItem(new BMenuItem("Cut", new BMessage(B_CUT), 'X')); 877 menu->AddItem(new BMenuItem("Copy", new BMessage(B_COPY), 'C')); 878 // menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); 879 880 menu->SetTargetForItems(PoseView()); 881 } 882 883 884 void 885 TFilePanel::AddWindowContextMenus(BMenu *menu) 886 { 887 BMenuItem *item = new BMenuItem("New Folder", new BMessage(kNewFolder), 'N'); 888 item->SetTarget(PoseView()); 889 menu->AddItem(item); 890 menu->AddSeparatorItem(); 891 892 item = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'); 893 item->SetTarget(PoseView()); 894 menu->AddItem(item); 895 menu->AddSeparatorItem(); 896 897 item = new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), 898 'A', B_SHIFT_KEY); 899 item->SetTarget(PoseView()); 900 menu->AddItem(item); 901 902 item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'); 903 item->SetTarget(PoseView()); 904 menu->AddItem(item); 905 906 item = new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S'); 907 item->SetTarget(PoseView()); 908 menu->AddItem(item); 909 910 item = new BMenuItem("Go To Parent", new BMessage(kOpenParentDir), B_UP_ARROW); 911 item->SetTarget(this); 912 menu->AddItem(item); 913 } 914 915 916 void 917 TFilePanel::AddDropContextMenus(BMenu *) 918 { 919 } 920 921 922 void 923 TFilePanel::MenusBeginning() 924 { 925 int32 count = PoseView()->SelectionList()->CountItems(); 926 927 EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()); 928 EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot() && count); 929 EnableNamedMenuItem(fMenuBar, kGetInfo, count != 0); 930 EnableNamedMenuItem(fMenuBar, kEditItem, count == 1); 931 932 SetCutItem(fMenuBar); 933 SetCopyItem(fMenuBar); 934 SetPasteItem(fMenuBar); 935 936 fIsTrackingMenu = true; 937 } 938 939 940 void 941 TFilePanel::MenusEnded() 942 { 943 fIsTrackingMenu = false; 944 } 945 946 947 void 948 TFilePanel::ShowContextMenu(BPoint point, const entry_ref *ref, BView *view) 949 { 950 EnableNamedMenuItem(fWindowContextMenu, kNewFolder, !TargetModel()->IsRoot()); 951 EnableNamedMenuItem(fWindowContextMenu, kOpenParentDir, !TargetModel()->IsRoot()); 952 EnableNamedMenuItem(fWindowContextMenu, kMoveToTrash, !TargetModel()->IsRoot()); 953 954 _inherited::ShowContextMenu(point, ref, view); 955 } 956 957 958 void 959 TFilePanel::SetupNavigationMenu(const entry_ref *, BMenu *) 960 { 961 // do nothing here so nav menu doesn't get added 962 } 963 964 965 void 966 TFilePanel::SetButtonLabel(file_panel_button selector, const char *text) 967 { 968 switch (selector) { 969 case B_CANCEL_BUTTON: 970 { 971 BButton *button = dynamic_cast<BButton *>(FindView("cancel button")); 972 if (!button) 973 break; 974 975 float old_width = button->StringWidth(button->Label()); 976 button->SetLabel(text); 977 float delta = old_width - button->StringWidth(text); 978 if (delta) { 979 button->MoveBy(delta, 0); 980 button->ResizeBy(-delta, 0); 981 } 982 } 983 break; 984 985 case B_DEFAULT_BUTTON: 986 { 987 fButtonText = text; 988 float delta = 0; 989 BButton *button = dynamic_cast<BButton *>(FindView("default button")); 990 if (button) { 991 float old_width = button->StringWidth(button->Label()); 992 button->SetLabel(text); 993 delta = old_width - button->StringWidth(text); 994 if (delta) { 995 button->MoveBy(delta, 0); 996 button->ResizeBy(-delta, 0); 997 } 998 } 999 1000 // now must move cancel button 1001 button = dynamic_cast<BButton *>(FindView("cancel button")); 1002 if (button) 1003 button->MoveBy(delta, 0); 1004 } 1005 break; 1006 } 1007 } 1008 1009 1010 void 1011 TFilePanel::SetSaveText(const char *text) 1012 { 1013 if (!text) 1014 return; 1015 1016 BTextControl *textControl = dynamic_cast<BTextControl *>(FindView("text view")); 1017 textControl->SetText(text); 1018 textControl->TextView()->SelectAll(); 1019 } 1020 1021 1022 void 1023 TFilePanel::MessageReceived(BMessage *message) 1024 { 1025 entry_ref ref; 1026 1027 switch (message->what) { 1028 case B_REFS_RECEIVED: 1029 // item was double clicked in file panel (PoseView) 1030 if (message->FindRef("refs", &ref) == B_OK) { 1031 1032 BEntry entry(&ref, true); 1033 if (entry.InitCheck() == B_OK) { 1034 1035 // Double-click on dir or link-to-dir ALWAYS opens the dir. 1036 // If more than one dir is selected, the 1037 // first is entered. 1038 if (entry.IsDirectory()) { 1039 entry.GetRef(&ref); 1040 bool isDesktop = SwitchDirToDesktopIfNeeded(ref); 1041 1042 PoseView()->SetIsDesktop(isDesktop); 1043 entry.SetTo(&ref); 1044 PoseView()->SwitchDir(&ref); 1045 SwitchDirMenuTo(&ref); 1046 } else { 1047 1048 // Otherwise, we have a file or a link to a file. 1049 // AdjustButton has already tested the flavor; 1050 // all we have to do is see if the button is enabled. 1051 BButton *button = dynamic_cast<BButton *>(FindView("default button")); 1052 if (!button) 1053 break; 1054 1055 if (IsSavePanel()) { 1056 int32 count = 0; 1057 type_code type; 1058 message->GetInfo("refs", &type, &count); 1059 1060 // Don't allow saves of multiple files 1061 if (count > 1) 1062 ShowCenteredAlert("Sorry, saving of more than one item is not allowed.", 1063 "Cancel"); 1064 else { 1065 // if we are a savepanel, set up the filepanel correctly 1066 // then pass control so we follow the same path as if the user 1067 // clicked the save button 1068 1069 // set the 'name' fld to the current ref's name 1070 // notify the panel that the default button should be enabled 1071 SetSaveText(ref.name); 1072 SelectionChanged(); 1073 1074 HandleSaveButton(); 1075 } 1076 break; 1077 } 1078 1079 1080 // send handler a message and close 1081 BMessage openMessage(*fMessage); 1082 for (int32 index = 0; ; index++) { 1083 if (message->FindRef("refs", index, &ref) != B_OK) 1084 break; 1085 openMessage.AddRef("refs", &ref); 1086 } 1087 OpenSelectionCommon(&openMessage); 1088 } 1089 } 1090 } 1091 break; 1092 1093 case kSwitchDirectory: 1094 { 1095 entry_ref ref; 1096 // this comes from dir menu or nav menu, so switch directories 1097 if (message->FindRef("refs", &ref) == B_OK) { 1098 BEntry entry(&ref, true); 1099 if (entry.GetRef(&ref) == B_OK) 1100 SetTo(&ref); 1101 } 1102 } 1103 break; 1104 1105 case kSwitchToHome: 1106 { 1107 BPath homePath; 1108 entry_ref ref; 1109 if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK 1110 || get_ref_for_path(homePath.Path(), &ref) != B_OK) 1111 break; 1112 1113 SetTo(&ref); 1114 } 1115 break; 1116 1117 case kAddCurrentDir: 1118 { 1119 BPath path; 1120 if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) 1121 break; 1122 1123 path.Append(kGoDirectory); 1124 BDirectory goDirectory(path.Path()); 1125 1126 if (goDirectory.InitCheck() == B_OK) { 1127 BEntry entry(TargetModel()->EntryRef()); 1128 entry.GetPath(&path); 1129 1130 BSymLink link; 1131 goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(), &link); 1132 } 1133 } 1134 break; 1135 1136 case kConfigShow: 1137 { 1138 if (fConfigWindow) 1139 fConfigWindow->Activate(); 1140 else { 1141 BPath path; 1142 if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) 1143 break; 1144 1145 path.Append(kGoDirectory); 1146 BDirectory goDirectory(path.Path()); 1147 1148 if (goDirectory.InitCheck() == B_OK) { 1149 entry_ref startref; 1150 BEntry entry; 1151 goDirectory.GetEntry(&entry); 1152 entry.GetRef(&startref); 1153 1154 int32 apps, docs, folders; 1155 TrackerSettings().RecentCounts(&apps, &docs, &folders); 1156 1157 // if this is a save panel 1158 // then don't show recent docs controls 1159 if (fIsSavePanel) 1160 docs = -1; 1161 1162 fConfigWindow = new TFavoritesConfigWindow(BRect(0, 0, 320, 24), 1163 "Configure Favorites", Feel() == B_MODAL_APP_WINDOW_FEEL, 1164 fNodeFlavors, BMessenger(this), &startref, -1, docs, folders); 1165 } 1166 } 1167 } 1168 break; 1169 1170 case kConfigClose: 1171 { 1172 int32 count = 0; 1173 TrackerSettings settings; 1174 1175 // save off whatever was last in the fields 1176 // do this just in case someone didn't tab out 1177 if (message->FindInt32("applications", &count) == B_OK) 1178 settings.SetRecentApplicationsCount(count); 1179 if (message->FindInt32("folders", &count) == B_OK) 1180 settings.SetRecentFoldersCount(count); 1181 if (message->FindInt32("documents", &count) == B_OK) 1182 settings.SetRecentDocumentsCount(count); 1183 1184 settings.SaveSettings(false); 1185 1186 fConfigWindow = NULL; 1187 } 1188 break; 1189 1190 case kUpdateAppsCount: 1191 case kUpdateDocsCount: 1192 case kUpdateFolderCount: 1193 { 1194 // messages sent when the user changes the count 1195 int32 count; 1196 TrackerSettings settings; 1197 1198 if (message->FindInt32("count", &count) == B_OK) { 1199 if (message->what == kUpdateAppsCount) 1200 settings.SetRecentApplicationsCount(count); 1201 else if (message->what == kUpdateDocsCount) 1202 settings.SetRecentDocumentsCount(count); 1203 else if (message->what == kUpdateFolderCount) 1204 settings.SetRecentFoldersCount(count); 1205 settings.SaveSettings(false); 1206 } 1207 } 1208 break; 1209 1210 case kCancelButton: 1211 PostMessage(B_QUIT_REQUESTED); 1212 break; 1213 1214 case kOpenDir: 1215 OpenDirectory(); 1216 break; 1217 1218 case kOpenParentDir: 1219 OpenParent(); 1220 break; 1221 1222 case kDefaultButton: 1223 if (fIsSavePanel) { 1224 if (PoseView()->IsFocus() 1225 && PoseView()->SelectionList()->CountItems() == 1) { 1226 Model *model = (PoseView()->SelectionList()->FirstItem())->TargetModel(); 1227 if (model->ResolveIfLink()->IsDirectory()) { 1228 PoseView()->CommitActivePose(); 1229 PoseView()->OpenSelection(); 1230 break; 1231 } 1232 } 1233 1234 HandleSaveButton(); 1235 } else 1236 HandleOpenButton(); 1237 1238 break; 1239 1240 case B_OBSERVER_NOTICE_CHANGE: 1241 { 1242 int32 observerWhat; 1243 if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { 1244 switch (observerWhat) { 1245 case kDesktopFilePanelRootChanged: 1246 { 1247 bool desktopIsRoot = true; 1248 message->FindBool("DesktopFilePanelRoot", &desktopIsRoot); 1249 TrackerSettings().SetDesktopFilePanelRoot(desktopIsRoot); 1250 SetTo(TargetModel()->EntryRef()); 1251 break; 1252 } 1253 } 1254 } 1255 } 1256 break; 1257 1258 default: 1259 _inherited::MessageReceived(message); 1260 } 1261 } 1262 1263 1264 void 1265 TFilePanel::OpenDirectory() 1266 { 1267 BObjectList<BPose> *list = PoseView()->SelectionList(); 1268 if (list->CountItems() != 1) 1269 return; 1270 1271 Model *model = list->FirstItem()->TargetModel(); 1272 if (model->ResolveIfLink()->IsDirectory()) { 1273 BMessage message(B_REFS_RECEIVED); 1274 message.AddRef("refs", model->EntryRef()); 1275 PostMessage(&message); 1276 } 1277 } 1278 1279 1280 void 1281 TFilePanel::OpenParent() 1282 { 1283 if (!CanOpenParent()) 1284 return; 1285 1286 BEntry parentEntry; 1287 BDirectory dir; 1288 1289 Model oldModel(*PoseView()->TargetModel()); 1290 BEntry entry(oldModel.EntryRef()); 1291 1292 if (entry.InitCheck() == B_OK 1293 && entry.GetParent(&dir) == B_OK 1294 && dir.GetEntry(&parentEntry) == B_OK 1295 && entry != parentEntry) { 1296 1297 entry_ref ref; 1298 parentEntry.GetRef(&ref); 1299 1300 PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref)); 1301 PoseView()->SwitchDir(&ref); 1302 SwitchDirMenuTo(&ref); 1303 1304 // make sure the child get's selected in the new view once it 1305 // shows up 1306 fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 1307 (&TFilePanel::SelectChildInParent, this, 1308 const_cast<const entry_ref *>(&ref), 1309 oldModel.NodeRef()), 100000, 200000, 5000000); 1310 } 1311 } 1312 1313 1314 bool 1315 TFilePanel::CanOpenParent() const 1316 { 1317 if (TrackerSettings().DesktopFilePanelRoot()) { 1318 // don't allow opening Desktop folder's parent 1319 BEntry entry(TargetModel()->EntryRef()); 1320 if (FSIsDeskDir(&entry, TargetModel()->NodeRef()->device)) 1321 return false; 1322 } 1323 1324 // block on "/" 1325 BEntry root("/"); 1326 node_ref rootRef; 1327 root.GetNodeRef(&rootRef); 1328 1329 return rootRef != *TargetModel()->NodeRef(); 1330 } 1331 1332 1333 bool 1334 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref) 1335 { 1336 // support showing Desktop as root of everything 1337 // This call implements the worm hole that maps Desktop as 1338 // a root above the disks 1339 TrackerSettings settings; 1340 if (!settings.DesktopFilePanelRoot()) 1341 // Tracker isn't set up that way, just let Disks show 1342 return false; 1343 1344 BEntry entry(&ref); 1345 BEntry root("/"); 1346 1347 BDirectory desktopDir; 1348 BVolume bootVol; 1349 BVolumeRoster().GetBootVolume(&bootVol); 1350 FSGetDeskDir(&desktopDir, bootVol.Device()); 1351 1352 if ((bootVol.Device() != ref.device && FSIsDeskDir(&entry, ref.device)) 1353 // navigated into non-boot desktop, switch to boot desktop 1354 || (entry == root && !settings.ShowDisksIcon())) { 1355 // hit "/" level, map to desktop 1356 1357 desktopDir.GetEntry(&entry); 1358 entry.GetRef(&ref); 1359 return true; 1360 } 1361 return FSIsDeskDir(&entry, ref.device); 1362 } 1363 1364 1365 bool 1366 TFilePanel::SelectChildInParent(const entry_ref *, const node_ref *child) 1367 { 1368 AutoLock<TFilePanel> lock(this); 1369 1370 if (!IsLocked()) 1371 return false; 1372 1373 int32 index; 1374 BPose *pose = PoseView()->FindPose(child, &index); 1375 if (!pose) 1376 return false; 1377 1378 PoseView()->UpdateScrollRange(); 1379 // ToDo: Scroll range should be updated by now, for some 1380 // reason sometimes it is not right, force it here 1381 PoseView()->SelectPose(pose, index, true); 1382 return true; 1383 } 1384 1385 1386 int32 1387 TFilePanel::ShowCenteredAlert(const char *text, const char *button1, 1388 const char *button2, const char *button3) 1389 { 1390 BAlert *alert = new BAlert("", text, button1, button2, button3, 1391 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1392 alert->MoveTo(Frame().left + 10, Frame().top + 10); 1393 return alert->Go(); 1394 } 1395 1396 1397 void 1398 TFilePanel::HandleSaveButton() 1399 { 1400 BDirectory dir; 1401 1402 if (TargetModel()->IsRoot()) { 1403 ShowCenteredAlert("Sorry, you can't save things at the root of " 1404 "your system.", "Cancel"); 1405 return; 1406 } 1407 1408 // check for some illegal file names 1409 if (strcmp(fTextControl->Text(), ".") == 0 1410 || strcmp(fTextControl->Text(), "..") == 0) { 1411 ShowCenteredAlert("The name you have specified is illegal. Please type " 1412 "another name.", "Cancel"); 1413 fTextControl->TextView()->SelectAll(); 1414 return; 1415 } 1416 1417 if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) { 1418 ShowCenteredAlert("There was a problem trying to save in the folder " 1419 "you specified. Please try another one.", "Cancel"); 1420 return; 1421 } 1422 1423 if (dir.Contains(fTextControl->Text())) { 1424 if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) { 1425 ShowCenteredAlert("The name you have specified is already the name " 1426 "of a folder. Please type another name.", "Cancel"); 1427 fTextControl->TextView()->SelectAll(); 1428 return; 1429 } else { 1430 // if this was invoked by a dbl click, it is an explicit replacement 1431 // of the file. 1432 BString str; 1433 str << "The file \"" << fTextControl->Text() << "\" already exists in the " 1434 "specified folder. Do you want to replace it?"; 1435 1436 if (ShowCenteredAlert(str.String(), "Cancel", "Replace") == 0) { 1437 // user canceled 1438 fTextControl->TextView()->SelectAll(); 1439 return; 1440 } 1441 // user selected "Replace" - let app deal with it 1442 } 1443 } 1444 1445 BMessage message(*fMessage); 1446 message.AddRef("directory", TargetModel()->EntryRef()); 1447 message.AddString("name", fTextControl->Text()); 1448 1449 if (fClientObject) 1450 fClientObject->SendMessage(&fTarget, &message); 1451 else 1452 fTarget.SendMessage(&message); 1453 1454 // close window if we're dealing with standard message 1455 if (fHideWhenDone) 1456 PostMessage(B_QUIT_REQUESTED); 1457 } 1458 1459 1460 void 1461 TFilePanel::OpenSelectionCommon(BMessage *openMessage) 1462 { 1463 if (!openMessage->HasRef("refs")) 1464 return; 1465 1466 for (int32 index = 0; ; index++) { 1467 entry_ref ref; 1468 if (openMessage->FindRef("refs", index, &ref) != B_OK) 1469 break; 1470 1471 BEntry entry(&ref, true); 1472 if (entry.InitCheck() == B_OK) { 1473 if (entry.IsDirectory()) 1474 BRoster().AddToRecentFolders(&ref); 1475 else 1476 BRoster().AddToRecentDocuments(&ref); 1477 } 1478 } 1479 1480 BRoster().AddToRecentFolders(TargetModel()->EntryRef()); 1481 1482 if (fClientObject) 1483 fClientObject->SendMessage(&fTarget, openMessage); 1484 else 1485 fTarget.SendMessage(openMessage); 1486 1487 // close window if we're dealing with standard message 1488 if (fHideWhenDone) 1489 PostMessage(B_QUIT_REQUESTED); 1490 } 1491 1492 1493 void 1494 TFilePanel::HandleOpenButton() 1495 { 1496 PoseView()->CommitActivePose(); 1497 BObjectList<BPose> *selection = PoseView()->SelectionList(); 1498 1499 // if we have only one directory and we're not opening dirs, enter. 1500 if ((fNodeFlavors & B_DIRECTORY_NODE) == 0 1501 && selection->CountItems() == 1) { 1502 Model *model = selection->FirstItem()->TargetModel(); 1503 1504 if (model->IsDirectory() 1505 || (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE) 1506 && model->ResolveIfLink()->IsDirectory())) { 1507 1508 BMessage message(B_REFS_RECEIVED); 1509 message.AddRef("refs", model->EntryRef()); 1510 PostMessage(&message); 1511 return; 1512 } 1513 } 1514 1515 // don't do anything unless there are items selected 1516 // message->fMessage->message from here to end 1517 if (selection->CountItems()) { 1518 BMessage message(*fMessage); 1519 // go through selection and add appropriate items 1520 for (int32 index = 0; index < selection->CountItems(); index++) { 1521 Model *model = selection->ItemAt(index)->TargetModel(); 1522 1523 if (((fNodeFlavors & B_DIRECTORY_NODE) != 0 1524 && model->ResolveIfLink()->IsDirectory()) 1525 || ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink()) 1526 || ((fNodeFlavors & B_FILE_NODE) != 0 && model->ResolveIfLink()->IsFile())) 1527 message.AddRef("refs", model->EntryRef()); 1528 } 1529 1530 OpenSelectionCommon(&message); 1531 } 1532 } 1533 1534 1535 void 1536 TFilePanel::SwitchDirMenuTo(const entry_ref *ref) 1537 { 1538 BEntry entry(ref); 1539 for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--) 1540 delete fDirMenu->RemoveItem(index); 1541 1542 fDirMenuField->MenuBar()->RemoveItem((int32)0); 1543 fDirMenu->Populate(&entry, 0, true, true, false, true); 1544 1545 ModelMenuItem *item = dynamic_cast<ModelMenuItem *>( 1546 fDirMenuField->MenuBar()->ItemAt(0)); 1547 ASSERT(item); 1548 item->SetEntry(&entry); 1549 } 1550 1551 1552 void 1553 TFilePanel::WindowActivated(bool active) 1554 { 1555 // force focus to update properly 1556 fBackView->Invalidate(); 1557 _inherited::WindowActivated(active); 1558 } 1559 1560 1561 BFilePanelPoseView::BFilePanelPoseView(Model *model, BRect frame, uint32 resizeMask) 1562 : BPoseView(model, frame, kListMode, resizeMask), 1563 fIsDesktop(false) 1564 { 1565 } 1566 1567 1568 void 1569 BFilePanelPoseView::StartWatching() 1570 { 1571 TTracker::WatchNode(0, B_WATCH_MOUNT, this); 1572 1573 // inter-application observing 1574 BMessenger tracker(kTrackerSignature); 1575 BHandler::StartWatching(tracker, kVolumesOnDesktopChanged); 1576 BHandler::StartWatching(tracker, kDesktopIntegrationChanged); 1577 } 1578 1579 1580 void 1581 BFilePanelPoseView::StopWatching() 1582 { 1583 stop_watching(this); 1584 1585 // inter-application observing 1586 BMessenger tracker(kTrackerSignature); 1587 BHandler::StopWatching(tracker, kVolumesOnDesktopChanged); 1588 BHandler::StopWatching(tracker, kDesktopIntegrationChanged); 1589 } 1590 1591 1592 bool 1593 BFilePanelPoseView::FSNotification(const BMessage *message) 1594 { 1595 if (IsDesktopView()) { 1596 // Pretty much copied straight from DesktopPoseView. Would be better 1597 // if the code could be shared somehow. 1598 switch (message->FindInt32("opcode")) { 1599 case B_DEVICE_MOUNTED: 1600 { 1601 dev_t device; 1602 if (message->FindInt32("new device", &device) != B_OK) 1603 break; 1604 1605 ASSERT(TargetModel()); 1606 TrackerSettings settings; 1607 1608 BVolume volume(device); 1609 if (volume.InitCheck() != B_OK) 1610 break; 1611 1612 if (settings.MountVolumesOntoDesktop() 1613 && (!volume.IsShared() || settings.MountSharedVolumesOntoDesktop())) { 1614 // place an icon for the volume onto the desktop 1615 CreateVolumePose(&volume, true); 1616 } 1617 1618 if (!ShouldIntegrateDesktop(volume)) 1619 break; 1620 1621 BDirectory otherDesktop; 1622 BEntry entry; 1623 1624 if (FSGetDeskDir(&otherDesktop, volume.Device()) == B_OK 1625 && otherDesktop.GetEntry(&entry) == B_OK) { 1626 // place desktop items from the mounted volume onto the desktop 1627 Model model(&entry); 1628 if (model.InitCheck() == B_OK) 1629 AddPoses(&model); 1630 } 1631 } 1632 break; 1633 } 1634 } 1635 return _inherited::FSNotification(message); 1636 } 1637 1638 1639 void 1640 BFilePanelPoseView::RestoreState(AttributeStreamNode *node) 1641 { 1642 _inherited::RestoreState(node); 1643 fViewState->SetViewMode(kListMode); 1644 } 1645 1646 1647 void 1648 BFilePanelPoseView::RestoreState(const BMessage &message) 1649 { 1650 _inherited::RestoreState(message); 1651 } 1652 1653 1654 void 1655 BFilePanelPoseView::SavePoseLocations(BRect *) 1656 { 1657 } 1658 1659 1660 EntryListBase * 1661 BFilePanelPoseView::InitDirentIterator(const entry_ref *ref) 1662 { 1663 if (IsDesktopView()) 1664 return DesktopPoseView::InitDesktopDirentIterator(this, ref); 1665 1666 return _inherited::InitDirentIterator(ref); 1667 } 1668 1669 1670 bool 1671 BFilePanelPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) 1672 { 1673 if (IsDesktopView() && !ShouldShowDesktopPose(TargetModel()->NodeRef()->device, 1674 model, poseInfo)) 1675 return false; 1676 1677 return _inherited::ShouldShowPose(model, poseInfo); 1678 } 1679 1680 1681 void 1682 BFilePanelPoseView::SetIsDesktop(bool on) 1683 { 1684 fIsDesktop = on; 1685 } 1686 1687 1688 bool 1689 BFilePanelPoseView::IsDesktopView() const 1690 { 1691 return fIsDesktop; 1692 } 1693 1694 1695 void 1696 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared) 1697 { 1698 if (IsDesktopView()) { 1699 if (!visible) 1700 RemoveRootPoses(); 1701 else 1702 AddRootPoses(true, showShared); 1703 } 1704 1705 1706 TFilePanel *filepanel = dynamic_cast<TFilePanel*>(Window()); 1707 if (filepanel) 1708 filepanel->SetTo(TargetModel()->EntryRef()); 1709 } 1710 1711 1712 void 1713 BFilePanelPoseView::AdaptToVolumeChange(BMessage *message) 1714 { 1715 bool showDisksIcon; 1716 bool mountVolumesOnDesktop; 1717 bool mountSharedVolumesOntoDesktop; 1718 1719 message->FindBool("ShowDisksIcon", &showDisksIcon); 1720 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1721 message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); 1722 1723 BEntry entry("/"); 1724 Model model(&entry); 1725 if (model.InitCheck() == B_OK) { 1726 BMessage monitorMsg; 1727 monitorMsg.what = B_NODE_MONITOR; 1728 1729 if (showDisksIcon) 1730 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1731 else 1732 monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED); 1733 1734 monitorMsg.AddInt32("device", model.NodeRef()->device); 1735 monitorMsg.AddInt64("node", model.NodeRef()->node); 1736 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1737 monitorMsg.AddString("name", model.EntryRef()->name); 1738 TrackerSettings().SetShowDisksIcon(showDisksIcon); 1739 if (Window()) 1740 Window()->PostMessage(&monitorMsg, this); 1741 } 1742 1743 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1744 } 1745 1746 1747 void 1748 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage *message) 1749 { 1750 bool mountVolumesOnDesktop = true; 1751 bool mountSharedVolumesOntoDesktop = true; 1752 1753 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1754 message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); 1755 1756 ShowVolumes(false, mountSharedVolumesOntoDesktop); 1757 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1758 } 1759 1760