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