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