interface.cpp 22.3 KB
Newer Older
1
2
3
#include "interface.hpp"
#include "ui_interface.h"

4
#include "savingdialog.hpp"
5
#include "structurewriter.hpp"
6
#include "structurereader.hpp"
7
8
9
10
#include "factory.hpp"
#include "transitionrule.hpp"
#include "neighborhoodrule.hpp"
#include "propertyvisitors.hpp"
11
#include "modelloadingdialog.hpp"
12
#include "configurationloadingdialog.hpp"
13
#include "neighborhoodDialog.hpp"
Yann Boucher's avatar
Yann Boucher committed
14
#include "colorlabel.h"
15

16
#include <QJsonArray>
Merwane Bouri's avatar
Merwane Bouri committed
17
18
#include <QDate>
#include <QTextStream>
19
#include <QInputDialog>
Anthony Noir's avatar
Anthony Noir committed
20
#include <QTimer>
21

22
23
24
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
      , ui(new Ui::MainWindow)
Anthony Noir's avatar
Anthony Noir committed
25
26
27
      , simulation()
      , timer(new QTimer(this))
      , m_customizing(false)
28
29
{
    ui->setupUi(this);
30

31
32
33
    m_transition_rule = nullptr;
    m_neighborhood_rule = nullptr;

34
35
    init_transition_neighborhood_list();
    update_transition_settings();
36
    update_neighborhood_settings();
37

38
    connect(ui->action_save_struct, &QAction::triggered, this, &MainWindow::afficher_interface_sauvegarde_structure);
39
    connect(ui->struct_library, &StructureLibraryView::structure_copied, this, &MainWindow::copy_structure_clicked);
40
    connect(ui->grid_view, &GridView::zoom_changed, ui->struct_library, &StructureLibraryView::update_cell_pixel_size);
Yann Boucher's avatar
Yann Boucher committed
41
42
43
44
45
    connect(ui->statesSettingsButton, &QPushButton::clicked, this, [this]
            {
                ColorLabel* dialog = new ColorLabel(simulation.getAlphabet(), this);
                if (dialog->exec())
                {
Yann Boucher's avatar
Yann Boucher committed
46
                    ui_update_alphabet(dialog->getAlphabet());
Yann Boucher's avatar
Yann Boucher committed
47
48
                }
            });
49
50
    connect(ui->transition_list, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int)
            { update_transition_settings(); });
51
52
53
54
55
56
57
58
59
60
    connect(ui->action_propos_de_Cellulut, &QAction::triggered, this, [this](bool)
            {
                QMessageBox::about(this, "À propos de Cellulut",
                                   "Projet de C++ réalisé dans le cadre de l'UV LO21 de l'UTC.\n\n"
                                   "Contributeurs : Anthony Noir, Eugène Pin, Merwane Bouri, Arthur Detree, Yann Boucher");
            });
    connect(ui->action_propos_de_Qt, &QAction::triggered, this, [this](bool)
            {
                QMessageBox::aboutQt(this, "À propos de Qt");
            });
61
62
63
64
    connect(ui->actionCharger_depuis_une_image, &QAction::triggered, this, [this](bool)
            {
                load_from_image();
            });
65

66
    ui->struct_library->update_cell_pixel_size(ui->grid_view->cell_screen_size());
67

68
69
70
71
72
    if (!try_load_saved_state())
    {
        load_model(default_model());
        load_grid_configuration(default_configuration());
    }
Anthony Noir's avatar
Anthony Noir committed
73
74
75
76
77

    connect(timer, &QTimer::timeout, this, [this](){
        on_nextButton_clicked();
        timer->start();
    });
78
79
80
81
82
}

MainWindow::~MainWindow()
{
    delete ui;
83
    // FIXME : il y a un risque de fuite de mémoire avec la gestion de pointeurs de m_transition_rule et m_neighborhood_rule, si l'application est quitée alors que ils pointent vers de la mémoire non transférée vers Simulation
84
85
}

86
87
/// Connections

88
89
90
91
92
93
94
95
96
void MainWindow::on_simSpeedSpinbox_valueChanged(int arg1)
{
    ui->simSpeedSlider->setValue(arg1);
}

void MainWindow::on_simSpeedSlider_valueChanged(int value)
{
    ui->simSpeedSpinbox->setValue(value);
}
97
98
99

