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