Moving an item with the mouse on QGraphicsView / QGraphicsScene - c++

I have a QGraphicsView area that displays some Item items. I would like to have mouse move implemented.
class Item
{
public:
Item();
void update();
int x, y; // position
int z; // order - should correspond also to index in item list
};
class Collection : public QGraphicsView
{
Q_OBJECT
public:
Collection(QWidget *parent = 0);
void update();
QList<Item> *m_items;
protected:
virtual void paintEvent(QPaintEvent * event);
virtual void mousePressEvent(QMouseEvent * event);
virtual void mouseReleaseEvent(QMouseEvent *event);
private:
QPoint offset;
int itemMoved;
};
trying:
void Collection::mousePressEvent(QMouseEvent* event)
{
Item *currentItem = NULL;
itemMoved = -1;
foreach (QGraphicsItem *item, this->items(event->pos()))
{
// never get into this loop since my items are not children of QGraphicsItem
currentItem = dynamic_cast<Item*>(item);
if (event->button() == Qt::LeftButton && currentItem)
{
itemMoved = currentItem->z;
offset = event->pos();
}
else if (event->button() == Qt::RightButton)
{
// set right click stuff
}
else
{
event->ignore();
}
break;
}
}
void CollectionView::mouseReleaseEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton && itemMoved > 0)
{
m_items->at(itemMoved).x = event->pos().x();
m_items->at(itemMoved).y = event->pos().y();
// So far multiple fails:
// error: assignment of data-member 'Item::x' in read-only structure
// error: assignment of data-member 'Item::y' in read-only structure
// error: passing 'const Item' as 'this' argument of 'void Item::update()' discards qualifiers
m_items->at(itemMoved).update();
update();
}
}
I would make my Item inherit QGraphicsItem but then I get errors about virtual functions that I don't know how to define (like boundingRect which would depend on the item contents... could be objects, images, svg, text... for some i know how to get the bounding rectangle but for others really idk)... Is that the only way to do this ?
How can I identify he items at the location of the mouse position, and what is preventing me to change the x and y for the item once mouse is moved ?

If you're going to implement everything yourself, you shouldn't be using QGraphicsView at all. If you wish to use QGraphicsView, there's no need to reimplement any of its virtual methods. Just use it as-is, and set the item's QGraphicsItem::ItemIsMovable flag.
A complete example is provided in this answer. The only reason for reimplementation of QGraphicsView::mousePressEvent was to implement creation of new items.

Related

QWidget::mousePressEvent() not called

