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