mirror of https://github.com/stella-emu/stella.git
More reworking of the file selection mechanism.
- removed GameList, and integrated functionality directly into 'FSList' (which was mostly doing the same thing) - have FSNode::getChildren() relabel directories, instead of Browser and Launcher dialogs - MD5 calculations in ROM launcher are now cached, instead of being recalculated each time a directory is left and re-entered Windows and macOS likely broken for a moment; this will be fixed next.
This commit is contained in:
parent
786f3b2a49
commit
bcca945951
|
@ -134,8 +134,8 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
|
||||||
{
|
{
|
||||||
_path += ("/" + _virtualPath);
|
_path += ("/" + _virtualPath);
|
||||||
_shortPath += ("/" + _virtualPath);
|
_shortPath += ("/" + _virtualPath);
|
||||||
_name = lastPathComponent(_path);
|
|
||||||
}
|
}
|
||||||
|
_name = lastPathComponent(_path);
|
||||||
|
|
||||||
_error = zip_error::NONE;
|
_error = zip_error::NONE;
|
||||||
if(!_realNode->isFile())
|
if(!_realNode->isFile())
|
||||||
|
@ -145,8 +145,7 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode,
|
bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const
|
||||||
bool hidden) const
|
|
||||||
{
|
{
|
||||||
// Files within ZIP archives don't contain children
|
// Files within ZIP archives don't contain children
|
||||||
if(!isDirectory() || _error != zip_error::NONE)
|
if(!isDirectory() || _error != zip_error::NONE)
|
||||||
|
|
|
@ -49,8 +49,10 @@ class FilesystemNodeZIP : public AbstractFSNode
|
||||||
|
|
||||||
bool exists() const override { return _realNode && _realNode->exists(); }
|
bool exists() const override { return _realNode && _realNode->exists(); }
|
||||||
const string& getName() const override { return _name; }
|
const string& getName() const override { return _name; }
|
||||||
|
void setName(const string& name) override { _name = name; }
|
||||||
const string& getPath() const override { return _path; }
|
const string& getPath() const override { return _path; }
|
||||||
string getShortPath() const override { return _shortPath; }
|
string getShortPath() const override { return _shortPath; }
|
||||||
|
bool hasParent() const override { return true; }
|
||||||
bool isDirectory() const override { return _isDirectory; }
|
bool isDirectory() const override { return _isDirectory; }
|
||||||
bool isFile() const override { return _isFile; }
|
bool isFile() const override { return _isFile; }
|
||||||
bool isReadable() const override { return _realNode && _realNode->isReadable(); }
|
bool isReadable() const override { return _realNode && _realNode->isReadable(); }
|
||||||
|
@ -62,7 +64,7 @@ class FilesystemNodeZIP : public AbstractFSNode
|
||||||
bool rename(const string& newfile) override { return false; }
|
bool rename(const string& newfile) override { return false; }
|
||||||
//////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
|
bool getChildren(AbstractFSList& list, ListMode mode) const override;
|
||||||
AbstractFSNodePtr getParent() const override;
|
AbstractFSNodePtr getParent() const override;
|
||||||
|
|
||||||
uInt32 read(ByteBuffer& image) const override;
|
uInt32 read(ByteBuffer& image) const override;
|
||||||
|
|
|
@ -37,7 +37,7 @@ FilesystemNode::FilesystemNode(const string& p)
|
||||||
{
|
{
|
||||||
// Is this potentially a ZIP archive?
|
// Is this potentially a ZIP archive?
|
||||||
#if defined(ZIP_SUPPORT)
|
#if defined(ZIP_SUPPORT)
|
||||||
if(BSPF::containsIgnoreCase(p, ".zip"))
|
if (BSPF::containsIgnoreCase(p, ".zip"))
|
||||||
_realNode = FilesystemNodeFactory::create(p, FilesystemNodeFactory::Type::ZIP);
|
_realNode = FilesystemNodeFactory::create(p, FilesystemNodeFactory::Type::ZIP);
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
|
@ -51,7 +51,7 @@ bool FilesystemNode::exists() const
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, bool hidden) const
|
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
|
||||||
{
|
{
|
||||||
if (!_realNode || !_realNode->isDirectory())
|
if (!_realNode || !_realNode->isDirectory())
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,11 +59,48 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, bool hidden) con
|
||||||
AbstractFSList tmp;
|
AbstractFSList tmp;
|
||||||
tmp.reserve(fslist.capacity());
|
tmp.reserve(fslist.capacity());
|
||||||
|
|
||||||
if (!_realNode->getChildren(tmp, mode, hidden))
|
if (!_realNode->getChildren(tmp, mode))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
std::sort(tmp.begin(), tmp.end(),
|
||||||
|
[](const AbstractFSNodePtr& node1, const AbstractFSNodePtr& node2)
|
||||||
|
{
|
||||||
|
if (node1->isDirectory() != node2->isDirectory())
|
||||||
|
return node1->isDirectory();
|
||||||
|
else
|
||||||
|
return BSPF::compareIgnoreCase(node1->getName(), node2->getName()) < 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add parent node, if it is valid to do so
|
||||||
|
if (hasParent())
|
||||||
|
{
|
||||||
|
FilesystemNode parent = getParent();
|
||||||
|
parent.setName(" [..]");
|
||||||
|
fslist.emplace_back(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And now add the rest of the entries
|
||||||
for (const auto& i: tmp)
|
for (const auto& i: tmp)
|
||||||
|
{
|
||||||
|
#if defined(ZIP_SUPPORT)
|
||||||
|
// Force ZIP c'tor to be called
|
||||||
|
if (BSPF::endsWithIgnoreCase(i->getPath(), ".zip"))
|
||||||
|
{
|
||||||
|
AbstractFSNodePtr ptr = FilesystemNodeFactory::create(i->getPath(),
|
||||||
|
FilesystemNodeFactory::Type::ZIP);
|
||||||
|
fslist.emplace_back(FilesystemNode(ptr));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// Make directories stand out
|
||||||
|
if (i->isDirectory())
|
||||||
|
i->setName(" [" + i->getName() + "]");
|
||||||
|
|
||||||
fslist.emplace_back(FilesystemNode(i));
|
fslist.emplace_back(FilesystemNode(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +111,13 @@ const string& FilesystemNode::getName() const
|
||||||
return _realNode->getName();
|
return _realNode->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
void FilesystemNode::setName(const string& name)
|
||||||
|
{
|
||||||
|
_realNode->setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
const string& FilesystemNode::getPath() const
|
const string& FilesystemNode::getPath() const
|
||||||
{
|
{
|
||||||
|
@ -109,7 +153,7 @@ string FilesystemNode::getPathWithExt(const string& ext) const
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool FilesystemNode::hasParent() const
|
bool FilesystemNode::hasParent() const
|
||||||
{
|
{
|
||||||
return _realNode ? (_realNode->getParent() != nullptr) : false;
|
return _realNode ? _realNode->hasParent() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -164,23 +208,23 @@ uInt32 FilesystemNode::read(ByteBuffer& image) const
|
||||||
uInt32 size = 0;
|
uInt32 size = 0;
|
||||||
|
|
||||||
// File must actually exist
|
// File must actually exist
|
||||||
if(!(exists() && isReadable()))
|
if (!(exists() && isReadable()))
|
||||||
throw runtime_error("File not found/readable");
|
throw runtime_error("File not found/readable");
|
||||||
|
|
||||||
// First let the private subclass attempt to open the file
|
// First let the private subclass attempt to open the file
|
||||||
if((size = _realNode->read(image)) > 0)
|
if ((size = _realNode->read(image)) > 0)
|
||||||
return size;
|
return size;
|
||||||
|
|
||||||
// Otherwise, the default behaviour is to read from a normal C++ ifstream
|
// Otherwise, the default behaviour is to read from a normal C++ ifstream
|
||||||
image = make_unique<uInt8[]>(512 * 1024);
|
image = make_unique<uInt8[]>(512 * 1024);
|
||||||
ifstream in(getPath(), std::ios::binary);
|
ifstream in(getPath(), std::ios::binary);
|
||||||
if(in)
|
if (in)
|
||||||
{
|
{
|
||||||
in.seekg(0, std::ios::end);
|
in.seekg(0, std::ios::end);
|
||||||
std::streampos length = in.tellg();
|
std::streampos length = in.tellg();
|
||||||
in.seekg(0, std::ios::beg);
|
in.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
if(length == 0)
|
if (length == 0)
|
||||||
throw runtime_error("Zero-byte file");
|
throw runtime_error("Zero-byte file");
|
||||||
|
|
||||||
size = std::min(uInt32(length), 512u * 1024u);
|
size = std::min(uInt32(length), 512u * 1024u);
|
||||||
|
|
|
@ -94,18 +94,6 @@ class FilesystemNode
|
||||||
FilesystemNode(const FilesystemNode&) = default;
|
FilesystemNode(const FilesystemNode&) = default;
|
||||||
FilesystemNode& operator=(const FilesystemNode&) = default;
|
FilesystemNode& operator=(const FilesystemNode&) = default;
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the name of this node to the name of another. Directories
|
|
||||||
* go before normal files.
|
|
||||||
*/
|
|
||||||
inline bool operator<(const FilesystemNode& node) const
|
|
||||||
{
|
|
||||||
if (isDirectory() != node.isDirectory())
|
|
||||||
return isDirectory();
|
|
||||||
|
|
||||||
return BSPF::compareIgnoreCase(getName(), node.getName()) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the name of this node to the name of another, testing for
|
* Compare the name of this node to the name of another, testing for
|
||||||
* equality,
|
* equality,
|
||||||
|
@ -139,11 +127,10 @@ 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,
|
bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly) const;
|
||||||
bool hidden = false) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return 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
|
||||||
* used e.g. by detection code that relies on matching the name of a given
|
* used e.g. by detection code that relies on matching the name of a given
|
||||||
* file. But it is *not* suitable for use with fopen / File::open, nor
|
* file. But it is *not* suitable for use with fopen / File::open, nor
|
||||||
* should it be archived.
|
* should it be archived.
|
||||||
|
@ -151,6 +138,7 @@ class FilesystemNode
|
||||||
* @return the file name
|
* @return the file name
|
||||||
*/
|
*/
|
||||||
const string& getName() const;
|
const string& getName() const;
|
||||||
|
void setName(const string& name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a string representation of the file which can be passed to fopen().
|
* Return a string representation of the file which can be passed to fopen().
|
||||||
|
@ -300,12 +288,11 @@ class AbstractFSNode
|
||||||
*
|
*
|
||||||
* @param list List to put the contents of the directory in.
|
* @param list List to put the contents of the directory in.
|
||||||
* @param mode Mode to use while listing the directory.
|
* @param mode Mode to use while listing the directory.
|
||||||
* @param hidden Whether to include hidden files or not in the results.
|
|
||||||
*
|
*
|
||||||
* @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).
|
||||||
*/
|
*/
|
||||||
virtual bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const = 0;
|
virtual bool getChildren(AbstractFSList& list, ListMode mode) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the last component of the path pointed by this FilesystemNode.
|
* Returns the last component of the path pointed by this FilesystemNode.
|
||||||
|
@ -318,6 +305,7 @@ class AbstractFSNode
|
||||||
* implementation for more information.
|
* implementation for more information.
|
||||||
*/
|
*/
|
||||||
virtual const string& getName() const = 0;
|
virtual const string& getName() const = 0;
|
||||||
|
virtual void setName(const string& name) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the 'path' of the current node, usable in fopen().
|
* Returns the 'path' of the current node, usable in fopen().
|
||||||
|
@ -330,6 +318,17 @@ class AbstractFSNode
|
||||||
|
|
||||||
virtual string getShortPath() const = 0;
|
virtual string getShortPath() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether this node has a parent.
|
||||||
|
*/
|
||||||
|
virtual bool hasParent() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent node of this directory.
|
||||||
|
* The parent of the root is 'nullptr'.
|
||||||
|
*/
|
||||||
|
virtual AbstractFSNodePtr getParent() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether this path refers to a directory or not.
|
* Indicates whether this path refers to a directory or not.
|
||||||
*/
|
*/
|
||||||
|
@ -392,12 +391,6 @@ class AbstractFSNode
|
||||||
* a try-catch block.
|
* a try-catch block.
|
||||||
*/
|
*/
|
||||||
virtual uInt32 read(ByteBuffer& buffer) const { return 0; }
|
virtual uInt32 read(ByteBuffer& buffer) const { return 0; }
|
||||||
|
|
||||||
/**
|
|
||||||
* The parent node of this directory.
|
|
||||||
* The parent of the root is the root itself.
|
|
||||||
*/
|
|
||||||
virtual AbstractFSNodePtr getParent() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -18,16 +18,21 @@
|
||||||
#include "ScrollBarWidget.hxx"
|
#include "ScrollBarWidget.hxx"
|
||||||
#include "FileListWidget.hxx"
|
#include "FileListWidget.hxx"
|
||||||
|
|
||||||
#include "Bankswitch.hxx"
|
|
||||||
#include "MD5.hxx"
|
|
||||||
#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)
|
||||||
: StringListWidget(boss, font, x, y, w, h),
|
: StringListWidget(boss, font, x, y, w, h),
|
||||||
_fsmode(FilesystemNode::ListMode::All),
|
_fsmode(FilesystemNode::ListMode::All),
|
||||||
_selectedPos(0)
|
_selected(0)
|
||||||
{
|
{
|
||||||
// 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);
|
||||||
|
@ -45,36 +50,15 @@ void FileListWidget::setLocation(const FilesystemNode& node, string select)
|
||||||
_node = _node.getParent();
|
_node = _node.getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with empty list
|
// Read in the data from the file system (start with an empty list)
|
||||||
_gameList.clear();
|
_fileList.clear();
|
||||||
|
_fileList.reserve(512);
|
||||||
// Read in the data from the file system
|
_node.getChildren(_fileList, _fsmode);
|
||||||
FSList content;
|
|
||||||
content.reserve(512);
|
|
||||||
_node.getChildren(content, _fsmode);
|
|
||||||
|
|
||||||
// Add '[..]' to indicate previous folder
|
|
||||||
if(_node.hasParent())
|
|
||||||
_gameList.appendGame(" [..]", _node.getParent().getPath(), "", true);
|
|
||||||
|
|
||||||
// Now add the directory entries
|
|
||||||
for(const auto& file: content)
|
|
||||||
{
|
|
||||||
string name = file.getName();
|
|
||||||
bool isDir = file.isDirectory();
|
|
||||||
if(isDir)
|
|
||||||
name = " [" + name + "]";
|
|
||||||
else if(!BSPF::endsWithIgnoreCase(name, _extension))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
_gameList.appendGame(name, file.getPath(), "", isDir);
|
|
||||||
}
|
|
||||||
_gameList.sortByName();
|
|
||||||
|
|
||||||
// Now fill the list widget with the contents of the GameList
|
// Now fill the list widget with the contents of the GameList
|
||||||
StringList l;
|
StringList l;
|
||||||
for(uInt32 i = 0; i < _gameList.size(); ++i)
|
for(const auto& file: _fileList)
|
||||||
l.push_back(_gameList.name(i));
|
l.push_back(file.getName());
|
||||||
|
|
||||||
setList(l);
|
setList(l);
|
||||||
setSelected(select);
|
setSelected(select);
|
||||||
|
@ -96,20 +80,7 @@ void FileListWidget::selectParent()
|
||||||
void FileListWidget::reload()
|
void FileListWidget::reload()
|
||||||
{
|
{
|
||||||
if(_node.exists())
|
if(_node.exists())
|
||||||
setLocation(_node, _gameList.name(_selectedPos));
|
setLocation(_node, selected().getName());
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
const string& FileListWidget::selectedMD5()
|
|
||||||
{
|
|
||||||
if(_selected.isDirectory() || !Bankswitch::isValidRomName(_selected))
|
|
||||||
return EmptyString;
|
|
||||||
|
|
||||||
// Make sure we have a valid md5 for this ROM
|
|
||||||
if(_gameList.md5(_selectedPos) == "")
|
|
||||||
_gameList.setMd5(_selectedPos, MD5::hash(_selected));
|
|
||||||
|
|
||||||
return _gameList.md5(_selectedPos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -123,19 +94,16 @@ void FileListWidget::handleCommand(CommandSender* sender, int cmd, int data, int
|
||||||
|
|
||||||
case ListWidget::kSelectionChangedCmd:
|
case ListWidget::kSelectionChangedCmd:
|
||||||
cmd = ItemChanged;
|
cmd = ItemChanged;
|
||||||
_selected = FilesystemNode(_gameList.path(data));
|
_selected = data;
|
||||||
_selectedPos = data;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ListWidget::kActivatedCmd:
|
case ListWidget::kActivatedCmd:
|
||||||
case ListWidget::kDoubleClickedCmd:
|
case ListWidget::kDoubleClickedCmd:
|
||||||
if(_gameList.isDir(data))
|
_selected = data;
|
||||||
|
if(selected().isDirectory())
|
||||||
{
|
{
|
||||||
cmd = ItemChanged;
|
cmd = ItemChanged;
|
||||||
if(_gameList.name(data) == " [..]")
|
setLocation(selected());
|
||||||
selectParent();
|
|
||||||
else
|
|
||||||
setLocation(FilesystemNode(_gameList.path(data)));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cmd = ItemActivated;
|
cmd = ItemActivated;
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
class CommandSender;
|
class CommandSender;
|
||||||
|
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
#include "GameList.hxx"
|
|
||||||
#include "StringListWidget.hxx"
|
#include "StringListWidget.hxx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,26 +63,19 @@ class FileListWidget : public StringListWidget
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
/** Gets current node(s) */
|
/** Gets current node(s) */
|
||||||
const FilesystemNode& selected() const { return _selected; }
|
const FilesystemNode& selected() const { return _fileList[_selected]; }
|
||||||
const FilesystemNode& currentDir() const { return _node; }
|
const FilesystemNode& currentDir() const { return _node; }
|
||||||
|
|
||||||
/** Gets MD5sum of the current node, if it is a file, and caches the result.
|
|
||||||
Otherwise, does nothing.
|
|
||||||
|
|
||||||
@return MD5sum of selected file, else EmptyString
|
|
||||||
*/
|
|
||||||
const string& selectedMD5();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
|
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FilesystemNode::ListMode _fsmode;
|
FilesystemNode::ListMode _fsmode;
|
||||||
FilesystemNode _node, _selected;
|
FilesystemNode _node;
|
||||||
GameList _gameList;
|
FSList _fileList;
|
||||||
|
|
||||||
string _extension;
|
string _extension;
|
||||||
uInt32 _selectedPos;
|
uInt32 _selected;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Following constructors and assignment operators not supported
|
// Following constructors and assignment operators not supported
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// Based on code from KStella - Stella frontend
|
|
||||||
// Copyright (C) 2003-2005 Stephen Anthony
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "GameList.hxx"
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
void GameList::sortByName()
|
|
||||||
{
|
|
||||||
if(myArray.size() < 2)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto cmp = [](const Entry& a, const Entry& b)
|
|
||||||
{
|
|
||||||
// directories always first
|
|
||||||
if(a._isdir != b._isdir)
|
|
||||||
return a._isdir;
|
|
||||||
|
|
||||||
auto it1 = a._name.cbegin(), it2 = b._name.cbegin();
|
|
||||||
|
|
||||||
// Account for ending ']' character in directory entries
|
|
||||||
auto end1 = a._isdir ? a._name.cend() - 1 : a._name.cend();
|
|
||||||
auto end2 = b._isdir ? b._name.cend() - 1 : b._name.cend();
|
|
||||||
|
|
||||||
// Stop when either string's end has been reached
|
|
||||||
while((it1 != end1) && (it2 != end2))
|
|
||||||
{
|
|
||||||
if(toupper(*it1) != toupper(*it2)) // letters differ?
|
|
||||||
return toupper(*it1) < toupper(*it2);
|
|
||||||
|
|
||||||
// proceed to the next character in each string
|
|
||||||
++it1;
|
|
||||||
++it2;
|
|
||||||
}
|
|
||||||
return a._name.size() < b._name.size();
|
|
||||||
};
|
|
||||||
|
|
||||||
sort(myArray.begin(), myArray.end(), cmp);
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
//============================================================================
|
|
||||||
//
|
|
||||||
// SSSS tt lll lll
|
|
||||||
// SS SS tt ll ll
|
|
||||||
// SS tttttt eeee ll ll aaaa
|
|
||||||
// SSSS tt ee ee ll ll aa
|
|
||||||
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
||||||
// SS SS tt ee ll ll aa aa
|
|
||||||
// SSSS ttt eeeee llll llll aaaaa
|
|
||||||
//
|
|
||||||
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
|
|
||||||
// and the Stella Team
|
|
||||||
//
|
|
||||||
// See the file "License.txt" for information on usage and redistribution of
|
|
||||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
||||||
//
|
|
||||||
// Based on code from KStella - Stella frontend
|
|
||||||
// Copyright (C) 2003-2005 Stephen Anthony
|
|
||||||
//============================================================================
|
|
||||||
|
|
||||||
#ifndef GAME_LIST_HXX
|
|
||||||
#define GAME_LIST_HXX
|
|
||||||
|
|
||||||
#include "bspf.hxx"
|
|
||||||
|
|
||||||
/**
|
|
||||||
Holds the list of game info for the ROM launcher.
|
|
||||||
*/
|
|
||||||
class GameList
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
GameList() = default;
|
|
||||||
|
|
||||||
const string& name(uInt32 i) const
|
|
||||||
{ return i < myArray.size() ? myArray[i]._name : EmptyString; }
|
|
||||||
const string& path(uInt32 i) const
|
|
||||||
{ return i < myArray.size() ? myArray[i]._path : EmptyString; }
|
|
||||||
const string& md5(uInt32 i) const
|
|
||||||
{ return i < myArray.size() ? myArray[i]._md5 : EmptyString; }
|
|
||||||
bool isDir(uInt32 i) const
|
|
||||||
{ return i < myArray.size() ? myArray[i]._isdir: false; }
|
|
||||||
|
|
||||||
void setMd5(uInt32 i, const string& md5)
|
|
||||||
{ myArray[i]._md5 = md5; }
|
|
||||||
|
|
||||||
uInt32 size() const { return uInt32(myArray.size()); }
|
|
||||||
void clear() { myArray.clear(); }
|
|
||||||
|
|
||||||
void appendGame(const string& name, const string& path, const string& md5,
|
|
||||||
bool isDir = false) {
|
|
||||||
myArray.emplace_back(name, path, md5, isDir);
|
|
||||||
}
|
|
||||||
void sortByName();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Entry {
|
|
||||||
string _name;
|
|
||||||
string _path;
|
|
||||||
string _md5;
|
|
||||||
bool _isdir;
|
|
||||||
|
|
||||||
Entry(string name, string path, string md5, bool isdir)
|
|
||||||
: _name(name), _path(path), _md5(md5), _isdir(isdir) { }
|
|
||||||
};
|
|
||||||
vector<Entry> myArray;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Following constructors and assignment operators not supported
|
|
||||||
GameList(const GameList&) = delete;
|
|
||||||
GameList(GameList&&) = delete;
|
|
||||||
GameList& operator=(const GameList&) = delete;
|
|
||||||
GameList& operator=(GameList&&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -16,6 +16,7 @@
|
||||||
//============================================================================
|
//============================================================================
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
|
#include "Bankswitch.hxx"
|
||||||
#include "BrowserDialog.hxx"
|
#include "BrowserDialog.hxx"
|
||||||
#include "ContextMenu.hxx"
|
#include "ContextMenu.hxx"
|
||||||
#include "DialogContainer.hxx"
|
#include "DialogContainer.hxx"
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
#include "EditTextWidget.hxx"
|
#include "EditTextWidget.hxx"
|
||||||
#include "FileListWidget.hxx"
|
#include "FileListWidget.hxx"
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
#include "GameList.hxx"
|
#include "MD5.hxx"
|
||||||
#include "OptionsDialog.hxx"
|
#include "OptionsDialog.hxx"
|
||||||
#include "GlobalPropsDialog.hxx"
|
#include "GlobalPropsDialog.hxx"
|
||||||
#include "StellaSettingsDialog.hxx"
|
#include "StellaSettingsDialog.hxx"
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
TODO:
|
TODO:
|
||||||
- show all files / only ROMs
|
- show all files / only ROMs
|
||||||
- connect to 'matchPattern'
|
- connect to 'matchPattern'
|
||||||
- history of selected folders/files
|
- create lambda filter to pass these to FileListWidget
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -246,7 +247,19 @@ const string& LauncherDialog::selectedRom() const
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
const string& LauncherDialog::selectedRomMD5()
|
const string& LauncherDialog::selectedRomMD5()
|
||||||
{
|
{
|
||||||
return myList->selectedMD5();
|
if(currentNode().isDirectory() || !Bankswitch::isValidRomName(currentNode()))
|
||||||
|
return EmptyString;
|
||||||
|
|
||||||
|
// Attempt to conserve memory
|
||||||
|
if(myMD5List.size() > 500)
|
||||||
|
myMD5List.clear();
|
||||||
|
|
||||||
|
// Lookup MD5, and if not present, cache it
|
||||||
|
auto iter = myMD5List.find(currentNode().getPath());
|
||||||
|
if(iter == myMD5List.end())
|
||||||
|
myMD5List[currentNode().getPath()] = MD5::hash(currentNode());
|
||||||
|
|
||||||
|
return myMD5List[currentNode().getPath()];
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -612,7 +625,7 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd,
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
void LauncherDialog::loadRom()
|
void LauncherDialog::loadRom()
|
||||||
{
|
{
|
||||||
const string& result = instance().createConsole(currentNode(), myList->selectedMD5());
|
const string& result = instance().createConsole(currentNode(), selectedRomMD5());
|
||||||
if(result == EmptyString)
|
if(result == EmptyString)
|
||||||
{
|
{
|
||||||
instance().settings().setValue("lastrom", myList->getSelectedString());
|
instance().settings().setValue("lastrom", myList->getSelectedString());
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace GUI {
|
||||||
class MessageBox;
|
class MessageBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "bspf.hxx"
|
#include "bspf.hxx"
|
||||||
#include "Dialog.hxx"
|
#include "Dialog.hxx"
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
|
@ -122,6 +124,7 @@ class LauncherDialog : public Dialog
|
||||||
CheckboxWidget* myAllFiles;
|
CheckboxWidget* myAllFiles;
|
||||||
|
|
||||||
RomInfoWidget* myRomInfoWidget;
|
RomInfoWidget* myRomInfoWidget;
|
||||||
|
std::unordered_map<string,string> myMD5List;
|
||||||
|
|
||||||
int mySelectedItem;
|
int mySelectedItem;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ MODULE_OBJS := \
|
||||||
src/gui/FileListWidget.o \
|
src/gui/FileListWidget.o \
|
||||||
src/gui/Font.o \
|
src/gui/Font.o \
|
||||||
src/gui/GameInfoDialog.o \
|
src/gui/GameInfoDialog.o \
|
||||||
src/gui/GameList.o \
|
|
||||||
src/gui/GlobalPropsDialog.o \
|
src/gui/GlobalPropsDialog.o \
|
||||||
src/gui/HelpDialog.o \
|
src/gui/HelpDialog.o \
|
||||||
src/gui/InputDialog.o \
|
src/gui/InputDialog.o \
|
||||||
|
|
|
@ -19,14 +19,11 @@
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
FilesystemNodeLIBRETRO::FilesystemNodeLIBRETRO()
|
FilesystemNodeLIBRETRO::FilesystemNodeLIBRETRO()
|
||||||
|
: _name("rom"),
|
||||||
|
_isDirectory(false),
|
||||||
|
_isFile(true),
|
||||||
|
_isValid(true)
|
||||||
{
|
{
|
||||||
_displayName = "rom";
|
|
||||||
_path = "";
|
|
||||||
|
|
||||||
_isDirectory = false;
|
|
||||||
_isFile = true;
|
|
||||||
_isPseudoRoot = false;
|
|
||||||
_isValid = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -60,7 +57,7 @@ string FilesystemNodeLIBRETRO::getShortPath() const
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool FilesystemNodeLIBRETRO::
|
bool FilesystemNodeLIBRETRO::
|
||||||
getChildren(AbstractFSList& myList, ListMode mode, bool hidden) const
|
getChildren(AbstractFSList& myList, ListMode mode) const
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,6 @@
|
||||||
|
|
||||||
#include "FSNode.hxx"
|
#include "FSNode.hxx"
|
||||||
|
|
||||||
// TODO - fix isFile() functionality so that it actually determines if something
|
|
||||||
// is a file; for now, it assumes a file if it isn't a directory
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implementation of the Stella file system API based on LIBRETRO API.
|
* Implementation of the Stella file system API based on LIBRETRO API.
|
||||||
*
|
*
|
||||||
|
@ -37,9 +34,11 @@ class FilesystemNodeLIBRETRO : public AbstractFSNode
|
||||||
explicit FilesystemNodeLIBRETRO(const string& path);
|
explicit FilesystemNodeLIBRETRO(const string& path);
|
||||||
|
|
||||||
bool exists() const override;
|
bool exists() const override;
|
||||||
const string& getName() const override { return _displayName; }
|
const string& getName() const override { return _name; }
|
||||||
|
void setName(const string& name) override { _name = name; }
|
||||||
const string& getPath() const override { return _path; }
|
const string& getPath() const override { return _path; }
|
||||||
string getShortPath() const override;
|
string getShortPath() const override;
|
||||||
|
bool hasParent() const override { return false; }
|
||||||
bool isDirectory() const override { return _isDirectory; }
|
bool isDirectory() const override { return _isDirectory; }
|
||||||
bool isFile() const override { return _isFile; }
|
bool isFile() const override { return _isFile; }
|
||||||
bool isReadable() const override;
|
bool isReadable() const override;
|
||||||
|
@ -47,13 +46,13 @@ class FilesystemNodeLIBRETRO : public AbstractFSNode
|
||||||
bool makeDir() override;
|
bool makeDir() override;
|
||||||
bool rename(const string& newfile) override;
|
bool rename(const string& newfile) override;
|
||||||
|
|
||||||
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
|
bool getChildren(AbstractFSList& list, ListMode mode) const override;
|
||||||
AbstractFSNodePtr getParent() const override;
|
AbstractFSNodePtr getParent() const override;
|
||||||
|
|
||||||
uInt32 read(ByteBuffer& image) const override;
|
uInt32 read(ByteBuffer& image) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
string _displayName;
|
string _name;
|
||||||
string _path;
|
string _path;
|
||||||
bool _isDirectory;
|
bool _isDirectory;
|
||||||
bool _isFile;
|
bool _isFile;
|
||||||
|
|
|
@ -53,13 +53,13 @@ FilesystemNodePOSIX::FilesystemNodePOSIX()
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
FilesystemNodePOSIX::FilesystemNodePOSIX(const string& p, bool verify)
|
FilesystemNodePOSIX::FilesystemNodePOSIX(const string& path, bool verify)
|
||||||
: _isValid(true),
|
: _isValid(true),
|
||||||
_isFile(false),
|
_isFile(false),
|
||||||
_isDirectory(true)
|
_isDirectory(true)
|
||||||
{
|
{
|
||||||
// Default to home directory
|
// Default to home directory
|
||||||
_path = p.length() > 0 ? p : "~";
|
_path = path.length() > 0 ? path : "~";
|
||||||
|
|
||||||
// Expand '~' to the HOME environment variable
|
// Expand '~' to the HOME environment variable
|
||||||
if(_path[0] == '~')
|
if(_path[0] == '~')
|
||||||
|
@ -99,8 +99,13 @@ string FilesystemNodePOSIX::getShortPath() const
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode,
|
bool FilesystemNodePOSIX::hasParent() const
|
||||||
bool hidden) const
|
{
|
||||||
|
return _path != "" && _path != ROOT_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode) const
|
||||||
{
|
{
|
||||||
assert(_isDirectory);
|
assert(_isDirectory);
|
||||||
|
|
||||||
|
@ -112,12 +117,8 @@ bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode,
|
||||||
struct dirent* dp;
|
struct dirent* dp;
|
||||||
while ((dp = readdir(dirp)) != nullptr)
|
while ((dp = readdir(dirp)) != nullptr)
|
||||||
{
|
{
|
||||||
// Skip 'invisible' files if necessary
|
// Ignore all hidden files
|
||||||
if (dp->d_name[0] == '.' && !hidden)
|
if (dp->d_name[0] == '.')
|
||||||
continue;
|
|
||||||
|
|
||||||
// Skip '.' and '..' to avoid cycles
|
|
||||||
if ((dp->d_name[0] == '.' && dp->d_name[1] == 0) || (dp->d_name[0] == '.' && dp->d_name[1] == '.'))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
string newPath(_path);
|
string newPath(_path);
|
||||||
|
|
|
@ -62,8 +62,10 @@ class FilesystemNodePOSIX : public AbstractFSNode
|
||||||
|
|
||||||
bool exists() const override { return access(_path.c_str(), F_OK) == 0; }
|
bool exists() const override { return access(_path.c_str(), F_OK) == 0; }
|
||||||
const string& getName() const override { return _displayName; }
|
const string& getName() const override { return _displayName; }
|
||||||
|
void setName(const string& name) override { _displayName = name; }
|
||||||
const string& getPath() const override { return _path; }
|
const string& getPath() const override { return _path; }
|
||||||
string getShortPath() const override;
|
string getShortPath() const override;
|
||||||
|
bool hasParent() const override;
|
||||||
bool isDirectory() const override { return _isDirectory; }
|
bool isDirectory() const override { return _isDirectory; }
|
||||||
bool isFile() const override { return _isFile; }
|
bool isFile() const override { return _isFile; }
|
||||||
bool isReadable() const override { return access(_path.c_str(), R_OK) == 0; }
|
bool isReadable() const override { return access(_path.c_str(), R_OK) == 0; }
|
||||||
|
@ -71,7 +73,7 @@ class FilesystemNodePOSIX : public AbstractFSNode
|
||||||
bool makeDir() override;
|
bool makeDir() override;
|
||||||
bool rename(const string& newfile) override;
|
bool rename(const string& newfile) override;
|
||||||
|
|
||||||
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
|
bool getChildren(AbstractFSList& list, ListMode mode) const override;
|
||||||
AbstractFSNodePtr getParent() const override;
|
AbstractFSNodePtr getParent() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
Loading…
Reference in New Issue