Qt signals can be bound to functions with less args in Python

With this change it is possible to bind signals to functions
accepting less arguments. For example:

    def triggered():
      ...

    b = pya.QPushButton()
    b.clicked(triggered)
    b.emit_clicked(True)

wasn't working before since triggered() gets one parameter
(checked) and the call fails. Now, additional parameters are
ignored.
This commit is contained in:
Matthias Koefferlein 2017-02-24 23:48:47 +01:00
parent ede58ae728
commit 1f60e7729e
8 changed files with 126 additions and 5 deletions

View File

@ -44,13 +44,14 @@ reuse=0
qt="/opt/qt/4.6.3/include"
qt5="/opt/qt/5.5.1/include"
inst_dir_common=`pwd`/scripts/mkqtdecl_common
inst_dir=`pwd`/scripts/mkqtdecl
inst_dir4=`pwd`/scripts/mkqtdecl4
inst_dir5=`pwd`/scripts/mkqtdecl5
src_dir=`pwd`/src
src_name4=gsiqt4
src_name5=gsiqt5
src_name=$src_name4
inst_dir=$inst_dir4
while [ "$1" != "" ]; do

View File

@ -24,6 +24,7 @@
#include "pyaObject.h"
#include "pyaMarshal.h"
#include "pyaUtils.h"
#include "pyaConvert.h"
#include "pya.h"
#include "tlLog.h"
@ -214,19 +215,46 @@ void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gs
tl::Heap heap;
PythonRef argv (PyTuple_New (std::distance (meth->begin_arguments (), meth->end_arguments ())));
// TODO: callbacks with default arguments?
int args_avail = int (std::distance (meth->begin_arguments (), meth->end_arguments ()));
PythonRef argv (PyTuple_New (args_avail));
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, NULL, heap).release ());
}
PythonRef result;
for (std::vector<CallbackFunction>::const_iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) {
// determine the number of arguments required
int arg_count = args_avail;
if (args_avail > 0) {
PythonRef fc (PyObject_GetAttrString (c->callable ().get (), "__code__"));
if (fc) {
PythonRef ac (PyObject_GetAttrString (fc.get (), "co_argcount"));
if (ac) {
arg_count = python2c<int> (ac.get ());
if (PyObject_HasAttrString (c->callable ().get (), "__self__")) {
arg_count -= 1;
}
}
}
}
// use less arguments if applicable
if (arg_count == 0) {
result = PythonRef (PyObject_CallObject (c->callable ().get (), NULL));
} else if (arg_count < args_avail) {
PythonRef argv_less (PyTuple_GetSlice (argv.get (), 0, arg_count));
result = PythonRef (PyObject_CallObject (c->callable ().get (), argv_less.get ()));
} else {
result = PythonRef (PyObject_CallObject (c->callable ().get (), argv.get ()));
}
if (! result) {
check_error ();
}
}
push_arg (meth->ret_type (), ret, result.get (), heap);

View File

@ -480,6 +480,73 @@ class QtBindingTest(unittest.TestCase):
self.assertEqual(parent._destroyed(), True)
self.assertEqual(child._destroyed(), True)
def test_45(self):
triggered = ""
class TriggerLog:
triggered = ""
def triggered1(self, b):
if b:
self.triggered += "1"
else:
self.triggered += "0"
def triggered0(self):
self.triggered += "*"
# Ability to connect to signals while ignoring arguments and
# to emit signals
b = pya.QPushButton()
log = TriggerLog()
b.clicked(log.triggered1)
self.assertEqual(log.triggered, "")
b.emit_clicked(True)
self.assertEqual(log.triggered, "1")
b.emit_clicked(False)
self.assertEqual(log.triggered, "10")
b.clicked(log.triggered0)
b.emit_clicked(True)
self.assertEqual(log.triggered, "10*")
b.emit_clicked(False)
self.assertEqual(log.triggered, "10**")
# We do the same with free functions since they behave differently in Python:
global trigger_log
trigger_log = ""
def triggered_f0():
global trigger_log
trigger_log += "x"
def triggered_f1(b):
global trigger_log
if b:
trigger_log += "+"
else:
trigger_log += "-"
b.clicked(triggered_f1)
self.assertEqual(trigger_log, "")
b.emit_clicked(True)
self.assertEqual(trigger_log, "+")
b.emit_clicked(False)
self.assertEqual(trigger_log, "+-")
b.clicked(triggered_f0)
b.emit_clicked(True)
self.assertEqual(trigger_log, "+-x")
b.emit_clicked(False)
self.assertEqual(trigger_log, "+-xx")
# run unit tests
if __name__ == '__main__':

View File

@ -567,6 +567,31 @@ class QtBinding_TestClass < TestBase
end
def test_45
# Ability to connect to signals while ignoring arguments and
# to emit signals
b = RBA::QPushButton::new
triggered = ""
b.clicked { |checked| triggered += (checked ? "1" : "0") }
assert_equal(triggered, "")
b.emit_clicked(true)
assert_equal(triggered, "1")
b.emit_clicked(false)
assert_equal(triggered, "10")
b.clicked { triggered += "*" }
b.emit_clicked(true)
assert_equal(triggered, "10*")
b.emit_clicked(false)
assert_equal(triggered, "10**")
end
end
load("test_epilogue.rb")