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