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