~ubuntu-branches/ubuntu/utopic/python-traitsui/utopic

« back to all changes in this revision

Viewing changes to traitsui/wx/ui_live.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-09 13:57:39 UTC
  • Revision ID: james.westby@ubuntu.com-20110709135739-x5u20q86huissmn1
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#------------------------------------------------------------------------------
 
2
#
 
3
#  Copyright (c) 2005, Enthought, Inc.
 
4
#  All rights reserved.
 
5
#
 
6
#  This software is provided without warranty under the terms of the BSD
 
7
#  license included in enthought/LICENSE.txt and may be redistributed only
 
8
#  under the conditions described in the aforementioned license.  The license
 
9
#  is also available online at http://www.enthought.com/licenses/BSD.txt
 
10
#
 
11
#  Thanks for using Enthought open source!
 
12
#
 
13
#  Author: David C. Morrill
 
14
#  Date:   11/01/2004
 
15
#
 
16
#------------------------------------------------------------------------------
 
17
 
 
18
""" Creates a wxPython user interface for a specified UI object, where the UI
 
19
    is "live", meaning that it immediately updates its underlying object(s).
 
20
"""
 
21
 
 
22
#-------------------------------------------------------------------------------
 
23
#  Imports:
 
24
#-------------------------------------------------------------------------------
 
25
 
 
26
import wx
 
27
 
 
28
from helper \
 
29
    import restore_window, save_window, TraitsUIScrolledPanel
 
30
 
 
31
from ui_base \
 
32
    import BaseDialog
 
33
 
 
34
from ui_panel \
 
35
    import panel, show_help
 
36
 
 
37
from constants \
 
38
    import DefaultTitle, WindowColor, screen_dy, \
 
39
                                     scrollbar_dx
 
40
from traitsui.undo \
 
41
    import UndoHistory
 
42
 
 
43
from traitsui.menu \
 
44
    import UndoButton, RevertButton, OKButton, CancelButton, HelpButton
 
45
 
 
46
#-------------------------------------------------------------------------------
 
47
#  Constants:
 
48
#-------------------------------------------------------------------------------
 
49
 
 
50
# Types of supported windows:
 
51
NONMODAL = 0
 
52
MODAL    = 1
 
53
POPUP    = 2
 
54
POPOVER  = 3
 
55
INFO     = 4
 
56
 
 
57
# Types of 'popup' dialogs:
 
58
Popups = set( ( POPUP, POPOVER, INFO ) )
 
59
 
 
60
#-------------------------------------------------------------------------------
 
61
#  Creates a 'live update' wxPython user interface for a specified UI object:
 
62
#-------------------------------------------------------------------------------
 
63
 
 
64
def ui_live ( ui, parent ):
 
65
    """ Creates a live, non-modal wxPython user interface for a specified UI
 
66
    object.
 
67
    """
 
68
    ui_dialog( ui, parent, NONMODAL )
 
69
 
 
70
def ui_livemodal ( ui, parent ):
 
71
    """ Creates a live, modal wxPython user interface for a specified UI object.
 
72
    """
 
73
    ui_dialog( ui, parent, MODAL )
 
74
 
 
75
def ui_popup ( ui, parent ):
 
76
    """ Creates a live, temporary popup wxPython user interface for a specified
 
77
        UI object.
 
78
    """
 
79
    ui_dialog( ui, parent, POPUP )
 
80
 
 
81
def ui_popover ( ui, parent ):
 
82
    """ Creates a live, temporary popup wxPython user interface for a specified
 
83
        UI object.
 
84
    """
 
85
    ui_dialog( ui, parent, POPOVER )
 
86
 
 
87
def ui_info ( ui, parent ):
 
88
    """ Creates a live, temporary popup wxPython user interface for a specified
 
89
        UI object.
 
90
    """
 
91
    ui_dialog( ui, parent, INFO )
 
92
 
 
93
def ui_dialog ( ui, parent, style ):
 
94
    """ Creates a live wxPython user interface for a specified UI object.
 
95
    """
 
96
    if ui.owner is None:
 
97
        ui.owner = LiveWindow()
 
98
 
 
99
    ui.owner.init( ui, parent, style )
 
100
    ui.control = ui.owner.control
 
101
    ui.control._parent = parent
 
102
 
 
103
    try:
 
104
        ui.prepare_ui()
 
105
    except:
 
106
        ui.control.Destroy()
 
107
        ui.control.ui = None
 
108
        ui.control    = None
 
109
        ui.owner      = None
 
110
        ui.result     = False
 
