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