class _MenuItem(HasTraits):
    """ A menu item representation of an action item. """

    #### '_MenuItem' interface ################################################

    # Is the item checked?
    checked = Bool(False)

    # A controller object we delegate taking actions through (if any).
    controller = Any

    # Is the item enabled?
    enabled = Bool(True)

    # Is the item visible?
    visible = Bool(True)

    # The radio group we are part of (None if the menu item is not part of such
    # a group).
    group = Any

    # The toolkit control.
    control = Any()

    # The toolkit control id.
    control_id = None

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

    def __init__(self, parent, menu, item, controller):
        """ Creates a new menu item for an action item. """

        self.item = item
        action = item.action

        # FIXME v3: This is a wx'ism and should be hidden in the toolkit code.
        self.control_id = None

        if action.style == 'widget':
            self.control = PyfaceWidgetAction(parent, action)
            menu.addAction(self.control)
        elif action.image is None:
            self.control = menu.addAction(action.name, self._qt4_on_triggered,
                                          action.accelerator)
        else:
            self.control = menu.addAction(action.image.create_icon(),
                                          action.name, self._qt4_on_triggered,
                                          action.accelerator)
        menu.menu_items.append(self)

        self.control.setToolTip(action.tooltip)
        self.control.setWhatsThis(action.description)
        self.control.setEnabled(action.enabled)
        self.control.setVisible(action.visible)

        if getattr(action, 'menu_role', False):
            if action.menu_role == "About":
                self.control.setMenuRole(QtGui.QAction.AboutRole)
            elif action.menu_role == "Preferences":
                self.control.setMenuRole(QtGui.QAction.PreferencesRole)

        if action.style == 'toggle':
            self.control.setCheckable(True)
            self.control.setChecked(action.checked)
        elif action.style == 'radio':
            # Create an action group if it hasn't already been done.
            try:
                ag = item.parent._qt4_ag
            except AttributeError:
                ag = item.parent._qt4_ag = QtGui.QActionGroup(parent)

            self.control.setActionGroup(ag)

            self.control.setCheckable(True)
            self.control.setChecked(action.checked)

        # Listen for trait changes on the action (so that we can update its
        # enabled/disabled/checked state etc).
        action.on_trait_change(self._on_action_enabled_changed, 'enabled')
        action.on_trait_change(self._on_action_visible_changed, 'visible')
        action.on_trait_change(self._on_action_checked_changed, 'checked')
        action.on_trait_change(self._on_action_name_changed, 'name')
        action.on_trait_change(self._on_action_accelerator_changed,
                               'accelerator')

        # Detect if the control is destroyed.
        self.control.destroyed.connect(self._qt4_on_destroyed)

        if controller is not None:
            self.controller = controller
            controller.add_to_menu(self)

    def dispose(self):
        action = self.item.action
        action.on_trait_change(self._on_action_enabled_changed,
                               'enabled',
                               remove=True)
        action.on_trait_change(self._on_action_visible_changed,
                               'visible',
                               remove=True)
        action.on_trait_change(self._on_action_checked_changed,
                               'checked',
                               remove=True)
        action.on_trait_change(self._on_action_name_changed,
                               'name',
                               remove=True)
        action.on_trait_change(self._on_action_accelerator_changed,
                               'accelerator',
                               remove=True)

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

    def _qt4_on_destroyed(self, control=None):
        """ Delete the reference to the control to avoid attempting to talk to
        it again.
        """
        self.control = None

    def _qt4_on_triggered(self):
        """ Called when the menu item has been clicked. """

        action = self.item.action
        action_event = ActionEvent()

        is_checkable = action.style in ['radio', 'toggle']

        # Perform the action!
        if self.controller is not None:
            if is_checkable:
                self.checked = action.checked = self.control.isChecked()

            # Most of the time, action's do no care about the event (it
            # contains information about the time the event occurred etc), so
            # we only pass it if the perform method requires it. This is also
            # useful as Traits UI controllers *never* require the event.
            argspec = getargspec(self.controller.perform)

            # If the only arguments are 'self' and 'action' then don't pass
            # the event!
            if len(argspec.args) == 2:
                self.controller.perform(action)

            else:
                self.controller.perform(action, action_event)

        else:
            if is_checkable:
                self.checked = action.checked = self.control.isChecked()

            # Most of the time, action's do no care about the event (it
            # contains information about the time the event occurred etc), so
            # we only pass it if the perform method requires it.
            argspec = getargspec(action.perform)

            # If the only argument is 'self' then don't pass the event!
            if len(argspec.argspecargs) == 1:
                action.perform()

            else:
                action.perform(action_event)

    #### Trait event handlers #################################################

    def _enabled_changed(self):
        """ Called when our 'enabled' trait is changed. """
        if self.control is not None:
            self.control.setEnabled(self.enabled)

    def _visible_changed(self):
        """ Called when our 'visible' trait is changed. """
        if self.control is not None:
            self.control.setVisible(self.visible)

    def _checked_changed(self):
        """ Called when our 'checked' trait is changed. """
        if self.control is not None:
            self.control.setChecked(self.checked)

    def _on_action_enabled_changed(self, action, trait_name, old, new):
        """ Called when the enabled trait is changed on an action. """
        if self.control is not None:
            self.control.setEnabled(action.enabled)

    def _on_action_visible_changed(self, action, trait_name, old, new):
        """ Called when the visible trait is changed on an action. """
        if self.control is not None:
            self.control.setVisible(action.visible)

    def _on_action_checked_changed(self, action, trait_name, old, new):
        """ Called when the checked trait is changed on an action. """
        if self.control is not None:
            self.control.setChecked(action.checked)

    def _on_action_name_changed(self, action, trait_name, old, new):
        """ Called when the name trait is changed on an action. """
        if self.control is not None:
            self.control.setText(action.name)

    def _on_action_accelerator_changed(self, action, trait_name, old, new):
        """ Called when the accelerator trait is changed on an action. """
        if self.control is not None:
            self.control.setShortcut(action.accelerator)
Exemple #2
0
class RectGrid3D(RectGrid):
    """
    Provides a cartesian 3D grid for the beamforming results.
    
    The grid has cubic or nearly cubic cells. It is defined by lower and upper 
    x-, y- and  z-limits.
    """

    #: The lower z-limit that defines the grid, defaults to -1.
    z_min = Float(-1.0, desc="minimum  z-value")

    #: The upper z-limit that defines the grid, defaults to 1.
    z_max = Float(1.0, desc="maximum  z-value")

    #: Number of grid points along x-axis, readonly.
    nzsteps = Property(desc="number of grid points along x-axis")

    # Private trait for increment handling
    _increment = Any(0.1)

    #: The cell side length for the grid. This can either be a scalar (same
    #: increments in all 3 dimensions) or a (3,) array of floats with
    #: respective increments in x,y, and z-direction (in m).
    #: Defaults to 0.1.
    increment = Property(desc="step size")

    def _get_increment(self):
        return self._increment

    def _set_increment(self, increment):
        if isscalar(increment):
            try:
                self._increment = absolute(float(increment))
            except:
                raise TraitError(args=self,
                                 name='increment',
                                 info='Float or CArray(3,)',
                                 value=increment)
        elif len(increment) == 3:
            self._increment = array(increment, dtype=float)
        else:
            raise (TraitError(args=self,
                              name='increment',
                              info='Float or CArray(3,)',
                              value=increment))

    # Respective increments in x,y, and z-direction (in m).
    # Deprecated: Use :attr:`~RectGrid.increment` for this functionality
    increment3D = Property(desc="3D step sizes")

    def _get_increment3D(self):
        if isscalar(self._increment):
            return array([self._increment, self._increment, self._increment])
        else:
            return self._increment

    def _set_increment3D(self, inc):
        if not isscalar(inc) and len(inc) == 3:
            self._increment = array(inc, dtype=float)
        else:
            raise (TraitError(args=self,
                              name='increment3D',
                              info='CArray(3,)',
                              value=inc))

    # internal identifier
    digest = Property(
        depends_on = ['x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max', \
        '_increment']
        )

    @property_depends_on('nxsteps, nysteps, nzsteps')
    def _get_size(self):
        return self.nxsteps * self.nysteps * self.nzsteps

    @property_depends_on('nxsteps, nysteps, nzsteps')
    def _get_shape(self):
        return (self.nxsteps, self.nysteps, self.nzsteps)

    @property_depends_on('x_min, x_max, increment3D')
    def _get_nxsteps(self):
        i = abs(self.increment3D[0])
        if i != 0:
            return int(round((abs(self.x_max - self.x_min) + i) / i))
        return 1

    @property_depends_on('y_min, y_max, increment3D')
    def _get_nysteps(self):
        i = abs(self.increment3D[1])
        if i != 0:
            return int(round((abs(self.y_max - self.y_min) + i) / i))
        return 1

    @property_depends_on('z_min, z_max, increment3D')
    def _get_nzsteps(self):
        i = abs(self.increment3D[2])
        if i != 0:
            return int(round((abs(self.z_max - self.z_min) + i) / i))
        return 1

    @property_depends_on('digest')
    def _get_gpos(self):
        """
        Calculates grid co-ordinates.
        
        Returns
        -------
        array of floats of shape (3, :attr:`~Grid.size`)
            The grid point x, y, z-coordinates in one array.
        """
        bpos = mgrid[self.x_min:self.x_max:self.nxsteps*1j, \
                     self.y_min:self.y_max:self.nysteps*1j, \
                     self.z_min:self.z_max:self.nzsteps*1j]
        bpos.resize((3, self.size))
        return bpos

    @cached_property
    def _get_digest(self):
        return digest(self)

    def index(self, x, y, z):
        """
        Queries the indices for a grid point near a certain co-ordinate.

        This can be used to query results or co-ordinates at/near a certain
        co-ordinate.
        
        Parameters
        ----------
        x, y, z : float
            The co-ordinates for which the indices is queried.

        Returns
        -------
        3-tuple of integers
            The indices that give the grid point nearest to the given x, y, z
            co-ordinates from an array with the same shape as the grid.            
        """
        if x < self.x_min or x > self.x_max:
            raise ValueError("x-value out of range %f (%f, %f)" % \
                (x,self.x_min,self.x_max))
        if y < self.y_min or y > self.y_max:
            raise ValueError("y-value out of range %f (%f, %f)" % \
                (y,self.y_min,self.y_max))
        if z < self.z_min or z > self.z_max:
            raise ValueError("z-value out of range %f (%f, %f)" % \
                (z,self.z_min,self.z_max))
        xi = int(round((x - self.x_min) / self.increment3D[0]))
        yi = int(round((y - self.y_min) / self.increment3D[1]))
        zi = int(round((z - self.z_min) / self.increment3D[2]))
        return xi, yi, zi

    def indices(self, x1, y1, z1, x2, y2, z2):
        """
        Queries the indices for a subdomain in the grid.
        
        Allows box-shaped subdomains. This can be used to
        mask or to query results from a certain sector or subdomain.
        
        Parameters
        ----------
        x1, y1, z1, x2, y2, z2 : float
            A box-shaped sector is assumed that is given by two corners
            (x1,y1,z1) and (x2,y2,z2). 

        Returns
        -------
        3-tuple of numpy slice objects
            The indices that can be used to mask/select the grid subdomain from 
            an array with the same shape as the grid.            
        """
        xi1, yi1, zi1 = self.index(min(x1, x2), min(y1, y2), min(z1, z2))
        xi2, yi2, zi2 = self.index(max(x1, x2), max(y1, y2), max(z1, z2))
        return s_[xi1:xi2 + 1], s_[yi1:yi2 + 1], s_[zi1:zi2 + 1]
class HB_Referrer ( HasPrivateTraits ):
    """ Describes the set of objects which refer to a specific object.
    """
    # A weak reference to the heap object being described:
    ref = Any

    # The actual object:
    object = Property

    # The name of this item:
    name = Property

    # The trait name this object referred to its referent by:
    trait_name = Str

    # The objects that refer to this object:
    referrers = Property( depends_on = 'update' )

    # Event fired when the list of referrers should be updated:
    update = Event

    # The name space for all objects in this referrer graph:
    name_space = Any( {} )

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

    view = View(
        Item( 'object',
              show_label = False,
              editor     = ValueEditor()
        ),
        id        = 'etsdevtools.developer.tools.heap_browser.HB_Referrer',
        width     = 0.20,
        height    = 0.25,
        resizable = True,
        handler   = HB_ReferrerHandler
    )

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

    def _get_object ( self ):
        return self.ref()

    @cached_property
    def _get_name ( self ):
        object     = self.object
        name       = object_name( object )
        instances  = self.name_space.setdefault( name, {} )
        n          = instances.setdefault( id( object ), len( instances ) + 1 )
        trait_name = self.trait_name
        if trait_name != '':
            trait_name = '.' + trait_name

        return '%s:%s%s[%s]' % ( name[ name.rfind( '.' ) + 1: ], n, trait_name,
                                 getrefcount( object ) - 2 )

    @cached_property
    def _get_referrers ( self ):
        instance = self.object
        if isinstance( instance, FrameType ):
            return []

        referrers  = []
        dicts      = set()
        seqs       = set()
        name_space = self.name_space
        for object in gc.get_referrers( instance ):
            try:
                referrers.append( HB_Referrer(
                    ref        = weakref.ref( object, self._instance_gone ),
                    name_space = name_space ) )
            except:
                if isinstance( object, dict ):
                    dicts.add( id( object ) )
                elif isinstance( object, SequenceTypes ):
                    seqs.add( id( object ) )
                else:
                    referrers.append( HB_Referrer(
                        ref        = lambda object = object: object,
                        name_space = name_space ) )

        len_dicts = len( dicts )
        len_seqs  = len( seqs )
        if (len_dicts > 0) or (len_seqs > 0):
           for object in gc.get_objects():
               try:
                   object_dict = object.__dict__
                   if (len_dicts > 0) and (id( object_dict ) in dicts):
                       referrers.extend( [
                           HB_Referrer(
                               ref = weakref.ref( object, self._instance_gone ),
                               trait_name = name,
                               name_space = name_space )
                           for name, value in object_dict.iteritems()
                           if instance is value ] )
                   elif len_seqs > 0:
                       isect = seqs.intersect( [ id( value )
                                       for value in object_dict.itervalues() ] )
                       seqs.difference_update( isect )
                       for seq in isect:
                           seqs.remove( seq )
                           for name, value in object_dict.iteritems():
                               if seq == id( value ):
                                   referrers.append( HB_Referrer(
                                       ref = weakref.ref( object,
                                                      self._instance_gone ),
                                       trait_name = name,
                                       name_space = name_space ) )
                                   break
               except:
                   pass

        return referrers

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

    def _instance_gone ( self, ref ):
        """ Handles the instance an HB_Referrer object refers to being garbage
            collected.
        """
        self.update = True
Exemple #4
0
class SingleValueTreeNodeObject(TreeNodeObject):
    """A tree node for objects of types that have a single value."""

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

    #: The parent of this node
    parent = Instance(TreeNodeObject)

    #: Name of the value
    name = Str()

    #: User-specified override of the default label
    label = Str()

    #: The value itself
    value = Any()

    #: Is the value readonly?
    readonly = Bool(False)

    def tno_allows_children(self, node):
        """Returns whether this object can have children (False for this
        class).
        """
        return False

    def tno_has_children(self, node):
        """Returns whether the object has children (False for this class)."""
        return False

    def tno_can_rename(self, node):
        """Returns whether the object's children can be renamed (False for
        this class).
        """
        return False

    def tno_can_copy(self, node):
        """Returns whether the object's children can be copied (True for this
        class).
        """
        return True

    def tno_can_delete(self, node):
        """Returns whether the object's children can be deleted (False for
        this class).
        """
        return False

    def tno_can_insert(self, node):
        """Returns whether the object's children can be inserted (False,
        meaning children are appended, for this class).
        """
        return False

    def tno_get_icon(self, node, is_expanded):
        """Returns the icon for a specified object."""
        return "@icons:%s_node" % self.__class__.__name__[:-4].lower()

    def tno_set_label(self, node, label):
        """Sets the label for a specified object."""
        if label == "?":
            label = ""
        self.label = label

    def tno_get_label(self, node):
        """Gets the label to display for a specified object."""
        if self.label != "":
            return self.label

        if self.name == "":
            return self.format_value(self.value)

        return "%s: %s" % (self.name, self.format_value(self.value))

    def format_value(self, value):
        """Returns the formatted version of the value."""
        return repr(value)

    def node_for(self, name, value):
        """Returns the correct node type for a specified value."""
        for type, node in basic_types():
            if isinstance(value, type):
                break
        else:
            node = OtherNode
            if inspect.isclass(value):
                node = ClassNode

            elif hasattr(value, "__class__"):
                node = ObjectNode

        return node(parent=self,
                    name=name,
                    value=value,
                    readonly=self.readonly)
Exemple #5
0
class IWidget(Interface):
    """ The base interface for all pyface widgets.

    Pyface widgets delegate to a toolkit specific control.
    """

    #: The toolkit specific control that represents the widget.
    control = Any()

    #: The control's optional parent control.
    parent = Any()

    #: Whether or not the control is visible
    visible = Bool(True)

    #: Whether or not the control is enabled
    enabled = Bool(True)

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

    def show(self, visible):
        """ Show or hide the widget.

        Parameters
        ----------
        visible : bool
            Visible should be ``True`` if the widget should be shown.
        """

    def enable(self, enabled):
        """ Enable or disable the widget.

        Parameters
        ----------
        enabled : bool
            The enabled state to set the widget to.
        """

    def create(self):
        """ Creates the toolkit specific control.

        This method should create the control and assign it to the
        :py:attr:``control`` trait.
        """

    def destroy(self):
        """ Destroy the control if it exists. """

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

    def _create_control(self, parent):
        """ Create toolkit specific control that represents the widget.

        Parameters
        ----------
        parent : toolkit control
            The toolkit control to be used as the parent for the widget's
            control.

        Returns
        -------
        control : toolkit control
            A control for the widget.
        """

    def _add_event_listeners(self):
        """ Set up toolkit-specific bindings for events """

    def _remove_event_listeners(self):
        """ Remove toolkit-specific bindings for events """
