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