From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- share/extensions/inkex/gui/window.py | 201 +++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 share/extensions/inkex/gui/window.py (limited to 'share/extensions/inkex/gui/window.py') diff --git a/share/extensions/inkex/gui/window.py b/share/extensions/inkex/gui/window.py new file mode 100644 index 0000000..a5c1ef6 --- /dev/null +++ b/share/extensions/inkex/gui/window.py @@ -0,0 +1,201 @@ +# +# Copyright 2012-2022 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# +# pylint: disable=too-many-instance-attributes +""" +Wraps the gtk windows with something a little nicer. +""" +import logging + +from gi.repository import Gtk + +PROPS = { + "Box": ["expand", "fill", "padding", "pack-type"], + "Grid": ["top-attach", "left-attach", "height", "width"], + "Table": ["top-attach", "left-attach", "bottom-attach", "right-attach"], +} + + +def protect(cls, *methods): + """Simple check for protecting an inherrited class from having + certain methods over-ridden""" + if not isinstance(cls, type): + cls = type(cls) + for method in methods: + if method in cls.__dict__: # pragma: no cover + raise RuntimeError( + f"{cls.__name__} in {cls.__module__} has" f" protected def {method}()" + ) + + +class Window: + """ + This wraps gtk windows and allows for having parent windows + + name = 'name-of-the-window' + + Should the window be the first loaded and end gtk when closed: + + primary = True/False + """ + + primary = True + name = None + + def __init__(self, gapp): + self.gapp = gapp + self.dead = False + self.parent = None + self.args = () + ui_file = gapp.get_ui_file(self.name) + + # Setup the gtk app connection + self.w_tree = Gtk.Builder() + self.widget = self.w_tree.get_object + self.w_tree.set_translation_domain(gapp.app_name) + self.w_tree.add_from_file(ui_file) + + # Setup the gtk builder window + self.window = self.widget(self.name) + if not self.window: # pragma: no cover + raise KeyError(f"Missing window widget '{self.name}' from '{ui_file}'") + + # Give us a window id to track this window + self.wid = str(hash(self.window)) + + def extract(self): + """Extract this window's container for use in other apps""" + for child in self.window.get_children(): + self.window.remove(child) + return child + + def init(self, parent=None, **kwargs): + """Initialise the window within the GtkApp""" + if "replace" not in kwargs: + protect(self, "destroy", "exit", "load_window", "proto_window") + self.args = kwargs + # Set object defaults + self.parent = parent + + self.w_tree.connect_signals(self) + + # These are some generic convience signals + self.window.connect("destroy", self.exit) + + # If we have a parent window, then we expect not to quit + if self.parent: + self.window.set_transient_for(self.parent) + self.parent.set_sensitive(False) + + # We may have some more gtk widgets to setup + self.load_widgets(**self.args) + self.window.show() + + def load_window(self, name, *args, **kwargs): + """Load child window, automatically sets parent""" + kwargs["parent"] = self.window + return self.gapp.load_window(name, *args, **kwargs) + + def load_widgets(self): + """Child class should use this to create widgets""" + + def destroy(self, widget=None): # pylint: disable=unused-argument + """Destroy the window""" + logging.debug("Destroying Window '%s'", self.name) + self.window.destroy() + # We don't need to call self.exit(), handeled by window event. + + def pre_exit(self): + """Internal method for what to do when the window has died""" + + def exit(self, widget=None): + """Called when the window needs to exit.""" + # Is the signal called by the window or by something else? + if not widget or not isinstance(widget, Gtk.Window): + self.destroy() + # Clean up any required processes + self.pre_exit() + if self.parent: + # We assume the parent didn't load another gtk loop + self.parent.set_sensitive(True) + # Exit our entire app if this is the primary window + # Or just remove from parent window list, which may still exit. + if self.primary: + logging.debug("Exiting the application") + self.gapp.exit() + else: + logging.debug("Removing Window %s from parent", self.name) + self.gapp.remove_window(self) + # Now finish up what ever is left to do now the window is dead. + self.dead = True + self.post_exit() + return widget + + def post_exit(self): + """Called after we've killed the window""" + + def if_widget(self, name): + """ + Attempt to get the widget from gtk, but if not return a fake that won't + cause any trouble if we don't further check if it's real. + """ + return self.widget(name) or FakeWidget(name) + + def replace(self, old, new): + """Replace the old widget with the new widget""" + if isinstance(old, str): + old = self.widget(old) + if isinstance(new, str): + new = self.widget(new) + target = old.get_parent() + source = new.get_parent() + if target is not None: + if source is not None: + source.remove(new) + target.remove(old) + target.add(new) + + @staticmethod + def get_widget_name(obj): + """Return the widget's name in the builder file""" + return Gtk.Buildable.get_name(obj) + + +class ChildWindow(Window): + """ + Base class for child window objects, these child windows are typically + window objects in the same gtk builder file as their parents. If you just want + to make a window that interacts with a parent window, use the normal + Window class and call with the optional parent attribute. + """ + + primary = False + + +class FakeWidget: + """A fake widget class that can take calls""" + + def __init__(self, name): + self._name = name + + def __getattr__(self, name): + def _fake(*args, **kwargs): + logging.info("Calling fake method: %s:%s", args, kwargs) + + return _fake + + def __bool__(self): + return False -- cgit v1.2.3