]> git.saurik.com Git - wxWidgets.git/blob - wxPython/demo/Joystick.py
Send an extra size event after creation to work around an apparent bug
[wxWidgets.git] / wxPython / demo / Joystick.py
1 #----------------------------------------------------------------------------
2 # Name: Joystick.py
3 # Purpose: Demonstrate use of wx.Joystick
4 #
5 # Author: Jeff Grimmett (grimmtoo@softhome.net), adapted from original
6 # .wdr-derived demo
7 #
8 # Created: 01/02/04
9 # RCS-ID: $Id$
10 # Copyright:
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
13 #
14
15 import math
16 import wx
17
18 #----------------------------------------------------------------------------
19
20 # Once all supported versions of Python support 32-bit integers on all
21 # platforms, this can go up to 32.
22 MAX_BUTTONS = 16
23
24 #----------------------------------------------------------------------------
25
26 class Label(wx.StaticText):
27 # A derived StaticText that always aligns right and renders
28 # in a bold font.
29 def __init__(self, parent, label):
30 wx.StaticText.__init__(self, parent, -1, label, style=wx.ALIGN_RIGHT)
31
32 self.SetFont(
33 wx.Font(
34 parent.GetFont().GetPointSize(),
35 parent.GetFont().GetFamily(),
36 parent.GetFont().GetStyle(),
37 wx.BOLD
38 ))
39
40 #----------------------------------------------------------------------------
41
42
43 class JoyGauge(wx.Panel):
44 def __init__(self, parent, stick):
45
46 self.stick = stick
47 size = (100,100)
48
49 wx.Panel.__init__(self, parent, -1, size=size)
50
51 self.Bind(wx.EVT_PAINT, self.OnPaint)
52 self.Bind(wx.EVT_SIZE, self.OnSize)
53 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
54
55 self.buffer = wx.EmptyBitmap(*size)
56 dc = wx.BufferedDC(None, self.buffer)
57 self.DrawFace(dc)
58 self.DrawJoystick(dc)
59
60
61 def OnSize(self, event):
62 # The face Bitmap init is done here, to make sure the buffer is always
63 # the same size as the Window
64 w, h = self.GetClientSize()
65 self.buffer = wx.EmptyBitmap(w,h)
66 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
67 self.DrawFace(dc)
68 self.DrawJoystick(dc)
69
70
71 def DrawFace(self, dc):
72 dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
73 dc.Clear()
74
75
76 def OnPaint(self, evt):
77 # When dc is destroyed it will blit self.buffer to the window,
78 # since no other drawing is needed we'll just return and let it
79 # do it's thing
80 dc = wx.BufferedPaintDC(self, self.buffer)
81
82
83 def DrawJoystick(self, dc):
84 # draw the guage as a maxed square in the center of this window.
85 w, h = self.GetClientSize()
86 edgeSize = min(w, h)
87
88 xorigin = (w - edgeSize) / 2
89 yorigin = (h - edgeSize) / 2
90 center = edgeSize / 2
91
92 # Restrict our drawing activities to the square defined
93 # above.
94 dc.SetClippingRegion((xorigin, yorigin), (edgeSize, edgeSize))
95
96 # Optimize drawing a bit (for Win)
97 dc.BeginDrawing()
98
99 dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237)))
100 dc.DrawRectangle((xorigin, yorigin), (edgeSize, edgeSize))
101
102 dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
103
104 dc.DrawLine((xorigin, yorigin + center), (xorigin + edgeSize, yorigin + center))
105 dc.DrawLine((xorigin + center, yorigin), (xorigin + center, yorigin + edgeSize))
106
107 if self.stick:
108 # Get the joystick position as a float
109 joyx = float(self.stick.GetPosition().x)
110 joyy = float(self.stick.GetPosition().y)
111
112 # Get the joystick range of motion
113 xrange = self.stick.GetXMax() - self.stick.GetXMin()
114 yrange = self.stick.GetYMax() - self.stick.GetYMin()
115
116 # calc a ratio of our range versus the joystick range
117 xratio = float(edgeSize) / xrange
118 yratio = float(edgeSize) / yrange
119
120 # calc the displayable value based on position times ratio
121 xval = int(joyx * xratio)
122 yval = int(joyy * xratio)
123
124 # and normalize the value from our brush's origin
125 x = xval + xorigin
126 y = yval + yorigin
127
128 # Now to draw it.
129 dc.SetPen(wx.Pen(wx.RED, 2))
130 dc.CrossHair((x, y))
131
132 # Turn off drawing optimization
133 dc.EndDrawing()
134
135
136 def Update(self):
137 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
138 self.DrawFace(dc)
139 self.DrawJoystick(dc)
140
141
142 #----------------------------------------------------------------------------
143
144 class JoyPanel(wx.Panel):
145 def __init__(self, parent, stick):
146
147 self.stick = stick
148
149 wx.Panel.__init__(self, parent, -1)
150
151 sizer = wx.BoxSizer(wx.VERTICAL)
152
153 fn = wx.Font(
154 parent.GetFont().GetPointSize() + 3,
155 parent.GetFont().GetFamily(),
156 parent.GetFont().GetStyle(),
157 wx.BOLD
158 )
159
160 t = wx.StaticText(self, -1, "X - Y Axes", style = wx.ALIGN_CENTRE)
161 t.SetFont(fn)
162 sizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
163
164 self.control = JoyGauge(self, self.stick)
165 sizer.Add(self.control, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
166
167 self.SetSizer(sizer)
168 sizer.Fit(self)
169
170 def Update(self):
171 self.control.Update()
172
173
174 #----------------------------------------------------------------------------
175
176 class POVGauge(wx.Panel):
177 #
178 # Display the current postion of the POV control
179 #
180 def __init__(self, parent, stick):
181
182 self.stick = stick
183 self.size = (100, 100)
184 self.avail = False
185 self.fourDir = False
186 self.cts = False
187
188 wx.Panel.__init__(self, parent, -1, size=self.size)
189
190 self.Bind(wx.EVT_PAINT, self.OnPaint)
191 self.Bind(wx.EVT_SIZE, self.OnSize)
192 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
193
194 self.buffer = wx.EmptyBitmap(*self.size)
195 dc = wx.BufferedDC(None, self.buffer)
196 self.DrawFace(dc)
197 self.DrawPOV(dc)
198
199
200 def OnSize(self, event):
201 # calculate the size of our display and make a buffer for it.
202 w, h = self.GetClientSize()
203 s = min(w, h)
204 self.size = (s, s)
205 self.buffer = wx.EmptyBitmap(w,h)
206 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
207 self.DrawFace(dc)
208 self.DrawPOV(dc)
209
210
211 def DrawFace(self, dc):
212 dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
213 dc.Clear()
214
215
216 def OnPaint(self, evt):
217 # When dc is destroyed it will blit self.buffer to the window,
218 # since no other drawing is needed we'll just return and let it
219 # do it's thing
220 dc = wx.BufferedPaintDC(self, self.buffer)
221
222
223 def DrawPOV(self, dc):
224 # draw the guage as a maxed circle in the center of this window.
225 w, h = self.GetClientSize()
226 diameter = min(w, h)
227
228 xorigin = (w - diameter) / 2
229 yorigin = (h - diameter) / 2
230 xcenter = xorigin + diameter / 2
231 ycenter = yorigin + diameter / 2
232
233 # Optimize drawing a bit (for Win)
234 dc.BeginDrawing()
235
236 # our 'raster'.
237 dc.SetBrush(wx.Brush(wx.WHITE))
238 dc.DrawCircle((xcenter, ycenter), diameter/2)
239 dc.SetBrush(wx.Brush(wx.BLACK))
240 dc.DrawCircle((xcenter, ycenter), 10)
241
242 # fancy decorations
243 dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
244 dc.DrawLine((xorigin, ycenter), (xorigin + diameter, ycenter))
245 dc.DrawLine((xcenter, yorigin), (xcenter, yorigin + diameter))
246
247 if self.stick:
248 if self.avail:
249
250 pos = -1
251
252 # use the appropriate function to get the POV position
253 if self.fourDir:
254 pos = self.stick.GetPOVPosition()
255
256 if self.cts:
257 pos = self.stick.GetPOVCTSPosition()
258
259 # trap invalid values
260 if 0 <= pos <= 36000:
261 vector = 30
262 else:
263 vector = 0
264
265 # rotate CCW by 90 so that 0 is up.
266 pos = (pos / 100) - 90
267
268 # Normalize
269 if pos < 0:
270 pos = pos + 360
271
272 # Stolen from wx.lib.analogclock :-)
273 radiansPerDegree = math.pi / 180
274 pointX = int(round(vector * math.cos(pos * radiansPerDegree)))
275 pointY = int(round(vector * math.sin(pos * radiansPerDegree)))
276
277 # normalise value to match our actual center.
278 nx = pointX + xcenter
279 ny = pointY + ycenter
280
281 # Draw the line
282 dc.SetPen(wx.Pen(wx.BLUE, 2))
283 dc.DrawLine((xcenter, ycenter), (nx, ny))
284
285 # And a little thing to show the endpoint
286 dc.SetBrush(wx.Brush(wx.BLUE))
287 dc.DrawCircle((nx, ny), 8)
288
289 # Turn off drawing optimization
290 dc.EndDrawing()
291
292
293 def Update(self):
294 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
295 self.DrawFace(dc)
296 self.DrawPOV(dc)
297
298
299 def Calibrate(self):
300 s = self.stick
301 self.avail = s.HasPOV()
302 self.fourDir = s.HasPOV4Dir()
303 self.cts = s.HasPOVCTS()
304
305
306 #----------------------------------------------------------------------------
307
308 class POVStatus(wx.Panel):
309 #
310 # Displays static info about the POV control
311 #
312 def __init__(self, parent, stick):
313
314 self.stick = stick
315
316 wx.Panel.__init__(self, parent, -1, size=(100, 100))
317
318 sizer = wx.BoxSizer(wx.VERTICAL)
319 sizer.Add((20,20))
320
321 self.avail = wx.CheckBox(self, -1, "Available")
322 sizer.Add(self.avail, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
323
324 self.fourDir = wx.CheckBox(self, -1, "4-Way Only")
325 sizer.Add(self.fourDir, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
326
327 self.cts = wx.CheckBox(self, -1, "Continuous")
328 sizer.Add(self.cts, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
329
330 self.SetSizer(sizer)
331 sizer.Fit(self)
332
333 # Effectively makes the checkboxes read-only.
334 self.Bind(wx.EVT_CHECKBOX, self.Calibrate)
335
336
337 def Calibrate(self, evt=None):
338 s = self.stick
339 self.avail.SetValue(s.HasPOV())
340 self.fourDir.SetValue(s.HasPOV4Dir())
341 self.cts.SetValue(s.HasPOVCTS())
342
343
344 #----------------------------------------------------------------------------
345
346 class POVPanel(wx.Panel):
347 def __init__(self, parent, stick):
348
349 self.stick = stick
350
351 wx.Panel.__init__(self, parent, -1, size=(100, 100))
352
353 sizer = wx.BoxSizer(wx.HORIZONTAL)
354 gsizer = wx.BoxSizer(wx.VERTICAL)
355
356 sizer.Add((25,25))
357
358 fn = wx.Font(
359 parent.GetFont().GetPointSize() + 3,
360 parent.GetFont().GetFamily(),
361 parent.GetFont().GetStyle(),
362 wx.BOLD
363 )
364 t = wx.StaticText(self, -1, "POV Control", style = wx.ALIGN_CENTER)
365 t.SetFont(fn)
366 gsizer.Add(t, 0, wx.ALL | wx.EXPAND, 1)
367
368 self.display = POVGauge(self, stick)
369 gsizer.Add(self.display, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
370 sizer.Add(gsizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
371
372 self.status = POVStatus(self, stick)
373 sizer.Add(self.status, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
374
375 self.SetSizer(sizer)
376 sizer.Fit(self)
377
378
379 def Calibrate(self):
380 self.display.Calibrate()
381 self.status.Calibrate()
382
383
384 def Update(self):
385 self.display.Update()
386
387
388 #----------------------------------------------------------------------------
389
390 class LED(wx.Panel):
391 def __init__(self, parent, number):
392
393 self.state = -1
394 self.size = (20, 20)
395 self.number = number
396
397 self.fn = wx.Font(
398 parent.GetFont().GetPointSize() - 1,
399 parent.GetFont().GetFamily(),
400 parent.GetFont().GetStyle(),
401 wx.BOLD
402 )
403
404 wx.Panel.__init__(self, parent, -1, size=self.size)
405
406 self.Bind(wx.EVT_PAINT, self.OnPaint)
407 self.Bind(wx.EVT_SIZE, self.OnSize)
408 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
409
410 self.buffer = wx.EmptyBitmap(*self.size)
411 dc = wx.BufferedDC(None, self.buffer)
412 self.DrawFace(dc)
413 self.DrawLED(dc)
414
415
416 def OnSize(self, event):
417 # calculate the size of our display.
418 w, h = self.GetClientSize()
419 s = min(w, h)
420 self.size = (s, s)
421 self.buffer = wx.EmptyBitmap(*self.size)
422 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
423 self.DrawFace(dc)
424 self.DrawLED(dc)
425
426
427 def DrawFace(self, dc):
428 dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
429 dc.Clear()
430
431
432 def OnPaint(self, evt):
433 # When dc is destroyed it will blit self.buffer to the window,
434 # since no other drawing is needed we'll just return and let it
435 # do it's thing
436 dc = wx.BufferedPaintDC(self, self.buffer)
437
438
439 def DrawLED(self, dc):
440 # bitmap size
441 bw, bh = self.size
442
443 # center of bitmap
444 center = bw / 2
445
446 # calc the 0, 0 origin of the bitmap
447 xorigin = center - (bw / 2)
448 yorigin = center - (bh / 2)
449
450 # Optimize drawing a bit (for Win)
451 dc.BeginDrawing()
452
453 # our 'raster'.
454 if self.state == 0:
455 dc.SetBrush(wx.Brush(wx.RED))
456 elif self.state == 1:
457 dc.SetBrush(wx.Brush(wx.GREEN))
458 else:
459 dc.SetBrush(wx.Brush(wx.BLACK))
460
461 dc.DrawCircle((center, center), bw/2)
462
463 txt = str(self.number)
464
465 # Set the font for the DC ...
466 dc.SetFont(self.fn)
467 # ... and calculate how much space our value
468 # will take up.
469 fw, fh = dc.GetTextExtent(txt)
470
471 # Calc the center of the LED, and from that
472 # derive the origin of our value.
473 tx = center - (fw/2)
474 ty = center - (fh/2)
475
476 # I draw the value twice so as to give it a pseudo-shadow.
477 # This is (mostly) because I'm too lazy to figure out how
478 # to blit my text onto the gauge using one of the logical
479 # functions. The pseudo-shadow gives the text contrast
480 # regardless of whether the bar is under it or not.
481 dc.SetTextForeground(wx.WHITE)
482 dc.DrawText(txt, (tx, ty))
483
484 # Turn off drawing optimization
485 dc.EndDrawing()
486
487
488 def Update(self):
489 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
490 self.DrawFace(dc)
491 self.DrawLED(dc)
492
493
494 #----------------------------------------------------------------------------
495
496 class JoyButtons(wx.Panel):
497 def __init__(self, parent, stick):
498
499 self.stick = stick
500 self.leds = {}
501
502 wx.Panel.__init__(self, parent, -1)
503
504 tsizer = wx.BoxSizer(wx.VERTICAL)
505
506 fn = wx.Font(
507 parent.GetFont().GetPointSize() + 3,
508 parent.GetFont().GetFamily(),
509 parent.GetFont().GetStyle(),
510 wx.BOLD
511 )
512
513 t = wx.StaticText(self, -1, "Buttons", style = wx.ALIGN_LEFT)
514 t.SetFont(fn)
515 tsizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1)
516
517 sizer = wx.FlexGridSizer(4, 16, 2, 2)
518
519 fn.SetPointSize(parent.GetFont().GetPointSize() + 1)
520
521 for i in range(0, MAX_BUTTONS):
522 t = LED(self, i)
523 self.leds[i] = t
524 sizer.Add(t, 1, wx.ALL|wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL, 1)
525 sizer.AddGrowableCol(i)
526
527 tsizer.Add(sizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1)
528
529 self.SetSizer(tsizer)
530 tsizer.Fit(self)
531
532 def Calibrate(self):
533 for i in range(0, MAX_BUTTONS):
534 self.leds[i].state = -1
535
536 t = self.stick.GetNumberButtons()
537
538 for i in range(0, t):
539 self.leds[i].state = 0
540
541 def Update(self):
542 t = self.stick.GetButtonState()
543
544 for i in range(0, MAX_BUTTONS):
545 if self.leds[i].state == 1:
546 self.leds[i].state = 0
547
548 if (t & (1<<i)):
549 self.leds[i].state = 1
550
551 self.leds[i].Update()
552
553
554 #----------------------------------------------------------------------------
555
556 class InfoPanel(wx.Panel):
557 def __init__(self, parent, stick):
558
559 self.stick = stick
560
561 wx.Panel.__init__(self, parent, -1)
562
563 sizer = wx.GridBagSizer(1, 1)
564
565 sizer.Add(Label(self, 'Mfr ID: '), (0, 0), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
566 self.MfgID = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
567 sizer.Add(self.MfgID, (0, 1), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
568
569 sizer.Add(Label(self, 'Prod Name: '), (0, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
570 self.ProdName = wx.TextCtrl(self, -1, value='', style=wx.TE_READONLY)
571 sizer.Add(self.ProdName, (0, 3), (1, 3), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
572
573 sizer.Add(Label(self, 'Threshold: '), (0, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
574 self.Threshold = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
575 sizer.Add(self.Threshold, (0, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
576
577 #----------------------------------------------------------------------------
578 b = wx.Button(self, -1, "Calibrate")
579 sizer.Add(b, (1, 0), (2, 2), wx.ALL | wx.ALIGN_CENTER, 2)
580
581 sizer.Add(Label(self, '# of Sticks: '), (1, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
582 self.NumJoysticks = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
583 sizer.Add(self.NumJoysticks, (1, 3), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
584
585 sizer.Add(Label(self, '# of Axes: '), (1, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
586 self.NumAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
587 sizer.Add(self.NumAxis, (1, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
588
589 sizer.Add(Label(self, 'Max # Axes: '), (1, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
590 self.MaxAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
591 sizer.Add(self.MaxAxis, (1, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
592
593 #----------------------------------------------------------------------------
594
595 sizer.Add(Label(self, 'Polling -- '), (2, 3), (1, 1), wx.ALL | wx.GROW, 2)
596
597 sizer.Add(Label(self, 'Min: '), (2, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
598 self.PollMin = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
599 sizer.Add(self.PollMin, (2, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
600
601 sizer.Add(Label(self, 'Max: '), (2, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
602 self.PollMax = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
603 sizer.Add(self.PollMax, (2, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
604
605 #----------------------------------------------------------------------------
606
607 self.SetSizer(sizer)
608 sizer.Fit(self)
609
610
611 def Calibrate(self):
612 if not self.stick:
613 return
614
615 s = self.stick
616
617 self.MfgID.SetValue(str(s.GetManufacturerId()))
618 self.ProdName.SetValue(str(s.GetProductName()))
619 self.Threshold.SetValue(str(s.GetMovementThreshold()))
620 self.NumJoysticks.SetValue(str(s.GetNumberJoysticks()))
621 self.NumAxis.SetValue(str(s.GetNumberAxes()))
622 self.MaxAxis.SetValue(str(s.GetMaxAxes()))
623 self.PollMin.SetValue(str(s.GetPollingMin()))
624 self.PollMax.SetValue(str(s.GetPollingMax()))
625
626
627 #----------------------------------------------------------------------------
628
629 class AxisBar(wx.Gauge):
630 #
631 # This class allows us to use a wx.Gauge to display the axis value
632 # with a fancy label overlayed onto the guage itself. Two values are
633 # used to do things: first of all, since the gauge is limited to
634 # positive numbers, the scale is fixed at 0 to 1000. We will receive
635 # an adjusted value to use to render the gauge itself. The other value
636 # is a raw value and actually reflects the value from the joystick itself,
637 # which is then drawn over the gauge.
638 #
639 def __init__(self, parent):
640 wx.Gauge.__init__(self, parent, -1, 1000, size=(-1, 20), style = wx.GA_HORIZONTAL | wx.GA_SMOOTH )
641
642 # This is the value we will display.
643 self.rawvalue = 0
644
645 self.SetBackgroundColour('light blue')
646 self.SetForegroundColour('orange')
647
648 # Capture paint events for purpose of updating
649 # the displayed value.
650 self.Bind(wx.EVT_PAINT, self.onPaint)
651
652 def Update(self, value, rawvalue):
653 # Updates the gauge itself, sets the raw value for
654 # the next EVT_PAINT
655 self.SetValue(value)
656 self.rawvalue = rawvalue
657
658 def onPaint(self, evt):
659 # Must always create a PaintDC when capturing
660 # an EVT_PAINT event
661 self.ShowValue(wx.PaintDC(self), evt)
662
663 def ShowValue(self, dc, evt):
664 # This method handles actual painting of and drawing
665 # on the gauge.
666
667 # Clear out the gauge
668 dc.Clear()
669 # and then carry out business as usual
670 wx.Gauge.OnPaint(self, evt)
671
672 # This is the size available to us.
673 w, h = dc.GetSize()
674
675 # This is what we will overlay on the gauge.
676 # It reflects the actual value received from the
677 # wx.Joystick.
678 txt = str(self.rawvalue)
679
680 # Copy the default font, make it bold.
681 fn = wx.Font(
682 self.GetFont().GetPointSize(),
683 self.GetFont().GetFamily(),
684 self.GetFont().GetStyle(),
685 wx.BOLD
686 )
687
688 # Set the font for the DC ...
689 dc.SetFont(fn)
690 # ... and calculate how much space our value
691 # will take up.
692 fw, fh = dc.GetTextExtent(txt)
693
694 # Calc the center of the gauge, and from that
695 # derive the origin of our value.
696 center = w / 2
697 tx = center - (fw/2)
698
699 center = h / 2
700 ty = center - (fh/2)
701
702 # I draw the value twice so as to give it a pseudo-shadow.
703 # This is (mostly) because I'm too lazy to figure out how
704 # to blit my text onto the gauge using one of the logical
705 # functions. The pseudo-shadow gives the text contrast
706 # regardless of whether the bar is under it or not.
707 dc.SetTextForeground(wx.BLACK)
708 dc.DrawText(txt, (tx, ty))
709
710 dc.SetTextForeground('white')
711 dc.DrawText(txt, (tx-1, ty-1))
712
713
714 #----------------------------------------------------------------------------
715
716 class Axis(wx.Panel):
717 #
718 # This class is a container for the min, max, and current
719 # values of the joystick axis in question. It contains
720 # also special features to render a 'dummy' if the axis
721 # in question is not available.
722 #
723 def __init__(self, parent, token, stick):
724
725 self.stick = stick
726
727 #
728 # token represents the type of axis we're displaying.
729 #
730 self.token = token
731
732 #
733 # Create a call to the 'Has*()' method for the stick.
734 # X and Y are always there, so we tie the Has* method
735 # to a hardwired True value.
736 #
737 if token not in ['X', 'Y']:
738 self.HasFunc = eval('stick.Has%s' % token)
739 else:
740 self.HasFunc = self.alwaysTrue
741
742 # Now init the panel.
743 wx.Panel.__init__(self, parent, -1)
744
745 sizer = wx.BoxSizer(wx.HORIZONTAL)
746
747 if self.HasFunc():
748 #
749 # Tie our calibration functions to the appropriate
750 # stick method. If we don't have the axis in question,
751 # we won't need them.
752 #
753 self.GetMin = eval('stick.Get%sMin' % token)
754 self.GetMax = eval('stick.Get%sMax' % token)
755
756 # Create our displays and set them up.
757 self.Min = wx.StaticText(self, -1, str(self.GetMin()),
758 size=(40,-1), style=wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE)
759 self.Max = wx.StaticText(self, -1, str(self.GetMax()),
760 size=(40,-1), style=wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE)
761 self.bar = AxisBar(self)
762
763 sizer.Add(self.Min, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 1)
764 sizer.Add(self.bar, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
765 sizer.Add(self.Max, 0, wx.ALL | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 1)
766
767 else:
768 # We go here if the axis in question is not available.
769 self.control = wx.StaticText(self, -1, ' *** Not Present ***')
770 sizer.Add(self.control, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
771
772 #----------------------------------------------------------------------------
773
774 self.SetSizer(sizer)
775 sizer.Fit(self)
776 wx.CallAfter(self.Update)
777
778
779 def Calibrate(self):
780 if not self.HasFunc():
781 return
782
783 self.Min.SetLabel(str(self.GetMin()))
784 self.Max.SetLabel(str(self.GetMax()))
785
786
787 def Update(self):
788 # Don't bother if the axis doesn't exist.
789 if not self.HasFunc():
790 return
791
792 min = int(self.Min.GetLabel())
793 max = int(self.Max.GetLabel())
794
795 #
796 # Not all values are available from a wx.JoystickEvent, so I've elected
797 # to not use it at all. Therefore, we are getting our values direct from
798 # the stick. These values also seem to be more stable and reliable than
799 # those received from the event itself, so maybe it's a good idea to
800 # use the stick directly for your program.
801 #
802 # Here we either select the appropriate member of stick.GetPosition() or
803 # apply the appropriate Get*Position method call.
804 #
805 if self.token == 'X':
806 val = self.stick.GetPosition().x
807 elif self.token == 'Y':
808 val = self.stick.GetPosition().y
809 else:
810 val = eval('self.stick.Get%sPosition()' % self.token)
811
812 #
813 # While we might be able to rely on a range of 0-FFFFFF on Win, that might
814 # not be true of all drivers on all platforms. Thus, calc the actual full
815 # range first.
816 #
817 range = float(max - min)
818
819 #
820 # The relative value is used by the derived wx.Gauge since it is a
821 # positive-only control.
822 #
823 relative = 0
824 if range:
825 relative = int(val / range * 1000)
826
827 #
828 # Pass both the raw and relative values to the derived Gauge
829 #
830 self.bar.Update(relative, val)
831
832
833 def alwaysTrue(self):
834 # a dummy method used for X and Y axis.
835 return True
836
837
838 #----------------------------------------------------------------------------
839
840 class AxisPanel(wx.Panel):
841 #
842 # Contained herein is a panel that offers a graphical display
843 # of the levels for all axes supported by wx.Joystick. If
844 # your system doesn't have a particular axis, it will be
845 # 'dummied' for transparent use.
846 #
847 def __init__(self, parent, stick):
848
849 self.stick = stick
850
851 # Defines labels and 'tokens' to identify each
852 # supporte axis.
853 axesList = [
854 ('X Axis ', 'X'), ('Y Axis ', 'Y'),
855 ('Z Axis ', 'Z'), ('Rudder ', 'Rudder'),
856 ('U Axis ', 'U'), ('V Axis ', 'V')
857 ]
858
859 # Contains a list of all axis initialized.
860 self.axes = []
861
862 wx.Panel.__init__(self, parent, -1)
863
864 sizer = wx.FlexGridSizer(3, 4, 1, 1)
865 sizer.AddGrowableCol(1)
866 sizer.AddGrowableCol(3)
867
868 #----------------------------------------------------------------------------
869
870 # Go through the list of labels and tokens and add a label and
871 # axis display to the sizer for each.
872 for label, token in axesList:
873 sizer.Add(Label(self, label), 0, wx.ALL | wx.ALIGN_RIGHT, 2)
874 t = Axis(self, token, self.stick)
875 self.axes.append(t)
876 sizer.Add(t, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
877
878 #----------------------------------------------------------------------------
879
880 self.SetSizer(sizer)
881 sizer.Fit(self)
882 wx.CallAfter(self.Update)
883
884 def Calibrate(self):
885 for i in self.axes:
886 i.Calibrate()
887
888 def Update(self):
889 for i in self.axes:
890 i.Update()
891
892
893 #----------------------------------------------------------------------------
894
895 class JoystickDemoPanel(wx.Panel):
896
897 def __init__(self, parent, log):
898
899 self.log = log
900
901 wx.Panel.__init__(self, parent, -1)
902
903 # Try to grab the control. If we get it, capture the stick.
904 # Otherwise, throw up an exception message and play stupid.
905 try:
906 self.stick = wx.Joystick()
907 self.stick.SetCapture(self)
908 # Calibrate our controls
909 wx.CallAfter(self.Calibrate)
910 wx.CallAfter(self.OnJoystick)
911 except NotImplementedError, v:
912 wx.MessageBox(str(v), "Exception Message")
913 self.stick = None
914
915 # One Sizer to Rule Them All...
916 sizer = wx.GridBagSizer(2,2)
917
918 self.info = InfoPanel(self, self.stick)
919 sizer.Add(self.info, (0, 0), (1, 3), wx.ALL | wx.GROW, 2)
920
921 self.info.Bind(wx.EVT_BUTTON, self.Calibrate)
922
923 self.joy = JoyPanel(self, self.stick)
924 sizer.Add(self.joy, (1, 0), (1, 1), wx.ALL | wx.GROW, 2)
925
926 self.pov = POVPanel(self, self.stick)
927 sizer.Add(self.pov, (1, 1), (1, 2), wx.ALL | wx.GROW, 2)
928
929 self.axes = AxisPanel(self, self.stick)
930 sizer.Add(self.axes, (2, 0), (1, 3), wx.ALL | wx.GROW, 2)
931
932 self.buttons = JoyButtons(self, self.stick)
933 sizer.Add(self.buttons, (3, 0), (1, 3), wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
934
935 self.SetSizer(sizer)
936 sizer.Fit(self)
937
938 # Capture Joystick events (if they happen)
939 self.Bind(wx.EVT_JOYSTICK_EVENTS, self.OnJoystick)
940
941 self.stick.SetMovementThreshold(10)
942
943 def Calibrate(self, evt=None):
944 # Do not try this without a stick
945 if not self.stick:
946 return
947
948 self.info.Calibrate()
949 self.axes.Calibrate()
950 self.pov.Calibrate()
951 self.buttons.Calibrate()
952
953 def OnJoystick(self, evt=None):
954 if not self.stick:
955 return
956
957 self.axes.Update()
958 self.joy.Update()
959 self.pov.Update()
960 self.buttons.Update()
961
962
963 #----------------------------------------------------------------------------
964
965 def runTest(frame, nb, log):
966 win = JoystickDemoPanel(nb, log)
967 return win
968
969 #----------------------------------------------------------------------------
970
971 overview = """\
972 <html>
973 <body>
974 <h1>wx.Joystick</h1>
975 This demo illustrates the use of the wx.Joystick class, which is an interface to
976 one or more joysticks attached to your system.
977
978 <p>The data that can be retrieved from the joystick comes in four basic flavors.
979 All of these are illustrated in the demo. In fact, this demo illustrates everything
980 you <b>can</b> get from the wx.Joystick control.
981
982 <ul>
983 <li>Static information such as Manufacturer ID and model name,
984 <li>Analog input from up to six axes, including X and Y for the actual stick,
985 <li>Button input from the fire button and any other buttons that the stick has,
986 <li>and the POV control (a kind of mini-joystick on top of the joystick) that many sticks come with.
987 </ul>
988
989 <p>Getting data from the joystick can be event-driven thanks to four event types associated
990 with wx.JoystickEvent, or the joystick can be polled programatically to get data on
991 a regular basis.
992
993 <h2>Data types</h2>
994
995 Data from the joystick comes in two flavors: that which defines the boundaries, and that
996 which defines the current state of the stick. Thus, we have Get*Max() and Get*Min()
997 methods for all axes, the max number of axes, the max number of buttons, and so on. In
998 general, this data can be read once and stored to speed computation up.
999
1000 <h3>Analog Input</h3>
1001
1002 Analog input (the axes) is delivered as a whole, positive number. If you need to know
1003 if the axis is at zero (centered) or not, you will first have to calculate that center
1004 based on the max and min values. The demo shows a bar graph for each axis expressed
1005 in native numerical format, plus a 'centered' X-Y axis compass showing the relationship
1006 of that input to the calculated stick position.
1007
1008 Analog input may be jumpy and spurious, so the control has a means of 'smoothing' the
1009 analog data by setting a movement threshold. This demo sets the threshold to 10, but
1010 you can set it at any valid value between the min and max.
1011
1012 <h3>Button Input</h3>
1013
1014 Button state is retrieved as one int that contains each button state mapped to a bit.
1015 You get the state of a button by AND-ing its bit against the returned value, in the form
1016
1017 <pre>
1018 # assume buttonState is what the stick returned, and buttonBit
1019 # is the bit you want to examine
1020
1021 if (buttonState & ( 1 &lt;&lt; buttonBit )) :
1022 # button pressed, do something with it
1023 </pre>
1024
1025 <p>The problem here is that some OSs return a 32-bit value for up to 32 buttons
1026 (imagine <i>that</i> stick!). Python V2.3 will generate an exception for bit
1027 values over 30. For that reason, this demo is limited to 16 buttons.
1028
1029 <p>Note that more than one button can be pressed at a time, so be sure to check all of them!
1030
1031
1032 <h3>POV Input</h3>
1033
1034 POV hats come in two flavors: four-way, and continuous. four-way POVs are restricted to
1035 the cardinal points of the compass; continuous, or CTS POV hats can deliver input in
1036 .01 degree increments, theoreticaly. The data is returned as a whole number; the last
1037 two digits are considered to be to the right of the decimal point, so in order to
1038 use this information, you need to divide by 100 right off the bat.
1039
1040 <p>Different methods are provided to retrieve the POV data for a CTS hat
1041 versus a four-way hat.
1042
1043 <h2>Caveats</h2>
1044
1045 The wx.Joystick control is in many ways incomplete at the C++ library level, but it is
1046 not insurmountable. In short, while the joystick interface <i>can</i> be event-driven,
1047 the wx.JoystickEvent class lacks event binders for all event types. Thus, you cannot
1048 rely on wx.JoystickEvents to tell you when something has changed, necessarilly.
1049
1050 <ul>
1051 <li>There are no events associated with the POV control.
1052 <li>There are no events associated with the Rudder
1053 <li>There are no events associated with the U and V axes.
1054 </ul>
1055
1056 <p>Fortunately, there is an easy workaround. In the top level frame, create a wx.Timer
1057 that will poll the stick at a set interval. Of course, if you do this, you might as
1058 well forgo catching wxEVT_JOYSTICK_* events at all and rely on the timer to do the
1059 polling.
1060
1061 <p>Ideally, the timer should be a one-shot; after it fires, collect and process data as
1062 needed, then re-start the timer, possibly using wx.CallAfter().
1063
1064 </body>
1065 </html>
1066 """
1067
1068 #----------------------------------------------------------------------------
1069
1070 if __name__ == '__main__':
1071 import sys,os
1072 import run
1073 run.main(['', os.path.basename(sys.argv[0])])