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