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