xref: /haiku/src/kits/tracker/NavMenu.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 	// truncate name if necessary
643 	BString truncatedString(model->Name());
644 	be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
645 		GetMaxMenuWidth());
646 
647 	ModelMenuItem* item = NULL;
648 	if (!isContainer || suppressFolderHierarchy) {
649 		item = new ModelMenuItem(model, truncatedString.String(), message);
650 		if (invokeMessage->what != B_REFS_RECEIVED)
651 			item->SetEnabled(false);
652 			// the above is broken for FavoritesMenu::AddNextItem, which uses a
653 			// workaround - should fix this
654 	} else {
655 		BNavMenu* menu = new BNavMenu(truncatedString.String(),
656 			invokeMessage->what, target, parentWindow, typeslist);
657 		menu->SetNavDir(&ref);
658 		if (hook != NULL) {
659 			menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
660 				hook->fDragMessage);
661 		}
662 
663 		item = new ModelMenuItem(model, menu);
664 		item->SetMessage(message);
665 	}
666 
667 	return item;
668 }
669 
670 
671 void
672 BNavMenu::BuildVolumeMenu()
673 {
674 	BVolumeRoster roster;
675 	BVolume volume;
676 
677 	roster.Rewind();
678 	while (roster.GetNextVolume(&volume) == B_OK) {
679 		if (!volume.IsPersistent())
680 			continue;
681 
682 		BDirectory startDir;
683 		if (volume.GetRootDirectory(&startDir) == B_OK) {
684 			BEntry entry;
685 			startDir.GetEntry(&entry);
686 
687 			Model* model = new Model(&entry);
688 			if (model->InitCheck() != B_OK) {
689 				delete model;
690 				continue;
691 			}
692 
693 			BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what,
694 				fMessenger, fParentWindow, fTypesList);
695 
696 			menu->SetNavDir(model->EntryRef());
697 
698 			ASSERT(menu->Name() != NULL);
699 
700 			ModelMenuItem* item = new ModelMenuItem(model, menu);
701 			BMessage* message = new BMessage(fMessage);
702 
703 			message->AddRef("refs", model->EntryRef());
704 
705 			item->SetMessage(message);
706 			fItemList->AddItem(item);
707 			ASSERT(item->Label() != NULL);
708 		}
709 	}
710 }
711 
712 
713 int
714 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2)
715 {
716 	ThrowOnAssert(i1 != NULL && i2 != NULL);
717 
718 	const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1);
719 	const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2);
720 
721 	if (item1 != NULL && item2 != NULL) {
722 		return item1->TargetModel()->CompareFolderNamesFirst(
723 			item2->TargetModel());
724 	}
725 
726 	return strcasecmp(i1->Label(), i2->Label());
727 }
728 
729 
730 int
731 BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2)
732 {
733 	ThrowOnAssert(i1 != NULL && i2 != NULL);
734 
735 	return strcasecmp(i1->Label(), i2->Label());
736 }
737 
738 
739 void
740 BNavMenu::DoneBuildingItemList()
741 {
742 	// add sorted items to menu
743 	if (TrackerSettings().SortFolderNamesFirst())
744 		fItemList->SortItems(CompareFolderNamesFirstOne);
745 	else
746 		fItemList->SortItems(CompareOne);
747 
748 	// if the parent link should be shown, it will be the first
749 	// entry in the menu - but don't add the item if we're already
750 	// at the file system's root
751 	if ((fFlags & kShowParent) != 0) {
752 		BDirectory directory(&fNavDir);
753 		BEntry entry(&fNavDir);
754 		if (!directory.IsRootDirectory()
755 			&& entry.GetParent(&entry) == B_OK) {
756 			Model model(&entry, true);
757 			BLooper* looper;
758 			AddNavParentDir(&model, fMessage.what,
759 				fMessenger.Target(&looper));
760 		}
761 	}
762 
763 	int32 count = fItemList->CountItems();
764 	for (int32 index = 0; index < count; index++)
765 		AddItem(fItemList->ItemAt(index));
766 
767 	fItemList->MakeEmpty();
768 
769 	if (count == 0) {
770 		BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0);
771 		item->SetEnabled(false);
772 		AddItem(item);
773 	}
774 
775 	SetTargetForItems(fMessenger);
776 }
777 
778 
779 int32
780 BNavMenu::GetMaxMenuWidth(void)
781 {
782 	return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth);
783 }
784 
785 
786 void
787 BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target,
788 	bool populateSubmenu)
789 {
790 	BMessage* message = new BMessage((uint32)what);
791 	message->AddRef("refs", model->EntryRef());
792 	ModelMenuItem* item = NULL;
793 
794 	if (populateSubmenu) {
795 		BNavMenu* navMenu = new BNavMenu(model->Name(), what, target);
796 		navMenu->SetNavDir(model->EntryRef());
797 		navMenu->InitTrackingHook(fTrackingHook.fTrackingHook,
798 			&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
799 		item = new ModelMenuItem(model, navMenu);
800 		item->SetMessage(message);
801 	} else
802 		item = new ModelMenuItem(model, model->Name(), message);
803 
804 	AddItem(item);
805 }
806 
807 
808 void
809 BNavMenu::AddNavParentDir(const char* name,const Model* model,
810 	uint32 what, BHandler* target)
811 {
812 	BNavMenu* menu = new BNavMenu(name, what, target);
813 	menu->SetNavDir(model->EntryRef());
814 	menu->SetShowParent(true);
815 	menu->InitTrackingHook(fTrackingHook.fTrackingHook,
816 		&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
817 
818 	BMenuItem* item = new SpecialModelMenuItem(model, menu);
819 	BMessage* message = new BMessage(what);
820 	message->AddRef("refs", model->EntryRef());
821 	item->SetMessage(message);
822 
823 	AddItem(item);
824 }
825 
826 
827 void
828 BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target)
829 {
830 	AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target);
831 }
832 
833 
834 void
835 BNavMenu::SetShowParent(bool show)
836 {
837 	fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
838 }
839 
840 
841 void
842 BNavMenu::SetTypesList(const BObjectList<BString>* list)
843 {
844 	if (list != NULL)
845 		*fTypesList = *list;
846 	else
847 		fTypesList->MakeEmpty();
848 }
849 
850 
851 const BObjectList<BString>*
852 BNavMenu::TypesList() const
853 {
854 	return fTypesList;
855 }
856 
857 
858 void
859 BNavMenu::SetTarget(const BMessenger& messenger)
860 {
861 	fMessenger = messenger;
862 }
863 
864 
865 BMessenger
866 BNavMenu::Target()
867 {
868 	return fMessenger;
869 }
870 
871 
872 TrackingHookData*
873 BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*),
874 	const BMessenger* target, const BMessage* dragMessage)
875 {
876 	fTrackingHook.fTrackingHook = hook;
877 	if (target != NULL)
878 		fTrackingHook.fTarget = *target;
879 
880 	fTrackingHook.fDragMessage = dragMessage;
881 	SetTrackingHookDeep(this, hook, &fTrackingHook);
882 
883 	return &fTrackingHook;
884 }
885 
886 
887 void
888 BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*),
889 	void* state)
890 {
891 	menu->SetTrackingHook(func, state);
892 	int32 count = menu->CountItems();
893 	for (int32 index = 0 ; index < count; index++) {
894 		BMenuItem* item = menu->ItemAt(index);
895 		if (item == NULL)
896 			continue;
897 
898 		BMenu* submenu = item->Submenu();
899 		if (submenu != NULL)
900 			SetTrackingHookDeep(submenu, func, state);
901 	}
902 }
903 
904 
905 //	#pragma mark - BPopUpNavMenu
906 
907 
908 BPopUpNavMenu::BPopUpNavMenu(const char* title)
909 	:
910 	BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL),
911 	fTrackThread(-1)
912 {
913 }
914 
915 
916 BPopUpNavMenu::~BPopUpNavMenu()
917 {
918 	_WaitForTrackThread();
919 }
920 
921 
922 void
923 BPopUpNavMenu::_WaitForTrackThread()
924 {
925 	if (fTrackThread >= 0) {
926 		status_t status;
927 		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
928 			;
929 	}
930 }
931 
932 
933 void
934 BPopUpNavMenu::ClearMenu()
935 {
936 	RemoveItems(0, CountItems(), true);
937 
938 	fMenuBuilt = false;
939 }
940 
941 
942 void
943 BPopUpNavMenu::Go(BPoint where)
944 {
945 	_WaitForTrackThread();
946 
947 	fWhere = where;
948 
949 	fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this);
950 }
951 
952 
953 bool
954 BPopUpNavMenu::IsShowing() const
955 {
956 	return Window() != NULL && !Window()->IsHidden();
957 }
958 
959 
960 BPoint
961 BPopUpNavMenu::ScreenLocation()
962 {
963 	return fWhere;
964 }
965 
966 
967 int32
968 BPopUpNavMenu::_TrackThread(void* _menu)
969 {
970 	BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu);
971 
972 	menu->Show();
973 
974 	BMenuItem* result = menu->Track();
975 	if (result != NULL)
976 		static_cast<BInvoker*>(result)->Invoke();
977 
978 	menu->Hide();
979 
980 	return 0;
981 }
982