111
        raise
 
112
 
 
113
    ui.handler.position( ui.info )
 
114
    restore_window( ui, is_popup = (style in Popups) )
 
115
 
 
116
    ui.control.Layout()
 
117
    # Check if the control is already being displayed modally. This would be
 
118
    # the case if after the window was displayed, some event caused the ui to
 
119
    # get rebuilt (typically when the user fires the 'updated' event on the ui
 
120
    # ). In this case, calling ShowModal again leads to the parent window
 
121
    # hanging even after the control has been closed by clicking OK or Cancel
 
122
    # (maybe the modal mode isn't ending?)
 
123
    if style == MODAL and not ui.control.IsModal():
 
124
        ui.control.ShowModal()
 
125
    else:
 
126
        ui.control.Show()
 
127
 
 
128
#-------------------------------------------------------------------------------
 
129
#  'LiveWindow' class:
 
130
#-------------------------------------------------------------------------------
 
131
 
 
132
class LiveWindow ( BaseDialog ):
 
133
    """ User interface window that immediately updates its underlying object(s).
 
134
    """
 
135
 
 
136
    #---------------------------------------------------------------------------
 
137
    #  Initializes the object:
 
138
    #---------------------------------------------------------------------------
 
139
 
 
140
    def init ( self, ui, parent, style ):
 
141
        self.is_modal = (style == MODAL)
 
142
        window_style  = 0
 
143
        view          = ui.view
 
144
        if view.resizable:
 
145
            window_style |= wx.RESIZE_BORDER
 
146
 
 
147
        title = view.title
 
148
        if title == '':
 
149
            title = DefaultTitle
 
150
 
 
151
        history = ui.history
 
152
        window  = ui.control
 
153
        if window is not None:
 
154
            if history is not None:
 
155
                history.on_trait_change( self._on_undoable, 'undoable',
 
156
                                         remove = True )
 
157
                history.on_trait_change( self._on_redoable, 'redoable',
 
158
                                         remove = True )
 
159
                history.on_trait_change( self._on_revertable, 'undoable',
 
160
                                         remove = True )
 
161
            window.SetSizer( None )
 
162
            ui.reset()
 
163
        else:
 
164
            self.ui = ui
 
165
            if style == MODAL:
 
166
                if view.resizable:
 
167
                    window_style |= (wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX)
 
168
                window = wx.Dialog( parent, -1, title,
 
169
                                style = window_style | wx.DEFAULT_DIALOG_STYLE )
 
170
            elif style == NONMODAL:
 
171
                if parent is not None:
 
172
                    window_style |= (wx.FRAME_FLOAT_ON_PARENT |
 
173
                                     wx.FRAME_NO_TASKBAR)
 
174
                window = wx.Frame( parent, -1, title, style = window_style |
 
175
                               (wx.DEFAULT_FRAME_STYLE & (~wx.RESIZE_BORDER)) )
 
176
            else:
 
177
                if window_style == 0:
 
178
                    window_style = wx.SIMPLE_BORDER
 
179
                if parent is not None:
 
180
                    window_style |= (wx.FRAME_FLOAT_ON_PARENT |
 
181
                                     wx.FRAME_NO_TASKBAR)
 
182
 
 
183
                window = wx.Frame( parent, -1, '', style = window_style )
 
184
                window._kind  = ui.view.kind
 
185
                self._monitor = MouseMonitor( ui )
 
186
 
 
187
            # Set the correct default window background color:
 
188
            window.SetBackgroundColour( WindowColor )
 
189
 
 
190
            self.control = window
 
191
            wx.EVT_CLOSE( window, self._on_close_page )
 
192
            wx.EVT_CHAR(  window, self._on_key )
 
193
 
 
194
        self.set_icon( view.icon )
 
195
        buttons     = [ self.coerce_button( button )
 
196
                        for button in view.buttons ]
 
197
        nbuttons    = len( buttons )
 
198
        no_buttons  = ((nbuttons == 1) and self.is_button( buttons[0], '' ))
 
199
        has_buttons = ((not no_buttons) and ((nbuttons > 0) or view.undo or
 
200
                                         view.revert or view.ok or view.cancel))
 
201
        if has_buttons or (view.menubar is not None):
 
202
            if history is None:
 
203
                history = UndoHistory()
 
204
        else:
 
205
            history = None
 
206
        ui.history = history
 
207
 
 
208
        # Create the actual trait sheet panel and imbed it in a scrollable
 
209
        # window (if requested):
 
210
        sw_sizer = wx.BoxSizer( wx.VERTICAL )
 
211
        if ui.scrollable:
 
212
            sizer       = wx.BoxSizer( wx.VERTICAL )
 
213
            sw          = TraitsUIScrolledPanel( window )
 
214
            trait_sheet = panel( ui, sw )
 
215
            sizer.Add( trait_sheet, 1, wx.EXPAND )
 
216
            tsdx, tsdy = trait_sheet.GetSize()
 
217
            sw.SetScrollRate( 16, 16 )
 
218
            max_dy = (2 * screen_dy) / 3
 
219
            sw.SetSizer( sizer )
 
220
            sw.SetSize( wx.Size( tsdx + ((tsdy > max_dy) * scrollbar_dx),
 
221
                                 min( tsdy, max_dy ) ) )
 
222
        else:
 
223
            sw = panel( ui, window )
 
224
 
 
225
        sw_sizer.Add( sw, 1, wx.EXPAND )
 
226
        sw_sizer.SetMinSize(sw.GetSize())
 
227
 
 
228
        # Check to see if we need to add any of the special function buttons:
 
229
        if (not no_buttons) and (has_buttons or view.help):
 
230
            sw_sizer.Add( wx.StaticLine( window, -1 ), 0, wx.EXPAND )
 
231
            b_sizer = wx.BoxSizer( wx.HORIZONTAL )
 
232
 
 
233
            # Convert all button flags to actual button actions if no buttons
 
234
            # were specified in the 'buttons' trait:
 
235
            if nbuttons == 0:
 
236
                if view.undo:
 
237
                    self.check_button( buttons, UndoButton )
 
238
 
 
239
                if view.revert:
 
240
                    self.check_button( buttons, RevertButton )
 
241
 
 
242
                if view.ok:
 
243
                    self.check_button( buttons, OKButton )
 
244
 
 
245
                if view.cancel:
 
246
                    self.check_button( buttons, CancelButton )
 
247
 
 
248
                if view.help:
 
249
                    self.check_button( buttons, HelpButton )
 
250
 
 
251
            # Create a button for each button action:
 
252
            for raw_button, button in zip( view.buttons, buttons ):
 
253
                button = self.coerce_button( button )
 
254
                default = raw_button == view.default_button
 
255
                
 
256
                if self.is_button( button, 'Undo' ):
 
257
                    self.undo = self.add_button( button, b_sizer,
 
258
                            self._on_undo, False, default = default )
 
259
                    self.redo = self.add_button( button, b_sizer,
 
260
                                                 self._on_redo, False, 'Redo' )
 
261
                    history.on_trait_change( self._on_undoable, 'undoable',
 
262
                                             dispatch = 'ui' )
 
263
                    history.on_trait_change( self._on_redoable, 'redoable',
 
264
                                             dispatch = 'ui' )
 
265
                    if history.can_undo:
 
266
                        self._on_undoable( True )
 
267
 
 
268
                    if history.can_redo:
 
269
                        self._on_redoable( True )
 
270
 
 
271
                elif self.is_button( button, 'Revert' ):
 
272
                    self.revert = self.add_button( button, b_sizer,
 
273
                            self._on_revert, False, default = default )
 
274
                    history.on_trait_change( self._on_revertable, 'undoable',
 
275
                                             dispatch = 'ui' )
 
276
                    if history.can_undo:
 
277
                        self._on_revertable( True )
 
278
 
 
279
                elif self.is_button( button, 'OK' ):
 
280
                    self.ok = self.add_button( button, b_sizer, self._on_ok,
 
281
                                               default = default)
 
282
                    ui.on_trait_change( self._on_error, 'errors',
 
283
                                        dispatch = 'ui' )
 
284
 
 
285
                elif self.is_button( button, 'Cancel' ):
 
286
                    self.add_button( button, b_sizer, self._on_cancel,
 
287
                                     default = default )
 
288
 
 
289
                elif self.is_button( button, 'Help' ):
 
290
                    self.add_button( button, b_sizer, self._on_help,
 
291
                                     default = default )
 
292
 
 
293
                elif not self.is_button( button, '' ):
 
294
                    self.add_button( button, b_sizer, default = default )
 
295
 
 
296
            sw_sizer.Add( b_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 5 )
 
297
 
 
298
        # Add the menu bar, tool bar and status bar (if any):
 
299
        self.add_menubar()
 
300
        self.add_toolbar()
 
301
        self.add_statusbar()
 
302
 
 
303
        # Lay all of the dialog contents out:
 
304
        window.SetSizer( sw_sizer )
 
305
        window.Fit()
 
306
 
 
307
    #---------------------------------------------------------------------------
 
308
    #  Closes the dialog window:
 
309
    #---------------------------------------------------------------------------
 
310
 
 
311
    def close ( self, rc = wx.ID_OK ):
 
312
        """ Closes the dialog window.
 
313
        """
 
314
        ui = self.ui
 
315
        ui.result = (rc == wx.ID_OK)
 
316
        save_window( ui )
 
317
        if self.is_modal:
 
318
            self.control.EndModal( rc )
 
319
 
 
320
        ui.finish()
 
321
        self.ui = self.undo = self.redo = self.revert = self.control = None
 
322
 
 
323
    #---------------------------------------------------------------------------
 
324
    #  Handles the user clicking the window/dialog 'close' button/icon:
 
325
    #---------------------------------------------------------------------------
 
326
 
 
327
    def _on_close_page ( self, event ):
 
328
        """ Handles the user clicking the window/dialog "close" button/icon.
 
329
        """
 
330
        if self.ui.view.close_result == False:
 
331
            self._on_cancel( event )
 
332
        else:
 
333
            self._on_ok( event )
 
334
 
 
335
    #---------------------------------------------------------------------------
 
336
    #  Handles the user giving focus to another window for a 'popup' view:
 
337
    #---------------------------------------------------------------------------
 
338
 
 
339
    def _on_close_popup ( self, event ):
 
340
        """ Handles the user giving focus to another window for a 'popup' view.
 
341
        """
 
342
        if not event.GetActive():
 
343
            self.close_popup()
 
344
 
 
345
    def close_popup ( self ):
 
346
        # Close the window if it has not already been closed:
 
347
        if self.ui.info is not None and self.ui.info.ui is not None:
 
348
            if self._on_ok():
 
349
                self._monitor.Stop()
 
350
 
 
351
    #---------------------------------------------------------------------------
 
352
    #  Handles the user clicking the 'OK' button:
 
353
    #---------------------------------------------------------------------------
 
354
 
 
355
    def _on_ok ( self, event = None ):
 
356
        """ Handles the user clicking the **OK** button.
 
357
        """
 
358
        if self.ui.handler.close( self.ui.info, True ):
 
359
            wx.EVT_ACTIVATE( self.control, None )
 
360
            self.close( wx.ID_OK )
 
361
            return True
 
362
 
 
363
        return False
 
364
 
 
365
    #---------------------------------------------------------------------------
 
366
    #  Handles the user hitting the 'Esc'ape key:
 
367
    #---------------------------------------------------------------------------
 
368
 
 
369
    def _on_key ( self, event ):
 
370
        """ Handles the user pressing the Escape key.
 
371
        """
 
372
        if event.GetKeyCode() == 0x1B:
 
373
           self._on_close_page( event )
 
374
 
 
375
    #---------------------------------------------------------------------------
 
376
    #  Handles an 'Undo' change request:
 
377
    #---------------------------------------------------------------------------
 
378
 
 
379
    def _on_undo ( self, event ):
 
380
        """ Handles an "Undo" change request.
 
381
        """
 
382
        self.ui.history.undo()
 
383
 
 
384
    #---------------------------------------------------------------------------
 
385
    #  Handles a 'Redo' change request:
 
386
    #---------------------------------------------------------------------------
 
387
 
 
388
    def _on_redo ( self, event ):
 
389
        """ Handles a "Redo" change request.
 
390
        """
 
391
        self.ui.history.redo()
 
392
 
 
393
    #---------------------------------------------------------------------------
 
394
    #  Handles a 'Revert' all changes request:
 
395
    #---------------------------------------------------------------------------
 
396
 
 
397
    def _on_revert ( self, event ):
 
398
        """ Handles a request to revert all changes.
 
399
        """
 
400
        ui = self.ui
 
401
        if ui.history is not None:
 
402
            ui.history.revert()
 
403
        ui.handler.revert( ui.info )
 
404
 
 
405
    #---------------------------------------------------------------------------
 
406
    #  Handles a 'Cancel' all changes request:
 
407
    #---------------------------------------------------------------------------
 
408
 
 
409
    def _on_cancel ( self, event ):
 
410
        """ Handles a request to cancel all changes.
 
411
        """
 
412
        if self.ui.handler.close( self.ui.info, False ):
 
413
            self._on_revert( event )
 
414
            self.close( wx.ID_CANCEL )
 
415
 
 
416
    #---------------------------------------------------------------------------
 
