mirror of https://github.com/stella-emu/stella.git
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:
parent
712cfbaeb1
commit
ad9b0e6e75
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue