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