From 119cf5e0904358478f65b6e64af8352e19cf2f28 Mon Sep 17 00:00:00 2001 From: Martin Hanzik <martin@hanzik.com> Date: Wed, 25 Apr 2018 12:19:37 +0200 Subject: [PATCH] Add basic save/load --- agui2/src/Graphics/Connection/Connection.hpp | 3 + .../Connection/InputConnectionBox.hpp | 1 + .../Connection/OutputConnectionBox.hpp | 1 + agui2/src/Graphics/GraphicsBox.cpp | 13 +- agui2/src/Graphics/GraphicsBox.hpp | 5 + agui2/src/MainWindow.cpp | 153 ++++++++++++++++++ agui2/src/MainWindow.hpp | 2 + agui2/src/MainWindow.ui | 18 +++ agui2/src/Models/AlgorithmModelBox.hpp | 1 + agui2/src/Models/ModelBox.cpp | 9 -- agui2/src/Models/ModelBox.hpp | 7 +- 11 files changed, 196 insertions(+), 17 deletions(-) diff --git a/agui2/src/Graphics/Connection/Connection.hpp b/agui2/src/Graphics/Connection/Connection.hpp index 7ed3bfb560..13e29f33bb 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 fa293cebf3..3447c260a4 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 d4de22b030..e768af48cb 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 75895c9162..97e1811e38 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 9e761320be..98177c67fa 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 6d7a5b6c66..c473f323b7 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 1e316f7000..7b7b10181c 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 935435260a..8db01826da 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 ab4daf091c..5de1de2311 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 cdb56daabb..518d4433b2 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 7c95c0fa45..03d4015c88 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; }; -- GitLab