示例#1
0
class Window(MWindow, Widget):
    """ The toolkit specific implementation of a Window.  See the IWindow
    interface for the API documentation.
    """

    #### 'IWindow' interface ##################################################

    position = Property(Tuple)

    size = Property(Tuple)

    title = Unicode

    #### Events #####

    activated = Event

    closed = Event

    closing = Event

    deactivated = Event

    key_pressed = Event(KeyPressedEvent)

    opened = Event

    opening = Event

    #### Private interface ####################################################

    # Shadow trait for position.
    _position = Tuple((-1, -1))

    # Shadow trait for size.
    _size = Tuple((-1, -1))

    ###########################################################################
    # 'IWindow' interface.
    ###########################################################################

    def activate(self):
        self.control.Iconize(False)
        self.control.Raise()

    def show(self, visible):
        self.control.Show(visible)

    ###########################################################################
    # Protected 'IWindow' interface.
    ###########################################################################

    def _add_event_listeners(self):
        self.control.Bind(wx.EVT_ACTIVATE, self._wx_on_activate)
        self.control.Bind(wx.EVT_SHOW, self._wx_on_show)
        self.control.Bind(wx.EVT_CLOSE, self._wx_on_close)
        self.control.Bind(wx.EVT_SIZE, self._wx_on_control_size)
        self.control.Bind(wx.EVT_MOVE, self._wx_on_control_move)
        self.control.Bind(wx.EVT_CHAR, self._wx_on_char)

    ###########################################################################
    # Protected 'IWidget' interface.
    ###########################################################################

    def _create_control(self, parent):
        # create a basic window control

        style = wx.DEFAULT_FRAME_STYLE \
                | wx.FRAME_NO_WINDOW_MENU \
                | wx.CLIP_CHILDREN
        control = wx.Frame(parent,
                           -1,
                           self.title,
                           style=style,
                           size=self.size,
                           pos=self.position)
        control.SetBackgroundColour(SystemMetrics().dialog_background_color)
        control.Enable(self.enabled)

        # XXX starting with self.visible true is generally a bad idea
        control.Show(self.visible)

        return control

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

    def _get_position(self):
        """ Property getter for position. """

        return self._position

    def _set_position(self, position):
        """ Property setter for position. """

        if self.control is not None:
            self.control.SetPosition(position)

        old = self._position
        self._position = position

        self.trait_property_changed('position', old, position)

    def _get_size(self):
        """ Property getter for size. """

        return self._size

    def _set_size(self, size):
        """ Property setter for size. """

        if self.control is not None:
            self.control.SetSize(size)

        old = self._size
        self._size = size

        self.trait_property_changed('size', old, size)

    def _title_changed(self, title):
        """ Static trait change handler. """

        if self.control is not None:
            self.control.SetTitle(title)

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

    def _wx_on_activate(self, event):
        """ Called when the frame is being activated or deactivated. """

        if event.GetActive():
            self.activated = self
        else:
            self.deactivated = self

        event.Skip()

    def _wx_on_show(self, event):
        """ Called when the frame is being activated or deactivated. """

        self.visible = event.IsShown()

        event.Skip()

    def _wx_on_close(self, event):
        """ Called when the frame is being closed. """

        self.close()

    def _wx_on_control_move(self, event):
        """ Called when the window is resized. """

        # Get the real position and set the trait without performing
        # notification.

        # WXBUG - From the API documentation you would think that you could
        # call event.GetPosition directly, but that would be wrong.  The pixel
        # reported by that call is the pixel just below the window menu and
        # just right of the Windows-drawn border.
        self._position = event.GetEventObject().GetPositionTuple()

        event.Skip()

    def _wx_on_control_size(self, event):
        """ Called when the window is resized. """

        # Get the new size and set the shadow trait without performing
        # notification.
        wxsize = event.GetSize()

        self._size = (wxsize.GetWidth(), wxsize.GetHeight())

        event.Skip()

    def _wx_on_char(self, event):
        """ Called when a key is pressed when the tree has focus. """

        self.key_pressed = KeyPressedEvent(
            alt_down=event.m_altDown == 1,
            control_down=event.m_controlDown == 1,
            shift_down=event.m_shiftDown == 1,
            key_code=event.m_keyCode,
            event=event)

        event.Skip()
示例#2
0
class PipelineBase(Base):
    """ Base class for all the Source, Filters and Modules in the
        pipeline.
    """

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

    # A list of outputs for this object.
    outputs = List(record=False)

    # The actors generated by this object that will be rendered on the
    # scene.  Changing this list while the actors are renderered *is*
    # safe and will do the right thing.
    actors = List(record=False)

    # The optional list of actors belonging to this object.  These
    # will be added to the scene at an appropriate time.  Changing
    # this list while the widgets are renderered *is* safe and will do
    # the right thing.
    widgets = List(record=False)

    # Information about what this object can consume.
    input_info = Instance(PipelineInfo)

    # Information about what this object can produce.
    output_info = Instance(PipelineInfo)

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

    # This event is fired when the pipeline has changed.
    pipeline_changed = Event(record=False)

    # This event is fired when the data alone changes but the pipeline
    # outputs are the same.
    data_changed = Event(record=False)

    ##################################################
    # Private traits.
    ##################################################
    # Identifies if `actors` and `widgets` are added to the `scene` or
    # not.
    _actors_added = Bool(False)

    # Stores the state of the widgets prior to disabling them.
    _widget_state = List

    ######################################################################
    # `object` interface.
    ######################################################################
    def __get_pure_state__(self):
        d = super(PipelineBase, self).__get_pure_state__()
        # These are setup dynamically so we should not pickle them.
        for x in ('outputs', 'actors', 'widgets', '_actors_added', ):
            d.pop(x, None)
        return d


    ######################################################################
    # `Base` interface
    ######################################################################
    def start(self):
        """This is invoked when this object is added to the mayavi
        pipeline.  Note that when start is invoked, all the other
        information for the pipeline should be already set.
        """
        if self.running:
            return

        # Add any actors.
        self.add_actors()

        # Call parent method to set the running state.
        super(PipelineBase, self).start()

    def stop(self):
        """Invoked when this object is removed from the mayavi
        pipeline.
        """
        if not self.running:
            return

        # Remove any of our actors from the scene.
        self.remove_actors()

        # Call parent method to set the running state.
        super(PipelineBase, self).stop()

    def render(self):
        """Invokes render on the scene, this in turn invokes Render on
        the VTK pipeline.
        """
        s = self.scene
        if s is not None:
            s.render()
        elif self.running:
            # If there is no scene and we are running, we flush the
            # pipeline manually by calling update.
            for actor in self.actors:
                if hasattr(actor, 'mapper'):
                    m = actor.mapper
                    if m is not None:
                        m.update()
            for widget in self.widgets:
                if hasattr(widget, 'input'):
                    input = widget.input
                    if input is not None:
                        input.update()
        if hasattr(self, 'components'):
            for component in self.components:
                    component.render()

    ######################################################################
    # `PipelineBase` interface.
    ######################################################################
    # Normally, you will not need to override the following methods
    # but you can if you really need to for whatever reason.
    def add_actors(self):
        """Adds `self.actors` to the scene.

        This is typically called when start is invoked.  You should
        avoid calling this unless you know what you are doing.
        """
        scene = self.scene
        if scene is not None:
            scene.add_actors(self.actors)
            scene.add_widgets(self.widgets)
            self._set_widget_visibility(self.widgets)
            self._actors_added = True

    def remove_actors(self):
        """Removes `self.actors` from the scene.

        This is typically called when stop is invoked.  You should
        avoid calling this unless you know what you are doing.
        """
        scene = self.scene
        if scene is not None:
            scene.remove_actors(self.actors)
            scene.remove_widgets(self.widgets)
            self._actors_added = False

    ######################################################################
    # Non-public interface
    ######################################################################
    def _outputs_changed(self, new):
        self.pipeline_changed = True

    def _outputs_items_changed(self, list_event):
        self.pipeline_changed = True

    def _actors_changed(self, old, new):
        if self._actors_added:
            self.scene.remove_actors(old)
            self.scene.add_actors(new)
            self.scene.render()

    def _actors_items_changed(self, list_event):
        if self._actors_added:
            self.scene.remove_actors(list_event.removed)
            self.scene.add_actors(list_event.added)
            self.scene.render()

    def _widgets_changed(self, old, new):
        self._handle_widgets_changed(old, new)

    def _widgets_items_changed(self, list_event):
        self._handle_widgets_changed(list_event.removed, list_event.added)

    def _handle_widgets_changed(self, removed, added):
        if self._actors_added:
            scene = self.scene
            scene.remove_widgets(removed)
            scene.add_widgets(added)
            self._set_widget_visibility(added)

    def _scene_changed(self, old_scene, new_scene):
        if self._actors_added:
            old_scene.remove_actors(self.actors)
            old_scene.remove_widgets(self.widgets)
            new_scene.add_actors(self.actors)
            new_scene.add_widgets(self.widgets)
            self._set_widget_visibility(self.widgets)

    def _backup_widget_state(self):
        # store the enabled trait of the widgets
        # in the _widget_state list
        state = []
        for w in self.widgets:
            state.append(w.enabled)
            self._widget_state[:] = state

    def _restore_widget_state(self):
        if len(self._widget_state) != len(self.widgets):
            # someone has played with the widgets
            # we just enable all of them
            for w in self.widgets:
                w.enabled = True
        else:
            for i in range(len(self.widgets)):
                self.widgets[i].enabled = self._widget_state[i]

    def _visible_changed(self,value):
        if value:
            # restore the state of the widgets from the
            # backed up values.
            self._restore_widget_state()
        else:
            self._backup_widget_state()
            # disable all the widgets
            self._set_widget_visibility(self.widgets)

        # hide all actors
        for a in self.actors:
            a.visibility = value

        self.render()
        super(PipelineBase , self)._visible_changed(value)

    def _set_widget_visibility(self, widgets):
        if not self.visible:
            for widget in widgets:
                widget.enabled = False
示例#3
0
class MEditorAreaPane(HasTraits):

    #### 'IEditorAreaPane' interface ##########################################

    active_editor = Instance(IEditor)
    editors = List(IEditor)
    file_drop_extensions = List(Str)
    file_dropped = Event(File)
    hide_tab_bar = Bool(False)

    #### Protected traits #####################################################

    _factory_map = Dict(Callable, List(Callable))

    ###########################################################################
    # 'IEditorAreaPane' interface.
    ###########################################################################

    def create_editor(self, obj, factory=None):
        """ Creates an editor for an object.
        """
        if factory is None:
            factory = self.get_factory(obj)

        if factory is not None:
            return factory(editor_area=self, obj=obj)

        return None

    def edit(self, obj, factory=None, use_existing=True):
        """ Edit an object.
        """
        if use_existing:
            # Is the object already being edited in the window?
            editor = self.get_editor(obj)
            if editor is not None:
                self.activate_editor(editor)
                return editor

        # If not, create an editor for it.
        editor = self.create_editor(obj, factory)
        if editor is None:
            logger.warn('Cannot create editor for obj %r', obj)

        else:
            self.add_editor(editor)
            self.activate_editor(editor)

        return editor

    def get_editor(self, obj):
        """ Returns the editor for an object.
        """
        for editor in self.editors:
            if editor.obj == obj:
                return editor
        return None

    def get_factory(self, obj):
        """ Returns an editor factory suitable for editing an object.
        """
        for factory, filters in self._factory_map.iteritems():
            for filter in filters:
                # FIXME: We should swallow exceptions, but silently?
                try:
                    if filter(obj):
                        return factory
                except:
                    pass
        return None

    def register_factory(self, factory, filter):
        """ Registers a factory for creating editors.
        """
        self._factory_map.setdefault(factory, []).append(filter)

    def unregister_factory(self, factory):
        """ Unregisters a factory for creating editors.
        """
        if factory in self._factory_map:
            del self._factory_map[factory]
class IPythonWidget(Widget):
    """ The toolkit specific implementation of a PythonShell.  See the
    IPythonShell interface for the API documentation.
    """

    # 'IPythonShell' interface ---------------------------------------------

    command_executed = Event()

    key_pressed = Event(KeyPressedEvent)

    # 'IPythonWidget' interface --------------------------------------------

    interp = Instance(Interpreter, ())

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

    # FIXME v3: Either make this API consistent with other Widget sub-classes
    # or make it a sub-class of HasTraits.
    def __init__(self, parent, **traits):
        """ Creates a new pager. """

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

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

    # ------------------------------------------------------------------------
    # 'IPythonShell' interface.
    # ------------------------------------------------------------------------

    def interpreter(self):
        return self.interp

    def execute_command(self, command, hidden=True):
        self.control.execute_command(command, hidden=hidden)
        self.command_executed = True

    def execute_file(self, path, hidden=True):
        self.control.execute_command("%run " + '"%s"' % path, hidden=hidden)
        self.command_executed = True

    # ------------------------------------------------------------------------
    # Protected 'IWidget' interface.
    # ------------------------------------------------------------------------

    def _create_control(self, parent):
        # Create the controller based on the version of the installed IPython
        klass = IPythonController
        if IPYTHON_VERSION[0] == 0:
            if IPYTHON_VERSION[1] == 9:
                klass = IPython09Controller
            elif IPYTHON_VERSION[1] == 10:
                klass = IPython010Controller
        shell = klass(parent, -1, shell=self.interp)

        # Listen for key press events.
        shell.Bind(wx.EVT_CHAR, self._wx_on_char)

        # Enable the shell as a drag and drop target.
        shell.SetDropTarget(PythonDropTarget(self))

        return shell

    # ------------------------------------------------------------------------
    # 'PythonDropTarget' handler interface.
    # ------------------------------------------------------------------------

    def on_drop(self, x, y, obj, default_drag_result):
        """ Called when a drop occurs on the shell. """

        # If this is a file, we'll just print the file name
        if isinstance(obj, EnthoughtFile):
            self.control.write(obj.absolute_path)

        elif (isinstance(obj, list) and len(obj) == 1
              and isinstance(obj[0], EnthoughtFile)):
            self.control.write(obj[0].absolute_path)

        else:
            # Not a file, we'll inject the object in the namespace
            # If we can't create a valid Python identifier for the name of an
            # object we use this instead.
            name = "dragged"

            if (hasattr(obj, "name") and isinstance(obj.name, str)
                    and len(obj.name) > 0):
                py_name = python_name(obj.name)

                # Make sure that the name is actually a valid Python identifier.
                try:
                    if eval(py_name, {py_name: True}):
                        name = py_name

                except:
                    pass

            self.interp.user_ns[name] = obj
            self.execute_command(name, hidden=False)
        self.control.SetFocus()

        # We always copy into the shell since we don't want the data
        # removed from the source
        return wx.DragCopy

    def on_drag_over(self, x, y, obj, default_drag_result):
        """ Always returns wx.DragCopy to indicate we will be doing a copy."""

        return wx.DragCopy

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

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

        self.key_pressed = KeyPressedEvent(
            alt_down=event.AltDown() == 1,
            control_down=event.ControlDown() == 1,
            shift_down=event.ShiftDown() == 1,
            key_code=event.GetKeyCode(),
            event=event,
        )

        # Give other event handlers a chance.
        event.Skip()
示例#5
0
class TVTKScene(HasPrivateTraits):
    """A TVTK interactor scene widget.

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

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

    - save the rendered scene to the clipboard.

    - adding/removing lists/tuples of actors

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

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

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

    """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # 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))

    ###########################################################################
    # '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.keys():
            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 == 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()

    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)
示例#6
0
class TableViewer(ContentViewer):
    """ A viewer for tabular data. """

    # The content provider provides the actual table data.
    content_provider = Instance(TableContentProvider)

    # The label provider provides, err, the labels for the items in the table
    # (a label can have text and/or an image).
    label_provider = Instance(TableLabelProvider, ())

    # The column provider provides information about the columns in the table
    # (column headers, width etc).
    column_provider = Instance(TableColumnProvider, ())

    # The colours used to render odd and even numbered rows.
    even_row_background = Color("white")
    odd_row_background = Color((245, 245, 255))

    # A row has been selected.
    row_selected = Event()

    # A row has been activated.
    row_activated = Event()

    # A drag operation was started on a node.
    row_begin_drag = Event()

    # The size of the icons in the table.
    _image_size = Tuple(Int, Int)

    def __init__(self, parent, image_size=(16, 16), **traits):
        """ Creates a new table viewer.

        'parent' is the toolkit-specific control that is the table's parent.

        'image_size' is a tuple in the form (int width, int height) that
        specifies the size of the images (if any) displayed in the table.

        """
        create = traits.pop('create', True)

        # Base class constructors.
        super().__init__(parent=parent, _image_size=image_size, **traits)

        if create:
            self.create()
            warnings.warn(
                "automatic widget creation is deprecated and will be removed "
                "in a future Pyface version, use create=False and explicitly "
                "call create() for future behaviour",
                PendingDeprecationWarning,
            )

    def _create_control(self, parent):
        # Create the toolkit-specific control.
        self.control = table = _Table(parent, self._image_size, self)

        # Table events.
        table.Bind(wx.EVT_LIST_ITEM_SELECTED, self._on_item_selected)
        table.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_item_activated)
        table.Bind(wx.EVT_LIST_BEGIN_DRAG, self._on_list_begin_drag)
        table.Bind(wx.EVT_LIST_BEGIN_RDRAG, self._on_list_begin_rdrag)

        table.Bind(
            wx.EVT_LIST_BEGIN_LABEL_EDIT, self._on_list_begin_label_edit
        )

        table.Bind(wx.EVT_LIST_END_LABEL_EDIT, self._on_list_end_label_edit)

        # fixme: Bug[732104] indicates that this event does not get fired
        # in a virtual list control (it *does* get fired in a regular list
        # control 8^().
        table.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._on_item_deselected)

        # Create the widget!
        self._create_widget(parent)

        # We use a dynamic handler instead of a static handler here, as we
        # don't want to react if the input is set in the constructor.
        self.observe(self._on_input_changed, "input")

        return table

    # ------------------------------------------------------------------------
    # 'TableViewer' interface.
    # ------------------------------------------------------------------------

    def select_row(self, row):
        """ Select the specified row. """

        self.control.SetItemState(
            row, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED
        )

        self.control.SetItemState(
            row, wx.LIST_STATE_FOCUSED, wx.LIST_STATE_FOCUSED
        )

        # Make sure that the selected row is visible.
        fudge = max(0, row - 5)
        self.EnsureVisible(fudge)

        # Trait event notification.
        self.row_selected = row

        return

    # ------------------------------------------------------------------------
    # Trait event handlers.
    # ------------------------------------------------------------------------

    def _on_input_changed(self, event):
        """ Called when the input is changed. """

        # Update the table contents.
        self._update_contents()

        if event.old is None:
            self._update_column_widths()

        return

    # ------------------------------------------------------------------------
    # wx event handlers.
    # ------------------------------------------------------------------------

    def _on_item_selected(self, event):
        """ Called when an item in the list is selected. """

        # Get the index of the row that was selected (nice wx interface huh?!).
        row = event.Index

        # Trait event notification.
        self.row_selected = row

        return

    # fixme: Bug[732104] indicates that this event does not get fired in a
    # virtual list control (it *does* get fired in a regular list control 8^().
    def _on_item_deselected(self, event):
        """ Called when an item in the list is selected. """

        # Trait event notification.
        self.row_selected = -1

    def _on_item_activated(self, event):
        """ Called when an item in the list is activated. """

        # Get the index of the row that was activated (nice wx interface!).
        row = event.Index

        # Trait event notification.
        self.row_activated = row

    def _on_list_begin_drag(self, event=None, is_rdrag=False):
        """ Called when a drag operation is starting on a list item. """

        # Trait notification.
        self.row_begin_drag = event.GetIndex()

    def _on_list_begin_rdrag(self, event=None):
        """ Called when a drag operation is starting on a list item. """

        self._on_list_begin_drag(event, True)

    def _on_list_begin_label_edit(self, event=None):
        """ Called when a label edit is started. """

        event.Veto()

    def _on_list_end_label_edit(self, event=None):
        """ Called when a label edit is completed. """

        return

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

    FORMAT_MAP = {
        "left": wx.LIST_FORMAT_LEFT,
        "right": wx.LIST_FORMAT_RIGHT,
        "center": wx.LIST_FORMAT_CENTRE,
        "centre": wx.LIST_FORMAT_CENTRE,
    }

    def _create_widget(self, parent):
        """ Creates the widget. """

        # Set up a default list item descriptor.
        info = wx.ListItem()
        info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT

        # Set the column headers.
        for index in range(self.column_provider.column_count):
            # Header text.
            info.m_text = self.column_provider.get_label(self, index)

            # Alignment of header text AND ALL cells in the column.
            alignment = self.column_provider.get_alignment(self, index)
            info.m_format = self.FORMAT_MAP.get(alignment, wx.LIST_FORMAT_LEFT)

            self.control.InsertColumn(index, info)  #

        # Update the table contents and the column widths.
        self._update_contents()
        self._update_column_widths()

    def _update_contents(self):
        """ Updates the table content. """

        self._elements = []
        if self.input is not None:
            # Filtering...
            for element in self.content_provider.get_elements(self.input):
                for filter in self.filters:
                    if not filter.select(self, self.input, element):
                        break

                else:
                    self._elements.append(element)

            # Sorting...
            if self.sorter is not None:
                self.sorter.sort(self, self.input, self._elements)

        # Setting this causes a refresh!
        self.control.SetItemCount(len(self._elements))

    def _update_column_widths(self):
        """ Updates the column widths. """

        # Set all columns to be the size of their largest item, or the size of
        # their header whichever is the larger.
        for column in range(self.control.GetColumnCount()):
            width = self.column_provider.get_width(self, column)
            if width == -1:
                width = self._get_column_width(column)

            self.control.SetColumnWidth(column, width)

    def _get_column_width(self, column):
        """ Return an appropriate width for the specified column. """

        self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE_USEHEADER)
        header_width = self.control.GetColumnWidth(column)

        if self.control.GetItemCount() == 0:
            width = header_width

        else:
            self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE)
            data_width = self.control.GetColumnWidth(column)

            width = max(header_width, data_width)

        return width
