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