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