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