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