示例#1
0
class Box(Component):
    """
    The box moves wherever the user clicks and drags.
    """

    normal_pointer = Pointer("arrow")
    moving_pointer = Pointer("hand")

    offset_x = Float
    offset_y = Float

    fill_color = (0.8, 0.0, 0.1, 1.0)
    moving_color = (0.0, 0.8, 0.1, 1.0)

    resizable = ""

    def _draw_mainlayer(self, gc, view_bounds=None, mode="default"):
        with gc:
            if self.event_state == "moving":
                gc.set_fill_color(self.moving_color)
            else:
                gc.set_fill_color(self.fill_color)
            dx, dy = self.bounds
            x, y = self.position
            gc.rect(x, y, dx, dy)
            gc.fill_path()

            # draw line around outer box
            gc.set_stroke_color((0, 0, 0, 1))
            gc.rect(self.outer_x, self.outer_y, self.outer_width,
                    self.outer_height)
            gc.stroke_path()

    def normal_key_pressed(self, event):
        print("Key:", event.character)

    def normal_left_down(self, event):
        self.event_state = "moving"
        event.window.set_pointer(self.moving_pointer)
        event.window.set_mouse_owner(self, event.net_transform())
        self.offset_x = event.x - self.x
        self.offset_y = event.y - self.y
        event.handled = True

    def moving_mouse_move(self, event):
        self.position = [event.x - self.offset_x, event.y - self.offset_y]
        event.handled = True
        self.request_redraw()

    def moving_left_up(self, event):
        self.event_state = "normal"
        event.window.set_pointer(self.normal_pointer)
        event.window.set_mouse_owner(None)
        event.handled = True
        self.request_redraw()

    def moving_mouse_leave(self, event):
        self.moving_left_up(event)
        event.handled = True
