[pygobject/wip/jfelder/template-gtk4: 4/4] gtk overrides: Add support for builder scope



commit 77a837fdcab8e47c202ab8e1776f91bbbae53e3d
Author: Jean Felder <jfelder src gnome org>
Date:   Mon Apr 27 22:37:29 2020 +0200

    gtk overrides: Add support for builder scope

 gi/_gtktemplate.py          | 49 +++++++++++++++++++++++++++-------
 gi/overrides/Gtk.py         | 64 ++++++++++++++++++---------------------------
 gi/pygi-struct-marshal.c    |  5 +++-
 tests/test_overrides_gtk.py | 55 +++++++++++++++++++++++++-------------
 4 files changed, 106 insertions(+), 67 deletions(-)
---
diff --git a/gi/_gtktemplate.py b/gi/_gtktemplate.py
index 15f6b2c5..a631a6eb 100644
--- a/gi/_gtktemplate.py
+++ b/gi/_gtktemplate.py
@@ -17,26 +17,57 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 # USA
 
+from collections import abc
 from functools import partial
 
 from gi.repository import GLib, GObject, Gio
 
 
+def _extract_handler_and_args(obj_or_map, handler_name):
+    handler = None
+    if isinstance(obj_or_map, abc.Mapping):
+        handler = obj_or_map.get(handler_name, None)
+    else:
+        handler = getattr(obj_or_map, handler_name, None)
+
+    if handler is None:
+        raise AttributeError('Handler %s not found' % handler_name)
+
+    args = ()
+    if isinstance(handler, abc.Sequence):
+        if len(handler) == 0:
+            raise TypeError("Handler %s tuple can not be empty" % handler)
+        args = handler[1:]
+        handler = handler[0]
+
+    elif not callable(handler):
+        raise TypeError('Handler %s is not a method, function or tuple' % handler)
+
+    return handler, args
+
+
 def define_builder_scope():
     from gi.repository import Gtk
 
     class BuilderScope(GObject.GObject, Gtk.BuilderScope):
 
-        def __init__(self):
+        def __init__(self, scope_object=None):
             super().__init__()
+            self._scope_object = scope_object
 
         def do_create_closure(self, builder, func_name, flags, obj):
-            current_object = builder.get_current_object()
+            current_object = builder.get_current_object() or self._scope_object
 
-            if func_name not in current_object.__gtktemplate_methods__:
-                return None
+            if not self._scope_object:
+                current_object = builder.get_current_object()
+                if func_name not in current_object.__gtktemplate_methods__:
+                    return None
 
-            current_object.__gtktemplate_handlers__.add(func_name)
+                current_object.__gtktemplate_handlers__.add(func_name)
+                handler_name = current_object.__gtktemplate_methods__[func_name]
+            else:
+                current_object = self._scope_object
+                handler_name = func_name
 
             swapped = int(flags & Gtk.BuilderClosureFlags.SWAPPED)
             if swapped:
@@ -44,12 +75,12 @@ def define_builder_scope():
                     "%r not supported" % GObject.ConnectFlags.SWAPPED)
                 return None
 
-            handler_name = current_object.__gtktemplate_methods__[func_name]
-            handler = getattr(current_object, handler_name)
+            handler, args = _extract_handler_and_args(current_object, handler_name)
+
             if obj:
-                return partial(handler, swap_data=obj)
+                return partial(handler, *args, swap_data=obj)
 
-            return handler
+            return partial(handler, *args)
 
     return BuilderScope
 
diff --git a/gi/overrides/Gtk.py b/gi/overrides/Gtk.py
index 64a5088e..e539dabf 100644
--- a/gi/overrides/Gtk.py
+++ b/gi/overrides/Gtk.py
@@ -21,11 +21,10 @@
 
 import sys
 import warnings
-from collections import abc
 
 from gi.repository import GObject
 from .._ossighelper import wakeup_on_signal, register_sigint_fallback
