xref: /haiku/src/kits/tracker/FilePanelPriv.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 
36 #include "FilePanelPriv.h"
37 
38 #include <string.h>
39 
40 #include <Alert.h>
41 #include <Application.h>
42 #include <Button.h>
43 #include <ControlLook.h>
44 #include <Catalog.h>
45 #include <Debug.h>
46 #include <Directory.h>
47 #include <FindDirectory.h>
48 #include <GridView.h>
49 #include <Locale.h>
50 #include <MenuBar.h>
51 #include <MenuField.h>
52 #include <MenuItem.h>
53 #include <MessageFilter.h>
54 #include <NodeInfo.h>
55 #include <NodeMonitor.h>
56 #include <Path.h>
57 #include <Roster.h>
58 #include <SymLink.h>
59 #include <ScrollView.h>
60 #include <String.h>
61 #include <StopWatch.h>
62 #include <TextControl.h>
63 #include <TextView.h>
64 #include <Volume.h>
65 #include <VolumeRoster.h>
66 
67 #include "Attributes.h"
68 #include "AttributeStream.h"
69 #include "AutoLock.h"
70 #include "Commands.h"
71 #include "CountView.h"
72 #include "DesktopPoseView.h"
73 #include "DirMenu.h"
74 #include "FavoritesMenu.h"
75 #include "FSUtils.h"
76 #include "FSClipboard.h"
77 #include "IconMenuItem.h"
78 #include "MimeTypes.h"
79 #include "NavMenu.h"
80 #include "Tracker.h"
81 #include "Utilities.h"
82 
83 #include "tracker_private.h"
84 
85 
86 #undef B_TRANSLATION_CONTEXT
87 #define B_TRANSLATION_CONTEXT "FilePanelPriv"
88 
89 
90 const char* kDefaultFilePanelTemplate = "FilePanelSettings";
91 
92 
93 static uint32
94 GetLinkFlavor(const Model* model, bool resolve = true)
95 {
96 	if (model && model->IsSymLink()) {
97 		if (!resolve)
98 			return B_SYMLINK_NODE;
99 		model = model->LinkTo();
100 	}
101 	if (!model)
102 		return 0;
103 
104 	if (model->IsDirectory())
105 		return B_DIRECTORY_NODE;
106 
107 	return B_FILE_NODE;
108 }
109 
110 
111 static filter_result
112 key_down_filter(BMessage* message, BHandler** handler, BMessageFilter* filter)
113 {
114 	ASSERT(filter != NULL);
115 	if (filter == NULL)
116 		return B_DISPATCH_MESSAGE;
117 
118 	TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper());
119 	ASSERT(panel != NULL);
120 
121 	if (panel == NULL)
122 		return B_DISPATCH_MESSAGE;
123 
124 	BPoseView* view = panel->PoseView();
125 	if (panel->TrackingMenu())
126 		return B_DISPATCH_MESSAGE;
127 
128 	uchar key;
129 	if (message->FindInt8("byte", (int8*)&key) != B_OK)
130 		return B_DISPATCH_MESSAGE;
131 
132 	int32 modifier = 0;
133 	message->FindInt32("modifiers", &modifier);
134 
135 	if (modifier & B_COMMAND_KEY && key == B_UP_ARROW) {
136 		filter->Looper()->PostMessage(kOpenParentDir);
137 		return B_SKIP_MESSAGE;
138 	}
139 
140 	if (modifier & B_COMMAND_KEY && key == 'w') {
141 		filter->Looper()->PostMessage(kCancelButton);
142 		return B_SKIP_MESSAGE;
143 	}
144 
145 	if (!modifier && key == B_ESCAPE) {
146 		if (view->ActivePose())
147 			view->CommitActivePose(false);
148 		else if (view->IsFiltering())
149 			filter->Looper()->PostMessage(B_CANCEL, *handler);
150 		else
151 			filter->Looper()->PostMessage(kCancelButton);
152 
153 		return B_SKIP_MESSAGE;
154 	}
155 
156 	if (key == B_RETURN && view->ActivePose()) {
157 		view->CommitActivePose();
158 
159 		return B_SKIP_MESSAGE;
160 	}
161 
162 	return B_DISPATCH_MESSAGE;
163 }
164 
165 
166 //	#pragma mark - TFilePanel
167 
168 
169 TFilePanel::TFilePanel(file_panel_mode mode, BMessenger* target,
170 	const BEntry* startDir, uint32 nodeFlavors, bool multipleSelection,
171 	BMessage* message, BRefFilter* filter, uint32 containerWindowFlags,
172 	window_look look, window_feel feel, bool hideWhenDone)
173 	:
174 	BContainerWindow(0, containerWindowFlags, look, feel, 0,
175 		B_CURRENT_WORKSPACE, false),
176 	fDirMenu(NULL),
177 	fDirMenuField(NULL),
178 	fTextControl(NULL),
179 	fClientObject(NULL),
180 	fSelectionIterator(0),
181 	fMessage(NULL),
182 	fHideWhenDone(hideWhenDone),
183 	fIsTrackingMenu(false),
184 	fDefaultStateRestored(false)
185 {
186 	InitIconPreloader();
187 
188 	fIsSavePanel = (mode == B_SAVE_PANEL);
189 
190 	const float labelSpacing = be_control_look->DefaultLabelSpacing();
191 	// approximately (84, 50, 568, 296) with default sizing
192 	BRect windRect(labelSpacing * 14.0f, labelSpacing * 8.0f,
193 		labelSpacing * 95.0f, labelSpacing * 49.0f);
194 	MoveTo(windRect.LeftTop());
195 	ResizeTo(windRect.Width(), windRect.Height());
196 
197 	fNodeFlavors = (nodeFlavors == 0) ? B_FILE_NODE : nodeFlavors;
198 
199 	if (target)
200 		fTarget = *target;
201 	else
202 		fTarget = BMessenger(be_app);
203 
204 	if (message)
205 		SetMessage(message);
206 	else if (fIsSavePanel)
207 		fMessage = new BMessage(B_SAVE_REQUESTED);
208 	else
209 		fMessage = new BMessage(B_REFS_RECEIVED);
210 
211 	gLocalizedNamePreferred
212 		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
213 
214 	// check for legal starting directory
215 	Model* model = new Model();
216 	bool useRoot = true;
217 
218 	if (startDir) {
219 		if (model->SetTo(startDir) == B_OK && model->IsDirectory())
220 			useRoot = false;
221 		else {
222 			delete model;
223 			model = new Model();
224 		}
225 	}
226 
227 	if (useRoot) {
228 		BPath path;
229 		if (find_directory(B_USER_DIRECTORY, &path) == B_OK) {
230 			BEntry entry(path.Path(), true);
231 			if (entry.InitCheck() == B_OK && model->SetTo(&entry) == B_OK)
232 				useRoot = false;
233 		}
234 	}
235 
236 	if (useRoot) {
237 		BVolume volume;
238 		BDirectory root;
239 		BVolumeRoster volumeRoster;
240 		volumeRoster.GetBootVolume(&volume);
241 		volume.GetRootDirectory(&root);
242 
243 		BEntry entry;
244 		root.GetEntry(&entry);
245 		model->SetTo(&entry);
246 	}
247 
248 	fTaskLoop = new PiggybackTaskLoop;
249 
250 	AutoLock<BWindow> lock(this);
251 	fBorderedView = new BorderedView;
252 	CreatePoseView(model);
253 	fBorderedView->GroupLayout()->SetInsets(1);
254 
255 	fPoseContainer = new BGridView(0.0, 0.0);
256 	fPoseContainer->GridLayout()->AddView(fBorderedView, 0, 1);
257 
258 	fCountContainer = new BGroupView(B_HORIZONTAL, 0);
259 	fPoseContainer->GridLayout()->AddView(fCountContainer, 0, 2);
260 
261 	fPoseView->SetRefFilter(filter);
262 	if (!fIsSavePanel)
263 		fPoseView->SetMultipleSelection(multipleSelection);
264 
265 	fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE);
266 	fPoseView->SetPoseEditing(false);
267 	AddCommonFilter(new BMessageFilter(B_KEY_DOWN, key_down_filter));
268 	AddCommonFilter(new BMessageFilter(B_SIMPLE_DATA,
269 		TFilePanel::MessageDropFilter));
270 	AddCommonFilter(new BMessageFilter(B_NODE_MONITOR, TFilePanel::FSFilter));
271 
272 	// inter-application observing
273 	BMessenger tracker(kTrackerSignature);
274 	BHandler::StartWatching(tracker, kDesktopFilePanelRootChanged);
275 
276 	Init();
277 
278 	// Avoid the need to save state later just because of changes made
279 	// during setup. This prevents unnecessary saving by Quit that can
280 	// overwrite user's changes previously saved from another panel object.
281 	if (StateNeedsSaving())
282 		SaveState(false);
283 }
284 
285 
286 TFilePanel::~TFilePanel()
287 {
288 	BMessenger tracker(kTrackerSignature);
289 	BHandler::StopWatching(tracker, kDesktopFilePanelRootChanged);
290 
291 	delete fMessage;
292 }
293 
294 
295 filter_result
296 TFilePanel::MessageDropFilter(BMessage* message, BHandler**,
297 	BMessageFilter* filter)
298 {
299 	if (message == NULL || !message->WasDropped())
300 		return B_DISPATCH_MESSAGE;
301 
302 	ASSERT(filter != NULL);
303 	if (filter == NULL)
304 		return B_DISPATCH_MESSAGE;
305 
306 	TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper());
307 	ASSERT(panel != NULL);
308 
309 	if (panel == NULL)
310 		return B_DISPATCH_MESSAGE;
311 
312 	uint32 type;
313 	int32 count;
314 	if (message->GetInfo("refs", &type, &count) != B_OK)
315 		return B_SKIP_MESSAGE;
316 
317 	if (count != 1)
318 		return B_SKIP_MESSAGE;
319 
320 	entry_ref ref;
321 	if (message->FindRef("refs", &ref) != B_OK)
322 		return B_SKIP_MESSAGE;
323 
324 	BEntry entry(&ref);
325 	if (entry.InitCheck() != B_OK)
326 		return B_SKIP_MESSAGE;
327 
328 	// if the entry is a symlink
329 	// resolve it and see if it is a directory
330 	// pass it on if it is
331 	if (entry.IsSymLink()) {
332 		entry_ref resolvedRef;
333 
334 		entry.GetRef(&resolvedRef);
335 		BEntry resolvedEntry(&resolvedRef, true);
336 
337 		if (resolvedEntry.IsDirectory()) {
338 			// both entry and ref need to be the correct locations
339 			// for the last setto
340 			resolvedEntry.GetRef(&ref);
341 			entry.SetTo(&ref);
342 		}
343 	}
344 
345 	// if not a directory, set to the parent, and select the child
346 	if (!entry.IsDirectory()) {
347 		node_ref child;
348 		if (entry.GetNodeRef(&child) != B_OK)
349 			return B_SKIP_MESSAGE;
350 
351 		BPath path(&entry);
352 
353 		if (entry.GetParent(&entry) != B_OK)
354 			return B_SKIP_MESSAGE;
355 
356 		entry.GetRef(&ref);
357 
358 		panel->fTaskLoop->RunLater(NewMemberFunctionObjectWithResult
359 			(&TFilePanel::SelectChildInParent, panel,
360 			const_cast<const entry_ref*>(&ref),
361 			const_cast<const node_ref*>(&child)),
362 			ref == *panel->TargetModel()->EntryRef() ? 0 : 100000, 200000,
363 				5000000);
364 				// if the target directory is already current, we won't
365 				// delay the initial selection try
366 
367 		// also set the save name to the dragged in entry
368 		if (panel->IsSavePanel())
369 			panel->SetSaveText(path.Leaf());
370 	}
371 
372 	panel->SetTo(&ref);
373 
374 	return B_SKIP_MESSAGE;
375 }
376 
377 
378 filter_result
379 TFilePanel::FSFilter(BMessage* message, BHandler**, BMessageFilter* filter)
380 {
381 	if (message == NULL)
382 		return B_DISPATCH_MESSAGE;
383 
384 	ASSERT(filter != NULL);
385 	if (filter == NULL)
386 		return B_DISPATCH_MESSAGE;
387 
388 	TFilePanel* panel = dynamic_cast<TFilePanel*>(filter->Looper());
389 	ASSERT(panel != NULL);
390 
391 	if (panel == NULL)
392 		return B_DISPATCH_MESSAGE;
393 
394 	switch (message->FindInt32("opcode")) {
395 		case B_ENTRY_MOVED:
396 		{
397 			node_ref itemNode;
398 			message->FindInt64("node", (int64*)&itemNode.node);
399 
400 			node_ref dirNode;
401 			message->FindInt32("device", &dirNode.device);
402 			itemNode.device = dirNode.device;
403 			message->FindInt64("to directory", (int64*)&dirNode.node);
404 
405 			const char* name;
406 			if (message->FindString("name", &name) != B_OK)
407 				break;
408 
409 			// if current directory moved, update entry ref and menu
410 			// but not wind title
411 			if (*(panel->TargetModel()->NodeRef()) == itemNode) {
412 				panel->TargetModel()->UpdateEntryRef(&dirNode, name);
413 				panel->SetTo(panel->TargetModel()->EntryRef());
414 				return B_SKIP_MESSAGE;
415 			}
416 			break;
417 		}
418 
419 		case B_ENTRY_REMOVED:
420 		{
421 			node_ref itemNode;
422 			message->FindInt32("device", &itemNode.device);
423 			message->FindInt64("node", (int64*)&itemNode.node);
424 
425 			// if folder we're watching is deleted, switch to root
426 			// or Desktop
427 			if (*(panel->TargetModel()->NodeRef()) == itemNode) {
428 				BVolumeRoster volumeRoster;
429 				BVolume volume;
430 				volumeRoster.GetBootVolume(&volume);
431 
432 				BDirectory root;
433 				volume.GetRootDirectory(&root);
434 
435 				BEntry entry;
436 				entry_ref ref;
437 				root.GetEntry(&entry);
438 				entry.GetRef(&ref);
439 
440 				panel->SwitchDirToDesktopIfNeeded(ref);
441 
442 				panel->SetTo(&ref);
443 				return B_SKIP_MESSAGE;
444 			}
445 			break;
446 		}
447 	}
448 
449 	return B_DISPATCH_MESSAGE;
450 }
451 
452 
453 void
454 TFilePanel::DispatchMessage(BMessage* message, BHandler* handler)
455 {
456 	_inherited::DispatchMessage(message, handler);
457 	if (message->what == B_KEY_DOWN || message->what == B_MOUSE_DOWN)
458 		AdjustButton();
459 }
460 
461 
462 BFilePanelPoseView*
463 TFilePanel::PoseView() const
464 {
465 	ASSERT(dynamic_cast<BFilePanelPoseView*>(fPoseView) != NULL);
466 
467 	return static_cast<BFilePanelPoseView*>(fPoseView);
468 }
469 
470 
471 bool
472 TFilePanel::QuitRequested()
473 {
474 	// If we have a client object then this window will simply hide
475 	// itself, to be closed later when the client object itself is
476 	// destroyed. If we have no client then we must have been started
477 	// from the "easy" functions which simply instantiate a TFilePanel
478 	// and expect it to go away by itself
479 
480 	if (fClientObject != NULL) {
481 		Hide();
482 		if (fClientObject != NULL)
483 			fClientObject->WasHidden();
484 
485 		BMessage message(*fMessage);
486 		message.what = B_CANCEL;
487 		message.AddInt32("old_what", (int32)fMessage->what);
488 		message.AddPointer("source", fClientObject);
489 		fTarget.SendMessage(&message);
490 
491 		return false;
492 	}
493 
494 	return _inherited::QuitRequested();
495 }
496 
497 
498 BRefFilter*
499 TFilePanel::Filter() const
500 {
501 	return fPoseView->RefFilter();
502 }
503 
504 
505 void
506 TFilePanel::SetTarget(BMessenger target)
507 {
508 	fTarget = target;
509 }
510 
511 
512 void
513 TFilePanel::SetMessage(BMessage* message)
514 {
515 	delete fMessage;
516 	fMessage = new BMessage(*message);
517 }
518 
519 
520 void
521 TFilePanel::SetRefFilter(BRefFilter* filter)
522 {
523 	ASSERT(filter != NULL);
524 	if (filter == NULL)
525 		return;
526 
527 	fPoseView->SetRefFilter(filter);
528 	fPoseView->CommitActivePose();
529 	fPoseView->Refresh();
530 
531 	if (fMenuBar == NULL)
532 		return;
533 
534 	BMenuItem* favoritesItem = fMenuBar->FindItem(B_TRANSLATE("Favorites"));
535 	if (favoritesItem == NULL)
536 		return;
537 
538 	FavoritesMenu* favoritesSubMenu
539 		= dynamic_cast<FavoritesMenu*>(favoritesItem->Submenu());
540 	if (favoritesSubMenu != NULL)
541 		favoritesSubMenu->SetRefFilter(filter);
542 }
543 
544 
545 void
546 TFilePanel::SetTo(const entry_ref* ref)
547 {
548 	if (ref == NULL)
549 		return;
550 
551 	entry_ref setToRef(*ref);
552 
553 	bool isDesktop = SwitchDirToDesktopIfNeeded(setToRef);
554 
555 	BEntry entry(&setToRef);
556 	if (entry.InitCheck() != B_OK || !entry.IsDirectory())
557 		return;
558 
559 	PoseView()->SetIsDesktop(isDesktop);
560 	PoseView()->SwitchDir(&setToRef);
561 	SwitchDirMenuTo(&setToRef);
562 
563 	AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome));
564 		// our shortcut got possibly removed because the home
565 		// menu item got removed - we shouldn't really have to do
566 		// this - this is a workaround for a kit bug.
567 }
568 
569 
570 void
571 TFilePanel::Rewind()
572 {
573 	fSelectionIterator = 0;
574 }
575 
576 
577 void
578 TFilePanel::SetClientObject(BFilePanel* panel)
579 {
580 	fClientObject = panel;
581 }
582 
583 
584 void
585 TFilePanel::AdjustButton()
586 {
587 	// adjust button state
588 	BButton* button = dynamic_cast<BButton*>(FindView("default button"));
589 	if (button == NULL)
590 		return;
591 
592 	BTextControl* textControl
593 		= dynamic_cast<BTextControl*>(FindView("text view"));
594 	BObjectList<BPose>* selectionList = fPoseView->SelectionList();
595 	BString buttonText = fButtonText;
596 	bool enabled = false;
597 
598 	if (fIsSavePanel && textControl != NULL) {
599 		enabled = textControl->Text()[0] != '\0';
600 		if (fPoseView->IsFocus()) {
601 			fPoseView->ShowSelection(true);
602 			if (selectionList->CountItems() == 1) {
603 				Model* model = selectionList->FirstItem()->TargetModel();
604 				if (model->ResolveIfLink()->IsDirectory()) {
605 					enabled = true;
606 					buttonText = B_TRANSLATE("Open");
607 				} else {
608 					// insert the name of the selected model into
609 					// the text field, do not alter focus
610 					textControl->SetText(model->Name());
611 				}
612 			}
613 		} else
614 			fPoseView->ShowSelection(false);
615 	} else {
616 		int32 count = selectionList->CountItems();
617 		if (count) {
618 			enabled = true;
619 
620 			// go through selection list looking at content
621 			for (int32 index = 0; index < count; index++) {
622 				Model* model = selectionList->ItemAt(index)->TargetModel();
623 
624 				uint32 modelFlavor = GetLinkFlavor(model, false);
625 				uint32 linkFlavor = GetLinkFlavor(model, true);
626 
627 				// if only one item is selected and we're not in dir
628 				// selection mode then we don't disable button ever
629 				if ((modelFlavor == B_DIRECTORY_NODE
630 						|| linkFlavor == B_DIRECTORY_NODE)
631 					&& count == 1) {
632 					break;
633 				}
634 
635 				if ((fNodeFlavors & modelFlavor) == 0
636 					&& (fNodeFlavors & linkFlavor) == 0) {
637 					enabled = false;
638 					break;
639 				}
640 			}
641 		} else if ((fNodeFlavors & B_DIRECTORY_NODE) != 0) {
642 			// No selection, but the current directory could be opened.
643 			enabled = true;
644 		}
645 	}
646 
647 	button->SetLabel(buttonText.String());
648 	button->SetEnabled(enabled);
649 }
650 
651 
652 void
653 TFilePanel::SelectionChanged()
654 {
655 	AdjustButton();
656 
657 	if (fClientObject)
658 		fClientObject->SelectionChanged();
659 }
660 
661 
662 status_t
663 TFilePanel::GetNextEntryRef(entry_ref* ref)
664 {
665 	if (!ref)
666 		return B_ERROR;
667 
668 	BPose* pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++);
669 	if (!pose)
670 		return B_ERROR;
671 
672 	*ref = *pose->TargetModel()->EntryRef();
673 	return B_OK;
674 }
675 
676 
677 BPoseView*
678 TFilePanel::NewPoseView(Model* model, uint32)
679 {
680 	return new BFilePanelPoseView(model);
681 }
682 
683 
684 void
685 TFilePanel::Init(const BMessage*)
686 {
687 	BRect windRect(Bounds());
688 	fBackView = new BView(Bounds(), "View", B_FOLLOW_ALL, 0);
689 	fBackView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
690 	AddChild(fBackView);
691 
692 	// add poseview menu bar
693 	fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar");
694 	fMenuBar->SetBorder(B_BORDER_FRAME);
695 	fBackView->AddChild(fMenuBar);
696 
697 	// add directory menu and menufield
698 	font_height ht;
699 	be_plain_font->GetHeight(&ht);
700 	const float f_height = ht.ascent + ht.descent + ht.leading;
701 	const float spacing = be_control_look->ComposeSpacing(B_USE_SMALL_SPACING);
702 
703 	BRect rect;
704 	rect.top = fMenuBar->Bounds().Height() + spacing;
705 	rect.left = spacing;
706 	rect.right = rect.left + (spacing * 50);
707 	rect.bottom = rect.top + (f_height > 22 ? f_height : 22);
708 
709 	fDirMenuField = new BMenuField(rect, "DirMenuField", "", NULL);
710 	fDirMenuField->MenuBar()->SetFont(be_plain_font);
711 	fDirMenuField->SetDivider(0);
712 	fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f);
713 		// Make room for the icon
714 
715 	fDirMenu = new BDirMenu(fDirMenuField->MenuBar(),
716 		this, kSwitchDirectory, "refs");
717 
718 	BEntry entry(TargetModel()->EntryRef());
719 	if (entry.InitCheck() == B_OK)
720 		fDirMenu->Populate(&entry, 0, true, true, false, true);
721 	else
722 		fDirMenu->Populate(0, 0, true, true, false, true);
723 
724 	fBackView->AddChild(fDirMenuField);
725 
726 	// add buttons
727 	fButtonText = fIsSavePanel ? B_TRANSLATE("Save") : B_TRANSLATE("Open");
728 	BButton* default_button = new BButton(BRect(), "default button",
729 		fButtonText.String(), new BMessage(kDefaultButton),
730 		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
731 	BSize preferred = default_button->PreferredSize();
732 	const BRect defaultButtonRect = BRect(BPoint(
733 		windRect.Width() - (preferred.Width() + spacing + be_control_look->GetScrollBarWidth()),
734 		windRect.Height() - (preferred.Height() + spacing)),
735 		preferred);
736 	default_button->MoveTo(defaultButtonRect.LeftTop());
737 	default_button->ResizeTo(preferred);
738 	fBackView->AddChild(default_button);
739 
740 	BButton* cancel_button = new BButton(BRect(), "cancel button",
741 		B_TRANSLATE("Cancel"), new BMessage(kCancelButton),
742 		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
743 	preferred = cancel_button->PreferredSize();
744 	cancel_button->MoveTo(defaultButtonRect.LeftTop()
745 		- BPoint(preferred.Width() + spacing, 0));
746 	cancel_button->ResizeTo(preferred);
747 	fBackView->AddChild(cancel_button);
748 
749 	// add file name text view
750 	if (fIsSavePanel) {
751 		BRect rect(defaultButtonRect);
752 		rect.left = spacing;
753 		rect.right = rect.left + spacing * 28;
754 
755 		fTextControl = new BTextControl(rect, "text view",
756 			B_TRANSLATE("save text"), "", NULL,
757 			B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
758 		DisallowMetaKeys(fTextControl->TextView());
759 		DisallowFilenameKeys(fTextControl->TextView());
760 		fBackView->AddChild(fTextControl);
761 		fTextControl->SetDivider(0.0f);
762 		fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
763 	}
764 
765 	// Add PoseView
766 	PoseView()->SetName("ActualPoseView");
767 	fPoseContainer->SetName("PoseView");
768 	fPoseContainer->SetResizingMode(B_FOLLOW_ALL);
769 	fBorderedView->EnableBorderHighlight(true);
770 
771 	rect.left = spacing;
772 	rect.top = fDirMenuField->Frame().bottom + spacing;
773 	rect.right = windRect.Width() - spacing;
774 	rect.bottom = defaultButtonRect.top - spacing;
775 	fPoseContainer->MoveTo(rect.LeftTop());
776 	fPoseContainer->ResizeTo(rect.Size());
777 
778 	PoseView()->AddScrollBars();
779 	PoseView()->SetDragEnabled(false);
780 	PoseView()->SetDropEnabled(false);
781 	PoseView()->SetSelectionHandler(this);
782 	PoseView()->SetSelectionChangedHook(true);
783 	PoseView()->DisableSaveLocation();
784 
785 	if (fIsSavePanel)
786 		fBackView->AddChild(fPoseContainer, fTextControl);
787 	else
788 		fBackView->AddChild(fPoseContainer);
789 
790 	AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton));
791 	AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome));
792 	AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
793 		new BMessage(kShowSelectionWindow));
794 	AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), this);
795 	AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection),
796 		PoseView());
797 	AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView());
798 	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir));
799 	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
800 		new BMessage(kOpenDir));
801 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir));
802 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
803 		new BMessage(kOpenParentDir));
804 
805 	if (!fIsSavePanel && (fNodeFlavors & B_DIRECTORY_NODE) == 0)
806 		default_button->SetEnabled(false);
807 
808 	default_button->MakeDefault(true);
809 
810 	RestoreState();
811 
812 	AddMenus();
813 	AddContextMenus();
814 
815 	FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"),
816 		new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED),
817 		BMessenger(this), IsSavePanel(), fPoseView->RefFilter());
818 	favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"),
819 		new BMessage(kAddCurrentDir)));
820 	favorites->AddItem(new BMenuItem(
821 		B_TRANSLATE("Edit favorites" B_UTF8_ELLIPSIS),
822 		new BMessage(kEditFavorites)));
823 
824 	fMenuBar->AddItem(favorites);
825 
826 	// configure menus
827 	BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window"));
828 	if (item) {
829 		fMenuBar->RemoveItem(item);
830 		delete item;
831 	}
832 
833 	item = fMenuBar->FindItem(B_TRANSLATE("File"));
834 	if (item) {
835 		BMenu* menu = item->Submenu();
836 		if (menu) {
837 			item = menu->FindItem(kOpenSelection);
838 			if (item && menu->RemoveItem(item))
839 				delete item;
840 
841 			// remove add-ons menu, identifier menu, separator
842 			item = menu->FindItem(B_TRANSLATE("Add-ons"));
843 			if (item) {
844 				int32 index = menu->IndexOf(item);
845 				delete menu->RemoveItem(index);
846 				delete menu->RemoveItem(--index);
847 				delete menu->RemoveItem(--index);
848 			}
849 
850 			// remove separator
851 			item = menu->FindItem(B_CUT);
852 			if (item) {
853 				item = menu->ItemAt(menu->IndexOf(item)-1);
854 				if (item && menu->RemoveItem(item))
855 					delete item;
856 			}
857 		}
858 	}
859 
860 	PoseView()->ScrollTo(B_ORIGIN);
861 	PoseView()->UpdateScrollRange();
862 	PoseView()->ScrollTo(B_ORIGIN);
863 
864 	// Focus on text control initially, but do not alter focus afterwords
865 	// because pose view focus is needed for Cut/Copy/Paste to work.
866 
867 	if (fIsSavePanel && fTextControl != NULL) {
868 		fTextControl->MakeFocus();
869 		fTextControl->TextView()->SelectAll();
870 	} else
871 		PoseView()->MakeFocus();
872 
873 	app_info info;
874 	BString title;
875 	if (be_app->GetAppInfo(&info) == B_OK) {
876 		if (!gLocalizedNamePreferred
877 			|| BLocaleRoster::Default()->GetLocalizedFileName(
878 				title, info.ref, false) != B_OK)
879 			title = info.ref.name;
880 		title << ": ";
881 	}
882 	title << fButtonText;	// Open or Save
883 
884 	SetTitle(title.String());
885 
886 	SetSizeLimits(spacing * 60, 10000, spacing * 33, 10000);
887 }
888 
889 
890 void
891 TFilePanel::RestoreState()
892 {
893 	BNode defaultingNode;
894 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
895 			false)) {
896 		AttributeStreamFileNode streamNodeSource(&defaultingNode);
897 		RestoreWindowState(&streamNodeSource);
898 		PoseView()->Init(&streamNodeSource);
899 		fDefaultStateRestored = true;
900 	} else {
901 		RestoreWindowState(NULL);
902 		PoseView()->Init(NULL);
903 		fDefaultStateRestored = false;
904 	}
905 
906 	// Finish UI creation now that the PoseView is initialized
907 	InitLayout();
908 }
909 
910 
911 void
912 TFilePanel::SaveState(bool)
913 {
914 	BNode defaultingNode;
915 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
916 		true, false)) {
917 		AttributeStreamFileNode streamNodeDestination(&defaultingNode);
918 		SaveWindowState(&streamNodeDestination);
919 		PoseView()->SaveState(&streamNodeDestination);
920 		fStateNeedsSaving = false;
921 	}
922 }
923 
924 
925 void
926 TFilePanel::SaveState(BMessage &message) const
927 {
928 	_inherited::SaveState(message);
929 }
930 
931 
932 void
933 TFilePanel::RestoreWindowState(AttributeStreamNode* node)
934 {
935 	SetSizeLimits(360, 10000, 200, 10000);
936 	if (!node)
937 		return;
938 
939 	const char* rectAttributeName = kAttrWindowFrame;
940 	BRect frame(Frame());
941 	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
942 		== sizeof(BRect)) {
943 		MoveTo(frame.LeftTop());
944 		ResizeTo(frame.Width(), frame.Height());
945 	}
946 	fStateNeedsSaving = false;
947 }
948 
949 
950 void
951 TFilePanel::RestoreState(const BMessage &message)
952 {
953 	_inherited::RestoreState(message);
954 }
955 
956 
957 void
958 TFilePanel::RestoreWindowState(const BMessage &message)
959 {
960 	_inherited::RestoreWindowState(message);
961 }
962 
963 
964 void
965 TFilePanel::AddFileContextMenus(BMenu* menu)
966 {
967 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
968 		new BMessage(kGetInfo), 'I'));
969 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
970 		new BMessage(kEditItem), 'E'));
971 	menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"),
972 		new BMessage(kDuplicateSelection), 'D'));
973 	menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash()
974 		? B_TRANSLATE("Delete")
975 		: B_TRANSLATE("Move to Trash"),
976 		new BMessage(kMoveToTrash), 'T'));
977 	menu->AddSeparatorItem();
978 
979 	BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
980 		new BMessage(B_CUT), 'X');
981 	menu->AddItem(cutItem);
982 	BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
983 		new BMessage(B_COPY), 'C');
984 	menu->AddItem(copyItem);
985 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
986 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
987 		new BMessage(B_PASTE), 'V');
988 	menu->AddItem(pasteItem);
989 #endif
990 
991 	menu->SetTargetForItems(PoseView());
992 	cutItem->SetTarget(this);
993 	copyItem->SetTarget(this);
994 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
995 	pasteItem->SetTarget(this);
996 #endif
997 }
998 
999 
1000 void
1001 TFilePanel::AddVolumeContextMenus(BMenu* menu)
1002 {
1003 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
1004 		new BMessage(kOpenSelection), 'O'));
1005 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
1006 		new BMessage(kGetInfo), 'I'));
1007 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
1008 		new BMessage(kEditItem), 'E'));
1009 
1010 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1011 	menu->AddSeparatorItem();
1012 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
1013 		new BMessage(B_PASTE), 'V');
1014 #endif
1015 
1016 	menu->SetTargetForItems(PoseView());
1017 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1018 	pasteItem->SetTarget(this);
1019 #endif
1020 }
1021 
1022 
1023 void
1024 TFilePanel::AddWindowContextMenus(BMenu* menu)
1025 {
1026 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
1027 		new BMessage(kNewFolder), 'N');
1028 	item->SetTarget(PoseView());
1029 	menu->AddItem(item);
1030 	menu->AddSeparatorItem();
1031 
1032 #if CUT_COPY_PASTE_IN_CONTEXT_MENU
1033 	item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
1034 	item->SetTarget(this);
1035 	menu->AddItem(item);
1036 	menu->AddSeparatorItem();
1037 #endif
1038 
1039 	item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
1040 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
1041 	item->SetTarget(PoseView());
1042 	menu->AddItem(item);
1043 
1044 	item = new BMenuItem(B_TRANSLATE("Select all"),
1045 		new BMessage(B_SELECT_ALL), 'A');
1046 	item->SetTarget(this);
1047 	menu->AddItem(item);
1048 
1049 	item = new BMenuItem(B_TRANSLATE("Invert selection"),
1050 		new BMessage(kInvertSelection), 'S');
1051 	item->SetTarget(PoseView());
1052 	menu->AddItem(item);
1053 
1054 	item = new BMenuItem(B_TRANSLATE("Go to parent"),
1055 		new BMessage(kOpenParentDir), B_UP_ARROW);
1056 	item->SetTarget(this);
1057 	menu->AddItem(item);
1058 }
1059 
1060 
1061 void
1062 TFilePanel::AddDropContextMenus(BMenu*)
1063 {
1064 }
1065 
1066 
1067 void
1068 TFilePanel::MenusBeginning()
1069 {
1070 	if (fMenuBar == NULL)
1071 		return;
1072 
1073 	if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
1074 		// don't commit active pose if only a keyboard shortcut is
1075 		// invoked - this would prevent Cut/Copy/Paste from working
1076 		PoseView()->CommitActivePose();
1077 	}
1078 
1079 	EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()
1080 		&& !PoseView()->TargetVolumeIsReadOnly());
1081 	EnableNamedMenuItem(fMenuBar, kDuplicateSelection,
1082 		PoseView()->CanMoveToTrashOrDuplicate());
1083 	EnableNamedMenuItem(fMenuBar, kMoveToTrash,
1084 		PoseView()->CanMoveToTrashOrDuplicate());
1085 	EnableNamedMenuItem(fMenuBar, kEditItem, PoseView()->CanEditName());
1086 
1087 	SetCutItem(fMenuBar);
1088 	SetCopyItem(fMenuBar);
1089 	SetPasteItem(fMenuBar);
1090 
1091 	fIsTrackingMenu = true;
1092 }
1093 
1094 
1095 void
1096 TFilePanel::MenusEnded()
1097 {
1098 	fIsTrackingMenu = false;
1099 }
1100 
1101 
1102 void
1103 TFilePanel::ShowContextMenu(BPoint where, const entry_ref* ref)
1104 {
1105 	ASSERT(IsLocked());
1106 	BPoint global(where);
1107 	PoseView()->ConvertToScreen(&global);
1108 	PoseView()->CommitActivePose();
1109 
1110 	if (ref != NULL) {
1111 		// clicked on a pose, show file or volume context menu
1112 		Model model(ref);
1113 		if (model.InitCheck() != B_OK)
1114 			return; // bail out, do not show context menu
1115 
1116 		if (TargetModel()->IsRoot() || model.IsVolume()) {
1117 			// Volume context menu
1118 			fContextMenu = fVolumeContextMenu;
1119 			EnableNamedMenuItem(fContextMenu, kOpenSelection, true);
1120 			EnableNamedMenuItem(fContextMenu, kEditItem,
1121 				PoseView()->CanEditName());
1122 
1123 			SetPasteItem(fContextMenu);
1124 		} else {
1125 			// File context menu
1126 			fContextMenu = fFileContextMenu;
1127 			EnableNamedMenuItem(fContextMenu, kEditItem,
1128 				PoseView()->CanEditName());
1129 			EnableNamedMenuItem(fContextMenu, kDuplicateSelection,
1130 				PoseView()->CanMoveToTrashOrDuplicate());
1131 			EnableNamedMenuItem(fContextMenu, kMoveToTrash,
1132 				PoseView()->CanMoveToTrashOrDuplicate());
1133 
1134 			SetCutItem(fContextMenu);
1135 			SetCopyItem(fContextMenu);
1136 			SetPasteItem(fContextMenu);
1137 		}
1138 	} else {
1139 		// Window context menu
1140 		fContextMenu = fWindowContextMenu;
1141 		EnableNamedMenuItem(fContextMenu, kNewFolder,
1142 			!TargetModel()->IsRoot()
1143 				&& !PoseView()->TargetVolumeIsReadOnly());
1144 		EnableNamedMenuItem(fContextMenu, kOpenParentDir,
1145 			!TargetModel()->IsRoot());
1146 
1147 		SetPasteItem(fContextMenu);
1148 	}
1149 
1150 	// context menu invalid or popup window is already open
1151 	if (fContextMenu == NULL || fContextMenu->Window() != NULL)
1152 		return;
1153 
1154 	fContextMenu->Go(global, true, true, true);
1155 	fContextMenu = NULL;
1156 }
1157 
1158 
1159 void
1160 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*)
1161 {
1162 	// do nothing here so nav menu doesn't get added
1163 }
1164 
1165 
1166 void
1167 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text)
1168 {
1169 	switch (selector) {
1170 		case B_CANCEL_BUTTON:
1171 			{
1172 				BButton* button
1173 					= dynamic_cast<BButton*>(FindView("cancel button"));
1174 				if (button == NULL)
1175 					break;
1176 
1177 				float old_width = button->StringWidth(button->Label());
1178 				button->SetLabel(text);
1179 				float delta = old_width - button->StringWidth(text);
1180 				if (delta) {
1181 					button->MoveBy(delta, 0);
1182 					button->ResizeBy(-delta, 0);
1183 				}
1184 			}
1185 			break;
1186 
1187 		case B_DEFAULT_BUTTON:
1188 			{
1189 				fButtonText = text;
1190 				float delta = 0;
1191 				BButton* button
1192 					= dynamic_cast<BButton*>(FindView("default button"));
1193 				if (button != NULL) {
1194 					float old_width = button->StringWidth(button->Label());
1195 					button->SetLabel(text);
1196 					delta = old_width - button->StringWidth(text);
1197 					if (delta) {
1198 						button->MoveBy(delta, 0);
1199 						button->ResizeBy(-delta, 0);
1200 					}
1201 				}
1202 
1203 				// now must move cancel button
1204 				button = dynamic_cast<BButton*>(FindView("cancel button"));
1205 				if (button != NULL)
1206 					button->MoveBy(delta, 0);
1207 			}
1208 			break;
1209 	}
1210 }
1211 
1212 
1213 void
1214 TFilePanel::SetSaveText(const char* text)
1215 {
1216 	if (text == NULL)
1217 		return;
1218 
1219 	BTextControl* textControl
1220 		= dynamic_cast<BTextControl*>(FindView("text view"));
1221 	if (textControl != NULL) {
1222 		textControl->SetText(text);
1223 		if (textControl->TextView() != NULL)
1224 			textControl->TextView()->SelectAll();
1225 	}
1226 }
1227 
1228 
1229 void
1230 TFilePanel::MessageReceived(BMessage* message)
1231 {
1232 	entry_ref ref;
1233 
1234 	switch (message->what) {
1235 		case B_REFS_RECEIVED:
1236 			// item was double clicked in file panel (PoseView)
1237 			if (message->FindRef("refs", &ref) == B_OK) {
1238 				BEntry entry(&ref, true);
1239 				if (entry.InitCheck() == B_OK) {
1240 					// Double-click on dir or link-to-dir ALWAYS opens the
1241 					// dir. If more than one dir is selected, the first is
1242 					// entered.
1243 					if (entry.IsDirectory()) {
1244 						entry.GetRef(&ref);
1245 						bool isDesktop = SwitchDirToDesktopIfNeeded(ref);
1246 
1247 						PoseView()->SetIsDesktop(isDesktop);
1248 						entry.SetTo(&ref);
1249 						PoseView()->SwitchDir(&ref);
1250 						SwitchDirMenuTo(&ref);
1251 					} else {
1252 						// Otherwise, we have a file or a link to a file.
1253 						// AdjustButton has already tested the flavor;
1254 						// all we have to do is see if the button is enabled.
1255 						BButton* button = dynamic_cast<BButton*>(
1256 							FindView("default button"));
1257 						if (button == NULL || !button->IsEnabled())
1258 							break;
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