diff --git a/agui2/src/Graphics/Connection/Connection.hpp b/agui2/src/Graphics/Connection/Connection.hpp index 7ed3bfb5607e7df42dd120830dd1c88b9888bdd7..13e29f33bb867eb5e042ebc87dcf44e41d0545b6 100644 --- a/agui2/src/Graphics/Connection/Connection.hpp +++ b/agui2/src/Graphics/Connection/Connection.hpp @@ -16,6 +16,9 @@ public: void destroy(); + const OutputConnectionBox* getOriginConnectionBox() const { return this->originConnectionBox; } + const InputConnectionBox* getTargetConnectionBox() const { return this->targetConnectionBox; } + private: void drawDirectConnection(QPainter* painter, const QPointF& originPoint, const QPointF& targetPoint); void drawAroundConnection(QPainter* painter, const QPointF& originPoint, const QPointF& targetPoint); diff --git a/agui2/src/Graphics/Connection/InputConnectionBox.hpp b/agui2/src/Graphics/Connection/InputConnectionBox.hpp index fa293cebf3bda07876c4194d4f8f7cf386fc2e87..3447c260a492b9ff535a988757f710de3c15a520 100644 --- a/agui2/src/Graphics/Connection/InputConnectionBox.hpp +++ b/agui2/src/Graphics/Connection/InputConnectionBox.hpp @@ -8,6 +8,7 @@ public: explicit InputConnectionBox(GraphicsBox* parent, uint8_t slot); void setConnection(Connection* connection); + const Connection* getConnection() const { return this->connection; } uint8_t getSlot() const { return this->slot; } private: diff --git a/agui2/src/Graphics/Connection/OutputConnectionBox.hpp b/agui2/src/Graphics/Connection/OutputConnectionBox.hpp index d4de22b030af9f64a7805ba3a3534e3c9ece6bb9..e768af48cbeaa4e9f17e9816c4c159ea55d72790 100644 --- a/agui2/src/Graphics/Connection/OutputConnectionBox.hpp +++ b/agui2/src/Graphics/Connection/OutputConnectionBox.hpp @@ -10,6 +10,7 @@ public: explicit OutputConnectionBox(GraphicsBox* parent); void addConnection(Connection* connection); + const std::set<Connection*>& getConnections() const { return this->connections; } private: void on_Disconnect() override; diff --git a/agui2/src/Graphics/GraphicsBox.cpp b/agui2/src/Graphics/GraphicsBox.cpp index 75895c91625ccfd2c50c5bdbf2ab17b8bb1bdb66..97e1811e386ef3c31e86e7032504a65f4962c40c 100644 --- a/agui2/src/Graphics/GraphicsBox.cpp +++ b/agui2/src/Graphics/GraphicsBox.cpp @@ -9,18 +9,22 @@ #include <Graphics/Connection/InputConnectionBox.hpp> #include <Graphics/Connection/OutputConnectionBox.hpp> +std::vector<GraphicsBox*> GraphicsBox::allGraphicsBoxes; + GraphicsBox::GraphicsBox(std::unique_ptr<ModelBox> modelBox, QPointF pos) : color(Qt::blue) , text(QString::fromStdString(modelBox->getName())) , modelBox(std::move(modelBox)) { + GraphicsBox::allGraphicsBoxes.push_back(this); + this->font.setBold(true); this->font.setPixelSize(18); this->setPos(pos); this->setFlags(ItemIsMovable); this->setZValue(1); - + this->boundRect = QFontMetrics(this->font).boundingRect(this->text); this->boundRect.setHeight(std::max(this->boundRect.height(), std::max(this->modelBox->getMaxInputCount(), uint8_t(1)) * 16.0)); this->boundRect.adjust(-20, -20, 20, 20); @@ -39,6 +43,11 @@ GraphicsBox::GraphicsBox(std::unique_ptr<ModelBox> modelBox, QPointF pos) } } +GraphicsBox::~GraphicsBox() { + auto& bv = GraphicsBox::allGraphicsBoxes; + bv.erase(std::remove(bv.begin(), bv.end(), this), bv.end()); +} + QRectF GraphicsBox::boundingRect() const { return this->boundRect; } @@ -56,4 +65,4 @@ const std::vector<InputConnectionBox*>& GraphicsBox::getInputConnectionBoxes() c OutputConnectionBox* GraphicsBox::getOutputConnectionBox() const { return outputConnectionBox; -} \ No newline at end of file +} diff --git a/agui2/src/Graphics/GraphicsBox.hpp b/agui2/src/Graphics/GraphicsBox.hpp index 9e761320be4a7d3459876eb07a674c05eda1240f..98177c67faaf93b08db9253dcca01ecde747f797 100644 --- a/agui2/src/Graphics/GraphicsBox.hpp +++ b/agui2/src/Graphics/GraphicsBox.hpp @@ -13,6 +13,7 @@ class GraphicsBox : public QGraphicsObject { Q_OBJECT public: GraphicsBox(std::unique_ptr<ModelBox> modelBox, QPointF pos); + virtual ~GraphicsBox(); QRectF boundingRect() const override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; @@ -22,6 +23,8 @@ public: const std::vector<InputConnectionBox*>& getInputConnectionBoxes() const; OutputConnectionBox* getOutputConnectionBox() const; + static const std::vector<GraphicsBox*>& getAllGraphicsBoxes() { return GraphicsBox::allGraphicsBoxes; } + protected: QColor color; @@ -34,6 +37,8 @@ private: std::vector<InputConnectionBox*> inputConnectionBoxes; OutputConnectionBox* outputConnectionBox; + + static std::vector<GraphicsBox*> allGraphicsBoxes; }; diff --git a/agui2/src/MainWindow.cpp b/agui2/src/MainWindow.cpp index 6d7a5b6c66022ba0a454ab4961c774fbfc6f2f8f..c473f323b7964382f271d38a37bdb9ac5555d9ca 100644 --- a/agui2/src/MainWindow.cpp +++ b/agui2/src/MainWindow.cpp @@ -2,10 +2,14 @@ #include <iostream> +#include <QtCore/QTextStream> #include <QFileDialog> #include <QGraphicsItem> #include <QtWidgets/QMessageBox> +#include <json.hpp> +using json = nlohmann::json; + #include <exception/CommonException.h> #include <Algorithm/Registry.hpp> @@ -18,6 +22,9 @@ #include <ui_MainWindow.h> #include <Models/AlgorithmModelBox.hpp> #include <Models/OutputModelBox.hpp> +#include <Graphics/Connection/Connection.hpp> +#include <Graphics/Connection/InputConnectionBox.hpp> +#include <Graphics/Connection/OutputConnectionBox.hpp> MainWindow* MainWindow::instance = nullptr; const std::string MainWindow::ADD_INPUT_CURSOR_DATA = "__ADD_INPUT__"; @@ -106,3 +113,149 @@ void MainWindow::setCursorData(const std::string& data) { MainWindow* MainWindow::getInstance() { return instance; } + +void MainWindow::on_actionOpen_triggered() { + using namespace std::string_literals; + + auto filename = QFileDialog::getOpenFileName(this, + "Open file", + QDir::homePath(), + "All files (*.*);;JSON files (*.json)"); + if (filename.isEmpty()) + return; + + QFile file(filename); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, "Warning", "File does not exist."); + return; + } + + // clear existing boxes + auto allGraphicsBoxes = GraphicsBox::getAllGraphicsBoxes(); + for (auto* box: allGraphicsBoxes) { + delete box; + } + allGraphicsBoxes.clear(); + + // read data + QTextStream stream(&file); + auto data = stream.readAll(); + + auto parsed = json::parse(data.toStdString()); + + if (parsed.count("boxes") != 1 || !parsed["boxes"].is_array() || parsed["boxes"].empty()) + throw std::runtime_error { "No boxes were defined."}; + + std::vector<GraphicsBox*> boxes; + boxes.reserve(parsed["boxes"].size()); + + for (const auto& box: parsed["boxes"]) { + if (box["type"] == "algorithm") { + if (auto* algo = Registry::getAlgorithm(box["algorithm"])) { + boxes.push_back(new GraphicsBox(std::make_unique<AlgorithmModelBox>(algo), { box["x"], box["y"] })); + this->scene->addItem(boxes.back()); + } else { + throw std::runtime_error { + "Invalid algorithm '"s + box["algorithm"].get<std::string>() + "'specified." }; + } + } + else if (box["type"] == "input") { + boxes.push_back(new InputGraphicsBox(std::make_unique<InputModelBox>(), { box["x"], box["y"] })); + this->scene->addItem(boxes.back()); + } + else if (box["type"] == "output") { + boxes.push_back(new GraphicsBox(std::make_unique<OutputModelBox>(), { box["x"], box["y"] })); + this->scene->addItem(boxes.back()); + } + } + + for (const auto& conn: parsed["connections"]) { + size_t from = conn["from"]; + size_t to = conn["to"]; + size_t slot = conn["slot"]; + if (from >= boxes.size() || to >= boxes.size()) { + throw std::runtime_error { "Invalid box index specified." }; + } + + if (from == to) { + throw std::runtime_error { "Cannot connect a box to itself. "}; + } + + auto* fromBox = boxes[from]; + auto* toBox = boxes[to]; + + auto* fromConnectionBox = fromBox->getOutputConnectionBox(); + auto& toConnectionBoxes = toBox->getInputConnectionBoxes(); + + if (slot >= toConnectionBoxes.size()) { + throw std::runtime_error { "Invalid connection slot specified." }; + } + + auto* toConnectionBox = toConnectionBoxes[slot]; + + auto* connection = new Connection(fromConnectionBox, toConnectionBox); + fromConnectionBox->addConnection(connection); + toConnectionBox->setConnection(connection); + } +} + +void MainWindow::on_actionSave_triggered() { + auto filename = QFileDialog::getSaveFileName(this, + "Save file", + QDir::homePath(), + "JSON files (*.json);;All files (*.*)"); + if (filename.isEmpty()) + return; + + QFile file(filename); + file.open(QFile::ReadWrite | QFile::Text); + + json data { + {"boxes", json::array()}, + {"connections", json::array()} + }; + + const auto& allBoxes = GraphicsBox::getAllGraphicsBoxes(); + auto& boxes = data["boxes"]; + auto& connections = data["connections"]; + + for (size_t i = 0; i < allBoxes.size(); ++i) { + auto* box = allBoxes[i]; + json boxData = { + {"x", box->pos().x()}, + {"y", box->pos().y()}, + }; + + if (auto* algorithmBox = dynamic_cast<AlgorithmModelBox*>(box->getModelBox())) { + boxData["type"] = "algorithm"; + boxData["algorithm"] = algorithmBox->getAlgorithm()->getFullName(); + } + else if (dynamic_cast<InputModelBox*>(box->getModelBox())) { + boxData["type"] = "input"; + } + else if (dynamic_cast<OutputModelBox*>(box->getModelBox())) { + boxData["type"] = "output"; + } + else { + assert(false); + } + + if (box->getOutputConnectionBox()) { + for (const Connection* conn: box->getOutputConnectionBox()->getConnections()) { + auto targetIndex = + std::find(allBoxes.begin(), allBoxes.end(), conn->getTargetConnectionBox()->getParent()) - + allBoxes.begin(); + connections.push_back({ + { "from", i }, + { "to", targetIndex }, + { "slot", conn->getTargetConnectionBox()->getSlot() }, + }); + } + } + boxes.push_back(boxData); + } + + QTextStream stream(&file); + stream << QString::fromStdString(data.dump(4)); + file.close(); +} diff --git a/agui2/src/MainWindow.hpp b/agui2/src/MainWindow.hpp index 1e316f7000042556228cf8cd60d95a82c47348d7..7b7b10181cdd0379acff262549e39bf68d487b23 100644 --- a/agui2/src/MainWindow.hpp +++ b/agui2/src/MainWindow.hpp @@ -33,6 +33,8 @@ private: private slots: void on_RunBtn_clicked(); + void on_actionOpen_triggered(); + void on_actionSave_triggered(); public: static const std::string ADD_INPUT_CURSOR_DATA; diff --git a/agui2/src/MainWindow.ui b/agui2/src/MainWindow.ui index 935435260a263563b66214bf2f2f50fc53786940..8db01826da9bb2e01278eaf8a040f7aa0cdd1b04 100644 --- a/agui2/src/MainWindow.ui +++ b/agui2/src/MainWindow.ui @@ -75,6 +75,8 @@ <property name="title"> <string>File</string> </property> + <addaction name="actionOpen"/> + <addaction name="actionSave"/> </widget> <widget class="QMenu" name="menuInsert"> <property name="title"> @@ -97,6 +99,22 @@ <string>Add Input</string> </property> </action> + <action name="actionOpen"> + <property name="text"> + <string>Open</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionSave"> + <property name="text"> + <string>Save</string> + </property> + <property name="shortcut"> + <string>Ctrl+S</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <customwidgets> diff --git a/agui2/src/Models/AlgorithmModelBox.hpp b/agui2/src/Models/AlgorithmModelBox.hpp index ab4daf091cc2b75e1067052ae4fdb3ffc6da3777..5de1de231110b3d8e3b13899378475df5ee6cdd3 100644 --- a/agui2/src/Models/AlgorithmModelBox.hpp +++ b/agui2/src/Models/AlgorithmModelBox.hpp @@ -7,6 +7,7 @@ public: explicit AlgorithmModelBox(Algorithm* algorithm); std::string getName() const override; + Algorithm* getAlgorithm() const { return this->algorithm; } std::shared_ptr<abstraction::OperationAbstraction> run() const override; diff --git a/agui2/src/Models/ModelBox.cpp b/agui2/src/Models/ModelBox.cpp index cdb56daabbefcf6c972a3e8800396f54f6d9cc34..518d4433b2958fa85979552a91cc92d43b16041f 100644 --- a/agui2/src/Models/ModelBox.cpp +++ b/agui2/src/Models/ModelBox.cpp @@ -7,8 +7,6 @@ #include <exception/CommonException.h> #include <QtCore/QString> -std::vector<ModelBox*> ModelBox::allModelBoxes; - ModelBox::ModelBox(ModelType type, uint8_t maxInputCount) : type(type) , maxInputCount(maxInputCount) @@ -16,8 +14,6 @@ ModelBox::ModelBox(ModelType type, uint8_t maxInputCount) { // Input box cannot have inputs, other boxes must have inputs Q_ASSERT((this->type == ModelType::Input) == (this->maxInputCount == 0)); - - ModelBox::allModelBoxes.push_back(this); } void ModelBox::setInput(uint8_t slot, ModelBox* model) { @@ -34,8 +30,3 @@ void ModelBox::removeOutput(ModelBox* model, uint8_t slot) { Q_ASSERT(this->canHaveOutput()); this->outputs.erase({model, slot}); } - -ModelBox::~ModelBox() { - auto& bv = ModelBox::allModelBoxes; - bv.erase(std::remove(bv.begin(), bv.end(), this), bv.end()); -} diff --git a/agui2/src/Models/ModelBox.hpp b/agui2/src/Models/ModelBox.hpp index 7c95c0fa454b0227146e593d92c4005fcf9e6a62..03d4015c880059969212f69ce47abef02e5b61e1 100644 --- a/agui2/src/Models/ModelBox.hpp +++ b/agui2/src/Models/ModelBox.hpp @@ -22,7 +22,7 @@ protected: explicit ModelBox(ModelType type, uint8_t maxInputCount); public: - virtual ~ModelBox(); + virtual ~ModelBox() = default; void setGraphicsBox(GraphicsBox* graphicsBox) { this->graphicsBox = graphicsBox; } ModelType getType() const { return this->type; } @@ -36,7 +36,6 @@ public: virtual bool canHaveOutput() const { return true; } virtual std::shared_ptr<abstraction::OperationAbstraction> run() const = 0; - protected: ModelType type; @@ -45,10 +44,6 @@ protected: std::set<std::pair<ModelBox*, uint8_t>> outputs; GraphicsBox* graphicsBox = nullptr; - -private: - - static std::vector<ModelBox*> allModelBoxes; };