#include "NavMenu.h"
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <Application.h>
#include <Catalog.h>
#include <Debug.h>
#include <Directory.h>
#include <Locale.h>
#include <Path.h>
#include <Query.h>
#include <Screen.h>
#include <StopWatch.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include "Attributes.h"
#include "Commands.h"
#include "ContainerWindow.h"
#include "DesktopPoseView.h"
#include "FunctionObject.h"
#include "FSUtils.h"
#include "IconMenuItem.h"
#include "MimeTypes.h"
#include "PoseView.h"
#include "QueryPoseView.h"
#include "Thread.h"
#include "Tracker.h"
#include "VirtualDirectoryEntryList.h"
namespace BPrivate {
const int32 kMinMenuWidth = 150;
enum nav_flags {
kVolumesOnly = 1,
kShowParent = 2
};
bool
SpringLoadedFolderCompareMessages(const BMessage* incoming, const BMessage* dragMessage)
{
if (incoming == NULL || dragMessage == NULL)
return false;
bool refsMatch = false;
for (int32 inIndex = 0; incoming->HasRef("refs", inIndex); inIndex++) {
entry_ref inRef;
if (incoming->FindRef("refs", inIndex, &inRef) != B_OK) {
refsMatch = false;
break;
}
bool inRefMatch = false;
for (int32 dragIndex = 0; dragMessage->HasRef("refs", dragIndex);
dragIndex++) {
entry_ref dragRef;
if (dragMessage->FindRef("refs", dragIndex, &dragRef) != B_OK) {
inRefMatch = false;
break;
}
if (inRef == dragRef) {
inRefMatch = true;
break;
}
}
refsMatch = inRefMatch;
if (!inRefMatch)
break;
}
if (refsMatch) {
refsMatch = false;
BPoint incomingPoint;
BPoint dragPoint;
if (incoming->FindPoint("click_pt", &incomingPoint) == B_OK
&& dragMessage->FindPoint("click_pt", &dragPoint) == B_OK) {
refsMatch = (incomingPoint == dragPoint);
}
}
return refsMatch;
}
void
SpringLoadedFolderSetMenuStates(const BMenu* menu,
const BStringList* typeslist)
{
if (menu == NULL || typeslist == NULL || typeslist->IsEmpty())
return;
int32 count = menu->CountItems();
for (int32 index = 0 ; index < count ; index++) {
ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(menu->ItemAt(index));
if (item == NULL)
continue;
const Model* model = item->TargetModel();
if (!model)
continue;
if (model->IsSymLink()) {
BEntry entry(model->EntryRef(), true);
if (entry.InitCheck() == B_OK) {
if (entry.IsDirectory()) {
item->SetEnabled(true);
} else {
Model resolvedModel(&entry);
int32 supported
= resolvedModel.SupportsMimeType(NULL, typeslist);
item->SetEnabled(supported != kDoesNotSupportType);
}
} else {
item->SetEnabled(false);
}
} else if (model->IsDirectory() || model->IsRoot() || model->IsVolume())
item->SetEnabled(true);
else if (model->IsFile() || model->IsExecutable()) {
int32 supported = model->SupportsMimeType(NULL, typeslist);
item->SetEnabled(supported != kDoesNotSupportType);
} else
item->SetEnabled(false);
}
}
void
SpringLoadedFolderAddUniqueTypeToList(entry_ref* ref,
BStringList* typeslist)
{
if (ref == NULL || typeslist == NULL)
return;
BNodeInfo nodeinfo;
BNode node(ref);
if (node.InitCheck() != B_OK)
return;
nodeinfo.SetTo(&node);
char mimestr[B_MIME_TYPE_LENGTH];
if (nodeinfo.GetType(mimestr) == B_OK && strlen(mimestr) > 0) {
if (strcmp(B_LINK_MIMETYPE, mimestr) == 0) {
BEntry entry(ref, true);
if (entry.InitCheck() == B_OK) {
entry_ref resolvedRef;
if (entry.GetRef(&resolvedRef) == B_OK)
SpringLoadedFolderAddUniqueTypeToList(&resolvedRef,
typeslist);
}
}
bool isUnique = true;
int32 count = typeslist->CountStrings();
for (int32 index = 0 ; index < count ; index++) {
if (typeslist->StringAt(index).Compare(mimestr) == 0) {
isUnique = false;
break;
}
}
if (isUnique)
typeslist->Add(mimestr);
}
}
void
SpringLoadedFolderCacheDragData(const BMessage* incoming, BMessage** message,
BStringList** typeslist)
{
if (incoming == NULL)
return;
delete* message;
delete* typeslist;
BMessage* localMessage = new BMessage(*incoming);
BStringList* localTypesList = new BStringList(10);
for (int32 index = 0; incoming->HasRef("refs", index); index++) {
entry_ref ref;
if (incoming->FindRef("refs", index, &ref) != B_OK)
continue;
SpringLoadedFolderAddUniqueTypeToList(&ref, localTypesList);
}
*message = localMessage;
*typeslist = localTypesList;
}
}
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "NavMenu"
BNavMenu::BNavMenu(const char* title, uint32 message, const BHandler* target,
BWindow* parentWindow, const BStringList* list)
:
BSlowMenu(title),
fMessage(message),
fMessenger(target, target->Looper()),
fParentWindow(parentWindow),
fFlags(0),
fItemList(NULL),
fContainer(NULL),
fIteratingDesktop(false),
fTypesList(new BStringList(10))
{
if (list != NULL)
*fTypesList = *list;
InitIconPreloader();
BContainerWindow* source = dynamic_cast<BContainerWindow*>(fParentWindow);
if (source != NULL) {
fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
source->TargetModel()->NodeRef(), sizeof(node_ref));
}
SetTriggersEnabled(false);
}
BNavMenu::BNavMenu(const char* title, uint32 message,
const BMessenger& messenger, BWindow* parentWindow,
const BStringList* list)
:
BSlowMenu(title),
fMessage(message),
fMessenger(messenger),
fParentWindow(parentWindow),
fFlags(0),
fItemList(NULL),
fContainer(NULL),
fIteratingDesktop(false),
fTypesList(new BStringList(10))
{
if (list != NULL)
*fTypesList = *list;
InitIconPreloader();
BContainerWindow* source = dynamic_cast<BContainerWindow*>(fParentWindow);
if (source != NULL) {
fMessage.AddData("nodeRefsToClose", B_RAW_TYPE,
source->TargetModel()->NodeRef(), sizeof(node_ref));
}
SetTriggersEnabled(false);
}
BNavMenu::~BNavMenu()
{
delete fTypesList;
}
void
BNavMenu::AttachedToWindow()
{
BSlowMenu::AttachedToWindow();
SpringLoadedFolderSetMenuStates(this, fTypesList);
ResetTargets();
}
void
BNavMenu::DetachedFromWindow()
{
}
void
BNavMenu::ResetTargets()
{
SetTargetForItems(Target());
}
void
BNavMenu::ForceRebuild()
{
ClearMenuBuildingState();
fMenuBuilt = false;
}
bool
BNavMenu::NeedsToRebuild() const
{
return !fMenuBuilt;
}
void
BNavMenu::SetNavDir(const entry_ref* ref)
{
ForceRebuild();
fNavDir = *ref;
}
void
BNavMenu::ClearMenuBuildingState()
{
delete fContainer;
fContainer = NULL;
if (fItemList != NULL) {
RemoveItems(0, fItemList->CountItems(), true);
delete fItemList;
fItemList = NULL;
}
}
bool
BNavMenu::StartBuildingItemList()
{
BEntry entry;
if (fNavDir.device < 0 || entry.SetTo(&fNavDir, true) != B_OK
|| !entry.Exists()) {
return false;
}
fItemList = new BObjectList<BMenuItem>(50);
fIteratingDesktop = false;
BDirectory parent;
status_t status = entry.GetParent(&parent);
fFlags = uint8((fFlags & ~kVolumesOnly) | (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0));
if ((fFlags & kVolumesOnly) != 0)
return true;
Model startModel(&entry, true);
if (startModel.InitCheck() != B_OK || !startModel.IsContainer())
return false;
if (startModel.IsQuery()) {
fContainer = new QueryEntryListCollection(&startModel);
} else if (startModel.IsVirtualDirectory()) {
fContainer = new VirtualDirectoryEntryList(&startModel);
} else if (startModel.IsDesktop()) {
fIteratingDesktop = true;
fContainer = DesktopPoseView::InitDesktopDirentIterator(0, startModel.EntryRef());
if (TrackerSettings().MountVolumesOntoDesktop())
AddVolumeItems();
else if (TrackerSettings().ShowDisksIcon())
AddRootItem();
AddTrashItem();
} else if (startModel.IsTrash()) {
BVolumeRoster volRoster;
volRoster.Rewind();
BVolume volume;
fContainer = new EntryIteratorList();
while (volRoster.GetNextVolume(&volume) == B_OK) {
if (volume.IsReadOnly() || !volume.IsPersistent() || volume.Capacity() == 0)
continue;
BDirectory trashDir;
if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
EntryIteratorList* iteratorList = dynamic_cast<EntryIteratorList*>(fContainer);
ASSERT(iteratorList != NULL);
if (iteratorList != NULL)
iteratorList->AddItem(new DirectoryEntryList(trashDir));
}
}
} else {
BDirectory* directory = dynamic_cast<BDirectory*>(startModel.Node());
ASSERT(directory != NULL);
if (directory != NULL)
fContainer = new DirectoryEntryList(*directory);
}
if (fContainer == NULL || fContainer->InitCheck() != B_OK)
return false;
fContainer->Rewind();
return true;
}
void
BNavMenu::AddRootItem()
{
BEntry entry("/");
Model model(&entry);
if (model.InitCheck() != B_OK)
return;
AddOneItem(&model);
}
void
BNavMenu::AddVolumeItems()
{
BVolumeRoster roster;
roster.Rewind();
BVolume volume;
BDirectory root;
BEntry entry;
Model model;
while (roster.GetNextVolume(&volume) == B_OK) {
if (volume.InitCheck() != B_OK || !volume.IsPersistent() || volume.Capacity() == 0
|| volume.GetRootDirectory(&root) != B_OK || root.GetEntry(&entry) != B_OK) {
continue;
}
model.SetTo(&entry);
AddOneItem(&model);
}
}
void
BNavMenu::AddTrashItem()
{
BPath path;
if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) {
BEntry entry(path.Path());
Model model(&entry);
AddOneItem(&model);
}
}
bool
BNavMenu::AddNextItem()
{
if ((fFlags & kVolumesOnly) != 0) {
BuildVolumeMenu();
return false;
}
BEntry entry;
if (fContainer->GetNextEntry(&entry) != B_OK) {
return false;
}
if (TrackerSettings().HideDotFiles()) {
char name[B_FILE_NAME_LENGTH];
if (entry.GetName(name) == B_OK && name[0] == '.')
return true;
}
Model model(&entry, true);
if (model.InitCheck() != B_OK) {
return true;
}
if (model.IsTrash())
return true;
QueryEntryListCollection* queryContainer = dynamic_cast<QueryEntryListCollection*>(fContainer);
if (queryContainer != NULL && !queryContainer->ShowResultsFromTrash()
&& FSInTrashDir(model.EntryRef())) {
return true;
}
ssize_t size = -1;
PoseInfo poseInfo;
if (model.Node() != NULL)
size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo));
model.CloseNode();
if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(&model, &poseInfo))
return true;
AddOneItem(&model);
return true;
}
void
BNavMenu::AddOneItem(Model* model)
{
BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false,
dynamic_cast<BContainerWindow*>(fParentWindow),
fTypesList, &fTrackingHook);
if (item != NULL)
fItemList->AddItem(item);
}
ModelMenuItem*
BNavMenu::NewModelItem(Model* model, const BMessage* invokeMessage,
const BMessenger& target, bool suppressFolderHierarchy,
BContainerWindow* parentWindow, const BStringList* typeslist,
TrackingHookData* hook)
{
if (model->InitCheck() != B_OK)
return 0;
entry_ref ref;
bool isContainer = false;
if (model->IsSymLink()) {
Model* newResolvedModel = 0;
Model* result = model->LinkTo();
if (result == NULL) {
newResolvedModel = new Model(model->EntryRef(), true, true);
if (newResolvedModel->InitCheck() != B_OK) {
delete newResolvedModel;
result = NULL;
} else
result = newResolvedModel;
}
if (result != NULL) {
BModelOpener opener(result);
PoseInfo poseInfo;
ssize_t size = -1;
if (result->Node() != NULL) {
size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
&poseInfo, sizeof(poseInfo));
}
result->CloseNode();
if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result,
&poseInfo)) {
delete newResolvedModel;
return NULL;
}
ref = *result->EntryRef();
isContainer = result->IsContainer();
}
model->SetLinkTo(result);
} else {
ref = *model->EntryRef();
isContainer = model->IsContainer();
}
BMessage* message = new BMessage(*invokeMessage);
message->AddRef("refs", model->EntryRef());
menu_info info;
get_menu_info(&info);
BFont menuFont;
menuFont.SetFamilyAndStyle(info.f_family, info.f_style);
menuFont.SetSize(info.font_size);
BString truncatedString(model->Name());
menuFont.TruncateString(&truncatedString, B_TRUNCATE_END, GetMaxMenuWidth());
ModelMenuItem* item = NULL;
if (!isContainer || suppressFolderHierarchy) {
item = new ModelMenuItem(model, truncatedString.String(), message);
if (invokeMessage->what != B_REFS_RECEIVED)
item->SetEnabled(false);
} else {
BNavMenu* menu = new BNavMenu(truncatedString.String(),
invokeMessage->what, target, parentWindow, typeslist);
menu->SetNavDir(&ref);
if (hook != NULL) {
menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
hook->fDragMessage);
}
item = new ModelMenuItem(model, menu);
item->SetMessage(message);
}
return item;
}
void
BNavMenu::BuildVolumeMenu()
{
BVolumeRoster roster;
roster.Rewind();
BVolume volume;
BDirectory startDir;
BEntry entry;
while (roster.GetNextVolume(&volume) == B_OK) {
if (volume.InitCheck() != B_OK || !volume.IsPersistent() || volume.Capacity() == 0)
continue;
if (volume.GetRootDirectory(&startDir) == B_OK) {
startDir.GetEntry(&entry);
Model* model = new Model(&entry);
if (model->InitCheck() != B_OK) {
delete model;
continue;
}
BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what,
fMessenger, fParentWindow, fTypesList);
menu->SetNavDir(model->EntryRef());
ASSERT(menu->Name() != NULL);
ModelMenuItem* item = new ModelMenuItem(model, menu);
BMessage* message = new BMessage(fMessage);
message->AddRef("refs", model->EntryRef());
item->SetMessage(message);
fItemList->AddItem(item);
ASSERT(item->Label() != NULL);
}
}
}
int
BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2)
{
ThrowOnAssert(i1 != NULL && i2 != NULL);
const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1);
const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2);
if (item1 != NULL && item2 != NULL)
return item1->TargetModel()->CompareFolderNamesFirst(item2->TargetModel());
return CompareOne(i1, i2);
}
int
BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2)
{
ThrowOnAssert(i1 != NULL && i2 != NULL);
return NaturalCompare(i1->Label(), i2->Label());
}
void
BNavMenu::DoneBuildingItemList()
{
if (TrackerSettings().SortFolderNamesFirst())
fItemList->SortItems(CompareFolderNamesFirstOne);
else
fItemList->SortItems(CompareOne);
if ((fFlags & kShowParent) != 0) {
BDirectory directory(&fNavDir);
BEntry entry(&fNavDir);
if (!directory.IsRootDirectory() && entry.GetParent(&entry) == B_OK) {
Model model(&entry, true);
BLooper* looper;
AddNavParentDir(&model, fMessage.what, fMessenger.Target(&looper));
}
}
int32 count = fItemList->CountItems();
for (int32 index = 0; index < count; index++)
AddItem(fItemList->ItemAt(index));
fItemList->MakeEmpty();
if (count == 0) {
BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0);
item->SetEnabled(false);
AddItem(item);
}
SetTargetForItems(fMessenger);
}
int32
BNavMenu::GetMaxMenuWidth(void)
{
return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth);
}
void
BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target,
bool populateSubmenu)
{
BMessage* message = new BMessage((uint32)what);
message->AddRef("refs", model->EntryRef());
ModelMenuItem* item = NULL;
if (populateSubmenu) {
BNavMenu* navMenu = new BNavMenu(model->Name(), what, target);
navMenu->SetNavDir(model->EntryRef());
navMenu->InitTrackingHook(fTrackingHook.fTrackingHook,
&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
item = new ModelMenuItem(model, navMenu);
item->SetMessage(message);
} else
item = new ModelMenuItem(model, model->Name(), message);
AddItem(item);
}
void
BNavMenu::AddNavParentDir(const char* name,const Model* model,
uint32 what, BHandler* target)
{
BNavMenu* menu = new BNavMenu(name, what, target);
menu->SetNavDir(model->EntryRef());
menu->SetShowParent(true);
menu->InitTrackingHook(fTrackingHook.fTrackingHook,
&(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
BMenuItem* item = new SpecialModelMenuItem(model, menu);
BMessage* message = new BMessage(what);
message->AddRef("refs", model->EntryRef());
item->SetMessage(message);
AddItem(item);
}
void
BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target)
{
AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target);
}
void
BNavMenu::SetShowParent(bool show)
{
fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0));
}
void
BNavMenu::SetTypesList(const BStringList* list)
{
if (list != NULL)
*fTypesList = *list;
else
fTypesList->MakeEmpty();
}
const BStringList*
BNavMenu::TypesList() const
{
return fTypesList;
}
void
BNavMenu::SetTarget(const BMessenger& messenger)
{
fMessenger = messenger;
}
BMessenger
BNavMenu::Target()
{
return fMessenger;
}
TrackingHookData*
BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*),
const BMessenger* target, const BMessage* dragMessage)
{
fTrackingHook.fTrackingHook = hook;
if (target != NULL)
fTrackingHook.fTarget = *target;
fTrackingHook.fDragMessage = dragMessage;
SetTrackingHookDeep(this, hook, &fTrackingHook);
return &fTrackingHook;
}
void
BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*),
void* state)
{
menu->SetTrackingHook(func, state);
int32 count = menu->CountItems();
for (int32 index = 0 ; index < count; index++) {
BMenuItem* item = menu->ItemAt(index);
if (item == NULL)
continue;
BMenu* submenu = item->Submenu();
if (submenu != NULL)
SetTrackingHookDeep(submenu, func, state);
}
}
BPopUpNavMenu::BPopUpNavMenu(const char* title)
:
BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL),
fTrackThread(-1)
{
}
BPopUpNavMenu::~BPopUpNavMenu()
{
_WaitForTrackThread();
}
void
BPopUpNavMenu::_WaitForTrackThread()
{
if (fTrackThread >= 0) {
status_t status;
while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
;
}
}
void
BPopUpNavMenu::ClearMenu()
{
RemoveItems(0, CountItems(), true);
fMenuBuilt = false;
}
void
BPopUpNavMenu::Go(BPoint where)
{
_WaitForTrackThread();
fWhere = where;
fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this);
}
bool
BPopUpNavMenu::IsShowing() const
{
return Window() != NULL && !Window()->IsHidden();
}
BPoint
BPopUpNavMenu::ScreenLocation()
{
return fWhere;
}
int32
BPopUpNavMenu::_TrackThread(void* _menu)
{
BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu);
menu->Show();
BMenuItem* result = menu->Track();
if (result != NULL)
static_cast<BInvoker*>(result)->Invoke();
menu->Hide();
return 0;
}