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