summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDongHun Kwak <dh0128.kwak@samsung.com>2017-07-12 08:48:42 +0900
committerDongHun Kwak <dh0128.kwak@samsung.com>2017-07-12 08:48:45 +0900
commit265684897ba0b24e28710a988cab901acbec1775 (patch)
tree332d43c2c54979ddb5390359f6ab1d865bdda3b6
parent01ddc2783a1d721a003f181000e266acde134d19 (diff)
downloadpygobject2-265684897ba0b24e28710a988cab901acbec1775.tar.gz
pygobject2-265684897ba0b24e28710a988cab901acbec1775.tar.bz2
pygobject2-265684897ba0b24e28710a988cab901acbec1775.zip
Imported Upstream version 3.7.90
Change-Id: I6f093f89379c4f834493e7edf7a32b8c4ad32393 Signed-off-by: DongHun Kwak <dh0128.kwak@samsung.com>
-rw-r--r--ChangeLog313
-rw-r--r--NEWS17
-rw-r--r--PKG-INFO4
-rwxr-xr-xautogen.sh30
-rwxr-xr-xconfigure29
-rw-r--r--configure.ac12
-rw-r--r--gi/_gobject/gobjectmodule.c10
-rw-r--r--gi/_gobject/pygobject-private.h2
-rw-r--r--gi/_gobject/pygobject.c62
-rw-r--r--gi/_gobject/pygobject.h8
-rw-r--r--gi/overrides/Gtk.py42
-rw-r--r--gi/overrides/__init__.py3
-rw-r--r--gi/pygi-argument.c109
-rw-r--r--gi/pygi-boxed.c31
-rw-r--r--gi/pygi-marshal-from-py.c196
-rw-r--r--gi/pygi-marshal-from-py.h14
-rw-r--r--gi/pygi-marshal-to-py.c45
-rw-r--r--gi/pygi-marshal-to-py.h5
-rw-r--r--gi/pygtkcompat.py2
-rw-r--r--gi/types.py6
-rw-r--r--pygtkcompat/Makefile.am1
-rw-r--r--pygtkcompat/Makefile.in1
-rw-r--r--pygtkcompat/generictreemodel.py420
-rw-r--r--pygtkcompat/pygtkcompat.py3
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/Makefile.in2
-rw-r--r--tests/test_generictreemodel.py406
-rw-r--r--tests/test_gi.py6
-rw-r--r--tests/test_object_marshaling.py616
-rw-r--r--tests/test_overrides_gtk.py123
30 files changed, 2241 insertions, 279 deletions
diff --git a/ChangeLog b/ChangeLog
index 31377c8..220002b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,316 @@
+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
diff --git a/NEWS b/NEWS
index 5e06801..bdd3810 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,20 @@
+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)
diff --git a/PKG-INFO b/PKG-INFO
index 4ffd422..e8e35f4 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
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
@@ -8,7 +8,7 @@ Author-email: james@daa.com.au
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
diff --git a/autogen.sh b/autogen.sh
index 467252d..420917c 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,17 +1,17 @@
#!/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"
@@ -19,7 +19,11 @@ else
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" "$@"
+
diff --git a/configure b/configure
index 4047ab2..a40bdb4 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /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>.
#
@@ -591,8 +591,8 @@ MAKEFLAGS=
# 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/'
@@ -1394,7 +1394,7 @@ if test "$ac_init_help" = "long"; then
# 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]...
@@ -1464,7 +1464,7 @@ fi
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
@@ -1599,7 +1599,7 @@ fi
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.
@@ -1877,7 +1877,7 @@ cat >config.log <<_ACEOF
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 $@
@@ -2241,9 +2241,9 @@ $as_echo "#define PYGOBJECT_MINOR_VERSION 7" >>confdefs.h
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"
@@ -2754,7 +2754,7 @@ fi
# Define the identity of the package.
PACKAGE='pygobject'
- VERSION='3.7.5.1'
+ VERSION='3.7.90'
cat >>confdefs.h <<_ACEOF
@@ -14327,6 +14327,10 @@ $as_echo "$complCFLAGS" >&6; }
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; }
@@ -14561,6 +14565,7 @@ DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
+
if test "x$GCC" = "xyes"; then
case " $CFLAGS " in
@@ -15255,7 +15260,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# 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
@@ -15322,7 +15327,7 @@ _ACEOF
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\\"
diff --git a/configure.ac b/configure.ac
index 3507f4f..a1b43cd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,7 +18,7 @@ m4_define(python3_min_ver, 3.1)
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 ...
@@ -228,7 +228,15 @@ AC_SUBST(INTROSPECTION_COMPILER)
# 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])
diff --git a/gi/_gobject/gobjectmodule.c b/gi/_gobject/gobjectmodule.c
index 96b5c94..fda21e7 100644
--- a/gi/_gobject/gobjectmodule.c
+++ b/gi/_gobject/gobjectmodule.c
@@ -996,7 +996,11 @@ pygobject_constructv(PyGObject *self,
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;
@@ -1034,7 +1038,9 @@ pygobject__g_instance_init(GTypeInstance *instance,
* 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
@@ -1477,7 +1483,7 @@ pyg_object_new (PyGObject *self, PyObject *args, PyObject *kwargs)
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;
diff --git a/gi/_gobject/pygobject-private.h b/gi/_gobject/pygobject-private.h
index 1b1d6db..0d1aca0 100644
--- a/gi/_gobject/pygobject-private.h
+++ b/gi/_gobject/pygobject-private.h
@@ -154,7 +154,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, 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);
diff --git a/gi/_gobject/pygobject.c b/gi/_gobject/pygobject.c
index a23d469..8673e79 100644
--- a/gi/_gobject/pygobject.c
+++ b/gi/_gobject/pygobject.c
@@ -951,7 +951,7 @@ pygobject_lookup_class(GType gtype)
/**
* 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
@@ -962,19 +962,30 @@ pygobject_lookup_class(GType gtype)
* 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);
@@ -1000,13 +1011,15 @@ pygobject_new_full(GObject *obj, gboolean sink, gpointer g_class)
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);
}
@@ -1017,7 +1030,9 @@ pygobject_new_full(GObject *obj, gboolean sink, gpointer g_class)
PyObject *
pygobject_new(GObject *obj)
{
- return pygobject_new_full(obj, TRUE, NULL);
+ return pygobject_new_full(obj,
+ /*steal=*/FALSE,
+ NULL);
}
static void
@@ -1438,14 +1453,17 @@ pygobject_set_properties(PyGObject *self, PyObject *args, PyObject *kwargs)
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;
@@ -2343,7 +2361,7 @@ pygobject_weak_ref_call(PyGObjectWeakRef *self, PyObject *args, PyObject *kw)
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;
diff --git a/gi/_gobject/pygobject.h b/gi/_gobject/pygobject.h
index ba40986..92dc030 100644
--- a/gi/_gobject/pygobject.h
+++ b/gi/_gobject/pygobject.h
@@ -24,7 +24,8 @@ struct _PyGClosure {
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
@@ -187,8 +188,9 @@ struct _PyGObject_Functions {
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_
diff --git a/gi/overrides/Gtk.py b/gi/overrides/Gtk.py
index 25f127f..002f22b 100644
--- a/gi/overrides/Gtk.py
+++ b/gi/overrides/Gtk.py
@@ -48,6 +48,22 @@ python module to use with Gtk 2.0"
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):
@@ -59,6 +75,17 @@ class Widget(Gtk.Widget):
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')
@@ -1160,7 +1187,7 @@ class TreePath(Gtk.TreePath):
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:
@@ -1306,23 +1333,14 @@ class TreeView(Gtk.TreeView, Container):
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)
diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py
index b7d365c..0bd94b8 100644
--- a/gi/overrides/__init__.py
+++ b/gi/overrides/__init__.py
@@ -55,7 +55,8 @@ class overridefunc(object):
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):
diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
index 53e0b5c..3b65246 100644
--- a/gi/pygi-argument.c
+++ b/gi/pygi-argument.c
@@ -31,6 +31,10 @@
#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,
@@ -1235,55 +1239,20 @@ array_success:
/* 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)) {
@@ -1329,17 +1298,10 @@ array_success:
}
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();
}
@@ -1856,23 +1818,26 @@ _pygi_argument_to_object (GIArgument *arg,
}
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:
diff --git a/gi/pygi-boxed.c b/gi/pygi-boxed.c
index ff3db9b..c15c927 100644
--- a/gi/pygi-boxed.c
+++ b/gi/pygi-boxed.c
@@ -53,24 +53,19 @@ _pygi_boxed_alloc (GIBaseInfo *info, gsize *size_out)
{
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)
diff --git a/gi/pygi-marshal-from-py.c b/gi/pygi-marshal-from-py.c
index 9f7d874..01138bc 100644
--- a/gi/pygi-marshal-from-py.c
+++ b/gi/pygi-marshal-from-py.c
@@ -893,7 +893,7 @@ _pygi_marshal_from_py_array (PyGIInvokeState *state,
GIArgument *arg)
{
PyGIMarshalFromPyFunc from_py_marshaller;
- int i;
+ int i = 0;
Py_ssize_t length;
gssize item_size;
gboolean is_ptr_array;
@@ -1629,53 +1629,11 @@ _pygi_marshal_from_py_interface_struct (PyGIInvokeState *state,
*/
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,
@@ -1760,11 +1718,7 @@ _pygi_marshal_from_py_interface_object (PyGIInvokeState *state,
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
@@ -1855,3 +1809,141 @@ gboolean _pygi_marshal_from_py_interface_instance (PyGIInvokeState *state,
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;
+}
diff --git a/gi/pygi-marshal-from-py.h b/gi/pygi-marshal-from-py.h
index b82f890..e0a57d3 100644
--- a/gi/pygi-marshal-from-py.h
+++ b/gi/pygi-marshal-from-py.h
@@ -184,6 +184,20 @@ gboolean _pygi_marshal_from_py_interface_instance (PyGIInvokeState *state,
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__ */
diff --git a/gi/pygi-marshal-to-py.c b/gi/pygi-marshal-to-py.c
index a6d90c2..172561d 100644
--- a/gi/pygi-marshal-to-py.c
+++ b/gi/pygi-marshal-to-py.c
@@ -877,28 +877,7 @@ _pygi_marshal_to_py_interface_object (PyGIInvokeState *state,
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 *
@@ -913,3 +892,25 @@ _pygi_marshal_to_py_interface_union (PyGIInvokeState *state,
"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;
+}
diff --git a/gi/pygi-marshal-to-py.h b/gi/pygi-marshal-to-py.h
index a07a13e..f2e1a5c 100644
--- a/gi/pygi-marshal-to-py.h
+++ b/gi/pygi-marshal-to-py.h
@@ -139,6 +139,11 @@ PyObject *_pygi_marshal_to_py_interface_union (PyGIInvokeState *state,
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__ */
diff --git a/gi/pygtkcompat.py b/gi/pygtkcompat.py
index 91b5cc1..9a80eef 100644
--- a/gi/pygtkcompat.py
+++ b/gi/pygtkcompat.py
@@ -2,7 +2,7 @@ import warnings
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,
diff --git a/gi/types.py b/gi/types.py
index f049da7..c44833d 100644
--- a/gi/types.py
+++ b/gi/types.py
@@ -100,14 +100,14 @@ def wraps_callable_info(info):
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)
@@ -128,7 +128,7 @@ class NativeVFunc(object):
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()
diff --git a/pygtkcompat/Makefile.am b/pygtkcompat/Makefile.am
index 6a73cb4..914b3e2 100644
--- a/pygtkcompat/Makefile.am
+++ b/pygtkcompat/Makefile.am
@@ -2,6 +2,7 @@ pygtkcompatdir = $(pyexecdir)/pygtkcompat
pygtkcompat_PYTHON = \
__init__.py \
+ generictreemodel.py \
pygtkcompat.py
# if we build in a separate tree, we need to symlink the *.py files from the
diff --git a/pygtkcompat/Makefile.in b/pygtkcompat/Makefile.in
index ff8ad79..9fddcf1 100644
--- a/pygtkcompat/Makefile.in
+++ b/pygtkcompat/Makefile.in
@@ -274,6 +274,7 @@ top_srcdir = @top_srcdir@
pygtkcompatdir = $(pyexecdir)/pygtkcompat
pygtkcompat_PYTHON = \
__init__.py \
+ generictreemodel.py \
pygtkcompat.py
all: all-am
diff --git a/pygtkcompat/generictreemodel.py b/pygtkcompat/generictreemodel.py
new file mode 100644
index 0000000..ad67d90
--- /dev/null
+++ b/pygtkcompat/generictreemodel.py
@@ -0,0 +1,420 @@
+# -*- 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
diff --git a/pygtkcompat/pygtkcompat.py b/pygtkcompat/pygtkcompat.py
index 18ebd7b..02394f9 100644
--- a/pygtkcompat/pygtkcompat.py
+++ b/pygtkcompat/pygtkcompat.py
@@ -427,6 +427,9 @@ def enable_gtk(version='2.0'):
value = getattr(Gdk, name)
setattr(keysyms, target, value)
+ from . import generictreemodel
+ Gtk.GenericTreeModel = generictreemodel.GenericTreeModel
+
def enable_vte():
gi.require_version('Vte', '0.0')
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1d40539..287542d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -92,6 +92,7 @@ EXTRA_DIST = \
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
@@ -107,6 +108,7 @@ EXTRA_DIST = \
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 \
diff --git a/tests/Makefile.in b/tests/Makefile.in
index a29792b..5c8f709 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -337,6 +337,7 @@ EXTRA_DIST = \
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
@@ -352,6 +353,7 @@ EXTRA_DIST = \
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 \
diff --git a/tests/test_generictreemodel.py b/tests/test_generictreemodel.py
new file mode 100644
index 0000000..ff0f523
--- /dev/null
+++ b/tests/test_generictreemodel.py
@@ -0,0 +1,406 @@
+# -*- 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()
diff --git a/tests/test_gi.py b/tests/test_gi.py
index ee2f3de..4545539 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2785,6 +2785,12 @@ class TestPropertiesObject(unittest.TestCase):
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):
diff --git a/tests/test_object_marshaling.py b/tests/test_object_marshaling.py
new file mode 100644
index 0000000..62570bc
--- /dev/null
+++ b/tests/test_object_marshaling.py
@@ -0,0 +1,616 @@
+# -*- 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)
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
index 9315019..69f0d38 100644
--- a/tests/test_overrides_gtk.py
+++ b/tests/test_overrides_gtk.py
@@ -2,6 +2,7 @@
# coding: UTF-8
# vim: tabstop=4 shiftwidth=4 expandtab
+import contextlib
import unittest
from compathelper import _unicode, _bytes
@@ -17,6 +18,38 @@ except ImportError:
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):
@@ -519,6 +552,38 @@ class TestGtk(unittest.TestCase):
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()
@@ -1363,19 +1428,13 @@ class TestTreeView(unittest.TestCase):
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()
@@ -1403,28 +1462,21 @@ class TestTreeView(unittest.TestCase):
# 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)
@@ -1442,19 +1494,8 @@ class TestTreeView(unittest.TestCase):
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)