enthought / traitsui
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 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:   10/07/2004
15
#
16
# ------------------------------------------------------------------------------
17

18 4
""" Defines the UI class used to represent an active traits-based user
19
    interface.
20
"""
21

22 4
import shelve
23 4
import os
24

25 4
from pyface.ui_traits import Image
26 4
from traits.api import (
27
    Any,
28
    Bool,
29
    Callable,
30
    Dict,
31
    Event,
32
    HasPrivateTraits,
33
    Instance,
34
    Int,
35
    List,
36
    Property,
37
    Str,
38
    TraitError,
39
    on_trait_change,
40
    property_depends_on,
41
)
42

43 4
from traits.trait_base import traits_home, is_str
44

45 4
from .editor import Editor
46

47 4
from .view_elements import ViewElements
48

49 4
from .handler import Handler, ViewHandler
50

51 4
from .toolkit import toolkit
52

53 4
from .ui_info import UIInfo
54

55 4
from .item import Item
56

57 4
from .group import Group, ShadowGroup
58

59

60
# List of **kind** types for views that must have a **parent** window specified
61 4
kind_must_have_parent = ("panel", "subpanel")
62

63

64 4
class UI(HasPrivateTraits):
65
    """ Information about the user interface for a View.
66
    """
67

68
    # -------------------------------------------------------------------------
69
    #  Trait definitions:
70
    # -------------------------------------------------------------------------
71

72
    #: The ViewElements object from which this UI resolves Include items
73 4
    view_elements = Instance(ViewElements)
74

75
    #: Context objects that the UI is editing
76 4
    context = Dict(Str, Any)
77

78
    #: Handler object used for event handling
79 4
    handler = Instance(Handler)
80

81
    #: View template used to construct the user interface
82 4
    view = Instance("traitsui.view.View")
83

84
    #: Panel or dialog associated with the user interface
85 4
    control = Any()
86

87
    #: The parent UI (if any) of this UI
88 4
    parent = Instance("UI")
89

90
    #: Toolkit-specific object that "owns" **control**
91 4
    owner = Any()
92

93
    #: UIInfo object containing context or editor objects
94 4
    info = Instance(UIInfo)
95

96
    #: Result from a modal or wizard dialog:
97 4
    result = Bool(False)
98

99
    #: Undo and Redo history
100 4
    history = Any()
101

102
    #: The KeyBindings object (if any) for this UI:
103 4
    key_bindings = Property(depends_on=["view._key_bindings", "context"])
104

105
    #: The unique ID for this UI for persistence
106 4
    id = Str()
107

108
    #: Have any modifications been made to UI contents?
109 4
    modified = Bool(False)
110

111
    #: Event when the user interface has changed
112 4
    updated = Event(Bool)
113

114
    #: Title of the dialog, if any
115 4
    title = Str()
116

117
    #: The ImageResource of the icon, if any
118 4
    icon = Image
119

120
    #: Should the created UI have scroll bars?
121 4
    scrollable = Bool(False)
122

123
    #: The number of currently pending editor error conditions
124 4
    errors = Int()
125

126
    #: The code used to rebuild an updated user interface
127 4
    rebuild = Callable()
128

129
    #: Set to True when the UI has finished being destroyed.
130 4
    destroyed = Bool(False)
131

132
    # -- Private Traits -------------------------------------------------------
133

134
    #: Original context when used with a modal dialog
135 4
    _context = Dict(Str, Any)
136

137
    #: Copy of original context used for reverting changes
138 4
    _revert = Dict(Str, Any)
139

140
    #: List of methods to call once the user interface is created
141 4
    _defined = List()
142

143
    #: List of (visible_when,Editor) pairs
144 4
    _visible = List()
145

146
    #: List of (enabled_when,Editor) pairs
147 4
    _enabled = List()
148

149
    #: List of (checked_when,Editor) pairs
150 4
    _checked = List()
151

152
    #: Search stack used while building a user interface
153 4
    _search = List()
154

155
    #: List of dispatchable Handler methods
156 4
    _dispatchers = List()
157

158
    #: List of editors used to build the user interface
159 4
    _editors = List()
160

161
    #: List of names bound to the **info** object
162 4
    _names = List()
163

164
    #: Index of currently the active group in the user interface
165 4
    _active_group = Int()
166

167
    #: List of top-level groups used to build the user interface
168 4
    _groups = Property()
169 4
    _groups_cache = Any()
170

171
    #: Count of levels of nesting for undoable actions
172 4
    _undoable = Int(-1)
173

174
    #: Code used to rebuild an updated user interface
175 4
    _rebuild = Callable()
176

177
    #: The statusbar listeners that have been set up:
178 4
    _statusbar = List()
179

180
    #: Control which gets focus after UI is created
181
    #: Note: this does not track focus after UI creation
182
    #: only used by Qt backend.
183 4
    _focus_control = Any()
184

185
    #: Does the UI contain any scrollable widgets?
186
    #:
187
    #: The _scrollable trait is set correctly, but not used currently because
188
    #: its value is arrived at too late to be of use in building the UI.
189 4
    _scrollable = Bool(False)
190

191
    #: Cache for key bindings.
192 4
    _key_bindings = Instance("traitsui.key_bindings.KeyBindings")
193

194
    #: List of traits that are reset when a user interface is recycled
195
    #: (i.e. rebuilt).
196 4
    recyclable_traits = [
197
        "_context",
198
        "_revert",
199
        "_defined",
200
        "_visible",
201
        "_enabled",
202
        "_checked",
203
        "_search",
204
        "_dispatchers",
205
        "_editors",
206
        "_names",
207
        "_active_group",
208
        "_undoable",
209
        "_rebuild",
210
        "_groups_cache",
211
        "_key_bindings",
212
        "_focus_control",
213
    ]
214

215
    #: List of additional traits that are discarded when a user interface is
216
    #: disposed.
217 4
    disposable_traits = [
218
        "view_elements",
219
        "info",
220
        "handler",
221
        "context",
222
        "view",
223
        "history",
224
        "key_bindings",
225
        "icon",
226
        "rebuild",
227
    ]
228

229 4
    def traits_init(self):
230
        """ Initializes the traits object.
231
        """
232 4
        self.info = UIInfo(ui=self)
233 4
        self.handler.init_info(self.info)
234

235 4
    def ui(self, parent, kind):
236
        """ Creates a user interface from the associated View template object.
237
        """
238 4
        if (parent is None) and (kind in kind_must_have_parent):
239 0
            kind = "live"
240 4
        self.view.on_trait_change(
241
            self._updated_changed, "updated", dispatch="ui"
242
        )
243 4
        self.rebuild = getattr(toolkit(), "ui_" + kind)
244 4
        self.rebuild(self, parent)
245

246 4
    def dispose(self, result=None, abort=False):
247
        """ Disposes of the contents of a user interface.
248
        """
249 4
        if result is not None:
250 4
            self.result = result
251

252
        # Only continue if the view has not already been disposed of:
253 4
        if self.control is not None:
254
            # Save the user preference information for the user interface:
255 4
            if not abort:
256 4
                self.save_prefs()
257

258
            # Finish disposing of the user interface:
259 4
            self.finish()
260

261 4
    def recycle(self):
262
        """ Recycles the user interface prior to rebuilding it.
263
        """
264
        # Reset all user interface editors:
265 0
        self.reset(destroy=False)
266

267
        # Discard any context object associated with the ui view control:
268 0
        self.control._object = None
269

270
        # Reset all recyclable traits:
271 0
        self.reset_traits(self.recyclable_traits)
272

273 4
    def finish(self):
274
        """ Finishes disposing of a user interface.
275
        """
276

277
        # Reset the contents of the user interface
278 4
        self.reset(destroy=False)
279

280
        # Make sure that 'visible', 'enabled', and 'checked' handlers are not
281
        # called after the editor has been disposed:
282 4
        for object in self.context.values():
283 4
            object.on_trait_change(self._evaluate_when, remove=True)
284

285
        # Notify the handler that the view has been closed:
286 4
        self.handler.closed(self.info, self.result)
287

288
        # Clear the back-link from the UIInfo object to us:
289 4
        self.info.ui = None
290

291
        # Destroy the view control:
292 4
        self.control._object = None
293 4
        toolkit().destroy_control(self.control)
294 4
        self.control = None
295

296
        # Dispose of any KeyBindings object we reference:
297 4
        if self._key_bindings is not None:
298 4
            self._key_bindings.dispose()
299

300
        # Break the linkage to any objects in the context dictionary:
301 4
        self.context.clear()
302

303
        # Remove specified symbols from our dictionary to aid in clean-up:
304 4
        self.reset_traits(self.recyclable_traits)
305 4
        self.reset_traits(self.disposable_traits)
306

307 4
        self.destroyed = True
308

309 4
    def reset(self, destroy=True):
310
        """ Resets the contents of a user interface.
311
        """
312 4
        for editor in self._editors:
313 4
            if editor._ui is not None:
314
                # Propagate result to enclosed ui objects:
315 4
                editor._ui.result = self.result
316 4
            editor.dispose()
317

318
            # Zap the control. If there are pending events for the control in
319
            # the UI queue, the editor's '_update_editor' method will see that
320
            # the control is None and discard the update request:
321 4
            editor.control = None
322

323
        # Remove any statusbar listeners that have been set up:
324 4
        for object, handler, name in self._statusbar:
325 0
            object.on_trait_change(handler, name, remove=True)
326

327 4
        del self._statusbar[:]
328

329 4
        if destroy:
330 4
            toolkit().destroy_children(self.control)
331

332 4
        for dispatcher in self._dispatchers:
333 0
            dispatcher.remove()
334

335 4
    def find(self, include):
336
        """ Finds the definition of the specified Include object in the current
337
            user interface building context.
338
        """
339 4
        context = self.context
340 4
        result = None
341

342
        # Get the context 'object' (if available):
343 4
        if len(context) == 1:
344 0
            object = list(context.values())[0]
345
        else:
346 4
            object = context.get("object")
347

348
        # Try to use our ViewElements objects:
349 4
        ve = self.view_elements
350

351
        # If none specified, try to get it from the UI context:
352 4
        if (ve is None) and (object is not None):
353
            # Use the context object's ViewElements (if available):
354 0
            ve = object.trait_view_elements()
355

356
        # Ask the ViewElements to find the requested item for us:
357 4
        if ve is not None:
358 4
            result = ve.find(include.id, self._search)
359

360
        # If not found, then try to search the 'handler' and 'object' for a
361
        # method we can call that will define it:
362 4
        if result is None:
363 0
            handler = context.get("handler")
364 4
            if handler is not None:
365 0
                method = getattr(handler, include.id, None)
366 4
                if callable(method):
367 0
                    result = method()
368

369 4
            if (result is None) and (object is not None):
370 0
                method = getattr(object, include.id, None)
371 4
                if callable(method):
372 0
                    result = method()
373

374 4
        return result
375

376 4
    def push_level(self):
377
        """ Returns the current search stack level.
378
        """
379 4
        return len(self._search)
380

381 4
    def pop_level(self, level):
382
        """ Restores a previously pushed search stack level.
383
        """
384 4
        del self._search[: len(self._search) - level]
385

386 4
    def prepare_ui(self):
387
        """ Performs all processing that occurs after the user interface is
388
            created.
389
        """
390
        # Invoke all of the editor 'name_defined' methods we've accumulated:
391 4
        info = self.info.trait_set(initialized=False)
392 4
        for method in self._defined:
393 0
            method(info)
394

395
        # Then reset the list, since we don't need it anymore:
396 4
        del self._defined[:]
397

398
        # Synchronize all context traits with associated editor traits:
399 4
        self.sync_view()
400

401
        # Hook all keyboard events:
402 4
        toolkit().hook_events(self, self.control, "keys", self.key_handler)
403

404
        # Hook all events if the handler is an extended 'ViewHandler':
405 4
        handler = self.handler
406 4
        if isinstance(handler, ViewHandler):
407 0
            toolkit().hook_events(self, self.control)
408

409
        # Invoke the handler's 'init' method, and abort if it indicates
410
        # failure:
411 4
        if handler.init(info) == False:
412 0
            raise TraitError("User interface creation aborted")
413

414
        # For each Handler method whose name is of the form
415
        # 'object_name_changed', where 'object' is the name of an object in the
416
        # UI's 'context', create a trait notification handler that will call
417
        # the method whenever 'object's 'name' trait changes. Also invoke the
418
        # method immediately so initial user interface state can be correctly
419
        # set:
420 4
        context = self.context
421 4
        for name in self._each_trait_method(handler):
422 4
            if name[-8:] == "_changed":
