source: trunk/src/testing/bin/sageLauncher.py @ 4

Revision 4, 219.3 KB checked in by ajaworski, 13 years ago (diff)

Added modified SAGE sources

Line 
1#!/usr/bin/env python
2
3############################################################################
4#
5# SAGE LAUNCHER - A GUI for launching SAGE and all related components
6#
7# Copyright (C) 2007 Electronic Visualization Laboratory,
8# University of Illinois at Chicago
9#
10# All rights reserved.
11#
12# Redistribution and use in source and binary forms, with or without
13# modification, are permitted provided that the following conditions are met:
14#
15#  * Redistributions of source code must retain the above copyright
16#    notice, this list of conditions and the following disclaimer.
17#  * Redistributions in binary form must reproduce the above
18#    copyright notice, this list of conditions and the following disclaimer
19#    in the documentation and/or other materials provided with the distribution.
20#  * Neither the name of the University of Illinois at Chicago nor
21#    the names of its contributors may be used to endorse or promote
22#    products derived from this software without specific prior written permission.
23#
24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35#
36# Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum
37#
38# Author: Ratko Jagodic
39#       
40############################################################################
41
42
43import wx, cPickle, os, sys, os.path, stat, string, copy, time, cStringIO, zlib, pickle
44import wx.lib.filebrowsebutton as fb
45import wx.lib.buttons as buttons
46import wx.lib.hyperlink as hl
47import wx.lib.scrolledpanel as scrolled
48from wx import BitmapFromImage, ImageFromStream
49import traceback as tb
50import subprocess as sp
51from threading import Thread
52
53
54# shortcut
55opj = os.path.join
56from sagePath import getUserPath, SAGE_DIR, getPath, getDefaultPath
57
58
59# --------------------------------------------------------
60#
61#                   GLOBALS   
62#
63# --------------------------------------------------------
64
65# component types and official names
66APP_LAUNCHER = "Application Launcher"
67FILE_SERVER  = "File Server"
68SAGE_PROXY   = "SAGE Proxy"
69SAGE_UI      = "SAGE UI"
70SAGE         = "SAGE"     
71componentNames = [SAGE, APP_LAUNCHER, SAGE_UI, FILE_SERVER, SAGE_PROXY]
72
73# this is where all the information about the components is stored
74components = {}   # key=componentType, value=Component()
75
76# tileConfig file (structure of the cluster driving the display)
77tileConfig = None
78
79MAX_TEXT_LEN = 64000     # max characters in the output text ctrl
80PREFS_FILE = getUserPath("sageLauncherSettings.pickle")   # store the settings in a pickle file
81PY_EXEC = sys.executable    # platform dependent python executable
82
83# quit if the SAGE_DIRECTORY env var is not set
84if not "SAGE_DIRECTORY" in os.environ:
85    print "SAGE_DIRECTORY environment variable not set."
86    print "Please first set the SAGE_DIRECTORY environment variable to your sage directory."
87    sys.exit(0)
88
89# default frame background color on windows (screwed up otherwise)
90global colour   
91
92
93
94
95
96# --------------------------------------------------------
97#
98#               CONVENIENCE FUNCTIONS
99#
100# --------------------------------------------------------
101
102
103def makeBoldFont(widget):
104    f = widget.GetFont()
105    f.SetWeight(wx.BOLD)
106    widget.SetFont(f)
107
108def makeBiggerFont(widget):
109    f = widget.GetFont()
110    ps = f.GetPointSize()
111    f.SetPointSize(ps + ps*0.2)  # make it 20% bigger
112    widget.SetFont(f)
113
114def makeSmallerFont(widget):
115    f = widget.GetFont()
116    ps = f.GetPointSize()
117    f.SetPointSize(ps - ps*0.2)  # make it 20% smaller
118    widget.SetFont(f)
119
120def makeBiggerBoldFont(widget):
121    makeBoldFont(widget)
122    makeBiggerFont(widget)   
123
124def isWin():
125    return "__WXMSW__" in wx.PlatformInfo
126
127
128
129# if the loadSettings fails for some reason (other than first execution),
130# do not save (since you will overwrite the previous good settings)
131DO_SAVE = True   
132def saveSettings():
133    if DO_SAVE:
134        # first assemble a dictionary of all the settings and then store it
135        settings = {}
136        for componentType, comp in components.iteritems():
137            settings[componentType] = comp.settings
138
139        # now try and save the dictionary
140        try:
141            f = open(PREFS_FILE, "wb")
142            cPickle.Pickler(f, cPickle.HIGHEST_PROTOCOL).dump(settings)
143            f.close()
144        except:
145            print " ***** Error occured while saving settings:"
146            print "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
147                       
148
149
150def loadSettings():
151    """ try loading the settings and if it fails just return the default
152        if it succeeds, update the default with the saved settings """
153
154    # default settings
155    settings = {}
156    settings[APP_LAUNCHER] = AppLauncherSettings(APP_LAUNCHER)
157    settings[FILE_SERVER] = FileServerSettings(FILE_SERVER)
158    settings[SAGE_PROXY] = SageProxySettings(SAGE_PROXY)
159    settings[SAGE_UI] = SageUISettings(SAGE_UI)
160    settings[SAGE] = SageSettings(SAGE)
161
162    # try loading the saved ones
163    try:
164        if os.path.isfile(PREFS_FILE):
165            f = open(PREFS_FILE, "rb")
166            (d) = cPickle.Unpickler(f).load()
167            f.close()
168            settings.update(d)
169    except:
170        print " ***** Error occured while loading saved settings and the changes you make will not be saved after you quit sageLauncher:"
171        print "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
172        global DO_SAVE
173        DO_SAVE = False
174       
175    return settings
176
177
178
179
180def getTileConfig():
181    """ returns the full path of the currently used tile
182        config file as specifed in the fsManager.conf """
183   
184    f = open( getPath("fsManager.conf"), "r")
185    for line in f:
186        line = line.strip()
187        if line.startswith('tileConfiguration'):
188            config = line.split()[1].strip()
189    f.close()
190    return getPath(config)
191
192
193
194
195# --------------------------------------------------------
196#
197#                      COMPONENTS
198#
199# --------------------------------------------------------
200
201
202class Component:
203    """ this is the class that contains all the information
204        about each of the components:
205         - settings, output field, process object
206        There is one of these for each component and they are
207        all stored in a global dictionary
208    """
209    def __init__(self, componentType):
210        self.componentType = componentType
211        self.settings = None
212        self.output = None
213        self.process = None
214
215
216
217
218
219# --------------------------------------------------------
220#
221#             COMPONENT SETTINGS
222#
223# --------------------------------------------------------
224
225# these are the objects that store all the information about
226# a component and will be saved in a file
227# they define how things are run and other specific settings
228
229
230class ComponentSettings:
231    """ this is the base class for all the settings """
232    def __init__(self, t):
233        self.inBG = False
234        self.doRun = True
235        self.cmd = ""
236        self.componentType = t
237        self.pid = -1  # pid from the started process
238
239
240    def getStartCommand(self):
241        """ this must be implemented by the subclasses
242            it should return the final command that is used to run the component
243        """
244        raise NotImplemented
245
246
247    def getCwd(self):
248        """ this must be implemented by the subclasses
249            returns the current working directory
250        """
251        raise NotImplemented
252       
253
254    def __getinitargs__(self):
255        """ for pickling to work properly if we add some
256            more settings to this class at a later time """
257        return (self.componentType,)
258
259
260
261
262class SageSettings(ComponentSettings):
263    def __init__(self, componentType):
264        ComponentSettings.__init__(self, componentType)
265        self.doRun = True
266        self.onStart = "xhost +local:\npython ../dim/dim.py\npython ../dim/hwcapture/localPointer.py localhost"  # a string of commands to be executed after shutdown (split by \n)
267        self.onStop = "fuser -k 19010/tcp"
268
269        # things to kill on each node
270        self.toKill = "fsManager sageDisplayManager sageAudioManager svc imageviewer mplayer bplay bplay-noglut VNCViewer render atlantis atlantis-mpi checker pdfviewer sagepdf"
271       
272
273    def getStartCommand(self):
274        """ commmand used to run this component """
275        return ["fsManager"]
276
277
278    def getCwd(self):
279        """ returns the current working directory """
280        return opj(SAGE_DIR, "bin")
281
282
283    def getKillCmd(self):
284        """ returns a list of kill commands to run for stopping sage
285            On Windows just kill the stuff on the local machine for now
286        """
287        cmds = []   # this will be returned as a list of lists
288        killString = self.toKill   # processes to be killed on each node
289
290        # windows
291        if isWin():
292            # first append .exe to the processes
293            killList = [proc + ".exe" for proc in killString.split()]
294           
295            # generate the commands to kill each process
296            c = ["taskkill", "/F"]
297            for proc in killList:
298                c.extend(["/IM", proc])
299            cmds.append(c)
300           
301        # Mac + Linux
302        else:           
303            # get the ip addresses of the nodes of the cluster
304            # + the master node
305            tileConfig.readConfigFile(getTileConfig())
306            IPs = tileConfig.getAllIPs() 
307            IPs.append("127.0.0.1")
308
309            for node in IPs:
310                c = []
311                k = "/usr/bin/killall -9 %s" % killString
312                c.extend( ["/usr/bin/ssh", "-fx", node, k] )
313                cmds.append(c)
314
315        return cmds
316   
317       
318    def __getinitargs__(self):
319        """ for pickling to work properly if we add some
320            more settings to this class at a later time """
321        return (self.componentType,)
322   
323
324
325class AppLauncherSettings(ComponentSettings):
326    def __init__(self, componentType):
327        ComponentSettings.__init__(self, componentType)
328        self.doReport = False
329        self.doRun = True
330        self.server = "sage.sl.startap.net"
331        self.port = 19010
332
333
334    def getAppConfigFilename(self):
335        return getPath("applications", "applications.conf")
336
337    def getStartCommand(self):
338        """ commmand used to run this component """
339        cmd = [PY_EXEC, "-u", opj(self.getCwd(), "appLauncher.py"), "-v", "-p", str(self.port)]
340
341        # report to sage server?
342        if self.doReport: cmd.extend( ["-s", self.server] )
343        else: rep = cmd.append("-n")
344
345        # run in background?
346        if self.inBG and not isWin(): cmd.append("&")
347       
348        return cmd
349
350
351    def getKillCmd(self):
352        """ returns the command to kill the appLauncher """
353        return [PY_EXEC, "KILL_LAUNCHER.py", str(self.port)]
354       
355       
356    def getCwd(self):
357        """ returns the current working directory """
358        return opj(SAGE_DIR, "bin", "appLauncher")
359
360       
361    def __getinitargs__(self):
362        """ for pickling to work properly if we add some
363            more settings to this class at a later time """
364        return (self.componentType,)
365
366
367
368class FileServerSettings(ComponentSettings):
369    def __init__(self, componentType):
370        ComponentSettings.__init__(self, componentType)
371
372
373    def getConfigFilename(self):
374        return getPath("fileServer", "fileServer.conf")
375
376
377    def getStartCommand(self):
378        """ commmand used to run this component """
379        if self.inBG and not isWin(): bg = "&"
380        else: bg = ""
381        return [PY_EXEC, "-u", opj(self.getCwd(), "fileServer.py"), "-v", bg]
382
383
384    def getCwd(self):
385        """ returns the current working directory """
386        return opj(SAGE_DIR, "bin", "fileServer")
387
388       
389    def __getinitargs__(self):
390        """ for pickling to work properly if we add some
391            more settings to this class at a later time """
392        return (self.componentType,)
393
394
395
396
397class SageProxySettings(ComponentSettings):
398    def __init__(self, componentType):
399        ComponentSettings.__init__(self, componentType)
400        self.host = 'localhost'
401        self.port = 20001
402        self.password = "pass"
403
404
405    def getStartCommand(self):
406        """ commmand used to run this component """
407        return [PY_EXEC, "-u", opj(self.getCwd(), "sageProxy.py"),
408                "-s", self.host, "-p", str(self.port), "-x", self.password, "-v"]
409
410
411    def getCwd(self):
412        """ returns the current working directory """
413        return opj(SAGE_DIR, "bin", "sageProxy")
414
415       
416    def __getinitargs__(self):
417        """ for pickling to work properly if we add some
418            more settings to this class at a later time """
419        return (self.componentType,)
420
421
422
423class SageUISettings(ComponentSettings):
424    def __init__(self, componentType):
425        ComponentSettings.__init__(self, componentType)
426        self.host = 'sage.sl.startap.net'
427        self.port = 15558
428        self.doRun = True
429        self.autologinMachine = ""
430        self.loadState = ""
431
432
433    def getStartCommand(self):
434        """ commmand used to run this component """
435        cmd = [PY_EXEC, "-u", opj(self.getCwd(),"sageui.py"), "-v", "-s", self.host, "-p", str(self.port), "-t"]
436        if self.autologinMachine != "":
437            cmd.extend( ["-a", self.autologinMachine] )
438        if self.loadState != "":
439            cmd.extend( ["-o", self.loadState] )
440        return cmd
441
442
443    def getCwd(self):
444        """ returns the current working directory """
445        return opj(SAGE_DIR, "ui")
446
447
448    def __getinitargs__(self):
449        """ for pickling to work properly if we add some
450            more settings to this class at a later time """
451        return (self.componentType,)
452
453   
454
455
456
457
458
459# --------------------------------------------------------
460#
461#                 UI STUFF
462#
463# --------------------------------------------------------
464
465
466###
467### these are the specific frames for each component that let's you configure the component
468###
469
470
471class ComponentFrame(wx.Frame):
472    def __init__(self, parent, componentType):
473        s = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL
474        wx.Frame.__init__(self, parent, wx.ID_ANY, componentType+" Configuration", style=s)
475        if isWin(): self.SetBackgroundColour(colour)
476        self.settings = components[componentType].settings
477        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
478        self.SetIcon(getSageIcon())
479        self.Bind(wx.EVT_CLOSE, self.OnClose)
480
481
482    def MakeWidgets(self):
483        okBtn = wx.Button(self, wx.ID_ANY, "OK")
484        okBtn.Bind(wx.EVT_BUTTON, self.DoClose)
485        self.mainSizer.Add(okBtn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 15)
486        self.SetSizerAndFit(self.mainSizer)
487
488
489    def DoClose(self, evt=None):
490        self.Close(True)
491       
492
493    def OnClose(self, evt):
494        """ save everything here but let the subclasses do that
495        since it's specific for each... therefore it must be implemented """
496        raise NotImplemented
497
498
499       
500
501
502class AppLauncherFrame(ComponentFrame):
503    def __init__(self, parent):
504        ComponentFrame.__init__(self, parent, APP_LAUNCHER)
505        self.configs = AppConfigurations(self.settings.getAppConfigFilename())
506        self.currentConfig = None   #current config object being edited
507        self.MakeWidgets()
508        self.Show()
509
510
511    def MakeWidgets(self):
512        self.run = wx.CheckBox(self, wx.ID_ANY, "Run in background")
513        self.run.SetToolTipString(HELP_INBG)
514        self.run.SetValue(self.settings.inBG)
515
516        self.mainSizer.Add(self.MakeConnectionBox(), 0, wx.EXPAND | wx.ALL, 10)
517        self.mainSizer.Add(self.MakeAppConfigBox(), 0, wx.EXPAND | wx.ALL, 10)
518        self.mainSizer.Add(self.run, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.ALL, 5)
519        ComponentFrame.MakeWidgets(self)
520
521
522    def MakeConnectionBox(self):
523        # the box
524        box = wx.StaticBox(self, wx.ID_ANY, "Public access:")
525        box.SetForegroundColour(wx.BLUE)
526        makeBiggerBoldFont( box )
527        boxSizer = wx.StaticBoxSizer( box, wx.VERTICAL )
528
529        # widgets
530        hostLabel = wx.StaticText(self, wx.ID_ANY, "Hostname / IP:")
531        self.hostText = wx.TextCtrl(self, wx.ID_ANY, self.settings.server)
532        self.hostText.SetMinSize((150, -1))
533        self.hostText.SetToolTipString(HELP_AL_PUBLIC_HOST)
534
535        #portLabel = wx.StaticText(self, wx.ID_ANY, "Port:")
536        #self.portText = wx.TextCtrl(self, wx.ID_ANY, str(self.settings.port))
537
538        self.reportCheck = wx.CheckBox(self, wx.ID_ANY, "Allow public access?")
539        self.reportCheck.SetToolTipString(HELP_AL_PUBLIC)
540        self.reportCheck.SetValue(self.settings.doReport)
541        self.reportCheck.Bind(wx.EVT_CHECKBOX, self.OnPublicCheck)
542        self.OnPublicCheck(None)
543       
544        # add widgets to the boxSizer
545        hSizer = wx.BoxSizer(wx.HORIZONTAL)
546        hSizer.Add(hostLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
547        hSizer.Add(self.hostText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
548        hSizer.Add((10,10))
549        #hSizer.Add(portLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
550        #hSizer.Add(self.portText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
551
552        boxSizer.Add(self.reportCheck, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
553        boxSizer.Add(hSizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
554       
555        return boxSizer
556
557
558    def MakeAppConfigBox(self):
559        # the box
560        box = wx.StaticBox(self, wx.ID_ANY, "SAGE Applications:")
561        box.SetForegroundColour(wx.BLUE)
562        makeBiggerBoldFont( box )
563        boxSizer = wx.StaticBoxSizer( box, wx.HORIZONTAL )
564
565        # apps label and list
566        appsLabel = wx.StaticText(self, wx.ID_ANY, "Applications:")
567        makeBoldFont(appsLabel)
568        self.appsList = wx.ListBox(self, wx.ID_ANY, choices=self.configs.getAppList())
569        self.appsList.Bind(wx.EVT_LISTBOX, self.OnAppList)
570        self.appsList.SetToolTipString(HELP_AL_APPS)
571        addBtn = wx.Button(self, wx.ID_ANY, "Add", style=wx.BU_EXACTFIT)
572        addBtn.Bind(wx.EVT_BUTTON, self.OnAppAdd)
573        delBtn = wx.Button(self, wx.ID_ANY, "Delete", style=wx.BU_EXACTFIT)
574        delBtn.Bind(wx.EVT_BUTTON, self.OnAppDel)
575
576        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
577        btnSizer.Add(addBtn, 1, wx.ALIGN_CENTER | wx.ALL, 4)
578        btnSizer.Add(delBtn, 1, wx.ALIGN_CENTER | wx.RIGHT | wx.TOP | wx.BOTTOM, 4)
579                     
580        appsSizer = wx.BoxSizer(wx.VERTICAL)
581        appsSizer.Add(appsLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
582        appsSizer.Add(self.appsList, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
583        appsSizer.Add(btnSizer, 0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 1)
584             
585        # add widgets to the boxSizer
586        self.confSizer = self.MakeConfigBox()
587        boxSizer.Add(appsSizer, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
588        boxSizer.Add(wx.StaticLine(self, wx.ID_ANY, style=wx.VERTICAL), 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
589        boxSizer.Add(self.confSizer, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
590
591        return boxSizer
592
593
594    def MakeConfigBox(self):
595        self.configSizers = {}
596       
597        # configuration list
598        configLabel = wx.StaticText(self, wx.ID_ANY, "Configurations:")
599        makeBoldFont(configLabel)
600        self.configCombo = wx.ComboBox(self, wx.ID_ANY, choices=[])
601        self.configCombo.SetMinSize((180, -1))
602        self.configCombo.Bind(wx.EVT_COMBOBOX, self.OnConfigCombo)
603        self.configCombo.SetToolTipString(HELP_AL_CFG)
604        newBtn = wx.Button(self, wx.ID_ANY, " Add ", style=wx.BU_EXACTFIT)
605        newBtn.Bind(wx.EVT_BUTTON, self.OnAddConfig)
606        delBtn = wx.Button(self, wx.ID_ANY, " Delete ", style=wx.BU_EXACTFIT)
607        delBtn.Bind(wx.EVT_BUTTON, self.OnDelConfig)
608        copyBtn = wx.Button(self, wx.ID_ANY, " Make Copy ", style=wx.BU_EXACTFIT)
609        copyBtn.Bind(wx.EVT_BUTTON, self.OnCopyConfig)
610        copyBtn.SetToolTipString(HELP_AL_CFG_COPY)
611
612        self.comboSizer = wx.BoxSizer(wx.HORIZONTAL)
613        self.comboSizer.Add(configLabel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
614        self.comboSizer.Add(self.configCombo, 0, wx.ALIGN_CENTER | wx.ALL, 5)
615        self.comboSizer.Add(newBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5)
616        self.comboSizer.Add(delBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5)
617        self.comboSizer.Add(copyBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5)
618
619        # add all to the main config sizer
620        vSizer = wx.BoxSizer(wx.VERTICAL)
621        vSizer.Add(self.comboSizer, 1, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.ALL, 9)
622        l = wx.StaticLine(self, wx.ID_ANY);  l.Disable()
623        vSizer.Add(l, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
624
625        # static app
626        self.staticCheck = wx.CheckBox(self, wx.ID_ANY, "Static application")#, style=wx.ALIGN_RIGHT)
627        self.staticCheck.SetValue(True)
628        self.staticCheck.Bind(wx.EVT_KILL_FOCUS, self.OnStaticFocus)
629        self.staticCheck.SetToolTipString(HELP_AL_CFG_STATIC)
630        vSizer.Add(self.staticCheck, 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 10)
631
632        # execution
633        execFoldBar = FoldBar(self, 101, "Execution", self.OnFoldBtn)
634        self.configSizers[101] = self.MakeExecutionSection()
635        vSizer.Add(execFoldBar, 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 5)
636        vSizer.Add(self.configSizers[101], 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM | wx.RIGHT, 15)
637        vSizer.Show(self.configSizers[101], False, True)
638       
639        # size and pos
640        sizePosFoldBar = FoldBar(self, 100, "Size and position", self.OnFoldBtn)
641        self.configSizers[100] = self.MakeSizePosSection()
642        vSizer.Add(sizePosFoldBar, 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 5)
643        vSizer.Add(self.configSizers[100], 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM | wx.RIGHT, 15)
644        vSizer.Show(self.configSizers[100], False, True)
645       
646        # parallel application stuff
647        parallelFoldBar = FoldBar(self, 102, "Parallel Applications", self.OnFoldBtn)
648        self.configSizers[102] = self.MakeParallelSection()
649        vSizer.Add(parallelFoldBar, 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 5)
650        vSizer.Add(self.configSizers[102], 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM | wx.RIGHT, 15)
651        vSizer.Show(self.configSizers[102], False, True)
652       
653        # advanced stuff...
654        advFoldBar = FoldBar(self, 103, "Advanced", self.OnFoldBtn)
655        self.configSizers[103] = self.MakeAdvancedSection()
656        vSizer.Add(advFoldBar, 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 5)
657        vSizer.Add(self.configSizers[103], 0, wx.EXPAND | wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM | wx.RIGHT, 15)
658        vSizer.Show(self.configSizers[103], False, True)
659       
660        return vSizer
661
662
663    def MakeParallelSection(self):
664        # parallel checkbox
665        self.parallelCheck = wx.CheckBox(self, wx.ID_ANY, "This is a parallel application", style=wx.ALIGN_RIGHT)
666        self.parallelCheck.Bind(wx.EVT_CHECKBOX, self.OnParallelCheck)
667       
668        # number of nodes
669        nodeNumLabel = wx.StaticText(self, wx.ID_ANY, "Number of nodes:")
670        self.nodeNumSpin = wx.SpinCtrl(self, wx.ID_ANY, min=1, initial=1)
671        self.nodeNumSpin.SetToolTipString(HELP_AL_CFG_NUM_NODES)
672        self.nodeNumSpin.Disable()
673        self.nodeNumSpin.Bind(wx.EVT_KILL_FOCUS, self.OnNodeNumFocus)
674
675        # master ip
676        masterIPLabel = wx.StaticText(self, wx.ID_ANY, "Master node:")
677        self.masterIPText = wx.TextCtrl(self, wx.ID_ANY)
678        self.masterIPText.SetToolTipString(HELP_AL_CFG_MASTER)
679        self.masterIPText.SetMinSize((120, -1))
680        self.masterIPText.Disable()
681        self.masterIPText.Bind(wx.EVT_KILL_FOCUS, self.OnMasterIPFocus)
682
683        # sizer stuff
684        pSizer = wx.FlexGridSizer(2,2,5,5)
685        pSizer.Add(nodeNumLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, 5)
686        pSizer.Add(self.nodeNumSpin, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT, 5)
687        pSizer.Add(masterIPLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, 5)
688        pSizer.Add(self.masterIPText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT, 5)
689       
690        sizer = wx.BoxSizer(wx.VERTICAL)
691        sizer.Add(self.parallelCheck, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
692        sizer.Add(pSizer, 0, wx.ALIGN_LEFT | wx.ALL, 5)
693
694        return sizer
695   
696
697    def MakeExecutionSection(self):
698        appLabel = wx.StaticText(self, wx.ID_ANY, "Run application")
699        appLabel.SetMinSize((100, -1))
700        self.appText = wx.TextCtrl(self, wx.ID_ANY)
701        self.appText.SetToolTipString(HELP_AL_CFG_APP)
702        self.appText.Bind(wx.EVT_KILL_FOCUS, self.OnAppTextFocus)
703
704        dirLabel = wx.StaticText(self, wx.ID_ANY, "from directory")
705        dirLabel.SetMinSize((100, -1))
706        self.dirText = wx.TextCtrl(self, wx.ID_ANY)
707        self.dirText.SetToolTipString(HELP_AL_CFG_DIR)
708        self.dirText.Bind(wx.EVT_KILL_FOCUS, self.OnDirTextFocus)
709           
710        machineLabel = wx.StaticText(self, wx.ID_ANY, "on host")
711        self.machineText = wx.TextCtrl(self, wx.ID_ANY)
712        self.machineText.SetToolTipString(HELP_AL_CFG_MACHINE)
713        self.machineText.SetMinSize((140, -1))
714        self.machineText.Bind(wx.EVT_KILL_FOCUS, self.OnMachineTextFocus)
715       
716        sizer = wx.FlexGridSizer(3,2, 3,10)
717        sizer.Add(appLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
718        sizer.Add(self.appText, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
719        sizer.Add(dirLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
720        sizer.Add(self.dirText, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
721        sizer.Add(machineLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
722        sizer.Add(self.machineText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
723        sizer.AddGrowableCol(1)
724       
725        return sizer
726
727
728    def MakeSizePosSection(self):
729        # sage window size
730        szLabel = wx.StaticText(self, wx.ID_ANY, "Initial SAGE window size")
731        szLabel.SetMinSize((180, -1))
732        wLabel = wx.StaticText(self, wx.ID_ANY, "W")
733        self.wText = wx.TextCtrl(self, wx.ID_ANY, "1000")
734        self.wText.Bind(wx.EVT_KILL_FOCUS, self.OnSizeTextFocus)
735        self.wText.SetToolTipString(HELP_AL_CFG_SIZE)
736        hLabel = wx.StaticText(self, wx.ID_ANY, "H")
737        self.hText = wx.TextCtrl(self, wx.ID_ANY, "1000")
738        self.hText.SetToolTipString(HELP_AL_CFG_SIZE)
739        self.hText.Bind(wx.EVT_KILL_FOCUS, self.OnSizeTextFocus)
740       
741        # sage window position
742        posLabel = wx.StaticText(self, wx.ID_ANY, "Initial SAGE window position")
743        posLabel.SetMinSize((180, -1))
744        xLabel = wx.StaticText(self, wx.ID_ANY, "X")
745        self.xText = wx.TextCtrl(self, wx.ID_ANY, "100")
746        self.xText.Bind(wx.EVT_KILL_FOCUS, self.OnPosTextFocus)
747        yLabel = wx.StaticText(self, wx.ID_ANY, "Y")
748        self.yText = wx.TextCtrl(self, wx.ID_ANY, "100")
749        self.yText.Bind(wx.EVT_KILL_FOCUS, self.OnPosTextFocus)
750
751        # add to sizer
752        sizer = wx.FlexGridSizer(2,5, 5,0)
753        sizer.Add(szLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
754        sizer.Add(wLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
755        sizer.Add(self.wText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
756        sizer.Add(hLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
757        sizer.Add(self.hText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
758
759        sizer.Add(posLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
760        sizer.Add(xLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
761        sizer.Add(self.xText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
762        sizer.Add(yLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
763        sizer.Add(self.yText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, border=5)
764       
765        return sizer
766
767
768    def MakeAdvancedSection(self):
769        pbLabel = wx.StaticText(self, wx.ID_ANY, "Pixel block size")
770        pbLabel.SetMinSize((130, -1))
771        self.pbSpin = wx.SpinCtrl(self, wx.ID_ANY, min=1, max=512, initial=64)
772        self.pbSpin.Bind(wx.EVT_KILL_FOCUS, self.OnPBSpinFocus)
773        self.pbSpin.SetToolTipString(HELP_AL_CFG_BP)
774
775        #protoLabel = wx.StaticText(self, wx.ID_ANY, "Streaming protocol")
776        #protoLabel.SetMinSize((130, -1))
777        #self.protoCombo = wx.ComboBox(self, wx.ID_ANY, choices=["TCP"])
778
779        bridgeIPLabel = wx.StaticText(self, wx.ID_ANY, "SAGE Bridge host")
780        bridgeIPLabel.SetMinSize((130, -1))
781        self.bridgeIPText = wx.TextCtrl(self, wx.ID_ANY)
782        self.bridgeIPText.SetMinSize((150, -1))
783        self.bridgeIPText.Bind(wx.EVT_KILL_FOCUS, self.OnBridgeIPTextFocus)
784        self.bridgeIPText.SetToolTipString(HELP_AL_CFG_BHOST)
785
786        bridgePortLabel = wx.StaticText(self, wx.ID_ANY, "SAGE Bridge port")
787        bridgePortLabel.SetMinSize((130, -1))
788        self.bridgePortText = wx.TextCtrl(self, wx.ID_ANY)
789        self.bridgePortText.SetMinSize((150, -1))
790        self.bridgePortText.Bind(wx.EVT_KILL_FOCUS, self.OnBridgePortTextFocus)
791        self.bridgePortText.SetToolTipString(HELP_AL_CFG_BPORT)
792       
793        sizer = wx.FlexGridSizer(3,2, 3,10)
794        #sizer.Add(protoLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
795        #sizer.Add(self.protoCombo, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
796        sizer.Add(pbLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
797        sizer.Add(self.pbSpin, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
798        sizer.Add(bridgeIPLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
799        sizer.Add(self.bridgeIPText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
800        sizer.Add(bridgePortLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
801        sizer.Add(self.bridgePortText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
802        sizer.AddGrowableCol(1)
803       
804        return sizer
805
806
807    def OnFoldBtn(self, sectionId, collapse):
808        self.confSizer.Show(self.configSizers[sectionId], not collapse, True)
809        self.Fit()
810       
811
812    def OnParallelCheck(self, evt):
813        self.nodeNumSpin.Enable( self.parallelCheck.IsChecked() )
814        self.masterIPText.Enable( self.parallelCheck.IsChecked() )
815        if not self.parallelCheck.IsChecked():
816            self.nodeNumSpin.SetValue(1)
817            self.currentConfig.setNodeNum(1)
818
819
820    def OnAddConfig(self, evt):
821        """ add a new configuration for the currently selected app """
822
823        # an app must be selected first
824        appName = self.appsList.GetStringSelection()
825        if appName == "":
826            wx.MessageBox("Please select an application first", "Error", parent=self)
827            return
828        app = self.configs.getApp(appName)
829
830        # get the config name, do nothing if cancel pressed
831        configName = wx.GetTextFromUser("Enter new configuration name", "Config Name", parent=self)
832        if configName == "":  return
833        if configName in app.getAllConfigNames():
834            wx.MessageBox("Configuration with that name already exists", "Error", parent=self)
835            return
836
837        # add the new config and clear the config fields
838        conf = OneConfig(configName, appName)
839        app.addConfig(conf)
840        self.ClearConfig()
841        self.currentConfig = conf
842        self.configCombo.Append(configName)
843        self.configCombo.SetStringSelection(configName)
844
845
846    def OnDelConfig(self, evt):
847        # an app must be selected first
848        appName = self.appsList.GetStringSelection()
849        if appName == "":
850            wx.MessageBox("Please select an application first", "Error", parent=self)
851            return
852        app = self.configs.getApp(appName)
853
854        # get the config name, do nothing if cancel pressed
855        configName = self.configCombo.GetValue()
856        if configName == "":  return
857
858        # there must be at least one config per app
859        if len(app.getAllConfigNames()) < 2:
860            wx.MessageBox("Cannot delete the last configuration. There must be at least one configuration per application.", "Cannot Delete", parent=self)
861            return
862
863        # add the new config and clear the config fields
864        app.delConfig(configName)
865        self.configCombo.Delete(self.configCombo.FindString(configName))
866        self.configCombo.SetValue("")
867        self.ClearConfig()
868        self.currentConfig = None
869
870
871    def OnCopyConfig(self, evt):
872        # an app must be selected first
873        appName = self.appsList.GetStringSelection()
874        if appName == "":
875            wx.MessageBox("Please select an application first", "Error", parent=self)
876            return
877        app = self.configs.getApp(appName)
878
879        # get the config name, do nothing if cancel pressed
880        configName = self.configCombo.GetValue()
881        if configName == "":  return
882
883        # get the new config name, do nothing if cancel pressed
884        newConfigName = wx.GetTextFromUser("Enter new configuration name", "Config Name", parent=self)
885        if newConfigName == "":  return
886        if newConfigName in app.getAllConfigNames():
887            wx.MessageBox("Configuration with that name already exists", "Error", parent=self)
888            return
889
890        # copy the old config, change its name and add it to the app
891        conf = copy.deepcopy( app.getConfig(configName) )
892        conf.setName(newConfigName)
893        app.addConfig(conf)
894
895        # select the new config
896        self.configCombo.Append(newConfigName)
897        self.configCombo.SetStringSelection(newConfigName)
898        self.__SetConfig(conf)
899       
900   
901    def OnAppList(self, evt):
902        """ refill configCombo, select the first one and fill the fields for it"""
903        a = evt.GetString()
904        if not a: return
905        self.configCombo.Clear()
906        for c in self.configs.getApp(a).getAllConfigNames():
907            self.configCombo.Append(c)
908        self.configCombo.SetSelection(0)
909        conf = self.configs.getConfig(a, self.configCombo.GetString(0))
910        self.__SetConfig(conf)
911
912
913    def OnConfigCombo(self, evt):
914        configName = self.configCombo.GetValue()
915        appName = self.appsList.GetStringSelection()
916        c = self.configs.getConfig(appName, configName)
917        self.__SetConfig(c)
918
919
920    def __SetConfig(self, conf):
921        """ fill all the widgets with correct values for this configuration """
922        self.currentConfig = conf
923       
924        self.staticCheck.SetValue( conf.getStaticApp() )
925
926        # execution
927        self.dirText.SetValue( conf.getBinDir() )
928        self.machineText.SetValue( conf.getTargetMachine() )
929        self.appText.SetValue( conf.getCommand() )
930
931        # size and pos
932        self.wText.SetValue( str(conf.getSize()[0]) )
933        self.hText.SetValue( str(conf.getSize()[1]) )
934        self.yText.SetValue( str(conf.getPosition()[1]) )
935        self.xText.SetValue( str(conf.getPosition()[0]) )
936
937        # parallel section
938        if conf.getNodeNum() == 1:
939            self.parallelCheck.SetValue(False)
940            self.nodeNumSpin.SetValue(1)
941            self.nodeNumSpin.Enable(False)
942            self.masterIPText.Enable(False)
943        else:
944            self.parallelCheck.SetValue(True)
945            self.nodeNumSpin.SetValue( conf.getNodeNum() )
946            if conf.getMasterIP():  # could be none
947                self.masterIPText.SetValue( conf.getMasterIP() )       
948            self.nodeNumSpin.Enable(True)
949            self.masterIPText.Enable(True)
950
951
952        # advanced
953        self.pbSpin.SetValue( conf.getBlockSize()[0] )
954        self.bridgeIPText.SetValue( conf.getBridgeIP() )
955        self.bridgePortText.SetValue( conf.getBridgePort() )
956
957
958    def OnAppAdd(self, evt):
959        appName = wx.GetTextFromUser("Enter new application name.\nHas to be the same as the sage app configuration file the application\nis trying to load (e.g. \"atlantis\" is trying to load \"atlantis.conf\")", "App Name", parent=self)
960        if appName == "":  return
961        if appName in self.configs.getAppList():
962            wx.MessageBox("Application already exists", "Error", parent=self)
963            return
964       
965        configName = wx.GetTextFromUser("Enter first configuration name", "Config Name", parent=self)
966        if configName == "":  return
967        self.ClearConfig(clearConfigNames=True)
968
969        # add to the list of apps
970        self.appsList.Append(appName)
971        self.appsList.SetStringSelection(appName)
972
973        # add to the list of configs
974        self.configCombo.Append(configName)
975        self.configCombo.SetStringSelection(configName)
976
977        # change the actual datastructure
978        self.configs.addNewApp(appName)
979        conf = OneConfig(configName, appName)
980        self.currentConfig = conf
981        self.configs.getApp(appName).addConfig(conf)
982
983
984    def OnAppDel(self, evt):
985        appName = self.appsList.GetStringSelection()
986        if appName == "":  return  #nothing selected
987        self.configs.delApp(appName)
988        self.ClearConfig(clearConfigNames=True)
989        self.currentConfig = None
990        self.appsList.Delete( self.appsList.FindString(appName) )
991       
992
993    def ClearConfig(self, clearConfigNames=False):
994        """ clears all the config fields to their default values """
995        self.pbSpin.SetValue(64)
996        self.bridgeIPText.SetValue("")
997        self.bridgePortText.SetValue("")
998        self.masterIPText.SetValue("")
999        self.nodeNumSpin.SetValue(1)
1000        self.parallelCheck.SetValue(False)
1001        self.nodeNumSpin.Enable(False)
1002        self.masterIPText.Enable(False)
1003        self.xText.SetValue("100")
1004        self.yText.SetValue("100")
1005        self.hText.SetValue("1000")
1006        self.wText.SetValue("1000")
1007        self.staticCheck.SetValue(True)
1008        self.dirText.SetValue("$SAGE_DIRECTORY/bin")
1009        self.machineText.SetValue("127.0.0.1")
1010        self.appText.SetValue("")
1011        if clearConfigNames:
1012            self.configCombo.Clear()
1013            self.configCombo.SetValue("")
1014
1015
1016    def OnPublicCheck(self, evt):
1017        if self.reportCheck.IsChecked():
1018            self.hostText.Enable()
1019            #self.portText.Enable()
1020        else:
1021            self.hostText.Disable()
1022            #self.portText.Disable()
1023
1024
1025    ####  KILL FOCUS EVENT HANDLERS (for saving config data right away)
1026
1027    def OnStaticFocus(self, evt):
1028        if self.currentConfig:
1029            self.currentConfig.setStaticApp(self.staticCheck.IsChecked())
1030
1031    def OnNodeNumFocus(self, evt):
1032        if self.currentConfig:
1033            self.currentConfig.setNodeNum(self.nodeNumSpin.GetValue())
1034
1035    def OnMasterIPFocus(self, evt):
1036        if self.currentConfig:
1037            self.currentConfig.setMasterIP(self.masterIPText.GetValue())
1038
1039    def OnAppTextFocus(self, evt):
1040        if self.currentConfig:
1041            self.currentConfig.setCommand(self.appText.GetValue())
1042
1043    def OnDirTextFocus(self, evt):
1044        if self.currentConfig:
1045            self.currentConfig.setBinDir(self.dirText.GetValue())
1046
1047    def OnMachineTextFocus(self, evt):
1048        if self.currentConfig:
1049            self.currentConfig.setTargetMachine(self.machineText.GetValue())
1050
1051    def OnSizeTextFocus(self, evt):
1052        if self.currentConfig:
1053            self.currentConfig.setSize((int(self.wText.GetValue()),
1054                                       int(self.hText.GetValue())))
1055
1056    def OnPosTextFocus(self, evt):
1057        if self.currentConfig:
1058            self.currentConfig.setPosition((int(self.xText.GetValue()),
1059                                           int(self.yText.GetValue())))
1060
1061    def OnPBSpinFocus(self, evt):
1062        if self.currentConfig:
1063            sz = self.pbSpin.GetValue()
1064            self.currentConfig.setBlockSize((sz,sz))
1065
1066    def OnBridgePortTextFocus(self, evt):
1067        if self.currentConfig:
1068            self.currentConfig.setBridgePort(self.bridgePortText.GetValue())
1069           
1070    def OnBridgeIPTextFocus(self, evt):
1071        if self.currentConfig:
1072            self.currentConfig.setBridgeIP(self.bridgeIPText.GetValue())
1073
1074   
1075    def OnClose(self, evt):
1076        """ save stuff here """
1077        self.settings.doReport = self.reportCheck.IsChecked()
1078        self.settings.server = self.hostText.GetValue()
1079        #self.settings.port = self.portText.GetValue()
1080        self.settings.inBG = self.run.GetValue()
1081        saveSettings()
1082        self.configs.writeConfig()
1083        evt.Skip()
1084
1085
1086
1087
1088
1089
1090class FileServerFrame(ComponentFrame):
1091    def __init__(self, parent):
1092        ComponentFrame.__init__(self, parent, FILE_SERVER)
1093
1094        # for reading the config file
1095        self.types ={}
1096        self.viewers = {}
1097        self.filesDir = ""
1098        self.ReadConfigFile()
1099       
1100        self.MakeWidgets()
1101        self.Show()
1102
1103
1104    def MakeWidgets(self):
1105        self.run = wx.CheckBox(self, wx.ID_ANY, "Run in background")
1106        self.run.SetToolTipString(HELP_INBG)
1107        self.run.SetValue(self.settings.inBG)
1108
1109        self.mainSizer.Add(self.MakeConfigBox(), 1, wx.EXPAND | wx.ALL, 10)
1110        self.mainSizer.Add(self.run, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.ALL, 10)
1111        ComponentFrame.MakeWidgets(self)
1112
1113
1114
1115    def MakeConfigBox(self):
1116        # the box
1117        box = wx.StaticBox(self, wx.ID_ANY, "File Server Configuration:")
1118        box.SetForegroundColour(wx.BLUE)
1119        makeBiggerBoldFont( box )
1120        boxSizer = wx.StaticBoxSizer( box, wx.VERTICAL )
1121
1122        # widgets
1123        self.fbb = fb.DirBrowseButton(self, wx.ID_ANY, labelText='Library Root:',
1124                                      dialogTitle="Choose Library Root",
1125                                      startDirectory=opj(SAGE_DIR,"bin"),
1126                                      toolTip=HELP_FS_ROOT)
1127        self.fbb.SetValue(self.filesDir)
1128        self.fbb.label.SetMinSize((80, -1))
1129        self.fbb.SetMinSize((600,-1))
1130
1131        # types label and list
1132        typesLabel = wx.StaticText(self, wx.ID_ANY, "File types:")
1133        self.typesList = wx.ListBox(self, wx.ID_ANY, choices=self.types.keys())
1134        self.typesList.Bind(wx.EVT_LISTBOX, self.OnList)
1135        self.typesList.SetToolTipString(HELP_FS_TYPES)
1136       
1137        addBtn = wx.Button(self, wx.ID_ANY, "Add", style=wx.BU_EXACTFIT)
1138        addBtn.Bind(wx.EVT_BUTTON, self.OnAdd)
1139        delBtn = wx.Button(self, wx.ID_ANY, "Delete", style=wx.BU_EXACTFIT)
1140        delBtn.Bind(wx.EVT_BUTTON, self.OnDel)
1141
1142        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1143        btnSizer.Add(addBtn, 1, wx.ALIGN_CENTER | wx.ALL, 4)
1144        btnSizer.Add(delBtn, 1, wx.ALIGN_CENTER | wx.RIGHT | wx.TOP | wx.BOTTOM, 4)
1145                     
1146        typesSizer = wx.BoxSizer(wx.VERTICAL)
1147        typesSizer.Add(typesLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1148        typesSizer.Add(self.typesList, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1149        typesSizer.Add(btnSizer, 0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 1)
1150       
1151
1152        # details (extensions and application)
1153        extLabel = wx.StaticText(self, wx.ID_ANY, "File extensions:")
1154        self.extText = wx.TextCtrl(self, wx.ID_ANY)
1155        self.extText.Bind(wx.EVT_KILL_FOCUS, self.OnExtText)
1156        self.extText.SetToolTipString(HELP_FS_EXT)
1157       
1158        appLabel = wx.StaticText(self, wx.ID_ANY, "Run with application (and any optional arguments):")
1159        self.appText = wx.TextCtrl(self, wx.ID_ANY)
1160        self.appText.Bind(wx.EVT_KILL_FOCUS, self.OnAppText)
1161        self.appText.SetToolTipString(HELP_FS_APP)
1162
1163        detailSizer = wx.BoxSizer(wx.VERTICAL)
1164        detailSizer.Add(extLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1165        detailSizer.Add(self.extText, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
1166        detailSizer.Add(appLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1167        detailSizer.Add(self.appText, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 5)
1168
1169        # add the above two to the horizontal sizer
1170        horSizer = wx.BoxSizer(wx.HORIZONTAL)
1171        horSizer.Add(typesSizer, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.ALL, 10)
1172        horSizer.Add(detailSizer, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.ALL, 10)
1173       
1174        # add widgets to the boxSizer
1175        boxSizer.Add(self.fbb, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 10)
1176        boxSizer.Add(horSizer, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1177
1178        return boxSizer
1179
1180
1181
1182    def ReadConfigFile(self):
1183        # reinitialize everything
1184        self.types = {}
1185        self.viewers = {}
1186
1187        # read the config file
1188        try:
1189            f = open(self.settings.getConfigFilename(), "r")
1190        except:
1191            m = """
1192            File Server configuration file doesn't exist yet which usually means
1193            that it hasn't been compiled yet. Please compile it first by running "make install" from
1194            %s.""" % opj(SAGE_DIR,"app","FileViewer")
1195            wx.MessageBox(m, "No FileServer")
1196            self.Destroy()
1197            return
1198               
1199        for line in f:
1200            line = line.strip()
1201
1202            # read root library directory
1203            if line.startswith("FILES_DIR"):
1204                self.filesDir = line.split("=")[1].strip()
1205
1206            # read types
1207            elif line.startswith("type:"):
1208                line = line.split(":",1)[1]
1209                (type, extensions) = line.split("=")
1210                self.types[type.strip()] = extensions.strip().split(" ")
1211
1212            # read apps for types
1213            elif line.startswith("app:"):
1214                line = line.split(":", 1)[1].strip()
1215                (type, app) = line.split("=")
1216                tpl = app.strip().split(" ", 1)
1217                if len(tpl) == 1:  params = ""
1218                else:  params = tpl[1].strip()
1219                app = tpl[0].strip()
1220                self.viewers[type.strip()] = (app, params)
1221        f.close()
1222       
1223
1224    def WriteConfigFile(self):
1225        try:
1226            self.filesDir = self.fbb.GetValue()
1227           
1228            f = open(getUserPath("fileServer", "fileServer.conf"), "w")
1229            f.write("FILES_DIR = "+ self.filesDir+"\n")
1230
1231            f.write("\n#"+"----"*12+"\n\n")  # separator
1232           
1233            # write types out and their extensions
1234            for t, ext in self.types.iteritems():
1235                f.write("type:" + t + " = " + " ".join(ext) + "\n")
1236
1237            f.write("\n#"+"----"*12+"\n\n")  # separator
1238           
1239            # write apps out and their parameters
1240            for t, appTuple in self.viewers.iteritems():
1241                f.write("app:"+ t + " = " + " ".join(appTuple) + "\n")
1242           
1243            f.close()
1244        except:
1245            print " ***** Error while writing file server config file"
1246            print "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
1247            return False
1248
1249
1250    def OnAdd(self, evt):
1251        newType = wx.GetTextFromUser("Enter new file type to the file library:", "New Type", parent=self)
1252        if newType != "":
1253            if newType in self.types:
1254                wx.MessageBox("File type already exists", "Error", parent=self)
1255                return
1256            self.extText.Clear()
1257            self.appText.Clear()
1258            self.types[newType] = []
1259            self.viewers[newType] = ()
1260            self.typesList.AppendAndEnsureVisible(newType)
1261            self.typesList.SetStringSelection(newType)
1262
1263
1264    def OnDel(self, evt):
1265        t = self.typesList.GetStringSelection()
1266        if t != "":
1267            self.extText.Clear()
1268            self.appText.Clear()
1269            del self.types[t]
1270            del self.viewers[t]
1271            self.typesList.Delete( self.typesList.FindString(t) )
1272           
1273
1274    def OnList(self, evt):
1275        t = evt.GetString()
1276        self.extText.SetValue(" ".join(self.types[t]))
1277        self.appText.SetValue(" ".join(self.viewers[t]))
1278
1279
1280    def OnAppText(self, evt):
1281        """ gets called when the text ctrl loses focus so we save the data """
1282        t = self.typesList.GetStringSelection()
1283        tpl = self.appText.GetValue().strip().split(" ", 1)
1284        if len(tpl) == 1:  params = ""
1285        else:  params = tpl[1].strip()
1286        app = tpl[0].strip()
1287        self.viewers[t] = (app, params)
1288       
1289
1290    def OnExtText(self, evt):
1291        """ gets called when the text ctrl loses focus so we save the data """
1292        t = self.typesList.GetStringSelection()
1293        self.types[t] = self.extText.GetValue().strip().split(' ')
1294
1295
1296    def OnClose(self, evt):
1297        """ save stuff here """
1298        self.settings.inBG = self.run.GetValue()
1299        self.WriteConfigFile()
1300        saveSettings()
1301        evt.Skip()
1302
1303
1304
1305
1306       
1307
1308class SageProxyFrame(ComponentFrame):
1309    def __init__(self, parent):
1310        ComponentFrame.__init__(self, parent, SAGE_PROXY)
1311        self.MakeWidgets()
1312        self.Show()
1313
1314
1315    def MakeWidgets(self):
1316        # add the boxes to the main sizer
1317        self.mainSizer.Add(self.MakeConnectionBox(), 0, wx.EXPAND | wx.ALL, 10)
1318        self.mainSizer.Add(self.MakePasswordBox(), 0, wx.EXPAND | wx.ALL, 10)
1319        ComponentFrame.MakeWidgets(self)
1320
1321
1322    def MakeConnectionBox(self):
1323        # sage connection box
1324        box = wx.StaticBox(self, wx.ID_ANY, "Run Proxy for SAGE on:")
1325        box.SetForegroundColour(wx.BLUE)
1326        makeBiggerBoldFont( box )
1327        boxSizer = wx.StaticBoxSizer( box, wx.HORIZONTAL )
1328
1329        # widgets
1330        hostLabel = wx.StaticText(self, wx.ID_ANY, "Hostname / IP:")
1331        self.hostText = wx.TextCtrl(self, wx.ID_ANY, self.settings.host)
1332        self.hostText.SetToolTipString(HELP_SP_HOST)
1333        self.hostText.SetMinSize((150, -1))
1334
1335        portLabel = wx.StaticText(self, wx.ID_ANY, "Port:")
1336        self.portText = wx.TextCtrl(self, wx.ID_ANY, str(self.settings.port))
1337        self.portText.SetToolTipString(HELP_SP_PORT)
1338       
1339        # add widgets to the boxSizer
1340        boxSizer.Add(hostLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1341        boxSizer.Add(self.hostText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
1342        boxSizer.Add((10,10))
1343        boxSizer.Add(portLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1344        boxSizer.Add(self.portText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
1345
1346        return boxSizer
1347
1348
1349    def MakePasswordBox(self):
1350        # password box
1351        box = wx.StaticBox(self, wx.ID_ANY, "Password for SAGE Web UI access:")
1352        box.SetForegroundColour(wx.BLUE)
1353        makeBiggerBoldFont( box )
1354        boxSizer = wx.StaticBoxSizer( box, wx.HORIZONTAL )
1355
1356        # widgets
1357        passLabel = wx.StaticText(self, wx.ID_ANY, "Password:")
1358        self.passText = wx.TextCtrl(self, wx.ID_ANY, self.settings.password)
1359        self.passText.SetToolTipString(HELP_SP_PASS)
1360        self.passText.SetMinSize((150, -1))
1361
1362        # add widgets to the boxSizer
1363        boxSizer.Add(passLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1364        boxSizer.Add(self.passText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
1365
1366        return boxSizer
1367
1368
1369    def OnClose(self, evt):
1370        """ save stuff here """
1371        self.settings.host = self.hostText.GetValue()
1372        self.settings.port = int(self.portText.GetValue())
1373        self.settings.password = self.passText.GetValue()
1374        saveSettings()
1375        evt.Skip()
1376       
1377
1378
1379
1380class SageUIFrame(ComponentFrame):
1381    def __init__(self, parent):
1382        ComponentFrame.__init__(self, parent, SAGE_UI)
1383        self.MakeWidgets()
1384        self.Show()
1385
1386       
1387    def MakeWidgets(self):
1388        # make the box first (this is REQUIRED since the box is a
1389        # sibling of the widgets inside it and not their parent)
1390       
1391        box = wx.StaticBox(self, wx.ID_ANY, "Connection Manager")
1392        box.SetForegroundColour(wx.BLUE)
1393        makeBiggerBoldFont( box )
1394        boxSizer = wx.StaticBoxSizer( box, wx.HORIZONTAL )
1395
1396        # widgets
1397        hostLabel = wx.StaticText(self, wx.ID_ANY, "Hostname / IP:")
1398        self.hostText = wx.TextCtrl(self, wx.ID_ANY, self.settings.host)
1399        self.hostText.SetToolTipString(HELP_SU_HOST)
1400        self.hostText.SetMinSize((150, -1))
1401
1402        portLabel = wx.StaticText(self, wx.ID_ANY, "Port:")
1403        self.portText = wx.TextCtrl(self, wx.ID_ANY, str(self.settings.port))
1404        self.portText.SetToolTipString(HELP_SU_HOST)
1405
1406        autologinLabel = wx.StaticText(self, wx.ID_ANY, "Autologin to (enter sage session name):")
1407        self.autologinText = wx.TextCtrl(self, wx.ID_ANY, self.settings.autologinMachine)
1408        self.autologinText.SetToolTipString(HELP_SU_AUTOLOGIN)
1409        self.autologinText.SetMinSize((150, -1))
1410       
1411        # add widgets to the boxSizer
1412        boxSizer.Add(hostLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1413        boxSizer.Add(self.hostText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
1414        boxSizer.Add((10,10))
1415        boxSizer.Add(portLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1416        boxSizer.Add(self.portText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.RIGHT | wx.BOTTOM | wx.TOP, 5)
1417
1418        # autologin sizer
1419        loginSizer = wx.BoxSizer(wx.HORIZONTAL)
1420        loginSizer.Add(autologinLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1421        loginSizer.Add(self.autologinText, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5)
1422       
1423        # add the box to the main sizer
1424        self.mainSizer.Add(boxSizer, 0, wx.EXPAND | wx.ALL, 10)
1425        self.mainSizer.Add(loginSizer, 0, wx.EXPAND | wx.ALL, 10)
1426        ComponentFrame.MakeWidgets(self)   # call this last
1427
1428
1429    def OnClose(self, evt):
1430        """ save stuff here """
1431        self.settings.host = self.hostText.GetValue()
1432        self.settings.port = int(self.portText.GetValue())
1433        self.settings.autologinMachine = self.autologinText.GetValue()
1434        saveSettings()
1435        evt.Skip()
1436
1437
1438
1439class SageFrame(ComponentFrame):
1440    def __init__(self, parent):
1441        ComponentFrame.__init__(self, parent, SAGE)
1442        self.MakeWidgets()
1443        self.Show()
1444
1445       
1446    def MakeWidgets(self):
1447        box1 = wx.StaticBox(self, wx.ID_ANY, "ON SAGE STARTUP")
1448        box1.SetForegroundColour(wx.BLUE)
1449        makeBiggerBoldFont( box1 )
1450        box1Sizer = wx.StaticBoxSizer( box1, wx.VERTICAL )
1451
1452        box2 = wx.StaticBox(self, wx.ID_ANY, "ON SAGE SHUTDOWN")
1453        box2.SetForegroundColour(wx.BLUE)
1454        makeBiggerBoldFont( box2 )
1455        box2Sizer = wx.StaticBoxSizer( box2, wx.VERTICAL )
1456
1457        # start stuff
1458        startLabel = wx.StaticText(self, wx.ID_ANY, "Specify any additional commands to be executed before SAGE starts")
1459        self.start = wx.TextCtrl(self, wx.ID_ANY, self.settings.onStart, style=wx.TE_MULTILINE)
1460        self.start.SetToolTipString(HELP_S_START)
1461        box1Sizer.AddSpacer((10,10))
1462        box1Sizer.Add(startLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1463        box1Sizer.Add(self.start, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1464
1465        # stop stuff
1466        stopLabel = wx.StaticText(self, wx.ID_ANY, "Specify any additional commands to be executed after SAGE is stopped")
1467        self.stop = wx.TextCtrl(self, wx.ID_ANY, self.settings.onStop, style=wx.TE_MULTILINE)
1468        self.stop.SetToolTipString(HELP_S_STOP)
1469        procLabel = wx.StaticText(self, wx.ID_ANY, "List all processes to be killed on the nodes and the master during SAGE shutdown")
1470        self.proc = wx.TextCtrl(self, wx.ID_ANY, self.settings.toKill)
1471        self.proc.SetToolTipString(HELP_S_PROC)
1472               
1473        box2Sizer.AddSpacer((10,10))
1474        box2Sizer.Add(stopLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1475        box2Sizer.Add(self.stop, 1, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1476        box2Sizer.AddSpacer((20,20))
1477        box2Sizer.Add(procLabel, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1478        box2Sizer.Add(self.proc, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1479
1480        self.mainSizer.Add(box1Sizer, 1, wx.EXPAND | wx.ALL, 15)
1481        self.mainSizer.Add(box2Sizer, 1, wx.EXPAND | wx.ALL, 15)
1482        ComponentFrame.MakeWidgets(self)   # call this last
1483
1484
1485    def OnClose(self, evt):
1486        """ save stuff here """
1487        self.settings.onStop = self.stop.GetValue().strip()
1488        self.settings.onStart = self.start.GetValue().strip()
1489        self.settings.toKill = self.proc.GetValue().strip()
1490        saveSettings()
1491        evt.Skip()
1492
1493
1494
1495       
1496
1497###
1498### the following are the main frame UI components
1499###
1500       
1501class ComponentSummary(wx.Panel):
1502    """ this is the container for one SAGE component...
1503        it has 3 widgets: name, run checkbox and edit button
1504    """
1505    def __init__(self, parent, componentType):
1506        wx.Panel.__init__(self, parent, wx.ID_ANY, style=wx.SIMPLE_BORDER)
1507        self.color = wx.Colour(252,172,130)
1508        self.componentType = componentType
1509        self.SetMinSize((250,70))
1510        self.SetMaxSize((250,70))
1511        self.settings = components[componentType].settings
1512        self.MakeWidgets()
1513       
1514        # for selecting the component on click
1515        self.Bind(wx.EVT_LEFT_DOWN, self.OnRunCheck)
1516        self.name.Bind(wx.EVT_LEFT_DOWN, self.OnRunCheck)
1517        self.SetBackgroundColour(self.color)
1518       
1519        # where the console output will be printed
1520        # it's a child of the whole ComponentsPanel
1521        # since it's placed inside a sizer there
1522        self.output = wx.TextCtrl(self.GetParent().outputNotebook, wx.ID_ANY, style=wx.TE_MULTILINE | wx.TE_READONLY)
1523        self.output.SetMinSize((800, 300))
1524        self.output.SetMaxLength(MAX_TEXT_LEN)
1525        components[componentType].output = self.output
1526
1527        # the process tied with the component
1528        components[componentType].process = Process(componentType)
1529
1530
1531    def ChangeColor(self, newColor):
1532        self.color = newColor
1533        self.SetBackgroundColour(self.color)
1534        self.run.faceDnClr = self.color
1535        self.Refresh()
1536       
1537
1538    def MakeWidgets(self):   
1539        # make all the widgets
1540        self.MakeName()
1541        self.MakeRun()
1542        self.MakeEdit()
1543        self.MakeStopBtn()
1544       
1545        # add the widgets to the sizers
1546        vSizer = wx.BoxSizer(wx.VERTICAL)
1547        hSizer = wx.BoxSizer(wx.HORIZONTAL)
1548        hSizer2 = wx.BoxSizer(wx.HORIZONTAL)
1549
1550        hSizer.Add(self.run, 0, wx.ALIGN_LEFT | wx.ALL, 0)
1551        hSizer.Add(self.name, 1, wx.ALIGN_LEFT | wx.ALL, 5)
1552
1553        hSizer2.Add(self.stopBtn, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20)
1554        hSizer2.Add(self.editBtn, 0, wx.ALIGN_RIGHT | wx.RIGHT, 3)
1555       
1556        vSizer.Add(hSizer, 1, wx.ALIGN_TOP | wx.ALIGN_LEFT | wx.ALL, 3)
1557        vSizer.Add(hSizer2, 0, wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT | wx.BOTTOM, 1)
1558
1559        self.SetSizer(vSizer)
1560
1561
1562    def MakeStopBtn(self):
1563        self.stopBtn = buttons.GenBitmapButton(self, wx.ID_ANY, getStopBitmap(), style=wx.BORDER_NONE)
1564        self.stopBtn.faceDnClr = self.color
1565        self.stopBtn.SetBestSize((24,24))
1566        self.stopBtn.Bind(wx.EVT_BUTTON, self.OnStopBtn)
1567        self.stopBtn.SetToolTipString(HELP_COMP_STOP)
1568           
1569    def MakeName(self):
1570        self.name = wx.StaticText(self, wx.ID_ANY, self.componentType)
1571        makeBoldFont(self.name)
1572        try: self.name.Wrap(150)  # wrap the text if longer than 150 pixels
1573        except: pass
1574        help = ""
1575        if self.componentType == APP_LAUNCHER:
1576            help = HELP_AL
1577        elif self.componentType == FILE_SERVER:
1578            help = HELP_FS
1579        elif self.componentType == SAGE_UI:
1580            help = HELP_SU
1581        elif self.componentType == SAGE_PROXY:
1582            help = HELP_SP
1583        elif self.componentType == SAGE:
1584            help = HELP_S
1585        self.name.SetToolTipString(help)
1586        self.SetToolTipString(help)
1587
1588
1589    def MakeRun(self):
1590        bmp = getRunBitmap(self.settings.doRun)
1591        self.run = buttons.GenBitmapButton(self, wx.ID_ANY, bmp, style=wx.BORDER_NONE)
1592        self.run.SetBitmapSelected(bmp)
1593        self.run.Bind(wx.EVT_BUTTON, self.OnRunCheck)
1594        self.run.SetToolTipString(HELP_RUN)
1595        self.run.faceDnClr = self.color
1596       
1597               
1598    def MakeEdit(self):
1599        self.editBtn = wx.Button(self, wx.ID_ANY, "Settings")
1600        self.editBtn.Bind(wx.EVT_BUTTON, self.OnEdit)
1601        self.editBtn.SetToolTipString(HELP_EDIT)
1602
1603
1604    def OnEdit(self, evt):
1605        if self.componentType == APP_LAUNCHER:
1606            frame = AppLauncherFrame(self)
1607        elif self.componentType == FILE_SERVER:
1608            frame = FileServerFrame(self)
1609        elif self.componentType == SAGE_PROXY:
1610            frame = SageProxyFrame(self)
1611        elif self.componentType == SAGE_UI:
1612            frame = SageUIFrame(self)
1613        elif self.componentType == SAGE:
1614            frame = SageFrame(self)
1615
1616
1617    def OnRunCheck(self, evt):
1618        self.settings.doRun = not self.settings.doRun # reverse the value
1619        bmp = getRunBitmap(self.settings.doRun)
1620        self.run.SetBitmapLabel(bmp, False)
1621        self.run.SetBitmapSelected(bmp)
1622        self.run.Refresh()
1623
1624
1625    def OnStopBtn(self, evt):
1626        components[self.componentType].process.stop()
1627
1628
1629
1630
1631
1632
1633class ComponentsPanel(scrolled.ScrolledPanel):
1634    """ this holds the summaries for each component on the main frame """
1635
1636    def __init__(self, parent):
1637        scrolled.ScrolledPanel.__init__(self, parent, wx.ID_ANY)
1638        self.MakeWidgets()
1639
1640
1641    def ChangeAllColors(self, newColor):
1642        for c in self.compSummary.itervalues():
1643            c.ChangeColor(newColor)
1644
1645
1646    def MakeWidgets(self):
1647        mainSizer = wx.BoxSizer( wx.HORIZONTAL )
1648        compSizer = wx.BoxSizer( wx.VERTICAL )
1649
1650        # notebook
1651        self.outputNotebook = wx.Notebook(self, wx.ID_ANY, style=wx.NO_BORDER)
1652
1653        # component objects
1654        self.compSummary = {}
1655        for componentType, c in components.iteritems():
1656            self.compSummary[componentType] = ComponentSummary(self, componentType)
1657
1658        # add components to the compSizer
1659        label = wx.StaticText(self, wx.ID_ANY, "Components to Start:")
1660        makeBoldFont(label)
1661        compSizer.Add(label, 0, wx.EXPAND | wx.TOP | wx.LEFT, 10)
1662        for cName in componentNames:
1663            c = self.compSummary[cName]
1664            compSizer.Add(c, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.ALL, 10)
1665            self.outputNotebook.AddPage(c.output, cName)  # add the notebook pages
1666           
1667        # run controls
1668        self.runPanel = RunPanel(self)
1669        compSizer.Add(self.runPanel, 1, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.ALL, 5)
1670
1671        # create the output label and sizer
1672        self.outLabel = wx.StaticText(self, wx.ID_ANY, "Output from:")
1673        makeBoldFont(self.outLabel)
1674        self.outSizer = wx.BoxSizer(wx.VERTICAL)
1675        self.outSizer.Add(self.outLabel, 0, wx.EXPAND | wx.TOP | wx.LEFT, 15)
1676        self.outSizer.Add(self.outputNotebook, 1, wx.EXPAND | wx.TOP | wx.LEFT | wx.BOTTOM, 5)
1677
1678        # add the stuff to the main sizer
1679        mainSizer.Add(compSizer, 0, wx.EXPAND | wx.ALIGN_LEFT | wx.ALL, 5)
1680        mainSizer.Add(self.outSizer, 1, wx.EXPAND | wx.LEFT | wx.BOTTOM, 15)
1681
1682        # select the default component
1683        self.SetSizerAndFit(mainSizer)
1684        self.SetAutoLayout(1)
1685        self.SetupScrolling()
1686
1687
1688
1689
1690class RunPanel(wx.Panel):
1691    """ the run panel on the main frame for starting and stopping sage """
1692
1693    def __init__(self, parent):
1694        wx.Panel.__init__(self, parent, wx.ID_ANY)
1695        self.MakeWidgets()
1696
1697       
1698    def MakeWidgets(self):
1699        mainSizer = wx.BoxSizer( wx.VERTICAL )
1700        boxSizer = wx.BoxSizer( wx.HORIZONTAL )
1701
1702        # add components to the boxSizer
1703        self.runBtn = wx.Button(self, 1, "START")
1704        self.runBtn.Bind(wx.EVT_BUTTON, self.OnRun)
1705        self.runBtn.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1706        self.runBtn.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeaveWindow)
1707        makeBiggerBoldFont(self.runBtn)
1708        makeBiggerFont(self.runBtn)
1709
1710        self.stopBtn = wx.Button(self, 2, "STOP")
1711        self.stopBtn.Bind(wx.EVT_BUTTON, self.OnStop)
1712        self.stopBtn.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1713        self.stopBtn.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeaveWindow)
1714        makeBiggerBoldFont(self.stopBtn)
1715        makeBiggerFont(self.stopBtn)
1716
1717        self.helpNote = wx.StaticText(self, wx.ID_ANY, "\n", style=wx.ALIGN_CENTRE)
1718        self.helpNote.SetMinSize((-1, 30))
1719
1720        self.simpleBtn = hl.HyperLinkCtrl(self, wx.ID_ANY, "<- Simple Mode")
1721        self.simpleBtn.SetBackgroundColour(self.GetBackgroundColour())
1722        self.simpleBtn.AutoBrowse(False)
1723        self.simpleBtn.Bind(hl.EVT_HYPERLINK_LEFT, self.OnSimple)
1724
1725        boxSizer.Add(self.runBtn, 0, wx.ALIGN_CENTER | wx.TOP | wx.RIGHT, 15)
1726        boxSizer.Add(self.stopBtn, 0, wx.ALIGN_CENTER | wx.TOP | wx.LEFT, 15)
1727
1728        mainSizer.Add(boxSizer, 0, wx.ALIGN_CENTER)
1729        mainSizer.Add(self.helpNote, 0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.ALL, 5)
1730        mainSizer.AddSpacer((10,10), 1)
1731        mainSizer.Add(self.simpleBtn, 0, wx.ALIGN_BOTTOM | wx.ALIGN_LEFT | wx.ALL, 10)
1732   
1733        self.SetSizerAndFit(mainSizer)
1734
1735
1736    def OnSimple(self, evt):
1737        wx.GetApp().GetTopWindow().advancedFrame.Hide()
1738        wx.GetApp().GetTopWindow().Show()
1739
1740
1741    def OnMouseEnterWindow(self, evt):
1742        if evt.GetId() == 1:
1743            self.helpNote.SetLabel("Start all the checked components\nthat are not already running.")
1744        else:
1745            self.helpNote.SetLabel("Stop all the components that are\nnot running in the background.")
1746        evt.Skip()
1747       
1748
1749    def OnMouseLeaveWindow(self, evt):
1750        self.helpNote.SetLabel("")
1751        self.GetSizer().Layout()
1752        evt.Skip()
1753           
1754
1755    def OnRun(self, evt):
1756        """ start all the checked processes... but start sage before everything else
1757            because some components need sage running already
1758        """
1759
1760        # first start sage and then sleep for a few seconds
1761        sage = components[SAGE]
1762        if sage.settings.doRun:
1763            sage.process.start()
1764            time.sleep(2)
1765
1766        # now start the rest of the components
1767        for c in components.itervalues():
1768            if c.componentType != SAGE and c.settings.doRun:
1769                c.process.start()
1770        saveSettings()  # to save the changed PIDs
1771       
1772
1773    def OnStop(self, evt):
1774        for c in components.itervalues():
1775            if not c.settings.inBG:
1776                c.process.stop()
1777        saveSettings()  # to save the changed PIDs
1778       
1779
1780
1781
1782
1783class QuickFrame(wx.Frame):
1784    def __init__(self):
1785        wx.Frame.__init__(self, None, wx.ID_ANY, "SAGE Launcher",
1786                          style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)
1787        self.SetBackgroundColour(wx.Colour(14,51,51))
1788        #self.SetClientSize((373, 315))
1789        self.SetMinSize((377, 315))
1790        self.MakeWidgets()
1791        self.SetThemeEnabled(False)
1792        self.SetIcon(getSageIcon())
1793        self.Bind(wx.EVT_CLOSE, self.OnClose)
1794        self.Centre()
1795        self.Show()
1796       
1797
1798    def MakeWidgets(self):
1799        ms = wx.BoxSizer(wx.VERTICAL)
1800        self.panel = wx.Panel(self, wx.ID_ANY)
1801        self.panel.SetBackgroundColour(self.GetBackgroundColour())
1802        self.panel.Bind(wx.EVT_PAINT, self.OnPaint)
1803        ms.Add(self.panel, 1, wx.EXPAND)
1804       
1805        self.runBtn = buttons.GenBitmapButton(self.panel, 1, getSageStartUpBitmap(), style=wx.BORDER_NONE)
1806        self.runBtn.SetBitmapSelected(getSageStartDownBitmap())
1807        self.runBtn.faceDnClr = self.GetBackgroundColour()
1808        self.runBtn.Bind(wx.EVT_BUTTON, self.OnRun)
1809           
1810        self.stopBtn = buttons.GenBitmapButton(self.panel, 2, getSageStopUpBitmap(), style=wx.BORDER_NONE)
1811        self.stopBtn.SetBitmapSelected(getSageStopDownBitmap())
1812        self.stopBtn.faceDnClr = self.GetBackgroundColour()
1813        self.stopBtn.Bind(wx.EVT_BUTTON, self.OnStop)
1814
1815        self.advancedBtn = hl.HyperLinkCtrl(self.panel, wx.ID_ANY, "Advanced Mode ->")
1816        self.advancedBtn.SetBackgroundColour(self.GetBackgroundColour())
1817        self.advancedBtn.SetForegroundColour(wx.Colour(103,153,102))
1818        self.advancedBtn.AutoBrowse(False)
1819        self.advancedBtn.Bind(hl.EVT_HYPERLINK_LEFT, self.OnAdvanced)
1820       
1821        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1822        btnSizer.Add(self.runBtn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.TOP | wx.RIGHT, 15)
1823        btnSizer.Add(self.stopBtn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.TOP | wx.LEFT, 15)
1824
1825        sizer = wx.BoxSizer(wx.VERTICAL)
1826        sizer.Add((150,150))
1827        sizer.Add(self.MakeStatesPanel(self.panel), 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.ALL, 10)
1828        sizer.Add(btnSizer, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM | wx.ALL, 10)
1829        sizer.Add(self.advancedBtn, 0, wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT | wx.ALL, 10)
1830
1831        self.panel.SetSizer(sizer)
1832        self.SetSizer(ms)
1833        self.Fit()
1834
1835
1836    def MakeStatesPanel(self, parent):
1837        def OnSelectedRadioButton(evt):
1838            session = evt.GetEventObject().GetLabel()
1839            if session == "New session":
1840                session = ""
1841            components[SAGE_UI].settings.loadState = session
1842
1843        stateHash = self.GetStateList()
1844       
1845        statesPanel = scrolled.ScrolledPanel(parent, wx.ID_ANY, size=(200,200))
1846        statesPanel.SetBackgroundColour(wx.Colour(14,51,51))
1847        statesPanel.SetForegroundColour(wx.Colour(200,200,200))
1848        sizer = wx.BoxSizer(wx.VERTICAL)
1849
1850        # add the default radio button
1851        choices = {}
1852        states = stateHash.keys()
1853        states.sort()
1854        defaultBtn = wx.RadioButton(statesPanel, -1, "New session", style=wx.RB_GROUP)
1855        defaultBtn.Bind(wx.EVT_RADIOBUTTON, OnSelectedRadioButton, defaultBtn)
1856        #defaultBtn.SetBackgroundColour(wx.Colour(14,51,51))
1857        #defaultBtn.SetForegroundColour(wx.Colour(200,200,200))
1858        defaultBtn.SetToolTipString("Empty display wall with nothing running")
1859        choices["new"] = defaultBtn
1860        sizer.Add(defaultBtn, 0, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.TOP, border=5)
1861        sizer.Add(wx.StaticLine(statesPanel, -1), 0, wx.EXPAND | wx.ALL, border=5)
1862
1863        # add the rest of them
1864        if len(stateHash) == 0:
1865            t = wx.StaticText(statesPanel, wx.ID_ANY, "Any sessions you save through SAGE UI will show up here.")
1866            try: t.Wrap(200)
1867            except: pass
1868            sizer.Add(t, 0, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.BOTTOM | wx.TOP, border=2)
1869           
1870        for stateName in states:
1871            btn = wx.RadioButton(statesPanel, wx.ID_ANY, stateName)
1872            btn.SetToolTipString(stateHash[stateName])
1873            #btn.SetBackgroundColour(wx.Colour(14,51,51))
1874            #btn.SetForegroundColour(wx.Colour(200,200,200))
1875            choices[stateName] = btn
1876            btn.Bind(wx.EVT_RADIOBUTTON, OnSelectedRadioButton, btn)
1877            sizer.Add(btn, 0, wx.ALIGN_LEFT | wx.ALIGN_TOP | wx.BOTTOM | wx.TOP, border=2)
1878
1879        choices["new"].SetValue(True)
1880        components[SAGE_UI].settings.loadState = ""
1881
1882        statesPanel.SetSizer(sizer)
1883        statesPanel.SetAutoLayout(1)
1884        statesPanel.SetupScrolling(scroll_x = False)
1885       
1886        return statesPanel
1887
1888
1889    def GetStateList(self):
1890        """ returns a hash of key=stateName, value=description """
1891        savedStatesDir = getUserPath("saved-states")
1892        stateHash = {}
1893        appList = []
1894        description = ""
1895
1896        if not os.path.isdir(savedStatesDir):
1897            return {}
1898       
1899        # load all the states and read descriptions from them
1900        for fileName in os.listdir(savedStatesDir):
1901            filePath = opj(savedStatesDir, fileName)
1902            if os.path.isfile(filePath) and os.path.splitext(filePath)[1] == ".state":
1903                try:
1904                    stateName = os.path.splitext( os.path.split(filePath)[1] )[0]
1905                    f = open(filePath, "rb")
1906                    (description, appList) = pickle.Unpickler(f).load()
1907                    f.close()
1908                    stateHash[stateName] = description
1909                except:
1910                    print "\nUnable to read saved state file: "+filePath
1911                    continue
1912
1913        return stateHash
1914       
1915
1916    def OnPaint(self, evt):
1917        dc = wx.PaintDC(self.panel)
1918        self.Redraw(dc)
1919
1920
1921    def Redraw(self, dc=None):
1922        if not dc:
1923            dc = wx.ClientDC(self.panel)
1924        dc.DrawBitmap(getSageBitmap(), 0, 0, False)
1925       
1926       
1927    def OnClose(self, evt):
1928        saveSettings()
1929
1930        # stop all the read threads from the executed processes
1931        for c in components.itervalues():
1932            c.process.stopRead()
1933       
1934        evt.Skip()
1935
1936
1937    def OnAdvanced(self, evt):
1938        self.Hide()
1939        self.advancedFrame.Show()
1940
1941
1942    def OnRun(self, evt):
1943        """ start all the checked processes... but start sage before everything else
1944            because some components need sage running already
1945        """
1946
1947        # first start sage and then sleep for a few seconds
1948        sage = components[SAGE]
1949        if sage.settings.doRun:
1950            sage.process.start()
1951            time.sleep(2)
1952
1953        # now start the rest of the components
1954        for c in components.itervalues():
1955            if c.componentType != SAGE and c.settings.doRun:
1956                c.process.start()
1957        saveSettings()  # to save the changed PIDs
1958       
1959
1960    def OnStop(self, evt):
1961        for c in components.itervalues():
1962            if not c.settings.inBG:
1963                c.process.stop()
1964        saveSettings()  # to save the changed PIDs
1965       
1966
1967
1968
1969
1970
1971class AdvancedFrame(wx.Frame):
1972    def __init__(self, parent):
1973        wx.Frame.__init__(self, parent, wx.ID_ANY, "SAGE Launcher - Advanced Settings")
1974        self.MakeWidgets()
1975        self.SetThemeEnabled(False)
1976        self.SetIcon(getSageIcon())
1977        self.Bind(wx.EVT_CLOSE, self.OnClose)
1978           
1979        # a hack to get correct frame colour on Windows
1980        if isWin():
1981            global colour
1982            colour = self.componentsPanel.GetBackgroundColour()
1983            self.SetBackgroundColour(colour)
1984
1985
1986    def MakeWidgets(self):
1987        sizer = wx.BoxSizer(wx.VERTICAL)
1988       
1989        # set up tooltips globally
1990        tt = wx.ToolTip("")
1991        tt.SetDelay(500)
1992        tt.Enable(True)
1993       
1994        t = HELP_INTRO
1995        introText = wx.StaticText(self, wx.ID_ANY, t)
1996       
1997        self.componentsPanel = ComponentsPanel(self)
1998        sizer.Add(introText, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 20)
1999        sizer.Add(self.componentsPanel, 1, wx.EXPAND | wx.ALL, 10)
2000        self.SetSizerAndFit(sizer)
2001
2002
2003    def OnClose(self, evt):
2004        self.GetParent().Close()
2005
2006
2007
2008 
2009# --------------------------------------------------------
2010#
2011#                  MAIN ENTRY POINT
2012#
2013# --------------------------------------------------------
2014
2015
2016def main():
2017    """ main called at the bottom of this file """
2018
2019    # load the settings before everything else
2020    settings = loadSettings()  # a hash comes back
2021
2022    # read the tile config
2023    global tileConfig
2024    tileConfig = TileConfig(getTileConfig())
2025
2026    # create the Component objects and set their settings
2027    global components
2028    for componentType in componentNames:
2029        c = Component(componentType)
2030        c.settings = settings[componentType]
2031        components[componentType] = c
2032       
2033    # create our GUI
2034    app = wx.App(redirect = False)
2035    quickFrame = QuickFrame()
2036    advancedFrame = AdvancedFrame(quickFrame)
2037    quickFrame.advancedFrame = advancedFrame
2038    app.SetTopWindow( quickFrame )
2039
2040    # start the app
2041    app.MainLoop()
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054# --------------------------------------------------------
2055#
2056#            MISCELLANEOUS HELPER CLASSES
2057#
2058# --------------------------------------------------------
2059
2060
2061
2062
2063class Process:
2064    """ there is one instance of this class for each component
2065        that is running (or needs to run). It starts the process
2066        itself and creates a thread to read data from that
2067        process.
2068    """
2069    def __init__(self, componentType):
2070        self.componentType = componentType
2071        self.settings = components[componentType].settings
2072        self.output = components[componentType].output
2073        self.running = False
2074        self.p = None    # Popen process object
2075        self.t = None    # thread reading the output from the process
2076        self._extraProcesses = []  # pids of extra processes started before SAGE
2077
2078
2079    def start(self):
2080        """ if the process is already running do nothing...
2081            otherwise launch the new process and
2082        """
2083        if self.isAlive():
2084            self.output.SetInsertionPointEnd()
2085            self.output.WriteText("\n\n**** Already running ****\n\n")
2086        else:
2087            cmd = self.settings.getStartCommand()
2088            cwdir = self.settings.getCwd()
2089
2090            try:
2091                # execute any extra commands first
2092                self.__startExtra()
2093               
2094                self.p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT, bufsize=1, cwd=cwdir)
2095                self.t = Thread(target=self.__readOutput)
2096                self.t.setDaemon(True)
2097                self.doRead = True
2098                self.t.start()
2099                self.running = True
2100                self.settings.pid = self.p.pid  # save the pid for later
2101               
2102            except:
2103                print " ***** Error while starting", self.componentType
2104                print "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
2105                self.running = False
2106                self.doRead = False
2107                self.t = None
2108                self.p = None
2109
2110
2111    def __startExtra(self):
2112        """ if the component has any extra commands to execute
2113            before startup, this will start them """
2114        if hasattr(self.settings, 'onStart'):
2115            if self.settings.onStart != "":
2116                for cmd in self.settings.onStart.splitlines():
2117                    if not cmd.strip().startswith("#"):
2118                        proc = sp.Popen(cmd.split())
2119                        self._extraProcesses.append(proc.pid)  # save the pid for later killing
2120       
2121
2122    def stop(self):
2123        """ stops the read thread and the process """
2124        try:
2125            # kill SAGE unconditionally
2126            if self.componentType == SAGE:
2127                for c in self.settings.getKillCmd():
2128                    sp.Popen(c)
2129
2130                # kill extra stuff started before sage
2131                for pid in self._extraProcesses:
2132                    self.__killProcess(pid)
2133                self._extraProcesses = []
2134               
2135
2136            # kill appLauncher unconditionally (because we can)
2137            elif self.componentType == APP_LAUNCHER:
2138                sp.Popen(self.settings.getKillCmd(), cwd=self.settings.getCwd())
2139
2140            # kill the other processes depending on whether they are running or not
2141            elif self.isAlive():
2142
2143                # get the pid from the current object or from the saved settings
2144                if self.p:    pid = self.p.pid
2145                else:         pid = self.settings.pid
2146
2147                # execute a kill command
2148                self.__killProcess(pid)
2149
2150            # execute any extra commands
2151            self.__stopExtra()
2152           
2153        except:
2154            print " ***** Error while stopping",self.componentType
2155            print "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
2156           
2157        self.settings.pid = -1
2158        self.running = False
2159        self.doRead = False
2160        self.p = None
2161        self.t = None
2162
2163
2164    def __stopExtra(self):
2165        """ if the component has any extra commands to execute
2166            after stopping, this will execute them """
2167        if hasattr(self.settings, 'onStop'):
2168            if self.settings.onStop != "":
2169                for cmd in self.settings.onStop.splitlines():
2170                    sp.Popen(cmd.split())
2171
2172
2173    def isAlive(self):
2174        """ return true if the process is currently running, false otherwise """
2175        if self.running or  \
2176           (self.p and (self.p.poll() is None)) or \
2177           self.settings.pid != -1:
2178
2179            # if it's presumably running but its process object is dead,
2180            # it's a zombie so report it as dead and close it's read thread
2181            if self.p and not (self.p.poll() is None):
2182                self.stopRead()
2183                if self.t: self.t.join(1) # wait for the old read thread to die
2184                return False
2185           
2186            return True
2187
2188        else:
2189            return False
2190
2191
2192    def __killProcess(self, pid):
2193        """ execute a kill command depending on the OS
2194            the Windows version will only work on XP
2195        """
2196        if isWin():
2197            sp.Popen(["taskkill", "/F", "/PID", str(pid)])
2198        else:
2199            sp.Popen(["/bin/kill", "-9", str(pid)])
2200               
2201
2202    def stopRead(self):
2203        self.doRead = False
2204       
2205   
2206    def __readOutput(self):
2207        """ this runs in a thread and it checks for the output
2208            from the executed process. Once the output comes in,
2209            it tells the main wx thread to add the text that was
2210            just read.
2211        """
2212        while self.doRead:
2213            txt = os.read(self.p.stdout.fileno(), 2048)
2214            wx.CallAfter(self.fillText, txt)
2215            time.sleep(0.3)   # read at most twice / sec
2216
2217
2218    def fillText(self, txt):
2219        """ first check whether the text we are about to insert
2220            will overflow the control or not. If it would, remove
2221            some text from the beginning and then append the
2222            new text. If not, just append the new text.
2223        """
2224        if len(txt) + self.output.GetLastPosition() > MAX_TEXT_LEN:
2225            self.output.Remove(0, 2048)
2226        self.output.SetInsertionPointEnd()
2227        self.output.WriteText(txt)
2228
2229
2230
2231
2232
2233
2234class FoldBar(wx.Panel):
2235    """ This class represents the fold bar title for the individual
2236        application configuration in the app launcher frame.
2237        When clicked it expands/collapses the sizer that's holding the actual controls
2238        for this section. It does that through the callback that's passed to this panel
2239    """
2240   
2241    def __init__(self, parent, btnId, title, callback):
2242        wx.Panel.__init__(self, parent, wx.ID_ANY, style = wx.NO_BORDER)
2243        label = wx.StaticText(self, wx.ID_ANY, title)
2244        label.Bind(wx.EVT_LEFT_DOWN, self.OnBtn)
2245        makeBoldFont(label)
2246        self.btn = buttons.GenBitmapButton(self, btnId, GetCollapsedIconBitmap(), style=wx.BORDER_NONE)
2247        self.btn.Bind(wx.EVT_BUTTON, self.OnBtn)
2248
2249        sizer = wx.BoxSizer(wx.HORIZONTAL)
2250        sizer.Add(label, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
2251        sizer.Add(self.btn, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
2252        self.SetSizer(sizer)
2253        self.Fit()
2254
2255        self.callback = callback
2256        self.collapsed = True
2257       
2258       
2259    def OnBtn(self, evt):
2260        if self.collapsed:
2261            self.collapsed = False
2262            self.btn.SetBitmapLabel(GetExpandedIconBitmap())
2263            self.btn.Refresh()
2264            self.callback(self.btn.GetId(), False)
2265        else:
2266            self.collapsed = True
2267            self.btn.SetBitmapLabel(GetCollapsedIconBitmap())
2268            self.btn.Refresh()
2269            self.callback(self.btn.GetId(), True)
2270
2271
2272
2273
2274
2275####                                        ##### 
2276####       TILE CONFIGURATION CLASSES       #####
2277####                                        #####
2278
2279class Tile:
2280    """ describes one tile """
2281
2282    def __init__(self):
2283        self.name = "node"
2284        self.ip = "127.0.0.1"
2285        self.numMonitors = 0
2286        self.monitors = []  # a list of positions for each monitor (tuples)
2287
2288
2289    def addMonitor(self, pos):
2290        if pos in self.monitors:
2291            return
2292        self.monitors.append(pos)
2293        self.numMonitors += 1
2294
2295
2296    def delMonitor(self, pos):
2297        if pos in self.monitors:
2298            self.monitors.remove(pos)
2299            self.numMonitors -= 1
2300
2301
2302    def toString(self):
2303        s = "\n\nDisplayNode\n"
2304        s += "\tName %s\n" % self.name
2305        s += "\tIP %s\n" % self.ip
2306        s += "\tMonitors %s " %self.numMonitors
2307        for m in self.monitors:
2308            s += str(m) + " "
2309
2310        return s
2311
2312
2313
2314       
2315
2316class TileConfig:
2317    """ describes the whole tiled display configuration """
2318
2319    def __init__(self, configName):
2320        self.dim = [1,1]                # dimensions of the display (xTiles, yTiles)
2321        self.mullions = ["0.75", "0.75", "0.75", "0.75"]  # mullions on top, down, left, right
2322        self.resolution = [800, 600]    # of each tile in pixels
2323        self.ppi = 72                   # pixels-per-inch of each tile
2324        self.numMachines = 1            # total number of machines in the display
2325        self.tiles = {}                 # key=IP, value=Tile object
2326        self.readConfigFile(configName)
2327
2328
2329    def getAllIPs(self):
2330        return self.tiles.keys()
2331       
2332
2333    def readConfigFile(self, filename):
2334        f = open(filename, "r")
2335        lines = f.readlines()
2336        f.close()
2337
2338        # reinitialize variables
2339        self.tiles = {}
2340        currentTile = None
2341
2342        # read the file line by line
2343        for line in lines:
2344
2345            if '#' in line:     # allow comments with # anywhere in the line
2346                line = line.split('#')[0].strip()
2347            line = line.strip()
2348
2349            if 'Dimensions' in line:
2350                d = line.lstrip('Dimensions').strip().split(' ',1)
2351                self.dim = [int(d[0]), int(d[1])]
2352
2353            elif 'Mullions' in line:
2354                m = line.lstrip('Mullions').strip().split(' ',3)
2355                self.mullions = [m[0], m[1], m[2], m[3]]
2356
2357            elif 'Resolution' in line:
2358                r = line.lstrip('Resolution').strip().split(' ',1)
2359                self.resolution = [int(r[0]), int(r[1])]
2360
2361            elif 'PPI' in line:
2362                self.ppi = int( float ( line.lstrip('PPI').strip() ) )
2363           
2364            elif 'Machines' in line:
2365                self.numMachines = int( line.lstrip('Machines').strip() )
2366
2367
2368            # reading the tiles
2369            elif 'DisplayNode' in line:
2370                if currentTile:  # store the previous Tile if exists
2371                    self.tiles[ currentTile.ip ] = currentTile
2372                currentTile = Tile()
2373
2374            elif 'Name' in line:
2375                currentTile.name = line.lstrip('Name').strip()
2376
2377            elif 'IP' in line:
2378                currentTile.ip = line.lstrip('IP').split(":")[0].strip()
2379
2380            elif 'Monitors' in line:
2381                (num, positions) = line.lstrip('Monitors').strip().split(' ', 1)
2382                nums = positions.replace('(', ' ').replace(')', ' ').replace(',', ' ').split()
2383                for i in range(0, len(nums)-1, 2):
2384                    currentTile.addMonitor( (int(nums[i]),int(nums[i+1])) )
2385
2386        # add the last one
2387        self.tiles[ currentTile.ip ] = currentTile
2388
2389       
2390
2391    def writeConfigFile(self):
2392        # first the global stuff
2393        s = "TileDisplay\n"
2394        s += "\tDimensions %d %d\n" % (self.dim[0], self.dim[1])
2395        s += "\tMullions %s %s %s %s\n" % (self.mullions[0], self.mullions[1],
2396                                           self.mullions[2], self.mullions[3])
2397        s += "\tResolution %d %d\n" % (self.resolution[0], self.resolution[1])
2398        s += "\tPPI %d\n" % self.ppi
2399        s += "\tMachines %d\n" % self.numMachines
2400
2401        # now each node
2402        for t in self.tiles.itervalues():
2403            s += t.toString()
2404
2405        return s
2406   
2407       
2408   
2409       
2410
2411####                                        ##### 
2412####   APP LAUNCHER CONFIGURATION CLASSES   #####
2413####                                        #####
2414class OneConfig:
2415    ''' describes one configuration for an app '''
2416   
2417    def __init__(self, name, appName, dynamic=False):
2418        self._configName = name
2419        self._dynamic = dynamic     # dynamic config???
2420        self._appName = appName
2421        self._configFilename = appName+".conf"
2422        self._launcherId = ""
2423
2424        self._binDir = "$SAGE_DIRECTORY/bin/"  # where the binary resides - this is where the config is copied to
2425        self._nodeNum = 1
2426        self._position = (100, 100)        # initial position of the window on SAGE
2427        self._size = (1000, 1000)          # initial size of the window on SAGE
2428        self._command = ""                 # the actual command used to start the application
2429        self._targetMachine = "127.0.0.1"           # the render machine where the app will be started
2430        self._protocol = "TCP"
2431        self._masterIP = None              # the master machine of a parallel application
2432        self._fsIP = None                  # which SAGE will the app connect to (if not using sageBridge)
2433        self._fsPort = None                # which SAGE will the app connect to (if not using sageBridge)
2434        self._useBridge = False            # if True the app will connect to sageBridge instead of fsManager
2435        self._bridgeIP = ""                # the machine for sage bridge
2436        self._bridgePort = ""              # the machine for sage bridge
2437
2438        self._additionalParams = ""        # any additional parameters you want to specify... used for testing
2439
2440        # audio stuff
2441        self._audioFile = ""
2442             
2443        self._nwID = 1
2444        self._msgPort = 23010
2445        self._syncPort = 13010
2446        self._nodeNum = 1
2447        self._appId = 0                    # the port number for the app on the render machine
2448        self._blockSize = (64,64)
2449        self._blockThreshold = 0
2450        self._streamType = "SAGE_BLOCK_HARD_SYNC"    # sync mode
2451        self._staticApp = False             # static applications dont refresh their windows so sage needs to keep the last frame
2452        self._runOnNodes = False    # if an app has to connect to the outside world or requires
2453                                    #an SDL/GLUT window for rendering then it can't run on the nodes
2454       
2455
2456    def getName(self): return self._configName
2457    def getAppName(self): return self._appName
2458    def isDynamic(self): return self._dynamic
2459    def getConfigFilename(self): return self._configFilename
2460
2461    # audio stuff
2462    def setAudioFile(self, f):
2463        self._audioFile = f
2464    def getAudioFile(self):
2465        return self._audioFile
2466
2467 
2468    def getLauncherId(self):
2469        return self._launcherId
2470    def setLauncherId(self, launcherId):
2471        self._launcherId = launcherId
2472
2473    def setBinDir(self, d):
2474        self._binDir = d
2475    def getBinDir(self):
2476        return self._binDir
2477
2478    def setNodeNum(self, num): self._nodeNum = num
2479    def getNodeNum(self): return self._nodeNum
2480
2481    def setPosition(self, pos): self._position = pos
2482    def getPosition(self): return self._position
2483
2484    def setSize(self, size): self._size = size
2485    def getSize(self): return self._size
2486
2487    def setCommand(self, command): self._command = command
2488    def getCommand(self): return self._command
2489
2490    def setTargetMachine(self, target): self._targetMachine = target
2491    def getTargetMachine(self): return self._targetMachine
2492
2493    def setProtocol(self, protocol):
2494        if protocol == "tvTcpModule.so" or protocol=="TCP":
2495            self._protocol = "TCP"
2496        else:
2497            self._protocol = "UDP"
2498    def getProtocol(self): return self._protocol
2499
2500    def setMasterIP(self, ip):
2501        self._masterIP = ip
2502    def getMasterIP(self):
2503        return self._masterIP
2504
2505    def setFSIP(self, ip):
2506        self._fsIP = ip
2507    def getFSIP(self):
2508        return self._fsIP
2509   
2510    def setFSPort(self, port):
2511        self._fsPort = port
2512    def getFSPort(self):
2513        return self._fsPort
2514
2515    def setBridgeIP(self, ip):
2516        self._bridgeIP = ip
2517    def getBridgeIP(self):
2518        return self._bridgeIP
2519
2520    def setBridgePort(self, port):
2521        self._bridgePort = port
2522    def getBridgePort(self):
2523        return self._bridgePort
2524
2525    def setUseBridge(self, doUse):
2526        self._useBridge = doUse
2527    def getUseBridge(self):
2528        return self._useBridge
2529
2530    def setNWId(self, id):
2531        self._nwID = id
2532    def getNWId(self):
2533        return self._nwID
2534
2535    def setMsgPort(self, port):
2536        self._msgPort = port
2537    def getMsgPort(self):
2538        return self._msgPort
2539
2540    def setSyncPort(self, port):
2541        self._syncPort = port
2542    def getSyncPort(self):
2543        return self._syncPort
2544
2545    def setAppId(self, id):
2546        self._appId = id
2547    def getAppId(self):
2548        return self._appId
2549
2550    def setBlockSize(self, size):
2551        self._blockSize = size
2552    def getBlockSize(self):
2553        return self._blockSize
2554
2555    def setBlockThreshold(self, threshold):
2556        self._blockThreshold = threshold
2557    def getBlockThreshold(self):
2558        return self._blockThreshold
2559
2560    def setStreamType(self, mode):
2561        self._streamType = mode
2562    def getStreamType(self):
2563        return self._streamType
2564
2565    def setStaticApp(self, do):
2566        self._staticApp = do
2567    def getStaticApp(self):
2568        return self._staticApp
2569
2570    def setRunOnNodes(self, run):
2571        self._runOnNodes = run
2572    def getRunOnNodes(self):
2573        return self._runOnNodes
2574
2575    def setAdditionalParams(self, param):
2576        self._additionalParams += param + "\n"
2577    def getAdditionalParams(self):
2578        return self._additionalParams
2579
2580    def writeToFile(self):
2581        s = ""
2582
2583        # sage bridge stuff
2584        if self.getUseBridge():      # using sageBridge
2585            s += 'bridgeOn true\n'
2586            s += 'bridgeIP %s\n'% self.getBridgeIP()
2587            s += 'bridgePort %s\n'% self.getBridgePort()
2588        else:                        # not using sageBridge
2589            s += 'bridgeOn false\n'
2590           
2591        s += 'fsIP %s\n'% self.getFSIP()
2592        s += 'fsPort %s\n'% self.getFSPort()
2593        s += 'masterIP %s\n'% self.getMasterIP()
2594        s += 'nwID %d\n' % self.getNWId()
2595        s += 'msgPort %d\n' % self.getMsgPort()
2596        s += 'syncPort %d\n' % self.getSyncPort()
2597        s += 'nodeNum %d\n' % self.getNodeNum()
2598        s += 'appID %d\n' % self.getAppId()
2599        s += 'launcherID %s\n' % self.getLauncherId()
2600        s += 'pixelBlockSize %d %d\n' % (self.getBlockSize()[0], self.getBlockSize()[1])
2601        s += 'blockThreshold %d\n' % self.getBlockThreshold()
2602        s += 'winX %d\n' % self.getPosition()[0]
2603        s += 'winY %d\n' % self.getPosition()[1]
2604        s += 'winWidth %d\n' % self.getSize()[0]
2605        s += 'winHeight %d\n' % self.getSize()[1]
2606        s += 'streamType %s\n' % self.getStreamType()
2607        s += 'nwProtocol %s\n' % self.getProtocol()
2608
2609        # audio
2610        if self.getAudioFile():
2611            s += 'audioOn true\n'
2612            s += 'audioFile %s\n' % self.getAudioFile()
2613            s += 'audioType read\ndeviceNum -1\n'
2614            s += 'framePerBuffer 512\n'
2615           
2616        # static app
2617        if self.getStaticApp():
2618            s += 'asyncUpdate true\n'
2619        else:
2620            s += 'asyncUpdate false\n'
2621
2622        # additional params
2623        s += self.getAdditionalParams()
2624           
2625        f = open(self._configFilename, "w")
2626        f.write(s)
2627        f.close()
2628
2629
2630    def getConfigString(self):
2631        """ returns a tuple of strings: (configName, optionalArgs that the app was started with) """
2632        return (self.getName(), self.getCommand().split(" ", 1)[1].strip())
2633
2634   
2635    def getAppLauncherConfig(self):
2636        """ this returns the appLauncher config format (for applications.conf)"""
2637
2638        s = ""
2639
2640        # common parameters
2641        s += 'configName %s\n' % self.getName()
2642        s += 'Init %d %d %d %d\n' % (self.getPosition()[0],
2643                                     self.getPosition()[1],
2644                                     self.getSize()[0],
2645                                     self.getSize()[1])
2646        s += 'exec %s %s\n' % (self.getTargetMachine(), self.getCommand())
2647
2648        # now the optional parameters
2649        if self.getNodeNum() != 1:
2650            s += 'nodeNum %d\n' % self.getNodeNum()
2651       
2652        if self.getStaticApp():
2653            s += 'staticApp\n'
2654
2655        if self.getProtocol() != "TCP":
2656            s += 'nwProtocol %s\n' % self.getProtocol()
2657           
2658        if self.getBinDir() != "$SAGE_DIRECTORY/bin/":
2659            s += 'binDir %s\n' % self.getBinDir()
2660           
2661        if self.getBridgeIP() != "":
2662            s += 'bridgeIP %s\n' % self.getBridgeIP()
2663            s += 'bridgePort %s\n' % self.getBridgePort()
2664
2665        if self.getBlockSize() != (64,64):
2666            s += 'pixelBlockSize %s %s\n' % (self.getBlockSize()[0],
2667                                             self.getBlockSize()[1])
2668           
2669        if self.getMasterIP():
2670            s += 'masterIP %s\n' % self.getMasterIP()
2671
2672        if self.getRunOnNodes():
2673            s += 'runOnNodes\n'
2674           
2675        if self.getStreamType() != "SAGE_BLOCK_HARD_SYNC":
2676            s += 'sync %s\n' % self.getStreamType()
2677
2678        if self.getAudioFile():
2679            s += 'audioFile %s\n' % self.getAudioFile()
2680
2681        s += self.getAdditionalParams()
2682
2683        return s
2684
2685
2686
2687
2688class AppConfig:
2689    ''' a collection of all the configurations for an app '''
2690   
2691    def __init__(self, appName):
2692        self._configs = {}   #key=configName, value=OneConfig object
2693        self._appName = appName
2694
2695    def getAppName(self):
2696        return self._appName
2697
2698    def addConfig(self, oneConfig):
2699        self._configs[oneConfig.getName()] = oneConfig
2700
2701    def addNewConfig(self, configName):
2702        cfg = OneConfig(configName, self._appName)
2703        self._configs[configName] = cfg
2704
2705    def delConfig(self, configName):
2706        if configName in self._configs:
2707            del self._configs[configName]
2708       
2709    def getConfig(self, configName):
2710        return self._configs[configName]
2711   
2712    def getDefaultConfig(self):
2713        return self._configs.values()[0]  #return an arbitrary config file
2714
2715    def getAllConfigs(self):
2716        return self._configs
2717   
2718    def getAllConfigNames(self):
2719        return self._configs.keys()
2720   
2721    def makeConfigFile(self, configName):
2722        config = self.getConfig(configName)
2723        config.writeToFile()
2724
2725
2726
2727   
2728
2729class AppConfigurations:
2730    ''' a collection of all applications and their configurations '''
2731   
2732    def __init__(self, configFile):
2733        self._configFile = configFile
2734        self._lastModTime = None  #last modification time to the config file
2735        # sageBridge stuff
2736        self._bridgeIP = None
2737        self._bridgePort = None
2738
2739        self._appConfigs = {}   #key=appName, value=AppConfig object
2740        self._readConfig()
2741        #self._printConfig()
2742
2743
2744        # so that we can change the config file without restarting the appLauncher
2745        # checks the last modification time so that we don't reload unnecessarily
2746    def reloadConfigFile(self):
2747        try:
2748            lastModTime = os.path.getmtime(self._configFile)
2749            if lastModTime != self._lastModTime:
2750                self._appConfigs = {}  #empty out the hash
2751                self._readConfig()
2752                self._lastModTime = lastModTime
2753        except:
2754            WriteLog( "".join(tb.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) )
2755
2756    def addNewApp(self, appName):
2757        self._appConfigs[appName] = AppConfig(appName)
2758
2759    def delApp(self, appName):
2760        if appName in self._appConfigs:
2761            del self._appConfigs[appName]
2762         
2763    def getConfig(self, appName, configName):   #returns a copy so that it can be safely modified without the destroying what's in the config file
2764        return self._appConfigs[appName].getConfig(configName)
2765
2766    def getDefaultConfig(self, appName):   #returns a copy so that it can be safely modified without destroying what's in the config file
2767        return self._appConfigs[appName].getDefaultConfig()
2768
2769    def getApp(self, appName):
2770        return self._appConfigs[appName]
2771
2772    def getAppList(self):   # returns just the names of the apps
2773        return self._appConfigs.keys()
2774
2775    def getBridgeIP(self):
2776        return self._bridgeIP
2777
2778    def getBridgePort(self):
2779        return self._bridgePort
2780
2781    def _printConfig(self):
2782        for name, app in self._appConfigs.iteritems():
2783            print "\n----------------------------------------"
2784            print "Config For: ", name
2785            for name, config in app.getAllConfigs().iteritems():
2786                print "Config: ", name
2787                print "nodeNum = ", config.getNodeNum()
2788                print "pos = ", config.getPosition()
2789                print "size = ", config.getSize()
2790                print "command = ", config.getCommand()
2791                print "target = ", config.getTargetMachine()
2792                print "protocol = ", config.getProtocol()
2793                print "runOnNodes = ", config.getRunOnNodes()
2794
2795        print "\n----------------------------------------"
2796        print "bridgePort = ", self._bridgePort
2797        print "bridgeIP = ", self._bridgeIP
2798
2799
2800    def getConfigHash(self):
2801        """ returns a hash of all the configurations without the objects... just tuples of strings and ints """
2802        strHash = {}   #keyed by appName, value = a list of configNames
2803        for appName, app in self._appConfigs.iteritems():
2804            strHash[appName] = app.getAllConfigNames()
2805        return strHash
2806   
2807               
2808    def _readConfig(self):
2809        f = open(self._configFile, "r")
2810        lines = f.readlines()
2811        f.close()
2812
2813        self.appconfig = None
2814        self.oneconfig = None
2815        self.lineCounter = 0
2816       
2817        for line in lines:
2818            self.lineCounter += 1
2819
2820            # allow comments with #
2821            if '#' in line:
2822                line = line.split('#')[0].strip()
2823               
2824               
2825            if '{' in line:
2826                appName = line.replace('{', ' ').strip()
2827                self.appconfig = AppConfig(appName)
2828               
2829            elif 'configName' in line:
2830                if self.oneconfig:
2831                    self.appconfig.addConfig(self.oneconfig)
2832                self.oneconfig = OneConfig(line.lstrip('configName').strip(), self.appconfig.getAppName())
2833
2834            elif 'nodeNum' in line:
2835                self.oneconfig.setNodeNum(int(line.lstrip('nodeNum').strip()))
2836
2837            elif 'Init' in line:
2838                lineTokens = line.split()
2839                pos = (int(lineTokens[1]), int(lineTokens[2]))
2840                size = (int(lineTokens[3]), int(lineTokens[4]))
2841                self.oneconfig.setPosition(pos)
2842                self.oneconfig.setSize(size)
2843
2844            elif 'exec' in line:
2845                bla, target, command = line.split(' ', 2)
2846                self.oneconfig.setTargetMachine(target.strip())
2847                if not self.oneconfig.getMasterIP():   #if it has been set, dont overwrite it
2848                    self.oneconfig.setMasterIP(target.strip())
2849                self.oneconfig.setCommand(command.strip())
2850
2851            elif 'nwProtocol' in line:
2852                self.oneconfig.setProtocol(line.lstrip('nwProtocol').strip())
2853
2854            elif 'bridgeIP' in line:
2855                self.oneconfig.setBridgeIP(line.split()[1].strip())
2856
2857            elif 'bridgePort' in line:
2858                self.oneconfig.setBridgePort(line.split()[1].strip())
2859
2860            elif 'runOnNodes' in line:
2861                self.oneconfig.setRunOnNodes(True)
2862
2863            elif 'staticApp' in line:
2864                self.oneconfig.setStaticApp(True)
2865
2866            elif 'pixelBlockSize' in line:
2867                s = line.split()
2868                self.oneconfig.setBlockSize( (int(s[1].strip()), int(s[2].strip()))  )
2869
2870            elif 'binDir' in line:
2871                p = line.split()[1].strip()
2872                if not p.endswith("/"):
2873                    p += "/"
2874                self.oneconfig.setBinDir(p)
2875
2876            elif 'masterIP' in line:
2877                self.oneconfig.setMasterIP(line.split()[1].strip())
2878
2879            elif 'audioFile' in line:
2880                self.oneconfig.setAudioFile(line.split()[1].strip())
2881
2882            elif 'sync' in line:
2883                mode = line.split()[1].strip()
2884                if not mode.startswith("SAGE_BLOCK_"):
2885                    mode = "SAGE_BLOCK_" + mode
2886                   
2887                if mode == "SAGE_BLOCK_NO_SYNC" or \
2888                   mode == "SAGE_BLOCK_SOFT_SYNC" or \
2889                   mode == "SAGE_BLOCK_HARD_SYNC":
2890                    self.oneconfig.setStreamType(mode)
2891                else:
2892                    WriteLog("\n*** Invalid streamType mode on line: "+str(self.lineCounter)+". Defaulting to NO_SYNC")
2893
2894            elif '}' in line:
2895                self.appconfig.addConfig(self.oneconfig)   #save the last config
2896                self._appConfigs[self.appconfig.getAppName()] = self.appconfig   #save the appConfig
2897                self.appconfig = None   #reinitialize everything
2898                self.oneconfig = None
2899
2900
2901            elif 'defaultBridgeIP' in line:
2902                self._bridgeIP = line.split()[1].strip()
2903
2904            elif 'defaultBridgePort' in line:
2905                self._bridgePort = line.split()[1].strip()
2906
2907
2908            elif line in string.whitespace:
2909                pass
2910           
2911            else:    # if line is not recognized
2912                self.oneconfig.setAdditionalParams(line.strip())
2913             
2914
2915    def writeConfig(self):
2916        """ write applications.conf based on the current configuration """
2917        self._configFile = getUserPath("applications", "applications.conf")
2918        f = open(self._configFile, "w")
2919
2920        # loop through all the apps and write their configs to a file
2921        for appName, app in self._appConfigs.iteritems():
2922            f.write("\n"+appName+" {\n")
2923
2924            # write all the configs
2925            for configName, conf in app.getAllConfigs().iteritems():
2926                f.write("\n")
2927                f.write(conf.getAppLauncherConfig())
2928                f.write("\n")
2929               
2930            f.write("}\n\n#"+"-"*30+"\n")   # finish the app
2931
2932        if self._bridgeIP: f.write("\ndefaultBridgeIP "+self._bridgeIP)
2933        if self._bridgePort: f.write("\ndefaultBridgePort "+self._bridgePort+"\n")
2934           
2935        f.close()
2936
2937
2938       
2939
2940
2941
2942
2943
2944
2945# --------------------------------------------------------
2946#
2947#                    HELP STRINGS
2948#
2949# --------------------------------------------------------
2950
2951HELP_INTRO = \
2952"""
2953This SAGE Launcher helps you run SAGE and all the related components listed below. Check the components you wish to run with SAGE and click START.
2954For more information hover your mouse over any area. """
2955
2956HELP_FS = "File Server allows you to easily show and organize\n"\
2957          "common multimedia files in a SAGE environment from\n"\
2958          "SAGE UI. Showing files is as easy as drag-and-drop\n"\
2959          "of files onto the UI."
2960
2961HELP_AL = "Application Launcher takes care of starting SAGE applications\n"\
2962          "either on a local machine or any node of a rendering cluster.\n"\
2963          "Applications are started through SAGE UI."
2964
2965HELP_SU = "SAGE UI is the main interface for controlling your SAGE display."
2966
2967HELP_SP = "SAGE Proxy allows interaction with SAGE through XML-RPC as opposed\n"\
2968          "to regular sockets. Primarily used by the SAGE Web UI."
2969
2970HELP_S = "SAGE itself (i.e. fsManager)"
2971
2972HELP_RUN = "If checked this component will run when START is pressed."
2973
2974HELP_EDIT = "Configure component specific settings."
2975
2976HELP_COMP_STOP = "Kills the component now.\n"\
2977                 "To restart it, make sure that it's checked and then press START below."
2978
2979HELP_INBG = "If run in background, the component will keep running\n"\
2980            "even after STOP is pressed and they need not be restarted.\n"\
2981            "Usually you want to do this if the components are used\n"\
2982            "independently of your local SAGE session."
2983
2984
2985### SAGE help stuff
2986
2987HELP_S_START = "These are just shell commands\n(each line is executed as a separate command)"
2988
2989HELP_S_STOP = "These are just shell commands\n(each line is executed as a separate command)"
2990
2991HELP_S_PROC = "Typically these are SAGE applications and SAGE itself.\n"\
2992              "This should not include components as they are killed separately."
2993
2994
2995### AppLauncher help stuff
2996
2997HELP_AL_PUBLIC = "Makes the appLauncher visible to other SAGE UIs connected to\n"\
2998                 "remote SAGE displays. This appLauncher can then be used by\n"\
2999                 "remote SAGE UIs to start applications and stream to their SAGE display."
3000
3001HELP_AL_PUBLIC_HOST = "Which connection manager/SAGE server to report to?"
3002
3003HELP_AL_APPS = "List of applications currently configured with this appLauncher\n"\
3004               "and available for running from the SAGE UI."
3005
3006HELP_AL_CFG = "Different configurations for a particular application.\n"\
3007                  "Configurations usually specify different running scenarios\n"\
3008                  "of an application. All will show up in the SAGE UI."
3009
3010HELP_AL_CFG_COPY = "Makes a copy of the current configuration with\n"\
3011                      "a different name but same parameters."
3012
3013HELP_AL_CFG_STATIC = "This should be checked for applications that are not animated\n"\
3014                        "(i.e. do not refresh on a regular basis)"
3015
3016HELP_AL_CFG_APP = "This is the actual command executed (could also be a script)\n"\
3017                  "Full paths and/or parameters can be specified as well."
3018
3019HELP_AL_CFG_DIR = "The directory where the app configuration file will be copied to.\n"\
3020                  "This must be in the same directory as the executable that's\n"\
3021                  "initializing SAIL and connecting to SAGE (basically your app).\n"\
3022                  "If empty it defaults to $SAGE_DIRECTORY/bin."
3023
3024HELP_AL_CFG_MACHINE = "This is the remote machine that the application will be started\n"\
3025                      "on. SSH and SCP need to be set up for this to work.\n"\
3026                      "If left empty it defaults to the local machine."
3027
3028HELP_AL_CFG_SIZE = "This is just the initial SAGE window size. Sometimes it is\n"\
3029                   "overwritten at startup by the application (such as imageviewer\n"\
3030                   "or mplayer) to get the correct aspect ratio for the data being displayed."
3031
3032HELP_AL_CFG_NUM_NODES = "Specify the number of nodes of this application that are\n"\
3033                        "streaming pixels to SAGE (this may or may not include the\n"\
3034                        "master node depending on whether it's doing any rendering)"
3035
3036HELP_AL_CFG_MASTER = "This is the IP address of the master node for your parallel\n"\
3037                     "application."
3038
3039HELP_AL_CFG_BP = "Block size that SAGE splits the image into and streams."
3040
3041HELP_AL_CFG_BHOST = "Used in visualcasting for sharing applications between displays.\n"\
3042                    "Specify the address of the SAGE Bridge that's handling this."
3043
3044HELP_AL_CFG_BPORT = "Used in visualcasting for sharing applications between displays.\n"\
3045                    "Specify the port of the SAGE Bridge that's handling this."
3046
3047
3048### SAGE UI help stuff
3049
3050HELP_SU_HOST = "Specify the connection manager/SAGE server to connect to\n"\
3051               "in order to find the running SAGE sessions. This should be\n"\
3052               "the same as for SAGE and the appLauncher."
3053
3054HELP_SU_AUTOLOGIN = "To automatically log in to a sage session specify its name here\n"\
3055                    "(the name should be exactly the same as the one that fsManager reports"\
3056                    "to the connection manager... from the first line of fsManager.conf)"
3057
3058### FileServer help
3059
3060HELP_FS_ROOT = "All the multimedia files will be organized under this directory.\n"\
3061               "Either specify a relative path to $SAGE_DIRECTORY/bin or a full path."
3062
3063HELP_FS_TYPES = "These are all the file types supported by the File Server.\n"\
3064                "All types have file extensions associated with them and all\n"\
3065                "the files are checked against that and matched to the correct\n"\
3066                "application for opening the files."
3067
3068HELP_FS_EXT = "List all the file extensions that match this file type\n"\
3069              "and can be opened by the specified application."
3070
3071HELP_FS_APP = "Specify the application for opening this file type.\n"\
3072              "When the application is started by the appLauncher,\n"\
3073              "the filename to open will always be the last argument."
3074
3075
3076### SageProxy help
3077
3078HELP_SP_HOST = "Specify the SAGE machine to connect to"
3079
3080HELP_SP_PORT = "Specify port on which SAGE is accepting UI connections."
3081
3082HELP_SP_PASS = "This password is only used by the SAGE Web UI to restrict\n"\
3083               "access to this SAGE session. Since no encryption is used,\n"\
3084               "this is more of a determent."
3085
3086
3087
3088#----------------------------------------------------------------------
3089#
3090#                           IMAGES
3091#
3092#----------------------------------------------------------------------
3093
3094
3095def GetCollapsedIconData():
3096    return \
3097'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
3098\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
3099\x00\x01\x8eIDAT8\x8d\xa5\x93-n\xe4@\x10\x85?g\x03\n6lh)\xc4\xd2\x12\xc3\x81\
3100\xd6\xa2I\x90\x154\xb9\x81\x8f1G\xc8\x11\x16\x86\xcd\xa0\x99F\xb3A\x91\xa1\
3101\xc9J&\x96L"5lX\xcc\x0bl\xf7v\xb2\x7fZ\xa5\x98\xebU\xbdz\xf5\\\x9deW\x9f\xf8\
3102H\\\xbfO|{y\x9dT\x15P\x04\x01\x01UPUD\x84\xdb/7YZ\x9f\xa5\n\xce\x97aRU\x8a\
3103\xdc`\xacA\x00\x04P\xf0!0\xf6\x81\xa0\xf0p\xff9\xfb\x85\xe0|\x19&T)K\x8b\x18\
3104\xf9\xa3\xe4\xbe\xf3\x8c^#\xc9\xd5\n\xa8*\xc5?\x9a\x01\x8a\xd2b\r\x1cN\xc3\
3105\x14\t\xce\x97a\xb2F0Ks\xd58\xaa\xc6\xc5\xa6\xf7\xdfya\xe7\xbdR\x13M2\xf9\
3106\xf9qKQ\x1fi\xf6-\x00~T\xfac\x1dq#\x82,\xe5q\x05\x91D\xba@\xefj\xba1\xf0\xdc\
3107zzW\xcff&\xb8,\x89\xa8@Q\xd6\xaaf\xdfRm,\xee\xb1BDxr#\xae\xf5|\xddo\xd6\xe2H\
3108\x18\x15\x84\xa0q@]\xe54\x8d\xa3\xedf\x05M\xe3\xd8Uy\xc4\x15\x8d\xf5\xd7\x8b\
3109~\x82\x0fh\x0e"\xb0\xad,\xee\xb8c\xbb\x18\xe7\x8e;6\xa5\x89\x04\xde\xff\x1c\
3110\x16\xef\xe0p\xfa>\x19\x11\xca\x8d\x8d\xe0\x93\x1b\x01\xd8m\xf3(;x\xa5\xef=\
3111\xb7w\xf3\x1d$\x7f\xc1\xe0\xbd\xa7\xeb\xa0(,"Kc\x12\xc1+\xfd\xe8\tI\xee\xed)\
3112\xbf\xbcN\xc1{D\x04k\x05#\x12\xfd\xf2a\xde[\x81\x87\xbb\xdf\x9cr\x1a\x87\xd3\
31130)\xba>\x83\xd5\xb97o\xe0\xaf\x04\xff\x13?\x00\xd2\xfb\xa9`z\xac\x80w\x00\
3114\x00\x00\x00IEND\xaeB`\x82'
3115
3116def GetCollapsedIconBitmap():
3117    return BitmapFromImage(GetCollapsedIconImage())
3118
3119def GetCollapsedIconImage():
3120    stream = cStringIO.StringIO(GetCollapsedIconData())
3121    return ImageFromStream(stream)
3122
3123#----------------------------------------------------------------------
3124def GetExpandedIconData():
3125    return \
3126'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
3127\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
3128\x00\x01\x9fIDAT8\x8d\x95\x93\xa1\x8e\xdc0\x14EO\xb2\xc4\xd0\xd2\x12\xb7(mI\
3129\xa4%V\xd1lQT4[4-\x9a\xfe\xc1\xc2|\xc6\xc2~BY\x83:A3E\xd3\xa0*\xa4\xd2\x90H!\
3130\x95\x0c\r\r\x1fK\x81g\xb2\x99\x84\xb4\x0fY\xd6\xbb\xc7\xf7>=\'Iz\xc3\xbcv\
3131\xfbn\xb8\x9c\x15 \xe7\xf3\xc7\x0fw\xc9\xbc7\x99\x03\x0e\xfbn0\x99F+\x85R\
3132\x80RH\x10\x82\x08\xde\x05\x1ef\x90+\xc0\xe1\xd8\ryn\xd0Z-\\A\xb4\xd2\xf7\
3133\x9e\xfbwoF\xc8\x088\x1c\xbbae\xb3\xe8y&\x9a\xdf\xf5\xbd\xe7\xfem\x84\xa4\
3134\x97\xccYf\x16\x8d\xdb\xb2a]\xfeX\x18\xc9s\xc3\xe1\x18\xe7\x94\x12cb\xcc\xb5\
3135\xfa\xb1l8\xf5\x01\xe7\x84\xc7\xb2Y@\xb2\xcc0\x02\xb4\x9a\x88%\xbe\xdc\xb4\
3136\x9e\xb6Zs\xaa74\xadg[6\x88<\xb7]\xc6\x14\x1dL\x86\xe6\x83\xa0\x81\xba\xda\
3137\x10\x02x/\xd4\xd5\x06\r\x840!\x9c\x1fM\x92\xf4\x86\x9f\xbf\xfe\x0c\xd6\x9ae\
3138\xd6u\x8d \xf4\xf5\x165\x9b\x8f\x04\xe1\xc5\xcb\xdb$\x05\x90\xa97@\x04lQas\
3139\xcd*7\x14\xdb\x9aY\xcb\xb8\\\xe9E\x10|\xbc\xf2^\xb0E\x85\xc95_\x9f\n\xaa/\
3140\x05\x10\x81\xce\xc9\xa8\xf6><G\xd8\xed\xbbA)X\xd9\x0c\x01\x9a\xc6Q\x14\xd9h\
3141[\x04\xda\xd6c\xadFkE\xf0\xc2\xab\xd7\xb7\xc9\x08\x00\xf8\xf6\xbd\x1b\x8cQ\
3142\xd8|\xb9\x0f\xd3\x9a\x8a\xc7\x08\x00\x9f?\xdd%\xde\x07\xda\x93\xc3{\x19C\
3143\x8a\x9c\x03\x0b8\x17\xe8\x9d\xbf\x02.>\x13\xc0n\xff{PJ\xc5\xfdP\x11""<\xbc\
3144\xff\x87\xdf\xf8\xbf\xf5\x17FF\xaf\x8f\x8b\xd3\xe6K\x00\x00\x00\x00IEND\xaeB\
3145`\x82'
3146
3147def GetExpandedIconBitmap():
3148    return BitmapFromImage(GetExpandedIconImage())
3149
3150def GetExpandedIconImage():
3151    stream = cStringIO.StringIO(GetExpandedIconData())
3152    return ImageFromStream(stream)
3153
3154#----------------------------------------------------------------------
3155def getCheckedData():
3156    return zlib.decompress(
3157'x\xda\x01\xaf\x01P\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x17\
3158\x00\x00\x00\x16\x08\x06\x00\x00\x00+v\x07\x05\x00\x00\x00\x04sBIT\x08\x08\
3159\x08\x08|\x08d\x88\x00\x00\x01fIDAT8\x8d\xc5\x95Mj\xc2P\x14\x85\xbf\x17\xad\
3160\xd6\x1fp\t\x82#\xc76\x11\x91B\xad\xa3\x16\xa4\xa8\x9d\xb8\x10\xe9*\xa4\xd3"\
3161\xddA\xd1\x06;\x97\x96\x8eK7\xe3@1!\xb7\x03m\x88&\x9a\xa8)\xbd!\x83\xf3\xee\
3162\xe1{\x87\x97\xfb\x08JK\x10\xd7kL\x0c\xf1\xea$1T\xdd\xac\xc9\x92\xa5o]\x8b\
3163\x03\x1e\x04\x06NK^5uqp\\]\xa4\xc8\x17\xdf\xae>)\xb9\x17\x8c\x03\xe3\x8e\xa9\
3164\xbc\xfd\xa3\x92\xebfE\xbc\xba\xa0\nL\xef\xdf\xd5\xb6\xcfM~\xf9Z\x97\xbaY\
3165\x93mCh\tL\xdb~\xb0\x0b\xd7\xcd\x8a,\xd4\x82%K\xc260\xcc\x8b\x8d\xbef\xef>\
3166\xd9d\xcd\xac\x8a\x8d\xed.\xa4%\xbd\x13*\xac\x9e\xdf\xeaez\xf4;\x0f\x81\xa9W\
3167\x1b{\xc0\x00\xb6\xd8\x81F/\x14\xc0\xb1\x1c\xfa7\xbb\xc1\x10\xf0A\xe7j\xbe\
3168\xa1\xb7\xc7\r\xa0q\xde`\xd0y\xdc\x0b\x86\x1d\xa3\xd8}k\x0b@{t\xe7\x03;\x96\
3169\xc3\xe06\x1c\x0c\xa0\x8c\x89\x11mB\x04J\x94x\xe9\x8e"\x81\x01\xb4\x9c\x95\
3170\x8bd,\xaa\xe2A`\x00\xad\xac\x95\xf7\x1a\xc4\x12\x8c3\xc3w\xfb"\xc1\xc3\x0cz\
3171V\xe7\xa95<\x18\x0c\xa0\x94\x96\xf0]gX%\xd6\xb3:\xc3\xd6\xf3Q`X\'O\x91\xf25\
3172\x9a\x99\xe6I`X\'\x07\xb8\x1e_\xc9L\x9b\x01\x90\'\xcfG\xe7\xf3$\xf0\x06\xfc/\
3173*\x96?\xd1\xbf\xc0\x7f\x00\x07#sW\xd1Zw\x10\x00\x00\x00\x00IEND\xaeB`\x82\
3174\x03\t\xb8>' )
3175
3176def getCheckedBitmap():
3177    return BitmapFromImage(getCheckedImage())
3178
3179def getCheckedImage():
3180    stream = cStringIO.StringIO(getCheckedData())
3181    return ImageFromStream(stream)
3182
3183#----------------------------------------------------------------------
3184def getUncheckedData():
3185    return zlib.decompress(
3186'x\xda\x01\xe4\x01\x1b\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\
3187\x13\x00\x00\x00\x16\x08\x06\x00\x00\x00"\x9d\xa7\x7f\x00\x00\x00\x04sBIT\
3188\x08\x08\x08\x08|\x08d\x88\x00\x00\x01\x9bIDAT8\x8d\xb5U;O\xc30\x10\xbe\xb3\
3189\xa9H\x93\x896\xc9\xd6f\xec\xd0\xb1]`\xe8\x06##\xfc\x84\xfe\x1d\xf8;\xb0C\'\
3190\xd8*\xb1u)\x12Q\x1f\x03mB\x1f\xc91D6\x8e\xed\x14\x89\xc7\'EQ\xee\xce\x9f\
3191\xbf{\xd8Ad\x1c\xfe\nL7\xe4\x94S\x1a\xb5\xe9\xd0\xa24jS\x1a\xb5i_w\xe8\xf5\
3192\xfaJ\xc6\x96\xc8\x84c\xddl\x1eTP\x8bc`\xab\x15d\xae\x0b\xcer)\xed(\xd2\xdc4\
3193\x1b\x04\x00\xc0\x93\x04X\x92\x14;!C\x95d_wH\xf8\x00\x00v\xbe\x0f<I\xe0(\xfd\
3194\xc0\x92\xb2\xe3\xf9\x02\xb9\x12h\x03\xb3\xf8\xe3\xcb\xcb\xaf\x0fd\\>\x84H\
3195\xfa\xb3\xeev)\xf3<\xc3\xbe\r\x02R\xd7"\xe3\xe5\x9a\xe5\xaek\xec\xec\x8e\xc7\
3196\x86\xa2\x9d\xef\xc3\xba\xd73\x95\x97\x82\xc2\xd0J\xa8\xe3=\x8a\xa0qw\x8f\
3197\x86C\x97\x8a\x8c\xc36\x08\x8c\xb4\x08\x912\xcf\xa3y\xbfo\xa4gMSUXe\xf7\x9f\
3198\x9eME\xb64\x05jql\r\xce\xbe)\x81A\xb6i6\xa86\x9bY\x83\x9d\xc9\x04\xd4\x89\
3199\xd7\x81\xea\xd9\xd4\x87\x12\xa0\xe8\xb0n\xd3\x87\xd9\xaa\xccF\xa4\xbe\x05\
3200\xaa\xd41\x80"\xb5\x9cr#\xe0e8\x94\xcdP\tOF#X\\\x9c\x1b\xf1\x0c\xa08\x8f6ton\
3201K\xe9\x08\xc2\xaaF 2\x0ei\xd4&\xb5\x83,IJuY\x9f\x9d\x12\x9bN%\xd1\xb6\xd5\
3202\xb2\x0e-\x13\x01\x99\xeb\xc2.\x0c\xad\xbbz\x0f\x8f\x08P\\MUD\x92l\xdbj\xc9\
3203\xdapM\x95@\xda\xe9\xc0\xdb`PI$\xd3\x14\xd8\xd7\x1d\x12w\xd3O\x80\xff\xfa\
3204\x0f\xf8\r>\x01lo\xba\\\x08x\xd9{\x00\x00\x00\x00IEND\xaeB`\x82\xd6d\xd4\xb4\
3205' )
3206
3207def getUncheckedBitmap():
3208    return BitmapFromImage(getUncheckedImage())
3209
3210def getUncheckedImage():
3211    stream = cStringIO.StringIO(getUncheckedData())
3212    return ImageFromStream(stream)
3213
3214
3215def getRunBitmap(checked=False):
3216    if checked:
3217        bmp = getCheckedBitmap()
3218    else:
3219        bmp = getUncheckedBitmap()
3220    return bmp
3221
3222
3223#----------------------------------------------------------------------
3224def getStopData():
3225    return \
3226'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x06\
3227\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
3228\x038IDATH\x89\xa5\x95]o\x1bE\x14\x86\x9f\xfd\xb0\xd7\x1fk\xc7\x9441m#\x12(T\
3229\x14\x81PDS\xae\xe0"w\xdcrU\xa9\xff\xaf?\x01n\xb8\xa7v\x89T\x90\xa8\x84R@\
3230\x04Gi\x9a\xb8q\xfc\x91\xf5\xec\xcc\x1c.v\xd7\xd9\x8d\x137\x88W\xb2\xb4\xda3\
3231\xf3\xbcg\x8e\xcf\x9eq\x1c\xd7\xe3:\xeav\xb4\x008\x0e\x88\xc0\x83-\xdf\xb9\
3232\xce>g\x91A\xb7\xa3\xe5/{\x82\t4\x81x\x94+\xe71\x15\x81V\x0e\xebnk\xa1\xd9\
3233\x9c\xc1\xde\x9b\xb1\xbc\xda\r\xf8m\xda\xa7\\\xb7l\xdc\x08x\xbf\x15P/{TK\x1e\
3234\x0e`E\x88\xb4\xe5$\xd2\xec\x9f*\xf6\x8f5k\xe6\x1d\xbe|0o4g\xd0\xedh\xf9\xc5\
3235\x1e\xb2\xd5.\xb1\xbe\xda\xa4^^\\B+p\x1aiv\xf6\xc6\xb4FKs&\x05\x83g]-\xcf\
3236\xcd!_\xaf\xd7\xf8`9L\x17,\xe4\xcf$\x02\xcfzC\xbc\xc3F\xc1df\x90\x87\x7fx3\
3237\xbc\x1e\xf5\x12u\xff)\x9a\xb8Y\xe0\xb99\xe4\x8b\x15\xff\x7f\xc1\x016o\x85\
3238\x9c\x84\x03:;S\x01\xf0!\xa9\xfb\xcb\xd2\x11\x1b+\xcd\xc2b}t\x8c\xff\xea`!P\
3239\xaf\xb6\xf1\x97\xdf\x05\xc0X\xc1\x08\xdc\xbd\x19\xf0\xfa\x0f\x8f\xfe(\x12\
3240\x1f\xe0\x85\xeaso5\xa0U\xf5\x0b\x9b\xbd\x1f\xbe\x87\'O\xe6\xa9\x9e\x07\xc6$\
3241\x8f\x8f\x1e\xc1\xe3\xc7(#\x00\xc4\xc6\xd2\xac\xf8\xbc\xb4Sv\x7f\xf7\x93\x12\
3242\xc5\xbe\xe6V\xb3|\xbd\x1a\xe4\xe0\x00N\x18"\x92\x80\x93_b$K\x11\x00n\xb7\
3243\xa3\xa5\x1e\xb8\xb4*\xfe\xa5\xbc\x194S\x0e\x0e \xa3\x11\xc4\x8a\xd8\x08\xb1\
3244\x11\x94\xb1(cy\xaf\xe12T\x1a7k\xc3\x8a\xefr\xa5.@/J\xa6S\x00\x94\xb1\xc4V\
3245\x88\xadP\xf6\x1dFg\x86Y\xda\x9e{\xcd\x86\xcf+w\xb2\x0cn\xd2\x12\xa9dt\xe1\
3246\x8a\xa4IZ\xf9\xef\x06\xe9\xc9\xecT\xcd\xe0\xb1\xd6\xc4Z32\x10V\xbd\xe4OVc\
3247\x97H\xdb\xb7fyY\xcc\x99L\x00\x88\xa61\xb1\xd6\xb3\xec\x87\x13KX\xb7\xb8\xd9\
3248$<\x1e\xc7I6\x92\x9cF\x94Zl\x92v\x93\xd4j\xa8\xd4$\x83[\xady3H*\xe2\x03\xdc\
3249\x0fn\xb0\xfbz\xc0\xed\xa5\x00\x93\xb6\\\x8d\xa4\x05\xcd\'\xf7\x17V\xc9\x05\
3250\x94["\x8a\xe2\xe2\xfb\xbe\xe6\xe1W\xcb\x8e\x0f\xc9\xa0\xda?6\x1c\xb4\x15\
3251\xad\x8aOl\x84\x01.\xcd\xedm\x9c\xedm\xecT\x156\xab\xc9\x04\xe5\x96(\xdb\x04\
3252\xda\x93\xe0<f\x85\xbfO\x1c\xee\xac\xd5\x81\xdc\xb0\xeb\xecL\xa5\xc7)\xdf|\
3253\xdcH\x16\xa6]\x91)\xeb\x0e\xa0Pk\x9b=\xa7k\xe3\xa9\xa1\xf7\xa7\xc3w\xdf\xb6\
3254\x8b\xc3nk3p\x9a\xaa\xc6\xce\xde\x18el\x01\x9c\xef\x8eXk\x94\x16\xa2(\xc6j\
3255\x8d\xb22\x07\xff|\xf3\xfc\xea+|\xbe\x8d\xb2\xcfpP\xe1g\x19\xf3\xe9Ju.\xdbL\
3256\x198\xaf\x0c~{C\xf8\xa8\xdd\x9a\xbf\x0f\xf2\xfa\xf1\xa7\xb1\xf4\xf4\x98{k%\
3257\xc24<\xba\xf01\xfb&\x9e\x81\xf7\x06%\xdc\xbe\xe6\xceZ\x9d\x87\x9f5\xae\xbe\
3258\xd1\xf2z\xfa\xebP\x0e\x8e\x84A)\xa2QshT]\x1a\xb5\xa4\xa2\xc3\x89exf\x19N\
3259\x84\xfa\xa9\xbd\x14\xfcV\x83\xbc\xd1h\xec2:3\xe8\xb3\t\xe3\xa6\xcbR\\!\xacz\
3260\x84u{%8\xd3\xbf\x07\xf8\xd8d\xb6\xf93\n\x00\x00\x00\x00IEND\xaeB`\x82'
3261
3262def getStopBitmap():
3263    return BitmapFromImage(getStopImage())
3264
3265def getStopImage():
3266    stream = cStringIO.StringIO(getStopData())
3267    return ImageFromStream(stream)
3268
3269
3270#----------------------------------------------------------------------
3271
3272def getSageIconData():
3273    return zlib.decompress(
3274'x\xda\x01s\x06\x8c\xf9\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
3275\x00\x00 \x08\x02\x00\x00\x00\xfc\x18\xed\xa3\x00\x00\x00\x03sBIT\x08\x08\
3276\x08\xdb\xe1O\xe0\x00\x00\x06+IDATH\x89\xb5V[oU\xc7\x15^k\xcd\xec\xbd\xcf\
3277\xdd\xe7\xd8.6\x17\x03\xe6j\x0c\xae0\xd4Ih\xa0\t)A\xa8IT\xd4(/\x95\xfa\x90\
3278\xa8?\xa8}\xe8[[\xa9J\x1b\xb5\xaa\xa0JS\xf5!j\xa9Dn$\x90\x00\xa1\\\xe2\x10HC\
3279\x08\x81\xe3\xdb\xf1\xf1\xb9\xec=3\xeb\xeb\xc3\x01\x83\xaa \x15J\xd7\xd3\xd6\
3280\xec\xf5}\xeb2#}\x1f\xef<\xfc\xc3\x85\xc5E\x80\x0103=\x8a\xe8Q1\xa3\xaf\\\
3281\xb6\xf5\xd9\xd9f\xab\ra\xc1#!\xbf\x1d\xca\xc4\n\xe7\x9c5&"#L\xa4 \xe2GT\xa4\
3282\xb7\x0b\xc3\xc6D\xc2\xcc\xacPU\x00\xcbC\x88>\xf0\xae\x96!\x02\x02\xa0\xaa\
3283\xac`f\xdb;ef\xb9\xf3\x9b\x88\x88\xf1\xc0\x1b\xbb\x07b\x98\x94\x99@Dd\x97\
3284\x13\x94I@\xbd>\x96?\x1e`\x82\xfb`\xe5\x1b\xb3\x1f|C\xf7\x85\xd8\xe5\xa7)\
3285\xca\xbd1{\xa3<D,\x03EY\xefPXU\xcd\xb2,\x80X\x19\xc2\xacP\xa3y\x9bx\n>c1\nR(\
3286\x91\xd1|R\x88HR\xcd\xd2,\x18\xcf\x1c\x8b@\x88T\x95l\x12G\x1c\x1a\x9d\xae\
3287\x10\x1bc,1\x14\x1d\xc7\xc5\xa2\xb7\n\xfa\xf6\xb6M\xc3\xab\xfbE\x08\x81D\x08\
3288dO\xbc\x7f\xbeT\x88\x1f{|w1F1.\xe5\xe2|\xa5R\xbat\xf9\xf3#o\x1e\x13\xe2\xa9\
3289\x9d;\xd6\x8d\x0e\xda\xd8\xb3\x18\x0b\x13\xe7\xe3k\x9f\xd5?<s\xf5\xe5\x1f\
3290\x1cZ\xbfj0sh\xb4\x17\x9a\xdaqi\xe7\xd2\xf9\xaf\xed\xf8\xd8\xc8\xd43#Jj\x94\
3291\x85\xa2\x8c;L\xf6\xe2\xf9\xca\xe4\x13\xeb7\xee\xe8\x8b\x9a\xc5\x88L_\xb9rp\
3292\xcf\xde\xf7\xce]|\xf5\xf57\x0e~o\xcf\xae\xef\xaf\x0c.\xa3n\xc1S\xd0\x94:iV\
3293\xac\x16\x96\x16\x9b?:\xf4tl\xf5\xe4\xb9K\xca1[\xca\xd7dh\xc5\xb8]72\x08\xf8\
3294so\xcd\x9d97mH\x9e}~\xe7\xc8\xe6\x1c3\x95+9M\xc3/~y\xa4\x99v\xb6\xac]\xfd\
3295\xc5\x17\xf5\xa3\xc7\x8e\xe5l\x92\x14\xc0\xd0O?\x9a;~\xfc\x12\xc7v\xb86\xf0\
3296\xe2\xe1}\x0b7\xafv\x83{\xef\xd4\xc7\xcdf\xf3g\xbf\xf9C)_0l\x7f\xf2\xd3\xef\
3297\x96\x8ay\x0b1b\xf9\xe6\xd7\x0b7\xeau\x11\xb9\xf0\xd1\xf5\xfa\x8d\xf2B\xb3\
3298\xf9\xee\xf1O\xca\x85|\xa3\xd5.\x98h\xdb\xe6\xedo\xbf\x7fz\xfa\xf2\xd5|\xb1\
3299\xf4\xce\x07\xd3&\xe1\xe9K33\x8d\x19k\xa3\xe6\xfc\xdc\xc9S\xfd\x93c\xdb\x0b\
3300\xb9w\xcf~2\xbdfx\xd8\x10\x03!(\x03\x89\x04\xb6\xce)\x14{\x0fn\x98zr\x84-\
3301\xa7i\xb8rqv\xa1\xd1\x18\x1e\xac\x8e\x8d\r\x8fO\xad\xc9\x1b\xfa\xce\xd8\xc4\
3302\xc7g\n\'.\x9c\xb5\xaa\xc5\\T\x1b\xccW\xfaK\xcf\x1d\xd8[\xae\x19\x0f\xef\x1d\
3303\xb5}\x8b\xc4$69\xf0\xc4\xee\\\x91S\xb4<\xfb\\\x9f\x0f\x99\xb7gNMG\xf1\xa6\
3304\xd2\x80$U\x03p\xae\xea\x9e\x1cY\xdbl\xa5\xbb\xf7\x8d\x14k\xc1\xb5\x12\x84\
3305\xee\xec\xd2\xc2\xee\x89\x89\\\xf2z\xd6u\xb5\x95\xa55\xa3\xfd\x17N\xd6GG\x87\
3306\xca\xab\xad\xcbP.\x13\xb9\xd8{mv\x97\n\xe5R\xb5Th8\x9f\x89v\x9b!\x0eb\x17\
3307\x96\xdao\xfc\xe9\x84\xc9\x19\x86\xf1]=ph\xd7\xda\xed\xe9\xd0`\x1f\xb1K\xeb\
3308\xf1\x1f_{;\r\xd9\x96u_\xbd\xf2\xd2K\xc5b\xf1V\xeb\x16\xbcs!\x03\xeb\xef~\
3309\xff\xb7|>\'0/\xbf\xf2,\x8b\xf3\xdd\xf6\xd6\xb5\xeb\xff\xfa\xe6[\xbf:r4\xc9\
3310\x17\x0c\xc8{_-W\xed\xfe\xa7v\x8el\xa8\xfc\xf6\xd7\xc7\xaf~u\x9d\x9c.-\xb6\
3311\xd8TI\xc9\xb2\xb4:Y\xbd\xb1H\xc6\xa7\x99k,\xce\x97\xf3\xc9\xc0\xfa\x8d\x99k\
3312\x82-\xac4\xe6\x1a\x8b\xb9\x0e{\xb26:}\xfab\x9ae\xe3\x9b\xd7\xbf\xf3\xe1\xd9\
3313\xa5F\xb3\xeb2\xf5\xe6\xc7/\xee\xcf\x15c\x1bG\xe4\x93\xf6\x9e\xa7\xc6\xc7n\
3314\x8e\x90\t\xabF\xfb\xbc\xf76\xc7.\xa0\\\x8b\xf7=\xbe]A\x93[v\xd4\xe7\xe7\xb6\
3315n\xdc\xb4n\xc5\xd0\x07\x9f\x9d\xd5\xd0\x99\x9cZ\xb5\xa2V\xf4\xc4B\xbe\xd6_\
3316\x99\x99\xef\xd4\xaa\xb54M\'\xb6n\xde\xb7w\x8a\x89l,\xab\xb6\x95b\x9fX\xcb\
3317\x11A6\x8c\x95\xedx\xd5\x91\x0b\x81\x96f\xa2sg\xff900\xd1\xbf!y\xe6\x85\x89\
3318\x1c\x15\x1f\xdb\xb4\xeb\xe8\x9f\xff^-\x15\'\xb6o\xf9\xcb\xb1\x7f\xb4g\xc7j\
3319\x03\x18ZYVE\xce$\x85bd\xc0CC+>\xbdv\xfd\x85\xfd\xfb\x922\xcda\x0e\x94:\xe7\
3320\xae\\\x9c\xe3\x89\xc3\xcfC\x19\xc1\x89X x\xefgg\x96Zi;_.\x0c\rT\xe0a\xa2d\
3321\xcf\xe4\xe4\xf4\x95\xab\x95B~\xdb\xe8\xa6\x9f\xbf\xf6j\xb5\xd4W\xab\xf6\x19\
3322\x06\x04\xd6\xc4I\x92\xa8\xd3\\\x12\xb9\xe0\xab\x95\xf2\x97\xd7o\x92U\xf8\
3323\xe0\x95\xd2\xd4\xd9\xf9\x85\xa5\xb9\xd9\xf9 \xc4\x10""\x811b\x12\xe9v\xbb\
3324\xff\xfa\xbc\x1d\x98XH\x9df!4\n\xc9|\xb3].\xe4;\xddv\xf3Z\x13\x86\x00\x108\
3325\x12\xb3rE\xed\xd6\xac:\xd7\xbd\x8c\xd0Z\xea\x1a&\x02\x8b\xc8`\xb5j\r\x8b\
3326\x8d\xd8\x08\x1b\x15\x00j\x84\xc0\x02\x80\x83\x89"\x16\xb1\x1an\xdc\xba\x01k\
3327\xeb\rJ\xf8V\x08\x81\x19\x12Y\x16\xe2@\xccL\x1cn\xd4\xe7\x8cae\x81\x0f\xb9(\
3328\x860\x00V\xa8\t\x16\xc4L\x96\x01\x05\x89\x11V\xcfdU\x94aY\xc8 \x80)0\xc4;O\
3329\x1c\xc8\x0b\x1b%0\x1b(D<\xb3\x81\x1a\x12\n\x04\t\xc2\x02(X\t\xe2`,\x91\xb5D\
3330D\x1c\x88D\r\x03$l\x94!j\x08\x80\x90\xb2\x12\x89h\xc4\xa4\xc2d@\x81\x95\x99\
3331\x05\x8e\x85\x03\x0c\x81\x8c\xa8\x80\x03\x91\xb0:\x82\x11\x10\x0c!\x12\x90\
3332\x04X\xe1\xdbbc\xf4\x1eW\xc1\xa0\xdb\xfa\'\xbd\x0e@$D\xb8\xabE\x02\x90\x10\
3333\x88\x08`"2 \xcf0`\xea!{TF,p\x1b\xa1\x82e9}\x08\xc9\xbcW\xd4\x96\xe5\x0c\xc0\
33347k\xf2CH\xe6\xfd \xf6?2\xeeZ\xa3\xff\xa1\xc6\xbd\xc6\xe2v\x01\x00\x01l\xf8\
3335\x8e\xf5\xb8G\xb5\xff[\xf6;\x10\x01\x05\x10\x11z\x97a\x01@X\x88\xa1\xa4|\xf7\
3336>\x1e\xb4\xfde\x88211\x0b\x83\x08\x80\r\xc1QP\x08K\xaf\xea\xa3\t(\x88\x15!8\
3337\xfb\xad\x81\x81(\x8a\xfeO\xf6\xbdZ\xa9\xfc\x1b\'\x9f`y\x8c\\\xb8\xf0\x00\
3338\x00\x00\x00IEND\xaeB`\x82e\xd2)\xef' )
3339
3340def getSageIconBitmap():
3341    return BitmapFromImage(getSageIconImage())
3342
3343def getSageIconImage():
3344    stream = cStringIO.StringIO(getSageIconData())
3345    return ImageFromStream(stream)
3346
3347def getSageIcon():
3348    icon = wx.EmptyIcon()
3349    icon.CopyFromBitmap(getSageIconBitmap())
3350    return icon
3351
3352
3353#----------------------------------------------------------------------
3354def getSageData():
3355    return \
3356'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01u\x00\x00\x01;\x08\x06\x00\
3357\x00\x00"\xaf\x11\xd4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00 \
3358\x00IDATx\x9c\xec\xbd{x\x1c\xe5y6~\xcfaO\xda\x83v\xad\xf5J\xb2\x0e\x16\xb21\
3359\x08[\x8aA\x96c\x08\xf6U\x88\x82\x81\x12N\r\x816!?\xd2\x03Iq\x93&_RZ\xda\x92\
33604\x906m\x9a4|I\xe0KH\x9a\xf2\x85\xf0\x05Jj\x08%\t6\x0e\xa6\xb6\x03\xc4\xb6\
3361\xc0\xf8$\x03\x96,\xeb`I\xeb\x95w\xb5\x07\xedi\x0e\xbf?F\xef\xec\xcc\xec\xec\
3362jw\xb5+\xad\x94\xb9\xafK\xd7\xca\xab\xdd\xf5\xec\xcc;\xf7\xfb\xbc\xf7\xfb<\
3363\xf7C\xb9\xba\xbbE\x180`\xc0\x80\x81e\x01z\xb1\x0f\xc0\x80\x01\x03\x06\x0c\
3364\x94\x0f\x06\xa9\x1b0`\xc0\xc02\x82A\xea\x06\x0c\x180\xb0\x8c`\x90\xba\x01\
3365\x03\x06\x0c,#\x18\xa4n\xc0\x80\x01\x03\xcb\x08\x06\xa9\x1b0`\xc0\xc02\x82A\
3366\xea\x06\x0c\x180\xb0\x8c`\x90\xba\x01\x03\x06\x0c,#\x18\xa4n\xc0\x80\x01\
3367\x03\xcb\x08\x06\xa9\x1b0`\xc0\xc02\x82A\xea\x06\x0c\x180\xb0\x8c`\x90\xba\
3368\x01\x03\x06\x0c,#\x18\xa4n\xc0\x80\x01\x03\xcb\x08\x06\xa9\x1b0`\xc0\xc02\
3369\x82A\xea\x06\x0c\x180\xb0\x8c`\x90\xba\x01\x03\x06\x0c,#\x18\xa4n\xc0\x80\
3370\x01\x03\xcb\x08\x06\xa9\x1b0`\xc0\xc02\x82A\xea\x06\x0c\x180\xb0\x8c`\x90\
3371\xba\x01\x03\x06\x0c,#\x18\xa4n\xc0\x80\x01\x03\xcb\x08\x06\xa9\x1b0`\xc0\
3372\xc02\x82A\xea\x06\x0c\x180\xb0\x8c`\x90\xba\x01\x03\x06\x0c,#\x18\xa4n\xc0\
3373\x80\x01\x03\xcb\x08\x06\xa9\x1b0`\xc0\xc02\x02\xbb\xd8\x07`\xc0\x00\x01MQe\
3374\xf9\x1cA\x14\xcb\xf29\x06\x0c,E\x18\xa4n`Q\xa1%r\x96a\xe6\xf5y\x1c\xcf\xab>\
3375\xd3 \xf8\xfc(t"5\xce\xe3\xd2\x81A\xea\x06\x16\rz\x84\xce\xf1\xbcL\xec\xac\
3376\xc9\x94\xf3\xbd\\:\r\xd6d\x02\x97Ng\x9eS\xbc\x97\xe3y\xf9\xff0\x08)\x1b\xc5\
3377\xae\x8a\xc8\xeb\x8dsY\xfd0H\xdd\xc0\xa2@I*\xca\xe8\x9ce\x98\xbcd.\xbfn\xf65\
3378\x85\xbc\xd6 v\tzD^\xcc\xca\xc8X\x05-\r\x18\xa4n`\xc1A\x88AK(Z\x82\xb6\x9a\
3379\xcds~V"\x95R\xbd\x9fD\xeeF\xc4\x9e\x81\xf2|+W3z \xd7@\xb9\x02\x92\xff\xa68\
3380\xa7\xe4\xb3\x94\xf8]=\xbf\xd5\x06\x83\xd4\r,(r-\xfb\t\x99h\x89\xdcf\xb5\xe8\
3381\xbe>\x9eHf\xbd>\x91J\xa9H)\x17\xf9\xfc. \xd7^E!\xd2V\xbe\xbfs\xe9\xb4\xea\
3382\xb3\x94\xe7\xf6wy\xe2\xac&\x18\xa4n`Q\xa0G.J\x82\xd6\x92\xb9\xddfC,\x1e\x97\
3383\x1f\xbd\x1e7b\xf1\xb8\xfc\xf7x")\xbf_I\xee\xe4\xff"\xd2\xc1r%\x9d\x9c\x93e\
3384\x9e\xd5P!+!\x02\xb2"\xd2\xeec\x142i\x96+\xab\xa9\x14,\xd7\xeb\x9d\x0f\x94\
3385\xab\xbb\xfbw\xef[\xff\x0e#\xdf\r\xa6\xbd\x01\xc42\xde\x10\x14E\xa9H\x95\xa6\
3386(\x95~N\x08FI\xe6v\x9bm\xce\xcf\x8d\xc5\xe3r\xd4\xae\x94b\x08\x08\x01\x11\
3387\xe2)\xf7M\xae\xfcN\xa2(B\x10\x04\x08\x82 \xff\x9d\x17*\x7f{14\x854\xc7I\xc7\
3388C3\xaa\xe7\xa5\xe72\xe5(&\x96\xcd\xda\xb7\xb0\x9a\xcd9WD@fU\x04\xa8\xcf\xb1\
3389\x9eD\x93\xe28\xd5y \xdf_\x10xp\xb3\xc7\xb8P`Y\x164\xcd\xa8\xce\x03M\xd3\xf2\
3390X\x04\x96\'\xe9\x1b\x91\xfa\xef\x00\x04Q\x84(\x08Hs<\x04!\x13U\x91\x9b\x8ce\
3391\xb3\x87\x81\xf2f\xa8\x148\x86\x01\x95N\x83\xa2(9\x9b\x05\x90\x88]I\xe8.\xa7\
33923\xeb\xbd\xe1H\x04\xa3\x93~Yf\x11D\x11\xa2(\xca7lV\x84Z\x86h]\x10E@\x14\xc1\
3393\xf3<xA\x94\x89\x8aeY\x98X\x16&\x9a\x81\xd3\xe1\x98\x93$\x17\n\xb9&E\xe5\xf9\
3394t;\x9c\x08E#\x08G"\x00\xd4\x93$ ]\x0b\xbd\xef\xa2\x95\xba\xa4\xf7%\xc0\xb2,\
3395\xac\x16\x0b\x9c\x0e\x07<.\xa7|\x1c\xe4\xfft;\xb2\xafe%\x10\x8aJ\xdf\'\x1c\
3396\x89\xc8\xdf)\x91J!63\x83\x99x\\\xben4\xcd\xc0lbAQ\x14\xa8E\\Q\x94\x13\xcb\
3397\x8e\xd4i\x8a\x02/\x08\xe08N\xbe\xf1\x96"L,\x0b\x8b\xd9\\2\x01\xd1\x14\x85d*\
3398\x854\xc7\xc1\xc4\xb2\xa8u\xb9\xd0\\\xefCk\xe3*\xb4\xaej\xc4\nW-\x00\xa0\xde\
3399d\x82\xbd\xc1\'\xbf\xcf\xdb\xe8@`<\no\xa3\xa3,\xdfC\x0b\xf2\xd9\x81\xf1\xa8\
3400\xfc\\l\xc2\x8f\xc9\xd9\xa8opt\x14\xe1hT\xbe)\x95 \x84\xf0\xe6\xc9\x93\x08\
3401\x04\x83\x00 \xdf\x88\xca\x9bR\x10E\xa48N\x97\xdc\x8bE:\x9d\x96\xa3`\x13\xcb\
3402\xc2n\xb3ae\xdd\n4\xfa|\xe8Z\xb7\x0e+\\\xb5hoi\x82\xb7\xd1\x01/\xed\x81\xb5\
3403\xae\x06\xde\xda:\xf9\xfd\xf5\x1e\x1f&\x83\xfeE{\x9c\xeb8\x00\xe0o\xbe\xfe-<\
3404\x7f\xfc\x84\xfcZ\xe5\xca\xa3\xce\xed\x86\xd7\xe3\x96\xaeS<\x0e\x9b\xd5"K]\
3405\x89T\n\x91h\x14&\x96\xc5\xfa\x8b\xd7\xe2\xea\xeenlZ\x7f\x19\xbc\x8d\x0e4\
3406\xafl\x9a\xf3<T\x02\xda\xcfV\x9e\x03\x00\x18\x1d\x1cA@\x08\xe2\xec[\x83x\xfd\
3407\xcc\x19\x1c\x7f\xef]\x9c>;\x8c\xe8\xcc\x0ch\x9a\x81\xc5lZ\xf2\xe4\xbel\xe4\
3408\x17\x9a\xa2\xc0\xf1<\xa2\xb1\x18\x00\xc0\xe9p\xc8\x03\xd2\xe5t\xc2\xedp\xc2\
3409\xe5\xa8\x0cQU\x02\xc7\xdf{\x17\xfd\x03\x83`J$\xa5\x99x\x1c&\x96\xc5\r\xdb\
3410\xb6\xe1\xf6\x0f}\x10\x1b\xbb.\xc1\xfa\xb6\x8e\x9c\xaf\xd7#\x82B\xa0|O1\x843\
3411\x17&\x83~\x8c\x0e\x8e\xe0\xd4\xd8\x08\x06G\xc6p\xe4\xd4)\x00\xc0\xf0\xf8\
3412\xb9\xa2\xce\x8b\x92\xd8\x95\xdao\xbe\xc9\x92\xa6(\xcc$\x92H\xa5\x92\xb0\xd7\
3413\xd4\xa0uU#\xde\xdf\xf5>\\y\xd1E\xf8\xc0u[u\xcf\xa3\xf2\xbc\x05\xa6\xa7\xe4\
3414\xdf\x13S3\xb0\xd6\xd5`\xf4\xfc\x18\xbc\xb4\x07\x01!\xb8`\x8f\xcd+\x9b\x90\
3415\x98\x9a\x91\x8f\xc5ZW#\x1f\x0f\x00\xfcf\xf7~<\xf2\xf3\xe71|n\x1c\x14M\x83\
3416\x02\xb2\x08M\x10\x04\\\xda~\x11\\NgV4\x1f\x8d\xc5\xb0\xad\xa7\x07\xf7~\xe2\
3417\x16l\xef\xe9\xcdy>\x94\xe7Dy<\xcac\xd5C0>\x05\x8f\xad\x0e\x81\xf1(\x18wR\
3418\xf75Zxlu\xaa\x7f\x93\x89\x16@\xd6$C\xd0\xd7\xd7\x87\x9f\xec\xde\x85]\x07\
3419\x0e\xa0\xff\xf4\x00lV\xeb\xbc\x02\xaa\xc5\xc6\xb2!\xf5\x99\xd9M\xb3m\x9b{\
3420\xd0\xb5n\x1d\xae\xef\xdd,G\x0b\x95\x8a\n*\x89\x87\x1f}\x14_\xfa\xf6\xb7\xe1\
3421v\xb9\x8a\x1a\\4E!:3\x83\xe6\x86\x06|\xf5/?\x83\x8f\xdd|+\x80\x0cI\xbe\x17;\
3422\r\x00\x08\xc5B\x88Fc\xba\x9f\x11\r%\xe0p[u\xff\xe6\n: \xb4\x88\xa0G(\x84=Q\
3423\xf9\xdf\x00@\x8fP\xf2\xef\x00\xe0L\xd7\xab\xde\xcb\xb8\x93\xe0C\x16\xf9\x06\
3424%7\xa0\xf2\xc6\x0327\x9f\xf2\xba\x9d\x18\xea\xc7\x91\xa3\xef\xe0\x1f\xbf\xff\
34258\x86F\xc7`* ?\x9d\xc0\xac\x90\x97\xe6\xd2\xd6\x05A@4\x16\x83\xd7\xe3\xc1M\
3426\xd7\\\x83\xde+\xdf\x8f\xde\xadW\xc9\xc7B\xcec@\x08\xe2L`\x00\xd1h\x0c\x11q\
3427\x1a\xb1d\x0c\xf1d\x12\x9c i\xceI.\x05\x8e\xd7K\x0b4\xa9\x9e/\xf4\xdf\xca\
3428\xc7|`g5u\x86f\xc0\x0b<\x18\x9a\x01K+6\xa0-\x16\xd8-v\x1c\xf8\xd5Y\xbc\xf2\
3429\xda\xeb0\x9b-\xb0Zro\x98\n\xa2\x08GM\r.[\xbb\x06@F\xce\x00\x80\xbf\xfb\xd4\
3430\xbd\xaa\xf1\x15\x98\x9eBbjFE\xd2\xc1\xb8D\xe6\xe4\xba\x87b!\xd5\xe7k\xc7`4\
3431\x94\xc8y,!!\n\xea\x82\x03\xe2\ni\x95\x17N\x85u_\x97BH>O\xe4\\\xb2\x8c\t,\
3432\xcd\xc0\xc2\xda`\xb3X\xd0`]\x85\xe6\xfa&\\l_\x8b\xe6\xf6\x16\xd4{|81\xd4\
3433\x8f\x1f\xfe\xf49\xfc\xf0\xd9\x9f!\x95\xe6Pc\xb5,Ib_\xf2\xa4\x9eN\xa7\x11O$p\
3434\xedUW\xe2\x8fo\xbb5\xe7\rH\x06W5#\x14\x0b\xe1"\xef\x1al\xef\xe9-\x89\xd4\t\
3435\xa1_\xdc\xb6\x1aO>\xf40\xba\xbb\xbbqb\xa8\x1f\xbf:\xf4+\x9c\x9c\x18\x84\xc0\
3436\xc4@\xf3\xf6\xbc\x9f\xe12\xbb\xe4\xdf\xc9\rD]p\xa0\xd6\x9b\xfdZ&U\x0b\x9bO"\
3437\xe8\xb8\xdf"\xff\xae$~%\xc9\x03\x12\xd1k\xa3.\xbd\xe8\n\x80*\xc2"\xd7\xb4\
3438\xe7#\x7f\x80\x13\xef\x9d.;\xa9\x8b\xa2\x88x"\x81\x1a\xb3\x05\xb7_\xbf\x1d\
3439\x7f\xf1\x91\x8f\xa0\xbb\xbb\x1b\x804\xa1\x1c\x1b>\x8a\xfe\x89\x93\x08\x84/ \
3440\x96\x8af\x11\xb6\x92l\xf5\x88\xb5R`i38!\xa5"n@"o%\xec\x16\xe9\xba;\xa9Z\x8c\
3441^\x08\xe0\xa9\xffx\x05\x89t\x1a,M\xcf=\xbe\x04\n\xeb\xda[\xd1\xda\xb8\n\xc3\
3442\xe3\xe7\xe0r:q\xff}\x1f\xc7\xf6\x9e^\x99\xccG\xcf\x8fe\xbd\x8dD\xda\xda{\
3443\x8f\x0fY\x101M\xc2\x99\xaeG\xc44)\x8f\x13e\xa0\x10\xf6D\x11\xf7[\xc0\x9b\
3444\xa7u\x0f)$Du\x9f\x0f\xa7\xc2\xb0\xd9(\xc4\xe3"L\x96\xcc5\xe2geXN\xe0\xb3&M\
3445\xaf\xb3\x0e\x9bV\xf7\xe0\xaemw\x02\x00\x9ez\xe1y|\xf1_\xbf\x89\xc0\x85\x0b\
3446\xa8)`\xb3\xbe\xda\xb0\xa45\xf5\x99x\x1c\xde\x15+\xf0\xc8\x8e\x1d\xb8\xf7\
3447\xde{\x01H7\xe0\xdec{q\xf8\xec!\x04"S\xba\xd1R5\xe3t\xe0=\xd5R\xb6\x98H\x81D\
3448\xe8\x84\xd0w\x1d\xda\x83\x9f\x1ez\n\x1c\x9f\x86\x19n\x15\xa1\xdbl\x14L\xbcz\
3449\xd3\xcaM;\x10\x12\xa2p\xd3\xb32\x95\x17\x00\x1c\xb3\x8f\x12\x08\x91kI\x1c\
3450\xbe(\x1c\x0e\xbb\x8a\xc8\xc9#!re\x94\x0e\xe4&s\x00\xaa\xc8\x9d,\xdd\x03\xd3\
3451S\x08\x04\xd5\x91^\xa1 \xa9x\xda\rS\xb2\x07\x13\x89F\xb1\xa9s\x03\xbe\xfa\
3452\xd7\x7f.\x9f\xff]\x87\xf6\xe0\xb53\xfb1\x11\x9a@,\x19\x93\x89\x9b\xa5\x19\
3453\x15q\xe7C9\t]K\xdc\xb9\x9e\xcfG\xe8\x0e\x87\x1d\xbfy\xe67\x88\xc5\xe3`Y\xa6\
3454\xa0\xf1\xc5\x8b\x9c\x1c\x9d\xbb\x9cN\xfc\xf1m\xb7\xaa\x08=15\xa3+\xa3h\t]{\
3455\xfd#\xa6I\x00P\x11:\x00\x99\xd0m\xbe$\xa2:\x97;\x1f\xa1\xbb\xcc.\xa4\x11\
3456\x81\xcdF\x01PNx\xd2\n\x8a\xa1\x19XX\xe9|\xf1\x02\x0fN\xe0\x11\x88L\xe1\xc5\
3457\xb7\xff\x1b\x87\xcf\x1e\xc2\'\xb7\xfe\t>v\xf3\xad\xf06:\xf0\xf1\xcf\xfc=\
3458\xc2\xb1\x99\xbc+\x99j\xc4\x92%\xf5p$\x82\x8e\xb5kT\x11\xe9\xfe\xfe}x\xe3\
3459\xcc\x1b\xf22\x95U\\\xc0\xa5\x80X2&\xdf\x80\x04\x85fk\xa4\xd3i\x98X\x16_\xfd\
3460\xcb\xcf\xc8\x84\xfe\xe4\x1bO\xc0\x0c7jm\x92N\xaa"q\x9d\xfd\xe3\\7\x8b\x12JB\
3461\'\x11\x95,\xc38D\xe9\xf7\x11)R\'\x91\x18A\xc44\t7\xa4M7>d\x01t\x82 \xa2\xfb\
3462\xe6\x832Eo.\x98Y6+\xb7Z\tN\x10\x10\x8dFq\xfbu\xd7\xe1\xb1\x87\x1eD\xbd\xc7\
3463\x87\xbe\xbe>\xfc\xf4\xe4O1\x11\x9a\x90\xc7\x91\xf6\xba,$r\x919\x81\xcdbA<\
3464\x99\xcc"s\x00\xaa\xe3v8\xec8\x1dx\x0f\xa3\x93\x92\xdeMQ\x85\x9dG\x8a\xa2\
3465\x10\x0cKz\xfa\x9dW^%K.\x84\xd0\xb5{\x06\x04\xdaH\x9dL\xeczQ\xba\xd0"\xc25\
3466\xe2\x90\x89\x9d\x8c3\x98\xb3\xe5\x18e\xf0\xa1\x1c\xb36\x1b\x854"\x88\xc7EE\
3467\xb4\x0eY\x12\xd3\x82\xa1\x19y\xd2\xb5\xb0fL\x84&\xf0\xb5\xff\xfeG\xdc\xbd\
3468\xe5\x1el\xef\xe9\xc5#\x7f\x1b\xc5\x9f=\xf8e\x08\x82P\xd4\x98[l,\x9d#U`&\x1e\
3469\xc7\xa6\xce\rx\xf6\xd1o\xca\x04\xf6\xed\xdd\x8f\xe0\xc0\xe9\xfd\xf2\rha\xcd\
3470\x15]\xf6V\x1b\xe2\x89\x04\xee\xbc\xf1F|\xec\xe6[qb\xa8\x1f?=\xf4\x14\xccpg\
3471\x067\xefD\x9a\x89\xe4\xd4!It.G\xe9:p\xb8\xad\xaa\x08\x9d\x909\x90\x89\xb6\
3472\x1c\x0e\xbb\x9cM\xa3\xbcy\x81\x8c\xc6\xee\xb1\xd5eI0\x84\x10\x94\x1by\x80zs\
3473\xcb[[\x87\xf6\xd6\x16\xa4\xb9\xe22\x9a\xacf\xb3n\x85\xa4(\x8a\x88F\xa3\xb8\
3474\xe7\xf6\xdb\xf1\xd8C\x0f\x02\x00\xbe\xf7\xab\xef\xe3;\xbf\xfd.\x02\x91\xa9\
3475\xaa\x18G\xf9\x08\xddf\xb1\xc8D\xae\x17\x9d+\t\xddI\xd5\x82\xf6Q\xe0f\xcc\
3476\xe0\xd2\xe9\xa2H\x8a\xa6i\x04.\\\x00\x00\xdcr\x87D\xe8\'\x86\xfa\x01($2\xc5\
3477*\x8b@\xb9\x12\xe3C\xd2\xf11\xee\xa4<&\x9c\xe9z\xd5~\x0c!tW\xd0!\x8f3&U\x9b\
3478\xf3\xb8\xb4A\x88\x89w\xc2\xc4;e\t\xd1f\xa3\xe6\x9c\x10\x95\xb0[\xec`\x19\
3479\x13\x9e\xed{\x1a\'\x86\xfa\xf1\xb1\x9bo\xc5\r\xdb\xb6!\x91L.j\x01U\xb1Xr\
3480\xa4\x1e\x8eD\xb0\xb1\xe3R<\xf1\x8d\xafb}[\x07\x9e\xde\xf7\x0c\x9e|\xe3\t9\
3481\xca\xfd]"r\x82t:\r\x9b\xd5\x8a\xbbn\xff \x00\xe0?\x0f>\r\x00\xb3K\xd0\xcc#\
3482\xa0\xd6\xcc\x95P\xc9.:\xd0\xbb\xb9H\xa4\xae\x95]\x94$\xce\xb8\x93p\xdb\xdd\
3483\xaa\xa57\x89\xe0\xb4\xd0n\x98\x02R4\xa8$\xf6\xaeu\xeb\x8aNSM\xa4R\xba\x91z<\
3484\x91\xc0\xa6\xce\r\xf8\xe7\xfb?\x8fz\x8f\x0fO\xee\xf9\x89\x1c\x18,\xe6\n\x8f\
3485\x10\x91\x1e!\x11"\xcf\x17\x95kW\x15Dv\x11\xfc"\x9aWx\xe1\xb0\xdbU\x05R\x85\
3486\x80\xd44h3\xa4\xc8\xaaJ/\x83E)\xbd\x90k\xaf\x8d\xd4\x81\x0c\xa1;\x1cvyLi%>-\
3487r\x8d\xd5p*\x8c4#E\xeb\x00\xe4\xc7B\xc9\xdd\xc2\x9a\x91H\'\xf0\x1f\xfb\xff\
3488\x1d\x00\xf0\xd0\x17\xee\x83\xa3\xa6fIYM,)RO$S\xf0z<\xf8\xea_\xff\xb9L\xe8/\
3489\xbe\xfd\xdf\xb0\x9a\xac\x8b\xba<\xae$\n\xd2<\x05\x11m\xcdM\xd8\xb8\xb6\x0b\
3490\'\x86\xfa129%\xeb\xe7$z!\xbfk\xe1\xa6\x1d\xf2\x8f\x1e\x98T-\x1cnk\x96\xec\
3491\x02@\x97\xd0\x9d\xe9z\xd0#\x94|\xd3\xf2!K\xc1Z\xba\x9e\xf4B\x08\x9d\xe8\xea\
3492\xd7\xf7n\x86\xcdj-\x88\x94h\x8a\x82\xc3.\x9d\x07m\xa4\xce\xf1<\x1c55\xf8\
3493\xde\x03\x7f\x8bz\x8f\x0f\xdfx\xf6\xdfpd\xe2MXM\xd6\xaa\x08\x0c\xf4tr%\x91\
3494\x93H\x9c\xfc\\\x08%\x10\x08_\xc8\x8a\xce\t\xa1G\xa31\xd0>\n\xcd\xf5M\xb8\
3495\xe2\xb2\xcb\xe4\xdc\xfb\x82\x8f\x87e1<~.+%U\x19\xa9\xe7\xd2\xd4\x95\xd7[\
3496\x19\xa9\x03\x90\xa3u2\x8e\x94\xd1\xba,\xc1\xe8\x80D\xe9\xdaq\xeb2\xbb\xe4h\
3497\x9d\xc80d3\xb9P\xd8-vL\x84&\xd0\xd7\xd7\x87\xf5m\x1d\xb8b\xc3\xfa%\x15\xad/\
3498)RO\xa5\x92\xf8\xec\xddwc{O/v\x1d\xda#\x13z5\xdc\x84\x95B!\x03I\x10xt\xb4\
3499\xafA\xbd\xc7\x87c\xc3G!0R\x9a\x98\x89w\xca\x91K.\x90\x9b#\x97\x9e\xce\x9b\
3500\xa7\x11\r%dB\x8f\xfb-\xaa\x1b/\x1a\x8d\xc9\xd2\x0b E\xe9B\x8b\xa8\x8a\xc6\
3501\xb4R\x8b6\x1bBI\x06Z\xe9\x85\x909!\xf7\x8dk\xbb\xf0\xa1\xab\xaeB4\x16\x9b\
3502\xf3\xdc\x90\xfct\xad\xc7\t\xcb0\x88\xc6b\xb8\xe7\xf6\xdbd\xf9\xaeZ\x08}.2\
3503\x07$\xd2\x89%c\x18\x99\x9cB ,\xc9"g\xdf\x8cb\xefs\x03\x002d\xae\x84\xc3a\
3504\x87+&=w\xc7\x1dW\xa3\xb9\xa1\x01|\x11\xd1\xa7\xc5lF\xff\xc0 \xfe\xf9{?\xc4\
3505\x89\xa1~\xd5\xea\t\x90\xae[\xf3\xca\xa6\xac\xf7)5u"\xc1\x90\xb1\x01H\x85g\
3506\xd1h\x0c\xd1hL\x1eWq\xbf\x05\xe7\xa8\x00\xa2\xa1\x84*\xfbe: \x8dS\xe5X\x1dN\
3507\x9c\x03 E\xe8\xda\x1f@\x8a\xd4\xe3q\xb1(\x19\x86`\xef\xe0\xff\x00\x90\xf6\
3508\x11\xa4b\xc6\xe2V7\x8b\x85%\xb1QJ\x8aA6un\xc0\xbd\x7ft\x07&\x83~\xbc\xf0\
3509\xf6\xcf\xc12\xa6E\xbf\t\xab\x014\xcd\xa0uU#\x00)-\x92\xe3\xd3\xa8\xb5Q\x08\
3510\xc73\xe9]\xb0IKR=\xf9e8q\x0e.\xb3+\x8b\xd8\x95Q\x90D\xee\x00\xcc\x89LF\x82\
3511\x07\xb3\x91T\x0c\xf0@\xde\xe8")\x8d$\x1a\x0b\xc5BY\x190\xca\x08N.\x9a\x99\
3512\xd2\xcfzQ\xa2\xde\xe3\x93\xbfk\xbeU\x8c(\x8a`M&\xd8\xac\x16y\x93\x8f@2\x04\
3513\xf3\xe0O\xff\xf06\x00\xc0\xcb\xa7^\x02P\xdeL\x15 \x93]\x01 o\x16\x962\x1d2\
3514\t\x85\x950\xcd\xc8\x11&!%\x9b\xc5\x82@\xf8\x02~\xf9\xd3\x93\x08\x85\xc3\xb0\
3515Z\xad\xf0\xb8\x9c\x88\'\x92\x18\x1a\x1d\xc5\xf1\xfdk\xf0\xa1\x1bW\xc9\xf9\
3516\xdf\x0e\x87\x14\xb9\xd3>\n\x98M\t\xf7\xd8\xeap\xcbG\xdf\x8f\xff\xf3\xdd\x17\
3517ab\xd9\x82V\x83\x0c#e\xca|\xf7\'O\xe1@_\x1f\xbe\xf0\xd9\x8f\xe2\x9a\xcekT\
3518\x9b\xa5\x85f\xc1\xb8\xedn\x00I\\d[#gV\x91J\xe3`|\n\xa8\x07\xc8\x1f\xc8x\xe1\
3519C\x160\x97\xa87Z\x01\xa86\xe2\x01u\xde{D\x9c\xc6t0\x8dp*<+\xc3\x98d)r\xae\
3520\xc8\x9deL\x98\x98\x9d0\xba\xbb\xbb\xe1v\xb9\x90L\xa5\x8aJ\xa5],,\tR\x17D\
3521\xa9\xdc\xffs\x9f\xb8\x1b\xf5\x1e\x1f\xbe\xf7\xab\xefc:\x1e\\\xb6\x92K)\xe8\
3522\xa8\x93n\x82\xf1\x884\x109!\x05\x93\x05\x00\xcc2\xb1\x93\xec\x00 \x13\xc5\
3523\x03\xc0\xe6\xb5\x9dp\xdb\xdd\xb8\xd8\xbe6K\xd3\xce\x05\x12Ek\xa3i\xad\x06\
3524\xae\x07eua@\x08\xca\x95\x85\xda\xff{\xae\xcf\xc9\x07\x11\x90\xbdGHI;\x01\
3525\xd9T^\xdf\xd6\x81]\x87\xf6`"4Q\x96\xb1DH\x9cd_\xd9\xcd\x0e\xb8g\x8b}\x00dE\
3526\xcfJD\xc4\xe9\xbc\x7fW\x92\xf3\xb97\x03\xb80\xfd\xbala0\x1d\x0e\x83\xa6i\
3527\x98\xcd\x16\x0c\x8f\x9f\x03\xb0\x01\xaev\x07\x04\xbf(\xbf\x87D\xe9d"\xbd\
3528\xe1\x8ak\xb1\xbb\xf5\x08\x06\x86G\x0bN\xd9\xa3)\nV\xab\x15\x87\x8f\x1d\xc77\
3529\xbf\rt~\xa3\x0b@f"&\xd7Q\t2yw\xb6v\xc1\xdb\xb9\xb0\x85\x80D\xfb\x0fLO\xe1\
3530\xd8\xf0Q\xec\x7f\xf7\x80\xac\xb1\xdbl\xd2w\xceE\xee\x16\xd6\x8c@d\n\x93A?\
3531\xacu5p\xbb\\\x18\x9d\x980H\xbd\\\x98\x89\xc7qq\xdbj\xf4n\xbd\n\x93A?\x0e\
3532\x9f=4ge\x9d\x1e\x947\xddBb>\x84QP\x14ES*\xff\x16\xe5\xb9!y\xbaJb\x8f\xc7E\
3533\xc4\x11FK}\x1d>\xba\xf9.\xb9\xec\xbdX\x8b\x00B\xba\xda\x0c\x15\x82\\\x04/\
3534\x97\xd9\xb7I\x0f\'\x86\xfau\xb5t\xbd\xf7O\x06\xfd\x18>7\x0e w\xba\xa7 \x08\
3535\xf0z\xb2\xb31X\x93\t\x89D\x02f\xb3\x05\xbdW\xbe\x1f\x00\xf0\xda\x99\xfd%\
3536\x8d%%H\xf5h\xad\xcd\x83Kk/Bg[WE\xab\x99\'\xdb\xfd\xf8\xc5\xae>\x1c}\xe7=0,\
3537\r\x13\xc3@\x14\x05X\xcc,\x86F\xc7p\xf0\xf41lFg\x86\xccu\n\x87\x9bW6\xe1\xa3\
3538\xb7^\x8d\x7f|\xe4)9_\xbf\x10\xd0\x14\x05\xb3\xd9\x82\xd1I?\x12S3hno\x91\'ib\
3539K\xa0\x8c\xd6/\xb6\xaf\x95\x8b\xb8&\x83~\xf4\xf5\xf5\xe1\xd4\xd8\x88\xec\xf7\
3540s!,\xc9++\\\xb5\xb8\x10\x9e\xce\xf9\xa8\xf7\x9a\\\xef\x03 \xfb\xf1\x90\xebp\
3541\xd7\xb6;\xd1\xd9\xda\x85\xff<\xf84F&\xa5\xe3%)\x8f\xf9@\xc6\xa0\xd7\xe3\xc6\
3542\xd0\xe8hA\xe7h\xb1\xb1$H\x9d\xe38l\xbf\xfaj\xd4{|xz\xdf3H\xa4\x13E\x13%\xb9\
3543\xf1\x1a\xdc\rh\xb0\xae\x92#\x1fi)X9\x84b!\x1c\x1b;\x8e$\x17/iy_H\x9e:\xb17%\
3544\xa4\xcc\xf1iU\xf6\x06\x89F\xa4\x9c]\x80,C\t\xa1?\xf5\xc2\xf3\xd8\xf3\xfao1<\
35451\x8eq\xbf_eeK\xa2\\\xed\xa3\x16\xca\xbf\xeb\x818/\xba\x9cN\xb464\xa2\xbd\
3546\xb9\x19\xed-M\xe8\xddz\x15\xd6\xb7u\xe0\x04\xfaU\xe9\x8cz\x84^\xef\xf1I\x05\
3547AG\x8e\xc0^S\xa3{^x\x9e\x87\x89eU&T\xca\xe3Ks\x1cV\xf9Vbc\xd7%\x98\x0c\xfa1\
3548\x11\x9a(9\xd3\x85\x17x$\xd2\t\xd4\xda<\xd8\xba\xeej\\\xd3yMV5\xf3\x11\xe1(\
3549\xce\xbe58\xe7g\xd9\x1b|\x88M\xf8U\xbf+\'j-\xae\xee\xeeF \x18B(\x1cF\x9a\xe3\
3550T\x0e\x9cO\xffp?\x8e]<\x8e\xdf\xdf\xde\r\xd7%\xb5\xba\x99F\x00p\xc7\x87n\xc3\
3551/v\xf5\xe1\xf0\xb1\xe3EU.34\x85D"!\xf9\xcb\xa0E\xf57\xe5f\xb7\x97\xf6\xa0\
3552\xbb\xbb\x1b\x93A?\xfe\xf9{?\xc4\xae\x03\x0700,\x11\xe3BX\xf1\x9a\xcd\x16\
3553\xb8\xec5hkn\xc2\x9f]\xb7\x1d\xf7\xde{/\xee\xab\xdd\x81\x87\xfe\xeba\xb9\xe2\
35544\xdf&*\xc7\xa7\xa5\xfc\xfb\xda:\xd9er)x\xf2W=\xa9\xa7\xd3i\x98\xcd\x16\\\
3555\xdf\xbb\x19\x00\xd0?q\xb2\xe8\xc8*\x96\x8caM\xfd\x1a\\u\xd1Vl\\\xdb\xb5\xe0\
3556K\xc0\xf1\x97\xcf\xe1l`\xb8b\xfa?CS\xb2\xab\xa2\xb2\xf21\x17\x04&\x86\xce\
3557\xa6-X\xdf\xd6\x81\xc7\x1f\x7f\x1c\x9f\xf9\xcew\x91J%u-x\xf5@\xcfYEI\xc1D3\
3558\x00\xc5\x80f\xa4\x8d\xc9D*\x85`8\x02k0\x84\xc1\xe1\x11\xfc\xf6\xe8\xdb\xb0\
3559\xdbl\x18\x1c\x19\xc3\x83;vH\x91\xb9\xe23\xf4"\xf5\xa7^x\x1e\x7f\xff\xbf\xbf\
3560\x83\x0b\xa1\xe9\x9c\x92\x01\xb1|U\x1aP\xa9\xbe\xbb \xa0\xbd\xb5E\x96^\x92\\\
3561\xaa$R\'\x84~\xf5\xda\xad\xb8m\xcbm\xb2w\xc8\xcf\x9f}\x1e\xbb\x8e\x1dC\xff\
3562\xe0\x00\xceO]@8\x96]H%\x08<h\x9a\x91\x1fS)i\x12U\x9e\x7f\x9af`b\x19\x95\xff\
35639\xb1\xf4%\x93c{\xabD\xa8\xe3~\xbf\xdc<\xe4\xbd\xa1\xb3\xb0Z\xad8\xd2\x7f\n\
3564\xa7\xcf\x0e\xe3\xda-[\xd0\xb2A"\xa4\xe6\x15^t\xb6u\xc1\xdb\xe3\x01\x86\xa4\
3565\x15\xd3\xe7>q7\xfe\xe2\xe1\x7fD\x9a\xe3\x8a2\x8f\x13xIZ\xa9\xf7\xf8\xb2\xf6\
3566>\xacu5\xc0\xf9 \x9a\xdb\xa5\xe3\xfb\x9b\xaf\x7f\x0bO\xec\xdc\xa9\xf2\x99QV\
3567\xf4\x02s\x1b\xaci_\x93\xf5\x9c@\x01t\xc6\xab_\x10E@\xa0\x10O&q\xf8\xd8q\x1c\
3568>v\x1c\x00p\xef\xbd\xf7\xa2\xb3i\x03\xde8\xf3\x06H\xc5\xe9\\P\xf2E\xb5\x13:\
3569\xb0\x14H\x9d\xe3\xd0\xdc\xd0\x80\xe6\x95M\x98\x0c\xfa1vaL.\xd1.\x04\xb1d\
3570\x0c\x1b\x1b\xae\xc0\xdd\xbd\x1fW\xddx\xfdS\x01\x84\xc2\x11\x84\xa3sWQ\x96\n\
3571=\xf7\xbaJ\xa0\x98F\x0c\xc4\x03ck\xc76L\x06\xfd\xf8\xc1\xee]H\xa5\x92p\xd9]\
3572\xaa\x9b"\x17\xb4\x83:g\x03\xe9\x1cM\x18\x88\xc77\x89|^=|\x08\x9b\x0f\xed\
3573\xc1\xf6\x9e\xde,r \xf2\xc5\x89\xa1~|\xe3\xfb?\xc6\xb3/\xbd\x84d*\x95\xd7\
3574\x8f\xc3\xe3r\xa2\xd1\xa7\x9e\xb4\x95v\xb1\x11\x9aF\xd7\xbau\x002Y8\xc5\xa6\
3575\xbc\x11\x19\xef\xa6\xf7}\x18wm\xbb\x13\x93A?>\xff\xb5\x7f\xc2\xf3{~\x8d\xd1\
3576\x89\t\x95W\xb7\x89e@e\x15\xfa\x98T\x8ff\xb3IER4E\xc9\xdfQ\xd9<D\xeb\x8fN\
3577\xac\x88\xdd\x0e\'6^z)\xee\xfd\xa3;p\xdf\x97\x1e\xc6\xce\xdd\xbba6[\x90L\xa5\
3578\xf0\xab}\xfb`z\xdd\x84D2\x85\x1a\xab\x05n\xd7/p\xc5e\x97\xa1\xb3\xab\x11G\
3579\xea\xdf\x81\xb7\xd1\x81\xb5\xab[q\xe2\xbd\xd3\x05\x93\xba\x94B\xbb\x12\x9d\
3580\xad]\x98\x0c\xfaU\xfb*\x80\xb4W\xd2\xbc\xb2I\xae\xce}q\xef^\xb8].U\x97$2\
3581\x8e\n!I]\x89M\xfb\x1c-f\xff\x8d\x16\xc1\x08,\xdc.\x17B\xe10~\xb0{\x17n\xb9\
3582\xe3Vl\xed\xd86K\xeasc)xFiQ\xf5\xa4\xceq\x1c\xda[[\xe0\xad\xad\xc3\xe8\xe0HQ\
3583\xd2K\x92K\xa1\xd6\xe6\x91\t\xfd\xf3_\xfb\'\xfc\xf0g\xff\x85T*\xa3\xa9W\xc2o\
3584\x9d,+\xb7wv\x02=\xd2\xc4R*\x8a\x8d\x0c\xe2\xc9\xb9-J-\xacY>\x9f\xa3\x93~\
3585\x98\xcd\x16\xfd\x9b\xa2\xc0\xe3\xcb7\t(\xe5\x18-\xa1\x13R"\xde\xeaz\xd1\xf9\
3586?\xfc\xe0\x9f\xf0\xfd\x1f\xff\n\x81\x0b\x17`b\xd99\r\x96F&&\x11\x0cG\xd0\\\
3587\xefS\xfd\x9f\x80\xd4\xc1\x87\xa2iySytR2\xa1Rf\x98\x14B\xee\x89tB&\xf4\x13C\
3588\xfd\xf8\xecW\xfeEv<\xb4\x98\xcdE\x9b@\xe5j\xc4M\xa0mRA\xce_(\x1a\x91\x9b\\\
3589\x00RD\xf9\xd0\x17\xee\x83\xcb\xe1\xc0\x8b{\xf7"\x14\x89H\x16\xb2\x82\x00\
3590\xb3\x89E\x9a\xe30\x19\x08\xe0\x85W^\xc1\x8b\xaff\xfe/\x97\xbd\xa6\xe0\r@Q\
3591\x14\xc1\xd0\x14>w\xcb\xadX\xdf\xd6!\xfb\xbfh\x89\x9d\xa0\xaf\xaf\x0f\x89tZ*\
3592\xe0\xc9a\xd5PQ\xd0"\x04\x11p\xd8\xed\x18\x1a\x95\xae\xb7\xb7\xb6\x0e\x16\
3593\xd6\\\xd4*m\xa1\x9a{\x94\x03UO\xea\x80tB\xeb=>\xec\x8d\xed-\xea}\x1c\x9f\
3594\xc6\xd6u\x92\x16\xff\xf0\xa3\x8f\xe2\x91\'\xfe/lVk\xc5\rz\x04A\x00\xcf\xf3\
3595\xb07H\xd5w\xa4\x98\xa1\x94e~%4<\xa7M:\x9fG\x84\xa3\xf3\xbe\xd1\x94\x84N\x1a\
3596=\xe7kQ\xa7$t\xe2oO\xb4d \xb3\xd1\xbag\xffkx\xe4\xc7O\xe2\xf0\xb1\xe3\xb0Y\
3597\xad\x05\x13%q\xaa\xec\x1f\x18\x84\xcdb\x91\x8b\x8fH\x07\x1fGMM\xc6\xc6@\x9c\
3598\x86\x855\xcb\xe9\x82dS9\x1f\xb1\x93\x95\x1f\x89\xd0\xef\xd8\xf1\x05\xf4\x9f\
3599\x1eP\xadtJ\x85\xf6\xfc\x11\xc4\x13I\xd8\xac\x16\xb9Gk8\x12\xd1\xed\x06Ep\
3600\xe5G.\xc2\xfa+V\xe2\x99g~#m\xa6\xd2\x94L\xda\x0c\xc3\xc0d2A\x14E\x88\x00(H-\
3601\xe8\x8a\x01/\x88\xe8\x9f\xca\x88eZB\x97\xb4vio\xa4\x7f*\x00V\xb1RY\xacF\xe0\
3602\x14E!\x95Latp\x04\xcd\xed-`i\xb3*}t9aI\x90\xba2\x07\xbb\x18\xb0\x8cI^"\xfe\
3603\xf4\x97\xbf\x90\xda\x8e-@J\x12EQ\xb2$B\xf4\xb8\xf9fX\xe4\x84P\\\x95\x1b\xa7\
3604Y\x99$R\xa9\xb2\xb4\xad\xcb\xd5\xa5^)\xbb\xe8\x11\xba\xdb\xe5\x94\xbb\x1e\
3605\x91\x8d\xd0\xc7\x7f\xfcs\xec:p\x00\xb1\x99\x99\xbc\xe4\x95\x0b4E\x01\x0c\
3606\x83d:\x8d\xc4l\x87$2\xc1\xb0&\x13.mj\xd1\xcd\xf4\xd1+\xcfW\x12</\xf0\xb0\
3607\x9a\xac\xb8\xa1\xe7\x06\x00\xc0\x1f\xfd\xaf\xfb\xd1\x7fz\xa0h\xcf\xfb\xb9\
3608\xa0l\xed\x97\x0b\x84\xd8\xf5:D\t~\x11\xcd\xf5M\xf8\xc2g?\x8a]\xbf:\x82\x17\
3609\xf7\xeeE<\x99T\xc9+\x14E\xa1\x94\xabNQ\x14\x18\x9a\xc2\xf7\x9f~\x06\x9b\xd6\
3610_&{\riA&\xe7P8\xa2\x1a\x1b$\x88X\xe8\rG\x8a\xa2\x10\x9e\x89\xca\x9b\xbb6\x8b\
3611\x05\xb1T\xe5\xa4\xd7\xc5\xc4\x92\xa8(%iJ@\xe1\xe4\xc8\x0b<\xecf\x07\xd6\xb7\
3612u\xe0\xc8\xe9\xa3\x18;7\x01S\x81\x1b\x81\xe5\x00!\xca\xc9\xa0\x1f\xb1d\xac*-\
3613\x80\x03\xe3Q\xa9\xfc\xb9D\x07\xba\xb9d\x17\xad\x8e\x0e\xa8\x97\xb1\xee\xd9<\
3614\xf2\xfaY\x02{\xfc\xf1\xc7q\xd7}\x0f`\xe7\xee\xdd\x10\x04\x01n\x97\xbeOM\xa1\
3615\xa0(Jn6\x9cL\xa7\x11O&1\x1d\x0e\xe3\x97od\xf4T\x966\xeb\xfa\xa8(AH>\x91N`\
3616\xd3\xea\x1e9c\xe8\x95\xd7^\x87\xab\xc6>or\xd2{?!?\x92iD2\x92H6O.\x90\xcc\
3617\x93\xb0]J\xf9\xbb\xeb\xf6\x0fb\xc7\x9f\xdc\x0c\x8a\xa2\x90H\xa6\xc0\xf3\xfc\
3618\xbc\x1b\x8a\x9bL&\xa49\x0e\x7f\xff\xbf\xbf\x93U]*\xa74\xceF\xee\x95\xdc\xb3\
3619*\x064E\x81eYU\x1b\xc5\xe5\x8a\xaa\'ueF\x00)\xac)\x04\x9c\xc0\xc3\xebZ\x01\
3620\x008\xfb\xd6 \x12\x1c\xb7(\xf6\x99\xf5\x1e\x9f\xec\xfeV\n\xe6$\x8cy.\xf9\
3621\x01\x14m\xee\xa4\x85V\x0b\xce\xa5\xa3\x13Bw9\x1c2\xa1\xafp\xd5\xc2\xde m\
3622\x86~\xf9\xc9\'\x11\n\x87\xe1r:a2\x99\xca\x1a\xc9Q\x14\x05\x86a\xc0\x0b"\xbe\
3623\xf1\xefO\xe0\xf1\xff\xf7\xac\xaa\xd8G\x8f\xd8\xb5r\x8c\xd5dE\x8fo\x13\x00\
3624\xe0\x91\x1f?)\xbd\x88)\xcf\x98"\xdfUO\x9a\xd0\xa6\x90\x12b\'\xd9=\xe1HD\xce\
3625\xdb\x06\xa4\xaaJRx\x14\x8cO\xe1\xd2K\xd6\xe0\xba\x0f|\x00kZ\x9ba\xb3X\x90L\
3626\xa51\x13\x8fc&\x1eG8\x12\xc1L<^\xf4\x18p\xd4\xd4`ht\x14\x9f\xfd\xca\xbfd\
3627\xed\x83\x04\x84 \xbc\xb5u\xf2jH\xbb\xea\x98o\xdf\xd8\xc5\x80\xde\x8a\xa8Z\
3628\xb1$\xe4\x17\x82|\x15w\xf9^?\x99N#\x95J\xc2lZ\xf8\n\xd4\\\xb9\xe3\x85b!\x96\
3629\xa9\xe5\x9a\xec\x947\xaf\x9e\x8e\x0ed\x13: 5\xbb\xfe\xe1O\x9f\xc3\x84\xdf\
3630\x0fg\x85\xfb\xc8Z-f\xa4\xd3)\xfch\xe7N\xfc\xc1=\x97\xc3\xebZ!od\x13b\'\x9b\
3631\xcdJ9&\xc9\xc5\xe1\xb6\xbb\xd1\xdc\xde\x82\xbe\xbe>\x0c\x8d\x8e\xc1f\xd5o\
3632\xf9W.he\x18\x12\xadk\xf5u\xd5\xf7\xab\xab\x01f\xd3\xe2\xc3\xf6i\xb9\x92\xf4\
3633\xdeO\xdc\x82`|\n\xa3\x93c\x18;\x9d\xc0\xd1w\xdfU\xbdopx\x04S\xa1P\xc1M\x97\
3634\x05Q\x84\xcb\xe9\xc4\xbe\x83\x87p\xe4\xe8;\xe8\xddzU\xa6\xce\xe0|\xc6&\x80\
3635\x90\xa1^}\xc3b\xe5|\x97\x92\xd2ll\x94V\x08\x11Q\xbf\xb5\x95\x1e8>-\x17\x18\
3636\x91(f!#\xf5j\xd5\xd4\xcb\x85\\\x19\x1bzQ:\x80\xac\xc6\xdf\xca\xca\xbf\xb3o\
3637\r\xe2\xf9=\xbf\x86\xd9l\x01SH{\xb5y\xc2d2atb\x02g\x0e&\xb1ak\xf6D\xaf%w\x82\
3638\x06\xeb*\xd4{|x\xfc\x8dg\x11\x08\x06\xe5M\xd8r\x83l\x98\x02\x19b\'\x84h5\
3639\x9b\xb36N\x95\x92\xcc\xe8\xf91\xe9>\x89\x02\x0e\xd8\x11BH\xf6}\xf1\xd8\xea\
3640\xe0i\xab\xc3\r=M2\x01\xafo\xeb\x903x&\x03\x81\xa2\xf6\x9ch\x8a\x02\xc7q\xd8\
3641\xf3\xfao\xb1\xb1\xeb\x12]\x97M-\x19j;P-$H}\xc5|\x93\x17\xaa\x1dU/\xbf(Ql\
3642\xa4\xbe\x98X0M]G~\xe1\xf84x\x81\xd7\xfdY\x08h\xa3t\xad\x8eN\x08\x9dh\xe9\
3643\xfdS\x01\x9c\xf3\x9f\x87\xd5\xb2p\x1d\xdcmV\x0b^;r\x04@vC\t\xd5\xebf7\xd4\
3644\x12\xe9\x04\x9a\xeb%\x17\xc2\xc1\xd9r\xf1r\xcb\x08\xca\xef\xce\xf1\xbcLz\
3645\xca\x0c%=\x8d=\x9eH"4kZ\xa6\xd7\xacB\xf0\x8b\x08\xdb\xa7\x11\x8cOI\xd1\xfa\
3646\xf91\x95\xe9\x96\xb7\xb6\x0e\xe3\xfe\xe2,"\xc8\xf1\xda\xacV\xfc\xf6\xe8\xdb\
3647\xf2sz\xbe\xea\xb9\xaa\x8c\x17\x1a$}y)6\xa2/\x06K\x8a\xd4\x8b\x89\xd4u\xc1/\
3648\xbcu\xa62R\xcfE\xb4\xf9\xc8\xb6X\x92#\xfa/G|n\xb4?|\xba\xa0\\\xf6B\xa0\x8c\
3649\xb6\xb4\xba\xa9\xb6P\xc6\xa5\x91U\xeaM&9\xb5\xf0@__Y\x8e\xa7\x18P\x14\x8dH4\
3650\x8a\x81\xc9\x019X\xd0\x12\xbb\xd7\xb5\x02\x13\xa1\t\xfc\xe2\xc7\xfd\x18\x7f\
3651\x93\x95\xcb\xed\x87\'\xc6+v\\\xda\xeb\x9d\x8b\xd8\x13\xa9\x14\xe2\x89$\xe2\
3652\x89\xa4J\xd6\x08\x08A\xc4\x921D\xc4i\x8cG\xce\xc9\xae\x85\x82_\x94\xb3\xc7\
3653\x08\xb9\x13\x8c\x0e\x8e \x18\x8e\x80)!\x91\x80a\x18\x04\xc3\x11\xb9\xf1\xb4\
3654\xde\xa4BP-\xe4\x0e\xcc\xafv\xa4\xda\xb1\xa4\xe4\x97yG\xeae\xda\xd4*\x06JM\
3655\xbd\x90h];\xd8\x8a\xd1\x1d\xeb=>|\xf6\xba\xcf\x15\x7f\x90%B)\x13\x00\xd97\
3656\xad6\x1dQ\x19\xa5+14:\x06\x13\xbb\xf0\x9bg\x82 \xe0\xcd\x97.`\xe3=\x99\xe7\
3657\x88W9\x00\x04\xc2\x17\xf0\x9b\xe7\xc7\xd1\xd6\xdc\x84\xcb\xaf\xf5\xe2\xc97\
3658\x9e\xc0\xc1\x83\xef \x1c\x89\x14l\xa9PN\x10b\'\x13(\xf1\xda\xd1\xd6\x1a\xd8\
3659-v\xb99\x86\x12\xc4\xe7H\xeb\x05\x13\x10\x82\x98\x89%PJ\x0f\x08\x8a\xa20\x1d\
3660\x0e\xe3L`\x00\x1b\xd7vat\x8e\xfe\xb2\xd5\x00"\xbf,W,)R\x9fw\xa4\xbe\x08\xa8\
3661\xf7\xf8\xf0\xc9\xad\x7f"G2\x80\xbaK\x8c\xd6\x7fZ\xaf\xd1@>\x08\xa2\x08\xab\
3662\xc9\x82\xb3o\r\xe2\xc4\xca\xec|\xe1\\81\xd4\x8f\xc1\x911\xd04S\xf0\xe6\x98\
3663\x16z\xb9\xe9\xdarv\xad\x96\x0ed\xa2to\xa3\x03/\xed9\x88X<\xbe(\x96\xa64M#\
3664\x18\x8e\xc8\xb6\xb7d|\x11b\xdf\xfb\xdc\x00F\'&P\xb7v\x1a\xbf|:\x8e\xfe\xd3\
3665\x03\xb8\xe7\xf6\x16\xd5dU\x89\xcd>\xad\xc7\x89\xbc\x1aR\xe8\xec\x00d\xad=\
3666\x91J\xc9\xa9\x83\xdb{z\xb1qmW\xce\xcfV\xb6\xa3#\xab\xc8\xb3o\r"\x91N\x96T\
3667\x94GQ\x14\xe2\x89\x04\xce\x0fr\x08t\xe4.\xa9\'V\r\xe4\xb8\x17\xa5\xbat\x16\
3668\xcb]~YR\xa4\xbe\x944u\x9a\xa6\xe5JIom]\xc6n\xb6\x00\x14c\x81KS\x14@\x89\xf8\
3669\xf2\x93O\xc2\xfa\xcc3E\x1dc"\x95\x82\xc5l*\x99\xd4\xf5\x10O$\xb3\xa4\x17 w\
3670\x94>|n\x1c\xbc b\xe1)=\x83X2\x86F\xef*D"\x99\xa0\xe1\xcc\xc1\xa4\x9c\xe1\
3671\xf2\xde!\n\x89\xf4(\\v\x17\xee\xba\xfd\x83\xf8\xfac?\xa9\xb8\xc3 \x81Vg\x07\
3672\xb2\xc9\x1d\x90\xb2L\xfatd,\xbd\x9e\xaf\x804&I\x8ey\xffT`\xdev\x19q\xc1\x83\
3673\xb5\x00\x00 \x00IDAT\x83\xa3\xa3\x19S\xb6\xf3\xd9\xbaz.\x14b\xe8Un\x14k1\
3674\xbd\xd4\xb0\xa4H\xbd,\x9a\xfa\x02H0$\'\xfa\x91\x9f?\x8f\x1f\xec\xde\x05 \
3675\x93[\xac\xcdV P>o\xb7\xd9\x10\x08\x86\xa4\x06\xc1\x05\x0e\xf6H4\x8a\x0b\x9c\
3676tc2\xb4T\xd1\xaa\xf7\xa8\x04M\xd3E9\xf3\x15\x02\xb2I\x9a\xaf\x12\x94h\xe9\
3677\x81\xf1(\xfa\x07\x07\xcaR\xd1Z\nh\x9a\xc6t8\x8c\xb7^\t`\xedG/VE\xebR\xb3\t\
3678\xe9Z\x82\x92\xaeA\xef\x07\xb6\xa0ye\xd3\xa2\xc9/\xc4gGK\xee,\xc3\xe0\xcd\
3679\xe3\'\xd0\xfb\xe9Og9:\x12\x90\x8a^\xb2r"i\xa5\x1du^\x1c}\xf7]X\xe6\xa1w\x9b\
3680\xcd\x16\xb8]N]\xef\x17 \xdb\xbbf\xb1QJ\xa4n\xe4\xa9W\x08\xf3\x8d\xd4E\xba\
3681\xb4\xd2\xe8R14:\x864\xc7\xc9\xae}\x00T\xbf+\xa1}\xdeb6\x17E\xb8\x0c\xc3\xa8\
3682^o\xca\xf18_h\xabH\xb5>/s\xa1^#\xb3\x9c\x9f\xba\xb0(Ea@f\xf2=\xda\x7f\n\x9d\
3683\xc3\xabpi\xebj \x9a\x1d<\x90^\x9e\xf7~\xe2\x96yuc*\x07\xc8$\xafG\xee\x80Zo\
3684\x07\xf4\xdd\x1d\x89T\xd3\xde\xdc\x0c@\x92\xc8\x04A(i\x82\'\xd5\xa9+\\\xb5\
3685\xb2I\\\xb5\xa3\x94H\xdd\xc8S\xaf\x10\xe6\x1b\xa9\x97Sf(\x04&\x93I7\x02R\xde\
3686\x98\xb9\xb0\x14|\x9b\x01u\x81\x8c6"\xd3\xd3\xd3\'\xd3i\xb4\x03\xb2\x9e\x9eH\
3687\xa7u\xaci\x17\x0e4M#\x14\x89\xe07/\xbf\x83\xe6;\xbdp8\xec\xf8\xd5\x7f\x1eE\
3688\xff\xc0 X\x96A:\x9dF\xad\xcb\x85\xcb\xd6\xae)z\xbf\xa3\x92P\xbac\xea\xe5\
3689\xb5\x13\x10\x1d\x9b\x10;qv\x04f\xeb7f%\xb1R2_\x00\xa9m`\x8d\xd5\x82\xf6\x96\
3690&\x04\xa6\xa7\xb2\x8a\x8f\xaa\x11\x86\xa6^E(5R\x0f\x85\x17o\xe9\x94\x8f\x9c\
3691\x97\nq\xe7\x83\x96@\xb4\xc4\x1e\x8eFeC6@\x1d\xa9\x87\xc2\x11\xf09V.\x0b\x89\
3692\x1a\x9b\r\'\xde;\x8d#\x136\x8c\xbf\xc9\xe2\xf57\xdf\x02\xcb2H\xa68\xacim\
3693\xc6U\x1f^U\xf1\x0eY\xa5 W\xd4\xae,X"+("\xed\xe9\xc9b\xca\xebS,x\x8eCKs\x13.\
3694mjY\xd6\x91\xfaR\xc2\x92"\xf5b"u\x961\xc99\xbaD?\x14Eq\xc1\xa3\xf5\xdf%h\t=\
3695\x14\x8d\xa0\xb5\xa1\x11\xa1pD^\xea\x13\x04\xc6\xa3\x18\x9e\x18G\x9a\xe3\xaa\
3696\xa2\x99/\xcb\xb2x\xe1\x89~\x00"\xd8\xd9\xf4J\xb3\xd9\x84+\xae_\x01\xb7\xdd\
3697\x8dH|\xe9h\xaaZ\xe4\x9al\x81Le/MQ%\xdd\x1f\xbc \xa2\xd1\xe7\x83\xb5\xaeFWS\
3698\xd7\xae\xd4\xaa\x01\xcb=R_R\xc5G\xa5F\xea\x1du^\xb0,\x0b\xbd\xb8\x98\xa6\
3699\xa8\x8a\xfe,W\x90\xc2\xa3|\xa9iJ\x1d\x92X5\x08-\x99\xabp\xf2\xf4\xc0\x9c\
3700\xad\xf1\x16\n\x14E\xcd\xfeH\xb7\x84 \x88\xf0\xba\xddhp7\x00\x90\x1a\xae\xe8\
3701\xa1\x1aV[z\x192\x04\x89TJw\xb2\xd5Z64\xd5\xfb\x8a\xce\xe8\xa1)\n\x82\xc0\
3702\xa3\xb5\xa11\xe7^C\xb5\xb84*aD\xeaU\x84b5\xf5\x89\x84\x94\xc1\xb0\xfa\xf2v\
3703\x98X\x16\xd1h\x146\xab\x15\xbc V\xa4\xe3Q.\xc8m\xcdd\xe2X<\xb2\'\xcd\x110K\
3704\x04\xa2(\x16\xe5\xd0G<mH\xc6J\x9a\xa6\xc1$S\x80C"\x10\x8f\xcb\x99\x95\xdd\
3705\xa3$\x90\xf3\x83\x1c\x9c\xb3\xbd\x8a\x9b\xeb}\x98\xf0\xfbUM\x1c\xaa\x03\x12\
3706Y}\xe0VI\x96\x88\'\x93Y-\x14\x17*\xa5\xb1P\xe8J1\xb3\xe7T\x19\xa9\x87#\x11\
3707\xb46\xaeB8\x1a\x85\xcb\xe1\xc0\x85\xf04:\xea\xbc\xf8\xc3\x1b\x7f\x1f?\xda\
3708\xb9\xb3(\xff\x17\xf2\x7f\xaen\xcf\xbf\x89\xa8ld\xce\xa5\xd3\xe0x\x1e\x82("\
3709\xad\x08\x08*\xbdY\xce#s\xcd\x96{\xa4\xbe\xa4H\xbd\x98H\xdd\xc2\x9a\x11\x88L\
3710\xa1\xaf\xaf\x0f\xdb{z\xf1\xc0\xbd\xf7\xe2\xa7\xbf\xfc\x85\xbca\xd4\xda\xb8J\
3711\x95\xdaU\t\x90\x1e\xa8\xc3\xe3\xe7\x10\x08\x860\x19\x08 =;\xb0L\x0b\xd4\xb0\
3712C\x10\x04\xa4\xd2\x9cj\x12#\xff\xb7(\x8ap\xd8\xedY\xe9o\xc5B\x99]\x91\xcfb\
3713\x17\x90\x0c\xbc\x00i\xa3\xf4\xab\x7f\xfd\xe78x\xf0\x1d\xfch\xe7N\x04\x82\
3714\xc1\x05\xcd\x82\x11\x04Aw\x82\x15D\x11<\xc7\xe1\xca+.G\x83\xdb;\xaf\xffc\
3715\xbe\xbe\xe5\xa5@@\x86\xd89\x9e\x87U\xe1$\x19\x8b\xc7\xd1\xe8\xf3\xa96K\x01\
3716\xc8MJ\x80\xd2\xc8u:\x90\xfbo.\x87\x03^Of?\x82L\xf8\x84\xe8\x17\x126\x8b\x05\
3717\xdeF\x87\x11\xa9W\x03\xc8\xd2\xdd\xe1\xb0\x83\x9b(\xce\xc2\xf6\x85\x81\x17\
3718\xd0\xdc\xde\x82\x07w\xec\xc0\x83;vT\xea\x10\xe7\xc4d\xd0\x8f#\xa7\x8f\xe2\
3719\xe0\xc1w\xf0\xea\xe1Cx\xf3\xf8\t\x84\xc2a\xd8\xacV\xa9\x8fd\x19\t\x80\xdc\
3720\xd4\xd1X\x0c,\xcb\xc2\xbbb\x05\x9a\xeb}\xd8p\xf1:\xb477\xa3\xbdE\xda\xd8\
3721\x02 w|\x072\xd5\x86\xf3}\xd4~V>l\xef\xe9\xc5\x8d[\xb6\xe0#\xf7\xdf/\x9bzU\
3722\x1a\xa2(\xe2\xd2\xf6\x8b04:\x86D:-\xcbd\xa2("\x95J\xe3\xeaMW\xe0\xf2k%B/\
3723\xc5\'\x87\xa6(\xa49\x0e\xb1\x99\xc5/\x997\'\x92\xa8\xb1Z\x10\x8d1p\xd8\xed\
3724\x18\xf7\xfb3\x1e\xf7\x8a\x15\xd4\xe0\xe8(\xce_\xb8PRZ#IDP:4\x06\xa6\xa7\xb0\
3725\xbe\xad\x03\xff|\xff\xe7\xe7\xf9\r\xca\x8bR\xa3t#O\xbd\x8c\xe08N\x1e4\xc5f \
3726XX3\x06&\x07\xf0\xaf/~\x1d\x9bV\xf7\xc0c\xabS\x95\xe5\x07\xe3SE\xb7\xc8+\x06\
3727\xe4x/\xb6\xaf\x85\xb5\xae\x06\x1b\xd7va{O/\x1e\x04\xb0\xeb\xd0\x1e<\xbd\xf3\
3728\xd7x\xf6\xa5\x97\xe4\xc6\x10\xe5\x00\xe9\xd1\t\x00\xd7^u%~oS\x0fn\xdc\xb2\
3729\x05\xcd\xed-\xaa\x01M\xc8W\xb9\xb95:8\x02k]M\xc9\x8f\xaa>\x95\xb3\xbf+\x9f\
3730\xd3\xd3]\xeb=>4\xb7\xb7\xc0\xebqcht\x145VKEujQ\x14\x91L\xa5\xb1\xe1\xe2uhm\
3731\\\x85\x17_\xfd\x1f\xd4X-Hs\x1c\xd2\x1c\x8f\xaeK.\x96\t]\tm\x1b\xc0|Hs\x1cZW\
37325b\xfb\xd5W\x97\xf3\xd0K\x86\xb6\x92Wi\xa6\x06\x00\x976\xb5\xe0\xbb?\xfb\x19\
3733\xd2\x1c_\x14\xa9\xd3\x14\x05\x9afd\xdd\\k\xbd\x9b+"\xce\xd7\xa8\xba\xd2\x08\
3734LO!15\x83X2V\x94\x1d\xb6\x91\xa7^f\x0cO\x8cc2\xe8\xcf2"*\x04v\x8b\x1d\x81\
3735\xc8\x14^|\xfb\xbf\xc12\xa6Ek+g\xb7\xd8\xe1\xb49\xb1\xd6{1z|\x9b\xb0\xbd\xa7\
3736\x17\xdb{zq\xd7\xed\x1f\xc4\xdf\xff\xcb\xff\xc1\xe1c\xc7\xe1p8\xe6\xbd\xb9\
3737\x1a\x9d\x99\xc1\xc5m\xab\xf1w\x9f\xba\x17\xbd[\xaf\x92\x89\xbc\xaf\xaf\x0f{\
3738c{1:9&\xef5\x90\x8c\x0e\xe2\x12I\\\x1c\xf3A\xefFP\xae\x9c\x9c6\xa7\xfc}\x01\
3739\xc8\xc6RZ3)/\xedQM\x04\xe5\x9a\xd4\xe6\x025\xbb\xb9\x17\x8eF\xf1\xa1\x1b7\
3740\xe0\xcd\x93\'1:1\x01\xb7\xd3\x89\xf7o\xbc\x14W\xdf\xb0\x1a\x80d\x1d@\xa2\
3741\xf4|\x8d\xa8\xb5\xa0)\n\xe1\x99\x19t\xb4\xaf\xc1\xb7\x1e\xf8\xdb\x8a|\x87\
3742\x8a\xe0g?C*\x95,jR%\xaf\x0bE#Y\x04=z~\x0c\xc7\x86\x8f\x82\x0fI\xb2^\xc44\tz\
3743\x84B\xd8\x13E\xdco\x01o\xce\xde\x1f\x0b\t\x99M\xd54\x13\x81\x89w"\xcdd"\xe4\
3744b\xaeC!X\x8e^\xea\xc0\x12 u\xb3\xd9\x82\x93\xa7\x07\x10\x98\x9e\xc2\xf6\x9e^\
3745\xbc\xf0\xf6\xcf\x91\xe4\xe2`\x8a\xc8\x9a\x90:\xc6\xcf\x96T\xeb\xbc\xaf\x98\
3746\xcf*\x05\xc4^7\x10\x99\xc2Dh\x02\x07N\xef\xc7\x9a\x815\xb8y\xcd\xcd\x92\x01\
3747\xd3\x0f\xba\xf07_\xff\x16\x9e\xd8\xb9\x13\xf6\x9a\x1a\x98X\xb6\xe8hU\x10ED\
3748\xa3Q\xdc~\xddux\xec\xa1\x07Q\xef\x91Z\xc4=\xf7\xc6s86v\x1c\xd3\xf1LA\x88\
3749\x96\x98Y\x9a\x01\'\xf0`iF\xf7\xfc\xe4\x82\xf2\xbc)\x9b5\x13(\xf7@B\xb1P\xd6\
3750J\x8b4j\xf0\xd6\xd6\xa1k\xdd:\xbc\xf2\xda\xeb\xe0g\xb5\xeeJ#\x14\x8d\x80\xf6\
3751Q\xf8\x83{.\xc7\x85P\x026\x1b\xa5\xea\x82\xa4\x94]\xe6\xd3y\xfe\xc4P?\xf6\
3752\xf7\xef+\xcb1\x17\x03\xb7\xdd\xad{\xce\x01\xb5Kc\xf3\xca&\xaco\xeb\xc0\xf0\
3753\xc48\xcc\xe6\xe2WI\x0cMey\xb1kM\xea\x94\x84\x0e \x8b\xd0\x95dN\x10\x8f\x8b\
3754\x88#,\xff[`b\xf28\x9d/X\x9a\xa9\xf8=\xbf\x98\xa8zR\xb7\x98M\x98\xf0\xfb\xf1\
3755\x9b\xdd\xfb\xb1\xfe\xde\x0et6m\xc0\x81\xd3\xfb\x8b\xb6\xce\\\xcc\x8bH\xfeo\
3756\x86f\xe4\xe8``r\x00\xdf\x9a\xfc\x16\xae\xf6o\xc5m[n\xc3\x7f|\xedkhon\xc6\
3757\x97\xbe\xfdm\xd8\xac\xd6\xa26QE\x1dB\xff\xde\xaf\xbe\x8f\xc3g\x0f!\x91N\xc0\
3758j\xb2\xc2j\xb2\xe6=\x07\xa5\x9e\x1f%\x99k\xfb|\x12\xf7C@_:S\x9aM]\xdf\xbb\
3759\x19?\xf9\xf9\x0b\x08\xc7f\x16DW\x07$\x8f\xf1\x06\xeb*\xd8\xeb%\x92\xd1#\xf4\
3760\xf9F\x87\xc7\x86\x8f\xe2\xc0\xe9\xfd\x00*\xd8\xfd*\x0f\xc8x#\r\xb6\xb5+\xa8\
3761\xe6\x95M\x98\x0c\xfa18<R\xb2\xfd1\xd9\xf4$\x86^\xa4\xf94l\x00l\x00\x13O\x82\
3762o\xb0@0\x89\xa0G(`\xd6r=\x1aJ\x00\x00\xdc\xb4#\x8b\xd8m6J\x15\xa9s\x82\xf4=\
3763\x963\x19\x97\x0bU\x9f\xa7\xce\xd04X\x96\xc5\x0fv\xef\xc2d\xd0\x8f\xdb\xb6\
3764\xdc\x06\xbb\xc5\x9e3ox\xa9\xc0n\xb1\xc3j\xb2\xe2\xc0\xe9\xfdx\xec\xe5G1\x19\
3765\xf4\xe3\xc1\x1d;\xf0\xb9{\xfe?\xc4\x13\x89\xa2\xd2\x0c\xe3\x89\x046un\xc0c\
3766\x0f=\x08\x00\xf8\xc6\xb3\xff&\x13\x89\xddb\x07S\xc1\xc8\xa4P\xd2S6h\x00\xd4\
3767\x1dr\x02\xd3S\xd8\xb8\xb6\x0b7]s\r\x04a\xfe\xdd\xee\x0b\xc5x\xe4\x9c\x9c&[L\
3768\xd3\x04=C\xb6\\ Q\xb1\xd5d\x85\x855/\xc8\x8f\xdd\xec\x90\x7f\x9c6\'\x9c6\
3769\xa7L\xe8N\xaa6\xcbk\xfd\xc8\xe9\xa3\xb8p\xa1\xb4\xec#\x86a\x90H\xa5T\xd6\
3770\xd2\xd6\xba\x1a\xd5\xf5\xf5\xd8\xea\xc0\xb8\x93p\xa6\xebao\xf0\xc1\xe1\xb0\
3771\xc3\x15t\xc0\xe1\xb6\xc2\xe1\xb6\x82I\xd5\xc2M;\xe4\x1f\x000\xf1\x199.\x1e\
3772\x17uW\x82\x06\xf4Q\xf5\xa4.\x88"jl6\x1c\xe9?\x85\xc7\xff\xdf\xb3\xa8\xf7\
3773\xf8p\xfb\xe5w\x80\xe3\xd3K\x9e\xd8\x19\x9a\x81\xddb\x977s\'\x83~|\xeb\x81\
3774\xbf\xc5\xb5W]\x89h\xac0\x92\x11E\x11&\x96\xc5W\xff\xfa\xcfQ\xef\xf1\xe1\xb9\
37757\x9e\xc3\x91\x897e2\xaf$\xc8\x8dF\x88\xbd\xd8L\x11m?\xcb\xbbn\xff \xdcNg\
3776\xd1\x9d\xedKA8\x12\x91W\x11\xb9\x08\x9d\x13R\x99\xef\xa8\xd8k\xd0\xb3\x16\
3777\x9e\x0b\x0b\x19a\xeaM\xb4$B\xd7\xab\xf58\xfb\xd6 \x12\x1cW\x9a\xecEQ\x98\
3778\x89f\x9f?e\x07$2\x913\xee\xcc\xf8\x10ZD\x99\xdcm\xbe\xa4L\xf0\x00T\x04\xbfR\
3779l\x84\xcb\xec\x82\x89w\xc2&\xd6\x81\xa5\xcd\x06\xc1\xcf\x81\xaa\'u\x02\x13\
3780\xcb\xe2\xdbO>)\xe7\x9d\xdf\xf4\xbe\x0f/\x0bb\x07\xa4(n"4\x81\'\xf7\xfc\x04\
3781\x00\xf0\xf5\xbf\xf8\x0c\xbc\x1e\x8f\xec\x0e\x98\x0f\xf1D\x02W^q9\xb6\xf7\
3782\xf4\xa2\xaf\xaf\x0fo\x9cyc\xc1\x96\xf9\xf9\xa2t%QFunz\x02%\xb17\xafl\xaaX3g\
3783%H\x15\xab\x96\xe0\xe2\xc9d\xd6\xe6(y\\\x0c\xe9\xa4\x9cP^\x0f\x12\xa5\xd3\
3784\xbe\xd9>\xba\xf3hXA\x01Hp\x1c\x02\xe3\x19\xf9D;Y\x93\xd5\n\xd94%P\xea\xec\
3785\x00\x10\xf7[\xc0\xa4\xb2kQH\xf4\x0eH\x11\xbc\x89wJr\x92A\xf2\xbaX:\xa4n2!\
3786\x14\x89\xe0\xee/=\x88\x13C\xfd\xb8k\xdb\x9d\xb8{\xcb=\x00\x96~\xbfA\x86f`5Y\
3787qd\xe2M\xec:\xb4\x07\xdd\xdd\xdd\xf8\xf8-7#63\x937\x1b\x86ts\xff\xe3\xdbn\
3788\x05\x00\xec\x1d\xfc\x1fp|qy\xfc\xe5\x80\xdeMU\xe8\x9e\x07\xd1\xd5I\x9a\x9b\
3789\xd7\xe3\xaex\xa4\xae\xf4o\xd7\x1b;\xca\x08]\x0f\xc5\xc8/\x04\x0b\xd5\xf4;\
3790\x17\x94Z:\x99d]1\x89@_=|h^\x15\xd6\x82\xc0cpdL\xf5\x9cR~Q\xf6C\x8d\x98&\x01\
3791H\x84N,#\\A\x07\xe2~\x0bl\xbe\xe4\x9cY1\x04Do\x8f\xc7\xa5\xcfH\'M\x15%\xf7\
3792\xa5\x94\xa7\xbedH\x1d\x90\xdc\xf4\xfaO\x0f\xe0\x8e\x1d_\x90#\xf6\xbf\xba\
3793\xf1~4\xb8\x1b\x10K\xc6\x16\xfd\xc6\x99\x0f\xc8\xf2\xfc\xe5S/a2\xe8\xc7\x9f\
3794\xfe\xe1mh\xf0\xf9\xe4\nT=$S)4\xf8|\xd8\xd8u\t&\x83~\x9c\x99>\xb3(\x11%\'\
3795\xa4\xb2$\x18B\x96\xcahX[\x13\xa0\xd7y\xbe\xb5q\x95lEPI\xc4\xe2q\x15\xa1k\
3796\xa5\xa3|\xab\x90R\xe4\x97\x85\x84\x1e\xb9\xe9E\xea\x80\xb4\x91;\xee\xf7\x97\
3797\xec\xc1C\xcd\x06\x16J2\xb6\xd6\xd5\xa8\xe4\x17\x8f\xad\x0e|\xc8"\xeb\xea\
3798\x80$\xbf\xd0#\xd2\xe4\x1a\xf6Da\xf3%\x11\xf7gW5\x87\x84\xa8*R\' )\x8f\x046\
3799\x1bU\xf6\x94G%\x96R\x9e\xfa\x92"u@\xcag\xee?=\x80\x8f\xdc\x7f?\x9ez\xe1y\
3800\xaco\xeb\xc0_\xddt?nz\xdf\x87\x01`I\x93\xbb\xddb\xc7Dh\x02GN\x1f\xc5\xfa\
3801\xb6\x0e\\\xb5qc\xceh\x9d\x9e\xed\ry\xd9\xda5X\xdf\xd6\x81\xd1\xc1\x11L\xc7\
3802\x83\x0b\x16\xa5\xe7;\xc7\xf1d\x12\xdcL\xee\xe3 \x91\x9b\x97\xf6d-\xd5[W5.\
3803\xa8/\x0fP\xd8^\x00\xc7\xa7\xb3\x8e\xb5\x18T\x93\xa6N"u\x8f\xad\x0eo\xf4\xbf\
3804\x8d\xd1\x89I\x98M%\xfa\xa9\x8b"X\x96UY\x05h\xcfS0>\x05\xc6\x9d\x94\xe5\x17%\
3805\x99\xbb\x82\x12a\x93H]\x0b\xbd\xcc\x18@\x8a\xd4\xc3\xa90l\xb6YO\xf99VW\xbfK\
3806\xa8\xfa\x94F=\xb8\x9cN\x8cNL\xe0\xcf\x1e\xfc2\x0e\x9f8\x89\xbf\xf9\xf4\x9f\
3807\xe2\xaemw\xa2\xb3\xb5\x0b\xfb\xfb\xf7\xcd\x99\x97]n(s\xbb\xcbq\xf3\x1e\x1b:\
3808*\x15\'uvb\xe7\xee\xddys\xb7[\x1b$\xd3\xa9>d\xf7\xa7,\x04\xbc\xc0#\x91N\xe4\
3809\xfc{\xbe\x82-\xe5yei\x06\xbc \xd5\x0f\xb0\xb4\x19lM\n\xd3A\xe9\xa6\x8c\xd4N\
3810\x03Q)B$\x11\x1b E\xea\x84\xd8\x89\x0c\xa3\xd7\xc7\xb4\xdc\xa0iZ\xd7{$_\xa4\
3811\xc72\xa6\xac~\x9f\x85\x80L`\x0b\xb5\xf7#\xd5d\xe4&7\x92\xf9B\xfb(\x04cS\x18\
38129\x1eA$\x1a\x85s\x1e\x16\xb9V\x8bEu\xdd\xacu5r6\x0c\xf9\xfe|\xc8"K/QO\x0c\
3813\x88JD\x0e_TJm4\'\x10\rI>2\xb5^\xb5\xe4\x12N\x85\xe12\xbb\x10N\x85\xa1\x05\
3814\x91_R\x88\x01X\xda2l\xb9\xb0$I\x1d\x90\xa4\x18\x9e\xe7\xf1\xc8\x13\xff\x17\
3815\xbb\x0e\x1c\x90+(?}\xc3\xa7d\x9f\x953\x81\x01D\xa31L$\xce\x95\xe4\xe1Q\x088\
3816!\x05^\xe0\xe5\xa2\x08B\x90,c*)j\xb6\x9a\xac83}\x06\x00\xd0\xdd\xdd\r\xaf\
3817\xc7\xa3\x9b\xbb\xcd\xcf\xea\xce\xc4\xa7<<X\x9c\xc5)!s\xab\xc9\x8a5\xf5k\xe0\
3818\xa4j\xd1\\/\x99m\x95R\xb9K@\nOH\xc5h\xce\xd7\xe5\xb0\x0choi\xaa\xb8\x1d/\
3819\xd1\xec\xe3\xc9\xa4*\xb7\x9e\xa5\xcdr\xc4\x97w\x13\xb8\x84\x94\xc6\xb9\xc8\
3820\xb6\x9c($j\x15\xfc"`\x07\xee\xb8\xe3j\x0c\x8f\x9f\xc3\x89\xf7N\x97l0\'\x8a\
3821\xa2l\xd4\x06d\xf2\xd4\xadu5hF\x93\x14\xb9\xdb\x01\xa0E\x92\xdc\x88\x0bC[\
3822\xe63\x02\xe3Qx\x1b\x1d\x08\xc6\xa7\xe4\x89_\xfb\x08dKx\x95D4\x1a\xc3\xc5\
3823\xf6\xb5\xaa\xe7\x96BO\x86%K\xea\x80\x94#\xebv\xb9\xf0\xde\xd0Y\xdc\xf3\xc0\
3824\xdfac\xc7\xa5\xb8\xf9\x9akq\xfb\xef_\x8b\xed=\xbd\x00z\x01\xa8}N\x88\xe7D\
3825\xb9\x1e\x95HL\xcd  \x04ql\xe8(\xceL\x9f\xc1t<XR\x91T,\x15\xc5\x89\xa1~4\xb7\
3826\xb7`e\xdd\n\x84"\xd9\x9b4\x82 \xc0f\xb5\xca7SD\x9c.xEB&\xa1\xab\xd7n\xc5\
3827\xd6\x8em\xf0\xd6\xd6U\xc4\x8eTk\xf6U\x08\xbc\x8d\x0e\xd4X-\xe0x\xbeb\xae\
3828\x8dz\x9f\xab$\xf1\xb9\xb4\xd9b4\xf5\x8dk\xbb\xd0\xbc\xf2\xef\xca>\xeer=\x1e\
3829\x1b>\x8a\x97\x8e\xbf\x04\xd6l\xce*\x06S\x9d\x03\x1f\x05\xc4\x80\xce\xd6.|\
3830\xef\x81\xb5\xb8~\xc7\x0e$R\xa9\xa2\xcf\xb9\x08\x80\xa2iU\xba"\x00\xb5\xd7P[\
3831Q\x1fY\xd5\xa8vB\x07\x968\xa9\x03\x99<vA\x10p\xa4\xff\x14\x8e\xf4\x9f\xc2cO?\
3832\x8d\xcb\xd6\xaeA\xd7\xbau\xe8\xa8\xf3b\xf5\xe5\xedY^#\xe5z$\xf0\xd6\xd6\xa1\
3833\xbe\xad\x03\x80\xe4<8\x19\xf4\xe3\xb97\x9e\x93R\x0c\x8b,\xfe\xe1\xf84F\xcf\
3834\x8f\xc1[[\x87F\x9f\x0f\xfd\xa7\x07r\xbe\xd6\xdb(-\x9b\x03\xe1\x0b\x05\x97\
3835\xf8s\x02\x8f\xeb7\\\x8f\xbb\xb6\xdd\t@2\x17;x\xf0\x1d\x0c\x8e\x8e"\x1c\x8d"\
3836\x14\x8d \x1c\x89\xa8"R\xbb\xcd\x86X<\xae\xfb\xa8\x07\xe2\xe5\xa2\xb4\xe1\
3837\x05\xa4.Td\xa9N\x8c\xa5\xbc\x8d\x8e\xd9IX\x8a\xf4\xdd.\x17&\x03\x81\x8a\x91\
3838\xba^vM\xbe\xe8\x9c/\xc0\x13G\xfe\xecY\x8d\x19\x90\xfcv\x94\xd9=\x95|\x04 \
3839\xed\xad(\x8a\x80\xb4+\x11\xd5q\xceF\xea\x80D\xc0\x0e\xbb\xbd\xa4\xd5\xac(\
3840\x08p:\x1c\xb8\xd8\xbe6sLB\x10\xcd\x90\xdc?\x1f~\xf4Q\xd9e\x15X\xdc\xd6\x92\
3841\xa5"\x1c\x8d\xe2\xcd\x93\'aS\xd8\x18W3\x96<\xa9\x13\xd04-\x93\xfb\x85\xd04^\
3842y\xedu\xec;x\x084\xcd\x80\xa1)X\xccf\xb99/k2\xc9}\x1c\xe7\xfbH`5\x9b\xe1\xf5\
3843\xb8\xd1\xda\xb8\n\xdb;;q\xcb\x1d\xb7\xe2\xd37|\n\xee}n\xbc\xf8\xf6\x7f\xcfY\
3844\xa6\xafE0>\x85z\x8fO\xd6\xcc\xb5\x10\x04\x01&\xc6\xac\xca2(\x04I.\x85\r+;q\
3845\xd7\xb6;qb\xa8\x1f\xf7|\xf1\xefq\xf4\x9d\xf7 \x08<8\x9d~\xa1D\n)d\xf3Rn\x06\
3846B\xd3\xb0\x9aL`M&\xd9\xab\xddn\xb3\xc1\xe5tJ\x93F8"\xf9\xac\xbbj\x81\x911\
3847\x00Mr4o\xad\xab\x81\xd7\xe3\xc69\xffyTj\'\x84aY\xa9a\x83\x90\x02`\x99S\xae(\
3848v\x9f\xa4\xc6f\xc3+o\xbc\x81}\x87\x0e\xc9c\xa4\\\xe3M\xef1\x95L\xa1\xf7\xea\
3849\x0f\xe0\xbf\xbe\xf3\x1d\xd5q\x10B\xd7Z\x03\x00\xc5;\x9e\xe6\x82 \x08\xf0\
3850\xb8\x9c*\xa9\x8d\x8c\xc9\xc9\xa0\x1f\xaf\x1e>\x84\xa3\xfd\xa7\x00HM2DQD\x9a\
3851\xe3\xe5\xf1\xb6\x14\xc0\xb2,\xac\x16K\x955r\xc9\x8deC\xea\x044M\xc3j1\xcb\
3852\x1a\xb4 \x8a\xa0\x00p\x82\x00n6B\x13\x93IP\x14U\x96G\x82iA\xc0d \x80#\xfd\
3853\xa7\xb0s\xf7n<\xf2\xf3\xe7\xf1\xec\xa3\xdf\xc4]\xdb\xeeD\xff\xc4I\x9c\r\x0c\
3854\x17E\x0eD;\xcc\xd5\xc4\x83\x17D\xd4\xba\xa4\xc8a2\xe8/*\x9d\xeb\x86\x9e\x1b\
3855\x00\x00\x9f\xfd\xca\xbf\xe0\xf0\xb1\xe3\xb0\xd7\xd4\x80\xa1,\x80\xad\xb4TB\
3856\x92\x9d\xa3\x9c4I\xc3c%\xa1\xeb\xa5\x85\xd5\x9bL\xf06:d\x99\xc6[[\x87\xd6\
3857\xc6U8\xd2\x7fJj\x97V\t\xcb\x001\x93\xdb\x8c\x02\x95\x14\xa5\xb4U\x88\xa6.\
3858\x88"\x12\xa9\x140Kd\xe5\x1aoY\x8f4\x8d\xe8L\xac\xe0<\xeah4\x06W\xbbC\xdeS\
3859\xf4\xd6\xd6a\xcf\xfe\xd7JnRBz\x94*\xe5He\xa4\xeev8\xe5\x822\x87\xdd.\x9d\
3860\x13 \xd3\x01\x89\x07 \x16\x90\xedD\xcd\xde;\xf4\xe2\xb7\x0f\xacv,\xb9\x94\
3861\xc6bA\xcfv\xb7Q\xf6\re\x18\x064M\x97\xe5\x91\xfc\x98L&\x98L&\xd4\xcc\x12X\
3862\xff\xe9\x01|\xe3\xfb?\x06\x00t4\\V\xd41g\xb9(\xb2lN?\x94b22\x92\\\n^g\x1d\
3863\xbc\xb5u\xe8\xeb\xeb\xc3\xe1#Ga6[$\x0f\xed\x05\xb8Y\xb4\xbd1\tH5"\xd1`[W5\
3864\x82\xe3\xb8\xca\xf9\xaaS\x94\xaa\xb7\xea\\\x1b\xa3Z\x14\xaa\xa9\xd34]\xb6q\
3865\x96\xeb\x91\x9d%\xe2B\xf3\xa8\x1d\x0e\xbb$\xbd\xcc"0=\x85=\xaf\xff\x16\x89\
3866\xd9\x89\xa2\x18\x90\x1e\xa5]\xeb\xd6\xc9\xcf\xc9f^:\xd0\x12\xba\xf4!"\xc0\
3867\xd0s\xff\xd0\xa2A\xe8\x05b\xd9\x93\xfa\xa2@\xa0\xc0\xb2,^=x\x10\x93A?:[\xbb\
3868fS\xfe\n\xcf\xbf\x1e\x8fH\x9e\xe7+\\\xb5\xba\xcb\xd4Rr\xb99>\r\xbb\xc5\x8ez\
3869\x8f\x0f\xa7\xc6F\x10\x9e\x89U\xcc\x11\x91\xdc\xc0Zh\x1b\x11+K\xd4\xc9\x86\
3870\xf6B\xa45\x02\x92\x9dk\xa1\x98o\x9ez\xa5\xa1\x8c\xd4\xe7\xda[!\xf6\x00\x1e[\
3871\x1dF\xcf\x8f\xe1\xd5\x83\x07ab\x8b_\xb4\x0b\xa2\x08\x9af\xb2\xd2\x19\x95EeK\
3872\xa9\x12s\xb9\xc0 \xf5J\x80\x96L\xb6\xa2\xb1\x18F\x07G\xe0\xad\xad\x83\x85-\
3873\xae\n\xb1\x98~\xac\xa5|\xae\xb6\xac{\xa1\xa0\x8d\xd4\xeb\x15:%\x89\xd4\xdb[\
3874\x9a`\xb3Z+g\x17 \x8aH%\x0b\x8f\xccy\x81/9O}\xa1\xa0\x8c\xd49\x81\xcf\xab\
3875\xa7\x13{\x00\x00xi\xcfA\x8cNL\x94\xd4\xc6N\x10\x04X-fl\xde|\x89,\xbdh\'>\
3876\xb7\xc3\xa9\x9a\xe0\xb9y\xf8\xcc\x18(\x0c\x06\xa9W\x10\xb1x\x1c\xa7\xc6FP\
3877\xef\xf1\xc1f\xb1\x14e\xf0\xaf5\x9b*\x97\x1d-\xb9\xb1IF\xc2|;-)\xc1\xe51 \
3878\x0b\xcf\xa6e\x16\x12\xa9\xc7&\xfc\x92=B\x05\xd2\xc7DQ\x84(\x8a\xd8\xb4\xb1\
3879\x0b^g]A\x19\x1f\xda\xbd\x90R\xbc_\x16\x12\x85dA\x91\xfc\xf9\xa3\xef\xbe\x0b\
3880\xa0\xb4\x86\xd3\x89d\x12\xeb\xdaV\xa3yeS\xce\xd7\x18\x91\xfa\xc2\xc3 \xf5\n\
3881\x81\xa6i\x95\x87I\xb1\xf9\xea\xcb\x016k&\x9d.W\xbb:\xbdH\xfd\xf53R\xf1\x15[\
3882\x81\x94FQ\x14\xc1P,:\xb7\xac\xca\xba&\x85\x16\x07U\x9b\xf7\x8b6cI\x0b\xe5\
3883\xaa\x8fH/\x00p&0\x80\xa3\xfd\xa7`1\x97&\xc1q\x1c\x87\xab\xbb\xbbu\x0b\xc9\
3884\xb4PE\xeb\xb3\x93\x7f%{\xd1\xfe.\xc3 \xf5\nB\x10x\xc4&\xa4\xe8\xb3RrJ\xa9X\
3885\x88|ae)~8\x12\xd1\xdd\xcc#\x91\xba\xb6Iq\xa5\xd2\xddh\x9aF\x9aOa\xe4\xb8\
3886\xfa\xfb\x97+O}1\xa0=Wde\xa1\xe7\xa1\xee\x8a\xd5\xcaQ\xfa;G\xa6\x10\x8aDJ\
3887\x92^x\x9e\x87\xd9l\xc1\xa6\xf5\xea$\x00=\x93\xb6\\(\xe7*\xd1@\x06K\x86\xd4i\
3888M\x06K\xa1?\x8b\x05\x92I\xa0\x94\x17\xaa\x99\x18\xca\r\xab&\xfas9\x9d\xbaKq\
3889\x12\xa9++N{\xaf|?\x1cv{^9g>Hs\x1cB\xd1H\xc1\x96\xcd\xd5\xdcB\x8d-\x80\x90\
3890\x95~/@\xc6\x8f\xe5@_i~A\x80\xe4\x10\xba\xca\xb7\x12\x976\xb5\xc8\xcf\xe5\
3891\xca|\xc9\xa5\xa3\x1b\x91ze\xb0$\xf2\xd4y\x9eGb\xb6`\xa1\x18\x90b\x98R"\x91r\
3892\xa3\x982\xfeJBYt2\xd7\xb2\xbdX(\t&\x91Je\x11\xbb\x1e&\xd3i\xb4\x93\xdf\x15\
3893\xd1z%=6L,;\xab\xf1{a\xb3X$W\xc99R\x1a\x8b\xcdS_H\x14z\x1dI\x15\xa9\xc7V\x87\
38943\x81\x01\xbcs\xe6\x8cJ"+\x06\x1c\xc7\xe1\xf76oFs{\x8b\xaa\xaa\x95D\xea\xc4\
3895\xba\xc0\xc0\xc2cI\x90\xba\xcdb\x81\xd3Q\x1a!r\xe94R\x8bX\xb9F"\xd1j\x91_\
3896\x94\x86H\x95\xac\xe8\xcbE\xe8se\xbfL\x06\xfd\xd8\xf3\xfao\x11\x9b\x99\x81\
3897\xdb\xe5\xaaX4\x17\x08*\xceC\x01&^JT\x8b\xa6\x9ek%\xaa\xdc\x1b\xc8UE\xfa\xdb\
3898}C\x88\'\x92\xa8)\xe1\xbb\xa4\xd3ix=\x1e\xdcu\xfb\x07\xe5\xe7d\xa7\xcd\xf3\
3899\x12\xa9+\t\x9d\xe3y\xb9\xfa\x95\xc0\x88\xd2+\x87\xaa\'\xf5t:\x8d/~\xf2\x93\
3900\xb8\xf7\x8f\xee\xc8\xf2[)\x04\xbf\xd9\xbd\x1f\x9f{\xf4Q\xb9`c\xa11\x9fVa\
3901\xcb\rn\x87d\x13\xa0W\x80\xa44\xfd\x1a\x9e\x18/j\x15!\xccZ\x13\x17\x1a\xd9\
3902\x93q\x10\x89G\xe0\xb49\x0b\xda \x95\xf3\xd4\x97H\xf4\xa9\xd4\xd3\x1d\xc8l\
3903\x08\x13=\xfd\xb7G\xdf.y\x834\xcdq\xd8\xd6\xd3\x83\x8dk\xbb\xe4{\xd2ZW\xa3Jg\
3904\xcc\x15\xa9WJR+\x14\xe9t\xba\xe8&,\x0cM-\x19\x8b\x00`)\x90:\xc7\xa1\xbd\xa5\
3905\xa9$\x17\xc1z\x8f\x0f\xa3\x97\x8f\x81\x17DT\x81\x02\xb3(\xad\xe6\xf2\xa1\
3906\xdc\xf2\x8b\x12\x89T\n\x9e\x1c\x16\x07J\xeb\x03"\xbf\x90\xeb\x1b\x98\x9e\
3907\xc2\xb8\xdf\x0f\x13\xcb\x16\x1c\xcd\xd14-\xa7*\x16B\xec4M!\x1a\x8b\xc96\x01\
3908sy\xbfT{\x9ez>\xabb\xb2B$\xae\x8c\x80\xe4\xd7?21\t\x9a.M\xde2\xb1,\xde<y\x12\
3909GN\x1f\x95\x89]\x9b\x9f\x9eKza\x19fQ\x89\xbd\xd6\xe5*\xe9}\x89D"o_\x83jB\xd5\
3910\x93\xba\x12\x8f\xbd\xfc(\xce\x06\x86\xe7$F^\xe0\xe1\xb6\xbb\xf1\x8d\xbb\xbf\
3911\xa1j\x88\xbb\x18 \xf2B5j\xea@\xe5\x96\xc1V\xb3\x19\xf1DR\xa5\xd9\x86\xa2\
3912\x11\xb464f\x0c\xbd\xa0\x96_\x00`\xf4\xfc\x18\xceO](h\x1fD\x10\x04X\xcdf|\
3913\xfc\x96\x9bq\xa0\xaf\x0fG\xfaO\x15$\'Hn@\xa4\xa2\xd4:\xa7\xf4\xc2\xd0\x0c\
3914\x12\xe9\x84\x1c\xa9W\x83\xa6\x9e/\t\x80\x14\x1e)%?e\xc1\xd1\xeb\xfb\xce \
3915\x95J\xc3l6\xa1\x14\x8a\xa2i\x1a\x81`P\xba\xb7f\xed\xc6\x95\x8d1\x00u\xa4\
3916\xae\xdc\n#\x84^1_\x9f<H$S\xf8\xe2\'?\x89\xdb\x7f\xff\xda\xa2\xdew\xe4\xe8;\
3917\xf8\xe2\xbf~\x13\xd3\xe1\xf0\x92\x88\xd8\x97\x14\xa9\xc7\x92\xb193H\xd8\xd9\
3918\x1b\x90\x80X\xd3.\x16\x88\xfcR\xad\x9a\xfa|o.-\xb9(\x9d+\xb5\x9bpD~i]\xa5\
3919\xef<\tH>0zMA\xf4\x90Js\xd8\xfc\xbe.|\xeb\x81\xbf\xc5\xc3\x8f>\x8aw\x87\xce\
3920\x82\xe7\xf99\'\x04\n\x12\xb9\xd0\xbc$K\x14\xaa\xa9\x13\xa3\xaaj\xd1\xd4\t\
3921\x18M\xc4\x1dO&e\xf9\x85\xe8\xe9a\xfb4V\x0b\xed\x08\xc6\xa7\xd0?(Y9\x97\x9a\
3922\x1d\x96\xe6x\xacim\xc6\xc6\xaeK\xe4\xe7H\xe6\x8bnJc!\x86]\x15\x06\xf1\xa9io\
3923i\xc2\xfa\xb6\x8e\xac\x14\xda\\P*\x04\xbc V\xcc9\xb4\x9c\xa8zRWn\xe6\xd9-\
3924\xf6\x82,l\x95\x11\xf1bF\xea\xcae\xb1\xb6Bt\xb9BkI\xac%@\xad\x9eN\xe4\x17\
3925\xa2\xa9\xc7&\xfc\x10\x04~\xce\xc9\x86\xa6(\xa4RI\xfc\xf1m\xb7\x02\x00n\xdc\
3926\xb2\x05\xaf\x1e>\x84\xd7\xdf|kNR\'\x9f\xaa\x8c\xd4\x0bE\xbd\xc7\x87\xd6\xc6\
3927U8|\xecx\xd5n\xf6\x91H]\xa9\xa7\xbbb\xb5\x80\r\x18\x9d\x1c\xc3\xd8\xa4\x1f&v\
3928~z\xa4\xd6\x99Q\xa9\xa9\x07\xe3S\xd8X\xdb%\xbfV,Q\xe6\xa9$\xe6Z\xf5\xf3\x02\
3929\x0f\x86f\xf0\xd0\x1d\x0f/\xf0\x91\xcd\x1fK&O\x1d\x98\x8d\xd4\x8bLk\\\xecH\
3930\x9d`\xbe\x91z%\xb4\xbcrj\xeaJ\xdb\xdd\xb9\x10\nGT\x8d2\x80LD4\x99N\x83\xa6\
3931\x999\t}&\x91\xc4\xa6\xce\r\xe8\xddz\x15&\x83\xfe\xa2\x8a^\xc8\xf1\x92H\xbdP\
3932\x9c\tH\x11n\xbe\x95F5A\xa5\xa7\xcf\xe2\xf5}g0\x13\x8f\xcf+i@\x10\xf8\xacB2\
3933\xd2\xf5\x0b\x906c\x95I\r\xe5\xb2\xb8(\x17&\x83~y\xd5\xcf\x0b\xbc\xee\x0f\'\
3934\xf0\x0b\xd6W\xb6\xdc\xa8zRW\x12O)\xa5\xf6\x81\xf1h\xd6\xf2t\xa1 \x08\xbcJ3.\
3935\xb5\xf8\xa8\x92\x1b\x9a\xe5\x8a6sm~\xe9\xe9\xcfn\x97S\xf6\x9e\xd1f\x07]\x08\
3936O\xcfY\x8f \x88"\x04\x81\xc7\x9f]\xb7\x1d\xf5\x1e\x1f\xea=>\x04\xc6\xa3\x18\
3937\xf7\x17\xb6\xa4\x86(\xaa&\x1fNH\x15\x94\xa7N\x9c37\xad\xbf\x0c,\xcb\x82_\
3938\xa4\r\xbfbe\x13b\xb5\x1b\x8cOax\\\xfa\x0e\xf3\r\x12\xf42\x98\x8am\xd8\xb2X\
3939\xa8\xf7\xf8d.af\xbb\x92i\x7fX\x9a\x91\xa3\xf8jv\xe7\xd4C\xd5\x93\xba\x12\
3940\xb1d\xac\xe0\x96m\x04\xdeF\x87d\x0e\xb5\xc0\xd0F\'\xa5n\x94\xb6\xb74I\xde\
3941\xe2er,Tn\x94\x12M\xbd\x1c\xc8U\xd9H\x1adh\xa1\x8d\xd4\t\x86\xcf\x8d\x03\xc8\
3942O\\3\xf18.n[\x8d\x0f\\\xb7U\xf5\xbc\xd2\x96 \x1f\xc8\xb51Y\xa4\t\x85\xc8/\
3943\xf9d\x18\x96f0\x11\x9a\xc0d\xd0\x8f\x8d]\x97\xa0\xb9\xa1\x01in\xf1\xb5b`n3.\
3944\xb7\xdd\r\x8f\xad\x0e\xa1X\x08C\xa3c%\xd9\xecf}\xa6&\xb3Ik\xb9\xab:>TA\xea\
3945\x99\x02\x85\xea\xe9\x04\xd5\x9a\xf5\x94\x0bUO\xeaZM\xbdX\xf9%0\x1e\xadxwz=\
3946\x08\x82\x00\xab\xd5\n{\x83$+\x14Z\x92\xae\x07\x96e+gC[\x01\x14RI\xaa\x85\
3947\xf6F\xcb\xb5\x82\x10E\x11\x1c\xc7\xe1\x0fo\xfc}\xac\x9f\xed\t\x0bH\xce\x8e\
3948\xd1X\xe1\xe7\xd8j6\xc3is\xaa\\\x1a\xe7\xca\x80\x89%c8r\xfa(\xd6\xb7u\xe0\
3949\xf76oF*U|O\xcf\x85\x82R\xee\x0b\xdb\xa5UQ4\x1a\x9bwCoQ\xc7C\x1d\xd0\xb7\x08\
3950\xa8\xf7\xf8$\x99\xa6J\x9a[p\x1c\'\xcb\xb1\xb1d\xac*\xb2\xd1*\x81\xaa\'\xf5\
3951\xf9\xc2\xdb\xe8X4\xf9\xc5QS\x83K\x9bZ0\x19\xf4#\x9eL\x16\xb5\xca\x88%cR{\
3952\xb7FGY"+\x02e\xf6\xcb|e\x9db\xa2|\xb7\xc3\xa9_t4+\xbf\x90j\xd2\xe1\xf1sy\'\
3953\xe1d*\x8d\xb6\xe6f\xdc\xb8eK\xd6\xdf\n\xcd\x7fNs<\xbc\x1ew\xd1r\x1e\xcb\x98\
3954\xf0\xf2\xa9\x97\x00\x00_\xfc\xd4\'\xd0\xe0\xf3I\xfa\xf4"\xe5.\xb3\x0c\x03\
3955\xd6d\xd2\x95R"\xe2\xb4\xae\x7fz\xa5\xa0\x8cf\x89\xb7L\xb5\x81e\xd9EOq^\x08T\
3956=\xa9+\x89\xa7\x94h70\x1e-\xba\x82\xac\x1cHs\x1c\x9a\xeb}\xe8\xee\xee\xc6\
3957\xe8\xe0\x08b\xa9h\xc1\xc6P,\xcd\xc8\x11\xa4\x97\xf6\x94\\\xf9\x97\x0fn\x97\
3958\xb3\xa26\x01\xdat\xc6P4\x92\xe5\xa5\x0ed\xcb/\x81`(\xef$,\x08<\xae\xb8\xec2\
39594\xb7\xb7\xc8\xd1=y\xb4\x16\xd8\xed=\x95J\xa2\xb5qU\xd6\xe6\xf5\\Y0D\x82yz\
3960\xdf3X\xdf\xd6\x81\'\xfe\xed\xcb\xa8\xb1\xd4 \x14\x0e/\x9a\x81\x9c\x9eYV\xae\
3961\xc9*\x1aJ\xc8}z\xf5 \x8a\xe2\x9c\xfb\x04\xd4lj\xe0\xe0\xe8\xa8\xeay\xedF)\
3962\x81\xdeD\xbe\x18 ]\x9aJM\x9c\xb0\x9a\xcd%u\x1b[\x0cT=\xa9+Q\xcaF\xa9\xb7\
3963\xd1\xb1`\x17\x83T5&f\xbb\xea|\xee\x13w\x03\x00\x0e\xf9\x0f\x17\xbdI\x9a\xe4\
3964\xe2\x08LO\xa1\xb9\xbd\x05n\x97\xabl\x13S4*M\x8c\xe5l\x19\x97+\xf3E\x99\xceH\
3965"\xf5\\\x8d\xb4\x01\xe0\xe7\xcf>\x8f\xf3\x17.\x80\xc9\xb1\x82\x10\x04\x01&\
3966\x96\xc5\xf6\xceNU\xfep\xbd\xc7\'\xcb\\sI\x0b\x84x]\x0e\x07\x9a\xeb\x9bT\x92\
3967\xcb\\\x9b\xa5\x0c\xcd\xc0j\xb2b\xcf\xc9\x97\xd1\xd7\xd7\x87\xed=\xbd\xf8\
3968\xcf\xef\x7f\r\x9b:7 \x14\x0e#:3\x03\x9e\xe7!\x08\x82<\x16\xca\re\xaa\'\xf1T\
3969\xa1(*\x8b<\x9dT-\xa2\xd1\x98*\xf3\x85@/\xb2\xe7y\x1e\xf6\x9a\x1at\xaci\x87y\
3970\x8ej^\x9af0<!\xed}\x90\x02#k]\x8d,\xbf\x04\xe3\x99\n\xd3\xf6\xe6f\xd5{\xc9X\
3971Y\xe8tPA\x14Qc\xb5\xc8\r=\ni\x8e\xb2T\xb1\xa4\xf2\xd4KA`<\n\x8e\xe3\x10\xe5\
3972\xb8\x8af\x91(\xd1\xdc\xd0\x80\x07\xee\xbc\x13\x1f\xbb\xf9V\x9c\x18\xea\xc7\
3973\xe1\xb3\x87`5\x15\x16E\x12p\x02/W0\xea9\xe9\x11\x89\xa2\x18?\x12\x961a"!e?\
3974\xdc\xb8e\x0b\x1ez\xec1Dgf`\xb3ZU7\xfa\\\x11\'\xb9!\x05Q\x9c\xf3\xb5\xb9\x9a\
3975c\x00\xd9Q\xfa\xaec\xc7\x10\x9b\x99\xc9\xf9\x1eQ\x14Q\xebr\xa1\xbb\xbb[\xa5\
3976\xc1\x93\xdf=.\'\xa6\xc3\xe1\xbcy\xea\x1c\xcf\x83eY\xf4^\xf9~xl\x19",\xd4\
3977\xd0\x8b\xa1\x19$\xb9\x14\x1e\xef{\x1c\xb7\x0bw`{O/6\xfe\xa0\x0b{\xf6\xbf\
3978\x86\x1f=\xf7<N\x9e\x1e@$\x1aEb\x81H\x83\x13\x04D\xa3\xd1\xacUPD\x9cF\xa3s\
3979\x95\xec\xccH@*I\xb5Hs<\xacf3n\xbe\xe6Z\xfch\xe7N\xc4\x02\x01\xd09\xd2S\x19\
3980\x9a\xc2\xb8\xdf\xafJ[Tf\x88xlur\xd4~\xe3\x96-\xf8\xf6\x93O":3\x03\xab\xc9\
3981\xb4\xa0\x16\x01dR\x15\x04\x01\xb1\x99\x19l\xea\xdc\x80\xf5m\x1d81\xd4\x8fX*\
3982Zt\xd2\xc5RA\xd5\x93\xba\x12\xa5\xc8/\x976\xb5`S\xe7\x86\xbc\xe42\x1f\x90|]\
3983\x97\xc3\x81\xf6\xe6f\xb4\xb74\xa1w\xebU\xa8\xf7\xf8pb\xa8\x1f\xff\xb1\xff\
3984\xdf\x91H\'\xaa\xa2\xf3\x91\x855#\x10\x99\xc2\x89\xa1~tww\xe3\xe37\xdf\x8c\
3985\x9f\xbc\xf0\x02":\xb2H\xb1\xe9\x9d<w\x00\x00\x1a1IDAT`YV\xb6:\xe6,\x16$R)\
3986\xc4\x13I\xc4\xe2\xf1\xac\x02$\x12\xad\x13=}s\xa3\x03\'\x86\xfa\xd1?8\x90w\
3987\xe2\x15\x04\x01\xcd\xf5>4\xb7\xb7\xe8\xfe\xbd\xd1\xe7\xc3\xd0h\xfe\xde\xab\
3988\xa94\x07\xef\x8a\x15\xd8\xd8u\t\xbc\xb5u\xd8\xf9\xd6\xb3\x00\xe6\x8e\xd2\
3989\x95\xb0\xb0f$\xb9\x14~z\xe8)\x9c\t\x0c\xe0\xb6-\xb7\xe1c7\xdf*O\xe2\xa3\xe7\
3990\xc7p\xf0\xe0;\xb8\x10\x9e\x96\xb3y*\xd5\xd6\xcd\xed\x90|\xea7^z\xa9\xeay=\
3991\xcf\x17\x19\xa2\xa8\xdb*0\x1a\x8ba\xf3\xe6Kp\xe4\xd4ex\xf1\xd5\xff\xc9Y=\
3992\xa9\\5\x92\x02$\xad\xa1W0\x9e\x19g\x9f\xbd\xfbn<\xf6\xf4\xd3\xb8\x10\x9a^\
3993\x94\xcde\x96e\xd1\xe0\xf3\xc9+\xe7\xfd\xfd\xfb\x8a\xf6aJ\xa4R\x8b\x92pQ\n\
3994\xaa\x9e\xd4\xb5y\xea\x81Hq\x9b0\xdd\xdd\xdd8\xf4\xb3\xff*\xf7a\xe5\xc5\x89\
3995\xa1~\xec=\xb6\x17\xfb\xdf=\x80X*Z2\xa1\x17\x93J\xc5\xd2f\xf0Ba\x9e$\xffy\
3996\xf0i\xdcW\xbb\x03\xff\xf1\xb5\xaf\xe1\xae\xdb?\x88\x83\x07\xdf\x915\xd2p4Z\
3997\x10\x01\x91\xc9\x8c\xbcV[\x8cB\xe4\x00B\xe0\xca\x14F"\x95\x104\xaflBbj\x06\
3998\xc1p$\xef\x8d\x93\xe6\xb8\x9c\x93\xf3\xa5M-\xe8Z\xb7\x0e\xbf=\xf2v\xde\xe3f\
3999h\n\\Zr\\\xaco\xeb\x80\xd3\xe6D$\x1e)\xcaz\x17\x90\x88\x9d\x17x\x1c8\xbd\x1f\
4000\x87\xcf\x1e\xc2\xa6\xd5=\xb8\xc8\xbb\x06\x1b\xd7v\xc1[[\x87\xed=\xbd\x05\
4001\x7fV%\xa1\x8c\xd4C\x824y\xe7\xcbQ\xf7\xd2\x1e\xb4\xaej,Z\xb2$\x9a\xba\xd2*`\
4002\xf4\xfc\x18\xbc\xb5uxp\xc7\x0e\xdc\xb8e\x0bN\x8d\x8d\xe0\xf0\x89\x93\xd2\
4003\xb1,@\xe7-2\xf6:\xea\xbc\xf8\xc0u[\xe5(\xbd\xd0\x953\xd9cIL\xcd\x80K\xa7\
4004\x17-\xe1\xa2XT=\xa9+Qh\x9e:\xd9h|z\xdf3\x002\x1b7\xc1\xf8TE~\x072Y%\xe3\x91\
4005s\x98\x08M\xc8)S\xa5\xba2\x92\xef\x19\x98\x9eBP\xe7\x06`h\n\x89\x84\xe4qS\
4006\xef\xf1\x15LH\x16\xd6\x8c\x81\xc9\x01<\xf6\xf2\xa3\xf8\xe8\xe6\xbb\xb0\xbd\
4007\xa77\x8b\x80H\xc9~\xbeG\xf2\xff\x92\xd7\x93\x7fk_\xabE\xae\xe7\x9f:\xfa\xbc\
4008$\x9d\xe4\xb8q\xf4d\x9e\xc0\xf4\x94\\|T\xdf-}\xe6\x13;\x9f\xcb\x9b\xb6\xc7\
4009\xb2,B\x91\x08~\xf9\xc6\x1b\xe8\xee\xee\xc6\xa6\xd5=x\xe9\xf8K\x00[\xb8\x04C\
4010\xc0\xd0\x0c\xec\x16;\x92\\\n\x07N\xef\xc7\x81\xd3\xfb\xb1\xf3-;\x1a\xdc\r\
4011\xb2\x8f9\xa9\x0b\xf0\xd8\xea\xca6\xe6\xf2\x81\x8cG=\x0fu7M\xf6\x97\xb2cp-\
4012\x89\xe7\x9b\\\x19\x9a\xc2\xf9\xa9\x0b8r\xf4\x1d\xf4n\xbd\n\xc0\xac\xa6>\x05\
4013uU\xe9x\x14{\xe3{\xd1\xd9\xda\x85\xee\xeentww\xe3c7\xdf:\xe7w\xa8\x04&\x83~\
4014\xec:\xb4\x07\xcf\xf6=\rN\xe0\xe7\xbc/9\x81\x87\xdbbA\xbd\xc7\x87#\xc2Q\x84c\
40153\x06\xa9\x97\x0b\xda<\xf5@dj\xce,\x12I\xf7\x8cK7+2\x95\x9c,c*\xfb\xefZ\x90\
4016\xdc\xd7\xf9\xc8-\x9c\xc0c\xb5\xb7\x15\xeb\xdb:\xf0\xd4\x0b\xcf#\x12\xcd\xae\
4017\x8a\xa5i\x1a3\x89\xa4|\x13\x15\x13\xa9\xdb-v\x9c\r\x0c\xe3[/}\x13n\xbb\x1b\
4018\r\xd6Uh\xae\xcft\x84W\x12P\xa1d\xa2\x04\xd90\x1b\xc5\x88\xfc\x1cYuxk\xebpb\
4019\xa8_\xfe\x9d`pd\x0c\xf1D\x02\xce<\xd9\x12\xda\xfd\x15om\x9dJ[onoA\xa3o\xa5,\
4020y\xe8\x81\xa2(\xd04\x83W\x0f\x1f\xc2\x83\x00:[\xbb\xb0\xf7\xd4+\xe0\x05\x1e@\
4021ie\xe1\x16\xd6,G\xeeI.\x85\x81\xc9\x01\xdd\xd7\x95:\xb6\xc8\x98R\xbe\x06\x90\
4022&~\xa9\xfa\xd1,\xfb\xbd\xd8-\xf6\xac\x1c\xf5\xd5\x82\xd4[j:\x909\x07\xba\xc7\
40237\xab\xa1_\xdf\xbb\x19?\xf9\xf9\x0bH\xa6\xd3\xba\xaf5\x99L\x08\x04\x83\xd8\
4024\xf9\xf2\xafeR\xd7"\x18\x9f\x02\xe3\x06\xf8\x90\x05\xfbc\xfb\xb0\xbf\x7f\x1f\
4025\x9c\xe9z0\xee\xa4j<i\xc7Y9\xd3!\x95\x81V |\x01\xd3\xf1 \xac&kA\x81\x16\xc7\
4026\xa7\xd1`]\x05\x008\xfb\xd6 R\xa9$\x1cU\x92\xc93\x17\xaa\x9e\xd4K\x05)\xf7\
4027\x05\xa0\xba\x88\x95\xf8\xbd\xdc\xe0\xf84\xae\xbaH\xaa\x96$]\x80r\x91\x1d\
4028\xc9\xbb\xf5\xbaV\xe0l`\xb8\xe0\xb4Ir\xfc\x81\xc8\x14&B\x1382\xf1&\x80\x0c\
4029\xa9\x10\xe2\xa0y;\x04&\x96\xd3\'\xc5e\x96\xfc\xa9\xdd\xb4\xfa\xf8\x1cniyK\
4030\xa2U~\xcc\x02o\xa3CZ\x92\x13\xd2\'\xddr\x00Y\xfe)\xa6|]\xaf\x11C\xa3\xcf\
4031\x87\x81\xe1\xd1\xbc\x9b\xa55V\x0b\x8e\xf6\x9f\xc2S/<\x8f\x8f\xdd|+.o\xb9\
4032\x02\x07N\xefG\xad\xcdST\xa4\xae\x05\x19s\xf9\xc6\xc6|\xc6\x16y^\x99z\xa9$s=\
4033\xb8b\xb5\x08b\n\x17\xdb\xd7b\xdaq\xae\x80o\x01l\\\xdb\x85\xb6\xe6&\x1c}\xe7\
4034\xbd\xbcn\x99\xc3\xe3\xe7r6\xae\xd1\x124=Ba\xdc3\x08L\x02\xa7B\xdaIo\x00\xd3\
4035\x01@\\\xa1\x96\xce\xc2\xa9p\xd6\xe7J&l\xc5Yn\xb0\x8c\t\xec\xec\xaa\xaa\x18\
4036\x90@\xa7\x7fJ\x9a\rY\x9a\xaeZ\x137%\xaa>\xa5q\xbey\xeaK\r\xb1d\x0ck\xea\xd7\
4037`{O/\xfa\xfa\xfa\xf0\xea\xc1\x830\x9b-YdG\xd34R\xa9$\x06G\xa4\x8dA\'U[\x92\
4038\xb7\x8c\x855\xc3n\xb1\xcb?\x16\xd6\x8cZ\x9bGz\xde,\x11\xb5\xdd\xec\x80\xcdF\
4039\xc1f\xa3\xe02\xbbT?zd\xeep[\xe1\n:\xe0p\xd8\xe1L\xd7\x83\x0fY\xc0\xb8\xb37\
4040\xc8\x94{\x06$E._6\r\x19\x0b\x84H\xf4\x1a1t\xad[\x87T*\x99\xf3sDQ\x94\xd3\
4041\xea~\xf4\xdc\xf3\x00\x80\xad\x1d\xdbPk\xf3 \x96\xaa\xee\xc2\x14\x966\xeb\
4042\x12\xba\x12\xca\xf6u$\x9dQ\x99\x8d\x02\x00\x10\xf4\xcf\x8d\xb2\x12\xd8\xe5t\
4043\xcan\x99s\xc1[[\x97\xd7\x1f\xc5\x99\xae\x87\xd0"\xc2\xe1\xb0K\xe3\xc2m\xc5*\
4044\xd1\xabzM\xadW\n\x0c\x94\xe3I9\xce\xc8\xf8\xa3y;\xecf\x87j\xcc\xce\xf5ca\
4045\xcdE5\x0f\xe7g\xa5\xa8\xce\xd6.L\x06\xfd8\xd0\xd7\'Uu/\x01B\x07\x96\x00\xa9\
4046+Q\r\x19$\x95D,\x19C\xad\xcd\x83\xfb>\xb4\x03\x00\xf0OO<\x81\xa1\xd1Q\xddh\
4047\x89\x90<1\xc6R\xca\'\xf3\x852Z\xb5\xd9(\xc4\xe3"L\xbc\x13\xf1\xb8\x884\xa3\
4048\xd6\xf7CB4\x8b\xd8\xb5\xd0.\xb9\xb5HL\xcd\x14n\xc6\x05\xa8,_\xb5\x91\xe2\
4049\xc7\xaf\xdb\x8e\xb6\xe6f$S\xf9#n\x87\xdd\x8e\xd7\xdf|\x0b\x8f?\xfe8\xd6\xb7\
4050u\xe0\xe6\xf7\xdd\x02 sCW\x1b\xb4\x85QJB\'\xf7\x85\xb6\x98JYI\xda\xbc\xb2\t\
4051\xe2\xb9\xd9\x82\xb3\x1ce\xfb\xc54\xa1V\x06[$\xfbE\x0f\x8c;\x89\x88i\x12\xce\
4052t\xbd\\#\x01\x00aO\x14L*\xbbVBo<\xa5\x99\x88<\xfel\xb6\xca\xeb\xda\x89t\x02\
4053\r\xee\x06\xaco\xeb\xc0\xe8\xe0\x08N\x9d\x19\x82\xad\xc0\xc2\xb6j@\xd5\x93z%\
4054\xab\x1e\xab\t2\xa1\xf7\xde\x87z\x8f\x0f\x0f?\xfa(^x\xe5\x95\x9c\xb2\x0b\x89\
4055\xa0\x8e\xbe\xfb.&\x83~t\xb6J\xfe\xd5\xe5 %B \x9c\x90\x92o\xa44\x13\x81\xcb\
4056\xec\x82\x89Wg\x9f\xb8i\x87\x9cU\xe1p[\x11\xf7g\x13\x03\x1f\xcaO\x16\x01!\
4057\x88`8\x02\x9b\xd5\x9a7\x1a"c\x81\xc8.\xcaG\x82\xee\xeen\xdc\xda\xfbA\xc4\
4058\x13\t\xdd\xcf\xa0(\n\x1c\xcf#\x91J\xc1n\xb3\xe1\xcbO>\x89]\x87\xf6`{O/\xae\
4059\xdfp=\x12\xe9D\xd5Z\xae\xce%\r\xe5\xf3\xec\']\x89\n\xad\xd5p;\n\xaf8V\xae\
4060\x98\xb4\xfe/|\xc8\x02g\xba\x1e\x11\xd3$\x1c\x0e;\xc2\x1ei\xac\xc4\xfd\x16\
4061\xd8|\xfa\xe9\x8dd<\x11\xc4\xe3\xd2\x98 \x84\x1e\x8f\x8bEy\xe0\x17\x03r\xff|\
4062\xe8\xd2\xeb\x01\x00\xdf\xfd\xd9\xcf\x10\x8dF\xc1,B\x7f\xe3R\xb1t\x8e\x14\
4063\xcbS~Ir)Yr\xb9\xaf\xf7>ys\xf4_~\xf8CX\xcc\xe6\x9c\x1a\xb3 \x8ap\xd8\xed8t\
4064\xf4\x18F\x07G\xb0\xbe\xad\x03\r\xee\x06U\xd7\xa7RA\xc8\x83\xa5\xcd\xaa\x1b)\
4065\xcdD\xb2tNed\x15\r%`\xf3%\xe1\n: \xb4d\xc8\x99q\'e}\x95\xa4\xbc)\xf3\x9a\
4066\x03\xe3Q\xcc\xc4\xe3yupA\x14\xc1\xb2,Z\x1b\x1a\xb3d\x17\xe5\xbf\'\x83~t\xd4\
4067y\xf3\x9a\xa0\xf1\xb3\xcf;\xecvDgf\xf0\xf9\x7f\xf87\xf4\xf5\xf5\xe1\xaemw\
4068\xe2\xee-\xf7\x80\x9d5\xef\xaa\x16h]$\xf5\xa2t@_zQB\xcf\xa6\x81@\x99\xedB&\
4069\xc9\xb9&\x80X<._\xc3\xc4\xec\xfe\x88\x9eSc\xc44)\xfd\x1f#\xb3\xd5\xbcA\x07l\
4070\xbe\xa4n\x00\xa0\xb7\xeaSF\xe7$\xc8\x98\xcf\xdeG>$\xd2\tll\xb8B\x96?_\xdc\
4071\xbb\x17\x0e\xbb}\xc9H/\xc0\x12 \xf5\xf9\xfa\xa9W+\x08\x99[X3nz\xdf\x87q\xdf\
4072\x87v`}[\x07\x1e~\xf4Q\xfc\xd9\x83_F\x9a\x9b\xbb-\x1b\xcb0\x88D\xa3\xf8\xc9\
4073\xee]\x002\xd1E\xb9"Mr\xe3\xc4\xe3\xa2\x1c\xa5\x93\x8d\xd1|\x08{\xa2\xa0G(8\
4074\xd3\xf5\xf2sD~!\xb9\xcc\t\xc5&il\xc2_P\xe3\x06\x9afp\xe5E\x17I\x9f\xa3\x88\
4075\xce\xb5\x12\x8c\xec\xd1\x9eG\x0f&\x9e)\x8e\x9a\x1a\x0c\x0c\x8f\xe2\xee/=(\
4076\x97\xfe\x7f\xfe\xfa/`M\xfd\x1a\xc4\x92\xb1\xaa\x8d\xdas\x81H\x1c\xc4C]\t\
4077\x97\xc3\x913\xfa\x16\x04^U$F\xea\x0c\xf8\x1c\x13#M3\x88+\xb2\xaf\xb4\xc5G\
4078\x00\xe4\xbd\x14\xe58 \xd1z\xdco\x01o\xce^Yh\xa3t\x000\xf1N9\x98\xa8\x94\xfc\
4079\xc2\x0b<b\xc9\x1866\\\x81\xbb{?\x8e\xc9\xa0\x1f\xf7\x7f\xf7;\x08E"\xf3r\xb5\
4080\\\x0c,\xa9\xa3-\xc5O}1\xa1\xed\xa6B\x88<\xc9\xa5\xe0u\xd6\xe1\xa6\xf7}\x18\
4081\x0f\xdd\xf10\xee\xdav\'\x00\xe0\x93\x0f<\x80\x87\x1e{\x0c\xbc \xf9T\xcc\x05\
4082A\x14a\xaf\xa9\xc1\x8f\xfek\xa7,!\xdc\xbd\xe5\x1ep|\x1a\xb1dL\xfe\x7fK\x85\
4083\x1c\x19\xda(\x84Sa\xddH]\t\xa5\xfc"\xb4\x88r\x84\xa6\x84^#\x05m\xa3\x0c=\
4084\xf0<\x8fU\xbe\x95X}y{\xe6\xb3\x14\x12\x0cA\xbd\xc7\x87\x0b\xe1\xe9\xa2\xbc\
4085\xe2]\xf6\x1a\xbc7t\x167}\xeesx\xea\x85\xe7\xb1\xbe\xad\x03_\xf9\xe8Wp\xf7\
4086\x96{\xe0u\xd6!\x96\x8c\xc9\xd7m\xbe\xe7\xb4T\xcc\x15\x99\x12=}\xaeH}\xae\
4087\xe8\x9b\x90t8*YV\xe7\xcbFJ\xe8\xec[(\xaf/\xe3N\x82\x0fYdM\x9dL\xf6\x00`\xf3\
4088%u5u ;Z\x0f\xa7\xc2p\x99]\xb2\x0cC\x1e\xe7\x03\xed=\xc9\xd0\x0cnz\xdf\x87\
4089\xf1\xc5;\xfe\x17\xea=>\xfc\xcd\xd7\xbf\x85W^{}Ii\xe9\x04U\x9f\xd2\xa8\x8d,\
4090\xca!/,\x14H*\x15C3p\xda\x9cr>xgk\x97\xec\x05>\x19\xf4\xe3\xf1\xc7\x1f\xc7\
4091\xd7\x9ey\x06C\xa3\xa3\xb0\xd7\xd4\xc0T\xc4N;\xc30H$S\xf8\xfc?\xfc\x1b\x9e|\
4092\xc8\x83\xed=\xbdh^\xd9\x84\xfd\xfd\xfbpl\xec8b\xa9\xe8<\xceY\xc6s\x9a\x86]\
4093\xbe\x99\x94\xc4\xee2\xbb2\xd1U\x08\x809\x01\xc0\nz\x84B\xd8\x13\x85\xd3"El\
4094\x81P\x14\xdeF\x87L\x1a\xcah\x9d\x90p.\xd0\x14\x85\xf0\xcc\x0c\xb6_}5\x9aW6\
4095\xe9n\x92\x12r\x9f\x0c\xfa\x8b\xaaV\xb4\x9a\xcdH\x00p;%\xdf\x98\x8f\xff\xd5_\
4096c\xe7\xcb\xbf\xc6C_\xb8O.\xcc:1\xd4\x8f\xfd\xfd\xfbp:\xf0\x1e"\xf1\x08\x92\\\
4097jA\xc7!\xcbH\xe4\x99D\n,\xcd\xc8\x8d\xb2U\x98\x8d\x01"\x91iI\x86\x81\x1d!\
4098\x84\xe0\xb6\xbbq\x96\x1e\xc4j\xa1\xbd\xacV\x05\x0cMa&\x96)~#\x9b\xa5D\xbb\
4099\xd7\xe6\x9b\xc7&\xfc\x80GZ\xc5EC\xb3\xe7\xce,=J\xe9\x8c\x99\x08=$D\xb3\x82\
4100\x07\xf2\xefx\\\x84\xc0\xc4\x10K\x96\xd6E\x8c\x80\x14\x06\xba\xednlZ\xdd\x83\
4101k:\xaf\x91\x8b\xe6\xee\xfb\xd2\xc3\xd8\xb9{7\x1cv{E\xdaHV\x1aUO\xeaJlZ\xdd\
4102\x83Q\xeb\x98\x1c\x8dT+H\xa7\x19@\xca:\xf0\xd6\xd6\xa9\xaa(\'\x83~\xf4\xf5\
4103\xf5\xe1\'\xbbwa\xd7\x81\x03\xe8?=\x00\xb3\xd9"\x97\xc0\x17\xab\xdfY-f\xf4\
4104\x9f\x1e\xc0G\xee\xbf\x1f_\xfd\xcb\xcf\xa0w\xebU\xf8\xf4\r\x9f\xc2d\xd0\x8f\
4105\xd1\xc1\x11i#2>\xa5\xf2R/7Ha\t \x91\xb8\xf7\xf2L\xb4E\xa27\xbd\x0c\te\x94\
4106\x9d\xab\xd94/\x080\x9b-\xd8\xde\xd9\xa9\x9b\xc6\xa8\xfc\x9cz\x8f/\xaf\x13$\
4107\x81\x9e\xb1\x94\xddf\x83\xc9d\xc2\xce\xdd\xbb\xb1\xef\xd0!\xdct\xcd5\xf8\
4108\x8b\x8f|\x04\xcd\xed-\xf8\xf4\r\x9f\x02 ]\xbb\xc0\xf4\x14F\xcf\x8feU\x13/\
4109\x06\x94\x15\xa3\x04\xdaL\xa3\xce\xba.\x8c\x9e\x1f\xc3\xb8\xdf\x9f\xd7\xc6\
4110\x99\x8c\xbf\xc4\xd4\x0c\\\x8e\xfc\xee\xa6\x0c\xc3`&9#\xd7I\x90 \x85\\\x87\
4111\xc0\xf4\xacS\xa3\x1d\x08\x08\x0e@\x91\xc1\x18\x18\x8f\xcaQ<\x000\x97d~\xd7[\
4112\xdd\xcd\x07\xda\xf3C\xce\x8d\x97\xf6\xc0ZW\x933\xb8\xaa\x94W\xd4B\xa0\xeaI]\
4113\xb9\\$2\xc5R\x02!\xd6=c\xaf!6\xe1\xc7\xebg\xce\xe0\xf8{\xef\xa2\x7f`\x10\
4114\xb1\x99\x19\x98\xcd\x16\xb8]\xaeyo\xc4\xb8].\x8cNL\xe0\x8f\xff\xeeK\xb8z\
4115\xd3\x15\xb8\xf3\xca\xab\xd0\xdd\xdd\x8d\xe6\xf6\x16t{\xba\xcb\xf4m*\x03\xe2\
4116\x0b\xa3w\x0eh\x8aBt\xb6}\xdd-w\xdc\xaak1\xa0EG\x9d\xc4 \xa2(\xeaFZT\x8e\xc9\
4117\x03\x00\xcc,\x0b\xaf\xc7\x83X<\x8e\'v\xee\xc4\xf3{\xf6\xe0\x8a\r\xeb\xf1{\
4118\x9bz\xd0\xde\xd2\x84K\x9bZ\xd0\xdc\xde\xa2\xea\xba\xb4\x14\xf0\x8d\xef\xff\
4119\x18\xfd\xa7\x07\xf2\x92U8\x92\x89\xe4\xdd.g\xde\xe6,4M\xc3\xc4\xb2\xd8\xf9\
4120\xf2\xaf\x01H\xde;\xd6\xba\x1a9\x80\xa9\xf7\xf8\x80\xb6\xb2\x1d~\xd9A&\xe7\
4121\xa7^x\x1e\x87O\x9c\xc4\x81\xbe>\x1c>v\xbcl\xf7\xe3b\xa2\xeaI\xddb6c\xe7\xcb\
4122\xbf\x96\x8bl\xaa\x19\x17\xc2\xd3\x08\x85#\xb2)V8\x12A \x18B"\x95Btf\x06\x89\
4123DB\x96\x19lVk\xc9\x91\xb9\x1e\x04Q\x84\xa3\xa6\x06i\x8e\xc3+\xaf\xbd\x8e\x03\
4124\x87\xdf\xc4\nw-\x9a\xeb}p9\x9dhmh\x04 \xdd\xac\xe5\xf4R\xd7Bi\xa7\xab5\xeeR\
41256( Y0^\xda\x83#\xa7N\xc1l\xd6\xdfC \xe7\xc6n\xb3a\xcf\xfe\xd7\xb2\x9a\x1c\
4126\xe4jv\xecp8 \x08B\xce\xcdfQ\x14\x91H\xa5\xb2Z\xef\x91\x08\xde\xf2\xff\xb7ww\
4127\xbfm[w\x1c\xc6\xbf\x14E\xd9\x8ad\xcf\xdd\xda4\xe9\x1b\x06\xf7\xaa\xd7\xf9\
4128\x97w\xbb\x9b\xdd\xec\x05E.z\xd1`\xc3\x80\r\x05\xd6\x15\xd9\x16\xb4i\x1b\'\
4129\xee\xe6\xda\x92\x15\xd9\xa6D\xeeB:\xf4\xe11)Q\xb6\x93H\xbf<\x1f h\x91\xc4\
4130\x8aM&\x8f\x8e\x0f\x0f\x0f;\x1dmu::\xbf\xb8\xd0\xc3/\x1f\xe9\xe1\x97\x8f\xd4\
4131\xef\xf7\xf5\xee\xde\x9e\xba\xdb[\xba\x7f\xf7\xee\x95\xa79\xbd\xeac\xeb{?I\
4132\xf4"M\x17\x1eoiv\xcc\xff\xf0\xf9_\xf4\xdb?\xfdQ\xfd^\xfdw\xb8\xadV\xac\x1f^\
4133\x1c\xea\xbb\xbf=\xd1\xbb\x0f\x1e\x14_G\xdd\x1b\xa3t\xb9[\xe3\xe8\xf9\xa1~3\
4134\xdf\xa8\xab\x89Wu\x8c\xc2\x07w\xf8\xfc\x95?\xfe\xbf\xcd\xe3\xc1@\xc7\x83\
4135\xd9\xd4N\xbf\xdf\xaf\xfdnq\x93\xac}\xd4\xe38\xd6\xef\xbf\xf8B\xbf{\xf8\xf0M\
4136\x7f*\x0b\xb5Z\xb1\xe2V\xa4\xf1\xd9Y\xb1\rm\x96M\x8b\xa5bq+\xd2\xf6\xd6\x96Z\
4137\xc16\xb4\xb7)\xcbs\xc5q\xac\xdd\x9d\x1dM\xa7S\x1d\x1d\x9f\xe8\xe8\xf8DY6-\
4138\xcdY\xdft_y\xf7Z\xee\xeb\x8c[\x91\x92$Q\xa7\xddV;I\xb4\xdd\xe9\xa8\xbb\xbdU\
4139<t\xda\x8f_\xb8k\xa34\xbb\xdd\xbcn\xb3\xa4,\xcfu\xa7\xdb\xd5\'\xf7?\xd0\xe7\
4140\x8f\xfe\\\xfc|\xd5\xeb\xf8>\xbe\xf7\xfe\xc2\xed\x02\xc2P\xb5\x93D\x934U;\
4141\x8e\x8b\xb0gy\xae$I\x94$I\xf1l\xd4g\x87?I\x92\xfe\xf5\xedwW\xae\x03\xdc\xe6\
4142~\xfd\xe1\xf9r\xc79\x8e\xe3bUO\xff\xcel:\xcb\x1d\xef\xf1\xd9\xb9NG\xa3\xd29\
4143\x90\xa4\x83\xc3\x9f\x94e\xf9\xc2\xd5T\x9d\xa4\xad\x97\xa3\xb3\xcb\xc7\x0b&\
4144\x89z\xdd\xae\xc6\xe7\xe7\xb5\x1f\x97e\xd3b\x9e\xfe\x97\xbb\xbf(\x065O\x9f\
4145\x1f\x14\xa3~7\xa8y9\x1e+MSM\xb3\xbc\x98\xd6\x99\xdc\xf23\x0eZ\xadx\xe1\xd6\
4146\xbe\xedv\xbb\xf83\xdd\xf1\x94\xa4\x9d~\x7f#\xe7\xce\xeb\xac}\xd4\xa5\xd9\
4147\x06B\xaf\xeb\x01\x177\x11E\xb3\xb8\xad\x838\x8e\x97\xae\xfb\xbe\x8e\xf0\xb1\
4148m\xee9\x99\x92J1\x97T\x04\xdd\xa9\x0b\xf1h\xbc`#\xb2<W\xc7;\xa6u\xaf\xe1\xb6\
4149\xf4\x1d=?T\xef\xde]}\xb6\xff\xa9\x9e>;X8\xd2\x9c\xa4\xa9\xd4\xf0Q\x81\xee\
4150\xdc.;\xbb\xb7\xf5\xb4#\xf79\x87\xc7\xba\xf8\xffy\xb8%\x15A?:9\x99\xad\xcdO\
4151\xd3b\x8f\xfc\xa8\xd5R+\x8a\x96.\x8f\xcd\xf3\\\xad\xf9?\xb1\xfff?\xabw\xef\
4152\xae\xfa\xbd\x9eF\x0b\xee\x1fp\x03\x16\xf7F\xe0.P\xbbs>\x18\x0e\xd5\xdd\xdeR\
4153w{kvAz\xbeZf\x92\xa6\xa57\xce\xf0\x98U\x9d\xaf\xf0\xefk\xf8;\xdc\xc7\xf8w_\
4154\xe7y6\xff\xb5\x8dZ\xe4wc\x1b\xf3\xd5FQ\xb4\xf6?6\x89\x8b\xf3\xaa?\x9aX\x18\
4155\xe9\n\xe3\xb3\xfa\xd1`\xc8\x85\xc3m\x8f ]N\xf9\xb8)\xbaa\xf2B\x9f|p_I\xbb\
4156\xbd0\xb2.,\xe1\x14\xccM\xbc\xaa\xbfKa\xd0\xa5\xf2\x92\xc2\xb3\x8b\x8b\xd9\
4157\xd7\x1aE\x8at\xf9\xa6\xde\xf4\x9c\xa5\x93\xa9\xee\xdf}O\xef\'\xc9\xe5&q\xef\
4158\xec)]\xb0*)nEW\xe6\xe1C\xbdnW\xe3\xb3\xcb\xd1s\xd53U\x9b\xfc;\n\xff\x1e6\
4159\xf9\x98(j\xbduA\x976(\xea\xd8\x1c\xfeM,U\xcb\xe8\xfc \x1f\rN*\xd7;\xfb\xc2g\
4160\x9f\x86\xdcH\xf1\xbd\xfd\xb6^\xa4\xa9Z\xdfG\xfa\xea\xf1c\xa5\x93I\xa3\x1bG\
4161\xc2?\xbf\xdd\xf0\r\xe6u\xf2W\xeb\xf8atO\x98\xda\xeet\xf4\xab\xbd=\xbd\xbb\
4162\xb7\xa7\xde\x9d;\x951wA\xcc\xb2\xac\xf81\x9dN5\x9dN\x95\xb4c}\xb6\xffii^\
4163\xbe\xc9\n\x10\xf7\x06\x1e^O\n\x7f\x1d\xaf\xcf\xfa\xcfi`\xa3\xb8Q\x99\x0b\
4164\xbb?\xfdr<\x18\x16\xa39?\xec\xdb\x9d\x8eNG\xd5\xb7\xe5\xe7y\xaew\xe6\x1f38=\
4165\xd5n\xbf_\xbc\xce\xd1\xe0\xa4\x98\x86y\x91\xa6\xd2\x13i\xff\xe3\x0f\xf5\xd7\
4166\x7f|\xad\xaf\xfe\xf9\xcd\xd2\xcf\xd5\xc5\xdb\x9f\x1a\x90.\x03\xba\x0e\x17\
4167\xcd\xfc\xe7\xc0N\xa6\xd3\xe2s\x9e\xa4ii\xc4\xeeO\xc5t\xb7\xb7\xa4`\xd4\xec\
4168\xaeoH\xd2\xc1\xe1aq\x9e\xdcE\xdfO\xee\xdd\xd7\xfeG\x1f\xe9\xc9\xf7?V>\x9d\
4169\xaaJ\xab\xd5\xd2\xcf\x83a\xe9\\\xe2\xcd#\xea\xb8U\xe1N\x7f\xc7\xa7C\xed\xf5\
4170w\x8a \xd7}L\xd54I\x9e\xe7J\xdam\xdd\xbf\xbb<0\xd2l\x1a\xe6\xc9\xf7?\xea\xe9\
4171\xb3\x03\rF/\x17\xee\x05\xbe\xa9\x16\x85\xdd\xe7\xceC\xf8|XI\xfal\xffSI\xe5G\
4172\x0e\xfa\xd7(^\xa4\xa9\xf6U^\xe2X%\x9a\x7f\x17Tu\xb3\xd7`8,\x8d\xd2\x97}7\
4173\x86\xdb\xc3\xf4\x0bn\x95?\x7fZ\xe5x0,E\xe0x0\x9c?\x9b\xf4\xea_E7W\xea\xc7\
4174\xc5-M\xab\x9a[wK\xfcv\xfb}u:\x89\xa6K\x9e\\\xefvk\x0c\xbfKX\xb7\xe9\x97,\
4175\xcfK\xdf1\x84S1\xe1t\x8c\x9b\x92q\x0f\xfevqu\xc7\xd1M\x8f\xf8\xc7\xf2hpR:\
4176\x96\xee\xfa\xc4\xa2\xc7\xda\xb5\xa2\xd9#\x15\xdd\xeb4}\xbe\xad\x7f\x91\x14\
4177\xb7\x8f\xa8\xe3\xc6\xaa.~I\xf5\x11\x91.\xe3\xfe\xf4\xf9\x81^\x8e\xc7\xf5+T\
4178\xe6\x01X\x14\x8b0\xec{\xbb;\xdan\xb0\n)\xcbs\x9d\xbe|\xa9\xf34-\x9e\xf7\xba\
4179\xce\xfc\xb8O\xa6\xd3\xa5q\x97.\xdfd\x9b\x84]\xd2\xcaS)n\xc3/\xff\xdc\xfao\
4180\xc2\x8b\xde\xe4\x9b^\xc4\xc5j\x88:\xae\xed:#\xdap\xfb\xd7\xc1pX\xbbEn\x96e\
4181\xa5\x95)u!\x92te\xa4\xd9\xef\xf5j_\xd7\xe7VR\xd4\x8d\x84\xd7\xd1\xb2\xb8;~\
4182\xd8\xdd\xa8]*\x87\xfd\xf8tX\x19\xf6\xbf\x7f\xf3\x8d\xbe\xfd\xe1Gu:\x8b\xdf\
4183\x1c\xf3<\xd7\xd3\x83g\xc5\xeb\xb9\xd7\xae\x9az\t\xdf\xfc\x19\xa9\xbf\x1aD\
4184\x1d\xb7\xc6\x9f7\xad\x1b\x19J\xb3(\xbb\x1fu\xab#\\\xd0\x7f\xfd\xd1\x87\xa5U\
4185\x18Ua\x0f\xe3.\xad\xf6\x14\x9f\xdc\xbb\x18Y\xfa\x1c\xd6<:\xe1\x1b\x91\x8b{U\
4186\xd8\x9d\xf0\xbc8\xee|<}v\xa0\xaf\x1e?\xd6\xe7\x8f\x1e\xe9,M\x1b\x8f\xa6\xab\
4187V\xbc,\x1a\xa5\xaf\xfb\xb1\xddd\\(\xc5\xca\xdc\x8a\x0c\xff\xa2\x9do|v^\x19U\
4188\xf7\x0f\xdf_\x11\xd3\xebv\xafL\xbd\xe4y^\x04]*\x07hwg\xa7\xf2\xe2kx\xb1n\
4189\xd9\xdc\xbe/\x8a\xa2\xcb\x8b\x8fk>J\x0f\xb98\x86+d\\\xd8\xdbIR\xac\x8eq\xe7\
4190e4\x1e\xab\xd7\xed\x16\xc7\xd5\xad\x869\xbb\xb8\xd0$M5\x9a\xefm\xdf\xe4\xde\
4191\x81(\x8a4\x1a\x8f\xb5\xbb\xb3S\xba8\xea\x8e\x7f8J\xdf\xb4\xe3\xbb\x89\x88:n\
4192$\x0c\xbb\x1f\x10I\xa5x\xb8\x11w8?\x9e\xab|\x87`\x96e\xc5\xaa\x0e\x17\xa0\
4193\x90\x1fvI\xa5\x955\xee\xe7\xaa.\xbeV\xdda\xda\xf1\xeeV\xf6\xb7\t\xd8$\xcb\
4194\x96>V\x85\xfd\xa7\xff\x1d\xe9l\x1e\xdb<sw_\xae~W\xf4\xcf\x83\xa1z\xdd\xee\
4195\xd2\xa0\xe3\xf5 \xea\xb8\x91\xaa\xe5u>?\xca~\xdc\x07\xc3\xa1vwv4\x1a\x8f\
4196\xb5\x9d$\xba\xf0\xee\\\xecn\xcdn+w\x91p\xff\r\xdf \xfc\x91\x7f\xb8a\xd3\xd9\
4197\xc5\xc5\x95x\xbb)\x9db\x9f\x17\xef\xf3\rG\x92\x9b8=P\x17v\'|\xc3\xbd\x98L\
4198\x14i\xbe\xca\xe8\x9a+~\xdcQj\x12\xf4\xc9t\xba\x91\xc7u\xd3D\xbb\x0f\x1ep\
4199\x94\xb1\xb2\xaa=I\xfcH\xfa7\xc38U#ni\x16\x84p\xba\xc4}\\8\x95\x13\xbeF\xdd]\
4200\x8f_\xff\xfb?\xb3\xa5\x8a\xde\x1e*w\xba\xdd\xe2F&7\xdd ]\r\x8f\xb4\x99Qw\
4201\x9a\x9e\x9b\xd3\xd1H\xe7i\xdax\x8b\x8b\xf0^\x82\xb8\xd5R;\x8eK\xbb?\x96n\
4202\xe2\xaa8\xae\xd2f\x1f\xdbM@\xd4qmM\xe3!\xadv\xe1\xb2n>\xbci\xdc\xfd\x15\x18\
4203\xfek\xb9\x8d\xaf\xa4\xea\xf8X\x08\xba\xb3h#0\xc7\xddI\xdbt9gx\x8e\xfd\xf3\
4204\x1b^\x90\xad\x9bC\xb7pl\xd7\x1dQ\xc7\xb55\r\x87\xaf*\xeeU\x11w\x91X\xf6\xf1\
4205u\xa3\x7f_\xdd\xd4\x80T\x1d\x1f+\xe1\xa9;?R\xfd~:\xabnnV\x17s\x89\xa0\xbf)D\
4206\x1d7\xd2$\xecR\xb3X4\xb9\x95|\x95\xd1\xbf\x9b\xba\xf1\xdf4\x96M\x0fX\nO\xd5\
4207r\xc4\xa6q_E\xd5\x85P\x82\xfe\xe6\x10u\xdc\xd8uF\x84\x8b\xf8\x171\xab.\xc0V\
4208\xbdA\xb8x\x87\x11w\x9a\x8c(\xad\x86\'<?uKQW9Wu+Z\x88\xf9\x9bG\xd4qc\xe1\x88\
4209\xf0:a_\xb6\xec\xed:\xd3\x05U#\xffE\xeb\xa5\xad\x07\xa8\xeeF\xa2U\xee\x0c\
4210\x0e\xdf\x10,N[m:\x964\xe2\xc6\xaan\x80\x91\xca\xcb\x1dWUZk]\x11\xe0\xba\xdd\
4211\t+_+\xf8\xf3\x8b\x1bt\xde\xb2 \x85_cx\xbe\x9ab4\xbe\xde\x88:nM\x16\xdcn\xef\
4212\xc7\xb3\xc9h0\x8cm\xf8\xe6P\xfa\xbdA\xa8\xc3\xe9\x9a\xbay\xde\xf0\x8d\xe2m\
4213\x0e\x92\xff\xb5\xaf\xba\xb9\xd6\xdb|\xdc\xd6\x1dQ\xc7\xad\xaa\x1b\xb57\x1d\
4214\rV\xfd\xbeE\x1f\x1b\xbe\t\xb8[\xe4\xeb>\x86\x98W\xe3x\xd8A\xd4\xf1J\xbc\xae\
4215H\xb8;Q\xcf\xe7#s\xb7\x83!\xdb\xba\xe2mE\xd4a\x82\x7fW$A\xc7\xdb\x8c\xadw\
4216\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\
4217\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\
4218\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\
4219\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\
4220\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\
4221\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\
4222\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\
4223\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\
4224\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\
4225\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\
4226\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\
4227\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\
4228\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\
4229\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\
4230\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\
4231\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88\
4232:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\
4233\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\
4234\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\
4235\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\
4236\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\
4237\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\
4238\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\
4239\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\
4240\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\
4241\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\
4242\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\
4243\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\
4244\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\
4245\x00\x0c!\xea\x00`\x08Q\x07\x00C\x88:\x00\x18B\xd4\x01\xc0\x10\xa2\x0e\x00\
4246\x86\x10u\x000\x84\xa8\x03\x80!D\x1d\x00\x0c\xf9?\xd2xo\xfe\xadF\xd4\xef\x00\
4247\x00\x00\x00IEND\xaeB`\x82'
4248
4249
4250
4251def getSageImage():
4252    stream = cStringIO.StringIO(getSageData())
4253    return ImageFromStream(stream)
4254
4255sageImg = getSageImage()#.Rescale(300,254)
4256def getSageBitmap():
4257    return BitmapFromImage(sageImg)#getSageImage())
4258
4259
4260
4261#----------------------------------------------------------------------
4262def getSageStartUpData():
4263    return \
4264'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x82\x00\x00\x002\x08\x06\
4265\x00\x00\x00\x90\xc9\x03\x88\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
4266\x00\x00\t&IDATx\x9c\xed\x9c_l\xdb\xc6\x1d\xc7\xbf\x92%eV\x9c\x00.\xed\rv\
4267\xe4\xd8\xc8*\xd9\xe8\x1cL\x9b\x03t\xb2\x83!\x813\xa4\xe8\xacb\x0fE\xeb\x05\
4268\xc5\xe0\xbe\xac\xe8\xd6>9\x18\x86"o\xc1\x02\x0c\x8d\x9f\x83\x0e\xc1\xea\xbe\
4269\xac\x80\xd1=d\xf20\x14M\x16\x03K\xa6\x16\xb5\r\xadq1[n\x13\xff\x91ld"\xedX\
4270\x94\xf8_\xe2\x1e\x18\xd2\xa4I\xd92-\xff\x91\xc3\xcf\x8bt\xd4\xf1\xeeD~y\xf7\
4271\xfb\xfd\xeex.\x97\xbb\x06\xa5\xb8\xff\xdf\x7f\xc9\xeaw\x86gJ\xe6s8xHE\t\x00\
4272 J"x\x89\x87(\x89\xb8\xf4\xd37\\\xa5\xf2\xbb\xac\x840\xf9\xed\xb8\x0c\x00\
4273\xac\xc0\x02PD\xa0\x16,\x15\xa4\xca\xb7\xda\xa1\xa2\xc8\xb2\xf6\xfcj"\xe0D\
4274\x0e4G\x83\x17y\xfc\xee\x17\xbf7\t\xc2$\x84\xc9o\xc7\xe5\x8d\x02\xd0\xab\xca\
4275\xa1\xba\xe0DN\xfb\xae\n\x81fi\\\xfd\xe5\x1f\x0cb\xd0\x84\xa0\x1f\x06\x00 \
4276\xcbf\x91\xe3r\x0659T/\xbc\xc8\x03\x00\x04I@\x96\xc9\x82\x17y\xe4\xf9<n\xfe\
4277\xe6\xcf.\x00\xf0l<\x81\xe1\x19<a\x9e\x80fi\xd0\x1c\r\x9a\xa5\xb5B\xd4O\x87\
4278\xea\xe2\x88\xf7\x88!\xcd\x8b<\x18\x9e\x81\xc0\n\xda1\x97\xcb]\x83\x7fL\xfe]\
4279\x06\x94\xf1\x9f\x139\xd0,\x8d\xa5\xd5%M9bA\xd4l\x04\xc08\x069\x1cL\\\xae\
4280\xf5\x9e\xdf\xe3\xf6\xc0[\xe3\xd5\xd2\xac\xc8\x82\xcbs\xe0\xf3<\xb8<\x87[\
4281\x7f\x8c\xb9<\x00\xb4\xb1\x9f\x97x,\xad.\x81fid\x19ehP\x05\xe0\xdc\xfc\xeaB\
4282\x7f\xbf\xa4\xa2\x04\xa9(\xc1\xe3V\x06\x00I\x90 \xf2"\xb8<\x07.\xa7\xd8\x10\
4283\xae\x8f\xef},\x03\xebF\x05I\x93 \xb3\xa4#\x82C\x86\xbe\x87P{\x83\xfcj\x1e\
4284\xb9\xd5\x1c\x00\xc0\xa3\xb7*I\x9a\xd4l\x02\xb1\xe0x\x08\x87\tY\x96\xe1r\xb9\
4285\xb4\x87\xba \x16 \n\xca=\xcef\xb2\xf0\xe8\xbd\x01A\x124\x11\xe8Or8\x1c\xe8\
4286\xef\xa7(\x88(\x88\x05d3Y\x00\x80[\xfdA\xef\x19HE\xc9\x11\xc13\x04Gs\x8a\xfb\
4287X\t\xf7\xd0\xef\xf3\xe3\x04q\xc2t<M\xa5\xc1\x08Nx\xfa "\t\xeb\x9e\xa0G\x1fh\
4288\xb0C$\x14\xc1\x85\xd3\x17\x10 \x02%\xf3\xb0\x02\x8b\x99\xa5\x19L\xa7\xa7\
4289\xf1\xc5\xec\x17e\x0b\xe3\x83_\x7f\xa0}O.\'1\x14\x1b\xb2\xd5\xc6jf/\xae\x81(\
4290\x88\xe6\x80\x12P\x9e\x97\xe0\xf7\xf91\x18\x1d\xdcT\x00*\xb5\xbeZ\x84\xdb\
4291\xc2\x08\xb7\x85\x91ZIavyv\xfb\xadu\xd8U,\x85P\x0eo_|\xdb \x02\xf5\xa9OQ)\
4292\xedX\xa89\x84\x16\xa2\x05\xb5\xbe\xda\x9d\xb5\xd2a\xd7\xb1%\x84\xce\x93\x9d\
4293\x085\x85\xb4t<\x19\xc7\xc8\xbfG\xcc]\xfe\x84\xf2\xd1\xda\xd8\x8aS\xdf=\x85\
4294\xb3\x1dgm7\xb4R\x04\x9b\x82\xe8h\xee\x00q\x8c\x00q\x8c\xd0\x8e3<\x83\x14\
4295\x95\xc2W\x0b_a>3_vy\x1bm#2Kb5\xbf\xaa\xa5;Ov\xe2\x85\x13/\xa0\xa5\xa1E;\xb6\
4296H."\x93\xcd\xe0\xee\xd7ww\xf8o*\x87\xeb\xfd\xbf\xbd/\x03\xeb\xaec\x96\xc9n9\
4297\x86\x0f\x9c\x1b@$\x14\x01\xa0\xf4\x04\xef\xfd\xe5\xbd\x1d\x1b\x84\x83\xd1A\
4298\x83\xb8\xecp=v\xddr\xd8\xa9?Z\x8f\xfe\x9e~\x84\xdb\xc2e\x95\x13O\xc61<6\\V\
4299\xde`S\x10\x97\xa3\x97\xb5\xf4\xe8\xc4(b\x131\x04\x9b\x82x\xf3\xfc\x9b \xea\
4300\x88\x92\xe7\xbe\xf5\xa7\xb7\x0c\xe9\xdd\xbc\x06zx\x86\xc7\xda\xff\xd6\xb0\
4301\xf6x\r\xd9L\x164E\xdb\xeb\x11\xf4O\x12ES\x07\xde+h8\xdeP\xb6\x08\x00\xc5\
4302\x00fx\x06#\xf1\x11[\xf5EB\x11\x0c\x9c\x1b\xb0u\xee~a\xdbFP\t\x10\x01\xb46\
4303\xb6n\xab;\xddO\x12s\tL\xa7\xa7\x91ZI\x81\xcc\x92\x00\x80\x13\xc4\t\\\xfc\
4304\xe1E\xc3\xd3\xd8\xdd\xdemK\x08\xe1\xb6\xb0\xc9vZ\xa4\x16\x91\\J\x828F \xdc\
4305\x16>\x906\x93-!P4\x054\xad\xa7_\xfe\xd1\xcb\xf8h\xec\xa3\x1d\xf5\x0cVnQ\xa5\
4306\\\'A\x120:1\x8a;\x0f\xeeX\xb6q5\xbf\x8a\xa9\x85)C\xd7\\\xeb\xabE\xb0)\xb8m\
4307\x0fG/\x02\xab:7\xeb\xfew\xf3\x1al\x85{\xeb,ff\x96f\x0c\xe9p[\x18W^\xbd\x82\
4308\xf3?8\x0f\xbf\xcf_\x91\x86U\x92\xf9\xcc<b\x13\xb1-\x85:\xf9p\xd2\x90\x0e<\
4309\xb7\xb5kl\x05+\xb0\xb8\x1e\xbb^V\x9d\x07\x05[=B<\x19Gw{\xb7A\xd9D\x1d\x81\
4310\xfe\x9e~\xf4\xf7\xf4#1\x97@b.\x81x2^\xb1\x86\xee\x05\xa9\x95\x94!]\xf7\x9d:\
4311[\xe5\xdc\xfc\xe7\xcd\x92=\xc9\'\x9f\x7f\x02\x9f\xc7g\xab\xdc\xdd\xc4\xb6\
4312\x8dp\xe3\xd3\x1bx\xad\xfb5\xcd{\xd0\xa3\x06\x8f^\xef~\x1d\x89\xb9\x04\xc6\
4313\x1f\x8ecjajG\r\xad\x16\xee<\xb8\xb3\xe9\x7f=\xa8\xb6\x94m!0\x02\x83\xe1\xb1\
4314a\xdc\x9f\xb9\x8fW\xce\xbcb9\xee\xd5\xfaj\x11\tE\x10\tE@\xe5(|x\xf7\xc3\x03\
4315\x11U,\x15K\xa8\xc4\xb0\xa6.\xfc\xad6v\xec5\xcc.\xcfb(6\x84\xd6\xc6V\xbc\xf8\
4316\xfc\x8b\xe8n\xef\xb6\xb4\x8a\x89:\x02\x97\xa3\x971<6\xbcoCF\xb4+\x8aH{dS\
4317\xdf\xfeYe\xc7BP\x99\xcf\xccc>3\x8f\x91\xf8\x08:Ov\xe2\xcc\xa93\x96\xc3\xc6\
4318\xc0\xb9\x01\x904\xb9\xa7=C\xfd\xd1z\xbc\xf3\xd2;\x96\xf3"\xc9\xe5\xa4\xf6\
4319\xdd\xef\xf3\x975wr\x18\xa9\x98\x10\xf4L-Laja\n\xb7\xbe\xbc\x85\xb3\x1dg\xd1\
4320\xd7\xd5g\xf8\xbd\xbf\xbb\x1fW\xffzu7\xaa6\xe1\xf7\xf9M"H\xcc%p\xfb\xc1m\x93\
4321\x187F\t\x9f%vE\x08*\xab\xf9U\xc4&b i\xd2\x10i\xdb\xcb\xa7\xae\xf7t\xaf\xc9\
4322\xb7\x8fM\xc4\xf6\xac\xfej\xc1V\x1ca\xbb\xc4\x93\xf1}3\xa2"\xed\xeb\xc3\x13+\
4323\xb0\x8e\x08J\xb0\'B\x00\x80Ejq\xaf\xaa2\xa07\x0c\xab%\xb8\xb3\x1f\xd8\x12B\
4324\xe7\xc9\xcem\xe5\xf7\xfb\xfch!\xd6\xa7a\xa9\x1cU\xd6y\xfa|\x95p\xed\x88:\
4325\xc2\xd2\x80U\xcb\xefi\xef\xd9q\x1d\x95Fo\xcc\xea]\xddJcK\x08\xef\xbe\xf4.\
4326\xae]\xba\x86hW\x14\xf5G\xeb7\xcd[\x7f\xb4\x1e\x83\xd1A\x83K\x19\x9f)\xcf}\\\
4327$\xd7{\x91\x00\x11@\xb4+\xba\xed\xb6n\x14\xdd\x85\xd3\x17L\xa2\x8a\x84"\xb8\
4328\xf2\xea\x15\x93Hv\xf3\xc2\x97\x0bE\xaf\xb7\x9f\xa8#vmV\xd3\xb6\xb1H\xd4\x11\
4329\xe8\xeb\xeaC_W\x1fRT\n3K3\xc8d3Z\x98\xb6\xa3\xb9\x03\x01"`\x9a\xfeMQ\xa9\
4330\xb2\xc7\xe9\xc4\\\xc2p~_W\x1f\xc2ma$\xe6\x12\xa6\xbc\xa1\xe6\x10&\x1fN\x9a\
4331\x16{|\xf6\x9f\xcf\xd0\xdf\xd3\xaf\xa5\x03D\x00\xd7.]\xd3\x86\xaa\xcdVP\xa9\
4332\xc2\xa0h\n\xf7\xa6\xef\x19\x16\x9c\xec\x15\xe3\x0f\xc7\r\x02\x8d\x84"\x085\
4333\x87\x90x\x940\xd9]\xa5\xaeA9T\xc4k\x08\x10\x81\xb2<\x81\xe4r\x127>\xbdQv\
4334\xb9\xf1d\xdc\xb40v\xb3\xba\x92KI\xd3\xb1\xbb_\xdfEkc\xab\xe1b\xd6\xfaj-#\
4335\xa1\xa3\x13\xa3&\x0fG\x7f\xde~\x18\x9aS\x0bSHQ)\xc3\x7f&\xea\x08\xf4\x9e\
4336\xee\xb5\xccou\r\xca\xc1\x96\x10\x86\xc7\x86\x11n\x0b\xa3\xa5\xa1\xa5\xac(]\
4337\x8aJ\xe1\xf6\x83\xdb\xb6"\x8aC\xb1\xa1\x92s\x1a\xe52<6\x0c\x8a\xa6\xd0{\xba\
4338\xd7\xf2\xe9\x8f\'\xe3\xb8\xf5\xe5-\xed\x89oon7\xd5\x97\xe3r\xb6\xeb\xdf)C\
4339\xb1!\xf4u\xf5\x95\x8c\xdaV\x02[K\xd5\xf4\xa8k\xf6\x02\xcf\x05L\xb3u\x8f2\
4340\x8f\x90\xa6\xd2\x15\xebR\x83MA4\x1ck@\xc3\xb1\x06\xed\x18I\x93 i\x12\x82$\
4341\x945\xa1\x13l\n\x1a\xd2\xa5"\x9c\xad\x8d\xad\xda,\xe1\xc6u\x88\xfbI%\xae\
4342\x81\xd5R\xb5\x1d\x0ba+\x9cW\xe7\x0e\x1eVBp\x03\xf6_n)\x07G\x04\x07\x8c\xa7\
4343\xb7\xa3 \x14\x0c\x87\xdd\xbb)\x02\x87\xea@dE\xe3K\xb0\xce\xd68\x87\x1f\x192\
4344D~\xfdux\x8eV\xb6E\xf0X\xdd|\xf5]z\x87C\x86\x0c\x14\xa5"\x00h{#\x88\x82\xa8\
4345\xbc\xfb\xb8\xb1\'P\xf7F\x80\x0c\xc0\xd1\xc2\xa1B\x86\x0cI\x94 \xb2\xa2\xb2\
4346\x7f\x12\xcdAdE\x14\xc5"<\xfa-r\xb4\x13\x9e\x1ax.\xd9\xe5\x88\xe1\x90 \xcb2\
4347\x8aRQ\xd9;)\xc7!\x9b\xc9B\x14\x14A\x00OwU\xfb\xd5\xd0\x1b\x06\xd3\xde\xe3U\
4348\xe2Ln\x8f\x1b.8b\xa8jd\xa5\'(JE\xb09\x16\xcc\x1a\x83\xb5\xc7kHO\xa7\x91#\
4349\x95 \xd9\xf27\x8f\x95]\xd5\x98\'\x8c6fx}^\xd4xk\xe0\xf6\xb8\xe1=\xe2\x85\
4350\xc7\xeb\x81\xdb\xa3\xd8\x94\x8e(\xaa\x04Y\xfdP\x04 \x89\x92a\xf3,\xbd\x08T\
4351\xb4\x9dW/\xfe\xf6grAT|\xcb\x1ao\x8d"\x08_\x8dA\x18\x0e\xd5\x83j\x14\x16\xc4\
4352\x02\xb8\x1c\x07.\xcfa%\xbd\x02\x9a\xa2\r"X\xfe\xe6\xb1q\xe7\xd5\x82X\xc0Jz\
4353\x05\xc7\x1b\x8fc%\xbd\x02\x008\xdex|O\x1b\xefPy\xd4\xcd\xb28\x9a\xc3Zf\rE\
4354\xb1h\x99\xcf\xb4)\xf7\x8f\x7f\x1e\x96U\xdf\xd2\xa1z\xd1\xdcCVq\x0f7\n@\xed\
4355\tT,\xb7\xe9\x0f\xfe\xe4\xfbr\xa9\x02\x1c\xaa\x9f\x8d"\x00J\x08A\xa5\xe9\xf9\
4356\xef9\x13\x05\x87\x08+\x01\xa8\xfc\x1f\x05\x81\x11\xc3\xb6\xc2\xdf\x94\x00\
4357\x00\x00\x00IEND\xaeB`\x82'
4358
4359def getSageStartUpBitmap():
4360    return BitmapFromImage(getSageStartUpImage())
4361
4362def getSageStartUpImage():
4363    stream = cStringIO.StringIO(getSageStartUpData())
4364    return ImageFromStream(stream)
4365
4366#----------------------------------------------------------------------
4367def getSageStartDownData():
4368    return \
4369'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x82\x00\x00\x002\x08\x06\
4370\x00\x00\x00\x90\xc9\x03\x88\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
4371\x00\x00\x06\nIDATx\x9c\xed\x9d1h\xdbX\x18\xc7\xffN\x1b\x1f\x166=xYb,\xf0\
4372\xf4\xbc\\\xb1\xc0\x934\x05\x0c\xbd\xc5\xde\x8a\x03]\x128\xae%]\xbd\x95\x0c\
4373\x07\x85l\x9eC\xaf\x14|\xcb\r\x86\xa3\x83\xbd\xf4\xa8!\x93\x0cG\x0c\n\xe9bA\
4374\xa9\x0f\x99\x18\x8e\xbc.\x0e1u9\xee\x86D\x8ae\xc9\x8e"\xcbv\x1a\xbf\x1f\x84\
4375D\xb2\xa4\xf7\xf4\xf4\x7f\xdf\xf7\xbd\xef=+\xa1\xd0\xca=\x8c\xe3\xe7\xfd\x9f\
4376\xfe\x1b\xfb!\xe7\x9b\xe3\xf5\xce\x9b\xd0\xb8\xcfBnB\xe0\x02\xb8\xdb\xb8\tbe\
4377t\x07\x17\xc1\xdd\xc7\xed\x19[\x16\x81\x0b`91\xad\x83\xc3"p\x96\x93\x15\x80[\
4378\x83e\xc6|\xf6\xdc"p\x00\x00\xa1\xa7\xaf\x9erk\xc0\xe1\x16\x81s\x01\x17\x02\
4379\x07\x00\x17\x02\xe7\x92\xfbA]H\x08\x0bH\xac%\x1c\xfb;\xa7\x1d\x9c\x0f\xce\
4380\x83*\x863#\xa6\x16\x82B\x15d\x1ff\x91 N\x11\x98\xf4\x07}\xe8\':Z\xdd\x16\
4381\x1a\xad\x86ga\xbcz\xfa\xca\xfa[\xef\xea(UK\xd3V\xf7\x9bc^m\xe0[\x08BX@1_\
4382\x9c(\x00\x93H8\x82t2\x8dt2\r\x83\x19\xd0Ot\xbf\xc5rf\x84o!\xec\xfc\xb8c\x13\
4383\x81\xd9\xeb\rfX\xfbh\x9cB$""\xe1\xc8t\xb5\xe4\xcc\x1c_B\x90\x92\x12\xe8:\
4384\xb5\xb6\x1bz\x03\x15\xb5\xe24\xf9\xcd\x8b_"\x11A\xe3\x14\nU|W4(h\x9c"\xb5\
4385\x9e\x02\x89\x11\x90\x18\xb1\xf6\xf7\xbf\xf4a0\x03Z[\xb3\x89\xf9:Fc#\xd6c`=f\
4386mKI\xc9\xea\x10&\x063\xc0z\x0c\xf5\xe3\xfa\x94w\x13\x1c\xbe\x12J\xdb\x1b\xdb\
4387\x90\xa9\x0c\xe0\xc2\x12\xbc\xf8\xfd\xc5\xd4\x01a1_\xb4\x89\xcb\x0f\xa5Z\xc9\
4388\xd5\xed\x90\x18\xc1\xa6\xbc\x89t2\xed\xe9:\r\xbd\x81\xf2A\xd9\xd3\xb14NQ\
4389\xcc\x15\xad\xedZ\xb3\x86j\xb3\n\x1a\xa7\xd8\xde\xd8\x06\x89\x92\xb1\xe7>\
4390\xfb\xf5\x99m{\x96mp\x1d\xbe,\xc2pOb=v\xebG\x05$F<\x8b\x00\x00d*\xe3|p\x8e\
4391\x8aZ\xf1U\x9eB\x15lml\xf9:wQL=jH\x90\x04D"\xde\xc8\x9c.\x92\xa3\xf6\x11Z\
4392\xdd\x96e\x9e\x81\x0b\xd7\x95}\x98\xb5\xf5F\x85*\xbe\x84 %%G\xecd\x06\xc8$F \
4393%\xa5[\x193\xf9\x12\x02\xeb1`\xfdj;\x9f\xc9\xa3|P\x9e\xca2\xb8\r\x8b\x82\x1a\
4394:\xf5\xbf\xf4Qk\xd6P?\xae\xbb\xd6\x91\xf5\x18\xb4\xb6f3\xcd\x91p\x044Nolf\
4395\x87E\xe0V\xe6$\xf3?\xcb6\xb8\x0e_\x99\xc5\xd1\xc6I\'\xd3\xd8}\xbc\x8b\xec\
4396\xc3,\x84\xb0\x10H\xc5\x82\xc4`\x06\xaa\xcd\xea\xb5B\xd5\xda\x9am{8\xc0\xbb\
4397\t\xfdA\x1f\xa5Z\xc9S\x99\xb7\x05_\x16A\xd5U\xc8)\xd9\xa6l\x12%(\xc8\x05\x14\
4398\xe4\x02\x8e\xdaG\xd0\xda\x1aT]\r\xac\xa2\xf3`\xd4\xbd\xf9\x15u\xf9\xa0<\xd6\
4399\x92T\xd4\n"\xdf\xdd\x11\xd7\x00\x00\xfb\xef\xf6QP\n\xd6\xe8a\x183yTP\n\xd0\
4400\xda\x9a\xf5\xb3\x0c\xd4?\xd4\'\xde\xebm\x8d\xa5|\x0b\xe1|p\x8e\xf2A\x19\xaa\
4401\xae"\x9f\xc9\xbb\xfa\xbdH8\x02\x99\xca\x90\xa9\x0cv\xc6&\xf6\x94y2.\x97\x10\
4402\x84[\xeb\x7f\xe9O}\x8dE0\xf5\xa8A?\xd1Q:)A$"\xe4\x94\x0c\x85*\xaeQ1\x89\x12\
4403\x14sE\xfcv\xf0\xdb\xc2\\F>\x93\x87\x9c\x92\'\x8e\xed\x97\x95\xc0f\x1f\rf\
4404\xc0P\rT\xd4\n\xa4\xa4\x04))\xb9\xba\x8d\xad\x8d-\x9c\x9e\x9d\xce\xd52\x90\
4405\x18\xc1\xf3G\xcf]\xe7E\xf4\xeeU=\x84\xb0\xe0i\xee\xe4.\x12\x98\x10\x861c\
4406\x82j\xb3\n\x85*\xc8er\xb6\xcf7\xe5M\xbc\xfc\xe3\xe5,\x8av \x84\x05\x87\x08\
4407\x8e\xdaGx\xff\xe1\xbdC\x8c\xa3Y\xc2eb&B0a=\x86j\xb3\n\xd6c\xb6L\xdb<{\xdd\
4408\xe8\x14\xb9\x99\x02\xe6\xd8\x99\xcb\n%UW\xd1\x1f,&\x88\x92SW\xee\xa9?\xe8s\
4409\x11\x8canK\xd5\x165l\x1a\x0e\x0c\xbf\x95\xe4\xce"\xf0%\x04))\xdd\xe8x!,\xd8\
4410\xb2t\xec\x8cM8\xfa\x8a\xe1\xe3\x82\x18\xda\x91(\x19;\x15.\x84\x85[1M>\xcap0\
4411;<\xd4\r\x1a_B\xd8y\xb4\x83\xbd\'{\xc8g\xf2\xd7V\x8e\xc4\x08\x8a\xf9\xa2mH\
4412\xd9h5<\x95\xd39\xedX\x7f\'H\x02\xf9L\xfe\xc6u\x1d\x15\x9d[\x1a\\\xa1\nv\x1f\
4413\xef:F9\xb3lx\xaf\x0c\xafm Q\x82\xed\x8d\xed\x99\x94\xe3;X$Q\x82\\&\x87\\&\
4414\x87\x0e\xeb\xa0\xd5m\x81\xf5\x98\xe5\x02R\xeb)\x88DtL\xffvX\xc7\xb3\x9f\xd6\
4415\xda\x9a\xed\xfc\\&\x07))\xb9f\xeeh\x9cBkk\x8e\xc5\x1e\xf5\xe3:\nr\xc1\xdaN\
4416\x90\x04\xf6\x9e\xecY\xf5\x9c\xb4\x82\xca\x14\x06\xeb1\xa8\xbaj{(\xf3Bkk6\
4417\x81\xcaT\xb6\xeeu4y5\xae\r\xbc\x10\xc8\xa8!A\x12\x9eF\x02zW\xc7\xfe\xbb}\
4418\xcf\xd7Uu\xd5\x11\xf5O*\xcb-7Q?\xae_$\xbb\x86\x1a3\x12\x8e\xb8fBk\xcd\x9ac\
4419\x843|\xde"\x02M\xad\xad\xa1\xc3:\xb6{&Q\x82\xec\x0fY\xd7\xe3\xfd\xe6g\xeee\
4420\xf2\x99_nz\xd2\xe7\xdeg\x84\x10\xc2\xea\xfdUO\xbe\xbb\xc3:x\xfb\xd7[T\x1a\
4421\x15|\xfd\xf7\xeb\x8d\xca:\xfcx\x88\x07\xc2\x03O3\x81zW\xb7\xf9T\x13\xad\xad\
4422!\x84\x10\xc45\x11\xab\xf7V\x1d\x9f7\xf4\x06\xf6\xff\xdc\xb7\x96\xa9\xad\xc5\
4423\xd6\x1c\xe5i\x7fk\xf8\xf4\xcf\'\xc7\xb9$f\x8f;\xc6\xd5a\x1a\x0e?\x1eb\xf5\
4424\xfe*\xd6\xbf_w\xad\xff0~\xcb\x9f\xfa\xbb\x8f\xe6\x9a=\x91\x88\x0eQ\x18\xcc\
4425\xb0-\x00\x99\x16\x1a\xa7X\x8b\xae9VH\x9d\x9e\x9dZk\x0e\xbd\\c\x98q=H$\xa25K\
44268\xba\x0eq\x91\x04\xd1\x06n\xf0/\xc1r\x00\xf0\xaf\xbcq.\xe1B\xe0\x00\xe0B\
4427\xe0\\\xc2\x85\xc0\x01\xc0\x85\xc0\xb9\x84\x0b\x81\x03\x00X\x99\xf4ZV\xcer\
4428\xf0z\xe7M\x88[\x04\x0e\x80K\xd7\xc0\xad\xc2\xf2\xc2\xdf\xbc\xca\xb1\xe1x;;\
4429\x7f\x0b\xebr0\xea\x05\x1c\x16\x81\xbb\x89\xbb\x8f\xdb3v\xfd\x7f\r&\xdc:\xdc\
4430-&u\xf2\xff\x01\x07\x85\x9d\x95\xbbRg\x1a\x00\x00\x00\x00IEND\xaeB`\x82'
4431
4432def getSageStartDownBitmap():
4433    return BitmapFromImage(getSageStartDownImage())
4434
4435def getSageStartDownImage():
4436    stream = cStringIO.StringIO(getSageStartDownData())
4437    return ImageFromStream(stream)
4438
4439#----------------------------------------------------------------------
4440def getSageStopDownData():
4441    return \
4442'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x82\x00\x00\x002\x08\x06\
4443\x00\x00\x00\x90\xc9\x03\x88\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
4444\x00\x00\x06}IDATx\x9c\xed\x9dOh\xdbV\x1c\xc7\xbfN[\x0f\x9b\x84\x0c^`\x8d\
4445\x91\xc1\xf4 _\x16,p/\xce\xc9`h/\xd2m$l\x97\x18\xc6\n\xee\xd5\xb7\xd0\xc3\
4446\x0e%\xb7\xd2\xddL)\x05\xe7\xb2\x81a\xf4\x90\\Z0d=H0\x1a\xb0i;\xf0\x83\x81A"\
4447\xbe\xf8\xf5\xe2\x10\xd3\x94\xb1\x1d\x1ci\xd6\x1f\xdbrb\xc9i\xf2>\x97\xe4\
4448\xe9\xcf{O\xd2\xf7\xfd~\xbf\xf7{\x8a\x12\x89,\xdc\xc0(~\xaa\xfc\xf8\xef\xc8\
4449\x9d\x9c/\x8e\xe7\xa5\x17\x91Q\xfb"^B\xe0\x02\xb8\xdax\tb\xc1\xb9\x81\x8b\
4450\xe0\xea\xe3\xf5\x8c-\x8b\xc0\x05p=1\xad\x83\xcb"p\xae\'\x0b\x00\xb7\x06\xd7\
4451\x19\xf3\xd9s\x8b\xc0\x01\x00D\x1e<{\xc0\xad\x01\x87[\x04\xce\x00.\x04\x0e\
4452\x00.\x04\xce\x197\xe7\xd1h<\x1a\x87\xb0"\xb8\xb6\x1b]\x03\'\xa7\'s\xe8\x11\
4453\'T!\xac\x8b\xeb(\xac\x15 \x10\xb7\x08L\xfa\xa7}\xd0#\x8aV\xa7\x05\xad\xa5\
4454\xf9\x16\xc6\xb3\x07\xcf\xac\xdfi\x87\xe2\xc9\xde\x93\x0b\xf7\xf7:\x11\x8a\
4455\x10\xe2\xd18\xcaJy\xac\x00Lb\xd1\x182\xa9\x0c2\xa9\x0ct\xa6\x83\x1e\xd1\x10\
4456z\xc8\tE\x08\xa5\xfb%\x9b\x08\xccQ\xaf3\xdd\xda&&D$I\x12\xb1h,\x8c.q\x1c\x04\
4457.\x04)%A\\\x15\xad\xb2F5\xd4\xd4\x9a\xdb\xe4\x1f\x0e~$I\x12bB\xc4\xba\xb8\
4458\x1et\xd7&\x12\x8f\xc6!\xa5$\x08+\x02\x92$im\xd7\x99\x0e\xa3k\xa0\xd1n\xf8r]\
4459d\x89\x80,\x11\xab<\x1c\x0b\x91%\x82uq\x1dbbp\x8f\xfa\x9f\xfaS\xbb\xc5Y\x10x\
4460B\xa9\x98/"\'\xe6\x00\x0c,\xc1\xf6\xaf\xdb\x17\xbe\xc0\xb2R\xb6\x89\xeb<<\
4461\xd9\x7f2\xd6\xed(Y\x05\x85\xb5\xc2D\x0b\xb5\x7f\xb8\x8f\xfa\xbb\xfa\xd8kR\
4462\xb2\n\xe4\xac\xecj{c}\x03\x85o\x0b\x9e\xe7\xf4O\xfb\xa8\x1eT\xd1h7&\\\xc9l\
4463\x08|\xfa8<\x12X\x8f}\x11\xb3\x82b\xbe\x089+\xfbrSrVFY)#\x1e\x8dO\xd5FY)\x8f\
4464\x14\x010\x88\x95J\xf7J\x90R\xd2T\xf5\x9e\x97Pg\r\x02\x19\x98\xd8\xe1\xd8\
4465\xe0\xb21l\xc1L4\xaaA\xa5*\x8c\xae\x01aE@z5m\xb3\x16\x02\x11P\xba_\xf2=S\xd9\
4466\xccmZ1S\xff\xb4\x8fF\xbb\x01\xd6c K\x04RJ\xb2\t\xb0\x98/\xce\xc4\x8aN"p!\
4467\xb0\x1e\x03V\xff/+Y\x05\xd5\x83\xea\x85.\xcc\xeb\x86\xcfb\xfa(&D\x97\x08*\
4468\xaf+6\xf3L\x8f(\xe8\x11E\xa3\xdd@Y)[\x0fM\\\x1d\xc45*U\'\xb6c\x8a\xc0+^J\
4469\x92\xa4\xad\xdeX4\x86\xc2Z\x01{\x87{S_\xcf4\x04\xee\x1a\x9c~8\x93\xca\xe0\
4470\xd1w\x8fPX+LmN\x83F\xc9*\xb6\xf2\xfe\xe1\xfeH\x1f\xad3\x1d5\xb5f\xdb&\xdf\
4471\x95=\x8f\xf5B\xa3\x9a\xe7\x80\xf0\xaa7\x97\xb6\x8b3\x08\x02\x17\x82JU\xd0\
4472\x8e]\x0cd\x91`#\xb7\x81\xa7\xc5\xa7xx\xef\xe1\xa5\x99!8\x03\xd0\xfa\xbb\xfa\
4473\xd8sT\xaa\x82\x1d3\xabL\x16\x89mv1\nS\x04\xe3\xea\x1d\x86,\x92\xc0\x07M(k\r\
4474\x95W\x15hT\xf3\xdc\x97Ie\xb0\x95\xdf\xc2/\xc5_P\xcc\x17C\x0b\x8e\x9c\x98\
4475\xd37\x93f\xbb\xe9\xcb}9-\x86\x9f\xfe\xfbq\x1f\xce\xc1\xe3\x95\x92\x9f%\xa1\
4476\x04\x8b\'\xa7\'\xa8\x1eT\xa1R\x15JV\xf1\x9c\xfa\xc5\xa21\xe4\xc4\x1crb\x0e\
4477\xec\x98\xa1zP\r5\xab\xe8\x1c\xc9~\x03Z\xa3k\xd8\xca\xb1\xaff\x93\x10s\xc6VA\
4478\x13\xea\xea#=\x1a\x04q\x8f\x7f\x7f\x8c\xfa\xfb:\xfa\xa7}\xcf\xe3\xc8"AY._\n\
4479\x971\x89\xeeq\xd7V\xf6\xe3\x1a\xfc\xc0z\xccV\x9eU\xbd\xa3\x98\xcb\xea\xa3\
4480\xcet\xe8\xea (\x92R\x12\xa4\x94\xe4\x8a\xd6\x01`+\xbf\x85\xeeq\x97\xaf7\xc0\
4481-\x8cY3\xf7\xf7\x11\x1a\xed\x06\xaa\x07Ul\xff\xb6\x8d\xfd\xc3}\xd7\xfe\xcd\
4482\xdc\xe6\x1cz5\x7f\x86\x13q\x00\x02\xcf#\xcc]\x08&\xac\xc7\xb0w\xb8\x87\xdd\
4483\x83]\xdbv?+\x96\xf3\xc4i\xb2ge\xbd\x9cB\xe8\x7f\xf2v\xa3\xb3\xe2\xd2\x08\
4484\xc1D\xa5\xea\xc8\xd8!HZ\x9d\x96\xad\xecw\xf6\xe2\x14\xc2\xacL\xf8y\x83\xd7\
4485\xf3r\xe9\x84\x00\x04\x7f\xd1^8G\xb2@\x04\xd7\xa8tb\xaeN\x0e\xe3\x14\xd4yp\
4486\xa6\x99\x9b\xed\xe6\x85\xeb\x9cD\xe0B\x986/\x10\x8f\xc6m\xa3a8a3\x8e\xe1\
4487\xe3\xce\x9b|q\xe6:&\xc5\'\xf2]\xfb\xc2\x14\xedP_\x16a\xd2l\xa8\xb0f_\x8c\nc\
4488\x052p!\x94\xee\x95\xb0\xf3\xc3\x0e\x94\xac2q\x84\x91%b\xcb\xb3\x03\x80\xd6\
4489\xf2ND9\x19\x9e\xcf\x0bDp\xa5\x8b\xfd\xb0w\xb8gsK\x99T\x06\xc5|\xd1\xf3\xd8\
4490\xc2Z\xc1\xb5z\xe8w= \'\xe6PV\xca\x9e\xf7\xa3\x98/\xda\xf2,\x063|%\xa0.J(\
4491\xd3G\xb2H ge\xc8Y\x19\x063\xd0\xea\xb4\xc0z\xccr\x01\xe9\xd54\x92$\x89L*c;\
4492\xcf`\x86\xef\x9b\xdbh7l\xe7\xcbY\x19RJ\xf2\x1cMbBD\xa3\xddp\xa5\x90Y\x8f\
4493\xa1\xa6\xd6\xb0\x95\xdf\xb2\xb6\xe5\xc4\x1c\xc4\x84h\x13\xa4\x94\x92\\Al\
4494\xfd}}\xaa@Q\\\x15\xb1\xf3\xfd\x0e4\xaaYV\xc4\xab\xdeq\xa9\xe8Y\x12z\x1eA \
4495\x82\xaf\x99\x00\xedPT^U|\xd7\xabR\xd5\xf5b\xec\xb8\xb6F=4s\xf4\r\x8b\xc1\
4496\x14\xf2(\xea\xef\xeb\xae\x85\xa2qhT\xb3\xf2&^\xf9\x13\x93\xdd\x83\xdd\xd0\
4497\xe2\xa5\x1bY%\xfbs\x90\r|\xec}D\x04\x11\xdc\xbay\xcb\x97\xef6\x98\x81\x97\
4498\x7f\xbeDM\xab\xe1\xf3?\x9f\xa7j\xeb\xed\xdfo\xb1\x1c_\xf6\x95\x85\xa3\x1d\
4499\xea\xca\xe7\x9b\xe8LG\xb3\xdd\xc4r|\x19\xb7\xbf\xbe=\xb6\x8e\xea\x1fU\xbc\
4500\xf9\xeb\xcd\xd8\xb6\xd2\x89\xb4m-\xa3\xa6\xd5\xd0l7q\xe7\x9b;\x9e\xf7\x84v(\
4501*\xaf+\xf8`|\x98x\x1d\xb3"\xd4\xbf}4\xff\x9e!I\x92\xae\x1b\xa03\x1d:\xd3g6\
4502\xfd\x12\x13"V\x16W\\oHu\x8f\xbb\xe8\x7f\xea\xfb\x1eif\x9f\xd3\xabik\x9b\xe9\
4503\xda\xfc\xf6u\xd4\xabjf?\xcd\xfb\xc1z\xcc\xaa;lBu\r\'\xa7\'\xd6\x8b\x1dAC\
4504\x8f((.\xdeN\xd0}\x0e\xeb~L\xe2R\xe6\x118\xe1\xc3\x85\xc0\x01\xc0\x85\xc09\
4505\x83\x0b\x81\x03\x80\x0b\x81s\x06\xfft\x0e\x07\x00\xb00\xee\xb3\xac\x9c\xeb\
4506\xc1\xf3\xd2\x8b\x08w\r\x1c\x00g1\x02\xb7\n\xd7\x17\xfe\xe5U\x8e\r\xd7\xd7\
4507\xd9\xf9WX\xaf\x07N/\xe0\xb2\x08\xdcM\\}\xbc\x9e\xb1\xe7\xffk0\xe1\xd6\xe1j1\
4508n\x90\xff\x07\x00?\xc8\r\xeep)4\x00\x00\x00\x00IEND\xaeB`\x82'
4509
4510def getSageStopDownBitmap():
4511    return BitmapFromImage(getSageStopDownImage())
4512
4513def getSageStopDownImage():
4514    stream = cStringIO.StringIO(getSageStopDownData())
4515    return ImageFromStream(stream)
4516
4517#----------------------------------------------------------------------
4518def getSageStopUpData():
4519    return \
4520'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x82\x00\x00\x002\x08\x06\
4521\x00\x00\x00\x90\xc9\x03\x88\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
4522\x00\x00\t\x91IDATx\x9c\xed\x9coh\x1b\xe7\x1d\xc7\xbf\x92%eV\x9c\x80{\xf66;r\
4523,\x92E5\x145\xea\x94\x17S\x12\x863wII\xa5\xd0\x17#8]\x19\x82\xc2\xc0K\xde\
4524\x14\x95\x11J\xdf\x8d\x85\x8e\xd6\xb4/\n\xa1\xd0\xaena+1\x85\x91\xd8c+\xd4\
4525\x8d\xd9\xe6h\xa5qpk\xc6\xa4\xf3\xda\xd9\xb1\xfe\x90Ig\xc7:Iw\xba;\xe9\xf6\
4526\xe2zg\x9dt\xfa\x17K\x8a\xed\xdc\xe7\x8d\xf4\x9c\xee\xcf\xa3\xbb\xef\xf3\xfb\
4527ww\x8f\xc1`\xec@%\xe6\xfe\xfdwQ\xfe\x9e\xcde+\xae\xa7\xb3\xfd\x10\n\x02\x00\
4528\x80\x17x\xe4\x84\x1cx\x81\xc7\xf3?~\xc1Pi}\x83\x96\x10\xee|}[\x04\x00\x86c\
4529\x00H"\x90w,\xe4\x85\xe6\xf7Z\xa7\xa9\x88\xa22~\x15\x11\xb0<\x8b4\x9b\x06\
4530\xcb\xb3\xf8\xf5s\x97\xcb\x04Q&\x84;_\xdf\x16K\x05P\xac*\x9d\x9d\x05\xcb\xb3\
4531\xcawY\x084C\xe37\x17~\xab\x12\x83"\x84b7\x00\x00)&\x854\x9bV\xa9Ig\xe7"\x0b\
4532\x82\x138\xa4\xb2)\xe4\xf8\x1c2\xb9\x0c\xde\xfd\xd5\xef\r\x00`*\xdd \x9b\xcb\
4533\xe2~\xf6>h\x86F\x9aM#\xc5H\x1b\x01P>uv\x16{\xcc{T\xed\x1c\x9fC6\x97\x05\xc7\
4534p\xca2\x83\xc1\xd8\x81\xbf\xdc\xf9\xb3\x08H\xfe_6\x1d\xb1\xf5\x98\xa2\x1c>\
4535\xcf+1\x02\xa0\xf6A:\xdb\x13\x83a\xd3\xf2\x9b\x8c&\x98;\xccJ\x9b\xe1\x19\xb0\
4536\x19\x16\xb9L\x0el\x86\xc5\xf5\xdfM\x19L\x00\x14\xdf\x9ff\xd3H\xd2I\xa4\x98\
4537\x14RY\xc95\xc8\x02\xd0/\xfe\xce\xa2\xf8z\t\x05\x01BA\x80\xc9(9\x00\x81\x13\
4538\xc0\xe7x\xb0\x19\x16lZr\x19\x86\x8f\xfe\xf1\x91\x08l\xfa\x90$\x9dD2\x95\xd4\
4539E\xb0\xcb(\xb6\x10\xb25\xc8\xacg\x90^\x97b?SqT\x99\xa4\x93\xa0\x19Zq\x07:\
4540\xbb\x07Q\x14a0\x18\x94A\x9d\xe7\xf3\xe09\xe9\x1a\xa7\x12)\x98\x8a\xb3\x01N\
4541\xe0\x14\x11\x14o\xa4\xb3;(\xbe\x9e<\xc7#\xcf\xe7\x91J\xa4\x00\x00F\xf9\x07\
4542\xd92\xe4\xf8\x1c\x84\x82\xa0\x8b\xe0\x11\x82\xa5Y\xb5khWzh\xb5Xq\x808P\xb6<\
4543JE\x91\xe5\xf4Rv\xbb\x10\xb8\xcdLP\xa9#p\x02\xa7\xb9r3\xf18<x\xda\xf94l\x84\
4544\xad\xe2:\x0c\xc7 \x1c\x0b#\x14\r\xe1\xf3\xa5\xcf\xeb\x16\xc6;\xbf|G\xf9N\
4545\xc6I\x8cO\x8do\xb9\xbf\x8f\n<\xc7\x97\x17\x94\x80\xe6g\tV\x8b\x15\x01_\xa0\
4546\xaa\x00d:-\x9dp\xd9]p\xd9]\x88\xacE\xb0\x14_jj_t\xb4\xd1\x14B\xb3\x19;3\xa6\
4547\x12\x81<\xea#TDY\xe6\xe8w`\x80\x18@\xa7\xa5\xb3\x1d]\xd2)\xa1\xe5Bp\x1et\
4548\xc2\xd1\xe7P\xdaA2\x88\xc9[\x93\xe5&\x7f^\xfa\x18\xec\x1d\xc4\xa1\xef\x1e\
4549\xc2\xc9\xa1\x93\xad\xeeZM\xac\x16+\x8e\xda\x8fb\x80\x18\xc0@\xcf\x80\xb2|5\
4550\xb9\x8aUj\x15_.\x7fY\x97\xeb\xea\xde\xdb\x8d\x9e\xfd=J\xbb8\x16\xea\xde\xdb\
4551\x8d\x93C\'\xe1\xe8\x97\xceQ6\x97m\xd8-6\x03\xc3\xeb7^\x17\x81\xcd\xd41\x95M\
45525\xb5\x03\xfea?<\x0e\x0f\x00\xc9\x12\xbc\xf2\xc7W\xb6\xbc\xff\x80/\xa0\x12\
4553\xd7\x83\xf0\xc6\xd4\x1bU\xdd\x8e\xcf\xed\xc3\x88s\xa4\xa6\x85\x9a\x9e\x9f\
4554\xc6\xcc\xe2L\xd5\xff\xe4s\xfb\xe0u{\xcb\x8e}\xdes\x1e#\xce\x11\xcdm\x18\x8e\
4555\xc1{\x9f\xbd\x87\xc5\xbb\x8b5\xfeI\xe3\xe4\xb29l\xfco\x03\x1b\xf76\x90J\xa4\
4556@S\xf4f\xfa\xd8*\x88}\x84\xf2\x9d\xa2\xa9\x1d\x91\x15\xf8\x87\xfd\xf0\xba\
4557\xbdu\xb9)\xaf\xdb\x8b\x80/\x00\xab\xc5\xda\xd01\x02\xbe@E\x11\x00R\xact\xe9\
4558\x99Kp\x1et6\xb4\xdf\x07\xa5-1\x82\x8c\x8d\xb0a\xb0w\x10+\x89\x95v\x1e\xb6!\
4559\x8a-\x98L\x90\x0cb.<\x87(\x15\xc5\x01\xe2\x00\x86\xfa\x87T\xd6\xc2F\xd80vf\
4560\xac\xeeLe\xf4\xf8\xa8\x1231\x1c\x83\x85\xe5\x05P4\x05b\x1f\x01\x97\xdd\xa5\
4561\x12\xe0\x8b?y\xb1)V\xb4\x16-\x17\x02ES@\xdff\xfb\xecSg\xf1\xc1\xec\x07[\xfa\
4562cZ\'\xbc\x19\xe9\xe3\x91\xbe#e"x\xfb\xafo\xab\xcc\xf3R|\tK\xf1%|u\xf7+\xbc\
4563\xf4\xecK\xcaEs\xf49\xe0qx\x10$\x835\x8f#\x8b@+^\x1a\xec\x1dT\xed\xb7\xd3\
4564\xd2\x89\x11\xe7\x08\xa6\xe6\xa7\x1a\xfe?\x8d\xd0r\xd7\x10\x8e\x85Um\x97\xdd\
4565\x85W\x7f\xf6*N=q\xaaas\xdaj\xce\x1d;\xa7jO\xcfOW\xf4\xd1+\x89\x15\\\xbbuM\
4566\xb5\xccw\xccW\xf7\xb1\x82d\x10\x13\xb3\x13e\x03Bk\xbf\x9e\xc7\xd5\xe2l\x05-\
4567\x17B\x90\x0c\x82\x8c\x93\xaaeD\x17\x81\xd1\x13\xa3x\xd3\xff&\xc6N\x8f\x95\
4568\x8d\xc2\x87\x81\xd5b-\x0b@g\x16g\xaan\x13$\x83\xa0\xd2\x94\xd2&\xba\x08\x0c\
4569\xf6\x0e\xd6<\x96,\x82j\xbf\x17Ct\x11-\x1f4-\x17\x02\x00\\\xfd\xe4jE\x93\xe9\
4570\xb2\xbb\xe0\x1f\xf6\xe3-\xff[\xf0\x0f\xfb\xdb\x16\x1c\x95r\xf8\xfb\x87U\xed\
4571\x85\xe5\x85\xba\xdc\xd7\xc2\x7f\x17T\xed\'\x0f>Ys\x9b\xb9\xf0\\\xcduJ\x07\
4572\x8fVI\xbe\x99\xb4%X\xccrYL\xccN`.<\x87s\xc7\xcei\xa6~\x9d\x96Nx\x1c\x1ex\
4573\x1c\x1ePi\n\xef\xdf|\xbf\xadUE{\xaf]\xd5..vUc\x95ZU\xb5\x9bU\x10+\x8d\xadZM\
4574[,\x82\xccR|\t\xe3S\xe3\xb8\xf2\xa7+\x98Y\x9cQ\x1e\x97/\x85\xe8"\xf0\xb2\xef\
4575\xe5m\xe12j\x91\xa4\x93\xaavq\xe1i+P4\xa5j\xdb\x1e\xab]\x9e\xdf\nmM\x1feV\
4576\x12+XI\xac`28\t\xe7A\'\xdc\x87\xdc\x9a\x17\xdd?\xecG\x92N\xea\xf7\x1bP.\xb8\
4577f\xd3V\x8b\xa0\xc5\xe2\xddEL\xccN\xe0\xf2\x1f.cz~\xba\xec\xf7\xd1\xe3\xa3\
4578\x0f\xa1W\x0f\x9f\xe2B\x1c\xa0~?\xa1\x15<t!\xc8\xacg\xd615?U\x16M\xd7s\xc7\
4579\xf2aRj\xb2\xc9\x18Ya\xcd\xc6(\x15B\xab\x1f\x13\xd86B\x90\t\x92\xc1\x8a\xb1C\
4580+\t\xc5B\xaa\xb6\xcb\xee\xaak\xbb\xd2t\xb1Y&|\x80P\xc7\x1a\xad\xae\xc6n;!\
4581\x00\xe5\x91x;(\x8dCl\x84\r\xdd{\xbb\xabnc\xb5X\xcb\x04\x13\x8a\x86*\xac]?\
4582\xce\x83NU\xf6\xb1\xb0\xbcPe\xed\xe6\xd0r!4Z\x17\xb0Z\xac\xaa\xd1P\\\xb0\xa9\
4583F\xf1z\x0fZ|)\xadu\x8c\x9e\xa8\x1e\x9f\x94\xde\x98"\xe3$\xd63\xeb5\x8fs\xe2\
4584\xf1\x13U\x7f?}\xf4\xb4\xaa\xbd+\x84p\xe9\x99K\xb8\xf2\xfc\x15\xf8\xdc\xbe\
4585\x9a#\xac{o7\x02\xbe\x80\xea\xe4\x06\xc3\xb5k\xf7\x80\xf4\x8c\x80\x8c\x8d\
4586\xb0\xc1\xe7\xae\xbf\xdc+s\xfd\x8b\xeb*\xb7$\x17\xbb\xb48\xf5\xc4\xa9\xb2\
4587\xbb\x877n\xdf\xa8\xeb8\x1e\x87\x07\x01_@\xf3|\xf8\x87\xfd\xaa:K\x84\x8a\xd4\
4588u\xffb\xab\xb4%}$\xba\x08x\xdd^x\xdd^D\xa8\x08\xc2\xb10\x12\xa9\x04"kR\xd1f\
4589\xa8\x7f\x086\xc2Vff#T\xa4\xee\x9b-\x0b\xcb\x0b\xaa\xed\xbdn/\\v\x97\xe6hr\
4590\xf4;p\xe7\x9b;\xb8\xf9\xaf\x9b\xaa\xe5\xeb\x99u\\\xbbuMu\xf1=\x0e\x0f\x1c\
4591\xfd\x0e\x95 ]vWY\x10;\xb38\xd3P\x9a\xeb\xe8s\xe0\xb5\x9f\xbf&\x95\xa9\xbf\
4592\xad\x19h\xed\xf7\xc3\xbf}X\xf7>\xb7B\xdb\xeb\x086\xc2VW&@\xc6I\\\xfd\xe4j\
4593\xdd\xfb\r\x92\xc1\xb2\x07c\xab\x1d\xabRt/\x8f\xbeb1\xc8B\xae\xc4\xcc\xe2\
4594\x0c&\x83\x93\r\xf5U\xae\x9bT+\x9aM\xccN\xb4\xed\x96}\xcb\x8501;\x01\x97\xdd\
4595\x85\x81\x9e\x01\x10]D\xcd\xf5#T\x04\x9f.~\xfa@\xe6p|j\x1c\xe7\x8f\x9f\xdfrE\
45962H\x06\x11[\x8f\xe1\xecSg\xabf\x0fd\x9c\xc4\x8d\xdb7\x1a.x\xcd\x85\xe70\xff\
4597\xcd<.\x9c\xbc\xa0yN\xc88\x89\x8f\xff\xf9q[\x9f\xdbh\xf9\xa3j\xc5\xc8\xef3\
4598\xd8\x1e\xb3\xa1\xeb;]\xaa\xdf\x96\x13\xcb\x88P\x91\xba\x82\xadz8\xd2w\x04=\
4599\xfbz\xd0\xb3o\xf3Y\xc1$\x9dD\x92N\x82\x13\xb8\xbaO\xb2\xdc\xe7\xa1\xfe!eY(\
4600\x16B2\x95\xac\xbb\xaf\x95\x1eU\x93\xfb)\x9f\x8f$\x9dD(\x1aj\xda9\xa8\x84\
4601\xd6\xa3jmu\rY.\xab<\xd8\xd1j\x9au\x9cV\xf7\xb9]\xe7\xa3\x16F\xa0=/\xb7\xe8l\
4602\x13\xbe}e%\xcf\xe5U\x8b\x8d\xba\x08tx\x86\xdf\xac#\xe4\xf8\x9c>5\xce#\x80\
4603\x08\x11|n\xf3ux\x96\x96nf\x99\xb4.\xbe\xfc.\xbd\xce.C\x04\nB\x01\x00\x94\
4604\xb9\x11x\x8e\x97\xde},\xb5\x04\xf2\xdc\x08\x10\x01\xe8Z\xd8U\x88\x10!\xf0\
4605\x02x\x86\x97\xe6O\xa2Y\xf0\x0c\x8f\x02_\x80\xe1\xe2\xbb\x17\xc5\xe2\x89\xb2\
4606\x00\xa0P\x90Tc\x80A\x17\xc3.A\x14E\x14\x84\x02\x984\x03:A#F\xc6@S4\xd2Ii\
4607\xa2\x14\x83\xc1\xd8\x81_\x8c\xbf\xa0z\xfd\xd9d\x96\xb2J\xa3\xc9\xa8\x8ba\
4608\xa7#J\x96@\x16Av#\x8b\x8d{\x1b\x88\x86\xa2\x8a\x08\xe2\xff\xb9\'\xcd\xaa\
4609\x96\xbd\x9fU|\x86\xd9bF\x87\xb9\x03F\x93\x11\xe6=f\x98\xcc&\x18MRL\xa9\x8bb\
4610\x87 \xca\x1f\x92\x00\x04^PM\x9eU,\x02\x19e\xe6\xd53\x17\x7f*\xe6y)\xb7\xec0\
4611wH\x82\xb0t\xa8\x84\xa1\xb3s\x90\x83\xc2<\x9f\x07\x9bf\xc1fX\xacE\xd7T\xee\
4612\x00\x90\xac\x01Pt\xaf!\xcf\xe7\xb1\x16]\xc3\xfe\xde\xfdX\x8b\xae\x01\x00\
4613\xf6\xf7\xeeok\xe7u\x9a\x8f<Y\x16K\xb3\xd8Hl\xa0\xc0\x174\xd7+\x9b\x94\xfb\
4614\x87\xcf\xbaD9\xb7\xd4\xd9\xb9(\xe9!#\xa5\x87\xa5\x02\x90-\x81\x8c\xe64\xfdG\
4615~tX\xac\xb4\x03\x9d\x9dO\xa9\x08\x80\nB\x90\xe9\xfb\xc1\xf7\xf49\xf6v\x11Z\
4616\x02\x90\xf9?\xec\x08#j\x16n\xfd\xae\x00\x00\x00\x00IEND\xaeB`\x82'
4617
4618def getSageStopUpBitmap():
4619    return BitmapFromImage(getSageStopUpImage())
4620
4621def getSageStopUpImage():
4622    stream = cStringIO.StringIO(getSageStopUpData())
4623    return ImageFromStream(stream)
4624
4625
4626
4627
4628
4629##########################################################
4630##########################################################
4631##########################################################
4632#
4633# start everything
4634#
4635##########################################################
4636
4637if __name__ == '__main__':
4638    # change to the folder where the script is running
4639    # so that the relative paths work out correctly
4640    os.chdir(sys.path[0]) 
4641    main()
Note: See TracBrowser for help on using the repository browser.