~ubuntu-branches/ubuntu/trusty/python-traitsui/trusty

« back to all changes in this revision

Viewing changes to traitsui/qt4/ui_base.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
# Copyright (c) 2007, Riverbank Computing Limited
 
3
# All rights reserved.
 
4
#
 
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
 
7
 
 
8
#
 
9
# Author: Riverbank Computing Limited
 
10
#------------------------------------------------------------------------------
 
11
 
 
12
"""Defines the base class for the PyQt-based Traits UI modal and non-modal
 
13
   dialogs.
 
14
"""
 
15
 
 
16
 
 
17
from pyface.qt import QtCore, QtGui
 
18
 
 
19
from traits.api \
 
20
    import HasStrictTraits, HasPrivateTraits, Instance, List, Event
 
21
 
 
22
from traitsui.api \
 
23
    import UI
 
24
 
 
25
from traitsui.menu \
 
26
    import Action
 
27
 
 
28
from constants \
 
29
    import DefaultTitle
 
30
 
 
31
from editor \
 
32
    import Editor
 
33
 
 
34
from helper \
 
35
    import restore_window, save_window
 
36
 
 
37
 
 
38
#-------------------------------------------------------------------------------
 
39
#  Constants:
 
40
#-------------------------------------------------------------------------------
 
41
 
 
42
# List of all predefined system button names:
 
43
SystemButtons = ['Undo', 'Redo', 'Apply', 'Revert', 'OK', 'Cancel', 'Help']
 
44
 
 
45
#-------------------------------------------------------------------------------
 
46
#  'RadioGroup' class:
 
47
#-------------------------------------------------------------------------------
 
48
 
 
49
class RadioGroup ( HasStrictTraits ):
 
50
    """ A group of mutually-exclusive menu or toolbar actions.
 
51
    """
 
52
    # List of menu or tool bar items
 
53
    items = List
 
54
 
 
55
    #---------------------------------------------------------------------------
 
56
    #  Handles a menu item in the group being checked:
 
57
    #---------------------------------------------------------------------------
 
58
 
 
59
    def menu_checked ( self, menu_item ):
 
60
        """ Handles a menu item in the group being checked.
 
61
        """
 
62
        for item in self.items:
 
63
            if item is not menu_item:
 
64
                item.control.Check( False )
 
65
                item.item.action.checked = False
 
66
 
 
67
    #---------------------------------------------------------------------------
 
68
    #  Handles a tool bar item in the group being checked:
 
69
    #---------------------------------------------------------------------------
 
70
 
 
71
    def toolbar_checked ( self, toolbar_item ):
 
72
        """ Handles a tool bar item in the group being checked.
 
73
        """
 
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
 
78
 
 
79
#-------------------------------------------------------------------------------
 
80
#  'ButtonEditor' class:
 
81
#-------------------------------------------------------------------------------
 
82
 
 
83
class ButtonEditor(Editor):
 
84
    """ Editor for buttons.
 
85
    """
 
86
    #---------------------------------------------------------------------------
 
87
    #  Trait definitions:
 
88
    #---------------------------------------------------------------------------
 
89
 
 
90
    # Action associated with the button
 
91
    action = Instance(Action)
 
92
 
 
93
    #---------------------------------------------------------------------------
 
94
    #  Initializes the object:
 
95
    #---------------------------------------------------------------------------
 
96
 
 
97
    def __init__(self, **traits):
 
98
        self.set(**traits)
 
99
 
 
100
    #---------------------------------------------------------------------------
 
101
    #  Handles the associated button being clicked:
 
102
    #---------------------------------------------------------------------------
 
103
 
 
104
    def perform(self):
 
105
        """Handles the associated button being clicked."""
 
106
        self.ui.do_undoable(self._perform, None)
 
107
 
 
108
    def _perform(self, event):
 
109
        method_name = self.action.action
 
110
        if method_name == '':
 
111
            method_name = '_%s_clicked' % self.action.name.lower()
 
112
 
 
113
        method = getattr(self.ui.handler, method_name, None)
 
114
        if method is not None:
 
115
            method(self.ui.info)
 
116
        else:
 
117
            self.action.perform(event)
 
118
 
 
119
 
 
120
class BasePanel(object):
 
121
    """Base class for Traits UI panels.
 
122
    """
 
123
 
 
124
    #---------------------------------------------------------------------------
 
125
    #  Performs the action described by a specified Action object:
 
126
    #---------------------------------------------------------------------------
 
127
 
 
128
    def perform ( self, action ):
 
129
        """ Performs the action described by a specified Action object.
 
130
        """
 
131
        self.ui.do_undoable( self._perform, action )
 
132
 
 
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 )
 
137
        else:
 
138
            action.perform()
 
