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