423 4
                prefix = name[:-8]
424 4
                col = prefix.find("_", 1)
425 4
                if col >= 0:
426 4
                    object = context.get(prefix[:col])
427 4
                    if object is not None:
428 0
                        method = getattr(handler, name)
429 0
                        trait_name = prefix[col + 1:]
430 0
                        self._dispatchers.append(
431
                            Dispatcher(method, info, object, trait_name)
432
                        )
433 4
                        if object.base_trait(trait_name).type != "event":
434 0
                            method(info)
435

436
        # If there are any Editor object's whose 'visible', 'enabled' or
437
        # 'checked' state is controlled by a 'visible_when', 'enabled_when' or
438
        # 'checked_when' expression, set up an 'anytrait' changed notification
439
        # handler on each object in the 'context' that will cause the
440
        # 'visible', 'enabled' or 'checked' state of each affected Editor to be
441
        #  set. Also trigger the evaluation immediately, so the visible,
442
        # enabled or checked state of each Editor can be correctly initialized:
443 4
        if (len(self._visible) + len(self._enabled) + len(self._checked)) > 0:
444 4
            for object in context.values():
445 4
                object.on_trait_change(self._evaluate_when, dispatch="ui")
446 4
            self._do_evaluate_when(at_init=True)
447

448
        # Indicate that the user interface has been initialized:
449 4
        info.initialized = True
450

451 4
    def sync_view(self):
452
        """ Synchronize context object traits with view editor traits.
453
        """
454 4
        for name, object in self.context.items():
455 4
            self._sync_view(name, object, "sync_to_view", "from")
456 4
            self._sync_view(name, object, "sync_from_view", "to")
457 4
            self._sync_view(name, object, "sync_with_view", "both")
458

459 4
    def _sync_view(self, name, object, metadata, direction):
460 4
        info = self.info
461 4
        for trait_name, trait in object.traits(**{metadata: is_str}).items():
462 4
            for sync in getattr(trait, metadata).split(","):
463 0
                try:
464 4
                    editor_id, editor_name = [
465
                        item.strip() for item in sync.split(".")
466
                    ]
467 0
                except:
468 0
                    raise TraitError(
469
                        "The '%s' metadata for the '%s' trait in "
470
                        "the '%s' context object should be of the form: "
471
                        "'id1.trait1[,...,idn.traitn]."
472
                        % (metadata, trait_name, name)
473
                    )
474

475 0
                editor = getattr(info, editor_id, None)
476 4
                if editor is not None:
477 0
                    editor.sync_value(
478
                        "%s.%s" % (name, trait_name), editor_name, direction
479
                    )
480
                else:
481 0
                    raise TraitError(
482
                        "No editor with id = '%s' was found for "
483
                        "the '%s' metadata for the '%s' trait in the '%s' "
484
                        "context object."
485
                        % (editor_id, metadata, trait_name, name)
486
                    )
487

488 4
    def get_extended_value(self, name):
489
        """ Gets the current value of a specified extended trait name.
490
        """
491 4
        names = name.split(".")
492 4
        if len(names) > 1:
493 4
            value = self.context[names[0]]
494 4
            del names[0]
495
        else:
496 0
            value = self.context["object"]
497

498 4
        for name in names:
499 4
            value = getattr(value, name)
500

501 4
        return value
502

503 4
    def restore_prefs(self):
504
        """ Retrieves and restores any saved user preference information
505
        associated with the UI.
506
        """
507 4
        id = self.id
508 4
        if id != "":
509 4
            db = self.get_ui_db()
510 4
            if db is not None:
511 4
                try:
512 4
                    ui_prefs = db.get(id)
513 4
                    db.close()
514 4
                    return self.set_prefs(ui_prefs)
515 0
                except:
516 0
                    pass
517

518 4
        return None
519

520 4
    def set_prefs(self, prefs):
521
        """ Sets the values of user preferences for the UI.
522
        """
523 4
        if isinstance(prefs, dict):
524 4
            info = self.info
525 4
            for name in self._names:
526 4
                editor = getattr(info, name, None)
527 4
                if isinstance(editor, Editor) and (editor.ui is self):
528 4
                    editor_prefs = prefs.get(name)
529 4
                    if editor_prefs is not None:
530 4
                        editor.restore_prefs(editor_prefs)
531

532 4
            if self.key_bindings is not None:
533 4
                key_bindings = prefs.get("$")
534 4
                if key_bindings is not None:
535 4
                    self.key_bindings.merge(key_bindings)
536

537 4
            return prefs.get("")
538

539 4
        return None
540

541 4
    def save_prefs(self, prefs=None):
542
        """ Saves any user preference information associated with the UI.
543
        """
544 4
        if prefs is None:
545 4
            toolkit().save_window(self)
546 4
            return
547

548 4
        id = self.id
549 4
        if id != "":
550 4
            db = self.get_ui_db(mode="c")
551 4
            if db is not None:
552 4
                db[id] = self.get_prefs(prefs)
553 4
                db.close()
554

555 4
    def get_prefs(self, prefs=None):
556
        """ Gets the preferences to be saved for the user interface.
557
        """
558 4
        ui_prefs = {}
559 4
        if prefs is not None:
560 4
            ui_prefs[""] = prefs
561

562 4
        if self.key_bindings is not None:
563 4
            ui_prefs["$"] = self.key_bindings
564

565 4
        info = self.info
566 4
        for name in self._names:
567 4
            editor = getattr(info, name, None)
568 4
            if isinstance(editor, Editor) and (editor.ui is self):
569 4
                prefs = editor.save_prefs()
570 4
                if prefs is not None:
571 4
                    ui_prefs[name] = prefs
572

573 4
        return ui_prefs
574

575 4
    def get_ui_db(self, mode="r"):
576
        """ Returns a reference to the Traits UI preference database.
577
        """
578 4
        try:
579 4
            return shelve.open(
580
                os.path.join(traits_home(), "traits_ui"),
581
                flag=mode,
582
                protocol=-1,
583
            )
584 4
        except:
585 4
            return None
586

587 4
    def get_editors(self, name):
588
        """ Returns a list of editors for the given trait name.
589
        """
590 4
        return [editor for editor in self._editors if editor.name == name]
591

592 4
    def get_error_controls(self):
593
        """ Returns the list of editor error controls contained by the user
594
            interface.
595
        """
596 0
        controls = []
597 4
        for editor in self._editors:
598 0
            control = editor.get_error_control()
599 4
            if isinstance(control, list):
600 0
                controls.extend(control)
601
            else:
602 0
                controls.append(control)
603

604 0
        return controls
605

606 4
    def add_defined(self, method):
607
        """ Adds a Handler method to the list of methods to be called once the
608
            user interface has been constructed.
609
        """
610 0
        self._defined.append(method)
611

612 4
    def add_visible(self, visible_when, editor):
613
        """ Adds a conditionally enabled Editor object to the list of monitored
614
            'visible_when' objects.
615
        """
616 4
        try:
617 4
            self._visible.append(
618
                (compile(visible_when, "<string>", "eval"), editor)
619
            )
620 0
        except:
621 0
            pass
622
            # fixme: Log an error here...
623

624 4
    def add_enabled(self, enabled_when, editor):
625
        """ Adds a conditionally enabled Editor object to the list of monitored
626
            'enabled_when' objects.
627
        """
628 4
        try:
629 4
            self._enabled.append(
630
                (compile(enabled_when, "<string>", "eval"), editor)
631
            )
632 0
        except:
633 0
            pass
634
            # fixme: Log an error here...
635

636 4
    def add_checked(self, checked_when, editor):
637
        """ Adds a conditionally enabled (menu) Editor object to the list of
638
            monitored 'checked_when' objects.
639
        """
640 0
        try:
641 0
            self._checked.append(
642
                (compile(checked_when, "<string>", "eval"), editor)
643
            )
644 0
        except:
645 0
            pass
646
            # fixme: Log an error here...
647

648 4
    def do_undoable(self, action, *args, **kw):
649
        """ Performs an action that can be undone.
650
        """
651 4
        undoable = self._undoable
652 4
        try:
653 4
            if (undoable == -1) and (self.history is not None):
654 4
                self._undoable = self.history.now
655

656 4
            action(*args, **kw)
657
        finally:
658 4
            if undoable == -1:
659 4
                self._undoable = -1
660

661 4
    def route_event(self, event):
662
        """ Routes a "hooked" event to the correct handler method.
663
        """
664 0
        toolkit().route_event(self, event)
665