示例#2
0
class LineSegmentTool(AbstractOverlay):
    """ The base class for tools that allow the user to draw a
    series of points connected by lines.
    """

    # The component that this tool overlays
    component = Instance(Component)

    # The current line segment being drawn.
    line = Instance(Line, args=())

    # A list of the points in data space as (index,value)
    points = List

    # The event states are:
    #
    # normal:
    #     The user may have selected points, and is moving the cursor around.
    # selecting:
    #     The user has clicked down but hasn't let go of the button yet,
    #     and can still drag the point around.
    # dragging:
    #     The user has clicked on an existing point and is dragging it
    #     around.  When the user releases the mouse button, the tool returns
    #     to the "normal" state
    event_state = Enum("normal", "selecting", "dragging")

    # The pixel distance from a vertex that is considered 'on' the vertex.
    proximity_distance = Int(4)

    # The data (index, value) position of the mouse cursor; this is used by various
    # draw() routines.
    mouse_position = Trait(None, None, Tuple)

    # The index of the vertex being dragged, if any.
    _dragged = Trait(None, None, Int)

    # Is the point being dragged is a newly placed point? This informs the
    # "dragging" state about what to do if the user presses Escape while
    # dragging.
    _drag_new_point = Bool(False)

    # The previous event state that the tool was in. This is used for states
    # that can be canceled (e.g., by pressing the Escape key), so that the
    # tool can revert to the correct state.
    _prev_event_state = Any

    # The cursor shapes to use for various modes

    # Cursor shape for non-tool use.
    original_cursor = Pointer("arrow")
    # Cursor shape for drawing.
    normal_cursor = Pointer("pencil")
    # Cursor shape for deleting points.
    delete_cursor = Pointer("bullseye")
    # Cursor shape for moving points.
    move_cursor = Pointer("sizing")

    #------------------------------------------------------------------------
    # Traits inherited from Component
    #------------------------------------------------------------------------

    # The tool is initially invisible, because there is nothing to draw.
    visible = Bool(False)

    #------------------------------------------------------------------------
    # Public methods
    #------------------------------------------------------------------------

    def __init__(self, component=None, **kwtraits):
        if "component" in kwtraits:
            component = kwtraits["component"]
        super(LineSegmentTool, self).__init__(**kwtraits)
        self.component = component
        self.reset()
        self.line.line_dash = (4.0, 2.0)
        return

    #------------------------------------------------------------------------
    # Drawing tool methods
    #------------------------------------------------------------------------

    def reset(self):
        """ Resets the tool, throwing away any points, and making the tool
        invisible.
        """
        self.points = []
        self.event_state = "normal"
        self.visible = False
        self.request_redraw()
        return

    def _activate(self):
        """
        Called by a PlotComponent when this becomes the active tool.
        """
        pass

    def _deactivate(self, component=None):
        """
        Called by a PlotComponent when this is no longer the active tool.
        """
        self.reset()
        #self.component.window.set_pointer("arrow")
        return

    #------------------------------------------------------------------------
    # PointLine methods
    #------------------------------------------------------------------------

    def add_point(self, point):
        """ Given a screen-space *point* (x,y), adds the corresponding data
        space point to the list for this tool.
        """
        self.points.append(self._map_data(point))
        return

    def get_point(self, index):
        """ Retrieves the indexed point and returns its screen space value.
        """
        return self._map_screen(self.points[index])

    def set_point(self, index, point):
        """ Sets the data-space *index* for a screen-space *point*.
        """
        self.points[index] = self._map_data(point)
        return

    def remove_point(self, index):
        """ Removes the point for a given *index* from this tool's list of
        points.
        """
        del self.points[index]
        return

    #------------------------------------------------------------------------
    # "normal" state
    #------------------------------------------------------------------------

    def normal_left_down(self, event):
        """ Handles the left mouse button being pressed while the tool is
        in the 'normal' state.

        For an existing point, if the user is pressing the Control key, the
        point is deleted. Otherwise, the user can drag the point.

        For a new point, the point is added, and the user can drag it.
        """
        # Determine if the user is dragging/deleting an existing point, or
        # creating a new one
        over = self._over_point(event, self.line.points)
        if over is not None:
            if event.control_down:
                # Delete the point
                self.points.pop(over)
                self.line.points = list(
                    self.component.map_screen(array(self.points)))
                self.request_redraw()
            else:
                self.event_state = "dragging"
                self._dragged = over
                self._drag_new_point = False
                self.dragging_mouse_move(event)
        else:
            self.points.append(self._map_data((event.x, event.y)))
            self._dragged = -1
            self._drag_new_point = True
            self.visible = True
            self.event_state = "dragging"
            self.dragging_mouse_move(event)
        return

    def normal_mouse_move(self, event):
        """ Handles the user moving the mouse in the 'normal' state.

        When the user moves the cursor over an existing point, if the Control
        key is pressed, the cursor changes to the **delete_cursor**, indicating
        that the point can be deleted. Otherwise, the cursor changes to the
        **move_cursor**, indicating that the point can be moved.

        When the user moves the cursor over any other point, the cursor
        changes to (or stays) the **normal_cursor**.
        """
        # If the user moves over an existing point, change the cursor to be the
        # move_cursor; otherwise, set it to the normal cursor
        over = self._over_point(event, self.line.points)
        if over is not None:
            if event.control_down:
                event.window.set_pointer(self.delete_cursor)
            else:
                event.window.set_pointer(self.move_cursor)
        else:
            event.handled = False
            event.window.set_pointer(self.normal_cursor)
        self.request_redraw()
        return

    def normal_draw(self, gc):
        """ Draws the line.
        """
        self.line.points = list(self.component.map_screen(array(self.points)))
        self.line._draw(gc)
        return

    def normal_key_pressed(self, event):
        """ Handles the user pressing a key in the 'normal' state.

        If the user presses the Enter key, the tool is reset.
        """
        if event.character == "Enter":
            self._finalize_selection()
            self.reset()
        return

    def normal_mouse_leave(self, event):
        """ Handles the user moving the cursor away from the tool area.
        """
        event.window.set_pointer("arrow")
        return

    #------------------------------------------------------------------------
    # "dragging" state
    #------------------------------------------------------------------------
    def dragging_mouse_move(self, event):
        """ Handles the user moving the mouse while in the 'dragging' state.

        The screen is updated to show the new mouse position as the end of the
        line segment being drawn.
        """
        mouse_position = self._map_data((event.x, event.y))
        self.points[self._dragged] = mouse_position
        self.line.points = list(self.component.map_screen(array(self.points)))
        self.request_redraw()
        return

    def dragging_draw(self, gc):
        """ Draws the polygon in the 'dragging' state.
        """
        self.line._draw(gc)
        return

    def dragging_left_up(self, event):
        """ Handles the left mouse coming up in the 'dragging' state.

        Switches to 'normal' state.
        """
        self.event_state = "normal"
        self._dragged = None
        self.updated = self
        return

    def dragging_key_pressed(self, event):
        """ Handles a key being pressed in the 'dragging' state.

        If the key is "Esc", the drag operation is canceled.
        """
        if event.character == "Esc":
            self._cancel_drag()
        return

    def dragging_mouse_leave(self, event):
        """ Handles the mouse leaving the tool area in the 'dragging' state.

        The drag is canceled and the cursor changes to an arrow.
        """
        self._cancel_drag()
        event.window.set_pointer("arrow")
        return

    def _cancel_drag(self):
        """ Cancels a drag operation.
        """
        if self._dragged != None:
            if self._drag_new_point:
                # Only remove the point if it was a newly-placed point
                self.points.pop(self._dragged)
            self._dragged = None
        self.mouse_position = None
        self.event_state = "normal"
        self.request_redraw()
        return

    #------------------------------------------------------------------------
    # override AbstractOverlay methods
    #------------------------------------------------------------------------

    def overlay(self, component, gc, view_bounds, mode="normal"):
        """ Draws this component overlaid on another component.

        Implements AbstractOverlay.
        """
        draw_func = getattr(self, self.event_state + "_draw", None)
        if draw_func:
            with gc:
                gc.clip_to_rect(component.x, component.y, component.width - 1,
                                component.height - 1)
                draw_func(gc)
        return

    def request_redraw(self):
        """ Requests that the component redraw itself.

        Overrides Enable Component.
        """
        self.component.invalidate_draw()
        self.component.request_redraw()
        return

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

    def _map_data(self, point):
        """ Maps values from screen space into data space.
        """
        x_mapper = self.component.x_mapper
        y_mapper = self.component.y_mapper
        if self.component.orientation == 'h':
            ndx = x_mapper.map_data(point[0])
            val = y_mapper.map_data(point[1])
        else:
            val = x_mapper.map_data(point[0])
            ndx = y_mapper.map_data(point[1])
        return (ndx, val)

    def _map_screen(self, point):
        """ Maps values from data space into screen space.
        """
        x_mapper = self.component.x_mapper
        y_mapper = self.component.y_mapper
        if self.component.orientation == 'h':
            x = x_mapper.map_screen(point[0])
            y = y_mapper.map_screen(point[1])
        else:
            x = x_mapper.map_screen(point[1])
            y = y_mapper.map_screen(point[0])
        return (x, y)

    def _is_near_point(self, point, event):
        """ Determines if the pointer is near a specified point.
        """
        event_point = (event.x, event.y)

        return ((abs( point[0] - event_point[0] ) + \
                 abs( point[1] - event_point[1] )) <= self.proximity_distance)

    def _over_point(self, event, points):
        """ Return the index of a point in *points* that *event* is 'over'.

        Returns None if there is no such point.
        """
        for i, point in enumerate(points):
            if self._is_near_point(point, event):
                result = i
                break
        else:
            result = None
        return result

    def _finalize_selection(self):
        """
        Abstract method called to take action after the line selection is complete
        """
        pass

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

    def _component_changed(self, old, new):
        if new:
            self.container = new
        return
