Commit e0084e7e authored by Cole Robinson's avatar Cole Robinson Committed by Simon Feltman
Browse files

GTK overrides: Make connect_signals handle tuple



This is used for passing extra arguments to callbacks during
signal emission in the form of:
builder.connect_signals({'on_clicked': (on_clicked, arg1, arg2)})
Co-Authored-By: default avatarSimon Feltman <sfeltman@src.gnome.org>

https://bugzilla.gnome.org/show_bug.cgi?id=693994
parent 46656737
......@@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
# USA
import collections
import sys
from gi.repository import GObject
from ..overrides import override, strip_boolean_result
......@@ -357,32 +358,52 @@ __all__.append('MenuItem')
class Builder(Gtk.Builder):
@staticmethod
def _extract_handler_and_args(obj_or_map, handler_name):
handler = None
if isinstance(obj_or_map, collections.Mapping):
handler = obj_or_map.get(handler_name, None)
else:
handler = getattr(obj_or_map, handler_name, None)
def connect_signals(self, obj_or_map):
def _full_callback(builder, gobj, signal_name, handler_name, connect_obj, flags, obj_or_map):
handler = None
if isinstance(obj_or_map, dict):
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, collections.Sequence):
if len(handler) == 0:
raise TypeError("Handler %s tuple can not be empty" % handler)
args = handler[1:]
handler = handler[0]
if handler is None:
raise AttributeError('Handler %s not found' % handler_name)
elif not _callable(handler):
raise TypeError('Handler %s is not a method, function or tuple' % handler)
if not _callable(handler):
raise TypeError('Handler %s is not a method or function' % handler_name)
return handler, args
def connect_signals(self, obj_or_map):
"""Connect signals specified by this builder to a name, handler mapping.
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:
builder.connect_signals({'on_clicked': (on_clicked, arg1, arg2)})
"""
def _full_callback(builder, gobj, signal_name, handler_name, connect_obj, flags, obj_or_map):
handler, args = self._extract_handler_and_args(obj_or_map, handler_name)
after = flags & GObject.ConnectFlags.AFTER
if connect_obj is not None:
if after:
gobj.connect_object_after(signal_name, handler, connect_obj)
gobj.connect_object_after(signal_name, handler, connect_obj, *args)
else:
gobj.connect_object(signal_name, handler, connect_obj)
gobj.connect_object(signal_name, handler, connect_obj, *args)
else:
if after:
gobj.connect_after(signal_name, handler)
gobj.connect_after(signal_name, handler, *args)
else:
gobj.connect(signal_name, handler)
gobj.connect(signal_name, handler, *args)
self.connect_signals_full(_full_callback, obj_or_map)
......
......@@ -156,73 +156,6 @@ class TestGtk(unittest.TestCase):
mi = ui.get_widget("/menubær1")
self.assertEqual(type(mi), Gtk.MenuBar)
def test_builder(self):
self.assertEqual(Gtk.Builder, gi.overrides.Gtk.Builder)
class SignalTest(GObject.GObject):
__gtype_name__ = "GIOverrideSignalTest"
__gsignals__ = {
"test-signal": (GObject.SignalFlags.RUN_FIRST,
None,
[]),
}
class SignalCheck:
def __init__(self):
self.sentinel = 0
self.after_sentinel = 0
def on_signal_1(self, *args):
self.sentinel += 1
self.after_sentinel += 1
def on_signal_3(self, *args):
self.sentinel += 3
def on_signal_after(self, *args):
if self.after_sentinel == 1:
self.after_sentinel += 1
signal_checker = SignalCheck()
builder = Gtk.Builder()
# add object1 to the builder
builder.add_from_string("""
<interface>
<object class="GIOverrideSignalTest" id="object1">
<signal name="test-signal" after="yes" handler="on_signal_after" />
<signal name="test-signal" handler="on_signal_1" />
</object>
</interface>
""")
# only add object3 to the builder
builder.add_objects_from_string("""
<interface>
<object class="GIOverrideSignalTest" id="object2">
<signal name="test-signal" handler="on_signal_2" />
</object>
<object class="GIOverrideSignalTest" id="object3">
<signal name="test-signal" handler="on_signal_3" />
</object>
<object class="GIOverrideSignalTest" id="object4">
<signal name="test-signal" handler="on_signal_4" />
</object>
</interface>
""", ['object3'])
# hook up signals
builder.connect_signals(signal_checker)
# call their notify signals and check sentinel
objects = builder.get_objects()
self.assertEqual(len(objects), 2)
for obj in objects:
obj.emit('test-signal')
self.assertEqual(signal_checker.sentinel, 4)
self.assertEqual(signal_checker.after_sentinel, 2)
def test_window(self):
# standard Window
w = Gtk.Window()
......@@ -656,6 +589,135 @@ class TestGtk(unittest.TestCase):
Gtk.main()
@unittest.skipUnless(Gtk, 'Gtk not available')
class TestBuilder(unittest.TestCase):
class SignalTest(GObject.GObject):
__gtype_name__ = "GIOverrideSignalTest"
__gsignals__ = {
"test-signal": (GObject.SignalFlags.RUN_FIRST,
None,
[]),
}
def test_extract_handler_and_args_object(self):
class Obj():
pass
obj = Obj()
obj.foo = lambda: None
handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo')
self.assertEqual(handler, obj.foo)
self.assertEqual(len(args), 0)
def test_extract_handler_and_args_dict(self):
obj = {'foo': lambda: None}
handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo')
self.assertEqual(handler, obj['foo'])
self.assertEqual(len(args), 0)
def test_extract_handler_and_args_with_seq(self):
obj = {'foo': (lambda: None, 1, 2)}
handler, args = Gtk.Builder._extract_handler_and_args(obj, 'foo')
self.assertEqual(handler, obj['foo'][0])
self.assertSequenceEqual(args, [1, 2])
def test_extract_handler_and_args_no_handler_error(self):
obj = dict(foo=lambda: None)
self.assertRaises(AttributeError,
Gtk.Builder._extract_handler_and_args,
obj, 'not_a_handler')
def test_builder_with_handler_and_args(self):
builder = Gtk.Builder()
builder.add_from_string("""
<interface>
<object class="GIOverrideSignalTest" id="object_sig_test">
<signal name="test-signal" handler="on_signal1" />
<signal name="test-signal" handler="on_signal2" after="yes" />
</object>
</interface>
""")
args_collector = []
def on_signal(*args):
args_collector.append(args)
builder.connect_signals({'on_signal1': (on_signal, 1, 2),
'on_signal2': on_signal})
objects = builder.get_objects()
self.assertEqual(len(objects), 1)
obj, = objects
obj.emit('test-signal')
self.assertEqual(len(args_collector), 2)
self.assertSequenceEqual(args_collector[0], (obj, 1, 2))
self.assertSequenceEqual(args_collector[1], (obj, ))
def test_builder(self):
self.assertEqual(Gtk.Builder, gi.overrides.Gtk.Builder)
class SignalCheck:
def __init__(self):
self.sentinel = 0
self.after_sentinel = 0
def on_signal_1(self, *args):
self.sentinel += 1
self.after_sentinel += 1
def on_signal_3(self, *args):
self.sentinel += 3
def on_signal_after(self, *args):
if self.after_sentinel == 1:
self.after_sentinel += 1
signal_checker = SignalCheck()
builder = Gtk.Builder()
# add object1 to the builder
builder.add_from_string("""
<interface>
<object class="GIOverrideSignalTest" id="object1">
<signal name="test-signal" after="yes" handler="on_signal_after" />
<signal name="test-signal" handler="on_signal_1" />
</object>
</interface>
""")
# only add object3 to the builder
builder.add_objects_from_string("""
<interface>
<object class="GIOverrideSignalTest" id="object2">
<signal name="test-signal" handler="on_signal_2" />
</object>
<object class="GIOverrideSignalTest" id="object3">
<signal name="test-signal" handler="on_signal_3" />
</object>
<object class="GIOverrideSignalTest" id="object4">
<signal name="test-signal" handler="on_signal_4" />
</object>
</interface>
""", ['object3'])
# hook up signals
builder.connect_signals(signal_checker)
# call their notify signals and check sentinel
objects = builder.get_objects()
self.assertEqual(len(objects), 2)
for obj in objects:
obj.emit('test-signal')
self.assertEqual(signal_checker.sentinel, 4)
self.assertEqual(signal_checker.after_sentinel, 2)
@unittest.skipUnless(Gtk, 'Gtk not available')
class TestTreeModel(unittest.TestCase):
def test_tree_model_sort(self):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment