Source code for enable.drawing.point_line
# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" A point-to-point drawn polygon. """
from traits.api import Event, Int, Instance
from enable.api import Line, Pointer
from .drawing_tool import DrawingTool
[docs]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
[docs] def add_point(self, point):
""" Add the point. """
self.line.points.append(point)
[docs] def get_point(self, index):
""" Get the point at the specified index. """
return self.line.points[index]
[docs] def set_point(self, index, point):
""" Set the point at the specified index to point. """
self.line.points[index] = point
[docs] def remove_point(self, index):
""" Remove the point with the specified index. """
del self.line.points[index]
# ------------------------------------------------------------------------
# DrawingTool interface
# ------------------------------------------------------------------------
[docs] def reset(self):
self.line.points = []
self.event_state = "normal"
# ------------------------------------------------------------------------
# "complete" state
# ------------------------------------------------------------------------
[docs] def complete_draw(self, gc):
# Draw the completed line
self.line.line_dash = None
with gc:
self.line._draw_mainlayer(gc)
[docs] 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()
[docs] 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
# ------------------------------------------------------------------------
[docs] def drag_point_draw(self, gc):
""" Draw the polygon in the 'drag_point' state. """
self.line._draw_mainlayer(gc)
[docs] 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
[docs] 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
# ------------------------------------------------------------------------
[docs] 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)
[docs] 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()
[docs] 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
[docs] 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
# ------------------------------------------------------------------------
[docs] 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)
[docs] 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