Imported Upstream version 3.7.90 16/138316/1
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 11 Jul 2017 23:48:42 +0000 (08:48 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 11 Jul 2017 23:48:45 +0000 (08:48 +0900)
Change-Id: I6f093f89379c4f834493e7edf7a32b8c4ad32393
Signed-off-by: DongHun Kwak <dh0128.kwak@samsung.com>
30 files changed:
ChangeLog
NEWS
PKG-INFO
autogen.sh
configure
configure.ac
gi/_gobject/gobjectmodule.c
gi/_gobject/pygobject-private.h
gi/_gobject/pygobject.c
gi/_gobject/pygobject.h
gi/overrides/Gtk.py
gi/overrides/__init__.py
gi/pygi-argument.c
gi/pygi-boxed.c
gi/pygi-marshal-from-py.c
gi/pygi-marshal-from-py.h
gi/pygi-marshal-to-py.c
gi/pygi-marshal-to-py.h
gi/pygtkcompat.py
gi/types.py
pygtkcompat/Makefile.am
pygtkcompat/Makefile.in
pygtkcompat/generictreemodel.py [new file with mode: 0644]
pygtkcompat/pygtkcompat.py
tests/Makefile.am
tests/Makefile.in
tests/test_generictreemodel.py [new file with mode: 0644]
tests/test_gi.py
tests/test_object_marshaling.py [new file with mode: 0644]
tests/test_overrides_gtk.py

index 31377c8700032a9830148dfdc31e7f12729bd311..220002b3a1fc36816d35e54c6d08576e89f515f5 100644 (file)
--- 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 5e06801f87e185864959a209a99ca6bac581a54a..bdd3810d4fc53a0808b90e4fd1eac43e33262800 100644 (file)
--- 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)
 
index 4ffd4225213ec356e1d32ddee8e66773bb219f1b..e8e35f4d0ff76d562bb594790fc30e3ecbc3596f 100644 (file)
--- 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
index 467252d62bdf8bdf4236ef9f8bc725ff02f034bc..420917cc2a33bcf851fffb0ce1e254d65745fb57 100755 (executable)
@@ -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" "$@"
+
index 4047ab2472a0a3813f73c0ef13ba4c2df942bd5d..a40bdb4c49d82f272aec17fbfa118ccf53ef18ff 100755 (executable)
--- 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\\"
 
index 3507f4f96b68bb2612e9ae3c09f49b91e5c287e1..a1b43cdb36a5b000bfdf8c2be0119acc05d4c3a2 100644 (file)
@@ -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])
index 96b5c94a2da297cf30afd9ddf7469f4239b0b8d8..fda21e764507b01b5670a1e4a34d0c2e957b28fa 100644 (file)
@@ -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;
index 1b1d6db65b0f593589c654ae646db1ea6634de3c..0d1aca02edcfa4686e7eba39265f9c38a22a64b1 100644 (file)
@@ -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);
index a23d46987b1da9b6d0ee13e77c9b373cffee0b66..8673e79b09df79890c72fe60e36fd786bea2d6a5 100644 (file)
@@ -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;
index ba40986c121b1d9aa3306d24fb33df58a05d6f50..92dc03059a9411d746faed6625ed71760aaca13e 100644 (file)
@@ -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_
index 25f127fa2f1e6748218591416c2523f15ae90b3e..002f22ba6fdf0822af4b0c29c4520da981ba8bf5 100644 (file)
@@ -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)
 
index b7d365cef45538dd896eee07d4209fe9d89f4aef..0bd94b8da2912f8aa301475922c96f14a5e7479e 100644 (file)
@@ -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):
index 53e0b5cdde0bfe31589e6249fc516463078cc942..3b65246cb1bd7152b2b2c33f658aa90537afd762 100644 (file)
 #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:
index ff3db9b1745ef18c08502dc81949795d9a707198..c15c927a20bd6bfb6f5b16278c3c866205ace473 100644 (file)
@@ -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)
index 9f7d87499891606094229b321ad006b90be78167..01138bc64f9694af65401885af8d9236880401d1 100644 (file)
@@ -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;
+}
index b82f8909021fff9c63efad3de7108d19f7567340..e0a57d3f335d393a0970a770091dd386280d1364 100644 (file)
@@ -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__ */
index a6d90c26cb4b978b0287daa275fcd3d306f510c3..172561dcb15ea74febdd58f21c55d6d7503dfb30 100644 (file)
@@ -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;
+}
index a07a13e8f27a6128b1a89ed7a95de60dcdebf172..f2e1a5ca7bf8a07d8a9d6c48c6a698484e9e0ed4 100644 (file)
@@ -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__ */
index 91b5cc1910c0c504c7159752ac1c31ebf1edfb0c..9a80eefa8984883d57cfdbad50429ac53dfaf71e 100644 (file)
@@ -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,
index f049da7054f31f39f0a4ac3e2613fe36196e8815..c44833d56200d9dca4760eeb1af377a68cb9f86b 100644 (file)
@@ -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()
index 6a73cb4cf6343e6a61145e6ed86fa483f06c4ae8..914b3e258cc84fb188240020995585d21fa1e079 100644 (file)
@@ -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
index ff8ad7971170c94fa16573499b6031e79ccfc5ed..9fddcf14d5e4d093990aba755eabc64eea2233b4 100644 (file)
@@ -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 (file)
index 0000000..ad67d90
--- /dev/null
@@ -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
index 18ebd7b97e56662204b4271471b13e140b5dce52..02394f91e3cb0c90cd0cd523e2439eb81fd0f892 100644 (file)
@@ -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')
index 1d405398bd52bb6fa20bd88849a2738f37ffc421..287542d35e9c2447eafeaac8bb23470bf0da1861 100644 (file)
@@ -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 \
index a29792b5366b5f93343a06beb4793b5c60074117..5c8f7092c780eddfb7e69d5fb59f6045ada9d3bf 100644 (file)
@@ -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 (file)
index 0000000..ff0f523
--- /dev/null
@@ -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()
index ee2f3de6a460386857399e612b6f8245694df563..45455399774f66d4508921d42bf749df401cbe86 100644 (file)
@@ -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 (file)
index 0000000..62570bc
--- /dev/null
@@ -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)
index 93150199a251a73776470a398d538b5128e5a24d..69f0d380a044587f0c10d77d0de2a21557679a10 100644 (file)
@@ -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)