interface.cpp 25.2 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
#include <QElapsedTimer>
22

23
24
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
25
26
27
28
    , ui(new Ui::MainWindow)
    , simulation()
    , timer(new QTimer(this))
    , m_customizing(false)
29
30
{
    ui->setupUi(this);
31

32
33
34
    m_transition_rule = nullptr;
    m_neighborhood_rule = nullptr;

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

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

68
    ui->struct_library->update_cell_pixel_size(ui->grid_view->cell_screen_size());
69

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

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

MainWindow::~MainWindow()
{
    delete ui;
85
    // 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
86
87
}

88
89
/// Connections

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

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

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

    load_grid_configuration(dialog.configuration());
107
108
109
110
111
}

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
112
113
114
115
116
117

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

    load_model(dialog.model());
118
119
}

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

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

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

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

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

    QTextStream stream(&file);
    stream << QString::fromStdString(save_data);
174
175

    return filename;
176
177
}

178

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

183

184
185
186
    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);
187

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
    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
    {
204
205
        //std::cout << "superieur\n";
        fflush(stdout);
206
207
208
        for (unsigned y = 0; y < nbrRow ; y++) {
            for (unsigned x = 0; x < nbrCol ; x++) {
                Coord pos = {static_cast<int>(x), static_cast<int>(y)};
209
210
211
212
213
214
                newGrid.set_cell(pos, oldGrid.get_state(pos));
                //std::cout << "oldState : " << oldGrid.get_state(pos) << endl;
            }
        }
    }

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

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

    ui->grid_view->set_current_pen(val);
}
227
228
229

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

    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)
        {
241
            unsigned state = rand() % alphabetSize;
242
243
244
245
246
            Coord pos = {static_cast<int>(x), static_cast<int>(y)};
            newGrid.set_cell(pos, state);
        }
    }

247
    ui->grid_view->copy_grid(newGrid);
248
}
249

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

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

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();
277
278
    m_transition_rule = Factory<TransitionRule>::make(selected).release();
    if (!m_transition_rule)
279
280
        return;

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

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

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

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

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

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

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

334
    ui->transition_list->setCurrentText(obj.value("transition_name").toString());
335
    ui->neighborhood_list->setCurrentText(obj.value("neighborhood_name").toString());
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
365
366
    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));
        }
    }


367
    update_transition_settings();
368
    update_neighborhood_settings();
369
370
    disable_rule_customization();

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

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

    // 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
390
391
    simulation.setNeighborhoodRule(m_neighborhood_rule);
    simulation.setTransitionRule(m_transition_rule);
392
    simulation.reset();
Yann Boucher's avatar
Yann Boucher committed
393
    ui_update_alphabet(alpha);
394
395

    m_loaded_model = obj;
396
397
398
}

void MainWindow::save_model()
399
400
401
402
403
404
405
{
    SavingDialog dialog(this);
    if (!dialog.exec())
        return;

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

406
    PropertySaverVisitor trans_saver;
407
    for (auto& prop : m_transition_rule->get_properties())
408
409
410
        prop->accept(trans_saver);

    PropertySaverVisitor neighborhood_saver;
411
    for (auto& prop : m_neighborhood_rule->get_properties())
412
        prop->accept(neighborhood_saver);
413
414
415
416
417
418
419
420

    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();
421
422
423
424
    root["transition_data"] = trans_saver.save();

    root["neighborhood_name"] = ui->neighborhood_list->currentText();
    root["neighborhood_data"] = neighborhood_saver.save();
425
426
427

    QJsonArray alphabet;

428
429
430
431
432
433
434
435
436
437
438
439
440
    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);
    }

441
442
443
444
    root["alphabet"] = alphabet;

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

    ui->rule_name->setText(dialog.titre());
    disable_rule_customization();
455
456

    load_model(root);
457
458
}

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
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()
{
482
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"",tr("Image (*.png *.jpg *.jpeg *.bmp *.gif)"));
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
    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);
}

501
502
void MainWindow::ui_update_alphabet(const Alphabet &alpha)
{
Yann Boucher's avatar
Yann Boucher committed
503
    simulation.setAlphabet(alpha);
504
    ui->struct_library->set_alphabet(alpha);
505
506
507
    ui->grid_view->set_alphabet(alpha);
    ui->nbrStateComboBox->clear();
    for (unsigned i = 0; i < alpha.taille(); ++i)
Yann Boucher's avatar
Yann Boucher committed
508
    {
509
        ui->nbrStateComboBox->addItem(QString::number(i));
Yann Boucher's avatar
Yann Boucher committed
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
536
537
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;
538
539
}

540
541
void MainWindow::save_grid_configuration()
{
542
543
544
545
546
547
548
549
550
    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);
551
552

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

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

585
    RLEStructureReader reader(configuration.value("data").toString().toStdString());
586
    Structure s = reader.read_structure();
587

588
589
590
591
    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());
592
593
    simulation.reset();
    simulation.setGrid(g);
594
595
}

596
597
QJsonObject MainWindow::default_model() const
{
598
599
600
    const char* json = R"(
    {
        "title" : "Game of Life",
601
602
        "neighborhood_name" : "Moore",
        "neighborhood_data" : {},
603
604
605
606
607
608
609
610
611
        "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));
612

613
    return doc.object();
614
615
}

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
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();
}

635
636
637
638
// Décommenter pour activer le son sur win
//#define BAD_APPLE_WIN

