What does the "widget" do in PyGTK - python

The sample code is like this:
class Something(gtk.Window):
def __init__(self):
...
treeview = gtk.TreeView(store)
tree_selection = treeview.get_selection()
tree_selection.set_mode(gtk.SELECTION_SINGLE)
tree_selection.connect("changed", self.onSelectionChanged)
...
def onSelectionChanged(self, tree_selection):
(model, pathlist) = tree_selection.get_selected()
self.selectedValue = model.get_value(pathlist, 0) - 1
How was the tree_selection passed in into onSelectionChanged function? I see many other similar uses, such as:
def onDestroy(self, widget):
gtk.main_quit()
What can we use this "widget" in the second param?

When you connect a handler (like onSelectionChanged) to a signal (like "changed"), the handler is added to the end of the list of signal handlers of that object.
When the object then emits the signal, it will call handlers (or "callbacks") in this list, pasing itself as the first argument.
This is true for all signals: the first argument is allways the object emitting the signal.
This way, you can e.g. call the get_selected() method of the TreeSelection object that called your onSelectionChanged method: you access it through the tree_selection parameter.

Related

Python lambda functions connected to Qt signals don't run when created in a different thread

When a QObject is created in a different thread and moved back to the main thread using QObject.moveToThread, lambda signals "disconnect" (they won't fire). My guess is that regular slots are linked to the QObject that is moved to the main thread, so they run in the main thread's event loop, but lambda functions aren't linked to the QObject, so there is no event loop for them to run.
This can be seen in the following short Python code:
if __name__ == "__main__":
from traits.etsconfig.api import ETSConfig
ETSConfig.toolkit = 'qt4'
import threading
from PySide import QtGui, QtCore
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
button.clicked.connect(lambda: self.mySlot('lambda'))
#
def mySlot(self, printing='object'):
print printing
#
myObj = None
# global variable to keep it in memory
def myThread(mainThread, button):
global myObj
myObj = MyObject(button)
myObj.moveToThread(mainThread)
#
if __name__ == '__main__':
appQT = QtGui.QApplication([])
#
myWidget = QtGui.QWidget()
myButton = QtGui.QPushButton('Press to see output')
myLayout = QtGui.QVBoxLayout(myWidget)
myLayout.addWidget(myButton)
myWidget.show()
#
mainThread = QtCore.QThread.currentThread()
if True:
# run myThread in a new thread
# prints only the first slot (object slot)
threading.Thread(target=myThread, args=[mainThread, myButton]).start()
else:
# run myThread in this thread
# prints both slots (object and lambda slots)
myThread(mainThread, myButton)
#
appQT.exec_()
You can see how the results differ from expected by changing the conditional from True to False.
When it is set to True, the output after clicking the button is:
object
When it is set to False, the output after clicking the button is:
object
lambda
My question is, can someone explain more precisely why it behaves in this way, and is there an easy way to keep the lambda slots working when moving the QObject back to the main thread?
Okay, I figured out some details about what is going on. It's still a partial answer, but I think it's more suitable as an answer rather than update to the question.
It seems that I was correct in my original question that the slots are linked to their instance object's QObject event loop (thread), but only if that slot is a bound method (has an object instance).
If you look into the PySide source code on Github, you'll see that it defines the receiver (the QObject that receives the signal) based on the type of slot it receives. So if you pass the QtSignal.connect() function a bound object method, the receiver is defined as the slotMethod.__self__ (which is PyMethod_GET_SELF(callback)). If you pass it a general callable object (for example a lambda function) which is not bound (no __self__ property), the receiver is simply set to NULL. The receiver tells Qt which event loop to send the signal to, so if it's NULL, it doesn't know to send the signal to the main event loop.
Here's a snippet of the PySide source code:
static bool getReceiver(QObject *source, const char* signal, PyObject* callback, QObject** receiver, PyObject** self, QByteArray* callbackSig)
{
bool forceGlobalReceiver = false;
if (PyMethod_Check(callback)) {
*self = PyMethod_GET_SELF(callback);
if (%CHECKTYPE[QObject*](*self))
*receiver = %CONVERTTOCPP[QObject*](*self);
forceGlobalReceiver = isDecorator(callback, *self);
} else if (PyCFunction_Check(callback)) {
*self = PyCFunction_GET_SELF(callback);
if (*self && %CHECKTYPE[QObject*](*self))
*receiver = %CONVERTTOCPP[QObject*](*self);
} else if (PyCallable_Check(callback)) {
// Ok, just a callable object
*receiver = 0;
*self = 0;
}
...
...
}
Does this help us fix our problem with the lambda functions? Not really... If we bind the lambda functions using the following (with types.MethodType), the behavior does not change:
import types
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
thisLambda = lambda self=self : self.mySlot('hello')
self.myLambda = types.MethodType( thisLambda, self )
button.clicked.connect(self.myLambda)
#
def mySlot(self, printing='object'):
print printing
Output:
object
This binding is definitely part of the problem since I have demonstrated below that the same behavior occurs with non-bound global methods, and by binding them using types.MethodType(), it fixes the problem:
import types
def abc(self):
print 'global1'
def xyz(self):
print 'global2'
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
self.xyz = types.MethodType( xyz, self )
button.clicked.connect(abc)
button.clicked.connect(self.xyz)
#
def mySlot(self, printing='object'):
print printing
Output:
object
global2
Anyways, it seems the simplest solution is just to not create the QObject in a separate thread in the first place, but this answer is a step towards understanding why it doesn't work properly.

How can I retrieve a signal parameter with New-Style Syntax?

Inside a custom button class I have a signal which emits when is dropped something into it. Here the relevant method:
class CustomButton
linked = QtCore.pyqtSignal()
...
def dropEvent(self, e):
print e.source().objectName()
print self.objectName()
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
#Emit linked signal with the drag object's name as parameter
self.linked.emit( e.source().objectName() )
return QtGui.QPushButton.dropEvent(self, QtGui.QDropEvent(QtCore.QPoint(e.pos().x(), e.pos().y()), e.possibleActions(), e.mimeData(), e.buttons(), e.modifiers()))
In otherhand, outside the class, in the main application I'm creating a slot, and a way to connect it to the signal.
#The slot (actually is just a python callable)
def on_link(self):
input = self.sender().objectName()[4:]
print input
#I need to print the name of the other object emitted as str parameter in the signal....
#Instance of custom button
custom_button.linked.connect( lambda: on_link( custom_button ) )
At this point I already know that I can get the sender() of the signal, however, I don't know how to get the parameter of self.linked.emit( e.source().objectName() ). I just know that first I have to change first this: linked = QtCore.pyqtSignal(str), but don't know how to write the connection or the slot and retrieve the e.source().objectName() in the emit signal.
The current design of the slot looks very confusing. At first glance, it looks like an instance method, but it is actually just a module-level function with a fake self parameter.
I would suggest something simpler, and more explicit, like this:
class CustomButton(QtGui.QPushButton):
linked = QtCore.pyqtSignal(str, str)
def dropEvent(self, event):
...
self.linked.emit(self.objectName(), event.source().objectName())
return QtGui.QPushButton.dropEvent(self, event)
def on_link(btn_name, src_name):
print btn_name, src_name
custom_button.linked.connect(on_link)
An alternative design would be to send the objects, instead of their names:
linked = QtCore.pyqtSignal(object, object)
...
self.linked.emit(self, event.source())
def on_link(button, source):
print button.objectName(), source.objectName()

How to get a variable from a different class?

I have a class named TerminalPanel which has the following method:
def OnSerialRead(self, event):
"""Handle input from the serial port."""
text = event.data
Now, I want to get the value of text from another method (get_data) in another class (GraphicsPanel).
How do I get this value? I tried marito = TerminalPanel.OnserialRead.text, but I get AttributeError: 'function' object has no attribute 'text'
Update
I have set-up the TerminalPanel class to include the variable text as part of it:
def OnSerialRead(self, event):
"""Handle input from the serial port."""
self.text = event.data
But now when I call it like this: marito = TerminalPanel.text inside my GraphicsPanel class I get the following error:
AttributeError: type object 'TerminalPanel' has no attribute 'text'
What am I doing wrong?
I think the problem is a lack of context and confusion what actually to do. I suppose you try to rework the wxTerminal.py from pyserial. I have to admit this part of pyserial is neither very readable (has been created by wxGlade) nor is it easy to understand (requires understanding of the wxPython event system and spinning off of threads (to keep the GUI responsive when reading on the serial port).
However, according to your problem description, it seems to me you want to do the following:
Get the value of event.text when it arrives and process it further in your GraphicsPanel instance.
You have to possibilities:
1) Bind to the event:
In your GraphicsPanel class:
class GraphicsPanel(wx.Panel):
def __init__(...):
...
self.parent = self.GetParent() # this should result in a wx.Frame instance!
# binding on wx.Frame required, because wx.Panel will not catch the event
self.parent.Bind(EVT_SERIALRX, self.OnSerialRead)
def OnSerialRead(self, event):
text = event.text
...
event.Skip() # important: you have to skip it also in ``TerminalPanel`` if you
# want to bind twice
2) Call the routine in GraphicsPanel instance with event.text as argument.
class TerminalPanel(wx.Panel):
def __init__(...):
...
self._grphpnl = GraphicsPanel(...)
self.Bind(EVT_SERIALRX, self.OnSerialRead)
def OnSerialRead(self, event):
text = event.text
# do something with it in GraphicsPanel instance
self._grphpnl.OnSerialText(text)
...
Somewhere else in your code:
class GraphicsPanel(wx.Panel):
...
def OnSerialText(text):
# do something with the text
That variable is defined at function scope. There is no way to get that value.
To make the value available to anything outside of the method you need to store the value on the class self.text = event.data or return the value return text
You need to decide what is right for the situation though, I'm guessing by the name of the function that returning the data is the right thing to do.
You need to return the value!
def OnSerialRead(self, event):
"""Handle input from the serial port."""
text = event.data
return text
Then you can access the value like this
marito = TerminalPanel.OnserialRead(event)
Or save the value in the class:
class Reader():
def OnSerialRead(...):
...
self.text = event.data
and then access the value from the class like so:
marito = Reader.text

