submitted by mobicurious 310 days ago (via taschenorakel.de)
From time to time applications need custom theming rules. Especially when the project has professional UI designers involved. So how to achieve this with GTK+?
Trivial Theming
Most easy and very wrong:
if (gdk_color_parse ("pink", &color))
gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, &color);
This will break and look childish as soon as your users use a custom color scheme.
Better:
static void
style_set_cb (GtkWidget *widget,
GtkStyle *old_style)
{
GtkStyle *style = gtk_widget_get_style (widget);
if (gtk_style_lookup_color (style, "SecondaryTextColor", &color))
gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, &color);
}
static void
my_widget_init (MyWidget *widget)
{
g_signal_connect (widget, "style-set", G_CALLBACK (style_set_cb), NULL);
}
This will allow theme designers to override your color choice. Notice that you'll have to update those color overrides when the theme changes. Btw, the "style-set" signal is emitted when the widget is realized, therefore you don't have to manually invoke the callback during device construction.
Guess it's also worth to mention that Hildon provides convenience API for simple theming requirements.
Complex Problems
So for simple requirements GTK+ (and Hildon) have reasonable API support. Things become troublesome when you designers invent rules like "this widget has a rounded border and drop shadow, but only within buttons". Obviously border and drop shadow radius should be themeable and therefore are implemented as style properties, but how to impose this rule?
You could scan the widget hierarchy when choosing a default values for your style properties:
button = gtk_widget_get_ancestor (widget, GTK_TYPE_BUTTON);
gtk_widget_style_get (widget, "border-radius", &border_radius, NULL);
if (border_radius)
border_radius = (button ? 12 : 0);
You'll quickly notices the flawed hard coded default value. Also such things are hard to override in theme files. So it's probably better to apply a custom theming rule via gtk_rc_parse_string():
static void
my_widget_class_init (MyWidgetClass *class)
{
...
gtk_rc_parse_string
("style 'my-widget-style-clickable' {"
" MyWidget::border-radius = 2"
"}"
"widget_class '*.<GtkButton>.MyWidget'"
"style 'my-widget-style-clickable''");
...
}
Application Theme Files
Looks like a perfect solution, until you realize that this rule is applied after all rules loaded from gtkrc files!
So how to inject this rule before the user's theming rules? This was a big question to me until I've found gtk_rc_add_default_file(). Well almost: This function only adds files to the end for the search path. Therefore it suffers from the same issues as gtk_rc_parse_string(). Fortunately the API author was smart enough to also provide gtk_rc_get_default_files() and gtk_rc_set_default_files(). Those functions can be used to apply application specific theming rules, which can be overwritten by the user - drum roll please:
static void
inject_rc_file (const char *filename)
{
char **system_rc_files, **p;
GPtrArray *custom_rc_files;
system_rc_files = gtk_rc_get_default_files ();
custom_rc_files = g_ptr_array_new ();
g_ptr_array_add (custom_rc_files, g_strdup (filename));
for (p = system_rc_files; *p; ++p)
g_ptr_array_add (custom_rc_files, g_strdup (*p));
g_ptr_array_add (custom_rc_files, NULL);
gtk_rc_set_default_files ((gpointer) custom_rc_files->pdata);
g_strfreev ((gpointer) g_ptr_array_free (custom_rc_files, FALSE));
}
int
main (int argc,
char **argv)
{
...
inject_rc_file (PKGDATADIR "/gtkrc." PACKAGE);
gtk_init (&argc, &argv);
...
}
Update: Benjamin Berg just pointed out that priorities can be assigned to styles. So the following should work fine:
gtk_rc_parse_string
("style 'my-widget-style-clickable' {"
" MyWidget::border-radius = 2"
"}"
"widget_class '*.<GtkButton>.MyWidget'"
"style : lowest 'my-widget-style-clickable''");
Awesome, little know feature.
2009-09-22 08:42 UTC with score 3