What’s in a Proxy-Style?

[[This is the English-language original of an article that appeared (in German) on Heise Developer.]]

I vividly remember the pre-Qt 4.0 discussion from the qt4-tech-preview mailing list: Someone was asking how they were supposed to port their Qt 3 code, which overrode virtual void QButton::drawButtonLabel(QPainter*) to draw his own label on a QPushButton (QButton was the base class of QPushButton in Qt 3, and had a lot more responsibilities than its Qt 4 equivalent QAbstractButton).

In Qt 4, the Trolls extinguished this virtual function, along with a lot of others of its kind, for a leaner Qt, with less relocations than Qt 3 had.

The only alternative, as the original poster saw it, was to copy the paintEvent() implementation from QPushButton, and change it to suit his needs.

Needless to say, this wasn’t exactly the perfect solution: If QPushButton::paintEvent() was to change, he would have to re-copy the code over, and patch in his changes again. Worse, the new code might require a recent Qt, and even if he #ifdefed his way around that by leaving the old code in, if the code was compiled against an old Qt, but run against a new Qt, his pushbutton could look different from a “native” one.

The answer he got—from a Troll, if I remember correctly—was: “Use a proxy style.”

Now, this sounds great: Make a proxy style that you insert between QPainter and QPushButton. Make that proxy style change only the drawing of a pushbutton label (there is QStyle::CE_PushButtonLabel for that), and—proxy-style (no pun intended)—forward all other drawing to the original style. Assuming we have a QProxyStyle that does all the forwarding for us, the solution might look something like this:

class PushButtonLabelProxyStyle : public QProxyStyle {
    QWidget * const m_widget;
    QStyle * const m_style;
public:
    // ...
    explicit PushButtonLabelProxyStyle( QWidget * w )
        : QProxyStyle(),
          m_widget( w ),
          m_style( w ? w->style() : 0 )
    {
        setSourceStyle( m_style );
        if ( w ) w->setStyle( this );
    }
    ~PushButtonLabelProxyStyle() {
        if ( m_widget ) m_widget->setStyle( m_style );
    }
    // ...
    void drawControl( ControlElement ce, const QStyleOption * opt, QPainter * p, const QWidget * w ) const {
        if ( ce == CE_PushButtonLabel )
            drawMyPushButtonLabel( opt, p );
        else
            QProxyStyle::drawControl( ce, opt, p, w ); // forwards to base style
    }
    // ...
};

void MyPushButton::paintEvent( QPaintEvent * e ) {
    const PushButtonLabelStyleProxy proxy( this );
    QPushButton::paintEvent( e );
}

There. Perfect (even though we’re a bit laissez-faire regarding object ownership here). We even threw in a bit of RAII for extra coolness.

Yet, it took seven feature releases for a QProxyStyle to make it into Qt (4.6 had it, finally). It seems so obviously useful… Why did it take that long?

Well, not all that is useful is in Qt. After all, that’s where third-party vendors come in. KDAB, for example, has KDTools, and there’s also Qxt, which, incidentally, has a QxtProxyStyle that does exactly what a QProxyStyle would do.

However, I invite you to flesh out the above code fragment into an executable application and test, e.g. by drawing the label in an outline font, or something similarly obvious, in Qt ≤ 4.5, or using QxtProxyStyle.

What you will find is that it just doesn’t work. And single-stepping into the MyPushButton::paintEvent() will quickly tell you why (as will reading the corresponding article in Qt-Quarterly #9). As we hunt the bug, we will discover an important caveat when applying a classical GoF pattern…

But first: Quick Quiz: Which GoF pattern does QProxyStyle implement?

You might be excused for picking Proxy. The class name is just too suggestive. But, as the GoF book hurries to mention, Proxy is just about controlling access to the RealSubject. Personally, I would pick Decorator. After all, our intention is not primarily to control access to the base style (although, of course, in the case of QStyle::CE_PushButtonLabel, that’s exactly what we do), but to “decorate” it. Sure, Decorator is usually used to add behaviour to a component, not rewrite it, but I still feel that the Decorator GoF examples of adding window borders to visual components, or compression to a stream, strike much closer to heart here than the Proxy example of delaying image loading in a text processor.

So, what’s the problem? Jasmin Blanchette, then-head of technical writers at then-Trolltech, describes it in the above-linked QQ#9 article as follows:

This approach has one issue that we must be aware of. Some virtual functions in Qt’s built-in styles are implemented in terms of other virtual functions; for example, QWindowsStyle::drawComplexControl() depends on QWindowsStyle::drawPrimitive() to draw the small arrow of a QComboBox. If we re-implement drawPrimitive() in our MyStyle class (which inherits ProxyStyle), our reimplementation will be ignored by QWindowsStyle, which will keep calling its own drawPrimitive() function. (This behavior occurs because MyStyle doesn’t inherit from QWindowsStyle; it only “wraps” it.) Depending on the results we want, this might mean that we must re-implement drawComplexControl() as well.

Well, there you go. Incidentally, the same is true for our drawControl(): For QStyle::CE_PushButton, it calls itself (see QCommonStyle::drawControl()) for QStyle::CE_PushButtonBevel, QStyle::CE_PushButtonLabel, as well as subElementRect() and drawPrimitive(). So, once QPushButton::paintEvent() calls style()->drawControl( QStyle::CE_PushButton, ... ), and our proxy style forwards this to the base style, control will not return to our proxy until it returns from the forwarded call:

UML sequence diagram for QxtProxyStyle

UML sequence diagram for QxtProxyStyle

And that’s the bug: our reimplementation of drawControl( CE_PushButtonLabel ) is never called.

Q: Can’t we get away with also reimplementing drawControl() for CE_PushButton?
A: Not in general. Who’s to say that all QStyle subclasses use the same implementation as QCommonStyle for CE_PushButton?

Q: What’s the underlying reason our code isn’t called?
A: Because here we have a class where one virtual function calls another virtual function.

Q: What does this mean for the Decorator Pattern?
A: Such classes cannot—in general—be Components in the Decorator Pattern.

I say “in general”, because there will always be use-cases for which it will work. For example, when you want to pin the order of buttons in a QDialogButtonBox, you can do this with a proxy style and reimplementing styleHint() to return a hard-coded QDialogButtonBox::ButtonLayout for SH_DialogButtonLayout. But that works by accident, not by design, because styleHint() happens to be called directly by the widget instead of indirectly through some style function.

Now, Qt 4.6 does come with a QProxyStyle. It is instructive to look at how they “dunnit”, and why that is not a solution in the general Decorator case.

Let’s first start with a UML diagram to show what we want:

UML sequence diagram for QProxyStyle

UML sequence diagram for QProxyStyle

(all UML bugs are Umbrello’s, not mine :))

Ugh. Yet, that’s exactly what we asked for: “somehow” call back into our proxy’s virtual functions—at any level in the call stack—instead of staying stuck on aBaseStyle. It’s quite easy to implement this if you have control over aBaseStyle: You simply implant the proxy into it, and then, instead of calling this->drawControl(...), you call proxy->drawControl(...). If there’s no proxy, you set proxy = this.

And that’s exactly what the Trolls have done: See Qt commit 4d0cc0b9600f8530bb0e8712b4bb109d1810c4a7 (direct links to gitorious seem to be broken, you may have to manually navigate to it) and, since that one’s “too large to be displayed in a browser”, here’s the diff-stat for the commit (and it doesn’t even include the implementation of QProxyStyle):

 src/gui/kernel/qapplication.cpp       |   81 +++---
 src/gui/kernel/qapplication_p.h       |    4 +-
 src/gui/kernel/qapplication_x11.cpp   |   76 +++--
 src/gui/styles/qcleanlooksstyle.cpp   |  148 +++++-----
 src/gui/styles/qcommonstyle.cpp       |  534 ++++++++++++++++----------------
 src/gui/styles/qgtkstyle.cpp          |  118 ++++----
 src/gui/styles/qmacstyle_mac.mm       |   92 +++---
 src/gui/styles/qmotifstyle.cpp        |  156 +++++-----
 src/gui/styles/qplastiquestyle.cpp    |  102 ++++----
 src/gui/styles/qstyle.cpp             |   34 ++-
 src/gui/styles/qstyle.h               |    5 +
 src/gui/styles/qstyle_p.h             |   14 +-
 src/gui/styles/qwindowsstyle.cpp      |   94 +++---
 src/gui/styles/qwindowsvistastyle.cpp |   76 +++---
 src/gui/styles/qwindowsxpstyle.cpp    |  144 +++++-----
 src/gui/styles/styles.pri             |    4 +
 tests/auto/qstyle/tst_qstyle.cpp      |   42 +++-
 17 files changed, 912 insertions(+), 812 deletions(-)

This monster—naturally—touched every single concrete style that is in Qt (and therefore fails to work with those that are not in Qt, something the class documentation mentions itself).

But that’s not what the GoF book intended: this is “make QProxyStyle work at any cost”, and as such not a solution in general. At its purest, Decorator can be applied to a hierarchy without the need for all constituents to know about a possible Decorator (and paying for this by another indirection even if no decorator is used). However, this was the only way to make a proxy style work with the QStyle we have.

So, what have we learned? If you want to allow your class designs to be decorated, don’t make virtual functions call each other.
And, as an addendum to the Decorator discussion in the GoF book: You cannot apply this pattern if Component has virtual functions that call each other.

I would like to thank David Faure for reminding me more than once why a general QProxyStyle was impossible (until the Trolls made it possible, that is).

Advertisement

3 Responses to What’s in a Proxy-Style?

  1. Pingback: Private Practice: What’s in a Proxy-Style? « -Wmarc

  2. Pingback: Private Practice: Taming Templates « -Wmarc

  3. finetjul says:

    The current implementation of QProxyStyle still needs some more care. For example, it doesn’t support nested QProxyStyles.
    When one QProxyStyle (C) has for base style a QProxyStyle (B) which has for base style a standard style (A) .
    The methods in C will end up never being called, only the ones in B will be.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: