Last Friday, we submitted a change into Qt 4.5 that fixes an old problem we’ve had with font and palette propagation in Qt. The patch appears in current snapshots and in the upcoming beta. Since this change affects code that’s essentially been the same since Qt 4.0, and Qt 4.5 is quite late for a change like this, I thought it would be worth writing a blog about it to draw some attention to it and gather some feedback.
The patch ensures that palette and font properties set on a widget by the user propagate all the way from the source to every single descendent widget, overriding application font and palette settings to the same font or palette property, and that all settings propagate correctly when changed. In short, if you want a specific font family for a form, and you assign a font defining only the family to that form, then this family will propagate through the entire form while keeping any existing font size settings intact, which is how it was always meant to work. It also ensures that new widgets are initialized with the right settings from their new ancestor widgets. The only thing that stops propagation is if the same property has been explicitly assigned to a child widget (or the child is a window that doesn’t enable WA_WindowPropagation). In this case, the child widget’s settings will take over and continue propagating.
The former behavior was to resolve fonts and palettes against the parent’s update mask only. Explicit settings applied by the parent’s ancestors, however, were lost when resolving against application font and palette settings. So if you set the font size of a form on Windows XP or Vista, this would not sometimes not apply to QComboBox or QTableView, but it would always apply to QPushButton and QLineEdit, because on Windows, QAbstractItemView has an application font family and size set by the system. The “sometimes” is the funny part, because if the above widgets’ immediate parent widget had font settings applied to them, then these settings would sometimes propagate. But put a widget between, and they would not (other funny behavior also applied).
Some background info might be useful. QFont and QPalette have many things in common. They represent two pilars in Qt’s style mechanism: the fonts, and the colors/gradients/pixmaps used by the style to render the appearance of its standard controls. Most widgets use both text and colors, and both the font and palette are subject to system settings (e.g., theme, locale, desktop settings for fonts and colors), style settings (most styles polish the palette somehow), and of course user settings. Each of the two has so many settings that it’s impractical to force the user to set them all. So instead, only the settings you change are applied, and the rest are derived from context sensitive defaults.
Now you don’t usually want to change the palette or font if you want your application to match the native look and feel. In some environments, however, changes can make a lot of sense. Changing the default font family or size may be important for accessibility (…OK, OK, or for fun). Changing the default window background color can give your form a special touch that separates it from the rest of your application (e.g., using a white background for configuration forms, regardless of the system theme, is quite common). However, you should be careful when making specific font or palette changes, because this may cause undesired effects when your changes are combined with system settings.
Now for the settings. Both QFont and QPalette represent lots of settings, or “roles”. They also both record which of these settings have been explicitly set, and which settings are left untouched. When Qt knows that only the QPalette::Window role of a certain widget has been set, this allows the rest of the palette to be derived from the widget’s ancestors, and from the application palette set (QApplication::palette allows the user to set default palette settings per widget class). If all you want is for your widget to use a larger font, then you should assign a QFont to that widget that only sets the point size (i.e., it leaves the family, bold and italic settings, etc, unset). This size should then automatically apply to the widget’s children without touching the other settings the children might have.
// Give w and its descendents a bold font, let all other
// font settings propagate from the defaults.
QFont f;
f.setBold(true);
w->setFont(f);
// If you want to keep existing settings that have been
// explicitly assigned to this widget, you may want to copy
// the current font out and make your modifications.
QFont f = w->font();
f.setBold(true);
w->setFont(f);
// Constructing a QFont completely from scratch is usually
// wrong.
QFont f;
f.setFamily(…);
f.setPointSize(12);
f.setBold(false);
f.set…
f.set…
w->setFont(f); // do I have to set this font on all other widgets as well?
// answer: no, if you want to change font or palette settings “globally”, start
// with QApplication::font or ::palette, and only set the entries that you’d
// like to change.
Note: QFont and QPalette both represent requests. Qt finds the effective/final font by comparing your settings against the font database to find the best match; see QFontInfo for details.
Assigning specific fonts or palettes to widgets is a privilege to the application author, and the application author’s settings should propagate. Qt’s kernel never uses direct assignment, but rather QApplication::font or ::palette, which provide “soft” default settings that the user can override. It’s unfortunately not uncommon that styles assign directly to widgets. This is unfortunate, because as soon as you assign specific font or palette settings to a widget, this stops propagation, which is often to the application author’s surprise. (For debugging purposes, you can check Qt::WA_SetPalette or Qt::WA_SetFont to see if the style is preventing your settings from propagating to a widget.) Styles are out of the application author’s control. Now if the app author wants a form and all widgets inside to use Qt::white for QPalette::Window, but the style explicitly assigns a gradient brush for that role to all QFrames, then the user’s settings are lost, which is bad (most should agree!). So why do styles do this? After all, the style can choose to render the widget just as it likes, regardless of the palette. Why should the style assign palettes to individual widgets at all? Usually, it’s to work around a specific limitation in Qt’s palette, font or style functionality, such as Qt’s lack of more context sensitivity for font or palette settings (e.g., if the QRadioButton font should be italic only if it’s an immediate child of a QGroupBox; Qt doesn’t support that out of the box. Also, widgets’ backgrounds are initialized using the widget’s background role, which is out of control for the style). The problem when doing so is that propagation stops for that role. But no finger-pointing; Qt’s own styles are guilty of this sin
. If your style assigns palettes to widgets (QStyle::polish(QWidget*)), you should take a second look and see if it’s really necessary. Your call
.
Solution 1: Since the style isn’t forced to follow the palette, the alternate colors should be applied when the widget is rendered, leaving the widget font and palette intact. Theme-based styles (which require 3rd party APIs) in particular.
Solution 2: Report the limitation to us, and we might implement the missing functionality!
QWidget tracks whether widgets have explicitly assigned QPalette and QFont settings through the attributes Qt::WA_SetPalette and Qt::WA_SetFont. Toggling this attribute from the outside is almost always wrong. Have you ever seen a snippet like this?
widget->setFont(font);
widget->setAttribute(Qt::WA_SetFont, false);
What does this code try to do? The WA_SetFont attribute does not affect propagation. Also, sometimes if propagation doesn’t seem to work, you see snippets like this show up:
bool Widget::event(QEvent *event)
{
switch (event-&gr;type()) {
…
case QEvent::FontChange:
child->setFont(font());
break;
…
This is also wrong; if child is a regular widget, propagation should work just fine. If it’s a window, you might want to enable Qt::WA_WindowPropagation for that child.
So now for a bottom line. I personally think Qt’s propagation mechanisms are very cool and well-designed. And I think we should continue developing them! This change takes a step into the realm of “let’s at least make sure it works consistently all over the place”. And then, we should look into how we can remove all explicit font and widget fiddling in our styles. One step at a time.
Possibly related posts:
10 comments
Thanks for this clarification. Sometimes I have a problem that’s quite the opposite:
can I stop palette from propagating to children? It happens that I’d like to change the background-color (QPalette::Window) of some widget but only for that one, not for all the descendants. I found out how to do that with stylesheets, so there must be a way to do it with QPalette…
It’s good that you’re taking care of polishing QPalette/QFont behavior
Hey Great! Why not do it right and propogate the widget style while you are at it.
Eric
Eric, the widget style already propagates.
Andreas, do you mean QStyle propagates in >= Qt 4.4. QStyle has never propagated in any version of Qt.
The style most certainly does not propogate….At least in 4.4 We’ve been asking for it to be changed for quite sometime.
Qt 4.4.3 has:
QStyle *QWidget::style() const
{
Q_D(const QWidget);
if (d->extra && d->extra->style)
return d->extra->style;
return qApp->style();
}
We’ve requested this to be changed sevat least a couple of times, and each time have been rebuffed (And similarly on palettes)
So if this already changed in 4.5? And if not…back to the origianl question, can it be?
Eric
Eric: This is how Qt has been doing it for a very long time so it’s rather unlikely that we can change the fundamental behavior in 4.5. Many styles are not designed to be propagated and fundamentally break when applied to other widgets than the ones they were designed for, so children ends up with ugly or non-functioning Windows or CommonStyle implementations. However stylesheets were designed to propagate by default and were largely intended to take care of such use cases. Adding style propagation as an option is something we could consider though. Out of curiosity, what is your primary use case for applying your custom style only on specific children as apposed to styling the whole application?
The exact reason you stated, we want certain aspects of the program to appear in our custom style, and others to appear in native style (Mostly popups dialogs etc), setting native style on just those sub elemtnts doens’t work for other reasons,including things bing doen int QT proper beyond our control.
Seems it would be simple to allow it be a requested behaviour for those that need it, and would sure beat chanign qt everytime you do a new version
We tried to use style sheets instead, but we couldn’t do as much, cross platform support wasn’t good, and speed was terrible.
Eric
(And fwiw, we were told the same things about palettes..”Can’t be done because it is by designed and would break things”….seems that something has changed in that regard, so why not this one?
Eric, Jens, you’re right; of course styles don’t propagate (don’t know what came over me!). Palettes and fonts have always propagated in Qt, but styles do not. Qt could have used the same pattern for styles as for fonts and palettes; that you can assign styles to special classes of widgets, or to the app, or to individual widgets, and that the style would propagate (in fact I wish this is how Qt worked from the beginning!). Jens is right in that we cannot make styles suddenly start propagating by default; we assume there are styles that are written for just one widget (e.g., QGroupBox), and if such a style was to suddenly propagate, subwidgets might not be drawn at all (or in a different but more like case, in a different style than the rest of the app). Making propagation an opt-in feature would be a must. If we do this, we should also look at adding optional propagation for fonts and palettes. We have already investigated this to quite a degree in the past, but have reached a dead end. Right now it’s not a burning issue since nothing is broken/has regressed because of this. But it is worth looking deeper into still.
Andreas, and you’ve jsut again hit the nail on the head as to why this is a problem.
If I write a custome style for my widget, but my widget in turn contatins other QT widgets that may in turn create their own private widgets which I then have no good way to set their style to my style….while I could make a hack at creation time to walk all my children and explicitly set thier styles, I can’t catch the case where those widgets (Scroll bars anyone?) might be lazily created as need….which all boils back down to using custom styles plain doesn’t work unless you want to have a single style for your whole app…propgating the style based on user prefernce or flag would fix this.
Eric
While we’re at it, is there any chance of Palettes and Styles actually working on the Mac platform?
In the Mac version of Qt neither 1) QApplication::setPalette(…) nor 2) qWidget->setPalette(…) actually works unless you call QApplication::setDesktopSettingsAware(false). If you do call setDesktopSettingsAware(false) then you end up with an application who’s style does not look like the native Mac style, it’s close, but noticeably wrong. The reasons for this are as follows…
For #1 calling QApplication::setPalette(…) does not work on the Mac because Qt resets the application’s palette to the Mac-native default palette every time the application is activated, thus destroying any palette that you might have set for the application. The reason that I was given for doing this was that since there is no “font changed event” or “color changed event” then Qt needs to reload the fonts and colors every time the application is activated. The problem with this logic is that you cannot change the Mac System Font, it is what it is and it will never change. And the only colors that can be changed are between two color sets – Blue and Graphite – with the addition of changing the text Highlight color. Changing these colors is so rare an event that it is hardly something that Qt should be doing every time the application is activated. And regardless of if Qt needs to reset the palette or not, if the we the developer have set the palette using QApplication::setPalette(…) then shouldn’t you at least compare the palette we’ve set with the Mac default palette and only reset the palette roles if we haven’t set one? It seems that always clobbering the entire palette is a very bad idea.
Issue #2, which I sincerely hope Qt 4.5 solves, is that palettes are not properly propagated to the children on the Mac version of Qt. That is to say if I don’t use QApplication::setPalette(…) and instead call setPalette(…) directly on a QWidget then that palettes is not properly propagated to the children, it is only partially propagated. For example, background colors and some other color roles are propagated to the children on the Mac but the text colors are not usually changed (though they are in some controls) and some colors for parts of other controls aren’t changed as well.
I hope that this announcement means that Palettes will finally work properly in the Mac version of Qt. If they are not fixed on the Mac then Palettes are pretty much completely useless on the Mac and should be documented as such. Sadly I had reported these Mac issues during the development cycle of Qt 4.4 and my bug report was quickly marked as rejected before I had even completed the email discussion regarding the issues. I hope that someone would recognize that these are valid issues and must be fixed.
Thanks
Comments on this entry are closed.