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 PoseView()->SetIsDesktop(isDesktop); 560 PoseView()->SwitchDir(&setToRef); 561 SwitchDirMenuTo(&setToRef); 562 563 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 564 // our shortcut got possibly removed because the home 565 // menu item got removed - we shouldn't really have to do 566 // this - this is a workaround for a kit bug. 567 } 568 569 570 void 571 TFilePanel::Rewind() 572 { 573 fSelectionIterator = 0; 574 } 575 576 577 void 578 TFilePanel::SetClientObject(BFilePanel* panel) 579 { 580 fClientObject = panel; 581 } 582 583 584 void 585 TFilePanel::AdjustButton() 586 { 587 // adjust button state 588 BButton* button = dynamic_cast<BButton*>(FindView("default button")); 589 if (button == NULL) 590 return; 591 592 BTextControl* textControl 593 = dynamic_cast<BTextControl*>(FindView("text view")); 594 BObjectList<BPose>* selectionList = fPoseView->SelectionList(); 595 BString buttonText = fButtonText; 596 bool enabled = false; 597 598 if (fIsSavePanel && textControl != NULL) { 599 enabled = textControl->Text()[0] != '\0'; 600 if (fPoseView->IsFocus()) { 601 fPoseView->ShowSelection(true); 602 if (selectionList->CountItems() == 1) { 603 Model* model = selectionList->FirstItem()->TargetModel(); 604 if (model->ResolveIfLink()->IsDirectory()) { 605 enabled = true; 606 buttonText = B_TRANSLATE("Open"); 607 } else { 608 // insert the name of the selected model into 609 // the text field, do not alter focus 610 textControl->SetText(model->Name()); 611 } 612 } 613 } else 614 fPoseView->ShowSelection(false); 615 } else { 616 int32 count = selectionList->CountItems(); 617 if (count) { 618 enabled = true; 619 620 // go through selection list looking at content 621 for (int32 index = 0; index < count; index++) { 622 Model* model = selectionList->ItemAt(index)->TargetModel(); 623 624 uint32 modelFlavor = GetLinkFlavor(model, false); 625 uint32 linkFlavor = GetLinkFlavor(model, true); 626 627 // if only one item is selected and we're not in dir 628 // selection mode then we don't disable button ever 629 if ((modelFlavor == B_DIRECTORY_NODE 630 || linkFlavor == B_DIRECTORY_NODE) 631 && count == 1) { 632 break; 633 } 634 635 if ((fNodeFlavors & modelFlavor) == 0 636 && (fNodeFlavors & linkFlavor) == 0) { 637 enabled = false; 638 break; 639 } 640 } 641 } else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) { 642 // No selection, but the current directory could be opened. 643 enabled = true; 644 } 645 } 646 647 button->SetLabel(buttonText.String()); 648 button->SetEnabled(enabled); 649 } 650 651 652 void 653 TFilePanel::SelectionChanged() 654 { 655 AdjustButton(); 656 657 if (fClientObject) 658 fClientObject->SelectionChanged(); 659 } 660 661 662 status_t 663 TFilePanel::GetNextEntryRef(entry_ref* ref) 664 { 665 if (!ref) 666 return B_ERROR; 667 668 BPose* pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++); 669 if (!pose) 670 return B_ERROR; 671 672 *ref = *pose->TargetModel()->EntryRef(); 673 return B_OK; 674 } 675 676 677 BPoseView* 678 TFilePanel::NewPoseView(Model* model, uint32) 679 { 680 return new BFilePanelPoseView(model); 681 } 682 683 684 void 685 TFilePanel::Init(const BMessage*) 686 { 687 BRect windRect(Bounds()); 688 fBackView = new BView(Bounds(), "View", B_FOLLOW_ALL, 0); 689 fBackView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 690 AddChild(fBackView); 691 692 // add poseview menu bar 693 fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar"); 694 fMenuBar->SetBorder(B_BORDER_FRAME); 695 fBackView->AddChild(fMenuBar); 696 697 // add directory menu and menufield 698 font_height ht; 699 be_plain_font->GetHeight(&ht); 700 const float f_height = ht.ascent + ht.descent + ht.leading; 701 const float spacing = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING); 702 703 BRect rect; 704 rect.top = fMenuBar->Bounds().Height() + spacing; 705 rect.left = spacing; 706 rect.right = rect.left + (spacing * 50); 707 rect.bottom = rect.top + (f_height > 22 ? f_height : 22); 708 709 fDirMenuField = new BMenuField(rect, "DirMenuField", "", NULL); 710 fDirMenuField->MenuBar()->SetFont(be_plain_font); 711 fDirMenuField->SetDivider(0); 712 fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f); 713 // Make room for the icon 714 715 fDirMenu = new BDirMenu(fDirMenuField->MenuBar(), 716 this, kSwitchDirectory, "refs"); 717 718 BEntry entry(TargetModel()->EntryRef()); 719 if (entry.InitCheck() == B_OK) 720 fDirMenu->Populate(&entry, 0, true, true, false, true); 721 else 722 fDirMenu->Populate(0, 0, true, true, false, true); 723 724 fBackView->AddChild(fDirMenuField); 725 726 // add buttons 727 fButtonText = fIsSavePanel ? B_TRANSLATE("Save") : B_TRANSLATE("Open"); 728 BButton* default_button = new BButton(BRect(), "default button", 729 fButtonText.String(), new BMessage(kDefaultButton), 730 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 731 BSize preferred = default_button->PreferredSize(); 732 const BRect defaultButtonRect = BRect(BPoint( 733 windRect.Width() - (preferred.Width() + spacing + be_control_look->GetScrollBarWidth()), 734 windRect.Height() - (preferred.Height() + spacing)), 735 preferred); 736 default_button->MoveTo(defaultButtonRect.LeftTop()); 737 default_button->ResizeTo(preferred); 738 fBackView->AddChild(default_button); 739 740 BButton* cancel_button = new BButton(BRect(), "cancel button", 741 B_TRANSLATE("Cancel"), new BMessage(kCancelButton), 742 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 743 preferred = cancel_button->PreferredSize(); 744 cancel_button->MoveTo(defaultButtonRect.LeftTop() 745 - BPoint(preferred.Width() + spacing, 0)); 746 cancel_button->ResizeTo(preferred); 747 fBackView->AddChild(cancel_button); 748 749 // add file name text view 750 if (fIsSavePanel) { 751 BRect rect(defaultButtonRect); 752 rect.left = spacing; 753 rect.right = rect.left + spacing * 28; 754 755 fTextControl = new BTextControl(rect, "text view", 756 B_TRANSLATE("save text"), "", NULL, 757 B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 758 DisallowMetaKeys(fTextControl->TextView()); 759 DisallowFilenameKeys(fTextControl->TextView()); 760 fBackView->AddChild(fTextControl); 761 fTextControl->SetDivider(0.0f); 762 fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); 763 } 764 765 // Add PoseView 766 PoseView()->SetName("ActualPoseView"); 767 fPoseContainer->SetName("PoseView"); 768 fPoseContainer->SetResizingMode(B_FOLLOW_ALL); 769 fBorderedView->EnableBorderHighlight(true); 770 771 rect.left = spacing; 772 rect.top = fDirMenuField->Frame().bottom + spacing; 773 rect.right = windRect.Width() - spacing; 774 rect.bottom = defaultButtonRect.top - spacing; 775 fPoseContainer->MoveTo(rect.LeftTop()); 776 fPoseContainer->ResizeTo(rect.Size()); 777 778 PoseView()->AddScrollBars(); 779 PoseView()->SetDragEnabled(false); 780 PoseView()->SetDropEnabled(false); 781 PoseView()->SetSelectionHandler(this); 782 PoseView()->SetSelectionChangedHook(true); 783 PoseView()->DisableSaveLocation(); 784 785 if (fIsSavePanel) 786 fBackView->AddChild(fPoseContainer, fTextControl); 787 else 788 fBackView->AddChild(fPoseContainer); 789 790 AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton)); 791 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 792 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, 793 new BMessage(kShowSelectionWindow)); 794 AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), this); 795 AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection), 796 PoseView()); 797 AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView()); 798 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir)); 799 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 800 new BMessage(kOpenDir)); 801 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir)); 802 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 803 new BMessage(kOpenParentDir)); 804 805 if (!fIsSavePanel && (fNodeFlavors & B_DIRECTORY_NODE) == 0) 806 default_button->SetEnabled(false); 807 808 default_button->MakeDefault(true); 809 810 RestoreState(); 811 812 AddMenus(); 813 AddContextMenus(); 814 815 FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"), 816 new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED), 817 BMessenger(this), IsSavePanel(), fPoseView->RefFilter()); 818 favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"), 819 new BMessage(kAddCurrentDir))); 820 favorites->AddItem(new BMenuItem( 821 B_TRANSLATE("Edit favorites" B_UTF8_ELLIPSIS), 822 new BMessage(kEditFavorites))); 823 824 fMenuBar->AddItem(favorites); 825 826 // configure menus 827 BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window")); 828 if (item) { 829 fMenuBar->RemoveItem(item); 830 delete item; 831 } 832 833 item = fMenuBar->FindItem(B_TRANSLATE("File")); 834 if (item) { 835 BMenu* menu = item->Submenu(); 836 if (menu) { 837 item = menu->FindItem(kOpenSelection); 838 if (item && menu->RemoveItem(item)) 839 delete item; 840 841 item = menu->FindItem(kDuplicateSelection); 842 if (item && menu->RemoveItem(item)) 843 delete item; 844 845 // remove add-ons menu, identifier menu, separator 846 item = menu->FindItem(B_TRANSLATE("Add-ons")); 847 if (item) { 848 int32 index = menu->IndexOf(item); 849 delete menu->RemoveItem(index); 850 delete menu->RemoveItem(--index); 851 delete menu->RemoveItem(--index); 852 } 853 854 // remove separator 855 item = menu->FindItem(B_CUT); 856 if (item) { 857 item = menu->ItemAt(menu->IndexOf(item)-1); 858 if (item && menu->RemoveItem(item)) 859 delete item; 860 } 861 } 862 } 863 864 PoseView()->ScrollTo(B_ORIGIN); 865 PoseView()->UpdateScrollRange(); 866 PoseView()->ScrollTo(B_ORIGIN); 867 868 // Focus on text control initially, but do not alter focus afterwords 869 // because pose view focus is needed for Cut/Copy/Paste to work. 870 871 if (fIsSavePanel && fTextControl != NULL) { 872 fTextControl->MakeFocus(); 873 fTextControl->TextView()->SelectAll(); 874 } else 875 PoseView()->MakeFocus(); 876 877 app_info info; 878 BString title; 879 if (be_app->GetAppInfo(&info) == B_OK) { 880 if (!gLocalizedNamePreferred 881 || BLocaleRoster::Default()->GetLocalizedFileName( 882 title, info.ref, false) != B_OK) 883 title = info.ref.name; 884 title << ": "; 885 } 886 title << fButtonText; // Open or Save 887 888 SetTitle(title.String()); 889 890 SetSizeLimits(spacing * 60, 10000, spacing * 33, 10000); 891 } 892 893 894 void 895 TFilePanel::RestoreState() 896 { 897 BNode defaultingNode; 898 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 899 false)) { 900 AttributeStreamFileNode streamNodeSource(&defaultingNode); 901 RestoreWindowState(&streamNodeSource); 902 PoseView()->Init(&streamNodeSource); 903 fDefaultStateRestored = true; 904 } else { 905 RestoreWindowState(NULL); 906 PoseView()->Init(NULL); 907 fDefaultStateRestored = false; 908 } 909 910 // Finish UI creation now that the PoseView is initialized 911 InitLayout(); 912 } 913 914 915 void 916 TFilePanel::SaveState(bool) 917 { 918 BNode defaultingNode; 919 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 920 true, false)) { 921 AttributeStreamFileNode streamNodeDestination(&defaultingNode); 922 SaveWindowState(&streamNodeDestination); 923 PoseView()->SaveState(&streamNodeDestination); 924 fStateNeedsSaving = false; 925 } 926 } 927 928 929 void 930 TFilePanel::SaveState(BMessage &message) const 931 { 932 _inherited::SaveState(message); 933 } 934 935 936 void 937 TFilePanel::RestoreWindowState(AttributeStreamNode* node) 938 { 939 SetSizeLimits(360, 10000, 200, 10000); 940 if (!node) 941 return; 942 943 const char* rectAttributeName = kAttrWindowFrame; 944 BRect frame(Frame()); 945 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 946 == sizeof(BRect)) { 947 MoveTo(frame.LeftTop()); 948 ResizeTo(frame.Width(), frame.Height()); 949 } 950 fStateNeedsSaving = false; 951 } 952 953 954 void 955 TFilePanel::RestoreState(const BMessage &message) 956 { 957 _inherited::RestoreState(message); 958 } 959 960 961 void 962 TFilePanel::RestoreWindowState(const BMessage &message) 963 { 964 _inherited::RestoreWindowState(message); 965 } 966 967 968 void 969 TFilePanel::AddFileContextMenus(BMenu* menu) 970 { 971 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 972 new BMessage(kGetInfo), 'I')); 973 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 974 new BMessage(kEditItem), 'E')); 975 976 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() 977 ? B_TRANSLATE("Delete") 978 : B_TRANSLATE("Move to Trash"), 979 new BMessage(kMoveToTrash), 'T')); 980 menu->AddSeparatorItem(); 981 982 BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"), 983 new BMessage(B_CUT), 'X'); 984 menu->AddItem(cutItem); 985 BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"), 986 new BMessage(B_COPY), 'C'); 987 menu->AddItem(copyItem); 988 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 989 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 990 new BMessage(B_PASTE), 'V'); 991 menu->AddItem(pasteItem); 992 #endif 993 994 menu->SetTargetForItems(PoseView()); 995 cutItem->SetTarget(this); 996 copyItem->SetTarget(this); 997 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 998 pasteItem->SetTarget(this); 999 #endif 1000 } 1001 1002 1003 void 1004 TFilePanel::AddVolumeContextMenus(BMenu* menu) 1005 { 1006 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 1007 new BMessage(kOpenSelection), 'O')); 1008 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 1009 new BMessage(kGetInfo), 'I')); 1010 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 1011 new BMessage(kEditItem), 'E')); 1012 menu->AddSeparatorItem(); 1013 1014 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1015 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 1016 new BMessage(B_PASTE), 'V'); 1017 #endif 1018 1019 menu->SetTargetForItems(PoseView()); 1020 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1021 pasteItem->SetTarget(this); 1022 #endif 1023 } 1024 1025 1026 void 1027 TFilePanel::AddWindowContextMenus(BMenu* menu) 1028 { 1029 BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"), 1030 new BMessage(kNewFolder), 'N'); 1031 item->SetTarget(PoseView()); 1032 menu->AddItem(item); 1033 menu->AddSeparatorItem(); 1034 1035 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1036 item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V'); 1037 item->SetTarget(this); 1038 menu->AddItem(item); 1039 menu->AddSeparatorItem(); 1040 #endif 1041 1042 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 1043 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); 1044 item->SetTarget(PoseView()); 1045 menu->AddItem(item); 1046 1047 item = new BMenuItem(B_TRANSLATE("Select all"), 1048 new BMessage(B_SELECT_ALL), 'A'); 1049 item->SetTarget(this); 1050 menu->AddItem(item); 1051 1052 item = new BMenuItem(B_TRANSLATE("Invert selection"), 1053 new BMessage(kInvertSelection), 'S'); 1054 item->SetTarget(PoseView()); 1055 menu->AddItem(item); 1056 1057 item = new BMenuItem(B_TRANSLATE("Go to parent"), 1058 new BMessage(kOpenParentDir), B_UP_ARROW); 1059 item->SetTarget(this); 1060 menu->AddItem(item); 1061 } 1062 1063 1064 void 1065 TFilePanel::AddDropContextMenus(BMenu*) 1066 { 1067 } 1068 1069 1070 void 1071 TFilePanel::MenusBeginning() 1072 { 1073 if (fMenuBar == NULL) 1074 return; 1075 1076 if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) { 1077 // don't commit active pose if only a keyboard shortcut is 1078 // invoked - this would prevent Cut/Copy/Paste from working 1079 PoseView()->CommitActivePose(); 1080 } 1081 1082 int32 selectCount = PoseView()->CountSelected(); 1083 1084 EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot() 1085 && !PoseView()->TargetVolumeIsReadOnly()); 1086 EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot() 1087 && selectCount > 0 && !PoseView()->SelectedVolumeIsReadOnly()); 1088 EnableNamedMenuItem(fMenuBar, kGetInfo, selectCount > 0); 1089 EnableNamedMenuItem(fMenuBar, kEditItem, selectCount == 1); 1090 1091 SetCutItem(fMenuBar); 1092 SetCopyItem(fMenuBar); 1093 SetPasteItem(fMenuBar); 1094 1095 fIsTrackingMenu = true; 1096 } 1097 1098 1099 void 1100 TFilePanel::MenusEnded() 1101 { 1102 fIsTrackingMenu = false; 1103 } 1104 1105 1106 void 1107 TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref) 1108 { 1109 ASSERT(IsLocked()); 1110 BPoint global(where); 1111 PoseView()->ConvertToScreen(&global); 1112 PoseView()->CommitActivePose(); 1113 1114 if (ref != NULL) { 1115 // clicked on a pose, show file or volume context menu 1116 Model model(ref); 1117 if (model.InitCheck() != B_OK) 1118 return; // bail out, do not show context menu 1119 1120 if (TargetModel()->IsRoot() || model.IsVolume()) { 1121 // Volume context menu 1122 fContextMenu = fVolumeContextMenu; 1123 EnableNamedMenuItem(fContextMenu, kOpenSelection, true); 1124 EnableNamedMenuItem(fContextMenu, kGetInfo, true); 1125 EnableNamedMenuItem(fContextMenu, kEditItem, !(model.IsDesktop() 1126 || model.IsRoot() || model.IsTrash())); 1127 1128 SetPasteItem(fContextMenu); 1129 } else { 1130 // File context menu 1131 fContextMenu = fFileContextMenu; 1132 EnableNamedMenuItem(fContextMenu, kGetInfo, true); 1133 EnableNamedMenuItem(fContextMenu, kEditItem, !(model.IsDesktop() 1134 || model.IsRoot() || model.IsTrash())); 1135 EnableNamedMenuItem(fContextMenu, kMoveToTrash, 1136 !PoseView()->SelectedVolumeIsReadOnly()); 1137 1138 SetCutItem(fContextMenu); 1139 SetCopyItem(fContextMenu); 1140 SetPasteItem(fContextMenu); 1141 } 1142 } else { 1143 // Window context menu 1144 fContextMenu = fWindowContextMenu; 1145 EnableNamedMenuItem(fContextMenu, kNewFolder, 1146 !TargetModel()->IsRoot() 1147 && !PoseView()->TargetVolumeIsReadOnly()); 1148 EnableNamedMenuItem(fContextMenu, kOpenParentDir, 1149 !TargetModel()->IsRoot()); 1150 EnableNamedMenuItem(fContextMenu, kMoveToTrash, 1151 !TargetModel()->IsRoot() && PoseView()->CountSelected() > 0 1152 && !PoseView()->SelectedVolumeIsReadOnly()); 1153 1154 SetPasteItem(fContextMenu); 1155 } 1156 1157 // context menu invalid or popup window is already open 1158 if (fContextMenu == NULL || fContextMenu->Window() != NULL) 1159 return; 1160 1161 fContextMenu->Go(global, true, true, true); 1162 fContextMenu = NULL; 1163 } 1164 1165 1166 void 1167 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*) 1168 { 1169 // do nothing here so nav menu doesn't get added 1170 } 1171 1172 1173 void 1174 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text) 1175 { 1176 switch (selector) { 1177 case B_CANCEL_BUTTON: 1178 { 1179 BButton* button 1180 = dynamic_cast<BButton*>(FindView("cancel button")); 1181 if (button == NULL) 1182 break; 1183 1184 float old_width = button->StringWidth(button->Label()); 1185 button->SetLabel(text); 1186 float delta = old_width - button->StringWidth(text); 1187 if (delta) { 1188 button->MoveBy(delta, 0); 1189 button->ResizeBy(-delta, 0); 1190 } 1191 } 1192 break; 1193 1194 case B_DEFAULT_BUTTON: 1195 { 1196 fButtonText = text; 1197 float delta = 0; 1198 BButton* button 1199 = dynamic_cast<BButton*>(FindView("default button")); 1200 if (button != NULL) { 1201 float old_width = button->StringWidth(button->Label()); 1202 button->SetLabel(text); 1203 delta = old_width - button->StringWidth(text); 1204 if (delta) { 1205 button->MoveBy(delta, 0); 1206 button->ResizeBy(-delta, 0); 1207 } 1208 } 1209 1210 // now must move cancel button 1211 button = dynamic_cast<BButton*>(FindView("cancel button")); 1212 if (button != NULL) 1213 button->MoveBy(delta, 0); 1214 } 1215 break; 1216 } 1217 } 1218 1219 1220 void 1221 TFilePanel::SetSaveText(const char* text) 1222 { 1223 if (text == NULL) 1224 return; 1225 1226 BTextControl* textControl 1227 = dynamic_cast<BTextControl*>(FindView("text view")); 1228 if (textControl != NULL) { 1229 textControl->SetText(text); 1230 if (textControl->TextView() != NULL) 1231 textControl->TextView()->SelectAll(); 1232 } 1233 } 1234 1235 1236 void 1237 TFilePanel::MessageReceived(BMessage* message) 1238 { 1239 entry_ref ref; 1240 1241 switch (message->what) { 1242 case B_REFS_RECEIVED: 1243 // item was double clicked in file panel (PoseView) 1244 if (message->FindRef("refs", &ref) == B_OK) { 1245 BEntry entry(&ref, true); 1246 if (entry.InitCheck() == B_OK) { 1247 // Double-click on dir or link-to-dir ALWAYS opens the 1248 // dir. If more than one dir is selected, the first is 1249 // entered. 1250 if (entry.IsDirectory()) { 1251 entry.GetRef(&ref); 1252 bool isDesktop = SwitchDirToDesktopIfNeeded(ref); 1253 1254 PoseView()->SetIsDesktop(isDesktop); 1255 entry.SetTo(&ref); 1256 PoseView()->SwitchDir(&ref); 1257 SwitchDirMenuTo(&ref); 1258 } else { 1259 // Otherwise, we have a file or a link to a file. 1260 // AdjustButton has already tested the flavor; 1261 // all we have to do is see if the button is enabled. 1262 BButton* button = dynamic_cast<BButton*>( 1263 FindView("default button")); 1264 if (button == NULL || !button->IsEnabled()) 1265 break; 1266 1267 if (IsSavePanel()) { 1268 int32 count = 0; 1269 type_code type; 1270 message->GetInfo("refs", &type, &count); 1271 1272 // Don't allow saves of multiple files 1273 if (count > 1) { 1274 ShowCenteredAlert( 1275 B_TRANSLATE( 1276 "Sorry, saving more than one " 1277 "item is not allowed."), 1278 B_TRANSLATE("Cancel")); 1279 } else { 1280 // if we are a savepanel, set up the 1281 // filepanel correctly then pass control 1282 // so we follow the same path as if the user 1283 // clicked the save button 1284 1285 // set the 'name' fld to the current ref's 1286 // name notify the panel that the default 1287 // button should be enabled 1288 SetSaveText(ref.name); 1289 SelectionChanged(); 1290 1291 HandleSaveButton(); 1292 } 1293 break; 1294 } 1295 1296 // send handler a message and close 1297 BMessage openMessage(*fMessage); 1298 for (int32 index = 0; ; index++) { 1299 if (message->FindRef("refs", index, &ref) != B_OK) 1300 break; 1301 openMessage.AddRef("refs", &ref); 1302 } 1303 OpenSelectionCommon(&openMessage); 1304 } 1305 } 1306 } 1307 break; 1308 1309 case kSwitchDirectory: 1310 { 1311 entry_ref ref; 1312 // this comes from dir menu or nav menu, so switch directories 1313 if (message->FindRef("refs", &ref) == B_OK) { 1314 BEntry entry(&ref, true); 1315 if (entry.GetRef(&ref) == B_OK) 1316 SetTo(&ref); 1317 } 1318 break; 1319 } 1320 1321 case kSwitchToHome: 1322 { 1323 BPath homePath; 1324 entry_ref ref; 1325 if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK 1326 || get_ref_for_path(homePath.Path(), &ref) != B_OK) { 1327 break; 1328 } 1329 1330 SetTo(&ref); 1331 break; 1332 } 1333 1334 case kAddCurrentDir: 1335 { 1336 BPath path; 1337 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) 1338 != B_OK) { 1339 break; 1340 } 1341 1342 path.Append(kGoDirectory); 1343 BDirectory goDirectory(path.Path()); 1344 1345 if (goDirectory.InitCheck() == B_OK) { 1346 BEntry entry(TargetModel()->EntryRef()); 1347 entry.GetPath(&path); 1348 1349 BSymLink link; 1350 goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(), 1351 &link); 1352 } 1353 break; 1354 } 1355 1356 case kEditFavorites: 1357 { 1358 BPath path; 1359 if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) 1360 != B_OK) { 1361 break; 1362 } 1363 1364 path.Append(kGoDirectory); 1365 BMessenger msgr(kTrackerSignature); 1366 if (msgr.IsValid()) { 1367 BMessage message(B_REFS_RECEIVED); 1368 entry_ref ref; 1369 if (get_ref_for_path(path.Path(), &ref) == B_OK) { 1370 message.AddRef("refs", &ref); 1371 msgr.SendMessage(&message); 1372 } 1373 } 1374 break; 1375 } 1376 1377 case kCancelButton: 1378 PostMessage(B_QUIT_REQUESTED); 1379 break; 1380 1381 case kResizeToFit: 1382 ResizeToFit(); 1383 break; 1384 1385 case kOpenDir: 1386 OpenDirectory(); 1387 break; 1388 1389 case kOpenParentDir: 1390 OpenParent(); 1391 break; 1392 1393 case kDefaultButton: 1394 if (fIsSavePanel) { 1395 if (PoseView()->IsFocus() 1396 && PoseView()->CountSelected() == 1) { 1397 Model* model = (PoseView()->SelectionList()-> 1398 FirstItem())->TargetModel(); 1399 if (model->ResolveIfLink()->IsDirectory()) { 1400 PoseView()->CommitActivePose(); 1401 PoseView()->OpenSelection(); 1402 break; 1403 } 1404 } 1405 1406 HandleSaveButton(); 1407 } else 1408 HandleOpenButton(); 1409 break; 1410 1411 case B_OBSERVER_NOTICE_CHANGE: 1412 { 1413 int32 observerWhat; 1414 if (message->FindInt32("be:observe_change_what", &observerWhat) 1415 == B_OK) { 1416 switch (observerWhat) { 1417 case kDesktopFilePanelRootChanged: 1418 { 1419 bool desktopIsRoot = true; 1420 if (message->FindBool("DesktopFilePanelRoot", 1421 &desktopIsRoot) == B_OK) { 1422 TrackerSettings(). 1423 SetDesktopFilePanelRoot(desktopIsRoot); 1424 } 1425 SetTo(TargetModel()->EntryRef()); 1426 break; 1427 } 1428 } 1429 } 1430 break; 1431 } 1432 1433 default: 1434 _inherited::MessageReceived(message); 1435 break; 1436 } 1437 } 1438 1439 1440 void 1441 TFilePanel::OpenDirectory() 1442 { 1443 BObjectList<BPose>* list = PoseView()->SelectionList(); 1444 if (list->CountItems() != 1) 1445 return; 1446 1447 Model* model = list->FirstItem()->TargetModel(); 1448 if (model->ResolveIfLink()->IsDirectory()) { 1449 BMessage message(B_REFS_RECEIVED); 1450 message.AddRef("refs", model->EntryRef()); 1451 PostMessage(&message); 1452 } 1453 } 1454 1455 1456 void 1457 TFilePanel::OpenParent() 1458 { 1459 if (!CanOpenParent()) 1460 return; 1461 1462 BEntry parentEntry; 1463 BDirectory dir; 1464 1465 Model oldModel(*PoseView()->TargetModel()); 1466 BEntry entry(oldModel.EntryRef()); 1467 1468 if (entry.InitCheck() == B_OK 1469 && entry.GetParent(&dir) == B_OK 1470 && dir.GetEntry(&parentEntry) == B_OK 1471 && entry != parentEntry) { 1472 1473 entry_ref ref; 1474 parentEntry.GetRef(&ref); 1475 1476 PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref)); 1477 PoseView()->SwitchDir(&ref); 1478 SwitchDirMenuTo(&ref); 1479 1480 // Make sure the child gets selected in the new view 1481 // once it shows up. 1482 fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 1483 (&TFilePanel::SelectChildInParent, this, 1484 const_cast<const entry_ref*>(&ref), 1485 oldModel.NodeRef()), 100000, 200000, 5000000); 1486 } 1487 } 1488 1489 1490 bool 1491 TFilePanel::CanOpenParent() const 1492 { 1493 if (TrackerSettings().DesktopFilePanelRoot()) { 1494 // don't allow opening Desktop folder's parent 1495 if (TargetModel()->IsDesktop()) 1496 return false; 1497 } 1498 1499 // block on "/" 1500 BEntry root("/"); 1501 node_ref rootRef; 1502 root.GetNodeRef(&rootRef); 1503 1504 return rootRef != *TargetModel()->NodeRef(); 1505 } 1506 1507 1508 bool 1509 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref) 1510 { 1511 // support showing Desktop as root of everything 1512 // This call implements the worm hole that maps Desktop as 1513 // a root above the disks 1514 TrackerSettings settings; 1515 if (!settings.DesktopFilePanelRoot()) 1516 // Tracker isn't set up that way, just let Disks show 1517 return false; 1518 1519 BEntry entry(&ref); 1520 BEntry root("/"); 1521 1522 BDirectory desktopDir; 1523 FSGetDeskDir(&desktopDir); 1524 if (FSIsDeskDir(&entry) 1525 // navigated into non-boot desktop, switch to boot desktop 1526 || (entry == root && !settings.ShowDisksIcon())) { 1527 // hit "/" level, map to desktop 1528 1529 desktopDir.GetEntry(&entry); 1530 entry.GetRef(&ref); 1531 return true; 1532 } 1533 return FSIsDeskDir(&entry); 1534 } 1535 1536 1537 bool 1538 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child) 1539 { 1540 AutoLock<TFilePanel> lock(this); 1541 1542 if (!IsLocked()) 1543 return false; 1544 1545 int32 index; 1546 BPose* pose = PoseView()->FindPose(child, &index); 1547 if (!pose) 1548 return false; 1549 1550 PoseView()->UpdateScrollRange(); 1551 // ToDo: Scroll range should be updated by now, for some 1552 // reason sometimes it is not right, force it here 1553 PoseView()->SelectPose(pose, index, true); 1554 return true; 1555 } 1556 1557 1558 int32 1559 TFilePanel::ShowCenteredAlert(const char* text, const char* button1, 1560 const char* button2, const char* button3) 1561 { 1562 BAlert* alert = new BAlert("", text, button1, button2, button3, 1563 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1564 alert->MoveTo(Frame().left + 10, Frame().top + 10); 1565 1566 #if 0 1567 if (button1 != NULL && !strncmp(button1, "Cancel", 7)) 1568 alert->SetShortcut(0, B_ESCAPE); 1569 else if (button2 != NULL && !strncmp(button2, "Cancel", 7)) 1570 alert->SetShortcut(1, B_ESCAPE); 1571 else if (button3 != NULL && !strncmp(button3, "Cancel", 7)) 1572 alert->SetShortcut(2, B_ESCAPE); 1573 #endif 1574 1575 return alert->Go(); 1576 } 1577 1578 1579 void 1580 TFilePanel::HandleSaveButton() 1581 { 1582 BDirectory dir; 1583 1584 if (TargetModel()->IsRoot()) { 1585 ShowCenteredAlert( 1586 B_TRANSLATE("Sorry, you can't save things at the root of " 1587 "your system."), 1588 B_TRANSLATE("Cancel")); 1589 return; 1590 } 1591 1592 // check for some illegal file names 1593 if (strcmp(fTextControl->Text(), ".") == 0 1594 || strcmp(fTextControl->Text(), "..") == 0) { 1595 ShowCenteredAlert( 1596 B_TRANSLATE("The specified name is illegal. Please choose " 1597 "another name."), 1598 B_TRANSLATE("Cancel")); 1599 fTextControl->TextView()->SelectAll(); 1600 return; 1601 } 1602 1603 if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) { 1604 ShowCenteredAlert( 1605 B_TRANSLATE("There was a problem trying to save in the folder " 1606 "you specified. Please try another one."), 1607 B_TRANSLATE("Cancel")); 1608 return; 1609 } 1610 1611 if (dir.Contains(fTextControl->Text())) { 1612 if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) { 1613 ShowCenteredAlert( 1614 B_TRANSLATE("The specified name is already used as the name " 1615 "of a folder. Please choose another name."), 1616 B_TRANSLATE("Cancel")); 1617 fTextControl->TextView()->SelectAll(); 1618 return; 1619 } else { 1620 // if this was invoked by a dbl click, it is an explicit 1621 // replacement of the file. 1622 BString str(B_TRANSLATE("The file \"%name\" already exists in " 1623 "the specified folder. Do you want to replace it?")); 1624 str.ReplaceFirst("%name", fTextControl->Text()); 1625 1626 if (ShowCenteredAlert(str.String(), B_TRANSLATE("Cancel"), 1627 B_TRANSLATE("Replace")) == 0) { 1628 // user canceled 1629 fTextControl->TextView()->SelectAll(); 1630 return; 1631 } 1632 // user selected "Replace" - let app deal with it 1633 } 1634 } 1635 1636 BMessage message(*fMessage); 1637 message.AddRef("directory", TargetModel()->EntryRef()); 1638 message.AddString("name", fTextControl->Text()); 1639 1640 if (fClientObject) 1641 fClientObject->SendMessage(&fTarget, &message); 1642 else 1643 fTarget.SendMessage(&message); 1644 1645 // close window if we're dealing with standard message 1646 if (fHideWhenDone) 1647 PostMessage(B_QUIT_REQUESTED); 1648 } 1649 1650 1651 void 1652 TFilePanel::OpenSelectionCommon(BMessage* openMessage) 1653 { 1654 if (!openMessage->HasRef("refs")) 1655 return; 1656 1657 for (int32 index = 0; ; index++) { 1658 entry_ref ref; 1659 if (openMessage->FindRef("refs", index, &ref) != B_OK) 1660 break; 1661 1662 BEntry entry(&ref, true); 1663 if (entry.InitCheck() == B_OK) { 1664 if (entry.IsDirectory()) 1665 BRoster().AddToRecentFolders(&ref); 1666 else 1667 BRoster().AddToRecentDocuments(&ref); 1668 } 1669 } 1670 1671 BRoster().AddToRecentFolders(TargetModel()->EntryRef()); 1672 1673 if (fClientObject) 1674 fClientObject->SendMessage(&fTarget, openMessage); 1675 else 1676 fTarget.SendMessage(openMessage); 1677 1678 // close window if we're dealing with standard message 1679 if (fHideWhenDone) 1680 PostMessage(B_QUIT_REQUESTED); 1681 } 1682 1683 1684 void 1685 TFilePanel::HandleOpenButton() 1686 { 1687 PoseView()->CommitActivePose(); 1688 BObjectList<BPose>* selection = PoseView()->SelectionList(); 1689 1690 // if we have only one directory and we're not opening dirs, enter. 1691 if ((fNodeFlavors & B_DIRECTORY_NODE) == 0 1692 && selection->CountItems() == 1) { 1693 Model* model = selection->FirstItem()->TargetModel(); 1694 1695 if (model->IsDirectory() 1696 || (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE) 1697 && model->ResolveIfLink()->IsDirectory())) { 1698 1699 BMessage message(B_REFS_RECEIVED); 1700 message.AddRef("refs", model->EntryRef()); 1701 PostMessage(&message); 1702 return; 1703 } 1704 } 1705 1706 if (selection->CountItems()) { 1707 // there are items selected 1708 // message->fMessage->message from here to end 1709 BMessage message(*fMessage); 1710 // go through selection and add appropriate items 1711 for (int32 index = 0; index < selection->CountItems(); index++) { 1712 Model* model = selection->ItemAt(index)->TargetModel(); 1713 1714 if (((fNodeFlavors & B_DIRECTORY_NODE) != 0 1715 && model->ResolveIfLink()->IsDirectory()) 1716 || ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink()) 1717 || ((fNodeFlavors & B_FILE_NODE) != 0 1718 && model->ResolveIfLink()->IsFile())) { 1719 message.AddRef("refs", model->EntryRef()); 1720 } 1721 } 1722 1723 OpenSelectionCommon(&message); 1724 } else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) { 1725 // Open the current directory. 1726 BMessage message(*fMessage); 1727 message.AddRef("refs", TargetModel()->EntryRef()); 1728 OpenSelectionCommon(&message); 1729 } 1730 } 1731 1732 1733 void 1734 TFilePanel::SwitchDirMenuTo(const entry_ref* ref) 1735 { 1736 BEntry entry(ref); 1737 for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--) 1738 delete fDirMenu->RemoveItem(index); 1739 1740 fDirMenuField->MenuBar()->RemoveItem((int32)0); 1741 fDirMenu->Populate(&entry, 0, true, true, false, true); 1742 1743 ModelMenuItem* item = dynamic_cast<ModelMenuItem*>( 1744 fDirMenuField->MenuBar()->ItemAt(0)); 1745 ASSERT(item != NULL); 1746 1747 if (item != NULL) 1748 item->SetEntry(&entry); 1749 } 1750 1751 1752 void 1753 TFilePanel::WindowActivated(bool active) 1754 { 1755 // force focus to update properly 1756 fBackView->Invalidate(); 1757 _inherited::WindowActivated(active); 1758 } 1759 1760 1761 // #pragma mark - 1762 1763 1764 BFilePanelPoseView::BFilePanelPoseView(Model* model) 1765 : 1766 BPoseView(model, kListMode), 1767 fIsDesktop(model->IsDesktop()) 1768 { 1769 } 1770 1771 1772 void 1773 BFilePanelPoseView::StartWatching() 1774 { 1775 TTracker::WatchNode(0, B_WATCH_MOUNT, this); 1776 1777 // inter-application observing 1778 BMessenger tracker(kTrackerSignature); 1779 BHandler::StartWatching(tracker, kVolumesOnDesktopChanged); 1780 } 1781 1782 1783 void 1784 BFilePanelPoseView::StopWatching() 1785 { 1786 stop_watching(this); 1787 1788 // inter-application observing 1789 BMessenger tracker(kTrackerSignature); 1790 BHandler::StopWatching(tracker, kVolumesOnDesktopChanged); 1791 } 1792 1793 1794 bool 1795 BFilePanelPoseView::FSNotification(const BMessage* message) 1796 { 1797 switch (message->FindInt32("opcode")) { 1798 case B_DEVICE_MOUNTED: 1799 { 1800 if (IsDesktopView()) { 1801 // Pretty much copied straight from DesktopPoseView. 1802 // Would be better if the code could be shared somehow. 1803 dev_t device; 1804 if (message->FindInt32("new device", &device) != B_OK) 1805 break; 1806 1807 ASSERT(TargetModel() != NULL); 1808 TrackerSettings settings; 1809 1810 BVolume volume(device); 1811 if (volume.InitCheck() != B_OK) 1812 break; 1813 1814 if (settings.MountVolumesOntoDesktop() 1815 && (!volume.IsShared() 1816 || settings.MountSharedVolumesOntoDesktop())) { 1817 // place an icon for the volume onto the desktop 1818 CreateVolumePose(&volume, true); 1819 } 1820 } 1821 break; 1822 } 1823 1824 case B_DEVICE_UNMOUNTED: 1825 { 1826 dev_t device; 1827 if (message->FindInt32("device", &device) == B_OK) { 1828 if (TargetModel() != NULL 1829 && TargetModel()->NodeRef()->device == device) { 1830 // Volume currently shown in this file panel 1831 // disappeared, reset location to home directory 1832 BMessage message(kSwitchToHome); 1833 MessageReceived(&message); 1834 } 1835 } 1836 break; 1837 } 1838 } 1839 return _inherited::FSNotification(message); 1840 } 1841 1842 1843 void 1844 BFilePanelPoseView::RestoreState(AttributeStreamNode* node) 1845 { 1846 _inherited::RestoreState(node); 1847 fViewState->SetViewMode(kListMode); 1848 } 1849 1850 1851 void 1852 BFilePanelPoseView::RestoreState(const BMessage &message) 1853 { 1854 _inherited::RestoreState(message); 1855 } 1856 1857 1858 void 1859 BFilePanelPoseView::SavePoseLocations(BRect*) 1860 { 1861 } 1862 1863 1864 EntryListBase* 1865 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref) 1866 { 1867 if (IsDesktopView()) 1868 return DesktopPoseView::InitDesktopDirentIterator(this, ref); 1869 1870 return _inherited::InitDirentIterator(ref); 1871 } 1872 1873 1874 void 1875 BFilePanelPoseView::AddPosesCompleted() 1876 { 1877 _inherited::AddPosesCompleted(); 1878 if (IsDesktopView()) 1879 CreateTrashPose(); 1880 } 1881 1882 1883 void 1884 BFilePanelPoseView::SetIsDesktop(bool on) 1885 { 1886 fIsDesktop = on; 1887 } 1888 1889 1890 bool 1891 BFilePanelPoseView::IsDesktopView() const 1892 { 1893 return fIsDesktop; 1894 } 1895 1896 1897 void 1898 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared) 1899 { 1900 if (IsDesktopView()) { 1901 if (!visible) 1902 RemoveRootPoses(); 1903 else 1904 AddRootPoses(true, showShared); 1905 } 1906 1907 TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window()); 1908 if (filepanel != NULL && TargetModel() != NULL) 1909 filepanel->SetTo(TargetModel()->EntryRef()); 1910 } 1911 1912 1913 void 1914 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message) 1915 { 1916 bool showDisksIcon; 1917 bool mountVolumesOnDesktop; 1918 bool mountSharedVolumesOntoDesktop; 1919 1920 message->FindBool("ShowDisksIcon", &showDisksIcon); 1921 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1922 message->FindBool("MountSharedVolumesOntoDesktop", 1923 &mountSharedVolumesOntoDesktop); 1924 1925 BEntry entry("/"); 1926 Model model(&entry); 1927 if (model.InitCheck() == B_OK) { 1928 BMessage monitorMsg; 1929 monitorMsg.what = B_NODE_MONITOR; 1930 1931 if (showDisksIcon) 1932 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1933 else 1934 monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED); 1935 1936 monitorMsg.AddInt32("device", model.NodeRef()->device); 1937 monitorMsg.AddInt64("node", model.NodeRef()->node); 1938 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1939 monitorMsg.AddString("name", model.EntryRef()->name); 1940 TrackerSettings().SetShowDisksIcon(showDisksIcon); 1941 if (Window() != NULL) 1942 Window()->PostMessage(&monitorMsg, this); 1943 } 1944 1945 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1946 } 1947 1948 1949 void 1950 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message) 1951 { 1952 bool mountVolumesOnDesktop = true; 1953 bool mountSharedVolumesOntoDesktop = true; 1954 1955 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1956 message->FindBool("MountSharedVolumesOntoDesktop", 1957 &mountSharedVolumesOntoDesktop); 1958 1959 ShowVolumes(false, mountSharedVolumesOntoDesktop); 1960 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1961 } 1962