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