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