Subnavigation

Font and Palette propagation in Qt

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. :-)


Blog Topics:

Comments