Skip to content
Snippets Groups Projects
Commit 96105d32 authored by Iiro Rastas's avatar Iiro Rastas Committed by Nicolas Pope
Browse files

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.
parent 309d9632
No related branches found
No related tags found
No related merge requests found
...@@ -15,8 +15,11 @@ using std::string; ...@@ -15,8 +15,11 @@ using std::string;
using std::vector; using std::vector;
using ftl::config::json_t; 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) { : nanogui::Window(parent, "Settings"), ctrl_(ctrl), peer_(peer) {
using namespace nanogui; using namespace nanogui;
...@@ -24,78 +27,115 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, con ...@@ -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)); setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
//setModal(true); //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"); new Label(this, "Select Configurable","sans-bold");
auto select = new ComboBox(this, configurables_); for (auto c : configurables_) {
select->setCallback([this](int ix) { auto itembutton = new Button(this, c);
LOG(INFO) << "Change configurable: " << ix; itembutton->setCallback([this,c]() {
_buildForm(configurables_[ix], ctrl_->get(peer_, configurables_[ix])); LOG(INFO) << "Change configurable: " << c;
_buildForm(c);
setVisible(false); setVisible(false);
//this->parent()->removeChild(this); //this->parent()->removeChild(this);
//delete this; //delete this;
//screen()->removeChild(this); //screen()->removeChild(this);
}); });
}
} }
ConfigWindow::~ConfigWindow() { 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; using namespace nanogui;
std::vector<References *> references;
auto data = nc.getConfig();
for (auto i=data.begin(); i!=data.end(); ++i) { for (auto i=data.begin(); i!=data.end(); ++i) {
if (i.key() == "$id") continue; if (i.key() == "$id") continue;
if (i.key() == "$ref" && i.value().is_string()) { if (i.key() == "$ref" && i.value().is_string()) {
LOG(INFO) << "Follow $ref: " << i.value(); 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; continue;
} }
if (i.value().is_boolean()) { if (i.value().is_boolean()) {
string key = i.key(); string key = i.key();
form->addVariable<bool>(i.key(), [this,data,key,suri](const bool &b){ form->addVariable<bool>(i.key(), [this,data,key,&nc](const bool &b){
ctrl_->set(peer_, suri + string("/") + key, json_t(b)); nc.set(key, b);
}, [data,key]() -> bool { }, [data,key]() -> bool {
return data[key].get<bool>(); return data[key].get<bool>();
}); });
} else if (i.value().is_number_integer()) { } else if (i.value().is_number_integer()) {
string key = i.key(); string key = i.key();
form->addVariable<int>(i.key(), [this,data,key,suri](const int &f){ form->addVariable<int>(i.key(), [this,data,key,&nc](const int &f){
ctrl_->set(peer_, suri + string("/") + key, json_t(f)); nc.set(key, f);
}, [data,key]() -> int { }, [data,key]() -> int {
return data[key].get<int>(); return data[key].get<int>();
}); });
} else if (i.value().is_number_float()) { } else if (i.value().is_number_float()) {
string key = i.key(); string key = i.key();
form->addVariable<float>(i.key(), [this,data,key,suri](const float &f){ form->addVariable<float>(i.key(), [this,data,key,&nc](const float &f){
ctrl_->set(peer_, suri + string("/") + key, json_t(f)); nc.set(key, f);
}, [data,key]() -> float { }, [data,key]() -> float {
return data[key].get<float>(); return data[key].get<float>();
}); });
} else if (i.value().is_string()) { } else if (i.value().is_string()) {
string key = i.key(); string key = i.key();
form->addVariable<string>(i.key(), [this,data,key,suri](const string &f){ form->addVariable<string>(i.key(), [this,data,key,&nc](const string &f){
ctrl_->set(peer_, suri + string("/") + key, json_t(f)); nc.set(key, f);
}, [data,key]() -> string { }, [data,key]() -> string {
return data[key].get<string>(); return data[key].get<string>();
}); });
} else if (i.value().is_object()) { } else if (i.value().is_object()) {
string key = i.key(); string key = i.key();
const ftl::config::json_t &v = i.value();
form->addButton(i.key(), [this,form,suri,key,v]() { // Checking the URI with exists() prevents unloaded local configurations from being shown.
_buildForm(suri+string("/")+key, v); if (suri.find('#') != string::npos && exists(suri+string("/")+key)) {
})->setIcon(ENTYPO_ICON_FOLDER); 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; using namespace nanogui;
ftl::URI uri(suri); ftl::URI uri(suri);
...@@ -105,11 +145,50 @@ void ConfigWindow::_buildForm(const std::string &suri, ftl::config::json_t data) ...@@ -105,11 +145,50 @@ void ConfigWindow::_buildForm(const std::string &suri, ftl::config::json_t data)
form->addWindow(Vector2i(100,50), uri.getFragment()); form->addWindow(Vector2i(100,50), uri.getFragment());
form->window()->setTheme(theme()); 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); form->window()->setVisible(false);
for(auto r : references) {
delete r;
}
if (peer_) {
delete nc;
}
delete config;
delete allocated_suri;
delete form; delete form;
}); });
closebutton->setIcon(ENTYPO_ICON_CROSS); 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
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <ftl/uuid.hpp> #include <ftl/uuid.hpp>
#include <nanogui/formhelper.h> #include <nanogui/formhelper.h>
#include <ftl/net_configurable.hpp>
namespace ftl { namespace ftl {
namespace gui { namespace gui {
...@@ -16,15 +17,23 @@ namespace gui { ...@@ -16,15 +17,23 @@ namespace gui {
class ConfigWindow : public nanogui::Window { class ConfigWindow : public nanogui::Window {
public: public:
ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl, const ftl::UUID &peer); 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(); ~ConfigWindow();
private: 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::ctrl::Master *ctrl_;
ftl::UUID peer_; std::optional<ftl::UUID> peer_;
std::vector<std::string> configurables_; std::vector<std::string> configurables_;
void _buildForm(const std::string &uri, ftl::config::json_t data); void _buildForm(const std::string &uri);
void _addElements(nanogui::FormHelper *form, const std::string &suri, const ftl::config::json_t &data); 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);
}; };
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "ctrl_window.hpp" #include "ctrl_window.hpp"
#include "src_window.hpp" #include "src_window.hpp"
#include "config_window.hpp"
#include "camera.hpp" #include "camera.hpp"
#include "media_panel.hpp" #include "media_panel.hpp"
...@@ -212,12 +213,35 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl ...@@ -212,12 +213,35 @@ ftl::gui::Screen::Screen(ftl::Configurable *proot, ftl::net::Universe *pnet, ftl
popup->setVisible(false); popup->setVisible(false);
}); });
button = new ToolButton(toolbar, ENTYPO_ICON_COG); popbutton = new PopupButton(innertool, "", ENTYPO_ICON_COG);
button->setIconExtraScale(1.5f); popbutton->setIconExtraScale(1.5f);
button->setTheme(toolbuttheme); popbutton->setTheme(toolbuttheme);
button->setTooltip("Settings"); popbutton->setTooltip("Settings");
button->setFixedSize(Vector2i(40,40)); popbutton->setFixedSize(Vector2i(40,40));
button->setPosition(Vector2i(5,height()-50)); 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_); //configwindow_ = new ConfigWindow(parent, ctrl_);
cwindow_ = new ftl::gui::ControlWindow(this, controller); cwindow_ = new ftl::gui::ControlWindow(this, controller);
......
...@@ -80,6 +80,7 @@ class Configurable { ...@@ -80,6 +80,7 @@ class Configurable {
template <typename T> template <typename T>
void set(const std::string &name, T value) { void set(const std::string &name, T value) {
(*config_)[name] = value; (*config_)[name] = value;
inject(name, (*config_)[name]);
_trigger(name); _trigger(name);
} }
...@@ -103,12 +104,12 @@ class Configurable { ...@@ -103,12 +104,12 @@ class Configurable {
protected: protected:
nlohmann::json *config_; nlohmann::json *config_;
virtual void inject(const std::string name, nlohmann::json &value) {}
private: private:
std::map<std::string, std::list<std::function<void(const config::Event&)>>> observers_; std::map<std::string, std::list<std::function<void(const config::Event&)>>> observers_;
void _trigger(const std::string &name); void _trigger(const std::string &name);
static void __changeURI(const std::string &uri, Configurable *cfg);
}; };
/*template <> /*template <>
......
...@@ -59,8 +59,4 @@ void Configurable::on(const string &prop, function<void(const ftl::config::Event ...@@ -59,8 +59,4 @@ void Configurable::on(const string &prop, function<void(const ftl::config::Event
} else { } else {
(*ix).second.push_back(f); (*ix).second.push_back(f);
} }
}
void Configurable::__changeURI(const string &uri, Configurable *cfg) {
} }
\ No newline at end of file
...@@ -9,13 +9,14 @@ add_library(ftlnet ...@@ -9,13 +9,14 @@ add_library(ftlnet
src/dispatcher.cpp src/dispatcher.cpp
src/universe.cpp src/universe.cpp
src/ws_internal.cpp src/ws_internal.cpp
src/net_configurable.cpp
) )
target_include_directories(ftlnet PUBLIC target_include_directories(ftlnet PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
PRIVATE src) 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 install(TARGETS ftlnet EXPORT ftlnet-config
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
......
#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_
#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);
}
...@@ -27,11 +27,20 @@ target_link_libraries(net_integration ...@@ -27,11 +27,20 @@ target_link_libraries(net_integration
Threads::Threads Threads::Threads
${UUID_LIBRARIES}) ${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(ProtocolUnitTest protocol_unit)
add_test(PeerUnitTest peer_unit) add_test(PeerUnitTest peer_unit)
add_test(NetIntegrationTest net_integration) add_test(NetIntegrationTest net_integration)
# Testing of NetConfigurable is disabled.
#add_test(NetConfigurableUnitTest net_configurable_unit)
add_custom_target(tests) add_custom_target(tests)
add_dependencies(tests peer_unit uri_unit) add_dependencies(tests peer_unit uri_unit)
......
#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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment