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