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 "PoseView.h" 79 #include "Tracker.h" 80 #include "Utilities.h" 81 82 #include "tracker_private.h" 83 84 85 #undef B_TRANSLATION_CONTEXT 86 #define B_TRANSLATION_CONTEXT "FilePanelPriv" 87 88 89 const char* kDefaultFilePanelTemplate = "FilePanelSettings"; 90 91 92 static uint32 93 GetLinkFlavor(const Model* model, bool resolve = true) 94 { 95 if (model && model->IsSymLink()) { 96 if (!resolve) 97 return B_SYMLINK_NODE; 98 model = model->LinkTo(); 99 } 100 if (!model) 101 return 0; 102 103 if (model->IsDirectory()) 104 return B_DIRECTORY_NODE; 105 106 return B_FILE_NODE; 107 } 108 109 110 static filter_result 111 key_down_filter(BMessage* message, BHandler** handler, BMessageFilter* filter) 112 { 113 ASSERT(filter != NULL); 114 if (filter == NULL) 115 return B_DISPATCH_MESSAGE; 116 117 TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper()); 118 ASSERT(panel != NULL); 119 120 if (panel == NULL) 121 return B_DISPATCH_MESSAGE; 122 123 BPoseView* view = panel->PoseView(); 124 if (panel->TrackingMenu()) 125 return B_DISPATCH_MESSAGE; 126 127 uchar key; 128 if (message->FindInt8("byte", (int8*)&key) != B_OK) 129 return B_DISPATCH_MESSAGE; 130 131 int32 modifier = 0; 132 message->FindInt32("modifiers", &modifier); 133 if (!modifier && key == B_ESCAPE) { 134 if (view->ActivePose()) 135 view->CommitActivePose(false); 136 else if (view->IsFiltering()) 137 filter->Looper()->PostMessage(B_CANCEL, *handler); 138 else 139 filter->Looper()->PostMessage(kCancelButton); 140 141 return B_SKIP_MESSAGE; 142 } 143 144 if (key == B_RETURN && view->ActivePose()) { 145 view->CommitActivePose(); 146 147 return B_SKIP_MESSAGE; 148 } 149 150 return B_DISPATCH_MESSAGE; 151 } 152 153 154 // #pragma mark - TFilePanel 155 156 157 TFilePanel::TFilePanel(file_panel_mode mode, BMessenger* target, 158 const BEntry* startDir, uint32 nodeFlavors, bool multipleSelection, 159 BMessage* message, BRefFilter* filter, uint32 containerWindowFlags, 160 window_look look, window_feel feel, bool hideWhenDone) 161 : 162 BContainerWindow(0, containerWindowFlags, look, feel, 0, 163 B_CURRENT_WORKSPACE, false), 164 fDirMenu(NULL), 165 fDirMenuField(NULL), 166 fTextControl(NULL), 167 fClientObject(NULL), 168 fSelectionIterator(0), 169 fMessage(NULL), 170 fHideWhenDone(hideWhenDone), 171 fIsTrackingMenu(false) 172 { 173 InitIconPreloader(); 174 175 fIsSavePanel = (mode == B_SAVE_PANEL); 176 177 BRect windRect(85, 50, 568, 296); 178 MoveTo(windRect.LeftTop()); 179 ResizeTo(windRect.Width(), windRect.Height()); 180 181 fNodeFlavors = (nodeFlavors == 0) ? B_FILE_NODE : nodeFlavors; 182 183 if (target) 184 fTarget = *target; 185 else 186 fTarget = BMessenger(be_app); 187 188 if (message) 189 SetMessage(message); 190 else if (fIsSavePanel) 191 fMessage = new BMessage(B_SAVE_REQUESTED); 192 else 193 fMessage = new BMessage(B_REFS_RECEIVED); 194 195 gLocalizedNamePreferred 196 = BLocaleRoster::Default()->IsFilesystemTranslationPreferred(); 197 198 // check for legal starting directory 199 Model* model = new Model(); 200 bool useRoot = true; 201 202 if (startDir) { 203 if (model->SetTo(startDir) == B_OK && model->IsDirectory()) 204 useRoot = false; 205 else { 206 delete model; 207 model = new Model(); 208 } 209 } 210 211 if (useRoot) { 212 BPath path; 213 if (find_directory(B_USER_DIRECTORY, &path) == B_OK) { 214 BEntry entry(path.Path(), true); 215 if (entry.InitCheck() == B_OK && model->SetTo(&entry) == B_OK) 216 useRoot = false; 217 } 218 } 219 220 if (useRoot) { 221 BVolume volume; 222 BDirectory root; 223 BVolumeRoster volumeRoster; 224 volumeRoster.GetBootVolume(&volume); 225 volume.GetRootDirectory(&root); 226 227 BEntry entry; 228 root.GetEntry(&entry); 229 model->SetTo(&entry); 230 } 231 232 fTaskLoop = new PiggybackTaskLoop; 233 234 AutoLock<BWindow> lock(this); 235 fBorderedView = new BorderedView; 236 CreatePoseView(model); 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->SetViewColor(ui_color(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 AddMenus(); 666 AddContextMenus(); 667 668 FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"), 669 new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED), 670 BMessenger(this), IsSavePanel(), fPoseView->RefFilter()); 671 favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"), 672 new BMessage(kAddCurrentDir))); 673 favorites->AddItem(new BMenuItem( 674 B_TRANSLATE("Edit favorites" B_UTF8_ELLIPSIS), 675 new BMessage(kEditFavorites))); 676 677 fMenuBar->AddItem(favorites); 678 679 // configure menus 680 BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window")); 681 if (item) { 682 fMenuBar->RemoveItem(item); 683 delete item; 684 } 685 686 item = fMenuBar->FindItem(B_TRANSLATE("File")); 687 if (item) { 688 BMenu* menu = item->Submenu(); 689 if (menu) { 690 item = menu->FindItem(kOpenSelection); 691 if (item && menu->RemoveItem(item)) 692 delete item; 693 694 item = menu->FindItem(kDuplicateSelection); 695 if (item && menu->RemoveItem(item)) 696 delete item; 697 698 // remove add-ons menu, identifier menu, separator 699 item = menu->FindItem(B_TRANSLATE("Add-ons")); 700 if (item) { 701 int32 index = menu->IndexOf(item); 702 delete menu->RemoveItem(index); 703 delete menu->RemoveItem(--index); 704 delete menu->RemoveItem(--index); 705 } 706 707 // remove separator 708 item = menu->FindItem(B_CUT); 709 if (item) { 710 item = menu->ItemAt(menu->IndexOf(item)-1); 711 if (item && menu->RemoveItem(item)) 712 delete item; 713 } 714 } 715 } 716 717 // add directory menu and menufield 718 fDirMenu = new BDirMenu(0, this, kSwitchDirectory, "refs"); 719 720 font_height ht; 721 be_plain_font->GetHeight(&ht); 722 float f_height = ht.ascent + ht.descent + ht.leading; 723 724 BRect rect; 725 rect.top = fMenuBar->Bounds().Height() + 8; 726 rect.left = windRect.left + 8; 727 rect.right = rect.left + 300; 728 rect.bottom = rect.top + (f_height > 22 ? f_height : 22); 729 730 fDirMenuField = new BMenuField(rect, "DirMenuField", "", fDirMenu); 731 fDirMenuField->MenuBar()->SetFont(be_plain_font); 732 fDirMenuField->SetDivider(0); 733 fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f); 734 // Make room for the icon 735 736 fDirMenuField->MenuBar()->RemoveItem((int32)0); 737 fDirMenu->SetMenuBar(fDirMenuField->MenuBar()); 738 // the above is a weird call from BDirMenu 739 // ToDo: clean up 740 741 BEntry entry(TargetModel()->EntryRef()); 742 if (entry.InitCheck() == B_OK) 743 fDirMenu->Populate(&entry, 0, true, true, false, true); 744 else 745 fDirMenu->Populate(0, 0, true, true, false, true); 746 747 fBackView->AddChild(fDirMenuField); 748 749 // add file name text view 750 if (fIsSavePanel) { 751 BRect rect(windRect); 752 rect.top = rect.bottom - 35; 753 rect.left = 8; 754 rect.right = rect.left + 170; 755 rect.bottom = rect.top + 13; 756 757 fTextControl = new BTextControl(rect, "text view", 758 B_TRANSLATE("save text"), "", NULL, 759 B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 760 DisallowMetaKeys(fTextControl->TextView()); 761 DisallowFilenameKeys(fTextControl->TextView()); 762 fBackView->AddChild(fTextControl); 763 fTextControl->SetDivider(0.0f); 764 fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); 765 766 fButtonText.SetTo(B_TRANSLATE("Save")); 767 } else 768 fButtonText.SetTo(B_TRANSLATE("Open")); 769 770 // Add PoseView 771 fBorderedView->SetName("PoseView"); 772 fBorderedView->SetResizingMode(B_FOLLOW_ALL); 773 fBorderedView->EnableBorderHighlight(true); 774 775 rect = windRect; 776 rect.OffsetTo(10, fDirMenuField->Frame().bottom + 10); 777 rect.bottom = windRect.bottom - 60; 778 rect.right -= B_V_SCROLL_BAR_WIDTH + 20; 779 fBorderedView->MoveTo(rect.LeftTop()); 780 fBorderedView->ResizeTo(rect.Width(), rect.Height()); 781 782 PoseView()->AddScrollBars(); 783 PoseView()->SetDragEnabled(false); 784 PoseView()->SetDropEnabled(false); 785 PoseView()->SetSelectionHandler(this); 786 PoseView()->SetSelectionChangedHook(true); 787 PoseView()->DisableSaveLocation(); 788 789 // horizontal 790 rect = fBorderedView->Frame(); 791 rect.top = rect.bottom; 792 rect.bottom = rect.top + (float)B_H_SCROLL_BAR_HEIGHT; 793 PoseView()->HScrollBar()->MoveTo(rect.LeftTop()); 794 PoseView()->HScrollBar()->ResizeTo(rect.Size()); 795 PoseView()->HScrollBar()->SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 796 fBackView->AddChild(PoseView()->HScrollBar()); 797 798 // vertical 799 rect = fBorderedView->Frame(); 800 rect.left = rect.right; 801 rect.right = rect.left + (float)B_V_SCROLL_BAR_WIDTH; 802 PoseView()->VScrollBar()->MoveTo(rect.LeftTop()); 803 PoseView()->VScrollBar()->ResizeTo(rect.Size()); 804 PoseView()->VScrollBar()->SetResizingMode(B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT); 805 fBackView->AddChild(PoseView()->VScrollBar()); 806 807 if (fIsSavePanel) 808 fBackView->AddChild(fBorderedView, fTextControl); 809 else 810 fBackView->AddChild(fBorderedView); 811 812 AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton)); 813 AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); 814 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, 815 new BMessage(kShowSelectionWindow)); 816 AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), PoseView()); 817 AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection), 818 PoseView()); 819 AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView()); 820 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir)); 821 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 822 new BMessage(kOpenDir)); 823 AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir)); 824 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, 825 new BMessage(kOpenParentDir)); 826 827 // New code to make buttons font sensitive 828 rect = windRect; 829 rect.top = rect.bottom - 35; 830 rect.bottom -= 10; 831 rect.right -= 25; 832 float default_width 833 = be_plain_font->StringWidth(fButtonText.String()) + 20; 834 rect.left = default_width > 75 835 ? rect.right - default_width : rect.right - 75; 836 837 BButton* default_button = new BButton(rect, "default button", 838 fButtonText.String(), new BMessage(kDefaultButton), 839 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 840 fBackView->AddChild(default_button); 841 842 rect.right = rect.left -= 10; 843 float cancel_width 844 = be_plain_font->StringWidth(B_TRANSLATE("Cancel")) + 20; 845 rect.left = cancel_width > 75 846 ? rect.right - cancel_width : rect.right - 75; 847 848 BButton* cancel_button = new BButton(rect, "cancel button", 849 B_TRANSLATE("Cancel"), new BMessage(kCancelButton), 850 B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); 851 fBackView->AddChild(cancel_button); 852 853 if (!fIsSavePanel) 854 default_button->SetEnabled(false); 855 856 default_button->MakeDefault(true); 857 858 RestoreState(); 859 860 PoseView()->ScrollTo(B_ORIGIN); 861 PoseView()->UpdateScrollRange(); 862 PoseView()->ScrollTo(B_ORIGIN); 863 864 if (fTextControl) { 865 fTextControl->MakeFocus(); 866 fTextControl->TextView()->SelectAll(); 867 } else 868 PoseView()->MakeFocus(); 869 870 app_info info; 871 BString title; 872 if (be_app->GetAppInfo(&info) == B_OK) { 873 if (!gLocalizedNamePreferred 874 || BLocaleRoster::Default()->GetLocalizedFileName( 875 title, info.ref, false) != B_OK) 876 title = info.ref.name; 877 title << ": "; 878 } 879 title << fButtonText; // Open or Save 880 881 SetTitle(title.String()); 882 883 SetSizeLimits(370, 10000, 200, 10000); 884 } 885 886 887 void 888 TFilePanel::RestoreState() 889 { 890 BNode defaultingNode; 891 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 892 false)) { 893 AttributeStreamFileNode streamNodeSource(&defaultingNode); 894 RestoreWindowState(&streamNodeSource); 895 PoseView()->Init(&streamNodeSource); 896 } else { 897 RestoreWindowState(NULL); 898 PoseView()->Init(NULL); 899 } 900 901 // Finish UI creation now that the PoseView is initialized 902 BLayoutItem* item 903 = fBorderedView->GroupLayout()->AddView(0, fPoseView->TitleView()); 904 BSize minSize = item->MinSize(); 905 BSize maxSize = item->MaxSize(); 906 item->SetExplicitMinSize(BSize(minSize.Width(), kTitleViewHeight)); 907 item->SetExplicitMaxSize(BSize(maxSize.Width(), kTitleViewHeight)); 908 909 BRect rect(fBorderedView->Frame()); 910 rect.right = rect.left + kCountViewWidth; 911 rect.top = rect.bottom + 1; 912 rect.bottom = rect.top + PoseView()->HScrollBar()->Bounds().Height() - 1; 913 PoseView()->CountView()->MoveTo(rect.LeftTop()); 914 PoseView()->CountView()->ResizeTo(rect.Size()); 915 PoseView()->CountView()->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 916 fBackView->AddChild(PoseView()->CountView(), fBorderedView); 917 918 PoseView()->HScrollBar()->MoveBy(kCountViewWidth + 1, 0); 919 PoseView()->HScrollBar()->ResizeBy(-kCountViewWidth - 1, 0); 920 921 // The Be Book states that the BTitleView will have a name of "TitleView", 922 // and so some apps will try to grab it by that name and move it around. 923 // They don't need to, because resizing "PoseView" (really the BorderedView) 924 // will resize the BTitleView as well. So just create a dummy view here 925 // so that they don't get NULL when trying to find the view. 926 BView* dummyTitleView = new BView(BRect(), "TitleView", B_FOLLOW_NONE, 0); 927 fBackView->AddChild(dummyTitleView); 928 dummyTitleView->Hide(); 929 } 930 931 932 void 933 TFilePanel::SaveState(bool) 934 { 935 BNode defaultingNode; 936 if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, 937 true, false)) { 938 AttributeStreamFileNode streamNodeDestination(&defaultingNode); 939 SaveWindowState(&streamNodeDestination); 940 PoseView()->SaveState(&streamNodeDestination); 941 } 942 } 943 944 945 void 946 TFilePanel::SaveState(BMessage &message) const 947 { 948 _inherited::SaveState(message); 949 } 950 951 952 void 953 TFilePanel::RestoreWindowState(AttributeStreamNode* node) 954 { 955 SetSizeLimits(360, 10000, 200, 10000); 956 if (!node) 957 return; 958 959 const char* rectAttributeName = kAttrWindowFrame; 960 BRect frame(Frame()); 961 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) 962 == sizeof(BRect)) { 963 MoveTo(frame.LeftTop()); 964 ResizeTo(frame.Width(), frame.Height()); 965 } 966 } 967 968 969 void 970 TFilePanel::RestoreState(const BMessage &message) 971 { 972 _inherited::RestoreState(message); 973 } 974 975 976 void 977 TFilePanel::RestoreWindowState(const BMessage &message) 978 { 979 _inherited::RestoreWindowState(message); 980 } 981 982 983 void 984 TFilePanel::AddFileContextMenus(BMenu* menu) 985 { 986 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 987 new BMessage(kGetInfo), 'I')); 988 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 989 new BMessage(kEditItem), 'E')); 990 menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() 991 ? B_TRANSLATE("Delete") 992 : B_TRANSLATE("Move to Trash"), 993 new BMessage(kMoveToTrash), 'T')); 994 menu->AddSeparatorItem(); 995 menu->AddItem(new BMenuItem(B_TRANSLATE("Cut"), 996 new BMessage(B_CUT), 'X')); 997 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy"), 998 new BMessage(B_COPY), 'C')); 999 //menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 1000 // 'V')); 1001 1002 menu->SetTargetForItems(PoseView()); 1003 } 1004 1005 1006 void 1007 TFilePanel::AddVolumeContextMenus(BMenu* menu) 1008 { 1009 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"), 1010 new BMessage(kOpenSelection), 'O')); 1011 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), 1012 new BMessage(kGetInfo), 'I')); 1013 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"), 1014 new BMessage(kEditItem), 'E')); 1015 menu->AddSeparatorItem(); 1016 menu->AddItem(new BMenuItem(B_TRANSLATE("Cut"), new BMessage(B_CUT), 1017 'X')); 1018 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy"), 1019 new BMessage(B_COPY), 'C')); 1020 //menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 1021 // 'V')); 1022 1023 menu->SetTargetForItems(PoseView()); 1024 } 1025 1026 1027 void 1028 TFilePanel::AddWindowContextMenus(BMenu* menu) 1029 { 1030 BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"), 1031 new BMessage(kNewFolder), 'N'); 1032 item->SetTarget(PoseView()); 1033 menu->AddItem(item); 1034 menu->AddSeparatorItem(); 1035 1036 item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V'); 1037 item->SetTarget(PoseView()); 1038 menu->AddItem(item); 1039 menu->AddSeparatorItem(); 1040 1041 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 1042 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); 1043 item->SetTarget(PoseView()); 1044 menu->AddItem(item); 1045 1046 item = new BMenuItem(B_TRANSLATE("Select all"), 1047 new BMessage(B_SELECT_ALL), 'A'); 1048 item->SetTarget(PoseView()); 1049 menu->AddItem(item); 1050 1051 item = new BMenuItem(B_TRANSLATE("Invert selection"), 1052 new BMessage(kInvertSelection), 'S'); 1053 item->SetTarget(PoseView()); 1054 menu->AddItem(item); 1055 1056 item = new BMenuItem(B_TRANSLATE("Go to parent"), 1057 new BMessage(kOpenParentDir), B_UP_ARROW); 1058 item->SetTarget(this); 1059 menu->AddItem(item); 1060 } 1061 1062 1063 void 1064 TFilePanel::AddDropContextMenus(BMenu*) 1065 { 1066 } 1067 1068 1069 void 1070 TFilePanel::MenusBeginning() 1071 { 1072 int32 count = PoseView()->SelectionList()->CountItems(); 1073 1074 EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()); 1075 EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot() 1076 && count); 1077 EnableNamedMenuItem(fMenuBar, kGetInfo, count != 0); 1078 EnableNamedMenuItem(fMenuBar, kEditItem, count == 1); 1079 1080 SetCutItem(fMenuBar); 1081 SetCopyItem(fMenuBar); 1082 SetPasteItem(fMenuBar); 1083 1084 fIsTrackingMenu = true; 1085 } 1086 1087 1088 void 1089 TFilePanel::MenusEnded() 1090 { 1091 fIsTrackingMenu = false; 1092 } 1093 1094 1095 void 1096 TFilePanel::ShowContextMenu(BPoint point, const entry_ref* ref, BView* view) 1097 { 1098 EnableNamedMenuItem(fWindowContextMenu, kNewFolder, 1099 !TargetModel()->IsRoot()); 1100 EnableNamedMenuItem(fWindowContextMenu, kOpenParentDir, 1101 !TargetModel()->IsRoot()); 1102 EnableNamedMenuItem(fWindowContextMenu, kMoveToTrash, 1103 !TargetModel()->IsRoot()); 1104 1105 _inherited::ShowContextMenu(point, ref, view); 1106 } 1107 1108 1109 void 1110 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*) 1111 { 1112 // do nothing here so nav menu doesn't get added 1113 } 1114 1115 1116 void 1117 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text) 1118 { 1119 switch (selector) { 1120 case B_CANCEL_BUTTON: 1121 { 1122 BButton* button 1123 = dynamic_cast<BButton*>(FindView("cancel button")); 1124 if (button == NULL) 1125 break; 1126 1127 float old_width = button->StringWidth(button->Label()); 1128 button->SetLabel(text); 1129 float delta = old_width - button->StringWidth(text); 1130 if (delta) { 1131 button->MoveBy(delta, 0); 1132 button->ResizeBy(-delta, 0); 1133 } 1134 } 1135 break; 1136 1137 case B_DEFAULT_BUTTON: 1138 { 1139 fButtonText = text; 1140 float delta = 0; 1141 BButton* button 1142 = dynamic_cast<BButton*>(FindView("default button")); 1143 if (button != NULL) { 1144 float old_width = button->StringWidth(button->Label()); 1145 button->SetLabel(text); 1146 delta = old_width - button->StringWidth(text); 1147 if (delta) { 1148 button->MoveBy(delta, 0); 1149 button->ResizeBy(-delta, 0); 1150 } 1151 } 1152 1153 // now must move cancel button 1154 button = dynamic_cast<BButton*>(FindView("cancel button")); 1155 if (button != NULL) 1156 button->MoveBy(delta, 0); 1157 } 1158 break; 1159 } 1160 } 1161 1162 1163 void 1164 TFilePanel::SetSaveText(const char* text) 1165 { 1166 if (text == NULL) 1167 return; 1168 1169 BTextControl* textControl 1170 = dynamic_cast<BTextControl*>(FindView("text view")); 1171 if (textControl != NULL) { 1172 textControl->SetText(text); 1173 if (textControl->TextView() != NULL) 1174 textControl->TextView()->SelectAll(); 1175 } 1176 } 1177 1178 1179 void 1180 TFilePanel::MessageReceived(BMessage* message) 1181 { 1182 entry_ref ref; 1183 1184 switch (message->what) { 1185 case B_REFS_RECEIVED: 1186 // item was double clicked in file panel (PoseView) 1187 if (message->FindRef("refs", &ref) == B_OK) { 1188 BEntry entry(&ref, true); 1189 if (entry.InitCheck() == B_OK) { 1190 // Double-click on dir or link-to-dir ALWAYS opens the 1191 // dir. If more than one dir is selected, the first is 1192 // entered. 1193 if (entry.IsDirectory()) { 1194 entry.GetRef(&ref); 1195 bool isDesktop = SwitchDirToDesktopIfNeeded(ref); 1196 1197 PoseView()->SetIsDesktop(isDesktop); 1198 entry.SetTo(&ref); 1199 PoseView()->SwitchDir(&ref); 1200 SwitchDirMenuTo(&ref); 1201 } else { 1202 // Otherwise, we have a file or a link to a file. 1203 // AdjustButton has already tested the flavor; 1204 // all we have to do is see if the button is enabled. 1205 BButton* button = dynamic_cast<BButton*>( 1206 FindView("default button")); 1207 if (button == NULL) 1208 break; 1209 1210 if (IsSavePanel()) { 1211 int32 count = 0; 1212 type_code type; 1213 message->GetInfo("refs", &type, &count); 1214 1215 // Don't allow saves of multiple files 1216 if (count > 1) { 1217 ShowCenteredAlert( 1218 B_TRANSLATE( 1219 "Sorry, saving more than one " 1220 "item is not allowed."), 1221 B_TRANSLATE("Cancel")); 1222 } else { 1223 // if we are a savepanel, set up the 1224 // filepanel correctly then pass control 1225 // so we follow the same path as if the user 1226 // clicked the save button 1227 1228 // set the 'name' fld to the current ref's 1229 // name notify the panel that the default 1230 // button should be enabled 1231 SetSaveText(ref.name); 1232 SelectionChanged(); 1233 1234 HandleSaveButton(); 1235 } 1236 break; 1237 } 1238 1239 // send handler a message and close 1240 BMessage openMessage(*fMessage); 1241 for (int32 index = 0; ; index++) { 1242 if (message->FindRef("refs", index, &ref) != B_OK) 1243 break; 1244 openMessage.AddRef("refs", &ref); 1245 } 1246 OpenSelectionCommon(&openMessage); 1247 } 1248 } 1249 } 1250 break; 1251 1252 case kSwitchDirectory: 1253 { 1254 entry_ref ref; 1255 // this comes from dir menu or nav menu, so switch directories 1256 if (message->FindRef("refs", &ref) == B_OK) { 1257 BEntry entry(&ref, true); 1258 if (entry.GetRef(&ref) == B_OK) 1259 SetTo(&ref); 1260 } 1261 break; 1262 } 1263 1264 case kSwitchToHome: 1265 { 1266 BPath homePath; 1267 entry_ref ref; 1268 if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK 1269 || get_ref_for_path(homePath.Path(), &ref) != B_OK) { 1270 break; 1271 } 1272 1273 SetTo(&ref); 1274 break; 1275 } 1276 1277 case kAddCurrentDir: 1278 { 1279 BPath path; 1280 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) 1281 != B_OK) { 1282 break; 1283 } 1284 1285 path.Append(kGoDirectory); 1286 BDirectory goDirectory(path.Path()); 1287 1288 if (goDirectory.InitCheck() == B_OK) { 1289 BEntry entry(TargetModel()->EntryRef()); 1290 entry.GetPath(&path); 1291 1292 BSymLink link; 1293 goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(), 1294 &link); 1295 } 1296 break; 1297 } 1298 1299 case kEditFavorites: 1300 { 1301 BPath path; 1302 if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) 1303 != B_OK) { 1304 break; 1305 } 1306 1307 path.Append(kGoDirectory); 1308 BMessenger msgr(kTrackerSignature); 1309 if (msgr.IsValid()) { 1310 BMessage message(B_REFS_RECEIVED); 1311 entry_ref ref; 1312 if (get_ref_for_path(path.Path(), &ref) == B_OK) { 1313 message.AddRef("refs", &ref); 1314 msgr.SendMessage(&message); 1315 } 1316 } 1317 break; 1318 } 1319 1320 case kCancelButton: 1321 PostMessage(B_QUIT_REQUESTED); 1322 break; 1323 1324 case kResizeToFit: 1325 ResizeToFit(); 1326 break; 1327 1328 case kOpenDir: 1329 OpenDirectory(); 1330 break; 1331 1332 case kOpenParentDir: 1333 OpenParent(); 1334 break; 1335 1336 case kDefaultButton: 1337 if (fIsSavePanel) { 1338 if (PoseView()->IsFocus() 1339 && PoseView()->SelectionList()->CountItems() == 1) { 1340 Model* model = (PoseView()->SelectionList()-> 1341 FirstItem())->TargetModel(); 1342 if (model->ResolveIfLink()->IsDirectory()) { 1343 PoseView()->CommitActivePose(); 1344 PoseView()->OpenSelection(); 1345 break; 1346 } 1347 } 1348 1349 HandleSaveButton(); 1350 } else 1351 HandleOpenButton(); 1352 break; 1353 1354 case B_OBSERVER_NOTICE_CHANGE: 1355 { 1356 int32 observerWhat; 1357 if (message->FindInt32("be:observe_change_what", &observerWhat) 1358 == B_OK) { 1359 switch (observerWhat) { 1360 case kDesktopFilePanelRootChanged: 1361 { 1362 bool desktopIsRoot = true; 1363 if (message->FindBool("DesktopFilePanelRoot", 1364 &desktopIsRoot) == B_OK) { 1365 TrackerSettings(). 1366 SetDesktopFilePanelRoot(desktopIsRoot); 1367 } 1368 SetTo(TargetModel()->EntryRef()); 1369 break; 1370 } 1371 } 1372 } 1373 break; 1374 } 1375 1376 default: 1377 _inherited::MessageReceived(message); 1378 break; 1379 } 1380 } 1381 1382 1383 void 1384 TFilePanel::OpenDirectory() 1385 { 1386 BObjectList<BPose>* list = PoseView()->SelectionList(); 1387 if (list->CountItems() != 1) 1388 return; 1389 1390 Model* model = list->FirstItem()->TargetModel(); 1391 if (model->ResolveIfLink()->IsDirectory()) { 1392 BMessage message(B_REFS_RECEIVED); 1393 message.AddRef("refs", model->EntryRef()); 1394 PostMessage(&message); 1395 } 1396 } 1397 1398 1399 void 1400 TFilePanel::OpenParent() 1401 { 1402 if (!CanOpenParent()) 1403 return; 1404 1405 BEntry parentEntry; 1406 BDirectory dir; 1407 1408 Model oldModel(*PoseView()->TargetModel()); 1409 BEntry entry(oldModel.EntryRef()); 1410 1411 if (entry.InitCheck() == B_OK 1412 && entry.GetParent(&dir) == B_OK 1413 && dir.GetEntry(&parentEntry) == B_OK 1414 && entry != parentEntry) { 1415 1416 entry_ref ref; 1417 parentEntry.GetRef(&ref); 1418 1419 PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref)); 1420 PoseView()->SwitchDir(&ref); 1421 SwitchDirMenuTo(&ref); 1422 1423 // make sure the child get's selected in the new view once it 1424 // shows up 1425 fTaskLoop->RunLater(NewMemberFunctionObjectWithResult 1426 (&TFilePanel::SelectChildInParent, this, 1427 const_cast<const entry_ref*>(&ref), 1428 oldModel.NodeRef()), 100000, 200000, 5000000); 1429 } 1430 } 1431 1432 1433 bool 1434 TFilePanel::CanOpenParent() const 1435 { 1436 if (TrackerSettings().DesktopFilePanelRoot()) { 1437 // don't allow opening Desktop folder's parent 1438 if (TargetModel()->IsDesktop()) 1439 return false; 1440 } 1441 1442 // block on "/" 1443 BEntry root("/"); 1444 node_ref rootRef; 1445 root.GetNodeRef(&rootRef); 1446 1447 return rootRef != *TargetModel()->NodeRef(); 1448 } 1449 1450 1451 bool 1452 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref) 1453 { 1454 // support showing Desktop as root of everything 1455 // This call implements the worm hole that maps Desktop as 1456 // a root above the disks 1457 TrackerSettings settings; 1458 if (!settings.DesktopFilePanelRoot()) 1459 // Tracker isn't set up that way, just let Disks show 1460 return false; 1461 1462 BEntry entry(&ref); 1463 BEntry root("/"); 1464 1465 BDirectory desktopDir; 1466 FSGetDeskDir(&desktopDir); 1467 if (FSIsDeskDir(&entry) 1468 // navigated into non-boot desktop, switch to boot desktop 1469 || (entry == root && !settings.ShowDisksIcon())) { 1470 // hit "/" level, map to desktop 1471 1472 desktopDir.GetEntry(&entry); 1473 entry.GetRef(&ref); 1474 return true; 1475 } 1476 return FSIsDeskDir(&entry); 1477 } 1478 1479 1480 bool 1481 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child) 1482 { 1483 AutoLock<TFilePanel> lock(this); 1484 1485 if (!IsLocked()) 1486 return false; 1487 1488 int32 index; 1489 BPose* pose = PoseView()->FindPose(child, &index); 1490 if (!pose) 1491 return false; 1492 1493 PoseView()->UpdateScrollRange(); 1494 // ToDo: Scroll range should be updated by now, for some 1495 // reason sometimes it is not right, force it here 1496 PoseView()->SelectPose(pose, index, true); 1497 return true; 1498 } 1499 1500 1501 int32 1502 TFilePanel::ShowCenteredAlert(const char* text, const char* button1, 1503 const char* button2, const char* button3) 1504 { 1505 BAlert* alert = new BAlert("", text, button1, button2, button3, 1506 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1507 alert->MoveTo(Frame().left + 10, Frame().top + 10); 1508 1509 #if 0 1510 if (button1 != NULL && !strncmp(button1, "Cancel", 7)) 1511 alert->SetShortcut(0, B_ESCAPE); 1512 else if (button2 != NULL && !strncmp(button2, "Cancel", 7)) 1513 alert->SetShortcut(1, B_ESCAPE); 1514 else if (button3 != NULL && !strncmp(button3, "Cancel", 7)) 1515 alert->SetShortcut(2, B_ESCAPE); 1516 #endif 1517 1518 return alert->Go(); 1519 } 1520 1521 1522 void 1523 TFilePanel::HandleSaveButton() 1524 { 1525 BDirectory dir; 1526 1527 if (TargetModel()->IsRoot()) { 1528 ShowCenteredAlert( 1529 B_TRANSLATE("Sorry, you can't save things at the root of " 1530 "your system."), 1531 B_TRANSLATE("Cancel")); 1532 return; 1533 } 1534 1535 // check for some illegal file names 1536 if (strcmp(fTextControl->Text(), ".") == 0 1537 || strcmp(fTextControl->Text(), "..") == 0) { 1538 ShowCenteredAlert( 1539 B_TRANSLATE("The specified name is illegal. Please choose " 1540 "another name."), 1541 B_TRANSLATE("Cancel")); 1542 fTextControl->TextView()->SelectAll(); 1543 return; 1544 } 1545 1546 if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) { 1547 ShowCenteredAlert( 1548 B_TRANSLATE("There was a problem trying to save in the folder " 1549 "you specified. Please try another one."), 1550 B_TRANSLATE("Cancel")); 1551 return; 1552 } 1553 1554 if (dir.Contains(fTextControl->Text())) { 1555 if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) { 1556 ShowCenteredAlert( 1557 B_TRANSLATE("The specified name is already used as the name " 1558 "of a folder. Please choose another name."), 1559 B_TRANSLATE("Cancel")); 1560 fTextControl->TextView()->SelectAll(); 1561 return; 1562 } else { 1563 // if this was invoked by a dbl click, it is an explicit 1564 // replacement of the file. 1565 BString str(B_TRANSLATE("The file \"%name\" already exists in " 1566 "the specified folder. Do you want to replace it?")); 1567 str.ReplaceFirst("%name", fTextControl->Text()); 1568 1569 if (ShowCenteredAlert(str.String(), B_TRANSLATE("Cancel"), 1570 B_TRANSLATE("Replace")) == 0) { 1571 // user canceled 1572 fTextControl->TextView()->SelectAll(); 1573 return; 1574 } 1575 // user selected "Replace" - let app deal with it 1576 } 1577 } 1578 1579 BMessage message(*fMessage); 1580 message.AddRef("directory", TargetModel()->EntryRef()); 1581 message.AddString("name", fTextControl->Text()); 1582 1583 if (fClientObject) 1584 fClientObject->SendMessage(&fTarget, &message); 1585 else 1586 fTarget.SendMessage(&message); 1587 1588 // close window if we're dealing with standard message 1589 if (fHideWhenDone) 1590 PostMessage(B_QUIT_REQUESTED); 1591 } 1592 1593 1594 void 1595 TFilePanel::OpenSelectionCommon(BMessage* openMessage) 1596 { 1597 if (!openMessage->HasRef("refs")) 1598 return; 1599 1600 for (int32 index = 0; ; index++) { 1601 entry_ref ref; 1602 if (openMessage->FindRef("refs", index, &ref) != B_OK) 1603 break; 1604 1605 BEntry entry(&ref, true); 1606 if (entry.InitCheck() == B_OK) { 1607 if (entry.IsDirectory()) 1608 BRoster().AddToRecentFolders(&ref); 1609 else 1610 BRoster().AddToRecentDocuments(&ref); 1611 } 1612 } 1613 1614 BRoster().AddToRecentFolders(TargetModel()->EntryRef()); 1615 1616 if (fClientObject) 1617 fClientObject->SendMessage(&fTarget, openMessage); 1618 else 1619 fTarget.SendMessage(openMessage); 1620 1621 // close window if we're dealing with standard message 1622 if (fHideWhenDone) 1623 PostMessage(B_QUIT_REQUESTED); 1624 } 1625 1626 1627 void 1628 TFilePanel::HandleOpenButton() 1629 { 1630 PoseView()->CommitActivePose(); 1631 BObjectList<BPose>* selection = PoseView()->SelectionList(); 1632 1633 // if we have only one directory and we're not opening dirs, enter. 1634 if ((fNodeFlavors & B_DIRECTORY_NODE) == 0 1635 && selection->CountItems() == 1) { 1636 Model* model = selection->FirstItem()->TargetModel(); 1637 1638 if (model->IsDirectory() 1639 || (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE) 1640 && model->ResolveIfLink()->IsDirectory())) { 1641 1642 BMessage message(B_REFS_RECEIVED); 1643 message.AddRef("refs", model->EntryRef()); 1644 PostMessage(&message); 1645 return; 1646 } 1647 } 1648 1649 // don't do anything unless there are items selected 1650 // message->fMessage->message from here to end 1651 if (selection->CountItems()) { 1652 BMessage message(*fMessage); 1653 // go through selection and add appropriate items 1654 for (int32 index = 0; index < selection->CountItems(); index++) { 1655 Model* model = selection->ItemAt(index)->TargetModel(); 1656 1657 if (((fNodeFlavors & B_DIRECTORY_NODE) != 0 1658 && model->ResolveIfLink()->IsDirectory()) 1659 || ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink()) 1660 || ((fNodeFlavors & B_FILE_NODE) != 0 1661 && model->ResolveIfLink()->IsFile())) { 1662 message.AddRef("refs", model->EntryRef()); 1663 } 1664 } 1665 1666 OpenSelectionCommon(&message); 1667 } 1668 } 1669 1670 1671 void 1672 TFilePanel::SwitchDirMenuTo(const entry_ref* ref) 1673 { 1674 BEntry entry(ref); 1675 for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--) 1676 delete fDirMenu->RemoveItem(index); 1677 1678 fDirMenuField->MenuBar()->RemoveItem((int32)0); 1679 fDirMenu->Populate(&entry, 0, true, true, false, true); 1680 1681 ModelMenuItem* item = dynamic_cast<ModelMenuItem*>( 1682 fDirMenuField->MenuBar()->ItemAt(0)); 1683 ASSERT(item != NULL); 1684 1685 if (item != NULL) 1686 item->SetEntry(&entry); 1687 } 1688 1689 1690 void 1691 TFilePanel::WindowActivated(bool active) 1692 { 1693 // force focus to update properly 1694 fBackView->Invalidate(); 1695 _inherited::WindowActivated(active); 1696 } 1697 1698 1699 // #pragma mark - 1700 1701 1702 BFilePanelPoseView::BFilePanelPoseView(Model* model) 1703 : 1704 BPoseView(model, kListMode), 1705 fIsDesktop(model->IsDesktop()) 1706 { 1707 } 1708 1709 1710 void 1711 BFilePanelPoseView::StartWatching() 1712 { 1713 TTracker::WatchNode(0, B_WATCH_MOUNT, this); 1714 1715 // inter-application observing 1716 BMessenger tracker(kTrackerSignature); 1717 BHandler::StartWatching(tracker, kVolumesOnDesktopChanged); 1718 } 1719 1720 1721 void 1722 BFilePanelPoseView::StopWatching() 1723 { 1724 stop_watching(this); 1725 1726 // inter-application observing 1727 BMessenger tracker(kTrackerSignature); 1728 BHandler::StopWatching(tracker, kVolumesOnDesktopChanged); 1729 } 1730 1731 1732 bool 1733 BFilePanelPoseView::FSNotification(const BMessage* message) 1734 { 1735 if (IsDesktopView()) { 1736 // Pretty much copied straight from DesktopPoseView. 1737 // Would be better if the code could be shared somehow. 1738 switch (message->FindInt32("opcode")) { 1739 case B_DEVICE_MOUNTED: 1740 { 1741 dev_t device; 1742 if (message->FindInt32("new device", &device) != B_OK) 1743 break; 1744 1745 ASSERT(TargetModel() != NULL); 1746 TrackerSettings settings; 1747 1748 BVolume volume(device); 1749 if (volume.InitCheck() != B_OK) 1750 break; 1751 1752 if (settings.MountVolumesOntoDesktop() 1753 && (!volume.IsShared() 1754 || settings.MountSharedVolumesOntoDesktop())) { 1755 // place an icon for the volume onto the desktop 1756 CreateVolumePose(&volume, true); 1757 } 1758 } 1759 break; 1760 } 1761 } 1762 return _inherited::FSNotification(message); 1763 } 1764 1765 1766 void 1767 BFilePanelPoseView::RestoreState(AttributeStreamNode* node) 1768 { 1769 _inherited::RestoreState(node); 1770 fViewState->SetViewMode(kListMode); 1771 } 1772 1773 1774 void 1775 BFilePanelPoseView::RestoreState(const BMessage &message) 1776 { 1777 _inherited::RestoreState(message); 1778 } 1779 1780 1781 void 1782 BFilePanelPoseView::SavePoseLocations(BRect*) 1783 { 1784 } 1785 1786 1787 EntryListBase* 1788 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref) 1789 { 1790 if (IsDesktopView()) 1791 return DesktopPoseView::InitDesktopDirentIterator(this, ref); 1792 1793 return _inherited::InitDirentIterator(ref); 1794 } 1795 1796 1797 void 1798 BFilePanelPoseView::AddPosesCompleted() 1799 { 1800 _inherited::AddPosesCompleted(); 1801 if (IsDesktopView()) 1802 CreateTrashPose(); 1803 } 1804 1805 1806 void 1807 BFilePanelPoseView::SetIsDesktop(bool on) 1808 { 1809 fIsDesktop = on; 1810 } 1811 1812 1813 bool 1814 BFilePanelPoseView::IsDesktopView() const 1815 { 1816 return fIsDesktop; 1817 } 1818 1819 1820 void 1821 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared) 1822 { 1823 if (IsDesktopView()) { 1824 if (!visible) 1825 RemoveRootPoses(); 1826 else 1827 AddRootPoses(true, showShared); 1828 } 1829 1830 TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window()); 1831 if (filepanel != NULL && TargetModel() != NULL) 1832 filepanel->SetTo(TargetModel()->EntryRef()); 1833 } 1834 1835 1836 void 1837 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message) 1838 { 1839 bool showDisksIcon; 1840 bool mountVolumesOnDesktop; 1841 bool mountSharedVolumesOntoDesktop; 1842 1843 message->FindBool("ShowDisksIcon", &showDisksIcon); 1844 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1845 message->FindBool("MountSharedVolumesOntoDesktop", 1846 &mountSharedVolumesOntoDesktop); 1847 1848 BEntry entry("/"); 1849 Model model(&entry); 1850 if (model.InitCheck() == B_OK) { 1851 BMessage monitorMsg; 1852 monitorMsg.what = B_NODE_MONITOR; 1853 1854 if (showDisksIcon) 1855 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1856 else 1857 monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED); 1858 1859 monitorMsg.AddInt32("device", model.NodeRef()->device); 1860 monitorMsg.AddInt64("node", model.NodeRef()->node); 1861 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1862 monitorMsg.AddString("name", model.EntryRef()->name); 1863 TrackerSettings().SetShowDisksIcon(showDisksIcon); 1864 if (Window()) 1865 Window()->PostMessage(&monitorMsg, this); 1866 } 1867 1868 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1869 } 1870 1871 1872 void 1873 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message) 1874 { 1875 bool mountVolumesOnDesktop = true; 1876 bool mountSharedVolumesOntoDesktop = true; 1877 1878 message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); 1879 message->FindBool("MountSharedVolumesOntoDesktop", 1880 &mountSharedVolumesOntoDesktop); 1881 1882 ShowVolumes(false, mountSharedVolumesOntoDesktop); 1883 ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); 1884 } 1885