예제 #1
0
파일: ui.py 프로젝트: timdiller/traitsui
class UI ( HasPrivateTraits ):
    """ Information about the user interface for a View.
    """

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    # The ViewElements object from which this UI resolves Include items
    view_elements = Instance( ViewElements )

    # Context objects that the UI is editing
    context = DictStrAny

    # Handler object used for event handling
    handler = Instance( Handler )

    # View template used to construct the user interface
    view = Instance( 'traitsui.view.View' )

    # Panel or dialog associated with the user interface
    control = Any

    # The parent UI (if any) of this UI
    parent = Instance( 'UI' )

    # Toolkit-specific object that "owns" **control**
    owner = Any

    # UIInfo object containing context or editor objects
    info = Instance( UIInfo )

    # Result from a modal or wizard dialog:
    result = Bool( False )

    # Undo and Redo history
    history = Any

    # The KeyBindings object (if any) for this UI:
    key_bindings = Property # Instance( KeyBindings )

    # The unique ID for this UI for persistence
    id = Str

    # Have any modifications been made to UI contents?
    modified = Bool( False )

    # Event when the user interface has changed
    updated = Event( Bool )

    # Title of the dialog, if any
    title = Str

    # The ImageResource of the icon, if any
    icon = Any

    # Should the created UI have scroll bars?
    scrollable = Bool( False )

    # The number of currently pending editor error conditions
    errors = Int

    # The code used to rebuild an updated user interface
    rebuild = Callable

    # Set to True when the UI has finished being destroyed.
    destroyed = Bool( False )

    #-- Private Traits ---------------------------------------------------------

    # Original context when used with a modal dialog
    _context = DictStrAny

    # Copy of original context used for reverting changes
    _revert = DictStrAny

    # List of methods to call once the user interface is created
    _defined = List

    # List of (visible_when,Editor) pairs
    _visible = List

    # List of (enabled_when,Editor) pairs
    _enabled = List

    # List of (checked_when,Editor) pairs
    _checked = List

    # Search stack used while building a user interface
    _search = List

    # List of dispatchable Handler methods
    _dispatchers = List

    # List of editors used to build the user interface
    _editors = List

    # List of names bound to the **info** object
    _names = List

    # Index of currently the active group in the user interface
    _active_group = Int

    # List of top-level groups used to build the user interface
    _groups = Property
    _groups_cache = Any

    # Count of levels of nesting for undoable actions
    _undoable = Int( -1 )

    # Code used to rebuild an updated user interface
    _rebuild = Callable

    # The statusbar listeners that have been set up:
    _statusbar = List

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

    # List of traits that are reset when a user interface is recycled
    # (i.e. rebuilt).
    recyclable_traits = [
        '_context', '_revert', '_defined', '_visible', '_enabled', '_checked',
        '_search', '_dispatchers', '_editors', '_names', '_active_group',
        '_undoable', '_rebuild', '_groups_cache'
    ]

    # List of additional traits that are discarded when a user interface is
    # disposed.
    disposable_traits = [
        'view_elements', 'info', 'handler', 'context', 'view', 'history',
        'key_bindings', 'icon', 'rebuild',
    ]

    #---------------------------------------------------------------------------
    #  Initializes the traits object:
    #---------------------------------------------------------------------------

    def traits_init ( self ):
        """ Initializes the traits object.
        """
        self.info = UIInfo( ui = self )
        self.handler.init_info( self.info )

    #---------------------------------------------------------------------------
    #  Creates a user interface from the associated View template object:
    #---------------------------------------------------------------------------

    def ui ( self, parent, kind ):
        """ Creates a user interface from the associated View template object.
        """
        if (parent is None) and (kind in kind_must_have_parent):
            kind = 'live'
        self.view.on_trait_change( self._updated_changed, 'updated',
                                   dispatch = 'ui' )
        self.rebuild = getattr( toolkit(), 'ui_' + kind )
        self.rebuild( self, parent )

    #---------------------------------------------------------------------------
    #  Disposes of the contents of a user interface:
    #---------------------------------------------------------------------------

    def dispose ( self, result = None, abort = False ):
        """ Disposes of the contents of a user interface.
        """
        if result is not None:
            self.result = result

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

            # Finish disposing of the user interface:
            self.finish()

    #---------------------------------------------------------------------------
    #  Recycles the user interface prior to rebuilding it:
    #---------------------------------------------------------------------------

    def recycle ( self ):
        """ Recycles the user interface prior to rebuilding it.
        """
        # Reset all user interface editors:
        self.reset( destroy = False )

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

        # Reset all recyclable traits:
        self.reset_traits( self.recyclable_traits )

    #---------------------------------------------------------------------------
    #  Finishes a user interface:
    #---------------------------------------------------------------------------

    def finish ( self ):
        """ Finishes disposing of a user interface.
        """

        # Reset the contents of the user interface
        self.reset( destroy = True )

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

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

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

        # Destroy the view control:
        self.control._object = None
        toolkit().destroy_control( self.control )
        self.control = None

        # Dispose of any KeyBindings object we reference:
        if self.key_bindings is not None:
            self.key_bindings.dispose()

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

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

        self.destroyed = True

    #---------------------------------------------------------------------------
    #  Resets the contents of the user interface:
    #---------------------------------------------------------------------------

    def reset ( self, destroy = True ):
        """ Resets the contents of a user interface.
        """
        for editor in self._editors:
            if editor._ui is not None:
                # Propagate result to enclosed ui objects:
                editor._ui.result = self.result
            editor.dispose()

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

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

        del self._statusbar[:]

        if destroy:
            toolkit().destroy_children( self.control )

        for dispatcher in self._dispatchers:
            dispatcher.remove()

    #---------------------------------------------------------------------------
    #  Find the definition of the specified Include object in the current user
    #  interface building context:
    #---------------------------------------------------------------------------

    def find ( self, include ):
        """ Finds the definition of the specified Include object in the current
            user interface building context.
        """
        context = self.context
        result  = None

        # Get the context 'object' (if available):
        if len( context ) == 1:
            object = context.values()[0]
        else:
            object = context.get( 'object' )

        # Try to use our ViewElements objects:
        ve = self.view_elements

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

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

        # If not found, then try to search the 'handler' and 'object' for a
        # method we can call that will define it:
        if result is None:
            handler = context.get( 'handler' )
            if handler is not None:
                method = getattr( handler, include.id, None )
                if callable( method ):
                    result = method()

            if (result is None) and (object is not None):
                method = getattr( object, include.id, None )
                if callable( method ):
                    result = method()

        return result

    #---------------------------------------------------------------------------
    #  Returns the current search stack level:
    #---------------------------------------------------------------------------

    def push_level ( self ):
        """ Returns the current search stack level.
        """
        return len( self._search )

    #---------------------------------------------------------------------------
    #  Restores a previously pushed search stack level:
    #---------------------------------------------------------------------------

    def pop_level ( self, level ):
        """ Restores a previously pushed search stack level.
        """
        del self._search[ : len( self._search ) - level ]

    #---------------------------------------------------------------------------
    #  Performs all post user interface creation processing:
    #---------------------------------------------------------------------------

    def prepare_ui ( self ):
        """ Performs all processing that occurs after the user interface is
            created.
        """
        # Invoke all of the editor 'name_defined' methods we've accumulated:
        info = self.info.set( initialized = False )
        for method in self._defined:
            method( info )

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

        # Synchronize all context traits with associated editor traits:
        self.sync_view()

        # Hook all keyboard events:
        toolkit().hook_events( self, self.control, 'keys', self.key_handler )

        # Hook all events if the handler is an extended 'ViewHandler':
        handler = self.handler
        if isinstance( handler, ViewHandler ):
            toolkit().hook_events( self, self.control )

        # Invoke the handler's 'init' method, and abort if it indicates failure:
        if handler.init( info ) == False:
            raise TraitError, 'User interface creation aborted'

        # For each Handler method whose name is of the form
        # 'object_name_changed', where 'object' is the name of an object in the
        # UI's 'context', create a trait notification handler that will call
        # the method whenever 'object's 'name' trait changes. Also invoke the
        # method immediately so initial user interface state can be correctly
        # set:
        context = self.context
        for name in self._each_trait_method( handler ):
            if name[-8:] == '_changed':
                prefix = name[:-8]
                col    = prefix.find( '_', 1 )
                if col >= 0:
                    object = context.get( prefix[ : col ] )
                    if object is not None:
                        method     = getattr( handler, name )
                        trait_name = prefix[ col + 1: ]
                        self._dispatchers.append( Dispatcher(
                             method, info, object, trait_name ) )
                        if object.base_trait( trait_name ).type != 'event':
                            method( info )

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

        # Indicate that the user interface has been initialized:
        info.initialized = True

    #---------------------------------------------------------------------------
    #  Synchronize context object traits with view editor traits:
    #---------------------------------------------------------------------------

    def sync_view ( self ):
        """ Synchronize context object traits with view editor traits.
        """
        for name, object in self.context.items():
            self._sync_view( name, object, 'sync_to_view',   'from' )
            self._sync_view( name, object, 'sync_from_view', 'to'   )
            self._sync_view( name, object, 'sync_with_view', 'both' )

    def _sync_view ( self, name, object, metadata, direction ):
        info = self.info
        for trait_name, trait in object.traits( **{metadata: is_str} ).items():
            for sync in getattr( trait, metadata ).split( ',' ):
                try:
                    editor_id, editor_name = [ item.strip()
                                               for item in sync.split( '.' ) ]
                except:
                    raise TraitError( "The '%s' metadata for the '%s' trait in "
                        "the '%s' context object should be of the form: "
                        "'id1.trait1[,...,idn.traitn]." %
                        ( metadata, trait_name, name ) )

                editor = getattr( info, editor_id, None )
                if editor is not None:
                    editor.sync_value( '%s.%s' % ( name, trait_name ),
                                       editor_name, direction )
                else:
                    raise TraitError( "No editor with id = '%s' was found for "
                        "the '%s' metadata for the '%s' trait in the '%s' "
                        "context object." %
                        ( editor_id,metadata, trait_name, name ) )

    #---------------------------------------------------------------------------
    #  Gets the current value of a specified extended trait name:
    #---------------------------------------------------------------------------

    def get_extended_value ( self, name ):
        """ Gets the current value of a specified extended trait name.
        """
        names = name.split( '.' )
        if len( names ) > 1:
            value = self.context[ names[0] ]
            del names[0]
        else:
            value = self.context[ 'object' ]

        for name in names:
            value = getattr( value, name )

        return value

    #---------------------------------------------------------------------------
    #  Restores any saved user preference information associated with the UI:
    #---------------------------------------------------------------------------

    def restore_prefs ( self ):
        """ Retrieves and restores any saved user preference information
        associated with the UI.
        """
        id = self.id
        if id != '':
            db = self.get_ui_db()
            if db is not None:
                try:
                    ui_prefs = db.get( id )
                    db.close()
                    return self.set_prefs( ui_prefs )
                except:
                    pass

        return None

    #---------------------------------------------------------------------------
    #  Restores user preference information for the UI:
    #---------------------------------------------------------------------------

    def set_prefs ( self, prefs ):
        """ Sets the values of user preferences for the UI.
        """
        if isinstance( prefs, dict ):
            info = self.info
            for name in self._names:
                editor = getattr( info, name, None )
                if isinstance( editor, Editor ) and (editor.ui is self):
                    editor_prefs = prefs.get( name )
                    if editor_prefs != None:
                        editor.restore_prefs( editor_prefs )

            if self.key_bindings is not None:
                key_bindings = prefs.get( '$' )
                if key_bindings is not None:
                    self.key_bindings.merge( key_bindings )

            return prefs.get( '' )

        return None

    #---------------------------------------------------------------------------
    #  Saves any user preference information associated with the UI:
    #---------------------------------------------------------------------------

    def save_prefs ( self, prefs = None ):
        """ Saves any user preference information associated with the UI.
        """
        if prefs is None:
            toolkit().save_window( self )
            return

        id = self.id
        if id != '':
            db = self.get_ui_db( mode = 'c' )
            if db is not None:
                db[ id ] = self.get_prefs( prefs )
                db.close()

    #---------------------------------------------------------------------------
    #  Gets the preferences to be saved for the user interface:
    #---------------------------------------------------------------------------

    def get_prefs ( self, prefs = None ):
        """ Gets the preferences to be saved for the user interface.
        """
        ui_prefs = {}
        if prefs is not None:
            ui_prefs[''] = prefs

        if self.key_bindings is not None:
            ui_prefs['$'] = self.key_bindings

        info = self.info
        for name in self._names:
            editor = getattr( info, name, None )
            if isinstance( editor, Editor ) and (editor.ui is self):
                prefs = editor.save_prefs()
                if prefs != None:
                    ui_prefs[ name ] = prefs

        return ui_prefs

    #---------------------------------------------------------------------------
    #  Gets a reference to the traits UI preference database:
    #---------------------------------------------------------------------------

    def get_ui_db ( self, mode = 'r' ):
        """ Returns a reference to the Traits UI preference database.
        """
        try:
            return shelve.open( os.path.join( traits_home(), 'traits_ui' ),
                                flag = mode, protocol = -1 )
        except:
            return None

    #---------------------------------------------------------------------------
    #  Returns a list of editors for the given trait name.
    #---------------------------------------------------------------------------

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

    #---------------------------------------------------------------------------
    #  Returns the list of editor error controls contained by the user
    #  interface:
    #---------------------------------------------------------------------------

    def get_error_controls ( self ):
        """ Returns the list of editor error controls contained by the user
            interface.
        """
        controls = []
        for editor in self._editors:
            control = editor.get_error_control()
            if isinstance( control, list ):
                controls.extend( control )
            else:
                controls.append( control )

        return controls

    #---------------------------------------------------------------------------
    #  Adds a Handler method to the list of methods to be called once the user
    #  interface has been constructed:
    #---------------------------------------------------------------------------

    def add_defined ( self, method ):
        """ Adds a Handler method to the list of methods to be called once the
            user interface has been constructed.
        """
        self._defined.append( method )

    #---------------------------------------------------------------------------
    #  Add's a conditionally enabled Editor object to the list of monitored
    #  'visible_when' objects:
    #---------------------------------------------------------------------------

    def add_visible ( self, visible_when, editor ):
        """ Adds a conditionally enabled Editor object to the list of monitored
            'visible_when' objects.
        """
        try:
            self._visible.append( ( compile( visible_when, '<string>', 'eval' ),
                                    editor ) )
        except:
            pass
            # fixme: Log an error here...

    #---------------------------------------------------------------------------
    #  Add's a conditionally enabled Editor object to the list of monitored
    #  'enabled_when' objects:
    #---------------------------------------------------------------------------

    def add_enabled ( self, enabled_when, editor ):
        """ Adds a conditionally enabled Editor object to the list of monitored
            'enabled_when' objects.
        """
        try:
            self._enabled.append( ( compile( enabled_when, '<string>', 'eval' ),
                                    editor ) )
        except:
            pass
            # fixme: Log an error here...

    #---------------------------------------------------------------------------
    #  Add's a conditionally checked (menu/toolbar) Editor object to the list of
    #  monitored 'checked_when' objects:
    #---------------------------------------------------------------------------

    def add_checked ( self, checked_when, editor ):
        """ Adds a conditionally enabled (menu) Editor object to the list of
            monitored 'checked_when' objects.
        """
        try:
            self._checked.append( ( compile( checked_when, '<string>', 'eval' ),
                                    editor ) )
        except:
            pass
            # fixme: Log an error here...

    #---------------------------------------------------------------------------
    #  Performs an 'undoable' action:
    #---------------------------------------------------------------------------

    def do_undoable ( self, action, *args, **kw ):
        """ Performs an action that can be undone.
        """
        undoable = self._undoable
        try:
            if (undoable == -1) and (self.history is not None):
                self._undoable = self.history.now

            action( *args, **kw )
        finally:
            if undoable == -1:
                self._undoable = -1

    #---------------------------------------------------------------------------
    #  Routes a 'hooked' event to the correct handler method:
    #---------------------------------------------------------------------------

    def route_event ( self, event ):
        """ Routes a "hooked" event to the correct handler method.
        """
        toolkit().route_event( self, event )

    #---------------------------------------------------------------------------
    #  Handles key events when the view has a set of KeyBindings:
    #---------------------------------------------------------------------------

    def key_handler ( self, event, skip = True ):
        """ Handles key events.
        """
        key_bindings = self.key_bindings
        handled      = ((key_bindings is not None) and
                         key_bindings.do( event, [], self.info,
                                          recursive = (self.parent is None) ))

        if (not handled) and (self.parent is not None):
            handled = self.parent.key_handler( event, False )

        if (not handled) and skip:
            toolkit().skip_event(event)

        return handled

    #---------------------------------------------------------------------------
    #  Evaluates a specified function in the UI's context:
    #---------------------------------------------------------------------------

    def evaluate ( self, function, *args, **kw_args ):
        """ Evaluates a specified function in the UI's **context**.
        """
        if function is None:
            return None

        if callable( function ):
            return function( *args, **kw_args )

        context = self.context.copy()
        context[ 'ui' ]      = self
        context[ 'handler' ] = self.handler
        return eval( function, globals(), context )( *args, **kw_args )

    #---------------------------------------------------------------------------
    #  Evaluates an expression in the UI's 'context' and returns the result:
    #---------------------------------------------------------------------------

    def eval_when ( self, when, result = True ):
        """ Evaluates an expression in the UI's **context** and returns the
            result.
        """
        context = self._get_context( self.context )
        try:
            result = eval( when, globals(), context )
        except:
            from traitsui.api import raise_to_debug
            raise_to_debug()

        del context[ 'ui' ]

        return result

    #---------------------------------------------------------------------------
    #  Gets the context to use for evaluating an expression:
    #---------------------------------------------------------------------------

    def _get_context ( self, context ):
        """ Gets the context to use for evaluating an expression.
        """
        name = 'object'
        n    = len( context )
        if (n == 2) and ('handler' in context):
            for name, value in context.items():
                if name != 'handler':
                    break
        elif n == 1:
            name = context.keys()[0]

        value = context.get( name )
        if value is not None:
            context2 = value.trait_get()
            context2.update( context )
        else:
            context2 = context.copy()

        context2['ui'] = self

        return context2

    #---------------------------------------------------------------------------
    #  Sets the 'visible', 'enabled' and/or 'checked' state for all Editors
    #  controlled by a 'visible_when', 'enabled_when' or 'checked_when'
    #  expression:
    #---------------------------------------------------------------------------

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


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

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

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


    #---------------------------------------------------------------------------
    #  Evaluates a list of ( eval, editor ) pairs and sets a specified trait on
    #  each editor to reflect the boolean truth of the expression evaluated:
    #---------------------------------------------------------------------------

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

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

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

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

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

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

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

        context = self._get_context( self.context )


        # list of elements that should be activated
        activate = []
        # list of elements that should be de-activated
        deactivate = []

        for when, editor in conditions:
            try:
                cond_value = eval(when, globals(), context)
                editor_state = getattr(editor, trait)

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

                if cond_value and (at_init or not editor_state):
                    activate.append(editor)

                if not cond_value and (at_init or editor_state):
                    deactivate.append(editor)

            except Exception:
                # catch errors in the validate_when expression
                from traitsui.api import raise_to_debug
                raise_to_debug()

        # update the state of the editors
        for editor in deactivate:
            setattr(editor, trait, False)
        for editor in activate:
            setattr(editor, trait, True)


    #---------------------------------------------------------------------------
    #  Implementation of the '_groups' property:
    #  (Returns the top-level Groups for the view (after resolving Includes))
    #---------------------------------------------------------------------------

    def _get__groups ( self ):
        """ Returns the top-level Groups for the view (after resolving
        Includes. (Implements the **_groups** property.)
        """
        if self._groups_cache is None:
            shadow_group       = self.view.content.get_shadow( self )
            self._groups_cache = shadow_group.get_content()
            for item in self._groups_cache:
                if isinstance( item, Item ):
                    self._groups_cache = [
                        ShadowGroup( shadow  = Group( *self._groups_cache ),
                                     content = self._groups_cache,
                                     groups  = 1 )
                    ]
                    break
        return self._groups_cache

    #-- Property Implementations -----------------------------------------------

    @property_depends_on( 'view, context' )
    def _get_key_bindings ( self ):
        view, context = self.view, self.context
        if (view is None) or (context is None):
            return None

        # Get the KeyBindings object to use:
        values       = context.values()
        key_bindings = view.key_bindings
        if key_bindings is None:
            from .key_bindings import KeyBindings

            return KeyBindings( controllers = values)

        return key_bindings.clone( controllers = values )

    #-- Traits Event Handlers --------------------------------------------------

    def _updated_changed ( self ):
        if self.rebuild is not None:
            toolkit().rebuild_ui( self )

    def _title_changed ( self ):
        if self.control is not None:
            toolkit().set_title( self )

    def _icon_changed ( self ):
        if self.control is not None:
            toolkit().set_icon( self )

    @on_trait_change( 'parent, view, context' )
    def _pvc_changed ( self ):
        parent = self.parent
        if (parent is not None) and (self.key_bindings is not None):
            # If we don't have our own history, use our parent's:
            if self.history is None:
                self.history = parent.history

            # Link our KeyBindings object as a child of our parent's
            # KeyBindings object (if any):
            if parent.key_bindings is not None:
                parent.key_bindings.children.append( self.key_bindings )
예제 #2
0
class TasksApplication(Application):
    """The entry point for an Envisage Tasks application.

    This class handles the common case for Tasks applications and is
    intended to be subclassed to modify its start/stop behavior, etc.

    """

    # Extension point IDs.
    TASK_FACTORIES = "envisage.ui.tasks.tasks"
    TASK_EXTENSIONS = "envisage.ui.tasks.task_extensions"

    # Pickle protocol to use for persisting layout information. Subclasses may
    # want to increase this, depending on their compatibility needs. Protocol
    # version 2 is safe for Python >= 2.3. Protocol version 4 is safe for
    # Python >= 3.4.
    layout_save_protocol = Int(2)

    #### 'TasksApplication' interface #########################################

    # The active task window (the last one to get focus).
    active_window = Instance("envisage.ui.tasks.task_window.TaskWindow")

    # The Pyface GUI for the application.
    gui = Instance("pyface.gui.GUI")

    # Icon for the whole application. Will be used to override all taskWindows
    # icons to have the same.
    icon = Instance("pyface.image_resource.ImageResource", allow_none=True)

    # The name of the application (also used on window title bars).
    name = Str

    # The splash screen for the application. By default, there is no splash
    # screen.
    splash_screen = Instance("pyface.splash_screen.SplashScreen")

    # The directory on the local file system used to persist window layout
    # information.
    state_location = Directory

    # The filename that the application uses to persist window layout
    # information.
    state_filename = Str(DEFAULT_STATE_FILENAME)

    # Contributed task factories. This attribute is primarily for run-time
    # inspection; to instantiate a task, use the 'create_task' method.
    task_factories = ExtensionPoint(id=TASK_FACTORIES)

    # Contributed task extensions.
    task_extensions = ExtensionPoint(id=TASK_EXTENSIONS)

    # The list of task windows created by the application.
    windows = List(Instance("envisage.ui.tasks.task_window.TaskWindow"))

    # The factory for creating task windows.
    window_factory = Callable

    #### Application layout ###################################################

    # The default layout for the application. If not specified, a single window
    # will be created with the first available task factory.
    default_layout = List(
        Instance("pyface.tasks.task_window_layout.TaskWindowLayout"))

    # Whether to always apply the default *application level* layout when the
    # application is started. Even if this is True, the layout state of
    # individual tasks will be restored.
    always_use_default_layout = Bool(False)

    #### Application lifecycle events #########################################

    # Fired after the initial windows have been created and the GUI event loop
    # has been started.
    application_initialized = Event

    # Fired immediately before the extant windows are destroyed and the GUI
    # event loop is terminated.
    application_exiting = Event

    # Fired when a task window has been created.
    window_created = Event(
        Instance("envisage.ui.tasks.task_window_event.TaskWindowEvent"))

    # Fired when a task window is opening.
    window_opening = Event(
        Instance(
            "envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent"))

    # Fired when a task window has been opened.
    window_opened = Event(
        Instance("envisage.ui.tasks.task_window_event.TaskWindowEvent"))

    # Fired when a task window is closing.
    window_closing = Event(
        Instance(
            "envisage.ui.tasks.task_window_event.VetoableTaskWindowEvent"))

    # Fired when a task window has been closed.
    window_closed = Event(
        Instance("envisage.ui.tasks.task_window_event.TaskWindowEvent"))

    #### Protected interface ##################################################

    # An 'explicit' exit is when the the 'exit' method is called.
    # An 'implicit' exit is when the user closes the last open window.
    _explicit_exit = Bool(False)

    # Application state.
    _state = Instance(
        "envisage.ui.tasks.tasks_application.TasksApplicationState")

    ###########################################################################
    # 'IApplication' interface.
    ###########################################################################

    def run(self):
        """ Run the application.

        Returns
        -------
        bool
            Whether the application started successfully (i.e., without a
            veto).
        """
        # Make sure the GUI has been created (so that, if required, the splash
        # screen is shown).
        gui = self.gui

        started = self.start()
        if started:
            # Create windows from the default or saved application layout.
            self._create_windows()

            # Start the GUI event loop.
            gui.set_trait_later(self, "application_initialized", self)
            gui.start_event_loop()

        return started

    ###########################################################################
    # 'TasksApplication' interface.
    ###########################################################################

    def create_task(self, id):
        """ Creates the Task with the specified ID.

        Returns
        -------
        pyface.tasks.task.Task
            The new Task, or None if there is not a suitable TaskFactory.
        """
        # Get the factory for the task.
        factory = self._get_task_factory(id)
        if factory is None:
            return None

        # Create the task using suitable task extensions.
        extensions = [
            ext for ext in self.task_extensions
            if ext.task_id == id or not ext.task_id
        ]
        task = factory.create_with_extensions(extensions)
        task.id = factory.id
        return task

    def create_window(self, layout=None, restore=True, **traits):
        """Creates a new TaskWindow, possibly with some Tasks.

        Parameters
        ----------
        layout : TaskWindowLayout, optional
             The layout to use for the window. The tasks described in
             the layout will be created and added to the window
             automatically. If not specified, the window will contain
             no tasks.

        restore : bool, optional (default True)
             If set, the application will restore old size and
             positions for the window and its panes, if possible. If a
             layout is not provided, this parameter has no effect.

        **traits : dict, optional
             Additional parameters to pass to ``window_factory()``
             when creating the TaskWindow.

        Returns
        -------
        envisage.ui.tasks.task_window.TaskWindow
            The new TaskWindow.

        """
        from .task_window_event import TaskWindowEvent
        from pyface.tasks.task_window_layout import TaskWindowLayout

        window = self.window_factory(application=self, **traits)

        # Listen for the window events.
        window.on_trait_change(self._on_window_activated, "activated")
        window.on_trait_change(self._on_window_opening, "opening")
        window.on_trait_change(self._on_window_opened, "opened")
        window.on_trait_change(self._on_window_closing, "closing")
        window.on_trait_change(self._on_window_closed, "closed")

        # Event notification.
        self.window_created = TaskWindowEvent(window=window)

        if layout:
            # Create and add tasks.
            for task_id in layout.get_tasks():
                task = self.create_task(task_id)
                if task:
                    window.add_task(task)
                else:
                    logger.error("Missing factory for task with ID %r",
                                 task_id)

            # Apply a suitable layout.
            if restore:
                layout = self._restore_layout_from_state(layout)
        else:
            # Create an empty layout to set default size and position only
            layout = TaskWindowLayout()

        window.set_window_layout(layout)

        return window

    def exit(self, force=False):
        """Exits the application, closing all open task windows.

        Each window is sent a veto-able closing event. If any window vetoes the
        close request, no window will be closed. Otherwise, all windows will be
        closed and the GUI event loop will terminate.

        This method is not called when the user clicks the close
        button on a window or otherwise closes a window through his or
        her window manager. It is only called via the File->Exit menu
        item. It can also, of course, be called programatically.

        Parameters
        ----------
        force : bool, optional (default False)
            If set, windows will receive no closing events and will be
            destroyed unconditionally. This can be useful for reliably
            tearing down regression tests, but should be used with
            caution.

        Returns
        -------
        bool
            A boolean indicating whether the application exited.

        """
        self._explicit_exit = True
        try:
            if not force:
                for window in reversed(self.windows):
                    window.closing = event = Vetoable()
                    if event.veto:
                        return False

            self._prepare_exit()
            for window in reversed(self.windows):
                window.destroy()
                window.closed = True
        finally:
            self._explicit_exit = False
        return True

    ###########################################################################
    # Protected interface.
    ###########################################################################

    def _create_windows(self):
        """ Called at startup to create TaskWindows from the default or saved
            application layout.
        """
        # Build a list of TaskWindowLayouts.
        self._load_state()
        if (self.always_use_default_layout
                or not self._state.previous_window_layouts):
            window_layouts = self.default_layout
        else:
            # Choose the stored TaskWindowLayouts, but only if all the task IDs
            # are still valid.
            window_layouts = self._state.previous_window_layouts
            for layout in window_layouts:
                for task_id in layout.get_tasks():
                    if not self._get_task_factory(task_id):
                        logger.warning("Saved application layout references "
                                       "non-existent task %r. Falling back to "
                                       "default application layout." % task_id)
                        window_layouts = self.default_layout
                        break
                else:
                    continue
                break

        # Create a TaskWindow for each TaskWindowLayout.
        for window_layout in window_layouts:
            if self.always_use_default_layout:
                window = self.create_window(window_layout, restore=False)
            else:
                window = self.create_window(window_layout, restore=True)
            window.open()

    def _get_task_factory(self, id):
        """ Returns the TaskFactory with the specified ID, or None.
        """
        for factory in self.task_factories:
            if factory.id == id:
                return factory
        return None

    def _prepare_exit(self):
        """ Called immediately before the extant windows are destroyed and the
            GUI event loop is terminated.
        """
        self.application_exiting = self
        self._save_state()

    def _load_state(self):
        """ Loads saved application state, if possible.
        """
        state = TasksApplicationState()
        filename = os.path.join(self.state_location, self.state_filename)
        if os.path.exists(filename):
            # Attempt to unpickle the saved application state.
            logger.debug("Loading application state from %s", filename)
            try:
                with open(filename, "rb") as f:
                    restored_state = pickle.load(f)
            except Exception:
                # If anything goes wrong, log the error and continue.
                logger.exception("Error while restoring application state")
            else:
                if state.version == restored_state.version:
                    state = restored_state
                    logger.debug("Application state successfully restored")
                else:
                    logger.warning(
                        "Discarding outdated application state: "
                        "expected version %s, got version %s",
                        state.version,
                        restored_state.version,
                    )
        else:
            logger.debug("No saved application state found at %s", filename)

        self._state = state

    def _restore_layout_from_state(self, layout):
        """ Restores an equivalent layout from saved application state.
        """
        # First, see if a window layout matches exactly.
        match = self._state.get_equivalent_window_layout(layout)
        if match:
            # The active task is not part of the equivalency relation, so we
            # ensure that it is correct.
            match.active_task = layout.get_active_task()
            layout = match

        # If that fails, at least try to restore the layout of
        # individual tasks.
        else:
            layout = layout.clone_traits()
            for i, item in enumerate(layout.items):
                id = item if isinstance(item, str) else item.id
                match = self._state.get_task_layout(id)
                if match:
                    layout.items[i] = match

        return layout

    def _save_state(self):
        """ Saves the application state.
        """
        # Grab the current window layouts.
        window_layouts = [w.get_window_layout() for w in self.windows]
        self._state.previous_window_layouts = window_layouts

        # Attempt to pickle the application state.
        filename = os.path.join(self.state_location, self.state_filename)
        logger.debug("Saving application state to %s", filename)
        try:
            with open(filename, "wb") as f:
                pickle.dump(self._state, f, protocol=self.layout_save_protocol)
        except Exception:
            # If anything goes wrong, log the error and continue.
            logger.exception("Error while saving application state")
        else:
            logger.debug("Application state successfully saved")

    #### Trait initializers ###################################################

    def _window_factory_default(self):
        from envisage.ui.tasks.task_window import TaskWindow

        return TaskWindow

    def _default_layout_default(self):
        from pyface.tasks.task_window_layout import TaskWindowLayout

        window_layout = TaskWindowLayout()
        if self.task_factories:
            window_layout.items = [self.task_factories[0].id]
        return [window_layout]

    def _gui_default(self):
        from pyface.gui import GUI

        return GUI(splash_screen=self.splash_screen)

    def _state_location_default(self):
        state_location = os.path.join(ETSConfig.application_home, "tasks",
                                      ETSConfig.toolkit)
        if not os.path.exists(state_location):
            os.makedirs(state_location)

        logger.debug("Tasks state location is %s", state_location)

        return state_location

    #### Trait change handlers ################################################

    def _on_window_activated(self, window, trait_name, event):
        self.active_window = window

    def _on_window_opening(self, window, trait_name, event):
        from .task_window_event import VetoableTaskWindowEvent

        # Event notification.
        self.window_opening = window_event = VetoableTaskWindowEvent(
            window=window)

        if window_event.veto:
            event.veto = True

    def _on_window_opened(self, window, trait_name, event):
        from .task_window_event import TaskWindowEvent

        self.windows.append(window)

        # Event notification.
        self.window_opened = TaskWindowEvent(window=window)

    def _on_window_closing(self, window, trait_name, event):
        from .task_window_event import VetoableTaskWindowEvent

        # Event notification.
        self.window_closing = window_event = VetoableTaskWindowEvent(
            window=window)

        if window_event.veto:
            event.veto = True
        else:
            # Store the layout of the window.
            window_layout = window.get_window_layout()
            self._state.push_window_layout(window_layout)

            # If we're exiting implicitly and this is the last window, save
            # state, because we won't get another chance.
            if len(self.windows) == 1 and not self._explicit_exit:
                self._prepare_exit()

    def _on_window_closed(self, window, trait_name, event):
        from .task_window_event import TaskWindowEvent

        self.windows.remove(window)

        # Event notification.
        self.window_closed = TaskWindowEvent(window=window)

        # Was this the last window?
        if len(self.windows) == 0:
            self.stop()
예제 #3
0
class _ListStrEditor(Editor):
    """ Traits UI editor for editing lists of strings.
    """

    # -------------------------------------------------------------------------
    #  Trait definitions:
    # -------------------------------------------------------------------------

    # The list view control associated with the editor:
    list_view = Any()

    #: The list model associated the editor:
    model = Instance(ListStrModel)

    #: The title of the editor:
    title = Str()

    #: The current set of selected items (which one is used depends upon the
    #: initial state of the editor factory 'multi_select' trait):
    selected = Any()
    multi_selected = List()

    #: The current set of selected item indices (which one is used depends upon
    #: the initial state of the editor factory 'multi_select' trait):
    selected_index = Int(-1)
    multi_selected_indices = List(Int)

    #: The most recently actived item and its index.
    #: Always trigger change notification.
    activated = Any(comparison_mode=NO_COMPARE)
    activated_index = Int(comparison_mode=NO_COMPARE)

    #: The most recently right_clicked item and its index:
    right_clicked = Event()
    right_clicked_index = Event()

    #: Is the list editor scrollable? This value overrides the default.
    scrollable = True

    #: Should the selected item be edited after rebuilding the editor list:
    edit = Bool(False)

    #: The adapter from list items to editor values:
    adapter = Instance(ListStrAdapter)

    #: Dictionary mapping image names to QIcons
    images = Any({})

    #: Dictionary mapping ImageResource objects to QIcons
    image_resources = Any({})

    #: The current number of item currently in the list:
    item_count = Property()

    #: The current search string:
    search = Str()

    # -------------------------------------------------------------------------
    #  Editor interface:
    # -------------------------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        factory = self.factory

        # Set up the adapter to use:
        self.adapter = factory.adapter
        self.sync_value(factory.adapter_name, "adapter", "from")

        # Create the list model and accompanying controls:
        self.model = ListStrModel(editor=self)

        self.control = QtGui.QWidget()
        layout = QtGui.QVBoxLayout(self.control)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        if factory.title or factory.title_name:
            header_view = QtGui.QHeaderView(QtCore.Qt.Horizontal, self.control)
            header_view.setModel(self.model)
            header_view.setMaximumHeight(header_view.sizeHint().height())
            if is_qt5:
                header_view.setSectionResizeMode(QtGui.QHeaderView.Stretch)
            else:
                header_view.setResizeMode(QtGui.QHeaderView.Stretch)
            layout.addWidget(header_view)

        self.list_view = _ListView(self)
        layout.addWidget(self.list_view)

        # Set up the list control's event handlers:
        if factory.multi_select:
            slot = self._on_rows_selection
        else:
            slot = self._on_row_selection
        selection_model = self.list_view.selectionModel()
        selection_model.selectionChanged.connect(slot)

        self.list_view.activated.connect(self._on_activate)

        # Initialize the editor title:
        self.title = factory.title
        self.sync_value(factory.title_name, "title", "from")

        # Set up the selection listener
        if factory.multi_select:
            self.sync_value(
                factory.selected, "multi_selected", "both", is_list=True
            )
            self.sync_value(
                factory.selected_index,
                "multi_selected_indices",
                "both",
                is_list=True,
            )
        else:
            self.sync_value(factory.selected, "selected", "both")
            self.sync_value(factory.selected_index, "selected_index", "both")

        # Synchronize other interesting traits as necessary:
        self.sync_value(factory.activated, "activated", "to")
        self.sync_value(factory.activated_index, "activated_index", "to")

        self.sync_value(factory.right_clicked, "right_clicked", "to")
        self.sync_value(
            factory.right_clicked_index, "right_clicked_index", "to"
        )

        # Make sure we listen for 'items' changes as well as complete list
        # replacements:
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", dispatch="ui"
        )

        # Create the mapping from user supplied images to QIcons:
        for image_resource in factory.images:
            self._add_image(image_resource)

        # Refresh the editor whenever the adapter changes:
        self.on_trait_change(
            self.refresh_editor, "adapter.+update", dispatch="ui"
        )

        # Set the list control's tooltip:
        self.set_tooltip()

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        self.context_object.on_trait_change(
            self.update_editor, self.extended_name + "_items", remove=True
        )

        self.on_trait_change(
            self.refresh_editor, "adapter.+update", remove=True
        )

        super(Editor, self).dispose()

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        if not self._no_update:
            self.model.beginResetModel()
            self.model.endResetModel()
            # restore selection back
            if self.factory.multi_select:
                self._multi_selected_changed(self.multi_selected)
            else:
                self._selected_changed(self.selected)

    # -------------------------------------------------------------------------
    #  ListStrEditor interface:
    # -------------------------------------------------------------------------

    def refresh_editor(self):
        """ Requests that the underlying list widget to redraw itself.
        """
        self.list_view.viewport().update()

    def callx(self, func, *args, **kw):
        """ Call a function without allowing the editor to update.
        """
        old = self._no_update
        self._no_update = True
        try:
            func(*args, **kw)
        finally:
            self._no_update = old

    def setx(self, **keywords):
        """ Set one or more attributes without allowing the editor to update.
        """
        old = self._no_notify
        self._no_notify = True
        try:
            for name, value in keywords.items():
                setattr(self, name, value)
        finally:
            self._no_notify = old

    def get_image(self, image):
        """ Converts a user specified image to a QIcon.
        """
        if isinstance(image, ImageResource):
            result = self.image_resources.get(image)
            if result is not None:
                return result

            return self._add_image(image)

        return self.images.get(image)

    def is_auto_add(self, index):
        """ Returns whether or not the index is the special 'auto add' item at
            the end of the list.
        """
        return self.factory.auto_add and (
            index >= self.adapter.len(self.object, self.name)
        )

    # -------------------------------------------------------------------------
    #  Private interface:
    # -------------------------------------------------------------------------

    def _add_image(self, image_resource):
        """ Adds a new image to the image map.
        """
        image = image_resource.create_icon()

        self.image_resources[image_resource] = image
        self.images[image_resource.name] = image

        return image

    # -- Property Implementations ---------------------------------------------

    def _get_item_count(self):
        return self.model.rowCount(None) - self.factory.auto_add

    # -- Trait Event Handlers -------------------------------------------------

    def _selected_changed(self, selected):
        """ Handles the editor's 'selected' trait being changed.
        """
        if not self._no_update:
            try:
                selected_index = self.value.index(selected)
            except ValueError:
                pass
            else:
                self._selected_index_changed(selected_index)

    def _selected_index_changed(self, selected_index):
        """ Handles the editor's 'selected_index' trait being changed.
        """
        if not self._no_update:
            smodel = self.list_view.selectionModel()
            if selected_index == -1:
                smodel.clearSelection()
            else:
                mi = self.model.index(selected_index)
                smodel.select(mi, QtGui.QItemSelectionModel.ClearAndSelect)
                self.list_view.scrollTo(mi)

    def _multi_selected_changed(self, selected):
        """ Handles the editor's 'multi_selected' trait being changed.
        """
        if not self._no_update:
            indices = []
            for item in selected:
                try:
                    indices.append(self.value.index(item))
                except ValueError:
                    pass
            self._multi_selected_indices_changed(indices)

    def _multi_selected_items_changed(self, event):
        """ Handles the editor's 'multi_selected' trait being modified.
        """
        if not self._no_update:
            try:
                added = [self.value.index(item) for item in event.added]
                removed = [self.value.index(item) for item in event.removed]
            except ValueError:
                pass
            else:
                event = TraitListEvent(
                    index=0,
                    added=added,
                    removed=removed
                )
                self._multi_selected_indices_items_changed(event)

    def _multi_selected_indices_changed(self, selected_indices):
        """ Handles the editor's 'multi_selected_indices' trait being changed.
        """
        if not self._no_update:
            smodel = self.list_view.selectionModel()
            smodel.clearSelection()
            for selected_index in selected_indices:
                smodel.select(
                    self.model.index(selected_index),
                    QtGui.QItemSelectionModel.Select,
                )
            if selected_indices:
                self.list_view.scrollTo(self.model.index(selected_indices[-1]))

    def _multi_selected_indices_items_changed(self, event):
        """ Handles the editor's 'multi_selected_indices' trait being modified.
        """
        if not self._no_update:
            smodel = self.list_view.selectionModel()
            for selected_index in event.removed:
                smodel.select(
                    self.model.index(selected_index),
                    QtGui.QItemSelectionModel.Deselect,
                )
            for selected_index in event.added:
                smodel.select(
                    self.model.index(selected_index),
                    QtGui.QItemSelectionModel.Select,
                )

    # -- List Control Event Handlers ------------------------------------------

    def _on_activate(self, mi):
        """ Handle a cell being activated.
        """
        self.activated_index = index = mi.row()
        self.activated = self.adapter.get_item(self.object, self.name, index)

    def _on_context_menu(self, point):
        """ Handle a context menu request.
        """
        mi = self.list_view.indexAt(point)
        if mi.isValid():
            self.right_clicked_index = index = mi.row()
            self.right_clicked = self.adapter.get_item(
                self.object, self.name, index
            )

    def _on_row_selection(self, added, removed):
        """ Handle the row selection being changed.
        """
        self._no_update = True
        try:
            indices = self.list_view.selectionModel().selectedRows()
            if len(indices):
                self.selected_index = indices[0].row()
                self.selected = self.adapter.get_item(
                    self.object, self.name, self.selected_index
                )
            else:
                self.selected_index = -1
                self.selected = None
        finally:
            self._no_update = False

    def _on_rows_selection(self, added, removed):
        """ Handle the rows selection being changed.
        """
        self._no_update = True
        try:
            indices = self.list_view.selectionModel().selectedRows()
            self.multi_selected_indices = indices = [i.row() for i in indices]
            self.multi_selected = [
                self.adapter.get_item(self.object, self.name, i)
                for i in self.multi_selected_indices
            ]
        finally:
            self._no_update = False

    def _on_context_menu(self, pos):
        menu = self.factory.menu

        index = self.list_view.indexAt(pos).row()

        if isinstance(menu, str):
            menu = getattr(self.object, menu, None)

        if isinstance(menu, collections.Callable):
            menu = menu(index)

        if menu is not None:
            qmenu = menu.create_menu(self.list_view, self)

            self._menu_context = {
                "selection": self.object,
                "object": self.object,
                "editor": self,
                "index": index,
                "info": self.ui.info,
                "handler": self.ui.handler,
            }

            qmenu.exec_(self.list_view.mapToGlobal(pos))

            self._menu_context = None
예제 #4
0
class ActionManager(HasTraits):
    """ Abstract base class for all action managers.

    An action manager contains a list of groups, with each group containing a
    list of items.

    There are currently three concrete sub-classes:

    1) 'MenuBarManager'
    2) 'MenuManager'
    3) 'ToolBarManager'

    """

    # 'ActionManager' interface --------------------------------------------

    #: The Id of the default group.
    DEFAULT_GROUP = Constant("additions")

    #: The action controller (if any) used to control how actions are performed.
    controller = Instance(ActionController)

    #: Is the action manager enabled?
    enabled = Bool(True)

    #: All of the contribution groups in the manager.
    groups = Property(List(Group))

    #: The manager's unique identifier (if it has one).
    id = Str()

    #: Is the action manager visible?
    visible = Bool(True)

    # Events ----

    #: fixme: We probably need more granular events than this!
    changed = Event()

    # Private interface ----------------------------------------------------

    #: All of the contribution groups in the manager.
    _groups = List(Group)

    # ------------------------------------------------------------------------
    # 'object' interface.
    # ------------------------------------------------------------------------

    def __init__(self, *args, **traits):
        """ Creates a new action manager.

        Parameters
        ----------
        args : collection of strings, Group instances, or ActionManagerItem instances
            Positional arguments are interpreted as Items or Groups managed
            by the action manager.

        Notes
        -----

        If a Group is passed as a positional agrument then it is added to the
        manager and any subsequent Items arguments are appended to the Group
        until another Group is encountered.

        If a string is passed, a Group is created with id set to the string.
        """
        # Base class constructor.
        super(ActionManager, self).__init__(**traits)

        # The last group in every manager is the group with Id 'additions'.
        #
        # fixme: The side-effect of this is to ensure that the 'additions'
        # group has been created.  Is the 'additions' group even a good idea?
        group = self._get_default_group()

        # Add all items to the manager.
        for arg in args:
            # We allow a group to be defined by simply specifying a string (its
            # Id).
            if isinstance(arg, str):
                # Create a group with the specified Id.
                arg = Group(id=arg)

            # If the item is a group then add it just before the default group
            # (ie. we always keep the default group as the last group in the
            # manager).
            if isinstance(arg, Group):
                self.insert(-1, arg)
                group = arg

            # Otherwise, the item is an action manager item so add it to the
            # current group.
            else:
                ##                 # If no group has been created then add one.  This is only
                ##                 # relevant when using the 'shorthand' way to define menus.
                ##                 if group is None:
                ##                     group = Group(id='__first__')
                ##                     self.insert(-1, group)

                group.append(arg)

    # ------------------------------------------------------------------------
    # 'ActionManager' interface.
    # ------------------------------------------------------------------------

    # Trait properties -----------------------------------------------------

    def _get_groups(self):
        return self._groups[:]

    # Trait change handlers ------------------------------------------------

    def _enabled_changed(self, trait_name, old, new):
        for group in self._groups:
            group.enabled = new

    def _visible_changed(self, trait_name, old, new):
        for group in self._groups:
            group.visible = new

    # Methods -------------------------------------------------------------#

    def append(self, item):
        """ Append an item to the manager.

        Parameters
        ----------
        item : string, Group instance or ActionManagerItem instance
            The item to append.

        Notes
        -----

        If the item is a group, the Group is appended to the manager's list
        of groups.  It the item is a string, then a group is created with
        the string as the ``id`` and the new group is appended to the list
        of groups.  If the item is an ActionManagerItem then the item is
        appended to the manager's default group.
        """
        item = self._prepare_item(item)
        if isinstance(item, Group):
            group = self._groups

        else:
            group = self._get_default_group()

        group.append(item)
        return group

    def destroy(self):
        """ Called when the manager is no longer required.

        By default this method simply calls 'destroy' on all of the manager's
        groups.
        """
        for group in self.groups:
            group.destroy()

    def insert(self, index, item):
        """ Insert an item into the manager at the specified index.

        Parameters
        ----------
        index : int
            The position at which to insert the object
        item : string, Group instance or ActionManagerItem instance
            The item to insert.

        Notes
        -----

        If the item is a group, the Group is inserted into the manager's list
        of groups.  It the item is a string, then a group is created with
        the string as the ``id`` and the new group is inserted into the list
        of groups.  If the item is an ActionManagerItem then the item is
        inserted into the manager's defualt group.
        """
        item = self._prepare_item(item)

        if isinstance(item, Group):
            group = self._groups

        else:
            group = self._get_default_group()

        group.insert(index, item)
        return group

    def find_group(self, id):
        """ Find a group with a specified Id.

        Parameters
        ----------
        id : str
            The id of the group to find.

        Returns
        -------
        group : Group instance
            The group which matches the id, or None if no such group exists.
        """
        for group in self._groups:
            if group.id == id:
                return group
        else:
            return None

    def find_item(self, path):
        """ Find an item using a path.

        Parameters
        ----------
        path : str
            A '/' separated list of contribution Ids.

        Returns
        -------
        item : ActionManagerItem or None
            Returns the matching ActionManagerItem, or None if any component
            of the path is not found.
        """
        components = path.split("/")

        # If there is only one component, then the path is just an Id so look
        # it up in this manager.
        if len(components) > 0:
            item = self._find_item(components[0])
            if len(components) > 1 and item is not None:
                item = item.find_item("/".join(components[1:]))
        else:
            item = None

        return item

    def walk(self, fn):
        """ Walk the manager applying a function at every item.

        The components are walked in pre-order.

        Parameters
        ----------
        fn : callable
            A callable to apply to the tree of groups and items, starting with
            the manager.
        """
        fn(self)

        for group in self._groups:
            self.walk_group(group, fn)

    def walk_group(self, group, fn):
        """ Walk a group applying a function at every item.

        The components are walked in pre-order.

        Parameters
        ----------
        fn : callable
            A callable to apply to the tree of groups and items.
        """
        fn(group)

        for item in group.items:
            if isinstance(item, Group):
                self.walk_group(item, fn)
            else:
                self.walk_item(item, fn)

    def walk_item(self, item, fn):
        """ Walk an item (may be a sub-menu manager remember!).

        The components are walked in pre-order.

        Parameters
        ----------
        fn : callable
            A callable to apply to the tree of items and subgroups.
        """
        if hasattr(item, "groups"):
            item.walk(fn)
        else:
            fn(item)

    # ------------------------------------------------------------------------
    # Private interface.
    # ------------------------------------------------------------------------

    def _get_default_group(self):
        """ Returns the manager's default group.

        This will create this group if it doesn't already exist.

        Returns
        -------
        group : Group instance
            The manager's default group.
        """
        group = self.find_group(self.DEFAULT_GROUP)
        if group is None:
            group = self._prepare_item(self.DEFAULT_GROUP)
            self._groups.append(group)

        return group

    def _prepare_item(self, item):
        """ Prepare an item to be added to this ActionManager.

        Parameters
        ----------
        item : string, Group instance or ActionManagerItem instance
            The item to be added to this ActionManager

        Returns
        -------
        item : Group or ActionManagerItem
            Modified item
        """
        # 1) The item is a 'Group' instance.
        if isinstance(item, Group):
            item.parent = self

        # 2) The item is a string.
        elif isinstance(item, str):
            # Create a group with that Id.
            item = Group(id=item)
            item.parent = self

        return item

    def _find_item(self, id):
        """ Find an item with a spcified Id.

        Parameters
        ----------
        id : str
            The id of the item to be found.

        Returns
        -------
        item : ActionManagerItem or None
            Returns the item with the specified Id, or None if no such item
            exists.
        """
        for group in self.groups:
            item = group.find(id)
            if item is not None:
                return item
        else:
            return None

    # ------------------------------------------------------------------------
    # Debugging interface.
    # ------------------------------------------------------------------------

    def dump(self, indent=""):
        """ Render a manager! """
        print(indent, "Manager", self.id)
        indent += "  "

        for group in self._groups:
            self.render_group(group, indent)

    def render_group(self, group, indent=""):
        """ Render a group! """
        print(indent, "Group", group.id)
        indent += "    "

        for item in group.items:
            if isinstance(item, Group):
                print("Surely, a group cannot contain another group!!!!")
                self.render_group(item, indent)

            else:
                self.render_item(item, indent)

    def render_item(self, item, indent=""):
        """ Render an item! """

        if hasattr(item, "groups"):
            item.dump(indent)

        else:
            print(indent, "Item", item.id)
예제 #5
0
class PythonEditor(MPythonEditor, Widget):
    """ The toolkit specific implementation of a PythonEditor.  See the
    IPythonEditor interface for the API documentation.
    """

    #### 'IPythonEditor' interface ############################################

    dirty = Bool(False)

    path = Str  #Unicode

    show_line_numbers = Bool(True)

    #### Events ####

    changed = Event

    key_pressed = Event(KeyPressedEvent)

    ###########################################################################
    # 'object' interface.
    ###########################################################################

    def __init__(self, parent, **traits):
        """ Creates a new pager. """

        # Base class constructor.
        super(PythonEditor, self).__init__(**traits)

        # Create the toolkit-specific control that represents the widget.
        self.control = self._create_control(parent)

        return

    ###########################################################################
    # 'PythonEditor' interface.
    ###########################################################################

    def load(self, path=None):
        """ Loads the contents of the editor. """

        if path is None:
            path = self.path

        # We will have no path for a new script.
        if len(path) > 0:
            f = open(self.path, 'r')
            text = f.read()
            f.close()

        else:
            text = ''

        self.control.SetText(text)
        self.dirty = False

        return

    def save(self, path=None):
        """ Saves the contents of the editor. """

        if path is None:
            path = self.path

        f = open(path, 'w')
        f.write(self.control.GetText())
        f.close()

        self.dirty = False

        return

    def set_style(self, n, fore, back):

        self.control.StyleSetForeground(n, fore)
        #self.StyleSetBackground(n, '#c0c0c0')
        #self.StyleSetBackground(n, '#ffffff')
        self.control.StyleSetBackground(n, back)
        self.control.StyleSetFaceName(n, "courier new")
        self.control.StyleSetSize(n, faces['size'])

        #self.StyleSetForeground(n, "#f0f0f0")
        ##self.StyleSetBackground(n, "#000000")
        #self.StyleSetFaceName(n, "courier new")
        #self.StyleSetSize(n, 20)
        #self.StyleSetUnderline(n, 1)
        #self.StyleSetItalic(n, 1)
        #self.StyleSetBold(n, 1)
        #StyleClearAll
        #StyleResetDefault
        #StyleSetCase
        #StyleSetChangeable
        #StyleSetCharacterSet
        #StyleSetEOLFilled
        #StyleSetFont
        #StyleSetFontAttr
        #StyleSetHotSpot
        #StyleSetSpec --- batch
        #StyleSetVisible

        return

    def select_line(self, lineno):
        """ Selects the specified line. """

        start = self.control.PositionFromLine(lineno)
        end = self.control.GetLineEndPosition(lineno)

        self.control.SetSelection(start, end)

        return

    ###########################################################################
    # Trait handlers.
    ###########################################################################

    def _path_changed(self):
        """ Handle a change to path. """

        self._changed_path()

        return

    ###########################################################################
    # Private interface.
    ###########################################################################

    def _create_control(self, parent):
        """ Creates the toolkit-specific control for the widget. """

        # Base-class constructor.
        self.control = stc = PythonSTC(parent, -1)

        # No folding!
        stc.SetProperty("fold", "0")

        # Mark the maximum line size.
        stc.SetEdgeMode(wx.stc.STC_EDGE_LINE)
        stc.SetEdgeColumn(79)

        # Display line numbers in the margin.
        if self.show_line_numbers:
            stc.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
            stc.SetMarginWidth(1, 45)
            self.set_style(wx.stc.STC_STYLE_LINENUMBER, "#000000", "#c0c0c0")
        else:
            stc.SetMarginWidth(1, 4)
            self.set_style(wx.stc.STC_STYLE_LINENUMBER, "#ffffff", "#ffffff")

        # Create 'tabs' out of spaces!
        stc.SetUseTabs(False)

        # One 'tab' is 4 spaces.
        stc.SetIndent(4)

        # Line ending mode.
        stc.SetEOLMode(wx.stc.STC_EOL_LF)  # Unix
        #self.SetEOLMode(wx.stc.STC_EOL_CR) # Apple Mac
        #self.SetEOLMode(wx.stc.STC_EOL_CRLF) # Windows

        ##########################################
        # Global styles for all languages.
        ##########################################

        self.set_style(wx.stc.STC_STYLE_DEFAULT, "#000000", "#ffffff")
        self.set_style(wx.stc.STC_STYLE_CONTROLCHAR, "#000000", "#ffffff")
        self.set_style(wx.stc.STC_STYLE_BRACELIGHT, "#000000", "#ffffff")
        self.set_style(wx.stc.STC_STYLE_BRACEBAD, "#000000", "#ffffff")

        ##########################################
        # Python styles.
        ##########################################

        # White space
        self.set_style(wx.stc.STC_P_DEFAULT, "#000000", "#ffffff")

        # Comment
        self.set_style(wx.stc.STC_P_COMMENTLINE, "#007f00", "#ffffff")

        # Number
        self.set_style(wx.stc.STC_P_NUMBER, "#007f7f", "#ffffff")

        # String
        self.set_style(wx.stc.STC_P_STRING, "#7f007f", "#ffffff")

        # Single quoted string
        self.set_style(wx.stc.STC_P_CHARACTER, "#7f007f", "#ffffff")

        # Keyword
        self.set_style(wx.stc.STC_P_WORD, "#00007f", "#ffffff")

        # Triple quotes
        self.set_style(wx.stc.STC_P_TRIPLE, "#7f0000", "#ffffff")

        # Triple double quotes
        self.set_style(wx.stc.STC_P_TRIPLEDOUBLE, "#ff0000", "#ffffff")

        # Class name definition
        self.set_style(wx.stc.STC_P_CLASSNAME, "#0000ff", "#ffffff")

        # Function or method name definition
        self.set_style(wx.stc.STC_P_DEFNAME, "#007f7f", "#ffffff")

        # Operators
        self.set_style(wx.stc.STC_P_OPERATOR, "#000000", "#ffffff")

        # Identifiers
        self.set_style(wx.stc.STC_P_IDENTIFIER, "#000000", "#ffffff")

        # Comment-blocks
        self.set_style(wx.stc.STC_P_COMMENTBLOCK, "#007f00", "#ffffff")

        # End of line where string is not closed
        self.set_style(wx.stc.STC_P_STRINGEOL, "#000000", "#ffffff")

        ##########################################
        # Events.
        ##########################################

        # By default, the will fire EVT_STC_CHANGE evented for all mask values
        # (STC_MODEVENTMASKALL). This generates too many events.
        stc.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT
                            | wx.stc.STC_MOD_DELETETEXT
                            | wx.stc.STC_PERFORMED_UNDO
                            | wx.stc.STC_PERFORMED_REDO)

        # Listen for changes to the file.
        stc.Bind(wx.stc.EVT_STC_CHANGE, self._on_stc_changed)

        # Listen for key press events.
        stc.Bind(wx.EVT_CHAR, self._on_char)

        # Load the editor's contents.
        self.load()

        return stc

    #### wx event handlers ####################################################

    def _on_stc_changed(self, event):
        """ Called whenever a change is made to the text of the document. """

        self.dirty = True
        self.changed = True

        # Give other event handlers a chance.
        event.Skip()

        return

    def _on_char(self, event):
        """ Called whenever a change is made to the text of the document. """

        self.key_pressed = KeyPressedEvent(
            alt_down=event.AltDown(),  #m_altDown == 1,
            control_down=event.ControlDown(),  #event.m_controlDown == 1,
            shift_down=event.ShiftDown(),  #    m_shiftDown == 1,
            key_code=event.KeyCode,
            event=event)

        # Give other event handlers a chance.
        event.Skip()

        return
