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 )
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()
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
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)
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
class A(HasTraits): event = Event(Int)
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)
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