示例#7
0
class ListBox(LayoutWidget):
    """ A simple list box widget with a model-view architecture. """

    # The model that provides the data for the list box.
    model = Instance(ListBoxModel)

    # The objects currently selected in the list.
    selection = Int(-1)

    # Events.

    # An item has been activated.
    item_activated = Event()

    # Default style.
    STYLE = wx.LB_SINGLE | wx.LB_HSCROLL | wx.LB_NEEDED_SB

    def __init__(self, parent=None, **traits):
        """ Creates a new list box. """

        create = traits.pop('create', True)

        # Base-class constructors.
        super().__init__(parent=parent, **traits)

        # Create the widget!
        if create:
            self.create()
            warnings.warn(
                "automatic widget creation is deprecated and will be removed "
                "in a future Pyface version, use create=False and explicitly "
                "call create() for future behaviour",
                PendingDeprecationWarning,
            )

    def _create(self):
        super()._create()

        self._populate()

        # Listen for changes to the model.
        self.model.observe(self._on_model_changed, "list_changed")

    def dispose(self):
        self.model.observe(
            self._on_model_changed, "list_changed", remove=True
        )
        self.model.dispose()

    # ------------------------------------------------------------------------
    # 'ListBox' interface.
    # ------------------------------------------------------------------------

    def refresh(self):
        """ Refreshes the list box. """

        # For now we just clear out the entire list.
        self.control.Clear()

        # Populate the list.
        self._populate()

    # ------------------------------------------------------------------------
    # wx event handlers.
    # ------------------------------------------------------------------------

    def _on_item_selected(self, event):
        """ Called when an item in the list is selected. """

        listbox = event.GetEventObject()

        self.selection = listbox.GetSelection()

    def _on_item_activated(self, event):
        """ Called when an item in the list is activated. """

        listbox = event.GetEventObject()
        index = listbox.GetSelection()

        # Trait event notification.
        self.item_activated = index

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

    # Static ---------------------------------------------------------------

    def _selection_changed(self, index):
        """ Called when the selected item is changed. """

        if index != -1:
            self.control.SetSelection(index)

    # Dynamic -------------------------------------------------------------#

    def _on_model_changed(self, event):
        """ Called when the model has changed. """

        # For now we just clear out the entire list.
        self.refresh()

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

    def _create_control(self, parent):
        """ Creates the widget. """

        control = wx.ListBox(parent, -1, style=self.STYLE)

        # Wire it up!
        control.Bind(
            wx.EVT_LISTBOX, self._on_item_selected, id=self.control.GetId()
        )
        control.Bind(
            wx.EVT_LISTBOX_DCLICK,
            self._on_item_activated,
            id=self.control.GetId(),
        )

        # Populate the list.
        return control

    def _populate(self):
        """ Populates the list box. """

        for index in range(self.model.get_item_count()):
            label, item = self.model.get_item_at(index)
            self.control.Append(label, item)
示例#8
0
class CustomEditor(SimpleTextEditor):
    """Custom style of file editor, consisting of a file system tree view."""

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

    #: Wildcard filter to apply to the file dialog:
    filter = filter_trait

    #: Event fired when the file system view should be rebuilt:
    reload = Event()

    #: Event fired when the user double-clicks a file:
    dclick = Event()

    def init(self, parent):
        """Finishes initializing the editor by creating the underlying toolkit
        widget.
        """
        style = self.get_style()
        factory = self.factory
        if (len(factory.filter) > 0) or (factory.filter_name != ""):
            style |= wx.DIRCTRL_SHOW_FILTERS

        self.control = wx.GenericDirCtrl(parent, style=style)
        self._tree = tree = self.control.GetTreeCtrl()
        id = tree.GetId()
        tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.update_object, id=id)
        tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_dclick, id=id)
        tree.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._on_tooltip, id=id)

        self.filter = factory.filter
        self.sync_value(factory.filter_name, "filter", "from", is_list=True)
        self.sync_value(factory.reload_name, "reload", "from")
        self.sync_value(factory.dclick_name, "dclick", "to")

        self.set_tooltip()

    def dispose(self):
        """Disposes of the contents of an editor."""
        tree, self._tree = self._tree, None

        tree.Unbind(wx.EVT_TREE_SEL_CHANGED)
        tree.Unbind(wx.EVT_TREE_ITEM_ACTIVATED)

        super().dispose()

    def update_object(self, event):
        """Handles the user changing the contents of the edit control."""
        if self.control is not None:
            path = self.control.GetPath()
            if self.factory.allow_dir or isfile(path):
                if self.factory.truncate_ext:
                    path = splitext(path)[0]

                self.value = path

    def update_editor(self):
        """Updates the editor when the object trait changes externally to the
        editor.
        """
        if exists(self.str_value):
            self.control.SetPath(self.str_value)

    def get_style(self):
        """Returns the basic style to use for the control."""
        return wx.DIRCTRL_EDIT_LABELS

    def get_error_control(self):
        """Returns the editor's control for indicating error status."""
        return self._tree

    def _filter_changed(self):
        """Handles the 'filter' trait being changed."""
        self.control.SetFilter("|".join(self.filter[:]))

    def _on_dclick(self, event):
        """Handles the user double-clicking on a file name."""
        self.dclick = self.control.GetPath()

    def _on_tooltip(self, event):
        """Handles the user hovering on a file name for a tooltip."""
        text = self._tree.GetItemText(event.GetItem())
        event.SetToolTip(text)

    def _reload_changed(self):
        """Handles the 'reload' trait being changed."""
        self.control.ReCreateTree()
