From 5751725213336309c0acd1962a5cf19e4f532253 Mon Sep 17 00:00:00 2001 From: dg Date: Fri, 31 Mar 2023 21:09:40 +0000 Subject: [PATCH] Allow options to be selected in the GUI. --- lib/flags.cc | 48 ++++---- lib/flags.h | 272 +++++++++++++++++++++++++++++++----------- src/gui/gui.h | 4 +- src/gui/layout.cpp | 7 ++ src/gui/layout.fbp | 120 +++++++++++++++++++ src/gui/layout.h | 2 + src/gui/main.cc | 12 +- src/gui/mainwindow.cc | 114 ++++++++++++++++-- 8 files changed, 479 insertions(+), 100 deletions(-) diff --git a/lib/flags.cc b/lib/flags.cc index 8cf4524a..7953b731 100644 --- a/lib/flags.cc +++ b/lib/flags.cc @@ -55,31 +55,39 @@ static bool setFallbackFlag( } else { - for (const auto& configs : config.option()) - { - if (path == configs.name()) - { - if (configs.config().option_size() > 0) - Error() << fmt::format( - "option '{}' has an option inside it, which isn't " - "allowed", - path); - if (configs.config().include_size() > 0) - Error() << fmt::format( - "option '{}' is trying to include something, which " - "isn't allowed", - path); - - Logger() << fmt::format("OPTION: {}", configs.message()); - config.MergeFrom(configs.config()); - return false; - } - } + if (FlagGroup::applyOption(path)) + return false; } } Error() << "unrecognised flag; try --help"; } +bool FlagGroup::applyOption(const std::string& option) +{ + for (const auto& configs : config.option()) + { + if (option == configs.name()) + { + if (configs.config().option_size() > 0) + Error() << fmt::format( + "option '{}' has an option inside it, which isn't " + "allowed", + option); + if (configs.config().include_size() > 0) + Error() << fmt::format( + "option '{}' is trying to include something, which " + "isn't allowed", + option); + + Logger() << fmt::format("OPTION: {}", configs.message()); + config.MergeFrom(configs.config()); + return true; + } + } + + return false; +} + std::vector FlagGroup::parseFlagsWithFilenames(int argc, const char* argv[], std::function callback) diff --git a/lib/flags.h b/lib/flags.h index df5bb58c..a13f2e5a 100644 --- a/lib/flags.h +++ b/lib/flags.h @@ -12,21 +12,42 @@ public: FlagGroup(std::initializer_list groups); public: - void parseFlags(int argc, const char* argv[], - std::function callback = - [](const auto&){ return false; }); + void parseFlags( + int argc, + const char* argv[], + std::function callback = + [](const auto&) + { + return false; + }); std::vector parseFlagsWithFilenames( - int argc, const char* argv[], - std::function callback = - [](const auto&){ return false; }); - void parseFlagsWithConfigFiles(int argc, const char* argv[], - const std::map& configFiles); - static ConfigProto parseSingleConfigFile( - const std::string& filename, - const std::map& configFiles); - static void parseConfigFile( - const std::string& filename, - const std::map& configFiles); + int argc, + const char* argv[], + std::function callback = + [](const auto&) + { + return false; + }); + void parseFlagsWithConfigFiles(int argc, + const char* argv[], + const std::map& configFiles); + + /* Load one config file (or internal config file), without expanding + * includes. */ + + static ConfigProto parseSingleConfigFile(const std::string& filename, + const std::map& configFiles); + + /* Load a top-level config file (or internal config file), expanding + * includes. */ + + static void parseConfigFile(const std::string& filename, + const std::map& configFiles); + + /* Modify the current config to engage the named option. */ + + static bool applyOption(const std::string& option); + void addFlag(Flag* flag); void checkInitialised() const; @@ -40,14 +61,25 @@ class Flag { public: Flag(const std::vector& names, const std::string helptext); - virtual ~Flag() {}; + virtual ~Flag(){}; void checkInitialised() const - { _group.checkInitialised(); } + { + _group.checkInitialised(); + } - const std::string& name() const { return _names[0]; } - const std::vector names() const { return _names; } - const std::string& helptext() const { return _helptext; } + const std::string& name() const + { + return _names[0]; + } + const std::vector names() const + { + return _names; + } + const std::string& helptext() const + { + return _helptext; + } virtual bool hasArgument() const = 0; virtual const std::string defaultValueAsString() const = 0; @@ -62,15 +94,26 @@ private: class ActionFlag : Flag { public: - ActionFlag(const std::vector& names, const std::string helptext, - std::function callback): + ActionFlag(const std::vector& names, + const std::string helptext, + std::function callback): Flag(names, helptext), _callback(callback) - {} + { + } - bool hasArgument() const { return false; } - const std::string defaultValueAsString() const { return ""; } - void set(const std::string& value) { _callback(); } + bool hasArgument() const + { + return false; + } + const std::string defaultValueAsString() const + { + return ""; + } + void set(const std::string& value) + { + _callback(); + } private: const std::function _callback; @@ -79,16 +122,30 @@ private: class SettableFlag : public Flag { public: - SettableFlag(const std::vector& names, const std::string helptext): + SettableFlag( + const std::vector& names, const std::string helptext): Flag(names, helptext) - {} + { + } operator bool() const - { checkInitialised(); return _value; } + { + checkInitialised(); + return _value; + } - bool hasArgument() const { return false; } - const std::string defaultValueAsString() const { return "false"; } - void set(const std::string& value) { _value = true; } + bool hasArgument() const + { + return false; + } + const std::string defaultValueAsString() const + { + return "false"; + } + void set(const std::string& value) + { + _value = true; + } private: bool _value = false; @@ -98,72 +155,122 @@ template class ValueFlag : public Flag { public: - ValueFlag(const std::vector& names, const std::string helptext, - const T defaultValue, - std::function callback = [](const T&) {}): + ValueFlag( + const std::vector& names, + const std::string helptext, + const T defaultValue, + std::function callback = + [](const T&) + { + }): Flag(names, helptext), _defaultValue(defaultValue), _value(defaultValue), - _callback(callback) - {} + _callback(callback) + { + } const T& get() const - { checkInitialised(); return _value; } + { + checkInitialised(); + return _value; + } - operator const T& () const - { return get(); } + operator const T&() const + { + return get(); + } - bool isSet() const - { return _isSet; } + bool isSet() const + { + return _isSet; + } void setDefaultValue(T value) { _value = _defaultValue = value; } - bool hasArgument() const { return true; } + bool hasArgument() const + { + return true; + } protected: T _defaultValue; T _value; - bool _isSet = false; - std::function _callback; + bool _isSet = false; + std::function _callback; }; class StringFlag : public ValueFlag { public: - StringFlag(const std::vector& names, const std::string helptext, - const std::string defaultValue = "", - std::function callback = [](const std::string&) {}): + StringFlag( + const std::vector& names, + const std::string helptext, + const std::string defaultValue = "", + std::function callback = + [](const std::string&) + { + }): ValueFlag(names, helptext, defaultValue, callback) - {} + { + } - const std::string defaultValueAsString() const { return _defaultValue; } - void set(const std::string& value) { _value = value; _callback(_value); _isSet = true; } + const std::string defaultValueAsString() const + { + return _defaultValue; + } + void set(const std::string& value) + { + _value = value; + _callback(_value); + _isSet = true; + } }; class IntFlag : public ValueFlag { public: - IntFlag(const std::vector& names, const std::string helptext, - int defaultValue = 0, - std::function callback = [](const int&) {}): + IntFlag( + const std::vector& names, + const std::string helptext, + int defaultValue = 0, + std::function callback = + [](const int&) + { + }): ValueFlag(names, helptext, defaultValue, callback) - {} + { + } - const std::string defaultValueAsString() const { return std::to_string(_defaultValue); } - void set(const std::string& value) { _value = std::stoi(value); _callback(_value); _isSet = true; } + const std::string defaultValueAsString() const + { + return std::to_string(_defaultValue); + } + void set(const std::string& value) + { + _value = std::stoi(value); + _callback(_value); + _isSet = true; + } }; class HexIntFlag : public IntFlag { public: - HexIntFlag(const std::vector& names, const std::string helptext, - int defaultValue = 0, - std::function callback = [](const int&) {}): + HexIntFlag( + const std::vector& names, + const std::string helptext, + int defaultValue = 0, + std::function callback = + [](const int&) + { + }): IntFlag(names, helptext, defaultValue, callback) - {} + { + } const std::string defaultValueAsString() const; }; @@ -171,26 +278,49 @@ public: class DoubleFlag : public ValueFlag { public: - DoubleFlag(const std::vector& names, const std::string helptext, - double defaultValue = 1.0, - std::function callback = [](const double&) {}): + DoubleFlag( + const std::vector& names, + const std::string helptext, + double defaultValue = 1.0, + std::function callback = + [](const double&) + { + }): ValueFlag(names, helptext, defaultValue, callback) - {} + { + } - const std::string defaultValueAsString() const { return std::to_string(_defaultValue); } - void set(const std::string& value) { _value = std::stod(value); _callback(_value); _isSet = true; } + const std::string defaultValueAsString() const + { + return std::to_string(_defaultValue); + } + void set(const std::string& value) + { + _value = std::stod(value); + _callback(_value); + _isSet = true; + } }; class BoolFlag : public ValueFlag { public: - BoolFlag(const std::vector& names, const std::string helptext, - bool defaultValue = false, - std::function callback = [](const bool&) {}): + BoolFlag( + const std::vector& names, + const std::string helptext, + bool defaultValue = false, + std::function callback = + [](const bool&) + { + }): ValueFlag(names, helptext, defaultValue, callback) - {} + { + } - const std::string defaultValueAsString() const { return _defaultValue ? "true" : "false"; } + const std::string defaultValueAsString() const + { + return _defaultValue ? "true" : "false"; + } void set(const std::string& value); }; diff --git a/src/gui/gui.h b/src/gui/gui.h index 1c3f5b2a..1dfc9569 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -9,6 +9,7 @@ class MainWindow; extern void postToUiThread(std::function callback); extern void runOnUiThread(std::function callback); extern void runOnWorkerThread(std::function callback); +extern bool isWorkerThread(); wxDECLARE_EVENT(UPDATE_STATE_EVENT, wxCommandEvent); @@ -34,7 +35,8 @@ private: void OnExec(const ExecEvent& event); public: - bool IsWorkerThreadRunning() const; + bool IsWorkerThread(); + bool IsWorkerThreadRunning(); protected: virtual wxThread::ExitCode Entry(); diff --git a/src/gui/layout.cpp b/src/gui/layout.cpp index b5158309..02dd5f81 100644 --- a/src/gui/layout.cpp +++ b/src/gui/layout.cpp @@ -169,6 +169,13 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t fgSizer6->Fit( m_panel11 ); fgSizer8->Add( m_panel11, 1, wxEXPAND | wxALL, 5 ); + m_staticText232 = new wxStaticText( idlePanel, wxID_ANY, wxT("then select some options (if there are any):"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL ); + m_staticText232->Wrap( -1 ); + fgSizer8->Add( m_staticText232, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); + + formatOptionsContainer = new wxPanel( idlePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + fgSizer8->Add( formatOptionsContainer, 1, wxALL|wxEXPAND, 5 ); + m_staticText19 = new wxStaticText( idlePanel, wxID_ANY, wxT("and press one of:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText19->Wrap( -1 ); fgSizer8->Add( m_staticText19, 0, wxALIGN_CENTER|wxALL, 5 ); diff --git a/src/gui/layout.fbp b/src/gui/layout.fbp index 31cf8ad9..c69d5690 100644 --- a/src/gui/layout.fbp +++ b/src/gui/layout.fbp @@ -1479,6 +1479,126 @@ + + 5 + wxALIGN_CENTER_HORIZONTAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + then select some options (if there are any): + 0 + + 0 + + + 0 + + 1 + m_staticText232 + 1 + + + protected + 1 + + Resizable + 1 + + wxALIGN_CENTER_HORIZONTAL + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + formatOptionsContainer + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + 5 wxALIGN_CENTER|wxALL diff --git a/src/gui/layout.h b/src/gui/layout.h index 84603e64..3a6a8367 100644 --- a/src/gui/layout.h +++ b/src/gui/layout.h @@ -77,6 +77,8 @@ class MainWindowGen : public wxFrame wxPanel* m_panel11; wxChoice* formatChoice; wxButton* customConfigurationButton; + wxStaticText* m_staticText232; + wxPanel* formatOptionsContainer; wxStaticText* m_staticText19; wxButton* readButton; wxButton* writeButton; diff --git a/src/gui/main.cc b/src/gui/main.cc index 8073312e..1ba66b91 100644 --- a/src/gui/main.cc +++ b/src/gui/main.cc @@ -115,7 +115,17 @@ void runOnWorkerThread(std::function callback) wxGetApp().RunOnWorkerThread(callback); } -bool FluxEngineApp::IsWorkerThreadRunning() const +bool isWorkerThread() +{ + return wxGetApp().IsWorkerThread(); +} + +bool FluxEngineApp::IsWorkerThread() +{ + return wxThread::GetCurrentId() != wxThread::GetMainId(); +} + +bool FluxEngineApp::IsWorkerThreadRunning() { return !!_callback; } diff --git a/src/gui/mainwindow.cc b/src/gui/mainwindow.cc index 1881a7f6..d48d2b27 100644 --- a/src/gui/mainwindow.cc +++ b/src/gui/mainwindow.cc @@ -34,6 +34,7 @@ extern const std::map formats; #define CONFIG_FORTYTRACK "FortyTrack" #define CONFIG_HIGHDENSITY "HighDensity" #define CONFIG_FORMAT "Format" +#define CONFIG_FORMATOPTIONS "FormatOptions" #define CONFIG_EXTRACONFIG "ExtraConfig" #define CONFIG_FLUXIMAGE "FluxImage" #define CONFIG_DISKIMAGE "DiskImage" @@ -95,11 +96,16 @@ public: Logger::setLogger( [&](std::shared_ptr message) { - runOnUiThread( - [message, this]() - { - OnLogMessage(message); - }); + if (isWorkerThread()) + { + runOnUiThread( + [message, this]() + { + OnLogMessage(message); + }); + } + else + OnLogMessage(message); }); _logWindow.reset( @@ -174,6 +180,7 @@ public: LoadConfig(); UpdateDevices(); + UpdateFormatOptions(); } void OnShowLogWindow(wxCommandEvent& event) override @@ -258,6 +265,7 @@ public: { SaveConfig(); UpdateState(); + UpdateFormatOptions(); } void OnControlsChanged(wxFileDirPickerEvent& event) override @@ -1220,8 +1228,18 @@ public: Error() << "no format selected"; config.Clear(); - FlagGroup::parseConfigFile( - _formatNames[formatChoice->GetSelection()], formats); + auto formatName = _formatNames[formatChoice->GetSelection()]; + FlagGroup::parseConfigFile(formatName, formats); + + /* Apply any format options. */ + + for (const auto& e : _formatOptions) + { + if (e.first == formatName) + FlagGroup::applyOption(e.second); + } + + /* Merge in any custom config. */ for (auto setting : split(_extraConfiguration, '\n')) { @@ -1242,6 +1260,8 @@ public: FlagGroup::parseConfigFile(setting, formats); } + /* Locate the device, if any. */ + auto serial = deviceCombo->GetValue().ToStdString(); if (!serial.empty() && (serial[0] == '/')) setProtoByString(&config, "usb.greaseweazle.port", serial); @@ -1250,6 +1270,8 @@ public: _logWindow->GetTextControl()->Clear(); + /* Apply the source/destination. */ + switch (_selectedSource) { case SELECTEDSOURCE_REAL: @@ -1476,6 +1498,24 @@ public: _config.Read(CONFIG_EXTRACONFIG, &s); _extraConfiguration = s; + /* Format options. */ + + _formatOptions.clear(); + s = ""; + _config.Read(CONFIG_FORMATOPTIONS, &s); + for (auto combined : split(std::string(s), ',')) + { + auto pair = split(combined, ':'); + try + { + auto key = std::make_pair(pair.at(0), pair.at(1)); + _formatOptions.insert(key); + } + catch (std::exception&) + { + } + } + /* Triggers SaveConfig */ _dontSaveConfig = false; @@ -1514,6 +1554,16 @@ public: _config.Write(CONFIG_FORMAT, formatChoice->GetString(formatChoice->GetSelection())); _config.Write(CONFIG_EXTRACONFIG, wxString(_extraConfiguration)); + + /* Format options. */ + + { + std::vector options; + for (auto& e : _formatOptions) + options.push_back(fmt::format("{}:{}", e.first, e.second)); + + _config.Write(CONFIG_FORMATOPTIONS, wxString(join(options, ","))); + } } void UpdateState() @@ -1615,6 +1665,55 @@ public: } } + void UpdateFormatOptions() + { + assert(!wxGetApp().IsWorkerThreadRunning()); + + formatOptionsContainer->DestroyChildren(); + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto formatSelection = formatChoice->GetSelection(); + if (formatSelection == wxNOT_FOUND) + sizer->Add(new wxStaticText( + formatOptionsContainer, wxID_ANY, "(no format selected)")); + else + { + config.Clear(); + std::string formatName = _formatNames[formatChoice->GetSelection()]; + FlagGroup::parseConfigFile(formatName, formats); + + if (config.option().empty()) + sizer->Add(new wxStaticText(formatOptionsContainer, + wxID_ANY, + "(no options for this format)")); + else + for (auto& option : config.option()) + { + auto* choice = new wxCheckBox( + formatOptionsContainer, wxID_ANY, option.comment()); + auto key = std::make_pair(formatName, option.name()); + sizer->Add(choice); + + if (_formatOptions.find(key) != _formatOptions.end()) + choice->SetValue(true); + + choice->Bind(wxEVT_CHECKBOX, + [this, choice, key](wxCommandEvent& e) + { + if (choice->GetValue()) + _formatOptions.insert(key); + else + _formatOptions.erase(key); + + OnControlsChanged(e); + }); + } + } + + formatOptionsContainer->SetSizerAndFit(sizer); + idlePanel->Layout(); + } + void OnTrackSelection(TrackSelectionEvent& event) { (new FluxViewerWindow(this, event.trackFlux))->Show(true); @@ -1713,6 +1812,7 @@ private: int _explorerSide; bool _explorerUpdatePending; std::unique_ptr _explorerFluxmap; + std::set> _formatOptions; }; wxWindow* FluxEngineApp::CreateMainWindow()