Suboptimal Theming in GTK

I am a fan of custom widgets. I write a lot of them. Most are not over the top, or even noticeable, but are useful. In fact, most of my widgets I more often call “composite” widgets instead of “custom.” That is, a widget that does some common things to a group of child widgets. Not much involved, but reusable and useful in some context. Gtk# makes this kind of widget writing actually a simple and natural thing to do, unlike C, where you start your work by despising the task at hand. Ahhh – the usefulness of a good binding.

However, I have also written a number of full blown custom widgets, wherein I handle all input and drawing. It’s this kind of custom widget I’d like to talk about today. Actually, let’s just skip to the drawing part!

Ideally, when a custom widget needs to draw some control part, the first thing the author should do to accomplish this is look to see if there is some style that can be reused in GTK. Adhering to this guideline means the custom widget should look like the rest of GTK. It’s good for integration on a number of levels. It’s at this level that the problem arises: in many cases the theme engine will completely ignore the draw request!

In GTK there are a number of theme drawing primitives: hline, vline, shadow, arrow, box, check, focus, tab – to name a few. It’s then up to the theme engine to actually draw the requested style. A “detail” can be passed to each of the drawing functions that the engine can use as a hint. I think these details are somewhat standard, but I can’t find any documentation of them.

So what’s the problem? I have come to the conclusion that either I do not know what I am doing or theme engine authors do not understand the object model and power of GTK itself.

For instanced, when I try to ask GTK to draw a box in the same style that it’s drawn for the GtkTreeView, I get just a regular plain box. No special theming. Why? Because the engine itself is written to not allow this!

Take this snippet, from Clearlooks:

else if (DETAIL ("button") && widget && widget->parent &&
    (GE_IS_TREE_VIEW(widget->parent) ||
    GE_IS_CLIST (widget->parent) ||
    ge_object_is_a (G_OBJECT(widget->parent), "ETree"))) /* ECanvas inside ETree */

Here it will only draw the requested style if it has the proper detail, and the widget happens to be a real GtkTreeView (among other special cases). This makes it impossible for custom widgets to be written which emulate the look of stock GTK. Evidence of this can be seen throughout the desktop.

Let’s start with a stock GtkTreeView. Observe very carefully how the header looks. This is what the three custom widgets following have to try to mimic because it is impossible to actually request this style to be drawn.

GTK Headers in GtkTreeView

Here’s Evolution’s ETree headers. They use the “button” detail to actually draw each column header, probably because the author ran into this larger issue when writing the theming for the widget. In fact, there’s a separate hard coded hack in Clearlooks itself (which can be seen in the snippet above) to draw the button in a special way just for ETree, implying that even the engine author knows this is an issue!

GTK Headers in ETree

For XUL, there is a theme that uses the GTK theming engine. Most widgets look okay, after padding and spacing hacks, but I point back to their list view. Here’s a screenshot of how their headers are drawn. I talked with Garrett about this, and he said he remembers running into this very same issue when working on the Firefox/GTK theme.

GTK Headers in XUL

Finally, when working on my new high performance data binding 2.0 synergistic .NET list view widget, I again ran into this problem (I have run against similar problems in other custom widgets) when working on the drawing code for my headers. I used another hack to best replicate what I observed in most themes. I drew one large “button” box for the entire header at something like (-10, -10, Allocation.Width + 20, Allocation.Height + 20). This way there would be no button border or rounding, but you could get the subtle gradient/raised look that themes often draw. A nice hack that worked relatively well, but was still a nasty hack at the end of the day.

GTK Headers in Managed List View

This is just one example of inconsistency regarding custom widgets due to limitations in the theme engine. It’s not just Clearlooks however. In the gtk-engines module, there is a support library of common functions that theme engines can use. There are a bunch of macros taking the form GE_IS*. These macros are used to determine the type of an object, so that theme parts can be drawn in different ways depending on what widget they are to be drawn.

