1# --------------------------------------------------------------------------- #
2# SHAPEDBUTTON Control wxPython IMPLEMENTATION
3# Python Code By:
4#
5# Andrea Gavana, @ 18 Oct 2005
6# Latest Revision: 16 Jul 2012, 15.00 GMT
7#
8#
9# TODO List/Caveats
10#
11# 1. Elliptic Buttons May Be Handled Better, In My Opinion. They Look Nice
12#    But They Are Somewhat More Difficult To Handle When Using Sizers.
13#    This Is Probably Due To Some Lack In My Implementation;
14#
15# 2. I Am Unable To Translate The 2 Files "UpButton.png" And "DownButton.png"
16#    Using "img2py" (Under wx.tools) Or With PIL In Order To Embed Them In
17#    A Python File. Every Translation I Made, Did Not Preserve The Alpha
18#    Channel So I Ended Up With Round Buttons Inside Black Squares. Does
19#    Anyone Have A Suggestion Here?
20#
21# 3. Creating *A Lot* Of ShapedButtons May Require Some Time. In The Demo,
22#    I Create 23 Buttons In About 0.4 Seconds On Windows XP, 3 GHz 1 GB RAM.
23#
24# 4. Creating Buttons With Size Greater Than wx.Size(200, 200) May Display
25#    Buttons With Clearly Evident Pixel Edges. This Is Due To The Size Of The
26#    Image Files I Load During Initialization. If This Is Not Satisfactory,
27#    Please Let Me Know And I Will Upload Bigger Files.
28#
29# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
30# Write To Me At:
31#
32# andrea.gavana@gmail.com
33# andrea.gavana@maerskoil.com
34#
35# Or, Obviously, To The wxPython Mailing List!!!
36#
37# Tags:        phoenix-port, unittest, documented, py3-port
38#
39# End Of Comments
40# --------------------------------------------------------------------------- #
41
42
43"""
44:class:`~wx.lib.agw.shapedbutton.SButton` tries to fill the lack of "custom shaped" controls in wxPython
45and it can be used to build round or elliptic-shaped buttons.
46
47
48Description
49===========
50
51`ShapedButton` tries to fill the lack of "custom shaped" controls in wxPython
52(that depends on the same lack in wxWidgets). It can be used to build round
53buttons or elliptic buttons.
54
55I have stolen some code from :mod:`lib.buttons` in order to recreate the same
56classes (`GenButton`, `GenBitmapButton`, `GenBitmapTextButton`, `GenToggleButton`,
57`GenBitmapToggleButton`, `GenBitmapTextToggleButton`). Here you have the same
58classes (with "Gen" replaced by "S"), with the same event handling, but they
59are rounded/elliptical buttons.
60
61`ShapedButton` is based on a :class:`wx.Window`, in which 2 images are drawn depending
62on the button state (pressed or not pressed). The 2 images have been stolen
63from Audacity (written with wxWidgets) and rearranged/reshaped/restyled
64using adobe PhotoShop.
65Changing the button colour in runtime was more difficult, but using some
66intelligent instruction from the PIL library it can be done.
67
68`ShapedButton` reacts on mouse events *only* if the mouse event occurred inside
69the circle/ellipse, even if `ShapedButton` is built on a rectangular window.
70This behavior is a lot different with respect to Audacity round buttons.
71
72
73Usage
74=====
75
76Usage example::
77
78    import wx
79    import wx.lib.agw.shapedbutton as SB
80
81    class MyFrame(wx.Frame):
82
83        def __init__(self, parent):
84
85            wx.Frame.__init__(self, parent, -1, "ShapedButton Demo")
86
87            panel = wx.Panel(self)
88
89            # Create 2 bitmaps for the button
90            upbmp = wx.Bitmap("play.png", wx.BITMAP_TYPE_PNG)
91            disbmp = wx.Bitmap("playdisabled.png", wx.BITMAP_TYPE_PNG)
92
93            play = SB.SBitmapToggleButton(panel, -1, upbmp, (100, 50))
94            play.SetUseFocusIndicator(False)
95            play.SetBitmapDisabled(disbmp)
96
97
98    # our normal wxApp-derived class, as usual
99
100    app = wx.App(0)
101
102    frame = MyFrame(None)
103    app.SetTopWindow(frame)
104    frame.Show()
105
106    app.MainLoop()
107
108
109The `ShapedButton` construction and usage is quite similar to the :mod:`lib.buttons`
110implementation.
111
112
113Methods and Settings
114====================
115
116With `ShapedButton` you can:
117
118- Create rounded/elliptical buttons/togglebuttons;
119- Set images for the enabled/disabled/focused/selected state of the button;
120- Draw the focus indicator (or disable it);
121- Set label colour and font;
122- Apply a rotation to the `ShapedButton` label;
123- Change `ShapedButton` shape and text orientation in runtime.
124
125
126:note: `ShapedButton` **requires** PIL (Python Imaging Library) library to be installed,
127 which can be downloaded from http://www.pythonware.com/products/pil/ .
128
129
130Window Styles
131=============
132
133`No particular window styles are available for this class.`
134
135
136Events Processing
137=================
138
139This class processes the following events:
140
141================= ==================================================
142Event Name        Description
143================= ==================================================
144``wx.EVT_BUTTON`` Process a `wxEVT_COMMAND_BUTTON_CLICKED` event, when the button is clicked.
145================= ==================================================
146
147
148License And Version
149===================
150
151`ShapedButton` is distributed under the wxPython license.
152
153Latest revision: Andrea Gavana @ 16 Jul 2012, 15.00 GMT
154
155Version 0.5
156
157"""
158
159
160#----------------------------------------------------------------------
161# Beginning Of SHAPEDBUTTON wxPython Code
162#----------------------------------------------------------------------
163
164import wx
165
166# First Check If PIL Is Installed Properly
167try:
168
169    import PIL.Image as Image
170
171except ImportError:
172
173    errstr = ("\nShapedButton *Requires* PIL (Python Imaging Library).\n"
174             "You Can Get It At:\n\n"
175             "http://www.pythonware.com/products/pil/\n\n"
176             "ShapedButton Can Not Continue. Exiting...\n")
177
178    raise Exception(errstr)
179
180import os
181
182folder = os.path.split(__file__)[0]
183
184# Import Some Stuff For The Annoying Ellipsis... ;-)
185from math import sin, cos, pi
186
187#-----------------------------------------------------------------------------
188# PATH & FILE FILLING FUNCTION (OS INDEPENDENT)
189# This Is Required To Load The Pressed And Non-Pressed Images From The
190# "images" Directory.
191#-----------------------------------------------------------------------------
192
193def opj(path):
194    """
195    Convert paths to the platform-specific separator.
196
197    :param `path`: the path to convert.
198    """
199
200    strs = os.path.join(*tuple(path.split('/')))
201    # HACK: on Linux, a leading / gets lost...
202    if path.startswith('/'):
203        strs = '/' + strs
204
205    return strs
206
207#-----------------------------------------------------------------------------
208
209
210#----------------------------------------------------------------------
211# Class SButtonEvent
212# Code Stolen From wx.lib.buttons. This Class Handles All The Button
213# And ToggleButton Events.
214#----------------------------------------------------------------------
215
216class SButtonEvent(wx.CommandEvent):
217    """ Event sent from the generic buttons when the button is activated. """
218
219    def __init__(self, eventType, eventId):
220        """
221        Default class constructor.
222
223        :param `eventType`: the event type;
224        :param `eventId`: the event identifier.
225        """
226
227        wx.CommandEvent.__init__(self, eventType, eventId)
228        self.isDown = False
229        self.theButton = None
230
231
232    def SetIsDown(self, isDown):
233        """
234        Sets the button event as pressed.
235
236        :param `isDown`: ``True`` to set the event as "pressed", ``False`` otherwise.
237        """
238
239        self.isDown = isDown
240
241
242    def GetIsDown(self):
243        """ Returns ``True`` if the button event is "pressed". """
244
245        return self.isDown
246
247
248    def SetButtonObj(self, btn):
249        """
250        Sets the event object for the event.
251
252        :param `btn`: the button object.
253        """
254
255        self.theButton = btn
256
257
258    def GetButtonObj(self):
259        """ Returns the object associated with this event. """
260
261        return self.theButton
262
263
264#----------------------------------------------------------------------
265# SBUTTON Class
266# This Is The Main Class Implementation. See __init__() Method For
267# Details. All The Other Button Types Depend On This Class For Event
268# Handling And Property Settings.
269#----------------------------------------------------------------------
270
271class SButton(wx.Window):
272    """ This is the main implementation of `ShapedButton`. """
273
274    _labeldelta = 1
275
276    def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition,
277                 size=wx.DefaultSize):
278        """
279        Default class constructor.
280
281        :param `parent`: the :class:`SButton` parent. Must not be ``None``;
282        :param `id`: window identifier. A value of -1 indicates a default value;
283        :param `label`: the button text label;
284        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
285         chosen by either the windowing system or wxPython, depending on platform;
286        :param `size`: the control size. A value of (-1, -1) indicates a default size,
287         chosen by either the windowing system or wxPython, depending on platform.
288        """
289
290        wx.Window.__init__(self, parent, id, pos, size)
291
292        if label is None:
293            label = ""
294
295        self._enabled = True
296        self._isup = True
297        self._hasfocus = False
298        self._usefocusind = True
299
300        # Initialize Button Properties
301        self.SetButtonColour()
302        self.SetLabel(label)
303        self.SetLabelColour()
304        self.InitColours()
305        self.SetAngleOfRotation()
306        self.SetEllipseAxis()
307
308        if size == wx.DefaultSize:
309            self.SetInitialSize(self.DoGetBestSize())
310
311        # Event Binding
312        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
313        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
314
315        if wx.Platform == '__WXMSW__':
316            self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
317
318        self.Bind(wx.EVT_MOTION, self.OnMotion)
319        self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
320        self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
321        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
322        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
323
324        self.Bind(wx.EVT_SIZE, self.OnSize)
325        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
326        self.Bind(wx.EVT_PAINT, self.OnPaint)
327
328
329    def SetButtonColour(self, colour=None):
330        """
331        Sets the button colour, for all button states.
332
333        :param `colour`: an instance of :class:`wx.Colour`.
334
335        :note: The original button images are greyscale with a lot of pixels with
336         different colours. Changing smoothly the button colour in order to
337         give the same 3D effect can be efficiently done only with PIL.
338        """
339
340        if colour is None:
341            colour = wx.WHITE
342
343        palette = colour.Red(), colour.Green(), colour.Blue()
344
345        self._buttoncolour = colour
346
347        self._mainbuttondown = DownButton.GetImage()
348        self._mainbuttonup = UpButton.GetImage()
349
350
351
352    def GetButtonColour(self):
353        """ Returns the button colour. """
354
355        return self._buttoncolour
356
357
358    def SetLabelColour(self, colour=None):
359        """
360        Sets the button label colour.
361
362        :param `colour`: an instance of :class:`wx.Colour`.
363        """
364
365        if colour is None:
366            colour = wx.BLACK
367
368        self._labelcolour = colour
369
370
371    def GetLabelColour(self):
372        """ Returns the button label colour. """
373
374        return self._labelcolour
375
376
377    def SetLabel(self, label=None):
378        """
379        Sets the button label.
380
381        :param `label`: the new button label.
382        """
383
384        if label is None:
385            label = ""
386
387        self._buttonlabel = label
388
389
390    def GetLabel(self):
391        """ Returns the button label. """
392
393        return self._buttonlabel
394
395
396    def SetBestSize(self, size=None):
397        """
398        Given the current font settings, calculate and set a good size.
399
400        :param `size`: if not ``None``, an instance of :class:`wx.Size` to pass to
401         `SetInitialSize`.
402        """
403
404        if size is None:
405            size = wx.DefaultSize
406
407        self.SetInitialSize(size)
408
409
410    def DoGetBestSize(self):
411        """
412        Overridden base class virtual. Determines the best size of the button
413        based on the label size.
414
415        :note: Overridden from :class:`wx.Window`.
416        """
417
418        w, h, usemin = self._GetLabelSize()
419        defsize = wx.Button.GetDefaultSize()
420        width = 12 + w
421
422        if usemin and width < defsize.width:
423           width = defsize.width
424
425        height = 11 + h
426
427        if usemin and height < defsize.height:
428            height = defsize.height
429
430        return (width, height)
431
432
433    def AcceptsFocus(self):
434        """
435        Can this window be given focus by mouse click?
436
437        :note: Overridden from :class:`wx.Window`.
438        """
439
440        return self.IsShown() and self.IsEnabled()
441
442
443    def ShouldInheritColours(self):
444        """
445        Overridden base class virtual. Buttons usually do not inherit
446        parent's colours.
447        """
448
449        return False
450
451
452    def Enable(self, enable=True):
453        """
454        Enables/disables the button.
455
456        :param `enable`: ``True`` to enable the button, ``False`` to disable it.
457
458        :note: Overridden from :class:`wx.Window`.
459        """
460
461        self._enabled = enable
462        self.Refresh()
463
464
465    def IsEnabled(self):
466        """ Returns wheter the button is enabled or not. """
467
468        return self._enabled
469
470
471    def SetUseFocusIndicator(self, flag):
472        """
473        Specifies if a focus indicator (dotted line) should be used.
474
475        :param `flag`: ``True`` to use the focus indicator, ``False`` otherwise.
476        """
477
478        self._usefocusind = flag
479
480
481    def GetUseFocusIndicator(self):
482        """ Returns focus indicator flag. """
483
484        return self._usefocusind
485
486
487    def InitColours(self):
488        """
489        Calculates a new set of focus indicator colour and indicator pen
490        based on button colour and label colour.
491        """
492
493        textclr = self.GetLabelColour()
494        faceclr = self.GetButtonColour()
495
496        r, g, b, a = faceclr.Get()
497        hr, hg, hb = min(255,r+64), min(255,g+64), min(255,b+64)
498        self._focusclr = wx.Colour(hr, hg, hb)
499
500        if wx.Platform == "__WXMAC__":
501            self._focusindpen = wx.Pen(textclr, 1)
502        else:
503            self._focusindpen = wx.Pen(textclr, 1, wx.USER_DASH)
504            self._focusindpen.SetDashes([1,1])
505            self._focusindpen.SetCap(wx.CAP_BUTT)
506
507
508    def SetDefault(self):
509        """ Sets the button as default item. """
510
511        self.GetParent().SetDefaultItem(self)
512
513
514    def _GetLabelSize(self):
515        """ Used internally """
516
517        w, h = self.GetTextExtent(self.GetLabel())
518        return w, h, True
519
520
521    def Notify(self):
522        """ Notifies an event and let it be processed. """
523
524        evt = SButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
525        evt.SetIsDown(not self._isup)
526        evt.SetButtonObj(self)
527        evt.SetEventObject(self)
528        self.GetEventHandler().ProcessEvent(evt)
529
530
531    def DrawMainButton(self, dc, width, height):
532        """
533        Draws the main button, in whichever state it is.
534
535        :param `dc`: an instance of :class:`wx.DC`;
536        :param `width`: the button width;
537        :param `height`: the button height.
538        """
539
540        w = min(width, height)
541
542        if w <= 2:
543            return
544
545        position = self.GetPosition()
546
547        main, secondary = self.GetEllipseAxis()
548        xc = width/2.0
549        yc = height/2.0
550
551        if abs(main - secondary) < 1e-6:
552            # In This Case The Button Is A Circle
553            if self._isup:
554                img = self._mainbuttonup.Scale(w, w)
555            else:
556                img = self._mainbuttondown.Scale(w, w)
557        else:
558            # In This Case The Button Is An Ellipse... Some Math To Do
559            rect = self.GetRect()
560
561            if main > secondary:
562                # This Is An Ellipse With Main Axis Aligned With X Axis
563                rect2 = w
564                rect3 = w*secondary//main
565
566            else:
567                # This Is An Ellipse With Main Axis Aligned With Y Axis
568                rect3 = w
569                rect2 = w*main//secondary
570
571            if self._isup:
572                img = self._mainbuttonup.Scale(rect2, rect3)
573            else:
574                img = self._mainbuttondown.Scale(rect2, rect3)
575
576        bmp = img.ConvertToBitmap()
577
578        if abs(main - secondary) < 1e-6:
579            if height > width:
580                xpos = 0
581                ypos = (height - width)//2
582            else:
583                xpos = (width - height)//2
584                ypos = 0
585        else:
586            if height > width:
587                if main > secondary:
588                    xpos = 0
589                    ypos = (height - rect3)//2
590                else:
591                    xpos = (width - rect2)//2
592                    ypos = (height - rect3)//2
593            else:
594                if main > secondary:
595                    xpos = (width - rect2)//2
596                    ypos = (height - rect3)//2
597                else:
598                    xpos = (width - rect2)//2
599                    ypos = 0
600
601        # Draw Finally The Bitmap
602        dc.DrawBitmap(bmp, xpos, ypos, True)
603
604        # Store Bitmap Position And Size To Draw An Elliptical Focus Indicator
605        self._xpos = xpos
606        self._ypos = ypos
607        self._imgx = img.GetWidth()
608        self._imgy = img.GetHeight()
609
610
611    def DrawLabel(self, dc, width, height, dw=0, dh=0):
612        """
613        Draws the label on the button.
614
615        :param `dc`: an instance of :class:`wx.DC`;
616        :param `width`: the button width;
617        :param `height`: the button height;
618        :param `dw`: width differential, to show a 3D effect;
619        :param `dh`: height differential, to show a 3D effect.
620        """
621
622        dc.SetFont(self.GetFont())
623
624        if self.IsEnabled():
625            dc.SetTextForeground(self.GetLabelColour())
626        else:
627            dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
628
629        label = self.GetLabel()
630        tw, th = dc.GetTextExtent(label)
631
632        w = min(width, height)
633
634        # labeldelta Is Used To Give The Impression Of A "3D" Click
635        if not self._isup:
636            dw = dh = self._labeldelta
637
638        angle = self.GetAngleOfRotation()*pi/180.0
639
640        # Check If There Is Any Rotation Chosen By The User
641        if angle == 0:
642            dc.DrawText(label, (width-tw)//2+dw, (height-th)//2+dh)
643        else:
644            xc, yc = (width//2, height//2)
645
646            xp = xc - (tw//2)* cos(angle) - (th//2)*sin(angle)
647            yp = yc + (tw//2)*sin(angle) - (th//2)*cos(angle)
648
649            dc.DrawRotatedText(label, xp + dw, yp + dh , angle*180/pi)
650
651
652    def DrawFocusIndicator(self, dc, width, height):
653        """
654        Draws the focus indicator. This is a circle/ellipse inside the button
655        drawn with a dotted-style pen, to let the user know which button has
656        the focus.
657
658        :param `dc`: an instance of :class:`wx.DC`;
659        :param `width`: the button width;
660        :param `height`: the button height.
661        """
662
663        self._focusindpen.SetColour(self._focusclr)
664        dc.SetLogicalFunction(wx.INVERT)
665        dc.SetPen(self._focusindpen)
666        dc.SetBrush(wx.TRANSPARENT_BRUSH)
667
668        main, secondary = self.GetEllipseAxis()
669
670        if abs(main - secondary) < 1e-6:
671            # Ah, That Is A Round Button
672            if height > width:
673                dc.DrawCircle(width//2, height//2, width//2-4)
674            else:
675                dc.DrawCircle(width//2, height//2, height//2-4)
676        else:
677            # This Is An Ellipse
678            if hasattr(self, "_xpos"):
679                dc.DrawEllipse(self._xpos + 2, self._ypos + 2, self._imgx - 4, self._imgy - 4)
680
681        dc.SetLogicalFunction(wx.COPY)
682
683
684    def OnSize(self, event):
685        """
686        Handles the ``wx.EVT_SIZE`` event for :class:`SButton`.
687
688        :param `event`: a :class:`wx.SizeEvent` event to be processed.
689        """
690
691        self.Refresh()
692        event.Skip()
693
694
695    def OnPaint(self, event):
696        """
697        Handles the ``wx.EVT_PAINT`` event for :class:`SButton`.
698
699        :param `event`: a :class:`PaintEvent` event to be processed.
700        """
701
702        width, height = self.GetClientSize()
703
704        # Use A Double Buffered DC (Good Speed Up)
705        dc = wx.BufferedPaintDC(self)
706
707        # The DC Background *Must* Be The Same As The Parent Background Colour,
708        # In Order To Hide The Fact That Our "Shaped" Button Is Still Constructed
709        # Over A Rectangular Window
710        brush = wx.Brush(self.GetParent().GetBackgroundColour())
711
712        dc.SetBackground(brush)
713        dc.Clear()
714
715        self.DrawMainButton(dc, width, height)
716        self.DrawLabel(dc, width, height)
717
718        if self._hasfocus and self._usefocusind:
719            self.DrawFocusIndicator(dc, width, height)
720
721
722    def IsOutside(self, x, y):
723        """
724        Checks if a mouse events occurred inside the circle/ellipse or not.
725
726        :param `x`: the mouse x position;
727        :param `y`: the mouse y position.
728        """
729
730        width, height = self.GetClientSize()
731        diam = min(width, height)
732        xc, yc = (width//2, height//2)
733
734        main, secondary = self.GetEllipseAxis()
735
736        if abs(main - secondary) < 1e-6:
737            # This Is A Circle
738            if ((x - xc)**2.0 + (y - yc)**2.0) > (diam/2.0)**2.0:
739                return True
740        else:
741            # This Is An Ellipse
742            mn = max(main, secondary)
743            main = self._imgx/2.0
744            secondary = self._imgy/2.0
745            if (((x-xc)/main)**2.0 + ((y-yc)/secondary)**2.0) > 1:
746                return True
747
748        return False
749
750
751    def OnLeftDown(self, event):
752        """
753        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`SButton`.
754
755        :param `event`: a :class:`MouseEvent` event to be processed.
756        """
757
758        if not self.IsEnabled():
759            return
760
761        x, y = (event.GetX(), event.GetY())
762
763        if self.IsOutside(x,y):
764            return
765
766        self._isup = False
767
768        if not self.HasCapture():
769            self.CaptureMouse()
770
771        self.SetFocus()
772
773        self.Refresh()
774        event.Skip()
775
776
777    def OnLeftUp(self, event):
778        """
779        Handles the ``wx.EVT_LEFT_UP`` event for :class:`SButton`.
780
781        :param `event`: a :class:`MouseEvent` event to be processed.
782        """
783
784        if not self.IsEnabled() or not self.HasCapture():
785            return
786
787        if self.HasCapture():
788            self.ReleaseMouse()
789
790            if not self._isup:
791                self.Notify()
792
793            self._isup = True
794            self.Refresh()
795            event.Skip()
796
797
798    def OnMotion(self, event):
799        """
800        Handles the ``wx.EVT_MOTION`` event for :class:`SButton`.
801
802        :param `event`: a :class:`MouseEvent` event to be processed.
803        """
804
805        if not self.IsEnabled() or not self.HasCapture():
806            return
807
808        if event.LeftIsDown() and self.HasCapture():
809            x, y = event.GetPosition()
810
811            if self._isup and not self.IsOutside(x, y):
812                self._isup = False
813                self.Refresh()
814                return
815
816            if not self._isup and self.IsOutside(x,y):
817                self._isup = True
818                self.Refresh()
819                return
820
821        event.Skip()
822
823
824    def OnGainFocus(self, event):
825        """
826        Handles the ``wx.EVT_SET_FOCUS`` event for :class:`SButton`.
827
828        :param `event`: a :class:`FocusEvent` event to be processed.
829        """
830
831        self._hasfocus = True
832        dc = wx.ClientDC(self)
833        w, h = self.GetClientSize()
834
835        if self._usefocusind:
836            self.DrawFocusIndicator(dc, w, h)
837
838
839    def OnLoseFocus(self, event):
840        """
841        Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`SButton`.
842
843        :param `event`: a :class:`FocusEvent` event to be processed.
844        """
845
846        self._hasfocus = False
847        dc = wx.ClientDC(self)
848        w, h = self.GetClientSize()
849
850        if self._usefocusind:
851            self.DrawFocusIndicator(dc, w, h)
852
853        self.Refresh()
854
855
856    def OnKeyDown(self, event):
857        """
858        Handles the ``wx.EVT_KEY_DOWN`` event for :class:`SButton`.
859
860        :param `event`: a :class:`KeyEvent` event to be processed.
861        """
862
863        if self._hasfocus and event.GetKeyCode() == ord(" "):
864
865            self._isup = False
866            self.Refresh()
867
868        event.Skip()
869
870
871    def OnKeyUp(self, event):
872        """
873        Handles the ``wx.EVT_KEY_UP`` event for :class:`SButton`.
874
875        :param `event`: a :class:`KeyEvent` event to be processed.
876        """
877
878        if self._hasfocus and event.GetKeyCode() == ord(" "):
879
880            self._isup = True
881            self.Notify()
882            self.Refresh()
883
884        event.Skip()
885
886
887    def MakePalette(self, tr, tg, tb):
888        """
889        Creates a palette to be applied on an image based on input colour.
890
891        :param `tr`: the red intensity of the input colour;
892        :param `tg`: the green intensity of the input colour;
893        :param `tb`: the blue intensity of the input colour.
894        """
895
896        l = []
897        for i in range(255):
898            l.extend([tr*i // 255, tg*i // 255, tb*i // 255])
899
900        return l
901
902
903    def ConvertWXToPIL(self, bmp):
904        """
905        Converts a :class:`wx.Image` into a PIL image.
906
907        :param `bmp`: an instance of :class:`wx.Image`.
908        """
909
910        width = bmp.GetWidth()
911        height = bmp.GetHeight()
912        img = Image.frombytes("RGBA", (width, height), bmp.GetData())
913
914        return img
915
916
917    def ConvertPILToWX(self, pil, alpha=True):
918        """
919        Converts a PIL image into a :class:`wx.Image`.
920
921        :param `pil`: a PIL image;
922        :param `alpha`: ``True`` if the image contains alpha transparency, ``False``
923         otherwise.
924        """
925
926        if alpha:
927            image = wx.Image(*pil.size)
928            image.SetData(pil.convert("RGB").tobytes())
929            image.SetAlpha(pil.convert("RGBA").tobytes()[3::4])
930        else:
931            image = wx.Image(pil.size[0], pil.size[1])
932            new_image = pil.convert('RGB')
933            data = new_image.tobytes()
934            image.SetData(data)
935
936        return image
937
938
939    def SetAngleOfRotation(self, angle=None):
940        """
941        Sets angle of button label rotation.
942
943        :param `angle`: the label rotation, in degrees.
944        """
945
946        if angle is None:
947            angle = 0
948
949        self._rotation = angle*pi/180
950
951
952    def GetAngleOfRotation(self):
953        """ Returns angle of button label rotation, in degrees. """
954
955        return self._rotation*180.0/pi
956
957
958    def SetEllipseAxis(self, main=None, secondary=None):
959        """
960        Sets the ellipse axis. What it is important is not their absolute values
961        but their ratio.
962
963        :param `main`: a floating point number representing the absolute dimension
964         of the main ellipse axis;
965        :param `secondary`: a floating point number representing the absolute dimension
966         of the secondary ellipse axis.
967        """
968
969        if main is None:
970            main = 1.0
971            secondary = 1.0
972
973        self._ellipseaxis = (main, secondary)
974
975
976    def GetEllipseAxis(self):
977        """ Returns the ellipse axes. """
978
979        return self._ellipseaxis
980
981
982#----------------------------------------------------------------------
983# SBITMAPBUTTON Class
984# It Is Derived From SButton, And It Is A Class Useful To Draw A
985# ShapedButton With An Image In The Middle. The Button Can Have 4
986# Different Bitmaps Assigned To Its Different States (Pressed, Non
987# Pressed, With Focus, Disabled).
988#----------------------------------------------------------------------
989
990
991class SBitmapButton(SButton):
992    """
993    Subclass of :class:`SButton` which displays a bitmap, acting like a
994    :class:`wx.BitmapButton`.
995    """
996
997    def __init__(self, parent, id, bitmap, pos=wx.DefaultPosition, size=wx.DefaultSize):
998        """
999        Default class constructor.
1000
1001        :param `parent`: the :class:`SBitmapButton` parent. Must not be ``None``;
1002        :param `id`: window identifier. A value of -1 indicates a default value;
1003        :param `bitmap`: the button bitmap (if any);
1004        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
1005         chosen by either the windowing system or wxPython, depending on platform;
1006        :param `size`: the control size. A value of (-1, -1) indicates a default size,
1007         chosen by either the windowing system or wxPython, depending on platform.
1008        """
1009
1010        self._bmpdisabled = None
1011        self._bmpfocus = None
1012        self._bmpselected = None
1013
1014        self.SetBitmapLabel(bitmap)
1015
1016        SButton.__init__(self, parent, id, "", pos, size)
1017
1018
1019    def GetBitmapLabel(self):
1020        """ Returns the bitmap associated with the button in the normal state. """
1021
1022        return self._bmplabel
1023
1024
1025    def GetBitmapDisabled(self):
1026        """ Returns the bitmap displayed when the button is disabled. """
1027
1028        return self._bmpdisabled
1029
1030
1031    def GetBitmapFocus(self):
1032        """ Returns the bitmap displayed when the button has the focus. """
1033
1034        return self._bmpfocus
1035
1036
1037    def GetBitmapSelected(self):
1038        """ Returns the bitmap displayed when the button is selected (pressed). """
1039
1040        return self._bmpselected
1041
1042
1043    def SetBitmapDisabled(self, bitmap):
1044        """
1045        Sets the bitmap to display when the button is disabled.
1046
1047        :param `bitmap`: a valid :class:`wx.Bitmap` object.
1048        """
1049
1050        self._bmpdisabled = bitmap
1051
1052
1053    def SetBitmapFocus(self, bitmap):
1054        """
1055        Sets the bitmap to display when the button has the focus.
1056
1057        :param `bitmap`: a valid :class:`wx.Bitmap` object.
1058        """
1059
1060        self._bmpfocus = bitmap
1061        self.SetUseFocusIndicator(False)
1062
1063
1064    def SetBitmapSelected(self, bitmap):
1065        """
1066        Sets the bitmap to display when the button is selected (pressed).
1067
1068        :param `bitmap`: a valid :class:`wx.Bitmap` object.
1069        """
1070
1071        self._bmpselected = bitmap
1072
1073
1074    def SetBitmapLabel(self, bitmap, createothers=True):
1075        """
1076        Sets the bitmap to display normally. This is the only one that is
1077        required.
1078
1079        :param `bitmap`: a valid :class:`wx.Bitmap` object;
1080        :param `createothers`: if set to ``True``, then the other bitmaps will be
1081         generated on the fly. Currently, only the disabled bitmap is generated.
1082        """
1083
1084        self._bmplabel = bitmap
1085
1086        if bitmap is not None and createothers:
1087            dis_bitmap = wx.Bitmap(bitmap.ConvertToImage().ConvertToGreyscale())
1088            self.SetBitmapDisabled(dis_bitmap)
1089
1090
1091    def _GetLabelSize(self):
1092        """ Used internally. """
1093
1094        if not self._bmplabel:
1095            return -1, -1, False
1096
1097        return self._bmplabel.GetWidth() + 2, self._bmplabel.GetHeight() + 2, False
1098
1099
1100    def DrawLabel(self, dc, width, height, dw=0, dh=0):
1101        """
1102        Draws the bitmap in the middle of the button.
1103
1104        :param `dc`: an instance of :class:`wx.DC`;
1105        :param `width`: the button width;
1106        :param `height`: the button height;
1107        :param `dw`: width differential, to show a 3D effect;
1108        :param `dh`: height differential, to show a 3D effect.
1109        """
1110
1111        bmp = self._bmplabel
1112
1113        if self._bmpdisabled and not self.IsEnabled():
1114            bmp = self._bmpdisabled
1115
1116        if self._bmpfocus and self._hasfocus:
1117            bmp = self._bmpfocus
1118
1119        if self._bmpselected and not self._isup:
1120            bmp = self._bmpselected
1121
1122        bw, bh = bmp.GetWidth(), bmp.GetHeight()
1123
1124        if not self._isup:
1125            dw = dh = self._labeldelta
1126
1127        hasMask = bmp.GetMask() != None
1128        dc.DrawBitmap(bmp, (width - bw)//2 + dw, (height - bh)//2 + dh, hasMask)
1129
1130
1131#----------------------------------------------------------------------
1132# SBITMAPTEXTBUTTON Class
1133# It Is Derived From SButton, And It Is A Class Useful To Draw A
1134# ShapedButton With An Image And Some Text Ceneterd In The Middle.
1135# The Button Can Have 4 Different Bitmaps Assigned To Its Different
1136# States (Pressed, Non Pressed, With Focus, Disabled).
1137#----------------------------------------------------------------------
1138
1139class SBitmapTextButton(SBitmapButton):
1140    """
1141    Subclass of :class:`SButton` which displays a bitmap and a label.
1142    """
1143
1144    def __init__(self, parent, id, bitmap, label,
1145                 pos=wx.DefaultPosition, size=wx.DefaultSize):
1146        """
1147        Default class constructor.
1148
1149        :param `parent`: the :class:`SBitmapTextButton` parent. Must not be ``None``;
1150        :param `id`: window identifier. A value of -1 indicates a default value;
1151        :param `bitmap`: the button bitmap (if any);
1152        :param `label`: the button text label;
1153        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
1154         chosen by either the windowing system or wxPython, depending on platform;
1155        :param `size`: the control size. A value of (-1, -1) indicates a default size,
1156         chosen by either the windowing system or wxPython, depending on platform.
1157        """
1158
1159        SBitmapButton.__init__(self, parent, id, bitmap, pos, size)
1160        self.SetLabel(label)
1161
1162
1163    def _GetLabelSize(self):
1164        """ Used internally. """
1165
1166        w, h = self.GetTextExtent(self.GetLabel())
1167
1168        if not self._bmplabel:
1169            return w, h, True       # if there isn't a bitmap use the size of the text
1170
1171        w_bmp = self._bmplabel.GetWidth() + 2
1172        h_bmp = self._bmplabel.GetHeight() + 2
1173        width = w + w_bmp
1174
1175        if h_bmp > h:
1176            height = h_bmp
1177        else:
1178            height = h
1179
1180        return width, height, True
1181
1182
1183    def DrawLabel(self, dc, width, height, dw=0, dh=0):
1184        """
1185        Draws the bitmap and the text label.
1186
1187        :param `dc`: an instance of :class:`wx.DC`;
1188        :param `width`: the button width;
1189        :param `height`: the button height;
1190        :param `dw`: width differential, to show a 3D effect;
1191        :param `dh`: height differential, to show a 3D effect.
1192        """
1193
1194        bmp = self._bmplabel
1195
1196        if bmp != None:     # if the bitmap is used
1197
1198            if self._bmpdisabled and not self.IsEnabled():
1199                bmp = self._bmpdisabled
1200
1201            if self._bmpfocus and self._hasfocus:
1202                bmp = self._bmpfocus
1203
1204            if self._bmpselected and not self._isup:
1205                bmp = self._bmpselected
1206
1207            bw, bh = bmp.GetWidth(), bmp.GetHeight()
1208
1209            if not self._isup:
1210                dw = dh = self._labeldelta
1211
1212            hasMask = bmp.GetMask() != None
1213
1214        else:
1215
1216            bw = bh = 0     # no bitmap -> size is zero
1217
1218        dc.SetFont(self.GetFont())
1219
1220        if self.IsEnabled():
1221            dc.SetTextForeground(self.GetLabelColour())
1222        else:
1223            dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
1224
1225        label = self.GetLabel()
1226        tw, th = dc.GetTextExtent(label)  # size of text
1227
1228        if not self._isup:
1229            dw = dh = self._labeldelta
1230
1231        w = min(width, height)
1232
1233        pos_x = (width - bw - tw)//2 + dw      # adjust for bitmap and text to centre
1234
1235        rotangle = self.GetAngleOfRotation()*pi/180.0
1236
1237        if bmp != None:
1238            if rotangle < 1.0/180.0:
1239                dc.DrawBitmap(bmp, pos_x, (height - bh)//2 + dh, hasMask) # draw bitmap if available
1240                pos_x = pos_x + 4   # extra spacing from bitmap
1241            else:
1242                pass
1243
1244        if rotangle < 1.0/180.0:
1245            dc.DrawText(label, pos_x + dw + bw, (height-th)//2+dh)      # draw the text
1246        else:
1247            xc, yc = (width//2, height//2)
1248            xp = xc - (tw//2)* cos(rotangle) - (th//2)*sin(rotangle)
1249            yp = yc + (tw//2)*sin(rotangle) - (th//2)*cos(rotangle)
1250            dc.DrawRotatedText(label, xp, yp , rotangle*180.0/pi)
1251
1252
1253#----------------------------------------------------------------------
1254# __STOGGLEMIXIN Class
1255# A Mixin That Allows To Transform Any Of SButton, SBitmapButton And
1256# SBitmapTextButton In The Corresponding ToggleButtons.
1257#----------------------------------------------------------------------
1258
1259class __SToggleMixin(object):
1260    """
1261    A mixin that allows to transform any of :class:`SButton`, :class:`SBitmapButton` and
1262    :class:`SBitmapTextButton` in the corresponding toggle buttons.
1263    """
1264
1265    def SetToggle(self, flag):
1266        """
1267        Sets the button as toggled/not toggled.
1268
1269        :param `flag`: ``True`` to set the button as toggled, ``False`` otherwise.
1270        """
1271
1272        self._isup = not flag
1273        self.Refresh()
1274
1275    SetValue = SetToggle
1276
1277
1278    def GetToggle(self):
1279        """ Returns the toggled state of a button. """
1280
1281        return not self._isup
1282
1283    GetValue = GetToggle
1284
1285
1286    def OnLeftDown(self, event):
1287        """
1288        Handles the ``wx.EVT_LEFT_DOWN`` event for the button.
1289
1290        :param `event`: a :class:`MouseEvent` event to be processed.
1291        """
1292
1293        if not self.IsEnabled():
1294            return
1295
1296        x, y = (event.GetX(), event.GetY())
1297
1298        if self.IsOutside(x,y):
1299            return
1300
1301        self._saveup = self._isup
1302        self._isup = not self._isup
1303
1304        if not self.HasCapture():
1305            self.CaptureMouse()
1306
1307        self.SetFocus()
1308        self.Refresh()
1309
1310
1311    def OnLeftUp(self, event):
1312        """
1313        Handles the ``wx.EVT_LEFT_UP`` event for the button.
1314
1315        :param `event`: a :class:`MouseEvent` event to be processed.
1316        """
1317
1318
1319        if not self.IsEnabled() or not self.HasCapture():
1320            return
1321
1322        if self.HasCapture():
1323            if self._isup != self._saveup:
1324                self.Notify()
1325
1326            self.ReleaseMouse()
1327            self.Refresh()
1328
1329
1330    def OnKeyDown(self, event):
1331        """
1332        Handles the ``wx.EVT_KEY_DOWN`` event for the button.
1333
1334        :param `event`: a :class:`KeyEvent` event to be processed.
1335        """
1336
1337        event.Skip()
1338
1339
1340    def OnMotion(self, event):
1341        """
1342        Handles the ``wx.EVT_MOTION`` event for the button.
1343
1344        :param `event`: a :class:`MouseEvent` event to be processed.
1345        """
1346
1347        if not self.IsEnabled():
1348            return
1349
1350        if event.LeftIsDown() and self.HasCapture():
1351
1352            x, y = event.GetPosition()
1353            w, h = self.GetClientSize()
1354
1355            if not self.IsOutside(x, y):
1356                self._isup = not self._saveup
1357                self.Refresh()
1358                return
1359
1360            if self.IsOutside(x,y):
1361                self._isup = self._saveup
1362                self.Refresh()
1363                return
1364
1365        event.Skip()
1366
1367
1368    def OnKeyUp(self, event):
1369        """
1370        Handles the ``wx.EVT_KEY_UP`` event for the button.
1371
1372        :param `event`: a :class:`KeyEvent` event to be processed.
1373        """
1374
1375        if self._hasfocus and event.GetKeyCode() == ord(" "):
1376
1377            self._isup = not self._isup
1378            self.Notify()
1379            self.Refresh()
1380
1381        event.Skip()
1382
1383
1384#----------------------------------------------------------------------
1385# STOGGLEBUTTON Class
1386#----------------------------------------------------------------------
1387
1388class SToggleButton(__SToggleMixin, SButton):
1389    """ A `ShapedButton` toggle button. """
1390    pass
1391
1392
1393#----------------------------------------------------------------------
1394# SBITMAPTOGGLEBUTTON Class
1395#----------------------------------------------------------------------
1396
1397class SBitmapToggleButton(__SToggleMixin, SBitmapButton):
1398    """ A `ShapedButton` toggle bitmap button. """
1399    pass
1400
1401
1402#----------------------------------------------------------------------
1403# SBITMAPTEXTTOGGLEBUTTON Class
1404#----------------------------------------------------------------------
1405
1406class SBitmapTextToggleButton(__SToggleMixin, SBitmapTextButton):
1407    """ A `ShapedButton` toggle bitmap button with a text label. """
1408    pass
1409
1410
1411
1412#----------------------------------------------------------------------
1413from wx.lib.embeddedimage import PyEmbeddedImage
1414
1415UpButton = PyEmbeddedImage(
1416    "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAACXBIWXMAAA7DAAAOwwHHb6hk"
1417    "AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAA"
1418    "F2+SX8VGAAADAFBMVEX//////8z//5n//2b//zP//wD/zP//zMz/zJn/zGb/zDP/zAD/mf//"
1419    "mcz/mZn/mWb/mTP/mQD/Zv//Zsz/Zpn/Zmb/ZjP/ZgD/M///M8z/M5n/M2b/MzP/MwD/AP//"
1420    "AMz/AJn/AGb/ADP/AADM///M/8zM/5nM/2bM/zPM/wDMzP/MzMzMzJnMzGbMzDPMzADMmf/M"
1421    "mczMmZnMmWbMmTPMmQDMZv/MZszMZpnMZmbMZjPMZgDMM//MM8zMM5nMM2bMMzPMMwDMAP/M"
1422    "AMzMAJnMAGbMADPMAACZ//+Z/8yZ/5mZ/2aZ/zOZ/wCZzP+ZzMyZzJmZzGaZzDOZzACZmf+Z"
1423    "mcyZmZmZmWaZmTOZmQCZZv+ZZsyZZpmZZmaZZjOZZgCZM/+ZM8yZM5mZM2aZMzOZMwCZAP+Z"
1424    "AMyZAJmZAGaZADOZAABm//9m/8xm/5lm/2Zm/zNm/wBmzP9mzMxmzJlmzGZmzDNmzABmmf9m"
1425    "mcxmmZlmmWZmmTNmmQBmZv9mZsxmZplmZmZmZjNmZgBmM/9mM8xmM5lmM2ZmMzNmMwBmAP9m"
1426    "AMxmAJlmAGZmADNmAAAz//8z/8wz/5kz/2Yz/zMz/wAzzP8zzMwzzJkzzGYzzDMzzAAzmf8z"
1427    "mcwzmZkzmWYzmTMzmQAzZv8zZswzZpkzZmYzZjMzZgAzM/8zM8wzM5kzM2YzMzMzMwAzAP8z"
1428    "AMwzAJkzAGYzADMzAAAA//8A/8wA/5kA/2YA/zMA/wAAzP8AzMwAzJkAzGYAzDMAzAAAmf8A"
1429    "mcwAmZkAmWYAmTMAmQAAZv8AZswAZpkAZmYAZjMAZgAAM/8AM8wAM5kAM2YAMzMAMwAAAP8A"
1430    "AMwAAJkAAGYAADMAAADU1NTT09PS0tLR0dHQ0NDNzc3Ly8vKysrJycnIyMjHx8fGxsbFxcXC"
1431    "wsLBwcH39/f09PTw8PDs7Ozo6Ojj4+Pe3t7Z2dnX19fV1dXOzs7ExMTDw8O+vr66urq1tbWw"
1432    "sLCsrKyoqKikpKSgoKCcnJyUlJSPj4////+2FWUJAAABAHRSTlP/////////////////////"
1433    "////////////////////////////////////////////////////////////////////////"
1434    "////////////////////////////////////////////////////////////////////////"
1435    "////////////////////////////////////////////////////////////////////////"
1436    "////////////////////////////////////////////////////////////////////////"
1437    "//////////////////////////////8AU/cHJQAAIcNJREFUeNpi+D9MAEAAMQwXjwAE0LDx"
1438    "CEAA0cIjL9+8wwreA8GHGzdvvn1FA0sBAoj6HsHhCySP3Lx54y3VrQUIIGp6BOHkt2/fAgkQ"
1439    "BWKBIIpvQB76cPPmrVtUtBwggBio6o232AHYT9g8cvs21awHCCCqeOQV2LFg8AYI4Yw3EIDq"
1440    "KUTUfPhw48ONGzdvvaaGGwACiAoeeQF14hv8AN03sJgB+uTWS8pdARBAlHoE2QuvXwMROgAK"
1441    "Y/UNIqWBY4XiRAYQQJR55DVKVLzGBbDEDCL/gzwCipVbbyhyCkAAUeARhnfIkQEFr5AAVr9g"
1442    "ZBZY8gLGyh0KXAMQQAwUJyp4enqFCRD+wOsTSO0CKsTukO0cgAAi0yMvoXEBcj/UBy/BAKdP"
1443    "wKohHkHzDKSeBCYvkFfu3CGz2gcIIAayYwPuESRvQHyC8A5q0nqNvTBG88gd8mIFIIAYyPIG"
1444    "PF9APIHwBlqkvMYaI2jZHV4Mg3L87Tu373z8SIajAAKIgazoQIqNl+jg1UusKes1IotAPPMO"
1445    "pUKBe+Q2KFLI8AlAAJHqkVeQDA72BLLzX2CLEWw5nVCMgD1y5+NdUgtjgAAi0SMMkNhAigug"
1446    "D168ePHyBdwr8Bh5/QqSpJA88vYNlsYKvO2F4pG7JLoMIIAYSM8d0KwB9sQLKECKEeQkhdZI"
1447    "eYPWknyH3FRB9gfII9raJDkNIIAYSKvHkWID5PQXCIDFH8hewNLcIugR7XckOA4ggBhIiw54"
1448    "3niBCtB8Ac/bb1Gd/w49f+DwyEeIR0iJFIAAIt4jqJkDiz+QMjiW5jtKF+sthjdu3ECOD6A/"
1449    "IB4h3icAAcRAWu5A8sbz58+RvPEKFhuoTUMC4D0MQFvASP6AeoRorwAEEJEeeQHxCKysAnsD"
1450    "2SOocYElAeH1CKTshXrjzkeEJ0CAyCYLQAAxEJ2skHPH8+cIj4C9AfUDSsEKdiEo0dy4AaE/"
1451    "gGkogPoAzPhwA8Uj2nfvaWvfA3niHhDcv0+UEwECiCiPvHqDiI4XSB55DvHIKzSPvEM0ocBO"
1452    "vHnzBrgpdQNMQwGSnyCKbsELLIgHYOD+faKGXAACiIHIShASHy+wRser17BGFCLBQPpKt3EA"
1453    "oBTMUx8gnrx5E9o4+Qj1yH0YeAAERLgSIIAYiKs9MP2BiA00f0Dj4RY0oeAAt2/dhIAbN2AN"
1454    "X6hH7mqDfAHxyEMgAHmEiEgBCCAGwtkclj+QPAH2BixzQJMUpNcKchKiJrh3TxsFQAqju3c/"
1455    "fgR6BR45EIDqkfsPHiKB+/cJDk8ABBADUf6A5o7nyB4BF7pgf8DiAlip3UJUaCDnAMMSCdy7"
1456    "D0ky9+4BvQKPGpBnQMQduEdA+iAeeAQEEI/cJ1R4AQQQAxHFLjA+sPkDmqjgjXFg3YyICUji"
1457    "Rg5VkKdgHtHW/ggFEM8gkhxQ9z1wdDyCAVjyIuATgABiIMYfryGlFcwTkOwB8gckb0C8cQOY"
1458    "Xe/AvQHyxSMUAHTOQ0iyv/8AnMjA4ONHlKzzEeaRR48eP0b1yAP8qQsggPB65DW82EWKjhdI"
1459    "qQocG5BRkNug0LwHThQgRzx58gSEkQDIYZDUAk4qkMR2T/se2DcwcBfZIzB9MC/hbUMCBBAD"
1460    "3nIX5BFocYWZzaGxASpqQd74CMwVD+4/hHrk0yc8HnkEKVaJ8cinT48hAOQTfI4FCCAG/PU5"
1461    "tNh9jpw9oKUVJHe8f38T4gvte5DU9BjkgE9A8OQJukfArnkM9RA010ByDCyh3dWG1iHg4AAa"
1462    "8xkIIGaBteFxLEAAMeDr1cKrDzSPIOXy9+9h/rhPkkdAAQz3yD3cHnn69CnQLzCP4KlOAAKI"
1463    "AV97F8MbKMkKlMeBjW+IJx4+gqakT6BgfAq2/xM6ePzkMTJ49BCUo6ClGbyuQfbI56fPnj2D"
1464    "+AUUMkAtOJ0LEEA4PfICVn+gegQRHeDYAFZ+4LgA+wPsWFBiwOWRJ+geQSqWtWGeQfHIM4hH"
1465    "QMZBPIKzEAYIIAa83UGUYhepEgTX5MCyClRSgS0FpieoH6DegHgFDaB45BEsqwC9Am0fwuME"
1466    "JUYg4CkkTh7hihOAAMLjEfSM/gI1PkDV+B1gSQUupUBpCskL2AEuj8AiBb9HID7B6RGAAGLA"
1467    "X2Chxwc8e7z/cOs2MG8CMzg4c4M98QyREEj1CNAruD3yBQQgfgEZgd3FAAHEgCdhoWQQFH+A"
1468    "Cl1gBwiYOSB5A+gNmEfw+AWnRxAlMdg3cI88+fQU5g+YV0D1E1YnAwQQVo+ABz9eofkD5BFY"
1469    "sgL2HoCp6j44b4DLKaAznxHwCCjzI3kCpS0FihOYR2ANHFD5Ac7tX74geQVoypMb2NwMEEAM"
1470    "OHu2mAkLXpuD6g5QfEDyBtgjz5AAdm8g53UMj8D9Au9MgUsQsMFfkH0CLr6wuRkggBjwllio"
1471    "6QoaH8BkBWqkP3gEyRuQaoOwR5BLX2I9Ak5bED98/QpPX5+x+QQggBiwNrHQa3R4eQXyx4fb"
1472    "d7TB0YGoNJ49I+wRUCsSzSOPH6FkFEgjF+SfB+BWAqiR8Bnska8g8AXmlc+fsLgaIIAYsI63"
1473    "o3sEls9B+eMGMJvfv/8I4g+QC9H9gdUj4PgAtyMf48rwcI88gETKw8fApAtMW19APoB4BZq8"
1474    "Pj/BbKsABBADrhLrFaJ/jpQ/gOXuzdsf74IKXWgex4gPXB55/BjSniXSI+AW8COIR2BxAvUL"
1475    "0COYJRdAAGF6BNE0gXgEPNQOi4/3wNocWFxBK/KnNPfI40+QtAX3CMgrwMSFmUsAAogBd9v9"
1476    "BaLgBfcGgc0ScDUIbK+DyiqwD2jqEUil+BTJI9++QT2CWXIBBBADrqrwJUpGh+Tzd8Dq4yMo"
1477    "m3/+jCVrPMPlIVjRCy6qnyCXWQ9RAepYBdgnT4CJC+yRb0BPgDAkToCFMJrDAQIIq0dQcjrI"
1478    "I+BuLTB/3LoDbJU8fIzcJCHKI5CiF8MjDwl65CGoiAf7AxQdYACLEzSHAwQQA9a2InLKghVY"
1479    "4PrjHtAbj2G5g9YeAVUmoNz47BnQFzB/fIPk+GdP0XwCEEAMGOsA4IOKSCUWOKND8gcwPsCx"
1480    "QSePgGpFUHZHipFvXyG1CerqKIAAYsCaQxAl1gt4RfgB2IkCtdmffCIlNhAeQfR4cXrkPjp4"
1481    "+ADURQB5BOKP70DwDVJ2AbMJitMBAogBayPrJWwiB17yAitCYEa/B27JkeMReC8euWZHi44H"
1482    "GB55gOSRrzCPfIPm96coTgcIIAa8/ngBa2EBMwgwn2s/fAwdECDTI2hNFCI8Ago5kG3QKPmG"
1483    "5JNnKD4BCCAGtHEs2PDoC/hIA7jAen/j9kdg8xpcYIGrD9I8AvEKcR5BnlIAV4vAwh7ska/Q"
1484    "GPkOK7ueIjseIIAYcDROYJNqkBwC7NcCK/QH9x+R5RF4dgd55AmWihApg8B6JDAfgTzy6RPM"
1485    "I9AYgRXCyFECEEAMOAYcENM4kKYJsIEFSrFP0LoeRHsGtWbHEhsP4IOo2tr3kLwE9AnYI1/g"
1486    "eeQ7rBB+hpzdAQKIAXVGB9q/RfgD2nS/dUcb2B8EV0/098gTYC5BLre+QwphoE/eI1wPEEAM"
1487    "2AZO4D6BNLFAJdZHkEceQ3vR1PbIA3SPwABkzOwxMJdAWinf4QCcuJ49Q7geIIAYUFYvoXgE"
1488    "HCHgEusDqIcO6tgS0zDBUiE+RXRzQQM6qBkd4Yn7yHOg9xADKjCPfEXxyLdvX5B9AhBADBhZ"
1489    "Hckj4KIXNDAKGmqHeuQZGR7B1l/HVuyiegLqEWAj9RHMI0Cf/EB4BcUjAAHEgJmyXiKVWJAq"
1490    "BNg0gRS9pMQHZhOFfI8Aszty2vrxA5K4gGJw5wMEEAPy5BTKghlowoLmdKBHnjz5/OwZeR5B"
1491    "GpAnyyNPYM0UkCfAAJpLvsDHggECCO6Rd2gegUcIsAr5CGpSPyalwEKtEKEtLewNRiRfaGMB"
1492    "QK8A7X4CLYC/w3wCihhwEQxzP0AAMSAv8UPyCLwqBHoE2EdHH4klwyPYW74o0YHTI/AYQY6S"
1493    "7+CCC7ZkECCAGOCbJhCrTV7CIwTSK7yrDR0sGzCPPHr8FN0jsMQFixKAAIJ75C3SshnI8itI"
1494    "oxeUQ+6BapDPT2nikfvEeOThI8iwENQjP2EeAeV3qAcAAogBo68OA6+hHrkN9gh4sIEEr6A2"
1495    "tRDTgDBfPHoAy+dITSttrOAeqIMFHk2BeuQnEIB8guIRgABC8cgr5DWvSOOK90Ctd3CnkKzi"
1496    "F1wAo3kEPqqIUQdi9wh4NAUeIzCPgHqLUA8ABBADvMxCXY8M8gh4eg1YGd67D6oKn1HoEbTR"
1497    "E1iFTtgj4CUdj8GjKV+/wmLkJyyXwDIJQADBPQJelIy8mhpSh4Da7/cHgUfAQ3XIHvmJ5hGA"
1498    "AGJAVCJIWQS0eAnSyrp5CzR/AOuFkFuzo/XVHyGVVjg9gJiyBmUihEe+wzzyE8UnAAEE9Qgk"
1499    "RuAp6xWsdQIbWSSpDiHJI/dJ9Mg3mEd+gj0CL4ABAgjhkTcoMQLMIW9AleGdO/fAWY0Sj6As"
1500    "wyDTI6DRG0jHHeyRX79+QRMXzCMAAYSatF6hZXXwFMLDweGRxxCPQDMJ0CO/UDwCEEDIHkEq"
1501    "s2BzhaApBNCg+OfPz8gF0LkRij3y6MnTZ7AY+QUGP8FFMLQmAQggBpR6/RU8h0CbJ3dAzRNg"
1502    "M5qcCEFMkH9GHplDH/rB5pG7yACyjA3YcAR7BJLboR75jvAIQAAxQPYJw2Lk1UuklXGw5snj"
1503    "x5R6BBonZHoE1gT+DKsSYTECye5fwHuZAAKIATmvvwIvDH85hDzyA55JAAII5JFX72DdXPhW"
1504    "HMiygA/gWh3UYIQvziA1o8PXK0AXnj0CDU2DpjvvIVWG2shr4e8irURD+AU0CPEJWLeDPAJK"
1505    "Wb9//4Z75AuoKQ8QQAyQHehYPAJapgGdZhscHnkE8wgsRsBpC+QRUNoCCCAGSMpC8chr2Lzn"
1506    "jY/alHoEufhFNBbRa3TklPQRC7gLXmYB8shXaIz8+g3LJF8hLWCAAMLpEXB1ODg9Ao6R3zCf"
1507    "QD0CEEDIHoF5Bb7C4TYoSkFOgM9EP0VxJEkZHeERuCfgKQfqAbhHIIugYUtpgWIghzx8As7t"
1508    "sBgB+QSWtoCeAAggLB55jeQRUAOaSh5BrgjRPIIRE+geAY+iQosteIz8hlQlUI8ABBCKR14h"
1509    "e+Q9uPAFjfNR5BGUWh2xxow0j9wFj0eBGykoMYLkEYAAwowR2HJ3SN9wMHrkB6T4BZfAcI8A"
1510    "BBBmjMBS1gesHsEF0DM6PGHBhq0fIryhrY3sEVgxi3uLBkgePUZ+//7zBxQn8NYWQABBPPIO"
1511    "ESOv4WXWDaBHwEPIyKUWsR6B1SDwvjrS4kWwR1BigpBHwFsYwJn9GzxGEB75+hXoCYAAgnkE"
1512    "2oyH7zECNuEp9Ah07hDJIzBvoJa4KPsWcHrlHtQj0KQF9MYfsE9+/oB6BCCAUDzyGr51DZi0"
1513    "BrVHwD4BF8AwjwAEEGrSeo3Y9UWER55B1g3g8Aik4IUva0dZE4te76FsWoICBBvJI8+QY+Q3"
1514    "cowABBAss79FOoACusAM7BHQxNeTJ9gzO3oNjuyRzzg8AtmWh15zE/YIsCKBeOQrOLNDkxZS"
1515    "jAAEECJpwc7SeANdv/+BZI9gX+2A4g1oqsK7jQyPR56ieAQlRgACCOqRd0hngkD3hQxij/zC"
1516    "EiMAAYQcI8h7VHF6BGnxO3jFJ6618NAlG+j5A9GWIsUjoH0dD+B55Ce2UgsggJBiBHpiDkke"
1517    "eQZy8NPPWJb0w/IHcm1+D7Z1j5A/kD0E2UkGrEgQHgHXIpAogXsEIIBIjBHIWgykWAA5Fus2"
1518    "C1iyQktUcG+gOBgtMnB75AvCI2FhoCiBewQggFBjBHHGxCD2yA+sHgEIIJRSiwiPQOZsoB6B"
1519    "bBpC8QjKTiRQ/niA2EX1Eb4jlAoeCYN55DvEIwABhFxqgRMX3CM3buL2CHTaB80j2LZUITwC"
1520    "y+TYNiET45F7D558/gLzyB+ER6AxAhBAqDECLXwhHXaQR+6hegQ8OwDdaPEZXsIic5BXwiI3"
1521    "S+5+RHcs0QDFI9+gHgkDeQS51AIIIAb46XFIHnk7uD3yC5tHAAII5JH379EyCcIj0LYWbP8U"
1522    "pJcE3RaGyBJI6+MeIQ3xwuYI4ZkcvsGYLI/AxrWwewQggLB6BLp4HNyxgux1efIZaYXlpyeI"
1523    "XA1eKPMJfTMq8oAi1CPwndJI275vI9PIAHN/OCizY/cINLMDBBA2jyDnkfvQzaawBjksMp5A"
1524    "Vy09hq1eQp3qROlGwQtdbG7GCjA9AgrSz8/QPIJcagEEEMgjHz6AG/JvkU/GweqRxzg8gjb9"
1525    "QSuPPH2GUmihegQggEAeuXHjPSJGIGcsImLkATaPwNIUcnJCzhtIc+hoGf0WmR4BOQSvRwAC"
1526    "COYRpBh5gxwj4GHwx5B2E8wjSKXTE/SNRrBMjt0joEMrbkKObYKd+QDn4/YISCvII5AZK5hH"
1527    "/qJ6BCCAEB55+xblFEWwR+5CVoI8eoxR12FLUihrAWDL+rTvIkUIzPmEPILsIUgzHlR8QmZ1"
1528    "f/78/RvkjbC/KB4BCCAkj6AeVzR4PfIL6pEwlNYvQACBPPL2xof372Dn42GNkUeYrQ80jzxE"
1529    "XwsA750jGia3iPYITAzmkbughVZPEFkE6BEggHsEdL4QQACBJ3pgHsEWIw9I98hDengEkrQg"
1530    "HgFtHQMIIPDU201QAYx2GBnYIx+hS1oePcaanLAUubB0BWu4IycrVIdiA+gegXgGWPiCVoyB"
1531    "Gyg/gLUIMKuDIgSYtKADdCA/AAQQ2CNv0TwC2XM/+DzyFegRYIcdVPZCkhbEI/dAfgAIIMj0"
1532    "9I0b75DOHX4HPXXmNvhAovv3ifUIYq8XbNgH4pHb6B65CT0uiDiPgEa1QEvfnsJSFihhgWME"
1533    "OmQK9gJAAEE8chNaACMfKwXxCOTcGFiVh1ZCAfmwA2VQd6whJqM+whu8EAfCPQIHN5EOEUKu"
1534    "Z9A98ukpqBaBlllggOIRgACCeOTWzffwo6wRx9feAh+4AszvuDyCc9UrYkrtI1LLHZGkcHkE"
1535    "S+YHZ3XQoprPsIEHSA75C2o0QtqMYC8ABBCyR6Bn3Q1Kj4DXPcA8AsnrYdDReIhHAAII4pHb"
1536    "N99DowQ5Rm7eAp8+Q5RHkFa430ee+CfPI0ieAWf1Bw+gUwo/4P6AeAQRIwABhOoRlPPhPtyE"
1537    "nT/z4BHK2UoYu3EePMBYIwOZHQd105EPB8PmETSAnv3BHrkPWpwJKXxhKQvskZ8/v8M8AhBA"
1538    "0KWAtz68Rz/mDnQiECi7Yx4Shc0j97F4BOUcrdtopS5ejyB8BDpY6R6sLwIss+ARAvLIL6BH"
1539    "oP74DxBACI+8H7QeAc26QZrwP9E9Ak9Z/wECCLZc9gb4mD5Uj3wArTu7izgoi2KP3CLdI6Dj"
1540    "f0FbosANRmAl8geRRUDVIdAjUA8ABBDCIx/QPPIe6hHIli3YeVk4PQJfhww9mRCllQXxyG28"
1541    "MQIVRDqXDnIMHGj3PKydBa7V//799w9cH0Jmp6EeAAggmEduQqLkPeoxkLfAKfQ+7OwCLB55"
1542    "gLwQGb3o/Yg+AnQLe8GFnMuRPAECYI88fPIJNDQHap6A4+MftIGClLL+AwQQzCPA2hYeJ6ge"
1543    "gewPGhiPQMZPgA1fWDsLWqv/gzQZkT0CEEAwj7wGtRs+YPHIHegio4HxCHiKB7ya8Ru0efIX"
1544    "krJA9Tp4AR3s9AeAAIJvhIF6BHFMKugkSJhH7mH1COIwAGj/4x6isYh8vhzpHrmF5BHQgnb4"
1545    "dMIfcA4B+gTuEZj7AQII7pGXt24Am46oHgHXq5BFRg8wt2yjbq1FWdGHPOmMfh4j3uoQ6ido"
1546    "pQ46ZQk2MPcdXBn++QuPEXB//Tt8LzhAACE2i4Fs+PAByRvQenUAPQLOIeDKEN41BOcPcBaB"
1547    "eATufIAAQvPIDZRjUgfaI5BxObS++r9/GE14EAAIICSPgK1A8QjFSQsph5DgkZtIHrn/4PEn"
1548    "xKwIPGHBPPIV7nyAAELa4opixU1Uj4D3Bz1APgsTzSOoa14hKxswJmpvY+vlYsn04FEHcB1y"
1549    "7yGkQwXpq8NyOqzBiBQh/wECCMkjb2C2wAIFfkzqXWhNQiePwIZPgBHyALRJ4StyDoF4BNqn"
1550    "QrqtCCCAkLeBI/erERNiA+URyJzIk2dIs1Rgb0DKXtDCeOQI+Q8QQNg8ApuuRHgEujMbejTG"
1551    "A4wDM5A6U9DGIopH0FrAmL7B9AhkcufBY8hUAqwf8g85Qn4g5ZD//wECCOW0AcgwMyI2oCUP"
1552    "4nRqSKMLwyMYK1/Rl8qgD8Pj9QikAgGdjX3vwWPQpj3QeiDwkAMiQiApC9nxAAGEcnjFR5TJ"
1553    "YvhCaGSP3Id2T/B5BHPNDwkegd4MA56jevDo0xfYwDU8Qv7B+4bIEfIfIIBQjxOB+OQOymH6"
1554    "8PMssXoEY58XhR4BV16QLvZ9YFsRntHh3vgHrUNQc8j//wABRIZH7mN4BDOfgzyBaGlh8Qjm"
1555    "wANafNwFpqtPT8HbE8ATCQiPhGFUhiAAEECoHnmFurQTiyfg+QTLhjXkpa8YGR2nR9CrEHj+"
1556    "gC3qB/cLkfwB6at//4Z6QwlAAKEdgvQRzSNIp7fTxSO34Kua7kHbWN9gjUUMj6C6HCCA0I+l"
1557    "+oh0OLU2Yhct7T0CHf25A11mBl/SD+mnI+cQzDoEBAACCJdHPiJ5BOMgHMiR4/dx78Mh2SOw"
1558    "MTnQXUOgMIQMkaI1TVAqdXSPAAQQxtFt0PXcd7WxnJeOs5GI6hFEoYVtwQxamYXaRQerBk2K"
1559    "f0LsTPgdhih54c13dH/8BwggzMP0IO5AHDaONMhOO48gJqfAM7jgcSzINiR4mxeWsMAR8h3D"
1560    "2QABhMUjd1E8gjwqSmCPF2UeuQM9Ol8bMtiAWHONHB+w8V6MCPkPEEBYjjeEegR6EDLaIAPS"
1561    "FAjGpmdUj9wh0iOwDsNteBnzCFaB/ICOLP5D8gikxMI8lxUggLAdponYxgiadUOMMKKMV9+7"
1562    "By/N7uGs1WHewLLkAbtHwIetQ3flwjuF/9Bz+ncsrgYIIGyHsqJ6BPVMffTBd/weuXOHoEdg"
1563    "s7fQchc00fcZsSnsdxhKhIBnDX9gSVj//wMEENZjclE88vgxxuo+KnsEceUTaFUVYkvYb8gU"
1564    "wj+UEgtplBQFAAQQVo+8gx8Sg1gb9Amydw1+SwJyvifHI7cQMyCwy8RA8yCgmSl4/kDK6OA2"
1565    "FmS09/sDbG4GCCDsR0kjTj0HH+j8CXbI+JMnj6B7KJDzPMGWL0aFCOKjRgh0hOMxJFl9A+/a"
1566    "AY9iIWUQcImFI0L+AwQQjsO9YUc6o3jkE3wP9EPI0fWUeOQ2fAIXkjkgu6DAhx1Bu7ZIFSEi"
1567    "YX3H4Y//AAGE67h1VI9Ajrx+irjoALbukiKP3ILHBviAioefwIv4vyIlK4wCC1d8/P8PEEC4"
1568    "PHIfzSOwjSGwFZqw9IXNI1hHHrCs+kFawa8N3ooLPcPlB9JgHIo/fuIosUAAIIBwXknwCna6"
1569    "IJJHYFvZIIeDQCd4EPsQULYbYQ6hIHkIedkypBEBqs1hh+r8xOYPaA2C85pEgADCfUkE7OhK"
1570    "8ImG4PPb0XYXwjd6IjwCm829i3YJDzaPwLs94PmXx+D14pBzT37BVjcg1ejQhPUdp3MBAgjP"
1571    "tR1vUT3yBePYc6hvwK0w1ObKXcLbRO6ANuDCLk158gmSqr5BvIESHdBRE/B84U3crgUIIHwX"
1572    "qUCP2CbkkYcPHiDvECHdI4/hhe53aLGL7A/48A/u/AECAAGE97YYuE+QPfIV5pmnzz6DffMI"
1573    "tLQG+QQd5Fndj2h77D9CW7iQQT/QQWog0+HHsmHxxl9YfKCOY6EDgADCe9nQW1jaeop8Dv1X"
1574    "+P0AkLs0oDeJoHpEm4BHYMd7Qq4dgJ7c8gOy6egvarkLrwjx3rMNEED4r396CfYI9LQz1MPC"
1575    "vyLd3PD08xPIlRqImNG+h3r1HOo1dNCblT5BDlP/ijiS4jdqdKDEB/5r3AECiMCFXK/AJ+5i"
1576    "9QjMM89AaQy6vhmxAO0+zi4y9BRf6PGuiAPZfkL3tyBnc0i5CzmkgsB19AABROiKtFcPERdo"
1577    "QDzyDQy/Ih+t/wyxnwferHyA1E9G6SxDDiP+hBIb3yHe+I3uDXC7hJj4+P8fIIAIXlr3EhTG"
1578    "jyFH1UKjBH7K9jdErCBOE0C6swoLgLYVYLdYQI+M/QFfJ46azcMgWw0Jx8f//wABRPgawbcP"
1579    "IZ0SqN1IB59Dj3FHzf6Yp2CjAHhRDj+/F5yowN5AzuSwZAU9juY2QWcCBBAxVyaCeyVPYCka"
1580    "2R9wv6B4Bt6SwQKewiMW2RvQ1e5///1F8wc0fxDhSoAAIuqqzbfQVI3shm+gA4S/w49A/4Za"
1581    "mH3BdnDCly/IiRNy4iLk+Abomh+UVAXb54K3PkcAgAAi7vLTh5BbOuDZE3rONtwj0JOEkX2C"
1582    "4RlobQoPBuj5fjCPoFYefyEr339hHYvDCgACiMjraF9BbqqCHBeOODAcfmQt7KhqlOSGARDa"
1583    "kP0AXYEF9wWkTQI79eQbkdeCAwQQsRcEI100g8UjsKOEkbzz9RumHyB6fkBOWoQe3PAHtq4a"
1584    "qcyFeQR8fhaRDgQIIOKvbIYcAwqtwr4i++QHGKJEz3fUAuE7TC3cD6BaA7Kd5S960yoMsi36"
1585    "J/jcVaKdBxBAJFyi/Qjhka/QvA51G8gjP9Di5zs6gJ6lCvEGzB+QxeF/kapAcNvqN+yoPOJd"
1586    "BxBApFxrfgN8zwVG6vpBGPyEHUkI8QJ0jxRkkeU/aKH79y+syP0Fi44HJDgOIIBIu2getAkU"
1587    "uTZB9wfkHEgQjeR4mA9+wX0B8QfUIyj+QNQdxOcOCAAIINI88p/hyZPPn5EbK3Cf/EQFv0AQ"
1588    "Dn5Dj8j5DfMDNElB4uMv3BPgEhfStvpGossAAohE5f/fPv70Ca12/vEd7hOQ85HCH+x8mB+A"
1589    "vvgNjwpYbCAAuKQCZw5IYfWBRIcBBBCpHoGkL/AwF6LtivAJcixAUxH0VAOUmEDyB3jbF7T6"
1590    "gzQQYXt0SAMAAUSGR/6De4xPUS4FgfoEkZx+wz3yB8Uj4M1Rf5E8A/MGOIuD8zipuQMCAAKI"
1591    "HI9AMv0XRHPlO1I9hxwhGB6BhD/CA2GQjAE7vQxyqvI3spwEEEDkeeT/68+fUdriKBU2wiPI"
1592    "XglD+CQsDO6PMMg5Lb+gp8US3yRBBwABRKZHgAB25yJynYIovpD8ghIvcACVg5xSCEtS5GQO"
1593    "KAAIIPI98p8BmrzgNT2KV5ALXah3fv9G8gHEDxBfQK8YILnIRQYAAUSBVtCMEOKWP6S2OXKs"
1594    "IMcMqg+gyQnhi1sUOQUggCjzyP//z56h9ZZQEhhycYwKYIco/4Alqm8UOgQggCj1CKivgiWz"
1595    "fEdqtGABiGsFIM3k15S7AiCAqOCR///fQHqAX7H0ubA1IVH6MEAtb6nhBoAAoopHoKXYl2fw"
1596    "zI8xRIHomKD2jqlmPUAAUc8joPzy7BnqfZLfcACYPBUtBwgganoEAu48RR0xwQq0qW4tQABR"
1597    "3yPALHMbv0fuvqWBpQABRAuPDAgACKBh4xGAABo2HgEIMABb8EQbIiGeqQAAAABJRU5ErkJg"
1598    "gg==")
1599
1600#----------------------------------------------------------------------
1601DownButton = PyEmbeddedImage(
1602    "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAACXBIWXMAAA7DAAAOwwHHb6hk"
1603    "AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAA"
1604    "F2+SX8VGAAADAFBMVEX//////8z//5n//2b//zP//wD/zP//zMz/zJn/zGb/zDP/zAD/mf//"
1605    "mcz/mZn/mWb/mTP/mQD/Zv//Zsz/Zpn/Zmb/ZjP/ZgD/M///M8z/M5n/M2b/MzP/MwD/AP//"
1606    "AMz/AJn/AGb/ADP/AADM///M/8zM/5nM/2bM/zPM/wDMzP/MzMzMzJnMzGbMzDPMzADMmf/M"
1607    "mczMmZnMmWbMmTPMmQDMZv/MZszMZpnMZmbMZjPMZgDMM//MM8zMM5nMM2bMMzPMMwDMAP/M"
1608    "AMzMAJnMAGbMADPMAACZ//+Z/8yZ/5mZ/2aZ/zOZ/wCZzP+ZzMyZzJmZzGaZzDOZzACZmf+Z"
1609    "mcyZmZmZmWaZmTOZmQCZZv+ZZsyZZpmZZmaZZjOZZgCZM/+ZM8yZM5mZM2aZMzOZMwCZAP+Z"
1610    "AMyZAJmZAGaZADOZAABm//9m/8xm/5lm/2Zm/zNm/wBmzP9mzMxmzJlmzGZmzDNmzABmmf9m"
1611    "mcxmmZlmmWZmmTNmmQBmZv9mZsxmZplmZmZmZjNmZgBmM/9mM8xmM5lmM2ZmMzNmMwBmAP9m"
1612    "AMxmAJlmAGZmADNmAAAz//8z/8wz/5kz/2Yz/zMz/wAzzP8zzMwzzJkzzGYzzDMzzAAzmf8z"
1613    "mcwzmZkzmWYzmTMzmQAzZv8zZswzZpkzZmYzZjMzZgAzM/8zM8wzM5kzM2YzMzMzMwAzAP8z"
1614    "AMwzAJkzAGYzADMzAAAA//8A/8wA/5kA/2YA/zMA/wAAzP8AzMwAzJkAzGYAzDMAzAAAmf8A"
1615    "mcwAmZkAmWYAmTMAmQAAZv8AZswAZpkAZmYAZjMAZgAAM/8AM8wAM5kAM2YAMzMAMwAAAP8A"
1616    "AMwAAJkAAGYAADMAAADV1dXU1NTT09PS0tLR0dHQ0NDOzs7Nzc3Ly8vKysrJycnIyMjGxsbD"
1617    "w8PCwsL39/f09PTx8fHu7u7p6enk5OTf39/Z2dnW1tbPz8/Hx8fExMS/v7+7u7u3t7e0tLSw"
1618    "sLCtra2pqamlpaWhoaGdnZ2Tk5OPj4////+ZlMjzAAABAHRSTlP/////////////////////"
1619    "////////////////////////////////////////////////////////////////////////"
1620    "////////////////////////////////////////////////////////////////////////"
1621    "////////////////////////////////////////////////////////////////////////"
1622    "////////////////////////////////////////////////////////////////////////"
1623    "//////////////////////////////8AU/cHJQAAIMRJREFUeNpi+D9MAEAAMQwXjwAE0LDx"
1624    "CEAA0cIjb+9/wQE+g8GdNzSwFCCAqO8R7S+4AcQjz549+0B1awECiJoeAbr0KwR8QwNQ4a9f"
1625    "viK8A/IPFS0HCCAG6nvjGy4A8Qsibp5R0ysAAUQVj7yFeeA7CPzADr5DANRHyL6hSpYBCCAq"
1626    "eOQ1NBJgfviJDSB7B9Mvryl3BUAAUeoRkCcgPgA59xcY/IYBKPcXlPEL4Smwb6ApjTr5BSCA"
1627    "KPPI7a+QqEDyxm/sAMUrkJhBRAzEJ+8ocgpAAFHgEQZIbEAiA+aFP1gAmm+Q/PIdyStAvzyl"
1628    "wDUAAcRAYaKCxQXcD2FQgN0zEJ/8gmYaSLRACmWwTz59Its5AAFEpkfewjwBiQhkH2AArL4B"
1629    "e+Yn3C9wrzz9RGbGBwgg8jwCLmrh/vjzG+6Pv38x/QH3ye8/6EkMFiuIbP/06acnZDkJIIAY"
1630    "yEtU4OgA+QIRFX+hANU3KMkLwyPwAgwSJ1/AcUKeVwACiIF8f/z8hUhSf+EewZmuUPMJwiff"
1631    "kYtisFc+PSHDJwABRKpH3oN9AU5TUE/8hccFqk9gSQqpCEb4AZhHEEUxwi/w4uvTk7ckOgwg"
1632    "gBhILXLBsQFJVMgJCs0bEF/AowAlIqBegBbDP38gl8RfYXn+yRMSXQYQQAykZnJQdPxE9cY/"
1633    "NH8g/IDi8p+/sLZdfqK0XJCS10eSnAYQQKR45AHUH6BkhYiMf3CfhCF7A7VVgtrgggHUNhi4"
1634    "wQZtf4Gi5Mnj9yQ4DiCAGEiKDkgWB3kD4oV/QAAkkOMCWk0g3P0dJ0BvGyO3WUDZ5OPHx8S7"
1635    "DiCAiPcIuAqEFLngDP4PCv6iegPqD2TngZ0IcymssY/mG1gTH1Z4gTP8ExJ8AhBADERHByyT"
1636    "QxPVP2SPQLwB8gRSM+o7om+I6Dti6XvBOipoLWJQgwWYvIjNKQABRKRH3kBzx29o7viH7A+E"
1637    "N34h1QxInQ40gOad78jRhewRcJx8JLLJAhBADMQmqx9IeRzNG6AkBU5OP38gJw945xwToHoK"
1638    "s3ePVJ98fPyIKCcCBBBRHrkJblhBklUYkjdAHgHFBjQygHEBb2wgjZigAQzvYHoFphvqEaLq"
1639    "RoAAIsIjDLBk9Qc1OqCJCpIzIG0mtM44sN0EA6D0DgKfPoF4yB5CpDZknyB55PHDh0S4EiCA"
1640    "CCu5Ayp24f74h5bJQakKxRufEZ749AnmfBj4+BHmmWdP4V4BewSRyBCtLkh18vHjo0dERApA"
1641    "ABH0yGtoskLzBiRRQWMD6At44wKcRUHWP34MdMCjx1Dw6NFDMICIIHkI4h+UYgAx+gWu4Z8A"
1642    "dT98RcidAAFEyCOvv4JrD1CTBCNZwWMD0rSApgWgHz5CHf7gIRw8eKCt/QAI4J75CE9qsGQG"
1643    "jhiwP5A9AiqCifEJQAAxEPIHuDaH+AM5PqB5HJKooNnzKahdAcycIGeDgPb9+/e1oeA+lA3x"
1644    "DCyqQDHzCZ5jMIdXIb2Tj2AzCfgEIIAYCPkDXO6CmogYhS4oWaGkKpAvQIEHCn2Qw+/fQwUw"
1645    "b4Hj5REk4YG9AvcLFq88hcQJMHBe4nUqQADh9cjtb5DaHCV/QH0Bjo1vUF+AG6tAy8Bhf+/D"
1646    "h7tYwYcPSB56AE9o0BwDjhp4/v8ML75B8Qz2iDbeHA8QQAx4Ox+g/AFuW6FkD2juAGcOaPcU"
1647    "6A9QTECiAe6ROwgA8woIgLxyHx43sHhB9clnRCEO8shHcDbD51iAAGLA274CxQd6sgLXHeCy"
1648    "ChQfkAwJSsTALAHyA9gbd7ACRLyAE959aLQ8APkFmA+eIOeXz5+RYwRUBIMyHh7HAgQQA/76"
1649    "HJQ/UMpdSFUOzh3gsgpS1kP9AQlwiEduAwEO34CTGNgrD6A+eQgrxz49hZfI6B4BlSF4EhdA"
1650    "ADHgaV/B4wMlf0Cyx/fvsMwBzhv3kSPiNgzgiBdEXrkPjxpwZnkMTWEIvzxD9cgDbZzOBQgg"
1651    "BtztXUj9gdJmh5ZW4OiAduQ+QiMD5I3bIE+A8K1bt+AeuY3qr9tYvAJOYw8fPn70+COibgH5"
1652    "5RmGR3AWwgABxIAnf0Drc5RS9w80WX0Fd36ePIHljbsQN0OcfQvqETSAGScwz8CzPbAyRYoV"
1653    "aGMNVDvBPKKNK04AAogBd8ICtq8w88fv3z/h/ngKzOQPH0CLqTsIP9zC6RFISYbNIyCvQD0C"
1654    "9gqkcQlvbhL2CEAA4fAIJF39Rs0f8GQFzh6g3IFIVLehLqfEI+DyC1pPPoG1YD6hewSHTwAC"
1655    "iAFnR+rXL5QKBNpIBPsDVOg+BRW52veheQPug5sgQIZHoMnrEaTKh9UsiGYz3CNA1VidDBBA"
1656    "WD3yECmjY40PUO4AZg5tSN4AxwTQ9TeRPIKIHjSPwCpGFI+APXMftVn5Edkv4Kb0Q2jTB+uM"
1657    "EEAAMeAYaQB5BDNhgfIHqFECKq1AbUJ4soL6AArI8sh9FI+gRspHhEdAWrC5GSCAGHDUIKCM"
1658    "jlzugvseP0FNRFChCMod4MwBzhuguLhJBY8g2soPYG0XKEDzCDafAAQQA9auLaRGR4kPcN8D"
1659    "lK6AzfVPjx6BcscdaJUBcvsNIMDvEUQjBbdHkDI+oq2PxSNYXA0QQAzYWibf0Uos5PzxGZKs"
1660    "7t2DJ6pbEG+geQSt9AJ7A5rREQC1kY/iGVgqewjrZz4CFfWwngHm1DxAADHgLrHgHoHWHxB/"
1661    "AIvdx6D4+HAXXlRh88gtDI8gSiyiPAKNFrhHwDGC6OJgOBsggDA98h0to8PKK2j++AT0hjYi"
1662    "WdHQI7BOC9xDeD0CEEAYHvkKGmv4jc0j0Ph49PA+MFkBqw6QD2jrEaTOMaTfD/fIB4zlRQAB"
1663    "xIC15AV10VEqQmAGAeePT6Dy6sMHSNWBWlRhAuSMjuhoIVeH2DI6LgCqDe/d/wDrK6D7BCCA"
1664    "MD2C1saCNhR/gJvtkPwBKnbhtTjVPQJLTsiegMmBm3XYPQIQQAwYEYJaYkEL3h+gZvuzp48f"
1665    "AePjLqxgpY9HYKMYaN3ou2g+AQggBrT1SmCPICIEWCeC+ufQgvcTqHV1F+aRm/TxCCRLwPR8"
1666    "+ADrvn1A7ZoABBADZtH7+88fhDf+ghvuYH8A+wWg+hzctoJn8Zuw2hDKhtCYDWBEpx0tj9/D"
1667    "VVIhxwbcI0j90Lt3UZwOEEAMmN1bpN4UrAcC6p5DK0KER27g8wjWbhVsIIUWHgEIIAbUovc7"
1668    "aFQR4Q/IQAMoo3/+DOrUgrqCsMYHvT0CH9dAJFdktwMEEANKI+v7D2SPwFq830EZ/Rmocw5q"
1669    "mMAqCBp55AEyQPIIbKQMXg0DTUR2PEAAMWCOYyEnLFBGB5dYT8G9c1D/A+ILRDUI9QQyG1Yj"
1670    "3kZt+0LyKJo/wIN1yB6BVH2I0WO4PyBDF7eR6to7SI4HCCBsHoE236FVOmjEBFyjQzpStxGl"
1671    "FYpHYHykyv3mLbRyC5y0sXkEpfkO9QiIBqcuiEfgIzI4PAIQQAyoI71Iray/0D7IN3CJ9fER"
1672    "pCN1h0iPoDTnyfbIA2SPYJT6t24jNYIBAogBtTJEamWBPQKpCr8Am4qgQbh7d+8gN0zo7RFE"
1673    "zwdq561bCNcDBBAD8uollI46KGGBRqpBTRNgifUA2FT8gPAHiuPRPYAIMrS+IcQjiCFT1DHg"
1674    "Bw8Q00JwH92HjMRCcjnMvvdAAPbIbbjzAQKIAaW5CBoihfgDOsb7E1r0gsevQHX6rZvEewSj"
1675    "Zgd5hSiPIE90ARM01CMQ295j9whAADGgDMkhWieIzhS4afIAUhUi0hW5HrlLoUfeIwFQ8Qh3"
1676    "PkAAMSAmPb/B50Kgy0sgHgH20UFDP/dBVeHNQeURkE/gDS6AAGJA6YjAptKho7zg3u3nZ8Cm"
1677    "yX1wZ+r2zZsEPQJlwyotcMUFrgthnSrwJArqMNAD1J4gEgCXwCAdoLyO8MY7EAAnL3iUAAQQ"
1678    "A7zd+/07Yk0AzCPfIW2sx5ASC1uDF8UfSBzkLiLK4PUHLDU6Po88gLR/7+LyCGypCkAAwTzy"
1679    "5Su0ZxgGS1h/oK1eoD8egUqOO7ep4pF7pHoE0sHF4hGwV+C5BCCAGFCmEf4gLRaFdW9BvXTI"
1680    "YNwg8cg7BADGCcwjAAGE5JGfSCv84K1eUB0CbCwCPXIbI6Oje+QGmkduIvVKwM2su9jG5Qh4"
1681    "5OGDh2CPAH1y8waaP5A9AhBAGB6BLYiD1iFfwHUIMECI9Qiemh0xcII8UoLkCUyPgL1yHzTo"
1682    "APII2Cc4PAIQQFCPfIGNv4eh5JAv0FYWpIVAVY9oYxu7wuIRcMF1HzwgCOw5oHgD7JWb0DoR"
1683    "IIAQHvmJWOYHnwkBeeTxQ8g476DzyFsUjwAEEAO0zEKMnsDWZwD7IV8hleF96DjvLSweQfYA"
1684    "No/cQp47BNUhmLNU0LYVzOGP0QB4hh1UlyA8AvbC27dg6ga05QgQQHCP/ED1yK9fkMrw0xPE"
1685    "gDWucRNSPIIx3Qavw4nxCHIOgXrkPdQjAAHEgFSLILIIZKwXPHLyGBStd2jvkYdY/IBY6gVM"
1686    "FUgeeQvyBRiA0hbEIwABxABtwiNG4BGD1sBa/dPHR/dAzfdB5hG4P4A+gXoEIICQPQJb7w4b"
1687    "AgJ2qIC1+r170CE5DMejd64w2l3o8yMf0DtTyHn8MU4AHodHeOQtMgBmkptgLwAEEGaMQBZc"
1688    "QponcI/cun0Tt0ewdhexllqwihBzyuAR0R55i90jAAEE9oj2V+gII9Qjf2DNk6fQ5glmsqLY"
1689    "I+i1+WNyY+T9DfBSG4AAYoCVvlCPQBa5w9tZ8ObJ4PEIeoy8fw/OJAABBPLIW3Dp+xvukT/I"
1690    "XVxt1PbiLbTJT6weuQGfAbqJaGtBV2ppQxYHaD9E8gg+X0BmD0FDH8DgxPQIuNwC9a4AAgjk"
1691    "kfvQagTNI6AuFcwjt28htWixVYqoNT2qR+7APIKeyR/AKsLHBMAj8EIqYPsXu0dAaQsggBjA"
1692    "G7i/ocYIuPAFLZsBDZ7cvYuY9rxFcF4Eq0dgjXik5ZlEFLsoiQu06uXObSweAWX3G0BPAAQQ"
1693    "wiNIMQJtMD4bSh4BCCAsMfIHPnoCbjDexVjChJxXiPUIYi0j1COPEEtmHxPjEaBP7iJ55A0Q"
1694    "oHgEIIAwY+QP2CPg6hDa8kX2yE20vEJ4zgoxwXPvPmKRGaKRi83hH8GruSH0RyweAXnizds3"
1695    "8HIL6AmAAMKMkT+IOcNPj6jmEdS1pYTqQEIeAUcINE6gHgEIICwx8hvWhB+0HgH64A3cK1CP"
1696    "AAQQlhj5DW3Cg/oiMI/cQvYInE3IM7Chh7uwkSz4gnIsHvkIBY+hK2c/IglgeOQNhkcAAgji"
1697    "EaQK8Q9s+QxoTgQywnjnDmKdD7JHcJZk8AVo0KoQuvpaGxYbWKLjI7K7Hz9B9ghY7CHMI8gp"
1698    "C9kjAAGEyyPg6hCLR5BpfB5BDKBAWr3Ia0mxZXLEKrOPH1HZaB6BxchrIIB4BOgVoCcAAgjT"
1699    "I78Ht0dQUxbQJ6AOCtATAAGE6pE/UI9A1gcAPaKN7hEYuIW2cAbDAyD6DswjiGQF8gCqR2CO"
1700    "fYILfAQvYAZ55A5KjAAxNG2BPAIQQMgeAUXJHzSPgGZFsHkE1tWCiWLGBKQ3BfcI0t4E1E1L"
1701    "ODyCIgL0CKhqvnPzBsgjb1BzOyRpAQQQwiO/oMdPwJf8IXnkDpbFr6gewbrWF6VpgrZMEWdM"
1702    "QHeYwdYxf8LiETB4jcjtII8ABBBajAwhj7xB9QhAAFEUI7B6Atd6eFgbCzwHAsnmiCIVPfkg"
1703    "9ilC1l5Dl8hj8chbSKEFiROYRwACCC2zk+IR6KYEnLLQuhA61gDfu4MUER+RowC2ghzJI5+g"
1704    "HvkI9chtpBh5/RpSAMPyCEAAIccIkke+E+URzJ0VWJMVuGeLsgMRJQ6eQrZeQjyBtIsUwsXq"
1705    "kddQAIwUWIwABBBGjPwmIUYGzCNwn7yGxwhAACHFyC9YHvlFcYwgFtDcgxW88GSFlIpg/kD3"
1706    "yFO4BIEYQUpaAAGEkdnBu1bBY1rPnnzE9Ai6u7FHCGLi8z4Oj3zC45GnxHsEESMAAQT1CGSA"
1707    "DuQVcj2CbR8V0sL9RyiJCppqPuHyCBIbxSOwJgoiQuAeAQggbB75ORQ9AhBAII98/gwdjQed"
1708    "ZwTeY4/dI3eQdrUhOxtl/T582SI0k8N3T6F7A5QXYA7G8AjSRni4R+7dufX+/Vu0lAX3CEAA"
1709    "MUDP84MesPEb6pHvSB75cAdthyTWnXl3kddYIwaqYQNwKHkDOeSfIgEUDtoeK6BHbt9CixBk"
1710    "jwAEECxGQEucYEfj/ER45AF4OT98oSvCR1g24mLM2GImq09YnYofQD3y4P6HWzch9fprZI+8"
1711    "gXoEIICQPAKPkcHtEeRaBKnUAgggqEe+wGLkF9wjoGk38OADTo98gK6ZRF+vj7xiFCV/oOwu"
1712    "JMUj4G2CH24hF76vwAgRIwABhMUjP2E1+yfogswPSB5BiwSsGw/g44na0I1fSPGB1yNwSWRF"
1713    "T2AeuYvskVdAgBIjAAGE6pFfkLwO37oDWgtP2CP3sGzAh+8mRs7oT6EnipDiEfDZC2B33L0J"
1714    "7h++gXoD4hNIXxfoCYAAwoiRX4gYGZQegWSRV0gxAvEIQACBPHLn2ecv0JOaoCcYQTdNfgJ1"
1715    "lRGrVUnyyEMkb1DBI8C8Cumxv8ESI6DFpgABBPLIm2dfMD3yBbdHkDP5PSwAl0eekuWRT6ge"
1716    "AfvkFWqMgCZ6AAIIPPX2DFz+Qj3yE3zQCXQbK2xf0weMUgrP5iLYtAHk2JCP8O4T3CPIAOED"
1717    "JB9ieuQ+0CNo/nj1ClIhglPWf4AAAnvk7ufPEI/8hMUIaP/n4PLIvbu336P54xU0RsCToQAB"
1718    "BJmeBnrkK+REGsiZg9+huR101II2ikfQAYZHkOsPJI+A2lXkeATU2AdNvYHmQt9hegQcI2Av"
1719    "AAQQzCNfvsKOwvwJPZfiC3yH9L3797DGBEGPPIZ5BKlNhc8jOKp1kEc+YPfIa7hHAAII2SPg"
1720    "XIIcI89AZoAWrN7D6wH0xQyoAz/QI1xweQSrp1DbvqDp6Q/AXhWiFnkJBLBCC+oRgACCeOQZ"
1721    "uCb5Djs8GeGRj4PCI8D2xd07wHod2R/oHgEIIKhHnkGj5AfsWE7w0S3PoI2D+7CNWjg8grEh"
1722    "B3wQBekeeYbLI9rad27ffA9LWC/hHoEMYoO9ABBAlHgEfjwQ+mIGkEegm54hcx3U8MgNLB5B"
1723    "yiMAAQRdCgjyCDS7w058BZ/rACz7IEvxsJxphH2h6APUjI44SYsYj2CkM2iDEeiRW4jCF+oP"
1724    "lLz+HyCAEB75Aj4X88cPZI88HSQeuX8fmLIgHnmF7BFEFvkPEECw5bIgj0AS1w+oR75CD6IB"
1725    "reqGbIMaKI+AGq6QBiMiYSE8AvUAQADh8QikSwLxiDYuj2hj2awGzSHwgV7MniEpHgFvO/8A"
1726    "iRA8HgEIILhHwD5BeOQ7xCNPwR55AN20hWf3oDZmfFDBI+BDwkDNkztoOeQluD58g+QRgACC"
1727    "eeQZ+Ag46PHpMI98gXjk4UB6BLSN88PtW+8wIwTiEdjZIgABBPPIG/A5YwiPQM7eBXd3YQeS"
1728    "EOURlKnnj4gWI3keAWd1bfC8CKo/ECkLthMGIIDgG2HAUYIUI7DzgcAtHUyPwHZoY/MIyqwU"
1729    "YtqDZI+Ahx2ALYu74MoQ3SOvUVLWf4AAQmxNguQScBEMPdkZfGDTp4/Q48zwnwUAW7eEOu0M"
1730    "bTCS4xFYwnpw/wNG0QuNEZA/XsDcDxBAiM1izz5//gK98gFybO3Xr9DzzMBV0gB4BJKw7n0A"
1731    "LZwD9dVfIXvkNUqZ9f8/QAAheQSR32FnbYPPAhtIjwCLXnCH6h1SifUC7BHYUBDc+QABhOIR"
1732    "SDsFcd8Jske0CXsEc0UDondIokeg0z2gEV+sHnkFHp1D9ghAACFtcX0GyyWwc4TB52E+gzWk"
1733    "0eMEc9Ur+loyyMwtWTU7eKAespcI3g8B++MFEMCyCLI//gMEEJJH3kF8AjkTGXZNAGiNEOQM"
1734    "RnI9QkozHqnAgjZOPsDGF8EeefEC4hFYgxHpdGaAAELeBv4MESdIHoE0E0Art+jnEcisHCiH"
1735    "3EZp9aJ7BMnxAAGE7JGnzyBHvH5F9chTqEe0cZ6ZAc7lmB5BrCkhzSOweV3QmTi3biJ55AWy"
1736    "R96gegQggFBOG3gKOUcU5hPoOaXgsxjBuQTppBL0dfoYay3h055A+hPWkThCCQuUQ+5Cpz/B"
1737    "Aw5wj8CaJ8iOBwgglMMrPqEkrq8wj0BOpkY9coVQskI5eY1Ej4C9Ackh8GFSNH+8Qo+Q/wAB"
1738    "hHqcyNOnz6BxAj/vHXqsJCUe+USGR0BLzZBKrNcvX+JNWP//AwQQqkc+fYJWi9T1yFMSPPIU"
1739    "mJIhxcsDRIkFL3lxewQggFA98voT7PxgxHVmEI8AI/oh5EQl2JFEaBu3sS4axTfdht0Xz6Dr"
1740    "gUAZ/Q64bYIoeZ8/RymyUA8yBgggtEOQnmD1CPToVeRjV2jkEViBBa0KIbO40AzyHMkjqJUh"
1741    "CAAEEPqxVJ+gGR7lQGfIwcTwoyvBB9yS4pFP2DyDuwIBzaqDlvTDRt8hNchzsEdwJKz//wEC"
1742    "CN0jT548hfkEcZg9uNyCHJgOO4gM0UiE7KfFtukAZW6dCI8Aswd0rBcUH6A5XGgbC5RBniN5"
1743    "5A0WjwAEEMbRbcBSHzVOYAfRQ/fRoR1zh2+tPsqSpk9ofsDwzFNY9gANQEG2WEBXB7yEZJDn"
1744    "0LIXa4T8BwggzMP0sHrkGXiW4jGtPIK8Tgs8/APehgRac/0aJWGBPPL6FShCMJwNEECYHgFV"
1745    "xMinuNPRI5CzPsFzIe+Rpqcg3sCbQ/7/BwggTI+8hZ8Wjri04hn8ugaERyC7ox7i2M+CvoL0"
1746    "EwGPgIoEyBQC+HC4m+9QmopQf8ByCEqzFwoAAgjbYZqgxPUMfoEDFo+gHDqIiBvUMQdUb3zC"
1747    "mPBE9wi04Y6oP2AFFpYSC4urAQII26GskKUW8KPosXoEtc2IzSN4khW6R57COyCPtCH1B7QC"
1748    "weURLI4GCCCsx+SC55KRTz3H7hFtIj3yCY9HkNbNgFvYHz6AeiDQtTPQLggig0DXNWFzM0AA"
1749    "YfXIDdgCGMzT22EtLuSDWQh65CkRHvkIObb97t1bN2FbEmBdKUQGgXgE62WpAAGE/ShpUIHz"
1750    "CXFDACTyYefpa6N3rMDnjKLld2SPPMXpEfhlK+CNFfeRNk1C59hgBRahhPX/P0AA4TjcGxyk"
1751    "sDiBpuInsMsacJ1mi73oxbdGCzLGAElWkNNwbkI37UAqdLg/niOVWNhdDBBAuI5bhywWQ8mO"
1752    "NPAI/Jzoh5ATOUH9QXh5RUp8/P8PEEA4PfIIlDygZ+rDp/OAHnmEOpzyAFqnYHoEW+sXNlYH"
1753    "W4wJ8Qd4lBx0NtFNRDWINT7weQQggHBeSfAKtLMAyRmwqftH8GtS7t+7j76gCZtX0G9OAnvg"
1754    "EyJvQEZogLU5YgfVa2hDEV6jv8Ac60UHAAGE+5KIhw/h909Ai3kMj0BH5RG9RGyNlE9IAHkt"
1755    "OSw2wPeWQOID7g/kcpeo+Pj/HyCA8Fzb8fYh0pH6iOPbUT2Cuk0S9xYwjH1HkCuJoLd9IMUG"
1756    "0ggWvKEI9wae69IAAgjfRSpoHvkI8wjsJGT47S4oHoF5Bst5/E8QQ0SwkwPAdxogzg6ArFdE"
1757    "TVa4+yDIACCA8N4Wg7JzBdx+ghy38gB65iDS+iD0zXmwiwU+YomJjx8ht0NpQ7xx+xasSfIG"
1758    "rZkIbyhC/YHPsQABhPeyobfw+yeQXQD2CHjtFspqofso+z7hHsECYFcr3YdGB2JPNLwaRPMI"
1759    "wYT1/z9AAOG//unVI+T9j6geQb1PCLatGFtj8hHm2V/a0BOIb99E2+yJHB+o+QN3gQUGAAFE"
1760    "4EKuVw/R+k7QM8jA7riL9coaZM88xHbMH3jKHnYkBux8ENiy5JeY6Qo6WYjfH/8BAojQFWmv"
1761    "cJ7TdxdlMw/y+mzktdios0FIJ3qCjsO48R4lOiBTa+jx8Qqyue0lAYcCBBDBS+teoaZ7SKMR"
1762    "cUwq5kYl5L3r6AB2hPJdZG+g+OMFotkOndF5/ZqI+Pj/HyCACF8j+Bb5hNGH6B7B3KAP2dKH"
1763    "9WoxxHmkkNOMEDn8NWyiE7nVjpQ/CN+3CRBAxFyZ+AAVIHvkFvaDK7Dd/ATbTHoDdpDc27co"
1764    "mRw2vIvwB3yJ8lsiXAkQQERdtfkWMYANLnHug0reO/AjnmDnvULP5YQf/YK+QRz5KDw0TyCP"
1765    "UsOHqokqdmEAIICIu/wUNb9CVs+CPXIT+eBaZK8gvIM4iAfVG6geeYnqEVh0vCZQnyMAQAAR"
1766    "eR3tK6QuCIZH3qOfA4k42BJVHHEqyxuEJ2Cp6iXCE4g1MxiD7jgBQAARe0Ew0sUA9xC3uSE8"
1767    "8o4IgPAFPDLgyeolcmzAcweukQYsACCAiL+yGeqRe7CLq2Bnx2PGB/RQNchhd2hH5MA21b9G"
1768    "TlOovoAtu3xDdLICAYAAIuES7fvIZ+mjeQRxduJbzNN90FIUYo8UfG0Jki9evkLkjjck3DYP"
1769    "EECkXGv+jpBHwDGB1QPIm+mhnkBe64Pii1ew2MA+7oMDAAQQaRfNwzxyF0eMYIkAlPMAXr9B"
1770    "eALJHyiryeDbC9+S5DSAACLNI/8Z7kHa79ADLWAeeYd2fCIsFtCONUDxBaZHYP4AJ6q3JLoM"
1771    "IIBIVP7/DbRHdQdyjjuqR3AmKKSNg69eY/cI0iYdcHS8JtFhAAFEqkcg6QveCkfxyDvs5ROK"
1772    "N5DiA+yRV8h7KGAl7tt3pLsKIIDI8Mh/XB7BWkIhe+M1si/QAKzEJT13QABAAJHjEaBXwB5B"
1773    "tLKwxsgbjBh5hRohyFHx+vUbSrzx/z9AAJHnkf+vkDxyg0CMoOYQHJHx+g0sWb0kz0UAAUSm"
1774    "R0C7F+F3HbxHbw2iHR31GjV5gTMK6g5oxIlyZDsHIIDI98h/BmgzHrldi80foLB+jQu8eY3s"
1775    "DQpcAxBAFGgFFcaIrhL6SbzYanM0P6BEBekFLioACCDKPPL/P7QFjFkpolbp8DT2BtUTSEfi"
1776    "UegQgACi1CNA8BJWDKMnrzcQ/Aarp5Aj490Lyl0BEEBU8Mj//69vofVk35IAIFttKQYAAUQV"
1777    "j0ASGTzjox/B/fYdZqMYctI4xQkKAQACiHoe+f8fOtSAWiBD+ihvMbuLVPXG//8AAURNj0CH"
1778    "XCCjKzh78hBvUN1agACivkeAWeYtfo+8fUUDSwECiBYeGRAAEEDDxiMAATRsPAIQYAAZi6PF"
1779    "fdLmvAAAAABJRU5ErkJggg==")
1780
1781
1782if __name__ == '__main__':
1783
1784    import wx
1785
1786    class MyFrame(wx.Frame):
1787
1788        def __init__(self, parent):
1789
1790            wx.Frame.__init__(self, parent, -1, "ShapedButton Demo")
1791
1792            panel = wx.Panel(self)
1793
1794            # Create bitmaps for the button
1795            bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
1796
1797            play = SBitmapToggleButton(panel, -1, bmp, (100, 50))
1798            play.SetUseFocusIndicator(False)
1799
1800
1801    # our normal wxApp-derived class, as usual
1802
1803    app = wx.App(0)
1804
1805    frame = MyFrame(None)
1806    app.SetTopWindow(frame)
1807    frame.Show()
1808
1809    app.MainLoop()
1810