class _ActorEditor(Editor):
    """ An editor for TVTK scenes.
    """

    # The editor is scrollable, so override the default.
    scrollable = Bool(True)

    # Internal GUI traits.
    _sizer = Any()
    _scene = Any()

    #### Public 'Editor' interface #############################################

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

        factory = self.factory
        self.control = wx.Panel(parent, -1)
        self._sizer = wx.BoxSizer(wx.VERTICAL)
        self.control.SetSizer(self._sizer)

        self._create_scene()

    def update_editor(self):
        """ Updates the editor when the object trait changes external to the
        editor.
        """

        # Everything should really be handled elsewhere in trait notifications.
        # Just pass here.
        pass

    def dispose(self):
        """ Disposes of the contents of an editor.
        """

        # Remove notifications.
        self._setup_scene_notifications(remove=True)

        # Remove the current scene.
        if self._scene is not None:
            self._scene.close()
            self._scene = None

        self._sizer = None

        # This will destroy self.control and all of its children, including the
        # scene's control.
        super(_ActorEditor, self).dispose()

    #### Private '_ActorEditor' interface ##################################

    def _create_scene(self):
        """ Create the TVTK scene widget.
        """

        factory = self.factory
        self._scene = factory.scene_class(self.control, **factory.scene_kwds)
        scene = self._scene

        # Disable rendering on the scene until we're finished.
        scene.disable_render = True
        # Add all of the actors in the current actor map.
        for obj, actors in self.value.items():
            self._add_actors_widgets(actors)
        # Set up Traits notifications.
        self._setup_scene_notifications()
        # Re-enable rendering.
        scene.disable_render = False

        # Ensure the scene's wx control is sized to fill our view's area.  Note
        # that the sizer doesn't automatically layout its contents upon adding
        # a new child so we have to force it to do a layout.
        self._sizer.Add(scene.control, 1, wx.EXPAND)
        self._sizer.Layout()

        wx.EVT_IDLE(scene.control, None)

        # Force a render.
        scene.render()

    def _setup_scene_notifications(self, remove=False):
        """ Set up or remove all of the Trait notifications that control the
        scene widget.
        """

        self.object.on_trait_change(
            self._set_scene_disable_render,
            name=self.factory.disable_render_name,
            remove=remove,
        )
        self.object.on_trait_event(
            self._scene.render,
            name=self.factory.do_render_name,
            remove=remove,
        )
        self.object.on_trait_change(
            self._actors_changed,
            name=self.name + '_items',
            remove=remove,
        )
        self.object.on_trait_change(
            self._actor_map_changed,
            name=self.name,
            remove=remove,
        )

    def _set_scene_disable_render(self, new):
        """ A callback for Traits notifications.
        """

        self._scene.disable_render = new

    def _actors_changed(self, event):
        """ Handle the event of the actors in the actor map changing.
        """

        scene = self._scene
        # Temporarily turn off rendering. We (re)store the old value of
        # disable_render because it may already be True.
        old_disable_render = scene.disable_render
        scene.disable_render = True
        try:
            for obj, actors in event.removed.items():
                self._remove_actors_widgets(actors)
            for obj, actors in event.added.items():
                self._add_actors_widgets(actors)
            for obj, actors in event.changed.items():
                # The actors in the event are the old ones. Grab the new ones
                # from the actor map itself.
                self._remove_actors_widgets(actors)
                self._add_actors_widgets(self.value[obj])
        finally:
            scene.disable_render = old_disable_render
            scene.render()

    def _actor_map_changed(self, object, name, old, new):
        """ Handle the case when the entire actor map is set to something else.
        """

        scene = self._scene
        # Temporarily turn off rendering. We (re)store the old value of
        # disable_render because it may already be True.
        old_disable_render = scene.disable_render
        scene.disable_render = True
        try:
            for obj, actors in old.items():
                self._remove_actors_widgets(actors)
            for obj, actors in new.items():
                self._add_actors_widgets(actors)
        finally:
            scene.disable_render = old_disable_render
            scene.render()

    def _separate_actors_widgets(self, actors_widgets):
        """Given a sequence (or single) of actors or widgets, this returns a
        list of just the actors and another of just the widgets.
        """
        if not hasattr(actors_widgets, '__getitem__'):
            actors_widgets = [actors_widgets]
        actors = []
        widgets = []
        for actor in actors_widgets:
            if actor.is_a('vtk3DWidget'):
                widgets.append(actor)
            else:
                actors.append(actor)
        return actors, widgets

    def _add_actors_widgets(self, actors_widgets):
        """Add actors and widgets to scene."""
        scene = self._scene
        actors, widgets = self._separate_actors_widgets(actors_widgets)
        scene.add_actors(actors)
        scene.add_widgets(widgets)

    def _remove_actors_widgets(self, actors_widgets):
        """Remove actors and widgets from scene."""
        scene = self._scene
        actors, widgets = self._separate_actors_widgets(actors_widgets)
        scene.remove_actors(actors)
        scene.remove_widgets(widgets)
class WorkflowItem(HasStrictTraits):
    """        
    The basic unit of a Workflow: wraps an operation and a list of views.
    
    Notes
    -----
    Because we serialize instances of this, we have to pay careful attention
    to which traits are ``transient`` (and aren't serialized)
    """

    # the operation's id
    friendly_id = DelegatesTo('operation')

    # the operation's name
    name = DelegatesTo('operation')

    # the operation this Item wraps
    operation = Instance(IOperation, copy="ref")

    # for the vertical notebook view, is this page deletable?
    deletable = Bool(True)

    # the handler that's associated with this operation; we get it from the
    # operation plugin, and it controls what operation traits are in the UI
    # and any special handling (heh) of them.  since the handler doesn't
    # maintain any state, we can make and destroy as needed.
    operation_handler = Property(depends_on='operation',
                                 trait=Instance(Handler),
                                 transient=True)

    operation_traits = View(
        Item('operation_handler', style='custom', show_label=False))

    # the Experiment that is the result of applying *operation* to the
    # previous_wi WorkflowItem's ``result``
    result = Instance(Experiment, transient=True)

    # the channels, conditions and statistics from result.  usually these would be
    # Properties (ie, determined dynamically), but that's hard with the
    # multiprocess model.

    channels = List(Str, status=True)
    conditions = Dict(Str, pd.Series, status=True)
    metadata = Dict(Str, Any, status=True)
    statistics = Dict(Tuple(Str, Str), pd.Series, status=True)

    # the IViews associated with this operation
    views = List(IView, copy="ref")

    # the currently selected view
    current_view = Instance(IView, copy="ref")

    # the handler for the currently selected view
    current_view_handler = Property(depends_on='current_view',
                                    trait=Instance(Handler),
                                    transient=True)

    current_view_traits = View(
        Item('current_view_handler', style='custom', show_label=False))

    # the view for the plot params
    plot_params_traits = View(
        Item('current_view_handler',
             editor=InstanceEditor(view='plot_params_traits'),
             style='custom',
             show_label=False))

    # the view for the current plot
    current_plot_view = View(
        Item('current_view_handler',
             editor=InstanceEditor(view='current_plot_view'),
             style='custom',
             show_label=False))

    # the default view for this workflow item
    default_view = Instance(IView, copy="ref")

    # the previous_wi WorkflowItem in the workflow
    previous_wi = Instance('WorkflowItem', transient=True)

    # the next_wi WorkflowItem in the workflow
    next_wi = Instance('WorkflowItem', transient=True)

    # is the wi valid?
    # MAGIC: first value is the default
    status = Enum("invalid",
                  "estimating",
                  "applying",
                  "valid",
                  "loading",
                  status=True)

    # report the errors and warnings
    op_error = Str(status=True)
    op_error_trait = Str(status=True)
    op_warning = Str(status=True)
    op_warning_trait = Str(status=True)
    estimate_error = Str(status=True)
    estimate_warning = Str(status=True)
    view_error = Str(status=True)
    view_error_trait = Str(status=True)
    view_warning = Str(status=True)
    view_warning_trait = Str(status=True)

    # the central event to kick of WorkflowItem update logic
    changed = Event

    # the icon for the vertical notebook view.  Qt specific, sadly.
    icon = Property(depends_on='status', transient=True)

    # synchronization primitive for updating wi traits
    lock = Instance(threading.RLock, (), transient=True)

    # synchronization primitives for plotting
    matplotlib_events = Any(transient=True)
    plot_lock = Any(transient=True)

    # events to track number of times apply() and plot() are called
    apply_called = Event
    plot_called = Event

    @cached_property
    def _get_icon(self):
        if self.status == "valid":
            return QtGui.QStyle.SP_DialogApplyButton  # @UndefinedVariable
        elif self.status == "estimating" or self.status == "applying":
            return QtGui.QStyle.SP_BrowserReload  # @UndefinedVariable
        else:  # self.valid == "invalid" or None
            return QtGui.QStyle.SP_DialogCancelButton  # @UndefinedVariable

    @cached_property
    def _get_operation_handler(self):
        return self.operation.handler_factory(model=self.operation,
                                              context=self)

    @cached_property
    def _get_current_view_handler(self):
        if self.current_view:
            return self.current_view.handler_factory(model=self.current_view,
                                                     context=self)
        else:
            return None

    def __str__(self):
        return "<{}: {}>".format(self.__class__.__name__,
                                 self.operation.__class__.__name__)

    def __repr__(self):
        return "<{}: {}>".format(self.__class__.__name__,
                                 self.operation.__class__.__name__)
Exemple #8
0
class DigSource(HasPrivateTraits):
    """Expose digitization information from a file.

    Parameters
    ----------
    file : File
        Path to the BEM file (*.fif).

    Attributes
    ----------
    fid : Array, shape = (3, 3)
        Each row contains the coordinates for one fiducial point, in the order
        Nasion, RAP, LAP. If no file is set all values are 0.
    """

    file = File(exists=True, filter=['*.fif'])

    inst_fname = Property(Str, depends_on='file')
    inst_dir = Property(depends_on='file')
    _info = Property(depends_on='file')

    points_filter = Any(desc="Index to select a subset of the head shape "
                        "points")
    n_omitted = Property(Int, depends_on=['points_filter'])

    # head shape
    _hsp_points = Property(depends_on='_info',
                           desc="Head shape points in the file (n x 3 array)")
    points = Property(depends_on=['_hsp_points', 'points_filter'],
                      desc="Head shape points selected by the filter (n x 3 "
                      "array)")

    # fiducials
    lpa = Property(depends_on='_info', desc="LPA coordinates (1 x 3 array)")
    nasion = Property(depends_on='_info',
                      desc="Nasion coordinates (1 x 3 array)")
    rpa = Property(depends_on='_info', desc="RPA coordinates (1 x 3 array)")

    # EEG
    eeg_points = Property(depends_on='_info',
                          desc="EEG sensor coordinates (N x 3 array)")

    view = View(
        VGroup(Item('file'),
               Item('inst_fname', show_label=False, style='readonly')))

    @cached_property
    def _get_n_omitted(self):
        if self.points_filter is None:
            return 0
        else:
            return np.sum(self.points_filter == False)  # noqa: E712

    @cached_property
    def _get__info(self):
        if self.file:
            info = None
            _, tree, _ = fiff_open(self.file)
            if len(dir_tree_find(tree, FIFF.FIFFB_MEAS_INFO)) > 0:
                info = read_info(self.file, verbose=False)
            elif len(dir_tree_find(tree, FIFF.FIFFB_ISOTRAK)) > 0:
                info = read_dig_montage(fif=self.file)

            if info is None:
                error(
                    None, "The selected FIFF file does not contain "
                    "digitizer information. Please select a different "
                    "file.", "Error Reading FIFF File")
                self.reset_traits(['file'])
                return
            elif isinstance(info, DigMontage):
                info.transform_to_head()
                digs = list()
                _append_fiducials(digs, info.lpa, info.nasion, info.rpa)
                for idx, pos in enumerate(info.hsp):
                    dig = {
                        'coord_frame': FIFF.FIFFV_COORD_HEAD,
                        'ident': idx,
                        'kind': FIFF.FIFFV_POINT_EXTRA,
                        'r': pos
                    }
                    digs.append(dig)
                info = _empty_info(1)
                info['dig'] = digs
            else:
                # check that all fiducial points are present
                has_point = {
                    FIFF.FIFFV_POINT_LPA: False,
                    FIFF.FIFFV_POINT_NASION: False,
                    FIFF.FIFFV_POINT_RPA: False
                }
                for d in info['dig']:
                    if d['kind'] == FIFF.FIFFV_POINT_CARDINAL:
                        has_point[d['ident']] = True
                if not all(has_point.values()):
                    points = _fiducial_coords(info['dig'])
                    if len(points) == 3:
                        _append_fiducials(info['dig'], *points.T)
                    else:
                        missing = []
                        if not has_point[FIFF.FIFFV_POINT_LPA]:
                            missing.append('LPA')
                        if not has_point[FIFF.FIFFV_POINT_NASION]:
                            missing.append('Nasion')
                        if not has_point[FIFF.FIFFV_POINT_RPA]:
                            missing.append('RPA')
                        error(
                            None, "The selected FIFF file does not contain "
                            "all cardinal points (missing: %s). Please "
                            "select a different file." % ', '.join(missing),
                            "Error Reading FIFF File")
                        self.reset_traits(['file'])
                        return

            return info

    @cached_property
    def _get_inst_dir(self):
        return op.dirname(self.file)

    @cached_property
    def _get_inst_fname(self):
        if self.file:
            return op.basename(self.file)
        else:
            return '-'

    @cached_property
    def _get__hsp_points(self):
        if not self._info:
            return np.zeros((0, 3))

        points = np.array([
            d['r'] for d in self._info['dig']
            if d['kind'] == FIFF.FIFFV_POINT_EXTRA
        ])
        points = np.empty((0, 3)) if len(points) == 0 else points
        return points

    @cached_property
    def _get_points(self):
        if self.points_filter is None:
            return self._hsp_points
        else:
            return self._hsp_points[self.points_filter]

    def _cardinal_point(self, ident):
        """Coordinates for a cardinal point."""
        if self._info:
            for d in self._info['dig']:
                if (d['kind'] == FIFF.FIFFV_POINT_CARDINAL
                        and d['ident'] == ident):
                    return d['r'][None, :]
        return np.zeros((1, 3))

    @cached_property
    def _get_nasion(self):
        return self._cardinal_point(FIFF.FIFFV_POINT_NASION)

    @cached_property
    def _get_lpa(self):
        return self._cardinal_point(FIFF.FIFFV_POINT_LPA)

    @cached_property
    def _get_rpa(self):
        return self._cardinal_point(FIFF.FIFFV_POINT_RPA)

    @cached_property
    def _get_eeg_points(self):
        if self._info:
            return np.array([
                d['r'] for d in self._info['dig']
                if d['kind'] == FIFF.FIFFV_POINT_EEG
            ])
        else:
            return np.empty((0, 3))

    def _file_changed(self):
        self.reset_traits(('points_filter', ))
Exemple #9
0
class Axis(ConfigLoadable):
    '''
    '''
    id = Int
    #    name = Str
    position = Float
    negative_limit = Float
    positive_limit = Float
    pdir = Str
    parent = Any(transient=True)
    calculate_parameters = Bool(True)
    drive_ratio = Float(1)

    velocity = Property(depends_on='_velocity')
    _velocity = Float(enter_set=True, auto_set=False)

    acceleration = Property(depends_on='_acceleration')
    _acceleration = Float(enter_set=True, auto_set=False)

    deceleration = Property(depends_on='_deceleration')
    _deceleration = Float(enter_set=True, auto_set=False)

    machine_velocity = Float
    machine_acceleration = Float
    machine_deceleration = Float
    # sets handled by child class

    nominal_velocity = Float
    nominal_acceleration = Float
    nominal_deceleration = Float

    sign = CInt(1)

    def _get_velocity(self):
        return self._velocity

    def _get_acceleration(self):
        return self._acceleration

    def _get_deceleration(self):
        return self._deceleration

    def upload_parameters_to_device(self):
        pass

    @on_trait_change('_velocity, _acceleration, _deceleration')
    def update_machine_values(self, obj, name, old, new):
        setattr(self, 'machine{}'.format(name), new)

    def _calibration_changed(self):
        self.parent.update_axes()

    def simple_view(self):
        v = View(
            Item('calculate_parameters'),
            Item('velocity',
                 format_str='%0.3f',
                 enabled_when='not calculate_parameters'),
            Item('acceleration',
                 format_str='%0.3f',
                 enabled_when='not calculate_parameters'),
            Item('deceleration',
                 format_str='%0.3f',
                 enabled_when='not calculate_parameters'), Item('drive_ratio'))
        return v

    def full_view(self):
        return self.simple_view()

    def dump(self):
        '''
        '''
        pass
#        self.loaded = False
#
#        p = os.path.join(self.pdir, '.%s' % self.name)
#        with open(p, 'w') as f:
#            pickle.dump(self, f)
#    def load_parameters_from_config(self, path):
#        self.config_path = path
#        self._load_parameters_from_config(path)
#
#    def load_parameters(self, pdir):
#        '''
#        '''
# #        self.pdir = pdir
# #        p = os.path.join(pdir, '.%s' % self.name)
# #
# #        if os.path.isfile(p):
# #            return p
# #        else:
#        self.load(pdir)

    def save(self):
        pass

    def ask(self, cmd):
        return self.parent.ask(cmd)

    def _get_parameters(self, path):
        '''
  
        '''
        #        cp = ConfigParser.ConfigParser()
        #        cp.read())
        params = []
        #        if path is None:
        if not os.path.isfile(path):
            path = os.path.join(path, '{}axis.cfg'.format(self.name))

        cp = self.get_configuration(path)
        if cp:
            params = [item for s in cp.sections() for item in cp.items(s)]

#        for ai in a:
#            print ai
#
#        for s in cp.sections():
#            for i in cp.items(s):
#                params.append(i)
        return params

    def _validate_float(self, v):
        try:
            v = float(v)
            return v
        except ValueError:
            pass
class GridModel(HasTraits):
    """ A model that provides data for a grid. """

    # fixme : factor this default model into "SimpleGridModel" or similar
    # An optional 2-dimensional list/array containing the grid data.
    data = Any()

    # The rows in the model.
    rows = List(GridRow)

    # The columns in the model.
    columns = List(GridColumn)

    # Show row headers?
    show_row_headers = Bool(True)

    # Show column headers?
    show_column_headers = Bool(True)

    # Fired when the data in the model has changed.
    model_changed = Event()

    def __init__(self, **traits):
        """ Create a new grid model. """

        # Base class constructors.
        HasTraits.__init__(self, **traits)

        # The wx virtual table hook.
        self._grid_table_base = _GridTableBase(self)

        if len(self.columns) == 0 and self.data is not None:
            print("Building default table column model")
            columns = []
            # Assume data is rectangular and use the length of the first row.
            for i in range(len(self.data[0])):
                columns.append(GridColumn(label=str(i)))
            self.columns = columns

        return

    # ------------------------------------------------------------------------
    # 'wxGridTableBase' interface.
    # ------------------------------------------------------------------------

    def GetNumberRows(self):
        """ Return the number of rows in the model. """

        return len(self.data)

    def GetNumberCols(self):
        """ Return the number of columns in the model. """

        return len(self.columns)

    def IsEmptyCell(self, row, col):
        """ Is the specified cell empty? """

        try:
            return not self.data[row][col]

        except IndexError:
            return True

    # Get/Set values in the table. The Python versions of these methods can
    # handle any data-type, (as long as the Editor and Renderer understands the
    # type too,) not just strings as in the C++ version.
    def GetValue(self, row, col):
        """ Get the value at the specified row and column. """

        try:
            return self.data[row][col]

        except IndexError:
            pass

        return ""

    def SetValue(self, row, col, value):
        """ Set the value at the specified row and column. """

        label = self.GetColLabelValue(col)

        try:
            self.data[row][col] = value

        except IndexError:
            # Add a new row.
            self.data.append([0] * self.GetNumberCols())
            self.data[row][col] = value

            # Tell the grid that we've added a row.
            #
            # N.B wxGridTableMessage(table, whatWeDid, howMany)
            message = GridTableMessage(self, GRIDTABLE_NOTIFY_ROWS_APPENDED, 1)

            # Trait event notification.
            self.model_changed = message

    def GetRowLabelValue(self, row):
        """ Called when the grid needs to display a row label. """

        return str(row)

    def GetColLabelValue(self, col):
        """ Called when the grid needs to display a column label. """

        return self.columns[col].label

    def GetTypeName(self, row, col):
        """ Called to determine the kind of editor/renderer to use.

        This doesn't necessarily have to be the same type used natively by the
        editor/renderer if they know how to convert.

        """

        return self.columns[col].type

    def CanGetValueAs(self, row, col, type_name):
        """ Called to determine how the data can be fetched.

        This allows you to enforce some type-safety in the grid.

        """

        column_typename = self.GetTypeName(row, col)

        return type_name == column_typename

    def CanSetValueAs(self, row, col, type_name):
        """ Called to determine how the data can be stored.

        This allows you to enforce some type-safety in the grid.

        """

        return self.CanGetValueAs(row, col, type_name)

    def DeleteRows(self, pos, num_rows):
        """ Called when the view is deleting rows. """

        del self.data[pos:pos + num_rows]

        # Tell the grid that we've deleted some rows.
        #
        # N.B Because of a bug in wxPython we have to send a "rows appended"
        # --- message with a negative number, instead of the "rows deleted"
        #     message 8^() TINSTAFS!
        message = GridTableMessage(self, GRIDTABLE_NOTIFY_ROWS_APPENDED,
                                   -num_rows)

        # Trait event notification.
        self.model_changed = message

        return True
