gridview.cpp 11.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
\file gridview.cpp
\date 28/04/2021
\author Yann Boucher
\version 1
\brief GridView

Cette classe représente le widget utilisé pour l'affichage et l'interaction avec la grille actuelle.
        **/


#include "gridview.hpp"

#include <QEvent>
#include <QMouseEvent>
#include <QGraphicsSceneHoverEvent>
#include <QApplication>
#include <QGridLayout>
#include <QToolTip>
#include <QLabel>

#include <QDebug>

24
25
#include <vector>

26
27
#include <qmath.h>

28
class GridItem : public QGraphicsRectItem
29
30
31
{
public:
    GridItem(QColor color, const QString& state_name, QGraphicsItem *parent = nullptr)
32
        : QGraphicsRectItem(0, 0, GridItem::width(), GridItem::height(), parent), m_state_name(state_name)
33
34
35
36
37
38
39
40
    {
        setAcceptHoverEvents(true);
        setPen(QPen(QBrush(color), 0));
        setBrush(QBrush(color));
        setToolTip(m_state_name);
        setFlag(QGraphicsItem::ItemIsSelectable);
    }

41
42
43
44
45
    static int width()
    { return 20; }
    static int height()
    { return 20; }

46
protected:
47
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
48
    {
49
50
51
52
        assert(scene()->views().size() != 0);
        QGraphicsView* view = scene()->views().first();
        QSizeF screen_size = view->transform().mapRect(sceneBoundingRect()).size();

53
54
        QRectF rect = boundingRect();

55
56
57
58
        if (screen_size.width() < 7.5)
            painter->setPen(Qt::NoPen); // if the cell is too small, don't draw its outline
        else
            painter->setPen(QPen(Qt::black, 0)); // cosmetic 1-pixel pen
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

        if (isSelected())
        {
            QBrush selection_brush(Qt::cyan);
            painter->setBrush(selection_brush);
        }
        else
        {
            painter->setBrush(brush());
        }

        painter->drawRect(rect);
    }
    // infos associées à la cellule affichée
public:
    unsigned cell_state;
    Coord cell_pos;

private:
    QString m_state_name;
};

81
82
83
84
85
static QColor stateColor_to_QColor(const stateColor& sc)
{
    return QColor::fromRgb(sc.getRed(), sc.getGreen(), sc.getBlue());
}

86
87
88
89
90
void GridGraphicsView::mousePressEvent(QMouseEvent *event)
{
    QGraphicsItem* gitem_ptr = itemAt(event->pos());
    GridItem* item = dynamic_cast<GridItem*>(gitem_ptr);

91
    if (event->button() == Qt::LeftButton && item)
92
93
94
    {
        unsigned state = m_gridview.current_pen();
        item->setBrush(QBrush(stateColor_to_QColor(m_gridview.alphabet().getState(state).getColor())));
95
        item->cell_state = state;
96
97
        item->update();
    }
98
    else if (event->button() == Qt::RightButton && item)
99
100
101
102
103
104
105
106
    {
        QGraphicsView::mousePressEvent(event);
        item->setSelected(true);
    }
    else
        QGraphicsView::mousePressEvent(event);
}

107
GridView::GridView(QWidget *parent)
108
    : QFrame(parent), m_width(10), m_height(10)
109
{
110
111
    setMouseTracking(true);

112
113
114
115
    setFrameStyle(Sunken | StyledPanel);

    m_scene = new QGraphicsScene(this);

116
    m_view = new GridGraphicsView(*this, this);
117
    m_view->setRenderHint(QPainter::Antialiasing, false);
118
119
120
121
122
123
124
125
    m_view->setDragMode(QGraphicsView::RubberBandDrag);
    m_view->setOptimizationFlags(QGraphicsView::DontSavePainterState);
    m_view->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
    m_view->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    m_view->setScene(m_scene);
    m_view->viewport()->setCursor(Qt::ArrowCursor);

    detail::Graphics_view_zoom* z = new detail::Graphics_view_zoom(m_view);
126
    (void)z;
127
128
129
130
131
132

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(m_view);
    layout->addWidget(new QLabel("Maintenir SHIFT pour déplacer la grille", this));
    setLayout(layout);

133
    set_current_pen(0);
134
135
136
137
138
    // Test Alphabet
    Alphabet alph(state{stateColor{255, 255, 255}, "Dead"});
    alph.newEtat(state{stateColor{0, 0, 255}, "Alive"});
    set_alphabet(alph);

139
140
141
    // Initialisation la première fois
    m_height = 10;
    m_width = 10;
142

143
144
    Grid default_grid(m_height, m_width);
    copy_grid(default_grid);
145
146
}

