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