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