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