My application has a QMdiArea, inside which subwindows are shown which contain instances of QGraphicsView-derived views (GfxInteractiveView), which in turn visualize scenes containing custom QGraphicsItem-derived items.
/// An image item which is displayed as the background of a scene.
class GfxImageItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
protected:
GfxInteractiveImgView *view_;
QPixmap pixmap_;
QList<GfxPointItem *> pointItems_;
public:
GfxImageItem(GfxInteractiveImgView *view);
// set the pixmap to the image loaded from this path
bool load(const QString &path);
// normally not overriden, here just for tracing
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QLOG("Painting image"); // homebrew macro for tracing
QGraphicsPixmapItem::paint(painter, option, widget);
}
};
/// A generated image drawn at the foreground of a scene.
class GfxMapItem : public QGraphicsPixmapItem
{
public:
GfxMapItem(QGraphicsItem *item);
void regenerateMap();
// same as GfxMapItem, here just for tracing
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
};
So far so good, but I also have an another item GfxPointItem which is fully custom with a paint() that actually does something, and when I add it to the scene, CPU usage jumps to full on one of the cores as all the items in the hierarchy enter a repaint loop which continues for as long the window is visible or the custom item is present in the scene. A glimpse at the stack when this happens shows that none of my function is responsible for calling paint(), the event is generated on the event loop. Here's the code for GfxPointItem:
/// A draggable item representing an analysis point on the map, drawn on top of the map.
class GfxPointItem : public QGraphicsObject
{
Q_OBJECT
protected:
GfxImageItem *imgParent_;
GfxInteractiveImgView *view_;
int size_, fontSize_;
QColor color_, oldColor_;
qreal paintScale_;
QRectF boundingRect_;
bool active_, static_;
QStaticText pointText_, valueText_;
public:
GfxPointItem(GfxImageItem *parent, GfxInteractiveImgView *view, const QPointF &pos);
void setActive(bool arg);
void setStatic(bool arg);
void setColor(const QColor &color) { color_ = color; update(); }
virtual QRectF boundingRect() const { return boundingRect_; }
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
signals:
void changedPos(int index, QPointF newpos);
public slots:
void setPaintScale(qreal value);
void setPointText(const QString &text);
void setValueText(const QString &text);
protected:
void updateBoundRect();
void updatePointText();
QPoint valueTextPos() const;
QPoint pointTextPos() const;
};
GfxPointItem::GfxPointItem(GfxImageItem *parent, GfxInteractiveImgView *view, const QPointF &pos, int index) : QGraphicsObject(parent),
imgParent_(parent),
view_(view),
index_(index), size_(8), fontSize_(8),
color_(Qt::black),
paintScale_(view->invscale()),
drawLabel_(true), active_(false), static_(false), floatPrec_(false)
{
QLOGX("Creating new at " << pos.x() << "," << pos.y() << ", index: " << index);
setPos(pos);
updatePointText();
connect(view, SIGNAL(scaleChanged(qreal)), this, SLOT(setPaintScale(qreal)));
}
/// An inactive point wil not respond to hover events and will not be movable.
void GfxPointItem::setActive(bool arg)
{
QLOGX("Setting active state: " << arg);
active_ = arg;
setAcceptHoverEvents(arg);
setFlag(QGraphicsItem::ItemIsMovable, arg);
}
/// Set or disable static mode on point. In static mode, the point text is not updated when changing position, so it can retain a constant label.
void GfxPointItem::setStatic(bool arg)
{
QLOGX("Setting static mode: " << arg);
static_ = arg;
}
void GfxPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QLOGX("Painting point");
static const int margin = 2;
setScale(paintScale_);
QPen pen;
pen.setWidth(1);
pen.setColor(color_);
painter->setPen(pen);
// paint the centerpoint marker (two crossed lines)
painter->drawLine(QPointF(-size_, 0), QPointF( size_, 0));
painter->drawLine(QPointF(0, -size_), QPointF(0, size_));
// the label box and the two static text lines inside
pen.setWidth(0);
painter->setPen(pen);
QFont font;
font.setPointSize(fontSize_);
painter->setFont(font);
QBrush brush(Qt::SolidPattern);
brush.setColor(QColor(255, 255, 127)); // sand yellow
painter->setBrush(brush);
// point text size, value text size
QSizeF pts = pointText_.size(),
vts = valueText_.size();
// point text position, value text position
QPoint vtp = valueTextPos(),
ptp = pointTextPos();
// point id and position label and value indicator label in a rectangular box
int shift = (valueText_.text().isEmpty()) ? 0 : vts.height();
QRectF rect(ptp.x()-margin, ptp.y(), std::max(pts.width(), vts.width())+margin, pts.height() + shift);
painter->drawRect(rect);
painter->drawStaticText(ptp, pointText_);
painter->drawStaticText(vtp, valueText_);
}
void GfxPointItem::setPaintScale(qreal value)
{
QLOGX("Updating scale: " << value);
paintScale_ = value;
update();
}
void GfxPointItem::setPointText(const QString &text)
{
QLOGX("Setting text: " << text);
pointText_.setText(text);
updateBoundRect();
update();
}
void GfxPointItem::setValueText(const QString &text)
{
QLOGX("Setting value text: " << text);
valueText_.setText(text);
updateBoundRect();
update();
}
void GfxPointItem::updateBoundRect()
{
QLOGX("Updating bounding rect");
boundingRect_.setRect(- size_, pointTextPos().y(),
(2 * size_) + std::max(pointText_.size().width(), valueText_.size().width()),
(2 * size_) + pointText_.size().height() + valueText_.size().height());
}
void GfxPointItem::updatePointText()
{
QLOGX("Updating point text");
pointText_.setText("P" + QString::number(index_ + 1) + " ("
+ (floatPrec_ ? QString::number(pos().x()) : QString::number(std::floor(pos().x()))) + ","
+ (floatPrec_ ? QString::number(pos().y()) : QString::number(std::floor(pos().y()))) + ")");
updateBoundRect();
update();
}
void GfxPointItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QLOGX("Mouse move");
QPointF p = pos();
QPointF ep = event->pos();
QGraphicsItem::mouseMoveEvent(event);
if (!static_) updatePointText();
emit changedPos(index_, pos());
}
I've no idea why the event loop keeps repainting the items. I wouldn't even notice this but I hit a problem with showing a standard QFileDialog::getExistingDirectory() which wouldn't even manage to get painted while a window with the graphical items was visible, because the repaints stole all execution time on the main thread away from it, resulting in a freeze. After that, I added tracing statements to the paint functions and came up with tens of thousands of entries in the logfile after a couple seconds while the application was seemingly doing nothing. In task manager, the CPU usage is around 25% when the window is visible (on a 4-core processor) and it drops to 0 when I close it.
None of my code forces these repaints, so what is? How can I debug the event loop to find the source of this behaviour, which slows down my application and causes freezes?
Thanks!
Qt version is the newest 5.0.2 binary bundle and the application is compiled with Visual C++ 2012 for x64.