Exemple #11
0
class TransformStatisticOp(HasStrictTraits):
    """
    Apply a function to a statistic, creating a new statistic.  The function can
    be applied to the entire statistic, or it can be applied individually to 
    groups of the statistic.  The function should take a `pandas.Series` as its
    only argument.  Return type is arbitrary, but a to be used with the rest of
    Cytoflow it should probably be a numeric type or an iterable of numeric
    types.
    
    As a special case, if the function returns a pandas.Series *with the same
    index that it was passed*, it is interpreted as a transformation.  The 
    resulting statistic will have the same length, index names and index levels
    as the original statistic.

    Attributes
    ----------
    name : Str
        The operation name.  Becomes the first element in the
        Experiment.statistics key tuple.
    
    statistic : Tuple(Str, Str)
        The statistic to apply the function to.
        
    function : Callable
        The function used to transform the statistic.  `function` must take a 
        Series as its only parameter.  The return type is arbitrary, to work 
        with the rest of Cytoflow it should probably be a numeric type or an
        iterable of numeric types..  If `statistic_name` is unset, the name of 
        the function becomes the second in element in the Experiment.statistics
        key tuple.
        
    statistic_name : Str
        The name of the function; if present, becomes the second element in
        the Experiment.statistics key tuple.
        
    by : List(Str)
        A list of metadata attributes to aggregate the input statistic before 
        applying the function.  For example, if the statistic has two indices
        `Time` and `Dox`, setting `by = ["Time", "Dox"]` will apply `function` 
        separately to each subset of the data with a unique combination of
        `Time` and `Dox`.
        
    fill : Any (default = 0)
        Value to use in the statistic if a slice of the data is empty.
   
    Examples
    --------
    
    >>> stats_op = ChannelStatisticOp(name = "Mean",
    ...                               channel = "Y2-A",
    ...                               function = np.mean,
    ...                               by = ["Dox"])
    >>> ex2 = stats_op.apply(ex)
    >>> log_op = TransformStatisticOp(name = "LogMean",
    ...                               statistic = ("Mean", "mean"),
    ...                               function = np.log)
    >>> ex3 = log_op.apply(ex2)  
    """

    id = Constant('edu.mit.synbio.cytoflow.operations.transform_statistic')
    friendly_id = Constant("Transform Statistic")

    name = CStr
    statistic = Tuple(Str, Str)
    function = Callable
    statistic_name = Str
    by = List(Str)
    fill = Any(0)

    def apply(self, experiment):

        if not experiment:
            raise util.CytoflowOpError("Must specify an experiment")

        if not self.name:
            raise util.CytoflowOpError("Must specify a name")

        if not self.statistic:
            raise util.CytoflowViewError("Statistic not set")

        if self.statistic not in experiment.statistics:
            raise util.CytoflowViewError(
                "Can't find the statistic {} in the experiment".format(
                    self.statistic))
        else:
            stat = experiment.statistics[self.statistic]

        if not self.function:
            raise util.CytoflowOpError("Must specify a function")

        if not self.by:
            raise util.CytoflowOpError("Must specify some grouping conditions "
                                       "in 'by'")

        for b in self.by:
            if b not in stat.index.names:
                raise util.CytoflowOpError("{} is not a statistic index; "
                                           " must be one of {}".format(
                                               b, stat.index.names))

        data = stat.reset_index()

        idx = pd.MultiIndex.from_product([data[x].unique() for x in self.by],
                                         names=self.by)

        new_stat = pd.Series(data=self.fill, index=idx,
                             dtype=np.dtype(object)).sort_index()

        for group in data[self.by].itertuples(index=False, name=None):
            s = stat.xs(group, level=self.by)
            if len(s) == 0:
                continue
            if len(group) == 1:
                group = group[0]
            try:
                new_stat[group] = self.function(s)
            except Exception as e:
                raise util.CytoflowOpError("On group {}, your function threw "
                                           "an error: {}".format(group, e))

            # check for, and warn about, NaNs.
            if np.any(np.isnan(new_stat[group])):
                warn("Category {} returned {}".format(group, x),
                     util.CytoflowOpWarning)

        matched_series = True
        for group in data[self.by].itertuples(index=False):
            s = stat.xs(group, level=self.by)
            if len(group) == 1:
                group = group[0]
            if isinstance(new_stat[group], pd.Series) and \
                s.index.equals(new_stat[group].index):
                pass
            else:
                matched_series = False
                break

        if matched_series:
            names = set(self.by) | set(new_stat.iloc[0].index.names)
            new_stat = pd.concat(new_stat.to_dict(), names=names)

        # try to convert to numeric, but if there are non-numeric bits ignore
        new_stat = pd.to_numeric(new_stat, errors='ignore')

        new_experiment = experiment.clone()
        new_experiment.history.append(
            self.clone_traits(transient=lambda t: True))
        if self.statistic_name:
            new_experiment.statistics[(self.name,
                                       self.statistic_name)] = new_stat
        else:
            new_experiment.statistics[(self.name,
                                       self.function.__name__)] = new_stat

        return new_experiment
Exemple #12
0
class Surf(Pipeline):
    """
    Plots a surface using regularly-spaced elevation data supplied as a 2D
    array.

    **Function signatures**::

        surf(s, ...)
        surf(x, y, s, ...)
        surf(x, y, f, ...)

    s is the elevation matrix, a 2D array, where indices along the first
    array axis represent x locations, and indices along the second array
    axis represent y locations.

    x and y can be 1D or 2D arrays such as returned by numpy.ogrid or
    numpy.mgrid. Arrays returned by numpy.meshgrid require a transpose
    first to obtain correct indexing order.
    The points should be located on an orthogonal grid (possibly
    non-uniform). In other words, all the points sharing a same
    index in the s array need to have the same x or y value. For
    arbitrary-shaped position arrays (non-orthogonal grids), see the mesh
    function.

    If only 1 array s is passed, the x and y arrays are assumed to be
    made from the indices of arrays, and an uniformly-spaced data set is
    created.

    If 3 positional arguments are passed the last one must be an array s,
    or a callable, f, that returns an array. x and y give the
    coordinates of positions corresponding to the s values."""

    _source_function = Callable(array2d_source)

    _pipeline = [WarpScalarFactory, PolyDataNormalsFactory, SurfaceFactory]

    warp_scale = Any(1,
                     help="""scale of the z axis (warped from
                        the value of the scalar). By default this scale
                        is a float value.

                        If you specify 'auto', the scale is calculated to
                        give a pleasant aspect ratio to the plot,
                        whatever the bounds of the data.

                        If you specify a value for warp_scale in
                        addition to an extent, the warp scale will be
                        determined by the warp_scale, and the plot be
                        positioned along the z axis with the zero of the
                        data centered on the center of the extent. If you
                        are using explicit extents, this is the best way
                        to control the vertical scale of your plots.

                        If you want to control the extent (or range)
                        of the surface object, rather than its scale,
                        see the `extent` keyword argument.
                        """)

    mask = Array(help="""boolean mask array to suppress some data points.
                 Note: this works based on colormapping of scalars and will
                 not work if you specify a solid color using the
                 `color` keyword.""")

    def __call_internal__(self, *args, **kwargs):
        """ Override the call to be able to scale automatically the axis.
        """
        self.source = self._source_function(*args, **kwargs)
        kwargs.pop('name', None)
        # Deal with both explicit warp scale and extent, this is
        # slightly hairy. The wigner example is a good test case for
        # this.
        if not 'warp_scale' in kwargs and not 'extent' in kwargs:
            try:
                xi, xf, yi, yf, _, _ = self.source.data.bounds
                zi, zf = self.source.data.scalar_range
            except AttributeError:
                xi, xf, yi, yf, _, _ = self.source.image_data.bounds
                zi, zf = self.source.image_data.scalar_range
            aspect_ratios = [(zf - zi) / (xf - xi), (zf - zi) / (yf - yi)]
            if min(aspect_ratios) < 0.01 or max(aspect_ratios) > 100:
                print('Warning: the range of your scalar values differs by ' \
                'more than a factor 100 than the range of the grid values ' \
                'and you did not '\
                'specify a warp_scale. You could try warp_scale="auto".')
        if 'warp_scale' in kwargs and not kwargs['warp_scale'] == 'auto' \
                and 'extent' in kwargs:
            # XXX: I should use the logging module.
            print('Warning: both warp_scale and extent keyword argument ' \
            'specified, the z bounds of the extents will be overridden')
            xi, xf, yi, yf, zi, zf = kwargs['extent']
            zo = 0.5 * (zi + zf)
            try:
                si, sf = self.source.data.scalar_range
            except AttributeError:
                si, sf = self.source.image_data.scalar_range
            z_span = kwargs['warp_scale'] * abs(sf - si)
            zi = zo + si * kwargs['warp_scale']
            zf = zi + z_span
            kwargs['extent'] = (xi, xf, yi, yf, zi, zf)
            kwargs['warp_scale'] = 1
        elif kwargs.get('warp_scale', 1) == 'auto':
            if 'extent' in kwargs:
                if 'warp_scale' in kwargs:
                    print("Warning: extent specified, warp_scale='auto' " \
                    "ignored.")
            else:
                try:
                    xi, xf, yi, yf, _, _ = self.source.data.bounds
                    zi, zf = self.source.data.scalar_range
                except AttributeError:
                    xi, xf, yi, yf, _, _ = self.source.image_data.bounds
                    zi, zf = self.source.image_data.scalar_range
                z0 = zf - zi
                dz = 0.3 * ((xf - xi) + (yf - yi))
                zi = z0 - 0.5 * dz
                zf = z0 + 0.5 * dz
                kwargs['extent'] = (xi, xf, yi, yf, zi, zf)
            kwargs['warp_scale'] = 1.
        self.store_kwargs(kwargs)

        # Copy the pipeline so as not to modify it for the next call
        self.pipeline = self._pipeline[:]
        return self.build_pipeline()
Exemple #13
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 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)

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

    # 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')
        self.sync_value(factory.refresh, 'refresh', 'from')
        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')

        # 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 list(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, 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)

    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 _update_changed(self):
        self.update_editor()

    def _refresh_changed(self):
        self.refresh_editor()

    def _selected_changed(self, new):
        if not self._no_update:
            try:
                selected_row = self.value.index(new)
            except Exception as e:
                if TRAITS_DEBUG:
                    from traits.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 == -1:
                smodel.clearSelection()
            else:
                smodel.select(
                    self.model.index(selected_row, 0),
                    QtGui.QItemSelectionModel.ClearAndSelect
                    | QtGui.QItemSelectionModel.Rows)

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

    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, 0), 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)
Exemple #14
0
class TimeSamples( SamplesGenerator ):
    """
    Container for time data in `*.h5` format.
    
    This class loads measured data from h5 files and
    and provides information about this data.
    It also serves as an interface where the data can be accessed
    (e.g. for use in a block chain) via the :meth:`result` generator.
    """

    #: Full name of the .h5 file with data.
    name = File(filter=['*.h5'], 
        desc="name of data file")

    #: Basename of the .h5 file with data, is set automatically.
    basename = Property( depends_on = 'name', #filter=['*.h5'], 
        desc="basename of data file")
    
    #: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional .
    calib = Trait( Calib, 
        desc="Calibration data")
    
    #: Number of channels, is set automatically / read from file.
    numchannels = CLong(0, 
        desc="number of input channels")

    #: Number of time data samples, is set automatically / read from file.
    numsamples = CLong(0, 
        desc="number of samples")

    #: The time data as array of floats with dimension (numsamples, numchannels).
    data = Any( transient = True, 
        desc="the actual time data array")

    #: HDF5 file object
    h5f = Instance(tables.File, transient = True)
    
    # internal identifier
    digest = Property( depends_on = ['basename', 'calib.digest'])

    traits_view = View(
        ['name{File name}', 
            ['sample_freq~{Sampling frequency}', 
            'numchannels~{Number of channels}', 
            'numsamples~{Number of samples}', 
            '|[Properties]'], 
            '|'
        ], 
        title='Time data', 
        buttons = OKCancelButtons
                    )

    @cached_property
    def _get_digest( self ):
        return digest(self)
    
    @cached_property
    def _get_basename( self ):
        return path.splitext(path.basename(self.name))[0]
    
    @on_trait_change('basename')
    def load_data( self ):
        """ 
        Open the .h5 file and set attributes.
        """
        if not path.isfile(self.name):
            # no file there
            self.numsamples = 0
            self.numchannels = 0
            self.sample_freq = 0
            raise IOError("No such file: %s" % self.name)
        if self.h5f != None:
            try:
                self.h5f.close()
            except IOError:
                pass
        self.h5f = tables.open_file(self.name)
        self.data = self.h5f.root.time_data
        self.sample_freq = self.data.get_attr('sample_freq')
        (self.numsamples, self.numchannels) = self.data.shape

    def result(self, num=128):
        """
        Python generator that yields the output block-wise.
                
        Parameters
        ----------
        num : integer, defaults to 128
            This parameter defines the size of the blocks to be yielded
            (i.e. the number of samples per block) .
        
        Returns
        -------
        Samples in blocks of shape (num, numchannels). 
            The last block may be shorter than num.
        """
        if self.numsamples == 0:
            raise IOError("no samples available")
        i = 0
        if self.calib:
            if self.calib.num_mics == self.numchannels:
                cal_factor = self.calib.data[newaxis]
            else:
                raise ValueError("calibration data not compatible: %i, %i" % \
                            (self.calib.num_mics, self.numchannels))
            while i < self.numsamples:
                yield self.data[i:i+num]*cal_factor
                i += num
        else:
            while i < self.numsamples:
                yield self.data[i:i+num]
                i += num
Exemple #15
0
class Test(HasTraits):
    i = Int(99)
    lf = List(Float)
    foo = Instance(Foo, ())
    any = Any([1, 2, 3])
class Team(HasTraits):
    leader = Instance(Person)

    member_names = List(Str())

    any_value = Any()
class TreeItem(HasTraits):
    """ A generic base-class for items in a tree data structure. """

    # 'TreeItem' interface -------------------------------------------------

    # Does this item allow children?
    allows_children = Bool(True)

    # The item's children.
    children = List(Instance("TreeItem"))

    # Arbitrary data associated with the item.
    data = Any()

    # Does the item have any children?
    has_children = Property(Bool)

    # The item's parent.
    parent = Instance("TreeItem")

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

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

        if self.data is None:
            s = ""

        else:
            s = str(self.data)

        return s

    # ------------------------------------------------------------------------
    # 'TreeItem' interface.
    # ------------------------------------------------------------------------

    # Properties -----------------------------------------------------------

    # has_children
    def _get_has_children(self):
        """ True iff the item has children. """

        return len(self.children) != 0

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

    def append(self, child):
        """ Appends a child to this item.

        This removes the child from its current parent (if it has one).

        """

        return self.insert(len(self.children), child)

    def insert(self, index, child):
        """ Inserts a child into this item at the specified index.

        This removes the child from its current parent (if it has one).

        """

        if child.parent is not None:
            child.parent.remove(child)

        child.parent = self
        self.children.insert(index, child)

        return child

    def remove(self, child):
        """ Removes a child from this item. """

        child.parent = None
        self.children.remove(child)

        return child

    def insert_before(self, before, child):
        """ Inserts a child into this item before the specified item.

        This removes the child from its current parent (if it has one).

        """

        index = self.children.index(before)

        self.insert(index, child)

        return (index, child)

    def insert_after(self, after, child):
        """ Inserts a child into this item after the specified item.

        This removes the child from its current parent (if it has one).

        """

        index = self.children.index(after)

        self.insert(index + 1, child)

        return (index, child)
class PlotApp(HasTraits):

    plotdata = Instance(ArrayPlotData)
    numpoints = Int(300)
    symbols = List()

    sym1 = Enum(values="symbols")
    sym2 = Enum(values="symbols")

    returns_plot = Instance(Plot)
    times_ds = Any()  # arraydatasource for the time axis data
    corr_plot = Instance(Plot)
    corr_renderer = Any()

    traits_view = View(
        HSplit(
            Item(
                'returns_plot', editor=ComponentEditor(), show_label=False),
            VGroup(
                VGroup(
                    Item(
                        'sym1', width=-225),
                    Item(
                        'sym2', width=-225), ),
                Item(
                    'corr_plot',
                    editor=ComponentEditor(),
                    show_label=False,
                    width=-275), ), ),
        width=1024,
        height=500,
        resizable=True,
        title="Correlations of returns")

    def __init__(self, *symbols, **kwtraits):
        super(PlotApp, self).__init__(symbols=list(symbols), **kwtraits)
        self._create_data(*symbols)
        self._create_returns_plot()
        self._create_corr_plot()
        if len(self.symbols) > 1:
            self.sym2 = self.symbols[1]

        return

    def _create_returns_plot(self):
        plot = Plot(self.plotdata)
        plot.legend.visible = True
        #FIXME: The legend move tool doesn't seem to quite work right now
        #plot.legend.tools.append(LegendTool(plot.legend))
        plot.x_axis = None
        x_axis = PlotAxis(
            plot,
            orientation="bottom",
            tick_generator=ScalesTickGenerator(scale=CalendarScaleSystem()))
        plot.overlays.append(x_axis)
        plot.x_grid.tick_generator = x_axis.tick_generator
        for i, name in enumerate(self.plotdata.list_data()):
            if name == "times":
                continue
            renderer = plot.plot(
                ("times", name),
                type="line",
                name=name,
                color=tuple(COLOR_PALETTE[i]))[0]

        # Tricky: need to set auto_handle_event on the RangeSelection
        # so that it passes left-clicks to the PanTool
        # FIXME: The range selection is still getting the initial left down
        renderer.tools.append(
            RangeSelection(
                renderer, left_button_selects=False, auto_handle_event=False))
        plot.tools.append(
            PanTool(
                plot,
                drag_button="left",
                constrain=True,
                constrain_direction="x"))
        plot.overlays.append(
            ZoomTool(
                plot, tool_mode="range", max_zoom_out=1.0))
        # Attach the range selection to the last renderer; any one will do
        self._range_selection_overlay = RangeSelectionOverlay(
            renderer, metadata_name="selections")
        renderer.overlays.append(self._range_selection_overlay)
        # Grab a reference to the Time axis datasource and add a listener to its
        # selections metadata
        self.times_ds = renderer.index
        self.times_ds.on_trait_change(self._selections_changed,
                                      'metadata_changed')
        self.returns_plot = plot

    def _create_corr_plot(self):
        plot = Plot(self.plotdata, padding=0)
        plot.padding_left = 25
        plot.padding_bottom = 25
        plot.tools.append(PanTool(plot))
        plot.overlays.append(ZoomTool(plot))
        self.corr_plot = plot

    def _create_data(self, *names):
        numpoints = self.numpoints
        plotdata = ArrayPlotData(times=create_dates(numpoints))
        for name in names:
            plotdata.set_data(
                name, cumprod(random.lognormal(
                    0.0, 0.04, size=numpoints)))
        self.plotdata = plotdata

    def _selections_changed(self, event):
        if self.corr_renderer is None:
            return
        if not isinstance(event, dict) or "selections" not in event:
            return
        corr_index = self.corr_renderer.index
        selections = event["selections"]
        if selections is None:
            corr_index.metadata.pop("selections", None)
            return
        else:
            low, high = selections
            data = self.times_ds.get_data()
            low_ndx = data.searchsorted(low)
            high_ndx = data.searchsorted(high)
            corr_index.metadata["selections"] = arange(
                low_ndx, high_ndx + 1, 1, dtype=int)
            self.corr_plot.request_redraw()

    @on_trait_change("sym1,sym2")
    def _update_corr_symbols(self):
        plot = self.corr_plot
        if self.corr_renderer is not None:
            # Remove the old one
            plot.remove(self.corr_renderer)
            self.corr_renderer = None

        self.corr_renderer = plot.plot(
            (self.sym1, self.sym2), type="scatter", color="blue")[0]
        self.corr_renderer.overlays.append(
            ScatterInspectorOverlay(
                self.corr_renderer, selection_color="lightgreen"))
        plot.request_redraw()
