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