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