+commit f2fb7f6142cd7112db9c2526d7f1c642a50cfc2a
+Author: Martin Pitt <martinpitt@gnome.org>
+Date: Tue Feb 19 12:19:35 2013 +0100
+
+ Release 3.7.90
+
+ NEWS | 17 +++++++++++++++++
+ configure.ac | 2 +-
+ 2 files changed, 18 insertions(+), 1 deletion(-)
+
+commit 840c871441cb215f24cc6e7ed26b9f38e5aad0df
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Mon Feb 18 01:46:22 2013 -0800
+
+ overrides: Fix inconsistencies with drag and drop target list API
+
+ Add support to Gtk.Widget.drag_dest_set_target_list and
+ Gtk.Widget.drag_source_set_target_list to accept iterables containing
+ mixed TargetEntry or a tuple of (target, flags, info).
+ Add support to Gtk.TreeView.enable_model_drag_source and
+ Gtk.TreeView.enable_model_drag_dest to accept a list of
+ Gtk.TargetEntry
+ items.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=680640
+
+ gi/overrides/Gtk.py | 40 +++++++++++++++++++++++++++++-----------
+ tests/test_overrides_gtk.py | 32 ++++++++++++++++++++++++++++++++
+ 2 files changed, 61 insertions(+), 11 deletions(-)
+
+commit 62e94b0f87845bb7a1cfddf70dcdc89ff7a80bf7
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Mon Feb 18 03:19:34 2013 -0800
+
+ tests: Add test_marshaling_object to Makefile.am
+
+ tests/Makefile.am | 1 +
+ 1 file changed, 1 insertion(+)
+
+commit a10fb7216de57046d1ecacb73dd032eaadcbad09
+Author: Simon Feltman <s.feltman@gmail.com>
+Date: Wed Aug 29 03:46:23 2012 -0700
+
+ pygtkcompat: Add pygtk compatible GenericTreeModel implementation
+
+ Add Python implementation of the GenericTreeModel that was
+ available in pygtk. The implementation attempts a better job
+ than the original at ref counting by guaranteeing no leaks
+ upon deletion of the model itself. Or by using the extra "node"
+ argument to the row_deleted signal. The model is available in
+ the pygtkcompat package directly as
+ pygtkcompat.generictreemodel.GenericTreeModel or with as
+ gtk.GenericTreeModel when pygtkcompat.enable_gtk() is set.
+
+ Add file list and tree demos making use of GenericTreeModel
+ to gtk-demo.
+
+ Auto-expand gtk-demo app tree to give a better overview of
+ the demos available.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=682933
+
+ .../gtk-demo/demos/Tree View/treemodel_filelist.py | 234 ++++++++++++
+ .../gtk-demo/demos/Tree View/treemodel_filetree.py | 279 ++++++++++++++
+ demos/gtk-demo/gtk-demo.py | 2 +-
+ gi/pygtkcompat.py | 2 +-
+ pygtkcompat/Makefile.am | 1 +
+ pygtkcompat/generictreemodel.py | 420
+ +++++++++++++++++++++
+ pygtkcompat/pygtkcompat.py | 3 +
+ tests/Makefile.am | 1 +
+ tests/test_generictreemodel.py | 406
+ ++++++++++++++++++++
+ 9 files changed, 1346 insertions(+), 2 deletions(-)
+
+commit 871878c7a1e18fbdbf0744e0dd52cbcc6b610cdb
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Mon Feb 18 02:54:14 2013 -0800
+
+ overrides: Add support for iterables besides tuples for TreePath
+ creation
+
+ Allow Gtk.TreePath to accept any iterable for creation of the path.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=682933
+
+ gi/overrides/Gtk.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 93c1536b45f56c20b6d874c41c4cacd2b6cdca0a
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Fri Feb 15 22:56:29 2013 -0800
+
+ Unify Python callable to GClosure GI marshaling code
+
+ Add pygi_marshal_from_py_gclosure which can be used for direct
+ gi method
+ call args and vfunc out args.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693405
+
+ gi/pygi-argument.c | 14 +-----------
+ gi/pygi-marshal-from-py.c | 55
+ ++++++++++++++++++++++++++++-------------------
+ gi/pygi-marshal-from-py.h | 3 +++
+ 3 files changed, 37 insertions(+), 35 deletions(-)
+
+commit 9e47afe459df942d9ffc4f71b39f1443976293df
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Fri Feb 15 20:56:12 2013 -0800
+
+ Unify Python object to GValue GI marshaling code
+
+ Add pygi_marshal_from_py_g_value which can be used for direct
+ gi method
+ call args and vfunc out args. The new method also adds an
+ "is_allocated"
+ parameter that will be used to fix leaks in the future.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693405
+
+ gi/pygi-argument.c | 43 +++++++--------------------
+ gi/pygi-marshal-from-py.c | 74
+ ++++++++++++++++++++++++++++++++---------------
+ gi/pygi-marshal-from-py.h | 5 ++++
+ 3 files changed, 65 insertions(+), 57 deletions(-)
+
+commit 15cd7be5ad80e2411d6c13b04f5e2c33e4f5605e
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Fri Feb 15 23:07:57 2013 -0800
+
+ Rename pygi_marshal_from_py_object to make it more explicit
+
+ Rename pygi_marshal_from_py_object to pygi_marshal_from_py_gobject
+ to make it more explicit and give consistency with future refactoring.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693405
+
+ gi/pygi-argument.c | 2 +-
+ gi/pygi-marshal-from-py.c | 22 +++++++++++++---------
+ gi/pygi-marshal-from-py.h | 6 +++---
+ 3 files changed, 17 insertions(+), 13 deletions(-)
+
+commit 84103dfabd05742d1a18729663a609e9bf7c45f8
+Author: Niklas Koep <niklas.koep@gmail.com>
+Date: Fri Feb 15 21:23:01 2013 -0800
+
+ Prefix __module__ attribute of function objects with gi.repository
+
+ This allows gi module methods to work with pydoc and help().
+ Additionally correct typo in two docstrings of the same module.
+
+ Co-authored-by: Simon Feltman <sfeltman@src.gnome.org>
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693839
+
+ gi/overrides/__init__.py | 3 ++-
+ gi/types.py | 6 +++---
+ 2 files changed, 5 insertions(+), 4 deletions(-)
+
+commit f6d4d2da676ae63d7a24dd172775b488ce665fe4
+Author: Jonathan Ballet <jon@multani.info>
+Date: Thu Feb 14 07:50:02 2013 +0100
+
+ configure.ac: only enable code coverage when available
+
+ When building with an older gnome-common which does not yet provide
+ code
+ coverage support, disable it instead of breaking the configure script.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693328
+
+ configure.ac | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+commit 42cbff60e2032f715d9be6ab280954211899e03c
+Author: Jonathan Ballet <jon@multani.info>
+Date: Tue Feb 12 23:03:00 2013 +0100
+
+ Correctly set properties on object with statically defined properties
+
+ Fix failures in GObject.Object.set_properties() when used with
+ statically defined properties:
+
+ * Calling the method was raising a "SystemError: error return without
+ exception set" since `result` was (most of the time) still NULL at the
+ end of pygobject_set_properties()
+
+ * Calling the method with several properties would only set one of
+ the properties, since the function was exiting too early.
+
+ Signed-off-by: Simon Feltman <sfeltman@src.gnome.org>
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693618
+
+ gi/_gobject/pygobject.c | 19 +++++++++++--------
+ tests/test_gi.py | 6 ++++++
+ 2 files changed, 17 insertions(+), 8 deletions(-)
+
+commit 2384769810a61d6ed08d8742b7ae976ebfaa8cb5
+Author: Martin Pitt <martinpitt@gnome.org>
+Date: Mon Feb 11 18:08:37 2013 +0100
+
+ autogen.sh: Use gnome-autogen.sh
+
+ We depend on gnome-common now anyway, so use gnome-autogen.sh. This
+ will result
+ in a much better error message when gnome-common is not installed,
+ too.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693328
+
+ autogen.sh | 30 +++++++++++++++++-------------
+ 1 file changed, 17 insertions(+), 13 deletions(-)
+
+commit c107bb1f9275a748b494d3f32818f227e07cadf0
+Author: Christoph Reiter <christoph.reiter@gmx.at>
+Date: Mon Feb 11 10:07:47 2013 +0100
+
+ GTK tests: Add and use context manager for realized widgets
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693377
+
+ tests/test_overrides_gtk.py | 91
+ +++++++++++++++++++++++++--------------------
+ 1 file changed, 50 insertions(+), 41 deletions(-)
+
+commit e6670ee26b7682e6213f71deef813ce2e7cd6730
+Author: Martin Pitt <martinpitt@gnome.org>
+Date: Mon Feb 11 08:55:19 2013 +0100
+
+ _pygi_marshal_from_py_array: Fix uninitialized variable
+
+ gi/pygi-marshal-from-py.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit a37bfdcb3d9dcc8bcdd8126ad55d80fab4729c62
+Author: Christoph Reiter <christoph.reiter@gmx.at>
+Date: Mon Feb 11 08:34:42 2013 +0100
+
+ Skip some vfunc tests with gi 1.34
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=693374
+
+ tests/test_object_marshaling.py | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+commit aff2ea1b681c3019f7dbdc841c2e33de78dbb88f
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Sun Feb 10 13:40:45 2013 -0800
+
+ Remove workaround for g_struct_info_get_size reporting incorrect size
+
+ Remove workaround for g_struct_info_get_size reporting incorrect size
+ for boxed GValues. Verified this now returns the correct size of 24.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=622711
+
+ gi/pygi-boxed.c | 31 +++++++++++++------------------
+ 1 file changed, 13 insertions(+), 18 deletions(-)
+
+commit 5efe2e5c8458d9f4d72329ea1209d96b5ebecfb4
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Mon Feb 4 20:50:10 2013 -0800
+
+ Fix reference leaks with transient floating objects
+
+ Unify and refactor caller and callee GObject argument marshalers.
+ Combine code from the large switch statement used to marshal
+ arguments to and from vfuncs/closures with the marshalers used
+ for direct calls to gi functions. This fixes a reference leak
+ when marshalling GObjects to Python with transfer=full due to
+ the diverging code paths.
+ Replace ability in gobject_new_full to optionally sink objects
+ with ability to optionaly "steal" objects. This fits the premise
+ that binding layers should always sink objects initially. The
+ steal argument is then used for marshalling arguments which are
+ transfer=full.
+ Add hacks and comments to work around GTK+ bugs 693393 and 693400.
+
+ https://bugzilla.gnome.org/show_bug.cgi?id=687522
+
+ gi/_gobject/gobjectmodule.c | 10 +++++--
+ gi/_gobject/pygobject-private.h | 2 +-
+ gi/_gobject/pygobject.c | 45 ++++++++++++++++++++----------
+ gi/_gobject/pygobject.h | 8 ++++--
+ gi/pygi-argument.c | 52 +++++++++++++++++------------------
+ gi/pygi-marshal-from-py.c | 61
+ +++++++++++++++++++++++++++++++++++++----
+ gi/pygi-marshal-from-py.h | 6 ++++
+ gi/pygi-marshal-to-py.c | 45 +++++++++++++++---------------
+ gi/pygi-marshal-to-py.h | 5 ++++
+ tests/test_object_marshaling.py | 44 ++++++++++++++---------------
+ 10 files changed, 181 insertions(+), 97 deletions(-)
+
+commit bd54b8ab30fc957849e7f57e9ee4c4b41aa37013
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date: Wed Feb 6 12:56:44 2013 -0800
+
+ tests: Fix spelling mistakes in new vfunc object marshalling tests
+
+ tests/test_object_marshaling.py | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+commit cd96fd8b8e10add9890f36ec237bb78548de7002
+Author: Martin Pitt <martinpitt@gnome.org>
+Date: Tue Feb 5 07:53:38 2013 +0100
+
+ configure.ac: post-release bump to 3.7.6
+
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
commit 92b1404e61f46348168f32720eff4a482531e5e3
Author: Martin Pitt <martinpitt@gnome.org>
Date: Tue Feb 5 07:46:46 2013 +0100
+3.7.90 19-Feb-2013
+ - overrides: Fix inconsistencies with drag and drop target list API
+ (Simon Feltman) (#680640)
+ - pygtkcompat: Add pygtk compatible GenericTreeModel implementation
+ (Simon Feltman) (#682933)
+ - overrides: Add support for iterables besides tuples for TreePath
+ creation (Simon Feltman) (#682933)
+ - Prefix __module__ attribute of function objects with gi.repository
+ (Niklas Koep) (#693839)
+ - configure.ac: only enable code coverage when available, to fix
+ autogen.sh with older gnome-commons (Jonathan Ballet) (#693328)
+ - Correctly set properties on object with statically defined properties
+ (Jonathan Ballet) (#693618)
+ - autogen.sh: Use gnome-autogen.sh (Martin Pitt) (#693328)
+ - Fix reference leaks with transient floating objects (Simon Feltman)
+ (#687522)
+
3.7.5.1 05-Feb-2013
- Fix ABI break with pygobject.h from 3.7.5 (Simon Feltman) (#675726)
Metadata-Version: 1.0
Name: PyGObject
-Version: 3.7.5.1
+Version: 3.7.90
Summary: Python bindings for GObject
Home-page: http://www.pygtk.org/
Author: James Henstridge
Maintainer: Johan Dahlin
Maintainer-email: johan@gnome.org
License: GNU LGPL
-Download-url: ftp://ftp.gnome.org/pub/GNOME/sources/pygobject/3.7/pygobject-3.7.5.1.tar.gz
+Download-url: ftp://ftp.gnome.org/pub/GNOME/sources/pygobject/3.7/pygobject-3.7.90.tar.gz
Description: Python bindings for GLib and GObject
Platform: POSIX, Windows
Classifier: Development Status :: 5 - Production/Stable
#!/bin/sh
# Run this to generate all the initial makefiles, etc.
+set -e
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
-test -n "$srcdir" || srcdir=`dirname "$0"`
-test -n "$srcdir" || srcdir=.
+PKG_NAME="pygobject"
-olddir=`pwd`
-cd "$srcdir"
-
-AUTORECONF=`which autoreconf`
-if test -z $AUTORECONF; then
- echo "*** No autoreconf found, please install it ***"
- exit 1
-fi
+(test -f $srcdir/configure.ac \
+ && test -f $srcdir/$PKG_NAME.doap) || {
+ echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+ echo " top-level $PKG_NAME directory"
+ exit 1
+}
if type lcov >/dev/null 2>&1; then
EXTRA_ARGS="--enable-code-coverage"
echo "lcov not installed, not enabling code coverage"
fi
-autoreconf --force --install --verbose || exit $?
-cd "$olddir"
-test -n "$NOCONFIGURE" || "$srcdir/configure" $EXTRA_ARGS "$@"
+which gnome-autogen.sh || {
+ echo "You need to install gnome-common."
+ exit 1
+}
+
+gnome-autogen.sh "$EXTRA_ARGS" "$@"
+
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for pygobject 3.7.5.1.
+# Generated by GNU Autoconf 2.69 for pygobject 3.7.90.
#
# Report bugs to <http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject>.
#
# Identity of this package.
PACKAGE_NAME='pygobject'
PACKAGE_TARNAME='pygobject'
-PACKAGE_VERSION='3.7.5.1'
-PACKAGE_STRING='pygobject 3.7.5.1'
+PACKAGE_VERSION='3.7.90'
+PACKAGE_STRING='pygobject 3.7.90'
PACKAGE_BUGREPORT='http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject'
PACKAGE_URL='https://live.gnome.org/PyGObject/'
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures pygobject 3.7.5.1 to adapt to many kinds of systems.
+\`configure' configures pygobject 3.7.90 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of pygobject 3.7.5.1:";;
+ short | recursive ) echo "Configuration of pygobject 3.7.90:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-pygobject configure 3.7.5.1
+pygobject configure 3.7.90
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by pygobject $as_me 3.7.5.1, which was
+It was created by pygobject $as_me 3.7.90, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
PYGOBJECT_MINOR_VERSION=7
-$as_echo "#define PYGOBJECT_MICRO_VERSION 5.1" >>confdefs.h
+$as_echo "#define PYGOBJECT_MICRO_VERSION 90" >>confdefs.h
-PYGOBJECT_MICRO_VERSION=5.1
+PYGOBJECT_MICRO_VERSION=90
ac_config_headers="$ac_config_headers config.h"
# Define the identity of the package.
PACKAGE='pygobject'
- VERSION='3.7.5.1'
+ VERSION='3.7.90'
cat >>confdefs.h <<_ACEOF
WARN_CFLAGS="$tested_warning_flags $complCFLAGS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Gnome code coverage support" >&5
+$as_echo_n "checking for Gnome code coverage support... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with code coverage support" >&5
$as_echo_n "checking whether to build with code coverage support... " >&6; }
+
if test "x$GCC" = "xyes"; then
case " $CFLAGS " in
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by pygobject $as_me 3.7.5.1, which was
+This file was extended by pygobject $as_me 3.7.90, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-pygobject config.status 3.7.5.1
+pygobject config.status 3.7.90
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
dnl the pygobject version number
m4_define(pygobject_major_version, 3)
m4_define(pygobject_minor_version, 7)
-m4_define(pygobject_micro_version, 5.1)
+m4_define(pygobject_micro_version, 90)
m4_define(pygobject_version, pygobject_major_version.pygobject_minor_version.pygobject_micro_version)
dnl versions of packages we require ...
# compiler warnings, errors, required cflags, and code coverage support
GNOME_COMPILE_WARNINGS([maximum])
-GNOME_CODE_COVERAGE
+AC_MSG_CHECKING(for Gnome code coverage support)
+m4_ifdef([GNOME_CODE_COVERAGE],
+ [AC_MSG_RESULT(yes)
+ GNOME_CODE_COVERAGE],
+ [AC_MSG_RESULT(no)
+ GNOME_CODE_COVERAGE_RULES=''
+ AC_SUBST([GNOME_CODE_COVERAGE_RULES])
+ enable_code_coverage="no"])
+
if test "x$GCC" = "xyes"; then
JH_ADD_CFLAG([-Wall])
JH_ADD_CFLAG([-Werror=unused-variable])
pygobject_init_wrapper_set((PyObject *) self);
obj = g_object_newv(pyg_type_from_object((PyObject *) self),
n_parameters, parameters);
+
+ if (g_object_is_floating (obj))
+ self->private_flags.flags |= PYGOBJECT_GOBJECT_WAS_FLOATING;
pygobject_sink (obj);
+
pygobject_init_wrapper_set(NULL);
if (self->obj == NULL) {
self->obj = obj;
* now */
PyGILState_STATE state;
state = pyglib_gil_state_ensure();
- wrapper = pygobject_new_full(object, TRUE, g_class);
+ wrapper = pygobject_new_full(object,
+ /*steal=*/ FALSE,
+ g_class);
/* float the wrapper ref here because we are going to orphan it
* so we don't destroy the wrapper. The next call to pygobject_new_full
if (obj) {
pygobject_sink (obj);
- self = (PyGObject *) pygobject_new_full((GObject *)obj, TRUE, NULL);
+ self = (PyGObject *) pygobject_new((GObject *)obj);
g_object_unref(obj);
} else
self = NULL;
PyObject *bases);
void pygobject_register_wrapper (PyObject *self);
PyObject * pygobject_new (GObject *obj);
-PyObject * pygobject_new_full (GObject *obj, gboolean sink, gpointer g_class);
+PyObject * pygobject_new_full (GObject *obj, gboolean steal, gpointer g_class);
void pygobject_sink (GObject *obj);
PyTypeObject *pygobject_lookup_class (GType gtype);
void pygobject_watch_closure (PyObject *self, GClosure *closure);
/**
* pygobject_new_full:
* @obj: a GObject instance.
- * @sink: whether to sink any floating reference found on the GObject.
+ * @steal: whether to steal a ref from the GObject or add (sink) a new one.
* @g_class: the GObjectClass
*
* This function gets a reference to a wrapper for the given GObject
* Returns: a reference to the wrapper for the GObject.
*/
PyObject *
-pygobject_new_full(GObject *obj, gboolean sink, gpointer g_class)
+pygobject_new_full(GObject *obj, gboolean steal, gpointer g_class)
{
PyGObject *self;
if (obj == NULL) {
- Py_INCREF(Py_None);
- return Py_None;
+ Py_RETURN_NONE;
}
- /* we already have a wrapper for this object -- return it. */
+ /* If the GObject already has a PyObject wrapper stashed in its qdata, re-use it.
+ */
self = (PyGObject *)g_object_get_qdata(obj, pygobject_wrapper_key);
if (self != NULL) {
- pygobject_ref_sink(self);
+ /* Note the use of "pygobject_ref_sink" here only deals with PyObject
+ * wrapper ref counts and has nothing to do with GObject.
+ */
+ pygobject_ref_sink(self);
+
+ /* If steal is true, we also want to decref the incoming GObjects which
+ * already have a Python wrapper because the wrapper is already holding a
+ * strong reference.
+ */
+ if (steal)
+ g_object_unref (obj);
+
} else {
/* create wrapper */
PyGObjectData *inst_data = pyg_object_peek_inst_data(obj);
self->weakreflist = NULL;
self->private_flags.flags = 0;
self->obj = obj;
- /* if we are creating a wrapper around a newly created object, it can have
- a floating ref (e.g. for methods like Gtk.Button.new()). Bug 640868 */
- if (sink)
- g_object_ref_sink(obj);
- else
- g_object_ref(obj);
- pygobject_register_wrapper((PyObject *)self);
+
+ /* If we are not stealing a ref or the object is floating,
+ * add a regular ref or sink the object. */
+ if (g_object_is_floating (obj))
+ self->private_flags.flags |= PYGOBJECT_GOBJECT_WAS_FLOATING;
+ if (!steal || self->private_flags.flags & PYGOBJECT_GOBJECT_WAS_FLOATING)
+ g_object_ref_sink (obj);
+
+ pygobject_register_wrapper((PyObject *)self);
PyObject_GC_Track((PyObject *)self);
}
PyObject *
pygobject_new(GObject *obj)
{
- return pygobject_new_full(obj, TRUE, NULL);
+ return pygobject_new_full(obj,
+ /*steal=*/FALSE,
+ NULL);
}
static void
goto exit;
}
- ret = pygi_set_property_value (self, pspec, value);
- if (ret == 0)
- goto exit;
- else if (PyErr_Occurred())
- goto exit;
+ ret = pygi_set_property_value (self, pspec, value);
+ if (ret != 0) {
+ /* Non-zero return code means that either an error occured ...*/
+ if (PyErr_Occurred())
+ goto exit;
- if (!set_property_from_pspec(G_OBJECT(self->obj), pspec, value))
- goto exit;
+ /* ... or the property couldn't be found , so let's try the default
+ * call. */
+ if (!set_property_from_pspec(G_OBJECT(self->obj), pspec, value))
+ goto exit;
+ }
}
result = Py_None;
return NULL;
if (self->obj)
- return pygobject_new_full(self->obj, FALSE, NULL);
+ return pygobject_new(self->obj);
else {
Py_INCREF(Py_None);
return Py_None;
typedef enum {
PYGOBJECT_USING_TOGGLE_REF = 1 << 0,
- PYGOBJECT_IS_FLOATING_REF = 1 << 1
+ PYGOBJECT_IS_FLOATING_REF = 1 << 1,
+ PYGOBJECT_GOBJECT_WAS_FLOATING = 1 << 2,
} PyGObjectFlags;
/* closures is just an alias for what is found in the
gpointer data);
gboolean (*gerror_exception_check) (GError **error);
PyObject* (*option_group_new) (GOptionGroup *group);
- GType (* type_from_object_strict) (PyObject *obj, gboolean strict);
- PyObject *(* newgobj_full)(GObject *obj, gboolean sink, gpointer g_class);
+ GType (* type_from_object_strict) (PyObject *obj, gboolean strict);
+
+ PyObject *(* newgobj_full)(GObject *obj, gboolean steal, gpointer g_class);
};
#ifndef _INSIDE_PYGOBJECT_
warnings.warn(warn_msg, RuntimeWarning)
+def _construct_target_list(targets):
+ """Create a list of TargetEntry items from a list of tuples in the form (target, flags, info)
+
+ The list can also contain existing TargetEntry items in which case the existing entry
+ is re-used in the return list.
+ """
+ target_entries = []
+ for entry in targets:
+ if not isinstance(entry, Gtk.TargetEntry):
+ entry = Gtk.TargetEntry.new(*entry)
+ target_entries.append(entry)
+ return target_entries
+
+__all__.append('_construct_target_list')
+
+
class Widget(Gtk.Widget):
def translate_coordinates(self, dest_widget, src_x, src_y):
def render_icon(self, stock_id, size, detail=None):
return super(Widget, self).render_icon(stock_id, size, detail)
+ def drag_dest_set_target_list(self, target_list):
+ if not isinstance(target_list, Gtk.TargetList):
+ target_list = Gtk.TargetList.new(_construct_target_list(target_list))
+ super(Widget, self).drag_dest_set_target_list(target_list)
+
+ def drag_source_set_target_list(self, target_list):
+ if not isinstance(target_list, Gtk.TargetList):
+ target_list = Gtk.TargetList.new(_construct_target_list(target_list))
+ super(Widget, self).drag_source_set_target_list(target_list)
+
+
Widget = override(Widget)
__all__.append('Widget')
def __new__(cls, path=0):
if isinstance(path, int):
path = str(path)
- elif isinstance(path, tuple):
+ elif not isinstance(path, _basestring):
path = ":".join(str(val) for val in path)
if len(path) == 0:
if success:
return (path, pos,)
- def _construct_target_list(self, targets):
- # FIXME: this should most likely be part of Widget or a global helper
- # function
- target_entries = []
- for t in targets:
- entry = Gtk.TargetEntry.new(*t)
- target_entries.append(entry)
- return target_entries
-
def enable_model_drag_source(self, start_button_mask, targets, actions):
- target_entries = self._construct_target_list(targets)
+ target_entries = _construct_target_list(targets)
super(TreeView, self).enable_model_drag_source(start_button_mask,
target_entries,
actions)
def enable_model_drag_dest(self, targets, actions):
- target_entries = self._construct_target_list(targets)
+ target_entries = _construct_target_list(targets)
super(TreeView, self).enable_model_drag_dest(target_entries,
actions)
if not hasattr(func, '__info__'):
raise TypeError("func must be an gi function")
from ..importer import modules
- self.module = modules[func.__module__]._introspection_module
+ module_name = func.__module__.rsplit('.', 1)[-1]
+ self.module = modules[module_name]._introspection_module
def __call__(self, func):
def wrapper(*args, **kwargs):
#include <pyglib-python-compat.h>
#include <pyglib.h>
+#include "pygi-marshal-from-py.h"
+#include "pygi-marshal-to-py.h"
+
+
static gboolean
gi_argument_to_gssize (GIArgument *arg_in,
GITypeTag type_tag,
/* Handle special cases first. */
if (g_type_is_a (type, G_TYPE_VALUE)) {
- GValue *value;
- GType object_type;
- gint retval;
-
- object_type = pyg_type_from_object_strict ( (PyObject *) object->ob_type, FALSE);
- if (object_type == G_TYPE_INVALID) {
- PyErr_SetString (PyExc_RuntimeError, "unable to retrieve object's GType");
- break;
- }
-
g_warn_if_fail (transfer == GI_TRANSFER_NOTHING);
+ /* This will currently leak the GValue that is allocated and
+ * stashed in arg.v_pointer. Out argument marshaling for caller
+ * allocated GValues already pass in memory for the GValue.
+ * Further re-factoring is needed to fix this leak.
+ * See: https://bugzilla.gnome.org/show_bug.cgi?id=693405
+ */
+ pygi_marshal_from_py_gvalue (object,
+ &arg,
+ transfer,
+ FALSE /*is_allocated*/);
- value = g_slice_new0 (GValue);
-
- /* if already a gvalue, copy, else marshal into gvalue */
- if (object_type == G_TYPE_VALUE) {
- /* src GValue's lifecycle is handled by Python
- * so we have to copy it into the destination's
- * GValue which is freed during the cleanup of
- * invoke.
- */
- GValue *src = (GValue *)((PyGObject *) object)->obj;
- g_value_init (value, G_VALUE_TYPE (src));
- g_value_copy(src, value);
- } else {
- g_value_init (value, object_type);
- retval = pyg_value_from_pyobject (value, object);
- if (retval < 0) {
- g_slice_free (GValue, value);
- PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GValue failed");
- break;
- }
- }
-
- arg.v_pointer = value;
} else if (g_type_is_a (type, G_TYPE_CLOSURE)) {
- GClosure *closure;
-
- if (pyg_type_from_object_strict (object, FALSE) == G_TYPE_CLOSURE) {
- closure = (GClosure *)pyg_boxed_get (object, void);
- } else {
- closure = pyg_closure_new (object, NULL, NULL);
- if (closure == NULL) {
- PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GClosure failed");
- break;
- }
- }
-
- arg.v_pointer = closure;
+ pygi_marshal_from_py_gclosure (object, &arg);
} else if (g_struct_info_is_foreign (info)) {
pygi_struct_foreign_convert_to_g_argument (object, info, transfer, &arg);
} else if (g_type_is_a (type, G_TYPE_BOXED)) {
}
case GI_INFO_TYPE_INTERFACE:
case GI_INFO_TYPE_OBJECT:
- if (object == Py_None) {
- arg.v_pointer = NULL;
- break;
- }
-
- arg.v_pointer = pygobject_get (object);
- if (transfer == GI_TRANSFER_EVERYTHING) {
- g_object_ref (arg.v_pointer);
- }
-
+ /* An error within this call will result in a NULL arg */
+ pygi_marshal_from_py_gobject (object, &arg, transfer);
break;
+
default:
g_assert_not_reached();
}
}
case GI_INFO_TYPE_INTERFACE:
case GI_INFO_TYPE_OBJECT:
- if (arg->v_pointer == NULL) {
- object = Py_None;
- Py_INCREF (object);
- break;
- }
-
- if (G_IS_PARAM_SPEC (arg->v_pointer)) {
- object = pyg_param_spec_new (arg->v_pointer);
- break;
- }
-
- /* Only sink incoming objects if the transfer everything.
- * See: https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ /* HACK:
+ * The following hack is to work around GTK+ sending signals which
+ * contain floating widgets in them. This assumes control of how
+ * references are added by the PyGObject wrapper and avoids the sink
+ * behavior by explicitly passing GI_TRANSFER_EVERYTHING as the transfer
+ * mode and then re-forcing the object as floating afterwards.
+ *
+ * This can be deleted once the following ticket is fixed:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=693400
*/
- object = pygobject_new_full (arg->v_pointer,
- /*sink=*/ transfer == GI_TRANSFER_EVERYTHING,
- /*type=*/ NULL);
+ if (arg->v_pointer &&
+ !G_IS_PARAM_SPEC (arg->v_pointer) &&
+ transfer == GI_TRANSFER_NOTHING &&
+ g_object_is_floating (arg->v_pointer)) {
+ g_object_ref (arg->v_pointer);
+ object = pygi_marshal_to_py_object (arg, GI_TRANSFER_EVERYTHING);
+ g_object_force_floating (arg->v_pointer);
+ } else {
+ object = pygi_marshal_to_py_object (arg, transfer);
+ }
break;
default:
{
gsize size;
- /* FIXME: Remove when bgo#622711 is fixed */
- if (g_registered_type_info_get_g_type (info) == G_TYPE_VALUE) {
- size = sizeof (GValue);
- } else {
- switch (g_base_info_get_type (info)) {
- case GI_INFO_TYPE_UNION:
- size = g_union_info_get_size ( (GIUnionInfo *) info);
- break;
- case GI_INFO_TYPE_BOXED:
- case GI_INFO_TYPE_STRUCT:
- size = g_struct_info_get_size ( (GIStructInfo *) info);
- break;
- default:
- PyErr_Format (PyExc_TypeError,
- "info should be Boxed or Union, not '%d'",
- g_base_info_get_type (info));
- return NULL;
- }
+ switch (g_base_info_get_type (info)) {
+ case GI_INFO_TYPE_UNION:
+ size = g_union_info_get_size ( (GIUnionInfo *) info);
+ break;
+ case GI_INFO_TYPE_BOXED:
+ case GI_INFO_TYPE_STRUCT:
+ size = g_struct_info_get_size ( (GIStructInfo *) info);
+ break;
+ default:
+ PyErr_Format (PyExc_TypeError,
+ "info should be Boxed or Union, not '%d'",
+ g_base_info_get_type (info));
+ return NULL;
}
if( size_out != NULL)
GIArgument *arg)
{
PyGIMarshalFromPyFunc from_py_marshaller;
- int i;
+ int i = 0;
Py_ssize_t length;
gssize item_size;
gboolean is_ptr_array;
*/
if (iface_cache->g_type == G_TYPE_CLOSURE) {
- GClosure *closure;
- GType object_gtype = pyg_type_from_object_strict (py_arg, FALSE);
-
- if ( !(PyCallable_Check(py_arg) ||
- g_type_is_a (object_gtype, G_TYPE_CLOSURE))) {
- PyErr_Format (PyExc_TypeError, "Must be callable, not %s",
- py_arg->ob_type->tp_name);
- return FALSE;
- }
-
- if (g_type_is_a (object_gtype, G_TYPE_CLOSURE))
- closure = (GClosure *)pyg_boxed_get (py_arg, void);
- else
- closure = pyg_closure_new (py_arg, NULL, NULL);
-
- if (closure == NULL) {
- PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GClosure failed");
- return FALSE;
- }
-
- arg->v_pointer = closure;
- return TRUE;
+ return pygi_marshal_from_py_gclosure (py_arg, arg);
} else if (iface_cache->g_type == G_TYPE_VALUE) {
- GValue *value;
- GType object_type;
-
- object_type = pyg_type_from_object_strict ( (PyObject *) py_arg->ob_type, FALSE);
- if (object_type == G_TYPE_INVALID) {
- PyErr_SetString (PyExc_RuntimeError, "unable to retrieve object's GType");
- return FALSE;
- }
-
- /* if already a gvalue, use that, else marshal into gvalue */
- if (object_type == G_TYPE_VALUE) {
- value = (GValue *)( (PyGObject *)py_arg)->obj;
- } else {
- value = g_slice_new0 (GValue);
- g_value_init (value, object_type);
- if (pyg_value_from_pyobject (value, py_arg) < 0) {
- g_slice_free (GValue, value);
- PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GValue failed");
- return FALSE;
- }
- }
-
- arg->v_pointer = value;
- return TRUE;
+ return pygi_marshal_from_py_gvalue(py_arg, arg,
+ arg_cache->transfer,
+ arg_cache->is_caller_allocates);
} else if (iface_cache->is_foreign) {
PyObject *success;
success = pygi_struct_foreign_convert_to_g_argument (py_arg,
return FALSE;
}
- arg->v_pointer = pygobject_get(py_arg);
- if (arg_cache->transfer == GI_TRANSFER_EVERYTHING)
- g_object_ref (arg->v_pointer);
-
- return TRUE;
+ return pygi_marshal_from_py_gobject (py_arg, arg, arg_cache->transfer);
}
gboolean
return TRUE;
}
+
+/* pygi_marshal_from_py_gobject:
+ * py_arg: (in):
+ * arg: (out):
+ */
+gboolean
+pygi_marshal_from_py_gobject (PyObject *py_arg, /*in*/
+ GIArgument *arg, /*out*/
+ GITransfer transfer) {
+ GObject *gobj;
+
+ if (py_arg == Py_None) {
+ arg->v_pointer = NULL;
+ return TRUE;
+ }
+
+ gobj = pygobject_get (py_arg);
+ if (transfer == GI_TRANSFER_EVERYTHING) {
+ /* An easy case of adding a new ref that the caller will take ownership of.
+ * Pythons existing ref to the GObject will be managed normally with the wrapper.
+ */
+ g_object_ref (gobj);
+
+ } else if (py_arg->ob_refcnt == 1 && gobj->ref_count == 1) {
+ /* If both object ref counts are only 1 at this point (the reference held
+ * in a return tuple), we assume the GObject will be free'd before reaching
+ * its target and become invalid. So instead of getting invalid object errors
+ * we add a new GObject ref.
+ */
+ g_object_ref (gobj);
+
+ if (((PyGObject *)py_arg)->private_flags.flags & PYGOBJECT_GOBJECT_WAS_FLOATING) {
+ /* HACK:
+ * We want to re-float instances that were floating and the Python
+ * wrapper assumed ownership. With the additional caveat that there
+ * are not any strong references beyond the return tuple.
+ * This should be removed once the following ticket is fixed:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=693393
+ */
+ g_object_force_floating (gobj);
+
+ } else {
+ PyObject *repr = PyObject_Repr (py_arg);
+ gchar *msg = g_strdup_printf ("Expecting to marshal a borrowed reference for %s, "
+ "but nothing in Python is holding a reference to this object. "
+ "See: https://bugzilla.gnome.org/show_bug.cgi?id=687522",
+ PYGLIB_PyUnicode_AsString(repr));
+ Py_DECREF (repr);
+ if (PyErr_WarnEx (PyExc_RuntimeWarning, msg, 2)) {
+ g_free (msg);
+ return FALSE;
+ }
+ g_free (msg);
+ }
+ }
+
+ arg->v_pointer = gobj;
+ return TRUE;
+}
+
+/* pygi_marshal_from_py_gvalue:
+ * py_arg: (in):
+ * arg: (out):
+ * transfer:
+ * is_allocated: TRUE if arg->v_pointer is an already allocated GValue
+ */
+gboolean
+pygi_marshal_from_py_gvalue (PyObject *py_arg,
+ GIArgument *arg,
+ GITransfer transfer,
+ gboolean is_allocated) {
+ GValue *value;
+ GType object_type;
+
+ object_type = pyg_type_from_object_strict ( (PyObject *) py_arg->ob_type, FALSE);
+ if (object_type == G_TYPE_INVALID) {
+ PyErr_SetString (PyExc_RuntimeError, "unable to retrieve object's GType");
+ return FALSE;
+ }
+
+ if (is_allocated)
+ value = (GValue *)arg->v_pointer;
+ else
+ value = g_slice_new0 (GValue);
+
+ /* if already a gvalue, use that, else marshal into gvalue */
+ if (object_type == G_TYPE_VALUE) {
+ GValue *source_value = pyg_boxed_get (py_arg, GValue);
+ if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
+ g_value_init (value, G_VALUE_TYPE (source_value));
+ g_value_copy (source_value, value);
+ } else {
+ if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
+ g_value_init (value, object_type);
+
+ if (pyg_value_from_pyobject (value, py_arg) < 0) {
+ if (!is_allocated)
+ g_slice_free (GValue, value);
+ PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GValue failed");
+ return FALSE;
+ }
+ }
+
+ arg->v_pointer = value;
+ return TRUE;
+}
+
+/* pygi_marshal_from_py_gclosure:
+ * py_arg: (in):
+ * arg: (out):
+ */
+gboolean
+pygi_marshal_from_py_gclosure(PyObject *py_arg,
+ GIArgument *arg)
+{
+ GClosure *closure;
+ GType object_gtype = pyg_type_from_object_strict (py_arg, FALSE);
+
+ if ( !(PyCallable_Check(py_arg) ||
+ g_type_is_a (object_gtype, G_TYPE_CLOSURE))) {
+ PyErr_Format (PyExc_TypeError, "Must be callable, not %s",
+ py_arg->ob_type->tp_name);
+ return FALSE;
+ }
+
+ if (g_type_is_a (object_gtype, G_TYPE_CLOSURE))
+ closure = (GClosure *)pyg_boxed_get (py_arg, void);
+ else
+ closure = pyg_closure_new (py_arg, NULL, NULL);
+
+ if (closure == NULL) {
+ PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GClosure failed");
+ return FALSE;
+ }
+
+ arg->v_pointer = closure;
+ return TRUE;
+}
PyObject *py_arg,
GIArgument *arg);
+/* Simplified marshalers shared between vfunc/closure and direct function calls. */
+
+gboolean pygi_marshal_from_py_gobject (PyObject *py_arg, /*in*/
+ GIArgument *arg, /*out*/
+ GITransfer transfer);
+
+gboolean pygi_marshal_from_py_gvalue (PyObject *py_arg, /*in*/
+ GIArgument *arg, /*out*/
+ GITransfer transfer,
+ gboolean is_allocated);
+
+gboolean pygi_marshal_from_py_gclosure(PyObject *py_arg, /*in*/
+ GIArgument *arg); /*out*/
+
G_END_DECLS
#endif /* __PYGI_MARSHAL_from_py_PY__ */
PyGIArgCache *arg_cache,
GIArgument *arg)
{
- PyObject *py_obj;
-
- if (arg->v_pointer == NULL) {
- py_obj = Py_None;
- Py_INCREF (py_obj);
- return py_obj;
- }
-
- if (G_IS_PARAM_SPEC(arg->v_pointer))
- {
- py_obj = pyg_param_spec_new (arg->v_pointer);
- if (arg_cache->transfer == GI_TRANSFER_EVERYTHING)
- g_param_spec_unref (arg->v_pointer);
- }
- else
- {
- py_obj = pygobject_new (arg->v_pointer);
- if (arg_cache->transfer == GI_TRANSFER_EVERYTHING)
- g_object_unref (arg->v_pointer);
- }
-
- return py_obj;
+ return pygi_marshal_to_py_object(arg, arg_cache->transfer);
}
PyObject *
"Marshalling for this type is not implemented yet");
return py_obj;
}
+
+PyObject *
+pygi_marshal_to_py_object (GIArgument *arg, GITransfer transfer) {
+ PyObject *pyobj;
+
+ if (arg->v_pointer == NULL) {
+ pyobj = Py_None;
+ Py_INCREF (pyobj);
+
+ } else if (G_IS_PARAM_SPEC(arg->v_pointer)) {
+ pyobj = pyg_param_spec_new (arg->v_pointer);
+ if (transfer == GI_TRANSFER_EVERYTHING)
+ g_param_spec_unref (arg->v_pointer);
+
+ } else {
+ pyobj = pygobject_new_full (arg->v_pointer,
+ /*steal=*/ transfer == GI_TRANSFER_EVERYTHING,
+ /*type=*/ NULL);
+ }
+
+ return pyobj;
+}
PyGIArgCache *arg_cache,
GIArgument *arg);
+/* Simplified marshalers shared between vfunc/closure and direct function calls. */
+
+PyObject *pygi_marshal_to_py_object (GIArgument *arg,
+ GITransfer transfer);
+
G_END_DECLS
#endif /* __PYGI_MARSHAL_TO_PY_H__ */
from gi import PyGIDeprecationWarning
warnings.warn('gi.pygtkcompat is being deprecated in favor of using "pygtkcompat" directly.',
- PyGIDeprecationWarning, stacklevel=2)
+ PyGIDeprecationWarning)
# pyflakes.ignore
from pygtkcompat import (enable,
def update_func(func):
func.__info__ = info
func.__name__ = info.get_name()
- func.__module__ = info.get_namespace()
+ func.__module__ = 'gi.repository.' + info.get_namespace()
func.__doc__ = get_callable_info_doc_string(info)
return func
return update_func
def Function(info):
- """Warps GIFunctionInfo"""
+ """Wraps GIFunctionInfo"""
@wraps_callable_info(info)
def function(*args, **kwargs):
return info.invoke(*args, **kwargs)
def Constructor(info):
- """Warps GIFunctionInfo with get_constructor() == True"""
+ """Wraps GIFunctionInfo with get_constructor() == True"""
@wraps_callable_info(info)
def constructor(cls, *args, **kwargs):
cls_name = info.get_container().get_name()
pygtkcompat_PYTHON = \
__init__.py \
+ generictreemodel.py \
pygtkcompat.py
# if we build in a separate tree, we need to symlink the *.py files from the
pygtkcompatdir = $(pyexecdir)/pygtkcompat
pygtkcompat_PYTHON = \
__init__.py \
+ generictreemodel.py \
pygtkcompat.py
all: all-am
--- /dev/null
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# generictreemodel - GenericTreeModel implementation for pygtk compatibility.
+# Copyright (C) 2013 Simon Feltman
+#
+# generictreemodel.py: GenericTreeModel implementation for pygtk compatibility
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+# USA
+
+
+# System
+import sys
+import random
+import collections
+import ctypes
+
+# GObject
+from gi.repository import GObject
+from gi.repository import Gtk
+
+
+class _CTreeIter(ctypes.Structure):
+ _fields_ = [('stamp', ctypes.c_int),
+ ('user_data', ctypes.c_void_p),
+ ('user_data2', ctypes.c_void_p),
+ ('user_data3', ctypes.c_void_p)]
+
+ @classmethod
+ def from_iter(cls, iter):
+ offset = sys.getsizeof(object()) # size of PyObject_HEAD
+ return ctypes.POINTER(cls).from_address(id(iter) + offset)
+
+
+def _get_user_data_as_pyobject(iter):
+ citer = _CTreeIter.from_iter(iter)
+ return ctypes.cast(citer.contents.user_data, ctypes.py_object).value
+
+
+def handle_exception(default_return):
+ """Returns a function which can act as a decorator for wrapping exceptions and
+ returning "default_return" upon an exception being thrown.
+
+ This is used to wrap Gtk.TreeModel "do_" method implementations so we can return
+ a proper value from the override upon an exception occurring with client code
+ implemented by the "on_" methods.
+ """
+ def decorator(func):
+ def wrapped_func(*args, **kargs):
+ try:
+ return func(*args, **kargs)
+ except:
+ # Use excepthook directly to avoid any printing to the screen
+ # if someone installed an except hook.
+ sys.excepthook(*sys.exc_info())
+ return default_return
+ return wrapped_func
+ return decorator
+
+
+class GenericTreeModel(GObject.GObject, Gtk.TreeModel):
+ """A base implementation of a Gtk.TreeModel for python.
+
+ The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python.
+ The class can be subclassed to provide a TreeModel implementation which works
+ directly with Python objects instead of iterators.
+
+ All of the on_* methods should be overridden by subclasses to provide the
+ underlying implementation a way to access custom model data. For the purposes of
+ this API, all custom model data supplied or handed back through the overridable
+ API will use the argument names: node, parent, and child in regards to user data
+ python objects.
+
+ The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are
+ available to help manage Gtk.TreeIter objects and their Python object references.
+
+ GenericTreeModel manages a pool of user data nodes that have been used with iters.
+ This pool stores a references to user data nodes as a dictionary value with the
+ key being the integer id of the data. This id is what the Gtk.TreeIter objects
+ use to reference data in the pool.
+ References will be removed from the pool when the model is deleted or explicitly
+ by using the optional "node" argument to the "row_deleted" method when notifying
+ the model of row deletion.
+ """
+
+ leak_references = GObject.Property(default=True, type=bool,
+ blurb="If True, strong references to user data attached to iters are "
+ "stored in a dictionary pool (default). Otherwise the user data is "
+ "stored as a raw pointer to a python object without a reference.")
+
+ #
+ # Methods
+ #
+ def __init__(self):
+ """Initialize. Make sure to call this from derived classes if overridden."""
+ super(GenericTreeModel, self).__init__()
+ self.stamp = 0
+
+ #: Dictionary of (id(user_data): user_data), used when leak-refernces=False
+ self._held_refs = dict()
+
+ # Set initial stamp
+ self.invalidate_iters()
+
+ def iter_depth_first(self):
+ """Depth-first iteration of the entire TreeModel yielding the python nodes."""
+ stack = collections.deque([None])
+ while stack:
+ it = stack.popleft()
+ if it is not None:
+ yield self.get_user_data(it)
+ children = [self.iter_nth_child(it, i) for i in range(self.iter_n_children(it))]
+ stack.extendleft(reversed(children))
+
+ def invalidate_iter(self, iter):
+ """Clear user data and its reference from the iter and this model."""
+ iter.stamp = 0
+ if iter.user_data:
+ if iter.user_data in self._held_refs:
+ del self._held_refs[iter.user_data]
+ iter.user_data = None
+
+ def invalidate_iters(self):
+ """
+ This method invalidates all TreeIter objects associated with this custom tree model
+ and frees their locally pooled references.
+ """
+ self.stamp = random.randint(-2147483648, 2147483647)
+ self._held_refs.clear()
+
+ def iter_is_valid(self, iter):
+ """
+ :Returns:
+ True if the gtk.TreeIter specified by iter is valid for the custom tree model.
+ """
+ return iter.stamp == self.stamp
+
+ def get_user_data(self, iter):
+ """Get the user_data associated with the given TreeIter.
+
+ GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter.
+ This method allows to retrieve the Python object held by the given iterator.
+ """
+ if self.leak_references:
+ return self._held_refs[iter.user_data]
+ else:
+ return _get_user_data_as_pyobject(iter)
+
+ def set_user_data(self, iter, user_data):
+ """Applies user_data and stamp to the given iter.
+
+ If the models "leak_references" property is set, a reference to the
+ user_data is stored with the model to ensure we don't run into bad
+ memory problems with the TreeIter.
+ """
+ iter.user_data = id(user_data)
+
+ if user_data is None:
+ self.invalidate_iter(iter)
+ else:
+ iter.stamp = self.stamp
+ if self.leak_references:
+ self._held_refs[iter.user_data] = user_data
+
+ def create_tree_iter(self, user_data):
+ """Create a Gtk.TreeIter instance with the given user_data specific for this model.
+
+ Use this method to create Gtk.TreeIter instance instead of directly calling
+ Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data.
+ """
+ iter = Gtk.TreeIter()
+ self.set_user_data(iter, user_data)
+ return iter
+
+ def _create_tree_iter(self, data):
+ """Internal creation of a (bool, TreeIter) pair for returning directly
+ back to the view interfacing with this model."""
+ if data is None:
+ return (False, None)
+ else:
+ it = self.create_tree_iter(data)
+ return (True, it)
+
+ def row_deleted(self, path, node=None):
+ """Notify the model a row has been deleted.
+
+ Use the node parameter to ensure the user_data reference associated
+ with the path is properly freed by this model.
+
+ :Parameters:
+ path : Gtk.TreePath
+ Path to the row that has been deleted.
+ node : object
+ Python object used as the node returned from "on_get_iter". This is
+ optional but ensures the model will not leak references to this object.
+ """
+ super(GenericTreeModel, self).row_deleted(path)
+ node_id = id(node)
+ if node_id in self._held_refs:
+ del self._held_refs[node_id]
+
+ #
+ # GtkTreeModel Interface Implementation
+ #
+ @handle_exception(0)
+ def do_get_flags(self):
+ """Internal method."""
+ return self.on_get_flags()
+
+ @handle_exception(0)
+ def do_get_n_columns(self):
+ """Internal method."""
+ return self.on_get_n_columns()
+
+ @handle_exception((False, None))
+ def do_get_column_type(self, index):
+ """Internal method."""
+ return self.on_get_column_type(index)
+
+ @handle_exception((False, None))
+ def do_get_iter(self, path):
+ """Internal method."""
+ return self._create_tree_iter(self.on_get_iter(path))
+
+ @handle_exception(False)
+ def do_iter_next(self, iter):
+ """Internal method."""
+ if iter is None:
+ next_data = self.on_iter_next(None)
+ else:
+ next_data = self.on_iter_next(self.get_user_data(iter))
+
+ self.set_user_data(iter, next_data)
+ return next_data is not None
+
+ @handle_exception(None)
+ def do_get_path(self, iter):
+ """Internal method."""
+ path = self.on_get_path(self.get_user_data(iter))
+ if path is None:
+ return None
+ else:
+ return Gtk.TreePath(path)
+
+ @handle_exception(None)
+ def do_get_value(self, iter, column):
+ """Internal method."""
+ return self.on_get_value(self.get_user_data(iter), column)
+
+ @handle_exception((False, None))
+ def do_iter_children(self, parent):
+ """Internal method."""
+ data = self.get_user_data(parent) if parent else None
+ return self._create_tree_iter(self.on_iter_children(data))
+
+ @handle_exception(False)
+ def do_iter_has_child(self, parent):
+ """Internal method."""
+ return self.on_iter_has_child(self.get_user_data(parent))
+
+ @handle_exception(0)
+ def do_iter_n_children(self, iter):
+ """Internal method."""
+ if iter is None:
+ return self.on_iter_n_children(None)
+ return self.on_iter_n_children(self.get_user_data(iter))
+
+ @handle_exception((False, None))
+ def do_iter_nth_child(self, parent, n):
+ """Internal method."""
+ if parent is None:
+ data = self.on_iter_nth_child(None, n)
+ else:
+ data = self.on_iter_nth_child(self.get_user_data(parent), n)
+ return self._create_tree_iter(data)
+
+ @handle_exception((False, None))
+ def do_iter_parent(self, child):
+ """Internal method."""
+ return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child)))
+
+ @handle_exception(None)
+ def do_ref_node(self, iter):
+ self.on_ref_node(self.get_user_data(iter))
+
+ @handle_exception(None)
+ def do_unref_node(self, iter):
+ self.on_unref_node(self.get_user_data(iter))
+
+ #
+ # Python Subclass Overridables
+ #
+ def on_get_flags(self):
+ """Overridable.
+
+ :Returns Gtk.TreeModelFlags:
+ The flags for this model. See: Gtk.TreeModelFlags
+ """
+ raise NotImplementedError
+
+ def on_get_n_columns(self):
+ """Overridable.
+
+ :Returns:
+ The number of columns for this model.
+ """
+ raise NotImplementedError
+
+ def on_get_column_type(self, index):
+ """Overridable.
+
+ :Returns:
+ The column type for the given index.
+ """
+ raise NotImplementedError
+
+ def on_get_iter(self, path):
+ """Overridable.
+
+ :Returns:
+ A python object (node) for the given TreePath.
+ """
+ raise NotImplementedError
+
+ def on_iter_next(self, node):
+ """Overridable.
+
+ :Parameters:
+ node : object
+ Node at current level.
+
+ :Returns:
+ A python object (node) following the given node at the current level.
+ """
+ raise NotImplementedError
+
+ def on_get_path(self, node):
+ """Overridable.
+
+ :Returns:
+ A TreePath for the given node.
+ """
+ raise NotImplementedError
+
+ def on_get_value(self, node, column):
+ """Overridable.
+
+ :Parameters:
+ node : object
+ column : int
+ Column index to get the value from.
+
+ :Returns:
+ The value of the column for the given node."""
+ raise NotImplementedError
+
+ def on_iter_children(self, parent):
+ """Overridable.
+
+ :Returns:
+ The first child of parent or None if parent has no children.
+ If parent is None, return the first node of the model.
+ """
+ raise NotImplementedError
+
+ def on_iter_has_child(self, node):
+ """Overridable.
+
+ :Returns:
+ True if the given node has children.
+ """
+ raise NotImplementedError
+
+ def on_iter_n_children(self, node):
+ """Overridable.
+
+ :Returns:
+ The number of children for the given node. If node is None,
+ return the number of top level nodes.
+ """
+ raise NotImplementedError
+
+ def on_iter_nth_child(self, parent, n):
+ """Overridable.
+
+ :Parameters:
+ parent : object
+ n : int
+ Index of child within parent.
+
+ :Returns:
+ The child for the given parent index starting at 0. If parent None,
+ return the top level node corresponding to "n".
+ If "n" is larger then available nodes, return None.
+ """
+ raise NotImplementedError
+
+ def on_iter_parent(self, child):
+ """Overridable.
+
+ :Returns:
+ The parent node of child or None if child is a top level node."""
+ raise NotImplementedError
+
+ def on_ref_node(self, node):
+ pass
+
+ def on_unref_node(self, node):
+ pass
value = getattr(Gdk, name)
setattr(keysyms, target, value)
+ from . import generictreemodel
+ Gtk.GenericTreeModel = generictreemodel.GenericTreeModel
+
def enable_vte():
gi.require_version('Vte', '0.0')
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
test_overrides_gdk.py \
test_overrides_gtk.py \
test_atoms.py \
+ test_generictreemodel.py \
compat_test_pygtk.py \
gi/__init__.py \
gi/overrides/__init__.py \
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
test_overrides_gdk.py \
test_overrides_gtk.py \
test_atoms.py \
+ test_generictreemodel.py \
compat_test_pygtk.py \
gi/__init__.py \
gi/overrides/__init__.py \
--- /dev/null
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# test_generictreemodel - Tests for GenericTreeModel
+# Copyright (C) 2013 Simon Feltman
+#
+# test_generictreemodel.py: Tests for GenericTreeModel
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+# USA
+
+
+# system
+import gc
+import sys
+import weakref
+import unittest
+
+# pygobject
+from gi.repository import GObject
+from gi.repository import Gtk
+from pygtkcompat.generictreemodel import GenericTreeModel
+from pygtkcompat.generictreemodel import _get_user_data_as_pyobject
+
+
+class Node(object):
+ """Represents a generic node with name, value, and children."""
+ def __init__(self, name, value, *children):
+ self.name = name
+ self.value = value
+ self.children = list(children)
+ self.parent = None
+ self.next = None
+
+ for i, child in enumerate(children):
+ child.parent = weakref.ref(self)
+ if i < len(children) - 1:
+ child.next = weakref.ref(children[i + 1])
+
+ def __repr__(self):
+ return 'Node("%s", %s)' % (self.name, self.value)
+
+
+class TesterModel(GenericTreeModel):
+ def __init__(self):
+ super(TesterModel, self).__init__()
+ self.root = Node('root', 0,
+ Node('spam', 1,
+ Node('sushi', 2),
+ Node('bread', 3)
+ ),
+ Node('eggs', 4)
+ )
+
+ def on_get_flags(self):
+ return 0
+
+ def on_get_n_columns(self):
+ return 2
+
+ def on_get_column_type(self, n):
+ return (str, int)[n]
+
+ def on_get_iter(self, path):
+ node = self.root
+ path = list(path)
+ idx = path.pop(0)
+ while path:
+ idx = path.pop(0)
+ node = node.children[idx]
+ return node
+
+ def on_get_path(self, node):
+ def rec_get_path(n):
+ for i, child in enumerate(n.children):
+ if child == node:
+ return [i]
+ else:
+ res = rec_get_path(child)
+ if res:
+ res.insert(0, i)
+
+ return rec_get_path(self.root)
+
+ def on_get_value(self, node, column):
+ if column == 0:
+ return node.name
+ elif column == 1:
+ return node.value
+
+ def on_iter_has_child(self, node):
+ return bool(node.children)
+
+ def on_iter_next(self, node):
+ if node.next:
+ return node.next()
+
+ def on_iter_children(self, node):
+ if node:
+ return node.children[0]
+ else:
+ return self.root
+
+ def on_iter_n_children(self, node):
+ if node is None:
+ return 1
+ return len(node.children)
+
+ def on_iter_nth_child(self, node, n):
+ if node is None:
+ assert n == 0
+ return self.root
+ return node.children[n]
+
+ def on_iter_parent(self, child):
+ if child.parent:
+ return child.parent()
+
+
+class TestReferences(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def test_c_tree_iter_user_data_as_pyobject(self):
+ obj = object()
+ obj_id = id(obj)
+ ref_count = sys.getrefcount(obj)
+
+ # This is essentially a stolen ref in the context of _CTreeIter.get_user_data_as_pyobject
+ it = Gtk.TreeIter()
+ it.user_data = obj_id
+
+ obj2 = _get_user_data_as_pyobject(it)
+ self.assertEqual(obj, obj2)
+ self.assertEqual(sys.getrefcount(obj), ref_count + 1)
+
+ def test_leak_references_on(self):
+ model = TesterModel()
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter increases by 1 do to assignment to iter.user_data
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Verify getting a TreeIter more then once does not further increase
+ # the ref count.
+ res2, it2 = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it2.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Deleting the iter does not decrease refcount because references
+ # leak by default (they are stored in the held_refs pool)
+ del it
+ gc.collect()
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Deleting a model should free all held references to user data
+ # stored by TreeIters
+ del model
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_row_deleted_frees_refs(self):
+ model = TesterModel()
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter increases by 1 do to assignment to iter.user_data
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Notifying the underlying model of a row_deleted should decrease the
+ # ref count.
+ model.row_deleted(Gtk.TreePath('0'), model.root)
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Finally deleting the actual object should collect it completely
+ del model.root
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_leak_references_off(self):
+ model = TesterModel()
+ model.leak_references = False
+
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter does not increas count by 1 when leak_references is false
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Deleting the iter does not decrease refcount because assigning user_data
+ # eats references and does not release them.
+ del it
+ gc.collect()
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Deleting the model decreases the final ref, and the object is collected
+ del model
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_iteration_refs(self):
+ # Pull iterators off the model using the wrapped C API which will
+ # then call back into the python overrides.
+ model = TesterModel()
+ nodes = [node for node in model.iter_depth_first()]
+ values = [node.value for node in nodes]
+
+ # Verify depth first ordering
+ self.assertEqual(values, [0, 1, 2, 3, 4])
+
+ # Verify ref counts for each of the nodes.
+ # 5 refs for each node at this point:
+ # 1 - ref held in getrefcount function
+ # 2 - ref held by "node" var during iteration
+ # 3 - ref held by local "nodes" var
+ # 4 - ref held by the root/children graph itself
+ # 5 - ref held by the model "held_refs" instance var
+ for node in nodes:
+ self.assertEqual(sys.getrefcount(node), 5)
+
+ # A second iteration and storage of the nodes in a new list
+ # should only increase refcounts by 1 even though new
+ # iterators are created and assigned.
+ nodes2 = [node for node in model.iter_depth_first()]
+ for node in nodes2:
+ self.assertEqual(sys.getrefcount(node), 6)
+
+ # Hold weak refs and start verifying ref collection.
+ node_refs = [weakref.ref(node) for node in nodes]
+
+ # First round of collection
+ del nodes2
+ gc.collect()
+ for node in nodes:
+ self.assertEqual(sys.getrefcount(node), 5)
+
+ # Second round of collection, no more local lists of nodes.
+ del nodes
+ gc.collect()
+ for ref in node_refs:
+ node = ref()
+ self.assertEqual(sys.getrefcount(node), 4)
+
+ # Using invalidate_iters or row_deleted(path, node) will clear out
+ # the pooled refs held internal to the GenericTreeModel implementation.
+ model.invalidate_iters()
+ self.assertEqual(len(model._held_refs), 0)
+ gc.collect()
+ for ref in node_refs:
+ node = ref()
+ self.assertEqual(sys.getrefcount(node), 3)
+
+ # Deleting the root node at this point should allow all nodes to be collected
+ # as there is no longer a way to reach the children
+ del node # node still in locals() from last iteration
+ del model.root
+ gc.collect()
+ for ref in node_refs:
+ self.assertEqual(ref(), None)
+
+
+class TestIteration(unittest.TestCase):
+ def test_iter_next_root(self):
+ model = TesterModel()
+ it = model.get_iter([0])
+ self.assertEqual(it.user_data, id(model.root))
+ self.assertEqual(model.root.next, None)
+
+ it = model.iter_next(it)
+ self.assertEqual(it, None)
+
+ def test_iter_next_multiple(self):
+ model = TesterModel()
+ it = model.get_iter([0, 0])
+ self.assertEqual(it.user_data, id(model.root.children[0]))
+
+ it = model.iter_next(it)
+ self.assertEqual(it.user_data, id(model.root.children[1]))
+
+ it = model.iter_next(it)
+ self.assertEqual(it, None)
+
+
+class ErrorModel(GenericTreeModel):
+ # All on_* methods will raise a NotImplementedError by default
+ pass
+
+
+class ExceptHook(object):
+ """
+ Temporarily installs an exception hook in a context which
+ expects the given exc_type to be raised. This allows verification
+ of exceptions that occur within python gi callbacks but
+ are never bubbled through from python to C back to python.
+ This works because exception hooks are called in PyErr_Print.
+ """
+ def __init__(self, exc_type):
+ self._exc_type = exc_type
+ self._exceptions = []
+
+ def _excepthook(self, exc_type, value, traceback):
+ self._exceptions.append(exc_type)
+
+ def __enter__(self):
+ self._oldhook = sys.excepthook
+ sys.excepthook = self._excepthook
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ sys.excepthook = self._oldhook
+ assert len(self._exceptions) == 1, 'Expecting exactly one exception of type %s' % self._exc_type
+ assert issubclass(self._exceptions[0], self._exc_type), 'Expecting exactly one exception of type %s' % self._exc_type
+
+
+class TestReturnsAfterError(unittest.TestCase):
+ def setUp(self):
+ self.model = ErrorModel()
+
+ def test_get_flags(self):
+ with ExceptHook(NotImplementedError):
+ flags = self.model.get_flags()
+ self.assertEqual(flags, 0)
+
+ def test_get_n_columns(self):
+ with ExceptHook(NotImplementedError):
+ count = self.model.get_n_columns()
+ self.assertEqual(count, 0)
+
+ def test_get_column_type(self):
+ with ExceptHook(NotImplementedError):
+ col_type = self.model.get_column_type(0)
+ self.assertEqual(col_type, GObject.TYPE_INVALID)
+
+ def test_get_iter(self):
+ with ExceptHook(NotImplementedError):
+ self.assertRaises(ValueError, self.model.get_iter, Gtk.TreePath(0))
+
+ def test_get_path(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ path = self.model.get_path(it)
+ self.assertEqual(path, None)
+
+ def test_get_value(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ try:
+ self.model.get_value(it, 0)
+ except TypeError:
+ pass # silence TypeError converting None to GValue
+
+ def test_iter_has_child(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_has_child(it)
+ self.assertEqual(res, False)
+
+ def test_iter_next(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_next(it)
+ self.assertEqual(res, None)
+
+ def test_iter_children(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_children(None)
+ self.assertEqual(res, None)
+
+ def test_iter_n_children(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_n_children(None)
+ self.assertEqual(res, 0)
+
+ def test_iter_nth_child(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_nth_child(None, 0)
+ self.assertEqual(res, None)
+
+ def test_iter_parent(self):
+ child = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_parent(child)
+ self.assertEqual(res, None)
+
+if __name__ == '__main__':
+ unittest.main()
self.assertEqual(obj.props.some_variant.get_type_string(), 'b')
self.assertEqual(obj.props.some_variant.get_boolean(), True)
+ def test_setting_several_properties(self):
+ obj = GIMarshallingTests.PropertiesObject()
+ obj.set_properties(some_uchar=54, some_int=42)
+ self.assertEqual(42, obj.props.some_int)
+ self.assertEqual(54, obj.props.some_uchar)
+
class TestKeywords(unittest.TestCase):
def test_method(self):
--- /dev/null
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+import unittest
+import weakref
+import gc
+import sys
+import warnings
+
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+
+
+class StrongRef(object):
+ # A class that behaves like weakref.ref but holds a strong reference.
+ # This allows re-use of the VFuncsBase by swapping out the ObjectRef
+ # class var with either weakref.ref or StrongRef.
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def __call__(self):
+ return self.obj
+
+
+class VFuncsBase(GIMarshallingTests.Object):
+ # Class which generically implements the vfuncs used for reference counting tests
+ # in a way that can be easily sub-classed and modified.
+
+ #: Object type used by this class for testing
+ #: This can be GObject.Object or GObject.InitiallyUnowned
+ Object = GObject.Object
+
+ #: Reference type used by this class for holding refs to in/out objects.
+ #: This can be set to weakref.ref or StrongRef
+ ObjectRef = weakref.ref
+
+ def __init__(self):
+ super(VFuncsBase, self).__init__()
+
+ #: Hold ref of input or output python wrappers
+ self.object_ref = None
+
+ #: store grefcount of input object
+ self.in_object_grefcount = None
+ self.in_object_is_floating = None
+
+ def do_vfunc_return_object_transfer_none(self):
+ # Return an object but keep a python reference to it.
+ obj = self.Object()
+ self.object_ref = self.ObjectRef(obj)
+ return obj
+
+ def do_vfunc_return_object_transfer_full(self):
+ # Return an object and hand off the reference to the caller.
+ obj = self.Object()
+ self.object_ref = self.ObjectRef(obj)
+ return obj
+
+ def do_vfunc_out_object_transfer_none(self):
+ # Same as do_vfunc_return_object_transfer_none but the pygi
+ # internals convert the return here into an out arg.
+ obj = self.Object()
+ self.object_ref = self.ObjectRef(obj)
+ return obj
+
+ def do_vfunc_out_object_transfer_full(self):
+ # Same as do_vfunc_return_object_transfer_full but the pygi
+ # internals convert the return here into an out arg.
+ obj = self.Object()
+ self.object_ref = self.ObjectRef(obj)
+ return obj
+
+ def do_vfunc_in_object_transfer_none(self, obj):
+ # 'obj' will have a python wrapper as well as still held
+ # by the caller.
+ self.object_ref = self.ObjectRef(obj)
+ self.in_object_grefcount = obj.__grefcount__
+ self.in_object_is_floating = obj.is_floating()
+
+ def do_vfunc_in_object_transfer_full(self, obj):
+ # 'obj' will now be owned by the Python GObject wrapper.
+ # When obj goes out of scope and is collected, the GObject
+ # should also be fully released.
+ self.object_ref = self.ObjectRef(obj)
+ self.in_object_grefcount = obj.__grefcount__
+ self.in_object_is_floating = obj.is_floating()
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+class TestVFuncsWithObjectArg(unittest.TestCase):
+ # Basic set of tests which work on non-floating objects which python does
+ # not keep an additional reference of.
+
+ class VFuncs(VFuncsBase):
+ # Object for testing non-floating objects without holding any refs.
+ Object = GObject.Object
+ ObjectRef = weakref.ref
+
+ def test_vfunc_self_arg_ref_count(self):
+ # Check to make sure vfunc "self" arguments don't leak.
+ vfuncs = self.VFuncs()
+ vfuncs_ref = weakref.ref(vfuncs)
+ vfuncs.get_ref_info_for_vfunc_return_object_transfer_full() # Use any vfunc to test this.
+
+ gc.collect()
+ self.assertEqual(sys.getrefcount(vfuncs), 2)
+ self.assertEqual(vfuncs.__grefcount__, 1)
+
+ del vfuncs
+ gc.collect()
+ self.assertTrue(vfuncs_ref() is None)
+
+ def test_vfunc_return_object_transfer_none(self):
+ # This tests a problem case where the vfunc returns a GObject owned solely by Python
+ # but the argument is marked as transfer-none.
+ # In this case pygobject marshaling adds an additional ref and gives a warning
+ # of a potential leak. If this occures it is really a bug in the underlying library
+ # but pygobject tries to react to this in a reasonable way.
+ vfuncs = self.VFuncs()
+ with warnings.catch_warnings(record=True) as warn:
+ warnings.simplefilter('always')
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+ self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+ # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+ # should be a single floating ref
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_out_object_transfer_none(self):
+ # Same as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ with warnings.catch_warnings(record=True) as warn:
+ warnings.simplefilter('always')
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+ self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_return_object_transfer_full(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+ # The vfunc caller receives full ownership of a single ref which should not
+ # be floating.
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_out_object_transfer_full(self):
+ # Same as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_in_object_transfer_none(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+ gc.collect()
+ self.assertEqual(vfuncs.in_object_grefcount, 2) # initial + python wrapper
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ self.assertEqual(ref_count, 1) # ensure python wrapper released
+ self.assertFalse(is_floating)
+
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_in_object_transfer_full(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+ gc.collect()
+
+ # python wrapper should take sole ownership of the gobject
+ self.assertEqual(vfuncs.in_object_grefcount, 1)
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ # ensure python wrapper took ownership and released, after vfunc was complete
+ self.assertEqual(ref_count, 0)
+ self.assertFalse(is_floating)
+
+ self.assertTrue(vfuncs.object_ref() is None)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+class TestVFuncsWithFloatingArg(unittest.TestCase):
+ # All tests here work with a floating object by using InitiallyUnowned as the argument
+
+ class VFuncs(VFuncsBase):
+ # Object for testing non-floating objects without holding any refs.
+ Object = GObject.InitiallyUnowned
+ ObjectRef = weakref.ref
+
+ def test_vfunc_return_object_transfer_none_with_floating(self):
+ # Python is expected to return a single floating reference without warning.
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+ # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+ # should be a single floating ref
+ self.assertEqual(ref_count, 1)
+ self.assertTrue(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_out_object_transfer_none_with_floating(self):
+ # Same as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+ self.assertEqual(ref_count, 1)
+ self.assertTrue(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_return_object_transfer_full_with_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+ # The vfunc caller receives full ownership of a single ref.
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_out_object_transfer_full_with_floating(self):
+ # Same as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_in_object_transfer_none_with_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+ gc.collect()
+
+ # python wrapper should maintain the object as floating and add an additional ref
+ self.assertEqual(vfuncs.in_object_grefcount, 2)
+ self.assertTrue(vfuncs.in_object_is_floating)
+
+ # vfunc caller should only have a single floating ref after the vfunc finishes
+ self.assertEqual(ref_count, 1)
+ self.assertTrue(is_floating)
+
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_in_object_transfer_full_with_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+ gc.collect()
+
+ # python wrapper sinks and owns the gobject
+ self.assertEqual(vfuncs.in_object_grefcount, 1)
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ # ensure python wrapper took ownership and released
+ self.assertEqual(ref_count, 0)
+ self.assertFalse(is_floating)
+
+ self.assertTrue(vfuncs.object_ref() is None)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+class TestVFuncsWithHeldObjectArg(unittest.TestCase):
+ # Same tests as TestVFuncsWithObjectArg except we hold
+ # onto the python object reference in all cases.
+
+ class VFuncs(VFuncsBase):
+ # Object for testing non-floating objects with a held ref.
+ Object = GObject.Object
+ ObjectRef = StrongRef
+
+ def test_vfunc_return_object_transfer_none_with_held_object(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+ # Python holds the single gobject ref in 'vfuncs.object_ref'
+ # Because of this, we do not expect a floating ref or a ref increase.
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ # The actual grefcount should stay at 1 even after the vfunc return.
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_out_object_transfer_none_with_held_object(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_return_object_transfer_full_with_held_object(self):
+ # The vfunc caller receives full ownership which should not
+ # be floating. However, the held python wrapper also has a ref.
+
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+ # Ref count from the perspective of C after the vfunc is called
+ # The vfunc caller receives a new reference which should not
+ # be floating. However, the held python wrapper also has a ref.
+ self.assertEqual(ref_count, 2)
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ # The vfunc caller should have decremented its reference.
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_out_object_transfer_full_with_held_object(self):
+ # Same test as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+ # Ref count from the perspective of C after the vfunc is called
+ # The vfunc caller receives a new reference which should not
+ # be floating. However, the held python wrapper also has a ref.
+ self.assertEqual(ref_count, 2)
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ # The vfunc caller should have decremented its reference.
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_in_object_transfer_none_with_held_object(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+ gc.collect()
+
+ # Ref count inside vfunc from the perspective of Python
+ self.assertEqual(vfuncs.in_object_grefcount, 2) # initial + python wrapper
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(ref_count, 2) # kept after vfunc + held python wrapper
+ self.assertFalse(is_floating)
+
+ # Current ref count after C cleans up its reference
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_in_object_transfer_full_with_held_object(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+ gc.collect()
+
+ # Ref count inside vfunc from the perspective of Python
+ self.assertEqual(vfuncs.in_object_grefcount, 1) # python wrapper takes ownership of the gobject
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+class TestVFuncsWithHeldFloatingArg(unittest.TestCase):
+ # Tests for a floating object which we hold a reference to the python wrapper
+ # on the VFuncs test class.
+
+ class VFuncs(VFuncsBase):
+ # Object for testing floating objects with a held ref.
+ Object = GObject.InitiallyUnowned
+ ObjectRef = StrongRef
+
+ def test_vfunc_return_object_transfer_none_with_held_floating(self):
+ # Python holds onto the wrapper which basically means the floating ref
+ # should also be owned by python.
+
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+ # This is a borrowed ref from what is held in python.
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ # The actual grefcount should stay at 1 even after the vfunc return.
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_out_object_transfer_none_with_held_floating(self):
+ # Same as above
+
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(is_floating)
+
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_return_object_transfer_full_with_held_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(ref_count, 2)
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ # vfunc wrapper destroyes ref it was given
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref)
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_out_object_transfer_full_with_held_floating(self):
+ # Same test as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(ref_count, 2)
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ # vfunc wrapper destroyes ref it was given
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_in_floating_transfer_none_with_held_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+ gc.collect()
+
+ # Ref count inside vfunc from the perspective of Python
+ self.assertTrue(vfuncs.in_object_is_floating)
+ self.assertEqual(vfuncs.in_object_grefcount, 2) # python wrapper sinks and owns the gobject
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertTrue(is_floating)
+ self.assertEqual(ref_count, 2) # floating + held by wrapper
+
+ # Current ref count after C cleans up its reference
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+ def test_vfunc_in_floating_transfer_full_with_held_floating(self):
+ vfuncs = self.VFuncs()
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+ gc.collect()
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(vfuncs.in_object_grefcount, 1) # python wrapper sinks and owns the gobject
+ self.assertFalse(vfuncs.in_object_is_floating)
+
+ # Ref count from the perspective of C after the vfunc is called
+ self.assertEqual(ref_count, 1) # held by wrapper
+ self.assertFalse(is_floating)
+
+ # Current ref count
+ self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+ held_object_ref = weakref.ref(vfuncs.object_ref())
+ del vfuncs.object_ref
+ gc.collect()
+ self.assertTrue(held_object_ref() is None)
+
+
+class TestPropertyHoldingObject(unittest.TestCase):
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_props_getter_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+
+ initial_ref_count = held.__grefcount__
+ holder.props.some_object
+ gc.collect()
+ self.assertEqual(held.__grefcount__, initial_ref_count)
+
+ @unittest.skipUnless(hasattr(GIMarshallingTests.PropertiesObject.props, 'some_object'),
+ 'too old gobject-introspection')
+ def test_get_property_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+
+ initial_ref_count = held.__grefcount__
+ holder.get_property('some-object')
+ gc.collect()
+ self.assertEqual(held.__grefcount__, initial_ref_count)
+
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_props_setter_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ # Setting property should only increase ref count by 1
+ holder.props.some_object = held
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 2)
+
+ # Clearing should pull it back down
+ holder.props.some_object = None
+ self.assertEqual(held.__grefcount__, 1)
+
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_set_property_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ # Setting property should only increase ref count by 1
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 2)
+
+ # Clearing should pull it back down
+ holder.set_property('some-object', None)
+ self.assertEqual(held.__grefcount__, 1)
# coding: UTF-8
# vim: tabstop=4 shiftwidth=4 expandtab
+import contextlib
import unittest
from compathelper import _unicode, _bytes
Gtk = None
+@contextlib.contextmanager
+def realized(widget):
+ """Makes sure the widget is realized.
+
+ view = Gtk.TreeView()
+ with realized(view):
+ do_something(view)
+ """
+
+ if isinstance(widget, Gtk.Window):
+ toplevel = widget
+ else:
+ toplevel = widget.get_parent_window()
+
+ if toplevel is None:
+ window = Gtk.Window()
+ window.add(widget)
+
+ widget.realize()
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+ assert widget.get_realized()
+ yield widget
+
+ if toplevel is None:
+ window.remove(widget)
+ window.destroy()
+
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+
+
@unittest.skipUnless(Gtk, 'Gtk not available')
class TestGtk(unittest.TestCase):
def test_container(self):
self.assertTrue(hasattr(widget.drag_dest_set_proxy, '__call__'))
self.assertTrue(hasattr(widget.drag_get_data, '__call__'))
+ def test_drag_target_list(self):
+ mixed_target_list = [Gtk.TargetEntry.new('test0', 0, 0),
+ ('test1', 1, 1),
+ Gtk.TargetEntry.new('test2', 2, 2),
+ ('test3', 3, 3)]
+
+ def _test_target_list(targets):
+ for i, target in enumerate(targets):
+ self.assertTrue(isinstance(target, Gtk.TargetEntry))
+ self.assertEqual(target.target, 'test' + str(i))
+ self.assertEqual(target.flags, i)
+ self.assertEqual(target.info, i)
+
+ _test_target_list(Gtk._construct_target_list(mixed_target_list))
+
+ widget = Gtk.Button()
+ widget.drag_dest_set(Gtk.DestDefaults.DROP, None, Gdk.DragAction.COPY)
+ widget.drag_dest_set_target_list(mixed_target_list)
+ widget.drag_dest_get_target_list()
+
+ widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, None, Gdk.DragAction.MOVE)
+ widget.drag_source_set_target_list(mixed_target_list)
+ widget.drag_source_get_target_list()
+
+ treeview = Gtk.TreeView()
+ treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
+ mixed_target_list,
+ Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
+ treeview.enable_model_drag_dest(mixed_target_list,
+ Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
def test_scrollbar(self):
# PyGTK compat
adjustment = Gtk.Adjustment()
store.append((0, "foo"))
store.append((1, "bar"))
view = Gtk.TreeView()
- # FIXME: We can't easily call get_cursor() to make sure this works as
- # expected as we need to realize and focus the column; the following
- # will raise a Gtk-CRITICAL which we ignore for now
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_WARNING | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
+
+ with realized(view):
view.set_cursor(store[1].path)
view.set_cursor(str(store[1].path))
view.get_cell_area(store[1].path)
view.get_cell_area(str(store[1].path))
- finally:
- GLib.log_set_always_fatal(old_mask)
def test_tree_view_column(self):
cell = Gtk.CellRendererText()
# unconnected
tree.insert_column_with_attributes(-1, 'Head4', cell4)
- # might cause a Pango warning, do not break on this
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
- # We must realize the TreeView for cell.props.text to receive a value
- dialog = Gtk.Dialog()
- dialog.get_action_area().add(tree)
- dialog.show_all()
- dialog.hide()
- finally:
- GLib.log_set_always_fatal(old_mask)
+ with realized(tree):
+ tree.set_cursor(model[0].path)
+ while Gtk.events_pending():
+ Gtk.main_iteration()
- self.assertEqual(tree.get_column(0).get_title(), 'Head1')
- self.assertEqual(tree.get_column(1).get_title(), 'Head2')
- self.assertEqual(tree.get_column(2).get_title(), 'Head3')
- self.assertEqual(tree.get_column(3).get_title(), 'Head4')
+ self.assertEqual(tree.get_column(0).get_title(), 'Head1')
+ self.assertEqual(tree.get_column(1).get_title(), 'Head2')
+ self.assertEqual(tree.get_column(2).get_title(), 'Head3')
+ self.assertEqual(tree.get_column(3).get_title(), 'Head4')
- # cursor should be at the first row
- self.assertEqual(cell1.props.text, 'cell11')
- self.assertEqual(cell2.props.text, 'cell12')
- self.assertEqual(cell3.props.text, 'cell13')
- self.assertEqual(cell4.props.text, None)
+ # cursor should be at the first row
+ self.assertEqual(cell1.props.text, 'cell11')
+ self.assertEqual(cell2.props.text, 'cell12')
+ self.assertEqual(cell3.props.text, 'cell13')
+ self.assertEqual(cell4.props.text, None)
def test_tree_view_column_set_attributes(self):
store = Gtk.ListStore(int, str)
column.pack_start(cell, expand=True)
column.set_attributes(cell, text=1)
- # might cause a Pango warning, do not break on this
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
- # We must realize the TreeView for cell.props.text to receive a value
- dialog = Gtk.Dialog()
- dialog.get_action_area().add(treeview)
- dialog.show_all()
- dialog.hide()
- finally:
- GLib.log_set_always_fatal(old_mask)
-
- self.assertTrue(cell.props.text in directors)
+ with realized(treeview):
+ self.assertTrue(cell.props.text in directors)
def test_tree_selection(self):
store = Gtk.ListStore(int, str)