From 1f60e7729ec7ec7f042c457d0de05e73d67a6e09 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 24 Feb 2017 23:48:47 +0100 Subject: [PATCH] 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. --- scripts/mkqtdecl.sh | 3 +- scripts/{mkqtdecl => mkqtdecl4}/allofqt.cpp | 0 scripts/{mkqtdecl => mkqtdecl4}/mkqtdecl.conf | 0 .../{mkqtdecl => mkqtdecl4}/mkqtdecl.events | 0 .../mkqtdecl.properties | 0 src/pya/pyaObject.cc | 36 ++++++++-- testdata/python/qtbinding.py | 67 +++++++++++++++++++ testdata/ruby/qtbinding.rb | 25 +++++++ 8 files changed, 126 insertions(+), 5 deletions(-) rename scripts/{mkqtdecl => mkqtdecl4}/allofqt.cpp (100%) rename scripts/{mkqtdecl => mkqtdecl4}/mkqtdecl.conf (100%) rename scripts/{mkqtdecl => mkqtdecl4}/mkqtdecl.events (100%) rename scripts/{mkqtdecl => mkqtdecl4}/mkqtdecl.properties (100%) diff --git a/scripts/mkqtdecl.sh b/scripts/mkqtdecl.sh index df05fc4c4..6444ef4a1 100755 --- a/scripts/mkqtdecl.sh +++ b/scripts/mkqtdecl.sh @@ -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 diff --git a/scripts/mkqtdecl/allofqt.cpp b/scripts/mkqtdecl4/allofqt.cpp similarity index 100% rename from scripts/mkqtdecl/allofqt.cpp rename to scripts/mkqtdecl4/allofqt.cpp diff --git a/scripts/mkqtdecl/mkqtdecl.conf b/scripts/mkqtdecl4/mkqtdecl.conf similarity index 100% rename from scripts/mkqtdecl/mkqtdecl.conf rename to scripts/mkqtdecl4/mkqtdecl.conf diff --git a/scripts/mkqtdecl/mkqtdecl.events b/scripts/mkqtdecl4/mkqtdecl.events similarity index 100% rename from scripts/mkqtdecl/mkqtdecl.events rename to scripts/mkqtdecl4/mkqtdecl.events diff --git a/scripts/mkqtdecl/mkqtdecl.properties b/scripts/mkqtdecl4/mkqtdecl.properties similarity index 100% rename from scripts/mkqtdecl/mkqtdecl.properties rename to scripts/mkqtdecl4/mkqtdecl.properties diff --git a/src/pya/pyaObject.cc b/src/pya/pyaObject.cc index bd0d501d8..af3aee816 100644 --- a/src/pya/pyaObject.cc +++ b/src/pya/pyaObject.cc @@ -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::const_iterator c = m_cbfuncs.begin (); c != m_cbfuncs.end (); ++c) { - result = PythonRef (PyObject_CallObject (c->callable ().get (), argv.get ())); + + // 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 (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); diff --git a/testdata/python/qtbinding.py b/testdata/python/qtbinding.py index 314523c53..ea7e3593f 100644 --- a/testdata/python/qtbinding.py +++ b/testdata/python/qtbinding.py @@ -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__': diff --git a/testdata/ruby/qtbinding.rb b/testdata/ruby/qtbinding.rb index b960ec5a0..0555a5050 100644 --- a/testdata/ruby/qtbinding.rb +++ b/testdata/ruby/qtbinding.rb @@ -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")