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