]> git.saurik.com Git - wxWidgets.git/blame - wxPython/demo/Joystick.py
Don't decref if the interpreter is not initialized, (eg. it's in its
[wxWidgets.git] / wxPython / demo / Joystick.py
CommitLineData
2421eb82
RD
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#
08b89fa8 8# Created: 02-Jan-2004
2421eb82
RD
9# RCS-ID: $Id$
10# Copyright:
11# Licence: wxWindows license
12#----------------------------------------------------------------------------
13#
14
15import math
16import wx
ac346f50 17
08b89fa8
RD
18haveJoystick = True
19if wx.Platform == "__WXMAC__":
20 haveJoystick = False
21
ac346f50
RD
22#----------------------------------------------------------------------------
23
60d8ee39
RD
24# Once all supported versions of Python support 32-bit integers on all
25# platforms, this can go up to 32.
2421eb82 26MAX_BUTTONS = 16
ac346f50 27
2421eb82 28#----------------------------------------------------------------------------
ac346f50 29
2421eb82
RD
30class 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)
ac346f50 35
2421eb82
RD
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
47class 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.
d7403ad2 98 dc.SetClippingRegion(xorigin, yorigin, edgeSize, edgeSize)
2421eb82
RD
99
100 # Optimize drawing a bit (for Win)
101 dc.BeginDrawing()
102
103 dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237)))
d7403ad2 104 dc.DrawRectangle(xorigin, yorigin, edgeSize, edgeSize)
2421eb82
RD
105
106 dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
107
d7403ad2
RD
108 dc.DrawLine(xorigin, yorigin + center, xorigin + edgeSize, yorigin + center)
109 dc.DrawLine(xorigin + center, yorigin, xorigin + center, yorigin + edgeSize)
2421eb82
RD
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
3ba55e8a
RD
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)
2421eb82
RD
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)
3ba55e8a 139 yval = int(joyy * yratio)
2421eb82
RD
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))
3ba55e8a 147 dc.CrossHair(x, y)
2421eb82
RD
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
161class 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()
ac346f50 189
ac346f50 190
2421eb82
RD
191#----------------------------------------------------------------------------
192
193class 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))
d7403ad2 255 dc.DrawCircle(xcenter, ycenter, diameter/2)
2421eb82 256 dc.SetBrush(wx.Brush(wx.BLACK))
d7403ad2 257 dc.DrawCircle(xcenter, ycenter, 10)
2421eb82
RD
258
259 # fancy decorations
260 dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
d7403ad2
RD
261 dc.DrawLine(xorigin, ycenter, xorigin + diameter, ycenter)
262 dc.DrawLine(xcenter, yorigin, xcenter, yorigin + diameter)
2421eb82
RD
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)))
ac346f50 293
2421eb82
RD
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))
d7403ad2 300 dc.DrawLine(xcenter, ycenter, nx, ny)
2421eb82
RD
301
302 # And a little thing to show the endpoint
303 dc.SetBrush(wx.Brush(wx.BLUE))
d7403ad2 304 dc.DrawCircle(nx, ny, 8)
2421eb82
RD
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
325class 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):
ac346f50 355 s = self.stick
2421eb82
RD
356 self.avail.SetValue(s.HasPOV())
357 self.fourDir.SetValue(s.HasPOV4Dir())
358 self.cts.SetValue(s.HasPOVCTS())
ac346f50
RD
359
360
2421eb82 361#----------------------------------------------------------------------------
ac346f50 362
2421eb82
RD
363class POVPanel(wx.Panel):
364 def __init__(self, parent, stick):
ac346f50 365
2421eb82 366 self.stick = stick
ac346f50 367
2421eb82 368 wx.Panel.__init__(self, parent, -1, size=(100, 100))
ac346f50 369
2421eb82
RD
370 sizer = wx.BoxSizer(wx.HORIZONTAL)
371 gsizer = wx.BoxSizer(wx.VERTICAL)
ac346f50 372
2421eb82
RD
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)
ac346f50 391
2421eb82
RD
392 self.SetSizer(sizer)
393 sizer.Fit(self)
ac346f50 394
ac346f50 395
2421eb82
RD
396 def Calibrate(self):
397 self.display.Calibrate()
398 self.status.Calibrate()
ac346f50 399
ac346f50 400
2421eb82
RD
401 def Update(self):
402 self.display.Update()
ac346f50 403
ac346f50 404
2421eb82 405#----------------------------------------------------------------------------
ac346f50 406
2421eb82
RD
407class LED(wx.Panel):
408 def __init__(self, parent, number):
ac346f50 409
2421eb82
RD
410 self.state = -1
411 self.size = (20, 20)
412 self.number = number
ac346f50 413
2421eb82
RD
414 self.fn = wx.Font(
415 parent.GetFont().GetPointSize() - 1,
416 parent.GetFont().GetFamily(),
417 parent.GetFont().GetStyle(),
418 wx.BOLD
419 )
ac346f50 420
2421eb82 421 wx.Panel.__init__(self, parent, -1, size=self.size)
ac346f50 422
2421eb82
RD
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)
ac346f50 426
2421eb82
RD
427 self.buffer = wx.EmptyBitmap(*self.size)
428 dc = wx.BufferedDC(None, self.buffer)
429 self.DrawFace(dc)
430 self.DrawLED(dc)
ac346f50 431
ac346f50 432
2421eb82
RD
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)
ac346f50 442
ac346f50 443
2421eb82
RD
444 def DrawFace(self, dc):
445 dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
446 dc.Clear()
ac346f50 447
ac346f50 448
2421eb82
RD
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)
ac346f50 454
ac346f50 455
2421eb82
RD
456 def DrawLED(self, dc):
457 # bitmap size
458 bw, bh = self.size
ac346f50 459
2421eb82
RD
460 # center of bitmap
461 center = bw / 2
ac346f50 462
2421eb82
RD
463 # calc the 0, 0 origin of the bitmap
464 xorigin = center - (bw / 2)
465 yorigin = center - (bh / 2)
ac346f50 466
2421eb82
RD
467 # Optimize drawing a bit (for Win)
468 dc.BeginDrawing()
ac346f50 469
2421eb82
RD
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))
ac346f50 477
d7403ad2 478 dc.DrawCircle(center, center, bw/2)
ac346f50 479
2421eb82 480 txt = str(self.number)
ac346f50 481
2421eb82
RD
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)
ac346f50 487
2421eb82
RD
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)
ac346f50 492
2421eb82
RD
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)
d7403ad2 499 dc.DrawText(txt, tx, ty)
ac346f50 500
2421eb82
RD
501 # Turn off drawing optimization
502 dc.EndDrawing()
ac346f50 503
ac346f50 504
2421eb82
RD
505 def Update(self):
506 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
507 self.DrawFace(dc)
508 self.DrawLED(dc)
ac346f50 509
ac346f50 510
2421eb82 511#----------------------------------------------------------------------------
ac346f50 512
2421eb82
RD
513class JoyButtons(wx.Panel):
514 def __init__(self, parent, stick):
ac346f50 515
2421eb82
RD
516 self.stick = stick
517 self.leds = {}
ac346f50 518
2421eb82
RD
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
573class 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
646class 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)
d7403ad2 725 dc.DrawText(txt, tx, ty)
2421eb82
RD
726
727 dc.SetTextForeground('white')
d7403ad2 728 dc.DrawText(txt, tx-1, ty-1)
2421eb82
RD
729
730
731#----------------------------------------------------------------------------
732
733class 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.
3ba55e8a
RD
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)
2421eb82
RD
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
3ba55e8a 827
2421eb82
RD
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 #
3ba55e8a
RD
833 if min < 0:
834 max += abs(min)
835 val += abs(min)
836 min = 0
2421eb82 837 range = float(max - min)
3ba55e8a 838
2421eb82
RD
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:
3ba55e8a 845 relative = int( val / range * 1000)
2421eb82
RD
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
860class 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
915class 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)
2421eb82
RD
960 self.stick.SetMovementThreshold(10)
961
3ba55e8a 962
2421eb82
RD
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
3ba55e8a 973
2421eb82
RD
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()
3ba55e8a
RD
981 if evt is not None and evt.IsButton():
982 self.buttons.Update()
2421eb82
RD
983
984
3ba55e8a
RD
985 def ShutdownDemo(self):
986 if self.stick:
987 self.stick.ReleaseCapture()
988 self.stick = None
989
2421eb82 990#----------------------------------------------------------------------------
ac346f50
RD
991
992def runTest(frame, nb, log):
08b89fa8
RD
993 if haveJoystick:
994 win = JoystickDemoPanel(nb, log)
995 return win
996 else:
c4ef95da
RD
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
08b89fa8 1001
ac346f50 1002
2421eb82 1003#----------------------------------------------------------------------------
ac346f50
RD
1004
1005overview = """\
05fd1c9e
RD
1006<html>
1007<body>
1008<h1>wx.Joystick</h1>
1009This demo illustrates the use of the wx.Joystick class, which is an interface to
1010one or more joysticks attached to your system.
1011
1012<p>The data that can be retrieved from the joystick comes in four basic flavors.
1013All of these are illustrated in the demo. In fact, this demo illustrates everything
1014you <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
1024with wx.JoystickEvent, or the joystick can be polled programatically to get data on
1025a regular basis.
1026
1027<h2>Data types</h2>
1028
1029Data from the joystick comes in two flavors: that which defines the boundaries, and that
1030which defines the current state of the stick. Thus, we have Get*Max() and Get*Min()
1031methods for all axes, the max number of axes, the max number of buttons, and so on. In
1032general, this data can be read once and stored to speed computation up.
1033
1034<h3>Analog Input</h3>
1035
1036Analog input (the axes) is delivered as a whole, positive number. If you need to know
1037if the axis is at zero (centered) or not, you will first have to calculate that center
1038based on the max and min values. The demo shows a bar graph for each axis expressed
60d8ee39
RD
1039in native numerical format, plus a 'centered' X-Y axis compass showing the relationship
1040of that input to the calculated stick position.
05fd1c9e
RD
1041
1042Analog input may be jumpy and spurious, so the control has a means of 'smoothing' the
1043analog data by setting a movement threshold. This demo sets the threshold to 10, but
1044you can set it at any valid value between the min and max.
1045
1046<h3>Button Input</h3>
1047
1048Button state is retrieved as one int that contains each button state mapped to a bit.
1049You 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
1061values 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
1068POV hats come in two flavors: four-way, and continuous. four-way POVs are restricted to
1069the 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
60d8ee39
RD
1071two digits are considered to be to the right of the decimal point, so in order to
1072use this information, you need to divide by 100 right off the bat.
05fd1c9e
RD
1073
1074<p>Different methods are provided to retrieve the POV data for a CTS hat
1075versus a four-way hat.
1076
1077<h2>Caveats</h2>
1078
1079The wx.Joystick control is in many ways incomplete at the C++ library level, but it is
1080not insurmountable. In short, while the joystick interface <i>can</i> be event-driven,
1081the wx.JoystickEvent class lacks event binders for all event types. Thus, you cannot
1082rely 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
1091that will poll the stick at a set interval. Of course, if you do this, you might as
1092well forgo catching wxEVT_JOYSTICK_* events at all and rely on the timer to do the
1093polling.
1094
1095<p>Ideally, the timer should be a one-shot; after it fires, collect and process data as
1096needed, then re-start the timer, possibly using wx.CallAfter().
1097
1098</body>
1099</html>
2421eb82 1100"""
1fded56b 1101
2421eb82 1102#----------------------------------------------------------------------------
1fded56b
RD
1103
1104if __name__ == '__main__':
1105 import sys,os
1106 import run
8eca4fef 1107 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])