File: gui_application.py

package info (click to toggle)
python-pyface 6.1.2-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 11,756 kB
  • sloc: python: 39,728; makefile: 79
file content (297 lines) | stat: -rw-r--r-- 10,167 bytes parent folder | download
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# Copyright (c) 2014-2017 by Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license.  The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
# Thanks for using Enthought open source!
"""
This module defines a :py:class:`GUIApplication` subclass of
:py:class:`pyface.application.Application`.  This adds cross-platform GUI
application support to the base class via :py:class:`pyface.application.GUI`.

At a minimum this class expects to be provided with a factory that returns
:py:class:`pyface.i_window.IWindow` instances.  For pure Pyface applications
this is most likely to be a subclass of
:py:class:`pyface.application_window.ApplicationWindow`.

"""

from __future__ import (
    absolute_import, division, print_function, unicode_literals
)
import logging

from traits.api import (
    Bool, Callable, Instance, List, ReadOnly, Tuple, Undefined, Vetoable,
    on_trait_change
)

from .application import Application
from .i_dialog import IDialog
from .i_splash_screen import ISplashScreen
from .i_window import IWindow
from .ui_traits import Image

logger = logging.getLogger(__name__)


def default_window_factory(application, **kwargs):
    """ The default window factory returns an application window.

    This is almost never the right thing, but allows users to get off the
    ground with the base class.
    """
    from pyface.application_window import ApplicationWindow
    return ApplicationWindow(**kwargs)


