I bind 2 keys to call 2 methods of my class. Is it possible to call the some method and knowing which key was pressed?
def initGui(self):
self.keyAction = QAction("Test Plugin", self.iface.mainWindow())
self.iface.registerMainWindowAction(self.keyAction, self.toggle_key_1)
self.iface.addPluginToMenu("&Test plugins", self.keyAction)
QObject.connect(self.keyAction, SIGNAL("triggered()"), self.toogle_layer_1)
self.keyAction = QAction("Test Plugin", self.iface.mainWindow())
self.iface.registerMainWindowAction(self.keyAction, self.toggle_key_2)
self.iface.addPluginToMenu("&Test plugins", self.keyAction)
QObject.connect(self.keyAction, SIGNAL("triggered()"), self.toogle_layer_2)
Yes, you can know which object has triggered the signal from your slot (function) with using QObject::sender() function. As Qt docs say:
Returns a pointer to the object that sent the signal, if called in a
slot activated by a signal; otherwise it returns 0. The pointer is
valid only during the execution of the slot that calls this function
from this object's thread context.
Update:
For example, in your slot you can write:
def toogle_layer(self):
action = QtCore.QObject.sender()
if action == self.action1:
# do something
elif action == self.action2:
# do something else
Related
I have a cache handler function that processes functions placed in a queue by threads.
The cache handler is called when the console is idle. I need to be able to know if a function is being processed by the cache handler, or if it's executing outside of the cache handler loop.
Some logic like so:
If cache handler in referring function stack, return True:
Here's the cache handler code:
# Processing all console items in queue.
def process_console_queue():
log = StandardLogger(logger_name='console_queue_handler')
if not CONSOLE_CH.CONSOLE_QUEUE:
return
set_console_lock()
CONSOLE_CH.PROCESSING_CONSOLE_QUEUE.acquire()
print('\nOutputs held during your last input operation: ')
while CONSOLE_CH.CONSOLE_QUEUE:
q = CONSOLE_CH.CONSOLE_QUEUE[0]
remove_from_console_queue()
q[0](*q[1], **q[2])
CONSOLE_CH.PROCESSING_CONSOLE_QUEUE.release()
release_console_lock()
return
If that code calls a function which calls a function which calls a function.... (anywhere in that chain is called by process_console_queue) return True within the called function.
How's that done?
How about using a global threading.local object with an attribute, in_cache_handler?
Have the cache handler set the attribute to True on entry, and set it to False on exit. Then any function that examines the attribute can tell whether the cache handler is somewhere below on the stack.
import threading
thread_local_object = threading.local()
thread_local_object.in_cache_handler = False
def cache_handler(...):
try:
thread_local_object.in_cache_handler = True
...
finally:
thread_local_object.in_cache_handler = False
def some_random_function(...):
if thread_local_object.in_cache_handler:
...
else
...
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.
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()
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.
I need to check the signal for the presence of the listener, before it is emitted.
class Test(QObject):
test = pyqtSignal(str,dict)
def run(self):
if self.receivers(SIGNAL("test(str,dict)"):
self.test.emit('blablabla',{})`
The signal is connected to the slot right and successfully emits signals.
When checking the signature signal, the method QObject.receivers() shows that this signal is not connected.
I understood, reason was incorrect signature, I did not find a method, to specify the faithful signature of signal.
In pyqt5 SIGNAL is deprecated. It is replaced with signal attribute of each QObject
if QObject.receivers(QObject.signal) > 0:
print('signal connected')
To Check QPushButton signal clicked() is connected to any slot
button = QPushButton()
.
.
if button.receivers(button.clicked) > 0:
.....
The signature for your signal is "test(QString, PyQt_PyObject)".
So obviously, str is mapped to QString and other native python object types, dict, list... are mapped to the C++ type PyQt_PyObject.
The list of signal signatures can be obtained through the QMetaObject associated with your object:
test = Test()
metaobject = test.metaObject()
for i in range(metaobject.methodCount()):
print(metaobject.method(i).signature())