xref: /haiku/src/kits/tracker/ContainerWindow.cpp (revision fd767621838e85a7a408f76ab711f99b220591c2)
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(PoseView()->TargetModel(),
1170 			kActionUpdatePath);
1171 	}
1172 }
1173 
1174 
1175 void
1176 BContainerWindow::UpdateBackgroundImage()
1177 {
1178 	if (BootedInSafeMode())
1179 		return;
1180 
1181 	WindowStateNodeOpener opener(this, false);
1182 
1183 	if (!TargetModel()->IsRoot() && opener.Node() != NULL) {
1184 		fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
1185 			opener.Node(), fIsDesktop, PoseView());
1186 	}
1187 
1188 		// look for background image info in the window's node
1189 	BNode defaultingNode;
1190 	if (!fBackgroundImage && !fIsDesktop
1191 		&& DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) {
1192 		// look for background image info in the source for defaults
1193 		fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage,
1194 			&defaultingNode, fIsDesktop, PoseView());
1195 	}
1196 }
1197 
1198 
1199 void
1200 BContainerWindow::FrameResized(float, float)
1201 {
1202 	if (PoseView() != NULL && !fIsDesktop) {
1203 		BRect extent = PoseView()->Extent();
1204 		float offsetX = extent.left - PoseView()->Bounds().left;
1205 		float offsetY = extent.top - PoseView()->Bounds().top;
1206 
1207 		// scroll when the size augmented, there is a negative offset
1208 		// and we have resized over the bottom right corner of the extent
1209 		BPoint scroll(B_ORIGIN);
1210 		if (offsetX < 0 && PoseView()->Bounds().right > extent.right
1211 			&& Bounds().Width() > fPreviousBounds.Width()) {
1212 			scroll.x = std::max(fPreviousBounds.Width() - Bounds().Width(),
1213 				offsetX);
1214 		}
1215 
1216 		if (offsetY < 0 && PoseView()->Bounds().bottom > extent.bottom
1217 			&& Bounds().Height() > fPreviousBounds.Height()) {
1218 			scroll.y = std::max(fPreviousBounds.Height() - Bounds().Height(),
1219 				offsetY);
1220 		}
1221 
1222 		if (scroll != B_ORIGIN)
1223 			PoseView()->ScrollBy(scroll.x, scroll.y);
1224 
1225 		PoseView()->UpdateScrollRange();
1226 		PoseView()->ResetPosePlacementHint();
1227 	}
1228 
1229 	fPreviousBounds = Bounds();
1230 	if (IsActive())
1231 		fStateNeedsSaving = true;
1232 }
1233 
1234 
1235 void
1236 BContainerWindow::FrameMoved(BPoint)
1237 {
1238 	if (IsActive())
1239 		fStateNeedsSaving = true;
1240 }
1241 
1242 
1243 void
1244 BContainerWindow::WorkspacesChanged(uint32, uint32)
1245 {
1246 	if (IsActive())
1247 		fStateNeedsSaving = true;
1248 }
1249 
1250 
1251 void
1252 BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode)
1253 {
1254 	if (fBackgroundImage == NULL)
1255 		return;
1256 
1257 	if (newMode == kListMode)
1258 		fBackgroundImage->Remove();
1259 	else if (oldMode == kListMode)
1260 		fBackgroundImage->Show(PoseView(), current_workspace());
1261 }
1262 
1263 
1264 void
1265 BContainerWindow::CheckScreenIntersect()
1266 {
1267 	BScreen screen(this);
1268 	BRect screenFrame(screen.Frame());
1269 	BRect frame(Frame());
1270 
1271 	if (sNewWindRect.bottom > screenFrame.bottom)
1272 		sNewWindRect.OffsetTo(85, 50);
1273 
1274 	if (sNewWindRect.right > screenFrame.right)
1275 		sNewWindRect.OffsetTo(85, 50);
1276 
1277 	if (!frame.Intersects(screenFrame))
1278 		MoveTo(sNewWindRect.LeftTop());
1279 }
1280 
1281 
1282 void
1283 BContainerWindow::SaveState(bool hide)
1284 {
1285 	if (SaveStateIsEnabled()) {
1286 		WindowStateNodeOpener opener(this, true);
1287 		if (opener.StreamNode() != NULL)
1288 			SaveWindowState(opener.StreamNode());
1289 
1290 		if (hide)
1291 			Hide();
1292 
1293 		if (opener.StreamNode())
1294 			fPoseView->SaveState(opener.StreamNode());
1295 
1296 		fStateNeedsSaving = false;
1297 	}
1298 }
1299 
1300 
1301 void
1302 BContainerWindow::SaveState(BMessage& message) const
1303 {
1304 	if (SaveStateIsEnabled()) {
1305 		SaveWindowState(message);
1306 		fPoseView->SaveState(message);
1307 	}
1308 }
1309 
1310 
1311 bool
1312 BContainerWindow::StateNeedsSaving() const
1313 {
1314 	return fPoseView != NULL && (fStateNeedsSaving || fPoseView->StateNeedsSaving());
1315 }
1316 
1317 
1318 status_t
1319 BContainerWindow::GetLayoutState(BNode* node, BMessage* message)
1320 {
1321 	if (node == NULL || message == NULL)
1322 		return B_BAD_VALUE;
1323 
1324 	status_t result = node->InitCheck();
1325 	if (result != B_OK)
1326 		return result;
1327 
1328 	// ToDo: get rid of this, use AttrStream instead
1329 	node->RewindAttrs();
1330 	char attrName[256];
1331 	while (node->GetNextAttrName(attrName) == B_OK) {
1332 		attr_info info;
1333 		if (node->GetAttrInfo(attrName, &info) != B_OK)
1334 			continue;
1335 
1336 		// filter out attributes that are not related to window position
1337 		// and column resizing
1338 		// more can be added as needed
1339 		if (strcmp(attrName, kAttrWindowFrame) != 0
1340 			&& strcmp(attrName, kAttrColumns) != 0
1341 			&& strcmp(attrName, kAttrViewState) != 0
1342 			&& strcmp(attrName, kAttrColumnsForeign) != 0
1343 			&& strcmp(attrName, kAttrViewStateForeign) != 0) {
1344 			continue;
1345 		}
1346 
1347 		char* buffer = new char[info.size];
1348 		if (node->ReadAttr(attrName, info.type, 0, buffer,
1349 				(size_t)info.size) == info.size) {
1350 			message->AddData(attrName, info.type, buffer, (ssize_t)info.size);
1351 		}
1352 		delete[] buffer;
1353 	}
1354 
1355 	return B_OK;
1356 }
1357 
1358 
1359 status_t
1360 BContainerWindow::SetLayoutState(BNode* node, const BMessage* message)
1361 {
1362 	status_t result = node->InitCheck();
1363 	if (result != B_OK)
1364 		return result;
1365 
1366 	for (int32 globalIndex = 0; ;) {
1367 #if B_BEOS_VERSION_DANO
1368 		const char* name;
1369 #else
1370 		char* name;
1371 #endif
1372 		type_code type;
1373 		int32 count;
1374 		status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name,
1375 			&type, &count);
1376 		if (result != B_OK)
1377 			break;
1378 
1379 		for (int32 index = 0; index < count; index++) {
1380 			const void* buffer;
1381 			ssize_t size;
1382 			result = message->FindData(name, type, index, &buffer, &size);
1383 			if (result != B_OK) {
1384 				PRINT(("error reading %s \n", name));
1385 				return result;
1386 			}
1387 
1388 			if (node->WriteAttr(name, type, 0, buffer,
1389 					(size_t)size) != size) {
1390 				PRINT(("error writing %s \n", name));
1391 				return result;
1392 			}
1393 			globalIndex++;
1394 		}
1395 	}
1396 
1397 	return B_OK;
1398 }
1399 
1400 
1401 bool
1402 BContainerWindow::ShouldAddMenus() const
1403 {
1404 	return true;
1405 }
1406 
1407 
1408 bool
1409 BContainerWindow::ShouldAddScrollBars() const
1410 {
1411 	return true;
1412 }
1413 
1414 
1415 Model*
1416 BContainerWindow::TargetModel() const
1417 {
1418 	return fPoseView->TargetModel();
1419 }
1420 
1421 
1422 void
1423 BContainerWindow::SelectionChanged()
1424 {
1425 }
1426 
1427 
1428 void
1429 BContainerWindow::Zoom(BPoint, float, float)
1430 {
1431 	BRect oldZoomRect(fSavedZoomRect);
1432 	fSavedZoomRect = Frame();
1433 	ResizeToFit();
1434 
1435 	if (fSavedZoomRect == Frame() && oldZoomRect.IsValid())
1436 		ResizeTo(oldZoomRect.Width(), oldZoomRect.Height());
1437 }
1438 
1439 
1440 void
1441 BContainerWindow::ResizeToFit()
1442 {
1443 	BScreen screen(this);
1444 	BRect screenFrame(screen.Frame());
1445 
1446 	screenFrame.InsetBy(5, 5);
1447 	BMessage decoratorSettings;
1448 	GetDecoratorSettings(&decoratorSettings);
1449 
1450 	float tabHeight = 15;
1451 	BRect tabRect;
1452 	if (decoratorSettings.FindRect("tab frame", &tabRect) == B_OK)
1453 		tabHeight = tabRect.Height();
1454 	screenFrame.top += tabHeight;
1455 
1456 	BRect frame(Frame());
1457 
1458 	float widthDiff = frame.Width() - PoseView()->Frame().Width();
1459 	float heightDiff = frame.Height() - PoseView()->Frame().Height();
1460 
1461 	// move frame left top on screen
1462 	BPoint leftTop(frame.LeftTop());
1463 	leftTop.ConstrainTo(screenFrame);
1464 	frame.OffsetTo(leftTop);
1465 
1466 	// resize to extent size
1467 	BRect extent(PoseView()->Extent());
1468 	frame.right = frame.left + extent.Width() + widthDiff;
1469 	frame.bottom = frame.top + extent.Height() + heightDiff;
1470 
1471 	// make sure entire window fits on screen
1472 	frame = frame & screenFrame;
1473 
1474 	ResizeTo(frame.Width(), frame.Height());
1475 	MoveTo(frame.LeftTop());
1476 	PoseView()->DisableScrollBars();
1477 
1478 	// scroll if there is an offset
1479 	PoseView()->ScrollBy(
1480 		extent.left - PoseView()->Bounds().left,
1481 		extent.top - PoseView()->Bounds().top);
1482 
1483 	PoseView()->UpdateScrollRange();
1484 	PoseView()->EnableScrollBars();
1485 }
1486 
1487 
1488 void
1489 BContainerWindow::MessageReceived(BMessage* message)
1490 {
1491 	switch (message->what) {
1492 		case B_CUT:
1493 		case B_COPY:
1494 		case B_PASTE:
1495 		case B_SELECT_ALL:
1496 		{
1497 			BView* view = CurrentFocus();
1498 			if (dynamic_cast<BTextView*>(view) == NULL) {
1499 				// The selected item is not a BTextView, so forward the
1500 				// message to the PoseView.
1501 				if (fPoseView != NULL)
1502 					PostMessage(message, fPoseView);
1503 			} else {
1504 				// Since we catch the generic clipboard shortcuts in a way that
1505 				// means the BTextView will never get them, we must
1506 				// manually forward them ourselves.
1507 				//
1508 				// However, we have to take care to not forward the custom
1509 				// clipboard messages, else we would wind up in infinite
1510 				// recursion.
1511 				PostMessage(message, view);
1512 			}
1513 			break;
1514 		}
1515 
1516 		case kCutMoreSelectionToClipboard:
1517 		case kCopyMoreSelectionToClipboard:
1518 		case kPasteLinksFromClipboard:
1519 			if (fPoseView != NULL)
1520 				PostMessage(message, fPoseView);
1521 			break;
1522 
1523 		case B_UNDO: {
1524 			BView* view = CurrentFocus();
1525 			if (dynamic_cast<BTextView*>(view) == NULL) {
1526 				FSUndo();
1527 			} else {
1528 				view->MessageReceived(message);
1529 			}
1530 			break;
1531 		}
1532 
1533 		case B_REDO: {
1534 			BView* view = CurrentFocus();
1535 			if (dynamic_cast<BTextView*>(view) == NULL) {
1536 				FSRedo();
1537 			} else {
1538 				view->MessageReceived(message);
1539 			}
1540 			break;
1541 		}
1542 
1543 		case kNewFolder:
1544 			PostMessage(message, PoseView());
1545 			break;
1546 
1547 		case kRestoreState:
1548 			if (message->HasMessage("state")) {
1549 				BMessage state;
1550 				message->FindMessage("state", &state);
1551 				Init(&state);
1552 			} else
1553 				Init();
1554 			break;
1555 
1556 		case kResizeToFit:
1557 			ResizeToFit();
1558 			break;
1559 
1560 		case kLoadAddOn:
1561 			LoadAddOn(message);
1562 			break;
1563 
1564 		case kCopySelectionTo:
1565 		{
1566 			entry_ref ref;
1567 			if (message->FindRef("refs", &ref) != B_OK)
1568 				break;
1569 
1570 			BRoster().AddToRecentFolders(&ref);
1571 
1572 			Model model(&ref);
1573 			if (model.InitCheck() != B_OK)
1574 				break;
1575 
1576 			if (*model.NodeRef() == *TargetModel()->NodeRef())
1577 				PoseView()->DuplicateSelection();
1578 			else
1579 				PoseView()->MoveSelectionInto(&model, this, true);
1580 			break;
1581 		}
1582 
1583 		case kMoveSelectionTo:
1584 		{
1585 			entry_ref ref;
1586 			if (message->FindRef("refs", &ref) != B_OK)
1587 				break;
1588 
1589 			BRoster().AddToRecentFolders(&ref);
1590 
1591 			Model model(&ref);
1592 			if (model.InitCheck() != B_OK)
1593 				break;
1594 
1595 			PoseView()->MoveSelectionInto(&model, this, false, true);
1596 			break;
1597 		}
1598 
1599 		case kCreateLink:
1600 		case kCreateRelativeLink:
1601 		{
1602 			entry_ref ref;
1603 			if (message->FindRef("refs", &ref) == B_OK) {
1604 				BRoster().AddToRecentFolders(&ref);
1605 
1606 				Model model(&ref);
1607 				if (model.InitCheck() != B_OK)
1608 					break;
1609 
1610 				PoseView()->MoveSelectionInto(&model, this, false, false,
1611 					message->what == kCreateLink,
1612 					message->what == kCreateRelativeLink);
1613 			} else if (!TargetModel()->IsQuery()
1614 				&& !TargetModel()->IsVirtualDirectory()) {
1615 				// no destination specified, create link in same dir as item
1616 				PoseView()->MoveSelectionInto(TargetModel(), this, false, false,
1617 					message->what == kCreateLink,
1618 					message->what == kCreateRelativeLink);
1619 			}
1620 			break;
1621 		}
1622 
1623 		case kShowSelectionWindow:
1624 			ShowSelectionWindow();
1625 			break;
1626 
1627 		case kSelectMatchingEntries:
1628 			PoseView()->SelectMatchingEntries(message);
1629 			break;
1630 
1631 		case kFindButton:
1632 			(new FindWindow())->Show();
1633 			break;
1634 
1635 		case kQuitTracker:
1636 			be_app->PostMessage(B_QUIT_REQUESTED);
1637 			break;
1638 
1639 		case kRestoreBackgroundImage:
1640 			UpdateBackgroundImage();
1641 			break;
1642 
1643 		case kSwitchDirectory:
1644 		{
1645 			entry_ref ref;
1646 			if (message->FindRef("refs", &ref) != B_OK)
1647 				break;
1648 
1649 			BEntry entry;
1650 			if (entry.SetTo(&ref) != B_OK)
1651 				break;
1652 
1653 			if (StateNeedsSaving())
1654 				SaveState(false);
1655 
1656 			bool wasInTrash = IsTrash() || InTrash();
1657 			bool isRoot = PoseView()->TargetModel()->IsRoot();
1658 
1659 			// Switch dir and apply new state
1660 			WindowStateNodeOpener opener(this, false);
1661 			opener.SetTo(&entry, false);
1662 
1663 			// Update PoseView
1664 			PoseView()->SwitchDir(&ref, opener.StreamNode());
1665 
1666 			fIsTrash = FSIsTrashDir(&entry);
1667 			fInTrash = FSInTrashDir(&ref);
1668 
1669 			if (wasInTrash ^ (IsTrash() || InTrash())
1670 				|| isRoot != PoseView()->TargetModel()->IsRoot()) {
1671 				RepopulateMenus();
1672 			}
1673 
1674 			if (Navigator() != NULL) {
1675 				// update Navigation bar
1676 				int32 action = kActionSet;
1677 				if (message->FindInt32("action", &action) != B_OK) {
1678 					// Design problem? Why does FindInt32 touch
1679 					// 'action' at all if he can't find it??
1680 					action = kActionSet;
1681 				}
1682 				Navigator()->UpdateLocation(PoseView()->TargetModel(),
1683 					action);
1684 			}
1685 
1686 			TrackerSettings settings;
1687 			if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar())
1688 				SetPathWatchingEnabled(true);
1689 
1690 			SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1691 
1692 			// Update draggable folder icon
1693 			if (fMenuBar != NULL) {
1694 				if (!TargetModel()->IsRoot() && !IsTrash()) {
1695 					// Folder icon should be visible, but in single
1696 					// window navigation, it might not be.
1697 					if (fDraggableIcon != NULL) {
1698 						IconCache::sIconCache->IconChanged(TargetModel());
1699 						if (fDraggableIcon->IsHidden())
1700 							fDraggableIcon->Show();
1701 						fDraggableIcon->Invalidate();
1702 					} else
1703 						_AddFolderIcon();
1704 				} else if (fDraggableIcon != NULL)
1705 					fDraggableIcon->Hide();
1706 			}
1707 
1708 			// Update window title
1709 			UpdateTitle();
1710 			break;
1711 		}
1712 
1713 		case B_REFS_RECEIVED:
1714 			if (Dragging()) {
1715 				// ref in this message is the target,
1716 				// the end point of the drag
1717 
1718 				entry_ref ref;
1719 				if (message->FindRef("refs", &ref) == B_OK) {
1720 					fWaitingForRefs = false;
1721 					BEntry entry(&ref, true);
1722 					// don't copy to printers dir
1723 					if (!FSIsPrintersDir(&entry)) {
1724 						if (entry.InitCheck() == B_OK
1725 							&& entry.IsDirectory()) {
1726 							Model targetModel(&entry, true, false);
1727 							BPoint dropPoint;
1728 							uint32 buttons;
1729 							PoseView()->GetMouse(&dropPoint, &buttons, true);
1730 							PoseView()->HandleDropCommon(fDragMessage,
1731 								&targetModel, NULL, PoseView(), dropPoint);
1732 						}
1733 					}
1734 				}
1735 				DragStop();
1736 			}
1737 			break;
1738 
1739 		case B_TRACKER_ADDON_MESSAGE:
1740 		{
1741 			_PassMessageToAddOn(message);
1742 			break;
1743 		}
1744 
1745 		case B_OBSERVER_NOTICE_CHANGE:
1746 		{
1747 			int32 observerWhat;
1748 			if (message->FindInt32("be:observe_change_what", &observerWhat)
1749 					== B_OK) {
1750 				TrackerSettings settings;
1751 				switch (observerWhat) {
1752 					case kWindowsShowFullPathChanged:
1753 						UpdateTitle();
1754 						if (!IsPathWatchingEnabled()
1755 							&& settings.ShowFullPathInTitleBar()) {
1756 							SetPathWatchingEnabled(true);
1757 						}
1758 						if (IsPathWatchingEnabled()
1759 							&& !(settings.ShowNavigator()
1760 								|| settings.ShowFullPathInTitleBar())) {
1761 							SetPathWatchingEnabled(false);
1762 						}
1763 						break;
1764 
1765 					case kSingleWindowBrowseChanged:
1766 						if (settings.SingleWindowBrowse()
1767 							&& !Navigator()
1768 							&& TargetModel()->IsDirectory()
1769 							&& !PoseView()->IsFilePanel()
1770 							&& !PoseView()->IsDesktopWindow()) {
1771 							fNavigator = new BNavigator(TargetModel());
1772 							fPoseContainer->GridLayout()->AddView(fNavigator,
1773 								0, 0, 2);
1774 							fNavigator->Hide();
1775 							SetPathWatchingEnabled(settings.ShowNavigator()
1776 								|| settings.ShowFullPathInTitleBar());
1777 						}
1778 
1779 						if (!settings.SingleWindowBrowse()
1780 							&& !fIsDesktop && TargetModel()->IsDesktop()) {
1781 							// Close the "Desktop" window, but not the Desktop
1782 							this->Quit();
1783 						}
1784 
1785 						SetSingleWindowBrowseShortcuts(
1786 							settings.SingleWindowBrowse());
1787 						break;
1788 
1789 					case kShowNavigatorChanged:
1790 						ShowNavigator(settings.ShowNavigator());
1791 						if (!IsPathWatchingEnabled()
1792 							&& settings.ShowNavigator()) {
1793 							SetPathWatchingEnabled(true);
1794 						}
1795 						if (IsPathWatchingEnabled()
1796 							&& !(settings.ShowNavigator()
1797 								|| settings.ShowFullPathInTitleBar())) {
1798 							SetPathWatchingEnabled(false);
1799 						}
1800 						SetSingleWindowBrowseShortcuts(
1801 							settings.SingleWindowBrowse());
1802 						break;
1803 
1804 					case kDontMoveFilesToTrashChanged:
1805 					{
1806 						bool dontMoveToTrash
1807 							= settings.DontMoveFilesToTrash();
1808 
1809 						BMenuItem* item
1810 							= fFileContextMenu->FindItem(kMoveToTrash);
1811 						if (item != NULL) {
1812 							item->SetLabel(dontMoveToTrash
1813 								? B_TRANSLATE("Delete")
1814 								: B_TRANSLATE("Move to Trash"));
1815 						}
1816 						// Deskbar doesn't have a menu bar, so check if
1817 						// there is fMenuBar
1818 						if (fMenuBar != NULL && fFileMenu != NULL) {
1819 							item = fFileMenu->FindItem(kMoveToTrash);
1820 							if (item != NULL) {
1821 								item->SetLabel(dontMoveToTrash
1822 									? B_TRANSLATE("Delete")
1823 									: B_TRANSLATE("Move to Trash"));
1824 							}
1825 						}
1826 						UpdateIfNeeded();
1827 						break;
1828 					}
1829 
1830 					default:
1831 						_inherited::MessageReceived(message);
1832 						break;
1833 				}
1834 			}
1835 			break;
1836 		}
1837 
1838 		case B_NODE_MONITOR:
1839 			UpdateTitle();
1840 			break;
1841 
1842 		default:
1843 			_inherited::MessageReceived(message);
1844 			break;
1845 	}
1846 }
1847 
1848 
1849 void
1850 BContainerWindow::SetCutItem(BMenu* menu)
1851 {
1852 	BMenuItem* item;
1853 	if ((item = menu->FindItem(B_CUT)) == NULL
1854 		&& (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) {
1855 		return;
1856 	}
1857 
1858 	if (PoseView() != CurrentFocus())
1859 		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1860 	else {
1861 		if (TargetModel()->IsRoot() || TargetModel()->IsTrash()
1862 			|| TargetModel()->IsVirtualDirectory()) {
1863 			// cannot cut files in root, trash or in a virtual directory
1864 			item->SetEnabled(false);
1865 		} else {
1866 			item->SetEnabled(PoseView()->CountSelected() > 0
1867 				&& !PoseView()->SelectedVolumeIsReadOnly());
1868 		}
1869 	}
1870 
1871 	if ((modifiers() & B_SHIFT_KEY) != 0) {
1872 		item->SetLabel(B_TRANSLATE("Cut more"));
1873 		item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY);
1874 		item->SetMessage(new BMessage(kCutMoreSelectionToClipboard));
1875 	} else {
1876 		item->SetLabel(B_TRANSLATE("Cut"));
1877 		item->SetShortcut('X', B_COMMAND_KEY);
1878 		item->SetMessage(new BMessage(B_CUT));
1879 	}
1880 }
1881 
1882 
1883 void
1884 BContainerWindow::SetCopyItem(BMenu* menu)
1885 {
1886 	BMenuItem* item;
1887 	if ((item = menu->FindItem(B_COPY)) == NULL
1888 		&& (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) {
1889 		return;
1890 	}
1891 
1892 	if (PoseView() != CurrentFocus())
1893 		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1894 	else
1895 		item->SetEnabled(PoseView()->CountSelected() > 0);
1896 
1897 	if ((modifiers() & B_SHIFT_KEY) != 0) {
1898 		item->SetLabel(B_TRANSLATE("Copy more"));
1899 		item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY);
1900 		item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard));
1901 	} else {
1902 		item->SetLabel(B_TRANSLATE("Copy"));
1903 		item->SetShortcut('C', B_COMMAND_KEY);
1904 		item->SetMessage(new BMessage(B_COPY));
1905 	}
1906 }
1907 
1908 
1909 void
1910 BContainerWindow::SetPasteItem(BMenu* menu)
1911 {
1912 	BMenuItem* item;
1913 	if ((item = menu->FindItem(B_PASTE)) == NULL
1914 		&& (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) {
1915 		return;
1916 	}
1917 
1918 	if (PoseView() != CurrentFocus())
1919 		item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1920 	else {
1921 		item->SetEnabled(FSClipboardHasRefs()
1922 			&& !PoseView()->TargetVolumeIsReadOnly());
1923 	}
1924 
1925 	if ((modifiers() & B_SHIFT_KEY) != 0) {
1926 		item->SetLabel(B_TRANSLATE("Paste links"));
1927 		item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY);
1928 		item->SetMessage(new BMessage(kPasteLinksFromClipboard));
1929 	} else {
1930 		item->SetLabel(B_TRANSLATE("Paste"));
1931 		item->SetShortcut('V', B_COMMAND_KEY);
1932 		item->SetMessage(new BMessage(B_PASTE));
1933 	}
1934 }
1935 
1936 
1937 void
1938 BContainerWindow::SetArrangeMenu(BMenu* menu)
1939 {
1940 	BMenuItem* item;
1941 	if ((item = menu->FindItem(kCleanup)) == NULL
1942 		&& (item = menu->FindItem(kCleanupAll)) == NULL) {
1943 		return;
1944 	}
1945 
1946 	item->Menu()->SetEnabled(PoseView()->CountItems() > 0
1947 		&& (PoseView()->ViewMode() != kListMode));
1948 
1949 	BMenu* arrangeMenu;
1950 
1951 	if ((modifiers() & B_SHIFT_KEY) != 0) {
1952 		item->SetLabel(B_TRANSLATE("Clean up all"));
1953 		item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY);
1954 		item->SetMessage(new BMessage(kCleanupAll));
1955 		arrangeMenu = item->Menu();
1956 	} else {
1957 		item->SetLabel(B_TRANSLATE("Clean up"));
1958 		item->SetShortcut('K', B_COMMAND_KEY);
1959 		item->SetMessage(new BMessage(kCleanup));
1960 		arrangeMenu = item->Menu();
1961 	}
1962 
1963 	MarkArrangeByMenu(arrangeMenu);
1964 }
1965 
1966 
1967 void
1968 BContainerWindow::SetCloseItem(BMenu* menu)
1969 {
1970 	BMenuItem* item;
1971 	if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL
1972 		&& (item = menu->FindItem(kCloseAllWindows)) == NULL) {
1973 		return;
1974 	}
1975 
1976 	if ((modifiers() & B_SHIFT_KEY) != 0) {
1977 		item->SetLabel(B_TRANSLATE("Close all"));
1978 		item->SetShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY);
1979 		item->SetTarget(be_app);
1980 		item->SetMessage(new BMessage(kCloseAllWindows));
1981 	} else {
1982 		item->SetLabel(B_TRANSLATE("Close"));
1983 		item->SetShortcut('W', B_COMMAND_KEY);
1984 		item->SetTarget(this);
1985 		item->SetMessage(new BMessage(B_QUIT_REQUESTED));
1986 	}
1987 }
1988 
1989 
1990 bool
1991 BContainerWindow::IsShowing(const node_ref* node) const
1992 {
1993 	return PoseView()->Represents(node);
1994 }
1995 
1996 
1997 bool
1998 BContainerWindow::IsShowing(const entry_ref* entry) const
1999 {
2000 	return PoseView()->Represents(entry);
2001 }
2002 
2003 
2004 void
2005 BContainerWindow::AddMenus()
2006 {
2007 	fFileMenu = new BMenu(B_TRANSLATE("File"));
2008 	AddFileMenu(fFileMenu);
2009 	fMenuBar->AddItem(fFileMenu);
2010 	fWindowMenu = new BMenu(B_TRANSLATE("Window"));
2011 	fMenuBar->AddItem(fWindowMenu);
2012 	AddWindowMenu(fWindowMenu);
2013 	// just create the attribute, decide to add it later
2014 	fAttrMenu = new BMenu(B_TRANSLATE("Attributes"));
2015 	NewAttributeMenu(fAttrMenu);
2016 	PopulateArrangeByMenu(fArrangeByMenu);
2017 }
2018 
2019 
2020 void
2021 BContainerWindow::AddFileMenu(BMenu* menu)
2022 {
2023 	BMenuItem* item;
2024 
2025 	if (!PoseView()->IsFilePanel()) {
2026 		menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
2027 			new BMessage(kFindButton), 'F'));
2028 	}
2029 
2030 	if (!TargetModel()->IsQuery() && !TargetModel()->IsVirtualDirectory()
2031 		&& !IsTrash() && !IsPrintersDir() && !TargetModel()->IsRoot()) {
2032 		if (!PoseView()->IsFilePanel()) {
2033 			TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(),
2034 				B_TRANSLATE("New"));
2035 			menu->AddItem(templatesMenu);
2036 			templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2037 			templatesMenu->SetTargetForItems(PoseView());
2038 		} else {
2039 			item = new BMenuItem(B_TRANSLATE("New folder"),
2040 				new BMessage(kNewFolder), 'N');
2041 			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2042 			menu->AddItem(item);
2043 		}
2044 	}
2045 	menu->AddSeparatorItem();
2046 
2047 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2048 		new BMessage(kOpenSelection), 'O'));
2049 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2050 		new BMessage(kGetInfo), 'I'));
2051 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2052 		new BMessage(kEditItem), 'E'));
2053 
2054 	if (IsTrash() || InTrash()) {
2055 		menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"),
2056 			new BMessage(kRestoreFromTrash)));
2057 		if (IsTrash()) {
2058 			// add as first item in menu
2059 			menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2060 				new BMessage(kEmptyTrash)), 0);
2061 			menu->AddItem(new BSeparatorItem(), 1);
2062 		}
2063 	} else if (IsPrintersDir()) {
2064 		menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
2065 			new BMessage(kAddPrinter), 'N'), 0);
2066 		menu->AddItem(new BSeparatorItem(), 1);
2067 		menu->AddItem(new BMenuItem(B_TRANSLATE("Make active printer"),
2068 			new BMessage(kMakeActivePrinter)));
2069 	} else if (TargetModel()->IsRoot()) {
2070 		item = new BMenuItem(B_TRANSLATE("Unmount"),
2071 			new BMessage(kUnmountVolume), 'U');
2072 		item->SetEnabled(false);
2073 		menu->AddItem(item);
2074 		menu->AddItem(new BMenuItem(
2075 			B_TRANSLATE("Mount settings" B_UTF8_ELLIPSIS),
2076 			new BMessage(kRunAutomounterSettings)));
2077 	} else {
2078 		item = new BMenuItem(B_TRANSLATE("Duplicate"),
2079 			new BMessage(kDuplicateSelection), 'D');
2080 		item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
2081 		menu->AddItem(item);
2082 
2083 		item = new BMenuItem(TrackerSettings().DontMoveFilesToTrash()
2084 			? B_TRANSLATE("Delete") : B_TRANSLATE("Move to Trash"),
2085 			new BMessage(kMoveToTrash), 'T');
2086 		item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
2087 		menu->AddItem(item);
2088 
2089 		menu->AddSeparatorItem();
2090 
2091 		// The "Move To", "Copy To", "Create Link" menus are inserted
2092 		// at this place, have a look at:
2093 		// BContainerWindow::SetupMoveCopyMenus()
2094 	}
2095 
2096 	BMenuItem* cutItem = NULL;
2097 	BMenuItem* copyItem = NULL;
2098 	BMenuItem* pasteItem = NULL;
2099 	if (!IsPrintersDir()) {
2100 		menu->AddSeparatorItem();
2101 
2102 		if (!TargetModel()->IsRoot()) {
2103 			cutItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Cut"),
2104 				new BMessage(B_CUT), 'X');
2105 			menu->AddItem(cutItem);
2106 			copyItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Copy"),
2107 				new BMessage(B_COPY), 'C');
2108 			menu->AddItem(copyItem);
2109 			pasteItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Paste"),
2110 				new BMessage(B_PASTE), 'V');
2111 			menu->AddItem(pasteItem);
2112 			menu->AddSeparatorItem();
2113 
2114 			menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
2115 				new BMessage(kIdentifyEntry)));
2116 		}
2117 		BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2118 		addOnMenuItem->SetFont(be_plain_font);
2119 		menu->AddItem(addOnMenuItem);
2120 	}
2121 
2122 	menu->SetTargetForItems(PoseView());
2123 	if (cutItem != NULL)
2124 		cutItem->SetTarget(this);
2125 
2126 	if (copyItem != NULL)
2127 		copyItem->SetTarget(this);
2128 
2129 	if (pasteItem != NULL)
2130 		pasteItem->SetTarget(this);
2131 }
2132 
2133 
2134 void
2135 BContainerWindow::AddWindowMenu(BMenu* menu)
2136 {
2137 	BMenuItem* item;
2138 
2139 	BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
2140 
2141 	static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
2142 	BMessage* message;
2143 
2144 	for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
2145 		uint32 iconSize = kIconSizes[i];
2146 		message = new BMessage(kIconMode);
2147 		message->AddInt32("size", iconSize);
2148 		BString label;
2149 		label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
2150 			"The '×' is the Unicode multiplication sign U+00D7"),
2151 			iconSize, iconSize);
2152 		item = new BMenuItem(label, message);
2153 		item->SetTarget(PoseView());
2154 		iconSizeMenu->AddItem(item);
2155 	}
2156 
2157 	iconSizeMenu->AddSeparatorItem();
2158 
2159 	message = new BMessage(kIconMode);
2160 	message->AddInt32("scale", 0);
2161 	item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
2162 	item->SetTarget(PoseView());
2163 	iconSizeMenu->AddItem(item);
2164 
2165 	message = new BMessage(kIconMode);
2166 	message->AddInt32("scale", 1);
2167 	item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
2168 	item->SetTarget(PoseView());
2169 	iconSizeMenu->AddItem(item);
2170 
2171 	// A sub menu where the super item can be invoked.
2172 	menu->AddItem(iconSizeMenu);
2173 	iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
2174 	iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
2175 	iconSizeMenu->Superitem()->SetTarget(PoseView());
2176 
2177 	item = new BMenuItem(B_TRANSLATE("Mini icon view"),
2178 		new BMessage(kMiniIconMode), '2');
2179 	item->SetTarget(PoseView());
2180 	menu->AddItem(item);
2181 
2182 	item = new BMenuItem(B_TRANSLATE("List view"),
2183 		new BMessage(kListMode), '3');
2184 	item->SetTarget(PoseView());
2185 	menu->AddItem(item);
2186 
2187 	menu->AddSeparatorItem();
2188 
2189 	item = new BMenuItem(B_TRANSLATE("Resize to fit"),
2190 		new BMessage(kResizeToFit), 'Y');
2191 	item->SetTarget(this);
2192 	menu->AddItem(item);
2193 
2194 	fArrangeByMenu = new BMenu(B_TRANSLATE("Arrange by"));
2195 	menu->AddItem(fArrangeByMenu);
2196 
2197 	item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
2198 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
2199 	item->SetTarget(PoseView());
2200 	menu->AddItem(item);
2201 
2202 	item = new BMenuItem(B_TRANSLATE("Select all"),
2203 		new BMessage(B_SELECT_ALL), 'A');
2204 	item->SetTarget(this);
2205 	menu->AddItem(item);
2206 
2207 	item = new BMenuItem(B_TRANSLATE("Invert selection"),
2208 		new BMessage(kInvertSelection), 'S');
2209 	item->SetTarget(PoseView());
2210 	menu->AddItem(item);
2211 
2212 	if (!IsTrash()) {
2213 		item = new BMenuItem(B_TRANSLATE("Open parent"),
2214 			new BMessage(kOpenParentDir), B_UP_ARROW);
2215 		item->SetTarget(PoseView());
2216 		menu->AddItem(item);
2217 	}
2218 
2219 	item = new BMenuItem(B_TRANSLATE("Close"),
2220 		new BMessage(B_QUIT_REQUESTED), 'W');
2221 	item->SetTarget(this);
2222 	menu->AddItem(item);
2223 
2224 	item = new BMenuItem(B_TRANSLATE("Close all in workspace"),
2225 		new BMessage(kCloseAllInWorkspace), 'Q');
2226 	item->SetTarget(be_app);
2227 	menu->AddItem(item);
2228 
2229 	menu->AddSeparatorItem();
2230 
2231 	item = new BMenuItem(B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS),
2232 		new BMessage(kShowSettingsWindow), ',');
2233 	item->SetTarget(be_app);
2234 	menu->AddItem(item);
2235 }
2236 
2237 
2238 void
2239 BContainerWindow::AddShortcuts()
2240 {
2241 	// add equivalents of the menu shortcuts to the menuless desktop window
2242 	ASSERT(!IsTrash());
2243 	ASSERT(!PoseView()->IsFilePanel());
2244 	ASSERT(!TargetModel()->IsQuery());
2245 	ASSERT(!TargetModel()->IsVirtualDirectory());
2246 
2247 	AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY,
2248 		new BMessage(kCutMoreSelectionToClipboard), this);
2249 	AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY,
2250 		new BMessage(kCopyMoreSelectionToClipboard), this);
2251 	AddShortcut('F', B_COMMAND_KEY,
2252 		new BMessage(kFindButton), PoseView());
2253 	AddShortcut('N', B_COMMAND_KEY,
2254 		new BMessage(kNewFolder), PoseView());
2255 	AddShortcut('O', B_COMMAND_KEY,
2256 		new BMessage(kOpenSelection), PoseView());
2257 	AddShortcut('I', B_COMMAND_KEY,
2258 		new BMessage(kGetInfo), PoseView());
2259 	AddShortcut('E', B_COMMAND_KEY,
2260 		new BMessage(kEditItem), PoseView());
2261 	AddShortcut('D', B_COMMAND_KEY,
2262 		new BMessage(kDuplicateSelection), PoseView());
2263 	AddShortcut('T', B_COMMAND_KEY,
2264 		new BMessage(kMoveToTrash), PoseView());
2265 	AddShortcut('K', B_COMMAND_KEY,
2266 		new BMessage(kCleanup), PoseView());
2267 	AddShortcut('A', B_COMMAND_KEY,
2268 		new BMessage(B_SELECT_ALL), PoseView());
2269 	AddShortcut('S', B_COMMAND_KEY,
2270 		new BMessage(kInvertSelection), PoseView());
2271 	AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
2272 		new BMessage(kShowSelectionWindow), PoseView());
2273 	AddShortcut('G', B_COMMAND_KEY,
2274 		new BMessage(kEditQuery), PoseView());
2275 		// it is ok to add a global Edit query shortcut here, PoseView will
2276 		// filter out cases where selected pose is not a query
2277 	AddShortcut('U', B_COMMAND_KEY,
2278 		new BMessage(kUnmountVolume), PoseView());
2279 	AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
2280 		new BMessage(kOpenParentDir), PoseView());
2281 	AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY,
2282 		new BMessage(kOpenSelectionWith), PoseView());
2283 
2284 	BMessage* decreaseSize = new BMessage(kIconMode);
2285 	decreaseSize->AddInt32("scale", 0);
2286 	AddShortcut('-', B_COMMAND_KEY, decreaseSize, PoseView());
2287 
2288 	BMessage* increaseSize = new BMessage(kIconMode);
2289 	increaseSize->AddInt32("scale", 1);
2290 	AddShortcut('+', B_COMMAND_KEY, increaseSize, PoseView());
2291 }
2292 
2293 
2294 void
2295 BContainerWindow::MenusBeginning()
2296 {
2297 	if (fMenuBar == NULL)
2298 		return;
2299 
2300 	if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
2301 		// don't commit active pose if only a keyboard shortcut is
2302 		// invoked - this would prevent Cut/Copy/Paste from working
2303 		PoseView()->CommitActivePose();
2304 	}
2305 
2306 	// File menu
2307 	int32 selectCount = PoseView()->SelectionList()->CountItems();
2308 
2309 	SetupOpenWithMenu(fFileMenu);
2310 	SetupMoveCopyMenus(selectCount
2311 		? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef()
2312 		: NULL, fFileMenu);
2313 
2314 	if (TargetModel()->IsRoot()) {
2315 		BVolume boot;
2316 		BVolumeRoster().GetBootVolume(&boot);
2317 
2318 		bool ejectableVolumeSelected = false;
2319 		for (int32 index = 0; index < selectCount; index++) {
2320 			Model* model
2321 				= PoseView()->SelectionList()->ItemAt(index)->TargetModel();
2322 			if (model->IsVolume()) {
2323 				BVolume volume;
2324 				volume.SetTo(model->NodeRef()->device);
2325 				if (volume != boot) {
2326 					ejectableVolumeSelected = true;
2327 					break;
2328 				}
2329 			}
2330 		}
2331 		BMenuItem* item = fMenuBar->FindItem(kUnmountVolume);
2332 		if (item != NULL)
2333 			item->SetEnabled(ejectableVolumeSelected);
2334 	}
2335 
2336 	UpdateMenu(fMenuBar, kMenuBarContext);
2337 
2338 	AddMimeTypesToMenu(fAttrMenu);
2339 
2340 	if (IsPrintersDir()) {
2341 		EnableNamedMenuItem(fFileMenu, B_TRANSLATE("Make active printer"),
2342 			selectCount == 1);
2343 	}
2344 }
2345 
2346 
2347 void
2348 BContainerWindow::MenusEnded()
2349 {
2350 	// when we're done we want to clear nav menus for next time
2351 	DeleteSubmenu(fNavigationItem);
2352 	DeleteSubmenu(fMoveToItem);
2353 	DeleteSubmenu(fCopyToItem);
2354 	DeleteSubmenu(fCreateLinkItem);
2355 	DeleteSubmenu(fOpenWithItem);
2356 }
2357 
2358 
2359 void
2360 BContainerWindow::SetupNavigationMenu(const entry_ref* ref, BMenu* parent)
2361 {
2362 	// start by removing nav item (and separator) from old menu
2363 	if (fNavigationItem != NULL) {
2364 		BMenu* menu = fNavigationItem->Menu();
2365 		if (menu != NULL) {
2366 			menu->RemoveItem(fNavigationItem);
2367 			BMenuItem* item = menu->RemoveItem((int32)0);
2368 			ASSERT(item != fNavigationItem);
2369 			delete item;
2370 		}
2371 	}
2372 
2373 	// if we weren't passed a ref then we're navigating this window
2374 	if (ref == NULL)
2375 		ref = TargetModel()->EntryRef();
2376 
2377 	BEntry entry;
2378 	if (entry.SetTo(ref) != B_OK)
2379 		return;
2380 
2381 	// only navigate directories and queries (check for symlink here)
2382 	Model model(&entry);
2383 	entry_ref resolvedRef;
2384 
2385 	if (model.InitCheck() != B_OK
2386 		|| (!model.IsContainer() && !model.IsSymLink())) {
2387 		return;
2388 	}
2389 
2390 	if (model.IsSymLink()) {
2391 		if (entry.SetTo(model.EntryRef(), true) != B_OK)
2392 			return;
2393 
2394 		Model resolvedModel(&entry);
2395 		if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer())
2396 			return;
2397 
2398 		entry.GetRef(&resolvedRef);
2399 		ref = &resolvedRef;
2400 	}
2401 
2402 	if (fNavigationItem == NULL) {
2403 		fNavigationItem = new ModelMenuItem(&model,
2404 			new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this));
2405 	}
2406 
2407 	// setup a navigation menu item which will dynamically load items
2408 	// as menu items are traversed
2409 	BNavMenu* navMenu = dynamic_cast<BNavMenu*>(fNavigationItem->Submenu());
2410 	navMenu->SetNavDir(ref);
2411 	fNavigationItem->SetLabel(model.Name());
2412 	fNavigationItem->SetEntry(&entry);
2413 
2414 	parent->AddItem(fNavigationItem, 0);
2415 	parent->AddItem(new BSeparatorItem(), 1);
2416 
2417 	BMessage* message = new BMessage(B_REFS_RECEIVED);
2418 	message->AddRef("refs", ref);
2419 	fNavigationItem->SetMessage(message);
2420 	fNavigationItem->SetTarget(be_app);
2421 
2422 	if (!Dragging())
2423 		parent->SetTrackingHook(NULL, NULL);
2424 }
2425 
2426 
2427 void
2428 BContainerWindow::SetUpEditQueryItem(BMenu* menu)
2429 {
2430 	ASSERT(menu);
2431 	// File menu
2432 	int32 selectCount = PoseView()->CountSelected();
2433 
2434 	// add Edit query if appropriate
2435 	bool queryInSelection = false;
2436 	if (selectCount && selectCount < 100) {
2437 		// only do this for a limited number of selected poses
2438 
2439 		// if any queries selected, add an edit query menu item
2440 		for (int32 index = 0; index < selectCount; index++) {
2441 			BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2442 			Model model(pose->TargetModel()->EntryRef(), true);
2443 			if (model.InitCheck() != B_OK)
2444 				continue;
2445 
2446 			if (model.IsQuery() || model.IsQueryTemplate()) {
2447 				queryInSelection = true;
2448 				break;
2449 			}
2450 		}
2451 	}
2452 
2453 	bool poseViewIsQuery = TargetModel()->IsQuery();
2454 		// if the view is a query pose view, add edit query menu item
2455 
2456 	BMenuItem* item = menu->FindItem(kEditQuery);
2457 	if (!poseViewIsQuery && !queryInSelection && item != NULL)
2458 		item->Menu()->RemoveItem(item);
2459 	else if ((poseViewIsQuery || queryInSelection) && item == NULL) {
2460 		// add edit query item after Open
2461 		item = menu->FindItem(kOpenSelection);
2462 		if (item) {
2463 			int32 itemIndex = item->Menu()->IndexOf(item);
2464 			BMenuItem* query = new BMenuItem(B_TRANSLATE("Edit query"),
2465 				new BMessage(kEditQuery), 'G');
2466 			item->Menu()->AddItem(query, itemIndex + 1);
2467 			query->SetTarget(PoseView());
2468 		}
2469 	}
2470 }
2471 
2472 
2473 void
2474 BContainerWindow::SetupOpenWithMenu(BMenu* parent)
2475 {
2476 	// start by removing nav item (and separator) from old menu
2477 	if (fOpenWithItem) {
2478 		BMenu* menu = fOpenWithItem->Menu();
2479 		if (menu != NULL)
2480 			menu->RemoveItem(fOpenWithItem);
2481 
2482 		delete fOpenWithItem;
2483 		fOpenWithItem = 0;
2484 	}
2485 
2486 	int32 selectCount = PoseView()->CountSelected();
2487 	if (selectCount <= 0) {
2488 		// no selection, nothing to open
2489 		return;
2490 	}
2491 
2492 	if (TargetModel()->IsRoot()) {
2493 		// don't add ourselves if we are root
2494 		return;
2495 	}
2496 
2497 	// ToDo:
2498 	// check if only item in selection list is the root
2499 	// and do not add if true
2500 
2501 	// add after "Open"
2502 	BMenuItem* item = parent->FindItem(kOpenSelection);
2503 
2504 	// build a list of all refs to open
2505 	BMessage message(B_REFS_RECEIVED);
2506 	for (int32 index = 0; index < selectCount; index++) {
2507 		BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2508 		message.AddRef("refs", pose->TargetModel()->EntryRef());
2509 	}
2510 
2511 	// add Tracker token so that refs received recipients can script us
2512 	message.AddMessenger("TrackerViewToken", BMessenger(PoseView()));
2513 
2514 	int32 index = item->Menu()->IndexOf(item);
2515 	fOpenWithItem = new BMenuItem(
2516 		new OpenWithMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS),
2517 			&message, this, be_app), new BMessage(kOpenSelectionWith));
2518 	fOpenWithItem->SetTarget(PoseView());
2519 	fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY);
2520 
2521 	item->Menu()->AddItem(fOpenWithItem, index + 1);
2522 }
2523 
2524 
2525 void
2526 BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu* navMenu, uint32 what,
2527 	const entry_ref* ref, bool addLocalOnly)
2528 {
2529 	BVolume volume;
2530 	BVolumeRoster volumeRoster;
2531 	BDirectory directory;
2532 	BEntry entry;
2533 	BPath path;
2534 	Model model;
2535 	dev_t device = ref->device;
2536 
2537 	int32 volumeCount = 0;
2538 
2539 	navMenu->RemoveItems(0, navMenu->CountItems(), true);
2540 
2541 	// count persistent writable volumes
2542 	volumeRoster.Rewind();
2543 	while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2544 		if (!volume.IsReadOnly() && volume.IsPersistent())
2545 			volumeCount++;
2546 	}
2547 
2548 	// add the current folder
2549 	if (entry.SetTo(ref) == B_OK
2550 		&& entry.GetParent(&entry) == B_OK
2551 		&& model.SetTo(&entry) == B_OK) {
2552 		BNavMenu* menu = new BNavMenu(B_TRANSLATE("Current folder"), what,
2553 			this);
2554 		menu->SetNavDir(model.EntryRef());
2555 		menu->SetShowParent(true);
2556 
2557 		BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2558 		item->SetMessage(new BMessage((uint32)what));
2559 
2560 		navMenu->AddItem(item);
2561 	}
2562 
2563 	// add the recent folder menu
2564 	// the "Tracker" settings directory is only used to get its icon
2565 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
2566 		path.Append("Tracker");
2567 		if (entry.SetTo(path.Path()) == B_OK
2568 			&& model.SetTo(&entry) == B_OK) {
2569 			BMenu* menu = new RecentsMenu(B_TRANSLATE("Recent folders"),
2570 				kRecentFolders, what, this);
2571 
2572 			BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2573 			item->SetMessage(new BMessage((uint32)what));
2574 
2575 			navMenu->AddItem(item);
2576 		}
2577 	}
2578 
2579 	// add Desktop
2580 	FSGetBootDeskDir(&directory);
2581 	if (directory.InitCheck() == B_OK && directory.GetEntry(&entry) == B_OK
2582 		&& model.SetTo(&entry) == B_OK) {
2583 		navMenu->AddNavDir(&model, what, this, true);
2584 			// ask NavMenu to populate submenu for us
2585 	}
2586 
2587 	// add the home dir
2588 	if (find_directory(B_USER_DIRECTORY, &path) == B_OK
2589 		&& entry.SetTo(path.Path()) == B_OK && model.SetTo(&entry) == B_OK) {
2590 		navMenu->AddNavDir(&model, what, this, true);
2591 	}
2592 
2593 	navMenu->AddSeparatorItem();
2594 
2595 	// either add all mounted volumes (for copy), or all the top-level
2596 	// directories from the same device (for move)
2597 	// ToDo: can be changed if cross-device moves are implemented
2598 
2599 	if (addLocalOnly || volumeCount < 2) {
2600 		// add volume this item lives on
2601 		if (volume.SetTo(device) == B_OK
2602 			&& volume.GetRootDirectory(&directory) == B_OK
2603 			&& directory.GetEntry(&entry) == B_OK
2604 			&& model.SetTo(&entry) == B_OK) {
2605 			navMenu->AddNavDir(&model, what, this, false);
2606 				// do not have submenu populated
2607 
2608 			navMenu->SetNavDir(model.EntryRef());
2609 		}
2610 	} else {
2611 		// add all persistent writable volumes
2612 		volumeRoster.Rewind();
2613 		while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2614 			if (volume.IsReadOnly() || !volume.IsPersistent())
2615 				continue;
2616 
2617 			// add root dir
2618 			if (volume.GetRootDirectory(&directory) == B_OK
2619 				&& directory.GetEntry(&entry) == B_OK
2620 				&& model.SetTo(&entry) == B_OK) {
2621 				navMenu->AddNavDir(&model, what, this, true);
2622 					// ask NavMenu to populate submenu for us
2623 			}
2624 		}
2625 	}
2626 }
2627 
2628 
2629 void
2630 BContainerWindow::SetupMoveCopyMenus(const entry_ref* item_ref, BMenu* parent)
2631 {
2632 	if (IsTrash() || InTrash() || IsPrintersDir() || fMoveToItem == NULL
2633 		|| fCopyToItem == NULL || fCreateLinkItem == NULL
2634 		|| TargetModel()->IsRoot()) {
2635 		return;
2636 	}
2637 
2638 	// re-parent items to this menu since they're shared
2639 	BMenuItem* trash = parent->FindItem(kMoveToTrash);
2640 	int32 index = trash != NULL ? parent->IndexOf(trash) + 2 : 0;
2641 
2642 	if (fMoveToItem->Menu() != parent) {
2643 		if (fMoveToItem->Menu() != NULL)
2644 			fMoveToItem->Menu()->RemoveItem(fMoveToItem);
2645 
2646 		parent->AddItem(fMoveToItem, index++);
2647 	}
2648 
2649 	if (fCopyToItem->Menu() != parent) {
2650 		if (fCopyToItem->Menu() != NULL)
2651 			fCopyToItem->Menu()->RemoveItem(fCopyToItem);
2652 
2653 		parent->AddItem(fCopyToItem, index++);
2654 	}
2655 
2656 	if (fCreateLinkItem->Menu() != parent) {
2657 		if (fCreateLinkItem->Menu() != NULL)
2658 			fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem);
2659 
2660 		parent->AddItem(fCreateLinkItem, index);
2661 	}
2662 
2663 	// Set the "Create Link" item label here so it
2664 	// appears correctly when menus are disabled, too.
2665 	if ((modifiers() & B_SHIFT_KEY) != 0)
2666 		fCreateLinkItem->SetLabel(B_TRANSLATE("Create relative link"));
2667 	else
2668 		fCreateLinkItem->SetLabel(B_TRANSLATE("Create link"));
2669 
2670 	// only enable once the menus are built
2671 	fMoveToItem->SetEnabled(false);
2672 	fCopyToItem->SetEnabled(false);
2673 	fCreateLinkItem->SetEnabled(false);
2674 
2675 	// get ref for item which is selected
2676 	BEntry entry;
2677 	if (entry.SetTo(item_ref) != B_OK)
2678 		return;
2679 
2680 	Model tempModel(&entry);
2681 	if (tempModel.InitCheck() != B_OK)
2682 		return;
2683 
2684 	if (tempModel.IsRoot() || tempModel.IsVolume())
2685 		return;
2686 
2687 	// configure "Move to" menu item
2688 	PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fMoveToItem->Submenu()),
2689 		kMoveSelectionTo, item_ref, true);
2690 
2691 	// configure "Copy to" menu item
2692 	// add all mounted volumes (except the one this item lives on)
2693 	PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fCopyToItem->Submenu()),
2694 		kCopySelectionTo, item_ref, false);
2695 
2696 	// Set "Create Link" menu item message and
2697 	// add all mounted volumes (except the one this item lives on)
2698 	if ((modifiers() & B_SHIFT_KEY) != 0) {
2699 		fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink));
2700 		PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2701 				(fCreateLinkItem->Submenu()),
2702 			kCreateRelativeLink, item_ref, false);
2703 	} else {
2704 		fCreateLinkItem->SetMessage(new BMessage(kCreateLink));
2705 		PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2706 				(fCreateLinkItem->Submenu()),
2707 			kCreateLink, item_ref, false);
2708 	}
2709 
2710 	fMoveToItem->SetEnabled(PoseView()->CountSelected() > 0
2711 		&& !PoseView()->SelectedVolumeIsReadOnly());
2712 	fCopyToItem->SetEnabled(PoseView()->CountSelected() > 0);
2713 	fCreateLinkItem->SetEnabled(PoseView()->CountSelected() > 0);
2714 
2715 	// Set the "Identify" item label
2716 	BMenuItem* identifyItem = parent->FindItem(kIdentifyEntry);
2717 	if (identifyItem != NULL) {
2718 		if ((modifiers() & B_SHIFT_KEY) != 0) {
2719 			identifyItem->SetLabel(B_TRANSLATE("Force identify"));
2720 			identifyItem->Message()->ReplaceBool("force", true);
2721 		} else {
2722 			identifyItem->SetLabel(B_TRANSLATE("Identify"));
2723 			identifyItem->Message()->ReplaceBool("force", false);
2724 		}
2725 	}
2726 }
2727 
2728 
2729 uint32
2730 BContainerWindow::ShowDropContextMenu(BPoint where, BPoseView* source)
2731 {
2732 	BPoint global(where);
2733 
2734 	PoseView()->ConvertToScreen(&global);
2735 	PoseView()->CommitActivePose();
2736 
2737 	// Change the "Create Link" item - allow user to
2738 	// create relative links with the Shift key down.
2739 	BMenuItem* item = fDropContextMenu->FindItem(kCreateLink);
2740 	if (item == NULL)
2741 		item = fDropContextMenu->FindItem(kCreateRelativeLink);
2742 
2743 	if (item != NULL && (modifiers() & B_SHIFT_KEY) != 0) {
2744 		item->SetLabel(B_TRANSLATE("Create relative link here"));
2745 		item->SetMessage(new BMessage(kCreateRelativeLink));
2746 	} else if (item != NULL) {
2747 		item->SetLabel(B_TRANSLATE("Create link here"));
2748 		item->SetMessage(new BMessage(kCreateLink));
2749 	}
2750 
2751 	int32 itemCount = fDropContextMenu->CountItems();
2752 	for(int32 i = 0; i < itemCount - 2; i++) {
2753 		// separator item and Cancel item are skipped
2754 		item = fDropContextMenu->ItemAt(i);
2755 		if (item == NULL)
2756 			break;
2757 
2758 		if (item->Command() == kMoveSelectionTo && source != NULL) {
2759 			item->SetEnabled(!source->SelectedVolumeIsReadOnly()
2760 				&& !PoseView()->TargetVolumeIsReadOnly());
2761 		} else
2762 			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2763 	}
2764 
2765 	item = fDropContextMenu->Go(global, true, true);
2766 	if (item != NULL)
2767 		return item->Command();
2768 
2769 	return 0;
2770 }
2771 
2772 
2773 void
2774 BContainerWindow::ShowContextMenu(BPoint where, const entry_ref* ref)
2775 {
2776 	ASSERT(IsLocked());
2777 	BPoint global(where);
2778 	PoseView()->ConvertToScreen(&global);
2779 	PoseView()->CommitActivePose();
2780 
2781 	if (ref != NULL) {
2782 		// clicked on a pose, show file or volume context menu
2783 		Model model(ref);
2784 
2785 		if (model.IsTrash()) {
2786 			if (fTrashContextMenu->Window() || Dragging())
2787 				return;
2788 
2789 			DeleteSubmenu(fNavigationItem);
2790 
2791 			// selected item was trash, show the trash context menu instead
2792 
2793 			EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash,
2794 				static_cast<TTracker*>(be_app)->TrashFull());
2795 
2796 			SetupNavigationMenu(ref, fTrashContextMenu);
2797 
2798 			fContextMenu = fTrashContextMenu;
2799 		} else {
2800 			bool showAsVolume = false;
2801 			bool isFilePanel = PoseView()->IsFilePanel();
2802 
2803 			if (Dragging()) {
2804 				fContextMenu = NULL;
2805 
2806 				BEntry entry;
2807 				model.GetEntry(&entry);
2808 
2809 				// only show for directories (directory, volume, root)
2810 				//
2811 				// don't show a popup for the trash or printers
2812 				// trash is handled in DeskWindow
2813 				//
2814 				// since this menu is opened asynchronously
2815 				// we need to make sure we don't open it more
2816 				// than once, the IsShowing flag is set in
2817 				// SlowContextPopup::AttachedToWindow and
2818 				// reset in DetachedFromWindow
2819 				// see the notes in SlowContextPopup::AttachedToWindow
2820 
2821 				if (!FSIsPrintersDir(&entry)
2822 					&& !fDragContextMenu->IsShowing()) {
2823 					//printf("ShowContextMenu - target is %s %i\n",
2824 					//	ref->name, IsShowing(ref));
2825 					fDragContextMenu->ClearMenu();
2826 
2827 					// in case the ref is a symlink, resolve it
2828 					// only pop open for directories
2829 					BEntry resolvedEntry(ref, true);
2830 					if (!resolvedEntry.IsDirectory())
2831 						return;
2832 
2833 					entry_ref resolvedRef;
2834 					resolvedEntry.GetRef(&resolvedRef);
2835 
2836 					// use the resolved ref for the menu
2837 					fDragContextMenu->SetNavDir(&resolvedRef);
2838 					fDragContextMenu->SetTypesList(fCachedTypesList);
2839 					fDragContextMenu->SetTarget(BMessenger(this));
2840 					BPoseView* poseView = PoseView();
2841 					if (poseView != NULL) {
2842 						BMessenger target(poseView);
2843 						fDragContextMenu->InitTrackingHook(
2844 							&BPoseView::MenuTrackingHook, &target,
2845 							fDragMessage);
2846 					}
2847 
2848 					// this is now asynchronous so that we don't
2849 					// deadlock in Window::Quit,
2850 					fDragContextMenu->Go(global);
2851 				}
2852 
2853 				return;
2854 			} else if (TargetModel()->IsRoot() || model.IsVolume()) {
2855 				fContextMenu = fVolumeContextMenu;
2856 				showAsVolume = true;
2857 			} else
2858 				fContextMenu = fFileContextMenu;
2859 
2860 			if (fContextMenu == NULL)
2861 				return;
2862 
2863 			// clean up items from last context menu
2864 			MenusEnded();
2865 
2866 			if (fContextMenu == fFileContextMenu) {
2867 				// Add all mounted volumes (except the one this item lives on.)
2868 				BNavMenu* navMenu = dynamic_cast<BNavMenu*>(
2869 					fCreateLinkItem->Submenu());
2870 				PopulateMoveCopyNavMenu(navMenu,
2871 				fCreateLinkItem->Message()->what, ref, false);
2872 			} else if (showAsVolume) {
2873 				// non-volume enable/disable copy, move, identify
2874 				EnableNamedMenuItem(fContextMenu, kDuplicateSelection, false);
2875 				EnableNamedMenuItem(fContextMenu, kMoveToTrash, false);
2876 				EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false);
2877 
2878 				// volume model, enable/disable the Unmount item
2879 				bool ejectableVolumeSelected = false;
2880 
2881 				BVolume boot;
2882 				BVolumeRoster().GetBootVolume(&boot);
2883 				BVolume volume;
2884 				volume.SetTo(model.NodeRef()->device);
2885 				if (volume != boot)
2886 					ejectableVolumeSelected = true;
2887 
2888 				EnableNamedMenuItem(fContextMenu,
2889 					B_TRANSLATE("Unmount"), ejectableVolumeSelected);
2890 			}
2891 
2892 			SetupNavigationMenu(ref, fContextMenu);
2893 			if (!showAsVolume && !isFilePanel) {
2894 				SetupMoveCopyMenus(ref, fContextMenu);
2895 				SetupOpenWithMenu(fContextMenu);
2896 			}
2897 
2898 			UpdateMenu(fContextMenu, kPosePopUpContext);
2899 		}
2900 	} else if (fWindowContextMenu != NULL) {
2901 		// Repopulate desktop menu if IsDesktop
2902 		if (fIsDesktop)
2903 			RepopulateMenus();
2904 
2905 		MenusEnded();
2906 
2907 		// clicked on a window, show window context menu
2908 
2909 		SetupNavigationMenu(ref, fWindowContextMenu);
2910 		UpdateMenu(fWindowContextMenu, kWindowPopUpContext);
2911 
2912 		fContextMenu = fWindowContextMenu;
2913 	}
2914 
2915 	// context menu invalid or popup window is already open
2916 	if (fContextMenu == NULL || fContextMenu->Window() != NULL)
2917 		return;
2918 
2919 	fContextMenu->Go(global, true, true, true);
2920 	fContextMenu = NULL;
2921 }
2922 
2923 
2924 void
2925 BContainerWindow::AddFileContextMenus(BMenu* menu)
2926 {
2927 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2928 		new BMessage(kOpenSelection), 'O'));
2929 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2930 		new BMessage(kGetInfo), 'I'));
2931 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2932 		new BMessage(kEditItem), 'E'));
2933 
2934 	if (!IsTrash() && !InTrash() && !IsPrintersDir()) {
2935 		menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"),
2936 			new BMessage(kDuplicateSelection), 'D'));
2937 	}
2938 
2939 	if (!IsTrash() && !InTrash()) {
2940 		menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash()
2941 			? B_TRANSLATE("Delete") : B_TRANSLATE("Move to Trash"),
2942 			new BMessage(kMoveToTrash), 'T'));
2943 		if (!IsPrintersDir()) {
2944 			// add separator for copy to/move to items (navigation items)
2945 			menu->AddSeparatorItem();
2946 		}
2947 	} else {
2948 		menu->AddItem(new BMenuItem(B_TRANSLATE("Delete"),
2949 			new BMessage(kDelete), 0));
2950 		menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"),
2951 			new BMessage(kRestoreFromTrash), 0));
2952 	}
2953 
2954 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2955 	menu->AddSeparatorItem();
2956 	BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
2957 		new BMessage(B_CUT), 'X');
2958 	menu->AddItem(cutItem);
2959 	BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
2960 		new BMessage(B_COPY), 'C');
2961 	menu->AddItem(copyItem);
2962 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
2963 		new BMessage(B_PASTE), 'V');
2964 	menu->AddItem(pasteItem);
2965 #endif
2966 	menu->AddSeparatorItem();
2967 
2968 	BMessage* message = new BMessage(kIdentifyEntry);
2969 	message->AddBool("force", false);
2970 	menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), message));
2971 
2972 	BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2973 	addOnMenuItem->SetFont(be_plain_font);
2974 	menu->AddItem(addOnMenuItem);
2975 
2976 	// set targets as needed
2977 	menu->SetTargetForItems(PoseView());
2978 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2979 	cutItem->SetTarget(this);
2980 	copyItem->SetTarget(this);
2981 	pasteItem->SetTarget(this);
2982 #endif
2983 }
2984 
2985 
2986 void
2987 BContainerWindow::AddVolumeContextMenus(BMenu* menu)
2988 {
2989 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2990 		new BMessage(kOpenSelection), 'O'));
2991 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2992 		new BMessage(kGetInfo), 'I'));
2993 	menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2994 		new BMessage(kEditItem), 'E'));
2995 
2996 	menu->AddSeparatorItem();
2997 	menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
2998 
2999 	BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
3000 		new BMessage(kUnmountVolume), 'U');
3001 	item->SetEnabled(false);
3002 	menu->AddItem(item);
3003 	menu->AddSeparatorItem();
3004 
3005 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
3006 	menu->AddItem(new BMenuItem(B_TRANSLATE("Paste"),
3007 		new BMessage(B_PASTE), 'V'));
3008 	menu->AddSeparatorItem();
3009 #endif
3010 
3011 	menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
3012 
3013 	menu->SetTargetForItems(PoseView());
3014 }
3015 
3016 
3017 void
3018 BContainerWindow::AddWindowContextMenus(BMenu* menu)
3019 {
3020 	// create context sensitive menu for empty area of window
3021 	// since we check view mode before display, this should be a radio
3022 	// mode menu
3023 
3024 	Model* targetModel = TargetModel();
3025 	ASSERT(targetModel != NULL);
3026 
3027 	bool needSeparator = true;
3028 	if (IsTrash()) {
3029 		menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
3030 			new BMessage(kEmptyTrash)));
3031 	} else if (IsPrintersDir()) {
3032 		menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
3033 			new BMessage(kAddPrinter), 'N'));
3034 	} else if (InTrash() || targetModel->IsRoot()) {
3035 		needSeparator = false;
3036 	} else {
3037 		if (!PoseView()->IsFilePanel()) {
3038 			TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(),
3039 				B_TRANSLATE("New"));
3040 			menu->AddItem(templatesMenu);
3041 			templatesMenu->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
3042 			templatesMenu->SetTargetForItems(PoseView());
3043 			templatesMenu->SetFont(be_plain_font);
3044 		} else {
3045 			BMenuItem* item = new BMenuItem(B_TRANSLATE("New folder"),
3046 				new BMessage(kNewFolder), 'N');
3047 			item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
3048 			menu->AddItem(item);
3049 		}
3050 	}
3051 
3052 	if (needSeparator)
3053 		menu->AddSeparatorItem();
3054 
3055 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
3056 	BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
3057 		new BMessage(B_PASTE), 'V');
3058 	pasteItem->SetEnabled(FSClipboardHasRefs()
3059 		&& !PoseView()->TargetVolumeIsReadOnly());
3060 	menu->AddItem(pasteItem);
3061 	menu->AddSeparatorItem();
3062 #endif
3063 
3064 	BMenu* arrangeBy = new BMenu(B_TRANSLATE("Arrange by"));
3065 	PopulateArrangeByMenu(arrangeBy);
3066 	menu->AddItem(arrangeBy);
3067 
3068 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
3069 		new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
3070 	menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
3071 		new BMessage(B_SELECT_ALL), 'A'));
3072 	if (!IsTrash()) {
3073 		menu->AddItem(new BMenuItem(B_TRANSLATE("Open parent"),
3074 			new BMessage(kOpenParentDir), B_UP_ARROW));
3075 	}
3076 
3077 	if (targetModel->IsRoot()) {
3078 		menu->AddSeparatorItem();
3079 		menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
3080 	}
3081 
3082 	menu->AddSeparatorItem();
3083 	BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
3084 	addOnMenuItem->SetFont(be_plain_font);
3085 	menu->AddItem(addOnMenuItem);
3086 
3087 #if DEBUG
3088 	menu->AddSeparatorItem();
3089 	BMenuItem* testing = new BMenuItem("Test icon cache",
3090 		new BMessage(kTestIconCache));
3091 	menu->AddItem(testing);
3092 #endif
3093 
3094 	// target items as needed
3095 	menu->SetTargetForItems(PoseView());
3096 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
3097 	pasteItem->SetTarget(this);
3098 #endif
3099 }
3100 
3101 
3102 void
3103 BContainerWindow::AddDropContextMenus(BMenu* menu)
3104 {
3105 	menu->AddItem(new BMenuItem(B_TRANSLATE("Move here"),
3106 		new BMessage(kMoveSelectionTo)));
3107 	menu->AddItem(new BMenuItem(B_TRANSLATE("Copy here"),
3108 		new BMessage(kCopySelectionTo)));
3109 	menu->AddItem(new BMenuItem(B_TRANSLATE("Create link here"),
3110 		new BMessage(kCreateLink)));
3111 	menu->AddSeparatorItem();
3112 	menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"),
3113 		new BMessage(kCancelButton)));
3114 }
3115 
3116 
3117 void
3118 BContainerWindow::AddTrashContextMenus(BMenu* menu)
3119 {
3120 	// setup special trash context menu
3121 	menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
3122 		new BMessage(kEmptyTrash)));
3123 	menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
3124 		new BMessage(kOpenSelection), 'O'));
3125 	menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
3126 		new BMessage(kGetInfo), 'I'));
3127 	menu->SetTargetForItems(PoseView());
3128 }
3129 
3130 
3131 void
3132 BContainerWindow::EachAddon(bool (*eachAddon)(const Model*, const char*,
3133 		uint32 shortcut, uint32 modifiers, bool primary, void* context,
3134 		BContainerWindow* window, BMenu* menu),
3135 	void* passThru, BStringList& mimeTypes, BMenu* menu)
3136 {
3137 	AutoLock<LockingList<AddonShortcut> > lock(fAddonsList);
3138 	if (lock.IsLocked()) {
3139 		for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) {
3140 			struct AddonShortcut* item = fAddonsList->ItemAt(i);
3141 			bool primary = false;
3142 
3143 			if (mimeTypes.CountStrings() > 0) {
3144 				BFile file(item->model->EntryRef(), B_READ_ONLY);
3145 				if (file.InitCheck() == B_OK) {
3146 					BAppFileInfo info(&file);
3147 					if (info.InitCheck() == B_OK) {
3148 						bool secondary = true;
3149 
3150 						// does this add-on has types set at all?
3151 						BMessage message;
3152 						if (info.GetSupportedTypes(&message) == B_OK) {
3153 							type_code typeCode;
3154 							int32 count;
3155 							if (message.GetInfo("types", &typeCode,
3156 									&count) == B_OK) {
3157 								secondary = false;
3158 							}
3159 						}
3160 
3161 						// check all supported types if it has some set
3162 						if (!secondary) {
3163 							for (int32 i = mimeTypes.CountStrings();
3164 									!primary && i-- > 0;) {
3165 								BString type = mimeTypes.StringAt(i);
3166 								if (info.IsSupportedType(type.String())) {
3167 									BMimeType mimeType(type.String());
3168 									if (info.Supports(&mimeType))
3169 										primary = true;
3170 									else
3171 										secondary = true;
3172 								}
3173 							}
3174 						}
3175 
3176 						if (!secondary && !primary)
3177 							continue;
3178 					}
3179 				}
3180 			}
3181 			((eachAddon)(item->model, item->model->Name(), item->key,
3182 				item->modifiers, primary, passThru, this, menu));
3183 		}
3184 	}
3185 }
3186 
3187 
3188 void
3189 BContainerWindow::BuildMimeTypeList(BStringList& mimeTypes)
3190 {
3191 	int32 selectCount = PoseView()->CountSelected();
3192 	if (selectCount <= 0) {
3193 		// just add the type of the current directory
3194 		AddMimeTypeString(mimeTypes, TargetModel());
3195 	} else {
3196 		_UpdateSelectionMIMEInfo();
3197 		for (int32 index = 0; index < selectCount; index++) {
3198 			BPose* pose = PoseView()->SelectionList()->ItemAt(index);
3199 			AddMimeTypeString(mimeTypes, pose->TargetModel());
3200 			// If it's a symlink, resolves it and add the Target's MimeType
3201 			if (pose->TargetModel()->IsSymLink()) {
3202 				Model* resolved = new Model(
3203 					pose->TargetModel()->EntryRef(), true, true);
3204 				if (resolved->InitCheck() == B_OK)
3205 					AddMimeTypeString(mimeTypes, resolved);
3206 
3207 				delete resolved;
3208 			}
3209 		}
3210 	}
3211 }
3212 
3213 
3214 void
3215 BContainerWindow::BuildAddOnMenu(BMenu* parentMenu)
3216 {
3217 	BMenuItem* item = parentMenu->FindItem(B_TRANSLATE("Add-ons"));
3218 	if (parentMenu->IndexOf(item) == 0) {
3219 		// the folder of the context menu seems to be named "Add-Ons"
3220 		// so we just take the last menu item, which is correct if not
3221 		// build with debug option
3222 		item = parentMenu->ItemAt(parentMenu->CountItems() - 1);
3223 	}
3224 	if (item == NULL)
3225 		return;
3226 
3227 	BFont font;
3228 	parentMenu->GetFont(&font);
3229 
3230 	BMenu* menu = item->Submenu();
3231 	if (menu == NULL)
3232 		return;
3233 
3234 	menu->SetFont(&font);
3235 
3236 	// found the addons menu, empty it first
3237 	for (;;) {
3238 		item = menu->RemoveItem((int32)0);
3239 		if (!item)
3240 			break;
3241 		delete item;
3242 	}
3243 
3244 	BObjectList<BMenuItem> primaryList;
3245 	BObjectList<BMenuItem> secondaryList;
3246 	BStringList mimeTypes(10);
3247 	BuildMimeTypeList(mimeTypes);
3248 
3249 	AddOneAddonParams params;
3250 	params.primaryList = &primaryList;
3251 	params.secondaryList = &secondaryList;
3252 
3253 	// build a list of the MIME types of the selected items
3254 
3255 	EachAddon(AddOneAddon, &params, mimeTypes, parentMenu);
3256 
3257 	primaryList.SortItems(CompareLabels);
3258 	secondaryList.SortItems(CompareLabels);
3259 
3260 	int32 count = primaryList.CountItems();
3261 	for (int32 index = 0; index < count; index++)
3262 		menu->AddItem(primaryList.ItemAt(index));
3263 
3264 	if (count > 0)
3265 		menu->AddSeparatorItem();
3266 
3267 	count = secondaryList.CountItems();
3268 	for (int32 index = 0; index < count; index++)
3269 		menu->AddItem(secondaryList.ItemAt(index));
3270 
3271 	menu->SetTargetForItems(this);
3272 }
3273 
3274 
3275 void
3276 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context)
3277 {
3278 	const int32 selectCount = PoseView()->CountSelected();
3279 	const int32 poseCount = PoseView()->CountItems();
3280 
3281 	if (context == kMenuBarContext) {
3282 		EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0);
3283 		EnableNamedMenuItem(menu, kGetInfo, selectCount > 0);
3284 		EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0);
3285 		EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0);
3286 		EnableNamedMenuItem(menu, kDelete,
3287 			PoseView()->CanMoveToTrashOrDuplicate());
3288 	}
3289 
3290 	if (context == kMenuBarContext || context == kPosePopUpContext) {
3291 		SetUpEditQueryItem(menu);
3292 
3293 		EnableNamedMenuItem(menu, kEditItem, PoseView()->CanEditName());
3294 		EnableNamedMenuItem(menu, kMoveToTrash,
3295 			PoseView()->CanMoveToTrashOrDuplicate());
3296 		EnableNamedMenuItem(menu, kDuplicateSelection,
3297 			PoseView()->CanMoveToTrashOrDuplicate());
3298 
3299 		SetCutItem(menu);
3300 		SetCopyItem(menu);
3301 		SetPasteItem(menu);
3302 	}
3303 
3304 	if (context == kMenuBarContext || context == kWindowPopUpContext) {
3305 		uint32 viewMode = PoseView()->ViewMode();
3306 
3307 		BMenu* iconSizeMenu = NULL;
3308 		if (BMenuItem* item = menu->FindItem(kIconMode))
3309 			iconSizeMenu = item->Submenu();
3310 
3311 		if (iconSizeMenu != NULL) {
3312 			if (viewMode == kIconMode) {
3313 				int32 iconSize = PoseView()->UnscaledIconSizeInt();
3314 				BMenuItem* item = iconSizeMenu->ItemAt(0);
3315 				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL;
3316 						i++) {
3317 					BMessage* message = item->Message();
3318 					if (message == NULL) {
3319 						item->SetMarked(false);
3320 						continue;
3321 					}
3322 					int32 size;
3323 					if (message->FindInt32("size", &size) != B_OK)
3324 						size = -1;
3325 					item->SetMarked(iconSize == size);
3326 				}
3327 			} else {
3328 				BMenuItem* item;
3329 				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++)
3330 					item->SetMarked(false);
3331 			}
3332 		}
3333 
3334 		MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode);
3335 		MarkNamedMenuItem(menu, kListMode, viewMode == kListMode);
3336 		MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode);
3337 
3338 		SetCloseItem(menu);
3339 		SetArrangeMenu(menu);
3340 		SetPasteItem(menu);
3341 
3342 		BEntry entry(TargetModel()->EntryRef());
3343 		BDirectory parent;
3344 		entry_ref ref;
3345 		BEntry root("/");
3346 
3347 		bool parentIsRoot = (entry.GetParent(&parent) == B_OK
3348 			&& parent.GetEntry(&entry) == B_OK
3349 			&& entry.GetRef(&ref) == B_OK
3350 			&& entry == root);
3351 
3352 		EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop()
3353 			&& !TargetModel()->IsRoot()
3354 			&& (!parentIsRoot
3355 				|| TrackerSettings().SingleWindowBrowse()
3356 				|| TrackerSettings().ShowDisksIcon()
3357 				|| (modifiers() & B_CONTROL_KEY) != 0));
3358 
3359 		EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0);
3360 		EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0);
3361 
3362 		BMenuItem* item = menu->FindItem(B_TRANSLATE("New"));
3363 		if (item != NULL) {
3364 			TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>(
3365 				item->Submenu());
3366 			if (templatesMenu != NULL)
3367 				templatesMenu->UpdateMenuState();
3368 		}
3369 	}
3370 
3371 	BuildAddOnMenu(menu);
3372 }
3373 
3374 
3375 BMessage*
3376 BContainerWindow::AddOnMessage(int32 what)
3377 {
3378 	BMessage* message = new BMessage(what);
3379 
3380 	// add selected refs to message
3381 	BObjectList<BPose>* selectionList = PoseView()->SelectionList();
3382 
3383 	int32 index = 0;
3384 	BPose* pose;
3385 	while ((pose = selectionList->ItemAt(index++)) != NULL)
3386 		message->AddRef("refs", pose->TargetModel()->EntryRef());
3387 
3388 	message->AddRef("dir_ref", TargetModel()->EntryRef());
3389 	message->AddMessenger("TrackerViewToken", BMessenger(PoseView()));
3390 
3391 	return message;
3392 }
3393 
3394 
3395 void
3396 BContainerWindow::LoadAddOn(BMessage* message)
3397 {
3398 	UpdateIfNeeded();
3399 
3400 	entry_ref addonRef;
3401 	status_t result = message->FindRef("refs", &addonRef);
3402 	if (result != B_OK) {
3403 		BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
3404 		buffer.ReplaceFirst("%error", strerror(result));
3405 		buffer.ReplaceFirst("%name", addonRef.name);
3406 
3407 		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
3408 			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3409 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3410 		alert->Go();
3411 		return;
3412 	}
3413 
3414 	// add selected refs to message
3415 	BMessage* refs = AddOnMessage(B_REFS_RECEIVED);
3416 
3417 	LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs,
3418 		addonRef, *TargetModel()->EntryRef());
3419 }
3420 
3421 
3422 //	#pragma mark - BContainerWindow private methods
3423 
3424 
3425 void
3426 BContainerWindow::_UpdateSelectionMIMEInfo()
3427 {
3428 	BPose* pose;
3429 	int32 index = 0;
3430 	while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) {
3431 		BString mimeType(pose->TargetModel()->MimeType());
3432 		if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3433 			pose->TargetModel()->Mimeset(true);
3434 			if (pose->TargetModel()->IsSymLink()) {
3435 				Model* resolved = new Model(pose->TargetModel()->EntryRef(),
3436 					true, true);
3437 				if (resolved->InitCheck() == B_OK) {
3438 					mimeType.SetTo(resolved->MimeType());
3439 					if (!mimeType.Length()
3440 						|| mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3441 						resolved->Mimeset(true);
3442 					}
3443 				}
3444 				delete resolved;
3445 			}
3446 		}
3447 	}
3448 }
3449 
3450 
3451 void
3452 BContainerWindow::_AddFolderIcon()
3453 {
3454 	if (fMenuBar == NULL) {
3455 		// We don't want to add the icon if there's no menubar
3456 		return;
3457 	}
3458 
3459 	float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1,
3460 		iconSize = fMenuBar->Bounds().Height() - 2;
3461 	if (iconSize < baseIconSize)
3462 		iconSize = baseIconSize;
3463 
3464 	fDraggableIcon = new(std::nothrow)
3465 		DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1));
3466 	if (fDraggableIcon != NULL) {
3467 		fMenuContainer->GroupLayout()->AddView(fDraggableIcon);
3468 		fMenuBar->SetBorders(
3469 			BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER);
3470 	}
3471 }
3472 
3473 
3474 void
3475 BContainerWindow::_PassMessageToAddOn(BMessage* message)
3476 {
3477 	LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY,
3478 		&RunAddOnMessageThread, new BMessage(*message), (void*)NULL);
3479 }
3480 
3481 
3482 BMenuItem*
3483 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3484 	int32 type, float width, int32 align, bool editable, bool statField)
3485 {
3486 	return NewAttributeMenuItem(label, name, type, NULL, width, align,
3487 		editable, statField);
3488 }
3489 
3490 
3491 BMenuItem*
3492 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3493 	int32 type, const char* displayAs, float width, int32 align,
3494 	bool editable, bool statField)
3495 {
3496 	BMessage* message = new BMessage(kAttributeItem);
3497 	message->AddString("attr_name", name);
3498 	message->AddInt32("attr_type", type);
3499 	message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type));
3500 	message->AddFloat("attr_width", width);
3501 	message->AddInt32("attr_align", align);
3502 	if (displayAs != NULL)
3503 		message->AddString("attr_display_as", displayAs);
3504 	message->AddBool("attr_editable", editable);
3505 	message->AddBool("attr_statfield", statField);
3506 
3507 	BMenuItem* menuItem = new BMenuItem(label, message);
3508 	menuItem->SetTarget(PoseView());
3509 
3510 	return menuItem;
3511 }
3512 
3513 
3514 void
3515 BContainerWindow::NewAttributeMenu(BMenu* menu)
3516 {
3517 	ASSERT(PoseView());
3518 
3519 	BMenuItem* item;
3520 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"),
3521 		new BMessage(kCopyAttributes)));
3522 	item->SetTarget(PoseView());
3523 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"),
3524 		new BMessage(kPasteAttributes)));
3525 	item->SetTarget(PoseView());
3526 	menu->AddSeparatorItem();
3527 
3528 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"),
3529 		kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3530 
3531 	if (gLocalizedNamePreferred) {
3532 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"),
3533 			kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3534 	}
3535 
3536 	menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize,
3537 		B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true));
3538 
3539 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"),
3540 		kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3541 
3542 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"),
3543 		kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3544 
3545 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"),
3546 		kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false));
3547 
3548 	if (IsTrash() || InTrash()) {
3549 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"),
3550 			kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false,
3551 			false));
3552 	} else {
3553 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath,
3554 			B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false));
3555 	}
3556 
3557 #ifdef OWNER_GROUP_ATTRIBUTES
3558 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner,
3559 		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3560 
3561 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup,
3562 		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3563 #endif
3564 
3565 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"),
3566 		kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true));
3567 }
3568 
3569 
3570 void
3571 BContainerWindow::ShowAttributeMenu()
3572 {
3573 	ASSERT(fAttrMenu);
3574 	fMenuBar->AddItem(fAttrMenu);
3575 }
3576 
3577 
3578 void
3579 BContainerWindow::HideAttributeMenu()
3580 {
3581 	ASSERT(fAttrMenu);
3582 	fMenuBar->RemoveItem(fAttrMenu);
3583 }
3584 
3585 
3586 void
3587 BContainerWindow::MarkAttributeMenu()
3588 {
3589 	MarkAttributeMenu(fAttrMenu);
3590 }
3591 
3592 
3593 void
3594 BContainerWindow::MarkAttributeMenu(BMenu* menu)
3595 {
3596 	if (menu == NULL)
3597 		return;
3598 
3599 	int32 count = menu->CountItems();
3600 	for (int32 index = 0; index < count; index++) {
3601 		BMenuItem* item = menu->ItemAt(index);
3602 		int32 attrHash;
3603 		if (item->Message() != NULL) {
3604 			if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK)
3605 				item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0);
3606 			else
3607 				item->SetMarked(false);
3608 		}
3609 
3610 		BMenu* submenu = item->Submenu();
3611 		if (submenu != NULL) {
3612 			int32 count2 = submenu->CountItems();
3613 			for (int32 subindex = 0; subindex < count2; subindex++) {
3614 				item = submenu->ItemAt(subindex);
3615 				if (item->Message() != NULL) {
3616 					if (item->Message()->FindInt32("attr_hash", &attrHash)
3617 						== B_OK) {
3618 						item->SetMarked(PoseView()->ColumnFor((uint32)attrHash)
3619 							!= 0);
3620 					} else
3621 						item->SetMarked(false);
3622 				}
3623 			}
3624 		}
3625 	}
3626 }
3627 
3628 
3629 void
3630 BContainerWindow::MarkArrangeByMenu(BMenu* menu)
3631 {
3632 	if (menu == NULL)
3633 		return;
3634 
3635 	int32 count = menu->CountItems();
3636 	for (int32 index = 0; index < count; index++) {
3637 		BMenuItem* item = menu->ItemAt(index);
3638 		if (item->Message() != NULL) {
3639 			uint32 attrHash;
3640 			if (item->Message()->FindInt32("attr_hash",
3641 					(int32*)&attrHash) == B_OK) {
3642 				item->SetMarked(PoseView()->PrimarySort() == attrHash);
3643 			} else if (item->Command() == kArrangeReverseOrder)
3644 				item->SetMarked(PoseView()->ReverseSort());
3645 		}
3646 	}
3647 }
3648 
3649 
3650 void
3651 BContainerWindow::AddMimeTypesToMenu()
3652 {
3653 	AddMimeTypesToMenu(fAttrMenu);
3654 }
3655 
3656 
3657 // Adds a menu for a specific MIME type if it doesn't exist already.
3658 // Returns the menu, if it existed or not.
3659 BMenu*
3660 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType,
3661 	BMenu* menu, int32 start)
3662 {
3663 	AutoLock<BLooper> _(menu->Looper());
3664 
3665 	if (!mimeType.IsValid())
3666 		return NULL;
3667 
3668 	// Check if we already have an entry for this MIME type in the menu.
3669 	for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) {
3670 		BMessage* message = item->Message();
3671 		if (message == NULL)
3672 			continue;
3673 
3674 		const char* type;
3675 		if (message->FindString("mimetype", &type) == B_OK
3676 			&& !strcmp(mimeType.Type(), type)) {
3677 			return item->Submenu();
3678 		}
3679 	}
3680 
3681 	BMessage attrInfo;
3682 	char description[B_MIME_TYPE_LENGTH];
3683 	const char* label = mimeType.Type();
3684 
3685 	if (!mimeType.IsInstalled())
3686 		return NULL;
3687 
3688 	// only add things to menu which have "user-visible" data
3689 	if (mimeType.GetAttrInfo(&attrInfo) != B_OK)
3690 		return NULL;
3691 
3692 	if (mimeType.GetShortDescription(description) == B_OK && description[0])
3693 		label = description;
3694 
3695 	// go through each field in meta mime and add it to a menu
3696 	BMenu* mimeMenu = NULL;
3697 	if (isSuperType) {
3698 		// If it is a supertype, we create the menu anyway as it may have
3699 		// submenus later on.
3700 		mimeMenu = new BMenu(label);
3701 		BFont font;
3702 		menu->GetFont(&font);
3703 		mimeMenu->SetFont(&font);
3704 	}
3705 
3706 	int32 index = -1;
3707 	const char* publicName;
3708 	while (attrInfo.FindString("attr:public_name", ++index, &publicName)
3709 			== B_OK) {
3710 		if (!attrInfo.FindBool("attr:viewable", index)) {
3711 			// don't add if attribute not viewable
3712 			continue;
3713 		}
3714 
3715 		int32 type;
3716 		int32 align;
3717 		int32 width;
3718 		bool editable;
3719 		const char* attrName;
3720 		if (attrInfo.FindString("attr:name", index, &attrName) != B_OK
3721 			|| attrInfo.FindInt32("attr:type", index, &type) != B_OK
3722 			|| attrInfo.FindBool("attr:editable", index, &editable) != B_OK
3723 			|| attrInfo.FindInt32("attr:width", index, &width) != B_OK
3724 			|| attrInfo.FindInt32("attr:alignment", index, &align) != B_OK)
3725 			continue;
3726 
3727 		BString displayAs;
3728 		attrInfo.FindString("attr:display_as", index, &displayAs);
3729 
3730 		if (mimeMenu == NULL) {
3731 			// do a lazy allocation of the menu
3732 			mimeMenu = new BMenu(label);
3733 			BFont font;
3734 			menu->GetFont(&font);
3735 			mimeMenu->SetFont(&font);
3736 		}
3737 		mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type,
3738 			displayAs.String(), width, align, editable, false));
3739 	}
3740 
3741 	if (mimeMenu == NULL)
3742 		return NULL;
3743 
3744 	BMessage* message = new BMessage(kMIMETypeItem);
3745 	message->AddString("mimetype", mimeType.Type());
3746 	menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type()));
3747 
3748 	return mimeMenu;
3749 }
3750 
3751 
3752 void
3753 BContainerWindow::AddMimeTypesToMenu(BMenu* menu)
3754 {
3755 	if (menu == NULL)
3756 		return;
3757 
3758 	// Remove old mime type menus
3759 	int32 start = menu->CountItems();
3760 	while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) {
3761 		delete menu->RemoveItem(start - 1);
3762 		start--;
3763 	}
3764 
3765 	// Add a separator item if there is none yet
3766 	if (start > 0
3767 		&& dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL)
3768 		menu->AddSeparatorItem();
3769 
3770 	// Add MIME type in case we're a default query type window
3771 	BPath path;
3772 	if (TargetModel() != NULL) {
3773 		TargetModel()->GetPath(&path);
3774 		if (path.InitCheck() == B_OK
3775 			&& strstr(path.Path(), "/" kQueryTemplates "/") != NULL) {
3776 			// demangle MIME type name
3777 			BString name(TargetModel()->Name());
3778 			name.ReplaceFirst('_', '/');
3779 
3780 			PoseView()->AddMimeType(name.String());
3781 		}
3782 	}
3783 
3784 	// Add MIME type menus
3785 
3786 	int32 typeCount = PoseView()->CountMimeTypes();
3787 
3788 	for (int32 index = 0; index < typeCount; index++) {
3789 		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3790 		if (mimeType.InitCheck() == B_OK) {
3791 			BMimeType superType;
3792 			mimeType.GetSupertype(&superType);
3793 			if (superType.InitCheck() == B_OK) {
3794 				BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3795 				if (superMenu != NULL) {
3796 					// We have a supertype menu.
3797 					AddMimeMenu(mimeType, false, superMenu, 0);
3798 				}
3799 			}
3800 		}
3801 	}
3802 
3803 	// remove empty super menus, promote sub-types if needed
3804 
3805 	for (int32 index = 0; index < typeCount; index++) {
3806 		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3807 		BMimeType superType;
3808 		mimeType.GetSupertype(&superType);
3809 
3810 		BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3811 		if (superMenu == NULL)
3812 			continue;
3813 
3814 		int32 itemsFound = 0;
3815 		int32 menusFound = 0;
3816 		for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) {
3817 			if (item->Submenu() != NULL)
3818 				menusFound++;
3819 			else
3820 				itemsFound++;
3821 		}
3822 
3823 		if (itemsFound == 0) {
3824 			if (menusFound != 0) {
3825 				// promote types to the top level
3826 				while (BMenuItem* item = superMenu->RemoveItem((int32)0)) {
3827 					menu->AddItem(item);
3828 				}
3829 			}
3830 
3831 			menu->RemoveItem(superMenu->Superitem());
3832 			delete superMenu->Superitem();
3833 		}
3834 	}
3835 
3836 	// remove separator if it's the only item in menu
3837 	BMenuItem* item = menu->ItemAt(menu->CountItems() - 1);
3838 	if (dynamic_cast<BSeparatorItem*>(item) != NULL) {
3839 		menu->RemoveItem(item);
3840 		delete item;
3841 	}
3842 
3843 	MarkAttributeMenu(menu);
3844 }
3845 
3846 
3847 BHandler*
3848 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index,
3849 	BMessage* specifier, int32 form, const char* property)
3850 {
3851 	if (strcmp(property, "Poses") == 0) {
3852 //		PRINT(("BContainerWindow::ResolveSpecifier %s\n", property));
3853 		message->PopSpecifier();
3854 		return PoseView();
3855 	}
3856 
3857 	return _inherited::ResolveSpecifier(message, index, specifier,
3858 		form, property);
3859 }
3860 
3861 
3862 PiggybackTaskLoop*
3863 BContainerWindow::DelayedTaskLoop()
3864 {
3865 	if (!fTaskLoop)
3866 		fTaskLoop = new PiggybackTaskLoop;
3867 
3868 	return fTaskLoop;
3869 }
3870 
3871 
3872 bool
3873 BContainerWindow::NeedsDefaultStateSetup()
3874 {
3875 	if (TargetModel() == NULL)
3876 		return false;
3877 
3878 	if (TargetModel()->IsRoot()) {
3879 		// don't try to set up anything if we are root
3880 		return false;
3881 	}
3882 
3883 	WindowStateNodeOpener opener(this, false);
3884 	if (opener.StreamNode() == NULL) {
3885 		// can't read state, give up
3886 		return false;
3887 	}
3888 
3889 	return !NodeHasSavedState(opener.Node());
3890 }
3891 
3892 
3893 bool
3894 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result,
3895 	bool createNew, bool createFolder)
3896 {
3897 	//PRINT(("looking for default state in tracker settings dir\n"));
3898 	BPath settingsPath;
3899 	if (FSFindTrackerSettingsDir(&settingsPath) != B_OK)
3900 		return false;
3901 
3902 	BDirectory dir(settingsPath.Path());
3903 
3904 	BPath path(settingsPath);
3905 	path.Append(name);
3906 	if (!BEntry(path.Path()).Exists()) {
3907 		if (!createNew)
3908 			return false;
3909 
3910 		BPath tmpPath(settingsPath);
3911 		for (;;) {
3912 			// deal with several levels of folders
3913 			const char* nextSlash = strchr(name, '/');
3914 			if (!nextSlash)
3915 				break;
3916 
3917 			BString tmp;
3918 			tmp.SetTo(name, nextSlash - name);
3919 			tmpPath.Append(tmp.String());
3920 
3921 			mkdir(tmpPath.Path(), 0777);
3922 
3923 			name = nextSlash + 1;
3924 			if (!name[0]) {
3925 				// can't deal with a slash at end
3926 				return false;
3927 			}
3928 		}
3929 
3930 		if (createFolder) {
3931 			if (mkdir(path.Path(), 0777) < 0)
3932 				return false;
3933 		} else {
3934 			BFile file;
3935 			if (dir.CreateFile(name, &file) != B_OK)
3936 				return false;
3937 		}
3938 	}
3939 
3940 	//PRINT(("using default state from %s\n", path.Path()));
3941 	result->SetTo(path.Path());
3942 	return result->InitCheck() == B_OK;
3943 }
3944 
3945 
3946 void
3947 BContainerWindow::SetUpDefaultState()
3948 {
3949 	BNode defaultingNode;
3950 		// this is where we'll ulitimately get the state from
3951 	bool gotDefaultingNode = 0;
3952 	bool shouldStagger = false;
3953 
3954 	ASSERT(TargetModel() != NULL);
3955 
3956 	PRINT(("folder %s does not have any saved state\n", TargetModel()->Name()));
3957 
3958 	WindowStateNodeOpener opener(this, true);
3959 		// this is our destination node, whatever it is for this window
3960 	if (opener.StreamNode() == NULL)
3961 		return;
3962 
3963 	if (!TargetModel()->IsRoot()) {
3964 		BDirectory deskDir;
3965 		FSGetDeskDir(&deskDir);
3966 
3967 		// try copying state from our parent directory, unless it is the
3968 		// desktop folder
3969 		BEntry entry(TargetModel()->EntryRef());
3970 		BNode parent;
3971 		if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK
3972 			&& parent != deskDir) {
3973 			PRINT(("looking at parent for state\n"));
3974 			if (NodeHasSavedState(&parent)) {
3975 				PRINT(("got state from parent\n"));
3976 				defaultingNode = parent;
3977 				gotDefaultingNode = true;
3978 				// when getting state from parent, stagger the window
3979 				shouldStagger = true;
3980 			}
3981 		}
3982 	}
3983 
3984 	if (!gotDefaultingNode
3985 		// parent didn't have any state, use the template directory from
3986 		// tracker settings folder for what our state should be
3987 		// For simplicity we are not picking up the most recent
3988 		// changes that didn't get committed if home is still open in
3989 		// a window, that's probably not a problem; would be OK if state
3990 		// got committed after every change
3991 		&& !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode,
3992 			true)) {
3993 		return;
3994 	}
3995 
3996 	if (fIsDesktop) {
3997 		// don't copy over the attributes if we are the Desktop
3998 		return;
3999 	}
4000 
4001 	// copy over the attributes
4002 
4003 	// set up a filter of the attributes we want copied
4004 	const char* allowAttrs[] = {
4005 		kAttrWindowFrame,
4006 		kAttrWindowWorkspace,
4007 		kAttrViewState,
4008 		kAttrViewStateForeign,
4009 		kAttrColumns,
4010 		kAttrColumnsForeign,
4011 		0
4012 	};
4013 
4014 	// copy over attributes that apply; transform them properly, stripping
4015 	// parts that do not apply, adding a window stagger, etc.
4016 
4017 	StaggerOneParams params;
4018 	params.rectFromParent = shouldStagger;
4019 	SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame,
4020 		OffsetFrameOne, &params);
4021 	SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState,
4022 		ClearViewOriginOne, &params);
4023 
4024 	// do it
4025 	AttributeStreamMemoryNode memoryNode;
4026 	NamesToAcceptAttrFilter filter(allowAttrs);
4027 	AttributeStreamFileNode fileNode(&defaultingNode);
4028 
4029 	*opener.StreamNode() << scrollOriginCleaner << frameOffsetter
4030 		<< memoryNode << filter << fileNode;
4031 }
4032 
4033 
4034 void
4035 BContainerWindow::RestoreWindowState(AttributeStreamNode* node)
4036 {
4037 	if (node == NULL || fIsDesktop) {
4038 		// don't restore any window state if we are the Desktop
4039 		return;
4040 	}
4041 
4042 	const char* rectAttributeName;
4043 	const char* workspaceAttributeName;
4044 	if (TargetModel()->IsRoot()) {
4045 		rectAttributeName = kAttrDisksFrame;
4046 		workspaceAttributeName = kAttrDisksWorkspace;
4047 	} else {
4048 		rectAttributeName = kAttrWindowFrame;
4049 		workspaceAttributeName = kAttrWindowWorkspace;
4050 	}
4051 
4052 	BRect frame(Frame());
4053 	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
4054 			== sizeof(BRect)) {
4055 		const float scalingFactor = be_plain_font->Size() / 12.0f;
4056 		frame.left *= scalingFactor;
4057 		frame.top *= scalingFactor;
4058 		frame.right *= scalingFactor;
4059 		frame.bottom *= scalingFactor;
4060 
4061 		MoveTo(frame.LeftTop());
4062 		ResizeTo(frame.Width(), frame.Height());
4063 	} else
4064 		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
4065 
4066 	fPreviousBounds = Bounds();
4067 
4068 	uint32 workspace;
4069 	if (((fContainerWindowFlags & kRestoreWorkspace) != 0)
4070 		&& node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
4071 			&workspace) == sizeof(uint32))
4072 		SetWorkspaces(workspace);
4073 
4074 	if ((fContainerWindowFlags & kIsHidden) != 0)
4075 		Minimize(true);
4076 
4077 	// restore window decor settings
4078 	int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE);
4079 	if (size > 0) {
4080 		char buffer[size];
4081 		if (((fContainerWindowFlags & kRestoreDecor) != 0)
4082 			&& node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer)
4083 				== size) {
4084 			BMessage decorSettings;
4085 			if (decorSettings.Unflatten(buffer) == B_OK)
4086 				SetDecoratorSettings(decorSettings);
4087 		}
4088 	}
4089 }
4090 
4091 
4092 void
4093 BContainerWindow::RestoreWindowState(const BMessage& message)
4094 {
4095 	if (fIsDesktop) {
4096 		// don't restore any window state if we are the Desktop
4097 		return;
4098 	}
4099 
4100 	const char* rectAttributeName;
4101 	const char* workspaceAttributeName;
4102 	if (TargetModel()->IsRoot()) {
4103 		rectAttributeName = kAttrDisksFrame;
4104 		workspaceAttributeName = kAttrDisksWorkspace;
4105 	} else {
4106 		rectAttributeName = kAttrWindowFrame;
4107 		workspaceAttributeName = kAttrWindowWorkspace;
4108 	}
4109 
4110 	BRect frame(Frame());
4111 	if (message.FindRect(rectAttributeName, &frame) == B_OK) {
4112 		const float scalingFactor = be_plain_font->Size() / 12.0f;
4113 		frame.left *= scalingFactor;
4114 		frame.top *= scalingFactor;
4115 		frame.right *= scalingFactor;
4116 		frame.bottom *= scalingFactor;
4117 
4118 		MoveTo(frame.LeftTop());
4119 		ResizeTo(frame.Width(), frame.Height());
4120 	} else
4121 		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
4122 
4123 	uint32 workspace;
4124 	if ((fContainerWindowFlags & kRestoreWorkspace)
4125 		&& message.FindInt32(workspaceAttributeName,
4126 			(int32*)&workspace) == B_OK) {
4127 		SetWorkspaces(workspace);
4128 	}
4129 
4130 	if (fContainerWindowFlags & kIsHidden)
4131 		Minimize(true);
4132 
4133 	// restore window decor settings
4134 	BMessage decorSettings;
4135 	if ((fContainerWindowFlags & kRestoreDecor)
4136 		&& message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) {
4137 		SetDecoratorSettings(decorSettings);
4138 	}
4139 
4140 	fStateNeedsSaving = false;
4141 		// Undo the effect of the above MoveTo and ResizeTo calls
4142 }
4143 
4144 
4145 void
4146 BContainerWindow::SaveWindowState(AttributeStreamNode* node)
4147 {
4148 	if (fIsDesktop) {
4149 		// don't save window state if we are the Desktop
4150 		return;
4151 	}
4152 
4153 	ASSERT(node != NULL);
4154 
4155 	const char* rectAttributeName;
4156 	const char* workspaceAttributeName;
4157 	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
4158 		rectAttributeName = kAttrDisksFrame;
4159 		workspaceAttributeName = kAttrDisksWorkspace;
4160 	} else {
4161 		rectAttributeName = kAttrWindowFrame;
4162 		workspaceAttributeName = kAttrWindowWorkspace;
4163 	}
4164 
4165 	// node is null if it already got deleted
4166 	BRect frame(Frame());
4167 	const float scalingFactor = be_plain_font->Size() / 12.0f;
4168 	frame.left /= scalingFactor;
4169 	frame.top /= scalingFactor;
4170 	frame.right /= scalingFactor;
4171 	frame.bottom /= scalingFactor;
4172 	node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame);
4173 
4174 	uint32 workspaces = Workspaces();
4175 	node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
4176 		&workspaces);
4177 
4178 	BMessage decorSettings;
4179 	if (GetDecoratorSettings(&decorSettings) == B_OK) {
4180 		int32 size = decorSettings.FlattenedSize();
4181 		char buffer[size];
4182 		if (decorSettings.Flatten(buffer, size) == B_OK) {
4183 			node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer);
4184 		}
4185 	}
4186 }
4187 
4188 
4189 void
4190 BContainerWindow::SaveWindowState(BMessage& message) const
4191 {
4192 	const char* rectAttributeName;
4193 	const char* workspaceAttributeName;
4194 
4195 	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
4196 		rectAttributeName = kAttrDisksFrame;
4197 		workspaceAttributeName = kAttrDisksWorkspace;
4198 	} else {
4199 		rectAttributeName = kAttrWindowFrame;
4200 		workspaceAttributeName = kAttrWindowWorkspace;
4201 	}
4202 
4203 	// node is null if it already got deleted
4204 	BRect frame(Frame());
4205 	const float scalingFactor = be_plain_font->Size() / 12.0f;
4206 	frame.left /= scalingFactor;
4207 	frame.top /= scalingFactor;
4208 	frame.right /= scalingFactor;
4209 	frame.bottom /= scalingFactor;
4210 	message.AddRect(rectAttributeName, frame);
4211 	message.AddInt32(workspaceAttributeName, (int32)Workspaces());
4212 
4213 	BMessage decorSettings;
4214 	if (GetDecoratorSettings(&decorSettings) == B_OK) {
4215 		message.AddMessage(kAttrWindowDecor, &decorSettings);
4216 	}
4217 }
4218 
4219 
4220 status_t
4221 BContainerWindow::DragStart(const BMessage* dragMessage)
4222 {
4223 	if (dragMessage == NULL)
4224 		return B_ERROR;
4225 
4226 	// if already dragging, or
4227 	// if all the refs match
4228 	if (Dragging()
4229 		&& SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) {
4230 		return B_OK;
4231 	}
4232 
4233 	// cache the current drag message
4234 	// build a list of the mimetypes in the message
4235 	SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage,
4236 		&fCachedTypesList);
4237 
4238 	fWaitingForRefs = true;
4239 
4240 	return B_OK;
4241 }
4242 
4243 
4244 void
4245 BContainerWindow::DragStop()
4246 {
4247 	delete fDragMessage;
4248 	fDragMessage = NULL;
4249 
4250 	delete fCachedTypesList;
4251 	fCachedTypesList = NULL;
4252 
4253 	fWaitingForRefs = false;
4254 }
4255 
4256 
4257 void
4258 BContainerWindow::ShowSelectionWindow()
4259 {
4260 	if (fSelectionWindow == NULL) {
4261 		fSelectionWindow = new SelectionWindow(this);
4262 		fSelectionWindow->Show();
4263 	} else if (fSelectionWindow->Lock()) {
4264 		// The window is already there, just bring it close
4265 		fSelectionWindow->MoveCloseToMouse();
4266 		if (fSelectionWindow->IsHidden())
4267 			fSelectionWindow->Show();
4268 
4269 		fSelectionWindow->Unlock();
4270 	}
4271 }
4272 
4273 
4274 void
4275 BContainerWindow::ShowNavigator(bool show)
4276 {
4277 	if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory()
4278 		|| fPoseView->IsFilePanel()) {
4279 		return;
4280 	}
4281 
4282 	if (show) {
4283 		if (Navigator() && !Navigator()->IsHidden())
4284 			return;
4285 
4286 		if (Navigator() == NULL) {
4287 			fNavigator = new BNavigator(TargetModel());
4288 			fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
4289 		}
4290 
4291 		if (Navigator()->IsHidden())
4292 			Navigator()->Show();
4293 
4294 		if (PoseView()->VScrollBar())
4295 			PoseView()->UpdateScrollRange();
4296 	} else {
4297 		if (!Navigator() || Navigator()->IsHidden())
4298 			return;
4299 
4300 		if (PoseView()->VScrollBar())
4301 			PoseView()->UpdateScrollRange();
4302 
4303 		fNavigator->Hide();
4304 	}
4305 }
4306 
4307 
4308 void
4309 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled)
4310 {
4311 	if (PoseView()->IsDesktopWindow())
4312 		return;
4313 
4314 	if (enabled) {
4315 		if (!Navigator())
4316 			return;
4317 
4318 		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4319 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4320 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4321 
4322 		AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY,
4323 			new BMessage(kNavigatorCommandBackward), Navigator());
4324 		AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY,
4325 			new BMessage(kNavigatorCommandForward), Navigator());
4326 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4327 			new BMessage(kNavigatorCommandUp), Navigator());
4328 
4329 		AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4330 			new BMessage(kNavigatorCommandBackward), Navigator());
4331 		AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4332 			new BMessage(kNavigatorCommandForward), Navigator());
4333 		AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4334 			new BMessage(kNavigatorCommandUp), Navigator());
4335 		AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4336 			new BMessage(kOpenSelection), PoseView());
4337 		AddShortcut('L', B_COMMAND_KEY,
4338 			new BMessage(kNavigatorCommandSetFocus), Navigator());
4339 
4340 	} else {
4341 		RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
4342 		RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
4343 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4344 			// This is added again, below, with a new meaning.
4345 
4346 		RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4347 		RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4348 		RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4349 		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4350 			// This also changes meaning, added again below.
4351 
4352 		AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4353 			new BMessage(kOpenSelection), PoseView());
4354 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4355 			new BMessage(kOpenParentDir), PoseView());
4356 			// We change the meaning from kNavigatorCommandUp
4357 			// to kOpenParentDir.
4358 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4359 			new BMessage(kOpenParentDir), PoseView());
4360 			// command + option results in closing the parent window
4361 		RemoveShortcut('L', B_COMMAND_KEY);
4362 	}
4363 }
4364 
4365 
4366 void
4367 BContainerWindow::SetPathWatchingEnabled(bool enable)
4368 {
4369 	if (IsPathWatchingEnabled()) {
4370 		stop_watching(this);
4371 		fIsWatchingPath = false;
4372 	}
4373 
4374 	if (enable) {
4375 		if (TargetModel() != NULL) {
4376 			BEntry entry;
4377 
4378 			TargetModel()->GetEntry(&entry);
4379 			status_t err;
4380 			do {
4381 				err = entry.GetParent(&entry);
4382 				if (err != B_OK)
4383 					break;
4384 
4385 				char name[B_FILE_NAME_LENGTH];
4386 				entry.GetName(name);
4387 				if (strcmp(name, "/") == 0)
4388 					break;
4389 
4390 				node_ref ref;
4391 				entry.GetNodeRef(&ref);
4392 				watch_node(&ref, B_WATCH_NAME, this);
4393 			} while (err == B_OK);
4394 
4395 			fIsWatchingPath = err == B_OK;
4396 		} else
4397 			fIsWatchingPath = false;
4398 	}
4399 }
4400 
4401 
4402 void
4403 BContainerWindow::PulseTaskLoop()
4404 {
4405 	if (fTaskLoop)
4406 		fTaskLoop->PulseMe();
4407 }
4408 
4409 
4410 void
4411 BContainerWindow::PopulateArrangeByMenu(BMenu* menu)
4412 {
4413 	if (!fAttrMenu || !menu)
4414 		return;
4415 	// empty fArrangeByMenu...
4416 	BMenuItem* item;
4417 	while ((item = menu->RemoveItem((int32)0)) != NULL)
4418 		delete item;
4419 
4420 	int32 itemCount = fAttrMenu->CountItems();
4421 	for (int32 i = 0; i < itemCount; i++) {
4422 		item = fAttrMenu->ItemAt(i);
4423 		if (item->Command() == kAttributeItem) {
4424 			BMessage* message = new BMessage(*(item->Message()));
4425 			message->what = kArrangeBy;
4426 			BMenuItem* newItem = new BMenuItem(item->Label(), message);
4427 			newItem->SetTarget(PoseView());
4428 			menu->AddItem(newItem);
4429 		}
4430 	}
4431 
4432 	menu->AddSeparatorItem();
4433 
4434 	item = new BMenuItem(B_TRANSLATE("Reverse order"),
4435 		new BMessage(kArrangeReverseOrder));
4436 
4437 	item->SetTarget(PoseView());
4438 	menu->AddItem(item);
4439 
4440 	menu->AddSeparatorItem();
4441 
4442 	item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K');
4443 	item->SetTarget(PoseView());
4444 	menu->AddItem(item);
4445 }
4446 
4447 
4448 //	#pragma mark - WindowStateNodeOpener
4449 
4450 
4451 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window,
4452 	bool forWriting)
4453 	:
4454 	fModelOpener(NULL),
4455 	fNode(NULL),
4456 	fStreamNode(NULL)
4457 {
4458 	if (window->TargetModel() && window->TargetModel()->IsRoot()) {
4459 		BDirectory dir;
4460 		if (FSGetDeskDir(&dir) == B_OK) {
4461 			fNode = new BDirectory(dir);
4462 			fStreamNode = new AttributeStreamFileNode(fNode);
4463 		}
4464 	} else if (window->TargetModel()){
4465 		fModelOpener = new ModelNodeLazyOpener(window->TargetModel(),
4466 			forWriting, false);
4467 		if (fModelOpener->IsOpen(forWriting)) {
4468 			fStreamNode = new AttributeStreamFileNode(
4469 				fModelOpener->TargetModel()->Node());
4470 		}
4471 	}
4472 }
4473 
4474 WindowStateNodeOpener::~WindowStateNodeOpener()
4475 {
4476 	delete fModelOpener;
4477 	delete fNode;
4478 	delete fStreamNode;
4479 }
4480 
4481 
4482 void
4483 WindowStateNodeOpener::SetTo(const BDirectory* node)
4484 {
4485 	delete fModelOpener;
4486 	delete fNode;
4487 	delete fStreamNode;
4488 
4489 	fModelOpener = NULL;
4490 	fNode = new BDirectory(*node);
4491 	fStreamNode = new AttributeStreamFileNode(fNode);
4492 }
4493 
4494 
4495 void
4496 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting)
4497 {
4498 	delete fModelOpener;
4499 	delete fNode;
4500 	delete fStreamNode;
4501 
4502 	fModelOpener = NULL;
4503 	fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY));
4504 	fStreamNode = new AttributeStreamFileNode(fNode);
4505 }
4506 
4507 
4508 void
4509 WindowStateNodeOpener::SetTo(Model* model, bool forWriting)
4510 {
4511 	delete fModelOpener;
4512 	delete fNode;
4513 	delete fStreamNode;
4514 
4515 	fNode = NULL;
4516 	fStreamNode = NULL;
4517 	fModelOpener = new ModelNodeLazyOpener(model, forWriting, false);
4518 	if (fModelOpener->IsOpen(forWriting)) {
4519 		fStreamNode = new AttributeStreamFileNode(
4520 			fModelOpener->TargetModel()->Node());
4521 	}
4522 }
4523 
4524 
4525 AttributeStreamNode*
4526 WindowStateNodeOpener::StreamNode() const
4527 {
4528 	return fStreamNode;
4529 }
4530 
4531 
4532 BNode*
4533 WindowStateNodeOpener::Node() const
4534 {
4535 	if (!fStreamNode)
4536 		return NULL;
4537 
4538 	if (fNode)
4539 		return fNode;
4540 
4541 	return fModelOpener->TargetModel()->Node();
4542 }
4543 
4544 
4545 //	#pragma mark - BorderedView
4546 
4547 
4548 BorderedView::BorderedView()
4549 	:
4550 	BGroupView(B_VERTICAL, 0),
4551 	fEnableBorderHighlight(true)
4552 {
4553 	GroupLayout()->SetInsets(1);
4554 }
4555 
4556 
4557 void
4558 BorderedView::WindowActivated(bool active)
4559 {
4560 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4561 	if (window == NULL)
4562 		return;
4563 
4564 	if (window->PoseView()->IsFocus())
4565 		PoseViewFocused(active); // Update border color
4566 }
4567 
4568 
4569 void BorderedView::EnableBorderHighlight(bool enable)
4570 {
4571 	fEnableBorderHighlight = enable;
4572 	PoseViewFocused(false);
4573 }
4574 
4575 
4576 void
4577 BorderedView::PoseViewFocused(bool focused)
4578 {
4579 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4580 	if (window == NULL)
4581 		return;
4582 
4583 	color_which base = B_DOCUMENT_BACKGROUND_COLOR;
4584 	float tint = B_DARKEN_2_TINT;
4585 	if (focused && window->IsActive() && fEnableBorderHighlight) {
4586 		base = B_KEYBOARD_NAVIGATION_COLOR;
4587 		tint = B_NO_TINT;
4588 	}
4589 
4590 	BScrollBar* hScrollBar = window->PoseView()->HScrollBar();
4591 	if (hScrollBar != NULL)
4592 		hScrollBar->SetBorderHighlighted(focused);
4593 
4594 	BScrollBar* vScrollBar = window->PoseView()->VScrollBar();
4595 	if (vScrollBar != NULL)
4596 		vScrollBar->SetBorderHighlighted(focused);
4597 
4598 	SetViewUIColor(base, tint);
4599 	Invalidate();
4600 }
4601 
4602 
4603 void
4604 BorderedView::Pulse()
4605 {
4606 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4607 	if (window != NULL)
4608 		window->PulseTaskLoop();
4609 }
4610