666 4
    def key_handler(self, event, skip=True):
667
        """ Handles key events.
668
        """
669 4
        key_bindings = self.key_bindings
670 4
        handled = (key_bindings is not None) and key_bindings.do(
671
            event, [], self.info, recursive=(self.parent is None)
672
        )
673

674 4
        if (not handled) and (self.parent is not None):
675 2
            handled = self.parent.key_handler(event, False)
676

677 4
        if (not handled) and skip:
678 4
            toolkit().skip_event(event)
679

680 4
        return handled
681

682 4
    def evaluate(self, function, *args, **kw_args):
683
        """ Evaluates a specified function in the UI's **context**.
684
        """
685 4
        if function is None:
686 4
            return None
687

688 4
        if callable(function):
689 0
            return function(*args, **kw_args)
690

691 0
        context = self.context.copy()
692 0
        context["ui"] = self
693 0
        context["handler"] = self.handler
694 0
        return eval(function, globals(), context)(*args, **kw_args)
695

696 4
    def eval_when(self, when, result=True):
697
        """ Evaluates an expression in the UI's **context** and returns the
698
            result.
699
        """
700 0
        context = self._get_context(self.context)
701 0
        try:
702 0
            result = eval(when, globals(), context)
703 0
        except:
704 0
            from traitsui.api import raise_to_debug
705

706 0
            raise_to_debug()
707

708 0
        del context["ui"]
709

710 0
        return result
711

712 4
    def _get_context(self, context):
713
        """ Gets the context to use for evaluating an expression.
714
        """
715 4
        name = "object"
716 4
        n = len(context)
717 4
        if (n == 2) and ("handler" in context):
718 4
            for name, value in context.items():
719 4
                if name != "handler":
720 4
                    break
721 4
        elif n == 1:
722 0
            name = list(context.keys())[0]
723

724 4
        value = context.get(name)
725 4
        if value is not None:
726 4
            context2 = value.trait_get()
727 4
            context2.update(context)
728
        else:
729 0
            context2 = context.copy()
730

731 4
        context2["ui"] = self
732

733 4
        return context2
734

735 4
    def _evaluate_when(self):
736
        """ Set the 'visible', 'enabled', and 'checked' states for all Editors
737
            controlled by a 'visible_when', 'enabled_when' or 'checked_when'
738
            expression.
739
        """
740 4
        self._do_evaluate_when(at_init=False)
741

742 4
    def _do_evaluate_when(self, at_init=False):
743
        """ Set the 'visible', 'enabled', and 'checked' states for all Editors.
744

745
        This function does the job of _evaluate_when. We define it here to
746
        work around the traits dispatching mechanism that automatically
747
        determines the number of arguments of a notification method.
748

749
        :attr:`at_init` is set to true when this function is called the first
750
        time at initialization. In that case, we want to force the state of
751
        the items to be set (normally it is set only if it changes).
752
        """
753 4
        self._evaluate_condition(self._visible, "visible", at_init)
754 4
        self._evaluate_condition(self._enabled, "enabled", at_init)
755 4
        self._evaluate_condition(self._checked, "checked", at_init)
756

757 4
    def _evaluate_condition(self, conditions, trait, at_init=False):
758
        """ Evaluates a list of (eval, editor) pairs and sets a specified trait
759
        on each editor to reflect the Boolean value of the expression.
760

761
        1) All conditions are evaluated
762
        2) The elements whose condition evaluates to False are updated
763
        3) The elements whose condition evaluates to True are updated
764

765
        E.g., we first make invisible all elements for which 'visible_when'
766
        evaluates to False, and then we make visible the ones
767
        for which 'visible_when' is True. This avoids mutually exclusive
768
        elements to be visible at the same time, and thus making a dialog
769
        unnecessarily large.
770

771
        The state of an editor is updated only when it changes, unless
772
        at_init is set to True.
773

774
        Parameters
775
        ----------
776
        conditions : list of (str, Editor) tuple
777
            A list of tuples, each formed by 1) a string that contains a
778
            condition that evaluates to either True or False, and
779
            2) the editor whose state depends on the condition
780

781
        trait : str
782
            The trait that is set by the condition.
783
            Either 'visible, 'enabled', or 'checked'.
784

785
        at_init : bool
786
            If False, the state of an editor is set only when it changes
787
            (e.g., a visible element would not be updated to visible=True
788
            again). If True, the state is always updated (used at
789
            initialization).
790
        """
