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