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