interface.cpp 19.8 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"
14

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

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

30
31
32
    m_transition_rule = nullptr;
    m_neighborhood_rule = nullptr;

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

37
    connect(ui->action_save_struct, &QAction::triggered, this, &MainWindow::afficher_interface_sauvegarde_structure);
38
    connect(ui->struct_library, &StructureLibraryView::structure_copied, this, &MainWindow::copy_structure_clicked);
39
    connect(ui->grid_view, &GridView::zoom_changed, ui->struct_library, &StructureLibraryView::update_cell_pixel_size);
40
41
    connect(ui->transition_list, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int)
            { update_transition_settings(); });
42
43
44
45
46
47
48
49
50
51
    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");
            });
52
53
54
55
    connect(ui->actionCharger_depuis_une_image, &QAction::triggered, this, [this](bool)
            {
                load_from_image();
            });
56

57
    ui->struct_library->update_cell_pixel_size(ui->grid_view->cell_screen_size());
58
59

    load_model(default_model());
Anthony Noir's avatar
Anthony Noir committed
60
61
62
63
64

    connect(timer, &QTimer::timeout, this, [this](){
        on_nextButton_clicked();
        timer->start();
    });
65
66
67
68
69
}

MainWindow::~MainWindow()
{
    delete ui;
70
    // 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
71
72
}

73
74
/// Connections

75
76
77
78
79
80
81
82
83
void MainWindow::on_simSpeedSpinbox_valueChanged(int arg1)
{
    ui->simSpeedSlider->setValue(arg1);
}

void MainWindow::on_simSpeedSlider_valueChanged(int value)
{
    ui->simSpeedSpinbox->setValue(value);
}
84
85
86

void MainWindow::on_openPatternButton_clicked()
{
87
    load_grid_configuration();
88
89
90
91
92
}

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
93
94
95
96
97
98

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

    load_model(dialog.model());
99
100
}

101
void MainWindow::on_neighborhood_list_currentTextChanged(const QString &)
102
{
103
    update_neighborhood_settings();
104
105
}

106
void MainWindow::on_widthSpinBox_valueChanged(int)
107
108
109
110
{
    ui->validateGridDim->setEnabled(true);
}

111
void MainWindow::on_heightSpinBox_valueChanged(int)
112
113
114
115
{
    ui->validateGridDim->setEnabled(true);
}

116
QString MainWindow::afficher_interface_sauvegarde_structure()
117
{
118
    SavingDialog dialog(this);
119
    if (!dialog.exec())
120
        return "";
121

122
    QString filename = QFileDialog::getSaveFileName(this, "Choisir un nom de fichier", "", "Format Cellulut (*.json);;Format Golly RLE (*.rle)");
123
    QFileInfo info(filename);
124
    //printf("%s\n", info.suffix().toStdString().c_str());
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    // 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());
150
        return "";
151
152
153
154
    }

    QTextStream stream(&file);
    stream << QString::fromStdString(save_data);
155
156

    return filename;
157
158
}

159

160
161
void MainWindow::on_validateGridDim_clicked()
{
162
163
    Grid oldGrid = ui->grid_view->get_grid();

164

165
166
167
    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);
168

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    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
    {
185
186
        //std::cout << "superieur\n";
        fflush(stdout);
187
188
189
        for (unsigned y = 0; y < nbrRow ; y++) {
            for (unsigned x = 0; x < nbrCol ; x++) {
                Coord pos = {static_cast<int>(x), static_cast<int>(y)};
190
191
192
193
194
195
                newGrid.set_cell(pos, oldGrid.get_state(pos));
                //std::cout << "oldState : " << oldGrid.get_state(pos) << endl;
            }
        }
    }

196
    ui->grid_view->copy_grid(newGrid);
197

198
199
    ui->validateGridDim->setEnabled(false);
}
200
201
202
203
204
205
206

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

    ui->grid_view->set_current_pen(val);
}
207
208
209

void MainWindow::on_randomPatternButton_clicked()
{
210
    int alphabetSize = simulation.getAlphabet().taille();
211
212
213
214
215
216
217
218
219
220

    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)
        {
221
            unsigned state = rand() % alphabetSize;
222
223
224
225
226
            Coord pos = {static_cast<int>(x), static_cast<int>(y)};
            newGrid.set_cell(pos, state);
        }
    }

227
    ui->grid_view->copy_grid(newGrid);
228
}
229
230
231

void MainWindow::on_nbrStatesComboBox_currentTextChanged(const QString &arg1)
{
232
233
    // A mettre dans la fonction valider
    /*
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
    // Nombre d'états possibles
    int val = arg1.toInt();
    // Nombre de valeurs dans le comboxBox (en partant de 0)
    int currentNbrValue = ui->nbrStateComboBox->count();
    // Si on change le nbr d'états
    if (val != currentNbrValue) {
        // Si on ajoute des états
        if (val > currentNbrValue) {
            for(int i = currentNbrValue; i < val; i++ ) {
                ui->nbrStateComboBox->addItem(QString::number(i));
            }
        }
        // Si on retire des états
        else {
            for(int i = currentNbrValue - 1; i >= val; i-- ) {
                ui->nbrStateComboBox->removeItem(i);
            }
        }
    }
253
    */
254
}
255

256
257
258
259
260
void MainWindow::on_drawCellButton_clicked()
{
    ui->grid_view->fill_selection(ui->nbrStateComboBox->currentIndex());
}

261
262
263
264
265
void MainWindow::copy_structure_clicked(const Structure &s)
{
    ui->grid_view->set_clipboard(s);
    statusBar()->showMessage("Structure copied.", 5000);
}
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

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();
283
284
    m_transition_rule = Factory<TransitionRule>::make(selected).release();
    if (!m_transition_rule)
