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