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