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