예제 #6
0
 class A(HasTraits):
     event = Event(Int)
예제 #7
0
class TVTKScene(HasPrivateTraits):
    """A TVTK interactor scene widget.

    This widget uses a RenderWindowInteractor and therefore supports
    interaction with VTK widgets.  The widget uses TVTK.  The widget
    also supports the following:

    - Save the scene to a bunch of common (and not so common) image
      formats.

    - save the rendered scene to the clipboard.

    - adding/removing lists/tuples of actors

    - setting the view to useful predefined views (just like in
      MayaVi).

    - If one passes `stereo=1` to the constructor, stereo rendering is
      enabled.  By default this is disabled.  Changing the stereo trait
      has no effect during runtime.

    - One can disable rendering by setting `disable_render` to True.

    """

    # The version of this class.  Used for persistence.
    __version__ = 0

    ###########################################################################
    # Traits.
    ###########################################################################

    # Turn on/off stereo rendering.  This is set on initialization and
    # has no effect once the widget is realized.
    stereo = Bool(False)

    # Perform line smoothing for all renderered lines.  This produces
    # much nicer looking lines but renders slower.  This setting works
    # only when called before the first render.
    line_smoothing = Bool(False)

    # Perform point smoothing for all renderered points.  This
    # produces much nicer looking points but renders slower.  This
    # setting works only when called before the first render.
    point_smoothing = Bool(False)

    # Perform polygon smoothing (anti-aliasing) for all rendered
    # polygons.  This produces much nicer looking points but renders
    # slower.  This setting works only when called before the first
    # render.
    polygon_smoothing = Bool(False)

    # Enable parallel projection.  This trait is synchronized with
    # that of the camera.
    parallel_projection = Bool(False, desc='if the camera uses parallel projection')

    # Disable rendering.
    disable_render = Bool(False, desc='if rendering is to be disabled')

    # Enable off-screen rendering.  This allows a user to render the
    # scene to an image without the need to have the window active.
    # For example, the application can be minimized and the saved
    # scene should be generated correctly.  This is handy for batch
    # scripts and the like.  This works under Win32.  Under Mac OS X
    # and Linux it requires a recent VTK version (later than Oct 2005
    # and ideally later than March 2006) to work correctly.
    off_screen_rendering = Bool(False, desc='if off-screen rendering is enabled')

    # The background color of the window.  This is really a shadow
    # trait of the renderer's background.  Delegation does not seem to
    # work nicely for this.
    background = Trait(vtk_color_trait((0.5, 0.5, 0.5)),
                       desc='the background color of the window')

    # The default foreground color of any actors.  This basically
    # saves the preference and actors will listen to changes --
    # the scene itself does not use this.
    foreground = Trait(vtk_color_trait((1.0, 1.0, 1.0)),
                       desc='the default foreground color of actors')

    # The magnification to use when generating images from the render
    # window.
    magnification = Range(1, 2048, 1,
                          desc='the magnification used when the screen is saved to an image')

    # Specifies the number of frames to use for anti-aliasing when
    # saving a scene.  This basically increases
    # `self.render_window.aa_frames` in order to produce anti-aliased
    # figures when a scene is saved to an image.  It then restores the
    # `aa_frames` in order to get interactive rendering rates.
    anti_aliasing_frames = Range(0, 20, 8, desc='number of frames to use for anti-aliasing when saving a scene')

    # Default JPEG quality.
    jpeg_quality = Range(10, 100, 95, desc='the quality of the JPEG image to produce')

    # Default JPEG progressive setting.
    jpeg_progressive = Bool(True, desc='if the generated JPEG should be progressive')

    # The light manager.
    light_manager = Instance(light_manager.LightManager, record=True)

    # The movie maker instance.
    movie_maker = Instance('tvtk.pyface.movie_maker.MovieMaker', record=True)

    # Is the scene busy or not.
    busy = Property(Bool, record=False)

    ########################################
    # Events

    # Lifecycle events: there are no opening/opened events since the
    # control is actually created in __init__.

    # The control is going to be closed.
    closing = Event(record=False)

    # The control has been closed.
    closed = Event(record=False)

    # Event fired when an actor is added to the scene.
    actor_added = Event(record=False)
    # Event fired when any actor is removed from the scene.
    actor_removed = Event(record=False)

    ########################################
    # Properties.

    # The interactor used by the scene.
    interactor = Property(Instance(tvtk.GenericRenderWindowInteractor))

    # The render_window.
    render_window = Property(Instance(tvtk.RenderWindow))

    # The renderer.
    renderer = Property(Instance(tvtk.Renderer))

    # The camera.
    camera = Property(Instance(tvtk.Camera))

    # The control to mimic the Widget behavior.
    control = Any

    ########################################
    # Private traits.

    # A recorder for script recording.
    recorder = Instance(HasTraits, record=False, transient=True)
    # Cached last camera state.
    _last_camera_state = Any(transient=True)
    _camera_observer_id = Int(transient=True)
    _script_id = Str(transient=True)

    # The renderer instance.
    _renderer = Instance(tvtk.Renderer)
    _renwin = Instance(tvtk.RenderWindow)
    _interactor = Instance(tvtk.RenderWindowInteractor)
    _camera = Instance(tvtk.Camera)
    _busy_count = Int(0)

    ###########################################################################
    # 'object' interface.
    ###########################################################################
    def __init__(self, parent=None, **traits):
        """ Initializes the object. """

        # Base class constructor.
        super(TVTKScene, self).__init__(**traits)

        # Used to set the view of the scene.
        self._def_pos = 1

        self.control = self._create_control(parent)
        self._renwin.update_traits()

    def __get_pure_state__(self):
        """Allows us to pickle the scene."""
        # The control attribute is not picklable since it is a VTK
        # object so we remove it.
        d = self.__dict__.copy()
        for x in ['control', '_renwin', '_interactor', '_camera',
                  '_busy_count', '__sync_trait__', 'recorder',
                  '_last_camera_state', '_camera_observer_id',
                  '_script_id', '__traits_listener__']:
            d.pop(x, None)
        # Additionally pickle these.
        d['camera'] = self.camera
        return d

    def __getstate__(self):
        return state_pickler.dumps(self)

    def __setstate__(self, str_state):
        # This method is unnecessary since this object will almost
        # never be pickled by itself and only via an object that
        # contains it, therefore __init__ will be called when the
        # scene is constructed.  However, setstate is defined just for
        # completeness.
        state_pickler.set_state(self, state_pickler.loads_state(str_state))

    ###########################################################################
    # 'event' interface.
    ###########################################################################
    def _closed_fired(self):
        self.light_manager = None
        self._interactor = None
        self.movie_maker = None

    ###########################################################################
    # 'Scene' interface.
    ###########################################################################
    def render(self):
        """ Force the scene to be rendered. Nothing is done if the
        `disable_render` trait is set to True."""
        if not self.disable_render:
            self._renwin.render()

    def add_actors(self, actors):
        """ Adds a single actor or a tuple or list of actors to the
        renderer."""
        # Reset the zoom if this is the first actor.
        reset_zoom = (len(self._renderer.actors) == 0 and len(self._renderer.volumes)==0)
        if hasattr(actors, '__iter__'):
            for actor in actors:
                self._renderer.add_actor(actor)
        else:
            self._renderer.add_actor(actors)
        self.actor_added = actors

        if reset_zoom:
            self.reset_zoom()
        else:
            self.render()

    def remove_actors(self, actors):
        """ Removes a single actor or a tuple or list of actors from
        the renderer."""
        if hasattr(actors, '__iter__'):
            for actor in actors:
                self._renderer.remove_actor(actor)
        else:
            self._renderer.remove_actor(actors)
        self.actor_removed = actors
        self.render()

    # Conevenience methods.
    add_actor = add_actors
    remove_actor = remove_actors

    def add_widgets(self, widgets, enabled=True):
        """Adds a single 3D widget or a sequence of widgets to the renderer.
        If `enabled` is True the widget is also enabled once it is added."""
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        iren = self._interactor
        for widget in widgets:
            widget.interactor = iren
            widget.enabled = enabled
        self.render()

    def remove_widgets(self, widgets):
        """Removes a single 3D widget or a sequence of widgets from the
        renderer."""
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        iren = self._interactor
        for widget in widgets:
            if widget.interactor is not None:
                widget.enabled = False
                widget.interactor = None
        self.render()

    def close(self):
        """Close the scene cleanly.  This ensures that the scene is
        shutdown cleanly.  This should be called if you are getting
        async errors when closing a scene from a UI.  This is based on
        the observations of Charl Botha here:

          http://public.kitware.com/pipermail/vtkusers/2008-May/095291.html

        """
        # Return if we are already closed.
        if self._renwin is None:
            return

        # Fire the "closing" event.
        self.closing = True
        # Disable any renders through traits listner callbacks.
        self.disable_render = True
        # Remove sync trait listeners.
        self.sync_trait('background', self._renderer, remove=True)
        self.sync_trait('parallel_projection', self.camera, remove=True)
        self.sync_trait('off_screen_rendering', self._renwin, remove=True)

        # Remove all the renderer's props.
        self._renderer.remove_all_view_props()
        # Set the renderwindow to release all resources and the OpenGL
        # context.
        self._renwin.finalize()
        # Disconnect the interactor from the renderwindow.
        self._interactor.render_window = None
        # Remove the reference to the render window.
        del self._renwin
        # Fire the "closed" event.
        self.closed = True

    def x_plus_view(self):
        """View scene down the +X axis. """
        self._update_view(self._def_pos, 0, 0, 0, 0, 1)
        self._record_methods('x_plus_view()')

    def x_minus_view(self):
        """View scene down the -X axis. """
        self._update_view(-self._def_pos, 0, 0, 0, 0, 1)
        self._record_methods('x_minus_view()')

    def z_plus_view(self):
        """View scene down the +Z axis. """
        self._update_view(0, 0, self._def_pos, 0, 1, 0)
        self._record_methods('z_plus_view()')

    def z_minus_view(self):
        """View scene down the -Z axis. """
        self._update_view(0, 0, -self._def_pos, 0, 1, 0)
        self._record_methods('z_minus_view()')

    def y_plus_view(self):
        """View scene down the +Y axis. """
        self._update_view(0, self._def_pos, 0, 1, 0, 0)
        self._record_methods('y_plus_view()')

    def y_minus_view(self):
        """View scene down the -Y axis. """
        self._update_view(0, -self._def_pos, 0, 1, 0, 0)
        self._record_methods('y_minus_view()')

    def isometric_view(self):
        """Set the view to an iso-metric view. """
        self._update_view(self._def_pos, self._def_pos, self._def_pos,
                          0, 0, 1)
        self._record_methods('isometric_view()')

    def reset_zoom(self):
        """Reset the camera so everything in the scene fits."""
        self._renderer.reset_camera()
        self.render()
        self._record_methods('reset_zoom()')

    def save(self, file_name, size=None, **kw_args):
        """Saves rendered scene to one of several image formats
        depending on the specified extension of the filename.

        If an additional size (2-tuple) argument is passed the window
        is resized to the specified size in order to produce a
        suitably sized output image.  Please note that when the window
        is resized, the window may be obscured by other widgets and
        the camera zoom is not reset which is likely to produce an
        image that does not reflect what is seen on screen.

        Any extra keyword arguments are passed along to the respective
        image format's save method.
        """
        ext = os.path.splitext(file_name)[1]
        meth_map = {'.ps': 'ps', '.bmp': 'bmp', '.tiff': 'tiff',
                    '.png': 'png', '.jpg': 'jpg', '.jpeg': 'jpg',
                    '.iv': 'iv', '.wrl': 'vrml', '.vrml':'vrml',
                    '.oogl': 'oogl', '.rib': 'rib', '.obj': 'wavefront',
                    '.eps': 'gl2ps', '.pdf':'gl2ps', '.tex': 'gl2ps',
                    '.x3d': 'x3d', '.pov': 'povray'}
        if ext.lower() not in meth_map:
            raise ValueError(
                'Unable to find suitable image type for given file extension.'
            )
        meth = getattr(self, 'save_' + meth_map[ext])
        if size is not None:
            orig_size = self.get_size()
            self.set_size(size)
            meth(file_name, **kw_args)
            self.set_size(orig_size)
            self._record_methods('save(%r, %r)'%(file_name, size))
        else:
            meth(file_name, **kw_args)
            self._record_methods('save(%r)'%(file_name))

    def save_ps(self, file_name):
        """Saves the rendered scene to a rasterized PostScript image.
        For vector graphics use the save_gl2ps method."""
        if len(file_name) != 0:
            w2if = tvtk.WindowToImageFilter(read_front_buffer=
                                              not self.off_screen_rendering)
            w2if.magnification = self.magnification
            self._lift()
            w2if.input = self._renwin
            ex = tvtk.PostScriptWriter()
            ex.file_name = file_name
            configure_input(ex, w2if)
            self._exporter_write(ex)

    def save_bmp(self, file_name):
        """Save to a BMP image file."""
        if len(file_name) != 0:
            w2if = tvtk.WindowToImageFilter(read_front_buffer=
                                              not self.off_screen_rendering)
            w2if.magnification = self.magnification
            self._lift()
            w2if.input = self._renwin
            ex = tvtk.BMPWriter()
            ex.file_name = file_name
            configure_input(ex, w2if)
            self._exporter_write(ex)

    def save_tiff(self, file_name):
        """Save to a TIFF image file."""
        if len(file_name) != 0:
            w2if = tvtk.WindowToImageFilter(read_front_buffer=
                                              not self.off_screen_rendering)
            w2if.magnification = self.magnification
            self._lift()
            w2if.input = self._renwin
            ex = tvtk.TIFFWriter()
            ex.file_name = file_name
            configure_input(ex, w2if)
            self._exporter_write(ex)

    def save_png(self, file_name):
        """Save to a PNG image file."""
        if len(file_name) != 0:
            w2if = tvtk.WindowToImageFilter(read_front_buffer=
                                              not self.off_screen_rendering)
            w2if.magnification = self.magnification
            self._lift()
            w2if.input = self._renwin
            ex = tvtk.PNGWriter()
            ex.file_name = file_name
            configure_input(ex, w2if)
            self._exporter_write(ex)

    def save_jpg(self, file_name, quality=None, progressive=None):
        """Arguments: file_name if passed will be used, quality is the
        quality of the JPEG(10-100) are valid, the progressive
        arguments toggles progressive jpegs."""
        if len(file_name) != 0:
            if not quality and not progressive:
                quality, progressive = self.jpeg_quality, self.jpeg_progressive
            w2if = tvtk.WindowToImageFilter(read_front_buffer=
                                              not self.off_screen_rendering)
            w2if.magnification = self.magnification
            self._lift()
            w2if.input = self._renwin
            ex = tvtk.JPEGWriter()
            ex.quality = quality
            ex.progressive = progressive
            ex.file_name = file_name
            configure_input(ex, w2if)
            self._exporter_write(ex)

    def save_iv(self, file_name):
        """Save to an OpenInventor file."""
        if len(file_name) != 0:
            ex = tvtk.IVExporter()
            self._lift()
            ex.input = self._renwin
            ex.file_name = file_name
            self._exporter_write(ex)

    def save_vrml(self, file_name):
        """Save to a VRML file."""
        if len(file_name) != 0:
            ex = tvtk.VRMLExporter()
            self._lift()
            ex.input = self._renwin
            ex.file_name = file_name
            self._exporter_write(ex)

    def save_oogl(self, file_name):
        """Saves the scene to a Geomview OOGL file. Requires VTK 4 to
        work."""
        if len(file_name) != 0:
            ex = tvtk.OOGLExporter()
            self._lift()
            ex.input = self._renwin
            ex.file_name = file_name
            self._exporter_write(ex)

    def save_rib(self, file_name, bg=0, resolution=None, resfactor=1.0):
        """Save scene to a RenderMan RIB file.

        Keyword Arguments:

        file_name -- File name to save to.

        bg -- Optional background option.  If 0 then no background is
        saved.  If non-None then a background is saved.  If left alone
        (defaults to None) it will result in a pop-up window asking
        for yes/no.

        resolution -- Specify the resolution of the generated image in
        the form of a tuple (nx, ny).

        resfactor -- The resolution factor which scales the resolution.
        """
        if resolution is None:
            # get present window size
            Nx, Ny = self.render_window.size
        else:
            try:
                Nx, Ny = resolution
            except TypeError:
                raise TypeError(
                    "Resolution (%s) should be a sequence with two elements"%resolution
                )

        if len(file_name) == 0:
            return

        f_pref = os.path.splitext(file_name)[0]
        ex = tvtk.RIBExporter()
        ex.size = int(resfactor*Nx), int(resfactor*Ny)
        ex.file_prefix = f_pref
        ex.texture_prefix = f_pref + "_tex"
        self._lift()
        ex.render_window = self._renwin
        ex.background = bg

        if VTK_VER[:3] in ['4.2', '4.4']:
            # The vtkRIBExporter is broken in respect to VTK light
            # types.  Therefore we need to convert all lights into
            # scene lights before the save and later convert them
            # back.

            ########################################
            # Internal functions
            def x3to4(x):
                # convert 3-vector to 4-vector (w=1 -> point in space)
                return (x[0], x[1], x[2], 1.0 )
            def x4to3(x):
                # convert 4-vector to 3-vector
                return (x[0], x[1], x[2])

            def cameralight_transform(light, xform, light_type):
                # transform light by 4x4 matrix xform
                origin = x3to4(light.position)
                focus = x3to4(light.focal_point)
                neworigin = xform.multiply_point(origin)
                newfocus = xform.multiply_point(focus)
                light.position = x4to3(neworigin)
                light.focal_point = x4to3(newfocus)
                light.light_type = light_type
            ########################################

            save_lights_type=[]
            for light in self.light_manager.lights:
                save_lights_type.append(light.source.light_type)

            # Convert lights to scene lights.
            cam = self.camera
            xform = tvtk.Matrix4x4()
            xform.deep_copy(cam.camera_light_transform_matrix)
            for light in self.light_manager.lights:
                cameralight_transform(light.source, xform, "scene_light")

            # Write the RIB file.
            self._exporter_write(ex)

            # Now re-convert lights to camera lights.
            xform.invert()
            for i,light in enumerate(self.light_manager.lights):
                cameralight_transform(light.source, xform, save_lights_type[i])

            # Change the camera position. Otherwise VTK would render
            # one broken frame after the export.
            cam.roll(0.5)
            cam.roll(-0.5)
        else:
            self._exporter_write(ex)

    def save_wavefront(self, file_name):
        """Save scene to a Wavefront OBJ file.  Two files are
        generated.  One with a .obj extension and another with a .mtl
        extension which contains the material proerties.

        Keyword Arguments:

        file_name -- File name to save to
        """
        if len(file_name) != 0:
            ex = tvtk.OBJExporter()
            self._lift()
            ex.input = self._renwin
            f_pref = os.path.splitext(file_name)[0]
            ex.file_prefix = f_pref
            self._exporter_write(ex)

    def save_gl2ps(self, file_name, exp=None):
        """Save scene to a vector PostScript/EPS/PDF/TeX file using
        GL2PS.  If you choose to use a TeX file then note that only
        the text output is saved to the file.  You will need to save
        the graphics separately.

        Keyword Arguments:

        file_name -- File name to save to.

        exp -- Optionally configured vtkGL2PSExporter object.
        Defaults to None and this will use the default settings with
        the output file type chosen based on the extention of the file
        name.
        """

        # Make sure the exporter is available.
        if not hasattr(tvtk, 'GL2PSExporter'):
            msg = "Saving as a vector PS/EPS/PDF/TeX file using GL2PS is "\
                  "either not supported by your version of VTK or "\
                  "you have not configured VTK to work with GL2PS -- read "\
                  "the documentation for the vtkGL2PSExporter class."
            print(msg)
            return

        if len(file_name) != 0:
            f_prefix, f_ext = os.path.splitext(file_name)
            ex = None
            if exp:
                ex = exp
                if not isinstance(exp, tvtk.GL2PSExporter):
                    msg = "Need a vtkGL2PSExporter you passed a "\
                          "%s"%exp.__class__.__name__
                    raise TypeError(msg)
                ex.file_prefix = f_prefix
            else:
                ex = tvtk.GL2PSExporter()
                # defaults
                ex.file_prefix = f_prefix
                if f_ext == ".ps":
                    ex.file_format = 'ps'
                elif f_ext == ".tex":
                    ex.file_format = 'tex'
                elif f_ext == ".pdf":
                    ex.file_format = 'pdf'
                else:
                    ex.file_format = 'eps'
                ex.sort = 'bsp'
                ex.compress = 1
                ex.edit_traits(kind='livemodal')

            self._lift()
            ex.render_window = self._renwin
            if ex.write3d_props_as_raster_image:
                self._exporter_write(ex)
            else:
                ex.write()
            # Work around for a bug in VTK where it saves the file as a
            # .pdf.gz when the file is really a PDF file.
            if f_ext == '.pdf' and os.path.exists(f_prefix + '.pdf.gz'):
                os.rename(f_prefix + '.pdf.gz', file_name)

    def save_x3d(self, file_name):
        """Save scene to an X3D file (http://www.web3d.org/x3d/).

        Keyword Arguments:

        file_name -- File name to save to.
        """
        # Make sure the exporter is available.
        if not hasattr(tvtk, 'X3DExporter'):
            msg = "Saving as a X3D file does not appear to be  "\
                  "supported by your version of VTK."
            print(msg)
            return

        if len(file_name) != 0:
            ex = tvtk.X3DExporter()
            ex.input = self._renwin
            ex.file_name = file_name
            ex.update()
            ex.write()

    def save_povray(self, file_name):
        """Save scene to a POVRAY (Persistance of Vision Raytracer),
        file (http://www.povray.org).

        Keyword Arguments:

        file_name -- File name to save to.
        """
        # Make sure the exporter is available.
        if not hasattr(tvtk, 'POVExporter'):
            msg = "Saving as a POVRAY file does not appear to be  "\
                  "supported by your version of VTK."
            print(msg)
            return

        if len(file_name) != 0:
            ex = tvtk.POVExporter()
            ex.input = self._renwin
            if hasattr(ex, 'file_name'):
                ex.file_name = file_name
            else:
                ex.file_prefix = os.path.splitext(file_name)[0]
            ex.update()
            ex.write()

    def get_size(self):
        """Return size of the render window."""
        return self._interactor.size

    def set_size(self, size):
        """Set the size of the window."""
        self._interactor.size = size
        self._renwin.size = size


    ###########################################################################
    # Properties.
    ###########################################################################
    def _get_interactor(self):
        """Returns the vtkRenderWindowInteractor of the parent class"""
        return self._interactor

    def _get_render_window(self):
        """Returns the scene's render window."""
        return self._renwin

    def _get_renderer(self):
        """Returns the scene's renderer."""
        return self._renderer

    def _get_camera(self):
        """ Returns the active camera. """
        return self._renderer.active_camera

    def _get_busy(self):
        return self._busy_count > 0

    def _set_busy(self, value):
        """The `busy` trait is either `True` or `False`.  However,
        this could be problematic since we could have two methods
        `foo` and `bar that both set `scene.busy = True`.  As soon as
        `bar` is done it sets `busy` back to `False`.  This is wrong
        since the UI is still busy as `foo` is not done yet.  We
        therefore store the number of busy calls and either increment
        it or decrement it and change the state back to `False` only
        when the count is zero.
        """
        bc = self._busy_count
        if value:
            bc += 1
        else:
            bc -= 1
            bc = max(0, bc)

        self._busy_count = bc
        if bc == 1:
            self.trait_property_changed('busy', False, True)
        if bc == 0:
            self.trait_property_changed('busy', True, False)

    ###########################################################################
    # Non-public interface.
    ###########################################################################
    def _create_control(self, parent):
        """ Create the toolkit-specific control that represents the widget. """

        # Create the renderwindow.
        renwin = self._renwin = tvtk.RenderWindow()
        # If we are doing offscreen rendering we set the window size to
        # (1,1) so the window does not appear at all
        if self.off_screen_rendering:
            renwin.size = (1,1)

        renwin.set(point_smoothing=self.point_smoothing,
                   line_smoothing=self.line_smoothing,
                   polygon_smoothing=self.polygon_smoothing)
        # Create a renderer and add it to the renderwindow
        self._renderer = tvtk.Renderer()
        renwin.add_renderer(self._renderer)
        self._interactor = tvtk.RenderWindowInteractor(render_window=renwin)
        # Save a reference to our camera so it is not GC'd -- needed for
        # the sync_traits to work.
        self._camera = self.camera

        # Sync various traits.
        self._renderer.background = self.background
        self.sync_trait('background', self._renderer)
        self._renderer.on_trait_change(self.render, 'background')
        self._camera.parallel_projection = self.parallel_projection
        self.sync_trait('parallel_projection', self._camera)
        renwin.off_screen_rendering = self.off_screen_rendering
        self.sync_trait('off_screen_rendering', self._renwin)
        self.render_window.on_trait_change(self.render, 'off_screen_rendering')
        self.render_window.on_trait_change(self.render, 'stereo_render')
        self.render_window.on_trait_change(self.render, 'stereo_type')
        self.camera.on_trait_change(self.render, 'parallel_projection')

        self._interactor.initialize()
        self._interactor.render()
        self.light_manager = light_manager.LightManager(self)

        if self.off_screen_rendering:
            # We want the default size to be the normal (300, 300).
            # Setting the size now should not resize the window if
            # offscreen is working properly in VTK.
            renwin.size = (300, 300)

        return self._interactor

    def _lift(self):
        """Lift the window to the top. Useful when saving screen to an
        image."""
        return

    def _exporter_write(self, ex):
        """Abstracts the exporter's write method."""
        # Bumps up the anti-aliasing frames when the image is saved so
        # that the saved picture looks nicer.
        rw = self.render_window
        aa_frames = rw.aa_frames
        rw.aa_frames = self.anti_aliasing_frames
        rw.render()
        ex.update()
        ex.write()
        # Set the frames back to original setting.
        rw.aa_frames = aa_frames
        rw.render()

    def _update_view(self, x, y, z, vx, vy, vz):
        """Used internally to set the view."""
        camera = self.camera
        camera.focal_point = 0.0, 0.0, 0.0
        camera.position = x, y, z
        camera.view_up = vx, vy, vz
        self._renderer.reset_camera()
        self.render()

    def _disable_render_changed(self, val):
        if not val and self._renwin is not None:
            self.render()

    def _record_methods(self, calls):
        """A method to record a simple method called on self.  We need a
        more powerful and less intrusive way like decorators to do this.
        Note that calls can be a string with new lines in which case we
        interpret this as multiple calls.
        """
        r = self.recorder
        if r is not None:
            sid = self._script_id
            for call in calls.split('\n'):
                r.record('%s.%s'%(sid, call))

    def _record_camera_position(self, vtk_obj=None, event=None):
        """Callback to record the camera position."""
        r = self.recorder
        if r is not None:
            state = self._get_camera_state()
            lcs = self._last_camera_state
            if state != lcs:
                self._last_camera_state = state
                sid = self._script_id
                for key, value in state:
                    r.record('%s.camera.%s = %r'%(sid, key, value))
                r.record('%s.camera.compute_view_plane_normal()'%sid)
                r.record('%s.render()'%sid)

    def _get_camera_state(self):
        c = self.camera
        state = []
        state.append(('position', list(c.position)))
        state.append(('focal_point', list(c.focal_point)))
        state.append(('view_angle', c.view_angle))
        state.append(('view_up', list(c.view_up)))
        state.append(('clipping_range', list(c.clipping_range)))
        return state

    def _recorder_changed(self, r):
        """When the recorder is set we add an event handler so we can
        record the change to the camera position after the interaction.
        """
        iren = self._interactor
        if r is not None:
            self._script_id = r.get_script_id(self)
            id = iren.add_observer('EndInteractionEvent',
                                   messenger.send)
            self._camera_observer_id = id
            i_vtk = tvtk.to_vtk(iren)
            messenger.connect(i_vtk, 'EndInteractionEvent',
                              self._record_camera_position)
        else:
            self._script_id = ''
            iren.remove_observer(self._camera_observer_id)
            i_vtk = tvtk.to_vtk(iren)
            messenger.disconnect(i_vtk, 'EndInteractionEvent',
                                 self._record_camera_position)

    def _movie_maker_default(self):
        from tvtk.pyface.movie_maker import MovieMaker
        return MovieMaker(scene=self)
