tests: don't test for specific device labels
[pygobject.git] / gi / _gtktemplate.py
blobefaca33942fc45d3b6d9676d73d7b7ba75dc4792
1 # Copyright 2015 Dustin Spicuzza <dustin@virtualroadside.com>
2 # 2018 Nikita Churaev <lamefun.x0r@gmail.com>
3 # 2018 Christoph Reiter <reiter.christoph@gmail.com>
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
18 # USA
20 from gi.repository import GLib, GObject, Gio
23 def connect_func(builder, obj, signal_name, handler_name,
24 connect_object, flags, cls):
26 if handler_name not in cls.__gtktemplate_methods__:
27 return
29 method_name = cls.__gtktemplate_methods__[handler_name]
30 template_inst = builder.get_object(cls.__gtype_name__)
31 template_inst.__gtktemplate_handlers__.add(handler_name)
32 handler = getattr(template_inst, method_name)
34 after = int(flags & GObject.ConnectFlags.AFTER)
35 swapped = int(flags & GObject.ConnectFlags.SWAPPED)
36 if swapped:
37 raise RuntimeError(
38 "%r not supported" % GObject.ConnectFlags.SWAPPED)
40 if connect_object is not None:
41 if after:
42 func = obj.connect_object_after
43 else:
44 func = obj.connect_object
45 func(signal_name, handler, connect_object)
46 else:
47 if after:
48 func = obj.connect_after
49 else:
50 func = obj.connect
51 func(signal_name, handler)
54 def register_template(cls):
55 bound_methods = {}
56 bound_widgets = {}
58 for attr_name, obj in list(cls.__dict__.items()):
59 if isinstance(obj, CallThing):
60 setattr(cls, attr_name, obj._func)
61 handler_name = obj._name
62 if handler_name is None:
63 handler_name = attr_name
65 if handler_name in bound_methods:
66 old_attr_name = bound_methods[handler_name]
67 raise RuntimeError(
68 "Error while exposing handler %r as %r, "
69 "already available as %r" % (
70 handler_name, attr_name, old_attr_name))
71 else:
72 bound_methods[handler_name] = attr_name
73 elif isinstance(obj, Child):
74 widget_name = obj._name
75 if widget_name is None:
76 widget_name = attr_name
78 if widget_name in bound_widgets:
79 old_attr_name = bound_widgets[widget_name]
80 raise RuntimeError(
81 "Error while exposing child %r as %r, "
82 "already available as %r" % (
83 widget_name, attr_name, old_attr_name))
84 else:
85 bound_widgets[widget_name] = attr_name
86 cls.bind_template_child_full(widget_name, obj._internal, 0)
88 cls.__gtktemplate_methods__ = bound_methods
89 cls.__gtktemplate_widgets__ = bound_widgets
91 cls.set_connect_func(connect_func, cls)
93 base_init_template = cls.init_template
94 cls.__dontuse_ginstance_init__ = \
95 lambda s: init_template(s, cls, base_init_template)
96 # To make this file work with older PyGObject we expose our init code
97 # as init_template() but make it a noop when we call it ourselves first
98 cls.init_template = cls.__dontuse_ginstance_init__
101 def init_template(self, cls, base_init_template):
102 self.init_template = lambda s: None
104 if self.__class__ is not cls:
105 raise TypeError(
106 "Inheritance from classes with @Gtk.Template decorators "
107 "is not allowed at this time")
109 self.__gtktemplate_handlers__ = set()
111 base_init_template(self)
113 for widget_name, attr_name in self.__gtktemplate_widgets__.items():
114 self.__dict__[attr_name] = self.get_template_child(cls, widget_name)
116 for handler_name, attr_name in self.__gtktemplate_methods__.items():
117 if handler_name not in self.__gtktemplate_handlers__:
118 raise RuntimeError(
119 "Handler '%s' was declared with @Gtk.Template.Callback "
120 "but was not present in template" % handler_name)
123 class Child(object):
125 def __init__(self, name=None, **kwargs):
126 self._name = name
127 self._internal = kwargs.pop("internal", False)
128 if kwargs:
129 raise TypeError("Unhandled arguments: %r" % kwargs)
132 class CallThing(object):
134 def __init__(self, name, func):
135 self._name = name
136 self._func = func
139 class Callback(object):
141 def __init__(self, name=None):
142 self._name = name
144 def __call__(self, func):
145 return CallThing(self._name, func)
148 def validate_resource_path(path):
149 """Raises GLib.Error in case the resource doesn't exist"""
151 try:
152 Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE)
153 except GLib.Error:
154 # resources_get_info() doesn't handle overlays but we keep using it
155 # as a fast path.
156 # https://gitlab.gnome.org/GNOME/pygobject/issues/230
157 Gio.resources_lookup_data(path, Gio.ResourceLookupFlags.NONE)
160 class Template(object):
162 def __init__(self, **kwargs):
163 self.string = None
164 self.filename = None
165 self.resource_path = None
166 if "string" in kwargs:
167 self.string = kwargs.pop("string")
168 elif "filename" in kwargs:
169 self.filename = kwargs.pop("filename")
170 elif "resource_path" in kwargs:
171 self.resource_path = kwargs.pop("resource_path")
172 else:
173 raise TypeError(
174 "Requires one of the following arguments: "
175 "string, filename, resource_path")
177 if kwargs:
178 raise TypeError("Unhandled keyword arguments %r" % kwargs)
180 @classmethod
181 def from_file(cls, filename):
182 return cls(filename=filename)
184 @classmethod
185 def from_string(cls, string):
186 return cls(string=string)
188 @classmethod
189 def from_resource(cls, resource_path):
190 return cls(resource_path=resource_path)
192 Callback = Callback
194 Child = Child
196 def __call__(self, cls):
197 from gi.repository import Gtk
199 if not isinstance(cls, type) or not issubclass(cls, Gtk.Widget):
200 raise TypeError("Can only use @Gtk.Template on Widgets")
202 if "__gtype_name__" not in cls.__dict__:
203 raise TypeError(
204 "%r does not have a __gtype_name__. Set it to the name "
205 "of the class in your template" % cls.__name__)
207 if hasattr(cls, "__gtktemplate_methods__"):
208 raise TypeError("Cannot nest template classes")
210 if self.string is not None:
211 data = self.string
212 if not isinstance(data, bytes):
213 data = data.encode("utf-8")
214 bytes_ = GLib.Bytes.new(data)
215 cls.set_template(bytes_)
216 register_template(cls)
217 return cls
218 elif self.resource_path is not None:
219 validate_resource_path(self.resource_path)
220 cls.set_template_from_resource(self.resource_path)
221 register_template(cls)
222 return cls
223 else:
224 assert self.filename is not None
225 file_ = Gio.File.new_for_path(self.filename)
226 bytes_ = GLib.Bytes.new(file_.load_contents()[1])
227 cls.set_template(bytes_)
228 register_template(cls)
229 return cls
232 __all__ = ["Template"]