791

792 4
        context = self._get_context(self.context)
793

794
        # list of elements that should be activated
795 4
        activate = []
796
        # list of elements that should be de-activated
797 4
        deactivate = []
798

799 4
        for when, editor in conditions:
800 4
            try:
801 4
                cond_value = eval(when, globals(), context)
802 4
                editor_state = getattr(editor, trait)
803

804
                # add to update lists only if at_init is True (called on
805
                # initialization), or if the editor state has to change
806

807 4
                if cond_value and (at_init or not editor_state):
808 4
                    activate.append(editor)
809

810 4
                if not cond_value and (at_init or editor_state):
811 4
                    deactivate.append(editor)
812

813 0
            except Exception:
814
                # catch errors in the validate_when expression
815 0
                from traitsui.api import raise_to_debug
816

817 0
                raise_to_debug()
818

819
        # update the state of the editors
820 4
        for editor in deactivate:
821 4
            setattr(editor, trait, False)
822 4
        for editor in activate:
823 4
            setattr(editor, trait, True)
824

825 4
    def _get__groups(self):
826
        """ Returns the top-level Groups for the view (after resolving
827
        Includes. (Implements the **_groups** property.)
828
        """
829 4
        if self._groups_cache is None:
830 4
            shadow_group = self.view.content.get_shadow(self)
831 4
            self._groups_cache = shadow_group.get_content()
832 4
            for item in self._groups_cache:
833 4
                if isinstance(item, Item):
834 0
                    self._groups_cache = [
835
                        ShadowGroup(
836
                            shadow=Group(*self._groups_cache),
837
                            content=self._groups_cache,
838
                            groups=1,
839
                        )
840
                    ]
841 0
                    break
842 4
        return self._groups_cache
843

844
    # -- Property Implementations ---------------------------------------------
845

846 4
    def _get_key_bindings(self):
847 4
        if self._key_bindings is None:
848
            # create a new key_bindings instance lazily
849

850 4
            view, context = self.view, self.context
851 4
            if (view is None) or (context is None):
852 4
                return None
853

854
            # Get the KeyBindings object to use:
855 4
            values = list(context.values())
856 4
            key_bindings = view.key_bindings
857 4
            if key_bindings is None:
858 4
                from .key_bindings import KeyBindings
859

860 4
                self._key_bindings = KeyBindings(controllers=values)
861
            else:
862 0
                self._key_bindings = key_bindings.clone(controllers=values)
863

864 4
        return self._key_bindings
865

866
    # -- Traits Event Handlers ------------------------------------------------
867

868 4
    def _updated_changed(self):
869 4
        if self.rebuild is not None:
870 0
            toolkit().rebuild_ui(self)
871

872 4
    def _title_changed(self):
873 4
        if self.control is not None:
874 0
            toolkit().set_title(self)
875

876 4
    def _icon_changed(self):
877 4
        if self.control is not None:
878 0
            toolkit().set_icon(self)
879

880 4
    @on_trait_change("parent, view, context")
881
    def _pvc_changed(self):
882 4
        parent = self.parent
883 4
        if (parent is not None) and (self.key_bindings is not None):
884
            # If we don't have our own history, use our parent's:
885 4
            if self.history is None:
886 4
                self.history = parent.history
887

888
            # Link our KeyBindings object as a child of our parent's
889
            # KeyBindings object (if any):
890 4
            if parent.key_bindings is not None:
891 4
                parent.key_bindings.children.append(self.key_bindings)
892

893

894 4
class Dispatcher(object):
895 4
    def __init__(self, method, info, object, method_name):
896
        """ Initializes the object.
897
        """
898 0
        self.method = method
899 0
        self.info = info
900 0
        self.object = object
901 0
        self.method_name = method_name
902 0
        object.on_trait_change(self.dispatch, method_name, dispatch="ui")
903

904 4
    def dispatch(self):
905
        """ Dispatches the method.
906
        """
907 0
        self.method(self.info)
908

909 4
    def remove(self):
910
        """ Removes the dispatcher.
911
        """
912 0
        self.object.on_trait_change(
913
            self.dispatch, self.method_name, remove=True
914
        )

Read our documentation on viewing source code .

Loading