I'm working on a TreeViewNode class derived from QObject and QGraphicsItem, responsible for creating the individual nodes of the tree view (which displays a family tree) and adding them to the scene. I made sure to include the setFlag(QGraphicsItem::ItemIsSelectable); method in the class constructor and I overloaded mousePressEvent like so:
void TreeViewNode::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
qDebug() << "Mouse pressed on node: " << m_node->getPatient()->get_Name().c_str();
emit clicked(this);
}
QGraphicsItem::mousePressEvent(event);
}
The clicked signal is defined in the header file as void clicked(TreeViewNode* node);
Then I have a updateSelectedPatient slot in MainWindow
void MainWindow::updateSelectedPatient(TreeViewNode* node) {
selected->setSelectedPatient(node->getNode()->getPatient());
}
and the corresponding connect statement in the MainWindow constructor
connect(treeView, &TreeViewNode::clicked, this, &MainWindow::updateSelectedPatient);
When I execute I see the nodes that have been added to the scene in the view but when I click on them nothing happens (I know through debugging and also because the setSelectedPatient() method updates a widget that shows the patient information of the currently selected patient); it seems like mousePressEvent() is not being called at all.. any help would be really appreciated, thank you for your time!
edit:
//TreeViewNode.hpp
class TreeViewNode : public QObject, public QGraphicsItem {
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
private:
node* m_node;
Family_tree* m_family;
QGraphicsScene* m_scene;
static std::set<node*> addedNodes;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
public:
TreeViewNode(node* node, Family_tree* family, QGraphicsScene* scene, QGraphicsItem* parent = nullptr);
TreeViewNode* getTreeViewNode(node* n);
node* getNode() const;
static void clearAddedNodes();
void updateNode(Patient& patient);
QRectF boundingRect() const override;
void drawBranches(QPainter* painter);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
signals:
void clicked(TreeViewNode* node);
};
//TreeViewNode.cpp
TreeViewNode::TreeViewNode(node* node, Family_tree* family, QGraphicsScene* scene, QGraphicsItem* parent) : QGraphicsItem(parent), m_node(node), m_family(family), m_scene(scene) {
if (m_node == m_family->get_root()) {m_scene->addItem(this);}
addedNodes.insert(m_node);
setFlag(QGraphicsItem::ItemIsSelectable);
qDebug() << "Scene item count: " << m_scene->items().count();
if (m_node->getFather()) {
auto father = m_node->getFather();
if (addedNodes.count(father) > 0) {return;}
TreeViewNode* fatherNode = new TreeViewNode(m_node->getFather(), family, m_scene, this);
fatherNode->setPos(-50, -100);
}
if (m_node->getMother()) {
auto mother = m_node->getMother();
if (addedNodes.count(mother) > 0) {return;}
TreeViewNode* motherNode = new TreeViewNode(m_node->getMother(), family, m_scene, this);
motherNode->setPos(50, -100);
}
if (m_node->getSpouse()) {
auto spouse = m_node->getSpouse();
if (addedNodes.count(spouse) > 0) {return;}
TreeViewNode* spouseNode = new TreeViewNode(m_node->getSpouse(), family, m_scene, this);
spouseNode->setPos(100, 0);
}
int childCount = 0;
for (auto child : m_node->getChildren()) {
if (addedNodes.count(child) > 0) {return;}
TreeViewNode* childNode = new TreeViewNode(child, family, m_scene, this);
childCount++;
double xPos, yPos;
xPos = pos().x() + 50 + 100 * (childCount - (m_node->getChildren().size() + 1) / 2.0);
yPos = pos().y() + 100;
childNode->setPos(xPos, yPos);
}
}
You could use a normal event filter and check if is it a mouse press, release or move event then cast it to a mouse event and then just do whatever you want with it.
Example:
bool YourWidgetClass::eventFilter(QObject* object, QEvent* event)
{
if (object == YourInstance)
{
switch (event->type())
{
case QEvent::GraphicsSceneMousePress:
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent*>(event);
switch (mouseEvent->button())
{
case Qt::LeftButton:
// Handle left button pressed here
return true;
case Qt::RightButton:
// Handle right button pressed here
return true;
default:
break;
}
break;
}
}
}
Don't forget to install the event filter on your class
YourClassCtor->installEventFilter(this)
I don't see from your question that you need it for anything special. the signal &QGraphicsScene::selectionChanged is not enough for you? Is the item set to interactive?
So simply put:
declare a slot:
public slots:
void selectionChanged();
define the slot:
void MainWindow::selectionChanged()
{
qDebug() << ...... ;
}
and connect:
connect(scene,&QGraphicsScene::selectionChanged,this,&MainWindow::selectionChanged);

How to prevent child QGraphicsItem from being moved with parent?

I have a GraphicsBoxItem that holds a list of GraphicsImageItem (some kind of floating buttons around the box). I create them on the fly in focusInEvent() and destroy them in focusOutEvent().
Some of the GraphicsImageItem should move with the parent when clicked, some of them should stay at the same spot when clicked, until the box gets a focusOut by clicking outside of any graphics items.
Is there a way to prevent a child QGraphicsItem from being moved with the parent?
class GraphicsBoxItem : public QObject, public QGraphicsItem
{
Q_OBJECT
private:
QColor mColor;
QVector<GraphicsImageItem*> mItemList;
public:
GraphicsBoxItem(QGraphicsItem *parent = NULL)
:QGraphicsItem(parent)
{
mColor = Qt::lightGray;
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsFocusable);
}
QRectF boundingRect() const { return QRectF(50, 20, 100, 60); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setBrush(mColor);
painter->drawRect(boundingRect());
}
virtual void focusOutEvent(QFocusEvent * event)
{
foreach(GraphicsImageItem *item1, mItemList)
{
item1->deleteLater();
item1 = NULL;
}
mItemList.clear();
mColor = Qt::lightGray;
QGraphicsItem::focusOutEvent(event);
}
virtual void focusInEvent(QFocusEvent * event)
{
GraphicsImageItem *movableItem = new GraphicsImageItem(this);
movableItem->setPos(150, 20);
mItemList.push_back(movableItem);
movableItem->installSceneEventFilter(this);
connect(movableItem, SIGNAL(SignalClicked()), this, SLOT(SlotButtonClicked()));
GraphicsImageItem *nonMovableItem = new GraphicsImageItem(this);
nonMovableItem->setPos(20, 20);
mItemList.push_back(nonMovableItem);
nonMovableItem->installSceneEventFilter(this);
connect(nonMovableItem, SIGNAL(SignalClicked()), this, SLOT(SlotButtonClicked()));
mColor = Qt::blue;
QGraphicsItem::focusInEvent(event);
}
bool sceneEventFilter(QGraphicsItem* target, QEvent* event)
{
if(event->type() == QEvent::GraphicsSceneMousePress || event->type() == QEvent::GraphicsSceneMouseDoubleClick)
{
GraphicsImageItem* item = dynamic_cast<GraphicsImageItem*>(target);
if(item)
{
item->SignalClicked();
qDebug() << "image button was clicked: Need to set the focus back to the box";
setFocus();
return true;
}
}
return QGraphicsItem::sceneEventFilter(target, event);
}
public slots:
void SlotButtonClicked()
{
setPos(pos().x() + 10, pos().y()+10);
}
};
SignalClicked() moves the GraphicsBoxItem by 10 pixels. Some of the GraphicsImageItem stay put, some move with GraphicsBoxItem:
class GraphicsImageItem : public QGraphicsSvgItem
{
Q_OBJECT
public:
GraphicsImageItem::GraphicsImageItem(QGraphicsItem *parent = NULL)
: QGraphicsSvgItem(QString(":/images/icon.svg"), parent)
{
setFlag(QGraphicsItem::ItemIsSelectable);
}
QRectF boundingRect() const { return QRectF(0, 0, 30, 30); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setBrush(Qt::lightGray);
painter->drawRect(boundingRect());
renderer()->render(painter, boundingRect().adjusted(3, 3, -3, -3));
}
Q_SIGNALS:
void SignalClicked();
};
A child's position is always relative to its parent, if the parent moves, then the child's absolute position changes too.
If you want to create the illusion that this doesn't happen, you will have to manually move the child in the opposite direction, so it can remain in the same absolute position.
The other option would be to not have the items as children in the first place.
Maybe you should create an invisible item, then parent the movable item as well as the non-movable children to it, and the movable children - under the movable item. Then you simply only move the movable item - its children move, its siblings stay in the same position, as their parent doesn't move.
For the child items you don't want to move, try set this flag:
setFlag(QGraphicsItem::ItemIgnoresTransformations);
If you can subclass your child items, then you can override the itemChange() method and watch for:
ItemScenePositionHasChanged
The item's scene position has changed. This notification is sent if
the ItemSendsScenePositionChanges flag is enabled, and after the
item's scene position has changed (i.e., the position or
transformation of the item itself or the position or transformation of
any ancestor has changed). The value argument is the new scene
position (the same as scenePos()), and QGraphicsItem ignores the
return value for this notification (i.e., a read-only notification).
Then then return the child item's last scenePos() which you'll have to store as a member and update each scene position change.
Remember to set the flag:
ItemSendsGeometryChanges
in your child subclass's ctor.

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();

How QTableView or QListView is scrolling with hand drag?

in QGraphicview,
if we set it with : ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
this code make graphicsview can scroll items with mouse pressed and drag.
How can we make QListView or QTableView as the QGraphicsView?
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent. However you will have to be careful because you may be interfering with actions that are mapped to these by default implementations (e.g. selecting) so it would need to be tweaked a bit. For example (assumed subclass of QListView):
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton) //lets map scrolling to right button
m_ScrollStart = event->pos(); //QPoint member, indicates the start of the scroll
else
QListView::mousePressEvent(event);
}
and then
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(!m_ScrollStart.isNull()) //if the scroll was started
{
bool direction = (m_ScrollStart.y() < event->pos().y()); //determine direction, true is up (start is below current), false is down (start is above current)
int singleStep = (direction ? 10 : -10); //fill in the desired value
verticalScrollBar()->setValue(verticalScrollBar()->value() + singleStep);
//scroll by the certain amount in determined direction,
//you decide how much will be a single step... test and see what you like
}
QListView::mouseMoveEvent(event);
}
and finally
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
m_ScrollStart = QPoint(); //resets the scroll drag
QListView::mouseReleaseEvent(event);
}
like Resurrection did mention
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent
but below code is more preferred by us:
class MyListView : public QListView
{
typedef QListView super;
public:
explicit MyListView(QWidget *parent = 0);
protected:
// QWidget interface
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
private:
enum DragState {
DragStopped,
DragStarted,
Dragged
};
quint8 m_dragState;
int m_dragStartPos;
};
MyListView::MyListView(QWidget *parent)
: super(parent)
, m_dragState(DragStopped)
, m_dragStartPos(-1)
{
}
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
m_dragState = DragStarted;
m_dragStartPos = event->pos().y();
} else
super::mousePressEvent(event);
}
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
if(m_dragState) {
m_dragState = DragStopped;
m_dragStartPos = -1;
return;
}
super::mouseReleaseEvent(event);
}
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(m_dragState != DragStopped) {
const int itemSize = sizeHintForRow(0) / 2;
const int distance = qAbs(m_dragStartPos - event->pos().y());
if(distance > 10)
m_dragState = Dragged;
if(distance > itemSize) {
QScrollBar *scrollBar = this->verticalScrollBar();
int stepCount = (distance/itemSize);
if(m_dragStartPos < event->pos().y())
stepCount = -stepCount; //scrolling up
scrollBar->setValue(scrollBar->value() + (stepCount * scrollBar->singleStep()));
m_dragStartPos = event->y();
}
return;
}
super::mouseMoveEvent(event);
}

Moving a QGraphicsRectItem with mouse

I'm trying to move a QGraphicsRectItem after I add it to the scene. It moves, but appears with a certain offset from the mouse pointer. I think it is simply adding the mouse pointer position to its original position. I am at a loss as to how to work around this.
Here is my code:
class ucFilter : public QGraphicsItem {
std::shared_ptr<QGraphicsRectItem> m_rect;
std::shared_ptr<QGraphicsTextItem> m_text;
std::shared_ptr<QString> m_name;
std::shared_ptr<QPointF> m_pos;
QGraphicsItem* selectedItem;
bool m_mouseGrabbed;
public:
static const int default_x = 80, default_y=40;
ucFilter::ucFilter(QString &name, QPointF &pos){
m_name = shared_ptr<QString>(new QString(name));
m_pos = shared_ptr<QPointF>(new QPointF(pos));
m_rect = shared_ptr<QGraphicsRectItem>( new QGraphicsRectItem(pos.x()-default_x, pos.y()-default_y, 2*default_x, 2*default_y ));
m_text = shared_ptr<QGraphicsTextItem>( new QGraphicsTextItem(name));
m_text->setPos(pos.x() - m_text->boundingRect().width()/2, pos.y()- 30);
selectedItem = NULL;
m_mouseGrabbed = false;
}
QGraphicsRectItem* getRect() { return m_rect.get(); }
QGraphicsTextItem* getText() { return m_text.get(); }
QString* getName() { return m_name.get(); }
QPointF* getPos() { return m_pos.get(); }
void setPos(QPointF newPos) { m_pos->setX(newPos.x()); m_pos->setY(newPos.y()); }
QRectF ucFilter::boundingRect() const
{
return m_rect->boundingRect();
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
//QBrush brush(
if (!m_mouseGrabbed){ grabMouse(); m_mouseGrabbed = true; }
}
void mousePressEvent(QGraphicsSceneMouseEvent *event){
selectedItem = this;
QGraphicsItem::mousePressEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
selectedItem = NULL;
QGraphicsItem::mouseReleaseEvent(event);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(NULL != selectedItem){
m_text->setPos(event->pos());
m_rect->setPos(event->pos());
}
QGraphicsItem::mouseMoveEvent(event);
}
};
the ucFilter object is created in the scene dropEvent:
void cGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent * event){
QTreeView* source = static_cast<QTreeView*>(event->source());
string name = event->mimeData()->text().toUtf8().constData();
if(0 == name.length()){
event->acceptProposedAction();
return ; // nothing to do anymore
}
QPointF pos = event->scenePos ();
shared_ptr<ucFilter> newFilter = shared_ptr<ucFilter>(new ucFilter(event->mimeData()->text(),event->scenePos ()));
m_filters.push_back(newFilter);
this->addItem(newFilter->getRect());
this->addItem(newFilter->getText());
this->addItem(newFilter.get()); // also add the item to grab mouse events
event->acceptProposedAction();
}
Where could the problem be ?
Here a screenshot of what I'm actually seeing :
I would like the rectangle to be drawn where the mouse is..
You have several issues:
The use of shared pointers to hold everything is completely unwarranted. The scene acts as a container for items - just like QObject is a container for objects.
ucFilter doesn't have children. It holds pointers to other items, but that is unnecessary. The base item can be a rectangle itself, and it can have the text as a child. That way you don't need to handle positioning in a special fashion.
ucFilter can be movable. Don't reimplement that functionality yourself.
When you pass things by reference, pass them as const references unless you are intending to pass the modified value out. If you wish to change the value inside of the body of the function, you can pass it by value instead.
The mouse is already grabbed when you are dragging the item.
Let's start with the ucFilter item. It is really simple and does everything you need. Note that m_text is held by value, and is made a child of the rectangle parent.
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-item-drop-32574576
#include <QtWidgets>
class ucFilter : public QGraphicsRectItem {
QGraphicsTextItem m_text;
public:
ucFilter(const QString &name, const QPointF &pos, QGraphicsItem * parent = 0) :
QGraphicsRectItem(parent),
m_text(this)
{
static const QRect defaultRect(0, 0, 160, 80);
setPos(pos);
setRect(QRect(-defaultRect.topLeft()/2, defaultRect.size()));
setFlags(QGraphicsItem::ItemIsMovable);
setName(name);
}
void setName(const QString & text) {
m_text.setPlainText(text);
m_text.setPos(-m_text.boundingRect().width()/2, -30);
}
QString name() const {
return m_text.toPlainText();
}
};
Since we're dropping from a convenience widget (a QListWidget), we need to decode the text from a application/x-qabstractitemmodeldatalist mime type:
const char * kMimeType = "application/x-qabstractitemmodeldatalist";
QVariant decode(const QMimeData* data, Qt::ItemDataRole role = Qt::DisplayRole) {
auto buf = data->data(kMimeType);
QDataStream stream(&buf, QIODevice::ReadOnly);
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> map;
stream >> row >> col >> map;
if (map.contains(role)) return map[role];
}
return QVariant();
}
The scene creates an item as soon as a drag enters it, and moves the item during the drag.
Ownership of the items remains with the scene: we don't need to delete any items unless we want to explicitly remove them from the scene. The m_dragItem is used to refer to the currently dragged item simply to move it and add it to m_filters upon completion of the drop. It the drag leaves the scene (or is aborted), the item is simply deleted from the scene.
class cGraphicsScene : public QGraphicsScene {
QList<ucFilter*> m_filters;
ucFilter* m_dragItem;
public:
cGraphicsScene(QObject * parent = 0) : QGraphicsScene(parent), m_dragItem(nullptr) {}
void dragEnterEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!event->mimeData()->hasFormat(kMimeType)) return;
auto name = decode(event->mimeData()).toString();
if (name.isEmpty()) return;
QScopedPointer<ucFilter> filter(new ucFilter(name, event->scenePos()));
addItem(m_dragItem = filter.take());
event->acceptProposedAction();
}
void dragMoveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
event->acceptProposedAction();
}
void dropEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
m_filters << m_dragItem;
event->acceptProposedAction();
}
void dragLeaveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
delete m_dragItem;
m_dragItem = nullptr;
event->acceptProposedAction();
}
};
The test harness is very simple: our scene, a view displaying it, and a list with two items that you can drag onto the scene.
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
cGraphicsScene scene;
QGraphicsView view(&scene);
QListWidget list;
QHBoxLayout l(&w);
l.addWidget(&view);
l.addWidget(&list);
list.setFixedWidth(120);
list.addItem("Item1");
list.addItem("Item2");
list.setDragDropMode(QAbstractItemView::DragOnly);
view.setAcceptDrops(true);
w.resize(500, 300);
w.show();
return app.exec();
}
You can drag items from the list on the right to the scene on the left. You can also move the items within the scene, to relocate them.