xref: /haiku/src/kits/tracker/ContainerWindow.cpp (revision f758e73fe6df01190d54716802d51635b609e1fd)
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 		AutoLock<BLooper> _(parentMenu->Looper());
2991 		parentMenu->GetFont(&font);
2992 	}
2993 
2994 	BMenu* menu = item->Submenu();
2995 	if (menu == NULL)
2996 		return;
2997 
2998 	menu->SetFont(&font);
2999 
3000 	// found add-ons menu, empty it first
3001 	for (;;) {
3002 		item = menu->RemoveItem((int32)0);
3003 		if (!item)
3004 			break;
3005 		delete item;
3006 	}
3007 
3008 	BObjectList<BMenuItem> primaryList;
3009 	BObjectList<BMenuItem> secondaryList;
3010 	BStringList mimeTypes(10);
3011 	BuildMimeTypeList(mimeTypes);
3012 
3013 	AddOneAddOnParams params;
3014 	params.primaryList = &primaryList;
3015 	params.secondaryList = &secondaryList;
3016 
3017 	// build a list of the MIME types of the selected items
3018 
3019 	EachAddOn(AddOneAddOn, &params, mimeTypes, parentMenu);
3020 
3021 	primaryList.SortItems(CompareLabels);
3022 	secondaryList.SortItems(CompareLabels);
3023 
3024 	int32 count = primaryList.CountItems();
3025 	for (int32 index = 0; index < count; index++)
3026 		menu->AddItem(primaryList.ItemAt(index));
3027 
3028 	if (count > 0)
3029 		menu->AddSeparatorItem();
3030 
3031 	count = secondaryList.CountItems();
3032 	for (int32 index = 0; index < count; index++)
3033 		menu->AddItem(secondaryList.ItemAt(index));
3034 
3035 	menu->SetTargetForItems(this);
3036 }
3037 
3038 
3039 void
3040 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context)
3041 {
3042 	const int32 selectCount = PoseView()->CountSelected();
3043 	const int32 poseCount = PoseView()->CountItems();
3044 
3045 	if (context == kMenuBarContext) {
3046 		EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0);
3047 		EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0);
3048 		EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0);
3049 		EnableNamedMenuItem(menu, kDelete,
3050 			PoseView()->CanMoveToTrashOrDuplicate());
3051 	}
3052 
3053 	if (context == kMenuBarContext || context == kPosePopUpContext) {
3054 		SetupEditQueryItem(menu);
3055 
3056 		EnableNamedMenuItem(menu, kEditItem, PoseView()->CanEditName());
3057 		EnableNamedMenuItem(menu, kMoveToTrash,
3058 			PoseView()->CanMoveToTrashOrDuplicate());
3059 		EnableNamedMenuItem(menu, kDuplicateSelection,
3060 			PoseView()->CanMoveToTrashOrDuplicate());
3061 
3062 		SetCutItem(menu);
3063 		SetCopyItem(menu);
3064 		SetPasteItem(menu);
3065 	}
3066 
3067 	if (context == kMenuBarContext || context == kWindowPopUpContext) {
3068 		uint32 viewMode = PoseView()->ViewMode();
3069 
3070 		BMenu* iconSizeMenu = NULL;
3071 		if (BMenuItem* item = menu->FindItem(kIconMode))
3072 			iconSizeMenu = item->Submenu();
3073 
3074 		if (iconSizeMenu != NULL) {
3075 			if (viewMode == kIconMode) {
3076 				int32 iconSize = PoseView()->UnscaledIconSizeInt();
3077 				BMenuItem* item = iconSizeMenu->ItemAt(0);
3078 				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL;
3079 						i++) {
3080 					BMessage* message = item->Message();
3081 					if (message == NULL) {
3082 						item->SetMarked(false);
3083 						continue;
3084 					}
3085 					int32 size;
3086 					if (message->FindInt32("size", &size) != B_OK)
3087 						size = -1;
3088 					item->SetMarked(iconSize == size);
3089 				}
3090 			} else {
3091 				BMenuItem* item;
3092 				for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++)
3093 					item->SetMarked(false);
3094 			}
3095 		}
3096 
3097 		MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode);
3098 		MarkNamedMenuItem(menu, kListMode, viewMode == kListMode);
3099 		MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode);
3100 
3101 		SetCloseItem(menu);
3102 		SetArrangeMenu(menu);
3103 		SetPasteItem(menu);
3104 
3105 		BEntry entry(TargetModel()->EntryRef());
3106 		BDirectory parent;
3107 		bool parentIsRoot = (entry.GetParent(&parent) == B_OK
3108 			&& parent.GetEntry(&entry) == B_OK
3109 			&& FSIsRootDir(&entry));
3110 
3111 		EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsDesktop()
3112 			&& !TargetModel()->IsRoot()
3113 			&& (!parentIsRoot
3114 				|| TrackerSettings().SingleWindowBrowse()
3115 				|| TrackerSettings().ShowDisksIcon()
3116 				|| (modifiers() & B_CONTROL_KEY) != 0));
3117 
3118 		EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0);
3119 		EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0);
3120 
3121 		BMenuItem* item = menu->FindItem(B_TRANSLATE("New"));
3122 		if (item != NULL) {
3123 			TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>(
3124 				item->Submenu());
3125 			if (templatesMenu != NULL)
3126 				templatesMenu->UpdateMenuState();
3127 		}
3128 	}
3129 
3130 	BuildAddOnsMenu(menu);
3131 }
3132 
3133 
3134 BMessage*
3135 BContainerWindow::AddOnMessage(int32 what)
3136 {
3137 	BMessage* message = new BMessage(what);
3138 
3139 	// add selected refs to message
3140 	BObjectList<BPose>* selectionList = PoseView()->SelectionList();
3141 
3142 	int32 index = 0;
3143 	BPose* pose;
3144 	while ((pose = selectionList->ItemAt(index++)) != NULL)
3145 		message->AddRef("refs", pose->TargetModel()->EntryRef());
3146 
3147 	message->AddRef("dir_ref", TargetModel()->EntryRef());
3148 	message->AddMessenger("TrackerViewToken", BMessenger(PoseView()));
3149 
3150 	return message;
3151 }
3152 
3153 
3154 void
3155 BContainerWindow::LoadAddOn(BMessage* message)
3156 {
3157 	UpdateIfNeeded();
3158 
3159 	entry_ref addOnRef;
3160 	status_t result = message->FindRef("refs", &addOnRef);
3161 	if (result != B_OK) {
3162 		BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
3163 		buffer.ReplaceFirst("%error", strerror(result));
3164 		buffer.ReplaceFirst("%name", addOnRef.name);
3165 
3166 		BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
3167 			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3168 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3169 		alert->Go();
3170 		return;
3171 	}
3172 
3173 	// add selected refs to message
3174 	BMessage* refs = AddOnMessage(B_REFS_RECEIVED);
3175 
3176 	LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs,
3177 		addOnRef, *TargetModel()->EntryRef());
3178 }
3179 
3180 
3181 //	#pragma mark - BContainerWindow private methods
3182 
3183 
3184 void
3185 BContainerWindow::_UpdateSelectionMIMEInfo()
3186 {
3187 	BPose* pose;
3188 	int32 index = 0;
3189 	while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) {
3190 		BString mimeType(pose->TargetModel()->MimeType());
3191 		if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3192 			pose->TargetModel()->Mimeset(true);
3193 			if (pose->TargetModel()->IsSymLink()) {
3194 				Model* resolved = new Model(pose->TargetModel()->EntryRef(),
3195 					true, true);
3196 				if (resolved->InitCheck() == B_OK) {
3197 					mimeType.SetTo(resolved->MimeType());
3198 					if (!mimeType.Length()
3199 						|| mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3200 						resolved->Mimeset(true);
3201 					}
3202 				}
3203 				delete resolved;
3204 			}
3205 		}
3206 	}
3207 }
3208 
3209 
3210 void
3211 BContainerWindow::_AddFolderIcon()
3212 {
3213 	if (fMenuBar == NULL) {
3214 		// We don't want to add the icon if there's no menubar
3215 		return;
3216 	}
3217 
3218 	float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1,
3219 		iconSize = fMenuBar->Bounds().Height() - 2;
3220 	if (iconSize < baseIconSize)
3221 		iconSize = baseIconSize;
3222 
3223 	fDraggableIcon = new(std::nothrow)
3224 		DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1));
3225 	if (fDraggableIcon != NULL) {
3226 		fMenuContainer->GroupLayout()->AddView(fDraggableIcon);
3227 		fMenuBar->SetBorders(
3228 			BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER);
3229 	}
3230 }
3231 
3232 
3233 void
3234 BContainerWindow::_PassMessageToAddOn(BMessage* message)
3235 {
3236 	LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY,
3237 		&RunAddOnMessageThread, new BMessage(*message), (void*)NULL);
3238 }
3239 
3240 
3241 BMenuItem*
3242 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3243 	int32 type, float width, int32 align, bool editable, bool statField)
3244 {
3245 	return NewAttributeMenuItem(label, name, type, NULL, width, align,
3246 		editable, statField);
3247 }
3248 
3249 
3250 BMenuItem*
3251 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3252 	int32 type, const char* displayAs, float width, int32 align,
3253 	bool editable, bool statField)
3254 {
3255 	BMessage* message = new BMessage(kAttributeItem);
3256 	message->AddString("attr_name", name);
3257 	message->AddInt32("attr_type", type);
3258 	message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type));
3259 	message->AddFloat("attr_width", width);
3260 	message->AddInt32("attr_align", align);
3261 	if (displayAs != NULL)
3262 		message->AddString("attr_display_as", displayAs);
3263 	message->AddBool("attr_editable", editable);
3264 	message->AddBool("attr_statfield", statField);
3265 
3266 	BMenuItem* menuItem = new BMenuItem(label, message);
3267 	menuItem->SetTarget(PoseView());
3268 
3269 	return menuItem;
3270 }
3271 
3272 
3273 void
3274 BContainerWindow::NewAttributesMenu(BMenu* menu)
3275 {
3276 	ASSERT(PoseView());
3277 
3278 	BMenuItem* item;
3279 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"),
3280 		new BMessage(kCopyAttributes)));
3281 	item->SetTarget(PoseView());
3282 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"),
3283 		new BMessage(kPasteAttributes)));
3284 	item->SetTarget(PoseView());
3285 	menu->AddSeparatorItem();
3286 
3287 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"),
3288 		kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3289 
3290 	if (gLocalizedNamePreferred) {
3291 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"),
3292 			kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3293 	}
3294 
3295 	menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize,
3296 		B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true));
3297 
3298 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"),
3299 		kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3300 
3301 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"),
3302 		kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3303 
3304 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"),
3305 		kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false));
3306 
3307 	if (IsTrash() || InTrash()) {
3308 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"),
3309 			kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false,
3310 			false));
3311 	} else {
3312 		menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath,
3313 			B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false));
3314 	}
3315 
3316 #ifdef OWNER_GROUP_ATTRIBUTES
3317 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner,
3318 		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3319 
3320 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup,
3321 		B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3322 #endif
3323 
3324 	menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"),
3325 		kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true));
3326 }
3327 
3328 
3329 void
3330 BContainerWindow::ShowAttributesMenu()
3331 {
3332 	ASSERT(fAttrMenu);
3333 	fMenuBar->AddItem(fAttrMenu);
3334 }
3335 
3336 
3337 void
3338 BContainerWindow::HideAttributesMenu()
3339 {
3340 	ASSERT(fAttrMenu);
3341 	fMenuBar->RemoveItem(fAttrMenu);
3342 }
3343 
3344 
3345 void
3346 BContainerWindow::MarkAttributesMenu()
3347 {
3348 	MarkAttributesMenu(fAttrMenu);
3349 }
3350 
3351 
3352 void
3353 BContainerWindow::MarkAttributesMenu(BMenu* menu)
3354 {
3355 	if (menu == NULL)
3356 		return;
3357 
3358 	int32 count = menu->CountItems();
3359 	for (int32 index = 0; index < count; index++) {
3360 		BMenuItem* item = menu->ItemAt(index);
3361 		int32 attrHash;
3362 		if (item->Message() != NULL) {
3363 			if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK)
3364 				item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0);
3365 			else
3366 				item->SetMarked(false);
3367 		}
3368 
3369 		BMenu* submenu = item->Submenu();
3370 		if (submenu != NULL) {
3371 			int32 count2 = submenu->CountItems();
3372 			for (int32 subindex = 0; subindex < count2; subindex++) {
3373 				item = submenu->ItemAt(subindex);
3374 				if (item->Message() != NULL) {
3375 					if (item->Message()->FindInt32("attr_hash", &attrHash)
3376 						== B_OK) {
3377 						item->SetMarked(PoseView()->ColumnFor((uint32)attrHash)
3378 							!= 0);
3379 					} else
3380 						item->SetMarked(false);
3381 				}
3382 			}
3383 		}
3384 	}
3385 }
3386 
3387 
3388 void
3389 BContainerWindow::MarkArrangeByMenu(BMenu* menu)
3390 {
3391 	if (menu == NULL)
3392 		return;
3393 
3394 	int32 count = menu->CountItems();
3395 	for (int32 index = 0; index < count; index++) {
3396 		BMenuItem* item = menu->ItemAt(index);
3397 		if (item->Message() != NULL) {
3398 			uint32 attrHash;
3399 			if (item->Message()->FindInt32("attr_hash",
3400 					(int32*)&attrHash) == B_OK) {
3401 				item->SetMarked(PoseView()->PrimarySort() == attrHash);
3402 			} else if (item->Command() == kArrangeReverseOrder)
3403 				item->SetMarked(PoseView()->ReverseSort());
3404 		}
3405 	}
3406 }
3407 
3408 
3409 void
3410 BContainerWindow::AddMimeTypesToMenu()
3411 {
3412 	AddMimeTypesToMenu(fAttrMenu);
3413 }
3414 
3415 
3416 // Adds a menu for a specific MIME type if it doesn't exist already.
3417 // Returns the menu, if it existed or not.
3418 BMenu*
3419 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType,
3420 	BMenu* menu, int32 start)
3421 {
3422 	AutoLock<BLooper> _(menu->Looper());
3423 
3424 	if (!mimeType.IsValid())
3425 		return NULL;
3426 
3427 	// Check if we already have an entry for this MIME type in the menu.
3428 	for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) {
3429 		BMessage* message = item->Message();
3430 		if (message == NULL)
3431 			continue;
3432 
3433 		const char* type;
3434 		if (message->FindString("mimetype", &type) == B_OK
3435 			&& !strcmp(mimeType.Type(), type)) {
3436 			return item->Submenu();
3437 		}
3438 	}
3439 
3440 	BMessage attrInfo;
3441 	char description[B_MIME_TYPE_LENGTH];
3442 	const char* label = mimeType.Type();
3443 
3444 	if (!mimeType.IsInstalled())
3445 		return NULL;
3446 
3447 	// only add things to menu which have "user-visible" data
3448 	if (mimeType.GetAttrInfo(&attrInfo) != B_OK)
3449 		return NULL;
3450 
3451 	if (mimeType.GetShortDescription(description) == B_OK && description[0])
3452 		label = description;
3453 
3454 	// go through each field in meta mime and add it to a menu
3455 	BMenu* mimeMenu = NULL;
3456 	if (isSuperType) {
3457 		// If it is a supertype, we create the menu anyway as it may have
3458 		// submenus later on.
3459 		mimeMenu = new BMenu(label);
3460 		BFont font;
3461 		menu->GetFont(&font);
3462 		mimeMenu->SetFont(&font);
3463 	}
3464 
3465 	int32 index = -1;
3466 	const char* publicName;
3467 	while (attrInfo.FindString("attr:public_name", ++index, &publicName)
3468 			== B_OK) {
3469 		if (!attrInfo.FindBool("attr:viewable", index)) {
3470 			// don't add if attribute not viewable
3471 			continue;
3472 		}
3473 
3474 		int32 type;
3475 		int32 align;
3476 		int32 width;
3477 		bool editable;
3478 		const char* attrName;
3479 		if (attrInfo.FindString("attr:name", index, &attrName) != B_OK
3480 			|| attrInfo.FindInt32("attr:type", index, &type) != B_OK
3481 			|| attrInfo.FindBool("attr:editable", index, &editable) != B_OK
3482 			|| attrInfo.FindInt32("attr:width", index, &width) != B_OK
3483 			|| attrInfo.FindInt32("attr:alignment", index, &align) != B_OK)
3484 			continue;
3485 
3486 		BString displayAs;
3487 		attrInfo.FindString("attr:display_as", index, &displayAs);
3488 
3489 		if (mimeMenu == NULL) {
3490 			// do a lazy allocation of the menu
3491 			mimeMenu = new BMenu(label);
3492 			BFont font;
3493 			menu->GetFont(&font);
3494 			mimeMenu->SetFont(&font);
3495 		}
3496 		mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type,
3497 			displayAs.String(), width, align, editable, false));
3498 	}
3499 
3500 	if (mimeMenu == NULL)
3501 		return NULL;
3502 
3503 	BMessage* message = new BMessage(kMIMETypeItem);
3504 	message->AddString("mimetype", mimeType.Type());
3505 	menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type()));
3506 
3507 	return mimeMenu;
3508 }
3509 
3510 
3511 void
3512 BContainerWindow::AddMimeTypesToMenu(BMenu* menu)
3513 {
3514 	if (menu == NULL)
3515 		return;
3516 
3517 	// Remove old mime type menus
3518 	int32 start = menu->CountItems();
3519 	while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) {
3520 		delete menu->RemoveItem(start - 1);
3521 		start--;
3522 	}
3523 
3524 	// Add a separator item if there is none yet
3525 	if (start > 0
3526 		&& dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL)
3527 		menu->AddSeparatorItem();
3528 
3529 	// Add MIME type in case we're a default query type window
3530 	BPath path;
3531 	if (TargetModel() != NULL) {
3532 		TargetModel()->GetPath(&path);
3533 		if (path.InitCheck() == B_OK
3534 			&& strstr(path.Path(), "/" kQueryTemplates "/") != NULL) {
3535 			// demangle MIME type name
3536 			BString name(TargetModel()->Name());
3537 			name.ReplaceFirst('_', '/');
3538 
3539 			PoseView()->AddMimeType(name.String());
3540 		}
3541 	}
3542 
3543 	// Add MIME type menus
3544 
3545 	int32 typeCount = PoseView()->CountMimeTypes();
3546 
3547 	for (int32 index = 0; index < typeCount; index++) {
3548 		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3549 		if (mimeType.InitCheck() == B_OK) {
3550 			BMimeType superType;
3551 			mimeType.GetSupertype(&superType);
3552 			if (superType.InitCheck() == B_OK) {
3553 				BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3554 				if (superMenu != NULL) {
3555 					// We have a supertype menu.
3556 					AddMimeMenu(mimeType, false, superMenu, 0);
3557 				}
3558 			}
3559 		}
3560 	}
3561 
3562 	// remove empty super menus, promote sub-types if needed
3563 
3564 	for (int32 index = 0; index < typeCount; index++) {
3565 		BMimeType mimeType(PoseView()->MimeTypeAt(index));
3566 		BMimeType superType;
3567 		mimeType.GetSupertype(&superType);
3568 
3569 		BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3570 		if (superMenu == NULL)
3571 			continue;
3572 
3573 		int32 itemsFound = 0;
3574 		int32 menusFound = 0;
3575 		for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) {
3576 			if (item->Submenu() != NULL)
3577 				menusFound++;
3578 			else
3579 				itemsFound++;
3580 		}
3581 
3582 		if (itemsFound == 0) {
3583 			if (menusFound != 0) {
3584 				// promote types to the top level
3585 				while (BMenuItem* item = superMenu->RemoveItem((int32)0)) {
3586 					menu->AddItem(item);
3587 				}
3588 			}
3589 
3590 			menu->RemoveItem(superMenu->Superitem());
3591 			delete superMenu->Superitem();
3592 		}
3593 	}
3594 
3595 	// remove separator if it's the only item in menu
3596 	BMenuItem* item = menu->ItemAt(menu->CountItems() - 1);
3597 	if (dynamic_cast<BSeparatorItem*>(item) != NULL) {
3598 		menu->RemoveItem(item);
3599 		delete item;
3600 	}
3601 
3602 	MarkAttributesMenu(menu);
3603 }
3604 
3605 
3606 BHandler*
3607 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index,
3608 	BMessage* specifier, int32 form, const char* property)
3609 {
3610 	if (strcmp(property, "Poses") == 0) {
3611 //		PRINT(("BContainerWindow::ResolveSpecifier %s\n", property));
3612 		message->PopSpecifier();
3613 		return PoseView();
3614 	}
3615 
3616 	return _inherited::ResolveSpecifier(message, index, specifier,
3617 		form, property);
3618 }
3619 
3620 
3621 PiggybackTaskLoop*
3622 BContainerWindow::DelayedTaskLoop()
3623 {
3624 	if (!fTaskLoop)
3625 		fTaskLoop = new PiggybackTaskLoop;
3626 
3627 	return fTaskLoop;
3628 }
3629 
3630 
3631 bool
3632 BContainerWindow::NeedsDefaultStateSetup()
3633 {
3634 	if (TargetModel() == NULL)
3635 		return false;
3636 
3637 	if (TargetModel()->IsRoot()) {
3638 		// don't try to set up anything if we are root
3639 		return false;
3640 	}
3641 
3642 	WindowStateNodeOpener opener(this, false);
3643 	if (opener.StreamNode() == NULL) {
3644 		// can't read state, give up
3645 		return false;
3646 	}
3647 
3648 	return !NodeHasSavedState(opener.Node());
3649 }
3650 
3651 
3652 bool
3653 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result,
3654 	bool createNew, bool createFolder)
3655 {
3656 	//PRINT(("looking for default state in tracker settings dir\n"));
3657 	BPath settingsPath;
3658 	if (FSFindTrackerSettingsDir(&settingsPath) != B_OK)
3659 		return false;
3660 
3661 	BDirectory dir(settingsPath.Path());
3662 
3663 	BPath path(settingsPath);
3664 	path.Append(name);
3665 	if (!BEntry(path.Path()).Exists()) {
3666 		if (!createNew)
3667 			return false;
3668 
3669 		BPath tmpPath(settingsPath);
3670 		for (;;) {
3671 			// deal with several levels of folders
3672 			const char* nextSlash = strchr(name, '/');
3673 			if (!nextSlash)
3674 				break;
3675 
3676 			BString tmp;
3677 			tmp.SetTo(name, nextSlash - name);
3678 			tmpPath.Append(tmp.String());
3679 
3680 			mkdir(tmpPath.Path(), 0777);
3681 
3682 			name = nextSlash + 1;
3683 			if (!name[0]) {
3684 				// can't deal with a slash at end
3685 				return false;
3686 			}
3687 		}
3688 
3689 		if (createFolder) {
3690 			if (mkdir(path.Path(), 0777) < 0)
3691 				return false;
3692 		} else {
3693 			BFile file;
3694 			if (dir.CreateFile(name, &file) != B_OK)
3695 				return false;
3696 		}
3697 	}
3698 
3699 	//PRINT(("using default state from %s\n", path.Path()));
3700 	result->SetTo(path.Path());
3701 	return result->InitCheck() == B_OK;
3702 }
3703 
3704 
3705 void
3706 BContainerWindow::SetupDefaultState()
3707 {
3708 	BNode defaultingNode;
3709 		// this is where we'll ulitimately get the state from
3710 	bool gotDefaultingNode = 0;
3711 	bool shouldStagger = false;
3712 
3713 	ASSERT(TargetModel() != NULL);
3714 
3715 	PRINT(("folder %s does not have any saved state\n", TargetModel()->Name()));
3716 
3717 	WindowStateNodeOpener opener(this, true);
3718 		// this is our destination node, whatever it is for this window
3719 	if (opener.StreamNode() == NULL)
3720 		return;
3721 
3722 	if (!TargetModel()->IsRoot()) {
3723 		BDirectory deskDir;
3724 		FSGetDeskDir(&deskDir);
3725 
3726 		// try copying state from our parent directory, unless it is the
3727 		// desktop folder
3728 		BEntry entry(TargetModel()->EntryRef());
3729 		BNode parent;
3730 		if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK
3731 			&& parent != deskDir) {
3732 			PRINT(("looking at parent for state\n"));
3733 			if (NodeHasSavedState(&parent)) {
3734 				PRINT(("got state from parent\n"));
3735 				defaultingNode = parent;
3736 				gotDefaultingNode = true;
3737 				// when getting state from parent, stagger the window
3738 				shouldStagger = true;
3739 			}
3740 		}
3741 	}
3742 
3743 	if (!gotDefaultingNode
3744 		// parent didn't have any state, use the template directory from
3745 		// tracker settings folder for what our state should be
3746 		// For simplicity we are not picking up the most recent
3747 		// changes that didn't get committed if home is still open in
3748 		// a window, that's probably not a problem; would be OK if state
3749 		// got committed after every change
3750 		&& !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode,
3751 			true)) {
3752 		return;
3753 	}
3754 
3755 	if (fIsDesktop) {
3756 		// don't copy over the attributes if we are the Desktop
3757 		return;
3758 	}
3759 
3760 	// copy over the attributes
3761 
3762 	// set up a filter of the attributes we want copied
3763 	const char* allowAttrs[] = {
3764 		kAttrWindowFrame,
3765 		kAttrWindowWorkspace,
3766 		kAttrViewState,
3767 		kAttrViewStateForeign,
3768 		kAttrColumns,
3769 		kAttrColumnsForeign,
3770 		0
3771 	};
3772 
3773 	// copy over attributes that apply; transform them properly, stripping
3774 	// parts that do not apply, adding a window stagger, etc.
3775 
3776 	StaggerOneParams params;
3777 	params.rectFromParent = shouldStagger;
3778 	SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame,
3779 		OffsetFrameOne, &params);
3780 	SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState,
3781 		ClearViewOriginOne, &params);
3782 
3783 	// do it
3784 	AttributeStreamMemoryNode memoryNode;
3785 	NamesToAcceptAttrFilter filter(allowAttrs);
3786 	AttributeStreamFileNode fileNode(&defaultingNode);
3787 
3788 	*opener.StreamNode() << scrollOriginCleaner << frameOffsetter
3789 		<< memoryNode << filter << fileNode;
3790 }
3791 
3792 
3793 void
3794 BContainerWindow::RestoreWindowState(AttributeStreamNode* node)
3795 {
3796 	if (node == NULL || fIsDesktop) {
3797 		// don't restore any window state if we are the Desktop
3798 		return;
3799 	}
3800 
3801 	const char* rectAttributeName;
3802 	const char* workspaceAttributeName;
3803 	if (TargetModel()->IsRoot()) {
3804 		rectAttributeName = kAttrDisksFrame;
3805 		workspaceAttributeName = kAttrDisksWorkspace;
3806 	} else {
3807 		rectAttributeName = kAttrWindowFrame;
3808 		workspaceAttributeName = kAttrWindowWorkspace;
3809 	}
3810 
3811 	BRect frame(Frame());
3812 	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
3813 			== sizeof(BRect)) {
3814 		const float scalingFactor = be_plain_font->Size() / 12.0f;
3815 		frame.left *= scalingFactor;
3816 		frame.top *= scalingFactor;
3817 		frame.right *= scalingFactor;
3818 		frame.bottom *= scalingFactor;
3819 
3820 		MoveTo(frame.LeftTop());
3821 		ResizeTo(frame.Width(), frame.Height());
3822 	} else
3823 		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3824 
3825 	fPreviousBounds = Bounds();
3826 
3827 	uint32 workspace;
3828 	if (((fOpenFlags & kRestoreWorkspace) != 0)
3829 		&& node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3830 			&workspace) == sizeof(uint32))
3831 		SetWorkspaces(workspace);
3832 
3833 	if ((fOpenFlags & kIsHidden) != 0)
3834 		Minimize(true);
3835 
3836 	// restore window decor settings
3837 	int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE);
3838 	if (size > 0) {
3839 		char buffer[size];
3840 		if (((fOpenFlags & kRestoreDecor) != 0)
3841 			&& node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer)
3842 				== size) {
3843 			BMessage decorSettings;
3844 			if (decorSettings.Unflatten(buffer) == B_OK)
3845 				SetDecoratorSettings(decorSettings);
3846 		}
3847 	}
3848 }
3849 
3850 
3851 void
3852 BContainerWindow::RestoreWindowState(const BMessage& message)
3853 {
3854 	if (fIsDesktop) {
3855 		// don't restore any window state if we are the Desktop
3856 		return;
3857 	}
3858 
3859 	const char* rectAttributeName;
3860 	const char* workspaceAttributeName;
3861 	if (TargetModel()->IsRoot()) {
3862 		rectAttributeName = kAttrDisksFrame;
3863 		workspaceAttributeName = kAttrDisksWorkspace;
3864 	} else {
3865 		rectAttributeName = kAttrWindowFrame;
3866 		workspaceAttributeName = kAttrWindowWorkspace;
3867 	}
3868 
3869 	BRect frame(Frame());
3870 	if (message.FindRect(rectAttributeName, &frame) == B_OK) {
3871 		const float scalingFactor = be_plain_font->Size() / 12.0f;
3872 		frame.left *= scalingFactor;
3873 		frame.top *= scalingFactor;
3874 		frame.right *= scalingFactor;
3875 		frame.bottom *= scalingFactor;
3876 
3877 		MoveTo(frame.LeftTop());
3878 		ResizeTo(frame.Width(), frame.Height());
3879 	} else
3880 		sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3881 
3882 	uint32 workspace;
3883 	if (((fOpenFlags & kRestoreWorkspace) != 0)
3884 		&& message.FindInt32(workspaceAttributeName,
3885 			(int32*)&workspace) == B_OK) {
3886 		SetWorkspaces(workspace);
3887 	}
3888 
3889 	if ((fOpenFlags & kIsHidden) != 0)
3890 		Minimize(true);
3891 
3892 	// restore window decor settings
3893 	BMessage decorSettings;
3894 	if (((fOpenFlags & kRestoreDecor) != 0)
3895 		&& message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) {
3896 		SetDecoratorSettings(decorSettings);
3897 	}
3898 
3899 	fStateNeedsSaving = false;
3900 		// Undo the effect of the above MoveTo and ResizeTo calls
3901 }
3902 
3903 
3904 void
3905 BContainerWindow::SaveWindowState(AttributeStreamNode* node)
3906 {
3907 	if (fIsDesktop) {
3908 		// don't save window state if we are the Desktop
3909 		return;
3910 	}
3911 
3912 	ASSERT(node != NULL);
3913 
3914 	const char* rectAttributeName;
3915 	const char* workspaceAttributeName;
3916 	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
3917 		rectAttributeName = kAttrDisksFrame;
3918 		workspaceAttributeName = kAttrDisksWorkspace;
3919 	} else {
3920 		rectAttributeName = kAttrWindowFrame;
3921 		workspaceAttributeName = kAttrWindowWorkspace;
3922 	}
3923 
3924 	// node is null if it already got deleted
3925 	BRect frame(Frame());
3926 	const float scalingFactor = be_plain_font->Size() / 12.0f;
3927 	frame.left /= scalingFactor;
3928 	frame.top /= scalingFactor;
3929 	frame.right /= scalingFactor;
3930 	frame.bottom /= scalingFactor;
3931 	node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame);
3932 
3933 	uint32 workspaces = Workspaces();
3934 	node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3935 		&workspaces);
3936 
3937 	BMessage decorSettings;
3938 	if (GetDecoratorSettings(&decorSettings) == B_OK) {
3939 		int32 size = decorSettings.FlattenedSize();
3940 		char buffer[size];
3941 		if (decorSettings.Flatten(buffer, size) == B_OK) {
3942 			node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer);
3943 		}
3944 	}
3945 }
3946 
3947 
3948 void
3949 BContainerWindow::SaveWindowState(BMessage& message) const
3950 {
3951 	const char* rectAttributeName;
3952 	const char* workspaceAttributeName;
3953 
3954 	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
3955 		rectAttributeName = kAttrDisksFrame;
3956 		workspaceAttributeName = kAttrDisksWorkspace;
3957 	} else {
3958 		rectAttributeName = kAttrWindowFrame;
3959 		workspaceAttributeName = kAttrWindowWorkspace;
3960 	}
3961 
3962 	// node is null if it already got deleted
3963 	BRect frame(Frame());
3964 	const float scalingFactor = be_plain_font->Size() / 12.0f;
3965 	frame.left /= scalingFactor;
3966 	frame.top /= scalingFactor;
3967 	frame.right /= scalingFactor;
3968 	frame.bottom /= scalingFactor;
3969 	message.AddRect(rectAttributeName, frame);
3970 	message.AddInt32(workspaceAttributeName, (int32)Workspaces());
3971 
3972 	BMessage decorSettings;
3973 	if (GetDecoratorSettings(&decorSettings) == B_OK) {
3974 		message.AddMessage(kAttrWindowDecor, &decorSettings);
3975 	}
3976 }
3977 
3978 
3979 status_t
3980 BContainerWindow::DragStart(const BMessage* dragMessage)
3981 {
3982 	if (dragMessage == NULL)
3983 		return B_ERROR;
3984 
3985 	// if already dragging, or
3986 	// if all the refs match
3987 	if (Dragging()
3988 		&& SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) {
3989 		return B_OK;
3990 	}
3991 
3992 	// cache the current drag message
3993 	// build a list of the mimetypes in the message
3994 	SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage,
3995 		&fCachedTypesList);
3996 
3997 	fWaitingForRefs = true;
3998 
3999 	return B_OK;
4000 }
4001 
4002 
4003 void
4004 BContainerWindow::DragStop()
4005 {
4006 	delete fDragMessage;
4007 	fDragMessage = NULL;
4008 
4009 	delete fCachedTypesList;
4010 	fCachedTypesList = NULL;
4011 
4012 	fWaitingForRefs = false;
4013 }
4014 
4015 
4016 void
4017 BContainerWindow::ShowSelectionWindow()
4018 {
4019 	if (fSelectionWindow == NULL) {
4020 		fSelectionWindow = new SelectionWindow(this);
4021 		fSelectionWindow->Show();
4022 	} else if (fSelectionWindow->Lock()) {
4023 		// The window is already there, just bring it close
4024 		fSelectionWindow->MoveCloseToMouse();
4025 		if (fSelectionWindow->IsHidden())
4026 			fSelectionWindow->Show();
4027 
4028 		fSelectionWindow->Unlock();
4029 	}
4030 }
4031 
4032 
4033 void
4034 BContainerWindow::ShowNavigator(bool show)
4035 {
4036 	if (PoseView()->IsDesktopWindow() || !TargetModel()->IsDirectory()
4037 		|| PoseView()->IsFilePanel()) {
4038 		return;
4039 	}
4040 
4041 	if (show) {
4042 		if (Navigator() && !Navigator()->IsHidden())
4043 			return;
4044 
4045 		if (Navigator() == NULL) {
4046 			fNavigator = new BNavigator(TargetModel());
4047 			fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
4048 		}
4049 
4050 		if (Navigator()->IsHidden())
4051 			Navigator()->Show();
4052 
4053 		if (PoseView()->VScrollBar())
4054 			PoseView()->UpdateScrollRange();
4055 	} else {
4056 		if (!Navigator() || Navigator()->IsHidden())
4057 			return;
4058 
4059 		if (PoseView()->VScrollBar())
4060 			PoseView()->UpdateScrollRange();
4061 
4062 		fNavigator->Hide();
4063 	}
4064 }
4065 
4066 
4067 void
4068 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled)
4069 {
4070 	if (PoseView()->IsDesktopWindow())
4071 		return;
4072 
4073 	if (enabled) {
4074 		if (!Navigator())
4075 			return;
4076 
4077 		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4078 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4079 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4080 
4081 		AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY,
4082 			new BMessage(kNavigatorCommandBackward), Navigator());
4083 		AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY,
4084 			new BMessage(kNavigatorCommandForward), Navigator());
4085 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4086 			new BMessage(kNavigatorCommandUp), Navigator());
4087 
4088 		AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4089 			new BMessage(kNavigatorCommandBackward), Navigator());
4090 		AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4091 			new BMessage(kNavigatorCommandForward), Navigator());
4092 		AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4093 			new BMessage(kNavigatorCommandUp), Navigator());
4094 		AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4095 			new BMessage(kOpenSelection), PoseView());
4096 		AddShortcut('L', B_COMMAND_KEY,
4097 			new BMessage(kNavigatorCommandSetFocus), Navigator());
4098 
4099 	} else {
4100 		RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
4101 		RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
4102 		RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4103 			// This is added again, below, with a new meaning.
4104 
4105 		RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4106 		RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4107 		RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4108 		RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4109 			// This also changes meaning, added again below.
4110 
4111 		AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4112 			new BMessage(kOpenSelection), PoseView());
4113 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4114 			new BMessage(kOpenParentDir), PoseView());
4115 			// We change the meaning from kNavigatorCommandUp
4116 			// to kOpenParentDir.
4117 		AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4118 			new BMessage(kOpenParentDir), PoseView());
4119 			// command + option results in closing the parent window
4120 		RemoveShortcut('L', B_COMMAND_KEY);
4121 	}
4122 }
4123 
4124 
4125 void
4126 BContainerWindow::SetPathWatchingEnabled(bool enable)
4127 {
4128 	if (IsPathWatchingEnabled()) {
4129 		stop_watching(this);
4130 		fIsWatchingPath = false;
4131 	}
4132 
4133 	if (enable) {
4134 		if (TargetModel() != NULL) {
4135 			BEntry entry;
4136 
4137 			TargetModel()->GetEntry(&entry);
4138 			status_t err;
4139 			do {
4140 				err = entry.GetParent(&entry);
4141 				if (err != B_OK)
4142 					break;
4143 
4144 				char name[B_FILE_NAME_LENGTH];
4145 				entry.GetName(name);
4146 				if (strcmp(name, "/") == 0)
4147 					break;
4148 
4149 				node_ref ref;
4150 				entry.GetNodeRef(&ref);
4151 				watch_node(&ref, B_WATCH_NAME, this);
4152 			} while (err == B_OK);
4153 
4154 			fIsWatchingPath = err == B_OK;
4155 		} else
4156 			fIsWatchingPath = false;
4157 	}
4158 }
4159 
4160 
4161 void
4162 BContainerWindow::PulseTaskLoop()
4163 {
4164 	if (fTaskLoop)
4165 		fTaskLoop->PulseMe();
4166 }
4167 
4168 
4169 void
4170 BContainerWindow::PopulateArrangeByMenu(BMenu* menu)
4171 {
4172 	if (!fAttrMenu || !menu)
4173 		return;
4174 	// empty fArrangeByMenu...
4175 	BMenuItem* item;
4176 	while ((item = menu->RemoveItem((int32)0)) != NULL)
4177 		delete item;
4178 
4179 	int32 itemCount = fAttrMenu->CountItems();
4180 	for (int32 i = 0; i < itemCount; i++) {
4181 		item = fAttrMenu->ItemAt(i);
4182 		if (item->Command() == kAttributeItem) {
4183 			BMessage* message = new BMessage(*(item->Message()));
4184 			message->what = kArrangeBy;
4185 			BMenuItem* newItem = new BMenuItem(item->Label(), message);
4186 			newItem->SetTarget(PoseView());
4187 			menu->AddItem(newItem);
4188 		}
4189 	}
4190 
4191 	menu->AddSeparatorItem();
4192 
4193 	item = new BMenuItem(B_TRANSLATE("Reverse order"),
4194 		new BMessage(kArrangeReverseOrder));
4195 
4196 	item->SetTarget(PoseView());
4197 	menu->AddItem(item);
4198 
4199 	menu->AddSeparatorItem();
4200 
4201 	item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K');
4202 	item->SetTarget(PoseView());
4203 	menu->AddItem(item);
4204 }
4205 
4206 
4207 //	#pragma mark - WindowStateNodeOpener
4208 
4209 
4210 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window,
4211 	bool forWriting)
4212 	:
4213 	fModelOpener(NULL),
4214 	fNode(NULL),
4215 	fStreamNode(NULL)
4216 {
4217 	if (window->TargetModel() && window->TargetModel()->IsRoot()) {
4218 		BDirectory dir;
4219 		if (FSGetDeskDir(&dir) == B_OK) {
4220 			fNode = new BDirectory(dir);
4221 			fStreamNode = new AttributeStreamFileNode(fNode);
4222 		}
4223 	} else if (window->TargetModel()){
4224 		fModelOpener = new ModelNodeLazyOpener(window->TargetModel(),
4225 			forWriting, false);
4226 		if (fModelOpener->IsOpen(forWriting)) {
4227 			fStreamNode = new AttributeStreamFileNode(
4228 				fModelOpener->TargetModel()->Node());
4229 		}
4230 	}
4231 }
4232 
4233 WindowStateNodeOpener::~WindowStateNodeOpener()
4234 {
4235 	delete fModelOpener;
4236 	delete fNode;
4237 	delete fStreamNode;
4238 }
4239 
4240 
4241 void
4242 WindowStateNodeOpener::SetTo(const BDirectory* node)
4243 {
4244 	delete fModelOpener;
4245 	delete fNode;
4246 	delete fStreamNode;
4247 
4248 	fModelOpener = NULL;
4249 	fNode = new BDirectory(*node);
4250 	fStreamNode = new AttributeStreamFileNode(fNode);
4251 }
4252 
4253 
4254 void
4255 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting)
4256 {
4257 	delete fModelOpener;
4258 	delete fNode;
4259 	delete fStreamNode;
4260 
4261 	fModelOpener = NULL;
4262 	fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY));
4263 	fStreamNode = new AttributeStreamFileNode(fNode);
4264 }
4265 
4266 
4267 void
4268 WindowStateNodeOpener::SetTo(Model* model, bool forWriting)
4269 {
4270 	delete fModelOpener;
4271 	delete fNode;
4272 	delete fStreamNode;
4273 
4274 	fNode = NULL;
4275 	fStreamNode = NULL;
4276 	fModelOpener = new ModelNodeLazyOpener(model, forWriting, false);
4277 	if (fModelOpener->IsOpen(forWriting)) {
4278 		fStreamNode = new AttributeStreamFileNode(
4279 			fModelOpener->TargetModel()->Node());
4280 	}
4281 }
4282 
4283 
4284 AttributeStreamNode*
4285 WindowStateNodeOpener::StreamNode() const
4286 {
4287 	return fStreamNode;
4288 }
4289 
4290 
4291 BNode*
4292 WindowStateNodeOpener::Node() const
4293 {
4294 	if (!fStreamNode)
4295 		return NULL;
4296 
4297 	if (fNode)
4298 		return fNode;
4299 
4300 	return fModelOpener->TargetModel()->Node();
4301 }
4302 
4303 
4304 //	#pragma mark - BorderedView
4305 
4306 
4307 BorderedView::BorderedView()
4308 	:
4309 	BGroupView(B_VERTICAL, 0),
4310 	fEnableBorderHighlight(true)
4311 {
4312 	GroupLayout()->SetInsets(1);
4313 }
4314 
4315 
4316 void
4317 BorderedView::WindowActivated(bool active)
4318 {
4319 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4320 	if (window == NULL)
4321 		return;
4322 
4323 	if (window->PoseView()->IsFocus())
4324 		PoseViewFocused(active); // Update border color
4325 }
4326 
4327 
4328 void BorderedView::EnableBorderHighlight(bool enable)
4329 {
4330 	fEnableBorderHighlight = enable;
4331 	PoseViewFocused(false);
4332 }
4333 
4334 
4335 void
4336 BorderedView::PoseViewFocused(bool focused)
4337 {
4338 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4339 	if (window == NULL)
4340 		return;
4341 
4342 	color_which base = B_DOCUMENT_BACKGROUND_COLOR;
4343 	float tint = B_DARKEN_2_TINT;
4344 	if (focused && window->IsActive() && fEnableBorderHighlight) {
4345 		base = B_KEYBOARD_NAVIGATION_COLOR;
4346 		tint = B_NO_TINT;
4347 	}
4348 
4349 	BScrollBar* hScrollBar = window->PoseView()->HScrollBar();
4350 	if (hScrollBar != NULL)
4351 		hScrollBar->SetBorderHighlighted(focused);
4352 
4353 	BScrollBar* vScrollBar = window->PoseView()->VScrollBar();
4354 	if (vScrollBar != NULL)
4355 		vScrollBar->SetBorderHighlighted(focused);
4356 
4357 	SetViewUIColor(base, tint);
4358 	Invalidate();
4359 }
4360 
4361 
4362 void
4363 BorderedView::Pulse()
4364 {
4365 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4366 	if (window != NULL)
4367 		window->PulseTaskLoop();
4368 }
4369