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