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"
Related
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);
(warning crossposted on: https://forum.qt.io/topic/105158/qgraphicsscene-item-is-drawn-at-twice-x2-position)
In the following code I am creating a custom widget which has a subclass of QGraphicsScene embeded in it. My aim is to click and add a point to the scene. A point is my subclass of QGraphicsItem (GIPoint). I want to move that point around and later connect it to other points and make a spline path.
The problem I am facing is that the point is not drawn at where I click but at a place which is formed by doubling the mouse-event's scenePos() coordinates. So if I click at (100,100) the point is drawn at (200,200). I suspect that I have misunderstood the coordinate system despite reading the documentation.
The question How to add item in a QGraphicsScene? seems relevant but the proposed solution to transform the mouse-event's coordinates via mapToScene(event->pos()); actually doubles up the position (before it will print that it draws on same position but it would then be x2. Now it also prints it as x2).
So I am asking additionally to point me to some simple-to-digest advice on how the widgets placement works. btw. is QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
correct regarding the (x,y) coordinates of the rectangle?
My example code follows:
/* use the following pro:
QT += widgets core gui
CONFIG += debug console
SOURCES = example.cpp
TARGET = example
*/
#include <QGraphicsItem>
#include <QPainter>
#include <QWidget>
#include <QRectF>
#include <QPointF>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QGraphicsView>
#include <QApplication>
#include <QOpenGLWidget>
#include <QMainWindow>
#include <QGridLayout>
#include <QDebug>
class GIPoint : public QGraphicsItem{
public:
GIPoint(QGraphicsItem * parent, const QPointF &position);
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
QRectF boundingRect() const override;
void paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
);
};
class GraphicsSceneWidget : public QGraphicsScene {
public:
explicit GraphicsSceneWidget(QObject *parent);
~GraphicsSceneWidget();
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
};
class VectorGraphicsWidget : public QWidget {
public:
VectorGraphicsWidget(QWidget *parent);
~VectorGraphicsWidget();
private:
GraphicsSceneWidget *myGraphicsSceneWidget;
};
// implementation
GIPoint::GIPoint(
QGraphicsItem *parent,
const QPointF &position
) : QGraphicsItem(parent) {
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPos(position);
qWarning() << "GIPoint::GIPoint() : init at " << position;
}
QVariant GIPoint::itemChange(
GraphicsItemChange change,
const QVariant &value
){
if (change == QGraphicsItem::ItemPositionChange) {
qWarning("position changed");
}
return value;
}
QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
// return QRectF(0,0, 5, 5);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QPointF xx = scenePos();
QRectF rect = QRectF(xx.x(), xx.y(), 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
//painter->fillRect(rect, brush);
painter->drawRect(rect);
}
GraphicsSceneWidget::GraphicsSceneWidget(QObject *parent)
: QGraphicsScene(parent)
{}
GraphicsSceneWidget::~GraphicsSceneWidget(){}
void GraphicsSceneWidget::mousePressEvent(
QGraphicsSceneMouseEvent *event
){
GIPoint *gip = new GIPoint(Q_NULLPTR, event->scenePos());
addItem(gip);
QGraphicsScene::mousePressEvent(event);
}
VectorGraphicsWidget::VectorGraphicsWidget(QWidget *parent) :
QWidget(parent)
{
myGraphicsSceneWidget = new GraphicsSceneWidget(this);
QGraphicsView *view = new QGraphicsView(myGraphicsSceneWidget);
myGraphicsSceneWidget->setSceneRect(QRectF(0, 0, 500, 500));
QGridLayout *centralLayout = new QGridLayout;
centralLayout->addWidget(view);
setLayout(centralLayout);
myGraphicsSceneWidget->addRect(
QRectF(0, 0, 100, 100),
QPen(Qt::black),
QBrush(Qt::green)
);
view->show();
}
VectorGraphicsWidget::~VectorGraphicsWidget() {
delete myGraphicsSceneWidget;
}
int main(int argc, char **argv){
QApplication app(argc, argv);
app.setApplicationName("test");
app.setOrganizationName("myorg");
app.setOrganizationDomain("myorg.com");
QMainWindow *w = new QMainWindow();
w->resize(500, 500);
w->setCentralWidget(new VectorGraphicsWidget(Q_NULLPTR));
w->show();
return app.exec();
}
The problem is that the boundingRect() and the paint() methods is respect the coordinate system of the item, not the scene. So the solution is not to use scenePos() in both methods but 0, 0:
QRectF GIPoint::boundingRect() const {
return QRectF(0, 0, 10, 10);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QRectF rect = QRectF(0, 0, 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
painter->fillRect(rect, brush);
painter->drawRect(rect);
}
Although I would recommend using QGraphicsRectItem as it implements what you have done.
I am learning Qt for fun. And I got a question:
How could I drag and drop the QLabel in Qt among two different windows?
Here is what I have so far:
As you can tell from the .gif(which does not want to become downloaded and visible here for some reasons, but if you click on the link to it, you can clearly see it) provided above right now there are two main problems:
I can not move the QLabel outside of the window (and hence am not able to register the drag and drop event).
The label is flashing for some reasons when I am moving it.
Here is the relevant part of the implementation from the .gif:
#ifndef DRAGGERP_H
#define DRAGGERP_H
#include <QLabel>
#include <QApplication>
#include <QMouseEvent>
#include <QPoint>
class DraggerP : public QLabel
{
QPoint offset;
QPoint startingPosition;
public:
DraggerP(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent* event) override
{
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
void leaveEvent(QEvent* event) override
{
QApplication::restoreOverrideCursor();
}
void mousePressEvent(QMouseEvent* event) override
{
startingPosition = pos();
offset = QPoint(
event->pos().x() - pos().x() + 0.5*width(),
event->pos().y() - pos().y() + 0.5*height()
);
}
void mouseMoveEvent(QMouseEvent* event) override
{
move(event->pos() + offset);
}
void mouseReleaseEvent(QMouseEvent* event) override
{
move(startingPosition);
}
};
#endif // DRAGGERP_H
This is the extension of the QLabel I am using to create the drag and drop effect.
I do not need the whole solution, at least an idea of how to accomplish this and what am I doing wrong here.
Here is a pretty good example and I used it as a starting point.
That strange movement that the QLabel suffers is because the position of the QLabel now depends on the layout, the job of the layout is to establish the position of the widgets depending on the policies you establish.
The solution is not to implement those actions in the QLabel but in the MainWindow as I show below:
#include <QApplication>
#include <QLabel>
#include <QMainWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QTime>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
class MainWindow: public QMainWindow {
QScrollArea scrollArea;
QWidget contentWidget;
QVBoxLayout lay;
public:
MainWindow(QWidget* parent=nullptr): QMainWindow(parent){
qsrand((uint) QTime::currentTime().msec());
setCentralWidget(&scrollArea);
scrollArea.setWidget(&contentWidget);
contentWidget.setLayout(&lay);
scrollArea.setWidgetResizable(true);
for(int i=0; i< 20; i++){
QLabel *label = new QLabel(QString("label %1").arg(i));
QPalette pal = label->palette();
pal.setColor(QPalette::Background, QColor(10 +qrand() % 240, 10 +qrand() % 240, 10 +qrand() % 240));
label->setAutoFillBackground(true);
label->setPalette(pal);
lay.addWidget(label);
}
setAcceptDrops(true);
}
protected:
void mousePressEvent(QMouseEvent *event){
QMainWindow::mousePressEvent(event);
QWidget *child = childAt(event->pos());
if(qobject_cast<QLabel *>(child))
createDrag(event->pos(), child);
}
void dropEvent(QDropEvent *event){
QByteArray byteArray = event->mimeData()->data("Label");
QWidget * widget = *reinterpret_cast<QWidget**>(byteArray.data());
QLabel * new_label = qobject_cast<QLabel *>(widget);
QWidget *current_children = childAt(event->pos());
QLabel * current_label = qobject_cast<QLabel*>(current_children);
int index = 0;
if(new_label){
if(current_label)
index = lay.indexOf(current_label);
else{
index = 0;
QLayoutItem *item = lay.itemAt(index);
while(item->widget()->pos().y() < event->pos().y() && item)
item = lay.itemAt(index++);
}
lay.insertWidget(index, new_label);
}
}
private:
void createDrag(const QPoint &pos, QWidget *widget){
if(widget == Q_NULLPTR)
return;
QByteArray byteArray(reinterpret_cast<char*>(&widget),sizeof(QWidget*));
QDrag *drag = new QDrag(this);
QMimeData * mimeData = new QMimeData;
mimeData->setData("Label",byteArray);
drag->setMimeData(mimeData);
QPoint globalPos = mapToGlobal(pos);
QPoint p = widget->mapFromGlobal(globalPos);
drag->setHotSpot(p);
drag->setPixmap(widget->grab());
drag->exec(Qt::CopyAction | Qt::MoveAction);
}
protected:
void dragEnterEvent(QDragEnterEvent *event){
if(event->mimeData()->hasFormat("Label"))
event->acceptProposedAction();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w1;
MainWindow w2;
w1.show();
w2.show();
return a.exec();
}
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;
}
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();