tags:

views:

527

answers:

3

It's a known bug/issue that a label in GTK will not dynamically resize when the parent changes. It's one of those really annoying small details, and I want to hack around it if possible.

I followed the approach at 16 software, but as per the disclaimer you cannot then resize it smaller. So I attempted a trick mentioned in one of the comments (the set_size_request call in the signal callback), but this results in some sort of infinite loop (try it and see).

Does anyone have any other ideas?

(You can't block the signal just for the duration of the call, since as the print statements seem to indicate, the problem starts after the function is left.)

The code is below. You can see what I mean if you run it and try to resize the window larger and then smaller. (If you want to see the original problem, comment out the line after "Connect to the size-allocate signal", run it, and resize the window bigger.)

The Glade file ("example.glade"):

<?xml version="1.0"?>
<glade-interface>
  <!-- interface-requires gtk+ 2.16 -->
  <!-- interface-naming-policy project-wide -->
  <widget class="GtkWindow" id="window1">
    <property name="visible">True</property>
    <signal name="destroy" handler="on_destroy"/>
    <child>
      <widget class="GtkLabel" id="label1">
        <property name="visible">True</property>
        <property name="label" translatable="yes">In publishing and graphic design, lorem ipsum[p][1][2] is the name given to commonly used placeholder text (filler text) to demonstrate the graphic elements of a document or visual presentation, such as font, typography, and layout. The lorem ipsum text, which is typically a nonsensical list of semi-Latin words, is a hacked version of a Latin text by Cicero, with words/letters omitted and others inserted, but not proper Latin[1][2] (see below: History and discovery). The closest English translation would be "pain itself" (dolorem = pain, grief, misery, suffering; ipsum = itself).</property>
        <property name="wrap">True</property>
      </widget>
    </child>
  </widget>
</glade-interface>

The Python code:

#!/usr/bin/python

import pygtk
import gobject
import gtk.glade

def wrapped_label_hack(gtklabel, allocation):
    print "In wrapped_label_hack"
    gtklabel.set_size_request(allocation.width, -1)
    # If you uncomment this, we get INFINITE LOOPING!
    # gtklabel.set_size_request(-1, -1)
    print "Leaving wrapped_label_hack"

class ExampleGTK:

    def __init__(self, filename):
        self.tree = gtk.glade.XML(filename, "window1", "Example")
        self.id = "window1"
        self.tree.signal_autoconnect(self)

        # Connect to the size-allocate signal
        self.get_widget("label1").connect("size-allocate", wrapped_label_hack)

    def on_destroy(self, widget):
        self.close()

    def get_widget(self, id):
        return self.tree.get_widget(id)

    def close(self):
        window = self.get_widget(self.id)
        if window is not None:
            window.destroy()
        gtk.main_quit()

if __name__ == "__main__":
    window = ExampleGTK("example.glade")
    gtk.main()
+1  A: 

VMware's libview has a widget called WrapLabel which should do what you want, but it's in C++. A Python translation is available in the Meld repository (separated out from busybox.py).

Kai
Wow, I *never* would have found this. Thanks. I manually added it by creating a containing HBox in Glade and adding the code to insert it in __init__. (If you're really diligent you might create a Glade catalogue file for it...). It also took me about half an hour to realise I needed to use "show_all()" on the HBox after adding the label, so I hope this comment saves someone else that trouble...
detly
+1  A: 

You can use this. Not sure where it came from originally. Create your label and then call label_set_autowrap(label)

def label_set_autowrap(widget): 
    "Make labels automatically re-wrap if their containers are resized.  Accepts label or container widgets."
    # For this to work the label in the glade file must be set to wrap on words.
    if isinstance(widget, gtk.Container):
        children = widget.get_children()
        for i in xrange(len(children)):
            label_set_autowrap(children[i])
    elif isinstance(widget, gtk.Label) and widget.get_line_wrap():
        widget.connect_after("size-allocate", _label_size_allocate)


def _label_size_allocate(widget, allocation):
    "Callback which re-allocates the size of a label."
    layout = widget.get_layout()
    lw_old, lh_old = layout.get_size()
    # fixed width labels
    if lw_old / pango.SCALE == allocation.width:
        return
    # set wrap width to the pango.Layout of the labels
    layout.set_width(allocation.width * pango.SCALE)
    lw, lh = layout.get_size()  # lw is unused.
    if lh_old != lh:
        widget.set_size_request(-1, lh / pango.SCALE)
Rob
nice stuff, but for some reason when a label is large enough to display the text on a single line it keep up wrapping, so all labels' "y align" value must be set on 0, otherwise the text will disappear moving up inside the label...
Giorgio Gelardi
+1  A: 

example for resize and wrap the label dynamically:

EDIT:

import gtk

class DynamicLabel(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)

        self.set_title("Dynamic Label")
        self.set_size_request(1, 1)
        self.set_default_size(300,300) 
        self.set_position(gtk.WIN_POS_CENTER)

        l = gtk.Label("Dynamic Label" * 10)
        l.set_line_wrap(True)
        l.connect("size-allocate", self.size_request)

        vbox = gtk.VBox(False, 2)
        vbox.pack_start(l, False, False, 0)

        self.add(vbox)
        self.connect("destroy", gtk.main_quit)
        self.show_all()

    def size_request(self, l, s ):
        l.set_size_request(s.width -1, -1)

DynamicLabel()
gtk.main()
killown
Try to make it smaller than 300x300...
detly
@detly: Works In any size.
killown
@killown - Running that code as is, it is impossible to resize it *dynamically* below 300x300, isn't it? That's certainly the case here.
detly
Removing the `set_size_request` line *sort of* works, but its behaviour is a little strange. I think perhaps they semi-fixed the problem I mention in my question.
detly
@detly: oh sorry, yes its impossible to resize below 300x300 but there is a issue, try to remove set_size_request and then maximize the window, after that, you will be unable to resize the window width again, so, it's fixed using set_size_request.
killown
fixed: using set_size_request() and set_default_size(). It will work smaller than 300x300
killown