示例#3
0
class PanTool(DragTool):
    """ An implementation of a pan tool based on the DragTool instead of
    a bare BaseTool
    """

    # The cursor to use when panning.
    drag_pointer = Pointer("hand")

    # Scaling factor on the panning "speed".
    speed = Float(1.0)

    # The modifier key that, if depressed when the drag is initiated, constrains
    # the panning to happen in the only direction of largest initial motion.
    # It is possible to permanently restrict this tool to always drag along one
    # direction.  To do so, set constrain=True, constrain_key=None, and
    # constrain_direction to the desired direction.
    constrain_key = Enum(None, "shift", "control", "alt")

    # Constrain the panning to one direction?
    constrain = Bool(False)

    # The direction of constrained draw. A value of None means that the user
    # has initiated the drag and pressed the constrain_key, but hasn't moved
    # the mouse yet; the magnitude of the components of the next mouse_move
    # event will determine the constrain_direction.
    constrain_direction = Enum(None, "x", "y")

    # Restrict to the bounds of the plot data
    restrict_to_data = Bool(False)

    # (x,y) of the point where the mouse button was pressed.
    _original_xy = Tuple

    # Data coordinates of **_original_xy**.  This may be either (index,value)
    # or (value,index) depending on the component's orientation.
    _original_data = Tuple

    # Was constrain=True triggered by the **contrain_key**? If False, it was
    # set programmatically.
    _auto_constrain = Bool(False)

    #------------------------------------------------------------------------
    # Inherited BaseTool traits
    #------------------------------------------------------------------------

    # The tool does not have a visual representation (overrides
    # BaseTool).
    draw_mode = "none"

    # The tool is not visible (overrides BaseTool).
    visible = False


    def drag_start(self, event):
        """ Called when the drag operation starts """
        self._start_pan(event)

    def dragging(self, event):
        plot = self.component

        if self._auto_constrain and self.constrain_direction is None:
            # Determine the constraint direction
            if abs(event.x - self._original_xy[0]) > abs(event.y - self._original_xy[1]):
                self.constrain_direction = "x"
            else:
                self.constrain_direction = "y"

        for direction, bound_name, ndx in [("x","width",0), ("y","height",1)]:
            if not self.constrain or self.constrain_direction == direction:
                mapper = getattr(plot, direction + "_mapper")
                range = mapper.range
                domain_min, domain_max = mapper.domain_limits
                eventpos = getattr(event, direction)
                origpos = self._original_xy[ndx]

                screenlow, screenhigh = mapper.screen_bounds
                screendelta = self.speed * (eventpos - origpos)
                #if getattr(plot, direction + "_direction", None) == "flipped":
                #    screendelta = -screendelta

                newlow = mapper.map_data(screenlow - screendelta)
                newhigh = mapper.map_data(screenhigh - screendelta)

                # Don't set the range in this dimension if the panning
                # would exceed the domain limits.
                # To do this offset properly, we would need to iteratively
                # solve for a root using map_data on successive trial
                # values.  As a first approximation, we're just going to
                # use a linear approximation, which works perfectly for
                # linear mappers (which is used 99% of the time).
                if domain_min is None:
                    if self.restrict_to_data:
                        domain_min = min([source.get_data().min() for source in range.sources])
                    else:
                        domain_min = -inf
                if domain_max is None:
                    if self.restrict_to_data:
                        domain_max = max([source.get_data().max() for source in range.sources])
                    else:
                        domain_max = inf
                if (newlow <= domain_min) and (newhigh >= domain_max):
                    # Don't do anything; effectively, freeze the pan
                    continue
                if newlow <= domain_min:
                    delta = newhigh - newlow
                    newlow = domain_min
                    # Don't let the adjusted newhigh exceed domain_max; this
                    # can happen with a nonlinear mapper.
                    newhigh = min(domain_max, domain_min + delta)
                elif newhigh >= domain_max:
                    delta = newhigh - newlow
                    newhigh = domain_max
                    # Don't let the adjusted newlow go below domain_min; this
                    # can happen with a nonlinear mapper.
                    newlow = max(domain_min, domain_max - delta)

                # Use .set_bounds() so that we don't generate two range_changed
                # events on the DataRange
                range.set_bounds(newlow, newhigh)

        event.handled = True

        self._original_xy = (event.x, event.y)
        plot.request_redraw()
        return

    def drag_cancel(self, event):
        # We don't do anything for "cancelling" of the drag event because its
        # transient states during the drag are generally valid and useful
        # terminal states unto themselves.
        pass

    def drag_end(self, event):
        return self._end_pan(event)

    def _start_pan(self, event, capture_mouse=True):
        self._original_xy = (event.x, event.y)
        if self.constrain_key is not None:
            if getattr(event, self.constrain_key + "_down"):
                self.constrain = True
                self._auto_constrain = True
                self.constrain_direction = None
        self.event_state = "panning"
        if capture_mouse:
            event.window.set_pointer(self.drag_pointer)
            event.window.set_mouse_owner(self, event.net_transform())
        event.handled = True
        return

    def _end_pan(self, event):
        if self._auto_constrain:
            self.constrain = False
            self.constrain_direction = None
        self.event_state = "normal"
        event.window.set_pointer("arrow")
        if event.window.mouse_owner == self:
            event.window.set_mouse_owner(None)
        event.handled = True
        return
示例#4
0
class DragPolygon(DrawingTool):
    """ A drag drawn polygon. """

    poly = Instance(Polygon, args=())

    draw_mode = "overlay"

    # Visible style. ####

    # Override the vertex color so as to not draw it.
    vertex_color = Delegate("poly", modify=True)

    # Override the vertex size so as to not draw it.
    vertex_size = Delegate("poly", modify=True)

    background_color = Delegate("poly", modify=True)

    # Pointers. ####

    # Pointer for the complete state.
    complete_pointer = Pointer("cross")

    # Pointer for the drawing state.
    drawing_pointer = Pointer("cross")

    # Pointer for the normal state.
    normal_pointer = Pointer("cross")

    # Miscellaneous. ####

    # The context menu for the polygon.
    menu = Instance(MenuManager)

    def reset(self):
        self.vertex_color = (0, 0, 0, 0)
        self.vertex_size = 0
        self.poly.model.points = []
        self.event_state = "normal"

    ###########################################################################
    # 'Component' interface.
    ###########################################################################

    # 'complete' state #####################################################

    def complete_draw(self, gc):
        """ Draw the completed polygon. """
        with gc:
            self.poly.border_dash = None
            self.poly._draw_closed(gc)

    def complete_left_down(self, event):
        """ Draw a new polygon. """
        self.reset()
        self.normal_left_down(event)

    def complete_right_down(self, event):
        """ Do the context menu if available. """
        if self.menu is not None:
            if self._is_in((event.x + self.x, event.y - self.y)):
                menu = self.menu.create_menu(event.window.control)
                # FIXME : The call to _flip_y is necessary but inappropriate.
                menu.show(event.x, event.window._flip_y(event.y))

    # 'drawing' state ######################################################

    def drawing_draw(self, gc):
        """ Draw the polygon while in 'drawing' state. """

        with gc:
            self.poly.border_dash = (4.0, 2.0)
            self.poly._draw_open(gc)

    def drawing_left_up(self, event):
        """ Handle the left mouse button coming up in 'drawing' state. """

        self.event_state = "complete"
        self.pointer = self.complete_pointer

        self.request_redraw()

        self.complete = True

    def drawing_mouse_move(self, event):
        """ Handle the mouse moving in 'drawing' state. """

        last_point = self.poly.model.points[-1]

        # If we have moved, we need to add a point.
        if last_point != (event.x + self.x, event.y - self.y):
            self.poly.model.points.append((event.x + self.x, event.y - self.y))
            self.request_redraw()

    # 'normal' state #######################################################

    def normal_left_down(self, event):
        """ Handle the left button down in the 'normal' state. """

        self.poly.model.points.append((event.x + self.x, event.y - self.y))

        self.event_state = "drawing"
        self.pointer = self.drawing_pointer

        self.request_redraw()

    def normal_mouse_move(self, event):
        """ Handle the mouse moving in the 'normal' state. """

        self.pointer = self.normal_pointer
