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 1010 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1011 menu->AddSeparatorItem(); 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 EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot() 1080 && !PoseView()->TargetVolumeIsReadOnly()); 1081 EnableNamedMenuItem(fMenuBar, kDuplicateSelection, 1082 PoseView()->CanMoveToTrashOrDuplicate()); 1083 EnableNamedMenuItem(fMenuBar, kMoveToTrash, 1084 PoseView()->CanMoveToTrashOrDuplicate()); 1085 EnableNamedMenuItem(fMenuBar, kEditItem, PoseView()->CanEditName()); 1086 1087 SetCutItem(fMenuBar); 1088 SetCopyItem(fMenuBar); 1089 SetPasteItem(fMenuBar); 1090 1091 fIsTrackingMenu = true; 1092 } 1093 1094 1095 void 1096 TFilePanel::MenusEnded() 1097 { 1098 fIsTrackingMenu = false; 1099 } 1100 1101 1102 void 1103 TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref) 1104 { 1105 ASSERT(IsLocked()); 1106 BPoint global(where); 1107 PoseView()->ConvertToScreen(&global); 1108 PoseView()->CommitActivePose(); 1109 1110 if (ref != NULL) { 1111 // clicked on a pose, show file or volume context menu 1112 Model model(ref); 1113 if (model.InitCheck() != B_OK) 1114 return; // bail out, do not show context menu 1115 1116 if (TargetModel()->IsRoot() || model.IsVolume()) { 1117 // Volume context menu 1118 fContextMenu = fVolumeContextMenu; 1119 EnableNamedMenuItem(fContextMenu, kOpenSelection, true); 1120 EnableNamedMenuItem(fContextMenu, kEditItem, 1121 PoseView()->CanEditName()); 1122 1123 SetPasteItem(fContextMenu); 1124 } else { 1125 // File context menu 1126 fContextMenu = fFileContextMenu; 1127 EnableNamedMenuItem(fContextMenu, kEditItem, 1128 PoseView()->CanEditName()); 1129 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, 1130 PoseView()->CanMoveToTrashOrDuplicate()); 1131 EnableNamedMenuItem(fContextMenu, kMoveToTrash, 1132 PoseView()->CanMoveToTrashOrDuplicate()); 1133 1134 SetCutItem(fContextMenu); 1135 SetCopyItem(fContextMenu); 1136 SetPasteItem(fContextMenu); 1137 } 1138 } else { 1139 // Window context menu 1140 fContextMenu = fWindowContextMenu; 1141 EnableNamedMenuItem(fContextMenu, kNewFolder, 1142 !TargetModel()->IsRoot() 1143 && !PoseView()->TargetVolumeIsReadOnly()); 1144 EnableNamedMenuItem(fContextMenu, kOpenParentDir, 1145 !TargetModel()->IsRoot()); 1146 1147 SetPasteItem(fContextMenu); 1148 } 1149 1150 // context menu invalid or popup window is already open 1151 if (fContextMenu == NULL || fContextMenu->Window() != NULL) 1152 return; 1153 1154 fContextMenu->Go(global, true, true, true); 1155 fContextMenu = NULL; 1156 } 1157 1158 1159 void 1160 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*) 1161 { 1162 // do nothing here so nav menu doesn't get added 1163 } 1164 1165 1166 void 1167 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text) 1168 { 1169 switch (selector) { 1170 case B_CANCEL_BUTTON: 1171 { 1172 BButton* button 1173 = dynamic_cast<BButton*>(FindView("cancel button")); 1174 if (button == NULL) 1175 break; 1176 1177 float old_width = button->StringWidth(button->Label()); 1178 button->SetLabel(text); 1179 float delta = old_width - button->StringWidth(text); 1180 if (delta) { 1181 button->MoveBy(delta, 0); 1182 button->ResizeBy(-delta, 0); 1183 } 1184 } 1185 break; 1186 1187 case B_DEFAULT_BUTTON: 1188 { 1189 fButtonText = text; 1190 float delta = 0; 1191 BButton* button 1192 = dynamic_cast<BButton*>(FindView("default button")); 1193 if (button != NULL) { 1194 float old_width = button->StringWidth(button->Label()); 1195 button->SetLabel(text); 1196 delta = old_width - button->StringWidth(text); 1197 if (delta) { 1198 button->MoveBy(delta, 0); 1199 button->ResizeBy(-delta, 0); 1200 } 1201 } 1202 1203 // now must move cancel button 1204 button = dynamic_cast<BButton*>(FindView("cancel button")); 1205 if (button != NULL) 1206 button->MoveBy(delta, 0); 1207 } 1208 break; 1209 } 1210 } 1211 1212 1213 void 1214 TFilePanel::SetSaveText(const char* text) 1215 { 1216 if (text == NULL) 1217 return; 1218 1219 BTextControl* textControl 1220 = dynamic_cast<BTextControl*>(FindView("text view")); 1221 if (textControl != NULL) { 1222 textControl->SetText(text); 1223 if (textControl->TextView() != NULL) 1224 textControl->TextView()->SelectAll(); 1225 } 1226 } 1227 1228 1229 void 1230 TFilePanel::MessageReceived(BMessage* message) 1231 { 1232 entry_ref ref; 1233 1234 switch (message->what) { 1235 case B_REFS_RECEIVED: 1236 // item was double clicked in file panel (PoseView) 1237 if (message->FindRef("refs", &ref) == B_OK) { 1238 BEntry entry(&ref, true); 1239 if (entry.InitCheck() == B_OK) { 1240 // Double-click on dir or link-to-dir ALWAYS opens the 1241 // dir. If more than one dir is selected, the first is 1242 // entered. 1243 if (entry.IsDirectory()) { 1244 entry.GetRef(&ref); 1245 bool isDesktop = SwitchDirToDesktopIfNeeded(ref); 1246 1247 PoseView()->SetIsDesktop(isDesktop); 1248 entry.SetTo(&ref); 1249 PoseView()->SwitchDir(&ref); 1250 SwitchDirMenuTo(&ref); 1251 } else { 1252 // Otherwise, we have a file or a link to a file. 1253 // AdjustButton has already tested the flavor; 1254 // all we have to do is see if the button is enabled. 1255 BButton* button = dynamic_cast<BButton*>( 1256 FindView("default button")); 1257 if (button == NULL || !button->IsEnabled()) 1258 break; 1259 1260 if (IsSavePanel()) { 1261 int32 count = 0; 1262 type_code type; 1263 message->GetInfo("refs", &type, &count); 1264 1265 // Don't allow saves of multiple files 1266 if (count > 1) { 1267 ShowCenteredAlert( 1268 B_TRANSLATE( 1269 "Sorry, saving more than one " 1270 "item is not allowed."), 1271 B_TRANSLATE("Cancel")); 1272 } else { 1273 // if we are a savepanel, set up the 1274 // filepanel correctly then pass control 1275 // so we follow the same path as if the user 1276 // clicked the save button 1277 1278 // set the 'name' fld to the current ref's 1279 // name notify the panel that the default 1280 // button should be enabled 1281 SetSaveText(ref.name); 1282 SelectionChanged(); 1283 1284 HandleSaveButton(); 1285 } 1286 break; 1287 } 1288 1289 // send handler a message and close 1290 BMessage openMessage(*fMessage); 1291 for (int32 index = 0; ; index++) { 1292 if (message->FindRef("refs", index, &ref) != B_OK) 1293 break; 1294 openMessage.AddRef("refs", &ref); 1295 } 1296 OpenSelectionCommon(&openMessage); 1297 } 1298 } 1299 } 1300 break; 1301 1302 case kSwitchDirectory: 1303 { 1304 entry_ref ref; 1305 // this comes from dir menu or nav menu, so switch directories 1306 if (message->FindRef("refs", &ref) == B_OK) { 1307 BEntry entry(&ref, true); 1308 if (entry.GetRef(&ref) == B_OK) 1309 SetTo(&ref); 1310 } 1311 break; 1312 } 1313 1314 case kSwitchToHome: 1315 { 1316 BPath homePath; 1317 entry_ref ref; 1318 if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK 1319 || get_ref_for_path(homePath.Path(), &ref) != B_OK) { 1320 break; 1321 } 1322 1323 SetTo(&ref); 1324 break; 1325 } 1326 1327 case kAddCurrentDir: 1328 { 1329 BPath path; 1330 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) 1331 != B_OK) { 1332 break; 1333 } 1334 1335 path.Append(kGoDirectory); 1336 BDirectory goDirectory(path.Path()); 1337 1338 if (goDirectory.InitCheck() == B_OK) { 1339 BEntry entry(TargetModel()->EntryRef()); 1340 entry.GetPath(&path); 1341 1342 BSymLink link; 1343 goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(), 1344 &link); 1345 } 1346 break; 1347 } 1348 1349 case kEditFavorites: 1350 { 1351 BPath path; 1352 if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) 1353 != B_OK) { 1354 break; 1355 } 1356 1357 path.Append(kGoDirectory); 1358 BMessenger msgr(kTrackerSignature); 1359 if (msgr.IsValid()) { 1360 BMessage message(B_REFS_RECEIVED); 1361 entry_ref ref; 1362 if (get_ref_for_path(path.Path(), &ref) == B_OK) { 1363 message.AddRef("refs", &ref); 1364 msgr.SendMessage(&message); 1365 } 1366 } 1367 break; 1368 } 1369 1370 case kCancelButton: 1371 PostMessage(B_QUIT_REQUESTED); 1372 break; 1373 1374 case kResizeToFit: 1375 ResizeToFit(); 1376 break; 1377 1378 case kOpenDir: 1379 OpenDirectory(); 1380 break; 1381 1382 case kOpenParentDir: 1383 OpenParent(); 1384 break; 1385 1386 case kDefaultButton: 1387 if (fIsSavePanel) { 1388 if (PoseView()->IsFocus() 1389 && PoseView()->CountSelected() == 1) { 1390 Model* model = (PoseView()->SelectionList()-> 1391 FirstItem())->TargetModel(); 1392 if (model->ResolveIfLink()->IsDirectory()) { 1393 PoseView()->CommitActivePose(); 1394 PoseView()->OpenSelection(); 1395 break; 1396 } 1397 } 1398 1399 HandleSaveButton(); 1400 } else 1401 HandleOpenButton(); 1402 break; 1403 1404 case B_OBSERVER_NOTICE_CHANGE: 1405 { 1406 int32 observerWhat; 1407 if (message->FindInt32("be:observe_change_what", &observerWhat) 1408 == B_OK) { 1409 switch (observerWhat) { 1410 case kDesktopFilePanelRootChanged: 1411 { 1412 bool desktopIsRoot = true; 1413 if (message->FindBool("DesktopFilePanelRoot", 1414 &desktopIsRoot) == B_OK) { 1415 TrackerSettings(). 1416 SetDesktopFilePanelRoot(desktopIsRoot); 1417 } 1418 SetTo(TargetModel()->EntryRef()); 1419 break; 1420 } 1421 } 1422 } 1423 break; 1424 } 1425 1426 default: 1427 _inherited::MessageReceived(message); 1428 break; 1429 } 1430 } 1431 1432 1433 void 1434 TFilePanel::OpenDirectory() 1435 { 1436 BObjectList<BPose>* list = PoseView()->SelectionList(); 1437 if (list->CountItems() != 1) 1438 return; 1439 1440 Model* model = list->FirstItem()->TargetModel(); 1441 if (model->ResolveIfLink()->IsDirectory()) { 1442 BMessage message(B_REFS_RECEIVED); 1443 message.AddRef("refs", model->EntryRef()); 1444 PostMessage(&message); 1445 } 1446 } 1447 1448 1449 void 1450 TFilePanel::OpenParent() 1451 { 1452 if (!CanOpenParent()) 1453 return; 1454 1455 BEntry parentEntry; 1456 BDirectory dir; 1457 1458 Model oldModel(*PoseView()->TargetModel()); 1459 BEntry entry(oldModel.EntryRef()); 1460 1461 if (entry.InitCheck() == B_OK 1462 && entry.GetParent(&dir) == B_OK 1463 && dir.GetEntry(&parentEntry) == B_OK 1464 && entry != parentEntry) { 1465 1466 entry_ref ref; 1467 parentEntry.GetRef(&ref); 1468 1469 PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref)); 1470 PoseView()->SwitchDir(&ref); 1471 SwitchDirMenuTo(&ref); 1472 1473 // Make sure the child gets selected in the new view 1474 // once it shows up. 1475 fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 1476 (&TFilePanel::SelectChildInParent, this, 1477 const_cast<const entry_ref*>(&ref), 1478 oldModel.NodeRef()), 100000, 200000, 5000000); 1479 } 1480 } 1481 1482 1483 bool 1484 TFilePanel::CanOpenParent() const 1485 { 1486 if (TrackerSettings().DesktopFilePanelRoot()) { 1487 // don't allow opening Desktop folder's parent 1488 if (TargetModel()->IsDesktop()) 1489 return false; 1490 } 1491 1492 // block on "/" 1493 BEntry root("/"); 1494 node_ref rootRef; 1495 root.GetNodeRef(&rootRef); 1496 1497 return rootRef != *TargetModel()->NodeRef(); 1498 } 1499 1500 1501 bool 1502 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref) 1503 { 1504 // support showing Desktop as root of everything 1505 // This call implements the worm hole that maps Desktop as 1506 // a root above the disks 1507 TrackerSettings settings; 1508 if (!settings.DesktopFilePanelRoot()) 1509 // Tracker isn't set up that way, just let Disks show 1510 return false; 1511 1512 BEntry entry(&ref); 1513 BEntry root("/"); 1514 1515 BDirectory desktopDir; 1516 FSGetDeskDir(&desktopDir); 1517 if (FSIsDeskDir(&entry) 1518 // navigated into non-boot desktop, switch to boot desktop 1519 || (entry == root && !settings.ShowDisksIcon())) { 1520 // hit "/" level, map to desktop 1521 1522 desktopDir.GetEntry(&entry); 1523 entry.GetRef(&ref); 1524 return true; 1525 } 1526 return FSIsDeskDir(&entry); 1527 } 1528 1529 1530 bool 1531 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child) 1532 { 1533 AutoLock<TFilePanel> lock(this); 1534 1535 if (!IsLocked()) 1536 return false; 1537 1538 int32 index; 1539 BPose* pose = PoseView()->FindPose(child, &index); 1540 if (!pose) 1541 return false; 1542 1543 PoseView()->UpdateScrollRange(); 1544 // ToDo: Scroll range should be updated by now, for some 1545 // reason sometimes it is not right, force it here 1546 PoseView()->SelectPose(pose, index, true); 1547 return true; 1548 } 1549 1550 1551 int32 1552 TFilePanel::ShowCenteredAlert(const char* text, const char* button1, 1553 const char* button2, const char* button3) 1554 { 1555 BAlert* alert = new BAlert("", text, button1, button2, button3, 1556 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1557 alert->MoveTo(Frame().left + 10, Frame().top + 10); 1558 1559 #if 0 1560 if (button1 != NULL && !strncmp(button1, "Cancel", 7)) 1561 alert->SetShortcut(0, B_ESCAPE); 1562 else if (button2 != NULL && !strncmp(button2, "Cancel", 7)) 1563 alert->SetShortcut(1, B_ESCAPE); 1564 else if (button3 != NULL && !strncmp(button3, "Cancel", 7)) 1565 alert->SetShortcut(2, B_ESCAPE); 1566 #endif 1567 1568 return alert->Go(); 1569 } 1570 1571 1572 void 1573 TFilePanel::HandleSaveButton() 1574 { 1575 BDirectory dir; 1576 1577 if (TargetModel()->IsRoot()) { 1578 ShowCenteredAlert( 1579 B_TRANSLATE("Sorry, you can't save things at the root of " 1580 "your system."), 1581 B_TRANSLATE("Cancel")); 1582 return; 1583 } 1584 1585 // check for some illegal file names 1586 if (strcmp(fTextControl->Text(), ".") == 0 1587 || strcmp(fTextControl->Text(), "..") == 0) { 1588 ShowCenteredAlert( 1589 B_TRANSLATE("The specified name is illegal. Please choose " 1590 "another name."), 1591 B_TRANSLATE("Cancel")); 1592 fTextControl->TextView()->SelectAll(); 1593 return; 1594 } 1595 1596 if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) { 1597 ShowCenteredAlert( 1598 B_TRANSLATE("There was a problem trying to save in the folder " 1599 "you specified. Please try another one."), 1600 B_TRANSLATE("Cancel")); 1601 return; 1602 } 1603 1604 if (dir.Contains(fTextControl->Text())) { 1605 if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) { 1606 ShowCenteredAlert( 1607 B_TRANSLATE("The specified name is already used as the name " 1608 "of a folder. Please choose another name."), 1609 B_TRANSLATE("Cancel")); 1610 fTextControl->TextView()->SelectAll(); 1611 return; 1612 } else { 1613 // if this was invoked by a dbl click, it is an explicit 1614 // replacement of the file. 1615 BString str(B_TRANSLATE("The file \"%name\" already exists in " 1616 "the specified folder. Do you want to replace it?")); 1617 str.ReplaceFirst("%name", fTextControl->Text()); 1618 1619 if (ShowCenteredAlert(str.String(), B_TRANSLATE("Cancel"), 1620 B_TRANSLATE("Replace")) == 0) { 1621 // user canceled 1622 fTextControl->TextView()->SelectAll(); 1623 return; 1624 } 1625 // user selected "Replace" - let app deal with it 1626 } 1627 } 1628 1629 BMessage message(*fMessage); 1630 message.AddRef("directory", TargetModel()->EntryRef()); 1631 message.AddString("name", fTextControl->Text()); 1632 1633 if (fClientObject) 1634 fClientObject->SendMessage(&fTarget, &message); 1635 else 1636 fTarget.SendMessage(&message); 1637 1638 // close window if we're dealing with standard message 1639 if (fHideWhenDone) 1640 PostMessage(B_QUIT_REQUESTED); 1641 } 1642 1643 1644 void 1645 TFilePanel::OpenSelectionCommon(BMessage* openMessage) 1646 { 1647 if (!openMessage->HasRef("refs")) 1648 return; 1649 1650 for (int32 index = 0; ; index++) { 1651 entry_ref ref; 1652 if (openMessage->FindRef("refs", index, &ref) != B_OK) 1653 break; 1654 1655 BEntry entry(&ref, true); 1656 if (entry.InitCheck() == B_OK) { 1657 if (entry.IsDirectory()) 1658 BRoster().AddToRecentFolders(&ref); 1659 else 1660 BRoster().AddToRecentDocuments(&ref); 1661 } 1662 } 1663 1664 BRoster().AddToRecentFolders(TargetModel()->EntryRef()); 1665 1666 if (fClientObject) 1667 fClientObject->SendMessage(&fTarget, openMessage); 1668 else 1669 fTarget.SendMessage(openMessage); 1670 1671 // close window if we're dealing with standard message 1672 if (fHideWhenDone) 1673 PostMessage(B_QUIT_REQUESTED); 1674 } 1675 1676 1677 void 1678 TFilePanel::HandleOpenButton() 1679 { 1680 PoseView()->CommitActivePose(); 1681 BObjectList<BPose>* selection = PoseView()->SelectionList(); 1682 1683 // if we have only one directory and we're not opening dirs, enter. 1684 if ((fNodeFlavors & B_DIRECTORY_NODE) == 0 1685 && selection->CountItems() == 1) { 1686 Model* model = selection->FirstItem()->TargetModel(); 1687 1688 if (model->IsDirectory() 1689 || (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE) 1690 && model->ResolveIfLink()->IsDirectory())) { 1691 1692 BMessage message(B_REFS_RECEIVED); 1693 message.AddRef("refs", model->EntryRef()); 1694 PostMessage(&message); 1695 return; 1696 } 1697 } 1698 1699 if (selection->CountItems()) { 1700 // there are items selected 1701 // message->fMessage->message from here to end 1702 BMessage message(*fMessage); 1703 // go through selection and add appropriate items 1704 for (int32 index = 0; index < selection->CountItems(); index++) { 1705 Model* model = selection->ItemAt(index)->TargetModel(); 1706 1707 if (((fNodeFlavors & B_DIRECTORY_NODE) != 0 1708 && model->ResolveIfLink()->IsDirectory()) 1709 || ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink()) 1710 || ((fNodeFlavors & B_FILE_NODE) != 0 1711 && model->ResolveIfLink()->IsFile())) { 1712 message.AddRef("refs", model->EntryRef()); 1713 } 1714 } 1715 1716 OpenSelectionCommon(&message); 1717 } else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) { 1718 // Open the current directory. 1719 BMessage message(*fMessage); 1720 message.AddRef("refs", TargetModel()->EntryRef()); 1721 OpenSelectionCommon(&message); 1722 } 1723 } 1724 1725 1726 void 1727 TFilePanel::SwitchDirMenuTo(const entry_ref* ref) 1728 { 1729 BEntry entry(ref); 1730 for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--) 1731 delete fDirMenu->RemoveItem(index); 1732 1733 fDirMenuField->MenuBar()->RemoveItem((int32)0); 1734 fDirMenu->Populate(&entry, 0, true, true, false, true); 1735 1736 ModelMenuItem* item = dynamic_cast<ModelMenuItem*>( 1737 fDirMenuField->MenuBar()->ItemAt(0)); 1738 ASSERT(item != NULL); 1739 1740 if (item != NULL) 1741 item->SetEntry(&entry); 1742 } 1743 1744 1745 void 1746 TFilePanel::WindowActivated(bool active) 1747 { 1748 // force focus to update properly 1749 fBackView->Invalidate(); 1750 _inherited::WindowActivated(active); 1751 } 1752 1753 1754 // #pragma mark - 1755 1756 1757 BFilePanelPoseView::BFilePanelPoseView(Model* model) 1758 : 1759 BPoseView(model, kListMode), 1760 fIsDesktop(model->IsDesktop()) 1761 { 1762 } 1763 1764 1765 void 1766 BFilePanelPoseView::StartWatching() 1767 { 1768 TTracker::WatchNode(0, B_WATCH_MOUNT, this); 1769 1770 // inter-application observing 1771 BMessenger tracker(kTrackerSignature); 1772 BHandler::StartWatching(tracker, kVolumesOnDesktopChanged); 1773 } 1774 1775 1776 void 1777 BFilePanelPoseView::StopWatching() 1778 { 1779 stop_watching(this); 1780 1781 // inter-application observing 1782 BMessenger tracker(kTrackerSignature); 1783 BHandler::StopWatching(tracker, kVolumesOnDesktopChanged); 1784 } 1785 1786 1787 bool 1788 BFilePanelPoseView::FSNotification(const BMessage* message) 1789 { 1790 switch (message->FindInt32("opcode")) { 1791 case B_DEVICE_MOUNTED: 1792 { 1793 if (IsDesktopView()) { 1794 // Pretty much copied straight from DesktopPoseView. 1795 // Would be better if the code could be shared somehow. 1796 dev_t device; 1797 if (message->FindInt32("new device", &device) != B_OK) 1798 break; 1799 1800 ASSERT(TargetModel() != NULL); 1801 TrackerSettings settings; 1802 1803 BVolume volume(device); 1804 if (volume.InitCheck() != B_OK) 1805 break; 1806 1807 if (settings.MountVolumesOntoDesktop() 1808 && (!volume.IsShared() 1809 || settings.MountSharedVolumesOntoDesktop())) { 1810 // place an icon for the volume onto the desktop 1811 CreateVolumePose(&volume, true); 1812 } 1813 } 1814 break; 1815 } 1816 1817 case B_DEVICE_UNMOUNTED: 1818 { 1819 dev_t device; 1820 if (message->FindInt32("device", &device) == B_OK) { 1821 if (TargetModel() != NULL 1822 && TargetModel()->NodeRef()->device == device) { 1823 // Volume currently shown in this file panel 1824 // disappeared, reset location to home directory 1825 BMessage message(kSwitchToHome); 1826 MessageReceived(&message); 1827 } 1828 } 1829 break; 1830 } 1831 } 1832 return _inherited::FSNotification(message); 1833 } 1834 1835 1836 void 1837 BFilePanelPoseView::RestoreState(AttributeStreamNode* node) 1838 { 1839 _inherited::RestoreState(node); 1840 fViewState->SetViewMode(kListMode); 1841 } 1842 1843 1844 void 1845 BFilePanelPoseView::RestoreState(const BMessage &message) 1846 { 1847 _inherited::RestoreState(message); 1848 } 1849 1850 1851 void 1852 BFilePanelPoseView::SavePoseLocations(BRect*) 1853 { 1854 } 1855 1856 1857 EntryListBase* 1858 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref) 1859 { 1860 if (IsDesktopView()) 1861 return DesktopPoseView::InitDesktopDirentIterator(this, ref); 1862 1863 return _inherited::InitDirentIterator(ref); 1864 } 1865 1866 1867 void 1868 BFilePanelPoseView::AddPosesCompleted() 1869 { 1870 _inherited::AddPosesCompleted(); 1871 if (IsDesktopView()) 1872 CreateTrashPose(); 1873 } 1874 1875 1876 void 1877 BFilePanelPoseView::SetIsDesktop(bool on) 1878 { 1879 fIsDesktop = on; 1880 } 1881 1882 1883 bool 1884 BFilePanelPoseView::IsDesktopView() const 1885 { 1886 return fIsDesktop; 1887 } 1888 1889 1890 void 1891 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared) 1892 { 1893 if (IsDesktopView()) { 1894 if (!visible) 1895 RemoveRootPoses(); 1896 else 1897 AddRootPoses(true, showShared); 1898 } 1899 1900 TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window()); 1901 if (filepanel != NULL && TargetModel() != NULL) 1902 filepanel->SetTo(TargetModel()->EntryRef()); 1903 } 1904 1905 1906 void 1907 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message) 1908 { 1909 bool showDisksIcon; 1910 bool mountVolumesOnDesktop; 1911 bool mountSharedVolumesOntoDesktop; 1912 1913 message->FindBool("ShowDisksIcon", &showDisksIcon); 1914 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1915 message->FindBool("MountSharedVolumesOntoDesktop", 1916 &mountSharedVolumesOntoDesktop); 1917 1918 BEntry entry("/"); 1919 Model model(&entry); 1920 if (model.InitCheck() == B_OK) { 1921 BMessage monitorMsg; 1922 monitorMsg.what = B_NODE_MONITOR; 1923 1924 if (showDisksIcon) 1925 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1926 else 1927 monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED); 1928 1929 monitorMsg.AddInt32("device", model.NodeRef()->device); 1930 monitorMsg.AddInt64("node", model.NodeRef()->node); 1931 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1932 monitorMsg.AddString("name", model.EntryRef()->name); 1933 TrackerSettings().SetShowDisksIcon(showDisksIcon); 1934 if (Window() != NULL) 1935 Window()->PostMessage(&monitorMsg, this); 1936 } 1937 1938 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1939 } 1940 1941 1942 void 1943 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message) 1944 { 1945 bool mountVolumesOnDesktop = true; 1946 bool mountSharedVolumesOntoDesktop = true; 1947 1948 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1949 message->FindBool("MountSharedVolumesOntoDesktop", 1950 &mountSharedVolumesOntoDesktop); 1951 1952 ShowVolumes(false, mountSharedVolumesOntoDesktop); 1953 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1954 } 1955