16 Software
Breevy: A text expander that lets you insert long words or phrases and launch apps, websites, and more just by typing abbreviations.
IcyScreen: Automatic screenshots. Have them saved, e-mailed, and uploaded.

"Dynamic" label wrapping in GTK

As it stands, the wrapping functionality of a GtkLabel isn't the best. You can call gtk_label_set_line_wrap() on your label, and it will wrap... but it'll wrap at a fixed width, and not dynamically. In other words, the label won't rewrap it's text as the parent container of the label (for example, the program window) changes size, as does text in a GtkTextView, or text in your typical text editor.

Here's an example of how GTK wraps a label normally:

GTK label wrapping without workaround

Here's an example of how the wrapping will look if you use the workaround I describe below:

GTK label wrapping with workaround

For the longest time I wondered why this was, and how to fix it, until I finally asked if there was a solution a while back on the #gtk IRC channel. I was provided with an example for a workaround, and thought I'd share it. It's hackish, for sure, and it's not perfect, but all you have to do is connect your GtkLabel (with line wrapping enabled, using gtk_label_set_line_wrap()) to a callback for the "size-allocate" signal:

     g_signal_connect(G_OBJECT(label), "size-allocate",
G_CALLBACK(wrapped_label_size_allocate_callback), NULL);

The callback should look like this:

void wrapped_label_size_allocate_callback(GtkWidget *label,
GtkAllocation *allocation,
gpointer data) {

gtk_widget_set_size_request(label, allocation->width, -1);
}

Apparently, (and according to the GTK+ docs) GtkLabels can't/don't detect when their parent has modified their size allocation. By connecting the label widget to the "size-allocate" signal, we can detect when this modification occurs, and then call gtk_widget_set_size_request() to tell GTK we want the label to utilize all of that allocated space.

There's one flaw with this method, and that is, you'll no longer be able to make your parent GtkWindow any smaller than the current width/height of the label, because the GtkLabel will be forcing it to keep a minimum width/height of however much space it requested with the gtk_widget_set_size_request() call. So you'll never be able to shrink the parent window, and if you make the window bigger, it'll stay at least that big, forever -- at least, until the window is recreated. Which isn't necessarily bad, especially if you've forbidden window resizing with a call to gtk_window_set_resizable().

A workaround for this issue is to utilize the gtk_window_set_policy() function, to unconditionally allow both window growing and shrinking:

     gtk_window_set_policy(GTK_WINDOW(win), TRUE, TRUE, FALSE);

Unfortunately, this brings up issues of it's own: it'll allow the user to make the window as small as he/she wants to (even 1x1), instead of restricting it to the minimum size necessary to show the entirety of all of the widgets in the window, as is the default GTK functionality. You can "fix" this by calling gtk_window_set_geometry_hints() with a minimum size for the window:

     GdkGeometry geom;
geom.min_width = 175;
geom.min_height = 50;

gtk_window_set_geometry_hints(GTK_WINDOW(win), GTK_WIDGET(win),
&geom, GDK_HINT_MIN_SIZE);

The only caveat is that you're forced to provide a hardcoded value.

A better solution?

In many situations, including in some of mine, these benefits of the workarounds outweigh the caveats. If there's anyone out there who knows of fix for the label wrapping issue that gives you the best of both worlds -- "dynamic" wrapping, as well as the ability to make the window shrink while still showing the entirety of all widgets, without forcing a minimum hardcoded value -- I'd love to hear your solution. I've had a few ideas, but all seem overly complex...

Edited on 4/25/09 to make the windows in both screenshots look a bit nicer.

Posted by Patrick on April 24, 2009 at 1:47pm | 11 Comments
Tagged: , , , and

 1