Search code examples
qtqgraphicsview

Snapping to grid doesn't work


I am trying to do snapping on grid such that whatever I will draw it should take only gridpoints and no other points. I have made grid in cadgraphicsscene.cpp and made different class for snapping. My grid is made as follows:

cadgraphicscene.cpp

void CadGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
    const int gridSize = 50;
    const int realLeft = static_cast<int>(std::floor(rect.left()));
    const int realRight = static_cast<int>(std::ceil(rect.right()));
    const int realTop = static_cast<int>(std::floor(rect.top()));
    const int realBottom = static_cast<int>(std::ceil(rect.bottom()));

    // Draw grid.
    const int firstLeftGridLine = realLeft - (realLeft % gridSize);
    const int firstTopGridLine = realTop - (realTop % gridSize);
    QVarLengthArray<QLine, 100> lines;

    for (qreal x = firstLeftGridLine; x <= realRight; x += gridSize)
        lines.append(QLine(x, realTop, x, realBottom));
    for (qreal y = firstTopGridLine; y <= realBottom; y += gridSize)
        lines.append(QLine(realLeft, y, realRight, y));

    painter->setPen(QPen(QColor(220, 220, 220), 0.0));
    painter->drawLines(lines.data(), lines.size());

    // Draw axes.
    painter->setPen(QPen(Qt::lightGray, 0.0));
    painter->drawLine(0, realTop, 0, realBottom);
    painter->drawLine(realLeft, 0, realRight, 0);
}

My snap class looks as follows:

snap.cpp

#include "snap.h"
#include <QApplication>

    Snap::Snap(const QRect& rect, QGraphicsItem* parent,
               QGraphicsScene* scene):
    QGraphicsRectItem(QRectF())
    {
        setFlags(QGraphicsItem::ItemIsSelectable |
                QGraphicsItem::ItemIsMovable |
                QGraphicsItem::ItemSendsGeometryChanges);
    }

    void Snap::mousePressEvent(QGraphicsSceneMouseEvent *event){
        offset = pos() - computeTopLeftGridPoint(pos());
        QGraphicsRectItem::mousePressEvent(event);
    }

    QVariant Snap::itemChange(GraphicsItemChange change,
    const QVariant &value)
    {
        if (change == ItemPositionChange && scene()) {
            QPointF newPos = value.toPointF();
            if(QApplication::mouseButtons() == Qt::LeftButton &&
                qobject_cast<CadGraphicsScene*> (scene())){
                    QPointF closestPoint = computeTopLeftGridPoint(newPos);
                    return closestPoint+=offset;
                }
            else
                return newPos;
        }
        else
            return QGraphicsItem::itemChange(change, value);
    }

    QPointF Snap::computeTopLeftGridPoint(const QPointF& pointP){
       CadGraphicsScene* customScene = qobject_cast<CadGraphicsScene*> (scene());
        int gridSize = customScene->getGridSize();
        qreal xV = floor(pointP.x()/gridSize)*gridSize;
        qreal yV = floor(pointP.y()/gridSize)*gridSize;
        return QPointF(xV, yV);
    }

snap.h

#ifndef SNAP_H
#define SNAP_H

#include <QGraphicsRectItem>
#include "cadgraphicsscene.h"

class Snap : public QGraphicsRectItem
{
public:
    Snap(const QRect& rect, QGraphicsItem* parent,
         QGraphicsScene* scene);
protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    QVariant itemChange(GraphicsItemChange change,
    const QVariant &value);
private:
    QPointF offset;
    QPointF computeTopLeftGridPoint(const QPointF &pointP);
};

#endif // SNAP_H

But nothing happened, no snapping is done. Can you please help me in the above?


Solution

  • One of the problems is that you pass QRect() to the QGraphicsRectItem constructor in the initialization list of Snap class. This means it will have 0 width and height. Instead pass the same QRect object that you pass to your snap constructor:

    Snap::Snap(const QRect& rect, QGraphicsItem* parent, QGraphicsScene* scene) :
    QGraphicsRectItem(rect)
    

    You also don't seem to use the parent and the scene arguments so you might as well leave them out:

    Snap::Snap(const QRect& rect) :
        QGraphicsRectItem(rect)
    

    Or if you plan to use the parent for something then you can set a default value to 0 in the declaration:

    Snap(const QRect& rect, QGraphicsItem* parent = 0);
    

    Then pass them both to the base class constructor:

    Snap::Snap(const QRect& rect, QGraphicsItem* parent) :
    QGraphicsRectItem(rect, parent)
    

    snap.h

    #ifndef SNAP_H
    #define SNAP_H
    
    #include <QGraphicsRectItem>
    
    class Snap : public QGraphicsRectItem
    {
    public:
        Snap(const QRect &rect, QGraphicsItem *parent = 0);
    protected:
        void mousePressEvent(QGraphicsSceneMouseEvent *event);
        QVariant itemChange(GraphicsItemChange change,
        const QVariant &value);
    private:
        QPointF offset;
        QPointF computeTopLeftGridPoint(const QPointF &pointP);
    };
    
    #endif // SNAP_H
    

    snap.cpp

    #include "snap.h"
    #include <QDebug>
    #include <qmath.h>
    
    Snap::Snap(const QRect& rect, QGraphicsItem* parent) :
        QGraphicsRectItem(rect, parent)
    {
        setFlags(QGraphicsItem::ItemIsSelectable |
                 QGraphicsItem::ItemIsMovable |
                 QGraphicsItem::ItemSendsGeometryChanges);
    }
    
    void Snap::mousePressEvent(QGraphicsSceneMouseEvent *event){
        offset = pos() - computeTopLeftGridPoint(pos());
        QGraphicsRectItem::mousePressEvent(event);
    }
    
    QVariant Snap::itemChange(GraphicsItemChange change,
                              const QVariant &value)
    {
        qDebug()<<"inside itemChange";
        if (change == ItemPositionChange && scene())
        {
            QPointF newPos = value.toPointF();
            QPointF closestPoint = computeTopLeftGridPoint(newPos);
            return closestPoint+=offset;
        }
        else
            return QGraphicsItem::itemChange(change, value);
    }
    
    QPointF Snap::computeTopLeftGridPoint(const QPointF& pointP){
        int gridSize = 100;
        qreal xV = qFloor(pointP.x()/gridSize)*gridSize;
        qreal yV = qFloor(pointP.y()/gridSize)*gridSize;
        return QPointF(xV, yV);
    }
    

    mainwindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QGraphicsView>
    #include <QLayout>
    #include "snap.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        centralWidget()->setLayout(new QVBoxLayout);
        QGraphicsView *view = new QGraphicsView(this);
        centralWidget()->layout()->addWidget(view);
        Snap *snap = new Snap(QRect(0,0,100,100));
        view->setScene(new QGraphicsScene);
        view->scene()->addItem(snap);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }