xref: /haiku/src/kits/tracker/NavMenu.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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 		RemoveItems(0, fItemList->CountItems(), true);
396 
397 		delete fItemList;
398 		fItemList = NULL;
399 	}
400 }
401 
402 
403 bool
404 BNavMenu::StartBuildingItemList()
405 {
406 	BEntry entry;
407 
408 	if (fNavDir.device < 0 || entry.SetTo(&fNavDir, true) != B_OK
409 		|| !entry.Exists()) {
410 		return false;
411 	}
412 
413 	fItemList = new BObjectList<BMenuItem>(50);
414 
415 	fIteratingDesktop = false;
416 
417 	BDirectory parent;
418 	status_t status = entry.GetParent(&parent);
419 
420 	// if ref is the root item then build list of volume root dirs
421 	fFlags = uint8((fFlags & ~kVolumesOnly)
422 		| (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0));
423 	if (fFlags & kVolumesOnly)
424 		return true;
425 
426 	Model startModel(&entry, true);
427 	if (startModel.InitCheck() != B_OK || !startModel.IsContainer())
428 		return false;
429 
430 	if (startModel.IsQuery()) {
431 		fContainer = new QueryEntryListCollection(&startModel);
432 	} else if (startModel.IsVirtualDirectory()) {
433 		fContainer = new VirtualDirectoryEntryList(&startModel);
434 	} else if (startModel.IsDesktop()) {
435 		fIteratingDesktop = true;
436 		fContainer = DesktopPoseView::InitDesktopDirentIterator(0,
437 			startModel.EntryRef());
438 		AddRootItemsIfNeeded();
439 		AddTrashItem();
440 	} else if (startModel.IsTrash()) {
441 		// the trash window needs to display a union of all the
442 		// trash folders from all the mounted volumes
443 		BVolumeRoster volRoster;
444 		volRoster.Rewind();
445 		BVolume volume;
446 		fContainer = new EntryIteratorList();
447 
448 		while (volRoster.GetNextVolume(&volume) == B_OK) {
449 			if (volume.IsReadOnly() || !volume.IsPersistent())
450 				continue;
451 
452 			BDirectory trashDir;
453 
454 			if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
455 				EntryIteratorList* iteratorList
456 					= dynamic_cast<EntryIteratorList*>(fContainer);
457 
458 				ASSERT(iteratorList != NULL);
459 
460 				if (iteratorList != NULL)
461 					iteratorList->AddItem(new DirectoryEntryList(trashDir));
462 			}
463 		}
464 	} else {
465 		BDirectory* directory = dynamic_cast<BDirectory*>(startModel.Node());
466 
467 		ASSERT(directory != NULL);
468 
469 		if (directory != NULL)
470 			fContainer = new DirectoryEntryList(*directory);
471 	}
472 
473 	if (fContainer == NULL || fContainer->InitCheck() != B_OK)
474 		return false;
475 
476 	fContainer->Rewind();
477 
478 	return true;
479 }
480 
481 
482 void
483 BNavMenu::AddRootItemsIfNeeded()
484 {
485 	BVolumeRoster roster;
486 	roster.Rewind();
487 	BVolume volume;
488 	while (roster.GetNextVolume(&volume) == B_OK) {
489 		BDirectory root;
490 		BEntry entry;
491 		if (!volume.IsPersistent()
492 			|| volume.GetRootDirectory(&root) != B_OK
493 			|| root.GetEntry(&entry) != B_OK) {
494 			continue;
495 		}
496 
497 		Model model(&entry);
498 		AddOneItem(&model);
499 	}
500 }
501 
502 
503 void
504 BNavMenu::AddTrashItem()
505 {
506 	BPath path;
507 	if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) {
508 		BEntry entry(path.Path());
509 		Model model(&entry);
510 		AddOneItem(&model);
511 	}
512 }
513 
514 
515 bool
516 BNavMenu::AddNextItem()
517 {
518 	if ((fFlags & kVolumesOnly) != 0) {
519 		BuildVolumeMenu();
520 		return false;
521 	}
522 
523 	BEntry entry;
524 	if (fContainer->GetNextEntry(&entry) != B_OK) {
525 		// we're finished
526 		return false;
527 	}
528 
529 	if (TrackerSettings().HideDotFiles()) {
530 		char name[B_FILE_NAME_LENGTH];
531 		if (entry.GetName(name) == B_OK && name[0] == '.')
532 			return true;
533 	}
534 
535 	Model model(&entry, true);
536 	if (model.InitCheck() != B_OK) {
537 //		PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
538 		return true;
539 	}
540 
541 	QueryEntryListCollection* queryContainer
542 		= dynamic_cast<QueryEntryListCollection*>(fContainer);
543 	if (queryContainer != NULL && !queryContainer->ShowResultsFromTrash()
544 		&& FSInTrashDir(model.EntryRef())) {
545 		// query entry is in trash and shall not be shown
546 		return true;
547 	}
548 
549 	ssize_t size = -1;
550 	PoseInfo poseInfo;
551 	if (model.Node() != NULL) {
552 		size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
553 			&poseInfo, sizeof(poseInfo));
554 	}
555 
556 	model.CloseNode();
557 
558 	// item might be in invisible
559 	if (size == sizeof(poseInfo)
560 			&& !BPoseView::PoseVisible(&model, &poseInfo)) {
561 		return true;
562 	}
563 
564 	AddOneItem(&model);
565 
566 	return true;
567 }
568 
569 
570 void
571 BNavMenu::AddOneItem(Model* model)
572 {
573 	BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false,
574 		dynamic_cast<BContainerWindow*>(fParentWindow),
575 		fTypesList, &fTrackingHook);
576 
577 	if (item != NULL)
578 		fItemList->AddItem(item);
579 }
580 
581 
582 ModelMenuItem*
583 BNavMenu::NewModelItem(Model* model, const BMessage* invokeMessage,
584 	const BMessenger& target, bool suppressFolderHierarchy,
585 	BContainerWindow* parentWindow, const BObjectList<BString>* typeslist,
586 	TrackingHookData* hook)
587 {
588 	if (model->InitCheck() != B_OK)
589 		return 0;
590 
591 	entry_ref ref;
592 	bool isContainer = false;
593 	if (model->IsSymLink()) {
594 		Model* newResolvedModel = 0;
595 		Model* result = model->LinkTo();
596 
597 		if (result == NULL) {
598 			newResolvedModel = new Model(model->EntryRef(), true, true);
599 
600 			if (newResolvedModel->InitCheck() != B_OK) {
601 				// broken link, still can show though, bail
602 				delete newResolvedModel;
603 				result = NULL;
604 			} else
605 				result = newResolvedModel;
606 		}
607 
608 		if (result != NULL) {
609 			BModelOpener opener(result);
610 				// open the model, if it ain't open already
611 
612 			PoseInfo poseInfo;
613 			ssize_t size = -1;
614 
615 			if (result->Node() != NULL) {
616 				size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
617 					&poseInfo, sizeof(poseInfo));
618 			}
619 
620 			result->CloseNode();
621 
622 			if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result,
623 				&poseInfo)) {
624 				// link target does not want to be visible
625 				delete newResolvedModel;
626 				return NULL;
627 			}
628 
629 			ref = *result->EntryRef();
630 			isContainer = result->IsContainer();
631 		}
632 
633 		model->SetLinkTo(result);
634 	} else {
635 		ref = *model->EntryRef();
636 		isContainer = model->IsContainer();
637 	}
638 
639 	BMessage* message = new BMessage(*invokeMessage);
640 	message->AddRef("refs", model->EntryRef());
641 
642 	menu_info info;
643 	get_menu_info(&info);
644 	BFont menuFont;
645 	menuFont.SetFamilyAndStyle(info.f_family, info.f_style);
646 	menuFont.SetSize(info.font_size);
647 
648 	// truncate name if necessary
649 	BString truncatedString(model->Name());
650 	menuFont.TruncateString(&truncatedString, B_TRUNCATE_END, GetMaxMenuWidth());
651 
652 	ModelMenuItem* item = NULL;
653 	if (!isContainer || suppressFolderHierarchy) {
654 		item = new ModelMenuItem(model, truncatedString.String(), message);
655 		if (invokeMessage->what != B_REFS_RECEIVED)
656 			item->SetEnabled(false);
657 			// the above is broken for FavoritesMenu::AddNextItem, which uses a
658 			// workaround - should fix this
659 	} else {
660 		BNavMenu* menu = new BNavMenu(truncatedString.String(),
661 			invokeMessage->what, target, parentWindow, typeslist);
662 		menu->SetNavDir(&ref);
663 		if (hook != NULL) {
664 			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
665 				hook->fDragMessage);
666 		}
667 
668 		item = new ModelMenuItem(model, menu);
669 		item->SetMessage(message);
670 	}
671 
672 	return item;
673 }
674 
675 
676 void
677 BNavMenu::BuildVolumeMenu()
678 {
679 	BVolumeRoster roster;
680 	BVolume volume;
681 
682 	roster.Rewind();
683 	while (roster.GetNextVolume(&volume) == B_OK) {
684 		if (!volume.IsPersistent())
685 			continue;
686 
687 		BDirectory startDir;
688 		if (volume.GetRootDirectory(&startDir) == B_OK) {
689 			BEntry entry;
690 			startDir.GetEntry(&entry);
691 
692 			Model* model = new Model(&entry);
693 			if (model->InitCheck() != B_OK) {
694 				delete model;
695 				continue;
696 			}
697 
698 			BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what,
699 				fMessenger, fParentWindow, fTypesList);
700 
701 			menu->SetNavDir(model->EntryRef());
702 
703 			ASSERT(menu->Name() != NULL);
704 
705 			ModelMenuItem* item = new ModelMenuItem(model, menu);
706 			BMessage* message = new BMessage(fMessage);
707 
708 			message->AddRef("refs", model->EntryRef());
709 
710 			item->SetMessage(message);
711 			fItemList->AddItem(item);
712 			ASSERT(item->Label() != NULL);
713 		}
714 	}
715 }
716 
717 
718 int
719 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2)
720 {
721 	ThrowOnAssert(i1 != NULL && i2 != NULL);
722 
723 	const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1);
724 	const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2);
725 
726 	if (item1 != NULL && item2 != NULL) {
727 		return item1->TargetModel()->CompareFolderNamesFirst(
728 			item2->TargetModel());
729 	}
730 
731 	return strcasecmp(i1->Label(), i2->Label());
732 }
733 
734 
735 int
736 BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2)
737 {
738 	ThrowOnAssert(i1 != NULL && i2 != NULL);
739 
740 	return strcasecmp(i1->Label(), i2->Label());
741 }
742 
743 
744 void
745 BNavMenu::DoneBuildingItemList()
746 {
747 	// add sorted items to menu
748 	if (TrackerSettings().SortFolderNamesFirst())
749 		fItemList->SortItems(CompareFolderNamesFirstOne);
750 	else
751 		fItemList->SortItems(CompareOne);
752 
753 	// if the parent link should be shown, it will be the first
754 	// entry in the menu - but don't add the item if we're already
755 	// at the file system's root
756 	if ((fFlags & kShowParent) != 0) {
757 		BDirectory directory(&fNavDir);
758 		BEntry entry(&fNavDir);
759 		if (!directory.IsRootDirectory()
760 			&& entry.GetParent(&entry) == B_OK) {
761 			Model model(&entry, true);
762 			BLooper* looper;
763 			AddNavParentDir(&model, fMessage.what,
764 				fMessenger.Target(&looper));
765 		}
766 	}
767 
768 	int32 count = fItemList->CountItems();
769 	for (int32 index = 0; index < count; index++)
770 		AddItem(fItemList->ItemAt(index));
771 
772 	fItemList->MakeEmpty();
773 
774 	if (count == 0) {
775 		BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0);
776 		item->SetEnabled(false);
777 		AddItem(item);
778 	}
779 
780 	SetTargetForItems(fMessenger);
781 }
782 
783 
784 int32
785 BNavMenu::GetMaxMenuWidth(void)
786 {
787 	return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth);
788 }
789 
790 
791 void
792 BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target,
793 	bool populateSubmenu)
794 {
795 	BMessage* message = new BMessage((uint32)what);
796 	message->AddRef("refs", model->EntryRef());
797 	ModelMenuItem* item = NULL;
798 
799 	if (populateSubmenu) {
800 		BNavMenu* navMenu = new BNavMenu(model->Name(), what, target);
801 		navMenu->SetNavDir(model->EntryRef());
802 		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook,
803 			&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
804 		item = new ModelMenuItem(model, navMenu);
805 		item->SetMessage(message);
806 	} else
807 		item = new ModelMenuItem(model, model->Name(), message);
808 
809 	AddItem(item);
810 }
811 
812 
813 void
814 BNavMenu::AddNavParentDir(const char* name,const Model* model,
815 	uint32 what, BHandler* target)
816 {
817 	BNavMenu* menu = new BNavMenu(name, what, target);
818 	menu->SetNavDir(model->EntryRef());
819 	menu->SetShowParent(true);
820 	menu->InitTrackingHook(fTrackingHook.fTrackingHook,
821 		&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
822 
823 	BMenuItem* item = new SpecialModelMenuItem(model, menu);
824 	BMessage* message = new BMessage(what);
825 	message->AddRef("refs", model->EntryRef());
826 	item->SetMessage(message);
827 
828 	AddItem(item);
829 }
830 
831 
832 void
833 BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target)
834 {
835 	AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target);
836 }
837 
838 
839 void
840 BNavMenu::SetShowParent(bool show)
841 {
842 	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
843 }
844 
845 
846 void
847 BNavMenu::SetTypesList(const BObjectList<BString>* list)
848 {
849 	if (list != NULL)
850 		*fTypesList = *list;
851 	else
852 		fTypesList->MakeEmpty();
853 }
854 
855 
856 const BObjectList<BString>*
857 BNavMenu::TypesList() const
858 {
859 	return fTypesList;
860 }
861 
862 
863 void
864 BNavMenu::SetTarget(const BMessenger& messenger)
865 {
866 	fMessenger = messenger;
867 }
868 
869 
870 BMessenger
871 BNavMenu::Target()
872 {
873 	return fMessenger;
874 }
875 
876 
877 TrackingHookData*
878 BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*),
879 	const BMessenger* target, const BMessage* dragMessage)
880 {
881 	fTrackingHook.fTrackingHook = hook;
882 	if (target != NULL)
883 		fTrackingHook.fTarget = *target;
884 
885 	fTrackingHook.fDragMessage = dragMessage;
886 	SetTrackingHookDeep(this, hook, &fTrackingHook);
887 
888 	return &fTrackingHook;
889 }
890 
891 
892 void
893 BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*),
894 	void* state)
895 {
896 	menu->SetTrackingHook(func, state);
897 	int32 count = menu->CountItems();
898 	for (int32 index = 0 ; index < count; index++) {
899 		BMenuItem* item = menu->ItemAt(index);
900 		if (item == NULL)
901 			continue;
902 
903 		BMenu* submenu = item->Submenu();
904 		if (submenu != NULL)
905 			SetTrackingHookDeep(submenu, func, state);
906 	}
907 }
908 
909 
910 //	#pragma mark - BPopUpNavMenu
911 
912 
913 BPopUpNavMenu::BPopUpNavMenu(const char* title)
914 	:
915 	BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL),
916 	fTrackThread(-1)
917 {
918 }
919 
920 
921 BPopUpNavMenu::~BPopUpNavMenu()
922 {
923 	_WaitForTrackThread();
924 }
925 
926 
927 void
928 BPopUpNavMenu::_WaitForTrackThread()
929 {
930 	if (fTrackThread >= 0) {
931 		status_t status;
932 		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
933 			;
934 	}
935 }
936 
937 
938 void
939 BPopUpNavMenu::ClearMenu()
940 {
941 	RemoveItems(0, CountItems(), true);
942 
943 	fMenuBuilt = false;
944 }
945 
946 
947 void
948 BPopUpNavMenu::Go(BPoint where)
949 {
950 	_WaitForTrackThread();
951 
952 	fWhere = where;
953 
954 	fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this);
955 }
956 
957 
958 bool
959 BPopUpNavMenu::IsShowing() const
960 {
961 	return Window() != NULL && !Window()->IsHidden();
962 }
963 
964 
965 BPoint
966 BPopUpNavMenu::ScreenLocation()
967 {
968 	return fWhere;
969 }
970 
971 
972 int32
973 BPopUpNavMenu::_TrackThread(void* _menu)
974 {
975 	BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu);
976 
977 	menu->Show();
978 
979 	BMenuItem* result = menu->Track();
980 	if (result != NULL)
981 		static_cast<BInvoker*>(result)->Invoke();
982 
983 	menu->Hide();
984 
985 	return 0;
986 }
987