void MainWindow::on_openPatternButton_clicked()
{
100
101
102
103
104
    ConfigurationLoadingDialog dialog(m_loaded_model.value("title").toString(), this);
    if (!dialog.exec())
        return;

    load_grid_configuration(dialog.configuration());
105
106
107
108
109
}

void MainWindow::on_openRuleButton_clicked()
{
    // Indiquer qu'une nouvelle règle vient d'être ouverte. La règle est sauvegardé, donc on peut lancer la simulation
110
111
112
113
114
115

    ModelLoadingDialog dialog(this);
    if (!dialog.exec())
        return;

    load_model(dialog.model());
116
117
}

118
void MainWindow::on_neighborhood_list_currentTextChanged(const QString &)
119
{
120
    update_neighborhood_settings();
121
122
}

123
void MainWindow::on_widthSpinBox_valueChanged(int)
124
125
126
127
{
    ui->validateGridDim->setEnabled(true);
}

128
void MainWindow::on_heightSpinBox_valueChanged(int)
129
130
131
132
{
    ui->validateGridDim->setEnabled(true);
}

133
QString MainWindow::afficher_interface_sauvegarde_structure()
134
{
135
    SavingDialog dialog(this);
136
    if (!dialog.exec())
137
        return "";
138

139
    QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", "", "Format Cellulut (*.json);;Format Golly RLE (*.rle)");
140
    QFileInfo info(filename);
141
    //printf("%s\n", info.suffix().toStdString().c_str());
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    // 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

    Structure s = ui->grid_view->selected_cells();
    s.author = dialog.auteur().toStdString();
    s.title = dialog.titre().toStdString();
    s.desc = dialog.desc().toStdString();
    s.date = dialog.date().toString().toStdString();

    std::string save_data;
    if (info.suffix() == "json")
    {
        JSONStructureWriter writer;
        save_data = writer.save_structure(s);
    }
    else if (info.suffix() == "rle")
    {
        RLEStructureWriter writer;
        save_data = writer.save_structure(s);
    }

    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly))
    {
        QMessageBox::information(this, "Impossible de créer le fichier",
                                 file.errorString());
167
        return "";
168
169
170
171
    }

    QTextStream stream(&file);
    stream << QString::fromStdString(save_data);
172
173

    return filename;
174
175
}

176

177
178
void MainWindow::on_validateGridDim_clicked()
{
179
180
    Grid oldGrid = ui->grid_view->get_grid();

181

182
183
184
    unsigned int nbrRow = ui->heightSpinBox->value(); // nbr de lignes => axe y
    unsigned int nbrCol = ui->widthSpinBox->value(); // nbr de colonne => axe x
    Grid newGrid(nbrRow, nbrCol);
185

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    unsigned oldNbrRow = oldGrid.get_rows(); // rows = nbr de lignes => axe y
    unsigned oldNbrCol = oldGrid.get_col(); // col = nbr de colonne => axe x

    if(oldNbrRow <= nbrRow && oldNbrCol <= nbrCol) {
        //std::cout << "superieur\n";
        fflush(stdout);
        for (unsigned y = 0; y < oldNbrRow ; y++) {
            for (unsigned x = 0; x < oldNbrCol ; x++) {
                Coord pos = {static_cast<int>(x), static_cast<int>(y)};
                newGrid.set_cell(pos, oldGrid.get_state(pos));
                //std::cout << "oldState : " << oldGrid.get_state(pos) << endl;
            }
        }
    }
    else
    {
202
203
        //std::cout << "superieur\n";
        fflush(stdout);
204
205
206
        for (unsigned y = 0; y < nbrRow ; y++) {
            for (unsigned x = 0; x < nbrCol ; x++) {
                Coord pos = {static_cast<int>(x), static_cast<int>(y)};
207
208
209
210
211
212
                newGrid.set_cell(pos, oldGrid.get_state(pos));
                //std::cout << "oldState : " << oldGrid.get_state(pos) << endl;
            }
        }
    }

213
    ui->grid_view->copy_grid(newGrid);
214
215
    simulation.reset();
    simulation.setGrid(newGrid);
216
217
    ui->validateGridDim->setEnabled(false);
}
218
219
220
221
222
223
224

