Commit 78ea73a0 authored by Yann Boucher's avatar Yann Boucher
Browse files

Significant performance improvements with GridView

parent bef2a147
Pipeline #78788 passed with stages
in 17 seconds
......@@ -295,7 +295,7 @@ pattern recorded :</string>
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
<number>1000</number>
</property>
<property name="value">
<number>10</number>
......@@ -318,7 +318,7 @@ pattern recorded :</string>
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
<number>1000</number>
</property>
<property name="value">
<number>10</number>
......
......@@ -12,6 +12,7 @@ Cette classe représente le widget utilisé pour l'affichage et l'interaction av
#define GRIDVIEW_HPP
#include <map>
#include <set>
#include <QFrame>
#include <QGraphicsView>
......@@ -48,9 +49,10 @@ signals:
void zoomed();
};
}
class GridItem;
class GridView;
class GridGraphicsView;
class DragDropHandlerItem;
/**
* \class GridView
......@@ -93,7 +95,7 @@ public:
//! \brief Retourne la taille du côté en pixels d'une cellule au niveau de zoom actuel.
//! \returns Taille en pixel du côté d'une cellule.
unsigned cell_pixel_size() const;
unsigned cell_screen_size() const;
//! \brief Retourne la taille de la grille, dans un QSize.
//! \returns La taille de la Grid.
......@@ -127,16 +129,22 @@ signals:
void zoom_changed(unsigned cell_size);
private:
GridItem* item_at(Coord pos);
const GridItem* item_at(Coord pos) const;
void load_grid(const Grid& grid);
void set_cell_state(Coord pos, unsigned state);
void update_gridview();
void copy_selection();
void paste_clipboard();
void delete_selection();
Coord top_left_of_selection() const;
const QPixmap &grid_pixmap() const;
private slots:
void handle_rubberband(QRect, QPointF, QPointF);
protected:
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
......@@ -145,8 +153,15 @@ private:
GridGraphicsView* m_view;
QGraphicsScene* m_scene;
detail::Graphics_view_zoom* m_zoom;
std::vector<GridItem*> m_item_cache; // cache pour pouvoir accéder efficacement à un Item selon une coordonnée
History m_undo_history;
QImage m_grid_image;
QPixmap m_grid_pixmap;
Grid m_grid;
std::set<Coord> m_selected_cells;
std::vector<QGraphicsRectItem*> m_selection_rects;
bool m_handling_rubberband;
DragDropHandlerItem* m_drag_drop_handler;
unsigned m_width;
unsigned m_height;
unsigned m_pen;
......
......@@ -85,71 +85,20 @@ bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
}
class GridItem : public QGraphicsRectItem
class DragDropHandlerItem : public QGraphicsRectItem
{
public:
GridItem(GridView& in_gridview, unsigned state = 0, Coord pos = Coord{0, 0}, QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(0, 0, GridItem::width(), GridItem::height(), parent), m_grid_view(in_gridview)
DragDropHandlerItem(GridView& in_gridview, QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(0, 0, 1, 1, parent), m_grid_view(in_gridview)
{
setAcceptHoverEvents(true);
setToolTip(m_state_name);
setFlag(QGraphicsItem::ItemIsSelectable);
setAcceptDrops(true);
//setCacheMode(QGraphicsItem::DeviceCoordinateCache);
set_state(state);
set_grid_pos(pos);
setPen(QPen(brush(), 0));
}
static int width()
{ return 20; }
static int height()
{ return 20; }
unsigned state() const
{ return m_cell_state; }
void set_state(unsigned s, bool update_history = false)
{
if (update_history && m_cell_state != s)
m_grid_view.push_history();
const Alphabet& alph = m_grid_view.alphabet();
setBrush(QBrush(stateColor_to_QColor(alph.getState(s).getColor())));
setToolTip(QString::fromStdString(alph.getState(s).getStateLabel()));
m_cell_state = s;
update();
}
Coord grid_pos() const
{ return m_cell_pos; }
void set_grid_pos(Coord c)
{
setPos(QPointF(c.x*GridItem::width(), c.y*GridItem::height()));
m_cell_pos = c;
setPen(Qt::NoPen);
setBrush(Qt::NoBrush);
}
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
QRectF rect = QRectF(0, 0, GridItem::width(), GridItem::height());
painter->setPen(Qt::NoPen);
if (isSelected())
{
QBrush selection_brush = brush();
selection_brush.setColor(brush().color().darker(175));
painter->setBrush(selection_brush);
}
else
{
painter->setBrush(brush());
}
painter->drawRect(rect);
}
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
{
if (event->mimeData()->hasText())
......@@ -175,12 +124,11 @@ protected:
return;
}
m_grid_view.paste_structure_at(m_cell_pos, s);
Coord coord = Coord{(int)event->pos().x(), (int)event->pos().y()};
m_grid_view.paste_structure_at(coord, s);
}
private:
Coord m_cell_pos;
unsigned m_cell_state;
QString m_state_name;
GridView& m_grid_view;
};
......@@ -204,6 +152,7 @@ protected:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void drawForeground(QPainter * painter, const QRectF & rect);
void drawBackground(QPainter * painter, const QRectF & rect);
private:
void draw_bresenham(QPointF from, QPointF to);
......@@ -217,42 +166,47 @@ private:
void GridGraphicsView::mousePressEvent(QMouseEvent *event)
{
QPointF item_pos = mapToScene(event->pos());
Coord coord = Coord{(int)item_pos.x()/GridItem::width(), (int)item_pos.y()/GridItem::height()};
GridItem* item = m_gridview.item_at(coord);
if (!sceneRect().contains(item_pos))
return;
Coord coord = Coord{(int)item_pos.x(), (int)item_pos.y()};
m_last_mouse_pos = event->pos();
if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->button() == Qt::LeftButton)
{
if (item)
{
unsigned state = m_gridview.current_pen();
item->set_state(state, true);
}
m_gridview.set_cell_state(coord, state);
m_gridview.update_gridview();
}
else if (event->button() == Qt::RightButton && item)
else if (event->button() == Qt::RightButton)
{
if ((QGuiApplication::keyboardModifiers() & Qt::ControlModifier) == 0)
m_gridview.clear_selection();
QGraphicsView::mousePressEvent(event);
item->setSelected(true);
//item->setSelected(true);
}
else
QGraphicsView::mousePressEvent(event);
}
void GridGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
QPointF item_pos = mapToScene(event->pos());
Coord coord = Coord{(int)item_pos.x()/GridItem::width(), (int)item_pos.y()/GridItem::height()};
GridItem* item = m_gridview.item_at(coord);
if (!sceneRect().contains(item_pos) || !sceneRect().contains(mapToScene(m_last_mouse_pos)))
return;
if (QGuiApplication::keyboardModifiers() == Qt::NoModifier && event->buttons() == Qt::LeftButton)
{
draw_bresenham(mapToScene(m_last_mouse_pos), mapToScene(event->pos()));
draw_bresenham(mapToScene(m_last_mouse_pos), item_pos);
m_gridview.update_gridview();
}
else if (event->buttons() == Qt::RightButton && item)
else if (event->buttons() == Qt::RightButton)
{
QGraphicsView::mouseMoveEvent(event);
item->setSelected(true);
//item->setSelected(true);
}
else
QGraphicsView::mouseMoveEvent(event);
......@@ -260,11 +214,11 @@ void GridGraphicsView::mouseMoveEvent(QMouseEvent *event)
m_last_mouse_pos = event->pos();
}
void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &)
{
qreal lod = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform());
if (lod < 0.4)
if (lod < 10)
return; // trop zoomé, on ne dessine rien
painter->setPen(QPen(Qt::black, 0)); // cosmetic 1-pixel pen
......@@ -273,14 +227,14 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
// horizontal lines
for (int i = -1; i < m_gridview.grid_size().height(); ++i)
{
const unsigned y_offset = (i+1)*GridItem::height();
lines.append(QLine(QPoint(0, y_offset), QPoint(GridItem::width()*m_gridview.grid_size().width(), y_offset)));
const unsigned y_offset = (i+1);
lines.append(QLine(QPoint(0, y_offset), QPoint(m_gridview.grid_size().width(), y_offset)));
}
// vertical lines
for (int j = -1; j < m_gridview.grid_size().width(); ++j)
{
const unsigned x_offset = (j+1)*GridItem::width();
lines.append(QLine(QPoint(x_offset, 0), QPoint(x_offset, GridItem::height()*m_gridview.grid_size().height())));
const unsigned x_offset = (j+1);
lines.append(QLine(QPoint(x_offset, 0), QPoint(x_offset, m_gridview.grid_size().height())));
}
painter->drawLines(lines);
......@@ -288,13 +242,20 @@ void GridGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
painter->setPen(Qt::NoPen);
}
void GridGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawBackground(painter, rect);
painter->drawPixmap(0, 0, m_gridview.grid_pixmap());
}
// Algorithme de Bresenham pour tracer une ligne entre les deux points et dessiner sur ces items
void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
{
int x0 = from.x()/GridItem::width();
int y0 = from.y()/GridItem::height();
int x1 = to.x()/GridItem::width();
int y1 = to.y()/GridItem::height();
int x0 = from.x();
int y0 = from.y();
int x1 = to.x();
int y1 = to.y();
// cf. https://gist.github.com/bert/1085538
int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
......@@ -303,10 +264,8 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
for (;;){ /* loop */
//printf("Trying %d %d\n", x, y);
auto item = m_gridview.item_at(Coord{x0, y0});
if (item)
item->set_state(m_gridview.current_pen());
m_gridview.set_cell_state(Coord{x0, y0}, m_gridview.current_pen());
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
......@@ -315,11 +274,12 @@ void GridGraphicsView::draw_bresenham(QPointF from, QPointF to)
}
GridView::GridView(QWidget *parent)
: QFrame(parent), m_undo_history(10), m_width(10), m_height(10)
: QFrame(parent), m_undo_history(10), m_grid(10, 10), m_handling_rubberband(false), m_width(10), m_height(10)
{
//setMouseTracking(true);
setFrameStyle(Sunken | StyledPanel);
setAcceptDrops(true);
m_scene = new QGraphicsScene(this);
......@@ -333,13 +293,6 @@ GridView::GridView(QWidget *parent)
m_view->viewport()->setCursor(Qt::ArrowCursor);
m_view->setBackgroundBrush(QBrush(Qt::gray));
m_view->setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect); // provides better performance
/*
QOpenGLWidget *gl = new QOpenGLWidget();
QSurfaceFormat format;
format.setSamples(4);
gl->setFormat(format);
m_view->setViewport(gl);
*/
m_zoom = new detail::Graphics_view_zoom(m_view);
......@@ -355,6 +308,9 @@ GridView::GridView(QWidget *parent)
alph.newEtat(state{stateColor{0, 0, 255}, "Alive"});
set_alphabet(alph);
m_drag_drop_handler = new DragDropHandlerItem(*this);
m_scene->addItem(m_drag_drop_handler);
// Initialisation la première fois
m_height = 10;
m_width = 10;
......@@ -364,8 +320,9 @@ GridView::GridView(QWidget *parent)
connect(m_zoom, &detail::Graphics_view_zoom::zoomed, this, [this]
{
emit zoom_changed(cell_pixel_size());
emit zoom_changed(cell_screen_size());
});
connect(m_view, &QGraphicsView::rubberBandChanged, this, &GridView::handle_rubberband);
}
void GridView::set_alphabet(const Alphabet &alph)
......@@ -396,9 +353,9 @@ void GridView::set_clipboard(const Structure &s)
m_copy_paste_buffer = s;
}
unsigned GridView::cell_pixel_size() const
unsigned GridView::cell_screen_size() const
{
return m_view->transform().mapRect(QRectF(0,0,GridItem::width(), GridItem::height())).size().toSize().width();
return m_view->transform().mapRect(QRectF(0,0,1,1)).size().toSize().width();
}
QSize GridView::grid_size() const
......@@ -410,30 +367,23 @@ void GridView::copy_grid(const Grid &grid)
{
push_history();
load_grid(grid);
update_gridview();
}
Grid GridView::get_grid() const
{
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);
Coord pos;
pos.x = i;
pos.y = j;
grid.set_cell(pos, item->state());
}
}
return grid;
return m_grid;
}
void GridView::clear_selection()
{
m_scene->clearSelection();
for (auto rect : m_selection_rects)
{
m_scene->removeItem(rect);
}
m_selection_rects.clear();
m_selected_cells.clear();
m_handling_rubberband = false;
}
Structure GridView::selected_cells() const
......@@ -443,12 +393,9 @@ Structure GridView::selected_cells() const
std::vector<std::pair<Coord, unsigned>> cells;
// 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();
Q_FOREACH(const auto& item, m_scene->selectedItems())
for (auto coord : m_selected_cells)
{
GridItem* cell = static_cast<GridItem*>(item);
cells.push_back(std::make_pair(cell->grid_pos() - origin, cell->state()));
cells.push_back(std::make_pair(coord, m_grid.get_state(coord)));
}
return Structure(cells.begin(), cells.end());
}
......@@ -466,65 +413,37 @@ void GridView::undo()
}
}
GridItem *GridView::item_at(Coord pos)
{
if (pos.x < 0 || pos.y < 0 || pos.x >= (int)m_width || pos.y >= (int)m_height)
return nullptr;
return m_item_cache[pos.y*m_width + pos.x];
//QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
//return dynamic_cast<GridItem*>(gitem_ptr);
}
const GridItem *GridView::item_at(Coord pos) const
{
if (pos.x < 0 || pos.y < 0 || pos.x >= (int)m_width || pos.y >= (int)m_height)
return nullptr;
return m_item_cache[pos.y*m_width + pos.x];
//QGraphicsItem* gitem_ptr = m_scene->itemAt(QPointF(pos.x*GridItem::width(), pos.y*GridItem::height()), QTransform());
//return dynamic_cast<GridItem*>(gitem_ptr);
}
void GridView::load_grid(const Grid &grid)
{
m_height = grid.get_rows();
m_width = grid.get_col();
if (m_height*m_width != m_item_cache.size())
{
m_scene->clear();
m_item_cache.resize(m_width*m_height);
// Populate scene;
for (unsigned i = 0; i < m_width; ++i)
m_grid = grid;
m_grid_image = QImage(m_width, m_height, QImage::Format_ARGB32);
for (int i = 0; i < (int)m_width; ++i)
{
for (unsigned j = 0; j < m_height; ++j)
for (int j = 0; j < (int)m_height; ++j)
{
Coord pos = {static_cast<int>(i), static_cast<int>(j)};
unsigned state = grid.get_state(pos);
GridItem *item = new GridItem(*this, state);
item->set_grid_pos(pos);
m_item_cache[pos.y*m_width + pos.x] = item;
m_scene->addItem(item);
QColor color = stateColor_to_QColor(m_alph.getState(grid.get_state(Coord{i, j})).getColor());
m_grid_image.setPixelColor(i, j, color);
}
}
m_grid_pixmap.convertFromImage(m_grid_image);
m_scene->setSceneRect(QRectF(0, 0, m_width, m_height));
m_drag_drop_handler->setRect(QRectF(0, 0, m_width, m_height));
}
// On met à jour la taille de la scène
QRectF rect = m_scene->itemsBoundingRect();
m_scene->setSceneRect(rect);
}
// Pas la peine de recharger la grille si la taille est la même
else
{
for (unsigned i = 0; i < m_width; ++i)
{
for (unsigned j = 0; j < m_height; ++j)
{
Coord pos = {static_cast<int>(i), static_cast<int>(j)};
unsigned state = grid.get_state(pos);
void GridView::set_cell_state(Coord pos, unsigned state)
{
QColor color = stateColor_to_QColor(m_alph.getState(state).getColor());
m_grid_image.setPixelColor(pos.x, pos.y, color);
m_grid.set_cell(pos, state);
}
GridItem *item = item_at(pos);
item->set_state(state);
}
}
}
void GridView::update_gridview()
{
m_grid_pixmap.convertFromImage(m_grid_image);
m_scene->update();
}
void GridView::copy_selection()
......@@ -549,11 +468,11 @@ void GridView::fill_selection(unsigned state)
{
push_history();
for (auto& abstract_item : m_scene->selectedItems())
for (auto coord : m_selected_cells)
{
GridItem* item = static_cast<GridItem*>(abstract_item);
item->set_state(state);
set_cell_state(coord, state);
}
update_gridview();
}
void GridView::delete_selection()
......@@ -570,37 +489,75 @@ void GridView::paste_structure_at(Coord origin, const Structure &s)
{
Coord corrected = wrap_coords(cell.first + origin, m_width, m_height);
GridItem* item = item_at(corrected);
assert(item != nullptr);
item->set_state(cell.second);
set_cell_state(corrected, cell.second);
}
update_gridview();
}
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()};
Q_FOREACH(const auto& item, m_scene->selectedItems())
for (const auto& rect : m_selection_rects)
{
GridItem* cell = static_cast<GridItem*>(item);
if (cell->grid_pos().x < selection_top_left.x)
selection_top_left.x = cell->grid_pos().x;
if (cell->grid_pos().y < selection_top_left.y)
selection_top_left.y = cell->grid_pos().y;
if (rect->pos().x() < selection_top_left.x)
selection_top_left.x = rect->pos().x();
if (rect->pos().y() < selection_top_left.y)
selection_top_left.y = rect->pos().y();
}
return selection_top_left;
}
const QPixmap &GridView::grid_pixmap() const
{
return m_grid_pixmap;
}
void GridView::handle_rubberband(QRect rubberBandRect, QPointF fromScenePoint, QPointF toScenePoint)
{
QGraphicsRectItem* item;
if (!m_handling_rubberband)
{
m_handling_rubberband = true;
item = new QGraphicsRectItem();
item->setPen(Qt::NoPen);
item->setBrush(QBrush(QColor(220, 220, 220, 200)));
m_selection_rects.push_back(item);
m_scene->addItem(item);
}
else
{
assert(!m_selection_rects.empty());
item = m_selection_rects.back();
}
// end of event
if (rubberBandRect.isNull())
{
m_handling_rubberband = false;
// Add the cells to the selection
QRect rect = m_selection_rects.back()->rect().toRect();
for (int i = rect.top(); i <= rect.bottom(); ++i)
for (int j = rect.left(); j <= rect.right(); ++j)
{
m_selected_cells.insert(Coord{j, i});
}
return;
}
// assign the integer coordinates
item->setRect(QRectF(fromScenePoint.toPoint(), toScenePoint.toPoint()).normalized());
}
void GridView::keyPressEvent(QKeyEvent *event)
{
if (event->modifiers