[pygobject/wip/jfelder/template-gtk4: 5/5] gtk overrides: Add support for builder scope
- From: Jean Felder <jfelder src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pygobject/wip/jfelder/template-gtk4: 5/5] gtk overrides: Add support for builder scope
- Date: Tue, 28 Apr 2020 20:54:48 +0000 (UTC)
commit 0002a96b7cc35dab13f98f069185790c11c704e1
Author: Jean Felder <jfelder src gnome org>
Date: Mon Apr 27 22:37:29 2020 +0200
gtk overrides: Add support for builder scope
This is a follow of the previous commit which fixes template support
in GTK4 by adding GtkBuilerScope. It is extended to support a scope
object (it can be a python object or a dictionnary). This scope object
is used to define the scope the builder should operate in. A new
GtkBuilder constructor is introduced to be able to define this scope
object.
The different relevant tests are updated to support both GTK3 and
GTK4.
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]