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
SpringLoadedFolderCompareMessages(const BMessage * incoming,const BMessage * dragMessage)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
SpringLoadedFolderSetMenuStates(const BMenu * menu,const BObjectList<BString> * typeslist)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
SpringLoadedFolderAddUniqueTypeToList(entry_ref * ref,BObjectList<BString> * typeslist)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
SpringLoadedFolderCacheDragData(const BMessage * incoming,BMessage ** message,BObjectList<BString> ** typeslist)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
BNavMenu(const char * title,uint32 message,const BHandler * target,BWindow * parentWindow,const BObjectList<BString> * list)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
BNavMenu(const char * title,uint32 message,const BMessenger & messenger,BWindow * parentWindow,const BObjectList<BString> * list)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
~BNavMenu()329 BNavMenu::~BNavMenu()
330 {
331 delete fTypesList;
332 }
333
334
335 void
AttachedToWindow()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
DetachedFromWindow()349 BNavMenu::DetachedFromWindow()
350 {
351 }
352
353
354 void
ResetTargets()355 BNavMenu::ResetTargets()
356 {
357 SetTargetForItems(Target());
358 }
359
360
361 void
ForceRebuild()362 BNavMenu::ForceRebuild()
363 {
364 ClearMenuBuildingState();
365 fMenuBuilt = false;
366 }
367
368
369 bool
NeedsToRebuild() const370 BNavMenu::NeedsToRebuild() const
371 {
372 return !fMenuBuilt;
373 }
374
375
376 void
SetNavDir(const entry_ref * ref)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
ClearMenuBuildingState()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
StartBuildingItemList()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
AddRootItemsIfNeeded()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
AddTrashItem()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
AddNextItem()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
AddOneItem(Model * model)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*
NewModelItem(Model * model,const BMessage * invokeMessage,const BMessenger & target,bool suppressFolderHierarchy,BContainerWindow * parentWindow,const BObjectList<BString> * typeslist,TrackingHookData * hook)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
BuildVolumeMenu()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
CompareFolderNamesFirstOne(const BMenuItem * i1,const BMenuItem * i2)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
CompareOne(const BMenuItem * i1,const BMenuItem * i2)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
DoneBuildingItemList()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
GetMaxMenuWidth(void)785 BNavMenu::GetMaxMenuWidth(void)
786 {
787 return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth);
788 }
789
790
791 void
AddNavDir(const Model * model,uint32 what,BHandler * target,bool populateSubmenu)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
AddNavParentDir(const char * name,const Model * model,uint32 what,BHandler * target)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
AddNavParentDir(const Model * model,uint32 what,BHandler * target)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
SetShowParent(bool show)840 BNavMenu::SetShowParent(bool show)
841 {
842 fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
843 }
844
845
846 void
SetTypesList(const BObjectList<BString> * list)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>*
TypesList() const857 BNavMenu::TypesList() const
858 {
859 return fTypesList;
860 }
861
862
863 void
SetTarget(const BMessenger & messenger)864 BNavMenu::SetTarget(const BMessenger& messenger)
865 {
866 fMessenger = messenger;
867 }
868
869
870 BMessenger
Target()871 BNavMenu::Target()
872 {
873 return fMessenger;
874 }
875
876
877 TrackingHookData*
InitTrackingHook(bool (* hook)(BMenu *,void *),const BMessenger * target,const BMessage * dragMessage)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
SetTrackingHookDeep(BMenu * menu,bool (* func)(BMenu *,void *),void * state)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
BPopUpNavMenu(const char * title)913 BPopUpNavMenu::BPopUpNavMenu(const char* title)
914 :
915 BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL),
916 fTrackThread(-1)
917 {
918 }
919
920
~BPopUpNavMenu()921 BPopUpNavMenu::~BPopUpNavMenu()
922 {
923 _WaitForTrackThread();
924 }
925
926
927 void
_WaitForTrackThread()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
ClearMenu()939 BPopUpNavMenu::ClearMenu()
940 {
941 RemoveItems(0, CountItems(), true);
942
943 fMenuBuilt = false;
944 }
945
946
947 void
Go(BPoint where)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
IsShowing() const959 BPopUpNavMenu::IsShowing() const
960 {
961 return Window() != NULL && !Window()->IsHidden();
962 }
963
964
965 BPoint
ScreenLocation()966 BPopUpNavMenu::ScreenLocation()
967 {
968 return fWhere;
969 }
970
971
972 int32
_TrackThread(void * _menu)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