All functionality now restored to FileListWidget (pattern matching, ROMs only, etc).

This now uses a lambda function, which is more versatile.  Eventually we may even add regular expressions.
This commit is contained in:
Stephen Anthony 2019-08-17 21:13:15 -02:30
parent 712cfbaeb1
commit ad9b0e6e75
7 changed files with 77 additions and 109 deletions

View File

@ -51,7 +51,8 @@ bool FilesystemNode::exists() const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode,
const NameFilter& filter) const
{
if (!_realNode || !_realNode->isDirectory())
return false;
@ -89,7 +90,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
// Force ZIP c'tor to be called
AbstractFSNodePtr ptr = FilesystemNodeFactory::create(i->getPath(),
FilesystemNodeFactory::Type::ZIP);
fslist.emplace_back(FilesystemNode(ptr));
FilesystemNode node(ptr);
if (filter(node))
fslist.emplace_back(node);
}
else
#endif
@ -98,7 +101,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
if (i->isDirectory())
i->setName(" [" + i->getName() + "]");
fslist.emplace_back(FilesystemNode(i));
FilesystemNode node(i);
if (filter(node))
fslist.emplace_back(node);
}
}

View File

@ -45,6 +45,8 @@
* we can build upon this.
*/
#include <functional>
#include "bspf.hxx"
class FilesystemNode;
@ -70,6 +72,10 @@ class FilesystemNode
*/
enum class ListMode { FilesOnly, DirectoriesOnly, All };
/** Function used to filter the file listing. Returns true if the filename
should be included, else false.*/
using NameFilter = std::function<bool(const FilesystemNode& node)>;
/**
* Create a new pathless FilesystemNode. Since there's no path associated
* with this node, path-related operations (i.e. exists(), isDirectory(),
@ -127,7 +133,8 @@ class FilesystemNode
* @return true if successful, false otherwise (e.g. when the directory
* does not exist).
*/
bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly) const;
bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly,
const NameFilter& filter = [](const FilesystemNode&){ return true; }) const;
/**
* Set/get a string representation of the name of the file. This is can be
@ -264,6 +271,7 @@ class AbstractFSNode
protected:
friend class FilesystemNode;
using ListMode = FilesystemNode::ListMode;
using NameFilter = FilesystemNode::NameFilter;
public:
/**

View File

@ -115,7 +115,9 @@ void BrowserDialog::show(const string& startpath,
{
case FileLoad:
_fileList->setListMode(FilesystemNode::ListMode::All);
_fileList->setFileExtension(ext);
_fileList->setNameFilter([&ext](const FilesystemNode& node) {
return BSPF::endsWithIgnoreCase(node.getName(), ext);
});
_selected->setEditable(false);
_selected->clearFlags(Widget::FLAG_INVISIBLE);
_type->clearFlags(Widget::FLAG_INVISIBLE);
@ -123,7 +125,9 @@ void BrowserDialog::show(const string& startpath,
case FileSave:
_fileList->setListMode(FilesystemNode::ListMode::All);
_fileList->setFileExtension(ext);
_fileList->setNameFilter([&ext](const FilesystemNode& node) {
return BSPF::endsWithIgnoreCase(node.getName(), ext);
});
_selected->setEditable(false); // FIXME - disable user input for now
_selected->clearFlags(Widget::FLAG_INVISIBLE);
_type->clearFlags(Widget::FLAG_INVISIBLE);
@ -131,6 +135,7 @@ void BrowserDialog::show(const string& startpath,
case Directories:
_fileList->setListMode(FilesystemNode::ListMode::DirectoriesOnly);
_fileList->setNameFilter([](const FilesystemNode&) { return true; });
_selected->setEditable(false);
_selected->setFlags(Widget::FLAG_INVISIBLE);
_type->setFlags(Widget::FLAG_INVISIBLE);

View File

@ -20,13 +20,6 @@
#include "bspf.hxx"
/**
TODO:
- extension handling not handled
- add lambda filter to selectively choose files based on pattern
- history of selected folders/files
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileListWidget::FileListWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h)
@ -36,6 +29,9 @@ FileListWidget::FileListWidget(GuiObject* boss, const GUI::Font& font,
{
// This widget is special, in that it catches signals and redirects them
setTarget(this);
// By default, all filenames are valid
_filter = [](const FilesystemNode& node) { return true; };
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -71,14 +67,14 @@ void FileListWidget::setDirectory(const FilesystemNode& node, string select)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FileListWidget::setLocation(const FilesystemNode& node, const string& select)
void FileListWidget::setLocation(const FilesystemNode& node, string select)
{
_node = node;
// Read in the data from the file system (start with an empty list)
_fileList.clear();
_fileList.reserve(512);
_node.getChildren(_fileList, _fsmode);
_node.getChildren(_fileList, _fsmode, _filter);
// Now fill the list widget with the names from the file list
StringList l;
@ -102,10 +98,7 @@ void FileListWidget::selectDirectory()
void FileListWidget::selectParent()
{
if(_node.hasParent())
{
const string& select = !_history.empty() ? _history.pop() : EmptyString;
setLocation(_node.getParent(), select);
}
setLocation(_node.getParent(), !_history.empty() ? _history.pop() : EmptyString);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -35,6 +35,9 @@ class CommandSender;
Note that the ItemActivated signal is not sent when activating a
directory; instead the selection descends into the directory.
Widgets wishing to enforce their own filename filtering are able
to use a 'NameFilter' as described below.
*/
class FileListWidget : public StringListWidget
{
@ -49,9 +52,10 @@ class FileListWidget : public StringListWidget
int x, int y, int w, int h);
virtual ~FileListWidget() = default;
/** Determines how to display files/folders */
/** Determines how to display files/folders; either setDirectory or reload
must be called after any of these are called. */
void setListMode(FilesystemNode::ListMode mode) { _fsmode = mode; }
void setFileExtension(const string& ext) { _extension = ext; } // TODO - re-implement this
void setNameFilter(const FilesystemNode::NameFilter& filter) { _filter = filter; }
/**
Set initial directory, and optionally select the given item.
@ -69,12 +73,15 @@ class FileListWidget : public StringListWidget
void reload();
/** Gets current node(s) */
const FilesystemNode& selected() const { return _fileList[_selected]; }
const FilesystemNode& selected() {
_selected = BSPF::clamp(_selected, 0u, uInt32(_fileList.size()-1));
return _fileList[_selected];
}
const FilesystemNode& currentDir() const { return _node; }
private:
/** Very similar to setDirectory(), but also updates the history */
void setLocation(const FilesystemNode& node, const string& select = EmptyString);
void setLocation(const FilesystemNode& node, string select = EmptyString);
/** Descend into currently selected directory */
void selectDirectory();
@ -83,12 +90,11 @@ class FileListWidget : public StringListWidget
private:
FilesystemNode::ListMode _fsmode;
FilesystemNode::NameFilter _filter;
FilesystemNode _node;
FSList _fileList;
Common::FixedStack<string> _history;
string _extension;
uInt32 _selected;
private:

