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