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