Exemple #19
0
class QualityAgentView(Dialog):

    size = Tuple((700, 900))
    title = Str("Quality Agent")

    # The associated LoggerService.
    service = Any()

    msg = Str("")
    subject = Str("Untitled Error Report")
    to_address = Str()
    cc_address = Str("")
    from_address = Str()
    smtp_server = Str()
    priority = Str(priority_levels[2])
    comments = Str("None")
    include_userdata = Any

    ###########################################################################
    # Protected 'Dialog' interface.
    ###########################################################################

    # fixme: Ideally, this should be passed in; this topic ID belongs to the
    #        Enlib help project/plug-in.
    help_id = "enlib|HID_Quality_Agent_Dlg"

    def _create_dialog_area(self, parent):
        """ Creates the main content of the dialog. """
        import wx

        parent.SetSizeHints(minW=300, minH=575)

        # Add the main panel
        sizer = wx.BoxSizer(wx.VERTICAL)
        panel = wx.Panel(parent, -1)
        panel.SetSizer(sizer)
        panel.SetAutoLayout(True)

        # Add a descriptive label at the top ...
        label = wx.StaticText(panel, -1, "Send a comment or bug report ...")
        sizer.Add(label, 0, wx.ALL, border=5)

        # Add the stack trace view ...
        error_panel = self._create_error_panel(panel)
        sizer.Add(
            error_panel, 1, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, border=5
        )

        # Update the layout:
        sizer.Fit(panel)

        # Add the error report view ...
        report_panel = self._create_report_panel(panel)
        sizer.Add(
            report_panel, 2, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, border=5
        )

        # Update the layout:
        sizer.Fit(panel)

        return panel

    def _create_buttons(self, parent):
        """ Creates the buttons. """
        import wx

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        # 'Send' button.
        send = wx.Button(parent, wx.ID_OK, "Send")
        wx.EVT_BUTTON(parent, wx.ID_OK, self._on_send)
        sizer.Add(send)
        send.SetDefault()

        # 'Cancel' button.
        cancel = wx.Button(parent, wx.ID_CANCEL, "Cancel")
        wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel)
        sizer.Add(cancel, 0, wx.LEFT, 10)

        # 'Help' button.
        if len(self.help_id) > 0:
            help = wx.Button(parent, wx.ID_HELP, "Help")
            wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help)
            sizer.Add(help, 0, wx.LEFT, 10)

        return sizer

    ### Utility methods #######################################################

    def _create_error_panel(self, parent):
        import wx

        box = wx.StaticBox(parent, -1, "Message:")
        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)

        # Print the stack trace
        label2 = wx.StaticText(
            parent,
            -1,
            "The following information will be included in the report:",
        )
        sizer.Add(
            label2,
            0,
            wx.LEFT | wx.TOP | wx.BOTTOM | wx.CLIP_CHILDREN,
            border=5,
        )

        details = wx.TextCtrl(
            parent,
            -1,
            self.msg,
            size=(-1, 75),
            style=wx.TE_MULTILINE
            | wx.TE_READONLY
            | wx.HSCROLL
            | wx.VSCROLL
            | wx.TE_RICH2
            | wx.CLIP_CHILDREN,
        )
        details.SetSizeHints(minW=-1, minH=75)
        # Set the font to not be proportional
        font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)
        details.SetStyle(0, len(self.msg), wx.TextAttr(font=font))
        sizer.Add(details, 1, wx.EXPAND | wx.ALL | wx.CLIP_CHILDREN, 5)

        return sizer

    def _create_report_panel(self, parent):
        import wx

        box = wx.StaticBox(parent, -1, "Report Information:")
        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)

        # Add email info ...
        sizer.Add(self._create_email_info(parent), 0, wx.ALL | wx.EXPAND, 5)

        # Add priority combo:
        sizer.Add(self._create_priority_combo(parent), 0, wx.ALL | wx.RIGHT, 5)

        # Extra comments from the user:
        label3 = wx.StaticText(parent, -1, "Additional Comments:")
        sizer.Add(
            label3, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.CLIP_CHILDREN, 5
        )

        comments_field = wx.TextCtrl(
            parent,
            -1,
            self.comments,
            size=(-1, 75),
            style=wx.TE_MULTILINE | wx.TE_RICH2 | wx.CLIP_CHILDREN,
        )
        comments_field.SetSizeHints(minW=-1, minH=75)
        font = wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL)
        comments_field.SetStyle(0, len(self.comments), wx.TextAttr(font=font))
        sizer.Add(comments_field, 1, wx.ALL | wx.EXPAND | wx.CLIP_CHILDREN, 5)
        wx.EVT_TEXT(parent, comments_field.GetId(), self._on_comments)

        # Include the project combobox?
        if len(self.service.mail_files) > 0:
            sizer.Add(self._create_project_upload(parent), 0, wx.ALL, border=5)

        return sizer

    def _create_email_info(self, parent):
        import wx

        # Layout setup ..
        sizer = wx.FlexGridSizer(5, 2, 10, 10)
        sizer.AddGrowableCol(1)

        title_label = wx.StaticText(parent, -1, "Subject:")
        sizer.Add(title_label, 0, wx.ALL | wx.ALIGN_RIGHT)
        title_field = wx.TextCtrl(parent, -1, self.subject, wx.Point(-1, -1))
        sizer.Add(
            title_field,
            1,
            wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN,
        )
        wx.EVT_TEXT(parent, title_field.GetId(), self._on_subject)

        to_label = wx.StaticText(parent, -1, "To:")
        sizer.Add(to_label, 0, wx.ALL | wx.ALIGN_RIGHT)
        to_field = wx.TextCtrl(parent, -1, self.to_address)
        sizer.Add(
            to_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN
        )
        wx.EVT_TEXT(parent, to_field.GetId(), self._on_to)

        cc_label = wx.StaticText(parent, -1, "Cc:")
        sizer.Add(cc_label, 0, wx.ALL | wx.ALIGN_RIGHT)
        cc_field = wx.TextCtrl(parent, -1, "")
        sizer.Add(
            cc_field, 1, wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN
        )
        wx.EVT_TEXT(parent, cc_field.GetId(), self._on_cc)

        from_label = wx.StaticText(parent, -1, "From:")
        sizer.Add(from_label, 0, wx.ALL | wx.ALIGN_RIGHT)
        from_field = wx.TextCtrl(parent, -1, self.from_address)
        sizer.Add(
            from_field,
            1,
            wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN,
        )
        wx.EVT_TEXT(parent, from_field.GetId(), self._on_from)

        smtp_label = wx.StaticText(parent, -1, "SMTP Server:")
        sizer.Add(smtp_label, 0, wx.ALL | wx.ALIGN_RIGHT)
        smtp_server_field = wx.TextCtrl(parent, -1, self.smtp_server)
        sizer.Add(
            smtp_server_field,
            1,
            wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT | wx.CLIP_CHILDREN,
        )
        wx.EVT_TEXT(parent, smtp_server_field.GetId(), self._on_smtp_server)

        return sizer

    def _create_priority_combo(self, parent):
        import wx

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        label = wx.StaticText(parent, -1, "How critical is this issue?")
        sizer.Add(label, 0, wx.ALL, border=0)

        cb = wx.ComboBox(
            parent,
            -1,
            self.priority,
            wx.Point(90, 50),
            wx.Size(95, -1),
            priority_levels,
            wx.CB_READONLY,
        )
        sizer.Add(cb, 1, wx.EXPAND | wx.LEFT | wx.CLIP_CHILDREN, border=10)

        wx.EVT_COMBOBOX(parent, cb.GetId(), self._on_priority)

        return sizer

    def _create_project_upload(self, parent):
        import wx

        id = wx.NewId()
        cb = wx.CheckBox(
            parent,
            id,
            "Include Workspace Files (will increase email size)        ",
            wx.Point(65, 80),
            wx.Size(-1, 20),
            wx.NO_BORDER,
        )
        wx.EVT_CHECKBOX(parent, id, self._on_project)

        return cb

    ## UI Listeners ###########################################################

    def _on_subject(self, event):
        self.subject = event.GetEventObject().GetValue()

    def _on_to(self, event):
        self.to_address = event.GetEventObject().GetValue()

    def _on_cc(self, event):
        self.cc_address = event.GetEventObject().GetValue()

    def _on_from(self, event):
        self.from_address = event.GetEventObject().GetValue()

    def _on_smtp_server(self, event):
        self.smtp_server = event.GetEventObject().GetValue()

    def _on_priority(self, event):
        self.priority = event.GetEventObject().GetStringSelection()

    def _on_comments(self, event):
        self.comments = event.GetEventObject().GetValue()

    def _on_project(self, event):
        self.include_userdata = event.Checked()
        cb = event.GetEventObject()

        if event.Checked():
            cb.SetLabel(
                "Include Workspace Files (approx. %.2f MBytes)"
                % self._compute_project_size()
            )
        else:
            cb.SetLabel("Include Workspace Files (will increase email size)")

    def _on_send(self, event):

        # Disable the Send button while we go through the possibly
        # time-consuming email-sending process.
        button = event.GetEventObject()
        button.Enable(0)

        fromaddr, toaddrs, ccaddrs = self._create_email_addresses()
        message = self._create_email(fromaddr, toaddrs, ccaddrs)

        self.service.send_bug_report(
            self.smtp_server, fromaddr, toaddrs, ccaddrs, message
        )

        # save the user's preferences
        self.service.preferences.smtp_server = self.smtp_server
        self.service.preferences.to_address = self.to_address
        self.service.preferences.from_address = self.from_address

        # finally we close the dialog
        self._wx_on_ok(event)

    ## Private ################################################################

    def _create_email_addresses(self):
        # utility function map addresses from ui into the standard format
        # FIXME: We should use standard To: header parsing instead of this ad
        # hoc whitespace-only approach.
        fromaddr = self.from_address
        if "" == fromaddr.strip():
            fromaddr = "anonymous"
        toaddrs = self.to_address.split()
        ccaddrs = self.cc_address.split()

        return fromaddr, toaddrs, ccaddrs

    def _compute_project_size(self):
        # determine size of email in MBytes
        fromaddr, toaddrs, ccaddrs = self._create_email_addresses()
        message = self._create_email(fromaddr, toaddrs, ccaddrs)
        return len(message.as_string()) / (2.0 ** 20)

    def _create_email(self, fromaddr, toaddrs, ccaddrs):
        return self.service.create_email_message(
            fromaddr,
            toaddrs,
            ccaddrs,
            self.subject,
            self.priority,
            self.include_userdata,
            self.msg,
            self.comments,
        )

    def _to_address_default(self):
        return self.service.preferences.to_address

    def _from_address_default(self):
        return self.service.preferences.from_address

    def _smtp_server_default(self):
        return self.service.preferences.smtp_server
