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