gridview.cpp 19.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
\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>
21
#include <QMimeData>
22
#include <QStyleOptionGraphicsItem>
23
24
25

#include <QDebug>

26
#include <vector>
27
#include <cstdlib>
28
#include <cassert>
29

30
31
#include <qmath.h>

32
33
#include "structurereader.hpp"

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
namespace 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 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 Graphics_view_zoom::set_zoom_factor_base(double value) {
    _zoom_factor_base = value;
}

bool 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);
        if (wheel_event->angleDelta().y() != 0) {
            double angle = wheel_event->angleDelta().y();
            double factor = qPow(_zoom_factor_base, angle);
            gentle_zoom(factor);
            return true;
        }
    }
    Q_UNUSED(object)
    return false;
}

}

81
class DragDropHandlerItem : public QGraphicsRectItem
82
83
{
public:
84
85
    DragDropHandlerItem(GridView& in_gridview, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(0, 0, 1, 1, parent), m_grid_view(in_gridview)
86
    {
87
88
        setAcceptDrops(true);

89
90
        setPen(Qt::NoPen);
        setBrush(Qt::NoBrush);
91
92
    }

93
protected:
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
    {
        if (event->mimeData()->hasText())
        {
            event->acceptProposedAction();
            update();
        }
    }

    void dropEvent(QGraphicsSceneDragDropEvent *event) override
    {
        event->acceptProposedAction();

        QString file_path = event->mimeData()->text();
        Structure s;
        try
        {
            s = load_structure(file_path.toStdString());
        }
        catch (const StructureReaderException& e)
        {
            //qDebug() << "StructureReaderException : " << e.what() << "\n";
            return;
        }

119
120
        Coord coord = Coord{(int)event->pos().x(), (int)event->pos().y()};
        m_grid_view.paste_structure_at(coord, s);
121
122
    }

123
124
private:
    QString m_state_name;
125
    GridView& m_grid_view;
126
127
};

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
 * \class GridGraphicsView
 *
 * Classe héritée de GraphicsView permettant les manipulations à la souris et de drag-and-drop.
 * Détail d'implémentation.
 */
class GridGraphicsView : public QGraphicsView
{
public:
    GridGraphicsView(GridView& gridview, QWidget* parent)
        : QGraphicsView(parent), m_gridview(gridview)
    {
        setAcceptDrops(true);
    }

protected:
    void mousePressEvent(QMouseEvent* event);
    void mouseMoveEvent(QMouseEvent* event);
146
    void drawForeground(QPainter * painter, const QRectF & rect);
147
    void drawBackground(QPainter * painter, const QRectF & rect);
148

149
150
151
private:
    void draw_bresenham(QPointF from, QPointF to);

152
153
private:
    GridView& m_gridview;
154
    QPoint m_last_mouse_pos;
155
156
157
};


158
159
void GridGraphicsView::mousePressEvent(QMouseEvent *event)
{
160
    QPointF item_pos = mapToScene(event->pos());
161
162
163
164
    if (!sceneRect().contains(item_pos))
        return;

    Coord coord = Coord{(int)item_pos.x(), (int)item_pos.y()};
165

166
167
    m_last_mouse_pos = event->pos();

168
    if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->button() == Qt::LeftButton)
169
    {
170
        m_gridview.push_history();
171
        m_gridview.click_on(coord);
172
        m_gridview.update_gridview();
173
    }
174
    else if (event->button() == Qt::RightButton)
175
    {
176
177
        if ((QGuiApplication::keyboardModifiers() & Qt::ControlModifier) == 0)
            m_gridview.clear_selection();
178
179
180
181
        QGraphicsView::mousePressEvent(event);
    }
    else
        QGraphicsView::mousePressEvent(event);
182
183

    m_gridview.update_current_mouse_pos(coord);
184
185
}

186
187
void GridGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
188
    QPointF item_pos = mapToScene(event->pos());
189
190
    if (!sceneRect().contains(item_pos) || !sceneRect().contains(mapToScene(m_last_mouse_pos)))
        return;
191

192
193
    Coord coord = Coord{(int)item_pos.x(), (int)item_pos.y()};

194
    if (!m_gridview.in_toggle_mode() && QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->buttons() == Qt::LeftButton)
