1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35
36#include "ContainerWindow.h"
37
38#include <Alert.h>
39#include <Application.h>
40#include <AppFileInfo.h>
41#include <Catalog.h>
42#include <ControlLook.h>
43#include <Debug.h>
44#include <Directory.h>
45#include <Entry.h>
46#include <FindDirectory.h>
47#include <GridView.h>
48#include <GroupLayout.h>
49#include <Keymap.h>
50#include <Locale.h>
51#include <MenuItem.h>
52#include <MenuBar.h>
53#include <NodeMonitor.h>
54#include <Path.h>
55#include <PopUpMenu.h>
56#include <Roster.h>
57#include <Screen.h>
58#include <UnicodeChar.h>
59#include <Volume.h>
60#include <VolumeRoster.h>
61#include <WindowPrivate.h>
62
63#include <fs_attr.h>
64#include <image.h>
65#include <strings.h>
66#include <stdlib.h>
67
68#include "Attributes.h"
69#include "AttributeStream.h"
70#include "AutoDeleter.h"
71#include "AutoLock.h"
72#include "BackgroundImage.h"
73#include "Commands.h"
74#include "CountView.h"
75#include "DeskWindow.h"
76#include "DraggableContainerIcon.h"
77#include "FavoritesMenu.h"
78#include "FindPanel.h"
79#include "FSClipboard.h"
80#include "FSUndoRedo.h"
81#include "FSUtils.h"
82#include "IconMenuItem.h"
83#include "OpenWithWindow.h"
84#include "MimeTypes.h"
85#include "Model.h"
86#include "MountMenu.h"
87#include "Navigator.h"
88#include "NavMenu.h"
89#include "PoseView.h"
90#include "QueryContainerWindow.h"
91#include "SelectionWindow.h"
92#include "TitleView.h"
93#include "Tracker.h"
94#include "TrackerSettings.h"
95#include "Thread.h"
96#include "TemplatesMenu.h"
97
98
99#undef B_TRANSLATION_CONTEXT
100#define B_TRANSLATION_CONTEXT "ContainerWindow"
101
102
103#ifdef _IMPEXP_BE
104_IMPEXP_BE
105#endif
106void do_minimize_team(BRect zoomRect, team_id team, bool zoom);
107
108
109struct AddOneAddOnParams {
110	BObjectList<BMenuItem>* primaryList;
111	BObjectList<BMenuItem>* secondaryList;
112};
113
114struct StaggerOneParams {
115	bool rectFromParent;
116};
117
118
119BRect BContainerWindow::sNewWindRect;
120static int32 sWindowStaggerBy;
121
122LockingList<AddOnShortcut>* BContainerWindow::fAddOnsList
123	= new LockingList<struct AddOnShortcut>(10, true);
124
125
126namespace BPrivate {
127
128filter_result
129ActivateWindowFilter(BMessage*, BHandler** target, BMessageFilter*)
130{
131	BView* view = dynamic_cast<BView*>(*target);
132
133	// activate the window if no PoseView or DraggableContainerIcon had been
134	// pressed (those will activate the window themselves, if necessary)
135	if (view != NULL
136		&& dynamic_cast<BPoseView*>(view) == NULL
137		&& dynamic_cast<DraggableContainerIcon*>(view) == NULL
138		&& view->Window() != NULL) {
139		view->Window()->Activate(true);
140	}
141
142	return B_DISPATCH_MESSAGE;
143}
144
145}	// namespace BPrivate
146
147
148static int32
149AddOnMenuGenerate(const entry_ref* addOnRef, BMenu* menu,
150	BContainerWindow* window)
151{
152	BEntry entry(addOnRef);
153	BPath path;
154	status_t result = entry.InitCheck();
155	if (result != B_OK)
156		return result;
157
158	result = entry.GetPath(&path);
159	if (result != B_OK)
160		return result;
161
162	image_id addOnImage = load_add_on(path.Path());
163	if (addOnImage < 0)
164		return addOnImage;
165
166	void (*populateMenu)(BMessage*, BMenu*, BHandler*);
167	result = get_image_symbol(addOnImage, "populate_menu", 2,
168		(void**)&populateMenu);
169	if (result < 0) {
170		PRINT(("Couldn't find populate_menu\n"));
171		unload_add_on(addOnImage);
172		return result;
173	}
174
175	BMessage* message = window->AddOnMessage(B_TRACKER_ADDON_MESSAGE);
176	message->AddRef("addon_ref", addOnRef);
177
178	// call add-on code
179	(*populateMenu)(message, menu, window->PoseView());
180
181	unload_add_on(addOnImage);
182	return B_OK;
183}
184
185
186static status_t
187RunAddOnMessageThread(BMessage *message, void *)
188{
189	entry_ref addOnRef;
190	BEntry entry;
191	BPath path;
192	status_t result = message->FindRef("addon_ref", &addOnRef);
193	image_id addOnImage;
194
195	if (result != B_OK)
196		goto end;
197
198	entry = BEntry(&addOnRef);
199	result = entry.InitCheck();
200	if (result != B_OK)
201		goto end;
202
203	result = entry.GetPath(&path);
204	if (result != B_OK)
205		goto end;
206
207	addOnImage = load_add_on(path.Path());
208	if (addOnImage < 0) {
209		result = addOnImage;
210		goto end;
211	}
212	void (*messageReceived)(BMessage*);
213	result = get_image_symbol(addOnImage, "message_received", 2,
214		(void**)&messageReceived);
215
216	if (result < 0) {
217		PRINT(("Couldn't find message_received\n"));
218		unload_add_on(addOnImage);
219		goto end;
220	}
221	// call add-on code
222	(*messageReceived)(message);
223	unload_add_on(addOnImage);
224	return B_OK;
225
226end:
227	BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
228	buffer.ReplaceFirst("%error", strerror(result));
229	buffer.ReplaceFirst("%name", addOnRef.name);
230
231	BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
232		0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
233	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
234	alert->Go();
235
236	return result;
237}
238
239
240static bool
241AddOneAddOn(const Model* model, const char* name, uint32 shortcut,
242	uint32 modifiers, bool primary, void* context,
243	BContainerWindow* window, BMenu* menu)
244{
245	AddOneAddOnParams* params = (AddOneAddOnParams*)context;
246
247	BMessage* message = new BMessage(kLoadAddOn);
248	message->AddRef("refs", model->EntryRef());
249
250	ModelMenuItem* item = new ModelMenuItem(model, name, message,
251		(char)shortcut, modifiers);
252
253	const entry_ref* addOnRef = model->EntryRef();
254	AddOnMenuGenerate(addOnRef, menu, window);
255
256	if (primary)
257		params->primaryList->AddItem(item);
258	else
259		params->secondaryList->AddItem(item);
260
261	return false;
262}
263
264
265static int32
266AddOnThread(BMessage* refsMessage, entry_ref addOnRef, entry_ref directoryRef)
267{
268	ObjectDeleter<BMessage> _(refsMessage);
269
270	BEntry entry(&addOnRef);
271	BPath path;
272	status_t result = entry.InitCheck();
273	if (result == B_OK)
274		result = entry.GetPath(&path);
275
276	if (result == B_OK) {
277		image_id addOnImage = load_add_on(path.Path());
278		if (addOnImage >= 0) {
279			void (*processRefs)(entry_ref, BMessage*, void*);
280			result = get_image_symbol(addOnImage, "process_refs", 2,
281				(void**)&processRefs);
282
283			if (result >= 0) {
284				// call add-on code
285				(*processRefs)(directoryRef, refsMessage, NULL);
286
287				unload_add_on(addOnImage);
288				return B_OK;
289			} else
290				PRINT(("couldn't find process_refs\n"));
291
292			unload_add_on(addOnImage);
293		} else
294			result = addOnImage;
295	}
296
297	BString buffer(B_TRANSLATE("Error %error loading Add-On %name."));
298	buffer.ReplaceFirst("%error", strerror(result));
299	buffer.ReplaceFirst("%name", addOnRef.name);
300
301	BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
302		0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
303	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
304	alert->Go();
305
306	return result;
307}
308
309
310static bool
311NodeHasSavedState(const BNode* node)
312{
313	attr_info info;
314	return node->GetAttrInfo(kAttrWindowFrame, &info) == B_OK;
315}
316
317
318static bool
319OffsetFrameOne(const char* DEBUG_ONLY(name), uint32, off_t, void* castToRect,
320	void* castToParams)
321{
322	ASSERT(strcmp(name, kAttrWindowFrame) == 0);
323	StaggerOneParams* params = (StaggerOneParams*)castToParams;
324
325	if (!params->rectFromParent)
326		return false;
327
328	if (!castToRect)
329		return false;
330
331	((BRect*)castToRect)->OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
332
333	return true;
334}
335
336
337static void
338AddMimeTypeString(BStringList& list, Model* model)
339{
340	if (model == NULL)
341		return;
342
343	const char* modelMimeType = model->MimeType();
344	if (modelMimeType == NULL || *modelMimeType == '\0')
345		return;
346
347	BString type = BString(modelMimeType);
348	if (list.HasString(type, true))
349		return;
350
351	list.Add(type);
352}
353
354
355//	#pragma mark - BContainerWindow
356
357
358BContainerWindow::BContainerWindow(LockingList<BWindow>* list,
359	uint32 openFlags, window_look look, window_feel feel, uint32 windowFlags,
360	uint32 workspace, bool useLayout, bool isDeskWindow)
361	:
362	BWindow(InitialWindowRect(feel), "TrackerWindow", look, feel, windowFlags,
363		workspace),
364	fWindowList(list),
365	fOpenFlags(openFlags),
366	fUsesLayout(useLayout),
367	fMenuContainer(NULL),
368	fPoseContainer(NULL),
369	fBorderedView(NULL),
370	fVScrollBarContainer(NULL),
371	fCountContainer(NULL),
372	fContextMenu(NULL),
373	fFileContextMenu(NULL),
374	fWindowContextMenu(NULL),
375	fDropContextMenu(NULL),
376	fVolumeContextMenu(NULL),
377	fTrashContextMenu(NULL),
378	fDragContextMenu(NULL),
379	fMoveToItem(NULL),
380	fCopyToItem(NULL),
381	fCreateLinkItem(NULL),
382	fOpenWithItem(NULL),
383	fNavigationItem(NULL),
384	fMenuBar(NULL),
385	fDraggableIcon(NULL),
386	fNavigator(NULL),
387	fPoseView(NULL),
388	fAttrMenu(NULL),
389	fWindowMenu(NULL),
390	fFileMenu(NULL),
391	fArrangeByMenu(NULL),
392	fSelectionWindow(NULL),
393	fTaskLoop(NULL),
394	fStateNeedsSaving(false),
395	fIsTrash(false),
396	fInTrash(false),
397	fIsPrinters(false),
398	fIsDesktop(isDeskWindow),
399	fBackgroundImage(NULL),
400	fSavedZoomRect(0, 0, -1, -1),
401	fDragMessage(NULL),
402	fCachedTypesList(NULL),
403	fSaveStateIsEnabled(true),
404	fIsWatchingPath(false)
405{
406	InitIconPreloader();
407
408	if (list != NULL) {
409		ASSERT(list->IsLocked());
410		list->AddItem(this);
411	}
412
413	if (fUsesLayout) {
414		SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS);
415
416		fRootLayout = new BGroupLayout(B_VERTICAL, 0);
417		fRootLayout->SetInsets(0);
418		SetLayout(fRootLayout);
419		fRootLayout->Owner()->AdoptSystemColors();
420
421		fMenuContainer = new BGroupView(B_HORIZONTAL, 0);
422		fRootLayout->AddView(fMenuContainer);
423
424		fPoseContainer = new BGridView(0.0, 0.0);
425		fRootLayout->AddView(fPoseContainer);
426
427		fBorderedView = new BorderedView;
428		fPoseContainer->GridLayout()->AddView(fBorderedView, 0, 1);
429
430		fCountContainer = new BGroupView(B_HORIZONTAL, 0);
431		fPoseContainer->GridLayout()->AddView(fCountContainer, 0, 2);
432	}
433
434	AddCommonFilter(new BMessageFilter(B_MOUSE_DOWN, ActivateWindowFilter));
435
436	Run();
437
438	// watch out for settings changes
439	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
440	if (tracker != NULL && tracker->Lock()) {
441		tracker->StartWatching(this, kWindowsShowFullPathChanged);
442		tracker->StartWatching(this, kSingleWindowBrowseChanged);
443		tracker->StartWatching(this, kShowNavigatorChanged);
444		tracker->Unlock();
445	}
446
447	// ToDo: remove me once we have undo/redo menu items
448	// (that is, move them to AddShortcuts())
449	AddShortcut('Z', B_COMMAND_KEY, new BMessage(B_UNDO), this);
450	AddShortcut('Z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO), this);
451}
452
453
454BContainerWindow::~BContainerWindow()
455{
456	ASSERT(IsLocked());
457
458	// stop the watchers
459	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
460	if (tracker != NULL && tracker->Lock()) {
461		tracker->StopWatching(this, kWindowsShowFullPathChanged);
462		tracker->StopWatching(this, kSingleWindowBrowseChanged);
463		tracker->StopWatching(this, kShowNavigatorChanged);
464		tracker->Unlock();
465	}
466
467	delete fTaskLoop;
468	delete fBackgroundImage;
469	delete fDragMessage;
470	delete fCachedTypesList;
471
472	if (fSelectionWindow != NULL && fSelectionWindow->Lock())
473		fSelectionWindow->Quit();
474}
475
476
477BRect
478BContainerWindow::InitialWindowRect(window_feel feel)
479{
480	if (!sNewWindRect.IsValid()) {
481		const float labelSpacing = be_control_look->DefaultLabelSpacing();
482		// approximately (85, 50, 548, 280) with default spacing
483		sNewWindRect = BRect(labelSpacing * 14, labelSpacing * 8,
484			labelSpacing * 91, labelSpacing * 46);
485		sWindowStaggerBy = (int32)(labelSpacing * 3.0f);
486	}
487
488	if (feel != kDesktopWindowFeel)
489		return sNewWindRect;
490
491	// do not offset desktop window
492	BRect result = sNewWindRect;
493	result.OffsetTo(0, 0);
494	return result;
495}
496
497
498void
499BContainerWindow::Minimize(bool minimize)
500{
501	if (minimize && (modifiers() & B_OPTION_KEY) != 0)
502		do_minimize_team(BRect(0, 0, 0, 0), be_app->Team(), true);
503	else
504		_inherited::Minimize(minimize);
505}
506
507
508bool
509BContainerWindow::QuitRequested()
510{
511	// this is a response to the DeskBar sending us a B_QUIT, when it really
512	// means to say close all your windows. It might be better to have it
513	// send a kCloseAllWindows message and have windowless apps stay running,
514	// which is what we will do for the Tracker
515	if (CurrentMessage() != NULL
516		&& ((CurrentMessage()->FindInt32("modifiers") & B_CONTROL_KEY)) != 0) {
517		be_app->PostMessage(kCloseAllWindows);
518	}
519
520	Hide();
521		// this will close the window instantly, even if
522		// the file system is very busy right now
523	return true;
524}
525
526
527void
528BContainerWindow::Quit()
529{
530	// get rid of context menus
531	if (fNavigationItem) {
532		BMenu* menu = fNavigationItem->Menu();
533		if (menu != NULL)
534			menu->RemoveItem(fNavigationItem);
535
536		delete fNavigationItem;
537		fNavigationItem = NULL;
538	}
539
540	if (fOpenWithItem != NULL && fOpenWithItem->Menu() == NULL) {
541		delete fOpenWithItem;
542		fOpenWithItem = NULL;
543	}
544
545	if (fMoveToItem != NULL && fMoveToItem->Menu() == NULL) {
546		delete fMoveToItem;
547		fMoveToItem = NULL;
548	}
549
550	if (fCopyToItem != NULL && fCopyToItem->Menu() == NULL) {
551		delete fCopyToItem;
552		fCopyToItem = NULL;
553	}
554
555	if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() == NULL) {
556		delete fCreateLinkItem;
557		fCreateLinkItem = NULL;
558	}
559
560	if (fAttrMenu != NULL && fAttrMenu->Supermenu() == NULL) {
561		delete fAttrMenu;
562		fAttrMenu = NULL;
563	}
564
565	delete fFileContextMenu;
566	fFileContextMenu = NULL;
567
568	delete fWindowContextMenu;
569	fWindowContextMenu = NULL;
570
571	delete fDropContextMenu;
572	fDropContextMenu = NULL;
573
574	delete fVolumeContextMenu;
575	fVolumeContextMenu = NULL;
576
577	delete fDragContextMenu;
578	fDragContextMenu = NULL;
579
580	delete fTrashContextMenu;
581	fTrashContextMenu = NULL;
582
583	int32 windowCount = 0;
584
585	// This is a deadlock code sequence - need to change this
586	// to acquire the window list while this container window is unlocked
587	if (fWindowList != NULL) {
588		AutoLock<LockingList<BWindow> > lock(fWindowList);
589		if (lock.IsLocked()) {
590			fWindowList->RemoveItem(this, false);
591			windowCount = fWindowList->CountItems();
592		}
593	}
594
595	if (StateNeedsSaving())
596		SaveState();
597
598	if (fWindowList != NULL && windowCount == 0)
599		be_app->PostMessage(B_QUIT_REQUESTED);
600
601	_inherited::Quit();
602}
603
604
605BPoseView*
606BContainerWindow::NewPoseView(Model* model, uint32 viewMode)
607{
608	return new BPoseView(model, viewMode);
609}
610
611
612void
613BContainerWindow::UpdateIfTrash(Model* model)
614{
615	BEntry entry(model->EntryRef());
616
617	if (entry.InitCheck() == B_OK) {
618		fIsTrash = model->IsTrash();
619		fInTrash = FSInTrashDir(model->EntryRef());
620		fIsPrinters = FSIsPrintersDir(&entry);
621	}
622}
623
624
625void
626BContainerWindow::CreatePoseView(Model* model)
627{
628	UpdateIfTrash(model);
629
630	fPoseView = NewPoseView(model, kListMode);
631	fBorderedView->GroupLayout()->AddView(fPoseView);
632	fBorderedView->GroupLayout()->SetInsets(1, 0, 1, 1);
633	fBorderedView->EnableBorderHighlight(false);
634
635	TrackerSettings settings;
636	if (settings.SingleWindowBrowse() && model->IsDirectory()
637		&& !fPoseView->IsFilePanel()) {
638		fNavigator = new BNavigator(model);
639		fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
640		if (!settings.ShowNavigator())
641			fNavigator->Hide();
642	}
643
644	SetPathWatchingEnabled(settings.ShowNavigator()
645		|| settings.ShowFullPathInTitleBar());
646}
647
648
649void
650BContainerWindow::AddContextMenus()
651{
652	// create context sensitive menus
653	fFileContextMenu = new BPopUpMenu("FileContext", false, false);
654	AddFileContextMenus(fFileContextMenu);
655
656	fVolumeContextMenu = new BPopUpMenu("VolumeContext", false, false);
657	AddVolumeContextMenus(fVolumeContextMenu);
658
659	fWindowContextMenu = new BPopUpMenu("WindowContext", false, false);
660	AddWindowContextMenus(fWindowContextMenu);
661
662	fDropContextMenu = new BPopUpMenu("DropContext", false, false);
663	AddDropContextMenus(fDropContextMenu);
664
665	fDragContextMenu = new BPopUpNavMenu("DragContext");
666		// will get added and built dynamically in ShowContextMenu
667
668	fTrashContextMenu = new BPopUpMenu("TrashContext", false, false);
669	AddTrashContextMenus(fTrashContextMenu);
670}
671
672
673void
674BContainerWindow::RepopulateMenus()
675{
676	// Avoid these menus to be destroyed:
677	if (fMoveToItem != NULL && fMoveToItem->Menu() != NULL)
678		fMoveToItem->Menu()->RemoveItem(fMoveToItem);
679
680	if (fCopyToItem != NULL && fCopyToItem->Menu() != NULL)
681		fCopyToItem->Menu()->RemoveItem(fCopyToItem);
682
683	if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() != NULL)
684		fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem);
685
686	if (fOpenWithItem != NULL && fOpenWithItem->Menu() != NULL) {
687		fOpenWithItem->Menu()->RemoveItem(fOpenWithItem);
688		delete fOpenWithItem;
689		fOpenWithItem = NULL;
690	}
691
692	if (fNavigationItem != NULL) {
693		BMenu* menu = fNavigationItem->Menu();
694		if (menu != NULL) {
695			menu->RemoveItem(fNavigationItem);
696			BMenuItem* item = menu->RemoveItem((int32)0);
697			ASSERT(item != fNavigationItem);
698			delete item;
699		}
700	}
701
702	delete fFileContextMenu;
703	fFileContextMenu = new BPopUpMenu("FileContext", false, false);
704	fFileContextMenu->SetFont(be_plain_font);
705	AddFileContextMenus(fFileContextMenu);
706
707	delete fWindowContextMenu;
708	fWindowContextMenu = new BPopUpMenu("WindowContext", false, false);
709	fWindowContextMenu->SetFont(be_plain_font);
710	AddWindowContextMenus(fWindowContextMenu);
711
712	if (fMenuBar != NULL) {
713		fMenuBar->RemoveItem(fFileMenu);
714		delete fFileMenu;
715		fFileMenu = new BMenu(B_TRANSLATE("File"));
716		AddFileMenu(fFileMenu);
717		fMenuBar->AddItem(fFileMenu);
718
719		fMenuBar->RemoveItem(fWindowMenu);
720		delete fWindowMenu;
721		fWindowMenu = new BMenu(B_TRANSLATE("Window"));
722		fMenuBar->AddItem(fWindowMenu);
723		AddWindowMenu(fWindowMenu);
724
725		// just create the attribute, decide to add it later
726		fMenuBar->RemoveItem(fAttrMenu);
727		delete fAttrMenu;
728		fAttrMenu = new BMenu(B_TRANSLATE("Attributes"));
729		NewAttributesMenu(fAttrMenu);
730		if (PoseView()->ViewMode() == kListMode)
731			ShowAttributesMenu();
732
733		PopulateArrangeByMenu(fArrangeByMenu);
734
735		int32 selectCount = PoseView()->CountSelected();
736
737		SetupOpenWithMenu(fFileMenu);
738		SetupMoveCopyMenus(selectCount ? PoseView()->SelectionList()
739				->FirstItem()->TargetModel()->EntryRef() : NULL,
740			fFileMenu);
741	}
742}
743
744
745void
746BContainerWindow::Init(const BMessage* message)
747{
748	// pose view is expected to be setup at this point
749	if (PoseView() == NULL)
750		return;
751
752	// deal with new unconfigured folders
753	if (NeedsDefaultStateSetup())
754		SetupDefaultState();
755
756	if (ShouldAddScrollBars())
757		PoseView()->AddScrollBars();
758
759	fMoveToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Move to"),
760		kMoveSelectionTo, this));
761	fCopyToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Copy to"),
762		kCopySelectionTo, this));
763	fCreateLinkItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Create link"),
764		kCreateLink, this), new BMessage(kCreateLink));
765
766	TrackerSettings settings;
767
768	if (ShouldAddMenus()) {
769		fMenuBar = new BMenuBar("MenuBar");
770		fMenuContainer->GroupLayout()->AddView(fMenuBar);
771		AddMenus();
772
773		if (!TargetModel()->IsRoot() && !IsTrash())
774			_AddFolderIcon();
775	} else {
776		// add equivalents of the menu shortcuts to the menuless
777		// desktop window
778		AddShortcuts();
779	}
780
781	AddContextMenus();
782	AddShortcut('T', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kDelete),
783		PoseView());
784	AddShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCleanupAll),
785		PoseView());
786	AddShortcut('Q', B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY
787		| B_CONTROL_KEY, new BMessage(kQuitTracker));
788
789	AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenSelection),
790		PoseView());
791
792	SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
793
794#if DEBUG
795	// add some debugging shortcuts
796	AddShortcut('D', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dbug'),
797		PoseView());
798	AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpcc'),
799		PoseView());
800	AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpfl'),
801		PoseView());
802	AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY,
803		new BMessage('dpfL'), PoseView());
804#endif
805
806	BKeymap keymap;
807	if (keymap.SetToCurrent() == B_OK) {
808		BObjectList<const char> unmodified(3, true);
809		if (keymap.GetModifiedCharacters("+", B_SHIFT_KEY, 0, &unmodified)
810				== B_OK) {
811			int32 count = unmodified.CountItems();
812			for (int32 i = 0; i < count; i++) {
813				uint32 key = BUnicodeChar::FromUTF8(unmodified.ItemAt(i));
814				if (!HasShortcut(key, 0)) {
815					// Add semantic zoom in shortcut, bug #6692
816					BMessage* increaseSize = new BMessage(kIconMode);
817					increaseSize->AddInt32("scale", 1);
818					AddShortcut(key, B_COMMAND_KEY, increaseSize, PoseView());
819				}
820			}
821		}
822		unmodified.MakeEmpty();
823	}
824
825	if (message != NULL)
826		RestoreState(*message);
827	else
828		RestoreState();
829
830	if (ShouldAddMenus() && PoseView()->ViewMode() == kListMode) {
831		// for now only show attributes in list view
832		// eventually enable attribute menu to allow users to select
833		// using different attributes as titles in icon view modes
834		ShowAttributesMenu();
835	}
836	MarkAttributesMenu(fAttrMenu);
837	CheckScreenIntersect();
838
839	if (fBackgroundImage != NULL && !fIsDesktop
840		&& PoseView()->ViewMode() != kListMode) {
841		fBackgroundImage->Show(PoseView(), current_workspace());
842	}
843
844	Show();
845
846	// done showing, turn the B_NO_WORKSPACE_ACTIVATION flag off;
847	// it was on to prevent workspace jerking during boot
848	SetFlags(Flags() & ~B_NO_WORKSPACE_ACTIVATION);
849}
850
851
852void
853BContainerWindow::InitLayout()
854{
855	fBorderedView->GroupLayout()->AddView(0, PoseView()->TitleView());
856
857	fCountContainer->GroupLayout()->AddView(PoseView()->CountView(), 0.25f);
858
859	bool forFilePanel = PoseView()->IsFilePanel();
860	if (!forFilePanel) {
861		// Eliminate the extra borders
862		fPoseContainer->GridLayout()->SetInsets(-1, 0, -1, -1);
863		fCountContainer->GroupLayout()->SetInsets(0, -1, 0, 0);
864	}
865
866	if (PoseView()->VScrollBar() != NULL) {
867		fVScrollBarContainer = new BGroupView(B_VERTICAL, 0);
868		fVScrollBarContainer->GroupLayout()->AddView(PoseView()->VScrollBar());
869		fVScrollBarContainer->GroupLayout()->SetInsets(-1, forFilePanel ? 0 : -1,
870			0, 0);
871		fPoseContainer->GridLayout()->AddView(fVScrollBarContainer, 1, 1);
872	}
873	if (PoseView()->HScrollBar() != NULL) {
874		BGroupView* hScrollBarContainer = new BGroupView(B_VERTICAL, 0);
875		hScrollBarContainer->GroupLayout()->AddView(PoseView()->HScrollBar());
876		hScrollBarContainer->GroupLayout()->SetInsets(0, -1, 0,
877			forFilePanel ? 0 : -1);
878		fCountContainer->GroupLayout()->AddView(hScrollBarContainer);
879
880		BSize size = PoseView()->HScrollBar()->MinSize();
881		if (forFilePanel) {
882			// Count view height is 1px smaller than scroll bar because it has
883			// no upper border.
884			size.height -= 1;
885		}
886		PoseView()->CountView()->SetExplicitMinSize(size);
887	}
888}
889
890
891void
892BContainerWindow::RestoreState()
893{
894	UpdateTitle();
895
896	WindowStateNodeOpener opener(this, false);
897	RestoreWindowState(opener.StreamNode());
898	PoseView()->Init(opener.StreamNode());
899
900	RestoreStateCommon();
901}
902
903
904void
905BContainerWindow::RestoreState(const BMessage &message)
906{
907	UpdateTitle();
908
909	RestoreWindowState(message);
910	PoseView()->Init(message);
911
912	RestoreStateCommon();
913}
914
915
916void
917BContainerWindow::RestoreStateCommon()
918{
919	if (fUsesLayout)
920		InitLayout();
921
922	if (BootedInSafeMode())
923		// don't pick up backgrounds in safe mode
924		return;
925
926	WindowStateNodeOpener opener(this, false);
927
928	if (!TargetModel()->IsRoot() && opener.Node() != NULL) {
929		// don't pick up background image for root disks
930		// to do this, would have to have a unique attribute for the
931		// disks window that doesn't collide with the desktop
932		// for R4 this was not done to make things simpler
933		// the default image will still work though
934		fBackgroundImage = BackgroundImage::GetBackgroundImage(
935			opener.Node(), fIsDesktop);
936			// look for background image info in the window's node
937	}
938
939	BNode defaultingNode;
940	if (fBackgroundImage == NULL && !fIsDesktop
941		&& DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) {
942		// look for background image info in the source for defaults
943		fBackgroundImage = BackgroundImage::GetBackgroundImage(&defaultingNode,
944			fIsDesktop);
945	}
946}
947
948
949void
950BContainerWindow::UpdateTitle()
951{
952	// set title to full path, if necessary
953	if (TrackerSettings().ShowFullPathInTitleBar()) {
954		// use the Entry's full path
955		BPath path;
956		TargetModel()->GetPath(&path);
957		SetTitle(path.Path());
958	} else {
959		// use the default look
960		SetTitle(TargetModel()->Name());
961	}
962
963	if (Navigator() != NULL)
964		Navigator()->UpdateLocation(TargetModel(), kActionUpdatePath);
965}
966
967
968void
969BContainerWindow::UpdateBackgroundImage()
970{
971	if (BootedInSafeMode())
972		return;
973
974	WindowStateNodeOpener opener(this, false);
975
976	if (!TargetModel()->IsRoot() && opener.Node() != NULL) {
977		fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
978			opener.Node(), fIsDesktop, PoseView());
979	}
980
981		// look for background image info in the window's node
982	BNode defaultingNode;
983	if (!fBackgroundImage && !fIsDesktop
984		&& DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) {
985		// look for background image info in the source for defaults
986		fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
987			&defaultingNode, fIsDesktop, PoseView());
988	}
989}
990
991
992void
993BContainerWindow::FrameResized(float, float)
994{
995	if (PoseView() != NULL && !fIsDesktop) {
996		BRect extent = PoseView()->Extent();
997		float offsetX = extent.left - PoseView()->Bounds().left;
998		float offsetY = extent.top - PoseView()->Bounds().top;
999
1000		// scroll when the size augmented, there is a negative offset
1001		// and we have resized over the bottom right corner of the extent
1002		BPoint scroll(B_ORIGIN);
1003		if (offsetX < 0 && PoseView()->Bounds().right > extent.right
1004			&& Bounds().Width() > fPreviousBounds.Width()) {
1005			scroll.x = std::max(fPreviousBounds.Width() - Bounds().Width(),
1006				offsetX);
1007		}
1008
1009		if (offsetY < 0 && PoseView()->Bounds().bottom > extent.bottom
1010			&& Bounds().Height() > fPreviousBounds.Height()) {
1011			scroll.y = std::max(fPreviousBounds.Height() - Bounds().Height(),
1012				offsetY);
1013		}
1014
1015		if (scroll != B_ORIGIN)
1016			PoseView()->ScrollBy(scroll.x, scroll.y);
1017
1018		PoseView()->UpdateScrollRange();
1019		PoseView()->ResetPosePlacementHint();
1020	}
1021
1022	fPreviousBounds = Bounds();
1023	if (IsActive())
1024		fStateNeedsSaving = true;
1025}
1026
1027
1028void
1029BContainerWindow::FrameMoved(BPoint)
1030{
1031	if (IsActive())
1032		fStateNeedsSaving = true;
1033}
1034
1035
1036void
1037BContainerWindow::WorkspacesChanged(uint32, uint32)
1038{
1039	if (IsActive())
1040		fStateNeedsSaving = true;
1041}
1042
1043
1044void
1045BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode)
1046{
1047	if (fBackgroundImage == NULL)
1048		return;
1049
1050	if (newMode == kListMode)
1051		fBackgroundImage->Remove();
1052	else if (oldMode == kListMode)
1053		fBackgroundImage->Show(PoseView(), current_workspace());
1054}
1055
1056
1057void
1058BContainerWindow::CheckScreenIntersect()
1059{
1060	BScreen screen(this);
1061	BRect screenFrame(screen.Frame());
1062	BRect frame(Frame());
1063
1064	if (sNewWindRect.bottom > screenFrame.bottom)
1065		sNewWindRect.OffsetTo(85, 50);
1066
1067	if (sNewWindRect.right > screenFrame.right)
1068		sNewWindRect.OffsetTo(85, 50);
1069
1070	if (!frame.Intersects(screenFrame))
1071		MoveTo(sNewWindRect.LeftTop());
1072}
1073
1074
1075void
1076BContainerWindow::SaveState(bool hide)
1077{
1078	if (SaveStateIsEnabled()) {
1079		WindowStateNodeOpener opener(this, true);
1080		if (opener.StreamNode() != NULL)
1081			SaveWindowState(opener.StreamNode());
1082
1083		if (hide)
1084			Hide();
1085
1086		if (opener.StreamNode())
1087			PoseView()->SaveState(opener.StreamNode());
1088
1089		fStateNeedsSaving = false;
1090	}
1091}
1092
1093
1094void
1095BContainerWindow::SaveState(BMessage& message) const
1096{
1097	if (SaveStateIsEnabled()) {
1098		SaveWindowState(message);
1099		PoseView()->SaveState(message);
1100	}
1101}
1102
1103
1104bool
1105BContainerWindow::StateNeedsSaving() const
1106{
1107	return PoseView() != NULL && (fStateNeedsSaving || PoseView()->StateNeedsSaving());
1108}
1109
1110
1111status_t
1112BContainerWindow::GetLayoutState(BNode* node, BMessage* message)
1113{
1114	if (node == NULL || message == NULL)
1115		return B_BAD_VALUE;
1116
1117	status_t result = node->InitCheck();
1118	if (result != B_OK)
1119		return result;
1120
1121	// ToDo: get rid of this, use AttrStream instead
1122	node->RewindAttrs();
1123	char attrName[256];
1124	while (node->GetNextAttrName(attrName) == B_OK) {
1125		attr_info info;
1126		if (node->GetAttrInfo(attrName, &info) != B_OK)
1127			continue;
1128
1129		// filter out attributes that are not related to window position
1130		// and column resizing
1131		// more can be added as needed
1132		if (strcmp(attrName, kAttrWindowFrame) != 0
1133			&& strcmp(attrName, kAttrColumns) != 0
1134			&& strcmp(attrName, kAttrViewState) != 0
1135			&& strcmp(attrName, kAttrColumnsForeign) != 0
1136			&& strcmp(attrName, kAttrViewStateForeign) != 0) {
1137			continue;
1138		}
1139
1140		char* buffer = new char[info.size];
1141		if (node->ReadAttr(attrName, info.type, 0, buffer,
1142				(size_t)info.size) == info.size) {
1143			message->AddData(attrName, info.type, buffer, (ssize_t)info.size);
1144		}
1145		delete[] buffer;
1146	}
1147
1148	return B_OK;
1149}
1150
1151
1152status_t
1153BContainerWindow::SetLayoutState(BNode* node, const BMessage* message)
1154{
1155	status_t result = node->InitCheck();
1156	if (result != B_OK)
1157		return result;
1158
1159	for (int32 globalIndex = 0; ;) {
1160#if B_BEOS_VERSION_DANO
1161		const char* name;
1162#else
1163		char* name;
1164#endif
1165		type_code type;
1166		int32 count;
1167		status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name,
1168			&type, &count);
1169		if (result != B_OK)
1170			break;
1171
1172		for (int32 index = 0; index < count; index++) {
1173			const void* buffer;
1174			ssize_t size;
1175			result = message->FindData(name, type, index, &buffer, &size);
1176			if (result != B_OK) {
1177				PRINT(("error reading %s \n", name));
1178				return result;
1179			}
1180
1181			if (node->WriteAttr(name, type, 0, buffer,
1182					(size_t)size) != size) {
1183				PRINT(("error writing %s \n", name));
1184				return result;
1185			}
1186			globalIndex++;
1187		}
1188	}
1189
1190	return B_OK;
1191}
1192
1193
1194bool
1195BContainerWindow::ShouldAddMenus() const
1196{
1197	return true;
1198}
1199
1200
1201bool
1202BContainerWindow::ShouldAddScrollBars() const
1203{
1204	return true;
1205}
1206
1207
1208Model*
1209BContainerWindow::TargetModel() const
1210{
1211	return PoseView()->TargetModel();
1212}
1213
1214
1215void
1216BContainerWindow::SelectionChanged()
1217{
1218}
1219
1220
1221void
1222BContainerWindow::Zoom(BPoint, float, float)
1223{
1224	BRect oldZoomRect(fSavedZoomRect);
1225	fSavedZoomRect = Frame();
1226	ResizeToFit();
1227
1228	if (fSavedZoomRect == Frame() && oldZoomRect.IsValid())
1229		ResizeTo(oldZoomRect.Width(), oldZoomRect.Height());
1230}
1231
1232
1233void
1234BContainerWindow::ResizeToFit()
1235{
1236	BScreen screen(this);
1237	BRect screenFrame(screen.Frame());
1238
1239	screenFrame.InsetBy(5, 5);
1240	BMessage decoratorSettings;
1241	GetDecoratorSettings(&decoratorSettings);
1242
1243	float tabHeight = 15;
1244	BRect tabRect;
1245	if (decoratorSettings.FindRect("tab frame", &tabRect) == B_OK)
1246		tabHeight = tabRect.Height();
1247	screenFrame.top += tabHeight;
1248
1249	BRect frame(Frame());
1250
1251	float widthDiff = frame.Width() - PoseView()->Frame().Width();
1252	float heightDiff = frame.Height() - PoseView()->Frame().Height();
1253
1254	// move frame left top on screen
1255	BPoint leftTop(frame.LeftTop());
1256	leftTop.ConstrainTo(screenFrame);
1257	frame.OffsetTo(leftTop);
1258
1259	// resize to extent size
1260	BRect extent(PoseView()->Extent());
1261	frame.right = frame.left + extent.Width() + widthDiff;
1262	frame.bottom = frame.top + extent.Height() + heightDiff;
1263
1264	// make sure entire window fits on screen
1265	frame = frame & screenFrame;
1266
1267	ResizeTo(frame.Width(), frame.Height());
1268	MoveTo(frame.LeftTop());
1269	PoseView()->DisableScrollBars();
1270
1271	// scroll if there is an offset
1272	PoseView()->ScrollBy(
1273		extent.left - PoseView()->Bounds().left,
1274		extent.top - PoseView()->Bounds().top);
1275
1276	PoseView()->UpdateScrollRange();
1277	PoseView()->EnableScrollBars();
1278}
1279
1280
1281void
1282BContainerWindow::MessageReceived(BMessage* message)
1283{
1284	switch (message->what) {
1285		case B_CUT:
1286		case B_COPY:
1287		case B_PASTE:
1288		case B_SELECT_ALL:
1289		{
1290			BView* view = CurrentFocus();
1291			if (dynamic_cast<BTextView*>(view) == NULL) {
1292				// The selected item is not a BTextView, so forward the
1293				// message to the PoseView.
1294				if (PoseView() != NULL)
1295					PostMessage(message, PoseView());
1296			} else {
1297				// Since we catch the generic clipboard shortcuts in a way that
1298				// means the BTextView will never get them, we must
1299				// manually forward them ourselves.
1300				//
1301				// However, we have to take care to not forward the custom
1302				// clipboard messages, else we would wind up in infinite
1303				// recursion.
1304				PostMessage(message, view);
1305			}
1306			break;
1307		}
1308
1309		case kCutMoreSelectionToClipboard:
1310		case kCopyMoreSelectionToClipboard:
1311		case kPasteLinksFromClipboard:
1312			if (PoseView() != NULL)
1313				PostMessage(message, PoseView());
1314			break;
1315
1316		case B_UNDO: {
1317			BView* view = CurrentFocus();
1318			if (dynamic_cast<BTextView*>(view) == NULL) {
1319				FSUndo();
1320			} else {
1321				view->MessageReceived(message);
1322			}
1323			break;
1324		}
1325
1326		case B_REDO: {
1327			BView* view = CurrentFocus();
1328			if (dynamic_cast<BTextView*>(view) == NULL) {
1329				FSRedo();
1330			} else {
1331				view->MessageReceived(message);
1332			}
1333			break;
1334		}
1335
1336		case kNewFolder:
1337			PostMessage(message, PoseView());
1338			break;
1339
1340		case kRestoreState:
1341			if (message->HasMessage("state")) {
1342				BMessage state;
1343				message->FindMessage("state", &state);
1344				Init(&state);
1345			} else
1346				Init();
1347			break;
1348
1349		case kResizeToFit:
1350			ResizeToFit();
1351			break;
1352
1353		case kLoadAddOn:
1354			LoadAddOn(message);
1355			break;
1356
1357		case kCopySelectionTo:
1358		{
1359			entry_ref ref;
1360			if (message->FindRef("refs", &ref) != B_OK)
1361				break;
1362
1363			BRoster().AddToRecentFolders(&ref);
1364
1365			Model model(&ref);
1366			if (model.InitCheck() != B_OK)
1367				break;
1368
1369			if (*model.NodeRef() == *TargetModel()->NodeRef())
1370				PoseView()->DuplicateSelection();
1371			else
1372				PoseView()->MoveSelectionInto(&model, this, true);
1373			break;
1374		}
1375
1376		case kMoveSelectionTo:
1377		{
1378			entry_ref ref;
1379			if (message->FindRef("refs", &ref) != B_OK)
1380				break;
1381
1382			BRoster().AddToRecentFolders(&ref);
1383
1384			Model model(&ref);
1385			if (model.InitCheck() != B_OK)
1386				break;
1387
1388			PoseView()->MoveSelectionInto(&model, this, false, true);
1389			break;
1390		}
1391
1392		case kCreateLink:
1393		case kCreateRelativeLink:
1394		{
1395			entry_ref ref;
1396			if (message->FindRef("refs", &ref) == B_OK) {
1397				BRoster().AddToRecentFolders(&ref);
1398
1399				Model model(&ref);
1400				if (model.InitCheck() != B_OK)
1401					break;
1402
1403				PoseView()->MoveSelectionInto(&model, this, false, false,
1404					message->what == kCreateLink,
1405					message->what == kCreateRelativeLink);
1406			} else if (!TargetModel()->IsQuery()
1407				&& !TargetModel()->IsVirtualDirectory()) {
1408				// no destination specified, create link in same dir as item
1409				PoseView()->MoveSelectionInto(TargetModel(), this, false, false,
1410					message->what == kCreateLink,
1411					message->what == kCreateRelativeLink);
1412			}
1413			break;
1414		}
1415
1416		case kShowSelectionWindow:
1417			ShowSelectionWindow();
1418			break;
1419
1420		case kSelectMatchingEntries:
1421			PoseView()->SelectMatchingEntries(message);
1422			break;
1423
1424		case kFindButton:
1425			(new FindWindow())->Show();
1426			break;
1427
1428		case kQuitTracker:
1429			be_app->PostMessage(B_QUIT_REQUESTED);
1430			break;
1431
1432		case kRestoreBackgroundImage:
1433			UpdateBackgroundImage();
1434			break;
1435
1436		case kSwitchDirectory:
1437		{
1438			entry_ref ref;
1439			if (message->FindRef("refs", &ref) != B_OK)
1440				break;
1441
1442			BEntry entry;
1443			if (entry.SetTo(&ref) != B_OK)
1444				break;
1445
1446			if (StateNeedsSaving())
1447				SaveState(false);
1448
1449			bool wasInTrash = IsTrash() || InTrash();
1450			bool isRoot = TargetModel()->IsRoot();
1451
1452			// Switch dir and apply new state
1453			WindowStateNodeOpener opener(this, false);
1454			opener.SetTo(&entry, false);
1455
1456			// Update PoseView
1457			PoseView()->SwitchDir(&ref, opener.StreamNode());
1458
1459			fIsTrash = FSIsTrashDir(&entry);
1460			fInTrash = FSInTrashDir(&ref);
1461
1462			if (wasInTrash ^ (IsTrash() || InTrash())
1463				|| isRoot != TargetModel()->IsRoot()) {
1464				RepopulateMenus();
1465			}
1466
1467			if (Navigator() != NULL) {
1468				// update Navigation bar
1469				int32 action = kActionSet;
1470				if (message->FindInt32("action", &action) != B_OK) {
1471					// Design problem? Why does FindInt32 touch
1472					// 'action' at all if he can't find it??
1473					action = kActionSet;
1474				}
1475				Navigator()->UpdateLocation(TargetModel(), action);
1476			}
1477
1478			TrackerSettings settings;
1479			if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar())
1480				SetPathWatchingEnabled(true);
1481
1482			SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1483
1484			// Update draggable folder icon
1485			if (fMenuBar != NULL) {
1486				if (!TargetModel()->IsRoot() && !IsTrash()) {
1487					// Folder icon should be visible, but in single
1488					// window navigation, it might not be.
1489					if (fDraggableIcon != NULL) {
1490						IconCache::sIconCache->IconChanged(TargetModel());
1491						if (fDraggableIcon->IsHidden())
1492							fDraggableIcon->Show();
1493						fDraggableIcon->Invalidate();
1494					} else
1495						_AddFolderIcon();
1496				} else if (fDraggableIcon != NULL)
1497					fDraggableIcon->Hide();
1498			}
1499
1500			// Update window title
1501			UpdateTitle();
1502			break;
1503		}
1504
1505		case B_REFS_RECEIVED:
1506			if (Dragging()) {
1507				// ref in this message is the target,
1508				// the end point of the drag
1509
1510				entry_ref ref;
1511				if (message->FindRef("refs", &ref) == B_OK) {
1512					fWaitingForRefs = false;
1513					BEntry entry(&ref, true);
1514					// don't copy to printers dir
1515					if (!FSIsPrintersDir(&entry)) {
1516						if (entry.InitCheck() == B_OK
1517							&& entry.IsDirectory()) {
1518							Model targetModel(&entry, true, false);
1519							BPoint dropPoint;
1520							uint32 buttons;
1521							PoseView()->GetMouse(&dropPoint, &buttons, true);
1522							PoseView()->HandleDropCommon(fDragMessage,
1523								&targetModel, NULL, PoseView(), dropPoint);
1524						}
1525					}
1526				}
1527				DragStop();
1528			}
1529			break;
1530
1531		case B_TRACKER_ADDON_MESSAGE:
1532		{
1533			_PassMessageToAddOn(message);
1534			break;
1535		}
1536
1537		case B_OBSERVER_NOTICE_CHANGE:
1538		{
1539			int32 observerWhat;
1540			if (message->FindInt32("be:observe_change_what", &observerWhat)
1541					== B_OK) {
1542				TrackerSettings settings;
1543				switch (observerWhat) {
1544					case kWindowsShowFullPathChanged:
1545						UpdateTitle();
1546						if (!IsPathWatchingEnabled()
1547							&& settings.ShowFullPathInTitleBar()) {
1548							SetPathWatchingEnabled(true);
1549						}
1550						if (IsPathWatchingEnabled()
1551							&& !(settings.ShowNavigator()
1552								|| settings.ShowFullPathInTitleBar())) {
1553							SetPathWatchingEnabled(false);
1554						}
1555						break;
1556
1557					case kSingleWindowBrowseChanged:
1558						if (settings.SingleWindowBrowse()
1559							&& !Navigator()
1560							&& TargetModel()->IsDirectory()
1561							&& !PoseView()->IsFilePanel()
1562							&& !PoseView()->IsDesktopWindow()) {
1563							fNavigator = new BNavigator(TargetModel());
1564							fPoseContainer->GridLayout()->AddView(fNavigator,
1565								0, 0, 2);
1566							fNavigator->Hide();
1567							SetPathWatchingEnabled(settings.ShowNavigator()
1568								|| settings.ShowFullPathInTitleBar());
1569						}
1570
1571						if (!settings.SingleWindowBrowse()
1572							&& !fIsDesktop && TargetModel()->IsDesktop()) {
1573							// Close the "Desktop" window, but not the Desktop
1574							this->Quit();
1575						}
1576
1577						SetSingleWindowBrowseShortcuts(
1578							settings.SingleWindowBrowse());
1579						break;
1580
1581					case kShowNavigatorChanged:
1582						ShowNavigator(settings.ShowNavigator());
1583						if (!IsPathWatchingEnabled()
1584							&& settings.ShowNavigator()) {
1585							SetPathWatchingEnabled(true);
1586						}
1587						if (IsPathWatchingEnabled()
1588							&& !(settings.ShowNavigator()
1589								|| settings.ShowFullPathInTitleBar())) {
1590							SetPathWatchingEnabled(false);
1591						}
1592						SetSingleWindowBrowseShortcuts(
1593							settings.SingleWindowBrowse());
1594						break;
1595
1596					default:
1597						_inherited::MessageReceived(message);
1598						break;
1599				}
1600			}
1601			break;
1602		}
1603
1604		case B_NODE_MONITOR:
1605			UpdateTitle();
1606			break;
1607
1608		default:
1609			_inherited::MessageReceived(message);
1610			break;
1611	}
1612}
1613
1614
1615void
1616BContainerWindow::SetCutItem(BMenu* menu)
1617{
1618	BMenuItem* item;
1619	if ((item = menu->FindItem(B_CUT)) == NULL
1620		&& (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) {
1621		return;
1622	}
1623
1624	if (PoseView() != CurrentFocus())
1625		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1626	else {
1627		if (TargetModel()->IsRoot() || TargetModel()->IsTrash()
1628			|| TargetModel()->IsVirtualDirectory()) {
1629			// cannot cut files in root, trash or in a virtual directory
1630			item->SetEnabled(false);
1631		} else {
1632			item->SetEnabled(PoseView()->CountSelected() > 0
1633				&& !PoseView()->SelectedVolumeIsReadOnly());
1634		}
1635	}
1636
1637	if ((modifiers() & B_SHIFT_KEY) != 0) {
1638		item->SetLabel(B_TRANSLATE("Cut more"));
1639		item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY);
1640		item->SetMessage(new BMessage(kCutMoreSelectionToClipboard));
1641	} else {
1642		item->SetLabel(B_TRANSLATE("Cut"));
1643		item->SetShortcut('X', B_COMMAND_KEY);
1644		item->SetMessage(new BMessage(B_CUT));
1645	}
1646}
1647
1648
1649void
1650BContainerWindow::SetCopyItem(BMenu* menu)
1651{
1652	BMenuItem* item;
1653	if ((item = menu->FindItem(B_COPY)) == NULL
1654		&& (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) {
1655		return;
1656	}
1657
1658	if (PoseView() != CurrentFocus())
1659		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1660	else
1661		item->SetEnabled(PoseView()->CountSelected() > 0);
1662
1663	if ((modifiers() & B_SHIFT_KEY) != 0) {
1664		item->SetLabel(B_TRANSLATE("Copy more"));
1665		item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY);
1666		item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard));
1667	} else {
1668		item->SetLabel(B_TRANSLATE("Copy"));
1669		item->SetShortcut('C', B_COMMAND_KEY);
1670		item->SetMessage(new BMessage(B_COPY));
1671	}
1672}
1673
1674
1675void
1676BContainerWindow::SetPasteItem(BMenu* menu)
1677{
1678	BMenuItem* item;
1679	if ((item = menu->FindItem(B_PASTE)) == NULL
1680		&& (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) {
1681		return;
1682	}
1683
1684	if (PoseView() != CurrentFocus())
1685		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1686	else {
1687		item->SetEnabled(FSClipboardHasRefs()
1688			&& !PoseView()->TargetVolumeIsReadOnly());
1689	}
1690
1691	if ((modifiers() & B_SHIFT_KEY) != 0) {
1692		item->SetLabel(B_TRANSLATE("Paste links"));
1693		item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY);
1694		item->SetMessage(new BMessage(kPasteLinksFromClipboard));
1695	} else {
1696		item->SetLabel(B_TRANSLATE("Paste"));
1697		item->SetShortcut('V', B_COMMAND_KEY);
1698		item->SetMessage(new BMessage(B_PASTE));
1699	}
1700}
1701
1702
1703void
1704BContainerWindow::SetArrangeMenu(BMenu* menu)
1705{
1706	BMenuItem* item;
1707	if ((item = menu->FindItem(kCleanup)) == NULL
1708		&& (item = menu->FindItem(kCleanupAll)) == NULL) {
1709		return;
1710	}
1711
1712	item->Menu()->SetEnabled(PoseView()->CountItems() > 0
1713		&& (PoseView()->ViewMode() != kListMode));
1714
1715	BMenu* arrangeMenu;
1716
1717	if ((modifiers() & B_SHIFT_KEY) != 0) {
1718		item->SetLabel(B_TRANSLATE("Clean up all"));
1719		item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY);
1720		item->SetMessage(new BMessage(kCleanupAll));
1721		arrangeMenu = item->Menu();
1722	} else {
1723		item->SetLabel(B_TRANSLATE("Clean up"));
1724		item->SetShortcut('K', B_COMMAND_KEY);
1725		item->SetMessage(new BMessage(kCleanup));
1726		arrangeMenu = item->Menu();
1727	}
1728
1729	MarkArrangeByMenu(arrangeMenu);
1730}
1731
1732
1733void
1734BContainerWindow::SetCloseItem(BMenu* menu)
1735{
1736	BMenuItem* item;
1737	if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL
1738		&& (item = menu->FindItem(kCloseAllWindows)) == NULL) {
1739		return;
1740	}
1741
1742	if ((modifiers() & B_SHIFT_KEY) != 0) {
1743		item->SetLabel(B_TRANSLATE("Close all"));
1744		item->SetShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY);
1745		item->SetTarget(be_app);
1746		item->SetMessage(new BMessage(kCloseAllWindows));
1747	} else {
1748		item->SetLabel(B_TRANSLATE("Close"));
1749		item->SetShortcut('W', B_COMMAND_KEY);
1750		item->SetTarget(this);
1751		item->SetMessage(new BMessage(B_QUIT_REQUESTED));
1752	}
1753}
1754
1755
1756bool
1757BContainerWindow::IsShowing(const node_ref* node) const
1758{
1759	return PoseView()->Represents(node);
1760}
1761
1762
1763bool
1764BContainerWindow::IsShowing(const entry_ref* entry) const
1765{
1766	return PoseView()->Represents(entry);
1767}
1768
1769
1770void
1771BContainerWindow::AddMenus()
1772{
1773	fFileMenu = new BMenu(B_TRANSLATE("File"));
1774	AddFileMenu(fFileMenu);
1775	fMenuBar->AddItem(fFileMenu);
1776	fWindowMenu = new BMenu(B_TRANSLATE("Window"));
1777	fMenuBar->AddItem(fWindowMenu);
1778	AddWindowMenu(fWindowMenu);
1779	// just create the attribute, decide to add it later
1780	fAttrMenu = new BMenu(B_TRANSLATE("Attributes"));
1781	NewAttributesMenu(fAttrMenu);
1782	PopulateArrangeByMenu(fArrangeByMenu);
1783}
1784
1785
1786void
1787BContainerWindow::AddFileMenu(BMenu* menu)
1788{
1789	BMenuItem* item;
1790
1791	if (!PoseView()->IsFilePanel()) {
1792		menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
1793			new BMessage(kFindButton), 'F'));
1794	}
1795
1796	if (!TargetModel()->IsQuery() && !TargetModel()->IsVirtualDirectory()
1797		&& !IsTrash() && !IsPrintersDir() && !TargetModel()->IsRoot()) {
1798		if (!PoseView()->IsFilePanel()) {
1799			TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(),
1800				B_TRANSLATE("New"));
1801			menu->AddItem(templatesMenu);
1802			templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
1803			templatesMenu->SetTargetForItems(PoseView());
1804		} else {
1805			item = new BMenuItem(B_TRANSLATE("New folder"),
1806				new BMessage(kNewFolder), 'N');
1807			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
1808			menu->AddItem(item);
1809		}
1810	}
1811	menu->AddSeparatorItem();
1812
1813	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
1814		new BMessage(kOpenSelection), 'O'));
1815	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
1816		new BMessage(kGetInfo), 'I'));
1817	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
1818		new BMessage(kEditItem), 'E'));
1819
1820	if (IsTrash() || InTrash()) {
1821		menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"),
1822			new BMessage(kRestoreFromTrash)));
1823		if (IsTrash()) {
1824			// add as first item in menu
1825			menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
1826				new BMessage(kEmptyTrash)), 0);
1827			menu->AddItem(new BSeparatorItem(), 1);
1828		}
1829	} else if (IsPrintersDir()) {
1830		menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
1831			new BMessage(kAddPrinter), 'N'), 0);
1832		menu->AddItem(new BSeparatorItem(), 1);
1833		menu->AddItem(new BMenuItem(B_TRANSLATE("Make active printer"),
1834			new BMessage(kMakeActivePrinter)));
1835	} else if (TargetModel()->IsRoot()) {
1836		item = new BMenuItem(B_TRANSLATE("Unmount"),
1837			new BMessage(kUnmountVolume), 'U');
1838		item->SetEnabled(false);
1839		menu->AddItem(item);
1840		menu->AddItem(new BMenuItem(
1841			B_TRANSLATE("Mount settings" B_UTF8_ELLIPSIS),
1842			new BMessage(kRunAutomounterSettings)));
1843	} else {
1844		item = new BMenuItem(B_TRANSLATE("Duplicate"),
1845			new BMessage(kDuplicateSelection), 'D');
1846		item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
1847		menu->AddItem(item);
1848
1849		item = new BMenuItem(B_TRANSLATE("Move to Trash"),
1850			new BMessage(kMoveToTrash), 'T');
1851		item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
1852		menu->AddItem(item);
1853
1854		menu->AddSeparatorItem();
1855
1856		// The "Move To", "Copy To", "Create Link" menus are inserted
1857		// at this place, have a look at:
1858		// BContainerWindow::SetupMoveCopyMenus()
1859	}
1860
1861	BMenuItem* cutItem = NULL;
1862	BMenuItem* copyItem = NULL;
1863	BMenuItem* pasteItem = NULL;
1864	if (!IsPrintersDir()) {
1865		menu->AddSeparatorItem();
1866
1867		if (!TargetModel()->IsRoot()) {
1868			cutItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Cut"),
1869				new BMessage(B_CUT), 'X');
1870			menu->AddItem(cutItem);
1871			copyItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Copy"),
1872				new BMessage(B_COPY), 'C');
1873			menu->AddItem(copyItem);
1874			pasteItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Paste"),
1875				new BMessage(B_PASTE), 'V');
1876			menu->AddItem(pasteItem);
1877			menu->AddSeparatorItem();
1878
1879			menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
1880				new BMessage(kIdentifyEntry)));
1881		}
1882		BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
1883		addOnMenuItem->SetFont(be_plain_font);
1884		menu->AddItem(addOnMenuItem);
1885	}
1886
1887	menu->SetTargetForItems(PoseView());
1888	if (cutItem != NULL)
1889		cutItem->SetTarget(this);
1890
1891	if (copyItem != NULL)
1892		copyItem->SetTarget(this);
1893
1894	if (pasteItem != NULL)
1895		pasteItem->SetTarget(this);
1896}
1897
1898
1899void
1900BContainerWindow::AddWindowMenu(BMenu* menu)
1901{
1902	BMenuItem* item;
1903
1904	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
1905
1906	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
1907	BMessage* message;
1908
1909	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
1910		uint32 iconSize = kIconSizes[i];
1911		message = new BMessage(kIconMode);
1912		message->AddInt32("size", iconSize);
1913		BString label;
1914		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" �� %" B_PRId32,
1915			"The '��' is the Unicode multiplication sign U+00D7"),
1916			iconSize, iconSize);
1917		item = new BMenuItem(label, message);
1918		item->SetTarget(PoseView());
1919		iconSizeMenu->AddItem(item);
1920	}
1921
1922	iconSizeMenu->AddSeparatorItem();
1923
1924	message = new BMessage(kIconMode);
1925	message->AddInt32("scale", 0);
1926	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
1927	item->SetTarget(PoseView());
1928	iconSizeMenu->AddItem(item);
1929
1930	message = new BMessage(kIconMode);
1931	message->AddInt32("scale", 1);
1932	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
1933	item->SetTarget(PoseView());
1934	iconSizeMenu->AddItem(item);
1935
1936	// A sub menu where the super item can be invoked.
1937	menu->AddItem(iconSizeMenu);
1938	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
1939	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
1940	iconSizeMenu->Superitem()->SetTarget(PoseView());
1941
1942	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
1943		new BMessage(kMiniIconMode), '2');
1944	item->SetTarget(PoseView());
1945	menu->AddItem(item);
1946
1947	item = new BMenuItem(B_TRANSLATE("List view"),
1948		new BMessage(kListMode), '3');
1949	item->SetTarget(PoseView());
1950	menu->AddItem(item);
1951
1952	menu->AddSeparatorItem();
1953
1954	item = new BMenuItem(B_TRANSLATE("Resize to fit"),
1955		new BMessage(kResizeToFit), 'Y');
1956	item->SetTarget(this);
1957	menu->AddItem(item);
1958
1959	fArrangeByMenu = new BMenu(B_TRANSLATE("Arrange by"));
1960	menu->AddItem(fArrangeByMenu);
1961
1962	item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
1963		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
1964	item->SetTarget(PoseView());
1965	menu->AddItem(item);
1966
1967	item = new BMenuItem(B_TRANSLATE("Select all"),
1968		new BMessage(B_SELECT_ALL), 'A');
1969	item->SetTarget(this);
1970	menu->AddItem(item);
1971
1972	item = new BMenuItem(B_TRANSLATE("Invert selection"),
1973		new BMessage(kInvertSelection), 'S');
1974	item->SetTarget(PoseView());
1975	menu->AddItem(item);
1976
1977	if (!IsTrash()) {
1978		item = new BMenuItem(B_TRANSLATE("Open parent"),
1979			new BMessage(kOpenParentDir), B_UP_ARROW);
1980		item->SetTarget(PoseView());
1981		menu->AddItem(item);
1982	}
1983
1984	item = new BMenuItem(B_TRANSLATE("Close"),
1985		new BMessage(B_QUIT_REQUESTED), 'W');
1986	item->SetTarget(this);
1987	menu->AddItem(item);
1988
1989	item = new BMenuItem(B_TRANSLATE("Close all in workspace"),
1990		new BMessage(kCloseAllInWorkspace), 'Q');
1991	item->SetTarget(be_app);
1992	menu->AddItem(item);
1993
1994	menu->AddSeparatorItem();
1995
1996	item = new BMenuItem(B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS),
1997		new BMessage(kShowSettingsWindow), ',');
1998	item->SetTarget(be_app);
1999	menu->AddItem(item);
2000}
2001
2002
2003void
2004BContainerWindow::AddShortcuts()
2005{
2006	// add equivalents of the menu shortcuts to the menuless desktop window
2007	ASSERT(!IsTrash());
2008	ASSERT(!PoseView()->IsFilePanel());
2009	ASSERT(!TargetModel()->IsQuery());
2010	ASSERT(!TargetModel()->IsVirtualDirectory());
2011
2012	AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY,
2013		new BMessage(kCutMoreSelectionToClipboard), this);
2014	AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY,
2015		new BMessage(kCopyMoreSelectionToClipboard), this);
2016	AddShortcut('F', B_COMMAND_KEY,
2017		new BMessage(kFindButton), PoseView());
2018	AddShortcut('N', B_COMMAND_KEY,
2019		new BMessage(kNewFolder), PoseView());
2020	AddShortcut('O', B_COMMAND_KEY,
2021		new BMessage(kOpenSelection), PoseView());
2022	AddShortcut('I', B_COMMAND_KEY,
2023		new BMessage(kGetInfo), PoseView());
2024	AddShortcut('E', B_COMMAND_KEY,
2025		new BMessage(kEditItem), PoseView());
2026	AddShortcut('D', B_COMMAND_KEY,
2027		new BMessage(kDuplicateSelection), PoseView());
2028	AddShortcut('T', B_COMMAND_KEY,
2029		new BMessage(kMoveToTrash), PoseView());
2030	AddShortcut('K', B_COMMAND_KEY,
2031		new BMessage(kCleanup), PoseView());
2032	AddShortcut('A', B_COMMAND_KEY,
2033		new BMessage(B_SELECT_ALL), PoseView());
2034	AddShortcut('S', B_COMMAND_KEY,
2035		new BMessage(kInvertSelection), PoseView());
2036	AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
2037		new BMessage(kShowSelectionWindow), PoseView());
2038	AddShortcut('G', B_COMMAND_KEY,
2039		new BMessage(kEditQuery), PoseView());
2040		// it is ok to add a global Edit query shortcut here, PoseView will
2041		// filter out cases where selected pose is not a query
2042	AddShortcut('U', B_COMMAND_KEY,
2043		new BMessage(kUnmountVolume), PoseView());
2044	AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
2045		new BMessage(kOpenParentDir), PoseView());
2046	AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY,
2047		new BMessage(kOpenSelectionWith), PoseView());
2048
2049	BMessage* decreaseSize = new BMessage(kIconMode);
2050	decreaseSize->AddInt32("scale", 0);
2051	AddShortcut('-', B_COMMAND_KEY, decreaseSize, PoseView());
2052
2053	BMessage* increaseSize = new BMessage(kIconMode);
2054	increaseSize->AddInt32("scale", 1);
2055	AddShortcut('+', B_COMMAND_KEY, increaseSize, PoseView());
2056}
2057
2058
2059void
2060BContainerWindow::MenusBeginning()
2061{
2062	if (fMenuBar == NULL)
2063		return;
2064
2065	if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
2066		// don't commit active pose if only a keyboard shortcut is
2067		// invoked - this would prevent Cut/Copy/Paste from working
2068		PoseView()->CommitActivePose();
2069	}
2070
2071	// File menu
2072	int32 selectCount = PoseView()->SelectionList()->CountItems();
2073
2074	SetupOpenWithMenu(fFileMenu);
2075	SetupMoveCopyMenus(selectCount
2076		? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef()
2077		: NULL, fFileMenu);
2078
2079	if (TargetModel()->IsRoot()) {
2080		BVolume boot;
2081		BVolumeRoster().GetBootVolume(&boot);
2082
2083		bool ejectableVolumeSelected = false;
2084		for (int32 index = 0; index < selectCount; index++) {
2085			Model* model
2086				= PoseView()->SelectionList()->ItemAt(index)->TargetModel();
2087			if (model->IsVolume()) {
2088				BVolume volume;
2089				volume.SetTo(model->NodeRef()->device);
2090				if (volume != boot) {
2091					ejectableVolumeSelected = true;
2092					break;
2093				}
2094			}
2095		}
2096		BMenuItem* item = fMenuBar->FindItem(kUnmountVolume);
2097		if (item != NULL)
2098			item->SetEnabled(ejectableVolumeSelected);
2099	}
2100
2101	UpdateMenu(fMenuBar, kMenuBarContext);
2102
2103	AddMimeTypesToMenu(fAttrMenu);
2104
2105	if (IsPrintersDir())
2106		EnableNamedMenuItem(fFileMenu, kMakeActivePrinter, selectCount == 1);
2107}
2108
2109
2110void
2111BContainerWindow::MenusEnded()
2112{
2113	// when we're done we want to clear nav menus for next time
2114	DeleteSubmenu(fNavigationItem);
2115	DeleteSubmenu(fMoveToItem);
2116	DeleteSubmenu(fCopyToItem);
2117	DeleteSubmenu(fCreateLinkItem);
2118	DeleteSubmenu(fOpenWithItem);
2119}
2120
2121
2122void
2123BContainerWindow::SetupNavigationMenu(const entry_ref* ref, BMenu* parent)
2124{
2125	// start by removing nav item (and separator) from old menu
2126	if (fNavigationItem != NULL) {
2127		BMenu* menu = fNavigationItem->Menu();
2128		if (menu != NULL) {
2129			menu->RemoveItem(fNavigationItem);
2130			BMenuItem* item = menu->RemoveItem((int32)0);
2131			ASSERT(item != fNavigationItem);
2132			delete item;
2133		}
2134	}
2135
2136	// if we weren't passed a ref then we're navigating this window
2137	if (ref == NULL)
2138		ref = TargetModel()->EntryRef();
2139
2140	BEntry entry;
2141	if (entry.SetTo(ref) != B_OK)
2142		return;
2143
2144	// only navigate directories and queries (check for symlink here)
2145	Model model(&entry);
2146	entry_ref resolvedRef;
2147
2148	if (model.InitCheck() != B_OK
2149		|| (!model.IsContainer() && !model.IsSymLink())) {
2150		return;
2151	}
2152
2153	if (model.IsSymLink()) {
2154		if (entry.SetTo(model.EntryRef(), true) != B_OK)
2155			return;
2156
2157		Model resolvedModel(&entry);
2158		if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer())
2159			return;
2160
2161		entry.GetRef(&resolvedRef);
2162		ref = &resolvedRef;
2163	}
2164
2165	if (fNavigationItem == NULL) {
2166		fNavigationItem = new ModelMenuItem(&model,
2167			new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this));
2168	}
2169
2170	// setup a navigation menu item which will dynamically load items
2171	// as menu items are traversed
2172	BNavMenu* navMenu = dynamic_cast<BNavMenu*>(fNavigationItem->Submenu());
2173	navMenu->SetNavDir(ref);
2174	fNavigationItem->SetLabel(model.Name());
2175	fNavigationItem->SetEntry(&entry);
2176
2177	parent->AddItem(fNavigationItem, 0);
2178	parent->AddItem(new BSeparatorItem(), 1);
2179
2180	BMessage* message = new BMessage(B_REFS_RECEIVED);
2181	message->AddRef("refs", ref);
2182	fNavigationItem->SetMessage(message);
2183	fNavigationItem->SetTarget(be_app);
2184
2185	if (!Dragging())
2186		parent->SetTrackingHook(NULL, NULL);
2187}
2188
2189
2190void
2191BContainerWindow::SetupEditQueryItem(BMenu* menu)
2192{
2193	ASSERT(menu);
2194	// File menu
2195	int32 selectCount = PoseView()->CountSelected();
2196
2197	// add Edit query if appropriate
2198	bool queryInSelection = false;
2199	if (selectCount && selectCount < 100) {
2200		// only do this for a limited number of selected poses
2201
2202		// if any queries selected, add an edit query menu item
2203		for (int32 index = 0; index < selectCount; index++) {
2204			BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2205			Model model(pose->TargetModel()->EntryRef(), true);
2206			if (model.InitCheck() != B_OK)
2207				continue;
2208
2209			if (model.IsQuery() || model.IsQueryTemplate()) {
2210				queryInSelection = true;
2211				break;
2212			}
2213		}
2214	}
2215
2216	bool poseViewIsQuery = TargetModel()->IsQuery();
2217		// if the view is a query pose view, add edit query menu item
2218
2219	BMenuItem* item = menu->FindItem(kEditQuery);
2220	if (!poseViewIsQuery && !queryInSelection && item != NULL)
2221		item->Menu()->RemoveItem(item);
2222	else if ((poseViewIsQuery || queryInSelection) && item == NULL) {
2223		// add edit query item after Open
2224		item = menu->FindItem(kOpenSelection);
2225		if (item) {
2226			int32 itemIndex = item->Menu()->IndexOf(item);
2227			BMenuItem* query = new BMenuItem(B_TRANSLATE("Edit query"),
2228				new BMessage(kEditQuery), 'G');
2229			item->Menu()->AddItem(query, itemIndex + 1);
2230			query->SetTarget(PoseView());
2231		}
2232	}
2233}
2234
2235
2236void
2237BContainerWindow::SetupOpenWithMenu(BMenu* parent)
2238{
2239	// start by removing nav item (and separator) from old menu
2240	if (fOpenWithItem) {
2241		BMenu* menu = fOpenWithItem->Menu();
2242		if (menu != NULL)
2243			menu->RemoveItem(fOpenWithItem);
2244
2245		delete fOpenWithItem;
2246		fOpenWithItem = 0;
2247	}
2248
2249	int32 selectCount = PoseView()->CountSelected();
2250	if (selectCount <= 0) {
2251		// no selection, nothing to open
2252		return;
2253	}
2254
2255	if (TargetModel()->IsRoot()) {
2256		// don't add ourselves if we are root
2257		return;
2258	}
2259
2260	// ToDo:
2261	// check if only item in selection list is the root
2262	// and do not add if true
2263
2264	// add after "Open"
2265	BMenuItem* item = parent->FindItem(kOpenSelection);
2266
2267	// build a list of all refs to open
2268	BMessage message(B_REFS_RECEIVED);
2269	for (int32 index = 0; index < selectCount; index++) {
2270		BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2271		message.AddRef("refs", pose->TargetModel()->EntryRef());
2272	}
2273
2274	// add Tracker token so that refs received recipients can script us
2275	message.AddMessenger("TrackerViewToken", BMessenger(PoseView()));
2276
2277	int32 index = item->Menu()->IndexOf(item);
2278	fOpenWithItem = new BMenuItem(
2279		new OpenWithMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS),
2280			&message, this, be_app), new BMessage(kOpenSelectionWith));
2281	fOpenWithItem->SetTarget(PoseView());
2282	fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY);
2283
2284	item->Menu()->AddItem(fOpenWithItem, index + 1);
2285}
2286
2287
2288void
2289BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu* navMenu, uint32 what,
2290	const entry_ref* ref, bool addLocalOnly)
2291{
2292	BVolume volume;
2293	BVolumeRoster volumeRoster;
2294	BDirectory directory;
2295	BEntry entry;
2296	BPath path;
2297	Model model;
2298	dev_t device = ref->device;
2299
2300	int32 volumeCount = 0;
2301
2302	navMenu->RemoveItems(0, navMenu->CountItems(), true);
2303
2304	// count persistent writable volumes
2305	volumeRoster.Rewind();
2306	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2307		if (!volume.IsReadOnly() && volume.IsPersistent())
2308			volumeCount++;
2309	}
2310
2311	// add the current folder
2312	if (entry.SetTo(ref) == B_OK
2313		&& entry.GetParent(&entry) == B_OK
2314		&& model.SetTo(&entry) == B_OK) {
2315		BNavMenu* menu = new BNavMenu(B_TRANSLATE("Current folder"), what,
2316			this);
2317		menu->SetNavDir(model.EntryRef());
2318		menu->SetShowParent(true);
2319
2320		BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2321		item->SetMessage(new BMessage((uint32)what));
2322
2323		navMenu->AddItem(item);
2324	}
2325
2326	// add the recent folder menu
2327	// the "Tracker" settings directory is only used to get its icon
2328	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
2329		path.Append("Tracker");
2330		if (entry.SetTo(path.Path()) == B_OK
2331			&& model.SetTo(&entry) == B_OK) {
2332			BMenu* menu = new RecentsMenu(B_TRANSLATE("Recent folders"),
2333				kRecentFolders, what, this);
2334
2335			BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2336			item->SetMessage(new BMessage((uint32)what));
2337
2338			navMenu->AddItem(item);
2339		}
2340	}
2341
2342	// add Desktop
2343	FSGetBootDeskDir(&directory);
2344	if (directory.InitCheck() == B_OK && directory.GetEntry(&entry) == B_OK
2345		&& model.SetTo(&entry) == B_OK) {
2346		navMenu->AddNavDir(&model, what, this, true);
2347			// ask NavMenu to populate submenu for us
2348	}
2349
2350	// add the home dir
2351	if (find_directory(B_USER_DIRECTORY, &path) == B_OK
2352		&& entry.SetTo(path.Path()) == B_OK && model.SetTo(&entry) == B_OK) {
2353		navMenu->AddNavDir(&model, what, this, true);
2354	}
2355
2356	navMenu->AddSeparatorItem();
2357
2358	// either add all mounted volumes (for copy), or all the top-level
2359	// directories from the same device (for move)
2360	// ToDo: can be changed if cross-device moves are implemented
2361
2362	if (addLocalOnly || volumeCount < 2) {
2363		// add volume this item lives on
2364		if (volume.SetTo(device) == B_OK
2365			&& volume.GetRootDirectory(&directory) == B_OK
2366			&& directory.GetEntry(&entry) == B_OK
2367			&& model.SetTo(&entry) == B_OK) {
2368			navMenu->AddNavDir(&model, what, this, false);
2369				// do not have submenu populated
2370
2371			navMenu->SetNavDir(model.EntryRef());
2372		}
2373	} else {
2374		// add all persistent writable volumes
2375		volumeRoster.Rewind();
2376		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2377			if (volume.IsReadOnly() || !volume.IsPersistent())
2378				continue;
2379
2380			// add root dir
2381			if (volume.GetRootDirectory(&directory) == B_OK
2382				&& directory.GetEntry(&entry) == B_OK
2383				&& model.SetTo(&entry) == B_OK) {
2384				navMenu->AddNavDir(&model, what, this, true);
2385					// ask NavMenu to populate submenu for us
2386			}
2387		}
2388	}
2389}
2390
2391
2392void
2393BContainerWindow::SetupMoveCopyMenus(const entry_ref* item_ref, BMenu* parent)
2394{
2395	if (IsTrash() || InTrash() || IsPrintersDir() || fMoveToItem == NULL
2396		|| fCopyToItem == NULL || fCreateLinkItem == NULL
2397		|| TargetModel()->IsRoot()) {
2398		return;
2399	}
2400
2401	// re-parent items to this menu since they're shared
2402	BMenuItem* trash = parent->FindItem(kMoveToTrash);
2403	int32 index = trash != NULL ? parent->IndexOf(trash) + 2 : 0;
2404
2405	if (fMoveToItem->Menu() != parent) {
2406		if (fMoveToItem->Menu() != NULL)
2407			fMoveToItem->Menu()->RemoveItem(fMoveToItem);
2408
2409		parent->AddItem(fMoveToItem, index++);
2410	}
2411
2412	if (fCopyToItem->Menu() != parent) {
2413		if (fCopyToItem->Menu() != NULL)
2414			fCopyToItem->Menu()->RemoveItem(fCopyToItem);
2415
2416		parent->AddItem(fCopyToItem, index++);
2417	}
2418
2419	if (fCreateLinkItem->Menu() != parent) {
2420		if (fCreateLinkItem->Menu() != NULL)
2421			fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem);
2422
2423		parent->AddItem(fCreateLinkItem, index);
2424	}
2425
2426	// Set the "Create Link" item label here so it
2427	// appears correctly when menus are disabled, too.
2428	if ((modifiers() & B_SHIFT_KEY) != 0)
2429		fCreateLinkItem->SetLabel(B_TRANSLATE("Create relative link"));
2430	else
2431		fCreateLinkItem->SetLabel(B_TRANSLATE("Create link"));
2432
2433	// only enable once the menus are built
2434	fMoveToItem->SetEnabled(false);
2435	fCopyToItem->SetEnabled(false);
2436	fCreateLinkItem->SetEnabled(false);
2437
2438	// get ref for item which is selected
2439	BEntry entry;
2440	if (entry.SetTo(item_ref) != B_OK)
2441		return;
2442
2443	Model tempModel(&entry);
2444	if (tempModel.InitCheck() != B_OK)
2445		return;
2446
2447	if (tempModel.IsRoot() || tempModel.IsVolume())
2448		return;
2449
2450	// configure "Move to" menu item
2451	PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fMoveToItem->Submenu()),
2452		kMoveSelectionTo, item_ref, true);
2453
2454	// configure "Copy to" menu item
2455	// add all mounted volumes (except the one this item lives on)
2456	PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fCopyToItem->Submenu()),
2457		kCopySelectionTo, item_ref, false);
2458
2459	// Set "Create Link" menu item message and
2460	// add all mounted volumes (except the one this item lives on)
2461	if ((modifiers() & B_SHIFT_KEY) != 0) {
2462		fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink));
2463		PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2464				(fCreateLinkItem->Submenu()),
2465			kCreateRelativeLink, item_ref, false);
2466	} else {
2467		fCreateLinkItem->SetMessage(new BMessage(kCreateLink));
2468		PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2469				(fCreateLinkItem->Submenu()),
2470			kCreateLink, item_ref, false);
2471	}
2472
2473	fMoveToItem->SetEnabled(PoseView()->CountSelected() > 0
2474		&& !PoseView()->SelectedVolumeIsReadOnly());
2475	fCopyToItem->SetEnabled(PoseView()->CountSelected() > 0);
2476	fCreateLinkItem->SetEnabled(PoseView()->CountSelected() > 0);
2477
2478	// Set the "Identify" item label
2479	BMenuItem* identifyItem = parent->FindItem(kIdentifyEntry);
2480	if (identifyItem != NULL) {
2481		if ((modifiers() & B_SHIFT_KEY) != 0) {
2482			identifyItem->SetLabel(B_TRANSLATE("Force identify"));
2483			identifyItem->Message()->ReplaceBool("force", true);
2484		} else {
2485			identifyItem->SetLabel(B_TRANSLATE("Identify"));
2486			identifyItem->Message()->ReplaceBool("force", false);
2487		}
2488	}
2489}
2490
2491
2492uint32
2493BContainerWindow::ShowDropContextMenu(BPoint where, BPoseView* source)
2494{
2495	BPoint global(where);
2496
2497	PoseView()->ConvertToScreen(&global);
2498	PoseView()->CommitActivePose();
2499
2500	// Change the "Create Link" item - allow user to
2501	// create relative links with the Shift key down.
2502	BMenuItem* item = fDropContextMenu->FindItem(kCreateLink);
2503	if (item == NULL)
2504		item = fDropContextMenu->FindItem(kCreateRelativeLink);
2505
2506	if (item != NULL && (modifiers() & B_SHIFT_KEY) != 0) {
2507		item->SetLabel(B_TRANSLATE("Create relative link here"));
2508		item->SetMessage(new BMessage(kCreateRelativeLink));
2509	} else if (item != NULL) {
2510		item->SetLabel(B_TRANSLATE("Create link here"));
2511		item->SetMessage(new BMessage(kCreateLink));
2512	}
2513
2514	int32 itemCount = fDropContextMenu->CountItems();
2515	for(int32 i = 0; i < itemCount - 2; i++) {
2516		// separator item and Cancel item are skipped
2517		item = fDropContextMenu->ItemAt(i);
2518		if (item == NULL)
2519			break;
2520
2521		if (item->Command() == kMoveSelectionTo && source != NULL) {
2522			item->SetEnabled(!source->SelectedVolumeIsReadOnly()
2523				&& !PoseView()->TargetVolumeIsReadOnly());
2524		} else
2525			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2526	}
2527
2528	item = fDropContextMenu->Go(global, true, true);
2529	if (item != NULL)
2530		return item->Command();
2531
2532	return 0;
2533}
2534
2535
2536void
2537BContainerWindow::ShowContextMenu(BPoint where, const entry_ref* ref)
2538{
2539	ASSERT(IsLocked());
2540	BPoint global(where);
2541	PoseView()->ConvertToScreen(&global);
2542	PoseView()->CommitActivePose();
2543
2544	if (ref != NULL) {
2545		// clicked on a pose, show file or volume context menu
2546		Model model(ref);
2547
2548		if (model.IsTrash()) {
2549			if (fTrashContextMenu->Window() || Dragging())
2550				return;
2551
2552			DeleteSubmenu(fNavigationItem);
2553
2554			// selected item was trash, show the trash context menu instead
2555
2556			EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash,
2557				static_cast<TTracker*>(be_app)->TrashFull());
2558
2559			SetupNavigationMenu(ref, fTrashContextMenu);
2560
2561			fContextMenu = fTrashContextMenu;
2562		} else {
2563			bool showAsVolume = false;
2564			bool isFilePanel = PoseView()->IsFilePanel();
2565
2566			if (Dragging()) {
2567				fContextMenu = NULL;
2568
2569				BEntry entry;
2570				model.GetEntry(&entry);
2571
2572				// only show for directories (directory, volume, root)
2573				//
2574				// don't show a popup for the trash or printers
2575				// trash is handled in DeskWindow
2576				//
2577				// since this menu is opened asynchronously
2578				// we need to make sure we don't open it more
2579				// than once, the IsShowing flag is set in
2580				// SlowContextPopup::AttachedToWindow and
2581				// reset in DetachedFromWindow
2582				// see the notes in SlowContextPopup::AttachedToWindow
2583
2584				if (!FSIsPrintersDir(&entry)
2585					&& !fDragContextMenu->IsShowing()) {
2586					//printf("ShowContextMenu - target is %s %i\n",
2587					//	ref->name, IsShowing(ref));
2588					fDragContextMenu->ClearMenu();
2589
2590					// in case the ref is a symlink, resolve it
2591					// only pop open for directories
2592					BEntry resolvedEntry(ref, true);
2593					if (!resolvedEntry.IsDirectory())
2594						return;
2595
2596					entry_ref resolvedRef;
2597					resolvedEntry.GetRef(&resolvedRef);
2598
2599					// use the resolved ref for the menu
2600					fDragContextMenu->SetNavDir(&resolvedRef);
2601					fDragContextMenu->SetTypesList(fCachedTypesList);
2602					fDragContextMenu->SetTarget(BMessenger(this));
2603					BPoseView* poseView = PoseView();
2604					if (poseView != NULL) {
2605						BMessenger target(poseView);
2606						fDragContextMenu->InitTrackingHook(
2607							&BPoseView::MenuTrackingHook, &target,
2608							fDragMessage);
2609					}
2610
2611					// this is now asynchronous so that we don't
2612					// deadlock in Window::Quit,
2613					fDragContextMenu->Go(global);
2614				}
2615
2616				return;
2617			} else if (TargetModel()->IsRoot() || model.IsVolume()) {
2618				fContextMenu = fVolumeContextMenu;
2619				showAsVolume = true;
2620			} else
2621				fContextMenu = fFileContextMenu;
2622
2623			if (fContextMenu == NULL)
2624				return;
2625
2626			// clean up items from last context menu
2627			MenusEnded();
2628
2629			if (fContextMenu == fFileContextMenu) {
2630				// Add all mounted volumes (except the one this item lives on.)
2631				BNavMenu* navMenu = dynamic_cast<BNavMenu*>(
2632					fCreateLinkItem->Submenu());
2633				PopulateMoveCopyNavMenu(navMenu,
2634				fCreateLinkItem->Message()->what, ref, false);
2635			} else if (showAsVolume) {
2636				// non-volume enable/disable copy, move, identify
2637				EnableNamedMenuItem(fContextMenu, kDuplicateSelection, false);
2638				EnableNamedMenuItem(fContextMenu, kMoveToTrash, false);
2639				EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false);
2640
2641				// volume model, enable/disable the Unmount item
2642				bool ejectableVolumeSelected = false;
2643
2644				BVolume boot;
2645				BVolumeRoster().GetBootVolume(&boot);
2646				BVolume volume;
2647				volume.SetTo(model.NodeRef()->device);
2648				if (volume != boot)
2649					ejectableVolumeSelected = true;
2650
2651				EnableNamedMenuItem(fContextMenu,
2652					B_TRANSLATE("Unmount"), ejectableVolumeSelected);
2653			}
2654
2655			SetupNavigationMenu(ref, fContextMenu);
2656			if (!showAsVolume && !isFilePanel) {
2657				SetupMoveCopyMenus(ref, fContextMenu);
2658				SetupOpenWithMenu(fContextMenu);
2659			}
2660
2661			UpdateMenu(fContextMenu, kPosePopUpContext);
2662		}
2663	} else if (fWindowContextMenu != NULL) {
2664		// Repopulate desktop menu if IsDesktop
2665		if (fIsDesktop)
2666			RepopulateMenus();
2667
2668		MenusEnded();
2669
2670		// clicked on a window, show window context menu
2671
2672		SetupNavigationMenu(ref, fWindowContextMenu);
2673		UpdateMenu(fWindowContextMenu, kWindowPopUpContext);
2674
2675		fContextMenu = fWindowContextMenu;
2676	}
2677
2678	// context menu invalid or popup window is already open
2679	if (fContextMenu == NULL || fContextMenu->Window() != NULL)
2680		return;
2681
2682	fContextMenu->Go(global, true, true, true);
2683	fContextMenu = NULL;
2684}
2685
2686
2687void
2688BContainerWindow::AddFileContextMenus(BMenu* menu)
2689{
2690	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2691		new BMessage(kOpenSelection), 'O'));
2692	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2693		new BMessage(kGetInfo), 'I'));
2694	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2695		new BMessage(kEditItem), 'E'));
2696
2697	if (!IsTrash() && !InTrash() && !IsPrintersDir()) {
2698		menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"),
2699			new BMessage(kDuplicateSelection), 'D'));
2700	}
2701
2702	if (!IsTrash() && !InTrash()) {
2703		menu->AddItem(new BMenuItem(B_TRANSLATE("Move to Trash"),
2704			new BMessage(kMoveToTrash), 'T'));
2705		if (!IsPrintersDir()) {
2706			// add separator for copy to/move to items (navigation items)
2707			menu->AddSeparatorItem();
2708		}
2709	} else {
2710		menu->AddItem(new BMenuItem(B_TRANSLATE("Delete"),
2711			new BMessage(kDelete), 0));
2712		menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"),
2713			new BMessage(kRestoreFromTrash), 0));
2714	}
2715
2716#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2717	menu->AddSeparatorItem();
2718	BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
2719		new BMessage(B_CUT), 'X');
2720	menu->AddItem(cutItem);
2721	BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
2722		new BMessage(B_COPY), 'C');
2723	menu->AddItem(copyItem);
2724	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
2725		new BMessage(B_PASTE), 'V');
2726	menu->AddItem(pasteItem);
2727#endif
2728	menu->AddSeparatorItem();
2729
2730	BMessage* message = new BMessage(kIdentifyEntry);
2731	message->AddBool("force", false);
2732	menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), message));
2733
2734	BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2735	addOnMenuItem->SetFont(be_plain_font);
2736	menu->AddItem(addOnMenuItem);
2737
2738	// set targets as needed
2739	menu->SetTargetForItems(PoseView());
2740#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2741	cutItem->SetTarget(this);
2742	copyItem->SetTarget(this);
2743	pasteItem->SetTarget(this);
2744#endif
2745}
2746
2747
2748void
2749BContainerWindow::AddVolumeContextMenus(BMenu* menu)
2750{
2751	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2752		new BMessage(kOpenSelection), 'O'));
2753	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2754		new BMessage(kGetInfo), 'I'));
2755	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2756		new BMessage(kEditItem), 'E'));
2757
2758	menu->AddSeparatorItem();
2759	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
2760
2761	BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
2762		new BMessage(kUnmountVolume), 'U');
2763	item->SetEnabled(false);
2764	menu->AddItem(item);
2765	menu->AddSeparatorItem();
2766
2767#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2768	menu->AddItem(new BMenuItem(B_TRANSLATE("Paste"),
2769		new BMessage(B_PASTE), 'V'));
2770	menu->AddSeparatorItem();
2771#endif
2772
2773	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
2774
2775	menu->SetTargetForItems(PoseView());
2776}
2777
2778
2779void
2780BContainerWindow::AddWindowContextMenus(BMenu* menu)
2781{
2782	// create context sensitive menu for empty area of window
2783	// since we check view mode before display, this should be a radio
2784	// mode menu
2785
2786	Model* targetModel = TargetModel();
2787	ASSERT(targetModel != NULL);
2788
2789	bool needSeparator = true;
2790	if (IsTrash()) {
2791		menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2792			new BMessage(kEmptyTrash)));
2793	} else if (IsPrintersDir()) {
2794		menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
2795			new BMessage(kAddPrinter), 'N'));
2796	} else if (InTrash() || targetModel->IsRoot()) {
2797		needSeparator = false;
2798	} else {
2799		if (!PoseView()->IsFilePanel()) {
2800			TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(),
2801				B_TRANSLATE("New"));
2802			menu->AddItem(templatesMenu);
2803			templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2804			templatesMenu->SetTargetForItems(PoseView());
2805			templatesMenu->SetFont(be_plain_font);
2806		} else {
2807			BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
2808				new BMessage(kNewFolder), 'N');
2809			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2810			menu->AddItem(item);
2811		}
2812	}
2813
2814	if (needSeparator)
2815		menu->AddSeparatorItem();
2816
2817#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2818	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
2819		new BMessage(B_PASTE), 'V');
2820	pasteItem->SetEnabled(FSClipboardHasRefs()
2821		&& !PoseView()->TargetVolumeIsReadOnly());
2822	menu->AddItem(pasteItem);
2823	menu->AddSeparatorItem();
2824#endif
2825
2826	BMenu* arrangeBy = new BMenu(B_TRANSLATE("Arrange by"));
2827	PopulateArrangeByMenu(arrangeBy);
2828	menu->AddItem(arrangeBy);
2829
2830	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
2831		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
2832	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
2833		new BMessage(B_SELECT_ALL), 'A'));
2834	if (!IsTrash()) {
2835		menu->AddItem(new BMenuItem(B_TRANSLATE("Open parent"),
2836			new BMessage(kOpenParentDir), B_UP_ARROW));
2837	}
2838
2839	if (targetModel->IsRoot()) {
2840		menu->AddSeparatorItem();
2841		menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
2842	}
2843
2844	menu->AddSeparatorItem();
2845	BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2846	addOnMenuItem->SetFont(be_plain_font);
2847	menu->AddItem(addOnMenuItem);
2848
2849#if DEBUG
2850	menu->AddSeparatorItem();
2851	BMenuItem* testing = new BMenuItem("Test icon cache",
2852		new BMessage(kTestIconCache));
2853	menu->AddItem(testing);
2854#endif
2855
2856	// target items as needed
2857	menu->SetTargetForItems(PoseView());
2858#ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2859	pasteItem->SetTarget(this);
2860#endif
2861}
2862
2863
2864void
2865BContainerWindow::AddDropContextMenus(BMenu* menu)
2866{
2867	menu->AddItem(new BMenuItem(B_TRANSLATE("Move here"),
2868		new BMessage(kMoveSelectionTo)));
2869	menu->AddItem(new BMenuItem(B_TRANSLATE("Copy here"),
2870		new BMessage(kCopySelectionTo)));
2871	menu->AddItem(new BMenuItem(B_TRANSLATE("Create link here"),
2872		new BMessage(kCreateLink)));
2873	menu->AddSeparatorItem();
2874	menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"),
2875		new BMessage(kCancelButton)));
2876}
2877
2878
2879void
2880BContainerWindow::AddTrashContextMenus(BMenu* menu)
2881{
2882	// setup special trash context menu
2883	menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2884		new BMessage(kEmptyTrash)));
2885	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2886		new BMessage(kOpenSelection), 'O'));
2887	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2888		new BMessage(kGetInfo), 'I'));
2889	menu->SetTargetForItems(PoseView());
2890}
2891
2892
2893void
2894BContainerWindow::EachAddOn(bool (*eachAddOn)(const Model*, const char*,
2895		uint32 shortcut, uint32 modifiers, bool primary, void* context,
2896		BContainerWindow* window, BMenu* menu),
2897	void* passThru, BStringList& mimeTypes, BMenu* menu)
2898{
2899	AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
2900	if (lock.IsLocked()) {
2901		for (int i = fAddOnsList->CountItems() - 1; i >= 0; i--) {
2902			struct AddOnShortcut* item = fAddOnsList->ItemAt(i);
2903			bool primary = false;
2904
2905			if (mimeTypes.CountStrings() > 0) {
2906				BFile file(item->model->EntryRef(), B_READ_ONLY);
2907				if (file.InitCheck() == B_OK) {
2908					BAppFileInfo info(&file);
2909					if (info.InitCheck() == B_OK) {
2910						bool secondary = true;
2911
2912						// does this add-on has types set at all?
2913						BMessage message;
2914						if (info.GetSupportedTypes(&message) == B_OK) {
2915							type_code typeCode;
2916							int32 count;
2917							if (message.GetInfo("types", &typeCode,
2918									&count) == B_OK) {
2919								secondary = false;
2920							}
2921						}
2922
2923						// check all supported types if it has some set
2924						if (!secondary) {
2925							for (int32 i = mimeTypes.CountStrings();
2926									!primary && i-- > 0;) {
2927								BString type = mimeTypes.StringAt(i);
2928								if (info.IsSupportedType(type.String())) {
2929									BMimeType mimeType(type.String());
2930									if (info.Supports(&mimeType))
2931										primary = true;
2932									else
2933										secondary = true;
2934								}
2935							}
2936						}
2937
2938						if (!secondary && !primary)
2939							continue;
2940					}
2941				}
2942			}
2943			((eachAddOn)(item->model, item->model->Name(), item->key,
2944				item->modifiers, primary, passThru, this, menu));
2945		}
2946	}
2947}
2948
2949
2950void
2951BContainerWindow::BuildMimeTypeList(BStringList& mimeTypes)
2952{
2953	int32 selectCount = PoseView()->CountSelected();
2954	if (selectCount <= 0) {
2955		// just add the type of the current directory
2956		AddMimeTypeString(mimeTypes, TargetModel());
2957	} else {
2958		_UpdateSelectionMIMEInfo();
2959		for (int32 index = 0; index < selectCount; index++) {
2960			BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2961			AddMimeTypeString(mimeTypes, pose->TargetModel());
2962			// If it's a symlink, resolves it and add the Target's MimeType
2963			if (pose->TargetModel()->IsSymLink()) {
2964				Model* resolved = new Model(
2965					pose->TargetModel()->EntryRef(), true, true);
2966				if (resolved->InitCheck() == B_OK)
2967					AddMimeTypeString(mimeTypes, resolved);
2968
2969				delete resolved;
2970			}
2971		}
2972	}
2973}
2974
2975
2976void
2977BContainerWindow::BuildAddOnsMenu(BMenu* parentMenu)
2978{
2979	BMenuItem* item = parentMenu->FindItem(B_TRANSLATE("Add-ons"));
2980	if (parentMenu->IndexOf(item) == 0) {
2981		// the folder of the context menu seems to be named "Add-Ons"
2982		// so we just take the last menu item, which is correct if not
2983		// build with debug option
2984		item = parentMenu->ItemAt(parentMenu->CountItems() - 1);
2985	}
2986	if (item == NULL)
2987		return;
2988
2989	BFont font; {
2990		AutoLock<BLooper> _(parentMenu->Looper());
2991		parentMenu->GetFont(&font);
2992	}
2993
2994	BMenu* menu = item->Submenu();
2995	if (menu == NULL)
2996		return;
2997
2998	menu->SetFont(&font);
2999
3000	// found add-ons menu, empty it first
3001	for (;;) {
3002		item = menu->RemoveItem((int32)0);
3003		if (!item)
3004			break;
3005		delete item;
3006	}
3007
3008	BObjectList<BMenuItem> primaryList;
3009	BObjectList<BMenuItem> secondaryList;
3010	BStringList mimeTypes(10);
3011	BuildMimeTypeList(mimeTypes);
3012
3013	AddOneAddOnParams params;
3014	params.primaryList = &primaryList;
3015	params.secondaryList = &secondaryList;
3016
3017	// build a list of the MIME types of the selected items
3018
3019	EachAddOn(AddOneAddOn, &params, mimeTypes, parentMenu);
3020
3021	primaryList.SortItems(CompareLabels);
3022	secondaryList.SortItems(CompareLabels);
3023
3024	int32 count = primaryList.CountItems();
3025	for (int32 index = 0; index < count; index++)
3026		menu->AddItem(primaryList.ItemAt(index));
3027
3028	if (count > 0)
3029		menu->AddSeparatorItem();
3030
3031	count = secondaryList.CountItems();
3032	for (int32 index = 0; index < count; index++)
3033		menu->AddItem(secondaryList.ItemAt(index));
3034
3035	menu->SetTargetForItems(this);
3036}
3037
3038
3039void
3040BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context)
3041{
3042	const int32 selectCount = PoseView()->CountSelected();
3043	const int32 poseCount = PoseView()->CountItems();
3044
3045	if (context == kMenuBarContext) {
3046		EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0);
3047		EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0);
3048		EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0);
3049		EnableNamedMenuItem(menu, kDelete,
3050			PoseView()->CanMoveToTrashOrDuplicate());
3051	}
3052
3053	if (context == kMenuBarContext || context == kPosePopUpContext) {
3054		SetupEditQueryItem(menu);
3055
3056		EnableNamedMenuItem(menu, kEditItem, PoseView()->CanEditName());
3057		EnableNamedMenuItem(menu, kMoveToTrash,
3058			PoseView()->CanMoveToTrashOrDuplicate());
3059		EnableNamedMenuItem(menu, kDuplicateSelection,
3060			PoseView()->CanMoveToTrashOrDuplicate());
3061
3062		SetCutItem(menu);
3063		SetCopyItem(menu);
3064		SetPasteItem(menu);
3065	}
3066
3067	if (context == kMenuBarContext || context == kWindowPopUpContext) {
3068		uint32 viewMode = PoseView()->ViewMode();
3069
3070		BMenu* iconSizeMenu = NULL;
3071		if (BMenuItem* item = menu->FindItem(kIconMode))
3072			iconSizeMenu = item->Submenu();
3073
3074		if (iconSizeMenu != NULL) {
3075			if (viewMode == kIconMode) {
3076				int32 iconSize = PoseView()->UnscaledIconSizeInt();
3077				BMenuItem* item = iconSizeMenu->ItemAt(0);
3078				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL;
3079						i++) {
3080					BMessage* message = item->Message();
3081					if (message == NULL) {
3082						item->SetMarked(false);
3083						continue;
3084					}
3085					int32 size;
3086					if (message->FindInt32("size", &size) != B_OK)
3087						size = -1;
3088					item->SetMarked(iconSize == size);
3089				}
3090			} else {
3091				BMenuItem* item;
3092				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++)
3093					item->SetMarked(false);
3094			}
3095		}
3096
3097		MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode);
3098		MarkNamedMenuItem(menu, kListMode, viewMode == kListMode);
3099		MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode);
3100
3101		SetCloseItem(menu);
3102		SetArrangeMenu(menu);
3103		SetPasteItem(menu);
3104
3105		BEntry entry(TargetModel()->EntryRef());
3106		BDirectory parent;
3107		bool parentIsRoot = (entry.GetParent(&parent) == B_OK
3108			&& parent.GetEntry(&entry) == B_OK
3109			&& FSIsRootDir(&entry));
3110
3111		EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop()
3112			&& !TargetModel()->IsRoot()
3113			&& (!parentIsRoot
3114				|| TrackerSettings().SingleWindowBrowse()
3115				|| TrackerSettings().ShowDisksIcon()
3116				|| (modifiers() & B_CONTROL_KEY) != 0));
3117
3118		EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0);
3119		EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0);
3120
3121		BMenuItem* item = menu->FindItem(B_TRANSLATE("New"));
3122		if (item != NULL) {
3123			TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>(
3124				item->Submenu());
3125			if (templatesMenu != NULL)
3126				templatesMenu->UpdateMenuState();
3127		}
3128	}
3129
3130	BuildAddOnsMenu(menu);
3131}
3132
3133
3134BMessage*
3135BContainerWindow::AddOnMessage(int32 what)
3136{
3137	BMessage* message = new BMessage(what);
3138
3139	// add selected refs to message
3140	BObjectList<BPose>* selectionList = PoseView()->SelectionList();
3141
3142	int32 index = 0;
3143	BPose* pose;
3144	while ((pose = selectionList->ItemAt(index++)) != NULL)
3145		message->AddRef("refs", pose->TargetModel()->EntryRef());
3146
3147	message->AddRef("dir_ref", TargetModel()->EntryRef());
3148	message->AddMessenger("TrackerViewToken", BMessenger(PoseView()));
3149
3150	return message;
3151}
3152
3153
3154void
3155BContainerWindow::LoadAddOn(BMessage* message)
3156{
3157	UpdateIfNeeded();
3158
3159	entry_ref addOnRef;
3160	status_t result = message->FindRef("refs", &addOnRef);
3161	if (result != B_OK) {
3162		BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
3163		buffer.ReplaceFirst("%error", strerror(result));
3164		buffer.ReplaceFirst("%name", addOnRef.name);
3165
3166		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
3167			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3168		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3169		alert->Go();
3170		return;
3171	}
3172
3173	// add selected refs to message
3174	BMessage* refs = AddOnMessage(B_REFS_RECEIVED);
3175
3176	LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs,
3177		addOnRef, *TargetModel()->EntryRef());
3178}
3179
3180
3181//	#pragma mark - BContainerWindow private methods
3182
3183
3184void
3185BContainerWindow::_UpdateSelectionMIMEInfo()
3186{
3187	BPose* pose;
3188	int32 index = 0;
3189	while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) {
3190		BString mimeType(pose->TargetModel()->MimeType());
3191		if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3192			pose->TargetModel()->Mimeset(true);
3193			if (pose->TargetModel()->IsSymLink()) {
3194				Model* resolved = new Model(pose->TargetModel()->EntryRef(),
3195					true, true);
3196				if (resolved->InitCheck() == B_OK) {
3197					mimeType.SetTo(resolved->MimeType());
3198					if (!mimeType.Length()
3199						|| mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3200						resolved->Mimeset(true);
3201					}
3202				}
3203				delete resolved;
3204			}
3205		}
3206	}
3207}
3208
3209
3210void
3211BContainerWindow::_AddFolderIcon()
3212{
3213	if (fMenuBar == NULL) {
3214		// We don't want to add the icon if there's no menubar
3215		return;
3216	}
3217
3218	float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1,
3219		iconSize = fMenuBar->Bounds().Height() - 2;
3220	if (iconSize < baseIconSize)
3221		iconSize = baseIconSize;
3222
3223	fDraggableIcon = new(std::nothrow)
3224		DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1));
3225	if (fDraggableIcon != NULL) {
3226		fMenuContainer->GroupLayout()->AddView(fDraggableIcon);
3227		fMenuBar->SetBorders(
3228			BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER);
3229	}
3230}
3231
3232
3233void
3234BContainerWindow::_PassMessageToAddOn(BMessage* message)
3235{
3236	LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY,
3237		&RunAddOnMessageThread, new BMessage(*message), (void*)NULL);
3238}
3239
3240
3241BMenuItem*
3242BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3243	int32 type, float width, int32 align, bool editable, bool statField)
3244{
3245	return NewAttributeMenuItem(label, name, type, NULL, width, align,
3246		editable, statField);
3247}
3248
3249
3250BMenuItem*
3251BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3252	int32 type, const char* displayAs, float width, int32 align,
3253	bool editable, bool statField)
3254{
3255	BMessage* message = new BMessage(kAttributeItem);
3256	message->AddString("attr_name", name);
3257	message->AddInt32("attr_type", type);
3258	message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type));
3259	message->AddFloat("attr_width", width);
3260	message->AddInt32("attr_align", align);
3261	if (displayAs != NULL)
3262		message->AddString("attr_display_as", displayAs);
3263	message->AddBool("attr_editable", editable);
3264	message->AddBool("attr_statfield", statField);
3265
3266	BMenuItem* menuItem = new BMenuItem(label, message);
3267	menuItem->SetTarget(PoseView());
3268
3269	return menuItem;
3270}
3271
3272
3273void
3274BContainerWindow::NewAttributesMenu(BMenu* menu)
3275{
3276	ASSERT(PoseView());
3277
3278	BMenuItem* item;
3279	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"),
3280		new BMessage(kCopyAttributes)));
3281	item->SetTarget(PoseView());
3282	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"),
3283		new BMessage(kPasteAttributes)));
3284	item->SetTarget(PoseView());
3285	menu->AddSeparatorItem();
3286
3287	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"),
3288		kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3289
3290	if (gLocalizedNamePreferred) {
3291		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"),
3292			kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3293	}
3294
3295	menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize,
3296		B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true));
3297
3298	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"),
3299		kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3300
3301	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"),
3302		kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3303
3304	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"),
3305		kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false));
3306
3307	if (IsTrash() || InTrash()) {
3308		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"),
3309			kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false,
3310			false));
3311	} else {
3312		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath,
3313			B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false));
3314	}
3315
3316#ifdef OWNER_GROUP_ATTRIBUTES
3317	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner,
3318		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3319
3320	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup,
3321		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3322#endif
3323
3324	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"),
3325		kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true));
3326}
3327
3328
3329void
3330BContainerWindow::ShowAttributesMenu()
3331{
3332	ASSERT(fAttrMenu);
3333	fMenuBar->AddItem(fAttrMenu);
3334}
3335
3336
3337void
3338BContainerWindow::HideAttributesMenu()
3339{
3340	ASSERT(fAttrMenu);
3341	fMenuBar->RemoveItem(fAttrMenu);
3342}
3343
3344
3345void
3346BContainerWindow::MarkAttributesMenu()
3347{
3348	MarkAttributesMenu(fAttrMenu);
3349}
3350
3351
3352void
3353BContainerWindow::MarkAttributesMenu(BMenu* menu)
3354{
3355	if (menu == NULL)
3356		return;
3357
3358	int32 count = menu->CountItems();
3359	for (int32 index = 0; index < count; index++) {
3360		BMenuItem* item = menu->ItemAt(index);
3361		int32 attrHash;
3362		if (item->Message() != NULL) {
3363			if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK)
3364				item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0);
3365			else
3366				item->SetMarked(false);
3367		}
3368
3369		BMenu* submenu = item->Submenu();
3370		if (submenu != NULL) {
3371			int32 count2 = submenu->CountItems();
3372			for (int32 subindex = 0; subindex < count2; subindex++) {
3373				item = submenu->ItemAt(subindex);
3374				if (item->Message() != NULL) {
3375					if (item->Message()->FindInt32("attr_hash", &attrHash)
3376						== B_OK) {
3377						item->SetMarked(PoseView()->ColumnFor((uint32)attrHash)
3378							!= 0);
3379					} else
3380						item->SetMarked(false);
3381				}
3382			}
3383		}
3384	}
3385}
3386
3387
3388void
3389BContainerWindow::MarkArrangeByMenu(BMenu* menu)
3390{
3391	if (menu == NULL)
3392		return;
3393
3394	int32 count = menu->CountItems();
3395	for (int32 index = 0; index < count; index++) {
3396		BMenuItem* item = menu->ItemAt(index);
3397		if (item->Message() != NULL) {
3398			uint32 attrHash;
3399			if (item->Message()->FindInt32("attr_hash",
3400					(int32*)&attrHash) == B_OK) {
3401				item->SetMarked(PoseView()->PrimarySort() == attrHash);
3402			} else if (item->Command() == kArrangeReverseOrder)
3403				item->SetMarked(PoseView()->ReverseSort());
3404		}
3405	}
3406}
3407
3408
3409void
3410BContainerWindow::AddMimeTypesToMenu()
3411{
3412	AddMimeTypesToMenu(fAttrMenu);
3413}
3414
3415
3416// Adds a menu for a specific MIME type if it doesn't exist already.
3417// Returns the menu, if it existed or not.
3418BMenu*
3419BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType,
3420	BMenu* menu, int32 start)
3421{
3422	AutoLock<BLooper> _(menu->Looper());
3423
3424	if (!mimeType.IsValid())
3425		return NULL;
3426
3427	// Check if we already have an entry for this MIME type in the menu.
3428	for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) {
3429		BMessage* message = item->Message();
3430		if (message == NULL)
3431			continue;
3432
3433		const char* type;
3434		if (message->FindString("mimetype", &type) == B_OK
3435			&& !strcmp(mimeType.Type(), type)) {
3436			return item->Submenu();
3437		}
3438	}
3439
3440	BMessage attrInfo;
3441	char description[B_MIME_TYPE_LENGTH];
3442	const char* label = mimeType.Type();
3443
3444	if (!mimeType.IsInstalled())
3445		return NULL;
3446
3447	// only add things to menu which have "user-visible" data
3448	if (mimeType.GetAttrInfo(&attrInfo) != B_OK)
3449		return NULL;
3450
3451	if (mimeType.GetShortDescription(description) == B_OK && description[0])
3452		label = description;
3453
3454	// go through each field in meta mime and add it to a menu
3455	BMenu* mimeMenu = NULL;
3456	if (isSuperType) {
3457		// If it is a supertype, we create the menu anyway as it may have
3458		// submenus later on.
3459		mimeMenu = new BMenu(label);
3460		BFont font;
3461		menu->GetFont(&font);
3462		mimeMenu->SetFont(&font);
3463	}
3464
3465	int32 index = -1;
3466	const char* publicName;
3467	while (attrInfo.FindString("attr:public_name", ++index, &publicName)
3468			== B_OK) {
3469		if (!attrInfo.FindBool("attr:viewable", index)) {
3470			// don't add if attribute not viewable
3471			continue;
3472		}
3473
3474		int32 type;
3475		int32 align;
3476		int32 width;
3477		bool editable;
3478		const char* attrName;
3479		if (attrInfo.FindString("attr:name", index, &attrName) != B_OK
3480			|| attrInfo.FindInt32("attr:type", index, &type) != B_OK
3481			|| attrInfo.FindBool("attr:editable", index, &editable) != B_OK
3482			|| attrInfo.FindInt32("attr:width", index, &width) != B_OK
3483			|| attrInfo.FindInt32("attr:alignment", index, &align) != B_OK)
3484			continue;
3485
3486		BString displayAs;
3487		attrInfo.FindString("attr:display_as", index, &displayAs);
3488
3489		if (mimeMenu == NULL) {
3490			// do a lazy allocation of the menu
3491			mimeMenu = new BMenu(label);
3492			BFont font;
3493			menu->GetFont(&font);
3494			mimeMenu->SetFont(&font);
3495		}
3496		mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type,
3497			displayAs.String(), width, align, editable, false));
3498	}
3499
3500	if (mimeMenu == NULL)
3501		return NULL;
3502
3503	BMessage* message = new BMessage(kMIMETypeItem);
3504	message->AddString("mimetype", mimeType.Type());
3505	menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type()));
3506
3507	return mimeMenu;
3508}
3509
3510
3511void
3512BContainerWindow::AddMimeTypesToMenu(BMenu* menu)
3513{
3514	if (menu == NULL)
3515		return;
3516
3517	// Remove old mime type menus
3518	int32 start = menu->CountItems();
3519	while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) {
3520		delete menu->RemoveItem(start - 1);
3521		start--;
3522	}
3523
3524	// Add a separator item if there is none yet
3525	if (start > 0
3526		&& dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL)
3527		menu->AddSeparatorItem();
3528
3529	// Add MIME type in case we're a default query type window
3530	BPath path;
3531	if (TargetModel() != NULL) {
3532		TargetModel()->GetPath(&path);
3533		if (path.InitCheck() == B_OK
3534			&& strstr(path.Path(), "/" kQueryTemplates "/") != NULL) {
3535			// demangle MIME type name
3536			BString name(TargetModel()->Name());
3537			name.ReplaceFirst('_', '/');
3538
3539			PoseView()->AddMimeType(name.String());
3540		}
3541	}
3542
3543	// Add MIME type menus
3544
3545	int32 typeCount = PoseView()->CountMimeTypes();
3546
3547	for (int32 index = 0; index < typeCount; index++) {
3548		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3549		if (mimeType.InitCheck() == B_OK) {
3550			BMimeType superType;
3551			mimeType.GetSupertype(&superType);
3552			if (superType.InitCheck() == B_OK) {
3553				BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3554				if (superMenu != NULL) {
3555					// We have a supertype menu.
3556					AddMimeMenu(mimeType, false, superMenu, 0);
3557				}
3558			}
3559		}
3560	}
3561
3562	// remove empty super menus, promote sub-types if needed
3563
3564	for (int32 index = 0; index < typeCount; index++) {
3565		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3566		BMimeType superType;
3567		mimeType.GetSupertype(&superType);
3568
3569		BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3570		if (superMenu == NULL)
3571			continue;
3572
3573		int32 itemsFound = 0;
3574		int32 menusFound = 0;
3575		for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) {
3576			if (item->Submenu() != NULL)
3577				menusFound++;
3578			else
3579				itemsFound++;
3580		}
3581
3582		if (itemsFound == 0) {
3583			if (menusFound != 0) {
3584				// promote types to the top level
3585				while (BMenuItem* item = superMenu->RemoveItem((int32)0)) {
3586					menu->AddItem(item);
3587				}
3588			}
3589
3590			menu->RemoveItem(superMenu->Superitem());
3591			delete superMenu->Superitem();
3592		}
3593	}
3594
3595	// remove separator if it's the only item in menu
3596	BMenuItem* item = menu->ItemAt(menu->CountItems() - 1);
3597	if (dynamic_cast<BSeparatorItem*>(item) != NULL) {
3598		menu->RemoveItem(item);
3599		delete item;
3600	}
3601
3602	MarkAttributesMenu(menu);
3603}
3604
3605
3606BHandler*
3607BContainerWindow::ResolveSpecifier(BMessage* message, int32 index,
3608	BMessage* specifier, int32 form, const char* property)
3609{
3610	if (strcmp(property, "Poses") == 0) {
3611//		PRINT(("BContainerWindow::ResolveSpecifier %s\n", property));
3612		message->PopSpecifier();
3613		return PoseView();
3614	}
3615
3616	return _inherited::ResolveSpecifier(message, index, specifier,
3617		form, property);
3618}
3619
3620
3621PiggybackTaskLoop*
3622BContainerWindow::DelayedTaskLoop()
3623{
3624	if (!fTaskLoop)
3625		fTaskLoop = new PiggybackTaskLoop;
3626
3627	return fTaskLoop;
3628}
3629
3630
3631bool
3632BContainerWindow::NeedsDefaultStateSetup()
3633{
3634	if (TargetModel() == NULL)
3635		return false;
3636
3637	if (TargetModel()->IsRoot()) {
3638		// don't try to set up anything if we are root
3639		return false;
3640	}
3641
3642	WindowStateNodeOpener opener(this, false);
3643	if (opener.StreamNode() == NULL) {
3644		// can't read state, give up
3645		return false;
3646	}
3647
3648	return !NodeHasSavedState(opener.Node());
3649}
3650
3651
3652bool
3653BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result,
3654	bool createNew, bool createFolder)
3655{
3656	//PRINT(("looking for default state in tracker settings dir\n"));
3657	BPath settingsPath;
3658	if (FSFindTrackerSettingsDir(&settingsPath) != B_OK)
3659		return false;
3660
3661	BDirectory dir(settingsPath.Path());
3662
3663	BPath path(settingsPath);
3664	path.Append(name);
3665	if (!BEntry(path.Path()).Exists()) {
3666		if (!createNew)
3667			return false;
3668
3669		BPath tmpPath(settingsPath);
3670		for (;;) {
3671			// deal with several levels of folders
3672			const char* nextSlash = strchr(name, '/');
3673			if (!nextSlash)
3674				break;
3675
3676			BString tmp;
3677			tmp.SetTo(name, nextSlash - name);
3678			tmpPath.Append(tmp.String());
3679
3680			mkdir(tmpPath.Path(), 0777);
3681
3682			name = nextSlash + 1;
3683			if (!name[0]) {
3684				// can't deal with a slash at end
3685				return false;
3686			}
3687		}
3688
3689		if (createFolder) {
3690			if (mkdir(path.Path(), 0777) < 0)
3691				return false;
3692		} else {
3693			BFile file;
3694			if (dir.CreateFile(name, &file) != B_OK)
3695				return false;
3696		}
3697	}
3698
3699	//PRINT(("using default state from %s\n", path.Path()));
3700	result->SetTo(path.Path());
3701	return result->InitCheck() == B_OK;
3702}
3703
3704
3705void
3706BContainerWindow::SetupDefaultState()
3707{
3708	BNode defaultingNode;
3709		// this is where we'll ulitimately get the state from
3710	bool gotDefaultingNode = 0;
3711	bool shouldStagger = false;
3712
3713	ASSERT(TargetModel() != NULL);
3714
3715	PRINT(("folder %s does not have any saved state\n", TargetModel()->Name()));
3716
3717	WindowStateNodeOpener opener(this, true);
3718		// this is our destination node, whatever it is for this window
3719	if (opener.StreamNode() == NULL)
3720		return;
3721
3722	if (!TargetModel()->IsRoot()) {
3723		BDirectory deskDir;
3724		FSGetDeskDir(&deskDir);
3725
3726		// try copying state from our parent directory, unless it is the
3727		// desktop folder
3728		BEntry entry(TargetModel()->EntryRef());
3729		BNode parent;
3730		if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK
3731			&& parent != deskDir) {
3732			PRINT(("looking at parent for state\n"));
3733			if (NodeHasSavedState(&parent)) {
3734				PRINT(("got state from parent\n"));
3735				defaultingNode = parent;
3736				gotDefaultingNode = true;
3737				// when getting state from parent, stagger the window
3738				shouldStagger = true;
3739			}
3740		}
3741	}
3742
3743	if (!gotDefaultingNode
3744		// parent didn't have any state, use the template directory from
3745		// tracker settings folder for what our state should be
3746		// For simplicity we are not picking up the most recent
3747		// changes that didn't get committed if home is still open in
3748		// a window, that's probably not a problem; would be OK if state
3749		// got committed after every change
3750		&& !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode,
3751			true)) {
3752		return;
3753	}
3754
3755	if (fIsDesktop) {
3756		// don't copy over the attributes if we are the Desktop
3757		return;
3758	}
3759
3760	// copy over the attributes
3761
3762	// set up a filter of the attributes we want copied
3763	const char* allowAttrs[] = {
3764		kAttrWindowFrame,
3765		kAttrWindowWorkspace,
3766		kAttrViewState,
3767		kAttrViewStateForeign,
3768		kAttrColumns,
3769		kAttrColumnsForeign,
3770		0
3771	};
3772
3773	// copy over attributes that apply; transform them properly, stripping
3774	// parts that do not apply, adding a window stagger, etc.
3775
3776	StaggerOneParams params;
3777	params.rectFromParent = shouldStagger;
3778	SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame,
3779		OffsetFrameOne, &params);
3780	SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState,
3781		ClearViewOriginOne, &params);
3782
3783	// do it
3784	AttributeStreamMemoryNode memoryNode;
3785	NamesToAcceptAttrFilter filter(allowAttrs);
3786	AttributeStreamFileNode fileNode(&defaultingNode);
3787
3788	*opener.StreamNode() << scrollOriginCleaner << frameOffsetter
3789		<< memoryNode << filter << fileNode;
3790}
3791
3792
3793void
3794BContainerWindow::RestoreWindowState(AttributeStreamNode* node)
3795{
3796	if (node == NULL || fIsDesktop) {
3797		// don't restore any window state if we are the Desktop
3798		return;
3799	}
3800
3801	const char* rectAttributeName;
3802	const char* workspaceAttributeName;
3803	if (TargetModel()->IsRoot()) {
3804		rectAttributeName = kAttrDisksFrame;
3805		workspaceAttributeName = kAttrDisksWorkspace;
3806	} else {
3807		rectAttributeName = kAttrWindowFrame;
3808		workspaceAttributeName = kAttrWindowWorkspace;
3809	}
3810
3811	BRect frame(Frame());
3812	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
3813			== sizeof(BRect)) {
3814		const float scalingFactor = be_plain_font->Size() / 12.0f;
3815		frame.left *= scalingFactor;
3816		frame.top *= scalingFactor;
3817		frame.right *= scalingFactor;
3818		frame.bottom *= scalingFactor;
3819
3820		MoveTo(frame.LeftTop());
3821		ResizeTo(frame.Width(), frame.Height());
3822	} else
3823		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3824
3825	fPreviousBounds = Bounds();
3826
3827	uint32 workspace;
3828	if (((fOpenFlags & kRestoreWorkspace) != 0)
3829		&& node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3830			&workspace) == sizeof(uint32))
3831		SetWorkspaces(workspace);
3832
3833	if ((fOpenFlags & kIsHidden) != 0)
3834		Minimize(true);
3835
3836	// restore window decor settings
3837	int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE);
3838	if (size > 0) {
3839		char buffer[size];
3840		if (((fOpenFlags & kRestoreDecor) != 0)
3841			&& node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer)
3842				== size) {
3843			BMessage decorSettings;
3844			if (decorSettings.Unflatten(buffer) == B_OK)
3845				SetDecoratorSettings(decorSettings);
3846		}
3847	}
3848}
3849
3850
3851void
3852BContainerWindow::RestoreWindowState(const BMessage& message)
3853{
3854	if (fIsDesktop) {
3855		// don't restore any window state if we are the Desktop
3856		return;
3857	}
3858
3859	const char* rectAttributeName;
3860	const char* workspaceAttributeName;
3861	if (TargetModel()->IsRoot()) {
3862		rectAttributeName = kAttrDisksFrame;
3863		workspaceAttributeName = kAttrDisksWorkspace;
3864	} else {
3865		rectAttributeName = kAttrWindowFrame;
3866		workspaceAttributeName = kAttrWindowWorkspace;
3867	}
3868
3869	BRect frame(Frame());
3870	if (message.FindRect(rectAttributeName, &frame) == B_OK) {
3871		const float scalingFactor = be_plain_font->Size() / 12.0f;
3872		frame.left *= scalingFactor;
3873		frame.top *= scalingFactor;
3874		frame.right *= scalingFactor;
3875		frame.bottom *= scalingFactor;
3876
3877		MoveTo(frame.LeftTop());
3878		ResizeTo(frame.Width(), frame.Height());
3879	} else
3880		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3881
3882	uint32 workspace;
3883	if (((fOpenFlags & kRestoreWorkspace) != 0)
3884		&& message.FindInt32(workspaceAttributeName,
3885			(int32*)&workspace) == B_OK) {
3886		SetWorkspaces(workspace);
3887	}
3888
3889	if ((fOpenFlags & kIsHidden) != 0)
3890		Minimize(true);
3891
3892	// restore window decor settings
3893	BMessage decorSettings;
3894	if (((fOpenFlags & kRestoreDecor) != 0)
3895		&& message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) {
3896		SetDecoratorSettings(decorSettings);
3897	}
3898
3899	fStateNeedsSaving = false;
3900		// Undo the effect of the above MoveTo and ResizeTo calls
3901}
3902
3903
3904void
3905BContainerWindow::SaveWindowState(AttributeStreamNode* node)
3906{
3907	if (fIsDesktop) {
3908		// don't save window state if we are the Desktop
3909		return;
3910	}
3911
3912	ASSERT(node != NULL);
3913
3914	const char* rectAttributeName;
3915	const char* workspaceAttributeName;
3916	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
3917		rectAttributeName = kAttrDisksFrame;
3918		workspaceAttributeName = kAttrDisksWorkspace;
3919	} else {
3920		rectAttributeName = kAttrWindowFrame;
3921		workspaceAttributeName = kAttrWindowWorkspace;
3922	}
3923
3924	// node is null if it already got deleted
3925	BRect frame(Frame());
3926	const float scalingFactor = be_plain_font->Size() / 12.0f;
3927	frame.left /= scalingFactor;
3928	frame.top /= scalingFactor;
3929	frame.right /= scalingFactor;
3930	frame.bottom /= scalingFactor;
3931	node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame);
3932
3933	uint32 workspaces = Workspaces();
3934	node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3935		&workspaces);
3936
3937	BMessage decorSettings;
3938	if (GetDecoratorSettings(&decorSettings) == B_OK) {
3939		int32 size = decorSettings.FlattenedSize();
3940		char buffer[size];
3941		if (decorSettings.Flatten(buffer, size) == B_OK) {
3942			node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer);
3943		}
3944	}
3945}
3946
3947
3948void
3949BContainerWindow::SaveWindowState(BMessage& message) const
3950{
3951	const char* rectAttributeName;
3952	const char* workspaceAttributeName;
3953
3954	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
3955		rectAttributeName = kAttrDisksFrame;
3956		workspaceAttributeName = kAttrDisksWorkspace;
3957	} else {
3958		rectAttributeName = kAttrWindowFrame;
3959		workspaceAttributeName = kAttrWindowWorkspace;
3960	}
3961
3962	// node is null if it already got deleted
3963	BRect frame(Frame());
3964	const float scalingFactor = be_plain_font->Size() / 12.0f;
3965	frame.left /= scalingFactor;
3966	frame.top /= scalingFactor;
3967	frame.right /= scalingFactor;
3968	frame.bottom /= scalingFactor;
3969	message.AddRect(rectAttributeName, frame);
3970	message.AddInt32(workspaceAttributeName, (int32)Workspaces());
3971
3972	BMessage decorSettings;
3973	if (GetDecoratorSettings(&decorSettings) == B_OK) {
3974		message.AddMessage(kAttrWindowDecor, &decorSettings);
3975	}
3976}
3977
3978
3979status_t
3980BContainerWindow::DragStart(const BMessage* dragMessage)
3981{
3982	if (dragMessage == NULL)
3983		return B_ERROR;
3984
3985	// if already dragging, or
3986	// if all the refs match
3987	if (Dragging()
3988		&& SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) {
3989		return B_OK;
3990	}
3991
3992	// cache the current drag message
3993	// build a list of the mimetypes in the message
3994	SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage,
3995		&fCachedTypesList);
3996
3997	fWaitingForRefs = true;
3998
3999	return B_OK;
4000}
4001
4002
4003void
4004BContainerWindow::DragStop()
4005{
4006	delete fDragMessage;
4007	fDragMessage = NULL;
4008
4009	delete fCachedTypesList;
4010	fCachedTypesList = NULL;
4011
4012	fWaitingForRefs = false;
4013}
4014
4015
4016void
4017BContainerWindow::ShowSelectionWindow()
4018{
4019	if (fSelectionWindow == NULL) {
4020		fSelectionWindow = new SelectionWindow(this);
4021		fSelectionWindow->Show();
4022	} else if (fSelectionWindow->Lock()) {
4023		// The window is already there, just bring it close
4024		fSelectionWindow->MoveCloseToMouse();
4025		if (fSelectionWindow->IsHidden())
4026			fSelectionWindow->Show();
4027
4028		fSelectionWindow->Unlock();
4029	}
4030}
4031
4032
4033void
4034BContainerWindow::ShowNavigator(bool show)
4035{
4036	if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory()
4037		|| PoseView()->IsFilePanel()) {
4038		return;
4039	}
4040
4041	if (show) {
4042		if (Navigator() && !Navigator()->IsHidden())
4043			return;
4044
4045		if (Navigator() == NULL) {
4046			fNavigator = new BNavigator(TargetModel());
4047			fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
4048		}
4049
4050		if (Navigator()->IsHidden())
4051			Navigator()->Show();
4052
4053		if (PoseView()->VScrollBar())
4054			PoseView()->UpdateScrollRange();
4055	} else {
4056		if (!Navigator() || Navigator()->IsHidden())
4057			return;
4058
4059		if (PoseView()->VScrollBar())
4060			PoseView()->UpdateScrollRange();
4061
4062		fNavigator->Hide();
4063	}
4064}
4065
4066
4067void
4068BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled)
4069{
4070	if (PoseView()->IsDesktopWindow())
4071		return;
4072
4073	if (enabled) {
4074		if (!Navigator())
4075			return;
4076
4077		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4078		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4079		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4080
4081		AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY,
4082			new BMessage(kNavigatorCommandBackward), Navigator());
4083		AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY,
4084			new BMessage(kNavigatorCommandForward), Navigator());
4085		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4086			new BMessage(kNavigatorCommandUp), Navigator());
4087
4088		AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4089			new BMessage(kNavigatorCommandBackward), Navigator());
4090		AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4091			new BMessage(kNavigatorCommandForward), Navigator());
4092		AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4093			new BMessage(kNavigatorCommandUp), Navigator());
4094		AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4095			new BMessage(kOpenSelection), PoseView());
4096		AddShortcut('L', B_COMMAND_KEY,
4097			new BMessage(kNavigatorCommandSetFocus), Navigator());
4098
4099	} else {
4100		RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
4101		RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
4102		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4103			// This is added again, below, with a new meaning.
4104
4105		RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4106		RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4107		RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4108		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4109			// This also changes meaning, added again below.
4110
4111		AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4112			new BMessage(kOpenSelection), PoseView());
4113		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4114			new BMessage(kOpenParentDir), PoseView());
4115			// We change the meaning from kNavigatorCommandUp
4116			// to kOpenParentDir.
4117		AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4118			new BMessage(kOpenParentDir), PoseView());
4119			// command + option results in closing the parent window
4120		RemoveShortcut('L', B_COMMAND_KEY);
4121	}
4122}
4123
4124
4125void
4126BContainerWindow::SetPathWatchingEnabled(bool enable)
4127{
4128	if (IsPathWatchingEnabled()) {
4129		stop_watching(this);
4130		fIsWatchingPath = false;
4131	}
4132
4133	if (enable) {
4134		if (TargetModel() != NULL) {
4135			BEntry entry;
4136
4137			TargetModel()->GetEntry(&entry);
4138			status_t err;
4139			do {
4140				err = entry.GetParent(&entry);
4141				if (err != B_OK)
4142					break;
4143
4144				char name[B_FILE_NAME_LENGTH];
4145				entry.GetName(name);
4146				if (strcmp(name, "/") == 0)
4147					break;
4148
4149				node_ref ref;
4150				entry.GetNodeRef(&ref);
4151				watch_node(&ref, B_WATCH_NAME, this);
4152			} while (err == B_OK);
4153
4154			fIsWatchingPath = err == B_OK;
4155		} else
4156			fIsWatchingPath = false;
4157	}
4158}
4159
4160
4161void
4162BContainerWindow::PulseTaskLoop()
4163{
4164	if (fTaskLoop)
4165		fTaskLoop->PulseMe();
4166}
4167
4168
4169void
4170BContainerWindow::PopulateArrangeByMenu(BMenu* menu)
4171{
4172	if (!fAttrMenu || !menu)
4173		return;
4174	// empty fArrangeByMenu...
4175	BMenuItem* item;
4176	while ((item = menu->RemoveItem((int32)0)) != NULL)
4177		delete item;
4178
4179	int32 itemCount = fAttrMenu->CountItems();
4180	for (int32 i = 0; i < itemCount; i++) {
4181		item = fAttrMenu->ItemAt(i);
4182		if (item->Command() == kAttributeItem) {
4183			BMessage* message = new BMessage(*(item->Message()));
4184			message->what = kArrangeBy;
4185			BMenuItem* newItem = new BMenuItem(item->Label(), message);
4186			newItem->SetTarget(PoseView());
4187			menu->AddItem(newItem);
4188		}
4189	}
4190
4191	menu->AddSeparatorItem();
4192
4193	item = new BMenuItem(B_TRANSLATE("Reverse order"),
4194		new BMessage(kArrangeReverseOrder));
4195
4196	item->SetTarget(PoseView());
4197	menu->AddItem(item);
4198
4199	menu->AddSeparatorItem();
4200
4201	item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K');
4202	item->SetTarget(PoseView());
4203	menu->AddItem(item);
4204}
4205
4206
4207//	#pragma mark - WindowStateNodeOpener
4208
4209
4210WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window,
4211	bool forWriting)
4212	:
4213	fModelOpener(NULL),
4214	fNode(NULL),
4215	fStreamNode(NULL)
4216{
4217	if (window->TargetModel() && window->TargetModel()->IsRoot()) {
4218		BDirectory dir;
4219		if (FSGetDeskDir(&dir) == B_OK) {
4220			fNode = new BDirectory(dir);
4221			fStreamNode = new AttributeStreamFileNode(fNode);
4222		}
4223	} else if (window->TargetModel()){
4224		fModelOpener = new ModelNodeLazyOpener(window->TargetModel(),
4225			forWriting, false);
4226		if (fModelOpener->IsOpen(forWriting)) {
4227			fStreamNode = new AttributeStreamFileNode(
4228				fModelOpener->TargetModel()->Node());
4229		}
4230	}
4231}
4232
4233WindowStateNodeOpener::~WindowStateNodeOpener()
4234{
4235	delete fModelOpener;
4236	delete fNode;
4237	delete fStreamNode;
4238}
4239
4240
4241void
4242WindowStateNodeOpener::SetTo(const BDirectory* node)
4243{
4244	delete fModelOpener;
4245	delete fNode;
4246	delete fStreamNode;
4247
4248	fModelOpener = NULL;
4249	fNode = new BDirectory(*node);
4250	fStreamNode = new AttributeStreamFileNode(fNode);
4251}
4252
4253
4254void
4255WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting)
4256{
4257	delete fModelOpener;
4258	delete fNode;
4259	delete fStreamNode;
4260
4261	fModelOpener = NULL;
4262	fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY));
4263	fStreamNode = new AttributeStreamFileNode(fNode);
4264}
4265
4266
4267void
4268WindowStateNodeOpener::SetTo(Model* model, bool forWriting)
4269{
4270	delete fModelOpener;
4271	delete fNode;
4272	delete fStreamNode;
4273
4274	fNode = NULL;
4275	fStreamNode = NULL;
4276	fModelOpener = new ModelNodeLazyOpener(model, forWriting, false);
4277	if (fModelOpener->IsOpen(forWriting)) {
4278		fStreamNode = new AttributeStreamFileNode(
4279			fModelOpener->TargetModel()->Node());
4280	}
4281}
4282
4283
4284AttributeStreamNode*
4285WindowStateNodeOpener::StreamNode() const
4286{
4287	return fStreamNode;
4288}
4289
4290
4291BNode*
4292WindowStateNodeOpener::Node() const
4293{
4294	if (!fStreamNode)
4295		return NULL;
4296
4297	if (fNode)
4298		return fNode;
4299
4300	return fModelOpener->TargetModel()->Node();
4301}
4302
4303
4304//	#pragma mark - BorderedView
4305
4306
4307BorderedView::BorderedView()
4308	:
4309	BGroupView(B_VERTICAL, 0),
4310	fEnableBorderHighlight(true)
4311{
4312	GroupLayout()->SetInsets(1);
4313}
4314
4315
4316void
4317BorderedView::WindowActivated(bool active)
4318{
4319	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4320	if (window == NULL)
4321		return;
4322
4323	if (window->PoseView()->IsFocus())
4324		PoseViewFocused(active); // Update border color
4325}
4326
4327
4328void BorderedView::EnableBorderHighlight(bool enable)
4329{
4330	fEnableBorderHighlight = enable;
4331	PoseViewFocused(false);
4332}
4333
4334
4335void
4336BorderedView::PoseViewFocused(bool focused)
4337{
4338	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4339	if (window == NULL)
4340		return;
4341
4342	color_which base = B_DOCUMENT_BACKGROUND_COLOR;
4343	float tint = B_DARKEN_2_TINT;
4344	if (focused && window->IsActive() && fEnableBorderHighlight) {
4345		base = B_KEYBOARD_NAVIGATION_COLOR;
4346		tint = B_NO_TINT;
4347	}
4348
4349	BScrollBar* hScrollBar = window->PoseView()->HScrollBar();
4350	if (hScrollBar != NULL)
4351		hScrollBar->SetBorderHighlighted(focused);
4352
4353	BScrollBar* vScrollBar = window->PoseView()->VScrollBar();
4354	if (vScrollBar != NULL)
4355		vScrollBar->SetBorderHighlighted(focused);
4356
4357	SetViewUIColor(base, tint);
4358	Invalidate();
4359}
4360
4361
4362void
4363BorderedView::Pulse()
4364{
4365	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4366	if (window != NULL)
4367		window->PulseTaskLoop();
4368}
4369