added cancel option (button, enter, ESC) to ProgressDialog

adapted all ProgressDialog using actions to allow canceling
This commit is contained in:
thrust26 2020-11-23 22:02:52 +01:00
parent 106bd3ab91
commit d309279852
12 changed files with 187 additions and 74 deletions

View File

@ -3689,19 +3689,29 @@
<p>ROM Info Viewer width at 50% , UI sized 1280x900, large launcher font:</p>
<img src="graphics/rominfo_2x_small.png">
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>The dialog items at the top can be used to define the listed files:</p>
<ul>
<li>
The 'Show all files' checkbox allows displaying files which do not
have a valid ROM extension.
</li><li>
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.</br>
Note that the search is not case sensitive, so you don't need to worry
about capital or lower-case letters. You also can use '*' and '?' as
wildcards. E.g. for '(198?)*atari' only ROMs from the 1980s made by
Atari will be listed.
</li><li>
If 'Incl. subdirectories' is checked, Stella will list matching files
from all subdirectories too.
</li>
</ul>
</br>
<h3><b><a name="ROMLauncherContextMenu">ROM Launcher Context Menu</a></b></h3>

View File

@ -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";

View File

@ -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;
}

View File

@ -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<bool(const FilesystemNode& node)>;
using CancelCheck = std::function<bool()> 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

View File

@ -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<ProgressDialog>(this, _font, "", false);
myProgressDialog = make_unique<ProgressDialog>(this, _font, "");
return *myProgressDialog;
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();

View File

@ -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()
{

View File

@ -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; }