diff --git a/docs/index.html b/docs/index.html index d9080cae9..f1a9ecb25 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3689,19 +3689,29 @@

ROM Info Viewer width at 50% , UI sized 1280x900, large launcher font:

-

The 'Show all files' checkbox allows displaying files which do not - have a valid ROM extension. The 'Filter' text box can be used to narrow down - the results in the ROM listing. When this box is empty, all files are shown. - Typing characters here will show only those files that match that - pattern. For example, typing 'Activision' will show only files that - contain the word 'Activision' in their name. This is very useful for - quickly finding a group of related ROMs.

-

Note that the search is not case sensitive, so you don't need to worry about - capital or lower-case letters. Also you can use '*' and '?' as wildcards. E.g. - for '(198?)*atari' only games from the 1980s made by Atari will be listed.

-

When there are least three characters in the filter, the checkbox 'Incl. - subdirectories' gets enabled. When checked, Stella will search files in all - subdirectories too.

+

The dialog items at the top can be used to define the listed files:

+ + +

ROM Launcher Context Menu

diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index 73f09268f..cd631bc7e 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -1747,9 +1747,13 @@ void DebuggerParser::executeRunTo() // Create a progress dialog box to show the progress searching through the // disassembly, since this may be a time-consuming operation ostringstream buf; - buf << "RunTo searching through " << max_iterations << " disassembled instructions"; - ProgressDialog progress(debugger.baseDialog(), debugger.lfont(), buf.str()); + ProgressDialog progress(debugger.baseDialog(), debugger.lfont()); + + buf << "RunTo searching through " << max_iterations << " disassembled instructions" + << progress.ELLIPSIS; + progress.setMessage(buf.str()); progress.setRange(0, max_iterations, 5); + progress.open(); bool done = false; do { @@ -1763,8 +1767,8 @@ void DebuggerParser::executeRunTo() done = (BSPF::findIgnoreCase(next, argStrings[0]) != string::npos); } // Update the progress bar - progress.setProgress(count); - } while(!done && ++count < max_iterations); + progress.incProgress(); + } while(!done && ++count < max_iterations && !progress.isCancelled()); progress.close(); @@ -1789,13 +1793,15 @@ void DebuggerParser::executeRunToPc() uInt32 count = 0; bool done = false; - constexpr uInt32 max_iterations = 1000000; // Create a progress dialog box to show the progress searching through the // disassembly, since this may be a time-consuming operation ostringstream buf; - buf << "RunTo PC searching through " << max_iterations << " instructions"; - ProgressDialog progress(debugger.baseDialog(), debugger.lfont(), buf.str()); - progress.setRange(0, max_iterations, 5); + ProgressDialog progress(debugger.baseDialog(), debugger.lfont()); + + buf << " RunTo PC running" << progress.ELLIPSIS << " "; + progress.setMessage(buf.str()); + progress.setRange(0, 100000, 5); + progress.open(); do { debugger.step(false); @@ -1803,8 +1809,9 @@ void DebuggerParser::executeRunToPc() // Update romlist to point to current PC int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc()); done = (pcline >= 0) && (list[pcline].address == args[0]); - progress.setProgress(count); - } while(!done && ++count < max_iterations/*list.size()*/); + progress.incProgress(); + ++count; + } while(!done && !progress.isCancelled()); progress.close(); if(done) @@ -1953,20 +1960,24 @@ void DebuggerParser::executeStepwhile() Expression* expr = YaccParser::getResult(); int ncycles = 0; uInt32 count = 0; - constexpr uInt32 max_iterations = 1000000; // Create a progress dialog box to show the progress searching through the // disassembly, since this may be a time-consuming operation ostringstream buf; - buf << "stepwhile running through " << max_iterations << " disassembled instructions"; - ProgressDialog progress(debugger.baseDialog(), debugger.lfont(), buf.str()); - progress.setRange(0, max_iterations, 5); + ProgressDialog progress(debugger.baseDialog(), debugger.lfont()); + + buf << "stepwhile running through disassembled instructions" + << progress.ELLIPSIS; + progress.setMessage(buf.str()); + progress.setRange(0, 100000, 5); + progress.open(); do { ncycles += debugger.step(false); - progress.setProgress(count); - } while (expr->evaluate() && ++count < max_iterations); + progress.incProgress(); + ++count; + } while (expr->evaluate() && !progress.isCancelled()); progress.close(); commandResult << "executed " << ncycles << " cycles"; diff --git a/src/emucore/FSNode.cxx b/src/emucore/FSNode.cxx index 6be4078b0..6ff0b6482 100644 --- a/src/emucore/FSNode.cxx +++ b/src/emucore/FSNode.cxx @@ -79,9 +79,10 @@ bool FilesystemNode::exists() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool FilesystemNode::getAllChildren(FSList& fslist, ListMode mode, const NameFilter& filter, - bool includeParentDirectory) const + bool includeParentDirectory, + const CancelCheck& isCancelled) const { - if(getChildren(fslist, mode, filter, includeParentDirectory)) + if(getChildren(fslist, mode, filter, includeParentDirectory, true, isCancelled)) { // Sort only once at the end #if defined(ZIP_SUPPORT) @@ -124,7 +125,6 @@ bool FilesystemNode::getAllChildren(FSList& fslist, ListMode mode, #endif return true; } - return false; } @@ -132,7 +132,8 @@ bool FilesystemNode::getAllChildren(FSList& fslist, ListMode mode, bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, const NameFilter& filter, bool includeChildDirectories, - bool includeParentDirectory) const + bool includeParentDirectory, + const CancelCheck& isCancelled) const { if (!_realNode || !_realNode->isDirectory()) return false; @@ -146,6 +147,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, // when incuding child directories, everything must be sorted once at the end if(!includeChildDirectories) { + if(isCancelled()) + return false; + #if defined(ZIP_SUPPORT) // before sorting, replace single file ZIP archive names with contained file names // because they are displayed using their contained file names @@ -182,6 +186,9 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, // And now add the rest of the entries for (const auto& i: tmp) { + if(isCancelled()) + return false; + #if defined(ZIP_SUPPORT) if (BSPF::endsWithIgnoreCase(i->getPath(), ".zip")) { @@ -214,7 +221,7 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, if(includeChildDirectories) { if(i->isDirectory()) - node.getChildren(fslist, mode, filter, includeChildDirectories, false); + node.getChildren(fslist, mode, filter, includeChildDirectories, false, isCancelled); else // do not add directories in this mode if(filter(node)) @@ -227,7 +234,6 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, } } } - return true; } diff --git a/src/emucore/FSNode.hxx b/src/emucore/FSNode.hxx index b7b93bd5d..05378d185 100644 --- a/src/emucore/FSNode.hxx +++ b/src/emucore/FSNode.hxx @@ -57,6 +57,7 @@ class FilesystemNode /** Function used to filter the file listing. Returns true if the filename should be included, else false.*/ using NameFilter = std::function; + using CancelCheck = std::function const; /** * Create a new pathless FilesystemNode. Since there's no path associated @@ -123,7 +124,8 @@ class FilesystemNode */ bool getAllChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly, const NameFilter& filter = [](const FilesystemNode&) { return true; }, - bool includeParentDirectory = true) const; + bool includeParentDirectory = true, + const CancelCheck& isCancelled = []() { return false; }) const; /** * Return a list of child nodes of this directory node. If called on a node @@ -135,7 +137,8 @@ class FilesystemNode bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly, const NameFilter& filter = [](const FilesystemNode&){ return true; }, bool includeChildDirectories = false, - bool includeParentDirectory = true) const; + bool includeParentDirectory = true, + const CancelCheck& isCancelled = []() { return false; }) const; /** * Set/get a string representation of the name of the file. This is can be diff --git a/src/gui/FileListWidget.cxx b/src/gui/FileListWidget.cxx index 42d497d92..be68d632f 100644 --- a/src/gui/FileListWidget.cxx +++ b/src/gui/FileListWidget.cxx @@ -75,6 +75,9 @@ void FileListWidget::setLocation(const FilesystemNode& node, { progress().resetProgress(); progress().open(); + class FilesystemNode::CancelCheck isCancelled = []() { + return myProgressDialog->isCancelled(); + }; _node = node; @@ -85,21 +88,24 @@ void FileListWidget::setLocation(const FilesystemNode& node, { // Actually this could become HUGE _fileList.reserve(0x2000); - _node.getAllChildren(_fileList, _fsmode, _filter); + _node.getAllChildren(_fileList, _fsmode, _filter, true, isCancelled); } else { _fileList.reserve(0x200); - _node.getChildren(_fileList, _fsmode, _filter); + _node.getChildren(_fileList, _fsmode, _filter, false, true, isCancelled); } - // Now fill the list widget with the names from the file list - StringList l; - for(const auto& file : _fileList) - l.push_back(file.getName()); + if(!isCancelled()) + { + // Now fill the list widget with the names from the file list + StringList l; + for(const auto& file : _fileList) + l.push_back(file.getName()); - setList(l); - setSelected(select); + setList(l); + setSelected(select); + } ListWidget::recalc(); @@ -134,7 +140,7 @@ void FileListWidget::reload() ProgressDialog& FileListWidget::progress() { if(myProgressDialog == nullptr) - myProgressDialog = make_unique(this, _font, "", false); + myProgressDialog = make_unique(this, _font, ""); return *myProgressDialog; } diff --git a/src/gui/LauncherDialog.cxx b/src/gui/LauncherDialog.cxx index 74f08328a..87d0938b4 100644 --- a/src/gui/LauncherDialog.cxx +++ b/src/gui/LauncherDialog.cxx @@ -177,10 +177,8 @@ LauncherDialog::LauncherDialog(OSystem& osystem, DialogContainer& parent, // Show the subdirectories checkbox xpos -= cwSubDirs + LBL_GAP; mySubDirs = new CheckboxWidget(this, font, xpos, ypos, lblSubDirs, kSubDirsCmd); - mySubDirs->setEnabled(false); ostringstream tip; - tip << "Search files in subdirectories too.\n" - << "Filter must have at least " << MIN_SUBDIRS_CHARS << " chars."; + tip << "Search files in subdirectories too."; mySubDirs->setToolTip(tip.str()); // Show the filter input field @@ -780,15 +778,14 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd, break; case EditableWidget::kChangedCmd: + case EditableWidget::kAcceptCmd: { - bool subAllowed = myPattern->getText().length() >= MIN_SUBDIRS_CHARS; - bool subDirs = subAllowed && mySubDirs->getState(); + bool subDirs = mySubDirs->getState(); - mySubDirs->setEnabled(subAllowed); myList->setIncludeSubDirs(subDirs); applyFiltering(); // pattern matching taken care of directly in this method - if(subDirs) + if(subDirs && cmd == EditableWidget::kChangedCmd) { // delay (potentially slow) subdirectories reloads until user stops typing myReloadTime = TimerManager::getTicks() / 1000 + myList->getQuickSelectDelay(); diff --git a/src/gui/LauncherDialog.hxx b/src/gui/LauncherDialog.hxx index a415f1403..a5acc262f 100644 --- a/src/gui/LauncherDialog.hxx +++ b/src/gui/LauncherDialog.hxx @@ -102,7 +102,6 @@ class LauncherDialog : public Dialog static constexpr int MIN_ROMINFO_CHARS = 30; static constexpr int MIN_ROMINFO_ROWS = 7; // full lines static constexpr int MIN_ROMINFO_LINES = 4; // extra lines - static constexpr int MIN_SUBDIRS_CHARS = 3; // minimum filter chars for subdirectory search void setPosition() override { positionAt(0); } void handleKeyDown(StellaKey key, StellaMod mod, bool repeated) override; diff --git a/src/gui/ProgressDialog.cxx b/src/gui/ProgressDialog.cxx index 026e0df82..e36cf6008 100644 --- a/src/gui/ProgressDialog.cxx +++ b/src/gui/ProgressDialog.cxx @@ -18,6 +18,8 @@ #include "bspf.hxx" #include "OSystem.hxx" #include "FrameBuffer.hxx" +#include "EventHandler.hxx" +#include "TimerManager.hxx" #include "Widget.hxx" #include "Dialog.hxx" #include "Font.hxx" @@ -26,7 +28,7 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ProgressDialog::ProgressDialog(GuiObject* boss, const GUI::Font& font, - const string& message, bool openDialog) + const string& message) : Dialog(boss->instance(), boss->parent()), myFont(font) { @@ -35,41 +37,56 @@ ProgressDialog::ProgressDialog(GuiObject* boss, const GUI::Font& font, lineHeight = font.getLineHeight(), VBORDER = fontHeight / 2, HBORDER = fontWidth * 1.25, - VGAP = fontHeight / 4; - int xpos, ypos, lwidth; + VGAP = fontHeight / 4, + buttonHeight = font.getLineHeight() * 1.25, + BTN_BORDER = fontWidth * 2.5, + buttonWidth = font.getStringWidth("Cancel") + BTN_BORDER, + lwidth = font.getStringWidth(message); + + int xpos, ypos; + WidgetArray wid; // Calculate real dimensions - lwidth = font.getStringWidth(message); - _w = HBORDER * 2 + lwidth; - _h = VBORDER * 2 + lineHeight * 2 + VGAP * 2; + _w = HBORDER * 2 + std::max(lwidth, buttonWidth); + _h = VBORDER * 2 + lineHeight * 2 + buttonHeight + VGAP * 6; xpos = HBORDER; ypos = VBORDER; myMessage = new StaticTextWidget(this, font, xpos, ypos, lwidth, fontHeight, message, TextAlign::Center); myMessage->setTextColor(kTextColorEm); - xpos = HBORDER; ypos += lineHeight + VGAP * 2; - mySlider = new SliderWidget(this, font, xpos, ypos, lwidth, lineHeight, "", 0, 0); + ypos += lineHeight + VGAP * 2; + mySlider = new SliderWidget(this, font, xpos, ypos, lwidth, lineHeight, + "", 0, 0); mySlider->setMinValue(1); mySlider->setMaxValue(100); - if(openDialog) - open(); + ypos += lineHeight + VGAP * 4; + ButtonWidget* b = new ButtonWidget(this, font, (_w - buttonWidth) / 2, ypos, + buttonWidth, buttonHeight, "Cancel", + Event::UICancel); + wid.push_back(b); + addCancelWidget(b); + addToFocusList(wid); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ProgressDialog::setMessage(const string& message) { const int fontWidth = myFont.getMaxCharWidth(), - HBORDER = fontWidth * 1.25; - const int lwidth = myFont.getStringWidth(message); + HBORDER = fontWidth * 1.25, + lwidth = myFont.getStringWidth(message), + BTN_BORDER = fontWidth * 2.5, + buttonWidth = myFont.getStringWidth("Cancel") + BTN_BORDER; // Recalculate real dimensions - _w = HBORDER * 2 + lwidth; + _w = HBORDER * 2 + std::max(lwidth, buttonWidth); myMessage->setWidth(lwidth); myMessage->setLabel(message); mySlider->setWidth(lwidth); + + _cancelWidget->setPos((_w - buttonWidth) / 2, _cancelWidget->getTop()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -88,6 +105,7 @@ void ProgressDialog::resetProgress() { myProgress = myStepProgress = 0; mySlider->setValue(0); + myIsCancelled = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -100,11 +118,13 @@ void ProgressDialog::setProgress(int progress) mySlider->setValue(progress % (myFinish - myStart + 1)); // Since this dialog is usually called in a tight loop that doesn't - // yield, we need to manually tell the framebuffer that a redraw is - // necessary + // yield, we need to manually: + // - tell the framebuffer that a redraw is necessary + // - poll the events // This isn't really an ideal solution, since all redrawing and // event handling is suspended until the dialog is closed instance().frameBuffer().update(); + instance().eventHandler().poll(TimerManager::getTicks()); } } @@ -113,3 +133,20 @@ void ProgressDialog::incProgress() { setProgress(++myProgress); } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ProgressDialog::handleCommand(CommandSender* sender, int cmd, + int data, int id) +{ + switch(cmd) + { + case Event::UICancel: + myIsCancelled = true; + break; + + default: + Dialog::handleCommand(sender, cmd, data, 0); + break; + } +} + diff --git a/src/gui/ProgressDialog.hxx b/src/gui/ProgressDialog.hxx index 1b36b9cef..a62fb8cbf 100644 --- a/src/gui/ProgressDialog.hxx +++ b/src/gui/ProgressDialog.hxx @@ -21,6 +21,7 @@ class GuiObject; class StaticTextWidget; class SliderWidget; +class ButtonWidget; #include "bspf.hxx" #include "Dialog.hxx" @@ -29,7 +30,7 @@ class ProgressDialog : public Dialog { public: ProgressDialog(GuiObject* boss, const GUI::Font& font, - const string& message, bool openDialog = true); + const string& message = ""); ~ProgressDialog() override = default; void setMessage(const string& message); @@ -37,6 +38,7 @@ class ProgressDialog : public Dialog void resetProgress(); void setProgress(int progress); void incProgress(); + bool isCancelled() const { return myIsCancelled; } private: const GUI::Font& myFont; @@ -46,6 +48,10 @@ class ProgressDialog : public Dialog int myStart{0}, myFinish{0}, myStep{0}; int myProgress{0}; int myStepProgress{0}; + bool myIsCancelled{false}; + + private: + void handleCommand(CommandSender* sender, int cmd, int data, int id) override; private: // Following constructors and assignment operators not supported diff --git a/src/gui/RomAuditDialog.cxx b/src/gui/RomAuditDialog.cxx index 09c264025..a95e4fce5 100644 --- a/src/gui/RomAuditDialog.cxx +++ b/src/gui/RomAuditDialog.cxx @@ -119,13 +119,17 @@ void RomAuditDialog::auditRoms() // Create a progress dialog box to show the progress of processing // the ROMs, since this is usually a time-consuming operation - ProgressDialog progress(this, instance().frameBuffer().font(), - "Auditing ROM files ..."); + ostringstream buf; + ProgressDialog progress(this, instance().frameBuffer().font()); + + buf << "Auditing ROM files" << ELLIPSIS; + progress.setMessage(buf.str()); progress.setRange(0, int(files.size()) - 1, 5); + progress.open(); Properties props; uInt32 renamed = 0, notfound = 0; - for(uInt32 idx = 0; idx < files.size(); ++idx) + for(uInt32 idx = 0; idx < files.size() && !progress.isCancelled(); ++idx) { string extension; if(files[idx].isFile() && @@ -156,7 +160,7 @@ void RomAuditDialog::auditRoms() } // Update the progress bar, indicating one more ROM has been processed - progress.setProgress(idx); + progress.incProgress(); } progress.close(); diff --git a/src/gui/Widget.cxx b/src/gui/Widget.cxx index e0b9a2389..61b9296cb 100644 --- a/src/gui/Widget.cxx +++ b/src/gui/Widget.cxx @@ -173,6 +173,36 @@ void Widget::drawChain() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Widget::setPosX(int x) +{ + setPos(x, _y); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Widget::setPosY(int y) +{ + setPos(_x, y); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Widget::setPos(int x, int y) +{ + setPos(Common::Point(x, y)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Widget::setPos(const Common::Point& pos) +{ + if(pos != Common::Point(_x, _y)) + { + _x = pos.x; + _y = pos.y; + // we have to redraw the whole dialog! + dialog().setDirty(); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Widget::handleMouseEntered() { diff --git a/src/gui/Widget.hxx b/src/gui/Widget.hxx index 64685813a..cf4637ea1 100644 --- a/src/gui/Widget.hxx +++ b/src/gui/Widget.hxx @@ -53,6 +53,10 @@ class Widget : public GuiObject virtual int getTop() const { return _y; } virtual int getRight() const { return _x + getWidth(); } virtual int getBottom() const { return _y + getHeight(); } + virtual void setPosX(int x); + virtual void setPosY(int y); + virtual void setPos(int x, int y); + virtual void setPos(const Common::Point& pos); virtual bool handleText(char text) { return false; } virtual bool handleKeyDown(StellaKey key, StellaMod mod) { return false; }