summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGustavo J. A. M. Carneiro <gjc@src.gnome.org>2006-01-11 16:09:18 +0000
committerGustavo J. A. M. Carneiro <gjc@src.gnome.org>2006-01-11 16:09:18 +0000
commite370df75fb2cfdd780188af1e81416a5443cb6e9 (patch)
tree0e54a2fdc133bc24d05caa5782eb8021efd1d737
parenta4834ddbe8dfe5b0f131ba3c1331de0b05f67a0f (diff)
downloadpygobject-e370df75fb2cfdd780188af1e81416a5443cb6e9.tar.gz
pygobject-e370df75fb2cfdd780188af1e81416a5443cb6e9.tar.xz
pygobject-e370df75fb2cfdd780188af1e81416a5443cb6e9.zip
signal accumulators
-rw-r--r--ChangeLog10
-rw-r--r--gobject/gobjectmodule.c100
-rw-r--r--tests/runtests.py3
-rw-r--r--tests/test_signal.py52
4 files changed, 161 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index 64821e6..cdd7e2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
2006-01-11 Gustavo J. A. M. Carneiro <gjc@inescporto.pt>
+ * gobject/gobjectmodule.c (_pyg_signal_accumulator),
+ (create_signal, pyg_signal_accumulator_true_handled),
+ (initgobject): Bug 155380 -- Add support for signal accumulators.
+
+ * tests/runtests.py: Add 'test_enum', 'test_conversion' to
+ SKIP_FILES, since they depend on pygtk modules so don't work
+ anymore. This should be eventually fixed, though.
+
+ * tests/test_signal.py: Test signal accumulators.
+
* gobject/pygtype.c (object_doc_descr_get): Reorder
properties/signals documentation more nicely: signals + properties
from each type are presented, with types ranging from the leaf
diff --git a/gobject/gobjectmodule.c b/gobject/gobjectmodule.c
index 06572cd..77008ed 100644
--- a/gobject/gobjectmodule.c
+++ b/gobject/gobjectmodule.c
@@ -30,6 +30,7 @@
static PyObject *gerror_exc = NULL;
static gboolean use_gil_state_api = FALSE;
+static PyObject *_pyg_signal_accumulator_true_handled_func;
GQuark pyginterface_type_key;
GQuark pygobject_class_init_key;
@@ -458,6 +459,61 @@ pyg_object_class_init(GObjectClass *class, PyObject *py_class)
class->get_property = pyg_object_get_property;
}
+typedef struct _PyGSignalAccumulatorData {
+ PyObject *callable;
+ PyObject *user_data;
+} PyGSignalAccumulatorData;
+
+static gboolean
+_pyg_signal_accumulator(GSignalInvocationHint *ihint,
+ GValue *return_accu,
+ const GValue *handler_return,
+ gpointer _data)
+{
+ PyObject *py_ihint, *py_return_accu, *py_handler_return, *py_detail;
+ PyObject *py_retval;
+ gboolean retval = FALSE;
+ PyGSignalAccumulatorData *data = _data;
+ PyGILState_STATE state;
+
+ state = pyg_gil_state_ensure();
+ if (ihint->detail)
+ py_detail = PyString_FromString(g_quark_to_string(ihint->detail));
+ else {
+ Py_INCREF(Py_None);
+ py_detail = Py_None;
+ }
+
+ py_ihint = Py_BuildValue("lNi", (long int) ihint->signal_id,
+ py_detail, ihint->run_type);
+ py_handler_return = pyg_value_as_pyobject(handler_return, TRUE);
+ py_return_accu = pyg_value_as_pyobject(return_accu, FALSE);
+ if (data->user_data)
+ py_retval = PyObject_CallFunction(data->callable, "NNNO", py_ihint,
+ py_return_accu, py_handler_return,
+ data->user_data);
+ else
+ py_retval = PyObject_CallFunction(data->callable, "NNN", py_ihint,
+ py_return_accu, py_handler_return);
+ if (!py_retval)
+ PyErr_Print();
+ else {
+ if (!PyTuple_Check(py_retval) || PyTuple_Size(py_retval) != 2) {
+ PyErr_SetString(PyExc_TypeError, "accumulator function must return"
+ " a (bool, object) tuple");
+ PyErr_Print();
+ } else {
+ retval = PyObject_IsTrue(PyTuple_GET_ITEM(py_retval, 0));
+ if (pyg_value_from_pyobject(return_accu, PyTuple_GET_ITEM(py_retval, 1))) {
+ PyErr_Print();
+ }
+ }
+ Py_DECREF(py_retval);
+ }
+ pyg_gil_state_release(state);
+ return retval;
+}
+
static gboolean
create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple)
{
@@ -467,9 +523,13 @@ create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple)
guint n_params, i;
GType *param_types;
guint signal_id;
+ GSignalAccumulator accumulator = NULL;
+ PyGSignalAccumulatorData *accum_data = NULL;
+ PyObject *py_accum = NULL, *py_accum_data = NULL;
- if (!PyArg_ParseTuple(tuple, "iOO", &signal_flags, &py_return_type,
- &py_param_types)) {
+ if (!PyArg_ParseTuple(tuple, "iOO|OO", &signal_flags, &py_return_type,
+ &py_param_types, &py_accum, &py_accum_data))
+ {
gchar buf[128];
PyErr_Clear();
@@ -478,6 +538,15 @@ create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple)
return FALSE;
}
+ if (py_accum && py_accum != Py_None && !PyCallable_Check(py_accum))
+ {
+ gchar buf[128];
+
+ g_snprintf(buf, sizeof(buf), "accumulator for __gsignals__['%s'] must be callable", signal_name);
+ PyErr_SetString(PyExc_TypeError, buf);
+ return FALSE;
+ }
+
return_type = pyg_type_from_object(py_return_type);
if (!return_type)
return FALSE;
@@ -502,9 +571,22 @@ create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple)
Py_DECREF(item);
}
+ if (py_accum == _pyg_signal_accumulator_true_handled_func)
+ accumulator = g_signal_accumulator_true_handled;
+ else {
+ if (py_accum != NULL && py_accum != Py_None) {
+ accum_data = g_new(PyGSignalAccumulatorData, 1);
+ accum_data->callable = py_accum;
+ Py_INCREF(py_accum);
+ accum_data->user_data = py_accum_data;
+ Py_XINCREF(py_accum_data);
+ accumulator = _pyg_signal_accumulator;
+ }
+ }
+
signal_id = g_signal_newv(signal_name, instance_type, signal_flags,
pyg_signal_class_closure_get(),
- (GSignalAccumulator)0, NULL,
+ accumulator, accum_data,
(GSignalCMarshaller)0,
return_type, n_params, param_types);
g_free(param_types);
@@ -2231,6 +2313,14 @@ pyg_main_depth(PyObject *unused)
return PyInt_FromLong(g_main_depth());
}
+static PyObject *
+pyg_signal_accumulator_true_handled(PyObject *unused, PyObject *args)
+{
+ PyErr_SetString(PyExc_TypeError, "signal_accumulator_true_handled can only"
+ " be used as accumulator argument when registering signals");
+ return NULL;
+}
+
static PyMethodDef pygobject_functions[] = {
{ "type_name", pyg_type_name, METH_VARARGS },
{ "type_from_name", pyg_type_from_name, METH_VARARGS },
@@ -2258,6 +2348,7 @@ static PyMethodDef pygobject_functions[] = {
{ "markup_escape_text", (PyCFunction)pyg_markup_escape_text, METH_VARARGS|METH_KEYWORDS },
{ "get_current_time", (PyCFunction)pyg_get_current_time, METH_NOARGS },
{ "main_depth", (PyCFunction)pyg_main_depth, METH_NOARGS },
+ { "signal_accumulator_true_handled", (PyCFunction)pyg_signal_accumulator_true_handled, METH_VARARGS },
{ NULL, NULL, 0 }
};
@@ -2929,4 +3020,7 @@ initgobject(void)
g_log_set_handler("GThread", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING,
_log_func, warning);
+ /* signal registration recognizes this special accumulator 'constant' */
+ _pyg_signal_accumulator_true_handled_func = \
+ PyDict_GetItemString(d, "signal_accumulator_true_handled");
}
diff --git a/tests/runtests.py b/tests/runtests.py
index 3ae7185..da866d4 100644
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -21,7 +21,8 @@ else:
common.importModules(buildDir=buildDir,
srcDir=srcDir)
-SKIP_FILES = ['common', 'runtests']
+SKIP_FILES = ['common', 'runtests',
+ 'test_enum', 'test_conversion']
dir = os.path.split(os.path.abspath(__file__))[0]
os.chdir(dir)
diff --git a/tests/test_signal.py b/tests/test_signal.py
index 36014fc..87c62f6 100644
--- a/tests/test_signal.py
+++ b/tests/test_signal.py
@@ -77,5 +77,57 @@ class TestList(unittest.TestCase):
def testListObject(self):
self.assertEqual(gobject.signal_list_names(C), ('my-signal',))
+
+def my_accumulator(ihint, return_accu, handler_return, user_data):
+ """An accumulator that stops emission when the sum of handler
+ returned values reaches 3"""
+ assert user_data == "accum data"
+ if return_accu >= 3:
+ return False, return_accu
+ return True, return_accu + handler_return
+
+class Foo(gobject.GObject):
+ __gsignals__ = {
+ 'my-acc-signal': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_INT,
+ (), my_accumulator, "accum data"),
+ 'my-other-acc-signal': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN,
+ (), gobject.signal_accumulator_true_handled)
+ }
+
+class TestAccumulator(unittest.TestCase):
+
+ def testAccumulator(self):
+ inst = Foo()
+ inst.connect("my-acc-signal", lambda obj: 1)
+ inst.connect("my-acc-signal", lambda obj: 2)
+ ## the value returned in the following handler will not be
+ ## considered, because at this point the accumulator already
+ ## reached its limit.
+ inst.connect("my-acc-signal", lambda obj: 3)
+ retval = inst.emit("my-acc-signal")
+ self.assertEqual(retval, 3)
+
+ def testAccumulatorTrueHandled(self):
+ inst = Foo()
+ inst.connect("my-other-acc-signal", self._true_handler1)
+ inst.connect("my-other-acc-signal", self._true_handler2)
+ ## the following handler will not be called because handler2
+ ## returns True, so it should stop the emission.
+ inst.connect("my-other-acc-signal", self._true_handler3)
+ self.__true_val = None
+ inst.emit("my-other-acc-signal")
+ self.assertEqual(self.__true_val, 2)
+
+ def _true_handler1(self, obj):
+ self.__true_val = 1
+ return False
+ def _true_handler2(self, obj):
+ self.__true_val = 2
+ return True
+ def _true_handler3(self, obj):
+ self.__true_val = 3
+ return False
+
+
if __name__ == '__main__':
unittest.main()