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