示例#5
0
class DragSegment(DrawingTool):
    """A dragged line segment"""

    # Override the vertex color so as to not draw it.
    vertex_color = (0.0, 0.0, 0.0, 0.0)

    # Because this class subclasses DrawingTool and not Line, it contains
    # an instance of the Line primitive.
    line = Instance(Line, args=())

    # Event fired when the line is complete
    complete = Event

    # Pointer for the complete state.
    complete_pointer = Pointer('arrow')

    # Pointer for the drawing state.
    drawing_pointer = Pointer('cross')

    # Pointer for the normal state.
    normal_pointer = Pointer('cross')

    #------------------------------------------------------------------------
    # DrawingTool interface
    #------------------------------------------------------------------------

    def reset(self):
        self.line.vertex_color = self.vertex_color
        self.line.points = []
        self.event_state = "normal"
        return

    #------------------------------------------------------------------------
    # "complete" state
    #------------------------------------------------------------------------

    def complete_draw(self, gc):
        """ Draw the completed line. """
        self.line.line_dash = None
        self.line._draw_mainlayer(gc)
        self.request_redraw()
        return

    #------------------------------------------------------------------------
    # "drawing" state
    #------------------------------------------------------------------------

    def drawing_draw(self, gc):
        self.line.line_dash = (4.0, 2.0)
        self.line._draw_mainlayer(gc)
        return

    def drawing_mouse_move(self, event):
        """ Handle the mouse moving in drawing state. """
        # Change the last point to the current event point
        self.line.points[-1] = (event.x, event.y)
        self.updated = self
        self.request_redraw()
        return

    def drawing_left_up(self, event):
        """ Handle the left mouse button coming up in the 'drawing' state. """
        self.event_state = 'complete'
        event.window.set_pointer(self.complete_pointer)
        self.request_redraw()
        self.complete = True
        return

    #------------------------------------------------------------------------
    # "normal" state
    #------------------------------------------------------------------------

    def normal_left_down(self, event):
        """ Handle the left button down in the 'normal' state. """
        # Set points the current segment, which is just the
        # current point twice.
        current_point = (event.x, event.y)
        self.line.points = [current_point, current_point]
        self.updated = self

        # Go into the drawing state
        self.event_state = 'drawing'
        event.window.set_pointer(self.drawing_pointer)

        self.request_redraw()
        return

    def normal_mouse_move(self, event):
        event.window.set_pointer(self.normal_pointer)
        return
示例#6
0
文件: pan_tool.py 项目: SDiot/chaco
class PanTool(BaseTool):
    """ A tool that enables the user to pan a plot by clicking a mouse
    button and dragging.
    """

    # The mouse button that initiates the drag operation.
    drag_button = Enum("left", "middle", "right")

    # The cursor to use when panning.
    drag_pointer = Pointer("hand")

    # Scaling factor on the panning "speed".
    speed = Float(1.0)

    # The modifier key that, if depressed when the drag is initiated, constrains
    # the panning to happen in the only direction of largest initial motion.
    # It is possible to permanently restrict this tool to always drag along one
    # direction.  To do so, set constrain=True, constrain_key=None, and
    # constrain_direction to the desired direction.
    constrain_key = Enum(None, "shift", "control", "alt")

    # Keys to Pan via keyboard
    pan_right_key = Instance(KeySpec, args=("Right", ))
    pan_left_key = Instance(KeySpec, args=("Left", ))
    pan_up_key = Instance(KeySpec, args=("Up", ))
    pan_down_key = Instance(KeySpec, args=("Down", ))

    # number of pixels the keys should pan
    # disabled if 0.0
    pan_keys_step = Float(0.0)

    # Constrain the panning to one direction?
    constrain = Bool(False)

    # The direction of constrained draw. A value of None means that the user
    # has initiated the drag and pressed the constrain_key, but hasn't moved
    # the mouse yet; the magnitude of the components of the next mouse_move
    # event will determine the constrain_direction.
    constrain_direction = Enum(None, "x", "y")

    # Restrict to the bounds of the plot data
    restrict_to_data = Bool(False)

    # (x,y) of the point where the mouse button was pressed.
    _original_xy = Tuple

    # Data coordinates of **_original_xy**.  This may be either (index,value)
    # or (value,index) depending on the component's orientation.
    _original_data = Tuple

    # Was constrain=True triggered by the **contrain_key**? If False, it was
    # set programmatically.
    _auto_constrain = Bool(False)

    #------------------------------------------------------------------------
    # Inherited BaseTool traits
    #------------------------------------------------------------------------

    # The tool does not have a visual representation (overrides
    # BaseTool).
    draw_mode = "none"

    # The tool is not visible (overrides BaseTool).
    visible = False

    # The possible event states of this tool (overrides enable.Interactor).
    event_state = Enum("normal", "panning")

    def normal_key_pressed(self, event):
        """ Handles a key being pressed when the tool is in the 'normal'
        state.
        """
        if self.pan_keys_step == 0.0:
            return
        src = self.component.bounds[0] / 2, self.component.bounds[1] / 2
        dest = src
        if self.pan_left_key.match(event):
            dest = (src[0] - self.pan_keys_step, src[1])
        elif self.pan_right_key.match(event):
            dest = (src[0] + self.pan_keys_step, src[1])
        elif self.pan_down_key.match(event):
            dest = (src[0], src[1] - self.pan_keys_step)
        elif self.pan_up_key.match(event):
            dest = (src[0], src[1] + self.pan_keys_step)
        if src != dest:
            self._original_xy = src
            event.x = dest[0]
            event.y = dest[1]
            self.panning_mouse_move(event)
        return

    def normal_left_down(self, event):
        """ Handles the left mouse button being pressed when the tool is in
        the 'normal' state.

        Starts panning if the left mouse button is the drag button.
        """
        if self.drag_button == "left":
            self._start_pan(event)
        return

    def normal_right_down(self, event):
        """ Handles the right mouse button being pressed when the tool is in
        the 'normal' state.

        Starts panning if the right mouse button is the drag button.
        """
        if self.drag_button == "right":
            self._start_pan(event)
        return

    def normal_middle_down(self, event):
        """ Handles the middle mouse button being pressed when the tool is in
        the 'normal' state.

        Starts panning if the middle mouse button is the drag button.
        """
        if self.drag_button == "middle":
            self._start_pan(event)
        return

    def panning_left_up(self, event):
        """ Handles the left mouse button coming up when the tool is in the
        'panning' state.

        Stops panning if the left mouse button is the drag button.
        """
        if self.drag_button == "left":
            self._end_pan(event)
        return

    def panning_right_up(self, event):
        """ Handles the right mouse button coming up when the tool is in the
        'panning' state.

        Stops panning if the right mouse button is the drag button.
        """
        if self.drag_button == "right":
            self._end_pan(event)
        return

    def panning_middle_up(self, event):
        """ Handles the middle mouse button coming up when the tool is in the
        'panning' state.

        Stops panning if the middle mouse button is the drag button.
        """
        if self.drag_button == "middle":
            self._end_pan(event)
        return

    def panning_mouse_move(self, event):
        """ Handles the mouse being moved when the tool is in the 'panning'
        state.
        """
        plot = self.component

        if self._auto_constrain and self.constrain_direction is None:
            # Determine the constraint direction
            x_orig, y_orig = self._original_xy
            if abs(event.x - x_orig) > abs(event.y - y_orig):
                self.constrain_direction = "x"
            else:
                self.constrain_direction = "y"

        direction_info = [("x", "width", 0), ("y", "height", 1)]
        for direction, bound_name, index in direction_info:
            if not self.constrain or self.constrain_direction == direction:
                mapper = getattr(plot, direction + "_mapper")
                domain_min, domain_max = mapper.domain_limits
                eventpos = getattr(event, direction)
                origpos = self._original_xy[index]

                screenlow, screenhigh = mapper.screen_bounds
                screendelta = self.speed * (eventpos - origpos)

                newlow = mapper.map_data(screenlow - screendelta)
                newhigh = mapper.map_data(screenhigh - screendelta)

                # Don't set the range in this dimension if the panning
                # would exceed the domain limits.
                # To do this offset properly, we would need to iteratively
                # solve for a root using map_data on successive trial
                # values.  As a first approximation, we're just going to
                # use a linear approximation, which works perfectly for
                # linear mappers (which is used 99% of the time).
                if domain_min is None:
                    if self.restrict_to_data:
                        domain_min = min([
                            source.get_data().min()
                            for source in mapper.range.sources
                        ])
                    else:
                        domain_min = -inf
                if domain_max is None:
                    if self.restrict_to_data:
                        domain_max = max([
                            source.get_data().max()
                            for source in mapper.range.sources
                        ])
                    else:
                        domain_max = inf

                if (newlow <= domain_min) and (newhigh >= domain_max):
                    # Don't do anything; effectively, freeze the pan
                    continue

                if newlow <= domain_min:
                    newlow = domain_min
                    # Calculate delta in screen space, which is always linear.
                    screen_delta = mapper.map_screen(domain_min) - screenlow
                    newhigh = mapper.map_data(screenhigh + screen_delta)
                elif newhigh >= domain_max:
                    newhigh = domain_max
                    # Calculate delta in screen space, which is always linear.
                    screen_delta = mapper.map_screen(domain_max) - screenhigh
                    newlow = mapper.map_data(screenlow + screen_delta)

                # Use .set_bounds() so that we don't generate two range_changed
                # events on the DataRange
                mapper.range.set_bounds(newlow, newhigh)

        event.handled = True

        self._original_xy = (event.x, event.y)
        plot.request_redraw()
        return

    def panning_mouse_leave(self, event):
        """ Handles the mouse leaving the plot when the tool is in the 'panning'
        state.

        Ends panning.
        """
        return self._end_pan(event)

    def _start_pan(self, event, capture_mouse=True):
        self._original_xy = (event.x, event.y)
        if self.constrain_key is not None:
            if getattr(event, self.constrain_key + "_down"):
                self.constrain = True
                self._auto_constrain = True
                self.constrain_direction = None
        self.event_state = "panning"
        if capture_mouse:
            event.window.set_pointer(self.drag_pointer)
            event.window.set_mouse_owner(self, event.net_transform())
        event.handled = True
        return

    def _end_pan(self, event):
        if self._auto_constrain:
            self.constrain = False
            self.constrain_direction = None
        self.event_state = "normal"
        event.window.set_pointer("arrow")
        if event.window.mouse_owner == self:
            event.window.set_mouse_owner(None)
        event.handled = True
        return
示例#7
0
class BaseCanvas(Component):
    '''
    '''
    # directory = None
    select_pointer = Pointer('hand')
    normal_pointer = Pointer('arrow')
示例#8
0
class Circle(Component):
    """
    The circle moves with the mouse cursor but leaves a translucent version of
    itself in its original position until the mouse button is released.
    """
    color = (0.6, 0.7, 1.0, 1.0)
    bgcolor = "none"

    normal_pointer = Pointer("arrow")
    moving_pointer = Pointer("hand")

    prev_x = Float
    prev_y = Float

    shadow_type = Enum("light", "dashed")
    shadow = Instance(Component)

    resizable = ""

    def __init__(self, **traits):
        Component.__init__(self, **traits)
        self.pointer = self.normal_pointer
        return

    def _draw_mainlayer(self, gc, view_bounds=None, mode="default"):
        with gc:
            gc.set_fill_color(self.color)
            dx, dy = self.bounds
            x, y = self.position
            radius = min(dx / 2.0, dy / 2.0)
            gc.arc(x + dx / 2.0, y + dy / 2.0, radius, 0.0, 2 * 3.14159)
            gc.fill_path()
        return

    def normal_left_down(self, event):
        self.event_state = "moving"
        self.pointer = self.moving_pointer
        event.window.set_mouse_owner(self, event.net_transform())

        # Create our shadow
        if self.shadow_type == "light":
            klass = LightCircle
        else:
            klass = DashedCircle
        self.shadow = klass(bounds=self.bounds,
                            position=self.position,
                            color=(1.0, 1.0, 1.0, 1.0))
        self.container.insert(0, self.shadow)
        x, y = self.position
        self.prev_x = event.x
        self.prev_y = event.y
        return

    def moving_mouse_move(self, event):
        self.position = [
            self.x + (event.x - self.prev_x), self.y + (event.y - self.prev_y)
        ]
        self.prev_x = event.x
        self.prev_y = event.y
        self.request_redraw()
        return

    def moving_left_up(self, event):
        self.event_state = "normal"
        self.pointer = self.normal_pointer
        event.window.set_mouse_owner(None)
        event.window.redraw()
        # Remove our shadow
        self.container.remove(self.shadow)
        return

    def moving_mouse_leave(self, event):
        self.moving_left_up(event)
        return