void MainWindow::on_nbrStateComboBox_currentTextChanged(const QString &arg1)
{
    unsigned val = arg1.toInt();

    ui->grid_view->set_current_pen(val);
}
225
226
227

void MainWindow::on_randomPatternButton_clicked()
{
228
    int alphabetSize = simulation.getAlphabet().taille();
229
230
231
232
233
234
235
236
237
238

    Grid oldGrid = ui->grid_view->get_grid();
    unsigned nbrRow = oldGrid.get_rows(); // rows = nbr de lignes => axe y
    unsigned nbrCol = oldGrid.get_col(); // col = nbr de colonne => axe x
    Grid newGrid(nbrRow, nbrCol);

    for (unsigned y = 0; y < nbrRow; ++y)
    {
        for (unsigned x = 0; x < nbrCol; ++x)
        {
239
            unsigned state = rand() % alphabetSize;
240
241
242
243
244
            Coord pos = {static_cast<int>(x), static_cast<int>(y)};
            newGrid.set_cell(pos, state);
        }
    }

245
    ui->grid_view->copy_grid(newGrid);
246
}
247

248
249
250
251
252
void MainWindow::on_drawCellButton_clicked()
{
    ui->grid_view->fill_selection(ui->nbrStateComboBox->currentIndex());
}

253
254
255
256
257
void MainWindow::copy_structure_clicked(const Structure &s)
{
    ui->grid_view->set_clipboard(s);
    statusBar()->showMessage("Structure copied.", 5000);
}
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

void MainWindow::init_transition_neighborhood_list()
{
    for (const auto& transition : Factory<TransitionRule>::list_choices())
    {
        ui->transition_list->addItem(QString::fromStdString(transition));
    }

    for (const auto& rule : Factory<NeighborhoodRule>::list_choices())
    {
        ui->neighborhood_list->addItem(QString::fromStdString(rule));
    }
}

void MainWindow::update_transition_settings()
{
    std::string selected = ui->transition_list->currentText().toStdString();
275
276
    m_transition_rule = Factory<TransitionRule>::make(selected).release();
    if (!m_transition_rule)
277
278
        return;

279
    if (m_transition_rule->get_properties().size() == 0)
280
281
282
283
284
285
        ui->rule_settings_area->hide();
    else
    {
        ui->rule_settings_area->show();

        UIBuilderVisitor visit(ui->rule_settings_area);
286
        for (auto& prop : m_transition_rule->get_properties())
287
288
            prop->accept(visit);
    }
289
}
290
291
292
void MainWindow::update_neighborhood_settings()
{
    std::string selected = ui->neighborhood_list->currentText().toStdString();
293
294
    m_neighborhood_rule = Factory<NeighborhoodRule>::make(selected).release();
    if (!m_neighborhood_rule)
295
296
        return;

297
    if (m_neighborhood_rule->get_properties().size() == 0)
298
299
300
301
302
303
        ui->neighborhood_settings_area->hide();
    else
    {
        ui->neighborhood_settings_area->show();

        UIBuilderVisitor visit(ui->neighborhood_settings_area);
304
        for (auto& prop : m_neighborhood_rule->get_properties())
305
306
307
            prop->accept(visit);
    }
}
308

309
310
void MainWindow::enable_rule_customization()
{
Yann Boucher's avatar
Yann Boucher committed
311
    //ui->simulation_tab->setEnabled(false);
Yann Boucher's avatar
Yann Boucher committed
312
    ui->savePatternButton->setEnabled(false);
313
    ui->customize_button->setText("Cancel customization");
314
    ui->rule_settings->setEnabled(true);
315
    m_customizing = true;
316
317
}

318
319
void MainWindow::disable_rule_customization()
{
Yann Boucher's avatar
Yann Boucher committed
320
    //ui->simulation_tab->setEnabled(true);
Yann Boucher's avatar
Yann Boucher committed
321
    ui->savePatternButton->setEnabled(true);
322
323
    ui->customize_button->setEnabled(true);
    ui->rule_settings->setEnabled(false);
324
325
    ui->customize_button->setText("Customize...");
    m_customizing = false;
326
327
328
329
330
}

