summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/gui/window.py
blob: a5c1ef6e75f69f98a85e22ae0b8236afd9574ede (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#
# Copyright 2012-2022 Martin Owens <doctormo@geek-2.com>
#
# 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 <http://www.gnu.org/licenses/>
#
# 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