2

Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?

The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).

My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.

If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?

The used Qt version is Qt 4.8.6

My questions are:

  • Is it possible to achieve what I want with the mentioned strategy?
  • Is it possible in any way to clip all the children, too?
  • Is my strategy appropriate or is it a bad Idea to solve it that way?
  • Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?

Additional Information

I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:

This works.

/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
    QPainter painter(this);
    QRect rect = this->geometry()
    QStyleOption opt;

    painter.setClipRegion(QRegion(rect.width()/2, 
                                  rect.height()/2,
                                  150, 150, 
                                  QRegion::Ellipse));
    painter.setPen(QColor(255, 0, 0));
    painter.setBrush(QColor(255, 0, 0));
    painter.setOpacity(1);

    painter.drawRect(rect);
}

But the following doesn't change anything:

/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
    QPainter painter(this);
    QRect rect = this->geometry();
    QStyleOption opt;

    painter.setClipRegion(QRegion(rect.width()/2, 
                                  rect.height()/2,
                                  150, 150, 
                                  QRegion::Ellipse));
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setOpacity(1);

    opt.init(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}

Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.

exilit
  • 1,156
  • 11
  • 23
  • 1
    Did you check [the animation framework](http://qt-project.org/doc/qt-5/animation-overview.html)? – thuga Oct 24 '14 at 06:34
  • Not really much, but as far as I understood, QAnimation and friends are for the animation itself (Changing values over time). But my problem is in the drawing (for now) not in the animation. – exilit Oct 24 '14 at 06:39
  • You need to tell us, which version of Qt you are using. – user1095108 Oct 24 '14 at 07:58
  • @user1095108 Yes, sorry. The version is Qt 4.8.6. I edited the question also. – exilit Oct 24 '14 at 08:03

1 Answers1

1

The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:

case PE_Widget:
    if (w && !rule.hasDrawable()) {
        QWidget *container = containerWidget(w);
        if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
            && (container == w || !renderRule(container, opt).hasBackground())) {
            //we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
            // (this may happen if we have rules like :focus)
            p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
        }
        break;
    }

As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:

if (q->testAttribute(Qt::WA_StyledBackground)) {
    painter->setClipRegion(rgn);
    QStyleOption opt;
    opt.initFrom(q);
    q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}

Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.

To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:

QPixmap pixmap(widget->size());
widget->render(&pixmap);

And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.

user1095108
  • 14,119
  • 9
  • 58
  • 116
  • Thank you for the lesson. I haven't tried it yet, but the approach you are viewing here is so helpful, that I want am inclined, to mark this as accepted answer. – exilit Oct 24 '14 at 08:36
  • One Question at this place: How did you get to the relevant source location? Just grepping? If so, what did you grep/search for? I am trying to 'dive' into code as often as possible, but often it ends in a unsuccessful search. So how did you made it? – exilit Oct 24 '14 at 08:43
  • 1
    @exilit I've grepped often yes. After lots of source dives you get to know where something of interest resides. But the dives are intimidating at first. Try doing `find -t f -name "*.cpp" | xargs grep ` – user1095108 Oct 24 '14 at 08:46
  • This works perfectly!! Thank you so much. Now I will try to clip away the children, too. Maybe I can learn a little bit to code dive. – exilit Oct 24 '14 at 09:28
  • 1
    @exilit The children are clipped automatically to the parent's rect, it is an annoyance with widgets to some people. I'll update answer with an approach with other clip regions. – user1095108 Oct 24 '14 at 11:06
  • Thank You once again. I think, I will go this way. It's not as elegant as I have hoped, but from there on I can move on myself. You have not only answered my question, but also you have pointed me to how I can help myself the next time. – exilit Oct 24 '14 at 12:53