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