]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/demo/Joystick.py
demo tweaks
[wxWidgets.git] / wxPython / demo / Joystick.py
index 2c4113adf0d5c354925fa6a681adcf6e4bc85a53..c94e2155071aa36f3ab0d4096b2e849843067e43 100644 (file)
+#----------------------------------------------------------------------------
+# Name:        Joystick.py
+# Purpose:     Demonstrate use of wx.Joystick
+#
+# Author:      Jeff Grimmett (grimmtoo@softhome.net), adapted from original
+#              .wdr-derived demo
+#
+# Created:     02-Jan-2004
+# RCS-ID:      $Id$
+# Copyright:
+# Licence:     wxWindows license
+#----------------------------------------------------------------------------
+#
+
+import  math
+import  wx
+
+haveJoystick = True
+if wx.Platform == "__WXMAC__":
+    haveJoystick = False
 
 #----------------------------------------------------------------------------
 
-from wxPython.wx import *
-from joystick_wdr import *
+# Once all supported versions of Python support 32-bit integers on all
+# platforms, this can go up to 32.
+MAX_BUTTONS = 16
 
+#----------------------------------------------------------------------------
 
-class JoystickTestPanel(wxPanel):
-    def __init__(self, parent, id,
-        pos = wxDefaultPosition, size = wxDefaultSize,
-        style = wxTAB_TRAVERSAL ):
-        wxPanel.__init__(self, parent, id, pos, size, style)
+class Label(wx.StaticText):
+    # A derived StaticText that always aligns right and renders
+    # in a bold font.
+    def __init__(self, parent, label):
+        wx.StaticText.__init__(self, parent, -1, label, style=wx.ALIGN_RIGHT)
 
-        MakeJoystickTestPanel( self, True )
+        self.SetFont(
+            wx.Font(
+                parent.GetFont().GetPointSize(),
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                ))
 
