xref: /haiku/src/kits/tracker/NavMenu.cpp (revision 2222d0559df303a9846a2fad53741f8b20b14d7c)
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 //	NavMenu is a hierarchical menu of volumes, folders, files and queries
36 //	displays icons, uses the SlowMenu API for full interruptability
37 
38 #include <string.h>
39 #include <stdlib.h>
40 
41 #include <Debug.h>
42 
43 #include <StopWatch.h>
44 #include <Application.h>
45 #include <Directory.h>
46 #include <Query.h>
47 #include <Path.h>
48 #include <Screen.h>
49 #include <VolumeRoster.h>
50 #include <Volume.h>
51 
52 #include "Attributes.h"
53 #include "Commands.h"
54 #include "ContainerWindow.h"
55 #include "DesktopPoseView.h"
56 #include "Tracker.h"
57 #include "FSUtils.h"
58 #include "IconMenuItem.h"
59 #include "MimeTypes.h"
60 #include "NavMenu.h"
61 #include "PoseView.h"
62 #include "Thread.h"
63 #include "FunctionObject.h"
64 #include "QueryPoseView.h"
65 
66 
67 namespace BPrivate {
68 
69 const int32 kMinMenuWidth = 150;
70 
71 enum nav_flags {
72 	kVolumesOnly = 1,
73 	kShowParent = 2
74 };
75 
76 
77 bool
78 SpringLoadedFolderCompareMessages(const BMessage *incoming, const BMessage *dragmessage)
79 {
80 	if (!dragmessage || !incoming)
81 		return false;
82 
83 	bool retvalue=false;
84 	for (int32 inIndex=0; incoming->HasRef("refs", inIndex); inIndex++) {
85 		entry_ref inRef;
86 		if (incoming->FindRef("refs", inIndex, &inRef) != B_OK) {
87 			retvalue = false;
88 			break;
89 		}
90 
91 		bool inRefMatch = false;
92 		for (int32 dragIndex=0; dragmessage->HasRef("refs", dragIndex); dragIndex++) {
93 			entry_ref dragRef;
94 			if (dragmessage->FindRef("refs", dragIndex, &dragRef) != B_OK) {
95 				inRefMatch =  false;
96 				break;
97 			}
98 			//	if the incoming ref matches any ref in the drag ref
99 			//	then we can try the next incoming ref
100 			if (inRef == dragRef) {
101 				inRefMatch = true;
102 				break;
103 			}
104 		}
105 		retvalue =  inRefMatch;
106 		if (!inRefMatch)
107 			break;
108 	}
109 
110 	if (retvalue) {
111 		//	if all the refs match
112 		//	try and see if this is another instance of the same
113 		//	drag contents, but new drag
114 		retvalue = false;
115 		BPoint inPt, dPt;
116 		if (incoming->FindPoint("click_pt", &inPt) == B_OK)
117 			if (dragmessage->FindPoint("click_pt", &dPt) == B_OK)
118 				retvalue = (inPt == dPt);
119 	}
120 
121 	return retvalue;
122 }
123 
124 
125 void
126 SpringLoadedFolderSetMenuStates(const BMenu* menu, const BObjectList<BString> *typeslist)
127 {
128 	if (!menu || !typeslist)
129 		return;
130 
131 	//	if a types list exists
132 	//		iterate through the list and see if each item
133 	//		can support any item in the list
134 	//		set the enabled state of the item
135 	int32 count = menu->CountItems();
136 	for (int32 index = 0 ; index < count ; index++) {
137 		ModelMenuItem *item = dynamic_cast<ModelMenuItem *>(menu->ItemAt(index));
138 		if (!item)
139 			continue;
140 
141 		const Model *model = item->TargetModel();
142 		if (!model)
143 			continue;
144 
145 		if (model->IsSymLink()) {
146 			//	find out what the model is, resolve if symlink
147 			BEntry entry(model->EntryRef(), true);
148 			if (entry.InitCheck() == B_OK) {
149 				if (entry.IsDirectory()) {
150 					//	folder? always keep enabled
151 					item->SetEnabled(true);
152 				} else {
153 					//	other, check its support
154 					Model resolvedModel(&entry);
155 					int32 supported = resolvedModel.SupportsMimeType(NULL, typeslist);
156 					item->SetEnabled(supported != kDoesNotSupportType);
157 				}
158 			} else
159 				//	bad entry ref (bad symlink?), disable
160 				item->SetEnabled(false);
161 		} else if (model->IsDirectory() || model->IsRoot() || model->IsVolume())
162 			//	always enabled if a container
163 			item->SetEnabled(true);
164 		else if (model->IsFile() || model->IsExecutable()) {
165 			int32 supported = model->SupportsMimeType(NULL, typeslist);
166 			item->SetEnabled(supported != kDoesNotSupportType);
167 		} else
168 			item->SetEnabled(false);
169 	}
170 }
171 
172 
173 void
174 SpringLoadedFolderAddUniqueTypeToList(entry_ref *ref, BObjectList<BString> *typeslist)
175 {
176 	if (!ref || !typeslist)
177 		return;
178 
179 	//	get the mime type for the current ref
180 	BNodeInfo nodeinfo;
181 	BNode node(ref);
182 	if (node.InitCheck() != B_OK)
183 		return;
184 
185 	nodeinfo.SetTo(&node);
186 
187 	char mimestr[B_MIME_TYPE_LENGTH];
188 	//	add it to the list
189 	if (nodeinfo.GetType(mimestr) == B_OK && strlen(mimestr) > 0) {
190 		//	if this is a symlink, add symlink to the list (below)
191 		//	resolve the symlink, add the resolved type
192 		//	to the list
193 		if (strcmp(B_LINK_MIMETYPE, mimestr) == 0) {
194 			BEntry entry(ref, true);
195 			if (entry.InitCheck() == B_OK) {
196 				entry_ref resolvedRef;
197 				if (entry.GetRef(&resolvedRef) == B_OK)
198 					SpringLoadedFolderAddUniqueTypeToList(&resolvedRef, typeslist);
199 			}
200 		}
201 		//	scan the current list, don't add dups
202 		bool unique = true;
203 		int32 count = typeslist->CountItems();
204 		for (int32 index = 0 ; index < count ; index++) {
205 			if (typeslist->ItemAt(index)->Compare(mimestr) == 0) {
206 				unique = false;
207 				break;
208 			}
209 		}
210 
211 		if (unique)
212 			typeslist->AddItem(new BString(mimestr));
213 	}
214 }
215 
216 
217 void
218 SpringLoadedFolderCacheDragData(const BMessage *incoming, BMessage **message, BObjectList<BString> **typeslist)
219 {
220 	if (!incoming)
221 		return;
222 
223 	delete *message;
224 	delete *typeslist;
225 
226 	BMessage *localMessage = new BMessage(*incoming);
227 	BObjectList<BString> *localTypesList = new BObjectList<BString>(10, true);
228 
229 	for (int32 index=0; incoming->HasRef("refs", index); index++) {
230 		entry_ref ref;
231 		if (incoming->FindRef("refs", index, &ref) != B_OK)
232 			continue;
233 
234 		SpringLoadedFolderAddUniqueTypeToList(&ref, localTypesList);
235 	}
236 
237 	*message = localMessage;
238 	*typeslist = localTypesList;
239 }
240 
241 }
242 
243 
244 //	#pragma mark -
245 
246 
247 BNavMenu::BNavMenu(const char *title, uint32 message, const BHandler *target,
248 	BWindow *parentWindow, const BObjectList<BString> *list)
249 	:	BSlowMenu(title),
250 		fMessage(message),
251 		fMessenger(target, target->Looper()),
252 		fParentWindow(parentWindow),
253 		fFlags(0),
254 		fItemList(0),
255 		fContainer(0),
256 		fTypesList(list)
257 {
258 	InitIconPreloader();
259 
260 	SetFont(be_plain_font);
261 
262 	// add the parent window to the invocation message so that it
263 	// can be closed if option modifier held down during invocation
264 	BContainerWindow *originatingWindow = dynamic_cast<BContainerWindow *>(fParentWindow);
265 	if (originatingWindow)
266 		fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
267 			originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref));
268 
269 	// too long to have triggers
270 	SetTriggersEnabled(false);
271 }
272 
273 
274 BNavMenu::BNavMenu(const char *title, uint32 message, const BMessenger &messenger,
275 	BWindow *parentWindow, const BObjectList<BString> *list)
276 	:	BSlowMenu(title),
277 		fMessage(message),
278 		fMessenger(messenger),
279 		fParentWindow(parentWindow),
280 		fFlags(0),
281 		fItemList(0),
282 		fContainer(0),
283 		fTypesList(list)
284 {
285 	InitIconPreloader();
286 
287 	SetFont(be_plain_font);
288 
289 	// add the parent window to the invocation message so that it
290 	// can be closed if option modifier held down during invocation
291 	BContainerWindow *originatingWindow = dynamic_cast<BContainerWindow *>(fParentWindow);
292 	if (originatingWindow)
293 		fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
294 			originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref));
295 
296 	// too long to have triggers
297 	SetTriggersEnabled(false);
298 }
299 
300 
301 BNavMenu::~BNavMenu()
302 {
303 }
304 
305 
306 void
307 BNavMenu::AttachedToWindow()
308 {
309 	BSlowMenu::AttachedToWindow();
310 
311 	SpringLoadedFolderSetMenuStates(this, fTypesList);
312 		//	if dragging (fTypesList != NULL)
313 		//	set the menu items enabled state
314 		//	relative to the ability to handle an item in the
315 		//	drag message
316 	ResetTargets();
317 		//	allow an opportunity to reset the target for each of the items
318 }
319 
320 
321 void
322 BNavMenu::DetachedFromWindow()
323 {
324 	//	does this need to set this to null?
325 	//	the parent, handling dnd should set this
326 	//	appropriately
327 	//
328 	//	if this changes, BeMenu and RecentsMenu
329 	//	in Deskbar should also change
330 	fTypesList = NULL;
331 }
332 
333 
334 void
335 BNavMenu::ResetTargets()
336 {
337 	SetTargetForItems(Target());
338 }
339 
340 
341 void
342 BNavMenu::ForceRebuild()
343 {
344 	ClearMenuBuildingState();
345 	fMenuBuilt = false;
346 }
347 
348 
349 bool
350 BNavMenu::NeedsToRebuild() const
351 {
352 	return !fMenuBuilt;
353 }
354 
355 
356 void
357 BNavMenu::SetNavDir(const entry_ref *ref)
358 {
359 	ForceRebuild();
360 		// reset the slow menu building mechanism so we can add more stuff
361 
362 	fNavDir = *ref;
363 }
364 
365 
366 void
367 BNavMenu::ClearMenuBuildingState()
368 {
369 	delete fContainer;
370 	fContainer = NULL;
371 
372 	// item list is non-owning, need to delete the items because
373 	// they didn't get added to the menu
374 	if (fItemList) {
375 		int32 count = fItemList->CountItems();
376 		for (int32 index = count - 1; index >= 0; index--)
377 			delete RemoveItem(index);
378 		delete fItemList;
379 		fItemList = NULL;
380 	}
381 }
382 
383 
384 bool
385 BNavMenu::StartBuildingItemList()
386 {
387 	BEntry entry;
388 
389 	if (fNavDir.device < 0 || entry.SetTo(&fNavDir) != B_OK
390 		|| !entry.Exists())
391 		return false;
392 
393 	fItemList = new BObjectList<BMenuItem>(50);
394 
395 	fIteratingDesktop = false;
396 
397 	BDirectory parent;
398 	status_t status = entry.GetParent(&parent);
399 
400 	// if ref is the root item then build list of volume root dirs
401 	fFlags = uint8((fFlags & ~kVolumesOnly) | (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0));
402 	if (fFlags & kVolumesOnly)
403 		return true;
404 
405 	Model startModel(&entry, true);
406 	if (startModel.InitCheck() != B_OK || !startModel.IsContainer())
407 		return false;
408 
409 	if (startModel.IsQuery())
410 		fContainer = new QueryEntryListCollection(&startModel);
411 	else if (FSIsDeskDir(&entry)) {
412 		fIteratingDesktop = true;
413 		fContainer = DesktopPoseView::InitDesktopDirentIterator(0, startModel.EntryRef());
414 		AddRootItemsIfNeeded();
415 	} else if (FSIsTrashDir(&entry)) {
416 		// the trash window needs to display a union of all the
417 		// trash folders from all the mounted volumes
418 		BVolumeRoster volRoster;
419 		volRoster.Rewind();
420 		BVolume volume;
421 		fContainer = new EntryIteratorList();
422 
423 		while (volRoster.GetNextVolume(&volume) == B_OK) {
424 			if (volume.IsReadOnly() || !volume.IsPersistent())
425 				continue;
426 
427 			BDirectory trashDir;
428 
429 			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK)
430 				dynamic_cast<EntryIteratorList *>(fContainer)->
431 					AddItem(new DirectoryEntryList(trashDir));
432 		}
433 	} else
434 		fContainer = new DirectoryEntryList(*dynamic_cast<BDirectory *>
435 			(startModel.Node()));
436 
437 	if (fContainer == NULL || fContainer->InitCheck() != B_OK)
438 		return false;
439 
440 	fContainer->Rewind();
441 
442 	return true;
443 }
444 
445 
446 void
447 BNavMenu::AddRootItemsIfNeeded()
448 {
449 	BVolumeRoster roster;
450 	roster.Rewind();
451 	BVolume volume;
452 	while (roster.GetNextVolume(&volume) == B_OK) {
453 
454 		BDirectory root;
455 		BEntry entry;
456 		if (!volume.IsPersistent()
457 			|| volume.GetRootDirectory(&root) != B_OK
458 			|| root.GetEntry(&entry) != B_OK)
459 			continue;
460 
461 		Model model(&entry);
462 		AddOneItem(&model);
463 	}
464 }
465 
466 
467 bool
468 BNavMenu::AddNextItem()
469 {
470 	if (fFlags & kVolumesOnly) {
471 		BuildVolumeMenu();
472 		return false;
473 	}
474 
475 	BEntry entry;
476 	if (fContainer->GetNextEntry(&entry) != B_OK) {
477 		// we're finished
478 		return false;
479 	}
480 
481 	if (TrackerSettings().HideDotFiles()) {
482 		char name[B_FILE_NAME_LENGTH];
483 		if (entry.GetName(name) == B_OK && name[0] == '.')
484 			return true;
485 	}
486 
487 	Model model(&entry, true);
488 	if (model.InitCheck() != B_OK) {
489 //		PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
490 		return true;
491 	}
492 
493 	QueryEntryListCollection *queryContainer
494 		= dynamic_cast<QueryEntryListCollection*>(fContainer);
495 	if (queryContainer && !queryContainer->ShowResultsFromTrash()
496 		&& FSInTrashDir(model.EntryRef())) {
497 		// query entry is in trash and shall not be shown
498 		return true;
499 	}
500 
501 	ssize_t size = -1;
502 	PoseInfo poseInfo;
503 
504 	if (model.Node())
505 		size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
506 			&poseInfo, sizeof(poseInfo));
507 
508 	model.CloseNode();
509 
510 	// item might be in invisible
511 	// ToDo:
512 	// use more of PoseView's filtering here
513 	if ((size == sizeof(poseInfo)
514 			&& !BPoseView::PoseVisible(&model, &poseInfo, false))
515 		|| (fIteratingDesktop && !ShouldShowDesktopPose(fNavDir.device,
516 			&model, &poseInfo))) {
517 //		PRINT(("not showing hidden item %s\n", model.Name()));
518 		return true;
519 	}
520 
521 	AddOneItem(&model);
522 	return true;
523 }
524 
525 
526 void
527 BNavMenu::AddOneItem(Model *model)
528 {
529 	BMenuItem *item = NewModelItem(model, &fMessage, fMessenger, false,
530 		dynamic_cast<BContainerWindow *>(fParentWindow),
531 		fTypesList, &fTrackingHook);
532 
533 	if (item)
534 		fItemList->AddItem(item);
535 }
536 
537 
538 ModelMenuItem *
539 BNavMenu::NewModelItem(Model *model, const BMessage *invokeMessage,
540 	const BMessenger &target, bool suppressFolderHierarchy,
541 	BContainerWindow *parentWindow, const BObjectList<BString> *typeslist,
542 	TrackingHookData *hook)
543 {
544 	if (model->InitCheck() != B_OK)
545 		return 0;
546 	entry_ref ref;
547 	bool container = false;
548 	if (model->IsSymLink()) {
549 
550 		Model *newResolvedModel = 0;
551 		Model *result = model->LinkTo();
552 
553 		if (!result) {
554 			newResolvedModel = new Model(model->EntryRef(), true, true);
555 
556 			if (newResolvedModel->InitCheck() != B_OK) {
557 				// broken link, still can show though, bail
558 				delete newResolvedModel;
559 				result = 0;
560 			} else
561 				result = newResolvedModel;
562 		}
563 
564 		if (result) {
565 			BModelOpener opener(result);
566 				// open the model, if it ain't open already
567 
568 			PoseInfo poseInfo;
569 			ssize_t size = -1;
570 
571 			if (result->Node())
572 				size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
573 					&poseInfo, sizeof(poseInfo));
574 
575 			result->CloseNode();
576 
577 			if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result,
578 				&poseInfo, false)) {
579 				// link target sez it doesn't want to be visible,
580 				// don't show the link
581 				PRINT(("not showing hidden item %s\n", model->Name()));
582 				delete newResolvedModel;
583 				return 0;
584 			}
585 			ref = *result->EntryRef();
586 			container = result->IsContainer();
587 		}
588 		model->SetLinkTo(result);
589 	} else {
590 		ref = *model->EntryRef();
591 		container = model->IsContainer();
592 	}
593 
594 	BMessage *message = new BMessage(*invokeMessage);
595 	message->AddRef("refs", model->EntryRef());
596 
597 	// Truncate the name if necessary
598 	BString truncatedString(model->Name());
599 	be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
600 		GetMaxMenuWidth());
601 
602 	ModelMenuItem *item = NULL;
603 	if (!container || suppressFolderHierarchy) {
604 		item = new ModelMenuItem(model, truncatedString.String(), message);
605 		if (invokeMessage->what != B_REFS_RECEIVED)
606 			item->SetEnabled(false);
607 			// the above is broken for FavoritesMenu::AddNextItem, which uses a
608 			// workaround - should fix this
609 	} else {
610 		BNavMenu *menu = new BNavMenu(truncatedString.String(),
611 			invokeMessage->what, target, parentWindow, typeslist);
612 
613 		menu->SetNavDir(&ref);
614 		if (hook)
615 			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
616 				hook->fDragMessage);
617 
618 		item = new ModelMenuItem(model, menu);
619 		item->SetMessage(message);
620 	}
621 
622 	return item;
623 }
624 
625 
626 void
627 BNavMenu::BuildVolumeMenu()
628 {
629 	BVolumeRoster roster;
630 	BVolume	volume;
631 
632 	roster.Rewind();
633 	while (roster.GetNextVolume(&volume) == B_OK) {
634 
635 		if (!volume.IsPersistent())
636 			continue;
637 
638 		BDirectory startDir;
639 		if (volume.GetRootDirectory(&startDir) == B_OK) {
640 			BEntry entry;
641 			startDir.GetEntry(&entry);
642 
643 			Model *model = new Model(&entry);
644 			if (model->InitCheck() != B_OK) {
645 				delete model;
646 				continue;
647 			}
648 
649 			BNavMenu *menu = new BNavMenu(model->Name(), fMessage.what,
650 				fMessenger, fParentWindow, fTypesList);
651 
652 			menu->SetNavDir(model->EntryRef());
653 
654 			ASSERT(menu->Name());
655 
656 			ModelMenuItem *item = new ModelMenuItem(model, menu);
657 			BMessage *message = new BMessage(fMessage);
658 
659 			message->AddRef("refs", model->EntryRef());
660 
661 			item->SetMessage(message);
662 			fItemList->AddItem(item);
663 			ASSERT(item->Label());
664 
665 		}
666 	}
667 }
668 
669 
670 int
671 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem *i1, const BMenuItem *i2)
672 {
673 	const ModelMenuItem *item1 = dynamic_cast<const ModelMenuItem *>(i1);
674 	const ModelMenuItem *item2 = dynamic_cast<const ModelMenuItem *>(i2);
675 
676 	if (item1 != NULL && item2 != NULL)
677 		return item1->TargetModel()->CompareFolderNamesFirst(item2->TargetModel());
678 
679 	return strcasecmp(i1->Label(), i2->Label());
680 }
681 
682 
683 int
684 BNavMenu::CompareOne(const BMenuItem *i1, const BMenuItem *i2)
685 {
686 	return strcasecmp(i1->Label(), i2->Label());
687 }
688 
689 
690 void
691 BNavMenu::DoneBuildingItemList()
692 {
693 	// add sorted items to menu
694 	if (TrackerSettings().SortFolderNamesFirst())
695 		fItemList->SortItems(CompareFolderNamesFirstOne);
696 	else
697 		fItemList->SortItems(CompareOne);
698 
699 	// if the parent link should be shown, it will be the first
700 	// entry in the menu - but don't add the item if we're already
701 	// at the file system's root
702 	if (fFlags & kShowParent) {
703 		BDirectory directory(&fNavDir);
704 		BEntry entry(&fNavDir);
705 		if (!directory.IsRootDirectory()
706 			&& entry.GetParent(&entry) == B_OK) {
707 			Model model(&entry, true);
708 			BLooper *looper;
709 			AddNavParentDir(&model,fMessage.what,fMessenger.Target(&looper));
710 		}
711 	}
712 
713 	int32 count = fItemList->CountItems();
714 	for (int32 index = 0; index < count; index++)
715 		AddItem(fItemList->ItemAt(index));
716 	fItemList->MakeEmpty();
717 
718 	if (!count) {
719 		BMenuItem *item = new BMenuItem("Empty Folder", 0);
720 		item->SetEnabled(false);
721 		AddItem(item);
722 	}
723 
724 	SetTargetForItems(fMessenger);
725 }
726 
727 
728 int32
729 BNavMenu::GetMaxMenuWidth(void)
730 {
731 	int32 width = (int32)(BScreen().Frame().Width() / 4);
732 	return (width < kMinMenuWidth) ? kMinMenuWidth : width;
733 }
734 
735 
736 void
737 BNavMenu::AddNavDir(const Model *model, uint32 what, BHandler *target,
738 	bool populateSubmenu)
739 {
740 	BMessage *message = new BMessage((uint32)what);
741 	message->AddRef("refs", model->EntryRef());
742 	ModelMenuItem *item = NULL;
743 
744 	if (populateSubmenu) {
745 		BNavMenu *navMenu = new BNavMenu(model->Name(), what, target);
746 		navMenu->SetNavDir(model->EntryRef());
747 		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
748 				fTrackingHook.fDragMessage);
749 		item = new ModelMenuItem(model, navMenu);
750 		item->SetMessage(message);
751 	} else
752 		item = new ModelMenuItem(model, model->Name(), message);
753 
754 	AddItem(item);
755 }
756 
757 
758 void
759 BNavMenu::AddNavParentDir(const char *name,const Model *model,uint32 what,BHandler *target)
760 {
761 	BNavMenu *menu = new BNavMenu(name,what,target);
762 	menu->SetNavDir(model->EntryRef());
763 	menu->SetShowParent(true);
764 	menu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
765 			fTrackingHook.fDragMessage);
766 
767 	BMenuItem *item = new SpecialModelMenuItem(model,menu);
768 
769 	BMessage *message = new BMessage(what);
770 	message->AddRef("refs",model->EntryRef());
771 	item->SetMessage(message);
772 
773 	AddItem(item);
774 }
775 
776 
777 void
778 BNavMenu::AddNavParentDir(const Model *model, uint32 what, BHandler *target)
779 {
780 	AddNavParentDir("parent folder",model,what,target);
781 }
782 
783 
784 void
785 BNavMenu::SetShowParent(bool show)
786 {
787 	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
788 }
789 
790 
791 void
792 BNavMenu::SetTypesList(const BObjectList<BString> *list)
793 {
794 	fTypesList = list;
795 }
796 
797 
798 const BObjectList<BString> *
799 BNavMenu::TypesList() const
800 {
801 	return fTypesList;
802 }
803 
804 
805 void
806 BNavMenu::SetTarget(const BMessenger &msngr)
807 {
808 	fMessenger = msngr;
809 }
810 
811 
812 BMessenger
813 BNavMenu::Target()
814 {
815 	return fMessenger;
816 }
817 
818 
819 TrackingHookData *
820 BNavMenu::InitTrackingHook(bool (*hook)(BMenu *, void *), const BMessenger *target,
821 	const BMessage *dragMessage)
822 {
823 	fTrackingHook.fTrackingHook = hook;
824 	if (target)
825 		fTrackingHook.fTarget = *target;
826 	fTrackingHook.fDragMessage = dragMessage;
827 	SetTrackingHookDeep(this, hook, &fTrackingHook);
828 	return &fTrackingHook;
829 }
830 
831 
832 void
833 BNavMenu::SetTrackingHookDeep(BMenu *menu, bool (*func)(BMenu *, void *), void *state)
834 {
835 	menu->SetTrackingHook(func, state);
836 	int32 count = menu->CountItems();
837 	for (int32 index = 0 ; index < count; index++) {
838 		BMenuItem *item = menu->ItemAt(index);
839 		if (!item)
840 			continue;
841 
842 		BMenu *submenu = item->Submenu();
843 		if (submenu)
844 			SetTrackingHookDeep(submenu, func, state);
845 	}
846 }
847 
848