How to interactively resize a QGraphicsObject? - c++

I'm having trouble figuring out how to scale a custom sub classed QGraphicsObject in Qt.
I have the code working so that the mouse cursor changes when hovering over the edges object's boundingRect(Left,Right,Top,Bottom).
I have the overloaded mouse events working properly.
And I have custom methods that are trying to change the boundingRect.
But the object refuses to change size.
As a quick and simple test.
I tried to change the size of the object in the paint() method using hard coded values. And what happens is that the object does change size. But the collisions are still using the old boundingRect size.
What seems to be tripping me up is that QGraphicsObject uses a 'const' boundingRect value. And I can't figure out how to deal with that.
ResizableObject::ResizableObject( QGraphicsItem *parent ): QGraphicsObject( parent ), mResizeLeft( false ), mResizeRight( false ), mResizeBottom( false ), mResizeTop( false )
{
setAcceptHoverEvents( true );
setFlags( ItemIsMovable | ItemIsSelectable );
}
QRectF ResizableObject::boundingRect() const
{
return QRectF(0 , 0 , 100 , 100);
}
void ResizableObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED( option )
Q_UNUSED( widget )
painter->setRenderHint(QPainter::Antialiasing);
//Determine whether to draw a solid, or dashed, outline border
if (isSelected() )
{
//Draw a dashed outlined rectangle
painter->setPen( Qt::DashLine );
painter->drawRect( boundingRect() );
}
else painter->drawRect(0,0,20,20); //Draw a normal solid outlined rectangle
//No collisions are happening
if(scene()->collidingItems(this).isEmpty())
{
//qDebug()<< "empty";
}
//Collisions are happening
else
{
foreach(QGraphicsItem *item, collidingItems())
{
qDebug()<< item->pos();
}
}
}
The only examples I can find use Qwidget or QGraphicsRectItem. But those are slightly different and the constructors are not constants.
Can anyone tell me how to change the size of these QGraphicsObjects with the mouse?

The paint method's only job is to paint. It can't be doing anything else - that includes attempting to change the object's size.
Instead of making the item itself resizable, let's see if we can have a more generic way of resizing any item, of any class. First, we may not wish all objects to be resizable. We need a way to figure which of them are. We also need a way to tell an item to resize - a QGraphicsItem doesn't offer a way to do that. In generic programming, one pattern used to provide such functionality is a traits class. This class implements the application-specific traits that will customize the generic resize behavior to our needs.
For this simple example, we allow any QGraphicsEllipseItems to be resizable. For your application, you can implement any logic you desire - without any subclassing. You can certainly create a base class for resizable items, and expose it via the traits, if you have many custom, resizable items. Even then, the traits class can still support items of other classes.
In all cases, the items to be resized must be selectable, and we exploit the selection mechanism to hook into the scene. This could be also done without using selections.
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-resizable-32416527
#include <QtWidgets>
class SimpleTraits {
public:
/// Determines whether an item is manually resizeable.
static bool isGraphicsItemResizeable(QGraphicsItem * item) {
return dynamic_cast<QGraphicsEllipseItem*>(item);
}
/// Gives the rectangle one can base the resize operations on for an item
static QRectF rectFor(QGraphicsItem * item) {
auto ellipse = dynamic_cast<QGraphicsEllipseItem*>(item);
if (ellipse) return ellipse->rect();
return QRectF();
}
/// Sets a new rectangle on the item
static void setRectOn(QGraphicsItem * item, const QRectF & rect) {
auto ellipse = dynamic_cast<QGraphicsEllipseItem*>(item);
if (ellipse) return ellipse->setRect(rect);
}
};
To implement a "wide rubber band" controller, we need a helper to tell what rectangle edges a given point intersects:
/// The set of edges intersecting a rectangle of given pen width
Qt::Edges edgesAt(const QPointF & p, const QRectF & r, qreal w) {
Qt::Edges edges;
auto hw = w / 2.0;
if (QRectF(r.x()-hw, r.y()-hw, w, r.height()+w).contains(p)) edges |= Qt::LeftEdge;
if (QRectF(r.x()+r.width()-hw, r.y()-hw, w, r.height()+w).contains(p)) edges |= Qt::RightEdge;
if (QRectF(r.x()-hw, r.y()-hw, r.width()+w, w).contains(p)) edges |= Qt::TopEdge;
if (QRectF(r.x()-hw, r.y()+r.height()-hw, r.width()+w, w).contains(p)) edges |= Qt::BottomEdge;
return edges;
}
Then, we have a "rubberband" resize helper item that tracks the selection on its scene. Whenever the selection contains one item, and the item is resizable, the helper makes itself a child of the item to be resized, and becomes visible. It tracks the mouse drags on the active edges of the rubber band, and resizes the parent items accordingly.
template <typename Tr>
class ResizeHelperItem : public QGraphicsObject {
QRectF m_rect;
QPen m_pen;
Qt::Edges m_edges;
void newGeometry() {
prepareGeometryChange();
auto parentRect = Tr::rectFor(parentItem());
m_rect.setTopLeft(mapFromParent(parentRect.topLeft()));
m_rect.setBottomRight(mapFromParent(parentRect.bottomRight()));
m_pen.setWidthF(std::min(m_rect.width(), m_rect.height()) * 0.1);
m_pen.setJoinStyle(Qt::MiterJoin);
}
public:
ResizeHelperItem() {
setAcceptedMouseButtons(Qt::LeftButton);
m_pen.setColor(QColor(255, 0, 0, 128));
m_pen.setStyle(Qt::SolidLine);
}
QRectF boundingRect() const Q_DECL_OVERRIDE {
auto hWidth = m_pen.widthF()/2.0;
return m_rect.adjusted(-hWidth, -hWidth, hWidth, hWidth);
}
void selectionChanged() {
if (!scene()) { setVisible(false); return; }
auto sel = scene()->selectedItems();
if (sel.isEmpty() || sel.size() > 1) { setVisible(false); return; }
auto item = sel.at(0);
if (! Tr::isGraphicsItemResizeable(item)) { setVisible(false); return; }
setParentItem(item);
newGeometry();
setVisible(true);
}
void paint(QPainter * p, const QStyleOptionGraphicsItem *, QWidget *) Q_DECL_OVERRIDE {
p->setPen(m_pen);
p->drawRect(m_rect);
}
void mousePressEvent(QGraphicsSceneMouseEvent * ev) Q_DECL_OVERRIDE {
m_edges = edgesAt(ev->pos(), m_rect, m_pen.widthF());
if (!m_edges) return;
ev->accept();
}
void mouseMoveEvent(QGraphicsSceneMouseEvent * ev) Q_DECL_OVERRIDE {
auto pos = mapToItem(parentItem(), ev->pos());
auto rect = Tr::rectFor(parentItem());
if (m_edges & Qt::LeftEdge) rect.setLeft(pos.x());
if (m_edges & Qt::TopEdge) rect.setTop(pos.y());
if (m_edges & Qt::RightEdge) rect.setRight(pos.x());
if (m_edges & Qt::BottomEdge) rect.setBottom(pos.y());
if (!!m_edges) {
Tr::setRectOn(parentItem(), rect);
newGeometry();
}
}
};
Finally, we create a simple scenario to try it all out:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QGraphicsScene scene;
QGraphicsView view { &scene };
typedef ResizeHelperItem<SimpleTraits> HelperItem;
HelperItem helper;
QObject::connect(&scene, &QGraphicsScene::selectionChanged, &helper, &HelperItem::selectionChanged);
scene.addItem(&helper);
auto item = scene.addEllipse(0, 0, 100, 100);
item->setFlag(QGraphicsItem::ItemIsSelectable);
view.setMinimumSize(400, 400);
view.show();
return app.exec();
}

I think you need to declare QRectF variable in somewhere and modify boundingRect() to simply return that one. And override mouse event handlers to modify that QRectF variable where you need (like mouseReleaseEvent() maybe,,)
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event)
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event)
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
And don't forget to call prepareGeometryChange() before changing that value to invalidate item's boundingRect() cache.

Related

Will paint(), redraw everything from scene or will redraw only updated items in Qt?

I have QGraphicsView which contains many QGraphicsItem such as Rectangle, polylines. I have overriden paint() method. I am drawing QGraphicsItem using boost-graph. While drawing items, I am storing boost-graph pointer on every QGraphicsItem.
Now I am right clicking on some rectangle from scene and trying to hide it. While hiding it, I am trying to hide lines connected to it also. For that, I am taking boost-graph pointer stored at every item and iterating through it.
In boost graph, there is a flag isVisible, through setting-resetting it, I am hidding-unhidding that item.
myView.cpp
while(true)
{
// iterating thorugh boost-graph and finding co-ordinates
myRect* _rect = new myRect(rect co-ordinates);
_rect->setBrush(Qt::yellow);
_rect->setPtr(boost-graph pointer);
_scene->addItem(static_cast<QGraphicsRectItem*>(_rect));
}
void myView::HideSelectedRectangle() // after choosing hide from right mouse click, control comes here
{
foreach(QGraphicsItem* currentItem, _scene->selectedItems())
{
myRect* rItem = qgraphicsitem_cast<myRect*>(currentItem);
if(rItem)
{
VertexDescriptor vPtr = rItem->getBoostPtr(); // getting boost-graph ptr
// logic for making it hide
// Question is how paint() will know about this QGraphicsItem that it is hidden?
};
}
}
myRect.h
class myRect: public QGraphicsRectItem
{
public:
explicit myRect();
explicit myRect(QRectF &rectPoints,QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(rectPoints,parent){}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void setPtr(VertexDescriptor);
VertexDescriptor getPtr();
VertexDescriptor boostPtr;
}
myRect.cpp
void myRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
QGraphicsRectItem::paint(painter, &copied_option, widget);
if(selected)
{
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(shape());
painter->restore();
}
}
void myRect::setPtr(VertexDescriptor vIter)
{
this->boostPtr = vIter;
}
VertexDescriptor myRect::getPtr()
{
return boostPtr;
}
Now assume I have made _isVisible = false (which is in boost-graph) for rectangle and some connected lines.
And now I want to redraw view using paint(). And expecting, paint() should not draw those
rectangle and lines which are marked as not visible.
While doing this :
Is paint() redraw every QGraphicsItem from view or it will redraw only
those were updated ?
How paint() will know, which shape should it draw and its co-ordinates
?
Is it possible in my paint(), by checking QGraphicsItem's flag (isVisible) I can guide paint() which items to redraw and which not to
redraw ?

How to keep the size and position of QGraphicsItem when scaling the view?

I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:
I have subclassed QGraphicsView like this:
class Graphics : public QGraphicsView
{
public:
Graphics();
QGraphicsScene *scene;
QGraphicsRectItem *rect;
QGraphicsRectItem *rect2;
protected:
void wheelEvent(QWheelEvent *event);
};
And in its constructor:
Graphics::Graphics()
{
scene = new QGraphicsScene;
rect = new QGraphicsRectItem(100,100,50,50);
rect2 = new QGraphicsRectItem(-100,-100,50,50);
scene->addLine(0,200,200,0);
rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(rect);
scene->addItem(rect2);
setScene(scene);
scene->addRect(scene->itemsBoundingRect());
}
The wheelEvent virtual function:
void Graphics::wheelEvent(QWheelEvent *event)
{
if(event->delta() < 0)
scale(1.0/2.0, 1.0/2.0);
else
scale(2, 2);
scene->addRect(scene->itemsBoundingRect());
qDebug() << rect->transform();
qDebug() << rect->boundingRect();
qDebug() << rect2->transform();
qDebug() << rect2->boundingRect();
}
orginal view looks like this:
1
take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene:
2
which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!
Sorry for delay, now I've solved the problem myself.
I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .
QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
QMatrix newMatrix = matrix;
qreal scaleX, scaleY;
scaleX = newMatrix.m11();
scaleY = newMatrix.m22();
newMatrix.scale(1.0/scaleX, 1.0/scaleY);
qreal offsetX, offsetY;
offsetX = p.x()*(scaleX-1.0);
offsetY = p.y()*(scaleY-1.0);
newMatrix.translate(offsetX, offsetY);
return newMatrix;
}
And the paint function:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPointF p(left, top);
painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
painter->drawRect(left, top, width, height);
}
The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine!
Hope this post help :)
The solution to this is even simpler.
QGraphicsItem::ItemIgnoresTransformations
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). [...]
And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.
Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:
#include <QGraphicsItem>
#include <QPainter>
class CrossHair : public QGraphicsItem
{
private:
class CrossHairImpl : public QGraphicsItem
{
public:
CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_len(len)
{
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
QRectF boundingRect (void) const override
{
return QRectF(-m_len, -m_len, m_len*2, m_len*2);
}
void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->setPen(QPen(Qt::red, 1));
painter->drawLine(0, -m_len, 0, m_len);
painter->drawLine(-m_len, 0, m_len, 0);
}
private:
qreal m_len;
};
public:
CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
{
setPos(x, y);
}
QRectF boundingRect (void) const override
{
return QRectF();
}
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
{
// empty
}
private:
CrossHairImpl m_impl;
};

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.

Qt moving an Object in a specific Region

I'm new to Qt programming. So far I made a QGraphicsScene draw some rectangles (looks like a Chess Board) and then added an own QGraphicsItem to the scene. For this item I set the ItemIsMovable flag. Now I can move it but I would like to restrict the movement to the area where the Chess Board is.
Would I have to unset the flag and realize the movement manually or is there like an option or flag where I can specify the area it can be moved in ?
renderableObject::renderableObject(QObject */*parent*/)
{
pressed = false;
setFlag(ItemIsMovable);
}
You can reimplement the QGraphicsItem's itemChange() if you want to restrict movable area. Repositioning the item will cause itemChange to get called. You should note that ItemSendsGeometryChanges flag is needed to capture the change in position of QGraphicsItem.
class MyItem : public QGraphicsRectItem
{
public:
MyItem(const QRectF & rect, QGraphicsItem * parent = 0);
protected:
virtual QVariant itemChange ( GraphicsItemChange change, const QVariant & value );
};
MyItem::MyItem(const QRectF & rect, QGraphicsItem * parent )
:QGraphicsRectItem(rect,parent)
{
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges);
}
QVariant MyItem::itemChange ( GraphicsItemChange change, const QVariant & value )
{
if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
This will keep item moving limited to scene rect. You can define any arbitrary rect if you like.

Context Menu on QGraphicsWidget

In my application I have two object type. One is field item, other is composite item.
Composite items may contain two or more field items.
Here is my composite item implementation.
#include "compositeitem.h"
CompositeItem::CompositeItem(QString id,QList<FieldItem *> _children)
{
children = _children;
}
CompositeItem::~CompositeItem()
{
}
QRectF CompositeItem::boundingRect() const
{
FieldItem *child;
QRectF rect(0,0,0,0);
foreach(child,children)
{
rect = rect.united(child->boundingRect());
}
return rect;
}
void CompositeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
{
FieldItem *child;
foreach(child,children)
{
child->paint(painter,option,widget);
}
}
QSizeF CompositeItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
QSizeF itsSize(0,0);
FieldItem *child;
foreach(child,children)
{
// if its size empty set first child size to itsSize
if(itsSize.isEmpty())
itsSize = child->sizeHint(Qt::PreferredSize);
else
{
QSizeF childSize = child->sizeHint(Qt::PreferredSize);
if(itsSize.width() < childSize.width())
itsSize.setWidth(childSize.width());
itsSize.setHeight(itsSize.height() + childSize.height());
}
}
return itsSize;
}
void CompositeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
qDebug()<<"Test";
}
My first question is how I can propagate context menu event to specific child.
Picture on the above demonstrates one of my possible composite item.
If you look on the code above you will see that I print "Test" when context menu event occurs.
When I right click on the line symbol I see that "Test" message is printed.
But when I right click on the signal symbol "Test" is not printed and I want it to be printed.
My second question what cause this behaviour.
How do I overcome this.
Could you try reimplementing your contextMenu with the mouseRelease event instead?
void CompositeItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button() == Qt::RightButton)
{
contextMenu->popup(QCursor::pos());
}
}
I figured out that there may be two solutions for catching events.
First one is reimplementing shape function.
In my case it will be implemented like this.
QPainterPath shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
Second one is to use QGraphicsItemGroup
It will be good idea to use QGraphicsItemGroup if you diretly adding your items to scene. But in my case I have to subclass QGraphicsItemGroup because of I am using a layout.
So temporarily I choose writing my own item.