1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "FilePanelPriv.h" 37 38 #include <string.h> 39 40 #include <Alert.h> 41 #include <Application.h> 42 #include <Button.h> 43 #include <ControlLook.h> 44 #include <Catalog.h> 45 #include <Debug.h> 46 #include <Directory.h> 47 #include <FindDirectory.h> 48 #include <GridView.h> 49 #include <Locale.h> 50 #include <MenuBar.h> 51 #include <MenuField.h> 52 #include <MenuItem.h> 53 #include <MessageFilter.h> 54 #include <NodeInfo.h> 55 #include <NodeMonitor.h> 56 #include <Path.h> 57 #include <Roster.h> 58 #include <SymLink.h> 59 #include <ScrollView.h> 60 #include <String.h> 61 #include <StopWatch.h> 62 #include <TextControl.h> 63 #include <TextView.h> 64 #include <Volume.h> 65 #include <VolumeRoster.h> 66 67 #include "Attributes.h" 68 #include "AttributeStream.h" 69 #include "AutoLock.h" 70 #include "Commands.h" 71 #include "CountView.h" 72 #include "DesktopPoseView.h" 73 #include "DirMenu.h" 74 #include "FavoritesMenu.h" 75 #include "FSUtils.h" 76 #include "FSClipboard.h" 77 #include "IconMenuItem.h" 78 #include "MimeTypes.h" 79 #include "NavMenu.h" 80 #include "Tracker.h" 81 #include "Utilities.h" 82 83 #include "tracker_private.h" 84 85 86 #undef B_TRANSLATION_CONTEXT 87 #define B_TRANSLATION_CONTEXT "FilePanelPriv" 88 89 90 const char* kDefaultFilePanelTemplate = "FilePanelSettings"; 91 92 93 static uint32 94 GetLinkFlavor(const Model* model, bool resolve = true) 95 { 96 if (model && model->IsSymLink()) { 97 if (!resolve) 98 return B_SYMLINK_NODE; 99 model = model->LinkTo(); 100 } 101 if (!model) 102 return 0; 103 104 if (model->IsDirectory()) 105 return B_DIRECTORY_NODE; 106 107 return B_FILE_NODE; 108 } 109 110 111 static filter_result 112 key_down_filter(BMessage* message, BHandler** handler, BMessageFilter* filter) 113 { 114 ASSERT(filter != NULL); 115 if (filter == NULL) 116 return B_DISPATCH_MESSAGE; 117 118 TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper()); 119 ASSERT(panel != NULL); 120 121 if (panel == NULL) 122 return B_DISPATCH_MESSAGE; 123 124 BPoseView* view = panel->PoseView(); 125 if (panel->TrackingMenu()) 126 return B_DISPATCH_MESSAGE; 127 128 uchar key; 129 if (message->FindInt8("byte", (int8*)&key) != B_OK) 130 return B_DISPATCH_MESSAGE; 131 132 int32 modifier = 0; 133 message->FindInt32("modifiers", &modifier); 134 135 if (modifier & B_COMMAND_KEY && key == B_UP_ARROW) { 136 filter->Looper()->PostMessage(kOpenParentDir); 137 return B_SKIP_MESSAGE; 138 } 139 140 if (modifier & B_COMMAND_KEY && key == 'w') { 141 filter->Looper()->PostMessage(kCancelButton); 142 return B_SKIP_MESSAGE; 143 } 144 145 if (!modifier && key == B_ESCAPE) { 146 if (view->ActivePose()) 147 view->CommitActivePose(false); 148 else if (view->IsFiltering()) 149 filter->Looper()->PostMessage(B_CANCEL, *handler); 150 else 151 filter->Looper()->PostMessage(kCancelButton); 152 153 return B_SKIP_MESSAGE; 154 } 155 156 if (key == B_RETURN && view->ActivePose()) { 157 view->CommitActivePose(); 158 159 return B_SKIP_MESSAGE; 160 } 161 162 return B_DISPATCH_MESSAGE; 163 } 164 165 166 // #pragma mark - TFilePanel 167 168 169 TFilePanel::TFilePanel(file_panel_mode mode, BMessenger* target, 170 const BEntry* startDir, uint32 nodeFlavors, bool multipleSelection, 171 BMessage* message, BRefFilter* filter, uint32 openFlags, window_look look, 172 window_feel feel, uint32 windowFlags, uint32 workspace, bool hideWhenDone) 173 : 174 BContainerWindow(0, openFlags, look, feel, windowFlags, workspace, false), 175 fDirMenu(NULL), 176 fDirMenuField(NULL), 177 fTextControl(NULL), 178 fClientObject(NULL), 179 fSelectionIterator(0), 180 fMessage(NULL), 181 fHideWhenDone(hideWhenDone), 182 fIsTrackingMenu(false), 183 fDefaultStateRestored(false) 184 { 185 InitIconPreloader(); 186 187 fIsSavePanel = (mode == B_SAVE_PANEL); 188 189 const float labelSpacing = be_control_look->DefaultLabelSpacing(); 190 // approximately (84, 50, 568, 296) with default sizing 191 BRect windRect(labelSpacing * 14.0f, labelSpacing * 8.0f, 192 labelSpacing * 95.0f, labelSpacing * 49.0f); 193 MoveTo(windRect.LeftTop()); 194 ResizeTo(windRect.Width(), windRect.Height()); 195 196 fNodeFlavors = (nodeFlavors == 0) ? B_FILE_NODE : nodeFlavors; 197 198 if (target) 199 fTarget = *target; 200 else 201 fTarget = BMessenger(be_app); 202 203 if (message) 204 SetMessage(message); 205 else if (fIsSavePanel) 206 fMessage = new BMessage(B_SAVE_REQUESTED); 207 else 208 fMessage = new BMessage(B_REFS_RECEIVED); 209 210 gLocalizedNamePreferred 211 = BLocaleRoster::Default()->IsFilesystemTranslationPreferred(); 212 213 // check for legal starting directory 214 Model* model = new Model(); 215 bool useRoot = true; 216 217 if (startDir) { 218 if (model->SetTo(startDir) == B_OK && model->IsDirectory()) 219 useRoot = false; 220 else { 221 delete model; 222 model = new Model(); 223 } 224 } 225 226 if (useRoot) { 227 BPath path; 228 if (find_directory(B_USER_DIRECTORY, &path) == B_OK) { 229 BEntry entry(path.Path(), true); 230 if (entry.InitCheck() == B_OK && model->SetTo(&entry) == B_OK) 231 useRoot = false; 232 } 233 } 234 235 if (useRoot) { 236 BVolume volume; 237 BDirectory root; 238 BVolumeRoster volumeRoster; 239 volumeRoster.GetBootVolume(&volume); 240 volume.GetRootDirectory(&root); 241 242 BEntry entry; 243 root.GetEntry(&entry); 244 model->SetTo(&entry); 245 } 246 247 fTaskLoop = new PiggybackTaskLoop; 248 249 AutoLock<BWindow> lock(this); 250 fBorderedView = new BorderedView; 251 CreatePoseView(model); 252 fBorderedView->GroupLayout()->SetInsets(1); 253 254 fPoseContainer = new BGridView(0.0, 0.0); 255 fPoseContainer->GridLayout()->AddView(fBorderedView, 0, 1); 256 257 fCountContainer = new BGroupView(B_HORIZONTAL, 0); 258 fPoseContainer->GridLayout()->AddView(fCountContainer, 0, 2); 259 260 fPoseView->SetRefFilter(filter); 261 if (!fIsSavePanel) 262 fPoseView->SetMultipleSelection(multipleSelection); 263 264 fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE); 265 fPoseView->SetPoseEditing(false); 266 AddCommonFilter(new BMessageFilter(B_KEY_DOWN, key_down_filter)); 267 AddCommonFilter(new BMessageFilter(B_SIMPLE_DATA, 268 TFilePanel::MessageDropFilter)); 269 AddCommonFilter(new BMessageFilter(B_NODE_MONITOR, TFilePanel::FSFilter)); 270 271 // inter-application observing 272 BMessenger tracker(kTrackerSignature); 273 BHandler::StartWatching(tracker, kDesktopFilePanelRootChanged); 274 275 Init(); 276 277 // Avoid the need to save state later just because of changes made 278 // during setup. This prevents unnecessary saving by Quit that can 279 // overwrite user's changes previously saved from another panel object. 280 if (StateNeedsSaving()) 281 SaveState(false); 282 } 283 284 285 TFilePanel::~TFilePanel() 286 { 287 BMessenger tracker(kTrackerSignature); 288 BHandler::StopWatching(tracker, kDesktopFilePanelRootChanged); 289 290 delete fMessage; 291 } 292 293 294 filter_result 295 TFilePanel::MessageDropFilter(BMessage* message, BHandler**, 296 BMessageFilter* filter) 297 { 298 if (message == NULL || !message->WasDropped()) 299 return B_DISPATCH_MESSAGE; 300 301 ASSERT(filter != NULL); 302 if (filter == NULL) 303 return B_DISPATCH_MESSAGE; 304 305 TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper()); 306 ASSERT(panel != NULL); 307 308 if (panel == NULL) 309 return B_DISPATCH_MESSAGE; 310 311 uint32 type; 312 int32 count; 313 if (message->GetInfo("refs", &type, &count) != B_OK) 314 return B_SKIP_MESSAGE; 315 316 if (count != 1) 317 return B_SKIP_MESSAGE; 318 319 entry_ref ref; 320 if (message->FindRef("refs", &ref) != B_OK) 321 return B_SKIP_MESSAGE; 322 323 BEntry entry(&ref); 324 if (entry.InitCheck() != B_OK) 325 return B_SKIP_MESSAGE; 326 327 // if the entry is a symlink 328 // resolve it and see if it is a directory 329 // pass it on if it is 330 if (entry.IsSymLink()) { 331 entry_ref resolvedRef; 332 333 entry.GetRef(&resolvedRef); 334 BEntry resolvedEntry(&resolvedRef, true); 335 336 if (resolvedEntry.IsDirectory()) { 337 // both entry and ref need to be the correct locations 338 // for the last setto 339 resolvedEntry.GetRef(&ref); 340 entry.SetTo(&ref); 341 } 342 } 343 344 // if not a directory, set to the parent, and select the child 345 if (!entry.IsDirectory()) { 346 node_ref child; 347 if (entry.GetNodeRef(&child) != B_OK) 348 return B_SKIP_MESSAGE; 349 350 BPath path(&entry); 351 352 if (entry.GetParent(&entry) != B_OK) 353 return B_SKIP_MESSAGE; 354 355 entry.GetRef(&ref); 356 357 panel->fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 358 (&TFilePanel::SelectChildInParent, panel, 359 const_cast<const entry_ref*>(&ref), 360 const_cast<const node_ref*>(&child)), 361 ref == *panel->TargetModel()->EntryRef() ? 0 : 100000, 200000, 362 5000000); 363 // if the target directory is already current, we won't 364 // delay the initial selection try 365 366 // also set the save name to the dragged in entry 367 if (panel->IsSavePanel()) 368 panel->SetSaveText(path.Leaf()); 369 } 370 371 panel->SetTo(&ref); 372 373 return B_SKIP_MESSAGE; 374 } 375 376 377 filter_result 378 TFilePanel::FSFilter(BMessage* message, BHandler**, BMessageFilter* filter) 379 { 380 if (message == NULL) 381 return B_DISPATCH_MESSAGE; 382 383 ASSERT(filter != NULL); 384 if (filter == NULL) 385 return B_DISPATCH_MESSAGE; 386 387 TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper()); 388 ASSERT(panel != NULL); 389 390 if (panel == NULL) 391 return B_DISPATCH_MESSAGE; 392 393 switch (message->FindInt32("opcode")) { 394 case B_ENTRY_MOVED: 395 { 396 node_ref itemNode; 397 message->FindInt64("node", (int64*)&itemNode.node); 398 399 node_ref dirNode; 400 message->FindInt32("device", &dirNode.device); 401 itemNode.device = dirNode.device; 402 message->FindInt64("to directory", (int64*)&dirNode.node); 403 404 const char* name; 405 if (message->FindString("name", &name) != B_OK) 406 break; 407 408 // if current directory moved, update entry ref and menu 409 // but not wind title 410 if (*(panel->TargetModel()->NodeRef()) == itemNode) { 411 panel->TargetModel()->UpdateEntryRef(&dirNode, name); 412 panel->SetTo(panel->TargetModel()->EntryRef()); 413 return B_SKIP_MESSAGE; 414 } 415 break; 416 } 417 418 case B_ENTRY_REMOVED: 419 { 420 node_ref itemNode; 421 message->FindInt32("device", &itemNode.device); 422 message->FindInt64("node", (int64*)&itemNode.node); 423 424 // if folder we're watching is deleted, switch to root 425 // or Desktop 426 if (*(panel->TargetModel()->NodeRef()) == itemNode) { 427 BVolumeRoster volumeRoster; 428 BVolume volume; 429 volumeRoster.GetBootVolume(&volume); 430 431 BDirectory root; 432 volume.GetRootDirectory(&root); 433 434 BEntry entry; 435 entry_ref ref; 436 root.GetEntry(&entry); 437 entry.GetRef(&ref); 438 439 panel->SwitchDirToDesktopIfNeeded(ref); 440 441 panel->SetTo(&ref); 442 return B_SKIP_MESSAGE; 443 } 444 break; 445 } 446 } 447 448 return B_DISPATCH_MESSAGE; 449 } 450 451 452 void 453 TFilePanel::DispatchMessage(BMessage* message, BHandler* handler) 454 { 455 _inherited::DispatchMessage(message, handler); 456 if (message->what == B_KEY_DOWN || message->what == B_MOUSE_DOWN) 457 AdjustButton(); 458 } 459 460 461 BFilePanelPoseView* 462 TFilePanel::PoseView() const 463 { 464 ASSERT(dynamic_cast<BFilePanelPoseView*>(fPoseView) != NULL); 465 466 return static_cast<BFilePanelPoseView*>(fPoseView); 467 } 468 469 470 bool 471 TFilePanel::QuitRequested() 472 { 473 // If we have a client object then this window will simply hide 474 // itself, to be closed later when the client object itself is 475 // destroyed. If we have no client then we must have been started 476 // from the "easy" functions which simply instantiate a TFilePanel 477 // and expect it to go away by itself 478 479 if (fClientObject != NULL) { 480 Hide(); 481 if (fClientObject != NULL) 482 fClientObject->WasHidden(); 483 484 BMessage message(*fMessage); 485 message.what = B_CANCEL; 486 message.AddInt32("old_what", (int32)fMessage->what); 487 message.AddPointer("source", fClientObject); 488 fTarget.SendMessage(&message); 489 490 return false; 491 } 492 493 return _inherited::QuitRequested(); 494 } 495 496 497 BRefFilter* 498 TFilePanel::Filter() const 499 { 500 return fPoseView->RefFilter(); 501 } 502 503 504 void 505 TFilePanel::SetTarget(BMessenger target) 506 { 507 fTarget = target; 508 } 509 510 511 void 512 TFilePanel::SetMessage(BMessage* message) 513 { 514 delete fMessage; 515 fMessage = new BMessage(*message); 516 } 517 518 519 void 520 TFilePanel::SetRefFilter(BRefFilter* filter) 521 { 522 ASSERT(filter != NULL); 523 if (filter == NULL) 524 return; 525 526 fPoseView->SetRefFilter(filter); 527 fPoseView->CommitActivePose(); 528 fPoseView->Refresh(); 529 530 if (fMenuBar == NULL) 531 return; 532 533 BMenuItem* favoritesItem = fMenuBar->FindItem(B_TRANSLATE("Favorites")); 534 if (favoritesItem == NULL) 535 return; 536 537 FavoritesMenu* favoritesSubMenu 538 = dynamic_cast<FavoritesMenu*>(favoritesItem->Submenu()); 539 if (favoritesSubMenu != NULL) 540 favoritesSubMenu->SetRefFilter(filter); 541 } 542 543 544 void 545 TFilePanel::SetTo(const entry_ref* ref) 546 { 547 if (ref == NULL) 548 return; 549 550 entry_ref setToRef(*ref); 551 552 bool isDesktop = SwitchDirToDesktopIfNeeded(setToRef); 553 554 BEntry entry(&setToRef); 555 if (entry.InitCheck() != B_OK || !entry.IsDirectory()) 556 return; 557 558 PoseView()->SetIsDesktop(isDesktop); 559 PoseView()->SwitchDir(&setToRef); 560 SwitchDirMenuTo(&setToRef); 561 562 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 563 // our shortcut got possibly removed because the home 564 // menu item got removed - we shouldn't really have to do 565 // this - this is a workaround for a kit bug. 566 } 567 568 569 void 570 TFilePanel::Rewind() 571 { 572 fSelectionIterator = 0; 573 } 574 575 576 void 577 TFilePanel::SetClientObject(BFilePanel* panel) 578 { 579 fClientObject = panel; 580 } 581 582 583 void 584 TFilePanel::AdjustButton() 585 { 586 // adjust button state 587 BButton* button = dynamic_cast<BButton*>(FindView("default button")); 588 if (button == NULL) 589 return; 590 591 BTextControl* textControl 592 = dynamic_cast<BTextControl*>(FindView("text view")); 593 BObjectList<BPose>* selectionList = fPoseView->SelectionList(); 594 BString buttonText = fButtonText; 595 bool enabled = false; 596 597 if (fIsSavePanel && textControl != NULL) { 598 enabled = textControl->Text()[0] != '\0'; 599 if (fPoseView->IsFocus()) { 600 fPoseView->ShowSelection(true); 601 if (selectionList->CountItems() == 1) { 602 Model* model = selectionList->FirstItem()->TargetModel(); 603 if (model->ResolveIfLink()->IsDirectory()) { 604 enabled = true; 605 buttonText = B_TRANSLATE("Open"); 606 } else { 607 // insert the name of the selected model into 608 // the text field, do not alter focus 609 textControl->SetText(model->Name()); 610 } 611 } 612 } else 613 fPoseView->ShowSelection(false); 614 } else { 615 int32 count = selectionList->CountItems(); 616 if (count) { 617 enabled = true; 618 619 // go through selection list looking at content 620 for (int32 index = 0; index < count; index++) { 621 Model* model = selectionList->ItemAt(index)->TargetModel(); 622 623 uint32 modelFlavor = GetLinkFlavor(model, false); 624 uint32 linkFlavor = GetLinkFlavor(model, true); 625 626 // if only one item is selected and we're not in dir 627 // selection mode then we don't disable button ever 628 if ((modelFlavor == B_DIRECTORY_NODE 629 || linkFlavor == B_DIRECTORY_NODE) 630 && count == 1) { 631 break; 632 } 633 634 if ((fNodeFlavors & modelFlavor) == 0 635 && (fNodeFlavors & linkFlavor) == 0) { 636 enabled = false; 637 break; 638 } 639 } 640 } else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) { 641 // No selection, but the current directory could be opened. 642 enabled = true; 643 } 644 } 645 646 button->SetLabel(buttonText.String()); 647 button->SetEnabled(enabled); 648 } 649 650 651 void 652 TFilePanel::SelectionChanged() 653 { 654 AdjustButton(); 655 656 if (fClientObject) 657 fClientObject->SelectionChanged(); 658 } 659 660 661 status_t 662 TFilePanel::GetNextEntryRef(entry_ref* ref) 663 { 664 if (!ref) 665 return B_ERROR; 666 667 BPose* pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++); 668 if (!pose) 669 return B_ERROR; 670 671 *ref = *pose->TargetModel()->EntryRef(); 672 return B_OK; 673 } 674 675 676 BPoseView* 677 TFilePanel::NewPoseView(Model* model, uint32) 678 { 679 return new BFilePanelPoseView(model); 680 } 681 682 683 void 684 TFilePanel::Init(const BMessage*) 685 { 686 BRect windRect(Bounds()); 687 fBackView = new BView(Bounds(), "View", B_FOLLOW_ALL, 0); 688 fBackView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 689 AddChild(fBackView); 690 691 // add poseview menu bar 692 fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar"); 693 fMenuBar->SetBorder(B_BORDER_FRAME); 694 fBackView->AddChild(fMenuBar); 695 696 // add directory menu and menufield 697 font_height ht; 698 be_plain_font->GetHeight(&ht); 699 const float f_height = ht.ascent + ht.descent + ht.leading; 700 const float spacing = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING); 701 702 BRect rect; 703 rect.top = fMenuBar->Bounds().Height() + spacing; 704 rect.left = spacing; 705 rect.right = rect.left + (spacing * 50); 706 rect.bottom = rect.top + (f_height > 22 ? f_height : 22); 707 708 fDirMenuField = new BMenuField(rect, "DirMenuField", "", NULL); 709 fDirMenuField->MenuBar()->SetFont(be_plain_font); 710 fDirMenuField->SetDivider(0); 711 fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f); 712 // Make room for the icon 713 714 fDirMenu = new BDirMenu(fDirMenuField->MenuBar(), 715 this, kSwitchDirectory, "refs"); 716 717 BEntry entry(TargetModel()->EntryRef()); 718 if (entry.InitCheck() == B_OK) 719 fDirMenu->Populate(&entry, 0, true, true, false, true); 720 else 721 fDirMenu->Populate(0, 0, true, true, false, true); 722 723 fBackView->AddChild(fDirMenuField); 724 725 // add buttons 726 fButtonText = fIsSavePanel ? B_TRANSLATE("Save") : B_TRANSLATE("Open"); 727 BButton* default_button = new BButton(BRect(), "default button", 728 fButtonText.String(), new BMessage(kDefaultButton), 729 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 730 BSize preferred = default_button->PreferredSize(); 731 const BRect defaultButtonRect = BRect(BPoint( 732 windRect.Width() - (preferred.Width() + spacing + be_control_look->GetScrollBarWidth()), 733 windRect.Height() - (preferred.Height() + spacing)), 734 preferred); 735 default_button->MoveTo(defaultButtonRect.LeftTop()); 736 default_button->ResizeTo(preferred); 737 fBackView->AddChild(default_button); 738 739 BButton* cancel_button = new BButton(BRect(), "cancel button", 740 B_TRANSLATE("Cancel"), new BMessage(kCancelButton), 741 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 742 preferred = cancel_button->PreferredSize(); 743 cancel_button->MoveTo(defaultButtonRect.LeftTop() 744 - BPoint(preferred.Width() + spacing, 0)); 745 cancel_button->ResizeTo(preferred); 746 fBackView->AddChild(cancel_button); 747 748 // add file name text view 749 if (fIsSavePanel) { 750 BRect rect(defaultButtonRect); 751 rect.left = spacing; 752 rect.right = rect.left + spacing * 28; 753 754 fTextControl = new BTextControl(rect, "text view", 755 B_TRANSLATE("save text"), "", NULL, 756 B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 757 DisallowMetaKeys(fTextControl->TextView()); 758 DisallowFilenameKeys(fTextControl->TextView()); 759 fBackView->AddChild(fTextControl); 760 fTextControl->SetDivider(0.0f); 761 fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); 762 } 763 764 // Add PoseView 765 PoseView()->SetName("ActualPoseView"); 766 fPoseContainer->SetName("PoseView"); 767 fPoseContainer->SetResizingMode(B_FOLLOW_ALL); 768 fBorderedView->EnableBorderHighlight(true); 769 770 rect.left = spacing; 771 rect.top = fDirMenuField->Frame().bottom + spacing; 772 rect.right = windRect.Width() - spacing; 773 rect.bottom = defaultButtonRect.top - spacing; 774 fPoseContainer->MoveTo(rect.LeftTop()); 775 fPoseContainer->ResizeTo(rect.Size()); 776 777 PoseView()->AddScrollBars(); 778 PoseView()->SetDragEnabled(false); 779 PoseView()->SetDropEnabled(false); 780 PoseView()->SetSelectionHandler(this); 781 PoseView()->SetSelectionChangedHook(true); 782 PoseView()->DisableSaveLocation(); 783 784 if (fIsSavePanel) 785 fBackView->AddChild(fPoseContainer, fTextControl); 786 else 787 fBackView->AddChild(fPoseContainer); 788 789 AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton)); 790 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 791 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, 792 new BMessage(kShowSelectionWindow)); 793 AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), this); 794 AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection), 795 PoseView()); 796 AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView()); 797 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir)); 798 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 799 new BMessage(kOpenDir)); 800 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir)); 801 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 802 new BMessage(kOpenParentDir)); 803 804 if (!fIsSavePanel && (fNodeFlavors & B_DIRECTORY_NODE) == 0) 805 default_button->SetEnabled(false); 806 807 default_button->MakeDefault(true); 808 809 RestoreState(); 810 811 AddMenus(); 812 AddContextMenus(); 813 814 FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"), 815 new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED), 816 BMessenger(this), IsSavePanel(), fPoseView->RefFilter()); 817 favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"), 818 new BMessage(kAddCurrentDir))); 819 favorites->AddItem(new BMenuItem( 820 B_TRANSLATE("Edit favorites" B_UTF8_ELLIPSIS), 821 new BMessage(kEditFavorites))); 822 823 fMenuBar->AddItem(favorites); 824 825 // configure menus 826 BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window")); 827 if (item) { 828 fMenuBar->RemoveItem(item); 829 delete item; 830 } 831 832 item = fMenuBar->FindItem(B_TRANSLATE("File")); 833 if (item) { 834 BMenu* menu = item->Submenu(); 835 if (menu) { 836 item = menu->FindItem(kOpenSelection); 837 if (item && menu->RemoveItem(item)) 838 delete item; 839 840 // remove add-ons menu, identifier menu, separator 841 item = menu->FindItem(B_TRANSLATE("Add-ons")); 842 if (item) { 843 int32 index = menu->IndexOf(item); 844 delete menu->RemoveItem(index); 845 delete menu->RemoveItem(--index); 846 delete menu->RemoveItem(--index); 847 } 848 849 // remove separator 850 item = menu->FindItem(B_CUT); 851 if (item) { 852 item = menu->ItemAt(menu->IndexOf(item)-1); 853 if (item && menu->RemoveItem(item)) 854 delete item; 855 } 856 } 857 } 858 859 PoseView()->ScrollTo(B_ORIGIN); 860 PoseView()->UpdateScrollRange(); 861 PoseView()->ScrollTo(B_ORIGIN); 862 863 // Focus on text control initially, but do not alter focus afterwords 864 // because pose view focus is needed for Cut/Copy/Paste to work. 865 866 if (fIsSavePanel && fTextControl != NULL) { 867 fTextControl->MakeFocus(); 868 fTextControl->TextView()->SelectAll(); 869 } else 870 PoseView()->MakeFocus(); 871 872 app_info info; 873 BString title; 874 if (be_app->GetAppInfo(&info) == B_OK) { 875 if (!gLocalizedNamePreferred 876 || BLocaleRoster::Default()->GetLocalizedFileName( 877 title, info.ref, false) != B_OK) 878 title = info.ref.name; 879 title << ": "; 880 } 881 title << fButtonText; // Open or Save 882 883 SetTitle(title.String()); 884 885 SetSizeLimits(spacing * 60, 10000, spacing * 33, 10000); 886 } 887 888 889 void 890 TFilePanel::RestoreState() 891 { 892 BNode defaultingNode; 893 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 894 false)) { 895 AttributeStreamFileNode streamNodeSource(&defaultingNode); 896 RestoreWindowState(&streamNodeSource); 897 PoseView()->Init(&streamNodeSource); 898 fDefaultStateRestored = true; 899 } else { 900 RestoreWindowState(NULL); 901 PoseView()->Init(NULL); 902 fDefaultStateRestored = false; 903 } 904 905 // Finish UI creation now that the PoseView is initialized 906 InitLayout(); 907 } 908 909 910 void 911 TFilePanel::SaveState(bool) 912 { 913 BNode defaultingNode; 914 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 915 true, false)) { 916 AttributeStreamFileNode streamNodeDestination(&defaultingNode); 917 SaveWindowState(&streamNodeDestination); 918 PoseView()->SaveState(&streamNodeDestination); 919 fStateNeedsSaving = false; 920 } 921 } 922 923 924 void 925 TFilePanel::SaveState(BMessage &message) const 926 { 927 _inherited::SaveState(message); 928 } 929 930 931 void 932 TFilePanel::RestoreWindowState(AttributeStreamNode* node) 933 { 934 SetSizeLimits(360, 10000, 200, 10000); 935 if (!node) 936 return; 937 938 const char* rectAttributeName = kAttrWindowFrame; 939 BRect frame(Frame()); 940 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 941 == sizeof(BRect)) { 942 MoveTo(frame.LeftTop()); 943 ResizeTo(frame.Width(), frame.Height()); 944 } 945 fStateNeedsSaving = false; 946 } 947 948 949 void 950 TFilePanel::RestoreState(const BMessage &message) 951 { 952 _inherited::RestoreState(message); 953 } 954 955 956 void 957 TFilePanel::RestoreWindowState(const BMessage &message) 958 { 959 _inherited::RestoreWindowState(message); 960 } 961 962 963 void 964 TFilePanel::AddFileContextMenus(BMenu* menu) 965 { 966 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 967 new BMessage(kGetInfo), 'I')); 968 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 969 new BMessage(kEditItem), 'E')); 970 menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"), 971 new BMessage(kDuplicateSelection), 'D')); 972 menu->AddItem(new BMenuItem(B_TRANSLATE("Move to Trash"), 973 new BMessage(kMoveToTrash), 'T')); 974 menu->AddSeparatorItem(); 975 976 BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"), 977 new BMessage(B_CUT), 'X'); 978 menu->AddItem(cutItem); 979 BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"), 980 new BMessage(B_COPY), 'C'); 981 menu->AddItem(copyItem); 982 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 983 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 984 new BMessage(B_PASTE), 'V'); 985 menu->AddItem(pasteItem); 986 #endif 987 988 menu->SetTargetForItems(PoseView()); 989 cutItem->SetTarget(this); 990 copyItem->SetTarget(this); 991 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 992 pasteItem->SetTarget(this); 993 #endif 994 } 995 996 997 void 998 TFilePanel::AddVolumeContextMenus(BMenu* menu) 999 { 1000 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 1001 new BMessage(kOpenSelection), 'O')); 1002 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 1003 new BMessage(kGetInfo), 'I')); 1004 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 1005 new BMessage(kEditItem), 'E')); 1006 1007 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1008 menu->AddSeparatorItem(); 1009 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 1010 new BMessage(B_PASTE), 'V'); 1011 #endif 1012 1013 menu->SetTargetForItems(PoseView()); 1014 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1015 pasteItem->SetTarget(this); 1016 #endif 1017 } 1018 1019 1020 void 1021 TFilePanel::AddWindowContextMenus(BMenu* menu) 1022 { 1023 BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"), 1024 new BMessage(kNewFolder), 'N'); 1025 item->SetTarget(PoseView()); 1026 menu->AddItem(item); 1027 menu->AddSeparatorItem(); 1028 1029 #if CUT_COPY_PASTE_IN_CONTEXT_MENU 1030 item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V'); 1031 item->SetTarget(this); 1032 menu->AddItem(item); 1033 menu->AddSeparatorItem(); 1034 #endif 1035 1036 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 1037 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); 1038 item->SetTarget(PoseView()); 1039 menu->AddItem(item); 1040 1041 item = new BMenuItem(B_TRANSLATE("Select all"), 1042 new BMessage(B_SELECT_ALL), 'A'); 1043 item->SetTarget(this); 1044 menu->AddItem(item); 1045 1046 item = new BMenuItem(B_TRANSLATE("Invert selection"), 1047 new BMessage(kInvertSelection), 'S'); 1048 item->SetTarget(PoseView()); 1049 menu->AddItem(item); 1050 1051 item = new BMenuItem(B_TRANSLATE("Go to parent"), 1052 new BMessage(kOpenParentDir), B_UP_ARROW); 1053 item->SetTarget(this); 1054 menu->AddItem(item); 1055 } 1056 1057 1058 void 1059 TFilePanel::AddDropContextMenus(BMenu*) 1060 { 1061 } 1062 1063 1064 void 1065 TFilePanel::MenusBeginning() 1066 { 1067 if (fMenuBar == NULL) 1068 return; 1069 1070 if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) { 1071 // don't commit active pose if only a keyboard shortcut is 1072 // invoked - this would prevent Cut/Copy/Paste from working 1073 PoseView()->CommitActivePose(); 1074 } 1075 1076 EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot() 1077 && !PoseView()->TargetVolumeIsReadOnly()); 1078 EnableNamedMenuItem(fMenuBar, kDuplicateSelection, 1079 PoseView()->CanMoveToTrashOrDuplicate()); 1080 EnableNamedMenuItem(fMenuBar, kMoveToTrash, 1081 PoseView()->CanMoveToTrashOrDuplicate()); 1082 EnableNamedMenuItem(fMenuBar, kEditItem, PoseView()->CanEditName()); 1083 1084 SetCutItem(fMenuBar); 1085 SetCopyItem(fMenuBar); 1086 SetPasteItem(fMenuBar); 1087 1088 fIsTrackingMenu = true; 1089 } 1090 1091 1092 void 1093 TFilePanel::MenusEnded() 1094 { 1095 fIsTrackingMenu = false; 1096 } 1097 1098 1099 void 1100 TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref) 1101 { 1102 ASSERT(IsLocked()); 1103 BPoint global(where); 1104 PoseView()->ConvertToScreen(&global); 1105 PoseView()->CommitActivePose(); 1106 1107 if (ref != NULL) { 1108 // clicked on a pose, show file or volume context menu 1109 Model model(ref); 1110 if (model.InitCheck() != B_OK) 1111 return; // bail out, do not show context menu 1112 1113 if (TargetModel()->IsRoot() || model.IsVolume()) { 1114 // Volume context menu 1115 fContextMenu = fVolumeContextMenu; 1116 EnableNamedMenuItem(fContextMenu, kOpenSelection, true); 1117 EnableNamedMenuItem(fContextMenu, kEditItem, 1118 PoseView()->CanEditName()); 1119 1120 SetPasteItem(fContextMenu); 1121 } else { 1122 // File context menu 1123 fContextMenu = fFileContextMenu; 1124 EnableNamedMenuItem(fContextMenu, kEditItem, 1125 PoseView()->CanEditName()); 1126 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, 1127 PoseView()->CanMoveToTrashOrDuplicate()); 1128 EnableNamedMenuItem(fContextMenu, kMoveToTrash, 1129 PoseView()->CanMoveToTrashOrDuplicate()); 1130 1131 SetCutItem(fContextMenu); 1132 SetCopyItem(fContextMenu); 1133 SetPasteItem(fContextMenu); 1134 } 1135 } else { 1136 // Window context menu 1137 fContextMenu = fWindowContextMenu; 1138 EnableNamedMenuItem(fContextMenu, kNewFolder, 1139 !TargetModel()->IsRoot() 1140 && !PoseView()->TargetVolumeIsReadOnly()); 1141 EnableNamedMenuItem(fContextMenu, kOpenParentDir, 1142 !TargetModel()->IsRoot()); 1143 1144 SetPasteItem(fContextMenu); 1145 } 1146 1147 // context menu invalid or popup window is already open 1148 if (fContextMenu == NULL || fContextMenu->Window() != NULL) 1149 return; 1150 1151 fContextMenu->Go(global, true, true, true); 1152 fContextMenu = NULL; 1153 } 1154 1155 1156 void 1157 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*) 1158 { 1159 // do nothing here so nav menu doesn't get added 1160 } 1161 1162 1163 void 1164 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text) 1165 { 1166 switch (selector) { 1167 case B_CANCEL_BUTTON: 1168 { 1169 BButton* button 1170 = dynamic_cast<BButton*>(FindView("cancel button")); 1171 if (button == NULL) 1172 break; 1173 1174 float old_width = button->StringWidth(button->Label()); 1175 button->SetLabel(text); 1176 float delta = old_width - button->StringWidth(text); 1177 if (delta) { 1178 button->MoveBy(delta, 0); 1179 button->ResizeBy(-delta, 0); 1180 } 1181 } 1182 break; 1183 1184 case B_DEFAULT_BUTTON: 1185 { 1186 fButtonText = text; 1187 float delta = 0; 1188 BButton* button 1189 = dynamic_cast<BButton*>(FindView("default button")); 1190 if (button != NULL) { 1191 float old_width = button->StringWidth(button->Label()); 1192 button->SetLabel(text); 1193 delta = old_width - button->StringWidth(text); 1194 if (delta) { 1195 button->MoveBy(delta, 0); 1196 button->ResizeBy(-delta, 0); 1197 } 1198 } 1199 1200 // now must move cancel button 1201 button = dynamic_cast<BButton*>(FindView("cancel button")); 1202 if (button != NULL) 1203 button->MoveBy(delta, 0); 1204 } 1205 break; 1206 } 1207 } 1208 1209 1210 void 1211 TFilePanel::SetSaveText(const char* text) 1212 { 1213 if (text == NULL) 1214 return; 1215 1216 BTextControl* textControl 1217 = dynamic_cast<BTextControl*>(FindView("text view")); 1218 if (textControl != NULL) { 1219 textControl->SetText(text); 1220 if (textControl->TextView() != NULL) 1221 textControl->TextView()->SelectAll(); 1222 } 1223 } 1224 1225 1226 void 1227 TFilePanel::MessageReceived(BMessage* message) 1228 { 1229 entry_ref ref; 1230 1231 switch (message->what) { 1232 case B_REFS_RECEIVED: 1233 // item was double clicked in file panel (PoseView) or from the favorites menu 1234 if (message->FindRef("refs", &ref) == B_OK) { 1235 BEntry entry(&ref, true); 1236 if (entry.InitCheck() == B_OK) { 1237 // Double-click on dir or link-to-dir ALWAYS opens the 1238 // dir. If more than one dir is selected, the first is 1239 // entered. 1240 if (entry.IsDirectory()) { 1241 entry.GetRef(&ref); 1242 bool isDesktop = SwitchDirToDesktopIfNeeded(ref); 1243 1244 PoseView()->SetIsDesktop(isDesktop); 1245 entry.SetTo(&ref); 1246 PoseView()->SwitchDir(&ref); 1247 SwitchDirMenuTo(&ref); 1248 } else { 1249 // Otherwise, we have a file or a link to a file. 1250 // AdjustButton has already tested the flavor if it comes from the file 1251 // panel; all we have to do is see if the button is enabled. 1252 // In other cases, however, we can't rely on that. So first check for 1253 // TrackerViewToken in the message to see if it's coming from the pose view 1254 if (message->HasMessenger("TrackerViewToken")) { 1255 BButton* button = dynamic_cast<BButton*>(FindView("default button")); 1256 if (button == NULL || !button->IsEnabled()) 1257 break; 1258 } 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