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	// add the parent window to the invocation message so that it
283	// can be closed if option modifier held down during invocation
284	BContainerWindow* originatingWindow =
285		dynamic_cast<BContainerWindow*>(fParentWindow);
286	if (originatingWindow != NULL) {
287		fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
288			originatingWindow->TargetModel()->NodeRef(), sizeof(node_ref));
289	}
290
291	// too long to have triggers
292	SetTriggersEnabled(false);
293}
294
295
296BNavMenu::BNavMenu(const char* title, uint32 message,
297	const BMessenger& messenger, BWindow* parentWindow,
298	const BObjectList<BString>* list)
299	:
300	BSlowMenu(title),
301	fMessage(message),
302	fMessenger(messenger),
303	fParentWindow(parentWindow),
304	fFlags(0),
305	fItemList(NULL),
306	fContainer(NULL),
307	fIteratingDesktop(false),
308	fTypesList(new BObjectList<BString>(10, true))
309{
310	if (list != NULL)
311		*fTypesList = *list;
312
313	InitIconPreloader();
314
315	// add the parent window to the invocation message so that it
316	// can be closed if option modifier held down during invocation
317	BContainerWindow* originatingWindow =
318		dynamic_cast<BContainerWindow*>(fParentWindow);
319	if (originatingWindow != NULL) {
320		fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
321			originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref));
322	}
323
324	// too long to have triggers
325	SetTriggersEnabled(false);
326}
327
328
329BNavMenu::~BNavMenu()
330{
331	delete fTypesList;
332}
333
334
335void
336BNavMenu::AttachedToWindow()
337{
338	BSlowMenu::AttachedToWindow();
339
340	SpringLoadedFolderSetMenuStates(this, fTypesList);
341		// If dragging, (fTypesList != NULL) set the menu items enabled state
342		// relative to the ability to handle an item in the drag message.
343	ResetTargets();
344		// allow an opportunity to reset the target for each of the items
345}
346
347
348void
349BNavMenu::DetachedFromWindow()
350{
351}
352
353
354void
355BNavMenu::ResetTargets()
356{
357	SetTargetForItems(Target());
358}
359
360
361void
362BNavMenu::ForceRebuild()
363{
364	ClearMenuBuildingState();
365	fMenuBuilt = false;
366}
367
368
369bool
370BNavMenu::NeedsToRebuild() const
371{
372	return !fMenuBuilt;
373}
374
375
376void
377BNavMenu::SetNavDir(const entry_ref* ref)
378{
379	ForceRebuild();
380		// reset the slow menu building mechanism so we can add more stuff
381
382	fNavDir = *ref;
383}
384
385
386void
387BNavMenu::ClearMenuBuildingState()
388{
389	delete fContainer;
390	fContainer = NULL;
391
392	// item list is non-owning, need to delete the items because
393	// they didn't get added to the menu
394	if (fItemList != NULL) {
395		RemoveItems(0, fItemList->CountItems(), true);
396
397		delete fItemList;
398		fItemList = NULL;
399	}
400}
401
402
403bool
404BNavMenu::StartBuildingItemList()
405{
406	BEntry entry;
407
408	if (fNavDir.device < 0 || entry.SetTo(&fNavDir, true) != B_OK
409		|| !entry.Exists()) {
410		return false;
411	}
412
413	fItemList = new BObjectList<BMenuItem>(50);
414
415	fIteratingDesktop = false;
416
417	BDirectory parent;
418	status_t status = entry.GetParent(&parent);
419
420	// if ref is the root item then build list of volume root dirs
421	fFlags = uint8((fFlags & ~kVolumesOnly)
422		| (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0));
423	if (fFlags & kVolumesOnly)
424		return true;
425
426	Model startModel(&entry, true);
427	if (startModel.InitCheck() != B_OK || !startModel.IsContainer())
428		return false;
429
430	if (startModel.IsQuery()) {
431		fContainer = new QueryEntryListCollection(&startModel);
432	} else if (startModel.IsVirtualDirectory()) {
433		fContainer = new VirtualDirectoryEntryList(&startModel);
434	} else if (startModel.IsDesktop()) {
435		fIteratingDesktop = true;
436		fContainer = DesktopPoseView::InitDesktopDirentIterator(0,
437			startModel.EntryRef());
438		AddRootItemsIfNeeded();
439		AddTrashItem();
440	} else if (startModel.IsTrash()) {
441		// the trash window needs to display a union of all the
442		// trash folders from all the mounted volumes
443		BVolumeRoster volRoster;
444		volRoster.Rewind();
445		BVolume volume;
446		fContainer = new EntryIteratorList();
447
448		while (volRoster.GetNextVolume(&volume) == B_OK) {
449			if (volume.IsReadOnly() || !volume.IsPersistent())
450				continue;
451
452			BDirectory trashDir;
453
454			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
455				EntryIteratorList* iteratorList
456					= dynamic_cast<EntryIteratorList*>(fContainer);
457
458				ASSERT(iteratorList != NULL);
459
460				if (iteratorList != NULL)
461					iteratorList->AddItem(new DirectoryEntryList(trashDir));
462			}
463		}
464	} else {
465		BDirectory* directory = dynamic_cast<BDirectory*>(startModel.Node());
466
467		ASSERT(directory != NULL);
468
469		if (directory != NULL)
470			fContainer = new DirectoryEntryList(*directory);
471	}
472
473	if (fContainer == NULL || fContainer->InitCheck() != B_OK)
474		return false;
475
476	fContainer->Rewind();
477
478	return true;
479}
480
481
482void
483BNavMenu::AddRootItemsIfNeeded()
484{
485	BVolumeRoster roster;
486	roster.Rewind();
487	BVolume volume;
488	while (roster.GetNextVolume(&volume) == B_OK) {
489		BDirectory root;
490		BEntry entry;
491		if (!volume.IsPersistent()
492			|| volume.GetRootDirectory(&root) != B_OK
493			|| root.GetEntry(&entry) != B_OK) {
494			continue;
495		}
496
497		Model model(&entry);
498		AddOneItem(&model);
499	}
500}
501
502
503void
504BNavMenu::AddTrashItem()
505{
506	BPath path;
507	if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) {
508		BEntry entry(path.Path());
509		Model model(&entry);
510		AddOneItem(&model);
511	}
512}
513
514
515bool
516BNavMenu::AddNextItem()
517{
518	if ((fFlags & kVolumesOnly) != 0) {
519		BuildVolumeMenu();
520		return false;
521	}
522
523	BEntry entry;
524	if (fContainer->GetNextEntry(&entry) != B_OK) {
525		// we're finished
526		return false;
527	}
528
529	if (TrackerSettings().HideDotFiles()) {
530		char name[B_FILE_NAME_LENGTH];
531		if (entry.GetName(name) == B_OK && name[0] == '.')
532			return true;
533	}
534
535	Model model(&entry, true);
536	if (model.InitCheck() != B_OK) {
537//		PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
538		return true;
539	}
540
541	QueryEntryListCollection* queryContainer
542		= dynamic_cast<QueryEntryListCollection*>(fContainer);
543	if (queryContainer != NULL && !queryContainer->ShowResultsFromTrash()
544		&& FSInTrashDir(model.EntryRef())) {
545		// query entry is in trash and shall not be shown
546		return true;
547	}
548
549	ssize_t size = -1;
550	PoseInfo poseInfo;
551	if (model.Node() != NULL) {
552		size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
553			&poseInfo, sizeof(poseInfo));
554	}
555
556	model.CloseNode();
557
558	// item might be in invisible
559	if (size == sizeof(poseInfo)
560			&& !BPoseView::PoseVisible(&model, &poseInfo)) {
561		return true;
562	}
563
564	AddOneItem(&model);
565
566	return true;
567}
568
569
570void
571BNavMenu::AddOneItem(Model* model)
572{
573	BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false,
574		dynamic_cast<BContainerWindow*>(fParentWindow),
575		fTypesList, &fTrackingHook);
576
577	if (item != NULL)
578		fItemList->AddItem(item);
579}
580
581
582ModelMenuItem*
583BNavMenu::NewModelItem(Model* model, const BMessage* invokeMessage,
584	const BMessenger& target, bool suppressFolderHierarchy,
585	BContainerWindow* parentWindow, const BObjectList<BString>* typeslist,
586	TrackingHookData* hook)
587{
588	if (model->InitCheck() != B_OK)
589		return 0;
590
591	entry_ref ref;
592	bool isContainer = false;
593	if (model->IsSymLink()) {
594		Model* newResolvedModel = 0;
595		Model* result = model->LinkTo();
596
597		if (result == NULL) {
598			newResolvedModel = new Model(model->EntryRef(), true, true);
599
600			if (newResolvedModel->InitCheck() != B_OK) {
601				// broken link, still can show though, bail
602				delete newResolvedModel;
603				result = NULL;
604			} else
605				result = newResolvedModel;
606		}
607
608		if (result != NULL) {
609			BModelOpener opener(result);
610				// open the model, if it ain't open already
611
612			PoseInfo poseInfo;
613			ssize_t size = -1;
614
615			if (result->Node() != NULL) {
616				size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
617					&poseInfo, sizeof(poseInfo));
618			}
619
620			result->CloseNode();
621
622			if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result,
623				&poseInfo)) {
624				// link target does not want to be visible
625				delete newResolvedModel;
626				return NULL;
627			}
628
629			ref = *result->EntryRef();
630			isContainer = result->IsContainer();
631		}
632
633		model->SetLinkTo(result);
634	} else {
635		ref = *model->EntryRef();
636		isContainer = model->IsContainer();
637	}
638
639	BMessage* message = new BMessage(*invokeMessage);
640	message->AddRef("refs", model->EntryRef());
641
642	// truncate name if necessary
643	BString truncatedString(model->Name());
644	be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
645		GetMaxMenuWidth());
646
647	ModelMenuItem* item = NULL;
648	if (!isContainer || suppressFolderHierarchy) {
649		item = new ModelMenuItem(model, truncatedString.String(), message);
650		if (invokeMessage->what != B_REFS_RECEIVED)
651			item->SetEnabled(false);
652			// the above is broken for FavoritesMenu::AddNextItem, which uses a
653			// workaround - should fix this
654	} else {
655		BNavMenu* menu = new BNavMenu(truncatedString.String(),
656			invokeMessage->what, target, parentWindow, typeslist);
657		menu->SetNavDir(&ref);
658		if (hook != NULL) {
659			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
660				hook->fDragMessage);
661		}
662
663		item = new ModelMenuItem(model, menu);
664		item->SetMessage(message);
665	}
666
667	return item;
668}
669
670
671void
672BNavMenu::BuildVolumeMenu()
673{
674	BVolumeRoster roster;
675	BVolume volume;
676
677	roster.Rewind();
678	while (roster.GetNextVolume(&volume) == B_OK) {
679		if (!volume.IsPersistent())
680			continue;
681
682		BDirectory startDir;
683		if (volume.GetRootDirectory(&startDir) == B_OK) {
684			BEntry entry;
685			startDir.GetEntry(&entry);
686
687			Model* model = new Model(&entry);
688			if (model->InitCheck() != B_OK) {
689				delete model;
690				continue;
691			}
692
693			BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what,
694				fMessenger, fParentWindow, fTypesList);
695
696			menu->SetNavDir(model->EntryRef());
697
698			ASSERT(menu->Name() != NULL);
699
700			ModelMenuItem* item = new ModelMenuItem(model, menu);
701			BMessage* message = new BMessage(fMessage);
702
703			message->AddRef("refs", model->EntryRef());
704
705			item->SetMessage(message);
706			fItemList->AddItem(item);
707			ASSERT(item->Label() != NULL);
708		}
709	}
710}
711
712
713int
714BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2)
715{
716	ThrowOnAssert(i1 != NULL && i2 != NULL);
717
718	const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1);
719	const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2);
720
721	if (item1 != NULL && item2 != NULL) {
722		return item1->TargetModel()->CompareFolderNamesFirst(
723			item2->TargetModel());
724	}
725
726	return strcasecmp(i1->Label(), i2->Label());
727}
728
729
730int
731BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2)
732{
733	ThrowOnAssert(i1 != NULL && i2 != NULL);
734
735	return strcasecmp(i1->Label(), i2->Label());
736}
737
738
739void
740BNavMenu::DoneBuildingItemList()
741{
742	// add sorted items to menu
743	if (TrackerSettings().SortFolderNamesFirst())
744		fItemList->SortItems(CompareFolderNamesFirstOne);
745	else
746		fItemList->SortItems(CompareOne);
747
748	// if the parent link should be shown, it will be the first
749	// entry in the menu - but don't add the item if we're already
750	// at the file system's root
751	if ((fFlags & kShowParent) != 0) {
752		BDirectory directory(&fNavDir);
753		BEntry entry(&fNavDir);
754		if (!directory.IsRootDirectory()
755			&& entry.GetParent(&entry) == B_OK) {
756			Model model(&entry, true);
757			BLooper* looper;
758			AddNavParentDir(&model, fMessage.what,
759				fMessenger.Target(&looper));
760		}
761	}
762
763	int32 count = fItemList->CountItems();
764	for (int32 index = 0; index < count; index++)
765		AddItem(fItemList->ItemAt(index));
766
767	fItemList->MakeEmpty();
768
769	if (count == 0) {
770		BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0);
771		item->SetEnabled(false);
772		AddItem(item);
773	}
774
775	SetTargetForItems(fMessenger);
776}
777
778
779int32
780BNavMenu::GetMaxMenuWidth(void)
781{
782	return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth);
783}
784
785
786void
787BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target,
788	bool populateSubmenu)
789{
790	BMessage* message = new BMessage((uint32)what);
791	message->AddRef("refs", model->EntryRef());
792	ModelMenuItem* item = NULL;
793
794	if (populateSubmenu) {
795		BNavMenu* navMenu = new BNavMenu(model->Name(), what, target);
796		navMenu->SetNavDir(model->EntryRef());
797		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook,
798			&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
799		item = new ModelMenuItem(model, navMenu);
800		item->SetMessage(message);
801	} else
802		item = new ModelMenuItem(model, model->Name(), message);
803
804	AddItem(item);
805}
806
807
808void
809BNavMenu::AddNavParentDir(const char* name,const Model* model,
810	uint32 what, BHandler* target)
811{
812	BNavMenu* menu = new BNavMenu(name, what, target);
813	menu->SetNavDir(model->EntryRef());
814	menu->SetShowParent(true);
815	menu->InitTrackingHook(fTrackingHook.fTrackingHook,
816		&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
817
818	BMenuItem* item = new SpecialModelMenuItem(model, menu);
819	BMessage* message = new BMessage(what);
820	message->AddRef("refs", model->EntryRef());
821	item->SetMessage(message);
822
823	AddItem(item);
824}
825
826
827void
828BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target)
829{
830	AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target);
831}
832
833
834void
835BNavMenu::SetShowParent(bool show)
836{
837	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
838}
839
840
841void
842BNavMenu::SetTypesList(const BObjectList<BString>* list)
843{
844	if (list != NULL)
845		*fTypesList = *list;
846	else
847		fTypesList->MakeEmpty();
848}
849
850
851const BObjectList<BString>*
852BNavMenu::TypesList() const
853{
854	return fTypesList;
855}
856
857
858void
859BNavMenu::SetTarget(const BMessenger& messenger)
860{
861	fMessenger = messenger;
862}
863
864
865BMessenger
866BNavMenu::Target()
867{
868	return fMessenger;
869}
870
871
872TrackingHookData*
873BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*),
874	const BMessenger* target, const BMessage* dragMessage)
875{
876	fTrackingHook.fTrackingHook = hook;
877	if (target != NULL)
878		fTrackingHook.fTarget = *target;
879
880	fTrackingHook.fDragMessage = dragMessage;
881	SetTrackingHookDeep(this, hook, &fTrackingHook);
882
883	return &fTrackingHook;
884}
885
886
887void
888BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*),
889	void* state)
890{
891	menu->SetTrackingHook(func, state);
892	int32 count = menu->CountItems();
893	for (int32 index = 0 ; index < count; index++) {
894		BMenuItem* item = menu->ItemAt(index);
895		if (item == NULL)
896			continue;
897
898		BMenu* submenu = item->Submenu();
899		if (submenu != NULL)
900			SetTrackingHookDeep(submenu, func, state);
901	}
902}
903
904
905//	#pragma mark - BPopUpNavMenu
906
907
908BPopUpNavMenu::BPopUpNavMenu(const char* title)
909	:
910	BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL),
911	fTrackThread(-1)
912{
913}
914
915
916BPopUpNavMenu::~BPopUpNavMenu()
917{
918	_WaitForTrackThread();
919}
920
921
922void
923BPopUpNavMenu::_WaitForTrackThread()
924{
925	if (fTrackThread >= 0) {
926		status_t status;
927		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
928			;
929	}
930}
931
932
933void
934BPopUpNavMenu::ClearMenu()
935{
936	RemoveItems(0, CountItems(), true);
937
938	fMenuBuilt = false;
939}
940
941
942void
943BPopUpNavMenu::Go(BPoint where)
944{
945	_WaitForTrackThread();
946
947	fWhere = where;
948
949	fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this);
950}
951
952
953bool
954BPopUpNavMenu::IsShowing() const
955{
956	return Window() != NULL && !Window()->IsHidden();
957}
958
959
960BPoint
961BPopUpNavMenu::ScreenLocation()
962{
963	return fWhere;
964}
965
966
967int32
968BPopUpNavMenu::_TrackThread(void* _menu)
969{
970	BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu);
971
972	menu->Show();
973
974	BMenuItem* result = menu->Track();
975	if (result != NULL)
976		static_cast<BInvoker*>(result)->Invoke();
977
978	menu->Hide();
979
980	return 0;
981}
982