Exemple #20
0
class Slider(Component):
    """ A horizontal or vertical slider bar """

    #------------------------------------------------------------------------
    # Model traits
    #------------------------------------------------------------------------

    min = Float()

    max = Float()

    value = Float()

    # The number of ticks to show on the slider.
    num_ticks = Int(4)

    #------------------------------------------------------------------------
    # Bar and endcap appearance
    #------------------------------------------------------------------------

    # Whether this is a horizontal or vertical slider
    orientation = Enum("h", "v")

    # The thickness, in pixels, of the lines used to render the ticks,
    # endcaps, and main slider bar.
    bar_width = Int(4)

    bar_color = ColorTrait("black")

    # Whether or not to render endcaps on the slider bar
    endcaps = Bool(True)

    # The extent of the endcaps, in pixels.  This is a read-only property,
    # since the endcap size can be set as either a fixed number of pixels or
    # a percentage of the widget's size in the transverse direction.
    endcap_size = Property

    # The extent of the tickmarks, in pixels.  This is a read-only property,
    # since the endcap size can be set as either a fixed number of pixels or
    # a percentage of the widget's size in the transverse direction.
    tick_size = Property

    #------------------------------------------------------------------------
    # Slider appearance
    #------------------------------------------------------------------------

    # The kind of marker to use for the slider.
    slider = SliderMarkerTrait("rect")

    # If the slider marker is "rect", this is the thickness of the slider,
    # i.e. its extent in the dimension parallel to the long axis of the widget.
    # For other slider markers, this has no effect.
    slider_thickness = Int(9)

    # The size of the slider, in pixels.  This is a read-only property, since
    # the slider size can be set as either a fixed number of pixels or a
    # percentage of the widget's size in the transverse direction.
    slider_size = Property

    # For slider markers with a filled area, this is the color of the filled
    # area.  For slider markers that are just lines/strokes (e.g. cross, plus),
    # this is the color of the stroke.
    slider_color = ColorTrait("red")

    # For slider markers with a filled area, this is the color of the outline
    # border drawn around the filled area.  For slider markers that have just
    # lines/strokes, this has no effect.
    slider_border = ColorTrait("none")

    # For slider markers with a filled area, this is the width, in pixels,
    # of the outline around the area.  For slider markers that are just lines/
    # strokes, this is the thickness of the stroke.
    slider_outline_width = Int(1)

    # The kiva.CompiledPath representing the custom path to render for the
    # slider, if the **slider** trait is set to "custom".
    custom_slider = Any()

    #------------------------------------------------------------------------
    # Interaction traits
    #------------------------------------------------------------------------

    # Can this slider be interacted with, or is it just a display
    interactive = Bool(True)

    mouse_button = Enum("left", "right")

    event_state = Enum("normal", "dragging")

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # Returns the coordinate index (0 or 1) corresponding to our orientation.
    # Used internally; read-only property.
    axis_ndx = Property()

    _slider_size_mode = Enum("fixed", "percent")
    _slider_percent = Float(0.0)
    _cached_slider_size = Int(10)

    _endcap_size_mode = Enum("fixed", "percent")
    _endcap_percent = Float(0.0)
    _cached_endcap_size = Int(20)

    _tick_size_mode = Enum("fixed", "percent")
    _tick_size_percent = Float(0.0)
    _cached_tick_size = Int(20)

    # A tuple of (dx, dy) of the difference between the mouse position and
    # center of the slider.
    _offset = Any((0, 0))

    def set_range(self, min, max):
        self.min = min
        self.max = max

    def map_screen(self, val):
        """ Returns an (x,y) coordinate corresponding to the location of
        **val** on the slider.
        """
        # Some local variables to handle orientation dependence
        axis_ndx = self.axis_ndx
        other_ndx = 1 - axis_ndx
        screen_low = self.position[axis_ndx]
        screen_high = screen_low + self.bounds[axis_ndx]

        # The return coordinate.  The return value along the non-primary
        # axis will be the same in all cases.
        coord = [0, 0]
        coord[
            other_ndx] = self.position[other_ndx] + self.bounds[other_ndx] / 2

        # Handle exceptional/boundary cases
        if val <= self.min:
            coord[axis_ndx] = screen_low
            return coord
        elif val >= self.max:
            coord[axis_ndx] = screen_high
            return coord
        elif self.min == self.max:
            coord[axis_ndx] = (screen_low + screen_high) / 2
            return coord

        # Handle normal cases
        coord[axis_ndx] = (val - self.min) / (
            self.max - self.min) * self.bounds[axis_ndx] + screen_low
        return coord

    def map_data(self, x, y, clip=True):
        """ Returns a value between min and max that corresponds to the given
        x and y values.

        Parameters
        ==========
        x, y : Float
            The screen coordinates to map
        clip : Bool (default=True)
            Whether points outside the range should be clipped to the max
            or min value of the slider (depending on which it's closer to)

        Returns
        =======
        value : Float
        """
        # Some local variables to handle orientation dependence
        axis_ndx = self.axis_ndx
        other_ndx = 1 - axis_ndx
        screen_low = self.position[axis_ndx]
        screen_high = screen_low + self.bounds[axis_ndx]
        if self.orientation == "h":
            coord = x
        else:
            coord = y

        # Handle exceptional/boundary cases
        if coord >= screen_high:
            return self.max
        elif coord <= screen_low:
            return self.min
        elif screen_high == screen_low:
            return (self.max + self.min) / 2

        # Handle normal cases
        return (coord - screen_low) /self.bounds[axis_ndx] * \
                    (self.max - self.min) + self.min

    def set_slider_pixels(self, pixels):
        """ Sets the width of the slider to be a fixed number of pixels

        Parameters
        ==========
        pixels : int
            The number of pixels wide that the slider should be
        """
        self._slider_size_mode = "fixed"
        self._cached_slider_size = pixels

    def set_slider_percent(self, percent):
        """ Sets the width of the slider to be a percentage of the width
        of the slider widget.

        Parameters
        ==========
        percent : float
            The percentage, between 0.0 and 1.0
        """
        self._slider_size_mode = "percent"
        self._slider_percent = percent
        self._update_sizes()

    def set_endcap_pixels(self, pixels):
        """ Sets the width of the endcap to be a fixed number of pixels

        Parameters
        ==========
        pixels : int
            The number of pixels wide that the endcap should be
        """
        self._endcap_size_mode = "fixed"
        self._cached_endcap_size = pixels

    def set_endcap_percent(self, percent):
        """ Sets the width of the endcap to be a percentage of the width
        of the endcap widget.

        Parameters
        ==========
        percent : float
            The percentage, between 0.0 and 1.0
        """
        self._endcap_size_mode = "percent"
        self._endcap_percent = percent
        self._update_sizes()

    def set_tick_pixels(self, pixels):
        """ Sets the width of the tick marks to be a fixed number of pixels

        Parameters
        ==========
        pixels : int
            The number of pixels wide that the endcap should be
        """
        self._tick_size_mode = "fixed"
        self._cached_tick_size = pixels

    def set_tick_percent(self, percent):
        """ Sets the width of the tick marks to be a percentage of the width
        of the endcap widget.

        Parameters
        ==========
        percent : float
            The percentage, between 0.0 and 1.0
        """
        self._tick_size_mode = "percent"
        self._tick_percent = percent
        self._update_sizes()

    #------------------------------------------------------------------------
    # Rendering methods
    #------------------------------------------------------------------------

    def _draw_mainlayer(self, gc, view_bounds=None, mode="normal"):
        start = [0, 0]
        end = [0, 0]
        axis_ndx = self.axis_ndx
        other_ndx = 1 - axis_ndx

        bar_x = self.x + self.width / 2
        bar_y = self.y + self.height / 2

        # Draw the bar and endcaps
        gc.set_stroke_color(self.bar_color_)
        gc.set_line_width(self.bar_width)
        if self.orientation == "h":
            gc.move_to(self.x, bar_y)
            gc.line_to(self.x2, bar_y)
            gc.stroke_path()
            if self.endcaps:
                start_y = bar_y - self._cached_endcap_size / 2
                end_y = bar_y + self._cached_endcap_size / 2
                gc.move_to(self.x, start_y)
                gc.line_to(self.x, end_y)
                gc.move_to(self.x2, start_y)
                gc.line_to(self.x2, end_y)
            if self.num_ticks > 0:
                x_pts = linspace(self.x, self.x2,
                                 self.num_ticks + 2).astype(int)
                starts = zeros((len(x_pts), 2), dtype=int)
                starts[:, 0] = x_pts
                starts[:, 1] = bar_y - self._cached_tick_size / 2
                ends = starts.copy()
                ends[:, 1] = bar_y + self._cached_tick_size / 2
                gc.line_set(starts, ends)
        else:
            gc.move_to(bar_x, self.y)
            gc.line_to(bar_x, self.y2)
            if self.endcaps:
                start_x = bar_x - self._cached_endcap_size / 2
                end_x = bar_x + self._cached_endcap_size / 2
                gc.move_to(start_x, self.y)
                gc.line_to(end_x, self.y)
                gc.move_to(start_x, self.y2)
                gc.line_to(end_x, self.y2)
            if self.num_ticks > 0:
                y_pts = linspace(self.y, self.y2,
                                 self.num_ticks + 2).astype(int)
                starts = zeros((len(y_pts), 2), dtype=int)
                starts[:, 1] = y_pts
                starts[:, 0] = bar_x - self._cached_tick_size / 2
                ends = starts.copy()
                ends[:, 0] = bar_x + self._cached_tick_size / 2
                gc.line_set(starts, ends)
        gc.stroke_path()

        # Draw the slider
        pt = self.map_screen(self.value)
        if self.slider == "rect":
            gc.set_fill_color(self.slider_color_)
            gc.set_stroke_color(self.slider_border_)
            gc.set_line_width(self.slider_outline_width)
            rect = self._get_rect_slider_bounds()
            gc.rect(*rect)
            gc.draw_path()
        else:
            self._render_marker(gc, pt, self._cached_slider_size,
                                self.slider_(), self.custom_slider)

    def _get_rect_slider_bounds(self):
        """ Returns the (x, y, w, h) bounds of the rectangle representing the slider.
        Used for rendering and hit detection.
        """
        bar_x = self.x + self.width / 2
        bar_y = self.y + self.height / 2
        pt = self.map_screen(self.value)
        if self.orientation == "h":
            slider_height = self._cached_slider_size
            return (pt[0] - self.slider_thickness, bar_y - slider_height / 2,
                    self.slider_thickness, slider_height)
        else:
            slider_width = self._cached_slider_size
            return (bar_x - slider_width / 2, pt[1] - self.slider_thickness,
                    slider_width, self.slider_thickness)

    def _render_marker(self, gc, point, size, marker, custom_path):
        with gc:
            gc.begin_path()
            if marker.draw_mode == STROKE:
                gc.set_stroke_color(self.slider_color_)
                gc.set_line_width(self.slider_thickness)
            else:
                gc.set_fill_color(self.slider_color_)
                gc.set_stroke_color(self.slider_border_)
                gc.set_line_width(self.slider_outline_width)

            if hasattr(gc, "draw_marker_at_points") and \
                    (marker.__class__ != CustomMarker) and \
                    (gc.draw_marker_at_points([point], size, marker.kiva_marker) != 0):
                pass
            elif hasattr(gc, "draw_path_at_points"):
                if marker.__class__ != CustomMarker:
                    path = gc.get_empty_path()
                    marker.add_to_path(path, size)
                    mode = marker.draw_mode
                else:
                    path = custom_path
                    mode = STROKE
                if not marker.antialias:
                    gc.set_antialias(False)
                gc.draw_path_at_points([point], path, mode)
            else:
                if not marker.antialias:
                    gc.set_antialias(False)
                if marker.__class__ != CustomMarker:
                    gc.translate_ctm(*point)
                    # Kiva GCs have a path-drawing interface
                    marker.add_to_path(gc, size)
                    gc.draw_path(marker.draw_mode)
                else:
                    path = custom_path
                    gc.translate_ctm(*point)
                    gc.add_path(path)
                    gc.draw_path(STROKE)

    #------------------------------------------------------------------------
    # Interaction event handlers
    #------------------------------------------------------------------------

    def normal_left_down(self, event):
        if self.mouse_button == "left":
            return self._mouse_pressed(event)

    def dragging_left_up(self, event):
        if self.mouse_button == "left":
            return self._mouse_released(event)

    def normal_right_down(self, event):
        if self.mouse_button == "right":
            return self._mouse_pressed(event)

    def dragging_right_up(self, event):
        if self.mouse_button == "right":
            return self._mouse_released(event)

    def dragging_mouse_move(self, event):
        dx, dy = self._offset
        self.value = self.map_data(event.x - dx, event.y - dy)
        event.handled = True
        self.request_redraw()

    def dragging_mouse_leave(self, event):
        self.event_state = "normal"

    def _mouse_pressed(self, event):
        # Determine the slider bounds so we can hit test it
        pt = self.map_screen(self.value)
        if self.slider == "rect":
            x, y, w, h = self._get_rect_slider_bounds()
            x2 = x + w
            y2 = y + h
        else:
            x, y = pt
            size = self._cached_slider_size
            x -= size / 2
            y -= size / 2
            x2 = x + size
            y2 = y + size

        # Hit test both the slider and against the bar.  If the user has
        # clicked on the bar but outside of the slider, we set the _offset
        # and call dragging_mouse_move() to teleport the slider to the
        # mouse click position.
        if self.orientation == "v" and (x <= event.x <= x2):
            if not (y <= event.y <= y2):
                self._offset = (event.x - pt[0], 0)
                self.dragging_mouse_move(event)
            else:
                self._offset = (event.x - pt[0], event.y - pt[1])
        elif self.orientation == "h" and (y <= event.y <= y2):
            if not (x <= event.x <= x2):
                self._offset = (0, event.y - pt[1])
                self.dragging_mouse_move(event)
            else:
                self._offset = (event.x - pt[0], event.y - pt[1])
        else:
            # The mouse click missed the bar and the slider.
            return

        event.handled = True
        self.event_state = "dragging"
        return

    def _mouse_released(self, event):
        self.event_state = "normal"
        event.handled = True

    #------------------------------------------------------------------------
    # Private trait event handlers and property getters/setters
    #------------------------------------------------------------------------

    def _get_axis_ndx(self):
        if self.orientation == "h":
            return 0
        else:
            return 1

    def _get_slider_size(self):
        return self._cached_slider_size

    def _get_endcap_size(self):
        return self._cached_endcap_size

    def _get_tick_size(self):
        return self._cached_tick_size

    @on_trait_change("bounds,bounds_items")
    def _update_sizes(self):
        if self._slider_size_mode == "percent":
            if self.orientation == "h":
                self._cached_slider_size = int(self.height *
                                               self._slider_percent)
            else:
                self._cached_slider_size = int(self.width *
                                               self._slider_percent)
        if self._endcap_size_mode == "percent":
            if self.orientation == "h":
                self._cached_endcap_size = int(self.height *
                                               self._endcap_percent)
            else:
                self._cached_endcap_size = int(self.width *
                                               self._endcap_percent)

        return
Exemple #21
0
class Comparator(HasTraits):
    """ The main application.
    """

    #### Configuration traits ##################################################

    # The root directory of the test suite.
    suitedir = Str()

    # Mapping of SVG basenames to their reference PNGs. Use None if there is no
    # reference PNG.
    svg_png = Dict()

    # The list of SVG file names.
    svg_files = List()

    # The name of the default PNG file to display when no reference PNG exists.
    default_png = Str(os.path.join(this_dir, 'images/default.png'))

    #### State traits ##########################################################

    # The currently selected SVG file.
    current_file = Str()
    abs_current_file = Property(depends_on=['current_file'])

    # The current XML ElementTree root Element and its XMLTree view model.
    current_xml = Any()
    current_xml_view = Any()

    # The profilers.
    profile_this = Instance(ProfileThis, args=())

    #### GUI traits ############################################################

    # The text showing the current mouse coordinates over any of the components.
    mouse_coords = Property(Str, depends_on=['ch_controller.svg_coords'])

    # Move forward and backward through the list of SVG files.
    move_forward = Button('>>')
    move_backward = Button('<<')

    # The description of the test.
    description = HTML()

    document = Instance(document.SVGDocument)

    # The components to view.
    kiva_component = ComponentTrait(klass=SVGComponent)
    ref_component = ComponentTrait(klass=ImageComponent, args=())
    ch_controller = Instance(MultiController)

    # The profiler views.
    parsing_sike = Instance(Sike, args=())
    drawing_sike = Instance(Sike, args=())
    wx_doc_sike = Instance(Sike, args=())
    kiva_doc_sike = Instance(Sike, args=())

    traits_view = tui.View(
        tui.Tabbed(
            tui.VGroup(
                tui.HGroup(
                    tui.Item('current_file',
                             editor=tui.EnumEditor(name='svg_files'),
                             style='simple',
                             width=1.0,
                             show_label=False),
                    tui.Item(
                        'move_backward',
                        show_label=False,
                        enabled_when="svg_files.index(current_file) != 0"),
                    tui.Item(
                        'move_forward',
                        show_label=False,
                        enabled_when=
                        "svg_files.index(current_file) != len(svg_files)-1"),
                ),
                tui.VSplit(
                    tui.HSplit(
                        tui.Item('description',
                                 label='Description',
                                 show_label=False),
                        tui.Item('current_xml_view',
                                 editor=xml_tree_editor,
                                 show_label=False),
                    ),
                    tui.HSplit(
                        tui.Item('document',
                                 editor=SVGEditor(),
                                 show_label=False),
                        tui.Item('kiva_component', show_label=False),
                        tui.Item('ref_component', show_label=False),
                        # TODO: tui.Item('agg_component', show_label=False),
                    ),
                ),
                label='SVG',
            ),
            tui.Item('parsing_sike',
                     style='custom',
                     show_label=False,
                     label='Parsing Profile'),
            tui.Item('drawing_sike',
                     style='custom',
                     show_label=False,
                     label='Kiva Drawing Profile'),
            tui.Item('wx_doc_sike',
                     style='custom',
                     show_label=False,
                     label='Creating WX document'),
            tui.Item('kiva_doc_sike',
                     style='custom',
                     show_label=False,
                     label='Creating WX document'),
        ),
        width=1280,
        height=768,
        resizable=True,
        statusbar='mouse_coords',
        title='SVG Comparator',
    )

    def __init__(self, **traits):
        super(Comparator, self).__init__(**traits)
        kiva_ch = activate_tool(self.kiva_component,
                                Crosshair(self.kiva_component))
        ref_ch = activate_tool(self.ref_component,
                               Crosshair(self.ref_component))
        self.ch_controller = MultiController(kiva_ch, ref_ch)

    @classmethod
    def fromsuitedir(cls, dirname, **traits):
        """ Find all SVG files and their related reference PNG files under
        a directory.

        This assumes that the SVGs are located under <dirname>/svg/ and the
        related PNGs under <dirname>/png/ and that there are no subdirectories.
        """
        dirname = os.path.abspath(dirname)
        svgs = glob.glob(os.path.join(dirname, 'svg', '*.svg'))
        pngdir = os.path.join(dirname, 'png')
        d = {}
        for svg in svgs:
            png = None
            base = os.path.splitext(os.path.basename(svg))[0]
            for prefix in ('full-', 'basic-', 'tiny-', ''):
                fn = os.path.join(pngdir, prefix + base + '.png')
                if os.path.exists(fn):
                    png = os.path.basename(fn)
                    break
            d[os.path.basename(svg)] = png
        svgs = sorted(d)
        x = cls(suitedir=dirname, svg_png=d, svg_files=svgs, **traits)
        x.current_file = svgs[0]
        return x

    def display_reference_png(self, filename):
        """ Read the image file and shove its data into the display component.
        """
        img = Image.open(filename)
        arr = np.array(img)
        self.ref_component.image = arr

    def display_test_description(self):
        """ Extract the test description for display.
        """
        html = ET.Element('html')

        title = self.current_xml.find('.//{http://www.w3.org/2000/svg}title')
        if title is not None:
            title_text = title.text
        else:
            title_text = os.path.splitext(self.current_file)[0]
        p = ET.SubElement(html, 'p')
        b = ET.SubElement(p, 'b')
        b.text = 'Title: '
        b.tail = title_text

        desc_text = None
        version_text = None
        desc = self.current_xml.find('.//{http://www.w3.org/2000/svg}desc')
        if desc is not None:
            desc_text = desc.text
        else:
            testcase = self.current_xml.find(
                './/{http://www.w3.org/2000/02/svg/testsuite/description/}SVGTestCase'
            )
            if testcase is not None:
                desc_text = testcase.get('desc', None)
                version_text = testcase.get('version', None)
        if desc_text is not None:
            p = ET.SubElement(html, 'p')
            b = ET.SubElement(p, 'b')
            b.text = 'Description: '
            b.tail = normalize_text(desc_text)

        if version_text is None:
            script = self.current_xml.find(
                './/{http://www.w3.org/2000/02/svg/testsuite/description/}OperatorScript'
            )
            if script is not None:
                version_text = script.get('version', None)
        if version_text is not None:
            p = ET.SubElement(html, 'p')
            b = ET.SubElement(p, 'b')
            b.text = 'Version: '
            b.tail = version_text

        paras = self.current_xml.findall(
            './/{http://www.w3.org/2000/02/svg/testsuite/description/}Paragraph'
        )
        if len(paras) > 0:
            div = ET.SubElement(html, 'div')
            for para in paras:
                p = ET.SubElement(div, 'p')
                p.text = normalize_text(para.text)
                # Copy over any children elements like <a>.
                p[:] = para[:]

        tree = ET.ElementTree(html)
        f = StringIO()
        tree.write(f)
        text = f.getvalue()
        self.description = text

    def locate_file(self, name, kind):
        """ Find the location of the given file in the suite.

        Parameters
        ----------
        name : str
            Path of the file relative to the suitedir.
        kind : either 'svg' or 'png'
            The kind of file.

        Returns
        -------
        path : str
            The full path to the file.
        """
        return os.path.join(self.suitedir, kind, name)

    def _kiva_component_default(self):
        return SVGComponent(profile_this=self.profile_this)

    def _move_backward_fired(self):
        idx = self.svg_files.index(self.current_file)
        idx = max(idx - 1, 0)
        self.current_file = self.svg_files[idx]

    def _move_forward_fired(self):
        idx = self.svg_files.index(self.current_file)
        idx = min(idx + 1, len(self.svg_files) - 1)
        self.current_file = self.svg_files[idx]

    def _get_abs_current_file(self):
        return self.locate_file(self.current_file, 'svg')

    def _current_file_changed(self, new):
        # Reset the warnings filters. While it's good to only get 1 warning per
        # file, we want to get the same warning again if a new file issues it.
        warnings.resetwarnings()

        self.profile_this.start('Parsing')
        self.current_xml = ET.parse(self.abs_current_file).getroot()
        self.current_xml_view = xml_to_tree(self.current_xml)
        resources = document.ResourceGetter.fromfilename(self.abs_current_file)
        self.profile_this.stop()
        try:
            self.profile_this.start('Creating WX document')
            self.document = document.SVGDocument(self.current_xml,
                                                 resources=resources,
                                                 renderer=WxRenderer)
        except:
            logger.exception('Error parsing document %s', new)
            self.document = None

        self.profile_this.stop()

        try:
            self.profile_this.start('Creating Kiva document')
            self.kiva_component.document = document.SVGDocument(
                self.current_xml, resources=resources, renderer=KivaRenderer)
        except Exception as e:
            logger.exception('Error parsing document %s', new)
            self.kiva_component.document

        self.profile_this.stop()

        png_file = self.svg_png.get(new, None)
        if png_file is None:
            png_file = self.default_png
        else:
            png_file = self.locate_file(png_file, 'png')
        self.display_test_description()
        self.display_reference_png(png_file)

    def _get_mouse_coords(self):
        if self.ch_controller is None:
            return ''
        else:
            return '%1.3g %1.3g' % self.ch_controller.svg_coords

    @on_trait_change('profile_this:profile_ended')
    def _update_profiling(self, new):
        if new is not None:
            name, p = new
            stats = pstats.Stats(p)
            if name == 'Parsing':
                self.parsing_sike.stats = stats
            elif name == 'Drawing':
                self.drawing_sike.stats = stats
            elif name == 'Creating WX document':
                self.wx_doc_sike.stats = stats
            elif name == 'Creating Kiva document':
                self.kiva_doc_sike.stats = stats
