I See Dead Code

… as sounding brass, or a tinkling cymbal.

I See Dead Code header image 2

Scrollable Widgets with PyGTK

Mai 17th, 2009 · No Comments

It is possible to write custom GTK widgets that have “native” scrolling support, as opposed to just shoving them into a GtkViewPort and forgetting about them.

Apart from having mastered a small coding challenge, as it turned out to be, this also gives you greater control over the scrolling itself, like making sure that certain elements are visible, viewport panning etc.

Anyway, especially when using PyGTK, it’s a bit unclear on how to proceed. From the documentation, it somehow gets clear that it has to do with the signal set_scroll_adjustment_signal:

This signal is emitted when a widget of this class is added to a scrolling aware parent, gtk_widget_set_scroll_adjustments() handles the emission. Implementation of this signal is optional.

This is not a signal name, but a signal ID1 that has to be set in GtkWidgetClass2.

Some more documentation reading reveals that you can set this signal by using the set_set_scroll_adjustments_signal method on a widget:

class ScrollableWidget(gtk.DrawingArea):
    __gsignals__ = {
        "set-scroll-adjustments": {
            gobject.SIGNAL_RUN_LAST,
            gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment)),
    }
 
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.set_set_scroll_adjustments_signal("set-scroll-adjustments")

It doesn’t really matter how you call the signal as long as it takes two arguments (the horizontal and vertical adjustment). This will make the method set_scroll_adjustments (which you can’t override from within Python) return True when it is called and signal that the widget supports scrolling.

This, however, is only half the way, because the scrollable widget still needs the adjustments handed in via said methods. It’s of course possible to connect to the signal explicitly, but there’s an even more direct way by using action signals.

Action signals are the C programmer’s idea of “generic methods”. In order to create such a signal, it has to have the flag gobject.SIGNAL_ACTION and they are directly connected to a function which is then called on each signal emission. While in C, you have to provide a function pointer, in Python you can just implement functions with a compounded magic name3 and have it called automatically. I haven’t found any documentation on that in the PyGObject or PyGTK docs, only some examples in the web:

class ScrollableWidget(gtk.DrawingArea):
    __gsignals__ = {
        "set-scroll-adjustments": {
            gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, 
            gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment)),
    }
 
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
 
    def do_set_scroll_adjustments(self, h_adjustment, v_adjustment):
         # do some useful stuff here, like saving them
         ...

The method being called on emission has to start with do_, following by the signal names with hyphens replaced by underscores.

The adjustment objects can then be configured to one’s own liking to have scroll bars show up or not. However, to know when the user did some scrolling, it’s necessary to listen on some signals:

    def do_set_scroll_adjustments(self, h_adjustment, v_adjustment):
        h_adjustment.connect("value-changed", self._scroll_value_changed)
        self._hadj = h_adjustment
        v_adjustment.connect("value-changed", self._scroll_value_changed)
        self._vadj = v_adjustment

To make the scroll bar show up, modify upper, lower and page_size on the adjustments.

self._hadj.lower = 0
self._hadj.upper = 50
self._hadj.page_size = 10

This tells the scrollbar that the size of the underlying picture (upper - lower) is 50, while the visible size (page_size) is 10.

The page size obviously depends on the current size of the widget, which can be retrieved from the underlying gtk.gdk.Window:

width, height = self.window.get_size()

The current position of the scroll bar is controlled by the property value of the adjustment object and should be in the range [lower .. upper - page_size]. Whenever the property is changed, the value-changed signal is emitted, which we’ve connected to previously, and the widget can be repainted.

If you’re curious, you can also see the whole gloriousness in actual working code.

  1. which you usually don’t seen when coding with PyGTK []
  2. ditto []
  3. a technique which I thoroughly dislike and should be converted to be used with decorators []

Tags: lang:en · programming · python

0 responses so far ↓

  • There are no comments yet...Kick things off by filling out the form below.

Leave a Comment