xref: /haiku/src/kits/tracker/FilePanelPriv.cpp (revision f73f5d4c42a01ece688cbb57b5d332cc0f68b2c6)
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, 568, 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
511 		= dynamic_cast<BTextControl*>(FindView("text view"));
512 	BObjectList<BPose>* selectionList = fPoseView->SelectionList();
513 	BString buttonText = fButtonText;
514 	bool enabled = false;
515 
516 	if (fIsSavePanel && textControl) {
517 		enabled = textControl->Text()[0] != '\0';
518 		if (fPoseView->IsFocus()) {
519 			fPoseView->ShowSelection(true);
520 			if (selectionList->CountItems() == 1) {
521 				Model* model = selectionList->FirstItem()->TargetModel();
522 				if (model->ResolveIfLink()->IsDirectory()) {
523 					enabled = true;
524 					buttonText = B_TRANSLATE("Open");
525 				} else {
526 					// insert the name of the selected model into
527 					// the text field
528 					textControl->SetText(model->Name());
529 					textControl->MakeFocus(true);
530 				}
531 			}
532 		} else
533 			fPoseView->ShowSelection(false);
534 	} else {
535 		int32 count = selectionList->CountItems();
536 		if (count) {
537 			enabled = true;
538 
539 			// go through selection list looking at content
540 			for (int32 index = 0; index < count; index++) {
541 				Model* model = selectionList->ItemAt(index)->TargetModel();
542 
543 				uint32 modelFlavor = GetLinkFlavor(model, false);
544 				uint32 linkFlavor = GetLinkFlavor(model, true);
545 
546 				// if only one item is selected and we're not in dir
547 				// selection mode then we don't disable button ever
548 				if ((modelFlavor == B_DIRECTORY_NODE
549 						|| linkFlavor == B_DIRECTORY_NODE)
550 					&& count == 1)
551 				  break;
552 
553 				if ((fNodeFlavors & modelFlavor) == 0
554 					&& (fNodeFlavors & linkFlavor) == 0) {
555 		    		enabled = false;
556 					break;
557 				}
558 			}
559 		}
560 	}
561 
562 	button->SetLabel(buttonText.String());
563 	button->SetEnabled(enabled);
564 }
565 
566 
567 void
568 TFilePanel::SelectionChanged()
569 {
570 	AdjustButton();
571 
572 	if (fClientObject)
573 		fClientObject->SelectionChanged();
574 }
575 
576 
577 status_t
578 TFilePanel::GetNextEntryRef(entry_ref* ref)
579 {
580 	if (!ref)
581 		return B_ERROR;
582 
583 	BPose* pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++);
584 	if (!pose)
585 		return B_ERROR;
586 
587 	*ref = *pose->TargetModel()->EntryRef();
588 	return B_OK;
589 }
590 
591 
592 BPoseView*
593 TFilePanel::NewPoseView(Model* model, BRect rect, uint32)
594 {
595 	return new BFilePanelPoseView(model, rect);
596 }
597 
598 
599 void
600 TFilePanel::Init(const BMessage*)
601 {
602 	BRect windRect(Bounds());
603 	AddChild(fBackView = new BackgroundView(windRect));
604 
605 	// add poseview menu bar
606 	fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar");
607 	fMenuBar->SetBorder(B_BORDER_FRAME);
608 	fBackView->AddChild(fMenuBar);
609 
610 	AddMenus();
611 	AddContextMenus();
612 
613 	FavoritesMenu* favorites = new FavoritesMenu(B_TRANSLATE("Favorites"),
614 		new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED),
615 		BMessenger(this), IsSavePanel(), fPoseView->RefFilter());
616 	favorites->AddItem(new BMenuItem(B_TRANSLATE("Add current folder"),
617 		new BMessage(kAddCurrentDir)));
618 	favorites->AddItem(new BMenuItem(
619 		B_TRANSLATE("Edit favorites"B_UTF8_ELLIPSIS),
620 		new BMessage(kEditFavorites)));
621 
622 	fMenuBar->AddItem(favorites);
623 
624 	// configure menus
625 	BMenuItem* item = fMenuBar->FindItem(B_TRANSLATE("Window"));
626 	if (item) {
627 		fMenuBar->RemoveItem(item);
628 		delete item;
629 	}
630 
631 	item = fMenuBar->FindItem(B_TRANSLATE("File"));
632 	if (item) {
633 		BMenu* menu = item->Submenu();
634 		if (menu) {
635 			item = menu->FindItem(kOpenSelection);
636 			if (item && menu->RemoveItem(item))
637 				delete item;
638 
639 			item = menu->FindItem(kDuplicateSelection);
640 			if (item && menu->RemoveItem(item))
641 				delete item;
642 
643 			// remove add-ons menu, identifier menu, separator
644 			item = menu->FindItem(B_TRANSLATE("Add-ons"));
645 			if (item) {
646 				int32 index = menu->IndexOf(item);
647 				delete menu->RemoveItem(index);
648 				delete menu->RemoveItem(--index);
649 				delete menu->RemoveItem(--index);
650 			}
651 
652 			// remove separator
653 			item = menu->FindItem(B_CUT);
654 			if (item) {
655 				item = menu->ItemAt(menu->IndexOf(item)-1);
656 				if (item && menu->RemoveItem(item))
657 					delete item;
658 			}
659 		}
660 	}
661 
662 	// add directory menu and menufield
663 	fDirMenu = new BDirMenu(0, this, kSwitchDirectory, "refs");
664 
665 	font_height ht;
666 	be_plain_font->GetHeight(&ht);
667 	float f_height = ht.ascent + ht.descent + ht.leading;
668 
669 	BRect rect;
670 	rect.top = fMenuBar->Bounds().Height() + 8;
671 	rect.left = windRect.left + 8;
672 	rect.right = rect.left + 300;
673 	rect.bottom = rect.top + (f_height > 22 ? f_height : 22);
674 
675 	fDirMenuField = new BMenuField(rect, "DirMenuField", "", fDirMenu);
676 	fDirMenuField->MenuBar()->SetFont(be_plain_font);
677 	fDirMenuField->SetDivider(0);
678 	fDirMenuField->MenuBar()->SetMaxContentWidth(rect.Width() - 26.0f);
679 		// Make room for the icon
680 
681 	fDirMenuField->MenuBar()->RemoveItem((int32)0);
682 	fDirMenu->SetMenuBar(fDirMenuField->MenuBar());
683 		// the above is a weird call from BDirMenu
684 		// ToDo: clean up
685 
686 	BEntry entry(TargetModel()->EntryRef());
687 	if (entry.InitCheck() == B_OK)
688 		fDirMenu->Populate(&entry, 0, true, true, false, true);
689 	else
690 		fDirMenu->Populate(0, 0, true, true, false, true);
691 
692 	fBackView->AddChild(fDirMenuField);
693 
694 	// add file name text view
695 	if (fIsSavePanel) {
696 		BRect rect(windRect);
697 		rect.top = rect.bottom - 35;
698 		rect.left = 8;
699 		rect.right = rect.left + 170;
700 		rect.bottom = rect.top + 13;
701 
702 		fTextControl = new BTextControl(rect, "text view",
703 			B_TRANSLATE("save text"), "", NULL,
704 			B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
705 		DisallowMetaKeys(fTextControl->TextView());
706 		DisallowFilenameKeys(fTextControl->TextView());
707 		fBackView->AddChild(fTextControl);
708 		fTextControl->SetDivider(0.0f);
709 		fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1);
710 
711 		fButtonText.SetTo(B_TRANSLATE("Save"));
712 	} else
713 		fButtonText.SetTo(B_TRANSLATE("Open"));
714 
715 	rect = windRect;
716 	rect.OffsetTo(10, fDirMenuField->Frame().bottom + 10);
717 	rect.bottom = windRect.bottom - 60;
718 	rect.right -= B_V_SCROLL_BAR_WIDTH + 20;
719 
720 	// re-parent the poseview to our backview
721 	// ToDo:
722 	// This is terrible, fix it up
723 	PoseView()->RemoveSelf();
724 	if (fIsSavePanel)
725 		fBackView->AddChild(PoseView(), fTextControl);
726 	else
727 		fBackView->AddChild(PoseView());
728 
729 	PoseView()->MoveTo(rect.LeftTop());
730 	PoseView()->ResizeTo(rect.Width(), rect.Height());
731 	PoseView()->AddScrollBars();
732 	PoseView()->SetDragEnabled(false);
733 	PoseView()->SetDropEnabled(false);
734 	PoseView()->SetSelectionHandler(this);
735 	PoseView()->SetSelectionChangedHook(true);
736 	PoseView()->DisableSaveLocation();
737 	PoseView()->VScrollBar()->MoveBy(0, -1);
738 	PoseView()->VScrollBar()->ResizeBy(0, 1);
739 
740 
741 	AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton));
742 	AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome));
743 	AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
744 		new BMessage(kShowSelectionWindow));
745 	AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), PoseView());
746 	AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection),
747 		PoseView());
748 	AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView());
749 	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir));
750 	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
751 		new BMessage(kOpenDir));
752 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir));
753 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
754 		new BMessage(kOpenParentDir));
755 
756 	// New code to make buttons font sensitive
757 	rect = windRect;
758 	rect.top = rect.bottom - 35;
759 	rect.bottom -= 10;
760 	rect.right -= 25;
761 	float default_width
762 		= be_plain_font->StringWidth(fButtonText.String()) + 20;
763 	rect.left = default_width > 75
764 		? rect.right - default_width : rect.right - 75;
765 
766 	BButton* default_button = new BButton(rect, "default button",
767 		fButtonText.String(), new BMessage(kDefaultButton),
768 		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
769 	fBackView->AddChild(default_button);
770 
771 	rect.right = rect.left -= 10;
772 	float cancel_width
773 		= be_plain_font->StringWidth(B_TRANSLATE("Cancel")) + 20;
774 	rect.left = cancel_width > 75
775 		? rect.right - cancel_width : rect.right - 75;
776 
777 	BButton* cancel_button = new BButton(rect, "cancel button",
778 		B_TRANSLATE("Cancel"), new BMessage(kCancelButton),
779 		B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM);
780 	fBackView->AddChild(cancel_button);
781 
782 	if (!fIsSavePanel)
783 		default_button->SetEnabled(false);
784 
785 	default_button->MakeDefault(true);
786 
787 	RestoreState();
788 
789 	PoseView()->ScrollTo(B_ORIGIN);
790 	PoseView()->UpdateScrollRange();
791 	PoseView()->ScrollTo(B_ORIGIN);
792 
793 	if (fTextControl) {
794 		fTextControl->MakeFocus();
795 		fTextControl->TextView()->SelectAll();
796 	} else
797 		PoseView()->MakeFocus();
798 
799 	app_info info;
800 	BString title;
801 	if (be_app->GetAppInfo(&info) == B_OK) {
802 		if (!gLocalizedNamePreferred
803 			|| BLocaleRoster::Default()->GetLocalizedFileName(
804 				title, info.ref, false) != B_OK)
805 			title = info.ref.name;
806 		title << ": ";
807 	}
808 	title << fButtonText;	// Open or Save
809 
810 	SetTitle(title.String());
811 
812 	SetSizeLimits(370, 10000, 200, 10000);
813 }
814 
815 
816 void
817 TFilePanel::RestoreState()
818 {
819 	BNode defaultingNode;
820 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
821 			false)) {
822 		AttributeStreamFileNode streamNodeSource(&defaultingNode);
823 		RestoreWindowState(&streamNodeSource);
824 		PoseView()->Init(&streamNodeSource);
825 	} else {
826 		RestoreWindowState(NULL);
827 		PoseView()->Init(NULL);
828 	}
829 }
830 
831 
832 void
833 TFilePanel::SaveState(bool)
834 {
835 	BNode defaultingNode;
836 	if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode,
837 		true, false)) {
838 		AttributeStreamFileNode streamNodeDestination(&defaultingNode);
839 		SaveWindowState(&streamNodeDestination);
840 		PoseView()->SaveState(&streamNodeDestination);
841 	}
842 }
843 
844 
845 void
846 TFilePanel::SaveState(BMessage &message) const
847 {
848 	_inherited::SaveState(message);
849 }
850 
851 
852 void
853 TFilePanel::RestoreWindowState(AttributeStreamNode* node)
854 {
855 	SetSizeLimits(360, 10000, 200, 10000);
856 	if (!node)
857 		return;
858 
859 	const char* rectAttributeName = kAttrWindowFrame;
860 	BRect frame(Frame());
861 	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
862 		== sizeof(BRect)) {
863 		MoveTo(frame.LeftTop());
864 		ResizeTo(frame.Width(), frame.Height());
865 	}
866 }
867 
868 
869 void
870 TFilePanel::RestoreState(const BMessage &message)
871 {
872 	_inherited::RestoreState(message);
873 }
874 
875 
876 void
877 TFilePanel::RestoreWindowState(const BMessage &message)
878 {
879 	_inherited::RestoreWindowState(message);
880 }
881 
882 
883 void
884 TFilePanel::AddFileContextMenus(BMenu* menu)
885 {
886 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
887 		new BMessage(kGetInfo), 'I'));
888 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
889 		new BMessage(kEditItem), 'E'));
890 	menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash()
891 		? B_TRANSLATE("Delete")
892 		: B_TRANSLATE("Move to Trash"),
893 		new BMessage(kMoveToTrash), 'T'));
894 	menu->AddSeparatorItem();
895 	menu->AddItem(new BMenuItem(B_TRANSLATE("Cut"),
896 		new BMessage(B_CUT), 'X'));
897 	menu->AddItem(new BMenuItem(B_TRANSLATE("Copy"),
898 		new BMessage(B_COPY), 'C'));
899 	//menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE),
900 	//	'V'));
901 
902 	menu->SetTargetForItems(PoseView());
903 }
904 
905 
906 void
907 TFilePanel::AddVolumeContextMenus(BMenu* menu)
908 {
909 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
910 		new BMessage(kOpenSelection), 'O'));
911 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
912 		new BMessage(kGetInfo), 'I'));
913 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
914 		new BMessage(kEditItem), 'E'));
915 	menu->AddSeparatorItem();
916 	menu->AddItem(new BMenuItem(B_TRANSLATE("Cut"), new BMessage(B_CUT),
917 		'X'));
918 	menu->AddItem(new BMenuItem(B_TRANSLATE("Copy"),
919 		new BMessage(B_COPY), 'C'));
920 	//menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE),
921 	//	'V'));
922 
923 	menu->SetTargetForItems(PoseView());
924 }
925 
926 
927 void
928 TFilePanel::AddWindowContextMenus(BMenu* menu)
929 {
930 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
931 		new BMessage(kNewFolder), 'N');
932 	item->SetTarget(PoseView());
933 	menu->AddItem(item);
934 	menu->AddSeparatorItem();
935 
936 	item = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
937 	item->SetTarget(PoseView());
938 	menu->AddItem(item);
939 	menu->AddSeparatorItem();
940 
941 	item = new BMenuItem(B_TRANSLATE("Select"B_UTF8_ELLIPSIS),
942 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
943 	item->SetTarget(PoseView());
944 	menu->AddItem(item);
945 
946 	item = new BMenuItem(B_TRANSLATE("Select all"),
947 		new BMessage(B_SELECT_ALL), 'A');
948 	item->SetTarget(PoseView());
949 	menu->AddItem(item);
950 
951 	item = new BMenuItem(B_TRANSLATE("Invert selection"),
952 		new BMessage(kInvertSelection), 'S');
953 	item->SetTarget(PoseView());
954 	menu->AddItem(item);
955 
956 	item = new BMenuItem(B_TRANSLATE("Go to parent"),
957 		new BMessage(kOpenParentDir), B_UP_ARROW);
958 	item->SetTarget(this);
959 	menu->AddItem(item);
960 }
961 
962 
963 void
964 TFilePanel::AddDropContextMenus(BMenu*)
965 {
966 }
967 
968 
969 void
970 TFilePanel::MenusBeginning()
971 {
972 	int32 count = PoseView()->SelectionList()->CountItems();
973 
974 	EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot());
975 	EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot()
976 		&& count);
977 	EnableNamedMenuItem(fMenuBar, kGetInfo, count != 0);
978 	EnableNamedMenuItem(fMenuBar, kEditItem, count == 1);
979 
980 	SetCutItem(fMenuBar);
981 	SetCopyItem(fMenuBar);
982 	SetPasteItem(fMenuBar);
983 
984 	fIsTrackingMenu = true;
985 }
986 
987 
988 void
989 TFilePanel::MenusEnded()
990 {
991 	fIsTrackingMenu = false;
992 }
993 
994 
995 void
996 TFilePanel::ShowContextMenu(BPoint point, const entry_ref* ref, BView* view)
997 {
998 	EnableNamedMenuItem(fWindowContextMenu, kNewFolder,
999 		!TargetModel()->IsRoot());
1000 	EnableNamedMenuItem(fWindowContextMenu, kOpenParentDir,
1001 		!TargetModel()->IsRoot());
1002 	EnableNamedMenuItem(fWindowContextMenu, kMoveToTrash,
1003 		!TargetModel()->IsRoot());
1004 
1005 	_inherited::ShowContextMenu(point, ref, view);
1006 }
1007 
1008 
1009 void
1010 TFilePanel::SetupNavigationMenu(const entry_ref*, BMenu*)
1011 {
1012 	// do nothing here so nav menu doesn't get added
1013 }
1014 
1015 
1016 void
1017 TFilePanel::SetButtonLabel(file_panel_button selector, const char* text)
1018 {
1019 	switch (selector) {
1020 		case B_CANCEL_BUTTON:
1021 			{
1022 				BButton* button
1023 					= dynamic_cast<BButton*>(FindView("cancel button"));
1024 				if (!button)
1025 					break;
1026 
1027 				float old_width = button->StringWidth(button->Label());
1028 				button->SetLabel(text);
1029 				float delta = old_width - button->StringWidth(text);
1030 				if (delta) {
1031 					button->MoveBy(delta, 0);
1032 					button->ResizeBy(-delta, 0);
1033 				}
1034 			}
1035 			break;
1036 
1037 		case B_DEFAULT_BUTTON:
1038 			{
1039 				fButtonText = text;
1040 				float delta = 0;
1041 				BButton* button
1042 					= dynamic_cast<BButton*>(FindView("default button"));
1043 				if (button) {
1044 					float old_width = button->StringWidth(button->Label());
1045 					button->SetLabel(text);
1046 					delta = old_width - button->StringWidth(text);
1047 					if (delta) {
1048 						button->MoveBy(delta, 0);
1049 						button->ResizeBy(-delta, 0);
1050 					}
1051 				}
1052 
1053 				// now must move cancel button
1054 				button = dynamic_cast<BButton*>(FindView("cancel button"));
1055 				if (button)
1056 					button->MoveBy(delta, 0);
1057 			}
1058 			break;
1059 	}
1060 }
1061 
1062 
1063 void
1064 TFilePanel::SetSaveText(const char* text)
1065 {
1066 	if (!text)
1067 		return;
1068 
1069 	BTextControl* textControl
1070 		= dynamic_cast<BTextControl*>(FindView("text view"));
1071 	textControl->SetText(text);
1072 	textControl->TextView()->SelectAll();
1073 }
1074 
1075 
1076 void
1077 TFilePanel::MessageReceived(BMessage* message)
1078 {
1079 	entry_ref ref;
1080 
1081 	switch (message->what) {
1082 		case B_REFS_RECEIVED:
1083 			// item was double clicked in file panel (PoseView)
1084 			if (message->FindRef("refs", &ref) == B_OK) {
1085 				BEntry entry(&ref, true);
1086 				if (entry.InitCheck() == B_OK) {
1087 					// Double-click on dir or link-to-dir ALWAYS opens the
1088 					// dir. If more than one dir is selected, the first is
1089 					// entered.
1090 					if (entry.IsDirectory()) {
1091 						entry.GetRef(&ref);
1092 						bool isDesktop = SwitchDirToDesktopIfNeeded(ref);
1093 
1094 						PoseView()->SetIsDesktop(isDesktop);
1095 						entry.SetTo(&ref);
1096 						PoseView()->SwitchDir(&ref);
1097 						SwitchDirMenuTo(&ref);
1098 					} else {
1099 						// Otherwise, we have a file or a link to a file.
1100 						// AdjustButton has already tested the flavor;
1101 						// all we have to do is see if the button is enabled.
1102 						BButton* button = dynamic_cast<BButton*>(
1103 							FindView("default button"));
1104 						if (!button)
1105 							break;
1106 
1107 						if (IsSavePanel()) {
1108 							int32 count = 0;
1109 							type_code type;
1110 							message->GetInfo("refs", &type, &count);
1111 
1112 							// Don't allow saves of multiple files
1113 							if (count > 1) {
1114 								ShowCenteredAlert(
1115 									B_TRANSLATE(
1116 										"Sorry, saving more than one "
1117 										"item is not allowed."),
1118 									B_TRANSLATE("Cancel"));
1119 							} else {
1120 								// if we are a savepanel, set up the
1121 								// filepanel correctly then pass control
1122 								// so we follow the same path as if the user
1123 								// clicked the save button
1124 
1125 								// set the 'name' fld to the current ref's
1126 								// name notify the panel that the default
1127 								// button should be enabled
1128 								SetSaveText(ref.name);
1129 								SelectionChanged();
1130 
1131 								HandleSaveButton();
1132 							}
1133 							break;
1134 						}
1135 
1136 					  	// send handler a message and close
1137 						BMessage openMessage(*fMessage);
1138 						for (int32 index = 0; ; index++) {
1139 					  		if (message->FindRef("refs", index, &ref) != B_OK)
1140 								break;
1141 							openMessage.AddRef("refs", &ref);
1142 					  	}
1143 						OpenSelectionCommon(&openMessage);
1144 					}
1145 				}
1146 			}
1147 			break;
1148 
1149 		case kSwitchDirectory:
1150 		{
1151 			entry_ref ref;
1152 			// this comes from dir menu or nav menu, so switch directories
1153 			if (message->FindRef("refs", &ref) == B_OK) {
1154 				BEntry entry(&ref, true);
1155 				if (entry.GetRef(&ref) == B_OK)
1156 					SetTo(&ref);
1157 			}
1158 			break;
1159 		}
1160 
1161 		case kSwitchToHome:
1162 		{
1163 			BPath homePath;
1164 			entry_ref ref;
1165 			if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK
1166 				|| get_ref_for_path(homePath.Path(), &ref) != B_OK)
1167 				break;
1168 
1169 			SetTo(&ref);
1170 			break;
1171 		}
1172 
1173 		case kAddCurrentDir:
1174 		{
1175 			BPath path;
1176 			if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)
1177 					!= B_OK) {
1178 				break;
1179 			}
1180 
1181 			path.Append(kGoDirectory);
1182 			BDirectory goDirectory(path.Path());
1183 
1184 			if (goDirectory.InitCheck() == B_OK) {
1185 				BEntry entry(TargetModel()->EntryRef());
1186 				entry.GetPath(&path);
1187 
1188 				BSymLink link;
1189 				goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(),
1190 					&link);
1191 			}
1192 			break;
1193 		}
1194 
1195 		case kEditFavorites:
1196 		{
1197 			BPath path;
1198 			if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true)
1199 					!= B_OK) {
1200 				break;
1201 			}
1202 
1203 			path.Append(kGoDirectory);
1204 			BMessenger msgr(kTrackerSignature);
1205 			if (msgr.IsValid()) {
1206 				BMessage message(B_REFS_RECEIVED);
1207 				entry_ref ref;
1208 				if (get_ref_for_path(path.Path(), &ref) == B_OK) {
1209 					message.AddRef("refs", &ref);
1210 					msgr.SendMessage(&message);
1211 				}
1212 			}
1213 			break;
1214 		}
1215 
1216 		case kCancelButton:
1217 			PostMessage(B_QUIT_REQUESTED);
1218 			break;
1219 
1220 		case kResizeToFit:
1221 			ResizeToFit();
1222 			break;
1223 
1224 		case kOpenDir:
1225 			OpenDirectory();
1226 			break;
1227 
1228 		case kOpenParentDir:
1229 			OpenParent();
1230 			break;
1231 
1232 		case kDefaultButton:
1233 			if (fIsSavePanel) {
1234 				if (PoseView()->IsFocus()
1235 					&& PoseView()->SelectionList()->CountItems() == 1) {
1236 					Model* model = (PoseView()->SelectionList()->
1237 						FirstItem())->TargetModel();
1238 					if (model->ResolveIfLink()->IsDirectory()) {
1239 						PoseView()->CommitActivePose();
1240 						PoseView()->OpenSelection();
1241 						break;
1242 					}
1243 				}
1244 
1245 				HandleSaveButton();
1246 			} else
1247 				HandleOpenButton();
1248 			break;
1249 
1250 		case B_OBSERVER_NOTICE_CHANGE:
1251 		{
1252 			int32 observerWhat;
1253 			if (message->FindInt32("be:observe_change_what", &observerWhat)
1254 					== B_OK) {
1255 				switch (observerWhat) {
1256 					case kDesktopFilePanelRootChanged:
1257 					{
1258 						bool desktopIsRoot = true;
1259 						if (message->FindBool("DesktopFilePanelRoot",
1260 								&desktopIsRoot) == B_OK) {
1261 							TrackerSettings().
1262 								SetDesktopFilePanelRoot(desktopIsRoot);
1263 						}
1264 						SetTo(TargetModel()->EntryRef());
1265 						break;
1266 					}
1267 				}
1268 			}
1269 			break;
1270 		}
1271 
1272 		default:
1273 			_inherited::MessageReceived(message);
1274 	}
1275 }
1276 
1277 
1278 void
1279 TFilePanel::OpenDirectory()
1280 {
1281 	BObjectList<BPose>* list = PoseView()->SelectionList();
1282 	if (list->CountItems() != 1)
1283 		return;
1284 
1285 	Model* model = list->FirstItem()->TargetModel();
1286 	if (model->ResolveIfLink()->IsDirectory()) {
1287 		BMessage message(B_REFS_RECEIVED);
1288 		message.AddRef("refs", model->EntryRef());
1289 		PostMessage(&message);
1290 	}
1291 }
1292 
1293 
1294 void
1295 TFilePanel::OpenParent()
1296 {
1297 	if (!CanOpenParent())
1298 		return;
1299 
1300 	BEntry parentEntry;
1301 	BDirectory dir;
1302 
1303 	Model oldModel(*PoseView()->TargetModel());
1304 	BEntry entry(oldModel.EntryRef());
1305 
1306 	if (entry.InitCheck() == B_OK
1307 		&& entry.GetParent(&dir) == B_OK
1308 		&& dir.GetEntry(&parentEntry) == B_OK
1309 		&& entry != parentEntry) {
1310 
1311 		entry_ref ref;
1312 		parentEntry.GetRef(&ref);
1313 
1314 		PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref));
1315 		PoseView()->SwitchDir(&ref);
1316 		SwitchDirMenuTo(&ref);
1317 
1318 		// make sure the child get's selected in the new view once it
1319 		// shows up
1320 		fTaskLoop->RunLater(NewMemberFunctionObjectWithResult
1321 			(&TFilePanel::SelectChildInParent, this,
1322 			const_cast<const entry_ref*>(&ref),
1323 			oldModel.NodeRef()), 100000, 200000, 5000000);
1324 	}
1325 }
1326 
1327 
1328 bool
1329 TFilePanel::CanOpenParent() const
1330 {
1331 	if (TrackerSettings().DesktopFilePanelRoot()) {
1332 		// don't allow opening Desktop folder's parent
1333 		if (TargetModel()->IsDesktop())
1334 			return false;
1335 	}
1336 
1337 	// block on "/"
1338 	BEntry root("/");
1339 	node_ref rootRef;
1340 	root.GetNodeRef(&rootRef);
1341 
1342 	return rootRef != *TargetModel()->NodeRef();
1343 }
1344 
1345 
1346 bool
1347 TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref)
1348 {
1349 	// support showing Desktop as root of everything
1350 	// This call implements the worm hole that maps Desktop as
1351 	// a root above the disks
1352 	TrackerSettings settings;
1353 	if (!settings.DesktopFilePanelRoot())
1354 		// Tracker isn't set up that way, just let Disks show
1355 		return false;
1356 
1357 	BEntry entry(&ref);
1358 	BEntry root("/");
1359 
1360 	BDirectory desktopDir;
1361 	FSGetDeskDir(&desktopDir);
1362 	if (FSIsDeskDir(&entry)
1363 		// navigated into non-boot desktop, switch to boot desktop
1364 		|| (entry == root && !settings.ShowDisksIcon())) {
1365 		// hit "/" level, map to desktop
1366 
1367 		desktopDir.GetEntry(&entry);
1368 		entry.GetRef(&ref);
1369 		return true;
1370 	}
1371 	return FSIsDeskDir(&entry);
1372 }
1373 
1374 
1375 bool
1376 TFilePanel::SelectChildInParent(const entry_ref*, const node_ref* child)
1377 {
1378 	AutoLock<TFilePanel> lock(this);
1379 
1380 	if (!IsLocked())
1381 		return false;
1382 
1383 	int32 index;
1384 	BPose* pose = PoseView()->FindPose(child, &index);
1385 	if (!pose)
1386 		return false;
1387 
1388 	PoseView()->UpdateScrollRange();
1389 		// ToDo: Scroll range should be updated by now, for some
1390 		//	reason sometimes it is not right, force it here
1391 	PoseView()->SelectPose(pose, index, true);
1392 	return true;
1393 }
1394 
1395 
1396 int32
1397 TFilePanel::ShowCenteredAlert(const char* text, const char* button1,
1398 	const char* button2, const char* button3)
1399 {
1400 	BAlert* alert = new BAlert("", text, button1, button2, button3,
1401 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1402 	alert->MoveTo(Frame().left + 10, Frame().top + 10);
1403 
1404 #if 0
1405 	if (button1 != NULL && !strncmp(button1, "Cancel", 7))
1406 		alert->SetShortcut(0, B_ESCAPE);
1407 	else if (button2 != NULL && !strncmp(button2, "Cancel", 7))
1408 		alert->SetShortcut(1, B_ESCAPE);
1409 	else if (button3 != NULL && !strncmp(button3, "Cancel", 7))
1410 		alert->SetShortcut(2, B_ESCAPE);
1411 #endif
1412 
1413 	return alert->Go();
1414 }
1415 
1416 
1417 void
1418 TFilePanel::HandleSaveButton()
1419 {
1420 	BDirectory dir;
1421 
1422 	if (TargetModel()->IsRoot()) {
1423 		ShowCenteredAlert(
1424 			B_TRANSLATE("Sorry, you can't save things at the root of "
1425 			"your system."),
1426 			B_TRANSLATE("Cancel"));
1427 		return;
1428 	}
1429 
1430 	// check for some illegal file names
1431 	if (strcmp(fTextControl->Text(), ".") == 0
1432 		|| strcmp(fTextControl->Text(), "..") == 0) {
1433 		ShowCenteredAlert(
1434 			B_TRANSLATE("The specified name is illegal. Please choose "
1435 			"another name."),
1436 			B_TRANSLATE("Cancel"));
1437 		fTextControl->TextView()->SelectAll();
1438 		return;
1439 	}
1440 
1441 	if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) {
1442 		ShowCenteredAlert(
1443 			B_TRANSLATE("There was a problem trying to save in the folder "
1444 			"you specified. Please try another one."),
1445 			B_TRANSLATE("Cancel"));
1446 		return;
1447 	}
1448 
1449 	if (dir.Contains(fTextControl->Text())) {
1450 		if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) {
1451 			ShowCenteredAlert(
1452 				B_TRANSLATE("The specified name is already used as the name "
1453 				"of a folder. Please choose another name."),
1454 				B_TRANSLATE("Cancel"));
1455 			fTextControl->TextView()->SelectAll();
1456 			return;
1457 		} else {
1458 			// if this was invoked by a dbl click, it is an explicit
1459 			// replacement of the file.
1460 			BString str(B_TRANSLATE("The file \"%name\" already exists in "
1461 				"the specified folder. Do you want to replace it?"));
1462 			str.ReplaceFirst("%name", fTextControl->Text());
1463 
1464 			if (ShowCenteredAlert(str.String(),	B_TRANSLATE("Cancel"),
1465 					B_TRANSLATE("Replace"))	== 0) {
1466 				// user canceled
1467 				fTextControl->TextView()->SelectAll();
1468 				return;
1469 			}
1470 			// user selected "Replace" - let app deal with it
1471 		}
1472 	}
1473 
1474 	BMessage message(*fMessage);
1475 	message.AddRef("directory", TargetModel()->EntryRef());
1476 	message.AddString("name", fTextControl->Text());
1477 
1478 	if (fClientObject)
1479 		fClientObject->SendMessage(&fTarget, &message);
1480 	else
1481 		fTarget.SendMessage(&message);
1482 
1483 	// close window if we're dealing with standard message
1484 	if (fHideWhenDone)
1485 		PostMessage(B_QUIT_REQUESTED);
1486 }
1487 
1488 
1489 void
1490 TFilePanel::OpenSelectionCommon(BMessage* openMessage)
1491 {
1492 	if (!openMessage->HasRef("refs"))
1493 		return;
1494 
1495 	for (int32 index = 0; ; index++) {
1496 		entry_ref ref;
1497 		if (openMessage->FindRef("refs", index, &ref) != B_OK)
1498 			break;
1499 
1500 		BEntry entry(&ref, true);
1501 		if (entry.InitCheck() == B_OK) {
1502 			if (entry.IsDirectory())
1503 				BRoster().AddToRecentFolders(&ref);
1504 			else
1505 				BRoster().AddToRecentDocuments(&ref);
1506 		}
1507 	}
1508 
1509 	BRoster().AddToRecentFolders(TargetModel()->EntryRef());
1510 
1511 	if (fClientObject)
1512 		fClientObject->SendMessage(&fTarget, openMessage);
1513 	else
1514 		fTarget.SendMessage(openMessage);
1515 
1516 	// close window if we're dealing with standard message
1517 	if (fHideWhenDone)
1518 		PostMessage(B_QUIT_REQUESTED);
1519 }
1520 
1521 
1522 void
1523 TFilePanel::HandleOpenButton()
1524 {
1525 	PoseView()->CommitActivePose();
1526 	BObjectList<BPose>* selection = PoseView()->SelectionList();
1527 
1528 	// if we have only one directory and we're not opening dirs, enter.
1529 	if ((fNodeFlavors & B_DIRECTORY_NODE) == 0
1530 		&& selection->CountItems() == 1) {
1531 		Model* model = selection->FirstItem()->TargetModel();
1532 
1533 		if (model->IsDirectory()
1534 			|| (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE)
1535 				&& model->ResolveIfLink()->IsDirectory())) {
1536 
1537 			BMessage message(B_REFS_RECEIVED);
1538 			message.AddRef("refs", model->EntryRef());
1539 			PostMessage(&message);
1540 			return;
1541 		}
1542 	}
1543 
1544 	// don't do anything unless there are items selected
1545     // message->fMessage->message from here to end
1546 	if (selection->CountItems()) {
1547 		BMessage message(*fMessage);
1548 		// go through selection and add appropriate items
1549 		for (int32 index = 0; index < selection->CountItems(); index++) {
1550 			Model* model = selection->ItemAt(index)->TargetModel();
1551 
1552 			if (((fNodeFlavors & B_DIRECTORY_NODE) != 0
1553 					&& model->ResolveIfLink()->IsDirectory())
1554 				|| ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink())
1555 				|| ((fNodeFlavors & B_FILE_NODE) != 0
1556 					&& model->ResolveIfLink()->IsFile())) {
1557 				message.AddRef("refs", model->EntryRef());
1558 			}
1559 		}
1560 
1561 		OpenSelectionCommon(&message);
1562 	}
1563 }
1564 
1565 
1566 void
1567 TFilePanel::SwitchDirMenuTo(const entry_ref* ref)
1568 {
1569 	BEntry entry(ref);
1570 	for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--)
1571 		delete fDirMenu->RemoveItem(index);
1572 
1573 	fDirMenuField->MenuBar()->RemoveItem((int32)0);
1574 	fDirMenu->Populate(&entry, 0, true, true, false, true);
1575 
1576 	ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(
1577 		fDirMenuField->MenuBar()->ItemAt(0));
1578 	ASSERT(item);
1579 	item->SetEntry(&entry);
1580 }
1581 
1582 
1583 void
1584 TFilePanel::WindowActivated(bool active)
1585 {
1586 	// force focus to update properly
1587 	fBackView->Invalidate();
1588 	_inherited::WindowActivated(active);
1589 }
1590 
1591 
1592 //	#pragma mark -
1593 
1594 
1595 BFilePanelPoseView::BFilePanelPoseView(Model* model, BRect frame,
1596 	uint32 resizeMask)
1597 	: BPoseView(model, frame, kListMode, resizeMask),
1598 	fIsDesktop(model->IsDesktop())
1599 {
1600 }
1601 
1602 
1603 void
1604 BFilePanelPoseView::StartWatching()
1605 {
1606 	TTracker::WatchNode(0, B_WATCH_MOUNT, this);
1607 
1608 	// inter-application observing
1609 	BMessenger tracker(kTrackerSignature);
1610 	BHandler::StartWatching(tracker, kVolumesOnDesktopChanged);
1611 }
1612 
1613 
1614 void
1615 BFilePanelPoseView::StopWatching()
1616 {
1617 	stop_watching(this);
1618 
1619 	// inter-application observing
1620 	BMessenger tracker(kTrackerSignature);
1621 	BHandler::StopWatching(tracker, kVolumesOnDesktopChanged);
1622 }
1623 
1624 
1625 bool
1626 BFilePanelPoseView::FSNotification(const BMessage* message)
1627 {
1628 	if (IsDesktopView()) {
1629 		// Pretty much copied straight from DesktopPoseView.
1630 		// Would be better if the code could be shared somehow.
1631 		switch (message->FindInt32("opcode")) {
1632 			case B_DEVICE_MOUNTED:
1633 			{
1634 				dev_t device;
1635 				if (message->FindInt32("new device", &device) != B_OK)
1636 					break;
1637 
1638 				ASSERT(TargetModel());
1639 				TrackerSettings settings;
1640 
1641 				BVolume volume(device);
1642 				if (volume.InitCheck() != B_OK)
1643 					break;
1644 
1645 				if (settings.MountVolumesOntoDesktop()
1646 					&& (!volume.IsShared()
1647 						|| settings.MountSharedVolumesOntoDesktop())) {
1648 					// place an icon for the volume onto the desktop
1649 					CreateVolumePose(&volume, true);
1650 				}
1651 			}
1652 			break;
1653 		}
1654 	}
1655 	return _inherited::FSNotification(message);
1656 }
1657 
1658 
1659 void
1660 BFilePanelPoseView::RestoreState(AttributeStreamNode* node)
1661 {
1662 	_inherited::RestoreState(node);
1663 	fViewState->SetViewMode(kListMode);
1664 }
1665 
1666 
1667 void
1668 BFilePanelPoseView::RestoreState(const BMessage &message)
1669 {
1670 	_inherited::RestoreState(message);
1671 }
1672 
1673 
1674 void
1675 BFilePanelPoseView::SavePoseLocations(BRect*)
1676 {
1677 }
1678 
1679 
1680 EntryListBase*
1681 BFilePanelPoseView::InitDirentIterator(const entry_ref* ref)
1682 {
1683 	if (IsDesktopView())
1684 		return DesktopPoseView::InitDesktopDirentIterator(this, ref);
1685 
1686 	return _inherited::InitDirentIterator(ref);
1687 }
1688 
1689 
1690 void
1691 BFilePanelPoseView::AddPosesCompleted()
1692 {
1693 	_inherited::AddPosesCompleted();
1694 	if (IsDesktopView())
1695 		CreateTrashPose();
1696 }
1697 
1698 
1699 void
1700 BFilePanelPoseView::SetIsDesktop(bool on)
1701 {
1702 	fIsDesktop = on;
1703 }
1704 
1705 
1706 bool
1707 BFilePanelPoseView::IsDesktopView() const
1708 {
1709 	return fIsDesktop;
1710 }
1711 
1712 
1713 void
1714 BFilePanelPoseView::ShowVolumes(bool visible, bool showShared)
1715 {
1716 	if (IsDesktopView()) {
1717 		if (!visible)
1718 			RemoveRootPoses();
1719 		else
1720 			AddRootPoses(true, showShared);
1721 	}
1722 
1723 
1724 	TFilePanel* filepanel = dynamic_cast<TFilePanel*>(Window());
1725 	if (filepanel)
1726 		filepanel->SetTo(TargetModel()->EntryRef());
1727 }
1728 
1729 
1730 void
1731 BFilePanelPoseView::AdaptToVolumeChange(BMessage* message)
1732 {
1733 	bool showDisksIcon;
1734 	bool mountVolumesOnDesktop;
1735 	bool mountSharedVolumesOntoDesktop;
1736 
1737 	message->FindBool("ShowDisksIcon", &showDisksIcon);
1738 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1739 	message->FindBool("MountSharedVolumesOntoDesktop",
1740 		&mountSharedVolumesOntoDesktop);
1741 
1742 	BEntry entry("/");
1743 	Model model(&entry);
1744 	if (model.InitCheck() == B_OK) {
1745 		BMessage monitorMsg;
1746 		monitorMsg.what = B_NODE_MONITOR;
1747 
1748 		if (showDisksIcon)
1749 			monitorMsg.AddInt32("opcode", B_ENTRY_CREATED);
1750 		else
1751 			monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED);
1752 
1753 		monitorMsg.AddInt32("device", model.NodeRef()->device);
1754 		monitorMsg.AddInt64("node", model.NodeRef()->node);
1755 		monitorMsg.AddInt64("directory", model.EntryRef()->directory);
1756 		monitorMsg.AddString("name", model.EntryRef()->name);
1757 		TrackerSettings().SetShowDisksIcon(showDisksIcon);
1758 		if (Window())
1759 			Window()->PostMessage(&monitorMsg, this);
1760 	}
1761 
1762 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1763 }
1764 
1765 
1766 void
1767 BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage* message)
1768 {
1769 	bool mountVolumesOnDesktop = true;
1770 	bool mountSharedVolumesOntoDesktop = true;
1771 
1772 	message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop);
1773 	message->FindBool("MountSharedVolumesOntoDesktop",
1774 		&mountSharedVolumesOntoDesktop);
1775 
1776 	ShowVolumes(false, mountSharedVolumesOntoDesktop);
1777 	ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop);
1778 }
1779