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