accessing button handlers in multiple python modules

More
02 Aug 2018 22:40 #115462 by cmorley
You might look at stepconf and mulitfilebuilder.py.
Stepconf loads multiple GLADE files.
Now stepconf only uses one python files for call backs but I think that is by design rather then by obligation.

Chris M
More
03 Aug 2018 14:39 #115524 by tripwire
Thanks for everyone's replies and suggestions.

It looked like the get_handlers() function would be the solution, because that function can be placed in each python module, but I couldn't get it to work because it seems the connect_signals() is the only place to actually connect the signals with handlers and I don't understand how get_handlers() helps with that.

But anyway, I did write a simpler version of the load_handlers() function in gscreen.py that gathers signal handlers from multiple python files and places them in a dictionary suitable for connect_signals(). I will post it later today...
More
03 Aug 2018 20:41 #115552 by tripwire
Here is a working version of a single Glade (xml) file referencing 2 python modules for it's signal handlers. The "magic" function is mainly a simplification of the load_handlers() function in gscreen.py.

The Glade file:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk+" version="2.18"/>
  <!-- interface-naming-policy toplevel-contextual -->
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="onDestroy" swapped="no"/>
    <child>
      <object class="GtkHBox" id="hbox1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkButton" id="button1">
            <property name="label" translatable="yes">button w. local handler</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="pressed" handler="onButton1Pressed" swapped="no"/>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button2">
            <property name="label" translatable="yes">button w. external handler</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="pressed" handler="onButton2Pressed" swapped="no"/>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">label</property>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">label</property>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">3</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

The "main" python file:
#!/usr/bin/env python

import gtk
import external_module

# trampoline and load_handlers are used for custom displays
# (Copied from gscreen.py. I don't know how it works but seems necessary. Guru help?)
#
class Trampoline(object):
    def __init__(self,methods):
        self.methods = methods
        print("Trampoline instance")

    def __call__(self, *a, **kw):
        for m in self.methods:
            m(*a, **kw)

class Handler:   

    def onDestroy(self, *args):
        print("quit app")
        gtk.main_quit()

    def onButton1Pressed(self, button):
        print("Button1 Pressed")
        self.builder.get_object("label1").set_text("Button 1 pressed")


    # This function scans the given class object for signal handlers
    # and adds them to a (class-global) dictionary, which we'll pass to
    # connect_signals().
    #
    # This allows us to add signal handlers from "self" as well as from classes in external python modules
    #
    def _load_handlers(self, object):
        #print("Registering handlers in object %s" % (object))
        #for object in objects:
            if isinstance(object, dict):
                methods = dict.items()
            else:
                methods = map(lambda n: (n, getattr(object, n, None)), dir(object))
                for method,f in methods:
                    if method.startswith('_'):
                        continue
                    if callable(f):
                        print("Registering callback %s" % (method))                    
                        if method in self.handlers:
                            # Note, I would sometimes get an error here but it would occur at the end of the iteration process.
                            # I found that I could replace the following line with a simple print warning.
                            self.handlers[method].append(f)
                        else:
                            self.handlers[method] = [f]

            # Wrap lists in Trampoline, unwrap single functions
            # (Copied from gscreen.py. I don't know how it or the Trampoline class works
            # but it seems necessary.) Python guru help?
            for n,v in list(self.handlers.items()):  
                try:          
                    if len(v) == 1:                
                        self.handlers[n] = v[0]
                    else:
                        self.handlers[n] = Trampoline(v)
                except:
                    continue
                    # I kept getting the error, "no len(v) for type instancemethod" but that was always after the iteration was complete
                    # so I'm simply trapping and ignoring the error. (Guru help needed...)

    def __init__(self):
        self.builder = gtk.Builder()
        self.builder.add_from_file("multi-module-test.glade")

       # Get an instance of the external python module
        self.xmod = external_module.externalHandler(self.builder)

       # Create a dictionary to hold the signal-handler pairs
        self.handlers = {}

        # Call the magic function to load signal handlers from this and any other class module
       #
        self._load_handlers(self.xmod)
        self._load_handlers(self)
       
        #self.handlers = {"onButton1Pressed": self.onButton1Pressed, "onDestroy": self.onDestroy, "onButton2Pressed": self.xmod.onButton2Pressed}
        self.builder.connect_signals(self.handlers)        

        self.window = self.builder.get_object("window1")
        self.window.show_all()

try:
    app = Handler()
except KeyboardInterrupt:
    sys.exit(0)

gtk.main()

The "external" python module:
#!/usr/bin/env python

import gtk

class externalHandler:

    def onButton2Pressed(self, button):
        self.builder.get_object("label2").set_text("Button 2 pressed")
        print("Button2 Pressed")        

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

There are a few areas of uncertainty but it basically works. I'm certainly open to suggestions/clarifications to improve the code.
More
03 Aug 2018 20:58 #115553 by cmorley
Glad you got something working.
In General, the magic function searches for callable functions, some are inside a class some are outside. It adjusts the function call signature to suit. Then passes that to connect_signals() to parse so GTK can find the functions.


I'm curious - why don't you use gscreen to load your GUI code - thats what it was made for.

Chris M
More
03 Aug 2018 23:58 #115565 by tripwire

cmorley wrote: I'm curious - why don't you use gscreen to load your GUI code - thats what it was made for.


I was under the impression that gscreen is a pre-built GUI. I wanted to build one from scratch.
More
04 Aug 2018 00:35 #115570 by cmorley
Gscreen is infrastructure for controlling linuxcnc with a GTK2 interface.

There are many example screens -
gscreen
gaxis
spartan
industrial
etc.
all of them are gscreen based

Tere is a very basic example in the maunal:
linuxcnc.org/docs/2.7/html/gui/gscreen.h..._sheet_custom_screen

Chris M
More
04 Aug 2018 01:39 #115572 by tripwire

Gscreen is infrastructure for controlling linuxcnc with a GTK2 interface.


OK, thanks. I'll check that out...
Moderators: mhaberler
Time to create page: 0.078 seconds
Powered by Kunena Forum