Cut copy paste in Graphics View - c++

I have 5 entities that can be added in graphics view on mouse events and button clicks. Each entity has been assigned the unique id. I need to add the operations cut, copy and paste on
these entities. How to proceed with that. I didn't get any example for cut, copy paste operations in graphics View in Qt. How can I do that?
I have different classes for all entities my class line, circle and ellipse are inherited from QGraphicsItem and line and text from QgraphicsLineItem and QgraphicsEllipse Item. Please tell me how can I work with them too.
line.cpp
#include "line.h"
Line::Line(int i, QPointF p1, QPointF p2)
{
// assigns id
id = i;
// set values of start point and end point of line
startP = p1;
endP = p2;
}
int Line::type() const
{
// Enable the use of qgraphicsitem_cast with line item.
return Type;
}
QRectF Line::boundingRect() const
{
qreal extra = 1.0;
// bounding rectangle for line
return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
line().p2().y() - line().p1().y()))
.normalized()
.adjusted(-extra, -extra, extra, extra);
}
void Line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
// draws/paints the path of line
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
// sets pen for line path
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawLine(startP, endP);
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
painter->drawLine(startP, endP);
}
}
How can I work with this QGraphicsLineItem?

I think you should use custom graphics scene. Create QGraphicsScene subclass. Reimplement keyPressEvent:
if (e->key() == Qt::Key_C && e->modifiers() & Qt::ControlModifier)
{
listCopiedItems = this->selectedItems();
}
 
 
if (e->key() == Qt::Key_V && e->modifiers() & Qt::ControlModifier)
{
for(int i=0; i< listCopiedItems.count(); i++)
{
//create new objects, set position and properties
}
}
You can get all needed properties from old objects such as color, size etc and set to new. For cut do same thing but delete old objects from scene and when all work will be done, delete this objects from memory. Also you can create shortcuts with QShortcut class.
Edit. I want to say that it is very complicate task, so I can't get you code for all cases, for all types. I give just example, but this example works(I tested it). I post here absolutely full code.
Header:
#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QStack>
#include <QPoint>
#include <QMouseEvent>
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = 0);
signals:
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
public slots:
private:
QList<QGraphicsItem *> lst; QPoint last;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
QVector<QGraphicsEllipseItem * > vec;
};
#endif // GRAPHICSSCENE_H
Cpp:
#include "graphicsscene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsItem>
GraphicsScene::GraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
//add something
addPixmap(QPixmap("G:/2/qt.jpg"));
vec.push_back(addEllipse(0,0,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+100,0+100,50,50,QPen(Qt::red),QBrush(Qt::blue)));
vec.push_back(addEllipse(0+150,0+150,50,50,QPen(Qt::red),QBrush(Qt::blue)));
}
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
//qDebug() << "in";
if (mouseEvent->button() == Qt::LeftButton)
{
last = mouseEvent->scenePos().toPoint();//remember this point, we need it for copying
QGraphicsItem *item = itemAt(mouseEvent->scenePos(), QTransform());
item->setFlags(QGraphicsItem::ItemIsSelectable);
item->setSelected(!item->isSelected());
}
QGraphicsScene::mousePressEvent(mouseEvent);
}
void GraphicsScene::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_C && e->modifiers() & Qt::ControlModifier)
{
lst = this->selectedItems();
}
if (e->key() == Qt::Key_V && e->modifiers() & Qt::ControlModifier)
{
for(int i=0; i< lst.count(); i++)
{
//if it is ellipse
QGraphicsEllipseItem *ell = qgraphicsitem_cast<QGraphicsEllipseItem *>(lst.at(i));
if(ell)
{//then add ellipse to scene with ell properties and new position
addEllipse(QRect(last,ell->rect().size().toSize()),ell->pen(),ell->brush());
qDebug() << "good";
}
}
}
QGraphicsScene::keyPressEvent(e);
}
It is so complicate because you have no any clone() method, so you can't clone object with all all properties and move it to the new position. If you have your specific items that you should provide something specific too. That's why it is complex and I can't get code for all cases.
EDIT
You can't show scene, you should use this instead:
QGraphicsView vview;
GraphicsScene ss;
vview.setScene(&ss);
vview.show();