示例#9
0
class Click2DPanelTool(PanTool):

    event_state = Enum('normal', 'deciding', 'panning')
    drag_pointer = Pointer('arrow')
    
    panel2d = Any #Instance(TwoDimensionalPanel)
    panel_id = Enum('xy','xz','yz')

    def __init__(self, panel2d, panel_id):
        super(PanTool, self).__init__(
            component=getattr(panel2d, '{0}_plane'.format(panel_id)))
        self.panel2d = panel2d
        self.panel_id = panel_id

    def normal_left_down(self, event):
        self.event_state = 'deciding'
        self._original_xy = event.x, event.y

    def deciding_left_up(self, event):
        self.event_state = 'normal'
        x,y,z = self.panel2d.cursor            

        #if the panel is not in the image (e.g. click on the axis), ignore it

        #remember event.x and event.y are in space of pixels

        if self.panel_id == 'xy':
            mx, my = self.panel2d.xy_plane.map_data((event.x, event.y))
            if x == mx and y == my:
                return
            self.panel2d.move_cursor(mx, my, z)

        elif self.panel_id == 'xz':
            mx, mz = self.panel2d.xz_plane.map_data((event.x, event.y))
            if x == mx and z == mz:
                return
            self.panel2d.move_cursor(mx, y, mz)

        elif self.panel_id == 'yz':
            my, mz = self.panel2d.yz_plane.map_data((event.x, event.y))
            if y == my and z == mz:
                return
            self.panel2d.move_cursor(x, my, mz)

        else:
            raise NotImplementedError('FailFish')

    def normal_right_down(self, event):
        pin_name = self.panel2d.current_pin
        if pin_name is None:
            print 'Nothing currently pinned'
            return

        x,y,z = self.panel2d.cursor

        image_name = self.panel2d.currently_showing.name

        px,py,pz,pcolor = self.panel2d.pins[image_name][pin_name]

        if self.panel_id == 'xy':
            mx, my = self.panel2d.xy_plane.map_data((event.x, event.y))
            if px == mx and py == my:
                return
            self.panel2d.drop_pin(mx, my, z, name=self.panel2d.current_pin,
                color=pcolor)

        elif self.panel_id == 'xz':
            mx, mz = self.panel2d.xz_plane.map_data((event.x, event.y))
            if px == mx and pz == mz:
                return
            self.panel2d.drop_pin(mx, y, mz, name=self.panel2d.current_pin,
                color=pcolor)

        elif self.panel_id == 'yz':
            my, mz = self.panel2d.yz_plane.map_data((event.x, event.y))
            if py == my and pz == mz:
                return
            self.panel2d.drop_pin(x, my, mz, name=self.panel2d.current_pin,
                color=pcolor)

        else:
            raise NotImplementedError('BabyRage')

    def normal_mouse_move(self, event):
        self._move_mouse(event)

    def deciding_mouse_move(self, event):
        self._move_mouse(event)
        self.event_state = 'panning'
        return self.panning_mouse_move(event)

    def _move_mouse(self, event):
        x,y,z = self.panel2d.cursor    

        if self.panel_id == 'xy':
            mx, my = self.panel2d.xy_plane.map_data((event.x, event.y))
            self.panel2d.move_mouse(mx, my, z) 

        elif self.panel_id == 'xz':
            mx, mz = self.panel2d.xz_plane.map_data((event.x, event.y))
            self.panel2d.move_mouse(mx, y, mz)

        elif self.panel_id == 'yz':
            my, mz = self.panel2d.yz_plane.map_data((event.x, event.y))
            self.panel2d.move_mouse(x, my, mz)

        else:
            raise NotImplementedError('DansGame')
示例#10
0
class BaseDataCanvas(DataView):
    """
    """
    # fill_padding = True
    #    bgcolor = (0.9, 0.9, 1.0)
    #    bgcolor = (0, 1.0, 0)
    #    border_visible = True
    # use_backbuffer = True
    #    bgcolor = 'lightblue'
    unified_draw = True
    x_range = Tuple
    y_range = Tuple
    view_x_range = Tuple
    view_y_range = Tuple
    select_pointer = Pointer('hand')
    normal_pointer = Pointer('arrow')
    cross_pointer = Pointer('cross')

    show_axes = Bool(True)
    show_grids = Bool(True)
    use_zoom = Bool(True)
    use_pan = Bool(True)

    plot = None

    def cmap_plot(self, z):
        from chaco.array_plot_data import ArrayPlotData
        from chaco.plot import Plot
        from chaco.default_colormaps import color_map_name_dict

        pd = ArrayPlotData()
        pd.set_data('cmapdata', z)

        p = Plot(pd, padding=0)
        p.img_plot('cmapdata',
                   xbounds=(-25, 25),
                   ybounds=(-25, 25),
                   colormap=color_map_name_dict['hot'])
        self.add(p)
        return pd

    def line_plot(self, x, y, new_plot=True):
        if self.plot is None or new_plot:
            if isinstance(x, (float, int)):
                x = [x]

            if isinstance(y, (float, int)):
                y = [y]

            self.plot = LinePlot(
                index=ArrayDataSource(x),
                value=ArrayDataSource(y),
                index_mapper=LinearMapper(range=self.index_range),
                value_mapper=LinearMapper(range=self.value_range))

            self.add(self.plot)
        else:

            datax = self.plot.index.get_data()
            datay = self.plot.value.get_data()
            nx = hstack((datax, [x]))
            ny = hstack((datay, [y]))

            self.plot.index.set_data(nx)
            self.plot.value.set_data(ny)

    def reset_plots(self):
        self.plot = None
        for c in self.components[:1]:
            self.remove(c)
        self.request_redraw()

    def __init__(self, *args, **kw):
        """

        """
        super(BaseDataCanvas, self).__init__(*args, **kw)
        if 'x_range' not in kw:
            self.x_range = (-25, 25)

        if 'y_range' not in kw:
            self.y_range = (-25, 25)

        if 'view_x_range' not in kw:
            self.view_x_range = (-25, 25)

        if 'view_y_range' not in kw:
            self.view_y_range = (-25, 25)

        # plot=BaseXYPlot
        plot = LinePlot

        sp = plot(index=ArrayDataSource(self.y_range),
                  value=ArrayDataSource(self.x_range),
                  index_mapper=LinearMapper(range=self.index_range),
                  value_mapper=LinearMapper(range=self.value_range))

        self.index_range.sources.append(sp.index)
        self.value_range.sources.append(sp.value)

        sp.visible = False
        self.add(sp)
        if self.use_zoom:
            self.add_zoom()

        if self.use_pan:
            self.add_pan()

        self.index_mapper.on_trait_change(self.update, 'updated')
        self.value_mapper.on_trait_change(self.update, 'updated')

        # set the view range
        self.set_mapper_limits('x', self.view_x_range)
        self.set_mapper_limits('y', self.view_y_range)

        #        if not self.show_axes:
        #            self.value_axis.visible = False
        #            self.index_axis.visible = False
        self.value_axis.visible = self.show_axes
        self.index_axis.visible = self.show_axes

        self.x_grid.visible = self.show_grids
        self.y_grid.visible = self.show_grids

    @on_trait_change('view_x_range')
    def _update_xrange(self):
        self.set_mapper_limits('x', self.view_x_range)

    @on_trait_change('view_y_range')
    def _update_yrange(self):
        self.set_mapper_limits('y', self.view_y_range)

    @on_trait_change('show_grids')
    def change_grid_visibility(self):
        try:
            self.x_grid.visible = self.show_grids
            self.y_grid.visible = self.show_grids
            self.request_redraw()
        except AttributeError:
            pass

    def set_mapper_limits(self, mapper, limits, pad=0):
        """
        """
        mapper = getattr(self, '{}_mapper'.format(mapper))
        if mapper is not None:
            mapper.range.low_setting = limits[0] - pad
            mapper.range.high_setting = limits[1] + pad
            self.request_redraw()

    def get_mapper_limits(self, mapper):
        mapper = getattr(self, '{}_mapper'.format(mapper))
        return mapper.range.low, mapper.range.high

    def update(self, *args, **kw):
        """

        """
        pass

    def add_pan(self):
        """
        """
        p = PanTool(self)
        self.tools.append(p)

    def add_zoom(self):
        """
        """
        z = ZoomTool(component=self,
                     always_on=False,
                     tool_mode='box',
                     max_zoom_out_factor=1,
                     max_zoom_in_factor=10000)

        # b=BroadcasterTool()
        # b.tools.append(z)
        self.overlays.append(z)

        # self.tools.append(b)

    def get_wh(self, *args):
        return self._get_wh(*args)

    def _get_wh(self, w, h):
        """

        """
        wh, oo = self.map_screen([(w, h), (0, 0)])
        w = wh[0] - oo[0]
        h = wh[1] - oo[1]

        return w, h

    def _vertical_line(self, gc, x, y1, y2, color=(0, 0, 0)):
        """
        """

        p1 = (x, y1)
        p2 = (x, y2)
        self.line_segment(gc, p1, p2, color)

    def _horizontal_line(self, gc, y, x1, x2, color=(0, 0, 0)):
        """
        """
        p1 = (x1, y)
        p2 = (x2, y)
        self.line_segment(gc, p1, p2, color)

    def _line_segment(self, gc, p1, p2, color=None):
        if color is not None:
            gc.set_stroke_color(color)

        gc.move_to(*p1)
        gc.line_to(*p2)
        gc.draw_path()
