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