void MainWindow::load_model(const QJsonObject &obj)
{
    ui->rule_name->setText(obj.value("title").toString());
331

332
    ui->transition_list->setCurrentText(obj.value("transition_name").toString());
333
    ui->neighborhood_list->setCurrentText(obj.value("neighborhood_name").toString());
334

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
    bool alpha_initialise = false;
    Alphabet alpha;
    if (obj.value("alphabet").isArray())
    {
        for (auto entry : obj.value("alphabet").toArray())
        {
            if (!entry.isObject())
                continue;

            auto entry_obj = entry.toObject();
            std::string name = entry_obj.value("name").toString().toStdString();

            if (!entry_obj.value("color").isArray())
                continue;

            auto color_array = entry_obj.value("color").toArray();
            stateColor color(color_array[0].toInt(), color_array[1].toInt(), color_array[2].toInt());

            // Initialisation avec le premier élement
            if (!alpha_initialise)
            {
                alpha = Alphabet(state(color, name));
                alpha_initialise = true;
            }
            else
                alpha.newEtat(state(color, name));
        }
    }


365
    update_transition_settings();
366
    update_neighborhood_settings();
367
368
    disable_rule_customization();

369
370
    {
        PropertyLoaderVisitor loader(obj.value("transition_data").toObject());
371
        for (auto& prop : m_transition_rule->get_properties())
372
373
            prop->accept(loader);
        UIBuilderVisitor visit(ui->rule_settings_area);
374
        for (auto& prop : m_transition_rule->get_properties())
375
376
377
378
379
            prop->accept(visit);
    }

    {
        PropertyLoaderVisitor loader(obj.value("neighborhood_data").toObject());
380
        for (auto& prop : m_neighborhood_rule->get_properties())
381
382
            prop->accept(loader);
        UIBuilderVisitor visit(ui->neighborhood_settings_area);
383
        for (auto& prop : m_neighborhood_rule->get_properties())
384
385
            prop->accept(visit);
    }
386
387

    // On transfère la propriété de ces pointeurs vers Simulation, qui en est désormais propriétaire pour l'exécution de l'automate
388
389
    simulation.setNeighborhoodRule(m_neighborhood_rule);
    simulation.setTransitionRule(m_transition_rule);
Yann Boucher's avatar
Yann Boucher committed
390
    ui_update_alphabet(alpha);
391
392

    m_loaded_model = obj;
393
394
395
}

void MainWindow::save_model()
396
397
398
399
400
401
402
{
    SavingDialog dialog(this);
    if (!dialog.exec())
        return;

    QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", QString(), "Modèle d'automate (*.json)");

403
    PropertySaverVisitor trans_saver;
404
    for (auto& prop : m_transition_rule->get_properties())
405
406
407
        prop->accept(trans_saver);

    PropertySaverVisitor neighborhood_saver;
408
    for (auto& prop : m_neighborhood_rule->get_properties())
409
        prop->accept(neighborhood_saver);
410
411
412
413
414
415
416
417

    QJsonObject root;
    root["title"] = dialog.titre();
    root["author"] = dialog.auteur();
    root["desc"] = dialog.desc();
    root["date"] = dialog.date().toString();

    root["transition_name"] = ui->transition_list->currentText();
418
419
420
421
    root["transition_data"] = trans_saver.save();

    root["neighborhood_name"] = ui->neighborhood_list->currentText();
    root["neighborhood_data"] = neighborhood_saver.save();
422
423
424

    QJsonArray alphabet;

425
426
427
428
429
430
431
432
433
434
435
436
437
    for (unsigned i = 0; i < simulation.getAlphabet().taille(); ++i)
    {
        auto state = simulation.getAlphabet().getState(i);
        QJsonArray color;
        QJsonObject entry;
        entry["name"] = QString::fromStdString(state.getStateLabel());
        color.push_back(state.getColor().getRed());
        color.push_back(state.getColor().getGreen());
        color.push_back(state.getColor().getBlue());
        entry["color"] = color;
        alphabet.push_back(entry);
    }

438
439
440
441
    root["alphabet"] = alphabet;

    QJsonDocument doc(root);
    QFile file(filename);
442
443
444
445
446
    if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate))
    {
        QMessageBox::information(this, "Impossible de créer le fichier",
                                 file.errorString());
    }
447
448
    file.write(doc.toJson());
    file.close();
449
450
451

    ui->rule_name->setText(dialog.titre());
    disable_rule_customization();
