[pygtk] Doc/tutorial on creating custom widgets

Mitko Haralanov mitko at qlogic.com
Sat Apr 5 05:31:26 WST 2008

On Fri, 4 Apr 2008 13:02:47 -0700
Mitko Haralanov <mitko at qlogic.com> wrote:

> If I understand the mechanics correctly, what I will have to do is make
> a custom "clicked" handler that gets the coordinates of the mouse when
> a click occurs, if they are within the frame of the checkbutton, act on
> the checkbutton, otherwise call the "clicked" callback.

For those that might be interested, here is what I came up with that
works as I want it to (well, almost). I do have to manually tweak the
pointer coordinates since I can't correctly compute the coordinates of
the checkbox (self.chk.get_parent_window() returns None). Also, I wish
I could have the CheckButton be only the check box instead of the check
box and the space allocated to the label, but I guess it's better then


import pygtk
import gtk
import gobject

class Size:
    def __init__ (self, x=0, y=0, w=0, h=0):
        self.x = x
        self.y = y
        self.width = w
        self.height = h
        self.x_end = self.x + self.width
        self.y_end = self.y + self.height
    def __repr__ (self):
        return "<Size x=%d, y=%d, width=%d, heigh=%d>"%(self.x, self.y, \
                                                        self.width, self.height)

    def __str__ (self):
        return self.__repr__ ()
class Button (gtk.Button):
    def __init__ (self, label=None):
        gtk.Button.__init__ (self)
        self.label = label
        self.callback = None
        self.user_data = None
        vbox = gtk.VBox ()
        self.chk = gtk.CheckButton ("Select")
        self.label = gtk.Label (self.label)

        vbox.pack_start (self.label)
        vbox.pack_start (self.chk, False)

        self.add (vbox)

    def widget_coords (self, widget):
        allocation = widget.get_allocation ()
        window = widget.get_parent_window ()
        if not window:
            # When 'widget' is self.chk, 'window' is
            # None, so let's use the button
            window = self.get_parent_window ()
        x, y = window.get_root_origin ()
        w, h = window.get_size ()
        extents = window.get_frame_extents ()
        coord = Size ()
        coord.x = x + (extents.width-w)/2 + allocation.x
        coord.y = y + (extents.height-h)-(extents.width-w)/2 + allocation.y
        coord.width = allocation.width
        coord.height = allocation.height
        coord.x_end = coord.x + coord.width
        coord.y_end = coord.y + coord.height
        return coord
    def do_realize (self):
        gtk.Button.do_realize (self)

        # try to get the coordinates of the checkbutton
        self.chk_coord = self.widget_coords (self.chk)
        self.add_events (gtk.gdk.BUTTON_PRESS_MASK | \
                         gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK)
        self.connect ("motion-notify-event", self.motion_notify_event)
    def connect (self, signal, callback, *user_data):
        if signal == "clicked":
            self.callback = callback
            self.user_data = user_data
            gtk.Button.connect (self, signal, self.clicked_handler)
            gtk.Button.connect (self, signal, callback, *user_data)
    def clicked_handler (self, button, *user_data):
        if self.pointer_x >= self.chk_coord.x and \
           self.pointer_x <= self.chk_coord.x_end and \
           self.pointer_y >= self.chk_coord.y and \
           self.pointer_y <= self.chk_coord.y_end:
            self.chk.set_active (not self.chk.get_active ())
            return False
            return self.callback (button, *self.user_data)

    def motion_notify_event(self, widget, event):
        # if this is a hint, then let's get all the necessary
        # information, if not it's all we need.
        if event.is_hint:
            self.pointer_x, self.pointer_y, state = event.window.get_pointer()
            self.pointer_x = event.x
            self.pointer_y = event.y
            state = event.state

        # Since I can't compute the coordinates of the checkbutton correctly
        # I have to tweak the pointer coordinates
        self.pointer_x += 5
        self.pointer_y += 5
        self.chk_coords = self.widget_coords (self.chk)
        return False

def c (widget, *user_data):
    print "c:", widget, user_data
if __name__ == "__main__":
    gobject.type_register (Button)
    window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    bcb = Button ("Checktext")
    bcb.connect ("clicked", c)
    window.connect("delete-event", gtk.main_quit)

Mitko Haralanov
Computer programmers do it byte by byte.

More information about the pygtk mailing list