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