452
453

    load_model(root);
454
455
}

456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
static unsigned closest_state(const Alphabet& alph, const QColor& color)
{
    unsigned best_distance = std::numeric_limits<unsigned>::max();
    unsigned best_match = 0;
    for (unsigned i = 0; i < alph.taille(); ++i)
    {
        state s = alph.getState(i);
        unsigned r_diff = qAbs(color.red() - s.getColor().getRed());
        unsigned g_diff = qAbs(color.green() - s.getColor().getGreen());
        unsigned b_diff = qAbs(color.blue() - s.getColor().getBlue());
        unsigned distance = r_diff*r_diff + g_diff*g_diff + b_diff*b_diff;
        if (distance < best_distance)
        {
            best_distance = distance;
            best_match = i;
        }
    }

    return best_match;
}

void MainWindow::load_from_image()
{
479
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"",tr("Image (*.png *.jpg *.jpeg *.bmp *.gif)"));
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
    QImage img(fileName);
    if (img.isNull())
        return;

    Grid grid = ui->grid_view->get_grid();

    img = img.scaled(grid.get_col(),
                     grid.get_rows());
    for (int x = 0; x < img.width(); ++x)
        for (int y = 0; y < img.height(); ++y)
        {
            unsigned closest = closest_state(ui->grid_view->alphabet(), img.pixelColor(x, y));
            grid.set_cell(Coord{x, y}, closest);
        }

    ui->grid_view->copy_grid(grid);
}

498
499
void MainWindow::ui_update_alphabet(const Alphabet &alpha)
{
Yann Boucher's avatar
Yann Boucher committed
500
    simulation.setAlphabet(alpha);
501
502
503
    ui->grid_view->set_alphabet(alpha);
    ui->nbrStateComboBox->clear();
    for (unsigned i = 0; i < alpha.taille(); ++i)
Yann Boucher's avatar
Yann Boucher committed
504
    {
505
        ui->nbrStateComboBox->addItem(QString::number(i));
Yann Boucher's avatar
Yann Boucher committed
506
    }
507
508
}

509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
bool MainWindow::try_load_saved_state()
{
    QFile file("saved_state.json");
    if (!file.open(QFile::ReadOnly | QFile::Text))
    {
        return false;
    }
    QTextStream in(&file);

    QJsonParseError parseError;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(in.readAll().toUtf8(), &parseError);
    if(parseError.error != QJsonParseError::NoError)
    {
        return false;
    }
    QJsonObject data = jsonDoc.object();
    QJsonObject model = data.value("model").toObject();
    QJsonObject config = data.value("config").toObject();
    if (model.isEmpty() || config.isEmpty())
        return false;

    load_model(model);
    load_grid_configuration(config);

    return true;
}

536
537
void MainWindow::save_grid_configuration()
{
538
539
540
541
542
543
544
545
546
    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);
547
548

    QJsonObject json_data;
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
    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();
571
572
}

573
void MainWindow::load_grid_configuration(const QJsonObject& configuration)
574
{
575
576
    Grid g(configuration.value("width").toInt(10),
           configuration.value("height").toInt(10));
577
    Coord origin;
578
579
    origin.x = configuration.value("left").toInt(0);
    origin.y = configuration.value("top").toInt(0);
580

581
    RLEStructureReader reader(configuration.value("data").toString().toStdString());
582
    Structure s = reader.read_structure();
583

584
585
586
587
    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());
588
589
    simulation.reset();
    simulation.setGrid(g);
590
591
}

592
593
QJsonObject MainWindow::default_model() const
{
594
595
596
    const char* json = R"(
    {
        "title" : "Game of Life",
597
598
        "neighborhood_name" : "Moore",
        "neighborhood_data" : {},
599
600
601
602
603
604
605
606
607
        "transition_name" : "Game of Life",
        "transition_data" : {},
        "alphabet" : [
        {"name" : "Dead", "color" : [255, 255, 255]},
        {"name" : "Alive", "color" : [0, 0, 255]}]
    }
)";

    QJsonDocument doc = QJsonDocument::fromJson(QByteArray(json));
608

609
    return doc.object();
610
611
}