Related

QGraphicsPathItem between two movable QGraphicsRectItem

I'm trying to follow this example and eyllanesc's answer here
Weird behaviour when dragging QGraphicsItem to draw a line ending with an arrow between two draggable/movable QGraphicsRectItems
Here is my code:
The customized QGraphicsPathItemclass (inherits from QObject also because I use signals):
wireArrow.h
public slots:
void changePosStart(QPointF newpos);
void changePosEnd(QPointF newpos);
private:
component *myStartItem;
component *myEndItem;
QPolygonF arrowHead;
QColor myColor = Qt::black;
WireArrow.cpp
WireArrow::WireArrow(component *startItem, component *endItem,
QGraphicsItem *parent )
: QGraphicsPathItem(parent), myStartItem(startItem), myEndItem(endItem)
{
connect(startItem,SIGNAL(componentPosChanged(QPointF)),this, SLOT(changePosStart(QPointF)) );
);
connect(endItem,SIGNAL(componentPosChanged(QPointF)),this,SLOT(changePosEnd(QPointF)) );;
this->setPos(myStartItem->pos());
setFlag(ItemIsSelectable);
setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
// QPainterPath path;
// path.quadTo(100,0,100,100);
QPainterPath rectPath;
rectPath.moveTo(0.0, 0.0);
rectPath.lineTo(myStartItem->pos().x()-myEndItem->pos().x() , myStartItem->pos().y()-myEndItem->pos().y());
this->setPath(rectPath);
}
void WireArrow::changePosStart(QPointF newpos)
{//#to-do: find how to update pos setpos + this->update maybe? }
void WireArrow::changePosEnd(QPointF newpos)
{//#to-do: find how to update the end pos}
The customized qgraphicsitem class (also inherits from QObject to emit signal on position update):
component::component(/*some irrelevant params*/QGraphicsItem*parent ):
QGraphicsRectItem(parent), //...init other params
{
setRect(-40, -40, 80, 80);
setFlag(ItemIsMovable);
setFlag(ItemIsSelectable);
setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
}
QVariant component::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
qDebug() << "cmp:: event change on scene";
QPointF newPos = value.toPointF();
emit componentPosChanged(newPos);
}
return QGraphicsItem::itemChange(change, value);
}
main
// create two components out and in
WireArrow * wa = new WireArrow(out,in);
scene->addItem(wa);
I can create the rectangles (components) and move them just fine (thanks to the answer here , my problem is I can draw lines orignation from out but :
I can't draw from one component to another correctly.
I need to make them move automatically when dragging rectangles. In other examples, I saw they were treating the lines as an attribute of the
item, which is something I can't do here since the wire is for two
items and not one so I replaced that with signal/slot connection but
I still can't figure out how to update the position...
An image explaining what I'm trying to do
(squares are really made with qt, but the line is made in paint, the squares are movable ).
So in short: Just trying to make a line between two points(the position of the two squares) and the squares are movable so the line should adapt if one of the the 2 squares is moved.
What is required is very similar to what I implement in this answer so I will limit the code translated from python to c++:
component.h
#ifndef COMPONENT_H
#define COMPONENT_H
#include <QGraphicsRectItem>
class Arrow;
class Component : public QGraphicsRectItem
{
public:
Component(QGraphicsItem *parent = nullptr);
void addArrow(Arrow *arrow);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
private:
QVector<Arrow *> mArrows;
};
#endif // COMPONENT_H
component.cpp
#include "arrow.h"
#include "component.h"
Component::Component(QGraphicsItem *parent):
QGraphicsRectItem(parent)
{
setRect(-40, -40, 80, 80);
setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);
}
void Component::addArrow(Arrow *arrow)
{
mArrows << arrow;
}
QVariant Component::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if(change == QGraphicsItem::ItemPositionHasChanged){
for(Arrow * arrow: qAsConst(mArrows)){
arrow->adjust();
}
}
return QGraphicsRectItem::itemChange(change, value);
}
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicsPathItem>
class Component;
class Arrow : public QGraphicsPathItem
{
public:
Arrow(Component *startItem, Component *endItem, QGraphicsItem *parent = nullptr);
void adjust();
private:
Component *mStartItem;
Component *mEndItem;
};
#endif // ARROW_H
arrow.cpp
#include "arrow.h"
#include "component.h"
#include <QPen>
Arrow::Arrow(Component *startItem, Component *endItem, QGraphicsItem *parent):
QGraphicsPathItem(parent), mStartItem(startItem), mEndItem(endItem)
{
mStartItem->addArrow(this);
mEndItem->addArrow(this);
setPen(QPen(QColor("red"), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
adjust();
}
void Arrow::adjust()
{
prepareGeometryChange();
QPainterPath path;
path.moveTo(mStartItem->pos());
path.lineTo(mEndItem->pos());
setPath(path);
}
Component *comp1 = new Component;
Component *comp2 = new Component;
comp1->setPos(50, 50);
comp2->setPos(400, 400);
Arrow *arrow = new Arrow(comp1, comp2);
scene->addItem(comp1);
scene->addItem(comp2);
scene->addItem(arrow);

QGraphicsPolygonItem not updating QPolygonF co-ordinates when dragged

I have a custom version of QGraphicsPolygonItem which is defined here:
#ifndef CUSTOMGPOLYGON_H
#define CUSTOMGPOLYGON_H
#include <QObject>
#include <QGraphicsPolygonItem>
#include <string>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QGraphicsTextItem>
class CustomGPolygon : public QObject, public QGraphicsPolygonItem
{
Q_OBJECT
public:
CustomGPolygon(QPolygonF poly, QObject *parent);
~CustomGPolygon();
using QGraphicsPolygonItem::boundingRect;
using QGraphicsPolygonItem::paint;
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
QGraphicsTextItem *className;
private slots:
void deletePolygon();
void copyPolygon();
signals:
void duplicatePoly(QPolygonF);
private:
QMenu menu;
};
#endif // CUSTOMGPOLYGON_H
This is the .cpp for my CustomGPolygon:
#include "customgpolygon.h"
#include <iostream>
CustomGPolygon::CustomGPolygon(QPolygonF poly, QObject *parent):QGraphicsPolygonItem(poly)
{
menu.addAction("Copy", this, SLOT(copyPolygon()));
menu.addAction("Delete", this, SLOT(deletePolygon()));
connect(this, SIGNAL(duplicatePoly(QPolygonF)), parent, SLOT(drawPolygon(QPolygonF)));
}
CustomGPolygon::~CustomGPolygon()
{}
void CustomGPolygon::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
QGraphicsItem::mouseMoveEvent(event);
QPolygonF poly = this->polygon();
QPointF edgePoint(0,0);
for(int i = 0; i<poly.size(); i++){
if(poly.at(i).x() > edgePoint.x() && poly.at(i).y() > edgePoint.y())
{
edgePoint.setX(poly.at(i).x());
edgePoint.setY(poly.at(i).y());
}
}
this->className->setPos(edgePoint);
}
}
void CustomGPolygon::deletePolygon()
{
delete this;
}
void CustomGPolygon::copyPolygon()
{
QPolygonF poly = this->polygon();
emit duplicatePoly(poly);
}
To Draw one of these polygons onto my QGraphicsScene, I use the following function in my mainwindow.cpp:
void MainWindow::drawPolygon(const QPolygonF &poly)
{
CustomGPolygon *objectPt = new CustomGPolygon(poly, this);
objectPt->setPen(pen);
objectPt->setFlag(QGraphicsItem::ItemIsMovable);
scene->addItem(objectPt);
objectPt->className = textItem;
map->drawing = false;
}
When I drag this drawn polygon I need the co-ordinates of the vectors within the boundingRect to update - which at the moment, they are not doing.
I have tried adding these flags to solve the problem:
objectPt->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
objectPt->setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
However the problem remained
The QPolygonF set in the item is not about the coordinates of the scene but about the coordinates of the item, so moving the item will not change the QPolygonF. It is similar to the position of our face: If roads move with respect to the world but not with respect to ourselves. So if you want to get the polygon with respect to the scene you will have to make a conversion using the mapToScene() method. On the other hand if you want to track the position of the item then you should not use mouseMoveEvent() but itemChange().
On the other hand your calculation of the point is incorrect, what you should compare is the distance based on some metric, for example the Euclidean distance, since for example with your logic if the polygon is in position with negative coordinates then the edgePoint will always be ( 0,0).
Considering the above, the solution is:
#include <QtWidgets>
class CustomGPolygon: public QObject, public QGraphicsPolygonItem{
Q_OBJECT
public:
CustomGPolygon(QPolygonF poly, QObject *parent=nullptr):
QObject(parent), QGraphicsPolygonItem(poly), className(nullptr){
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
menu.addAction("Copy", this, &CustomGPolygon::copyPolygon);
menu.addAction("Delete", this, &CustomGPolygon::deletePolygon);
// if(parent)
// connect(this, &CustomGPolygon:: SIGNAL(duplicatePoly(QPolygonF)), parent, SLOT(drawPolygon(QPolygonF)));
}
~CustomGPolygon(){}
QGraphicsTextItem *getClassName() const{return className;}
void setClassName(QGraphicsTextItem *value){className = value;}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == GraphicsItemChange::ItemPositionChange && !polygon().isEmpty()){
QPolygonF p = mapToScene(polygon());
QPointF edgePoint = *std::max_element(p.begin(), p.end(),
[](const QPointF & x, const QPointF & y) -> bool
{
return QVector2D(x).length() > QVector2D(y).length();
});
if(className)
className->setPos(edgePoint);
}
return QGraphicsPolygonItem::itemChange(change, value);
}
private Q_SLOTS:
void deletePolygon(){delete this;}
void copyPolygon(){
QPolygonF poly = mapToScene(polygon());
Q_EMIT duplicatePoly(poly);
}
Q_SIGNALS:
void duplicatePoly(QPolygonF);
private:
QGraphicsTextItem *className;
QMenu menu;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
QPolygonF poly;
poly << QPointF(0, 0) << QPointF(100, 0) << QPointF(100, 100);
CustomGPolygon *item = new CustomGPolygon(poly);
QGraphicsTextItem *textItem = new QGraphicsTextItem("Stack Overflow");
scene.addItem(textItem);
scene.addItem(item);
item->setClassName(textItem);
view.show();
view.resize(640, 480);
return a.exec();
}
#include "main.moc"