예제 #8
0
class PropertyListDemo(HasPrivateTraits):
    """ Displays a random list of Person objects in a TableEditor that is
        refreshed every 3 seconds by a background thread.
     """

    # An event used to trigger a Property value update:
    ticker = Event()

    # The property being display in the TableEditor:
    people = Property(List, depends_on='ticker')

    # Tiny hack to allow starting the background thread easily:
    begin = Int()

    # -- Traits View Definitions ----------------------------------------------

    traits_view = View(Item('people',
                            show_label=False,
                            editor=TableEditor(
                                columns=[
                                    ObjectColumn(name='name',
                                                 editable=False,
                                                 width=0.50),
                                    ObjectColumn(name='age',
                                                 editable=False,
                                                 width=0.15),
                                    ObjectColumn(name='gender',
                                                 editable=False,
                                                 width=0.35)
                                ],
                                auto_size=False,
                                show_toolbar=False,
                                sortable=False,
                            )),
                       title='Property List Demo',
                       width=0.25,
                       height=0.33,
                       resizable=True)

    # -- Property Implementations ---------------------------------------------
    @cached_property
    def _get_people(self):
        """ Returns the value for the 'people' property.
        """
        return [
            Person(name='%s %s' %
                   (choice(['Tom', 'Dick', 'Harry', 'Alice', 'Lia', 'Vibha']),
                    choice(['Thomas', 'Jones', 'Smith', 'Adams', 'Johnson'])),
                   age=randint(21, 75),
                   gender=choice(['Male', 'Female']))
            for i in range(randint(10, 20))
        ]

    # -- Default Value Implementations ----------------------------------------
    def _begin_default(self):
        """ Starts the background thread running.
        """
        thread = Thread(target=self._timer)
        thread.setDaemon(True)
        thread.start()

        return 0

    # -- Private Methods ------------------------------------------------------

    def _timer(self):
        """ Triggers a property update every 3 seconds for 30 seconds.
        """
        for i in range(10):
            sleep(3)
            self.ticker = True