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