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