3

I am updating some code from using libglade to GtkBuilder, which is supposed to be the way of the future.

With gtk.glade, you could call glade_xml.signal_autoconnect(...) repeatedly to connect signals onto objects of different classes corresponding to different windows in the program. However Builder.connect_signals seems to work only once, and (therefore) to give warnings about any handlers that aren't defined in the first class that's passed in.

I realize I can connect them manually but this seems a bit laborious. (Or for that matter I could use some getattr hackery to let it connect them through a proxy to all the objects...)

Is it a bug there's no function to hook up handlers across multiple objects? Or am I missing something?

Someone else has a similar problem http://www.gtkforums.com/about1514.html which I assume means this can't be done.

poolie
  • 9,289
  • 1
  • 47
  • 74

4 Answers4

4

Here's what I currently have. Feel free to use it, or to suggest something better:

class HandlerFinder(object):
    """Searches for handler implementations across multiple objects.
    """
    # See <http://stackoverflow.com/questions/4637792> for why this is
    # necessary.

    def __init__(self, backing_objects):
        self.backing_objects = backing_objects

    def __getattr__(self, name):
        for o in self.backing_objects:
            if hasattr(o, name):
                return getattr(o, name)
        else:
            raise AttributeError("%r not found on any of %r"
                % (name, self.backing_objects))
poolie
  • 9,289
  • 1
  • 47
  • 74
3

I have been looking for a solution to this for some time and found that it can be done by passing a dict of all the handlers to connect_signals.

The inspect module can extract methods using inspect.getmembers(instance, predicate=inspect.ismethod These can then be concatenated into a dictionary using d.update(d3), watching out for duplicate functions such as on_delete.

Example code:

import inspect
...    
handlers = {}
for c in [win2, win3, win4, self]:  # self is the main window
    methods = inspect.getmembers(c, predicate=inspect.ismethod)
    handlers.update(methods)
builder.connect_signals(handlers)

This will not pick up alias method names declared using @alias. For an example of how to do that, see the code for Builder.py, at def dict_from_callback_obj.

Antonio Beamud
  • 2,281
  • 1
  • 15
  • 26
simonltwick
  • 310
  • 2
  • 6
2

I'm only a novice but this is what I do, maybe it can inspire;-)

I instantiate the major components from a 'control' and pass the builder object so that the instantiated object can make use of any of the builder objects (mainwindow in example) or add to the builder (aboutDialog example). I also pass a dictionary (dic) where each component adds "signals" to it.
Then the 'connect_signals(dic)' is executed.
Of course I need to do some manual signal connecting when I need to pass user arguments to the callback method, but those are few.

#modules.control.py
class Control:

    def __init__(self):

        # Load the builder obj
        guibuilder = gtk.Builder()
        guibuilder.add_from_file("gui/mainwindow.ui")
        # Create a dictionnary to store signal from loaded components
        dic = {}

        # Instanciate the components...
        aboutdialog = modules.aboutdialog.AboutDialog(guibuilder, dic)           
        mainwin = modules.mainwindow.MainWindow(guibuilder, dic, self)
        ...

        guibuilder.connect_signals(dic)
        del dic


#modules/aboutdialog.py
class AboutDialog:

    def __init__(self, builder, dic):
        dic["on_OpenAboutWindow_activate"] = self.on_OpenAboutWindow_activate
        self.builder = builder

    def on_OpenAboutWindow_activate(self, menu_item):
        self.builder.add_from_file("gui/aboutdialog.ui")
        self.aboutdialog = self.builder.get_object("aboutdialog")
        self.aboutdialog.run()

        self.aboutdialog.destroy()

#modules/mainwindow.py
class MainWindow:

    def __init__(self, builder, dic, controller):

        self.control = controller

        # get gui xml and/or signals
        dic["on_file_new_activate"] = self.control.newFile
        dic["on_file_open_activate"] = self.control.openFile
        dic["on_file_save_activate"] = self.control.saveFile
        dic["on_file_close_activate"] = self.control.closeFile
        ...

        # get needed gui objects
        self.mainWindow = builder.get_object("mainWindow")
        ...

Edit: alternative to auto attach signals to callbacks:
Untested code

def start_element(name, attrs):
    if name == "signal":
        if attrs["handler"]:
            handler = attrs["handler"]
            #Insert code to verify if handler is part of the collection
            #we want.
            self.handlerList.append(handler)

def extractSignals(uiFile)
    import xml.parsers.expat
    p = xml.parsers.expat.ParserCreate()
    p.StartElementHandler = self.start_element
    p.ParseFile(uiFile)

self.handlerList = []
extractSignals(uiFile)

for handler in handlerList:
    dic[handler] = eval(''. join(["self.", handler, "_cb"]))
Dave
  • 875
  • 6
  • 15
  • 1
    Thanks for answering. I can see that will work, but it's repeating the handler names which is just what I wanted to avoid :) – poolie Jan 10 '11 at 14:17
  • 1
    I'm learning by trying to answer. Something I will try... name my handlers with a convention that identifies which class would need it. Then parse the ui file to locate the handlers desired, or it could be all the handlers if the ui file is specific to the class. Code snipped added in my answer. Unsure if this helps you or not, but I'm sure learning fom it. :-) – Dave Jan 10 '11 at 17:02
1
builder.connect_signals
({ 
   "on_window_destroy" : gtk.main_quit, 
   "on_buttonQuit_clicked" : gtk.main_quit 
})
Flat Eric
  • 7,971
  • 9
  • 36
  • 45