示例#9
0
class TabularEditor(Editor):
    """ A traits UI editor for editing tabular data (arrays, list of tuples,
        lists of objects, etc).
    """

    # -- Trait Definitions ----------------------------------------------------

    #: The event fired when a table update is needed:
    update = Event

    #: The event fired when a simple repaint is needed:
    refresh = Event

    #: 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_row = Int(-1)
    multi_selected_rows = List(Int)

    #: The optional extended name of the trait to synchronize the selection
    #: column with:
    selected_column = Int(-1)

    #: The most recently actived item and its index:
    activated = Any(comparison_mode=NO_COMPARE)
    activated_row = Int(comparison_mode=NO_COMPARE)

    #: The most recent left click data:
    clicked = Instance("TabularEditorEvent")

    #: The most recent left double click data:
    dclicked = Instance("TabularEditorEvent")

    #: The most recent right click data:
    right_clicked = Instance("TabularEditorEvent")

    #: The most recent right double click data:
    right_dclicked = Instance("TabularEditorEvent")

    #: The most recent column click data:
    column_clicked = Instance("TabularEditorEvent")

    #: The most recent column click data:
    column_right_clicked = Instance("TabularEditorEvent")

    #: The event triggering scrolling.
    scroll_to_row = Event(Int)

    #: The event triggering scrolling.
    scroll_to_column = Event(Int)

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

    #: NIT: This doesn't seem to be used anywhere...can I delete?
    #: # Row index of item to select after rebuilding editor list:
    #: row = Any

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

    #: The adapter from trait values to editor values:
    adapter = Instance(TabularAdapter)

    #: The table model associated with the editor:
    model = Instance(TabularModel)

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

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

    #: An image being converted:
    image = Image

    header_event_filter = Any()

    widget_factory = Callable(lambda *args, **kwds: _TableView(*args, **kwds))

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

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

        # Create the control
        control = self.control = self.widget_factory(self)

        # 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_row,
                "multi_selected_rows",
                "both",
                is_list=True,
            )
        else:
            self.sync_value(factory.selected, "selected", "both")
            self.sync_value(factory.selected_row, "selected_row", "both")

        # Connect to the mode specific selection handler
        if factory.multi_select:
            slot = self._on_rows_selection
        else:
            slot = self._on_row_selection
        selection_model = self.control.selectionModel()
        selection_model.selectionChanged.connect(slot)

        # Synchronize other interesting traits as necessary:
        self.sync_value(factory.update, "update", "from", is_event=True)
        self.sync_value(factory.refresh, "refresh", "from", is_event=True)
        self.sync_value(factory.activated, "activated", "to")
        self.sync_value(factory.activated_row, "activated_row", "to")
        self.sync_value(factory.clicked, "clicked", "to")
        self.sync_value(factory.dclicked, "dclicked", "to")
        self.sync_value(factory.right_clicked, "right_clicked", "to")
        self.sync_value(factory.right_dclicked, "right_dclicked", "to")
        self.sync_value(factory.column_clicked, "column_clicked", "to")
        self.sync_value(factory.column_right_clicked, "column_right_clicked",
                        "to")
        self.sync_value(factory.scroll_to_row,
                        "scroll_to_row",
                        "from",
                        is_event=True)
        self.sync_value(factory.scroll_to_column,
                        "scroll_to_column",
                        "from",
                        is_event=True)

        # Connect other signals as necessary
        control.activated.connect(self._on_activate)
        control.clicked.connect(self._on_click)
        control.clicked.connect(self._on_right_click)
        control.doubleClicked.connect(self._on_dclick)
        control.horizontalHeader().sectionClicked.connect(
            self._on_column_click)

        control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        control.customContextMenuRequested.connect(self._on_context_menu)

        self.header_event_filter = HeaderEventFilter(self)
        control.horizontalHeader().installEventFilter(self.header_event_filter)

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

        # If the user has requested automatic update, attempt to set up the
        # appropriate listeners:
        if factory.auto_update:
            self.context_object.on_trait_change(self.refresh_editor,
                                                self.extended_name + ".-",
                                                dispatch="ui")

        # Create the mapping from user supplied images to QImages:
        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")

        # Rebuild the editor columns and headers whenever the adapter's
        # 'columns' changes:
        self.on_trait_change(self.update_editor,
                             "adapter.columns",
                             dispatch="ui")

    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)

        if self.factory.auto_update:
            self.context_object.on_trait_change(self.refresh_editor,
                                                self.extended_name + ".-",
                                                remove=True)

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

        self.adapter.cleanup()

        super(TabularEditor, 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()
            if self.factory.multi_select:
                self._multi_selected_changed(self.multi_selected)
            else:
                self._selected_changed(self.selected)

    # -------------------------------------------------------------------------
    #  TabularEditor interface:
    # -------------------------------------------------------------------------

    def refresh_editor(self):
        """ Requests the table view to redraw itself.
        """
        self.control.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

    # -------------------------------------------------------------------------
    #  UI preference save/restore interface:
    # -------------------------------------------------------------------------

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        cws = prefs.get("cached_widths")
        num_columns = len(self.adapter.columns)
        if cws is not None and num_columns == len(cws):
            for column in range(num_columns):
                self.control.setColumnWidth(column, cws[column])

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        widths = [
            self.control.columnWidth(column)
            for column in range(len(self.adapter.columns))
        ]
        return {"cached_widths": widths}

    # -------------------------------------------------------------------------
    #  Private methods:
    # -------------------------------------------------------------------------

    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

    def _get_image(self, image):
        """ Converts a user specified image to a QIcon.
        """
        if isinstance(image, six.string_types):
            self.image = image
            image = self.image

        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 _mouse_click(self, index, trait):
        """ Generate a TabularEditorEvent event for a specified model index and
            editor trait name.
        """
        event = TabularEditorEvent(editor=self,
                                   row=index.row(),
                                   column=index.column())
        setattr(self, trait, event)

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

    def _clicked_changed(self):
        """ When mouse is clicked on a specific cell, update the selected
            indices first
        """
        if not self.factory.multi_select:
            self.selected_row = self.clicked.row
            self.selected_column = self.clicked.column

    def _column_clicked_changed(self):
        """ When column is clicked, update selected column first
        """
        if not self.factory.multi_select:
            self.selected_column = self.clicked.column

    def _update_changed(self):
        self.update_editor()

    def _refresh_changed(self):
        self.refresh_editor()

    def _selected_changed(self, new):
        if not self._no_update:
            if new is None:
                self._selected_row_changed(-1)
            else:
                try:
                    selected_row = self.value.index(new)
                except Exception:
                    from traitsui.api import raise_to_debug

                    raise_to_debug()
                else:
                    self._selected_row_changed(selected_row)

    def _selected_row_changed(self, selected_row):
        if not self._no_update:
            smodel = self.control.selectionModel()
            if selected_row < 0:
                smodel.clearSelection()
            else:
                smodel.select(
                    self.model.index(selected_row, max(self.selected_column,
                                                       0)),
                    QtGui.QItemSelectionModel.ClearAndSelect
                    | QtGui.QItemSelectionModel.Rows,
                )
                # Once selected, scroll to the row
                self.scroll_to_row = selected_row

    def _multi_selected_changed(self, new):
        if not self._no_update:
            values = self.value
            try:
                rows = [values.index(i) for i in new]
            except:
                pass
            else:
                self._multi_selected_rows_changed(rows)

    def _multi_selected_items_changed(self, event):
        values = self.value
        try:
            added = [values.index(item) for item in event.added]
            removed = [values.index(item) for item in event.removed]
        except:
            pass
        else:
            list_event = TraitListEvent(0, added, removed)
            self._multi_selected_rows_items_changed(list_event)

    def _multi_selected_rows_changed(self, selected_rows):
        if not self._no_update:
            smodel = self.control.selectionModel()
            selection = QtGui.QItemSelection()
            for row in selected_rows:
                selection.select(self.model.index(row, 0),
                                 self.model.index(row, 0))
            smodel.clearSelection()
            smodel.select(
                selection,
                QtGui.QItemSelectionModel.Select
                | QtGui.QItemSelectionModel.Rows,
            )

    def _multi_selected_rows_items_changed(self, event):
        if not self._no_update:
            smodel = self.control.selectionModel()
            for row in event.removed:
                smodel.select(
                    self.model.index(row, 0),
                    QtGui.QItemSelectionModel.Deselect
                    | QtGui.QItemSelectionModel.Rows,
                )
            for row in event.added:
                smodel.select(
                    self.model.index(row, 0),
                    QtGui.QItemSelectionModel.Select
                    | QtGui.QItemSelectionModel.Rows,
                )

    def _selected_column_changed(self, selected_column):
        if not self._no_update:
            smodel = self.control.selectionModel()
            if selected_column >= 0:
                smodel.select(
                    self.model.index(max(self.selected_row, 0),
                                     selected_column),
                    QtGui.QItemSelectionModel.ClearAndSelect
                    | QtGui.QItemSelectionModel.Rows,
                )
                # Once selected, scroll to the column
                self.scroll_to_column = selected_column

    scroll_to_row_hint_map = {
        "center": QtGui.QTableView.PositionAtCenter,
        "top": QtGui.QTableView.PositionAtTop,
        "bottom": QtGui.QTableView.PositionAtBottom,
        "visible": QtGui.QTableView.EnsureVisible,
    }

    def _scroll_to_row_changed(self, row):
        """ Scroll to the given row.
        """
        scroll_hint = self.scroll_to_row_hint_map.get(
            self.factory.scroll_to_row_hint, self.control.PositionAtCenter)
        self.control.scrollTo(
            self.model.index(row, max(self.selected_column, 0)), scroll_hint)

    def _scroll_to_column_changed(self, column):
        """ Scroll to the given column.
        """
        scroll_hint = self.scroll_to_row_hint_map.get(
            self.factory.scroll_to_row_hint, self.control.PositionAtCenter)
        self.control.scrollTo(
            self.model.index(max(self.selected_row, 0), column), scroll_hint)

    # -- Table Control Event Handlers -----------------------------------------

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

    def _on_click(self, index):
        """ Handle a cell being clicked.
        """
        self._mouse_click(index, "clicked")

    def _on_dclick(self, index):
        """ Handle a cell being double clicked.
        """
        self._mouse_click(index, "dclicked")

    def _on_column_click(self, column):
        event = TabularEditorEvent(editor=self, row=0, column=column)
        setattr(self, "column_clicked", event)

    def _on_right_click(self, column):
        event = TabularEditorEvent(editor=self, row=0, column=column)
        setattr(self, "right_clicked", event)

    def _on_column_right_click(self, column):
        event = TabularEditorEvent(editor=self, row=0, column=column)
        setattr(self, "column_right_clicked", event)

    def _on_row_selection(self, added, removed):
        """ Handle the row selection being changed.
        """
        self._no_update = True
        try:
            indexes = self.control.selectionModel().selectedRows()
            if len(indexes):
                self.selected_row = indexes[0].row()
                self.selected = self.adapter.get_item(self.object, self.name,
                                                      self.selected_row)
            else:
                self.selected_row = -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:
            indexes = self.control.selectionModel().selectedRows()
            selected_rows = []
            selected = []
            for index in indexes:
                row = index.row()
                selected_rows.append(row)
                selected.append(
                    self.adapter.get_item(self.object, self.name, row))
            self.multi_selected_rows = selected_rows
            self.multi_selected = selected
        finally:
            self._no_update = False

    def _on_context_menu(self, pos):
        column, row = (
            self.control.columnAt(pos.x()),
            self.control.rowAt(pos.y()),
        )
        menu = self.adapter.get_menu(self.object, self.name, row, column)
        if menu:
            self._menu_context = {
                "selection": self.object,
                "object": self.object,
                "editor": self,
                "column": column,
                "row": row,
                "item": self.adapter.get_item(self.object, self.name, row),
                "info": self.ui.info,
                "handler": self.ui.handler,
            }
            qmenu = menu.create_menu(self.control, self)
            qmenu.exec_(self.control.mapToGlobal(pos))
            self._menu_context = None

    def _on_column_context_menu(self, pos):
        column = self.control.columnAt(pos.x())
        menu = self.adapter.get_column_menu(self.object, self.name, -1, column)
        if menu:
            self._menu_context = {
                "selection": self.object,
                "object": self.object,
                "editor": self,
                "column": column,
                "info": self.ui.info,
                "handler": self.ui.handler,
            }
            qmenu = menu.create_menu(self.control, self)
            qmenu.exec_(self.control.mapToGlobal(pos))
            self._menu_context = None
        else:
            # If no menu is defined on the adapter, just trigger a click event.
            self._on_column_right_click(column)
示例#10
0
class SourceEditor(Editor):
    """ Editor for source code which uses the advanced code widget.
    """

    # -------------------------------------------------------------------------
    #  Pyface PythonEditor interface:
    # -------------------------------------------------------------------------

    #: Event that is fired on keypresses:
    key_pressed = Event(KeyPressedEvent)

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

    #: The code editor is scrollable. This value overrides the default.
    scrollable = True

    # -------------------------------------------------------------------------
    #  SoureEditor interface:
    # -------------------------------------------------------------------------

    #: Is the editor read only?
    readonly = Bool(False)

    #: The currently selected line
    selected_line = Int()

    #: The start position of the selected
    selected_start_pos = Int()

    #: The end position of the selected
    selected_end_pos = Int()

    #: The currently selected text
    selected_text = Str()

    #: The list of line numbers to mark
    mark_lines = List(Int)

    #: The current line number
    line = Event()

    #: The current column
    column = Event()

    #: The lines to be dimmed
    dim_lines = List(Int)
    dim_color = Str()
    dim_style_number = Int(16)  # 0-15 are reserved for the python lexer

    #: The lines to have squiggles drawn under them
    squiggle_lines = List(Int)
    squiggle_color = Str()

    #: The lexer to use.
    lexer = Str()

    # -------------------------------------------------------------------------
    #  Finishes initializing the editor by creating the underlying toolkit
    #  widget:
    # -------------------------------------------------------------------------
    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        self.control = QtGui.QWidget()
        layout = QtGui.QVBoxLayout(self.control)
        layout.setContentsMargins(0, 0, 0, 0)

        self._widget = control = AdvancedCodeWidget(None,
                                                    lexer=self.factory.lexer)
        layout.addWidget(control)

        factory = self.factory

        # Set up listeners for the signals we care about
        code_editor = self._widget.code

        if self.readonly:
            code_editor.setReadOnly(True)
        else:
            if factory.auto_set:
                code_editor.textChanged.connect(self.update_object)
            else:
                code_editor.focus_lost.connect(self.update_object)

        if factory.selected_text != "":
            code_editor.selectionChanged.connect(self._selection_changed)
        if (factory.line != "") or (factory.column != ""):
            code_editor.cursorPositionChanged.connect(self._position_changed)

        code_editor.line_number_widget.setVisible(factory.show_line_numbers)

        # Make sure the editor has been initialized:
        self.update_editor()

        # Set up any event listeners:
        self.sync_value(factory.mark_lines, "mark_lines", "from", is_list=True)
        self.sync_value(factory.selected_line, "selected_line", "from")
        self.sync_value(factory.selected_text, "selected_text", "to")
        self.sync_value(factory.line, "line")
        self.sync_value(factory.column, "column")

        self.sync_value(factory.selected_start_pos, "selected_start_pos", "to")
        self.sync_value(factory.selected_end_pos, "selected_end_pos", "to")

        self.sync_value(factory.dim_lines, "dim_lines", "from", is_list=True)
        if self.factory.dim_color == "":
            self.dim_color = "grey"
        else:
            self.sync_value(factory.dim_color, "dim_color", "from")

        self.sync_value(factory.squiggle_lines,
                        "squiggle_lines",
                        "from",
                        is_list=True)
        if factory.squiggle_color == "":
            self.squiggle_color = "red"
        else:
            self.sync_value(factory.squiggle_color, "squiggle_color", "from")

        # Set the control tooltip:
        self.set_tooltip()

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        # Make sure that the editor does not try to update as the control is
        # being destroyed:
        if not self.factory.auto_set:
            self._widget.code.focus_lost.disconnect(self.update_object)

        super(SourceEditor, self).dispose()

    def update_object(self):
        """ Handles the user entering input data in the edit control.
        """
        if not self._locked:
            try:
                value = str(self._widget.code.toPlainText())
                if isinstance(self.value, SequenceTypes):
                    value = value.split()
                self.value = value
            except TraitError as excp:
                pass

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        self._locked = True
        new_value = self.value
        if isinstance(new_value, SequenceTypes):
            new_value = "\n".join([line.rstrip() for line in new_value])
        control = self._widget
        if control.code.toPlainText() != new_value:
            control.code.setPlainText(new_value)

            if self.factory.selected_line:
                # TODO: update the factory selected line
                pass

            # TODO: put the cursor somewhere

        self._locked = False

    def error(self, excp):
        """ Handles an error that occurs while setting the object's trait value.
        """
        pass

    # -- UI preference save/restore interface ---------------------------------

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        if self.factory.key_bindings is not None:
            key_bindings = prefs.get("key_bindings")
            if key_bindings is not None:
                self.factory.key_bindings.merge(key_bindings)

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        return {"key_bindings": self.factory.key_bindings}

    def _mark_lines_changed(self):
        """ Handles the set of marked lines being changed.
        """
        # FIXME: Not implemented at this time.
        return

    def _mark_lines_items_changed(self):
        self._mark_lines_changed()

    def _selection_changed(self):
        self.selected_text = str(self._widget.code.textCursor().selectedText())
        start = self._widget.code.textCursor().selectionStart()
        end = self._widget.code.textCursor().selectionEnd()

        if start > end:
            start, end = end, start

        self.selected_start_pos = start
        self.selected_end_pos = end

    def _selected_line_changed(self):
        """ Handles a change in which line is currently selected.
        """
        control = self._widget
        line = max(1, min(control.lines(), self.selected_line))
        _, column = control.get_line_column()
        control.set_line_column(line, column)
        if self.factory.auto_scroll:
            control.centerCursor()

    def _line_changed(self, line):
        if not self._locked:
            _, column = self._widget.get_line_column()
            self._widget.set_line_column(line, column)
            if self.factory.auto_scroll:
                self._widget.centerCursor()

    def _column_changed(self, column):
        if not self._locked:
            line, _ = self._widget.get_line_column()
            self._widget.set_line_column(line, column)

    def _position_changed(self):
        """ Handles the cursor position being changed.
        """
        control = self._widget
        self._locked = True
        self.line, self.column = control.get_line_column()
        self._locked = False
        self.selected_text = control.get_selected_text()
        if self.factory.auto_scroll:
            self._widget.centerCursor()

    def _key_pressed_changed(self, event):
        """ Handles a key being pressed within the editor.
        """
        key_bindings = self.factory.key_bindings
        if key_bindings:
            processed = key_bindings.do(event.event, self.ui.handler,
                                        self.ui.info)
        else:
            processed = False
        if not processed and event.event.matches(QtGui.QKeySequence.Find):
            self._find_widget.show()

    def _dim_color_changed(self):
        pass

    def _squiggle_color_changed(self):
        pass

    @observe("dim_lines, squiggle_lines")
    def _style_document(self, event):
        self._widget.set_warn_lines(self.squiggle_lines)
示例#11
0
class KeyBindings(HasPrivateTraits):
    """ A set of key bindings.
    """

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

    # Set of defined key bindings (redefined dynamically)
    bindings = List(KeyBinding)

    # Optional prefix to add to each method name
    prefix = Str

    # Optional suffix to add to each method name
    suffix = Str

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

    # The (optional) list of controllers associated with this KeyBindings
    # object. The controllers may also be provided with the 'do' method:
    controllers = List(transient=True)

    # The 'parent' KeyBindings object of this one (if any):
    parent = Instance('KeyBindings', transient=True)

    # The root of the KeyBindings tree this object is part of:
    root = Property(depends_on='parent')

    # The child KeyBindings of this object (if any):
    children = List(transient=True)

    # Event fired when one of the contained KeyBinding objects is changed
    binding_modified = Event(KeyBinding)

    # Control that currently has the focus (if any)
    focus_owner = Any(transient=True)

    #-------------------------------------------------------------------------
    #  Traits view definitions:
    #-------------------------------------------------------------------------

    traits_view = View([Item('bindings',
                             style='custom',
                             show_label=False,
                             editor=ListEditor(style='custom')),
                        '|{Click on an entry field, then press the key to '
                        'assign. Double-click a field to clear it.}<>'],
                       title='Update Key Bindings',
                       kind='livemodal',
                       resizable=True,
                       width=0.4,
                       height=0.4)

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

    def __init__(self, *bindings, **traits):
        super(KeyBindings, self).__init__(**traits)

        if (len(bindings) == 1) and isinstance(bindings[0], SequenceTypes):
            bindings = bindings[0]

        n = len(bindings)
        self.add_trait('bindings', List(KeyBinding, minlen=n,
                                        maxlen=n,
                                        mode='list'))
        self.bindings = [binding.trait_set(owner=self) for binding in bindings]

    #-------------------------------------------------------------------------
    #  Processes a keyboard event:
    #-------------------------------------------------------------------------

    def do(self, event, controllers=[], *args, **kw):
        """ Processes a keyboard event.
        """
        if isinstance(controllers, dict):
            controllers = list(controllers.values())
        elif not isinstance(controllers, SequenceTypes):
            controllers = [controllers]
        else:
            controllers = list(controllers)

        return self._do(toolkit().key_event_to_name(event),
                        controllers, args, kw.get('recursive', False))

    #-------------------------------------------------------------------------
    #  Merges another set of key bindings into this set:
    #-------------------------------------------------------------------------

    def merge(self, key_bindings):
        """ Merges another set of key bindings into this set.
        """
        binding_dic = {}
        for binding in self.bindings:
            binding_dic[binding.method_name] = binding

        for binding in key_bindings.bindings:
            binding2 = binding_dic.get(binding.method_name)
            if binding2 is not None:
                binding2.binding1 = binding.binding1
                binding2.binding2 = binding.binding2

    #-------------------------------------------------------------------------
    #  Returns a clone of the KeyBindings object:
    #-------------------------------------------------------------------------

    def clone(self, **traits):
        """ Returns a clone of the KeyBindings object.
        """
        return self.__class__(*self.bindings, **traits).trait_set(
            **self.get('prefix', 'suffix'))

    #-------------------------------------------------------------------------
    #  Dispose of the object:
    #-------------------------------------------------------------------------

    def dispose(self):
        """ Dispose of the object.
        """
        if self.parent is not None:
            self.parent.children.remove(self)

        del self.controllers
        del self.children
        del self.bindings

        self.parent = self._root = self.focus_owner = None

    #-------------------------------------------------------------------------
    #  Edits a possibly hierarchical set of KeyBindings:
    #-------------------------------------------------------------------------

    def edit(self):
        """ Edits a possibly hierarchical set of KeyBindings.
        """
        bindings = list(set(self.root._get_bindings([])))
        bindings.sort(key=lambda x: '%s%02d' % (x.binding1[-1:], x.binding1))
        KeyBindings(bindings).edit_traits()

    #-------------------------------------------------------------------------
    #  Returns the current binding for a specified key (if any):
    #-------------------------------------------------------------------------

    def key_binding_for(self, binding, key_name):
        """ Returns the current binding for a specified key (if any).
        """
        if key_name != '':
            for a_binding in self.bindings:
                if ((a_binding is not binding) and
                    ((key_name == a_binding.binding1) or
                     (key_name == a_binding.binding2))):
                    return a_binding

        return None

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

    @cached_property
    def _get_root(self):
        root = self
        while root.parent is not None:
            root = root.parent

        return root

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

    def _binding_modified_changed(self, binding):
        """ Handles a binding being changed.
        """
        binding1 = binding.binding1
        binding2 = binding.binding2
        for a_binding in self.bindings:
            if binding is not a_binding:
                if binding1 == a_binding.binding1:
                    a_binding.binding1 = ''
                if binding1 == a_binding.binding2:
                    a_binding.binding2 = ''
                if binding2 == a_binding.binding1:
                    a_binding.binding1 = ''
                if binding2 == a_binding.binding2:
                    a_binding.binding2 = ''

    def _focus_owner_changed(self, old, new):
        """ Handles the focus owner being changed.
        """
        if old is not None:
            old.border_size = 0

    @on_trait_change('children[]')
    def _children_modified(self, removed, added):
        """ Handles child KeyBindings being added to the object.
        """
        for item in added:
            item.parent = self

    #-- object Method Overrides ----------------------------------------------

    #-------------------------------------------------------------------------
    #  Restores the state of a previously pickled object:
    #-------------------------------------------------------------------------

    def __setstate__(self, state):
        """ Restores the state of a previously pickled object.
        """
        n = len(state['bindings'])
        self.add_trait('bindings', List(KeyBinding, minlen=n, maxlen=n))
        self.__dict__.update(state)
        self.bindings = self.bindings[:]

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

    def _get_bindings(self, bindings):
        """ Returns all of the bindings of this object and all of its children.
        """
        bindings.extend(self.bindings)
        for child in self.children:
            child._get_bindings(bindings)

        return bindings

    def _do(self, key_name, controllers, args, recursive):
        """ Process the specified key for the specified set of controllers for
            this KeyBindings object and all of its children.
        """
        # Search through our own bindings for a match:
        for binding in self.bindings:
            if (key_name == binding.binding1) or (
                    key_name == binding.binding2):
                method_name = '%s%s%s' % (
                              self.prefix, binding.method_name, self.suffix)
                for controller in (controllers + self.controllers):
                    method = getattr(controller, method_name, None)
                    if method is not None:
                        result = method(*args)
                        if result is not False:
                            return True

                if binding.method_name == 'edit_bindings':
                    self.edit()
                    return True

        # If recursive, continue searching through a children's bindings:
        if recursive:
            for child in self.children:
                if child._do(key_name, controllers, args, recursive):
                    return True

        # Indicate no one processed the key:
        return False
示例#12
0
class SceneModel(TVTKScene):

    ########################################
    # TVTKScene traits.

    picker = Property

    ########################################
    # SceneModel traits.

    # A convenient dictionary based interface to add/remove actors and widgets.
    # This is similar to the interface provided for the ActorEditor.
    actor_map = Dict()

    # This is used primarily to implement the add_actor/remove_actor methods.
    actor_list = List()

    # The actual scene being edited.
    scene_editor = Instance(TVTKScene)

    do_render = Event()

    # Fired when this is activated.
    activated = Event()

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

    # This exists just to mirror the TVTKWindow api.
    scene = Property

    ###################################
    # View related traits.

    # Render_window's view.
    _stereo_view = Group(Item(name='stereo_render'),
                         Item(name='stereo_type'),
                         show_border=True,
                         label='Stereo rendering',
                         )

    # The default view of this object.
    default_view = View(Group(
                            Group(Item(name='background'),
                                  Item(name='foreground'),
                                  Item(name='parallel_projection'),
                                  Item(name='disable_render'),
                                  Item(name='off_screen_rendering'),
                                  Item(name='jpeg_quality'),
                                  Item(name='jpeg_progressive'),
                                  Item(name='magnification'),
                                  Item(name='anti_aliasing_frames'),
                                  ),
                            Group(Item(name='render_window',
                                       style='custom',
                                       visible_when='object.stereo',
                                       editor=InstanceEditor(view=View(_stereo_view)),                                       show_label=False),
                                  ),
                            label='Scene'),
                        Group(Item(name='light_manager',
                                   style='custom',
                                   editor=InstanceEditor(),
                                   show_label=False),
                                   label='Lights')
                        )

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

    # Used by the editor to determine if the widget was enabled or not.
    enabled_info = Dict()

    def __init__(self, parent=None, **traits):
        """ Initializes the object. """
        # Base class constructor.  We call TVTKScene's super here on purpose.
        # Calling TVTKScene's init will create a new window which we do not
        # want.
        super(TVTKScene, self).__init__(**traits)
        self.control = None

    ######################################################################
    # TVTKScene API.
    ######################################################################
    def render(self):
        """ Force the scene to be rendered. Nothing is done if the
        `disable_render` trait is set to True."""

        self.do_render = True

    def add_actors(self, actors):
        """ Adds a single actor or a tuple or list of actors to the
        renderer."""
        if hasattr(actors, '__iter__'):
            self.actor_list.extend(actors)
        else:
            self.actor_list.append(actors)

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

    # Conevenience methods.
    add_actor = add_actors
    remove_actor = remove_actors

    def add_widgets(self, widgets, enabled=True):
        """Adds widgets to the renderer.
        """
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        for widget in widgets:
            self.enabled_info[widget] = enabled
        self.add_actors(widgets)

    def remove_widgets(self, widgets):
        """Removes widgets from the renderer."""
        if not hasattr(widgets, '__iter__'):
            widgets = [widgets]
        self.remove_actors(widgets)
        for widget in widgets:
            del self.enabled_info[widget]

    def reset_zoom(self):
        """Reset the camera so everything in the scene fits."""
        if self.scene_editor is not None:
            self.scene_editor.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.
        """
        self._check_scene_editor()
        self.scene_editor.save(file_name, size, **kw_args)

    def save_ps(self, file_name):
        """Saves the rendered scene to a rasterized PostScript image.
        For vector graphics use the save_gl2ps method."""
        self._check_scene_editor()
        self.scene_editor.save_ps(file_name)

    def save_bmp(self, file_name):
        """Save to a BMP image file."""
        self._check_scene_editor()
        self.scene_editor.save_bmp(file_name)

    def save_tiff(self, file_name):
        """Save to a TIFF image file."""
        self._check_scene_editor()
        self.scene_editor.save_tiff(file_name)

    def save_png(self, file_name):
        """Save to a PNG image file."""
        self._check_scene_editor()
        self.scene_editor.save_png(file_name)

    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."""
        self._check_scene_editor()
        self.scene_editor.save_jpg(file_name, quality, progressive)

    def save_iv(self, file_name):
        """Save to an OpenInventor file."""
        self._check_scene_editor()
        self.scene_editor.save_iv(file_name)

    def save_vrml(self, file_name):
        """Save to a VRML file."""
        self._check_scene_editor()
        self.scene_editor.save_vrml(file_name)

    def save_oogl(self, file_name):
        """Saves the scene to a Geomview OOGL file. Requires VTK 4 to
        work."""
        self._check_scene_editor()
        self.scene_editor.save_oogl(file_name)

    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.
        """
        self._check_scene_editor()
        self.scene_editor.save_rib(file_name, bg, resolution, resfactor)

    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
        """
        self._check_scene_editor()
        self.scene_editor.save_wavefront(file_name)

    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.
        """
        self._check_scene_editor()
        self.scene_editor.save_gl2ps(file_name, exp)

    def save_povray(self, file_name):
        self._check_scene_editor()
        self.scene_editor.save_povray(file_name)

    def save_x3d(self, filename):
        self._check_scene_editor()
        self.scene_editor.save_x3d(file_name)

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

    def set_size(self, size):
        """Set the size of the window."""
        self._check_scene_editor()
        self.scene_editor.set_size(size)

    def _update_view(self, x, y, z, vx, vy, vz):
        """Used internally to set the view."""
        if self.scene_editor is not None:
            self.scene_editor._update_view(x, y, z, vx, vy, vz)

    def _check_scene_editor(self):
        if self.scene_editor is None:
            msg = """
            This method requires that there be an active scene editor.
            To do this, you will typically need to invoke::
              object.edit_traits()
            where object is the object that contains the SceneModel.
            """
            raise SceneModelError(msg)

    def _scene_editor_changed(self, old, new):
        if new is None:
            self._renderer = None
            self._renwin = None
            self._interactor = None
        else:
            self._renderer = new._renderer
            self._renwin = new._renwin
            self._interactor = new._interactor

    def _get_picker(self):
        """Getter for the picker."""
        se = self.scene_editor
        if se is not None and hasattr(se, 'picker'):
            return se.picker
        return None

    def _get_light_manager(self):
        """Getter for the light manager."""
        se = self.scene_editor
        if se is not None:
            return se.light_manager
        return None

    ######################################################################
    # SceneModel API.
    ######################################################################
    def _get_scene(self):
        """Getter for the scene property."""
        return self
示例#13
0
class MEditor(MWorkbenchPart):
    """ Mixin containing common code for toolkit-specific implementations. """

    # 'IEditor' interface -------------------------------------------------#

    # The optional command stack.
    command_stack = Instance("pyface.undo.api.ICommandStack")

    # Is the object that the editor is editing 'dirty' i.e., has it been
    # modified but not saved?
    dirty = Bool(False)

    # The object that the editor is editing.
    #
    # The framework sets this when the editor is created.
    obj = Any()

    # Editor Lifecycle Events ---------------------------------------------#

    # Fired when the editor is opening.
    opening = VetoableEvent()

    # Fired when the editor has been opened.
    open = Event()

    # Fired when the editor is closing.
    closing = Event(VetoableEvent)

    # Fired when the editor is closed.
    closed = Event()

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

    def __str__(self):
        """ Return an informal string representation of the object. """

        return "Editor(%s)" % self.id

    # ------------------------------------------------------------------------
    # 'IWorkbenchPart' interface.
    # ------------------------------------------------------------------------

    def _id_default(self):
        """ Trait initializer. """

        # If no Id is specified then use a random uuid
        # this gaurantees (barring *really* unusual cases) that there are no
        # collisions between the ids of editors.
        return uuid.uuid4().hex

    # ------------------------------------------------------------------------
    # 'IEditor' interface.
    # ------------------------------------------------------------------------

    def close(self):
        """ Close the editor. """

        if self.control is not None:
            self.closing = event = Vetoable()
            if not event.veto:
                self.window.close_editor(self)

                self.closed = True

        return

    # Initializers ---------------------------------------------------------

    def _command_stack_default(self):
        """ Trait initializer. """

        # We make sure the undo package is entirely optional.
        try:
            from pyface.undo.api import CommandStack
        except ImportError:
            return None

        return CommandStack(undo_manager=self.window.workbench.undo_manager)
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
示例#15
0
文件: view.py 项目: kozmaz87/traitsui
class View(ViewElement):
    """ A Traits-based user interface for one or more objects.

        The attributes of the View object determine the contents and layout of
        an attribute-editing window. A View object contains a set of Group,
        Item, and Include objects. A View object can be an attribute of an
        object derived from HasTraits, or it can be a standalone object.
    """

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

    #: A unique identifier for the view:
    id = AnId

    #: The top-level Group object for the view:
    content = Content

    #: The menu bar for the view. Usually requires a custom **handler**:
    menubar = Any  # Instance( pyface.action.MenuBarManager )

    #: The toolbar for the view. Usually requires a custom **handler**:
    toolbar = Any  # Instance( pyface.action.ToolBarManager )

    #: Status bar items to add to the view's status bar. The value can be:
    #:
    #:   - **None**: No status bar for the view (the default).
    #:   - string: Same as [ StatusItem( name = string ) ].
    #:   - StatusItem: Same as [ StatusItem ].
    #:   - [ [StatusItem|string], ... ]: Create a status bar with one field for
    #:     each StatusItem in the list (or tuple). The status bar fields are
    #:     defined from left to right in the order specified. A string value is
    #:     converted to: StatusItem( name = string ):
    statusbar = ViewStatus

    #: List of button actions to add to the view. The **traitsui.menu**
    #: module defines standard buttons, such as **OKButton**, and standard sets
    #: of buttons, such as **ModalButtons**, which can be used to define a value
    #: for this attribute. This value can also be a list of button name strings,
    #: such as ``['OK', 'Cancel', 'Help']``. If set to the empty list, the
    #: view contains a default set of buttons (equivalent to **LiveButtons**:
    #: Undo/Redo, Revert, OK, Cancel, Help). To suppress buttons in the view,
    #: use the **NoButtons** variable, defined in **traitsui.menu**.
    buttons = Buttons

    #: The default button to activate when Enter is pressed. If not specified,
    #: pressing Enter will not activate any button.
    default_button = AButton

    #: The set of global key bindings for the view. Each time a key is pressed
    #: while the view has keyboard focus, the key is checked to see if it is one
    #: of the keys recognized by the KeyBindings object. If it is, the matching
    #: KeyBinding's method name is checked to see if it is defined on any of the
    #: object's in the view's context. If it is, the method is invoked. If the
    #: result of the method is **False**, then the search continues with the
    #: next object in the context. If any invoked method returns a non-False
    #: value, processing stops and the key is marked as having been handled. If
    #: all invoked methods return **False**, or no matching KeyBinding object is
    #: found, the key is processed normally. If the view has a non-empty *id*
    #: trait, the contents of the **KeyBindings** object will be saved as part
    #: of the view's persistent data:
    key_bindings = AKeyBindings

    #: The Handler object that provides GUI logic for handling events in the
    #: window. Set this attribute only if you are using a custom Handler. If
    #: not set, the default Traits UI Handler is used.
    handler = AHandler

    #: The factory function for converting a model into a model/view object:
    model_view = AModelView

    #: Title for the view, displayed in the title bar when the view appears as a
    #: secondary window (i.e., dialog or wizard). If not specified, "Edit
    #: properties" is used as the title.
    title = ATitle

    #: The name of the icon to display in the dialog window title bar:
    icon = Image

    #: The kind of user interface to create:
    kind = AKind

    #: The default object being edited:
    object = AnObject

    #: The default editor style of elements in the view:
    style = EditorStyle

    #: The default docking style to use for sub-groups of the view. The following
    #: values are possible:
    #:
    #: * 'fixed': No rearrangement of sub-groups is allowed.
    #: * 'horizontal': Moveable elements have a visual "handle" to the left by
    #:   which the element can be dragged.
    #: * 'vertical': Moveable elements have a visual "handle" above them by
    #:   which the element can be dragged.
    #: * 'tabbed': Moveable elements appear as tabbed pages, which can be
    #:   arranged within the window or "stacked" so that only one appears at
    #:   at a time.
    dock = DockStyle

    #: The image to display on notebook tabs:
    image = Image

    #: Called when modal changes are applied or reverted:
    on_apply = OnApply

    #: Can the user resize the window?
    resizable = IsResizable

    #: Can the user scroll the view? If set to True, window-level scroll bars
    #: appear whenever the window is too small to show all of its contents at
    #: one time. If set to False, the window does not scroll, but individual
    #: widgets might still contain scroll bars.
    scrollable = IsScrollable

    #: The category of exported elements:
    export = ExportType

    #: The valid categories of imported elements:
    imports = ImportTypes

    #: External help context identifier, which can be used by a custom help
    #: handler. This attribute is ignored by the default help handler.
    help_id = HelpId

    #: Requested x-coordinate (horizontal position) for the view window. This
    #: attribute can be specified in the following ways:
    #:
    #: * A positive integer: indicates the number of pixels from the left edge
    #:   of the screen to the left edge of the window.
    #: * A negative integer: indicates the number of pixels from the right edge
    #:   of the screen to the right edge of the window.
    #: * A floating point value between 0 and 1: indicates the fraction of the
    #:   total screen width between the left edge of the screen and the left edge
    #:   of the window.
    #: * A floating point value between -1 and 0: indicates the fraction of the
    #:   total screen width between the right edge of the screen and the right
    #:   edge of the window.
    x = XCoordinate

    #: Requested y-coordinate (vertical position) for the view window. This
    #: attribute behaves exactly like the **x** attribute, except that its value
    #: indicates the position of the top or bottom of the view window relative
    #: to the top or bottom of the screen.
    y = YCoordinate

    #: Requested width for the view window, as an (integer) number of pixels, or
    #: as a (floating point) fraction of the screen width.
    width = Width

    #: Requested height for the view window, as an (integer) number of pixels, or
    #: as a (floating point) fraction of the screen height.
    height = Height

    #: Class of dropped objects that can be added:
    drop_class = Any()

    #: Event when the view has been updated:
    updated = Event()

    #: What result should be returned if the user clicks the window or dialog
    #: close button or icon?
    close_result = CloseResult

    #: Note: Group objects delegate their 'object' and 'style' traits to the
    #: View

    # -- Deprecated Traits (DO NOT USE) ---------------------------------------

    ok = Bool(False)
    cancel = Bool(False)
    undo = Bool(False)
    redo = Bool(False)
    apply = Bool(False)
    revert = Bool(False)
    help = Bool(False)

    def __init__(self, *values, **traits):
        """ Initializes the object.
        """
        ViewElement.__init__(self, **traits)
        self.set_content(*values)

    def set_content(self, *values):
        """ Sets the content of a view.
        """
        content = []
        accum = []
        for value in values:
            if isinstance(value, ViewSubElement):
                content.append(value)
            elif type(value) in SequenceTypes:
                content.append(Group(*value))
            elif (isinstance(value, str) and (value[:1] == "<")
                  and (value[-1:] == ">")):
                # Convert string to an Include value:
                content.append(Include(value[1:-1].strip()))
            else:
                content.append(Item(value))

        # If there are any 'Item' objects in the content, wrap the content in a
        # Group:
        for item in content:
            if isinstance(item, Item):
                content = [Group(*content)]
                break

        # Wrap all of the content up into a Group and save it as our content:
        self.content = Group(container=self, *content)

    def ui(
        self,
        context,
        parent=None,
        kind=None,
        view_elements=None,
        handler=None,
        id="",
        scrollable=None,
        args=None,
    ):
        """ Creates a **UI** object, which generates the actual GUI window or
        panel from a set of view elements.

        Parameters
        ----------
        context : object or dictionary
            A single object or a dictionary of string/object pairs, whose trait
            attributes are to be edited. If not specified, the current object is
            used.
        parent : window component
            The window parent of the View object's window
        kind : string
            The kind of window to create. See the **AKind** trait for details.
            If *kind* is unspecified or None, the **kind** attribute of the
            View object is used.
        view_elements : ViewElements object
            The set of Group, Item, and Include objects contained in the view.
            Do not use this parameter when calling this method directly.
        handler : Handler object
            A handler object used for event handling in the dialog box. If
            None, the default handler for Traits UI is used.
        id : string
            A unique ID for persisting preferences about this user interface,
            such as size and position. If not specified, no user preferences
            are saved.
        scrollable : Boolean
            Indicates whether the dialog box should be scrollable. When set to
            True, scroll bars appear on the dialog box if it is not large enough
            to display all of the items in the view at one time.

        """
        handler = handler or self.handler or default_handler()
        if not isinstance(handler, Handler):
            handler = handler()

        if args is not None:
            handler.trait_set(**args)

        if not isinstance(context, dict):
            context = context.trait_context()

        context.setdefault("handler", handler)
        handler = context["handler"]

        if self.model_view is not None:
            context["object"] = self.model_view(context["object"])

        self_id = self.id
        if self_id != "":
            if id != "":
                id = "%s:%s" % (self_id, id)
            else:
                id = self_id

        if scrollable is None:
            scrollable = self.scrollable

        ui = UI(
            view=self,
            context=context,
            handler=handler,
            view_elements=view_elements,
            title=self.title,
            id=id,
            scrollable=scrollable,
        )

        if kind is None:
            kind = self.kind

        ui.ui(parent, kind)

        return ui

    def replace_include(self, view_elements):
        """ Replaces any items that have an ID with an Include object with
            the same ID, and puts the object with the ID into the specified
            ViewElements object.
        """
        if self.content is not None:
            self.content.replace_include(view_elements)

    def __repr__(self):
        """ Returns a "pretty print" version of the View.
        """
        if self.content is None:
            return "()"
        return "( %s )" % ", ".join(
            [item.__repr__() for item in self.content.content])
示例#16
0
 class DataFrameViewer(HasTraits):
     data = Instance(DataFrame)
     df_refreshed = Event()
     view = View(
         Item("data", editor=DataFrameEditor(refresh="df_refreshed")))
示例#17
0
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 = Dict(Str, Any)

    #: 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(depends_on=["view._key_bindings", "context"])

    #: 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 = Image

    #: 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 = Dict(Str, Any)

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

    #: 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()

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

    #: 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)

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

    #: 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",
        "_key_bindings",
        "_focus_control",
    ]

    #: 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",
    ]

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

    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)

    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()

    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)

    def finish(self):
        """ Finishes disposing of a user interface.
        """
        # Destroy the control early to silence cascade events when the UI
        # enters an inconsistent state.
        toolkit().destroy_control(self.control)

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

        # 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
        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

    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()

    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 = list(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

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

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

    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.trait_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

    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))

    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

    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

    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 is not 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

    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()

    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 is not None:
                    ui_prefs[name] = prefs

        return ui_prefs

    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

    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]

    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

    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)

    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...

    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...

    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...

    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

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

    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

    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)

    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

    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 = list(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

    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)

    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)

    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 ---------------------------------------------

    def _get_key_bindings(self):
        if self._key_bindings is None:
            # create a new key_bindings instance lazily

            view, context = self.view, self.context
            if (view is None) or (context is None):
                return None

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

                self._key_bindings = KeyBindings(controllers=values)
            else:
                self._key_bindings = key_bindings.clone(controllers=values)

        return self._key_bindings

    # -- 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)
示例#18
0
class Window(MWindow, Widget):
    """ The toolkit specific implementation of a Window.  See the IWindow
    interface for the API documentation.
    """

    # 'IWindow' interface -------------------------------------------------#

    position = Property(Tuple)

    size = Property(Tuple)

    title = Str()

    # Events -----

    activated = Event()

    closed = Event()

    closing = Event()

    deactivated = Event()

    key_pressed = Event(KeyPressedEvent)

    opened = Event()

    opening = Event()

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

    # Shadow trait for position.
    _position = Tuple((-1, -1))

    # Shadow trait for size.
    _size = Tuple((-1, -1))

    # ------------------------------------------------------------------------
    # 'IWindow' interface.
    # ------------------------------------------------------------------------

    def show(self, visible):
        pass

    # ------------------------------------------------------------------------
    # Protected 'IWindow' interface.
    # ------------------------------------------------------------------------

    def _add_event_listeners(self):
        pass

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

    def _get_position(self):
        """ Property getter for position. """

        return self._position

    def _set_position(self, position):
        """ Property setter for position. """

        old = self._position
        self._position = position

        self.trait_property_changed("position", old, position)

    def _get_size(self):
        """ Property getter for size. """

        return self._size

    def _set_size(self, size):
        """ Property setter for size. """

        old = self._size
        self._size = size

        self.trait_property_changed("size", old, size)
示例#19
0
class MRISubjectSource(HasPrivateTraits):
    """Find subjects in SUBJECTS_DIR and select one.

    Parameters
    ----------
    subjects_dir : directory
        SUBJECTS_DIR.
    subject : str
        Subject, corresponding to a folder in SUBJECTS_DIR.
    """

    refresh = Event(desc="Refresh the subject list based on the directory "
                    "structure of subjects_dir.")

    # settings
    subjects_dir = Directory(exists=True)
    subjects = Property(List(Str), depends_on=['subjects_dir', 'refresh'])
    subject = Enum(values='subjects')

    # info
    can_create_fsaverage = Property(Bool, depends_on=['subjects_dir',
                                                      'subjects'])
    subject_has_bem = Property(Bool, depends_on=['subjects_dir', 'subject'],
                               desc="whether the subject has a file matching "
                               "the bem file name pattern")
    bem_pattern = Property(depends_on='mri_dir')

    @cached_property
    def _get_can_create_fsaverage(self):
        if not op.exists(self.subjects_dir) or 'fsaverage' in self.subjects:
            return False
        return True

    @cached_property
    def _get_mri_dir(self):
        if not self.subject:
            return
        elif not self.subjects_dir:
            return
        else:
            return op.join(self.subjects_dir, self.subject)

    @cached_property
    def _get_subjects(self):
        sdir = self.subjects_dir
        is_dir = sdir and op.isdir(sdir)
        if is_dir:
            dir_content = os.listdir(sdir)
            subjects = [s for s in dir_content if _is_mri_subject(s, sdir)]
            if len(subjects) == 0:
                subjects.append('')
        else:
            subjects = ['']

        return sorted(subjects)

    @cached_property
    def _get_subject_has_bem(self):
        if not self.subject:
            return False
        return _mri_subject_has_bem(self.subject, self.subjects_dir)

    def create_fsaverage(self):  # noqa: D102
        if not self.subjects_dir:
            raise RuntimeError(
                "No subjects directory is selected. Please specify "
                "subjects_dir first.")

        fs_home = get_fs_home()
        if fs_home is None:
            raise RuntimeError(
                "FreeSurfer contains files that are needed for copying the "
                "fsaverage brain. Please install FreeSurfer and try again.")

        create_default_subject(fs_home=fs_home, update=True,
                               subjects_dir=self.subjects_dir)
        self.refresh = True
        self.subject = 'fsaverage'

    @on_trait_change('subjects_dir')
    def _emit_subject(self):
        # This silliness is the only way I could figure out to get the
        # on_trait_change('subject_panel.subject') in CoregFrame to work!
        self.subject = self.subject
示例#20
0
class MRIHeadWithFiducialsModel(HasPrivateTraits):
    """Represent an MRI head shape (high and low res) with fiducials.

    Attributes
    ----------
    points : array (n_points, 3)
        MRI head surface points.
    tris : array (n_tris, 3)
        Triangles based on points.
    lpa : array (1, 3)
        Left peri-auricular point coordinates.
    nasion : array (1, 3)
        Nasion coordinates.
    rpa : array (1, 3)
        Right peri-auricular point coordinates.
    """

    subject_source = Instance(MRISubjectSource, ())
    bem_low_res = Instance(SurfaceSource, ())
    bem_high_res = Instance(SurfaceSource, ())
    fid = Instance(FiducialsSource, ())

    fid_file = DelegatesTo('fid', 'file')
    fid_fname = DelegatesTo('fid', 'fname')
    fid_points = DelegatesTo('fid', 'points')
    subjects_dir = DelegatesTo('subject_source')
    subject = DelegatesTo('subject_source')
    subject_has_bem = DelegatesTo('subject_source')
    lpa = Array(float, (1, 3))
    nasion = Array(float, (1, 3))
    rpa = Array(float, (1, 3))

    reset = Event(desc="Reset fiducials to the file.")

    # info
    can_save = Property(depends_on=['file', 'can_save_as'])
    can_save_as = Property(depends_on=['lpa', 'nasion', 'rpa'])
    can_reset = Property(depends_on=['file', 'fid.points', 'lpa', 'nasion',
                                     'rpa'])
    fid_ok = Property(depends_on=['lpa', 'nasion', 'rpa'], desc="All points "
                      "are set")
    default_fid_fname = Property(depends_on=['subjects_dir', 'subject'],
                                 desc="the default file name for the "
                                 "fiducials fif file")

    # switch for the GUI (has no effect in the model)
    lock_fiducials = Bool(False, desc="Used by GIU, has no effect in the "
                          "model.")

    @on_trait_change('fid_points')
    def reset_fiducials(self):  # noqa: D102
        if self.fid_points is not None:
            self.lpa = self.fid_points[0:1]
            self.nasion = self.fid_points[1:2]
            self.rpa = self.fid_points[2:3]

    def save(self, fname=None):
        """Save the current fiducials to a file.

        Parameters
        ----------
        fname : str
            Destination file path. If None, will use the current fid filename
            if available, or else use the default pattern.
        """
        if fname is None:
            fname = self.fid_file
        if not fname:
            fname = self.default_fid_fname

        dig = [{'kind': FIFF.FIFFV_POINT_CARDINAL,
                'ident': FIFF.FIFFV_POINT_LPA,
                'r': np.array(self.lpa[0])},
               {'kind': FIFF.FIFFV_POINT_CARDINAL,
                'ident': FIFF.FIFFV_POINT_NASION,
                'r': np.array(self.nasion[0])},
               {'kind': FIFF.FIFFV_POINT_CARDINAL,
                'ident': FIFF.FIFFV_POINT_RPA,
                'r': np.array(self.rpa[0])}]
        write_fiducials(fname, dig, FIFF.FIFFV_COORD_MRI)
        self.fid_file = fname

    @cached_property
    def _get_can_reset(self):
        if not self.fid_file:
            return False
        elif np.any(self.lpa != self.fid.points[0:1]):
            return True
        elif np.any(self.nasion != self.fid.points[1:2]):
            return True
        elif np.any(self.rpa != self.fid.points[2:3]):
            return True
        return False

    @cached_property
    def _get_can_save_as(self):
        can = not (np.all(self.nasion == self.lpa) or
                   np.all(self.nasion == self.rpa) or
                   np.all(self.lpa == self.rpa))
        return can

    @cached_property
    def _get_can_save(self):
        if not self.can_save_as:
            return False
        elif self.fid_file:
            return True
        elif self.subjects_dir and self.subject:
            return True
        else:
            return False

    @cached_property
    def _get_default_fid_fname(self):
        fname = fid_fname.format(subjects_dir=self.subjects_dir,
                                 subject=self.subject)
        return fname

    @cached_property
    def _get_fid_ok(self):
        return all(np.any(pt) for pt in (self.nasion, self.lpa, self.rpa))

    def _reset_fired(self):
        self.reset_fiducials()

    # if subject changed because of a change of subjects_dir this was not
    # triggered
    @on_trait_change('subjects_dir,subject')
    def _subject_changed(self):
        subject = self.subject
        subjects_dir = self.subjects_dir
        if not subjects_dir or not subject:
            return

        # find high-res head model (if possible)
        high_res_path = _find_head_bem(subject, subjects_dir, high_res=True)
        low_res_path = _find_head_bem(subject, subjects_dir, high_res=False)
        if high_res_path is None and low_res_path is None:
            msg = 'No standard head model was found for subject %s' % subject
            error(None, msg, "No head surfaces found")
            raise RuntimeError(msg)
        if high_res_path is not None:
            self.bem_high_res.file = high_res_path
        else:
            self.bem_high_res.file = low_res_path
        if low_res_path is None:
            # This should be very rare!
            warn('No low-resolution head found, decimating high resolution '
                 'mesh (%d vertices): %s' % (len(self.bem_high_res.surf.rr),
                                             high_res_path,))
            # Create one from the high res one, which we know we have
            rr, tris = decimate_surface(self.bem_high_res.surf.rr,
                                        self.bem_high_res.surf.tris,
                                        n_triangles=5120)
            surf = complete_surface_info(dict(rr=rr, tris=tris),
                                         copy=False, verbose=False)
            # directly set the attributes of bem_low_res
            self.bem_low_res.surf = Surf(tris=surf['tris'], rr=surf['rr'],
                                         nn=surf['nn'])
        else:
            self.bem_low_res.file = low_res_path

        # Set MNI points
        try:
            fids = get_mni_fiducials(subject, subjects_dir)
        except Exception:  # some problem, leave at origin
            self.fid.mni_points = None
        else:
            self.fid.mni_points = np.array([f['r'] for f in fids], float)

        # find fiducials file
        fid_files = _find_fiducials_files(subject, subjects_dir)
        if len(fid_files) == 0:
            self.fid.reset_traits(['file'])
            self.lock_fiducials = False
        else:
            self.fid_file = fid_files[0].format(subjects_dir=subjects_dir,
                                                subject=subject)
            self.lock_fiducials = True

        # does not seem to happen by itself ... so hard code it:
        self.reset_fiducials()
class ScriptManager(HasTraits):
    """ The ScriptManager class is the default implementation of
    IScriptManager.
    """

    #### 'IScriptManager' interface ###########################################

    # This event is fired whenever a scriptable object is bound or unbound.  It
    # is intended to be used by an interactive Python shell to give the
    # advanced user access to the scriptable objects.  If an object is created
    # via a factory then the event is fired when the factory is called, and not
    # when the factory is bound.
    bind_event = Event(IBindEvent)

    # This is set if user actions are being recorded as a script.  It is
    # maintained by the script manager.
    recording = Bool(False)

    # This is the text of the script currently being recorded (or the last
    # recorded script if none is currently being recorded).  It is updated
    # automatically as the user performs actions.
    script = Property(Unicode)

    # This event is fired when the recorded script changes.  The value of the
    # event will be the ScriptManager instance.
    script_updated = Event(IScriptManager)

    #### Private interface ####################################################

    # The list of calls to scriptable calls.
    _calls = List(Instance(_ScriptCall))

    # The dictionary of bound names.  The value is the next numerical suffix
    # to use when the binding policy is 'auto'.
    _names = Dict

    # The dictionary of _BoundObject instances keyed by the name the object is
    # bound to.
    _namespace = Dict

    # The next sequential result number.
    _next_result_nr = Int

    # The results returned by previous scriptable calls.  The key is the id()
    # of the result object.  The value is a two element tuple of the sequential
    # result number (easier for the user to use than the id()) and the result
    # object itself.
    _results = Dict

    # The dictionary of _ScriptObject instances keyed by the object's id().
    _so_by_id = Dict

    # The dictionary of _ScriptObject instances keyed by the a weak reference
    # to the object.
    _so_by_ref = Dict

    # The date and time when the script was recorded.
    _when_started = Any

    ###########################################################################
    # 'IScriptManager' interface.
    ###########################################################################

    def bind(self,
             obj,
             name=None,
             bind_policy='unique',
             api=None,
             includes=None,
             excludes=None):
        """ Bind obj to name and make (by default) its public methods and
        traits (ie. those not beginning with an underscore) scriptable.  The
        default value of name is the type of obj with the first character
        forced to lower case.  name may be a dotted name (eg. 'abc.def.xyz').

        bind_policy determines what happens if the name is already bound.  If
        the policy is 'auto' then a numerical suffix will be added to the name,
        if necessary, to make it unique.  If the policy is 'unique' then an
        exception is raised.  If the policy is 'rebind' then the previous
        binding is discarded.  The default is 'unique'

        If api is given then it is a class, or a list of classes, that define
        the attributes that will be made scriptable.

        Otherwise if includes is given it is a list of names of attributes that
        will be made scriptable.

        Otherwise all the public attributes of scripted_type will be made
        scriptable except those in the excludes list.
        """

        # Register the object.
        self.new_object(obj, obj.__class__, name=name, bind_policy=bind_policy)

        # Make it scriptable.
        make_object_scriptable(obj,
                               api=api,
                               includes=includes,
                               excludes=excludes)

    def bind_factory(self,
                     factory,
                     name,
                     bind_policy='unique',
                     api=None,
                     includes=None,
                     excludes=None):
        """ Bind factory to name.  This does the same as the bind() method
        except that it uses a factory that will be called later on to create
        the object only if the object is needed.

        See the documentation for bind() for a description of the remaining
        arguments.
        """

        name = self._unique_name(name, bind_policy)
        self._namespace[name] = _FactoryObject(name=name,
                                               factory=factory,
                                               api=api,
                                               includes=includes,
                                               excludes=excludes)

    def run(self, script):
        """ Run the given script, either a string or a file-like object.
        """

        # Initialise the namespace with all explicitly bound objects.
        nspace = LazyNamespace()
        for name, bo in self._namespace.items():
            if bo.explicitly_bound:
                add_to_namespace(bo.obj, name, nspace)

        exec(script, nspace)

    def run_file(self, file_name):
        """ Run the given script file.
        """

        f = open(file_name)
        self.run(f)
        f.close()

    def start_recording(self):
        """ Start the recording of user actions.  The 'script' trait is cleared
        and all subsequent actions are added to 'script'.  The 'recording'
        trait is updated appropriately.
        """

        self._calls = []
        self._next_result_nr = 0
        self._results = {}

        self.recording = True
        self.script_updated = self

    def stop_recording(self):
        """ Stop the recording of user actions.  The 'recording' trait is
        updated appropriately.
        """

        self.recording = False

    ###########################################################################
    # 'ScriptManager' interface.
    ###########################################################################

    def record_method(self, func, args, kwargs):
        """ Record the call of a method of a ScriptableObject instance and
        return the result.  This is intended to be used only by the scriptable
        decorator.
        """
        if self.recording:
            # Record the arguments before the function has a chance to modify
            # them.
            srec = self._new_method(func, args, kwargs)
            result = func(*args, **kwargs)
            self._add_method(srec, result)

            self.script_updated = self
        else:
            result = func(*args, **kwargs)

        return result

    def record_trait_get(self, obj, name, result):
        """ Record the get of a trait of a scriptable object.  This is intended
        to be used only by the Scriptable trait getter.
        """

        if self.recording:
            side_effects = self._add_trait_get(obj, name, result)

            # Don't needlessly fire the event if there are no side effects.
            if side_effects:
                self.script_updated = self

    def record_trait_set(self, obj, name, value):
        """ Record the set of a trait of a scriptable object.  This is intended
        to be used only by the Scriptable trait getter.
        """

        if self.recording:
            self._add_trait_set(obj, name, value)

            self.script_updated = self

    def new_object(self,
                   obj,
                   scripted_type,
                   args=None,
                   kwargs=None,
                   name=None,
                   bind_policy='auto'):
        """ Register a scriptable object and the arguments used to create it.
        If no arguments were provided then assume the object is being
        explicitly bound.
        """

        # The name defaults to the type name.
        if not name:
            name = scripted_type.__name__
            name = name[0].lower() + name[1:]

        name = self._unique_name(name, bind_policy)

        obj_id = id(obj)
        obj_ref = weakref.ref(obj, self._gc_script_obj)

        so = _ScriptObject(name=name,
                           obj_id=obj_id,
                           obj_ref=obj_ref,
                           scripted_type=scripted_type)

        # If we are told how to create the object then it must be implicitly
        # bound.
        if args is not None:
            # Convert each argument to its string representation if possible.
            # Doing this now avoids problems with mutable arguments.
            so.args = [self._scriptable_object_as_string(a) for a in args]

            for n, value in kwargs.items():
                so.kwargs[n] = self._scriptable_object_as_string(value)

            so.explicitly_bound = False

        # Remember the scriptable object via the different access methods.
        self._so_by_id[obj_id] = so
        self._so_by_ref[obj_ref] = so
        self._namespace[name] = so

        # Note that if anything listening to this event doesn't use weak
        # references then the object will be kept alive.
        self.bind_event = BindEvent(name=name, obj=obj)

    @staticmethod
    def args_as_string_list(args, kwargs, so_needed=None):
        """ Return a complete argument list from sets of positional and keyword
        arguments.  Update the optional so_needed list for those arguments that
        refer to a scriptable object.
        """

        if so_needed is None:
            so_needed = []

        all_args = []

        for arg in args:
            s = ScriptManager.arg_as_string(arg, so_needed)
            all_args.append(s)

        for name, value in kwargs.items():
            s = ScriptManager.arg_as_string(value, so_needed)
            all_args.append('%s=%s' % (name, s))

        return all_args

    @staticmethod
    def arg_as_string(arg, so_needed):
        """ Return the string representation of an argument.  Update the
        so_needed list if the argument refers to a scriptable object.  Any
        delayed conversion exception is handled here.
        """

        if isinstance(arg, Exception):
            raise arg

        if isinstance(arg, _ScriptObject):
            # Check it hasn't been unbound.
            if not arg.name:
                raise NameError(
                    "%s has been unbound but is needed by the script" %
                    arg.obj_ref())

            # Add it to the needed list if it isn't already there.
            if arg not in so_needed:
                so_needed.append(arg)

            arg = arg.name

        return arg

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

    def _new_method(self, func, args, kwargs):
        """ Return an object that encapsulates a call to a scriptable method.
        _add_method() must be called to add it to the current script.
        """

        # Convert each argument to its string representation if possible.
        # Doing this now avoids problems with mutable arguments.
        nargs = [self._object_as_string(arg) for arg in args]

        if type(func) is types.FunctionType:
            so = None
        else:
            so = nargs[0]
            nargs = nargs[1:]

        nkwargs = {}
        for name, value in kwargs.items():
            nkwargs[name] = self._object_as_string(value)

        return _ScriptMethod(name=func.__name__,
                             so=so,
                             args=nargs,
                             kwargs=nkwargs)

    def _add_method(self, entry, result):
        """ Add a method call (returned by _new_method()), with it's associated
        result and ID, to the current script.
        """

        self._start_script()

        if result is not None:
            # Assume that a tuple represents multiple returned values - not
            # necessarily a valid assumption unless we make it a rule for
            # scriptable functions.
            if type(result) is type(()):
                for r in result:
                    self._save_result(r)
            else:
                self._save_result(result)

            entry.result = result

        self._calls.append(entry)

    def _add_trait_get(self, obj, name, result):
        """ Add a call to a trait getter, with it's associated result and ID,
        to the current script.  Return True if the get had side effects.
        """

        self._start_script()

        side_effects = obj.trait(name).has_side_effects

        if side_effects is None:
            side_effects = False

        so = self._object_as_string(obj)

        if result is not None:
            self._save_result(result)

        self._calls.append(
            _ScriptTraitGet(so=so,
                            name=name,
                            result=result,
                            has_side_effects=side_effects))

        return side_effects

    def _add_trait_set(self, obj, name, value):
        """ Add a call to a trait setter, with it's associated value and ID,
        to the current script.
        """

        self._start_script()

        so = self._object_as_string(obj)
        value = self._object_as_string(value)

        self._calls.append(_ScriptTraitSet(so=so, name=name, value=value))

    def _unique_name(self, name, bind_policy):
        """ Return a name that is guaranteed to be unique according to the bind
        policy.
        """

        # See if the name is already is use.
        bo = self._namespace.get(name)

        if bo is None:
            self._names[name] = 1
        elif bind_policy == 'auto':
            suff = self._names[name]
            self._names[name] = suff + 1

            name = '%s%d' % (name, suff)
        elif bind_policy == 'rebind':
            self._unbind(bo)
        else:
            raise NameError("\"%s\" is already bound to a scriptable object" %
                            name)

        return name

    def _unbind(self, bo):
        """Unbind the given bound object."""

        # Tell everybody it is no longer bound.  Don't bother if it is a
        # factory because the corresponding bound event wouldn't have been
        # fired.
        if not isinstance(bo, _FactoryObject):
            self.bind_event = BindEvent(name=bo.name, obj=None)

        # Forget about it.
        del self._namespace[bo.name]
        bo.name = ''

    @staticmethod
    def _gc_script_obj(obj_ref):
        """ The callback invoked when a scriptable object is garbage collected.
        """

        # Avoid recursive imports.
        from .package_globals import get_script_manager

        sm = get_script_manager()
        so = sm._so_by_ref[obj_ref]

        if so.name:
            sm._unbind(so)

        del sm._so_by_id[so.obj_id]
        del sm._so_by_ref[so.obj_ref]

    def _start_script(self):
        """ Save when a script recording is started. """

        if len(self._calls) == 0:
            self._when_started = datetime.datetime.now().strftime('%c')

    def _object_as_string(self, obj):
        """ Convert an object to a string as it will appear in a script.  An
        exception may be returned (not raised) if there was an error in the
        conversion.
        """

        obj_id = id(obj)

        # See if the argument is the result of a previous call.
        nr, _ = self._results.get(obj_id, (None, None))

        if nr is not None:
            if nr < 0:
                nr = self._next_result_nr
                self._next_result_nr += 1

                # Key on the ID of the argument (which is hashable) rather than
                # the argument itself (which might not be).
                self._results[obj_id] = (nr, obj)

            return "r%d" % nr

        return self._scriptable_object_as_string(obj)

    def _scriptable_object_as_string(self, obj):
        """ Convert an object to a string as it will appear in a script.  An
        exception may be returned (not raised) if there was an error in the
        conversion.
        """

        obj_id = id(obj)

        # If it is a scriptable object we return the object and convert it to a
        # string later when we know it is really needed.
        so = self._so_by_id.get(obj_id)

        if so is not None:
            return so

        # Use the repr result if it doesn't appear to be the generic response,
        # ie. it doesn't contain its own address as a hex string.
        s = repr(obj)

        if hex(obj_id) not in s:
            return s

        # We don't know how to represent the argument as a string.  This is
        # most likely because an appropriate __init__ hasn't been made
        # scriptable.  We don't raise an exception until the user decides to
        # convert the calls to a script.
        return ValueError("unable to create a script representation of %s" %
                          obj)

    def _save_result(self, result):
        """ Save the result of a call to a scriptable method so that it can be
        recognised later.
        """

        if id(result) not in self._results:
            self._results[id(result)] = (-1, result)

    def _get_script(self):
        """ Convert the current list of calls to a script. """

        # Handle the trivial case.
        if len(self._calls) == 0:
            return ""

        # Generate the header.
        header = "# Script generated %s" % self._when_started

        # Generate the calls.
        so_needed = []
        calls = []

        for call in self._calls:
            s = call.as_str(self, so_needed)

            if s:
                calls.append(s)

        calls = "\n".join(calls)

        # Generate the scriptable object constructors.
        types_needed = []
        ctors = []

        for so in so_needed:
            if so.explicitly_bound:
                continue

            so_type = so.scripted_type
            args = self.args_as_string_list(so.args, so.kwargs)

            ctors.append("%s = %s(%s)" %
                         (so.name, so_type.__name__, ", ".join(args)))

            # See if a new import is needed.
            if so_type not in types_needed:
                types_needed.append(so_type)

        ctors = "\n".join(ctors)

        # Generate the import statements.
        imports = []

        for so_type in types_needed:
            imports.append("from %s import %s" %
                           (so_type.__module__, so_type.__name__))

        imports = "\n".join(imports)

        return "\n\n".join([header, imports, ctors, calls]) + "\n"
示例#22
0
class ProcessView(HasTraits):
    """A view object containing the process to optimise. This is a
    hierarchical construct consisting of execution layers containing
    data sources"""

    # -------------------
    # Required Attributes
    # -------------------

    #: The Process model
    model = Instance(Workflow)

    #: The Variable Names Registry
    variable_names_registry = Instance(VariableNamesRegistry)

    # ------------------
    # Regular Attributes
    # ------------------

    #: List of the data source's modelviews.
    #: Must be a list otherwise the tree editor will not consider it
    #: as a child.
    execution_layer_views = List(Instance(ExecutionLayerView))

    #: The label to display in the list
    label = Str('Process')

    # ---------------------
    #  Dependent Attributes
    # ---------------------

    #: Defines if the MCO is valid or not. Updated by
    #: :func:`verify_tree
    #: <force_wfmanager.ui.setup.workflow_tree.WorkflowTree.verify_tree>`
    valid = Bool(True)

    #: An error message for issues in this modelview. Updated by
    #: :func:`workflow_tree.WorkflowTree.verify_tree
    #: <force_wfmanager.ui.setup.workflow_tree.WorkflowTree.verify_tree>`
    error_message = Str()

    #: An event which runs a verification check on the current workflow when
    #: triggered.
    #: Listens to: :func:`execution_layer_views.verify_workflow_event`
    verify_workflow_event = Event()

    def __init__(self, model, *args, **kwargs):
        super(ProcessView, self).__init__(*args, **kwargs)
        # Assigns model after super instantiation in order to ensure
        # variable_names_registry has been assigned first
        self.model = model

    # -------------------
    #     Listeners
    # -------------------

    @on_trait_change('model.execution_layers[]')
    def update_execution_layers_views(self):
        """Update the ExecutionLayer ModelViews when the model changes."""
        self.execution_layer_views = [
            ExecutionLayerView(
                model=execution_layer,
                layer_index=idx,
                variable_names_registry=self.variable_names_registry,
                label="Layer {}".format(idx))
            for idx, execution_layer in enumerate(self.model.execution_layers)
        ]

    @on_trait_change('execution_layer_views.verify_workflow_event')
    def received_verify_request(self):
        """Fires :attr:`verify_workflow_event` when a data source contained
        in this execution layer fires its `verify_workflow_event`
        """
        self.verify_workflow_event = True

    # -------------------
    #   Public Methods
    # -------------------

    def add_execution_layer(self, execution_layer):
        """Adds a new empty execution layer"""
        self.model.execution_layers.append(execution_layer)

    def remove_execution_layer(self, layer):
        """Removes the execution layer from the model."""
        self.model.execution_layers.remove(layer)
        self.verify_workflow_event = True

    # NOTE: Currently needed by TreeEditor as a reference point
    def remove_data_source(self, data_source):
        """Removes the data source from the model"""
        for execution_layer_view in self.execution_layer_views:
            if data_source in execution_layer_view.model.data_sources:
                execution_layer_view.remove_data_source(data_source)
示例#23
0
class LRUCache(HasStrictTraits):
    """ A least-recently used cache.

    Items older than `size()` accesses are dropped from the cache.

    """

    size = Int

    # Called with the key and value that was dropped from the cache
    cache_drop_callback = Callable

    # This event contains the set of cached cell keys whenever it changes
    updated = Event()

    _lock = Instance(RLock, args=())

    _cache = Instance(OrderedDict)

    def __init__(self, size, **traits):
        self.size = size
        self._initialize_cache()
        super(LRUCache, self).__init__(**traits)

    def _initialize_cache(self):
        with self._lock:
            if self._cache is None:
                self._cache = OrderedDict()
            else:
                self._cache.clear()

    def _renew(self, key):
        with self._lock:
            r = self._cache.pop(key)
            self._cache[key] = r
        return r

    # -------------------------------------------------------------------------
    # LRUCache interface
    # -------------------------------------------------------------------------

    def __contains__(self, key):
        with self._lock:
            return key in self._cache

    def __len__(self):
        with self._lock:
            return len(self._cache)

    def __getitem__(self, key):
        with self._lock:
            return self._renew(key)

    def __setitem__(self, key, result):
        try:
            dropped = None
            with self._lock:
                self._cache[key] = result
                self._renew(key)
                if self.size < len(self._cache):
                    dropped = self._cache.popitem(last=False)
            if dropped and self.cache_drop_callback is not None:
                self.cache_drop_callback(*dropped)
        finally:
            self.updated = self.keys()

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def items(self):
        with self._lock:
            return self._cache.items()

    def keys(self):
        with self._lock:
            return self._cache.keys()

    def values(self):
        with self._lock:
            return self._cache.values()

    def clear(self):
        with self._lock:
            self._initialize_cache()
        self.updated = []
示例#24
0
class PythonEditor(MPythonEditor, Widget):
    """ The toolkit specific implementation of a PythonEditor.  See the
    IPythonEditor interface for the API documentation.
    """

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

    dirty = Bool(False)

    path = Unicode

    show_line_numbers = Bool(True)

    #### Events ####

    changed = Event

    key_pressed = Event(KeyPressedEvent)

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

    def __init__(self, parent, **traits):
        super(PythonEditor, self).__init__(**traits)
        self.control = self._create_control(parent)

    ###########################################################################
    # '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.code.setPlainText(text)
        self.dirty = False

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

        f = file(path, 'w')
        f.write(self.control.code.toPlainText())
        f.close()

        self.dirty = False

    def select_line(self, lineno):
        """ Selects the specified line.
        """
        self.control.code.set_line_column(lineno, 0)
        self.control.code.moveCursor(QtGui.QTextCursor.EndOfLine,
                                     QtGui.QTextCursor.KeepAnchor)

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

    def _path_changed(self):
        self._changed_path()

    def _show_line_numbers_changed(self):
        if self.control is not None:
            self.control.code.line_number_widget.setVisible(
                self.show_line_numbers)
            self.control.code.update_line_number_width()

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

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

        # Install event filter to trap key presses.
        event_filter = PythonEditorEventFilter(self, self.control)
        self.control.installEventFilter(event_filter)
        self.control.code.installEventFilter(event_filter)

        # Connect signals for text changes.
        control.code.modificationChanged.connect(self._on_dirty_changed)
        control.code.textChanged.connect(self._on_text_changed)

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

        return control

    def _on_dirty_changed(self, dirty):
        """ Called whenever a change is made to the dirty state of the
            document.
        """
        self.dirty = dirty

    def _on_text_changed(self):
        """ Called whenever a change is made to the text of the document.
        """
        self.changed = True
示例#25
0
class _ShellEditor(Editor):
    """ Base class for an editor that displays an interactive Python shell.
    """

    #: An event fired to execute a command in the shell.
    command_to_execute = Event()

    #: An event fired whenver the user executes a command in the shell:
    command_executed = Event(Bool)

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

    # -------------------------------------------------------------------------
    # 'Editor' Interface
    # -------------------------------------------------------------------------

    def init(self, parent):
        """ Finishes initializing the editor by creating the underlying toolkit
            widget.
        """
        # Moving the import here, since PythonShell is implemented in the
        # Pyface backend packages, and we want to delay loading this toolkit
        # specific class until this editor is actually used.
        from pyface.python_shell import PythonShell

        locals = None
        value = self.value
        if self.factory.share and isinstance(value, dict):
            locals = value
        self._shell = shell = PythonShell(parent, locals=locals)
        self.control = shell.control
        if locals is None:
            object = self.object
            shell.bind("self", object)
            shell.observe(self.update_object,
                          "command_executed",
                          dispatch="ui")
            if not isinstance(value, dict):
                object.observe(self.update_any, dispatch="ui")
            else:
                self._base_locals = locals = {}
                for name in self._shell.interpreter().locals.keys():
                    locals[name] = None

        # Synchronize any editor events:
        self.sync_value(self.factory.command_to_execute, "command_to_execute",
                        "from")
        self.sync_value(self.factory.command_executed, "command_executed",
                        "to")

        self.set_tooltip()

    def update_object(self, event):
        """ Handles the user entering input data in the edit control.
        """
        locals = self._shell.interpreter().locals
        base_locals = self._base_locals
        if base_locals is None:
            object = self.object
            for name in object.trait_names():
                if name in locals:
                    try:
                        setattr(object, name, locals[name])
                    except:
                        pass
        else:
            dic = self.value
            for name in locals.keys():
                if name not in base_locals:
                    try:
                        dic[name] = locals[name]
                    except:
                        pass

        self.command_executed = True

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        if self.factory.share:
            value = self.value
            if isinstance(value, dict):
                self._shell.interpreter().locals = value
        else:
            locals = self._shell.interpreter().locals
            base_locals = self._base_locals
            if base_locals is None:
                object = self.object
                for name in object.trait_names():
                    locals[name] = getattr(object, name, None)
            else:
                dic = self.value
                for name, value in dic.items():
                    locals[name] = value

    def update_any(self, event):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        locals = self._shell.interpreter().locals
        if self._base_locals is None:
            locals[event.name] = event.new
        else:
            self.value[event.name] = event.new

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        self._shell.observe(self.update_object,
                            "command_executed",
                            remove=True,
                            dispatch="ui")
        if self._base_locals is None:
            self.object.observe(self.update_any, remove=True, dispatch="ui")

        super().dispose()

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        shell = self._shell
        try:
            history = prefs.get("history", [])
            history_index = prefs.get("history_index", -1)
            shell.set_history(history, history_index)
        except:
            pass

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        history, history_index = self._shell.get_history()
        return {"history": history, "history_index": history_index}

    # -------------------------------------------------------------------------
    # Private Interface
    # -------------------------------------------------------------------------

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

    def _command_to_execute_fired(self, command):
        """ Handles the 'command_to_execute' trait being fired.
        """
        # Show the command. A 'hidden' command should be executed directly on
        # the namespace trait!
        self._shell.execute_command(command, hidden=False)
示例#26
0
class TabularAdapter(HasPrivateTraits):
    """ The base class for adapting list items to values that can be edited
        by a TabularEditor.
    """

    # -- Public Trait Definitions ---------------------------------------------

    #: A list of columns that should appear in the table. Each entry can have
    #: one of two forms: ``string`` or ``(string, id)``, where ``string`` is
    #: the UI name of the column, and ``id`` is a value that identifies that
    #: column to the adapter. Normally this value is either a trait name or an
    #: index, but it can be any value that the adapter wants. If only
    #: ``string`` is specified, then ``id`` is the index of the ``string``
    #: within :py:attr:`columns`.
    columns = List()

    #: Maps UI name of column to value identifying column to the adapter, if
    #: different.
    column_dict = Property()

    #: Specifies the default value for a new row.  This will usually need to be
    #: overridden.
    default_value = Any("")

    #: The default text color for odd table rows.
    odd_text_color = Color(None, update=True)

    #: The default text color for even table rows.
    even_text_color = Color(None, update=True)

    #: The default text color for table rows.
    default_text_color = Color(None, update=True)

    #: The default background color for odd table rows.
    odd_bg_color = Color(None, update=True)

    #: The default background color for even table rows.
    even_bg_color = Color(None, update=True)

    #: The default background color for table rows.
    default_bg_color = Color(None, update=True)

    #: Horizontal alignment to use for a specified column.
    alignment = Enum("left", "center", "right")

    #: The Python format string to use for a specified column.
    format = Str("%s")

    #: Width of a specified column.
    width = Float(-1)

    #: Can the text value of each item be edited?
    can_edit = Bool(True)

    #: The value to be dragged for a specified row item.
    drag = Property

    #: Can any arbitrary value be dropped onto the tabular view.
    can_drop = Bool(False)

    #: Specifies where a dropped item should be placed in the table relative to
    #: the item it is dropped on.
    dropped = Enum("after", "before")

    #: The font for a row item.
    font = Font(None)

    #: The text color for a row item.
    text_color = Property

    #: The background color for a row item.
    bg_color = Property

    #: The name of the default image to use for column items.
    image = Str(None, update=True)

    #: The text of a row/column item.
    text = Property

    #: The content of a row/column item (may be any Python value).
    content = Property

    #: The tooltip information for a row/column item.
    tooltip = Str

    #: The context menu for a row/column item.
    menu = Any

    #: The context menu for column header.
    column_menu = Any

    #: List of optional delegated adapters.
    adapters = List(ITabularAdapter, update=True)

    # -- Traits Set by the Editor ---------------------------------------------

    #: The object whose trait is being edited.
    object = Instance(HasTraits)

    #: The name of the trait being edited.
    name = Str

    #: The row index of the current item being adapted.
    row = Int

    #: The column index of the current item being adapted.
    column = Int

    #: The current column id being adapted (if any).
    column_id = Any

    #: Current item being adapted.
    item = Any

    #: The current value (if any).
    value = Any

    # -- Private Trait Definitions --------------------------------------------

    #: Cache of attribute handlers.
    cache = Any({})

    #: Event fired when the cache is flushed.
    cache_flushed = Event(update=True)

    #: The mapping from column indices to column identifiers (defined by the
    #: :py:attr:`columns` trait).
    column_map = Property(depends_on="columns")

    #: The mapping from column indices to column labels (defined by the
    #: :py:attr:`columns` trait).
    label_map = Property(depends_on="columns")

    #: The name of the trait on a row item containing the value to use
    #: as a row label. If ``None``, the label will be the empty string.
    row_label_name = Either(None, Str)

    #: For each adapter, specifies the column indices the adapter handles.
    adapter_column_indices = Property(depends_on="adapters,columns")

    #: For each adapter, specifies the mapping from column index to column id.
    adapter_column_map = Property(depends_on="adapters,columns")

    # -------------------------------------------------------------------------
    # TabularAdapter interface
    # -------------------------------------------------------------------------

    def cleanup(self):
        """ Clean up the adapter to remove references to objects.
        """
        self.trait_setq(object=None, item=None, value=None)

    # -- Adapter methods that are sensitive to item type ----------------------

    def get_alignment(self, object, trait, column):
        """ Returns the alignment style to use for a specified column.

        The possible values that can be returned are: ``'left'``, ``'center'``
        or ``'right'``. All table items share the same alignment for a
        specified column.
        """
        return self._result_for("get_alignment", object, trait, 0, column)

    def get_width(self, object, trait, column):
        """ Returns the width to use for a specified column.

        If the value is <= 0, the column will have a *default* width, which is
        the same as specifying a width of 0.1.

        If the value is > 1.0, it is converted to an integer and the result is
        the width of the column in pixels. This is referred to as a
        *fixed width* column.

        If the value is a float such that 0.0 < value <= 1.0, it is treated as
        the *unnormalized fraction of the available space* that is to be
        assigned to the column. What this means requires a little explanation.

        To arrive at the size in pixels of the column at any given time, the
        editor adds together all of the *unnormalized fraction* values
        returned for all columns in the table to arrive at a total value. Each
        *unnormalized fraction* is then divided by the total to create a
        *normalized fraction*. Each column is then assigned an amount of space
        in pixels equal to the maximum of 30 or its *normalized fraction*
        multiplied by the *available space*. The *available space* is defined
        as the actual width of the table minus the width of all *fixed width*
        columns. Note that this calculation is performed each time the table is
        resized in the user interface, thus allowing columns of this type to
        increase or decrease their width dynamically, while leaving *fixed
        width* columns unchanged.
        """
        return self._result_for("get_width", object, trait, 0, column)

    def get_can_edit(self, object, trait, row):
        """ Returns whether the user can edit a specified row.

        A ``True`` result indicates that the value can be edited, while a
        ``False`` result indicates that it cannot.
        """
        return self._result_for("get_can_edit", object, trait, row, 0)

    def get_drag(self, object, trait, row):
        """ Returns the value to be *dragged* for a specified row.

        A result of ``None`` means that the item cannot be dragged. Note that
        the value returned does not have to be the actual row item. It can be
        any value that you want to drag in its place. In particular, if you
        want the drag target to receive a copy of the row item, you should
        return a copy or clone of the item in its place.

        Also note that if multiple items are being dragged, and this method
        returns ``None`` for any item in the set, no drag operation is
        performed.
        """
        return self._result_for("get_drag", object, trait, row, 0)

    def get_can_drop(self, object, trait, row, value):
        """ Returns whether the specified ``value`` can be dropped on the specified row.

        A value of ``True`` means the ``value`` can be dropped; and a value of
        ``False`` indicates that it cannot be dropped.

        The result is used to provide the user positive or negative drag
        feedback while dragging items over the table. ``value`` will always be
        a single value, even if multiple items are being dragged. The editor
        handles multiple drag items by making a separate call to
        :py:meth:`get_can_drop` for each item being dragged.
        """
        return self._result_for("get_can_drop", object, trait, row, 0, value)

    def get_dropped(self, object, trait, row, value):
        """ Returns how to handle a specified ``value`` being dropped on a specified row.

        The possible return values are:

        - ``'before'``: Insert the specified ``value`` before the dropped on item.
        - ``'after'``: Insert the specified ``value`` after the dropped on item.

        Note there is no result indicating *do not drop* since you will have
        already indicated that the ``object`` can be dropped by the result
        returned from a previous call to :py:meth:`get_can_drop`.
        """
        return self._result_for("get_dropped", object, trait, row, 0, value)

    def get_font(self, object, trait, row, column=0):
        """ Returns the font to use for displaying a specified row or cell.

        A result of ``None`` means use the default font; otherwise a toolkit
        font object should be returned. Note that all columns for the specified
        table row will use the font value returned.
        """
        return self._result_for("get_font", object, trait, row, column)

    def get_text_color(self, object, trait, row, column=0):
        """ Returns the text color to use for a specified row or cell.

        A result of ``None`` means use the default text color; otherwise a
        toolkit-compatible color should be returned. Note that all columns for
        the specified table row will use the text color value returned.
        """
        return self._result_for("get_text_color", object, trait, row, column)

    def get_bg_color(self, object, trait, row, column=0):
        """ Returns the background color to use for a specified row or cell.

        A result of ``None`` means use the default background color; otherwise
        a toolkit-compatible color should be returned. Note that all columns
        for the specified table row will use the background color value
        returned.
        """
        return self._result_for("get_bg_color", object, trait, row, column)

    def get_image(self, object, trait, row, column):
        """ Returns the image to display for a specified cell.

        A result of ``None`` means no image will be displayed in the specified
        table cell. Otherwise the result should either be the name of the
        image, or an :py:class:`~pyface.image_resource.ImageResource` object
        specifying the image to display.

        A name is allowed in the case where the image is specified in the
        :py:class:`~traitsui.editors.tabular_editor.TabularEditor`
        :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.images` trait.
        In that case, the name should be the same as the string specified in
        the :py:class:`~pyface.image_resource.ImageResource` constructor.
        """
        return self._result_for("get_image", object, trait, row, column)

    def get_format(self, object, trait, row, column):
        """ Returns the Python formatting string to apply to the specified cell.

        The resulting of formatting with this string will be used as the text
        to display it in the table.

        The return can be any Python string containing exactly one old-style
        Python formatting sequence, such as ``'%.4f'`` or ``'(%5.2f)'``.
        """
        return self._result_for("get_format", object, trait, row, column)

    def get_text(self, object, trait, row, column):
        """ Returns a string containing the text to display for a specified cell.

        If the underlying data representation for a specified item is not a
        string, then it is your responsibility to convert it to one before
        returning it as the result.
        """
        return self._result_for("get_text", object, trait, row, column)

    def get_content(self, object, trait, row, column):
        """ Returns the content to display for a specified cell.
        """
        return self._result_for("get_content", object, trait, row, column)

    def set_text(self, object, trait, row, column, text):
        """ Sets the value for the specified cell.

        This method is called when the user completes an editing operation on a
        table cell.

        The string specified by ``text`` is the value that the user has
        entered in the table cell.  If the underlying data does not store the
        value as text, it is your responsibility to convert ``text`` to the
        correct representation used.
        """
        self._result_for("set_text", object, trait, row, column, text)

    def get_tooltip(self, object, trait, row, column):
        """ Returns a string containing the tooltip to display for a specified cell.

        You should return the empty string if you do not wish to display a
        tooltip.
        """
        return self._result_for("get_tooltip", object, trait, row, column)

    def get_menu(self, object, trait, row, column):
        """ Returns the context menu for a specified cell.
        """
        return self._result_for("get_menu", object, trait, row, column)

    def get_column_menu(self, object, trait, row, column):
        """ Returns the context menu for a specified column.
        """
        return self._result_for("get_column_menu", object, trait, row, column)

    # -- Adapter methods that are not sensitive to item type ------------------

    def get_item(self, object, trait, row):
        """ Returns the specified row item.

        The value returned should be the value that exists (or *logically*
        exists) at the specified ``row`` in your data. If your data is not
        really a list or array, then you can just use ``row`` as an integer
        *key* or *token* that can be used to retrieve a corresponding item. The
        value of ``row`` will always be in the range: 0 <= row <
        ``len(object, trait)`` (i.e. the result returned by the adapter
        :py:meth:`len` method).

        The default implementation assumes the trait defined by
        ``object.trait`` is a *sequence* and attempts to return the value at
        index ``row``. If an error occurs, it returns ``None`` instead. This
        definition should work correctly for lists, tuples and arrays, or any
        other object that is indexable, but will have to be overridden for all
        other cases.
        """
        try:
            return getattr(object, trait)[row]
        except:
            return None

    def len(self, object, trait):
        """ Returns the number of row items in the specified ``object.trait``.

        The result should be an integer greater than or equal to 0.

        The default implementation assumes the trait defined by
        ``object.trait`` is a *sequence* and attempts to return the result of
        calling ``len(object.trait)``. It will need to be overridden for any
        type of data which for which :py:func:`len` will not work.
        """
        # Sometimes, during shutdown, the object has been set to None.
        if object is None:
            return 0
        else:
            return len(getattr(object, trait))

    def get_default_value(self, object, trait):
        """ Returns a new default value for the specified ``object.trait`` list.

        This method is called when *insert* or *append* operations are allowed
        and the user requests that a new item be added to the table. The result
        should be a new instance of whatever underlying representation is being
        used for table items.

        The default implementation simply returns the value of the adapter's
        :py:attr:`default_value` trait.
        """
        return self.default_value

    def delete(self, object, trait, row):
        """ Deletes the specified row item.

        This method is only called if the *delete* operation is specified in
        the :py:class:`~traitsui.editors.tabular_editor.TabularEditor`
        :py:attr:`~traitsui.editors.tabular_editor.TabularEditor.operation`
        trait, and the user requests that the item be deleted from the table.

        The adapter can still choose not to delete the specified item if
        desired, although that may prove confusing to the user.

        The default implementation assumes the trait defined by
        ``object.trait`` is a mutable sequence and attempts to perform a
        ``del object.trait[row]`` operation.
        """
        del getattr(object, trait)[row]

    def insert(self, object, trait, row, value):
        """ Inserts ``value`` at the specified ``object.trait[row]`` index.

        The specified ``value`` can be:

        - An item being moved from one location in the data to another.
        - A new item created by a previous call to
          :py:meth:`~TabularAdapter.get_default_value`.
        - An item the adapter previously approved via a call to
          :py:meth:`~TabularAdapter.get_can_drop`.

        The adapter can still choose not to insert the item into the data,
        although that may prove confusing to the user.

        The default implementation assumes the trait defined by
        ``object.trait`` is a mutable sequence and attempts to perform an
        ``object.trait[row:row] = [value]`` operation.
        """
        getattr(object, trait)[row:row] = [value]

    def get_column(self, object, trait, index):
        """ Returns the column id corresponding to a specified column index.
        """
        self.object, self.name = object, trait
        return self.column_map[index]

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

    def _get_drag(self):
        return self.item

    def _get_text_color(self):
        if (self.row % 2) == 1:
            return self.even_text_color_ or self.default_text_color

        return self.odd_text_color or self.default_text_color_

    def _get_bg_color(self):
        if (self.row % 2) == 1:
            return self.even_bg_color_ or self.default_bg_color_

        return self.odd_bg_color or self.default_bg_color_

    def _get_text(self):
        return self.get_format(
            self.object, self.name, self.row, self.column) % self.get_content(
                self.object, self.name, self.row, self.column)

    def _set_text(self, value):
        if isinstance(self.column_id, int):
            self.item[self.column_id] = self.value
        else:
            # Convert value to the correct trait type.
            try:
                trait_handler = self.item.trait(self.column_id).handler
                setattr(
                    self.item,
                    self.column_id,
                    trait_handler.evaluate(self.value),
                )
            except:
                setattr(self.item, self.column_id, value)

    def _get_content(self):
        if isinstance(self.column_id, int):
            return self.item[self.column_id]

        return getattr(self.item, self.column_id)

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

    @cached_property
    def _get_column_dict(self):
        cols = {}
        for i, value in enumerate(self.columns):
            if isinstance(value, six.string_types):
                cols.update({value: value})
            else:
                cols.update({value[0]: value[1]})
        return cols

    @cached_property
    def _get_column_map(self):
        map = []
        for i, value in enumerate(self.columns):
            if isinstance(value, six.string_types):
                map.append(i)
            else:
                map.append(value[1])

        return map

    def get_label(self, section, obj=None):
        """Override this method if labels will vary from object to object."""
        return self.label_map[section]

    def get_row_label(self, section, obj=None):
        if self.row_label_name is None:
            return None
        rows = getattr(obj, self.name, None)
        if rows is None:
            return None
        item = rows[section]
        return getattr(item, self.row_label_name, None)

    @cached_property
    def _get_label_map(self):
        map = []
        for i, value in enumerate(self.columns):
            if isinstance(value, six.string_types):
                map.append(value)
            else:
                map.append(value[0])

        return map

    @cached_property
    def _get_adapter_column_indices(self):
        labels = self.label_map
        map = []
        for adapter in self.adapters:
            indices = []
            for label in adapter.columns:
                if not isinstance(label, six.string_types):
                    label = label[0]

                indices.append(labels.index(label))
            map.append(indices)
        return map

    @cached_property
    def _get_adapter_column_map(self):
        labels = self.label_map
        map = []
        for adapter in self.adapters:
            mapping = {}
            for label in adapter.columns:
                id = None
                if not isinstance(label, six.string_types):
                    label, id = label

                key = labels.index(label)
                if id is None:
                    id = key

                mapping[key] = id

            map.append(mapping)

        return map

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

    def _result_for(self, name, object, trait, row, column, value=None):
        """ Returns/Sets the value of the specified *name* attribute for the
            specified *object.trait[row].column* item.
        """
        self.object = object
        self.name = trait
        self.row = row
        self.column = column
        self.column_id = column_id = self.column_map[column]
        self.value = value
        self.item = item = self.get_item(object, trait, row)
        item_class = item.__class__
        key = "%s:%s:%d" % (item_class.__name__, name, column)
        handler = self.cache.get(key)
        if handler is not None:
            return handler()

        prefix = name[:4]
        trait_name = name[4:]

        for i, adapter in enumerate(self.adapters):
            if column in self.adapter_column_indices[i]:
                adapter.row = row
                adapter.item = item
                adapter.value = value
                adapter.column = column_id = self.adapter_column_map[i][column]
                if adapter.accepts:
                    get_name = "%s_%s" % (column_id, trait_name)
                    if adapter.trait(get_name) is not None:
                        if prefix == "get_":
                            handler = lambda: getattr(
                                adapter.trait_set(
                                    row=self.row,
                                    column=column_id,
                                    item=self.item,
                                ),
                                get_name,
                            )
                        else:
                            handler = lambda: setattr(
                                adapter.trait_set(
                                    row=self.row,
                                    column=column_id,
                                    item=self.item,
                                ),
                                get_name,
                                self.value,
                            )

                        if adapter.is_cacheable:
                            break

                        return handler()
        else:
            if item is not None and hasattr(item_class, "__mro__"):
                for klass in item_class.__mro__:
                    handler = self._get_handler_for(
                        "%s_%s_%s" % (klass.__name__, column_id, trait_name),
                        prefix,
                    ) or self._get_handler_for(
                        "%s_%s" % (klass.__name__, trait_name), prefix)
                    if handler is not None:
                        break

            if handler is None:
                handler = self._get_handler_for(
                    "%s_%s" % (column_id, trait_name),
                    prefix) or self._get_handler_for(trait_name, prefix)

        self.cache[key] = handler
        return handler()

    def _get_handler_for(self, name, prefix):
        """ Returns the handler for a specified trait name (or None if not
            found).
        """
        if self.trait(name) is not None:
            if prefix == "get_":
                return lambda: getattr(self, name)

            return lambda: setattr(self, name, self.value)

        return None

    @on_trait_change("columns,adapters.+update")
    def _flush_cache(self):
        """ Flushes the cache when the columns or any trait on any adapter
            changes.
        """
        self.cache = {}
        self.cache_flushed = True
示例#27
0
class Application(HasTraits):
    """ An extensible, pluggable, application.

    This class handles the common case for non-GUI applications, and it is
    intended to be subclassed to change start/stop behaviour etc.

    """

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

    # The application's globally unique identifier.
    id = Str

    # The name of a directory (created for you) to which the application can
    # read and write non-user accessible data, i.e. configuration information,
    # preferences, etc.
    home = Str

    # The name of a directory (created for you upon access) to which the
    # application can read and write user-accessible data, e.g. projects
    # created by the user.
    user_data = Str

    # The root preferences node.
    preferences = Instance(IPreferences)

    #### Events ####

    # Fired when the application is starting.
    starting = VetoableEvent(ApplicationEvent)

    # Fired when all plugins have been started.
    started = Event(ApplicationEvent)

    # Fired when the application is stopping.
    stopping = VetoableEvent(ApplicationEvent)

    # Fired when all plugins have been stopped.
    stopped = Event(ApplicationEvent)

    #### 'IPluginManager' interface ###########################################

    #### Events ####

    # Fired when a plugin has been added.
    plugin_added = Delegate("plugin_manager", modify=True)

    # Fired when a plugin has been removed.
    plugin_removed = Delegate("plugin_manager", modify=True)

    #### 'Application' interface ##############################################

    # These traits allow application developers to build completely different
    # styles of extensible application. It allows Envisage to be used as a
    # framework for frameworks ;^)
    #
    # The extension registry.
    extension_registry = Instance(IExtensionRegistry)

    # The plugin manager (starts and stops plugins etc).
    plugin_manager = Instance(IPluginManager)

    # The service registry.
    service_registry = Instance(IServiceRegistry)

    #### Private interface ####################################################

    # The import manager.
    _import_manager = Instance(IImportManager, factory=ImportManager)

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

    def __init__(self, plugins=None, **traits):
        """ Constructor.

        We allow the caller to specify an initial list of plugins, but the list
        itself is not part of the public API. To add and remove plugins after
        after construction, use the 'add_plugin' and 'remove_plugin' methods
        respectively. The application is also iterable, so to iterate over the
        plugins use 'for plugin in application: ...'.

        """

        super(Application, self).__init__(**traits)

        # fixme: We have to initialize the application home here (i.e. we can't
        # wait until the 'home' trait is accessed) because the scoped
        # preferences uses 'ETSConfig.application' home as the name of the
        # default preferences file.
        self._initialize_application_home()

        # Set the default preferences node used by the preferences package.
        # This allows 'PreferencesHelper' and 'PreferenceBinding' instances to
        # be used as more convenient ways to access preferences.
        #
        # fixme: This is another sneaky global!
        set_default_preferences(self.preferences)

        # We allow the caller to specify an initial list of plugins, but the
        # list itself is not part of the public API. To add and remove plugins
        # after construction, use the 'add_plugin' and 'remove_plugin' methods
        # respectively. The application is also iterable, so to iterate over
        # the plugins use 'for plugin in application: ...'.
        if plugins is not None:
            for plugin in plugins:
                self.add_plugin(plugin)

        return

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

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

    def _home_default(self):
        """ Trait initializer. """

        return ETSConfig.application_home

    def _user_data_default(self):
        """ Trait initializer. """

        user_data = os.path.join(ETSConfig.user_data, self.id)

        # Make sure it exists!
        if not os.path.exists(user_data):
            os.makedirs(user_data)

        return user_data

    def _preferences_default(self):
        """ Trait initializer. """

        return ScopedPreferences()

    #### Methods ##############################################################

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

        if self.start():
            self.stop()

        return

    ###########################################################################
    # 'IExtensionRegistry' interface.
    ###########################################################################

    def add_extension_point_listener(self, listener, extension_point_id=None):
        """ Add a listener for extensions being added/removed. """

        self.extension_registry.add_extension_point_listener(
            listener, extension_point_id)

        return

    def add_extension_point(self, extension_point):
        """ Add an extension point. """

        self.extension_registry.add_extension_point(extension_point)

        return

    def get_extensions(self, extension_point_id):
        """ Return a list containing all contributions to an extension point.

        """

        return self.extension_registry.get_extensions(extension_point_id)

    def get_extension_point(self, extension_point_id):
        """ Return the extension point with the specified Id. """

        return self.extension_registry.get_extension_point(extension_point_id)

    def get_extension_points(self):
        """ Return all extension points that have been added to the registry.

        """

        return self.extension_registry.get_extension_points()

    def remove_extension_point_listener(self,
                                        listener,
                                        extension_point_id=None):
        """ Remove a listener for extensions being added/removed. """

        self.extension_registry.remove_extension_point_listener(
            listener, extension_point_id)

        return

    def remove_extension_point(self, extension_point_id):
        """ Remove an extension point. """

        self.extension_registry.remove_extension_point(extension_point_id)

        return

    def set_extensions(self, extension_point_id, extensions):
        """ Set the extensions contributed to an extension point. """

        self.extension_registry.set_extensions(extension_point_id, extensions)

        return

    ###########################################################################
    # 'IImportManager' interface.
    ###########################################################################

    def import_symbol(self, symbol_path):
        """ Import the symbol defined by the specified symbol path. """

        return self._import_manager.import_symbol(symbol_path)

    ###########################################################################
    # 'IPluginManager' interface.
    ###########################################################################

    def __iter__(self):
        """ Return an iterator over the manager's plugins. """

        return iter(self.plugin_manager)

    def add_plugin(self, plugin):
        """ Add a plugin to the manager. """

        self.plugin_manager.add_plugin(plugin)

        return

    def get_plugin(self, plugin_id):
        """ Return the plugin with the specified Id. """

        return self.plugin_manager.get_plugin(plugin_id)

    def remove_plugin(self, plugin):
        """ Remove a plugin from the manager. """

        self.plugin_manager.remove_plugin(plugin)

        return

    def start(self):
        """ Start the plugin manager.

        Returns True unless the start was vetoed.

        """

        # fixme: This method is notionally on the 'IPluginManager' interface
        # but that interface knows nothing about the vetoable events etc and
        # hence doesn't have a return value.
        logger.debug("---------- application starting ----------")

        # Lifecycle event.
        self.starting = event = self._create_application_event()
        if not event.veto:
            # Start the plugin manager (this starts all of the manager's
            # plugins).
            self.plugin_manager.start()

            # Lifecycle event.
            self.started = self._create_application_event()

            logger.debug("---------- application started ----------")

        else:
            logger.debug("---------- application start vetoed ----------")

        return not event.veto

    def start_plugin(self, plugin=None, plugin_id=None):
        """ Start the specified plugin. """

        return self.plugin_manager.start_plugin(plugin, plugin_id)

    def stop(self):
        """ Stop the plugin manager.

        Returns True unless the stop was vetoed.

        """

        # fixme: This method is notionally on the 'IPluginManager' interface
        # but that interface knows nothing about the vetoable events etc and
        # hence doesn't have a return value.
        logger.debug("---------- application stopping ----------")

        # Lifecycle event.
        self.stopping = event = self._create_application_event()
        if not event.veto:
            # Stop the plugin manager (this stops all of the manager's
            # plugins).
            self.plugin_manager.stop()

            # Save all preferences.
            self.preferences.save()

            # Lifecycle event.
            self.stopped = self._create_application_event()

            logger.debug("---------- application stopped ----------")

        else:
            logger.debug("---------- application stop vetoed ----------")

        return not event.veto

    def stop_plugin(self, plugin=None, plugin_id=None):
        """ Stop the specified plugin. """

        return self.plugin_manager.stop_plugin(plugin, plugin_id)

    ###########################################################################
    # 'IServiceRegistry' interface.
    ###########################################################################

    def get_required_service(self,
                             protocol,
                             query="",
                             minimize="",
                             maximize=""):
        """ Return the service that matches the specified query.

        Raise a 'NoSuchServiceError' exception if no such service exists.

        """

        service = self.service_registry.get_required_service(
            protocol, query, minimize, maximize)

        return service

    def get_service(self, protocol, query="", minimize="", maximize=""):
        """ Return at most one service that matches the specified query. """

        service = self.service_registry.get_service(protocol, query, minimize,
                                                    maximize)

        return service

    def get_service_from_id(self, service_id):
        """ Return the service with the specified id. """

        return self.service_registry.get_service_from_id(service_id)

    def get_service_properties(self, service_id):
        """ Return the dictionary of properties associated with a service. """

        return self.service_registry.get_service_properties(service_id)

    def get_services(self, protocol, query="", minimize="", maximize=""):
        """ Return all services that match the specified query. """

        services = self.service_registry.get_services(protocol, query,
                                                      minimize, maximize)

        return services

    def register_service(self, protocol, obj, properties=None):
        """ Register a service. """

        service_id = self.service_registry.register_service(
            protocol, obj, properties)

        return service_id

    def set_service_properties(self, service_id, properties):
        """ Set the dictionary of properties associated with a service. """

        self.service_registry.set_service_properties(service_id, properties)

        return

    def unregister_service(self, service_id):
        """ Unregister a service. """

        self.service_registry.unregister_service(service_id)

        return

    ###########################################################################
    # 'Application' interface.
    ###########################################################################

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

    def _extension_registry_default(self):
        """ Trait initializer. """

        # Do the import here to emphasize the fact that this is just the
        # default implementation and that the application developer is free
        # to override it!
        from .plugin_extension_registry import PluginExtensionRegistry

        return PluginExtensionRegistry(plugin_manager=self)

    def _plugin_manager_default(self):
        """ Trait initializer. """

        # Do the import here to emphasize the fact that this is just the
        # default implementation and that the application developer is free
        # to override it!
        from .plugin_manager import PluginManager

        return PluginManager(application=self)

    def _service_registry_default(self):
        """ Trait initializer. """

        # Do the import here to emphasize the fact that this is just the
        # default implementation and that the application developer is free
        # to override it!
        from .service_registry import ServiceRegistry

        return ServiceRegistry()

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

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

    # fixme: We have this to make it easier to assign a new plugin manager
    # at construction time due to the fact that the plugin manager needs a
    # reference to the application and vice-versa, e.g. we can do
    #
    #    application = Application(plugin_manager=EggPluginManager())
    #
    # If we didn't have this then we would have to do this:-
    #
    #    application = Application()
    #    application.plugin_manager = EggPluginManager(application=application)
    #
    # Of course, it would be better if the plugin manager didn't require a
    # reference to the application at all (it currently uses it to set the
    # 'application' trait of plugin instances - but that is only done for the
    # same reason as this (i.e. it is nice to be able to pass plugins into the
    # application constructor).
    def _plugin_manager_changed(self, trait_name, old, new):
        """ Static trait change handler. """

        if old is not None:
            old.application = None

        if new is not None:
            new.application = self

        return

    #### Methods ##############################################################

    def _create_application_event(self):
        """ Create an application event. """

        return ApplicationEvent(application=self)

    def _initialize_application_home(self):
        """ Initialize the application home directory. """

        ETSConfig.application_home = os.path.join(ETSConfig.application_data,
                                                  self.id)

        # Make sure it exists!
        if not os.path.exists(ETSConfig.application_home):
            os.makedirs(ETSConfig.application_home)

        return
示例#28
0
class TableEditor(Editor, BaseTableEditor):
    """ Editor that presents data in a table. Optionally, tables can have
        a set of filters that reduce the set of data displayed, according to
        their criteria.
    """

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

    #: The table view control associated with the editor:
    table_view = Any()

    def _table_view_default(self):
        return TableView(editor=self)

    #: A wrapper around the source model which provides filtering and sorting:
    model = Instance(SortFilterTableModel)

    def _model_default(self):
        return SortFilterTableModel(editor=self)

    #: The table model associated with the editor:
    source_model = Instance(TableModel)

    def _source_model_default(self):
        return TableModel(editor=self)

    #: The set of columns currently defined on the editor:
    columns = List(TableColumn)

    #: The currently selected row(s), column(s), or cell(s).
    selected = Any()

    #: The current selected row
    selected_row = Property(Any, depends_on="selected")

    selected_indices = Property(Any, depends_on="selected")

    #: Current filter object (should be a TableFilter or callable or None):
    filter = Any()

    #: The indices of the table items currently passing the table filter:
    filtered_indices = List(Int)

    #: Current filter summary message
    filter_summary = Str("All items")

    #: Update the filtered contents.
    update_filter = Event()

    #: The event fired when a cell is clicked on:
    click = Event()

    #: The event fired when a cell is double-clicked on:
    dclick = Event()

    #: The Traits UI associated with the table editor toolbar:
    toolbar_ui = Instance(UI)

    #: The context menu associated with empty space in the table
    empty_menu = Instance(QtGui.QMenu)

    #: The context menu associated with the vertical header
    header_menu = Instance(QtGui.QMenu)

    #: The context menu actions for moving rows up and down
    header_menu_up = Instance(QtGui.QAction)
    header_menu_down = Instance(QtGui.QAction)

    #: The index of the row that was last right clicked on its vertical header
    header_row = Int()

    #: Whether to auto-size the columns or not.
    auto_size = Bool(False)

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

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

    #: An image being converted:
    image = Image

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

        factory = self.factory
        self.filter = factory.filter

        columns = factory.columns[:]
        if (len(columns) == 0) and (len(self.value) > 0):
            columns = [
                ObjectColumn(name=name)
                for name in self.value[0].editable_traits()
            ]
        self.columns = columns

        if factory.table_view_factory is not None:
            self.table_view = factory.table_view_factory(editor=self)
        if factory.source_model_factory is not None:
            self.source_model = factory.source_model_factory(editor=self)
        if factory.model_factory is not None:
            self.model = factory.model_factory(editor=self)

        # Create the table view and model
        self.model.setDynamicSortFilter(True)
        self.model.setSourceModel(self.source_model)
        self.table_view.setModel(self.model)

        # Create the vertical header context menu and connect to its signals
        self.header_menu = QtGui.QMenu(self.table_view)
        insertable = factory.row_factory is not None
        if factory.editable:
            if insertable:
                action = self.header_menu.addAction("Insert new item")
                action.triggered.connect(self._on_context_insert)
            if factory.deletable:
                action = self.header_menu.addAction("Delete item")
                action.triggered.connect(self._on_context_remove)
        if factory.reorderable:
            if factory.editable and (insertable or factory.deletable):
                self.header_menu.addSeparator()
            self.header_menu_up = self.header_menu.addAction("Move item up")
            self.header_menu_up.triggered.connect(self._on_context_move_up)
            self.header_menu_down = self.header_menu.addAction(
                "Move item down")
            self.header_menu_down.triggered.connect(self._on_context_move_down)

        # Create the empty space context menu and connect its signals
        self.empty_menu = QtGui.QMenu(self.table_view)
        action = self.empty_menu.addAction("Add new item")
        action.triggered.connect(self._on_context_append)

        # When sorting is enabled, the first column is initially displayed with
        # the triangle indicating it is the sort index, even though no sorting
        # has actually been done. Sort here for UI/model consistency.
        if self.factory.sortable and not self.factory.reorderable:
            self.model.sort(0, QtCore.Qt.AscendingOrder)

        # Connect to the mode specific selection handler and select the first
        # row/column/cell. Do this before creating the edit_view to make sure
        # that it has a valid item to use when constructing its view.
        smodel = self.table_view.selectionModel()
        mode_slot = getattr(self, "_on_%s_selection" % factory.selection_mode)
        smodel.selectionChanged.connect(mode_slot)
        self.table_view.setCurrentIndex(self.model.index(0, 0))

        # Create the toolbar if necessary
        if factory.show_toolbar and len(factory.filters) > 0:
            main_view = QtGui.QWidget()
            layout = QtGui.QVBoxLayout(main_view)
            layout.setContentsMargins(0, 0, 0, 0)
            self.toolbar_ui = self.edit_traits(
                parent=parent,
                kind="subpanel",
                view=View(
                    Group(
                        Item("filter{View}", editor=factory._filter_editor),
                        Item("filter_summary{Results}", style="readonly"),
                        spring,
                        orientation="horizontal",
                    ),
                    resizable=True,
                ),
            )
            self.toolbar_ui.parent = self.ui
            layout.addWidget(self.toolbar_ui.control)
            layout.addWidget(self.table_view)
        else:
            main_view = self.table_view

        # Create auxiliary editor and encompassing splitter if necessary
        mode = factory.selection_mode
        if (factory.edit_view == " ") or mode not in {"row", "rows"}:
            self.control = main_view
        else:
            if factory.orientation == "horizontal":
                self.control = QtGui.QSplitter(QtCore.Qt.Horizontal)
            else:
                self.control = QtGui.QSplitter(QtCore.Qt.Vertical)
            self.control.setSizePolicy(QtGui.QSizePolicy.Expanding,
                                       QtGui.QSizePolicy.Expanding)
            self.control.addWidget(main_view)
            self.control.setStretchFactor(0, 2)

            # Create the row editor below the table view
            editor = InstanceEditor(view=factory.edit_view, kind="subpanel")
            self._ui = self.edit_traits(
                parent=self.control,
                kind="subpanel",
                view=View(
                    Item(
                        "selected_row",
                        style="custom",
                        editor=editor,
                        show_label=False,
                        resizable=True,
                        width=factory.edit_view_width,
                        height=factory.edit_view_height,
                    ),
                    resizable=True,
                    handler=factory.edit_view_handler,
                ),
            )
            self._ui.parent = self.ui
            self.control.addWidget(self._ui.control)
            self.control.setStretchFactor(1, 1)

        # Connect to the click and double click handlers
        self.table_view.clicked.connect(self._on_click)
        self.table_view.doubleClicked.connect(self._on_dclick)

        # 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")

        # Listen for changes to traits on the objects in the list
        self.context_object.on_trait_change(self.refresh_editor,
                                            self.extended_name + ".-",
                                            dispatch="ui")

        # Listen for changes on column definitions
        self.on_trait_change(self._update_columns, "columns", dispatch="ui")
        self.on_trait_change(self._update_columns,
                             "columns_items",
                             dispatch="ui")

        # Set up the required externally synchronized traits
        is_list = mode in ("rows", "columns", "cells")
        self.sync_value(factory.click, "click", "to")
        self.sync_value(factory.dclick, "dclick", "to")
        self.sync_value(factory.columns_name, "columns", is_list=True)
        self.sync_value(factory.selected, "selected", is_list=is_list)
        self.sync_value(factory.selected_indices,
                        "selected_indices",
                        is_list=is_list)
        self.sync_value(factory.filter_name, "filter", "from")
        self.sync_value(factory.filtered_indices, "filtered_indices", "to")
        self.sync_value(factory.update_filter_name, "update_filter", "from")

        self.auto_size = self.factory.auto_size

        # Initialize the ItemDelegates for each column
        self._update_columns()

    def dispose(self):
        """ Disposes of the contents of an editor."""
        self.model.beginResetModel()
        self.model.endResetModel()

        # Make sure that the auxiliary UIs are properly disposed
        if self.toolbar_ui is not None:
            self.toolbar_ui.dispose()
        if self._ui is not None:
            self._ui.dispose()

        # Remove listener for 'items' changes on object trait
        self.context_object.on_trait_change(self.update_editor,
                                            self.extended_name + "_items",
                                            remove=True)

        # Remove listener for changes to traits on the objects in the list
        self.context_object.on_trait_change(self.refresh_editor,
                                            self.extended_name + ".-",
                                            remove=True)

        # Remove listeners for column definition changes
        self.on_trait_change(self._update_columns, "columns", remove=True)
        self.on_trait_change(self._update_columns,
                             "columns_items",
                             remove=True)

        super(TableEditor, self).dispose()

    def update_editor(self):
        """Updates the editor when the object trait changes externally to the
        editor."""

        if self._no_notify:
            return

        self.table_view.setUpdatesEnabled(False)
        try:
            filtering = (len(self.factory.filters) > 0
                         or self.filter is not None)
            if filtering:
                self._update_filtering()

            # invalidate the model, but do not reset it. Resetting the model
            # may cause problems if the selection sync'ed traits are being used
            # externally to manage the selections
            self.model.invalidate()

            self.table_view.resizeColumnsToContents()
            if self.auto_size:
                self.table_view.resizeRowsToContents()

        finally:
            self.table_view.setUpdatesEnabled(True)

    def restore_prefs(self, prefs):
        """ Restores any saved user preference information associated with the
            editor.
        """
        header = self.table_view.horizontalHeader()
        if header is not None and "column_state" in prefs:
            header.restoreState(prefs["column_state"])

    def save_prefs(self):
        """ Returns any user preference information associated with the editor.
        """
        prefs = {}
        header = self.table_view.horizontalHeader()
        if header is not None:
            prefs["column_state"] = header.saveState().data()
        return prefs

    def refresh_editor(self):
        """Requests that the underlying table widget to redraw itself."""

        self.table_view.viewport().update()

    def create_new_row(self):
        """Creates a new row object using the provided factory."""

        factory = self.factory
        kw = factory.row_factory_kw.copy()
        if "__table_editor__" in kw:
            kw["__table_editor__"] = self

        return self.ui.evaluate(factory.row_factory, *factory.row_factory_args,
                                **kw)

    def items(self):
        """Returns the raw list of model objects."""

        items = self.value
        if not isinstance(items, SequenceTypes):
            items = [items]

        if self.factory and self.factory.reverse:
            items = ReversedList(items)

        return items

    def callx(self, func, *args, **kw):
        """Call a function without notifying the underlying table view or
        model."""

        old = self._no_notify
        self._no_notify = True
        try:
            func(*args, **kw)
        finally:
            self._no_notify = old

    def setx(self, **keywords):
        """Set one or more attributes without notifying the underlying table
        view or model."""

        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 set_selection(self, objects=[], notify=True):
        """Sets the current selection to a set of specified objects."""

        if not isinstance(objects, list):
            objects = [objects]

        mode = self.factory.selection_mode
        indexes = []
        flags = QtGui.QItemSelectionModel.ClearAndSelect

        # In the case of row or column selection, we need a dummy value for the
        # other dimension that has not been filtered.
        source_index = self.model.mapToSource(self.model.index(0, 0))
        source_row, source_column = source_index.row(), source_index.column()

        # Selection mode is 'row' or 'rows'
        if mode.startswith("row"):
            flags |= QtGui.QItemSelectionModel.Rows
            items = self.items()
            for obj in objects:
                try:
                    row = items.index(obj)
                except ValueError:
                    continue
                indexes.append(self.source_model.index(row, source_column))

        # Selection mode is 'column' or 'columns'
        elif mode.startswith("column"):
            flags |= QtGui.QItemSelectionModel.Columns
            for name in objects:
                column = self._column_index_from_name(name)
                if column != -1:
                    indexes.append(self.source_model.index(source_row, column))

        # Selection mode is 'cell' or 'cells'
        else:
            items = self.items()
            for obj, name in objects:
                try:
                    row = items.index(obj)
                except ValueError:
                    continue
                column = self._column_index_from_name(name)
                if column != -1:
                    indexes.append(self.source_model.index(row, column))

        # Perform the selection so that only one signal is emitted
        selection = QtGui.QItemSelection()
        smodel = self.table_view.selectionModel()
        if smodel is None:
            # guard against selection during tear-down
            return
        for index in indexes:
            index = self.model.mapFromSource(index)
            if index.isValid():
                smodel.setCurrentIndex(index,
                                       QtGui.QItemSelectionModel.NoUpdate)
                selection.select(index, index)

        smodel.blockSignals(not notify)
        try:
            if len(selection.indexes()):
                smodel.clear()
                smodel.select(selection, flags)
            else:
                smodel.clear()
        finally:
            smodel.blockSignals(False)

        self.refresh_editor()

    # -------------------------------------------------------------------------
    #  Private methods:
    # -------------------------------------------------------------------------

    def _column_index_from_name(self, name):
        """Returns the index of the column with the given name or -1 if no
        column exists with that name."""

        for i, column in enumerate(self.columns):
            if name == column.name:
                return i
        return -1

    def _customize_filters(self, filter):
        """Allows the user to customize the current set of table filters."""

        filter_editor = TableFilterEditor(editor=self)
        ui = filter_editor.edit_traits(parent=self.control)
        if ui.result:
            self.factory.filters = filter_editor.templates
            self.filter = filter_editor.selected_filter
        else:
            self.setx(filter=filter)

    def _update_filtering(self):
        """Update the filter summary and the filtered indices."""

        items = self.items()
        num_items = len(items)

        f = self.filter
        if f is None:
            self._filtered_cache = None
            self.filtered_indices = list(range(num_items))
            self.filter_summary = "All %i items" % num_items
        else:
            if not callable(f):
                f = f.filter
            self._filtered_cache = fc = [f(item) for item in items]
            self.filtered_indices = fi = [i for i, ok in enumerate(fc) if ok]
            self.filter_summary = "%i of %i items" % (len(fi), num_items)

    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

    def _get_image(self, image):
        """ Converts a user specified image to a QIcon.
        """
        if isinstance(image, str):
            self.image = image
            image = self.image

        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)

    # -- Trait Property getters/setters ---------------------------------------

    @cached_property
    def _get_selected_row(self):
        """Gets the selected row, or the first row if multiple rows are
        selected."""

        mode = self.factory.selection_mode

        if mode.startswith("column"):
            return None
        elif mode == "row":
            return self.selected

        try:
            if mode == "rows":
                return self.selected[0]
            elif mode == "cell":
                return self.selected[0]
            elif mode == "cells":
                return self.selected[0][0]
        except IndexError:
            return None

    @cached_property
    def _get_selected_indices(self):
        """Gets the row,column indices which match the selected trait"""
        selection_items = self.table_view.selectionModel().selection()
        indices = self.model.mapSelectionToSource(selection_items).indexes()
        if self.factory.selection_mode.startswith("row"):
            indices = sorted(set(index.row() for index in indices))
        elif self.factory.selection_mode.startswith("column"):
            indices = sorted(set(index.column() for index in indices))
        else:
            indices = [(index.row(), index.column()) for index in indices]

        if self.factory.selection_mode in {"rows", "columns", "cells"}:
            return indices
        elif len(indices) > 0:
            return indices[0]
        else:
            return -1

    def _set_selected_indices(self, indices):
        if not isinstance(indices, list):
            indices = [indices]
        selected = []
        if self.factory.selection_mode.startswith("row"):
            for row in indices:
                selected.append(self.value[row])
        elif self.factory.selection_mode.startswith("column"):
            for col in indices:
                selected.append(self.columns[col].name)
        else:
            for row, col in indices:
                selected.append((self.value[row], self.columns[col].name))

        self.selected = selected
        self.set_selection(self.selected, False)

    # -- Trait Change Handlers ------------------------------------------------

    def _filter_changed(self, old_filter, new_filter):
        """Handles the current filter being changed."""

        if not self._no_notify:
            if new_filter is customize_filter:
                do_later(self._customize_filters, old_filter)
            else:
                self._update_filtering()
                self.model.invalidate()
                self.set_selection(self.selected)

    def _update_columns(self):
        """Handle the column list being changed."""

        self.table_view.setItemDelegate(TableDelegate(self.table_view))
        for i, column in enumerate(self.columns):
            if column.renderer:
                self.table_view.setItemDelegateForColumn(i, column.renderer)

        self.model.invalidate()
        self.table_view.resizeColumnsToContents()
        if self.auto_size:
            self.table_view.resizeRowsToContents()

    def _selected_changed(self, new):
        """Handle the selected row/column/cell being changed externally."""
        if not self._no_notify:
            self.set_selection(self.selected, notify=False)

    def _update_filter_changed(self):
        """ The filter has changed internally.
        """
        self._filter_changed(self.filter, self.filter)

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

    def _on_row_selection(self, added, removed):
        """Handle the row selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedRows()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            selected = items[index.row()]
        else:
            selected = None

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_rows_selection(self, added, removed):
        """Handle the rows selection being changed."""
        items = self.items()
        indexes = self.table_view.selectionModel().selectedRows()
        selected = [
            items[self.model.mapToSource(index).row()] for index in indexes
        ]

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_column_selection(self, added, removed):
        """Handle the column selection being changed."""

        indexes = self.table_view.selectionModel().selectedColumns()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            selected = self.columns[index.column()].name
        else:
            selected = ""

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_columns_selection(self, added, removed):
        """Handle the columns selection being changed."""

        indexes = self.table_view.selectionModel().selectedColumns()
        selected = [
            self.columns[self.model.mapToSource(index).column()].name
            for index in indexes
        ]

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_cell_selection(self, added, removed):
        """Handle the cell selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedIndexes()
        if len(indexes):
            index = self.model.mapToSource(indexes[0])
            obj = items[index.row()]
            column_name = self.columns[index.column()].name
        else:
            obj = None
            column_name = ""
        selected = (obj, column_name)

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_cells_selection(self, added, removed):
        """Handle the cells selection being changed."""

        items = self.items()
        indexes = self.table_view.selectionModel().selectedIndexes()
        selected = []
        for index in indexes:
            index = self.model.mapToSource(index)
            obj = items[index.row()]
            column_name = self.columns[index.column()].name
            selected.append((obj, column_name))

        self.setx(selected=selected)
        self.ui.evaluate(self.factory.on_select, self.selected)

    def _on_click(self, index):
        """Handle a cell being clicked."""

        index = self.model.mapToSource(index)
        column = self.columns[index.column()]
        obj = self.items()[index.row()]

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        self.click = (obj, column)

        # Invoke the column's click handler:
        column.on_click(obj)

    def _on_dclick(self, index):
        """Handle a cell being double clicked."""

        index = self.model.mapToSource(index)
        column = self.columns[index.column()]
        obj = self.items()[index.row()]

        # Fire the same event on the editor after mapping it to a model object
        # and column name:
        self.dclick = (obj, column)

        # Invoke the column's double-click handler:
        column.on_dclick(obj)

    def _on_context_insert(self):
        """Handle 'insert item' being selected from the header context menu."""

        self.model.insertRow(self.header_row)

    def _on_context_append(self):
        """Handle 'add item' being selected from the empty space context
        menu."""

        self.model.insertRow(self.model.rowCount())

    def _on_context_remove(self):
        """Handle 'remove item' being selected from the header context menu."""

        self.model.removeRow(self.header_row)

    def _on_context_move_up(self):
        """Handle 'move up' being selected from the header context menu."""

        self.model.moveRow(self.header_row, self.header_row - 1)

    def _on_context_move_down(self):
        """Handle 'move down' being selected from the header context menu."""

        self.model.moveRow(self.header_row, self.header_row + 1)
示例#29
0
class IEditorAreaPane(ITaskPane):
    """ A central pane that contains tabbed editors.

    There are currently two implementations of this interface in Tasks.
    EditorAreaPane provides a simple, tabbed editor area. AdvancedEditorAreaPane
    additionally permits arbitrary splitting of the editor area so that editors
    can be displayed side-by-side.
    """

    #### 'IEditorAreaPane' interface ##########################################

    # The currently active editor.
    active_editor = Instance(IEditor)

    # The list of all the visible editors in the pane.
    editors = List(IEditor)

    # A list of extensions for file types to accept via drag and drop.
    # Note: This functionality is provided because it is very common, but drag
    # and drop support is in general highly toolkit-specific. If more
    # sophisticated support is required, subclass an editor area implementation.
    file_drop_extensions = List(Str)

    # A file with a supported extension was dropped into the editor area.
    file_dropped = Event(File)

    # Whether to hide the tab bar when there is only a single editor.
    hide_tab_bar = Bool(False)

    ###########################################################################
    # 'IEditorAreaPane' interface.
    ###########################################################################

    def activate_editor(self, editor):
        """ Activates the specified editor in the pane.
        """

    def add_editor(self, editor):
        """ Adds an editor to the pane.
        """

    def create_editor(self, obj, factory=None):
        """ Creates an editor for an object.

        If a factory is specified, it will be used instead of the editor factory
        registry. Otherwise, this method will return None if a suitable factory
        cannot be found in the registry.

        Note that the editor is not added to the pane.
        """

    def edit(self, obj, factory=None, use_existing=True):
        """ Edit an object.

        This is a convenience method that creates and adds an editor for the
        specified object. If 'use_existing' is set and the object is already
        being edited, then that editor will be activated and a new editor will
        not be created.

        Returns the (possibly new) editor for the object.
        """

    def get_editor(self, obj):
        """ Returns the editor for an object.

        Returns None if the object is not being edited.
        """

    def get_factory(self, obj):
        """ Returns an editor factory suitable for editing an object.

        Returns None if there is no such editor factory.
        """

    def register_factory(self, factory, filter):
        """ Registers a factory for creating editors.

        The 'factory' parameter is a callabe of form:
            callable(editor_area=editor_area, obj=obj) -> IEditor
        Often, factory will be a class that provides the 'IEditor' interface.

        The 'filter' parameter is a callable of form:
            callable(obj) -> bool
        that indicates whether the editor factory is suitable for an object.

        If multiple factories apply to a single object, it is undefined which
        factory is used. On the other hand, multiple filters may be registered
        for a single factory, in which case only one must apply for the factory
        to be selected.
        """

    def remove_editor(self, editor):
        """ Removes an editor from the pane.
        """

    def unregister_factory(self, factory):
        """ Unregisters a factory for creating editors.
示例#30
0
class TreeModel(HasTraits):
    """ Model for tree views. """

    #### 'TreeModel' interface ################################################

    # The root of the model.
    root = Any

    # Fired when nodes in the tree have changed in some way that affects their
    # appearance but NOT their structure or position in the tree.
    nodes_changed = Event(NodeEvent)

    # Fired when nodes have been inserted into the tree.
    nodes_inserted = Event(NodeEvent)

    # Fired when nodes have been removed from the tree.
    nodes_removed = Event(NodeEvent)

    # Fired when nodes have been replaced in the tree.
    nodes_replaced = Event(NodeEvent)

    # Fire when the structure of the tree has changed DRASTICALLY from a given
    # node down.
    structure_changed = Event(NodeEvent)

    #########################################################################
    # 'TreeModel' interface.
    #########################################################################

    def has_children(self, node):
        """ Returns True if a node has children, otherwise False.

        This method is provided in case the model has an efficient way to
        determine whether or not a node has any children without having to
        actually get the children themselves.

        """

        raise NotImplementedError

    def get_children(self, node):
        """ Returns the children of a node. """

        raise NotImplementedError

    def get_drag_value(self, node):
        """ Get the value that is dragged for a node.

        By default the drag value is the node itself.

        """

        return node

    def can_drop(self, node, obj):
        """ Returns True if a node allows an object to be dropped onto it. """

        return False

    def drop(self, node, obj):
        """ Drops an object onto a node. """

        raise NotImplementedError

    def get_image(self, node, selected, expanded):
        """ Returns the label image for a node.

        Return None (the default) if no image is required.

        """

        return None

    def get_key(self, node):
        """ Generate a unique key for a node. """

        try:
            key = hash(node)

        except:
            key = id(node)

        return key

    def get_selection_value(self, node):
        """ Get the value that is used when a node is selected.

        By default the selection value is the node itself.

        """

        return node

    def get_text(self, node):
        """ Returns the label text for a node.

        Return None if no text is required.  By default we return 'str(node)'.

        """

        return str(node)

    def can_set_text(self, node, text):
        """ Returns True if the node's label can be set. """

        return len(text.strip()) > 0

    def set_text(self, node, text):
        """ Sets the label text for a node. """

        pass

    def is_collapsible(self, node):
        """ Returns True if the node is collapsible, otherwise False. """

        return True

    def is_draggable(self, node):
        """ Returns True if the node is draggable, otherwise False. """

        return True

    def is_editable(self, node):
        """ Returns True if the node is editable, otherwise False.

        If the node is editable, its text can be set via the UI.

        """

        return False

    def is_expandable(self, node):
        """ Returns True if the node is expandanble, otherwise False. """

        return True

    def add_listener(self, node):
        """ Adds a listener for changes to a node. """

        pass

    def remove_listener(self, node):
        """ Removes a listener for changes to a node. """

        pass

    def fire_nodes_changed(self, node, children):
        """ Fires the nodes changed event. """

        self.nodes_changed = NodeEvent(node=node, children=children)

        return

    def fire_nodes_inserted(self, node, children):
        """ Fires the nodes inserted event. """

        self.nodes_inserted = NodeEvent(node=node, children=children)

        return

    def fire_nodes_removed(self, parent, children):
        """ Fires the nodes removed event. """

        self.nodes_removed = NodeEvent(node=node, children=children)

        return

    def fire_nodes_replaced(self, node, old_children, new_children):
        """ Fires the nodes removed event. """

        self.nodes_replaced = NodeEvent(
            node=node, old_children=old_children, children=new_children
        )

        return

    def fire_structure_changed(self, node):
        """ Fires the structure changed event. """

        self.structure_changed = NodeEvent(node=node)

        return