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