File: point_line.py

package info (click to toggle)
python-enable 4.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 7,280 kB
  • ctags: 13,899
  • sloc: cpp: 48,447; python: 28,502; ansic: 9,004; makefile: 315; sh: 44
file content (236 lines) | stat: -rw-r--r-- 8,384 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
""" A point-to-point drawn polygon. """

from __future__ import with_statement

from enable.api import cursor_style_trait, Line
from traits.api import Event, Int, Instance

from drawing_tool import DrawingTool


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 = cursor_style_trait('arrow')
    drawing_cursor = cursor_style_trait('pencil')
    delete_cursor = cursor_style_trait('bullseye')
    move_cursor = cursor_style_trait('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)
        return

    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
        return

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

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

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

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

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

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

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

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

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

    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
        return

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

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

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

    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
        return

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

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

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

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

# EOF