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