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