-from .._gtktemplate import Template
+from .._gtktemplate import Template, _extract_handler_and_args
 from ..overrides import override, strip_boolean_result, deprecated_init
 from ..module import get_introspection_module
 from gi import PyGIDeprecationWarning
@@ -39,6 +38,10 @@ __all__ = []
 Template = Template
 __all__.append('Template')
 
+# Exposed for unit-testing.
+_extract_handler_and_args = _extract_handler_and_args
+__all__.append('_extract_handler_and_args')
+
 if Gtk._version == '2.0':
     warn_msg = "You have imported the Gtk 2.0 module.  Because Gtk 2.0 \
 was not designed for use with introspection some of the \
@@ -74,33 +77,6 @@ def _construct_target_list(targets):
 __all__.append('_construct_target_list')
 
 
-def _extract_handler_and_args(obj_or_map, handler_name):
-    handler = None
-    if isinstance(obj_or_map, abc.Mapping):
-        handler = obj_or_map.get(handler_name, None)
-    else:
-        handler = getattr(obj_or_map, handler_name, None)
-
-    if handler is None:
-        raise AttributeError('Handler %s not found' % handler_name)
-
-    args = ()
-    if isinstance(handler, abc.Sequence):
-        if len(handler) == 0:
-            raise TypeError("Handler %s tuple can not be empty" % handler)
-        args = handler[1:]
-        handler = handler[0]
-
-    elif not callable(handler):
-        raise TypeError('Handler %s is not a method, function or tuple' % handler)
-
-    return handler, args
-
-
-# Exposed for unit-testing.
-__all__.append('_extract_handler_and_args')
-
-
 def _builder_connect_callback(builder, gobj, signal_name, handler_name, connect_obj, flags, obj_or_map):
     handler, args = _extract_handler_and_args(obj_or_map, handler_name)
 
@@ -465,19 +441,29 @@ def _get_utf8_length(string):
 
 
 class Builder(Gtk.Builder):
-    def connect_signals(self, obj_or_map):
-        """Connect signals specified by this builder to a name, handler mapping.
+    if Gtk._version == '4.0':
+        from .._gtktemplate import define_builder_scope
+        BuilderScope = define_builder_scope()
 
-        Connect signal, name, and handler sets specified in the builder with
-        the given mapping "obj_or_map". The handler/value aspect of the mapping
-        can also contain a tuple in the form of (handler [,arg1 [,argN]])
-        allowing for extra arguments to be passed to the handler. For example:
+        def __init__(self, scope_object_or_map=None):
+            super(Builder, self).__init__()
+            if scope_object_or_map:
+                self.set_scope(Builder.BuilderScope(scope_object_or_map))
 
-        .. code-block:: python
+    else:
+        def connect_signals(self, obj_or_map):
+            """Connect signals specified by this builder to a name, handler mapping.
 
-            builder.connect_signals({'on_clicked': (on_clicked, arg1, arg2)})
-        """
-        self.connect_signals_full(_builder_connect_callback, obj_or_map)
+            Connect signal, name, and handler sets specified in the builder with
+            the given mapping "obj_or_map". The handler/value aspect of the mapping
+            can also contain a tuple in the form of (handler [,arg1 [,argN]])
+            allowing for extra arguments to be passed to the handler. For example:
+
+            .. code-block:: python
+
+                builder.connect_signals({'on_clicked': (on_clicked, arg1, arg2)})
+            """
+            self.connect_signals_full(_builder_connect_callback, obj_or_map)
 
     def add_from_string(self, buffer):
         if not isinstance(buffer, str):
