diff options
Diffstat (limited to 'gi/_gtktemplate.py')
-rw-r--r-- | gi/_gtktemplate.py | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/gi/_gtktemplate.py b/gi/_gtktemplate.py new file mode 100644 index 00000000..37707e8c --- /dev/null +++ b/gi/_gtktemplate.py @@ -0,0 +1,218 @@ +# Copyright 2015 Dustin Spicuzza <dustin@virtualroadside.com> +# 2018 Nikita Churaev <lamefun.x0r@gmail.com> +# 2018 Christoph Reiter <reiter.christoph@gmail.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 +# USA + +from gi.repository import GLib, GObject, Gio + + +def connect_func(builder, obj, signal_name, handler_name, + connect_object, flags, cls): + + if handler_name not in cls.__gtktemplate_methods__: + return + + method_name = cls.__gtktemplate_methods__[handler_name] + template_inst = builder.get_object(cls.__gtype_name__) + template_inst.__gtktemplate_handlers__.add(handler_name) + handler = getattr(template_inst, method_name) + + after = int(flags & GObject.ConnectFlags.AFTER) + swapped = int(flags & GObject.ConnectFlags.SWAPPED) + if swapped: + raise RuntimeError( + "%r not supported" % GObject.ConnectFlags.SWAPPED) + + if connect_object is not None: + if after: + func = obj.connect_object_after + else: + func = obj.connect_object + func(signal_name, handler, connect_object) + else: + if after: + func = obj.connect_after + else: + func = obj.connect + func(signal_name, handler) + + +def register_template(cls): + bound_methods = {} + bound_widgets = {} + + for attr_name, obj in list(cls.__dict__.items()): + if isinstance(obj, CallThing): + setattr(cls, attr_name, obj._func) + handler_name = obj._name + if handler_name is None: + handler_name = attr_name + + if handler_name in bound_methods: + old_attr_name = bound_methods[handler_name] + raise RuntimeError( + "Error while exposing handler %r as %r, " + "already available as %r" % ( + handler_name, attr_name, old_attr_name)) + else: + bound_methods[handler_name] = attr_name + elif isinstance(obj, Child): + widget_name = obj._name + if widget_name is None: + widget_name = attr_name + + if widget_name in bound_widgets: + old_attr_name = bound_widgets[widget_name] + raise RuntimeError( + "Error while exposing child %r as %r, " + "already available as %r" % ( + widget_name, attr_name, old_attr_name)) + else: + bound_widgets[widget_name] = attr_name + cls.bind_template_child_full(widget_name, False, 0) + + cls.__gtktemplate_methods__ = bound_methods + cls.__gtktemplate_widgets__ = bound_widgets + + cls.set_connect_func(connect_func, cls) + + base_init_template = cls.init_template + cls.__dontuse_ginstance_init__ = \ + lambda s: init_template(s, cls, base_init_template) + # To make this file work with older PyGObject we expose our init code + # as init_template() but make it a noop when we call it ourselves first + cls.init_template = cls.__dontuse_ginstance_init__ + + +def init_template(self, cls, base_init_template): + cls.init_template = lambda s: None + + if self.__class__ is not cls: + raise TypeError( + "Inheritance from classes with @Gtk.Template decorators " + "is not allowed at this time") + + self.__gtktemplate_handlers__ = set() + + base_init_template(self) + + for widget_name, attr_name in self.__gtktemplate_widgets__.items(): + self.__dict__[attr_name] = self.get_template_child(cls, widget_name) + + for handler_name, attr_name in self.__gtktemplate_methods__.items(): + if handler_name not in self.__gtktemplate_handlers__: + raise RuntimeError( + "Handler '%s' was declared with @Gtk.Template.Callback " + "but was not present in template" % handler_name) + + +class Child(object): + + def __init__(self, name=None): + self._name = name + + +class CallThing(object): + + def __init__(self, name, func): + self._name = name + self._func = func + + +class Callback(object): + + def __init__(self, name=None): + self._name = name + + def __call__(self, func): + return CallThing(self._name, func) + + +class Template(object): + + def __init__(self, **kwargs): + self.string = None + self.filename = None + self.resource_path = None + if "string" in kwargs: + self.string = kwargs.pop("string") + elif "filename" in kwargs: + self.filename = kwargs.pop("filename") + elif "resource_path" in kwargs: + self.resource_path = kwargs.pop("resource_path") + else: + raise TypeError( + "Requires one of the following arguments: " + "string, filename, resource_path") + + if kwargs: + raise TypeError("Unhandled keyword arguments %r" % kwargs) + + @classmethod + def from_file(cls, filename): + return cls(filename=filename) + + @classmethod + def from_string(cls, string): + return cls(string=string) + + @classmethod + def from_resource(cls, resource_path): + return cls(resource_path=resource_path) + + Callback = Callback + + Child = Child + + def __call__(self, cls): + from gi.repository import Gtk + + if not isinstance(cls, type) or not issubclass(cls, Gtk.Widget): + raise TypeError("Can only use @Gtk.Template on Widgets") + + if "__gtype_name__" not in cls.__dict__: + raise TypeError( + "%r does not have a __gtype_name__. Set it to the name " + "of the class in your template" % cls.__name__) + + if hasattr(cls, "__gtktemplate_methods__"): + raise TypeError("Cannot nest template classes") + + if self.string is not None: + data = self.string + if not isinstance(data, bytes): + data = data.encode("utf-8") + bytes_ = GLib.Bytes.new(data) + cls.set_template(bytes_) + register_template(cls) + return cls + elif self.resource_path is not None: + Gio.resources_get_info( + self.resource_path, Gio.ResourceLookupFlags.NONE) + cls.set_template_from_resource(self.resource_path) + register_template(cls) + return cls + else: + assert self.filename is not None + file_ = Gio.File.new_for_path(self.filename) + bytes_ = GLib.Bytes.new(file_.load_contents()[1]) + cls.set_template(bytes_) + register_template(cls) + return cls + + +__all__ = ["Template"] |