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()) if (!_realNode || !_realNode->isDirectory())
return false; return false;
@ -89,7 +90,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
// Force ZIP c'tor to be called // Force ZIP c'tor to be called
AbstractFSNodePtr ptr = FilesystemNodeFactory::create(i->getPath(), AbstractFSNodePtr ptr = FilesystemNodeFactory::create(i->getPath(),
FilesystemNodeFactory::Type::ZIP); FilesystemNodeFactory::Type::ZIP);
fslist.emplace_back(FilesystemNode(ptr)); FilesystemNode node(ptr);
if (filter(node))
fslist.emplace_back(node);
} }
else else
#endif #endif
@ -98,7 +101,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
if (i->isDirectory()) if (i->isDirectory())
i->setName(" [" + i->getName() + "]"); 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. * we can build upon this.
*/ */
#include <functional>
#include "bspf.hxx" #include "bspf.hxx"
class FilesystemNode; class FilesystemNode;
@ -70,6 +72,10 @@ class FilesystemNode
*/ */
enum class ListMode { FilesOnly, DirectoriesOnly, All }; 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 * Create a new pathless FilesystemNode. Since there's no path associated
* with this node, path-related operations (i.e. exists(), isDirectory(), * 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 * @return true if successful, false otherwise (e.g. when the directory
* does not exist). * 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 * Set/get a string representation of the name of the file. This is can be
@ -264,6 +271,7 @@ class AbstractFSNode
protected: protected:
friend class FilesystemNode; friend class FilesystemNode;
using ListMode = FilesystemNode::ListMode; using ListMode = FilesystemNode::ListMode;
using NameFilter = FilesystemNode::NameFilter;
public: public:
/** /**

View File

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

View File

@ -20,13 +20,6 @@
#include "bspf.hxx" #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, FileListWidget::FileListWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h) 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 // This widget is special, in that it catches signals and redirects them
setTarget(this); 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; _node = node;
// Read in the data from the file system (start with an empty list) // Read in the data from the file system (start with an empty list)
_fileList.clear(); _fileList.clear();
_fileList.reserve(512); _fileList.reserve(512);
_node.getChildren(_fileList, _fsmode); _node.getChildren(_fileList, _fsmode, _filter);
// Now fill the list widget with the names from the file list // Now fill the list widget with the names from the file list
StringList l; StringList l;
@ -102,10 +98,7 @@ void FileListWidget::selectDirectory()
void FileListWidget::selectParent() void FileListWidget::selectParent()
{ {
if(_node.hasParent()) if(_node.hasParent())
{ setLocation(_node.getParent(), !_history.empty() ? _history.pop() : EmptyString);
const string& select = !_history.empty() ? _history.pop() : EmptyString;
setLocation(_node.getParent(), select);
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -35,6 +35,9 @@ class CommandSender;
Note that the ItemActivated signal is not sent when activating a Note that the ItemActivated signal is not sent when activating a
directory; instead the selection descends into the directory. 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 class FileListWidget : public StringListWidget
{ {
@ -49,9 +52,10 @@ class FileListWidget : public StringListWidget
int x, int y, int w, int h); int x, int y, int w, int h);
virtual ~FileListWidget() = default; 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 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. Set initial directory, and optionally select the given item.
@ -69,12 +73,15 @@ class FileListWidget : public StringListWidget
void reload(); void reload();
/** Gets current node(s) */ /** 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; } const FilesystemNode& currentDir() const { return _node; }
private: private:
/** Very similar to setDirectory(), but also updates the history */ /** 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 */ /** Descend into currently selected directory */
void selectDirectory(); void selectDirectory();
@ -83,12 +90,11 @@ class FileListWidget : public StringListWidget
private: private:
FilesystemNode::ListMode _fsmode; FilesystemNode::ListMode _fsmode;
FilesystemNode::NameFilter _filter;
FilesystemNode _node; FilesystemNode _node;
FSList _fileList; FSList _fileList;
Common::FixedStack<string> _history; Common::FixedStack<string> _history;
string _extension;
uInt32 _selected; uInt32 _selected;
private: private:

View File

@ -43,45 +43,10 @@
#include "Version.hxx" #include "Version.hxx"
#include "LauncherDialog.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, LauncherDialog::LauncherDialog(OSystem& osystem, DialogContainer& parent,
int x, int y, int w, int h) int x, int y, int w, int h)
: Dialog(osystem, parent, x, y, w, h), : Dialog(osystem, parent, x, y, w, h),
myStartButton(nullptr), myStartButton(nullptr),
myPrevDirButton(nullptr), myPrevDirButton(nullptr),
myOptionsButton(nullptr), myOptionsButton(nullptr),
@ -349,6 +314,27 @@ void LauncherDialog::updateUI()
loadRomInfo(); 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() void LauncherDialog::loadRomInfo()
{ {
@ -384,47 +370,7 @@ void LauncherDialog::showOnlyROMs(bool state)
{ {
myShowOnlyROMs = state; myShowOnlyROMs = state;
instance().settings().setValue("launcherroms", state); instance().settings().setValue("launcherroms", state);
} applyFiltering();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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;
}
}
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -565,6 +511,11 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd,
myEventHandled = true; myEventHandled = true;
break; break;
case EditableWidget::kChangedCmd:
applyFiltering(); // pattern matching taken care of directly in this method
reload();
break;
case kQuitCmd: case kQuitCmd:
close(); close();
instance().eventHandler().quit(); instance().eventHandler().quit();

View File

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