xref: /haiku/src/kits/tracker/NavMenu.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
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 (FSIsDeskDir(&entry)) {
412 		fIteratingDesktop = true;
413 		fContainer = DesktopPoseView::InitDesktopDirentIterator(0, startModel.EntryRef());
414 		AddRootItemsIfNeeded();
415 	} else if (FSIsTrashDir(&entry)) {
416 		// the trash window needs to display a union of all the
417 		// trash folders from all the mounted volumes
418 		BVolumeRoster volRoster;
419 		volRoster.Rewind();
420 		BVolume volume;
421 		fContainer = new EntryIteratorList();
422 
423 		while (volRoster.GetNextVolume(&volume) == B_OK) {
424 			if (volume.IsReadOnly() || !volume.IsPersistent())
425 				continue;
426 
427 			BDirectory trashDir;
428 
429 			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK)
430 				dynamic_cast<EntryIteratorList *>(fContainer)->
431 					AddItem(new DirectoryEntryList(trashDir));
432 		}
433 	} else
434 		fContainer = new DirectoryEntryList(*dynamic_cast<BDirectory *>
435 			(startModel.Node()));
436 
437 	if (fContainer == NULL || fContainer->InitCheck() != B_OK)
438 		return false;
439 
440 	fContainer->Rewind();
441 
442 	return true;
443 }
444 
445 
446 void
447 BNavMenu::AddRootItemsIfNeeded()
448 {
449 	BVolumeRoster roster;
450 	roster.Rewind();
451 	BVolume volume;
452 	while (roster.GetNextVolume(&volume) == B_OK) {
453 
454 		BDirectory root;
455 		BEntry entry;
456 		if (!volume.IsPersistent()
457 			|| volume.GetRootDirectory(&root) != B_OK
458 			|| root.GetEntry(&entry) != B_OK)
459 			continue;
460 
461 		Model model(&entry);
462 		AddOneItem(&model);
463 	}
464 }
465 
466 
467 bool
468 BNavMenu::AddNextItem()
469 {
470 	if (fFlags & kVolumesOnly) {
471 		BuildVolumeMenu();
472 		return false;
473 	}
474 
475 	// limit nav menus to 500 items only
476 	if (fItemList->CountItems() > 500)
477 		return false;
478 
479 	BEntry entry;
480 	if (fContainer->GetNextEntry(&entry) != B_OK) {
481 		// we're finished
482 		return false;
483 	}
484 
485 	if (TrackerSettings().HideDotFiles()) {
486 		char name[B_FILE_NAME_LENGTH];
487 		if (entry.GetName(name) == B_OK && name[0] == '.')
488 			return true;
489 	}
490 
491 	Model model(&entry, true);
492 	if (model.InitCheck() != B_OK) {
493 //		PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
494 		return true;
495 	}
496 
497 	QueryEntryListCollection *queryContainer
498 		= dynamic_cast<QueryEntryListCollection*>(fContainer);
499 	if (queryContainer && !queryContainer->ShowResultsFromTrash()
500 		&& FSInTrashDir(model.EntryRef())) {
501 		// query entry is in trash and shall not be shown
502 		return true;
503 	}
504 
505 	ssize_t size = -1;
506 	PoseInfo poseInfo;
507 
508 	if (model.Node())
509 		size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
510 			&poseInfo, sizeof(poseInfo));
511 
512 	model.CloseNode();
513 
514 	// item might be in invisible
515 	// ToDo:
516 	// use more of PoseView's filtering here
517 	if ((size == sizeof(poseInfo)
518 			&& !BPoseView::PoseVisible(&model, &poseInfo, false))
519 		|| (fIteratingDesktop && !ShouldShowDesktopPose(fNavDir.device,
520 			&model, &poseInfo))) {
521 //		PRINT(("not showing hidden item %s\n", model.Name()));
522 		return true;
523 	}
524 
525 	AddOneItem(&model);
526 	return true;
527 }
528 
529 
530 void
531 BNavMenu::AddOneItem(Model *model)
532 {
533 	BMenuItem *item = NewModelItem(model, &fMessage, fMessenger, false,
534 		dynamic_cast<BContainerWindow *>(fParentWindow),
535 		fTypesList, &fTrackingHook);
536 
537 	if (item)
538 		fItemList->AddItem(item);
539 }
540 
541 
542 ModelMenuItem *
543 BNavMenu::NewModelItem(Model *model, const BMessage *invokeMessage,
544 	const BMessenger &target, bool suppressFolderHierarchy,
545 	BContainerWindow *parentWindow, const BObjectList<BString> *typeslist,
546 	TrackingHookData *hook)
547 {
548 	if (model->InitCheck() != B_OK)
549 		return 0;
550 	entry_ref ref;
551 	bool container = false;
552 	if (model->IsSymLink()) {
553 
554 		Model *newResolvedModel = 0;
555 		Model *result = model->LinkTo();
556 
557 		if (!result) {
558 			newResolvedModel = new Model(model->EntryRef(), true, true);
559 
560 			if (newResolvedModel->InitCheck() != B_OK) {
561 				// broken link, still can show though, bail
562 				delete newResolvedModel;
563 				result = 0;
564 			} else
565 				result = newResolvedModel;
566 		}
567 
568 		if (result) {
569 			BModelOpener opener(result);
570 				// open the model, if it ain't open already
571 
572 			PoseInfo poseInfo;
573 			ssize_t size = -1;
574 
575 			if (result->Node())
576 				size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
577 					&poseInfo, sizeof(poseInfo));
578 
579 			result->CloseNode();
580 
581 			if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result,
582 				&poseInfo, false)) {
583 				// link target sez it doesn't want to be visible,
584 				// don't show the link
585 				PRINT(("not showing hidden item %s\n", model->Name()));
586 				delete newResolvedModel;
587 				return 0;
588 			}
589 			ref = *result->EntryRef();
590 			container = result->IsContainer();
591 		}
592 		model->SetLinkTo(result);
593 	} else {
594 		ref = *model->EntryRef();
595 		container = model->IsContainer();
596 	}
597 
598 	BMessage *message = new BMessage(*invokeMessage);
599 	message->AddRef("refs", model->EntryRef());
600 
601 	// Truncate the name if necessary
602 	BString truncatedString(model->Name());
603 	be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
604 		GetMaxMenuWidth());
605 
606 	ModelMenuItem *item = NULL;
607 	if (!container || suppressFolderHierarchy) {
608 		item = new ModelMenuItem(model, truncatedString.String(), message);
609 		if (invokeMessage->what != B_REFS_RECEIVED)
610 			item->SetEnabled(false);
611 			// the above is broken for FavoritesMenu::AddNextItem, which uses a
612 			// workaround - should fix this
613 	} else {
614 		BNavMenu *menu = new BNavMenu(truncatedString.String(),
615 			invokeMessage->what, target, parentWindow, typeslist);
616 
617 		menu->SetNavDir(&ref);
618 		if (hook)
619 			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
620 				hook->fDragMessage);
621 
622 		item = new ModelMenuItem(model, menu);
623 		item->SetMessage(message);
624 	}
625 
626 	return item;
627 }
628 
629 
630 void
631 BNavMenu::BuildVolumeMenu()
632 {
633 	BVolumeRoster roster;
634 	BVolume	volume;
635 
636 	roster.Rewind();
637 	while (roster.GetNextVolume(&volume) == B_OK) {
638 
639 		if (!volume.IsPersistent())
640 			continue;
641 
642 		BDirectory startDir;
643 		if (volume.GetRootDirectory(&startDir) == B_OK) {
644 			BEntry entry;
645 			startDir.GetEntry(&entry);
646 
647 			Model *model = new Model(&entry);
648 			if (model->InitCheck() != B_OK) {
649 				delete model;
650 				continue;
651 			}
652 
653 			BNavMenu *menu = new BNavMenu(model->Name(), fMessage.what,
654 				fMessenger, fParentWindow, fTypesList);
655 
656 			menu->SetNavDir(model->EntryRef());
657 
658 			ASSERT(menu->Name());
659 
660 			ModelMenuItem *item = new ModelMenuItem(model, menu);
661 			BMessage *message = new BMessage(fMessage);
662 
663 			message->AddRef("refs", model->EntryRef());
664 
665 			item->SetMessage(message);
666 			fItemList->AddItem(item);
667 			ASSERT(item->Label());
668 
669 		}
670 	}
671 }
672 
673 
674 int
675 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem *i1, const BMenuItem *i2)
676 {
677 	const ModelMenuItem *item1 = dynamic_cast<const ModelMenuItem *>(i1);
678 	const ModelMenuItem *item2 = dynamic_cast<const ModelMenuItem *>(i2);
679 
680 	if (item1 != NULL && item2 != NULL)
681 		return item1->TargetModel()->CompareFolderNamesFirst(item2->TargetModel());
682 
683 	return strcasecmp(i1->Label(), i2->Label());
684 }
685 
686 
687 int
688 BNavMenu::CompareOne(const BMenuItem *i1, const BMenuItem *i2)
689 {
690 	return strcasecmp(i1->Label(), i2->Label());
691 }
692 
693 
694 void
695 BNavMenu::DoneBuildingItemList()
696 {
697 	// add sorted items to menu
698 	if (TrackerSettings().SortFolderNamesFirst())
699 		fItemList->SortItems(CompareFolderNamesFirstOne);
700 	else
701 		fItemList->SortItems(CompareOne);
702 
703 	// if the parent link should be shown, it will be the first
704 	// entry in the menu - but don't add the item if we're already
705 	// at the file system's root
706 	if (fFlags & kShowParent) {
707 		BDirectory directory(&fNavDir);
708 		BEntry entry(&fNavDir);
709 		if (!directory.IsRootDirectory()
710 			&& entry.GetParent(&entry) == B_OK) {
711 			Model model(&entry, true);
712 			BLooper *looper;
713 			AddNavParentDir(&model,fMessage.what,fMessenger.Target(&looper));
714 		}
715 	}
716 
717 	int32 count = fItemList->CountItems();
718 	for (int32 index = 0; index < count; index++)
719 		AddItem(fItemList->ItemAt(index));
720 	fItemList->MakeEmpty();
721 
722 	if (!count) {
723 		BMenuItem *item = new BMenuItem("Empty Folder", 0);
724 		item->SetEnabled(false);
725 		AddItem(item);
726 	}
727 
728 	SetTargetForItems(fMessenger);
729 }
730 
731 
732 int32
733 BNavMenu::GetMaxMenuWidth(void)
734 {
735 	int32 width = (int32)(BScreen().Frame().Width() / 4);
736 	return (width < kMinMenuWidth) ? kMinMenuWidth : width;
737 }
738 
739 
740 void
741 BNavMenu::AddNavDir(const Model *model, uint32 what, BHandler *target,
742 	bool populateSubmenu)
743 {
744 	BMessage *message = new BMessage((uint32)what);
745 	message->AddRef("refs", model->EntryRef());
746 	ModelMenuItem *item = NULL;
747 
748 	if (populateSubmenu) {
749 		BNavMenu *navMenu = new BNavMenu(model->Name(), what, target);
750 		navMenu->SetNavDir(model->EntryRef());
751 		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
752 				fTrackingHook.fDragMessage);
753 		item = new ModelMenuItem(model, navMenu);
754 		item->SetMessage(message);
755 	} else
756 		item = new ModelMenuItem(model, model->Name(), message);
757 
758 	AddItem(item);
759 }
760 
761 
762 void
763 BNavMenu::AddNavParentDir(const char *name,const Model *model,uint32 what,BHandler *target)
764 {
765 	BNavMenu *menu = new BNavMenu(name,what,target);
766 	menu->SetNavDir(model->EntryRef());
767 	menu->SetShowParent(true);
768 	menu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget),
769 			fTrackingHook.fDragMessage);
770 
771 	BMenuItem *item = new SpecialModelMenuItem(model,menu);
772 
773 	BMessage *message = new BMessage(what);
774 	message->AddRef("refs",model->EntryRef());
775 	item->SetMessage(message);
776 
777 	AddItem(item);
778 }
779 
780 
781 void
782 BNavMenu::AddNavParentDir(const Model *model, uint32 what, BHandler *target)
783 {
784 	AddNavParentDir("parent folder",model,what,target);
785 }
786 
787 
788 void
789 BNavMenu::SetShowParent(bool show)
790 {
791 	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
792 }
793 
794 
795 void
796 BNavMenu::SetTypesList(const BObjectList<BString> *list)
797 {
798 	fTypesList = list;
799 }
800 
801 
802 const BObjectList<BString> *
803 BNavMenu::TypesList() const
804 {
805 	return fTypesList;
806 }
807 
808 
809 void
810 BNavMenu::SetTarget(const BMessenger &msngr)
811 {
812 	fMessenger = msngr;
813 }
814 
815 
816 BMessenger
817 BNavMenu::Target()
818 {
819 	return fMessenger;
820 }
821 
822 
823 TrackingHookData *
824 BNavMenu::InitTrackingHook(bool (*hook)(BMenu *, void *), const BMessenger *target,
825 	const BMessage *dragMessage)
826 {
827 	fTrackingHook.fTrackingHook = hook;
828 	if (target)
829 		fTrackingHook.fTarget = *target;
830 	fTrackingHook.fDragMessage = dragMessage;
831 	SetTrackingHookDeep(this, hook, &fTrackingHook);
832 	return &fTrackingHook;
833 }
834 
835 
836 void
837 BNavMenu::SetTrackingHookDeep(BMenu *menu, bool (*func)(BMenu *, void *), void *state)
838 {
839 	menu->SetTrackingHook(func, state);
840 	int32 count = menu->CountItems();
841 	for (int32 index = 0 ; index < count; index++) {
842 		BMenuItem *item = menu->ItemAt(index);
843 		if (!item)
844 			continue;
845 
846 		BMenu *submenu = item->Submenu();
847 		if (submenu)
848 			SetTrackingHookDeep(submenu, func, state);
849 	}
850 }
851 
852