c4ef95daf6
be runnable in certain conditions git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@30229 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1108 lines
35 KiB
Python
1108 lines
35 KiB
Python
#----------------------------------------------------------------------------
|
|
# 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
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
# Once all supported versions of Python support 32-bit integers on all
|
|
# platforms, this can go up to 32.
|
|
MAX_BUTTONS = 16
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
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)
|
|
|
|
self.SetFont(
|
|
wx.Font(
|
|
parent.GetFont().GetPointSize(),
|
|
parent.GetFont().GetFamily(),
|
|
parent.GetFont().GetStyle(),
|
|
wx.BOLD
|
|
))
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
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
|
|
|
|
# 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.avail = s.HasPOV()
|
|
self.fourDir = s.HasPOV4Dir()
|
|
self.cts = s.HasPOVCTS()
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
class POVStatus(wx.Panel):
|
|
#
|
|
# Displays static info about the POV control
|
|
#
|
|
def __init__(self, parent, stick):
|
|
|
|
self.stick = stick
|
|
|
|
wx.Panel.__init__(self, parent, -1, size=(100, 100))
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
self.SetSizer(sizer)
|
|
sizer.Fit(self)
|
|
|
|
# Effectively makes the checkboxes read-only.
|
|
self.Bind(wx.EVT_CHECKBOX, self.Calibrate)
|
|
|
|
|
|
def Calibrate(self, evt=None):
|
|
s = self.stick
|
|
self.avail.SetValue(s.HasPOV())
|
|
self.fourDir.SetValue(s.HasPOV4Dir())
|
|
self.cts.SetValue(s.HasPOVCTS())
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
class POVPanel(wx.Panel):
|
|
def __init__(self, parent, stick):
|
|
|
|
self.stick = stick
|
|
|
|
wx.Panel.__init__(self, parent, -1, size=(100, 100))
|
|
|
|
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
gsizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
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)
|
|
|
|
self.SetSizer(sizer)
|
|
sizer.Fit(self)
|
|
|
|
|
|
def Calibrate(self):
|
|
self.display.Calibrate()
|
|
self.status.Calibrate()
|
|
|
|
|
|
def Update(self):
|
|
self.display.Update()
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
class LED(wx.Panel):
|
|
def __init__(self, parent, number):
|
|
|
|
self.state = -1
|
|
self.size = (20, 20)
|
|
self.number = number
|
|
|
|
self.fn = wx.Font(
|
|
parent.GetFont().GetPointSize() - 1,
|
|
parent.GetFont().GetFamily(),
|
|
parent.GetFont().GetStyle(),
|
|
wx.BOLD
|
|
)
|
|
|
|
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.DrawLED(dc)
|
|
|
|
|
|
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 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 DrawLED(self, dc):
|
|
# bitmap size
|
|
bw, bh = self.size
|
|
|
|
# center of bitmap
|
|
center = bw / 2
|
|
|
|
# calc the 0, 0 origin of the bitmap
|
|
xorigin = center - (bw / 2)
|
|
yorigin = center - (bh / 2)
|
|
|
|
# Optimize drawing a bit (for Win)
|
|
dc.BeginDrawing()
|
|
|
|
# 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)
|
|
|
|
# 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 << 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])] + sys.argv[1:])
|