+        #----------------------------------------------------------------------------
+
+        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
+    
+
+#----------------------------------------------------------------------------