285
286
        return;

287
    if (m_transition_rule->get_properties().size() == 0)
288
289
290
291
292
293
        ui->rule_settings_area->hide();
    else
    {
        ui->rule_settings_area->show();

        UIBuilderVisitor visit(ui->rule_settings_area);
294
        for (auto& prop : m_transition_rule->get_properties())
295
296
            prop->accept(visit);
    }
297
}
298
299
300
void MainWindow::update_neighborhood_settings()
{
    std::string selected = ui->neighborhood_list->currentText().toStdString();
301
302
    m_neighborhood_rule = Factory<NeighborhoodRule>::make(selected).release();
    if (!m_neighborhood_rule)
303
304
        return;

305
    if (m_neighborhood_rule->get_properties().size() == 0)
306
307
308
309
310
311
        ui->neighborhood_settings_area->hide();
    else
    {
        ui->neighborhood_settings_area->show();

        UIBuilderVisitor visit(ui->neighborhood_settings_area);
312
        for (auto& prop : m_neighborhood_rule->get_properties())
313
314
315
            prop->accept(visit);
    }
}
316

317
318
319
void MainWindow::enable_rule_customization()
{
    ui->simulation_tab->setEnabled(false);
320
    ui->customize_button->setText("Cancel customization");
321
    ui->rule_settings->setEnabled(true);
322
    m_customizing = true;
323
324
}

325
326
327
328
329
void MainWindow::disable_rule_customization()
{
    ui->simulation_tab->setEnabled(true);
    ui->customize_button->setEnabled(true);
    ui->rule_settings->setEnabled(false);
330
331
    ui->customize_button->setText("Customize...");
    m_customizing = false;
332
333
334
335
336
}

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

338
    ui->transition_list->setCurrentText(obj.value("transition_name").toString());
339
    ui->neighborhood_list->setCurrentText(obj.value("neighborhood_name").toString());
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
367
368
369
370
    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));
        }
    }


371
    update_transition_settings();
372
    update_neighborhood_settings();
373
374
    disable_rule_customization();

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

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

393
394
    ui_update_alphabet(alpha);

395
    // 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
396
397
    simulation.setNeighborhoodRule(m_neighborhood_rule);
    simulation.setTransitionRule(m_transition_rule);
398
399
400
    simulation.setAlphabet(alpha);

    m_loaded_model = obj;
401
402
403
}

void MainWindow::save_model()
404
405
406
407
408
409
410
{
    SavingDialog dialog(this);
    if (!dialog.exec())
        return;

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

411
    PropertySaverVisitor trans_saver;
412
    for (auto& prop : m_transition_rule->get_properties())
413
414
415
        prop->accept(trans_saver);

    PropertySaverVisitor neighborhood_saver;
416
    for (auto& prop : m_neighborhood_rule->get_properties())
417
        prop->accept(neighborhood_saver);
418
419
420
421
422
423
424
425

    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();
426
427
428
429
    root["transition_data"] = trans_saver.save();

    root["neighborhood_name"] = ui->neighborhood_list->currentText();
    root["neighborhood_data"] = neighborhood_saver.save();
430
431
432

    QJsonArray alphabet;

433
434
435
436
437
438
439
440
441
442
443
444
445
    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);
    }

446
447
448
449
    root["alphabet"] = alphabet;

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

    ui->rule_name->setText(dialog.titre());
    disable_rule_customization();
460
461

    load_model(root);
462
463
}

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

506
507
508
509
510
511
512
513
514
515
void MainWindow::ui_update_alphabet(const Alphabet &alpha)
{
    ui->grid_view->set_alphabet(alpha);
    ui->nbrStateComboBox->clear();
    for (unsigned i = 0; i < alpha.taille(); ++i)
        ui->nbrStateComboBox->addItem(QString::number(i));
    ui->nbrStatesComboBox->clear();
    ui->nbrStatesComboBox->addItem(QString::number(alpha.taille()));
}

516
517
void MainWindow::save_grid_configuration()
{
518
519
520
521
522
523
524
525
526
    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);
527
528

    QJsonObject json_data;
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
    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();
551
552
553
554
}

void MainWindow::load_grid_configuration()
{
555
556
557
558
559
560
561
562
563
564
565
566
    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();
567

568
569
570
571
    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());
572
573
}

574
575
QJsonObject MainWindow::default_model() const
{
576
577
578
579
580
581
582
583
584
585
586
587
    const char* json = R"(
    {
        "title" : "Game of Life",
        "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));
588

589
    return doc.object();
590
591
592
593
594
595
596
597
598
}

void MainWindow::on_saveRuleButton_clicked()
{
    save_model();
}

void MainWindow::on_customize_button_clicked()
{
599
600
601
602
603
604
605
606
607
    if (!m_customizing)
    {
        enable_rule_customization();
    }
    else
    {
        disable_rule_customization();
        load_model(m_loaded_model);
    }
608
}
609

610
611
612

void MainWindow::on_savePatternButton_clicked()
{
613
    save_grid_configuration();
614
}
Anthony Noir's avatar
Anthony Noir committed
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

void MainWindow::on_nextButton_clicked()
{
    simulation.setGrid(ui->grid_view->get_grid());
    simulation.step();
    ui->grid_view->copy_grid(simulation.getGrid());
}

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());
}

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
649
650
651
652
653
654
655
656

void MainWindow::on_resetButton_clicked() {
    if(timer->isActive()) {
        timer->stop();
    }
    simulation.reset();
    ui->grid_view->copy_grid(simulation.getGrid());
}