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