1
#------------------------------------------------------------------------------
3
# Copyright (c) 2005, Enthought, Inc.
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
11
# Thanks for using Enthought open source!
13
# Author: David C. Morrill
16
#------------------------------------------------------------------------------
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).
22
#-------------------------------------------------------------------------------
24
#-------------------------------------------------------------------------------
29
import restore_window, save_window, TraitsUIScrolledPanel
35
import panel, show_help
38
import DefaultTitle, WindowColor, screen_dy, \
44
import UndoButton, RevertButton, OKButton, CancelButton, HelpButton
46
#-------------------------------------------------------------------------------
48
#-------------------------------------------------------------------------------
50
# Types of supported windows:
57
# Types of 'popup' dialogs:
58
Popups = set( ( POPUP, POPOVER, INFO ) )
60
#-------------------------------------------------------------------------------
61
# Creates a 'live update' wxPython user interface for a specified UI object:
62
#-------------------------------------------------------------------------------
64
def ui_live ( ui, parent ):
65
""" Creates a live, non-modal wxPython user interface for a specified UI
68
ui_dialog( ui, parent, NONMODAL )
70
def ui_livemodal ( ui, parent ):
71
""" Creates a live, modal wxPython user interface for a specified UI object.
73
ui_dialog( ui, parent, MODAL )
75
def ui_popup ( ui, parent ):
76
""" Creates a live, temporary popup wxPython user interface for a specified
79
ui_dialog( ui, parent, POPUP )
81
def ui_popover ( ui, parent ):
82
""" Creates a live, temporary popup wxPython user interface for a specified
85
ui_dialog( ui, parent, POPOVER )
87
def ui_info ( ui, parent ):
88
""" Creates a live, temporary popup wxPython user interface for a specified
91
ui_dialog( ui, parent, INFO )
93
def ui_dialog ( ui, parent, style ):
94
""" Creates a live wxPython user interface for a specified UI object.
97
ui.owner = LiveWindow()
99
ui.owner.init( ui, parent, style )
100
ui.control = ui.owner.control
101
ui.control._parent = parent
113
ui.handler.position( ui.info )
114
restore_window( ui, is_popup = (style in Popups) )
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()
128
#-------------------------------------------------------------------------------
129
# 'LiveWindow' class:
130
#-------------------------------------------------------------------------------
132
class LiveWindow ( BaseDialog ):
133
""" User interface window that immediately updates its underlying object(s).
136
#---------------------------------------------------------------------------
137
# Initializes the object:
138
#---------------------------------------------------------------------------
140
def init ( self, ui, parent, style ):
141
self.is_modal = (style == MODAL)
145
window_style |= wx.RESIZE_BORDER
153
if window is not None:
154
if history is not None:
155
history.on_trait_change( self._on_undoable, 'undoable',
157
history.on_trait_change( self._on_redoable, 'redoable',
159
history.on_trait_change( self._on_revertable, 'undoable',
161
window.SetSizer( None )
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 |
174
window = wx.Frame( parent, -1, title, style = window_style |
175
(wx.DEFAULT_FRAME_STYLE & (~wx.RESIZE_BORDER)) )
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 |
183
window = wx.Frame( parent, -1, '', style = window_style )
184
window._kind = ui.view.kind
185
self._monitor = MouseMonitor( ui )
187
# Set the correct default window background color:
188
window.SetBackgroundColour( WindowColor )
190
self.control = window
191
wx.EVT_CLOSE( window, self._on_close_page )
192
wx.EVT_CHAR( window, self._on_key )
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):
203
history = UndoHistory()
208
# Create the actual trait sheet panel and imbed it in a scrollable
209
# window (if requested):
210
sw_sizer = wx.BoxSizer( wx.VERTICAL )
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
220
sw.SetSize( wx.Size( tsdx + ((tsdy > max_dy) * scrollbar_dx),
221
min( tsdy, max_dy ) ) )
223
sw = panel( ui, window )
225
sw_sizer.Add( sw, 1, wx.EXPAND )
226
sw_sizer.SetMinSize(sw.GetSize())
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 )
233
# Convert all button flags to actual button actions if no buttons
234
# were specified in the 'buttons' trait:
237
self.check_button( buttons, UndoButton )
240
self.check_button( buttons, RevertButton )
243
self.check_button( buttons, OKButton )
246
self.check_button( buttons, CancelButton )
249
self.check_button( buttons, HelpButton )
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
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',
263
history.on_trait_change( self._on_redoable, 'redoable',
266
self._on_undoable( True )
269
self._on_redoable( True )
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',
277
self._on_revertable( True )
279
elif self.is_button( button, 'OK' ):
280
self.ok = self.add_button( button, b_sizer, self._on_ok,
282
ui.on_trait_change( self._on_error, 'errors',
285
elif self.is_button( button, 'Cancel' ):
286
self.add_button( button, b_sizer, self._on_cancel,
289
elif self.is_button( button, 'Help' ):
290
self.add_button( button, b_sizer, self._on_help,
293
elif not self.is_button( button, '' ):
294
self.add_button( button, b_sizer, default = default )
296
sw_sizer.Add( b_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 5 )
298
# Add the menu bar, tool bar and status bar (if any):
303
# Lay all of the dialog contents out:
304
window.SetSizer( sw_sizer )
307
#---------------------------------------------------------------------------
308
# Closes the dialog window:
309
#---------------------------------------------------------------------------
311
def close ( self, rc = wx.ID_OK ):
312
""" Closes the dialog window.
315
ui.result = (rc == wx.ID_OK)
318
self.control.EndModal( rc )
321
self.ui = self.undo = self.redo = self.revert = self.control = None
323
#---------------------------------------------------------------------------
324
# Handles the user clicking the window/dialog 'close' button/icon:
325
#---------------------------------------------------------------------------
327
def _on_close_page ( self, event ):
328
""" Handles the user clicking the window/dialog "close" button/icon.
330
if self.ui.view.close_result == False:
331
self._on_cancel( event )
335
#---------------------------------------------------------------------------
336
# Handles the user giving focus to another window for a 'popup' view:
337
#---------------------------------------------------------------------------
339
def _on_close_popup ( self, event ):
340
""" Handles the user giving focus to another window for a 'popup' view.
342
if not event.GetActive():
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:
351
#---------------------------------------------------------------------------
352
# Handles the user clicking the 'OK' button:
353
#---------------------------------------------------------------------------
355
def _on_ok ( self, event = None ):
356
""" Handles the user clicking the **OK** button.
358
if self.ui.handler.close( self.ui.info, True ):
359
wx.EVT_ACTIVATE( self.control, None )
360
self.close( wx.ID_OK )
365
#---------------------------------------------------------------------------
366
# Handles the user hitting the 'Esc'ape key:
367
#---------------------------------------------------------------------------
369
def _on_key ( self, event ):
370
""" Handles the user pressing the Escape key.
372
if event.GetKeyCode() == 0x1B:
373
self._on_close_page( event )
375
#---------------------------------------------------------------------------
376
# Handles an 'Undo' change request:
377
#---------------------------------------------------------------------------
379
def _on_undo ( self, event ):
380
""" Handles an "Undo" change request.
382
self.ui.history.undo()
384
#---------------------------------------------------------------------------
385
# Handles a 'Redo' change request:
386
#---------------------------------------------------------------------------
388
def _on_redo ( self, event ):
389
""" Handles a "Redo" change request.
391
self.ui.history.redo()
393
#---------------------------------------------------------------------------
394
# Handles a 'Revert' all changes request:
395
#---------------------------------------------------------------------------
397
def _on_revert ( self, event ):
398
""" Handles a request to revert all changes.
401
if ui.history is not None:
403
ui.handler.revert( ui.info )
405
#---------------------------------------------------------------------------
406
# Handles a 'Cancel' all changes request:
407
#---------------------------------------------------------------------------
409
def _on_cancel ( self, event ):
410
""" Handles a request to cancel all changes.
412
if self.ui.handler.close( self.ui.info, False ):
413
self._on_revert( event )
414
self.close( wx.ID_CANCEL )
416
#---------------------------------------------------------------------------
417
# Handles editing errors:
418
#---------------------------------------------------------------------------
420
def _on_error ( self, errors ):
421
""" Handles editing errors.
423
self.ok.Enable( errors == 0 )
425
#---------------------------------------------------------------------------
426
# Handles the 'Help' button being clicked:
427
#---------------------------------------------------------------------------
429
def _on_help ( self, event ):
430
""" Handles the 'user clicking the Help button.
432
self.ui.handler.show_help( self.ui.info, event.GetEventObject() )
434
#---------------------------------------------------------------------------
435
# Handles the undo history 'undoable' state changing:
436
#---------------------------------------------------------------------------
438
def _on_undoable ( self, state ):
439
""" Handles a change to the "undoable" state of the undo history
441
self.undo.Enable( state )
443
#---------------------------------------------------------------------------
444
# Handles the undo history 'redoable' state changing:
445
#---------------------------------------------------------------------------
447
def _on_redoable ( self, state ):
448
""" Handles a change to the "redoable state of the undo history.
450
self.redo.Enable( state )
452
#---------------------------------------------------------------------------
453
# Handles the 'revert' state changing:
454
#---------------------------------------------------------------------------
456
def _on_revertable ( self, state ):
457
""" Handles a change to the "revert" state.
459
self.revert.Enable( state )
461
#-------------------------------------------------------------------------------
462
# 'MouseMonitor' class:
463
#-------------------------------------------------------------------------------
465
class MouseMonitor ( wx.Timer ):
466
""" Monitors a specified window and closes it the first time the mouse
467
pointer leaves the window.
470
def __init__ ( self, ui ):
471
super( MouseMonitor, self ).__init__()
474
self.is_activated = self.is_info = (kind == 'info')
483
if ui.control is None:
484
# Looks like someone forgot to tell us that the ui has been closed:
488
mx, my = wx.GetMousePosition()
489
cx, cy = control.ClientToScreenXY( 0, 0 )
490
cdx, cdy = control.GetSizeTuple()
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():
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
502
parent = control._parent
503
if isinstance( parent, wx.Window ):
504
px, py, pdx, pdy = parent.GetScreenRect()
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
513
# Allow for a 'dead zone' border around the window to allow for
514
# small motor control problems:
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