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