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