139
 
 
140
    #---------------------------------------------------------------------------
 
141
    #  Check to see if a specified 'system' button is in the buttons list, and
 
142
    # add it if it is not:
 
143
    #---------------------------------------------------------------------------
 
144
 
 
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.
 
148
        """
 
149
        name = action.name
 
150
        for button in buttons:
 
151
            if self.is_button( button, name ):
 
152
                return
 
153
        buttons.append( action )
 
154
 
 
155
    #---------------------------------------------------------------------------
 
156
    #  Check to see if a specified Action button is a 'system' button:
 
157
    #---------------------------------------------------------------------------
 
158
 
 
159
    def is_button ( self, action, name ):
 
160
        """ Returns whether a specified action button is a system button.
 
161
        """
 
162
        if isinstance(action, basestring):
 
163
            return (action == name)
 
164
        return (action.name == name)
 
165
 
 
166
    #---------------------------------------------------------------------------
 
167
    #  Coerces a string to an Action if necessary:
 
168
    #---------------------------------------------------------------------------
 
169
 
 
170
    def coerce_button ( self, action ):
 
171
        """ Coerces a string to an Action if necessary.
 
172
        """
 
173
        if isinstance(action, basestring):
 
174
            return Action( name   = action,
 
175
                           action = '?'[ (not action in SystemButtons): ] )
 
176
        return action
 
177
 
 
178
    #---------------------------------------------------------------------------
 
179
    #  Creates a user specified button:
 
180
    #---------------------------------------------------------------------------
 
181
 
 
182
    def add_button ( self, action, bbox, role, method=None, enabled=True,
 
183
                     name=None, default=False ):
 
184
        """ Creates a button.
 
185
        """
 
186
        ui = self.ui
 
187
        if ((action.defined_when != '') and
 
188
            (not ui.eval_when( action.defined_when ))):
 
189
            return None
 
190
 
 
191
        if name is None:
 
192
            name = action.name
 
193
        id     = action.id
 
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,
 
200
                                   action  = action,
 
201
                                   control = button )
 
202
            if id != '':
 
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 )
 
208
            if method is None:
 
209
                method = editor.perform
 
210
 
 
211
        if method is not None:
 
212
            button.connect(button, QtCore.SIGNAL('clicked()'), method)
 
213
 
 
214
        if action.tooltip != '':
 
215
            button.setToolTip(action.tooltip)
 
216
 
 
217
        return button
 
218
 
 
219
    def _on_help(self):
 
220
        """Handles the user clicking the Help button.
 
221
        """
 
222
        # FIXME: Needs porting to PyQt.
 
223
        self.ui.handler.show_help(self.ui.info, event.GetEventObject())
 
224
 
 
225
    def _on_undo(self):
 
226
        """Handles a request to undo a change.
 
227
        """
 
228
        self.ui.history.undo()
 
229
 
 
230
    def _on_undoable(self, state):
 
231
        """Handles a change to the "undoable" state of the undo history
 
232
        """
 
233
        self.undo.setEnabled(state)
 
234
 
 
235
    def _on_redo(self):
 
236
        """Handles a request to redo a change.
 
237
        """
 
238
        self.ui.history.redo()
 
239
 
 
240
    def _on_redoable(self, state):
 
241
        """Handles a change to the "redoable" state of the undo history.
 
242
        """
 
243
        self.redo.setEnabled(state)
 
244
 
 
245
    def _on_revert(self):
 
246
        """Handles a request to revert all changes.
 
247
        """
 
248
        ui = self.ui
 
249
        if ui.history is not None:
 
250
            ui.history.revert()
 
251
        ui.handler.revert(ui.info)
 
252
 
 
253
    def _on_revertable(self, state):
 
254
        """ Handles a change to the "revert" state.
 
255
        """
 
256
        self.revert.setEnabled(state)
 
257
 
 
258
 
 
259
class _StickyDialog(QtGui.QDialog):
 
260
    """A QDialog that will only close if the traits handler allows it."""
 
261
 
 
262
    def __init__(self, ui, parent):
 
263
        """Initialise the dialog."""
 
264
 
 
265
        QtGui.QDialog.__init__(self, parent)
 
266
 
 
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)
 
273
 
 
274
        # Set the dialog's window flags and properties.
 
275
        if ui.view.resizable:
 
276
            flags = QtCore.Qt.Window
 
277
        else:
 
278
            flags = QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint
 
279
            layout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
 
280
        try:
 
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.
 
287
            pass
 
288
        self.setWindowFlags(flags)
 
289
 
 
290
        self._ui = ui
 
291
        self._result = None
 
292
 
 
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.)"""
 
297
 
 
298
        if self._ok_to_close():
 
299
            QtGui.QDialog.closeEvent(self, e)
 
