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

11 Comments so far

  1. Hongli Lai, on April 24, 2009 at 3:44pm, said:

    I don't understand why they can't just fix the issue. I think GTK is a good toolkit but needing hacks like these is weird.

    Edit Comment

  2. mark, on April 24, 2009 at 5:24pm, said:

    Not really elegant. But lets compare it - how is Qt handling this? And WxWidgets?

    Edit Comment

  3. David Trowbridge, on April 24, 2009 at 6:54pm, said:

    At VMware we implemented a truly-wrapping GtkLabel subclass -- it's part of our open-source libview (http://view.sourceforge.net/). It's in C++/gtkmm but shouldn't be a hard port if you wanted a C version. libsexy uses a similar method for URLLabel.

    GTK+ itself will get real word-wrapping labels when they merge the "extended layout" branch. The current GTK+ doesn't have a height-for-width sizing mechanism, which is what's needed.

    Edit Comment

  4. Jonathan Allen, on April 24, 2009 at 7:37pm, said:

    Let me get thsi straight. Your "fix" is to remove the white space that makes the dialog easier to read?

    Edit Comment

  5. Michael Pyne, on April 24, 2009 at 9:48pm, said:

    mark: Sadly Qt is deficient here as well. It allows resizing down to very small sizes in this situation, unless you set a minimum size in one or both dimensions. It's led to quite a few different KDE bug reports.

    Edit Comment

  6. Patrick, on April 24, 2009 at 10:24pm, said:

    David: Sweet, I wasn't aware someone had done that. Thanks for sharing; I'll check that out next time I get a chance.

    The current GTK+ doesn't have a height-for-width sizing mechanism, which is what's needed.

    Interesting. I hadn't a clue what you were talking about until I Googled and found this. Apparently that was a SoC project back in 2007.

    Jonathan: You're missing the point. Even if the window was 1024x768, and the label was packed in the box with padding/filling enabled, the text, without the workaround, would be wrapped at exactly the same point in the label as it is in the screenshot above. That'd be like a text editor always wrapping long lines at 100 pixels in, instead of at whatever the width of the editor window is.

    The screenshot I showed without the workaround looks easier to read because there's padding, and also because the text wrapped differently (because the text was different). You can add padding with the workaround, as well...

    Edit Comment

  7. kumyco, on May 6, 2009 at 1:35pm, said:

    you can allow it to downsize by
    after calling gtk_widget_set_size_request(label, allocation->width, -1);
    set it to natural size
    gtk_widget_set_size_request(label, -1, -1);
    picked that trick up while trying to get an image to auto-resize and not activate the scrollbars on its parent

    Edit Comment

  8. Jessicabed, on May 10, 2009 at 1:52pm, said:

    Hi there, not sure that this is true, but thanks

    Edit Comment

  9. Arianawart, on May 13, 2009 at 8:49am, said:

    Great point and very interesting food for thought. I'm not sure I have any clients I can replicate this with, but will bear in mind for the future. Regards

    Edit Comment

  10. Flimm, on February 7, 2010 at 4:29pm, said:

    Thanks, I managed to translate that to Python and it works.

    Here it is in Python for those who are looking:

    def label_size_allocate(widget, rect):
    widget.set_size_request(rect.width, -1)
    # end label_size_allocate, in case the whitespace gets deleted.
    label.connect("size-allocate", label_size_allocate)

    Edit Comment

  11. Pioz, on March 25, 2010 at 9:14pm, said:

    In Ruby u can do this:

    require 'gtk2'
    class WrapLabel Gtk::Label
    def initialize(s = '')
    super()
    self.wrap = true
    self.wrap_mode = Pango::WRAP_WORD_CHAR
    self.set_alignment(0.0, 0.0)
    self.text = s
    self.signal_connect_after('size-allocate') do |w, a|
    lw_old, lh_old = self.layout.size
    if lw_old / Pango: :S CALE != a.width
    self.layout.set_width(a.width * Pango: :S CALE)
    lw, lh = self.layout.size
    self.set_size_request(-1, lh / Pango: :S CALE) if lh_old != lh
    end
    end
    end
    end
    end

    Edit Comment

Post a comment