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