195
    {
196
197
        draw_bresenham(mapToScene(m_last_mouse_pos), item_pos);
        m_gridview.update_gridview();
198
    }
199
    else if (event->buttons() == Qt::RightButton)
200
201
    {
        QGraphicsView::mouseMoveEvent(event);
202
        //item->setSelected(true);
203
204
205
    }
    else
        QGraphicsView::mouseMoveEvent(event);
206
207

    m_last_mouse_pos = event->pos();
208
    m_gridview.update_current_mouse_pos(coord);
209
210
}

211
void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &)
212
213
214
{
    qreal lod = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform());

215
    if (lod < 10)
216
217
        return; // trop zoomé, on ne dessine rien

Yann Boucher's avatar
Yann Boucher committed
218
    painter->setPen(QPen(Qt::gray, 0)); // cosmetic 1-pixel pen
219
220
221
222
223

    QVector<QLine> lines;
    // horizontal lines
    for (int i = -1; i < m_gridview.grid_size().height(); ++i)
    {
224
225
        const unsigned y_offset = (i+1);
        lines.append(QLine(QPoint(0, y_offset), QPoint(m_gridview.grid_size().width(), y_offset)));
226
227
228
229
    }
    // vertical lines
    for (int j = -1; j < m_gridview.grid_size().width(); ++j)
    {
230
231
        const unsigned x_offset = (j+1);
        lines.append(QLine(QPoint(x_offset, 0), QPoint(x_offset, m_gridview.grid_size().height())));
232
233
234
235
236
237
238
    }

    painter->drawLines(lines);

    painter->setPen(Qt::NoPen);
}

239
240
241
242
void GridGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
    QGraphicsView::drawBackground(painter, rect);

243
244
    //painter->drawPixmap(0, 0, m_gridview.grid_image());
    painter->drawImage(0, 0, m_gridview.grid_image());
245
246
}

247
248
249
// Algorithme de Bresenham pour tracer une ligne entre les deux points et dessiner sur ces items
void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
{
250
251
252
253
    int x0 = from.x();
    int y0 = from.y();
    int x1 = to.x();
    int y1 = to.y();
254
255
256
257
258
259
260
261

    // cf. https://gist.github.com/bert/1085538
    int dx =  abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2; /* error value e_xy */

    for (;;){  /* loop */

262
263
        m_gridview.set_cell_state(Coord{x0, y0}, m_gridview.current_pen());

264
265
266
267
268
        if (x0 == x1 && y0 == y1) break;
        e2 = 2 * err;
        if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
        if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
    }
269
270
}

271
GridView::GridView(QWidget *parent)
272
    : QFrame(parent), m_undo_history(10),  m_grid(10, 10), m_in_toggle_mode(false), m_handling_rubberband(false), m_width(10), m_height(10)
273
{
274
    //setMouseTracking(true);
275

276
    setFrameStyle(Sunken | StyledPanel);
277
    setAcceptDrops(true);
278
279
280

    m_scene = new QGraphicsScene(this);

281
    m_view = new GridGraphicsView(*this, this);
282
    m_view->setRenderHint(QPainter::Antialiasing, false);
283
    m_view->setDragMode(QGraphicsView::RubberBandDrag);
Yann Boucher's avatar
Yann Boucher committed
284
    m_view->setOptimizationFlags(QGraphicsView::DontSavePainterState|QGraphicsView::DontAdjustForAntialiasing);
285
286
287
288
    m_view->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
    m_view->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    m_view->setScene(m_scene);
    m_view->viewport()->setCursor(Qt::ArrowCursor);
Yann Boucher's avatar
Yann Boucher committed
289
    m_view->setBackgroundBrush(QBrush(Qt::gray));
290
    m_view->setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect); // provides better performance
291
    m_view->scale(20, 20); // Begin with 20x20 pixels cells
292

293
    m_zoom = new detail::Graphics_view_zoom(m_view);
294
295
296

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(m_view);
Yann Boucher's avatar
Yann Boucher committed
297
298
299
    m_info_section = new QWidget(this);

    QVBoxLayout* info_layout = new QVBoxLayout;
300
    m_mouse_pos_label = new QLabel("");