Override static member in DaemonRunner

I'm trying to override the DaemonRunner in the python standard daemon process library (found here https://pypi.python.org/pypi/python-daemon/)
The DaemonRunner responds to command line arguments for start, stop, and restart, but I want to add a fourth option for status.
The class I want to override looks something like this:
class DaemonRunner(object):
def _start(self):
...etc
action_funcs = {'start': _start}
I've tried to override it like this:
class StatusDaemonRunner(DaemonRunner):
def _status(self):
...
DaemonRunner.action_funcs['status'] = _status
This works to some extent, but the problem is that every instance of DaemonRunner now have the new behaviour. Is it possible to override it without modifying every instance of DaemonRunner?
I would override action_functs to make it a non-static member of class StatusDaemonRunner(DaemonRunner).
In terms of code I would do:
class StatusDaemonRunner(runner.DaemonRunner):
def __init__(self, app):
self.action_funcs = runner.DaemonRunner.action_funcs.copy()
self.action_funcs['status'] = StatusDaemonRunner._status
super(StatusDaemonRunner, self).__init__(app)
def _status(self):
pass # do your stuff
Indeed, if we look at the getter in the implementation of DaemonRunner (here) we can see that it acess the attribute using self
def _get_action_func(self):
""" Return the function for the specified action.
Raises ``DaemonRunnerInvalidActionError`` if the action is
unknown.
"""
try:
func = self.action_funcs[self.action]
except KeyError:
raise DaemonRunnerInvalidActionError(
u"Unknown action: %(action)r" % vars(self))
return func
Hence the previous code should do the trick.

pygtk gtk.Builder.connect_signals onto multiple objects?

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.
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))
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.
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"]))
builder.connect_signals
({
"on_window_destroy" : gtk.main_quit,
"on_buttonQuit_clicked" : gtk.main_quit
})

Categories

Resources