300
        else:
 
301
            # Ignore the event thereby keeping the dialog open.
 
302
            e.ignore()
 
303
 
 
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."""
 
307
 
 
308
        if e.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return) and \
 
309
               not self._ui.view.default_button:
 
310
            return
 
311
        
 
312
        if e.key() == QtCore.Qt.Key_Escape and not self._ok_to_close():
 
313
            return
 
314
        
 
315
        QtGui.QDialog.keyPressEvent(self, e)
 
316
 
 
317
    def sizeHint(self):
 
318
        """Reimplemented to provide an appropriate size hint for the window.
 
319
        """
 
320
        size = QtGui.QDialog.sizeHint(self)
 
321
        view = self._ui.view
 
322
        if view.width > 0:
 
323
            size.setWidth(view.width)
 
324
        if view.height > 0:
 
325
            size.setHeight(view.height)
 
326
        return size
 
327
 
 
328
    def done(self, r):
 
329
        """Reimplemented to ignore calls to accept() or reject() if
 
330
        appropriate."""
 
331
 
 
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)
 
337
 
 
338
    def _ok_to_close(self, is_ok=None):
 
339
        """Let the handler decide if the dialog should be closed."""
 
340
 
 
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'
 
343
        # button.
 
344
        if is_ok is None:
 
345
            # Use the view's default, if there is one.
 
346
            is_ok = self._ui.view.close_result
 
347
            if is_ok is None:
 
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()
 
351
 
 
352
        ok_to_close = self._ui.handler.close(self._ui.info, is_ok)
 
353
        if ok_to_close:
 
354
            # Save the result now.
 
355
            self._result = is_ok
 
356
 
 
357
        return ok_to_close
 
358
 
 
359
 
 
360
class BaseDialog(BasePanel):
 
361
    """Base class for Traits UI dialog boxes."""
 
362
 
 
363
    # The different dialog styles.
 
364
    NONMODAL, MODAL, POPUP = range(3)
 
365
 
 
366
    def init(self, ui, parent, style):
 
367
        """Initialise the dialog by creating the controls."""
 
368
 
 
369
        raise NotImplementedError
 
370
 
 
371
    def create_dialog(self, parent, style):
 
372
        """Create the dialog control."""
 
373
 
 
374
        self.control = control = _StickyDialog(self.ui, parent)
 
375
 
 
376
        view = self.ui.view
 
377
 
 
378
        control.setModal(style == BaseDialog.MODAL)
 
379
        control.setWindowTitle(view.title or DefaultTitle)
 
380
 
 
381
        QtCore.QObject.connect(control, QtCore.SIGNAL('finished(int)'),
 
382
                self._on_finished)
 
383
 
 
384
    def add_contents(self, panel, buttons):
 
385
        """Add a panel (either a widget, layout or None) and optional buttons
 
386
        to the dialog."""
 
387
 
 
388
        # If the panel is a layout then provide a widget for it.
 
389
        if isinstance(panel, QtGui.QLayout):
 
390
            w = QtGui.QWidget()
 
391
            panel.setContentsMargins(0, 0, 0, 0)
 
392
            w.setLayout(panel)
 
393
            panel = w
 
394
 
 
395
        if panel is not None:
 
396
            self.control._mw.setCentralWidget(panel)
 
397
 
 
398
        # Add the optional buttons.
 
399
        if buttons is not None:
 
400
            self.control.layout().addWidget(buttons)
 
401
 
 
402
        # Add the menu bar, tool bar and status bar (if any).
 
403
        self._add_menubar()
 
404
        self._add_toolbar()
 
405
        self._add_statusbar()
 
406
 
 
407
    def close(self, rc=True):
 
408
        """Close the dialog and set the given return code."""
 
409
 
 
410
        self.ui.dispose(rc)
 
411
        self.ui = self.control = None
 
412
 
 
413
    @staticmethod
 
414
    def display_ui(ui, parent, style):
 
415
        """Display the UI."""
 
416
 
 
417
        ui.owner.init(ui, parent, style)
 
418
        ui.control = ui.owner.control
 
419
        ui.control._parent = parent
 
420
 
 
421
        try:
 
422
            ui.prepare_ui()
 
423
        except:
 
424
            ui.control.setParent(None)
 
425
            ui.control.ui = None
 
426
            ui.control = None
 
427
            ui.owner = None
 
428
            ui.result = False
 
429
            raise
 
430
 
 
431
        ui.handler.position(ui.info)
 
432
        restore_window(ui)
 
433
 
 
434
        if style == BaseDialog.NONMODAL:
 
435
            ui.control.show()
 
436
        else:
 
437
            ui.control.setWindowModality(QtCore.Qt.WindowModal)
 
438
            ui.control.exec_()
 
439
 
 
440
    def set_icon(self, icon=None):
 
441
        """Sets the dialog's icon."""
 
