From 96105d32f077cc3f17be3017fcd1db436ce275f9 Mon Sep 17 00:00:00 2001 From: Iiro Rastas <iiro.t.rastas@utu.fi> Date: Tue, 29 Oct 2019 14:37:31 +0200 Subject: [PATCH] Add local configurations to the GUI A new class NetConfigurable is created, which enables handling of both local and remote configurations in a polymorphic way. In the GUI, configurations can now be changed via the cog icon. --- applications/gui/src/config_window.cpp | 137 ++++++++++++++---- applications/gui/src/config_window.hpp | 15 +- applications/gui/src/screen.cpp | 36 ++++- .../common/cpp/include/ftl/configurable.hpp | 5 +- components/common/cpp/src/configurable.cpp | 4 - components/net/cpp/CMakeLists.txt | 3 +- .../net/cpp/include/ftl/net_configurable.hpp | 26 ++++ components/net/cpp/src/net_configurable.cpp | 12 ++ components/net/cpp/test/CMakeLists.txt | 9 ++ .../net/cpp/test/net_configurable_unit.cpp | 43 ++++++ 10 files changed, 245 insertions(+), 45 deletions(-) create mode 100644 components/net/cpp/include/ftl/net_configurable.hpp create mode 100644 components/net/cpp/src/net_configurable.cpp create mode 100644 components/net/cpp/test/net_configurable_unit.cpp diff --git a/applications/gui/src/config_window.cpp b/applications/gui/src/config_window.cpp index a9db26b46..fcba8ff7a 100644 --- a/applications/gui/src/config_window.cpp +++ b/applications/gui/src/config_window.cpp @@ -15,8 +15,11 @@ using std::string; using std::vector; using ftl::config::json_t; +ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const ftl::UUID &peer) : ConfigWindow(parent, ctrl, std::optional<ftl::UUID>(peer)) { -ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const ftl::UUID &peer) +} + +ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const std::optional<ftl::UUID> &peer) : nanogui::Window(parent, "Settings"), ctrl_(ctrl), peer_(peer) { using namespace nanogui; @@ -24,78 +27,115 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, con setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f)); //setModal(true); - configurables_ = ctrl->getConfigurables(peer); + if (peer) { + configurables_ = ctrl->getConfigurables(peer.value()); + } else { + configurables_ = ftl::config::list(); + } new Label(this, "Select Configurable","sans-bold"); - auto select = new ComboBox(this, configurables_); - select->setCallback([this](int ix) { - LOG(INFO) << "Change configurable: " << ix; - _buildForm(configurables_[ix], ctrl_->get(peer_, configurables_[ix])); - - setVisible(false); - //this->parent()->removeChild(this); - //delete this; - //screen()->removeChild(this); - }); + for (auto c : configurables_) { + auto itembutton = new Button(this, c); + itembutton->setCallback([this,c]() { + LOG(INFO) << "Change configurable: " << c; + _buildForm(c); + setVisible(false); + //this->parent()->removeChild(this); + //delete this; + //screen()->removeChild(this); + }); + } } ConfigWindow::~ConfigWindow() { } -void ConfigWindow::_addElements(nanogui::FormHelper *form, const std::string &suri, const ftl::config::json_t &data) { +class ConfigWindow::References { + public: + References(ftl::NetConfigurable* nc, ftl::config::json_t* config, const std::string* suri) : nc(nc), config(config), suri(suri) { + } + + ~References() { + delete nc; + delete config; + delete suri; + } + + private: + ftl::NetConfigurable* nc; + ftl::config::json_t* config; + const std::string* suri; +}; + +std::vector<ftl::gui::ConfigWindow::References *> ConfigWindow::_addElements(nanogui::FormHelper *form, ftl::Configurable &nc, const std::string &suri, std::function<ftl::Configurable*(const std::string*, std::vector<References *>&)> construct) { using namespace nanogui; + std::vector<References *> references; + + auto data = nc.getConfig(); + for (auto i=data.begin(); i!=data.end(); ++i) { if (i.key() == "$id") continue; if (i.key() == "$ref" && i.value().is_string()) { LOG(INFO) << "Follow $ref: " << i.value(); - _addElements(form, suri, ctrl_->get(peer_, i.value().get<string>())); + const std::string* suri = new std::string(i.value().get<string>()); + ftl::Configurable* rc = construct(suri, references); + auto new_references = _addElements(form, *rc, *suri, construct); + references.insert(references.end(), new_references.begin(), new_references.end()); continue; } if (i.value().is_boolean()) { string key = i.key(); - form->addVariable<bool>(i.key(), [this,data,key,suri](const bool &b){ - ctrl_->set(peer_, suri + string("/") + key, json_t(b)); + form->addVariable<bool>(i.key(), [this,data,key,&nc](const bool &b){ + nc.set(key, b); }, [data,key]() -> bool { return data[key].get<bool>(); }); } else if (i.value().is_number_integer()) { string key = i.key(); - form->addVariable<int>(i.key(), [this,data,key,suri](const int &f){ - ctrl_->set(peer_, suri + string("/") + key, json_t(f)); + form->addVariable<int>(i.key(), [this,data,key,&nc](const int &f){ + nc.set(key, f); }, [data,key]() -> int { return data[key].get<int>(); }); } else if (i.value().is_number_float()) { string key = i.key(); - form->addVariable<float>(i.key(), [this,data,key,suri](const float &f){ - ctrl_->set(peer_, suri + string("/") + key, json_t(f)); + form->addVariable<float>(i.key(), [this,data,key,&nc](const float &f){ + nc.set(key, f); }, [data,key]() -> float { return data[key].get<float>(); }); } else if (i.value().is_string()) { string key = i.key(); - form->addVariable<string>(i.key(), [this,data,key,suri](const string &f){ - ctrl_->set(peer_, suri + string("/") + key, json_t(f)); + form->addVariable<string>(i.key(), [this,data,key,&nc](const string &f){ + nc.set(key, f); }, [data,key]() -> string { return data[key].get<string>(); }); } else if (i.value().is_object()) { string key = i.key(); - const ftl::config::json_t &v = i.value(); - form->addButton(i.key(), [this,form,suri,key,v]() { - _buildForm(suri+string("/")+key, v); - })->setIcon(ENTYPO_ICON_FOLDER); + // Checking the URI with exists() prevents unloaded local configurations from being shown. + if (suri.find('#') != string::npos && exists(suri+string("/")+key)) { + form->addButton(key, [this,suri,key]() { + _buildForm(suri+string("/")+key); + })->setIcon(ENTYPO_ICON_FOLDER); + } else if (exists(suri+string("#")+key)) { + form->addButton(key, [this,suri,key]() { + _buildForm(suri+string("#")+key); + })->setIcon(ENTYPO_ICON_FOLDER); + } } } + + return references; } -void ConfigWindow::_buildForm(const std::string &suri, ftl::config::json_t data) { +void ConfigWindow::_buildForm(const std::string &suri) { using namespace nanogui; ftl::URI uri(suri); @@ -105,11 +145,50 @@ void ConfigWindow::_buildForm(const std::string &suri, ftl::config::json_t data) form->addWindow(Vector2i(100,50), uri.getFragment()); form->window()->setTheme(theme()); - _addElements(form, suri, data); + ftl::config::json_t* config; + config = new ftl::config::json_t; + const std::string* allocated_suri = new std::string(suri); + std::vector<ftl::gui::ConfigWindow::References *> references; + + ftl::Configurable* nc; + + if (peer_) { + *config = ctrl_->get(peer_.value(), suri); + nc = new ftl::NetConfigurable(peer_.value(), *allocated_suri, *ctrl_, *config); + + references = _addElements(form, *nc, *allocated_suri, [this](auto suri, auto &references) { + ftl::config::json_t* config = new ftl::config::json_t; + *config = ctrl_->get(peer_.value(), *suri); + auto nc = new ftl::NetConfigurable(peer_.value(), *suri, *ctrl_, *config); + auto r = new References(nc, config, suri); + references.push_back(r); + return nc; + }); + } else { + nc = ftl::config::find(suri); + if (nc) { + references = _addElements(form, *nc, *allocated_suri, [this](auto suri, auto &references) { + return ftl::config::find(*suri); + }); + } + } - auto closebutton = form->addButton("Close", [form]() { + auto closebutton = form->addButton("Close", [this,form,config,allocated_suri,nc,references]() { form->window()->setVisible(false); + for(auto r : references) { + delete r; + } + if (peer_) { + delete nc; + } + delete config; + delete allocated_suri; delete form; }); closebutton->setIcon(ENTYPO_ICON_CROSS); } + +bool ConfigWindow::exists(const std::string &uri) { + // If the Configurable is a NetConfigurable, the URI is not checked. + return peer_ || ftl::config::find(uri); +} \ No newline at end of file diff --git a/applications/gui/src/config_window.hpp b/applications/gui/src/config_window.hpp index 87170b1d3..23279d93c 100644 --- a/applications/gui/src/config_window.hpp +++ b/applications/gui/src/config_window.hpp @@ -6,6 +6,7 @@ #include <ftl/uuid.hpp> #include <nanogui/formhelper.h> +#include <ftl/net_configurable.hpp> namespace ftl { namespace gui { @@ -16,15 +17,23 @@ namespace gui { class ConfigWindow : public nanogui::Window { public: ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const ftl::UUID &peer); + ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const std::optional<ftl::UUID> &peer = std::nullopt); ~ConfigWindow(); private: + /* + References holds the pointers to a NetConfigurable and all its members so that + they can all be returned from _addElements() and then simultaneously deleted + as the form is closed. + */ + class References; ftl::ctrl::Master *ctrl_; - ftl::UUID peer_; + std::optional<ftl::UUID> peer_; std::vector<std::string> configurables_; - void _buildForm(const std::string &uri, ftl::config::json_t data); - void _addElements(nanogui::FormHelper *form, const std::string &suri, const ftl::config::json_t &data); + void _buildForm(const std::string &uri); + std::vector<References *> _addElements(nanogui::FormHelper *form, ftl::Configurable &nc, const std::string &suri, std::function<ftl::Configurable*(const std::string*, std::vector<References *>&)> construct); + bool exists(const std::string &uri); }; } diff --git a/applications/gui/src/screen.cpp b/applications/gui/src/screen.cpp index 5df535c3b..bb69755fb 100644 --- a/applications/gui/src/screen.cpp +++ b/applications/gui/src/screen.cpp @@ -17,6 +17,7 @@ #include "ctrl_window.hpp" #include "src_window.hpp" +#include "config_window.hpp" #include "camera.hpp" #include "media_panel.hpp" @@ -212,12 +213,35 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl popup->setVisible(false); }); - button = new ToolButton(toolbar, ENTYPO_ICON_COG); - button->setIconExtraScale(1.5f); - button->setTheme(toolbuttheme); - button->setTooltip("Settings"); - button->setFixedSize(Vector2i(40,40)); - button->setPosition(Vector2i(5,height()-50)); + popbutton = new PopupButton(innertool, "", ENTYPO_ICON_COG); + popbutton->setIconExtraScale(1.5f); + popbutton->setTheme(toolbuttheme); + popbutton->setTooltip("Settings"); + popbutton->setFixedSize(Vector2i(40,40)); + popbutton->setSide(Popup::Side::Right); + popbutton->setChevronIcon(0); + // popbutton->setPosition(Vector2i(5,height()-50)); + popup = popbutton->popup(); + popup->setLayout(new GroupLayout()); + popup->setTheme(toolbuttheme); + + auto node_details = ctrl_->getSlaves(); + std::vector<std::string> node_titles; + + for (auto &d : node_details) { + auto peer = ftl::UUID(d["id"].get<std::string>()); + itembutton = new Button(popup, d["title"].get<std::string>()); + itembutton->setCallback([this,popup,peer]() { + auto config_window = new ConfigWindow(this, ctrl_, peer); + config_window->setTheme(windowtheme); + }); + } + + itembutton = new Button(popup, "Local"); + itembutton->setCallback([this,popup]() { + auto config_window = new ConfigWindow(this, ctrl_); + config_window->setTheme(windowtheme); + }); //configwindow_ = new ConfigWindow(parent, ctrl_); cwindow_ = new ftl::gui::ControlWindow(this, controller); diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp index 6593119c7..244b54828 100644 --- a/components/common/cpp/include/ftl/configurable.hpp +++ b/components/common/cpp/include/ftl/configurable.hpp @@ -80,6 +80,7 @@ class Configurable { template <typename T> void set(const std::string &name, T value) { (*config_)[name] = value; + inject(name, (*config_)[name]); _trigger(name); } @@ -103,12 +104,12 @@ class Configurable { protected: nlohmann::json *config_; + virtual void inject(const std::string name, nlohmann::json &value) {} + private: std::map<std::string, std::list<std::function<void(const config::Event&)>>> observers_; void _trigger(const std::string &name); - - static void __changeURI(const std::string &uri, Configurable *cfg); }; /*template <> diff --git a/components/common/cpp/src/configurable.cpp b/components/common/cpp/src/configurable.cpp index f47e12195..5116292ad 100644 --- a/components/common/cpp/src/configurable.cpp +++ b/components/common/cpp/src/configurable.cpp @@ -59,8 +59,4 @@ void Configurable::on(const string &prop, function<void(const ftl::config::Event } else { (*ix).second.push_back(f); } -} - -void Configurable::__changeURI(const string &uri, Configurable *cfg) { - } \ No newline at end of file diff --git a/components/net/cpp/CMakeLists.txt b/components/net/cpp/CMakeLists.txt index c7e8bb341..9df16c0b2 100644 --- a/components/net/cpp/CMakeLists.txt +++ b/components/net/cpp/CMakeLists.txt @@ -9,13 +9,14 @@ add_library(ftlnet src/dispatcher.cpp src/universe.cpp src/ws_internal.cpp + src/net_configurable.cpp ) target_include_directories(ftlnet PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE src) -target_link_libraries(ftlnet ftlcommon Threads::Threads glog::glog ${UUID_LIBRARIES}) +target_link_libraries(ftlnet ftlctrl ftlcommon Threads::Threads glog::glog ${UUID_LIBRARIES}) install(TARGETS ftlnet EXPORT ftlnet-config ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/components/net/cpp/include/ftl/net_configurable.hpp b/components/net/cpp/include/ftl/net_configurable.hpp new file mode 100644 index 000000000..3a31ecc27 --- /dev/null +++ b/components/net/cpp/include/ftl/net_configurable.hpp @@ -0,0 +1,26 @@ +#pragma once +#ifndef _FTL_NETCONFIGURABLE_HPP_ +#define _FTL_NETCONFIGURABLE_HPP_ + +#include <ftl/configurable.hpp> +#include <ftl/master.hpp> + +namespace ftl { + + class NetConfigurable : public ftl::Configurable { + public: + NetConfigurable(ftl::UUID peer, const std::string &suri, ftl::ctrl::Master &ctrl, ftl::config::json_t &config); + ~NetConfigurable(); + + protected: + void inject(const std::string name, nlohmann::json &value); + + private: + ftl::UUID peer; + const std::string &suri; + ftl::ctrl::Master &ctrl; + }; + +} + +#endif // _FTL_NETCONFIGURABLE_HPP_ diff --git a/components/net/cpp/src/net_configurable.cpp b/components/net/cpp/src/net_configurable.cpp new file mode 100644 index 000000000..edcec65b8 --- /dev/null +++ b/components/net/cpp/src/net_configurable.cpp @@ -0,0 +1,12 @@ +#include <ftl/net_configurable.hpp> + +#include <string> + +ftl::NetConfigurable::NetConfigurable(ftl::UUID peer, const std::string &suri, ftl::ctrl::Master &ctrl, ftl::config::json_t &config) : ftl::Configurable(config), peer(peer), suri(suri), ctrl(ctrl) { +} + +ftl::NetConfigurable::~NetConfigurable(){} + +void ftl::NetConfigurable::inject(const std::string name, nlohmann::json &value) { + ctrl.set(peer, suri + std::string("/") + name, value); +} diff --git a/components/net/cpp/test/CMakeLists.txt b/components/net/cpp/test/CMakeLists.txt index 34a642fa0..c786f73d8 100644 --- a/components/net/cpp/test/CMakeLists.txt +++ b/components/net/cpp/test/CMakeLists.txt @@ -27,11 +27,20 @@ target_link_libraries(net_integration Threads::Threads ${UUID_LIBRARIES}) +### NetConfigurable Unit ####################################################### +add_executable(net_configurable_unit + ./tests.cpp + ./net_configurable_unit.cpp) +target_link_libraries(net_configurable_unit + ftlnet) + #add_test(ProtocolUnitTest protocol_unit) add_test(PeerUnitTest peer_unit) add_test(NetIntegrationTest net_integration) +# Testing of NetConfigurable is disabled. +#add_test(NetConfigurableUnitTest net_configurable_unit) add_custom_target(tests) add_dependencies(tests peer_unit uri_unit) diff --git a/components/net/cpp/test/net_configurable_unit.cpp b/components/net/cpp/test/net_configurable_unit.cpp new file mode 100644 index 000000000..c8bf42247 --- /dev/null +++ b/components/net/cpp/test/net_configurable_unit.cpp @@ -0,0 +1,43 @@ +#include "catch.hpp" +#include <ftl/net_configurable.hpp> +#include <ftl/slave.hpp> + +using ftl::NetConfigurable; + +SCENARIO( "NetConfigurable::set()" ) { + GIVEN( "valid peer UUID, URI and Master" ) { + // Set up Master + nlohmann::json json = {{"$id", "root"}, {"test", {{"listen", "tcp://localhost:7077"}}}}; // Check what values are needed + ftl::Configurable *root; + root = new ftl::Configurable(json); + ftl::net::Universe *net = ftl::config::create<ftl::net::Universe>(root, std::string("test")); + net->start(); + ftl::ctrl::Master *controller = new ftl::ctrl::Master(root, net); + + // Set up a slave, then call getSlaves() to get the UUID string + nlohmann::json jsonSlave = {{"$id", "slave"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; + ftl::Configurable *rootSlave; + rootSlave = new ftl::Configurable(jsonSlave); + ftl::net::Universe *netSlave = ftl::config::create<ftl::net::Universe>(rootSlave, std::string("test")); + ftl::ctrl::Slave slave(netSlave, rootSlave); + netSlave->start(); + netSlave->waitConnections(); + net->waitConnections(); + + auto slaves = controller->getSlaves(); + REQUIRE( slaves.size() == 1 ); + + ftl::UUID peer = ftl::UUID(slaves[0]["id"].get<std::string>()); + const std::string suri = "slave_test"; + nlohmann::json jsonTest = {{"$id", "slave_test"}, {"test", {{"peers", {"tcp://localhost:7077"}}}}}; + NetConfigurable nc(peer, suri, *controller, jsonTest); + nc.set("test_value", 5); + REQUIRE( nc.get<int>("test_value") == 5 ); + } + + // invalid peer UUID + + // invalid URI + + // null Master +} \ No newline at end of file -- GitLab