xref: /haiku/src/kits/tracker/FilePanelPriv.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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) or from the favorites menu
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 if it comes from the file
1251 						// panel; all we have to do is see if the button is enabled.
1252 						// In other cases, however, we can't rely on that. So first check for
1253 						// TrackerViewToken in the message to see if it's coming from the pose view
1254 						if (message->HasMessenger("TrackerViewToken")) {
1255 							BButton* button = dynamic_cast<BButton*>(FindView("default button"));
1256 							if (button == NULL || !button->IsEnabled())
1257 								break;
1258 						}
1259 
1260 						if (IsSavePanel()) {
1261 							int32 count = 0;
1262 							type_code type;
1263 							message->GetInfo("refs", &type, &count);
1264 
1265 							// Don't allow saves of multiple files
1266 							if (count > 1) {
1267 								ShowCenteredAlert(
1268 									B_TRANSLATE(
1269 										"Sorry, saving more than one "
1270 										"item is not allowed."),
1271 									B_TRANSLATE("Cancel"));
1272 							} else {
1273 								// if we are a savepanel, set up the
1274 								// filepanel correctly then pass control
1275 								// so we follow the same path as if the user
1276 								// clicked the save button
1277 
1278 								// set the 'name' fld to the current ref's
1279 								// name notify the panel that the default
1280 								// button should be enabled
1281 								SetSaveText(ref.name);
1282 								SelectionChanged();
1283 
1284 								HandleSaveButton();
1285 							}
1286 							break;
1287 						}
1288 
1289 						// send handler a message and close
1290 						BMessage openMessage(*fMessage);
1291 						for (int32 index = 0; ; index++) {
1292 							if (message->FindRef("refs", index, &ref) != B_OK)
1293 								break;
1294 							openMessage.AddRef("refs", &ref);
1295 						}
1296 						OpenSelectionCommon(&openMessage);
1297 					}
1298 				}
1299 			}
1300 			break;
1301 
1302 		case kSwitchDirectory:
1303 		{
1304 			entry_ref ref;
1305 			// this comes from dir menu or nav menu, so switch directories
1306 			if (message->FindRef("refs", &ref) == B_OK) {
1307 				BEntry entry(&ref, true);
1308 				if (entry.GetRef(&ref) == B_OK)
1309 					SetTo(&ref);
1310 			}
1311 			break;
1312 		}
1313 
1314 		case kSwitchToHome:
1315 		{
1316 			BPath homePath;
1317 			entry_ref ref;
1318 			if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK
1319 				|| get_ref_for_path(homePath.Path(), &ref) != B_OK) {
1320 				break;
1321 			}
1322 
1323 			SetTo(&ref);
1324 			break;
1325 		}
1326 
1327 		case kAddCurrentDir:
1328 		{
1329 			BPath path;
1330 			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)
1331 					!= B_OK) {
1332 				break;
1333 			}
1334 
1335 			path.Append(kGoDirectory);
1336 			BDirectory goDirectory(path.Path());
1337 
1338 			if (goDirectory.InitCheck() == B_OK) {
1339 				BEntry entry(TargetModel()->EntryRef());
1340 				entry.GetPath(&path);
1341 
1342 				BSymLink link;
1343 				goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(),
1344 					&link);
1345 			}
1346 			break;
1347 		}
1348 
1349 		case kEditFavorites:
1350 		{
1351 			BPath path;
1352 			if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true)
1353 					!= B_OK) {
1354 				break;
1355 			}
1356 
1357 			path.Append(kGoDirectory);
1358 			BMessenger msgr(kTrackerSignature);
1359 			if (msgr.IsValid()) {
1360 				BMessage message(B_REFS_RECEIVED);
1361 				entry_ref ref;
1362 				if (get_ref_for_path(path.Path(), &ref) == B_OK) {
1363 					message.AddRef("refs", &ref);
1364 					msgr.SendMessage(&message);
1365 				}
1366 			}
1367 			break;
1368 		}
1369 
1370 		case kCancelButton:
1371 			PostMessage(B_QUIT_REQUESTED);
1372 			break;
1373 
1374 		case kResizeToFit:
1375 			ResizeToFit();
1376 			break;
1377 
1378 		case kOpenDir:
1379 			OpenDirectory();
1380 			break;
1381 
1382 		case kOpenParentDir:
1383 			OpenParent();
1384 			break;
1385 
1386 		case kDefaultButton:
1387 			if (fIsSavePanel) {
1388 				if (PoseView()->IsFocus()
1389 					&& PoseView()->CountSelected() == 1) {
1390 					Model* model = (PoseView()->SelectionList()->
1391 						FirstItem())->TargetModel();
1392 					if (model->ResolveIfLink()->IsDirectory()) {
1393 						PoseView()->CommitActivePose();
1394 						PoseView()->OpenSelection();
1395 						break;
1396 					}
1397 				}
1398 
1399 				HandleSaveButton();
1400 			} else
1401 				HandleOpenButton();
1402 			break;
1403 
1404 		case B_OBSERVER_NOTICE_CHANGE:
1405 		{
1406 			int32 observerWhat;
1407 			if (message->FindInt32("be:observe_change_what", &observerWhat)
1408 					== B_OK) {
1409 				switch (observerWhat) {
1410 					case kDesktopFilePanelRootChanged:
1411 					{
1412 						bool desktopIsRoot = true;
1413 						if (message->FindBool("DesktopFilePanelRoot",
1414 								&desktopIsRoot) == B_OK) {
1415 							TrackerSettings().
1416 								SetDesktopFilePanelRoot(desktopIsRoot);
1417 						}
1418 						SetTo(TargetModel()->EntryRef());
1419 						break;
1420 					}
1421 				}
1422 			}
1423 			break;
1424 		}
1425 
1426 		default:
1427 			_inherited::MessageReceived(message);
1428 			break;
1429 	}
1430 }
1431 
1432 
1433 void
1434 TFilePanel::OpenDirectory()
1435 {
1436 	BObjectList<BPose>* list = PoseView()->SelectionList();
1437 	if (list->CountItems() != 1)
1438 		return;
1439 
1440 	Model* model = list->FirstItem()->TargetModel();
1441 	if (model->ResolveIfLink()->IsDirectory()) {
1442 		BMessage message(B_REFS_RECEIVED);
1443 		message.AddRef("refs", model->EntryRef());
1444 		PostMessage(&message);
1445 	}
1446 }
1447 
1448 
1449 void
1450 TFilePanel::OpenParent()
1451 {
1452 	if (!CanOpenParent())
1453 		return;
1454 
1455 	BEntry parentEntry;
1456 	BDirectory dir;
1457 
1458 	Model oldModel(*PoseView()->TargetModel());
1459 	BEntry entry(oldModel.EntryRef());
1460 
1461 	if (entry.InitCheck() == B_OK
1462 		&& entry.GetParent(&dir) == B_OK
1463 		&& dir.GetEntry(&parentEntry) == B_OK
1464 		&& entry != parentEntry) {
1465 
1466 		entry_ref ref;
1467 		parentEntry.GetRef(&ref);
1468 
1469 		PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref));
1470 		PoseView()->SwitchDir(&ref);
1471 		SwitchDirMenuTo(&ref);
1472 
1473 		// Make sure the child gets selected in the new view
1474 		// once it shows up.
1475 		fTaskLoop->RunLater(NewMemberFunctionObjectWithResult
1476 			(&TFilePanel::SelectChildInParent, this,
1477 			const_cast<const entry_ref*>(&ref),
1478 			oldModel.NodeRef()), 100000, 200000, 5000000);
1479 	}
1480 }
1481 
1482 
1483 bool
1484 TFilePanel::CanOpenParent() const
1485 {
1486 	if (TrackerSettings().DesktopFilePanelRoot()) {
1487 		// don't allow opening Desktop folder's parent
1488 		if (TargetModel()->IsDesktop())
1489 			return false;
1490 	}
1491 
1492 	// block on "/"
1493 	BEntry root("/");
1494 	node_ref rootRef;
1495 	root.GetNodeRef(&rootRef);
1496 
1497 	return rootRef != *TargetModel()->NodeRef();
1498 }
1499 
1500 
1501 bool
1502 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref)
1503 {
1504 	// support showing Desktop as root of everything
1505 	// This call implements the worm hole that maps Desktop as
1506 	// a root above the disks
1507 	TrackerSettings settings;
1508 	if (!settings.DesktopFilePanelRoot())
1509 		// Tracker isn't set up that way, just let Disks show
1510 		return false;
1511 
1512 	BEntry entry(&ref);
1513 	BEntry root("/");
1514 
1515 	BDirectory desktopDir;
1516 	FSGetDeskDir(&desktopDir);
1517 	if (FSIsDeskDir(&entry)
1518 		// navigated into non-boot desktop, switch to boot desktop
1519 		|| (entry == root && !settings.ShowDisksIcon())) {
1520 		// hit "/" level, map to desktop
1521 
1522 		desktopDir.GetEntry(&entry);
1523 		entry.GetRef(&ref);
1524 		return true;
1525 	}
1526 	return FSIsDeskDir(&entry);
1527 }
1528 
1529 
1530 bool
1531 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child)
1532 {
1533 	AutoLock<TFilePanel> lock(this);
1534 
1535 	if (!IsLocked())
1536 		return false;
1537 
1538 	int32 index;
1539 	BPose* pose = PoseView()->FindPose(child, &index);
1540 	if (!pose)
1541 		return false;
1542 
1543 	PoseView()->UpdateScrollRange();
1544 		// ToDo: Scroll range should be updated by now, for some
1545 		//	reason sometimes it is not right, force it here
1546 	PoseView()->SelectPose(pose, index, true);
1547 	return true;
1548 }
1549 
1550 
1551 int32
1552 TFilePanel::ShowCenteredAlert(const char* text, const char* button1,
1553 	const char* button2, const char* button3)
1554 {
1555 	BAlert* alert = new BAlert("", text, button1, button2, button3,
1556 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1557 	alert->MoveTo(Frame().left + 10, Frame().top + 10);
1558 
1559 #if 0
1560 	if (button1 != NULL && !strncmp(button1, "Cancel", 7))
1561 		alert->SetShortcut(0, B_ESCAPE);
1562 	else if (button2 != NULL && !strncmp(button2, "Cancel", 7))
1563 		alert->SetShortcut(1, B_ESCAPE);
1564 	else if (button3 != NULL && !strncmp(button3, "Cancel", 7))
1565 		alert->SetShortcut(2, B_ESCAPE);
1566 #endif
1567 
1568 	return alert->Go();
1569 }
1570 
1571 
1572 void
1573 TFilePanel::HandleSaveButton()
1574 {
1575 	BDirectory dir;
1576 
1577 	if (TargetModel()->IsRoot()) {
1578 		ShowCenteredAlert(
1579 			B_TRANSLATE("Sorry, you can't save things at the root of "
1580 			"your system."),
1581 			B_TRANSLATE("Cancel"));
1582 		return;
1583 	}
1584 
1585 	// check for some illegal file names
1586 	if (strcmp(fTextControl->Text(), ".") == 0
1587 		|| strcmp(fTextControl->Text(), "..") == 0) {
1588 		ShowCenteredAlert(
1589 			B_TRANSLATE("The specified name is illegal. Please choose "
1590 			"another name."),
1591 			B_TRANSLATE("Cancel"));
1592 		fTextControl->TextView()->SelectAll();
1593 		return;
1594 	}
1595 
1596 	if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) {
1597 		ShowCenteredAlert(
1598 			B_TRANSLATE("There was a problem trying to save in the folder "
1599 			"you specified. Please try another one."),
1600 			B_TRANSLATE("Cancel"));
1601 		return;
1602 	}
1603 
1604 	if (dir.Contains(fTextControl->Text())) {
1605 		if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) {
1606 			ShowCenteredAlert(
1607 				B_TRANSLATE("The specified name is already used as the name "
1608 				"of a folder. Please choose another name."),
1609 				B_TRANSLATE("Cancel"));
1610 			fTextControl->TextView()->SelectAll();
1611 			return;
1612 		} else {
1613 			// if this was invoked by a dbl click, it is an explicit
1614 			// replacement of the file.
1615 			BString str(B_TRANSLATE("The file \"%name\" already exists in "
1616 				"the specified folder. Do you want to replace it?"));
1617 			str.ReplaceFirst("%name", fTextControl->Text());
1618 
1619 			if (ShowCenteredAlert(str.String(),	B_TRANSLATE("Cancel"),
1620 					B_TRANSLATE("Replace"))	== 0) {
1621 				// user canceled
1622 				fTextControl->TextView()->SelectAll();
1623 				return;
1624 			}
1625 			// user selected "Replace" - let app deal with it
1626 		}
1627 	}
1628 
1629 	BMessage message(*fMessage);
1630 	message.AddRef("directory", TargetModel()->EntryRef());
1631 	message.AddString("name", fTextControl->Text());
1632 
1633 	if (fClientObject)
1634 		fClientObject->SendMessage(&fTarget, &message);
1635 	else
1636 		fTarget.SendMessage(&message);
1637 
1638 	// close window if we're dealing with standard message
1639 	if (fHideWhenDone)
1640 		PostMessage(B_QUIT_REQUESTED);
1641 }
1642 
1643 
1644 void
1645 TFilePanel::OpenSelectionCommon(BMessage* openMessage)
1646 {
1647 	if (!openMessage->HasRef("refs"))
1648 		return;
1649 
1650 	for (int32 index = 0; ; index++) {
1651 		entry_ref ref;
1652 		if (openMessage->FindRef("refs", index, &ref) != B_OK)
1653 			break;
1654 
1655 		BEntry entry(&ref, true);
1656 		if (entry.InitCheck() == B_OK) {
1657 			if (entry.IsDirectory())
1658 				BRoster().AddToRecentFolders(&ref);
1659 			else
1660 				BRoster().AddToRecentDocuments(&ref);
1661 		}
1662 	}
1663 
1664 	BRoster().AddToRecentFolders(TargetModel()->EntryRef());
1665 
1666 	if (fClientObject)
1667 		fClientObject->SendMessage(&fTarget, openMessage);
1668 	else
1669 		fTarget.SendMessage(openMessage);
1670 
1671 	// close window if we're dealing with standard message
1672 	if (fHideWhenDone)
1673 		PostMessage(B_QUIT_REQUESTED);
1674 }
1675 
1676 
1677 void
1678 TFilePanel::HandleOpenButton()
1679 {
1680 	PoseView()->CommitActivePose();
1681 	BObjectList<BPose>* selection = PoseView()->SelectionList();
1682 
1683 	// if we have only one directory and we're not opening dirs, enter.
1684 	if ((fNodeFlavors & B_DIRECTORY_NODE) == 0
1685 		&& selection->CountItems() == 1) {
1686 		Model* model = selection->FirstItem()->TargetModel();
1687 
1688 		if (model->IsDirectory()
1689 			|| (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE)
1690 				&& model->ResolveIfLink()->IsDirectory())) {
1691 
1692 			BMessage message(B_REFS_RECEIVED);
1693 			message.AddRef("refs", model->EntryRef());
1694 			PostMessage(&message);
1695 			return;
1696 		}
1697 	}
1698 
1699 	if (selection->CountItems()) {
1700 			// there are items selected
1701 			// message->fMessage->message from here to end
1702 		BMessage message(*fMessage);
1703 		// go through selection and add appropriate items
1704 		for (int32 index = 0; index < selection->CountItems(); index++) {
1705 			Model* model = selection->ItemAt(index)->TargetModel();
1706 
1707 			if (((fNodeFlavors & B_DIRECTORY_NODE) != 0
1708 					&& model->ResolveIfLink()->IsDirectory())
1709 				|| ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink())
1710 				|| ((fNodeFlavors & B_FILE_NODE) != 0
1711 					&& model->ResolveIfLink()->IsFile())) {
1712 				message.AddRef("refs", model->EntryRef());
1713 			}
1714 		}
1715 
1716 		OpenSelectionCommon(&message);
1717 	} else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) {
1718 		// Open the current directory.
1719 		BMessage message(*fMessage);
1720 		message.AddRef("refs", TargetModel()->EntryRef());
1721 		OpenSelectionCommon(&message);
1722 	}
1723 }
1724 
1725 
1726 void
1727 TFilePanel::SwitchDirMenuTo(const entry_ref* ref)
1728 {
1729 	BEntry entry(ref);
1730 	for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--)
1731 		delete fDirMenu->RemoveItem(index);
1732 
1733 	fDirMenuField->MenuBar()->RemoveItem((int32)0);
1734 	fDirMenu->Populate(&entry, 0, true, true, false, true);
1735 
1736 	ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(
1737 		fDirMenuField->MenuBar()->ItemAt(0));
1738 	ASSERT(item != NULL);
1739 
1740 	if (item != NULL)
1741 		item->SetEntry(&entry);
1742 }
1743 
1744 
1745 void
1746 TFilePanel::WindowActivated(bool active)
1747 {
1748 	// force focus to update properly
1749 	fBackView->Invalidate();
1750 	_inherited::WindowActivated(active);
1751 }
1752 
1753 
1754 //	#pragma mark -
1755 
1756 
1757 BFilePanelPoseView::BFilePanelPoseView(Model* model)
1758 	:
1759 	BPoseView(model, kListMode),
1760 	fIsDesktop(model->IsDesktop())
1761 {
1762 }
1763 
1764 
1765 void
1766 BFilePanelPoseView::StartWatching()
1767 {
1768 	TTracker::WatchNode(0, B_WATCH_MOUNT, this);
1769 
1770 	// inter-application observing
1771 	BMessenger tracker(kTrackerSignature);
1772 	BHandler::StartWatching(tracker, kVolumesOnDesktopChanged);
1773 }
1774 
1775 
1776 void
1777 BFilePanelPoseView::StopWatching()
1778 {
1779 	stop_watching(this);
1780 
1781 	// inter-application observing
1782 	BMessenger tracker(kTrackerSignature);
1783 	BHandler::StopWatching(tracker, kVolumesOnDesktopChanged);
1784 }
1785 
1786 
1787 bool
1788 BFilePanelPoseView::FSNotification(const BMessage* message)
1789 {
1790 	switch (message->FindInt32("opcode")) {
1791 		case B_DEVICE_MOUNTED:
1792 		{
1793 			if (IsDesktopView()) {
1794 				// Pretty much copied straight from DesktopPoseView.
1795 				// Would be better if the code could be shared somehow.
1796 				dev_t device;
1797 				if (message->FindInt32("new device", &device) != B_OK)
1798 					break;
1799 
1800 				ASSERT(TargetModel() != NULL);
1801 				TrackerSettings settings;
1802 
1803 				BVolume volume(device);
1804 				if (volume.InitCheck() != B_OK)
1805 					break;
1806 
1807 				if (settings.MountVolumesOntoDesktop()
1808 					&& (!volume.IsShared()
1809 						|| settings.MountSharedVolumesOntoDesktop())) {
1810 					// place an icon for the volume onto the desktop
1811 					CreateVolumePose(&volume, true);
1812 				}
1813 			}
1814 			break;
1815 		}
1816 
1817 		case B_DEVICE_UNMOUNTED:
1818 		{
1819 			dev_t device;
1820 			if (message->FindInt32("device", &device) == B_OK) {
1821 				if (TargetModel() != NULL
1822 					&& TargetModel()->NodeRef()->device == device) {
1823 					// Volume currently shown in this file panel
1824 					// disappeared, reset location to home directory
1825 					BMessage message(kSwitchToHome);
1826 					MessageReceived(&message);
1827 				}
1828 			}
1829 			break;
1830 		}
1831 	}
1832 	return _inherited::FSNotification(message);
1833 }
1834 
1835 
1836 void
1837 BFilePanelPoseView::RestoreState(AttributeStreamNode* node)
1838 {
1839 	_inherited::RestoreState(node);
1840 	fViewState->SetViewMode(kListMode);
1841 }
1842 
1843 
1844 void
1845 BFilePanelPoseView::RestoreState(const BMessage &message)
1846 {
1847 	_inherited::RestoreState(message);
1848 }
1849 
1850 
1851 void
1852 BFilePanelPoseView::SavePoseLocations(BRect*)
1853 {
1854 }
1855 
1856 
1857 EntryListBase*
1858 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref)
1859 {
1860 	if (IsDesktopView())
1861 		return DesktopPoseView::InitDesktopDirentIterator(this, ref);
1862 
1863 	return _inherited::InitDirentIterator(ref);
1864 }
1865 
1866 
1867 void
1868 BFilePanelPoseView::AddPosesCompleted()
1869 {
1870 	_inherited::AddPosesCompleted();
1871 	if (IsDesktopView())
1872 		CreateTrashPose();
1873 }
1874 
1875 
1876 void
1877 BFilePanelPoseView::SetIsDesktop(bool on)
1878 {
1879 	fIsDesktop = on;
1880 }
1881 
1882 
1883 bool
1884 BFilePanelPoseView::IsDesktopView() const
1885 {
1886 	return fIsDesktop;
1887 }
1888 
1889 
1890 void
1891 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared)
1892 {
1893 	if (IsDesktopView()) {
1894 		if (!visible)
1895 			RemoveRootPoses();
1896 		else
1897 			AddRootPoses(true, showShared);
1898 	}
1899 
1900 	TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window());
1901 	if (filepanel != NULL && TargetModel() != NULL)
1902 		filepanel->SetTo(TargetModel()->EntryRef());
1903 }
1904 
1905 
1906 void
1907 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message)
1908 {
1909 	bool showDisksIcon;
1910 	bool mountVolumesOnDesktop;
1911 	bool mountSharedVolumesOntoDesktop;
1912 
1913 	message->FindBool("ShowDisksIcon", &showDisksIcon);
1914 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1915 	message->FindBool("MountSharedVolumesOntoDesktop",
1916 		&mountSharedVolumesOntoDesktop);
1917 
1918 	BEntry entry("/");
1919 	Model model(&entry);
1920 	if (model.InitCheck() == B_OK) {
1921 		BMessage monitorMsg;
1922 		monitorMsg.what = B_NODE_MONITOR;
1923 
1924 		if (showDisksIcon)
1925 			monitorMsg.AddInt32("opcode", B_ENTRY_CREATED);
1926 		else
1927 			monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED);
1928 
1929 		monitorMsg.AddInt32("device", model.NodeRef()->device);
1930 		monitorMsg.AddInt64("node", model.NodeRef()->node);
1931 		monitorMsg.AddInt64("directory", model.EntryRef()->directory);
1932 		monitorMsg.AddString("name", model.EntryRef()->name);
1933 		TrackerSettings().SetShowDisksIcon(showDisksIcon);
1934 		if (Window() != NULL)
1935 			Window()->PostMessage(&monitorMsg, this);
1936 	}
1937 
1938 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1939 }
1940 
1941 
1942 void
1943 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message)
1944 {
1945 	bool mountVolumesOnDesktop = true;
1946 	bool mountSharedVolumesOntoDesktop = true;
1947 
1948 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1949 	message->FindBool("MountSharedVolumesOntoDesktop",
1950 		&mountSharedVolumesOntoDesktop);
1951 
1952 	ShowVolumes(false, mountSharedVolumesOntoDesktop);
1953 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1954 }
1955