i 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();
}
}
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.
// 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"