Yann Boucher's avatar
Yann Boucher committed
301
302
303
    info_layout->addWidget(m_mouse_pos_label);
    info_layout->addWidget(new QLabel("Left click : edit; Right click : select; F11 for fullscreen mode", this));
    info_layout->addWidget(new QLabel("Hold SHIFT to move across the grid; Scroll wheel or CTRL+/CTRL- to zoom", this));
Eugene Pin's avatar
Eugene Pin committed
304
    info_layout->setContentsMargins(0,0,0,0);
Yann Boucher's avatar
Yann Boucher committed
305
306
307
    m_info_section->setLayout(info_layout);

    layout->addWidget(m_info_section);
308
309
    setLayout(layout);

310
311
312
    m_drag_drop_handler = new DragDropHandlerItem(*this);
    m_scene->addItem(m_drag_drop_handler);

313
314
315
    // Initialisation la première fois
    m_height = 10;
    m_width = 10;
316

317
    Grid default_grid(m_height, m_width);
318
    load_grid(default_grid);
319

Yann Boucher's avatar
Yann Boucher committed
320
321
322
323
324
    set_current_pen(0);
    // Test Alphabet
    Alphabet alph(state{stateColor{255, 255, 255}, "Dead"});
    alph.newEtat(state{stateColor{0, 0, 255}, "Alive"});
    set_alphabet(alph);
325
    update_current_mouse_pos({0, 0});
Yann Boucher's avatar
Yann Boucher committed
326

327
328
    connect(m_zoom, &detail::Graphics_view_zoom::zoomed, this, [this]
            {
329
                emit zoom_changed(cell_screen_size());
330
            });
331
    connect(m_view, &QGraphicsView::rubberBandChanged, this, &GridView::handle_rubberband);
332
333
}

334
335
void GridView::set_alphabet(const Alphabet &alph)
{
336
337
338
    while (!m_undo_history.isEmpty())
        m_undo_history.popGrid();

339
    m_alph = alph;
340
341
    // Default to using the first non-null state as the current pen
    set_current_pen(1);
Yann Boucher's avatar
Yann Boucher committed
342
343
344
    // Reload the current grid to update the colors
    load_grid(get_grid());
    update_gridview();
345
346
}

347
348
349
350
351
352
353
const Alphabet &GridView::alphabet() const
{
    return m_alph;
}

void GridView::set_current_pen(unsigned state)
{
354
    state %= m_alph.taille();
355
356
357
358
359
360
361
362
    m_pen = state;
}

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

363
364
365
void GridView::enable_toggle_mode(unsigned checked_state, unsigned unchecked_state)
{
    m_in_toggle_mode = true;
366
367
368
369
370
371
    add_toggle_rule(checked_state, unchecked_state);
}

void GridView::add_toggle_rule(unsigned checked_state, unsigned unchecked_state)
{
    m_toggle_states.push_back({checked_state, unchecked_state});
372
373
374
375
376
377
378
}

void GridView::disable_toggle_mode()
{
    m_in_toggle_mode = false;
}

379
380
381
382
383
void GridView::set_clipboard(const Structure &s)
{
    m_copy_paste_buffer = s;
}

384
unsigned GridView::cell_screen_size() const
385
{
386
    return m_view->transform().mapRect(QRectF(0,0,1,1)).size().toSize().width();
387
}
388

389
390
391
QSize GridView::grid_size() const
{
    return QSize(m_width, m_height);
392
393
}

394
395
void GridView::copy_grid(const Grid &grid)
{
396
397
    push_history();
    load_grid(grid);
398
    update_gridview();
399
400
}

401
const Grid& GridView::get_grid() const
402
{
403
    return m_grid;
404
405
406
407
}

void GridView::clear_selection()
{
408
409
410
411
412
413
    for (auto rect : m_selection_rects)
    {
        m_scene->removeItem(rect);
    }
    m_selection_rects.clear();
    m_handling_rubberband = false;
414
415
416
417
418
}

Structure GridView::selected_cells() const
{
    std::vector<std::pair<Coord, unsigned>> cells;
419

420
    for (const auto& item : m_selection_rects)
421
    {
422
423
424
425
426
427
        const auto& rect = item->rect().toRect();
        for (int i = rect.top(); i <= rect.bottom(); ++i)
            for (int j = rect.left(); j <= rect.right(); ++j)
            {
                cells.push_back({Coord{j, i}, m_grid.get_state(Coord{j, i})});
            }
428
429
430
431
    }
    return Structure(cells.begin(), cells.end());
}

432
433
434
435
436
437
438
439
440
441
442
void GridView::push_history()
{
    m_undo_history.pushGrid(get_grid());
}

void GridView::undo()
{
    if (!m_undo_history.isEmpty())
    {
        load_grid(m_undo_history.popGrid());
    }
443
    update_gridview();
444
445
446
447
448
449
}

void GridView::load_grid(const Grid &grid)
{
    m_height = grid.get_rows();
    m_width = grid.get_col();
450
    m_grid = grid;
451

452
453
    m_image_data.resize(m_width*m_height);
    for (int i = 0; i < (int)m_height; ++i)
454
    {
455
        for (int j = 0; j < (int)m_width; ++j)
456
        {
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
            unsigned state_id = grid.get_state(Coord{j, i});
            if (state_id < m_alph.taille())
            {
                const auto& state_color = m_alph.getState(state_id).getColor();
                m_image_data[i * m_width + j] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
                                                | ((uint32_t)state_color.getGreen() << 8)
                                                | ((uint32_t)state_color.getBlue() << 0);
            }
            else
            {
                const auto& state_color = m_alph.getState(state_id % m_alph.taille()).getColor();
                m_image_data[i * m_width + j] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
                                                | ((uint32_t)state_color.getGreen() << 8)
                                                | ((uint32_t)state_color.getBlue() << 0);
                m_grid.set_cell(Coord{j, i}, state_id % m_alph.taille());
            }
473
474
        }
    }
475
476

    m_grid_image = QImage((const uchar*)m_image_data.data(), m_width, m_height, QImage::Format_ARGB32);
477
478
    m_scene->setSceneRect(QRectF(0, 0, m_width, m_height));
    m_drag_drop_handler->setRect(QRectF(0, 0, m_width, m_height));
479
    update_current_mouse_pos(m_last_mouse_pos);
480
481
482
483
}

void GridView::set_cell_state(Coord pos, unsigned state)
{
484
    state %= m_alph.taille();
485
486
487
488
    auto state_color = m_alph.getState(state).getColor();
    m_image_data[pos.y * m_width + pos.x] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
                                            | ((uint32_t)state_color.getGreen() << 8)
                                            | ((uint32_t)state_color.getBlue() << 0);
489
490
491
492
493
    m_grid.set_cell(pos, state);
}

void GridView::update_gridview()
{
494
    m_grid_image = QImage((const uchar*)m_image_data.data(), m_width, m_height, QImage::Format_ARGB32);
495
    m_scene->update();
496
497
}

498
499
500
501
502
503
504
505
506
507
508
509
QGraphicsRectItem *GridView::create_selection_rect(const QRectF &rect)
{
    QGraphicsRectItem* item = new QGraphicsRectItem();
    item->setPen(Qt::NoPen);
    item->setBrush(QBrush(QColor(220, 220, 220, 200)));
    item->setRect(rect);
    m_selection_rects.push_back(item);
    m_scene->addItem(item);

    return item;
}

510
511
512
513
514
515
516
void GridView::copy_selection()
{
    m_copy_paste_buffer = selected_cells();
}

void GridView::paste_clipboard()
{
517
518
    push_history();

519
520
521
522
523
    auto scene_pos = m_view->mapToScene(m_view->mapFromGlobal(QCursor::pos()));
    if (!m_scene->sceneRect().contains(scene_pos))
        return;

    Coord origin = {scene_pos.toPoint().x(), scene_pos.toPoint().y()};
524

525
    paste_structure_at(origin, m_copy_paste_buffer);
526
527
}

528
void GridView::fill_selection(unsigned state)
529
{
530
531
    push_history();

532
    for (const auto& item : m_selection_rects)
533
    {
534
535
536
537
538
539
        const auto& rect = item->rect().toRect();
        for (int i = rect.top(); i <= rect.bottom(); ++i)
            for (int j = rect.left(); j <= rect.right(); ++j)
            {
                set_cell_state(Coord{(int)j, (int)i}, state);
            }
540
    }
541

542
    update_gridview();
543
544
545
546
547
}

void GridView::delete_selection()
{
    fill_selection(0);
548
549
550
    clear_selection();
}

