1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All 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 "Attributes.h"
68#include "AttributeStream.h"
69#include "AutoLock.h"
70#include "Commands.h"
71#include "CountView.h"
72#include "DesktopPoseView.h"
73#include "DirMenu.h"
74#include "FavoritesMenu.h"
75#include "FSUtils.h"
76#include "FSClipboard.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
90const char* kDefaultFilePanelTemplate = "FilePanelSettings";
91
92
93static uint32
94GetLinkFlavor(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
111static filter_result
112key_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
169TFilePanel::TFilePanel(file_panel_mode mode, BMessenger* target,
170	const BEntry* startDir, uint32 nodeFlavors, bool multipleSelection,
171	BMessage* message, BRefFilter* filter, uint32 openFlags, window_look look,
172	window_feel feel, uint32 windowFlags, uint32 workspace, 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
285TFilePanel::~TFilePanel()
286{
287	BMessenger tracker(kTrackerSignature);
288	BHandler::StopWatching(tracker, kDesktopFilePanelRootChanged);
289
290	delete fMessage;
291}
292
293
294filter_result
295TFilePanel::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
377filter_result
378TFilePanel::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
452void
453TFilePanel::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
461BFilePanelPoseView*
462TFilePanel::PoseView() const
463{
464	ASSERT(dynamic_cast<BFilePanelPoseView*>(fPoseView) != NULL);
465
466	return static_cast<BFilePanelPoseView*>(fPoseView);
467}
468
469
470bool
471TFilePanel::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
497BRefFilter*
498TFilePanel::Filter() const
499{
500	return fPoseView->RefFilter();
501}
502
503
504void
505TFilePanel::SetTarget(BMessenger target)
506{
507	fTarget = target;
508}
509
510
511void
512TFilePanel::SetMessage(BMessage* message)
513{
514	delete fMessage;
515	fMessage = new BMessage(*message);
516}
517
518
519void
520TFilePanel::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
544void
545TFilePanel::SetTo(const entry_ref* ref)
546{
547	if (ref == NULL)
548		return;
549
550	entry_ref setToRef(*ref);
551
552	bool isDesktop = SwitchDirToDesktopIfNeeded(setToRef);
553
554	BEntry entry(&setToRef);
555	if (entry.InitCheck() != B_OK || !entry.IsDirectory())
556		return;
557
558	PoseView()->SetIsDesktop(isDesktop);
559	PoseView()->SwitchDir(&setToRef);
560	SwitchDirMenuTo(&setToRef);
561
562	AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome));
563		// our shortcut got possibly removed because the home
564		// menu item got removed - we shouldn't really have to do
565		// this - this is a workaround for a kit bug.
566}
567
568
569void
570TFilePanel::Rewind()
571{
572	fSelectionIterator = 0;
573}
574
575
576void
577TFilePanel::SetClientObject(BFilePanel* panel)
578{
579	fClientObject = panel;
580}
581
582
583void
584TFilePanel::AdjustButton()
585{
586	// adjust button state
587	BButton* button = dynamic_cast<BButton*>(FindView("default button"));
588	if (button == NULL)
589		return;
590
591	BTextControl* textControl
592		= dynamic_cast<BTextControl*>(FindView("text view"));
593	BObjectList<BPose>* selectionList = fPoseView->SelectionList();
594	BString buttonText = fButtonText;
595	bool enabled = false;
596
597	if (fIsSavePanel && textControl != NULL) {
598		enabled = textControl->Text()[0] != '\0';
599		if (fPoseView->IsFocus()) {
600			fPoseView->ShowSelection(true);
601			if (selectionList->CountItems() == 1) {
602				Model* model = selectionList->FirstItem()->TargetModel();
603				if (model->ResolveIfLink()->IsDirectory()) {
604					enabled = true;
605					buttonText = B_TRANSLATE("Open");
606				} else {
607					// insert the name of the selected model into
608					// the text field, do not alter focus
609					textControl->SetText(model->Name());
610				}
611			}
612		} else
613			fPoseView->ShowSelection(false);
614	} else {
615		int32 count = selectionList->CountItems();
616		if (count) {
617			enabled = true;
618
619			// go through selection list looking at content
620			for (int32 index = 0; index < count; index++) {
621				Model* model = selectionList->ItemAt(index)->TargetModel();
622
623				uint32 modelFlavor = GetLinkFlavor(model, false);
624				uint32 linkFlavor = GetLinkFlavor(model, true);
625
626				// if only one item is selected and we're not in dir
627				// selection mode then we don't disable button ever
628				if ((modelFlavor == B_DIRECTORY_NODE
629						|| linkFlavor == B_DIRECTORY_NODE)
630					&& count == 1) {
631					break;
632				}
633
634				if ((fNodeFlavors & modelFlavor) == 0
635					&& (fNodeFlavors & linkFlavor) == 0) {
636					enabled = false;
637					break;
638				}
639			}
640		} else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) {
641			// No selection, but the current directory could be opened.
642			enabled = true;
643		}
644	}
645
646	button->SetLabel(buttonText.String());
647	button->SetEnabled(enabled);
648}
649
650
651void
652TFilePanel::SelectionChanged()
653{
654	AdjustButton();
655
656	if (fClientObject)
657		fClientObject->SelectionChanged();
658}
659
660
661status_t
662TFilePanel::GetNextEntryRef(entry_ref* ref)
663{
664	if (!ref)
665		return B_ERROR;
666
667	BPose* pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++);
668	if (!pose)
669		return B_ERROR;
670
671	*ref = *pose->TargetModel()->EntryRef();
672	return B_OK;
673}
674
675
676BPoseView*
677TFilePanel::NewPoseView(Model* model, uint32)
678{
679	return new BFilePanelPoseView(model);
680}
681
682
683void
684TFilePanel::Init(const BMessage*)
685{
686	BRect windRect(Bounds());
687	fBackView = new BView(Bounds(), "View", B_FOLLOW_ALL, 0);
688	fBackView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
689	AddChild(fBackView);
690
691	// add poseview menu bar
692	fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar");
693	fMenuBar->SetBorder(B_BORDER_FRAME);
694	fBackView->AddChild(fMenuBar);
695
696	// add directory menu and menufield
697	font_height ht;
698	be_plain_font->GetHeight(&ht);
699	const float f_height = ht.ascent + ht.descent + ht.leading;
700	const float spacing = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING);
701
702	BRect rect;
703	rect.top = fMenuBar->Bounds().Height() + spacing;
704	rect.left = spacing;
705	rect.right = rect.left + (spacing * 50);
706	rect.bottom = rect.top + (f_height > 22 ? f_height : 22);
707
708	fDirMenuField = new BMenuField(rect, "DirMenuField", "", NULL);
709	fDirMenuField->MenuBar()->SetFont(be_plain_font);
710	fDirMenuField->SetDivider(0);
711	fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f);
712		// Make room for the icon
713
714	fDirMenu = new BDirMenu(fDirMenuField->MenuBar(),
715		this, kSwitchDirectory, "refs");
716
717	BEntry entry(TargetModel()->EntryRef());
718	if (entry.InitCheck() == B_OK)
719		fDirMenu->Populate(&entry, 0, true, true, false, true);
720	else
721		fDirMenu->Populate(0, 0, true, true, false, true);
722
723	fBackView->AddChild(fDirMenuField);
724
725	// add buttons
726	fButtonText = fIsSavePanel ? B_TRANSLATE("Save") : B_TRANSLATE("Open");
727	BButton* default_button = new BButton(BRect(), "default button",
728		fButtonText.String(), new BMessage(kDefaultButton),
729		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
730	BSize preferred = default_button->PreferredSize();
731	const BRect defaultButtonRect = BRect(BPoint(
732		windRect.Width() - (preferred.Width() + spacing + be_control_look->GetScrollBarWidth()),
733		windRect.Height() - (preferred.Height() + spacing)),
734		preferred);
735	default_button->MoveTo(defaultButtonRect.LeftTop());
736	default_button->ResizeTo(preferred);
737	fBackView->AddChild(default_button);
738
739	BButton* cancel_button = new BButton(BRect(), "cancel button",
740		B_TRANSLATE("Cancel"), new BMessage(kCancelButton),
741		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
742	preferred = cancel_button->PreferredSize();
743	cancel_button->MoveTo(defaultButtonRect.LeftTop()
744		- BPoint(preferred.Width() + spacing, 0));
745	cancel_button->ResizeTo(preferred);
746	fBackView->AddChild(cancel_button);
747
748	// add file name text view
749	if (fIsSavePanel) {
750		BRect rect(defaultButtonRect);
751		rect.left = spacing;
752		rect.right = rect.left + spacing * 28;
753
754		fTextControl = new BTextControl(rect, "text view",
755			B_TRANSLATE("save text"), "", NULL,
756			B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
757		DisallowMetaKeys(fTextControl->TextView());
758		DisallowFilenameKeys(fTextControl->TextView());
759		fBackView->AddChild(fTextControl);
760		fTextControl->SetDivider(0.0f);
761		fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
762	}
763
764	// Add PoseView
765	PoseView()->SetName("ActualPoseView");
766	fPoseContainer->SetName("PoseView");
767	fPoseContainer->SetResizingMode(B_FOLLOW_ALL);
768	fBorderedView->EnableBorderHighlight(true);
769
770	rect.left = spacing;
771	rect.top = fDirMenuField->Frame().bottom + spacing;
772	rect.right = windRect.Width() - spacing;
773	rect.bottom = defaultButtonRect.top - spacing;
774	fPoseContainer->MoveTo(rect.LeftTop());
775	fPoseContainer->ResizeTo(rect.Size());
776
777	PoseView()->AddScrollBars();
778	PoseView()->SetDragEnabled(false);
779	PoseView()->SetDropEnabled(false);
780	PoseView()->SetSelectionHandler(this);
781	PoseView()->SetSelectionChangedHook(true);
782	PoseView()->DisableSaveLocation();
783
784	if (fIsSavePanel)
785		fBackView->AddChild(fPoseContainer, fTextControl);
786	else
787		fBackView->AddChild(fPoseContainer);
788
789	AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton));
790	AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome));
791	AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
792		new BMessage(kShowSelectionWindow));
793	AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), this);
794	AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection),
795		PoseView());
796	AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView());
797	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir));
798	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
799		new BMessage(kOpenDir));
800	AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir));
801	AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
802		new BMessage(kOpenParentDir));
803
804	if (!fIsSavePanel && (fNodeFlavors & B_DIRECTORY_NODE) == 0)
805		default_button->SetEnabled(false);
806
807	default_button->MakeDefault(true);
808
809	RestoreState();
810
811	AddMenus();
812	AddContextMenus();
813
814	FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"),
815		new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED),
816		BMessenger(this), IsSavePanel(), fPoseView->RefFilter());
817	favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"),
818		new BMessage(kAddCurrentDir)));
819	favorites->AddItem(new BMenuItem(
820		B_TRANSLATE("Edit favorites" B_UTF8_ELLIPSIS),
821		new BMessage(kEditFavorites)));
822
823	fMenuBar->AddItem(favorites);
824
825	// configure menus
826	BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window"));
827	if (item) {
828		fMenuBar->RemoveItem(item);
829		delete item;
830	}
831
832	item = fMenuBar->FindItem(B_TRANSLATE("File"));
833	if (item) {
834		BMenu* menu = item->Submenu();
835		if (menu) {
836			item = menu->FindItem(kOpenSelection);
837			if (item && menu->RemoveItem(item))
838				delete item;
839
840			// remove add-ons menu, identifier menu, separator
841			item = menu->FindItem(B_TRANSLATE("Add-ons"));
842			if (item) {
843				int32 index = menu->IndexOf(item);
844				delete menu->RemoveItem(index);
845				delete menu->RemoveItem(--index);
846				delete menu->RemoveItem(--index);
847			}
848
849			// remove separator
850			item = menu->FindItem(B_CUT);
851			if (item) {
852				item = menu->ItemAt(menu->IndexOf(item)-1);
853				if (item && menu->RemoveItem(item))
854					delete item;
855			}
856		}
857	}
858
859	PoseView()->ScrollTo(B_ORIGIN);
860	PoseView()->UpdateScrollRange();
861	PoseView()->ScrollTo(B_ORIGIN);
862
863	// Focus on text control initially, but do not alter focus afterwords
864	// because pose view focus is needed for Cut/Copy/Paste to work.
865
866	if (fIsSavePanel && fTextControl != NULL) {
867		fTextControl->MakeFocus();
868		fTextControl->TextView()->SelectAll();
869	} else
870		PoseView()->MakeFocus();
871
872	app_info info;
873	BString title;
874	if (be_app->GetAppInfo(&info) == B_OK) {
875		if (!gLocalizedNamePreferred
876			|| BLocaleRoster::Default()->GetLocalizedFileName(
877				title, info.ref, false) != B_OK)
878			title = info.ref.name;
879		title << ": ";
880	}
881	title << fButtonText;	// Open or Save
882
883	SetTitle(title.String());
884
885	SetSizeLimits(spacing * 60, 10000, spacing * 33, 10000);
886}
887
888
889void
890TFilePanel::RestoreState()
891{
892	BNode defaultingNode;
893	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
894			false)) {
895		AttributeStreamFileNode streamNodeSource(&defaultingNode);
896		RestoreWindowState(&streamNodeSource);
897		PoseView()->Init(&streamNodeSource);
898		fDefaultStateRestored = true;
899	} else {
900		RestoreWindowState(NULL);
901		PoseView()->Init(NULL);
902		fDefaultStateRestored = false;
903	}
904
905	// Finish UI creation now that the PoseView is initialized
906	InitLayout();
907}
908
909
910void
911TFilePanel::SaveState(bool)
912{
913	BNode defaultingNode;
914	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
915		true, false)) {
916		AttributeStreamFileNode streamNodeDestination(&defaultingNode);
917		SaveWindowState(&streamNodeDestination);
918		PoseView()->SaveState(&streamNodeDestination);
919		fStateNeedsSaving = false;
920	}
921}
922
923
924void
925TFilePanel::SaveState(BMessage &message) const
926{
927	_inherited::SaveState(message);
928}
929
930
931void
932TFilePanel::RestoreWindowState(AttributeStreamNode* node)
933{
934	SetSizeLimits(360, 10000, 200, 10000);
935	if (!node)
936		return;
937
938	const char* rectAttributeName = kAttrWindowFrame;
939	BRect frame(Frame());
940	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
941		== sizeof(BRect)) {
942		MoveTo(frame.LeftTop());
943		ResizeTo(frame.Width(), frame.Height());
944	}
945	fStateNeedsSaving = false;
946}
947
948
949void
950TFilePanel::RestoreState(const BMessage &message)
951{
952	_inherited::RestoreState(message);
953}
954
955
956void
957TFilePanel::RestoreWindowState(const BMessage &message)
958{
959	_inherited::RestoreWindowState(message);
960}
961
962
963void
964TFilePanel::AddFileContextMenus(BMenu* menu)
965{
966	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
967		new BMessage(kGetInfo), 'I'));
968	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
969		new BMessage(kEditItem), 'E'));
970	menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"),
971		new BMessage(kDuplicateSelection), 'D'));
972	menu->AddItem(new BMenuItem(B_TRANSLATE("Move to Trash"),
973		new BMessage(kMoveToTrash), 'T'));
974	menu->AddSeparatorItem();
975
976	BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
977		new BMessage(B_CUT), 'X');
978	menu->AddItem(cutItem);
979	BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
980		new BMessage(B_COPY), 'C');
981	menu->AddItem(copyItem);
982#if CUT_COPY_PASTE_IN_CONTEXT_MENU
983	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
984		new BMessage(B_PASTE), 'V');
985	menu->AddItem(pasteItem);
986#endif
987
988	menu->SetTargetForItems(PoseView());
989	cutItem->SetTarget(this);
990	copyItem->SetTarget(this);
991#if CUT_COPY_PASTE_IN_CONTEXT_MENU
992	pasteItem->SetTarget(this);
993#endif
994}
995
996
997void
998TFilePanel::AddVolumeContextMenus(BMenu* menu)
999{
1000	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
1001		new BMessage(kOpenSelection), 'O'));
1002	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
1003		new BMessage(kGetInfo), 'I'));
1004	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
1005		new BMessage(kEditItem), 'E'));
1006
1007#if CUT_COPY_PASTE_IN_CONTEXT_MENU
1008	menu->AddSeparatorItem();
1009	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
1010		new BMessage(B_PASTE), 'V');
1011#endif
1012
1013	menu->SetTargetForItems(PoseView());
1014#if CUT_COPY_PASTE_IN_CONTEXT_MENU
1015	pasteItem->SetTarget(this);
1016#endif
1017}
1018
1019
1020void
1021TFilePanel::AddWindowContextMenus(BMenu* menu)
1022{
1023	BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
1024		new BMessage(kNewFolder), 'N');
1025	item->SetTarget(PoseView());
1026	menu->AddItem(item);
1027	menu->AddSeparatorItem();
1028
1029#if CUT_COPY_PASTE_IN_CONTEXT_MENU
1030	item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
1031	item->SetTarget(this);
1032	menu->AddItem(item);
1033	menu->AddSeparatorItem();
1034#endif
1035
1036	item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
1037		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
1038	item->SetTarget(PoseView());
1039	menu->AddItem(item);
1040
1041	item = new BMenuItem(B_TRANSLATE("Select all"),
1042		new BMessage(B_SELECT_ALL), 'A');
1043	item->SetTarget(this);
1044	menu->AddItem(item);
1045
1046	item = new BMenuItem(B_TRANSLATE("Invert selection"),
1047		new BMessage(kInvertSelection), 'S');
1048	item->SetTarget(PoseView());
1049	menu->AddItem(item);
1050
1051	item = new BMenuItem(B_TRANSLATE("Go to parent"),
1052		new BMessage(kOpenParentDir), B_UP_ARROW);
1053	item->SetTarget(this);
1054	menu->AddItem(item);
1055}
1056
1057
1058void
1059TFilePanel::AddDropContextMenus(BMenu*)
1060{
1061}
1062
1063
1064void
1065TFilePanel::MenusBeginning()
1066{
1067	if (fMenuBar == NULL)
1068		return;
1069
1070	if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
1071		// don't commit active pose if only a keyboard shortcut is
1072		// invoked - this would prevent Cut/Copy/Paste from working
1073		PoseView()->CommitActivePose();
1074	}
1075
1076	EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()
1077		&& !PoseView()->TargetVolumeIsReadOnly());
1078	EnableNamedMenuItem(fMenuBar, kDuplicateSelection,
1079		PoseView()->CanMoveToTrashOrDuplicate());
1080	EnableNamedMenuItem(fMenuBar, kMoveToTrash,
1081		PoseView()->CanMoveToTrashOrDuplicate());
1082	EnableNamedMenuItem(fMenuBar, kEditItem, PoseView()->CanEditName());
1083
1084	SetCutItem(fMenuBar);
1085	SetCopyItem(fMenuBar);
1086	SetPasteItem(fMenuBar);
1087
1088	fIsTrackingMenu = true;
1089}
1090
1091
1092void
1093TFilePanel::MenusEnded()
1094{
1095	fIsTrackingMenu = false;
1096}
1097
1098
1099void
1100TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref)
1101{
1102	ASSERT(IsLocked());
1103	BPoint global(where);
1104	PoseView()->ConvertToScreen(&global);
1105	PoseView()->CommitActivePose();
1106
1107	if (ref != NULL) {
1108		// clicked on a pose, show file or volume context menu
1109		Model model(ref);
1110		if (model.InitCheck() != B_OK)
1111			return; // bail out, do not show context menu
1112
1113		if (TargetModel()->IsRoot() || model.IsVolume()) {
1114			// Volume context menu
1115			fContextMenu = fVolumeContextMenu;
1116			EnableNamedMenuItem(fContextMenu, kOpenSelection, true);
1117			EnableNamedMenuItem(fContextMenu, kEditItem,
1118				PoseView()->CanEditName());
1119
1120			SetPasteItem(fContextMenu);
1121		} else {
1122			// File context menu
1123			fContextMenu = fFileContextMenu;
1124			EnableNamedMenuItem(fContextMenu, kEditItem,
1125				PoseView()->CanEditName());
1126			EnableNamedMenuItem(fContextMenu, kDuplicateSelection,
1127				PoseView()->CanMoveToTrashOrDuplicate());
1128			EnableNamedMenuItem(fContextMenu, kMoveToTrash,
1129				PoseView()->CanMoveToTrashOrDuplicate());
1130
1131			SetCutItem(fContextMenu);
1132			SetCopyItem(fContextMenu);
1133			SetPasteItem(fContextMenu);
1134		}
1135	} else {
1136		// Window context menu
1137		fContextMenu = fWindowContextMenu;
1138		EnableNamedMenuItem(fContextMenu, kNewFolder,
1139			!TargetModel()->IsRoot()
1140				&& !PoseView()->TargetVolumeIsReadOnly());
1141		EnableNamedMenuItem(fContextMenu, kOpenParentDir,
1142			!TargetModel()->IsRoot());
1143
1144		SetPasteItem(fContextMenu);
1145	}
1146
1147	// context menu invalid or popup window is already open
1148	if (fContextMenu == NULL || fContextMenu->Window() != NULL)
1149		return;
1150
1151	fContextMenu->Go(global, true, true, true);
1152	fContextMenu = NULL;
1153}
1154
1155
1156void
1157TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*)
1158{
1159	// do nothing here so nav menu doesn't get added
1160}
1161
1162
1163void
1164TFilePanel::SetButtonLabel(file_panel_button selector, const char* text)
1165{
1166	switch (selector) {
1167		case B_CANCEL_BUTTON:
1168			{
1169				BButton* button
1170					= dynamic_cast<BButton*>(FindView("cancel button"));
1171				if (button == NULL)
1172					break;
1173
1174				float old_width = button->StringWidth(button->Label());
1175				button->SetLabel(text);
1176				float delta = old_width - button->StringWidth(text);
1177				if (delta) {
1178					button->MoveBy(delta, 0);
1179					button->ResizeBy(-delta, 0);
1180				}
1181			}
1182			break;
1183
1184		case B_DEFAULT_BUTTON:
1185			{
1186				fButtonText = text;
1187				float delta = 0;
1188				BButton* button
1189					= dynamic_cast<BButton*>(FindView("default button"));
1190				if (button != NULL) {
1191					float old_width = button->StringWidth(button->Label());
1192					button->SetLabel(text);
1193					delta = old_width - button->StringWidth(text);
1194					if (delta) {
1195						button->MoveBy(delta, 0);
1196						button->ResizeBy(-delta, 0);
1197					}
1198				}
1199
1200				// now must move cancel button
1201				button = dynamic_cast<BButton*>(FindView("cancel button"));
1202				if (button != NULL)
1203					button->MoveBy(delta, 0);
1204			}
1205			break;
1206	}
1207}
1208
1209
1210void
1211TFilePanel::SetSaveText(const char* text)
1212{
1213	if (text == NULL)
1214		return;
1215
1216	BTextControl* textControl
1217		= dynamic_cast<BTextControl*>(FindView("text view"));
1218	if (textControl != NULL) {
1219		textControl->SetText(text);
1220		if (textControl->TextView() != NULL)
1221			textControl->TextView()->SelectAll();
1222	}
1223}
1224
1225
1226void
1227TFilePanel::MessageReceived(BMessage* message)
1228{
1229	entry_ref ref;
1230
1231	switch (message->what) {
1232		case B_REFS_RECEIVED:
1233			// item was double clicked in file panel (PoseView) or from the favorites menu
1234			if (message->FindRef("refs", &ref) == B_OK) {
1235				BEntry entry(&ref, true);
1236				if (entry.InitCheck() == B_OK) {
1237					// Double-click on dir or link-to-dir ALWAYS opens the
1238					// dir. If more than one dir is selected, the first is
1239					// entered.
1240					if (entry.IsDirectory()) {
1241						entry.GetRef(&ref);
1242						bool isDesktop = SwitchDirToDesktopIfNeeded(ref);
1243
1244						PoseView()->SetIsDesktop(isDesktop);
1245						entry.SetTo(&ref);
1246						PoseView()->SwitchDir(&ref);
1247						SwitchDirMenuTo(&ref);
1248					} else {
1249						// Otherwise, we have a file or a link to a file.
1250						// AdjustButton has already tested the flavor if it comes from the file
1251						// panel; all we have to do is see if the button is enabled.
1252						// In other cases, however, we can't rely on that. So first check for
1253						// TrackerViewToken in the message to see if it's coming from the pose view
1254						if (message->HasMessenger("TrackerViewToken")) {
1255							BButton* button = dynamic_cast<BButton*>(FindView("default button"));
1256							if (button == NULL || !button->IsEnabled())
1257								break;
1258						}
1259
1260						if (IsSavePanel()) {
1261							int32 count = 0;
1262							type_code type;
1263							message->GetInfo("refs", &type, &count);
1264
1265							// Don't allow saves of multiple files
1266							if (count > 1) {
1267								ShowCenteredAlert(
1268									B_TRANSLATE(
1269										"Sorry, saving more than one "
1270										"item is not allowed."),
1271									B_TRANSLATE("Cancel"));
1272							} else {
1273								// if we are a savepanel, set up the
1274								// filepanel correctly then pass control
1275								// so we follow the same path as if the user
1276								// clicked the save button
1277
1278								// set the 'name' fld to the current ref's
1279								// name notify the panel that the default
1280								// button should be enabled
1281								SetSaveText(ref.name);
1282								SelectionChanged();
1283
1284								HandleSaveButton();
1285							}
1286							break;
1287						}
1288
1289						// send handler a message and close
1290						BMessage openMessage(*fMessage);
1291						for (int32 index = 0; ; index++) {
1292							if (message->FindRef("refs", index, &ref) != B_OK)
1293								break;
1294							openMessage.AddRef("refs", &ref);
1295						}
1296						OpenSelectionCommon(&openMessage);
1297					}
1298				}
1299			}
1300			break;
1301
1302		case kSwitchDirectory:
1303		{
1304			entry_ref ref;
1305			// this comes from dir menu or nav menu, so switch directories
1306			if (message->FindRef("refs", &ref) == B_OK) {
1307				BEntry entry(&ref, true);
1308				if (entry.GetRef(&ref) == B_OK)
1309					SetTo(&ref);
1310			}
1311			break;
1312		}
1313
1314		case kSwitchToHome:
1315		{
1316			BPath homePath;
1317			entry_ref ref;
1318			if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK
1319				|| get_ref_for_path(homePath.Path(), &ref) != B_OK) {
1320				break;
1321			}
1322
1323			SetTo(&ref);
1324			break;
1325		}
1326
1327		case kAddCurrentDir:
1328		{
1329			BPath path;
1330			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)
1331					!= B_OK) {
1332				break;
1333			}
1334
1335			path.Append(kGoDirectory);
1336			BDirectory goDirectory(path.Path());
1337
1338			if (goDirectory.InitCheck() == B_OK) {
1339				BEntry entry(TargetModel()->EntryRef());
1340				entry.GetPath(&path);
1341
1342				BSymLink link;
1343				goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(),
1344					&link);
1345			}
1346			break;
1347		}
1348
1349		case kEditFavorites:
1350		{
1351			BPath path;
1352			if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true)
1353					!= B_OK) {
1354				break;
1355			}
1356
1357			path.Append(kGoDirectory);
1358			BMessenger msgr(kTrackerSignature);
1359			if (msgr.IsValid()) {
1360				BMessage message(B_REFS_RECEIVED);
1361				entry_ref ref;
1362				if (get_ref_for_path(path.Path(), &ref) == B_OK) {
1363					message.AddRef("refs", &ref);
1364					msgr.SendMessage(&message);
1365				}
1366			}
1367			break;
1368		}
1369
1370		case kCancelButton:
1371			PostMessage(B_QUIT_REQUESTED);
1372			break;
1373
1374		case kResizeToFit:
1375			ResizeToFit();
1376			break;
1377
1378		case kOpenDir:
1379			OpenDirectory();
1380			break;
1381
1382		case kOpenParentDir:
1383			OpenParent();
1384			break;
1385
1386		case kDefaultButton:
1387			if (fIsSavePanel) {
1388				if (PoseView()->IsFocus()
1389					&& PoseView()->CountSelected() == 1) {
1390					Model* model = (PoseView()->SelectionList()->
1391						FirstItem())->TargetModel();
1392					if (model->ResolveIfLink()->IsDirectory()) {
1393						PoseView()->CommitActivePose();
1394						PoseView()->OpenSelection();
1395						break;
1396					}
1397				}
1398
1399				HandleSaveButton();
1400			} else
1401				HandleOpenButton();
1402			break;
1403
1404		case B_OBSERVER_NOTICE_CHANGE:
1405		{
1406			int32 observerWhat;
1407			if (message->FindInt32("be:observe_change_what", &observerWhat)
1408					== B_OK) {
1409				switch (observerWhat) {
1410					case kDesktopFilePanelRootChanged:
1411					{
1412						bool desktopIsRoot = true;
1413						if (message->FindBool("DesktopFilePanelRoot",
1414								&desktopIsRoot) == B_OK) {
1415							TrackerSettings().
1416								SetDesktopFilePanelRoot(desktopIsRoot);
1417						}
1418						SetTo(TargetModel()->EntryRef());
1419						break;
1420					}
1421				}
1422			}
1423			break;
1424		}
1425
1426		default:
1427			_inherited::MessageReceived(message);
1428			break;
1429	}
1430}
1431
1432
1433void
1434TFilePanel::OpenDirectory()
1435{
1436	BObjectList<BPose>* list = PoseView()->SelectionList();
1437	if (list->CountItems() != 1)
1438		return;
1439
1440	Model* model = list->FirstItem()->TargetModel();
1441	if (model->ResolveIfLink()->IsDirectory()) {
1442		BMessage message(B_REFS_RECEIVED);
1443		message.AddRef("refs", model->EntryRef());
1444		PostMessage(&message);
1445	}
1446}
1447
1448
1449void
1450TFilePanel::OpenParent()
1451{
1452	if (!CanOpenParent())
1453		return;
1454
1455	BEntry parentEntry;
1456	BDirectory dir;
1457
1458	Model oldModel(*PoseView()->TargetModel());
1459	BEntry entry(oldModel.EntryRef());
1460
1461	if (entry.InitCheck() == B_OK
1462		&& entry.GetParent(&dir) == B_OK
1463		&& dir.GetEntry(&parentEntry) == B_OK
1464		&& entry != parentEntry) {
1465
1466		entry_ref ref;
1467		parentEntry.GetRef(&ref);
1468
1469		PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref));
1470		PoseView()->SwitchDir(&ref);
1471		SwitchDirMenuTo(&ref);
1472
1473		// Make sure the child gets selected in the new view
1474		// once it shows up.
1475		fTaskLoop->RunLater(NewMemberFunctionObjectWithResult
1476			(&TFilePanel::SelectChildInParent, this,
1477			const_cast<const entry_ref*>(&ref),
1478			oldModel.NodeRef()), 100000, 200000, 5000000);
1479	}
1480}
1481
1482
1483bool
1484TFilePanel::CanOpenParent() const
1485{
1486	if (TrackerSettings().DesktopFilePanelRoot()) {
1487		// don't allow opening Desktop folder's parent
1488		if (TargetModel()->IsDesktop())
1489			return false;
1490	}
1491
1492	// block on "/"
1493	BEntry root("/");
1494	node_ref rootRef;
1495	root.GetNodeRef(&rootRef);
1496
1497	return rootRef != *TargetModel()->NodeRef();
1498}
1499
1500
1501bool
1502TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref)
1503{
1504	// support showing Desktop as root of everything
1505	// This call implements the worm hole that maps Desktop as
1506	// a root above the disks
1507	TrackerSettings settings;
1508	if (!settings.DesktopFilePanelRoot())
1509		// Tracker isn't set up that way, just let Disks show
1510		return false;
1511
1512	BEntry entry(&ref);
1513	BEntry root("/");
1514
1515	BDirectory desktopDir;
1516	FSGetDeskDir(&desktopDir);
1517	if (FSIsDeskDir(&entry)
1518		// navigated into non-boot desktop, switch to boot desktop
1519		|| (entry == root && !settings.ShowDisksIcon())) {
1520		// hit "/" level, map to desktop
1521
1522		desktopDir.GetEntry(&entry);
1523		entry.GetRef(&ref);
1524		return true;
1525	}
1526	return FSIsDeskDir(&entry);
1527}
1528
1529
1530bool
1531TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child)
1532{
1533	AutoLock<TFilePanel> lock(this);
1534
1535	if (!IsLocked())
1536		return false;
1537
1538	int32 index;
1539	BPose* pose = PoseView()->FindPose(child, &index);
1540	if (!pose)
1541		return false;
1542
1543	PoseView()->UpdateScrollRange();
1544		// ToDo: Scroll range should be updated by now, for some
1545		//	reason sometimes it is not right, force it here
1546	PoseView()->SelectPose(pose, index, true);
1547	return true;
1548}
1549
1550
1551int32
1552TFilePanel::ShowCenteredAlert(const char* text, const char* button1,
1553	const char* button2, const char* button3)
1554{
1555	BAlert* alert = new BAlert("", text, button1, button2, button3,
1556		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1557	alert->MoveTo(Frame().left + 10, Frame().top + 10);
1558
1559#if 0
1560	if (button1 != NULL && !strncmp(button1, "Cancel", 7))
1561		alert->SetShortcut(0, B_ESCAPE);
1562	else if (button2 != NULL && !strncmp(button2, "Cancel", 7))
1563		alert->SetShortcut(1, B_ESCAPE);
1564	else if (button3 != NULL && !strncmp(button3, "Cancel", 7))
1565		alert->SetShortcut(2, B_ESCAPE);
1566#endif
1567
1568	return alert->Go();
1569}
1570
1571
1572void
1573TFilePanel::HandleSaveButton()
1574{
1575	BDirectory dir;
1576
1577	if (TargetModel()->IsRoot()) {
1578		ShowCenteredAlert(
1579			B_TRANSLATE("Sorry, you can't save things at the root of "
1580			"your system."),
1581			B_TRANSLATE("Cancel"));
1582		return;
1583	}
1584
1585	// check for some illegal file names
1586	if (strcmp(fTextControl->Text(), ".") == 0
1587		|| strcmp(fTextControl->Text(), "..") == 0) {
1588		ShowCenteredAlert(
1589			B_TRANSLATE("The specified name is illegal. Please choose "
1590			"another name."),
1591			B_TRANSLATE("Cancel"));
1592		fTextControl->TextView()->SelectAll();
1593		return;
1594	}
1595
1596	if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) {
1597		ShowCenteredAlert(
1598			B_TRANSLATE("There was a problem trying to save in the folder "
1599			"you specified. Please try another one."),
1600			B_TRANSLATE("Cancel"));
1601		return;
1602	}
1603
1604	if (dir.Contains(fTextControl->Text())) {
1605		if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) {
1606			ShowCenteredAlert(
1607				B_TRANSLATE("The specified name is already used as the name "
1608				"of a folder. Please choose another name."),
1609				B_TRANSLATE("Cancel"));
1610			fTextControl->TextView()->SelectAll();
1611			return;
1612		} else {
1613			// if this was invoked by a dbl click, it is an explicit
1614			// replacement of the file.
1615			BString str(B_TRANSLATE("The file \"%name\" already exists in "
1616				"the specified folder. Do you want to replace it?"));
1617			str.ReplaceFirst("%name", fTextControl->Text());
1618
1619			if (ShowCenteredAlert(str.String(),	B_TRANSLATE("Cancel"),
1620					B_TRANSLATE("Replace"))	== 0) {
1621				// user canceled
1622				fTextControl->TextView()->SelectAll();
1623				return;
1624			}
1625			// user selected "Replace" - let app deal with it
1626		}
1627	}
1628
1629	BMessage message(*fMessage);
1630	message.AddRef("directory", TargetModel()->EntryRef());
1631	message.AddString("name", fTextControl->Text());
1632
1633	if (fClientObject)
1634		fClientObject->SendMessage(&fTarget, &message);
1635	else
1636		fTarget.SendMessage(&message);
1637
1638	// close window if we're dealing with standard message
1639	if (fHideWhenDone)
1640		PostMessage(B_QUIT_REQUESTED);
1641}
1642
1643
1644void
1645TFilePanel::OpenSelectionCommon(BMessage* openMessage)
1646{
1647	if (!openMessage->HasRef("refs"))
1648		return;
1649
1650	for (int32 index = 0; ; index++) {
1651		entry_ref ref;
1652		if (openMessage->FindRef("refs", index, &ref) != B_OK)
1653			break;
1654
1655		BEntry entry(&ref, true);
1656		if (entry.InitCheck() == B_OK) {
1657			if (entry.IsDirectory())
1658				BRoster().AddToRecentFolders(&ref);
1659			else
1660				BRoster().AddToRecentDocuments(&ref);
1661		}
1662	}
1663
1664	BRoster().AddToRecentFolders(TargetModel()->EntryRef());
1665
1666	if (fClientObject)
1667		fClientObject->SendMessage(&fTarget, openMessage);
1668	else
1669		fTarget.SendMessage(openMessage);
1670
1671	// close window if we're dealing with standard message
1672	if (fHideWhenDone)
1673		PostMessage(B_QUIT_REQUESTED);
1674}
1675
1676
1677void
1678TFilePanel::HandleOpenButton()
1679{
1680	PoseView()->CommitActivePose();
1681	BObjectList<BPose>* selection = PoseView()->SelectionList();
1682
1683	// if we have only one directory and we're not opening dirs, enter.
1684	if ((fNodeFlavors & B_DIRECTORY_NODE) == 0
1685		&& selection->CountItems() == 1) {
1686		Model* model = selection->FirstItem()->TargetModel();
1687
1688		if (model->IsDirectory()
1689			|| (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE)
1690				&& model->ResolveIfLink()->IsDirectory())) {
1691
1692			BMessage message(B_REFS_RECEIVED);
1693			message.AddRef("refs", model->EntryRef());
1694			PostMessage(&message);
1695			return;
1696		}
1697	}
1698
1699	if (selection->CountItems()) {
1700			// there are items selected
1701			// message->fMessage->message from here to end
1702		BMessage message(*fMessage);
1703		// go through selection and add appropriate items
1704		for (int32 index = 0; index < selection->CountItems(); index++) {
1705			Model* model = selection->ItemAt(index)->TargetModel();
1706
1707			if (((fNodeFlavors & B_DIRECTORY_NODE) != 0
1708					&& model->ResolveIfLink()->IsDirectory())
1709				|| ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink())
1710				|| ((fNodeFlavors & B_FILE_NODE) != 0
1711					&& model->ResolveIfLink()->IsFile())) {
1712				message.AddRef("refs", model->EntryRef());
1713			}
1714		}
1715
1716		OpenSelectionCommon(&message);
1717	} else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) {
1718		// Open the current directory.
1719		BMessage message(*fMessage);
1720		message.AddRef("refs", TargetModel()->EntryRef());
1721		OpenSelectionCommon(&message);
1722	}
1723}
1724
1725
1726void
1727TFilePanel::SwitchDirMenuTo(const entry_ref* ref)
1728{
1729	BEntry entry(ref);
1730	for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--)
1731		delete fDirMenu->RemoveItem(index);
1732
1733	fDirMenuField->MenuBar()->RemoveItem((int32)0);
1734	fDirMenu->Populate(&entry, 0, true, true, false, true);
1735
1736	ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(
1737		fDirMenuField->MenuBar()->ItemAt(0));
1738	ASSERT(item != NULL);
1739
1740	if (item != NULL)
1741		item->SetEntry(&entry);
1742}
1743
1744
1745void
1746TFilePanel::WindowActivated(bool active)
1747{
1748	// force focus to update properly
1749	fBackView->Invalidate();
1750	_inherited::WindowActivated(active);
1751}
1752
1753
1754//	#pragma mark -
1755
1756
1757BFilePanelPoseView::BFilePanelPoseView(Model* model)
1758	:
1759	BPoseView(model, kListMode),
1760	fIsDesktop(model->IsDesktop())
1761{
1762}
1763
1764
1765void
1766BFilePanelPoseView::StartWatching()
1767{
1768	TTracker::WatchNode(0, B_WATCH_MOUNT, this);
1769
1770	// inter-application observing
1771	BMessenger tracker(kTrackerSignature);
1772	BHandler::StartWatching(tracker, kVolumesOnDesktopChanged);
1773}
1774
1775
1776void
1777BFilePanelPoseView::StopWatching()
1778{
1779	stop_watching(this);
1780
1781	// inter-application observing
1782	BMessenger tracker(kTrackerSignature);
1783	BHandler::StopWatching(tracker, kVolumesOnDesktopChanged);
1784}
1785
1786
1787bool
1788BFilePanelPoseView::FSNotification(const BMessage* message)
1789{
1790	switch (message->FindInt32("opcode")) {
1791		case B_DEVICE_MOUNTED:
1792		{
1793			if (IsDesktopView()) {
1794				// Pretty much copied straight from DesktopPoseView.
1795				// Would be better if the code could be shared somehow.
1796				dev_t device;
1797				if (message->FindInt32("new device", &device) != B_OK)
1798					break;
1799
1800				ASSERT(TargetModel() != NULL);
1801				TrackerSettings settings;
1802
1803				BVolume volume(device);
1804				if (volume.InitCheck() != B_OK)
1805					break;
1806
1807				if (settings.MountVolumesOntoDesktop()
1808					&& (!volume.IsShared()
1809						|| settings.MountSharedVolumesOntoDesktop())) {
1810					// place an icon for the volume onto the desktop
1811					CreateVolumePose(&volume, true);
1812				}
1813			}
1814			break;
1815		}
1816
1817		case B_DEVICE_UNMOUNTED:
1818		{
1819			dev_t device;
1820			if (message->FindInt32("device", &device) == B_OK) {
1821				if (TargetModel() != NULL
1822					&& TargetModel()->NodeRef()->device == device) {
1823					// Volume currently shown in this file panel
1824					// disappeared, reset location to home directory
1825					BMessage message(kSwitchToHome);
1826					MessageReceived(&message);
1827				}
1828			}
1829			break;
1830		}
1831	}
1832	return _inherited::FSNotification(message);
1833}
1834
1835
1836void
1837BFilePanelPoseView::RestoreState(AttributeStreamNode* node)
1838{
1839	_inherited::RestoreState(node);
1840	fViewState->SetViewMode(kListMode);
1841}
1842
1843
1844void
1845BFilePanelPoseView::RestoreState(const BMessage &message)
1846{
1847	_inherited::RestoreState(message);
1848}
1849
1850
1851void
1852BFilePanelPoseView::SavePoseLocations(BRect*)
1853{
1854}
1855
1856
1857EntryListBase*
1858BFilePanelPoseView::InitDirentIterator(const entry_ref* ref)
1859{
1860	if (IsDesktopView())
1861		return DesktopPoseView::InitDesktopDirentIterator(this, ref);
1862
1863	return _inherited::InitDirentIterator(ref);
1864}
1865
1866
1867void
1868BFilePanelPoseView::AddPosesCompleted()
1869{
1870	_inherited::AddPosesCompleted();
1871	if (IsDesktopView())
1872		CreateTrashPose();
1873}
1874
1875
1876void
1877BFilePanelPoseView::SetIsDesktop(bool on)
1878{
1879	fIsDesktop = on;
1880}
1881
1882
1883bool
1884BFilePanelPoseView::IsDesktopView() const
1885{
1886	return fIsDesktop;
1887}
1888
1889
1890void
1891BFilePanelPoseView::ShowVolumes(bool visible, bool showShared)
1892{
1893	if (IsDesktopView()) {
1894		if (!visible)
1895			RemoveRootPoses();
1896		else
1897			AddRootPoses(true, showShared);
1898	}
1899
1900	TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window());
1901	if (filepanel != NULL && TargetModel() != NULL)
1902		filepanel->SetTo(TargetModel()->EntryRef());
1903}
1904
1905
1906void
1907BFilePanelPoseView::AdaptToVolumeChange(BMessage* message)
1908{
1909	bool showDisksIcon;
1910	bool mountVolumesOnDesktop;
1911	bool mountSharedVolumesOntoDesktop;
1912
1913	message->FindBool("ShowDisksIcon", &showDisksIcon);
1914	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1915	message->FindBool("MountSharedVolumesOntoDesktop",
1916		&mountSharedVolumesOntoDesktop);
1917
1918	BEntry entry("/");
1919	Model model(&entry);
1920	if (model.InitCheck() == B_OK) {
1921		BMessage monitorMsg;
1922		monitorMsg.what = B_NODE_MONITOR;
1923
1924		if (showDisksIcon)
1925			monitorMsg.AddInt32("opcode", B_ENTRY_CREATED);
1926		else
1927			monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED);
1928
1929		monitorMsg.AddInt32("device", model.NodeRef()->device);
1930		monitorMsg.AddInt64("node", model.NodeRef()->node);
1931		monitorMsg.AddInt64("directory", model.EntryRef()->directory);
1932		monitorMsg.AddString("name", model.EntryRef()->name);
1933		TrackerSettings().SetShowDisksIcon(showDisksIcon);
1934		if (Window() != NULL)
1935			Window()->PostMessage(&monitorMsg, this);
1936	}
1937
1938	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1939}
1940
1941
1942void
1943BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message)
1944{
1945	bool mountVolumesOnDesktop = true;
1946	bool mountSharedVolumesOntoDesktop = true;
1947
1948	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1949	message->FindBool("MountSharedVolumesOntoDesktop",
1950		&mountSharedVolumesOntoDesktop);
1951
1952	ShowVolumes(false, mountSharedVolumesOntoDesktop);
1953	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1954}
1955