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