6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
35 // NavMenu is a hierarchical menu of volumes, folders, files and queries
36 // displays icons, uses the SlowMenu API for full interruptability
47 #include <Application.h>
50 #include <Directory.h>
55 #include <StopWatch.h>
57 #include <VolumeRoster.h>
59 #include "Attributes.h"
61 #include "ContainerWindow.h"
62 #include "DesktopPoseView.h"
63 #include "FunctionObject.h"
65 #include "IconMenuItem.h"
66 #include "MimeTypes.h"
68 #include "QueryPoseView.h"
71 #include "VirtualDirectoryEntryList.h"
76 const int32 kMinMenuWidth
= 150;
85 SpringLoadedFolderCompareMessages(const BMessage
* incoming
,
86 const BMessage
* dragMessage
)
88 if (incoming
== NULL
|| dragMessage
== NULL
)
91 bool refsMatch
= false;
92 for (int32 inIndex
= 0; incoming
->HasRef("refs", inIndex
); inIndex
++) {
94 if (incoming
->FindRef("refs", inIndex
, &inRef
) != B_OK
) {
99 bool inRefMatch
= false;
100 for (int32 dragIndex
= 0; dragMessage
->HasRef("refs", dragIndex
);
103 if (dragMessage
->FindRef("refs", dragIndex
, &dragRef
) != B_OK
) {
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
) {
114 refsMatch
= inRefMatch
;
120 // If all the refs match try and see if this is a new drag with
121 // the same drag contents.
123 BPoint incomingPoint
;
125 if (incoming
->FindPoint("click_pt", &incomingPoint
) == B_OK
126 && dragMessage
->FindPoint("click_pt", &dragPoint
) == B_OK
) {
127 refsMatch
= (incomingPoint
== dragPoint
);
136 SpringLoadedFolderSetMenuStates(const BMenu
* menu
,
137 const BObjectList
<BString
>* typeslist
)
139 if (menu
== NULL
|| typeslist
== NULL
|| typeslist
->IsEmpty())
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
));
150 const Model
* model
= item
->TargetModel();
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);
162 // other, check its support
163 Model
resolvedModel(&entry
);
165 = resolvedModel
.SupportsMimeType(NULL
, typeslist
);
166 item
->SetEnabled(supported
!= kDoesNotSupportType
);
169 // bad entry ref (bad symlink?), disable
170 item
->SetEnabled(false);
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
);
179 item
->SetEnabled(false);
185 SpringLoadedFolderAddUniqueTypeToList(entry_ref
* ref
,
186 BObjectList
<BString
>* typeslist
)
188 if (ref
== NULL
|| typeslist
== NULL
)
191 // get the mime type for the current ref
194 if (node
.InitCheck() != B_OK
)
197 nodeinfo
.SetTo(&node
);
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
,
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) {
224 typeslist
->AddItem(new BString(mimestr
));
230 SpringLoadedFolderCacheDragData(const BMessage
* incoming
, BMessage
** message
,
231 BObjectList
<BString
>** typeslist
)
233 if (incoming
== NULL
)
239 BMessage
* localMessage
= new BMessage(*incoming
);
240 BObjectList
<BString
>* localTypesList
= new BObjectList
<BString
>(10, true);
242 for (int32 index
= 0; incoming
->HasRef("refs", index
); index
++) {
244 if (incoming
->FindRef("refs", index
, &ref
) != B_OK
)
247 SpringLoadedFolderAddUniqueTypeToList(&ref
, localTypesList
);
250 *message
= localMessage
;
251 *typeslist
= localTypesList
;
257 // #pragma mark - BNavMenu
260 #undef B_TRANSLATION_CONTEXT
261 #define B_TRANSLATION_CONTEXT "NavMenu"
264 BNavMenu::BNavMenu(const char* title
, uint32 message
, const BHandler
* target
,
265 BWindow
* parentWindow
, const BObjectList
<BString
>* list
)
269 fMessenger(target
, target
->Looper()),
270 fParentWindow(parentWindow
),
274 fIteratingDesktop(false),
275 fTypesList(new BObjectList
<BString
>(10, true))
282 SetFont(be_plain_font
);
284 // add the parent window to the invocation message so that it
285 // can be closed if option modifier held down during invocation
286 BContainerWindow
* originatingWindow
=
287 dynamic_cast<BContainerWindow
*>(fParentWindow
);
288 if (originatingWindow
!= NULL
) {
289 fMessage
.AddData("nodeRefsToClose", B_RAW_TYPE
,
290 originatingWindow
->TargetModel()->NodeRef(), sizeof(node_ref
));
293 // too long to have triggers
294 SetTriggersEnabled(false);
298 BNavMenu::BNavMenu(const char* title
, uint32 message
,
299 const BMessenger
& messenger
, BWindow
* parentWindow
,
300 const BObjectList
<BString
>* list
)
304 fMessenger(messenger
),
305 fParentWindow(parentWindow
),
309 fIteratingDesktop(false),
310 fTypesList(new BObjectList
<BString
>(10, true))
317 SetFont(be_plain_font
);
319 // add the parent window to the invocation message so that it
320 // can be closed if option modifier held down during invocation
321 BContainerWindow
* originatingWindow
=
322 dynamic_cast<BContainerWindow
*>(fParentWindow
);
323 if (originatingWindow
!= NULL
) {
324 fMessage
.AddData("nodeRefsToClose", B_RAW_TYPE
,
325 originatingWindow
->TargetModel()->NodeRef(), sizeof (node_ref
));
328 // too long to have triggers
329 SetTriggersEnabled(false);
333 BNavMenu::~BNavMenu()
340 BNavMenu::AttachedToWindow()
342 BSlowMenu::AttachedToWindow();
344 SpringLoadedFolderSetMenuStates(this, fTypesList
);
345 // If dragging, (fTypesList != NULL) set the menu items enabled state
346 // relative to the ability to handle an item in the drag message.
348 // allow an opportunity to reset the target for each of the items
353 BNavMenu::DetachedFromWindow()
359 BNavMenu::ResetTargets()
361 SetTargetForItems(Target());
366 BNavMenu::ForceRebuild()
368 ClearMenuBuildingState();
374 BNavMenu::NeedsToRebuild() const
381 BNavMenu::SetNavDir(const entry_ref
* ref
)
384 // reset the slow menu building mechanism so we can add more stuff
391 BNavMenu::ClearMenuBuildingState()
396 // item list is non-owning, need to delete the items because
397 // they didn't get added to the menu
398 if (fItemList
!= NULL
) {
399 int32 count
= fItemList
->CountItems();
400 for (int32 index
= count
- 1; index
>= 0; index
--)
401 delete RemoveItem(index
);
410 BNavMenu::StartBuildingItemList()
414 if (fNavDir
.device
< 0 || entry
.SetTo(&fNavDir
, true) != B_OK
415 || !entry
.Exists()) {
419 fItemList
= new BObjectList
<BMenuItem
>(50);
421 fIteratingDesktop
= false;
424 status_t status
= entry
.GetParent(&parent
);
426 // if ref is the root item then build list of volume root dirs
427 fFlags
= uint8((fFlags
& ~kVolumesOnly
)
428 | (status
== B_ENTRY_NOT_FOUND
? kVolumesOnly
: 0));
429 if (fFlags
& kVolumesOnly
)
432 Model
startModel(&entry
, true);
433 if (startModel
.InitCheck() != B_OK
|| !startModel
.IsContainer())
436 if (startModel
.IsQuery())
437 fContainer
= new QueryEntryListCollection(&startModel
);
438 else if (startModel
.IsVirtualDirectory())
439 fContainer
= new VirtualDirectoryEntryList(&startModel
);
440 else if (startModel
.IsDesktop()) {
441 fIteratingDesktop
= true;
442 fContainer
= DesktopPoseView::InitDesktopDirentIterator(
443 0, startModel
.EntryRef());
444 AddRootItemsIfNeeded();
446 } else if (startModel
.IsTrash()) {
447 // the trash window needs to display a union of all the
448 // trash folders from all the mounted volumes
449 BVolumeRoster volRoster
;
452 fContainer
= new EntryIteratorList();
454 while (volRoster
.GetNextVolume(&volume
) == B_OK
) {
455 if (volume
.IsReadOnly() || !volume
.IsPersistent())
460 if (FSGetTrashDir(&trashDir
, volume
.Device()) == B_OK
) {
461 EntryIteratorList
* iteratorList
462 = dynamic_cast<EntryIteratorList
*>(fContainer
);
464 ASSERT(iteratorList
!= NULL
);
466 if (iteratorList
!= NULL
)
467 iteratorList
->AddItem(new DirectoryEntryList(trashDir
));
471 BDirectory
* directory
= dynamic_cast<BDirectory
*>(startModel
.Node());
473 ASSERT(directory
!= NULL
);
475 if (directory
!= NULL
)
476 fContainer
= new DirectoryEntryList(*directory
);
479 if (fContainer
== NULL
|| fContainer
->InitCheck() != B_OK
)
482 fContainer
->Rewind();
489 BNavMenu::AddRootItemsIfNeeded()
491 BVolumeRoster roster
;
494 while (roster
.GetNextVolume(&volume
) == B_OK
) {
497 if (!volume
.IsPersistent()
498 || volume
.GetRootDirectory(&root
) != B_OK
499 || root
.GetEntry(&entry
) != B_OK
) {
510 BNavMenu::AddTrashItem()
513 if (find_directory(B_TRASH_DIRECTORY
, &path
) == B_OK
) {
514 BEntry
entry(path
.Path());
522 BNavMenu::AddNextItem()
524 if ((fFlags
& kVolumesOnly
) != 0) {
530 if (fContainer
->GetNextEntry(&entry
) != B_OK
) {
535 if (TrackerSettings().HideDotFiles()) {
536 char name
[B_FILE_NAME_LENGTH
];
537 if (entry
.GetName(name
) == B_OK
&& name
[0] == '.')
541 Model
model(&entry
, true);
542 if (model
.InitCheck() != B_OK
) {
543 // PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
547 QueryEntryListCollection
* queryContainer
548 = dynamic_cast<QueryEntryListCollection
*>(fContainer
);
549 if (queryContainer
!= NULL
&& !queryContainer
->ShowResultsFromTrash()
550 && FSInTrashDir(model
.EntryRef())) {
551 // query entry is in trash and shall not be shown
557 if (model
.Node() != NULL
) {
558 size
= model
.Node()->ReadAttr(kAttrPoseInfo
, B_RAW_TYPE
, 0,
559 &poseInfo
, sizeof(poseInfo
));
564 // item might be in invisible
565 if (size
== sizeof(poseInfo
)
566 && !BPoseView::PoseVisible(&model
, &poseInfo
)) {
577 BNavMenu::AddOneItem(Model
* model
)
579 BMenuItem
* item
= NewModelItem(model
, &fMessage
, fMessenger
, false,
580 dynamic_cast<BContainerWindow
*>(fParentWindow
),
581 fTypesList
, &fTrackingHook
);
584 fItemList
->AddItem(item
);
589 BNavMenu::NewModelItem(Model
* model
, const BMessage
* invokeMessage
,
590 const BMessenger
& target
, bool suppressFolderHierarchy
,
591 BContainerWindow
* parentWindow
, const BObjectList
<BString
>* typeslist
,
592 TrackingHookData
* hook
)
594 if (model
->InitCheck() != B_OK
)
598 bool isContainer
= false;
599 if (model
->IsSymLink()) {
600 Model
* newResolvedModel
= 0;
601 Model
* result
= model
->LinkTo();
603 if (result
== NULL
) {
604 newResolvedModel
= new Model(model
->EntryRef(), true, true);
606 if (newResolvedModel
->InitCheck() != B_OK
) {
607 // broken link, still can show though, bail
608 delete newResolvedModel
;
611 result
= newResolvedModel
;
614 if (result
!= NULL
) {
615 BModelOpener
opener(result
);
616 // open the model, if it ain't open already
621 if (result
->Node() != NULL
) {
622 size
= result
->Node()->ReadAttr(kAttrPoseInfo
, B_RAW_TYPE
, 0,
623 &poseInfo
, sizeof(poseInfo
));
628 if (size
== sizeof(poseInfo
) && !BPoseView::PoseVisible(result
,
630 // link target does not want to be visible
631 delete newResolvedModel
;
635 ref
= *result
->EntryRef();
636 isContainer
= result
->IsContainer();
639 model
->SetLinkTo(result
);
641 ref
= *model
->EntryRef();
642 isContainer
= model
->IsContainer();
645 BMessage
* message
= new BMessage(*invokeMessage
);
646 message
->AddRef("refs", model
->EntryRef());
648 // truncate name if necessary
649 BString
truncatedString(model
->Name());
650 be_plain_font
->TruncateString(&truncatedString
, B_TRUNCATE_END
,
653 ModelMenuItem
* item
= NULL
;
654 if (!isContainer
|| suppressFolderHierarchy
) {
655 item
= new ModelMenuItem(model
, truncatedString
.String(), message
);
656 if (invokeMessage
->what
!= B_REFS_RECEIVED
)
657 item
->SetEnabled(false);
658 // the above is broken for FavoritesMenu::AddNextItem, which uses a
659 // workaround - should fix this
661 BNavMenu
* menu
= new BNavMenu(truncatedString
.String(),
662 invokeMessage
->what
, target
, parentWindow
, typeslist
);
663 menu
->SetNavDir(&ref
);
665 menu
->InitTrackingHook(hook
->fTrackingHook
, &(hook
->fTarget
),
669 item
= new ModelMenuItem(model
, menu
);
670 item
->SetMessage(message
);
678 BNavMenu::BuildVolumeMenu()
680 BVolumeRoster roster
;
684 while (roster
.GetNextVolume(&volume
) == B_OK
) {
685 if (!volume
.IsPersistent())
689 if (volume
.GetRootDirectory(&startDir
) == B_OK
) {
691 startDir
.GetEntry(&entry
);
693 Model
* model
= new Model(&entry
);
694 if (model
->InitCheck() != B_OK
) {
699 BNavMenu
* menu
= new BNavMenu(model
->Name(), fMessage
.what
,
700 fMessenger
, fParentWindow
, fTypesList
);
702 menu
->SetNavDir(model
->EntryRef());
704 ASSERT(menu
->Name() != NULL
);
706 ModelMenuItem
* item
= new ModelMenuItem(model
, menu
);
707 BMessage
* message
= new BMessage(fMessage
);
709 message
->AddRef("refs", model
->EntryRef());
711 item
->SetMessage(message
);
712 fItemList
->AddItem(item
);
713 ASSERT(item
->Label() != NULL
);
720 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem
* i1
, const BMenuItem
* i2
)
722 ThrowOnAssert(i1
!= NULL
&& i2
!= NULL
);
724 const ModelMenuItem
* item1
= dynamic_cast<const ModelMenuItem
*>(i1
);
725 const ModelMenuItem
* item2
= dynamic_cast<const ModelMenuItem
*>(i2
);
727 if (item1
!= NULL
&& item2
!= NULL
) {
728 return item1
->TargetModel()->CompareFolderNamesFirst(
729 item2
->TargetModel());
732 return strcasecmp(i1
->Label(), i2
->Label());
737 BNavMenu::CompareOne(const BMenuItem
* i1
, const BMenuItem
* i2
)
739 ThrowOnAssert(i1
!= NULL
&& i2
!= NULL
);
741 return strcasecmp(i1
->Label(), i2
->Label());
746 BNavMenu::DoneBuildingItemList()
748 // add sorted items to menu
749 if (TrackerSettings().SortFolderNamesFirst())
750 fItemList
->SortItems(CompareFolderNamesFirstOne
);
752 fItemList
->SortItems(CompareOne
);
754 // if the parent link should be shown, it will be the first
755 // entry in the menu - but don't add the item if we're already
756 // at the file system's root
757 if ((fFlags
& kShowParent
) != 0) {
758 BDirectory
directory(&fNavDir
);
759 BEntry
entry(&fNavDir
);
760 if (!directory
.IsRootDirectory()
761 && entry
.GetParent(&entry
) == B_OK
) {
762 Model
model(&entry
, true);
764 AddNavParentDir(&model
, fMessage
.what
,
765 fMessenger
.Target(&looper
));
769 int32 count
= fItemList
->CountItems();
770 for (int32 index
= 0; index
< count
; index
++)
771 AddItem(fItemList
->ItemAt(index
));
773 fItemList
->MakeEmpty();
776 BMenuItem
* item
= new BMenuItem(B_TRANSLATE("Empty folder"), 0);
777 item
->SetEnabled(false);
781 SetTargetForItems(fMessenger
);
786 BNavMenu::GetMaxMenuWidth(void)
788 return std::max((int32
)(BScreen().Frame().Width() / 4), kMinMenuWidth
);
793 BNavMenu::AddNavDir(const Model
* model
, uint32 what
, BHandler
* target
,
794 bool populateSubmenu
)
796 BMessage
* message
= new BMessage((uint32
)what
);
797 message
->AddRef("refs", model
->EntryRef());
798 ModelMenuItem
* item
= NULL
;
800 if (populateSubmenu
) {
801 BNavMenu
* navMenu
= new BNavMenu(model
->Name(), what
, target
);
802 navMenu
->SetNavDir(model
->EntryRef());
803 navMenu
->InitTrackingHook(fTrackingHook
.fTrackingHook
,
804 &(fTrackingHook
.fTarget
), fTrackingHook
.fDragMessage
);
805 item
= new ModelMenuItem(model
, navMenu
);
806 item
->SetMessage(message
);
808 item
= new ModelMenuItem(model
, model
->Name(), message
);
815 BNavMenu::AddNavParentDir(const char* name
,const Model
* model
,
816 uint32 what
, BHandler
* target
)
818 BNavMenu
* menu
= new BNavMenu(name
, what
, target
);
819 menu
->SetNavDir(model
->EntryRef());
820 menu
->SetShowParent(true);
821 menu
->InitTrackingHook(fTrackingHook
.fTrackingHook
,
822 &(fTrackingHook
.fTarget
), fTrackingHook
.fDragMessage
);
824 BMenuItem
* item
= new SpecialModelMenuItem(model
, menu
);
825 BMessage
* message
= new BMessage(what
);
826 message
->AddRef("refs", model
->EntryRef());
827 item
->SetMessage(message
);
834 BNavMenu::AddNavParentDir(const Model
* model
, uint32 what
, BHandler
* target
)
836 AddNavParentDir(B_TRANSLATE("parent folder"),model
, what
, target
);
841 BNavMenu::SetShowParent(bool show
)
843 fFlags
= uint8((fFlags
& ~kShowParent
) | (show
? kShowParent
: 0));
848 BNavMenu::SetTypesList(const BObjectList
<BString
>* list
)
853 fTypesList
->MakeEmpty();
857 const BObjectList
<BString
>*
858 BNavMenu::TypesList() const
865 BNavMenu::SetTarget(const BMessenger
& messenger
)
867 fMessenger
= messenger
;
879 BNavMenu::InitTrackingHook(bool (*hook
)(BMenu
*, void*),
880 const BMessenger
* target
, const BMessage
* dragMessage
)
882 fTrackingHook
.fTrackingHook
= hook
;
884 fTrackingHook
.fTarget
= *target
;
886 fTrackingHook
.fDragMessage
= dragMessage
;
887 SetTrackingHookDeep(this, hook
, &fTrackingHook
);
889 return &fTrackingHook
;
894 BNavMenu::SetTrackingHookDeep(BMenu
* menu
, bool (*func
)(BMenu
*, void*),
897 menu
->SetTrackingHook(func
, state
);
898 int32 count
= menu
->CountItems();
899 for (int32 index
= 0 ; index
< count
; index
++) {
900 BMenuItem
* item
= menu
->ItemAt(index
);
904 BMenu
* submenu
= item
->Submenu();
906 SetTrackingHookDeep(submenu
, func
, state
);