21 def sign(n): |
21 def sign(n): |
22 if n < 0: return -1 |
22 if n < 0: return -1 |
23 elif n > 0: return 1 |
23 elif n > 0: return 1 |
24 else: return 0 |
24 else: return 0 |
25 |
25 |
26 class XYButtons(BufferedCanvas): |
26 DASHES = [4, 7] |
|
27 # Brush and pen for grey overlay when mouse hovers over |
|
28 HOVER_PEN_COLOR = wx.Colour(100, 100, 100, 172) |
|
29 HOVER_BRUSH_COLOR = wx.Colour(0, 0, 0, 128) |
|
30 |
|
31 class FocusCanvas(BufferedCanvas): |
|
32 def __init__(self, *args, **kwds): |
|
33 super().__init__(*args, **kwds) |
|
34 self.Bind(wx.EVT_SET_FOCUS, self.onFocus) |
|
35 |
|
36 def onFocus(self, evt): |
|
37 self.Refresh() |
|
38 evt.Skip() |
|
39 |
|
40 def drawFocusRect(self, dc): |
|
41 if self.HasFocus(): |
|
42 pen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_USER_DASH) |
|
43 pen.SetDashes(DASHES) |
|
44 dc.Pen = pen |
|
45 dc.Brush = wx.Brush(wx.TRANSPARENT_BRUSH) |
|
46 dc.DrawRectangle(self.ClientRect) |
|
47 |
|
48 class XYButtons(FocusCanvas): |
27 keypad_positions = { |
49 keypad_positions = { |
28 0: (106, 100), |
50 0: (104, 99), |
29 1: (86, 83), |
51 1: (86, 83), |
30 2: (68, 65), |
52 2: (68, 65), |
31 3: (53, 50) |
53 3: (53, 50) |
32 } |
54 } |
|
55 keypad_radius = 9 |
33 corner_size = (49, 49) |
56 corner_size = (49, 49) |
34 corner_inset = (7, 13) |
57 corner_inset = (7, 13) |
35 label_overlay_positions = { |
58 label_overlay_positions = { |
36 1: (145, 98.5, 9), |
59 1: (145, 98.5, 9), |
37 2: (160.5, 83.5, 10.6), |
60 2: (160.5, 83.5, 10.6), |
53 |
76 |
54 def __init__(self, parent, moveCallback = None, cornerCallback = None, spacebarCallback = None, bgcolor = "#FFFFFF", ID=-1, zcallback=None): |
77 def __init__(self, parent, moveCallback = None, cornerCallback = None, spacebarCallback = None, bgcolor = "#FFFFFF", ID=-1, zcallback=None): |
55 self.bg_bmp = wx.Image(imagefile(self.imagename), wx.BITMAP_TYPE_PNG).ConvertToBitmap() |
78 self.bg_bmp = wx.Image(imagefile(self.imagename), wx.BITMAP_TYPE_PNG).ConvertToBitmap() |
56 self.keypad_bmp = wx.Image(imagefile("arrow_keys.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() |
79 self.keypad_bmp = wx.Image(imagefile("arrow_keys.png"), wx.BITMAP_TYPE_PNG).ConvertToBitmap() |
57 self.keypad_idx = -1 |
80 self.keypad_idx = -1 |
|
81 self.hovered_keypad = None |
58 self.quadrant = None |
82 self.quadrant = None |
59 self.concentric = None |
83 self.concentric = None |
60 self.corner = None |
84 self.corner = None |
61 self.moveCallback = moveCallback |
85 self.moveCallback = moveCallback |
62 self.cornerCallback = cornerCallback |
86 self.cornerCallback = cornerCallback |
66 # Remember the last clicked buttons, so we can repeat when spacebar pressed |
90 # Remember the last clicked buttons, so we can repeat when spacebar pressed |
67 self.lastMove = None |
91 self.lastMove = None |
68 self.lastCorner = None |
92 self.lastCorner = None |
69 |
93 |
70 self.bgcolor = wx.Colour() |
94 self.bgcolor = wx.Colour() |
71 self.bgcolor.SetFromName(bgcolor) |
95 self.bgcolor.Set(bgcolor) |
72 self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) |
96 self.bgcolormask = wx.Colour(self.bgcolor.Red(), self.bgcolor.Green(), self.bgcolor.Blue(), 128) |
73 |
97 |
74 BufferedCanvas.__init__(self, parent, ID, size=self.bg_bmp.GetSize()) |
98 super().__init__(parent, ID, size=self.bg_bmp.GetSize()) |
75 |
99 |
76 self.bind_events() |
100 self.bind_events() |
77 |
101 |
78 def bind_events(self): |
102 def bind_events(self): |
79 # Set up mouse and keyboard event capture |
103 # Set up mouse and keyboard event capture |
80 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) |
104 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) |
81 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) |
105 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) |
82 self.Bind(wx.EVT_MOTION, self.OnMotion) |
106 self.Bind(wx.EVT_MOTION, self.OnMotion) |
83 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) |
107 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) |
84 self.Bind(wx.EVT_KEY_UP, self.OnKey) |
108 self.Bind(wx.EVT_CHAR_HOOK, self.OnKey) |
85 wx.GetTopLevelParent(self).Bind(wx.EVT_CHAR_HOOK, self.OnTopLevelKey) |
109 self.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus) |
|
110 |
|
111 def onKillFocus(self, evt): |
|
112 self.setKeypadIndex(-1) |
|
113 evt.Skip() |
86 |
114 |
87 def disable(self): |
115 def disable(self): |
88 self.enabled = False |
116 self.Enabled = self.enabled = False |
89 self.update() |
117 self.update() |
90 |
118 |
91 def enable(self): |
119 def enable(self): |
92 self.enabled = True |
120 self.Enabled = self.enabled = True |
93 self.update() |
121 self.update() |
94 |
122 |
95 def repeatLast(self): |
123 def repeatLast(self): |
96 if self.lastMove: |
124 if self.lastMove: |
97 self.moveCallback(*self.lastMove) |
125 self.moveCallback(*self.lastMove) |
110 return abs(xlen * pylen - ylen * pxlen) / math.sqrt(xlen ** 2 + ylen ** 2) |
138 return abs(xlen * pylen - ylen * pxlen) / math.sqrt(xlen ** 2 + ylen ** 2) |
111 |
139 |
112 def distanceToPoint(self, x1, y1, x2, y2): |
140 def distanceToPoint(self, x1, y1, x2, y2): |
113 return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) |
141 return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) |
114 |
142 |
115 def cycleKeypadIndex(self): |
143 def cycleKeypadIndex(self, forward): |
116 idx = self.keypad_idx + 1 |
144 idx = self.keypad_idx + (1 if forward else -1) |
117 if idx > 2: idx = 0 |
145 # do not really cycle to allow exiting of jog controls widget |
118 return idx |
146 return idx if idx < len(self.keypad_positions) else -1 |
119 |
147 |
120 def setKeypadIndex(self, idx): |
148 def setKeypadIndex(self, idx): |
121 self.keypad_idx = idx |
149 self.keypad_idx = idx |
122 self.update() |
150 self.update() |
123 |
151 |
124 def getMovement(self): |
152 def getMovement(self, event): |
125 xdir = [1, 0, -1, 0, 0, 0][self.quadrant] |
153 xdir = [1, 0, -1, 0, 0, 0][self.quadrant] |
126 ydir = [0, 1, 0, -1, 0, 0][self.quadrant] |
154 ydir = [0, 1, 0, -1, 0, 0][self.quadrant] |
127 zdir = [0, 0, 0, 0, 1, -1][self.quadrant] |
155 zdir = [0, 0, 0, 0, 1, -1][self.quadrant] |
128 magnitude = math.pow(10, self.concentric - 2) |
156 magnitude = math.pow(10, self.concentric - 2) |
129 if not zdir == 0: |
157 magnitude *= event.ShiftDown() and 2 or event.ControlDown() and 0.5 or 1 |
|
158 |
|
159 if zdir: |
130 magnitude = min(magnitude, 10) |
160 magnitude = min(magnitude, 10) |
131 return (magnitude * xdir, magnitude * ydir, magnitude * zdir) |
161 return (magnitude * xdir, magnitude * ydir, magnitude * zdir) |
132 |
162 |
133 def lookupConcentric(self, radius): |
163 def lookupConcentric(self, radius): |
134 idx = 0 |
164 idx = 0 |
139 return len(self.concentric_circle_radii) |
169 return len(self.concentric_circle_radii) |
140 |
170 |
141 def getQuadrantConcentricFromPosition(self, pos): |
171 def getQuadrantConcentricFromPosition(self, pos): |
142 rel_x = pos[0] - self.center[0] |
172 rel_x = pos[0] - self.center[0] |
143 rel_y = pos[1] - self.center[1] |
173 rel_y = pos[1] - self.center[1] |
144 radius = math.sqrt(rel_x ** 2 + rel_y ** 2) |
|
145 if rel_x > rel_y and rel_x > -rel_y: |
174 if rel_x > rel_y and rel_x > -rel_y: |
146 quadrant = 0 # Right |
175 quadrant = 0 # Right |
147 elif rel_x <= rel_y and rel_x > -rel_y: |
176 elif rel_x <= rel_y and rel_x > -rel_y: |
148 quadrant = 3 # Down |
177 quadrant = 3 # Down |
149 elif rel_x > rel_y and rel_x < -rel_y: |
178 elif rel_x > rel_y and rel_x < -rel_y: |
150 quadrant = 1 # Up |
179 quadrant = 1 # Up |
151 else: |
180 else: |
152 quadrant = 2 # Left |
181 quadrant = 2 # Left |
153 |
182 |
|
183 radius = math.sqrt(rel_x ** 2 + rel_y ** 2) |
154 idx = self.lookupConcentric(radius) |
184 idx = self.lookupConcentric(radius) |
155 return (quadrant, idx) |
185 return (quadrant, idx) |
156 |
186 |
157 def mouseOverKeypad(self, mpos): |
187 def mouseOverKeypad(self, mpos): |
158 for idx, kpos in self.keypad_positions.items(): |
188 for idx, kpos in self.keypad_positions.items(): |
159 radius = self.distanceToPoint(mpos[0], mpos[1], kpos[0], kpos[1]) |
189 radius = self.distanceToPoint(mpos[0], mpos[1], kpos[0], kpos[1]) |
160 if radius < 9: |
190 if radius < XYButtons.keypad_radius: |
161 return idx |
191 return idx |
162 return None |
192 return None |
163 |
193 |
164 def drawPartialPie(self, gc, center, r1, r2, angle1, angle2): |
194 def drawPartialPie(self, gc, center, r1, r2, angle1, angle2): |
165 p1 = wx.Point(center.x + r1 * math.cos(angle1), center.y + r1 * math.sin(angle1)) |
195 p1 = wx.Point(center.x + r1 * math.cos(angle1), center.y + r1 * math.sin(angle1)) |
245 if self.bg_bmp: |
276 if self.bg_bmp: |
246 w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) |
277 w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) |
247 gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) |
278 gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) |
248 |
279 |
249 if self.enabled and self.IsEnabled(): |
280 if self.enabled and self.IsEnabled(): |
250 # Brush and pen for grey overlay when mouse hovers over |
281 gc.SetPen(wx.Pen(HOVER_PEN_COLOR, 4)) |
251 gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) |
282 gc.SetBrush(wx.Brush(HOVER_BRUSH_COLOR)) |
252 gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) |
|
253 |
283 |
254 if self.concentric is not None: |
284 if self.concentric is not None: |
255 if self.concentric < len(self.concentric_circle_radii): |
285 if self.concentric < len(self.concentric_circle_radii): |
256 if self.concentric == 0: |
286 if self.concentric == 0: |
257 self.drawCenteredDisc(gc, self.concentric_circle_radii[1]) |
287 self.drawCenteredDisc(gc, self.concentric_circle_radii[1]) |
263 if self.keypad_idx >= 0: |
293 if self.keypad_idx >= 0: |
264 padw, padh = (self.keypad_bmp.GetWidth(), self.keypad_bmp.GetHeight()) |
294 padw, padh = (self.keypad_bmp.GetWidth(), self.keypad_bmp.GetHeight()) |
265 pos = self.keypad_positions[self.keypad_idx] |
295 pos = self.keypad_positions[self.keypad_idx] |
266 pos = (pos[0] - padw / 2 - 3, pos[1] - padh / 2 - 3) |
296 pos = (pos[0] - padw / 2 - 3, pos[1] - padh / 2 - 3) |
267 gc.DrawBitmap(self.keypad_bmp, pos[0], pos[1], padw, padh) |
297 gc.DrawBitmap(self.keypad_bmp, pos[0], pos[1], padw, padh) |
|
298 |
|
299 if self.hovered_keypad is not None and self.hovered_keypad != self.keypad_idx: |
|
300 pos = self.keypad_positions[self.hovered_keypad] |
|
301 r = XYButtons.keypad_radius |
|
302 gc.DrawEllipse(pos[0]-r/2, pos[1]-r/2, r, r) |
268 |
303 |
269 # Draw label overlays |
304 # Draw label overlays |
270 gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) |
305 gc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 128), 1)) |
271 gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128 + 64))) |
306 gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255, 128 + 64))) |
272 for idx, kpos in self.label_overlay_positions.items(): |
307 for idx, kpos in self.label_overlay_positions.items(): |
275 gc.DrawEllipse(kpos[0] - r, kpos[1] - r, r * 2, r * 2) |
310 gc.DrawEllipse(kpos[0] - r, kpos[1] - r, r * 2, r * 2) |
276 else: |
311 else: |
277 gc.SetPen(wx.Pen(self.bgcolor, 0)) |
312 gc.SetPen(wx.Pen(self.bgcolor, 0)) |
278 gc.SetBrush(wx.Brush(self.bgcolormask)) |
313 gc.SetBrush(wx.Brush(self.bgcolormask)) |
279 gc.DrawRectangle(0, 0, w, h) |
314 gc.DrawRectangle(0, 0, w, h) |
|
315 |
|
316 self.drawFocusRect(dc) |
|
317 |
280 # Used to check exact position of keypad dots, should we ever resize the bg image |
318 # Used to check exact position of keypad dots, should we ever resize the bg image |
281 # for idx, kpos in self.label_overlay_positions.items(): |
319 # for idx, kpos in self.label_overlay_positions.items(): |
282 # dc.DrawCircle(kpos[0], kpos[1], kpos[2]) |
320 # dc.DrawCircle(kpos[0], kpos[1], kpos[2]) |
283 |
321 |
284 # ------ # |
322 # ------ # |
285 # Events # |
323 # Events # |
286 # ------ # |
324 # ------ # |
287 def OnTopLevelKey(self, evt): |
|
288 # Let user press escape on any control, and return focus here |
|
289 if evt.GetKeyCode() == wx.WXK_ESCAPE: |
|
290 self.SetFocus() |
|
291 evt.Skip() |
|
292 |
|
293 def OnKey(self, evt): |
325 def OnKey(self, evt): |
|
326 # print('XYButtons key', evt.GetKeyCode()) |
294 if not self.enabled: |
327 if not self.enabled: |
|
328 evt.Skip() |
295 return |
329 return |
|
330 key = evt.KeyCode |
296 if self.keypad_idx >= 0: |
331 if self.keypad_idx >= 0: |
297 if evt.GetKeyCode() == wx.WXK_TAB: |
332 if key == wx.WXK_TAB: |
298 self.setKeypadIndex(self.cycleKeypadIndex()) |
333 keypad = self.cycleKeypadIndex(not evt.ShiftDown()) |
299 elif evt.GetKeyCode() == wx.WXK_UP: |
334 self.setKeypadIndex(keypad) |
|
335 if keypad == -1: |
|
336 # exit widget after largest step |
|
337 # evt.Skip() |
|
338 # On MS Windows if tab event is delivered, |
|
339 # it is not handled |
|
340 self.Navigate(not evt.ShiftDown()) |
|
341 return |
|
342 elif key == wx.WXK_ESCAPE: |
|
343 self.setKeypadIndex(-1) |
|
344 elif key == wx.WXK_UP: |
300 self.quadrant = 1 |
345 self.quadrant = 1 |
301 elif evt.GetKeyCode() == wx.WXK_DOWN: |
346 elif key == wx.WXK_DOWN: |
302 self.quadrant = 3 |
347 self.quadrant = 3 |
303 elif evt.GetKeyCode() == wx.WXK_LEFT: |
348 elif key == wx.WXK_LEFT: |
304 self.quadrant = 2 |
349 self.quadrant = 2 |
305 elif evt.GetKeyCode() == wx.WXK_RIGHT: |
350 elif key == wx.WXK_RIGHT: |
306 self.quadrant = 0 |
351 self.quadrant = 0 |
307 elif evt.GetKeyCode() == wx.WXK_PAGEUP: |
352 elif key == wx.WXK_PAGEUP: |
308 self.quadrant = 4 |
353 self.quadrant = 4 |
309 elif evt.GetKeyCode() == wx.WXK_PAGEDOWN: |
354 elif key == wx.WXK_PAGEDOWN: |
310 self.quadrant = 5 |
355 self.quadrant = 5 |
311 else: |
356 else: |
312 evt.Skip() |
357 evt.Skip() |
313 return |
358 return |
314 |
359 |
315 self.concentric = self.keypad_idx |
360 self.concentric = self.keypad_idx + 1 |
316 x, y, z = self.getMovement() |
361 |
317 |
362 if self.quadrant is not None: |
318 if x != 0 or y != 0 and self.moveCallback: |
363 x, y, z = self.getMovement(evt) |
319 self.moveCallback(x, y) |
364 if (x or y) and self.moveCallback: |
320 if z != 0 and self.zCallback: |
365 self.moveCallback(x, y) |
321 self.zCallback(z) |
366 if z and self.zCallback: |
322 elif evt.GetKeyCode() == wx.WXK_SPACE: |
367 self.zCallback(z) |
|
368 self.Refresh() |
|
369 elif key == wx.WXK_SPACE: |
323 self.spacebarCallback() |
370 self.spacebarCallback() |
|
371 elif key == wx.WXK_TAB: |
|
372 self.setKeypadIndex(len(self.keypad_positions)-1 if evt.ShiftDown() else 0) |
|
373 else: |
|
374 # handle arrows elsewhere |
|
375 evt.Skip() |
324 |
376 |
325 def OnMotion(self, event): |
377 def OnMotion(self, event): |
326 if not self.enabled: |
378 if not self.enabled: |
327 return |
379 return |
328 |
380 |
329 oldcorner = self.corner |
381 oldcorner = self.corner |
330 oldq, oldc = self.quadrant, self.concentric |
382 oldq, oldc = self.quadrant, self.concentric |
|
383 old_hovered_keypad = self.hovered_keypad |
331 |
384 |
332 mpos = event.GetPosition() |
385 mpos = event.GetPosition() |
333 idx = self.mouseOverKeypad(mpos) |
386 self.hovered_keypad = self.mouseOverKeypad(mpos) |
334 self.quadrant = None |
387 self.quadrant = None |
335 self.concentric = None |
388 self.concentric = None |
336 if idx is None: |
389 if self.hovered_keypad is None: |
337 center = wx.Point(self.center[0], self.center[1]) |
390 center = wx.Point(self.center[0], self.center[1]) |
338 riseDist = self.distanceToLine(mpos, center.x - 1, center.y - 1, center.x + 1, center.y + 1) |
391 riseDist = self.distanceToLine(mpos, center.x - 1, center.y - 1, center.x + 1, center.y + 1) |
339 fallDist = self.distanceToLine(mpos, center.x - 1, center.y + 1, center.x + 1, center.y - 1) |
392 fallDist = self.distanceToLine(mpos, center.x - 1, center.y + 1, center.x + 1, center.y - 1) |
340 self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) |
393 self.quadrant, self.concentric = self.getQuadrantConcentricFromPosition(mpos) |
341 |
394 |
373 if self.concentric == 0: |
427 if self.concentric == 0: |
374 self.lastCorner = -1 |
428 self.lastCorner = -1 |
375 self.lastMove = None |
429 self.lastMove = None |
376 self.cornerCallback(self.corner_to_axis[-1]) |
430 self.cornerCallback(self.corner_to_axis[-1]) |
377 elif self.quadrant is not None: |
431 elif self.quadrant is not None: |
378 x, y, z = self.getMovement() |
432 x, y, z = self.getMovement(event) |
379 if self.moveCallback: |
433 if self.moveCallback: |
380 self.lastMove = (x, y) |
434 self.lastMove = (x, y) |
381 self.lastCorner = None |
435 self.lastCorner = None |
382 self.moveCallback(x, y) |
436 self.moveCallback(x, y) |
383 elif self.corner is not None: |
437 elif self.corner is not None: |
384 if self.cornerCallback: |
438 if self.cornerCallback: |
385 self.lastCorner = self.corner |
439 self.lastCorner = self.corner |
386 self.lastMove = None |
440 self.lastMove = None |
387 self.cornerCallback(self.corner_to_axis[self.corner]) |
441 self.cornerCallback(self.corner_to_axis[self.corner]) |
388 else: |
442 else: |
389 if self.keypad_idx == idx: |
443 self.setKeypadIndex(-1 if self.keypad_idx == idx else idx) |
390 self.setKeypadIndex(-1) |
|
391 else: |
|
392 self.setKeypadIndex(idx) |
|
393 |
444 |
394 def OnLeaveWindow(self, evt): |
445 def OnLeaveWindow(self, evt): |
395 self.quadrant = None |
446 self.quadrant = None |
396 self.concentric = None |
447 self.concentric = None |
397 self.update() |
448 self.update() |
484 if self.bg_bmp: |
535 if self.bg_bmp: |
485 w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) |
536 w, h = (self.bg_bmp.GetWidth(), self.bg_bmp.GetHeight()) |
486 gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) |
537 gc.DrawBitmap(self.bg_bmp, 0, 0, w, h) |
487 |
538 |
488 if self.enabled and self.IsEnabled(): |
539 if self.enabled and self.IsEnabled(): |
489 # Brush and pen for grey overlay when mouse hovers over |
540 gc.SetPen(wx.Pen(HOVER_PEN_COLOR, 4)) |
490 gc.SetPen(wx.Pen(wx.Colour(100, 100, 100, 172), 4)) |
541 gc.SetBrush(wx.Brush(HOVER_BRUSH_COLOR)) |
491 gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0, 128))) |
|
492 |
542 |
493 if self.concentric is not None: |
543 if self.concentric is not None: |
494 if self.concentric < len(self.concentric_circle_radii): |
544 if self.concentric < len(self.concentric_circle_radii): |
495 self.drawCenteredDisc(gc, self.concentric_circle_radii[-1]) |
545 self.drawCenteredDisc(gc, self.concentric_circle_radii[-1]) |
496 elif self.corner is not None: |
546 elif self.corner is not None: |