xref: /haiku/src/kits/tracker/NavMenu.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
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 (fIteratingDesktop && !ShouldShowDesktopPose(fNavDir.device,
514 			&model, &poseInfo)) {
515 //		PRINT(("not showing hidden item %s\n", model.Name()));
516 		return true;
517 	}
518 
519 	AddOneItem(&model);
520 	return true;
521 }
522 
523 
524 void
525 BNavMenu::AddOneItem(Model *model)
526 {
527 	BMenuItem *item = NewModelItem(model, &fMessage, fMessenger, false,
528 		dynamic_cast<BContainerWindow *>(fParentWindow),
529 		fTypesList, &fTrackingHook);
530 
531 	if (item)
532 		fItemList->AddItem(item);
533 }
534 
535 
536 ModelMenuItem *
537 BNavMenu::NewModelItem(Model *model, const BMessage *invokeMessage,
538 	const BMessenger &target, bool suppressFolderHierarchy,
539 	BContainerWindow *parentWindow, const BObjectList<BString> *typeslist,
540 	TrackingHookData *hook)
541 {
542 	if (model->InitCheck() != B_OK)
543 		return 0;
544 	entry_ref ref;
545 	bool container = false;
546 	if (model->IsSymLink()) {
547 
548 		Model *newResolvedModel = 0;
549 		Model *result = model->LinkTo();
550 
551 		if (!result) {
552 			newResolvedModel = new Model(model->EntryRef(), true, true);
553 
554 			if (newResolvedModel->InitCheck() != B_OK) {
555 				// broken link, still can show though, bail
556 				delete newResolvedModel;
557 				result = 0;
558 			} else
559 				result = newResolvedModel;
560 		}
561 
562 		if (result) {
563 			BModelOpener opener(result);
564 				// open the model, if it ain't open already
565 
566 			PoseInfo poseInfo;
567 			ssize_t size = -1;
568 
569 			if (result->Node())
570 				size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
571 					&poseInfo, sizeof(poseInfo));
572 
573 			result->CloseNode();
574 
575 			ref = *result->EntryRef();
576 			container = result->IsContainer();
577 		}
578 		model->SetLinkTo(result);
579 	} else {
580 		ref = *model->EntryRef();
581 		container = model->IsContainer();
582 	}
583 
584 	BMessage *message = new BMessage(*invokeMessage);
585 	message->AddRef("refs", model->EntryRef());
586 
587 	// Truncate the name if necessary
588 	BString truncatedString(model->Name());
589 	be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
590 		GetMaxMenuWidth());
591 
592 	ModelMenuItem *item = NULL;
593 	if (!container || suppressFolderHierarchy) {
594 		item = new ModelMenuItem(model, truncatedString.String(), message);
595 		if (invokeMessage->what != B_REFS_RECEIVED)
596 			item->SetEnabled(false);
597 			// the above is broken for FavoritesMenu::AddNextItem, which uses a
598 			// workaround - should fix this
599 	} else {
600 		BNavMenu *menu = new BNavMenu(truncatedString.String(),
601 			invokeMessage->what, target, parentWindow, typeslist);
602 
603 		menu->SetNavDir(&ref);
604 		if (hook)
605 			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
606 				hook->fDragMessage);
607 
608 		item = new ModelMenuItem(model, menu);
609 		item->SetMessage(message);
610 	}
611 
612 	return item;
613 }
614 
615 
616 void
617 BNavMenu::BuildVolumeMenu()
618 {
619 	BVolumeRoster roster;
620 	BVolume	volume;
621 
622 	roster.Rewind();
623 	while (roster.GetNextVolume(&volume) == B_OK) {
624 
625 		if (!volume.IsPersistent())
626 			continue;
627 
628 		BDirectory startDir;
629 		if (volume.GetRootDirectory(&startDir) == B_OK) {
630 			BEntry entry;
631 			startDir.GetEntry(&entry);
632 
633 			Model *model = new Model(&entry);
634 			if (model->InitCheck() != B_OK) {
635 				delete model;
636 				continue;
637 			}
638 
639 			BNavMenu *menu = new BNavMenu(model->Name(), fMessage.what,
640 				fMessenger, fParentWindow, fTypesList);
641 
642 			menu->SetNavDir(model->EntryRef());
643 
644 			ASSERT(menu->Name());
645 
646 			ModelMenuItem *item = new ModelMenuItem(model, menu);
647 			BMessage *message = new BMessage(fMessage);
648 
649 			message->AddRef("refs", model->EntryRef());
650 
651 			item->SetMessage(message);
652 			fItemList->AddItem(item);
653 			ASSERT(item->Label());
654 
655 		}
656 	}
657 }
658 
659 
660 int
661 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem *i1, const BMenuItem *i2)
662 {
663 	const ModelMenuItem *item1 = dynamic_cast<const ModelMenuItem *>(i1);
664 	const ModelMenuItem *item2 = dynamic_cast<const ModelMenuItem *>(i2);
665 
666 	if (item1 != NULL && item2 != NULL)
667 		return item1->TargetModel()->CompareFolderNamesFirst(item2->TargetModel());
668 
669 	return strcasecmp(i1->Label(), i2->Label());
670 }
671 
672 
673 int
674 BNavMenu::CompareOne(const BMenuItem *i1, const BMenuItem *i2)
675 {
676 	return strcasecmp(i1->Label(), i2->Label());
677 }
678 
679 
680 void
681 BNavMenu::DoneBuildingItemList()
682 {
683 	// add sorted items to menu
684 	if (TrackerSettings().SortFolderNamesFirst())
685 		fItemList->SortItems(CompareFolderNamesFirstOne);
686 	else
687 		fItemList->SortItems(CompareOne);
688 
689 	// if the parent link should be shown, it will be the first
690 	// entry in the menu - but don't add the item if we're already
691 	// at the file system's root
692 	if (fFlags & kShowParent) {
693 		BDirectory directory(&fNavDir);
694 		BEntry entry(&fNavDir);
695 		if (!directory.IsRootDirectory()
696 			&& entry.GetParent(&entry) == B_OK) {
697 			Model model(&entry, true);
698 			BLooper *looper;
699 			AddNavParentDir(&model,fMessage.what,fMessenger.Target(&looper));
700 		}
701 	}
702 
703 	int32 count = fItemList->CountItems();
704 	for (int32 index = 0; index < count; index++)
705 		AddItem(fItemList->ItemAt(index));
706 	fItemList->MakeEmpty();
707 
708 	if (!count) {
709 		BMenuItem *item = new BMenuItem("Empty folder", 0);
710 		item->SetEnabled(false);
711 		AddItem(item);
712 	}
713 
714 	SetTargetForItems(fMessenger);
715 }
716 
717 
718 int32
719 BNavMenu::GetMaxMenuWidth(void)
720 {
721 	int32 width = (int32)(BScreen().Frame().Width() / 4);
722 	return (width < kMinMenuWidth) ? kMinMenuWidth : width;
723 }
724 
725 
726 void
727 BNavMenu::AddNavDir(const Model *model, uint32 what, BHandler *target,
728 	bool populateSubmenu)
729 {
730 	BMessage *message = new BMessage((uint32)what);
731 	message->AddRef("refs", model->EntryRef());
732 	ModelMenuItem *item = NULL;
733 
734 	if (populateSubmenu) {
735 		BNavMenu *navMenu = new BNavMenu(model->Name(), what, target);
736 		navMenu->SetNavDir(model->EntryRef());
737 		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
738 				fTrackingHook.fDragMessage);
739 		item = new ModelMenuItem(model, navMenu);
740 		item->SetMessage(message);
741 	} else
742 		item = new ModelMenuItem(model, model->Name(), message);
743 
744 	AddItem(item);
745 }
746 
747 
748 void
749 BNavMenu::AddNavParentDir(const char *name,const Model *model,uint32 what,BHandler *target)
750 {
751 	BNavMenu *menu = new BNavMenu(name,what,target);
752 	menu->SetNavDir(model->EntryRef());
753 	menu->SetShowParent(true);
754 	menu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
755 			fTrackingHook.fDragMessage);
756 
757 	BMenuItem *item = new SpecialModelMenuItem(model,menu);
758 
759 	BMessage *message = new BMessage(what);
760 	message->AddRef("refs",model->EntryRef());
761 	item->SetMessage(message);
762 
763 	AddItem(item);
764 }
765 
766 
767 void
768 BNavMenu::AddNavParentDir(const Model *model, uint32 what, BHandler *target)
769 {
770 	AddNavParentDir("parent folder",model,what,target);
771 }
772 
773 
774 void
775 BNavMenu::SetShowParent(bool show)
776 {
777 	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
778 }
779 
780 
781 void
782 BNavMenu::SetTypesList(const BObjectList<BString> *list)
783 {
784 	fTypesList = list;
785 }
786 
787 
788 const BObjectList<BString> *
789 BNavMenu::TypesList() const
790 {
791 	return fTypesList;
792 }
793 
794 
795 void
796 BNavMenu::SetTarget(const BMessenger &msngr)
797 {
798 	fMessenger = msngr;
799 }
800 
801 
802 BMessenger
803 BNavMenu::Target()
804 {
805 	return fMessenger;
806 }
807 
808 
809 TrackingHookData *
810 BNavMenu::InitTrackingHook(bool (*hook)(BMenu *, void *), const BMessenger *target,
811 	const BMessage *dragMessage)
812 {
813 	fTrackingHook.fTrackingHook = hook;
814 	if (target)
815 		fTrackingHook.fTarget = *target;
816 	fTrackingHook.fDragMessage = dragMessage;
817 	SetTrackingHookDeep(this, hook, &fTrackingHook);
818 	return &fTrackingHook;
819 }
820 
821 
822 void
823 BNavMenu::SetTrackingHookDeep(BMenu *menu, bool (*func)(BMenu *, void *), void *state)
824 {
825 	menu->SetTrackingHook(func, state);
826 	int32 count = menu->CountItems();
827 	for (int32 index = 0 ; index < count; index++) {
828 		BMenuItem *item = menu->ItemAt(index);
829 		if (!item)
830 			continue;
831 
832 		BMenu *submenu = item->Submenu();
833 		if (submenu)
834 			SetTrackingHookDeep(submenu, func, state);
835 	}
836 }
837 
838