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

4
#include "savingdialog.hpp"
5
#include "structurewriter.hpp"
6
7
8
9
#include "factory.hpp"
#include "transitionrule.hpp"
#include "neighborhoodrule.hpp"
#include "propertyvisitors.hpp"
10
#include "modelloadingdialog.hpp"
11
#include "neighborhoodWidget.hpp"
12

13
#include <QJsonArray>
Merwane Bouri's avatar
Merwane Bouri committed
14
15
#include <QDate>
#include <QTextStream>
16

17
18
19
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
      , ui(new Ui::MainWindow)
20
      , simulation()
21
22
{
    ui->setupUi(this);
23

24
25
26
    m_transition_rule = nullptr;
    m_neighborhood_rule = nullptr;

27
28
    init_transition_neighborhood_list();
    update_transition_settings();
29
    update_neighborhood_settings();
30

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

51
    ui->struct_library->update_cell_pixel_size(ui->grid_view->cell_screen_size());
52
53

    load_model(default_model());
54
55
56
57
58
}

MainWindow::~MainWindow()
{
    delete ui;
59
    // 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
60
61
}

62
63
/// Connections

64
65
66
67
68
69
70
71
72
void MainWindow::on_simSpeedSpinbox_valueChanged(int arg1)
{
    ui->simSpeedSlider->setValue(arg1);
}

void MainWindow::on_simSpeedSlider_valueChanged(int value)
{
    ui->simSpeedSpinbox->setValue(value);
}
73
74
75

void MainWindow::on_openPatternButton_clicked()
{
76
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"",tr("Pattern file (*.json)"));
77
78
    if(fileName == ""){
        // L'utilisateur a annulé la recherche
79
        return;
80
81
82
83
84
85
86
    }
    // Indiquer qu'un nouveau pattern vient d'être ouvert
}

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
87
88
89
90
91
92

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

    load_model(dialog.model());
93
94
}

95
void MainWindow::on_neighborhood_list_currentTextChanged(const QString &)
96
{
97
    update_neighborhood_settings();
98
99
}

100
void MainWindow::on_widthSpinBox_valueChanged(int)
101
102
103
104
{
    ui->validateGridDim->setEnabled(true);
}

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

110
111
void MainWindow::afficher_interface_sauvegarde_structure(bool)
{
112
    SavingDialog dialog(this);
113
114
115
116
117
    if (!dialog.exec())
        return;

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

    QTextStream stream(&file);
    stream << QString::fromStdString(save_data);
149
150
}

151

152
153
void MainWindow::on_validateGridDim_clicked()
{
154
155
    Grid oldGrid = ui->grid_view->get_grid();

156

157
158
159
    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);
160

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    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
    {
177
178
        //std::cout << "superieur\n";
        fflush(stdout);
179
180
181
        for (unsigned y = 0; y < nbrRow ; y++) {
            for (unsigned x = 0; x < nbrCol ; x++) {
                Coord pos = {static_cast<int>(x), static_cast<int>(y)};
182
183
184
185
186
187
                newGrid.set_cell(pos, oldGrid.get_state(pos));
                //std::cout << "oldState : " << oldGrid.get_state(pos) << endl;
            }
        }
    }

188
    ui->grid_view->copy_grid(newGrid);
189

190
191
    ui->validateGridDim->setEnabled(false);
}
192
193
194
195
196
197
198

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

    ui->grid_view->set_current_pen(val);
}
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

void MainWindow::on_randomPatternButton_clicked()
{
    // TODO Prendre en compte l'alphabet !
    // int nbr = rand() % nbr de states différents;

    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)
        {
            // TODO Remplacer 2 par le nbr d'états différents
            // TODO comprendre pourquoi il faut inverser x et y (???)
            unsigned state = rand() % 2;
            Coord pos = {static_cast<int>(x), static_cast<int>(y)};
218
            //std::cout << "position x= "<< pos.x << " y= "<< pos.y << endl;
219
220
221
222
            newGrid.set_cell(pos, state);
        }
    }

223
    ui->grid_view->copy_grid(newGrid);
224
}
225
226
227

void MainWindow::on_nbrStatesComboBox_currentTextChanged(const QString &arg1)
{
228
229
    // A mettre dans la fonction valider
    /*
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    // 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);
            }
        }
    }
249
    */
250
}
251

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

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

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

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

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

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

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

313
314
315
316
317
void MainWindow::enable_rule_customization()
{
    ui->simulation_tab->setEnabled(false);
    ui->customize_button->setEnabled(false);
    ui->rule_settings->setEnabled(true);
318
319
}

320
321
322
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);
}

void MainWindow::load_model(const QJsonObject &obj)
{
    ui->rule_name->setText(obj.value("title").toString());
330
    // TODO : load alphabet
331
    ui->transition_list->setCurrentText(obj.value("transition_name").toString());
332
    ui->neighborhood_list->setCurrentText(obj.value("neighborhood_name").toString());
333
334

    update_transition_settings();
335
    update_neighborhood_settings();
336
337
    disable_rule_customization();

338
339
    {
        PropertyLoaderVisitor loader(obj.value("transition_data").toObject());
340
        for (auto& prop : m_transition_rule->get_properties())
341
342
            prop->accept(loader);
        UIBuilderVisitor visit(ui->rule_settings_area);
343
        for (auto& prop : m_transition_rule->get_properties())
344
345
346
347
348
            prop->accept(visit);
    }

    {
        PropertyLoaderVisitor loader(obj.value("neighborhood_data").toObject());
349
        for (auto& prop : m_neighborhood_rule->get_properties())
350
351
            prop->accept(loader);
        UIBuilderVisitor visit(ui->neighborhood_settings_area);
352
        for (auto& prop : m_neighborhood_rule->get_properties())
353
354
            prop->accept(visit);
    }
355
356

    // 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
357
358
    simulation.setNeighborhoodRule(m_neighborhood_rule);
    simulation.setTransitionRule(m_transition_rule);
359
    simulation.setAlphabet(ui->grid_view->alphabet());
360
361
362
}

void MainWindow::save_model()
363
364
365
366
367
368
369
{
    SavingDialog dialog(this);
    if (!dialog.exec())
        return;

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

370
    PropertySaverVisitor trans_saver;
371
    for (auto& prop : m_transition_rule->get_properties())
372
373
374
        prop->accept(trans_saver);

    PropertySaverVisitor neighborhood_saver;
375
    for (auto& prop : m_neighborhood_rule->get_properties())
376
        prop->accept(neighborhood_saver);
377
378
379
380
381
382
383
384

    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();
385
386
387
388
    root["transition_data"] = trans_saver.save();

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

390
    // TODO : sauvegarder l'alphabet quand on pourra y accéder avec Simulation
391
392
393
394
395
396
397
398
399
    QJsonArray alphabet;

    root["alphabet"] = alphabet;

    QJsonDocument doc(root);
    QFile file(filename);
    file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate);
    file.write(doc.toJson());
    file.close();
400
401
402

    ui->rule_name->setText(dialog.titre());
    disable_rule_customization();
403
404

    load_model(root);
405
406
}

407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
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()
{
430
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"",tr("Image (*.png *.jpg *.jpeg *.bmp *.gif"));
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
    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);
}

449
450
QJsonObject MainWindow::default_model() const
{
451
452
453
454
    QJsonObject obj;
    obj["title"] = "Game of Life";
    obj["transition_name"] = "Game of Life";
    obj["transition_data"] = QJsonObject();
455

456
    return obj;
457
458
459
460
461
462
463
464
465
466
}

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

void MainWindow::on_customize_button_clicked()
{
    enable_rule_customization();
467
}
468