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