class GUIApplication(Application):
    """ A basic Pyface GUI application. """

    # 'GUIApplication' traits -------------------------------------------------

    # Branding ---------------------------------------------------------------

    #: The splash screen for the application. No splash screen by default
    splash_screen = Instance(ISplashScreen)

    #: The about dialog for the application.
    about_dialog = Instance(IDialog)

    #: Icon for the application (used in window titlebars)
    icon = Image

    #: Logo of the application (used in splash screens and about dialogs)
    logo = Image

    # Window management ------------------------------------------------------

    #: The window factory to use when creating a window for the application.
    window_factory = Callable(default_window_factory)

    #: Default window size
    window_size = Tuple((800, 600))

    #: Currently active Window if any
    active_window = Instance(IWindow)

    #: List of all open windows in the application
    windows = List(Instance(IWindow))

    #: The Pyface GUI instance for the application
    gui = ReadOnly

    # Protected interface ----------------------------------------------------

    #: Flag if the exiting of the application was explicitely requested by user
    # An 'explicit' exit is when the 'exit' method is called.
    # An 'implicit' exit is when the user closes the last open window.
    _explicit_exit = Bool(False)

    # -------------------------------------------------------------------------
    # 'GUIApplication' interface
    # -------------------------------------------------------------------------

    # Window lifecycle methods -----------------------------------------------

    def create_window(self, **kwargs):
        """ Create a new application window.

        By default uses the :py:attr:`window_factory` to do this.  Subclasses
        can override if they want to do something different or additional.

        Parameters
        ----------
        **kwargs : dict
            Additional keyword arguments to pass to the window factory.

        Returns
        -------
        window : IWindow instance or None
            The new IWindow instance.
        """
        window = self.window_factory(application=self, **kwargs)

        if window.size == (-1, -1):
            window.size = self.window_size
        if not window.title:
            window.title = self.name
        if self.icon:
            window.icon = self.icon

        return window

    def add_window(self, window):
        """ Add a new window to the windows we are tracking. """

        # Keep a handle on all windows created so that non-active windows don't
        # get garbage collected
        self.windows.append(window)

        # Something might try to veto the opening of the window.
        opened = window.open()
        if opened:
            window.activate()

    # Action handlers --------------------------------------------------------

    def do_about(self):
        """ Display the about dialog, if it exists. """
        if self.about_dialog is not None:
            self.about_dialog.open()

    # -------------------------------------------------------------------------
    # 'Application' interface
    # -------------------------------------------------------------------------

    def start(self):
        """ Start the application, setting up things that are required

        Subclasses should open at least one ApplicationWindow or subclass in
        their start method, and should call the superclass start() method
        before doing any work themselves.
        """
        from pyface.gui import GUI

        ok = super(GUIApplication, self).start()
        if ok:
            # create the GUI so that the splash screen comes up first thing
            if self.gui is Undefined:
                self.gui = GUI(splash_screen=self.splash_screen)

            # create the initial windows to show
            self._create_windows()

        return ok

    # -------------------------------------------------------------------------
    # 'GUIApplication' Private interface
    # -------------------------------------------------------------------------

    def _create_windows(self):
        """ Create the initial windows to display.

        By default calls :py:meth:`create_window` once. Subclasses can
        override this method.
        """
        window = self.create_window()
        self.add_window(window)

    # -------------------------------------------------------------------------
    # 'Application' private interface
    # -------------------------------------------------------------------------

    def _run(self):
        """ Actual implementation of running the application: starting the GUI
        event loop.
        """
        # Fire a notification that the app is running.  This is guaranteed to
        # happen after all initialization has occurred and the event loop has
        # started.  A listener for this event is a good place to do things
        # where you want the event loop running.
        self.gui.invoke_later(
            self._fire_application_event, 'application_initialized'
        )

        # start the GUI - script blocks here
        self.gui.start_event_loop()
        return True

    # Destruction methods -----------------------------------------------------

    def _can_exit(self):
        """ Check with each window to see if it can be closed

        The fires closing events for each window, and returns False if any
        listener vetos.
        """
        if not super(GUIApplication, self)._can_exit():
            return False

        for window in reversed(self.windows):
            window.closing = event = Vetoable()
            if event.veto:
                return False
        else:
            return True

    def _prepare_exit(self):
        """ Close each window """
        # ensure copy of list, as we modify original list while closing
        for window in list(reversed(self.windows)):
            window.destroy()
            window.closed = window

    def _exit(self):
        """ Shut down the event loop """
        self.gui.stop_event_loop()

    # Trait default handlers ------------------------------------------------

    def _window_factory_default(self):
        """ Default to ApplicationWindow

        This is almost never the right thing, but allows users to get off the
        ground with the base class.
        """
        from pyface.application_window import ApplicationWindow
        return lambda application, **kwargs: ApplicationWindow(**kwargs)

    def _splash_screen_default(self):
        """ Default SplashScreen """
        from pyface.splash_screen import SplashScreen

        dialog = SplashScreen()
        if self.logo:
            dialog.image = self.logo
        return dialog

    def _about_dialog_default(self):
        """ Default AboutDialog """
        from sys import version_info
        if (version_info.major, version_info.minor) >= (3, 2):
            from html import escape
        else:
            from cgi import escape
        from pyface.about_dialog import AboutDialog

        additions = [
            u"<h1>{}</h1>".format(escape(self.name)),
            u"Copyright &copy; 2018 {}, all rights reserved".format(
                escape(self.company),
            ),
            u"",
        ]
        additions += [escape(line) for line in self.description.split('\n\n')]

        dialog = AboutDialog(
            title=u"About {}".format(self.name),
            additions=additions,
        )
        if self.logo:
            dialog.image = self.logo
        return dialog

    # Trait listeners --------------------------------------------------------

    @on_trait_change('windows:activated')
    def _on_activate_window(self, window, trait, old, new):
        """ Listener that tracks currently active window.
        """
        if window in self.windows:
            self.active_window = window

    @on_trait_change('windows:deactivated')
    def _on_deactivate_window(self, window, trait, old, new):
        """ Listener that tracks currently active window.
        """
        self.active_window = None

    @on_trait_change('windows:closed')
    def _on_window_closed(self, window, trait, old, new):
        """ Listener that ensures window handles are released when closed.
        """
        if window in self.windows:
            self.windows.remove(window)