示例#11
0
class PointLine(DrawingTool):
    """ A point-to-point drawn line. """

    # Our contained "Line" instance; it stores the points and does the actual
    # drawing.
    line = Instance(Line, args=())

    # Override the draw_mode value we inherit from DrawingTool
    draw_mode = "overlay"

    # The pixel distance from a vertex that is considered 'on' the vertex.
    proximity_distance = Int(4)

    # The cursor shapes to use for various modes
    normal_cursor = Pointer("arrow")
    drawing_cursor = Pointer("pencil")
    delete_cursor = Pointer("bullseye")
    move_cursor = Pointer("sizing")

    # The index of the vertex being dragged, if any.
    _dragged = Int

    complete = Event

    def add_point(self, point):
        """ Add the point. """
        self.line.points.append(point)

    def get_point(self, index):
        """ Get the point at the specified index. """
        return self.line.points[index]

    def set_point(self, index, point):
        """ Set the point at the specified index to point. """
        self.line.points[index] = point

    def remove_point(self, index):
        """ Remove the point with the specified index. """
        del self.line.points[index]

    # ------------------------------------------------------------------------
    # DrawingTool interface
    # ------------------------------------------------------------------------

    def reset(self):
        self.line.points = []
        self.event_state = "normal"

    # ------------------------------------------------------------------------
    # "complete" state
    # ------------------------------------------------------------------------

    def complete_draw(self, gc):
        # Draw the completed line
        self.line.line_dash = None
        with gc:
            self.line._draw_mainlayer(gc)

    def complete_left_down(self, event):
        """ Handle the left mouse button going down in the 'complete' state.
        """

        # Ignore the click if it contains modifiers we do not handle.
        if event.shift_down or event.alt_down:
            event.handled = False

        else:
            # If we are over a point, we will either move it or remove it.
            over = self._over_point(event, self.line.points)
            if over is not None:
                # Control down means remove it.
                if event.control_down:
                    self.remove_point(over)
                    self.updated = self

                # Otherwise, prepare to drag it.
                else:
                    self._dragged = over
                    event.window.set_pointer(self.move_cursor)
                    self.event_state = "drag_point"
                    self.request_redraw()

    def complete_mouse_move(self, event):
        """ Handle the mouse moving in the 'complete' state. """
        # If we are over a point, then we have to prepare to move it.
        over = self._over_point(event, self.line.points)
        if over is not None:
            if event.control_down:
                event.window.set_pointer(self.delete_cursor)
            else:
                event.window.set_pointer(self.move_cursor)
        else:
            event.handled = False
            event.window.set_pointer(self.normal_cursor)
        self.request_redraw()

    # ------------------------------------------------------------------------
    # "drag" state
    # ------------------------------------------------------------------------

    def drag_point_draw(self, gc):
        """ Draw the polygon in the 'drag_point' state. """
        self.line._draw_mainlayer(gc)

    def drag_point_left_up(self, event):
        """ Handle the left mouse coming up in the 'drag_point' state. """
        self.event_state = "complete"
        self.updated = self

    def drag_point_mouse_move(self, event):
        """ Handle the mouse moving in the 'drag_point' state. """
        # Only worry about the event if it's inside our bounds.
        dragged_point = self.get_point(self._dragged)
        # If the point has actually moved, update it.
        if dragged_point != (event.x, event.y):
            self.set_point(self._dragged, (event.x, event.y))
            self.request_redraw()

    # ------------------------------------------------------------------------
    # "incomplete" state
    # ------------------------------------------------------------------------

    def incomplete_draw(self, gc):
        """ Draw the line in the 'incomplete' state. """
        with gc:
            gc.set_fill_color((0, 0, 0, 0))
            gc.rect(50, 50, 100, 100)
        self.line._draw_mainlayer(gc)

    def incomplete_left_dclick(self, event):
        """ Handle a left double-click in the incomplete state. """
        # Remove the point that was placed by the first mouse down, since
        # another one will be placed on the down stroke of the double click.
        self.remove_point(-1)
        event.window.set_pointer(self.move_cursor)
        self.event_state = "complete"
        self.complete = True
        self.request_redraw()

    def incomplete_left_down(self, event):
        """ Handle the left mouse button coming up in incomplete state. """
        # Add the point.
        self.add_point((event.x, event.y))
        self.updated = self

    def incomplete_mouse_move(self, event):
        """ Handle the mouse moving in incomplete state. """
        # If we move over the initial point, then we change the cursor.
        event.window.set_pointer(self.drawing_cursor)

        # If the point has actually changed, then we need to update our model.
        if self.get_point(-1) != (event.x, event.y):
            self.set_point(-1, (event.x, event.y))
        self.request_redraw()

    # ------------------------------------------------------------------------
    # "normal" state
    # ------------------------------------------------------------------------

    def normal_left_down(self, event):
        """ Handle the left button up in the 'normal' state. """

        # Append the current point twice, because we need to have the starting
        # point and the current point be separate, since the current point
        # will be moved with the mouse from now on.
        self.add_point((event.x, event.y))
        self.add_point((event.x, event.y))
        self.event_state = "incomplete"
        self.updated = self
        self.line_dash = (4.0, 2.0)

    def normal_mouse_move(self, event):
        """ Handle the mouse moving in the 'normal' state. """
        event.window.set_pointer(self.drawing_cursor)

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

    def _updated_fired(self, event):
        # The self.updated trait is used by point_line and can be used by
        # others to indicate that the model has been updated.  For now, the
        # only action taken is to do a redraw.
        self.request_redraw()

    def _is_near_point(self, point, event):
        """ Determine if the pointer is near a specified point. """
        event_point = (event.x, event.y)

        return (abs(point[0] - event_point[0]) +
                abs(point[1] - event_point[1])) <= self.proximity_distance

    def _is_over_start(self, event):
        """ Test if the event is 'over' the starting vertex. """
        return len(self.points) > 0 and self._is_near_point(
            self.points[0], event)

    def _over_point(self, event, points):
        """ Return the index of a point in points that event is 'over'.

        Returns None if there is no such point.
        """
        for i, point in enumerate(points):
            if self._is_near_point(point, event):
                result = i
                break
        else:
            result = None
        return result
示例#12
0
class Circle(Component):
    """
    The circle moves with the mouse cursor but leaves a translucent version of
    itself in its original position until the mouse button is released.
    """

    color = (0.3, 0.4, 0.8, 1.0)
    bgcolor = "none"

    normal_pointer = Pointer("arrow")
    moving_pointer = Pointer("hand")

    offset_x = Float
    offset_y = Float

    shadow_type = Enum("light", "dashed")
    shadow = Instance(Component)

    def __init__(self, **traits):
        Component.__init__(self, **traits)
        self.pointer = self.normal_pointer

    def _draw_mainlayer(self, gc, view_bounds=None, mode="default"):
        with gc:
            gc.set_fill_color(self.color)
            dx, dy = self.bounds
            x, y = self.position
            radius = min(dx / 2.0, dy / 2.0)
            gc.arc(x + dx / 2.0, y + dy / 2.0, radius, 0.0, 2 * 3.14159)
            gc.fill_path()

    def normal_left_down(self, event):
        self.event_state = "moving"
        self.pointer = self.moving_pointer

        # Create our shadow
        if self.shadow_type == "light":
            klass = LightCircle
        else:
            klass = DashedCircle
        dx, dy = self.bounds
        self.shadow = klass(bounds=self.bounds,
                            position=self.position,
                            color=self.color)
        self.container.add(self.shadow)
        x, y = self.position
        self.offset_x = event.x - x
        self.offset_y = event.y - y

    def moving_mouse_move(self, event):
        self.position = [event.x - self.offset_x, event.y - self.offset_y]
        self.request_redraw()

    def moving_left_up(self, event):
        self.event_state = "normal"
        self.pointer = self.normal_pointer
        self.request_redraw()
        # Remove our shadow
        self.container.remove(self.shadow)

    def moving_mouse_leave(self, event):
        self.moving_left_up(event)
示例#13
0
class Bullet(Component):

    color = ColorTrait("gray")
    
    c_selected = ColorTrait("yellow")
    c_up = ColorTrait("yellow")
    c_dragging = ColorTrait("yellow")
    c_over = ColorTrait("yellow")
    c_dropping = ColorTrait("yellow")
    
    drag_color = ColorTrait((0.6, 0.6, 0.6, 0.5))
    border_color = ColorTrait((0.4, 0.4, 0.4, 1.0))
    bgcolor = "clear"

    bullet_state = Enum("up", "down", "dragging", "over", "dropping")

    normal_pointer = Pointer("arrow")

    moving_pointer = Pointer("hand")

    # This is currently determines the size of the widget, since we're just
    # drawing a triangle for now
    bounds=[9,12]

    def perform(self, event):
        """
        Called when the button is depressed.  'event' is the Enable mouse event
        that triggered this call.
        """
        pass

    def _draw_mainlayer(self, gc, view_bounds, mode="default"):
        if self.event_state == "selected":
            enable_glyph_lib.io_bullet_over(gc, self.x, self.y, self.width, self.height, self.c_selected_)
        elif self.bullet_state == "up":
            enable_glyph_lib.io_bullet_up(gc, self.x, self.y, self.width, self.height, self.color_)
        elif self.bullet_state == "dragging":
            enable_glyph_lib.io_bullet_drag(gc, self.x, self.y, self.width, self.height, self.c_dragging_)
        elif self.bullet_state == "over":
            enable_glyph_lib.io_bullet_over(gc, self.x, self.y, self.width, self.height, self.c_over_)
        elif self.bullet_state == "dropping":
            enable_glyph_lib.io_bullet_drop_target(gc, self.x, self.y, self.width, self.height, self.color_)
        else:
            enable_glyph_lib.io_bullet_down(gc, self.x, self.y, self.width, self.height, self.color_)

        return

    def normal_left_down(self, event):
        self.bullet_state = "down"
        self.pointer = self.moving_pointer
        self.request_redraw()
        event.handled = True

    def normal_mouse_enter(self, event):
        self.bullet_state = "over"
        self.pointer = self.moving_pointer
        self.request_redraw()
        event.handled = True

    def normal_mouse_leave(self, event):
        self.bullet_state = "up"
        self.pointer = self.normal_pointer
        self.request_redraw()
        event.handled = True

    def normal_left_up(self, event):
        self.bullet_state = "up"
        self.pointer = self.normal_pointer
        self.request_redraw()
        self.perform(event)
        event.handled = True