442
 
 
443
        from pyface.image_resource import ImageResource
 
444
 
 
445
        if not isinstance(icon, ImageResource):
 
446
            icon = ImageResource('frame.png')
 
447
 
 
448
        self.control.setWindowIcon(icon.create_icon())
 
449
 
 
450
    def _on_error(self, errors):
 
451
        """Handles editing errors."""
 
452
 
 
453
        self.ok.setEnabled(errors == 0)
 
454
 
 
455
    #---------------------------------------------------------------------------
 
456
    #  Adds a menu bar to the dialog:
 
457
    #---------------------------------------------------------------------------
 
458
 
 
459
    def _add_menubar(self):
 
460
        """Adds a menu bar to the dialog.
 
461
        """
 
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
 
468
 
 
469
    #---------------------------------------------------------------------------
 
470
    #  Adds a tool bar to the dialog:
 
471
    #---------------------------------------------------------------------------
 
472
 
 
473
    def _add_toolbar ( self ):
 
474
        """ Adds a toolbar to the dialog.
 
475
        """
 
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
 
483
 
 
484
    #---------------------------------------------------------------------------
 
485
    #  Adds a status bar to the dialog:
 
486
    #---------------------------------------------------------------------------
 
487
 
 
488
    def _add_statusbar ( self ):
 
489
        """ Adds a statusbar to the dialog.
 
490
        """
 
491
        if self.ui.view.statusbar is not None:
 
492
            control = QtGui.QStatusBar()
 
493
            control.setSizeGripEnabled(self.ui.view.resizable)
 
494
            listeners = []
 
495
            for item in self.ui.view.statusbar:
 
496
                # Create the status widget with initial text
 
497
                name = item.name
 
498
                item_control = QtGui.QLabel()
 
499
                item_control.setText(self.ui.get_extended_value(name))
 
500
 
 
501
                # Add the widget to the control with correct size
 
502
                width = abs(item.width)
 
503
                stretch = 0
 
504
                if width <= 1.0:
 
505
                    stretch = int(100 * width)
 
506
                else:
 
507
                    item_control.setMinimumWidth(width)
 
508
                control.addWidget(item_control, stretch)
 
509
 
 
510
                # Set up event listener for updating the status text
 
511
                col = name.find('.')
 
512
                obj = 'object'
 
513
                if col >= 0:
 
514
                    obj = name[:col]
 
515
                    name = name[col+1:]
 
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))
 
520
 
 
521
            self.control._mw.setStatusBar(control)
 
522
            self.ui._statusbar = listeners
 
523
 
 
524
    def _set_status_text(self, control):
 
525
        """ Helper function for _add_statusbar.
 
526
        """
 
527
        def set_status_text(text):
 
528
            control.setText(text)
 
529
 
 
530
        return set_status_text
 
531
 
 
532
    #---------------------------------------------------------------------------
 
533
    #  Adds a menu item to the menu bar being constructed:
 
534
    #---------------------------------------------------------------------------
 
535
 
 
536
    def add_to_menu ( self, menu_item ):
 
537
        """ Adds a menu item to the menu bar being constructed.
 
538
        """
 
539
        item   = menu_item.item
 
540
        action = item.action
 
541
 
 
542
        if action.id != '':
 
543
            self.ui.info.bind( action.id, menu_item )
 
544
 
 
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
 
552
 
 
553
        if action.visible_when != '':
 
554
            self.ui.add_visible( action.visible_when, menu_item )
 
555
 
 
556
        if action.enabled_when != '':
 
557
            self.ui.add_enabled( action.enabled_when, menu_item )
 
558
 
 
559
        if action.checked_when != '':
 
560
            self.ui.add_checked( action.checked_when, menu_item )
 
561
 
 
562
    #---------------------------------------------------------------------------
 
563
    #  Adds a tool bar item to the tool bar being constructed:
 
564
    #---------------------------------------------------------------------------
 
565
 
 
566
    def add_to_toolbar ( self, toolbar_item ):
 
567
        """ Adds a toolbar item to the toolbar being constructed.
 
568
        """
 
569
        self.add_to_menu( toolbar_item )
 
570
 
 
571
    def can_add_to_menu(self, action, action_event=None):
 
572
        """Returns whether the action should be defined in the user interface.
 
573
        """
 
574
        if action.defined_when == '':
 
575
            return True
 
576
 
 
577
        return self.ui.eval_when(action.defined_when)
 
578
 
 
579
    def can_add_to_toolbar(self, action):
 
580
        """Returns whether the toolbar action should be defined in the user
 
581
           interface.
 
582
        """
 
583
        return self.can_add_to_menu(action)