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