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