diff --git a/gi/pygi-struct-marshal.c b/gi/pygi-struct-marshal.c
index 13715888..0e190b8e 100644
--- a/gi/pygi-struct-marshal.c
+++ b/gi/pygi-struct-marshal.c
@@ -193,16 +193,19 @@ pygi_arg_gclosure_from_py_marshal (PyObject   *py_arg,
 
         if (partial && PyObject_IsInstance (py_arg, partial) > 0) {
             PyObject *partial_func;
+            PyObject *partial_args;
             PyObject *partial_keywords;
             PyObject *swap_data;
 
             partial_func = PyObject_GetAttrString (py_arg, "func");
+            partial_args = PyObject_GetAttrString (py_arg, "args");
             partial_keywords = PyObject_GetAttrString (py_arg, "keywords");
             swap_data = PyDict_GetItemString (partial_keywords, "swap_data");
 
-            closure = pyg_closure_new (partial_func, NULL, swap_data);
+            closure = pyg_closure_new (partial_func, partial_args, swap_data);
 
             Py_DECREF (partial_func);
+            Py_DECREF (partial_args);
             Py_DECREF (partial_keywords);
             g_closure_ref (closure);
             g_closure_sink (closure);
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
index 57db828b..a2f785c4 100644
--- a/tests/test_overrides_gtk.py
+++ b/tests/test_overrides_gtk.py
@@ -1052,7 +1052,19 @@ class TestBuilder(unittest.TestCase):
             Gtk._extract_handler_and_args({"foo": []}, "foo")
 
     def test_builder_with_handler_and_args(self):
-        builder = Gtk.Builder()
+        args_collector = []
+
+        def on_signal(*args):
+            args_collector.append(args)
+
+        signals_dict = {'on_signal1': (on_signal, 1, 2),
+                        'on_signal2': on_signal}
+
+        if GTK4:
+            builder = Gtk.Builder(signals_dict)
+        else:
+            builder = Gtk.Builder()
+
         builder.add_from_string("""
             <interface>
               <object class="GIOverrideSignalTest" id="object_sig_test">
@@ -1062,13 +1074,8 @@ class TestBuilder(unittest.TestCase):
             </interface>
             """)
 
-        args_collector = []
-
-        def on_signal(*args):
-            args_collector.append(args)
-
-        builder.connect_signals({'on_signal1': (on_signal, 1, 2),
-                                 'on_signal2': on_signal})
+        if not GTK4:
+            builder.connect_signals(signals_dict)
 
         objects = builder.get_objects()
         self.assertEqual(len(objects), 1)
@@ -1080,7 +1087,19 @@ class TestBuilder(unittest.TestCase):
         self.assertSequenceEqual(args_collector[1], (obj, ))
 
     def test_builder_with_handler_object(self):
-        builder = Gtk.Builder()
+        args_collector = []
+
+        def on_signal(*args):
+            args_collector.append(args)
+
+        signals_dict = {'on_signal1': (on_signal, 1, 2),
+                        'on_signal2': on_signal}
+
+        if GTK4:
+            builder = Gtk.Builder(signals_dict)
+        else:
+            builder = Gtk.Builder()
+
         builder.add_from_string("""
             <interface>
               <object class="GIOverrideSignalTestObject" id="foo"/>
@@ -1091,13 +1110,9 @@ class TestBuilder(unittest.TestCase):
             </interface>
             """)
 
-        args_collector = []
-
-        def on_signal(*args):
-            args_collector.append(args)
+        if not GTK4:
+            builder.connect_signals(signals_dict)
 
-        builder.connect_signals({'on_signal1': (on_signal, 1, 2),
-                                 'on_signal2': on_signal})
         obj = builder.get_object("foo")
         emitter = builder.get_object("object_sig_test")
         emitter.emit("test-signal")
@@ -1125,7 +1140,10 @@ class TestBuilder(unittest.TestCase):
                     self.after_sentinel += 1
 
         signal_checker = SignalCheck()
-        builder = Gtk.Builder()
+        if GTK4:
+            builder = Gtk.Builder(signal_checker)
+        else:
+            builder = Gtk.Builder()
 
         # add object1 to the builder
         builder.add_from_string("""
@@ -1152,8 +1170,9 @@ class TestBuilder(unittest.TestCase):
 </interface>
 """, ['object3'])
 
-        # hook up signals
-        builder.connect_signals(signal_checker)
+        # hook up signals for Gtk3
+        if not GTK4:
+            builder.connect_signals(signal_checker)
 
         # call their notify signals and check sentinel
         objects = builder.get_objects()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]