1# --------------------------------------------------------------------------- # 2# SPEEDMETER Control wxPython IMPLEMENTATION 3# Python Code By: 4# 5# Andrea Gavana, @ 25 Sep 2005 6# Latest Revision: 27 Dec 2012, 21.00 GMT 7# 8# 9# TODO List/Caveats 10# 11# 1. Combination Of The Two Styles: 12# 13# SM_DRAW_PARTIAL_FILLER 14# SM_DRAW_SECTORS 15# 16# Does Not Work Very Well. It Works Well Only In Case When The Sector Colours 17# Are The Same For All Intervals. 18# 19# 20# Thanks To Gerard Grazzini That Has Tried The Demo On MacOS, I Corrected A 21# Bug On Line 246 22# 23# 24# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please 25# Write To Me At: 26# 27# andrea.gavana@gmail.com 28# andrea.gavana@maerskoil.com 29# 30# Or, Obviously, To The wxPython Mailing List!!! 31# 32# Tags: phoenix-port, unittest, documented, py3-port 33# 34# End Of Comments 35# --------------------------------------------------------------------------- # 36 37 38""" 39:class:`~wx.lib.agw.speedmeter.SpeedMeter` tries to reproduce the behavior of some car controls (but not only), 40by creating an "angular" control (actually, circular). 41 42 43Description 44=========== 45 46:class:`SpeedMeter` tries to reproduce the behavior of some car controls (but not only), 47by creating an "angular" control (actually, circular). I remember to have seen 48it somewhere, and i decided to implement it in wxPython. 49 50:class:`SpeedMeter` starts its construction from an empty bitmap, and it uses some 51functions of the :class:`wx.DC` class to create the rounded effects. everything is 52processed in the `Draw()` method of :class:`SpeedMeter` class. 53 54This implementation allows you to use either directly the :class:`PaintDC`, or the 55better (for me) double buffered style with :class:`BufferedPaintDC`. the double 56buffered implementation has been adapted from the wxPython wiki example: 57 58http://wiki.wxpython.org/index.cgi/doublebuffereddrawing 59 60 61Usage 62===== 63 64Usage example:: 65 66 import wx 67 import wx.lib.agw.speedmeter as SM 68 69 class MyFrame(wx.Frame): 70 71 def __init__(self, parent): 72 73 wx.Frame.__init__(self, parent, -1, "SpeedMeter Demo") 74 75 speed = SM.SpeedMeter(self, agwStyle=SM.SM_DRAW_HAND|SM.SM_DRAW_SECTORS|SM.SM_DRAW_MIDDLE_TEXT|SM.SM_DRAW_SECONDARY_TICKS) 76 77 # Set The Region Of Existence Of SpeedMeter (Always In Radians!!!!) 78 speed.SetAngleRange(-pi/6, 7*pi/6) 79 80 # Create The Intervals That Will Divide Our SpeedMeter In Sectors 81 intervals = range(0, 201, 20) 82 speed.SetIntervals(intervals) 83 84 # Assign The Same Colours To All Sectors (We Simulate A Car Control For Speed) 85 # Usually This Is Black 86 colours = [wx.BLACK]*10 87 speed.SetIntervalColours(colours) 88 89 # Assign The Ticks: Here They Are Simply The String Equivalent Of The Intervals 90 ticks = [str(interval) for interval in intervals] 91 speed.SetTicks(ticks) 92 # Set The Ticks/Tick Markers Colour 93 speed.SetTicksColour(wx.WHITE) 94 # We Want To Draw 5 Secondary Ticks Between The Principal Ticks 95 speed.SetNumberOfSecondaryTicks(5) 96 97 # Set The Font For The Ticks Markers 98 speed.SetTicksFont(wx.Font(7, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) 99 100 # Set The Text In The Center Of SpeedMeter 101 speed.SetMiddleText("Km/h") 102 # Assign The Colour To The Center Text 103 speed.SetMiddleTextColour(wx.WHITE) 104 # Assign A Font To The Center Text 105 speed.SetMiddleTextFont(wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) 106 107 # Set The Colour For The Hand Indicator 108 speed.SetHandColour(wx.Colour(255, 50, 0)) 109 110 # Do Not Draw The External (Container) Arc. Drawing The External Arc May 111 # Sometimes Create Uglier Controls. Try To Comment This Line And See It 112 # For Yourself! 113 speed.DrawExternalArc(False) 114 115 # Set The Current Value For The SpeedMeter 116 speed.SetSpeedValue(44) 117 118 119 # our normal wxApp-derived class, as usual 120 121 app = wx.App(0) 122 123 frame = MyFrame(None) 124 app.SetTopWindow(frame) 125 frame.Show() 126 127 app.MainLoop() 128 129 130 131Methods and Settings 132==================== 133 134:class:`SpeedMeter` is highly customizable, and in particular you can set: 135 136- The start and end angle of existence for :class:`SpeedMeter`; 137- The intervals in which you divide the :class:`SpeedMeter` (numerical values); 138- The corresponding thicks for the intervals; 139- The interval colours (different intervals may have different filling colours); 140- The ticks font and colour; 141- The background colour (outsize the :class:`SpeedMeter` region); 142- The external arc colour; 143- The hand (arrow) colour; 144- The hand's shadow colour; 145- The hand's style ("arrow" or "hand"); 146- The partial filler colour; 147- The number of secondary (intermediate) ticks; 148- The direction of increasing speed ("advance" or "reverse"); 149- The text to be drawn in the middle and its font; 150- The icon to be drawn in the middle; 151- The first and second gradient colours (that fills the :class:`SpeedMeter` control); 152- The current value. 153 154 155Window Styles 156============= 157 158This class supports the following window styles: 159 160=========================== =========== ================================================== 161Window Styles Hex Value Description 162=========================== =========== ================================================== 163``SM_ROTATE_TEXT`` 0x1 Draws the ticks rotated: the ticks are rotated accordingly to the tick marks positions. 164``SM_DRAW_SECTORS`` 0x2 Different intervals are painted in differend colours (every sector of the circle has its own colour). 165``SM_DRAW_PARTIAL_SECTORS`` 0x4 Every interval has its own colour, but only a circle corona is painted near the ticks. 166``SM_DRAW_HAND`` 0x8 The hand (arrow indicator) is drawn. 167``SM_DRAW_SHADOW`` 0x10 A shadow for the hand is drawn. 168``SM_DRAW_PARTIAL_FILLER`` 0x20 A circle corona that follows the hand position is drawn near the ticks. 169``SM_DRAW_SECONDARY_TICKS`` 0x40 Intermediate (smaller) ticks are drawn between principal ticks. 170``SM_DRAW_MIDDLE_TEXT`` 0x80 Some text is printed in the middle of the control near the center. 171``SM_DRAW_MIDDLE_ICON`` 0x100 An icon is drawn in the middle of the control near the center. 172``SM_DRAW_GRADIENT`` 0x200 A gradient of colours will fill the control. 173``SM_DRAW_FANCY_TICKS`` 0x400 With this style you can use xml tags to create some custom text and draw it at the ticks position. See :mod:`lib.fancytext` for the tags. 174=========================== =========== ================================================== 175 176 177Events Processing 178================= 179 180`No custom events are available for this class.` 181 182 183License And Version 184=================== 185 186:class:`SpeedMeter` is distributed under the wxPython license. 187 188Latest revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT 189 190Version 0.3 191 192""" 193 194#---------------------------------------------------------------------- 195# Beginning Of SPEEDMETER wxPython Code 196#---------------------------------------------------------------------- 197 198import wx 199import wx.lib.colourdb 200import wx.lib.fancytext as fancytext 201 202from math import pi, sin, cos, log, sqrt, atan2 203 204#---------------------------------------------------------------------- 205# DC Drawing Options 206#---------------------------------------------------------------------- 207# SM_NORMAL_DC Uses The Normal wx.PaintDC 208# SM_BUFFERED_DC Uses The Double Buffered Drawing Style 209 210SM_NORMAL_DC = 0 211""" Uses the normal :class:`PaintDC`. """ 212SM_BUFFERED_DC = 1 213""" Uses a double buffered drawing code. """ 214 215#---------------------------------------------------------------------- 216# SpeedMeter Styles 217#---------------------------------------------------------------------- 218# SM_ROTATE_TEXT: Draws The Ticks Rotated: The Ticks Are Rotated 219# Accordingly To The Tick Marks Positions 220# SM_DRAW_SECTORS: Different Intervals Are Painted In Differend Colours 221# (Every Sector Of The Circle Has Its Own Colour) 222# SM_DRAW_PARTIAL_SECTORS: Every Interval Has Its Own Colour, But Only 223# A Circle Corona Is Painted Near The Ticks 224# SM_DRAW_HAND: The Hand (Arrow Indicator) Is Drawn 225# SM_DRAW_SHADOW: A Shadow For The Hand Is Drawn 226# SM_DRAW_PARTIAL_FILLER: A Circle Corona That Follows The Hand Position 227# Is Drawn Near The Ticks 228# SM_DRAW_SECONDARY_TICKS: Intermediate (Smaller) Ticks Are Drawn Between 229# Principal Ticks 230# SM_DRAW_MIDDLE_TEXT: Some Text Is Printed In The Middle Of The Control 231# Near The Center 232# SM_DRAW_MIDDLE_ICON: An Icon Is Drawn In The Middle Of The Control Near 233# The Center 234# SM_DRAW_GRADIENT: A Gradient Of Colours Will Fill The Control 235# SM_DRAW_FANCY_TICKS: With This Style You Can Use XML Tags To Create 236# Some Custom Text And Draw It At The Ticks Position. 237# See wx.lib.fancytext For The Tags. 238 239SM_ROTATE_TEXT = 1 240""" Draws the ticks rotated: the ticks are rotated accordingly to the tick marks positions. """ 241SM_DRAW_SECTORS = 2 242""" Different intervals are painted in differend colours (every sector of the circle has its own colour). """ 243SM_DRAW_PARTIAL_SECTORS = 4 244""" Every interval has its own colour, but only a circle corona is painted near the ticks. """ 245SM_DRAW_HAND = 8 246""" The hand (arrow indicator) is drawn. """ 247SM_DRAW_SHADOW = 16 248""" A shadow for the hand is drawn. """ 249SM_DRAW_PARTIAL_FILLER = 32 250""" A circle corona that follows the hand position is drawn near the ticks. """ 251SM_DRAW_SECONDARY_TICKS = 64 252""" Intermediate (smaller) ticks are drawn between principal ticks. """ 253SM_DRAW_MIDDLE_TEXT = 128 254""" Some text is printed in the middle of the control near the center. """ 255SM_DRAW_MIDDLE_ICON = 256 256""" An icon is drawn in the middle of the control near the center. """ 257SM_DRAW_GRADIENT = 512 258""" A gradient of colours will fill the control. """ 259SM_DRAW_FANCY_TICKS = 1024 260""" With this style you can use xml tags to create some custom text and draw it at the ticks position. See :mod:`lib.fancytext` for the tags. """ 261 262#---------------------------------------------------------------------- 263# Event Binding 264#---------------------------------------------------------------------- 265# SM_MOUSE_TRACK: The Mouse Left Click/Drag Allow You To Change The 266# SpeedMeter Value Interactively 267 268SM_MOUSE_TRACK = 1 269""" Flag to allow the left/right click of the mouse to change the :class:`SpeedMeter` value interactively. """ 270 271fontfamily = list(range(70, 78)) 272familyname = ["default", "decorative", "roman", "script", "swiss", "modern", "teletype"] 273 274weights = list(range(90, 93)) 275weightsname = ["normal", "light", "bold"] 276 277styles = [90, 93, 94] 278stylesname = ["normal", "italic", "slant"] 279 280#---------------------------------------------------------------------- 281# BUFFERENDWINDOW Class 282# This Class Has Been Taken From The wxPython Wiki, And Slightly 283# Adapted To Fill My Needs. See: 284# 285# http://wiki.wxpython.org/index.cgi/DoubleBufferedDrawing 286# 287# For More Info About DC And Double Buffered Drawing. 288#---------------------------------------------------------------------- 289 290class BufferedWindow(wx.Window): 291 """ 292 A buffered window class. 293 294 To use it, subclass it and define a `Draw(dc)` method that takes a `dc` 295 to draw to. In that method, put the code needed to draw the picture 296 you want. The window will automatically be double buffered, and the 297 screen will be automatically updated when a Paint event is received. 298 299 When the drawing needs to change, you app needs to call the 300 :meth:`BufferedWindow.UpdateDrawing() <BufferedWindow.UpdateDrawing>` method. Since the drawing is stored in a bitmap, you 301 can also save the drawing to file by calling the 302 `SaveToFile(self, file_name, file_type)` method. 303 """ 304 305 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, 306 style=wx.NO_FULL_REPAINT_ON_RESIZE, bufferedstyle=SM_BUFFERED_DC): 307 """ 308 Default class constructor. 309 310 :param `parent`: parent window. Must not be ``None``; 311 :param `id`: window identifier. A value of -1 indicates a default value; 312 :param `pos`: the control position. A value of (-1, -1) indicates a default position, 313 chosen by either the windowing system or wxPython, depending on platform; 314 :param `size`: the control size. A value of (-1, -1) indicates a default size, 315 chosen by either the windowing system or wxPython, depending on platform; 316 :param `style`: the window style; 317 :param `bufferedstyle`: if set to ``SM_BUFFERED_DC``, double-buffering will 318 be used. 319 """ 320 321 wx.Window.__init__(self, parent, id, pos, size, style) 322 323 self.Bind(wx.EVT_PAINT, self.OnPaint) 324 self.Bind(wx.EVT_SIZE, self.OnSize) 325 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) 326 327 self._isWindowCreated = False 328 if '__WXGTK__' in wx.PlatformInfo: 329 self.Bind(wx.EVT_WINDOW_CREATE, self.doSetWindowCreated) 330 else: 331 # OnSize called to make sure the buffer is initialized. 332 # This might result in OnSize getting called twice on some 333 # platforms at initialization, but little harm done. 334 self.doSetWindowCreated(None) 335 336 337 def doSetWindowCreated(self, evt): 338 """ 339 Method to call OnSize on GTK when window is created. 340 """ 341 self._isWindowCreated = True 342 self.OnSize(None) 343 344 345 def Draw(self, dc): 346 """ 347 This method should be overridden when sub-classed. 348 349 :param `dc`: an instance of :class:`wx.DC`. 350 """ 351 pass 352 353 354 def OnPaint(self, event): 355 """ 356 Handles the ``wx.EVT_PAINT`` event for :class:`BufferedWindow`. 357 358 :param `event`: a :class:`PaintEvent` event to be processed. 359 """ 360 361 if self._bufferedstyle == SM_BUFFERED_DC: 362 dc = wx.BufferedPaintDC(self, self._Buffer) 363 else: 364 dc = wx.PaintDC(self) 365 dc.DrawBitmap(self._Buffer,0,0) 366 367 368 def OnSize(self,event): 369 """ 370 Handles the ``wx.EVT_SIZE`` event for :class:`BufferedWindow`. 371 372 :param `event`: a :class:`wx.SizeEvent` event to be processed. 373 """ 374 375 self.Width, self.Height = self.GetClientSize() 376 377 # Make new off screen bitmap: this bitmap will always have the 378 # current drawing in it, so it can be used to save the image to 379 # a file, or whatever. 380 381 # Some platforms object to creating bitmaps with size < (1,1) 382 self.Width = max(self.Width, 1) 383 self.Height = max(self.Height, 1) 384 385 self._Buffer = wx.Bitmap(self.Width, self.Height) 386 if self._isWindowCreated: 387 self.UpdateDrawing() 388 389 390 def UpdateDrawing(self): 391 """ 392 This would get called if the drawing needed to change, for whatever reason. 393 394 The idea here is that the drawing is based on some data generated 395 elsewhere in the system. if that data changes, the drawing needs to 396 be updated. 397 """ 398 399 if self._bufferedstyle == SM_BUFFERED_DC: 400 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) 401 if 'wxMSW' in wx.PlatformInfo: 402 dc = wx.GCDC(dc) 403 self.Draw(dc) 404 else: 405 # update the buffer 406 dc = wx.MemoryDC() 407 dc.SelectObject(self._Buffer) 408 409 if 'wxMSW' in wx.PlatformInfo: 410 dc = wx.GCDC(dc) 411 self.Draw(dc) 412 # update the screen 413 wx.ClientDC(self).Blit(0, 0, self.Width, self.Height, dc, 0, 0) 414 415 416#---------------------------------------------------------------------- 417# SPEEDMETER Class 418# This Is The Main Class Implementation. See __init__() Method For 419# Details. 420#---------------------------------------------------------------------- 421 422class SpeedMeter(BufferedWindow): 423 """ 424 :class:`SpeedMeter` tries to reproduce the behavior of some car controls (but not only), 425 by creating an "angular" control (actually, circular). 426 427 This is the main class implementation. 428 """ 429 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, 430 size=wx.DefaultSize, agwStyle=SM_DRAW_HAND, 431 bufferedstyle=SM_BUFFERED_DC, 432 mousestyle=0): 433 """ 434 Default class constructor. 435 436 :param `parent`: parent window. Must not be ``None``; 437 :param `id`: window identifier. A value of -1 indicates a default value; 438 :param `pos`: the control position. A value of (-1, -1) indicates a default position, 439 chosen by either the windowing system or wxPython, depending on platform; 440 :param `size`: the control size. A value of (-1, -1) indicates a default size, 441 chosen by either the windowing system or wxPython, depending on platform; 442 :param `agwStyle`: this value specifies the :class:`SpeedMeter` styles, and can be a 443 combination of the following bits: 444 445 =========================== =========== ================================================== 446 Window Styles Hex Value Description 447 =========================== =========== ================================================== 448 ``SM_ROTATE_TEXT`` 0x1 Draws the ticks rotated: the ticks are rotated accordingly to the tick marks positions. 449 ``SM_DRAW_SECTORS`` 0x2 Different intervals are painted in differend colours (every sector of the circle has its own colour). 450 ``SM_DRAW_PARTIAL_SECTORS`` 0x4 Every interval has its own colour, but only a circle corona is painted near the ticks. 451 ``SM_DRAW_HAND`` 0x8 The hand (arrow indicator) is drawn. 452 ``SM_DRAW_SHADOW`` 0x10 A shadow for the hand is drawn. 453 ``SM_DRAW_PARTIAL_FILLER`` 0x20 A circle corona that follows the hand position is drawn near the ticks. 454 ``SM_DRAW_SECONDARY_TICKS`` 0x40 Intermediate (smaller) ticks are drawn between principal ticks. 455 ``SM_DRAW_MIDDLE_TEXT`` 0x80 Some text is printed in the middle of the control near the center. 456 ``SM_DRAW_MIDDLE_ICON`` 0x100 An icon is drawn in the middle of the control near the center. 457 ``SM_DRAW_GRADIENT`` 0x200 A gradient of colours will fill the control. 458 ``SM_DRAW_FANCY_TICKS`` 0x400 With this style you can use xml tags to create some custom text and draw it at the ticks position. See :mod:`lib.fancytext` for the tags. 459 =========================== =========== ================================================== 460 461 :param `bufferedstyle`: this value allows you to use the normal :class:`PaintDC` or the 462 double buffered drawing options: 463 464 =========================== =========== ================================================== 465 Buffered Styles Hex Value Description 466 =========================== =========== ================================================== 467 ``SM_NORMAL_DC`` 0x0 Uses the normal :class:`PaintDC` 468 ``SM_BUFFERED_DC`` 0x1 Uses the double buffered drawing style 469 =========================== =========== ================================================== 470 471 :param `mousestyle`: this value allows you to use the mouse to change the :class:`SpeedMeter` 472 value interactively with left click/drag events. If set to ``SM_MOUSE_TRACK``, the mouse 473 left click/drag allow you to change the :class:`SpeedMeter` value interactively. 474 """ 475 476 self._agwStyle = agwStyle 477 self._bufferedstyle = bufferedstyle 478 self._mousestyle = mousestyle 479 480 if self._agwStyle & SM_DRAW_SECTORS and self._agwStyle & SM_DRAW_GRADIENT: 481 errstr = "\nERROR: Incompatible Options: SM_DRAW_SECTORS Can Not Be Used In " 482 errstr = errstr + "Conjunction With SM_DRAW_GRADIENT." 483 raise Exception(errstr) 484 485 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS and self._agwStyle & SM_DRAW_SECTORS: 486 errstr = "\nERROR: Incompatible Options: SM_DRAW_SECTORS Can Not Be Used In " 487 errstr = errstr + "Conjunction With SM_DRAW_PARTIAL_SECTORS." 488 raise Exception(errstr) 489 490 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS and self._agwStyle & SM_DRAW_PARTIAL_FILLER: 491 errstr = "\nERROR: Incompatible Options: SM_DRAW_PARTIAL_SECTORS Can Not Be Used In " 492 errstr = errstr + "Conjunction With SM_DRAW_PARTIAL_FILLER." 493 raise Exception(errstr) 494 495 if self._agwStyle & SM_DRAW_FANCY_TICKS and self._agwStyle & SM_ROTATE_TEXT: 496 errstr = "\nERROR: Incompatible Options: SM_DRAW_FANCY_TICKS Can Not Be Used In " 497 errstr = errstr + "Conjunction With SM_ROTATE_TEXT." 498 raise Exception(errstr) 499 500 if self._agwStyle & SM_DRAW_SHADOW and self._agwStyle & SM_DRAW_HAND == 0: 501 errstr = "\nERROR: Incompatible Options: SM_DRAW_SHADOW Can Be Used Only In " 502 errstr = errstr + "Conjunction With SM_DRAW_HAND." 503 raise Exception(errstr) 504 505 if self._agwStyle & SM_DRAW_FANCY_TICKS: 506 wx.lib.colourdb.updateColourDB() 507 508 self.SetAngleRange() 509 self.SetIntervals() 510 self.SetSpeedValue() 511 self.SetIntervalColours() 512 self.SetArcColour() 513 self.SetTicks() 514 self.SetTicksFont() 515 self.SetTicksColour() 516 self.SetSpeedBackground() 517 self.SetHandColour() 518 self.SetShadowColour() 519 self.SetFillerColour() 520 self.SetDirection() 521 self.SetNumberOfSecondaryTicks() 522 self.SetMiddleText() 523 self.SetMiddleTextFont() 524 self.SetMiddleTextColour() 525 self.SetFirstGradientColour() 526 self.SetSecondGradientColour() 527 self.SetHandStyle() 528 self.DrawExternalArc() 529 530 # If no initial size is set then use (1,1) instead of the (20,20) that 531 # wxWindow will default to. This is so we don't set the initial 532 # self.scale based on (20,20) but will instead wait until the initial 533 # size is set by the sizer. See Draw() implementation below. 534 if size == wx.DefaultSize: 535 size = wx.Size(1,1) 536 537 BufferedWindow.__init__(self, parent, id, pos, size, 538 style=wx.NO_FULL_REPAINT_ON_RESIZE, 539 bufferedstyle=bufferedstyle) 540 if self._mousestyle & SM_MOUSE_TRACK: 541 self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseMotion) 542 543 544 def Draw(self, dc): 545 """ 546 Draws everything on the empty bitmap. 547 Here all the chosen styles are applied. 548 549 :param `dc`: an instance of :class:`wx.DC`. 550 """ 551 552 size = self.GetClientSize() 553 554 if size.x < 2 or size.y < 2: 555 return 556 557 new_dim = size.Get() 558 559 if not hasattr(self, "dim"): 560 self.dim = new_dim 561 562 self.scale = min([float(new_dim[0]) / self.dim[0], 563 float(new_dim[1]) / self.dim[1]]) 564 565 # Create An Empty Bitmap 566 self.faceBitmap = wx.Bitmap(size.width, size.height) 567 568 speedbackground = self.GetSpeedBackground() 569 # Set Background Of The Control 570 dc.SetBackground(wx.Brush(speedbackground)) 571 dc.Clear() 572 573 centerX = self.faceBitmap.GetWidth()/2 574 centerY = self.faceBitmap.GetHeight()/2 575 576 self.CenterX = centerX 577 self.CenterY = centerY 578 579 # Get The Radius Of The Sector. Set It A Bit Smaller To Correct Draw After 580 radius = min(centerX, centerY) - 2 581 582 self.Radius = radius 583 self.Radius = radius 584 585 # Get The Angle Of Existance Of The Sector 586 anglerange = self.GetAngleRange() 587 startangle = anglerange[1] 588 endangle = anglerange[0] 589 590 self.StartAngle = startangle 591 self.EndAngle = endangle 592 593 # Initialize The Colours And The Intervals - Just For Reference To The 594 # Children Functions 595 colours = None 596 intervals = None 597 598 if self._agwStyle & SM_DRAW_SECTORS or self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 599 # Get The Intervals Colours 600 colours = self.GetIntervalColours()[:] 601 602 textangles = [] 603 colourangles = [] 604 xcoords = [] 605 ycoords = [] 606 607 # Get The Intervals (Partial Sectors) 608 intervals = self.GetIntervals()[:] 609 610 start = min(intervals) 611 end = max(intervals) 612 span = end - start 613 614 self.StartValue = start 615 self.EndValue = end 616 617 self.Span = span 618 619 # Get The Current Value For The SpeedMeter 620 currentvalue = self.GetSpeedValue() 621 622 # Get The Direction Of The SpeedMeter 623 direction = self.GetDirection() 624 if direction == "Reverse": 625 intervals.reverse() 626 627 if self._agwStyle & SM_DRAW_SECTORS or self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 628 colours.reverse() 629 630 currentvalue = end - currentvalue 631 632 # This Because DrawArc Does Not Draw Last Point 633 offset = 0.1*self.scale/180.0 634 635 xstart, ystart = self.CircleCoords(radius+1, -endangle, centerX, centerY) 636 xend, yend = self.CircleCoords(radius+1, -startangle-offset, centerX, centerY) 637 638 # Calculate The Angle For The Current Value Of SpeedMeter 639 accelangle = (currentvalue - start)/float(span)*(startangle-endangle) - startangle 640 641 dc.SetPen(wx.TRANSPARENT_PEN) 642 643 if self._agwStyle & SM_DRAW_PARTIAL_FILLER: 644 645 # Get Some Data For The Partial Filler 646 fillercolour = self.GetFillerColour() 647 fillerendradius = radius - 10.0*self.scale 648 fillerstartradius = radius 649 650 if direction == "Advance": 651 fillerstart = accelangle 652 fillerend = -startangle 653 else: 654 fillerstart = -endangle 655 fillerend = accelangle 656 657 xs1, ys1 = self.CircleCoords(fillerendradius, fillerstart, centerX, centerY) 658 xe1, ye1 = self.CircleCoords(fillerendradius, fillerend, centerX, centerY) 659 xs2, ys2 = self.CircleCoords(fillerstartradius, fillerstart, centerX, centerY) 660 xe2, ye2 = self.CircleCoords(fillerstartradius, fillerend, centerX, centerY) 661 662 # Get The Sector In Which The Current Value Is 663 intersection = self.GetIntersection(currentvalue, intervals) 664 sectorradius = radius - 10*self.scale 665 666 else: 667 668 sectorradius = radius 669 670 if self._agwStyle & SM_DRAW_PARTIAL_FILLER: 671 # Draw The Filler (Both In "Advance" And "Reverse" Directions) 672 673 dc.SetBrush(wx.Brush(fillercolour)) 674 dc.DrawArc(xs2, ys2, xe2, ye2, centerX, centerY) 675 676 if self._agwStyle & SM_DRAW_SECTORS == 0: 677 dc.SetBrush(wx.Brush(speedbackground)) 678 xclean1, yclean1 = self.CircleCoords(sectorradius, -endangle, centerX, centerY) 679 xclean2, yclean2 = self.CircleCoords(sectorradius, -startangle-offset, centerX, centerY) 680 dc.DrawArc(xclean1, yclean1, xclean2, yclean2, centerX, centerY) 681 682 683 # This Is Needed To Fill The Partial Sector Correctly 684 xold, yold = self.CircleCoords(radius, startangle+endangle, centerX, centerY) 685 686 # Draw The Sectors 687 for ii, interval in enumerate(intervals): 688 689 if direction == "Advance": 690 current = interval - start 691 else: 692 current = end - interval 693 694 angle = (current/float(span))*(startangle-endangle) - startangle 695 angletext = -((pi/2.0) + angle)*180/pi 696 textangles.append(angletext) 697 colourangles.append(angle) 698 xtick, ytick = self.CircleCoords(radius, angle, centerX, centerY) 699 700 # Keep The Coordinates, We Will Need Them After To Position The Ticks 701 xcoords.append(xtick) 702 ycoords.append(ytick) 703 x = xtick 704 y = ytick 705 706 if self._agwStyle & SM_DRAW_SECTORS: 707 if self._agwStyle & SM_DRAW_PARTIAL_FILLER: 708 if direction == "Advance": 709 if current > currentvalue: 710 x, y = self.CircleCoords(radius, angle, centerX, centerY) 711 else: 712 x, y = self.CircleCoords(sectorradius, angle, centerX, centerY) 713 else: 714 if current < end - currentvalue: 715 x, y = self.CircleCoords(radius, angle, centerX, centerY) 716 else: 717 x, y = self.CircleCoords(sectorradius, angle, centerX, centerY) 718 else: 719 x, y = self.CircleCoords(radius, angle, centerX, centerY) 720 721 722 if ii > 0: 723 if self._agwStyle & SM_DRAW_PARTIAL_FILLER and ii == intersection: 724 # We Got The Interval In Which There Is The Current Value. If We Choose 725 # A "Reverse" Direction, First We Draw The Partial Sector, Next The Filler 726 727 dc.SetBrush(wx.Brush(speedbackground)) 728 729 if direction == "Reverse": 730 if self._agwStyle & SM_DRAW_SECTORS: 731 dc.SetBrush(wx.Brush(colours[ii-1])) 732 733 dc.DrawArc(xe2, ye2, xold, yold, centerX, centerY) 734 735 if self._agwStyle & SM_DRAW_SECTORS: 736 dc.SetBrush(wx.Brush(colours[ii-1])) 737 else: 738 dc.SetBrush(wx.Brush(speedbackground)) 739 740 741 dc.DrawArc(xs1, ys1, xe1, ye1, centerX, centerY) 742 743 if self._agwStyle & SM_DRAW_SECTORS: 744 dc.SetBrush(wx.Brush(colours[ii-1])) 745 # Here We Draw The Rest Of The Sector In Which The Current Value Is 746 if direction == "Advance": 747 dc.DrawArc(xs1, ys1, x, y, centerX, centerY) 748 x = xs1 749 y = ys1 750 else: 751 dc.DrawArc(xe2, ye2, x, y, centerX, centerY) 752 753 elif self._agwStyle & SM_DRAW_SECTORS: 754 dc.SetBrush(wx.Brush(colours[ii-1])) 755 756 # Here We Still Use The SM_DRAW_PARTIAL_FILLER Style, But We Are Not 757 # In The Sector Where The Current Value Resides 758 if self._agwStyle & SM_DRAW_PARTIAL_FILLER and ii != intersection: 759 if direction == "Advance": 760 dc.DrawArc(x, y, xold, yold, centerX, centerY) 761 else: 762 if ii < intersection: 763 dc.DrawArc(x, y, xold, yold, centerX, centerY) 764 765 # This Is The Case Where No SM_DRAW_PARTIAL_FILLER Has Been Chosen 766 else: 767 dc.DrawArc(x, y, xold, yold, centerX, centerY) 768 769 else: 770 if self._agwStyle & SM_DRAW_PARTIAL_FILLER and self._agwStyle & SM_DRAW_SECTORS: 771 dc.SetBrush(wx.Brush(fillercolour)) 772 dc.DrawArc(xs2, ys2, xe2, ye2, centerX, centerY) 773 x, y = self.CircleCoords(sectorradius, angle, centerX, centerY) 774 dc.SetBrush(wx.Brush(colours[ii])) 775 dc.DrawArc(xs1, ys1, xe1, ye1, centerX, centerY) 776 x = xs2 777 y = ys2 778 779 xold = x 780 yold = y 781 782 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 783 784 sectorendradius = radius - 10.0*self.scale 785 sectorstartradius = radius 786 787 xps, yps = self.CircleCoords(sectorstartradius, angle, centerX, centerY) 788 789 if ii > 0: 790 dc.SetBrush(wx.Brush(colours[ii-1])) 791 dc.DrawArc(xps, yps, xpsold, ypsold, centerX, centerY) 792 793 xpsold = xps 794 ypsold = yps 795 796 797 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 798 799 xps1, yps1 = self.CircleCoords(sectorendradius, -endangle+2*offset, centerX, centerY) 800 xps2, yps2 = self.CircleCoords(sectorendradius, -startangle-2*offset, centerX, centerY) 801 802 dc.SetBrush(wx.Brush(speedbackground)) 803 dc.DrawArc(xps1, yps1, xps2, yps2, centerX, centerY) 804 805 806 if self._agwStyle & SM_DRAW_GRADIENT: 807 808 dc.SetPen(wx.TRANSPARENT_PEN) 809 810 xcurrent, ycurrent = self.CircleCoords(radius, accelangle, centerX, centerY) 811 812 # calculate gradient coefficients 813 col2 = self.GetSecondGradientColour() 814 col1 = self.GetFirstGradientColour() 815 816 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue()) 817 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue()) 818 819 flrect = float(radius+self.scale) 820 821 numsteps = 200 822 823 rstep = float((r2 - r1)) / numsteps 824 gstep = float((g2 - g1)) / numsteps 825 bstep = float((b2 - b1)) / numsteps 826 827 rf, gf, bf = 0, 0, 0 828 829 radiusteps = flrect/numsteps 830 interface = 0 831 832 for ind in range(numsteps+1): 833 currCol = (r1 + rf, g1 + gf, b1 + bf) 834 dc.SetBrush(wx.Brush(currCol)) 835 836 gradradius = flrect - radiusteps*ind 837 xst1, yst1 = self.CircleCoords(gradradius, -endangle, centerX, centerY) 838 xen1, yen1 = self.CircleCoords(gradradius, -startangle-offset, centerX, centerY) 839 840 if self._agwStyle & SM_DRAW_PARTIAL_FILLER: 841 if gradradius >= fillerendradius: 842 if direction == "Advance": 843 dc.DrawArc(xstart, ystart, xcurrent, ycurrent, centerX, centerY) 844 else: 845 dc.DrawArc(xcurrent, ycurrent, xend, yend, centerX, centerY) 846 else: 847 if interface == 0: 848 interface = 1 849 myradius = fillerendradius + 1 850 xint1, yint1 = self.CircleCoords(myradius, -endangle, centerX, centerY) 851 xint2, yint2 = self.CircleCoords(myradius, -startangle-offset, centerX, centerY) 852 dc.DrawArc(xint1, yint1, xint2, yint2, centerX, centerY) 853 854 dc.DrawArc(xst1, yst1, xen1, yen1, centerX, centerY) 855 else: 856 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 857 if gradradius <= sectorendradius: 858 if interface == 0: 859 interface = 1 860 myradius = sectorendradius + 1 861 xint1, yint1 = self.CircleCoords(myradius, -endangle, centerX, centerY) 862 xint2, yint2 = self.CircleCoords(myradius, -startangle-offset, centerX, centerY) 863 dc.DrawArc(xint1, yint1, xint2, yint2, centerX, centerY) 864 else: 865 dc.DrawArc(xst1, yst1, xen1, yen1, centerX, centerY) 866 else: 867 dc.DrawArc(xst1, yst1, xen1, yen1, centerX, centerY) 868 869 rf = rf + rstep 870 gf = gf + gstep 871 bf = bf + bstep 872 873 textheight = 0 874 875 # Get The Ticks And The Ticks Colour 876 ticks = self.GetTicks()[:] 877 tickscolour = self.GetTicksColour() 878 879 if direction == "Reverse": 880 ticks.reverse() 881 882 if self._agwStyle & SM_DRAW_SECONDARY_TICKS: 883 ticknum = self.GetNumberOfSecondaryTicks() 884 oldinterval = intervals[0] 885 886 dc.SetPen(wx.Pen(tickscolour, 1)) 887 dc.SetBrush(wx.Brush(tickscolour)) 888 dc.SetTextForeground(tickscolour) 889 890 # Get The Font For The Ticks 891 tfont, fontsize = self.GetTicksFont() 892 tfont = tfont[0] 893 myfamily = tfont.GetFamily() 894 895 fsize = self.scale*fontsize 896 tfont.SetPointSize(int(fsize)) 897 tfont.SetFamily(myfamily) 898 dc.SetFont(tfont) 899 900 if self._agwStyle & SM_DRAW_FANCY_TICKS: 901 facename = tfont.GetFaceName() 902 ffamily = familyname[fontfamily.index(tfont.GetFamily())] 903 fweight = weightsname[weights.index(tfont.GetWeight())] 904 fstyle = stylesname[styles.index(tfont.GetStyle())] 905 fcolour = wx.TheColourDatabase.FindName(tickscolour) 906 907 textheight = 0 908 909 # Draw The Ticks And The Markers (Text Ticks) 910 for ii, angles in enumerate(textangles): 911 912 strings = ticks[ii] 913 if self._agwStyle & SM_DRAW_FANCY_TICKS == 0: 914 width, height, dummy, dummy = dc.GetFullTextExtent(strings, tfont) 915 textheight = height 916 else: 917 width, height, dummy = fancytext.GetFullExtent(strings, dc) 918 textheight = height 919 920 lX = dc.GetCharWidth()/2.0 921 lY = dc.GetCharHeight()/2.0 922 923 if self._agwStyle & SM_ROTATE_TEXT: 924 angis = colourangles[ii] - float(width)/(2.0*radius) 925 x, y = self.CircleCoords(radius-10.0*self.scale, angis, centerX, centerY) 926 dc.DrawRotatedText(strings, x, y, angles) 927 else: 928 angis = colourangles[ii] 929 if self._agwStyle & SM_DRAW_FANCY_TICKS == 0: 930 x, y = self.CircleCoords(radius-10*self.scale, angis, centerX, centerY) 931 lX = lX*len(strings) 932 x = x - lX - width*cos(angis)/2.0 933 y = y - lY - height*sin(angis)/2.0 934 935 if self._agwStyle & SM_DRAW_FANCY_TICKS: 936 fancystr = '<font family="' + ffamily + '" size="' + str(int(fsize)) + '" weight="' + fweight + '"' 937 fancystr = fancystr + ' color="' + fcolour + '"' + ' style="' + fstyle + '"> ' + strings + ' </font>' 938 939 width, height, dummy = fancytext.GetFullExtent(fancystr, dc) 940 x, y = self.CircleCoords(radius-10*self.scale, angis, centerX, centerY) 941 x = x - width/2.0 - width*cos(angis)/2.0 942 y = y - height/2.0 - height*sin(angis)/2.0 943 fancytext.RenderToDC(fancystr, dc, x, y) 944 else: 945 dc.DrawText(strings, x, y) 946 947 # This Is The Small Rectangle --> Tick Mark 948 rectangle = colourangles[ii] + pi/2.0 949 950 sinrect = sin(rectangle) 951 cosrect = cos(rectangle) 952 x1 = xcoords[ii] - self.scale*cosrect 953 y1 = ycoords[ii] - self.scale*sinrect 954 x2 = x1 + 3*self.scale*cosrect 955 y2 = y1 + 3*self.scale*sinrect 956 x3 = x1 - 10*self.scale*sinrect 957 y3 = y1 + 10*self.scale*cosrect 958 x4 = x3 + 3*self.scale*cosrect 959 y4 = y3 + 3*self.scale*sinrect 960 961 points = [(x1, y1), (x2, y2), (x4, y4), (x3, y3)] 962 963 dc.DrawPolygon(points) 964 965 if self._agwStyle & SM_DRAW_SECONDARY_TICKS: 966 if ii > 0: 967 newinterval = intervals[ii] 968 oldinterval = intervals[ii-1] 969 970 spacing = (newinterval - oldinterval)/float(ticknum+1) 971 972 for tcount in range(ticknum): 973 if direction == "Advance": 974 oldinterval = (oldinterval + spacing) 975 stint = oldinterval - start 976 else: 977 #oldinterval = start + (oldinterval + spacing) 978 oldinterval = (oldinterval + spacing) 979 stint = end - oldinterval 980 981 angle = (stint/float(span))*(startangle-endangle) - startangle 982 rectangle = angle + pi/2.0 983 sinrect = sin(rectangle) 984 cosrect = cos(rectangle) 985 xt, yt = self.CircleCoords(radius, angle, centerX, centerY) 986 x1 = xt - self.scale*cosrect 987 y1 = yt - self.scale*sinrect 988 x2 = x1 + self.scale*cosrect 989 y2 = y1 + self.scale*sinrect 990 x3 = x1 - 6*self.scale*sinrect 991 y3 = y1 + 6*self.scale*cosrect 992 x4 = x3 + self.scale*cosrect 993 y4 = y3 + self.scale*sinrect 994 995 points = [(x1, y1), (x2, y2), (x4, y4), (x3, y3)] 996 997 dc.DrawPolygon(points) 998 999 oldinterval = newinterval 1000 1001 tfont.SetPointSize(fontsize) 1002 tfont.SetFamily(myfamily) 1003 1004 self.SetTicksFont(tfont) 1005 1006 # Draw The External Arc 1007 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1008 1009 if self._drawarc: 1010 dc.SetPen(wx.Pen(self.GetArcColour(), 2.0)) 1011 # If It's Not A Complete Circle, Draw The Connecting Lines And The Arc 1012 if abs(abs(startangle - endangle) - 2*pi) > 1.0/180.0: 1013 dc.DrawArc(xstart, ystart, xend, yend, centerX, centerY) 1014 dc.DrawLine(xstart, ystart, centerX, centerY) 1015 dc.DrawLine(xend, yend, centerX, centerY) 1016 else: 1017 # Draw A Circle, Is A 2*pi Extension Arc = Complete Circle 1018 dc.DrawCircle(centerX, centerY, radius) 1019 1020 1021 # Here We Draw The Text In The Middle, Near The Start Of The Arrow (If Present) 1022 # This Is Like The "Km/h" Or "mph" Text In The Cars 1023 if self._agwStyle & SM_DRAW_MIDDLE_TEXT: 1024 1025 middlecolour = self.GetMiddleTextColour() 1026 middletext = self.GetMiddleText() 1027 middleangle = (startangle + endangle)/2.0 1028 1029 middlefont, middlesize = self.GetMiddleTextFont() 1030 middlesize = self.scale*middlesize 1031 middlefont.SetPointSize(int(middlesize)) 1032 dc.SetFont(middlefont) 1033 1034 mw, mh, dummy, dummy = dc.GetFullTextExtent(middletext, middlefont) 1035 1036 newx = centerX + 1.5*mw*cos(middleangle) - mw/2.0 1037 newy = centerY - 1.5*mh*sin(middleangle) - mh/2.0 1038 dc.SetTextForeground(middlecolour) 1039 dc.DrawText(middletext, newx, newy) 1040 1041 # Here We Draw The Icon In The Middle, Near The Start Of The Arrow (If Present) 1042 # This Is Like The "Fuel" Icon In The Cars 1043 if self._agwStyle & SM_DRAW_MIDDLE_ICON: 1044 1045 middleicon = self.GetMiddleIcon() 1046 middlewidth, middleheight = self.GetMiddleIconDimens() 1047 middleicon.SetWidth(middlewidth*self.scale) 1048 middleicon.SetHeight(middleheight*self.scale) 1049 middleangle = (startangle + endangle)/2.0 1050 1051 mw = middleicon.GetWidth() 1052 mh = middleicon.GetHeight() 1053 1054 newx = centerX + 1.5*mw*cos(middleangle) - mw/2.0 1055 newy = centerY - 1.5*mh*sin(middleangle) - mh/2.0 1056 1057 dc.DrawIcon(middleicon, newx, newy) 1058 1059 # Restore Icon Dimension, If Not Something Strange Happens 1060 middleicon.SetWidth(middlewidth) 1061 middleicon.SetHeight(middleheight) 1062 1063 1064 # Requested To Draw The Hand 1065 if self._agwStyle & SM_DRAW_HAND: 1066 1067 handstyle = self.GetHandStyle() 1068 handcolour = self.GetHandColour() 1069 1070 # Calculate The Data For The Hand 1071 if textheight == 0: 1072 maxradius = radius-10*self.scale 1073 else: 1074 maxradius = radius-5*self.scale-textheight 1075 1076 xarr, yarr = self.CircleCoords(maxradius, accelangle, centerX, centerY) 1077 1078 if handstyle == "Arrow": 1079 x1, y1 = self.CircleCoords(maxradius, accelangle - 4.0/180, centerX, centerY) 1080 x2, y2 = self.CircleCoords(maxradius, accelangle + 4.0/180, centerX, centerY) 1081 x3, y3 = self.CircleCoords(maxradius+3*(abs(xarr-x1)), accelangle, centerX, centerY) 1082 1083 newx = centerX + 4*cos(accelangle)*self.scale 1084 newy = centerY + 4*sin(accelangle)*self.scale 1085 1086 else: 1087 1088 x1 = centerX + 4*self.scale*sin(accelangle) 1089 y1 = centerY - 4*self.scale*cos(accelangle) 1090 x2 = xarr 1091 y2 = yarr 1092 x3 = centerX - 4*self.scale*sin(accelangle) 1093 y3 = centerY + 4*self.scale*cos(accelangle) 1094 1095 x4, y4 = self.CircleCoords(5*self.scale*sqrt(3), accelangle+pi, centerX, centerY) 1096 1097 if self._agwStyle & SM_DRAW_SHADOW: 1098 1099 if handstyle == "Arrow": 1100 # Draw The Shadow 1101 shadowcolour = self.GetShadowColour() 1102 dc.SetPen(wx.Pen(shadowcolour, 5*log(self.scale+1))) 1103 dc.SetBrush(wx.Brush(shadowcolour)) 1104 shadowdistance = 2.0*self.scale 1105 dc.DrawLine(newx + shadowdistance, newy + shadowdistance, 1106 xarr + shadowdistance, yarr + shadowdistance) 1107 1108 dc.DrawPolygon([(x1+shadowdistance, y1+shadowdistance), 1109 (x2+shadowdistance, y2+shadowdistance), 1110 (x3+shadowdistance, y3+shadowdistance)]) 1111 else: 1112 # Draw The Shadow 1113 shadowcolour = self.GetShadowColour() 1114 dc.SetBrush(wx.Brush(shadowcolour)) 1115 dc.SetPen(wx.Pen(shadowcolour, 1.0)) 1116 shadowdistance = 1.5*self.scale 1117 1118 dc.DrawPolygon([(x1+shadowdistance, y1+shadowdistance), 1119 (x2+shadowdistance, y2+shadowdistance), 1120 (x3+shadowdistance, y3+shadowdistance), 1121 (x4+shadowdistance, y4+shadowdistance)]) 1122 1123 if handstyle == "Arrow": 1124 1125 dc.SetPen(wx.Pen(handcolour, 1.5)) 1126 1127 # Draw The Small Circle In The Center --> The Hand "Holder" 1128 dc.SetBrush(wx.Brush(speedbackground)) 1129 dc.DrawCircle(centerX, centerY, 4*self.scale) 1130 1131 dc.SetPen(wx.Pen(handcolour, 5*log(self.scale+1))) 1132 # Draw The "Hand", An Arrow 1133 dc.DrawLine(newx, newy, xarr, yarr) 1134 1135 # Draw The Arrow Pointer 1136 dc.SetBrush(wx.Brush(handcolour)) 1137 dc.DrawPolygon([(x1, y1), (x2, y2), (x3, y3)]) 1138 1139 else: 1140 1141 # Draw The Hand Pointer 1142 dc.SetPen(wx.Pen(handcolour, 1.5)) 1143 dc.SetBrush(wx.Brush(handcolour)) 1144 dc.DrawPolygon([(x1, y1), (x2, y2), (x3, y3), (x4, y4)]) 1145 1146 # Draw The Small Circle In The Center --> The Hand "Holder" 1147 dc.SetBrush(wx.Brush(speedbackground)) 1148 dc.DrawCircle(centerX, centerY, 4*self.scale) 1149 1150 1151 def SetIntervals(self, intervals=None): 1152 """ 1153 Sets the intervals for :class:`SpeedMeter` (main ticks numeric values). 1154 1155 :param `intervals`: a Python list of main ticks to be displayed. If defaulted 1156 to ``None``, the list `[0, 50, 100]` is used. 1157 """ 1158 1159 if intervals is None: 1160 intervals = [0, 50, 100] 1161 1162 self._intervals = intervals 1163 1164 1165 def GetIntervals(self): 1166 """ Returns the intervals for :class:`SpeedMeter`, a Python list of main ticks displayed. """ 1167 1168 return self._intervals 1169 1170 1171 def SetSpeedValue(self, value=None): 1172 """ 1173 Sets the current value for :class:`SpeedMeter`. 1174 1175 :param `value`: a floating point number representing the current value. If defaulted 1176 to ``None``, the :class:`SpeedMeter` value will be the middle point of the control range. 1177 """ 1178 1179 if value is None: 1180 value = (max(self._intervals) - min(self._intervals))/2.0 1181 else: 1182 if value < min(self._intervals): 1183 raise Exception("\nERROR: Value Is Smaller Than Minimum Element In Points List") 1184 return 1185 elif value > max(self._intervals): 1186 raise Exception("\nERROR: Value Is Greater Than Maximum Element In Points List") 1187 return 1188 1189 self._speedvalue = value 1190 try: 1191 self.UpdateDrawing() 1192 except: 1193 pass 1194 1195 1196 def GetSpeedValue(self): 1197 """ Returns the current value for :class:`SpeedMeter`. """ 1198 1199 return self._speedvalue 1200 1201 1202 def SetAngleRange(self, start=0, end=pi): 1203 """ 1204 Sets the range of existence for :class:`SpeedMeter`. 1205 1206 :param `start`: the starting angle, in radians; 1207 :param `end`: the ending angle, in radians. 1208 """ 1209 1210 self._anglerange = [start, end] 1211 1212 1213 def GetAngleRange(self): 1214 """ 1215 Returns the range of existence for :class:`SpeedMeter`. 1216 The returned values are in radians. 1217 """ 1218 1219 return self._anglerange 1220 1221 1222 def SetIntervalColours(self, colours=None): 1223 """ 1224 Sets the colours for the intervals. 1225 1226 :param `colours`: a Python list of colours. The length of this list should be 1227 the same as the number of circle sectors in :class:`SpeedMeter`. If defaulted to ``None``, 1228 all the intervals will have a white colour. 1229 1230 :note: Every interval (circle sector) should have a colour. 1231 """ 1232 1233 if colours is None: 1234 if not hasattr(self, "_anglerange"): 1235 errstr = "\nERROR: Impossible To Set Interval Colours," 1236 errstr = errstr + " Please Define The Intervals Ranges Before." 1237 raise Exception(errstr) 1238 return 1239 1240 colours = [wx.WHITE]*len(self._intervals) 1241 else: 1242 if len(colours) != len(self._intervals) - 1: 1243 errstr = "\nERROR: Length Of Colour List Does Not Match Length" 1244 errstr = errstr + " Of Intervals Ranges List." 1245 raise Exception(errstr) 1246 return 1247 1248 self._intervalcolours = colours 1249 1250 1251 def GetIntervalColours(self): 1252 """ Returns the colours for the intervals.""" 1253 1254 if hasattr(self, "_intervalcolours"): 1255 return self._intervalcolours 1256 else: 1257 raise Exception("\nERROR: No Interval Colours Have Been Defined") 1258 1259 1260 def SetTicks(self, ticks=None): 1261 """ 1262 Sets the ticks for :class:`SpeedMeter` intervals (main ticks string values). 1263 1264 :param `ticks`: a Python list of strings, representing the ticks values. 1265 If defaulted to ``None``, the ticks will be taken from the interval values. 1266 """ 1267 1268 if ticks is None: 1269 if not hasattr(self, "_anglerange"): 1270 errstr = "\nERROR: Impossible To Set Interval Ticks," 1271 errstr = errstr + " Please Define The Intervals Ranges Before." 1272 raise Exception(errstr) 1273 return 1274 1275 ticks = [] 1276 1277 for values in self._intervals: 1278 ticks.append(str(values)) 1279 1280 else: 1281 if len(ticks) != len(self._intervals): 1282 errstr = "\nERROR: Length Of Ticks List Does Not Match Length" 1283 errstr = errstr + " Of Intervals Ranges List." 1284 raise Exception(errstr) 1285 return 1286 1287 self._intervalticks = ticks 1288 1289 1290 def GetTicks(self): 1291 """ Returns the ticks for :class:`SpeedMeter` intervals (main ticks string values).""" 1292 1293 if hasattr(self, "_intervalticks"): 1294 return self._intervalticks 1295 else: 1296 raise Exception("\nERROR: No Interval Ticks Have Been Defined") 1297 1298 1299 def SetTicksFont(self, font=None): 1300 """ 1301 Sets the ticks font. 1302 1303 :param `font`: a valid :class:`wx.Font` object. If defaulted to ``None``, some standard 1304 font will be chosen automatically. 1305 """ 1306 1307 if font is None: 1308 self._originalfont = [wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False)] 1309 self._originalsize = 10 1310 else: 1311 self._originalfont = [font] 1312 self._originalsize = font.GetPointSize() 1313 1314 1315 def GetTicksFont(self): 1316 """ Returns the ticks font.""" 1317 1318 return self._originalfont[:], self._originalsize 1319 1320 1321 def SetTicksColour(self, colour=None): 1322 """ 1323 Sets the ticks colour. 1324 1325 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the 1326 ticks colour will be set as blue. 1327 """ 1328 1329 if colour is None: 1330 colour = wx.BLUE 1331 1332 self._tickscolour = colour 1333 1334 1335 def GetTicksColour(self): 1336 """ Returns the ticks colour.""" 1337 1338 return self._tickscolour 1339 1340 1341 def SetSpeedBackground(self, colour=None): 1342 """ 1343 Sets the background colour outside the :class:`SpeedMeter` control. 1344 1345 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the 1346 :class:`SpeedMeter` background will be taken from the system default. 1347 """ 1348 1349 if colour is None: 1350 colour = wx.SystemSettings.GetColour(0) 1351 1352 self._speedbackground = colour 1353 1354 1355 def GetSpeedBackground(self): 1356 """ Returns the background colour outside the :class:`SpeedMeter` control.""" 1357 1358 return self._speedbackground 1359 1360 1361 def SetHandColour(self, colour=None): 1362 """ 1363 Sets the hand (arrow indicator) colour. 1364 1365 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the arrow 1366 indicator will be red. 1367 """ 1368 1369 if colour is None: 1370 colour = wx.RED 1371 1372 self._handcolour = colour 1373 1374 1375 def GetHandColour(self): 1376 """ Returns the hand (arrow indicator) colour.""" 1377 1378 return self._handcolour 1379 1380 1381 def SetArcColour(self, colour=None): 1382 """ 1383 Sets the external arc colour (thicker line). 1384 1385 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the arc 1386 colour will be black. 1387 """ 1388 1389 if colour is None: 1390 colour = wx.BLACK 1391 1392 self._arccolour = colour 1393 1394 1395 def GetArcColour(self): 1396 """ Returns the external arc colour.""" 1397 1398 return self._arccolour 1399 1400 1401 def SetShadowColour(self, colour=None): 1402 """ 1403 Sets the hand's shadow colour. 1404 1405 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the shadow 1406 colour will be light grey. 1407 """ 1408 1409 if colour is None: 1410 colour = wx.Colour(150, 150, 150) 1411 1412 self._shadowcolour = colour 1413 1414 1415 def GetShadowColour(self): 1416 """ Returns the hand's shadow colour.""" 1417 1418 return self._shadowcolour 1419 1420 1421 def SetFillerColour(self, colour=None): 1422 """ 1423 Sets the partial filler colour. 1424 1425 A circle corona near the ticks will be filled with this colour, from 1426 the starting value to the current value of :class:`SpeedMeter`. 1427 1428 :param `colour`: a valid :class:`wx.Colour` object. 1429 """ 1430 1431 if colour is None: 1432 colour = wx.Colour(255, 150, 50) 1433 1434 self._fillercolour = colour 1435 1436 1437 def GetFillerColour(self): 1438 """ Returns the partial filler colour.""" 1439 1440 return self._fillercolour 1441 1442 1443 def SetDirection(self, direction=None): 1444 """ 1445 Sets the direction of advancing :class:`SpeedMeter` value. 1446 1447 :param `direction`: specifying "advance" will move the hand in clock-wise direction 1448 (like normal car speed control), while using "reverse" will move it counterclock-wise 1449 direction. If defaulted to ``None``, then "advance" will be used. 1450 """ 1451 1452 if direction is None: 1453 direction = "Advance" 1454 1455 if direction not in ["Advance", "Reverse"]: 1456 raise Exception('\nERROR: Direction Parameter Should Be One Of "Advance" Or "Reverse".') 1457 1458 self._direction = direction 1459 1460 1461 def GetDirection(self): 1462 """ Returns the direction of advancing :class:`SpeedMeter` value.""" 1463 1464 return self._direction 1465 1466 1467 def SetNumberOfSecondaryTicks(self, ticknum=None): 1468 """ 1469 Sets the number of secondary (intermediate) ticks. 1470 1471 :param `ticknum`: the number of intermediate ticks. If defaulted to ``None``, 1472 3 ticks are used. 1473 """ 1474 1475 if ticknum is None: 1476 ticknum = 3 1477 1478 if ticknum < 1: 1479 raise Exception("\nERROR: Number Of Ticks Must Be Greater Than 1.") 1480 1481 self._secondaryticks = ticknum 1482 1483 1484 def GetNumberOfSecondaryTicks(self): 1485 """ Returns the number of secondary (intermediate) ticks. """ 1486 1487 return self._secondaryticks 1488 1489 1490 def SetMiddleText(self, text=None): 1491 """ 1492 Sets the text to be drawn near the center of :class:`SpeedMeter`. 1493 1494 :param `text`: the text to be drawn near the center of :class:`SpeedMeter`. If 1495 defaulted to ``None``, an empty string will be used. 1496 """ 1497 1498 if text is None: 1499 text = "" 1500 1501 self._middletext = text 1502 1503 1504 def GetMiddleText(self): 1505 """ Returns the text to be drawn near the center of :class:`SpeedMeter`. """ 1506 1507 return self._middletext 1508 1509 1510 def SetMiddleTextFont(self, font=None): 1511 """ 1512 Sets the font for the text in the middle. 1513 1514 :param `font`: a valid :class:`wx.Font` object. If defaulted to ``None``, some 1515 standard font will be generated. 1516 """ 1517 1518 if font is None: 1519 self._middletextfont = wx.Font(1, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False) 1520 self._middletextsize = 10.0 1521 self._middletextfont.SetPointSize(self._middletextsize) 1522 else: 1523 self._middletextfont = font 1524 self._middletextsize = font.GetPointSize() 1525 self._middletextfont.SetPointSize(self._middletextsize) 1526 1527 1528 def GetMiddleTextFont(self): 1529 """ Returns the font for the text in the middle.""" 1530 1531 return self._middletextfont, self._middletextsize 1532 1533 1534 def SetMiddleTextColour(self, colour=None): 1535 """ 1536 Sets the colour for the text in the middle. 1537 1538 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, the text 1539 in the middle will be painted in blue. 1540 """ 1541 1542 if colour is None: 1543 colour = wx.BLUE 1544 1545 self._middlecolour = colour 1546 1547 1548 def GetMiddleTextColour(self): 1549 """ Returns the colour for the text in the middle.""" 1550 1551 return self._middlecolour 1552 1553 1554 def SetMiddleIcon(self, icon): 1555 """ 1556 Sets the icon to be drawn near the center of :class:`SpeedMeter`. 1557 1558 :param `icon`: a valid :class:`wx.Bitmap` object. 1559 """ 1560 1561 if icon.IsOk(): 1562 self._middleicon = icon 1563 else: 1564 raise Exception("\nERROR: Invalid Icon Passed To SpeedMeter.") 1565 1566 1567 def GetMiddleIcon(self): 1568 """ Returns the icon to be drawn near the center of :class:`SpeedMeter`. """ 1569 1570 return self._middleicon 1571 1572 1573 def GetMiddleIconDimens(self): 1574 """ Used internally. """ 1575 1576 return self._middleicon.GetWidth(), self._middleicon.GetHeight() 1577 1578 1579 def CircleCoords(self, radius, angle, centerX, centerY): 1580 """ 1581 Converts the input values into logical x, y coordinates. 1582 1583 :param `radius`: the :class:`SpeedMeter` radius; 1584 :param `angle`: the angular position of the mouse; 1585 :param `centerX`: the `x` position of the :class:`SpeedMeter` center; 1586 :param `centerX`: the `y` position of the :class:`SpeedMeter` center. 1587 """ 1588 1589 x = radius*cos(angle) + centerX 1590 y = radius*sin(angle) + centerY 1591 1592 return x, y 1593 1594 1595 def GetIntersection(self, current, intervals): 1596 """ Used internally. """ 1597 1598 if self.GetDirection() == "Reverse": 1599 interval = intervals[:] 1600 interval.reverse() 1601 else: 1602 interval = intervals 1603 1604 indexes = list(range(len(intervals))) 1605 try: 1606 intersection = [ind for ind in indexes if interval[ind] <= current <= interval[ind+1]] 1607 except: 1608 if self.GetDirection() == "Reverse": 1609 intersection = [len(intervals) - 1] 1610 else: 1611 intersection = [0] 1612 1613 return intersection[0] 1614 1615 1616 def SetFirstGradientColour(self, colour=None): 1617 """ 1618 Sets the first gradient colour (near the ticks). 1619 1620 :param `colour`: a valid :class:`wx.Colour` object. 1621 """ 1622 1623 if colour is None: 1624 colour = wx.Colour(145, 220, 200) 1625 1626 self._firstgradientcolour = colour 1627 1628 1629 def GetFirstGradientColour(self): 1630 """ Returns the first gradient colour (near the ticks). """ 1631 1632 return self._firstgradientcolour 1633 1634 1635 def SetSecondGradientColour(self, colour=None): 1636 """ 1637 Sets the second gradient colour (near the center). 1638 1639 :param `colour`: a valid :class:`wx.Colour` object. 1640 """ 1641 1642 if colour is None: 1643 colour = wx.WHITE 1644 1645 self._secondgradientcolour = colour 1646 1647 1648 def GetSecondGradientColour(self): 1649 """ Returns the first gradient colour (near the center). """ 1650 1651 return self._secondgradientcolour 1652 1653 1654 def SetHandStyle(self, style=None): 1655 """ 1656 Sets the style for the hand (arrow indicator). 1657 1658 :param `style`: by specifying "Hand", :class:`SpeedMeter` will draw a polygon 1659 that simulates the car speed control indicator. Using "Arrow" will force 1660 :class:`SpeedMeter` to draw a simple arrow. If defaulted to ``None``, "Hand" will 1661 be used. 1662 """ 1663 1664 if style is None: 1665 style = "Hand" 1666 1667 if style not in ["Hand", "Arrow"]: 1668 raise Exception('\nERROR: Hand Style Parameter Should Be One Of "Hand" Or "Arrow".') 1669 return 1670 1671 self._handstyle = style 1672 1673 1674 def GetHandStyle(self): 1675 """ Returns the style for the hand (arrow indicator).""" 1676 1677 return self._handstyle 1678 1679 1680 def DrawExternalArc(self, draw=True): 1681 """ 1682 Specify wheter or not you wish to draw the external (thicker) arc. 1683 1684 :param `draw`: ``True`` to draw the external arc, ``False`` otherwise. 1685 """ 1686 1687 self._drawarc = draw 1688 1689 1690 def OnMouseMotion(self, event): 1691 """ 1692 Handles the ``wx.EVT_MOUSE_EVENTS`` event for :class:`SpeedMeter`. 1693 1694 :param `event`: a :class:`MouseEvent` event to be processed. 1695 1696 :note: Here only left clicks/drags are involved. Should :class:`SpeedMeter` 1697 have something more? 1698 """ 1699 1700 mousex = event.GetX() 1701 mousey = event.GetY() 1702 1703 if event.Leaving(): 1704 return 1705 1706 pos = self.GetClientSize() 1707 size = self.GetPosition() 1708 centerX = self.CenterX 1709 centerY = self.CenterY 1710 1711 direction = self.GetDirection() 1712 1713 if event.LeftIsDown(): 1714 1715 angle = atan2(float(mousey) - centerY, centerX - float(mousex)) + pi - self.EndAngle 1716 if angle >= 2*pi: 1717 angle = angle - 2*pi 1718 1719 if direction == "Advance": 1720 currentvalue = (self.StartAngle - self.EndAngle - angle)*float(self.Span)/(self.StartAngle - self.EndAngle) + self.StartValue 1721 else: 1722 currentvalue = (angle)*float(self.Span)/(self.StartAngle - self.EndAngle) + self.StartValue 1723 1724 if currentvalue >= self.StartValue and currentvalue <= self.EndValue: 1725 self.SetSpeedValue(currentvalue) 1726 1727 event.Skip() 1728 1729 1730 def GetSpeedStyle(self): 1731 """ Returns a list of strings and a list of integers containing the styles. """ 1732 1733 stringstyle = [] 1734 integerstyle = [] 1735 1736 if self._agwStyle & SM_ROTATE_TEXT: 1737 stringstyle.append("SM_ROTATE_TEXT") 1738 integerstyle.append(SM_ROTATE_TEXT) 1739 1740 if self._agwStyle & SM_DRAW_SECTORS: 1741 stringstyle.append("SM_DRAW_SECTORS") 1742 integerstyle.append(SM_DRAW_SECTORS) 1743 1744 if self._agwStyle & SM_DRAW_PARTIAL_SECTORS: 1745 stringstyle.append("SM_DRAW_PARTIAL_SECTORS") 1746 integerstyle.append(SM_DRAW_PARTIAL_SECTORS) 1747 1748 if self._agwStyle & SM_DRAW_HAND: 1749 stringstyle.append("SM_DRAW_HAND") 1750 integerstyle.append(SM_DRAW_HAND) 1751 1752 if self._agwStyle & SM_DRAW_SHADOW: 1753 stringstyle.append("SM_DRAW_SHADOW") 1754 integerstyle.append(SM_DRAW_SHADOW) 1755 1756 if self._agwStyle & SM_DRAW_PARTIAL_FILLER: 1757 stringstyle.append("SM_DRAW_PARTIAL_FILLER") 1758 integerstyle.append(SM_DRAW_PARTIAL_FILLER) 1759 1760 if self._agwStyle & SM_DRAW_SECONDARY_TICKS: 1761 stringstyle.append("SM_DRAW_SECONDARY_TICKS") 1762 integerstyle.append(SM_DRAW_SECONDARY_TICKS) 1763 1764 if self._agwStyle & SM_DRAW_MIDDLE_TEXT: 1765 stringstyle.append("SM_DRAW_MIDDLE_TEXT") 1766 integerstyle.append(SM_DRAW_MIDDLE_TEXT) 1767 1768 if self._agwStyle & SM_DRAW_MIDDLE_ICON: 1769 stringstyle.append("SM_DRAW_MIDDLE_ICON") 1770 integerstyle.append(SM_DRAW_MIDDLE_ICON) 1771 1772 if self._agwStyle & SM_DRAW_GRADIENT: 1773 stringstyle.append("SM_DRAW_GRADIENT") 1774 integerstyle.append(SM_DRAW_GRADIENT) 1775 1776 if self._agwStyle & SM_DRAW_FANCY_TICKS: 1777 stringstyle.append("SM_DRAW_FANCY_TICKS") 1778 integerstyle.append(SM_DRAW_FANCY_TICKS) 1779 1780 return stringstyle, integerstyle 1781