Exemple #22
0
class QuadSelection(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a quadrant gate.
    
    Attributes
    ----------
    op : Instance(Range2DOp)
        The instance of Range2DOp that we're viewing / editing
        
    huefacet : Str
        The conditioning variable to plot multiple colors
        
    subset : Str
        The string passed to `Experiment.query()` to subset the data before
        plotting
        
    interactive : Bool
        is this view interactive?  Ie, can the user set the threshold with a 
        mouse click?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but
    they must both be unset!
        
    Examples
    --------
    
    In an IPython notebook with `%matplotlib notebook`
    
    >>> q = flow.QuadOp(name = "Quad",
    ...                 xchannel = "V2-A",
    ...                 ychannel = "Y2-A"))
    >>> qv = q.default_view()
    >>> qv.interactive = True
    >>> qv.plot(ex2) 
    """

    id = Constant('edu.mit.synbio.cytoflow.views.quad')
    friendly_id = Constant("Quadrant Selection")

    op = Instance(IOperation)
    name = DelegatesTo('op')
    xchannel = DelegatesTo('op')
    ychannel = DelegatesTo('op')
    interactive = Bool(False, transient=True)

    # internal state.
    _ax = Any(transient=True)
    _hline = Instance(Line2D, transient=True)
    _vline = Instance(Line2D, transient=True)
    _cursor = Instance(Cursor, transient=True)

    def plot(self, experiment, **kwargs):
        """Plot the underlying scatterplot and then plot the selection on top of it."""

        if not experiment:
            raise util.CytoflowOpError("No experiment specified")

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if self.xfacet:
            raise util.CytoflowViewError(
                "RangeSelection.xfacet must be empty or `Undefined`")

        if self.yfacet:
            raise util.CytoflowViewError(
                "RangeSelection.yfacet must be empty or `Undefined`")

        super(QuadSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_lines()
        self._interactive()

    @on_trait_change('op.xthreshold, op.ythreshold', post_init=True)
    def _draw_lines(self):
        if not self._ax:
            return

        if self._hline and self._hline in self._ax.lines:
            self._hline.remove()

        if self._vline and self._vline in self._ax.lines:
            self._vline.remove()

        if self.op.xthreshold and self.op.ythreshold:
            self._hline = plt.axhline(self.op.ythreshold,
                                      linewidth=3,
                                      color='blue')
            self._vline = plt.axvline(self.op.xthreshold,
                                      linewidth=3,
                                      color='blue')

            plt.draw_if_interactive()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax,
                                  horizOn=True,
                                  vertOn=True,
                                  color='blue')
            self._cursor.connect_event('button_press_event', self._onclick)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None

    def _onclick(self, event):
        """Update the threshold location"""
        self.op.xthreshold = event.xdata
        self.op.ythreshold = event.ydata
Exemple #23
0
class VolumeFactory(PipeFactory):
    """ Applies the Volume mayavi module to the given VTK data
        source (Mayavi source, or VTK dataset).

        **Note**

        The range of the colormap can be changed simply using the
        vmin/vmax parameters (see below). For more complex modifications of
        the colormap, here is some pseudo code to change the ctf (color
        transfer function), or the otf (opacity transfer function)::

            vol = mlab.pipeline.volume(src)

            # Changing the ctf:
            from tvtk.util.ctf import ColorTransferFunction
            ctf = ColorTransferFunction()
            ctf.add_rgb_point(value, r, g, b)  # r, g, and b are float
                                               # between 0 and 1
            ctf.add_hsv_point(value, h, s, v)
            # ...
            vol._volume_property.set_color(ctf)
            vol._ctf = ctf
            vol.update_ctf = True

            # Changing the otf:
            from tvtk.util.ctf import PiecewiseFunction
            otf = PiecewiseFunction()
            otf.add_point(value, opacity)
            vol._otf = otf
            vol._volume_property.set_scalar_opacity(otf)

        Also, it might be useful to change the range of the ctf::

            ctf.range = [0, 1]
    """

    color = Trait(
        None,
        None,
        TraitTuple(Range(0., 1.), Range(0., 1.), Range(0., 1.)),
        help="""the color of the vtk object. Overides the colormap,
                        if any, when specified. This is specified as a
                        triplet of float ranging from 0 to 1, eg (1, 1,
                        1) for white.""",
    )

    vmin = Trait(None,
                 None,
                 CFloat,
                 help="""vmin is used to scale the transparency
                            gradient. If None, the min of the data will be
                            used""")

    vmax = Trait(None,
                 None,
                 CFloat,
                 help="""vmax is used to scale the transparency
                            gradient. If None, the max of the data will be
                            used""")

    _target = Instance(modules.Volume, ())

    __last_vrange = Any(None)

    ######################################################################
    # Non-public interface.
    ######################################################################
    def _color_changed(self):
        if not self.color:
            return
        range_min, range_max = self._target.current_range
        from tvtk.util.ctf import ColorTransferFunction
        ctf = ColorTransferFunction()
        try:
            ctf.range = (range_min, range_max)
        except Exception:
            # VTK versions < 5.2 don't seem to need this.
            pass

        r, g, b = self.color
        ctf.add_rgb_point(range_min, r, g, b)
        ctf.add_rgb_point(range_max, r, g, b)

        self._target._ctf = ctf
        self._target._volume_property.set_color(ctf)
        self._target.update_ctf = True

    def _vmin_changed(self):
        vmin = self.vmin
        vmax = self.vmax
        range_min, range_max = self._target.current_range
        if vmin is None:
            vmin = range_min
        if vmax is None:
            vmax = range_max

        # Change the opacity function
        from tvtk.util.ctf import PiecewiseFunction, save_ctfs

        otf = PiecewiseFunction()
        if range_min < vmin:
            otf.add_point(range_min, 0.)
        if range_max > vmax:
            otf.add_point(range_max, 0.2)
        otf.add_point(vmin, 0.)
        otf.add_point(vmax, 0.2)
        self._target._otf = otf
        self._target._volume_property.set_scalar_opacity(otf)
        if self.color is None and \
           ((self.vmin is not None) or (self.vmax is not None)):
            # FIXME: We don't use 'rescale_ctfs' because it screws up the
            # nodes, this is because, the values are actually scaled between
            # the specified vmin/vmax and NOT the full range of values
            # specified in the CTF or in the volume object.
            if self.__last_vrange:
                last_min, last_max = self.__last_vrange
            else:
                last_min, last_max = range_min, range_max

            def _rescale_value(x):
                nx = (x - last_min) / (last_max - last_min)
                return vmin + nx * (vmax - vmin)

            # The range of the existing ctf can vary.
            scale_min, scale_max = self._target._ctf.range

            def _rescale_node(x):
                nx = (x - scale_min) / (scale_max - scale_min)
                return range_min + nx * (range_max - range_min)

            if hasattr(self._target._ctf, 'nodes'):
                rgb = list()
                for value in self._target._ctf.nodes:
                    r, g, b = \
                            self._target._ctf.get_color(value)
                    rgb.append((_rescale_node(value), r, g, b))
            else:
                rgb = save_ctfs(self._target.volume_property)['rgb']

            from tvtk.util.ctf import ColorTransferFunction
            ctf = ColorTransferFunction()
            try:
                ctf.range = (range_min, range_max)
            except Exception:
                # VTK versions < 5.2 don't seem to need this.
                pass
            rgb.sort()
            v = rgb[0]
            ctf.add_rgb_point(range_min, v[1], v[2], v[3])
            for v in rgb:
                ctf.add_rgb_point(_rescale_value(v[0]), v[1], v[2], v[3])
            ctf.add_rgb_point(range_max, v[1], v[2], v[3])

            self._target._ctf = ctf
            self._target._volume_property.set_color(ctf)
            self.__last_vrange = vmin, vmax

        self._target.update_ctf = True

    # This is not necessary: the job is already done by _vmin_changed
    _vmax_changed = _vmin_changed
Exemple #24
0
class _ListStrEditor(Editor):
    """ Traits UI editor for editing lists of strings.
    """

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

    # The title of the editor:
    title = Str

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

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

    # The most recently actived item and its index:
    activated = Any
    activated_index = Int

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

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

    # Index of item to select after rebuilding editor list:
    index = Any

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

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

    # Dictionaly mapping image names to wx.ImageList indices:
    images = Any({})

    # Dictionary mapping ImageResource objects to wx.ImageList indices:
    image_resources = Any({})

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

    # The current search string:
    search = 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.
        """
        factory = self.factory

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

        # Determine the style to use for the list control:
        style = wx.LC_REPORT | wx.LC_VIRTUAL

        if factory.editable:
            style |= wx.LC_EDIT_LABELS

        if factory.horizontal_lines:
            style |= wx.LC_HRULES

        if not factory.multi_select:
            style |= wx.LC_SINGLE_SEL

        if (factory.title == '') and (factory.title_name == ''):
            style |= wx.LC_NO_HEADER

        # Create the list control and link it back to us:
        self.control = control = wxListCtrl(parent, -1, style=style)
        control._editor = self

        # Create the list control column:
        control.InsertColumn(0, '')

        # Set up the list control's event handlers:
        id = control.GetId()
        wx.EVT_LIST_BEGIN_DRAG(parent, id, self._begin_drag)
        wx.EVT_LIST_BEGIN_LABEL_EDIT(parent, id, self._begin_label_edit)
        wx.EVT_LIST_END_LABEL_EDIT(parent, id, self._end_label_edit)
        wx.EVT_LIST_ITEM_SELECTED(parent, id, self._item_selected)
        wx.EVT_LIST_ITEM_DESELECTED(parent, id, self._item_selected)
        wx.EVT_LIST_ITEM_RIGHT_CLICK(parent, id, self._right_clicked)
        wx.EVT_LIST_ITEM_ACTIVATED(parent, id, self._item_activated)
        wx.EVT_SIZE(control, self._size_modified)

        # Handle key events:
        wx.EVT_CHAR(control, self._key_pressed)

        # Handle mouse events:
        if 'edit' in factory.operations:
            wx.EVT_LEFT_DOWN(control, self._left_down)

        # Set up the drag and drop target:
        if PythonDropTarget is not None:
            control.SetDropTarget(PythonDropTarget(self))

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

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

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

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

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

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

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

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

    def dispose(self):
        """ Disposes of the contents of an editor.
        """
        disconnect(self.control, wx.EVT_LIST_BEGIN_DRAG,
                   wx.EVT_LIST_BEGIN_LABEL_EDIT, wx.EVT_LIST_END_LABEL_EDIT,
                   wx.EVT_LIST_ITEM_SELECTED, wx.EVT_LIST_ITEM_DESELECTED,
                   wx.EVT_LIST_ITEM_RIGHT_CLICK, wx.EVT_LIST_ITEM_ACTIVATED)

        disconnect_no_id(self.control, wx.EVT_SIZE, wx.EVT_CHAR,
                         wx.EVT_LEFT_DOWN)

        self.context_object.on_trait_change(self.update_editor,
                                            self.extended_name + '_items',
                                            remove=True)
        self.on_trait_change(self._refresh, 'adapter.+update', remove=True)

        super(_ListStrEditor, self).dispose()

    def update_editor(self):
        """ Updates the editor when the object trait changes externally to the
            editor.
        """
        control = self.control
        top = control.GetTopItem()
        pn = control.GetCountPerPage()
        n = self.adapter.len(self.object, self.name)
        if self.factory.auto_add:
            n += 1

        control.DeleteAllItems()
        control.SetItemCount(n)
        if control.GetItemCount() > 0:
            control.RefreshItems(0, control.GetItemCount() - 1)
        control.SetColumnWidth(0, control.GetClientSizeTuple()[0])

        edit, self.edit = self.edit, False
        index, self.index = self.index, None

        if index is not None:
            if index >= n:
                index -= 1
                if index < 0:
                    index = None

        if index is None:
            visible = top + pn - 2
            if visible >= 0 and visible < control.GetItemCount():
                control.EnsureVisible(visible)
            if self.factory.multi_select:
                for index in self.multi_selected_indices:
                    if 0 <= index < n:
                        control.SetItemState(index, wx.LIST_STATE_SELECTED,
                                             wx.LIST_STATE_SELECTED)
            else:
                if 0 <= self.selected_index < n:
                    control.SetItemState(self.selected_index,
                                         wx.LIST_STATE_SELECTED,
                                         wx.LIST_STATE_SELECTED)
            return

        if 0 <= (index - top) < pn:
            control.EnsureVisible(max(0, top + pn - 2))
        elif index < top:
            control.EnsureVisible(min(n, index + pn - 1))
        else:
            control.EnsureVisible(index)

        control.SetItemState(index,
                             wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED,
                             wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)

        if edit:
            control.EditLabel(index)

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

    def _get_item_count(self):
        return (self.control.GetItemCount() - self.factory.auto_add)

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

    def _title_changed(self, title):
        """ Handles the editor title being changed.
        """
        list_item = wx.ListItem()
        list_item.SetText(title)
        self.control.SetColumn(0, list_item)

    def _selected_changed(self, selected):
        """ Handles the editor's 'selected' trait being changed.
        """
        if not self._no_update:
            try:
                self.control.SetItemState(self.value.index(selected),
                                          wx.LIST_STATE_SELECTED,
                                          wx.LIST_STATE_SELECTED)
            except Exception:
                pass

    def _selected_index_changed(self, selected_index):
        """ Handles the editor's 'selected_index' trait being changed.
        """
        if not self._no_update:
            try:
                self.control.SetItemState(selected_index,
                                          wx.LIST_STATE_SELECTED,
                                          wx.LIST_STATE_SELECTED)
            except Exception:
                pass

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

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

    def _multi_selected_indices_changed(self, selected_indices):
        """ Handles the editor's 'multi_selected_indices' trait being changed.
        """
        if not self._no_update:
            control = self.control
            selected = self._get_selected()

            # Select any new items that aren't already selected:
            for index in selected_indices:
                if index in selected:
                    selected.remove(index)
                else:
                    try:
                        control.SetItemState(index, wx.LIST_STATE_SELECTED,
                                             wx.LIST_STATE_SELECTED)
                    except Exception:
                        pass

            # Unselect all remaining selected items that aren't selected now:
            for index in selected:
                control.SetItemState(index, 0, wx.LIST_STATE_SELECTED)

    def _multi_selected_indices_items_changed(self, event):
        """ Handles the editor's 'multi_selected_indices' trait being modified.
        """
        control = self.control

        # Remove all items that are no longer selected:
        for index in event.removed:
            control.SetItemState(index, 0, wx.LIST_STATE_SELECTED)

        # Select all newly added items:
        for index in event.added:
            control.SetItemState(index, wx.LIST_STATE_SELECTED,
                                 wx.LIST_STATE_SELECTED)

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

    def _begin_drag(self, event):
        """ Handles the user beginning a drag operation with the left mouse
            button.
        """
        if PythonDropSource is not None:
            adapter = self.adapter
            object, name = self.object, self.name
            index = event.GetIndex()
            selected = self._get_selected()
            drag_items = []

            # Collect all of the selected items to drag:
            for index in selected:
                drag = adapter.get_drag(object, name, index)
                if drag is None:
                    return

                drag_items.append(drag)

            # Save the drag item indices, so that we can later handle a
            # completed 'move' operation:
            self._drag_indices = selected

            try:
                # If only one item is being dragged, drag it as an item, not a
                # list:
                if len(drag_items) == 1:
                    drag_items = drag_items[0]

                # Perform the drag and drop operation:
                ds = PythonDropSource(self.control, drag_items)

                # If moves are allowed and the result was a drag move:
                if ((ds.result == wx.DragMove)
                        and (self._drag_local or self.factory.drag_move)):
                    # Then delete all of the original items (in reverse order
                    # from highest to lowest, so the indices don't need to be
                    # adjusted):
                    indices = self._drag_indices
                    indices.reverse()
                    for index in indices:
                        adapter.delete(object, name, index)
            finally:
                self._drag_indices = None
                self._drag_local = False

    def _begin_label_edit(self, event):
        """ Handles the user starting to edit an item label.
        """
        index = event.GetIndex()

        if ((not self._is_auto_add(index)) and
            (not self.adapter.get_can_edit(self.object, self.name, index))):
            event.Veto()

    def _end_label_edit(self, event):
        """ Handles the user finishing editing an item label.
        """
        self._set_text_current(event.GetIndex(), event.GetText())

    def _item_selected(self, event):
        """ Handles an item being selected.
        """
        self._no_update = True
        try:
            get_item = self.adapter.get_item
            object, name = self.object, self.name
            selected_indices = self._get_selected()
            if self.factory.multi_select:
                self.multi_selected_indices = selected_indices
                self.multi_selected = [
                    get_item(object, name, index) for index in selected_indices
                ]
            elif len(selected_indices) == 0:
                self.selected_index = -1
                self.selected = None
            else:
                self.selected_index = selected_indices[0]
                self.selected = get_item(object, name, selected_indices[0])
        finally:
            self._no_update = False

    def _item_activated(self, event):
        """ Handles an item being activated (double-clicked or enter pressed).
        """
        self.activated_index = event.GetIndex()
        if 'edit' in self.factory.operations:
            self._edit_current()
        else:
            self.activated = self.adapter.get_item(self.object, self.name,
                                                   self.activated_index)

    def _right_clicked(self, event):
        """ Handles an item being right clicked.
        """
        self.right_clicked_index = index = event.GetIndex()
        self.right_clicked = self.adapter.get_item(self.object, self.name,
                                                   index)

    def _key_pressed(self, event):
        key = event.GetKeyCode()
        control = event.ControlDown()

        if 32 <= key <= 126:
            self.search += chr(key).lower()
            self._search_for_string()
        elif key in (wx.WXK_HOME, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN):
            self.search = ''
            event.Skip()
        elif key == wx.WXK_END:
            self.search = ''
            self._append_new()
        elif (key == wx.WXK_UP) and control:
            self._search_for_string(-1)
        elif (key == wx.WXK_DOWN) and control:
            self._search_for_string(1)
        elif key in (wx.WXK_BACK, wx.WXK_DELETE):
            self._delete_current()
        elif key == wx.WXK_INSERT:
            self._insert_current()
        elif key == wx.WXK_LEFT:
            self._move_up_current()
        elif key == wx.WXK_RIGHT:
            self._move_down_current()
        elif key == wx.WXK_RETURN:
            self._edit_current()
        elif key == 3:  # Ctrl-C
            self._copy_current()
        elif key == 22:  # Ctrl-V
            self._paste_current()
        elif key == 24:  # Ctrl-X
            self._cut_current()
        else:
            event.Skip()

    def _size_modified(self, event):
        """ Handles the size of the list control being changed.
        """
        dx, dy = self.control.GetClientSizeTuple()
        self.control.SetColumnWidth(0, dx - 1)
        event.Skip()

    def _left_down(self, event):
        """ Handles the user pressing the left mouse button.
        """
        index, flags = self.control.HitTest(
            wx.Point(event.GetX(), event.GetY()))
        selected = self._get_selected()
        if (len(selected) == 1) and (index == selected[0]):
            self._edit_current()
        else:
            event.Skip()

    #-- Drag and Drop Event Handlers -------------------------------------------

    def wx_dropped_on(self, x, y, data, drag_result):
        """ Handles a Python object being dropped on the list control.
        """
        index, flags = self.control.HitTest(wx.Point(x, y))

        # If the user dropped it on an empty list, set the target as past the
        # end of the list:
        if ((index == -1) and ((flags & wx.LIST_HITTEST_NOWHERE) != 0)
                and (self.control.GetItemCount() == 0)):
            index = 0

        # If we have a valid drop target index, proceed:
        if index != -1:
            if not isinstance(data, list):
                # Handle the case of just a single item being dropped:
                self._wx_dropped_on(index, data)
            else:
                # Handles the case of a list of items being dropped, being
                # careful to preserve the original order of the source items if
                # possible:
                data.reverse()
                for item in data:
                    self._wx_dropped_on(index, item)

            # If this was an inter-list drag, mark it as 'local':
            if self._drag_indices is not None:
                self._drag_local = True

            # Return a successful drop result:
            return drag_result

        # Indicate we could not process the drop:
        return wx.DragNone

    def _wx_dropped_on(self, index, item):
        """ Helper method for handling a single item dropped on the list
            control.
        """
        adapter = self.adapter
        object, name = self.object, self.name

        # Obtain the destination of the dropped item relative to the target:
        destination = adapter.get_dropped(object, name, index, item)

        # Adjust the target index accordingly:
        if destination == 'after':
            index += 1

        # Insert the dropped item at the requested position:
        adapter.insert(object, name, index, item)

        # If the source for the drag was also this list control, we need to
        # adjust the original source indices to account for their new position
        # after the drag operation:
        indices = self._drag_indices
        if indices is not None:
            for i in range(len(indices) - 1, -1, -1):
                if indices[i] < index:
                    break

                indices[i] += 1

    def wx_drag_over(self, x, y, data, drag_result):
        """ Handles a Python object being dragged over the tree.
        """
        if isinstance(data, list):
            rc = wx.DragNone
            for item in data:
                rc = self.wx_drag_over(x, y, item, drag_result)
                if rc == wx.DragNone:
                    break

            return rc

        index, flags = self.control.HitTest(wx.Point(x, y))

        # If the user is dragging over an empty list, set the target to the end
        # of the list:
        if ((index == -1) and ((flags & wx.LIST_HITTEST_NOWHERE) != 0)
                and (self.control.GetItemCount() == 0)):
            index = 0

        # If the drag target index is valid and the adapter says it is OK to
        # drop the data here, then indicate the data can be dropped:
        if ((index != -1) and self.adapter.get_can_drop(
                self.object, self.name, index, data)):
            return drag_result

        # Else indicate that we will not accept the data:
        return wx.DragNone

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

    def _refresh(self):
        """ Refreshes the contents of the editor's list control.
        """
        self.control.RefreshItems(0, len(self.value) - 1)

    def _add_image(self, image_resource):
        """ Adds a new image to the wx.ImageList and its associated mapping.
        """
        bitmap = image_resource.create_image().ConvertToBitmap()

        image_list = self._image_list
        if image_list is None:
            self._image_list = image_list = wx.ImageList(
                bitmap.GetWidth(), bitmap.GetHeight())
            self.control.AssignImageList(image_list, wx.IMAGE_LIST_SMALL)

        self.image_resources[image_resource] = \
        self.images[ image_resource.name ]   = index = image_list.Add( bitmap )

        return index

    def _get_image(self, image):
        """ Converts a user specified image to a wx.ListCtrl image index.
        """
        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 _get_selected(self):
        """ Returns a list of the indices of all currently selected list items.
        """
        selected = []
        item = -1
        control = self.control
        while True:
            item = control.GetNextItem(item, wx.LIST_NEXT_ALL,
                                       wx.LIST_STATE_SELECTED)
            if item == -1:
                break

            selected.append(item)

        return selected

    def _search_for_string(self, increment=0):
        """ Searches for the next occurrence of the current search string.
        """
        selected = self._get_selected()
        if len(selected) > 1:
            return

        start = 0
        if len(selected) == 1:
            start = selected[0] + increment

        get_text = self.adapter.get_text
        search = self.search
        object = self.object
        name = self.name

        if increment >= 0:
            items = xrange(start, self.item_count)
        else:
            items = xrange(start, -1, -1)

        for index in items:
            if search in get_text(object, name, index).lower():
                self.index = index
                self.update_editor()
                break

    def _append_new(self):
        """ Append a new item to the end of the list control.
        """
        if 'append' in self.factory.operations:
            self.edit = True
            adapter = self.adapter
            index = self.control.GetItemCount()
            if self.factory.auto_add:
                self.index = index - 1
                self.update_editor()
            else:
                self.index = index
                adapter.insert(
                    self.object, self.name, self.index,
                    adapter.get_default_value(self.object, self.name))

    def _copy_current(self):
        """ Copies the currently selected list control item to the clipboard.
        """
        selected = self._get_selected()
        if len(selected) == 1:
            index = selected[0]
            if index < self.item_count:
                try:
                    from pyface.wx.clipboard import clipboard

                    clipboard.data = self.adapter.get_text(
                        self.object, self.name, index)
                except:
                    # Handle the traits.util package not being installed by
                    # just ignoring the request:
                    pass

    def _cut_current(self):
        """ Cuts the currently selected list control item and places its value
            in the clipboard.
        """
        ops = self.factory.operations
        if ('insert' in ops) and ('delete' in ops):
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                if index < self.item_count:
                    try:
                        from pyface.wx.clipboard import clipboard

                        clipboard.data = self.adapter.get_text(
                            self.object, self.name, index)
                        self.index = index
                        self.adapter.delete(self.object, self.name, index)
                    except:
                        # Handle the traits.util package not being installed
                        # by just ignoring the request:
                        pass

    def _paste_current(self):
        """ Pastes the clipboard contents into the currently selected list
            control item.
        """
        if 'insert' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                try:
                    from pyface.wx.clipboard import clipboard

                    self._set_text_current(selected[0],
                                           clipboard.text_data,
                                           insert=True)
                except:
                    # Handle the traits.util package not being installed by
                    # just ignoring the request:
                    pass

    def _insert_current(self):
        """ Inserts a new item after the currently selected list control item.
        """
        if 'insert' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                self.index = selected[0]
                self.edit = True
                adapter = self.adapter
                adapter.insert(
                    self.object, self.name, selected[0],
                    adapter.get_default_value(self.object, self.name))

    def _delete_current(self):
        """ Deletes the currently selected items from the list control.
        """
        if 'delete' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 0:
                return

            n = self.item_count
            delete = self.adapter.delete
            selected.reverse()
            self.index = selected[-1]
            for index in selected:
                if index < n:
                    delete(self.object, self.name, index)

    def _move_up_current(self):
        """ Moves the currently selected item up one line in the list control.
        """
        if 'move' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                n = self.item_count
                if 0 < index < n:
                    adapter = self.adapter
                    object, name = self.object, self.name
                    item = adapter.get_item(object, name, index)
                    adapter.delete(object, name, index)
                    self.index = index - 1
                    adapter.insert(object, name, index - 1, item)

    def _move_down_current(self):
        """ Moves the currently selected item down one line in the list control.
        """
        if 'move' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                index = selected[0]
                n = self.item_count - 1
                if index < n:
                    adapter = self.adapter
                    object, name = self.object, self.name
                    item = adapter.get_item(object, name, index)
                    adapter.delete(object, name, index)
                    self.index = index + 1
                    adapter.insert(object, name, index + 1, item)

    def _edit_current(self):
        """ Allows the user to edit the current item in the list control.
        """
        if 'edit' in self.factory.operations:
            selected = self._get_selected()
            if len(selected) == 1:
                self.control.EditLabel(selected[0])

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

    def _set_text_current(self, index, text, insert=False):
        """ Sets the text value of the specified list control item.
        """
        if text.strip() != '':
            object, name, adapter = self.object, self.name, self.adapter
            if insert or self._is_auto_add(index):
                adapter.insert(object, name, index,
                               adapter.get_default_value(object, name))
                self.edit = (not insert)

            self.index = index + 1
            adapter.set_text(object, name, index, text)
