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