#if defined(BAD_APPLE_WIN)
639
640
641
#include <windows.h>
#endif

642
643
644
645
646
void MainWindow::play_bad_apple()
{
    static QJsonArray bad_apple_frame_list;
    static QTimer bad_apple_timer;
    static bool bad_apple_connected;
647
    static QElapsedTimer bad_apple_elapsed;
648

649
    QFile f("extras/bad_apple.json");
650
    if (!f.open(QFile::ReadOnly | QFile::Text))
651
652
653
654
    {
        QMessageBox::warning(this, "", "Can't find 'extras/bad_apple.json'\n");
        return;
    }
655
656
657
658
659
660
661
662
663
664
665
666
    QTextStream in(&f);

    QJsonParseError parseError;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(in.readAll().toUtf8(), &parseError);
    QJsonObject frames = jsonDoc.object();

    bad_apple_timer.stop();
    bad_apple_frame_list = frames["data"].toArray();

    if (!bad_apple_connected)
    {
        bad_apple_timer.callOnTimeout([this]
667
        {
668

669
670
671
672
673
674
            unsigned frame_idx = bad_apple_elapsed.elapsed()/(1000.f/30);
            if ((int)frame_idx >= bad_apple_frame_list.size())
            {
                bad_apple_timer.stop();
                return;
            }
675

676
            Grid grid(120, 160);
677

678
            QJsonObject frame = bad_apple_frame_list[frame_idx].toObject();
679

680
681
682
            Coord origin;
            origin.x = frame.value("left").toInt();
            origin.y = frame.value("top").toInt();
683

684
685
686
            RLEStructureReader reader(frame.value("data").toString().toStdString());
            Structure s = reader.read_structure();
            s.top_left = origin;
687

688
689
690
691
            for (auto cell : s)
            {
                grid.set_cell(cell.first + s.top_left, cell.second);
            }
692

693
694
            ui->grid_view->copy_grid(grid);
        });
695
696
    }

697
698
    //bad_apple_player.setMedia(QUrl::fromLocalFile("bad-apple.mp3"));
    //bad_apple_player.play();
699
    ;
700

701
#ifdef BAD_APPLE_WIN
702
703
    PlaySound(TEXT("extras/bad-apple.wav"), GetModuleHandle(NULL), SND_FILENAME | SND_ASYNC);
    Sleep(1300); // delay for the sound to load
704
#else
705
706
707
    QMessageBox::information(this, "", "Music functionnality is only available on Windows");
#endif

708
    bad_apple_elapsed.start();
709
710
711
712
    bad_apple_timer.start(24);
    statusBar()->showMessage("Original : \"Bad Apple!!\" feat. Nomico by Alstroemeria Records", 60000);
}

713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
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);
}

751
752
753
754
755
756
757
void MainWindow::on_saveRuleButton_clicked()
{
    save_model();
}

void MainWindow::on_customize_button_clicked()
{
758
759
760
761
762
763
764
765
766
    if (!m_customizing)
    {
        enable_rule_customization();
    }
    else
    {
        disable_rule_customization();
        load_model(m_loaded_model);
    }
767
}
768

769
770
771

void MainWindow::on_savePatternButton_clicked()
{
772
    save_grid_configuration();
773
}
Anthony Noir's avatar
Anthony Noir committed
774
775
776
777
778
779

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
780
    ui->stepsPeriodLabel->setText(QString::number(simulation.getPeriod())+" steps");
Anthony Noir's avatar
Anthony Noir committed
781
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
Anthony Noir committed
782
783
784
785
786
787
788
789
}

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
790
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
Anthony Noir committed
791
792
793
794
795
796
}

void MainWindow::on_playPauseButton_clicked()
{
    if(timer->isActive()) {
        //Pause
Merwane Bouri's avatar
Merwane Bouri committed
797
        ui->playPauseButton->setText("Play");
Anthony Noir's avatar
Anthony Noir committed
798
799
800
        timer->stop();
    } else {
        //Play
Merwane Bouri's avatar
Merwane Bouri committed
801
        ui->playPauseButton->setText("Pause");
Anthony Noir's avatar
Anthony Noir committed
802
803
804
805
806
807
808
809
810
811
812
        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
813
814
815
816
817

void MainWindow::on_resetButton_clicked() {
    if(timer->isActive()) {
        timer->stop();
    }
Merwane Bouri's avatar
Merwane Bouri committed
818
    ui->playPauseButton->setText("Play");
Anthony Noir's avatar
reset    
Anthony Noir committed
819
820
    simulation.reset();
    ui->grid_view->copy_grid(simulation.getGrid());
Anthony Noir's avatar
Anthony Noir committed
821
    ui->stepsPeriodLabel->setText(QString::number(simulation.getPeriod())+" steps");
Anthony Noir's avatar
Anthony Noir committed
822
    ui->nbStepsLabel->setText(QString::number(simulation.getTime())+" steps");
Anthony Noir's avatar
reset    
Anthony Noir committed
823
}
Anthony Noir's avatar
Anthony Noir committed
824
825
826
827

void MainWindow::on_recordSpinBox_valueChanged(int newSize) {
    simulation.setHistorySize(newSize);
}
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854






void MainWindow::on_pushButton_clicked()
{
    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)
        {
            unsigned state = 0;
            Coord pos = {static_cast<int>(x), static_cast<int>(y)};
            newGrid.set_cell(pos, state);
        }
    }

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