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