Composed QGraphicsItem not detecting clicks on sub-items

I am trying to make a simple extension of QGraphicsRectItem that allows me to resize the rectangle and move it around with the mouse. I model the handles with elliptical arches on the corners I want to enable for dragging, which I implement as QGraphicsEllipseItems:
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
typedef enum {
None,
BottomLeft,
TopRight
} ActiveAnchor;
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
void setAnchorRadius(float radius);
float getAnchorRadius();
QPainterPath shape() const;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent * event);
void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
};
I want to be able to detect clicks both on the rectangle and on the handle items (this is necessary because if the rectangle gets too small the handles are the only easily clickable area), so I thought I'd extend QGraphicsRectItem::shape() to add to the returned QPainterPath the paths of the sub-items (adjusting coordinates to make them relative to the parent item):
QPainterPath QGraphicsBoxWithHandlesItem::shape() const
{
auto curShape = QGraphicsRectItem::shape();
curShape.addPath( mapFromItem(&m_anchorBottomLeft, m_anchorBottomLeft.shape()) );
curShape.addPath( mapFromItem(&m_anchorTopRight, m_anchorTopRight.shape()) );
return curShape;
}
What I get, however, is that now clicks inside the handles areas are completely ignored and only clicks in the central area of the rectangle are handled.
What is the correct way to extend the clickable area of an item when it has a non-trivial shape?
Update: I tried to set the ItemIsSelectable flag on a handle and now, if I click on it, I see that it gets selected. I still don't get any mousePressEvent in the parent, however. What am I doing wrong?
Edit:
This is the constructor implementation:
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorBottomLeft.setParentItem(this);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorTopRight.setParentItem(this);
}
Your QGraphicsEllipseItem are on top of the base item so the mouse events will never come to you.
What you have to do is use a sceneEventFilter, but since the QGraphicsEllipseItem are children of the main item they will never move, so they should not have a parent but you should add them directly to the scene.
The full functionality is implemented in the following code:
*.h
#ifndef QGRAPHICSBOXWITHHANDLESITEM_H
#define QGRAPHICSBOXWITHHANDLESITEM_H
#include <QGraphicsRectItem>
#include <QObject>
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
enum ActiveAnchor{
None,
BottomLeft,
TopRight
};
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
};
#endif // QGRAPHICSBOXWITHHANDLESITEM_H
*.cpp
#include "qgraphicsboxwithhandlesitem.h"
#include <QEvent>
#include <QGraphicsScene>
#include <QDebug>
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorBottomLeft.setParentItem(this);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsSelectable);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorTopRight.setParentItem(this);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsSelectable);
}
QVariant QGraphicsBoxWithHandlesItem::itemChange(GraphicsItemChange change, const QVariant & value){
if(change == QGraphicsItem::ItemSceneHasChanged){
if(scene()){
scene()->addItem(&m_anchorBottomLeft);
scene()->addItem(&m_anchorTopRight);
m_anchorBottomLeft.installSceneEventFilter(this);
m_anchorTopRight.installSceneEventFilter(this);
}
}
else if (change == QGraphicsItem::ItemPositionHasChanged) {
m_anchorBottomLeft.setPos(mapToScene(rect().bottomLeft()));
m_anchorTopRight.setPos(mapToScene(rect().topRight()));
}
return QGraphicsRectItem::itemChange(change, value);
}
bool QGraphicsBoxWithHandlesItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
if(watched == &m_anchorTopRight){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorTopRight.mapToScene(m_anchorTopRight.rect().center());
r.setTopRight(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorTopRight";
break;
}
}
}
if(watched == &m_anchorBottomLeft){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorBottomLeft.mapToScene(m_anchorBottomLeft.rect().center());
r.setBottomLeft(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorBottomLeft";
break;
}
}
}
return QGraphicsRectItem::sceneEventFilter(watched, event);
}