612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
QJsonObject MainWindow::default_configuration() const
{
    const char* json = R"(
    {
    "data": "x = 2, y = 2\n!",
    "height": 50,
    "left": 0,
    "model": "Game of Life",
    "title": "Game of Life Default",
    "top": 0,
    "width": 50
    }
)";

    QJsonDocument doc = QJsonDocument::fromJson(QByteArray(json));

    return doc.object();
}

void MainWindow::closeEvent(QCloseEvent *e)
{
    // Sauvegarder l'état actuel de l'application

    const Grid& grid = ui->grid_view->get_grid();
    Structure grid_data = grid.to_structure();

    RLEStructureWriter writer;
    std::string structure_data = writer.save_structure(grid_data);

    QJsonObject config_data;
    config_data["width"] = (int)grid.get_col();
    config_data["height"] = (int)grid.get_rows();
    config_data["title"] = "Saved configuration";
    config_data["model"] = m_loaded_model.value("title");
    config_data["left"] = grid_data.top_left.x;
    config_data["top"] = grid_data.top_left.y;
    config_data["data"] = QString::fromStdString(structure_data);

    QJsonObject model = m_loaded_model;

    QJsonObject save_data;
    save_data["config"] = config_data;
    save_data["model"] = model;

    QFile file("saved_state.json");
    if (!file.open(QIODevice::WriteOnly | QFile::Text | QFile::Truncate))
    {
        return;
    }

    QJsonDocument doc(save_data);
    file.write(doc.toJson());
    file.close();

    QMainWindow::closeEvent(e);
}

669
670
671
672
673
674
675
void MainWindow::on_saveRuleButton_clicked()
{
    save_model();
}

void MainWindow::on_customize_button_clicked()
{
676
677
678
679
680
681
682
683
684
    if (!m_customizing)
    {
        enable_rule_customization();
    }
    else
    {
        disable_rule_customization();
        load_model(m_loaded_model);
    }
685
}
686

687
688
689

void MainWindow::on_savePatternButton_clicked()
{
690
    save_grid_configuration();
691
}
Anthony Noir's avatar
Anthony Noir committed
692
693
694
695
696
697

void MainWindow::on_nextButton_clicked()
{
    simulation.setGrid(ui->grid_view->get_grid());
    simulation.step();
    ui->grid_view->copy_grid(simulation.getGrid());
Anthony Noir's avatar
Anthony Noir committed
698
    ui->stepsPeriodLabel->setText(QString::number(simulation.getPeriod())+" steps");
Anthony Noir's avatar
Anthony Noir committed
699
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
Anthony Noir committed
700
701
702
703
704
705
706
707
}

void MainWindow::on_prevButton_clicked()
{
    if(!simulation.back()) {
        QMessageBox::critical(this, "Retour arrière impossible", "L'historique est vide : impossible de revenir en arrière.");
    }
    ui->grid_view->copy_grid(simulation.getGrid());
Anthony Noir's avatar
Anthony Noir committed
708
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
Anthony Noir committed
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
}

void MainWindow::on_playPauseButton_clicked()
{
    if(timer->isActive()) {
        //Pause
        timer->stop();
    } else {
        //Play
        int frequence = ui->simSpeedSpinbox->value();
        if(frequence == 0) {
            QMessageBox::critical(this, "Impossible de démarrer", "Impossible de lancer la simulation à une vitesse nulle.");
        } else if(!simulation.runnable()) {
            QMessageBox::critical(this, "Impossible de démarrer", "Impossible de lancer la simulation : les règles ne sont pas définies ou incompatibles.");
        } else {
            timer->setInterval(1000/frequence);
            timer->start();
        }
    }
}
Anthony Noir's avatar
reset    
Anthony Noir committed
729
730
731
732
733
734
735

void MainWindow::on_resetButton_clicked() {
    if(timer->isActive()) {
        timer->stop();
    }
    simulation.reset();
    ui->grid_view->copy_grid(simulation.getGrid());
Anthony Noir's avatar
Anthony Noir committed
736
    ui->stepsPeriodLabel->setText(QString::number(simulation.getPeriod())+" steps");
Anthony Noir's avatar
Anthony Noir committed
737
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
reset    
Anthony Noir committed
738
}
Anthony Noir's avatar
Anthony Noir committed
739
740
741
742

void MainWindow::on_recordSpinBox_valueChanged(int newSize) {
    simulation.setHistorySize(newSize);
}