diff --git a/CMakeLists.txt b/CMakeLists.txt index 207fc26..67c6574 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ set(SOURCES src/FileDB.cpp src/ToolbarBackend.cpp src/plugins/MARSIMUConfig.cpp + src/plugins/ROCKTASKConfig.cpp src/BuildModuleDialog.cpp src/LinkHardwareSoftwareDialog.cpp ) @@ -78,6 +79,7 @@ set (QT_MOC_HEADER src/BundleSelectionDialog.hpp src/ToolbarBackend.hpp src/plugins/MARSIMUConfig.hpp + src/plugins/ROCKTASKConfig.hpp src/BuildModuleDialog.hpp src/LinkHardwareSoftwareDialog.hpp ) diff --git a/src/XRockGUI.cpp b/src/XRockGUI.cpp index 6521cc6..70c641d 100644 --- a/src/XRockGUI.cpp +++ b/src/XRockGUI.cpp @@ -18,6 +18,7 @@ #include "ConfigMapHelper.hpp" #include "plugins/MARSIMUConfig.hpp" +#include "plugins/ROCKTASKConfig.hpp" #include #include @@ -69,8 +70,11 @@ namespace xrock_gui_model // register config plugins ConfigureDialogLoader *l = new MARSIMUConfigLoader(); configPlugins["mars::IMU"] = l; + ConfigureDialogLoader *lt = new ROCKTASKConfigLoader(this); + configPlugins["Rock::Task"] = lt; loadSettingsFromFile("generalsettings.yml"); + loadModelFromParameter(); } @@ -1160,11 +1164,16 @@ namespace xrock_gui_model } } { - std::string type = node["model"]["name"]; + std::string modelName = node["model"]["name"]; std::string nodeName = node.hasKey("alias") && node["alias"] != "" ? (std::string)node["alias"] : name; + + bool isTask = std::any_of(node["model"]["types"].begin(), node["model"]["types"].end(), [](configmaps::ConfigItem &type) + { return type["name"] == "Rock::Task"; }); + std::map::iterator it; - it = configPlugins.find(type); - if(it != configPlugins.end()) + // first check if we find the node type (model name) in the configPlugis + it = configPlugins.find(modelName); + if (it != configPlugins.end()) { ConfigMap globalConfig = bagelGui->getGlobalConfig(); QDialog *d = it->second->createDialog(&config, env, globalConfig); @@ -1174,10 +1183,22 @@ namespace xrock_gui_model } else { - ConfigureDialog cd(&config, env, node["model"]["name"], true, true); - cd.setWindowTitle(QString::fromStdString("Configure Node " + nodeName)); - cd.resize(400, 400); - cd.exec(); + it = configPlugins.find("Rock::Task"); + if (isTask && it != configPlugins.end()) + { + ConfigMap globalConfig = bagelGui->getGlobalConfig(); + QDialog *d = it->second->createDialog(&config, env, globalConfig); + d->setWindowTitle(QString::fromStdString("Configure Node " + nodeName)); + d->exec(); + delete d; + } + else + { + ConfigureDialog cd(&config, env, node["model"]["name"], true, true); + cd.setWindowTitle(QString::fromStdString("Configure Node " + nodeName)); + cd.resize(400, 400); + cd.exec(); + } } } // Update the node configuration diff --git a/src/plugins/ROCKTASKConfig.cpp b/src/plugins/ROCKTASKConfig.cpp new file mode 100644 index 0000000..0fe17c7 --- /dev/null +++ b/src/plugins/ROCKTASKConfig.cpp @@ -0,0 +1,451 @@ +#include "ROCKTASKConfig.hpp" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "../ComponentModelInterface.hpp" +using namespace configmaps; +using namespace bagel_gui; + +namespace xrock_gui_model +{ + bool startsWith(const std::string &str, const std::string &prefix) { return str.compare(0, prefix.length(), prefix) == 0; } + + ConfigAtom ROCKTASKConfig::propertyToConfigAtom(ConfigItem &property) + { + std::string type = property["Type"]; + + if (type == "bool") + { + return property.hasKey("DefaultVal") ? (bool)property["DefaultVal"] : false; + } + else if (type == "::std::string") + { + return property.hasKey("DefaultVal") ? (std::string)property["DefaultVal"] : std::string(""); + } + else if (type == "double" || type == "float") + { + return property.hasKey("DefaultVal") ? (double)property["DefaultVal"] : 0.0; + } + else if (startsWith(type, "boost::int") || startsWith(type, "boost::uint")) + { + return property.hasKey("DefaultVal") ? (std::int32_t)property["DefaultVal"] : 0; + } + else + { + return ConfigAtom(); //ItemType::UNDEFINED_TYPE; + } + } + + void ROCKTASKConfig::initEditablePattern(ConfigMap &newConfig) + { + for (auto &property : nodeMap["model"]["versions"][0]["data"]["properties"]) + { + std::string key = property["Name"].getString(); + std::string type = property["Type"].getString(); + + if (mars::utils::tolower(key).find("frame") != std::string::npos && type == "::std::string") continue; + if (key == "bool") + continue; + + ConfigAtom atom = propertyToConfigAtom(property); + + //non-atom + if (atom.getType() == ConfigAtom::ItemType::UNDEFINED_TYPE) { + if (newConfig["config"].hasKey(key)) + dwConfig["config"][key] = newConfig["config"][key]; + else + dwConfig["config"][key] = configmaps::ConfigMap(); + } + else // atom (str, int, double..) + { + if (newConfig["config"].hasKey(key)) + dwConfig["config"][key] = newConfig["config"][key]; + else + dwConfig["config"][key] = atom; + } + } + } + + void ROCKTASKConfig::initCheckablePattern(ConfigMap &newConfig) + { + //std::vector pattern; + for (auto &property : nodeMap["model"]["versions"][0]["data"]["properties"]) + { + std::string key = property["Name"].getString(); + std::string type = property["Type"].getString(); + if (type == "bool") + { + if (newConfig["config"].hasKey(key)) + { + dwConfig["config"][key] = (bool)newConfig["config"][key]; + } + else + { + if (property.hasKey("DefaultVal")) + { + dwConfig["config"][key] = (bool)property["DefaultVal"]; + } + else + { + dwConfig["config"][key] = false; + } + } + checkablePattern.push_back("../config/" + key); + } + } + // dw->setCheckablePattern(pattern); + } + + void ROCKTASKConfig::initDropDownPattern(ConfigMap &newConfig) + { + // std::vector pattern; + // std::vector> values; + // Activity and state + dwConfig["activity"]["type"] = "PERIODIC"; + dwConfig["state"] = "PREOPERATIONAL"; + + dropDownPattern.push_back("../activity/type"); + dropDownPattern.push_back("../state"); + + dropDownValues.resize(dropDownPattern.size()); + dropDownValues[0].push_back("PERIODIC"); // ../activity/type + dropDownValues[0].push_back("FD_DRIVEN"); + dropDownValues[0].push_back("TRIGGERED"); + dropDownValues[1].push_back("PRREOPERATIONAL"); // ../state + dropDownValues[1].push_back("RUNNING"); + dropDownValues[1].push_back("STOPPED"); + + // Frame names + for (auto &property : nodeMap["model"]["versions"][0]["data"]["properties"]) + { + std::string key = property["Name"].getString(); + std::string type = property["Type"].getString(); + if (mars::utils::tolower(key).find("frame") != std::string::npos && type == "::std::string") + { + dropDownPattern.push_back("../config/" + key); + dropDownValues.emplace_back(); + + if(newConfig["config"].hasKey(key)) { + dropDownValues.back().push_back(newConfig["config"][key]); + dwConfig["config"][key] = newConfig["config"][key]; + } + else + { + if (property.hasKey("DefaultVal")) + { + dropDownValues.back().push_back((std::string)property["DefaultVal"]); + dwConfig["config"][key] = (std::string)property["DefaultVal"]; + } + else + { + dropDownValues.back().push_back("-"); + dwConfig["config"][key] = "-"; + } + + } + if (globalConfig.hasKey("frameNames")) + { + for (auto &frameName : globalConfig["frameNames"]) + { + dropDownValues.back().push_back((std::string)frameName); + } + } + } + } + + } + + void ROCKTASKConfig::postUpdateCheckablePattern() + { + //std::vector pattern; + // Update the dw boolean values 'true' and 'false' should be checkboxes. + + if(dwConfig.hasKey("activity")) + { + + auto it = dwConfig["activity"].beginMap(); + for (; it != dwConfig["activity"].endMap(); ++it) + { + auto &[key, value] = *it; + + + if (value.isAtom() ) { + + ConfigAtom& atom = static_cast(value); + std::string v = mars::utils::tolower(atom.getString()); + if (v == "false" || v == "true") + { + value = ConfigAtom(v == "true"); + checkablePattern.push_back("../activity/" + key); + } + } + } + } + + } + + + + void ROCKTASKConfig::updateDataWidget(ConfigMap &newConfig) + { + dropDownPattern.clear(); + dropDownValues.clear(); + checkablePattern.clear(); + initDropDownPattern(newConfig); + initCheckablePattern(newConfig); + initEditablePattern(newConfig); + dwConfig.updateMap(newConfig); + postUpdateCheckablePattern(); + dw->setCheckablePattern(checkablePattern); + dw->setDropDownPattern(dropDownPattern, dropDownValues); + dw->setConfigMap("", dwConfig); + } + + ROCKTASKConfig::ROCKTASKConfig(XRockGUI *xrockGui, configmaps::ConfigMap *configuration, + configmaps::ConfigMap &env, + configmaps::ConfigMap &globalConfig, + const std::string &type, bool onlyMap, + bool noTreeEdit, configmaps::ConfigMap *dropdown, std::string fileName) : xrockGui(xrockGui), configuration(configuration), globalConfig(globalConfig), textOnly(false) + { + configMapEdit = nullptr; + dw = nullptr; + statusLabel = nullptr; + nodeMap = xrockGui->getBagelGui()->getCurrentTabView()->getView()->getSelectedNodeMap(); // TODO: will be better to pass in nodeMap from XROCKGUI.cpp + std::string nodeName = nodeMap["model"]["name"]; + this->setWindowTitle(QString::fromStdString(nodeName+" Configuration")); + if (fileName.size()) + { + // open file + configFile = new QTextEdit(); + configFileName = fileName; + std::ifstream t(configFileName.c_str()); + std::stringstream buffer; + buffer << t.rdbuf(); + configFileContent = buffer.str(); + configFile->setText(configFileContent.c_str()); + } + else + { + configMapEdit = new QTextEdit(); + configMapEdit->setText(configuration->toYamlString().c_str()); + connect(configMapEdit, SIGNAL(textChanged()), this, SLOT(textChanged())); + statusLabel = new QLabel(); + statusLabel->setText("valid yaml syntax"); + } + dw = new mars::config_map_gui::DataWidget(nullptr, this, true, false); + + ConfigMap &config = *configuration; + updateDataWidget(config); + + connect(dw, SIGNAL(mapChanged()), this, SLOT(mapChanged())); + + QVBoxLayout *v = new QVBoxLayout(); + v->setContentsMargins(0, 0, 0, 0); + if (!onlyMap) + { + QLabel *l = new QLabel("config file:"); + configFile = new QTextEdit(); + configFileName = ""; + // try to open config file + { + // contruct path starting with RockRoot + // todo: check override behavior + std::string bundle = env["BundelEnvVarName"]; + std::string path = env["ConfigDir"]; + if (env.hasKey("RockRoot")) + { + path << env["RockRoot"]; + } + const char *envS; + if ((envS = getenv("AUTOPROJ_CURRENT_ROOT"))) + { + path = envS; + } + if (path.back() != '/') + path += "/"; + if (env.hasKey("BundleFolder")) + { + path += (std::string)env["BundleFolder"]; + } + else + path += "bundles/"; + if (path.back() != '/') + path += "/"; + if ((envS = getenv(bundle.c_str()))) + { + path += envS; + if (path.back() != '/') + path += "/"; + if (env.hasKey("BundelOrogenConfigFolder")) + { + path += (std::string)env["BundelOrogenConfigFolder"]; + } + else + path += "config/orogen/"; + if (path.back() != '/') + path += "/"; + path += type + ".yml"; + std::cerr << "check for config file: " << path.c_str() << std::endl; + if (mars::utils::pathExists(path)) + { + configFileName = path; + std::ifstream t(path.c_str()); + std::stringstream buffer; + buffer << t.rdbuf(); + configFileContent = buffer.str(); + configFile->setText(configFileContent.c_str()); + } + else + { + // todo: generate configuration file + } + } + } + QSplitter *split = new QSplitter(Qt::Vertical, nullptr); + if (dw) + { + split->addWidget(dw); + } + else + { + split->addWidget(configMapEdit); + } + QVBoxLayout *vLayout2 = new QVBoxLayout(); + vLayout2->setContentsMargins(0, 0, 0, 0); + vLayout2->addWidget(l); + vLayout2->addWidget(configFile); + QWidget *w2 = new QWidget(); + w2->setLayout(vLayout2); + split->addWidget(w2); + v->addWidget(split); + } + else + { + v->addWidget(dw); + v->addWidget(configMapEdit); + configMapEdit->setText(dwConfig.toYamlString().c_str()); + } + + if (configMapEdit) + { + statusLabel->setStyleSheet("QLabel { background-color : green; color: white; }"); + v->addWidget(statusLabel); + lastTextChanged = 0; + ticks = 0; + startTimer(100); + } + QPushButton *button = new QPushButton("close"); + v->addWidget(button); + connect(button, SIGNAL(clicked()), this, SLOT(close())); + setLayout(v); + } + + ROCKTASKConfig::ROCKTASKConfig(std::string *text) : text(text), textOnly(true) + { + configMapEdit = nullptr; + dw = nullptr; + statusLabel = nullptr; + configMapEdit = new QTextEdit(); + configMapEdit->setText(text->c_str()); + connect(configMapEdit, SIGNAL(textChanged()), this, SLOT(textChanged())); + QVBoxLayout *v = new QVBoxLayout(); + v->setContentsMargins(0, 0, 0, 0); + v->addWidget(configMapEdit); + QPushButton *button = new QPushButton("close"); + v->addWidget(button); + connect(button, SIGNAL(clicked()), this, SLOT(close())); + setLayout(v); + } + + ROCKTASKConfig::~ROCKTASKConfig() + { + if (!configFileName.empty()) + { + std::string content = configFile->toPlainText().toStdString(); + if (content != configFileContent) + { + FILE *f = fopen(configFileName.c_str(), "w"); + fprintf(f, "%s", content.c_str()); + fclose(f); + } + } + else + { + if (textOnly) + { + *text = configMapEdit->toPlainText().toStdString(); + } + else + { + try + { + ConfigMap tmpMap = ConfigMap::fromYamlString(configMapEdit->toPlainText().toStdString()); + *configuration = tmpMap; + } + catch (...) + { + std::cerr << "Error converting config into yaml map!" << std::endl; + } + } + } + + } + + void ROCKTASKConfig::textChanged() + { + lastTextChanged = ticks; + } + + void ROCKTASKConfig::timerEvent(QTimerEvent *event) + { + + + if (lastTextChanged != -1 && ticks - lastTextChanged > 5) + { + try + { + ConfigMap newConfig = ConfigMap::fromYamlString(configMapEdit->toPlainText().toStdString()); + statusLabel->setText("valid yaml syntax"); + statusLabel->setStyleSheet("QLabel { background-color : green; color: white;}"); + + updateDataWidget(newConfig); + + fprintf(stderr, "dw updated from text edit"); + } + catch (...) + { + statusLabel->setText("invalid yaml syntax"); + statusLabel->setStyleSheet("QLabel { background-color : red; color: white;}"); + } + lastTextChanged = -1; + ticks = -1; + } + ticks += 1; + } + + QDialog *ROCKTASKConfigLoader::createDialog(configmaps::ConfigMap *configuration, + configmaps::ConfigMap &env, + configmaps::ConfigMap &globalConfig) + { + QDialog *d = new ROCKTASKConfig(xrockGui, configuration, env, globalConfig, "ROCK::TASK", true, true); + return d; + } + + void ROCKTASKConfig::mapChanged() + { + dwConfig = dw->getConfigMap(); + configMapEdit->setText(dwConfig.toYamlString().c_str()); + lastTextChanged = -1; + ticks = -1; + } + +} // end of namespace xrock_gui_model diff --git a/src/plugins/ROCKTASKConfig.hpp b/src/plugins/ROCKTASKConfig.hpp new file mode 100644 index 0000000..4d6098f --- /dev/null +++ b/src/plugins/ROCKTASKConfig.hpp @@ -0,0 +1,83 @@ +#pragma once +#include "../ConfigureDialogLoader.hpp" + +#include "../XRockGUI.hpp" +#include + +#include +#include +#include +#include +namespace mars +{ + namespace config_map_gui + { + class DataWidget; + } +} +namespace bagel_gui +{ + class BagelModel; +} + +namespace xrock_gui_model +{ + + class XRockGUI; + + class ROCKTASKConfig : public QDialog + { + Q_OBJECT + + public: + ROCKTASKConfig(XRockGUI *xrockGui, configmaps::ConfigMap *configuration, + configmaps::ConfigMap &env, + configmaps::ConfigMap &globalConfig, + const std::string &type, bool onlyMap = false, + bool noTreeEdit = false, configmaps::ConfigMap *dropdown = NULL, + std::string fileName = ""); + explicit ROCKTASKConfig(std::string *text); + ~ROCKTASKConfig(); + + void updateDataWidget(configmaps::ConfigMap &newConfig); + void initEditablePattern(configmaps::ConfigMap &newConfig); + void initCheckablePattern(configmaps::ConfigMap &newConfig); + void initDropDownPattern(configmaps::ConfigMap &newConfig); + void postUpdateCheckablePattern(); + configmaps::ConfigAtom propertyToConfigAtom(configmaps::ConfigItem &property); + + protected slots: + void textChanged(); + void mapChanged(); + void timerEvent(QTimerEvent *event); + + private: + configmaps::ConfigMap nodeMap; + mars::config_map_gui::DataWidget *dw; + XRockGUI *xrockGui; + QTextEdit *configFile, *configMapEdit; + configmaps::ConfigMap *configuration, dwConfig, globalConfig; + std::string configFileName, configFileContent, *text, contextNodeName; + std::vector dropDownPattern; + std::vector checkablePattern; + std::vector> dropDownValues; + QLabel *statusLabel; + long ticks, lastTextChanged; + bool textOnly; + }; + + class ROCKTASKConfigLoader : public ConfigureDialogLoader + { + public: + ROCKTASKConfigLoader(XRockGUI *xrockGui) : xrockGui(xrockGui) {} + virtual ~ROCKTASKConfigLoader() override {} + virtual QDialog* createDialog(configmaps::ConfigMap *configuration, + configmaps::ConfigMap &env, + configmaps::ConfigMap &globalConfig) override; + + private: + XRockGUI *xrockGui; + }; + +} // end of namespace xrock_gui_model +