How to mix mouseEnterEvent and mouseMove?

in my application I try to connect nodes with lines. I use a QGraphicsView with a QGraphicsScene and my own QGraphicsItems. Now if I click on an item I want to draw a line to another node. To give a visual feedback, the goal should change color if the mouse hovers over the goal. The basics works so far, but my problem is that if I drag a line with the mouse (via mouseMoveEvent), I do not get any hoverEvents any more. I replicated the behaviour with this code:
Header File:
#pragma once
#include <QtWidgets/Qwidget>
#include <QGraphicsItem>
#include <QGraphicsScene>
class HaggiLearnsQt : public QWidget
{
Q_OBJECT
public:
HaggiLearnsQt(QWidget *parent = Q_NULLPTR);
};
class MyScene : public QGraphicsScene
{
public:
MyScene(QObject* parent = 0);
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
};
class MyItem : public QGraphicsItem
{
public:
MyItem(QGraphicsItem* parent = Q_NULLPTR);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
bool mouseOverItem;
};
Implementation:
#include "HaggiLearnsQt.h"
#include <QMessageBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QGraphicsView>
MyScene::MyScene(QObject* parent)
{}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
MyItem::MyItem(QGraphicsItem* parent) : mouseOverItem(false)
{
setAcceptHoverEvents(true);
}
QRectF MyItem::boundingRect() const
{
return QRectF(-50, -50, 50, 50);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QBrush b = QBrush(Qt::black);
if(mouseOverItem)
b = QBrush(Qt::yellow);
painter->setBrush(b);
painter->drawRect(boundingRect());
}
void MyItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
mouseOverItem = true;
QGraphicsItem::hoverEnterEvent(event);
}
void MyItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
mouseOverItem = false;
QGraphicsItem::hoverLeaveEvent(event);
}
HaggiLearnsQt::HaggiLearnsQt(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
MyScene* graphicsScene = new MyScene();
QGraphicsView* graphicsView = new QGraphicsView();
graphicsView->setRenderHint(QPainter::RenderHint::Antialiasing, true);
graphicsView->setScene(graphicsScene);
layout->addWidget(graphicsView);
graphicsView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
graphicsView->setMinimumHeight(200);
graphicsView->setMinimumWidth(200);
graphicsView->setStyleSheet("background-color : gray");
MyItem* myitem = new MyItem();
myitem->setPos(50, 50);
graphicsScene->addItem(myitem);
}
And the default main.cpp:
#include "HaggiLearnsQt.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
HaggiLearnsQt w;
w.show();
return a.exec();
}
If you run the code, a box appears in the middle of the window. If you hover over the box, it changes color. Now try to klick outside the box and drag wiht pressed button into the box. The box does not receive a hover and does not change color.
So my question is: Can I somehow change the item while I move the mouse with a pressed button?
You can get the hovered item passing mouseEvent->scenePos() to the QGraphicsScene::itemAt method inside the scene mouse move event handler.
Have a pointer to a MyItem instance, in MyScene:
class MyScene : public QGraphicsScene
{
MyItem * hovered;
//...
initialize it to zero in MyScene constructor:
MyScene::MyScene(QObject* parent)
{
hovered = 0;
}
then use it to track the current highlighted item (if there's one):
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if(mouseEvent->buttons())
{
QGraphicsItem * item = itemAt(mouseEvent->scenePos(), QTransform());
MyItem * my = dynamic_cast<MyItem*>(item);
if(my != 0)
{
qDebug() << mouseEvent->scenePos();
if(!my->mouseOverItem)
{
my->mouseOverItem = true;
my->update();
hovered = my;
}
}
else
{
if(hovered != 0)
{
hovered->mouseOverItem = false;
hovered->update();
hovered = 0;
}
}
}
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
The line if(mouseEvent->buttons()) at the beginning prevents the check to be performed if no mouse button is held.
Don't forget to initialize mouseOverItem to false in MyItem constructor:
MyItem::MyItem(QGraphicsItem* parent) : mouseOverItem(false)
{
setAcceptHoverEvents(true);
mouseOverItem = false;
}

How to refresh QGraphicsView to show changes in the QGraphicsScene's background

I have a custom QGraphicsView and QGraphicsScene. Inside QGraphicsScene I have overriden void drawBackground(QPainter *painter, const QRectF &rect) and based on a boolean flag I want to toggle a grid on and off. I tried calling clear() or calling the painter's eraseRect(sceneRect()) inside my function but it didn't work. So after doing some reading I guess it wasn't supposed to work since after changing the scene you need to refresh the view. That's why I'm emitting a signal called signalUpdateViewport()
void Scene::drawBackground(QPainter *painter, const QRectF &rect) {
if(this->gridEnabled) {
// Draw grid
}
else {
// Erase using the painter
painter->eraseRect(sceneRect());
// or by calling
//clear();
}
// Trigger refresh of view
emit signalUpdateViewport();
QGraphicsScene::drawBackground(painter, rect);
}
which is then captured by my view:
void View::slotUpdateViewport() {
this->viewport()->update();
}
Needless to say this didn't work. With doesn't work I mean that the results (be it a refresh from inside the scene or inside the view) are made visible only when changing the widget for example triggering a resize event.
How do I properly refresh the view to my scene to display the changed that I have made in the scene's background?
The code:
scene.h
#ifndef SCENE_HPP
#define SCENE_HPP
#include <QGraphicsScene>
class View;
class Scene : public QGraphicsScene
{
Q_OBJECT
Q_ENUMS(Mode)
Q_ENUMS(ItemType)
public:
enum Mode { Default, Insert, Select };
enum ItemType { None, ConnectionCurve, ConnectionLine, Node };
Scene(QObject* parent = Q_NULLPTR);
~Scene();
void setMode(Mode mode, ItemType itemType);
signals:
void signalCursorCoords(int x, int y);
void signalSceneModeChanged(Scene::Mode sceneMode);
void signalSceneItemTypeChanged(Scene::ItemType sceneItemType);
void signalGridDisplayChanged(bool gridEnabled);
void signalUpdateViewport();
public slots:
void slotSetSceneMode(Scene::Mode sceneMode);
void slotSetSceneItemType(Scene::ItemType sceneItemType);
void slotSetGridStep(int gridStep);
void slotToggleGrid(bool flag);
private:
Mode sceneMode;
ItemType sceneItemType;
bool gridEnabled;
int gridStep;
void makeItemsControllable(bool areControllable);
double round(double val);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void drawBackground(QPainter *painter, const QRectF &rect);
};
Q_DECLARE_METATYPE(Scene::Mode)
Q_DECLARE_METATYPE(Scene::ItemType)
#endif // SCENE_HPP
scene.cpp
#include <QGraphicsItem>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QRectF>
#include <QKeyEvent>
#include <QtDebug>
#include "scene.h"
Scene::Scene(QObject* parent)
: QGraphicsScene (parent),
sceneMode(Default),
sceneItemType(None),
gridEnabled(true),
gridStep(30)
{
}
Scene::~Scene()
{
}
void Scene::setMode(Mode mode, ItemType itemType)
{
this->sceneMode = mode;
this->sceneItemType = itemType;
QGraphicsView::DragMode vMode = QGraphicsView::NoDrag;
switch(mode) {
case Insert:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
case Select:
{
makeItemsControllable(true);
vMode = QGraphicsView::RubberBandDrag;
break;
}
case Default:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
}
QGraphicsView* mView = views().at(0);
if(mView) {
mView->setDragMode(vMode);
}
}
void Scene::slotSetSceneMode(Scene::Mode sceneMode)
{
this->sceneMode = sceneMode;
qDebug() << "SM" << (int)this->sceneMode;
emit signalSceneModeChanged(this->sceneMode);
}
void Scene::slotSetSceneItemType(Scene::ItemType sceneItemType)
{
this->sceneItemType = sceneItemType;
qDebug() << "SIT:" << (int)this->sceneItemType;
emit signalSceneItemTypeChanged(this->sceneItemType);
}
void Scene::slotSetGridStep(int gridStep)
{
this->gridStep = gridStep;
}
void Scene::slotToggleGrid(bool flag)
{
this->gridEnabled = flag;
invalidate(sceneRect(), BackgroundLayer);
qDebug() << "Grid " << (this->gridEnabled ? "enabled" : "disabled");
update(sceneRect());
emit signalGridDisplayChanged(this->gridEnabled);
}
void Scene::makeItemsControllable(bool areControllable)
{
foreach(QGraphicsItem* item, items()) {
if(/*item->type() == QCVN_Node_Top::Type
||*/ item->type() == QGraphicsLineItem::Type
|| item->type() == QGraphicsPathItem::Type) {
item->setFlag(QGraphicsItem::ItemIsMovable, areControllable);
item->setFlag(QGraphicsItem::ItemIsSelectable, areControllable);
}
}
}
double Scene::round(double val)
{
int tmp = int(val) + this->gridStep/2;
tmp -= tmp % this->gridStep;
return double(tmp);
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mousePressEvent(event);
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
emit signalCursorCoords(int(event->scenePos().x()), int(event->scenePos().y()));
QGraphicsScene::mouseMoveEvent(event);
}
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mouseReleaseEvent(event);
}
void Scene::keyPressEvent(QKeyEvent* event)
{
if(event->key() == Qt::Key_M) {
slotSetSceneMode(static_cast<Mode>((int(this->sceneMode) + 1) % 3));
}
else if(event->key() == Qt::Key_G) {
slotToggleGrid(!this->gridEnabled);
}
QGraphicsScene::keyPressEvent(event);
}
void Scene::drawBackground(QPainter *painter, const QRectF &rect)
{
// FIXME Clearing and drawing the grid happens only when scene size or something else changes
if(this->gridEnabled) {
painter->setPen(QPen(QColor(200, 200, 255, 125)));
// draw horizontal grid
double start = round(rect.top());
if (start > rect.top()) {
start -= this->gridStep;
}
for (double y = start - this->gridStep; y < rect.bottom(); ) {
y += this->gridStep;
painter->drawLine(int(rect.left()), int(y), int(rect.right()), int(y));
}
// now draw vertical grid
start = round(rect.left());
if (start > rect.left()) {
start -= this->gridStep;
}
for (double x = start - this->gridStep; x < rect.right(); x += this->gridStep) {
painter->drawLine(int(x), int(rect.top()), int(x), int(rect.bottom()));
}
}
else {
// Erase whole scene's background
painter->eraseRect(sceneRect());
}
slotToggleGrid(this->gridEnabled);
QGraphicsScene::drawBackground(painter, rect);
}
The View currently doesn't contain anything - all is default. The setting of my Scene and View instance inside my QMainWindow is as follows:
void App::initViewer()
{
this->scene = new Scene(this);
this->view = new View(this->scene, this);
this->viewport = new QGLWidget(QGLFormat(QGL::SampleBuffers), this->view);
this->view->setRenderHints(QPainter::Antialiasing);
this->view->setViewport(this->viewport);
this->view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
this->view->setCacheMode(QGraphicsView::CacheBackground);
setCentralWidget(this->view);
}
EDIT: I tried calling invalidate(sceneRect(), BackgroundLayer) before the update(), clear() or whatever I tried to trigger a refresh.
I also tried QGraphicsScene::update() from within the scene but it didn't work Passing both no argument to the function call and then passing sceneRect() didn't result in anything different then what I've described above.
Found the issue - I forgot to set the size of the scene's rectangle:
this->scene->setSceneRect(QRectF(QPointF(-1000, 1000), QSizeF(2000, 2000)));
I actually found the problem by deciding to print the size of the QRectF returned by the sceneRect() call and when I looked at the output I saw 0, 0 so basically I was indeed triggering the update but on a scene with the area of 0 which (obviously) would result in nothing.
Another thing that I tested and worked was to remove the background caching of my view.
When you change your grid settings, whether it's on or off (or color, etc.), you need to call QGraphicsScene::update. That's also a slot, so you can connect a signal to it if you want. You can also specify the exposed area; if you don't, then it uses a default of everything. For a grid, that's probably what you want.
You don't need to clear the grid. The update call ensures that the updated area gets cleared, and then you either paint on it if you want the grid, or don't paint on it if the grid shouldn't be there.
Sometimes when the view/scene refuse to run update() directly, processing the app events before the update() fixes it, like so:
qApp->processEvents();
update();