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