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