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