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
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
__:
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
)
38 "%r not supported" % GObject
.ConnectFlags
.SWAPPED
)
40 if connect_object
is not None:
42 func
= obj
.connect_object_after
44 func
= obj
.connect_object
45 func(signal_name
, handler
, connect_object
)
48 func
= obj
.connect_after
51 func(signal_name
, handler
)
54 def register_template(cls
):
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
]
68 "Error while exposing handler %r as %r, "
69 "already available as %r" % (
70 handler_name
, attr_name
, old_attr_name
))
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
]
81 "Error while exposing child %r as %r, "
82 "already available as %r" % (
83 widget_name
, attr_name
, old_attr_name
))
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
:
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
__:
119 "Handler '%s' was declared with @Gtk.Template.Callback "
120 "but was not present in template" % handler_name
)
125 def __init__(self
, name
=None, **kwargs
):
127 self
._internal
= kwargs
.pop("internal", False)
129 raise TypeError("Unhandled arguments: %r" % kwargs
)
132 class CallThing(object):
134 def __init__(self
, name
, func
):
139 class Callback(object):
141 def __init__(self
, name
=None):
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"""
152 Gio
.resources_get_info(path
, Gio
.ResourceLookupFlags
.NONE
)
154 # resources_get_info() doesn't handle overlays but we keep using it
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
):
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")
174 "Requires one of the following arguments: "
175 "string, filename, resource_path")
178 raise TypeError("Unhandled keyword arguments %r" % kwargs
)
181 def from_file(cls
, filename
):
182 return cls(filename
=filename
)
185 def from_string(cls
, string
):
186 return cls(string
=string
)
189 def from_resource(cls
, resource_path
):
190 return cls(resource_path
=resource_path
)
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
__:
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:
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
)
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
)
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
)
232 __all__
= ["Template"]