View File

@ -43,45 +43,10 @@
#include "Version.hxx"
#include "LauncherDialog.hxx"
/**
TODO:
- show all files / only ROMs
- connect to 'matchPattern'
- create lambda filter to pass these to FileListWidget
*/
#if 0
// TODO - rough contents of lambda filter
FSList files;
files.reserve(2048);
myCurrentNode.getChildren(files, FilesystemNode::ListMode::All);
// Add '[..]' to indicate previous folder
if(myCurrentNode.hasParent())
myGameList->appendGame(" [..]", "", "", true);
// Now add the directory entries
bool domatch = myPattern && myPattern->getText() != "";
for(const auto& f: files)
{
bool isDir = f.isDirectory();
const string& name = isDir ? (" [" + f.getName() + "]") : f.getName();
// Do we want to show only ROMs or all files?
if(!isDir && myShowOnlyROMs && !Bankswitch::isValidRomName(f))
continue;
// Skip over files that don't match the pattern in the 'pattern' textbox
if(domatch && !isDir && !matchPattern(name, myPattern->getText()))
continue;
myGameList->appendGame(name, f.getPath(), "", isDir);
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LauncherDialog::LauncherDialog(OSystem& osystem, DialogContainer& parent,
int x, int y, int w, int h)
: Dialog(osystem, parent, x, y, w, h),
LauncherDialog::LauncherDialog(OSystem& osystem, DialogContainer& parent,
int x, int y, int w, int h)
: Dialog(osystem, parent, x, y, w, h),
myStartButton(nullptr),
myPrevDirButton(nullptr),
myOptionsButton(nullptr),
@ -349,6 +314,27 @@ void LauncherDialog::updateUI()
loadRomInfo();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LauncherDialog::applyFiltering()
{
myList->setNameFilter(
[&](const FilesystemNode& node) {
if(!node.isDirectory())
{
// Do we want to show only ROMs or all files?
if(myShowOnlyROMs && !Bankswitch::isValidRomName(node))
return false;
// Skip over files that don't match the pattern in the 'pattern' textbox
if(myPattern && myPattern->getText() != "" &&
!BSPF::containsIgnoreCase(node.getName(), myPattern->getText()))
return false;
}
return true;
}
);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LauncherDialog::loadRomInfo()
{
@ -384,47 +370,7 @@ void LauncherDialog::showOnlyROMs(bool state)
{
myShowOnlyROMs = state;
instance().settings().setValue("launcherroms", state);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool LauncherDialog::matchPattern(const string& s, const string& pattern) const
{
// This method is modelled after strcasestr, which we don't use
// because it isn't guaranteed to be available everywhere
// The strcasestr uses the KMP algorithm when the comparisons
// reach a certain point, but since we'll be dealing with relatively
// short strings, I think the overhead of building a KMP table
// each time would be slower than the brute force method used here
const char* haystack = s.c_str();
const char* needle = pattern.c_str();
uInt8 b = tolower(*needle);
needle++;
for(;; haystack++)
{
if(*haystack == '\0') /* No match */
return false;
/* The first character matches */
if(tolower(*haystack) == b)
{
const char* rhaystack = haystack + 1;
const char* rneedle = needle;
for(;; rhaystack++, rneedle++)
{
if(*rneedle == '\0') /* Found a match */
return true;
if(*rhaystack == '\0') /* No match */
return false;
/* Nothing in this round */
if(tolower(*rhaystack) != tolower(*rneedle))
break;
}
}
}
applyFiltering();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -565,6 +511,11 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd,
myEventHandled = true;
break;
case EditableWidget::kChangedCmd:
applyFiltering(); // pattern matching taken care of directly in this method
reload();
break;
case kQuitCmd:
close();
instance().eventHandler().quit();

View File

@ -48,7 +48,6 @@ class LauncherDialog : public Dialog
public:
// These must be accessible from dialogs created by this class
enum {
kAllfilesCmd = 'lalf', // show all files (or ROMs only)
kLoadROMCmd = 'STRT', // load currently selected ROM
kRomDirChosenCmd = 'romc' // rom dir chosen
};
@ -97,12 +96,12 @@ class LauncherDialog : public Dialog
void loadConfig() override;
void updateUI();
void applyFiltering();
void loadRom();
void loadRomInfo();
void handleContextMenu();
void showOnlyROMs(bool state);
bool matchPattern(const string& s, const string& pattern) const;
void openSettings();
private:
@ -134,9 +133,10 @@ class LauncherDialog : public Dialog
bool myEventHandled;
enum {
kPrevDirCmd = 'PRVD',
kOptionsCmd = 'OPTI',
kQuitCmd = 'QUIT'
kAllfilesCmd = 'lalf', // show all files (or ROMs only)
kPrevDirCmd = 'PRVD',
kOptionsCmd = 'OPTI',
kQuitCmd = 'QUIT'
};
private: