Context Menu on QGraphicsWidget - c++

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.

Related

Error in typecasting of QGraphicsItemGroup in Qt

I am trying to create a digital gates symbols ( like AND,NAND etc ) using line, arc, circle. To do this I am using QGraphicsItemGroup also.
For example. If a symbol contains 3 lines, 2 arc and 1 circle, then I add everything in object of QGraphicsItemGroup and then I add this object in scene.
myGroupItem.h
class myGroupItem: public QGraphicsItemGroup
{
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
}
myGroupItem.cpp
void myGroupItem ::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;
QGraphicsItemGroup::paint(painter, &copied_option, widget);
if (selected)
{
foreach (QGraphicsItem* item, _scene->selectedItems())
{
myGroupItem* inst = qgraphicsitem_cast<myGroupItem*>(item);
if (!inst)
continue;
QList<QGraphicsItem*> childItems = inst->childItems();
foreach (QGraphicsItem* chItem, childItems)
{
if (myPoly* poly = dynamic_cast<myPoly*>(chItem))
{
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(poly->shape());
painter->restore();
}
} else {
if (myLine* line = dynamic_cast<myLine*>(chItem)) {
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(line->shape());
painter->restore();
} else {
if (myEllipse* ell = dynamic_cast<myEllipse*>(chItem)) {
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(ell->shape());
painter->restore();
}
}
}
}
}
}
}
myLine.h
class myLine : public QGraphicsLineItem
{
}
myEllipse.h
class myEllipse : public QGraphicsEllipseItem
{
}
class BuildDesign()
{
myGroupItem* groupItem = new myGroupItem();
// accessing all lines
myLine* line = new myLine(co-ordinate of lines);
groupItem ->addToGroup(line);
// accessing arc
groupItem ->addToGroup(arc);
// accessing circle
groupItem ->addToGroup(ellipse);
scene->addItem(groupItem);
}
This way I am creating a symbol and it is creating propely. Issue is when I want to select that
symbol and process on it.
// This function will check is there any item under mouse click. If yes then call another
// function to highlight it and if not then Refresh the scene.
void BuildDesign::SelectObject(QEvent* event)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF mousePoint = _view->mapToScene(mouseEvent->pos());
QGraphicsItem* itemUnderClick = _scene->itemAt(mousePoint, QTransform());
if (!itemUnderClick)
RefreshScene();
else
HighlightSelectedObject(itemUnderClick);
}
void BuildDesign::HighlightSelectedObject(QGraphicsItem* itemUnderClick)
{
// QGraphicsItemGroup* gItem = qgraphicsitem_cast<QGraphicsItemGroup*>(itemUnderClick);
myGroupItem* gItem = qgraphicsitem_cast<myGroupItem*>(itemUnderClick);
if (gItem) {
gItem->setSelected(true); // this never gets executed
}
}
Now after debugging I found that, itemUnderClick has some address but after type casting to gItem always shows address as 0x0.
I want to execute gItem->setSelected(true); ( which never execute ) and further I want to use
foreach (QGraphicsItem* currentItem, _scene->selectedItems()) and currentItem should be that selected symbol. But it is not happening.
As stated by the doc (https://doc.qt.io/qt-6/qgraphicsitem.html#qgraphicsitem_cast), qgraphicsitem_cast returns nullptr if the given item is not of type T, OR, if it cannot deduce its type.
The last case is the cause of your issue here, you have to reimplement the type() function for each custom QGraphicsItem subclass.
The following link shows how to do this, https://www.qtcentre.org/threads/54530-qgraphicsitem_cast-cannot-convert-seft-defined-types , see the answer of Santosh Reddy.

How to paint object by overriding paintEvent in Qt?

In my QGraphicsView, I have many QGraphicsItem. I want to search specific QGraphicsItem out of all the items present in view and highlight the matched item. For highlighting item, I am trying to use paintEvent.
So I am calling paintEvent, but not understanding how to heighlight border of the matched object ?
Should I need co-ordinates of that matched object ?
I tried like this:
foreach(QGraphicsItem* currentItem, _scene->items())
{
pEvent = false;
QGraphicsRectItem* rItem = qgraphicsitem_cast<QGraphicsRectItem*>(currentItem);
if(rItem)
{
// some logic to get i->Name()
QString name1 = i->Name();
QString name2 = "reN"; // I want to find reN named item in view
if(name1 == name2)
{
pEvent = true;
qDebug()<<"Object Found ";
this->repaint();
break;
}
}
}
void myClass::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
qDebug()<<"In paint event ";
if(pEvent)
{
QPainter qp(this);
drawBody(&qp);
}
}
void myClass::drawBody(QPainter *qp) {
Q_UNUSED(qp);
// want logic for heighlighting border of the item
}
I understand from your problem that you are overridng QGraphicsView in MyClass since paintEnent is defined QWidgets classes.
To solve your problem, if I understand correctly. It's necessary to save the QGraphicsRectItem coordinates:
QRectF rectF = item.boundingRect();
then use the following in the function drawBody:
qp.save();
const float width = 5;
QPen pen;
pen.setWidth(width );
pen.setColor("green");
painter->drawRect(rectF.x(), rectF().y(),
rectF().width(), rectF().height());
qp.restore();

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.

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.

How to interactively resize a QGraphicsObject?

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.