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