1
#------------------------------------------------------------------------------
2
# Copyright (c) 2007, Riverbank Computing Limited
5
# This software is provided without warranty under the terms of the BSD license.
6
# However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply
9
# Author: Riverbank Computing Limited
10
#------------------------------------------------------------------------------
12
"""Defines the base class for the PyQt-based Traits UI modal and non-modal
17
from pyface.qt import QtCore, QtGui
20
import HasStrictTraits, HasPrivateTraits, Instance, List, Event
35
import restore_window, save_window
38
#-------------------------------------------------------------------------------
40
#-------------------------------------------------------------------------------
42
# List of all predefined system button names:
43
SystemButtons = ['Undo', 'Redo', 'Apply', 'Revert', 'OK', 'Cancel', 'Help']
45
#-------------------------------------------------------------------------------
47
#-------------------------------------------------------------------------------
49
class RadioGroup ( HasStrictTraits ):
50
""" A group of mutually-exclusive menu or toolbar actions.
52
# List of menu or tool bar items
55
#---------------------------------------------------------------------------
56
# Handles a menu item in the group being checked:
57
#---------------------------------------------------------------------------
59
def menu_checked ( self, menu_item ):
60
""" Handles a menu item in the group being checked.
62
for item in self.items:
63
if item is not menu_item:
64
item.control.Check( False )
65
item.item.action.checked = False
67
#---------------------------------------------------------------------------
68
# Handles a tool bar item in the group being checked:
69
#---------------------------------------------------------------------------
71
def toolbar_checked ( self, toolbar_item ):
72
""" Handles a tool bar item in the group being checked.
74
for item in self.items:
75
if item is not toolbar_item:
76
item.tool_bar.ToggleTool( item.control_id, False )
77
item.item.action.checked = False
79
#-------------------------------------------------------------------------------
80
# 'ButtonEditor' class:
81
#-------------------------------------------------------------------------------
83
class ButtonEditor(Editor):
84
""" Editor for buttons.
86
#---------------------------------------------------------------------------
88
#---------------------------------------------------------------------------
90
# Action associated with the button
91
action = Instance(Action)
93
#---------------------------------------------------------------------------
94
# Initializes the object:
95
#---------------------------------------------------------------------------
97
def __init__(self, **traits):
100
#---------------------------------------------------------------------------
101
# Handles the associated button being clicked:
102
#---------------------------------------------------------------------------
105
"""Handles the associated button being clicked."""
106
self.ui.do_undoable(self._perform, None)
108
def _perform(self, event):
109
method_name = self.action.action
110
if method_name == '':
111
method_name = '_%s_clicked' % self.action.name.lower()
113
method = getattr(self.ui.handler, method_name, None)
114
if method is not None:
117
self.action.perform(event)
120
class BasePanel(object):
121
"""Base class for Traits UI panels.
124
#---------------------------------------------------------------------------
125
# Performs the action described by a specified Action object:
126
#---------------------------------------------------------------------------
128
def perform ( self, action ):
129
""" Performs the action described by a specified Action object.
131
self.ui.do_undoable( self._perform, action )
133
def _perform ( self, action ):
134
method = getattr( self.ui.handler, action.action, None )
135
if method is not None:
136
method( self.ui.info )
140
#---------------------------------------------------------------------------
141
# Check to see if a specified 'system' button is in the buttons list, and
142
# add it if it is not:
143
#---------------------------------------------------------------------------
145
def check_button ( self, buttons, action ):
146
""" Adds *action* to the system buttons list for this dialog, if it is
147
not already in the list.
150
for button in buttons:
151
if self.is_button( button, name ):
153
buttons.append( action )
155
#---------------------------------------------------------------------------
156
# Check to see if a specified Action button is a 'system' button:
157
#---------------------------------------------------------------------------
159
def is_button ( self, action, name ):
160
""" Returns whether a specified action button is a system button.
162
if isinstance(action, basestring):
163
return (action == name)
164
return (action.name == name)
166
#---------------------------------------------------------------------------
167
# Coerces a string to an Action if necessary:
168
#---------------------------------------------------------------------------
170
def coerce_button ( self, action ):
171
""" Coerces a string to an Action if necessary.
173
if isinstance(action, basestring):
174
return Action( name = action,
175
action = '?'[ (not action in SystemButtons): ] )
178
#---------------------------------------------------------------------------
179
# Creates a user specified button:
180
#---------------------------------------------------------------------------
182
def add_button ( self, action, bbox, role, method=None, enabled=True,
183
name=None, default=False ):
184
""" Creates a button.
187
if ((action.defined_when != '') and
188
(not ui.eval_when( action.defined_when ))):
194
button = bbox.addButton(name, role)
195
button.setAutoDefault(False)
196
button.setDefault(default)
197
button.setEnabled(enabled)
198
if (method is None) or (action.enabled_when != '') or (id != ''):
199
editor = ButtonEditor( ui = ui,
203
ui.info.bind( id, editor )
204
if action.visible_when != '':
205
ui.add_visible( action.visible_when, editor )
206
if action.enabled_when != '':
207
ui.add_enabled( action.enabled_when, editor )
209
method = editor.perform
211
if method is not None:
212
button.connect(button, QtCore.SIGNAL('clicked()'), method)
214
if action.tooltip != '':
215
button.setToolTip(action.tooltip)
220
"""Handles the user clicking the Help button.
222
# FIXME: Needs porting to PyQt.
223
self.ui.handler.show_help(self.ui.info, event.GetEventObject())
226
"""Handles a request to undo a change.
228
self.ui.history.undo()
230
def _on_undoable(self, state):
231
"""Handles a change to the "undoable" state of the undo history
233
self.undo.setEnabled(state)
236
"""Handles a request to redo a change.
238
self.ui.history.redo()
240
def _on_redoable(self, state):
241
"""Handles a change to the "redoable" state of the undo history.
243
self.redo.setEnabled(state)
245
def _on_revert(self):
246
"""Handles a request to revert all changes.
249
if ui.history is not None:
251
ui.handler.revert(ui.info)
253
def _on_revertable(self, state):
254
""" Handles a change to the "revert" state.
256
self.revert.setEnabled(state)
259
class _StickyDialog(QtGui.QDialog):
260
"""A QDialog that will only close if the traits handler allows it."""
262
def __init__(self, ui, parent):
263
"""Initialise the dialog."""
265
QtGui.QDialog.__init__(self, parent)
267
# Create the main window so we can add toolbars etc.
268
self._mw = QtGui.QMainWindow()
269
layout = QtGui.QVBoxLayout()
270
layout.setContentsMargins(0, 0, 0, 0)
271
layout.addWidget(self._mw)
272
self.setLayout(layout)
274
# Set the dialog's window flags and properties.
275
if ui.view.resizable:
276
flags = QtCore.Qt.Window
278
flags = QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint
279
layout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
281
flags |= QtCore.Qt.WindowCloseButtonHint
282
if ui.view.resizable:
283
flags |= (QtCore.Qt.WindowMinimizeButtonHint |
284
QtCore.Qt.WindowMaximizeButtonHint)
285
except AttributeError:
286
# Either PyQt or Qt is too old.
288
self.setWindowFlags(flags)
293
def closeEvent(self, e):
294
"""Reimplemented to check when the clicks the window close button.
295
(Note that QDialog doesn't get a close event when the dialog is closed
296
in any other way.)"""
298
if self._ok_to_close():
299
QtGui.QDialog.closeEvent(self, e)
301
# Ignore the event thereby keeping the dialog open.
304
def keyPressEvent(self, e):
305
"""Reimplemented to ignore the Escape key if appropriate, and to ignore
306
the Enter key if no default button has been explicitly set."""
308
if e.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return) and \
309
not self._ui.view.default_button:
312
if e.key() == QtCore.Qt.Key_Escape and not self._ok_to_close():
315
QtGui.QDialog.keyPressEvent(self, e)
318
"""Reimplemented to provide an appropriate size hint for the window.
320
size = QtGui.QDialog.sizeHint(self)
323
size.setWidth(view.width)
325
size.setHeight(view.height)
329
"""Reimplemented to ignore calls to accept() or reject() if
332
# If we already have a result then we already know that we are done.
333
if self._result is not None:
334
QtGui.QDialog.done(self, self._result)
335
elif self._ok_to_close(bool(r)):
336
QtGui.QDialog.done(self, r)
338
def _ok_to_close(self, is_ok=None):
339
"""Let the handler decide if the dialog should be closed."""
341
# The is_ok flag is also the dialog result. If we don't know it then
342
# the the user closed the dialog other than by an 'Ok' or 'Cancel'
345
# Use the view's default, if there is one.
346
is_ok = self._ui.view.close_result
348
# There is no default, so use False for a modal dialog and True
349
# for a non-modal one.
350
is_ok = not self.isModal()
352
ok_to_close = self._ui.handler.close(self._ui.info, is_ok)
354
# Save the result now.
360
class BaseDialog(BasePanel):
361
"""Base class for Traits UI dialog boxes."""
363
# The different dialog styles.
364
NONMODAL, MODAL, POPUP = range(3)
366
def init(self, ui, parent, style):
367
"""Initialise the dialog by creating the controls."""
369
raise NotImplementedError
371
def create_dialog(self, parent, style):
372
"""Create the dialog control."""
374
self.control = control = _StickyDialog(self.ui, parent)
378
control.setModal(style == BaseDialog.MODAL)
379
control.setWindowTitle(view.title or DefaultTitle)
381
QtCore.QObject.connect(control, QtCore.SIGNAL('finished(int)'),
384
def add_contents(self, panel, buttons):
385
"""Add a panel (either a widget, layout or None) and optional buttons
388
# If the panel is a layout then provide a widget for it.
389
if isinstance(panel, QtGui.QLayout):
391
panel.setContentsMargins(0, 0, 0, 0)
395
if panel is not None:
396
self.control._mw.setCentralWidget(panel)
398
# Add the optional buttons.
399
if buttons is not None:
400
self.control.layout().addWidget(buttons)
402
# Add the menu bar, tool bar and status bar (if any).
405
self._add_statusbar()
407
def close(self, rc=True):
408
"""Close the dialog and set the given return code."""
411
self.ui = self.control = None
414
def display_ui(ui, parent, style):
415
"""Display the UI."""
417
ui.owner.init(ui, parent, style)
418
ui.control = ui.owner.control
419
ui.control._parent = parent
424
ui.control.setParent(None)
431
ui.handler.position(ui.info)
434
if style == BaseDialog.NONMODAL:
437
ui.control.setWindowModality(QtCore.Qt.WindowModal)
440
def set_icon(self, icon=None):
441
"""Sets the dialog's icon."""
443
from pyface.image_resource import ImageResource
445
if not isinstance(icon, ImageResource):
446
icon = ImageResource('frame.png')
448
self.control.setWindowIcon(icon.create_icon())
450
def _on_error(self, errors):
451
"""Handles editing errors."""
453
self.ok.setEnabled(errors == 0)
455
#---------------------------------------------------------------------------
456
# Adds a menu bar to the dialog:
457
#---------------------------------------------------------------------------
459
def _add_menubar(self):
460
"""Adds a menu bar to the dialog.
462
menubar = self.ui.view.menubar
463
if menubar is not None:
464
self._last_group = self._last_parent = None
465
self.control.layout().setMenuBar(
466
menubar.create_menu_bar( self.control, self ) )
467
self._last_group = self._last_parent = None
469
#---------------------------------------------------------------------------
470
# Adds a tool bar to the dialog:
471
#---------------------------------------------------------------------------
473
def _add_toolbar ( self ):
474
""" Adds a toolbar to the dialog.
476
toolbar = self.ui.view.toolbar
477
if toolbar is not None:
478
self._last_group = self._last_parent = None
479
qt_toolbar = toolbar.create_tool_bar( self.control, self )
480
qt_toolbar.setMovable( False )
481
self.control._mw.addToolBar( qt_toolbar )
482
self._last_group = self._last_parent = None
484
#---------------------------------------------------------------------------
485
# Adds a status bar to the dialog:
486
#---------------------------------------------------------------------------
488
def _add_statusbar ( self ):
489
""" Adds a statusbar to the dialog.
491
if self.ui.view.statusbar is not None:
492
control = QtGui.QStatusBar()
493
control.setSizeGripEnabled(self.ui.view.resizable)
495
for item in self.ui.view.statusbar:
496
# Create the status widget with initial text
498
item_control = QtGui.QLabel()
499
item_control.setText(self.ui.get_extended_value(name))
501
# Add the widget to the control with correct size
502
width = abs(item.width)
505
stretch = int(100 * width)
507
item_control.setMinimumWidth(width)
508
control.addWidget(item_control, stretch)
510
# Set up event listener for updating the status text
516
obj = self.ui.context[obj]
517
set_text = self._set_status_text(item_control)
518
obj.on_trait_change(set_text, name, dispatch='ui')
519
listeners.append((obj, set_text, name))
521
self.control._mw.setStatusBar(control)
522
self.ui._statusbar = listeners
524
def _set_status_text(self, control):
525
""" Helper function for _add_statusbar.
527
def set_status_text(text):
528
control.setText(text)
530
return set_status_text
532
#---------------------------------------------------------------------------
533
# Adds a menu item to the menu bar being constructed:
534
#---------------------------------------------------------------------------
536
def add_to_menu ( self, menu_item ):
537
""" Adds a menu item to the menu bar being constructed.
539
item = menu_item.item
543
self.ui.info.bind( action.id, menu_item )
545
if action.style == 'radio':
546
if ((self._last_group is None) or
547
(self._last_parent is not item.parent)):
548
self._last_group = RadioGroup()
549
self._last_parent = item.parent
550
self._last_group.items.append( menu_item )
551
menu_item.group = self._last_group
553
if action.visible_when != '':
554
self.ui.add_visible( action.visible_when, menu_item )
556
if action.enabled_when != '':
557
self.ui.add_enabled( action.enabled_when, menu_item )
559
if action.checked_when != '':
560
self.ui.add_checked( action.checked_when, menu_item )
562
#---------------------------------------------------------------------------
563
# Adds a tool bar item to the tool bar being constructed:
564
#---------------------------------------------------------------------------
566
def add_to_toolbar ( self, toolbar_item ):
567
""" Adds a toolbar item to the toolbar being constructed.
569
self.add_to_menu( toolbar_item )
571
def can_add_to_menu(self, action, action_event=None):
572
"""Returns whether the action should be defined in the user interface.
574
if action.defined_when == '':
577
return self.ui.eval_when(action.defined_when)
579
def can_add_to_toolbar(self, action):
580
"""Returns whether the toolbar action should be defined in the user
583
return self.can_add_to_menu(action)