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