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