Commit 47b53c18 authored by Yann Boucher's avatar Yann Boucher
Browse files

Support pour le chargement et la sauvegarde de configurations

parent 9187c1d4
Pipeline #79044 passed with stages
in 18 seconds
{
"data": "x = 88, y = 81\n30bo$25bo3bo12b2o$47bo2bo3bo$56bo$43bo3$41bo2$52bo4bo$50bo6b2o$43bo13b2o$56bob3o$47bo7bo3bo$50bo2bo2bo$19bo25b2obo2bob2o2bob2o2bo$47b2ob3ob3ob4obo$17bo2b4ob2o18b2o3bo3b6obob3o$14b5obob8o17b2o2b2o2b2obobob4o$16b2o3b7obo14bob3ob2o4b5o2b2obo$15b3o4b5ob3o15b2o9bo3b6o$17b4o3b2ob2o16bo4bo4bob2o2bob3o$17bo5b3o2b2o20bo6bobob3o3bo$32bo16b3o5bo3b2ob2o$25bo23b5o3b2o2b6o$52b2o3b2ob6obo$57b2o3bobo2b2o$49bo7b4ob2o2bo$48bo7b2ob3obobo$46bo8b5o3bobo4bo$13b2ob4o34b4obo2bob2o3bo4bo$14bo6bo27bo4bob13ob2o$16b2o6bo20bo5bo5bo2b3o4bo2b2obo$15bobobo27b3o3b2obobo2bob4o3b2o2bo$14b2o5bo12b2o11b2o4b7o2bo4b2o2b3o$14b4obobo14bo10bo6b12ob2o2b2o$14bob5o11bo2b2o17bob3ob5o4bo$15bo2b2ob3o12bo17b5ob2obo2bobobo$14bo2b2o13b3o19b2ob4ob3obobo2b2o$16bo16bobo18b2ob5ob2ob3o5bo9bo$14bo15b7o17bo2b8ob4o14bo$16bo14bobo22b7o2bo2b2o$15b2o14b2o22b9ob5o$15bobob2o8b2ob3o19b18o2bo$16b8o2bo2bobo2b2o17b10ob10o2bo2bo$11bo3b10o4b3o20bob9ob11obo4bo$18bob10ob2obo7bo8b13ob14o2bo$19b2o4bobobo3bobo13b7ob20o$21bobo3bo5bo14b31obobo$22b5ob2obobo4bo8b3o2b9ob10ob2o4b3o$20b8o2b4o4b4o4b2ob5ob18o9b3o$18b17o2b3o6b10ob12ob2o7b3ob3o$17b3ob10ob2ob3o7b14o2b4o3b3o2b2o2bob4o$15b2ob16o2bo6b13ob3ob12o5bo4b2o2bo$13b3ob17o9b7ob10ob10o3bo11bo$11bob3ob9ob7o7b10ob4ob5ob8o5b2o3bo3bobo$9b9ob5ob11o3b9ob4o3b4ob6ob2o8bob3o$9b7ob5ob4ob2ob3ob14o2b4ob3o2b8o4bobo3bo2b2o$7bob7ob25o3b4ob7obob9o7bo2b4o$4bobob3ob4ob2ob4o2b4ob3ob12o2bo2b15o2bo7b2ob2obo$7b5ob22ob2ob30o5b2obo5bo$o2b3obob11o2b8ob10obob2ob5o2b6ob3ob4o3bobo2bobo$3bo2bob8ob22obobo3b2o4bo6b10o4bo2bo4bo$3b2o2b10o2b18o2b5obob8o5bob8o2bo3bo4bo$2bo3b11ob18obob3o4bob14ob4o4bobo2bo$6b6o2bo2b7o2b8ob4o3b2o2bo2b13ob2o3b2obobob3o$3bo2bob2o3b9o2b14o3bo6bob3o3b3ob2o2bo4b2obob3o$o3bob4o4bob2ob4ob9ob3o2bo9bob6obo2b3o3b3obob2o$8b3ob21ob2obo6bo5bob2o2bobo2bobo4bo2b4o$3bobo2b3o2b2obob2o2b2ob6o2bo9bo9b5o2bob2obob4ob2o$3bobobob4o3b5o2b10o3bo15bob4o3bo3bo2bo2b3o$4b2obo2b3o3bo2b3ob2ob5obo12bo8b3o5bo2bo2b5o$4bo2bo2b3o2bo3bo2b7o2bo6bo8bo2bo2bob4o2b2o5b3obo$3bo4bob4o4bo3b6o11bo2bob2o3bob2ob4o4bo6b2o$4bo4b2o2b2o2b3o3bob3o11bo7b2ob2obobo10bo3bo$9bo2bo3b4obo2b6o4bobo5bo8b2o16bo$7bo6b2o4b4ob2o5bobo4bo7bo2b3o4bo$16bo2bo3b5o20b2o2bo$10bo9bo3b2o3bo16b2o$21bob2obo9bo7b2obo$24bo17bo!",
"height": 100,
"left": 8,
"model": "Game of Life",
"title": "Gravure",
"top": 9,
"width": 100
}
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurationLoadingDialog</class>
<widget class="QDialog" name="ConfigurationLoadingDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>304</width>
<height>368</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="tree">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QCheckBox" name="include_other_models">
<property name="text">
<string>Include configurations from different models</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>3</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Title : </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="title">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_size">
<property name="text">
<string>Size : </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="size">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Model : </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="model">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigurationLoadingDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurationLoadingDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -349,14 +349,14 @@ pattern recorded :</string>
<item row="1" column="1">
<widget class="QPushButton" name="savePatternButton">
<property name="text">
<string>Save current pattern</string>
<string>Save configuration</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="openPatternButton">
<property name="text">
<string>(file) Open pattern</string>
<string>Open configuration</string>
</property>
</widget>
</item>
......
#ifndef CONFIGURATIONLOADINGDIALOG_HPP
#define CONFIGURATIONLOADINGDIALOG_HPP
#include <QDialog>
#include <QDir>
#include <QTreeWidgetItem>
#include <QFileSystemWatcher>
#include <QJsonObject>
namespace Ui {
class ConfigurationLoadingDialog;
}
//! \brief Exception lancée lors d'une erreur de chargement de configuration.
class ConfigurationLoadingException : public std::exception
{
std::string _msg;
public:
ConfigurationLoadingException(const std::string& msg) : _msg(msg){}
virtual const char* what() const noexcept override
{
return _msg.c_str();
}
};
class ConfigurationLoadingDialog : public QDialog
{
Q_OBJECT
public:
explicit ConfigurationLoadingDialog(const QString& current_model, QWidget *parent = nullptr);
~ConfigurationLoadingDialog();
//! \brief Retourne la configuration choisie en tant qu'objet JSON.
QJsonObject configuration() const;
protected:
void done(int r);
private:
QJsonObject load_configuration(const QString& filename);
void load_configurations();
QTreeWidgetItem *add_directory_contents(const QDir& dir);
private slots:
void update_info(QTreeWidgetItem* item, int);
void on_include_other_models_stateChanged(int);
private:
Ui::ConfigurationLoadingDialog *ui;
QFileSystemWatcher m_watcher;
QJsonObject m_current_configuration;
QString m_current_model;
};
#endif // CONFIGURATIONLOADINGDIALOG_HPP
......@@ -51,7 +51,8 @@ private slots:
void on_heightSpinBox_valueChanged(int arg1);
//! \brief Affiche l'interface pour sauvegarder la sélection actuelle comme une structure
void afficher_interface_sauvegarde_structure();
//! \returns Le nom du fichier choisi
QString afficher_interface_sauvegarde_structure();
void on_nbrStateComboBox_currentTextChanged(const QString &arg1);
......
......@@ -35,6 +35,8 @@ public:
std::string author;
//! Date de création de la structure, si connue
std::string date;
//! Position de la structure dans la grille, si elle est connue (utilisé pour la sauvegarde de configurations
Coord top_left;
public:
//! \brief Constructeur par défaut, représente une structure vide.
......@@ -73,6 +75,8 @@ public:
if (it->first.y < min_y) min_y = it->first.y;
}
top_left.x = min_x; top_left.y = min_y;
m_width = max_x - min_x + 1;
m_height = max_y - min_y + 1;
......@@ -83,7 +87,7 @@ public:
if (it->second == 0)
continue;
else
m_cells[it->first - Coord{min_x, min_y}] = it->second;
m_cells[it->first - top_left] = it->second;
}
}
//! \brief Fonction vidant la structure, la laissant vide.
......
#include "configurationloadingdialog.hpp"
#include "ui_configurationloadingdialog.h"
#include <QJsonDocument>
#include <QMessageBox>
#include <QTextStream>
ConfigurationLoadingDialog::ConfigurationLoadingDialog(const QString &current_model, QWidget *parent) :
QDialog(parent),
ui(new Ui::ConfigurationLoadingDialog),
m_current_model(current_model)
{
ui->setupUi(this);
load_configurations();
connect(ui->tree, &QTreeWidget::itemClicked, this, &ConfigurationLoadingDialog::update_info);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString&)
{
load_configurations();
});
}
ConfigurationLoadingDialog::~ConfigurationLoadingDialog()
{
delete ui;
}
QJsonObject ConfigurationLoadingDialog::configuration() const
{
return m_current_configuration;
}
void ConfigurationLoadingDialog::load_configurations()
{
// clear the previously watched directories
if (!m_watcher.directories().empty())
m_watcher.removePaths(m_watcher.directories());
QTreeWidgetItem* model_list = add_directory_contents(QDir("configurations/"));
model_list->setText(0, "Configurations");
ui->tree->clear();
ui->tree->addTopLevelItem(model_list);
model_list->setExpanded(true);
}
QTreeWidgetItem *ConfigurationLoadingDialog::add_directory_contents(const QDir &dir)
{
QTreeWidgetItem* root = new QTreeWidgetItem;
QFileInfoList dirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (QFileInfo dir, dirs)
{
QTreeWidgetItem *child = add_directory_contents(QDir(dir.absoluteFilePath()));
// empty directory
if (child->childCount() == 0)
continue;
child->setText(0, dir.fileName());
root->addChild(child);
}
QFileInfoList files = dir.entryInfoList(QStringList() << "*.json", QDir::Files);
Q_FOREACH (QFileInfo file, files)
{
QJsonObject obj = load_configuration(file.absoluteFilePath());
// Ignore configurations not associated with the current model
if (!ui->include_other_models->isChecked() && obj.value("model") != m_current_model)
continue;
QTreeWidgetItem *child = new QTreeWidgetItem();
child->setText(0, file.fileName());
// data is the complete filepath of the structure, store it so it can be easily accessed when an item is selected
child->setData(0, Qt::UserRole, file.absoluteFilePath());
root->addChild(child);
}
// register this directory for filesystem watching
m_watcher.addPath(dir.absolutePath());
return root;
}
void ConfigurationLoadingDialog::update_info(QTreeWidgetItem *item, int)
{
if (item->data(0, Qt::UserRole).isNull())
return;
const QJsonObject root = load_configuration(item->data(0, Qt::UserRole).toString());
ui->title->setText(root.value("title").toString());
ui->model->setText(root.value("model").toString());
unsigned width = root.value("width").toInt();
unsigned height = root.value("height").toInt();
ui->size->setText(QString::number(width) + "x" + QString::number(height));
m_current_configuration = root;
}
void ConfigurationLoadingDialog::done(int r)
{
if(QDialog::Accepted == r) // ok was pressed
{
if(!m_current_configuration.empty() && ui->tree->selectedItems().size() != 1) // validate the data somehow
{
QMessageBox::information(this, "Erreur", "Il est nécéssaire de sélectionner un modèle.");
return;
}
else
{
QDialog::done(r);
return;
}
}
else // cancel, close or exc was pressed
{
QDialog::done(r);
return;
}
}
QJsonObject ConfigurationLoadingDialog::load_configuration(const QString &filename)
{
QFile f(filename);
if (!f.open(QFile::ReadOnly | QFile::Text))
throw ConfigurationLoadingException("Impossible de lire le fichier");
QTextStream in(&f);
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(in.readAll().toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError)
{
throw ConfigurationLoadingException("Erreur de parsing JSON à " + std::to_string(parseError.offset) + ":" + parseError.errorString().toStdString());
}
const QJsonObject root = jsonDoc.object();
return root;
}
void ConfigurationLoadingDialog::on_include_other_models_stateChanged(int)
{
load_configurations();
}
......@@ -53,8 +53,8 @@ Structure Grid::to_structure() const
for (size_t i = 0; i < get_rows(); ++i)
for (size_t j = 0; j < get_col(); ++j) {
Coord pos;
pos.x = i;
pos.y = j;
pos.x = j;
pos.y = i;
data.push_back(std::make_pair(Coord{(int)j, (int)i}, get_state(pos)));
}
......
......@@ -3,16 +3,19 @@
#include "savingdialog.hpp"
#include "structurewriter.hpp"
#include "structurereader.hpp"
#include "factory.hpp"
#include "transitionrule.hpp"
#include "neighborhoodrule.hpp"
#include "propertyvisitors.hpp"
#include "modelloadingdialog.hpp"
#include "configurationloadingdialog.hpp"
#include "neighborhoodWidget.hpp"
#include <QJsonArray>
#include <QDate>
#include <QTextStream>
#include <QInputDialog>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
......@@ -74,12 +77,7 @@ void MainWindow::on_simSpeedSlider_valueChanged(int value)
void MainWindow::on_openPatternButton_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"",tr("Pattern file (*.json)"));
if(fileName == ""){
// L'utilisateur a annulé la recherche
return;
}
// Indiquer qu'un nouveau pattern vient d'être ouvert
load_grid_configuration();
}
void MainWindow::on_openRuleButton_clicked()
......@@ -108,13 +106,13 @@ void MainWindow::on_heightSpinBox_valueChanged(int)
ui->validateGridDim->setEnabled(true);
}
void MainWindow::afficher_interface_sauvegarde_structure()
QString MainWindow::afficher_interface_sauvegarde_structure()
{
SavingDialog dialog(this);
if (!dialog.exec())
return;
return "";
QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", QString(), "Format Cellulut (*.json);;Format Golly RLE (*.rle)");
QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", "", "Format Cellulut (*.json);;Format Golly RLE (*.rle)");
QFileInfo info(filename);
//printf("%s\n", info.suffix().toStdString().c_str());
// on pourrait utiliser un Factory ici, mais c'est possiblement overkill, les possibilités d'évolution de format de fichier sont faibles et ne justifient pas l'effort
......@@ -142,11 +140,13 @@ void MainWindow::afficher_interface_sauvegarde_structure()
{
QMessageBox::information(this, "Impossible de créer le fichier",
file.errorString());
return;
return "";
}
QTextStream stream(&file);
stream << QString::fromStdString(save_data);
return filename;
}
......@@ -444,7 +444,11 @@ void MainWindow::save_model()
QJsonDocument doc(root);
QFile file(filename);
file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate);
if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate))
{
QMessageBox::information(this, "Impossible de créer le fichier",
file.errorString());
}
file.write(doc.toJson());
file.close();
......@@ -508,14 +512,60 @@ void MainWindow::ui_update_alphabet(const Alphabet &alpha)
void MainWindow::save_grid_configuration()
{
Structure grid_data = ui->grid_view->get_grid().to_structure();
const Grid& grid = ui->grid_view->get_grid();
Structure grid_data = grid.to_structure();
QString title = QInputDialog::getText(this, "Choisir un titre", "Titre de la configuration : ");
if (title.isEmpty())
return;
RLEStructureWriter writer;
std::string structure_data = writer.save_structure(grid_data);
QJsonObject json_data;
json_data["width"] = (int)grid.get_col();
json_data["height"] = (int)grid.get_rows();
json_data["title"] = title;
json_data["model"] = m_loaded_model.value("title");
json_data["left"] = grid_data.top_left.x;
json_data["top"] = grid_data.top_left.y;
json_data["data"] = QString::fromStdString(structure_data);
QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", "", "Configuration Cellulut (*.json)");
if (filename.isEmpty())
return;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QFile::Text | QFile::Truncate))
{
QMessageBox::information(this, "Impossible de créer le fichier",
file.errorString());
}
QJsonDocument doc(json_data);
file.write(doc.toJson());
file.close();
}
void MainWindow::load_grid_configuration()
{
ConfigurationLoadingDialog dialog(m_loaded_model.value("title").toString(), this);
if (!dialog.exec())
return;
Grid g(dialog.configuration().value("width").toInt(10),
dialog.configuration().value("height").toInt(10));
Coord origin;
origin.x = dialog.configuration().value("left").toInt(0);
origin.y = dialog.configuration().value("top").toInt(0);
RLEStructureReader reader(dialog.configuration().value("data").toString().toStdString());
Structure s = reader.read_structure();
ui->grid_view->copy_grid(g);
ui->grid_view->paste_structure_at(origin, s);
ui->widthSpinBox->setValue(g.get_col());
ui->heightSpinBox->setValue(g.get_rows());
}
QJsonObject MainWindow::default_model() const
......@@ -557,5 +607,5 @@ void MainWindow::on_customize_button_clicked()
void MainWindow::on_savePatternButton_clicked()
{
afficher_interface_sauvegarde_structure();
save_grid_configuration();
}
......@@ -35,6 +35,7 @@ SOURCES += \
structurewriter.cpp \
structurelibraryview.cpp \
modelloadingdialog.cpp \
configurationloadingdialog.cpp \
transition_rules/totalistictransition.cpp \
uibuildervisitor.cpp
......@@ -69,7 +70,8 @@ HEADERS += \
../include/transition_rules/circulartransition.hpp \
../include/transition_rules/totalistictransition.hpp \
../include/modelloadingdialog.hpp \
../include/neighborhoodWidget.hpp
../include/neighborhoodWidget.hpp \
../include/configurationloadingdialog.hpp
FORMS += \
../forms/colorlabel.ui \
......@@ -77,7 +79,8 @@ FORMS += \
../forms/savingdialog.ui \
../forms/structurelibraryview.ui \
../forms/modelloadingdialog.ui \
../forms/neighborhoodWidget.ui
../forms/neighborhoodWidget.ui \
../forms/configurationloadingdialog.ui
# Default rules for deployment.
......
Supports Markdown
0%