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