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