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