3

This is what i mean that happens, first tried a circle, then on the right a squarei want to draw different arbitrary figures. Drawing starts when the mouse is clicked in the graphicsview and ends when stop clicking the mouse. However, when starting at a different point in the graphics view to make a new drawing, or to continue on the previous drawing, a line is drawn from the last mouse coordinate of the first drawing, to the first coordinate of the second drawing. The drawings do not necessarily need to be different drawings, but can also just be adjustments to the drawing. This is my code.

#include "mousedraw.h"
#include <QDebug>

MouseDraw::MouseDraw()
{
    setFlag(ItemIsMovable);
}

QRectF MouseDraw::boundingRect() const
{
    return QRectF(0,0,300,300);
}

void MouseDraw::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,      QWidget *widget)
{
    QPainterPath path;
    path.addPolygon(polyPoints2);
    painter->setPen(QPen(QColor(qrand() % 256, qrand() % 256, qrand() % 256),3));
    painter->drawPath(path);
}

void MouseDraw::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF point = event->pos();
    if (boundingRect().contains(point)) {
         array1 = point.x();
        array2 = point.y();
        polyPoints2 << QPoint(array1,array2);
        update();
    }
}
László Papp
  • 51,870
  • 39
  • 111
  • 135
  • 1
    You must redefine `QGraphicsScene` or `QGraphicsView` mouse events, not parent widget`s ones – dvvrd Dec 17 '13 at 11:21
  • 1
    Also, `setMouseTracking(true)` should be called once in the constructor. And it's needed only if you want to track mouse movements without pressing mouse buttons. – Pavel Strakhov Dec 17 '13 at 11:27
  • No i dont want the connection, the connection is my problem, but i dont know how to solve this.. all drawings need to be made in one move, the problem is when for example drawing a square,drawing first from left to right, then from right to top, and if you then release the mouse button and click it again in the left bottom, to draw a line up for example, a line is also drawn from right top to left bottom – user3110781 Dec 18 '13 at 16:22

2 Answers2

1

Having a custom MouseDraw class derived from QGraphicsItem is unnecessary. There is a QGraphicsPathItem that handles it all for you, correctly.

It is conceptually incorrect to process the mouse interaction within the item being created - your large bounding rectangle is a hack that's needed for it to work, but it's the wrong approach. You don't want the item to interact with the mouse. You want the scene to interact with the mouse and create an item on the fly. Only when editing an existing item you'd want to have mouse interaction, but even then it's better to create a separate editor item overlaid on the item being edited to handle the editing interaction.

Click on the window with right mouse button to get a context menu with the action to clear the picture. You can also toggle whether the subsequent path is joined to the previous one. This code is tested with both Qt 4.8.5 and 5.2.

When the scene is smaller than the view, the view insists on centering it (using the alignment property). An EmptyItem is used as a workaround to this "feature", to constrain the location of the scene within the view when the scene is smaller than the view (including when the scene is "empty").

The code creates a separate item for each disjoint path - QGraphicsScene generally will perform the best with multiple non-overlapping items. You could of course retain just one item and keep adding new segments to it on each mouse press, but that will, eventually, have performance implications - especially if you zoom into the scene, where you'd expect the performance to get better as less is shown.

screenshot

// https://github.com/KubaO/stackoverflown/tree/master/questions/gscene-paint-20632209
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif

class EmptyItem : public QGraphicsItem
{
public:
    EmptyItem(QGraphicsItem * parent = nullptr) : QGraphicsItem{parent} {}
    QRectF boundingRect() const override { return {0, 0, 1, 1}; }
    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
};

class Scene : public QGraphicsScene
{
    Q_OBJECT
    Q_PROPERTY(bool joinFigures READ joinFigures WRITE setJoinFigures)
    bool m_joinFigures = false;
    QGraphicsPathItem * m_item = nullptr;
    QPainterPath m_path;

    void newItem() {
        addItem(m_item = new QGraphicsPathItem);
        m_item->setPen(QPen{{qrand() % 256, qrand() % 256, qrand() % 256}});
        m_path = QPainterPath{}; // using std::swap; swap(m_path, QPainterPath());
    }
    void newPoint(const QPointF& pt) {
        if (! m_item) {
            newItem();
            m_path.moveTo(pt);
        } else {
            m_path.lineTo(pt);
            m_item->setPath(m_path);
        }
    }
    void mousePressEvent(QGraphicsSceneMouseEvent * ev) override {
        if (ev->buttons() != Qt::LeftButton) return;
        if (! m_joinFigures) m_item = nullptr;
        newPoint(ev->scenePos());
    }
    void mouseMoveEvent(QGraphicsSceneMouseEvent *ev) override {
        if (ev->buttons() != Qt::LeftButton) return;
        newPoint(ev->scenePos());
    }
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
        if (! m_path.isEmpty()) return;
        delete m_item; // Empty items are useless
        m_item = nullptr;
    }
public:
    Scene(QObject *parent = nullptr) : QGraphicsScene{parent}
    {
        addItem(new EmptyItem{});
    }
    Q_SLOT void setJoinFigures(bool j) { m_joinFigures = j; }
    bool joinFigures() const { return m_joinFigures; }
};

class Window : public QWidget
{
    Q_OBJECT
    QGridLayout m_layout{this};
    QGraphicsView m_view;
    QCheckBox m_join{"Join Figures (toggle with Spacebar)"};
    QAction m_toggleJoin{this};
public:
    Window(QWidget * parent = 0) : QWidget{parent}
    {
        m_layout.addWidget(&m_view);
        m_layout.addWidget(&m_join);
        m_view.setAlignment(Qt::AlignLeft | Qt::AlignTop);

        m_toggleJoin.setShortcut(QKeySequence(Qt::Key_Space));
        connect(&m_toggleJoin, SIGNAL(triggered()), &m_join, SLOT(toggle()));
        addAction(&m_toggleJoin);

        m_view.addAction(new QAction{"Clear", this});
        m_view.setContextMenuPolicy(Qt::ActionsContextMenu);
        connect(m_view.actions().at(0), SIGNAL(triggered()), SLOT(newScene()));

        // Create a new scene instead of clear()-ing it, since scenes can only grow their
        // sceneRect().
        newScene();
    }
    Q_SLOT void newScene() {
        if (m_view.scene()) m_view.scene()->deleteLater();
        m_view.setScene(new Scene);
        m_view.scene()->connect(&m_join, SIGNAL(toggled(bool)), SLOT(setJoinFigures(bool)));
    }
};

int main(int argc, char *argv[])
{
    QApplication a{argc, argv};
    Window w;
    w.show();
    return a.exec();
}

#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I used a different approach, which also works, but this doesnt allow to draw seperate figures without connecting them. I also tried your approach but i cant get it working, it looks like you can draw several firgures without them being connected. I am also not able to change the color of my pen. – user3110781 Dec 18 '13 at 13:11
  • I have one more question about this, how does this work with the buttons? Usually i go to the design and just drag and drop buttons and then connect them. But with this setup that does not seem to work? – user3110781 Dec 20 '13 at 07:53
  • @user3110781: I don't know what you mean. Do you mean how to integrate `Window` into a widget/dialog that's designed as a .ui file? – Kuba hasn't forgotten Monica Dec 20 '13 at 15:40
0

Create a class that is inherited from QGraphicsItem, or QGraphicsObject if you want signals and slots.

Store a QPainterPath as a member of the class. When you detect the mouse button pressed, call the painter path moveTo() function, providing the coordinate. On receiving mouse move events, call the painter path lineTo() function with the coordinates.

Overload the boundingRect function to return the painter path's rect and also overload the shape() function to return the correct shape for collision.

Finally in the class's paint function, draw the painter path. Here's a skeleton class you can use.

class MouseDraw : public QGraphicsItem
{
    public:
        QRectF boundingRect() const
        {
            return m_painterpath.boundingRect();
        }

        QPainterPath shape() const
        {
            return m_painterpath;
        }

        void QGraphicsItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
        {
            // setup pen and brush
            // ....
            // draw the path

            painter->drawPath(m_painterpath);
        }

    private:
        QPainterPath m_painterpath;
};

Handle the mousePress, mouseMove and mouseRelease events to add the required points to the painter path and then instantiate an object of the class and add it to the scene: -

MouseDraw* mouseDraw = new MouseDraw;
scene->addItem(mouseDraw);

Note that the object is added only once to the scene and is created dynamically.

TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85