Here’s a really crappy shell command that counts all of the GE_IS* instances in gtk-engines to show how ingrained the idea of using an object’s type to conditionally style it is:

(s=0; for v in $(grep -c GE_IS `find -iregex "^.*\.[ch]$" | xargs` | cut -d':' -f 2); do s=$((s + v)); done; echo $s)

I’m sure there’s an easier way to get the sum of the counts from grep, but I am just that lame. Anyway, I am counting 286. Wow.

My widget does not derive from GtkTreeView, so I cannot use the styles defined for it by the engine. Because of this, custom widgets immediately become second class citizens. This is bad for complex applications that may do their own theming, and probably ISVs if they care, and most certainly for other toolkits (XUL, QT, Wx) which can use the GTK engine to provide a theme that integrates applications consuming another toolkit with the rest of GTK.

This limitation is pointless to me. All of this object-hierarchy-bound conditional theming can be replaced with a much better detail system that would allow any widget to draw any style. Details should also be published and made standard so it is easy to replicate the styles of a particular widget. The bottom line is that the theme engine should never need to know, or at least never require a widget to be of a particular type for the desired style to be drawn.

There are also a lot of other minor issues, mainly regarding RC styles and proper descriptions of states and colors. There are many themes that do not follow the directions when defining colors for states, which leads to many inconsistencies in custom widgets. This just needs to be better documented.

Of course, and again, maybe I just “don’t get it.” After all I am no theming expert… What I do know is that if I can’t achieve consistent widget theming in GTK, I might as well stop trying to fake it. I have decided, although tentatively, to just use Cairo to do all my drawing with my new list widget, and make it blend the best I can by reusing RC colors. Good-bye GTK Style API.