147
148
149
150
151
void GridView::set_alphabet(const Alphabet &alph)
{
    m_alph = alph;
}

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
const Alphabet &GridView::alphabet() const
{
    return m_alph;
}

void GridView::set_current_pen(unsigned state)
{
    m_pen = state;
}

unsigned GridView::current_pen() const
{
    return m_pen;
}

167
168
169
170
171
void GridView::set_clipboard(const Structure &s)
{
    m_copy_paste_buffer = s;
}

172
173
174
void GridView::copy_grid(const Grid &grid)
{
    m_scene->clear();
175
176
    m_height = grid.get_rows();
    m_width = grid.get_col();
177
    // Populate scene;
178
    for (unsigned i = 0; i < m_width; ++i)
179
    {
180
        for (unsigned j = 0; j < m_height; ++j)
181
        {
182
183
            Coord pos = {static_cast<int>(i), static_cast<int>(j)};
            unsigned state = grid.get_state(pos);
184
185
186

            GridItem *item = new GridItem(stateColor_to_QColor(m_alph.getState(state).getColor()), QString::fromStdString(m_alph.getState(state).getStateLabel()));
            //GridItem *item = new GridItem((i ^ j) % 2 ? Qt::blue : Qt::white, (i ^ j) % 2 ? "Alive" : "Dead");
187
188
189
            item->setPos(QPointF(i*GridItem::width(), j*GridItem::height()));
            item->cell_pos = Coord{(int)i, (int)j};
            item->cell_state = state;
190
191
192
            m_scene->addItem(item);
        }
    }
193
194
    m_width = grid.get_col();
    m_height = grid.get_rows();
195
196
197
198
}

Grid GridView::get_grid() const
{
199
200
201
202
203
204
205
    Grid grid = Grid(m_height, m_width);
    for (size_t i = 0; i < grid.get_col(); ++i)
    {
        for (size_t j = 0; j < grid.get_rows(); ++j)
        {
            const GridItem* item = item_at(Coord{(int)i, (int)j});
            assert(item);
206
207
208
209
            Coord pos;
            pos.x = i;
            pos.y = j;
            grid.set_cell(pos, item->cell_state);
210
211
212
213
        }
    }

    return grid;
214
215
216
217
218
219
220
221
222
}

void GridView::clear_selection()
{
    m_scene->clearSelection();
}

Structure GridView::selected_cells() const
{
223
224
225
    if (m_scene->selectedItems().empty())
        return Structure{};

226
    std::vector<std::pair<Coord, unsigned>> cells;
227
228
229

    // on calcule la position du coin supérieur gauche de la sélection, afin de la considérer comme l'origine de de la structure
    Coord origin = top_left_of_selection();
230
    Q_FOREACH(const auto& item, m_scene->selectedItems())
231
232
    {
        GridItem* cell = static_cast<GridItem*>(item);
233
        cells.push_back(std::make_pair(cell->cell_pos - origin, cell->cell_state));
234
235
236
237
    }
    return Structure(cells.begin(), cells.end());
}

238
239
240
241
242
GridItem *GridView::item_at(Coord pos)
{
    QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
    return dynamic_cast<GridItem*>(gitem_ptr);
}
243
244
245
246
247
const GridItem *GridView::item_at(Coord pos) const
{
    QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
    return dynamic_cast<GridItem*>(gitem_ptr);
}
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

void GridView::copy_selection()
{
    m_copy_paste_buffer = selected_cells();
}

void GridView::paste_clipboard()
{
    // la destination est la cellule sélectionnée actuelle, on ne fait rien si on est pas dans ce cas
    if (m_scene->selectedItems().size() < 1)
        return;

    Coord origin = top_left_of_selection();

    for (const auto& cell : m_copy_paste_buffer)
    {
264
        Coord corrected = wrap_coords(cell.first + origin, m_width, m_height);
265
266
267
268
269

        GridItem* item = item_at(corrected);
        assert(item != nullptr);

        item->cell_state = cell.second;
270
        item->setBrush(QBrush(stateColor_to_QColor(m_alph.getState(cell.second).getColor())));
271
272
273
274
275
276
277
278
279
280
        item->update();
    }

}

void GridView::delete_selection()
{
    for (auto& abstract_item : m_scene->selectedItems())
    {
        GridItem* item = static_cast<GridItem*>(abstract_item);
281
        item->setBrush(QBrush(stateColor_to_QColor(m_alph.getState(0).getColor())));
282
283
284
285
286
287
288
289
290
291
        item->cell_state = 0;
        item->update();
    }
    clear_selection();
}

Coord GridView::top_left_of_selection() const
{
    // on calcule la position du coin supérieur gauche de la sélection
    Coord selection_top_left = Coord{std::numeric_limits<int>::max(), std::numeric_limits<int>::max()};
292
    Q_FOREACH(const auto& item, m_scene->selectedItems())
293
294
295
296
297
298
299
300
301
302
303
304
    {
        GridItem* cell = static_cast<GridItem*>(item);
        if (cell->cell_pos.x < selection_top_left.x)
            selection_top_left.x = cell->cell_pos.x;
        if (cell->cell_pos.y < selection_top_left.y)
            selection_top_left.y = cell->cell_pos.y;
    }

    return selection_top_left;
}


305
306
307
308
void GridView::keyPressEvent(QKeyEvent *event)
{
    if (event->modifiers() & Qt::ShiftModifier)
    {
309
310
311
        // prevent items from stealing the mouse event
        for (auto& item : m_scene->items())
            item->setAcceptedMouseButtons(Qt::NoButton);
312
313
        m_view->setDragMode(QGraphicsView::ScrollHandDrag);
    }
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

    // copier-couper-coller
    if (event->modifiers() & Qt::ControlModifier)
    {
        if (event->key() == Qt::Key_C)
        {
            copy_selection();
        }
        else if (event->key() == Qt::Key_X)
        {
            copy_selection();
            delete_selection();
        }
        else if (event->key() == Qt::Key_V)
        {
            paste_clipboard();
        }
    }


    // suppression de sélection
    if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete)
    {
        delete_selection();
    }

    return QFrame::keyPressEvent(event);
341
342
343
344
345
346
347
348
}

void GridView::keyReleaseEvent(QKeyEvent *event)
{
    (void)event;
    if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)
    {
        m_view->setDragMode(QGraphicsView::RubberBandDrag);
349
350
351
        // prevent item mouse event handling
        for (auto& item : m_scene->items())
            item->setAcceptedMouseButtons(Qt::AllButtons);
352
    }
353
354

    return QFrame::keyReleaseEvent(event);
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
}

detail::Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
    : QObject(view), _view(view)
{
    _view->viewport()->installEventFilter(this);
    _view->setMouseTracking(true);
    _zoom_factor_base = 1.0015;
}

void detail::Graphics_view_zoom::gentle_zoom(double factor) {
    _view->scale(factor, factor);
    _view->centerOn(target_scene_pos);
    QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
                                                               _view->viewport()->height() / 2.0);
    QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
    _view->centerOn(_view->mapToScene(viewport_center.toPoint()));
    emit zoomed();
}

void detail::Graphics_view_zoom::set_zoom_factor_base(double value) {
    _zoom_factor_base = value;
}

bool detail::Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
    if (event->type() == QEvent::MouseMove) {
        QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
        QPointF delta = target_viewport_pos - mouse_event->pos();
        if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
            target_viewport_pos = mouse_event->pos();
            target_scene_pos = _view->mapToScene(mouse_event->pos());
        }
    } else if (event->type() == QEvent::Wheel) {
        QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
389
390
391
392
393
394
        if (wheel_event->angleDelta().y() != 0) {
            double angle = wheel_event->angleDelta().y();
            double factor = qPow(_zoom_factor_base, angle);
            gentle_zoom(factor);
            return true;
        }
395
396
397
398
    }
    Q_UNUSED(object)
    return false;
}