xref: /haiku/src/kits/tracker/FilePanelPriv.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 			item = menu->FindItem(kDuplicateSelection);
842 			if (item && menu->RemoveItem(item))
843 				delete item;
844 
845 			// remove add-ons menu, identifier menu, separator
846 			item = menu->FindItem(B_TRANSLATE("Add-ons"));
847 			if (item) {
848 				int32 index = menu->IndexOf(item);
849 				delete menu->RemoveItem(index);
850 				delete menu->RemoveItem(--index);
851 				delete menu->RemoveItem(--index);
852 			}
853 
854 			// remove separator
855 			item = menu->FindItem(B_CUT);
856 			if (item) {
857 				item = menu->ItemAt(menu->IndexOf(item)-1);
858 				if (item && menu->RemoveItem(item))
859 					delete item;
860 			}
861 		}
862 	}
863 
864 	PoseView()->ScrollTo(B_ORIGIN);
865 	PoseView()->UpdateScrollRange();
866 	PoseView()->ScrollTo(B_ORIGIN);
867 
868 	// Focus on text control initially, but do not alter focus afterwords
869 	// because pose view focus is needed for Cut/Copy/Paste to work.
870 
871 	if (fIsSavePanel && fTextControl != NULL) {
872 		fTextControl->MakeFocus();
873 		fTextControl->TextView()->SelectAll();
874 	} else
875 		PoseView()->MakeFocus();
876 
877 	app_info info;
878 	BString title;
879 	if (be_app->GetAppInfo(&info) == B_OK) {
880 		if (!gLocalizedNamePreferred
881 			|| BLocaleRoster::Default()->GetLocalizedFileName(
882 				title, info.ref, false) != B_OK)
883 			title = info.ref.name;
884 		title << ": ";
885 	}
886 	title << fButtonText;	// Open or Save
887 
888 	SetTitle(title.String());
889 
890 	SetSizeLimits(spacing * 60, 10000, spacing * 33, 10000);
891 }
892 
893 
894 void
895 TFilePanel::RestoreState()
896 {
897 	BNode defaultingNode;
898 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
899 			false)) {
900 		AttributeStreamFileNode streamNodeSource(&defaultingNode);
901 		RestoreWindowState(&streamNodeSource);
902 		PoseView()->Init(&streamNodeSource);
903 		fDefaultStateRestored = true;
904 	} else {
905 		RestoreWindowState(NULL);
906 		PoseView()->Init(NULL);
907 		fDefaultStateRestored = false;
908 	}
909 
910 	// Finish UI creation now that the PoseView is initialized
911 	InitLayout();
912 }
913 
914 
915 void
916 TFilePanel::SaveState(bool)
917 {
918 	BNode defaultingNode;
919 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
920 		true, false)) {
921 		AttributeStreamFileNode streamNodeDestination(&defaultingNode);
922 		SaveWindowState(&streamNodeDestination);
923 		PoseView()->SaveState(&streamNodeDestination);
924 		fStateNeedsSaving = false;
925 	}
926 }
927 
928 
929 void
930 TFilePanel::SaveState(BMessage &message) const
931 {
932 	_inherited::SaveState(message);
933 }
934 
935 
936 void
937 TFilePanel::RestoreWindowState(AttributeStreamNode* node)
938 {
939 	SetSizeLimits(360, 10000, 200, 10000);
940 	if (!node)
941 		return;
942 
943 	const char* rectAttributeName = kAttrWindowFrame;
944 	BRect frame(Frame());
945 	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
946 		== sizeof(BRect)) {
947 		MoveTo(frame.LeftTop());
948 		ResizeTo(frame.Width(), frame.Height());
949 	}
950 	fStateNeedsSaving = false;
951 }
952 
953 
954 void
955 TFilePanel::RestoreState(const BMessage &message)
956 {
957 	_inherited::RestoreState(message);
958 }
959 
960 
961 void
962 TFilePanel::RestoreWindowState(const BMessage &message)
963 {
964 	_inherited::RestoreWindowState(message);
965 }
966 
967 
968 void
969 TFilePanel::AddFileContextMenus(BMenu* menu)
970 {
971 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
972 		new BMessage(kGetInfo), 'I'));
973 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
974 		new BMessage(kEditItem), 'E'));
975 
976 	menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash()
977 		? B_TRANSLATE("Delete")
978 		: B_TRANSLATE("Move to Trash"),
979 		new BMessage(kMoveToTrash), 'T'));
980 	menu->AddSeparatorItem();
981 
982 	BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
983 		new BMessage(B_CUT), 'X');
984 	menu->AddItem(cutItem);
985 	BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
986 		new BMessage(B_COPY), 'C');
987 	menu->AddItem(copyItem);
988 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
989 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
990 		new BMessage(B_PASTE), 'V');
991 	menu->AddItem(pasteItem);
992 #endif
993 
994 	menu->SetTargetForItems(PoseView());
995 	cutItem->SetTarget(this);
996 	copyItem->SetTarget(this);
997 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
998 	pasteItem->SetTarget(this);
999 #endif
1000 }
1001 
1002 
1003 void
1004 TFilePanel::AddVolumeContextMenus(BMenu* menu)
1005 {
1006 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
1007 		new BMessage(kOpenSelection), 'O'));
1008 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
1009 		new BMessage(kGetInfo), 'I'));
1010 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
1011 		new BMessage(kEditItem), 'E'));
1012 	menu->AddSeparatorItem();
1013 
1014 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1015 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
1016 		new BMessage(B_PASTE), 'V');
1017 #endif
1018 
1019 	menu->SetTargetForItems(PoseView());
1020 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1021 	pasteItem->SetTarget(this);
1022 #endif
1023 }
1024 
1025 
1026 void
1027 TFilePanel::AddWindowContextMenus(BMenu* menu)
1028 {
1029 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
1030 		new BMessage(kNewFolder), 'N');
1031 	item->SetTarget(PoseView());
1032 	menu->AddItem(item);
1033 	menu->AddSeparatorItem();
1034 
1035 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1036 	item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
1037 	item->SetTarget(this);
1038 	menu->AddItem(item);
1039 	menu->AddSeparatorItem();
1040 #endif
1041 
1042 	item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
1043 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
1044 	item->SetTarget(PoseView());
1045 	menu->AddItem(item);
1046 
1047 	item = new BMenuItem(B_TRANSLATE("Select all"),
1048 		new BMessage(B_SELECT_ALL), 'A');
1049 	item->SetTarget(this);
1050 	menu->AddItem(item);
1051 
1052 	item = new BMenuItem(B_TRANSLATE("Invert selection"),
1053 		new BMessage(kInvertSelection), 'S');
1054 	item->SetTarget(PoseView());
1055 	menu->AddItem(item);
1056 
1057 	item = new BMenuItem(B_TRANSLATE("Go to parent"),
1058 		new BMessage(kOpenParentDir), B_UP_ARROW);
1059 	item->SetTarget(this);
1060 	menu->AddItem(item);
1061 }
1062 
1063 
1064 void
1065 TFilePanel::AddDropContextMenus(BMenu*)
1066 {
1067 }
1068 
1069 
1070 void
1071 TFilePanel::MenusBeginning()
1072 {
1073 	if (fMenuBar == NULL)
1074 		return;
1075 
1076 	if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
1077 		// don't commit active pose if only a keyboard shortcut is
1078 		// invoked - this would prevent Cut/Copy/Paste from working
1079 		PoseView()->CommitActivePose();
1080 	}
1081 
1082 	int32 selectCount = PoseView()->CountSelected();
1083 
1084 	EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()
1085 		&& !PoseView()->TargetVolumeIsReadOnly());
1086 	EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot()
1087 		&& selectCount > 0 && !PoseView()->SelectedVolumeIsReadOnly());
1088 	EnableNamedMenuItem(fMenuBar, kGetInfo, selectCount > 0);
1089 	EnableNamedMenuItem(fMenuBar, kEditItem, selectCount == 1);
1090 
1091 	SetCutItem(fMenuBar);
1092 	SetCopyItem(fMenuBar);
1093 	SetPasteItem(fMenuBar);
1094 
1095 	fIsTrackingMenu = true;
1096 }
1097 
1098 
1099 void
1100 TFilePanel::MenusEnded()
1101 {
1102 	fIsTrackingMenu = false;
1103 }
1104 
1105 
1106 void
1107 TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref)
1108 {
1109 	ASSERT(IsLocked());
1110 	BPoint global(where);
1111 	PoseView()->ConvertToScreen(&global);
1112 	PoseView()->CommitActivePose();
1113 
1114 	if (ref != NULL) {
1115 		// clicked on a pose, show file or volume context menu
1116 		Model model(ref);
1117 		if (model.InitCheck() != B_OK)
1118 			return; // bail out, do not show context menu
1119 
1120 		if (TargetModel()->IsRoot() || model.IsVolume()) {
1121 			// Volume context menu
1122 			fContextMenu = fVolumeContextMenu;
1123 			EnableNamedMenuItem(fContextMenu, kOpenSelection, true);
1124 			EnableNamedMenuItem(fContextMenu, kGetInfo, true);
1125 			EnableNamedMenuItem(fContextMenu, kEditItem, !(model.IsDesktop()
1126 				|| model.IsRoot() || model.IsTrash()));
1127 
1128 			SetPasteItem(fContextMenu);
1129 		} else {
1130 			// File context menu
1131 			fContextMenu = fFileContextMenu;
1132 			EnableNamedMenuItem(fContextMenu, kGetInfo, true);
1133 			EnableNamedMenuItem(fContextMenu, kEditItem, !(model.IsDesktop()
1134 				|| model.IsRoot() || model.IsTrash()));
1135 			EnableNamedMenuItem(fContextMenu, kMoveToTrash,
1136 				!PoseView()->SelectedVolumeIsReadOnly());
1137 
1138 			SetCutItem(fContextMenu);
1139 			SetCopyItem(fContextMenu);
1140 			SetPasteItem(fContextMenu);
1141 		}
1142 	} else {
1143 		// Window context menu
1144 		fContextMenu = fWindowContextMenu;
1145 		EnableNamedMenuItem(fContextMenu, kNewFolder,
1146 			!TargetModel()->IsRoot()
1147 				&& !PoseView()->TargetVolumeIsReadOnly());
1148 		EnableNamedMenuItem(fContextMenu, kOpenParentDir,
1149 			!TargetModel()->IsRoot());
1150 		EnableNamedMenuItem(fContextMenu, kMoveToTrash,
1151 			!TargetModel()->IsRoot() && PoseView()->CountSelected() > 0
1152 				&& !PoseView()->SelectedVolumeIsReadOnly());
1153 
1154 		SetPasteItem(fContextMenu);
1155 	}
1156 
1157 	// context menu invalid or popup window is already open
1158 	if (fContextMenu == NULL || fContextMenu->Window() != NULL)
1159 		return;
1160 
1161 	fContextMenu->Go(global, true, true, true);
1162 	fContextMenu = NULL;
1163 }
1164 
1165 
1166 void
1167 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*)
1168 {
1169 	// do nothing here so nav menu doesn't get added
1170 }
1171 
1172 
1173 void
1174 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text)
1175 {
1176 	switch (selector) {
1177 		case B_CANCEL_BUTTON:
1178 			{
1179 				BButton* button
1180 					= dynamic_cast<BButton*>(FindView("cancel button"));
1181 				if (button == NULL)
1182 					break;
1183 
1184 				float old_width = button->StringWidth(button->Label());
1185 				button->SetLabel(text);
1186 				float delta = old_width - button->StringWidth(text);
1187 				if (delta) {
1188 					button->MoveBy(delta, 0);
1189 					button->ResizeBy(-delta, 0);
1190 				}
1191 			}
1192 			break;
1193 
1194 		case B_DEFAULT_BUTTON:
1195 			{
1196 				fButtonText = text;
1197 				float delta = 0;
1198 				BButton* button
1199 					= dynamic_cast<BButton*>(FindView("default button"));
1200 				if (button != NULL) {
1201 					float old_width = button->StringWidth(button->Label());
1202 					button->SetLabel(text);
1203 					delta = old_width - button->StringWidth(text);
1204 					if (delta) {
1205 						button->MoveBy(delta, 0);
1206 						button->ResizeBy(-delta, 0);
1207 					}
1208 				}
1209 
1210 				// now must move cancel button
1211 				button = dynamic_cast<BButton*>(FindView("cancel button"));
1212 				if (button != NULL)
1213 					button->MoveBy(delta, 0);
1214 			}
1215 			break;
1216 	}
1217 }
1218 
1219 
1220 void
1221 TFilePanel::SetSaveText(const char* text)
1222 {
1223 	if (text == NULL)
1224 		return;
1225 
1226 	BTextControl* textControl
1227 		= dynamic_cast<BTextControl*>(FindView("text view"));
1228 	if (textControl != NULL) {
1229 		textControl->SetText(text);
1230 		if (textControl->TextView() != NULL)
1231 			textControl->TextView()->SelectAll();
1232 	}
1233 }
1234 
1235 
1236 void
1237 TFilePanel::MessageReceived(BMessage* message)
1238 {
1239 	entry_ref ref;
1240 
1241 	switch (message->what) {
1242 		case B_REFS_RECEIVED:
1243 			// item was double clicked in file panel (PoseView)
1244 			if (message->FindRef("refs", &ref) == B_OK) {
1245 				BEntry entry(&ref, true);
1246 				if (entry.InitCheck() == B_OK) {
1247 					// Double-click on dir or link-to-dir ALWAYS opens the
1248 					// dir. If more than one dir is selected, the first is
1249 					// entered.
1250 					if (entry.IsDirectory()) {
1251 						entry.GetRef(&ref);
1252 						bool isDesktop = SwitchDirToDesktopIfNeeded(ref);
1253 
1254 						PoseView()->SetIsDesktop(isDesktop);
1255 						entry.SetTo(&ref);
1256 						PoseView()->SwitchDir(&ref);
1257 						SwitchDirMenuTo(&ref);
1258 					} else {
1259 						// Otherwise, we have a file or a link to a file.
1260 						// AdjustButton has already tested the flavor;
1261 						// all we have to do is see if the button is enabled.
1262 						BButton* button = dynamic_cast<BButton*>(
1263 							FindView("default button"));
1264 						if (button == NULL || !button->IsEnabled())
1265 							break;
1266 
1267 						if (IsSavePanel()) {
1268 							int32 count = 0;
1269 							type_code type;
1270 							message->GetInfo("refs", &type, &count);
1271 
1272 							// Don't allow saves of multiple files
1273 							if (count > 1) {
1274 								ShowCenteredAlert(
1275 									B_TRANSLATE(
1276 										"Sorry, saving more than one "
1277 										"item is not allowed."),
1278 									B_TRANSLATE("Cancel"));
1279 							} else {
1280 								// if we are a savepanel, set up the
1281 								// filepanel correctly then pass control
1282 								// so we follow the same path as if the user
1283 								// clicked the save button
1284 
1285 								// set the 'name' fld to the current ref's
1286 								// name notify the panel that the default
1287 								// button should be enabled
1288 								SetSaveText(ref.name);
1289 								SelectionChanged();
1290 
1291 								HandleSaveButton();
1292 							}
1293 							break;
1294 						}
1295 
1296 						// send handler a message and close
1297 						BMessage openMessage(*fMessage);
1298 						for (int32 index = 0; ; index++) {
1299 							if (message->FindRef("refs", index, &ref) != B_OK)
1300 								break;
1301 							openMessage.AddRef("refs", &ref);
1302 						}
1303 						OpenSelectionCommon(&openMessage);
1304 					}
1305 				}
1306 			}
1307 			break;
1308 
1309 		case kSwitchDirectory:
1310 		{
1311 			entry_ref ref;
1312 			// this comes from dir menu or nav menu, so switch directories
1313 			if (message->FindRef("refs", &ref) == B_OK) {
1314 				BEntry entry(&ref, true);
1315 				if (entry.GetRef(&ref) == B_OK)
1316 					SetTo(&ref);
1317 			}
1318 			break;
1319 		}
1320 
1321 		case kSwitchToHome:
1322 		{
1323 			BPath homePath;
1324 			entry_ref ref;
1325 			if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK
1326 				|| get_ref_for_path(homePath.Path(), &ref) != B_OK) {
1327 				break;
1328 			}
1329 
1330 			SetTo(&ref);
1331 			break;
1332 		}
1333 
1334 		case kAddCurrentDir:
1335 		{
1336 			BPath path;
1337 			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)
1338 					!= B_OK) {
1339 				break;
1340 			}
1341 
1342 			path.Append(kGoDirectory);
1343 			BDirectory goDirectory(path.Path());
1344 
1345 			if (goDirectory.InitCheck() == B_OK) {
1346 				BEntry entry(TargetModel()->EntryRef());
1347 				entry.GetPath(&path);
1348 
1349 				BSymLink link;
1350 				goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(),
1351 					&link);
1352 			}
1353 			break;
1354 		}
1355 
1356 		case kEditFavorites:
1357 		{
1358 			BPath path;
1359 			if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true)
1360 					!= B_OK) {
1361 				break;
1362 			}
1363 
1364 			path.Append(kGoDirectory);
1365 			BMessenger msgr(kTrackerSignature);
1366 			if (msgr.IsValid()) {
1367 				BMessage message(B_REFS_RECEIVED);
1368 				entry_ref ref;
1369 				if (get_ref_for_path(path.Path(), &ref) == B_OK) {
1370 					message.AddRef("refs", &ref);
1371 					msgr.SendMessage(&message);
1372 				}
1373 			}
1374 			break;
1375 		}
1376 
1377 		case kCancelButton:
1378 			PostMessage(B_QUIT_REQUESTED);
1379 			break;
1380 
1381 		case kResizeToFit:
1382 			ResizeToFit();
1383 			break;
1384 
1385 		case kOpenDir:
1386 			OpenDirectory();
1387 			break;
1388 
1389 		case kOpenParentDir:
1390 			OpenParent();
1391 			break;
1392 
1393 		case kDefaultButton:
1394 			if (fIsSavePanel) {
1395 				if (PoseView()->IsFocus()
1396 					&& PoseView()->CountSelected() == 1) {
1397 					Model* model = (PoseView()->SelectionList()->
1398 						FirstItem())->TargetModel();
1399 					if (model->ResolveIfLink()->IsDirectory()) {
1400 						PoseView()->CommitActivePose();
1401 						PoseView()->OpenSelection();
1402 						break;
1403 					}
1404 				}
1405 
1406 				HandleSaveButton();
1407 			} else
1408 				HandleOpenButton();
1409 			break;
1410 
1411 		case B_OBSERVER_NOTICE_CHANGE:
1412 		{
1413 			int32 observerWhat;
1414 			if (message->FindInt32("be:observe_change_what", &observerWhat)
1415 					== B_OK) {
1416 				switch (observerWhat) {
1417 					case kDesktopFilePanelRootChanged:
1418 					{
1419 						bool desktopIsRoot = true;
1420 						if (message->FindBool("DesktopFilePanelRoot",
1421 								&desktopIsRoot) == B_OK) {
1422 							TrackerSettings().
1423 								SetDesktopFilePanelRoot(desktopIsRoot);
1424 						}
1425 						SetTo(TargetModel()->EntryRef());
1426 						break;
1427 					}
1428 				}
1429 			}
1430 			break;
1431 		}
1432 
1433 		default:
1434 			_inherited::MessageReceived(message);
1435 			break;
1436 	}
1437 }
1438 
1439 
1440 void
1441 TFilePanel::OpenDirectory()
1442 {
1443 	BObjectList<BPose>* list = PoseView()->SelectionList();
1444 	if (list->CountItems() != 1)
1445 		return;
1446 
1447 	Model* model = list->FirstItem()->TargetModel();
1448 	if (model->ResolveIfLink()->IsDirectory()) {
1449 		BMessage message(B_REFS_RECEIVED);
1450 		message.AddRef("refs", model->EntryRef());
1451 		PostMessage(&message);
1452 	}
1453 }
1454 
1455 
1456 void
1457 TFilePanel::OpenParent()
1458 {
1459 	if (!CanOpenParent())
1460 		return;
1461 
1462 	BEntry parentEntry;
1463 	BDirectory dir;
1464 
1465 	Model oldModel(*PoseView()->TargetModel());
1466 	BEntry entry(oldModel.EntryRef());
1467 
1468 	if (entry.InitCheck() == B_OK
1469 		&& entry.GetParent(&dir) == B_OK
1470 		&& dir.GetEntry(&parentEntry) == B_OK
1471 		&& entry != parentEntry) {
1472 
1473 		entry_ref ref;
1474 		parentEntry.GetRef(&ref);
1475 
1476 		PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref));
1477 		PoseView()->SwitchDir(&ref);
1478 		SwitchDirMenuTo(&ref);
1479 
1480 		// Make sure the child gets selected in the new view
1481 		// once it shows up.
1482 		fTaskLoop->RunLater(NewMemberFunctionObjectWithResult
1483 			(&TFilePanel::SelectChildInParent, this,
1484 			const_cast<const entry_ref*>(&ref),
1485 			oldModel.NodeRef()), 100000, 200000, 5000000);
1486 	}
1487 }
1488 
1489 
1490 bool
1491 TFilePanel::CanOpenParent() const
1492 {
1493 	if (TrackerSettings().DesktopFilePanelRoot()) {
1494 		// don't allow opening Desktop folder's parent
1495 		if (TargetModel()->IsDesktop())
1496 			return false;
1497 	}
1498 
1499 	// block on "/"
1500 	BEntry root("/");
1501 	node_ref rootRef;
1502 	root.GetNodeRef(&rootRef);
1503 
1504 	return rootRef != *TargetModel()->NodeRef();
1505 }
1506 
1507 
1508 bool
1509 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref)
1510 {
1511 	// support showing Desktop as root of everything
1512 	// This call implements the worm hole that maps Desktop as
1513 	// a root above the disks
1514 	TrackerSettings settings;
1515 	if (!settings.DesktopFilePanelRoot())
1516 		// Tracker isn't set up that way, just let Disks show
1517 		return false;
1518 
1519 	BEntry entry(&ref);
1520 	BEntry root("/");
1521 
1522 	BDirectory desktopDir;
1523 	FSGetDeskDir(&desktopDir);
1524 	if (FSIsDeskDir(&entry)
1525 		// navigated into non-boot desktop, switch to boot desktop
1526 		|| (entry == root && !settings.ShowDisksIcon())) {
1527 		// hit "/" level, map to desktop
1528 
1529 		desktopDir.GetEntry(&entry);
1530 		entry.GetRef(&ref);
1531 		return true;
1532 	}
1533 	return FSIsDeskDir(&entry);
1534 }
1535 
1536 
1537 bool
1538 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child)
1539 {
1540 	AutoLock<TFilePanel> lock(this);
1541 
1542 	if (!IsLocked())
1543 		return false;
1544 
1545 	int32 index;
1546 	BPose* pose = PoseView()->FindPose(child, &index);
1547 	if (!pose)
1548 		return false;
1549 
1550 	PoseView()->UpdateScrollRange();
1551 		// ToDo: Scroll range should be updated by now, for some
1552 		//	reason sometimes it is not right, force it here
1553 	PoseView()->SelectPose(pose, index, true);
1554 	return true;
1555 }
1556 
1557 
1558 int32
1559 TFilePanel::ShowCenteredAlert(const char* text, const char* button1,
1560 	const char* button2, const char* button3)
1561 {
1562 	BAlert* alert = new BAlert("", text, button1, button2, button3,
1563 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1564 	alert->MoveTo(Frame().left + 10, Frame().top + 10);
1565 
1566 #if 0
1567 	if (button1 != NULL && !strncmp(button1, "Cancel", 7))
1568 		alert->SetShortcut(0, B_ESCAPE);
1569 	else if (button2 != NULL && !strncmp(button2, "Cancel", 7))
1570 		alert->SetShortcut(1, B_ESCAPE);
1571 	else if (button3 != NULL && !strncmp(button3, "Cancel", 7))
1572 		alert->SetShortcut(2, B_ESCAPE);
1573 #endif
1574 
1575 	return alert->Go();
1576 }
1577 
1578 
1579 void
1580 TFilePanel::HandleSaveButton()
1581 {
1582 	BDirectory dir;
1583 
1584 	if (TargetModel()->IsRoot()) {
1585 		ShowCenteredAlert(
1586 			B_TRANSLATE("Sorry, you can't save things at the root of "
1587 			"your system."),
1588 			B_TRANSLATE("Cancel"));
1589 		return;
1590 	}
1591 
1592 	// check for some illegal file names
1593 	if (strcmp(fTextControl->Text(), ".") == 0
1594 		|| strcmp(fTextControl->Text(), "..") == 0) {
1595 		ShowCenteredAlert(
1596 			B_TRANSLATE("The specified name is illegal. Please choose "
1597 			"another name."),
1598 			B_TRANSLATE("Cancel"));
1599 		fTextControl->TextView()->SelectAll();
1600 		return;
1601 	}
1602 
1603 	if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) {
1604 		ShowCenteredAlert(
1605 			B_TRANSLATE("There was a problem trying to save in the folder "
1606 			"you specified. Please try another one."),
1607 			B_TRANSLATE("Cancel"));
1608 		return;
1609 	}
1610 
1611 	if (dir.Contains(fTextControl->Text())) {
1612 		if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) {
1613 			ShowCenteredAlert(
1614 				B_TRANSLATE("The specified name is already used as the name "
1615 				"of a folder. Please choose another name."),
1616 				B_TRANSLATE("Cancel"));
1617 			fTextControl->TextView()->SelectAll();
1618 			return;
1619 		} else {
1620 			// if this was invoked by a dbl click, it is an explicit
1621 			// replacement of the file.
1622 			BString str(B_TRANSLATE("The file \"%name\" already exists in "
1623 				"the specified folder. Do you want to replace it?"));
1624 			str.ReplaceFirst("%name", fTextControl->Text());
1625 
1626 			if (ShowCenteredAlert(str.String(),	B_TRANSLATE("Cancel"),
1627 					B_TRANSLATE("Replace"))	== 0) {
1628 				// user canceled
1629 				fTextControl->TextView()->SelectAll();
1630 				return;
1631 			}
1632 			// user selected "Replace" - let app deal with it
1633 		}
1634 	}
1635 
1636 	BMessage message(*fMessage);
1637 	message.AddRef("directory", TargetModel()->EntryRef());
1638 	message.AddString("name", fTextControl->Text());
1639 
1640 	if (fClientObject)
1641 		fClientObject->SendMessage(&fTarget, &message);
1642 	else
1643 		fTarget.SendMessage(&message);
1644 
1645 	// close window if we're dealing with standard message
1646 	if (fHideWhenDone)
1647 		PostMessage(B_QUIT_REQUESTED);
1648 }
1649 
1650 
1651 void
1652 TFilePanel::OpenSelectionCommon(BMessage* openMessage)
1653 {
1654 	if (!openMessage->HasRef("refs"))
1655 		return;
1656 
1657 	for (int32 index = 0; ; index++) {
1658 		entry_ref ref;
1659 		if (openMessage->FindRef("refs", index, &ref) != B_OK)
1660 			break;
1661 
1662 		BEntry entry(&ref, true);
1663 		if (entry.InitCheck() == B_OK) {
1664 			if (entry.IsDirectory())
1665 				BRoster().AddToRecentFolders(&ref);
1666 			else
1667 				BRoster().AddToRecentDocuments(&ref);
1668 		}
1669 	}
1670 
1671 	BRoster().AddToRecentFolders(TargetModel()->EntryRef());
1672 
1673 	if (fClientObject)
1674 		fClientObject->SendMessage(&fTarget, openMessage);
1675 	else
1676 		fTarget.SendMessage(openMessage);
1677 
1678 	// close window if we're dealing with standard message
1679 	if (fHideWhenDone)
1680 		PostMessage(B_QUIT_REQUESTED);
1681 }
1682 
1683 
1684 void
1685 TFilePanel::HandleOpenButton()
1686 {
1687 	PoseView()->CommitActivePose();
1688 	BObjectList<BPose>* selection = PoseView()->SelectionList();
1689 
1690 	// if we have only one directory and we're not opening dirs, enter.
1691 	if ((fNodeFlavors & B_DIRECTORY_NODE) == 0
1692 		&& selection->CountItems() == 1) {
1693 		Model* model = selection->FirstItem()->TargetModel();
1694 
1695 		if (model->IsDirectory()
1696 			|| (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE)
1697 				&& model->ResolveIfLink()->IsDirectory())) {
1698 
1699 			BMessage message(B_REFS_RECEIVED);
1700 			message.AddRef("refs", model->EntryRef());
1701 			PostMessage(&message);
1702 			return;
1703 		}
1704 	}
1705 
1706 	if (selection->CountItems()) {
1707 			// there are items selected
1708 			// message->fMessage->message from here to end
1709 		BMessage message(*fMessage);
1710 		// go through selection and add appropriate items
1711 		for (int32 index = 0; index < selection->CountItems(); index++) {
1712 			Model* model = selection->ItemAt(index)->TargetModel();
1713 
1714 			if (((fNodeFlavors & B_DIRECTORY_NODE) != 0
1715 					&& model->ResolveIfLink()->IsDirectory())
1716 				|| ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink())
1717 				|| ((fNodeFlavors & B_FILE_NODE) != 0
1718 					&& model->ResolveIfLink()->IsFile())) {
1719 				message.AddRef("refs", model->EntryRef());
1720 			}
1721 		}
1722 
1723 		OpenSelectionCommon(&message);
1724 	} else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) {
1725 		// Open the current directory.
1726 		BMessage message(*fMessage);
1727 		message.AddRef("refs", TargetModel()->EntryRef());
1728 		OpenSelectionCommon(&message);
1729 	}
1730 }
1731 
1732 
1733 void
1734 TFilePanel::SwitchDirMenuTo(const entry_ref* ref)
1735 {
1736 	BEntry entry(ref);
1737 	for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--)
1738 		delete fDirMenu->RemoveItem(index);
1739 
1740 	fDirMenuField->MenuBar()->RemoveItem((int32)0);
1741 	fDirMenu->Populate(&entry, 0, true, true, false, true);
1742 
1743 	ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(
1744 		fDirMenuField->MenuBar()->ItemAt(0));
1745 	ASSERT(item != NULL);
1746 
1747 	if (item != NULL)
1748 		item->SetEntry(&entry);
1749 }
1750 
1751 
1752 void
1753 TFilePanel::WindowActivated(bool active)
1754 {
1755 	// force focus to update properly
1756 	fBackView->Invalidate();
1757 	_inherited::WindowActivated(active);
1758 }
1759 
1760 
1761 //	#pragma mark -
1762 
1763 
1764 BFilePanelPoseView::BFilePanelPoseView(Model* model)
1765 	:
1766 	BPoseView(model, kListMode),
1767 	fIsDesktop(model->IsDesktop())
1768 {
1769 }
1770 
1771 
1772 void
1773 BFilePanelPoseView::StartWatching()
1774 {
1775 	TTracker::WatchNode(0, B_WATCH_MOUNT, this);
1776 
1777 	// inter-application observing
1778 	BMessenger tracker(kTrackerSignature);
1779 	BHandler::StartWatching(tracker, kVolumesOnDesktopChanged);
1780 }
1781 
1782 
1783 void
1784 BFilePanelPoseView::StopWatching()
1785 {
1786 	stop_watching(this);
1787 
1788 	// inter-application observing
1789 	BMessenger tracker(kTrackerSignature);
1790 	BHandler::StopWatching(tracker, kVolumesOnDesktopChanged);
1791 }
1792 
1793 
1794 bool
1795 BFilePanelPoseView::FSNotification(const BMessage* message)
1796 {
1797 	switch (message->FindInt32("opcode")) {
1798 		case B_DEVICE_MOUNTED:
1799 		{
1800 			if (IsDesktopView()) {
1801 				// Pretty much copied straight from DesktopPoseView.
1802 				// Would be better if the code could be shared somehow.
1803 				dev_t device;
1804 				if (message->FindInt32("new device", &device) != B_OK)
1805 					break;
1806 
1807 				ASSERT(TargetModel() != NULL);
1808 				TrackerSettings settings;
1809 
1810 				BVolume volume(device);
1811 				if (volume.InitCheck() != B_OK)
1812 					break;
1813 
1814 				if (settings.MountVolumesOntoDesktop()
1815 					&& (!volume.IsShared()
1816 						|| settings.MountSharedVolumesOntoDesktop())) {
1817 					// place an icon for the volume onto the desktop
1818 					CreateVolumePose(&volume, true);
1819 				}
1820 			}
1821 			break;
1822 		}
1823 
1824 		case B_DEVICE_UNMOUNTED:
1825 		{
1826 			dev_t device;
1827 			if (message->FindInt32("device", &device) == B_OK) {
1828 				if (TargetModel() != NULL
1829 					&& TargetModel()->NodeRef()->device == device) {
1830 					// Volume currently shown in this file panel
1831 					// disappeared, reset location to home directory
1832 					BMessage message(kSwitchToHome);
1833 					MessageReceived(&message);
1834 				}
1835 			}
1836 			break;
1837 		}
1838 	}
1839 	return _inherited::FSNotification(message);
1840 }
1841 
1842 
1843 void
1844 BFilePanelPoseView::RestoreState(AttributeStreamNode* node)
1845 {
1846 	_inherited::RestoreState(node);
1847 	fViewState->SetViewMode(kListMode);
1848 }
1849 
1850 
1851 void
1852 BFilePanelPoseView::RestoreState(const BMessage &message)
1853 {
1854 	_inherited::RestoreState(message);
1855 }
1856 
1857 
1858 void
1859 BFilePanelPoseView::SavePoseLocations(BRect*)
1860 {
1861 }
1862 
1863 
1864 EntryListBase*
1865 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref)
1866 {
1867 	if (IsDesktopView())
1868 		return DesktopPoseView::InitDesktopDirentIterator(this, ref);
1869 
1870 	return _inherited::InitDirentIterator(ref);
1871 }
1872 
1873 
1874 void
1875 BFilePanelPoseView::AddPosesCompleted()
1876 {
1877 	_inherited::AddPosesCompleted();
1878 	if (IsDesktopView())
1879 		CreateTrashPose();
1880 }
1881 
1882 
1883 void
1884 BFilePanelPoseView::SetIsDesktop(bool on)
1885 {
1886 	fIsDesktop = on;
1887 }
1888 
1889 
1890 bool
1891 BFilePanelPoseView::IsDesktopView() const
1892 {
1893 	return fIsDesktop;
1894 }
1895 
1896 
1897 void
1898 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared)
1899 {
1900 	if (IsDesktopView()) {
1901 		if (!visible)
1902 			RemoveRootPoses();
1903 		else
1904 			AddRootPoses(true, showShared);
1905 	}
1906 
1907 	TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window());
1908 	if (filepanel != NULL && TargetModel() != NULL)
1909 		filepanel->SetTo(TargetModel()->EntryRef());
1910 }
1911 
1912 
1913 void
1914 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message)
1915 {
1916 	bool showDisksIcon;
1917 	bool mountVolumesOnDesktop;
1918 	bool mountSharedVolumesOntoDesktop;
1919 
1920 	message->FindBool("ShowDisksIcon", &showDisksIcon);
1921 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1922 	message->FindBool("MountSharedVolumesOntoDesktop",
1923 		&mountSharedVolumesOntoDesktop);
1924 
1925 	BEntry entry("/");
1926 	Model model(&entry);
1927 	if (model.InitCheck() == B_OK) {
1928 		BMessage monitorMsg;
1929 		monitorMsg.what = B_NODE_MONITOR;
1930 
1931 		if (showDisksIcon)
1932 			monitorMsg.AddInt32("opcode", B_ENTRY_CREATED);
1933 		else
1934 			monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED);
1935 
1936 		monitorMsg.AddInt32("device", model.NodeRef()->device);
1937 		monitorMsg.AddInt64("node", model.NodeRef()->node);
1938 		monitorMsg.AddInt64("directory", model.EntryRef()->directory);
1939 		monitorMsg.AddString("name", model.EntryRef()->name);
1940 		TrackerSettings().SetShowDisksIcon(showDisksIcon);
1941 		if (Window() != NULL)
1942 			Window()->PostMessage(&monitorMsg, this);
1943 	}
1944 
1945 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1946 }
1947 
1948 
1949 void
1950 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message)
1951 {
1952 	bool mountVolumesOnDesktop = true;
1953 	bool mountSharedVolumesOntoDesktop = true;
1954 
1955 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1956 	message->FindBool("MountSharedVolumesOntoDesktop",
1957 		&mountSharedVolumesOntoDesktop);
1958 
1959 	ShowVolumes(false, mountSharedVolumesOntoDesktop);
1960 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1961 }
1962