28 Replies to “Suboptimal Theming in GTK”

  1. I stopped reading this when you started bashing on C again, halfway paragraph 1. Why is it that every person on earth using some custom $language that – oh for god – actually depends on C libraries to do anything useful whatsoever has to bash his way through to show his superiority? Can’t you just make your point without this? I hate it that every second post on planet gnome tells me that C#, Python, C++, Scheme, Lisp or some other horrific abuse of mankind is superior to C, even though the blog entry appears to contain something else that could actually be useful, interesting, even pleasant to read. Stop it, please.

    Too bad, because I use custom widgets a lot too and I guess this could have been useful to read for me. What a waste of www.

  2. Wow, you have that all wrong my friend. I meant only that ANY LANGUAGE BINDING is generally more conducive to getting work done over C. I was not BASHING C – I write and love C, but in 2007, I find myself MORE PRODUCTIVE and can BETTER SPEND MY TIME using $LANGUAGE_BINDING. I do not care what binding you use or prefer. At all. Sounds like you need a vacation or a masseuse or something.

  3. Furthermore, this post had not a damn thing to do with languages. I opened by justifying the number of custom widgets I write due to the fact that it is easier to write them in $LANGUAGE_BINDING over C. It was a transition into the subject matter. Get over yourself.

  4. With the advent of Cairo, I think your approach in your recent BansheeNG work is probably the best method, when dealing with owner-draw widgets. Perhaps there should be a “style guide” to follow, rather than just letting developers abuse Cairo to the max… but sensible use is definitely the way forward.

    As long as the Cairo-drawn UI responds the way you’d expect, and it does the job, where’s the harm? Surely this approach also paves the way for bling and other goodies in a composited environment too?

  5. Over the past few years I’ve been involved with theming, I too prefer a defined guidelines and a single consistent look for all widgets.

    Themes are the new preference options, making things more complex and prone to bugs and actually working against a consistent experience. Themes suck.

  6. Very good entry, and ignore Ronald :)

    We’ve hit this issue at VMware before and I’ve hit it in some custom apps in personal projects. It really does need to be fixed and it’s quite bad how it is. Hopefully your post will get the attention of some maintainers and developers and we can figure out how to improve all this.

  7. Aaron, your whole mention of C was unnecessary. That is all I meant to say. It annoys me as a reader, you would make a better impression if you would leave it unsaid. It’s like starting a blog entry about how KDE sucks over GNOME when your blog is actually about pets or starting to say how MacOSX is superior over Amiga and then continuing about your new Ferrari. What I’m trying to say is: the rest of your blog entry (I read it anyway, because it’s interesting, as opposed to the first paragraph) deserves a better introduction than such a *gasp* rant-for-ranting. Your current leaves a sour taste in my mouth. Without any such a need. It happens too much lately, unfortunately.

    And the rest of your blog entry _is_ actually very thoughtful, and I could’ve discussed that, if it wasn’t just for, ah, what the hell, nevermind. I hope you at least sort of see my point. :-).

  8. I agree with the problem at hand. (And, just for the record, I am the one responsible for that evolution specific hack …)

    In the past I have spend some time thinking about how this could be fixed. The only way I came up with (that works with the current model) is to require the application to use gtk_rc_get_style_by_paths and get the real style of the treeview. Not too nice, but a lot better than creating a whole widget to use when drawing anything.

    Even then you get some more issues. You cannot read the relavant style properties (bug #412134). The paddings inside widgets or other options may not be documented and, even if they are, impossible to replicate without access to the style properties. Quite a lot of the theming is not documented, so you may end up reading GTK+ code to figure out the correct hints to pass to the engine.
    And then of course that every theme using the affected engines needs to be rewritten to support this with such a change …

    Enough for now,

    Benjamin

  9. Wow, this seems like it would be so much better if the themes were patched… any chance of submitting any bugs / patches. We could have a lot more consistant desktops if this was fixed.

  10. Great post Aaron, ignore the trolls and nutters. It is really important that we start to address the desktop consistency errors in themes. There are lots of these little buggers floating around the desktop, a pixel here or there out of place sometimes something (like evolution list headers) look bloody aweful. It would be especially good if we could ensure that the theme engines and gtk itself were modified to ensure that this kind of tom foolery is has a stop put to it ASAP.

    GTK 2.14?

  11. Seeing that a few gtk+ devs are reading this, I’ll add my 2¢.

    Has anyone tried creating a theme engine or reusing ClearLooks or Industrial and removing _all_ of these app specific hacks to see what happens?

    Just a thought …

    @Aaron, keep up the good work on Banshee, it’s one of my favorite apps :)

  12. Is there a bug reported on this?

    Perhaps the right way to fix this is to compile a list of the current detail hints, add more, and then submit patches to as many theme engines as possible to have them use the hints and fall back to the old ugly method failing that. Then maybe a patch to evolution and such so that it sets a detail as well, etc.

    It probably wouldn’t take long for it to catch on. Especially if many new apps start taking advantage of the hints.

  13. So, if I read it right, can we remove some of these special cases and not break the themes (in fact they’ll look better?) if so, then like David says we should start patching and hope it catches on…

    As well as patching themes it might mean patching the apps that have custom widgets to not use such crazy custom widgets once it’s possible.

  14. ….maybe a good place to start with patches would be to affect the custom widgets talked about on this page?

  15. What’s so ugly in my opinion is the way scrollbars are kind of outside what they’re scrolling when it comes to things such as TreeViews and HTML views and so on. If you look at OS X for example, the scrollbar is inside the list, and infact, just above the scrollbar is where the column headers are.

  16. @Cameron:

    That’s totally configurable via gtkrc:

    GtkScrolledWindow::scrollbar-spacing = 0
    GtkScrolledWindow::scrollbars-within-bevel = 1

    Just some themes (Clearlooks) don’t it use this option.

  17. That post was really very interesting. Im a big fan of your work. With all the work going into mono, gtk-sharp and all those mono based apps, developing in linux becomes much more attractive!

    Im looking forward for big improvements about the theming in gtk3.

Comments are closed.