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