-        try:
-            self.stick = wxJoystick()
-            self.stick.SetCapture(self)
-            EVT_JOYSTICK_EVENTS(self, self.OnJoystick)
-            self.UpdateFields()
-        except NotImplementedError, v:
-            wxMessageBox(str(v), "Exception Message")
+#----------------------------------------------------------------------------
+
+
+class JoyGauge(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+        size = (100,100)
+        
+        wx.Panel.__init__(self, parent, -1, size=size)
+
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
+
+        self.buffer = wx.EmptyBitmap(*size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+       
+
+    def OnSize(self, event):
+        # The face Bitmap init is done here, to make sure the buffer is always
+        # the same size as the Window
+        w, h  = self.GetClientSize()
+        self.buffer = wx.EmptyBitmap(w,h)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+
+
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
+
+
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)
+
+
+    def DrawJoystick(self, dc):
+        # draw the guage as a maxed square in the center of this window.
+        w, h = self.GetClientSize()
+        edgeSize = min(w, h)
+
+        xorigin = (w - edgeSize) / 2
+        yorigin = (h - edgeSize) / 2
+        center = edgeSize / 2
+
+        # Restrict our drawing activities to the square defined
+        # above.
+        dc.SetClippingRegion(xorigin, yorigin, edgeSize, edgeSize)
+
+        # Optimize drawing a bit (for Win)
+        dc.BeginDrawing()
+
+        dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237)))
+        dc.DrawRectangle(xorigin, yorigin, edgeSize, edgeSize)
+
+        dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
+
+        dc.DrawLine(xorigin, yorigin + center, xorigin + edgeSize, yorigin + center)
+        dc.DrawLine(xorigin + center, yorigin, xorigin + center, yorigin + edgeSize)
+
+        if self.stick:
+            # Get the joystick position as a float
+            joyx =  float(self.stick.GetPosition().x)
+            joyy =  float(self.stick.GetPosition().y)
+
+            # Get the joystick range of motion
+            xmin = self.stick.GetXMin()
+            xmax = self.stick.GetXMax()
+            if xmin < 0:
+                xmax += abs(xmin)
+                joyx += abs(xmin)
+                xmin = 0
+            xrange = max(xmax - xmin, 1)
+
+            ymin = self.stick.GetYMin()
+            ymax = self.stick.GetYMax()
+            if ymin < 0:
+                ymax += abs(ymin)
+                joyy += abs(ymin)
+                ymin = 0
+            yrange = max(ymax - ymin, 1)
+
+            # calc a ratio of our range versus the joystick range
+            xratio = float(edgeSize) / xrange
+            yratio = float(edgeSize) / yrange
+
+            # calc the displayable value based on position times ratio
+            xval = int(joyx * xratio)
+            yval = int(joyy * yratio)
+
+            # and normalize the value from our brush's origin
+            x = xval + xorigin
+            y = yval + yorigin
+
+            # Now to draw it.
+            dc.SetPen(wx.Pen(wx.RED, 2))
+            dc.CrossHair(x, y)
+
+        # Turn off drawing optimization
+        dc.EndDrawing()
+
+
+    def Update(self):
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+
+
+#----------------------------------------------------------------------------
+
+class JoyPanel(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        wx.Panel.__init__(self, parent, -1)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        fn = wx.Font(
+                parent.GetFont().GetPointSize() + 3,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+
+        t = wx.StaticText(self, -1, "X - Y Axes", style = wx.ALIGN_CENTRE)
+        t.SetFont(fn)
+        sizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
+
+        self.control = JoyGauge(self, self.stick)
+        sizer.Add(self.control, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def Update(self):
+        self.control.Update()
+
+
+#----------------------------------------------------------------------------
+
+class POVGauge(wx.Panel):
+    #
+    # Display the current postion of the POV control
+    #
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+        self.size = (100, 100)
+        self.avail = False
+        self.fourDir = False
+        self.cts = False
+
+        wx.Panel.__init__(self, parent, -1, size=self.size)
+
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
+
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+
+    def OnSize(self, event):
+        # calculate the size of our display and make a buffer for it.
+        w, h  = self.GetClientSize()
+        s = min(w, h)
+        self.size = (s, s)
+        self.buffer = wx.EmptyBitmap(w,h)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+    
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
+
+
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)
+
+
+    def DrawPOV(self, dc):
+        # draw the guage as a maxed circle in the center of this window.
+        w, h = self.GetClientSize()
+        diameter = min(w, h)
 
+        xorigin = (w - diameter) / 2
+        yorigin = (h - diameter) / 2
+        xcenter = xorigin + diameter / 2
+        ycenter = yorigin + diameter / 2
 
-    def UpdateFields(self):
+        # Optimize drawing a bit (for Win)
+        dc.BeginDrawing()
+
+        # our 'raster'.
+        dc.SetBrush(wx.Brush(wx.WHITE))
+        dc.DrawCircle(xcenter, ycenter, diameter/2)
+        dc.SetBrush(wx.Brush(wx.BLACK))
+        dc.DrawCircle(xcenter, ycenter, 10)
+
+        # fancy decorations
+        dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
+        dc.DrawLine(xorigin, ycenter, xorigin + diameter, ycenter)
+        dc.DrawLine(xcenter, yorigin, xcenter, yorigin + diameter)
+
+        if self.stick:
+            if self.avail:
+
+                pos = -1
+
+                # use the appropriate function to get the POV position
+                if self.fourDir:
+                    pos = self.stick.GetPOVPosition()
+
+                if self.cts:
+                    pos = self.stick.GetPOVCTSPosition()
+
+                # trap invalid values
+                if 0 <= pos <= 36000:
+                    vector = 30
+                else:
+                    vector = 0
+
+                # rotate CCW by 90 so that 0 is up.
+                pos = (pos / 100) - 90
+
+                # Normalize
+                if pos < 0:
+                    pos = pos + 360
+
+                # Stolen from wx.lib.analogclock :-)
+                radiansPerDegree = math.pi / 180
+                pointX = int(round(vector * math.cos(pos * radiansPerDegree)))
+                pointY = int(round(vector * math.sin(pos * radiansPerDegree)))
+
+                # normalise value to match our actual center.
+                nx = pointX + xcenter
+                ny = pointY + ycenter
+
+                # Draw the line
+                dc.SetPen(wx.Pen(wx.BLUE, 2))
+                dc.DrawLine(xcenter, ycenter, nx, ny)
+
+                # And a little thing to show the endpoint
+                dc.SetBrush(wx.Brush(wx.BLUE))
+                dc.DrawCircle(nx, ny, 8)
+
+        # Turn off drawing optimization
+        dc.EndDrawing()
+
+
+    def Update(self):
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+
+    def Calibrate(self):
         s = self.stick
-        self.GetXPositionCtrl().SetValue(str(s.GetPosition().x))
-        self.GetYPositionCtrl().SetValue(str(s.GetPosition().y))
-        self.GetZPositionCtrl().SetValue(str(s.GetZPosition()))
-        self.GetPovCtsPosCtrl().SetValue(str(s.GetPOVPosition()))
-        self.GetRudderPosCtrl().SetValue(str(s.GetRudderPosition()))
-        self.GetHasRudderCtrl().SetValue(str(s.HasRudder()))
-        self.GetHasZCtrl().SetValue(str(s.HasZ()))
-        self.GetHasPovCtrl().SetValue(str(s.HasPOV()))
-        self.GetHasPov4dirCtrl().SetValue(str(s.HasPOV4Dir()))
-        self.GetMfgIdCtrl().SetValue(str(s.GetManufacturerId()))
-        self.GetProdNameCtrl().SetValue(str(s.GetProductName()))
-        self.GetZMinCtrl().SetValue(str(s.GetZMin()))
-        self.GetXMaxCtrl().SetValue(str(s.GetXMax()))
-        self.GetNumButtonsCtrl().SetValue(str(s.GetNumberButtons()))
-        self.GetNumAxesCtrl().SetValue(str(s.GetNumberAxes()))
-        self.GetPollingMinCtrl().SetValue(str(s.GetPollingMin()))
-        self.GetPollingMaxCtrl().SetValue(str(s.GetPollingMax()))
-        self.GetUMinCtrl().SetValue(str(s.GetUMin()))
-        self.GetUMaxCtrl().SetValue(str(s.GetUMax()))
-        self.GetButtonStateCtrl().SetValue(str(s.GetButtonState()))
-        self.GetPovPositionCtrl().SetValue(str(s.GetPOVPosition()))
-        self.GetUPositionCtrl().SetValue(str(s.GetUPosition()))
-        self.GetVPositionCtrl().SetValue(str(s.GetVPosition()))
-        self.GetHasUCtrl().SetValue(str(s.HasU()))
-        self.GetHasVCtrl().SetValue(str(s.HasV()))
-        self.GetHasPovCtsCtrl().SetValue(str(s.HasPOVCTS()))
-        self.GetNumSticksCtrl().SetValue(str(s.GetNumberJoysticks()))
-        self.GetXMinCtrl().SetValue(str(s.GetXMin()))
-        self.GetYMinCtrl().SetValue(str(s.GetYMin()))
-        self.GetYMaxCtrl().SetValue(str(s.GetYMax()))
-        self.GetZMaxCtrl().SetValue(str(s.GetZMax()))
-        self.GetMaxButtonsCtrl().SetValue(str(s.GetMaxButtons()))
-        self.GetMaxAxesCtrl().SetValue(str(s.GetMaxAxes()))
-        self.GetRudderMinCtrl().SetValue(str(s.GetRudderMin()))
-        self.GetRudderMaxCtrl().SetValue(str(s.GetRudderMax()))
-        self.GetVMinCtrl().SetValue(str(s.GetVMin()))
-        self.GetVMaxCtrl().SetValue(str(s.GetVMax()))
+        self.avail = s.HasPOV()
+        self.fourDir = s.HasPOV4Dir()
+        self.cts = s.HasPOVCTS()
 
 
-    def OnJoystick(self, evt):
-        self.UpdateFields()
+#----------------------------------------------------------------------------
+
+class POVStatus(wx.Panel):
+    #
+    # Displays static info about the POV control
+    #
+    def __init__(self, parent, stick):
 
+        self.stick = stick
 
-    # WDR: methods for JoystickTestPanel
+        wx.Panel.__init__(self, parent, -1, size=(100, 100))
 
-    def GetYPositionCtrl(self):
-        return self.FindWindowById(ID_Y_Position_Ctrl)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add((20,20))
+        
+        self.avail = wx.CheckBox(self, -1, "Available")
+        sizer.Add(self.avail, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
 
-    def GetXPositionCtrl(self):
-        return self.FindWindowById(ID_X_Position_Ctrl)
+        self.fourDir = wx.CheckBox(self, -1, "4-Way Only")
+        sizer.Add(self.fourDir, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
 
+        self.cts = wx.CheckBox(self, -1, "Continuous")
+        sizer.Add(self.cts, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
 
-    def GetVMaxCtrl(self):
-        return self.FindWindowById(ID_V_Max_Ctrl)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
 
-    def GetVMinCtrl(self):
-        return self.FindWindowById(ID_V_Min_Ctrl)
+        # Effectively makes the checkboxes read-only.
+        self.Bind(wx.EVT_CHECKBOX, self.Calibrate)
 
-    def GetRudderMaxCtrl(self):
-        return self.FindWindowById(ID_Rudder_Max_Ctrl)
 
-    def GetRudderMinCtrl(self):
-        return self.FindWindowById(ID_Rudder_Min_Ctrl)
+    def Calibrate(self, evt=None):
+        s = self.stick
+        self.avail.SetValue(s.HasPOV())
+        self.fourDir.SetValue(s.HasPOV4Dir())
+        self.cts.SetValue(s.HasPOVCTS())
 
-    def GetMaxAxesCtrl(self):
-        return self.FindWindowById(ID_Max_Axes_Ctrl)
 
-    def GetMaxButtonsCtrl(self):
-        return self.FindWindowById(ID_Max_Buttons_Ctrl)
+#----------------------------------------------------------------------------
 
-    def GetZMaxCtrl(self):
-        return self.FindWindowById(ID_Z_Max_Ctrl)
+class POVPanel(wx.Panel):
+    def __init__(self, parent, stick):
 
-    def GetYMaxCtrl(self):
-        return self.FindWindowById(ID_Y_Max_Ctrl)
+        self.stick = stick
 
-    def GetYMinCtrl(self):
-        return self.FindWindowById(ID_Y_Min_Ctrl)
+        wx.Panel.__init__(self, parent, -1, size=(100, 100))
 
-    def GetXMinCtrl(self):
-        return self.FindWindowById(ID_X_Min_Ctrl)
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        gsizer = wx.BoxSizer(wx.VERTICAL)
 
-    def GetNumSticksCtrl(self):
-        return self.FindWindowById(ID_Num_Sticks_Ctrl)
+        sizer.Add((25,25))
+        
+        fn = wx.Font(
+                parent.GetFont().GetPointSize() + 3,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+        t = wx.StaticText(self, -1, "POV Control", style = wx.ALIGN_CENTER)
+        t.SetFont(fn)
+        gsizer.Add(t, 0, wx.ALL | wx.EXPAND, 1)
+        
+        self.display = POVGauge(self, stick)
+        gsizer.Add(self.display, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
+        sizer.Add(gsizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
+        
+        self.status = POVStatus(self, stick)
+        sizer.Add(self.status, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
 
-    def GetHasPovCtsCtrl(self):
-        return self.FindWindowById(ID_Has_POV_CTS_Ctrl)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
 
-    def GetHasVCtrl(self):
-        return self.FindWindowById(ID_Has_V_Ctrl)
 
-    def GetHasUCtrl(self):
-        return self.FindWindowById(ID_Has_U_Ctrl)
+    def Calibrate(self):
+        self.display.Calibrate()
+        self.status.Calibrate()
 
-    def GetVPositionCtrl(self):
-        return self.FindWindowById(ID_V_Position_Ctrl)
 
-    def GetUPositionCtrl(self):
-        return self.FindWindowById(ID_U_Position_Ctrl)
+    def Update(self):
+        self.display.Update()
 
-    def GetPovPositionCtrl(self):
-        return self.FindWindowById(ID_POV_Position_Ctrl)
 
-    def GetButtonStateCtrl(self):
-        return self.FindWindowById(ID_Button_State_Ctrl)
+#----------------------------------------------------------------------------
 
-    def GetUMaxCtrl(self):
-        return self.FindWindowById(ID_U_Max_Ctrl)
+class LED(wx.Panel):
+    def __init__(self, parent, number):
 
-    def GetUMinCtrl(self):
-        return self.FindWindowById(ID_U_Min_Ctrl)
+        self.state = -1
+        self.size = (20, 20)
+        self.number = number
 
-    def GetPollingMaxCtrl(self):
-        return self.FindWindowById(ID_Polling_Max_Ctrl)
+        self.fn = wx.Font(
+                parent.GetFont().GetPointSize() - 1,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
 
-    def GetPollingMinCtrl(self):
-        return self.FindWindowById(ID_Polling_Min_Ctrl)
+        wx.Panel.__init__(self, parent, -1, size=self.size)
 
-    def GetNumAxesCtrl(self):
-        return self.FindWindowById(ID_Num_Axes_Ctrl)
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
 
-    def GetNumButtonsCtrl(self):
-        return self.FindWindowById(ID_Num_Buttons_Ctrl)
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawLED(dc)
 
-    def GetXMaxCtrl(self):
-        return self.FindWindowById(ID_X_Max_Ctrl)
 
-    def GetZMinCtrl(self):
-        return self.FindWindowById(ID_Z_Min_Ctrl)
+    def OnSize(self, event):
+        # calculate the size of our display.
+        w, h  = self.GetClientSize()
+        s = min(w, h)
+        self.size = (s, s)
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawLED(dc)
 
-    def GetProdNameCtrl(self):
-        return self.FindWindowById(ID_Prod_Name_Ctrl)
 
-    def GetMfgIdCtrl(self):
-        return self.FindWindowById(ID_Mfg_ID_Ctrl)
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
 
-    def GetHasPov4dirCtrl(self):
-        return self.FindWindowById(ID_Has_POV_4DIR_Ctrl)
 
-    def GetHasPovCtrl(self):
-        return self.FindWindowById(ID_Has_POV_Ctrl)
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)
 
-    def GetHasZCtrl(self):
-        return self.FindWindowById(ID_Has_Z_Ctrl)
 
-    def GetHasRudderCtrl(self):
-        return self.FindWindowById(ID_Has_Rudder_Ctrl)
+    def DrawLED(self, dc):
+        # bitmap size
+        bw, bh = self.size
 
-    def GetRudderPosCtrl(self):
-        return self.FindWindowById(ID_Rudder_Pos_Ctrl)
+        # center of bitmap
+        center = bw / 2
 
-    def GetPovCtsPosCtrl(self):
-        return self.FindWindowById(ID_POV_CTS_Pos_Ctrl)
+        # calc the 0, 0 origin of the bitmap
+        xorigin = center - (bw / 2)
+        yorigin = center - (bh / 2)
 
-    def GetZPositionCtrl(self):
-        return self.FindWindowById(ID_Z_Position_Ctrl)
+        # Optimize drawing a bit (for Win)
+        dc.BeginDrawing()
 
-    # WDR: handler implementations for JoysticktestPanel
+        # our 'raster'.
+        if self.state == 0:
+            dc.SetBrush(wx.Brush(wx.RED))
+        elif self.state == 1:
+            dc.SetBrush(wx.Brush(wx.GREEN))
+        else:
+            dc.SetBrush(wx.Brush(wx.BLACK))
 
+        dc.DrawCircle(center, center, bw/2)
 
-#----------------------------------------------------------------------
+        txt = str(self.number)
 
-def runTest(frame, nb, log):
-    win = JoystickTestPanel(nb, -1)
-    return win
+        # Set the font for the DC ...
+        dc.SetFont(self.fn)
+        # ... and calculate how much space our value
+        # will take up.
+        fw, fh = dc.GetTextExtent(txt)
+
+        # Calc the center of the LED, and from that
+        # derive the origin of our value.
+        tx = center - (fw/2)
+        ty = center - (fh/2)
+
+        # I draw the value twice so as to give it a pseudo-shadow.
+        # This is (mostly) because I'm too lazy to figure out how
+        # to blit my text onto the gauge using one of the logical
+        # functions. The pseudo-shadow gives the text contrast
+        # regardless of whether the bar is under it or not.
+        dc.SetTextForeground(wx.WHITE)
+        dc.DrawText(txt, tx, ty)
+
+        # Turn off drawing optimization
+        dc.EndDrawing()
+
+
+    def Update(self):
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawLED(dc)
+
+
+#----------------------------------------------------------------------------
+
+class JoyButtons(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+        self.leds = {}
+
+        wx.Panel.__init__(self, parent, -1)
+
+        tsizer = wx.BoxSizer(wx.VERTICAL)
+
+        fn = wx.Font(
+                parent.GetFont().GetPointSize() + 3,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+
+        t = wx.StaticText(self, -1, "Buttons", style = wx.ALIGN_LEFT)
+        t.SetFont(fn)
+        tsizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1)
+
+        sizer = wx.FlexGridSizer(4, 16, 2, 2)
+
+        fn.SetPointSize(parent.GetFont().GetPointSize() + 1)
+
+        for i in range(0, MAX_BUTTONS):
+            t = LED(self, i)
+            self.leds[i] = t
+            sizer.Add(t, 1, wx.ALL|wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL, 1)
+            sizer.AddGrowableCol(i)
+
+        tsizer.Add(sizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 1)
+
+        self.SetSizer(tsizer)
+        tsizer.Fit(self)
+
+    def Calibrate(self):
+        for i in range(0, MAX_BUTTONS):
+            self.leds[i].state = -1
+
+        t = self.stick.GetNumberButtons()
+
+        for i in range(0, t):
+            self.leds[i].state = 0
+
+    def Update(self):
+        t = self.stick.GetButtonState()
+
+        for i in range(0, MAX_BUTTONS):
+            if self.leds[i].state == 1:
+                self.leds[i].state = 0
+
+            if (t & (1<<i)):
+                self.leds[i].state = 1
+
+            self.leds[i].Update()
+
+
+#----------------------------------------------------------------------------
+
+class InfoPanel(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        wx.Panel.__init__(self, parent, -1)
+
+        sizer = wx.GridBagSizer(1, 1)
+
+        sizer.Add(Label(self, 'Mfr ID: '), (0, 0), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.MfgID = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.MfgID, (0, 1), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        sizer.Add(Label(self, 'Prod Name: '), (0, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.ProdName = wx.TextCtrl(self, -1, value='', style=wx.TE_READONLY)
+        sizer.Add(self.ProdName, (0, 3), (1, 3), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        sizer.Add(Label(self, 'Threshold: '), (0, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.Threshold = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.Threshold, (0, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        #----------------------------------------------------------------------------
+        b = wx.Button(self, -1, "Calibrate")
+        sizer.Add(b, (1, 0), (2, 2), wx.ALL | wx.ALIGN_CENTER, 2)
+
+        sizer.Add(Label(self, '# of Sticks: '), (1, 2), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.NumJoysticks = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.NumJoysticks, (1, 3), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        sizer.Add(Label(self, '# of Axes: '), (1, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.NumAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.NumAxis, (1, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
 
-#----------------------------------------------------------------------
+        sizer.Add(Label(self, 'Max # Axes: '), (1, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.MaxAxis = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.MaxAxis, (1, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
 
+        #----------------------------------------------------------------------------
+
+        sizer.Add(Label(self, 'Polling -- '), (2, 3), (1, 1), wx.ALL | wx.GROW, 2)
+
+        sizer.Add(Label(self, 'Min: '), (2, 4), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.PollMin = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.PollMin, (2, 5), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        sizer.Add(Label(self, 'Max: '), (2, 6), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_RIGHT, 2)
+        self.PollMax = wx.TextCtrl(self, -1, value='', size=(45, -1), style=wx.TE_READONLY)
+        sizer.Add(self.PollMax, (2, 7), (1, 1), wx.ALL | wx.GROW | wx.ALIGN_LEFT, 2)
+
+        #----------------------------------------------------------------------------
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+
+    def Calibrate(self):
+        if not self.stick:
+            return
+
+        s = self.stick
+
+        self.MfgID.SetValue(str(s.GetManufacturerId()))
+        self.ProdName.SetValue(str(s.GetProductName()))
+        self.Threshold.SetValue(str(s.GetMovementThreshold()))
+        self.NumJoysticks.SetValue(str(s.GetNumberJoysticks()))
+        self.NumAxis.SetValue(str(s.GetNumberAxes()))
+        self.MaxAxis.SetValue(str(s.GetMaxAxes()))
+        self.PollMin.SetValue(str(s.GetPollingMin()))
+        self.PollMax.SetValue(str(s.GetPollingMax()))
+
+
+#----------------------------------------------------------------------------
+
+class AxisBar(wx.Gauge):
+    #
+    # This class allows us to use a wx.Gauge to display the axis value
+    # with a fancy label overlayed onto the guage itself. Two values are
+    # used to do things: first of all, since the gauge is limited to
+    # positive numbers, the scale is fixed at 0 to 1000. We will receive
+    # an adjusted value to use to render the gauge itself. The other value
+    # is a raw value and actually reflects the value from the joystick itself,
+    # which is then drawn over the gauge.
+    #
+    def __init__(self, parent):
+        wx.Gauge.__init__(self, parent, -1, 1000, size=(-1, 20), style = wx.GA_HORIZONTAL | wx.GA_SMOOTH )
+
+        # This is the value we will display.
+        self.rawvalue = 0
+
+        self.SetBackgroundColour('light blue')
+        self.SetForegroundColour('orange')
+
+        # Capture paint events for purpose of updating
+        # the displayed value.
+        self.Bind(wx.EVT_PAINT, self.onPaint)
+
+    def Update(self, value, rawvalue):
+        # Updates the gauge itself, sets the raw value for
+        # the next EVT_PAINT
+        self.SetValue(value)
+        self.rawvalue = rawvalue
+
+    def onPaint(self, evt):
+        # Must always create a PaintDC when capturing
+        # an EVT_PAINT event
+        self.ShowValue(wx.PaintDC(self), evt)
+
+    def ShowValue(self, dc, evt):
+        # This method handles actual painting of and drawing
+        # on the gauge.
+
+        # Clear out the gauge
+        dc.Clear()
+        # and then carry out business as usual
+        wx.Gauge.OnPaint(self, evt)
+
+        # This is the size available to us.
+        w, h = dc.GetSize()
+
+        # This is what we will overlay on the gauge.
+        # It reflects the actual value received from the
+        # wx.Joystick.
+        txt = str(self.rawvalue)
+
+        # Copy the default font, make it bold.
+        fn = wx.Font(
+                self.GetFont().GetPointSize(),
+                self.GetFont().GetFamily(),
+                self.GetFont().GetStyle(),
+                wx.BOLD
+                )
+
+        # Set the font for the DC ...
+        dc.SetFont(fn)
+        # ... and calculate how much space our value
+        # will take up.
+        fw, fh = dc.GetTextExtent(txt)
+
+        # Calc the center of the gauge, and from that
+        # derive the origin of our value.
+        center = w / 2
+        tx = center - (fw/2)
+
+        center = h / 2
+        ty = center - (fh/2)
+
+        # I draw the value twice so as to give it a pseudo-shadow.
+        # This is (mostly) because I'm too lazy to figure out how
+        # to blit my text onto the gauge using one of the logical
+        # functions. The pseudo-shadow gives the text contrast
+        # regardless of whether the bar is under it or not.
+        dc.SetTextForeground(wx.BLACK)
+        dc.DrawText(txt, tx, ty)
+
+        dc.SetTextForeground('white')
+        dc.DrawText(txt, tx-1, ty-1)
+
+
+#----------------------------------------------------------------------------
+
+class Axis(wx.Panel):
+    #
+    # This class is a container for the min, max, and current
+    # values of the joystick axis in question. It contains
+    # also special features to render a 'dummy' if the axis
+    # in question is not available.
+    #
+    def __init__(self, parent, token, stick):
+
+        self.stick = stick
+
+        #
+        # token represents the type of axis we're displaying.
+        #
+        self.token = token
+
+        #
+        # Create a call to the 'Has*()' method for the stick.
+        # X and Y are always there, so we tie the Has* method
+        # to a hardwired True value.
+        #
+        if token not in ['X', 'Y']:
+            self.HasFunc = eval('stick.Has%s' % token)
+        else:
+            self.HasFunc = self.alwaysTrue
+
+        # Now init the panel.
+        wx.Panel.__init__(self, parent, -1)
+
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        if self.HasFunc():
+            #
+            # Tie our calibration functions to the appropriate
+            # stick method. If we don't have the axis in question,
+            # we won't need them.
+            #
+            self.GetMin = eval('stick.Get%sMin' % token)
+            self.GetMax = eval('stick.Get%sMax' % token)
+
+            # Create our displays and set them up.
+            self.Min = wx.StaticText(self, -1, str(self.GetMin()), style=wx.ALIGN_RIGHT)
+            self.Max = wx.StaticText(self, -1, str(self.GetMax()), style=wx.ALIGN_LEFT)
+            self.bar = AxisBar(self)
+
+            sizer.Add(self.Min, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 1)
+            sizer.Add(self.bar, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
+            sizer.Add(self.Max, 0, wx.ALL | wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 1)
+
+        else:
+            # We go here if the axis in question is not available.
+            self.control = wx.StaticText(self, -1, '       *** Not Present ***')
+            sizer.Add(self.control, 1, wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
+
+        #----------------------------------------------------------------------------
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        wx.CallAfter(self.Update)
+
+
+    def Calibrate(self):
+        if not self.HasFunc():
+            return
+
+        self.Min.SetLabel(str(self.GetMin()))
+        self.Max.SetLabel(str(self.GetMax()))
+
+
+    def Update(self):
+        # Don't bother if the axis doesn't exist.
+        if not self.HasFunc():
+            return
+
+        min = int(self.Min.GetLabel())
+        max = int(self.Max.GetLabel())
+
+        #
+        # Not all values are available from a wx.JoystickEvent, so I've elected
+        # to not use it at all. Therefore, we are getting our values direct from
+        # the stick. These values also seem to be more stable and reliable than
+        # those received from the event itself, so maybe it's a good idea to
+        # use the stick directly for your program.
+        #
+        # Here we either select the appropriate member of stick.GetPosition() or
+        # apply the appropriate Get*Position method call.
+        #
+        if self.token == 'X':
+            val = self.stick.GetPosition().x
+        elif self.token == 'Y':
+            val = self.stick.GetPosition().y
+        else:
+            val = eval('self.stick.Get%sPosition()' % self.token)
+
+
+        #
+        # While we might be able to rely on a range of 0-FFFFFF on Win, that might
+        # not be true of all drivers on all platforms. Thus, calc the actual full
+        # range first.
+        #
+        if min < 0:
+            max += abs(min)
+            val += abs(min)
+            min = 0        
+        range = float(max - min)
+        
+        #
+        # The relative value is used by the derived wx.Gauge since it is a
+        # positive-only control.
+        #
+        relative = 0
+        if range:
+            relative = int( val / range * 1000)
+
+        #
+        # Pass both the raw and relative values to the derived Gauge
+        #
+        self.bar.Update(relative, val)
+
+
+    def alwaysTrue(self):
+        # a dummy method used for X and Y axis.
+        return True
+
+
+#----------------------------------------------------------------------------
+
+class AxisPanel(wx.Panel):
+    #
+    # Contained herein is a panel that offers a graphical display
+    # of the levels for all axes supported by wx.Joystick. If
+    # your system doesn't have a particular axis, it will be
+    # 'dummied' for transparent use.
+    #
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        # Defines labels and 'tokens' to identify each
+        # supporte axis.
+        axesList = [
+            ('X Axis ', 'X'),   ('Y Axis ', 'Y'),
+            ('Z Axis ', 'Z'),   ('Rudder ', 'Rudder'),
+            ('U Axis ', 'U'),   ('V Axis ', 'V')
+            ]
+
+        # Contains a list of all axis initialized.
+        self.axes = []
+
+        wx.Panel.__init__(self, parent, -1)
+
+        sizer = wx.FlexGridSizer(3, 4, 1, 1)
+        sizer.AddGrowableCol(1)
+        sizer.AddGrowableCol(3)
+
+        #----------------------------------------------------------------------------
+
+        # Go through the list of labels and tokens and add a label and
+        # axis display to the sizer for each.
+        for label, token in axesList:
+            sizer.Add(Label(self, label), 0, wx.ALL | wx.ALIGN_RIGHT, 2)
+            t = Axis(self, token, self.stick)
+            self.axes.append(t)
+            sizer.Add(t, 1, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
+
+        #----------------------------------------------------------------------------
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        wx.CallAfter(self.Update)
+
+    def Calibrate(self):
+        for i in self.axes:
+            i.Calibrate()
+
+    def Update(self):
+        for i in self.axes:
+            i.Update()
+
+
+#----------------------------------------------------------------------------
+
+class JoystickDemoPanel(wx.Panel):
+
+    def __init__(self, parent, log):
+
+        self.log = log
+
+        wx.Panel.__init__(self, parent, -1)
+
+        # Try to grab the control. If we get it, capture the stick.
+        # Otherwise, throw up an exception message and play stupid.
+        try:
+            self.stick = wx.Joystick()
+            self.stick.SetCapture(self)
+            # Calibrate our controls
+            wx.CallAfter(self.Calibrate)
+            wx.CallAfter(self.OnJoystick)
+        except NotImplementedError, v:
+            wx.MessageBox(str(v), "Exception Message")
+            self.stick = None
+
+        # One Sizer to Rule Them All...
+        sizer = wx.GridBagSizer(2,2)
+
+        self.info = InfoPanel(self, self.stick)
+        sizer.Add(self.info, (0, 0), (1, 3), wx.ALL | wx.GROW, 2)
+
+        self.info.Bind(wx.EVT_BUTTON, self.Calibrate)
+
+        self.joy = JoyPanel(self, self.stick)
+        sizer.Add(self.joy, (1, 0), (1, 1), wx.ALL | wx.GROW, 2)
+                  
+        self.pov = POVPanel(self, self.stick)
+        sizer.Add(self.pov, (1, 1), (1, 2), wx.ALL | wx.GROW, 2)
+        
+        self.axes = AxisPanel(self, self.stick)
+        sizer.Add(self.axes, (2, 0), (1, 3), wx.ALL | wx.GROW, 2)
+
+        self.buttons = JoyButtons(self, self.stick)
+        sizer.Add(self.buttons, (3, 0), (1, 3), wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, 1)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+        # Capture Joystick events (if they happen)
+        self.Bind(wx.EVT_JOYSTICK_EVENTS, self.OnJoystick)
+        self.stick.SetMovementThreshold(10)
+
+
+    def Calibrate(self, evt=None):
+        # Do not try this without a stick
+        if not self.stick:
+            return
+
+        self.info.Calibrate()
+        self.axes.Calibrate()
+        self.pov.Calibrate()
+        self.buttons.Calibrate()
+
+
+    def OnJoystick(self, evt=None):
+        if not self.stick:
+            return
+
+        self.axes.Update()
+        self.joy.Update()
+        self.pov.Update()
+        if evt is not None and evt.IsButton():
+            self.buttons.Update()
+
+
+    def ShutdownDemo(self):
+        if self.stick:
+            self.stick.ReleaseCapture()
+        self.stick = None
+        
+#----------------------------------------------------------------------------
+
+def runTest(frame, nb, log):
+    if haveJoystick:
+        win = JoystickDemoPanel(nb, log)
+        return win
+    else:
+        from Main import MessagePanel
+        win = MessagePanel(nb, 'wx.Joystick is not available on this platform.',
+                           'Sorry', wx.ICON_WARNING)
+        return win
+    
+
+#----------------------------------------------------------------------------
 
 overview = """\
-"""
+<html>
+<body>
+<h1>wx.Joystick</h1>
+This demo illustrates the use of the wx.Joystick class, which is an interface to
+one or more joysticks attached to your system.
+
+<p>The data that can be retrieved from the joystick comes in four basic flavors.
+All of these are illustrated in the demo. In fact, this demo illustrates everything
+you <b>can</b> get from the wx.Joystick control.
+
+<ul>
+<li>Static information such as Manufacturer ID and model name,
+<li>Analog input from up to six axes, including X and Y for the actual stick,
+<li>Button input from the fire button and any other buttons that the stick has,
+<li>and the POV control (a kind of mini-joystick on top of the joystick) that many sticks come with.
+</ul>
+
+<p>Getting data from the joystick can be event-driven thanks to four event types associated
+with wx.JoystickEvent, or the joystick can be polled programatically to get data on
+a regular basis.
+
+<h2>Data types</h2>
+
+Data from the joystick comes in two flavors: that which defines the boundaries, and that
+which defines the current state of the stick. Thus, we have Get*Max() and Get*Min() 
+methods for all axes, the max number of axes, the max number of buttons, and so on. In
+general, this data can be read once and stored to speed computation up.
+
+<h3>Analog Input</h3>
+
+Analog input (the axes) is delivered as a whole, positive number. If you need to know 
+if the axis is at zero (centered) or not, you will first have to calculate that center
+based on the max and min values. The demo shows a bar graph for each axis expressed
+in native numerical format, plus a 'centered' X-Y axis compass showing the relationship
+of that input to the calculated stick position.
+
+Analog input may be jumpy and spurious, so the control has a means of 'smoothing' the
+analog data by setting a movement threshold. This demo sets the threshold to 10, but
+you can set it at any valid value between the min and max.
 
+<h3>Button Input</h3>
 
+Button state is retrieved as one int that contains each button state mapped to a bit.
+You get the state of a button by AND-ing its bit against the returned value, in the form
 
+<pre>
+     # assume buttonState is what the stick returned, and buttonBit 
+     # is the bit you want to examine
+     
+     if (buttonState & ( 1 &lt;&lt; buttonBit )) :
+         # button pressed, do something with it
+</pre>
+
+<p>The problem here is that some OSs return a 32-bit value for up to 32 buttons 
+(imagine <i>that</i> stick!). Python V2.3 will generate an exception for bit 
+values over 30. For that reason, this demo is limited to 16 buttons.
+
+<p>Note that more than one button can be pressed at a time, so be sure to check all of them!
+     
+
+<h3>POV Input</h3>
+
+POV hats come in two flavors: four-way, and continuous. four-way POVs are restricted to
+the cardinal points of the compass; continuous, or CTS POV hats can deliver input in
+.01 degree increments, theoreticaly. The data is returned as a whole number; the last
+two digits are considered to be to the right of the decimal point, so in order to 
+use this information, you need to divide by 100 right off the bat. 
+
+<p>Different methods are provided to retrieve the POV data for a CTS hat 
+versus a four-way hat.
+
+<h2>Caveats</h2>
+
+The wx.Joystick control is in many ways incomplete at the C++ library level, but it is
+not insurmountable.  In short, while the joystick interface <i>can</i> be event-driven,
+the wx.JoystickEvent class lacks event binders for all event types. Thus, you cannot
+rely on wx.JoystickEvents to tell you when something has changed, necessarilly.
+
+<ul>
+<li>There are no events associated with the POV control.
+<li>There are no events associated with the Rudder
+<li>There are no events associated with the U and V axes.
+</ul>
+
+<p>Fortunately, there is an easy workaround. In the top level frame, create a wx.Timer
+that will poll the stick at a set interval. Of course, if you do this, you might as
+well forgo catching wxEVT_JOYSTICK_* events at all and rely on the timer to do the
+polling. 
+
+<p>Ideally, the timer should be a one-shot; after it fires, collect and process data as 
+needed, then re-start the timer, possibly using wx.CallAfter().
+
+</body>
+</html>
+"""
+
+#----------------------------------------------------------------------------
 
 if __name__ == '__main__':
     import sys,os
     import run
-    run.main(['', os.path.basename(sys.argv[0])])
-
+    run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])