diff options
| author | Gustavo J. A. M. Carneiro <gjc@src.gnome.org> | 2005-07-09 18:25:32 +0000 |
|---|---|---|
| committer | Gustavo J. A. M. Carneiro <gjc@src.gnome.org> | 2005-07-09 18:25:32 +0000 |
| commit | 83a43997b2136c6f5337219ddfe6aa6829d66fae (patch) | |
| tree | e31a18b2a0f0fcc7cb55849c88c2afa12aa85d3d | |
| parent | 49eabd6c5ab0b3c849bd03bd2149b47e50f19aee (diff) | |
| download | pygobject-83a43997b2136c6f5337219ddfe6aa6829d66fae.tar.gz pygobject-83a43997b2136c6f5337219ddfe6aa6829d66fae.tar.xz pygobject-83a43997b2136c6f5337219ddfe6aa6829d66fae.zip | |
Bug 161177: Allow creation of python classes from g_object_new
| -rw-r--r-- | gobject/gobjectmodule.c | 172 | ||||
| -rw-r--r-- | gobject/pygobject-private.h | 15 | ||||
| -rw-r--r-- | gobject/pygobject.c | 18 | ||||
| -rw-r--r-- | gobject/pygobject.h | 11 | ||||
| -rw-r--r-- | tests/Makefile.am | 2 | ||||
| -rw-r--r-- | tests/runtests.py | 2 | ||||
| -rw-r--r-- | tests/test_properties.py | 35 | ||||
| -rw-r--r-- | tests/test_subtype.py | 33 | ||||
| -rw-r--r-- | tests/testhelpermodule.c | 75 | ||||
| -rw-r--r-- | tests/testmodule.py | 9 |
10 files changed, 347 insertions, 25 deletions
diff --git a/gobject/gobjectmodule.c b/gobject/gobjectmodule.c index e151306..19017ac 100644 --- a/gobject/gobjectmodule.c +++ b/gobject/gobjectmodule.c @@ -26,6 +26,7 @@ #include "pygobject-private.h" #include "pythread.h" +#include <gobject/gvaluecollector.h> static PyObject *gerror_exc = NULL; static gboolean use_gil_state_api = FALSE; @@ -40,6 +41,9 @@ GQuark pygboxed_marshal_key; GQuark pygenum_class_key; GQuark pygflags_class_key; GQuark pygpointer_class_key; +GQuark pygobject_has_updated_constructor_key; + + static void pyg_flags_add_constants(PyObject *module, GType flags_type, const gchar *strip_prefix); @@ -1028,6 +1032,56 @@ get_type_name_for_class(PyTypeObject *class) return type_name; } + +static GStaticPrivate pygobject_contruction_wrapper = G_STATIC_PRIVATE_INIT; + +static inline void +pygobject_init_wrapper_set(PyObject *wrapper) +{ + g_static_private_set(&pygobject_contruction_wrapper, wrapper, NULL); +} + +static inline PyObject * +pygobject_init_wrapper_get(void) +{ + return (PyObject *) g_static_private_get(&pygobject_contruction_wrapper); +} + +static void +pygobject__g_instance_init(GTypeInstance *instance, + gpointer g_class) +{ + GObject *object = (GObject *) instance; + PyObject *wrapper, *args, *kwargs; + + if (!g_type_get_qdata(G_OBJECT_TYPE(object), pygobject_has_updated_constructor_key)) + return; + + wrapper = g_object_get_qdata(object, pygobject_wrapper_key); + if (wrapper == NULL) { + wrapper = pygobject_init_wrapper_get(); + if (wrapper) { + g_object_set_qdata(object, pygobject_wrapper_key, wrapper); + } + } + pygobject_init_wrapper_set(NULL); + if (wrapper == NULL) { + /* this looks like a python object created through + * g_object_new -> we have no python wrapper, so create it + * now */ + PyGILState_STATE state; + state = pyg_gil_state_ensure(); + wrapper = pygobject_new_full(object, FALSE); + args = PyTuple_New(0); + kwargs = PyDict_New(); + if (wrapper->ob_type->tp_init(wrapper, args, kwargs)) + PyErr_Print(); + Py_DECREF(args); + Py_DECREF(kwargs); + pyg_gil_state_release(state); + } +} + int pyg_type_register(PyTypeObject *class, char *type_name) { @@ -1036,6 +1090,7 @@ pyg_type_register(PyTypeObject *class, char *type_name) gint i; GTypeQuery query; gpointer gclass; + gpointer has_new_constructor_api; GTypeInfo type_info = { 0, /* class_size */ @@ -1048,7 +1103,7 @@ pyg_type_register(PyTypeObject *class, char *type_name) 0, /* instance_size */ 0, /* n_preallocs */ - (GInstanceInitFunc) NULL + (GInstanceInitFunc) pygobject__g_instance_init }; @@ -1088,6 +1143,16 @@ pyg_type_register(PyTypeObject *class, char *type_name) PyDict_SetItemString(class->tp_dict, "__gtype__", gtype); Py_DECREF(gtype); + /* propagate new constructor API compatility flag from parent to child type */ + has_new_constructor_api = g_type_get_qdata(parent_type, + pygobject_has_updated_constructor_key); + if (has_new_constructor_api == NULL) + g_warning("Constructor wrapper for %s needs to be updated to the new API", + g_type_name(parent_type)); + else + g_type_set_qdata(instance_type, pygobject_has_updated_constructor_key, + has_new_constructor_api); + /* if no __doc__, set it to the auto doc descriptor */ if (PyDict_GetItemString(class->tp_dict, "__doc__") == NULL) { PyDict_SetItemString(class->tp_dict, "__doc__", @@ -1572,8 +1637,12 @@ pyg_object_new (PyGObject *self, PyObject *args, PyObject *kwargs) g_type_class_unref(class); if (obj) - return pygobject_new ((GObject *)obj); - return NULL; + self = (PyGObject *) pygobject_new ((GObject *)obj); + else + self = NULL; + g_object_unref(obj); + + return (PyObject *) self; } static gint @@ -2455,6 +2524,95 @@ pyg_parse_constructor_args(GType obj_type, return TRUE; } +int +pygobject_constructv(PyGObject *self, + guint n_parameters, + GParameter *parameters) +{ + if (self->obj == NULL) { + pygobject_init_wrapper_set((PyObject *) self); + self->obj = g_object_newv(pyg_type_from_object((PyObject *) self), + n_parameters, parameters); + pygobject_init_wrapper_set(NULL); + pygobject_register_wrapper((PyObject *) self); + } else { + int i; + for (i = 0; i < n_parameters; ++i) + g_object_set_property(self->obj, parameters[i].name, ¶meters[i].value); + } + return 0; +} + +/* This function is mostly juste copy-paste from g_object_new, but + * calls pygobject_constructv instead of g_object_newv */ +int +pygobject_construct(PyGObject *self, const char *first_property_name, ...) +{ + va_list var_args; + GObjectClass *class; + GParameter *params; + const gchar *name; + guint n_params = 0, n_alloced_params = 16; + GType object_type = pyg_type_from_object((PyObject *) self); + int retval; + + if (!first_property_name) + return pygobject_constructv(self, 0, NULL); + + va_start(var_args, first_property_name); + class = g_type_class_ref(object_type); + + params = g_new(GParameter, n_alloced_params); + name = first_property_name; + while (name) + { + gchar *error = NULL; + GParamSpec *pspec = g_object_class_find_property(class, name); + + if (!pspec) + { + g_warning("%s: object class `%s' has no property named `%s'", + G_STRFUNC, + g_type_name(object_type), + name); + break; + } + if (n_params >= n_alloced_params) + { + n_alloced_params += 16; + params = g_renew(GParameter, params, n_alloced_params); + } + params[n_params].name = name; + params[n_params].value.g_type = 0; + g_value_init(¶ms[n_params].value, G_PARAM_SPEC_VALUE_TYPE(pspec)); + G_VALUE_COLLECT(¶ms[n_params].value, var_args, 0, &error); + if (error) + { + g_warning("%s: %s", G_STRFUNC, error); + g_free(error); + g_value_unset(¶ms[n_params].value); + break; + } + n_params++; + name = va_arg(var_args, gchar*); + } + + retval = pygobject_constructv(self, n_params, params); + + while (n_params--) + g_value_unset(¶ms[n_params].value); + g_free(params); + va_end(var_args); + g_type_class_unref(class); + return retval; +} + +void +pyg_set_object_has_new_constructor(GType type) +{ + g_type_set_qdata(type, pygobject_has_updated_constructor_key, GINT_TO_POINTER(1)); +} + /* ----------------- gobject module initialisation -------------- */ struct _PyGObject_Functions pygobject_api_functions = { @@ -2521,7 +2679,10 @@ struct _PyGObject_Functions pygobject_api_functions = { pyg_register_class_init, pyg_register_interface_info, - pyg_closure_set_exception_handler + pyg_closure_set_exception_handler, + pygobject_constructv, + pygobject_construct, + pyg_set_object_has_new_constructor }; #define REGISTER_TYPE(d, type, name) \ @@ -2560,6 +2721,8 @@ initgobject(void) pyginterface_type_key = g_quark_from_static_string("PyGInterface::type"); pyginterface_info_key = g_quark_from_static_string("PyGInterface::info"); pygpointer_class_key = g_quark_from_static_string("PyGPointer::class"); + pygobject_has_updated_constructor_key =\ + g_quark_from_static_string("PyGObject::has-updated-constructor"); REGISTER_TYPE(d, PyGTypeWrapper_Type, "GType"); @@ -2582,6 +2745,7 @@ initgobject(void) &PyGObject_Type, NULL); PyDict_SetItemString(PyGObject_Type.tp_dict, "__gdoc__", pyg_object_descr_doc_get()); + pyg_set_object_has_new_constructor(G_TYPE_OBJECT); /* GObject properties descriptor */ if (PyType_Ready(&PyGProps_Type) < 0) diff --git a/gobject/pygobject-private.h b/gobject/pygobject-private.h index 4fd48e4..22bcab6 100644 --- a/gobject/pygobject-private.h +++ b/gobject/pygobject-private.h @@ -51,9 +51,17 @@ extern GQuark pygobject_class_init_key; extern GQuark pygobject_class_key; extern GQuark pygobject_wrapper_key; extern GQuark pygpointer_class_key; - -void pyg_destroy_notify (gpointer user_data); -gboolean pyg_error_check(GError **error); +extern GQuark pygobject_has_updated_constructor_key; + +void pyg_destroy_notify (gpointer user_data); +gboolean pyg_error_check (GError **error); +int pygobject_constructv (PyGObject *self, + guint n_parameters, + GParameter *parameters); +int pygobject_construct (PyGObject *self, + const char *first_property_name, + ...); +void pyg_set_object_has_new_constructor (GType gtype); /* from pygtype.h */ extern PyTypeObject PyGTypeWrapper_Type; @@ -103,6 +111,7 @@ void pygobject_register_class (PyObject *dict, PyObject *bases); void pygobject_register_wrapper (PyObject *self); PyObject * pygobject_new (GObject *obj); +PyObject * pygobject_new_full (GObject *obj, gboolean sink); PyTypeObject *pygobject_lookup_class (GType gtype); void pygobject_watch_closure (PyObject *self, GClosure *closure); void pygobject_register_sinkfunc(GType type, diff --git a/gobject/pygobject.c b/gobject/pygobject.c index 1263554..1fe837d 100644 --- a/gobject/pygobject.c +++ b/gobject/pygobject.c @@ -660,6 +660,7 @@ pygobject_lookup_class(GType gtype) /** * pygobject_new: * @obj: a GObject instance. + * @sink: whether to sink any floating reference found on the GObject. * * This function gets a reference to a wrapper for the given GObject * instance. If a wrapper has already been created, a new reference @@ -669,7 +670,7 @@ pygobject_lookup_class(GType gtype) * Returns: a reference to the wrapper for the GObject. */ PyObject * -pygobject_new(GObject *obj) +pygobject_new_full(GObject *obj, gboolean sink) { PyGObject *self; @@ -695,7 +696,8 @@ pygobject_new(GObject *obj) pyg_begin_allow_threads; self->obj = g_object_ref(obj); pyg_end_allow_threads; - sink_object(self->obj); + if (sink) + sink_object(self->obj); self->inst_dict = NULL; self->weakreflist = NULL; @@ -711,6 +713,12 @@ pygobject_new(GObject *obj) return (PyObject *)self; } +PyObject * +pygobject_new(GObject *obj) +{ + return pygobject_new_full(obj, TRUE); +} + static void pygobject_unwatch_closure(gpointer data, GClosure *closure) { @@ -943,11 +951,7 @@ pygobject_init(PyGObject *self, PyObject *args, PyObject *kwargs) n_params++; } } - - self->obj = g_object_newv(object_type, n_params, params); - if (self->obj) - pygobject_register_wrapper((PyObject *)self); - else + if (pygobject_constructv(self, n_params, params)) PyErr_SetString (PyExc_RuntimeError, "could not create object"); cleanup: diff --git a/gobject/pygobject.h b/gobject/pygobject.h index 18c50c0..3bba3ac 100644 --- a/gobject/pygobject.h +++ b/gobject/pygobject.h @@ -166,6 +166,13 @@ struct _PyGObject_Functions { void (*register_class_init) (GType gtype, PyGClassInitFunc class_init); void (*register_interface_info) (GType gtype, const GInterfaceInfo *info); void (*closure_set_exception_handler) (GClosure *closure, PyClosureExceptionHandler handler); + int (*pygobject_constructv) (PyGObject *self, + guint n_parameters, + GParameter *parameters); + int (*pygobject_construct) (PyGObject *self, + const char *first_property_name, + ...); + void (*set_object_has_new_constructor) (GType type); }; #ifndef _INSIDE_PYGOBJECT_ @@ -222,6 +229,10 @@ struct _PyGObject_Functions *_PyGObject_API; #define pyg_enable_threads (_PyGObject_API->enable_threads) #define pyg_register_class_init (_PyGObject_API->register_class_init) #define pyg_register_interface_info (_PyGObject_API->register_interface_info) +#define pygobject_construct (_PyGObject_API->pygobject_construct) +#define pygobject_constructv (_PyGObject_API->pygobject_constructv) +#define pyg_set_object_has_new_constructor (_PyGObject_API->set_object_has_new_constructor) + #define pyg_block_threads() G_STMT_START { \ if (_PyGObject_API->block_threads != NULL) \ diff --git a/tests/Makefile.am b/tests/Makefile.am index 0f8ac7b..25a1a6d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,7 +6,7 @@ INCLUDES = \ $(GTK_CFLAGS) \ -I$(top_srcdir)/gobject -EXTRA_DIST = $(tests) common.py runtests.py test-thread.h test-unknown.h +EXTRA_DIST = $(tests) common.py runtests.py test-thread.h test-unknown.h testmodule.py noinst_LTLIBRARIES = testhelper.la linked_LIBS = testhelper.la diff --git a/tests/runtests.py b/tests/runtests.py index 3ae7185..42f6a0c 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -21,7 +21,7 @@ else: common.importModules(buildDir=buildDir, srcDir=srcDir) -SKIP_FILES = ['common', 'runtests'] +SKIP_FILES = ['common', 'runtests', 'testmodule'] dir = os.path.split(os.path.abspath(__file__))[0] os.chdir(dir) diff --git a/tests/test_properties.py b/tests/test_properties.py index db620ea..970b646 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -2,27 +2,38 @@ import unittest from common import testhelper -from gobject import GObject, GType, PARAM_READWRITE +from gobject import * class PropertyObject(GObject): __gproperties__ = { 'property': (str, 'blurb', 'description', 'default', PARAM_READWRITE), + 'construct-property': (str, 'blurb', 'description', + 'default', + PARAM_READWRITE|PARAM_CONSTRUCT_ONLY), } def __init__(self): GObject.__init__(self) self._value = 'default' + self._construct_property = None def do_get_property(self, pspec): - if pspec.name != 'property': - raise AssertionError - return self._value + if pspec.name == 'property': + return self._value + elif pspec.name == 'construct-property': + return self._construct_property + raise AssertionError + def do_set_property(self, pspec, value): - if pspec.name != 'property': + if pspec.name == 'property': + self._value = value + elif pspec.name == 'construct-property': + self._construct_property = value + else: raise AssertionError - self._value = value + class TestProperties(unittest.TestCase): def testGetSet(self): @@ -48,7 +59,13 @@ class TestProperties(unittest.TestCase): for pspec in obj: gtype = GType(pspec) self.assertEqual(gtype.parent.name, 'GParam') - self.assertEqual(pspec.name, 'property') + self.assert_(pspec.name in ['property', 'construct-property']) + + self.assertEqual(len(obj), 2) - self.assertEqual(len(obj), 1) - self.assertEqual(list(obj), [pspec]) + def testConstructProperty(self): + obj = new(PropertyObject, construct_property="123") + self.assertEqual(obj.props.construct_property, "123") + ## TODO: raise exception when setting construct-only properties + #obj.set_property("construct-property", "456") + #self.assertEqual(obj.props.construct_property, "456") diff --git a/tests/test_subtype.py b/tests/test_subtype.py index b2b5dcc..ea66fab 100644 --- a/tests/test_subtype.py +++ b/tests/test_subtype.py @@ -1,6 +1,7 @@ import unittest from common import gobject, gtk, testhelper +import testmodule class TestSubType(unittest.TestCase): def testSubType(self): @@ -20,3 +21,35 @@ class TestSubType(unittest.TestCase): treemodel = testhelper.get_tp_basicsize(gtk.TreeModel) self.assert_(iface == treemodel) + + def testBuiltinContructorRefcount(self): + foo = gtk.Label() + self.assertEqual(foo.__grefcount__, 1) + + def testPyContructorRefcount(self): + foo = testmodule.PyLabel() + self.assertEqual(foo.__grefcount__, 1) + + def testBuiltinObjNewRefcount(self): + foo = gobject.new(gtk.Label) + self.assertEqual(foo.__grefcount__, 1) + + def testPyObjNewRefcount(self): + foo = gobject.new(testmodule.PyLabel) + self.assertEqual(foo.__grefcount__, 1) + + def testPyContructorPropertyChaining(self): + foo = testmodule.PyLabel() + self.assertEqual(foo.__grefcount__, 1) + + def testPyObjNewPropertyChaining(self): + foo = gobject.new(testmodule.PyLabel) + self.assertEqual(foo.props.label, "hello") + + def testCPyCSubclassing1(self): + obj = testhelper.create_test_type() + self.assertEqual(obj.__grefcount__, 1) + + def testCPyCSubclassing1(self): + refcount = testhelper.test_g_object_new() + self.assertEqual(refcount, 2) diff --git a/tests/testhelpermodule.c b/tests/testhelpermodule.c index cb9a991..962a0dc 100644 --- a/tests/testhelpermodule.c +++ b/tests/testhelpermodule.c @@ -5,6 +5,56 @@ #include "test-unknown.h" +static GType +py_label_get_type(void) +{ + static GType gtype = 0; + if (gtype == 0) { + PyObject *module; + if ((module = PyImport_ImportModule("testmodule")) != NULL) { + PyObject *moddict = PyModule_GetDict(module); + PyObject *py_label_type = PyDict_GetItemString(moddict, "PyLabel"); + if (py_label_type != NULL) + gtype = pyg_type_from_object(py_label_type); + } + } + if (gtype == 0) + g_warning("could not get PyLabel from testmodule"); + return gtype; +} + +GType +test_type_get_type(void) +{ + static GType gtype = 0; + + if (gtype == 0) + { + GTypeQuery q; + GTypeInfo type_info = { + 0, /* class_size */ + + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + + (GClassInitFunc) NULL, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + + 0, /* instance_size */ + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL + }; + g_type_query(py_label_get_type(), &q); + type_info.class_size = q.class_size; + type_info.instance_size = q.instance_size; + gtype = g_type_register_static(py_label_get_type(), "TestType", &type_info, 0); + } + return gtype; +} + +#define TYPE_TEST (test_type_get_type()) + static PyObject * _wrap_get_tp_basicsize (PyObject * self, PyObject * args) { @@ -34,10 +84,35 @@ _wrap_get_unknown (PyObject * self) } +static PyObject * +_wrap_create_test_type (PyObject * self) +{ + GObject *obj; + PyObject *rv; + obj = g_object_new(TYPE_TEST, NULL); + rv = pygobject_new(obj); + g_object_unref(obj); + return rv; +} + +static PyObject * +_wrap_test_g_object_new (PyObject * self) +{ + GObject *obj; + PyObject *rv; + + obj = g_object_new(py_label_get_type(), NULL); + rv = PyInt_FromLong(obj->ref_count); /* should be == 2 at this point */ + g_object_unref(obj); + return rv; +} + static PyMethodDef testhelper_methods[] = { { "get_tp_basicsize", _wrap_get_tp_basicsize, METH_VARARGS }, { "get_test_thread", (PyCFunction)_wrap_get_test_thread, METH_NOARGS }, { "get_unknown", (PyCFunction)_wrap_get_unknown, METH_NOARGS }, + { "create_test_type", (PyCFunction)_wrap_create_test_type, METH_NOARGS }, + { "test_g_object_new", (PyCFunction)_wrap_test_g_object_new, METH_NOARGS }, { NULL, NULL } }; diff --git a/tests/testmodule.py b/tests/testmodule.py new file mode 100644 index 0000000..8761ecf --- /dev/null +++ b/tests/testmodule.py @@ -0,0 +1,9 @@ +import gobject +import gtk + +class PyLabel(gtk.Label): + __gtype_name__ = 'PyLabel' + + def __init__(self): + gtk.Label.__init__(self, "hello") + |
