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