I need to implement a "Loading..." window in my application but I do prefer to cover the whole QMainWindow with a dark transparent layer with a text above. Does anybody know how to do that? I am not sure how to overlap widgets/layouts in Qt. Any help will be appreciated.
This answer is in a series of my overlay-related answers: first, second, third.
The most trivial solution is to simply add a child transparent widget to QMainWindow. That widget must merely track the size of its parent window. It is important to properly handle changes of widget parentage, and the z-order with siblings. Below is a correct example of how to do it.
If you want to stack overlays, subsequent overlays should be the children of OverlayWidget, in the z-order. If they were to be siblings of the OverlayWidget, their stacking order is undefined.
This solution has the benefit of providing minimal coupling to other code. It doesn't require any knowledge from the widget you apply the overlay to. You can apply the overlay to a QMainWindow or any other widget, the widget can also be in a layout.
Reimplementing QMainWindow's paint event would not be considered the best design. It makes it tied to a particular class. If you really think that a QWidget instance is too much overhead, you better had measurements to show that being the case.
It is possible, of course, to make the overlay merely a QObject and to put the painting code into an event filter. That'd be an alternative solution. It's harder to do since you have to also properly deal with the parent widget's Qt::WA_StaticContents attribute, and with the widget potentially calling its scroll() method. Dealing with a separate widget is the simplest.
// https://github.com/KubaO/stackoverflown/tree/master/questions/overlay-widget-19362455
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class OverlayWidget : public QWidget
{
void newParent() {
if (!parent()) return;
parent()->installEventFilter(this);
raise();
}
public:
explicit OverlayWidget(QWidget * parent = {}) : QWidget{parent} {
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
newParent();
}
protected:
//! Catches resize and child events from the parent widget
bool eventFilter(QObject * obj, QEvent * ev) override {
if (obj == parent()) {
if (ev->type() == QEvent::Resize)
resize(static_cast<QResizeEvent*>(ev)->size());
else if (ev->type() == QEvent::ChildAdded)
raise();
}
return QWidget::eventFilter(obj, ev);
}
//! Tracks parent widget changes
bool event(QEvent* ev) override {
if (ev->type() == QEvent::ParentAboutToChange) {
if (parent()) parent()->removeEventFilter(this);
}
else if (ev->type() == QEvent::ParentChange)
newParent();
return QWidget::event(ev);
}
};
class LoadingOverlay : public OverlayWidget
{
public:
LoadingOverlay(QWidget * parent = {}) : OverlayWidget{parent} {
setAttribute(Qt::WA_TranslucentBackground);
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.fillRect(rect(), {100, 100, 100, 128});
p.setPen({200, 200, 255});
p.setFont({"arial,helvetica", 48});
p.drawText(rect(), "Loading...", Qt::AlignHCenter | Qt::AlignVCenter);
}
};
int main(int argc, char * argv[])
{
QApplication a{argc, argv};
QMainWindow window;
QLabel central{"Hello"};
central.setAlignment(Qt::AlignHCenter | Qt::AlignTop);
central.setMinimumSize(400, 300);
LoadingOverlay overlay{¢ral};
QTimer::singleShot(5000, &overlay, SLOT(hide()));
window.setCentralWidget(¢ral);
window.show();
return a.exec();
}
I would suggest execute a modal, frameless dialog on top and add a graphics effect on the background widget.
This is IMHO a very flexible and short solution without touching the event system directly.
The performance might be bad - one could improve that by calling drawSource(), but I haven't a reliable solution here yet.
class DarkenEffect : public QGraphicsEffect
{
public:
void draw( QPainter* painter ) override
{
QPixmap pixmap;
QPoint offset;
if( sourceIsPixmap() ) // No point in drawing in device coordinates (pixmap will be scaled anyways)
pixmap = sourcePixmap( Qt::LogicalCoordinates, &offset );
else // Draw pixmap in device coordinates to avoid pixmap scaling;
{
pixmap = sourcePixmap( Qt::DeviceCoordinates, &offset );
painter->setWorldTransform( QTransform() );
}
painter->setBrush( QColor( 0, 0, 0, 255 ) ); // black bg
painter->drawRect( pixmap.rect() );
painter->setOpacity( 0.5 );
painter->drawPixmap( offset, pixmap );
}
};
// prepare overlay widget
overlayWidget->setWindowFlags( Qt::FramelessWindowHint | Qt::Dialog | Qt::WindowStaysOnTopHint );
// usage
parentWidget->setGraphicsEffect( new DarkenEffect );
overlayWidget->exec();
parentWidget->setGraphicsEffect( nullptr );
If you are talking about using a separate layout/widget over your 'Main Window', you could just make the "Loading..." window modal either through the UI editor or in the constructor for your UI.
Related
I've got four QLineEdit placed inside of a QLineEdits, where I want the first the parent to look as if it is in focus when any of the containing ones is selected. Note: I don't want the focus to actually change, just the "focus frame" (the thin blue border) to appear on the parent LineEdit.
I've tried to draw a rect, but while it works on Windows I'm running into issues of the drawn rectangle not looking like a proper rectangle on ex. Linux, where it is supposed to be rounded. Is there a way to fix this OR, if possible, just make it draw itself as focused despite focus not being on it?
Here's my attempt at drawing a custom rect, but haven't been able to make it successfully mirror the OS style properly.
if (childHasFocus) {
QPainter painter(this);
QLineEdit textBox;
QColor color = textBox.palette().color(QPalette::Highlight);
painter.setPen(color);
QRect rect;
rect.setTopLeft(QPoint(0,0));
rect.setWidth(this->width() - 1);
rect.setHeight(this->height() - 1);
painter.drawRect(rect);
}
EDIT: Added an image of the desired look. Note that I'm trying to get it to look like other LineEdits focusframe independent of OS, so hardcoding a blue rectangle won't work due to ex. Linux having a rounded focusframe.
Desired look:
Here's how to do it. Its a very basic class that draws the focus frame if any of the childs have focus. On focus change, we do an update (which can probably be optimized a bit to avoid unnecessary repaints).
Screenshot:
class IPEdit : public QWidget
{
public:
IPEdit(QWidget *parent = nullptr)
: QWidget(parent)
{
delete layout();
auto l = new QHBoxLayout(this);
setFocusProxy(&a);
setAttribute(Qt::WA_Hover);
for (auto *w : {&a, &b, &c, &d}) {
l->addWidget(w);
w->installEventFilter(this);
}
}
bool eventFilter(QObject *o, QEvent *e) override
{
if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
update();
}
return QWidget::eventFilter(o, e);
}
void paintEvent(QPaintEvent *e) override
{
QStyleOptionFrame opt;
opt.initFrom(this);
opt.frameShape = QFrame::StyledPanel;
opt.state |= QStyle::State_Sunken;
// clear mouseOver and focus state
// update from relevant widgets
opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
const auto widgets = {&a, &b, &c, &d};
for (const QWidget *w : widgets) {
if (w->hasFocus()) {
opt.state |= QStyle::State_HasFocus;
}
}
opt.rect = contentsRect();
QPainter paint(this);
paint.setClipRegion(e->region());
paint.setRenderHints(QPainter::Antialiasing);
style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this);
}
private:
QLineEdit a;
QLineEdit b;
QLineEdit c;
QLineEdit d;
};
QlineEdit class is also a qwidget, use the setFocus method
https://doc.qt.io/qt-6/qwidget.html#setFocus
I'm under Ubuntu 18.04 and using last QtCreator with last Qt5 framework
I'm trying to setup a vertical layout with at top an horizontal nested layout and under a custom made widget (based on clock exemple for the moment)
I setup a fresh widget project but as I dont understand yet how to get acess to nested layout from c++ code I removed automaticaly created mainform and created layout at execution.
If I use my custom widget as window it works if I use a window and add my custom widget it works but if I add a layout, add my custom widget to this layout and call setLayout on window it disappear...
I tried almost all orders : set the layout first or after adding my widget.
I tried to call show() on my widget or not befor or after adding it to layout
I tried to add the nested layer first or last nothing change
I 've read several time exemples and manual about layout and nested one
I can see my nested layout but not my widget
here's my main :
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setWindowTitle("WOUND : Windings Organiser with Usefull and Neat Drawings");
QVBoxLayout* hl=new QVBoxLayout;// top vertical layout
QHBoxLayout* slotqueryLayout=new QHBoxLayout; // nested first horizontal layout
QLabel *slotqueryLabel = new QLabel("Slot number:");
QLineEdit *slotqueryEdit = new QLineEdit("48");
slotqueryLayout->addWidget(slotqueryLabel);
slotqueryLayout->addWidget(slotqueryEdit);
hl->addLayout(slotqueryLayout);
WindingViewer* wv=new WindingViewer(&w); // my widget is just a simple canvas to draw things
hl->addWidget(wv);
wv->show(); // dont know if it's needed but if I remove it it dont change anything / tried to do it before or after adding to layout
w.setLayout(hl); // if called before adding content it dont change
w.show();
return a.exec();
}
and here you can find my custom widget:
class WindingViewer : public QWidget
{
Q_OBJECT
public:
explicit WindingViewer(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
signals:
public :
int SlotNumber;
public slots:
};
and
WindingViewer::WindingViewer(QWidget *parent) : QWidget(parent)
{
SlotNumber=3;
resize(200, 200);
}
void WindingViewer::paintEvent(QPaintEvent *)
{
int side = qMin(width(), height());
QColor SlotColor(127, 127, 127);
QPainter painter(this);
static const QPoint slotpolygonext[4] = {
QPoint(-2,85),
QPoint(-3,95),
QPoint(3, 95),
QPoint(2, 85)
};
static const QPoint slotpolygonint[5] = {
QPoint(-1,75),
QPoint(-2,85),
QPoint(2, 85),
QPoint(1, 75),
QPoint(-1,75),
};
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0);
painter.setPen(SlotColor);
for (int i = 0; i < SlotNumber; ++i) {
painter.drawPolyline(slotpolygonext,4);
painter.drawPolyline(slotpolygonint,5);
painter.rotate(360.0/SlotNumber);
}
}
I hope the question is clear enough. I've search for an answer here and over internet before posting. I found few things but nothing totaly related.
The custom widget is part of the window, you could see if manually with the mouse you make the height increase.
And then why is it hidden?
The layouts handle size policies and use the sizeHint() function of the widgets to obtain the default size, and in your case the sizeHint() is not implemented because it is not observed when the window is displayed, the solution is to implement that method:
*.h
public:
explicit WindingViewer(QWidget *parent = nullptr);
QSize sizeHint() const override;
*.cpp
QSize WindingViewer::sizeHint() const
{
return QSize(200, 200);
}
I've been struggling with this problem for the past couple of days. I want to be able to grow and shrink whatever the assigned Pixmap is in a QLabel as the user resizes the window. The issue is preserving the aspect ratio and image quality. Another user on here suggested that I reimplement the paint event for the label - but I'm still very lost. I'm not even sure if I have overridden the paintEvent correctly. I would kill for a bit of sample code here.
This is where I'm at:
void MyLabel::paintEvent(QPaintEvent * event)
{
//if this widget is assigned a pixmap
//paint that pixmap at the size of the parent, aspect ratio preserved
//otherwise, nothing
}
Here is a possible implementation of a QLabel subclass that scales its pixmap content while keeping its aspect ratio. It is implemented based on the way QLabel::paintEvent is implemented.
If you are using it in a layout you can also set its size policy to QSizePolicy::Expanding, so that additional space in the layout is taken up by the QLabel for a larger display of the pixmap content.
#include <QApplication>
#include <QtWidgets>
class PixmapLabel : public QLabel{
public:
explicit PixmapLabel(QWidget* parent=nullptr):QLabel(parent){
//By default, this class scales the pixmap according to the label's size
setScaledContents(true);
}
~PixmapLabel(){}
protected:
void paintEvent(QPaintEvent* event);
private:
//QImage to cache the pixmap()
//to avoid constructing a new QImage on every scale operation
QImage cachedImage;
//used to cache the last scaled pixmap
//to avoid calling scale again when the size is still at the same
QPixmap scaledPixmap;
//key for the currently cached QImage and QPixmap
//used to make sure the label was not set to another QPixmap
qint64 cacheKey{0};
};
//based on the implementation of QLabel::paintEvent
void PixmapLabel::paintEvent(QPaintEvent *event){
//if this is assigned to a pixmap
if(pixmap() && !pixmap()->isNull()){
QStyle* style= PixmapLabel::style();
QPainter painter(this);
drawFrame(&painter);
QRect cr = contentsRect();
cr.adjust(margin(), margin(), -margin(), -margin());
int align= QStyle::visualAlignment(layoutDirection(), alignment());
QPixmap pix;
if(hasScaledContents()){ //if scaling is enabled
QSize scaledSize= cr.size() * devicePixelRatioF();
//if scaledPixmap is invalid
if(scaledPixmap.isNull() || scaledPixmap.size()!=scaledSize
|| pixmap()->cacheKey()!=cacheKey){
//if cachedImage is also invalid
if(pixmap()->cacheKey() != cacheKey){
//reconstruct cachedImage
cachedImage= pixmap()->toImage();
}
QImage scaledImage= cachedImage.scaled(
scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
scaledPixmap= QPixmap::fromImage(scaledImage);
scaledPixmap.setDevicePixelRatio(devicePixelRatioF());
}
pix= scaledPixmap;
} else { // no scaling, Just use pixmap()
pix= *pixmap();
}
QStyleOption opt;
opt.initFrom(this);
if(!isEnabled())
pix= style->generatedIconPixmap(QIcon::Disabled, pix, &opt);
style->drawItemPixmap(&painter, cr, align, pix);
} else { //otherwise (if the label is not assigned to a pixmap)
//call base paintEvent
QLabel::paintEvent(event);
}
}
//DEMO program
QPixmap generatePixmap(QSize size) {
QPixmap pixmap(size);
pixmap.fill(Qt::white);
QPainter p(&pixmap);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(QPen(Qt::black, 10));
p.drawEllipse(pixmap.rect());
p.setPen(QPen(Qt::red, 2));
p.drawLine(pixmap.rect().topLeft(), pixmap.rect().bottomRight());
p.drawLine(pixmap.rect().topRight(), pixmap.rect().bottomLeft());
return pixmap;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPixmap pixmap= generatePixmap(QSize(1280, 960));
PixmapLabel label;
label.setPixmap(pixmap);
label.setAlignment(Qt::AlignCenter);
label.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
label.setMinimumSize(320, 240);
label.show();
return a.exec();
}
I think this is better than the solution in this answer, as the QLabel here takes care of resizing its pixmap. So, there is no need to resize it manually every time the parent widget is resized and everytime a new pixmap is set on it.
First of all I wanted to say thanks to Mike.
Additionally I would like to add to his answer from 21.10.2016 - however I am not able to add a comment as of yet - thus here is a full fledged answer. [If anyone is able to move this to the comment section, feel free.]
I also added an overwrite of the other constructor of QLabel and the window flags [with default arguments as in QLabel] and added the Q_OBJECT macro, as to be compatible to the Qt framework:
Header:
class PixmapLabel : public QLabel
{
Q_OBJECT
public:
explicit PixmapLabel(QWidget* parent=nullptr, Qt::WindowFlags f = Qt::WindowFlags());
explicit PixmapLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
...
}
Module:
PixmapLabel::PixmapLabel(QString const & text, QWidget * parent, Qt::WindowFlags f) :
QLabel(text, parent, f)
{
//By default, this class scales the pixmap according to the label's size
setScaledContents(true);
}
I have a project where i want to draw a point into a image inside a QPixmap. The point would be draw with the mouse click on the QLabel. I created a eventFilter() which corresponds to mouse click. When I click with the mouse, these eventFilter is called and draw a point in the image, but my code doesn't works. I tried many others options like subclassing the QLabel, but didn't work either.
And sometimes my compiler shows these error messages:
QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::setPen: Painter not active
QPainter::drawPoints: Painter not active
QPainter::end: Painter not active, aborted
but I don't understand, because the Qt documentation says that is allowed use the QPainter outside of paintEvent just using with QPixmap.
Below is my code with the method that starts the QPainter.
bool mainwindow::eventFilter(QObject* watched, QEvent* event) {
if ( watched != ui->labelScreen )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
const QMouseEvent* const me = static_cast<const QMouseEvent*>( event );
//might want to check the buttons here
const QPoint p = me->pos(); //...or ->globalPos();
ui->label_Xget->setNum(this->xReal);
ui->label_Yget->setNum(this->yReal);
///////////////////////////////////
QPixmap pix;
pix.fromImage(QImage::fromData("C:/Users/Syn/Pictures/imagem137.jpg"));
QPainter *painter = new QPainter(&pix);
painter->setPen(Qt::red);
painter->drawPoint(p.x(), p.y());
ui->labelScreen->setPixmap(pix);
painter->end();
///////////////////////////////////
return false;
}
Someone would can help me solve this problem? Thanks.
The error messages are not from your compiler, they happen at runtime, and you can't be sure whether the code you quote above is their cause.
There are several problem:
QPixmap::fromImage is a static method that returns a pixmap. You're ignoring its return value. The correct way to use it would be:
// C++11
auto pix = QPixmap::fromImage(QImage{"filename"});
// C++98
QPixmap pix(QPixmap::fromImage(QImage("filename")));
You can pass the filename directly to QPixmap constructor:
// C++11
QPixmap pix{"filename"};
// C++98
QPixmap pix("filename");
You're leaking the painter instance. You should avoid any dynamic memory allocation unless you truly need it. Also, store ui by value - it's cheaper that way.
It is a very bad idea to do any blocking I/O, such as reading a file, in event handlers. Preload and store the pixmap as a member of the MainWindow class.
Thus (in C++11):
Interface
template <class F>
class MainWindow : public QMainWindow {
QPixmap m_clickPixmap;
Ui::MainWindow ui;
bool eventFilter(QObject*, QEvent*) override;
public:
MainWindow(QWidget * parent = nullptr);
};
Implementation
void loadImage(const QString & fileName, QObject * context, F && functor) {
QtConcurrent::run([=]{
QImage img{fileName};
if (img.isNull()) return;
QTimer::singleShot(0, context, functor, img);
});
}
MainWindow::MainWindow(QWidget * parent) :
QMainWindow{parent}
{
loadImage("C:/Users/Syn/Pictures/imagem137.jpg", this, [this](const QImage & img){
m_clickPixmap = QPixmap::fromImage(img);
});
ui.setupUi(this);
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
if ( watched != ui.labelScreen )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
auto const me = static_cast<const QMouseEvent*>(event);
auto const p = me->pos(); //...or ->globalPos();
ui.label_Xget->setNum(this->xReal);
ui.label_Yget->setNum(this->yReal);
auto pix{m_clickPixmap};
QPainter painter(&pix);
painter.setPen(Qt::red);
painter.drawPoint(p.x(), p.y());
painter.end(); // probably not needed
ui.labelScreen->setPixmap(pix);
return false;
}
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.