[pygobject] GObject.bind_property: Support transform functions
- From: Martin Pitt <martinpitt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pygobject] GObject.bind_property: Support transform functions
- Date: Fri, 1 Jun 2012 08:42:51 +0000 (UTC)
commit edc17e703e1a05e20545d3df9167ceb076450443
Author: Bastian Winkler <buz netbuz org>
Date: Wed May 16 11:13:05 2012 +0200
GObject.bind_property: Support transform functions
Add support for optional transformation functions to
pygobject_bind_property(). It uses a custom PyGClosure to marshal the
return value correctly.
https://bugzilla.gnome.org/show_bug.cgi?id=676169
Signed-off-by: Martin Pitt <martinpitt gnome org>
gi/_gobject/pygobject.c | 130 ++++++++++++++++++++++++++++++++++++++++++++---
tests/test_gobject.py | 59 +++++++++++++++++++++
2 files changed, 181 insertions(+), 8 deletions(-)
---
diff --git a/gi/_gobject/pygobject.c b/gi/_gobject/pygobject.c
index 3e96a8e..75c8ba3 100644
--- a/gi/_gobject/pygobject.c
+++ b/gi/_gobject/pygobject.c
@@ -1485,18 +1485,113 @@ pygobject_set_properties(PyGObject *self, PyObject *args, PyObject *kwargs)
return result;
}
+/* custom closure for gobject bindings */
+static void
+pygbinding_closure_invalidate(gpointer data, GClosure *closure)
+{
+ PyGClosure *pc = (PyGClosure *)closure;
+ PyGILState_STATE state;
+
+ state = pyglib_gil_state_ensure();
+ Py_XDECREF(pc->callback);
+ Py_XDECREF(pc->extra_args);
+ pyglib_gil_state_release(state);
+
+ pc->callback = NULL;
+ pc->extra_args = NULL;
+}
+
+static void
+pygbinding_marshal (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ PyGILState_STATE state;
+ PyGClosure *pc = (PyGClosure *)closure;
+ PyObject *params, *ret;
+ GValue *out_value;
+
+ state = pyglib_gil_state_ensure();
+
+ /* construct Python tuple for the parameter values */
+ params = PyTuple_New(2);
+ PyTuple_SetItem (params, 0, pyg_value_as_pyobject(¶m_values[0], FALSE));
+ PyTuple_SetItem (params, 1, pyg_value_as_pyobject(¶m_values[1], FALSE));
+
+ /* params passed to function may have extra arguments */
+ if (pc->extra_args) {
+ PyObject *tuple = params;
+ params = PySequence_Concat(tuple, pc->extra_args);
+ Py_DECREF(tuple);
+ }
+ ret = PyObject_CallObject(pc->callback, params);
+ if (!ret) {
+ PyErr_Print ();
+ goto out;
+ } else if (ret == Py_None) {
+ g_value_set_boolean (return_value, FALSE);
+ goto out;
+ }
+
+ out_value = g_value_get_boxed (¶m_values[2]);
+ if (pyg_value_from_pyobject (out_value, ret) != 0) {
+ PyErr_SetString (PyExc_ValueError, "can't convert value");
+ PyErr_Print ();
+ g_value_set_boolean (return_value, FALSE);
+ } else {
+ g_value_set_boolean (return_value, TRUE);
+ }
+
+ Py_DECREF(ret);
+
+out:
+ Py_DECREF(params);
+ pyglib_gil_state_release(state);
+}
+
+static GClosure *
+pygbinding_closure_new (PyObject *callback, PyObject *extra_args)
+{
+ GClosure *closure;
+
+ g_return_val_if_fail(callback != NULL, NULL);
+ closure = g_closure_new_simple(sizeof(PyGClosure), NULL);
+ g_closure_add_invalidate_notifier(closure, NULL, pygbinding_closure_invalidate);
+ g_closure_set_marshal(closure, pygbinding_marshal);
+ Py_INCREF(callback);
+ ((PyGClosure *)closure)->callback = callback;
+ if (extra_args && extra_args != Py_None) {
+ Py_INCREF(extra_args);
+ if (!PyTuple_Check(extra_args)) {
+ PyObject *tmp = PyTuple_New(1);
+ PyTuple_SetItem(tmp, 0, extra_args);
+ extra_args = tmp;
+ }
+ ((PyGClosure *)closure)->extra_args = extra_args;
+ }
+ return closure;
+}
static PyObject *
pygobject_bind_property(PyGObject *self, PyObject *args)
{
gchar *source_name, *target_name;
gchar *source_canon, *target_canon;
- PyObject *target, *source_repr, *target_repr, *pybinding;
+ PyObject *target, *source_repr, *target_repr;
+ PyObject *transform_to, *transform_from, *user_data = NULL;
GBinding *binding;
GBindingFlags flags = G_BINDING_DEFAULT;
+ GClosure *to_closure = NULL, *from_closure = NULL;
+
+ transform_from = NULL;
+ transform_to = NULL;
- if (!PyArg_ParseTuple(args, "sOs|i:GObject.bind_property",
- &source_name, &target, &target_name, &flags))
+ if (!PyArg_ParseTuple(args, "sOs|iOOO:GObject.bind_property",
+ &source_name, &target, &target_name, &flags,
+ &transform_to, &transform_from, &user_data))
return NULL;
CHECK_GOBJECT(self);
@@ -1505,12 +1600,31 @@ pygobject_bind_property(PyGObject *self, PyObject *args)
return NULL;
}
+ if (transform_to && transform_to != Py_None) {
+ if (!PyCallable_Check (transform_to)) {
+ PyErr_SetString (PyExc_TypeError,
+ "transform_to must be callable or None");
+ return NULL;
+ }
+ to_closure = pygbinding_closure_new (transform_to, user_data);
+ }
+
+ if (transform_from && transform_from != Py_None) {
+ if (!PyCallable_Check (transform_from)) {
+ PyErr_SetString (PyExc_TypeError,
+ "transform_from must be callable or None");
+ return NULL;
+ }
+ from_closure = pygbinding_closure_new (transform_from, user_data);
+ }
+
/* Canonicalize underscores to hyphens. Note the results must be freed. */
source_canon = g_strdelimit(g_strdup(source_name), "_", '-');
target_canon = g_strdelimit(g_strdup(target_name), "_", '-');
- binding = g_object_bind_property(G_OBJECT(self->obj), source_canon,
- pygobject_get(target), target_canon, flags);
+ binding = g_object_bind_property_with_closures (G_OBJECT(self->obj), source_canon,
+ pygobject_get(target), target_canon,
+ flags, to_closure, from_closure);
g_free(source_canon);
g_free(target_canon);
source_canon = target_canon = NULL;
@@ -1519,14 +1633,14 @@ pygobject_bind_property(PyGObject *self, PyObject *args)
source_repr = PyObject_Repr((PyObject*)self);
target_repr = PyObject_Repr(target);
PyErr_Format(PyExc_TypeError, "Cannot create binding from %s.%s to %s.%s",
- PYGLIB_PyUnicode_AsString(source_repr), source_name,
- PYGLIB_PyUnicode_AsString(target_repr), target_name);
+ PYGLIB_PyUnicode_AsString(source_repr), source_name,
+ PYGLIB_PyUnicode_AsString(target_repr), target_name);
Py_DECREF(source_repr);
Py_DECREF(target_repr);
return NULL;
}
- return pygbinding_weak_ref_new(binding);
+ return pygbinding_weak_ref_new(G_OBJECT (binding));
}
static PyObject *
diff --git a/tests/test_gobject.py b/tests/test_gobject.py
index f1595f6..d225d50 100644
--- a/tests/test_gobject.py
+++ b/tests/test_gobject.py
@@ -386,6 +386,65 @@ class TestPropertyBindings(unittest.TestCase):
self.assertEqual(self.source.int_prop, 2)
self.assertEqual(self.target.int_prop, 2)
+ def testTransformToOnly(self):
+ def transform_to(binding, value, user_data=None):
+ self.assertEqual(user_data, 'test-data')
+ return value * 2
+
+ binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+ GObject.BindingFlags.DEFAULT,
+ transform_to, None, 'test-data')
+ binding = binding # PyFlakes
+
+ self.source.int_prop = 1
+ self.assertEqual(self.source.int_prop, 1)
+ self.assertEqual(self.target.int_prop, 2)
+
+ self.target.props.int_prop = 1
+ self.assertEqual(self.source.int_prop, 1)
+ self.assertEqual(self.target.int_prop, 1)
+
+ def testTransformFromOnly(self):
+ def transform_from(binding, value, user_data=None):
+ self.assertEqual(user_data, None)
+ return value * 2
+
+ binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+ GObject.BindingFlags.BIDIRECTIONAL,
+ None, transform_from)
+ binding = binding # PyFlakes
+
+ self.source.int_prop = 1
+ self.assertEqual(self.source.int_prop, 1)
+ self.assertEqual(self.target.int_prop, 1)
+
+ self.target.props.int_prop = 1
+ self.assertEqual(self.source.int_prop, 2)
+ self.assertEqual(self.target.int_prop, 1)
+
+ def testTransformBidrectional(self):
+ def transform_to(binding, value, user_data=None):
+ self.assertEqual(user_data, 'test-data')
+ return value * 2
+
+ def transform_from(binding, value, user_data=None):
+ self.assertEqual(user_data, 'test-data')
+ return value / 2
+
+ # bidirectional bindings
+ binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+ GObject.BindingFlags.BIDIRECTIONAL,
+ transform_to, transform_from, 'test-data')
+ binding = binding # PyFlakes
+
+ self.source.int_prop = 1
+ self.assertEqual(self.source.int_prop, 1)
+ self.assertEqual(self.target.int_prop, 2)
+
+ self.target.props.int_prop = 4
+ self.assertEqual(self.source.int_prop, 2)
+ self.assertEqual(self.target.int_prop, 4)
+
def testExplicitUnbindClearsConnection(self):
self.assertEqual(self.source.int_prop, 0)
self.assertEqual(self.target.int_prop, 0)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]