551
552
553
554
555
556
void GridView::select_all()
{
    clear_selection();
    create_selection_rect(QRect(0, 0, m_width, m_height));
}

557
558
559
560
561
562
563
564
565
void GridView::click_on(Coord coord)
{
    if (!in_toggle_mode())
    {
        set_cell_state(coord, current_pen());
    }
    else
    {
        unsigned prev_state = get_grid().get_state(coord);
566
567
568
569
570
571
572
573
574
575
576
577
578
        for (auto toggle_pair : m_toggle_states)
        {
            if (prev_state == toggle_pair.first)
            {
                set_cell_state(coord, toggle_pair.second);
                break;
            }
            else if (prev_state == toggle_pair.second)
            {
                set_cell_state(coord, toggle_pair.first);
                break;
            }
        }
579
580
581
    }
}

582
583
584
585
586
587
588
589
void GridView::update_current_mouse_pos(Coord coord)
{
    m_last_mouse_pos = coord;
    auto state_label = m_alph.getState(m_grid.get_state(coord)).getStateLabel();
    m_mouse_pos_label->setText(QString("Mouse position : %1,%2 : \"%3\"").arg(coord.x).arg(coord.y).arg(
        QString::fromStdString(state_label)));
}

590
591
void GridView::paste_structure_at(Coord origin, const Structure &s)
{
592
593
    push_history();

594
595
596
597
    for (const auto& cell : s)
    {
        Coord corrected = wrap_coords(cell.first + origin, m_width, m_height);

598
        set_cell_state(corrected, cell.second);
599
    }
600
    update_gridview();
601
602
}

603
const QImage &GridView::grid_image() const
604
{
605
    return m_grid_image;
606
607
}

Yann Boucher's avatar
Yann Boucher committed
608
609
610
611
612
613
614
615
616
void GridView::enter_fullscreen()
{
    m_info_section->hide();
    layout()->removeWidget(this);
    setParent(nullptr);
    raise();
    showFullScreen();
}

617
618
619
620
621
622
void GridView::handle_rubberband(QRect rubberBandRect, QPointF fromScenePoint, QPointF toScenePoint)
{
    QGraphicsRectItem* item;
    if (!m_handling_rubberband)
    {
        m_handling_rubberband = true;
623
        item = create_selection_rect();
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
    }
    else
    {
        assert(!m_selection_rects.empty());
        item = m_selection_rects.back();
    }

    // end of event
    if (rubberBandRect.isNull())
    {
        m_handling_rubberband = false;

        return;
    }

    // assign the integer coordinates
    item->setRect(QRectF(fromScenePoint.toPoint(), toScenePoint.toPoint()).normalized());
}

643

644
645
646
647
648
649
void GridView::keyPressEvent(QKeyEvent *event)
{
    if (event->modifiers() & Qt::ShiftModifier)
    {
        m_view->setDragMode(QGraphicsView::ScrollHandDrag);
    }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666

    // 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();
        }
667
668
669
670
671
672
        else if (event->key() == Qt::Key_Z)
        {
            undo();
        }
        else if (event->key() == Qt::Key_A)
        {
673
            select_all();
674
        }
675
676
677
678
679
680
681
682
        else if (event->key() == Qt::Key_Plus)
        {
            m_zoom->gentle_zoom(1.25);
        }
        else if (event->key() == Qt::Key_Minus)
        {
            m_zoom->gentle_zoom(0.75);
        }
683
684
685
686
687
688
689
690
691
    }


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

Yann Boucher's avatar
Yann Boucher committed
692
693
694
695
696
697
698
699
700
701
702
703
704
705
    // Exit fullscreen ?
    if (parent() == nullptr)
    {
        if (event->key() == Qt::Key_F11 || event->key() == Qt::Key_Escape)
        {
            event->accept();
            m_info_section->show();
            emit exit_fullscreen();
        }
    }
    else
    {
        return QFrame::keyPressEvent(event);
    }
706
707
708
709
710
711
712
713
714
}

void GridView::keyReleaseEvent(QKeyEvent *event)
{
    (void)event;
    if (QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)
    {
        m_view->setDragMode(QGraphicsView::RubberBandDrag);
    }
715
716

    return QFrame::keyReleaseEvent(event);
717
718
}