417
    #  Handles editing errors:
 
418
    #---------------------------------------------------------------------------
 
419
 
 
420
    def _on_error ( self, errors ):
 
421
        """ Handles editing errors.
 
422
        """
 
423
        self.ok.Enable( errors == 0 )
 
424
 
 
425
    #---------------------------------------------------------------------------
 
426
    #  Handles the 'Help' button being clicked:
 
427
    #---------------------------------------------------------------------------
 
428
 
 
429
    def _on_help ( self, event ):
 
430
        """ Handles the 'user clicking the Help button.
 
431
        """
 
432
        self.ui.handler.show_help( self.ui.info, event.GetEventObject() )
 
433
 
 
434
    #---------------------------------------------------------------------------
 
435
    #  Handles the undo history 'undoable' state changing:
 
436
    #---------------------------------------------------------------------------
 
437
 
 
438
    def _on_undoable ( self, state ):
 
439
        """ Handles a change to the "undoable" state of the undo history
 
440
        """
 
441
        self.undo.Enable( state )
 
442
 
 
443
    #---------------------------------------------------------------------------
 
444
    #  Handles the undo history 'redoable' state changing:
 
445
    #---------------------------------------------------------------------------
 
446
 
 
447
    def _on_redoable ( self, state ):
 
448
        """ Handles a change to the "redoable state of the undo history.
 
449
        """
 
450
        self.redo.Enable( state )
 
451
 
 
452
    #---------------------------------------------------------------------------
 
453
    #  Handles the 'revert' state changing:
 
454
    #---------------------------------------------------------------------------
 
455
 
 
456
    def _on_revertable ( self, state ):
 
457
        """ Handles a change to the "revert" state.
 
458
        """
 
459
        self.revert.Enable( state )
 
460
 
 
461
#-------------------------------------------------------------------------------
 
462
#  'MouseMonitor' class:
 
463
#-------------------------------------------------------------------------------
 
464
 
 
465
class MouseMonitor ( wx.Timer ):
 
466
    """ Monitors a specified window and closes it the first time the mouse
 
467
        pointer leaves the window.
 
468
    """
 
469
 
 
470
    def __init__ ( self, ui ):
 
471
        super( MouseMonitor, self ).__init__()
 
472
        self.ui           = ui
 
473
        kind              = ui.view.kind
 
474
        self.is_activated = self.is_info = (kind == 'info')
 
475
        self.border = 3
 
476
        if kind == 'popup':
 
477
            self.border = 10
 
478
        self.Start( 100 )
 
479
 
 
480
    def Notify ( self ):
 
481
        ui      = self.ui
 
482
        control = ui.control
 
483
        if ui.control is None:
 
484
            # Looks like someone forgot to tell us that the ui has been closed:
 
485
            self.Stop()
 
486
            return
 
487
 
 
488
        mx, my   = wx.GetMousePosition()
 
489
        cx, cy   = control.ClientToScreenXY( 0, 0 )
 
490
        cdx, cdy = control.GetSizeTuple()
 
491
 
 
492
        if self.is_activated:
 
493
            # Don't close the popup if any mouse buttons are currently pressed:
 
494
            ms = wx.GetMouseState()
 
495
            if ms.LeftDown() or ms.MiddleDown() or ms.RightDown():
 
496
                return
 
497
 
 
498
            # Check for the special case of the mouse pointer having to be
 
499
            # within the original bounds of the object the popup was created
 
500
            # for:
 
501
            if self.is_info:
 
502
                parent = control._parent
 
503
                if isinstance( parent, wx.Window ):
 
504
                    px, py, pdx, pdy = parent.GetScreenRect()
 
505
                else:
 
506
                    px, py, pdx, pdy = parent
 
507
                if ((mx < px) or (mx >= (px + pdx)) or
 
508
                    (my < py) or (my >= (py + pdy))):
 
509
                    ui.owner.close_popup()
 
510
                    self.is_activated = False
 
511
 
 
512
            else:
 
513
                # Allow for a 'dead zone' border around the window to allow for
 
514
                # small motor control problems:
 
515
                border = self.border
 
516
                if ((mx < (cx - border)) or (mx >= (cx + cdx + border)) or
 
517
                    (my < (cy - border)) or (my >= (cy + cdy + border))):
 
518
                    ui.owner.close_popup()
 
519
                    self.is_activated = False
 
520
        elif (cx <= mx < (cx + cdx)) and (cy <= my < (cy + cdy)):
 
521
            self.is_activated = True
 
522