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