Exemple #25
0
class RangeSelection2D(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a 2D selection.
    
    Attributes
    ----------
    op : Instance(Range2DOp)
        The instance of Range2DOp that we're viewing / editing
        
    huefacet : Str
        The conditioning variable to plot multiple colors
        
    subset : Str
        The string passed to `Experiment.query()` to subset the data before
        plotting
        
    interactive : Bool
        is this view interactive?  Ie, can the user set min and max
        with a mouse drag?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but
    they must both be unset!
        
    Examples
    --------
    
    In an IPython notebook with `%matplotlib notebook`
    
    >>> r = flow.Range2DOp(name = "Range2D",
    ...                    xchannel = "V2-A",
    ...                    ychannel = "Y2-A"))
    >>> rv = r.default_view()
    >>> rv.interactive = True
    >>> rv.plot(ex2) 
    """

    id = Constant('edu.mit.synbio.cytoflow.views.range2d')
    friendly_id = Constant("2D Range Selection")

    op = Instance(IOperation)
    name = DelegatesTo('op')

    xchannel = DelegatesTo('op')
    xlow = DelegatesTo('op')
    xhigh = DelegatesTo('op')

    ychannel = DelegatesTo('op')
    ylow = DelegatesTo('op')
    yhigh = DelegatesTo('op')

    interactive = Bool(False, transient=True)

    # internal state.
    _ax = Any(transient=True)
    _selector = Instance(RectangleSelector, transient=True)
    _box = Instance(Rectangle, transient=True)

    def plot(self, experiment, **kwargs):
        """Plot the underlying scatterplot and then plot the selection on top of it."""

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if self.xfacet:
            raise util.CytoflowViewError(
                "RangeSelection.xfacet must be empty or `Undefined`")

        if self.yfacet:
            raise util.CytoflowViewError(
                "RangeSelection.yfacet must be empty or `Undefined`")

        super(RangeSelection2D, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_rect()
        self._interactive()

    @on_trait_change('xlow, xhigh, ylow, yhigh', post_init=True)
    def _draw_rect(self):
        if not self._ax:
            return

        if self._box and self._box in self._ax.patches:
            self._box.remove()

        if self.xlow and self.xhigh and self.ylow and self.yhigh:
            self._box = Rectangle((self.xlow, self.ylow),
                                  (self.xhigh - self.xlow),
                                  (self.yhigh - self.ylow),
                                  facecolor="grey",
                                  alpha=0.2)
            self._ax.add_patch(self._box)
            plt.draw()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._selector = RectangleSelector(self._ax,
                                               onselect=self._onselect,
                                               rectprops={
                                                   'alpha': 0.2,
                                                   'color': 'grey'
                                               },
                                               useblit=True)
        else:
            self._selector = None

    def _onselect(self, pos1, pos2):
        """Update selection traits"""
        self.xlow = min(pos1.xdata, pos2.xdata)
        self.xhigh = max(pos1.xdata, pos2.xdata)
        self.ylow = min(pos1.ydata, pos2.ydata)
        self.yhigh = max(pos1.ydata, pos2.ydata)
Exemple #26
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

    #-------------------------------------------------------------------------
    #  Finishes initializing the editor by creating the underlying toolkit
    #  widget:
    #-------------------------------------------------------------------------

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

        factory = self.factory
        self.columns = factory.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)
        signal = QtCore.SIGNAL('triggered()')
        insertable = factory.row_factory is not None and not factory.auto_add
        if factory.editable:
            if insertable:
                action = self.header_menu.addAction('Insert new item')
                QtCore.QObject.connect(action, signal, self._on_context_insert)
            if factory.deletable:
                action = self.header_menu.addAction('Delete item')
                QtCore.QObject.connect(action, signal, 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')
            QtCore.QObject.connect(self.header_menu_up, signal,
                                   self._on_context_move_up)
            self.header_menu_down = self.header_menu.addAction(
                'Move item down')
            QtCore.QObject.connect(self.header_menu_down, signal,
                                   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')
        QtCore.QObject.connect(action, signal, 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()
        signal = QtCore.SIGNAL(
            'selectionChanged(QItemSelection, QItemSelection)')
        mode_slot = getattr(self, '_on_%s_selection' % factory.selection_mode)
        QtCore.QObject.connect(smodel, signal, 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 auxillary editor and encompassing splitter if necessary
        mode = factory.selection_mode
        if (factory.edit_view == ' ') or not mode 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
        signal = QtCore.SIGNAL('clicked(QModelIndex)')
        QtCore.QObject.connect(self.table_view, signal, self._on_click)
        signal = QtCore.SIGNAL('doubleClicked(QModelIndex)')
        QtCore.QObject.connect(self.table_view, signal, 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()

    #-------------------------------------------------------------------------
    #  Disposes of the contents of an editor:
    #-------------------------------------------------------------------------

    def dispose(self):
        """ Disposes of the contents of an editor."""

        # Make sure that the auxillary 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()

    #-------------------------------------------------------------------------
    #  Updates the editor when the object trait changes external to the editor:
    #-------------------------------------------------------------------------

    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'] = str(header.saveState())
        return prefs

    #-------------------------------------------------------------------------
    #  Requests that the underlying table widget to redraw itself:
    #-------------------------------------------------------------------------

    def refresh_editor(self):
        """Requests that the underlying table widget to redraw itself."""

        self.table_view.viewport().update()

    #-------------------------------------------------------------------------
    #  Creates a new row object using the provided factory:
    #-------------------------------------------------------------------------

    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)

    #-------------------------------------------------------------------------
    #  Returns the raw list of model objects:
    #-------------------------------------------------------------------------

    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

    #-------------------------------------------------------------------------
    #  Perform actions without notifying the underlying table view or model:
    #-------------------------------------------------------------------------

    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

    #-------------------------------------------------------------------------
    #  Sets the current selection to a set of specified objects:
    #-------------------------------------------------------------------------

    def set_selection(self, objects=[], notify=True):
        """Sets the current selection to a set of specified objects."""

        if not isinstance(objects, SequenceTypes):
            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()
        for index in indexes:
            index = self.model.mapFromSource(index)
            if index.isValid():
                self.table_view.setCurrentIndex(index)
                selection.select(index, index)
        smodel = self.table_view.selectionModel()
        try:
            smodel.blockSignals(not notify)
            if len(selection.indexes()):
                smodel.select(selection, flags)
            else:
                smodel.clear()
        finally:
            smodel.blockSignals(False)

    #-------------------------------------------------------------------------
    #  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 = 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, basestring):
            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()
        return [(index.row(), index.column()) for index in indices]

    def _set_selected_indices(self, indices):
        selected = []
        for row, col in indices:
            selected.append((self.value[row], self.columns[col].name))

        self.selected = selected
        self.set_selection(self.selected, False)
        return

    #-- 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.reset()
        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)
Exemple #27
0
class JitterPlot(AbstractPlotRenderer):
    """A renderer for a jitter plot, a 1D plot with some width in the
    dimension perpendicular to the primary axis.  Useful for understanding
    dense collections of points.
    """

    # The data source of values
    index = Instance(ArrayDataSource)

    # The single mapper that this plot uses
    mapper = Instance(AbstractMapper)

    # Just an alias for "mapper"
    index_mapper = Property(lambda obj, attr: getattr(obj, "mapper"),
                            lambda obj, attr, val: setattr(obj, "mapper", val))

    x_mapper = Property()
    y_mapper = Property()

    orientation = Enum("h", "v")

    # The size, in pixels, of the area over which to spread the data points
    # along the dimension orthogonal to the index direction.
    jitter_width = Int(50)

    # How the plot should center itself along the orthogonal dimension if the
    # component's width is greater than the jitter_width
    #align = Enum("center", "left", "right", "top", "bottom")

    # The type of marker to use.  This is a mapped trait using strings as the
    # keys.
    marker = MarkerTrait

    # The pixel size of the marker, not including the thickness of the outline.
    marker_size = Float(4.0)

    # The CompiledPath to use if **marker** is set to "custom". This attribute
    # must be a compiled path for the Kiva context onto which this plot will
    # be rendered.  Usually, importing kiva.GraphicsContext will do
    # the right thing.
    custom_symbol = Any

    # The function which actually renders the markers
    render_markers_func = Callable(render_markers)

    # The thickness, in pixels, of the outline to draw around the marker.  If
    # this is 0, no outline is drawn.
    line_width = Float(1.0)

    # The fill color of the marker.
    color = black_color_trait

    # The color of the outline to draw around the marker.
    outline_color = black_color_trait

    # Override the base class default for **origin**, which specifies corners.
    # Since this is a 1D plot, it only makes sense to have the origin at the
    # edges.
    origin = Enum("bottom", "top", "left", "right")

    #------------------------------------------------------------------------
    # Built-in selection handling
    #------------------------------------------------------------------------

    # The name of the metadata attribute to look for on the datasource for
    # determine which points are selected and which are not.  The metadata
    # value returned should be a *list* of numpy arrays suitable for masking
    # the values returned by index.get_data().
    selection_metadata_name = Str("selections")

    # The color to use to render selected points
    selected_color = black_color_trait

    # Alpha value to apply to points that are not in the set of "selected"
    # points
    unselected_alpha = Float(0.3)
    unselected_line_width = Float(0.0)

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    _cache_valid = Bool(False)

    _cached_data_pts = Any()
    _cached_data_pts_sorted = Any()
    _cached_data_argsort = Any()

    _screen_cache_valid = Bool(False)
    _cached_screen_pts = Any()
    _cached_screen_map = Any()  # dict mapping index to value points

    # The random number seed used to generate the jitter.  We store this
    # so that the jittering is stable as the data is replotted.
    _jitter_seed = Trait(None, None, Int)

    #------------------------------------------------------------------------
    # Component/AbstractPlotRenderer interface
    #------------------------------------------------------------------------

    def map_screen(self, data_array):
        """ Maps an array of data points into screen space and returns it as
        an array.  Although the orthogonal (non-scaled) axis does not have
        a mapper, this method returns the scattered values in that dimension.

        Implements the AbstractPlotRenderer interface.
        """
        if len(data_array) == 0:
            return np.zeros(0)

        if self._screen_cache_valid:
            sm = self._cached_screen_map
            new_x = [x for x in data_array if x not in sm]
            if new_x:
                new_y = self._make_jitter_vals(len(new_x))
                sm.update(dict(
                    (new_x[i], new_y[i]) for i in range(len(new_x))))
            xs = self.mapper.map_screen(data_array)
            ys = [sm[x] for x in xs]

        else:
            if self._jitter_seed is None:
                self._set_seed(data_array)
            xs = self.mapper.map_screen(data_array)
            ys = self._make_jitter_vals(len(data_array))

        if self.orientation == "h":
            return np.vstack((xs, ys)).T
        else:
            return np.vstack((ys, xs)).T

    def _make_jitter_vals(self, numpts):
        vals = np.random.uniform(0, self.jitter_width, numpts)
        if self.orientation == "h":
            ymin = self.y
            height = self.height
            vals += ymin + height / 2 - self.jitter_width / 2
        else:
            xmin = self.x
            width = self.width
            vals += xmin + width / 2 - self.jitter_width / 2
        return vals

    def map_data(self, screen_pt):
        """ Maps a screen space point into the index space of the plot.
        """
        x, y = screen_pt
        if self.orientation == "v":
            x, y = y, x
        return self.mapper.map_data(x)

    def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \
                  index_only = True):
        """ Maps a screen space point to an index into the plot's index array(s).
        """
        screen_points = self._cached_screen_pts

        if len(screen_points) == 0:
            return None

        data_pt = self.map_data(screen_pt)
        if ((data_pt < self.mapper.range.low) or \
            (data_pt > self.mapper.range.high)) and outside_returns_none:
            return None

        if self._cached_data_pts_sorted is None:
            self._cached_data_argsort = np.argsort(self._cached_data_pts)
            self._cached_data_pts_sorted = self._cached_data_pts[
                self._cached_data_argsort]

        data = self._cached_data_pts_sorted
        try:
            ndx = reverse_map_1d(data, data_pt, "ascending")
        except IndexError, e:
            if outside_returns_none:
                return None
            else:
                if data_pt < data[0]:
                    return 0
                else:
                    return len(data) - 1

        orig_ndx = self._cached_data_argsort[ndx]

        if threshold == 0.0:
            return orig_ndx

        sx, sy = screen_points[orig_ndx]
        if sqrt((screen_pt[0] - sx)**2 + (screen_pt[1] - sy)**2) <= threshold:
            return orig_ndx
        else:
            return None
Exemple #28
0
class InstSource(HasPrivateTraits):
    """Expose measurement information from a inst file.

    Parameters
    ----------
    file : File
        Path to the BEM file (*.fif).

    Attributes
    ----------
    fid : Array, shape = (3, 3)
        Each row contains the coordinates for one fiducial point, in the order
        Nasion, RAP, LAP. If no file is set all values are 0.
    """

    file = File(exists=True, filter=['*.fif'])

    inst_fname = Property(Str, depends_on='file')
    inst_dir = Property(depends_on='file')
    inst = Property(depends_on='file')

    points_filter = Any(desc="Index to select a subset of the head shape "
                        "points")
    n_omitted = Property(Int, depends_on=['points_filter'])

    # head shape
    inst_points = Property(depends_on='inst',
                           desc="Head shape points in the "
                           "inst file(n x 3 array)")
    points = Property(depends_on=['inst_points', 'points_filter'],
                      desc="Head "
                      "shape points selected by the filter (n x 3 array)")

    # fiducials
    fid_dig = Property(depends_on='inst',
                       desc="Fiducial points "
                       "(list of dict)")
    fid_points = Property(depends_on='fid_dig',
                          desc="Fiducial points {ident: "
                          "point} dict}")
    lpa = Property(depends_on='fid_points',
                   desc="LPA coordinates (1 x 3 "
                   "array)")
    nasion = Property(depends_on='fid_points',
                      desc="Nasion coordinates (1 x "
                      "3 array)")
    rpa = Property(depends_on='fid_points',
                   desc="RPA coordinates (1 x 3 "
                   "array)")

    view = View(
        VGroup(Item('file'),
               Item('inst_fname', show_label=False, style='readonly')))

    @cached_property
    def _get_n_omitted(self):
        if self.points_filter is None:
            return 0
        else:
            return np.sum(self.points_filter == False)  # noqa: E712

    @cached_property
    def _get_inst(self):
        if self.file:
            info = read_info(self.file)
            if info['dig'] is None:
                error(
                    None, "The selected FIFF file does not contain "
                    "digitizer information. Please select a different "
                    "file.", "Error Reading FIFF File")
                self.reset_traits(['file'])
            else:
                return info

    @cached_property
    def _get_inst_dir(self):
        return os.path.dirname(self.file)

    @cached_property
    def _get_inst_fname(self):
        if self.file:
            return os.path.basename(self.file)
        else:
            return '-'

    @cached_property
    def _get_inst_points(self):
        if not self.inst:
            return np.zeros((1, 3))

        points = np.array([
            d['r'] for d in self.inst['dig']
            if d['kind'] == FIFF.FIFFV_POINT_EXTRA
        ])
        return points

    @cached_property
    def _get_points(self):
        if self.points_filter is None:
            return self.inst_points
        else:
            return self.inst_points[self.points_filter]

    @cached_property
    def _get_fid_dig(self):  # noqa: D401
        """Fiducials for info['dig']."""
        if not self.inst:
            return []
        dig = self.inst['dig']
        dig = [d for d in dig if d['kind'] == FIFF.FIFFV_POINT_CARDINAL]
        return dig

    @cached_property
    def _get_fid_points(self):
        if not self.inst:
            return {}
        digs = dict((d['ident'], d) for d in self.fid_dig)
        return digs

    @cached_property
    def _get_nasion(self):
        if self.fid_points:
            return self.fid_points[FIFF.FIFFV_POINT_NASION]['r'][None, :]
        else:
            return np.zeros((1, 3))

    @cached_property
    def _get_lpa(self):
        if self.fid_points:
            return self.fid_points[FIFF.FIFFV_POINT_LPA]['r'][None, :]
        else:
            return np.zeros((1, 3))

    @cached_property
    def _get_rpa(self):
        if self.fid_points:
            return self.fid_points[FIFF.FIFFV_POINT_RPA]['r'][None, :]
        else:
            return np.zeros((1, 3))

    def _file_changed(self):
        self.reset_traits(('points_filter', ))
Exemple #29
0
class HB_HeapBrowser ( HasPrivateTraits ):

    # The name of the plugin:
    name = Str( 'Heap Browser' )

    # The current heap statistics table entries:
    current_counts = List( HB_ClassCount )

    # The list of currently selected counts:
    selected_counts = List( HB_ClassCount )

    # The sting that all class names must contain:
    filter_name = Str( event = 'heap_updated' )

    # The threshold for the number of instances to display:
    filter_instances = Int( event = 'heap_updated' )

    # The threshold for the amount of change to display:
    filter_change = Int( event = 'heap_updated' )

    # Number of items in the current_counts list:
    current_classes = Property( depends_on = 'current_counts' )

    # Total number of instances in the current_counts list:
    current_instances = Property( depends_on = 'current_counts' )

    # Total change in the current_counts list:
    current_change = Property( depends_on = 'current_counts' )

    # Indicates whether or not the selection set is empty or not:
    has_counts = Property

    # The set of all current active object ids:
    current_ids = Any( set() )

    # Current heap statistics:
    current_heap = Any( {} )

    # Previous heap statistics:
    previous_heap = Any( {} )

    # Event fired when anything changes that affects the heap statistics view:
    heap_updated = Event

    # The list of object classes currently being ignored:
    ignored_classes = Any( set() )

    # The list of object classes currently being shown:
    shown_classes = Any( set() )

    # Refresh button:
    refresh = Button( 'Refresh' )

    # Baseline button (to create a new 'baseline' set):
    baseline = Button( 'Baseline' )

    # Hide all selected items button:
    hide_selected = Button( 'Hide Selected' )

    # Show only the selected items button:
    show_selected = Button( 'Show Selected' )

    # Show all items button:
    show_all = Button( 'Show All' )

    # Show the details of the current selected items button:
    show_details = Button( 'Show Details' )

    # Event fired when a 'counts' item is double-clicked:
    counts_dclick = Event

    #-- View 2 Traits ----------------------------------------------------------

    # The list of objects which the user has requested detailed information for:
    details = List( HB_Detail )

    #-- View 3 Traits ----------------------------------------------------------

    # The list of object referrer trees:
    referrers = List( HB_Referrers )

    #-- View 4 Traits ----------------------------------------------------------

    # The list of object baselines:
    baselines = List( HB_Baseline )

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

    heap_stats_view = View(
        VSplit(
            VGroup(
                HGroup(
                    Item( 'filter_name',
                          id     = 'filter_name',
                          label  = 'Name',
                          width  = -90,
                          editor = HistoryEditor( auto_set = True ),
                    ),
                    '_',
                    Item( 'filter_instances',
                          label = 'Min Instances',
                          width = -36
                    ),
                    '_',
                    Item( 'filter_change',
                          label = 'Min Change',
                          width = -36
                    ),
                    '_',
                    spring,
                    '_',
                    Item( 'current_classes',
                          label       = 'Classes',
                          style       = 'readonly',
                          format_func = commatize,
                          width       = -35
                    ),
                    '_',
                    Item( 'current_instances',
                          label       = 'Instances',
                          style       = 'readonly',
                          format_func = commatize,
                          width       = -50
                    ),
                    '_',
                    Item( 'current_change',
                          label       = 'Change',
                          style       = 'readonly',
                          format_func = commatize,
                          width       = -50
                    ),
                ),
                Item( 'current_counts',
                      id     = 'current_counts',
                      editor = counts_table_editor
                ),
                '_',
                HGroup(
                    Item( 'refresh',
                          tooltip = 'Refresh the heap statistics' ),
                    Item( 'baseline',
                          tooltip = 'Create a new baseline set' ),
                    spring,
                    Item( 'hide_selected',
                          tooltip = 'Remove all selected items from the view',
                          enabled_when = 'has_counts' ),
                    Item( 'show_selected',
                          tooltip = 'Show only the selected items in the view',
                          enabled_when = 'has_counts' ),
                    Item( 'show_all',
                          tooltip = 'Show all items again',
                          enabled_when = '(len( ignored_classes ) > 0) '
                                      'or (len( shown_classes ) > 0)' ),
                    '_',
                    Item( 'show_details',
                          tooltip = 'Show the instances of the selected '
                                    'classes',
                          enabled_when = 'has_counts' ),
                    show_labels = False,
                ),
                label       = 'Heap',
                show_labels = False,
                dock        = 'tab'
            ),
            Item( 'details',
                  id     = 'details',
                  style  = 'custom',
                  dock   = 'tab',
                  editor = ListEditor( use_notebook = True,
                                       deletable    = True,
                                       dock_style   = 'tab',
                                       export       = 'DockWindowShell',
                                       page_name    = '.short_name' )
            ),
            Item( 'referrers',
                  id     = 'referrers',
                  style  = 'custom',
                  dock   = 'tab',
                  editor = ListEditor( use_notebook = True,
                                       deletable    = True,
                                       dock_style   = 'tab',
                                       export       = 'DockWindowShell',
                                       page_name    = '.name' )
            ),
            Item( 'baselines',
                  id     = 'baselines',
                  style  = 'custom',
                  dock   = 'tab',
                  editor = ListEditor( use_notebook = True,
                                       deletable    = True,
                                       dock_style   = 'tab',
                                       export       = 'DockWindowShell',
                                       page_name    = 'Baseline' )
            ),
            id          = 'splitter',
            show_labels = False
        ),
        title        = 'Heap Browser',
        id           = 'etsdevtools.developer.tools.heap_browser.HB_HeapBrowser',
        width        = 0.5,
        height       = 0.75,
        resizable    = True,
        key_bindings = heap_browser_key_bindings
    )

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

    def _get_has_counts ( self ):
        return (len( self.selected_counts ) > 0)

    def _get_current_classes ( self ):
        return len( self.current_counts )

    @cached_property
    def _get_current_instances ( self ):
        return reduce( lambda l, r: l + r.instances, self.current_counts, 0 )

    @cached_property
    def _get_current_change ( self ):
        return reduce( lambda l, r: l + r.change, self.current_counts, 0 )

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

    def _heap_updated_changed ( self ):
        """ Handles the 'heap_updated' event being fired by rebuilding the
            heap class counts list.
        """
        ignored = ignored_classes
        shown   = self.shown_classes
        no_show = (len( shown ) == 0)
        if no_show:
            ignored = ignored_classes.union( self.ignored_classes )

        old              = self.previous_heap
        filter_name      = self.filter_name.lower()
        filter_instances = self.filter_instances
        filter_change    = self.filter_change
        counts = [ HB_ClassCount( name      = name,
                                  instances = instances,
                                  change    = instances - old.get( name, 0 ) )
                   for name, instances in self.current_heap.iteritems()
                   if (name not in ignored)                   and
                      (no_show or (name in shown))            and
                      (name.lower().find( filter_name ) >= 0) and
                      (instances >= filter_instances)         and
                      (abs( instances - old.get( name, 0 ) ) >= filter_change)
        ]
        counts.sort( lambda l, r: cmp( l.lower_name, r.lower_name ) )
        self.current_counts = counts

    def _refresh_changed ( self, ignore = None ):
        """ Handles the 'Refresh' button being clicked.
        """
        self.update()

    def _baseline_changed ( self, ignore = None ):
        """ Handles the 'Baseline' button being clicked.
        """
        self.baselines.append( self._create_baseline() )

    def _hide_selected_changed ( self, info = None ):
        """ Handles the 'Hide Selected' button being clicked.
        """
        self.ignored_classes.update(
            set( [ item.name for item in self.selected_counts ] )
        )
        self.shown_classes.clear()
        del self.selected_counts[:]
        self.heap_updated = True

    def _show_selected_changed ( self, info = None ):
        """ Handles the 'Show Selected' button being clicked.
        """
        self.shown_classes = set(
            set( [ item.name for item in self.selected_counts ] )
        )
        self.ignored_classes.clear()
        del self.selected_counts[:]
        self.heap_updated = True

    def _show_all_changed ( self, info = None ):
        """ Handles the 'Show All' button being clicked.
        """
        self.ignored_classes.clear()
        self.shown_classes.clear()
        del self.selected_counts[:]
        self.heap_updated = True

    def _show_details_changed ( self, ignore = True ):
        """ Handles the 'Show Details' button being clicked.
        """
        self.details.extend( [ HB_Detail( name = item.name, owner = self )
                               for item in self.selected_counts ] )

    def _counts_dclick_changed ( self, event ):
        """ Handles the user double-clicking a 'counts' table entry in order
            to show its details.
        """
        item, column = event
        self.details.append( HB_Detail( name = item.name, owner = self ) )

    def _set_filter_change_1 ( self, info = None ):
        """ Sets the filter change value to 1.
        """
        self.filter_change = 1

    def _clear_filter ( self, info = None ):
        """ Clears all current filter variable settings back to their defaults.
        """
        self.filter_change = filter_instances = 0
        self.filter_name   = ''

    def _select_all ( self, info = None ):
        """ Selects all current displayed items.
        """
        self.selected_counts = self.current_counts

    def _unselect_all ( self, info = None ):
        """ Unselects all currently selected items.
        """
        self.selected_counts = []

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

    def update ( self ):
        """ Updates the heap statistics.
        """
        self.previous_heap = old = self.current_heap
        self.current_heap  = new = {}
        self.current_ids   = ids = set()

        gc.collect()

        for object in gc.get_objects():
            name = object_name( object )
            if name not in ignored_classes:
                new[ name ] = new.setdefault( name, 0 ) + 1
                ids.add( id( object ) )

        self.heap_updated = True

        # Indicate that each class detail should update its instance
        # information:
        for detail in self.details:
            detail.update = True

        # Indicate that each referrer should update its instance information:
        for referrer in self.referrers:
            referrer.root.update = True

        # Indicate that each baseline view should update its contents:
        for baseline in self.baselines:
            baseline.update = ids

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

    def _create_baseline ( self ):
        """ Creates a new baseline object.
        """
        current_ids = self.current_ids
        object_ids  = {}

        gc.collect()

        for object in gc.get_objects():
            if id( object ) not in current_ids:
                name = object_name( object )
                if name not in ignored_classes:
                    object_ids[ id( object ) ] = name

        return HB_Baseline( object_ids = object_ids, owner = self )
class _Tool(HasTraits):
    """ A tool bar tool representation of an action item. """

    #### '_Tool' interface ####################################################

    # Is the item checked?
    checked = Bool(False)

    # A controller object we delegate taking actions through (if any).
    controller = Any

    # Is the item enabled?
    enabled = Bool(True)

    # Is the item visible?
    visible = Bool(True)

    # The radio group we are part of (None if the tool is not part of such a
    # group).
    group = Any

    # The toolkit control.
    control = Any()

    # The toolkit control id.
    control_id = None

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

    def __init__(self, parent, tool_bar, image_cache, item, controller,
                 show_labels):
        """ Creates a new tool bar tool for an action item. """

        self.item = item
        self.tool_bar = tool_bar
        action = item.action

        if action.style == 'widget':
            widget = action.create_control(tool_bar)
            self.control = tool_bar.addWidget(widget)
        elif action.image is None:
            self.control = tool_bar.addAction(action.name)
        else:
            size = tool_bar.iconSize()
            image = action.image.create_icon((size.width(), size.height()))
            self.control = tool_bar.addAction(image, action.name)

        self.control.triggered.connect(self._qt4_on_triggered)

        self.control.setToolTip(action.tooltip)
        self.control.setWhatsThis(action.description)
        self.control.setEnabled(action.enabled)
        self.control.setVisible(action.visible)

        if action.style == 'toggle':
            self.control.setCheckable(True)
            self.control.setChecked(action.checked)
        elif action.style == 'radio':
            # Create an action group if it hasn't already been done.
            try:
                ag = item.parent._qt4_ag
            except AttributeError:
                ag = item.parent._qt4_ag = QtGui.QActionGroup(parent)

            self.control.setActionGroup(ag)

            self.control.setCheckable(True)
            self.control.setChecked(action.checked)

        # Keep a reference in the action.  This is done to make sure we live as
        # long as the action (and still respond to its signals) and don't die
        # if the manager that created us is garbage collected.
        self.control._tool_instance = self

        # Listen for trait changes on the action (so that we can update its
        # enabled/disabled/checked state etc).
        action.on_trait_change(self._on_action_enabled_changed, 'enabled')
        action.on_trait_change(self._on_action_visible_changed, 'visible')
        action.on_trait_change(self._on_action_checked_changed, 'checked')
        action.on_trait_change(self._on_action_name_changed, 'name')
        action.on_trait_change(self._on_action_accelerator_changed,
                               'accelerator')

        # Detect if the control is destroyed.
        self.control.destroyed.connect(self._qt4_on_destroyed)

        if controller is not None:
            self.controller = controller
            controller.add_to_toolbar(self)

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

    def _qt4_on_destroyed(self, control=None):
        """ Delete the reference to the control to avoid attempting to talk to
        it again.
        """
        self.control = None

    def _qt4_on_triggered(self):
        """ Called when the tool bar tool is clicked. """

        action = self.item.action
        action_event = ActionEvent()

        # Perform the action!
        if self.controller is not None:
            self.checked = action.checked = self.control.isChecked()

            # Most of the time, action's do no care about the event (it
            # contains information about the time the event occurred etc), so
            # we only pass it if the perform method requires it. This is also
            # useful as Traits UI controllers *never* require the event.
            argspec = getargspec(self.controller.perform)

            # If the only arguments are 'self' and 'action' then don't pass
            # the event!
            if len(argspec.args) == 2:
                self.controller.perform(action)

            else:
                self.controller.perform(action, action_event)

        else:
            self.checked = action.checked = self.control.isChecked()

            # Most of the time, action's do no care about the event (it
            # contains information about the time the event occurred etc), so
            # we only pass it if the perform method requires it.
            argspec = getargspec(action.perform)

            # If the only argument is 'self' then don't pass the event!
            if len(argspec.args) == 1:
                action.perform()

            else:
                action.perform(action_event)

    #### Trait event handlers #################################################

    def _enabled_changed(self):
        """ Called when our 'enabled' trait is changed. """
        if self.control is not None:
            self.control.setEnabled(self.enabled)

    def _visible_changed(self):
        """ Called when our 'visible' trait is changed. """
        if self.control is not None:
            self.control.setVisible(self.visible)

    def _checked_changed(self):
        """ Called when our 'checked' trait is changed. """
        if self.control is not None:
            self.control.setChecked(self.checked)

    def _on_action_enabled_changed(self, action, trait_name, old, new):
        """ Called when the enabled trait is changed on an action. """
        if self.control is not None:
            self.control.setEnabled(action.enabled)

    def _on_action_visible_changed(self, action, trait_name, old, new):
        """ Called when the visible trait is changed on an action. """
        if self.control is not None:
            self.control.setVisible(action.visible)

    def _on_action_checked_changed(self, action, trait_name, old, new):
        """ Called when the checked trait is changed on an action. """
        if self.control is not None:
            self.control.setChecked(action.checked)

    def _on_action_name_changed(self, action, trait_name, old, new):
        """ Called when the name trait is changed on an action. """
        if self.control is not None:
            self.control.setText(action.name)

    def _on_action_accelerator_changed(self, action, trait_name, old, new):
        """ Called when the accelerator trait is changed on an action. """
        if self.control is not None:
            self.control.setShortcut(action.accelerator)