--- /dev/null
+#----------------------------------------------------------------------------
+# Name: projectmodel.py
+# Purpose: This file contains project model information
+#
+# Author: Morgan Hua
+#
+# Created: 4/18/06
+# CVS-ID: $Id$
+# Copyright: (c) 2006 ActiveGrid, Inc.
+# License: wxWindows License
+#----------------------------------------------------------------------------
+
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+LANGUAGE_PYTHON = "python"
+LANGUAGE_PHP = "php"
+LANGUAGE_DEFAULT = LANGUAGE_PYTHON
+LANGUAGE_LIST = [LANGUAGE_PHP, LANGUAGE_PYTHON]
# Author: Morgan Hua
#
# Created: 3/22/05
-# Copyright: (c) 2005 ActiveGrid, Inc.
+# Copyright: (c) 2005-2006 ActiveGrid, Inc.
# CVS-ID: $Id$
# License: wxWindows License
#----------------------------------------------------------------------------
("Python 2.4", "Python Software Foundation License", "http://www.python.org/2.4/license.html"),
("wxPython 2.6", "wxWidgets 2 - LGPL", "http://wxwidgets.org/newlicen.htm"),
("wxWidgets", "wxWindows Library License 3", "http://www.wxwidgets.org/manuals/2.6.1/wx_wxlicense.html"),
- ("pychecker", "MetaSlash - BSD", "http://pychecker.sourceforge.net/COPYRIGHT"),
+ ("pychecker", "MetaSlash - BSD", "http://pychecker.sourceforge.net/COPYRIGHT"),
("process.py", "See file", "http://starship.python.net/~tmick/"),
("pysvn", "Apache License, Version 2.0", "http://pysvn.tigris.org/"),
]
if not ACTIVEGRID_BASE_IDE: # add licenses for non-base IDE features such as database connections
licenseData += [
- ("pydb2", "LGPL", "http://sourceforge.net/projects/pydb2"),
+ ("pydb2", "LGPL", "http://sourceforge.net/projects/pydb2"),
("pysqlite", "Python License (CNRI)", "http://sourceforge.net/projects/pysqlite"),
- ("mysql-python", "GPL, Python License (CNRI), Zope Public License", "http://sourceforge.net/projects/mysql-python"),
- ("cx_Oracle", "Computronix", "http://www.computronix.com/download/License(cxOracle).txt"),
+ ("mysql-python", "GPL, Python License (CNRI), Zope Public License", "http://sourceforge.net/projects/mysql-python"),
+ ("cx_Oracle", "Computronix", "http://www.computronix.com/download/License(cxOracle).txt"),
("SQLite", "Public Domain", "http://www.sqlite.org/copyright.html"),
("PyGreSQL", "BSD", "http://www.pygresql.org"),
("pyXML", "CNRI Python License", "http://sourceforge.net/softwaremap/trove_list.php?form_cat=194"),
("Zolera Soap Infrastructure", "Zope Public License 2.0", "http://www.zope.org/Resources/License/"),
+ ("python-ldap", "Python Software Foundation License", "http://python-ldap.sourceforge.net"),
("Sarissa", "LGPL", "http://sourceforge.net/projects/sarissa/"),
("Dynarch DHTML Calendar", "LGPL", "http://www.dynarch.com/projects/calendar/"),
+ ("python-dateutil", "Python Software Foundation License", "http://labix.org/python-dateutil"),
]
if wx.Platform == '__WXMSW__': # add Windows only licenses
else:
splash_bmp = getIDESplashBitmap()
- # find version number from
+ # find version number from
versionFilepath = os.path.join(sysutilslib.mainModuleDir, "version.txt")
if os.path.exists(versionFilepath):
versionfile = open(versionFilepath, 'r')
image = wx.StaticBitmap(aboutPage, -1, splash_bmp, (0,0), (splash_bmp.GetWidth(), splash_bmp.GetHeight()))
sizer.Add(image, 0, wx.ALIGN_CENTER|wx.ALL, 0)
- sizer.Add(wx.StaticText(aboutPage, -1, wx.GetApp().GetAppName() + _("\n%s\n\nCopyright (c) 2003-2005 ActiveGrid Incorporated and Contributors. All rights reserved.") % version), 0, wx.ALIGN_LEFT|wx.ALL, 10)
+ sizer.Add(wx.StaticText(aboutPage, -1, wx.GetApp().GetAppName() + _("\n%s\n\nCopyright (c) 2003-2006 ActiveGrid Incorporated and Contributors. All rights reserved.") % version), 0, wx.ALIGN_LEFT|wx.ALL, 10)
sizer.Add(wx.StaticText(aboutPage, -1, _("http://www.activegrid.com")), 0, wx.ALIGN_LEFT|wx.LEFT|wx.BOTTOM, 10)
aboutPage.SetSizer(sizer)
nb.AddPage(aboutPage, _("Copyright"))
licensePage = wx.Panel(nb, -1)
grid = wx.grid.Grid(licensePage, -1)
grid.CreateGrid(len(licenseData), 2)
-
+
dc = wx.ClientDC(grid)
dc.SetFont(grid.GetLabelFont())
grid.SetColLabelValue(0, _("License"))
grid.SetColLabelValue(1, _("URL"))
- w, maxHeight = dc.GetTextExtent(_("License"))
- w, h = dc.GetTextExtent(_("URL"))
- if h > maxHeight:
- maxHeight = h
+ w, h1 = dc.GetTextExtent(_("License"))
+ w, h2 = dc.GetTextExtent(_("URL"))
+ maxHeight = max(h1, h2)
grid.SetColLabelSize(maxHeight + 6) # add a 6 pixel margin
maxW = 0
grid.SetCellValue(row, 0, license)
if url:
grid.SetCellValue(row, 1, url)
-
+
grid.EnableEditing(False)
grid.EnableDragGridSize(False)
grid.EnableDragColSize(False)
creditsPage = wx.Panel(nb, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
- sizer.Add(wx.StaticText(creditsPage, -1, _("ActiveGrid Development Team:\n\nLarry Abrahams\nLawrence Bruhmuller\nEric Chu\nBeth Fryer\nMatt Fryer\nJoel Hare\nMorgan Hua\nMatt McNulty\nPratik Mehta\nAlan Mullendore\nJeff Norton\nSimon Toens\nKevin Wang\nPeter Yared")), 0, wx.ALIGN_LEFT|wx.ALL, 10)
+ sizer.Add(wx.StaticText(creditsPage, -1, _("ActiveGrid Development Team:\n\nLarry Abrahams\nLawrence Bruhmuller\nEric Chu\nBeth Fryer\nMatt Fryer\nFrankie Fu\nJoel Hare\nMorgan Hua\nMatt McNulty\nPratik Mehta\nAlan Mullendore\nJeff Norton\nKevin Ollivier\nMatt Small\nSimon Toens\nKevin Wang\nPeter Yared\nJeremy Yun")), 0, wx.ALIGN_LEFT|wx.ALL, 10)
creditsPage.SetSizer(sizer)
nb.AddPage(creditsPage, _("Credits"))
-
+
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(nb, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
btn = wx.Button(self, wx.ID_OK)
self.Layout()
self.Fit()
grid.ForceRefresh() # wxBug: Get rid of unnecessary scrollbars
-
+
PARKING_HORIZONTAL = 2
PARKING_OFFSET = 30 # space between shapes
+FORCE_REDRAW_METHOD = "ForceRedraw"
def GetRawModel(model):
if hasattr(model, "GetRawModel"):
self._propShape = None
self._maxWidth = 2000
self._maxHeight = 16000
+ self._valetParking = False
def OnDraw(self, dc):
self.SetPropertyModel(None)
+ def SetLastRightClick(self, x, y):
+ self._lastRightClick = (x,y)
+
+
+ def GetLastRightClick(self):
+ if hasattr(self, "_lastRightClick"):
+ return self._lastRightClick
+ return (-1,-1)
+
+
def OnKeyPressed(self, event):
key = event.KeyCode()
if key == wx.WXK_DELETE:
dc = wx.ClientDC(self._canvas)
self._canvas.PrepareDC(dc)
x, y = event.GetLogicalPosition(dc) # this takes into account scrollbar offset
+ self.SetLastRightClick(x, y)
shape = self._canvas.FindShape(x, y)[0]
model = None
pass
else:
# click on empty part of canvas, deselect everything
+ forceRedrawShapes = []
needRefresh = False
for shape in self._diagram.GetShapeList():
if hasattr(shape, "GetModel"):
if shape.Selected():
needRefresh = True
shape.Select(False, dc)
+ if hasattr(shape, FORCE_REDRAW_METHOD):
+ forceRedrawShapes.append(shape)
if needRefresh:
self._canvas.Redraw(dc)
if len(self.GetSelection()) == 0:
self.SetPropertyShape(None)
-
+ for shape in forceRedrawShapes:
+ shape.ForceRedraw()
def OnLeftDoubleClick(self, event):
propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
dc.EndDrawing()
+ def SetValetParking(self, enable=True):
+ """ If valet parking is enabled, remember last parking spot and try for a spot near it """
+ self._valetParking = enable
+ if enable:
+ self._valetPosition = None
+
+
def FindParkingSpot(self, width, height, parking=PARKING_HORIZONTAL, x=PARKING_OFFSET, y=PARKING_OFFSET):
- """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """
+ """
+ Given a width and height, find a upper left corner where shape can be parked without overlapping other shape
+ """
+ if self._valetParking and self._valetPosition:
+ x, y = self._valetPosition
+
max = 700 # max distance to the right where we'll place tables
noParkingSpot = True
else:
noParkingSpot = False
+ if self._valetParking:
+ self._valetPosition = (x, y)
+
return x, y
self._diagram.RemoveShape(line)
line.Delete()
- shape.RemoveFromCanvas(self._canvas)
+ if self._canvas:
+ shape.RemoveFromCanvas(self._canvas)
self._diagram.RemoveShape(shape)
shape.Delete()
self._propShape.SetTextColour("WHITE", 0)
self._propShape.Draw(dc)
+ if hasattr(self._propShape, FORCE_REDRAW_METHOD):
+ self._propShape.ForceRedraw()
+
dc.EndDrawing()
import re
import string
import sys
-import DebuggerService
import MarkerService
from UICommon import CaseInsensitiveCompare
_ = wx.GetTranslation
return False
id = event.GetId()
if id == EXPAND_TEXT_ID:
- event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine()))
+ if self.GetCtrl().GetViewFolding():
+ event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine()))
+ else:
+ event.Enable(False)
return True
elif id == COLLAPSE_TEXT_ID:
- event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine()))
+ if self.GetCtrl().GetViewFolding():
+ event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine()))
+ else:
+ event.Enable(False)
return True
elif (id == EXPAND_TOP_ID
or id == COLLAPSE_TOP_ID
or id == EXPAND_ALL_ID
- or id == COLLAPSE_ALL_ID
- or id == AUTO_COMPLETE_ID
+ or id == COLLAPSE_ALL_ID):
+ if self.GetCtrl().GetViewFolding():
+ event.Enable(self.GetCtrl().GetTextLength() > 0)
+ else:
+ event.Enable(False)
+ return True
+ elif (id == AUTO_COMPLETE_ID
or id == CLEAN_WHITESPACE
or id == INDENT_LINES_ID
or id == DEDENT_LINES_ID
elif id == CHECK_CODE_ID:
event.Enable(False)
return True
- elif (id == SET_INDENT_WIDTH_ID
- or id == FOLDING_ID):
+ elif id == SET_INDENT_WIDTH_ID:
event.Enable(True)
return True
+ elif id == FOLDING_ID:
+ event.Enable(self.GetCtrl().GetViewFolding())
+ return True
elif id == USE_TABS_ID:
event.Enable(True)
event.Check(self.GetCtrl().GetUseTabs())
filename = document.GetFilename()
if filename:
rootItem = treeCtrl.AddRoot(os.path.basename(filename))
- treeCtrl.SetDoSelectCallback(rootItem, self, None)
+ treeCtrl.SetDoSelectCallback(rootItem, self, (0,0))
else:
return True
if classLine:
indent = classLine.start(0)
itemStr = classLine.string[classLine.start(0):classLine.end(0)-1] # don't take the closing ':'
+ itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
else:
defLine = defPat.search(line)
if defLine:
indent = defLine.start(0)
itemStr = defLine.string[defLine.start(0):defLine.end(0)]
+ itemStr = itemStr.replace("\n", "").replace("\r", "").replace(",\\", ",").replace(" ", "") # remove line continuations and spaces from outline view
if indent == 0:
parentItem = rootItem
def OnUpdate(self, sender = None, hint = None):
+ if wx.lib.docview.View.OnUpdate(self, sender, hint):
+ return
+
if hint == "ViewStuff":
self.GetCtrl().SetViewDefaults()
elif hint == "Font":
self.GetCtrl().SetFont(font)
self.GetCtrl().SetFontColor(color)
else:
+ import DebuggerService
dbg_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
if dbg_service:
dbg_service.SetCurrentBreakpointMarkers(self)
BREAKPOINT_MARKER_MASK = 0x2
- def __init__(self, parent, id=-1, style = wx.NO_FULL_REPAINT_ON_RESIZE):
+ def __init__(self, parent, id=-1, style = wx.NO_FULL_REPAINT_ON_RESIZE, clearTab=True):
STCTextEditor.TextCtrl.__init__(self, parent, id, style)
self.UsePopUp(False)
self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL)
self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS)
self.SetMarginSensitive(2, True)
- self.SetMarginWidth(2, 12)
self.SetMarginSensitive(1, False)
self.SetMarginMask(1, 0x4)
# Define the breakpoint marker
self.MarkerDefine(CodeCtrl.BREAKPOINT_MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, (255,0,0))
- if _WINDOWS: # should test to see if menu item exists, if it does, add this workaround
+ if _WINDOWS and clearTab: # should test to see if menu item exists, if it does, add this workaround
self.CmdKeyClear(wx.stc.STC_KEY_TAB, 0) # menu item "Indent Lines" from CodeService.InstallControls() generates another INDENT_LINES_ID event, so we'll explicitly disable the tab processing in the editor
wx.stc.EVT_STC_MARGINCLICK(self, self.GetId(), self.OnMarginClick)
item = wx.MenuItem(menu, TOGGLEBREAKPOINT_ID, _("Toggle Breakpoint"))
menu.AppendItem(item)
self.Bind(wx.EVT_MENU, self.OnPopToggleMarker, id=TOGGLEMARKER_ID)
- item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Marker"))
+ item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Bookmark"))
menu.AppendItem(item)
menu.AppendSeparator()
def OnPopToggleBP(self, event):
""" Toggle break point on right click line, not current line """
+ import DebuggerService
wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine)
elif evt.GetMargin() == 0:
#This is used to toggle breakpoints via the debugger service.
+ import DebuggerService
db_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
if db_service:
db_service.OnToggleBreakpoint(evt, line=self.LineFromPosition(evt.GetPosition()))
#----------------------------------------------------------------------------
# Name: DebuggerService.py
-# Purpose: Debugger Service for Python.
+# Purpose: Debugger Service for Python and PHP
#
# Author: Matt Fryer
#
import STCTextEditor
import CodeEditor
import PythonEditor
+import PHPEditor
+import PHPDebugger
+import activegrid.model.projectmodel as projectmodel
from IDE import ACTIVEGRID_BASE_IDE
if not ACTIVEGRID_BASE_IDE:
import ProcessModelEditor
import traceback
import StringIO
import UICommon
+import activegrid.util.sysutils as sysutilslib
+import subprocess
+import shutil
+
if wx.Platform == '__WXMSW__':
try:
import win32api
_VERBOSE = False
_WATCHES_ON = False
+import wx.lib.newevent
+(UpdateTextEvent, EVT_UPDATE_STDTEXT) = wx.lib.newevent.NewEvent()
+(UpdateErrorEvent, EVT_UPDATE_ERRTEXT) = wx.lib.newevent.NewEvent()
+(DebugInternalWebServer, EVT_DEBUG_INTERNAL) = wx.lib.newevent.NewEvent()
+
# Class to read from stdout or stderr and write the result to a text control.
# Args: file=file-like object
# callback_function= function that takes a single argument, the line of text
self._accumulate = accumulate
self._callbackOnExit = callbackOnExit
self.setDaemon(True)
-
+
def __del__(self):
- # See comment on DebugCommandUI.StopExecution
+ # See comment on PythonDebuggerUI.StopExecution
self._keepGoing = False
-
+
def run(self):
file = self._file
start = time.time()
# Should use a buffer? StringIO?
output += text
# Seems as though the read blocks if we got an error, so, to be
- # sure that at least some of the exception gets printed, always
+ # sure that at least some of the exception gets printed, always
# send the first hundred lines back as they come in.
- if self._lineCount < 100 and self._keepGoing:
+ if self._lineCount < 100 and self._keepGoing:
self._callback_function(output)
self._lineCount += 1
- output = ""
+ output = ""
elif time.time() - start > 0.25 and self._keepGoing:
try:
- self._callback_function(output)
+ self._callback_function(output)
except wx._core.PyDeadObjectError:
# GUI was killed while we were blocked.
self._keepGoing = False
start = time.time()
- output = ""
+ output = ""
#except TypeError:
# pass
except:
except wx._core.PyDeadObjectError:
pass
if _VERBOSE: print "Exiting OutputReaderThread"
-
+
def AskToStop(self):
self._keepGoing = False
-
-import wx.lib.newevent
-(UpdateTextEvent, EVT_UPDATE_STDTEXT) = wx.lib.newevent.NewEvent()
-(UpdateErrorEvent, EVT_UPDATE_ERRTEXT) = wx.lib.newevent.NewEvent()
-
+
+
class Executor:
-
+ PHP_CGI_BIN_PATH_WIN = "../../3rdparty/php"
+ PHP_CGI_BIN_PATH_UNIX = "../../../3rdparty/php/bin"
+ PHP_CGI_BIN_PATH_OSX = "../3rdparty/php/bin"
+ PHP_CGI_EXEC_WIN = "php-cgi.exe"
+ PHP_CGI_EXEC_UNIX = "php"
+ PHP_CGI_EXEC_OSX = "php-cgi"
+
def GetPythonExecutablePath():
path = UICommon.GetPythonExecPath()
if path:
return path
- wx.MessageBox(_("To proceed I need to know the location of the python.exe you would like to use.\nTo set this, go to Tools-->Options and use the 'Python' tab to enter a value.\n"), _("Python Executable Location Unknown"))
+ wx.MessageBox(_("To proceed we need to know the location of the python.exe you would like to use.\nTo set this, go to Tools-->Options and use the 'Python' tab to enter a value.\n"), _("Python Executable Location Unknown"))
return None
- GetPythonExecutablePath = staticmethod(GetPythonExecutablePath)
-
+ GetPythonExecutablePath = staticmethod(GetPythonExecutablePath)
+
+ def GetPHPExecutablePath():
+ if sysutilslib.isWindows():
+ phpCgiBinPath = Executor.PHP_CGI_BIN_PATH_WIN
+ phpCgiExec = Executor.PHP_CGI_EXEC_WIN
+ elif sys.platform == "darwin":
+ phpCgiBinPath = Executor.PHP_CGI_BIN_PATH_OSX
+ phpCgiExec = Executor.PHP_CGI_EXEC_OSX
+ else:
+ phpCgiBinPath = Executor.PHP_CGI_BIN_PATH_UNIX
+ phpCgiExec = Executor.PHP_CGI_EXEC_UNIX
+
+ if sysutilslib.isRelease():
+ phpCgiExecFullPath = os.path.normpath(os.path.join(sysutilslib.mainModuleDir, phpCgiBinPath, phpCgiExec))
+ else:
+ phpCgiExecFullPath = phpCgiExec
+
+ if _VERBOSE:
+ print "php cgi executable full path is: %s" % phpCgiExecFullPath
+
+ return phpCgiExecFullPath
+ GetPHPExecutablePath = staticmethod(GetPHPExecutablePath)
+
def __init__(self, fileName, wxComponent, arg1=None, arg2=None, arg3=None, arg4=None, arg5=None, arg6=None, arg7=None, arg8=None, arg9=None, callbackOnExit=None):
self._fileName = fileName
self._stdOutCallback = self.OutCall
self._stdErrCallback = self.ErrCall
self._callbackOnExit = callbackOnExit
self._wxComponent = wxComponent
- path = Executor.GetPythonExecutablePath()
- self._cmd = '"' + path + '" -u \"' + fileName + '\"'
+ if fileName.endswith('.py') or fileName.endswith('.pyc'):
+ self._path = Executor.GetPythonExecutablePath()
+ self._cmd = '"' + self._path + '" -u \"' + fileName + '\"'
+ else:
+ self._path = Executor.GetPHPExecutablePath()
+ self._cmd = '"' + self._path + '"'
#Better way to do this? Quotes needed for windows file paths.
def spaceAndQuote(text):
if text.startswith("\"") and text.endswith("\""):
self._cmd += spaceAndQuote(arg8)
if(arg9 != None):
self._cmd += spaceAndQuote(arg9)
-
+
self._stdOutReader = None
self._stdErrReader = None
self._process = None
-
+
def OutCall(self, text):
evt = UpdateTextEvent(value = text)
wx.PostEvent(self._wxComponent, evt)
-
+
def ErrCall(self, text):
evt = UpdateErrorEvent(value = text)
wx.PostEvent(self._wxComponent, evt)
-
+
def Execute(self, arguments, startIn=None, environment=None):
if not startIn:
startIn = str(os.getcwd())
startIn = os.path.abspath(startIn)
- command = self._cmd + ' ' + arguments
+
+ if arguments and arguments != " ":
+ command = self._cmd + ' ' + arguments
+ else:
+ command = self._cmd
+
+ if _VERBOSE: print "start debugger executable: " + command + "\n"
self._process = process.ProcessOpen(command, mode='b', cwd=startIn, env=environment)
# Kick off threads to read stdout and stderr and write them
- # to our text control.
+ # to our text control.
self._stdOutReader = OutputReaderThread(self._process.stdout, self._stdOutCallback, callbackOnExit=self._callbackOnExit)
self._stdOutReader.start()
self._stdErrReader = OutputReaderThread(self._process.stderr, self._stdErrCallback, accumulate=False)
self._stdErrReader.start()
-
+
def DoStopExecution(self):
- # See comment on DebugCommandUI.StopExecution
+ # See comment on PythonDebuggerUI.StopExecution
if(self._process != None):
self._stdOutReader.AskToStop()
self._stdErrReader.AskToStop()
except:
pass
self._process = None
-
+
+ def GetExecPath(self):
+ return self._path
+
class RunCommandUI(wx.Panel):
runners = []
-
+
def ShutdownAllRunners():
- # See comment on DebugCommandUI.StopExecution
+ # See comment on PythonDebuggerUI.StopExecution
for runner in RunCommandUI.runners:
try:
runner.StopExecution(None)
except wx._core.PyDeadObjectError:
pass
- RunCommandUI.runners = []
+ RunCommandUI.runners = []
ShutdownAllRunners = staticmethod(ShutdownAllRunners)
-
+
def __init__(self, parent, id, fileName):
wx.Panel.__init__(self, parent, id)
self._noteBook = parent
-
+
threading._VERBOSE = _VERBOSE
-
+
self.KILL_PROCESS_ID = wx.NewId()
self.CLOSE_TAB_ID = wx.NewId()
-
-
+
+
# GUI Initialization follows
sizer = wx.BoxSizer(wx.HORIZONTAL)
self._tb = tb = wx.ToolBar(self, -1, wx.DefaultPosition, (30,1000), wx.TB_VERTICAL| wx.TB_FLAT, "Runner" )
close_bmp = getCloseBitmap()
tb.AddSimpleTool( self.CLOSE_TAB_ID, close_bmp, _('Close Window'))
wx.EVT_TOOL(self, self.CLOSE_TAB_ID, self.OnToolClicked)
-
+
stop_bmp = getStopBitmap()
tb.AddSimpleTool(self.KILL_PROCESS_ID, stop_bmp, _("Stop the Run."))
wx.EVT_TOOL(self, self.KILL_PROCESS_ID, self.OnToolClicked)
-
+
tb.Realize()
self._textCtrl = STCTextEditor.TextCtrl(self, wx.NewId()) #id)
sizer.Add(self._textCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
self._textCtrl.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font))
self._textCtrl.SetFontColor(wx.BLACK)
self._textCtrl.StyleClearAll()
-
- #Disabling for now...may interfere with file open. wx.stc.EVT_STC_DOUBLECLICK(self._textCtrl, self._textCtrl.GetId(), self.OnDoubleClick)
+
+ wx.stc.EVT_STC_DOUBLECLICK(self._textCtrl, self._textCtrl.GetId(), self.OnDoubleClick)
self.SetSizer(sizer)
sizer.Fit(self)
-
+
self._stopped = False
# Executor initialization
self._executor = Executor(fileName, self, callbackOnExit=self.ExecutorFinished)
self.Bind(EVT_UPDATE_STDTEXT, self.AppendText)
self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText)
-
+
RunCommandUI.runners.append(self)
-
+
def __del__(self):
- # See comment on DebugCommandUI.StopExecution
+ # See comment on PythonDebuggerUI.StopExecution
self._executor.DoStopExecution()
-
- def Execute(self, initialArgs, startIn, environment):
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
self._executor.Execute(initialArgs, startIn, environment)
-
+
def ExecutorFinished(self):
self._tb.EnableTool(self.KILL_PROCESS_ID, False)
nb = self.GetParent()
newText = text.replace("Running", "Finished")
nb.SetPageText(i, newText)
break
-
+
def StopExecution(self):
if not self._stopped:
self._stopped = True
self.Unbind(EVT_UPDATE_STDTEXT)
self.Unbind(EVT_UPDATE_ERRTEXT)
self._executor.DoStopExecution()
-
+
def AppendText(self, event):
self._textCtrl.SetReadOnly(False)
self._textCtrl.AddText(event.value)
self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
self._textCtrl.SetReadOnly(True)
-
+
def AppendErrorText(self, event):
self._textCtrl.SetReadOnly(False)
- self._textCtrl.SetFontColor(wx.RED)
+ self._textCtrl.SetFontColor(wx.RED)
self._textCtrl.StyleClearAll()
self._textCtrl.AddText(event.value)
self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
- self._textCtrl.SetFontColor(wx.BLACK)
+ self._textCtrl.SetFontColor(wx.BLACK)
self._textCtrl.StyleClearAll()
self._textCtrl.SetReadOnly(True)
index = self._noteBook.GetSelection()
self._noteBook.GetPage(index).Show(False)
self._noteBook.RemovePage(index)
-
+
#------------------------------------------------------------------------------
# Event handling
#-----------------------------------------------------------------------------
-
+
def OnToolClicked(self, event):
id = event.GetId()
-
+
if id == self.KILL_PROCESS_ID:
self.StopExecution()
-
+
elif id == self.CLOSE_TAB_ID:
self.StopAndRemoveUI(event)
-
+
def OnDoubleClick(self, event):
# Looking for a stack trace line.
lineText, pos = self._textCtrl.GetCurLine()
fileBegin = lineText.find("File \"")
- fileEnd = lineText.find("\", line ")
- lineEnd = lineText.find(", in ")
+ fileEnd = lineText.find("\", line ")
+ lineEnd = lineText.find(", in ")
if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1:
# Check the line before the one that was clicked on
lineNumber = self._textCtrl.GetCurrentLine()
return
lineText = self._textCtrl.GetLine(lineNumber - 1)
fileBegin = lineText.find("File \"")
- fileEnd = lineText.find("\", line ")
- lineEnd = lineText.find(", in ")
+ fileEnd = lineText.find("\", line ")
+ lineEnd = lineText.find(", in ")
if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1:
return
-
+
filename = lineText[fileBegin + 6:fileEnd]
lineNum = int(lineText[fileEnd + 8:lineEnd])
foundView.Activate()
foundView.GotoLine(lineNum)
startPos = foundView.PositionFromLine(lineNum)
-
- # FACTOR THIS INTO DocManager
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if(isinstance(openDoc, CodeEditor.CodeDocument)):
- openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers()
-
- foundView.GetCtrl().MarkerAdd(lineNum -1, CodeEditor.CodeCtrl.CURRENT_LINE_MARKER_NUM)
+ lineText = foundView.GetCtrl().GetLine(lineNum - 1)
+ foundView.SetSelection(startPos, startPos + len(lineText.rstrip("\n")))
+ import OutlineService
+ wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos)
DEFAULT_PORT = 32032
-DEFAULT_HOST = 'localhost'
-PORT_COUNT = 21
+DEFAULT_HOST = 'localhost'
+PORT_COUNT = 21
-class DebugCommandUI(wx.Panel):
- debuggerPortList = None
+class BaseDebuggerUI(wx.Panel):
debuggers = []
-
+
def NotifyDebuggersOfBreakpointChange():
- for debugger in DebugCommandUI.debuggers:
+ for debugger in BaseDebuggerUI.debuggers:
debugger.BreakPointChange()
-
+
NotifyDebuggersOfBreakpointChange = staticmethod(NotifyDebuggersOfBreakpointChange)
-
+
def DebuggerRunning():
- for debugger in DebugCommandUI.debuggers:
+ for debugger in BaseDebuggerUI.debuggers:
if debugger._executor:
return True
return False
DebuggerRunning = staticmethod(DebuggerRunning)
-
+
+ def DebuggerInWait():
+ for debugger in BaseDebuggerUI.debuggers:
+ if debugger._executor:
+ if debugger._callback._waiting:
+ return True
+ return False
+ DebuggerInWait = staticmethod(DebuggerInWait)
+
+ def DebuggerPastAutoContinue():
+ for debugger in BaseDebuggerUI.debuggers:
+ if debugger._executor:
+ if debugger._callback._waiting and not debugger._callback._autoContinue:
+ return True
+ return False
+ DebuggerPastAutoContinue = staticmethod(DebuggerPastAutoContinue)
+
def ShutdownAllDebuggers():
- # See comment on DebugCommandUI.StopExecution
- for debugger in DebugCommandUI.debuggers:
+ for debugger in BaseDebuggerUI.debuggers:
try:
debugger.StopExecution(None)
except wx._core.PyDeadObjectError:
pass
- DebugCommandUI.debuggers = []
+ BaseDebuggerUI.debuggers = []
ShutdownAllDebuggers = staticmethod(ShutdownAllDebuggers)
-
- def GetAvailablePort():
- for index in range( 0, len(DebugCommandUI.debuggerPortList)):
- port = DebugCommandUI.debuggerPortList[index]
- if DebugCommandUI.PortAvailable(port):
- DebugCommandUI.debuggerPortList.pop(index)
- return port
- wx.MessageBox(_("Out of ports for debugging! Please restart the application builder.\nIf that does not work, check for and remove running instances of python."), _("Out of Ports"))
- assert False, "Out of ports for debugger."
-
- GetAvailablePort = staticmethod(GetAvailablePort)
-
- def ReturnPortToPool(port):
- config = wx.ConfigBase_Get()
- startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
- val = int(startingPort) + int(PORT_COUNT)
- if int(port) >= startingPort and (int(port) <= val):
- DebugCommandUI.debuggerPortList.append(int(port))
-
- ReturnPortToPool = staticmethod(ReturnPortToPool)
-
- def PortAvailable(port):
- config = wx.ConfigBase_Get()
- hostname = config.Read("DebuggerHostName", DEFAULT_HOST)
- try:
- server = AGXMLRPCServer((hostname, port))
- server.server_close()
- if _VERBOSE: print "Port ", str(port), " available."
- return True
- except:
- tp,val,tb = sys.exc_info()
- if _VERBOSE: traceback.print_exception(tp, val, tb)
- if _VERBOSE: print "Port ", str(port), " unavailable."
- return False
-
- PortAvailable = staticmethod(PortAvailable)
-
- def NewPortRange():
- config = wx.ConfigBase_Get()
- startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
- DebugCommandUI.debuggerPortList = range(startingPort, startingPort + PORT_COUNT)
- NewPortRange = staticmethod(NewPortRange)
-
- def __init__(self, parent, id, command, service):
- # Check for ports before creating the panel.
- if not DebugCommandUI.debuggerPortList:
- DebugCommandUI.NewPortRange()
- self._debuggerPort = str(DebugCommandUI.GetAvailablePort())
- self._guiPort = str(DebugCommandUI.GetAvailablePort())
- self._debuggerBreakPort = str(DebugCommandUI.GetAvailablePort())
+ def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
-
self._parentNoteBook = parent
- self._command = command
- self._service = service
+
+ self._service = None
+ self._executor = None
+ self._callback = None
+ self._stopped = False
+
+ BaseDebuggerUI.debuggers.append(self)
+ self._stopped = True
+ self.Bind(EVT_UPDATE_STDTEXT, self.AppendText)
+ self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText)
self._executor = None
+
self.STEP_ID = wx.NewId()
self.CONTINUE_ID = wx.NewId()
self.STEP_OUT_ID = wx.NewId()
self._tb = tb = wx.ToolBar(self, -1, wx.DefaultPosition, (1000,30), wx.TB_HORIZONTAL| wx.NO_BORDER| wx.TB_FLAT| wx.TB_TEXT, "Debugger" )
sizer.Add(tb, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1)
tb.SetToolBitmapSize((16,16))
-
+
close_bmp = getCloseBitmap()
tb.AddSimpleTool( self.CLOSE_WINDOW_ID, close_bmp, _('Close Window'))
wx.EVT_TOOL(self, self.CLOSE_WINDOW_ID, self.StopAndRemoveUI)
-
+
stop_bmp = getStopBitmap()
tb.AddSimpleTool( self.KILL_PROCESS_ID, stop_bmp, _("Stop Debugging"))
wx.EVT_TOOL(self, self.KILL_PROCESS_ID, self.StopExecution)
-
+
tb.AddSeparator()
-
+
break_bmp = getBreakBitmap()
tb.AddSimpleTool( self.BREAK_INTO_DEBUGGER_ID, break_bmp, _("Break into Debugger"))
wx.EVT_TOOL(self, self.BREAK_INTO_DEBUGGER_ID, self.BreakExecution)
-
+
tb.AddSeparator()
-
+
continue_bmp = getContinueBitmap()
tb.AddSimpleTool( self.CONTINUE_ID, continue_bmp, _("Continue Execution"))
wx.EVT_TOOL(self, self.CONTINUE_ID, self.OnContinue)
-
+ self.Bind(EVT_DEBUG_INTERNAL, self.OnContinue)
+
tb.AddSeparator()
-
next_bmp = getNextBitmap()
tb.AddSimpleTool( self.NEXT_ID, next_bmp, _("Step to next line"))
wx.EVT_TOOL(self, self.NEXT_ID, self.OnNext)
-
+
step_bmp = getStepInBitmap()
tb.AddSimpleTool( self.STEP_ID, step_bmp, _("Step in"))
wx.EVT_TOOL(self, self.STEP_ID, self.OnSingleStep)
-
+
stepOut_bmp = getStepReturnBitmap()
tb.AddSimpleTool(self.STEP_OUT_ID, stepOut_bmp, _("Stop at function return"))
wx.EVT_TOOL(self, self.STEP_OUT_ID, self.OnStepOut)
tb.AddSimpleTool(self.ADD_WATCH_ID, watch_bmp, _("Add a watch"))
wx.EVT_TOOL(self, self.ADD_WATCH_ID, self.OnAddWatch)
tb.AddSeparator()
-
+
clear_bmp = getClearOutputBitmap()
tb.AddSimpleTool(self.CLEAR_ID, clear_bmp, _("Clear output pane"))
wx.EVT_TOOL(self, self.CLEAR_ID, self.OnClearOutput)
-
+
+ self._toolEnabled = True
self.framesTab = None
self.DisableWhileDebuggerRunning()
self.framesTab = self.MakeFramesUI(self, wx.NewId(), None)
self._statusBar = wx.StatusBar( self, -1)
self._statusBar.SetFieldsCount(1)
sizer.Add(self._statusBar, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1)
-
self.SetStatusText("Starting debug...")
-
self.SetSizer(sizer)
tb.Realize()
sizer.Fit(self)
- config = wx.ConfigBase_Get()
- self._debuggerHost = self._guiHost = config.Read("DebuggerHostName", DEFAULT_HOST)
- url = 'http://' + self._debuggerHost + ':' + self._debuggerPort + '/'
- self._breakURL = 'http://' + self._debuggerHost + ':' + self._debuggerBreakPort + '/'
- self._callback = DebuggerCallback(self._guiHost, self._guiPort, url, self._breakURL, self)
- if DebuggerHarness.__file__.find('library.zip') > 0:
- try:
- fname = DebuggerHarness.__file__
- parts = fname.split('library.zip')
- path = os.path.join(parts[0],'activegrid', 'tool', 'DebuggerHarness.py')
- except:
- tp, val, tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
-
- else:
- print "Starting debugger on these ports: %s, %s, %s" % (str(self._debuggerPort) , str(self._guiPort) , str(self._debuggerBreakPort))
- path = DebuggerService.ExpandPath(DebuggerHarness.__file__)
- self._executor = Executor(path, self, self._debuggerHost, \
- self._debuggerPort, self._debuggerBreakPort, self._guiHost, self._guiPort, self._command, callbackOnExit=self.ExecutorFinished)
-
- self.Bind(EVT_UPDATE_STDTEXT, self.AppendText)
- self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText)
- DebugCommandUI.debuggers.append(self)
- self._stopped = False
-
+
def OnSingleStep(self, event):
self._callback.SingleStep()
def OnContinue(self, event):
self._callback.Continue()
-
+
def OnStepOut(self, event):
self._callback.Return()
-
+
def OnNext(self, event):
self._callback.Next()
def BreakPointChange(self):
if not self._stopped:
- self._callback.pushBreakpoints()
+ self._callback.PushBreakpoints()
self.framesTab.PopulateBPList()
-
+
def __del__(self):
- # See comment on DebugCommandUI.StopExecution
- self.StopExecution(None)
-
+ # See comment on PythonDebuggerUI.StopExecution
+ self.StopExecution(None)
+
def DisableWhileDebuggerRunning(self):
- self._tb.EnableTool(self.STEP_ID, False)
- self._tb.EnableTool(self.CONTINUE_ID, False)
- self._tb.EnableTool(self.STEP_OUT_ID, False)
- self._tb.EnableTool(self.NEXT_ID, False)
- self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, True)
- if _WATCHES_ON:
- self._tb.EnableTool(self.ADD_WATCH_ID, False)
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if(isinstance(openDoc, CodeEditor.CodeDocument)):
- openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers()
- if self.framesTab:
- self.framesTab.ClearWhileRunning()
-
+ if self._toolEnabled:
+ self._tb.EnableTool(self.STEP_ID, False)
+ self._tb.EnableTool(self.CONTINUE_ID, False)
+ self._tb.EnableTool(self.STEP_OUT_ID, False)
+ self._tb.EnableTool(self.NEXT_ID, False)
+ self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, True)
+
+ if _WATCHES_ON:
+ self._tb.EnableTool(self.ADD_WATCH_ID, False)
+
+ self.DeleteCurrentLineMarkers()
+
+ if self.framesTab:
+ self.framesTab.ClearWhileRunning()
+
+ self._toolEnabled = False
+
def EnableWhileDebuggerStopped(self):
self._tb.EnableTool(self.STEP_ID, True)
self._tb.EnableTool(self.CONTINUE_ID, True)
self._tb.EnableTool(self.STEP_OUT_ID, True)
self._tb.EnableTool(self.NEXT_ID, True)
+ self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False)
+ self._tb.EnableTool(self.KILL_PROCESS_ID, True)
+
if _WATCHES_ON:
self._tb.EnableTool(self.ADD_WATCH_ID, True)
- self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False)
-
- def ExecutorFinished(self):
+
+ self._toolEnabled = True
+
+ def DisableAfterStop(self):
+ if self._toolEnabled:
+ self.DisableWhileDebuggerRunning()
+ self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False)
+ self._tb.EnableTool(self.KILL_PROCESS_ID, False)
+
+ def ExecutorFinished(self):
if _VERBOSE: print "In ExectorFinished"
- try:
+ try:
self.DisableAfterStop()
except wx._core.PyDeadObjectError:
pass
except:
if _VERBOSE: print "In ExectorFinished, got exception"
- def DisableAfterStop(self):
- self.DisableWhileDebuggerRunning()
- self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False)
- self._tb.EnableTool(self.KILL_PROCESS_ID, False)
+ def SetStatusText(self, text):
+ self._statusBar.SetStatusText(text,0)
+
+ def BreakExecution(self, event):
+ self._callback.BreakExecution()
+
+ def StopExecution(self, event):
+ self._callback.ShutdownServer()
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
+ assert False, "Execute not overridden"
def SynchCurrentLine(self, filename, lineNum, noArrow=False):
- # FACTOR THIS INTO DocManager
self.DeleteCurrentLineMarkers()
-
+
# Filename will be <string> if we're in a bit of code that was executed from
# a string (rather than a file). I haven't been able to get the original string
# for display.
if DebuggerService.ComparePaths(openDoc.GetFilename(),filename):
foundView = openDoc.GetFirstView()
break
-
+
if not foundView:
if _VERBOSE:
print "filename=", filename
foundView.Activate()
foundView.GotoLine(lineNum)
startPos = foundView.PositionFromLine(lineNum)
-
- if not noArrow:
+
+ if not noArrow:
foundView.GetCtrl().MarkerAdd(lineNum -1, CodeEditor.CodeCtrl.CURRENT_LINE_MARKER_NUM)
def DeleteCurrentLineMarkers(self):
openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if(isinstance(openDoc, CodeEditor.CodeDocument)):
- openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers()
-
- def LoadFramesListXML(self, framesXML):
- self.framesTab.LoadFramesListXML(framesXML)
-
- def SetStatusText(self, text):
- self._statusBar.SetStatusText(text,0)
-
- def Execute(self, initialArgs, startIn, environment):
- self._callback.start()
- self._executor.Execute(initialArgs, startIn, environment)
- self._callback.waitForRPC()
-
- def BreakExecution(self, event):
- self._callback.BreakExecution()
-
-
- def StopExecution(self, event):
- # This is a general comment on shutdown for the running and debugged processes. Basically, the
- # current state of this is the result of trial and error coding. The common problems were memory
- # access violations and threads that would not exit. Making the OutputReaderThreads daemons seems
- # to have side-stepped the hung thread issue. Being very careful not to touch things after calling
- # process.py:ProcessOpen.kill() also seems to have fixed the memory access violations, but if there
- # were more ugliness discovered I would not be surprised. If anyone has any help/advice, please send
- # it on to mfryer@activegrid.com.
- if not self._stopped:
- self._stopped = True
- try:
- self.DisableAfterStop()
- except wx._core.PyDeadObjectError:
- pass
- try:
- self._callback.ShutdownServer()
- except:
- tp,val,tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
+ for openDoc in openDocs:
+ if(isinstance(openDoc, CodeEditor.CodeDocument)):
+ openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers()
- try:
- self.DeleteCurrentLineMarkers()
- except:
- pass
- try:
- DebugCommandUI.ReturnPortToPool(self._debuggerPort)
- DebugCommandUI.ReturnPortToPool(self._guiPort)
- DebugCommandUI.ReturnPortToPool(self._debuggerBreakPort)
- except:
- pass
- try:
- if self._executor:
- self._executor.DoStopExecution()
- self._executor = None
- except:
- tp,val,tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
def StopAndRemoveUI(self, event):
self.StopExecution(None)
- if self in DebugCommandUI.debuggers:
- DebugCommandUI.debuggers.remove(self)
+ if self in BaseDebuggerUI.debuggers:
+ BaseDebuggerUI.debuggers.remove(self)
index = self._parentNoteBook.GetSelection()
self._parentNoteBook.GetPage(index).Show(False)
- self._parentNoteBook.RemovePage(index)
+ self._parentNoteBook.RemovePage(index)
def OnAddWatch(self, event):
if self.framesTab:
self.framesTab.OnWatch(event)
-
+
def MakeFramesUI(self, parent, id, debugger):
- panel = FramesUI(parent, id, self)
- return panel
-
+ assert False, "MakeFramesUI not overridden"
+
def AppendText(self, event):
self.framesTab.AppendText(event.value)
-
+
def AppendErrorText(self, event):
self.framesTab.AppendErrorText(event.value)
-
+
def OnClearOutput(self, event):
self.framesTab.ClearOutput(None)
def SwitchToOutputTab(self):
self.framesTab.SwitchToOutputTab()
-
+
+
+class PHPDebuggerUI(BaseDebuggerUI):
+ DEFAULT_LISTENER_HOST = "127.0.0.1"
+ DEFAULT_LISTENER_PORT = 10001
+ DEFAULT_DBG_MOD_TIMEOUT = 300
+ DEFAULT_DBG_MAX_EXEC_TIME = 240
+ dbgSessSeqId = 1
+
+ def __init__(self, parent, id, command, service):
+ BaseDebuggerUI.__init__(self, parent, id)
+ #Note host and port need to come out of options or a pool.
+ self._dbgHost = PHPDebuggerUI.DEFAULT_LISTENER_HOST
+ self._dbgPort = PHPDebuggerUI.DEFAULT_LISTENER_PORT
+ self._dbgTimeout = PHPDebuggerUI.DEFAULT_DBG_MOD_TIMEOUT
+ self._dbgMaxExecTime = PHPDebuggerUI.DEFAULT_DBG_MAX_EXEC_TIME
+ self._dbgSessId = None
+ self._dbgSessParam = None
+ self._dbgPhpIniFile = None
+ self._callback = PHPDebugger.PHPDebuggerCallback(self, service, self._dbgHost, self._dbgPort)
+ self._executor = Executor(command, self)
+ self._service = service
+ self._stopped = False
+ self._allStopped = False
+
+ self._createPhpDbgSess()
+
+ def showErrorDialog(self, message, title):
+ wx.MessageBox(_(message), _(title))
+ return
+
+ def _createPhpDbgSess(self):
+ currTimeStr = str(time.time())
+ (secStr, usecStr) = currTimeStr.split('.')
+ secLongInt = long(secStr)
+ usecLongInt = long(usecStr)
+ self._dbgSessId = "%06ld%06ld%04d" % (secLongInt, usecLongInt, PHPDebuggerUI.dbgSessSeqId)
+ PHPDebuggerUI.dbgSessSeqId = PHPDebuggerUI.dbgSessSeqId + 1
+ self._dbgSessParam = "DBGSESSID=%s@clienthost:%d" % (self._dbgSessId, self._dbgPort)
+
+ if _VERBOSE:
+ print "phpDbgParam=%s" % self._dbgSessParam
+
+ self._service.SetPhpDbgParam(self._dbgSessParam)
+
+ def _preparePhpIniFile(self):
+ success = False
+
+ phpCgiExec = Executor.GetPHPExecutablePath()
+ phpExec = phpCgiExec.replace("php-cgi", "php")
+ iniPath = self._getPhpIniFromRunningPhp(phpExec)
+
+ try:
+ iniDbgPath = os.path.normpath(iniPath + ".ag_debug_enabled")
+ dbgFile = open(iniDbgPath, "w")
+ oriFile = open(iniPath, "r")
+
+ while True:
+ oneOriLine = oriFile.readline()
+ if oneOriLine == '':
+ break
+
+ if not oneOriLine.startswith("debugger.") and not oneOriLine.startswith("max_execution_time="):
+ dbgFile.write(oneOriLine)
+
+ oriFile.close()
+
+ if _WINDOWS:
+ dbgExtFile = "php_dbg.dll"
+ else:
+ dbgExtFile = "dbg.so"
+
+ #
+ # TODO: we should make all of these options configurable.
+ #
+ configStr = "\n; ===============================================================\n; The followings are added by ActiveGrid IDE PHP Debugger Runtime\n; ===============================================================\n\n; As we are running with the dbg module, it takes a much longer time for each script to run.\nmax_execution_time=%d\n\n[debugger]\nextension=%s\ndebugger.enabled=On\ndebugger.JIT_enabled=On\ndebugger.JIT_host=%s\ndebugger.JIT_port=%d\ndebugger.fail_silently=Off\ndebugger.timeout_seconds=%d\ndebugger.ignore_nops=Off\ndebugger.enable_session_cookie=On\ndebugger.session_nocache=On\ndebugger.profiler_enabled=Off\n" % (self._dbgMaxExecTime, dbgExtFile, self._dbgHost, self._dbgPort, self._dbgTimeout)
+ dbgFile.write(configStr)
+ dbgFile.close()
+ success = True
+ except:
+ #TODO: print stack trace.
+ print "Caught exceptions while minipulating php.ini files"
+
+ if success:
+ self._dbgPhpIniFile = iniDbgPath
+ else:
+ self._dbgPhpIniFile = None
+
+ def _getPhpIniFromRunningPhp(self, phpExec):
+ phpIniPath = None
+
+ cmdEnv = os.environ
+ if cmdEnv.has_key('PYTHONPATH'):
+ del cmdEnv['PYTHONPATH']
+
+ cmdLine = [phpExec, "-r", "phpinfo();"]
+ phpProc = subprocess.Popen(args=cmdLine, bufsize=0, executable=None, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=os.environ, universal_newlines=False, startupinfo=None, creationflags=0)
+ phpOutput = phpProc.stdout
+
+ phpIniPattern = "Configuration File (php.ini) Path => "
+ while True:
+ oneLine = phpOutput.readline()
+ if oneLine == '':
+ break
+
+ if oneLine.startswith(phpIniPattern):
+ if oneLine.endswith("\n"):
+ endIndex = oneLine.index("\n")
+ phpIniPath = oneLine[len(phpIniPattern):endIndex]
+ else:
+ phpIniPath = oneLine[len(phpIniPattern):]
+
+ if phpIniPath and len(phpIniPath) > 0:
+ phpIniPath = os.path.normpath(phpIniPath)
+ break
+
+ phpOutput.close()
+
+ if _VERBOSE:
+ print "php.ini path is: %s" % repr(phpIniPath)
+
+ return phpIniPath
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
+ self._preparePhpIniFile()
+ self._callback.Start()
+
+ if not onWebServer:
+ if self._dbgPhpIniFile:
+ args = '-c "' + self._dbgPhpIniFile + '" ' + initialArgs
+ else:
+ args = initialArgs
+
+ self._executor.Execute(args, startIn, environment)
+
+ def StopExecution(self, event):
+ # This is a general comment on shutdown for the running and debugged processes. Basically, the
+ # current state of this is the result of trial and error coding. The common problems were memory
+ # access violations and threads that would not exit. Making the OutputReaderThreads daemons seems
+ # to have side-stepped the hung thread issue. Being very careful not to touch things after calling
+ # process.py:ProcessOpen.kill() also seems to have fixed the memory access violations, but if there
+ # were more ugliness discovered I would not be surprised. If anyone has any help/advice, please send
+ # it on to mfryer@activegrid.com.
+ if not self._allStopped:
+ self._stopped = True
+ try:
+ self.DisableAfterStop()
+ except wx._core.PyDeadObjectError:
+ pass
+
+ try:
+ #
+ # If this is called by clicking the "Stop" button, we only stop
+ # the current running php script, and keep the listener
+ # running.
+ #
+ if event:
+ self._callback.ShutdownServer(stopLsnr = False)
+ else:
+ self._callback.ShutdownServer(stopLsnr = True)
+ self._allStopped = True
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ try:
+ self.DeleteCurrentLineMarkers()
+ except:
+ pass
+
+ try:
+ if self._executor:
+ self._executor.DoStopExecution()
+ self._executor = None
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ def MakeFramesUI(self, parent, id, debugger):
+ return PHPFramesUI(parent, id, self)
+
+ def LoadPHPFramesList(self, stackList):
+ self.framesTab.LoadFramesList(stackList)
+
+ #
+ # TODO: this is a hack to overwrite BaseDebuggerUI's function. The purpose
+ # is to always push breakpoints no matter if a php is running or not. If
+ # no php is running, an exception will be thrown and handled like nothing
+ # happened.
+ #
+ def BreakPointChange(self):
+ self._callback.PushBreakpoints()
+ self.framesTab.PopulateBPList()
+
+
+class PythonDebuggerUI(BaseDebuggerUI):
+ debuggerPortList = None
+
+ def GetAvailablePort():
+ for index in range( 0, len(PythonDebuggerUI.debuggerPortList)):
+ port = PythonDebuggerUI.debuggerPortList[index]
+ if PythonDebuggerUI.PortAvailable(port):
+ PythonDebuggerUI.debuggerPortList.pop(index)
+ return port
+ wx.MessageBox(_("Out of ports for debugging! Please restart the application builder.\nIf that does not work, check for and remove running instances of python."), _("Out of Ports"))
+ assert False, "Out of ports for debugger."
+
+ GetAvailablePort = staticmethod(GetAvailablePort)
+
+ def ReturnPortToPool(port):
+ config = wx.ConfigBase_Get()
+ startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
+ val = int(startingPort) + int(PORT_COUNT)
+ if int(port) >= startingPort and (int(port) <= val):
+ PythonDebuggerUI.debuggerPortList.append(int(port))
+
+ ReturnPortToPool = staticmethod(ReturnPortToPool)
+
+ def PortAvailable(port):
+ config = wx.ConfigBase_Get()
+ hostname = config.Read("DebuggerHostName", DEFAULT_HOST)
+ try:
+ server = AGXMLRPCServer((hostname, port))
+ server.server_close()
+ if _VERBOSE: print "Port ", str(port), " available."
+ return True
+ except:
+ tp,val,tb = sys.exc_info()
+ if _VERBOSE: traceback.print_exception(tp, val, tb)
+ if _VERBOSE: print "Port ", str(port), " unavailable."
+ return False
+
+ PortAvailable = staticmethod(PortAvailable)
+
+ def NewPortRange():
+ config = wx.ConfigBase_Get()
+ startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
+ PythonDebuggerUI.debuggerPortList = range(startingPort, startingPort + PORT_COUNT)
+ NewPortRange = staticmethod(NewPortRange)
+
+ def __init__(self, parent, id, command, service, autoContinue=True):
+ # Check for ports before creating the panel.
+ if not PythonDebuggerUI.debuggerPortList:
+ PythonDebuggerUI.NewPortRange()
+ self._debuggerPort = str(PythonDebuggerUI.GetAvailablePort())
+ self._guiPort = str(PythonDebuggerUI.GetAvailablePort())
+ self._debuggerBreakPort = str(PythonDebuggerUI.GetAvailablePort())
+ BaseDebuggerUI.__init__(self, parent, id)
+ self._command = command
+ self._service = service
+ config = wx.ConfigBase_Get()
+ self._debuggerHost = self._guiHost = config.Read("DebuggerHostName", DEFAULT_HOST)
+
+ url = 'http://' + self._debuggerHost + ':' + self._debuggerPort + '/'
+ self._breakURL = 'http://' + self._debuggerHost + ':' + self._debuggerBreakPort + '/'
+ self._callback = PythonDebuggerCallback(self._guiHost, self._guiPort, url, self._breakURL, self, autoContinue)
+ if DebuggerHarness.__file__.find('library.zip') > 0:
+ try:
+ fname = DebuggerHarness.__file__
+ parts = fname.split('library.zip')
+ path = os.path.join(parts[0],'activegrid', 'tool', 'DebuggerHarness.py')
+ except:
+ tp, val, tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ else:
+ print "Starting debugger on these ports: %s, %s, %s" % (str(self._debuggerPort) , str(self._guiPort) , str(self._debuggerBreakPort))
+ path = DebuggerService.ExpandPath(DebuggerHarness.__file__)
+ self._executor = Executor(path, self, self._debuggerHost, \
+ self._debuggerPort, self._debuggerBreakPort, self._guiHost, self._guiPort, self._command, callbackOnExit=self.ExecutorFinished)
+
+ self._stopped = False
+
+ def LoadPythonFramesList(self, framesXML):
+ self.framesTab.LoadFramesList(framesXML)
+
+ def Execute(self, initialArgs, startIn, environment, onWebServer = False):
+ self._callback.Start()
+ self._executor.Execute(initialArgs, startIn, environment)
+ self._callback.WaitForRPC()
+
+
+ def StopExecution(self, event):
+ # This is a general comment on shutdown for the running and debugged processes. Basically, the
+ # current state of this is the result of trial and error coding. The common problems were memory
+ # access violations and threads that would not exit. Making the OutputReaderThreads daemons seems
+ # to have side-stepped the hung thread issue. Being very careful not to touch things after calling
+ # process.py:ProcessOpen.kill() also seems to have fixed the memory access violations, but if there
+ # were more ugliness discovered I would not be surprised. If anyone has any help/advice, please send
+ # it on to mfryer@activegrid.com.
+ if not self._stopped:
+ self._stopped = True
+ try:
+ self.DisableAfterStop()
+ except wx._core.PyDeadObjectError:
+ pass
+ try:
+ self._callback.ShutdownServer()
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ try:
+ self.DeleteCurrentLineMarkers()
+ except:
+ pass
+ try:
+ PythonDebuggerUI.ReturnPortToPool(self._debuggerPort)
+ PythonDebuggerUI.ReturnPortToPool(self._guiPort)
+ PythonDebuggerUI.ReturnPortToPool(self._debuggerBreakPort)
+ except:
+ pass
+ try:
+ if self._executor:
+ self._executor.DoStopExecution()
+ self._executor = None
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+
+ def MakeFramesUI(self, parent, id, debugger):
+ panel = PythonFramesUI(parent, id, self)
+ return panel
+
+
class BreakpointsUI(wx.Panel):
def __init__(self, parent, id, ui):
wx.Panel.__init__(self, parent, id)
p1 = self
self._bpListCtrl = wx.ListCtrl(p1, -1, pos=wx.DefaultPosition, size=(1000,1000), style=wx.LC_REPORT)
sizer.Add(self._bpListCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
- self._bpListCtrl.InsertColumn(0, "File")
- self._bpListCtrl.InsertColumn(1, "Line")
- self._bpListCtrl.InsertColumn(2, "Path")
+ self._bpListCtrl.InsertColumn(0, "File")
+ self._bpListCtrl.InsertColumn(1, "Line")
+ self._bpListCtrl.InsertColumn(2, "Path")
self._bpListCtrl.SetColumnWidth(0, 150)
self._bpListCtrl.SetColumnWidth(1, 50)
self._bpListCtrl.SetColumnWidth(2, 450)
self._bpListCtrl.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListRightClick)
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.ListItemSelected, self._bpListCtrl)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.ListItemDeselected, self._bpListCtrl)
-
+
def OnLeftDoubleClick(event):
self.SyncBPLine(event)
-
+
wx.EVT_LEFT_DCLICK(self._bpListCtrl, OnLeftDoubleClick)
self.PopulateBPList()
p1.SetSizer(sizer)
sizer.Fit(p1)
p1.Layout()
-
+
def PopulateBPList(self):
list = self._bpListCtrl
list.DeleteAllItems()
list.InsertStringItem(index, shortFile)
list.SetStringItem(index, 1, str(line))
list.SetStringItem(index, 2, fileName)
-
+
def OnListRightClick(self, event):
menu = wx.Menu()
item = wx.MenuItem(menu, self.clearBPID, "Clear Breakpoint")
list = self._bpListCtrl
fileName = list.GetItem(self.currentItem, 2).GetText()
lineNumber = list.GetItem(self.currentItem, 1).GetText()
- self._ui.SynchCurrentLine( fileName, int(lineNumber) , noArrow=True)
+ self._ui.SynchCurrentLine( fileName, int(lineNumber) , noArrow=True)
def ClearBreakPoint(self, event):
if self.currentItem >= 0:
list = self._bpListCtrl
fileName = list.GetItem(self.currentItem, 2).GetText()
lineNumber = list.GetItem(self.currentItem, 1).GetText()
- wx.GetApp().GetService(DebuggerService).OnToggleBreakpoint(None, line=int(lineNumber) -1, fileName=fileName )
-
+ wx.GetApp().GetService(DebuggerService).OnToggleBreakpoint(None, line=int(lineNumber) -1, fileName=fileName )
+
def ListItemSelected(self, event):
self.currentItem = event.m_itemIndex
def ListItemDeselected(self, event):
self.currentItem = -1
-
+
class Watch:
CODE_ALL_FRAMES = 1
CODE_THIS_BLOCK = 2
CODE_THIS_LINE = 4
CODE_RUN_ONCE = 8
-
+
def __init__(self, name, command, show_code=CODE_ALL_FRAMES):
self._name = name
- self._command = command
+ self._command = command
self._show_code = show_code
-
+
class WatchDialog(wx.Dialog):
WATCH_ALL_FRAMES = "Watch in all frames"
WATCH_THIS_FRAME = "Watch in this frame only"
return
self.EndModal(wx.ID_OK)
self.Bind(wx.EVT_BUTTON, OnOkClick, self._okButton)
-
+
self._cancelButton = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
self._cancelButton.SetHelpText(_("The Cancel button cancels the dialog."))
-
+
self.__set_properties()
self.__do_layout()
-
+
def GetSettings(self):
return self._watchNameTextCtrl.GetValue(), self._watchValueTextCtrl.GetValue(), self.GetSendFrame(), self.GetRunOnce()
-
+
def GetSendFrame(self):
return (WatchDialog.WATCH_ALL_FRAMES != self.radio_box_1.GetStringSelection())
-
+
def GetRunOnce(self):
return (WatchDialog.WATCH_ONCE == self.radio_box_1.GetStringSelection())
-
+
def __set_properties(self):
self.SetTitle("Add a Watch")
#self.SetSize((400, 250))
grid_sizer_4.Add(self.label_4, 0, wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE, 0)
sizer_1.Add(grid_sizer_4, 0, wx.EXPAND, 0)
sizer_1.Add(self.radio_box_1, 0, wx.EXPAND, 0)
-
+
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(self._okButton, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
box.Add(self._cancelButton, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
sizer_1.Add(box, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
-
-class FramesUI(wx.SplitterWindow):
+
+class BaseFramesUI(wx.SplitterWindow):
def __init__(self, parent, id, ui):
wx.SplitterWindow.__init__(self, parent, id, style = wx.SP_3D)
self._ui = ui
sizer = wx.BoxSizer(wx.HORIZONTAL)
framesLabel = wx.StaticText(self, -1, "Stack Frame:")
sizer.Add(framesLabel, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT|wx.LEFT, border=2)
-
+
self._framesChoiceCtrl = wx.Choice(p1, -1, choices=[" "])
sizer.Add(self._framesChoiceCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
self._framesChoiceCtrl.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListRightClick)
sizer2 = wx.BoxSizer(wx.VERTICAL)
p1.SetSizer(sizer2)
-
self._treeCtrl = wx.gizmos.TreeListCtrl(p1, -1, style=wx.TR_DEFAULT_STYLE| wx.TR_FULL_ROW_HIGHLIGHT)
self._treeCtrl.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick)
sizer2.Add(sizer, 0, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
sizer2.Add(self._treeCtrl,1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
- tree = self._treeCtrl
+ tree = self._treeCtrl
tree.AddColumn("Thing")
tree.AddColumn("Value")
tree.SetMainColumn(0) # the one with the tree in it...
tree.SetColumnWidth(0, 175)
tree.SetColumnWidth(1, 355)
self._root = tree.AddRoot("Frame")
+ tree.SetPyData(self._root, "root")
tree.SetItemText(self._root, "", 1)
tree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.IntrospectCallback)
-
self._p2 = p2 = wx.Window(self, -1)
sizer3 = wx.BoxSizer(wx.HORIZONTAL)
p2.SetSizer(sizer3)
self._notebook.AddPage(self.consoleTab, "Output")
self._notebook.AddPage(self.inspectConsoleTab, "Interact")
self._notebook.AddPage(self.breakPointsTab, "Break Points")
-
self.SetMinimumPaneSize(20)
self.SplitVertically(p1, p2, 550)
self.currentItem = None
self._notebook.Show(True)
-
+
def PopulateBPList(self):
self.breakPointsTab.PopulateBPList()
-
+
def OnSize(self, event):
self._notebook.SetSize(self._p2.GetSize())
-
+
+ def OnDoubleClick(self, event):
+ # Looking for a stack trace line.
+ lineText, pos = self._textCtrl.GetCurLine()
+ fileBegin = lineText.find("File \"")
+ fileEnd = lineText.find("\", line ")
+ lineEnd = lineText.find(", in ")
+ if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1:
+ # Check the line before the one that was clicked on
+ lineNumber = self._textCtrl.GetCurrentLine()
+ if(lineNumber == 0):
+ return
+ lineText = self._textCtrl.GetLine(lineNumber - 1)
+ fileBegin = lineText.find("File \"")
+ fileEnd = lineText.find("\", line ")
+ lineEnd = lineText.find(", in ")
+ if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1:
+ return
+
+ filename = lineText[fileBegin + 6:fileEnd]
+ lineNum = int(lineText[fileEnd + 8:lineEnd])
+
+ foundView = None
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for openDoc in openDocs:
+ if openDoc.GetFilename() == filename:
+ foundView = openDoc.GetFirstView()
+ break
+
+ if not foundView:
+ doc = wx.GetApp().GetDocumentManager().CreateDocument(filename, wx.lib.docview.DOC_SILENT)
+ foundView = doc.GetFirstView()
+
+ if foundView:
+ foundView.GetFrame().SetFocus()
+ foundView.Activate()
+ foundView.GotoLine(lineNum)
+ startPos = foundView.PositionFromLine(lineNum)
+ lineText = foundView.GetCtrl().GetLine(lineNum - 1)
+ foundView.SetSelection(startPos, startPos + len(lineText.rstrip("\n")))
+ import OutlineService
+ wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos)
+
def MakeConsoleTab(self, parent, id):
panel = wx.Panel(parent, id)
sizer = wx.BoxSizer(wx.HORIZONTAL)
self._textCtrl.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font))
self._textCtrl.SetFontColor(wx.BLACK)
self._textCtrl.StyleClearAll()
+ wx.stc.EVT_STC_DOUBLECLICK(self._textCtrl, self._textCtrl.GetId(), self.OnDoubleClick)
+
panel.SetSizer(sizer)
#sizer.Fit(panel)
return panel
-
- def ReplaceLastLine(self, command):
- line = self._interCtrl.GetLineCount() - 1
- self._interCtrl.GotoLine(line)
- start = self._interCtrl.GetCurrentPos()
- self._interCtrl.SetTargetStart(start)
- end = self._interCtrl.GetLineEndPosition(line)
- self._interCtrl.SetTargetEnd(end)
- self._interCtrl.ReplaceTarget(">>> " + command)
- self._interCtrl.GotoLine(line)
- self._interCtrl.SetSelectionStart(self._interCtrl.GetLineEndPosition(line))
-
def ExecuteCommand(self, command):
- if not len(self.command_list) or not command == self.command_list[len(self.command_list) -1]:
- self.command_list.append(command)
- self.command_index = len(self.command_list) - 1
- retval = self._ui._callback._debuggerServer.execute_in_frame(self._framesChoiceCtrl.GetStringSelection(), command)
- self._interCtrl.AddText("\n" + str(retval))
- self._interCtrl.ScrollToLine(self._interCtrl.GetLineCount())
- # Refresh the tree view in case this command resulted in changes there. TODO: Need to reopen tree items.
- self.PopulateTreeFromFrameMessage(self._framesChoiceCtrl.GetStringSelection())
-
+ assert False, "ExecuteCommand not overridden"
+
def MakeInspectConsoleTab(self, parent, id):
- self.command_list = []
- self.command_index = 0
-
+ def handleCommand():
+ cmdStr = self._cmdInput.GetValue()
+ if cmdStr:
+ self._cmdList.append(cmdStr)
+ self._cmdIndex = len(self._cmdList)
+ self._cmdInput.Clear()
+ self._cmdOutput.SetDefaultStyle(style=self._cmdOutputTextStyle)
+ self._cmdOutput.AppendText(">>> " + cmdStr + "\n")
+ self._cmdOutput.SetDefaultStyle(style=self._defaultOutputTextStyle)
+ self.ExecuteCommand(cmdStr)
+ return
+
+ def OnCmdButtonPressed(event):
+ handleCommand()
+ return
+
def OnKeyPressed(event):
key = event.KeyCode()
- if key == wx.WXK_DELETE or key == wx.WXK_BACK:
- if self._interCtrl.GetLine(self._interCtrl.GetCurrentLine()) == ">>> ":
- return
- elif key == wx.WXK_RETURN:
- command = self._interCtrl.GetLine(self._interCtrl.GetCurrentLine())[4:]
- self.ExecuteCommand(command)
- self._interCtrl.AddText("\n>>> ")
- return
+ if key == wx.WXK_RETURN:
+ handleCommand()
elif key == wx.WXK_UP:
- if not len(self.command_list):
+ if len(self._cmdList) < 1 or self._cmdIndex < 1:
return
- self.ReplaceLastLine(self.command_list[self.command_index])
- if self.command_index == 0:
- self.command_index = len(self.command_list) - 1
- else:
- self.command_index = self.command_index - 1
- return
+
+ self._cmdInput.Clear()
+ self._cmdInput.AppendText(self._cmdList[self._cmdIndex - 1])
+ self._cmdIndex = self._cmdIndex - 1
elif key == wx.WXK_DOWN:
- if not len(self.command_list):
+ if len(self._cmdList) < 1 or self._cmdIndex >= len(self._cmdList):
return
- if self.command_index < len(self.command_list) - 1:
- self.command_index = self.command_index + 1
- else:
- self.command_index = 0
- self.ReplaceLastLine(self.command_list[self.command_index])
- return
- event.Skip()
- try:
- panel = wx.Panel(parent, id)
- sizer = wx.BoxSizer(wx.HORIZONTAL)
- self._interCtrl = STCTextEditor.TextCtrl(panel, wx.NewId())
- sizer.Add(self._interCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 2)
- self._interCtrl.SetViewLineNumbers(False)
- if wx.Platform == '__WXMSW__':
- font = "Courier New"
+ self._cmdInput.Clear()
+ self._cmdInput.AppendText(self._cmdList[self._cmdIndex - 1])
+ self._cmdIndex = self._cmdIndex + 1
else:
- font = "Courier"
- self._interCtrl.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font))
- self._interCtrl.SetFontColor(wx.BLACK)
- self._interCtrl.StyleClearAll()
- wx.EVT_KEY_DOWN(self._interCtrl, OnKeyPressed)
- self._interCtrl.AddText(">>> ")
- panel.SetSizer(sizer)
- except:
- tp, val, tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
-
- return panel
-
+ event.Skip()
+ return
+
+ def OnClrButtonPressed(event):
+ self._cmdOutput.Clear()
+
+ panel = wx.Panel(parent, id)
+
+ cmdLabel = wx.StaticText(panel, -1, "Cmd: ")
+ self._cmdInput = wx.TextCtrl(panel)
+ cmdButton = wx.Button(panel, label="Execute")
+ clrButton = wx.Button(panel, label="Clear")
+ self._cmdOutput = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.HSCROLL | wx.TE_READONLY | wx.TE_RICH2)
+
+ hbox = wx.BoxSizer()
+ hbox.Add(cmdLabel, proportion=0, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
+ hbox.Add(self._cmdInput, proportion=1, flag=wx.EXPAND)
+ hbox.Add(cmdButton, proportion=0, flag=wx.RIGHT)
+ hbox.Add(clrButton, proportion=0, flag=wx.RIGHT)
+
+ vbox = wx.BoxSizer(wx.VERTICAL)
+ vbox.Add(hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=2)
+ vbox.Add(self._cmdOutput, proportion=1, flag=wx.EXPAND | wx.LEFT, border=2)
+
+ panel.SetSizer(vbox)
+ cmdButton.Bind(wx.EVT_BUTTON, OnCmdButtonPressed)
+ clrButton.Bind(wx.EVT_BUTTON, OnClrButtonPressed)
+ wx.EVT_KEY_DOWN(self._cmdInput, OnKeyPressed)
+
+ fixedFont = wx.Font(self._cmdInput.GetFont().GetPointSize(), family=wx.TELETYPE, style=wx.NORMAL, weight=wx.NORMAL)
+ self._defaultOutputTextStyle = wx.TextAttr("BLACK", wx.NullColour, fixedFont)
+ self._cmdOutputTextStyle = wx.TextAttr("RED", wx.NullColour, fixedFont)
+ self._cmdOutput.SetDefaultStyle(style=self._defaultOutputTextStyle)
+ self._cmdList = []
+ self._cmdIndex = 0
+
+ panel.Show()
+ return panel
+
def MakeBreakPointsTab(self, parent, id):
panel = BreakpointsUI(parent, id, self._ui)
return panel
-
+
+ def OnRightClick(self, event):
+ assert False, "OnRightClick not overridden"
+
+ def ClearWhileRunning(self):
+ list = self._framesChoiceCtrl
+ list.Clear()
+ list.Enable(False)
+ tree = self._treeCtrl
+ root = self._root
+ tree.DeleteChildren(root)
+ self._cmdInput.Enable(False)
+ self._cmdOutput.Enable(False)
+
+ def OnListRightClick(self, event):
+ if not hasattr(self, "syncFrameID"):
+ self.syncFrameID = wx.NewId()
+ self.Bind(wx.EVT_MENU, self.OnSyncFrame, id=self.syncFrameID)
+ menu = wx.Menu()
+ item = wx.MenuItem(menu, self.syncFrameID, "Goto Source Line")
+ menu.AppendItem(item)
+ self.PopupMenu(menu, event.GetPosition())
+ menu.Destroy()
+
+ def OnSyncFrame(self, event):
+ assert False, "OnSyncFrame not overridden"
+
+ def LoadFramesList(self, framesXML):
+ assert False, "LoadFramesList not overridden"
+
+ def ListItemSelected(self, event):
+ assert False, "ListItemSelected not overridden"
+
+ def PopulateTreeFromFrameMessage(self, message):
+ assert False, "PopulateTreeFromFrameMessage not overridden"
+
+ def IntrospectCallback(self, event):
+ assert False, "IntrospectCallback not overridden"
+
+ def AppendText(self, text):
+ self._textCtrl.SetReadOnly(False)
+ self._textCtrl.AddText(text)
+ self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
+ self._textCtrl.SetReadOnly(True)
+
+ def AppendErrorText(self, text):
+ self._textCtrl.SetReadOnly(False)
+ self._textCtrl.SetFontColor(wx.RED)
+ self._textCtrl.StyleClearAll()
+ self._textCtrl.AddText(text)
+ self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
+ self._textCtrl.SetFontColor(wx.BLACK)
+ self._textCtrl.StyleClearAll()
+ self._textCtrl.SetReadOnly(True)
+
+ def ClearOutput(self, event):
+ self._textCtrl.SetReadOnly(False)
+ self._textCtrl.ClearAll()
+ self._textCtrl.SetReadOnly(True)
+
+ def SwitchToOutputTab(self):
+ self._notebook.SetSelection(0)
+
+class PHPFramesUI(BaseFramesUI):
+ def __init__(self, parent, id, ui):
+ BaseFramesUI.__init__(self, parent, id, ui)
+
+ def LoadFramesList(self, stackList):
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ self._cmdInput.Enable(True)
+ self._cmdOutput.Enable(True)
+ index = 0
+ self._stack = stackList
+ list = self._framesChoiceCtrl
+ list.Clear()
+
+ if len(stackList) > 0:
+ self._displayVariableTreeRootNode()
+
+ for stackFrame in stackList:
+ message = stackFrame.getDisplayStr(stackList)
+ list.Append(message)
+
+ self.currentItem = index
+ list.SetSelection(index)
+ list.Enable(True)
+ self.OnSyncFrame(None)
+ self._p1.FitInside()
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+ def PopulateTreeFromStackFrame(self, frameNode):
+ vars = frameNode.getVariables()
+ tree = self._treeCtrl
+ rootTreeNode = self._root
+
+ #
+ # Build a new sub variable tree from the root node.
+ #
+ tree.DeleteChildren(rootTreeNode)
+ for var in vars:
+ aTreeNode = self.AppendSubTreeFromAVariable(tree, var, rootTreeNode)
+
+ #
+ # No need to expand the node here, as the IntrospectCallback() has
+ # already called it.
+ #
+ self._p2.FitInside()
+
+ def AppendSubTreeFromAVariable(self, tree, var, parentTreeNode, previousOne = None):
+ varName = var.getName()
+ varValueStr = var.getValueString()
+
+ #
+ # If previously we already have this item in the tree, replace it.
+ # Otherwise, insert a new one.
+ #
+ if previousOne:
+ newNode = tree.InsertItem(parentTreeNode, previousOne, varName)
+ else:
+ newNode = tree.AppendItem(parentTreeNode, varName)
+
+ #
+ # Associate this variable object with this newNode.
+ #
+ tree.SetPyData(newNode, var)
+
+ #
+ # Set this variable's value string (for displaying).
+ #
+ if varValueStr and len(varValueStr) > 0:
+ tree.SetItemText(newNode, varValueStr, 1)
+
+ #
+ # If this variable has child variables, recursively build the sub
+ # variable tree.
+ #
+ if var.hasChildren():
+ childrenVarList = var.getChildrenVariables()
+ for childVar in childrenVarList:
+ self.AppendSubTreeFromAVariable(tree, childVar, newNode)
+
+ #
+ # If its child variables are sortable, sort it.
+ #
+ if var.childrenIsSortable():
+ tree.SortChildren(newNode)
+
+ return newNode
+
+ def IntrospectCallback(self, event):
+ tree = self._treeCtrl
+ item = event.GetItem()
+
+ #
+ # Only when the introspection happens to root, we get the whole
+ # variable tree. For all the individual nodes, we have populated
+ # the subtree already, so don't need to do anything.
+ #
+ if tree.GetPyData(item) == "root" and self._stack and self._stack[self.currentItem]:
+ stackFrame = self._stack[self.currentItem]
+ self.PopulateTreeFromStackFrame(stackFrame)
+
+ event.Skip()
+
+ def OnSyncFrame(self, event):
+ stackFrame = self._stack[self.currentItem]
+ fileName = stackFrame.getFileName()
+ lineNo = stackFrame.getLineNo()
+ if _VERBOSE:
+ print "OnSyncFrame(): about to sync: fileName: %s, lineNo: %d" % (fileName, lineNo)
+ self._ui.SynchCurrentLine(fileName, lineNo)
+ if _VERBOSE:
+ print "OnSyncFrame(): sync done"
+
+ def ListItemSelected(self, event):
+ selectedStackFrameStr = event.GetString()
+
+ if not self._stack or len(self._stack) < 1:
+ return
+
+ found = False
+ for stackFrame in self._stack:
+ if stackFrame.getDisplayStr() == selectedStackFrameStr:
+ self.currentItem = stackFrame.getFrameIndex()
+ found = True
+ break
+
+ if found:
+ self._displayVariableTreeRootNode()
+ self.OnSyncFrame(None)
+
+ return
+
+ def _displayVariableTreeRootNode(self):
+ #
+ # Add a dummy item to rootTreeNode so that it will be shown as
+ # expandable. Only do real tree population on the fly when the
+ # rootTreeNode is expanded in OnIntrospection().
+ #
+ tree = self._treeCtrl
+ rootTreeNode = self._root
+ dummyNode = tree.AppendItem(rootTreeNode, "dummy")
+ tree.Collapse(rootTreeNode)
+
+ return
+
+
+class PythonFramesUI(BaseFramesUI):
+ def __init__(self, parent, id, ui):
+ BaseFramesUI.__init__(self, parent, id, ui)
+
+ def ExecuteCommand(self, command):
+ retval = self._ui._callback._debuggerServer.execute_in_frame(self._framesChoiceCtrl.GetStringSelection(), command)
+ self._cmdOutput.AppendText(str(retval) + "\n")
+ # Refresh the tree view in case this command resulted in changes there. TODO: Need to reopen tree items.
+ self.PopulateTreeFromFrameMessage(self._framesChoiceCtrl.GetStringSelection())
+
def OnRightClick(self, event):
#Refactor this...
self._introspectItem = event.GetItem()
menu.Destroy()
self._parentChain = None
self._introspectItem = None
-
+
def GetItemChain(self, item):
parentChain = []
if item:
item = self._treeCtrl.GetItemParent(item)
parentChain.reverse()
return parentChain
-
+
def OnView(self, event):
title = self._treeCtrl.GetItemText(self._introspectItem,0)
value = self._treeCtrl.GetItemText(self._introspectItem,1)
dlg = wx.lib.dialogs.ScrolledMessageDialog(self, value, title, style=wx.DD_DEFAULT_STYLE | wx.RESIZE_BORDER)
dlg.Show()
-
+
def OnSendToInteract(self, event):
value = ""
prevItem = ""
for item in self._parentChain:
-
+
if item.find(prevItem + '[') != -1:
value += item[item.find('['):]
continue
value += item
prevItem = item
print value
- self.ReplaceLastLine(value)
self.ExecuteCommand(value)
-
+
def OnWatch(self, event):
try:
if hasattr(self, '_parentChain'):
name, text, send_frame, run_once = wd.GetSettings()
if send_frame:
frameNode = self._stack[int(self.currentItem)]
- message = frameNode.getAttribute("message")
+ message = frameNode.getAttribute("message")
else:
message = ""
binType = self._ui._callback._debuggerServer.add_watch(name, text, message, run_once)
wd.Destroy()
except:
tp, val, tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
-
+ traceback.print_exception(tp, val, tb)
+
def OnIntrospect(self, event):
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
try:
- list = self._framesChoiceCtrl
- frameNode = self._stack[int(self.currentItem)]
- message = frameNode.getAttribute("message")
- binType = self._ui._callback._debuggerServer.attempt_introspection(message, self._parentChain)
- xmldoc = bz2.decompress(binType.data)
- domDoc = parseString(xmldoc)
- #wx.MessageBox(xmldoc, "result of introspection")
- nodeList = domDoc.getElementsByTagName('replacement')
- replacementNode = nodeList.item(0)
- if len(replacementNode.childNodes):
- thingToWalk = replacementNode.childNodes.item(0)
- tree = self._treeCtrl
- parent = tree.GetItemParent(self._introspectItem)
- treeNode = self.AppendSubTreeFromNode(thingToWalk, thingToWalk.getAttribute('name'), parent, insertBefore=self._introspectItem)
- if thingToWalk.getAttribute('name').find('[') == -1:
- self._treeCtrl.SortChildren(treeNode)
- self._treeCtrl.Expand(treeNode)
- tree.Delete(self._introspectItem)
- except:
- tp,val,tb = sys.exc_info()
- traceback.print_exception(tp, val, tb)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
-
- def ClearWhileRunning(self):
- list = self._framesChoiceCtrl
- list.Clear()
- list.Enable(False)
- tree = self._treeCtrl
- root = self._root
- tree.DeleteChildren(root)
- self._interCtrl.Enable(False)
-
- #tree.Hide()
-
- def OnListRightClick(self, event):
- if not hasattr(self, "syncFrameID"):
- self.syncFrameID = wx.NewId()
- self.Bind(wx.EVT_MENU, self.OnSyncFrame, id=self.syncFrameID)
- menu = wx.Menu()
- item = wx.MenuItem(menu, self.syncFrameID, "Goto Source Line")
- menu.AppendItem(item)
- self.PopupMenu(menu, event.GetPosition())
- menu.Destroy()
+
+ try:
+ list = self._framesChoiceCtrl
+ frameNode = self._stack[int(self.currentItem)]
+ message = frameNode.getAttribute("message")
+ binType = self._ui._callback._debuggerServer.attempt_introspection(message, self._parentChain)
+ xmldoc = bz2.decompress(binType.data)
+ domDoc = parseString(xmldoc)
+ nodeList = domDoc.getElementsByTagName('replacement')
+ replacementNode = nodeList.item(0)
+ if len(replacementNode.childNodes):
+ thingToWalk = replacementNode.childNodes.item(0)
+ tree = self._treeCtrl
+ parent = tree.GetItemParent(self._introspectItem)
+ treeNode = self.AppendSubTreeFromNode(thingToWalk, thingToWalk.getAttribute('name'), parent, insertBefore=self._introspectItem)
+ if thingToWalk.getAttribute('name').find('[') == -1:
+ self._treeCtrl.SortChildren(treeNode)
+ self._treeCtrl.Expand(treeNode)
+ tree.Delete(self._introspectItem)
+ except:
+ tp,val,tb = sys.exc_info()
+ traceback.print_exception(tp, val, tb)
+
+ finally:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
def OnSyncFrame(self, event):
list = self._framesChoiceCtrl
frameNode = self._stack[int(self.currentItem)]
file = frameNode.getAttribute("file")
line = frameNode.getAttribute("line")
- self._ui.SynchCurrentLine( file, int(line) )
-
- def LoadFramesListXML(self, framesXML):
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- self._interCtrl.Enable(True)
+ self._ui.SynchCurrentLine( file, int(line) )
+ def LoadFramesList(self, framesXML):
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
try:
- domDoc = parseString(framesXML)
- list = self._framesChoiceCtrl
- list.Clear()
- self._stack = []
- nodeList = domDoc.getElementsByTagName('frame')
- frame_count = -1
- for index in range(0, nodeList.length):
+ self._cmdInput.Enable(True)
+ self._cmdOutput.Enable(True)
+
+ try:
+ domDoc = parseString(framesXML)
+ list = self._framesChoiceCtrl
+ list.Clear()
+ self._stack = []
+ nodeList = domDoc.getElementsByTagName('frame')
+ frame_count = -1
+ for index in range(0, nodeList.length):
+ frameNode = nodeList.item(index)
+ message = frameNode.getAttribute("message")
+ list.Append(message)
+ self._stack.append(frameNode)
+ frame_count += 1
+ index = len(self._stack) - 1
+ list.SetSelection(index)
+
+ node = self._stack[index]
+ self.currentItem = index
+ self.PopulateTreeFromFrameNode(node)
+ self.OnSyncFrame(None)
+
+ self._p1.FitInside()
frameNode = nodeList.item(index)
- message = frameNode.getAttribute("message")
- list.Append(message)
- self._stack.append(frameNode)
- frame_count += 1
- index = len(self._stack) - 1
- list.SetSelection(index)
+ file = frameNode.getAttribute("file")
+ line = frameNode.getAttribute("line")
+ self._ui.SynchCurrentLine( file, int(line) )
+ except:
+ tp,val,tb=sys.exc_info()
+ traceback.print_exception(tp, val, tb)
- node = self._stack[index]
- self.currentItem = index
- self.PopulateTreeFromFrameNode(node)
- self.OnSyncFrame(None)
+ finally:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- self._p1.FitInside()
- frameNode = nodeList.item(index)
- file = frameNode.getAttribute("file")
- line = frameNode.getAttribute("line")
- self._ui.SynchCurrentLine( file, int(line) )
- except:
- tp,val,tb=sys.exc_info()
- traceback.print_exception(tp, val, tb)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
-
def ListItemSelected(self, event):
self.PopulateTreeFromFrameMessage(event.GetString())
self.OnSyncFrame(None)
-
- def PopulateTreeFromFrameMessage(self, message):
+
+ def PopulateTreeFromFrameMessage(self, message):
index = 0
for node in self._stack:
if node.getAttribute("message") == message:
self.PopulateTreeFromFrameNode(nodeList[0])
return
index = index + 1
-
+
def PopulateTreeFromFrameNode(self, frameNode):
list = self._framesChoiceCtrl
list.Enable(True)
- tree = self._treeCtrl
+ tree = self._treeCtrl
#tree.Show(True)
- root = self._root
+ root = self._root
tree.DeleteChildren(root)
children = frameNode.childNodes
firstChild = None
if firstChild:
tree.Expand(firstChild)
self._p2.FitInside()
-
+
def IntrospectCallback(self, event):
- tree = self._treeCtrl
+ tree = self._treeCtrl
item = event.GetItem()
if _VERBOSE:
print "In introspectCallback item is %s, pydata is %s" % (event.GetItem(), tree.GetPyData(item))
self._parentChain = self.GetItemChain(item)
self.OnIntrospect(event)
event.Skip()
-
+
def AppendSubTreeFromNode(self, node, name, parent, insertBefore=None):
- tree = self._treeCtrl
+ tree = self._treeCtrl
if insertBefore != None:
treeNode = tree.InsertItem(parent, insertBefore, name)
- else:
+ else:
treeNode = tree.AppendItem(parent, name)
children = node.childNodes
intro = node.getAttribute('intro')
-
+
if intro == "True":
tree.SetItemHasChildren(treeNode, True)
tree.SetPyData(treeNode, "Introspect")
for index in range(0, children.length):
subNode = children.item(index)
if self.HasChildren(subNode):
- self.AppendSubTreeFromNode(subNode, subNode.getAttribute("name"), treeNode)
+ self.AppendSubTreeFromNode(subNode, subNode.getAttribute("name"), treeNode)
else:
name = subNode.getAttribute("name")
value = self.StripOuterSingleQuotes(subNode.getAttribute("value"))
if name.find('[') == -1:
self._treeCtrl.SortChildren(treeNode)
return treeNode
-
+
def StripOuterSingleQuotes(self, string):
if string.startswith("'") and string.endswith("'"):
retval = string[1:-1]
elif string.startswith("\"") and string.endswith("\""):
retval = string[1:-1]
else:
- retval = string
- if retval.startswith("u'") and retval.endswith("'"):
- retval = retval[1:]
- return retval
-
- def HasChildren(self, node):
- try:
- return node.childNodes.length > 0
- except:
- tp,val,tb=sys.exc_info()
- return False
-
- def AppendText(self, text):
- self._textCtrl.SetReadOnly(False)
- self._textCtrl.AddText(text)
- self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
- self._textCtrl.SetReadOnly(True)
-
- def AppendErrorText(self, text):
- self._textCtrl.SetReadOnly(False)
- self._textCtrl.SetFontColor(wx.RED)
- self._textCtrl.StyleClearAll()
- self._textCtrl.AddText(text)
- self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
- self._textCtrl.SetFontColor(wx.BLACK)
- self._textCtrl.StyleClearAll()
- self._textCtrl.SetReadOnly(True)
-
- def ClearOutput(self, event):
- self._textCtrl.SetReadOnly(False)
- self._textCtrl.ClearAll()
- self._textCtrl.SetReadOnly(True)
+ retval = string
+ if retval.startswith("u'") and retval.endswith("'"):
+ retval = retval[1:]
+ return retval
+
+ def HasChildren(self, node):
+ try:
+ return node.childNodes.length > 0
+ except:
+ tp,val,tb=sys.exc_info()
+ return False
+
- def SwitchToOutputTab(self):
- self._notebook.SetSelection(0)
-
class DebuggerView(Service.ServiceView):
-
+
#----------------------------------------------------------------------------
# Overridden methods
#----------------------------------------------------------------------------
def __init__(self, service):
Service.ServiceView.__init__(self, service)
-
+
def _CreateControl(self, parent, id):
return None
-
+
#------------------------------------------------------------------------------
# Event handling
#-----------------------------------------------------------------------------
-
+
def OnToolClicked(self, event):
self.GetFrame().ProcessEvent(event)
-
- def ProcessUpdateUIEvent(self, event):
- return False
-
- def ProcessEvent(self, event):
- return False
#------------------------------------------------------------------------------
# Class methods
self._message = message
self._info = info
self._quit = quit
-
+
def getFramesXML(self):
return self._framesXML
-
+
def getMessage(self):
return self._message
-
+
def getInfo(self):
return self._info
-
+
def getQuit(self):
- return self._quit
-
+ return self._quit
+
class AGXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
def __init__(self, address, logRequests=0):
- SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests)
-
+ SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests)
+
class RequestHandlerThread(threading.Thread):
def __init__(self, queue, address):
threading.Thread.__init__(self)
self._server.register_function(self.quit)
self._server.register_function(self.dummyOperation)
if _VERBOSE: print "RequestHandlerThread on fileno %s" % str(self._server.fileno())
-
+
def run(self):
while self._keepGoing:
try:
- self._server.handle_request()
+ self._server.handle_request()
except:
tp, val, tb = sys.exc_info()
traceback.print_exception(tp, val, tb)
self._keepGoing = False
if _VERBOSE: print "Exiting Request Handler Thread."
-
+
def interaction(self, message, frameXML, info):
if _VERBOSE: print "In RequestHandlerThread.interaction -- adding to queue"
interaction = Interaction(message, frameXML, info)
self._queue.put(interaction)
return ""
-
+
def quit(self):
interaction = Interaction(None, None, info=None, quit=True)
self._queue.put(interaction)
return ""
-
+
def dummyOperation(self):
return ""
-
+
def AskToStop(self):
self._keepGoing = False
if type(self._server) is not types.NoneType:
self._pushBreakpoints = pushBreakpoints
self._breakDict = breakDict
self._kill = kill
-
+
def run(self):
try:
if _VERBOSE: print "RequestBreakThread, before call"
def __init__(self, function):
threading.Thread.__init__(self)
self._function = function
-
+
def run(self):
if _VERBOSE: print "In DOT, before call"
try:
except:
tp,val,tb = sys.exc_info()
traceback.print_exception(tp, val, tb)
- if _VERBOSE: print "In DOT, after call"
-
-class DebuggerCallback:
+ if _VERBOSE:
+ print "In DOT, after call"
+
+class BaseDebuggerCallback(object):
+
+ def Start(self):
+ assert False, "Start not overridden"
+
+ def ShutdownServer(self):
+ assert False, "ShutdownServer not overridden"
+
+ def BreakExecution(self):
+ assert False, "BreakExecution not overridden"
+
+ def SingleStep(self):
+ assert False, "SingleStep not overridden"
+
+ def Next(self):
+ assert False, "Next not overridden"
+
+ def Continue(self):
+ assert False, "Start not overridden"
+
+ def Return(self):
+ assert False, "Return not overridden"
+
+ def PushBreakpoints(self):
+ assert False, "PushBreakpoints not overridden"
- def __init__(self, host, port, debugger_url, break_url, debuggerUI):
+class PythonDebuggerCallback(BaseDebuggerCallback):
+
+ def __init__(self, host, port, debugger_url, break_url, debuggerUI, autoContinue=False):
if _VERBOSE: print "+++++++ Creating server on port, ", str(port)
-
+ self._timer = None
self._queue = Queue.Queue(50)
self._host = host
self._port = int(port)
threading._VERBOSE = _VERBOSE
self._serverHandlerThread = RequestHandlerThread(self._queue, (self._host, self._port))
-
+
self._debugger_url = debugger_url
self._debuggerServer = None
self._waiting = False
self._breakServer = None
self._firstInteraction = True
self._pendingBreak = False
-
- def start(self):
+ self._autoContinue = autoContinue
+
+ def Start(self):
self._serverHandlerThread.start()
-
+
def ShutdownServer(self):
#rbt = RequestBreakThread(self._breakServer, kill=True)
#rbt.start()
- self.setWaiting(False)
+ self._waiting = False
if self._serverHandlerThread:
self._serverHandlerThread.AskToStop()
self._serverHandlerThread = None
-
+
def BreakExecution(self):
rbt = RequestBreakThread(self._breakServer, interrupt=True)
rbt.start()
-
+
def SingleStep(self):
self._debuggerUI.DisableWhileDebuggerRunning()
self._debuggerServer.set_step() # Figure out where to set allowNone
- self.waitForRPC()
+ self.WaitForRPC()
def Next(self):
self._debuggerUI.DisableWhileDebuggerRunning()
self._debuggerServer.set_next()
- self.waitForRPC()
-
+ self.WaitForRPC()
+
def Continue(self):
self._debuggerUI.DisableWhileDebuggerRunning()
self._debuggerServer.set_continue()
- self.waitForRPC()
-
+ self.WaitForRPC()
+
def Return(self):
self._debuggerUI.DisableWhileDebuggerRunning()
self._debuggerServer.set_return()
- self.waitForRPC()
-
- def setWaiting(self, value):
- self._waiting = value
-
- def getWaiting(self):
- return self._waiting
-
- def readQueue(self):
+ self.WaitForRPC()
+
+ def ReadQueue(self):
if self._queue.qsize():
try:
item = self._queue.get_nowait()
self.interaction(None, None, None, True)
else:
data = bz2.decompress(item.getFramesXML().data)
- self.interaction(item.getMessage().data, data, item.getInfo(), False)
+ self.interaction(item.getMessage().data, data, item.getInfo(), False)
except Queue.Empty:
pass
-
- def pushBreakpoints(self):
+
+ def PushBreakpoints(self):
rbt = RequestBreakThread(self._breakServer, pushBreakpoints=True, breakDict=self._service.GetMasterBreakpointDict())
rbt.start()
-
- def waitForRPC(self):
- self.setWaiting(True)
- while self.getWaiting():
+
+ def WaitForRPC(self):
+ self._waiting = True
+ while self._waiting:
try:
- self.readQueue()
+ self.ReadQueue()
import time
time.sleep(0.02)
except:
tp, val, tb = sys.exc_info()
traceback.print_exception(tp, val, tb)
wx.GetApp().Yield(True)
- if _VERBOSE: print "Exiting waitForRPC."
-
+ if _VERBOSE: print "Exiting WaitForRPC."
+
def interaction(self, message, frameXML, info, quit):
-
+
#This method should be hit as the debugger starts.
if self._firstInteraction:
self._firstInteraction = False
self._debuggerServer = xmlrpclib.ServerProxy(self._debugger_url, allow_none=1)
self._breakServer = xmlrpclib.ServerProxy(self._break_url, allow_none=1)
- self.pushBreakpoints()
- self.setWaiting(False)
+ self.PushBreakpoints()
+ self._waiting = False
if _VERBOSE: print "+"*40
if(quit):
self._debuggerUI.StopExecution(None)
self._debuggerUI.SwitchToOutputTab()
else:
if _VERBOSE: print "Hit interaction no exception"
+ #if not self._autoContinue:
self._debuggerUI.SetStatusText(message)
- self._debuggerUI.LoadFramesListXML(frameXML)
- self._debuggerUI.EnableWhileDebuggerStopped()
+ if not self._autoContinue:
+ self._debuggerUI.LoadPythonFramesList(frameXML)
+ self._debuggerUI.EnableWhileDebuggerStopped()
+
+ if self._autoContinue:
+ self._timer = wx.PyTimer(self.DoContinue)
+ self._autoContinue = False
+ self._timer.Start(250)
if _VERBOSE: print "+"*40
-
+
+ def DoContinue(self):
+ self._timer.Stop()
+ dbgService = wx.GetApp().GetService(DebuggerService)
+ evt = DebugInternalWebServer()
+ evt.SetId(self._debuggerUI.CONTINUE_ID)
+ wx.PostEvent(self._debuggerUI, evt)
+ if _VERBOSE: print "Event Continue posted"
+
+ evt = DebugInternalWebServer()
+ evt.SetId(DebuggerService.DEBUG_WEBSERVER_NOW_RUN_PROJECT_ID)
+ wx.PostEvent(dbgService._frame, evt)
+ if _VERBOSE: print "Event RunProject posted"
+
+ def SendRunEvent(self):
+ class SendEventThread(threading.Thread):
+ def __init__(self):
+ threading.Thread.__init__(self)
+
+ def run(self):
+ dbgService = wx.GetApp().GetService(DebuggerService)
+ evt = DebugInternalWebServer()
+ evt.SetId(DebuggerService.DEBUG_WEBSERVER_NOW_RUN_PROJECT_ID)
+ wx.PostEvent(dbgService._frame, evt)
+ print "Event posted"
+ set = SendEventThread()
+ set.start()
+
class DebuggerService(Service.Service):
#----------------------------------------------------------------------------
CLEAR_ALL_BREAKPOINTS = wx.NewId()
RUN_ID = wx.NewId()
DEBUG_ID = wx.NewId()
+ RUN_LAST_ID = wx.NewId()
+ DEBUG_LAST_ID = wx.NewId()
DEBUG_WEBSERVER_ID = wx.NewId()
RUN_WEBSERVER_ID = wx.NewId()
-
+ DEBUG_WEBSERVER_CONTINUE_ID = wx.NewId()
+ DEBUG_WEBSERVER_NOW_RUN_PROJECT_ID = wx.NewId()
def ComparePaths(first, second):
one = DebuggerService.ExpandPath(first)
two = DebuggerService.ExpandPath(second)
else:
return one == two
ComparePaths = staticmethod(ComparePaths)
-
+
# Make sure we're using an expanded path on windows.
def ExpandPath(path):
if _WINDOWS:
except:
if _VERBOSE:
print "Cannot get long path for %s" % path
-
+
return path
-
+
ExpandPath = staticmethod(ExpandPath)
-
+
#----------------------------------------------------------------------------
# Overridden methods
#----------------------------------------------------------------------------
self._masterBPDict = {}
else:
self._masterBPDict = {}
-
+ self._frame = None
+ self.projectPath = None
+ self.fileToDebug = None
+ self.phpDbgParam = None
+ self.dbgLanguage = projectmodel.LANGUAGE_DEFAULT
+
def OnCloseFrame(self, event):
# IS THIS THE RIGHT PLACE?
try:
tp,val,tb = sys.exc_info()
traceback.print_exception(tp, val, tb)
return True
-
+
def _CreateView(self):
return DebuggerView(self)
def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
#Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
-
+ self._frame = frame
config = wx.ConfigBase_Get()
debuggerMenu = wx.Menu()
if not menuBar.FindItemById(DebuggerService.CLEAR_ALL_BREAKPOINTS):
-
+
debuggerMenu.Append(DebuggerService.RUN_ID, _("&Run...\tCtrl+R"), _("Runs a file"))
wx.EVT_MENU(frame, DebuggerService.RUN_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, DebuggerService.RUN_ID, frame.ProcessUpdateUIEvent)
-
+
debuggerMenu.Append(DebuggerService.DEBUG_ID, _("&Debug...\tCtrl+D"), _("Debugs a file"))
wx.EVT_MENU(frame, DebuggerService.DEBUG_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, DebuggerService.DEBUG_ID, frame.ProcessUpdateUIEvent)
-
+
+ debuggerMenu.Append(DebuggerService.RUN_LAST_ID, _("&Run Using Last Settings\tF5"), _("Runs a file using previous settings"))
+ wx.EVT_MENU(frame, DebuggerService.RUN_LAST_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, DebuggerService.RUN_LAST_ID, frame.ProcessUpdateUIEvent)
+
+ debuggerMenu.Append(DebuggerService.DEBUG_LAST_ID, _("&Debug Using Last Settings\tF8"), _("Debugs a file using previous settings"))
+ wx.EVT_MENU(frame, DebuggerService.DEBUG_LAST_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, DebuggerService.DEBUG_LAST_ID, frame.ProcessUpdateUIEvent)
+
if not ACTIVEGRID_BASE_IDE:
debuggerMenu.AppendSeparator()
debuggerMenu.Append(DebuggerService.DEBUG_WEBSERVER_ID, _("Debug Internal Web Server"), _("Debugs the internal webservier"))
debuggerMenu.Append(DebuggerService.RUN_WEBSERVER_ID, _("Restart Internal Web Server"), _("Restarts the internal webservier"))
wx.EVT_MENU(frame, DebuggerService.RUN_WEBSERVER_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, DebuggerService.RUN_WEBSERVER_ID, frame.ProcessUpdateUIEvent)
-
+
+ frame.Bind(EVT_DEBUG_INTERNAL, frame.ProcessEvent)
debuggerMenu.AppendSeparator()
-
- debuggerMenu.Append(DebuggerService.TOGGLE_BREAKPOINT_ID, _("&Toggle Breakpoint...\tCtrl+B"), _("Toggle a breakpoint"))
+
+ debuggerMenu.Append(DebuggerService.TOGGLE_BREAKPOINT_ID, _("&Toggle Breakpoint\tCtrl+B"), _("Toggle a breakpoint"))
wx.EVT_MENU(frame, DebuggerService.TOGGLE_BREAKPOINT_ID, self.ProcessEvent)
wx.EVT_UPDATE_UI(frame, DebuggerService.TOGGLE_BREAKPOINT_ID, self.ProcessUpdateUIEvent)
debuggerMenu.Append(DebuggerService.CLEAR_ALL_BREAKPOINTS, _("&Clear All Breakpoints"), _("Clear All Breakpoints"))
wx.EVT_MENU(frame, DebuggerService.CLEAR_ALL_BREAKPOINTS, self.ProcessEvent)
wx.EVT_UPDATE_UI(frame, DebuggerService.CLEAR_ALL_BREAKPOINTS, self.ProcessUpdateUIEvent)
-
-
+
+
viewMenuIndex = menuBar.FindMenu(_("&Project"))
menuBar.Insert(viewMenuIndex + 1, debuggerMenu, _("&Run"))
-
+
toolBar.AddSeparator()
- toolBar.AddTool(DebuggerService.RUN_ID, getRunningManBitmap(), shortHelpString = _("Run"), longHelpString = _("Run"))
- toolBar.AddTool(DebuggerService.DEBUG_ID, getDebuggingManBitmap(), shortHelpString = _("Debug"), longHelpString = _("Debug"))
+ toolBar.AddTool(DebuggerService.RUN_LAST_ID, getRunningManBitmap(), shortHelpString = _("Run Using Last Settings"), longHelpString = _("Run Using Last Settings"))
+ toolBar.AddTool(DebuggerService.DEBUG_LAST_ID, getDebuggingManBitmap(), shortHelpString = _("Debug Using Last Settings"), longHelpString = _("Debug Using Last Settings"))
toolBar.Realize()
return True
-
+
#----------------------------------------------------------------------------
# Event Processing Methods
#----------------------------------------------------------------------------
elif an_id == DebuggerService.DEBUG_ID:
self.OnDebugProject(event)
return True
+ elif an_id == DebuggerService.RUN_LAST_ID:
+ self.OnRunProject(event, showDialog=False)
+ return True
+ elif an_id == DebuggerService.DEBUG_LAST_ID:
+ self.OnDebugProject(event, showDialog=False)
+ return True
elif an_id == DebuggerService.DEBUG_WEBSERVER_ID:
self.OnDebugWebServer(event)
return True
+ elif an_id == DebuggerService.DEBUG_WEBSERVER_CONTINUE_ID:
+ self.OnDebugWebServerContinue(event)
+ return True
+ elif an_id == DebuggerService.DEBUG_WEBSERVER_NOW_RUN_PROJECT_ID:
+ self.WaitDebuggerThenRunProject()
+ return True
elif an_id == DebuggerService.RUN_WEBSERVER_ID:
self.OnRunWebServer(event)
return True
return False
-
+
def ProcessUpdateUIEvent(self, event):
if Service.Service.ProcessUpdateUIEvent(self, event):
return True
event.Enable(self.HasBreakpointsSet())
return True
elif (an_id == DebuggerService.RUN_ID
- or an_id == DebuggerService.DEBUG_ID):
+ or an_id == DebuggerService.RUN_LAST_ID
+ or an_id == DebuggerService.DEBUG_ID
+ or an_id == DebuggerService.DEBUG_LAST_ID):
event.Enable(self.HasAnyFiles())
return True
else:
#----------------------------------------------------------------------------
# Class Methods
- #----------------------------------------------------------------------------
-
- def OnDebugProject(self, event):
+ #----------------------------------------------------------------------------
+
+ def OnDebugProject(self, event, showDialog=True):
if _WINDOWS and not _PYWIN32_INSTALLED:
wx.MessageBox(_("Python for Windows extensions (pywin32) is required to debug on Windows machines. Please go to http://sourceforge.net/projects/pywin32/, download and install pywin32."))
return
if not Executor.GetPythonExecutablePath():
return
- if DebugCommandUI.DebuggerRunning():
+ if BaseDebuggerUI.DebuggerRunning():
wx.MessageBox(_("A debugger is already running. Please shut down the other debugger first."), _("Debugger Running"))
return
+ config = wx.ConfigBase_Get()
+ host = config.Read("DebuggerHostName", DEFAULT_HOST)
+ if not host:
+ wx.MessageBox(_("No debugger host set. Please go to Tools->Options->Debugger and set one."), _("No Debugger Host"))
+ return
+
self.ShowWindow(True)
projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
try:
- dlg = CommandPropertiesDialog(self.GetView().GetFrame(), 'Debug Python File', projectService, None, pythonOnly=True, okButtonName="Debug", debugging=True)
+ dlg = CommandPropertiesDialog(self.GetView().GetFrame(), 'Debug File', projectService, None, okButtonName="Debug", debugging=True)
except:
return
dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_OK:
+ if not showDialog:
+ showDialog = dlg.MustShowDialog()
+ if showDialog and dlg.ShowModal() == wx.ID_OK:
+ projectPath, fileToDebug, initialArgs, startIn, isPython, environment = dlg.GetSettings()
+ elif not showDialog:
projectPath, fileToDebug, initialArgs, startIn, isPython, environment = dlg.GetSettings()
- dlg.Destroy()
else:
dlg.Destroy()
return
+ dlg.Destroy()
self.PromptToSaveFiles()
-
shortFile = os.path.basename(fileToDebug)
fileToDebug = DebuggerService.ExpandPath(fileToDebug)
- try:
- page = DebugCommandUI(Service.ServiceView.bottomTab, -1, str(fileToDebug), self)
+ if fileToDebug.endswith('.bpel'):
+ self.projectPath = projectPath
+ self.fileToDebug = fileToDebug
+
+ #
+ # TODO: merge getting project stuff and save the results for
+ # WaitDebuggerThenRunProject() which currently does it again.
+ #
+ projects = projectService.FindProjectByFile(projectPath)
+ if not projects:
+ return
+ project = projects[0]
+ lang = project.GetAppInfo().language
+ if lang:
+ self.dbgLanguage = lang
+
+ dbgService = wx.GetApp().GetService(DebuggerService)
+
+ evt = DebugInternalWebServer()
+ evt.SetId(DebuggerService.DEBUG_WEBSERVER_CONTINUE_ID)
+ wx.PostEvent(dbgService._frame, evt)
+
+ if lang == projectmodel.LANGUAGE_PHP:
+ evt = DebugInternalWebServer()
+ evt.SetId(DebuggerService.DEBUG_WEBSERVER_NOW_RUN_PROJECT_ID)
+ wx.PostEvent(dbgService._frame, evt)
+
+ return
+
+ elif fileToDebug.endswith('.php'):
+ page = PHPDebuggerUI(Service.ServiceView.bottomTab, -1, str(fileToDebug), self)
count = Service.ServiceView.bottomTab.GetPageCount()
Service.ServiceView.bottomTab.AddPage(page, _("Debugging: ") + shortFile)
Service.ServiceView.bottomTab.SetSelection(count)
+
+ fullPhpScriptPath = os.path.normpath(fileToDebug)
+ environment["REDIRECT_STATUS"] = "200"
+ environment["REDIRECT_URL"] = fullPhpScriptPath
+ environment["SERVER_SOFTWARE"] = "AG PHP Debugger 1.7"
+ environment["SERVER_NAME"] = "localhost"
+ environment["SERVER_ADDR"] = "127.0.0.1"
+ environment["SERVER_PORT"] = "80"
+ environment["REMOTE_ADDR"] = "127.0.0.1"
+ environment["SCRIPT_FILENAME"] = "php"
+ environment["GATEWAY_INTERFACE"] = "CGI/1.1"
+ environment["SERVER_PROTOCOL"] = "HTTP/1.1"
+ environment["REQUEST_METHOD"] = "GET"
+ environment["REQUEST_URI"] = fullPhpScriptPath
+ environment["PATH_INFO"] = fullPhpScriptPath
+ environment["PATH_TRANSLATED"] = fullPhpScriptPath
+ environment["HTTP_COOKIE"] = "DBGSESSID=11439636363807700001@clienthost:10001"
+
page.Execute(initialArgs, startIn, environment)
- except:
- pass
-
- def OnDebugWebServer(self, event):
+ else:
+ try:
+ page = PythonDebuggerUI(Service.ServiceView.bottomTab, -1, str(fileToDebug), self)
+ count = Service.ServiceView.bottomTab.GetPageCount()
+ Service.ServiceView.bottomTab.AddPage(page, _("Debugging: ") + shortFile)
+ Service.ServiceView.bottomTab.SetSelection(count)
+ page.Execute(initialArgs, startIn, environment)
+ except:
+ pass
+
+ def WaitDebuggerThenRunProject(self):
+ import time
+ #while not BaseDebuggerUI.DebuggerPastAutoContinue():
+ # time.sleep(0.2)
+ # wx.GetApp().Yield(True)
+ # print "After Yield"
+ time.sleep(2.0)
+ projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+ projects = projectService.FindProjectByFile(self.projectPath)
+ if not projects:
+ return
+ project = projects[0]
+ try:
+ deployFilePath = project.GenerateDeployment()
+ except ProjectEditor.DataServiceExistenceException, e:
+ dataSourceName = str(e)
+ projectService.PromptForMissingDataSource(dataSourceName)
+ return
+ projectService.RunProcessModel(self.fileToDebug, project.GetAppInfo().language, deployFilePath)
+
+ def OnDebugWebServerContinue(self, event):
+ self.OnDebugWebServer(event, autoContinue=True)
+
+ def OnDebugWebServer(self, event, autoContinue=False):
+ #print "xxxxx debugging OnDebugWebServer"
if _WINDOWS and not _PYWIN32_INSTALLED:
wx.MessageBox(_("Python for Windows extensions (pywin32) is required to debug on Windows machines. Please go to http://sourceforge.net/projects/pywin32/, download and install pywin32."))
return
if not Executor.GetPythonExecutablePath():
return
- if DebugCommandUI.DebuggerRunning():
+ if BaseDebuggerUI.DebuggerRunning():
wx.MessageBox(_("A debugger is already running. Please shut down the other debugger first."), _("Debugger Running"))
return
import WebServerService
wsService = wx.GetApp().GetService(WebServerService.WebServerService)
fileName, args = wsService.StopAndPrepareToDebug()
+ #print "xxxxx OnDebugWebServer: fileName=%s, args=%s" % (repr(fileName), repr(args))
+ config = wx.ConfigBase_Get()
+ host = config.Read("DebuggerHostName", DEFAULT_HOST)
+ if not host:
+ wx.MessageBox(_("No debugger host set. Please go to Tools->Options->Debugger and set one."), _("No Debugger Host"))
+ return
try:
- page = DebugCommandUI(Service.ServiceView.bottomTab, -1, fileName, self)
+ if self.dbgLanguage == projectmodel.LANGUAGE_PHP:
+ page = PHPDebuggerUI(Service.ServiceView.bottomTab, -1, fileName, self)
+ else:
+ page = PythonDebuggerUI(Service.ServiceView.bottomTab, -1, fileName, self, autoContinue)
+
count = Service.ServiceView.bottomTab.GetPageCount()
Service.ServiceView.bottomTab.AddPage(page, _("Debugging: Internal WebServer"))
Service.ServiceView.bottomTab.SetSelection(count)
- page.Execute(args, startIn=os.getcwd(), environment=os.environ)
+ page.Execute(args, startIn=sysutilslib.mainModuleDir, environment=os.environ, onWebServer = True)
except:
pass
-
+
def OnRunWebServer(self, event):
if not Executor.GetPythonExecutablePath():
return
import WebServerService
wsService = wx.GetApp().GetService(WebServerService.WebServerService)
- wsService.ShutDownAndRestart()
-
+ wsService.ShutDownAndRestart()
+
def HasAnyFiles(self):
docs = wx.GetApp().GetDocumentManager().GetDocuments()
return len(docs) > 0
-
+
def PromptToSaveFiles(self, running=True):
filesModified = False
docs = wx.GetApp().GetDocumentManager().GetDocuments()
filesModified = True
break
if filesModified:
- frame = self.GetView().GetFrame()
- if running:
+ frame = self.GetView().GetFrame()
+ if running:
yesNoMsg = wx.MessageDialog(frame,
_("Files have been modified.\nWould you like to save all files before running?"),
_("Run"),
wx.YES_NO|wx.ICON_QUESTION
)
- else:
+ else:
yesNoMsg = wx.MessageDialog(frame,
_("Files have been modified.\nWould you like to save all files before debugging?"),
_("Debug"),
yesNoMsg.Destroy()
def OnExit(self):
- DebugCommandUI.ShutdownAllDebuggers()
+ BaseDebuggerUI.ShutdownAllDebuggers()
RunCommandUI.ShutdownAllRunners()
-
- def OnRunProject(self, event):
+
+ def OnRunProject(self, event, showDialog=True):
if _WINDOWS and not _PYWIN32_INSTALLED:
wx.MessageBox(_("Python for Windows extensions (pywin32) is required to run on Windows machines. Please go to http://sourceforge.net/projects/pywin32/, download and install pywin32."))
return
except:
return
dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_OK:
+ if not showDialog:
+ showDialog = dlg.MustShowDialog()
+ if showDialog and dlg.ShowModal() == wx.ID_OK:
+ projectPath, fileToRun, initialArgs, startIn, isPython, environment = dlg.GetSettings()
+ elif not showDialog:
projectPath, fileToRun, initialArgs, startIn, isPython, environment = dlg.GetSettings()
- dlg.Destroy()
else:
dlg.Destroy()
return
+ dlg.Destroy()
self.PromptToSaveFiles()
- # This will need to change when we can run more than .py and .bpel files.
- if not isPython:
+ if fileToRun.endswith('bpel'):
projects = projectService.FindProjectByFile(projectPath)
if not projects:
return
project = projects[0]
- deployFilePath = project.GenerateDeployment()
+ try:
+ deployFilePath = project.GenerateDeployment()
+ except ProjectEditor.DataServiceExistenceException, e:
+ dataSourceName = str(e)
+ projectService.PromptForMissingDataSource(dataSourceName)
+ return
projectService.RunProcessModel(fileToRun, project.GetAppInfo().language, deployFilePath)
return
-
+
self.ShowWindow(True)
shortFile = os.path.basename(fileToRun)
page = RunCommandUI(Service.ServiceView.bottomTab, -1, str(fileToRun))
count = Service.ServiceView.bottomTab.GetPageCount()
Service.ServiceView.bottomTab.AddPage(page, "Running: " + shortFile)
Service.ServiceView.bottomTab.SetSelection(count)
- page.Execute(initialArgs, startIn, environment)
+ page.Execute(initialArgs, startIn, environment, onWebServer = True)
def OnToggleBreakpoint(self, event, line=-1, fileName=None):
if not fileName:
view.GetCtrl().Refresh()
# Now refresh all the markers icons in all the open views.
self.ClearAllBreakpointMarkers()
- self.SetAllBreakpointMarkers()
-
+ self.SetAllBreakpointMarkers()
+
def SilentToggleBreakpoint(self, fileName, line):
found = False
for lineNumber in self.GetBreakpointList(fileName):
self.SetBreak(fileName, line)
else:
self.ClearBreak(fileName, line)
-
+
def SetBreak(self, fileName, line):
expandedName = DebuggerService.ExpandPath(fileName)
if not self._masterBPDict.has_key(expandedName):
self._masterBPDict[expandedName] = [line]
else:
self._masterBPDict[expandedName] += [line]
- # If we're already debugging, pass this bp off to the DebuggerCallback
+ # If we're already debugging, pass this bp off to the PythonDebuggerCallback
self.NotifyDebuggersOfBreakpointChange()
-
+
def NotifyDebuggersOfBreakpointChange(self):
- DebugCommandUI.NotifyDebuggersOfBreakpointChange()
-
+ BaseDebuggerUI.NotifyDebuggersOfBreakpointChange()
+
def GetBreakpointList(self, fileName):
expandedName = DebuggerService.ExpandPath(fileName)
if not self._masterBPDict.has_key(expandedName):
return []
else:
- return self._masterBPDict[expandedName]
+ return self._masterBPDict[expandedName]
def SetBreakpointList(self, fileName, bplist):
expandedName = DebuggerService.ExpandPath(fileName)
self._masterBPDict[expandedName] = bplist
-
+
def BreakpointSet(self, fileName, line):
expandedName = DebuggerService.ExpandPath(fileName)
if not self._masterBPDict.has_key(expandedName):
if(int(number) == int(line)):
return True
return False
-
+
def ClearBreak(self, fileName, line):
expandedName = DebuggerService.ExpandPath(fileName)
if not self._masterBPDict.has_key(expandedName):
newList.append(number)
self._masterBPDict[expandedName] = newList
self.NotifyDebuggersOfBreakpointChange()
-
+
def HasBreakpointsSet(self):
for key, value in self._masterBPDict.items():
if len(value) > 0:
return True
return False
-
+
def ClearAllBreakpoints(self):
self._masterBPDict = {}
self.NotifyDebuggersOfBreakpointChange()
self.ClearAllBreakpointMarkers()
-
+
def ClearAllBreakpointMarkers(self):
openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if isinstance(openDoc, CodeEditor.CodeDocument):
- openDoc.GetFirstView().MarkerDeleteAll(CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
-
+ for openDoc in openDocs:
+ if isinstance(openDoc, CodeEditor.CodeDocument):
+ openDoc.GetFirstView().MarkerDeleteAll(CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
+
def UpdateBreakpointsFromMarkers(self, view, fileName):
newbpLines = view.GetMarkerLines(CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
self.SetBreakpointList(fileName, newbpLines)
-
+
def GetMasterBreakpointDict(self):
return self._masterBPDict
-
+
def SetAllBreakpointMarkers(self):
openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if(isinstance(openDoc, CodeEditor.CodeDocument)):
+ for openDoc in openDocs:
+ if(isinstance(openDoc, CodeEditor.CodeDocument)):
self.SetCurrentBreakpointMarkers(openDoc.GetFirstView())
-
+
def SetCurrentBreakpointMarkers(self, view):
if isinstance(view, CodeEditor.CodeView) and hasattr(view, 'GetDocument'):
view.MarkerDeleteAll(CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
- for linenum in self.GetBreakpointList(view.GetDocument().GetFilename()):
+ for linenum in self.GetBreakpointList(view.GetDocument().GetFilename()):
view.MarkerAdd(lineNum=int(linenum) - 1, marker_index=CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
+ def GetPhpDbgParam(self):
+ return self.phpDbgParam
+
+ def SetPhpDbgParam(self, value = None):
+ self.phpDbgParam = value
+
class DebuggerOptionsPanel(wx.Panel):
localHostStaticText = wx.StaticText(self, -1, _("Local Host Name:"))
self._LocalHostTextCtrl = wx.TextCtrl(self, -1, config.Read("DebuggerHostName", DEFAULT_HOST), size = (150, -1))
portNumberStaticText = wx.StaticText(self, -1, _("Port Range:"))
- dashStaticText = wx.StaticText(self, -1, _("through to"))
+ dashStaticText = wx.StaticText(self, -1, _("through to"))
startingPort=config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
self._PortNumberTextCtrl = wx.lib.intctrl.IntCtrl(self, -1, startingPort, size = (50, -1))
self._PortNumberTextCtrl.SetMin(1)#What are real values?
self._flushPortsButton = wx.Button(self, FLUSH_PORTS_ID, "Reset Port List")
wx.EVT_BUTTON(parent, FLUSH_PORTS_ID, self.FlushPorts)
debuggerPanelSizer.Add(self._flushPortsButton, (2,2), (1,2), flag=wx.ALIGN_RIGHT)
-
+
debuggerPanelBorderSizer.Add(debuggerPanelSizer, 0, wx.ALL, SPACE)
self.SetSizer(debuggerPanelBorderSizer)
self.Layout()
parent.AddPage(self, _("Debugger"))
-
+
def FlushPorts(self, event):
if self._PortNumberTextCtrl.IsInBounds():
config = wx.ConfigBase_Get()
config.WriteInt("DebuggerStartingPort", self._PortNumberTextCtrl.GetValue())
- DebugCommandUI.NewPortRange()
+ PythonDebuggerUI.NewPortRange()
else:
wx.MessageBox(_("The starting port is not valid. Please change the value and try again.", "Invalid Starting Port Number"))
-
+
def MinPortChange(self, event):
self._EndPortNumberTextCtrl.Enable( True )
self._EndPortNumberTextCtrl.SetValue( self._PortNumberTextCtrl.GetValue() + PORT_COUNT)
self._EndPortNumberTextCtrl.Enable( False )
-
+
def OnOK(self, optionsDialog):
config = wx.ConfigBase_Get()
config.Write("DebuggerHostName", self._LocalHostTextCtrl.GetValue())
if self._PortNumberTextCtrl.IsInBounds():
config.WriteInt("DebuggerStartingPort", self._PortNumberTextCtrl.GetValue())
-
+
def GetIcon(self):
return getContinueIcon()
class CommandPropertiesDialog(wx.Dialog):
-
- def __init__(self, parent, title, projectService, currentProjectDocument, pythonOnly=False, okButtonName="Run", debugging=False):
+ def __init__(self, parent, title, projectService, currentProjectDocument, okButtonName="Run", debugging=False):
self._projService = projectService
self._pmext = None
- self._pyext = None
+ self._pyext = '.py'
+ self._phpext = '.php'
for template in self._projService.GetDocumentManager().GetTemplates():
if not ACTIVEGRID_BASE_IDE and template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument:
self._pmext = template.GetDefaultExtension()
- if template.GetDocumentType() == PythonEditor.PythonDocument:
- self._pyext = template.GetDefaultExtension()
- self._pythonOnly = pythonOnly
- self._currentProj = currentProjectDocument
+ break
+ self._currentProj = projectService.GetCurrentProject()
self._projectNameList, self._projectDocumentList, selectedIndex = self.GetProjectList()
if not self._projectNameList:
wx.MessageBox(_("To run or debug you must have an open runnable file or project containing runnable files. Use File->Open to open the file you wish to run or debug."), _("Nothing to Run"))
- raise BadBadBad
+ raise Exception("Nothing to Run or Debug.")
wx.Dialog.__init__(self, parent, -1, title)
-
- projStaticText = wx.StaticText(self, -1, _("Project:"))
- fileStaticText = wx.StaticText(self, -1, _("File:"))
- argsStaticText = wx.StaticText(self, -1, _("Arguments:"))
+
+ projStaticText = wx.StaticText(self, -1, _("Project:"))
+ fileStaticText = wx.StaticText(self, -1, _("File:"))
+ argsStaticText = wx.StaticText(self, -1, _("Arguments:"))
startInStaticText = wx.StaticText(self, -1, _("Start in:"))
pythonPathStaticText = wx.StaticText(self, -1, _("PYTHONPATH:"))
postpendStaticText = _("Postpend win32api path")
if wx.Platform == "__WXMAC__":
GAP = 10
flexGridSizer = wx.GridBagSizer(GAP, GAP)
-
+
flexGridSizer.Add(projStaticText, (0,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
flexGridSizer.Add(self._projList, (0,1), (1,2), flag=wx.EXPAND)
-
+
flexGridSizer.Add(fileStaticText, (1,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
self._fileList = wx.Choice(self, -1)
self.Bind(wx.EVT_CHOICE, self.OnFileSelected, self._fileList)
flexGridSizer.Add(self._fileList, (1,1), (1,2), flag=wx.EXPAND)
-
+
config = wx.ConfigBase_Get()
- self._lastArguments = config.Read("LastRunArguments")
+ self._lastArguments = config.Read(self.GetKey("LastRunArguments"))
self._argsEntry = wx.TextCtrl(self, -1, str(self._lastArguments))
self._argsEntry.SetToolTipString(str(self._lastArguments))
flexGridSizer.Add(argsStaticText, (2,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
flexGridSizer.Add(self._argsEntry, (2,1), (1,2), flag=wx.EXPAND)
-
+
flexGridSizer.Add(startInStaticText, (3,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
- self._lastStartIn = config.Read("LastRunStartIn")
+ self._lastStartIn = config.Read(self.GetKey("LastRunStartIn"))
if not self._lastStartIn:
self._lastStartIn = str(os.getcwd())
self._startEntry = wx.TextCtrl(self, -1, self._lastStartIn)
self._findDir = wx.Button(self, -1, _("Browse..."))
self.Bind(wx.EVT_BUTTON, self.OnFindDirClick, self._findDir)
flexGridSizer.Add(self._findDir, (3,2))
-
+
flexGridSizer.Add(pythonPathStaticText, (4,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
if os.environ.has_key('PYTHONPATH'):
startval = os.environ['PYTHONPATH']
else:
startval = ""
- self._lastPythonPath = config.Read("LastPythonPath", startval)
+ self._lastPythonPath = config.Read(self.GetKey("LastPythonPath"), startval)
self._pythonPathEntry = wx.TextCtrl(self, -1, self._lastPythonPath)
self._pythonPathEntry.SetToolTipString(self._lastPythonPath)
flexGridSizer.Add(self._pythonPathEntry, (4,1), (1,2), flag=wx.EXPAND)
-
+
if debugging and _WINDOWS:
self._postpendCheckBox = wx.CheckBox(self, -1, postpendStaticText)
- checked = bool(config.ReadInt("PythonPathPostpend", 1))
+ checked = bool(config.ReadInt(self.GetKey("PythonPathPostpend"), 1))
self._postpendCheckBox.SetValue(checked)
flexGridSizer.Add(self._postpendCheckBox, (5,1), flag=wx.EXPAND)
cpPanelBorderSizer.Add(flexGridSizer, 0, flag=wx.ALL, border=10)
-
+
box = wx.StdDialogButtonSizer()
self._okButton = wx.Button(self, wx.ID_OK, okButtonName)
self._okButton.SetDefault()
box.AddButton(btn)
box.Realize()
cpPanelBorderSizer.Add(box, 0, flag=wx.ALIGN_RIGHT|wx.ALL, border=5)
-
+
self.SetSizer(cpPanelBorderSizer)
-
+
# Set up selections based on last values used.
self._fileNameList = None
- self._selectedFileIndex = 0
- lastProject = config.Read("LastRunProject")
- lastFile = config.Read("LastRunFile")
+ self._selectedFileIndex = -1
+ lastProject = config.Read(self.GetKey("LastRunProject"))
+ lastFile = config.Read(self.GetKey("LastRunFile"))
+ self._mustShow = not lastFile
if lastProject in self._projectNameList:
selectedIndex = self._projectNameList.index(lastProject)
cpPanelBorderSizer.Fit(self)
-
+ def MustShowDialog(self):
+ return self._mustShow
+
+ def GetKey(self, lastPart):
+ if self._currentProj:
+ return "%s/%s/%s" % (ProjectEditor.PROJECT_KEY, self._currentProj.GetFilename().replace(os.sep, '|'), lastPart)
+
+
def OnOKClick(self, event):
startIn = self._startEntry.GetValue()
fileToRun = self._fileList.GetStringSelection()
wx.MessageBox(_("Starting directory does not exist. Please change this value."))
return
config = wx.ConfigBase_Get()
- config.Write("LastRunProject", self._projectNameList[self._selectedProjectIndex])
- config.Write("LastRunFile", fileToRun)
+ config.Write(self.GetKey("LastRunProject"), self._projectNameList[self._selectedProjectIndex])
+ config.Write(self.GetKey("LastRunFile"), fileToRun)
# Don't update the arguments or starting directory unless we're runing python.
if isPython:
- config.Write("LastRunArguments", self._argsEntry.GetValue())
- config.Write("LastRunStartIn", self._startEntry.GetValue())
- config.Write("LastPythonPath",self._pythonPathEntry.GetValue())
+ config.Write(self.GetKey("LastRunArguments"), self._argsEntry.GetValue())
+ config.Write(self.GetKey("LastRunStartIn"), self._startEntry.GetValue())
+ config.Write(self.GetKey("LastPythonPath"),self._pythonPathEntry.GetValue())
if hasattr(self, "_postpendCheckBox"):
- config.WriteInt("PythonPathPostpend", int(self._postpendCheckBox.GetValue()))
+ config.WriteInt(self.GetKey("PythonPathPostpend"), int(self._postpendCheckBox.GetValue()))
self.EndModal(wx.ID_OK)
-
- def GetSettings(self):
+
+ def GetSettings(self):
projectPath = self._selectedProjectDocument.GetFilename()
filename = self._fileNameList[self._selectedFileIndex]
- args = self._argsEntry.GetValue()
+ args = self._argsEntry.GetValue()
startIn = self._startEntry.GetValue()
isPython = filename.endswith(self._pyext)
env = os.environ
env['PYTHONPATH'] = self._pythonPathEntry.GetValue() + os.pathsep + os.path.join(os.getcwd(), "3rdparty", "pywin32")
else:
env['PYTHONPATH'] = self._pythonPathEntry.GetValue()
-
+
return projectPath, filename, args, startIn, isPython, env
-
+
def OnFileSelected(self, event):
self._selectedFileIndex = self._fileList.GetSelection()
self.EnableForFileType(event.GetString())
-
+
def EnableForFileType(self, fileName):
- show = fileName.endswith(self._pyext)
+ show = fileName.endswith(self._pyext) or fileName.endswith(self._phpext)
self._startEntry.Enable(show)
self._findDir.Enable(show)
self._argsEntry.Enable(show)
self._lastStartIn = self._startEntry.GetValue()
self._startEntry.SetValue("")
self._lastArguments = self._argsEntry.GetValue()
- self._argsEntry.SetValue("")
+ self._argsEntry.SetValue("")
else:
- self._startEntry.SetValue(self._lastStartIn)
- self._argsEntry.SetValue(self._lastArguments)
-
-
+ if fileName.endswith(self._phpext):
+ self._startEntry.SetValue(os.path.dirname(fileName))
+ else:
+ self._startEntry.SetValue(self._lastStartIn)
+ self._argsEntry.SetValue(self._lastArguments)
+
+
+
def OnFindDirClick(self, event):
dlg = wx.DirDialog(self, "Choose a starting directory:", self._startEntry.GetValue(),
style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON)
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
self._startEntry.SetValue(dlg.GetPath())
-
- dlg.Destroy()
-
+
+ dlg.Destroy()
+
def EvtListBox(self, event):
if event.GetString():
self._selectedProjectDocument = self._projectDocumentList[index]
self._selectedProjectIndex = index
self.PopulateFileList(self._selectedProjectDocument)
-
+
def FilterFileList(self, list):
- if self._pythonOnly:
- files = filter(lambda f: f.endswith(self._pyext), list)
- else:
- files = filter(lambda f: (self._pmext and f.endswith(self._pmext)) or f.endswith(self._pyext), list)
+ files = filter(lambda f: (self._phpext and f.endswith(self._phpext)) or (self._pmext and f.endswith(self._pmext)) or f.endswith(self._pyext), list)
return files
-
+
def PopulateFileList(self, project, shortNameToSelect=None):
self._fileNameList = self.FilterFileList(project.GetFiles()[:])
self._fileList.Clear()
strings = map(lambda file: os.path.basename(file), self._fileNameList)
for index in range(0, len(strings)):
if shortNameToSelect == strings[index]:
- self._selectedFileIndex = index
- break
+ self._selectedFileIndex = index
+ break
self._fileList.Hide()
self._fileList.AppendItems(strings)
self._fileList.Show()
- if self._selectedFileIndex not in range(0, len(strings)) : self._selectedFileIndex = 0
+ if self._selectedFileIndex not in range(0, len(strings)):
+ # Pick first bpel file if there is one.
+ for index in range(0, len(strings)):
+ if strings[index].endswith('.bpel'):
+ self._selectedFileIndex = index
+ break
+ # Still no selected file, use first file.
+ if self._selectedFileIndex not in range(0, len(strings)):
+ self._selectedFileIndex = 0
self._fileList.SetSelection(self._selectedFileIndex)
self.EnableForFileType(strings[self._selectedFileIndex])
-
+
def GetProjectList(self):
docList = []
nameList = []
if projectDocument.IsFileInProject(fileName):
return True
return False
-
+
unprojectedFiles = []
for document in self._projService.GetDocumentManager().GetDocuments():
if not ACTIVEGRID_BASE_IDE and type(document) == ProcessModelEditor.ProcessModelDocument:
if not AlreadyInProject(document.GetFilename()):
unprojectedFiles.append(document.GetFilename())
- if type(document) == PythonEditor.PythonDocument:
+ if type(document) == PythonEditor.PythonDocument or type(document) == PHPEditor.PHPDocument:
if not AlreadyInProject(document.GetFilename()):
unprojectedFiles.append(document.GetFilename())
-
+
if unprojectedFiles:
unprojProj = ProjectEditor.ProjectDocument()
- unprojProj.SetFilename(_("Not in any Project"))
+ unprojProj.SetFilename(_("Not in any Project"))
unprojProj.AddFiles(unprojectedFiles)
docList.append(unprojProj)
- nameList.append(_("Not in any Project"))
-
+ nameList.append(_("Not in any Project"))
+
return nameList, docList, index
-
+
#----------------------------------------------------------------------
from wx import ImageFromStream, BitmapFromImage
\x03\x98\xa9z\x07\x00%\xd6\xa9\xd27\x90\xac\xbbk\xe5\x15I\xcdD$\xdc\xa7\xceT\
5a\xce\xf3\xe4\xa0\xaa\x8bO\x12\x11\xabC\xcb\x9c}\xd57\xef\xb0\xf3\xb7\x86p\
\x97\xf7\xb5\xaa\xde\xb9\xfa|-O\xbdjN\x9b\xf8\x06A\xcb\x00\x00\x00\x00IEND\
-\xaeB`\x82'
+\xaeB`\x82'
def getBreakBitmap():
return BitmapFromImage(getBreakImage())
\xda\xa7\x92\xfb\xc5w\xdf\t\x07\xc4\x05ym{\xd0\x1a\xe3\xb9xS\x81\x04\x18\x05\
\xc9\x04\xc9a\x00Dc9\x9d\x82\xa4\xbc\xe8P\xb2\xb5P\xac\xf2\x0c\xd4\xf5\x00\
\x88>\xac\xe17\x84\xe4\xb9G\x8b7\x9f\xf3\x1fsUl^\x7f\xe7y\x0f\x00\x00\x00\
-\x00IEND\xaeB`\x82'
+\x00IEND\xaeB`\x82'
def getClearOutputBitmap():
return BitmapFromImage(getClearOutputImage())
\x08!\x84\x90y\x9e\x11\xf1\x8bP\x96\xa5\xef\xdd\xb6\xad\xb5VJ\xf9\x9b\xe0\
\xe9\xa6i8\xe7\xbe\xdb\xb6mi\x9a\x0e\xc3\xf0F\x88\xe3\x18\x00\xfa\xbe\x0f\
\xc3\xd0\'\x9c\xf3eY\xa2(*\x8ab\xc7\x9e\xaed\x8c\xa1\x94\xben\xf5\xb1\xd2W\
-\xfa,\xfce.\x0b\x0f\xb8\x96e\x90gS\xe0v\x00\x00\x00\x00IEND\xaeB`\x82'
+\xfa,\xfce.\x0b\x0f\xb8\x96e\x90gS\xe0v\x00\x00\x00\x00IEND\xaeB`\x82'
def getCloseBitmap():
return BitmapFromImage(getCloseImage())
\x98\xc4\x07\x01\x00D\x1dd^\xa8\xa8j\x9ew\xed`\xa9\x16\x99\xde\xa6G\x8b\xd3Y\
\xe6\x85]\n\r\x7f\x99\xf5\x96Jnlz#\xab\xdb\xc1\x17\x19\xb0XV\xc2\xdf\xa3)\
\x85<\xe4\x88\x85.F\x9a\xf3H3\xb0\xf3g\xda\xd2\x0b\xc5_|\x17\xe8\xf5R\xd6\
-\x00\x00\x00\x00IEND\xaeB`\x82'
+\x00\x00\x00\x00IEND\xaeB`\x82'
def getContinueBitmap():
return BitmapFromImage(getContinueImage())
\x1c\x98\xae\x82\xbf\x81\xa4\x8eA\x16\xe1\n\xd1\xa4\x19\xb3\xe9\n\xce\xe8\
\xf1\n\x9eg^\x18\x18\x90\xec<\x11\xf9#\x04XMZ\x19\xaac@+\x94\xd4\x99)SeP\xa1\
)\xd6\x1dI\xe7*\xdc\xf4\x03\xdf~\xe7\x13T^Q?:X\x19d\x00\x00\x00\x00IEND\xaeB\
-`\x82'
+`\x82'
def getNextBitmap():
return BitmapFromImage(getNextImage())
\x96\xd9#\xf6\x06\xc3;p1I\xd1\x14\x0b#|\x17aF\xec\r\xeeF\xa0eB\xd34\xca\xd0A\
]j\x84\xa6\x03\x00""\xb7\xb0tRZ\xf7x\xb7\x83\x91]\xcb\x7fa\xd9\x89\x0fC\xfd\
\x94\x9d|9\x99^k\x13\xa1 \xb3\x16\x0f#\xd4\x88N~\x14\xe1-\x96\x7f\xe3\x0f\
-\x11\x91UC\x0cX\'\x1e\x00\x00\x00\x00IEND\xaeB`\x82'
+\x11\x91UC\x0cX\'\x1e\x00\x00\x00\x00IEND\xaeB`\x82'
def getStepInBitmap():
return BitmapFromImage(getStepInImage())
\x00\x00QIDAT8\x8d\xdd\x93A\n\xc00\x08\x04g\xb5\xff\x7fq\x13sn\xda&\x01\x0b\
\xa5]\xf0"\xec(.J\xe6dd)\xf7\x13\x80\xadoD-12\xc8\\\xd3\r\xe2\xa6\x00j\xd9\
\x0f\x03\xde\xbf\xc1\x0f\x00\xa7\x18\x01t\xd5\\\x05\xc8\\}T#\xe9\xfb\xbf\x90\
-\x064\xd8\\\x12\x1fQM\xf5\xd9\x00\x00\x00\x00IEND\xaeB`\x82'
+\x064\xd8\\\x12\x1fQM\xf5\xd9\x00\x00\x00\x00IEND\xaeB`\x82'
def getStopBitmap():
return BitmapFromImage(getStopImage())
\x84\xbf\x16V\xb0l\x01@\no\x86\xae\x82Q\xa8=\xa4\x0c\x80\xe70\xbd\x10jh\xbd\
\x07R\x06#\xc9^N\xb6\xde\x03)\x83\x18\xaeU\x90\x9c>a\xb2P\r\xb3&/Y\xa8\xd1^^\
\xb6\xf0\x16\xdb\xbf\xf1\x02\x81\xa5TK\x1d\x07\xde\x92\x00\x00\x00\x00IEND\
-\xaeB`\x82"
+\xaeB`\x82"
def getStepReturnBitmap():
return BitmapFromImage(getStepReturnImage())
def getStepReturnIcon():
return wx.IconFromBitmap(getStepReturnBitmap())
-
+
#----------------------------------------------------------------------
def getAddWatchData():
return \
\xeb,!w\x100 \x1dK\xac\x10\r\x08\x05".yFL\x85\x8c\x18b\xa8|Ty\xa2\x13\x92\'\
\xc3\xe4\xff\x9f\x18\x1e3\xb82t\xa2\x88\x13\xedg.\x06aa&\x06VV\x7f\x86\xb9\
\xcfU\x19\xbc\xb0\xba\x86h\xe0\xc8\xd0\xfc\xbf\x80\xe1>q)\x94\xe6\x00\x00\
-\x85\x923_\xd22\xa4\xcd\x00\x00\x00\x00IEND\xaeB`\x82'
+\x85\x923_\xd22\xa4\xcd\x00\x00\x00\x00IEND\xaeB`\x82'
def getAddWatchBitmap():
return BitmapFromImage(getAddWatchImage())
def getAddWatchIcon():
return wx.IconFromBitmap(getAddWatchBitmap())
-
+
#----------------------------------------------------------------------
def getRunningManData():
return \
\x03[\xb0.XP\xcafu^m\x04>\x18\xd7\x9aM\xe4\xea\xba\xc0x\xec\x8c\xa9\xca*^\
\xa5\x1b}\xc0u*\xc9B\xd14\x12\xe8\x97%\x15\xcbF`\xdaH\xba\x80P4\r)\x13#R\xc6\
\xf0\xdc\x8f2\x01\x80\x94\x89\xe9>\xc9(\xcd:\xb6\xd9\x1aw\xa0\x95i\xf8\x0e\
-\xc6\xd1\'\'\x86\xa2\xd5\x8d \xbe@\x00\x00\x00\x00IEND\xaeB`\x82'
+\xc6\xd1\'\'\x86\xa2\xd5\x8d \xbe@\x00\x00\x00\x00IEND\xaeB`\x82'
def getRunningManBitmap():
return BitmapFromImage(getRunningManImage())
\x8d\xa9\x9cf\x1dq\x9au\xc4\x8dM\x0c\x85#\xa2x\x91cw\xd2\xd6i\x83\trk\x13\
\x9f\x0fL\xab\xda\xe6\xd4\xd6Y+\xf1h\x8f\xb9T~G\xd2\x11\xb4\xd4\xe7O[\xf7\
\x1e\xd6\x9d\xc7\xe4\xb7\xbe\x86\xf8\xb1?\xf4\x9c\xff\x01\xbe\xe9\xaf\x96\
-\xf0\x7fPA\x00\x00\x00\x00IEND\xaeB`\x82'
+\xf0\x7fPA\x00\x00\x00\x00IEND\xaeB`\x82'
def getDebuggingManBitmap():
return BitmapFromImage(getDebuggingManImage())
#
# Created: 5/23/05
# CVS-ID: $ID:$
-# Copyright: (c) 2005 ActiveGrid, Inc.
+# Copyright: (c) 2005-2006 ActiveGrid, Inc.
# License: wxWindows License
#----------------------------------------------------------------------------
import os
import os.path
import activegrid.util.xmlutils as xmlutils
-
_ = wx.GetTranslation
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
SPACE = 10
HALF_SPACE = 5
-EXTENSIONS_CONFIG_STRING = "Extensions"
-
-
-
-# TODO: Redo extensions menu on OK, or provide alert that it won't happen until restart
-
-
#----------------------------------------------------------------------------
# Classes
#----------------------------------------------------------------------------
class Extension:
-
+
def __init__(self, menuItemName=None):
self.menuItemName = menuItemName
self.commandPostArgs = ''
self.fileExt = None
self.opOnSelectedFile = True
-
+
class ExtensionService(wx.lib.pydocview.DocService):
def __getExtensionKeyName(extensionName):
return "%s/%s" % (ExtensionService.EXTENSIONS_KEY, extensionName)
-
-
+
+
__getExtensionKeyName = staticmethod(__getExtensionKeyName)
cont, value, index = config.GetNextEntry(index)
finally:
config.SetPath(path)
-
+
for extensionName in extensionNames:
extensionData = config.Read(self.__getExtensionKeyName(extensionName))
if extensionData:
toolsMenu = menuBar.GetMenu(toolsMenuIndex)
else:
toolsMenu = wx.Menu()
-
+
if self._extensions:
if toolsMenu.GetMenuItems():
- toolsMenu.AppendSeparator()
+ toolsMenu.AppendSeparator()
for ext in self._extensions:
# Append a tool menu item for each extension
ext.id = wx.NewId()
if extension.commandPostArgs:
cmds.append(extension.commandPostArgs)
os.spawnv(os.P_NOWAIT, extension.command, cmds)
-
+
else:
cmd = extension.command
if extension.commandPreArgs:
view.AddLines(line)
view.GetControl().EnsureCaretVisible()
f.close()
-
+
class ExtensionOptionsPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
-
+
extOptionsPanelBorderSizer = wx.BoxSizer(wx.VERTICAL)
-
+
extOptionsPanelSizer = wx.BoxSizer(wx.HORIZONTAL)
-
+
extCtrlSizer = wx.BoxSizer(wx.VERTICAL)
extCtrlSizer.Add(wx.StaticText(self, -1, _("External Tools:")), 0, wx.BOTTOM, HALF_SPACE)
- self._extListBox = wx.ListBox(self, -1, size=(-1,160), style=wx.LB_SINGLE)
+ self._extListBox = wx.ListBox(self, -1, style=wx.LB_SINGLE)
self.Bind(wx.EVT_LISTBOX, self.OnListBoxSelect, self._extListBox)
- extCtrlSizer.Add(self._extListBox, 1, wx.BOTTOM | wx.EXPAND, SPACE)
- buttonSizer = wx.GridSizer(cols=2, vgap=5, hgap=10)
+ extCtrlSizer.Add(self._extListBox, 1, wx.BOTTOM | wx.EXPAND, SPACE)
+ buttonSizer = wx.GridSizer(cols=2, vgap=HALF_SPACE, hgap=HALF_SPACE)
self._moveUpButton = wx.Button(self, -1, _("Move Up"))
self.Bind(wx.EVT_BUTTON, self.OnMoveUp, self._moveUpButton)
buttonSizer.Add(self._moveUpButton, 1, wx.EXPAND)
self._extDetailPanel = wx.Panel(self)
staticBox = wx.StaticBox(self, label=_("Selected External Tool"))
staticBoxSizer = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
-
- extDetailSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=3)
+
+ extDetailSizer = wx.FlexGridSizer(cols=2, vgap=5, hgap=5)
extDetailSizer.AddGrowableCol(1,1)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Name:")))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Name:")), flag=wx.ALIGN_CENTER_VERTICAL)
self._menuItemNameTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
extDetailSizer.Add(self._menuItemNameTextCtrl, 0, wx.EXPAND)
- self.Bind(wx.EVT_TEXT, self.SaveCurrentItem, self._menuItemNameTextCtrl)
+ self.Bind(wx.EVT_TEXT, self.SaveCurrentItem, self._menuItemNameTextCtrl)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Description:")))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Menu Item Description:")), flag=wx.ALIGN_CENTER_VERTICAL)
self._menuItemDescTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
extDetailSizer.Add(self._menuItemDescTextCtrl, 0, wx.EXPAND)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Path:")))
- self._commandTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Path:")), flag=wx.ALIGN_CENTER_VERTICAL)
+ self._commandTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
findFileButton = wx.Button(self._extDetailPanel, -1, _("Browse..."))
def OnBrowseButton(event):
fileDlg = wx.FileDialog(self, _("Choose an Executable:"), style=wx.OPEN|wx.FILE_MUST_EXIST|wx.HIDE_READONLY|wx.CHANGE_DIR)
hsizer.Add(findFileButton, 0, wx.LEFT, HALF_SPACE)
extDetailSizer.Add(hsizer, 0, wx.EXPAND)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Pre Args:")))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Pre Args:")), flag=wx.ALIGN_CENTER_VERTICAL)
self._commandPreArgsTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
extDetailSizer.Add(self._commandPreArgsTextCtrl, 0, wx.EXPAND)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Post Args:")))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("Command Post Args:")), flag=wx.ALIGN_CENTER_VERTICAL)
self._commandPostArgsTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
extDetailSizer.Add(self._commandPostArgsTextCtrl, 0, wx.EXPAND)
- extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("File Extensions:")))
+ extDetailSizer.Add(wx.StaticText(self._extDetailPanel, -1, _("File Extensions:")), flag=wx.ALIGN_CENTER_VERTICAL)
self._fileExtTextCtrl = wx.TextCtrl(self._extDetailPanel, -1, size = (-1, -1))
self._fileExtTextCtrl.SetToolTipString(_("""For example: "txt, text" (comma separated) or "*" for all files"""))
extDetailSizer.Add(self._fileExtTextCtrl, 0, wx.EXPAND)
self._selFileCtrl = wx.CheckBox(self._extDetailPanel, -1, _("Operate on Selected File"))
- extDetailSizer.Add(self._selFileCtrl)
+ extDetailSizer.Add(self._selFileCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE)
self._selFileCtrl.SetToolTipString(_("If focus is in the project, instead of operating on the project file, operate on the selected file."))
self._extDetailPanel.SetSizer(extDetailSizer)
staticBoxSizer.Add(self._extDetailPanel, 1, wx.ALL|wx.EXPAND, SPACE)
-
+
extOptionsPanelSizer.Add(staticBoxSizer, 1, wx.LEFT|wx.EXPAND, SPACE)
extOptionsPanelBorderSizer.Add(extOptionsPanelSizer, 1, wx.ALL|wx.EXPAND, SPACE)
self.OnListBoxSelect()
self.Layout()
-
- parent.AddPage(self, _("External Tools"))
+
+ parent.AddPage(self, _("External Tools"))
def OnOK(self, optionsDialog):
msgTitle,
wx.OK | wx.ICON_INFORMATION,
self.GetParent())
-
+
def PopulateItems(self):
extensionsService = wx.GetApp().GetService(ExtensionService)
self._currentItem = None
self._currentItemIndex = -1
return len(self._extensions)
-
+
def OnListBoxSelect(self, event=None):
self.SaveCurrentItem()
else:
extension.fileExt = fileExt.split(',')
extension.opOnSelectedFile = self._selFileCtrl.GetValue()
-
+
def LoadItem(self, extension):
if extension:
self._fileExtTextCtrl.SetValue('')
self._selFileCtrl.SetValue(True)
self._extDetailPanel.Enable(False)
-
-
+
+
def OnAdd(self, event):
self.SaveCurrentItem()
name = _("Untitled")
self.OnListBoxSelect()
self._menuItemNameTextCtrl.SetFocus()
self._menuItemNameTextCtrl.SetSelection(-1, -1)
-
+
def OnDelete(self, event):
self._extListBox.Delete(self._currentItemIndex)
- self._extensions.remove(self._currentItem)
+ self._extensions.remove(self._currentItem)
self._currentItemIndex = min(self._currentItemIndex, self._extListBox.GetCount() - 1)
if self._currentItemIndex > -1:
self._extListBox.SetSelection(self._currentItemIndex)
#----------------------------------------------------------------------------
FILENAME_MARKER = _("Found in file: ")
PROJECT_MARKER = _("Searching project: ")
+FILE_MARKER = _("Searching file: ")
FIND_MATCHDIR = "FindMatchDir"
FIND_MATCHDIRSUBFOLDERS = "FindMatchDirSubfolders"
#----------------------------------------------------------------------------
# Constants
#----------------------------------------------------------------------------
+ FINDFILE_ID = wx.NewId() # for bringing up Find in File dialog box
FINDALL_ID = wx.NewId() # for bringing up Find All dialog box
FINDDIR_ID = wx.NewId() # for bringing up Find Dir dialog box
FindService.FindService.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+ wx.EVT_MENU(frame, FindInDirService.FINDFILE_ID, self.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, FindInDirService.FINDFILE_ID, self.ProcessUpdateUIEvent)
+ editMenu.Append(FindInDirService.FINDFILE_ID, _("Find in File...\tCtrl+Shift+F"), _("Searches for the specified text in the current file"))
wx.EVT_MENU(frame, FindInDirService.FINDALL_ID, self.ProcessEvent)
wx.EVT_UPDATE_UI(frame, FindInDirService.FINDALL_ID, self.ProcessUpdateUIEvent)
- editMenu.Append(FindInDirService.FINDALL_ID, _("Find in Project...\tCtrl+Shift+F"), _("Searches for the specified text in all the files in the project"))
+ editMenu.Append(FindInDirService.FINDALL_ID, _("Find in Project...\tCtrl+Shift+P"), _("Searches for the specified text in all the files in the project"))
wx.EVT_MENU(frame, FindInDirService.FINDDIR_ID, self.ProcessEvent)
wx.EVT_UPDATE_UI(frame, FindInDirService.FINDDIR_ID, self.ProcessUpdateUIEvent)
- editMenu.Append(FindInDirService.FINDDIR_ID, _("Find in Directory..."), _("Searches for the specified text in all the files in the directory"))
+ editMenu.Append(FindInDirService.FINDDIR_ID, _("Find in Directory...\tCtrl+Shift+D"), _("Searches for the specified text in all the files in the directory"))
def ProcessEvent(self, event):
id = event.GetId()
- if id == FindInDirService.FINDALL_ID:
+ if id == FindInDirService.FINDFILE_ID:
view = wx.GetApp().GetDocumentManager().GetCurrentView()
if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
- self.ShowFindAllDialog(view.GetCtrl().GetSelectedText())
+ self.ShowFindInFileDialog(view.GetCtrl().GetSelectedText())
else:
- self.ShowFindAllDialog()
+ self.ShowFindInFileDialog()
+ return True
+ elif id == FindInDirService.FINDALL_ID:
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
+ self.ShowFindInProjectDialog(view.GetCtrl().GetSelectedText())
+ else:
+ self.ShowFindInProjectDialog()
return True
elif id == FindInDirService.FINDDIR_ID:
view = wx.GetApp().GetDocumentManager().GetCurrentView()
if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
- self.ShowFindDirDialog(view.GetCtrl().GetSelectedText())
+ self.ShowFindInDirDialog(view.GetCtrl().GetSelectedText())
else:
- self.ShowFindDirDialog()
+ self.ShowFindInDirDialog()
return True
else:
return FindService.FindService.ProcessEvent(self, event)
def ProcessUpdateUIEvent(self, event):
id = event.GetId()
- if id == FindInDirService.FINDALL_ID:
+ if id == FindInDirService.FINDFILE_ID:
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if view and view.GetDocument() and not isinstance(view.GetDocument(), ProjectEditor.ProjectDocument): # don't search project model
+ event.Enable(True)
+ else:
+ event.Enable(False)
+ return True
+ elif id == FindInDirService.FINDALL_ID:
projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
if projectService.GetFilesFromCurrentProject():
event.Enable(True)
return FindService.FindService.ProcessUpdateUIEvent(self, event)
- def ShowFindDirDialog(self, findString=None):
+ def ShowFindInDirDialog(self, findString=None):
config = wx.ConfigBase_Get()
frame = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("Find in Directory"), size= (320,200))
findDirButton = wx.Button(frame, -1, _("Browse..."))
lineSizer.Add(findDirButton, 0, wx.LEFT, HALF_SPACE)
contentSizer.Add(lineSizer, 0, wx.BOTTOM, SPACE)
-
+
def OnBrowseButton(event):
dlg = wx.DirDialog(frame, _("Choose a directory:"), style=wx.DD_DEFAULT_STYLE)
dir = dirCtrl.GetValue()
lineSizer = wx.BoxSizer(wx.VERTICAL) # let the line expand horizontally without vertical expansion
lineSizer.Add(wx.StaticLine(frame, -1, size = (10,-1)), 0, flag=wx.EXPAND)
contentSizer.Add(lineSizer, flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.BOTTOM, border=HALF_SPACE)
-
+
if wx.Platform == "__WXMAC__":
contentSizer.Add((-1, 10), 0, wx.EXPAND)
-
+
lineSizer = wx.BoxSizer(wx.HORIZONTAL)
lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
if not findString:
status = frame.ShowModal()
else:
passedCheck = True
-
+
# save user choice state for this and other Find Dialog Boxes
dirString = dirCtrl.GetValue()
searchSubfolders = subfolderCtrl.IsChecked()
- self.SaveFindDirConfig(dirString, searchSubfolders)
-
+ self.SaveFindInDirConfig(dirString, searchSubfolders)
+
findString = findCtrl.GetValue()
matchCase = matchCaseCtrl.IsChecked()
wholeWord = wholeWordCtrl.IsChecked()
self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
frame.Destroy()
- if status == wx.ID_OK:
+ if status == wx.ID_OK:
messageService = wx.GetApp().GetService(MessageService.MessageService)
messageService.ShowWindow()
view = messageService.GetView()
if view:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- view.ClearLines()
- view.SetCallback(self.OnJumpToFoundLine)
-
- view.AddLines(_("Searching for '%s' in '%s'\n\n") % (findString, dirString))
- if os.path.isfile(dirString):
- try:
- docFile = file(dirString, 'r')
- lineNum = 1
- needToDisplayFilename = True
- line = docFile.readline()
- while line:
- count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr)
- if count != -1:
- if needToDisplayFilename:
- view.AddLines(FILENAME_MARKER + dirString + "\n")
- needToDisplayFilename = False
- line = repr(lineNum).zfill(4) + ":" + line
- view.AddLines(line)
- line = docFile.readline()
- lineNum += 1
- if not needToDisplayFilename:
- view.AddLines("\n")
- except IOError, (code, message):
- print _("Warning, unable to read file: '%s'. %s") % (dirString, message)
- else:
- # do search in files on disk
- for root, dirs, files in os.walk(dirString):
- if not searchSubfolders and root != dirString:
- break
-
- for name in files:
- filename = os.path.join(root, name)
- try:
- docFile = file(filename, 'r')
- except IOError, (code, message):
- print _("Warning, unable to read file: '%s'. %s") % (filename, message)
- continue
-
+ try:
+ view.ClearLines()
+ view.SetCallback(self.OnJumpToFoundLine)
+
+ view.AddLines(_("Searching for '%s' in '%s'\n\n") % (findString, dirString))
+
+ if os.path.isfile(dirString):
+ try:
+ docFile = file(dirString, 'r')
lineNum = 1
needToDisplayFilename = True
line = docFile.readline()
count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr)
if count != -1:
if needToDisplayFilename:
- view.AddLines(FILENAME_MARKER + filename + "\n")
+ view.AddLines(FILENAME_MARKER + dirString + "\n")
needToDisplayFilename = False
line = repr(lineNum).zfill(4) + ":" + line
view.AddLines(line)
lineNum += 1
if not needToDisplayFilename:
view.AddLines("\n")
-
- view.AddLines(_("Search completed."))
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ except IOError, (code, message):
+ print _("Warning, unable to read file: '%s'. %s") % (dirString, message)
+ else:
+ # do search in files on disk
+ for root, dirs, files in os.walk(dirString):
+ if not searchSubfolders and root != dirString:
+ break
+
+ for name in files:
+ filename = os.path.join(root, name)
+ try:
+ docFile = file(filename, 'r')
+ except IOError, (code, message):
+ print _("Warning, unable to read file: '%s'. %s") % (filename, message)
+ continue
+
+ lineNum = 1
+ needToDisplayFilename = True
+ line = docFile.readline()
+ while line:
+ count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr)
+ if count != -1:
+ if needToDisplayFilename:
+ view.AddLines(FILENAME_MARKER + filename + "\n")
+ needToDisplayFilename = False
+ line = repr(lineNum).zfill(4) + ":" + line
+ view.AddLines(line)
+ line = docFile.readline()
+ lineNum += 1
+ if not needToDisplayFilename:
+ view.AddLines("\n")
+
+ view.AddLines(_("Search completed."))
+
+ finally:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
return True
else:
return False
-
- def SaveFindDirConfig(self, dirString, searchSubfolders):
+
+ def SaveFindInDirConfig(self, dirString, searchSubfolders):
""" Save search dir patterns and flags to registry.
-
+
dirString = search directory
searchSubfolders = Search subfolders
"""
config = wx.ConfigBase_Get()
config.Write(FIND_MATCHDIR, dirString)
config.WriteInt(FIND_MATCHDIRSUBFOLDERS, searchSubfolders)
+
+
+ def DoFindIn(self, findString, matchCase, wholeWord, regExpr, currFileOnly=False, jumpToFound=False):
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ if not messageService:
+ return
+
+ messageService.ShowWindow()
+
+ view = messageService.GetView()
+ if not view:
+ return
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ try:
+ #Switch to messages tab.
+ view.GetControl().GetParent().SetSelection(0)
+ view.ClearLines()
+ view.SetCallback(self.OnJumpToFoundLine)
+
+ projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+
+ if wx.GetApp().GetDocumentManager().GetCurrentView():
+ currDoc = wx.GetApp().GetDocumentManager().GetCurrentView().GetDocument()
+ else:
+ currDoc = None
+ if currFileOnly:
+ if currDoc:
+ projectFilenames = [currDoc.GetFilename()]
+ view.AddLines(FILE_MARKER + currDoc.GetFilename() + "\n\n")
+ else:
+ projectFilenames = []
+ else:
+ projectFilenames = projectService.GetFilesFromCurrentProject()
+
+ projView = projectService.GetView()
+ if projView:
+ projName = wx.lib.docview.FileNameFromPath(projView.GetDocument().GetFilename())
+ view.AddLines(PROJECT_MARKER + projName + "\n\n")
+
+ firstDef = -1
+
+ # do search in open files first, open files may have been modified and different from disk because it hasn't been saved
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ openDocsInProject = filter(lambda openDoc: openDoc.GetFilename() in projectFilenames, openDocs)
+ if currDoc and currDoc in openDocsInProject:
+ # make sure current document is searched first.
+ openDocsInProject.remove(currDoc)
+ openDocsInProject.insert(0, currDoc)
+ for openDoc in openDocsInProject:
+ if isinstance(openDoc, ProjectEditor.ProjectDocument): # don't search project model
+ continue
+
+ openDocView = openDoc.GetFirstView()
+ # some views don't have a in memory text object to search through such as the PM and the DM
+ # even if they do have a non-text searchable object, how do we display it in the message window?
+ if not hasattr(openDocView, "GetValue"):
+ continue
+ text = openDocView.GetValue()
+
+ lineNum = 1
+ needToDisplayFilename = True
+ start = 0
+ end = 0
+ count = 0
+ while count != -1:
+ count, foundStart, foundEnd, newText = self.DoFind(findString, None, text, start, end, True, matchCase, wholeWord, regExpr)
+ if count != -1:
+ if needToDisplayFilename:
+ view.AddLines(FILENAME_MARKER + openDoc.GetFilename() + "\n")
+ needToDisplayFilename = False
+
+ lineNum = openDocView.LineFromPosition(foundStart)
+ line = repr(lineNum).zfill(4) + ":" + openDocView.GetLine(lineNum)
+ view.AddLines(line)
+ if firstDef == -1:
+ firstDef = view.GetControl().GetCurrentLine() - 1
+
+ start = text.find("\n", foundStart)
+ if start == -1:
+ break
+ end = start
+ if not needToDisplayFilename:
+ view.AddLines("\n")
+ wx.GetApp().Yield(True)
+ openDocNames = map(lambda openDoc: openDoc.GetFilename(), openDocs)
+
+ # do search in closed files, skipping the open ones we already searched
+ filenames = filter(lambda filename: filename not in openDocNames, projectFilenames)
+ for filename in filenames:
+ try:
+ docFile = file(filename, 'r')
+ except IOError, (code, message):
+ print _("Warning, unable to read file: '%s'. %s") % (filename, message)
+ continue
+
+ lineNum = 1
+ needToDisplayFilename = True
+ line = docFile.readline()
+ while line:
+ count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr)
+ if count != -1:
+ if needToDisplayFilename:
+ view.AddLines(FILENAME_MARKER + filename + "\n")
+ needToDisplayFilename = False
+ line = repr(lineNum).zfill(4) + ":" + line
+ view.AddLines(line)
+ if firstDef == -1:
+ firstDef = view.GetControl().GetCurrentLine() - 1
+ line = docFile.readline()
+ lineNum += 1
+ if not needToDisplayFilename:
+ view.AddLines("\n")
+ wx.GetApp().Yield(True)
+
+ view.AddLines(_("Search for '%s' completed.") % findString)
+ if jumpToFound:
+ self.OnJumpToFoundLine(event=None, defLineNum=firstDef)
- def ShowFindAllDialog(self, findString=None):
+ finally:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+ def FindInProject(self, findString):
+ self.DoFindIn(findString, matchCase=True, wholeWord=True, regExpr=True, jumpToFound=True)
+
+
+ def ShowFindInProjectDialog(self, findString=None):
config = wx.ConfigBase_Get()
frame = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("Find in Project"), size= (320,200))
self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
frame.Destroy()
+
if status == wx.ID_OK:
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
+ self.DoFindIn(findString, matchCase, wholeWord, regExpr)
+ return True
+ else:
+ return False
- view = messageService.GetView()
- if view:
- view.ClearLines()
- view.SetCallback(self.OnJumpToFoundLine)
- projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
- projectFilenames = projectService.GetFilesFromCurrentProject()
+ def ShowFindInFileDialog(self, findString=None):
+ config = wx.ConfigBase_Get()
- projView = projectService.GetView()
- if projView:
- projName = wx.lib.docview.FileNameFromPath(projView.GetDocument().GetFilename())
- view.AddLines(PROJECT_MARKER + projName + "\n\n")
+ frame = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("Find in File"), size= (320,200))
+ borderSizer = wx.BoxSizer(wx.HORIZONTAL)
- # do search in open files first, open files may have been modified and different from disk because it hasn't been saved
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- openDocsInProject = filter(lambda openDoc: openDoc.GetFilename() in projectFilenames, openDocs)
- for openDoc in openDocsInProject:
- if isinstance(openDoc, ProjectEditor.ProjectDocument): # don't search project model
- continue
-
- openDocView = openDoc.GetFirstView()
- # some views don't have a in memory text object to search through such as the PM and the DM
- # even if they do have a non-text searchable object, how do we display it in the message window?
- if not hasattr(openDocView, "GetValue"):
- continue
- text = openDocView.GetValue()
-
- lineNum = 1
- needToDisplayFilename = True
- start = 0
- end = 0
- count = 0
- while count != -1:
- count, foundStart, foundEnd, newText = self.DoFind(findString, None, text, start, end, True, matchCase, wholeWord, regExpr)
- if count != -1:
- if needToDisplayFilename:
- view.AddLines(FILENAME_MARKER + openDoc.GetFilename() + "\n")
- needToDisplayFilename = False
-
- lineNum = openDocView.LineFromPosition(foundStart)
- line = repr(lineNum).zfill(4) + ":" + openDocView.GetLine(lineNum)
- view.AddLines(line)
-
- start = text.find("\n", foundStart)
- if start == -1:
- break
- end = start
- if not needToDisplayFilename:
- view.AddLines("\n")
- openDocNames = map(lambda openDoc: openDoc.GetFilename(), openDocs)
-
- # do search in closed files, skipping the open ones we already searched
- filenames = filter(lambda filename: filename not in openDocNames, projectFilenames)
- for filename in filenames:
- try:
- docFile = file(filename, 'r')
- except IOError, (code, message):
- print _("Warning, unable to read file: '%s'. %s") % (filename, message)
- continue
-
- lineNum = 1
- needToDisplayFilename = True
- line = docFile.readline()
- while line:
- count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr)
- if count != -1:
- if needToDisplayFilename:
- view.AddLines(FILENAME_MARKER + filename + "\n")
- needToDisplayFilename = False
- line = repr(lineNum).zfill(4) + ":" + line
- view.AddLines(line)
- line = docFile.readline()
- lineNum += 1
- if not needToDisplayFilename:
- view.AddLines("\n")
-
- view.AddLines(_("Search for '%s' completed.") % findString)
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+ lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+ lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
+ if not findString:
+ findString = config.Read(FindService.FIND_MATCHPATTERN, "")
+ findCtrl = wx.TextCtrl(frame, -1, findString, size=(200,-1))
+ lineSizer.Add(findCtrl, 0, wx.LEFT, HALF_SPACE)
+ contentSizer.Add(lineSizer, 0, wx.BOTTOM, SPACE)
+ wholeWordCtrl = wx.CheckBox(frame, -1, _("Match whole word only"))
+ wholeWordCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHWHOLEWORD, False))
+ matchCaseCtrl = wx.CheckBox(frame, -1, _("Match case"))
+ matchCaseCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHCASE, False))
+ regExprCtrl = wx.CheckBox(frame, -1, _("Regular expression"))
+ regExprCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHREGEXPR, False))
+ contentSizer.Add(wholeWordCtrl, 0, wx.BOTTOM, SPACE)
+ contentSizer.Add(matchCaseCtrl, 0, wx.BOTTOM, SPACE)
+ contentSizer.Add(regExprCtrl, 0, wx.BOTTOM, SPACE)
+ borderSizer.Add(contentSizer, 0, wx.TOP | wx.BOTTOM | wx.LEFT, SPACE)
+
+ buttonSizer = wx.BoxSizer(wx.VERTICAL)
+ findBtn = wx.Button(frame, wx.ID_OK, _("Find"))
+ findBtn.SetDefault()
+ BTM_SPACE = HALF_SPACE
+ if wx.Platform == "__WXMAC__":
+ BTM_SPACE = SPACE
+ buttonSizer.Add(findBtn, 0, wx.BOTTOM, BTM_SPACE)
+ buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL), 0)
+ borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE)
+
+ frame.SetSizer(borderSizer)
+ frame.Fit()
+
+ frame.CenterOnParent()
+ status = frame.ShowModal()
+
+ # save user choice state for this and other Find Dialog Boxes
+ findString = findCtrl.GetValue()
+ matchCase = matchCaseCtrl.IsChecked()
+ wholeWord = wholeWordCtrl.IsChecked()
+ regExpr = regExprCtrl.IsChecked()
+ self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
+ frame.Destroy()
+
+ if status == wx.ID_OK:
+ self.DoFindIn(findString, matchCase, wholeWord, regExpr, currFileOnly=True)
return True
else:
return False
- def OnJumpToFoundLine(self, event):
+ def OnJumpToFoundLine(self, event=None, defLineNum=-1):
messageService = wx.GetApp().GetService(MessageService.MessageService)
- lineText, pos = messageService.GetView().GetCurrLine()
- if lineText == "\n" or lineText.find(FILENAME_MARKER) != -1 or lineText.find(PROJECT_MARKER) != -1:
+ if defLineNum == -1:
+ lineText, pos = messageService.GetView().GetCurrLine()
+ else:
+ lineText = messageService.GetView().GetControl().GetLine(defLineNum)
+ pos = 0
+
+ if lineText == "\n" or lineText.find(FILENAME_MARKER) != -1 or lineText.find(PROJECT_MARKER) != -1 or lineText.find(FILE_MARKER) != -1:
return
lineEnd = lineText.find(":")
if lineEnd == -1:
lineNum = int(lineText[0:lineEnd])
text = messageService.GetView().GetText()
- curPos = messageService.GetView().GetCurrentPos()
+ if defLineNum == -1:
+ curPos = messageService.GetView().GetCurrentPos()
+ else:
+ curPos = messageService.GetView().GetControl().GetLineEndPosition(defLineNum)
startPos = text.rfind(FILENAME_MARKER, 0, curPos)
endPos = text.find("\n", startPos)
# time, we don't see the selection, it is scrolled off screen
foundView.SetSelection(startPos - 1 + len(lineText[lineEnd:].rstrip("\n")), startPos)
wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos)
-
-
def SetViewDefaults(self):
- CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Html", hasWordWrap = True, hasTabs = True)
+ CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Html", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetFontAndColorFromConfig(self):
class HtmlOptionsPanel(STCTextEditor.TextOptionsPanel):
def __init__(self, parent, id):
- STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Html", label = "HTML", hasWordWrap = True, hasTabs = True)
+ STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Html", label = "HTML", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetIcon(self):
import os.path
import activegrid.util.sysutils as sysutilslib
import activegrid.util.appdirs as appdirs
+import shutil
+
+# Required for Unicode support with python
+# site.py sets this, but Windows builds don't have site.py because of py2exe problems
+# If site.py has already run, then the setdefaultencoding function will have been deleted.
+if hasattr(sys,"setdefaultencoding"):
+ sys.setdefaultencoding("utf-8")
_ = wx.GetTranslation
-ACTIVEGRID_BASE_IDE = False
+ACTIVEGRID_BASE_IDE = False
+USE_OLD_PROJECTS = False
#----------------------------------------------------------------------------
# Helper functions for command line args
#----------------------------------------------------------------------------
return result
+# The default log action in wx is to prompt with a big message box
+# which is often inappropriate (for example, if the clipboard data
+# is not readable on Mac, we'll get one of these messages repeatedly)
+# so just log the errors instead.
+# NOTE: This does NOT supress fatal system errors. Only non-fatal ones.
+class AppLog(wx.PyLog):
+ def __init__(self):
+ wx.PyLog.__init__(self)
+ self.items = []
+
+ def DoLogString(self, message, timeStamp):
+ self.items.append(str(timeStamp) + u" " + message.decode())
+
#----------------------------------------------------------------------------
# Classes
#----------------------------------------------------------------------------
def OnInit(self):
global ACTIVEGRID_BASE_IDE
-
+ global USE_OLD_PROJECTS
args = sys.argv
-
+
+ # Suppress non-fatal errors that might prompt the user even in cases
+ # when the error does not impact them.
+ wx.Log_SetActiveTarget(AppLog())
+
if "-h" in args or "-help" in args or "--help" in args\
or (wx.Platform == "__WXMSW__" and "/help" in args):
print "Usage: ActiveGridAppBuilder.py [options] [filenames]\n"
elif isInArgs("baseide", args):
self.SetAppName(_("ActiveGrid IDE"))
ACTIVEGRID_BASE_IDE = True
+ elif isInArgs("tools", args):
+ USE_OLD_PROJECTS = True
else:
self.SetAppName(_("ActiveGrid Application Builder"))
self.SetDebug(False)
import AboutDialog
import SVNService
import ExtensionService
+## import UpdateLogIniService
if not ACTIVEGRID_BASE_IDE:
import activegrid.model.basedocmgr as basedocmgr
- import UpdateService as updater
+ import UpdateService
import DataModelEditor
import ProcessModelEditor
import DeploymentService
import WebServerService
import WelcomeService
- import ViewEditor
+ import XFormEditor
import PropertyService
import WSDLEditor
import WsdlAgEditor
import XPathEditor
+ import XPathExprEditor
import ImportServiceWizard
import RoleEditor
import HelpService
if not config.Exists("MDIEmbedRightVisible"): # Make the properties embedded window hidden as default
config.WriteInt("MDIEmbedRightVisible", False)
- docManager = wx.lib.docview.DocManager(flags = self.GetDefaultDocManagerFlags())
+ docManager = IDEDocManager(flags = self.GetDefaultDocManagerFlags())
self.SetDocumentManager(docManager)
+ # Note: These templates must be initialized in display order for the "Files of type" dropdown for the "File | Open..." dialog
+
defaultTemplate = wx.lib.docview.DocTemplate(docManager,
_("Any"),
"*.*",
_("Deployment View"),
XmlEditor.XmlDocument,
XmlEditor.XmlView,
+ wx.lib.docview.TEMPLATE_INVISIBLE,
icon = DeploymentService.getDPLIcon())
docManager.AssociateTemplate(dplTemplate)
icon = HtmlEditor.getHTMLIcon())
docManager.AssociateTemplate(htmlTemplate)
+ if not ACTIVEGRID_BASE_IDE:
+ identityTemplate = wx.lib.docview.DocTemplate(docManager,
+ _("Identity"),
+ "*.xacml",
+ _("Identity"),
+ _(".xacml"),
+ _("Identity Configuration"),
+ _("Identity View"),
+ RoleEditor.RoleEditorDocument,
+ RoleEditor.RoleEditorView,
+ wx.lib.docview.TEMPLATE_NO_CREATE,
+ icon = XmlEditor.getXMLIcon())
+ docManager.AssociateTemplate(identityTemplate)
+
imageTemplate = wx.lib.docview.DocTemplate(docManager,
_("Image"),
"*.bmp;*.ico;*.gif;*.jpg;*.jpeg;*.png",
# XmlEditor.XmlView,
LayoutEditor.LayoutEditorDocument,
LayoutEditor.LayoutEditorView,
- wx.lib.docview.TEMPLATE_NO_CREATE,
icon = LayoutEditor.getLytIcon())
docManager.AssociateTemplate(layoutTemplate)
_("Process View"),
ProcessModelEditor.ProcessModelDocument,
ProcessModelEditor.ProcessModelView,
+ wx.lib.docview.TEMPLATE_NO_CREATE,
icon = ProcessModelEditor.getProcessModelIcon())
docManager.AssociateTemplate(processModelTemplate)
_("Project View"),
ProjectEditor.ProjectDocument,
ProjectEditor.ProjectView,
+ wx.lib.docview.TEMPLATE_NO_CREATE,
icon = ProjectEditor.getProjectIcon())
docManager.AssociateTemplate(projectTemplate)
icon = DataModelEditor.getDataModelIcon())
docManager.AssociateTemplate(dataModelTemplate)
+ if not ACTIVEGRID_BASE_IDE:
+ wsdlagTemplate = wx.lib.docview.DocTemplate(docManager,
+ _("Service Reference"),
+ "*.wsdlag",
+ _("Project"),
+ _(".wsdlag"),
+ _("Service Reference Document"),
+ _("Service Reference View"),
+ WsdlAgEditor.WsdlAgDocument,
+ WsdlAgEditor.WsdlAgView,
+ wx.lib.docview.TEMPLATE_NO_CREATE,
+ icon = WSDLEditor.getWSDLIcon())
+ docManager.AssociateTemplate(wsdlagTemplate)
+
if not ACTIVEGRID_BASE_IDE and _EDIT_LAYOUTS:
layoutTemplate = wx.lib.docview.DocTemplate(docManager,
_("Skin"),
docManager.AssociateTemplate(layoutTemplate)
if not ACTIVEGRID_BASE_IDE:
- identityTemplate = wx.lib.docview.DocTemplate(docManager,
- _("Identity"),
- "*.xacml",
- _("Identity"),
- _(".xacml"),
- _("Identity Configuration"),
- _("Identity View"),
- RoleEditor.RoleEditorDocument,
- RoleEditor.RoleEditorView,
+ sqlTemplate = wx.lib.docview.DocTemplate(docManager,
+ _("SQL"),
+ "*.sql",
+ _("SQL"),
+ _(".sql"),
+ _("SQL Document"),
+ _("SQL View"),
+ SQLEditor.SQLDocument,
+ SQLEditor.SQLView,
wx.lib.docview.TEMPLATE_NO_CREATE,
- icon = XmlEditor.getXMLIcon())
- docManager.AssociateTemplate(identityTemplate)
+ icon = SQLEditor.getSQLIcon())
+ docManager.AssociateTemplate(sqlTemplate)
textTemplate = wx.lib.docview.DocTemplate(docManager,
_("Text"),
docManager.AssociateTemplate(textTemplate)
if not ACTIVEGRID_BASE_IDE:
- sqlTemplate = wx.lib.docview.DocTemplate(docManager,
- _("SQL"),
- "*.sql",
- _("SQL"),
- _(".sql"),
- _("SQL Document"),
- _("SQL View"),
- SQLEditor.SQLDocument,
- SQLEditor.SQLView,
- wx.lib.docview.TEMPLATE_NO_CREATE,
- icon = SQLEditor.getSQLIcon())
- docManager.AssociateTemplate(sqlTemplate)
-
-
- wsdlagTemplate = wx.lib.docview.DocTemplate(docManager,
- _("Service Reference"),
- "*.wsdlag",
- _("Project"),
- _(".wsdlag"),
- _("Service Reference Document"),
- _("Service Reference View"),
- WsdlAgEditor.WsdlAgDocument,
- WsdlAgEditor.WsdlAgView,
- wx.lib.docview.TEMPLATE_NO_CREATE,
- icon = WSDLEditor.getWSDLIcon())
- docManager.AssociateTemplate(wsdlagTemplate)
-
wsdlTemplate = WSDLEditor.WSDLTemplate(docManager,
_("WSDL"),
"*.wsdl",
_("WSDL View"),
WSDLEditor.WSDLDocument,
WSDLEditor.WSDLView,
+ wx.lib.docview.TEMPLATE_NO_CREATE,
icon = WSDLEditor.getWSDLIcon())
docManager.AssociateTemplate(wsdlTemplate)
+ if not ACTIVEGRID_BASE_IDE:
xformTemplate = wx.lib.docview.DocTemplate(docManager,
_("XForm"),
"*.xform",
_(".xform"),
_("XForm Document"),
_("XForm View"),
- ViewEditor.ViewEditorDocument,
- ViewEditor.ViewEditorView,
+ XFormEditor.XFormDocument,
+ XFormEditor.XFormView,
wx.lib.docview.TEMPLATE_NO_CREATE,
- icon = ViewEditor.getXFORMIcon())
+ icon = XFormEditor.getXFormIcon())
docManager.AssociateTemplate(xformTemplate)
xmlTemplate = wx.lib.docview.DocTemplate(docManager,
icon = XmlEditor.getXMLIcon())
docManager.AssociateTemplate(xmlTemplate)
+
+ # Note: Child document types aren't displayed in "Files of type" dropdown
if not ACTIVEGRID_BASE_IDE:
viewTemplate = wx.lib.pydocview.ChildDocTemplate(docManager,
- _("View"),
+ _("XForm"),
"*.none",
- _("View"),
+ _("XForm"),
_(".bpel"),
- _("ViewEditor Document"),
- _("ViewEditor View"),
- ViewEditor.ViewEditorDocument,
- ViewEditor.ViewEditorView,
- icon = ProcessModelEditor.getXFORMIcon())
+ _("XFormEditor Document"),
+ _("XFormEditor View"),
+ XFormEditor.XFormDocument,
+ XFormEditor.XFormView,
+ icon = XFormEditor.getXFormIcon())
docManager.AssociateTemplate(viewTemplate)
+ if not ACTIVEGRID_BASE_IDE:
bpelTemplate = wx.lib.pydocview.ChildDocTemplate(docManager,
_("BPEL"),
"*.none",
icon = ProcessModelEditor.getProcessModelIcon())
docManager.AssociateTemplate(bpelTemplate)
+ if not ACTIVEGRID_BASE_IDE:
dataModelChildTemplate = wx.lib.pydocview.ChildDocTemplate(docManager,
_("Schema"),
"*.none",
projectService = self.InstallService(ProjectEditor.ProjectService("Projects", embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT))
findService = self.InstallService(FindInDirService.FindInDirService())
if not ACTIVEGRID_BASE_IDE:
- webServerService = self.InstallService(WebServerService.WebServerService())
- webBrowserService = self.InstallService(WebBrowserService.WebBrowserService())
+ webBrowserService = self.InstallService(WebBrowserService.WebBrowserService()) # this must be before webServerService since it sets the proxy environment variable that is needed by the webServerService.
+ webServerService = self.InstallService(WebServerService.WebServerService()) # this must be after webBrowserService since that service sets the proxy environment variables.
outlineService = self.InstallService(OutlineService.OutlineService("Outline", embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOMLEFT))
filePropertiesService = self.InstallService(wx.lib.pydocview.FilePropertiesService())
markerService = self.InstallService(MarkerService.MarkerService())
debuggerService = self.InstallService(DebuggerService.DebuggerService("Debugger", embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM))
if not ACTIVEGRID_BASE_IDE:
processModelService = self.InstallService(ProcessModelEditor.ProcessModelService())
- viewEditorService = self.InstallService(ViewEditor.ViewEditorService())
+ viewEditorService = self.InstallService(XFormEditor.XFormService())
deploymentService = self.InstallService(DeploymentService.DeploymentService())
dataModelService = self.InstallService(DataModelEditor.DataModelService())
dataSourceService = self.InstallService(DataModelEditor.DataSourceService())
if not ACTIVEGRID_BASE_IDE:
helpPath = os.path.join(sysutilslib.mainModuleDir, "activegrid", "tool", "data", "AGDeveloperGuideWebHelp", "AGDeveloperGuideWebHelp.hhp")
helpService = self.InstallService(HelpService.HelpService(helpPath))
+ if self.GetUseTabbedMDI():
+ windowService = self.InstallService(wx.lib.pydocview.WindowMenuService())
if not ACTIVEGRID_BASE_IDE:
if not ACTIVEGRID_BASE_IDE:
propertyService.AddViewTypeForBackgroundHandler(DataModelEditor.DataModelView)
propertyService.AddViewTypeForBackgroundHandler(ProcessModelEditor.ProcessModelView)
- propertyService.AddViewTypeForBackgroundHandler(ViewEditor.ViewEditorView)
+ propertyService.AddViewTypeForBackgroundHandler(XFormEditor.XFormView)
propertyService.AddViewTypeForBackgroundHandler(BPELEditor.BPELView)
propertyService.AddViewTypeForBackgroundHandler(WSDLEditor.WSDLView)
propertyService.StartBackgroundTimer()
propertyService.AddCustomCellRenderers(DataModelEditor.GetCustomGridCellRendererDict())
propertyService.AddCustomCellRenderers(BPELEditor.GetCustomGridCellRendererDict())
- propertyService.AddCustomCellRenderers(WsdlAgEditor.GetCustomGridCellRendererDict())
+ propertyService.AddCustomCellRenderers(XFormEditor.GetCustomGridCellRendererDict())
propertyService.AddCustomCellRenderers(XPathEditor.GetCustomGridCellRendererDict())
- propertyService.AddCustomCellRenderers(ViewEditor.GetCustomGridCellRendererDict())
+ propertyService.AddCustomCellRenderers(XPathExprEditor.GetCustomGridCellRendererDict())
propertyService.AddCustomCellRenderers(WSDLEditor.GetCustomGridCellRendererDict())
+ propertyService.AddCustomCellRenderers(WsdlAgEditor.GetCustomGridCellRendererDict())
propertyService.AddCustomCellEditors(DataModelEditor.GetCustomGridCellEditorDict())
propertyService.AddCustomCellEditors(BPELEditor.GetCustomGridCellEditorDict())
- propertyService.AddCustomCellEditors(ViewEditor.GetCustomGridCellEditorDict())
- propertyService.AddCustomCellEditors(WsdlAgEditor.GetCustomGridCellEditorDict())
+ propertyService.AddCustomCellEditors(XFormEditor.GetCustomGridCellEditorDict())
propertyService.AddCustomCellEditors(XPathEditor.GetCustomGridCellEditorDict())
+ propertyService.AddCustomCellEditors(XPathExprEditor.GetCustomGridCellEditorDict())
propertyService.AddCustomCellEditors(WSDLEditor.GetCustomGridCellEditorDict())
+ propertyService.AddCustomCellEditors(WsdlAgEditor.GetCustomGridCellEditorDict())
if not ACTIVEGRID_BASE_IDE:
projectService.AddNameDefault(".bpel", projectService.GetDefaultNameCallback)
projectService.AddFileTypeDefault(".gif", basedocmgr.FILE_TYPE_STATIC)
projectService.AddFileTypeDefault(".jpg", basedocmgr.FILE_TYPE_STATIC)
projectService.AddFileTypeDefault(".jpeg", basedocmgr.FILE_TYPE_STATIC)
- # projectService.AddFileTypeDefault(".xform", basedocmgr.FILE_TYPE_XFORM) # don't register xform as a default, must be explicitly added
+ projectService.AddFileTypeDefault(".xform", basedocmgr.FILE_TYPE_XFORM)
- projectService.AddLogicalViewFolderDefault(".wsdlag", _("Services"))
- projectService.AddLogicalViewFolderDefault(".wsdl", _("Services"))
- projectService.AddLogicalViewFolderDefault("wsdl.php", _("Services"))
- projectService.AddLogicalViewFolderDefault("wsdl.py", _("Services"))
- projectService.AddLogicalViewFolderDefault(".xsd", _("Schemas"))
- projectService.AddLogicalViewFolderDefault("xsd.php", _("Schemas"))
- projectService.AddLogicalViewFolderDefault("xsd.py", _("Schemas"))
- projectService.AddLogicalViewFolderDefault(".bpel", _("Processes"))
- projectService.AddLogicalViewFolderDefault(".xform", _("XForms"))
- projectService.AddLogicalViewFolderDefault(".lyt", _("XForms/Skins_and_Layouts"))
- projectService.AddLogicalViewFolderDefault(".skn", _("XForms/Skins_and_Layouts"))
- projectService.AddLogicalViewFolderDefault(".xacml", _("Security"))
- projectService.AddLogicalViewFolderDefault(".css", _("XForms/Static"))
- projectService.AddLogicalViewFolderDefault(".js", _("XForms/Static"))
- projectService.AddLogicalViewFolderDefault(".gif", _("Images"))
- projectService.AddLogicalViewFolderDefault(".jpeg", _("Images"))
- projectService.AddLogicalViewFolderDefault(".jpg", _("Images"))
- projectService.AddLogicalViewFolderDefault(".py", None)
+ projectService.AddLogicalViewFolderDefault(".agp", _("Projects"))
+ projectService.AddLogicalViewFolderDefault(".wsdlag", _("Services"))
+ projectService.AddLogicalViewFolderDefault(".wsdl", _("Services"))
+ projectService.AddLogicalViewFolderDefault(".xsd", _("Data Models"))
+ projectService.AddLogicalViewFolderDefault(".bpel", _("Page Flows"))
+ projectService.AddLogicalViewFolderDefault(".xform", _("Pages"))
+ projectService.AddLogicalViewFolderDefault(".xacml", _("Security"))
+ projectService.AddLogicalViewFolderDefault(".lyt", _("Presentation/Layouts"))
+ projectService.AddLogicalViewFolderDefault(".skn", _("Presentation/Skins"))
+ projectService.AddLogicalViewFolderDefault(".css", _("Presentation/Stylesheets"))
+ projectService.AddLogicalViewFolderDefault(".js", _("Presentation/Javascript"))
+ projectService.AddLogicalViewFolderDefault(".html", _("Presentation/Static"))
+ projectService.AddLogicalViewFolderDefault(".htm", _("Presentation/Static"))
+ projectService.AddLogicalViewFolderDefault(".gif", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".jpeg", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".jpg", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".png", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".ico", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".bmp", _("Presentation/Images"))
+ projectService.AddLogicalViewFolderDefault(".py", _("Code"))
+ projectService.AddLogicalViewFolderDefault(".php", _("Code"))
+ projectService.AddLogicalViewFolderDefault(".pl", _("Code"))
+ projectService.AddLogicalViewFolderDefault(".sql", _("Code"))
+ projectService.AddLogicalViewFolderDefault(".xml", _("Code"))
+ projectService.AddLogicalViewFolderDefault(".dpl", _("Code"))
+
+ projectService.AddLogicalViewFolderCollapsedDefault(_("Page Flows"), False)
+ projectService.AddLogicalViewFolderCollapsedDefault(_("Pages"), False)
self.SetDefaultIcon(getActiveGridIcon())
else:
embeddedWindows = wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT | wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOMLEFT |wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM
if self.GetUseTabbedMDI():
- self.frame = IDEDocTabbedParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows)
+ self.frame = IDEDocTabbedParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows, minSize=150)
else:
- self.frame = IDEMDIParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows)
+ self.frame = IDEMDIParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows, minSize=150)
self.frame.Show(True)
# wxBug: On Mac, having the updates fire while the tip dialog is at front
# for some reason messes up menu updates. This seems a low-level wxWidgets bug,
# so until I track this down, turn off UI updates while the tip dialog is showing.
- #wx.UpdateUIEvent.SetUpdateInterval(-1)
- #appUpdater = updater.AppUpdateService(self)
- #appUpdater.RunUpdateIfNewer()
if not ACTIVEGRID_BASE_IDE:
wx.UpdateUIEvent.SetUpdateInterval(-1)
- appUpdater = updater.AppUpdateService(self)
+ UpdateService.UpdateVersionNag()
+ appUpdater = UpdateService.AppUpdateService(self)
appUpdater.RunUpdateIfNewer()
if not welcomeService.RunWelcomeIfFirstTime():
if os.path.isfile(tips_path):
if os.path.isfile(tips_path):
self.ShowTip(docManager.FindSuitableParent(), wx.CreateFileTipProvider(tips_path, 0))
+ iconPath = os.path.join(sysutilslib.mainModuleDir, "activegrid", "tool", "bmp_source", "activegrid.ico")
+ if os.path.isfile(iconPath):
+ ib = wx.IconBundle()
+ ib.AddIconFromFile(iconPath, wx.BITMAP_TYPE_ANY)
+ wx.GetApp().GetTopWindow().SetIcons(ib)
+
wx.UpdateUIEvent.SetUpdateInterval(1000) # Overhead of updating menus was too much. Change to update every n milliseconds.
- # we need this for a while due to the Mac 1.0 release which put things
- # in ~/Documents/ActiveGrid Projects/demos.
- # Now it should be ~/Documents/ActiveGrid Demos/
- base_path = appdirs.documents_folder
- if os.path.isdir(os.path.join(base_path, "ActiveGrid Projects", "demos")):
- message = _("The location where demo files are stored has changed between the 1.0 and 1.1 release as a result of improved multi-user support across platforms. In order for ActiveGrid Application Builder to find these files, they need to be moved from '%s/ActiveGrid Projects/demos' to '%s/ActiveGrid Demos'. Click OK to move the files.") % (base_path, base_path)
- wx.MessageBox(message, _("Demo Files Location Update"))
- import shutil
- shutil.copytree(os.path.join(base_path, "ActiveGrid Projects", "demos"), os.path.join(base_path, "ActiveGrid Demos"))
- shutil.rmtree(os.path.join(base_path, "ActiveGrid Projects"))
-
-
return True
+class IDEDocManager(wx.lib.docview.DocManager):
+
+ # Overriding default document creation.
+ def OnFileNew(self, event):
+ import NewDialog
+ newDialog = NewDialog.NewDialog(wx.GetApp().GetTopWindow())
+ if newDialog.ShowModal() == wx.ID_OK:
+ isTemplate, object = newDialog.GetSelection()
+ if isTemplate:
+ object.CreateDocument('', wx.lib.docview.DOC_NEW)
+ else:
+ import ProcessModelEditor
+ if object == NewDialog.FROM_DATA_SOURCE:
+ wiz = ProcessModelEditor.CreateAppWizard(wx.GetApp().GetTopWindow(), title=_("New Database Application"), minimalCreate=False, startingType=object)
+ wiz.RunWizard()
+ elif object == NewDialog.FROM_DATABASE_SCHEMA:
+ wiz = ProcessModelEditor.CreateAppWizard(wx.GetApp().GetTopWindow(), title=_("New Database Application"), minimalCreate=False, startingType=object)
+ wiz.RunWizard()
+ elif object == NewDialog.FROM_SERVICE:
+ wiz = ProcessModelEditor.CreateAppWizard(wx.GetApp().GetTopWindow(), title=_("New Service Application"), minimalCreate=False, startingType=object)
+ wiz.RunWizard()
+ elif object == NewDialog.CREATE_SKELETON_APP:
+ wiz = ProcessModelEditor.CreateAppWizard(wx.GetApp().GetTopWindow(), title=_("New Skeleton Application"), minimalCreate=False, startingType=object)
+ wiz.RunWizard()
+ elif object == NewDialog.CREATE_PROJECT:
+ import ProjectEditor
+ for temp in self.GetTemplates():
+ if isinstance(temp,ProjectEditor.ProjectTemplate):
+ temp.CreateDocument('', wx.lib.docview.DOC_NEW)
+ break
+ else:
+ assert False, "Unknown type returned from NewDialog"
+
+
class IDEDocTabbedParentFrame(wx.lib.pydocview.DocTabbedParentFrame):
\x94Zv\xe0\xfb\x8e{\x8bA\x8dT\xd9=\xcf-\x16\x0b\x04\x80RbY\xb6\xfa\xb8\xc5Ba\
\x9f\nq]7\xf3]\x18cD\xcaR\xa9X*\x15\xdb\xed\xce\xf6\xeen\xb7\xdb\xd7\xb3.3\
\x11J\xc9\xc0\xb5t\xaa:1Q\xb6(U\xc3\'!$\xcc\xe5\x84\x90T\xb51\xdb\xb6,+\x9f\
-\xcf\x1fD\x15x\xc3E\x07\x1b@\t|\x0f\xb5\xfd\x86{-i\x11\xff\x7f{\xef\xfe\x1b\
-\xd7q\xe6y?u\xae\xdd\xa7\xaf\xbc4u#u#%Y\xa2\xed\xc8\xb2\x13\xc7\xce&\x93\xcc\
-$c\xcf&\xeb\xcc\xcc\xee\xecb\x81\xc5\x02\xefO\x0b,\xf6\xcf\x19\x0c0x\xf1\x02\
-/\x06\x03\x0c2~\xd7\xef$\xefd\xc7\x8e\xb3\x13G\x8e-\xcb\x17I\xbeP\xb2l\x8a\
-\x92LR\x94\xd8$\xc5>};\x97\xaaz\xde\x1f\xaa\xbbX<}QK\xb6(\x92\xaa\x0f\x0c\
-\xa1\xdd\xacS\xa7\xaa\xfa\x9co=\xe7\xa9\xe7<\x15\xdce\xc1*\xc0=\xa6\n\xd3\
-\xcd\xa5r\xfbv\x9e\xb8\xcb+I:\xbe\xc5\r&.Gqo/\r\xff\xe5\xad\xa1?O\xc5\xcb^4\
-\x9f\x8eoY\xbcA\x00\xd1\xb0b\xb3\x18\xa4\x0eE\xa9\t\xee\x8e\x98\xa6i\xb5sgSJ\
-\xabC\xdf[\xcf\x7fWVeY\x96\xeb\xba"/\xa0x\x19\xbd\xb3\x19\xa2\x01\xb0\xd9/a\
-\x9a\xa6\x88\xa0\xe0\xdcdFa\xa5\xf8\'\xe5\xc2\x1f\x1b\xac\xe1\xb2\xb5tt+E\
-\x97-^\x07\xe0\x80\xc0\x8dTd\x8f6\xdd\t\xea\xee\x05wH\xa4!4\x8c\xeeQ\xde\x9d\
-\xbe y\xf30\xc3X\xcf}\xe7n\xf6\xdb&\xab\xa5\xe8r:\xba\xe5\xd2\xb2\x89\xedWH\
-\x0c\x07\xed<\xf7\x0e\x18\xc5c\xe9\xe1\xc3\xe9\xfc\xa8\xeb\xbaD\xc10\x8cf\
-\xfe\xe9\xeb\xee\t1a\x88:-%-b6\x9bM\xa5R\xa2\x97\x03\xfe("\x8cD\xdc\xe7\x0b{\
-\xfe\x8fD|\x8e8\xa9\xf8\xed\xd2\xe9t&\x93\xf1R^\x9f\xa5\xd4\xcesu\xbe\xe2$N\
-\x1d\x86a+\x84\x861\xceM\xdf<]\xf1\xbee`l\xd1\xf5t\xbc\x94\x8e\x97l\xb6n\x00\
-\x03\x04n8\x915\x12\xb8\xe3\x91\xbb\x9f;\xc3r1P\xae\xa3XJ6\xf9\xae\xcd\x90\
-\x13|\xa2\r\xe2\x02\x90\xd6k.\x97\xcd\xe5\xb2\xd0z\xeb\x12\xc5FE\xc2\x08\xb0\
-m[>\x8d\x89\xbe\x8b\xf1\x8fc\x8a(\xfd!-\x97\x0e1\xfa-2\x9b\xa696V\x1a\x1e\
-\x1e"\x848\x8e\x93N\xa7E\xea$\x91\xcfr\x90Q\xed\x1cO\xc30\n\xf9\xfc\x13\xc7\
-\xd3\x88h\xdb\xb6\xf8\xa5\xc4\xbd\xd0k\xceS\']\xb5\x9e\xf6/\xc2\xc5hp\xce\
-\xe3\x98\x06A\x10F\x91\x98\xc9\x08!\xb6m{^\xdaK\xa7\xc5\x8e\xaff{{E\x91Bj\
-\xdf\xde={\xf7\x8c\x99\xa6\xe9\xba\xaeH\x12\xe5y\xde}\xe5U~$lJ6\xc06=\xa8\
-\xab\x0c\xbc\xd7\x12\xc6J\x16\x13\xd6;\xbdEf\xe4(<\x94T\xe3[\x82\xfa\\\xac^I\
-ma\xe5\x88\x18\x9b\xfb+\xa9\xfd\x15\x00Pn\x03q`\xaa\x9d\x8d\x00\x00x{\xfbG)\
-\x9aB\xdd\xa4\xef\xb8\xd3\xa2TM\x15\xa9\x922J/\x8a"q{o8\x85\xad|\x84\xb9(}\
-\xa8\xa2\xd4\x908J\xac\x10\xb6v5\xea\xb6\x9f\xaa\xea\x0b\xc2\xf6n\xf1\xa6i\
-\xca;\x07\xadb\xe0\x14\x02\xef\x98\x1c\x10\x00\x90S\xa0\xe7y\xc4\xc9$d]\x1a\
-\xda\x88h\x18\x86\x14w\xf9R\x92\xd5;\xf0\xbc\x13\xa2,\xab\x8aIHT\x9b\xf0\x0f\
-\xa8\x13\x95\xab\xec1{_\x1a$*\x91\x17\x83*\xac\xe2\x1a\x90\x97\x01\xe7\x16\
-\xb3R\xb5\xd4\xde\x1a<\x93\x18|q\x94\xd3v%Ie\xbfg\x93H{7g\xd1\x86\xc4x\xaa\
-\x17\x80\xaat\x96E:\x0b\xcb\xf5gad\xb4\x1e\x05:^\x81\xf3\xd2\xe9\xce)-\xf1#\
-\x8ao\xd4\xebv\xf0\xf1T\xab\x95\xb7\x89e\xb5\xf4!q/t=\n\xda\x97\xa8Z\x8f\xfa\
-\xa3\xc8\tO\x8c[:\x9dR\x8f\xed\x1c\x13\xf1=\xa5T\\\xe4\x9csQ\xbfzK\xde\xef\
-\xbc\xb5\xc5\xc4\xfeM\xe1\x05\x15;rt-3\xf8^K\xb4~\x9bGU\x00 \x00q\xef\x85Y\'\
-S\xb2\xd3C\xb0\x13\x17T\xa1\xe3J\x92W\xb9eY\xeae$\xa4\n7\xafI&d\x0b\x00\xd4\
-\x8d\xe7\xd5\x8bO\xa8\x8f\xb4\xe0z\xb5A6@\xca\xb4\xf0H\xc8\tCu\xcb&|\x08\xf2\
-.\x92\xe1\x1f\x89\xbd\xea\x13g\x94\xcf\n\xa0\x18Jr>\xebt\x11@[\xdceD\x87\xa9\
-\xbc\xff)O-\xd2\xb1RJe\xf3\xd4\xee\x0f\xe2-\x81\xcdZ\xa3*\xbbt\x9a%:\xa2\xce\
-g\xfd\xdd \xbd.\x00YX\x15#!\xac\x89\xc1\x97W\x02\xc0\xa6iU]\xc6\x90\x8d\xb17\
-oo\xddG\xdfAI\x0f)\xc73q\x01\xa8\xbf\xbe\xfa\xc3%\xd6\'\x08!\xc2\xff \xb6(`\
-\x8c\'\xfc\xec\xb9\x0egHb$\x85\xa1\x03\x00j\xb2\xe2\x07\x982\x13\x17\x86\x98\
-\xf8\x13\xf7\xc2=\xf5\xbd\xebl\xa7\xdaO\t\x7fZb\xf4\xe4\x98\x88\tO\x15w\x19\
-\x8ds\xbf]\xdbzx\\\xa7\x8d\xd6{\xcb}v\xe4\xf0\x86\x8f\xf4\x0fX\x10 \xa7\x1b\
-\xe1\x8f\xd0s\xaa\x00\xc3\xf4FZ>\xe4\x1d)\xee\x02U\xfe\xa4jKqWWuT1\xb5\xd4\
-\xfc\xb4m\xa7\x9e\xaa\x05\xd2(\xb66\xc7\xed\xf6\xd1\xf7\xc4\xa5,\xd2U\xaaO\
-\x03\x9d\xb2\xab\xb6\xc7T\xde\xdd\x97O\x0c}\x8cw\xd8\xac\xec\x8a#"\xb9\xc0H6\
-\x9b\xd2\xc2\x0f`\xb4\x83\xe5\xc5\xf7"\xf0C\xd4\x00\xed\xb9Pu\xb0\x0c~#\x91\
-\xcd\x0f\xe6\x84\x10\xcb\xb2\xc4\x08\x80\xa2\xad\xb2#\xc2X\x96w\xf2\xfd\xca\
-\x90\x98<`\xf3\xe4\x9a\x98Y\x13\x93kb\xf0U;\xb1\xeb\xe0\xf7o\x92:\xc7t\xbd\
-\x00:\x7f}\xb5\xa9\xa2\xb5\xd2\xc8\x10\x87\x18\x86\xb1^\xa90\xa6\xbe\xb0\x03\
-\x84\x90|\xbe\x8b\xa7[*;\x00\x98\xa6)\xc4]\xeac\xd7\x8b\xf6\x9e\xa3\x9a\xb80\
-\x84\x16\xcb\x8b\xa1\xff\x03\x01\xd9l\xf1\xa8#\x9c\x98q;\x17K\xe4\x80\xc8\
-\xc9ULx\x8c11\xa4b\xf2\xeborm+b\xff\x86\xdcH\xaf\xd7\x8e\x1c\xb67,\xac\xec{B\
-\xab\xf3\xc8\x02\xb8\x97\xd9\x9e.L\xc8x\xca\x9d*\xee\x89{[\n\xa5\x0c\x9eI,\
-\xd9\xabf\xb2\xd9\x8e\xa1$m\xa7^\xc2\xc4\x03\xe5I\xbf\xff\xd3\xa8\xa0Z\xad]\
-\xbb6\xc7\x11\xbf\xf5\xf4\x93\xe2~\x90a\x03\xa2f\xf5\xf1\x1c6_\xd0\x89&\xa9\
-\x06W\xe2\x8c\t\xcbH\xde\x84\x9d"\xd2\xa9#\t\x83\x88(&\xb6\xa1x{\xe4\xb9\xd4\
-\xee\xf7\xbf\x91\xc4\xb9\xe6\x17\x16WVV\\\xd7=\xf9\xc4\t9\x03\x19\x86!\x03\
-\x16\x13G\xc9\x9b\xb9\xebL\xd6\xeb,\xbe\xef_\x9b\xbb\x0e\x00g\x9e9\r\x9b\'WY\
-\xa1\x18|1&\x97\xaf\\E\xc4}\xfb\xf6d=O\xb5\x9d\xe5\xc5 \xdb\xa0\xfe\xd0\xf2\
-\xb7\x1ep>Ce\xf7gU\xce\xd4\x9f>1\xb6\xaa\xbe\x9bm\xdf\xa0\xb4\xdc\xdbo\xedo\
-\x98z\xa9\x94\x9brS\x89!J\xfc\x88B\x8e\xa1}\x85\x0cr\xdd\xf6\xea\x8bza\xa8\
-\x0fs]}\x86\x9d\x95\x00\x804\xf0\xa5\x95\xa0*{\xe2vH\xfc\x16\xea\xf3\x01"\nq\
-\x97\xf7\xb2\x9c\x03\xee\xebQo\xeb\xe1\x91\xdfZ\xf9\x04\x88)v7\xdb\x07\xdfk\
-\x89\x06\xb4\xbe\x08\x00\xa4\xaf\x87\xc7\xb0R\xe9\xe2F:\xc9\x9d*\xee\xd0\xcd\
-p\x96vbW\x1f\x85zK\xcb{XX%\xa8\xec\xbc,\xcb\xab\xf6]\xe7\x05\xad\xbaY|\xdf?w\
-\xfe}\x00x\xe6\xf4\xd3\xf2\xf6^^.[\xb6\x95\xf1<Us\xb1\x1d\xdd\xa8\xb6GmR\x7f\
-e\xe9\x943q\xdd\xcb.\xaf\xae\xad9\x8e\x93\xcddd\x17T\xe1\xb3\x14\x07\xba\xda\
-\x0cKY\x04\xeb\xda\xfdN3V\x16\x16\x1f\xca\xcb\xe5s\xe7\xdf\x9f\x18? \xc4\x1d\
-\xdar\xd0\xe9\x94H\xd4\xdf\xdf{\xb0\xbc\\v]\'\x9f\xcf\x8b\x1a*\x15\xff\xdds\
-\xe7\x01\xe0\x99\xd3\xdf\x92\xe5eG\xd4\xc1\x11\xa7\xfel\xe62\x00\x8c\x8d\x95\
-\xb2\xd9l\xe7\xe0\x93\xb6gF\xfd\x89\xd5\xc1\x19\x10q\x15\x01\xc0\x97\xb3\xd7\
-\x0e\x1c\xd8\xef\xa5\xd3\xe2:T/\xc2\xae^\x08\xf5"\x14\xb1\xed\x88\xb8\xb2\
-\xb2\xdahe\xcb\xdah\xc3\xde={\xa4\x87:q%\x88\xd9Z\xf4W\x8a\xbb\xfa\xc3\r\xde\
-\x1192\xb0\xf9\xc2\x00e"\xe9\xf3cu\xb6M\x1e%\x05Z\x9a \x1dq2\x9bn\x04\xd9rq{\
-\x8a;\x14\x15\xff\xea\xfd\xce[[OT\xb9!"\xa3:3\xf1J\xdc\xdc\xde\x81\xf7Z\xba\
-\x8e\xbc\x15C\x15\xf7Y\x98\x1d>B\x8c\rI\xdf\xc1\xe2.\x90\x96\x82a\x18\xc2w,\
-\xaf\x9eN\xb5\x92w\xb5\xbc2\xa4\xb8\'\xd4M=\xa4\xd3@\xc0\xcd\xc8+U^\xd6\x9f_\
-\xfd\xe2\xec\xdb\xef\xb8\xae\xf3\xf2K?\x19\x1d\x19\x91\x95\xab\xfa\xd2\xd9\
-\x9e>\xb2\x9e\xe8\xaf*\x10\xb2\xd7W\xaf~\xf9\xee{\xe7\x1d\xc7\xf9\xd3\x1f\
-\xff\xf1\xe8\xe8HB\xc8\xe4m)\xeb\x97\xdfw\xf6\xbd\x7f\xf7;\x07\x81\xb7_\x1fL\
-\xdc\xd8\xea\x14\xd8\xbf~\xf9s\xc8\xca/_\xfe\xfc\xad\xb3o\xbb\xae\xfb\xef~\
-\xfag\xa3\xa3#\tE\xc0\xcd/\x01\xc8\x9fR\x8c\x86\xdaf\xe1\xfcQ\xbb\x90\x18|\
-\xf5z\xb8/\xbdH\xb4\xf6\xec\x1f\xfe\xb0\x7f\xdf\xbe?\xfe\xd1\x1f\x89(\xbd\
-\xc4E\xd2g`\xa5u\xef\xfb\xd5\x1b7\xe7\x13IT\x081&&\xc6m\xdb\xeel\x9bly\xa7\
-\xe9\xd0\xff\xb7\xeb\x83z\xc1t\xceI\x83W\x98\xb8P\xe5-\x99\xb0\xa2\xa0c\xc2K\
-\x9cBH\xf97\xd5\xbb\xad\x81\x05k<l%!\xef\xe3\x1cO\x17\x0f\rR\x1b\x8f*\xacY\
-\x06\x00"\x92\xb6\xf7\xf0\xf0X\xe9\xa2\x9b\x1d\xdb\xf4\xcd}4y\xbb"\x7f`q\x11\
-\xc8K\x01\x94\xdb/qYt\x1e\xdbK\x80\x12\x1f$\xa8\xbc\xc0\xc2\xda\xa2#\x9e\xac\
-\x11qa\xf1\x16\x00\x84at\xfb\xf6\xf2\xbe\xbd{;\xeb\xef\xda\x9e\xfb\xbdm\xc4\
-\x12\x93\xf8\xd20\x8c\xa5;w\x00 \x8a\xa2\xe5\xf2\xca\xfe\xfd\xfbd\xcd\xea\
-\x87\xc4Y\xc8fI\x1d\xb0\xfbrJS\xff\x95\x7fHt\xea\x9e\x95\xab\xcdP\xa5p~a\x01\
-\x00\xc20\xfcj~\xa1X,$\xc6\xd9\xe8\xb6\xe0\x9c\xb0\x07\xc5\x07\xcb2]\xd7M\
-\x94\xec?,\x83\xa0\xb6v\xe9\xf6\xed\xf3\x1f|\xc89.,\xde\xfa\x977\xde\xfc7/\
-\xbe\xb0\x7f\xff>\xb5\xfb\t-SO\'\x07\xb0\xd9l\xce\\\xf9\xbc\xdehl~\x82\'\xc5\
-b\xbe\xd4\x9e\xaa;{\x01\x1d?b\xd7\xcb{@z\xd5\xd9\xf9\xa7\xc1k#\xed\'\x1bu\
-\xb2\xef: \x89{A\xce+\xdfT\xef\xb6\x8c\xd8\xbf!>\xf41\xdb\xd3\x85\xf1\xc1\
-\xf6Z\xc2\xb8\xfd\x10\x80\xf7\xf0\xf0$\x17fw\x83\xb8\x0b\xd4{\xa6\x97\xa0@\
-\xef\x0b\xe2\xbe.\x14lo\xe2\xda\xf2!\xb6\xdf\x17\x17{m\x03\xc0\xd3ON\xaf\x94\
-W\xf2\xf9\xdc\xf4\xa9\'\xfa??\x0e~^\xd9\xa9\x99\xcbW\xae]\x9b+\x95J\xdf}~#\
-\xed;\xe7\xfc\xe9\xa7\xa6WVV\xc5I\xadv$Y\x9f\xb3<\xc0\xed\xaa6\x06\xdb\xa1{\
-\x1b\xe1(JlR\xffS\xf7\xa9V\x8a\xddSON/\x97Wr\xb9\xec\xb1c\x93"\x92G\x8es\xaf\
-\xdf\x17\x11\xe5R\xc7F\x03\x80HgT\x82\x07V\x079\xbdq\xce\xab\xd5\xda\xd9\xb7\
-\xdfi6\x9b\xe2\xd1\xa5\\^y\xe3\xcd\xdf~\xeb\xe9\xa7N\x9d<\x99N\xa7\xfa\x9cBV\
-\xc2\x18\xbbsg\xf9\xd2\xc7\x9f\x94\xcb\x89\x9d\xcd\xd14\xad\xa9\xc9I\xcf\xf3\
-zY\xa9_\xe7G\xec\xc57^\xe7\x83\xdd\x9b;B\xc7;a\x8de\x11\xb0(\xb4\xb8{!B\xdc\
-\xdc\xbe\xc1j+\xf3p\x1d\x00\x08\x81>\xc9\x06\xdc\xdc\xde\xcet\x92\xbbG\xdcU\
-\x1e\xea\xa5\xa0\xde\x93b\x8d\x88\xb2\x96\xe80\xc6\xc4\xa9\x87\x86\x8a\x7f\
-\xf5\x1f\xfeB\x86\x16|\xcd\xf6\xa8\xd6\x1f"\x86A8\xbf\xb0\x88\x9b[\x82\x88\
-\xc3CC\xff\xf1\x9b;\xe9=\x11\x13[{\x95\x8c\xb5\x1a\xfa\xa0\xef\xbbK+Xv\xa7X,\
-\xfc\xfb\xbfxE\x9dE\xa4d\xab\x0f\xe9\x89\x1a\xe4\xef"["\xdf\\\xff\xa6\x06D\
-\x9e\x88RZ\xad\xd6\xdey\xf7\xbd\xbbw\xd7\xc5\x19D\x81F\xa3y\xee\xbd\xf7\xaf_\
-\xbf\xf0xc\xbe\x00\x00 \x00IDATy\xf8\xf0\xa1\xc9\xa3G\x86\x86\x8a}*YZ\xba=w\
-\xe3\xc6\xdc\xb5\xb9F3\xe8H3@\xf6\xee\x1d;tp\xa2\xab\xbfb\x87\xb2\x0b\xba\
-\xd0\x0f\xe4\x91L\xed\xdb/\x14}t \xb3\x1d\x99|\x08@\xec9U\x10\xd3\xf6\x86\
-\x0ew~\xbf;\xc5}\x0bH\xe8\xbb\xf8R\x8a\xbb\xe0\x1b\xbc\x1bQYE\xe0b\x8f\x11\
-\xdcp\r\xa9\xb1.[#\x01B\x9bD\xf7\x13\x96{\xa7\xec\xdeW\xb5\xb2;r2\x93\xdf\
-\xa8\xe2\xde\xe7\xf0\xa4\xb8?\xe0t\xd3\xb3\x85R\xd9\xe38\xfe\xfc\xea\xd5\x85\
-\xc5E\xd5\x81 \xb9}\xe7\xce\xed;w>\xfdlf\xcfXi\xff\xfe}CCE\x11\xe3!\xc6-\x08\
-\xc2\xd5\xd5\xd5\xc5\xc5[\xabkw\x83@\xca\xfa\xa6\x1a\xf2\xf9\xdc\x93\xa7Nn\
-\xff\xc8\x10\x8ddSj\xdf\x9e;r\x18\xde\xd0\x91\x81j\xab\xddB\xdaDa\xb6\xf7\
-\xcfKcuI\xbd\xb9\x1b\xc4]\xde\xc6\xe5\xf2\xca\xfc\xc2\xc2\xc2\xc2"\x00\xe4\
-\xf3\xf9\xb1\xb1\xd2\xf4\xa9\x93j\x99\x0b\x17/\x01\xc0\xd4\xe4Q\xd7ug._)\x97\
-W\xc20t]\xb7T\x1a}r\xfa\x94\xf4\xcc\xde\xb3\xf0\x13\'\x8e\x8b\x95\xcc\x96\
-\x0c\xb5S)\t\x9f;\x00\xd4j\xb5\xf9\x85E\xc30D\xd0\x9e*F\xe5\xf2\xca\xec\xb5\
-\xb9r\xb9\xac6R\x16\x08\xc3p\xf6\xda\\\xb9\xbc\xe2\xfb>\x00\x8c\x8f\x1f\x98\
-\x9a<\x9a\xcb\xe5\x84f]\xfa\xf8\x13D\xb8\xb5\xb4\x04\x00\xbe\xef_\xb8\xf81\
-\x00\xda\xb6=y\xf4\x08\xe7\xbc\xcfI}\xdf\x9f\xbd6\xd7udz\xf5W\x16\x9e\x9a\
-\x9a\x9c\x18O&\xbeP\xfd\x12\x1b\xef(!r\xceo--\xcd\xcd\xdd\x10\xed/\x95J\x13\
-\x13\xe3\xf2py"B\xc8\xd4\xe4\xd1|>\xaf~?s\xf9J\xb3\x19\x0c\x0f\x15\xc7\xc6J\
-\xa2;7\xbf\x9aG\xc4\'N\x1c\x97\x81\x16\xea\xd9\xd5>\xce/,\xce\xcf\xcf//\x979\
-\xe2P\xb1p\xe8\xe0\x84\'S\xabo.\x1c\x86\xa1\xfc5\xc5P\x88.\xcb\x02\xf3\x0b\
-\x8b\xe5r\xd9u\xdd\xe9S\'\xe7\x17\x16\x17\x16\x16\xcb\xe5\xf2\x0b/<?V*\xc9\
-\xfa\x84\xb8GQT\xc8\xe7\xb3\xd9\x8c\xb2GG\xab\x88\x94\xe9z\xbd6w\xbd~\xfd\
-\xc6M\xc30TqFL\xec\x9aB\x12f{:\x9dzj\xfaT.\x97\xb3\xee\xe7%a\xcd#\x049\x8dk\
-\xf3\xe2s\x9f\xd4\xbenn\xaf\xe9x\xdd\xfe\xb2\xb96\x16\xc5\xd5yqMp\xdes\xf3&\
-\xd3\xf1z\xe5\xa5\xd9\xd9\xe2.o\xf2 \x08~\x7f\xf6\x0f"\x08\xba\xcd\xe2\xcc\
-\xe5+\x9f\xcd\\~\xe5g\xff\xd6u]Q\xec\xdc{\xef\x03\x00\xe7\xfc\xd2\xc7\x9f\
-\x86\xe1\xc6\x1e1\xb3\xd7\xe6.\\\xfc\xf8\xe7\xff\xee\xa7\xa5\xd2\xa8\xac\xb3\
-\x7f\xe1\x97\xfe\xf4O\xf2\xb9\\K\xe00\x19\xc7]\xab7>\xbap\t\x00\xce<sZ1!\xf1\
-\xf7g\xffp\xf9\xca\xe7\x9d\x8d\xfc\xe1\x0f\xbe?::2{m\xee\xec\xdb\xef\xa8\xe7\
-\x9a_X<\xf7\xde\xfb?\xf8\xfe\xf7\x8eMM2\xc6>\xf8\xf0\x82\xfc\x93_\xad\xbe\
-\xff\xc1\x87\x00\xb0\x7f\xdf\xbe\xc9\xa3G\xfa\x9c\xf4\xc2\xc5\x8f\xcf\xbf\
-\xffA\xe7I_\xfe\xd3\x1f\x8b(\xc3\xbe\xfd]\x9c\xb9|\xe5\x87\x7f\xf4\xfd\'\xa7\
-Ou\x1d\xfc\xb6\xc4\xb7\xd4\xf3\xec\xdb\xef\\\xfd\xe2K\xb5\xfd\x17.^\x9a>u\
-\xf2\x87\x7f\xf4}\xd9$q\xa2\xd1\xd1Q\xf1\xd6\xa5T\xdek\xd7\xe6\x16\x16o=\xf7\
-\xec\x99\xb1\xb1\x12\x00Tk\xf5\x8b\x97>\x01\x80\x13\xc7\x8f%B,ZO0\x9c\x8b\
-\x9f\xfe\xbd\xf3\x1f\xa8\xa3\xba\xb4t\xfb\xf2\x95\xab\xcf|\xebi\xb5\x9d\xa2\
-\xf0\xcc\xe5+\xe7\xdf\xff0\xd1\xbb\x8f.\\|\xf9O\x7f2::"\n///\xbfw\xfe\x83\
-\xf1\x03\xfb\x97\x97\xcb\xb2\xdag\x82P>\x91\xc8G\x96(\x8a\xb2\xd9\xcc\xd3ON\
-\x7f\xf1\xe5\xec\xea\xda\x9a\x8c\x18J\xb8V\xe4!\xea\xe0)j.5}\xe3\xa8l63}\xea\
-\x89\xd1\xd1\x11\xf5\x8d\x1e\xd0lohm\x11i\x00 \xf25\xde\x9f\x0b\xa5\x93\xb8z\
-\x13\xd5\xa4\xed=\xc3\x1f\x8f\xf6\xcaK\xb3\x83\xc5]\x15\x17JY\xa5\xe2\xe7r\
-\xd9\xa7\x9fz\xf2\x89\x13\xc7\t!KK\xb7\xdf\xff\xe0\xa3ryE\x88\xa3\xea\xb88\
-\xff\xfe\x87\xae\xe3<\xf7\xec\x99\xd1\x91aBH\xadV\xff\xf8\x93O\xfdj\xf5\x97\
-\xff\xdf\xaf\xff\xc3_\xfe\xb9Hlt\xcf\xc2\xaf\xbf\xf1\xdb?{\xe9\'\xae\xeb$\\\
-\x04\xad\xb5\xa3n\x8d<\xff~K\x83\x9e{\xf6Lit\x04\x08Y]]\xfb\xf8\x93O\x01\x81\
-qF)m6\x9ba\x18\x9e8~\xec\xe9\xa7\x9e\xcc\xe7sqL?\xbf\xfa\xc5\xfb\x1f|x\xf6\
-\xedwFGGR\xae\xfb\xf4S\xd3\x88\xb8\xbc\\\xbe\xb3\\\xcef\xb3\'\x8e\x1f#\x848\
-\x8eM:\x1e\xda\xe5y/^\xfa\xf8\xfd\x0f>\x02\x80\x13\xc7\x8f\x1d9|\x88\x18F\
-\xadV\xfb\xf8\x93O\xcb\xe5\x15\xd1_\xc7qT\x13\xf2\xfc\xfb\x1f\x8e\x8e\x8c\
-\xbc\xf8\xc2\xf3)\xd7\x8d\xa2\xe8\x93Og\xca++o\xfd\xfe\xed\x89\xf1\x03\x85B\
-\x97\r\xc0T\x17\xca\xd2\xed;K\xb7\xef\xec\xdf\xb7\xf7\xc4\x89\xe3)\xd7\x05Bn\
-\xdc\xb8y\xe5\xf3\xab3\x97\xaf8\x8e\xf3\xfcw\x9e\xdb4V\x8aA\x8d\xaarC2\xdef\
-\xa38l\x88\xbbxH\xe2\x9c\xbf{\xee\xfc\xe7W\xbf\x00\x80\xe3\xc7\xa7&\x0e\x1c`\
-\x8c\xf9\xd5\xea\xd5\xab_^\xfc\xf8\x93V\x0b\xdb\x1e\xad\x85\xc5\xc5\xb3o\xbf\
-#\xc6\xff\xe8\x91C\xf9|\xbeV\xaf\x7f\xfc\xf1\xa7W>\xbf\xfa\xfao\xde\xfc\xf7\
-\x7f\xf1s1\x14\x8cq\x00XX\xbc\x05\x8b\xb7FGG&\x8f\x1e\x89\xa2x\xfc\xc0~u`\
-\xa5\xf3\'\x8ec\xd34\x8e\x1e9\x94\xcdxw\x96W\xda9\xdc\xef\t\xc1\x8d\r\xbc\
-\x89j\xb1\x13B\xf6\x8c\x95\xa6&\x8f\x14\n\x05\x99\x9ba\xd78\xdcw1\xc8cZk\xed\
-\xd0\xdb\'GX\xaa0nX\xf7\xcew\xc6\xe3\x9a\xcc8\xc6\xfa\xbe\xe0\xeadF{U\xb2S\
-\xc5]\x15M\xc6\x98i\x1a?\xf9\xf1\x8f\x10Q\xbc\xe8L\x08\xd9\xb3g\xec\xb9\xe7\
-\xce\xfc\xcb\xeb\xbf\xb967\xf7\xc2w\xbf\xa3\xea\xb5\xe38\x7f\xf2\xc7?\x14)\
-\xf4D\xc9C\x87\x0f\xbe\xfe\xc6\x9b++\xab\x17.^\xfa\xde\x8b\xdfU=\x00\x9d\x85\
-\x0f\x1e\x9ax\xe3\x8d\xdf\xae\xac\xae\xce\\\xbe\xf2\xcc\xe9\xa7\xef\xd9N\xa1\
-,\xeb\xeb\x95\x8f?\xf9\x0c\x00\x9e\xff\xces\xc7\xa6&\xc5\x83\xf6\xbe\xbd{\
-\xf6\xed\xdb\x93\xcf\xe5\x1d\xc7f\x8cMM\x1e-\x95F\xc5+H\x94R\xc3 \xd3\xa7\
-\x9eXX\\\xbcukin\xee\xfa\xb1\xa9\xc9\xa3G\x0e\x8bP\x90;\xcb\xe5l\xc6;u\xf2\
-\x84|\xbfQ=\xa9\x8c\xc8\\_\xaf\x08e\x17\'%\x84\x18\x86\xb1w\xcf\xd8\xe1C\x87\
-\xfe\xe5\x8d\xdf\xac\xac\xac\x9e{\xef\xfdD\x7f\xb3\xd9\xec\x1f\xff\xe8\x07\
-\x8e\xe3\x88\xc2\xc3\xc3\xc3\xaf\xfe\xcf\xff\x17\x00\xe6\xe7\x17\xf3\xf9|W}\
-\xe1\x9cc\xfb\xd9e\xf2\xe8\x91\xef>\xffm1V\xa2\x83##\xc3\x7fx\xe7\xdc\xc5K\
-\x1f\x9f8>\xa5\xeey\xcf\x95_P5\xcc7\xe9\xfc\xbdF\xb5R\xf1\x85\xb2?\xff\x9d\
-\xe7&\x8f\x1e\x11\x82[,\x16\xc6J\xa3\xef\x9d\xffp\xbdR\x01\x00l;\xeb\xdf\xfe\
-\xc3\xbb\x00\xf0\xdd\xe7\xbf-\x86"\x8e\xe3t*\xf5\xe2\x0b\xcf\x87a8w\xfd\xc6g\
-3W\x9ez\xf2\x14\xe7\x9c\xb5\x97\xc7\x9f\x9c>\xf5\x9do?+\xe3\xacep^bN\x12-\
-\x19\x1e\x1e\xf2\xbc\xf4\xddu\x7f\xbdRi6\xbbJ\xbc\xe8T;\x10E\xcd.\xd06\xe1\
-\x0b\x85\xc2Xi\xf4\xc0\xfe}\xe9t:\x91\x9c\xb2\xff\x80h\x1e9\xb1\xff\x950\xb4\
-\xc5\x1b\xa4]\x7f0b\xb9\xe9\xc2@\xa9}\xe3\xcau\xb9X\xd4;\xfc\xd1\xe8\xff\x82\
-\xeb\xcev\xe4Iqg\x8c\t\xeb&\x8ec\x91\xa5(\x8a\xa2\xb1\xd2(\x00\x84a$\x9e\xa0\
-\xe5\xc3\xf8\xd1#\x87\xb2\xd9L\xa4`\x10\xf2\xd4\xf4)\x00\xb8\xf2\xf9Uql\xff\
-\xc2\xd3\xa7\x9e\x00\x80\xd9ks\x9d\xedi\xdd\xf0\xed\x1fF4/\x8e\xe3\xd9k\xd7\
-\x00`h\xa8x\xe8\xe0D\xdc\x86R:T,Z\x96)\x93\x97\xa5S)\xf1\'q\xae8\x8e\xf7\xee\
-\x19\x03\x00\xc6Z+xq\x1c\x0b\xd3\x12\xdba\x7f\x1b+\xaa\x9bOJ)\xbd67\x07\x00\
-\xc3CC\x87\x0f\x1d\xdc\xd4\x05\x83<\xf5\xe44\x00\xcc\xcd]\x97\xe7\x12\xc7\
-\x1e9|\x08\x11e\xc9T\xca\xdd\xbfo\x1f\x00\x04a\x17\xc1R\x05N|s\xea\xd4\x13r\
-\xfc\x05\x93G\x8f\x8c\x8c\x0c\x03\xc0\x97\xb3s\xa2\xfd\xadc\x95eR\xd1w)\x9a\
-\xed\x95\x8c\x9e)My;\x1d\x90\x1c\xd5\xc3\x87\x0e\xaa\xf9 \r\xc3\x98<zX-|gy\
-\xb9Z\xade\xb3\x19QR4R\x94?tp\x02\x00\x16\x17\x17\xc5\xf7\x942\x00p\x1c\xe7\
-\xe9\xa7\xa6e.9Q\x95\x1a\xd2\'\xdf\xa8\x94\x96\xb5\xeb\xba{\xf7\x94\x8e\x1e>\
-81~`h\xa8\xe0&\x13\xd2&\xd7K%\xae\xeb\x0e\x0f\x0f\x1d9|\xe8\xc4\xb1\xc9\x03\
-\xfb\xf7\xa5R\xa9T*\x95\xc8\x97\xa9\xf5};\x834\xa0\r%\xb5o\x8fb\x99\xcdo\x90\
-\xf6\x82\x05k\xac\xbd\x7fr\x9fH\xf9T~_\xff\x17\\w\xaa\xe5\x0e\x1d\x0f\xc8\
-\xcdfp\xf3\xab\xaf\xee,\x97\xd7\xd7\xd7\xeb\xf5M\xdb\xdfDQ\xa4\xc6\xb4\x8c\
-\x8c\x8c\xc8\x80th\xbf[q\xa0\xfd\xdc=\xbf\xb002<\xdc\xa70\xe7\\\xbe\xa2r\xe7\
-\xce\xb2P.\xd9$\xbe\xf9\xedj\xf1Z9\xa5\xf4\xd6\xd2m\x008\xb0\x7f\xbf\x88\xa8\
-I\x18\xa7\xb2#++\xab\xf3\x0b\x0bw\xefV\x96\xcb\xe5\xcd\x05\xb8\x92\x97\xa3\
-\x15\x9a",t\xe1\x9dP\x1fM\xc4\n\'c\xac\xbc\xb2\n\x00\xfb\xf7\xef\xa3\xed\x08\
-q\xd2~]P\xf8\x19\xc2(\xbasg9\x97\xcb\xca\xfe\xe6\xf39\xd9_\xd2z\xf5\x91\xcb\
-\xd1\xee\x94\x18\xd9e\x00(\x8d\x8e\x98\x86!\x9f\x03H;\xf1\xd3\xc1\xf1\xf1\
-\xd5\xd5\xb5\xc5[\xb7\x8e\x1f\x9b\x945pe=\xb6\xa5\xa1\x1c\x01\x80\xb1v>\x96\
-\xcd\x1ev\x15\xd1Z\xce\xb9\x1c\xd5V \xbc\x92cg\xef\xdeV\x1aU\xcey\x1c\xc7b}8\
-\n\xa3\x7f}\xeb\xac\xac\x87\x00!\x04\xa28\x06\x80[K\xb7\xd5\xeb\xa4X,\x08\
-\x8b\x01\x15\xe4\xb0$\x12\xc8\x88\x0bC\xf4\xc2u\xdd\x92\xeb\x8e\x0c\x17\x19\
-\xe7A\x10\xd4\xeb\xcd \x0c)\xa5\x9c3\xe4\xadH\'\x83\x18\xc4 \x96i\xa6Rn6\x93\
-\xc9d<\x99\xfdX\xec" \xc4\xfd\x01\xf2\xc3h\x1e\t\xb1\x7f\x038\x07\x02\xbc\
-\xf7\x1b\xa4\xa6\x93\x19,\xb5/\xc6\x95\x96\xd5\xd8?R>U\xb8\xc7\xb6|;X\xdcA\
-\xb1\x1c\xc30\xfc\xcdo\xff\xb5^\xaf\xdb\xb6],\x16\x0e\x1f:(T\xef\xd3\xcf.C;\
-\xdf\x9e\xba\xa2e\xb4\xb7\xe9\x10\x8a\xb0Y\x8e[\x86d\x9f\xc2\x8aBq\xd5\x8b\
-\xc0\xdb\xa9eQ\x11wy\x94\xf8\x86t{\xcdZT~\xf9\xca\xd5O>\xfd\x0c\x00D\x172\
-\x19\x8f\x10\xb2\\^Y^.\x8b\xa3I+\xf5\xc7\xa6wL\x94\xa0\x9d\xe4I\xe5\xf3\x87\
-\\\t\xec\xeco3h\xa6\xd3\xa9\xcd\xcb}\x1b\x9e\x07Ji;\x10f\x93\xc0%J\xaac%\x87\
-K\x88\xaca\x18\x8c3\x00@\x8e\xea4\x99\xb0\xdc\xf9F\xec\xa3L\xb6\xd53\xf0Qv\
-\xa1\xfd\xa7M\xaf\xc8\xcb\xd6\xca\xd1\x88\xda\x9bBDq\xbc\xbc\\\x86\x1e\xb4\
-\x93E\xb3v\x95\xa0V+\x95\x9d\xb4\xf3\xae\x88\x18\x1b\xa2$\xd6\x97\xb9\xb1\
-\xc4\x04\xe0\xd8v\xa1\x1d\x11\xd4\x15y\xac\x9aP>\x95JIo\xbb\x96\xf5\xed\x0f\
-\x0b\xee\xd2f\x19\x00\x00\xfb\xbdA\x9a)\x1d\x87\x01~\xcd\xd8\xbf\xc9\xe3V\
-\xf0\x15\xa5\xd8s7\xed\xfc\x81{F\xca\xeflq\x87\xf6\x9d\xff\xc1\x87\x17\xea\
-\xf5\xfa\xd8X\xe9\xdb\xcf>c\xb6\xf7O0\x0cC\x88;\xdb\x9c!\xb2\xd9l\x8a\xb4\
-\x85mC\x189\xe72\x9a-\x9dJ\xa9Z\xdcY\x981\xd6h\x88\xd4N\xe0\xa57\x854Iq\xef4\
-\xa23\x9e\x07\x00\x8dFC<hwF.\xaf\xae\xad\te\xff\xce\xb7\x9f\xdd\xb7w\x8f\x90\
-\'!\x94\xcb\xcbeB6t\xd30\x88\xec\xa00\xae\xf9\xe6HA)\xee\xc5Bay\xb9\xdch4d\
-\x02U\xf9`Q\xab\x89\xd4\x83\x90\xcf\xe5\xd4\xfe\x1a\x84\xa8\x99\xd48\xe7b\
-\x19S\x95K\x15U\xf8\x1a\xcd\xa6\xa5d\xc9\x1750\xc6\xaa\xd5\x1a\x00x^Z8LZ?\\{\
-\xbe\x90\x12/\\C\x9c\xb7\xa6\x1f\xec\xd8\xabS\x9e_\xfej\xedQm\xcaL\xc8\xb2\
-\xa9\xf27\x12n\x19qU\x94FG^|\xe1yy\xe5H\xa5\x96\xc3\xb8\xb1zA@\xa6ZK\xfcRD\
-\xd9\'D\xb5\xe2\x13\xe9\x97\xf9\xa6H\xc7.\x83&\x1d;B\xd9\xd5\x84\xfe\x83$\
-\x94\xd7l\x0f0\xf6\xe7\xc4\xfa)\xa5}\xde \xdd\xd7\xf9\x06i\'\x9c6h\xb5\x15L\
-\xc99P\xd6\xbd6\xc3Jy\xc3\x87\xefY\xdbN\xf5\xb9K]\x10\xff~5\xbf\x00\x00\'\
-\x8eMY\xca\xb6\xc5\xabkkjay\xec\xe2\xad%K\xd9\x01G\x14\x98_X\x04\x80L\xc6K\
-\xa7S\xea\xfad\xd7\xc2\x0b\x8b\x8b\x00\x90\xf1\xbct:\xa5\xd6\xbc\xa1V\xb8!\
-\xee\xe2V/\x16\x0b\x00\xb0\xb0x\x8bs.w\\2\x95\xec\xd8\xb7o/\x03\xc0\xd8X\xe9\
-\xc0\xfe}\xd2L\xe6\x9c\xcb\xb7\x1f\xd5\xb4|\x00@\xda\xf9\xb96\xce\xab\x9cT\
-\x8c\xcc\x90rR\x99\\W\xedo\xb1X\x10R.\xbb\xa0\xa6\xe1\x15%\xa1e\xb9\xf7\xfc-\
-\x08!by\xb0^o\xac\xac\xae&\xa2\xf7\xc20\x14\x99v\xf2\xf9\x9c:\x03U\xfd\x9a\
-\xda\xcdZ\xad~w]\xf4\xb4\xfd\xb3\xb6\xbb\xd39\xaf\xc8\x87\x95ba\xa3\x83\x89}\
-6\x96n\xb7\xf6I`\x9c3\xc6\x86\x87\x86\x00\xa0\xbc\xb2\xda>\xcbF\xcd\xd5Z-\
-\x0cCuJ\x86\xb6jK\x97\xfa\xa6\xce*\xe6\xb60\xb4\xe5\x16t\x12\xcf\xf3\xd2\xe9\
-\xb4X\x17\x95\xbb;\xc9m\xfc\xe4Q\x9e\xe7%\x0e\x91\t\xee\xb5\xb2\xef\x08hc\
-\x99Gu }\xc3\x1f\r+]<8Hmq\xe5\xba\x8cM\x88Y\xcf{\xce\x1b><\x88\xef~G\x8a;\
-\xb6\xc3\x9c\xa5\xc5\xea86\x00\xc8\xfb\x16\x11\xc30\x14\xd1)\xd0\xe1/\xbesg\
-\xf9\x83\x0f/\x08\xbd\x13Z\xf9\xc5\x97\xb33\x97\xaf\x00\xc0\xe1C\x07\xf9\xe6\
-u\xbc\xce\xc2_\xce^\x9b\xb9\xfc9\x00\x1c:4\xd1i\x9a\xb5\xf5*\xf1\xbfx\xe4\
-\xf0\xa1L&\x13E\xd1\xff\xfe\xdd\xef\xc5\xd3\x80\x90\xbf\x0f?\xbax\xf5\x8b/\
-\xa5\x95W\xaf\xd7\x85\x01+\x8e\xba\xf9\xd5\xfc\xe2\xad%\x00\x10\x96\xbb\xd0\
-\x1aB\x0c\x00X\xbb\xbb.\xbc\x1c\x8a\xbeo\x9cTTx\xe0\xc0\xfe\xa1\xa1b\x14E\
-\xff\xfa\xd6\xd9 \x08\xe5I\xaf~1\xfb\xd9\xcc\x15\x008~lJ\x1c"\x07\xc7\xdc\
-\x9cY^\xd4\'\xbb\xd2\xf9[\xb4\xec\xf6\xf6\x0e\x9f\x7fx\xe7\xbdryE*l\xbd\xdex\
-\xe7\xdd\xf3q\x1c{\x9ew\xe8\xe0\x84\xe8\xd4\x9e\xb1\x12\x00|\xfa\xd9L\x14E\
-\xe2\x9b0\x8a\xce\x7f\xf0a\xbb\xf1r\xdc\xfa]\x00\xadQ=r\xc8q\xecD\x07\r\xc3\
-\x98\x9b\xbb!\xc3\xed\xc5E\x92J\xb9\xe2]\xaas\xef}\xa0\xa6p\xb9\xbb\xbe\xfe\
-\xc1\x87\x17\xfe\xf0\xee{\xeb\xeb\x15)\xdc\x00@\x80$\xccg9D\xaa\xbe[\xed\xcd\
-f\xa5Rg\xb3\xd9l6+>\xc8\x7fU\xb2\x9b\x91\xb2\xaezc\xb4\xb2\xef\x0c\x90\xcb\
-\xdd\x91\x18\xeb\xedB\x19,G\x18\x0b\xd7Y\xb3ue\xf6YG\x1d|[\xbe\x9d\xea\x96\
-\x91\x16\x9f\xf0\xd8\x1e?v\xec\xb3\x99\xcb\x9f\xcd\\\xa9\xd5\xea\x9e\x97\x0e\
-\xa3hi\xe9\xb6m\xdb\xc5Ba\xbdRI\xe8\xc4\xf0\xf0\xd0\xd5/\xbe\xbcq\xe3\xe6\
-\xf0\xc80r^\xad\xd5\xeb\xf5:\x00\x1c>t\xf0\xd8\xd4\xa4\\\x0c\xec_\xf8\xd0\
-\xc1\x89\xa9\xc9\xa3\xd0\xcd#\xdc\xd9N\xf1\xf9{/<\xff\xd6\xd9?\xac\xae\xae\
-\xfd\xe2\xd5\xd7\xc4\xfa\xad\xefW\xab\xd5*\x00\xa4\\\xf7\xf0\xe1\x83\x9f\x7f\
-~\xb5^o\xbcu\xf6\x0fB\x07\xd7+\xfe\xd2\xd2\xed#\x87\x0f]\xbfqS\xea\x8ba\x18{\
-\xc6J3\x97\xafDQ\xf4\xcf\xff\xeb\xf5l6[\xadV\xff\xec\xa5\x9f\xf4j\xc0\x8b/<\
-\xff\xfb\xb3\xef\xac\xae\xae\xfd?\xaf\xfd\xd3\xbe}{\xd5.L\x9f:\xb9\x7f\xdf^\
-\xd8p\xe5\x03(;\xb5b\xf2\xbd\x9b{\x93\xcdf\xa2(~\xfd7\xbf\x1d\x19\x19vl\x9b#\
-\xde\xb9\xb3\x0c\x00\xb6m?w\xa6\xf5^\x15"\x1e9r\xf8\xcer\xb9Z\xab\xbd\xfa?\
-\xffidx\x88#\xae\xad\xdd\x05\x80\x8c\xe7\xd5\x1b\x8d\xfe\xa7\xe8\x1c\xd5\x17\
-\xbf\xfb\xfc\xbb\xef\xbd/;(<N\xf5z\xa3\x90\xcfW|_\x96\x07\x80\x13\xc7\xa7\
-\xfcj\xb5R\xf1\xdf}\xef\xfdb\xb1 6,\x14B_,\x16\xb2\xd9\x8c\xf4\xaa\x03\x001\
-\x88\x1a\t\xd3\xd9\x06\xf1\xa5\x98,\xe5\xbc+\x97X7\x16B:\xa6)\xd5\'\xa3\xc6\
-\xdbt\x9dH4\xdb\x19Z_B\xda\x04\x02\x88\xd8\xeb\rRb\xb9\xea\x06\x1a\xbd\xc1\
-\xb8\xd2z\x07\xb3\xdf:*@fdj\x10\xdf=\xechqW-\xf7cSG\x11\xf1\x8b/go\xdclM\xa4\
-\x07\x0f\x8e\x9f<q\xfc\xc2\xa5O\xd4C\xc4\x87o=\xfd\x94\xef\xfb\x9f~vyi\xe9\
-\xb6\xf8\xc6\xb6\xed\xe3\xc7&\x8f\x1f\x9bB\xe5-\xc4>\x85\x8fM\x1d\x9d\x9a<:\
-\xc8\x1d\xa8\x96)\x14\xf2?\xfa\xe1\xf7?\xf9tfi\xe9\xf6\xe2\xe2-\xf1e6\x9b=\
-\xf9\xc4\xf1\x91\x91a\xce\xf9\xf7^\xfc\xee\xcc\xe5+\xcb\xe5\x95\xcbW\xae\x02\
-\x80\xe7y\xcf=\xfbL*\xe5^\xbfqS\x9a\xc8\xa2\x9e\'N\x1c\xff\xfc\xea\x17\xd5jM\
-\xb8\xb3\xbb\x9eTt!\xe3y/\xff\xe9\x9f|\xf2\xe9\xcc\x17_\xce\xca.d2\xde\xf4\
-\xa9\x93\x07\xda1?\xaa~\x11E_HGTOW\xa4~e2\x99\x1f\xfc\x9b\xd3\x97>\xfe\xe4V\
-\xfbD\x00P*\x8d\x9e:y\xa2X(Hs{\xff\xbe\xbd\xcf\x9dy\xe6\x93\xcff\xa2(\x12\
-\xce\x93Ri\xf4\xe4\x13\xc7\xaf|\xfe\xc5\x80\xe2\xaev\xb0T\x1a\x95\xa3*;x\xf0\
-\xe0\xf8\x89cSo\xfe\xef\xb7\xa0\xfd\xd0\x03\x00\x8e\xe3|\xf7;\xcf]\x9b\xbbq\
-\xf3\xab\xf9\xf5\xf5\xd6^\xe5\x8em\x8f\x8f\x1fx\xf2\xc9S\x8em\xb7\x9f\x9f6Y\
-\xe8}~e\xd2\x8e\x89\x84\xf6J2\xaak\x15\n\x89\xa3\xd4\xa9\xda\xb8\xcf\x84\xfe\
-\x9am\x02r\x1a\xb7\xfd\xe3\x94\xf6~\x83t\xe8\xd0 .\x14Z\xbf\xc3\xa3\x96-\xd2\
-\xe7!\xc0\xcd\xed\xb5R\xfd\x96\xe8U\x06\xba{\xb7\x1b\x88(\x82\xbeE@\xba\x9a%\
-\xaa\\^A\xc4|>\'\xec2\xf9\x8e\x8f0B\x7f\xfd/\xbf\x01\x80\x1f|\xff{\xe3\x07\
-\xf6\x13Bn\xddZb\x9c[\xa6Y(\xe4\xa5\x96\x89\xf57\xce\xf9\xffz\xfd\xcd^\x85\
-\x13\x8d\x11f\x97\xfc_a\xfb\xab\xbb\xc9\xf0\xcd\xfbn3\xc6\xee\xde]G\xc4L\xc6\
-K\xa7\xd3r\'eQ\xbe\xdeh4\xea\r\xd32\x0b\xf9\xbc\xd1\xde2\x8d\xb4\xb7\x1c\x13\
-a<bia}\xbd\x02\x84\x0c\x15\x0b\x84\x10\xd1A\xe9K\x91\x93\x9fj\x12.-\xddf\x9c\
-{\xe9\xb4\xe7\xa5U\x7f\x8e8\xb5\xbao\xb8\x08\x1f\x8c\xa2\x88sn\x9a\xa6\xdc<\
-\xdaR\xb6-\x16\x83\x1f\x86\xa1\xf0\xae\xc8\rHM\xd3\x0c\xc3pee\x951V,\x16\xe4\
-R\xa7\x1c\x07\xb9r\xb0\\.#G\x11\xf8\'\xbeW[E\x94u\xcb^\xa3*;h\x9af\x1c\xc7++\
-\xab\x94\xd1\\6+\x05W\xf1YmD\xd4\x18\x86Q^Y%\x04\x0cb\xec\xdd\xbb\'Q\xa1\xd9\
-\xde/T\x86\x99\x0fxM\xaa\xff&d]\xbd\xcb\xa4\x82\xab\x93\x87\x96\xf5\x1dG\xec\
-\xdf\x88\xfd\xaf\x80\x00r\x0c\xa2\x9e\x89_\n\xe3\xcf\t?j\x1f\x90\xd3\xe0\xce\
-\x87\xc8B\x00@\x84 \xc6\xaeS\x051\xac\xc2\xf8s\x83e\x81\x07\xd8\xb9\x96\xbbD\
-\xbd\x1f\x08!\xa5\xd2(\xb4\xef\xa5\xc4\x9d\xa3F\xb0\x88\xb7XGF\x86\xc5\x1d(\
-\xfd\x12\xf2\x86\x94wcgaP\xb6\x00N\x1c\x0emU\x82\xcd2\x04J\xd8\x8cP1\x91\xc9\
-\xa4\xf3\xc9\x9d\x10\x92\xf1<\x11\x04"\xa6\ri\xdc\xc9s\x89\x0f\x96e\xed\xd93\
-&E_\xaa\xa1jq\x8b\x92r\x19sttD:\x9dT\xe1\x93\x16\xa8\xb1\xd9lOHdb\xa8\x13\
-\x06\xbe\x9cA\xc5T!\xcf%\xeb\x94-\x91*?:2"\xdd\x17\xf2\x8c\xb2Ur\xe9\xb8\xcf\
-\xa8\xca\x0e\x8a\x02##\xc3b\x05[\x8eF".S\xb6\x7f\xac4\xaa\x8e\xadz\xcd\xa8_\
-\xde\xefu\xa8V\x05\x9b5\xbdky\xd0\x82\xbe3A\x1e\xd3Z\xeb\xe1;f\xc9L\xcd\x12o\
-\xe8\xc8=\x95\x1dDR\x1a\xd6\x8aZ\xa6\xac\xbb\xb2\x03@\xba81\xb8\xb2\xc3\xce\
-\x15w\xa2l\xa5h\xb6\xf7}\x87\x8e{FJ\xa1z\xac\xb0\xcd\x8d\xf6\xbe0\xa4\x1d\
-\x17\x01m\xb5\xbdg\xe1\x84I\x98\x10wQRU\x169\xbbH%\xda\xe4\ti\xbfX\x94\xd0P\
-\xa1w\xedE\xd4\x96^K\r\x15\x82%\xf57\xd1\xb6\xc4I\xd5\xf1!\x84\xc8`\x0cP\x8c\
-bU\xec\xe4\xf0\x12e\xa3\xd1\xce\x9f@\xc6\x93\xa8UI\xd9U\x7f#\xd9$9\xd3H\xdbV\
-\xceI\t\x07\xf7=GU=\xa3\x1c\x13l\x078\xaam\x93\xf3\x8a\x9c\xdbd\x1fe\xd7\xc4\
-\x8f"\x86\xf4\xc1\xf4]\x1d\x99\xc4\x07\xcd.#\xf6\xbfBN\x81\x88\xc8\xdd\x9e\
-\xfb\xde\xf5I\xfc"A\x16\xd2\xea\x82\xf8\xcc\xb1w\xf8\xa3\x9dN\x15\x06\xf1\
-\xdd+\r\xb8\xaf\xd2\xdb\x07\xa2\x84\xa3A[\xc2\xe4\x9f\x04\xe2\x7f\xa5\x1d\
-\xd7\xcb\xf0\x94+Z\xd2*\xbfga\xd5]\xa0\xda\xf2\xd0M\x86\xe4_\xd5@@\xb5N\xe9\
-\xaeMt\x01\xda\xcb\x9b2\x04\x13:\x0cm\xa9\x9b\x86\xb2\xe3\xb3\xfc\x06:b\xfc\
-\xa5\x13CUd\xd5\xa5#EM\xcev]\xd7\x15\x89\x12."\x07Y\xfe)Q@\xb6_|\xa9\x8e\x83\
-,\x9f8\xfb\x80S\xa6:[@[\xa0\xe5\xd9et\x90\xe8\x85:\x14j\xf3\xd4\x876\xe9\xe7\
-I\\E\x1a\xcd\x06\xc8\xc5\xa6\xa6\x00\x10\xd3>f\xfb\xe1\x81\xdeZ\xaa\xdc@\xa4\
-\xed\xdaz\xbc\x03\x05\x90\xe9\x9d\xfd\xb1\x17;^\xdc\xa1\xfd\x9a{Wq\x97F\xa2j\
-xZ\xa6i\xdb\xb6\x14q\xa9t\xd2\xa6\x96B\xdf\xabp\xa7\xe8\xa8* \x85I\x1c\x92\
-\x90!9\x19\xa8\xb3H\xa7\xdf@\xd6\xa0\xca\x998Jh1\xe9p \x90\xcd\xebr"\xcfA\
-\xc2b\x15\x87$\xe6\'i\xfe\xab\xa2&\xeb\xe9<\x11\xb4\xcdvDt\x1cG\x18\xbc\x89\
-\'$\xf9\x03%\xe6\x1b\xe96I\xfc^\xaa\x11\xadV\x92\x18UU\xf1\xe5ht\x0e\x1dQ\
-\x1e8\xc8\xe6\xf7\xbc:\x87B\x96\x91\x95\xf4\t\x92\xd1hhc\x19Y$\xccv\xce\xbb\
-\x9b\xedN\xa6d\xa7\xbb\xec\xc0\x95\x80GU\xda\x90/d\xf4\x0c\x7f\xb4\xbda\'[\
-\xba\xdfv\xeeHq\x97\xa6+\xb4\xefd\xf9\x8c\x0f\x8a\xb8K\x1f\x88H\x12"\x0f7\
-\x15\xbdNh\x8a\xea\xfc\xedSX\x15\xf7>\x8d\x94\xea uD\xb5\x1c\xc9f\x17s\xd7.$\
-&\t\xdc\xbcx\xab\xd6\x908JN0Ry\xd5\x93\xf6\x9a\x9fTA\xef\xfc\xac~#\xfe\x15\
-\xeb\xab\xea)T\xc9\xee\x1c.9\x0e]{\xaa\x9e\x02;\xf2\x1c\x0c2\xaa\x9dC\xa7Z\
-\xe5F;\x9aE=<1\xc5\xca?\xa9\'\xd2h6@&\x83d\xfa\xe6k<2HeQeNf\xba\xe8\x9dFf\
-\xd0\xda\x12\xecHq\x07E\xdf\xa5\xb8\xc0\xe6uTB\x88\xf8R<h\x8bbcc%B\x88\xe7y\
-\xea\xd6e\xa4\xed\xf2N({\x9f\xc2\xf2\xb6\xef/\xee\xf23\xb6\x17B\x13:\x9b\x90\
-\x95\xc4s@\xa2\x8cZ\x8fz\x16\xb2Y\xdc\xd5\xb3\xcb\xae\t\xd4\xf6\'\x9a\x978\n\
-7;\xb2\xba*\x9d\xd1^\'\xc0\xcd\xc8C\xe4\x89\x04\xb2N\xb5=\xd01\x91\x0c\xf2\
-\xbbw\x8e*(\xf3\x01\xd9\x0c\xf4\x16\xf7\xae\xbfBb\xfc\xfb\xb7G\xf3\xb8\x11\
-\xd7n\x89\xd8v\xc6\x90\xf7\xca\xd78\xd8^K\xac\xb9"6\xbf\x06\x00\xda\xe7\x1d\
-\xa8\xfc>\xcb\xcd=@Swd(\xa4D\xb5\x16\x13w&\xb4\xd7B\xd5\x94\xb6\xa6\x92\xc7C\
-\xd58B\x88\x08\xfe\x1b\xb0\xf0\x03\xb76\xa1\xcb\xf2_\xf9\xbd*\xee]\xcf%;\xab\
-\xfe\xa9\xb3\x18Q\x02f:\x07\xe7\xc1\xba\x908J\xedK\xa2\xfd\x89\x13%&\xb0\xce\
-b\x0f\xdc\xa4\xfb\xaa\xf0~\x87B+\xbb&\x01\xf28\xb8\xf3\x11\xb2\x08\x00\x82\
-\x88w\xd5Nb\xda\xc5\xf1o\xdf{G\x0e\xe4\xcd;\x1f!m@\xff\xf0G\xd3)N<g\x98\xf7\
-\xde\xdf\xa3\x93\x9dj\xb9\x0b\x06\xb9?I\xdb\x11\x81\x88r-Q\xfa\x97\x13%\x07,\
-\xfcuZ{\xbf\x7f\xfa\x9a%\x1f\x9eB\xf5\x9f]\xb6\xac=\x0fc\xe84\x9a\xae\xd0\
-\xda\xa2\xf0\xb6S\xd6\xd3*N\x0f\xb6\xd7\x12\xad\xdd\x12\xca\x0e}\xc3\x1f\xbd\
-\xa1\x83\x0f\xa6\xec\xb0\xd3\xc5\xfd\x9eH\xb1\x96\xaf\xc0\xf4Y+\xbb\xaf\xc2\
-\x1a\x8d\xe6\xb1\x02Y\xd8\xdaH\x0f\x81\xf6\xf0\x8f\x1bV*5\xd0^K\x18\xb7\xf7\
-\xe4\xeb\x13\xfeh:\x197\xbf\xff\xc1Z\x0b\xbb^\xdc\xe5\x9a\x9b\xfa\xbf\xbd\
-\x96\xcb\xee\xab\xb0F\xa3y\xac\x88\xab_!g"H\xa6\xa7\xd9>X\xb2\x01\xd6\\E\xd6\
-\xda\xda\xac_\xf8\xe3\xc8\xe4 \xef@\xf5b7\x8b\xbb\x10e\xb3\x9d\xda\t\xdb\xa1\
-\x84\xbd\xcc\xf6\xc1\x0bk4\x9a\xc7\nN\x1b2f\xb1Wl\xbb\xe9fS\xb9\xbd\xf7\xae\
-\x0b\xb9\xcc\x11\xc6x\xcfUY;3j{\xc3\xdd\xfe2(\xbbY\xdcA\x89\xd9P\xa3\xf1zY\
-\xe2\xf7UX\xa3\xd1<>\xc4\xfeM\xb1\x91\x1ec=c\xdb\x07|k\x89\xd6nq\xe9m\xa7=\n\
-\x11#\xd3w\xf3\xebA\xd8\xcd\xe2.W\xf0:C\x0c\xbffa\x8dF\xf3\xf8\xc0#\xbf\x95i\
-\xbd\xf7\x8e\x1c\xb67<P\xb2\x01\x1e\xc7\xd5\x9b\xe2\xb3\x08\x7f\xecJ\xba0>H0\
-e\x7fv\xb3\xb8C\xb7\x90m\xe8\xad\xd7\xf7UX\xa3\xd1<&\xc4\xfeW"\xa5/e\xd8\xeb\
-%Ro\xe8\xf0`U\xdd@N\x01\x00\x11h\x8f\xed\x12\x0c\xcbM\x0f\r\xb4sS\x7fv\xb9\
-\xb8\xc3}\xaa\xb3\x96r\x8dF\xa3\xc2\x82\xbb,X\x03!\xc7\xb4\xbbC\xc6\xcd\xed\
-\x19$\xcd:\x8f\xeb\xb4\xde\xdar\xa0\xcf[K\xe9\xa1\x81v\xd1\xbb\';r\x9b=\x8dF\
-\xa3\xd9\x1aZ^\x14\xd2\xdaC\xa3\x13b\x98\xe9\x01\xcd\xf6\xcau@\xf16u\xbf]\
-\xf4\x06Z\x95\x1d\x00-\xee\x1a\x8dF\xd3\x1d\xd6,\xf3\xd0\x97\xfb_\xf70\xdb\
-\xf7\x99v\xfa\xdeU\x05wY\xb0\xda\xfa\xdc+\x98\x92\x90\xcc\xc8\xe4 \xab\xb2\
-\x83\xa0\xc5]\xa3\xd1h\xba\x82r\xff\xeb^\x8b\x9f\xc4t\x06\xf3\x8fc\xec\xb7\
-\xb7H\xed\xedmw2%+Ux\x90\x96vC\x8b\xbbF\xa3\xd1t\x81\xd6o\xf3\xb8.\xf6\xbf\
-\xeee\xb6\xa7\x8b\xe3\x83\xa4\x07\xa0\xf5\xdb<\xaa\xb6>\xf7t\xefX\x99\x91\
-\xc9\xaf\xd3\xe0\x04Z\xdc5\x1a\x8d&\t\xf28\xae\xb6\xcc\xf6\x98BW/\x8a\xe9dR\
-\xf9{\'\x1b@\x16\xc5\xfe\r\xf1\xb9O\xb2\x81\xf4\xd0!\xc3r\x1f\xb0\xb9\xdd\
-\xd0\xe2\xae\xd1h4Ib\xff\x06\xd2P\xac\xa3v\xdfH\x8f\x90l\xe9\xc4 \xbb#E\x95k\
-"\x91$\xb4\x93\rtb\xa7\x8b\xe9\xe2\xc4\xd7jq\x07Z\xdc5\x1a\x8df\x13\x9c6h\
-\xfd\x0e@\xeb\xad\xa5\xaer\xecx\xa3\x03\x85?\x86\x15\xd6X\x06\x00\x02@Y\x8f \
-\x19B\xbco\xd4!#\xd0\xe2\xae\xd1h4\x9b\x88\xfd\x9b"f\x91\xf6\xca\xfd2\xe8\
-\xeeH\x18\xb5\xd3\xc8p\xe8\x19o\x93\xca=\xe0v\x1c\xfd\xd1\xe2\xae\xd1h4\x1bl\
-$\x1b\x80\x9e\xa9}S\xf9\xfd\x03\xed\xb5\xd4(\xf3\xa8\x02\xc2l\xa7\xd0k\x1du\
-\xc00\xf9\xfbE\x8b\xbbF\xa3\xd1l\x10\xfb7\xc5\xfai\xcf\xdc/\x84\xa4\x06I\xb3\
-\x8e,j\x87?\xf2\xdea\xf2\xe9\xa1C\x83l\xee\xf1\x00hq\xd7h4\x9a\x16,\\g\xc1]\
-\x00\xc0\xde^\x14;=4\x88\xd9\x1e\xd7\x16\x91\xb6\x93\xb63\xe8:M\x98\x8e7\xd8\
-\xe6\x1e\x0f\x82\x16w\x8dF\xa3i!3\xad\xf71\xdb\x07\xf1\xb6#\x0biu\x1e\x00\
-\x08\x00\xe7\xd0=\xde\x06\xc0\x1b\xfeZ\xdbq\xf4G\x8b\xbbF\xa3\xd1\x00\x00\
-\xd0\xc6\xb2x\xd5H\xbc\xb5\xd4\xb5\x8c\x9b\xdd3\xc8\xe2g\xec\xdfle\x7f\x04\
-\x88{\xc5\xdbdF\x9d\xcc\xc8\xd7ip\x7f\xb4\xb8k4\x1a\r\x00\xa0\xb0\xb5\x01\
-\x802\xe8\xbac\xf5\x809\xc2x\\\xa3\x8d\xdb \xd6Q9|\xbdx\x9b\x07g\xf7\xa7\xfc\
-\xd5h4\x9a{\xd2J6\x00\xc0{\'\x1bp\xf3\xfbM;\xd5\xaf\x12\xca(e\xdc\xbfA\x90\
-\x03\x10\xec\x13oS8`:\x99o\xa0\xdd\xbd\xd1\xe2\xae\xd1h\x1e{\x90\xc9d\x03\
-\x94u\xdf"\x95\x98\x8eW\xec\x99#\x8cQ\x1a\x06QH\t\x89W\x9ch\x15\x81\x10\x80\
-\x98A\xf7\xa4\xed\x84\x0c\x92\xb7\xe0k\xa2\xdd2\x1a\x8d\xe6q\'\xae-"\r\x01\
-\x80c\x8fd\x03\x00\xe9\xe2\x041\xed\xae\x87s\xc6\xd7\xcb\xe5 lzi\xf4\xe0\x0e\
- \'}\xc3\x1f\x9dL\xa9\xff\x13\xc07\x82\x16w\x8dF\xf3X\x83\x9c\xd2\xda\xa2\
-\xf8\x1c\xd3\xee1\x8b\x86\x9d\xea\x13\xdb\xde\xa8\xd5\xd6\xca\xcb<\xa0f|\x97\
-\x06>\x10\x03D\xf8c\x8f\xcd=\xbc\xaf\xbd\xf9\xf5 h\xb7\x8cF\xa3y\xac\x89\xab\
-\xf3\xc8b\x00`\x1c\x19\xefnkg\x86\'\xfb\xe4\x08C\xc0\xc2\xd0(\xd2 \\\x9f\'\
-\xc4 \x00\xacw\xf8c\xaa0\xb1\x05f;h\xcb]\xa3\xd1<\xce \x0bi}\t\xda1\x8b]\xcb\
-X\xa9\xbc\x93-\xf5\xa9\xc42\xadT~\xc4\xb5j\x04C\xb1\x8e\xda+\xfc\xd1\xb0S\
-\xdfx\xf6\xc7\x9e\xad\xda\x9a\xd3h4\x1a\xcd6$\xf6o\x02\xa7\xad-R{\xeck\xda?f\
-\xb1Y\xab\x87\x11\x02\xad8\xb8\x06`\x12\x00\xca\xb0\xd7\x16\xa9\xfd\x9f\x00\
-\xbeY\xb4\xb8k4\x9a\xc7\x14N\x1b\xb4\xb1\x0cb\xeb\xbb\x1e1\x8bN\xa6d\xa7\x87\
-z\xd5\x10\x06a\xa3\x11"\x18._!\x18#\x98\x1c!\xee\xb1\x1d\x87\x9d\x1e\xea\xff\
-\x04\xf0\xcd\xa2\xc5]\xa3\xd1<\xa6\xc4\x95\x1b\x80\x1c\x080\x8a\xbdb\x16\xd3\
-C\x87\xba\x1f\x8cP\xaf\xd5\xe3(\xe6`:F\xd3\x8c\xca\x08\x06!\x10Sd\x8c\x13\
-\xb1.K\xc8Fv\x01B\xbc\x91\xadXG\x95hq\xd7h4\x8f#<\xf2Y\xb0\n\xad\x1d\xab{$\
-\x1b\xc8\xed\xb3\xdcl\x97? \xd4k\xf5\xa0\x19\x12\xd36\rfE\x8b\x04\x11\x89!^\
-\x80\xb2\xd3\x05;]4\x80\xc7a5\x0e|\xa1\xf3nn\xef\xc3H\xda\xde\x07-\xee\x1a\
-\x8d\xe6q$\xf2o\x88$\x03\xb4\x87\xd9N\x0c\xcb\xeba\xb6W+~\x18\xc6\x86esF\x1d\
-R!\xac\x82\xc4$\x00\xf5\x80\xbb\xf9\x83\xf9=\x93\x00\x040L\x03m\xac|\xd5\xac\
-\xdc&\x96\xe3\r=\xdcd\x03\x9d\xe8h\x19\x8dF\xf3\xd8\xc1\x82\xbb<X\x07\xd2\
-\xcflO\x15\xc6\xbb\xeeX\xdd\xac7\x9aAlZ6c\xd4\xb6\r;\xbe\r\x84\x10\x80\x88b\
-\xcc\\@oc\xa6 \xe0zC\x80\xb67t\xf0!%m\xef\x83\xb6\xdc5\x1a\xcd\xe3\x06\xc6\
-\xfe\r\xf1\x89R\xec\x9e#\xccr\xd3\xc5\xf1\xce#\xeb~5\x08"\xcb2)\xa5\x8e\x9b\
-\xb6\xa2\x9b\xc0\x9b\x04\x0c\x00\xa8\x87h\x18N\xbd\xd6LGq\xc4 l\xc6\x801\x01\
-34FF\xf2]\xaaz\xd8h\xcb]\xa3\xd1<^\xd0F\x99GU \xc0yO\xb3\xdd+\x1e$F\xd2\xf6\
-\xadU\xaa\xcd &\x86\xc59\xda\xb6c\xa1O\xe2\x15\x04\x02\x04\x02\na\xcc9R\xcb\
-\xb5\xeb\x8df\x18\x04\x1c9\xe5\x06\x037=r h\x06\x0f\xbf[I\xb4\xe5\xae\xd1h\
-\x1e/d\xb2\x01\xca\xb0k\x8e0\xd3\xc9\xb8\xf9}\x89/\xabw+a\xccM\xd3\xa0\x94;\
-\x8e\x814\x8c\x9b\x8b\xae\x11\x03\x18\x88Po2\xcb\xb2mo$\x8cXpw\r\x10\x01\x90\
-\x00!\x86A\x88Q\xa3\xf1\xf8\xd1#\x84t\x8d\x90|Xhq\xd7h4\x8f\x11<\xaa\xf2\xb8\
-\x06\xf7\xd8 \xe9Hb\x83$\xffn%\x8c\xb8m\x19\x1c\x88m\x03\x8f\xa3\xb0y7gT\x01\
-\x0c \xd0\x088\x07\xb30z\xacY\xa7\xf5\xda]\xc30\x81\x00\x08\x8f>@kG\xd6(\xb6\
-\xdd-u\xbbkq\xd7h4\x8f\x11qu\x1e\x11\t@L\xbb\x9b\xedV\xaa\xe0dF\xe5\xff"\xe7\
-\xfe\xba\x1f3b\x1a\x18sp,0\x90E\x9c\xdaP!\x06\x00\x10\xcaXH\xcd\xfc\xc8d\x14\
-\x134m\'\x9d\xe6\x8c\x8bC\xdbU\x80i\x99\xa6\xbd\xd5b\xab\xc5]\xa3\xd1<.\xd0f\
-\x996W\x08\x00\xed\x95#\xcc03\xa3\xc76\xca\xc7q\xbd\xda\x88)\x02p4L\xd7"4hR\
-\x00\xdbA\x88\x1a\x04\x00\x91S\x92\xf7\x8a{\x83\x08\x18gn\xcaK\xb9V\xadQg\
-\x8c\x03\xe7\xad*\rc\xa4T2\x8c\xad^\xe0\xd4\xe2\xae\xd1h\x1e\x0f\x90\xc5\x95\
-\x1b\x00\xd0o\x83\xa4\xfc~\xf9\xd6R\x14\x84\xd5J\x8d\x986!`\x98\x8em\xf2\xb0\
-Q3m7?\\\\\x9b\xff(c! \x82=b\x18{\x1a!g,v\xbd\xbc\xe3Xa}\x1dXL\x0c\xe2\xa4\
-\xd2\x04\xc0yg\xfe\x95\x00\x00\x1cPIDATq\xdcl>\xe78[\x1d\x07\tZ\xdc5\x1a\xcd\
-cB\\\xbf\xcdi\x93\x00\xc4\xac\xc7[K\x96+\xf7Z\n\x1a\xcdf3DB\x90S\xc3r,B\xc3z\
-\xc3J\xa5\xf2CC\xcd\xda\x9a\xc1\x1a\xc4\x02L\xed\x8bx1\x88\x90\xb3\xd8\xcb\r\
-\x19@\xa3\xba\xbf\xbez\xdb@\x7fhd\xac\xb8\xe701\x8c-^DU\xd1\xa1\x90\x1a\x8df\
-\xf7\x83\x9c\xc6\xd5\x05\x00@\x00\xd63\xfc\xb1\xb5\xd7R\xd0h\xd6j\x01r@\x04\
-\xc3\xb4\x1d\x935\xeb5\xcbM\x15\x86\x87\x01`\xed\xf6\x97\xb6\x85\xdc\x1d\x8f\
-`\xb4\x112\xc6\xa8\x97\x1b\x06\x1e\xb1\xa0\xb1V\xfe\xca\x84j&m\x16\xc6\x8e\
-\x18\xa6\xf9\x08\x95\x1d\xb4\xe5\xae\xd1h\x1e\x07hm\x01Y(\xbc\xed]\xcdv\xd3N\
-\xb9\xf9\xfd\x00\xd0\xac7\xeb\xb5\xa6a\x1a\x8cq\xcb\xb2,B\x1b\xd5z\xca\xf3\
-\xf2\xc3\xc3\x00\xb0\xbe\xb2`a\xd3H\x1f\x8c\xa0Xo4\r\xd3N\xa7\xb3\xc8\x02\
-\x8c\x9a\xab\xab\x8b\x96\xd1\xcc\xb8\x98\x1b\x197mo\xcb\xbb\x98D\x8b\xbbF\
-\xa3\xd9\xe5 \x8f\xe3\xda-\xe8\xebmO\x0f\x1f%\xc4\xa8W\xebA\x10\x1b\x06A$\
-\x96e9\x164j\x8dT&\x93\x1f\x1a\x02\x00F#\x7f\xe5F.\xbf\x8f\x99#\xcdZ\xc5rs\
-\xb6e\x01F4\xa8\xfb\xeb\xb7,\xd2\xcc\xa5\xc1r\xd2\xe9\xde\xfbho%Z\xdc5\x1a\
-\xcd.\'\xf6\xbfBN\x01\x80\xf5\xf0\xb6\xdb\xa9\xbc\x9b\x1d\xabUjAH\t0N,\x83\
-\x80mb\xcd\xf7\xbd\\.W,\x8abkwnx\xb9=\xa6;\xd2hT\xad\xf4\x10pj\x12\xacW\xd6\
-\xea\xd5e\x934\x0b\x9e\x01\x00\xde\xd0\x91\xceW[\x1f\t\xdb\xa2\x11\x1a\x8dF\
-\xf3\x90\xe0\xb4\x19\xd7\x97\x00\x10\x90tO6@ 5<U\xab\xd4\x1a\xcd\xc84\x10\
-\x0c\xcb@n\x12h6\xea\xd9b1\x93k\xe5\xe9\r\x83\xbaA,+=\x145\xab\xa6\x93G\x16\
-\xa5\\\xbb\xbav\xa7Y_\xb1\x8cf\xc13\x11\xc1J\x15\xdc\xdc\x9e-\xed^o\xb4\xb8k\
-4\x9a\xddL\xec\xdf\x04\xce\x80\xf4\xf2\xb6s;\xb3?\x0c0\x8c\x99i\x12 \x96A8\
-\x01\x8c\x82F\xb68\x9c\xce\xb4\\\xe7\x88\x18\xd4\x1bv\xaa\x184|\xe2\x14\x01i\
-\xca\xb1*+\xb7\xc2\xe6\x9am\x86\xb9\xb4\x89\xf8\x08\xb6\xe3\xe8\x8f\x16w\x8d\
-F\xb3k\xe1q\x8d6\xca\xd0sG\x0e\x04bq\xa3\xc8\xb9A\x08%\x86i\x00\x07\xce\xa2\
-\xa0^\x18\x1ds\xd3\xa9V!\xce\xa3 \xe4\x8c\x04Q`\xbbE\xce\xa8k\x93\xbb\xe5\
-\xc5(XM9\xd4s\r\x91e\xc0\xcd\x8e\xd9\xa9\xc2V\xf6\xae?Z\xdc5\x1a\xcd\xae%\
-\xaa\xdc\x04@ \x84uM\xed\x8b\x1c\x9d\x12\'\x19\xe4\xcc0L\xc3@Nc\x1a\x05\xc3{\
-\xf7Y\xb6-\x8ap\xce\x9b\xb5F\xb3Q\x8d)\xb1\xec\x14r\x96v\xad\xca\xcab\xd0,g\
-\xd3\x98\xb2\x8c\xd6\x86z\x86\xe5\ro#\xb3\x1d\xb4\xb8k4\x9a\xdd\n\x0b\xd7\
-\xdb\x1b\xe9aW\xb3\x1d\r\xd7J\xefc\x80\x84\x10\xd3\x04\x16\x854\x8e\x86\xf7\
-\xee7M\xb3U\x03\xa5A\xbd\x194k\x0c-\xd32\r\xd3\xb4\x0c\xbe\xbe\xb2\xd4l\x94\
-\x0b\x1e\xda\xe6\xc6d\x91.Nt\xdd\xd9\xe3\x11\xa2_b\xd2h4\xbb\x93\xd8\xbf\t\
-\x00\x04\x802\xe8\x96#\x0c\xcd\xf4\x1e\n\x0epf\xdb\x06\x8b\x9a4\x8eJ\xfb\x0f\
-l({L\x83z\xc0y\xcc\x88\x8bHl\xc76\x90\xd6\xd7n7\xebwr)\xae*\xbba\xa7S\xc5\
-\x89\xad\xea\xd6\xa0h\xcb]\xa3\xd1\xecBXs\x85\x85\x15\x00\xe0\xd85\xb5/"Iqs\
-\x048u\\\x9b\x065JY\xe9\xc0\xc6~I\x9c\xb2\xa0\xd1\xa4q\xcc\xc0\xe4,J\xa7\xd3\
-4\xac\x07\xd5\x95Z\xbd\x9c\xf7\xd017\xd5\x97\x19\x99J\xa4\x08\xde\x0ehq\xd7h\
-4\xbb\x0f\x8c\xfc\xaf\xc4\xa7\xae;r "I\xef\x05\xc3t-3n\xfa\x1cI\xe9\xc0\x01\
-\xf9W\x1a\xc5A3\xa01\xe5`Fq\xec\xa5\xd3aP\x0b\xab\xcb\xf5\xc6j\xc1\x03{\xb3\
-\xb2\xdb\xe9!\'3\xf2\xd0;t\xffhq\xd7h4\xbb\rZ\xbf\xc3\xe3\x1a\x01`\xdd\xccvD\
-N\xec\x1cq\x86,\xd3\x88\x1a\xeb\x86\xed\x8e\x96J\xf2\xafq\x10FQ\x14\xc7\x94\
-\xa3A\x19M\xb9N\xd8\xf0\x83\xear\x14\xaf\x17=\xb06+;\x10\xe2\x8dLnE\x97\xee\
-\x1f-\xee\x1a\x8dfw\x81<\xae\xceC;\xd9@\x17o;!\xc4\xddc\x1af\xdc\xac\x98N\
-\xaa8\xba\xb15G\x1c\x86\xf5Z\r\x89\xc9\x90 \xf2\xb4k\xc7\xcdZ\xdd\xbf\xcd\
-\xa8\x9fM\xa1m\x12\xdc\xbc.\xab\xa6\x08\xdenhq\xd7h4\xbb\x8a\xb8vK\xa4\xf6e\
-\xddw\xe4\xe0\x86]4\xed\x02m\xfa\xb6\xd7J\x1a#h\xd6\x1aQ\x10049\xe7\x061L\
-\x93\xc4\xcd\xea\xfa\xda\x12\xf2Z.\xddE\xd9\x89i\xa7\x87\x0e=\xfc\x0e= \xdbn\
-\x11@\xa3\xd1h\x1e\x18\xe4q\\k\xa5\xf6\x8di\x97\x02\x1c\tqKQ\xb3\xeaf\xf3\
-\x1b\xca\x8e\x10\x05a\x14\x04\x14\rJ\x99eZ\x048\x0f\xaa\xab\xe5\x85 \xaaeS\
-\xd0\xa9\xec\x00\xe0\r\x1d6\xccG\xb0\x0b\xc7\x80h\xcb]\xa3\xd1\xec\x1e\xe2\
-\xaaH\xedK(C\x8eI\xb3\x9d#R\xf0\x08%\x99b\xd1\xcb\xb6\xdd)\x88A#\x08\x82\x80\
-\x83I)u\x1c\x1b9\xe5A\xb5\xea/5\xc2\xe6p\x968\x16t*\xbb\xe9f\xdd\xfc\xbe-\
-\xe8\xd1\x03\xa3\xc5]\xa3\xd1\xec\x12\x90E\xb4\xbe\x04]\xdfZ"\xc094#\xb4S\
-\xd9\xec\xf0H*\x9dn\x1d\x82\x10\xd6\x1bA\x18RfPF]\xc7\xe2,\xc6\xb0\x1a\x07K\
-\xf5 H;\xc4s\xbb(;\x00d\x86\x8fn\xc3\xf0G\x15-\xee\x1a\x8df\x97\x10W\xbfB\
-\x16\x03\x01\xcaaS\x8e0\x02\x8cC\x10!\x92Tvx|C\xd99\x06\xf5F\x18\x86\x0c\r\
-\xc6\xb9\xebX\xc8\x19\x0f\xab,\xb8\x83<\xb0-\x92Mu\xc9\x0f\x0c\x00N\xa6d{\
-\xc3[\xd2\xa7\x07G\x8b\xbbF\xa3\xd9\rp\xda\x88\xeb\xb7\x81t\xe4\x08#\xc0\x18\
-\x041"\xc2\xe8\xf8\xc9L\xbe\x95\xdb\x8bs\x1e\xd6\xebA\x183\x0e\x9cs\xcb\x00d\
-\x94\xb0\xa6\x01U \x8d\x98\x13\xdb\x04\xc7\xec~\xae\xf4\xd0\xb6\xd8\x8e\xa3?\
-\xdb\xfa\xb1B\xa3\xd1h\x06$\xf6o\x022\x00`\x0c6r\x84\x11\xa0\x0c\x9a1"\x82\
-\x97\x1b\xc9\xe4[+\xa8\x9c\xb1F\xc5\x0fBF9A0L\x13\x08R\x03\xe2T&\xc5\xe3\x8a\
-m\x19\x8c\x83c\x82\xd9M \xadT\xc1r\xb6i\xf8\xa3\x8a\x16w\x8dF\xb3\xe3\xe1Q\
-\x956V\x00\x08"P\xbea\xb6\xc7\x14\x82\x18\x01\x81\x18\xa4P:\xd2*\xccxP\xab\
-\xc5\x9c\xc4\x94\x13 \x06\xe1\x843\x02,?<\x1c5\x96S\x16C\x04\xc6!\xe5t\xf3\
-\xb6\x8b\xa4\xed\x8ft\xe7\xeb\x01\xd1\xe2\xae\xd1hv<\x91\x7f\x13\x00A\xe4\
-\x08k\x9b\xed\x11\x83\x90\xa2\xc8\x19\xe6\xe5\xc6\x9ct\x0e\x00\x90\xb1f\xad\
-\x1eD<\x8a\xa9e\x9a\x04\x18a1\x00/\x8c\x96h\xdc\x84\xb0l\x99\xa4\x19A\xca\
-\xee.\xe0nv\xcf\xb6J\xda\xde\x07\xeds\xd7h4;\x1b\x16\xac\xb2`\r\x00\x10Q&\
-\x1b\x88(Db/l\x02\x86a\x15\xc7\x8e\x02\x00\xa3\xb4Y\xad\xc5\x0cb\x86\x96i\
-\x02p\xc2c\xdb&\xd9\xa1Q $X\xfb\xc2\xb1\x88\xf8k\xc6\x15\x93\xc5&\x88\xe9x\
-\xc3G\xb6\xb4o_\x03-\xee\x1a\x8dfG\x83\xd1\xfa5\xf1I\xbc\xb5\x84\x00A\x04L\
-\x86\xcb \x14\xf7LZN\x8aEQ\xbd\xd6\x08)p\xc6\\\xc7b,\x8e\x1b\xb5\xa1\xd2\x90\
-\x93)\x00@\xb4~\xcd\x82&\x004BL\xd9\xdd\x83d\xb2\xa5\xe3\xdb-i{\x1f\xb4\xb8k\
-4\x9a\x1d\t\x8di\x18#\x84\xcb$\xae\x13bR\x86\x9c#G\x08b\xe0J \xa4\xedz\xb9\
-\xa1\xfd\\({\x8c\x9cs\xdb6\x19\x8bI\xdc\x18\xde3b\xa7s\x00\xc0i\x83\xd5\x97\
-\x00\x80q0\t\x18\xdd\xa4\xddN\x0f9\x99\xd1.\x7f\xd8\xaehq\xd7h4;\x0cD\xf4\
-\xd7+\x14,\xdb\xe0Fc\x1e\x88\x01\x00\xac\xa5\xec\xc8\xb9L\x15F\x90\xb3\xd1\
-\xfd\'Y\x18\xd4\xebA\x18#\xe3\xcc\xb1L\xe4\x0c\xe2F\xb14j8\xad\xfd\xaf\xa9\
-\x7f\x13\x91\x13"b(\xbb\xbd\xb3\xb4\xcd6\xbf\x1e\x04\xbd\xa0\xaa\xd1hv\x18\
-\xeb+\xab\xeb\xe5\x15\xd72\xd2d\x8d\xf0&\x01\xc2\x18\xc6\x14\x9a\x91\xaa\xec\
-\x80\x9ce\x0b{\x08\x10\xbf\xda\x0c)0\xce]\xdbB\xe4\x845\x8b\xa5\x92Tv\x16\
-\xae\xcbM\xb4\x11\xbb\xaf\xa3\xa6\xf2\xfb-7\xb7E\xdd\xfb\x86\xd0\xe2\xae\xd1\
-hv\x18\x8dz\x1d(\xc7\xb0F\xeb\xb7\x01\x0c\x04\x08c\x14\xaf))\xca\x8e\xa6e{\
-\xb9\xb1z\x00\x94\x01r\xee\xda\x06\xe7\x94\x05\xd5\xe2\xe8\xa8\xe9\xa4\xdb\
-\x95a\\\xb9\x0e\x00\x84\x00\xc7\xaeY$\xb7{\xf6\xc7^hq\xd7h4;\x0c/\x9f\x07\
-\xcb\x8a\xaa_!\x0b\x88A\xc2\x18\x1bQB\xd9\xb9e\x99CcS\x11s\xe3\x98\x02\x80c\
-\x1b\x8c\xc6\x185\x87\xc6\xc6\x0c;%\xab\xa2\x8de\x1eU\x01\x00\x11b\xda\xcd!\
-\x03\xe0\r\x1d\xda\xce\xd9\x1f{\xa1}\xee\x1a\x8df\x87Q(\xe4ic\xdd\xe1u\x00\
-\x83q\xa8\x07\xa8\xbaS\x90s\xdbv\xf3\xa3\x93\x11\xf7(e\x86A,\x93\xd0(D\x16\
-\x8e\xec\xd9\xa4\xec\xc8i\xec\xdf\x04@BH\xcc\x90\xf1.A2\xa6\x93q\xf3\xfb\xb7\
-\xa8c\xdf(Z\xdc5\x1a\xcd\x0e\xc30\xcd\\:dM\x00\x02\xcd\x803\x8e\xaa\xb2;\xae\
-W\x18\x9dl\xc46\x00\x9a\xa6A\x08\xf28B\x1e\x8f\xec\xdd\x93\x08d\xa4\xb5\x05\
-\xa4M\x10\xa9\x80{\x98\xed\x99\x91\xc9m\x9e\xfd\xb1\x17Z\xdc5\x1a\xcd\x0e\
-\x83GU\xd6,\x13 \x94b\x10mR\xf6T:\x97\x1b>\x1a\xc4&g\xdc\xb6-\xc3@\x1a\x04\
-\x96\x89\xf9\xd2\x98amr\xad \x0b\xe3\xda\xa2\xf8L\xd9\xe6,\x92m\xec\xcc\xe8\
-\xf6\xcf\xfe\xd8\x0b-\xee\x1a\x8df\x87\x11Un\x00r \xd0\x08\x91s\x0e\x00\x84\
-\x18\x88<\xe5\x15\xbc\xc2\xa1Fdr\xce-\xd3\x00`\xb4\x19\x98\x16\x14Jc\xc4Hj]T\
-\xb9\x0e\x9c\x02\x00"t7\xdb\x89\x91\xd99\xef\xa3v\xa2\xc5]\xa3\xd1\xec$Xp\
-\x97\x05k@ \xa2\x18QpRY\x00\xa0q\xd3\xf3F\xd2\xf9\x83\x8d\x90 2\xc7\xb1\x019\
-\r\x1an\xca\xc9\r\x8f\x12#\xe9W\xe1Q\x955\x97\xc5\xe7\x98A\x97M\xb4\x01\xd2\
-\x85q\xd3\xc9<\xf4\xfe<4\xb4\xb8k4\x9a\x9d\x04\xad\xdf\x06\x00@\xa4\x98\x1a;\
-\xf4\x84\xeb\xe5y\xdc\x8c\x9a\xf5(6\x1a!!\xc0\x1d\xd7F\xceh\xb3\x91\xce\xa62\
-\xc5\x11\xd2-p=\xaa\xcc\x89\x94\x8f\x9c\x83LG\xa3B,7]\x9cx\xd8}y\xa8\xec\xc8\
-\x85\x02\x8dF\xf3x\x824\xa0\xc1] \x103\x12\xb3\xa2\xe9\xe4\t1\x10L\xcaS!5\
-\x01\xb8i r\x167k^\xce\xcb\x0euWv\xd6\\\xe1aE\x18\xeb1\xeb\xba\x89\x1exC\x87\
-\x89i?\xe4\xde<\\\xb4\xe5\xae\xd1hv\x0c\x91\xff\x15\xf0\x18\x01\x83\xc8\x8c9\
-\xe1\x8cr\xd3\x89cdH,\x0b\x80#\x02\xa1\xf5jv\xb8\xe8\xe5z&\xe6\x8d\xab\xf3\
-\x00\xad}<\xba\x86?Z\xa9|*\xb7\xf7!vcK\xd0\xe2\xae\xd1hv\x06<\xae\xd1\xc6\
-\x1d \x10E\x10RN\x08\x8f\xa38b$\n8 \xb1\x08DqL0\x1e\xda3b\xa7z\xee\x94\xc4\
-\xa3*\x8fk\xd0g\x1d\x15\xc0\x1b\xde\x19\xdbq\xf4G\x8b\xbbF\xa3\xd9\x19D\xfeW\
-\x80\x1c\x01\x1a\x11"\x07\'\x97\x8e(E\xca\x18r\x02\xc0\r\xd30\xa08<j\xba^\
-\xbfJ*\xd7\xc5v\x1e\x94b\xd7\xf0G7\xb7\xc7N\x17\x1f^/\xb6\x0c-\xee\x1a\x8df\
-\x07\xc0B\x9f5V\x80@\x18!e\xc8\xc1\xa9\xd7\x9aa\x18\x03\x80A\x08\x00\x10\xc3\
-\x04d\xc5\xd1\x91~\x954Wx\xb4\xdee\x13\xed6\xc4\xb0\xd2C;8\xfcQE\x8b\xbbF\
-\xa3\xd9\x01\xc4\xfeM\x04$\x08\x94A\xca\x86\x18\xdd\x80\x12\x1a\xc7\x00\x04\
-\x00\x01\x01\x01\x10\x91\x06u\xd3\xee\x91\x07\x06Y\xe4_\x07\x00\x02\x10Q\xc4\
-nf{\xaa8a*\xf9\tv4Z\xdc5\x1a\xcdv\x876Wi\xb0F\xc4f\x1a\x06\x18@L\xc2\xb8\xed\
-2\xca\x84\xb2\x03 "\xb1,b\xb9\xe9\x9e\x95\xd4\x97D\xb2\x01\xd6#\xfc\xd1\xb0\
-\xd3\xe9\xc2\x81\x87\xd8\x8d\xadE\x8b\xbbF\xa3\xd9\xe6`\xec\x7f\x05\x00\x08\
-\xc08\x02\x00\x021\xb0\x91K\xd5\xa87\x1c\x86\x9cs\x06\x00\xa6AFG\n\xa6\xd3\
-\xdd\xeeF\x16\xb5\x82d\x00b\x8a]\xdfZ\xf2\x86\x8ft\xbe\xc8\xbas\xd9==\xd1h4\
-\xbb\x12\xdaXf\x91/\xccv\x19\xb9\x88@ XqS\xa1\xe3flo\x8fiY\xb6\x932\xad\x9e\
-\x89y\xe3\xea<\xf2\x18\x00\x10\x81wK\xdan\xa5\x8bnv\xec\xa1u\xe2\x11\xa0\xc5\
-]\xa3\xd1l_\x10Y\xd46\xdb\xd5%P\x02\x00@\xe2f\xd5\xb4\xe3\xcc\xde\'\xba\x19\
-\xe2\xb2\n\x86q\x85\xd6n\x89"\xdd\xcdvB2#\x93\xdf|\xeb\x1f)Z\xdc5\x1a\xcd\
-\xf6\x85\xd6\xef\xf0\xb8A\x00(\xef\x92\xb8\x91\xb4\xb66\xed\xa6\xec<\x00ZCZ\
-\x8d\x9a\x15\x1e\xd5\xc5\xce\xa8\x9ccWo\xbb\x9b\xdb\xb7\xe3v\xd1\xbb\'Z\xdc5\
-\x1a\xcd6\x059\x8d\xfcy\x10\xde\xf6n\x91\x8bf\xaa\xe0f\xf7(\x070\xe0M\xa0U\
-\x16\xf9a\xb3\x1a4\xa30\xa4\x94\xf1\xa1,1\x0c\x02\xd8\xddl\'\xa6\xed\r\x1f~\
-\x98\xfdx4hq\xd7h4\xdb\x94\xb8\xb6\x88, \xbd\xf2\xad\x13\x92+\x1d\x03\x00\
-\xe0!\xf0:F\x958\xac\x05\x8dz\x18\xd2(b\x94qD@\x00\xdb$\x86A\x00\x80\xf2\xee\
-{-\xa5\x8b\x07w\xe2.z\xf7D\x8b\xbbF\xa3\xd9\x8e \x8b\xe3\xea-\x10y\x02x\x17\
-\xb3\xddIgY\xe4\x87\xd5y`\xf5\xa0\x19\x85\x11\xa51\x17\xe14@\x80\x00\x18\x06\
-\xc4\x0c,\x8b\x10h\xbd\xb5\xd4m\x17=/\xb5\x8b\xc2\x1fU\xb4\xb8k4\x9a\xedHT\
-\xb9\x8e<\x02\x80\x98w\xbcpD\x802\xb8s\xbb\x16\xce\x7fQ\xf4\xc04\x90\xf3\x96\
-\xa0\xab;\xe2q\x04\xc6\xc1\xb5\x10\x10\x82\x18\xc2\x08\x88\x01&\x01\x00@\x04\
-\xc3\x00B\x8c\xcc\xe8\xb1\x1d\xba\x8b\xde=\xd1\xe2\xae\xd1h\xb6\x1d,\xac\xc4\
-\xf5\xdb\x00\xc09p\xd6\xe1%\x07X\xab\xe1J\x85\x8e\xe6\r\xd3 \x88\x9b4]\xbe\
-\xd5\x141b\xdbF\xdas,\xcbD\x1b\x89IM\x03\x08gB\xdb\xc3\x98\x071)\xda;x;\x8e\
-\xfehq\xd7h4\xdb\r\x8c*7Z\xe9\xbdXr\t\x94\x10\x08bX\xafq\xcf52)\x82\x002\xf4\
-\x1d\x11\x00\x90\x18\x86m\x99\xaek9\x8e\xe9\xda\x06A\x16\x061\xa5\x0c8\xa71"\
-\x10B\xc04\xb9k\x11\xbfA\xe38\xec\x13\x1d\xbf\xa3\xd1\xe2\xae\xd1h\xb6\r\xac\
-\n\xbcJ\x9bwY\xb0\x0e\x04X\xb7\xf0G\x00X\xabr\xc6a8M\x0c\x02\x1c[\xb2nZ\x86\
-\xebZ\xaec9\x8ei\x12\xe4\x9c\xd1(j\x04\x8cq\x04D \x04\x89\x83\x04\x0c\x8c\
-\x01yL\xc120\x93",\x0e!\xbd\xdb\x82 \x05Z\xdc5\x1a\xcd6\x80\xc7\xf1\xdd\xcf\
-\xeb\r?\x8c\xd01\xa8m\x11\xc0.\xe1\x8f\x84@#\xc4J\x03\xbd\x14I9\xc0\x18p\x04\
-\xc71\x8aE\xcf\xb1\x08pN)\x8d\x9a\x11\xa3\x8csq\x00\x10@Nl\xf0\xc6M\xb7\xc8\
-\x18\x1a\xd8\xc4\xfau\xc2#\x86\xc4$@\xe3p\xeb\xfb\xba5hq\xd7h4\x8f\x1c\xac\
-\x95/\xaf\xdf\xbdK)"\x80\x9b5\xba\xbf\xb5D\x00\x00\xd6j\x08\x00Y\x97\x10Bl\
-\x87\xd8&\xb1-\x03\xe3\xb0\xde\x10Fz\xfb\xed\xd5\x8d#I-\xc8\xf0(6\xcd\x15B\
-\x08Gbc:mE\xa2$\x8d\x9a[\xd9\xcf\xadD\x8b\xbbF\xa3y\xc4D\xcd\xca\xfa\xdd\xbb\
-\xc2N7\x0cb\x99\x80\x08Ld\x80i\x07\xb7p\x0e\x8c#\xe3$\x932F\x0b\xc4\xb5\r\
-\xc3\x00\x82\xc8\x11\x19\xa54nE\xcbt:q\x10\xc0\xcb\xa6\xea\xa1A)\x05@ f\xda1\
-\xc5\x9f\x0c\x00\x1a\x07[\xd8\xd1-E\x8b\xbbF\xa3y\xc4\xc4Q$b\x19)\x03\xcf\
-\x06\x02@9"\x02G`\x0c\x18\x07\x04 \x84X&q\x1c\x925\x00\x119g\x9c\x82\xf0\xda\
-\x10\xe8\xbf)\x1e:\xc4\xb7\x0bc!\xcdp\x8e6\x89,ZC\x06\x00@\x08p\x1a"b\xd7}\
-\xb4w:Z\xdc5\x1a\xcd#\xc6I\xe5\x80\x10\xe4H\x19\xba\x16!\xa6\x19\xc5\x18D\
-\x94\x10b\x1a\xc4\xb1\x89i\x00!\x88\x1c\x90s\xca\x01\xda\x06\xfa \x92L\x80\
-\xb0\xa8a\xb0\xf9\xb4\x9d\x02 H\x03\xce\xa8<\x14Y\x84\xc8\t1\x1fV\xdf\x1e\
-\x1d\xbb3z\xff1\xc1\xf7\xfd\xf9\x85E\xdf\xf7\x07/\x10\x86a\xe7!\xf3\x0b\x8b\
-\xcb\xe5\xf2Cl\xe8\xfd\xb3\\.\xdfo\xabf._\xe93\x14\xfd\x0f\xb9p\xf1\xd2\xfd\
-\xb5\xef\x9b8\xfb\xf6\xc1\xf7\xfd\x99\xcbW\x1ea\x03l7\xedx#\x84\x90\x91\x91L\
-a\xb4\xe0d2\xe9\x94\x95v\x0c\xcf%\xae\r\x06\xe1\xc89c\x88\xd8\x8a{\xbc_3\x9b\
-\x10\xc29\xa7A\x9d\x865\xc6\xd8\xa6\n\x90\x89\x08\xca\xdd\x87\xb6\xdcw$a\x18\
-\xbe\xfe\xc6\x9b\xae\xeb\x8e\x8d\x95fg\xaf\xf9\xbe\xff\xf2K?q]W-3s\xf9\xca\
-\xef\xde:;1~`\xb9\xbc\xf2\xec\x99\xd3g\x9e9\xed\xfb\xfe/^}m\xac4\xba\\^\xf9\
-\xd1\x0f\x7f05y\x14\x00\xfe\xf1\xd5\xd7\x00 \x08\xc3\xa9\xc9\xa3/\xbe\xf0<\
-\x00\xfc\xf5\xdf\xfc\xad\xac\xe4\xe7\xaf\xfclb<\xf9r\xb6\xef\xfb\x7f\xf7\xf7\
-\xff\xd0\xf5O\x9d\xcc^\x9b\xbbp\xe1\xd2\x7f\xfc\xab\xbf\x1c\xb0k\xcb\xe5\xf2\
-\xebo\xfc6\xe5\xba\xae\xeb\x84a\x14\x84\xe1\x9f\xbf\xf2\xd3|>\xaf\x96\xf9\
-\xc7W_;s\xe6\xb4h\xff\xc6\x89f\xaf\xe5\xf3\xf9D\xc9\xfe\xcd\x96\x87\xbc{\xee\
-\xfc\x99gN\xab\x85\xe5 \xb8\xae+F\xaf\x7fG\xeey\xf6^t\x9e\xe8~k\xf8\xfaT\xfc\
-\xea\xec\xec\xb5\xe9S\'\xb7\xfe\xd4\x92\xa1\xe1\x91\xc8X\xb5l\xde\xf0\xeb4\
-\xa6\x88H\x00\x90\x03\xb6=._\xd3oB\xa0\xbb\xef\xc6\xb4\xcc\xfe>\x9d\x9d\x8b\
-\x16\xf7\x9d\x87P\xf63g\x9e)\x97\xcb\xf3\xf3\x0b\x13\x13\xe3\xd3\xd3\'_\x7f\
-\xe3\xcd\x84\xbe\xcf\xcf/\xbc\xfc\xd2O\xa6&\x8f\nM?\xf3\xcc\xe9\xcff\xae\x08\
-\xf9\x98_X<w\xee\xfc\xd4\xe4\xd1\xd9ks\xae\xeb\xfc\xfc\x95\x9f\x85a\xf8w\x7f\
-\xff\x0f\xcf\x9e9-j\xf8\x1f\xff\xfd\xbf\xf5i\xc0G\x17.\xe5\xf3\xf9\x99\x99\
-\xcb}\xc4\xfd\x97\xbf\xfa\xe7\x9f\xbf\xf23\x00\x18d\x02\x90\xf8\xbe\xff\xcb_\
-\xfdZN<\x000\xbf\xb0\xd8\xa9\x98g\xce\x9c\xbe\xafj\x07o\xb6\x8a\x18\x04\xdf\
-\xf7\xff\xe9W\xbf\x9e\x9a<\xfa\x00g\x94\x83p_\'z\x80\x19b\xa7\x83\xb4\x19U\
-\xaeG1\x86a\x00\x00\x84\x00\x1a.Gb`D\x80\x7fma\xef}^\x00\xcb4\x8d]\x9a~`w\
-\xf6jw3s\xf9\xca\xc4\xc4\xf8\xfc\xfcB\xa5\xe2\x9f9\xf3L\xa5\xe2/,,NMM&\x9e\
-\xac\x85\xb2\xab\xdf\xcc^\x9b\x13\xdfL\x8c\x1f\x08\xc2\xd0\xf7\xfd\xf9\xf9\
-\x85\xa9\xa9I\x00p]Wh\xbd(\xb9\\.\xf7r2\x08\xc7\xce\x9f\xbf\xf2\xd3\xd9ksj\
-\x99\xf9\x85\xc5\x0b\x17/\xcd\\\xbe"\n\x88\xffD\x81|>\'\xbeT\x0b\x87a(\x9at\
-\xe1\xe2%\xf9\'\xd1B\xb5\xd9RRE\x93.\\\xbc\xe4\xfb~>\xbf\xf1\xd6\xc9\xcc\xe5\
-+j\r\xbd\xe8\xd5\xec{\x92\xcf\xe7S\xae[\xf1\xab\xa2#\x89\xce\xfa\xbe/k\x0b\
-\xc3\xf0\xc2\xc5K\x17.^\n\xc3\xb0s\x10DO\xe5\x08\xf7?Q\x18\x86\xa2_\xb2\xf2\
-\xe5rY\x0e\xaf\x1c+\xb5\xe3r|\x84GKV+\x87Z\xd6 \xff$\xca\x8b\x06\x0f> \x0f\
-\x83\xda\xdd[A\x10\x01\x02!\x04\x810g\x1f\xc9?!\xfe\x03+\x8f\x0f\xcfm\x82\
-\x00H\xb9X]\xdduhq\xdfy\xcc\xcf/\x08/\xc1\x8f~\xf8\x83\x89\xf1\x03/\xbe\xf0\
-\xfc\xf8\xf8\x81\xe9S\'\xe7\xe7\x17\xba\x96\xff\xdd[g\x9f=s\x1a\x00|\xdf\x97\
-Va!\x9f\xab\xf8\xd5M\xdf\x14\xf2\xe2&w]\xf7\xdc\xb9\xf3\xff\xf4\xab_\xff\xf2\
-W\xff\xdcY\xdb\xcc\xe5+\x13\xe3\x07\xf2\xf9\xfc\xf4\xa9\x93\x9f\xcd\xb4\x94b\
-\xf6\xda\xdc\xb9s\xe7E\xdbf._\xb9p\xe1"\x00\\\xb8pq~aq\xb9\xbcr\xee\xdcy\xd7\
-u\x7f\xf7\xd6Y!:\xbe\xef\xff\xee\xad\xb3\xae\xeb\xbe{\xee\xfc\xec\xec5QRx\
-\xbd\xc5\x83\x88\xa8SJ\x8f8J4)\x08B\xf1y\xb9\xbc\x02\x00j\r\xe2\x9b^tmv\x7f\
-\xc4\xd9_\x7f\xe3M\x00\x10\xde-\xd1G\xe1\xef\x02\x80\xe5\xe5\xf2/^}M\xea\xf5\
-\xbb\xe7\xce\x8b/_\x7f\xe3\xcd\x8a\xef\xab\x830\xbf\xb0x\xe1\xc2%\x00\x98\
-\x99\xb9\xdc9\xb5$N\x04\x00\xbf\xfc\xd5\xaf+\x15\x1f\x00^\x7f\xe3\xb7B\x9d_\
-\x7f\xe3\xb7b|\xc4ht\x0e\x9d\x1c\x9fT\xb7\xa1\x9e\xbd6\xf7\xd6[o\xcb\xe6A\
-\xfbA!\x08\xc2 \x08E\xcb\x1f!Q\x14\xb5\x9d/\x181\xaf\xe2\x13\xbf\xbc\xd2X[\
-\xad\xde\xad\xf9\r\x0f\xe0\x1bZ\xedD\x8e-\xf8\xc6w,fq\xf4\xcd\xd4\xbf\xcd\
-\xd0n\x99\x1dL\x18\x86\t?{\'B\x86\xee\xcb\x93\xfb_\xff\xcb\x7fv]7\x0c\xc3_\
-\xfe\xea\xd7\xd2\xd8\x97|6s\xe5\xe5\x97~\x0c\x00\xd3\xd3\'\x7f\xf9\xab_\x0bO\
-\xce\x85\x0b\x97^~\xe9\xc7r\x9e8\xf3\xcc\xe9\xbf\xfe\x9b\xbf\x15\x1e\tiE>9}r\
-v\xf6\xda\xc4\xf8\x81\xcff\xae<9}2\x0cC1?\x01\x80\xea\xf5N\xf4H\xccXB\xf2\
-\x9e=sZ\xf5\x0b\x8b\x1a\xfe\xeb\x7f\xf9\xcf\xe2\x8c\x7f\xf7\xf7\xff\xd0\xa7S\
-]\x9b}\xcf\xa1\x98\x9d\x9d\xcb\xe7s?\x7f\xe5\xa7\xea\x97\x1f]\xb8\xd4\xb9\
-\x0c\x00\x00/\xbe\xf0\xbc\x18\xab\xbf\xfe\x9b\xbf\xfdy\xa9\xf4\xf3W~&\x07\
-\xe1\xc2\xc5K\xae\xeb\x8c\x8f\x1f\xe8\xf5C\xa8\'\x9a\xbd6\x97\xcf\xe7\xe4$7;\
-{\xcdu\x1d1J\xe2\xf0^C\'\xc7\'1\xd4\x00p\xe1\xc2\xa53gN\xbb\xae[*\x95~\xf7\
-\xd6Y\xdf\xf7\x85\x8fN\x94/\x14\xf2b\xaaxT\xd8\xa9|\xd0\xbcc\x12\x00\x04\xd3\
-6m\xcbaq\xcc(\x03BR\x8ei\x18\x06r\xf6u\x9d3\x84@j\xaf\xe1\x0c1\xc6Mv\x17\x83\
-\x96)@\x009\x8b\x00\xbco\xa0\x1b\xdb\x0c-\xee;\x8f\x89\x89\xf1\x99\xcbW\x9e\
-\x9c>\xf9\xee\xb9\xf3SS\x93\xb3\xb3\xd7\n\x85|\xb9\xbc"\xe5@\xf2\xbb\xb7\xce\
-\x96\xcb+R\x9b\xf2\xf9\xbc4\xd5+~\xb5\x90\xcf\x89o\x00\x0e\x00@\xa5\xe2\x8f\
-\x8d\x95\xa0-\xaf\xae\xebNM\x1dM\x98\x99\xc2\x11!\xd6`\x05\xb3\xd7\xe6\xa6O\
-\x9d\\.\x97\xef\xe9)\x9e>u\xf2\xff\xfc\xbf\xfe\xef\x17_x~\xe6\xf2\x95\xff\
-\xf4W\x7f\xb9\\^\t\xc3P\x98\xb7\x00P\xc8\xe7D\xd7\x84*\x81\xa2e\x92\xc4)\x96\
-\xcb+\x05\xc5?\xa3~N\xd0\xab\xd9\xfd\x1b|\xe6\x99\xd3S\x93G\x7f\xf1\xeak\x89\
-I4\xf1\xb8#\xbf\xef3[\x88s\t\xeb\xbbsbH\x9c\xc8\xf7\xfd\xe5\xf2J\xd8\x1e\x99\
-R\xa94V*={\xe6\xf4\x85\x0b\x97~\xf7\xd6\xd9\x1f\xfd\xf0\x07b\xeaM\x0c\x1d(\
-\xe3\x93\x18j\x00X.\x97gf.\xcb\xf2A\x18\x96\xcbey\xc1<r/\xbf\x97\x1f\xad\xaf\
-\xcd\x99\x06\x03$\x16\xd6\n\x99\xb4\x93\xceS\x06H)\x89\xd6h\x18\x7fm\xb7;\
-\xd6\xc3,\xe7\x9e\x15S\xc30BV0Y\xdd1\x1b\xe2\xc5\'\x1a5]\xaf\xf8\xcd\xf4d;\
-\xa1\xc5}\xe71}\xea\xe4\xebo\xbc\xf9\xc2\x0b\xcf\x03\xc0\xcc\xcc\xe5\xb1\xb1\
-\xd2\xf8\xf8\x81\xb7\xdez;a`\n\x9b]\x8d\xee\x10^u\xb1\xa0\x9ar\xdd|>?11>3sy\
-\xfaT\xcb\x8e~\xf1\x85\xe7\xe7\x17\x16\xcb\xe5\xb2P\xd5\xf9\xf9\x85\xe9\xe9S\
-j\x9d33W~\xf4\xc3\x1fHY\x9c\xb9|\xe5\xa3\x0b\x97\xa6O\x9d\x9c\x9a<:\xbf\xb08\
-1~@:v:\x9b\xed\xba\xaeh\xb9p\x8f\xe4\xf3y\xd7u\xc5"p\x18\x86\x15\xdf\x17-\
-\xfc\xc5\xab\xaf\x15\nIY\xef\xca\xc4\xf8\x81\xdf\xbduV\x08b\x18\x86\xd2-3{mn\
-b\xfc\x80\xda\x86^\xcd\xbe\xe7)\xf2\xf9\xfc\xb3gN\xbf{\xee\xfc\xcb/\xfdD\x1d\
-\xc6\x0b\x17/\x89\xe8#i\x1a\xdf\x93\xe9S\'\xcf<s\xfa\xdds\xe7\xc5O\xd0\xe7D\
-\xe3\xe3\x07\xe6\xe7\x17\x84\xc9\xbf\\.\x17\xf2y\xd1\xd9\xe9S\'g\xaf\xcd\xcd\
-\xcc\\\xfe\xf9+?\xeb\x1c:\x95\xc4P\x8b6OO\x9f\x12\xb3\xe6\xfc\xc2\xe2X\xa941\
-1^.\x97\xc57\xe5v\xc8\xa9\x88=\x1d+\x95\x06\xe9\xd17\x88e\xbb\xe9\xe2\xa1\
-\xa82g\x8a\x97N\xebw\xe2h\xdd0MNcF\xbf\xbe\xb2\x03\x00q\xd3\x99j#\x8cCN\x00\
-\x11\xcd\xac\x9b"\xa4\x81"\xa5\x98\xb1\x0b\x83\xdcA\x8b\xfbND\xdc\xd8\xaf\
-\xbf\xf1f>\x9f\x1f\x1b+U*\xfe\xfc\xfc\xc2\xcb/\xfd\xb83\x14R\xfe\x0b\x00\xff\
-\xe3\xbf\xff\xb7\'\xa7O\xfe\xe2\xd5\xd7\xe6\xe7\x17D($\x08\x9d\xbap\xe9\x1f_\
-}-\x08\xc3\xe9S\']\xd7-\xe4s\xaf\xbf\xf1\xe6\xfc\xfcB\x18F\xa5\xd2\xa8\xea\
-\x93\x99_X\xac\xf8\xbe\xfa\xcd\xd4\xe4\xd1w\xcf\x9d\x9f_X<s\xe6\xf4/\x7f\xf5\
-\xeb\xb1\xd2h\xc5\xaf>9}\xf2\xcc3\xa7\xc7J\xa5\x7f|\xf5\xb5\xe9\xe9\x93\xaaU\
-855\xf9\xcb_\xfd\xb3\x14\xcag\xcf\x9c\xfe\xbb\xbf\xff\x07q\xd4\xb3gN\x8f\x95\
-J\xf9|\xfe\xe7\xaf\xfc\xf4\xf57~;;;\'|\x11\xf3\x0b\x8b\xa2\xa9]Qk\x90\xdd\
-\x17\xdem5\xde\xa6W\xb3\x07\x19m\xe1\xa3W\x0b\xff\xe8\x87?x\xfd\x8d7\xdf=w>\
-\x9f\xcf\x8f\x95F\xfb\x1c+\x07!\x0c\xc3\xcff\xae\x88u\x8e?\xdf<\x07w\x9eH(\
-\xf2\xdf\xfd\xfd?\x88\xf2/\xbf\xf4\xe30\x8c^\x7f\xe3M\xd1M\x11\xae\xda9t\x89\
-\xda\x12C-\x7f \xf1\xb3\x8a\xa9\xe2\x97\xbf\xfa\xf5\xeclk\xc1@\x8c\xb6\xf8\
-\xdf\xad\x17w\x00(\x8cN\xac\xd10\xae-Y\x06\'@X\x1c\xb2\x18\x00D\xe8\x0c\x00\
-\x01\xf8\x1a\xab\xaa\x88\xe8X\x8d\xc2\xd0X\x10\x01"\xda$v\xb1)\x96Q9\xd8\xbb\
-\xd2l\x07\x00\xf2\x10W\xa25\x0f\x19\xdf\xf7\xa5we\xc0C\x84\x85\x9b8d~a\xd1u\
-\x1dyK\x8b2\xea7\x0fPs\xd7\x13\r\xd8\x1e\x00X.\x97\xc30\x1a\xa4\rb\x10\xc6J\
-\xa3\x83\xf8\xd0\xbfq\xde=w\xbeP\xc8\xf7z\x08P{\'z4x;\x13\xfd\xea\xfc\xad\
-\x07\x1ca\x95\xc4\x0f\xdd\xf5\x9bGK\x14\xd4\x83\xc6:\x8b\x1a\x80\x94\xc5!"\
-\x03N\x018g\x8c\x00\x07\x10q\xefHHWc\x9e\xb4\xfe\xe9&i\x88h:)\xc3\xcer\x8e\
-\x18W\x91E\x00\x84q\xf0FOd\x87\xf6=\xdc^="\xb4\xb8k4\xf7\xc7\xebo\xbc96V*\
-\x95J\xbe\xef\x7ft\xe1\xd2\x7f\xfa\xab\xbf|$\xf3\xca\xe3\x02""g,\xa6\x8d2k\
-\xdc\xaa\x86\xf98\x8a)\xe7\x06A\x038\x10$\xc0\tp\xd3@\x03\xa8a\x00G\x93\x10\
-\xf1\xde)kW\xd0zK\xc9 \xc2\x0b\x03\x00\x84#A\xc3\xf5\x86\x8fd\x8b{\x1fa\xe7\
-\x1e*Z\xdc5\x9a\xfbc\xb9\\\x9e\x99\xb9"\x96U\x9f=s\xfa\x91\xafF>&D\xfeM\x08\
-\x97\xc1\x1a[\xb9\x1b\x07\x14L\x83\xb0v@#\x01\xdc3d\x86\xe1\xdd\x8cMy\xe6h\
-\xdc\xac\xc4\xcd5\x1a\xd5\x11\xc1 \xe0\xb9\x04\x01\xb85D!E\x90\x11\x02\x1c\
-\x0c\']\xf0r#\x86i?\xd2>=\\\xb4\xb8k4\x9a\x1d\x80\xbf\xfc\x85Co\xbb\xe9\x92\
-\xdft\x10\xa0\x1e`\x10#!\x04\x11-\x83\xec\x1f6W\xd7\xcb\x9e\xd5\xe0h\xd0\xf6\
-\x16\x1f\x08\x90u!\x9d&\x80\x00V\x01\x8a\xdfz\xb4]\xd8b\xf4KL\x1a\x8df\x07\
-\x10Ea\x18s\x1e\xafy\x0e-d,\xdb\xb6,\x93\x98\x06\xa4\x1d\xa3\x901\xa3(\xe6,\
-\xa86QUv\x93@\xca!\xc0\x01\x10 \xae@\xe3\xfa\xa3\xed\xc2\x16\xa3\xa3e4\x1a\
-\xcd\x0e\x80\xd1\xb8\xd6\x00\xcb\x88\x11VjA\xb1\x90\xc9\xa7m\xe4\xc8-\x93\
-\xb0\xb8Y\xad\xdf\xe5\x9c!\x92\x8d\xdd\xb4\x11R.\x10CY_\xad/\x80=\x02\xf6\
-\xe3\xe2F\xd3n\x19\x8dF\xb3MA\x1ec\xdc@Z5h\xe5\xf6\xf2j\x10q\xcb\x00\xcf\x85\
-\x88"\x80E\x0c\x0b\x08AN9\xa3\x00\x88H8\x82\xe7\x12h\x9b\xedC\xd9\x8eM8L\x0f\
-\x86\x9e\x81\xdd\x98\xbd\xbd\x13m\xb9k4\x9am\x04\xf2\x18i\x83\x87\x15\x1e\
-\xf9\xad\x98E\x02\x88\xc0\x18\n\xa1nF`\x19\x04\x80\x02\xa7\x00@\x00,\x93\x00\
-\x90\x88n\xe4\xeeE\x84t\xc2l\x17\xb0\x06\xd4o@vrK\xbb\xf4\x88\xd0\xe2\xae\
-\xd1h\x1e)\xc8\x90S\xa4\r\x1eUxT\xc5\xb8\x86<n\xa9r[\x9a9\xdf\x88h\x14\x1b\
-\xa5\x9a\x84\xc8hw\xde*\x80\x86A\x08\x01\xce\xc12 \xe5\x90\xee\xef=5oAz\x1f\
-\x98\xbb0\x99L\x02-\xee\x1a\x8df\xcb\xe1\x11\xd0:\xc6>\r*\x9c6\x81\xc7j\xa6\
-\xc6\x04\x04\x80#pT\xfe_\x01\x01\x00A$\x120\xdao0eR\xdd\xccvyD\xf5\x0b(<\r\
-\xbb4\x8d\xbb\xe4\xff\x07_\xffI\xbb\x17\xf3\xcc{\x00\x00\x00\x00IEND\xaeB`\
-\x82'
+\xcf\x1fD\x15x\xc3E\x07\x1b@\t|\x0f\xb5\xfd\x86{-i\x11\xff\x7f{\xef\xfe\x1c\
+\xd7q\xe5y\x9e\xbc\xcf\xaa[O<\n|\x01|\x01$EB\x92IJ\xb6,y\xec\xb6\xbb\xed\x96\
+z\xed\x91\xdc=\xd33\xbf\xee\xc6\xc6\xc6DL\xcc\x9f\xb3\xb1\x11\x1b\x13\x131\
+\xd1\xd1\x11\x1d\xdd\xda\xf1\xb6\xbd\xedi\xc9\xf2\x8c\xd5\x94%\x8az\x90\xd4\
+\x03\xa4(\x81 )\x00\x04\x89\x02@\xd4\xad\xd7}d\xe6\xd9\x1f\xb2*\x91\xb8\xf5`\
+\x91\x12A\x00\xccO(\x18\xa5B\xde\xbc\x99Y\xf7~\xf3\xdc\x93\xe7\x9e\x0c\xee\
+\xb1`\x15\xe0>S\x85\xe9\xe6R\xb9};O\xdc\xe5\x95$\x1d\xdf\xe2\x06\x13\x97\xa3\
+\xb8\xb7\x97\x86\xff\xea\xf6\xd0/S\xf1\xb2\x17\xcd\xa7\xe3\xdb\x16o\x10@4\
+\xac\xd8,\x06\xa9CQj\x82\xbb#\xa6iZ\xed\xdc\xd9\x94\xd2\xea\xd0\x0f\xd6\xf3\
+\xdf\x97UY\x96\xe5\xba\xae\xc8\x0b(^F\xefl\x86h\x00l\xf6K\x98\xa6)"(87\x99QX\
+)\xfeY\xb9\xf0\xa7\x06k\xb8l-\x1d\xddN\xd1e\x8b\xd7\x018 p#\x15\xd9\xa3Mw\
+\x82\xba{\xc1\x1d\x12i\x08\r\xa3{\x94w\xa7/H\xde<\xcc0\xd6s\xdf\xbb\x97\xfd\
+\xae\xc9j)\xba\x9c\x8en\xbb\xb4lb\xfb\x15\x12\xc3A;\xcf\xbd\x03F\xf1Xz\xf8p:\
+?\xea\xba.Q0\x0c\xa3\x99\x7f\xf6\x86{BL\x18\xa2NKI\x8b\x98\xcdfS\xa9\x94\xe8\
+\xe5\x80?\x8a\x08#\x11\xf7\xf9\xc2\x9e\xff-\x11\x9f#N*~\xbbt:\x9d\xc9d\xbc\
+\x94\xd7g)\xb5\xf3\\\x9d\xaf8\x89S\x87a\xd8\n\xa1a\x8cs\xd37OW\xbc\xef\x18\
+\x18[t=\x1d/\xa5\xe3%\x9b\xad\x1b\xc0\x00\x81\x1bNd\x8d\x04\xeex\xe4\xee\xe7\
+\xce\xb0\\\x0c\x94\xeb(\x96\x92M\xbek3\xe4\x04\x9fh\x83\xb8\x00\xa4\xf5\x9a\
+\xcbes\xb9,\xb4\xde\xbaD\xb1Q\x910\x02l\xdb\x96Oc\xa2\xefb\xfc\xe3\x98"J\x7f\
+H\xcb\xa5C\x8c~\x8b\xcc\xa6i\x8e\x8d\x95\x86\x87\x87\x08!\x8e\xe3\xa4\xd3i\
+\x91:I\xe4\xb3\x1cdT;\xc7\xd30\x8cB>\xff\xd4\xf14"\xda\xb6-~)q/\xf4\x9a\xf3\
+\xd4IW\xad\xa7\xfd\x8bp1\x1a\x9c\xf38\xa6A\x10\x84Q$f2B\x88m\xdb\x9e\x97\xf6\
+\xd2i\xb1\xe3\xab\xd9\xde^Q\xa4\x90\xda\xb7w\xcf\xde=c\xa6i\xba\xae+\x92Dy\
+\x9e\xf7@y\x95\x1f\x0b\x9b\x92\r\xb0M\x0f\xea*\x03\xef\xb5\x84\xb1\x92\xc5\
+\x84\xf5No\x91\x199\n\x8f$\xd5\xf8\x96\xa0>\x17\xabWR[X9"\xc6\xe6\xfeJj\x7f\
+\x05\x00\x94\xdb@\x1c\x98jg#\x00\x00\xde\xde\xfeQ\x8a\xa6P7\xe9;\xee\xb4(USE\
+\xaa\xa4\x8c\xd2\x8b\xa2H\xdc\xde\x1bNa+\x1fa.J\x1f\xaa(5$\x8e\x12+\x84\xad]\
+\x8d\xba\xed\xa7\xaa\xfa\x82\xb0\xbd[\xbci\x9a\xf2\xceA\xab\x188\x85\xc0;&\
+\x07\x04\x00\xe4\x14\xe8y\x1eq2\tY\x97\x866"\x1a\x86!\xc5]\xbe\x94d\xf5\x0e<\
+\xef\x84(\xcb\xaab\x12\x12\xd5&\xfc\x03\xeaD\xe5*{\xcc>\x90\x06\x89J\xe4\xc5\
+\xa0\n\xab\xb8\x06\xe4e\xc0\xb9\xc5\xacT-\xb5\xb7\x06g\x12\x83/\x8er\xda\xae\
+$\xa9\xec\xf7m\x12i\xef\xe6,\xda\x90\x18O\xf5\x02P\x95\xce\xb2Hga\xb9\xfe,\
+\x8c\x8c\xd6\xa3@\xc7+p^:\xdd9\xa5%~D\xf1\x8dz\xdd\x0e>\x9ej\xb5\xf26\xb1\
+\xac\x96>$\xee\x85\xaeGA\xfb\x12U\xebQ\x7f\x149\xe1\x89qK\xa7S\xea\xb1\x9dc"\
+\xbe\xa7\x94\x8a\x8b\x9cs.\xeaWo\xc9\x07\x9d\xb7\xb6\x98\xd8\xbf%\xbc\xa0bG\
+\x8e\xaee\x06\xdfk\x89\xd6\xef\xf0\xa8\n\x00\x04 \xee\xbd0\xebdJvz\x08v\xe2\
+\x82*t\\I\xf2*\xb7,K\xbd\x8c\x84T\xe1\xe65\xc9\x84l\x01\x80\xba\xf1\xbcz\xf1\
+\t\xf5\x91\x16\\\xaf6\xc8\x06H\x99\x16\x1e\t9a\xa8n\xd9\x84\x0fA\xdeE2\xfc#\
+\xb1W}\xe2\x8c\xf2Y\x01\x14CI\xceg\x9d.\x02h\x8b\xbb\x8c\xe80\x95\xf7?\xe5\
+\xa9E:VJ\xa9l\x9e\xda\xfdA\xbc%\xb0YkTe\x97N\xb3DG\xd4\xf9\xac\xbf\x1b\xa4\
+\xd7\x05 \x0b\xabb$\x8451\xf8\xf2J\x00\xd84\xad\xaa\xcb\x18\xb21\xf6\xe6\xed\
+\xad\xfb\xe8;(\xe9!\xe5x&.\x00\xf5\xd7W\x7f\xb8\xc4\xfa\x04!D\xf8\x1f\xc4\
+\x16\x05\x8c\xf1\x84\x9f=\xd7\xe1\x0cI\x8c\xa40t\x00@MV\xfc\x10Sf\xe2\xc2\
+\x10\x13\x7f\xe2^\xb8\xaf\xbew\x9d\xedT\xfb)\xe1OK\x8c\x9e\x1c\x131\xe1\xa9\
+\xe2.\xa3q\x1e\xb4k[\x0f\x8f\xeb\xb4\xd1zo\xb9\xcf\x8e\x1c\xde\xf0\x91\xfe\
+\x01\x0b\x02\xe4t#\xfc\x11zN\x15`\x98\xdeH\xcb\x87\xbc#\xc5]\xa0\xca\x9fTm)\
+\xee\xea\xaa\x8e*\xa6\x96\x9a\x9f\xb6\xed\xd4S\xb5@\x1a\xc5\xd6\xe6\xb8\xdd>\
+\xfa\x9e\xb8\x94E\xbaJ\xf5i\xa0Sv\xd5\xf6\x98\xca\xbb\xfb\xf2\x89\xa1\x8f\
+\xf1\x0e\x9b\x95]qD$\x17\x18\xc9fSZ\xf8\x01\x8cv\xb0\xbc\xf8^\x04~\x88\x1a\
+\xa0=\x17\xaa\x0e\x96\xc1o$\xb2\xf9\xc1\x9c\x10bY\x96\x18\x01P\xb4UvD\x18\
+\xcb\xf2N~P\x19\x12\x93\x07l\x9e\\\x133kbrM\x0c\xbej\'v\x1d\xfc\xfeMR\xe7\
+\x98\xae\x17@\xe7\xaf\xaf6U\xb4V\x1a\x19\xe2\x10\xc30\xd6+\x15\xc6\xd4\x17v\
+\x80\x10\x92\xcfw\xf1tKe\x07\x00\xd34\x85\xb8K}\xecz\xd1\xdewT\x13\x17\x86\
+\xd0by1\xf4\x7f \x9b-\x1eu\x84\x133n\xe7b\x89\x1c\x109\xb9\x8a\t\x8f1&\x86T\
+L~\xfdM\xaemE\xec\xdf\x94\x1b\xe9\xf5\xda\x91\xc3\xf6\x86\x85\x95}_hu\x1eY\
+\x00\xf73\xdb\xd3\x85\t\x19O\xb9S\xc5=qoK\xa1\x94\xc13\x89%{\xd5L6\xdb1\x94\
+\xa4\xed\xd4K\x98x\xa0<\xe9\xf7\x7f\x1a\x15T\xab\xb5\xeb\xd7\xe78\xe2w\x9e}Z\
+\xdc\x0f2l@\xd4\xac>\x9e\xc3\xe6\x0b:\xd1$\xd5\xe0J\x9c1a\x19\xc9\x9b\xb0SD:\
+u$a\x10\x11\xc5\xc46\x14o\x8f<\x97\xda\xfd\xfe7\x928\xd7\xfc\xc2\xe2\xca\xca\
+\x8a\xeb\xba\'\x9f:!g \xc30d\xc0b\xe2(y3w\x9d\xc9z\x9d\xc5\xf7\xfd\xebs7\x00\
+\xe0\xec\x99\xd3\xb0yr\x95\x15\x8a\xc1\x17cr\xe5\xea5D\xdc\xb7oO\xd6\xf3T\
+\xdbY^\x0c\xb2\r\xea\x0f-\x7f\xeb\x01\xe73Tv\x7fV\xe5L\xfd\xe9\x13c\xab\xea\
+\xbb\xd9\xf6\rJ\xcb\xbd\xfd\xd6\xfe\x86\xa9\x97J\xb9)7\x95\x18\xa2\xc4\x8f(\
+\xe4\x18\xdaW\xc8 \xd7m\xaf\xbe\xa8\x17\x86\xfa0\xd7\xd5g\xd8Y\t\x00H\x03_Z\
+\t\xaa\xb2\'n\x87\xc4o\xa1>\x1f \xa2\x10wy/\xcb9\xe0\x81\x1e\xf5\xb6\x1e\x1e\
+\xf9\xad\x95O\x80\x98bw\xb3}\xf0\xbd\x96h@\xeb\x8b\x00@\xfazx\x0c+\x95.n\xa4\
+\x93\xdc\xa9\xe2\x0e\xdd\x0cgi\'v\xf5Q\xa8\xb7\xb4\xbc\x87\x85U\x82\xca\xce\
+\xcb\xb2\xbcj\xdfu^\xd0\xaa\x9b\xc5\xf7\xfd\xf3\x17>\x00\x803\xa7\x9f\x95\
+\xb7\xf7\xf2r\xd9\xb2\xad\x8c\xe7\xa9\x9a\x8b\xed\xe8F\xb5=j\x93\xfa+K\xa7\
+\x9c\x89\xeb^vyum\xcdq\x9cl&#\xbb\xa0\n\x9f\xa58\xd0\xd5fX\xca"X\xd7\xeew\
+\x9a\xb1\xb2\xb0\xf8P^.\x9f\xbf\xf0\xc1\xc4\xf8\x01!\xee\xd0\x96\x83N\xa7D\
+\xa2\xfe\xfe\xde\x83\xe5\xe5\xb2\xeb:\xf9|^\xd4P\xa9\xf8\xef\x9d\xbf\x00\x00\
+gN\x7fG\x96\x97\x1dQ\x07G\x9c\xfa\xf3\x99+\x0006V\xcaf\xb3\x9d\x83O\xda\x9e\
+\x19\xf5\'V\x07g@\xc4U\x04\x00_\xcd^?p`\xbf\x97N\x8b\xebP\xbd\x08\xbbz!\xd4\
+\x8bP\xc4\xb6#\xe2\xca\xcaj\xa3\x95-k\xa3\r{\xf7\xec\x91\x1e\xea\xc4\x95 fk\
+\xd1_)\xee\xea\x0f7xG\xe4\xc8\xc0\xe6\x0b\x03\x94\x89\xa4\xcf\x8f\xd5\xd96y\
+\x94\x14hi\x82t\xc4\xc9l\xba\x11d\xcb\xc5\xed)\xeePT\xfc\xab\x0f:om=Q\xe5\
+\xa6\x88\x8c\xea\xcc\xc4+qs{\x07\xdek\xe9\x06\xf2V\x0cU\xdcgav\xf8\x0816$}\
+\x07\x8b\xbb@Z\n\x86a\x08\xdf\xb1\xbcz:\xd5J\xde\xd5\xf2\xca\x90\xe2\x9eP7\
+\xf5\x90N\x03\x017#\xafTyY\x7fq\xed\xcbs\xef\xbc\xeb\xba\xce+/\xffltdDV\xae\
+\xeaKg{\xfa\xc8z\xa2\xbf\xaa@\xc8^_\xbb\xf6\xd5{\xef_p\x1c\xe7\xcf\x7f\xfa\
+\xa7\xa3\xa3#\t!\x93\xb7\xa5\xac_~\xdf\xd9\xf7\xfe\xdd\xef\x1c\x04\xde~}0qc\
+\xabS`\xff\xfa\xe5\xcf!+\xbfr\xe5\x8b\xb7\xcf\xbd\xe3\xba\xee\xbf\xfe\xf9_\
+\x8c\x8e\x8e$\x14\x017\xbf\x04 \x7fJ1\x1aj\x9b\x85\xf3G\xedBb\xf0\xd5\xeb\
+\xe1\x81\xf4"\xd1\xdas\x7f\xfc\xe3\xfe}\xfb\xfe\xf4\'\x7f"\xa2\xf4\x12\x17I\
+\x9f\x81\x95\xd6\xbd\xefWo\xde\x9aO$Q!\xc4\x98\x98\x18\xb7m\xbb\xb3m\xb2\xe5\
+\x9d\xa6C\xff\xdf\xae\x0f\xea\x05\xd39\'\r^a\xe2B\x95\xb7d\xc2\x8a\x82\x8e\t\
+/q\n!\xe5\xdfV\xef\xb6\x06\x16\xac\xf1\xb0\x95\x84\xbc\x8fs<]<4Hm<\xaa\xb0f\
+\x19\x00\x88H\xda\xde\xc3\xc3c\xa5\x8bnvl\xd37\x0f\xd0\xe4\xed\x8a\xfc\x81\
+\xc5E /\x05Pn\xbf\xc4e\xd1yl/\x01J|\x90\xa0\xf2\x02\x0bk\x8b\x8ex\xb2F\xc4\
+\x85\xc5\xdb\x00\x10\x86\xd1\x9d;\xcb\xfb\xf6\xee\xed\xac\xbfk{\x1e\xf4\xb6\
+\x11KL\xe2K\xc30\x96\xee\xde\x05\x80(\x8a\x96\xcb+\xfb\xf7\xef\x935\xab\x1f\
+\x12g!\x9b%u\xc0\xee\xcb)M\xfdW\xfe!\xd1\xa9\xfbV\xae6C\x95\xc2\xf9\x85\x05\
+\x00\x08\xc3\xf0\xeb\xf9\x85b\xb1\x90\x18g\xa3\xdb\x82s\xc2\x1e\x14\x1f,\xcb\
+t]7Q\xb2\xff\xb0\x0c\x82\xda\xda\xa5;w.|\xf8\x11\xe7\xb8\xb0x\xfb\x9f\xdf|\
+\xeb_\xbd\xf4\xe2\xfe\xfd\xfb\xd4\xee\'\xb4L=\x9d\x1c\xc0f\xb39s\xf5\x8bz\
+\xa3\xb1\xf9\t\x9e\x14\x8b\xf9R{\xaa\xee\xec\x05t\xfc\x88]/\xef\x01\xe9Ug\
+\xe7\x9f\x06\xaf\x8d\xb4\x9fl\xd4\xc9\xbe\xeb\x80$\xee\x059\xaf|[\xbd\xdb2b\
+\xff\xa6\xf8\xd0\xc7lO\x17\xc6\x07\xdbk\t\xe3\xf6C\x00\xde\xc7\xc3\x93\\\x98\
+\xdd\r\xe2.P\xef\x99^\x82\x02\xbd/\x88\x07\xbaP\xb0\xbd\x89k\xcb\x87\xd8~_\\\
+\xec\xb5\r\x00\xcf>=\xbdR^\xc9\xe7s\xd3\xa7\x9e\xea\xff\xfc8\xf8ye\xa7f\xae\
+\\\xbd~}\xaeT*}\xff\x85\x8d\xb4\xef\x9c\xf3g\x9f\x99^YY\x15\'\xb5\xda\x91d}\
+\xce\xf2\x10\xb7\xab\xda\x18l\x87\xeem\x84\xa3(\xb1I\xfdO\xdd\xa7Z)v\xcf<=\
+\xbd\\^\xc9\xe5\xb2\xc7\x8eM\x8aH\x1e9\xce\xbd~_D\x94K\x1d\x1b\r\x00"\x9dQ\t\
+\x1eZ\x1d\xe4\xf4\xc69\xafVk\xe7\xdey\xb7\xd9l\x8aG\x97ry\xe5\xcd\xb7~\xff\
+\x9dg\x9f9u\xf2d:\x9d\xeas\nY\tc\xec\xee\xdd\xe5\xcb\x9f|Z.\'v6G\xd3\xb4\xa6\
+&\'=\xcf\xebe\xa5~\x93\x1f\xb1\x17\xdfz\x9d\x0fwo\xee\x08\x1d\xef\x845\x96E\
+\xc0\xa2\xd0\xe2\xee\x85\x08qs\xfb\x06\xab\xad\xcc\xc3u\x00 \x04\xfa$\x1bps{\
+;\xd3I\xee\x1eqWy\xa4\x97\x82zO\x8a5"\xcaZ\xa2\xc3\x18\x13\xa7\x1e\x1a*\xfe\
+\xf5\xbf\xfdK\x19Z\xf0\r\xdb\xa3Z\x7f\x88\x18\x06\xe1\xfc\xc2"nn\t"\x0e\x0f\
+\r\xfd\xbbo\xef\xa4\xf7ELl\xedU2\xd6j\xe8\xc3\xbe\xef.\xad`\xd9\x9db\xb1\xf0\
+o\xfe\xf2Uu\x16\x91\x92\xad>\xa4\'j\x90\xbf\x8bl\x89|s\xfd\xdb\x1a\x10y"Ji\
+\xb5Z{\xf7\xbd\xf7\xef\xdd[\x17g\x10\x05\x1a\x8d\xe6\xf9\xf7?\xb8q\xe3\xd6\
+\xe1\xc6 3\x08\x00\x00 \x00IDAT\xc3\x87&\x8f\x1e\x19\x1a*\xf6\xa9di\xe9\xce\
+\xdc\xcd\x9bs\xd7\xe7\x1a\xcd\xa0#\xcd\x00\xd9\xbbw\xec\xd0\xc1\x89\xae\xfe\
+\x8a\x1d\xca.\xe8B?\x90G2\xb5o\xbfP\xf4\xd1\x81\xccvd\xf2!\x00\xb1\xe7TAL\
+\xdb\x1b:\xdc\xf9\xfd\xee\x14\xf7- \xa1\xef\xe2K)\xee\x82o\xf1nDe\x15\x81\
+\x8b=Fp\xc35\xa4\xc6\xbal\x8d\x04\x08m\x12\xddOX\xee\x9d\xb2\xfb@\xd5\xca\
+\xee\xc8\xc9L~\xa3\x8a{\x9f\xc3\x93\xe2\xfe\x90\xd3M\xcf\x16Je\x8f\xe3\xf8\
+\x8bk\xd7\x16\x16\x17U\x07\x82\xe4\xce\xdd\xbbw\xee\xde\xfd\xec\xf3\x99=c\
+\xa5\xfd\xfb\xf7\r\r\x15E\x8c\x87\x18\xb7 \x08WWW\x17\x17o\xaf\xae\xdd\x0b\
+\x02)\xeb\x9bj\xc8\xe7sO\x9f:\xb9\xfd#C4\x92M\xa9}{\xee\xc8axCG\x06\xaa\xadv\
+\x1bi\x13\x85\xd9\xde?/\x8d\xd5%\xf5\xe6n\x10wy\x1b\x97\xcb+\xf3\x0b\x0b\x0b\
+\x0b\x8b\x00\x90\xcf\xe7\xc7\xc6J\xd3\xa7N\xaae.^\xba\x0c\x00S\x93G]\xd7\x9d\
+\xb9r\xb5\\^\t\xc3\xd0u\xddRi\xf4\xe9\xe9S\xd23{\xdf\xc2O\x9d8.V2[2\xd4N\xa5\
+$|\xee\x00P\xab\xd5\xe6\x17\x16\r\xc3\x10A{\xaa\x18\x95\xcb+\xb3\xd7\xe7\xca\
+\xe5\xb2\xdaHY \x0c\xc3\xd9\xebs\xe5\xf2\x8a\xef\xfb\x000>~`j\xf2h.\x97\x13\
+\x9au\xf9\x93O\x11\xe1\xf6\xd2\x12\x00\xf8\xbe\x7f\xf1\xd2\'\x00h\xdb\xf6\
+\xe4\xd1#\x9c\xf3>\'\xf5}\x7f\xf6\xfa\\\xd7\x91\xe9\xd5_Yxjjrb<\x99\xf8B\xf5\
+Kl\xbc\xa3\x84\xc89\xbf\xbd\xb447wS\xb4\xbfT*ML\x8c\xcb\xc3\xe5\x89\x08!S\
+\x93G\xf3\xf9\xbc\xfa\xfd\xcc\x95\xab\xcdf0<T\x1c\x1b+\x89\xee\xdc\xfaz\x1e\
+\x11\x9f:q\\\x06Z\xa8gW\xfb8\xbf\xb08??\xbf\xbc\\\xe6\x88C\xc5\xc2\xa1\x83\
+\x13\x9eL\xad\xbe\xb9p\x18\x86\xf2\xd7\x14C!\xba,\x0b\xcc/,\x96\xcbe\xd7u\
+\xa7O\x9d\x9c_X\\XX,\x97\xcb/\xbe\xf8\xc2X\xa9$\xeb\x13\xe2\x1eEQ!\x9f\xcff3\
+\xca\x1e\x1d\xad"R\xa6\xeb\xf5\xda\xdc\x8d\xfa\x8d\x9b\xb7\x0c\xc3P\xc5\x191\
+\xb1k\nI\x98\xed\xe9t\xea\x99\xe9S\xb9\\\xcez\x90\x97\x845\x8f\x11\xe44\xae\
+\xcd\x8b\xcf}R\xfb\xba\xb9\xbd\xa6\xe3u\xfb\xcb\xe6\xdaX\x14W\xe7\xc55\xc1y\
+\xcf\xcd\x9bL\xc7\xeb\x95\x97fg\x8b\xbb\xbc\xc9\x83 \xf8\x97s\x7f\x14A\xd0m\
+\x16g\xae\\\xfd|\xe6\xca\xab\xbf\xf8_\\\xd7\x15\xc5\xce\xbf\xff\x01\x00p\xce\
+/\x7f\xf2Y\x18n\xec\x113{}\xee\xe2\xa5O^\xfb\xd7?/\x95Fe\x9d\xfd\x0b\xbf\xfc\
+\xe7\x7f\x96\xcf\xe5Z\x02\x87\xc98\xeeZ\xbd\xf1\xf1\xc5\xcb\x00p\xf6\xcci\
+\xc5\x84\xc4\x7f9\xf7\xc7+W\xbf\xe8l\xe4\x8f\x7f\xf4\xc3\xd1\xd1\x91\xd9\xeb\
+s\xe7\xdeyW=\xd7\xfc\xc2\xe2\xf9\xf7?\xf8\xd1\x0f\x7fplj\x921\xf6\xe1G\x17\
+\xe5\x9f\xfcj\xf5\x83\x0f?\x02\x80\xfd\xfb\xf6M\x1e=\xd2\xe7\xa4\x17/}r\xe1\
+\x83\x0f;O\xfa\xca\x9f\xffTD\x19\xf6\xed\xef\xe2\xcc\x95\xab?\xfe\x93\x1f>=}\
+\xaa\xeb\xe0\xb7%\xbe\xa5\x9e\xe7\xdey\xf7\xda\x97_\xa9\xed\xbfx\xe9\xf2\xf4\
+\xa9\x93?\xfe\x93\x1f\xca&\x89\x13\x8d\x8e\x8e\x8a\xb7.\xa5\xf2^\xbf>\xb7\
+\xb0x\xfb\xf9\xe7\xce\x8e\x8d\x95\x00\xa0Z\xab_\xba\xfc)\x00\x9c8~,\x11b\xd1\
+z\x82\xe1\\\xfc\xf4\xef_\xf8P\x1d\xd5\xa5\xa5;W\xae^;\xf3\x9dg\xd5v\x8a\xc23\
+W\xae^\xf8\xe0\xa3D\xef>\xbex\xe9\x95?\xff\xd9\xe8\xe8\x88(\xbc\xbc\xbc\xfc\
+\xfe\x85\x0f\xc7\x0f\xec_^.\xcbj\xcf\x04\xa1|"\x91\x8f,Q\x14e\xb3\x99g\x9f\
+\x9e\xfe\xf2\xab\xd9\xd5\xb55\x191\x94p\xad\xc8C\xd4\xc1S\xd4\\j\xfa\xc6Q\
+\xd9lf\xfa\xd4S\xa3\xa3#\xea\x1b=\xa0\xd9\xde\xd0\xda"\xd2\x00@\xe4k|0\x17J\
+\'q\xf5\x16\xaaI\xdb{\x86?\x1e\xed\x95\x97f\x07\x8b\xbb*.\x94\xb2J\xc5\xcf\
+\xe5\xb2\xcf>\xf3\xf4S\'\x8e\x13B\x96\x96\xee|\xf0\xe1\xc7\xe5\xf2\x8a\x10G\
+\xd5qq\xe1\x83\x8f\\\xc7y\xfe\xb9\xb3\xa3#\xc3\x84\x90Z\xad\xfe\xc9\xa7\x9f\
+\xf9\xd5\xea\xaf\xff\xbf\xdf\xfe\xdb\xbf\xfa\xa5Hlt\xdf\xc2o\xbc\xf9\xfb\xbf\
+x\xf9g\xae\xeb$\\\x04\xad\xb5\xa3n\x8d\xbc\xf0AK\x83\x9e\x7f\xeelit\x04\x08Y\
+]]\xfb\xe4\xd3\xcf\x00\x81qF)m6\x9ba\x18\x9e8~\xec\xd9g\x9e\xce\xe7sqL\xbf\
+\xb8\xf6\xe5\x07\x1f~t\xee\x9dwGGGR\xae\xfb\xec3\xd3\x88\xb8\xbc\\\xbe\xbb\\\
+\xcef\xb3\'\x8e\x1f#\x848\x8eM:\x1e\xda\xe5y/]\xfe\xe4\x83\x0f?\x06\x80\x13\
+\xc7\x8f\x1d9|\x88\x18F\xadV\xfb\xe4\xd3\xcf\xca\xe5\x15\xd1_\xc7qT\x13\xf2\
+\xc2\x07\x1f\x8d\x8e\x8c\xbc\xf4\xe2\x0b)\xd7\x8d\xa2\xe8\xd3\xcff\xca++o\
+\xff\xcb;\x13\xe3\x07\n\x85.\x1b\x80\xa9.\x94\xa5;w\x97\xee\xdc\xdd\xbfo\xef\
+\x89\x13\xc7S\xae\x0b\x84\xdc\xbcy\xeb\xea\x17\xd7f\xae\\u\x1c\xe7\x85\xef=\
+\xbfi\xac\x14\x83\x1aU\xe5\x86d\xbc\xcdFq\xd8\x10w\xf1\x90\xc49\x7f\xef\xfc\
+\x85/\xae}\t\x00\xc7\x8fOM\x1c8\xc0\x18\xf3\xab\xd5k\xd7\xbe\xba\xf4\xc9\xa7\
+\xad\x16\xb6=Z\x0b\x8b\x8b\xe7\xdeyW\x8c\xff\xd1#\x87\xf2\xf9|\xad^\xff\xe4\
+\x93\xcf\xae~q\xed\x8d\xdf\xbd\xf5o\xfe\xf251\x14\x8cq\x00XX\xbc\r\x8b\xb7GG\
+G&\x8f\x1e\x89\xa2x\xfc\xc0~u`\xa5\xf3\'\x8ec\xd34\x8e\x1e9\x94\xcdxw\x97W\
+\xda9\xdc\xef\x0b\xc1\x8d\r\xbc\x89j\xb1\x13B\xf6\x8c\x95\xa6&\x8f\x14\n\x05\
+\x99\x9ba\xd78\xdcw1\xc8cZk\xed\xd0\xdb\'GX\xaa0nX\xf7\xcfw\xc6\xe3\x9a\xcc8\
+\xc6\xfa\xbe\xe0\xeadF{U\xb2S\xc5]\x15M\xc6\x98i\x1a?\xfb\xe9O\x10Q\xbc\xe8L\
+\x08\xd9\xb3g\xec\xf9\xe7\xcf\xfe\xf3\x1b\xbf\xbb>7\xf7\xe2\xf7\xbf\xa7\xea\
+\xb5\xe38\x7f\xf6\xa7?\x16)\xf4D\xc9C\x87\x0f\xbe\xf1\xe6[++\xab\x17/]\xfe\
+\xc1K\xdfW=\x00\x9d\x85\x0f\x1e\x9ax\xf3\xcd\xdf\xaf\xac\xae\xce\\\xb9z\xe6\
+\xf4\xb3\xf7m\xa7P\x96\xf5\xf5\xca\'\x9f~\x0e\x00/|\xef\xf9cS\x93\xe2A{\xdf\
+\xde=\xfb\xf6\xed\xc9\xe7\xf2\x8ec3\xc6\xa6&\x8f\x96J\xa3\xe2\x15$J\xa9a\x90\
+\xe9SO-,.\xde\xbe\xbd47w\xe3\xd8\xd4\xe4\xd1#\x87E(\xc8\xdd\xe5r6\xe3\x9d:yB\
+\xbe\xdf\xa8\x9eTFd\xae\xafW\x84\xb2\x8b\x93\x12B\x0c\xc3\xd8\xbbg\xec\xf0\
+\xa1C\xff\xfc\xe6\xefVVV\xcf\xbf\xffA\xa2\xbf\xd9l\xf6O\x7f\xf2#\xc7qD\xe1\
+\xe1\xe1\xe1\xd7\xff\xdb\xff\x0b\x00\xf3\xf3\x8b\xf9|\xbe\xab\xbep\xce\xb1\
+\xfd\xec2y\xf4\xc8\xf7_\xf8\xae\x18+\xd1\xc1\x91\x91\xe1?\xbe{\xfe\xd2\xe5ON\
+\x1c\x9fR\xf7\xbc\xe7\xca/\xa8\x1a\xe6\x9bt\xfe~\xa3Z\xa9\xf8B\xd9_\xf8\xde\
+\xf3\x93G\x8f\x08\xc1-\x16\x0bc\xa5\xd1\xf7/|\xb4^\xa9\x00\x00\xb6\x9d\xf5\
+\xef\xfc\xf1=\x00\xf8\xfe\x0b\xdf\x15C\x11\xc7q:\x95z\xe9\xc5\x17\xc20\x9c\
+\xbbq\xf3\xf3\x99\xab\xcf<}\x8as\xce\xda\xcb\xe3OO\x9f\xfa\xdew\x9f\x93q\xd6\
+28/1\'\x89\x96\x0c\x0f\x0fy^\xfa\xde\xba\xbf^\xa94\x9b]%^t\xaa\x1d\x88\xa2f\
+\x17h\x9b\xf0\x85Ba\xac4z`\xff\xbet:\x9dHN\xd9\x7f@4\x8f\x9d\xd8\xffZ\x18\
+\xda\xe2\r\xd2\xae?\x18\xb1\xdcta\xa0\xd4\xbeq\xe5\x86\\,\xea\x1d\xfeh\xf4\
+\x7f\xc1ug;\xf2\xa4\xb83\xc6\x84u\x13\xc7\xb1\xc8R\x14E\xd1Xi\x14\x00\xc20\
+\x12O\xd0\xf2a\xfc\xe8\x91C\xd9l&R0\x08yf\xfa\x14\x00\\\xfd\xe2\x9a8\xb6\x7f\
+\xe1\xe9SO\x01\xc0\xec\xf5\xb9\xce\xf6\xb4n\xf8\xf6\x0f#\x9a\x17\xc7\xf1\xec\
+\xf5\xeb\x0004T<tp"nC)\x1d*\x16-\xcb\x94\xc9\xcb\xd2\xa9\x94\xf8\x938W\x1c\
+\xc7{\xf7\x8c\x01\x00c\xad\x15\xbc8\x8e\x85i\x89\xed\xb0\xbf\x8d\x15\xd5\xcd\
+\'\xa5\x94^\x9f\x9b\x03\x80\xe1\xa1\xa1\xc3\x87\x0en\xea\x82A\x9eyz\x1a\x00\
+\xe6\xe6n\xc8s\x89c\x8f\x1c>\x84\x88\xb2d*\xe5\xee\xdf\xb7\x0f\x00\x82\xb0\
+\x8b`\xa9\x02\'\xbe9u\xea)9\xfe\x82\xc9\xa3GFF\x86\x01\xe0\xab\xd99\xd1\xfe\
+\xd6\xb1\xca2\xa9\xe8\xbb\x14\xcd\xf6JF\xcf\x94\xa6\xbc\x9d\x0eH\x8e\xea\xe1\
+C\x07\xd5|\x90\x86aL\x1e=\xac\x16\xbe\xbb\xbc\\\xad\xd6\xb2\xd9\x8c()\x1a)\
+\xca\x1f:8\x01\x00\x8b\x8b\x8b\xe2{J\x19\x008\x8e\xf3\xec3\xd32\x97\x9c\xa8J\
+\r\xe9\x93oTJ\xcb\xdau\xdd\xbd{JG\x0f\x1f\x9c\x18?04Tp\x93\ti\x93\xeb\xa5\
+\x12\xd7u\x87\x87\x87\x8e\x1c>t\xe2\xd8\xe4\x81\xfd\xfbR\xa9T*\x95J\xe4\xcb\
+\xd4\xfa\xbe\x9dA\x1a\xd0\x86\x92\xda\xb7G\xb1\xcc\xe67H{\xc1\x825\xd6\xde?\
+\xb9O\xa4|*\xbf\xaf\xff\x0b\xae;\xd5r\x87\x8e\x07\xe4f3\xb8\xf5\xf5\xd7w\x97\
+\xcb\xeb\xeb\xeb\xf5\xfa\xa6\xedo\xa2(RcZFFFd@:\xb4\xdf\xad8\xd0~\xee\x9e_X\
+\x18\x19\x1e\xeeS\x98s._Q\xb9{wY(\x97l\x12\xdf\xfcv\xb5x\xad\x9cRz{\xe9\x0e\
+\x00\x1c\xd8\xbf_D\xd4$\x8cS\xd9\x91\x95\x95\xd5\xf9\x85\x85{\xf7*\xcb\xe5\
+\xf2\xe6\x02\\\xc9\xcb\xd1\nM\x11\x16\xba\xf0N\xa8\x8f&b\x85\x931V^Y\x05\x80\
+\xfd\xfb\xf7\xd1v\x848i\xbf.(\xfc\x0ca\x14\xdd\xbd\xbb\x9c\xcbee\x7f\xf3\xf9\
+\x9c\xec/i\xbd\xfa\xc8\xe5hwJ\x8c\xec2\x00\x94FGL\xc3\x90\xcf\x01\xa4\x9d\
+\xf8\xe9\xe0\xf8\xf8\xea\xea\xda\xe2\xed\xdb\xc7\x8fM\xca\x1a\xb8\xb2\x1e\
+\xdb\xd2P\x8e\x00\xc0X;\x1f\xcbf\x0f\xbb\x8ah-\xe7\\\x8ej+\x10^\xc9\xb1\xb3w\
+o+\x8d*\xe7<\x8ec\xb1>\x1c\x85\xd1\xff|\xfb\x9c\xac\x87\x00!\x04\xa28\x06\
+\x80\xdbKw\xd4\xeb\xa4X,\x08\x8b\x01\x15\xe4\xb0$\x12\xc8\x88\x0bC\xf4\xc2u\
+\xdd\x92\xeb\x8e\x0c\x17\x19\xe7A\x10\xd4\xeb\xcd \x0c)\xa5\x9c3\xe4\xadH\'\
+\x83\x18\xc4 \x96i\xa6Rn6\x93\xc9d<\x99\xfdX\xec" \xc4\xfd!\xf2\xc3h\x1e\x0b\
+\xb1\x7f\x138\x07\x02\xbc\xf7\x1b\xa4\xa6\x93\x19,\xb5/\xc6\x95\x96\xd5\xd8?\
+R>U\xb8\xcf\xb6|;X\xdcA\xb1\x1c\xc30\xfc\xdd\xef\xffg\xbd^\xb7m\xbbX,\x1c>tP\
+\xa8\xdeg\x9f_\x81v\xbe=uE\xcbho\xd3!\x14a\xb3\x1c\xb7\x0c\xc9>\x85\x15\x85\
+\xe2\xaa\x17\x81\xb7S\xcb\xa2"\xee\xf2(\xf1\r\xe9\xf6\x9a\xb5\xa8\xfc\xca\
+\xd5k\x9f~\xf69\x00\x88.d2\x1e!d\xb9\xbc\xb2\xbc\\\x16G\x93V\xea\x8fM\xef\
+\x98(A;\xc9\x93\xca\xe7\x0f\xb9\x12\xd8\xd9\xdff\xd0L\xa7S\x9b\x97\xfb6<\x0f\
+\x94\xd2v \xcc&\x81K\x94T\xc7J\x0e\x97\x10Y\xc30\x18g\x00\x80\x1c\xd5i2a\xb9\
+\xf3\x8d\xd8G\x99l\xabg\xe0\xa3\xecB\xfbO\x9b^\x91\x97\xad\x95\xa3\x11\xb57\
+\x85\x88\xe2xy\xb9\x0c=h\'\x8bf\xed*A\xadV*;i\xe7]\x1116DI\xac/sc\x89\t\xc0\
+\xb1\xedB;"\xa8+\xf2X5\xa1|*\x95\x92\xdev-\xeb\xdb\x1f\x16\xdc\xa3\xcd2\x00\
+\x00\xf6{\x834S:\x0e\x03\xfc\x9a\xb1\x7f\x8b\xc7\xad\xe0+J\xb1\xe7n\xda\xf9\
+\x03\xf7\x8d\x94\xdf\xd9\xe2\x0e\xed;\xff\xc3\x8f.\xd6\xeb\xf5\xb1\xb1\xd2w\
+\x9f;c\xb6\xf7O0\x0cC\x88;\xdb\x9c!\xb2\xd9l\x8a\xb4\x85mC\x189\xe72\x9a-\
+\x9dJ\xa9Z\xdcY\x981\xd6h\x88\xd4N\xe0\xa57\x854Iq\xef4\xa23\x9e\x07\x00\x8d\
+FC<hwF.\xaf\xae\xad\te\xff\xdew\x9f\xdb\xb7w\x8f\x90\'!\x94\xcb\xcbeB6t\xd30\
+\x88\xec\xa00\xae\xf9\xe6HA)\xee\xc5Bay\xb9\xdch4d\x02U\xf9`Q\xab\x89\xd4\
+\x83\x90\xcf\xe5\xd4\xfe\x1a\x84\xa8\x99\xd48\xe7b\x19S\x95K\x15U\xf8\x1a\
+\xcd\xa6\xa5d\xc9\x1750\xc6\xaa\xd5\x1a\x00x^Z8LZ?\\{\xbe\x90\x12/\\C\x9c\
+\xb7\xa6\x1f\xec\xd8\xabS\x9e_\xfej\xedQm\xcaL\xc8\xb2\xa9\xf27\x12n\x19qU\
+\x94FG^z\xf1\x05y\xe5H\xa5\x96\xc3\xb8\xb1zA@\xa6ZK\xfcRD\xd9\'D\xb5\xe2\x13\
+\xe9\x97\xf9\xa6H\xc7.\x83&\x1d;B\xd9\xd5\x84\xfe\x83$\x94\xd7l\x0f0\xf6\xe7\
+\xc4\xfa)\xa5}\xde \xdd\xd7\xf9\x06i\'\x9c6h\xb5\x15L\xc99P\xd6\xbd6\xc3Jy\
+\xc3\x87\xef[\xdbN\xf5\xb9K]\x10\xff~=\xbf\x00\x00\'\x8eMY\xca\xb6\xc5\xabkk\
+jay\xec\xe2\xed%K\xd9\x01G\x14\x98_X\x04\x80L\xc6K\xa7S\xea\xfad\xd7\xc2\x0b\
+\x8b\x8b\x00\x90\xf1\xbct:\xa5\xd6\xbc\xa1V\xb8!\xee\xe2V/\x16\x0b\x00\xb0\
+\xb0x\x9bs.w\\2\x95\xec\xd8w\xee,\x03\xc0\xd8X\xe9\xc0\xfe}\xd2L\xe6\x9c\xcb\
+\xb7\x1f\xd5\xb4|\x00@\xda\xf9\xb96\xce\xab\x9cT\x8c\xcc\x90rR\x99\\W\xedo\
+\xb1X\x10R.\xbb\xa0\xa6\xe1\x15%\xa1e\xb9\xf7\xfc-\x08!by\xb0^o\xac\xac\xae&\
+\xa2\xf7\xc20\x14\x99v\xf2\xf9\x9c:\x03U\xfd\x9a\xda\xcdZ\xad~o]\xf4\xb4\xfd\
+\xb3\xb6\xbb\xd39\xaf\xc8\x87\x95ba\xa3\x83\x89}6\x96\xee\xb4\xf6I`\x9c3\xc6\
+\x86\x87\x86\x00\xa0\xbc\xb2\xda>\xcbF\xcd\xd5Z-\x0cCuJ\x86\xb6jK\x97\xfa\
+\xa6\xce*\xe6\xb60\xb4\xe5\x16t\x12\xcf\xf3\xd2\xe9\xb4X\x17\x95\xbb;\xc9m\
+\xfc\xe4Q\x9e\xe7%\x0e\x91\t\xee\xb5\xb2\xef\x08hc\x99Gu }\xc3\x1f\r+]<8Hmq\
+\xe5\x86\x8cM\x88Y\xcf{\xce\x1b><\x88\xef~G\x8a;\xb6\xc3\x9c\xa5\xc5\xea86\
+\x00\xc8\xfb\x16\x11\xc30\x14\xd1)\xd0\xe1/\xbe{w\xf9\xc3\x8f.\n\xbd\x13Z\
+\xf9\xe5W\xb33W\xae\x02\xc0\xe1C\x07\xf9\xe6u\xbc\xce\xc2_\xcd^\x9f\xb9\xf2\
+\x05\x00\x1c:4\xd1i\x9a\xb5\xf5*\xf1\xbfx\xe4\xf0\xa1L&\x13E\xd1\xff\xf8\xc3\
+\xbf\x88\xa7\x01!\x7f\x1f}|\xe9\xda\x97_I+\xaf^\xaf\x0b\x03V\x1cu\xeb\xeb\
+\xf9\xc5\xdbK\x00 ,w\xa15\x84\x18\x00\xb0vo]x9\x14}\xdf8\xa9\xa8\xf0\xc0\x81\
+\xfdCC\xc5(\x8a\xfe\xe7\xdb\xe7\x82 \x94\'\xbd\xf6\xe5\xec\xe73W\x01\xe0\xf8\
+\xb1)q\x88\x1c\x1cssfyQ\x9f\xecJ\xe7o\xd1\xb2\xdb\xdb;|\xfe\xf1\xdd\xf7\xcb\
+\xe5\x15\xa9\xb0\xf5z\xe3\xdd\xf7.\xc4q\xecy\xde\xa1\x83\x13\xa2S{\xc6J\x00\
+\xf0\xd9\xe73Q\x14\x89o\xc2(\xba\xf0\xe1G\xed\xc6\xcbq\xebw\x01\xb4F\xf5\xc8\
+!\xc7\xb1\x13\x1d4\x0ccn\xee\xa6\x0c\xb7\x17\x17I*\xe5\x8aw\xa9\xce\xbf\xff\
+\xa1\x9a\xc2\xe5\xde\xfa\xfa\x87\x1f]\xfc\xe3{\xef\xaf\xafW\xa4p\x03\x00\x01\
+\x920\x9f\xe5\x10\xa9\xfan\xb57\x9b\x95J\x9d\xcdf\xb3\xd9\xac\xf8 \xffU\xc9n\
+F\xca\xba\xea\x8d\xd1\xca\xbe3@.wGb\xac\xb7\x0be\xb0\x1ca,\\g\xcd\xd6\x95\
+\xd9g\x1du\xf0m\xf9v\xaa[FZ|\xc2c{\xfc\xd8\xb1\xcfg\xae|>s\xb5V\xab{^:\x8c\
+\xa2\xa5\xa5;\xb6m\x17\x0b\x85\xf5J%\xa1\x13\xc3\xc3C\xd7\xbe\xfc\xea\xe6\
+\xcd[\xc3#\xc3\xc8y\xb5V\xaf\xd7\xeb\x00p\xf8\xd0\xc1cS\x93r1\xb0\x7f\xe1C\
+\x07\'\xa6&\x8fB7\x8fpg;\xc5\xe7\x1f\xbc\xf8\xc2\xdb\xe7\xfe\xb8\xba\xba\xf6\
+\xf7\xaf\xffJ\xac\xdf\xfa~\xb5Z\xad\x02@\xcau\x0f\x1f>\xf8\xc5\x17\xd7\xea\
+\xf5\xc6\xdb\xe7\xfe(tp\xbd\xe2/-\xdd9r\xf8\xd0\x8d\x9b\xb7\xa4\xbe\x18\x86\
+\xb1g\xac4s\xe5j\x14E\xff\xf4\xdf\xdf\xc8f\xb3\xd5j\xf5/^\xfeY\xaf\x06\xbc\
+\xf4\xe2\x0b\xffr\xee\xdd\xd5\xd5\xb5\xff\xe7W\xff\xb8o\xdf^\xb5\x0b\xd3\xa7\
+N\xee\xdf\xb7\x176\\\xf9\x00\xcaN\xad\x98|\xef\xe6\xfed\xb3\x99(\x8a\xdf\xf8\
+\xdd\xefGF\x86\x1d\xdb\xe6\x88w\xef.\x03\x80m\xdb\xcf\x9fm\xbdW\x85\x88G\x8e\
+\x1c\xbe\xbb\\\xae\xd6j\xaf\xff\xb7\x7f\x1c\x19\x1e\xe2\x88kk\xf7\x00 \xe3y\
+\xf5F\xa3\xff):G\xf5\xa5\xef\xbf\xf0\xde\xfb\x1f\xc8\x0e\n\x8fS\xbd\xde(\xe4\
+\xf3\x15\xdf\x97\xe5\x01\xe0\xc4\xf1)\xbfZ\xadT\xfc\xf7\xde\xff\xa0X,\x88\r\
+\x0b\x85\xd0\x17\x8b\x85l6#\xbd\xea\x00@\x0c\xa2F\xc2t\xb6A|)&K9\xef\xca%\
+\xd6\x8d\x85\x90\x8eiJ\xf5\xc9\xa8\xf16]\'\x12\xcdv\x86\xd6\x97\x906\x81\x00\
+"\xf6z\x83\x94X\xae\xba\x81Fo0\xae\xb4\xde\xc1\xec\xb7\x8e\n\x90\x19\x99\x1a\
+\xc4w\x0f;Z\xdcU\xcb\xfd\xd8\xd4QD\xfc\xf2\xab\xd9\x9b\xb7Z\x13\xe9\xc1\x83\
+\xe3\'O\x1c\xbfx\xf9S\xf5\x10\xf1\xe1;\xcf>\xe3\xfb\xfeg\x9f_YZ\xba#\xbe\xb1\
+m\xfb\xf8\xb1\xc9\xe3\xc7\xa6Py\x0b\xb1O\xe1cSG\xa7&\x8f\x0er\x07\xaae\n\x85\
+\xfcO~\xfc\xc3O?\x9bYZ\xba\xb3\xb8x[|\x99\xcdfO>u|dd\x98s\xfe\x83\x97\xbe?s\
+\xe5\xeary\xe5\xca\xd5k\x00\xe0y\xde\xf3\xcf\x9dI\xa5\xdc\x1b7oI\x13Y\xd4\
+\xf3\xd4\x89\xe3_\\\xfb\xb2Z\xad\twv\xd7\x93\x8a.d<\xef\x95?\xff\xb3O?\x9b\
+\xf9\xf2\xabY\xd9\x85L\xc6\x9b>u\xf2@;\xe6G\xd5/\xa2\xe8\x0b\xe9\x88\xea\xe9\
+\x8a\xd4\xafL&\xf3\xa3\x7fu\xfa\xf2\'\x9f\xden\x9f\x08\x00J\xa5\xd1S\'O\x14\
+\x0b\x05in\xef\xdf\xb7\xf7\xf9\xb3g>\xfd|&\x8a"\xe1<)\x95FO>u\xfc\xea\x17_\
+\x0e(\xeej\x07K\xa5Q9\xaa\xb2\x83\x07\x0f\x8e\x9f86\xf5\xd6\xffx\x1b\xda\x0f\
+=\x00\xe08\xce\xf7\xbf\xf7\xfc\xf5\xb9\x9b\xb7\xbe\x9e__o\xedU\xee\xd8\xf6\
+\xf8\xf8\x81\xa7\x9f>\xe5\xd8v\xfb\xf9i\x93\x85\xde\xe7W&\xed\x98Hh\xaf$\xa3\
+\xbaV\xa1\x908J\x9d\xaa\x8d\x07L\xe8\xaf\xd9& \xa7q\xdb?Ni\xef7H\x87\x0e\r\
+\xe2B\xa1\xf5\xbb<j\xd9"}\x1e\x02\xdc\xdc^+\xd5o\x89^e\xa0\xbbw\xbb\x81\x88"\
+\xe8[\x04\xa4\xabY\xa2\xca\xe5\x15D\xcc\xe7s\xc2.\x93\xef\xf8\x08#\xf4\xb7\
+\xff\xfc;\x00\xf8\xd1\x0f\x7f0~`?!\xe4\xf6\xed%\xc6\xb9e\x9a\x85B^j\x99X\x7f\
+\xe3\x9c\xff\xf77\xde\xeaU8\xd1\x18av\xc9\xff\x15\xb6\xbf\xba\x9b\x0c\xdf\
+\xbc\xef6c\xec\xde\xbduD\xccd\xbct:-wR\x16\xe5\xeb\x8dF\xa3\xde0-\xb3\x90\
+\xcf\x1b\xed-\xd3H{\xcb1\x11\xc6#\x96\x16\xd6\xd7+@\xc8P\xb1@\x08\x11\x1d\
+\x94\xbe\x149\xf9\xa9&\xe1\xd2\xd2\x1d\xc6\xb9\x97N{^Z\xf5\xe7\x88S\xab\xfb\
+\x86\x8b\xf0\xc1(\x8a8\xe7\xa6i\xca\xcd\xa3-e\xdbb1\xf8a\x18\n\xef\x8a\xdc\
+\x80\xd44\xcd0\x0cWVV\x19c\xc5bA.u\xcaq\x90+\x07\xcb\xe52r\x14\x81\x7f\xe2{\
+\xb5UDY\xb7\xec5\xaa\xb2\x83\xa6i\xc6q\xbc\xb2\xb2J\x19\xcde\xb3Rp\x15\x9f\
+\xd5FD\x8da\x18\xe5\x95UB\xc0 \xc6\xde\xbd{\x12\x15\x9a\xed\xfdBe\x98\xf9\
+\x80\xd7\xa4\xfaoB\xd6\xd5\xbbL*\xb8:yhY\xdfq\xc4\xfe\xcd\xd8\xff\x1a\x08 \
+\xc7 \xea\x99\xf8\xa50\xfe\xbc\xf0\xa3\xf6\x019\r\xee~\x84,\x04\x00D\x08b\
+\xec:U\x10\xc3*\x8c??X\x16x\x80\x9dk\xb9K\xd4\xfb\x81\x10R*\x8dB\xfb^J\xdc9j\
+\x04\x8bx\x8buddX\xdc\x81\xd2/!oHy7v\x16\x06e\x0b\xe0\xc4\xe1\xd0V%\xd8,C\
+\xa0\x84\xcd\x08\x15\x13\x99L:\x9f\xdc\t!\x19\xcf\x13A b\xda\x90\xc6\x9d<\
+\x97\xf8`Y\xd6\x9e=cR\xf4\xa5\x1a\xaa\x16\xb7()\x971GGG\xa4\xd3I\x15>i\x81\
+\x1a\x9b\xcd\xf6\x84D&\x86:a\xe0\xcb\x19TL\x15\xf2\\\xb2N\xd9\x12\xa9\xf2\
+\xa3##\xd2}!\xcf([%\x97\x8e\xfb\x8c\xaa\xec\xa0(022,V\xb0\xe5h$\xe22e\xfb\
+\xc7J\xa3\xea\xd8\xaa\xd7\x8c\xfa\xe5\x83^\x87jU\xb0Y\xd3\xbb\x96\x07-\xe8;\
+\x13\xe41\xad\xb5\x1e\xbec\x96\xcc\xd4,\xf1\x86\x8e\xdcW\xd9A$\xa5a\xad\xa8e\
+\xca\xba+;\x00\xa4\x8b\x13\x83+;\xec\\q\'\xcaV\x8af{\xdfw\xe8\xb8g\xa4\x14\
+\xaa\xc7\n\xdb\xdch\xef\x0bC\xdaq\x11\xd0V\xdb\xfb\x16N\x98\x84\tq\x17%Ue\
+\x91\xb3\x8bT\xa2M\x9e\x90\xf6\x8bE\t\r\x15z\xd7^Dm\xe9\xb5\xd4P!XR\x7f\x13m\
+K\x9cT\x1d\x1fB\x88\x0c\xc6\x00\xc5(V\xc5N\x0e/Q6\x1a\xed\xfc\td<\x89Z\x95\
+\x94]\xf57\x92M\x923\x8d\xb4m\xe5\x9c\x94pp\xdfwT\xd53\xca1\xc1v\x80\xa3\xda\
+69\xaf\xc8\xb9M\xf6QvM\xfc(bH\x1fN\xdf\xd5\x91I|\xd0\xec2b\xffk\xe4\x14\x88\
+\x88\xdc\xed\xb9\xef]\x9f\xc4/\x12d!\xad.\x88\xcf\x1c{\x87?\xda\xe9Ta\x10\
+\xdf\xbd\xd2\x80\x07*\xbd} J8\x1a\xb4%L\xfeI \xfeW\xdaq\xbd\x0cO\xb9\xa2%\
+\xad\xf2\xfb\x16V\xdd\x05\xaa-\x0f\xdddH\xfeU\r\x04T\xeb\x94\xee\xdaD\x17\
+\xa0\xbd\xbc)C0\xa1\xc3\xd0\x96\xbai(;>\xcbo\xa0#\xc6_:1TEV]:R\xd4\xe4l\xd7u\
+]\x91(\xe1"r\x90\xe5\x9f\x12\x05d\xfb\xc5\x97\xea8\xc8\xf2\x89\xb3\x0f8e\xaa\
+\xb3\x05\xb4\x05Z\x9e]F\x07\x89^\xa8C\xa16O}h\x93~\x9e\xc4U\xa4\xd1l\x80\\lj\
+\n\x001\xedc\xb6\x1f\x1e\xe8\xad\xa5\xcaMD\xda\xae\xad\xc7;P\x00\x99\xde\xd9\
+\x1f{\xb1\xe3\xc5\x1d\xda\xaf\xb9w\x15wi$\xaa\x86\xa7e\x9a\xb6mK\x11\x97J\'m\
+j)\xf4\xbd\nw\x8a\x8e\xaa\x02R\x98\xc4!\t\x19\x92\x93\x81:\x8bt\xfa\rd\r\xaa\
+\x9c\x89\xa3\x84\x16\x93\x0e\x07\x02\xd9\xbc.\'\xf2\x1c$,VqHb~\x92\xe6\xbf*j\
+\xb2\x9e\xce\x13A\xdblGD\xc7q\x84\xc1\x9bxB\x92?Pb\xbe\x91n\x93\xc4\xef\xa5\
+\x1a\xd1j%\x89QU\x15_\x8eF\xe7\xd0\x11\xe5\x81\x83l~\xcf\xabs(d\x19YI\x9f \
+\x19\x8d\x866\x96\x91E\xc2l\xe7\xbc\xbb\xd9\xeedJv\xba\xcb\x0e\\\txT\xa5\r\
+\xf9BF\xcf\xf0G\xdb\x1bv\xb2\xa5\x07m\xe7\x8e\x14wi\xbaB\xfbN\x96\xcf\xf8\
+\xa0\x88\xbb\xf4\x81\x88$!\xf2pS\xd1\xeb\x84\xa6\xa8\xce\xdf>\x85Uq\xef\xd3H\
+\xa9\x0eRGT\xcb\x91lv1w\xedBb\x92\xc0\xcd\x8b\xb7j\r\x89\xa3\xe4\x04#\x95W=i\
+\xaf\xf9I\x15\xf4\xce\xcf\xea7\xe2_\xb1\xbe\xaa\x9eB\x95\xec\xce\xe1\x92\xe3\
+\xd0\xb5\xa7\xea)\xb0#\xcf\xc1 \xa3\xda9t\xaaUn\xb4\xa3Y\xd4\xc3\x13S\xac\
+\xfc\x93z"\x8df\x03d2H\xa6o\xbe\xc6#\x83T\x16U\xe6d\xa6\x8b\xdeid\x06\xad-\
+\xc1\x8e\x14wP\xf4]\x8a\x0bl^G%\x84\x88/\xc5\x83\xb6(66V"\x84x\x9e\xa7n]F\
+\xda.\xef\x84\xb2\xf7),o\xfb\xfe\xe2.?c{!4\xa1\xb3\tYI<\x07$\xca\xa8\xf5\xa8\
+g!\x9b\xc5]=\xbb\xec\x9a@m\x7f\xa2y\x89\xa3p\xb3#\xab\xab\xd2\x19\xedu\x02\
+\xdc\x8c<D\x9eH \xebT\xdb\x03\x1d\x13\xc9 \xbf{\xe7\xa8\x822\x1f\x90\xcd@oq\
+\xef\xfa+$\xc6\xbf\x7f{4O\x1aq\xed\xb6\x88mg\x0cy\xaf|\x8d\x83\xed\xb5\xc4\
+\x9a+b\xf3k\x00\xa0}\xde\x81\xca\xef\xb3\xdc\xdcC4uG\x86BJTk1qgB{-TMik*y<T\
+\x8d#\x84\x88\xe0\xbf\x01\x0b?tk\x13\xba,\xff\x95\xdf\xab\xe2\xde\xf5\\\xb2\
+\xb3\xea\x9f:\x8b\x11%`\xa6sp\x1e\xae\x0b\x89\xa3\xd4\xbe$\xda\x9f8Qb\x02\
+\xeb,\xf6\xd0Mz\xa0\n\x1ft(\xb4\xb2k\x12 \x8f\x83\xbb\x1f#\x8b\x00 \x88xW\
+\xed$\xa6]\x1c\xff\xee\xfdw\xe4@\xde\xbc\xfb1\xd2\x06\xf4\x0f\x7f4\x9d\xe2\
+\xc4\xf3\x86y\xff\xfd=:\xd9\xa9\x96\xbb`\x90\xfb\x93\xb4\x1d\x11\x88(\xd7\
+\x12\xa5\x7f9Qr\xc0\xc2\xdf\xa4\xb5\x0f\xfa\xa7oX\xf2\xd1)T\xff\xd9e\xcb\xda\
+\xf3(\x86N\xa3\xe9\n\xad-\no;e=\xad\xe2\xf4`{-\xd1\xdam\xa1\xec\xd07\xfc\xd1\
+\x1b:\xf8p\xca\x0e;]\xdc\xef\x8b\x14k\xf9\nL\x9f\xb5\xb2\x07*\xac\xd1h\x9e(\
+\x90\x85\xad\x8d\xf4\x10h\x0f\xff\xb8a\xa5R\x03\xed\xb5\x84q{O\xbe>\xe1\x8f\
+\xa6\x93q\xf3\xfb\x1f\xae\xb5\xb0\xeb\xc5]\xae\xb9\xa9\xff\xdbk\xb9\xec\x81\
+\nk4\x9a\'\x8a\xb8\xfa5r&\x82dz\x9a\xed\x83%\x1b`\xcdUd\xad\xad\xcd\xfa\x85?\
+\x8eL\x0e\xf2\x0eT/v\xb3\xb8\x0bQ6\xdb\xa9\x9d\xb0\x1dJ\xd8\xcbl\x1f\xbc\xb0\
+F\xa3y\xa2\xe0\xb4!c\x16{\xc5\xb6\x9bn6\x95\xdb{\xff\xba\x90\xcb\x1ca\x8c\
+\xf7\\\x95\xb53\xa3\xb67\xdc\xed/\x83\xb2\x9b\xc5\x1d\x94\x98\r5\x1a\xaf\x97\
+%\xfe@\x855\x1a\xcd\x93C\xec\xdf\x12\x1b\xe91\xd63\xb6}\xc0\xb7\x96h\xed6\
+\x97\xdev\xda\xa3\x1012}7\xbf\x1e\x84\xdd,\xeer\x05\xaf3\xc4\xf0\x1b\x16\xd6\
+h4O\x0e<\xf2[\x99\xd6{\xef\xc8a{\xc3\x03%\x1b\xe0q\\\xbd%>\x8b\xf0\xc7\xae\
+\xa4\x0b\xe3\x83\x04S\xf6g7\x8b;t\x0b\xd9\x86\xdez\xfd@\x855\x1a\xcd\x13B\
+\xec\x7f-R\xfaR\x86\xbd^"\xf5\x86\x0e\x0fV\xd5M\xe4\x14\x00\x10\x81\xf6\xd8.\
+\xc1\xb0\xdc\xf4\xd0@;7\xf5g\x97\x8b;<\xa0:k)\xd7h4*,\xb8\xc7\x825\x10rL\xbb\
+;d\xdc\xdc\x9eA\xd2\xac\xf3\xb8N\xeb\xad-\x07\xfa\xbc\xb5\x94\x1e\x1ah\x17\
+\xbd\xfb\xb2#\xb7\xd9\xd3h4\x9a\xad\xa1\xe5E!\xad=4:!\x86\x99\x1e\xd0l\xaf\
+\xdc\x00\x14oS\xf7\xdbEo\xa0U\xd9\x01\xd0\xe2\xae\xd1h4\xdda\xcd2\x0f}\xb9\
+\xffu\x0f\xb3}\x9fi\xa7\xef_Up\x8f\x05\xab\xad\xcf\xbd\x82)\t\xc9\x8cL\x0e\
+\xb2*;\x08Z\xdc5\x1a\x8d\xa6+(\xf7\xbf\xee\xb5\xf8ILg0\xff8\xc6~{\x8b\xd4\
+\xde\xdev\'S\xb2R\x85\x87ii7\xb4\xb8k4\x1aM\x17h\xfd\x0e\x8f\xebb\xff\xeb^f{\
+\xba8>Hz\x00Z\xbf\xc3\xa3j\xebsO\xf7\x8e\x95\x19\x99\xfc&\rN\xa0\xc5]\xa3\
+\xd1h\x92 \x8f\xe3j\xcbl\x8f)t\xf5\xa2\x98N&\x95\xbf\x7f\xb2\x01dQ\xec\xdf\
+\x14\x9f\xfb$\x1bH\x0f\x1d2,\xf7!\x9b\xdb\r-\xee\x1a\x8dF\x93$\xf6o"\r\xc5:j\
+\xf7\x8d\xf4\x08\xc9\x96N\x0c\xb2;RT\xb9.\x12IB;\xd9@\'v\xba\x98.N|\xa3\x16w\
+\xa0\xc5]\xa3\xd1h6\xc1i\x83\xd6\xef\x02\xb4\xdeZ\xea*\xc7\x8e7:P\xf8cXa\x8d\
+e\x00 \x00\x94\xf5\x08\x92!\xc4\xfbV\x1d2\x02-\xee\x1a\x8dF\xb3\x89\xd8\xbf%\
+b\x16i\xaf\xdc/\x83\xee\x8e\x84Q;\x8d\x0c\x87\x9e\xf16\xa9\xdcCn\xc7\xd1\x1f\
+-\xee\x1a\x8dF\xb3\xc1F\xb2\x01\xe8\x99\xda7\x95\xdf?\xd0^K\x8d2\x8f* \xccv\
+\n\xbd\xd6Q\x07\x0c\x93\x7fP\xb4\xb8k4\x1a\xcd\x06\xb1\x7fK\xac\x9f\xf6\xcc\
+\xfdBHj\x904\xeb\xc8\xa2v\xf8#\xef\x1d&\x9f\x1e:4\xc8\xe6\x1e\x0f\x81\x16w\
+\x8dF\xa3i\xc1\xc2u\x16\xdc\x03\x00\xec\xedE\xb1\xd3C\x83\x98\xedqm\x11i;i;\
+\x83\xae\xd3\x84\xe9x\x83m\xee\xf10hq\xd7h4\x9a\x162\xd3z\x1f\xb3}\x10o;\xb2\
+\x90V\xe7\x01\x80\x00p\x0e\xdd\xe3m\x00\xbc\xe1o\xb4\x1dG\x7f\xb4\xb8k4\x1a\
+\r\x00\x00m,\x8bW\x8d\xc4[K]\xcb\xb8\xd9=\x83,~\xc6\xfe\xadV\xf6G\x80\xb8W\
+\xbcMf\xd4\xc9\x8c|\x93\x06\xf7G\x8b\xbbF\xa3\xd1\x00\x00\n[\x1b\x00(\x83\
+\xae;V\x0f\x98#\x8c\xc75\xda\xb8\x03b\x1d\x95\xc37\x8b\xb7yxv\x7f\xca_\x8dF\
+\xa3\xb9/\xadd\x03\x00\xbcw\xb2\x017\xbf\xdf\xb4S\xfd*\xa1\x8cR\xc6\xfd\x9b\
+\x049\x00\xc1>\xf16\x85\x03\xa6\x93\xf9\x16\xda\xdd\x1b-\xee\x1a\x8d\xe6\x89\
+\x07\x99L6@Y\xf7-R\x89\xe9x\xc5\x9e9\xc2\x18\xa5a\x10\x85\x94\x90x\xc5\x89V\
+\x11\x08\x01\x88\x19tO\xdaN\xc8 y\x0b\xbe!\xda-\xa3\xd1h\x9et\xe2\xda"\xd2\
+\x10\x008\xf6H6\x00\x90.N\x10\xd3\xeez8g|\xbd\\\x0e\xc2\xa6\x97F\x0f\xee\x02\
+r\xd27\xfc\xd1\xc9\x94\xfa?\x01|+hq\xd7h4O4\xc8)\xad-\x8a\xcf1\xed\x1e\xb3h\
+\xd8\xa9>\xb1\xed\x8dZm\xad\xbc\xcc\x03j\xc6\xf7h\xe0\x031@\x84?\xf6\xd8\xdc\
+\xc3\xfb\xc6\x9b_\x0f\x82v\xcbh4\x9a\'\x9a\xb8:\x8f,\x06\x00\xc6\x91\xf1\xee\
+\xb6vfx\xb2O\x8e0\x04,\x0c\x8d"\r\xc2\xf5yB\x0c\x02\xc0z\x87?\xa6\n\x13[`\
+\xb6\x83\xb6\xdc5\x1a\xcd\x93\x0c\xb2\x90\xd6\x97\xa0\x1d\xb3\xd8\xb5\x8c\
+\x95\xca;\xd9R\x9fJ,\xd3J\xe5G\\\xabF0\x14\xeb\xa8\xbd\xc2\x1f\r;\xf5\xadg\
+\x7f\xec\xd9\xaa\xad9\x8dF\xa3\xd1lCb\xff\x16p\xda\xda"\xb5\xc7\xbe\xa6\xfdc\
+\x16\x9b\xb5z\x18!\xd0\x8a\x83k\x00&\x01\xa0\x0c{m\x91\xda\xff\t\xe0\xdbE\
+\x8b\xbbF\xa3yB\xe1\xb4A\x1b\xcb \xb6\xbe\xeb\x11\xb3\xe8dJvz\xa8W\ra\x106\
+\x1a!\x82\xe1\xf2\x15\x821\x82\xc9\x11\xe2\x1e\xdbq\xd8\xe9\xa1\xfeO\x00\xdf\
+.Z\xdc5\x1a\xcd\x13J\\\xb9\t\xc8\x81\x00\xa3\xd8+f1=t\xa8\xfb\xc1\x08\xf5Z=\
+\x8eb\x0e\xa6c4\xcd\xa8\x8c`\x10\x021E\xc68\x11\xeb\xb2\x84ld\x17 \xc4\x1b\
+\xd9\x8auT\x89\x16w\x8dF\xf3$\xc2#\x9f\x05\xab\xd0\xda\xb1\xbaG\xb2\x81\xdc>\
+\xcb\xcdv\xf9\x03B\xbdV\x0f\x9a!1m\xd3`V\xb4H\x10\x91\x18\xe2\x05(;]\xb0\xd3\
+E\x03x\x1cV\xe3\xc0\x17:\xef\xe6\xf6>\x8a\xa4\xed}\xd0\xe2\xae\xd1h\x9eD"\
+\xff\xa6H2@{\x98\xed\xc4\xb0\xbc\x1ef{\xb5\xe2\x87alX6g\xd4!\x15\xc2*HL\x02P\
+\x0f\xb8\x9b?\x98\xdf3\t@\x00\xc34\xd0\xc6\xca\xd7\xcd\xca\x1db9\xde\xd0\xa3\
+M6\xd0\x89\x8e\x96\xd1h4O\x1c,\xb8\xc7\x83u \xfd\xcc\xf6Ta\xbc\xeb\x8e\xd5\
+\xcdz\xa3\x19\xc4\xa6e3Fm\xdb\xb0\xe3;@\x08\x01\x88(\xc6\xcc\x05\xf46f\n\x02\
+\xae7\x04h{C\x07\x1fQ\xd2\xf6>h\xcb]\xa3\xd1<i`\xec\xdf\x14\x9f(\xc5\xee9\
+\xc2,7]\x1c\xef<\xb2\xeeW\x83 \xb2,\x93R\xea\xb8i+\xba\x05\xbcI\xc0\x00\x80z\
+\x88\x86\xe1\xd4k\xcdt\x14G\x0c\xc2f\x0c\x18\x130Ccd$\xdf\xa5\xaaG\x8d\xb6\
+\xdc5\x1a\xcd\x93\x05m\x94yT\x05\x02\x9c\xf74\xdb\xbd\xe2Ab$m\xdfZ\xa5\xda\
+\x0cbbX\x9c\xa3m;\x16\xfa$^A @ \xa0\x10\xc6\x9c#\xb5\\\xbb\xdeh\x86A\xc0\x91\
+Sn0p\xd3#\x07\x82f\xf0\xe8\xbb\x95D[\xee\x1a\x8d\xe6\xc9B&\x1b\xa0\x0c\xbb\
+\xe6\x083\x9d\x8c\x9b\xdf\x97\xf8\xb2z\xaf\x12\xc6\xdc4\rJ\xb9\xe3\x18H\xc3\
+\xb8\xb9\xe8\x1a1\x80\x81\x08\xf5&\xb3,\xdb\xf6F\xc2\x88\x05\xf7\xd6\x00\x11\
+\x00\t\x10b\x18\x84\x185\x1a\x8f\x1f=BH\xd7\x08\xc9G\x85\x16w\x8dF\xf3\x04\
+\xc1\xa3*\x8fkp\x9f\r\x92\x8e$6H\xf2\xefU\xc2\x88\xdb\x96\xc1\x81\xd86\xf08\
+\n\x9b\xf7rF\x15\xc0\x00\x02\x8d\x80s0\x0b\xa3\xc7\x9auZ\xaf\xdd3\x0c\x13\
+\x08\x80\xf0\xe8\x03\xb4vd\x8db\xdb\xddR\xb7\xbb\x16w\x8dF\xf3\x04\x11W\xe7\
+\x11\x91\x00\xc4\xb4\xbb\xd9n\xa5\nNfT\xfe/r\xee\xaf\xfb1#\xa6\x811\x07\xc7\
+\x02\x03Y\xc4\xa9\r\x15b\x00\x00\xa1\x8c\x85\xd4\xcc\x8fLF1A\xd3v\xd2i\xce\
+\xb88\xb4]\x05\x98\x96i\xda[-\xb6Z\xdc5\x1a\xcd\x93\x02m\x96is\x85\x00\xd0^9\
+\xc2\x0c33zl\xa3|\x1c\xd7\xab\x8d\x98"\x00G\xc3t-B\x83&\x05\xb0\x1d\x84\xa8A\
+\x00\x109%y\xaf\xb87\x88\x80q\xe6\xa6\xbc\x94k\xd5\x1au\xc68p\xde\xaa\xd20FJ\
+%\xc3\xd8\xea\x05N-\xee\x1a\x8d\xe6\xc9\x00Y\\\xb9\t\x00\xfd6H\xca\xef\x97o-\
+EAX\xad\xd4\x88i\x13\x02\x86\xe9\xd8&\x0f\x1b5\xd3v\xf3\xc3\xc5\xb5\xf9\x8f3\
+\x16\x02"\xd8#\x86\xb1\xa7\x11r\xc6b\xd7\xcb;\x8e\x15\xd6\xd7\x81\xc5\xc4 N*\
+M\x00\x1c\xc7V\xd5#\x86\x00\x00\x1c[IDAT\xcd\xe6s\x8e\xb3\xd5q\x90\xa0\xc5]\
+\xa3\xd1<!\xc4\xf5;\x9c6\t@\xccz\xbc\xb5d\xb9r\xaf\xa5\xa0\xd1l6C$\x0495,\
+\xc7"4\xac7\xacT*?4\xd4\xac\xad\x19\xacA,\xc0\xd4\xbe\x88\x17\x83\x089\x8b\
+\xbd\xdc\x90\x014\xaa\xfb\xeb\xabw\x0c\xf4\x87F\xc6\x8a{\x0e\x13\xc3\xd8\xe2\
+ET\x15\x1d\n\xa9\xd1hv?\xc8i\\]\x00\x00\x04`=\xc3\x1f[{-\x05\x8df\xad\x16 \
+\x07D0L\xdb1Y\xb3^\xb3\xdcTax\x18\x00\xd6\xee|e[\xc8\xdd\xf1\x08F\x1b!c\x8cz\
+\xb9a\xe0\x11\x0b\x1ak\xe5\xafM\xa8f\xd2fa\xec\x88a\x9a\x8fQ\xd9A[\xee\x1a\
+\x8d\xe6I\x80\xd6\x16\x90\x85\xc2\xdb\xde\xd5l7\xed\x94\x9b\xdf\x0f\x00\xcdz\
+\xb3^k\x1a\xa6\xc1\x18\xb7,\xcb"\xb4Q\xad\xa7</?<\x0c\x00\xeb+\x0b\x166\x8d\
+\xf4\xc1\x08\x8a\xf5F\xd30\xedt:\x8b,\xc0\xa8\xb9\xba\xbah\x19\xcd\x8c\x8b\
+\xb9\x91q\xd3\xf6\xb6\xbc\x8bI\xb4\xb8k4\x9a]\x0e\xf28\xae\xdd\x86\xbe\xde\
+\xf6\xf4\xf0QB\x8cz\xb5\x1e\x04\xb1a\x10DbY\x96cA\xa3\xd6He2\xf9\xa1!\x00`4\
+\xf2Wn\xe6\xf2\xfb\x989\xd2\xacU,7g[\x16`D\x83\xba\xbf~\xdb"\xcd\\\x1a,\'\
+\x9d\xee\xbd\x8f\xf6V\xa2\xc5]\xa3\xd1\xecrb\xffk\xe4\x14\x00X\x0fo\xbb\x9d\
+\xca\xbb\xd9\xb1Z\xa5\x16\x84\x94\x00\xe3\xc42\x08\xd8&\xd6|\xdf\xcb\xe5r\
+\xc5\xa2(\xb6v\xf7\xa6\x97\xdbc\xba#\x8dF\xd5J\x0f\x01\xa7&\xc1ze\xad^]6I\
+\xb3\xe0\x19\x00\xe0\r\x1d\xe9|\xb5\xf5\xb1\xb0-\x1a\xa1\xd1h4\x8f\x08N\x9bq\
+}\t\x00\x01I\xf7d\x03\x04R\xc3S\xb5J\xad\xd1\x8cL\x03\xc1\xb0\x0c\xe4&\x81f\
+\xa3\x9e-\x163\xb9V\x9e\xde0\xa8\x1b\xc4\xb2\xd2CQ\xb3j:ydQ\xca\xb5\xabkw\
+\x9b\xf5\x15\xcbh\x16<\x13\x11\xacT\xc1\xcd\xed\xd9\xd2\xee\xf5F\x8b\xbbF\
+\xa3\xd9\xcd\xc4\xfe-\xe0\x0cH/o;\xb73\xfb\xc3\x00\xc3\x98\x99&\x01b\x19\x84\
+\x13\xc0(hd\x8b\xc3\xe9L\xcbu\x8e\x88A\xbda\xa7\x8aA\xc3\'N\x11\x90\xa6\x1c\
+\xab\xb2r;l\xae\xd9f\x98K\x9b\x88\x8fa;\x8e\xfehq\xd7h4\xbb\x16\x1e\xd7h\xa3\
+\x0c=w\xe4@ \x167\x8a\x9c\x1b\x84Pb\x98\x06p\xe0,\n\xea\x85\xd117\x9dj\x15\
+\xe2<\nB\xceH\x10\x05\xb6[\xe4\x8c\xba6\xb9W^\x8c\x82\xd5\x94C=\xd7\x10Y\x06\
+\xdc\xec\x98\x9d*le\xef\xfa\xa3\xc5]\xa3\xd1\xecZ\xa2\xca-\x00\x04BX\xd7\xd4\
+\xbe\xc8\xd1)q\x92A\xce\x0c\xc34\x0c\xe44\xa6Q0\xbcw\x9fe\xdb\xa2\x08\xe7\
+\xbcYk4\x1b\xd5\x98\x12\xcbN!gi\xd7\xaa\xac,\x06\xcdr6\x8d)\xcbhm\xa8gX\xde\
+\xf062\xdbA\x8b\xbbF\xa3\xd9\xad\xb0p\xbd\xbd\x91\x1ev5\xdb\xd1p\xad\xf4>\
+\x06H\x081M`QH\xe3hx\xef~\xd34[5P\x1a\xd4\x9bA\xb3\xc6\xd02-\xd30M\xcb\xe0\
+\xeb+K\xcdF\xb9\xe0\xa1mnL\x16\xe9\xe2D\xd7\x9d=\x1e#\xfa%&\x8dF\xb3;\x89\
+\xfd[\x00@\x00(\x83n9\xc2\xd0L\xef\xa1\xe0\x00g\xb6m\xb0\xa8I\xe3\xa8\xb4\
+\xff\xc0\x86\xb2\xc74\xa8\x07\x9c\xc7\x8c\xb8\x88\xc4vl\x03i}\xedN\xb3~7\x97\
+\xe2\xaa\xb2\x1bv:U\x9c\xd8\xaan\r\x8a\xb6\xdc5\x1a\xcd.\x845WXX\x01\x00\x8e\
+]S\xfb"\x92\x147G\x80S\xc7\xb5iP\xa3\x94\x95\x0el\xec\x97\xc4)\x0b\x1aM\x1a\
+\xc7\x0cL\xce\xa2t:M\xc3zP]\xa9\xd5\xcby\x0f\x1dsS}\x99\x91\xa9D\x8a\xe0\xed\
+\x80\x16w\x8dF\xb3\xfb\xc0\xc8\xffZ|\xea\xba#\x07"\x92\xf4^0L\xd72\xe3\xa6\
+\xcf\x91\x94\x0e\x1c\x90\x7f\xa5Q\x1c4\x03\x1aS\x0ef\x14\xc7^:\x1d\x06\xb5\
+\xb0\xba\\o\xac\x16<\xb07+\xbb\x9d\x1er2#\x8f\xbcC\x0f\x8e\x16w\x8dF\xb3\xdb\
+\xa0\xf5\xbb<\xae\x11\x00\xd6\xcdlG\xe4\xc4\xce\x11g\xc82\x8d\xa8\xb1n\xd8\
+\xeeh\xa9$\xff\x1a\x07a\x14EqL9\x1a\x94\xd1\x94\xeb\x84\r?\xa8.G\xf1z\xd1\
+\x03k\xb3\xb2\x03!\xde\xc8\xe4Vt\xe9\xc1\xd1\xe2\xae\xd1hv\x17\xc8\xe3\xea<\
+\xb4\x93\rt\xf1\xb6\x13B\xdc=\xa6a\xc6\xcd\x8a\xe9\xa4\x8a\xa3\x1b[s\xc4aX\
+\xaf\xd5\x90\x98\x0c\t"O\xbbv\xdc\xac\xd5\xfd;\x8c\xfa\xd9\x14\xda&\xc1\xcd\
+\xeb\xb2j\x8a\xe0\xed\x86\x16w\x8dF\xb3\xab\x88k\xb7Ej_\xd6}G\x0en\xd8E\xd3.\
+\xd0\xa6o{\xad\xa41\x82f\xad\x11\x05\x01C\x93sn\x10\xc34I\xdc\xac\xae\xaf-!\
+\xaf\xe5\xd2]\x94\x9d\x98vz\xe8\xd0\xa3\xef\xd0C\xb2\xed\x16\x014\x1a\x8d\
+\xe6\xa1A\x1e\xc7\xb5Vj\xdf\x98v)\xc0\x91\x10\xb7\x145\xabn6\xbf\xa1\xec\x08\
+Q\x10FA@\xd1\xa0\x94Y\xa6E\x80\xf3\xa0\xbaZ^\x08\xa2Z6\x05\x9d\xca\x0e\x00\
+\xde\xd0a\xc3|\x0c\xbbp\x0c\x88\xb6\xdc5\x1a\xcd\xee!\xae\x8a\xd4\xbe\x842\
+\xe4\x984\xdb9"\x05\x8fP\x92)\x16\xbdl\xdb\x9d\x82\x184\x82 \x088\x98\x94R\
+\xc7\xb1\x91S\x1eT\xab\xfeR#l\x0eg\x89cA\xa7\xb2\x9bn\xd6\xcd\xef\xdb\x82\
+\x1e=4Z\xdc5\x1a\xcd.\x01YD\xebK\xd0\xf5\xad%\x02\x9cC3B;\x95\xcd\x0e\x8f\
+\xa4\xd2\xe9\xd6!\x08a\xbd\x11\x84!e\x06e\xd4u,\xceb\x0c\xabq\xb0T\x0f\x82\
+\xb4C<\xb7\x8b\xb2\x03@f\xf8\xe86\x0c\x7fT\xd1\xe2\xae\xd1hv\tq\xf5kd1\x10\
+\xa0\x1c6\xe5\x08#\xc08\x04\x11"Ie\x87\xc77\x94\x9dcPo\x84a\xc8\xd0`\x9c\xbb\
+\x8e\x85\x9c\xf1\xb0\xca\x82\xbb\xc8\x03\xdb"\xd9T\x97\xfc\xc0\x00\xe0dJ\xb6\
+7\xbc%}zx\xb4\xb8k4\x9a\xdd\x00\xa7\x8d\xb8~\x07HG\x8e0\x02\x8cA\x10#"\x8c\
+\x8e\x9f\xcc\xe4[\xb9\xbd8\xe7a\xbd\x1e\x841\xe3\xc09\xb7\x0c@F\tk\x1aP\x05\
+\xd2\x889\xb1Mp\xcc\xee\xe7J\x0fm\x8b\xed8\xfa\xb3\xad\x1f+4\x1a\x8df@b\xff\
+\x16 \x03\x00\xc6`#G\x18\x01\xca\xa0\x19#"x\xb9\x91L\xbe\xb5\x82\xca\x19kT\
+\xfc d\x94\x13\x04\xc34\x81 5 NeR<\xae\xd8\x96\xc188&\x98\xdd\x04\xd2J\x15,g\
+\x9b\x86?\xaahq\xd7h4;\x1e\x1eUic\x05\x80 \x02\xe5\x1bf{L!\x88\x11\x10\x88A\
+\n\xa5#\xad\xc2\x8c\x07\xb5Z\xccIL9\x01b\x10N8#\xc0\xf2\xc3\xc3Qc9e1D`\x1cRN\
+7o\xbbH\xda\xfeXw\xbe\x1e\x10-\xee\x1a\x8df\xc7\x13\xf9\xb7\x00\x10D\x8e\xb0\
+\xb6\xd9\x1e1\x08)\x8a\x9ca^n\xccI\xe7\x00\x00\x19k\xd6\xeaA\xc4\xa3\x98Z\
+\xa6I\x80\x11\x16\x03\xf0\xc2h\x89\xc6M\x08\xcb\x96I\x9a\x11\xa4\xec\xee\x02\
+\xeef\xf7l\xab\xa4\xed}\xd0>w\x8dF\xb3\xb3a\xc1*\x0b\xd6\x00\x00\x11e\xb2\
+\x81\x88B$\xf6\xc2&`\x18Vq\xec(\x000J\x9b\xd5Z\xcc fh\x99&\x00\'<\xb6m\x92\
+\x1d\x1a\x05B\x82\xb5/\x1d\x8b\x88\xbff\\1Yl\x82\x98\x8e7|dK\xfb\xf6\r\xd0\
+\xe2\xae\xd1hv4\x18\xad_\x17\x9f\xc4[K\x08\x10D\xc0d\xb8\x0cBq\xcf\xa4\xe5\
+\xa4X\x14\xd5k\x8d\x90\x02g\xccu,\xc6\xe2\xb8Q\x1b*\r9\x99\x02\x00D\xeb\xd7-\
+h\x02@#\xc4\x94\xdd=H&[:\xbe\xdd\x92\xb6\xf7A\x8b\xbbF\xa3\xd9\x91\xd0\x98\
+\x861B\xb8L\xe2:!&e\xc89r\x84 \x06\xae\x04B\xda\xae\x97\x1b\xda\xcf\x85\xb2\
+\xc7\xc89\xb7m\x93\xb1\x98\xc4\x8d\xe1=#v:\x07\x00\x9c6X}\t\x00\x18\x07\x93\
+\x80\xd1M\xda\xed\xf4\x90\x93\x19\xed\xf2\x87\xed\x8a\x16w\x8dF\xb3\xc3@D\
+\x7f\xbdB\xc1\xb2\rn4\xe6\x81\x18\x00\xc0Z\xca\x8e\x9c\xcbTa\x049\x1b\xdd\
+\x7f\x92\x85A\xbd\x1e\x8412\xce\x1c\xcbD\xce n\x14K\xa3\x86\xd3\xda\xff\x9a\
+\xfa\xb7\x109!"\x86\xb2\xdb;K\xdbl\xf3\xebA\xd0\x0b\xaa\x1a\x8df\x87\xb1\xbe\
+\xb2\xba^^q-#M\xd6\x08o\x12 \x8caL\xa1\x19\xa9\xca\x0e\xc8Y\xb6\xb0\x87\x00\
+\xf1\xab\xcd\x90\x02\xe3\xdc\xb5-DNX\xb3X*Ieg\xe1\xba\xdcD\x1b\xb1\xfb:j*\
+\xbf\xdfrs[\xd4\xbdo\t-\xee\x1a\x8df\x87\xd1\xa8\xd7\x81r\x0ck\xb4~\x07\xc0@\
+\x800F\xf1\x9a\x92\xa2\xechZ\xb6\x97\x1b\xab\x07@\x19 \xe7\xaempNYP-\x8e\x8e\
+\x9aN\xba]\x19\xc6\x95\x1b\x00@\x08p\xec\x9aEr\xbbg\x7f\xec\x85\x16w\x8dF\
+\xb3\xc3\xf0\xf2y\xb0\xac\xa8\xfa5\xb2\x80\x18$\x8c\xb1\x11%\x94\x9d[\x96946\
+\x1517\x8e)\x008\xb6\xc1h\x8cQshl\xcc\xb0S\xb2*\xdaX\xe6Q\x15\x00\x10!\xa6\
+\xdd\x1c2\x00\xde\xd0\xa1\xed\x9c\xfd\xb1\x17\xda\xe7\xae\xd1hv\x18\x85B\x9e\
+6\xd6\x1d^\x070\x18\x87z\x80\xaa;\x059\xb7m7?:\x19q\x8fRf\x18\xc42\t\x8dBd\
+\xe1\xc8\x9eM\xca\x8e\x9c\xc6\xfe-\x00$\x84\xc4\x0c\x19\xef\x12$c:\x197\xbf\
+\x7f\x8b:\xf6\xad\xa2\xc5]\xa3\xd1\xec0\x0c\xd3\xcc\xa5C\xd6\x04 \xd0\x0c8\
+\xe3\xa8*\xbb\xe3z\x85\xd1\xc9Fl\x03\xa0i\x1a\x84 \x8f#\xe4\xf1\xc8\xde=\x89\
+@FZ[@\xda\x04\x91\n\xb8\x87\xd9\x9e\x19\x99\xdc\xe6\xd9\x1f{\xa1\xc5]\xa3\
+\xd1\xec0xTe\xcd2\x01B)\x06\xd1&eO\xa5s\xb9\xe1\xa3Alr\xc6m\xdb2\x0c\xa4A`\
+\x99\x98/\x8d\x19\xd6&\xd7\n\xb20\xae-\x8a\xcf\x94m\xce"\xd9\xc6\xce\x8cn\
+\xff\xec\x8f\xbd\xd0\xe2\xae\xd1hv\x18Q\xe5& \x07\x02\x8d\x109\xe7\x00@\x88\
+\x81\xc8S^\xc1+\x1cjD&\xe7\xdc2\r\x00F\x9b\x81iA\xa14F\x8c\xa4\xd6E\x95\x1b\
+\xc0)\x00 Bw\xb3\x9d\x18\x99\x9d\xf3>j\'Z\xdc5\x1a\xcdN\x82\x05\xf7X\xb0\x06\
+\x04"\x8a\x11\x05\'\x95\x05\x00\x1a7=o$\x9d?\xd8\x08\t"s\x1c\x1b\x90\xd3\xa0\
+\xe1\xa6\x9c\xdc\xf0(1\x92~\x15\x1eUYsY|\x8e\x19t\xd9D\x1b ]\x187\x9d\xcc#\
+\xef\xcf#C\x8b\xbbF\xa3\xd9I\xd0\xfa\x1d\x00\x00D\x8a\xa9\xb1CO\xb9^\x9e\xc7\
+\xcd\xa8Y\x8fb\xa3\x11\x12\x02\xdcqm\xe4\x8c6\x1b\xe9l*S\x1c!\xdd\x02\xd7\
+\xa3\xca\x9cH\xf9\xc89\xc8t4*\xc4r\xd3\xc5\x89G\xdd\x97G\xca\x8e\\(\xd0h4O&H\
+\x03\x1a\xdc\x03\x021#1+\x9aN\x9e\x10\x03\xc1\xa4<\x15R\x13\x80\x9b\x06"gq\
+\xb3\xe6\xe5\xbc\xecPweg\xcd\x15\x1eV\x84\xb1\x1e\xb3\xae\x9b\xe8\x817t\x98\
+\x98\xf6#\xee\xcd\xa3E[\xee\x1a\x8df\xc7\x10\xf9_\x03\x8f\x110\x88\xcc\x98\
+\x13\xce(7\x9d8F\x86\xc4\xb2\x008"\x10Z\xaff\x87\x8b^\xaegb\xde\xb8:\x0f\xd0\
+\xda\xc7\xa3k\xf8\xa3\x95\xca\xa7r{\x1fa7\xb6\x04-\xee\x1a\x8dfg\xc0\xe3\x1a\
+m\xdc\x05\x02Q\x04!\xe5\x84\xf08\x8a#F\xa2\x80\x03\x12\x8b@\x14\xc7\x04\xe3\
+\xa1=#v\xaa\xe7NI<\xaa\xf2\xb8\x06}\xd6Q\x01\xbc\xe1\x9d\xb1\x1dG\x7f\xb4\
+\xb8k4\x9a\x9dA\xe4\x7f\r\xc8\x11\xa0\x11!rpr\xe9\x88R\xa4\x8c!\'\x00\xdc0\r\
+\x03\x8a\xc3\xa3\xa6\xeb\xf5\xab\xa4rCl\xe7A)v\r\x7fts{\xect\xf1\xd1\xf5b\
+\xcb\xd0\xe2\xae\xd1hv\x00,\xf4Yc\x05\x08\x84\x11R\x86\x1c\x9cz\xad\x19\x861\
+\x00\x18\x84\x00\x001L@V\x1c\x1d\xe9WIs\x85G\xeb]6\xd1nC\x0c+=\xb4\x83\xc3\
+\x1fU\xb4\xb8k4\x9a\x1d@\xec\xdfB@\x82@\x19\xa4l\x88\xd1\r(\xa1q\x0c@\x00\
+\x10\x10\x10\x00\x11iP7\xed\x1ey`\x90E\xfe\r\x00 \x00\x11E\xecf\xb6\xa7\x8a\
+\x13\xa6\x92\x9f`G\xa3\xc5]\xa3\xd1lwhs\x95\x06kDl\xa6a\x80\x01\xc4$\x8c\xdb\
+.\xa3L(;\x00"\x12\xcb"\x96\x9b\xeeYI}I$\x1b`=\xc2\x1f\r;\x9d.\x1cx\x84\xdd\
+\xd8Z\xb4\xb8k4\x9am\x0e\xc6\xfe\xd7\x00\x80\x00\x8c#\x00 \x10\x03\x1b\xb9T\
+\x8dz\xc3a\xc89g\x00`\x1adt\xa4`:\xdd\xedndQ+H\x06 \xa6\xd8\xf5\xad%o\xf8H\
+\xe7\x8b\xac;\x97\xdd\xd3\x13\x8dF\xb3+\xa1\x8de\x16\xf9\xc2l\x97\x91\x8b\
+\x08\x04\x82\x157\x15:n\xc6\xf6\xf6\x98\x96e;)\xd3\xea\x99\x987\xae\xce#\x8f\
+\x01\x00\x11x\xb7\xa4\xedV\xba\xe8f\xc7\x1eY\'\x1e\x03Z\xdc5\x1a\xcd\xf6\x05\
+\x91Em\xb3]]\x02%\x00\x00$nVM;\xce\xec}\xaa\x9b!.\xab`\x18Wh\xed\xb6(\xd2\
+\xddl\'$32\xf9\xed\xb7\xfe\xb1\xa2\xc5]\xa3\xd1l_h\xfd.\x8f\x1b\x04\x80\xf2.\
+\x89\x1bIkk\xd3n\xca\xce\x03\xa05\xa4\xd5\xa8Y\xe1Q]\xec\x8c\xca9v\xf5\xb6\
+\xbb\xb9};n\x17\xbd\xfb\xa2\xc5]\xa3\xd1lS\x90\xd3\xc8\x9f\x07\xe1m\xef\x16\
+\xb9h\xa6\nnv\x8fr\x00\x03\xde\x04Ze\x91\x1f6\xabA3\nCJ\x19\x1f\xca\x12\xc3 \
+\x80\xdd\xcdvb\xda\xde\xf0\xe1G\xd9\x8f\xc7\x83\x16w\x8dF\xb3M\x89k\x8b\xc8\
+\x02\xd2+\xdf:!\xb9\xd21\x00\x00\x1e\x02\xafcT\x89\xc3Z\xd0\xa8\x87!\x8d"F\
+\x19G\x04\x04\xb0Mb\x18\x04\x00(\xef\xbe\xd7R\xbaxp\'\xee\xa2w_\xb4\xb8k4\
+\x9a\xed\x08\xb28\xae\xde\x06\x91\'\x80w1\xdb\x9dt\x96E~X\x9d\x07V\x0f\x9aQ\
+\x18Q\x1as\x11N\x03\x04\x08\x80a@\xcc\xc0\xb2\x08\x81\xd6[K\xddv\xd1\xf3R\
+\xbb(\xfcQE\x8b\xbbF\xa3\xd9\x8eD\x95\x1b\xc8#\x00\x88y\xc7\x0bG\x04(\x83\
+\xbbwj\xe1\xfc\x97E\x0fL\x039o\t\xba\xba#\x1eG`\x1c\\\x0b\x01!\x88!\x8c\x80\
+\x18`\x12\x00\x00D0\x0c \xc4\xc8\x8c\x1e\xdb\xa1\xbb\xe8\xdd\x17-\xee\x1a\
+\x8df\xdb\xc1\xc2J\\\xbf\x03\x00\x9c\x03g\x1d^r\x80\xb5\x1a\xaeT\xe8h\xde0\r\
+\x82\xb8I\xd3\xe5[M\x11#\xb6m\xa4=\xc7\xb2L\xb4\x91\x98\xd44\x80p&\xb4=\x8cy\
+\x10\x93\xa2\xbd\x83\xb7\xe3\xe8\x8f\x16w\x8dF\xb3\xdd\xc0\xa8r\xb3\x95\xde\
+\x8b%\x97@\t\x81 \x86\xf5\x1a\xf7\\#\x93"\x08 C\xdf\x11\x01\x00\x89a\xd8\x96\
+\xe9\xba\x96\xe3\x98\xaem\x10da\x10S\xca\x80s\x1a#\x02!\x04L\x93\xbb\x16\xf1\
+\x1b4\x8e\xc3>\xd1\xf1;\x1a-\xee\x1a\x8df\xdb\xc0\xaa\xc0\xab\xb4y\x8f\x05\
+\xeb@\x80u\x0b\x7f\x04\x80\xb5*g\x1c\x86\xd3\xc4 \xc0\xb1%\xeb\xa6e\xb8\xae\
+\xe5:\x96\xe3\x98&A\xce\x19\x8d\xa2F\xc0\x18G@\x04B\x908H\xc0\xc0\x18\x90\
+\xc7\x14,\x033)\xc2\xe2\x10\xd2\xbb-\x08R\xa0\xc5]\xa3\xd1l\x03x\x1c\xdf\xfb\
+\xa2\xde\xf0\xc3\x08\x1d\x83\xda\x16\x01\xec\x12\xfeH\x084B\xac4\xd0K\x91\
+\x94\x03\x8c\x01Gp\x1c\xa3X\xf4\x1c\x8b\x00\xe7\x94\xd2\xa8\x191\xca8\x17\
+\x07\x00\x01\xe4\xc4\x06o\xdct\x8b\x8c\xa1\x81M\xac\xdf <bHL\x024\x0e\xb7\
+\xbe\xaf[\x83\x16w\x8dF\xf3\xd8\xc1Z\xf9\xca\xfa\xbd{\x94"\x02\xb8Y\xa3\xfb[\
+K\x04\x00`\xad\x86\x00\x90u\t!\xc4v\x88m\x12\xdb20\x0e\xeb\ra\xa4\xb7\xdf^\
+\xdd8\x92\xd4\x82\x0c\x8fb\xd3\\!\x84p$6\xa6\xd3V$J\xd2\xa8\xb9\x95\xfd\xdcJ\
+\xb4\xb8k4\x9a\xc7L\xd4\xac\xac\xdf\xbb\'\xect\xc3 \x96\t\x88\xc0D\x06\x98vp\
+\x0b\xe7\xc082N2)c\xb4@\\\xdb0\x0c \x88\x1c\x91QJ\xe3V\xb4L\xa7\x13\x07\x01\
+\xbcl\xaa\x1e\x1a\x94R\x00\x04b\xa6\x1dS\xfc\xc9\x00\xa0q\xb0\x85\x1d\xddR\
+\xb4\xb8k4\x9a\xc7L\x1cE"\x96\x912\xf0l \x00\x94#"p\x04\xc6\x80q@\x00B\x88e\
+\x12\xc7!Y\x03\x10\x91s\xc6)\x08\xaf\r\x81\xfe\x9b\xe2\xa1C|\xbb0\x16\xd2\
+\x0c\xe7h\x93\xc8\xa25d\x00\x00\x84\x00\xa7!"v\xddG{\xa7\xa3\xc5]\xa3\xd1<f\
+\x9cT\x0e\x08A\x8e\x94\xa1k\x11b\x9aQ\x8cAD\t!\xa6A\x1c\x9b\x98\x06\x10\x82\
+\xc8\x019\xa7\x1c\xa0m\xa0\x0f"\xc9\x04\x08\x8b\x1a\x06\x9bO\xdb)\x00\x824\
+\xe0\x8c\xcaC\x91E\x88\x9c\x10\xf3Q\xf5\xed\xf1\xb1;\xa3\xf7\x9f\x10|\xdf\
+\x9f_X\xf4}\x7f\xf0\x02a\x18v\x1e2\xbf\xb0\xb8\\.?\xc2\x86>8\xcb\xe5\xf2\x83\
+\xb6j\xe6\xca\xd5>C\xd1\xff\x90\x8b\x97.?X\xfb\xbe\x8d\xb3o\x1f|\xdf\x9f\xb9\
+r\xf516\xc0v\xd3\x8e7B\x08\x19\x19\xc9\x14F\x0bN&\x93NYi\xc7\xf0\\\xe2\xda`\
+\x10\x8e\x9c3\x86\x88\xad\xb8\xc7\x075\xb3\t!\x9cs\x1a\xd4iXc\x8cm\xaa\x00\
+\x99\x88\xa0\xdc}h\xcb}G\x12\x86\xe1\x1bo\xbe\xe5\xba\xee\xd8Xiv\xf6\xba\xef\
+\xfb\xaf\xbc\xfc3\xd7u\xd523W\xae\xfe\xe1\xeds\x13\xe3\x07\x96\xcb+\xcf\x9d=\
+}\xf6\xcci\xdf\xf7\xff\xfe\xf5_\x8d\x95F\x97\xcb+?\xf9\xf1\x8f\xa6&\x8f\x02\
+\xc0?\xbc\xfe+\x00\x08\xc2pj\xf2\xe8K/\xbe \x8e\x15%_{\xf5\xe7c\xa5R\xe7\xd9\
+}\xdf\xff\x9b\xbf\xfd\xbb\xd7^\xfd\xc5\xc4\xf8\xfd\xdf\xdb\x9e\xbd>w\xf1\xe2\
+\xe5\x7f\xf7\xd7\x7f5`\xd7\x96\xcb\xe57\xde\xfc}\xcau]\xd7\t\xc3(\x08\xc3_\
+\xbe\xfa\xf3|>\xaf\x96\xf9\x87\xd7\x7fu\xf6\xeci\xd1\xfe\x8d\x13\xcd^\xcf\
+\xe7\xf3\x89\x92\xfd\x9b-\x0fy\xef\xfc\x85\xb3gN\xab\x85\xff\xcf\xff\xeb\xff\
+\x16\x1f\\\xd7\x15\xa3\xd7\xbf#\xf7={/:O\xf4\xa05|s*~uv\xf6\xfa\xf4\xa9\x93[\
+\x7fj\xc9\xd0\xf0Hd\xacZ6o\xf8u\x1aSD$\x00\xc8\x01\xdb\x1e\x97o\xe87!\xd0\
+\xddwcZf\x7f\x9f\xce\xceE\x8b\xfb\xceC(\xfb\xd9\xb3g\xca\xe5\xf2\xfc\xfc\xc2\
+\xc4\xc4\xf8\xf4\xf4\xc97\xde|+\xa1\xef\xf3\xf3\x0b\xaf\xbc\xfc\xb3\xa9\xc9\
+\xa3B\xa9\xcf\x9e9\xfd\xf9\xccU!\x1f\xf3\x0b\x8b\xe7\xcf_\x98\x9a<:{}\xceu\
+\x9d\xd7^\xfdE\x18\x86\x7f\xf3\xb7\x7f\xf7\xdc\xd9\xd3\xa2\x86\xf7\xce_x\xee\
+\xec\xe9\xae\xca\x0e\x00\x1f_\xbc\x9c\xcf\xe7gf\xae\xf4\x11\xf7_\xff\xe6\x9f\
+^{\xf5\x17\x000\xc8\x04 \xf1}\xff\xd7\xbf\xf9\xad\x9cx\x00`~a\xb1S1\xcf\x9e=\
+\xfd@\xd5\x0e\xdel\x95\xff\xf4\x1f\xff\x83h\xd2?\xfe\xe6\xb7S\x93G\x1f\xe2\
+\x8cr\x10\x1e\xe8D\x0f1C\xect\x906\xa3\xca\x8d(\xc60\x0c\x00\x80\x10@\xc3\
+\xe5H\x0c\x8c\x08\xf0o,\xec\xbd\xcf\x0b`\x99\xa6\xb1K\xd3\x0f\xec\xce^\xednf\
+\xae\\\x9d\x98\x18\x9f\x9f_\xa8T\xfc\xb3g\xcfT*\xfe\xc2\xc2\xe2\xd4\xd4d\xe2\
+\xc9Z(\xbb\xfa\xcd\xec\xf59\xf1\xcd\xc4\xf8\x81 \x0c}\xdf\x9f\x9f_\x98\x9a\
+\x9a\x04\x00\xd7u\x85\xd6C\xcbK\xb3\xd2\xcb\x8e\x13\x8e\x9d_\xbe\xfa\xf3\xd9\
+\xebs\xaa#b~a\xf1\xe2\xa5\xcb3W\xae\x8a\x02\xe2?Q \x9f\xcf\x89/\xd5\xc2a\x18\
+\x8a&]\xbctY\xfeI\xb4Pm\xb6\x94\xd4\xe5r\xd9\xf7\xfd\x8b\x97.\xfb\xbe\x9f\
+\xcfo\xbcu2s\xe5\xaaZC/z5\xfb\xbe\xe4\xf3\xf9\x94\xebV\xfc\xaa\xe8H\xa2\xb3\
+\xbe\xef\xcb\xda\xc20\xbcx\xe9\xf2\xc5K\x97\xc30\xec\x1c\x04\xd1S1\xc2\xf7=Q\
+\x18\x86\xa2_\xb2\xf2\xe5rY\x0e\xaf\x1c+\xb5\xe3r|\x84GKV+\x87Z\xd6 \xff$\
+\xca\x8b\x06\x0f> \x8f\x82\xda\xbd\xdbA\x10\x01\x02!\x04\x810g\x1f\xc9?%\xfe\
+\x03+\x8f\x8f\xcem\x82\x00H\xb9X]\xdduhq\xdfy\xcc\xcf/\x08/\xc1O~\xfc\xa3\
+\x89\xf1\x03/\xbd\xf8\xc2\xf8\xf8\x81\xe9S\'\xe7\xe7\x17\xba\x96\xff\xc3\xdb\
+\xe7\x9e;{\x1a\x00|\xdf\x97Va!\x9f\xab\xf8\xd5M\xdf\x14\xf2\xe2&\xff\xc3\xdb\
+\xe7\x00\xe0?\xff\x97\xff\xfa\x87\xb7\xcfu\xde\xf63W\xaeN\x8c\x1f\xc8\xe7\
+\xf3\xd3\xa7N~>\xd3R\x8a\xd9\xebs\xe7\xcf_\x10m\x9b\xb9r\xf5\xe2\xc5K\x00p\
+\xf1\xe2%1O\x9c?\x7f\xc1u\xdd?\xbc}N\x88\x8e\xef\xfb\x7fx\xfb\x9c\xeb\xba\
+\xef\x9d\xbf0;{]\x94\x14^o\xf1 "\xea\x94\xd2#\x8e:\x7f\xfe\xc2?\xfe\xe6\xb7A\
+\x10\x8a\xcf\xcb\xe5\x15\x00Pk\x10\xdf\xf4\xa2k\xb3\xfb#\xce\xfe\xc6\x9bo\
+\x01\x80\xf0n\x89>\n\x7f\x17\x00,/\x97\xff\xfe\xf5_I\xbd~\xef\xfc\x05\xf1\
+\xe5\x1bo\xbeU\xf1}u\x10\xe6\x17\x16/^\xbc\x0c\x0033W:\xa7\x96\xc4\x89\x00\
+\xe0\xd7\xbf\xf9m\xa5\xe2\x03\xc0\x1bo\xfe^\xa8\xf3\x1bo\xfe^\x8c\x8f\x18\
+\x8d\xce\xa1\x93\xe3\x93\xea6\xd4\xb3\xd7\xe7\xde~\xfb\x1d\xd9<h?(\x04A\x18\
+\x04\xa1h\xf9c$\x8a\xa2\xb6\xf3\x05#\xe6U|\xe2\x97W\x1ak\xab\xd5{5\xbf\xe1\
+\x01|K\xab\x9d\xc8\xb1\x05\xdf\xf8\x8e\xc5,\x8e\xbe\x9d\xfa\xb7\x19Z\xdcw0\
+\x83\x18\\B\x86\x06\xf7\xe4\nQ\xf8\xe5\xab?\xff?\xfe\xf7\xffU\xd8\x8f\x89\
+\x02\x9f\xcf\\\x9d\x9e>\t\x00\xd3\xd3\'\xa5!y\xf1\xe2\xe5W^\xfe\xe9\xd93\xa7\
+_y\xf9gg\xcf\x9c\x16\xbe\x88\xd7^\xfd\x85j\xfe?=}R\xe8\xd1\xe73W\x9f\x9e>\
+\x19\x86\xe1\xec\xf5\xb9\xe9\xe9S\xa5Riz\xfa\x94\x14\xdc\xc4\xca\xc1\xfc\xfc\
+B\xb9\xbd\xac\xfa\xdc\xd9\xd3/\xbd\xf8\x82\x9c\x8dD\r\xaf\xbd\xfa\x0bq\xc6\
+\xc4\x81\x834\xfb\xbe\xcc\xce\xce\x01\xc0k\xaf\xfe\\\xfd\xf2\xe3\x8b\x97\x7f\
+\xf9\xea\xcf\xcf\x9e9\xad\xba\x8f\x00\xe0\xa5\x17_\x10#0\xbf\xb08V*\xa9\x83P\
+.\x97]\xd7\x19\x1f?\xf0\xda\xab\xbf\xe8\xeauQO4{}.\x9f\xcfML\x8c\x97J\xa5\
+\xa9\xa9\xa3\xb3\xb3\xd7+\xad\x07\xa0\xbcx\x1a\xeb5tr|\x12C\r\x00\x17/^>{\
+\xf6t\xa9T\x9a\x9a\x9a\\.\xaf\xf8\xbe/|t/\xbd\xf8\xc2K/\xbe \xe6\xfe\xc7\x88\
+\x9d\xca\xb7r\xfa"\x98\xb6i\xa7\x1c\x0e\x10SF\x19\xb3\x1d\xd30\r\x80ol\xbc\
+\x13\x02\xe9\xbdF\xe1)\xcc\x1e7\xd2\x1b{\xa5\x12@\xcev\xa7\xb8k\x9f\xfb\xcec\
+bb|\xe6\xca\xd5\xa7\xa7O\xbew\xfe\xc2\xd4\xd4\xe4\xec\xec\xf5B!_.\xafH\x9bW\
+\xf2\x87\xb7\xcf\x95\xcb+R\x9b\xf2\xf9\xbc4\xd5+~\xb5\x90\xcf\x89o\x00\x0e\
+\x00@\xa5\xe2\x8f\x8d\x95\xca\xe5\xf2\xd3\xd3\'E\x99\xe9\xe9S\x17/^R\'\x06\
+\xe1\x88\x10k\xb0\x82\xd9\xebs\xd3\xa7N.\x97\xcb\xf7\xf5\x14O\x9f:\xf9\x9f\
+\xff\xcb\x7f}\xe9\xc5\x17f\xae\\\xfd\xf7\x7f\xfdW\xcb\xe5\x950\x0c\x85y\x0b\
+\x00\x85|Ntmv\xf6\xba\xb0^\xf3\xf9|bNJ\x9cb\xb9\xbcRP\xfc3\xea\xe7\x04\xbd\
+\x9a\xdd\xbf\xc1g\xcf\x9c\x9e\x9a<\xfa\xf7\xaf\xff*\x0cCu\xe6H<\xee\xc8\xef\
+\xfb\xcc.\xe2\\\xc2\xfa\xee\\\x1fN\x9c\xc8\xf7\xfd\xe5\xf2J\xd8\x1e\x99R\xa9\
+4V*=w\xf6\xf4\xc5\x8b\x97\xff\xf0\xf6\xb9\x9f\xfc\xf8G\xae\xebv\x0e\x1d(\xe3\
+\x93\x18j\x00X.\x97gf\xae\xc8\xf2A\x18\x96\xcbey\xc1<v/\xbf\x97\x1f\xad\xaf\
+\xcd\x99\x06\x03$\x16\xd6\n\x99\xb4\x93\xceS\x06H)\x89\xd6h\x18\x7fc\xb7;\
+\xd6\xc3,\xe7\x9e\x15S\xc30BV0Y\xdd1\x1b\xe2\xc5\'\x1a5]\xaf\xf8\xed\xf4d;\
+\xa1\xc5}\xe71}\xea\xe4\x1bo\xbe\xf5\xe2\x8b/\x00\xc0\xcc\xcc\x95\xb1\xb1\
+\xd2\xf8\xf8\x81\xb7\xdf~\'a`\n\x9b]\x8d\xee\x10^u\xb1\xa0\x9ar\xdd|>?11>3se\
+\xfaT\xcb\x8e~\xe9\xc5\x17\xe6\x17\x16gf\xae\x08U-\x97\xcb\xa5\xcdk\xaa33W\
+\x7f\xf2\xe3\x1fIY\x9c\xb9r\xf5\xe3\x8b\x97\xa7O\x9d\x9c\x9a<:\xbf\xb081~@X\
+\xc4]e\xceu]\xd1r\xe1\x1e\xc9\xe7\xf3\xae\xeb\x8aE\xe00\x0c\x85q*4\xaePH\xca\
+zW&\xc6\x0f\x08\xc7\x91\xa8A\xbaef\xaf\xcfM\x8c\x1fP\xdb\xd0\xab\xd9\xf7=E>\
+\x9f\x7f\xee\xec\xe9\xf7\xce_x\xe5\xe5\x9f\xa9\xc3x\xf1\xd2e\x11}$M\xe3\xfb2\
+}\xea\xe4\xd93\xa7\xdf;\x7fA\xfc\x04}N4>~`~~A\x18\xfe\xcb\xe5r!\x9f\x17\x9d\
+\x9d>ur\xf6\xfa\xdc\xcc\xcc\x15\xf1\x98\x92\x18:\x95\xc4P\x8b6OO\x9f\x12\xb3\
+\xa6x\xb0\x98\x98\x18/\x97\xcb\xe2\x1b\xf9l$bO{-\xa4?:,\xdbM\x17\x0fE\x959S\
+\xbctZ\xbf\x1bG\xeb\x86ir\x1a3\xfa\xcd\x95\x1d\x00\x88\x9b\xceT\x1ba\x1cr\
+\x02\x88hf\xdd\x14!\r\x14)\xc5\x8c]\x18\xe4\x0eZ\xdcw"\xe2\xc6~\xe3\xcd\xb7\
+\xf2\xf9\xfc\xd8X\xa9R\xf1\xe7\xe7\x17^y\xf9\xa7\x9d\xa1\x90\xf2_\x00\xf8O\
+\xff\xf1?<=}\xf2\xef_\xff\xd5\xfc\xfc\x82\x08\x85\x04\xa1S\x17/\xff\xc3\xeb\
+\xbf\n\xc2p\xfa\xd4\xc9\xd6\xb2\xea\xec\xf5\x7fx\xfdW\xae\xebT\xfc\xea/\x95\
+\tc~a\xb1\xe2\xfb\xaa#bj\xf2\xe8{\xe7/\xcc/,\x9e={\xfa\xd7\xbf\xf9\xedXi\xb4\
+\xe2W\x9f\x9e>y\xf6\xcc\xe9\xb1R\xe9\x1f^\xff\xd5t\xfb!\xa0U~j\xf2\xd7\xbf\
+\xf9\')\x94\xcf\x9d=\xfd7\x7f\xfbw\xe2(\x11\x9c\x93\xcf\xe7_{\xf5\xe7o\xbc\
+\xf9\xfb\xd9\xd99\xd7u\xc4IES\xbb\xa2\xd6 \xbb/\xbc\xdbj\xbcM\xaff\x0f2\xda\
+\xc2G\xaf\x16\xfe\xc9\x8f\x7f\xf4\xc6\x9bo\xbdw\xfeB>\x9f\x1f+\x8d\xf69V\x0e\
+B\x18\x86\x9f\xcf\\\x15\xeb\x1c\xbf\xdc<\x07w\x9eH(\xf2\xdf\xfc\xed\xdf\x89\
+\xf2\xaf\xbc\xfc\xd30\x8c\xdex\xf3-\xd1M\x11\xae\xda9t\x89\xda\x12C-\x7f\xa0\
+0\x8cJ\xa5Q1U\xfc\xfa7\xbf\x15\xee \x00\x10\xa3-\xfew\xeb\xc5\x1d\x00\n\xa3\
+\x13k4\x8ckK\x96\xc1\t\x10\x16\x87,\x06\x00\x11:\x03@\xbe\x91c\x06\x11\x1d\
+\xabQ\x18\x1a\x0b"@D\x9b\xc4.6\xc52*\x07{W\x9a\xed\x00@\x1e\xe1J\xb4\xe6\x11\
+\xe3\xfb\xbe\xf4\xae\x0cx\x88\xb0p\x13\x87\xcc/,\xba\xae\xa3\xde\xd2\xcb\xe5\
+r\x18F\x0f\x14\xfc\x97\xa8\xb9\xeb\x89\x06l\x8fl@\xa2U]\x11\x830V\x1a\xed\
+\xefs\x7fD\xbcw\xfeB\xa1\x90\xef\x13Y${\'z4x;\x13\xfd\xea\xfc\xad\x07\x1ca\
+\x95\xce\x1f\xba\xf3\x9b\xc7K\x14\xd4\x83\xc6:\x8b\x1a\x80\x94\xc5!"\x03N\
+\x018g\x8c\x00\x07\x10q\xefHHWc\x9e\xb4\xfe\xe9&i\x88h:)\xc3\xcer\x8e\x18W\
+\x91E\x00\x84q\xf0FOd\x87\xf6=\xda^=&\xb4\xb8k4\x0f\xc6\x1bo\xbe56V*\x95J\
+\xbe\xef\x7f|\xf1\xf2\xbf\xff\xeb\xbfz,\xf3\xca\x93\x02""g,\xa6\x8d2k\xdc\
+\xae\x86\xf98\x8a)\xe7\x06A\x038\x10$\xc0\tp\xd3@\x03\xa8a\x00G\x93\x10\xf1\
+\xde)kW\xd0zK\xc9 \xc2\x0b\x03\x00\x84#A\xc3\xf5\x86\x8fd\x8b{\x1fc\xe7\x1e)\
+Z\xdc5\x9a\x07c\xb9\\\x9e\x99\xb9*\x96U\x9f;{\xfa\xb1\xafF>!D\xfe-\x08\x97\
+\xc1\x1a[\xb9\x17\x07\x14L\x83\xb0v@#\x01\xdc3d\x86\xe1\xbd\x8cMy\xe6h\xdc\
+\xac\xc4\xcd5\x1a\xd5\x11\xc1 \xe0\xb9\x04\x01\xb85D!E\x90\x11\x02\x1c\x0c\'\
+]\xf0r#\x86i?\xd6>=Z\xb4\xb8k4\x9a\x1d\x80\xbf\xfc\xa5C\xef\xb8\xe9\x92\xdft\
+\x10\xa0\x1e`\x10#!\x04\x11-\x83\xec\x1f6W\xd7\xcb\x9e\xd5\xe0h\xd0\xf6\x16\
+\x1f\x08\x90u!\x9d&\x80\x00V\x01\x8a\xdfy\xbc]\xd8bt\x9c\xbbF\xa3\xd9\x01DQ\
+\x18\xc6\x9c\xc7k\x9eC\x0b\x19\xcb\xb6-\xcb$\xa6\x01i\xc7(d\xcc(\x8a9\x0b\
+\xaaMT\x95\xdd$\x90r\x08p\x00\x04\x88+\xd0\xb8\xf1x\xbb\xb0\xc5\xe8h\x19\x8d\
+F\xb3\x03`4\xae5\xc02b\x84\x95ZP,d\xf2i\x1b9r\xcb$,nV\xeb\xf78g\x88dc7m\x84\
+\x94\x0bD}\xff\xa9\xbe\x00\xf6\x08\xd8O\x8a\x1bM\xbbe4\x1a\xcd6\x05y\x8cq\
+\x03i\xd5\xa0\x95;\xcb\xabA\xc4-\x03<\x17"\x8a\x00\x161, \x049\xe5\x8c\x02 "\
+\xe1\x08\x9eK\xa0m\xb6\x0fe;6\xe10=\x18:\x03\xbb1{{\'\xdar\xd7h4\xdb\x08\xe4\
+1\xd2\x06\x0f+<\xf2[1\x8b\x04\x10\x811\x14B\xdd\x8c\xc02\x08\x00\x05N\x01\
+\x80\x00X&\x01 \x11\xdd\xc8\xdd\x8b\x08\xe9\x84\xd9.`\r\xa8\xdf\x84\xec\xe4\
+\x96v\xe91\xa1\xc5]\xa3\xd1<V\x90!\xa7H\x1b<\xaa\xf0\xa8\x8aq\ry\xdcR\xe5\
+\xb64s\xbe\x11\xd1(6J5\t\x91\xd1\xee\xbcU\x00\r\x83\x10\x02\x9c\x83e@\xca!\
+\xdd\xdf{j\xde\x86\xf4>0\xbd-\xe8\xd9\xe3E\x8b\xbbF\xa3\xd9rx\x04\xb4\x8e\
+\xb1O\x83\n\xa7M\xe0\xb1\x9a\xa91\x01\x01\xe0\x08\x1c\x95\xffW@\x00@\x10\x89\
+\x04\x8c\xf6\x1bL\x99T7\xb3]\x1eQ\xfd\x12\n\xcf\xc2.M\xe3.\xf9\xff\x01\x9e\
+\xe6W\xce\xb2\x99\x1b\xa3\x00\x00\x00\x00IEND\xaeB`\x82'
def getSplashBitmap():
return BitmapFromImage(getSplashImage())
\x94Zv\xe0\xfb\x8e{\x8bA\x8dT\xd9=\xcf-\x16\x0b\x04\x80RbY\xb6\xfa\xb8\xc5Ba\
\x9f\nq]7\xf3]\x18cD\xcaR\xa9X*\x15\xdb\xed\xce\xf6\xeen\xb7\xdb\xd7\xb3.3\
\x11J\xc9\xc0\xb5t\xaa:1Q\xb6(U\xc3\'!$\xcc\xe5\x84\x90T\xb51\xdb\xb6,+\x9f\
-\xcf\x1fD\x15x\xc3E\x07\x1b@\t|\x0f\xb5\xfd\x86{-i\x11\xff\x7f{\xef\xfe\x1b\
-\xc9u\xdey?\xe7\xd4\xbd\x9a}#\xd9\x9c\x1b\xa9\xb9*\xf2\xcc(\xc9h\x94D\x91w\
-\xe1\xd7\xce\xeb\xbc\xf6\xc2\x86\x9c56\xbbX`\xb1\xc0\xfe\xb4\xc0b\xff\x9c @\
-\xb0X\xe0\x85a\xc0p\x04\x08+!\xc6+\xad\xbck\xbdr\xa2\x89\x12{4N\xc4\x19\xc7\
-\x99;\xc9\xb9\xb09$\xbb\xfaR\x97sy\xf6\x87\xd3],V_\xd8\x9c\x19r\xd8\x9c\xf3\
-\x81!\xf74\xabO\xd59]\xf5=O?\xe79\xcf\x13m\x88\xe8\t\xc0\x0eS\x85\xe1\x14\
-\xdd\xe2\xb1\xc9\x13\xf7\xf4NJ\x1d\xdf\xea\x01S\xb7\xa3z\xb6\x1fN\x7f\xffA\
-\xf5O\\\xb6\xea\'K\x1e{`\xca\x0e\x01Dj2\xa3\x12\xb9\'\x13wA:3\x86a\x98\xbd\
-\xdc\xd9\x9c\xf3f\xf5_l\x96\xfe0m\xca4M\xc7qT^@\xb5\x19\xbd\xff2\xd4\x05\xc0\
-v\xbf\x84a\x18*\x82BJC\xd0\xf2Z\xe5\xff\xae\x97\xff\x88\x8a\x8e#\xd6\xbd\xe4\
-\x81\xcbWM\xd9\x06\x90\x80 \xa9\x9bX\xb3\xa1\xb3\xc0\x9d\xa3\xe0TU\x1aBJ\x07\
-Gy\xf7\xfb\x82\xd2\x87GP\xbaY\xfc\x83\x8d\xa9\xdf7D\xcb\xe5\xab^\xf2\xc0\xe1\
-u\x03{[H\xa8\x8dVI\xfa\'h\xe5Uo\xfa\x94W\x9au\x1c\x87d\xa0\x94\x86\xa5\xdf\
-\xb9\xe3\xbc\xa6&\x0c\xd5\xa6\x99I\x8b855\xe5\xba\xae\xea\xe5\x98_\x8a\n#Q\
-\xcf\xf9\xf2\x91\xff\x94\x8b\xcfQ\'U\xdf\x9d\xe7y\x85B\xc1w\xfd\x11K\xa9\xfd\
-\xe7\xea\xdf\xe2\xa4N\x1d\xc7q7\x84F\x08)\x8d\xc0\xb8\xd4\xf0\x7f\x97"3\xf9\
-\xa6\xc7\x1ez\xec\xa1%6)\x08@\x90\xd4N\xcc\x99\xc8\x99O\x9c\xe3\xd2\x9eN\x17\
-\x03\xd3u\x143\x93M~\xe0e\xa4\x13|\xee\x1a\xd4\r\x90Z\xaf\xc5\xe2T\xb18\x05\
-\xdd]\x97\xa8\n\x15)#\xc0\xb2\xac\xf4\xd7\x98\xea\xbb\x1a\x7f\xc68b\xea\x0f\
-\xe9\xbat\x08\x1d\xb5\xc8l\x18\xc6\xdc\\mz\xbaJ\x08\xb1m\xdb\xf3<\x95:I\xe5\
-\xb3\x1cgT\xfb\xc7\x93RZ.\x95\xbe\xf2[\x1e"Z\x96\xa5\xbe)\xf5,\x0c\x9b\xf3\
-\xb2\x93n\xb6\x9d\xde7"\xd5hH)\x19\xe3Q\x14\xc5I\xa2f2B\x88eY\xbe\xef\xf9\
-\x9e\xa7*\xbe\x1a\xbd\xf2\x8a*\x85\xd4\xb1\xa3G\x8e\x1e\x993\x0c\xc3q\x1c\
-\x95$\xca\xf7\xfd]\xe5U~!lK6 \xb6\xfdP\xcf2v\xad%d\x99,&bxz\x8b\xc2\xcc\x19\
-\xd8\x93T\xe3\xfbB\xf6wq\xf6N\xea\t\xabDDf\x1co\xb8\xc7\x1b\x00\x90y\x0c\xd4\
-\x07\xdd^6\x02\x00\x90\xbd\xf2\x8f\xa9h*uK}\xc7\xfd\x16e\xd6TIU2\x8d\xd2K\
-\x92D=\xde[Na\xb3\x94`1\xf1N62-\xe4>\xa5V\x08\xbbU\x8d\x06\xd5S\xcd\xfa\x82\
-\xb0W-\xde0\x8c\xf4\xc9A\xb3\x12\xd9\xe5\xc8\x7f5\x1d\x10\x00H\xa7@\xdf\xf7\
-\x89]\xc8\xc9zjh#"\xa54\x15\xf7tS\x929<\xf0\xbc\x1f\x92YVU\x93\x90j6\xe7\x1f\
-\xc8NTN\xa6\xc6\xec\xae4H5\x92\xde\x0cYaU\xf7@z\x1bHi\n\xd3m\xb9G[\xf0Fn\xf0\
-\xd5\xa7\xec\x9e+)U\xf6\x1d/\x89\xf4\xaa9\xabk\xc8\x8dg\xf6\x06\xc8*\x9di\
-\x92\xfe\x83\xd3\xf5gedt\x7f\n\xf4m\x81\xf3=\xaf\x7fJ\xcb}\x89\xea\x9d\xec};\
-\xfexf\x9bM\x1f\x13\xd3\xec\xeaC\xeeY\x18\xf8)\xe8\xdd\xa2\xd9v\xb2_J:\xe1\
-\xa9q\xf3<7\xfb\xd9\xfe1Q\xefs\xce\xd5M.\xa5T\xedg\x1f\xc9\xdd\xce[\xfb\x0c\
-\x0b\xee)/\xa8\xaa\xc81\xf0\x98\xf1k-\xf1\xf6#\x994\x01\x80\x00\xb0\xe1\x0b\
-\xb3v\xa1fyU\x98\xc4\x05U\xe8\xbb\x93\xd2\xbb\xdc4\xcd\xecm\xa4\xa4\n\xb7\
-\xafI\xe6d\x0b\x00\xb2\x85\xe7\xb37\x9fR\x9f\xd4\x82\x1bv\r\xe9\x05\xa42\xad\
-<\x12\xe9\x84\x91u\xcb\xe6|\x08\xe9S\x94\x86\x7f\xe4j\xd5\xe7\xce\x98\xfeV\
-\x80\x8c\xa1\x94\xceg\xfd.\x02\xe8\x89{\x1a\xd1ad\xf6\x7f\xa6\xa7V\xe9X9\xe7\
-\xe9\xe5e\xbb?\x8e\xb7\x04\xb6kMV\xd9S\xa7Y\xae#\xd9\xf9l\xb4\x1bd\xd8\r\x90\
-\x1e\x9c\x15#%\xac\xb9\xc1O\xef\x04\x80m\xd3jv\x19#\xbd\x18k{y\xeb\x11\xfa\
-\x0e\x99\xf4\x90\xe9x\xe6n\x80\xec\xb7\x9f\xfd\xe2r\xeb\x13\x84\x10\xe5\x7fP\
-%\n\x84\x909?{\xb1\xcf\x19\x92\x1bIe\xe8\x00@6Y\xf1SL\x99\xb9\x1bCM\xfc\xb9g\
-aG}\x1f8\xdbe\xed\xa7\x9c?-7z\xe9\x98\xa8\t/+\xeei4\xcen\xbb\xb6\xffH\xd6\
-\xe6\x9d\xee\xbe\xe5\x11\x159\xfc\xe9\xd3\xa3\x03\x16\x14(\xf9V\xf8#\x0c\x9d\
-*\x80\x1a\xfeL\xd7\x87<\x91\xe2\xae\xc8\xca_\xaa\xda\xa9\xb8gWu\xb2bjf\xf3\
-\xd3\xf6\x9czY-H\x8dbs{\xdc\xee\x08}\xcf\xdd\xca*]e\xf6\xd7@\xbf\xecf\xaf\
-\xc7\xc8\xec\xddO\x7f1\x8c0\xdea\xbb\xb2g\x1c\x11\xf9\x05F\xb2\xdd\x94V~\x00\
-\xda\x0b\x96W\xef\xab\xc0\x0f\xd5\x02\xf4\xe6\xc2\xac\x83e\xfc\x07\x89l\xffa\
-N\x081MS\x8d\x00d\xb45\xed\x882\x96\xd3\'y\xb72\xa4&\x0f\xd8>\xb9\xe6f\xd6\
-\xdc\xe4\x9a\x1b\xfc\xac\x9d8p\xf0G_Rv\x8e\x19x\x03\xf4\x7f\xfb\xd9KUW\x9b\
-\x1a\x19\xea#\x94\xd2\xcdFC\x88\xec\x86\x1d \x84\x94J\x03<\xdd\xa9\xb2\x03\
-\x80a\x18J\xdcS}\x1cx\xd3\xee8\xaa\xb9\x1bCiqz3\x8c\xfeA@\xb6[<\xd9\x11\xce\
-\xcd\xb8\xfd\x8b%\xe9\x80\xa4\x93\xab\x9a\xf0\x84\x10jH\xd5\xe47\xda\xe4:P\
-\xb0\xe0nZHoXE\x0e\xcb\x9fVV\xf6\x8e\xf0\xe6\x12\x8a\x08v2\xdb\xbd\xf2B\x1aO\
-9\xa9\xe2\x9e{\xb6S\xa1L\x83grK\xf6\xa9\xa8\x99\xa6\xb9\xb6\xf6\xa4\x11\x04\
-\xae\xe3\xbe\xf6\xda\xab\x00\x90\x95\x80t\x85\'\xfb\xf0\xef\xf8k4k\x0c\xaa\
-\xe7!\r\x1bP-g\x7f\x9e\xc3\xf6\x1b:\xd5w\xb3\xb7\x8e\x97^j\xee\x8c9\xcb(}\
-\x08\xfbE\xa4_Gr\x06\x11\xc9\x98\xd84\xe3\xedI\xcf\x95\xed\xfe\xf8\x0fR\xd6\
-\x04S\x8d\xa4\x01\x8b\xb9#\xd3\x87y\xe0L6&\xd9\xc95m07\xf8\xfd\xe2\x9e\xf6.{\
-\r\xd9/:\xfd\xae\xc7\xec2f\xaa?g\xe5,\xfb\xd5\xe7\xc66\xab\xefF\xcf7\x98Z\
-\xee\xbd]\xfb[\xa6\x9e\xeb:\xae\xe3\xe6\x86(\xf7%*9\x86\xde\x1d2\xfa\xbe\x1d\
-\xdd\x97\xec\x8d\x91\xfd17\xd0g8\xf0KI\r\xfc\xd4J\xc8*{\xeeq\xc8}\x17\xd9\
-\xdf\x07\x88\xa8\xc4=\xfd\x06\xd39`W?\xf5\xf6\x1f\x99\x04\xdd\x95O\x00\xc6q\
-\xb0\xd9>~\xad%\x1e\xf1\xf6\n\x00\x90\x91\x1e\x1ej\xba^e+\x9d\xe4\xa4\x8a;\
-\xf4\x19\xce\xeb\xeb\x1b\x96e\xfa\xbe\x9f\x0b\x7f\x86\x8c5\xaa\xfe\x1b4[W\
-\xbf\xf8\xd5\x89\xe3\xc7_\x7f\xfd\x02\x00\x18\x86\x81\x99\xca\xcbi\xe3Y\xfbn\
-\xc4\r\x9d\xbei\xf4*=\xa9{4;a`\x86\xac\xa1\x97U\x99\xf4\xf5\x08e\xe9\x973u\
-\xdf\x8fp\xfed\x85\xcf\xcc8\xd0\xb3\x97af\x16\xc1\x06v?mj\x9c/%m_\xd9\x929a\
-\xcd\xb5\xfft\xde\x83\xfe1\xc9\xcd\xafr;\x03\x07\x9f\xf4<3\xd9\xaf8;8\xe3_\
-\x80\xea\xe0?\xdf\xbcu\xe2\xc4q\xdf\xf3\xb2\xdf~\xf6\xab\x87\xed_J\xdaq\xe5|\
-\x00\x00D\\[{\xd2\xe9f\xcb\xda\xba\x86\xa3G\x8e\xa4\x1e\xeal\xaf\xd3\x99^u9\
-\x15\xf7\xec\x17\xb7\xdb\xc1\xcc\x1aLYq\x1f\xff\xcb\xea\xbfKS\x81NM\x10\x99\
-\x8f\x93\xd9\xf6 \xa4W\xae~4\xa8\'\x143\xfe\xd5\xdd\xce[\xfbO\xd2\xb8\xab"\
-\xa3\xfa3\xf1\xa68\xc5\xa3c\xd7Z\xba\x83\xb2\x1bC\xc5F,\xccN\x9f&tK\xd2\'X\
-\xdcS\xc9@\xc4\x1b\xbf\xfe\xa7\xff\xff\xd3\xbfv\x1c\xe7;\xff\xea[33\xd3\x98Y\
-h\xcd\x1e\t\xdd;I=\xe1\xa0\x8c\xa6\xf4\xd6\xc9i\x10\xd9\xce\xe8\x8bIoh\xf5_u\
-Sf\xe7\x8c\xec4\x03\xdb\x1f\xf2\xf4\xbf#d=w\xa2\xec]\x9e\x13\x11\xd8\xee\x00\
-\xc9\x9e"7E\xa5\xef\xf7\xf7}\xb7\xdd\xef\x1f\x87\xb4e\xd8>\xfe\xc3\xda\x7f\
-\x96\x07\x95d\xe6We{fG\xbe\x7fv\xc9\r~\xfa\x02v)\xeb\xd9\x1b\xec\xfa\xf5_\
-\x7f\xfa\xd7\x7f}\xfc\xd8\xb1?\xfa\xc6\xff\xa5\xa2\xf4p;\xc3:\x0e\x00\xa9u\
-\x1f\x04\xcd\xbb\xf7\x96rIT\x08\xa1\x0b\x0b\xf3\x96e\xf5_[z\xe5\xfd\xb3\xd7S\
-\x0fl\xf6\x86\xe9\x9f\x93\xc6o0w\xa3\xa6_D\xce\x8a\x82\xbe\t/w\nu\x93?\xaf\
-\xde\xed\x0f"Z\x97q7\t\xf9\x08\xe7\xb8W99Nk2i\x88\xb0\x0e\x00D%m\x1f\xe2\xe1\
-1\xbd\x8a35\xb7\xed\x9d]\\\xf2A"\xf7\xd8,/\xaf\x00@\x1c\xc7\x0f\x1e><rd\x0ez\
-\x0f\x1e\xf6\xb1\xd5\x02\x00\xf4n\x14\x18.@\xb9\x17\xa3\xc9\x1e\x9f}6\x06\
-\xce\x1c\x90\xb9M\x9f\xe2,\xa4g0f5t\xc4Y\xb2\xff\xcc\x1d0\xb0\xef\xb0\xfb\
-\xee\xe7N\xaa\xfe\xb9c\xe3\xbbm\x7f\xc7\xf3\xa6\xc3\x02\x991\xc9\xa9\t\x8c\
-\x1c\x96q\xc8\xdeT\x0f\x1f=\xfa\xfc\xef\x7f!%.\xaf<\xf8\xff>\xfa\xf8_~\xf5\
-\xed\xe3\xc7\x8f\xa5\x87\r;\xbb\xfag*ya\x18.\xde\xf8u\xbb\xd3\xd9\xfe\x0b\
-\x9eT*\xa5\xda\xec\xcc@e\xcf\xbeHO\xf1,\x03;\xac\xcd\xfe?\x8d\xdf\xda\x8e7\
-\xea\xb0g!k\xa3<\x97\xde\xed\x1b,\xb8\xab^\x8c0\xdb\xbd\xf2\xfcx\xb5\x96\x90\
-\xf5~\x04\xe0\x0e\x1e\x9e\xfc\xc2\xec\xa4\x8a;\x00dm\x81\xdf~\xfdb\xbd\xbeV*\
-\x15/\x9c\xff\n\xdd\xbe\xe0\x83\xbdm\x84\x99\xd8\xb8\xbc\xd5\x00\xcf\xf5F\
-\x19\xf1\x84\x8c8\xf8\xa9O\x94>\x06\xbb=\xcb\xd3i\xf7\xf8\x17\xb6G\x8d\x8fy\
-\xf6=\x1d\xfc\xf47\x81\x94\xb2\xd9l}\xfa\xf3\xbf\t\xc3Py\xc9\xeb\xf5\xb5\x8f\
->\xfe\xe9\xef\xfe\xceo_8\x7f\xde\xf3\xdc\x11\xa7H\x1b\x11B<~\xbcz\xedW\xffP\
-\xaf\xe7*\x9b\xa3a\x98\xe7\xce\x9e\xf5}\x7f\x98\x95\xba\x17_\xe2sos\xcco$w\
-\xba\x89\xd0\xf1~DgU\x05,*-\x1e|\x10!N\xf1\xd8x\xad\xd5e\xbc\t\x00\x84\xc0\
-\x88d\x03N\xf1h\x7f:\xc9\x89\x14\xf7\xd4bJ\x17g\xaa\xd5\xca\x9f\xfe\x9b\x7f\
-\xad<q\xd8[hM!\x84(Y\xef\xad\xb0\x89\xb4\x95}\xb8\xda}\xb8/\'\xe8\xd6\xdfg\
-\xf6bd\xd2{\x8fs\xdel\xb6\xfe\xe6\xb3\xbf\xdd\xd8\xd8TgS\x07t:\xe1\x95\xbf\
-\xfd\xbb;w\xee\x9d:u\xf2\xec\x99\xd3\xd5jeD#\x0f\x1f>\xba}\xf7\xee\xed[\xb7;\
-a\xd4\x97f\x80\x1c=:w\xf2\x95\x85\x81\xfe\x8a\t\xe5\x10ta\x14(\x934\xb5\xef\
-\xa8P\xf4\xd9\xb1\xccv\x14\xe9\x8f\x00\xc4\xa1S\x051,\xbfz\xaa\xff\xfd\x89\
-\x14wEj:\xa5\x8b3\xc3\xee~\xf5 \xa9x\xf6\xf4x\xec\xbd\x7f\xc8\xef6\xcds%\xab\
-\xec\x8c\xb1_\xff\xd3?-\xaf\xacd\x1d\x08)\x8f\x1e?~\xf4\xf8\xf1?~\xb9xd\xaev\
-\xfc\xf8\xb1j\xb5\xa2b<\xd4\xad\x18E\xf1\x93\'OVV\x1e<Y\xdf\x88\xa2T\xd6\xb7\
-\xb5P*\x15_\xbfp\xfe\xe0G\x86hR\xb6\xa5\xf6\x1dZ\x91\x83\xfa\xd5\xd3c\xb5\
-\xd6z\x80<De\xb6\x8f\xceKc\x0eH\xbd9\xa9\xe2\x9e\xf5xJ)[\xad\xd6\xd2\xf2\n\
-\xa5\xf4\xf2\x1b\x97 \xf3\xd3/\x8e\xe3\xc5\xc5\x1b\x8fWW\xa3(\xb6,\xb3V\x9b=\
-u\xf2\x95\xee_1\xef\x0f\x8d\xe3x\xf1\xfa\x8dz}-\x8e\xe3R\xa947W;w\xf6\x8c\
-\xe3\x8cNX\xaay\xe9H\xc5=I\x92r\xa945U\xc8\xd4\xe8vd\x9b\xa7\x00\x00 \x00IDA\
-T\xe8\x1e\x92\xcat\xbb\xdd\xba}\xa7}\xe7\xee=JiV\x9c\x11sUSH\xcel\xf7<\xf7\
-\xb7/^(\x16\x8b\xe6n6\tk^ (9k-\xa9\xd7#R\xfb:\xc5\xa3\x86\xed\x0f\xfa\xcb\
-\xf6\xd6D\xc2\x9aK\xea\x9e\x90rh\xf1&\xc3\xf6\x87\xe5\xa5\x99Tq\xcf\xd1jw~y\
-\xf5\x1a\x00\\~\xe3R*\xd9K\xcb+\x1f\xff\xf4\x7f\xc7\xf1V-\x98{\xf7\x97n\xde\
-\xba}dN\xad)o-\xb1"\xe2\xe2\xf5\x1b\x9f\xff\xdd/2\x07\xaf,^\xbf\xf1\xcb\xab\
-\xd7\xbe\xfd\xff|sn\xae\xb6\xcf\xdd\xd1\x1cd\xd2_\x81I\x92LM\x15~\xe7\xf5\
-\x8b\xbf\xf9\xe7\x9bO\xd6\xd7{\x8b^9\x03\x9c\xa4\x1f\xc9\xb6\x91Q\xf3T\xd3\
-\xb7>55U\xb8x\xe1+\xb3\xb33\xd9\x1d=\xfb\xd17\xcd3\xc0[+\xc8#\xe8\xe6k\xdc\
-\x9d\x0b\xa5\x1f\xd6\xbc\x87\xd9\xa4\xedC\xc3\x1f\xcf\x0c\xcbK3\xa9\xe2\x9e[\
-a\xdf\n\x1c\xeb\x85\xd0\xae\xae\xd6?\xfe\xf8\x7f\xc5IR,N]\xbcp\xde\xf3<!\xc4\
-\xfd\xa5\xa5\xbbw\xefon6\xa0\xe7\x96Q\x07/-/\x7f\xfa\xf3\xbf\x01\x80\xdf{\
-\xf3\xf2\x99\xd3\'K\xa5R\xab\xdd\xfe\x87\x7f\xf8\xf2\xfa\x8d_\x7f\xf8??\xfe\
-\xb7\xff\xe6\xfb*\x01\x93F\x93]\xecQ\x9e\x19\xc3\xa0gN\x9f\x9c*\xf8\x8fW\xd7\
-z9\xdcw\x84\xe0V\x01o\xb2-\x8f\x0c!G\xe6j\xe7\xce\x9e.\x97\xcbin\x86C\xe3p?\
-\xc4\xa0d\xbc\xd5\xad\xd0;"G\x98[\x9e\xa7\xe6\xce\xf9\xce$k\xa5\x19\xc7\xc4\
-\xc8\r\xaevavX#\x93*\xee9\xd2YR\xf6\xb2\x88,\xde\xb8\x11\'\xc9\xcc\xf4\xf4\
-\x1f}\xe3kj\xfb\xb2\x10\xa2Z)\x97K\xa5_\xfd\xc3\x97\xb9\x83\x7f\xfe\xd7\x9f\
-\x01\xc0\x1f\xbe\xf5\xfb\xaf\x9e;K\x08a\x8cy\xae\xfb\xd5\xb7\xdf\x8a\xe2\xf8\
-\xf6\xed;\x8b\x8b7._\xbe\xa4\x1f-M\xd6\x8f\x97\xf5\n"\xe2\xf4t\xd5\xf7\xbd\
-\x8d\xcd`\xb3\xd1\x08\xc3\x81\x12\xdf\r\xbe\x85\xee\xffeBAz&|\xb9\\\x9e\xab\
-\xcd\x9e8~\xcc\xf3\xbc\\r\xca\xbd\xee\x9a\xe6\x19a\xc1}eh\xab\x1d\xa4\x03\
-\xbf0b:^y\xac\xd4\xbe\xacq\'-B0<\xfc\x91\x8e\xde\xe0:\xa9\x8e\xbc\xdc\x03\
-\x96\xfa.S{\xea7\xbf\xb9\t\x00*\xc1@6o\xd1\xc2\xfc\x89J\xa5\xac\x9aP\x07?z\
-\xfc\xb8\xd9lMM\x15N\x9d|\x851\xa6\x0eV\x9c:\xf9\n\x00,-\xaf\xbc\xa8nj\x0e\
-\x14\xd9\x90\xbetGejY;\x8es\xf4H\xed\xcc\xa9W\x16\xe6OT\xabe\'\x9f\x906\xbf^\
-\x9a\xe28\xce\xf4t\xf5\xf4\xa9\x93\xaf\xbdz\xf6\xc4\xf1c\xae\xeb\xba\xae\x9b\
-\xcb\x97\xa9\xf5\xfd \x83<\xe2\x9dLj\xdf!\x87\x15\xb6\xef \x1d\x86\x88\xd6E\
-\xaf~\xf2\x88Hy\xb7tl\xf4\x06\xd7\t\xb6\xdc\xd3\x18\x98l\xf6\x12\x15\x0f\xf3\
-\xe0Aw\xa0\x8f\x1e\x99K\xd3Y\xa4\xe9;\x8e\x1f;\xba\xb9\xd9\xc0\xde\x9eo\xb5\
-\x01*\x89\x93\xff\xfd\xc9\xa7i\xe3\x04\x08!\x84q\x06\x00\xc3\xc2!4/\'$\x93\
-\x8e-\xbd\xf7(\xa5\xea\xdes\x1c\xa7\xe683\xd3\x15!e\x14E\xedv\x18\xc51\xe7\\\
-J\x81\x12\xa4\xca\xe7N(\xa1\xc44\x0c\xd7u\xa6\n\x85B\xc1O\xb3\x1f\xab*\x02J\
-\xdc\x9f"?\x8c\xe6\x85\xc0\x82\xbb %\x10\x90\xc3w\x90\x1ava\xbc\xd4\xbe\xc8\
-\x1a\xb7\xbb\xafFF\xca\xbb\xe5\x1d\xca\xf2M\xb6\xb8+\xbd\xde\n]\x07`\x8ce\
-\x17\xafR#\xab?w\x15bw&\x10B\x02@\xc2\xd8\xeaj\x1d\x86\xa0#&5\neD\xab\xbc+*\
-\x92\x8ad\x12\xeb\xa7\xb9\xb1\xd4\x04`[V\xb9T\x1a\xdd\x1a\xcd\xa4\xa1V\t\xe5\
-]\xd7M\xbd\xed\xfa\xae;\xf8\x88h\x83\x87u\x00\x00\x1c\xb5\x83\xb4P\xfb-\x18\
-\xe3\xdbd\xc1=\xc9\xba\xc1W\x9c\xe3\xd0j\xda\xa5\x13;F\xcaO\xaa\xb8g\xb7\x08\
-\xa6Ie\x01@%%O\x0b\'EQT,\x16s\xca\xden\xab@\xd4nm\x04\xc3\xa0\x00P\x9b\x9d\
-\xf9\xea\xdbo\xa5\x8d\xab\xa7NYRi\xbe=\x8d\x06z\x96{\xf6u\x9a\xd56\x9b~y`\
-\xc6\xb4\xf4S\xa4\xaf\xc0@6\xa1\xff8\t\xe55\x07\x03d\xc1m\xb5~\xca\xf9\x88\
-\x1d\xa4\xc7\xfaw\x90\xf6#y\x877\xbb\xc1\x94R\x02\x17\x83[\xa3\xa6\xebO\x9f\
-\xda\xb1\xb5I\x15w\xc8\xe5\x8d\xe9e\x14PO\x94\xef{\x85\x82\xdfnwV\x1e<|\xfdb\
-5\xeb\xa0O\x92\xe4\xc1\xc3G\xea\xf3\xea!\x9c\x99\x9e\x06\x80\xfa\xda\x93\x8d\
-\xcd\xcdj\xa5\x92m\xbc\xd9l"b\x9a\x1bO\xa3\xc9\xba\xddS\xbb\xdb\xc8\xd4\x12H\
-30g\x93\xfd\xa6\xf7O\xff\x07\xd3l\xcc\xd9\x84\xfe\xbb\xca\xc4\xa9y\x81\xf0\
-\xce\xaaL\xda@F\x86?R\xd3\xab\xbc2Nk\xacqG\x15b\x04\x00&\x86\xfc\x08\x00\xf0\
-\xa7O\x8d\xe3\xbb\x9f`qW\xe4\x16TS#\xfd\xf4\xa9S_.^\xbf\xf6\xab\x7f4M\xf3\
-\xe2\x85\xf3\xea1k\xb6Z\x7f\xff\x8b\xab\xaaD=\x02(\xab\xdfq\xec\x85\xf9\x13K\
-\xcb+W\xfe\xf6\xef\x7f\xff\xcd7j\xb5nh\xd1\xe6f\xe3W\xff\xf8%!\xe4_\xfe\x8b\
-\xb7\x8f\x1d=\xaa=3\x1aE\xea\xebK\r\xf04\x8f|\xaa\xec\xb9\xdaXYq\xcf~*\x9b\
-\xc4<\x97\xd0\x1f\xf4\x1a\xcf\xc1\x07eZ\x1dI\x88\xe1.\x94\xf1r\x84\x89xS\x84\
-\xdd\xe4B#\xd6Q\xc7/\xcb7\xf1\xe2\xde\x8fR\xe1\xaf\xbc\xf6j\x10\x04\xf7\x97\
-\x96\x7f\xf1\xcb/\xfe\xe97\xff\\(\x14P\xca\xc7\xabu\x00(\x97K\x8dF\x00\x99\
-\xect\xaf\xfd\xd6\xb9\xa0\xd9l4\x82\xcf\xfe\xf6\xef*\x95\xb2*e\xa7\xb28\xcdL\
-O\x97J%mCir\xa8\xfb!\x9b\xc4_\xd9\xef\xd9<\xfe\xfd\xe9ma\xbbO&\x1bocLNiP\x8d\
-\x82\xb7\x1f"\x0f\x81\x00"\x0e\xdbAJL\'[@c8\xc8\x1aw\xba\xafF\xac\xa3\x02\
-\x14f\xce\x8d\xe3\xbb\x87C#\xee\xfdy\xec\x10\xf1\xed?\xfc\x83\x99\x99\xe9\
-\xc5\xeb\xbfn6[\xcdf\x0b\x00|\xdf?\xff\xda\xab\x9d0T\xe2\x9e\xa6\x99\xb6m\
-\xfb\x0f\xff\xe0\xf7n\xdd\xbe{\xef\xfe\x92\xda\xe2\x04\x00\xb6m\xbd\xb2\xb0\
-\xf0\x07\xbf\xff\xa6\xab3\x10h\x06\x91\x9a\xf0\xd0\x8b\x8c\xc4\xbe\xac\xe5\
-\xc3\xc4=+\xf1d7\t\xfd5\x07\x04\x94\x9c\xf5\xfc\xe3\x9c\x0f\xdfAZ=9\x8e\x0b\
-\x85\xb7\x1f\xcb$P\xafG\xfc\x08p\x8aGMw\xd4\x12}\x96\x9d\xb3\xc5\x1e@T<\x8c\
-\nHW\xff\xc5L&H\xf58\xa5!\r\x94\xd2\'\xeb\xeba\x18QB\x8a\xc5)\xec\x15\x12#\
-\xbd:\t\xb2W7U}\xa4\xbe\xf6\x84\x10\xa0\x84\x1e;v4[CUG\xa4iF0p\x7fS\xfape\
-\x9f\xb2\xfc\xe6\xeag\xc8)\xafy\x81\xb0\xe0.\x0b\xee\x03\x01\x94\x18%C\x13\
-\xbf\x94\xe7\x7f\x8f\x90\x1d\xb6\x13\xa1\xe4\xd1\xe3_\xa0\x88\x01\x00\x11"\
-\x86\x03\xa7\nB\xcd\xf2\xfc\xef\x8d\x97\x05\x1e`\xa2-\xf7\xac\xed\x03=\x1fhv\
-\xd3\xa0\x8ad\'\x84\x94\x8a\xc5\x82\xef\xab@\x9a\xf4G\xb4\xb2\xb3\x08!\xb9"\
-\x9fs\xb5Y\x9aA\xffL\xd6\x8cCV\x9d\x07j\xfa\xc0\xe3A\x0b\xfad\x82\x92\xf1\
-\xd6\x03\xf5\x9a\x89|\xa6\xe6\x14\xbfzzGe\x07\x95\x94Ft\xb3Zq1X\xd9\x01\xc0\
-\xab,\x8c\xaf\xec0\xb9\xe2\x9eU\xf6\xb4\x1c\xb3zN\xd2\xe0\xc8\xdcBk\xfa#:-\
-\xca\xac\xde\xa7\x94\xa6\xfa\x9e\xfb\xbd\xac\xf5]\xf3\x14h\xe1>\xf4\xb0\xe0>\
-J\x0e\x04\xa4\xc4\x11u\xefF$~IA\x11\xf3\xe6\xb2z-qx\xf8\xa3\xe5\xb9\xe5q|\
-\xf7\x99\x0b\xd8\xd5\xd1\x07\x87\xac\xb2g\xa3\x17\x00@9^\x94d\xa7?\x93\xd3\
-\xc0\xe4\xac\xb8\x13B\xd2\x0cPiTrj\xdako\x8cF\xa3\x19\x00JU\xd4\x14\x00\x18\
-\x1fa\xb6\x9f\x1ak\xd7R\xe3."\xef\xb564\xfc\xb10<\xfb\xe30&R\xdc\x95\xfb%U\
-\xf6\xac[\x06zR\xdeo\xbc\xa7\x06x\x1a\x9f\x00\x99\x99 -\xe2A2\xbbK\xf4.\x12\
-\x8dF\x93\x83wVQ$\xcal\x97r\xb0\xd9n\x17j\x967\xa0\x02W\x0e\x994y\xe7\xb1z="\
-\xfc\xd1\xf2\xa7\xed\xa9]\'\x1e\x9fHq\x87\x9e\x04C\xa6\x82h\xaa\xc2\xca\xe7N\
-3E\xd3\xd3\x8f\xa4.\x97tA5\x15\xf7t\xbf\t\xf4"\x1f\xd2\xc3\xb4\xbek4\x9a.(\
-\xd2 \x99\x91\xf9\x1a\xc7\xaa\xb5\x944n\xa79m\x87\xa7\x91\x19\xb7\xb5\x1c\
-\x93*\xee\xd0\xd3\xf7\xdc\xde?\x85\xb2\xc4\xb3\x11\x0b\xb9\xc8\x84\xac^\x0f\
-\x14\xf7\\t\x9aF\xa3\xd1\x00\x00k=P\xb1\xedB\xa0\x1c\x96\xafq\xbcZK"\\S\xc5\
-\xaf\x01\x80\x8f\xd8\x03U:f:\xc5\xa7\xb8\xd4I\x15we\xb0\x0f\x13\xdf\xac\x15\
-\x9f}3\xf7b\xd8\xc1::M\xa3\xd1\xf4\xb3U\x91cd\xb9jo\x9cZK(\x93t\xd7\x12\x02\
-\x13\x83\x8f"\x86\xedUO>\xcd\xb5N\xae\xb8\xc3N\xb2\xbb+Q\xd6\n\xae\xd1hv\x84\
-\xb7V\x94\xb7\x9d\x8b\xa1a\xae\xdex\xb5\x96x\xeb\x01\xf2N\xf7\xf5\xf0\xf0G\
-\xbf\xfa\n5vnm \x93Z\xacC\xa3\xd1h\xf6\x13\x14qj\xb6\xf3!f;5]w\xacZK\xc8z5\
-\xf9F\x84?\x1av\xc1)\x1d\x7f\xba\xab\x05-\xee\x1a\x8dF3\x0e\xacy\x1f\xa5\x00\
-\x022\x9fQb\x0bo\xbcd\x03"|\x82\xa2[\x8bQ\xad\xca\x0e\xa40sv\x9c=P\xc3\xd0\
-\xe2\xae\xd1h4; y\'\x8dYT\xb1\xed\xfd\x18\xce\x94[<\xbas[(\xd3\x1caB\x0e]\
-\x95\xb5\n\xb3\x96?\xfdtW\xab\xd0\xe2\xae\xd1h4;\xc0\x82{\xaa\x90\x9e\x10Cc\
-\xdb\xc7\xdc\xb5\xc4[\x0fd\xeam\xe7C\x0e"\xb40\xb2\xf8\xf58hq\xd7h4\x9aQ\xc8\
-$\xe8fZ\x1f^\x91\xc3\xf2\xa7\xc7J6 \x19k\xdeS\xafU\xf8\xe3@\xbc\xf2\xfc8\xc1\
-\x94\xa3\xd1\xe2\xae\xd1h4\xa3`\xc1\xfdn!=\x89"_\x8c\xb9\x8b?N\xf8#\x00\x0b\
-\xee\xa2\xe4\xa0j8\x0f\t\x7f\xa4\xa6\xe3U\xc7\xaa\xdc4\x1a-\xee\x1a\x8dF3\
-\x14\x11m\x88h\x1d\x94\x1c\xf3\xc1\x0e\x19\xa7xd\x9c4\xeb\x92\xb5y\xfb\x91z=\
-\xcal\xaf\x8eUEoG\xb4\xb8k4\x1a\xcdP\xba^\x14\xd2\xad\xa1\xd1\x0f\xa1\xc6X\
-\xbb\x96\x00X\xe3\x0e\xa0\xaa\xa9;\xaa\x8a\xdeX\xab\xb2c\xa0\xc5]\xa3\xd1h\
-\x06#\xc2\xba\x8c\x83\xb4\xfe\xf5\x10\xb3\xfd\x98ay;7\x15m\x88\xe8I\xf7\xb5\
-\x1c\x12LIHa\xe6\xec\x98U\xf4vD\x8b\xbbF\xa3\xd1\x0c\x04\xd3\xfa\xd7\xc3\xbc\
-(\xc4\xb0\xc7\xf3\x8f#\x0b\xb6\x92\r\x0c\xf3\xb6\xdb\x85\x9a\xe9\x96\x9f\xe6\
-J\x07\xa1\xc5]\xa3\xd1h\x06\xc0\xdb\x8f$k\xab\xfa\xd7\xc3\xccv\xaf2?Nz\x00\
-\xde~$\x93f\xf7\xf5P\xf7\x8eY\x989\xfb,\x17\x9cC\x8b\xbbF\xa3\xd1\xe4A\xc9X\
-\xb3k\xb63\x0e\x03\xbd(\x86]pK;\'\x1b@\x91\xb0\xe0\xaez="\xd9\x80W=IM\xe7)/w\
-\x10Z\xdc5\x1a\x8d&\x0f\x0b\xee"\x8f\xd5:\xea\xe0Bz\x84L\xd5^\x1b\xa7:R\xd2\
-\xb8\x85"\xe96;$\xd9\x80\xe5U\xbc\xca\xc23]q\x1fZ\xdc5\x1a\x8df\x1b\x92wx\
-\xfb1@w\xd7\xd2@9\xb6\xfd\xd9\xb1\xc2\x1f\xe3\x86\xe8\xac\x02\x00\x01\xe0bH\
-\x90\x0c!\xfesu\xc8(\xb4\xb8k4\x1a\xcd6XpO\xc5,\xf2a\xb9_\xc6\xad\x8e\x84i\
-\xd2v\tC\xe3m\xdc\xe2S\x96\xe3\x18\x8d\x16w\x8dF\xa3\xd9b+\xd9\x00\x0cM\xed\
-\xeb\x96\x8e\x8fUk\xa9S\x97I\x03\x94\xd9\xcea\xd8:\xea\x98a\xf2\xbbE\x8b\xbb\
-F\xa3\xd1l\xc1\x82{j\xfdt\xe8&RB\xdcq\xd2\xac\xa3Hz\xe1\x8frx\x98\xbcW=9Nq\
-\x8f\xa7@\x8b\xbbF\xa3\xd1t\x11\xf1\xa6\x886\x00\x00\x87{Q,\xaf:\x8e\xd9\xce\
-Z+\xc8{I\xdb\xc5\x90,\xc1\xb6?^q\x8f\xa7A\x8b\xbbF\xa3\xd1tI3\xad\x8f0\xdb\
-\xc7\xf1\xb6\xa3\x88ys\t\x00\x08\x80\x9408\xde\x06\xc0\x9f~\xa6r\x1c\xa3\xd1\
-\xe2\xae\xd1h4\x00\x00\xbc\xb3\xaa\xb6\x1a\xa9]K\x03\x8fq\xa6\x8e\x8c\xb3\
-\xf8\xc9\x82{\xdd\xec\x8f\x00lX\xbcMa\xd6.\xcc<\xcb\x05\x8fF\x8b\xbbF\xa3\
-\xd1\x00\x00*[\x1b\x00\xb8\x80\x81\x15\xab\xc7\xcc\x11&Y\x8bw\x1e\x81ZG\x95\
-\xf0l\xf16O\xcfsH,\xa9\xd1h4\x93N7\xd9\x00\x80\x1c\x9el\xc0)\x1d7,wT#\\p.dp\
-\x97\xa0\x04 8"\xde\xa6|\xc2\xb0\x0b\xcf\xe1\xba\x87\xa3\xc5]\xa3\xd1\xbc\
-\xf4\xa0H\x93\rp\x01\x08\x83\xccv\xc3\xf6+Cs\x84\t\xce\xe3(\x899!l\xcdN\x9e \
-\x10\x02\xc0\x04\xc8A\xbf\x00\x80\x90q\xf2\x16<#\xda-\xa3\xd1h^vXk\x05y\x0c\
-\x00\x12\x87$\x1b\x00\xf0*\x0b\xc4\xb0\x06~\\\n\xb9Y\xafGq\xe8{\xe8\xc3c@IF\
-\x86?\xda\x85\xda\xe8_\x00\xcf\x05-\xee\x1a\x8d\xe6\xa5\x06%\xe7\xad\x15\xf5\
-\x9a\xf1\xc11\x8b\xd4rG\xc4\xb6wZ\xad\xf5\xfa\xaa\x8c\xb8\xc16x\x14\x00\xa1\
-\xa0\xc2\x1f\x87\x14\xf7\xf0\x9f\xb9\xf8\xf58h\xb7\x8cF\xa3y\xa9a\xcd%\x14\
-\x0c\x00\x84D!\x07\xdb\xda\x85\xe9\xb3#r\x84!`\xb9:\x8b<\x8a7\x97\x08\xa1\
-\x04@\x0c\x0f\x7ft\xcb\x0b\xfb`\xb6\x83\xb6\xdc5\x1a\xcd\xcb\x0c\x8a\x98\xb7\
-\x1fB/fq\xe01\xa6[\xb2\xa7j#\x1a1\r\xd3-\xcd8f\x8b`\xac\xd6Q\x87\x85?R\xcb}\
-\xee\xd9\x1f\x87^\xd5\xfe\x9cF\xa3\xd1h\x0e ,\xb8\x07\x92wK\xa4\x0e\xa9k::f1\
-l\xb5\xe3\x04\x817l\\\x070\x08\x00\x178\xacD\xea\xe8_\x00\xcf\x17-\xee\x1a\
-\x8d\xe6%E\xf2\x0e\xef\xac\x82*}7$f\xd1.\xd4,\xaf:\xac\x858\x8a;\x9d\x18\x81\
-:r\x8d C0$\x02\x1bR\x8e\xc3\xf2\xaa\xa3\x7f\x01<_\xb4\xb8k4\x9a\x97\x14\xd6\
-\xb8\x0b(\x81\x80\xe08,f\xd1\xab\x9e\x1c\xfca\x84v\xab\xcd\x12&\xc1\xb0ih$u\
-\x04J\x080\x8eBH\xa2\xd6e\t\xd9\xca.@\x88?\xb3\x1f\xeb\xa8)Z\xdc5\x1a\xcd\
-\xcb\x88L\x02\x11=\x81n\xc5\xea!\xc9\x06\x8a\xc7Lgj\xc0\x1f\x10\xda\xadv\x14\
-\xc6\xc4\xb0\x0c*\xccd\x85 "\xa1j\x03\x94\xe5\x95-\xafBA\xb2\xb8\xc9\xa2@\
-\xe9\xbcS<\xba\x17I\xdbG\xa0\xc5]\xa3\xd1\xbc\x8c$\xc1]\x95d\x80\x0f1\xdb\t5\
-\xfd!f{\xb3\x11\xc41\xa3\xa6%\x05\xb7I\x83\x88\x06\x12\x83\x00\xb4#\xe9\x94^\
-)\x1d9\x0b@\x00c\x0fxg\xed~\xd8xDL\xdb\xaf\xeem\xb2\x81~t\xb4\x8cF\xa3y\xe9\
-\x10\xd1\x86\x8c6\x81\x8c2\xdb\xdd\xf2\xfc\xc0\x8a\xd5a\xbb\x13F\xcc0-!\xb8e\
-Q\x8b=\x02B\x08@\xc2\x91\t\x07\xd0\xdf\x9a)\x088~\x15\xd0\xf2\xab\xaf\xecQ\
-\xd2\xf6\x11h\xcb]\xa3\xd1\xbcl \x0b\xee\xaaW\x9c\xe3\xe0\x1ca\xa6\xe3U\xe6\
-\xfb?\xd9\x0e\x9aQ\x94\x98\xa6\xc19\xb7\x1d\xcfL\xee\x81\x0c\tP\x00h\xc7H\
-\xa9\xddn\x85^\xc2\x12\x01q\xc8\x00\x19\x01#\xa633\xa5\x01M\xed5\xdar\xd7h4/\
-\x17\xbcS\x97I\x13\x08H9\xd4l\xf7+\xaf\x10\x9a\xb7}[\x8df\x181BM)\xd1\xb2l\
-\x13\x03\xc2\xd6\x10\x08\x10\x888\xc4LJ\xe4\xa6c\xb5;a\x1cE\x12%\x97T\x80\
-\xe3\xcd\x9c\x88\xc2h\xef\xbb\x95G[\xee\x1a\x8d\xe6\xe5"M6\xc0\x05\x0e\xcc\
-\x11f\xd8\x05\xa7t,\xf7fs\xa3\x113i\x18\x94si\xdb\x14y\xcc\xc2\x15\x872\x00\
-\x8a\x08\xedP\x98\xa6e\xf93q"\xa2\x8du@\x04@\x02\x84PJ\x08mq6\x7f\xe64!\x03#\
-$\xf7\n-\xee\x1a\x8d\xe6%B&M\xc9Z\xb0C\x81\xa4\xd3\xb9\x02I\xc1F#N\xa4eR\t\
-\xc4\xb2@\xb2$\x0e7\x8a\xb4\t@\x81@\'\x92\x12\x8c\xf2\xec\xaba\x9b\xb7[\x1b\
-\x94\x1a@\x00\x94G\x1f\xa0[\x915a\x96\xb3\xafnw-\xee\x1a\x8d\xe6%\x825\x97\
-\x10\x91\x000>\xd8l7\xdd\xb2]\x98M\xff\x89R\x06\x9b\x01\x13\xc4\xa0\xc8$\xd8\
-&P\x14\x89\xe4\x164\x08\x05\x00\xc2\x85\x88\xb9Q\x9a9\x9b0\x82\x86e{\x9e\x14\
-R}\xb4\xd7\x04\x18\xa6aX\xfb-\xb6Z\xdc5\x1a\xcd\xcb\x02\x0f\xeb<\\#\x00|X\
-\x8e0j\x14f_\xdd:\x9e\xb1v\xb3\xc38\x02H\xa4\x86c\x12\x1e\x85\x1c\xc0\xb2\
-\x11\x92\x0e\x01@\x94\x9c\x94\xfc\xca\xd1(\x01!\x85\xe3\xfa\xaec\xb6:m!$H\
-\xd9m\x92\xd2\x99Z\x8d\xd2\xfd^\xe0\xd4\xe2\xae\xd1h^\x0eP\xb0\xc6]\x00\x18U\
- \xa9t<\xdd\xb5\x94Dq\xb3\xd1"\x86E\x08P\xc3\xb6\x0c\x19wZ\x86\xe5\x94\xa6+\
-\xebK\xbf,\x98\x08\x88`\xcdPz\xa4\x13K!\x98\xe3\x97l\xdb\x8c\xdb\x9b \x18\
-\xa1\xc4v=\x02`\xdb\xceT\xa9h\xdb\xfb\x1d\x07\tZ\xdc5\x1a\xcdK\x02k?\x92<$\
-\x00L\x0c\xd9\xb5d:i\xad\xa5\xa8\x13\x86a\x8c\x84\xa0\xe4\xd4\xb4M\xc2\xe3v\
-\xc7t\xddR\xb5\x1a\xb6\xd6\xa9\xe8\x10\x13\xd0=\x96\xc8J\x94\xa0\x14\xcc/V)\
-\xf0\xa4\x1dl>yD1\xa8\xce\xccU\x8e\x9c"\x94\xee\xf3"j\x16\x1d\n\xa9\xd1h\x0e\
-?(9k.\x03\x00\x02\x88\xa1\xe1\x8f\xddZKQ\'l\xb5"\x94\x80\x08\xd4\xb0lC\x84\
-\xed\x96\xe9\xb8\xe5\xe9i\x00X\x7f\xf4\xcf\x96\x89\xd2\x99O`\xb6\x13\x0b!\
-\xb8_\x9c\x06\x99\x88\xa8\xb3^\xbfo@\xb3\xe0\x19\xe5\xb9\xd3\xd40^\xa0\xb2\
-\x83\xb6\xdc5\x1a\xcd\xcb\x00o-\xa3\x88\x95\xb7}\xa0\xd9nX\xaeS:\x0e\x00a;l\
-\xb7BjP!\xa4i\x9a&\xe1\x9df\xdb\xf5\xfd\xd2\xf44\x00l\xae-\x9b\x18R\xef\x95\
-\x04*\xedNH\r\xcb\xf3\xa6PD\x98\x84O\x9e\xac\x984,8X\x9c\x997,\x7f\xdf\xbb\
-\x98G\x8b\xbbF\xa39\xe4\xa0d\xac\xf5\x00Fz\xdb\xbd\xe93\x84\xd0v\xb3\x1dE\
-\x8cR\x82HL\xd3\xb4M\xe8\xb4:n\xa1P\xaaV\x01@\xf0$X\xbb[,\x1d\x13\xc6L\xd8j\
-\x98N\xd12M\xc0\x84G\xed`\xf3\x81I\xc2\xa2\x07\xa6\xedy\xc3\xebh\xef\'Z\xdc5\
-\x1a\xcd!\x87\x05\xf7Qr\x00\x10C\xbc\xed\x96[r\xa6\xe6Z\x8dV\x14s\x02B\x12\
-\x93\x12\xb0\x0cl\x05\x81_,\x16+\x15u\xd8\xfa\xe3\xbb~\xf1\x88\xe1\xcct:M\
-\xd3\xab\x82\xe4\x06\xc1vc\xbd\xdd\\5HX\xf6)\x00\xf8\xd5\xd3\xfd[[_\x08\x07\
-\xe2"4\x1a\x8df\x8f\x90<d\xed\x87\x00\x08H\x06\'\x1b \xe0N\x9fk5Z\x9d01(\x02\
-5)J\x83@\xd8iOU*\x85b7Oo\x1c\xb5)1M\xaf\x9a\x84M\xc3.\xa1H\\\xc7j\xae?\x0e\
-\xdbk&\r\xcb\xbe\x81\x08\xa6[v\x8aG\xf6\xb5{\xc3\xd1\xe2\xae\xd1h\x0e3,\xb8\
-\x07R\x00\x19\xe6m\x97V\xe1x\x1ca\xcc\x84a\x10 &%\x92\x00&Qg\xaa2\xed\x15\
-\xba\xaesD\x8c\xda\x1d\xcb\xadD\x9d\x80\xd8\x15@\xee\xdafc\xedA\x1c\xae[F\\\
-\xf4\x0c\xc4\x17P\x8ec4Z\xdc5\x1a\xcd\xa1E\xb2\x16\xef\xd4ahE\x0e\x04bJZ\x91\
-\x92\x12\xc2\t5(H\x90"\x89\xda\xe5\xd99\xc7s\xbb\x07I\x99D\xb1\x14$J"\xcb\
-\xa9H\xc1\x1d\x8bl\xd4W\x92\xe8\x89ks\xdf\xa1*\xcb\x8035g\xb9\xe5\xfd\xec\
-\xddh\xb4\xb8k4\x9aCK\xd2\xb8\x07\x80@\x88\x18\x98\xda\x17%\xda5I\n(\x05\xa5\
-\x06\xa5(9\xe3I4}\xf4\x98iY\xea\x10)e\xd8\xea\x84\x9d&\xe3\xc4\xb4\\\x94\xc2\
-s\xcc\xc6\xdaJ\x14\xd6\xa7<tM\xda-\xa8GM\x7f\xfa\x00\x99\xed\xa0\xc5]\xa3\
-\xd1\x1cVD\xbc\xd9+\xa4\x87\x03\xcdv\xa4\x8e\xe9\x1d\x13\x80\x84\x10\xc3\x00\
-\x91\xc4\x9c%\xd3G\x8f\x1b\x86\xd1m\x81\xf3\xa8\x1dFaK\xa0i\x98\x065\x0c\x93\
-\xca\xcd\xb5\x87a\xa7^\xf6\xd12\xb6&\x0b\xaf\xb20\xb0\xb2\xc7\x0bDob\xd2h4\
-\x87\x13\x16\xdc\x03\x00\x02\xc0\x05\x0c\xca\x11\x86\x86w\x84\x83\rRX\x16\
-\x15I\xc8YR;~bK\xd9\x19\x8f\xda\x91\x94L\x10\x07\x91X\xb6E\x91\xb7\xd7\x1f\
-\x85\xed\xc7EWf\x95\x9dZ\x9e[Y\xd8\xafn\x8d\x8b\xb6\xdc5\x1a\xcd!D\x84k"n\
-\x00\x80\xc4\x81\xa9}\x11\x89+\x8d\x19\x90\xdcv,\x1e\xb58\x17\xb5\x13[\xf5\
-\x92$\x17Q\'\xe4\x8c\t0\xa4H<\xcf\xe3q;j\xae\xb5\xda\xf5\x92\x8f\xb6\xb1\xad\
-\xbd\xc2\xcc\xb9\\\x8a\xe0\x83\x80\x16w\x8dFs\xf8\xc0$\xb8\xaf^\r\xac\xc8\
-\x81\x88\xc4;\n\xd4pL\x83\x85\x81DR;q"\xfd+OX\x14F\x9cq\tF\xc2\x98\xefyq\xd4\
-\x8a\x9b\xab\xed\xce\x93\xb2\x0f\xd6ve\xb7\xbc\xaa]\x98\xd9\xf3\x0e\xed\x1e-\
-\xee\x1a\x8d\xe6\xb0\xc1\xdb\x8f%k\x11\x001\xc8lG\x94\xc4*\x12\xbbj\x1a4\xe9\
-lR\xcb\x99\xad\xd5\xd2\xbf\xb2(N\x92\x841.\x91r\xc1]\xc7\x8e;A\xd4\\M\xd8f\
-\xc5\x07s\xbb\xb2\x03!\xfe\xcc\xd9\xfd\xe8\xd2\xee\xd1\xe2\xae\xd1h\x0e\x17(\
-Ys\tz\xc9\x06\x06x\xdb\t!\xce\x11\x83\x1a,l\x18\xb6[\x99\xdd*\xcd\xc1\xe2\
-\xb8\xddj!1\x04\x12D\xe99\x16\x0b[\xed\xe0\x91\xe0\xc1\x94\x8b\x96Ap\xfb\xba\
-l6E\xf0AC\x8b\xbbF\xa39T\xb0\xd6\x03\x95\xdaW\x0c\xae\xc8!\xa9U1\xac2\x0f\
-\x03\xcb\xef&\x8dQ\x84\xadN\x12E\x02\r)%%\xd40\x08\x0b\x9b\x9b\xeb\x0fQ\xb6\
-\x8a\xde\x00e\'\x86\xe5UO\xee}\x87\x9e\x92\x03\xb7\x08\xa0\xd1h4O\rJ\xc6Z\
-\xdd\xd4\xbe\x8c\x0f8@"!N-\t\x9b\xceTiK\xd9\x11\x92(N\xa2\x88#\xe5\\\x98\x86\
-I@\xca\xa8\xf9\xa4\xbe\x1c%\xad)\x17\xfa\x95\x1d\x00\xfc\xea)j\xbc\x80*\x1cc\
-\xa2-w\x8dFsx`M\x95\xda\x97p\x81\x12\xf3f\xbbD\xe4\xe0\x13N\n\x95\x8a?\xd5s\
-\xa7 F\x9d(\x8a"\t\x06\xe7\xdc\xb6-\x94\\F\xcdf\xf0\xb0\x13\x87\xd3S\xc46\
-\xa1_\xd9\rg\xca)\x1d\xdb\x87\x1e=5Z\xdc5\x1a\xcd!\x01E\xc2\xdb\x0fa\xe0\xae\
-%\x02RB\x98\xa0\xe5NMM\xcf\xb8\x9e\xd7\xfd\x08B\xdc\xeeDq\xcc\x05\xe5\x82;\
-\xb6)\x05\xc3\xb8\xc9\xa2\x87\xed(\xf2l\xe2;\x03\x94\x1d\x00\n\xd3g\x0e`\xf8\
-c\x16-\xee\x1a\x8d\xe6\x90\xc0\x9a\xf7Q0 \xc0%l\xcb\x11F@H\x88\x12D\xe2NM\
-\xcfo)\xbb\xc4\xa8\xdd\x89\xe3X \x15R:\xb6\x89R\xc8\xb8)\xa2\xc7(#\xcb$S\xee\
-\x80\xfc\xc0\x00`\x17j\x96?\xbd/}zz\xb4\xb8k4\x9a\xc3\x80\xe4\x1d\xd6~\x04\
-\xa4/G\x18\x01! b\x88\x08\xb3\xf3\xe7\x0b\xa5nn/)e\xdcnG1\x13\x12\xa4\x94&\
-\x05\x14\x9c\x88\x90B\x13H\x87Ib\x19`\x1b\x83\xcf\xe5U\x0fD9\x8e\xd1\x1c\xe8\
-\x9f\x15\x1a\x8dF3&,\xb8\x07(\x00@\x08\xd8\xca\x11F\x80\x0b\x08\x19"\x82_\
-\x9c)\x94\xba+\xa8R\x88N#\x88b\xc1%A\xa0\x86\x01\x049\x05\xe6\x16\\\xc9\x1a\
-\x96I\x85\x04\xdb\x00c\x90@\x9an\xd9\xb4\x0fh\xf8c\x16-\xee\x1a\x8df\xe2\x91\
-I\x93w\xd6\x00\x08"p\xb9e\xb63\x0e\x11C@ \x94\x94k\xa7\xbb\x07\x0b\x19\xb5ZL\
-\x12\xc6%\x01B\x89$R\x10\x10\xa5\xe9\xe9\xa4\xb3\xea\x9a\x02\x11\x84\x04\xd7\
-\x1e\xe4mWI\xdb_h\xe5\xeb1\xd1\xe2\xae\xd1h&\x9e$\xb8\x07\x80\xa0r\x84\xf5\
-\xcc\xf6D@\xccQ\xe5\x0c\xf3\x8bs\xb6W\x04\x00\x14"l\xb5\xa3D&\x8c\x9b\x86A@\
-\x10\xc1\x00dy\xb6\xc6Y\x08q\xdd4H\x98\x80k\r\x16pg\xea\xc8\x81J\xda>\x02\
-\xeds\xd7h4\x93\x8d\x88\x9e\x88h\x1d\x00\x101M6\x90pHT-l\x02\x94\x9a\x95\xb9\
-3\x00 8\x0f\x9b-&\x80\t4\r\x03@\x12\xc9,\x8bLUg\x81\x90h\xfd7\xb6I\xd4_\x0b\
-\x8e\x9a,\xb6A\x0c\xdb\x9f>\xbd\xaf}{\x06\xb4\xb8k4\x9a\x89\x06\x93\xcd[\xea\
-\x95\xda\xb5\x84\x00Q\x02"\r\x97A\xa8\x1c9k\xda\xaeH\x92v\xab\x13s\x90B8\xb6\
-)\x04c\x9dV\xb5V\xb5\x0be\x00H6o\x99\x10\x02@\'F\xd7\x1a\x1c$3U\xfb\xad\x83\
-\x96\xb4}\x04Z\xdc5\x1a\xcdD\xc2\x19\x8f\x19B\xbcJX\x9b\x10\x83\x0b\x94\x12%\
-B\xc4@f\x02!-\xc7/V\x8fK\xa5\xec\x0c\xa5\x94\x96e\x08\xc1\x08\xebL\x1f\x99\
-\xb1\xbc"\x00H\xde\x11\xed\x87\x00 $\x18\x04\xe8 i\xb7\xbc\xaa]\x98\x1d\xf0\
-\x87\x83\x8a\x16w\x8dF3a b\xb0\xd9\xe0`ZT\xd2\xce\x12\x10\n\x00\xa2\xab\xec(\
-e\x9a*\x8c\xa0\x14\xb3\xc7\xcf\x8b8j\xb7\xa3\x98\xa1\x90\xc26\r\x94\x02X\xa7\
-R\x9b\xa5v\xb7\xfe5\x0f\xee!JBT\x0c\xe5\xa0=K\x07\xac\xf8\xf58\xe8\x05U\x8dF\
-3al\xae=\xd9\xac\xaf9&\xf5\xc8:\x91!\x01"\x042\x0ea\x92Uv@)\xa6\xcaG\x08\x90\
-\xa0\x19\xc6\x1c\x84\x94\x8ee"J"\xc2J\xad\x96*\xbb\x887\xd3"\xda\x88\x83\xd7\
-Q\xdd\xd2q\xd3)\xeeS\xf7\x9e\x13Z\xdc5\x1a\xcd\x84\xd1i\xb7\x81K\x8c[\xbc\
-\xfd\x08\x80"@\xccPmS\xca(;\x1a\xa6\xe5\x17\xe7\xda\x11p\x01(\xa5cQ)\xb9\x88\
-\x9a\x95\xd9Y\xc3\xf6z\x8d!k\xdc\x01\x00B@\xe2\xc0,\x92\x07=\xfb\xe30\xb4\
-\xb8k4\x9a\t\xc3/\x95\xc04\x93\xe6}\x14\x11\xa1$f\xd8Ir\xca.M\xd3\xa8\xce\
-\x9dK\x84\xc3\x18\x07\x00\xdb\xa2\x823L\xc2\xea\xdc\x1c\xb5\xdc\xb4)\xdeY\
-\x95I\x13\x00\x10\x81\xf1A\x0e\x19\x00\xbfz\xf2 g\x7f\x1c\x86\xf6\xb9k4\x9a\
-\t\xa3\\.\xf1\xce\xa6-\xdb\x00THhG\x98u\xa7\xa0\x94\x96\xe5\x94f\xcf&\xd2\
-\xe7\\PJL\x83\xf0$F\x11\xcf\x1c\xd9\xa6\xec(9\x0b\xee\x01 !\x84\t\x14r@\x90\
-\x8ca\x17\x9c\xd2\xf1}\xea\xd8sE\x8b\xbbF\xa3\x990\xa8a\x14\xbdX\x84\x00\x04\
-\xc2H\n\x89Ye\xb7\x1d\xbf<{\xb6\xc3,\x004\x0cJ\x08J\x96\xa0d3G\x8f\xe4\x02\
-\x19yk\x19y\x08*\x15\xf0\x10\xb3\xbd0s\xf6\x80g\x7f\x1c\x86\x16w\x8dF3a\xc8\
-\xa4)\xc2:\x01\xc29F\xc96ew\xbdbq\xfaL\xc4\x0c)\xa4e\x99\x94"\x8f"\xd3\xc0Rm\
-\x8e\x9a\xdb\\+(b\xd6ZQ\xaf\xb9\xd8\x9eE\xb2\x87U\x98=\xf8\xd9\x1f\x87\xa1\
-\xc5]\xa3\xd1L\x18I\xe3.\xa0\x04\x02\x9d\x18\xa5\x94\x00@\x08E\x94\xae_\xf6\
-\xcb\';\x89!\xa54\r\n x\x18\x19&\x94ks\x84\xe6\xb5.i\xdc\x01\xc9\x01\x00\x11\
-\x06\x9b\xed\x84\x16&g?j?Z\xdc5\x1a\xcd$!\xa2\r\x11\xad\x03\x81\x84c\xc2\xc1\
-v\xa7\x00\x80\xb3\xd0\xf7g\xbc\xd2+\x9d\x98 \n\xdb\xb6\x00%\x8f:\x8ek\x17\
-\xa7g\t\xcd\xfbUd\xd2\x14\xe1\xaaz\xcd\x04\x0c(\xa2\r\xe0\x95\xe7\r\xbb\xb0\
-\xe7\xfd\xd93\xb4\xb8k4\x9aI\x82\xb7\x1f\x01\x00 rt\xe7N~\xc5\xf1K\x92\x85I\
-\xd8N\x18\xed\xc4\x84\x80\xb4\x1d\x0b\xa5\xe0a\xc7\x9br\x0b\x95\x192(p=i\xdc\
-V)\x1f\xa5\x844\x1dM\x16b:^ea\xaf\xfb\xb2\xa7L\xe4B\x81F\xa3y9A\x1e\xf1h\x03\
-\x080A\x98\xa8\x18v\x89\x10\x8a`p\xe9\xc6\xdc\x00\x90\x06E\x94\x82\x85-\xbf\
-\xe8OU\x07+\xbb\x08\xd7d\xdcP\xc6:\x13\x03\x8b\xe8\x81_=E\x0ck\x8f{\xb3\xb7h\
-\xcb]\xa3\xd1L\x0cIp\x1f$C\xc0(1\x98$Rpi\xd8\x8c\xa1@b\x9a\x00\x12\x11\x08o7\
-\xa7\xa6+~qhb^\xd6\\\x02\xe8\xd6\xf1\x18\x18\xfeh\xba%\xb7xt\x0f\xbb\xb1/hq\
-\xd7h4\x93\x81d-\xdey\x0c\x04\x92\x04b.\t\x91,a\x89 I$\x01\x89I a\x8c \xab\
-\x1e\x99\xb1\xdc\xa1\x95\x92d\xd2\x94\xac\x05#\xd6Q\x01\xfc\xe9\xc9(\xc71\
-\x1a-\xee\x1a\x8df2H\x82\xfb\x80\x12\x01:\t\xa2\x04\xbb\xe8%\x9c#\x17\x02%\
-\x01\x90\xd4\xa0\x14*\xd3\xb3\x86\xe3\x8fj\xa4qG\x95\xf3\xe0\x1c\x07\x86?:\
-\xc5#\x96W\xd9\xbb^\xec\x1bZ\xdc5\x1a\xcd\x04 \xe2@t\xd6\x80@\x9c \x17(\xc1n\
-\xb7\xc28f\x00@\t\x01\x00B\r@Q\x99\x9d\x19\xd5H\xb8&\x93\xcd\x01E\xb4{\x10jz\
-\xd5\t\x0e\x7f\xcc\xa2\xc5]\xa3\xd1L\x00,\xb8\x87\x80\x04\x81\x0bp-`\xe8D\
-\x9cp\xc6\x00\x08\x00\x02\x02\x02 "\x8f\xda\x865$\x0f\x0c\x8a$\xb8\x03\x00\
-\x04 \xe1\x88\x83\xccv\xb7\xb2`d\xf2\x13L4Z\xdc5\x1a\xcdA\x87\x87Ox\xb4NT1\r\
-\n\x14\x88A\x84\xb4\x1c\xc1\x85Rv\x00D$\xa6IL\xc7\x1b\xdaH\xfb\xa1J6 \x86\
-\x84?R\xcb\xf3\xca\'\xf6\xb0\x1b\xfb\x8b\x16w\x8dFs\xc0A\x16\xdc\x07\x00\x04\
-\x10\x12\x01\x00\x81P\xec\x14\xdd\x16\xf7\xa7\xe3XJ)\x00\xc0\xa0dv\xa6l\xd8\
-\x83\xedn\x14I7H\x06\x80q\x1c\xb8k\xc9\x9f>\xdd\xbf\x91ur9<=\xd1h4\x87\x12\
-\xdeY\x15I\xa0\xcc\xf64r\x11\x81@\xb4\xe6\xb8\xb1\xed\x14,\xff\x88a\x9a\x96\
-\xed\x1a\xe6\xd0\xc4\xbc\xac\xb9\x84\x92\x01\x00"\xc8AI\xdbM\xaf\xe2L\xcd\
-\xedY\'^\x00Z\xdc5\x1a\xcd\xc1\x05Q$=\xb3=\xbb\x04J\x00\x00\x08\x0b\x9b\x86\
-\xc5\nG\xbf2\xc8\x10O\x9b\x10\xc8\x1a\xbc\xf5@\x1d2\xd8l\'\xa40s\xf6\xf9_\
-\xfd\x0bE\x8b\xbbF\xa39\xb8\xf0\xf6c\xc9:\x04\x80\xcb\x01\x89\x1bI\xb7\xb4\
-\xe9 e\x97\x11\xf0\x16\xf2f\x126d\xd2V\x95Q\xa5\xc4\x81\xdev\xa7xl\xe2\xaa\
-\xe8\xed\x88\x16w\x8dFs@A\xc9\x93`\t\x94\xb7}P\xe4\xa2\xe1\x96\x9d\xa9#\x99\
-\x0f\x08\x90!\xf0\xa6H\x828lFa\x12\xc7\x9c\x0bY\x9d"\x94\x12\xc0\xc1f;1,\x7f\
-\xfa\xd4^\xf6\xe3\xc5\xa0\xc5]\xa3\xd1\x1cPXk\x05ED\x86\xe5[\'\xa4X{\x15\x00\
-@\xc6 \xdb\x984X\xdc\x8a:\xed8\xe6I"\xb8\x90\x88\x80\x00\x96A(%\x00\xc0\xe5\
-\xe0ZK^\xe5\x95I\xac\xa2\xb7#Z\xdc5\x1a\xcdA\x04\x05c\xcd\x07\xa0\xf2\x04\
-\xc8\x01f\xbb\xedM\x89$\x88\x9bK \xdaQ\x98\xc4\t\xe7L\xaap\x1a @\x00(\x05&\
-\xc04\t\x81\xee\xae\xa5AU\xf4|\xf7\x10\x85?f\xd1\xe2\xae\xd1h\x0e"I\xe3\x0e\
-\xca\x04\x00\x98\xec\xdbpD\x80\x0bx\xfc\xa8\x15/\xfd\xa6\xe2\x83AQ\xca\xae\
-\xa0g+\xe2I\x04!\xc11\x11\x10"\x06q\x02\x84\x82A\x00\x00\x10\x81R \x84\x16f_\
-\x9d\xd0*z;\xa2\xc5]\xa3\xd1\x1c8D\xdc`\xedG\x00 %H\xd1\xe7%\x07Xo\xe1Z\x83\
-\xcf\x96\xa8A\t\xe26MOw5%\x82X\x16\xf5|\xdb4\r\xb4\x90\x18\xdc\xa0@\xa4P\xda\
-\x1e3\x191R\xb1&\xb8\x1c\xc7h\xb4\xb8k4\x9a\x83\x06&\x8d\xbb\xdd\xf4^"\xbf\
-\x04J\x08D\x0c6[\xd2wh\xc1%\x08\x90\x86\xbe#\x02\x00\x12J-\xd3p\x1c\xd3\xb6\
-\r\xc7\xa2\x04E\x1c1\xce\x05H\xc9\x19"\x10B\xc00\xa4c\x92\xa0\xc3\x19\x8bGD\
-\xc7O4Z\xdc5\x1a\xcd\x81A4A6y\xb8!\xa2M \x06\x85?\x02\xc0zS\n\t\xd3\x1e\xa1\
-\x04$ve\xdd0\xa9\xe3\x98\x8em\xda\xb6a\x10\x94R\xf0$\xe9DBH\x04D \x04\x89\
-\x8d\x04(2@\xc98\x98\x14\x0b.\x11,\x06\xef\xb0\x05A*\xb4\xb8k4\x9a\x03\x80dl\
-\xe3\xd7\xedN\x10\'hSn\x99\x04p@\xf8#!\xd0\x89\xb1\xd1A\xdf%\xae\rB\x80D\xb0\
-mZ\xa9\xf8\xb6I@J\xcey\x12&\x82\x0b)\xd5\x07\x80\x00Jb\x81?o8\x15!\x90b\x88\
-\xed;D&\x02\x89A\x80\xb3x\xff\xfb\xba?hq\xd7h4/\x1cl\xd5\xafonlp\x8e\x08\xe0\
-L\xd1\xc1\xbb\x96\x08\x00\xc0z\x0b\x01`\xca!\x84\x10\xcb&\x96A,\x93"\x8b\xdb\
-\x1de\xa4\xf7v\xafn}\x92\xb4\xa2\x82L\x98a\xac\x11B$\x12\x0b=\xcfL\xd4\x91<\
-\t\xf7\xb3\x9f\xfb\x89\x16w\x8dF\xf3\x82I\xc2\xc6\xe6\xc6\x86\xb2\xd3)%\xa6\
-\x01\x88 T\x06\x98^p\x8b\x94 $\nI\n.\x9d-\x13\xc7\xa2\x94\x02A\x94\x88\x82s\
-\xce\xba\xd12\xfdN\x1c\x04\xf0\xa7\xdcvL9\xe7\x00\x08\xc4\xf0lC\xfd\x89\x02p\
-\x16\xedcG\xf7\x15-\xee\x1a\x8d\xe6\x05\xc3\x92D\xc52r\x01\xbe\x05\x04\x80KD\
-\x04\x89 \x04\x08\t\x08@\x081\rb\xdbd\x8a\x02"J)$\x07\xe5\xb5!0\xba(\x1e\xda\
-$\xb0\xcas1/H\x89\x16IL\xdeB\x01\x00@9\x1e!C\x00\x00\x0c\x95IDAT\x08H\x1e#\
-\xe2\xc0:\xda\x93\x8e\x16w\x8dF\xf3\x82\xb1\xdd"\x10\x82\x12\xb9@\xc7$\xc40\
-\x12\x86Q\xc2\t!\x06%\xb6E\x0c\n\x84 J@)\xb9\x04\xe8\x19\xe8\xe3H2\x01"\x92\
-\x0e\x15K\x9e\xe5\x02\x10\xe4\x91\x14<\xfd(\x8a\x04Q\x12b\xecU\xdf^\x1c\x873\
-z\xff%!\x08\x82\xa5\xe5\x95 \x08\xc6? \x8e\xe3\xfe\x8f,-\xaf\xac\xd6\xeb{x\
-\xa1\xbbg\xb5^\xdf\xedU-^\xbf1b(F\x7f\xe4\xea\x17\xd7vw}\xcf\xe3\xec\x07\x87\
- \x08\x16\xaf\xdfx\x81\x17`9\x9e\xed\xcf\x10Bff\n\xe5\xd9\xb2](x\xae\xe9\xd9\
-\xd4w\x88c\x01%\x12\xa5\x14\x02\x11\xbbq\x8f\xbb5\xb3\t!RJ\x1e\xb5y\xdc\x12B\
-lk\x00\x85\x8a\xa0<|h\xcb}"\x89\xe3\xf8\xc3\x8f>v\x1cgn\xaev\xf3\xe6\xad \
-\x08\xbe\xfd\xad?v\x1c\'{\xcc\xe2\xf5\x1b?\xfb\xe4\xd3\x85\xf9\x13\xab\xf5\
-\xb57/_\xba\xfc\xc6\xa5 \x08~\xfc\xee{s\xb5\xd9\xd5\xfa\xda7\xbe\xfe\xb5sg\
-\xcf\x00\xc0_\xbe\xfb\x1e\x00Dq|\xee\xec\x99\xaf\xbe\xfd\x16\x00\xfc\xd9\x9f\
-\xffE\xda\xc8\xf7\xde\xf9\xee\xc2|~sv\x10\x04?\xf8\xe1\x8f\x06\xfe\xa9\x9f\
-\x9b\xb7n_\xbdz\xed\xdf\xfe\xe9\xf7\xc7\xec\xdaj\xbd\xfe\xe1G?u\x1d\xc7q\xec\
-8N\xa28\xfe\x93w\xbeS*\x95\xb2\xc7\xfc\xe5\xbb\xef]\xbe|I]\xff\xd6\x89n\xde*\
-\x95J\xb9#G_v\xfa\x91\xcf\xae|~\xf9\x8dK\xd9\x83\xd3Ap\x1cG\x8d\xde\xe8\x8e\
-\xecx\xf6a\xf4\x9fh\xb7-<;\x8d\xa0y\xf3\xe6\xad\x8b\x17\xce\xef\xff\xa9S\xaa\
-\xd33\t}bZ\xb2\x13\xb49\xe3\x88H\x00P\x02\xf6<.\xcf\xe87!0\xd8wc\x98\xc6h\
-\x9f\xce\xe4\xa2\xc5}\xf2P\xca~\xf9\xf2\x1b\xf5z}iiyaa\xfe\xe2\xc5\xf3\x1f~\
-\xf4qN\xdf\x97\x96\x96\xbf\xfd\xad?>w\xf6\x8c\xd2\xf4\xcbo\\\xfar\xf1\x86\
-\x92\x8f\xa5\xe5\x95+W>?w\xf6\xcc\xcd[\xb7\x1d\xc7\xfe\xde;\xdf\x8d\xe3\xf8\
-\x07?\xfc\xd1\x9b\x97/\xa9\x16\xfe\xeb\x7f\xf9\xcf#.\xe0\x97W\xaf\x95J\xa5\
-\xc5\xc5\xeb#\xc4\xfd\xfd\x0f\xfe\xea{\xef|\x17\x00\xc6\x99\x00R\x82 x\xff\
-\x83\x9f\xa4\x13\x0f\x00,-\xaf\xf4+\xe6\xe5\xcb\x97v\xd5\xec\xf8\x97\x9dE\rB\
-\x10\x04\xff\xe3\x83\x9f\x9c;{\xe6)\xce\x98\x0e\xc2\xaeN\xf4\x143\xc4\xa4\
-\x83<L\x1aw\x12\x86q\x1c\x01\x00!\x80\xd4\x91H(&\x04\xe43\x0b\xfb\xf0\xf3\
-\x02\x98\x86A\x0fi\xfa\x81\xc3\xd9\xab\xc3\xcd\xe2\xf5\x1b\x0b\x0b\xf3KK\xcb\
-\x8dFp\xf9\xf2\x1b\x8dF\xb0\xbc\xbcr\xee\xdc\xd9\xdc/k\xa5\xec\xd9wn\xde\xba\
-\xad\xdeY\x98?\x11\xc5q\x10\x04KK\xcb\xe7\xce\x9d\x05\x00\xc7q\x94\xd6\xab#W\
-\xeb\xf5aN\x06\xe5\xd8\xf9\x93w\xbes\xf3\xd6\xed\xec1K\xcb+W\xbf\xb8\xb6x\
-\xfd\x86:@\xfdO\x1dP*\x15\xd5\x9b\xd9\x83\xe38V\x97t\xf5\x8bk\xe9\x9f\xd4\
-\x15f/;\x95TuIW\xbf\xb8\x16\x04A\xa9\xb4\xb5\xebd\xf1\xfa\x8dl\x0b\xc3\x18v\
-\xd9;R*\x95\\\xc7i\x04M\xd5\x91\\g\x83 H[\x8b\xe3\xf8\xea\x17\xd7\xae~q-\x8e\
-\xe3\xfeAP=MGx\xf4\x89\xe28V\xfdJ\x1b_\xad\xd7\xd3\xe1M\xc7*\xdb\xf1t|\x94G+\
-m6\x1d\xea\xb4\x85\xf4O\xeaxu\xc1\xe3\x0f\xc8^\xd0\xdax\x10E\t \x10B\x10\x88\
-\xb0\x8f\x91\xd2W\xd4\xff\xc0,\xe1\xde\xb9M\x10\x00\xb9T\xab\xab\x87\x0e-\
-\xee\x93\xc7\xd2\xd2\xb2\xf2\x12|\xe3\xeb_[\x98?\xf1\xd5\xb7\xdf\x9a\x9f?q\
-\xf1\xc2\xf9\xa5\xa5\xe5\x81\xc7\xff\xec\x93O\xdf\xbc|\t\x00\x82 H\xad\xc2r\
-\xa9\xd8\x08\x9a\xdb\xde)\x97\xd4C\xee8\xce\x95+\x9f\xff\x8f\x0f~\xf2\xfe\
-\x07\x7f\xd5\xdf\xda\xe2\xf5\x1b\x0b\xf3\'J\xa5\xd2\xc5\x0b\xe7\xbf\\\xec*\
-\xc5\xcd[\xb7\xaf\\\xf9\\]\xdb\xe2\xf5\x1bW\xaf~\x01\x00W\xaf~\xb1\xb4\xbc\
-\xb2Z_\xbbr\xe5s\xc7q~\xf6\xc9\xa7Jt\x82 \xf8\xd9\'\x9f:\x8e\xf3\xd9\x95\xcf\
-o\xde\xbc\xa5\x8eT^o\xf5CD\xb5\x99J\x8f\xfa\x94\xba\xa4(\x8a\xd5\xeb\xd5\xfa\
-\x1a\x00d[P\xef\x0cc\xe0e\x8fF\x9d\xfd\xc3\x8f>\x06\x00\xe5\xddR}T\xfe.\x00X\
-]\xad\xff\xf8\xdd\xf7R\xbd\xfe\xec\xca\xe7\xea\xcd\x0f?\xfa\xb8\x11\x04\xd9A\
-XZ^\xb9z\xf5\x1a\x00,.^\xef\x9fZr\'\x02\x80\xf7?\xf8I\xa3\x11\x00\xc0\x87\
-\x1f\xfdT\xa9\xf3\x87\x1f\xfdT\x8d\x8f\x1a\x8d\xfe\xa1K\xc7\xc7\x1d4\xd47o\
-\xdd\xfe\xe4\x93\x9f\xa7\x97\x07\xbd\x1f\nQ\x14GQ\xac\xae\xfc\x05\x92$I\xcf\
-\xf9\x82\x89\xf0\x1b\x01\t\xeak\x9d\xf5\'\xcd\x8dV\xd0\xf1\x01\x9e\xd3j\'J\
-\xec"\xb7\xde\x13L\xb0\xe4\xf9\xb4\x7f\xc0\xd0n\x99\t&\x8e\xe3\x9c\x9f\xbd\
-\x1f%C\xbb\xf2\xe4\xfe\xc7\xff\xf0\xef\x1d\xc7\x89\xe3\xf8\xfd\x0f~\x92\x1a\
-\xfb)_.\xde\xf8\xf6\xb7\xbe\t\x00\x17/\x9e\x7f\xff\x83\x9f(O\xce\xd5\xab\xd7\
-\xbe\xfd\xado\xa6\xf3\xc4\xe57.\xfd\xd9\x9f\xff\x85\xf2H\xa4V\xe4\xeb\x17\
-\xcf\xdf\xbcyka\xfe\xc4\x97\x8b7^\xbfx>\x8ec5?\x01@\xd6\xeb\x9d\xeb\x91\x9a\
-\xb1\x94\xe4\xbdy\xf9R\xd6/\xacZ\xf8\x8f\xff\xe1\xdf\xab3\xfe\xe0\x87?\x1a\
-\xd1\xa9\x81\x97\xbd\xe3P\xdc\xbcy\xbbT*~\xef\x9d\xefd\xdf\xfc\xe5\xd5k\xfd\
-\xcb\x00\x00\xf0\xd5\xb7\xdfRc\xf5g\x7f\xfe\x17\xdf\xab\xd5\xbe\xf7\xcew\xd3\
-A\xb8\xfa\xc55\xc7\xb1\xe7\xe7O\x0c\xfb"\xb2\'\xbay\xebv\xa9TL\'\xb9\x9b7o9\
-\x8e\xadFI}|\xd8\xd0\xa5\xe3\x93\x1bj\x00\xb8z\xf5\xda\xe5\xcb\x97\x1c\xc7\
-\xa9\xd5j?\xfb\xe4\xd3 \x08\x94\x8fN\x1d_.\x97\xd4T\xf1\xa2\xb0\xdcR\x14>6\
-\x08\x00\x82a\x19\x96i\x0b\xc6\x04\x17@\x88k\x1b\x94R\x94\xe2Y\x9d3\x84\x80{\
-\x94\xdaU!\xa4!60\xea\x9a\x02\x04P\x8a\x04\xc0\x7f\x0e\xdd8`hq\x9f<\x16\x16\
-\xe6\x17\xaf\xdfx\xfd\xe2\xf9\xcf\xae|~\xee\xdc\xd9\x9b7o\x95\xcb\xa5z}-\x95\
-\x83\x94\x9f}\xf2i\xbd\xbe\x96jS\xa9TJM\xf5F\xd0,\x97\x8a\xea\x1d\x80\x13\
-\x00\xd0h\x04ss5\xe8\xc9\xab\xe38\xe7\xce\x9d\xc9\x99\x99\xca\x11\xa1\xd6`\
-\x157o\xdd\xbex\xe1\xfcj\xbd\xbe\xa3\xa7\xf8\xe2\x85\xf3\xff\xed\xbf\xff\xbf\
-_}\xfb\xad\xc5\xeb7\xfe\xdd\x9f~\x7f\xb5\xbe\x16\xc7\xb12o\x01\xa0\\*\xaa\
-\xae)U\x82\x8c\x96\xa5\xe4N\xb1Z_+g\xfc3\xd9\xd79\x86]\xf6\xe8\x0b\xbe\xfc\
-\xc6\xa5sg\xcf\xfc\xf8\xdd\xf7r\x93h\xee\xe7N\xfa\xfe\x88\xd9B\x9dKY\xdf\xfd\
-\x13C\xeeDA\x10\xac\xd6\xd7\xe2\xde\xc8\xd4j\xb5\xb9Z\xed\xcd\xcb\x97\xae^\
-\xbd\xf6\xb3O>\xfd\xc6\xd7\xbf\xa6\xa6\xde\xdc\xd0Af|rC\r\x00\xab\xf5\xfa\
-\xe2\xe2\xf5\xf4\xf8(\x8e\xeb\xf5zz\xc3\xbcp/\xbf_\x9am\xaf\xdf6\xa8\x00$&\
-\xb6\xca\x05\xcf\xf6J\\\x00rN\x92u\x1e\xb3gv\xbbc;\x9e\x92\xd27\x19\xa7\x94\
-\xc6\xa2l\x88\xb6mt\xd4\xc6\'\x9e\x84\x8e_y>=9Hhq\x9f<.^8\xff\xe1G\x1f\xbf\
-\xfd\xf6[\x00\xb0\xb8x}n\xae6?\x7f\xe2\x93O~\x9e30\x95\xcd\x9e\x8d\xeeP^u\
-\xb5\xa0\xea:N\xa9TZX\x98_\\\xbc~\xf1B\xd7\x8e\xfe\xea\xdbo--\xaf\xd4\xebu\
-\xa5\xaaKK\xcb\x17/^\xc8\xb6\xb9\xb8x\xe3\x1b_\xffZ*\x8b\x8b\xd7o\xfc\xf2\
-\xea\xb5\x8b\x17\xce\x9f;{fiyea\xfeD\xea\xd8\xe9\xbfl\xc7q\xd4\x95+\xf7H\xa9\
-Tr\x1cG-\x02\xc7q\xdc\x08\x02u\x85?~\xf7\xbdr9/\xeb\x03Y\x98?\xf1\xb3O>U\x82\
-\x18\xc7q\xea\x96\xb9y\xeb\xf6\xc2\xfc\x89\xec5\x0c\xbb\xec\x1dOQ*\x95\xde\
-\xbc|\xe9\xb3+\x9f\x7f\xfb[\x7f\x9c\x1d\xc6\xab_\\S\xd1G\xa9i\xbc#\x17/\x9c\
-\xbf\xfc\xc6\xa5\xcf\xae|\xae\xbe\x82\x11\'\x9a\x9f?\xb1\xb4\xb4\xacL\xfe\
-\xd5z\xbd\\*\xa9\xce^\xbcp\xfe\xe6\xad\xdb\x8b\x8b\xd7\xbf\xf7\xcew\xfb\x87.\
-Kn\xa8\xd55_\xbcxA\xcd\x9aK\xcb+s\xb5\xda\xc2\xc2|\xbd^W\xef\xd4{!\xa7*\xf6t\
-\xaeV\x1b\xa7G\xcf\x11\xd3r\xbc\xca\xc9\xa4q\xdbP\x9bN\xdb\x8fY\xb2I\rCr&\
-\xf8\xb3+;\x00\x10\xc7+4;1\x8b%\x01D4\xa6\x1c\x97\x90\x0e\xaa\x94b\xf4\x10\
-\x06\xb9\x83\x16\xf7ID=\xd8\x1f~\xf4q\xa9T\x9a\x9b\xab5\x1a\xc1\xd2\xd2\xf2\
-\xb7\xbf\xf5\xcd\xfeP\xc8\xf4\xbf\x00\xf0_\xff\xcb\x7f~\xfd\xe2\xf9\x1f\xbf\
-\xfb\xde\xd2\xd2\xb2\n\x85\x04\xa5SW\xaf\xfd\xe5\xbb\xefEq|\xf1\xc2y\xc7q\
-\xca\xa5\xe2\x87\x1f}\xbc\xb4\xb4\x1c\xc7I\xad6\x9b\xf5\xc9,-\xaf4\x82 \xfb\
-\xce\xb9\xb3g>\xbb\xf2\xf9\xd2\xf2\xca\xe5\xcb\x97\xde\xff\xe0\'s\xb5\xd9F\
-\xd0|\xfd\xe2\xf9\xcbo\\\x9a\xab\xd5\xfe\xf2\xdd\xf7.^<\x9f\xb5\n\xcf\x9d;\
-\xfb\xfe\x07\x7f\x95\n\xe5\x9b\x97/\xfd\xe0\x87?R\x9fz\xf3\xf2\xa5\xb9Z\xadT\
-*}\xef\x9d\xef|\xf8\xd1Oo\xde\xbc\xad|\x11K\xcb+\xeaR\x07\x92m!\xed\xbe\xf2n\
-g\xe3m\x86]\xf68\xa3\xad|\xf4\xd9\x83\xbf\xf1\xf5\xaf}\xf8\xd1\xc7\x9f]\xf9\
-\xbcT*\xcd\xd5fG|6\x1d\x848\x8e\xbf\\\xbc\xa1\xd69\xfed\xfb\x1c\xdc\x7f"\xa5\
-\xc8?\xf8\xe1\x8f\xd4\xf1\xdf\xfe\xd67\xe38\xf9\xf0\xa3\x8fU7U\xb8j\xff\xd0\
-\xe5Z\xcb\ru\xfa\x05\xa9\xafUM\x15\xef\x7f\xf0\x93\x9b7\xbb\x0b\x06j\xb4\xd5\
-?\xf7_\xdc\x01\xa0<\xbb\xb0\xcec\xd6zhRI\x80\x08\x16\x0b\x06\x00*t\x06\x80\
-\x00<\xc3\xaa*"\xdaf\xa7\\\x9d\x8b\x12@D\x8b0\x07C\xb5\x8c*\xc1:\x94f;\x00\
-\x90=\\\x89\xd6\xec1A\x10\xa4\xde\x951?\xa2,\xdc\xdcG\x96\x96W\x1c\xc7N\x1fi\
-uL\xf6\x9d\xa7hy\xe0\x89\xc6\xbc\x1e\x00X\xad\xd7\xe38\x19\xe7\x1a\xd4 \xcc\
-\xd5f\xc7\xf1\xa1?w>\xbb\xf2y\xb9\\\x1a\xf6# \xdb;\xd5\xa3\xf1\xaf3\xd7\xaf\
-\xfe\xefz\xcc\x11\xce\x92\xfb\xa2\x07\xbe\xf3bI\xa2v\xd4\xd9\x14I\x07\x90\
-\x0b\x16#\n\x90\x1c@J!\x08H\x00\x15\xf7\x8e\x84\x0c4\xe6I\xf7?\x83$\r\x11\r\
-\xdb\xa5\xd6\x94\x94\x88\xac\x89"\x01 B\x82?\xfb\xdaT\xf5\xd8\xde\xf6\xea\
-\x05\xa1\xc5]\xa3\xd9\x1d\x1f~\xf4\xf1\xdc\\\xadV\xab\x05A\xf0\xcb\xab\xd7\
-\xfe\xdd\x9f~\xff\x85\xcc+/\x0b\x88\x88R\x08\xc6;u\xd1y\xd0\x8cK,a\\JJ\x90\
-\x82\x04\x82\x04$\x01iP\xa4\xc0)\x05\x89\x06!j\xdf\xa9\xe85\xd0\xdd\xa5D\x89\
-\xf2\xc2\x00\x00\x91H\x90:\xfe\xf4\xe9\xa9\xca\xd1\x17\xd8\xb9=E\x8b\xbbF\
-\xb3;V\xeb\xf5\xc5\xc5\x1bjY\xf5\xcd\xcb\x97^\xf8j\xe4KB\x12\xdc\x83x\x15\
-\xcc\xb9\xb5\r\x16q0(\x11\xbd\x80F\x02x\xa4j\xc4\xf1F\xc1\xe2\xb2p\x86\x85\r\
-\x16\xae\xf3\xa4\x8d\x08\x94\x80\xef\x10\x04\x90f\x95\x83KP\x10\x02\x12\xa8\
-\xed\x95\xfd\xe2\x0c5\xac\x17\xda\xa7\xbdE\x8b\xbbF\xa3\x99\x00\x82\xd5\xdf\
-\xd8\xfc\x91\xe3\xd5\x82\xd0F\x80v\x84\x11CB\x08"\x9a\x94\x1c\x9f6\x9el\xd6}\
-\xb3#\x91\xf2^\x89\x0f\x04\x98r\xc0\xf3\x08 \x80Y\x86\xca\xef\xbe\xd8.\xec3z\
-\x13\x93F\xa3\x99\x00\x92$\x8e\x99\x94l\xdd\xb7y\xb9`Z\x96i\x1a\xc4\xa0\xe0\
-\xd9\xb4\\0\x92\x84I\x115C\xcc*\xbbA\xc0\xb5\tH\x00\x04`\r\xe8\xdcy\xb1]\xd8\
-gt\xb4\x8cF\xa3\x99\x00\x04g\xad\x0e\x98\x94!\xac\xb5\xa2J\xb9P\xf2,\x94(M\
-\x83\x08\x166\xdb\x1bR\nD\xb2UM\x1b\xc1u\x80\xd0\xcc\xfaj{\x19\xac\x19\xb0^\
-\x167\x9av\xcbh4\x9a\x03\nJ\x86\xac\x83\xbcIy\xe3\xd1\xea\x93(\x91&\x05\xdf\
-\x81\x84#\x80I\xa8\t\x84\xa0\xe4Rp\x00D$\x12\xc1w\x08\xf4\xcc\xf6\xeaT_\x11\
-\x0e\xc3\x87\xea\x1bp\x18\xb3\xb7\xf7\xa3-w\x8dFs\x80@\xc9\x90wd\xdc\x90I\
-\xd0\x8dY$\x80\x08B\xa0\x12\xea0\x01\x93\x12\x00\x0e\x92\x03\x00\x010\r\x02@\
-\x12\xbe\x95\xbb\x17\x11\xbc\x9c\xd9\xae\x10\x1dh\xdf\x85\xa9\xb3\xfb\xda\
-\xa5\x17\x84\x16w\x8dF\xf3BA\x81\x92#\xef\xc8\xa4!\x93&\xb2\x16J\xd6U\xe5\
-\x9e4K\xb9\x15\xd1\xa8\n\xa5\x1a\x84\xa4\xd1\xee\xb2{\x00RJ\x08\x01)\xc1\xa4\
-\xe0\xdad\xf0\xbe\xa7\xf0\x01x\xc7\xc08\x84\xc9drhq\xd7h4\xfb\x8eL\x80\xb7\
-\x91\x05<jH\x1e\x82d\xd9L\x8d9\x08\x80D\x90\x98\xf9w\x06\x04\x00\x04\x95H\
-\x80\xf6v0\x15\xdcAf{\xfa\x89\xe6o\xa0\xfc;pH\xd3\xb8\xa7\xfc\x1f\x1b\x081\
-\x83\xa9\x05{\xd0\x00\x00\x00\x00IEND\xaeB`\x82'
+\xcf\x1fD\x15x\xc3E\x07\x1b@\t|\x0f\xb5\xfd\x86{-i\x11\xff\x7f{\xef\xfe\x1bI\
+v\xddy\x9e{\xe3\x1d\xc9|\x91L\xd6\x8b\xecz\xb6ZUl\xdb\xd5l\xdb\xed\xee\x19h%\
+\x8f\xbd\xd2BB\xb7F\x18\xcf\xfc:\x8b\xc5b\x80\xc1\xfc9\x8b\x05\x16\x83\x01\
+\x06\x86\x01\xc3j@\x18\t#l\xf7\xb6v\xd4\xdb\xb2\xbb\xdc\xb6T]\xb2\x9bU\xb2\\\
+o\x92\xf5`\xb2X\xcc\xc8G<\xee\xe3\xec\x0f73\x18\x8c|0\xeb\xc1G\xb2\xee\x07B+\
++\x19q#\xee\xcd\x88o\x9c8\xf7\xdcs\xa2\xa7"z\x02\xb0\xcb\xa3\xc2p\x8an\xf1\
+\xc4\xe4\x89{z%\xa5\x8eou\x83\xa9\xcbQ\xdd\xdb\x0f\xa7\x7f\xf0\xa0\xfa}\x97\
+\xad\xfb\xc9\x8a\xc7\x1e\x98\xb2C\x00\x91\x9a\xcc\xa8D\xee\xe9\xc4]\x90\xce\
+\x8ca\x18f/w6\xe7\xbcY\xfd\x17[\xa5?J\x9b2M\xd3q\x1c\x95\x17P-F\xef?\ru\x02\
+\xb0\xd3/a\x18\x86\x8a\xa0\x90\xd2\x10\xb4\xbcQ\xf9W\xf5\xf2\x1fS\xd1q\xc4\
+\xa6\x97<p\xf9\xba)\xdb\x00\x12\x10$u\x13k6t\x16\xb8s\x1c\x9c\xaaJCH\xe9\xe0\
+(\xef~_Pz\xf3\x08J\xb7\x8a\x7f\xf8t\xea\x0f\x0c\xd1r\xf9\xba\x97<px\xdd\xc0\
+\xde\x12\x12j\xa3U\x92\xfe)Zy\xdd\x9b>\xe3\x95f\x1d\xc7!\x19(\xa5a\xe9w\xef8\
+o\xa8\x07\x86j\xd3\xcc\xa4E\x9c\x9a\x9ar]W\xf5r\xcc\x1fE\x85\x91\xa8\xfb|\
+\xf5\xd8\xff\x9a\x8b\xcfQ\x07U\xbf\x9d\xe7y\x85B\xc1w\xfd\x11S\xa9\xfd\xc7\
+\xea_\xe2\xa4\x0e\x1d\xc7q7\x84F\x08)\x8d\xc0\xb8\xdc\xf0\x7f\x8f"3\xf9\x96\
+\xc7\x1ez\xec\xa1%\xb6(\x08@\x90\xd4N\xcc\x99\xc8\x99O\x9c\x93\xd2\x9eN\'\
+\x03\xd3y\x143\x93M~\xe0i\xa4\x0f\xf8\xdc9\xa8\x0b \xb5^\x8b\xc5\xa9bq\n\xba\
+\xab.Q\x15*RF\x80eY\xe9\xdb\x98\xea\xbb\x1a\x7f\xc68b\xea\x0f\xe9\xbat\x08\
+\x1d5\xc9l\x18\xc6\xdc\\mz\xbaJ\x08\xb1m\xdb\xf3<\x95:I\xe5\xb3\x1cgT\xfb\
+\xc7\x93RZ.\x95\xbe\xfe5\x0f\x11-\xcbR\xbf\x94\xba\x17\x86=\xf3\xb2\x0f\xddl\
+;\xbd_D\xaa\xd1\x90R2\xc6\xa3(\x8a\x93D=\xc9\x08!\x96e\xf9\xbe\xe7{\x9e\xaa\
+\xf8j\xf4\xca+\xaa\x14R\'\x8e\x1f;~l\xce0\x0c\xc7qT\x92(\xdf\xf7\x9f)\xaf\
+\xf2\x81\xb0#\xd9\x80\xd8\xf1\xa2\x9ee\xecZK\xc82YL\xc4\xf0\xf4\x16\x85\x99s\
+\xb0\'\xa9\xc6\xf7\x85\xec{q\xf6J\xea\t\xabDDf\x9cl\xb8\'\x1b\x00\x90\xb9\r\
+\xd4\x8en/\x1b\x01\x00\xc8^\xf9\xc7T4\x95\xba\xa5\xbe\xe3~\x8b2k\xaa\xa4*\
+\x99F\xe9%I\xa2n\xefm\xa7\xb0YJ\xb0\x98x\xa7\x1b\x99\x16r{\xa9\x19\xc2nU\xa3\
+A\xf5T\xb3\xbe \xecU\x8b7\x0c#\xbds\xd0\xacDv9\xf2_O\x07\x04\x00\xd2G\xa0\
+\xef\xfb\xc4.\xe4d=5\xb4\x11\x91R\x9a\x8a{\xba(\xc9\x1c\x1ex\xde\x0f\xc9L\
+\xab\xaa\x87\x90j6\xe7\x1f\xc8>\xa8\x9cL\x8d\xd9g\xd2 \xd5Hz1d\x85U]\x03\xe9\
+e \xa5)L\xb7\xe5\x1eo\xc1[\xb9\xc1W{\xd9=WR\xaa\xec\xbb\x9e\x12\xe9UsV\xe7\
+\x90\x1b\xcf\xec\x05\x90U:\xd3$\xfd\x1b\xa7\xf3\xcf\xca\xc8\xe8\xbe\n\xf4-\
+\x81\xf3=\xaf\xff\x91\x96\xfb\x11\xd57\xd9\xebv\xfc\xf1\xcc6\x9b\xde&\xa6\
+\xd9\xd5\x87\xdc\xbd0p/\xe8]\xa2\xd9v\xb2?J\xfa\xc0S\xe3\xe6ynv\xdf\xfe1Q\
+\xdfs\xce\xd5E.\xa5T\xedgo\xc9g}n\xed3,\xb8\xa7\xbc\xa0\xaa"\xc7\xc0m\xc6\
+\xaf\xb5\xc4\xdb\x8fd\xd2\x04\x00\x02\xc0\x86O\xcc\xda\x85\x9a\xe5Ua\x12\'T\
+\xa1\xefJJ\xafr\xd34\xb3\x97\x91\x92*\xdc9\'\x99\x93-\x00\xc8\x16\x9e\xcf^|J\
+}R\x0bn\xd89\xa4\'\x90\xca\xb4\xf2H\xa4\x0f\x8c\xac[6\xe7CH\xef\xa24\xfc#W\
+\xab>w\xc4\xf4]\x012\x86R\xfa<\xebw\x11@O\xdc\xd3\x88\x0e#\xb3\xfe3=\xb4J\
+\xc7\xca9OO/\xdb\xfdq\xbc%\xb0Sk\xb2\xca\x9e:\xcdr\x1d\xc9>\xcfF\xbbA\x86]\
+\x00\xe9\xc6Y1R\xc2\x9a\x1b\xfc\xf4J\x00\xd8\xf1X\xcdNc\xa4\'c\xed,o=B\xdf!\
+\x93\x1e2\x1d\xcf\xdc\x05\x90\xfd\xf5\xb3?\\n~\x82\x10\xa2\xfc\x0f\xaaD\x81\
+\x102\xe7g/\xf69Cr#\xa9\x0c\x1d\x00\xc8&+~\x8eGf\xee\xc2P\x0f\xfe\xdc\xbd\
+\xb0\xab\xbe\x0f|\xdae\xed\xa7\x9c?-7z\xe9\x98\xa8\x07^V\xdc\xd3h\x9cg\xed\
+\xda\xfe#Y\x9bw\xba\xeb\x96GT\xe4\xf0\xa7\xcf\x8e\x0eXP\xa0\xe4\xdb\xe1\x8f0\
+\xf4Q\x01\xd4\xf0g\xba>\xe4\x89\x14wEV\xfeR\xd5N\xc5=;\xab\x93\x15S3\x9b\x9f\
+\xb6\xe7\xd4\xcbjAj\x14\x9b;\xe3vG\xe8{\xeeRV\xe9*\xb3o\x03\xfd\xb2\x9b=\x1f\
+#\xb3v?}c\x18a\xbc\xc3Ne\xcf8"\xf2\x13\x8cd\xa7)\xad\xfc\x00\xb4\x17,\xaf\
+\xbeW\x81\x1f\xaa\x05\xe8=\x0b\xb3\x0e\x96\xf1o$\xb2\xf3\xc5\x9c\x10b\x9a\
+\xa6\x1a\x01\xc8hk\xda\x11e,\xa7w\xf2\xb3\xca\x90zx\xc0\xce\x87k\xee\xc9\x9a\
+{\xb8\xe6\x06?k\'\x0e\x1c\xfc\xd1\xa7\x94}\xc6\x0c\xbc\x00\xfa\x7f\xfd\xec\
+\xa9\xaa\xb3M\x8d\x0c\xb5\x0b\xa5t\xab\xd1\x10"\xbb`\x07\x08!\xa5\xd2\x00Ow\
+\xaa\xec\x00`\x18\x86\x12\xf7T\x1f\x07^\xb4\xbb\x8ej\xee\xc2PZ\x9c^\x0c\xa3_\
+\x08\xc8N\x8b\';\xc2\xb9\'n\xffdI: \xe9\xc3U=\xf0\x84\x10jH\xd5\xc3o\xb4\xc9\
+u\xa8`\xc1\xdd\xb4\x90\xde\xb0\x8a\x1c\x96?\xad\xac\xec]\xe1\xcd\x15\x14\x11\
+\xecf\xb6{\xe5\x854\x9erR\xc5=wo\xa7B\x99\x06\xcf\xe4\xa6\xecSQ3Msc\xe3I#\
+\x08\\\xc7}\xe3\x8d\xd7\x01 +\x01\xe9\x0cO\xf6\xe6\xdf\xf5m4k\x0c\xaa\xfb!\r\
+\x1bP-g_\xcfa\xe7\x05\x9d\xea\xbb\xd9\x9b\xc7KO5w\xc4\x9ce\x94\xde\x84\xfd"\
+\xd2\xaf#9\x83\x88dLl\x9a\xf1\xf6\xa4\xc7\xcav\x7f\xfc\x1b)k\x82\xa9F\xd2\
+\x80\xc5\xdc\x96\xe9\xcd<\xf0I6&\xd9\x87k\xda`n\xf0\xfb\xc5=\xed]\xf6\x1c\
+\xb2?t\xfa[\x8f\xd9e\xccT\x7f\xce\xcaY\xf6\xa7\xcf\x8dmV\xdf\x8d\x9eo0\xb5\
+\xdc{\xab\xf6\xb7M=\xd7u\\\xc7\xcd\rQ\xeeGTr\x0c\xbd+d\xf4u;\xba/\xd9\x0b#\
+\xfb27\xd0g8\xf0GI\r\xfc\xd4J\xc8*{\xeev\xc8\xfd\x16\xd9\xf7\x03DT\xe2\x9e\
+\xfe\x82\xe93\xe0\x99^\xf5\xf6\x1f\x99\x04\xdd\x99O\x00\xc6q\xb0\xd9>~\xad%\
+\x1e\xf1\xf6\x1a\x00\x90\x91\x1e\x1ej\xba^e;\x9d\xe4\xa4\x8a;\xf4\x19\xce\
+\x9b\x9bO-\xcb\xf4}?\x17\xfe\x0c\x19kT\xfd7h\xb6\xae~\xf9\xebS\'O\xbe\xf9\
+\xe6%\x000\x0c\x033\x95\x97\xd3\xc6\xb3\xf6\xdd\x88\x0b:\xfd\xd2\xe8UzR\xd7h\
+\xf6\x81\x81\x19\xb2\x86^Ve\xd2\xcf#\x94\xa5_\xce\xd4u?\xc2\xf9\x93\x15>3\
+\xe3@\xcf\x9e\x86\x99\x99\x04\x1b\xd8\xfd\xb4\xa9q~\x94\xb4}eK\xe6\x845\xd7\
+\xfe\xf3y\x0f\xfa\xc7$\xf7|\x95;\x198\xf8\xa4\xe7\x99\xc9\xfe\xc4\xd9\xc1\
+\x19\xff\x04T\x07\xff\xf9\xe6\xadS\xa7N\xfa\x9e\x97\xfd\xf5\xb3?=\xec\xfcQ\
+\xd2\x8e+\xe7\x03\x00 \xe2\xc6\xc6\x93N7[\xd6\xf69\x1c?v,\xf5Pg{\x9d>\xe9U\
+\x97Sq\xcf\xfep\xcf:\x98Y\x83)+\xee\xe3\xffX\xfdWi*\xd0\xa9\t"\xf3q2;n\x84\
+\xf4\xcc\xd5K\x83\xbaC1\xe3_}\xd6\xe7\xd6\xfe\x934\xee\xaa\xc8\xa8\xfeL\xbc)\
+N\xf1\xf8\xd8\xb5\x96\xee\xa0\xec\xc6P\xb1\x11\x13\xb3\xd3g\t\xdd\x96\xf4\t\
+\x16\xf7T2\x10\xf1\xc6o\xfe\xe9\xff\xfb\xec\xaf\x1d\xc7\xf9\xee\xff\xf2\xed\
+\x99\x99i\xccL\xb4f\xb7\x84\xee\x95\xa4\xeepPFSz\xe9\xe44\x88\xecd\xf4\xc9\
+\xa4\x17\xb4\xfa\xaf\xba(\xb3\xcf\x8c\xecc\x06v\xde\xe4\xe9\x7fG\xc8z\xee@\
+\xd9\xab<\'"\xb0\xd3\x01\x92=D\xee\x11\x95~\xdf\xdf\xf7g\xed~\xff8\xa4-\xc3\
+\xce\xf1\x1f\xd6\xfe\x8b\xdc\xa8$\xf3|U\xb6gv\xe4\xfb\x9f.\xb9\xc1O?\xc03\
+\xcaz\xf6\x02\xbb~\xfd7\x9f\xfd\xf5_\x9f<q\xe2\x8f\xbf\xf5?\xa9(=\xdc\xc9\
+\xb0\x8e\x03@j\xdd\x07A\xf3\xee\xbd\x95\\\x12\x15B\xe8\xc2\xc2\xbceY\xfd\xe7\
+\x96\x9ey\xff\xd3\xeb\xb9\x076{\xc1\xf4?\x93\xc6o0w\xa1\xa6?D\xce\x8a\x82\
+\xbe\x07^\xee\x10\xea"\x7fY\xbd\xdb\x1fD\xb4)\xe3n\x12\xf2\x11\xceq\xafrz\
+\x9c\xd6d\xd2\x10a\x1d\x00\x88J\xda>\xc4\xc3cz\x15gjn\xc77\xcfp\xca\x87\x89\
+\xdcm\xb3\xba\xba\x06\x00q\x1c?x\xf8\xf0\xd8\xb19\xe8\xddx\xd8\xc7v\x0b\x00\
+\xd0\xbbP`\xb8\x00\xe5>\x8c&\xbb}\xf6\xde\x18\xf8\xe4\x80\xcce\xfa\x1cG!=\
+\x831\xab\xa1#\x8e\x92\xfdgn\x83\x81}\x87g\xef~\xee\xa0\xea\x9f\xbb6\xfe\xac\
+\xed\xefz\xdctX 3&95\x81\x91\xc32\x0e\xd9\x8b\xea\xe1\xa3G_\xfc\xfd/\xa5\xc4\
+\xd5\xb5\x07\xff\xf7\xc7\x9f\xfc\xcb\xf7\xde=y\xf2D\xba\xd9\xb0\xa3\xab\x7f\
+\xa6\x92\x17\x86\xe1\xf2\x8d\xdf\xb4;\x9d\x9do\xf0\xa4R)\xd5fg\x06*{\xf6Cz\
+\x88\x17\x19\xd8am\xf6\xffi\xfc\xd6v\xbdP\x87\xdd\x0bY\x1b\xe5\xa5\xf4n\xdf`\
+\xc1]\xf5a\x84\xd9\xee\x95\xe7\xc7\xab\xb5\x84\xac\xf7\x12\x80\xbbxx\xf2\x13\
+\xb3\x93*\xee\x00\x90\xb5\x05~\xe7\xcd\xc5z}\xa3T*^\xba\xf8u\xbas\xc2\x07{\
+\xcb\x083\xb1qy\xab\x01^\xea\x852\xe2\x0e\x19\xb1\xf1s\x1f(\xbd\r\x9e\xf5(\
+\xcf\xa7\xdd\xe3\x9f\xd8\x1e5>\xe6\xd1\xf7t\xf0\xd3w\x02)e\xb3\xd9\xfa\xec\
+\x17\x7f\x13\x86\xa1\xf2\x92\xd7\xeb\x1b\x1f\x7f\xf2\xb3\xdf\xfb\xdd\xdf\xb9\
+t\xf1\xa2\xe7\xb9#\x0e\x916"\x84x\xfcx\xfd\xda\xaf\xff\xa1^\xcfU6G\xc30/\x9c\
+?\xef\xfb\xfe0+u/~\xc4\x97\xde\xe6\x98\xbfH\xeep\x13\xa1\xe3\xfd\x88\xce\xba\
+\nXTZ<x#B\x9c\xe2\x89\xf1Z\xab\xcbx\x0b\x00\x08\x81\x11\xc9\x06\x9c\xe2\xf1\
+\xfet\x92\x13)\xee\xa9\xc5\x94N\xceT\xab\x95?\xfb7\xffZy\xe2\xb07\xd1\x9aB\
+\x08Q\xb2\xde\x9ba\x13i+\xfbp\xb6\xfbp]N\xd0\xa5\xbf\xcf\xec\xc5\xc8\xa4\xd7\
+\x1e\xe7\xbc\xd9l\xfd\xcd\xe7\x7f\xfb\xf4\xe9\x96:\x9a\xda\xa0\xd3\t\xaf\xfc\
+\xed\xdf\xdd\xb9s\xef\xcc\x99\xd3\xe7\xcf\x9d\xadV+#\x1ay\xf8\xf0\xd1\xed\
+\xbbwo\xdf\xba\xdd\t\xa3\xbe4\x03\xe4\xf8\xf1\xb9\xd3\xaf-\x0c\xf4WL(G\xa0\
+\x0b\xa3@\x99\xa4\xa9}G\x85\xa2\xcf\x8ee\xb6\xa3H_\x02\x10\x87>*\x88a\xf9\
+\xd53\xfd\xdfO\xa4\xb8+R\xd3)\x9d\x9c\x19v\xf5\xab\x1bI\xc5\xb3\xa7\xdbc\xef\
+\xfb#~\xb5i^*Yeg\x8c\xfd\xe6\x9f\xfeium-\xeb@Hy\xf4\xf8\xf1\xa3\xc7\x8f\xff\
+\xf1\xab\xe5cs\xb5\x93\'OT\xab\x15\x15\xe3\xa1.\xc5(\x8a\x9f<y\xb2\xb6\xf6\
+\xe0\xc9\xe6\xd3(Je}G\x0b\xa5R\xf1\xcdK\x17\x0f\x7fd\x88&eGj\xdf\xa1\x159\
+\xa8_=;Vk\xad\x07\xc8CTf\xfb\xe8\xbc4\xe6\x80\xd4\x9b\x93*\xeeY\x8f\xa7\x94\
+\xb2\xd5j\xad\xac\xaeQJ\x97\xde\xba\x0c\x99W\xbf8\x8e\x97\x97o<^_\x8f\xa2\
+\xd8\xb2\xccZm\xf6\xcc\xe9\xd7\xba\x7f\xc5\xbc?4\x8e\xe3\xe5\xeb7\xea\xf5\
+\x8d8\x8eK\xa5\xd2\xdc\\\xed\xc2\xf9s\x8e3:a\xa9\xe6\x95#\x15\xf7$I\xca\xa5\
+\xd2\xd4T\xd5\x8e\xa82\x00\x00 \x00IDAT!S\xa3\xa3\xbbI*\xd3\xedv\xeb\xf6\x9d\
+\xf6\x9d\xbb\xf7(\xa5YqF\xccUM!9\xb3\xdd\xf3\xdc\xdfY\xbcT,\x16\xcdgY$\xac9@\
+Pr\xd6ZQ\x9fG\xa4\xf6u\x8a\xc7\r\xdb\x1f\xf4\x97\x9d\xad\x89\x845W\xd45!\xe5\
+\xd0\xe2M\x86\xed\x0f\xcbK3\xa9\xe2\x9e\xa3\xd5\xee\xfc\xea\xea5\x00Xz\xebr*\
+\xd9+\xabk\x9f\xfc\xec\x7f\xc4\xf1v-\x98{\xf7Wn\xde\xba}lN\xcd)oO\xb1"\xe2\
+\xf2\xf5\x1b_\xfc\xdd/3\x1b\xaf-_\xbf\xf1\xab\xab\xd7\xbe\xf3?\xff\xc9\xdc\\\
+m\x9f\xbb\xa39\xcc\xa4o\x81I\x92LM\x15~\xf7\xcd\xc5\xdf\xfe\xf3\xcd\'\x9b\
+\x9b\xbdI\xaf\x9c\x01N\xd2]\xb2md\xd4<\xd5\xf4\xed\xbd\xa6\xa6\n\x8b\x97\xbe\
+>;;\x93]\xd1\xb3\x1f}\xd3\xbc\x00\xbc\xb5\x86<\x82n\xbe\xc6gs\xa1\xf4\xc3\
+\x9a\xf70\x9b\xb4}h\xf8\xe3\xb9ayi&U\xdcs3\xec\xdb\x81c\xbd\x10\xda\xf5\xf5\
+\xfa\'\x9f\xfc\xbfq\x92\x14\x8bS\x8b\x97.z\x9e\'\x84\xb8\xbf\xb2r\xf7\xee\
+\xfd\xad\xad\x06\xf4\xdc2j\xe3\x95\xd5\xd5\xcf~\xf17\x00\xf0\xfbo/\x9d;{\xba\
+T*\xb5\xda\xed\x7f\xf8\x87\xaf\xae\xdf\xf8\xcdG\xff\xcf\'\xff\xf6\xdf\xfc@%`\
+\xd2h\xb2\x93=\xca3c\x18\xf4\xdc\xd9\xd3S\x05\xff\xf1\xfaF/\x87\xfb\xae\x10\
+\xdc.\xe0Mv\xe4\x91!\xe4\xd8\\\xed\xc2\xf9\xb3\xe5r9\xcd\xcdpd\x1c\xeeG\x18\
+\x94\x8c\xb7\xba\x15zG\xe4\x08s\xcb\xf3\xd4\xdc=\xdf\x99d\xad4\xe3\x98\x18\
+\xb9\xc0\xd5.\xcc\x0ekdR\xc5=G\xfa\x94\x94\xbd,"\xcb7n\xc4I23=\xfd\xc7\xdf\
+\xfa\x86Z\xbe,\x84\xa8V\xca\xe5R\xe9\xd7\xff\xf0Un\xe3_\xfc\xf5\xe7\x00\xf0G\
+\xef\xfc\xc1\xeb\x17\xce\x13B\x18c\x9e\xeb\xbe\xf7\xee;Q\x1c\xdf\xbe}gy\xf9\
+\xc6\xd2\xd2e}ki\xb2~\xbc\xacW\x10\x11\xa7\xa7\xab\xbe\xef=\xdd\n\xb6\x1a\
+\x8d0\x1c(\xf1\xdd\xe0[\xe8\xfe_&\x14\xa4g\xc2\x97\xcb\xe5\xb9\xda\xec\xa9\
+\x93\'<\xcf\xcb%\xa7\xdc\xeb\xaei^\x10\x16\xdcW\x86\xb6ZA:\xf0\x07#\xa6\xe3\
+\x95\xc7J\xed\xcb\x1aw\xd2"\x04\xc3\xc3\x1f\xe9\xe8\x05\xae\x93\xea\xc8\xcb\
+\xdd`\xa9\xef2\xb5\xa7~\xfb\xdb\x9b\x00\xa0\x12\x0cd\xf3\x16-\xcc\x9f\xaaT\
+\xca\xaa\t\xb5\xf1\xa3\xc7\x8f\x9b\xcd\xd6\xd4T\xe1\xcc\xe9\xd7\x18cjc\xc5\
+\x99\xd3\xaf\x01\xc0\xca\xea\xdaAuSs\xa8\xc8\x86\xf4\xa5+*S\xcb\xdaq\x9c\xe3\
+\xc7j\xe7\xce\xbc\xb60\x7f\xaaZ-;\xf9\x84\xb4\xf9\xf9\xd2\x14\xc7q\xa6\xa7\
+\xabg\xcf\x9c~\xe3\xf5\xf3\xa7N\x9ep]\xd7u\xdd\\\xbeL\xad\xef\x87\x19\xe4\
+\x11\xefdR\xfb\x0e\xd9\xac\xb0s\x05\xe90D\xb4)z\xf5\x93GD\xca\xbb\xa5\x13\
+\xa3\x17\xb8N\xb0\xe5\x9e\xc6\xc0d\xb3\x97\xa8x\x98\x07\x0f\xba\x03}\xfc\xd8\
+\\\x9a\xce"M\xdfq\xf2\xc4\xf1\xad\xad\x06\xf6\xd6|\xab\x05PI\x9c\xfc\x8fO?K\
+\x1b\'@\x08!\x8c3\x00\x18\x16\x0e\xa1y5!\x99tl\xe9\xb5G)U\xd7\x9e\xe385\xc7\
+\x99\x99\xae\x08)\xa3(j\xb7\xc3(\x8e9\xe7R\n\x94 U>wB\t%\xa6a\xb8\xae3U(\x14\
+\n~\x9a\xfdXU\x11P\xe2\xfe\x1c\xf9a4\x07\x02\x0b\xee\x82\x94@@\x0e_Aj\xd8\
+\x85\xf1R\xfb"k\xdc\xee~\x1a\x19)\xef\x96w)\xcb7\xd9\xe2\xae\xf4z;t\x1d\x801\
+\x96\x9d\xbcJ\x8d\xac\xfe\xdcU\x88\xdd\'\x81\x10\x12\x00\x12\xc6\xd6\xd7\xeb\
+0\x04\x1d1\xa9Q(#Z\xe5]Q\x91T$\x93X?\xcd\x8d\xa5\x1e\x00\xb6e\x95K\xa5\xd1\
+\xad\xd1L\x1aj\x95P\xdeu\xdd\xd4\xdb\xae\xaf\xba\xc3\x8f\x88\x9e\xf2\xb0\x0e\
+\x00\x80\xa3V\x90\x16j_\x831~M\x16\xdc\x93\xac\x1b|\xc59\x0e\xad\xa6]:\xb5k\
+\xa4\xfc\xa4\x8a{v\x89`\x9aT\x16\x00TR\xf2\xb4pR\x14E\xc5b1\xa7\xec\xed\xb6\
+\nD\xed\xd6F0\x0c\n\x00\xb5\xd9\x99\xf7\xde}\'m\\\xddu\xca\x92J\xf3\xedi4\
+\xd0\xb3\xdc\xb3\x9f\xd3\xac\xb6\xd9\xf4\xcb\x033\xa6\xa5{\x91\xbe\x02\x03\
+\xd9\x84\xfe\xe3$\x94\xd7\x1c\x0e\x90\x05\xb7\xd5\xfc)\xe7#V\x90\x9e\xe8_A\
+\xda\x8f\xe4\x1d\xde\xec\x06SJ\t\\\x0cn\x8d\x9a\xae?}f\xd7\xd6&U\xdc!\x977\
+\xa6\x97Q@\xddQ\xbe\xef\x15\n~\xbb\xddY{\xf0\xf0\xcd\xc5j\xd6A\x9f$\xc9\x83\
+\x87\x8f\xd4\xfe\xea&\x9c\x99\x9e\x06\x80\xfa\xc6\x93\xa7[[\xd5J%\xdbx\xb3\
+\xd9D\xc447\x9eF\x93u\xbb\xa7v\xb7\x91\xa9%\x90f`\xce&\xfbM\xaf\x9f\xfe\x1d\
+\xd3l\xcc\xd9\x84\xfe\xcf\x94\x89Ss\x80\xf0\xce\xbaL\xda@F\x86?R\xd3\xab\xbc\
+6Nk\xacqG\x15b\x04\x00&\x86\xbc\x04\x00\xf8\xd3g\xc6\xf1\xddO\xb0\xb8+r\x13\
+\xaa\xa9\x91~\xf6\xcc\x99\xaf\x96\xaf_\xfb\xf5?\x9a\xa6\xb9x\xe9\xa2\xba\xcd\
+\x9a\xad\xd6\xdf\xff\xf2\xaa*Q\x8f\x00\xca\xeaw\x1c{a\xfe\xd4\xca\xea\xda\
+\x95\xbf\xfd\xfb?x\xfb\xadZ\xad\x1bZ\xb4\xb5\xd5\xf8\xf5?~E\x08\xf9\x97\xff\
+\xe2\xdd\x13\xc7\x8fk\xcf\x8cF\x91\xfa\xfaR\x03<\xcd#\x9f*{\xae6VV\xdc\xb3{e\
+\x93\x98\xe7\x12\xfa\x83\x9e\xe39\xfc\xa0L\xab#\t1\xdc\x852^\x8e0\x11o\x89\
+\xb0\x9b\\h\xc4<\xea\xf8e\xf9&^\xdc\xfbQ*\xfc\xf57^\x0f\x82\xe0\xfe\xca\xea/\
+\x7f\xf5\xe5?\xfd\xf6\x9f\x0b\x85\x02J\xf9x\xbd\x0e\x00\xe5r\xa9\xd1\x08 \
+\x93\x9d\xee\x8d\xaf]\x08\x9a\xcdF#\xf8\xfco\xff\xaeR)\xabRv*\x8b\xd3\xcc\
+\xf4t\xa9T\xd26\x94&\x87\xba\x1e\xb2I\xfc\x95\xfd\x9e\xcd\xe3\xdf\x9f\xde\
+\x16v\xfad\xb2\xf16\xc6\xe4\x94\x06\xd5(x\xfb!\xf2\x10\x08 \xe2\xb0\x15\xa4\
+\xc4t\xb2\x054\x86\x83\xacq\xa7\xfbi\xc4<*@a\xe6\xc28\xbe{82\xe2\xde\x9f\xc7\
+\x0e\x11\xdf\xfd\xa3?\x9c\x99\x99^\xbe\xfe\x9bf\xb3\xd5l\xb6\x00\xc0\xf7\xfd\
+\x8bo\xbc\xde\tC%\xeei\x9ai\xdb\xb6\xff\xe8\x0f\x7f\xff\xd6\xed\xbb\xf7\xee\
+\xaf\xa8%N\x00`\xdb\xd6k\x0b\x0b\x7f\xf8\x07o\xbb:\x03\x81f\x10\xa9\t\x0f\
+\xbd\xc8H\xec\xcbZ>L\xdc\xb3\x12O\x9e%\xa1\xbf\xe6\x90\x80\x92\xb3\x9e\x7f\
+\x9c\xf3\xe1+H\xab\xa7\xc7q\xa1\xf0\xf6c\x99\x04\xea\xf3\x88\x97\x00\xa7x\
+\xdctGM\xd1g\xd9=[\xec!D\xc5\xc3\xa8\x80t\xf5_\xccd\x82T\xb7S\x1a\xd2@)}\xb2\
+\xb9\x19\x86\x11%\xa4X\x9c\xc2^!1\xd2\xab\x93 {uS\xd5.\xf5\x8d\'\x84\x00%\
+\xf4\xc4\x89\xe3\xd9\x1a\xaa:"M3\x82\x81\xeb\x9b\xd2\x9b+{\x97\xe5\x17W\xbf@\
+Ny\xcd\x01\xc2\x82\xbb,\xb8\x0f\x04Pb\x94\x0cM\xfcR\x9e\xff}BvYN\x84\x92G\
+\x8f\x7f\x89"\x06\x00D\x88\x18\x0e|T\x10j\x96\xe7\x7f\x7f\xbc,\xf0\x00\x13m\
+\xb9gm\x1f\xe8\xf9@\xb3\x8b\x06U$;!\xa4T,\x16|_\x05\xd2\xa4/\xd1\xca\xce"\
+\x84\xe4\x8a|\xce\xd5fi\x06\xfd\x9a\xac\x19\x87\xac:\x0f\xd4\xf4\x81\xdb\x83\
+\x16\xf4\xc9\x04%\xe3\xad\x07\xea3\x13\xf9L\xcd)~\xf5\xec\xae\xca\x0e*)\x8d\
+\xe8f\xb5\xe2b\xb0\xb2\x03\x80WY\x18_\xd9ar\xc5=\xab\xeci9fu\x9f\xa4\xc1\x91\
+\xb9\x89\xd6\xf4%:-\xca\xac\xbe\xa7\x94\xa6\xfa\x9e{_\xd6\xfa\xaey\x0e\xb4p\
+\x1fyXp\x1f%\x07\x02R\xe2\x88\xbaw#\x12\xbf\xa4\xa0\x88ysU}\x968<\xfc\xd1\
+\xf2\xdc\xf28\xbe\xfb\xcc\t<\xd3\xd6\x87\x87\xac\xb2g\xa3\x17\x00@9^\x94d\
+\xa7\xaf\xc9i`rV\xdc\t!i\x06\xa84*95\xed\xb57F\xa3\xd1\x0c\x00\xa5*j\n\x00\
+\x8c\x8f0\xdb\xcf\x8c\xb5j\xa9q\x17\x91\xf7Z\x1b\x1a\xfeX\x18\x9e\xfdq\x18\
+\x13)\xee\xca\xfd\x92*{\xd6-\x03=)\xef7\xdeS\x03<\x8dO\x80\xcc\x93 -\xe2A2\
+\xabK\xf4*\x12\x8dF\x93\x83w\xd6Q$\xcal\x97r\xb0\xd9n\x17j\x967\xa0\x02W\x0e\
+\x994y\xe7\xb1\xfa<"\xfc\xd1\xf2\xa7\xed\xa9gN<>\x91\xe2\x0e=\t\x86L\x05\xd1\
+T\x85\x95\xcf\x9df\x8a\xa6\xa7\xbb\xa4.\x97tB5\x15\xf7t\xbd\t\xf4"\x1f\xd2\
+\xcd\xb4\xbek4\x9a.(\xd2 \x99\x91\xf9\x1a\xc7\xaa\xb5\x944n\xa79m\x87\xa7\
+\x91\x19\xb7\xb5\x1c\x93*\xee\xd0\xd3\xf7\xdc\xda?\x85\xb2\xc4\xb3\x11\x0b\
+\xb9\xc8\x84\xac^\x0f\x14\xf7\\t\x9aF\xa3\xd1\x00\x00k=P\xb1\xedB\xa0\x1c\
+\x96\xafq\xbcZK"\xdcP\xc5\xaf\x01\x80\x8fX\x03U:a:\xc5\xe78\xd5I\x15we\xb0\
+\x0f\x13\xdf\xac\x15\x9f\xfd2\xf7a\xd8\xc6::M\xa3\xd1\xf4\xb3]\x91cd\xb9jo\
+\x9cZK(\x93t\xd5\x12\x02\x13\x83\xb7"\x86\xedUO?\xcf\xb9N\xae\xb8\xc3n\xb2\
+\xfbL\xa2\xac\x15\\\xa3\xd1\xec\no\xad)o;\x17C\xc3\\\xbd\xf1j-\xf1\xd6\x03\
+\xe4\x9d\xee\xe7\xe1\xe1\x8f~\xf55j\xec\xde\xda@&\xb5X\x87F\xa3\xd1\xec\'(\
+\xe2\xd4l\xe7C\xccvj\xba\xeeX\xb5\x96\x90\xf5j\xf2\x8d\x08\x7f4\xec\x82S:\
+\xf9|g\x0bZ\xdc5\x1a\x8df\x1cX\xf3>J\x01\x04d>\xa3\xc46\xdex\xc9\x06D\xf8\
+\x04E\xb7\x16\xa3\x9a\x95\x1dHa\xe6\xfc8k\xa0\x86\xa1\xc5]\xa3\xd1hvA\xf2N\
+\x1a\xb3\xa8b\xdb\xfb1\x9c)\xb7x|\xf7\xb6P\xa69\xc2\x84\x1c:+k\x15f-\x7f\xfa\
+\xf9\xceV\xa1\xc5]\xa3\xd1hv\x81\x05\xf7T!=!\x86\xc6\xb6\x8f\xb9j\x89\xb7\
+\x1e\xc8\xd4\xdb\xce\x87lDhad\xf1\xebq\xd0\xe2\xae\xd1h4\xa3\x90I\xd0\xcd\
+\xb4>\xbc"\x87\xe5O\x8f\x95l@2\xd6\xbc\xa7>\xab\xf0\xc7\x81x\xe5\xf9q\x82)G\
+\xa3\xc5]\xa3\xd1hF\xc1\x82\xfb\xddBz\x12E\xbe\x18s\x17\x7f\x9c\xf0G\x00\x16\
+\xdcE\xc9A\xd5p\x1e\x12\xfeHM\xc7\xab\x8eU\xb9i4Z\xdc5\x1a\x8df("z*\xa2MPr\
+\xcc\x07;d\x9c\xe2\xb1q\xd2\xacK\xd6\xe6\xedG\xea\xf3(\xb3\xbd:V\x15\xbd]\
+\xd1\xe2\xae\xd1h4C\xe9zQH\xb7\x86F?\x84\x1ac\xadZ\x02`\x8d;\x80\xaa\xa6\xee\
+\xa8*zc\xcd\xca\x8e\x81\x16w\x8dF\xa3\x19\x8c\x08\xeb2\x0e\xd2\xfa\xd7C\xcc\
+\xf6\x13\x86\xe5\xed\xdeT\xf4TDO\xba\x9f\xe5\x90`JB\n3\xe7\xc7\xac\xa2\xb7+Z\
+\xdc5\x1a\x8df \x98\xd6\xbf\x1e\xe6E!\x86=\x9e\x7f\x1cY\xb0\x9dl`\x98\xb7\
+\xdd.\xd4L\xb7\xfc<g:\x08-\xee\x1a\x8dF3\x00\xde~$Y[\xd5\xbf\x1ef\xb6{\x95\
+\xf9q\xd2\x03\xf0\xf6#\x994\xbb\x9f\x87\xbaw\xcc\xc2\xcc\xf9\x179\xe1\x1cZ\
+\xdc5\x1a\x8d&\x0fJ\xc6\x9a]\xb3\x9dq\x18\xe8E1\xec\x82[\xda=\xd9\x00\x8a\
+\x84\x05w\xd5\xe7\x11\xc9\x06\xbc\xeaij:\xcfy\xba\x83\xd0\xe2\xae\xd1h4yXp\
+\x17y\xac\xe6Q\x07\x17\xd2#d\xaa\xf6\xc68\xd5\x91\x92\xc6-\x14I\xb7\xd9!\xc9\
+\x06,\xaf\xe2U\x16^\xe8\x8c\xfb\xd0\xe2\xae\xd1h4;\x90\xbc\xc3\xdb\x8f\x01\
+\xba\xab\x96\x06\xca\xb1\xed\xcf\x8e\x15\xfe\x187Dg\x1d\x00\x08\x00\x17C\x82\
+d\x08\xf1_\xaaCF\xa1\xc5]\xa3\xd1hv\xc0\x82{*f\x91\x0f\xcb\xfd2nu$L\x93\xb6K\
+\x18\x1ao\xe3\x16\x9f\xb3\x1c\xc7h\xb4\xb8k4\x1a\xcd6\xdb\xc9\x06`hj_\xb7tr\
+\xacZK\x9d\xbaL\x1a\xa0\xccv\x0e\xc3\xe6Q\xc7\x0c\x93\x7fV\xb4\xb8k4\x1a\xcd\
+6,\xb8\xa7\xe6O\x87."%\xc4\x1d\'\xcd:\x8a\xa4\x17\xfe(\x87\x87\xc9{\xd5\xd3\
+\xe3\x14\xf7x\x0e\xb4\xb8k4\x1aM\x17\x11o\x89\xe8)\x00\xe0p/\x8a\xe5U\xc71\
+\xdbYk\ry/i\xbb\x18\x92%\xd8\xf6\xc7+\xee\xf1<hq\xd7h4\x9a.i\xa6\xf5\x11f\
+\xfb8\xdev\x141o\xae\x00\x00\x01\x90\x12\x06\xc7\xdb\x00\xf8\xd3/T\x8ec4Z\
+\xdc5\x1a\x8d\x06\x00\x80w\xd6\xd5R#\xb5ji\xe06\xce\xd4\xb1q&?Yp\xaf\x9b\xfd\
+\x11\x80\r\x8b\xb7)\xcc\xda\x85\x99\x179\xe1\xd1hq\xd7h4\x1a\x00@ek\x03\x00\
+\x170\xb0b\xf5\x989\xc2$k\xf1\xce#P\xf3\xa8\x12^,\xde\xe6\xf9y\t\x89%5\x1a\
+\x8df\xd2\xe9&\x1b\x00\x90\xc3\x93\r8\xa5\x93\x86\xe5\x8ej\x84\x0b\xce\x85\
+\x0c\xee\x12\x94\x00\x04G\xc4\xdb\x94O\x19v\xe1%\x9c\xf7p\xb4\xb8k4\x9aW\x1e\
+\x14i\xb2\x01.\x00a\x90\xd9n\xd8~eh\x8e0\xc1y\x1c%1\'\x84m\xd8\xc9\x13\x04B\
+\x00\x98\x009\xe8\r\x00\x08\x19\'o\xc1\x0b\xa2\xdd2\x1a\x8d\xe6U\x87\xb5\xd6\
+\x90\xc7\x00 qH\xb2\x01\x00\xaf\xb2@\x0ck\xe0\xeeR\xc8\xadz=\x8aC\xdfC\x1f\
+\x1e\x03J22\xfc\xd1.\xd4F\xbf\x01\xbc\x14\xb4\xb8k4\x9aW\x1a\x94\x9c\xb7\xd6\
+\xd4g\xc6\x07\xc7,R\xcb\x1d\x11\xdb\xdei\xb56\xeb\xeb2\xe2\x06{\xca\xa3\x00\
+\x08\x05\x15\xfe8\xa4\xb8\x87\xff\xc2\xc5\xaf\xc7A\xbbe4\x1a\xcd+\rk\xae\xa0\
+`\x00 $\n9\xd8\xd6.L\x9f\x1f\x91#\x0c\x01\xcb\xd5Y\xe4Q\xbc\xb5B\x08%\x00bx\
+\xf8\xa3[^\xd8\x07\xb3\x1d\xb4\xe5\xae\xd1h^eP\xc4\xbc\xfd\x10z1\x8b\x03\xb7\
+1\xdd\x92=U\x1b\xd1\x88i\x98ni\xc61[\x04c5\x8f:,\xfc\x91Z\xeeK\xcf\xfe8\xf4\
+\xac\xf6\xe70\x1a\x8dFs\x08a\xc1=\x90\xbc["uH]\xd3\xd11\x8ba\xab\x1d\'\x08\
+\xbca\xe3&\x80A\x00\xb8\xc0a%RG\xbf\x01\xbc\\\xb4\xb8k4\x9aW\x14\xc9;\xbc\
+\xb3\x0e\xaa\xf4\xdd\x90\x98E\xbbP\xb3\xbc\xea\xb0\x16\xe2(\xeetb\x04\xea\
+\xc8\r\x82\x0c\xc1\x90\x08lH9\x0e\xcb\xab\x8e~\x03x\xb9hq\xd7h4\xaf(\xacq\
+\x17P\x02\x01\xc1qX\xcc\xa2W==xg\x84v\xab\xcd\x12&\xc1\xb0ih$u\x04J\x080\x8e\
+BH\xa2\xe6e\t\xd9\xce.@\x88?\xb3\x1f\xf3\xa8)Z\xdc5\x1a\xcd\xab\x88L\x02\x11\
+=\x81n\xc5\xea!\xc9\x06\x8a\'Lgj\xc0\x1f\x10\xda\xadv\x14\xc6\xc4\xb0\x0c*\
+\xccd\x8d "\xa1j\x01\x94\xe5\x95-\xafBA\xb2\xb8\xc9\xa2@\xe9\xbcS<\xbe\x17I\
+\xdbG\xa0\xc5]\xa3\xd1\xbc\x8a$\xc1]\x95d\x80\x0f1\xdb\t5\xfd!f{\xb3\x11\xc4\
+1\xa3\xa6%\x05\xb7I\x83\x88\x06\x12\x83\x00\xb4#\xe9\x94^+\x1d;\x0f@\x00c\
+\x0fxg\xe3~\xd8xDL\xdb\xaf\xeem\xb2\x81~t\xb4\x8cF\xa3y\xe5\x10\xd1S\x19m\
+\x01\x19e\xb6\xbb\xe5\xf9\x81\x15\xab\xc3v\'\x8c\x98aZBp\xcb\xa2\x16{\x04\
+\x84\x10\x80\x84#\x13\x0e\xa0\xbf\xfd\xa4 \xe0\xf8U@\xcb\xaf\xbe\xb6GI\xdbG\
+\xa0-w\x8dF\xf3\xaa\x81,\xb8\xab>q\x8e\x83s\x84\x99\x8eW\x99\xef\xdf\xb3\x1d\
+4\xa3(1M\x83sn;\x9e\x99\xdc\x03\x19\x12\xa0\x00\xd0\x8e\x91R\xbb\xdd\n\xbd\
+\x84%\x02\xe2\x90\x012\x02FLgfJ\x03\x9a\xdak\xb4\xe5\xae\xd1h^-x\xa7.\x93&\
+\x10\x90r\xa8\xd9\xeeW^#4o\xfb\xb6\x1a\xcd0b\x84\x9aR\xa2e\xd9&\x06\x84m \
+\x10 \x10q\x88\x99\x94\xc8M\xc7jw\xc28\x8a$J.\xa9\x00\xc7\x9b9\x15\x85\xd1\
+\xdew+\x8f\xb6\xdc5\x1a\xcd\xabE\x9al\x80\x0b\x1c\x98#\xcc\xb0\x0bN\xe9D\xee\
+\xcb\xe6\xd3F\xcc\xa4aP\xce\xa5mS\xe41\x0b\xd7\x1c\xca\x00("\xb4Ca\x9a\x96\
+\xe5\xcf\xc4\x89\x88\x9en\x02"\x00\x12 \x84RBh\x8b\xb3\xf9sg\t\x19\x18!\xb9W\
+hq\xd7h4\xaf\x102iJ\xd6\x82]\n$\x9d\xcd\x15H\n\x9e6\xe2DZ&\x95@,\x0b$K\xe2\
+\xf0i\x916\x01(\x10\xe8DR\x82Q\x9e}=l\xf3v\xeb)\xa5\x06\x10\x00\xe5\xd1\x07\
+\xe8VdM\x98\xe5\xec\xab\xdb]\x8b\xbbF\xa3y\x85`\xcd\x15D$\x00\x8c\x0f6\xdbM\
+\xb7l\x17f\xd3\x7f\xa2\x94\xc1V\xc0\x041(2\t\xb6\t\x14E"\xb9\x05\rB\x01\x80p\
+!bn\x94f\xce\'\x8c\xa0a\xd9\x9e\'\x85T\xbb\xf6\x9a\x00\xc34\x0ck\xbf\xc5V\
+\x8b\xbbF\xa3yU\xe0a\x9d\x87\x1b\x04\x80\x0f\xcb\x11F\x8d\xc2\xec\xeb\xdb\
+\xdb3\xd6nv\x18G\x00\x89\xd4pL\xc2\xa3\x90\x03X6B\xd2!\x00\x88\x92\x93\x92_9\
+\x1e% \xa4p\\\xdfu\xccV\xa7-\x84\x04)\xbbMR:S\xabQ\xba\xdf\x13\x9cZ\xdc5\x1a\
+\xcd\xab\x01\n\xd6\xb8\x0b\x00\xa3\n$\x95N\xa6\xab\x96\x92(n6Z\xc4\xb0\x08\
+\x01j\xd8\x96!\xe3N\xcb\xb0\x9c\xd2tes\xe5W\x05\x13\x01\x11\xac\x19J\x8fub)\
+\x04s\xfc\x92m\x9bq{\x0b\x04#\x94\xd8\xaeG\x00l\xdb\x99*\x15m{\xbf\xe3 A\x8b\
+\xbbF\xa3yE`\xedG\x92\x87\x04\x80\x89!\xab\x96L\'\xad\xb5\x14u\xc20\x8c\x91\
+\x10\x94\x9c\x9a\xb6Ix\xdc\xee\x98\xae[\xaaV\xc3\xd6&\x15\x1db\x02\xba\'\x12\
+Y\x89\x12\x94\x82\xf9\xc5*\x05\x9e\xb4\x83\xad\'\x8f(\x06\xd5\x99\xb9\xca\
+\xb13\x84\xd2}\x9eD\xcd\xa2C!5\x1a\xcd\xd1\x07%g\xcdU\x00@\x0014\xfc\xb1[k)\
+\xea\x84\xadV\x84\x12\x10\x81\x1a\x96m\x88\xb0\xdd2\x1d\xb7<=\r\x00\x9b\x8f\
+\xfe\xd92Q:\xf3\t\xccvb!\x04\xf7\x8b\xd3 \x13\x11u6\xeb\xf7\rh\x16<\xa3<w\
+\x96\x1a\xc6\x01*;h\xcb]\xa3\xd1\xbc\n\xf0\xd6*\x8aXy\xdb\x07\x9a\xed\x86\
+\xe5:\xa5\x93\x00\x10\xb6\xc3v+\xa4\x06\x15B\x9a\xa6i\x12\xdei\xb6]\xdf/MO\
+\x03\xc0\xd6\xc6\xaa\x89!\xf5^K\xa0\xd2\xee\x84\xd4\xb0<o\nE\x84I\xf8\xe4\
+\xc9\x9aI\xc3\x82\x83\xc5\x99y\xc3\xf2\xf7\xbd\x8by\xb4\xb8k4\x9a#\x0eJ\xc6Z\
+\x0f`\xa4\xb7\xdd\x9b>G\x08m7\xdbQ\xc4(%\x88\xc44M\xdb\x84N\xab\xe3\x16\n\
+\xa5j\x15\x00\x04O\x82\x8d\xbb\xc5\xd2\ta\xcc\x84\xad\x86\xe9\x14-\xd3\x04Lx\
+\xd4\x0e\xb6\x1e\x98$,z`\xda\x9e7\xbc\x8e\xf6~\xa2\xc5]\xa3\xd1\x1cqXp\x1f%\
+\x07\x001\xc4\xdbn\xb9%gj\xae\xd5hE1\' $1)\x01\xcb\xc0V\x10\xf8\xc5b\xb1RQ\
+\x9bm>\xbe\xeb\x17\x8f\x19\xceL\xa7\xd34\xbd*Hn\x10l76\xdb\xcdu\x83\x84e\x9f\
+\x02\x80_=\xdb\xbf\xb4\xf5@8\x14\'\xa1\xd1h4{\x84\xe4!k?\x04@@28\xd9\x00\x01\
+w\xfaB\xab\xd1\xea\x84\x89A\x11\xa8IQ\x1a\x04\xc2N{\xaaR)\x14\xbbyz\xe3\xa8M\
+\x89iz\xd5$l\x1av\tE\xe2:Vs\xf3q\xd8\xde0iX\xf6\rD0\xdd\xb2S<\xb6\xaf\xdd\
+\x1b\x8e\x16w\x8dFs\x94a\xc1=\x90\x02\xc80o\xbb\xb4\n\'\xe3\x08c&\x0c\x83\
+\x001)\x91\x040\x89:S\x95i\xaf\xd0u\x9d#b\xd4\xeeXn%\xea\x04\xc4\xae\x00r\
+\xd76\x1b\x1b\x0f\xe2p\xd32\xe2\xa2g \x1e@9\x8e\xd1hq\xd7h4G\x16\xc9Z\xbcS\
+\x87\xa1\x159\x10\x88)iEJJ\x08\'\xd4\xa0 A\x8a$j\x97g\xe7\x1c\xcf\xedn$e\x12\
+\xc5R\x90(\x89,\xa7"\x05w,\xf2\xb4\xbe\x96DO\\\x9b\xfb\x0eUY\x06\x9c\xa99\
+\xcb-\xefg\xefF\xa3\xc5]\xa3\xd1\x1cY\x92\xc6=\x00\x04B\xc4\xc0\xd4\xbe(\xd1\
+\xaeIR@)(5(E\xc9\x19O\xa2\xe9\xe3\'L\xcbR\x9bH)\xc3V\'\xec4\x19\'\xa6\xe5\
+\xa2\x14\x9ec66\xd6\xa2\xb0>\xe5\xa1k\xd2nA=j\xfa\xd3\x87\xc8l\x07-\xee\x1a\
+\x8d\xe6\xa8"\xe2\xad^!=\x1ch\xb6#uL\xef\x84\x00$\x84\x18\x06\x88$\xe6,\x99>\
+~\xd20\x8cn\x0b\x9cG\xed0\n[\x02M\xc34\xa8a\x98Tnm<\x0c;\xf5\xb2\x8f\x96\xb1\
+\xfd\xb0\xf0*\x0b\x03+{\x1c z\x11\x93F\xa39\x9a\xb0\xe0\x1e\x00\x10\x00.`P\
+\x8e04\xbcc\x1cl\x90\xc2\xb2\xa8HB\xce\x92\xda\xc9S\xdb\xca\xcex\xd4\x8e\xa4\
+d\x828\x88\xc4\xb2-\x8a\xbc\xbd\xf9(l?.\xba2\xab\xec\xd4\xf2\xdc\xca\xc2~uk\
+\\\xb4\xe5\xae\xd1h\x8e "\xdc\x10q\x03\x00$\x0eL\xed\x8bH\\i\xcc\x80\xe4\xb6\
+c\xf1\xa8\xc5\xb9\xa8\x9d\xda\xae\x97$\xb9\x88:!gL\x80!E\xe2y\x1e\x8f\xdbQs\
+\xa3\xd5\xae\x97|\xb4\x8d\x1d\xed\x15f.\xe4R\x04\x1f\x06\xb4\xb8k4\x9a\xa3\
+\x07&\xc1}\xf5i`E\x0eD$\xdeq\xa0\x86c\x1a,\x0c$\x92\xda\xa9S\xe9_y\xc2\xa20\
+\xe2\x8cK0\x12\xc6|\xcf\x8b\xa3V\xdc\\ow\x9e\x94}\xb0v*\xbb\xe5U\xed\xc2\xcc\
+\x9ew\xe8\xd9\xd1\xe2\xae\xd1h\x8e\x1a\xbc\xfdX\xb2\x16\x01\x10\x83\xccvDI\
+\xac"\xb1\xab\xa6A\x93\xce\x16\xb5\x9c\xd9Z-\xfd+\x8b\xe2$I\x18\xe3\x12)\x17\
+\xdcu\xec\xb8\x13D\xcd\xf5\x84mU|0w*;\x10\xe2\xcf\x9c\xdf\x8f.=;Z\xdc5\x1a\
+\xcd\xd1\x02%k\xae@/\xd9\xc0\x00o;!\xc49fP\x83\x85\r\xc3v+\xb3\xdb\xa59X\x1c\
+\xb7[-$\x86@\x82(=\xc7ba\xab\x1d<\x12<\x98r\xd12\x08\xee\x9c\x97\xcd\xa6\x08\
+>lhq\xd7h4G\n\xd6z\xa0R\xfb\x8a\xc1\x159$\xb5*\x86U\xe6a`\xf9\xdd\xa41\x8a\
+\xb0\xd5I\xa2H\xa0!\xa5\xa4\x84\x1a\x06aask\xf3!\xcaV\xd1\x1b\xa0\xec\xc4\
+\xb0\xbc\xea\xe9\xbd\xef\xd0sr\xe8&\x014\x1a\x8d\xe6\xb9A\xc9X\xab\x9b\xda\
+\x97\xf1\x01\x1bH$\xc4\xa9%a\xd3\x99*m+;B\x12\xc5I\x14q\xa4\x9c\x0b\xd30\tH\
+\x195\x9f\xd4W\xa3\xa45\xe5B\xbf\xb2\x03\x80_=C\x8d\x03\xa8\xc21&\xdar\xd7h4\
+G\x07\xd6T\xa9}\t\x17(1o\xb6KD\x0e>\xe1\xa4P\xa9\xf8S=w\nb\xd4\x89\xa2(\x92`\
+p\xcem\xdbB\xc9e\xd4l\x06\x0f;q8=El\x13\xfa\x95\xddp\xa6\x9c\xd2\x89}\xe8\
+\xd1s\xa3\xc5]\xa3\xd1\x1c\x11P$\xbc\xfd\x10\x06\xaeZ" %\x84\tZ\xee\xd4\xd4\
+\xf4\x8c\xeby\xdd]\x10\xe2v\'\x8ac.(\x17\xdc\xb1M)\x18\xc6M\x16=lG\x91g\x13\
+\xdf\x19\xa0\xec\x00P\x98>w\x08\xc3\x1f\xb3hq\xd7h4G\x04\xd6\xbc\x8f\x82\x01\
+\x01.aG\x8e0\x02BB\x94 \x12wjz~[\xd9%F\xedN\x1c\xc7\x02\xa9\x90\xd2\xb1M\x94\
+B\xc6M\x11=F\x19Y&\x99r\x07\xe4\x07\x06\x00\xbbP\xb3\xfc\xe9}\xe9\xd3\xf3\
+\xa3\xc5]\xa3\xd1\x1c\x05$\xef\xb0\xf6# }9\xc2\x08\x08\x01\x11CD\x98\x9d\xbf\
+X(us{I)\xe3v;\x8a\x99\x90 \xa54)\xa0\xe0D\x84\x14\x9a@:L\x12\xcb\x00\xdb\x18\
+|,\xafz(\xcaq\x8c\xe6P\xbfVh4\x1a\xcd\x98\xb0\xe0\x1e\xa0\x00\x00!`;G\x18\
+\x01. d\x88\x08~q\xa6P\xea\xce\xa0J!:\x8d \x8a\x05\x97\x04\x81\x1a\x06\x10\
+\xe4\x14\x98[p%kX&\x15\x12l\x03\x8cA\x02i\xbae\xd3>\xa4\xe1\x8fY\xb4\xb8k4\
+\x9a\x89G&M\xde\xd9\x00 \x88\xc0\xe5\xb6\xd9\xce8D\x0c\x01\x81PR\xae\x9d\xed\
+n,d\xd4j1I\x18\x97\x04\x08%\x92HA@\x94\xa6\xa7\x93\xce\xbak\nD\x10\x12\\{\
+\x90\xb7]%m?\xd0\xca\xd7c\xa2\xc5]\xa3\xd1L<Ip\x0f\x00A\xe5\x08\xeb\x99\xed\
+\x89\x80\x98\xa3\xca\x19\xe6\x17\xe7l\xaf\x08\x00(D\xd8jG\x89L\x187\r\x83\
+\x80 \x82\x01\xc8\xf2l\x8d\xb3\x10\xe2\xbai\x900\x01\xd7\x1a,\xe0\xce\xd4\
+\xb1C\x95\xb4}\x04\xda\xe7\xae\xd1h&\x1b\x11=\x11\xd1&\x00 b\x9al \xe1\x90\
+\xa8Z\xd8\x04(5+s\xe7\x00@p\x1e6[L\x00\x13h\x1a\x06\x80$\x92Y\x16\x99\xaa\
+\xce\x02!\xd1\xe6om\x93\xa8\xbf\x16\x1c\xf5\xb0\xd8\x011l\x7f\xfa\xec\xbe\
+\xf6\xed\x05\xd0\xe2\xae\xd1h&\x1aL\xb6n\xa9Oj\xd5\x12\x02D\t\x884\\\x06\xa1\
+r\xec\xbci\xbb"I\xda\xadN\xccA\n\xe1\xd8\xa6\x10\x8cuZ\xd5Z\xd5.\x94\x01 \
+\xd9\xbaeB\x08\x00\x9d\x18]kp\x90\xccT\xedk\x87-i\xfb\x08\xb4\xb8k4\x9a\x89\
+\x843\x1e3\x84x\x9d\xb06!\x06\x17(%J\x84\x88\x81\xcc\x04BZ\x8e_\xac\x9e\x94J\
+\xd9\x19J)-\xcb\x10\x82\x11\xd6\x99>6cyE\x00\x90\xbc#\xda\x0f\x01@H0\x08\xd0\
+A\xd2nyU\xbb0;\xe0\x0f\x87\x15-\xee\x1a\x8df\xc2@\xc4`\xab\xc1\xc1\xb4\xa8\
+\xa4\x9d\x15 \x14\x00DW\xd9Q\xca4U\x18A)fO^\x14q\xd4nG1C!\x85m\x1a(\x05\xb0N\
+\xa56K\xedn\xfdk\x1e\xdcC\x94\x84\xa8\x18\xcaAk\x96\x0eY\xf1\xebq\xd0\x13\
+\xaa\x1a\x8df\xc2\xd8\xdax\xb2U\xdfpL\xea\x91M"C\x02D\x08d\x1c\xc2$\xab\xec\
+\x80RL\x95\x8f\x11 A3\x8c9\x08)\x1d\xcbD\x94D\x84\x95Z-Uv\x11o\xa5E\xb4\x11\
+\x07\xcf\xa3\xba\xa5\x93\xa6S\xdc\xa7\xee\xbd$\xb4\xb8k4\x9a\t\xa3\xd3n\x03\
+\x97\x18\xb7x\xfb\x11\x00E\x80\x98\xa1Z\xa6\x94Qv4L\xcb/\xce\xb5#\xe0\x02PJ\
+\xc7\xa2Rr\x115+\xb3\xb3\x86\xed\xf5\x1aC\xd6\xb8\x03\x00\x84\x80\xc4\x81Y$\
+\x0f{\xf6\xc7ahq\xd7h4\x13\x86_*\x81i&\xcd\xfb("BI\xcc\xb0\x93\xe4\x94]\x9a\
+\xa6Q\x9d\xbb\x90\x08\x871\x0e\x00\xb6E\x05g\x98\x84\xd5\xb99j\xb9iS\xbc\xb3\
+.\x93&\x00 \x02\xe3\x83\x1c2\x00~\xf5\xf4a\xce\xfe8\x0c\xeds\xd7h4\x13F\xb9\
+\\\xe2\x9d-[\xb6\x01\xa8\x90\xd0\x8e0\xebNA)-\xcb)\xcd\x9eO\xa4\xcf\xb9\xa0\
+\x94\x98\x06\xe1I\x8c"\x9e9\xb6C\xd9Qr\x16\xdc\x03@B\x08\x13(\xe4\x80 \x19\
+\xc3.8\xa5\x93\xfb\xd4\xb1\x97\x8a\x16w\x8dF3aP\xc3(z\xb1\x08\x01\x08\x84\
+\x91\x14\x12\xb3\xcan;~y\xf6|\x87Y\x00h\x18\x94\x10\x94,A\xc9f\x8e\x1f\xcb\
+\x052\xf2\xd6*\xf2\x10T*\xe0!f{a\xe6\xfc!\xcf\xfe8\x0c-\xee\x1a\x8df\xc2\x90\
+IS\x84u\x02\x84s\x8c\x92\x1d\xca\xeez\xc5\xe2\xf4\xb9\x88\x19RH\xcb2)E\x1eE\
+\xa6\x81\xa5\xda\x1c5w\xb8VP\xc4\xac\xb5\xa6>s\xb13\x8bd\x0f\xab0{\xf8\xb3?\
+\x0eC\x8b\xbbF\xa3\x990\x92\xc6]@\t\x04:1J)\x01\x80\x10\x8a(]\xbf\xec\x97Ow\
+\x12CJi\x1a\x14@\xf002L(\xd7\xe6\x08\xcdk]\xd2\xb8\x03\x92\x03\x00"\x0c6\xdb\
+\t-L\xcez\xd4~\xb4\xb8k4\x9aIBDOE\xb4\t\x04\x12\x8e\t\x07\xdb\x9d\x02\x00\
+\xceB\xdf\x9f\xf1J\xafub\x82(l\xdb\x02\x94<\xea8\xae]\x9c\x9e%4\xefW\x91IS\
+\x84\xeb\xea3\x130\xa0\x886\x80W\x9e7\xec\xc2\x9e\xf7g\xcf\xd0\xe2\xae\xd1h&\
+\t\xde~\x04\x00\x80\xc8\xd1\x9d;\xfdu\xc7/I\x16&a;a\xb4\x13\x13\x02\xd2v,\
+\x94\x82\x87\x1do\xca-Tf\xc8\xa0\xc0\xf5\xa4q[\xa5|\x94\x12\xd2t4Y\x88\xe9x\
+\x95\x85\xbd\xee\xcb\x9e2\x91\x13\x05\x1a\x8d\xe6\xd5\x04y\xc4\xa3\xa7@\x80\
+\t\xc2D\xc5\xb0K\x84P\x04\x83K7\xe6\x06\x804(\xa2\x14,l\xf9E\x7f\xaa:X\xd9E\
+\xb8!\xe3\x862\xd6\x99\x18XD\x0f\xfc\xea\x19bX{\xdc\x9b\xbdE[\xee\x1a\x8dfbH\
+\x82\xfb \x19\x02F\x89\xc1$\x91\x82K\xc3f\x0c\x05\x12\xd3\x04\x90\x88@x\xbb9\
+5]\xf1\x8bC\x13\xf3\xb2\xe6\n@\xb7\x8e\xc7\xc0\xf0G\xd3-\xb9\xc5\xe3{\xd8\
+\x8d}A\x8b\xbbF\xa3\x99\x0c$k\xf1\xcec \x90$\x10sI\x88d\tK\x04I"\tHL\x02\tc\
+\x04Y\xf5\xd8\x8c\xe5\x0e\xad\x94$\x93\xa6d-\x181\x8f\n\xe0OOF9\x8e\xd1hq\
+\xd7h4\x93A\x12\xdc\x07\x94\x08\xd0I\x10%\xd8E/\xe1\x1c\xb9\x10(\t\x80\xa4\
+\x06\xa5P\x99\x9e5\x1c\x7fT#\x8d;\xaa\x9c\x07\xe780\xfc\xd1)\x1e\xb3\xbc\xca\
+\xde\xf5b\xdf\xd0\xe2\xae\xd1h&\x00\x11\x07\xa2\xb3\x01\x04\xe2\x04\xb9@\tv\
+\xbb\x15\xc61\x03\x00J\x08\x00\x10j\x00\x8a\xca\xec\xcc\xa8F\xc2\r\x99l\r(\
+\xa2\xdd\x83P\xd3\xabNp\xf8c\x16-\xee\x1a\x8df\x02`\xc1=\x04$\x08\\\x80k\x01\
+C\'\xe2\x843\x06@\x00\x10\x10\x10\x00\x11y\xd46\xac!y`P$\xc1\x1d\x00 \x00\tG\
+\x1cd\xb6\xbb\x95\x05#\x93\x9f`\xa2\xd1\xe2\xae\xd1h\x0e;<|\xc2\xa3M\xa2\x8a\
+iP\xa0@\x0c"\xa4\xe5\x08.\x94\xb2\x03 "1Mb:\xde\xd0F\xda\x0fU\xb2\x011$\xfc\
+\x91Z\x9eW>\xb5\x87\xdd\xd8_\xb4\xb8k4\x9aC\x0e\xb2\xe0>\x00 \x80\x90\x08\
+\x00\x08\x84b\xa7\xe8\xb6\xb8?\x1d\xc7RJ\x01\x00\x06%\xb33e\xc3\x1elw\xa3H\
+\xbaA2\x00\x8c\xe3\xc0UK\xfe\xf4\xd9\xfe\x85\xac\x93\xcb\xd1\xe9\x89F\xa39\
+\x92\xf0\xce\xbaH\x02e\xb6\xa7\x91\x8b\x08\x04\xa2\r\xc7\x8dm\xa7`\xf9\xc7\
+\x0c\xd3\xb4l\xd70\x87&\xe6e\xcd\x15\x94\x0c\x00\x10A\x0eJ\xdanz\x15gjn\xcf:\
+q\x00hq\xd7h4\x87\x17D\x91\xf4\xcc\xf6\xec\x14(\x01\x00 ,l\x1a\x16+\x1c\xff\
+\xfa C<mB k\xf0\xd6\x03\xb5\xc9`\xb3\x9d\x90\xc2\xcc\xf9\x97\x7f\xf6\x07\x8a\
+\x16w\x8dFsx\xe1\xed\xc7\x92u\x08\x00\x97\x03\x127\x92ni\xd3A\xca.#\xe0-\xe4\
+\xcd$l\xc8\xa4\xad*\xa3J\x89\x03\xbd\xedN\xf1\xc4\xc4U\xd1\xdb\x15-\xee\x1a\
+\x8d\xe6\x90\x82\x92\'\xc1\n(o\xfb\xa0\xc8E\xc3-;S\xc72;\x08\x90!\xf0\xa6H\
+\x828lFa\x12\xc7\x9c\x0bY\x9d"\x94\x12\xc0\xc1f;1,\x7f\xfa\xcc^\xf6\xe3`\xd0\
+\xe2\xae\xd1h\x0e)\xac\xb5\x86""\xc3\xf2\xad\x13R\xac\xbd\x0e\x00 c\x90mL\
+\x1a,nE\x9dv\x1c\xf3$\x11\\HD@\x00\xcb \x94\x12\x00\xe0rp\xad%\xaf\xf2\xda$V\
+\xd1\xdb\x15-\xee\x1a\x8d\xe60\x82\x82\xb1\xe6\x03Py\x02\xe4\x00\xb3\xdd\xf6\
+\xa6D\x12\xc4\xcd\x15\x10\xed(L\xe2\x84s&U8\r\x10 \x00\x94\x02\x13`\x9a\x84@\
+w\xd5\xd2\xa0*z\xbe{\x84\xc2\x1f\xb3hq\xd7h4\x87\x91\xa4q\x07e\x02\x00L\xf6-\
+8"\xc0\x05<~\xd4\x8aW~[\xf1\xc1\xa0(eW\xd0\xb3\x15\xf1$\x82\x90\xe0\x98\x08\
+\x08\x11\x838\x01B\xc1 \x00\x00\x88@)\x10B\x0b\xb3\xafOh\x15\xbd]\xd1\xe2\
+\xae\xd1h\x0e\x1d"n\xb0\xf6#\x00\x90\x12\xa4\xe8\xf3\x92\x03l\xb6p\xa3\xc1gK\
+\xd4\xa0\x04q\x87\xa6\xa7\xab\x9a\x12A,\x8bz\xbem\x9a\x06ZH\x0cnP R(m\x8f\
+\x99\x8c\x18\xa9X\x13\\\x8ec4Z\xdc5\x1a\xcda\x03\x93\xc6\xddnz/\x91\x9f\x02%\
+\x04"\x06[-\xe9;\xb4\xe0\x12\x04HC\xdf\x11\x01\x00\t\xa5\x96i8\x8ei\xdb\x86c\
+Q\x82"\x8e\x18\xe7\x02\xa4\xe4\x0c\x11\x08!`\x18\xd21I\xd0\xe1\x8c\xc5#\xa2\
+\xe3\'\x1a-\xee\x1a\x8d\xe6\xd0 \x9a \x9b<|*\xa2- \x06\x85?\x02\xc0fS\n\t\
+\xd3\x1e\xa1\x04$ve\xdd0\xa9\xe3\x98\x8em\xda\xb6a\x10\x94R\xf0$\xe9DBH\x04D\
+ \x04\x89\x8d\x04(2@\xc98\x98\x14\x0b.\x11,\x06\xef\xa8\x05A*\xb4\xb8k4\x9aC\
+\x80d\xec\xe9o\xda\x9d N\xd0\xa6\xdc2\t\xe0\x80\xf0GB\xa0\x13c\xa3\x83\xbeK\
+\\\x1b\x84\x00\x89`\xdb\xb4R\xf1m\x93\x80\x94\x9c\xf3$L\x04\x17R\xaa\x1d\x80\
+\x00Jb\x81?o8\x15!\x90b\x88\xed;D&\x02\x89A\x80\xb3x\xff\xfb\xba?hq\xd7h4\
+\x07\x0e\xb6\xea\xd7\xb7\x9e>\xe5\x1c\x11\xc0\x99\xa2\x83W-\x11\x00\x80\xcd\
+\x16\x02\xc0\x94C\x08!\x96M,\x83X&E\x16\xb7;\xcaH\xef\xad^\xdd\xde\x93\xb4\
+\xa2\x82L\x98al\x10B$\x12\x0b=\xcfL\xd4\x96<\t\xf7\xb3\x9f\xfb\x89\x16w\x8dF\
+s\xc0$ac\xeb\xe9Se\xa7SJL\x03\x10A\xa8\x0c0\xbd\xe0\x16)AH\x14\x92\x14\\:[&\
+\x8eE)\x05\x82(\x11\x05\xe7\x9cu\xa3e\xfa\x9d8\x08\xe0O\xb9\xed\x98r\xce\x01\
+\x10\x88\xe1\xd9\x86\xfa\x13\x05\xe0,\xda\xc7\x8e\xee+Z\xdc5\x1a\xcd\x01\xc3\
+\x92D\xc52r\x01\xbe\x05\x04\x80KD\x04\x89 \x04\x08\t\x08@\x081\rb\xdbd\x8a\
+\x02"J)$\x07\xe5\xb5!0\xba(\x1e\xda$\xb0\xcas1/H\x89\x16IL\xdeB\x01\x00\xf7\
+\x9f\x18\xb1\x00\x00\x0c\xa1IDAT@\x08H\x1e#\xe2\xc0:\xda\x93\x8e\x16w\x8dFs\
+\xc0\xd8n\x11\x08A\x89\\\xa0c\x12b\x18\t\xc3(\xe1\x84\x10\x83\x12\xdb"\x06\
+\x05B\x10%\xa0\x94\\\x02\xf4\x0c\xf4q$\x99\x00\x11I\x87\x8a\x15\xcfr\x01\x08\
+\xf2H\n\x9e\xee\x8a"A\x94\x84\x18{\xd5\xb7\x83\xe3hF\xef\xbf"\x04A\xb0\xb2\
+\xba\x16\x04\xc1\xf8\x1b\xc4q\xdc\xbf\xcb\xca\xea\xdaz\xbd\xbe\x87\'\xfa\xec\
+\xac\xd7\xeb\xcfzV\xcb\xd7o\x8c\x18\x8a\xd1\xbb\\\xfd\xf2\xda\xb3\x9d\xdf\
+\xcb8\xfa\xe1!\x08\x82\xe5\xeb7\x0e\xf0\x04,\xc7\xb3\xfd\x19B\xc8\xccL\xa1<[\
+\xb6\x0b\x05\xcf5=\x9b\xfa\x0eq,\xa0D\xa2\x94B b7\xee\xf1Y\xcdlB\x88\x94\x92\
+Gm\x1e\xb7\x84\x10;\x1a@\xa1"(\x8f\x1e\xdar\x9fH\xe28\xfe\xe8\xe3O\x1c\xc7\
+\x99\x9b\xab\xdd\xbcy+\x08\x82\xef|\xfbO\x1d\xc7\xc9n\xb3|\xfd\xc6\xcf?\xfdl\
+a\xfe\xd4z}\xe3\xed\xa5\xcbKo]\x0e\x82\xe0\xaf>\xfc\xd1\\mv\xbd\xbe\xf1\xado\
+~\xe3\xc2\xf9s\x00\xf0\xc3\x0f\x7f\x04\x00Q\x1c_8\x7f\xee\xbdw\xdfQ\xfb\xaa-\
+?x\xff\xbbs\xb5Z\xff\xd1\x83 \xf8\xf3\xbf\xf8\xcb\x0f\xde\xff\xde\xc2\xfc\
+\xee\xeb\xb6o\xde\xba}\xf5\xea\xb5\x7f\xfbg?\x18\xb3k\xeb\xf5\xfaG\x1f\xff\
+\xccu\x1c\xc7\xb1\xe38\x89\xe2\xf8\xfb\xef\x7f\xb7T*e\xb7\xf9\xe1\x87?ZZ\xba\
+\xac\xce\x7f\xfb@7o\x95J\xa5\xdc\x96\xa3O;\xdd\xe5\xf3+_,\xbdu9\xbb\xf1\xff\
+\xf1\x7f\xfe_\xea\x83\xe38j\xf4Fwd\xd7\xa3\x0f\xa3\xff@\xcf\xda\xc2\x8b\xd3\
+\x08\x9a7o\xdeZ\xbctq\xff\x0f\x9dR\x9d\x9eI\xe8\x13\xd3\x92\x9d\xa0\xcd\x19G\
+D\x02\x80\x12\xb0\xe7qyA\xbf\t\x81\xc1\xbe\x1b\xc34F\xfbt&\x17-\xee\x93\x87R\
+\xf6\xa5\xa5\xb7\xea\xf5\xfa\xca\xca\xea\xc2\xc2\xfc\xe2\xe2\xc5\x8f>\xfe$\
+\xa7\xef++\xab\xdf\xf9\xf6\x9f^8\x7fN)\xf5\xd2[\x97\xbfZ\xbe\xa1\xe4ceu\xed\
+\xca\x95/.\x9c?w\xf3\xd6m\xc7\xb1?x\xff{q\x1c\xff\xf9_\xfc\xe5\xdbK\x97U\x0b\
+\x9f_\xf9\xe2\xed\xa5\xcb\x03\x95\x1d\x00~u\xf5Z\xa9TZ^\xbe>B\xdc\x7f\xfc\
+\x93\xff\xfe\xc1\xfb\xdf\x03\x80q\x1e\x00)A\x10\xfc\xf8\'?M\x1f<\x00\xb0\xb2\
+\xba\xd6\xaf\x98KK\x97\x9f\xa9\xd9\xf1O;\xcb\x7f\xfa\x8f\xffA\x9d\xd2\x7f\
+\xfb\xc9O/\x9c?\xf7\x1cGL\x07\xe1\x99\x0e\xf4\x1cO\x88I\x07y\x984\xee$\x0c\
+\xe38\x02\x00B\x00\xa9#\x91PL\x08\xc8\x17\x16\xf6\xe1\xc7\x050\r\x83\x1e\xd1\
+\xf4\x03G\xb3WG\x9b\xe5\xeb7\x16\x16\xe6WVV\x1b\x8d`i\xe9\xadF#X]]\xbbp\xe1|\
+\xee\xcdZ){\xf6\x9b\x9b\xb7n\xabo\x16\xe6OEq\x1c\x04\xc1\xca\xca\xea\x85\x0b\
+\xe7\x01\xc0q\x1c\xa5\xf5\xd0\xf5\xd2l\x0c\xb3\xe3\x94c\xe7\xfb\xef\x7f\xf7\
+\xe6\xad\xdbYG\xc4\xca\xea\xda\xd5/\xaf-_\xbf\xa16P\xffS\x1b\x94JE\xf5ev\xe3\
+8\x8e\xd5)]\xfd\xf2Z\xfa\'u\x86\xd9\xd3N%u\xbd^\x0f\x82\xe0\xea\x97\xd7\x82 \
+(\x95\xb6W\x9d,_\xbf\x91ma\x18\xc3N{WJ\xa5\x92\xeb8\x8d\xa0\xa9:\x92\xebl\
+\x10\x04ikq\x1c_\xfd\xf2\xda\xd5/\xaf\xc5q\xdc?\x08\xaa\xa7j\x84w=P\x1c\xc7\
+\xaa_i\xe3\xeb\xf5z:\xbc\xe9Xe;\x9e\x8e\x8f\xf2h\xa5\xcd\xa6C\x9d\xb6\x90\
+\xfeIm\xafNx\xfc\x01\xd9\x0bZO\x1fDQ\x02\x08\x84\x10\x04"\xec\x13\xa4\xf4u\
+\xf5?0K\xb8wn\x13\x04@.\xd5\xec\xea\x91C\x8b\xfb\xe4\xb1\xb2\xb2\xaa\xbc\x04\
+\xdf\xfa\xe67\x16\xe6O\xbd\xf7\xee;\xf3\xf3\xa7\x16/]\\YY\x1d\xb8\xfd\xcf?\
+\xfd\xec\xed\xa5\xcb\x00\x10\x04Aj\x15\x96K\xc5F\xd0\xdc\xf1M\xb9\xa4n\xf2\
+\x9f\x7f\xfa\x19\x00\xfc\xe7\xff\xf2_\x7f\xfe\xe9g\xfd\xb7\xfd\xf2\xf5\x1b\
+\x0b\xf3\xa7J\xa5\xd2\xe2\xa5\x8b_-w\x95\xe2\xe6\xad\xdbW\xae|\xa1\xcem\xf9\
+\xfa\x8d\xabW\xbf\x04\x80\xabW\xbfT\xcf\x89+W\xbep\x1c\xe7\xe7\x9f~\xa6D\'\
+\x08\x82\x9f\x7f\xfa\x99\xe38\x9f_\xf9\xe2\xe6\xcd[jK\xe5\xf5V/"\xaa\xcdTz\
+\xd4^W\xae|\xf1\xdf~\xf2\xd3(\x8a\xd5\xe7\xf5\xfa\x06\x00d[P\xdf\x0cc\xe0i\
+\x8fF\x1d\xfd\xa3\x8f?\x01\x00\xe5\xddR}T\xfe.\x00X_\xaf\xff\xd5\x87?J\xf5\
+\xfa\xf3+_\xa8/?\xfa\xf8\x93F\x10d\x07aeu\xed\xea\xd5k\x00\xb0\xbc|\xbd\xff\
+\xd1\x92;\x10\x00\xfc\xf8\'?m4\x02\x00\xf8\xe8\xe3\x9f)u\xfe\xe8\xe3\x9f\xa9\
+\xf1Q\xa3\xd1?t\xe9\xf8\xb8\x83\x86\xfa\xe6\xad\xdb\x9f~\xfa\x8b\xf4\xf4\xa0\
+\xf7\xa2\x10Eq\x14\xc5\xea\xcc\x0f\x90$Iz\xce\x17L\x84\xdf\x08HP\xdf\xe8l>i>\
+m\x05\x1d\x1f\xe0%\xcdv\xa2\xc4.r\xfb;\xc1\x04K^N\xfb\x87\x0c-\xee\x13\xcc8\
+\x06\x97\x92\xa1\xf1=\xb9J\x14\xbe\xff\xfew\xff\xf7\xff\xed\xdf+\xfb1\xb7\
+\xc1W\xcb7\x16\x17/\x02\xc0\xe2\xe2\xc5\xd4\x90\xbcz\xf5\xdaw\xbe\xfd\'Ko]\
+\xfe\xce\xb7\xfft\xe9\xad\xcb\xca\x17\xf1\xc1\xfb\xdf\xcb\x9a\xffo.^Tz\xf4\
+\xd5\xf2\x8d7\x17/\xc6q|\xf3\xd6\xed\xc5\xc5K\xb5Zmq\xf1R*\xb8\xb9\x99\x83\
+\x95\x95\xd5zoZ\xf5\xed\xa5\xcb\xef\xbd\xfbN\xfa4R-|\xf0\xfe\xf7\xd4\x11s;\
+\x8es\xda\xbbr\xf3\xe6m\x00\xf8\xe0\xfd\xeff\xbf\xfc\xd5\xd5k\xdf\x7f\xff\
+\xbbKo]\xce\xba\x8f\x00\xe0\xbdw\xdfQ#\xb0\xb2\xba6W\xabe\x07\xa1^\xaf;\x8e=\
+?\x7f\xea\x83\xf7\xbf7\xd0\xeb\x92=\xd0\xcd[\xb7K\xa5\xe2\xc2\xc2|\xadV\xbbp\
+\xe1\xdc\xcd\x9b\xb7\x1a\xdd\x17\xa0\x92z\x1b\x1b6t\xe9\xf8\xe4\x86\x1a\x00\
+\xae^\xbd\xb6\xb4t\xb9V\xab]\xb8p~\xbd\xbe\x11\x04\x81\xf2\xd1\xbd\xf7\xee;\
+\xef\xbd\xfb\x8ez\xf6\x1f \x96[\xea\xe6\xf4E0,\xc3rm\t\xc0\xb8\xe0BX\xb6A\r\
+\n\xf0\xc2\xc6;!\xe0\x1d\xa7\xe5\xaf\xe3\xd4\xd7\xa8\xb7]+\x95\x00Jq4\xc5]\
+\xfb\xdc\'\x8f\x85\x85\xf9\xe5\xeb7\xde\\\xbc\xf8\xf9\x95/.\\8\x7f\xf3\xe6\
+\xadr\xb9T\xafo\xa46o\xca\xcf?\xfd\xac^\xdfH\xb5\xa9T*\xa5\xa6z#h\x96KE\xf5\
+\r\xc0)\x00h4\x82\xb9\xb9Z\xbd^\x7fs\xf1\xa2\xdafq\xf1\xd2\xd5\xab_f\x1f\x0c\
+\xca\x11\xa1\xe6`\x157o\xdd^\xbctq\xbd^\xdf\xd5S\xbcx\xe9\xe2\x7f\xfe/\xff\
+\xf5\xbdw\xdfY\xbe~\xe3\xdf\xfd\xd9\x0f\xd6\xeb\x1bq\x1c+\xf3\x16\x00\xca\
+\xa5\xa2\xea\xda\xcd\x9b\xb7\x94\xf5Z*\x95r\xcf\xa4\xdc!\xd6\xeb\x1b\xe5\x8c\
+\x7f&\xfb9\xc7\xb0\xd3\x1e}\xc2Ko]\xbep\xfe\xdc_}\xf8\xa38\x8e\xb3O\x8e\xdc\
+\xebN\xfa\xfd\x88\xa7\x8b:\x96\xb2\xbe\xfb\xe7\x87s\x07\n\x82`\xbd\xbe\x11\
+\xf7F\xa6V\xab\xcd\xd5jo/]\xbez\xf5\xda\xcf?\xfd\xec[\xdf\xfc\x86\xe38\xfdC\
+\x07\x99\xf1\xc9\r5\x00\xac\xd7\xeb\xcb\xcb\xd7\xd3\xed\xa38\xae\xd7\xeb\xe9\
+\x05s\xe0^~\xbf4\xdb\xde\xbcmP\x01HLl\x95\x0b\x9e\xed\x95\xb8\x00\xe4\x9c$\
+\x9b<f/\xecv\xc7v<%\xa5o2N)\x8dE\xd9\x10m\xdb\xe8\xa8\x85O<\t\x1d\xbf\xf2rzr\
+\x98\xd0\xe2>y,^\xba\xf8\xd1\xc7\x9f\xbc\xfb\xee;\x00\xb0\xbc|}n\xae6?\x7f\
+\xea\xd3O\x7f\x9130\x95\xcd\x9e\x8d\xeeP^u5\xa1\xea:N\xa9TZX\x98_^\xbe\xbex\
+\xa9kG\xbf\xf7\xee;+\xabk\xcb\xcb\xd7\x95\xaa\xd6\xeb\xf5\xda\xce9\xd5\xe5\
+\xe5\x1b\xdf\xfa\xe67RY\\\xbe~\xe3WW\xaf-^\xbax\xe1\xfc\xb9\x95\xd5\xb5\x85\
+\xf9S\xca"\x1e(s\x8e\xe3\xa83W\xee\x91R\xa9\xe48\x8e\x9a\x04\x8e\xe3X\x19\
+\xa7J\xe3\xca\xe5\xbc\xac\x0fda\xfe\x94r\x1c\xa9\x16R\xb7\xcc\xcd[\xb7\x17\
+\xe6Oe\xcfa\xd8i\xefz\x88R\xa9\xf4\xf6\xd2\xe5\xcf\xaf|\xf1\x9do\xffiv\x18\
+\xaf~yME\x1f\xa5\xa6\xf1\xae,^\xba\xb8\xf4\xd6\xe5\xcf\xaf|\xa1~\x82\x11\x07\
+\x9a\x9f?\xb5\xb2\xb2\xaa\x0c\xff\xf5z\xbd\\*\xa9\xce.^\xbax\xf3\xd6\xed\xe5\
+\xe5\xeb\xea5%7tYrC\xad\xceyq\xf1\x92zj\xaa\x17\x8b\x85\x85\xf9z\xbd\xae\xbe\
+I\xdf\x8dT\xec\xe9\xb0\x89\xf4\xbd\xc3\xb4\x1c\xafr:i\xdc6\xd4\xa2\xd3\xf6c\
+\x96lQ\xc3\x90\x9c\t\xfe\xe2\xca\x0e\x00\xc4\xf1\n\xcdN\xccbI\x00\x11\x8d)\
+\xc7%\xa4\x83*\xa5\x18=\x82A\xee\xa0\xc5}\x12Q7\xf6G\x1f\x7fR*\x95\xe6\xe6j\
+\x8dF\xb0\xb2\xb2\xfa\x9do\xffI\x7f(d\xfa_\x00\xf8O\xff\xf1?\xbc\xb9x\xf1\
+\xaf>\xfc\xd1\xca\xca\xaa\n\x85\x04\xa5SW\xaf\xfd\xf0\xc3\x1fEq\xbcx\xe9bwZ\
+\xf5\xe6\xad\x1f~\xf8#\xc7\xb1\x1bA\xf3\xfb\x99\x07\xc6\xca\xeaZ#\x08\xb2\
+\x8e\x88\x0b\xe7\xcf}~\xe5\x8b\x95\xd5\xb5\xa5\xa5\xcb?\xfe\xc9O\xe7j\xb3\
+\x8d\xa0\xf9\xe6\xe2\xc5\xa5\xb7.\xcf\xd5j?\xfc\xf0G\x8b\xbd\x97\x80\xee\xf6\
+\x17\xce\xff\xf8\'\xff=\x15\xca\xb7\x97.\xff\xf9_\xfc\xa5\xdaK\x05\xe7\x94J\
+\xa5\x0f\xde\xff\xeeG\x1f\xff\xec\xe6\xcd\xdb\x8ec\xab\x83\xaaS\x1dH\xb6\x85\
+\xb4\xfb\xca\xbb\x9d\x8d\xb7\x19v\xda\xe3\x8c\xb6\xf2\xd1g7\xfe\xd67\xbf\xf1\
+\xd1\xc7\x9f|~\xe5\x8bR\xa94W\x9b\x1d\xb1o:\x08q\x1c\x7f\xb5|C\xcds|\x7f\xe7\
+3\xb8\xff@J\x91\xff\xfc/\xfeRm\xff\x9do\xffI\x1c\'\x1f}\xfc\x89\xea\xa6\nW\
+\xed\x1f\xba\\k\xb9\xa1N\x7f\xa08Nj\xb5Y\xf5\xa8\xf8\xf1O~\xaa\xdcA\x00\xa0F\
+[\xfds\xff\xc5\x1d\x00\xca\xb3\x0b\x9b<f\xad\x87&\x95\x04\x88`\xb1`\x00\xa0B\
+g\x00\xc8\x0b9f\x10\xd16;\xe5\xea\\\x94\x00"Z\x849\x18\xaaiT\t\xd6\x914\xdb\
+\x01\x80\xec\xe1L\xb4f\x8f\t\x82 \xf5\xae\x8c\xb9\x8b\xb2ps\xbb\xac\xac\xae9\
+\x8e\x9d\xbd\xa5\xd7\xeb\xf58N\x9e)\xf8/\xd7\xf2\xc0\x03\x8dy>\xe9\t\xe4\xce\
+j j\x10\xe6j\xb3\xa3}\xee{\xc4\xe7W\xbe(\x97K#"\x8b\xd2\xde\xa9\x1e\x8d\x7f\
+\x9e\xb9~\xf5\xff\xd6c\x8ep\x96\xfe\x1f\xba\xff\x9b\x83%\x89\xdaQgK$\x1d@.X\
+\x8c(@r\x00)\x85 \x01T\xdc;\x122\xd0\x98\'\xdd\xff\x0c\x924D4l\x97ZSR"\xb2&\
+\x8a\x04\x80\x08\t\xfe\xec\x1bS\xd5\x13{\xdb\xab\x03B\x8b\xbbF\xf3l|\xf4\xf1\
+\'ss\xb5Z\xad\x16\x04\xc1\xaf\xae^\xfbw\x7f\xf6\x83\x03y\xae\xbc* "J!\x18\
+\xef\xd4E\xe7A3.\xb1\x84q))A\n\x12\x08\x12\x90\x04\xa4A\x91\x02\xa7\x14$\x1a\
+\x84\xa8u\xa7\xa2\xd7@w\x95\x12%\xca\x0b\x03\x00D"A\xea\xf8\xd3g\xa7*\xc7\
+\x0f\xb0s{\x8a\x16w\x8d\xe6\xd9X\xaf\xd7\x97\x97o\xa8i\xd5\xb7\x97.\x1f\xf8l\
+\xe4+B\x12\xdc\x83x\x1d\xcc\xb9\x8d\xa7,\xe2`P"z\x01\x8d\x04\xf0X\xd5\x88\
+\xe3\xa7\x05\x8b\xcb\xc29\x166X\xb8\xc9\x936"P\x02\xbeC\x10@\x9aU\x0e.AA\x08\
+H\xa0\xb6W\xf6\x8b3\xd4\xb0\x0e\xb4O{\x8b\x16w\x8dF3\x01\x04\xeb\xbf\xb5\xf9\
+#\xc7\xab\x05\xa1\x8d\x00\xed\x08#\x86\x84\x10D4)99m<\xd9\xaa\xfbfG"\xe5\xbd\
+\x12\x1f\x080\xe5\x80\xe7\x11@\x00\xb3\x0c\x95\xdf;\xd8.\xec3:\xce]\xa3\xd1L\
+\x00I\x12\xc7LJ\xb6\xe9\xdb\xbc\\0-\xcb4\rbP\xf0lZ.\x18I\xc2\xa4\x88\x9a!f\
+\x95\xdd \xe0\xda\x04$\x00\x02\xb0\x06t\xee\x1cl\x17\xf6\x19\x1d-\xa3\xd1h&\
+\x00\xc1Y\xab\x03&e\x08\x1b\xad\xa8R.\x94<\x0b%J\xd3 \x82\x85\xcd\xf6S)\x05"\
+\xd9\xae\xa6\x8d\xe0:@\xb2\xeb\x9f\xda\xab`\xcd\x80\xf5\xaa\xb8\xd1\xb4[F\
+\xa3\xd1\x1cRP2d\x1d\xe4M\xca\x1b\x8f\xd6\x9fD\x894)\xf8\x0e$\x1c\x01LBM \
+\x04%\x97\x82\x03 "\x91\x08\xbeC\xa0g\xb6W\xa7\xfa\x8ap\x18>T\xdf\x82\xa3\
+\x98\xbd\xbd\x1fm\xb9k4\x9aC\x04J\x86\xbc#\xe3\x86L\x82n\xcc"\x01D\x10\x02\
+\x95P\x87\t\x98\x94\x00p\x90\x1c\x00\x08\x80i\x10\x00\x92\xf0\xed\xdc\xbd\
+\x88\xe0\xe5\xccv\x85\xe8@\xfb.L\x9d\xdf\xd7.\x1d\x10Z\xdc5\x1a\xcd\x81\x82\
+\x02%G\xde\x91IC&Md-\x94\xac\xab\xca=i\x96r;\xa2Q\x15J5\x08I\xa3\xddew\x03\
+\xa4\x94\x10\x02R\x82I\xc1\xb5\xc9\xe0uO\xe1\x03\xf0N\x80\xe1\xefC\xcf\x0e\
+\x16-\xee\x1a\x8df\xdf\x91\t\xf06\xb2\x80G\r\xc9C\x90,\x9b\xa91\x07\x01\x90\
+\x08\x123\xff\xce\x80\x00\x80\xa0\x12\t\xd0\xde\n\xa6\x82;\xc8lO\xf7h\xfe\
+\x16\xca\xbf\x0bG4\x8d{\xca\xff\x0fY\xef?\x965\xa5@\x17\x00\x00\x00\x00IEND\
+\xaeB`\x82'
def getIDESplashBitmap():
return BitmapFromImage(getIDESplashImage())
editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
editMenu.AppendSeparator()
- editMenu.Append(MarkerService.MARKERTOGGLE_ID, _("Toggle &Marker\tCtrl+M"), _("Toggles a jump marker to text line"))
+ editMenu.Append(MarkerService.MARKERTOGGLE_ID, _("Toggle &Bookmark\tCtrl+M"), _("Toggles a bookmark at text line"))
wx.EVT_MENU(frame, MarkerService.MARKERTOGGLE_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, MarkerService.MARKERTOGGLE_ID, frame.ProcessUpdateUIEvent)
- editMenu.Append(MarkerService.MARKERDELALL_ID, _("Clear Markers"), _("Removes all jump markers from selected file"))
+ editMenu.Append(MarkerService.MARKERDELALL_ID, _("Clear Bookmarks"), _("Removes all jump bookmarks from selected file"))
wx.EVT_MENU(frame, MarkerService.MARKERDELALL_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, MarkerService.MARKERDELALL_ID, frame.ProcessUpdateUIEvent)
- editMenu.Append(MarkerService.MARKERNEXT_ID, _("Marker Next\tF4"), _("Moves to next marker in selected file"))
+ editMenu.Append(MarkerService.MARKERNEXT_ID, _("Bookmark Next\tF4"), _("Moves to next bookmark in selected file"))
wx.EVT_MENU(frame, MarkerService.MARKERNEXT_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, MarkerService.MARKERNEXT_ID, frame.ProcessUpdateUIEvent)
- editMenu.Append(MarkerService.MARKERPREV_ID, _("Marker Previous\tShift+F4"), _("Moves to previous marker in selected file"))
+ editMenu.Append(MarkerService.MARKERPREV_ID, _("Bookmark Previous\tShift+F4"), _("Moves to previous bookmark in selected file"))
wx.EVT_MENU(frame, MarkerService.MARKERPREV_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, MarkerService.MARKERPREV_ID, frame.ProcessUpdateUIEvent)
def AddLines(self, text):
+ self.GetControl().SetCurrentPos(self.GetControl().GetTextLength())
self.GetControl().SetReadOnly(False)
self.GetControl().AddText(text)
self.GetControl().SetReadOnly(True)
return
treeCtrl = self.GetControl()
+
parentItem = treeCtrl.GetRootItem()
+ if not parentItem:
+ return
+
if expanded[0] != treeCtrl.GetItemText(parentItem):
return
treeCtrl.Expand(child)
(child, cookie) = treeCtrl.GetNextChild(parentItem, cookie)
- if parentItem:
- treeCtrl.EnsureVisible(parentItem)
+ treeCtrl.EnsureVisible(parentItem)
class OutlineTreeCtrl(wx.TreeCtrl):
if self.ItemHasChildren(item):
child, cookie = self.GetFirstChild(item)
- while child and child.IsOk():
+ while child.IsOk():
self.FindDistanceToTreeItems(child, position, distances, items)
child, cookie = self.GetNextChild(item, cookie)
return False
--- /dev/null
+#---------------------------------------------------------------------------
+# Name: PHPDebugger.py
+# Purpose: php dbg client and supporting code
+# Author: Matt Fryer, Kevin Wang
+# Created: 2/1/06
+# Copyright: (c) 2006 ActiveGrid, Inc.
+# License: wxWindows License
+#---------------------------------------------------------------------------
+
+
+import os
+import socket
+import sys
+import threading
+import traceback
+import wx
+import DebuggerService
+import activegrid.util.sysutils as sysutils
+
+
+DBGC_REPLY = 0x0 # reply to previous DBGA_REQUEST request
+DBGC_STARTUP = 0x0001 # script startup
+DBGC_END = 0x0002 # script done
+DBGC_BREAKPOINT = 0x0003 # user definded breakpoint occured
+DBGC_STEPINTO_DONE = 0x0004 # step to the next statement is completed
+DBGC_STEPOVER_DONE = 0x0005 # step to the next statement is completed
+DBGC_STEPOUT_DONE = 0x0006 # step to the next statement is completed
+DBGC_EMBEDDED_BREAK = 0x0007 # breakpoint caused by DebugBreak() function
+DBGC_ERROR = 0x0010 # error occured
+DBGC_LOG = 0x0011 # logging support
+DBGC_SID = 0x0012 # send SID
+DBGC_PAUSE = 0x0013 # pause current session as soon as possible
+DBGC_AG_SHUTDOWN_REQ = 0x0201 # special ActiveGrid UI shutdown listening thread command
+
+
+FRAME_STACK = 100000 # "call:stack" - e.g. backtrace
+FRAME_SOURCE = 100100 # source text
+FRAME_SRC_TREE = 100200 # tree of source files
+FRAME_RAWDATA = 100300 # raw data or string
+FRAME_ERROR = 100400 # error notification
+FRAME_EVAL = 100500 # evaluating/watching
+FRAME_BPS = 100600 # set/remove breakpoint
+FRAME_BPL = 100700 # breakpoint(s) request = get the list
+FRAME_VER = 100800 # version request
+FRAME_SID = 100900 # session id info
+FRAME_SRCLINESINFO = 101000 # source lines info
+FRAME_SRCCTXINFO = 101100 # source contexts info
+FRAME_LOG = 101200 # logging
+FRAME_PROF = 101300 # profiler
+FRAME_PROF_C = 101400 # profiler counter/accuracy
+FRAME_SET_OPT = 101500 # set/update options
+
+
+DBGF_STARTED = 0x0001 # debugger has been started
+DBGF_FINISHED = 0x0002 # DBGC_END notification has been sent
+DBGF_WAITACK = 0x0004 # awaiting replay|request
+DBGF_UNSYNC = 0x0008 # protocol has been unsynchronized
+DBGF_REQUESTPENDING = 0x0010 # Debug session request pending
+DBGF_REQUESTFOUND = 0x0020 # Debug session request found
+DBGF_REJECTIONFOUND = 0x0040 # DBGSESSID=-1 found - session rejection
+
+
+E_ERROR = 1 << 0
+E_WARNING = 1 << 1
+E_PARSE = 1 << 2
+E_NOTICE = 1 << 3
+E_CORE_ERROR = 1 << 4
+E_CORE_WARNING = 1 << 5
+E_COMPILE_ERROR = 1 << 6
+E_COMPILE_WARNING = 1 << 7
+E_USER_ERROR = 1 << 8
+E_USER_WARNING = 1 << 9
+E_USER_NOTICE = 1 << 10
+
+
+BPS_DELETED = 0
+BPS_DISABLED = 1
+BPS_ENABLED = 2
+BPS_UNRESOLVED = 0x100
+
+
+DBG_SYNC = 0x5953
+DBG_SYNC2_STR = chr(0) + chr(0) + chr(89) + chr(83)
+RESPONSE_HEADER_SIZE = 16
+
+
+_VERBOSE = False
+def myprint(format, vlist=None):
+ if _VERBOSE:
+ if vlist:
+ print format % vlist
+ else:
+ print format
+
+
+#
+# 4 Char's to an Integer
+#
+def C4ToInt(ch, startPos):
+ retval = 0
+ pos = startPos
+
+ retval = retval + (CharToInt(ch[pos]) << 24)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 16)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 8)
+ pos = pos + 1
+ retval = retval + (CharToInt(ch[pos]) << 0)
+
+ return retval
+
+
+def CharToInt(ch):
+ return int((ord(ch) & 0x00FF));
+
+
+#
+# An Integer to 4 Char's
+#
+def IntToC4(num):
+ retval = chr((num >> 24) & 0x00FF)
+ retval += chr((num >> 16) & 0x00FF)
+ retval += chr((num >> 8 ) & 0x00FF)
+ retval += chr((num >> 0 ) & 0x00FF)
+
+ return retval
+
+
+DBGA_CONTINUE = IntToC4(0x8001)
+DBGA_STOP = IntToC4(0x8002)
+DBGA_STEPINTO = IntToC4(0x8003)
+DBGA_STEPOVER = IntToC4(0x8004)
+DBGA_STEPOUT = IntToC4(0x8005)
+DBGA_IGNORE = IntToC4(0x8006)
+DBGA_REQUEST = IntToC4(0x8010)
+
+
+def getCommandString(code):
+ if code == DBGC_REPLY:
+ return "REPLY"
+ elif code == DBGC_STARTUP:
+ return "STARTUP"
+ elif code == DBGC_END:
+ return "END"
+ elif code == DBGC_BREAKPOINT:
+ return "BREAKPOINT"
+ elif code == DBGC_STEPINTO_DONE:
+ return "STEPINTO DONE"
+ elif code == DBGC_STEPOVER_DONE:
+ return "STEPOVER DONE"
+ elif code == DBGC_STEPOUT_DONE:
+ return "STEPOUT DONE"
+ elif code == DBGC_EMBEDDED_BREAK:
+ return "EMBEDDED BREAK"
+ elif code == DBGC_PAUSE:
+ return "PAUSE"
+ elif code == DBGC_ERROR:
+ return "ERROR"
+ elif code == DBGC_LOG:
+ return "LOG"
+ elif code == DBGC_SID:
+ return "SEND SID"
+ elif code == DBGC_AG_SHUTDOWN_REQ:
+ return "AG SHUTDOWN REQ"
+
+
+def reportFlags(flagsValue):
+ flagsRetVal = ""
+ if flagsValue & DBGF_STARTED: # debugger has been started
+ flagsRetVal += "started+"
+ if flagsValue & DBGF_FINISHED: # DBGC_END notification has been sent
+ flagsRetVal += "finished+"
+ if flagsValue & DBGF_WAITACK: # awaiting replay|request
+ flagsRetVal += "awaiting ack+"
+ if flagsValue & DBGF_UNSYNC: # protocol has been unsynchronized
+ flagsRetVal += "protocol unsynchronized+"
+ if flagsValue & DBGF_REQUESTPENDING: # Debug session request pending
+ flagsRetVal += "request pending+"
+ if flagsValue & DBGF_REQUESTFOUND: # Debug session request found
+ flagsRetVal += "request found+"
+ if flagsValue & DBGF_REJECTIONFOUND : # DBGSESSID=-1 found - session rejection
+ flagsRetVal += "session rejection+"
+ return flagsRetVal
+
+
+def getErrorTypeString(code):
+ if code == E_ERROR:
+ return "[Error]"
+ elif code == E_WARNING:
+ return "[Warning]"
+ elif code == E_PARSE:
+ return "[Parse Error]"
+ elif code == E_NOTICE:
+ return "[Notice]"
+ elif code == E_CORE_ERROR:
+ return "[Core Error]"
+ elif code == E_CORE_WARNING:
+ return "[Core Warning]"
+ elif code == E_COMPILE_ERROR:
+ return "[Compile Error]"
+ elif code == E_COMPILE_WARNING:
+ return "[Compile Warning]"
+ elif code == E_USER_ERROR:
+ return "[User Error]"
+ elif code == E_USER_WARNING:
+ return "[User Warning]"
+ elif code == E_USER_NOTICE:
+ return "[User Notice]"
+ else:
+ return "[Unexpected Error]"
+
+
+class ResponseHeader(object):
+ def __init__(self, conn, blocking = False):
+ self.isValid = False
+ receivedData = conn.recv(RESPONSE_HEADER_SIZE, blocking)
+
+ if not receivedData:
+ myprint("Tried to get %d bytes of PHP DBG header, got None\n" % RESPONSE_HEADER_SIZE)
+ return
+ elif len(receivedData) != RESPONSE_HEADER_SIZE:
+ myprint("Tried to get %d bytes of PHP DBG header, got %d\n" % (RESPONSE_HEADER_SIZE, len(receivedData)))
+ return
+
+ self.sync = C4ToInt(receivedData, 0)
+ self.command = C4ToInt(receivedData, 4)
+ self.flags = C4ToInt(receivedData, 8)
+ self.toRead = C4ToInt(receivedData, 12)
+
+ myprint("ResponseHeader: sync=%x, command=%s, flags=(%s), toRead=%s\n", (self.sync, getCommandString(self.command), reportFlags(self.flags), self.toRead))
+ if self.sync != DBG_SYNC:
+ myprint("Sync wrong for header! Expected %x, got %s\n" % (DBG_SYNC, self.sync))
+ return
+
+ self.isValid = True
+
+
+class ResponsePacketFrame(object):
+ def __init__(self, conn, size, data, blocking = False):
+ self.isValid = False
+ self.conn = conn
+ self.data = ''
+
+ if data:
+ self.data = data
+ newlyReceived = False
+ elif conn:
+ newlyReceived = True
+
+ sizeToReceive = size
+ while True:
+ oneChunk = conn.recv(sizeToReceive, blocking)
+ sizeReceived = len(oneChunk)
+ if sizeReceived > 0:
+ self.data = self.data + oneChunk
+
+ if sizeReceived < sizeToReceive:
+ sizeToReceive = sizeToReceive - sizeReceived
+ continue
+ else:
+ break
+
+ if len(self.data) != size:
+ myprint("Expected to get %d bytes of a PHP DBG packet, got %d\n" % (size, len(self.data)))
+ return
+ else:
+ return
+
+ self.frameName = C4ToInt(self.data, 0)
+ self.frameSize = C4ToInt(self.data, 4)
+ if newlyReceived:
+ myprint("Newly received ResponsePacketFrame: frameName=%d, frameSize=%d", (self.frameName, self.frameSize))
+ else:
+ myprint("Created from existing ResponsePacketFrame: frameName=%d, frameSize=%d", (self.frameName, self.frameSize))
+
+ if self.frameSize == 0:
+ return
+
+ self.currPos = 8
+ self.totalDataLen = len(self.data)
+ self.length = 8 + self.frameSize
+ self.isValid = True
+ myprint("new ResponsePacketFrame: currPos=%s, totalDataLen=%s, length=%s", (repr(self.currPos), repr(self.totalDataLen), repr(self.length)))
+ return
+
+ def getNextInt(self):
+ myprint("getNextInt(): currPos=%s, totalDataLen=%s, length=%s", (repr(self.currPos), repr(self.totalDataLen), repr(self.length)))
+ if self.isValid and self.currPos + 4 <= self.length:
+ val = C4ToInt(self.data, self.currPos)
+ self.currPos = self.currPos + 4
+ myprint("getNextInt(): got an integar: %s", repr(val))
+ return val
+ else:
+ return self._errorReturn("getNextInt(): no more integar available with current frame: ")
+
+ def getNextString(self, strLen):
+ endPos = self.currPos + strLen
+ if self.isValid and endPos <= self.length:
+ #
+ # Trim the ending '\0'. TODO: confirm this applies to all raw string data.
+ #
+ str = self.data[self.currPos:endPos - 1]
+ self.currPos = endPos
+ myprint("getNextString(): got a string: %s", str)
+ return str
+ else:
+ return self._errorReturn("getNextString(): no more string available with current frame: ")
+
+ def getNextFrame(self, useAbsolutePos = False):
+ if useAbsolutePos:
+ #
+ # Skip this frame's header (8 bytes for frameSize and frameSize) and frame data (frameSize).
+ #
+ self.currPos = self.length
+
+ if self.isValid and self.currPos < self.totalDataLen:
+ return ResponsePacketFrame(None, None, self.data[self.currPos:])
+ else:
+ return self._errorReturn("getNextFrame(): no more frame available with current frame: ")
+
+ def _errorReturn(self, preMsg = ''):
+ myprint(preMsg + "frameName=%s, frameSize=%s, totalDataLen=%s, length=%s, currPos:%s", (repr(self.frameName), repr(self.frameSize), repr(self.totalDataLen), repr(self.length), repr(self.currPos)))
+ self.isValid = False
+ return None
+
+
+class PHPDBGFrame(object):
+ FRAME_HEADER_SIZE = 8
+ def __init__(self, frameType):
+ self.frameType = IntToC4(frameType)
+ self.frameData = ""
+
+ def addInt(self, intVal):
+ self.frameData = self.frameData + IntToC4(intVal)
+
+ def addChar(self, charVal):
+ self.frameData = self.frameData + charVal
+
+ def addStr(self, string):
+ #
+ # Add the trailing '\0'.
+ #
+ self.frameData = self.frameData + string + '\0'
+
+ def getSize(self):
+ return len(self.frameData) + PHPDBGFrame.FRAME_HEADER_SIZE
+
+ def writeFrame(self, conn):
+ header = self.frameType + IntToC4(len(self.frameData))
+ conn.sendall(header)
+ conn.sendall(self.frameData)
+
+
+class PHPDBGPacket(object):
+ def __init__(self, packetType):
+ self.header = DBG_SYNC2_STR + packetType
+ self.frames = []
+ self.packetSize = 0
+
+ def addFrame(self, frame):
+ self.frames.append(frame)
+ self.packetSize += frame.getSize()
+
+ def sendPacket(self, conn, flags = 0):
+ self.header += IntToC4(flags)
+ self.header += IntToC4(self.packetSize)
+ conn.sendall(self.header)
+ for frame in self.frames:
+ frame.writeFrame(conn)
+
+
+class PHPDBGException(Exception):
+ def __init__(self, msg = None, cause = None):
+ if (msg == None):
+ Exception.__init__(self)
+ elif (cause == None):
+ Exception.__init__(self, msg)
+ else:
+ Exception.__init__(self, "PHPDBGException: message:%s\n, cause:%s" % (msg, cause))
+
+
+class PHPDBGConnException(PHPDBGException):
+ pass
+
+
+class PHPDebuggerCallback(object):
+ ACTION_NONE = 0
+ ACTION_STOP = 1
+ ACTION_LISTEN = 2
+
+ def __init__(self, ui, service, lsnrHosti, lsnrPorti):
+ self.ui = ui
+ self.service = service
+ self.lsnrHost = lsnrHosti
+ self.lsnrPort = lsnrPorti
+ self.lsnrThr = None
+ self.lsnrAction = PHPDebuggerCallback.ACTION_NONE
+ self.clearInternals()
+ self.initLsnrThr()
+
+
+ ############################################################################
+ # Public callback functions begin
+ #
+ def Start(self):
+ self.lsnrThr.start()
+
+ def ShutdownServer(self, stopLsnr = True):
+ #
+ # First to tell php debugger to stop execution of the current PHP
+ # program. Disconnect with php dbg module too.
+ #
+ self.stopPhpDbg()
+
+ #
+ # Stop debug listener.
+ #
+ if stopLsnr:
+ self.stopLsnr()
+
+ def BreakExecution(self):
+ reqPacket = PHPDBGPacket(IntToC4(DBGC_PAUSE))
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse()
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def SingleStep(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPINTO)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPINTO
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Next(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPOVER)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPOVER
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Continue(self):
+ reqPacket = PHPDBGPacket(DBGA_CONTINUE)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_CONTINUE
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def Return(self):
+ reqPacket = PHPDBGPacket(DBGA_STEPOUT)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.lastCommand = DBGA_STEPOUT
+ self.awaitAndHandleResponse(blocking = True)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.ui.LoadPHPFramesList(self.stackList)
+ return
+
+ def PushBreakpoints(self, noRemove = False):
+ tmpList = []
+ bps = self.service.GetMasterBreakpointDict()
+ for fileName in bps.keys():
+ if fileName.endswith('.php'):
+ lines = bps[fileName]
+ if lines:
+ for lineNo in lines:
+ if lineNo:
+ #
+ # A tuple (fileName, lineNo) is an item which is
+ # used as a key in self.bpDict.
+ #
+ tmpList.append(self.createBpKey(fileName, lineNo))
+ myprint("PushBreakpoints(): global breakpoint \'%s:%i\'", (fileName, lineNo))
+
+ #
+ # Check to see if we have any new breakpoints added.
+ #
+ for oneKey in tmpList:
+ if not self.bpDict.has_key(oneKey):
+ #
+ # A new breakpoint.
+ #
+ newBp = BreakPoint(self, oneKey[0], oneKey[1])
+ newBp.addSelf()
+ self.bpDict[oneKey] = newBp
+ myprint("PushBreakpoints(): newly added global breakpoint \'%s:%i\'", (oneKey[0], oneKey[1]))
+
+ if noRemove:
+ return
+
+ #
+ # Check to see if any bp that is in our list, but not in the latest
+ # global list. If so, it must have been removed recently in the
+ # global one. Remove it from our list and tell php debugger to do
+ # so as well.
+ #
+ toRemoveList = []
+ for oneKey in self.bpDict.keys():
+ if tmpList.count(oneKey) == 0:
+ toRemoveList.append((oneKey, self.bpDict[oneKey]))
+ myprint("PushBreakpoints(): recently removed global breakpoint \'%s:%i\'", (oneKey[0], oneKey[1]))
+
+ for bp in toRemoveList:
+ bp[1].removeSelf()
+ del self.bpDict[bp[0]]
+ myprint("PushBreakpoints(): successfully removed breakpoint \'%s:%i\' from both our local list and php debugger", (bp[0][0], bp[0][1]))
+
+ return
+ #
+ # Public callback functions end
+ ############################################################################
+
+
+ def newConnEventHandler(self):
+ #
+ # Ok, we've got a connection from the php debugger, and some initial
+ # frame data from it. Everything is ready and let's make some initial
+ # actions.
+ #
+ self.clearInternals()
+
+ try:
+ self.awaitAndHandleResponse(self.lsnrThr.getConnHeader())
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return
+
+ self.PushBreakpoints(True)
+ self.ui.LoadPHPFramesList(self.stackList)
+
+ #
+ # This could be called when this object is constructed or when self is
+ # re-initialized after getting a new dbg module connection as a new
+ # session.
+ #
+ def clearInternals(self):
+ self.stackList = []
+ self.errStackList = []
+ self.stackFrameIndex = 0
+ self.isErrStack = False
+ self.errStr = ''
+ self.modList = []
+ self.stopOnError = True
+ self.lastCommand = None
+ self.evalRet = ''
+ self.modDict = {}
+ self.bpDict = {}
+ self.rawDataDict = {}
+ self.sessID = 0
+ self.sessType = 0
+ self.sessEnded = False
+ self.frameCounter = 1000
+ self.variableList = []
+ self.verMajor = 0
+ self.verMinor = 0
+ self.verDesc = None
+
+ def initLsnrThr(self):
+ self.actionEvent = threading.Event()
+ self.lsnrThr = PHPDBGLsnrThr(self, self.lsnrHost, self.lsnrPort, self.actionEvent, self.ui)
+
+ def awaitAndHandleResponse(self, header = None, blocking = False, disable = True, stopping = False):
+ if disable:
+ self.ui.DisableWhileDebuggerRunning()
+
+ while self.readResponse(header, blocking) != 0:
+ myprint("Waiting for response")
+
+ if stopping:
+ self.ui.DisableAfterStop()
+ else:
+ self.ui.EnableWhileDebuggerStopped()
+
+ def requestDBGVersion(self):
+ #TODO: necessary?
+ pass
+
+ def getSourceTree(self):
+ #TODO: necessary?
+ pass
+
+ def addDBGModName(self):
+ #TODO: necessary?
+ pass
+
+ def getNextFrameCounter(self):
+ self.frameCounter = self.frameCounter + 1
+ return self.frameCounter
+
+ def getVariables(self, stack):
+ self.variableList = []
+
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame = PHPDBGFrame(FRAME_EVAL)
+
+ reqFrame.addInt(0)
+ reqFrame.addInt(stack.getFrameScopeId())
+ reqPacket.addFrame(reqFrame)
+ myprint("PHPDebuggerCallback::getVariables(): about to send eval request")
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(disable = False)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return self.variableList
+
+ myprint("PHPDebuggerCallback::getVariables(): evalRet=%s", self.evalRet)
+ evalStr = PHPDBGEvalString(stack, self.evalRet)
+ if evalStr:
+ self.variableList = evalStr.getVars()
+ myprint("PHPDebuggerCallback::getVariables(): about to return")
+
+ return self.variableList
+
+ def evalBlock(self, stack, evalStr):
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame1 = PHPDBGFrame(FRAME_EVAL)
+ reqFrame2 = PHPDBGFrame(FRAME_RAWDATA)
+
+ frameID = self.getNextFrameCounter()
+ reqFrame1.addInt(frameID)
+ reqFrame1.addInt(1)
+
+ reqFrame2.addInt(frameID)
+ reqFrame2.addInt(len(evalStr) + 1)
+ reqFrame2.addStr(evalString)
+
+ reqPacket.addFrame(reqFrame2)
+ reqPacket.addFrame(reqFrame1)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(disable = False)
+ except PHPDBGConnException:
+ self.currConnFinished()
+ return None
+
+ evalStr = PHPDBGEvalString(stack, self.evalRet)
+
+ return evalStr.getVars()
+
+ def getBPUnderHit(self):
+ for bp in self.bpDict.values():
+ if bp.isUnderHit():
+ return bp
+
+ return None
+
+ def getRawFrameData(self, frameNo):
+ if self.rawDataDict.has_key(frameNo):
+ #
+ # Once the frameData is consumed, remove it from rawDataDict.
+ #
+ return self.rawDataDict.pop(frameNo)
+ else:
+ #
+ # TODO: do we need to handle the case when the raw frame data hasn't
+ # been received before?
+ #
+ return None
+
+ def getModByNum(self, modNum):
+ if self.modDict.has_key(modNum):
+ return self.modDict[modNum]
+ else:
+ return None
+
+ def getModByFileName(self, fileName):
+ for mn, fn in self.modDict.iteritems():
+ if fn == fileName:
+ return mn
+
+ return 0
+
+ def setMod(self, modNum, fileName):
+ if modNum != 0 and fileName:
+ self.modDict[modNum] = fileName
+
+ return
+
+ def readResponse(self, headeri = None, blockingi = False):
+ inHeader = headeri
+ header = None
+ cmdReceived = 0
+ isFirstPacket = True
+ blocking = blockingi
+ self.isErrStack = False
+ self.rawDataDict.clear()
+
+ while True:
+ #
+ # If we have already received the first packet, we can't block any
+ # more.
+ #
+ if not isFirstPacket:
+ blocking = False
+
+ #
+ # If this is the first loop and we have a non-empty header passed in, use it. Otherwise,
+ # read in a new header. For subsequent loops, inHeader is None so we always read a new
+ # header from the wire.
+ #
+ if inHeader:
+ header = inHeader
+ inHeader = None
+ else:
+ header = ResponseHeader(self.lsnrThr, blocking)
+
+ if not header.isValid:
+ return 0
+
+ cmdReceived = header.command
+ frame = ResponsePacketFrame(self.lsnrThr, header.toRead, None, blocking)
+ if not frame.isValid:
+ return 0
+
+ isFirstPacket = False
+ isFirstStackFrame = True
+ while frame and frame.isValid:
+ frameName = frame.frameName
+ if frameName == FRAME_STACK:
+ if self.isErrStack:
+ self.errStackList = self.handleRespFrameStack(self.errStackList, frame, isFirstStackFrame)
+ else:
+ self.stackList = self.handleRespFrameStack(self.stackList, frame, isFirstStackFrame)
+
+ if isFirstStackFrame:
+ isFirstStackFrame = False
+ elif frameName == FRAME_SOURCE:
+ self.handleRespFrameSource(frame)
+ elif frameName == FRAME_SRC_TREE:
+ self.handleRespFrameSrcTree(frame)
+ elif frameName == FRAME_RAWDATA:
+ self.handleRespFrameRawdata(frame)
+ elif frameName == FRAME_ERROR:
+ self.handleRespFrameError(frame)
+ elif frameName == FRAME_EVAL:
+ self.handleRespFrameEval(frame)
+ elif frameName == FRAME_BPS:
+ self.handleRespFrameBps(frame)
+ elif frameName == FRAME_BPL:
+ self.handleRespFrameBpl(frame)
+ elif frameName == FRAME_VER:
+ self.handleRespFrameVer(frame)
+ elif frameName == FRAME_SID:
+ self.handleRespFrameSid(frame)
+ elif frameName == FRAME_SRCLINESINFO:
+ self.handleRespFrameSrclinesinfo(frame)
+ elif frameName == FRAME_SRCCTXINFO:
+ self.handleRespFrameSrcctxinfo(frame)
+ elif frameName == FRAME_LOG:
+ self.handleRespFrameLog(frame)
+ elif frameName == FRAME_PROF:
+ self.handleRespFrameProf(frame)
+ elif frameName == FRAME_PROF_C:
+ self.handleRespFrameProfC(frame)
+ elif frameName == FRAME_SET_OPT:
+ self.handleRespFrameSetOpt(frame)
+ else:
+ self.handleRespFrameUnknown(frame)
+ return 0
+
+ #
+ # After handling of this frame, force frame to point to the
+ # next one based on current frame's absolute size.
+ #
+ frame = frame.getNextFrame(True)
+
+ if cmdReceived == DBGC_REPLY:
+ self.handleRespCmdReply()
+ elif cmdReceived == DBGC_STARTUP:
+ self.handleRespCmdStartup()
+ elif cmdReceived == DBGC_END:
+ self.handleRespCmdEnd()
+ elif cmdReceived == DBGC_BREAKPOINT:
+ self.handleRespCmdBreakpoint()
+ cmdReceived = 0
+ elif cmdReceived == DBGC_STEPINTO_DONE:
+ self.handleRespCmdStepintoDone()
+ elif cmdReceived == DBGC_STEPOVER_DONE:
+ self.handleRespCmdStepoverDone()
+ elif cmdReceived == DBGC_STEPOUT_DONE:
+ self.handleRespCmdStepoutDone()
+ elif cmdReceived == DBGC_EMBEDDED_BREAK:
+ self.handleRespCmdEmbeddedBreak()
+ elif cmdReceived == DBGC_PAUSE:
+ self.handleRespCmdPause()
+ elif cmdReceived == DBGC_ERROR:
+ self.handleRespCmdError()
+ elif cmdReceived == DBGC_LOG:
+ self.handleRespCmdLog()
+ elif cmdReceived == DBGC_SID:
+ self.handleRespCmdSid()
+ else:
+ self.handleRespCmdUnknown()
+
+ return cmdReceived
+
+ def handleRespFrameStack(self, stackList, frame, isFirst):
+ if isFirst:
+ stackList = []
+ self.stackFrameIndex = 0
+
+ lineNo = frame.getNextInt()
+ modNo = frame.getNextInt()
+ scopeId = frame.getNextInt()
+ frameId = frame.getNextInt()
+ if modNo != 0:
+ newStackFrame = PHPStackFrame(self, self.getModByNum(modNo), lineNo, self.stackFrameIndex, scopeId, self.getRawFrameData(frameId), modNo)
+ stackList.append(newStackFrame)
+ self.stackFrameIndex = self.stackFrameIndex + 1
+
+ return stackList
+
+ def handleRespFrameSource(self, frame):
+ modNo = frame.getNextInt()
+ fromFilePos = frame.getNextInt()
+ error = frame.getNextInt()
+ fullSize = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+ textFrameId = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ #
+ # TODO: fullSize string and textFrameId are not handled here.
+ #
+ return
+
+ def handleRespFrameSrcTree(self, frame):
+ parentModNo = frame.getNextInt()
+ parentLineNo = frame.getNextInt()
+ modNo = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ return
+
+ def handleRespFrameRawdata(self, frame):
+ frameNo = frame.getNextInt()
+ if frameNo > 0:
+ toRead = frame.getNextInt()
+ if toRead > 0:
+ str = frame.getNextString(toRead)
+ self.rawDataDict[frameNo] = str
+ myprint("handleRespFrameRawdata(): added \'%d\'=\'%s\' to rawDataDict.", (frameNo, str))
+
+ return
+
+ def handleRespFrameError(self, frame):
+ self.isErrStack = True
+
+ #
+ # Type of the error.
+ #
+ errInt0 = frame.getNextInt()
+ #
+ # ID of error message.
+ #
+ errInt1 = frame.getNextInt()
+
+ if errInt0 == E_ERROR:
+ errIdStr = "[Error]"
+ elif errInt0 == E_WARNING:
+ errIdStr = "[Warning]"
+ elif errInt0 == E_PARSE:
+ errIdStr = "[Parse Error]"
+ elif errInt0 == E_NOTICE:
+ errIdStr = "[Notice]"
+ elif errInt0 == E_CORE_ERROR:
+ errIdStr = "[Core Error]"
+ elif errInt0 == E_CORE_WARNING:
+ errIdStr = "[Core Warning]"
+ elif errInt0 == E_COMPILE_ERROR:
+ errIdStr = "[Compile Error]"
+ elif errInt0 == E_COMPILE_WARNING:
+ errIdStr = "[Compile Warning]"
+ elif errInt0 == E_USER_ERROR:
+ errIdStr = "[User Error]"
+ elif errInt0 == E_USER_WARNING:
+ errIdStr = "[User Warning]"
+ elif errInt0 == E_USER_NOTICE:
+ errIdStr = "[User Notice]"
+ else:
+ errIdStr = "[Unexpected Error]"
+
+ errMsg = self.getRawFrameData(errInt1)
+ if errMsg and len(errMsg) > 0:
+ self.errStr = errIdStr + ": " + errMsg + "\n"
+ else:
+ self.errStr = errIdStr + ": <Invalid Error Message>\n"
+
+ if not self.stopOnError:
+ if self.lastCommand == DBGA_CONTINUE:
+ self.Continue()
+ elif self.lastCommand == DBGA_STEPINTO:
+ self.SingleStep()
+ elif self.lastCommand == DBGA_STEPOUT:
+ self.Return()
+ elif self.lastCommand == DBGA_STEPOVER:
+ self.Next()
+
+ return
+
+ def handleRespFrameEval(self, frame):
+ evalInt0 = frame.getNextInt()
+ evalInt1 = frame.getNextInt()
+ evalInt2 = frame.getNextInt()
+ self.evalRet = self.getRawFrameData(evalInt1)
+ #TODO: is the following necessary?
+ evalStr = self.getRawFrameData(evalInt0)
+
+ return
+
+ def handleRespFrameBps(self, frame):
+ return
+
+ def handleRespFrameBpl(self, frame):
+ #
+ # Get this breakpoint.
+ #
+ dbgBp = []
+ for i in range(10):
+ dbgBp.append(frame.getNextInt())
+
+ if dbgBp[2] != 0:
+ #
+ # If filename is sent, get it from the rawDataDict.
+ #
+ fileName = self.getRawFrameData(dbgBp[2])
+ if not fileName:
+ return
+
+ #
+ # If this filename comes with a mod number, store this
+ # modNum/fileName into this session's modDict. Notice it might
+ # overwrite previous value.
+ #
+ if dbgBp[0] != 0:
+ self.setMod(dbgBp[0], fileName)
+ elif dbgBp[0] != 0:
+ #
+ # Use modNum to get the fileName.
+ #
+ fileName = self.getModByNum(dbgBp[0])
+ if not fileName:
+ return
+ else:
+ #
+ # Couldn't get the filename; nothing we can do with.
+ #
+ return
+
+ bpKey = self.createBpKey(fileName, dbgBp[1])
+ if not self.bpDict.has_key(bpKey):
+ #
+ # Not in our bp list? Anyway, create one for it.
+ #
+ ourBp = BreakPoint(self, fileName, dbgBp[1], dbgBp[0], dbgBp[3], dbgBp[4], dbgBp[5], dbgBp[6], dbgBp[7], dbgBp[8], dbgBp[9])
+ self.bpDict[bpKey] = ourBp
+ newlyCreated = True
+ else:
+ ourBp = self.bpDict[bpKey]
+ newlyCreated = False
+
+ #
+ # Update with the latest bp information.
+ #
+ if not newlyCreated:
+ ourBp.update(dbgBp)
+
+ return
+
+ def handleRespFrameVer(self, frame):
+ self.verMajor = frame.getNextInt()
+ self.verMinor = frame.getNextInt()
+ verFrameNo = frame.getNextInt()
+ self.verDesc = self.getRawFrameData(verFrameNo)
+ myprint("respFrameVer: verMajor=%s, verMinor=%s, verDesc=%s", (repr(self.verMajor), repr(self.verMinor), repr(self.verDesc)))
+
+ return
+
+ def handleRespFrameSid(self, frame):
+ self.sessID = frame.getNextInt()
+ self.sessType = frame.getNextInt()
+ myprint("respFrameSid: sessID=%s, sessType=%s", (self.sessID, self.sessType))
+
+ return
+
+ def handleRespFrameSrclinesinfo(self, frame):
+ return
+
+ def handleRespFrameSrcctxinfo(self, frame):
+ return
+
+ def handleRespFrameLog(self, frame):
+ #
+ # TODO:
+ # Now we don't do much here besides following the protocol to retrieve
+ # the data.
+ #
+ logId = frame.getNextInt()
+ logType = frame.getNextInt()
+ modNo = frame.getNextInt()
+ lineNo = frame.getNextInt()
+ fileNameFrameId = frame.getNextInt()
+ extInfo = frame.getNextInt()
+
+ fileName = self.getModByNum(modNo)
+ if not fileName:
+ self.setFileMod(modNo, fileNameFrameId)
+
+ return
+
+ def handleRespFrameProf(self, frame):
+ return
+
+ def handleRespFrameProfC(self, frame):
+ return
+
+ def handleRespFrameSetOpt(self, frame):
+ return
+
+ def handleRespCmdReply(self):
+ return
+
+ def handleRespCmdStartup(self):
+ return
+
+ def handleRespCmdEnd(self):
+ self.sessEnded = True
+ return
+
+ def handleRespCmdBreakpoint(self):
+ return
+
+ def handleRespCmdStepintoDone(self):
+ return
+
+ def handleRespCmdStepoverDone(self):
+ return
+
+ def handleRespCmdStepoutDone(self):
+ return
+
+ def handleRespCmdEmbeddedBreak(self):
+ return
+
+ def handleRespCmdPause(self):
+ return
+
+ def handleRespCmdError(self):
+ self.stackList = []
+
+ if len(self.errStackList) > 0:
+ self.errStr = self.errStr + "Stack Trace:\n"
+
+ while len(self.errStackList) > 0:
+ oneStack = self.errStackList.pop()
+ self.errStr = self.errStr + "%s\n" % oneStack.getLongDisplayStr()
+
+ self.ui.showErrorDialog(self.errStr, "PHP Error")
+ myprint("Got PHP Error:\n%s", self.errStr)
+
+ return
+
+ def handleRespCmdLog(self):
+ return
+
+ def handleRespCmdSid(self):
+ return
+
+ def setFileMod(self, modNo, fileNameFrameId):
+ if fileNameFrameId != 0:
+ fileName = self.getRawFrameData(fileNameFrameId)
+ if fileName and modNo != 0:
+ self.setMod(modNo, fileName)
+
+ return
+
+ def createBpKey(self, fileName, lineNo):
+ #
+ # This is to work around a bug in dbg module where it changes the path
+ # names that we pass to it to lower cases.
+ #
+ if sysutils.isWindows():
+ fileName = fileName.lower()
+
+ return (fileName, lineNo)
+
+ def setLsnrAction(self, actioni):
+ self.lsnrAction = actioni
+ return
+
+ def getLsnrAction(self):
+ return self.lsnrAction
+
+ def currConnFinished(self):
+ self.clearInternals()
+ self.setLsnrAction(PHPDebuggerCallback.ACTION_LISTEN)
+ self.actionEvent.set()
+ self.ui.DisableAfterStop()
+
+ def stopPhpDbg(self):
+ #
+ # TODO: should send a request to stop the current running PHP program.
+ # should handle network blocking issue correctly, otherwise, we
+ # might hang here.
+ #
+ reqPacket = PHPDBGPacket(DBGA_STOP)
+
+ try:
+ reqPacket.sendPacket(self.lsnrThr)
+ self.awaitAndHandleResponse(stopping = True)
+ except PHPDBGConnException:
+ pass
+
+ self.currConnFinished()
+ return
+
+ def stopLsnr(self):
+ if not self.lsnrThr:
+ return
+
+ #
+ # Then we try to stop our listener thread.
+ #
+ if self.lsnrThr.hasBeenConnected():
+ #
+ # If the listener thread has already accepted a connection from a
+ # php debug module/client, it is sleeping now and wait for this
+ # condition object to be set so that it can exit.
+ #
+ self.setLsnrAction(PHPDebuggerCallback.ACTION_STOP)
+ self.actionEvent.set()
+ else:
+ #
+ # If the listener thread has never been connected from a php debug
+ # module/client, it is still blocking on a accept() call. We
+ # connect to it here and send a special shutdown command asking it
+ # to exit.
+ #
+ shutdownMessage = IntToC4(DBG_SYNC) + IntToC4(DBGC_AG_SHUTDOWN_REQ) + IntToC4(0) + IntToC4(0)
+ tempSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ tempSocket.connect((self.lsnrHost, self.lsnrPort))
+ tempSocket.sendall(shutdownMessage)
+ except:
+ myprint("shutdown connection/send message got exception!")
+
+ tempSocket.close()
+
+ self.lsnrThr.join()
+ self.lsnrThr = None
+
+
+class PHPDBGLsnrThr(threading.Thread):
+ def __init__(self, interfacei, hosti, porti, actionEventi, uii):
+ threading.Thread.__init__(self)
+ self.interface = interfacei
+ self.svrHost = hosti
+ self.svrPort = porti
+ self.actionEvent = actionEventi
+ self.svrSocket = None
+ self.clntConn = None
+ self.clntAddr = None
+ self.nonBlockingTimeout = 1
+ self.connHeader = None
+ self.ui = uii
+
+ def initSvrSocket(self):
+ self.svrSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.svrSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.svrSocket.bind((self.svrHost, self.svrPort))
+
+ def waitForClntConn(self):
+ self.svrSocket.listen(5)
+ self.clntConn, self.clntAddr = self.svrSocket.accept()
+ self.clntConn.settimeout(self.nonBlockingTimeout)
+
+ def run(self):
+ #
+ # Initialize this server socket.
+ #
+ self.initSvrSocket()
+
+ while True:
+ #
+ # Block until we get a new connection from a php debug client or our
+ # debugger ui (with a special shutting down header/command).
+ #
+ self.waitForClntConn()
+
+ #
+ # Ok, a new connection comes in ... Read the header to see where it
+ # comes from.
+ #
+ self.connHeader = ResponseHeader(self)
+ if self.connHeader.command == DBGC_AG_SHUTDOWN_REQ:
+ #
+ # This is a special command coming from our UI asking this
+ # thread to exit. This only happens if after this thread has
+ # been waiting for new connections from PHP debug module, no one
+ # connects, and UI is ready to shutdown this thread.
+ #
+ self.shutdown()
+ break
+ else:
+ #
+ # Tell the main gui thread to handle this new connection.
+ #
+ wx.CallAfter(self.interface.newConnEventHandler)
+
+ #
+ # From now on, PHPDebuggerCallback will communicate with the php
+ # debug module using this thread's clntConn socket. This thread
+ # itself will keep sleeping until get notified to make some
+ # actions.
+ #
+ self.actionEvent.wait()
+ self.actionEvent.clear()
+
+ action = self.interface.getLsnrAction()
+ if action == PHPDebuggerCallback.ACTION_STOP:
+ self.shutdown()
+ break
+ elif action == PHPDebuggerCallback.ACTION_LISTEN:
+ if self.clntConn:
+ self.clntConn.shutdown(socket.SHUT_RDWR)
+ self.clntConn.close()
+ self.clntConn = None
+
+ continue
+ else:
+ continue
+
+ def shutdown(self):
+ #
+ # Cleanup and ready to exit.
+ #
+ self.clntConn.shutdown(socket.SHUT_RDWR)
+ self.clntConn.close()
+ self.svrSocket.close()
+
+ def recv(self, size, blocking = False):
+ if self.clntConn:
+ myprint("recv: trying to receive %d bytes of data ...", size)
+ if blocking:
+ self.clntConn.settimeout(None)
+ else:
+ self.clntConn.settimeout(self.nonBlockingTimeout)
+
+ try:
+ rData = self.clntConn.recv(size)
+ except socket.timeout:
+ myprint("recv: got timed out")
+ rData = None
+ except:
+ myprint("recv: got an unexpected exception: %s", sys.exc_info()[0])
+ raise PHPDBGConnException
+
+ return rData
+ else:
+ raise PHPDBGConnException
+
+ def sendall(self, message):
+ if self.clntConn:
+ try:
+ self.clntConn.sendall(message)
+ except:
+ myprint("sendall: got an unexpected exception: %s", sys.exc_info()[0])
+ raise PHPDBGConnException
+ else:
+ raise PHPDBGConnException
+
+ def hasBeenConnected(self):
+ return self.clntConn != None
+
+ def getConnHeader(self):
+ return self.connHeader
+
+
+class PHPValue(object):
+ PEV_NAMES = ("undefined", "long", "double", "string", "array", "object", "boolean", "resource", "reference", "soft reference", "null")
+ PEVT_UNKNOWN = 0
+ PEVT_LONG = 1
+ PEVT_DOUBLE = 2
+ PEVT_STRING = 3
+ PEVT_ARRAY = 4
+ PEVT_OBJECT = 5
+ PEVT_BOOLEAN = 6
+ PEVT_RESOURCE = 7
+ PEVT_REF = 8
+ PEVT_SOFTREF = 9
+ PEVT_NULL = 10
+
+ NULL_VALUE_STR = "NULL"
+ TRUE_VALUE_STR = "True"
+ FALSE_VALUE_STR = "False"
+ OBJECT_VALUE_STR = "<%s> object"
+ STRING_VALUE_STR = "\"%s\""
+ REFERENCE_VALUE_STR = "<reference><%s>"
+ RESOURCE_VALUE_STR = "<%s><%s>"
+
+ def __init__(self, frame, type, valueList):
+ self.fStackFrame = frame
+ self.fValueType = type
+
+ if type == self.PEVT_OBJECT:
+ self.fValueString = self.OBJECT_VALUE_STR % valueList[0]
+ self.fVariables = valueList[1:]
+ elif type == self.PEVT_ARRAY:
+ self.fValueString = ''
+ self.fVariables = valueList
+ else:
+ self.fVariables = []
+ if type == self.PEVT_STRING:
+ self.fValueString = self.STRING_VALUE_STR % valueList[0]
+ elif type == self.PEVT_NULL:
+ self.fValueString = self.NULL_VALUE_STR
+ elif type == self.PEVT_BOOLEAN:
+ if valueList[0] == "0":
+ self.fValueString = self.FALSE_VALUE_STR
+ else:
+ self.fValueString = self.TRUE_VALUE_STR
+ elif type == self.PEVT_REF or type == self.PEVT_SOFTREF:
+ self.fValueString = self.REFERENCE_VALUE_STR % valueList[0]
+ elif type == self.PEVT_RESOURCE:
+ self.fValueString = self.RESOURCE_VALUE_STR % (valueList[0], valueList[1])
+ else:
+ self.fValueString = valueList[0]
+
+ def addVariable(self, item):
+ if item != None:
+ self.fVariables.append(item)
+
+ return self.fVariables
+
+ def setParent(self, parent):
+ if self.fVariables != None and len(self.fVariables) > 0:
+ for item in self.fVariables:
+ item.setParent(parent)
+
+ def getReferenceType(self):
+ return self.fValueType
+
+ def getReferenceTypeName(self):
+ return self.PEV_NAMES[self.fValueType]
+
+ def setReferenceType(self, type):
+ self.fValueType = type
+
+ def getValueString(self):
+ return self.fValueString
+
+ def getChildrenVariables(self):
+ return self.fVariables
+
+ def hasVariables(self):
+ return len(self.fVariables) > 0
+
+ def childrenIsSortable(self):
+ #
+ # TODO: if self.fValueType != self.PEVT_ARRAY:
+ #
+ return True
+
+
+class PHPVariable(object):
+ def __init__(self, frame, parent, valueType, name, valueList):
+ self.fStackFrame = frame
+ self.fName = None
+ self.fLongName = None
+ self.fPureName = None
+ self.fValue = PHPValue(frame, valueType, valueList)
+ self.fParent = parent
+ self.setName(name)
+ self.setChildrensParent(valueList)
+
+ def setName(self, name):
+ self.fPureName = name
+
+ type = self.getReferenceType()
+ if type == PHPValue.PEVT_ARRAY:
+ numItems = len(self.getChildrenVariables())
+ self.fName = name + "[" + str(numItems) + "]"
+ else:
+ self.fName = name
+
+ if not self.fParent or self.fParent.getName() == None:
+ self.fLongName = name
+ else:
+ self.setLongName()
+
+ return
+
+ def setLongName(self):
+ parentType = self.fParent.getReferenceType()
+ if parentType == PHPValue.PEVT_ARRAY:
+ self.fLongName = self.fParent.getLongName() + "['" + self.fPureName + "']"
+ elif parentType == PHPValue.PEVT_OBJECT:
+ self.fLongName = self.fParent.getLongName() + "." + self.fName
+ else:
+ self.fLongName = self.fName
+
+ return
+
+ def getValue(self):
+ return self.fValue
+
+ def getValueString(self):
+ return self.fValue.getValueString()
+
+ def getChildrenVariables(self):
+ return self.fValue.getChildrenVariables()
+
+ def getName(self):
+ return self.fName
+
+ def getParent(self):
+ return self.fParent
+
+ def setParent(self, parent):
+ self.fParent = parent
+ self.setLongName()
+ return
+
+ def setChildrensParent(self, childrenList):
+ if self.fValue.hasVariables():
+ for child in self.fValue.getChildrenVariables():
+ child.setParent(self)
+
+ def getLongName(self):
+ return self.fLongName
+
+ def getReferenceTypeName(self):
+ return self.fValue.getReferenceTypeName()
+
+ def getReferenceType(self):
+ return self.fValue.getReferenceType()
+
+ def setReferenceType(self, type):
+ tp = self.getValue.setReferenceType(type)
+ return tp
+
+ def setValue(self, expression):
+ if self.fValue.getReferenceType() == PHPValue.PEVT_STRING:
+ evalString = self.fLongName + "=\"" + expression + "\""
+ else:
+ evalString = self.fLongName + "=" + expression
+
+ vars = self.fStackFrame.getPHPDBGInterface().evalBlock(self.fStackFrame, evalString)
+ self.fValue = vars[0].fValue
+
+ def toString(self):
+ rtype = self.getReferenceType()
+ if rtype == PHPValue.PEVT_ARRAY:
+ elements = len(self.fValue.getChildrenVariables())
+ if elements == 0:
+ tmpStr = self.getName() + " [no elements]"
+ elif elements == 1:
+ tmpStr = self.getName() + " [1 element]"
+ else:
+ tmpStr = self.getName() + " [" + str(elements) + " elements]"
+ elif rtype == PHPValue.PEVT_OBJECT:
+ tmpStr = self.getName() + " [ class: " + self.fValue.getValueString() + "]"
+ elif rtype == PHPValue.PEVT_STRING:
+ tmpStr = self.getName() + " = \"" + self.fValue.getValueString() + "\""
+ else:
+ tmpStr = self.getName() + " = " + self.fValue.getValueString()
+
+ return tmpStr
+
+ def hasChildren(self):
+ return self.fValue.hasVariables()
+
+ def childrenIsSortable(self):
+ return self.fValue.childrenIsSortable()
+
+
+class PHPStackFrame(object):
+ def __init__(self, interface, file, line, frameIndex, scopeId, desc, modNum):
+ self.interface = interface
+ self.fileName = file
+ self.lineNo = line
+ self.frameIndex = frameIndex
+ self.scopeId = scopeId
+ self.desc = desc
+ self.modNum = modNum
+ self.variables = []
+ self.shortFileName = None
+ self.shortDesc = None
+ self.displayStr = None
+ self.longDisplayStr = None
+
+ self._getFileNamesAndShortDesc()
+
+ myprint("PHPStackFrame::__init__(): new PHPStackFrame: file=%s, lineNo=%s, frameIndex=%s, scopeId=%s, desc=%s, modNum=%s, shortFileName=%s, shortDesc=%s", (repr(file), repr(line), repr(frameIndex), repr(scopeId), repr(desc), repr(modNum), repr(self.shortFileName), repr(self.shortDesc)))
+
+ def _getFileNamesAndShortDesc(self):
+ tmp = []
+ if self.desc:
+ tmp = self.desc.split("::")
+
+ if self.fileName:
+ self.shortFileName = os.path.basename(self.fileName)
+ if len(tmp) == 2:
+ self.shortDesc = tmp[1]
+ elif len(tmp) == 1:
+ self.shortDesc = tmp[0]
+ else:
+ self.shortDesc = None
+
+ return
+
+ #
+ # The fileName is None, we will try our best efforts to get it.
+ #
+ if len(tmp) == 2:
+ #
+ # We retrieved long finename from the description. If we haven't
+ # stored the file mod before, set this one as the new one.
+ # Otherwise, we prefer to keep the stored one.
+ #
+ if self.modNum != 0:
+ storedFileName = self.interface.getModByNum(self.modNum)
+ if not storedFileName:
+ self.interface.setMod(self.modNum, tmp[0])
+
+ self.fileName = tmp[0]
+ self.shortFileName = os.path.basename(tmp[0])
+ self.shortDesc = tmp[1]
+ elif len(tmp) == 1:
+ self.fileName = None
+ self.shortFileName = None
+ self.shortDesc = tmp[0]
+ else:
+ self.shortFileName = None
+ self.shortDesc = None
+ myprint("PHPStackFrame::_getFileNamesAndShortDesc(): something wrong with desc: %s?", self.desc)
+
+ return
+
+ def getShortFileName(self):
+ return self.shortFileName
+
+ def setShortFileName(self, shortFileName):
+ self.shortFileName = shortFileName
+
+ def getShortDesc(self):
+ return self.shortDesc
+
+ def getLineNo(self):
+ return self.lineNo
+
+ def getInterface(self):
+ return self.interface
+
+ def getVariables(self):
+ if len(self.variables) == 0:
+ self.variables = self.interface.getVariables(self)
+
+ return self.variables
+
+ def findVariables(self, s):
+ if self.hasVariables():
+ name = "$" + s
+ for var in self.variables:
+ if var.getName() == name:
+ return var
+
+ return None
+
+ def hasVariables(self):
+ if len(self.variables) == 0:
+ return False
+ else:
+ return True
+
+ def getName(self):
+ if self.getDesc():
+ return self.getDesc() + " [line: " + str(self.getLineNo()) + "]"
+ else:
+ return self.getFileName() + " [line: " + str(self.getLineNo()) + "]"
+
+ def getFileName(self):
+ return self.fileName
+
+ def setFileName(self, fileName):
+ self.fileName = fileName
+
+ def setDesc(self, desc):
+ self.desc = desc
+
+ def getDesc(self):
+ return self.desc
+
+ def getFrameScopeId(self):
+ return self.scopeId
+
+ def getFrameIndex(self):
+ return self.frameIndex
+
+ def getDisplayStr(self, stackList = None):
+ if self.displayStr:
+ return self.displayStr
+
+ if not self.shortFileName:
+ if stackList:
+ i = stackList.index(self)
+ for j in range(i + 1, len(stackList)):
+ self.shortFileName = stackList[j].getShortFileName()
+ if self.shortFileName:
+ self.fileName = stackList[j].getFileName()
+
+ if self.shortFileName:
+ if self.shortDesc:
+ self.displayStr = "<%s> at %s:%d" % (self.shortDesc, self.shortFileName, self.lineNo)
+ else:
+ self.displayStr = "%s:%d" % (self.shortFileName, self.lineNo)
+ else:
+ if self.shortDesc:
+ self.displayStr = "<%s>" % self.shortDesc
+ else:
+ self.displayStr = "<internal stack error>"
+
+ return self.displayStr
+
+ def getLongDisplayStr(self):
+ if self.longDisplayStr:
+ return self.longDisplayStr
+
+ if self.fileName:
+ if self.shortDesc:
+ self.longDisplayStr = "<%s> at %s:%d" % (self.shortDesc, self.fileName, self.lineNo)
+ else:
+ self.longDisplayStr = "%s:%d" % (self.fileName, self.lineNo)
+ else:
+ if self.shortDesc:
+ self.longDisplayStr = "<%s>" % self.shortDesc
+ else:
+ self.longDisplayStr = "<internal stack error>"
+
+ return self.longDisplayStr
+
+class BreakPoint(object):
+ def __init__(self, interface, fileName, lineNo, modNum = 0, state = BPS_ENABLED + BPS_UNRESOLVED, isTemp = 0, hitCount = 0, skipHits = 0, condition = 0, bpId = 0, isUnderHit = 0):
+ self.interface = interface
+ self.fileName = fileName
+ self.lineNo = lineNo
+ self.bpID = bpId
+ self.state = state
+ self.isTemp = isTemp
+ self.hitCount = hitCount
+ self.skipHits = skipHits
+ self.condition = condition
+ self.isUnderHit = 0
+ if modNum == 0:
+ self.modNum = self.interface.getModByFileName(fileName)
+ else:
+ self.modNum = modNum
+
+ if self.modNum:
+ self.fCounterOrZero = 0
+ else:
+ self.fCounterOrZero = interface.getNextFrameCounter()
+
+ def sendSelf(self):
+ reqPacket = PHPDBGPacket(DBGA_REQUEST)
+ reqFrame1 = PHPDBGFrame(FRAME_BPS)
+
+ if self.modNum:
+ reqFrame1.addInt(self.modNum)
+ else:
+ #
+ # 0 in modNum to tell to use fileName instead.
+ #
+ reqFrame1.addInt(0)
+
+ reqFrame1.addInt(self.lineNo) # lineNo
+ reqFrame1.addInt(self.fCounterOrZero) # fileName frameCounter or 0
+ reqFrame1.addInt(self.state) # state
+ reqFrame1.addInt(self.isTemp) # isTemp
+ reqFrame1.addInt(self.hitCount) # hitCount
+ reqFrame1.addInt(self.skipHits) # skipHits
+ reqFrame1.addInt(self.condition) # condition
+ reqFrame1.addInt(self.bpID) # breakpoint sequence id
+ reqFrame1.addInt(self.isUnderHit) # isUnderHit
+
+ if not self.modNum:
+ reqFrame2 = PHPDBGFrame(FRAME_RAWDATA)
+ reqFrame2.addInt(self.fCounterOrZero)
+ reqFrame2.addInt(len(self.fileName) + 1)
+ reqFrame2.addStr(self.fileName)
+ reqPacket.addFrame(reqFrame2)
+
+ reqPacket.addFrame(reqFrame1)
+
+ try:
+ reqPacket.sendPacket(self.interface.lsnrThr)
+ self.interface.awaitAndHandleResponse()
+ except PHPDBGConnException:
+ self.interface.currConnFinished()
+
+ return
+
+ def addSelf(self):
+ self.sendSelf()
+
+ def removeSelf(self):
+ self.state = BPS_DISABLED
+ self.sendSelf()
+
+ def isUnderHit(self):
+ return self.isUnderHit == 1
+
+ def update(self, dbgBp):
+ self.modNum = dbgBp[0]
+ self.state = dbgBp[3]
+ self.isTemp = dbgBp[4]
+ self.hitCount = dbgBp[5]
+ self.skipHits = dbgBp[6]
+ self.condition = dbgBp[7]
+ self.bpID = dbgBp[8]
+ self.isUnderHit = dbgBp[9]
+
+
+class PHPDBGEvalString(object):
+ def __init__(self, stackFrame, dataStr):
+ self.stackFrame = stackFrame
+ self.dataStr = dataStr
+
+ #
+ # Get a list of variables under self.stackFrame.
+ #
+ def getVars(self):
+ return self.parseAVariable(isRealVar = False)
+
+ #
+ # if isRealVar:
+ # returnList[0] = The Variable
+ # else:
+ # returnList = list of variables.
+ #
+ def parseAVariable(self, isRealVar = True):
+ returnList = []
+
+ #
+ # Get the variable name first. Notice we ignore this entity's data
+ # type here.
+ #
+ if isRealVar:
+ nameEntity = self.parseAnEntity()
+ if not nameEntity or len(nameEntity) != 2 or type(nameEntity[1]) != str:
+ myprint("PHPDBGEvalStr::parseAVariable() got a wrong name entity")
+ return returnList
+ else:
+ varName = nameEntity[1]
+
+ #
+ # Get the variable's value.
+ #
+ valueEntity = self.parseAnEntity()
+ if not valueEntity or len(valueEntity) < 1:
+ myprint("PHPDBGEvalStr::parseAVariable(): couldn't get a variable's value entity.")
+ return returnList
+
+ #
+ # This variable's data type.
+ #
+ varType = valueEntity[0]
+
+ if isRealVar:
+ #
+ # If this is a real variable, return a list which contains only
+ # this variable item.
+ #
+ #valueEntity = valueEntity[1:]
+ variable = PHPVariable(self.stackFrame, None, varType, varName, valueEntity[1:])
+ #myprint("xxxxCreated variable varName=%s, valueEntity=%s", (repr(varName), repr(valueEntity[1])))
+ myprint("xxxxCreated variable: %s", repr(variable.toString()))
+ returnList.append(variable)
+ else:
+ #
+ # If this is a root variable container, returns a list of
+ # variables under the root. Do a sanity check here.
+ #
+ if valueEntity[0] != PHPValue.PEVT_ARRAY:
+ myprint("PHPDBGEvalStr::parseAVariable(): failed to parse the root variable container.")
+ else:
+ returnList = valueEntity[1:]
+
+ return returnList
+
+ #
+ # An entity could be a variable's name or its value.
+ #
+ # returnList[0] = variable data type
+ # returnList[1:] = the real list
+ #
+ def parseAnEntity(self):
+ if not self.dataStr or len(self.dataStr) < 2 or (self.dataStr[1] != ':' and self.dataStr[1] != ';'):
+ myprint("PHPDBGEvalStr::parseAnEntity(): failed to parse %s.", repr(self.dataStr))
+ return None
+
+ returnList = []
+ typeChar = self.dataStr[0]
+ self.dataStr = self.dataStr[2:]
+ if typeChar == 'i':
+ returnList.append(PHPValue.PEVT_LONG)
+ self.parseInt(returnList)
+ elif typeChar == 'a':
+ returnList.append(PHPValue.PEVT_ARRAY)
+ self.parseArray(returnList)
+ elif typeChar == 's':
+ returnList.append(PHPValue.PEVT_STRING)
+ self.parseString(returnList)
+ elif typeChar == 'O':
+ returnList.append(PHPValue.PEVT_OBJECT)
+ self.parseObject(returnList)
+ elif typeChar == 'r':
+ returnList.append(PHPValue.PEVT_SOFTREF)
+ self.parseReference(returnList, isSoftRef = True)
+ elif typeChar == 'R':
+ returnList.append(PHPValue.PEVT_REF)
+ self.parseReference(returnList, isSoftRef = False)
+ elif typeChar == 'b':
+ returnList.append(PHPValue.PEVT_BOOLEAN)
+ self.parseBoolean(returnList)
+ elif typeChar == 'd':
+ returnList.append(PHPValue.PEVT_DOUBLE)
+ self.parseDouble(returnList)
+ elif typeChar == 'z':
+ returnList.append(PHPValue.PEVT_RESOURCE)
+ self.parseResource(returnList)
+ elif typeChar == 'N':
+ returnList.append(PHPValue.PEVT_NULL)
+ self.parseNull(returnList)
+ else:
+ myprint("PHPDBGEvalStr::parseAnEntity(): unknown data type: %s", typeChar)
+
+ return returnList
+
+ def parseInt(self, returnList):
+ myprint("enter parseInt().")
+ returnList.append(self.getAnIntStr(';'))
+
+ return
+
+ def parseArray(self, returnList):
+ myprint("enter parseArray().")
+ #
+ # The shortest array is 'a:0:{}'.
+ #
+ if len(self.dataStr) < 4:
+ myprint("PHPDBGEvalStr::parseArray(): failed (1) to parse an array: %s.", repr(self.dataStr))
+ return
+
+ expectedNumItems = self.getAnInt(':')
+ if len(self.dataStr) < 2 or self.dataStr[0] != '{':
+ myprint("PHPDBGEvalStr::parseArray(): failed (3) to parse an array: %s.", repr(self.dataStr))
+ return
+
+ self.dataStr = self.dataStr[1:]
+ varList = []
+ while self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] != '}':
+ tmpList = self.parseAVariable()
+ if not tmpList or len(tmpList) != 1 or not tmpList[0]:
+ myprint("PHPDBGEvalStr::parseArray(): failed (4) to parse an array. dataStr=%s.", repr(self.dataStr))
+ break
+ else:
+ varList.append(tmpList[0])
+
+ if expectedNumItems != len(varList):
+ myprint("PHPDBGEvalStr::parseArray(): failed (5) expected no. of items=%d, but got %d", (expectedNumItems, len(varList)))
+
+ #
+ # An array should end with a '}'.
+ #
+ if self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] == '}':
+ self.dataStr = self.dataStr[1:]
+ returnList.extend(varList)
+ else:
+ myprint("PHPDBGEvalStr::parseArray(): failed (6) to parse an array. dataStr=%s.", repr(self.dataStr))
+
+ myprint("parseArray() ends.")
+ return
+
+ def parseString(self, returnList, endChar = ';'):
+ myprint("enter parseString().")
+ #
+ # The shortest string is 's:<str_len>:"<str>"<endChar>'.
+ #
+ if len(self.dataStr) < 5:
+ myprint("PHPDBGEvalStr::parseString(): failed (1) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ expectedStrLen = self.getAnInt(':')
+ if len(self.dataStr) < expectedStrLen + 3 or self.dataStr[0] != '"':
+ myprint("PHPDBGEvalStr::parseString(): failed (3) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ strValue = self.dataStr[1:expectedStrLen + 1]
+ if self.dataStr[expectedStrLen + 1:expectedStrLen + 3] != '"' + endChar:
+ myprint("PHPDBGEvalStr::parseString(): failed (4) to parse a string. dataStr=%s.", repr(self.dataStr))
+ return
+
+ #
+ # Skip the starting double quote, real string, ending double quote, and ending semicolon.
+ #
+ self.dataStr = self.dataStr[expectedStrLen + 3:]
+ returnList.append(strValue)
+
+ return
+
+ def parseObject(self, returnList):
+ #
+ # A simple sanity check. The shortest object is:
+ # 'O:<class_name_len>:"<class_name>":<num_of_items>:{<list_of_items>}'
+ #
+ if not self.dataStr or len(self.dataStr) < 10:
+ myprint("PHPDBGEvalStr::parseObject(): failed (1) to parse an object: %s.", repr(self.dataStr))
+
+ #
+ # Get the class name in classNameList[0].
+ #
+ classNameList = []
+ self.parseString(classNameList, ':')
+
+ expectedNumItems = self.getAnInt(':')
+ if len(self.dataStr) < 2 or self.dataStr[0] != '{':
+ myprint("PHPDBGEvalStr::parseObject(): failed (2) to parse an object: %s.", repr(self.dataStr))
+ return
+
+ self.dataStr = self.dataStr[1:]
+ varList = []
+ while self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] != '}':
+ tmpList = self.parseAVariable()
+ if not tmpList or len(tmpList) != 1 or not tmpList[0]:
+ myprint("PHPDBGEvalStr::parseObject(): failed (3) to parse an object. dataStr=%s.", repr(self.dataStr))
+ break
+ else:
+ varList.append(tmpList[0])
+
+ if expectedNumItems != len(varList):
+ myprint("PHPDBGEvalStr::parseObject(): failed (4) expected no. of items=%d, but got %d", (expectedNumItems, len(varList)))
+
+ #
+ # An object should end with a '}'.
+ #
+ if self.dataStr and len(self.dataStr) > 0 and self.dataStr[0] == '}':
+ self.dataStr = self.dataStr[1:]
+ returnList.append(classNameList[0])
+ returnList.extend(varList)
+ else:
+ myprint("PHPDBGEvalStr::parseObject(): failed (6) to parse an object. dataStr=%s.", repr(self.dataStr))
+
+ myprint("parseObject() ends.")
+ return
+
+ def parseReference(self, returnList, isSoftRef):
+ myprint("enter parseReference().")
+ intStr = self.getAnIntStr(';')
+ if intStr:
+ returnList.append(intStr)
+
+ return
+
+ def parseBoolean(self, returnList):
+ tmpBooleanStr = self.getAnIntStr(';')
+ returnList.append(tmpBooleanStr)
+
+ return
+
+ def parseDouble(self, returnList):
+ tmpStr = self.getAStrTillEndChar(';')
+ if tmpStr:
+ returnList.append(tmpStr)
+
+ return
+
+ def parseResource(self, returnList):
+ tmpList = []
+ self.parseString(tmpList, ':')
+
+ if len(tmpList) == 1:
+ returnList.extend(tmpList)
+ else:
+ return
+
+ resourceId = self.getAnIntStr(';')
+ if resourceId:
+ returnList.append(resourceId)
+
+ return
+
+ def parseNull(self, returnList):
+ return
+
+ def getAStrTillEndChar(self, endChar):
+ if len(self.dataStr) < 1:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): no more data string to work with.")
+ return
+
+ i = self.findNextChar(self.dataStr, endChar)
+ if i == -1:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): no double/float string supplied.")
+ return
+
+ tmpStr = self.dataStr[:i]
+ self.dataStr = self.dataStr[i + 1:]
+
+ if self.isFloat(tmpStr):
+ return tmpStr
+ else:
+ myprint("PHPDBGEvalStr::getAStrTillEndChar(): parsing error. tried to get an float number, but get %s.", tmpStr)
+ return None
+
+ def getAnInt(self, endChar):
+ tmpStr = self.getAnIntStr(endChar)
+ if tmpStr:
+ return int(tmpStr)
+ else:
+ return 0
+
+ def getAnIntStr(self, endChar):
+ if len(self.dataStr) == 0:
+ myprint("PHPDBGEvalStr::getAnIntStr(): no more data string to work with.")
+ return
+
+ i = self.findNextChar(self.dataStr, endChar)
+ if i == -1:
+ tmpStr = self.dataStr
+ self.dataStr = ''
+ else:
+ tmpStr = self.dataStr[:i]
+ self.dataStr = self.dataStr[i + 1:]
+
+ if self.isInt(tmpStr):
+ return tmpStr
+ else:
+ myprint("PHPDBGEvalStr::getAnIntStr(): parsing error. tried to get an integer, but get %s.", tmpStr)
+ return None
+
+ def isInt(self, aStr):
+ try:
+ int(aStr)
+ except ValueError:
+ return False
+
+ return True
+
+ def isFloat(self, aStr):
+ try:
+ float(aStr)
+ except ValueError:
+ return False
+
+ return True
+
+ def findNextChar(self, aStr, aChar):
+ try:
+ index = aStr.index(aChar)
+ except ValueError:
+ index = -1
+
+ return index
import OutlineService
import os
import re
+import FindInDirService
+import activegrid.util.appdirs as appdirs
+import activegrid.util.sysutils as sysutils
+_ = wx.GetTranslation
class PHPDocument(CodeEditor.CodeDocument):
def __init__(self, parent, id=-1, style=wx.NO_FULL_REPAINT_ON_RESIZE):
CodeEditor.CodeCtrl.__init__(self, parent, id, style)
- self.SetLexer(wx.stc.STC_LEX_PHP)
+ self.SetLexer(wx.stc.STC_LEX_HTML)
self.SetStyleBits(7)
self.SetKeyWords(4, string.join(PHPKEYWORDS))
self.SetProperty("fold.html", "1")
+ def CreatePopupMenu(self):
+ FINDCLASS_ID = wx.NewId()
+ FINDDEF_ID = wx.NewId()
+
+ menu = CodeEditor.CodeCtrl.CreatePopupMenu(self)
+
+ self.Bind(wx.EVT_MENU, self.OnPopFindDefinition, id=FINDDEF_ID)
+ menu.Insert(1, FINDDEF_ID, _("Find 'function'"))
+
+ self.Bind(wx.EVT_MENU, self.OnPopFindClass, id=FINDCLASS_ID)
+ menu.Insert(2, FINDCLASS_ID, _("Find 'class'"))
+
+ return menu
+
+
+ def OnPopFindDefinition(self, event):
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
+ pattern = view.GetCtrl().GetSelectedText().strip()
+ if pattern:
+ searchPattern = "function\s+%s" % pattern
+ wx.GetApp().GetService(FindInDirService.FindInDirService).FindInProject(searchPattern)
+
+
+ def OnPopFindClass(self, event):
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
+ definition = "class\s+%s"
+ pattern = view.GetCtrl().GetSelectedText().strip()
+ if pattern:
+ searchPattern = definition % pattern
+ wx.GetApp().GetService(FindInDirService.FindInDirService).FindInProject(searchPattern)
+
+
def CanWordWrap(self):
return True
def SetViewDefaults(self):
- CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "PHP", hasWordWrap = True, hasTabs = True)
+ CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "PHP", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetFontAndColorFromConfig(self):
class PHPOptionsPanel(STCTextEditor.TextOptionsPanel):
def __init__(self, parent, id):
- STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "PHP", label = "PHP", hasWordWrap = True, hasTabs = True)
-
+ wx.Panel.__init__(self, parent, id)
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ config = wx.ConfigBase_Get()
+
+ pathLabel = wx.StaticText(self, -1, _("PHP Executable Path:"))
+ path = config.Read("ActiveGridPHPLocation")
+ self._pathTextCtrl = wx.TextCtrl(self, -1, path, size = (150, -1))
+ self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue())
+ self._pathTextCtrl.SetInsertionPointEnd()
+ choosePathButton = wx.Button(self, -1, _("Browse..."))
+ pathSizer = wx.BoxSizer(wx.HORIZONTAL)
+ HALF_SPACE = 5
+ SPACE = 10
+ pathSizer.Add(pathLabel, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP, HALF_SPACE)
+ pathSizer.Add(self._pathTextCtrl, 1, wx.EXPAND|wx.LEFT|wx.TOP, HALF_SPACE)
+ pathSizer.Add(choosePathButton, 0, wx.ALIGN_RIGHT|wx.LEFT|wx.RIGHT|wx.TOP, HALF_SPACE)
+ wx.EVT_BUTTON(self, choosePathButton.GetId(), self.OnChoosePath)
+ mainSizer.Add(pathSizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, SPACE)
+
+ iniLabel = wx.StaticText(self, -1, _("php.ini Path:"))
+ ini = config.Read("ActiveGridPHPINILocation")
+ if not ini:
+ if sysutils.isRelease():
+ ini = os.path.normpath(os.path.join(appdirs.getSystemDir(), "php.ini"))
+ else:
+ tmp = self._pathTextCtrl.GetValue().strip()
+ if tmp and len(tmp) > 0:
+ ini = os.path.normpath(os.path.join(os.path.dirname(tmp), "php.ini"))
+
+ self._iniTextCtrl = wx.TextCtrl(self, -1, ini, size = (150, -1))
+ self._iniTextCtrl.SetToolTipString(self._iniTextCtrl.GetValue())
+ self._iniTextCtrl.SetInsertionPointEnd()
+ chooseIniButton = wx.Button(self, -1, _("Browse..."))
+ iniSizer = wx.BoxSizer(wx.HORIZONTAL)
+ HALF_SPACE = 5
+ SPACE = 10
+ iniSizer.Add(iniLabel, 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP, HALF_SPACE)
+ iniSizer.Add(self._iniTextCtrl, 1, wx.EXPAND|wx.LEFT|wx.TOP, HALF_SPACE)
+ iniSizer.Add(chooseIniButton, 0, wx.ALIGN_RIGHT|wx.LEFT|wx.RIGHT|wx.TOP, HALF_SPACE)
+ wx.EVT_BUTTON(self, chooseIniButton.GetId(), self.OnChooseIni)
+ mainSizer.Add(iniSizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, SPACE)
+
+ self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "PHP", label = "PHP", hasWordWrap = True, hasTabs = True, addPage=False, hasFolding=False)
+ #STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "PHP", label = "PHP", hasWordWrap = True, hasTabs = True)
+ mainSizer.Add(self._otherOptions, 0, wx.EXPAND|wx.BOTTOM, SPACE)
+
+ self.SetSizer(mainSizer)
+ parent.AddPage(self, _("PHP"))
+
+ def OnChoosePath(self, event):
+ defaultDir = os.path.dirname(self._pathTextCtrl.GetValue().strip())
+ defaultFile = os.path.basename(self._pathTextCtrl.GetValue().strip())
+ if wx.Platform == '__WXMSW__':
+ wildcard = _("Executable (*.exe)|*.exe|All|*.*")
+ if not defaultFile:
+ defaultFile = "php-cgi.exe"
+ else:
+ wildcard = _("*")
+ dlg = wx.FileDialog(wx.GetApp().GetTopWindow(),
+ _("Select a File"),
+ defaultDir=defaultDir,
+ defaultFile=defaultFile,
+ wildcard=wildcard,
+ style=wx.OPEN|wx.FILE_MUST_EXIST|wx.HIDE_READONLY)
+ # dlg.CenterOnParent() # wxBug: caused crash with wx.FileDialog
+ if dlg.ShowModal() == wx.ID_OK:
+ path = dlg.GetPath()
+ if path:
+ self._pathTextCtrl.SetValue(path)
+ self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue())
+ self._pathTextCtrl.SetInsertionPointEnd()
+ dlg.Destroy()
+
+ def OnChooseIni(self, event):
+ defaultDir = os.path.dirname(self._iniTextCtrl.GetValue().strip())
+ defaultFile = os.path.basename(self._iniTextCtrl.GetValue().strip())
+ if wx.Platform == '__WXMSW__':
+ wildcard = _("Ini (*.ini)|*.ini|All|*.*")
+ if not defaultFile:
+ defaultFile = "php.ini"
+ else:
+ wildcard = _("*")
+ dlg = wx.FileDialog(wx.GetApp().GetTopWindow(),
+ _("Select a File"),
+ defaultDir=defaultDir,
+ defaultFile=defaultFile,
+ wildcard=wildcard,
+ style=wx.OPEN|wx.FILE_MUST_EXIST|wx.HIDE_READONLY)
+ # dlg.CenterOnParent() # wxBug: caused crash with wx.FileDialog
+ if dlg.ShowModal() == wx.ID_OK:
+ ini = dlg.GetPath()
+ if ini:
+ self._iniTextCtrl.SetValue(ini)
+ self._iniTextCtrl.SetToolTipString(self._iniTextCtrl.GetValue())
+ self._iniTextCtrl.SetInsertionPointEnd()
+ dlg.Destroy()
+
+ def OnOK(self, optionsDialog):
+ config = wx.ConfigBase_Get()
+ config.Write("ActiveGridPHPLocation", self._pathTextCtrl.GetValue().strip())
+ config.Write("ActiveGridPHPINILocation", self._iniTextCtrl.GetValue().strip())
+
+ self._otherOptions.OnOK(optionsDialog)
def GetIcon(self):
return getPHPIcon()
def SetViewDefaults(self):
- CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Perl", hasWordWrap = True, hasTabs = True)
+ CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Perl", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetFontAndColorFromConfig(self):
class PerlOptionsPanel(STCTextEditor.TextOptionsPanel):
def __init__(self, parent, id):
- STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Perl", label = "Perl", hasWordWrap = True, hasTabs = True)
+ STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Perl", label = "Perl", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetIcon(self):
#
# Created: 8/15/03
# CVS-ID: $Id$
-# Copyright: (c) 2003, 2004, 2005 ActiveGrid, Inc.
+# Copyright: (c) 2003-2006 ActiveGrid, Inc.
# License: wxWindows License
#----------------------------------------------------------------------------
import types
import activegrid.util.appdirs as appdirs
import activegrid.util.fileutils as fileutils
+import activegrid.util.aglogging as aglogging
import UICommon
import Wizard
import SVNService
import activegrid.server.deployment as deploymentlib
import ProcessModelEditor
import DataModelEditor
+ import DeploymentGeneration
import WsdlAgEditor
+ import WsdlAgModel
APP_LAST_LANGUAGE = "LastLanguage"
import activegrid.model.basedocmgr as basedocmgr
import activegrid.model.basemodel as basemodel
+ import activegrid.model.projectmodel as projectmodel
import PropertyService
from activegrid.server.toolsupport import GetTemplate
import activegrid.util.xmlutils as xmlutils
import activegrid.util.sysutils as sysutils
+ DataServiceExistenceException = DeploymentGeneration.DataServiceExistenceException
+ import WebBrowserService
from SVNService import SVN_INSTALLED
HALF_SPACE = 5
PROJECT_EXTENSION = ".agp"
+if not ACTIVEGRID_BASE_IDE:
+ PRE_17_TMP_DPL_NAME = "RunTime_tmp" + deploymentlib.DEPLOYMENT_EXTENSION
+ _17_TMP_DPL_NAME = ".tmp" + deploymentlib.DEPLOYMENT_EXTENSION
+
# wxBug: the wxTextCtrl and wxChoice controls on Mac do not correctly size
# themselves with sizers, so we need to add a right border to the sizer to
# get the control to shrink itself to fit in the sizer.
PROJECT_KEY = "/AG_Projects"
PROJECT_DIRECTORY_KEY = "NewProjectDirectory"
-NEW_PROJECT_DIRECTORY_DEFAULT = appdirs.documents_folder
+NEW_PROJECT_DIRECTORY_DEFAULT = appdirs.getSystemDir()
#----------------------------------------------------------------------------
# Methods
#----------------------------------------------------------------------------
-def getProjectKeyName(projectName, mode):
- return "%s/%s/%s" % (PROJECT_KEY, projectName.replace(os.sep, '|'), mode)
+def AddProjectMapping(doc, projectDoc=None, hint=None):
+ projectService = wx.GetApp().GetService(ProjectService)
+ if projectService:
+ if not projectDoc:
+ if not hint:
+ hint = doc.GetFilename()
+ projectDocs = projectService.FindProjectByFile(hint)
+ if projectDocs:
+ projectDoc = projectDocs[0]
+
+ projectService.AddProjectMapping(doc, projectDoc)
+ if hasattr(doc, "GetModel"):
+ projectService.AddProjectMapping(doc.GetModel(), projectDoc)
+
+
+def getProjectKeyName(projectName, mode=None):
+ if mode:
+ return "%s/%s/%s" % (PROJECT_KEY, projectName.replace(os.sep, '|'), mode)
+ else:
+ return "%s/%s" % (PROJECT_KEY, projectName.replace(os.sep, '|'))
def GetDocCallback(filepath):
""" Get the Document used by the IDE and the in-memory document model used by runtime engine """
docMgr = wx.GetApp().GetDocumentManager()
- doc = docMgr.CreateDocument(filepath, docMgr.GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW)
- if (doc == None): # already open
- for d in docMgr.GetDocuments():
- if os.path.normcase(d.GetFilename()) == os.path.normcase(filepath):
- doc = d
- break
- else:
- projectService = wx.GetApp().GetService(ProjectService)
- if projectService:
- projectDocs = projectService.FindProjectByFile(filepath)
- if projectDocs:
- projectDoc = projectDocs[0]
- projectService.AddProjectMapping(doc, projectDoc)
- if hasattr(doc, "GetModel"):
- projectService.AddProjectMapping(doc.GetModel(), projectDoc)
-
-
- if doc and doc.GetDocumentTemplate().GetDocumentType() == WsdlAgEditor.WsdlAgDocument:
- # get referenced wsdl doc instead
- if os.path.isabs(doc.GetModel().filePath): # if absolute path, leave it alone
- filepath = doc.GetModel().filePath
- else:
- filepath = doc.GetAppDocMgr().fullPath(doc.GetModel().filePath) # check relative to project homeDir
-
- if not os.path.isfile(filepath):
- filepath = os.path.normpath(os.path.join(os.path.dirname(doc.GetFilename()), doc.GetModel().filePath)) # check relative to wsdlag file
-
- if not os.path.isfile(filepath):
- filename = os.sep + os.path.basename(doc.GetModel().filePath) # check to see if in project file
- filePaths = findDocumentMgr(doc).filePaths
- for fp in filePaths:
- if fp.endswith(filename):
- filepath = fp
- break
-
+ try:
doc = docMgr.CreateDocument(filepath, docMgr.GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW)
- if (doc == None): # already open
+ if doc:
+ AddProjectMapping(doc)
+ else: # already open
for d in docMgr.GetDocuments():
if os.path.normcase(d.GetFilename()) == os.path.normcase(filepath):
doc = d
break
+ except Exception,e:
+ doc = None
+ aglogging.reportException(e, stacktrace=True)
+
+ if doc and doc.GetDocumentTemplate().GetDocumentType() == WsdlAgEditor.WsdlAgDocument:
+ # get referenced wsdl doc instead
+ if doc.GetModel().filePath:
+ if os.path.isabs(doc.GetModel().filePath): # if absolute path, leave it alone
+ filepath = doc.GetModel().filePath
+ else:
+ filepath = doc.GetAppDocMgr().fullPath(doc.GetModel().filePath) # check relative to project homeDir
+
+ if not os.path.isfile(filepath):
+ filepath = os.path.normpath(os.path.join(os.path.dirname(doc.GetFilename()), doc.GetModel().filePath)) # check relative to wsdlag file
+
+ if not os.path.isfile(filepath):
+ filename = os.sep + os.path.basename(doc.GetModel().filePath) # check to see if in project file
+ filePaths = findDocumentMgr(doc).filePaths
+ for fp in filePaths:
+ if fp.endswith(filename):
+ filepath = fp
+ break
+
+ try:
+ doc = docMgr.CreateDocument(filepath, docMgr.GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW)
+ except Exception,e:
+ doc = None
+ aglogging.reportException(e, stacktrace=True)
+
+ if doc:
+ AddProjectMapping(doc)
+ else: # already open
+ for d in docMgr.GetDocuments():
+ if os.path.normcase(d.GetFilename()) == os.path.normcase(filepath):
+ doc = d
+ break
else:
- projectService = wx.GetApp().GetService(ProjectService)
- if projectService:
- projectDocs = projectService.FindProjectByFile(filepath)
- if projectDocs:
- projectDoc = projectDocs[0]
- projectService.AddProjectMapping(doc, projectDoc)
- if hasattr(doc, "GetModel"):
- projectService.AddProjectMapping(doc.GetModel(), projectDoc)
+ doc = None
if doc:
docModel = doc.GetModel()
# Classes
#----------------------------------------------------------------------------
-class ProjectDocument(wx.lib.docview.Document):
+if not ACTIVEGRID_BASE_IDE:
+ class IDEResourceFactory(DeploymentGeneration.DeploymentResourceFactory):
+
+ def __init__(self, openDocs, dataSourceService, projectDir,
+ preview=False, deployFilepath=None):
+
+ self.openDocs = openDocs
+ self.dataSourceService = dataSourceService
+ self.projectDir = projectDir
+ self.preview = preview
+ self.deployFilepath = deployFilepath
+
+ self.defaultFlagsNoView = (
+ wx.GetApp().GetDocumentManager().GetFlags()|
+ wx.lib.docview.DOC_SILENT|
+ wx.lib.docview.DOC_OPEN_ONCE|
+ wx.lib.docview.DOC_NO_VIEW)
+
+ def getModel(self, projectFile):
+ doc = wx.GetApp().GetDocumentManager().CreateDocument(
+ projectFile.filePath, flags=self.defaultFlagsNoView)
+ if (doc == None): # already open
+ doc = self._findOpenDoc(projectFile.filePath)
+ else:
+ AddProjectMapping(doc)
+ if (doc != None):
+ return doc.GetModel()
+
+ def getDataSource(self, dataSourceName):
+ # in preview mode, runtime needs the generated Deployment
+ # to contain the requried data source. But runtime doesn't
+ # actually need to communicate to db. So here is the logic to
+ # make preview works if the required data soruce has not
+ # yet been defined.
+ dataSource = self.dataSourceService.getDataSource(dataSourceName)
+ if (dataSource != None):
+ return dataSource
+ elif not self.preview:
+ raise DataServiceExistenceException(dataSourceName)
+ else:
+ # first to see if an existing dpl file is there, if so,
+ # use the data source in dpl file
+ if (self.deployFilepath != None):
+ tempDply = None
+ try:
+ tempDply = xmlutils.load(deployFilepath)
+ except:
+ pass
+ if (tempDply != None):
+ for tempDataSource in tempDply.dataSources:
+ if (tempDataSource.name == dataSourceName):
+ return tempDataSource
+
+ # if unable to use dpl file, then create a dummy data source
+ import activegrid.data.dataservice as dataservice
+ return dataservice.DataSource(
+ name=dataSourceName, dbtype=dataservice.DB_TYPE_SQLITE)
+
+ def initDocumentRef(self, projectFile, documentRef, dpl):
+ doc = self._findOpenDoc(projectFile.filePath)
+ if (doc and hasattr(doc, 'GetModel')):
+ documentRef.document = doc.GetModel()
+ if isinstance(documentRef, deploymentlib.XFormRef):
+ doc.GetModel().linkDeployment(dpl, dpl.loader)
+
+ def _findOpenDoc(self, filePath):
+ for openDoc in self.openDocs:
+ if openDoc.GetFilename() == filePath:
+ return openDoc
+ return None
+
+ def getProjectDir(self):
+ return self.projectDir
+
+class ProjectDocument(wx.lib.docview.Document):
def __init__(self, model=None):
wx.lib.docview.Document.__init__(self)
return [fileRef.filePath for fileRef in invalidFileRefs]
+
def SetStageProjectFile(self):
self._stageProjectFile = True
- def ArchiveProject(self, zipdest, tmpdir=None, stagedir=None):
- """Stages the application files in tmpdir, and zips the stagedir, creating a zipfile that has the projectname, in zipdest. Returns path to zipfile. Optionally, pass in stagedir and we assume the app is already staged at stagedir (we don't stage again in that case)."""
- if not stagedir:
- if not tmpdir:
- raise AssertionError("'tmpdir' must be set when not passing 'stagedir' so we know where to stage the app")
- stagedir = self.StageProject(tmpdir)
+
+ def ArchiveProject(self, zipdest, stagedir):
+ """Zips stagedir, creates a zipfile that has as name the projectname, in zipdest. Returns path to zipfile."""
if os.path.exists(zipdest):
raise AssertionError("Cannot archive project, %s already exists" % zipdest)
fileutils.zip(zipdest, stagedir)
return zipdest
-
- def StageProject(self, tmpdir):
- """ Copies all files that project knows about into staging location. Files that live outside of the project dir are copied into the root of the stage dir, and their recorded file path is updated. Files that live inside of the project dir keep their relative path. Generates .dpl file into staging dir. Returns path to staging dir."""
+
+ def StageProject(self, tmpdir, targetDataSourceMapping={}):
+ """ Copies all files this project knows about into staging location. Files that live outside of the project dir are copied into the root of the stage dir, and their recorded file path is updated. Files that live inside of the project dir keep their relative path. Generates .dpl file into staging dir. Returns path to staging dir."""
projname = self.GetProjectName()
stagedir = os.path.join(tmpdir, projname)
# copy files to staging dir
self._StageFiles(fileDict)
+ # set target data source for schemas
+ self._SetSchemaTargetDataSource(fileDict, targetDataSourceMapping)
+
# it is unfortunate we require this. it would be nice if filepaths
# were only in the project
self._FixWsdlAgFiles(stagedir)
# generate .dpl file
dplfilename = projname + deploymentlib.DEPLOYMENT_EXTENSION
dplfilepath = os.path.join(stagedir, dplfilename)
- self.GenerateDeployment(dplfilepath, productionDeployment=True)
+ self.GenerateDeployment(dplfilepath)
if self._stageProjectFile:
# save project so we get the .agp file. not required for deployment
# but convenient if user wants to open the deployment in the IDE
agpfilename = projname + PROJECT_EXTENSION
- agpfilepath = os.path.join(stagedir, agpfilename)
+ agpfilepath = os.path.join(stagedir, agpfilename)
+
+ # if this project has deployment data sources configured, remove
+ # them. changing the project is fine, since this is a clone of
+ # the project the IDE has.
+ self.GetModel().GetAppInfo().ResetDeploymentDataSources()
+
f = None
try:
f = open(agpfilepath, "w")
+
# setting homeDir correctly is required for the "figuring out
# relative paths" logic when saving the project
self.GetModel().homeDir = stagedir
+
projectlib.save(f, self.GetModel(), productionDeployment=True)
finally:
try:
return stagedir
-
def _FixWsdlAgFiles(self, stagedir):
- """For each wsdlag file in the stagedir:
- Ensure the referenced wsdl file lives in root of stagedir. This
- should be the case if wsdl is part of project (and staging has run).
- If it is not at root of stagedir, copy it. Then update path in
- wsdlag."""
+ """For each wsdlag file in the stagedir: if referenced artifact (wsdl or code file) is a known product file (such as securityservice.wsdl), make sure patch to it is parameterized with special env var. We do not want to copy those files. For user artifacts, ensure the file lives in root of stagedir. This should be the case if it is part of project (since staging has run). If it is not at root of stagedir, copy it. Then update path in wsdlag."""
files = os.listdir(stagedir)
for f in files:
- if f.endswith(WsdlAgEditor.WsdlAgDocument.WSDL_AG_EXT):
+ if (f.endswith(WsdlAgEditor.WsdlAgDocument.WSDL_AG_EXT)):
wsdlagpath = os.path.join(stagedir, f)
fileObject = None
- mod = False
+ modified = False
try:
fileObject = open(wsdlagpath)
serviceref = WsdlAgEditor.load(fileObject)
- if hasattr(serviceref, "filePath") and serviceref.filePath:
- mod = self.UpdateServiceRefFilePath(stagedir,serviceref)
+
+ # referenced wsdl
+ if (hasattr(serviceref, WsdlAgModel.WSDL_FILE_ATTR)):
+ modified = (modified |
+ self._UpdateServiceRefPathAttr(
+ stagedir, serviceref,
+ WsdlAgModel.WSDL_FILE_ATTR))
+
+ # referenced code file
+ if (hasattr(serviceref, WsdlAgModel.LOCAL_SERVICE_ELEMENT)):
+ lse = getattr(serviceref,
+ WsdlAgModel.LOCAL_SERVICE_ELEMENT)
+ if (hasattr(lse, WsdlAgModel.LOCAL_SERVICE_FILE_ATTR)):
+ modified = (modified |
+ self._UpdateServiceRefPathAttr(
+ stagedir, lse,
+ WsdlAgModel.LOCAL_SERVICE_FILE_ATTR))
+
+
finally:
try:
fileObject.close()
pass
# no need to save the file if we did not change anything
- if not mod: continue
+ if not modified: continue
# write the wsdlag file
fileObject = open(wsdlagpath)
pass
- def UpdateServiceRefFilePath(self, stagedir, serviceref):
- """Returns True if serviceref.filePath has been updated, False otherwise."""
- if not os.path.exists(serviceref.filePath):
- # should be an error? wrong place to
- # validate that referenced file exists
- # could print warning
+ def _UpdateServiceRefPathAttr(self, stagedir, serviceref, attrName):
+ """Returns True if serviceref path has been updated, False otherwise."""
+
+ filePath = getattr(serviceref, attrName)
+
+ if (filePath == None):
+ return False
+
+ filePath = filePath.strip()
+
+ if (len(filePath) == 0):
+ return False
+
+
+ # if filePath starts with one of the AG systems vars, we don't
+ # have to do anything
+ if (fileutils.startsWithAgSystemVar(filePath)):
+ return False
+
+ # remove any known env var refs (we'll put them back a little below)
+ # we remove them here so that paths that do not have env vars also
+ # get parameterized correctly below
+ filePath = fileutils.expandKnownAGVars(filePath)
+
+ # make sure we have forward slashes. this is a workaround, which
+ # would not be necessary if we only write paths with forward slashes
+ # into our files
+ filePath = filePath.replace("\\", "/")
+
+ filePath = os.path.abspath(filePath)
+
+ if (not os.path.exists(filePath)):
+ # Wrong place to validate that referenced file exists, so just
+ # give up
return False
# If the referenced file is in stagedir already, there's nothing to do
- if fileutils.hasAncestorDir(serviceref.filePath, stagedir):
+ if (fileutils.hasAncestorDir(filePath, stagedir)):
return False
# The path points outside of stagedir.
# Check if we already have the referenced wsdl file at root, should be
- # the case if the referenced wsdl is part of project
- # Copy it if we don't have it
- relPath = os.path.basename(serviceref.filePath)
- stagepath = os.path.join(stagedir, relPath)
- if not os.path.exists(stagepath):
- fileutils.copyFile(serviceref.filePath, stagepath)
-
- serviceref.filePath = relPath
+ # the case if the referenced wsdl is part of project.
+ # Copy it if we don't have it, unless it lives in one of the known
+ # product directories - in which case we parameterize the known path
+ # with one of our AG system vars
+ relPath = os.path.basename(filePath)
+ stagePath = os.path.join(stagedir, relPath)
+
+ if (not os.path.exists(stagePath)):
+ pFilePath = fileutils.parameterizePathWithAGSystemVar(filePath)
+ if pFilePath == filePath: # no parameterization happened, copy
+ fileutils.copyFile(filePath, stagePath)
+ setattr(serviceref, attrName, relPath)
+ else:
+ setattr(serviceref, attrName, pFilePath.replace("\\", "/"))
+ else:
+ setattr(serviceref, attrName, relPath)
return True
+
+
+ def _SetSchemaTargetDataSource(self, projectFiles, dsmapping):
+ """Update schema's default data source, if necessary."""
+
+ for projectFile in projectFiles:
+ if (projectFile.type == basedocmgr.FILE_TYPE_SCHEMA):
+ name = os.path.basename(projectFile.filePath)
+ if (dsmapping.has_key(name)):
+ schema = xmlutils.load(projectFile.filePath)
+ defaultName = schema.getDefaultDataSourceName()
+ if (defaultName != dsmapping[name]):
+ schema.setDefaultDataSourceName(dsmapping[name])
+ xmlutils.save(projectFile.filePath, schema)
def _StageFiles(self, fileDict):
for filename in foreignFiles:
if filename in projectRootFiles:
raise IOError("File outside of project, \"%s\", cannot have same name as file at project root" % filename)
-
- # REVIEW stoens@activegrid.com 19-Oct-05 --
- # We could also validate that user does not already have a .dpl file
- # since we're going to generate one...
-
return rtn
self.Modify(True)
return True
+ def GetSchemas(self):
+ """Returns list of schema models (activegrid.model.schema.schema) for all schemas in this project."""
+
+ rtn = []
+ resourceFactory = self._GetResourceFactory()
+ for projectFile in self.GetModel().projectFiles:
+ if (projectFile.type == basedocmgr.FILE_TYPE_SCHEMA):
+ schema = resourceFactory.getModel(projectFile)
+ if (schema != None):
+ rtn.append(schema)
+ return rtn
+
def GetFiles(self):
return self.GetModel().filePaths
return os.path.splitext(os.path.basename(self.GetFilename()))[0]
- def GetDeploymentFilepath(self):
- projectName = self.GetProjectName()
- return os.path.join(self.GetModel().homeDir, projectName + "RunTime_tmp" + deploymentlib.DEPLOYMENT_EXTENSION)
-
+ def GetDeploymentFilepath(self, pre17=False):
+ if (pre17):
+ name = self.GetProjectName() + PRE_17_TMP_DPL_NAME
+ else:
+ name = self.GetProjectName() + _17_TMP_DPL_NAME
+ return os.path.join(self.GetModel().homeDir, name)
+
- def GenerateDeployment(self, deployFilepath=None, preview=False, productionDeployment=False):
+ def _GetResourceFactory(self, preview=False, deployFilepath=None):
+ return IDEResourceFactory(
+ openDocs=wx.GetApp().GetDocumentManager().GetDocuments(),
+ dataSourceService=wx.GetApp().GetService(DataModelEditor.DataSourceService),
+ projectDir=os.path.dirname(self.GetFilename()),
+ preview=preview,
+ deployFilepath=deployFilepath)
+
+ def GenerateDeployment(self, deployFilepath=None, preview=False):
+
if ACTIVEGRID_BASE_IDE:
return
-
- def FindOpenDoc(filePath):
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for openDoc in openDocs:
- if openDoc.GetFilename() == filePath:
- return openDoc
- return None
if not deployFilepath:
deployFilepath = self.GetDeploymentFilepath()
-
- deployment = deploymentlib.Deployment(deployFilepath)
- defaultFlagsNoView = wx.GetApp().GetDocumentManager().GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW
- self.GetAppInfo().CopyToDeployment(deployment)
-
- for file in self.GetModel()._files:
- if not file.type:
- continue
- elif file.type == basedocmgr.FILE_TYPE_SERVICE: # set serviceRefs
- doc = wx.GetApp().GetDocumentManager().CreateDocument(file.filePath, flags=defaultFlagsNoView)
- if (doc == None): # already open
- doc = FindOpenDoc(file.filePath)
- if doc:
- serviceRef = doc.GetModel()
- if serviceRef:
- documentRef = copy.copy(serviceRef)
- deployment.serviceRefs.append(documentRef)
-
- if not productionDeployment:
- # filePath should point to location of wsdl file
- # wsdlag filePath points to relative path to wsdl file from wsdlag location
- # but deployment needs relative path from deployment location, so here's the conversion
- curDir = os.path.dirname(self.GetFilename()) + os.sep
- filePath = file.document.fileName
- if (filePath == None):
- raise Exception("Cannot find file \"%s\"" % file.filePath)
- if filePath.startswith(curDir):
- filePath = filePath[len(curDir):]
- if os.sep != '/':
- filePath = filePath.replace(os.sep, "/")
- documentRef.filePath = filePath
-
- documentRef.document = file.document
-
- if serviceRef.serviceType == deploymentlib.SERVICE_DATABASE and serviceRef.databaseService:
- dataSourceService = wx.GetApp().GetService(DataModelEditor.DataSourceService)
- ds = dataSourceService.getDataSource(serviceRef.databaseService.datasourceName)
- if ds:
- found = False
- for d in deployment.dataSources:
- if d.name == ds.name:
- found = True
- break
- if not found:
- deployment.dataSources.append(ds)
- else:
- curDir = os.path.dirname(self.GetFilename()) + os.sep
- filePath = file.filePath
- if filePath.startswith(curDir):
- filePath = filePath[len(curDir):]
- if os.sep != '/':
- filePath = filePath.replace(os.sep, "/")
-
- if file.type == basedocmgr.FILE_TYPE_XFORM:
- documentRef = deploymentlib.XFormRef()
- deployment.xformRefs.append(documentRef)
- elif file.type == basedocmgr.FILE_TYPE_PROCESS:
- documentRef = deploymentlib.ProcessRef()
- deployment.processRefs.append(documentRef)
- elif file.type == basedocmgr.FILE_TYPE_SCHEMA:
- # set schemaRefs
- documentRef = deploymentlib.SchemaRef()
- deployment.schemaRefs.append(documentRef)
-
- # set dataSources
- doc = wx.GetApp().GetDocumentManager().CreateDocument(file.filePath, flags=defaultFlagsNoView)
- if (doc == None): # already open
- doc = FindOpenDoc(file.filePath)
- if doc:
- dataSourceService = wx.GetApp().GetService(DataModelEditor.DataSourceService)
- ds = dataSourceService.getDataSource(doc.GetModel().getDefaultDataSourceName())
- if ds:
- found = False
- for d in deployment.dataSources:
- if d.name == ds.name:
- found = True
- break
- if not found:
- deployment.dataSources.append(ds)
-
- # set keyServices
- keyServices = doc.GetModel().keyServices
- for keyService in keyServices:
- # add default key service to deployment
- if not productionDeployment:
- mainModuleDir = sysutils.mainModuleDir
- else:
- mainModuleDir = sysutils.MAINMODULE_DIR_VAR
- wsdlFullPath = os.path.join(mainModuleDir, "..", "wsdl", DataModelEditor.DEFAULT_KEYSERVICE_WSDL_FILENAME)
- keyServiceRef = deploymentlib.ServiceRef(filePath=wsdlFullPath)
- deployment.serviceRefs.append(keyServiceRef)
-
- keyServiceRef.name = keyService
- keyServiceRef.serviceType = deploymentlib.SERVICE_LOCAL
- keyServiceRef.localService = deploymentlib.LocalService()
- if keyService == DataModelEditor.DEFAULT_KEYSERVICE:
- keyServiceRef.filePath = wsdlFullPath
- keyServiceRef.localServiceClassName = DataModelEditor.DEFAULT_KEYSERVICE_CLASSNAME
-
+ d = DeploymentGeneration.DeploymentGenerator(
+ self.GetModel(), self._GetResourceFactory(preview,
+ deployFilepath))
+
+ dpl = d.getDeployment(deployFilepath)
- elif file.type == basedocmgr.FILE_TYPE_SKIN:
- documentRef = deploymentlib.SkinRef(deployment)
- deployment.skinref = documentRef
- elif file.type == basedocmgr.FILE_TYPE_IDENTITY:
- documentRef = deploymentlib.IdentityRef()
- deployment.identityRefs.append(documentRef)
- else:
- continue
-
- documentRef.name = file.name
- documentRef.filePath = filePath
- doc = FindOpenDoc(file.filePath)
- if doc and hasattr(doc, 'GetModel'):
- documentRef.document = doc.GetModel()
- if isinstance(documentRef, deploymentlib.XFormRef):
- doc.GetModel().linkDeployment(deployment, deployment.loader)
-
if preview:
- deployment.initialize() # used in preview only
-
- if 0: # preview: # setPrototype not working, commented this out
- deploymentlib._deploymentCache.setPrototype(deployment.fileName, deployment)
- else:
- deploymentlib.saveThroughCache(deployment.fileName, deployment)
+ dpl.initialize() # used in preview only
- return deployFilepath
-
+ # REVIEW 07-Apr-06 stoens@activegrid.com -- Check if there's a
+ # tmp dpl file with pre 17 name, if so, delete it, so user doesn't end
+ # up with unused file in project dir. We should probably remove this
+ # check after 1.7 goes out.
+ fileutils.remove(self.GetDeploymentFilepath(pre17=True))
+ deploymentlib.saveThroughCache(dpl.fileName, dpl)
+ return deployFilepath
+
def AddNameSpaces(self, filePaths):
- """ Add any new wsdl namespaces to bpel files """
+ """ Add any new wsdl and schema namespaces to bpel files """
""" Add any new schema namespaces to wsdl files """
if ACTIVEGRID_BASE_IDE:
return
- serviceRefs = self.GetAppDocMgr().allServiceRefs # wsdl
-
processRefs = self.GetAppDocMgr().findRefsByFileType(basedocmgr.FILE_TYPE_PROCESS) # bpel
- if processRefs and serviceRefs:
+ schemaRefs = self.GetAppDocMgr().findRefsByFileType(basedocmgr.FILE_TYPE_SCHEMA) # xsd
+ serviceRefs = self.GetAppDocMgr().allServiceRefs # wsdl
+
+ # update bpel files
+ if processRefs and (serviceRefs or schemaRefs):
for processRef in processRefs:
- processDoc = processRef._GetDoc()
+ processDoc = processRef.ideDocument
process = processDoc.GetModel()
- modified = False
- for serviceRef in serviceRefs:
- wsdl = serviceRef.document
- if (wsdl.fileName in filePaths
- or serviceRef.filePath in filePaths):
- wsdlLongNS = wsdl.targetNamespace
- wsdlShortNS = self.GetAppDocMgr().findShortNS(wsdlLongNS)
- if not wsdlShortNS:
- wsdlShortNS = xmlutils.genShortNS(process, wsdlLongNS)
- xmlutils.addNSAttribute(process, wsdlShortNS, wsdlLongNS)
- modified = True
- if modified:
- processDoc.OnSaveDocument(processDoc.GetFilename())
-
- schemaRefs = self.GetAppDocMgr().findRefsByFileType(basedocmgr.FILE_TYPE_SCHEMA)
- if schemaRefs and serviceRefs:
+ if processDoc and process:
+ modified = False
+
+ # add wsdl namespaces to bpel file
+ for serviceRef in serviceRefs:
+ wsdl = serviceRef.document
+ if (wsdl
+ and (wsdl.fileName in filePaths
+ or serviceRef.filePath in filePaths)):
+ wsdlLongNS = wsdl.targetNamespace
+ wsdlShortNS = self.GetAppDocMgr().findShortNS(wsdlLongNS)
+ if not wsdlShortNS:
+ wsdlShortNS = xmlutils.genShortNS(process, wsdlLongNS)
+ xmlutils.addNSAttribute(process, wsdlShortNS, wsdlLongNS)
+ modified = True
+
+ # add schema namespaces to bpel file
+ for schemaRef in schemaRefs:
+ schema = schemaRef.document
+ if schema and schema.fileName in filePaths:
+ schemaLongNS = schema.targetNamespace
+ schemaShortNS = self.GetAppDocMgr().findShortNS(schemaLongNS)
+ if not schemaShortNS:
+ schemaShortNS = xmlutils.genShortNS(process, schemaLongNS)
+ xmlutils.addNSAttribute(process, schemaShortNS, schemaLongNS)
+ modified = True
+
+ if modified:
+ processDoc.OnSaveDocument(processDoc.GetFilename())
+
+
+ # update wsdl files
+ if serviceRefs and schemaRefs:
for serviceRef in serviceRefs:
wsdl = serviceRef.document
wsdlDoc = serviceRef.ideDocument
- modified = False
- for schemaRef in schemaRefs:
- schema = schemaRef.document
- if schema.fileName in filePaths:
- schemaLongNS = schema.targetNamespace
- schemaShortNS = self.GetAppDocMgr().findShortNS(schemaLongNS)
- if not schemaShortNS:
- schemaShortNS = xmlutils.genShortNS(process, schemaLongNS)
- xmlutils.addNSAttribute(wsdl, schemaShortNS, schemaLongNS)
- modified = True
- if modified:
- wsdlDoc.OnSaveDocument(wsdlDoc.GetFilename())
+ if wsdl and wsdlDoc:
+ modified = False
+
+ # add schema namespace to wsdl file
+ for schemaRef in schemaRefs:
+ schema = schemaRef.document
+ if schema and schema.fileName in filePaths:
+ schemaLongNS = schema.targetNamespace
+ schemaShortNS = self.GetAppDocMgr().findShortNS(schemaLongNS)
+ if not schemaShortNS:
+ schemaShortNS = xmlutils.genShortNS(wsdl, schemaLongNS)
+ xmlutils.addNSAttribute(wsdl, schemaShortNS, schemaLongNS)
+ modified = True
+
+ if modified:
+ wsdlDoc.OnSaveDocument(wsdlDoc.GetFilename())
class NewProjectWizard(Wizard.BaseWizard):
def CreateProjectLocation(self,wizard):
- page = Wizard.TitledWizardPage(wizard, _("Project File Location"))
+ page = Wizard.TitledWizardPage(wizard, _("Name and Location"))
- page.GetSizer().Add(wx.StaticText(page, -1, _("\nSelect the directory and filename for the project.\n\n")))
- self._projectName, self._dirCtrl, sizer, self._fileValidation = UICommon.CreateDirectoryControl(page, fileExtension="agp", appDirDefaultStartDir=True)
+ page.GetSizer().Add(wx.StaticText(page, -1, _("\nEnter the name and location for the project.\n")))
+ self._projectName, self._dirCtrl, sizer, self._fileValidation = UICommon.CreateDirectoryControl(page, fileExtension="agp", appDirDefaultStartDir=True, fileLabel=_("Name:"), dirLabel=_("Location:"))
page.GetSizer().Add(sizer, 1, flag=wx.EXPAND)
wizard.Layout()
def RunWizard(self, existingTables = None, existingRelationships = None):
- status = wx.wizard.Wizard.RunWizard(self, self._projectLocationPage)
+ status = Wizard.BaseWizard.RunWizard(self, self._projectLocationPage)
if status:
wx.ConfigBase_Get().Write(PROJECT_DIRECTORY_KEY, self._dirCtrl.GetValue())
docManager = wx.GetApp().GetTopWindow().GetDocumentManager()
def OnWizPageChanging(self, event):
if event.GetDirection(): # It's going forwards
if event.GetPage() == self._projectLocationPage:
- if not self._fileValidation(noFirstCharDigit=True):
+ if not self._fileValidation(validClassName=True):
event.Veto()
return
self._fullProjectPath = os.path.join(self._dirCtrl.GetValue(),UICommon.MakeNameEndInExtension(self._projectName.GetValue(), PROJECT_EXTENSION))
self._treeCtrl = ProjectTreeCtrl(panel, -1, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.TR_DEFAULT_STYLE | wx.TR_MULTIPLE | wx.TR_EXTENDED)
self._treeCtrl.AddRoot(_("Projects"))
- wx.EVT_TREE_BEGIN_DRAG(self._treeCtrl, self._treeCtrl.GetId(), self.OnBeginDrag)
- wx.EVT_TREE_END_DRAG(self._treeCtrl, self._treeCtrl.GetId(), self.OnEndDrag)
if self._embeddedWindow:
sizer.Add(self._treeCtrl, 1, wx.EXPAND|wx.BOTTOM, HALF_SPACE) # allow space for embedded window resize-sash
else:
sizer = wx.BoxSizer(wx.VERTICAL)
if wx.GetApp().IsMDI():
- sizer.Add(panel, 1, wx.EXPAND|wx.BOTTOM, 1) # wx.Bug: without bottom margin, can't resize embedded window
+ sizer.Add(panel, 1, wx.EXPAND|wx.BOTTOM, 3) # wxBug: without bottom margin, can't resize embedded window
else:
sizer.Add(panel, 1, wx.EXPAND)
wx.EVT_TREE_BEGIN_LABEL_EDIT(self._treeCtrl, self._treeCtrl.GetId(), self.OnBeginLabelEdit)
wx.EVT_TREE_END_LABEL_EDIT(self._treeCtrl, self._treeCtrl.GetId(), self.OnEndLabelEdit)
wx.EVT_RIGHT_DOWN(self._treeCtrl, self.OnRightClick)
- wx.EVT_LEFT_DOWN(self._treeCtrl, self.OnLeftClick)
wx.EVT_KEY_DOWN(self._treeCtrl, self.OnKeyPressed)
wx.EVT_TREE_ITEM_COLLAPSED(self._treeCtrl, self._treeCtrl.GetId(), self.SaveFolderState)
wx.EVT_TREE_ITEM_EXPANDED(self._treeCtrl, self._treeCtrl.GetId(), self.SaveFolderState)
- # wx.EVT_COMMAND_RIGHT_CLICK(self._treeCtrl, self._treeCtrl.GetId(), self.OnRightClick) # wxBug: This isn't working for some reason
+ wx.EVT_TREE_BEGIN_DRAG(self._treeCtrl, self._treeCtrl.GetId(), self.OnBeginDrag)
+ wx.EVT_TREE_END_DRAG(self._treeCtrl, self._treeCtrl.GetId(), self.OnEndDrag)
+ wx.EVT_LEFT_DOWN(self._treeCtrl, self.OnLeftClick)
# drag-and-drop support
dt = ProjectFileDropTarget(self)
self._treeCtrl.SetDropTarget(dt)
-
+
return True
else:
# need this to accelerate closing down app if treeCtrl has lots of items
self._treeCtrl.Freeze()
- rootItem = self._treeCtrl.GetRootItem()
- self._treeCtrl.DeleteChildren(rootItem)
- self._treeCtrl.Thaw()
+ try:
+ rootItem = self._treeCtrl.GetRootItem()
+ self._treeCtrl.DeleteChildren(rootItem)
+ finally:
+ self._treeCtrl.Thaw()
# We don't need to delete the window since it is a floater/embedded
return True
def OnUpdate(self, sender = None, hint = None):
- wx.lib.docview.View.OnUpdate(self, sender, hint)
+ if wx.lib.docview.View.OnUpdate(self, sender, hint):
+ return
if hint:
if hint[0] == "add":
self._treeCtrl.Freeze()
- newFilePaths = hint[2] # need to be added and selected, and sorted
- oldFilePaths = hint[3] # need to be selected
- self._treeCtrl.UnselectAll()
-
- mode = self.GetMode()
-
- project = projectDoc.GetModel()
- projectDir = project.homeDir
- rootItem = self._treeCtrl.GetRootItem()
+ try:
+ newFilePaths = hint[2] # need to be added and selected, and sorted
+ oldFilePaths = hint[3] # need to be selected
+ self._treeCtrl.UnselectAll()
- # add new folders and new items
- addList = []
- for filePath in newFilePaths:
- file = project.FindFile(filePath)
- if file:
- if mode == ProjectView.LOGICAL_MODE:
- folderPath = file.logicalFolder
- else: # ProjectView.PHYSICAL_MODE
- folderPath = file.GetRelativeFolder(projectDir)
- if folderPath:
- self._treeCtrl.AddFolder(folderPath)
- folder = self._treeCtrl.FindFolder(folderPath)
- else:
- folder = rootItem
- item = self._treeCtrl.AppendItem(folder, os.path.basename(file.filePath), file)
- addList.append(item)
-
- # sort folders with new items
- parentList = []
- for item in addList:
- parentItem = self._treeCtrl.GetItemParent(item)
- if parentItem not in parentList:
- parentList.append(parentItem)
- for parentItem in parentList:
- self._treeCtrl.SortChildren(parentItem)
-
- # select all the items user wanted to add
- lastItem = None
- for filePath in (oldFilePaths + newFilePaths):
- item = self._treeCtrl.FindItem(filePath)
- if item:
- self._treeCtrl.SelectItem(item)
- lastItem = item
+ mode = self.GetMode()
+
+ project = projectDoc.GetModel()
+ projectDir = project.homeDir
+ rootItem = self._treeCtrl.GetRootItem()
- if lastItem:
- self._treeCtrl.EnsureVisible(lastItem)
+ # add new folders and new items
+ addList = []
+ for filePath in newFilePaths:
+ file = project.FindFile(filePath)
+ if file:
+ if mode == ProjectView.LOGICAL_MODE:
+ folderPath = file.logicalFolder
+ else: # ProjectView.PHYSICAL_MODE
+ folderPath = file.physicalFolder
+ if folderPath:
+ self._treeCtrl.AddFolder(folderPath)
+ folder = self._treeCtrl.FindFolder(folderPath)
+ else:
+ folder = rootItem
+ item = self._treeCtrl.AppendItem(folder, os.path.basename(file.filePath), file)
+ addList.append(item)
+
+ # sort folders with new items
+ parentList = []
+ for item in addList:
+ parentItem = self._treeCtrl.GetItemParent(item)
+ if parentItem not in parentList:
+ parentList.append(parentItem)
+ for parentItem in parentList:
+ self._treeCtrl.SortChildren(parentItem)
+
+ # select all the items user wanted to add
+ lastItem = None
+ for filePath in (oldFilePaths + newFilePaths):
+ item = self._treeCtrl.FindItem(filePath)
+ if item:
+ self._treeCtrl.SelectItem(item)
+ lastItem = item
+
+ if lastItem:
+ self._treeCtrl.EnsureVisible(lastItem)
- self._treeCtrl.Thaw()
+ finally:
+ self._treeCtrl.Thaw()
return
elif hint[0] == "remove":
self._treeCtrl.Freeze()
- filePaths = hint[2]
- self._treeCtrl.UnselectAll()
-
- for filePath in filePaths:
- item = self._treeCtrl.FindItem(filePath)
- if item:
- self._treeCtrl.Delete(item)
-
- self._treeCtrl.UnselectAll() # wxBug: even though we unselected earlier, an item still gets selected after the delete
+ try:
+ filePaths = hint[2]
+ self._treeCtrl.UnselectAll()
+
+ for filePath in filePaths:
+ item = self._treeCtrl.FindItem(filePath)
+ if item:
+ self._treeCtrl.Delete(item)
+
+ self._treeCtrl.UnselectAll() # wxBug: even though we unselected earlier, an item still gets selected after the delete
- self._treeCtrl.Thaw()
+ finally:
+ self._treeCtrl.Thaw()
return
elif hint[0] == "rename":
return
self._treeCtrl.Freeze()
- item = self._treeCtrl.FindItem(hint[2])
- self._treeCtrl.SetItemText(item, os.path.basename(hint[3]))
- self._treeCtrl.EnsureVisible(item)
- self._treeCtrl.Thaw()
+ try:
+ item = self._treeCtrl.FindItem(hint[2])
+ self._treeCtrl.SetItemText(item, os.path.basename(hint[3]))
+ self._treeCtrl.EnsureVisible(item)
+ finally:
+ self._treeCtrl.Thaw()
return
elif hint[0] == "rename folder":
return
self._treeCtrl.Freeze()
- item = self._treeCtrl.FindFolder(hint[2])
- if item:
- self._treeCtrl.UnselectAll()
- self._treeCtrl.SetItemText(item, os.path.basename(hint[3]))
- self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(item))
- self._treeCtrl.SelectItem(item)
- self._treeCtrl.EnsureVisible(item)
- self._treeCtrl.Thaw()
+ try:
+ item = self._treeCtrl.FindFolder(hint[2])
+ if item:
+ self._treeCtrl.UnselectAll()
+ self._treeCtrl.SetItemText(item, os.path.basename(hint[3]))
+ self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(item))
+ self._treeCtrl.SelectItem(item)
+ self._treeCtrl.EnsureVisible(item)
+ finally:
+ self._treeCtrl.Thaw()
return
def ProcessEvent(self, event):
id = event.GetId()
if id == ProjectService.CLOSE_PROJECT_ID:
- document = self.GetDocument()
- if document:
- if self.GetDocumentManager().CloseDocument(document, False):
+ projectDoc = self.GetDocument()
+ if projectDoc:
+ projectService = wx.GetApp().GetService(ProjectService)
+ if projectService:
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for openDoc in openDocs[:]: # need to make a copy, as each file closes we're off by one
+ if projectDoc == openDoc: # close project last
+ continue
+
+ if projectDoc == projectService.FindProjectFromMapping(openDoc):
+ self.GetDocumentManager().CloseDocument(openDoc, False)
+
+ projectService.RemoveProjectMapping(openDoc)
+ if hasattr(openDoc, "GetModel"):
+ projectService.RemoveProjectMapping(openDoc.GetModel())
+
+ if self.GetDocumentManager().CloseDocument(projectDoc, False):
self.RemoveCurrentDocumentUpdate()
return True
elif id == ProjectService.ADD_FILES_TO_PROJECT_ID:
return True
elif (id == wx.ID_CLEAR
or id == ProjectService.RENAME_ID):
+ items = self._treeCtrl.GetSelections()
+ if items:
+ hasViewSelected = False
+ for item in items:
+ if self._IsItemFile(item):
+ file = self._GetItemFile(item)
+ if file.type == 'xform':
+ hasViewSelected = True
+ break
+ if hasViewSelected:
+ event.Enable(False)
+ return True
+
event.Enable(self._HasFilesSelected() or (self.GetDocument() != None and self.GetMode() == ProjectView.LOGICAL_MODE and self._HasFoldersSelected()))
return True
elif id == wx.ID_PASTE:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
self._treeCtrl.Freeze()
- rootItem = self._treeCtrl.GetRootItem()
- self._treeCtrl.DeleteChildren(rootItem)
-
- if document:
- mode = self.GetMode()
- docFilePath = document.GetFilename()
+ try:
+ rootItem = self._treeCtrl.GetRootItem()
+ self._treeCtrl.DeleteChildren(rootItem)
- if mode == ProjectView.LOGICAL_MODE:
- folders = document.GetModel().logicalFolders
- else:
- folders = document.GetModel().GetRelativeFolders()
+ if document:
+ mode = self.GetMode()
+ docFilePath = document.GetFilename()
- folders.sort()
- folderItems = []
- for folderPath in folders:
- folderItems = folderItems + self._treeCtrl.AddFolder(folderPath)
-
- for file in document.GetModel()._files:
if mode == ProjectView.LOGICAL_MODE:
- folder = file.logicalFolder
+ folders = document.GetModel().logicalFolders
else:
- folder = file.GetRelativeFolder(document.GetModel().homeDir)
- if folder:
- folderTree = folder.split('/')
-
- item = rootItem
- for folderName in folderTree:
- found = False
- (child, cookie) = self._treeCtrl.GetFirstChild(item)
- while child.IsOk():
- if self._treeCtrl.GetItemText(child) == folderName:
- item = child
- found = True
+ folders = document.GetModel().physicalFolders
+
+ folders.sort()
+ folderItems = []
+ for folderPath in folders:
+ folderItems = folderItems + self._treeCtrl.AddFolder(folderPath)
+
+ for file in document.GetModel()._files:
+ if mode == ProjectView.LOGICAL_MODE:
+ folder = file.logicalFolder
+ else:
+ folder = file.physicalFolder
+ if folder:
+ folderTree = folder.split('/')
+
+ item = rootItem
+ for folderName in folderTree:
+ found = False
+ (child, cookie) = self._treeCtrl.GetFirstChild(item)
+ while child.IsOk():
+ if self._treeCtrl.GetItemText(child) == folderName:
+ item = child
+ found = True
+ break
+ (child, cookie) = self._treeCtrl.GetNextChild(item, cookie)
+
+ if not found:
+ print "error folder '%s' not found for %s" % (folder, file.filePath)
break
- (child, cookie) = self._treeCtrl.GetNextChild(item, cookie)
-
- if not found:
- print "error folder '%s' not found for %s" % (folder, file.filePath)
- break
- else:
- item = rootItem
+ else:
+ item = rootItem
+
+ fileItem = self._treeCtrl.AppendItem(item, os.path.basename(file.filePath), file)
- fileItem = self._treeCtrl.AppendItem(item, os.path.basename(file.filePath), file)
+ self._treeCtrl.SortChildren(rootItem)
+ for item in folderItems:
+ self._treeCtrl.SortChildren(item)
+
+ self.LoadFolderState()
+
+ self._treeCtrl.SetFocus()
+ (child, cookie) = self._treeCtrl.GetFirstChild(self._treeCtrl.GetRootItem())
+ if child.IsOk():
+ self._treeCtrl.UnselectAll()
+ self._treeCtrl.SelectItem(child)
+ self._treeCtrl.ScrollTo(child)
- self._treeCtrl.SortChildren(rootItem)
- for item in folderItems:
- self._treeCtrl.SortChildren(item)
-
- self.LoadFolderState()
-
- if self._embeddedWindow:
- document.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame()))
+ if self._embeddedWindow:
+ document.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame()))
- self._treeCtrl.Thaw()
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ finally:
+ self._treeCtrl.Thaw()
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
def ProjectHasFocus(self):
return False
+ def ClearFolderState(self):
+ config = wx.ConfigBase_Get()
+ config.DeleteGroup(getProjectKeyName(self.GetDocument().GetFilename()))
+
+
def SaveFolderState(self, event=None):
""" Save the open/close state of folders """
def LoadFolderState(self):
- """ Load the open/close state of folders """
+ """ Load the open/close state of folders. """
self._loading = True
-
+
config = wx.ConfigBase_Get()
- openFolderData = config.Read(getProjectKeyName(self.GetDocument().GetFilename(), self.GetMode()))
+ openFolderData = config.Read(getProjectKeyName(self.GetDocument().GetFilename(), self.GetMode()), "")
if openFolderData:
folderList = eval(openFolderData)
folderItemList = self._GetFolderItems(self._treeCtrl.GetRootItem())
for item in folderItemList:
- f = self._GetItemFolderPath(item)
- if f in folderList:
+ folderPath = self._GetItemFolderPath(item)
+ if folderPath in folderList:
self._treeCtrl.Expand(item)
-## else: # not needed, initial state is collapsed
-## self._treeCtrl.Collapse(item)
- else: # default is to open all folders
+ else:
+ self._treeCtrl.Collapse(item)
+
+ else:
+ projectService = wx.GetApp().GetService(ProjectService)
+
folderItemList = self._GetFolderItems(self._treeCtrl.GetRootItem())
for item in folderItemList:
- self._treeCtrl.Expand(item)
+ folderPath = self._GetItemFolderPath(item)
+ if projectService.FindLogicalViewFolderCollapsedDefault(folderPath): # get default initial state
+ self._treeCtrl.Collapse(item)
+ else:
+ self._treeCtrl.Expand(item)
self._loading = False
if len(descr) > 0:
descr = descr + _('|')
descr = descr + temp.GetDescription() + _(" (") + temp.GetFileFilter() + _(") |") + temp.GetFileFilter() # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
- descr = _("All (*.*)|*.*|%s") % descr # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
+ descr = _("All|*.*|%s") % descr # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
else:
descr = _("*.*")
descr = descr + _('|')
descr = template.GetDescription() + _(" (") + template.GetFileFilter() + _(")")
choices.append(descr)
- choices.insert(0, _("All (*.*)")) # first item
+ choices.insert(0, _("All")) # first item
filterChoice = wx.Choice(frame, -1, size=(250, -1), choices=choices)
filterChoice.SetSelection(0)
filterChoice.SetToolTipString(_("Select file type filter."))
if status == wx.ID_OK:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- doc = self.GetDocument()
- searchSubfolders = subfolderCtrl.IsChecked()
- dirString = dirCtrl.GetValue()
-
- if os.path.isfile(dirString):
- # If they pick a file explicitly, we won't prevent them from adding it even if it doesn't match the filter.
- # We'll assume they know what they're doing.
- paths = [dirString]
- else:
- paths = []
-
- index = filterChoice.GetSelection()
- lastIndex = filterChoice.GetCount()-1
- if index and index != lastIndex: # if not All or Any
- template = visibleTemplates[index-1]
-
- # do search in files on disk
- for root, dirs, files in os.walk(dirString):
- if not searchSubfolders and root != dirString:
- break
-
- for name in files:
- if index == 0: # All
- for template in visibleTemplates:
- if template.FileMatchesTemplate(name):
- filename = os.path.join(root, name)
-
- # if already in project, don't add it, otherwise undo will remove it from project even though it was already in it.
- if doc.IsFileInProject(filename):
- break
-
- paths.append(filename)
- break
- elif index == lastIndex: # Any
- filename = os.path.join(root, name)
- # if already in project, don't add it, otherwise undo will remove it from project even though it was already in it.
- if not doc.IsFileInProject(filename):
- paths.append(filename)
- else: # use selected filter
- if template.FileMatchesTemplate(name):
+ try:
+ doc = self.GetDocument()
+ searchSubfolders = subfolderCtrl.IsChecked()
+ dirString = dirCtrl.GetValue()
+
+ if os.path.isfile(dirString):
+ # If they pick a file explicitly, we won't prevent them from adding it even if it doesn't match the filter.
+ # We'll assume they know what they're doing.
+ paths = [dirString]
+ else:
+ paths = []
+
+ index = filterChoice.GetSelection()
+ lastIndex = filterChoice.GetCount()-1
+ if index and index != lastIndex: # if not All or Any
+ template = visibleTemplates[index-1]
+
+ # do search in files on disk
+ for root, dirs, files in os.walk(dirString):
+ if not searchSubfolders and root != dirString:
+ break
+
+ for name in files:
+ if index == 0: # All
filename = os.path.join(root, name)
# if already in project, don't add it, otherwise undo will remove it from project even though it was already in it.
if not doc.IsFileInProject(filename):
paths.append(filename)
+ else: # use selected filter
+ if template.FileMatchesTemplate(name):
+ filename = os.path.join(root, name)
+ # if already in project, don't add it, otherwise undo will remove it from project even though it was already in it.
+ if not doc.IsFileInProject(filename):
+ paths.append(filename)
+
+ folderPath = None
+ if self.GetMode() == ProjectView.LOGICAL_MODE:
+ selections = self._treeCtrl.GetSelections()
+ if selections:
+ item = selections[0]
+ if not self._IsItemFile(item):
+ folderPath = self._GetItemFolderPath(item)
- folderPath = None
- if self.GetMode() == ProjectView.LOGICAL_MODE:
- selections = self._treeCtrl.GetSelections()
- if selections:
- item = selections[0]
- if not self._IsItemFile(item):
- folderPath = self._GetItemFolderPath(item)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
-
- doc.GetCommandProcessor().Submit(ProjectAddFilesCommand(doc, paths, folderPath=folderPath))
- self.Activate() # after add, should put focus on project editor
+ doc.GetCommandProcessor().Submit(ProjectAddFilesCommand(doc, paths, folderPath=folderPath))
+ self.Activate() # after add, should put focus on project editor
+
+ finally:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
def DoAddFilesToProject(self, filePaths, folderPath):
def OnLeftClick(self, event):
- """ wxBug: If tree has selection, but focus is in another window, single click in tree should do
- single selection of item at mouse position. But what it does is just put focus back into the
- window and all items go from inactive selection to active selection. Another click on the item
- either activates double-click or edit for that item. This behavior is odd.
-
- This fix makes gives the tree view the focus and makes the item under the mouse position the
- only active selection, as expected.
- """
- if not self.ProjectHasFocus() and not self.FilesHasFocus() and not event.ShiftDown() and not event.ControlDown() and not event.MetaDown():
- self._treeCtrl.UnselectAll()
- event.Skip()
+ """
+ wxBug: We also spurious drag events on a single click of on item that is already selected,
+ so the solution was to consume the left click event. But his broke the single click expand/collapse
+ of a folder, so if it is a folder, we do an event.Skip() to allow the expand/collapse,
+ otherwise we consume the event.
+ """
+ # if folder let it collapse/expand
+ if wx.Platform == '__WXMSW__':
item, flags = self._treeCtrl.HitTest(event.GetPosition())
- self._treeCtrl.SelectItem(item)
- return
-
- event.Skip()
-
+ if item.IsOk() and self._treeCtrl.GetChildrenCount(item, False):
+ event.Skip()
+ else:
+ event.Skip()
def OnRightClick(self, event):
self.Activate()
if not itemID:
menu.AppendSeparator()
else:
- if itemID == ProjectService.RUN_SELECTED_PM_ID:
- menu.Append(ProjectService.RUN_SELECTED_PM_ID, _("Run Process"))
- wx.EVT_MENU(self._GetParentFrame(), ProjectService.RUN_SELECTED_PM_ID, self.OnRunSelectedPM)
+ if itemID == ProjectService.RUN_SELECTED_PM_ID and not ACTIVEGRID_BASE_IDE:
+ webBrowserService = wx.GetApp().GetService(WebBrowserService.WebBrowserService)
+ if webBrowserService:
+ if wx.Platform == '__WXMSW__':
+ menu.Append(ProjectService.RUN_SELECTED_PM_ID, _("Run Process"))
+ wx.EVT_MENU(self._GetParentFrame(), ProjectService.RUN_SELECTED_PM_ID, self.ProjectServiceProcessEvent)
+
+ if wx.Platform == '__WXMSW__':
+ menuLabel = _("Run Process in External Browser")
+ else:
+ menuLabel = _("Run Process")
+ menu.Append(ProjectService.RUN_SELECTED_PM_EXTERNAL_BROWSER_ID, menuLabel)
+ wx.EVT_MENU(self._GetParentFrame(), ProjectService.RUN_SELECTED_PM_EXTERNAL_BROWSER_ID, self.ProjectServiceProcessEvent)
+
+ if wx.Platform == '__WXMSW__':
+
+ if wx.GetApp().GetUseTabbedMDI():
+ menuLabel = _("Run Process in new Tab")
+ else:
+ menuLabel = _("Run Process in new Window")
+ menu.Append(ProjectService.RUN_SELECTED_PM_INTERNAL_WINDOW_ID, menuLabel)
+ wx.EVT_MENU(self._GetParentFrame(), ProjectService.RUN_SELECTED_PM_INTERNAL_WINDOW_ID, self.ProjectServiceProcessEvent)
+
elif itemID == ProjectService.REMOVE_FROM_PROJECT:
menu.Append(ProjectService.REMOVE_FROM_PROJECT, _("Remove Selected Files from Project"))
wx.EVT_MENU(self._GetParentFrame(), ProjectService.REMOVE_FROM_PROJECT, self.OnClear)
wx.EVT_UPDATE_UI(self._GetParentFrame(), ProjectService.REMOVE_FROM_PROJECT, self._GetParentFrame().ProcessUpdateUIEvent)
else:
- svnService = wx.GetApp().GetService(SVNService.SVNService)
item = menuBar.FindItemById(itemID)
if item:
+ if SVN_INSTALLED:
+ svnService = wx.GetApp().GetService(SVNService.SVNService)
+
if itemID in svnIDs:
if SVN_INSTALLED and svnService:
wx.EVT_MENU(self._GetParentFrame(), itemID, svnService.ProcessEvent)
menu.Destroy()
- def OnRunSelectedPM(self, event):
+ def ProjectServiceProcessEvent(self, event):
projectService = wx.GetApp().GetService(ProjectService)
if projectService:
- projectService.OnRunProcessModel(event, runSelected=True)
+ projectService.ProcessEvent(event)
def OnRename(self, event=None):
items = self._treeCtrl.GetSelections()
- if items:
- self._treeCtrl.EditLabel(items[0])
+ if not items:
+ return
+ item = items[0]
+ if wx.Platform == "__WXGTK__":
+ dlg = wx.TextEntryDialog(self.GetFrame(), _("Enter New Name"), _("Enter New Name"))
+ dlg.CenterOnParent()
+ if dlg.ShowModal() == wx.ID_OK:
+ text = dlg.GetValue()
+ self.ChangeLabel(item, text)
+ else:
+ if items:
+ self._treeCtrl.EditLabel(item)
def OnBeginLabelEdit(self, event):
self._editingSoDontKillFocus = True
item = event.GetItem()
+ if self._IsItemFile(item):
+ file = self._GetItemFile(item)
+ if file.type == 'xform':
+ event.Veto()
if (self.GetMode() == ProjectView.PHYSICAL_MODE) and not self._IsItemFile(item):
event.Veto()
self._editingSoDontKillFocus = False
item = event.GetItem()
newName = event.GetLabel()
- if not newName:
+ if not self.ChangeLabel(item, newName):
event.Veto()
- return
+
+
+ def ChangeLabel(self, item, newName):
+ if not newName:
+ return False
if self._IsItemFile(item):
oldFilePath = self._GetItemFilePath(item)
newFilePath = os.path.join(os.path.dirname(oldFilePath), newName)
doc = self.GetDocument()
if not doc.GetCommandProcessor().Submit(ProjectRenameFileCommand(doc, oldFilePath, newFilePath)):
- event.Veto()
- return
+ return False
self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(item))
else:
oldFolderPath = self._GetItemFolderPath(item)
"Rename Folder",
wx.OK | wx.ICON_EXCLAMATION,
self.GetFrame())
- event.Veto()
- return
+ return False
doc = self.GetDocument()
if not doc.GetCommandProcessor().Submit(ProjectRenameFolderCommand(doc, oldFolderPath, newFolderPath)):
- event.Veto()
- return
+ return False
self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(item))
+ return True
+
def CanPaste(self):
# wxBug: Should be able to use IsSupported/IsSupportedFormat here
#fileDataObject = wx.FileDataObject()
#hasFilesInClipboard = wx.TheClipboard.IsSupportedFormat(wx.FileDataObject)
+ hasFilesInClipboard = False
if not wx.TheClipboard.IsOpened():
if wx.TheClipboard.Open():
fileDataObject = wx.FileDataObject()
hasFilesInClipboard = wx.TheClipboard.GetData(fileDataObject)
wx.TheClipboard.Close()
- else:
- hasFilesInClipboard = False
return hasFilesInClipboard
if closeFiles or delFiles:
filesInProject = doc.GetFiles()
- filesInProject.append(self.GetDocument().GetDeploymentFilepath()) # remove deployment file also.
+ deploymentFilePath = self.GetDocument().GetDeploymentFilepath()
+ if deploymentFilePath:
+ filesInProject.append(deploymentFilePath) # remove deployment file also.
+ import activegrid.server.secutils as secutils
+ keystoreFilePath = os.path.join(os.path.dirname(deploymentFilePath), secutils.AGKEYSTORE_FILENAME)
+ filesInProject.append(keystoreFilePath) # remove keystore file also.
# don't remove self prematurely
filePath = doc.GetFilename()
filePath = doc.GetFilename()
+ self.ClearFolderState() # remove from registry folder settings
+
# close project
- if doc:
+ if doc:
doc.Modify(False) # make sure it doesn't ask to save the project
if self.GetDocumentManager().CloseDocument(doc, True):
self.RemoveCurrentDocumentUpdate()
if not doc and filepath.endswith(PROJECT_EXTENSION): # project already open
self.SetProject(filepath)
elif doc:
- projectService = wx.GetApp().GetService(ProjectService)
- if projectService:
- projectService.AddProjectMapping(doc)
- if hasattr(doc, "GetModel"):
- projectService.AddProjectMapping(doc.GetModel())
+ AddProjectMapping(doc)
except IOError, (code, message):
tab.SetSizer(spacerGrid)
notebook.AddPage(tab, _("Physical View"))
+ if wx.Platform == "__WXMSW__":
+ notebook.SetPageSize((310,300))
+
if not ACTIVEGRID_BASE_IDE:
tab = wx.Panel(notebook, -1)
self._appInfoCtrl = PropertyService.PropertyCtrl(tab, header=False)
self._appInfoCtrl.SetDocument(document)
self._appInfoCtrl.SetModel(document.GetAppInfo())
sizer = wx.BoxSizer(wx.HORIZONTAL)
- sizer.Add(self._appInfoCtrl, 1, wx.EXPAND)
+ sizer.Add(self._appInfoCtrl, 1, wx.EXPAND|wx.ALL, PropertyService.LEAVE_MARGIN)
tab.SetSizer(sizer)
notebook.AddPage(tab, _("App Info"))
+ self._appInfoCtrl._grid.AutoSizeColumns()
- if wx.Platform == "__WXMSW__":
- notebook.SetPageSize((310,300))
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 0, wx.ALL | wx.EXPAND, SPACE)
projectSizer.Add(self._projSaveDocsCheckBox, 0, wx.ALL, HALF_SPACE)
if not ACTIVEGRID_BASE_IDE:
self._projShowWelcomeCheckBox = wx.CheckBox(self, -1, _("Show Welcome Dialog"))
- self._projShowWelcomeCheckBox.SetValue(config.ReadInt("RunWelcomeDialog", True))
+ self._projShowWelcomeCheckBox.SetValue(config.ReadInt("RunWelcomeDialog2", True))
projectSizer.Add(self._projShowWelcomeCheckBox, 0, wx.ALL, HALF_SPACE)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.StaticText(self, -1, _("Default language for projects:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, HALF_SPACE)
- self._langCtrl = wx.Choice(self, -1, choices=deploymentlib.LANGUAGE_LIST)
- self._langCtrl.SetStringSelection(config.Read(APP_LAST_LANGUAGE, deploymentlib.LANGUAGE_DEFAULT))
+ self._langCtrl = wx.Choice(self, -1, choices=projectmodel.LANGUAGE_LIST)
+ self._langCtrl.SetStringSelection(config.Read(APP_LAST_LANGUAGE, projectmodel.LANGUAGE_DEFAULT))
self._langCtrl.SetToolTipString(_("Programming language to be used throughout the project."))
sizer.Add(self._langCtrl, 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, MAC_RIGHT_BORDER)
projectSizer.Add(sizer, 0, wx.ALL, HALF_SPACE)
config = wx.ConfigBase_Get()
config.WriteInt("ProjectSaveDocs", self._projSaveDocsCheckBox.GetValue())
if not ACTIVEGRID_BASE_IDE:
- config.WriteInt("RunWelcomeDialog", self._projShowWelcomeCheckBox.GetValue())
+ config.WriteInt("RunWelcomeDialog2", self._projShowWelcomeCheckBox.GetValue())
config.Write(APP_LAST_LANGUAGE, self._langCtrl.GetStringSelection())
#----------------------------------------------------------------------------
SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service
RUN_SELECTED_PM_ID = wx.NewId()
+ RUN_SELECTED_PM_INTERNAL_WINDOW_ID = wx.NewId()
+ RUN_SELECTED_PM_EXTERNAL_BROWSER_ID = wx.NewId()
RUN_CURRENT_PM_ID = wx.NewId()
+ RUN_CURRENT_PM_INTERNAL_WINDOW_ID = wx.NewId()
+ RUN_CURRENT_PM_EXTERNAL_BROWSER_ID = wx.NewId()
RENAME_ID = wx.NewId()
OPEN_SELECTION_ID = wx.NewId()
REMOVE_FROM_PROJECT = wx.NewId()
self._runHandlers = []
self._suppressOpenProjectMessages = False
self._logicalViewDefaults = []
+ self._logicalViewOpenDefaults = []
self._fileTypeDefaults = []
self._nameDefaults = []
self._mapToProject = dict()
wx.EVT_MENU(frame, ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, frame.ProcessUpdateUIEvent)
if not menuBar.FindItemById(ProjectService.ADD_FOLDER_ID):
- projectMenu.Append(ProjectService.ADD_FOLDER_ID, _("Add Folder to Project"), _("Adds a new folder"))
+ projectMenu.Append(ProjectService.ADD_FOLDER_ID, _("New Folder"), _("Creates a new folder"))
wx.EVT_MENU(frame, ProjectService.ADD_FOLDER_ID, frame.ProcessEvent)
wx.EVT_UPDATE_UI(frame, ProjectService.ADD_FOLDER_ID, frame.ProcessUpdateUIEvent)
if not menuBar.FindItemById(ProjectService.CLOSE_PROJECT_ID):
self._mapToProject[key] = projectDoc
+ def RemoveProjectMapping(self, key):
+ """ Remove mapping from model or document to project. """
+ if self._mapToProject.has_key(key):
+ del self._mapToProject[key]
+
+
#----------------------------------------------------------------------------
# Default Logical View Folder Methods
#----------------------------------------------------------------------------
return None
+ def AddLogicalViewFolderCollapsedDefault(self, folderName, collapsed=True):
+ # default is collapsed, don't add to list if collapse is True
+ if not collapsed:
+ self._logicalViewOpenDefaults.append(folderName)
+
+
+ def FindLogicalViewFolderCollapsedDefault(self, folderName):
+ if folderName in self._logicalViewOpenDefaults:
+ return False
+ return True
+
+
#----------------------------------------------------------------------------
# Default File Type Methods
#----------------------------------------------------------------------------
if id == ProjectService.RUN_SELECTED_PM_ID:
self.OnRunProcessModel(event, runSelected=True)
return True
+ elif id == ProjectService.RUN_SELECTED_PM_INTERNAL_WINDOW_ID:
+ self.OnRunProcessModel(event, runSelected=True, newWindow=True, forceInternal=True)
+ return True
+ elif id == ProjectService.RUN_SELECTED_PM_EXTERNAL_BROWSER_ID:
+ self.OnRunProcessModel(event, runSelected=True, newWindow=True, forceExternal=True)
+ return True
elif id == ProjectService.RUN_CURRENT_PM_ID:
self.OnRunProcessModel(event, runCurrentFile=True)
return True
+ elif id == ProjectService.RUN_CURRENT_PM_INTERNAL_WINDOW_ID:
+ self.OnRunProcessModel(event, runCurrentFile=True, newWindow=True, forceInternal=True)
+ return True
+ elif id == ProjectService.RUN_CURRENT_PM_EXTERNAL_BROWSER_ID:
+ self.OnRunProcessModel(event, runCurrentFile=True, newWindow=True, forceExternal=True)
+ return True
elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
self.OnAddCurrentFileToProject(event)
return True
return True
id = event.GetId()
- if (id == ProjectService.RUN_SELECTED_PM_ID
- or id == ProjectService.RUN_CURRENT_PM_ID):
+ if id in [ProjectService.RUN_SELECTED_PM_ID,
+ ProjectService.RUN_SELECTED_PM_INTERNAL_WINDOW_ID,
+ ProjectService.RUN_SELECTED_PM_EXTERNAL_BROWSER_ID,
+ ProjectService.RUN_CURRENT_PM_ID,
+ ProjectService.RUN_CURRENT_PM_INTERNAL_WINDOW_ID,
+ ProjectService.RUN_CURRENT_PM_EXTERNAL_BROWSER_ID]:
event.Enable(True)
return True
elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
event.Enable(self._CanAddCurrentFileToProject())
return True
- elif (id == ProjectService.ADD_FILES_TO_PROJECT_ID
- or id == ProjectService.ADD_DIR_FILES_TO_PROJECT_ID
- or id == ProjectService.RENAME_ID
- or id == ProjectService.OPEN_SELECTION_ID
- or id == ProjectService.DELETE_FILE_ID):
+ elif id in [ProjectService.ADD_FILES_TO_PROJECT_ID,
+ ProjectService.ADD_DIR_FILES_TO_PROJECT_ID,
+ ProjectService.RENAME_ID,
+ ProjectService.OPEN_SELECTION_ID,
+ ProjectService.DELETE_FILE_ID]:
event.Enable(False)
return True
elif id == ProjectService.PROJECT_PROPERTIES_ID:
event.Enable(self._HasOpenedProjects())
return True
- elif (id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID
- or id == ProjectService.ADD_FOLDER_ID
- or id == ProjectService.DELETE_PROJECT_ID
- or id == ProjectService.CLOSE_PROJECT_ID):
+ elif id in [wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID,
+ ProjectService.ADD_FOLDER_ID,
+ ProjectService.DELETE_PROJECT_ID,
+ ProjectService.CLOSE_PROJECT_ID]:
if self.GetView():
return self.GetView().ProcessUpdateUIEvent(event)
else:
return False
- def OnRunProcessModel(self, event, runSelected=False, runCurrentFile=False):
+ def OnRunProcessModel(self, event, runSelected=False, runCurrentFile=False, newWindow=False, forceExternal=False, forceInternal=False):
project = self.GetCurrentProject()
if runCurrentFile:
fileToRun = files[res]
else:
fileToRun = files[0]
-
- deployFilePath = project.GenerateDeployment()
- self.RunProcessModel(fileToRun, project.GetAppInfo().language, deployFilePath)
+
+ try:
+ deployFilePath = project.GenerateDeployment()
+ except DataServiceExistenceException, e:
+ dataSourceName = str(e)
+ self.PromptForMissingDataSource(dataSourceName)
+ return
+ self.RunProcessModel(fileToRun, project.GetAppInfo().language, deployFilePath, newWindow, forceExternal, forceInternal)
- def RunProcessModel(self, fileToRun, language, deployFilePath):
+ def RunProcessModel(self, fileToRun, language, deployFilePath, newWindow=True, forceExternal=False, forceInternal=False):
for runHandler in self.GetRunHandlers():
- if runHandler.RunProjectFile(fileToRun, language, deployFilePath):
+ if runHandler.RunProjectFile(fileToRun, language, deployFilePath, newWindow, forceExternal, forceInternal):
return
os.system('"' + fileToRun + '"')
retval.append(document)
elif document.IsFileInProject(filename):
retval.append(document)
+
+ # make sure current project is first in list
+ currProject = self.GetCurrentProject()
+ if currProject and currProject in retval:
+ retval.remove(currProject)
+ retval.insert(0, currProject)
+
return retval
def OnAddCurrentFileToProject(self, event):
- file = self.GetDocumentManager().GetCurrentDocument().GetFilename()
- document = self.GetView().GetDocument()
- document.GetCommandProcessor().Submit(ProjectAddFilesCommand(document, [file]))
+ doc = self.GetDocumentManager().GetCurrentDocument()
+ file = doc.GetFilename()
+ projectDoc = self.GetView().GetDocument()
+ projectDoc.GetCommandProcessor().Submit(ProjectAddFilesCommand(projectDoc, [file]))
+
+ AddProjectMapping(doc, projectDoc)
+
self.GetView().Activate() # after add, should put focus on project editor
if docString:
doc = None
docList = eval(docString)
+ self.GetView()._treeCtrl.Freeze()
+
for fileName in docList:
if isinstance(fileName, types.StringTypes):
if os.path.exists(fileName):
doc = self.GetDocumentManager().CreateDocument(fileName, wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE)
+ self.GetView()._treeCtrl.Thaw()
if doc:
openedDocs = True
return openedDocs
+ def PromptForMissingDataSource(self, dataSourceName):
+ prompt = "A required Data Source '%s' was not found. The process cannot be run without this Data Source.\n\nWould you like to configure this Data Source now?" % dataSourceName
+ msgTitle = "Unknown Data Source"
+ dataSourceMissingDlg = wx.MessageDialog(self.GetView().GetFrame(), prompt, msgTitle, wx.YES_NO|wx.ICON_QUESTION)
+ dataSourceMissingDlg.CenterOnParent()
+ if dataSourceMissingDlg.ShowModal() == wx.ID_YES:
+ dataSourceMissingDlg.Destroy()
+ self._AddDataSource(dataSourceName)
+ else:
+ dataSourceMissingDlg.Destroy()
+
+
+ def _AddDataSource(self, defaultDataSourceName=None):
+ dataSourceService = wx.GetApp().GetService(DataModelEditor.DataSourceService)
+ dsChoices = dataSourceService.getDataSourceNames()
+ dlg = DataModelEditor.AddDataSourceDialog(self.GetView().GetFrame(), 'Add Data Source', dsChoices, defaultDataSourceName)
+ dlg.CenterOnParent()
+ if dlg.ShowModal() == wx.ID_OK:
+ dataSource = dlg.GetDataSource()
+ dlg.Destroy()
+ else:
+ dlg.Destroy()
+ return False
+ if (dataSource == None):
+ wx.MessageBox(_("Error getting data source."), self._title)
+ dataSourceService.updateDataSource(dataSource)
+ if ((dsChoices == None) or (len(dsChoices) <= 0)):
+ wx.ConfigBase_Get().Write(DataModelEditor.SchemaOptionsPanel.DEFAULT_DATASOURCE_KEY, dataSource.name)
+ dataSourceService.save()
+ return True
+
+
#----------------------------------------------------------------------------
# Icon Bitmaps - generated by encode_bitmaps.py
#----------------------------------------------------------------------------
import sys # for GetAutoCompleteKeywordList
import MessageService # for OnCheckCode
import OutlineService
+import FindInDirService
from UICommon import CaseInsensitiveCompare
try:
import checker # for pychecker
# Set cursor to Wait cursor
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
- # This takes a while for involved code
- checker.checkSyntax(self.GetDocument().GetFilename(), view)
+ try:
+ # This takes a while for involved code
+ checker.checkSyntax(self.GetDocument().GetFilename(), view)
- # Set cursor to Default cursor
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ finally:
+ # Set cursor to Default cursor
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
def OnJumpToFoundLine(self, event):
self.SetKeyWords(0, string.join(keyword.kwlist))
+ def CreatePopupMenu(self):
+ FINDCLASS_ID = wx.NewId()
+ FINDDEF_ID = wx.NewId()
+
+ menu = CodeEditor.CodeCtrl.CreatePopupMenu(self)
+
+ self.Bind(wx.EVT_MENU, self.OnPopFindDefinition, id=FINDDEF_ID)
+ menu.Insert(1, FINDDEF_ID, _("Find 'def'"))
+
+ self.Bind(wx.EVT_MENU, self.OnPopFindClass, id=FINDCLASS_ID)
+ menu.Insert(2, FINDCLASS_ID, _("Find 'class'"))
+
+ return menu
+
+
+ def OnPopFindDefinition(self, event):
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
+ pattern = view.GetCtrl().GetSelectedText().strip()
+ if pattern:
+ searchPattern = "def\s+%s" % pattern
+ wx.GetApp().GetService(FindInDirService.FindInDirService).FindInProject(searchPattern)
+
+
+ def OnPopFindClass(self, event):
+ view = wx.GetApp().GetDocumentManager().GetCurrentView()
+ if hasattr(view, "GetCtrl") and view.GetCtrl() and hasattr(view.GetCtrl(), "GetSelectedText"):
+ definition = "class\s+%s"
+ pattern = view.GetCtrl().GetSelectedText().strip()
+ if pattern:
+ searchPattern = definition % pattern
+ wx.GetApp().GetService(FindInDirService.FindInDirService).FindInProject(searchPattern)
+
+
def SetViewDefaults(self):
- CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Python", hasWordWrap = True, hasTabs = True)
+ CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix="Python", hasWordWrap=True, hasTabs=True, hasFolding=True)
def GetFontAndColorFromConfig(self):
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(pathSizer, 0, wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, SPACE)
- self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "Python", label = "Python", hasWordWrap = True, hasTabs = True, addPage=False)
+ self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "Python", label = "Python", hasWordWrap = True, hasTabs = True, addPage=False, hasFolding=True)
mainSizer.Add(self._otherOptions, 0, wx.EXPAND|wx.BOTTOM, SPACE)
self.SetSizer(mainSizer)
parent.AddPage(self, _("Python"))
defaultDir = os.path.dirname(self._pathTextCtrl.GetValue().strip())
defaultFile = os.path.basename(self._pathTextCtrl.GetValue().strip())
if _WINDOWS:
- wildcard = _("Executable (*.exe)|*.exe|All (*.*)|*.*")
+ wildcard = _("Executable (*.exe)|*.exe|All|*.*")
if not defaultFile:
defaultFile = "python.exe"
else:
#
# Created: 8/10/03
# CVS-ID: $Id$
-# Copyright: (c) 2003-2005 ActiveGrid, Inc.
+# Copyright: (c) 2003-2006 ActiveGrid, Inc.
# License: wxWindows License
#----------------------------------------------------------------------------
class TextDocument(wx.lib.docview.Document):
+ def __init__(self):
+ wx.lib.docview.Document .__init__(self)
+ self._inModify = False
+
+
def SaveObject(self, fileObject):
view = self.GetFirstView()
fileObject.write(view.GetValue())
+ view.SetModifyFalse()
return True
view = self.GetFirstView()
data = fileObject.read()
view.SetValue(data)
+ view.SetModifyFalse()
return True
def IsModified(self):
view = self.GetFirstView()
if view:
- return wx.lib.docview.Document.IsModified(self) or view.IsModified()
- else:
- return wx.lib.docview.Document.IsModified(self)
+ return view.IsModified()
+ return False
- def Modify(self, mod):
+ def Modify(self, modify):
+ if self._inModify:
+ return
+ self._inModify = True
+
view = self.GetFirstView()
- wx.lib.docview.Document.Modify(self, mod)
- if not mod and view:
+ if not modify and view:
view.SetModifyFalse()
+ wx.lib.docview.Document.Modify(self, modify) # this must called be after the SetModifyFalse call above.
+ self._inModify = False
+
+
def OnCreateCommandProcessor(self):
# Don't create a command processor, it has its own
pass
self._dynSash._view = self
self._textEditor = self.GetCtrlClass()(self._dynSash, -1, style=wx.NO_BORDER)
wx.EVT_LEFT_DOWN(self._textEditor, self.OnLeftClick)
+ self._textEditor.Bind(wx.stc.EVT_STC_MODIFIED, self.OnModify)
+
self._CreateSizer(frame)
self.Activate()
frame.Show(True)
return True
+ def OnModify(self, event):
+ self.GetDocument().Modify(self._textEditor.GetModify())
+
+
def _CreateSizer(self, frame):
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self._dynSash, 1, wx.EXPAND)
def OnUpdate(self, sender = None, hint = None):
+ if wx.lib.docview.View.OnUpdate(self, sender, hint):
+ return
+
if hint == "ViewStuff":
self.GetCtrl().SetViewDefaults()
elif hint == "Font":
def EnsureVisible(self, line):
self.GetCtrl().EnsureVisible(line-1) # line numbering for editor is 0 based, we are 1 based.
+
def EnsureVisibleEnforcePolicy(self, line):
self.GetCtrl().EnsureVisibleEnforcePolicy(line-1) # line numbering for editor is 0 based, we are 1 based.
+
def LineFromPosition(self, pos):
return self.GetCtrl().LineFromPosition(pos)+1 # line numbering for editor is 0 based, we are 1 based.
class TextOptionsPanel(wx.Panel):
- def __init__(self, parent, id, configPrefix = "Text", label = "Text", hasWordWrap = True, hasTabs = False, addPage=True):
+ def __init__(self, parent, id, configPrefix = "Text", label = "Text", hasWordWrap = True, hasTabs = False, addPage=True, hasFolding=False):
wx.Panel.__init__(self, parent, id)
self._configPrefix = configPrefix
self._hasWordWrap = hasWordWrap
self._hasTabs = hasTabs
+ self._hasFolding = hasFolding
SPACE = 10
HALF_SPACE = 5
config = wx.ConfigBase_Get()
self._viewRightEdgeCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewRightEdge", False))
self._viewLineNumbersCheckBox = wx.CheckBox(self, -1, _("Show line numbers"))
self._viewLineNumbersCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewLineNumbers", True))
+ if self._hasFolding:
+ self._viewFoldingCheckBox = wx.CheckBox(self, -1, _("Show folding"))
+ self._viewFoldingCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewFolding", True))
if self._hasTabs:
self._hasTabsCheckBox = wx.CheckBox(self, -1, _("Use spaces instead of tabs"))
self._hasTabsCheckBox.SetValue(not wx.ConfigBase_Get().ReadInt(self._configPrefix + "EditorUseTabs", False))
textPanelSizer.Add(self._viewIndentationGuideCheckBox, 0, wx.ALL, HALF_SPACE)
textPanelSizer.Add(self._viewRightEdgeCheckBox, 0, wx.ALL, HALF_SPACE)
textPanelSizer.Add(self._viewLineNumbersCheckBox, 0, wx.ALL, HALF_SPACE)
+ if self._hasFolding:
+ textPanelSizer.Add(self._viewFoldingCheckBox, 0, wx.ALL, HALF_SPACE)
if self._hasTabs:
textPanelSizer.Add(self._hasTabsCheckBox, 0, wx.ALL, HALF_SPACE)
textIndentWidthSizer = wx.BoxSizer(wx.HORIZONTAL)
config.WriteInt(self._configPrefix + "EditorViewRightEdge", self._viewRightEdgeCheckBox.GetValue())
doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewLineNumbers", True) != self._viewLineNumbersCheckBox.GetValue()
config.WriteInt(self._configPrefix + "EditorViewLineNumbers", self._viewLineNumbersCheckBox.GetValue())
+ if self._hasFolding:
+ doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewFolding", True) != self._viewFoldingCheckBox.GetValue()
+ config.WriteInt(self._configPrefix + "EditorViewFolding", self._viewFoldingCheckBox.GetValue())
if self._hasWordWrap:
doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorWordWrap", False) != self._wordWrapCheckBox.GetValue()
config.WriteInt(self._configPrefix + "EditorWordWrap", self._wordWrapCheckBox.GetValue())
event.Skip()
- def SetViewDefaults(self, configPrefix = "Text", hasWordWrap = True, hasTabs = False):
+ def SetViewDefaults(self, configPrefix="Text", hasWordWrap=True, hasTabs=False, hasFolding=False):
config = wx.ConfigBase_Get()
self.SetViewWhiteSpace(config.ReadInt(configPrefix + "EditorViewWhitespace", False))
self.SetViewEOL(config.ReadInt(configPrefix + "EditorViewEOL", False))
self.SetIndentationGuides(config.ReadInt(configPrefix + "EditorViewIndentationGuides", False))
self.SetViewRightEdge(config.ReadInt(configPrefix + "EditorViewRightEdge", False))
self.SetViewLineNumbers(config.ReadInt(configPrefix + "EditorViewLineNumbers", True))
+ if hasFolding:
+ self.SetViewFolding(config.ReadInt(configPrefix + "EditorViewFolding", True))
if hasWordWrap:
self.SetWordWrap(config.ReadInt(configPrefix + "EditorWordWrap", False))
if hasTabs: # These methods do not exist in STCTextEditor and are meant for subclasses
self.SetMarginWidth(1, 0)
+ def GetViewFolding(self):
+ return self.GetMarginWidth(2) > 0
+
+
+ def SetViewFolding(self, viewFolding = True):
+ if viewFolding:
+ self.SetMarginWidth(2, 12)
+ else:
+ self.SetMarginWidth(2, 0)
+
+
def CanWordWrap(self):
return True
return False
- if id == SVNService.SVN_UPDATE_ID:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- filenames = self.GetCurrentDocuments()
- if filenames:
- filenames = filenames[:]
- filenames.sort(self.BasenameCaseInsensitiveCompare)
- else:
- folderPath = self.GetCurrentFolder()
- if folderPath:
- filenames = [folderPath]
-
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
-
- view = messageService.GetView()
- view.ClearLines()
- view.AddLines(_("SVN Update:\n"))
-
- for filename in filenames:
- view.AddLines("%s\n" % filename)
- try:
- status = self._client.update(filename)
-
- if status.number > 0:
- view.AddLines(_("Updated to revision %s\n") % status.number)
-
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for doc in openDocs:
- if doc.GetFilename() == filename:
- yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
- _("Updated file '%s' is currently open. Close it?") % os.path.basename(filename),
- _("Close File"),
- wx.YES_NO|wx.ICON_QUESTION)
- yesNoMsg.CenterOnParent()
- status = yesNoMsg.ShowModal()
- yesNoMsg.Destroy()
- if status == wx.ID_YES:
- doc.DeleteAllViews()
- break
- else:
- view.AddLines(_("Update failed.\n"))
+ try:
+ if id == SVNService.SVN_UPDATE_ID:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ filenames = self.GetCurrentDocuments()
+ if filenames:
+ filenames = filenames[:]
+ filenames.sort(self.BasenameCaseInsensitiveCompare)
+ else:
+ folderPath = self.GetCurrentFolder()
+ if folderPath:
+ filenames = [folderPath]
- except pysvn.ClientError, e:
- view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
- except:
- extype, ex, tb = sys.exc_info()
- view.AddLines("Update failed: (%s) %s\n" % (extype, str(ex)))
- for line in traceback.format_tb(tb):
- view.AddLines(line)
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ messageService.ShowWindow()
- wx.MessageBox(_("Update failed."), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
-
- return True
-
- elif id == SVNService.SVN_UPDATE_ALL_ID:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
-
- view = messageService.GetView()
- view.ClearLines()
- view.AddLines(_("SVN Update:\n"))
-
- project = self.GetCurrentProject()
- if project:
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for doc in openDocs:
- if doc.GetFilename() == project:
- filenames = doc.GetFiles()[:] # make a copy and sort it.
- filenames.sort(self.BasenameCaseInsensitiveCompare)
-
- for filename in filenames:
- view.AddLines("%s\n" % filename)
- try:
- status = self._client.update(filename)
+ view = messageService.GetView()
+ view.ClearLines()
+ view.AddLines(_("SVN Update:\n"))
+
+ for filename in filenames:
+ view.AddLines("%s\n" % filename)
+ try:
+ status = self._client.update(filename)
+
+ if status.number > 0:
+ view.AddLines(_("Updated to revision %s\n") % status.number)
+
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for doc in openDocs:
+ if doc.GetFilename() == filename:
+ yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
+ _("Updated file '%s' is currently open. Close it?") % os.path.basename(filename),
+ _("Close File"),
+ wx.YES_NO|wx.ICON_QUESTION)
+ yesNoMsg.CenterOnParent()
+ status = yesNoMsg.ShowModal()
+ yesNoMsg.Destroy()
+ if status == wx.ID_YES:
+ doc.DeleteAllViews()
+ break
+ else:
+ view.AddLines(_("Update failed.\n"))
+
+ except pysvn.ClientError, e:
+ view.AddLines("%s\n" % str(e))
+ wx.MessageBox(str(e), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
+ except:
+ extype, ex, tb = sys.exc_info()
+ view.AddLines("Update failed: (%s) %s\n" % (extype, str(ex)))
+ for line in traceback.format_tb(tb):
+ view.AddLines(line)
+
+ wx.MessageBox(_("Update failed."), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+ return True
- if status.number > 0:
- view.AddLines(_("Updated to revision %s\n") % status.number)
-
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for doc in openDocs:
- if doc.GetFilename() == filename:
- yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
- _("Updated file '%s' is currently open. Close it?") % os.path.basename(filename),
- _("Close File"),
- wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
- yesNoMsg.CenterOnParent()
- status = yesNoMsg.ShowModal()
- yesNoMsg.Destroy()
- if status == wx.ID_YES:
- doc.DeleteAllViews()
- elif status == wx.ID_NO:
- pass
- else: # elif status == wx.CANCEL:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- return True
- break
- else:
- view.AddLines(_("Update failed.\n"))
+ elif id == SVNService.SVN_UPDATE_ALL_ID:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ messageService.ShowWindow()
+
+ view = messageService.GetView()
+ view.ClearLines()
+ view.AddLines(_("SVN Update:\n"))
- except pysvn.ClientError, e:
- view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
- except:
- extype, ex, tb = sys.exc_info()
- view.AddLines("Update failed: (%s) %s\n" % (extype, str(ex)))
- for line in traceback.format_tb(tb):
- view.AddLines(line)
+ project = self.GetCurrentProject()
+ if project:
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for doc in openDocs:
+ if doc.GetFilename() == project:
+ filenames = doc.GetFiles()[:] # make a copy and sort it.
+ filenames.sort(self.BasenameCaseInsensitiveCompare)
+
+ for filename in filenames:
+ view.AddLines("%s\n" % filename)
+ try:
+ status = self._client.update(filename)
+
+ if status.number > 0:
+ view.AddLines(_("Updated to revision %s\n") % status.number)
- wx.MessageBox(_("Update failed."), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- return True
-
- elif id == SVNService.SVN_CHECKIN_ALL_ID:
- filenames = []
- project = self.GetCurrentProject()
- if project:
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for doc in openDocs:
+ if doc.GetFilename() == filename:
+ yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
+ _("Updated file '%s' is currently open. Close it?") % os.path.basename(filename),
+ _("Close File"),
+ wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
+ yesNoMsg.CenterOnParent()
+ status = yesNoMsg.ShowModal()
+ yesNoMsg.Destroy()
+ if status == wx.ID_YES:
+ doc.DeleteAllViews()
+ elif status == wx.ID_NO:
+ pass
+ else: # elif status == wx.CANCEL:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ return True
+ break
+ else:
+ view.AddLines(_("Update failed.\n"))
+
+ except pysvn.ClientError, e:
+ view.AddLines("%s\n" % str(e))
+ wx.MessageBox(str(e), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
+ except:
+ extype, ex, tb = sys.exc_info()
+ view.AddLines("Update failed: (%s) %s\n" % (extype, str(ex)))
+ for line in traceback.format_tb(tb):
+ view.AddLines(line)
+
+ wx.MessageBox(_("Update failed."), _("SVN Update"), wx.OK | wx.ICON_EXCLAMATION)
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ return True
+
+ elif id == SVNService.SVN_CHECKIN_ALL_ID:
+ filenames = []
+ project = self.GetCurrentProject()
+ if project:
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for doc in openDocs:
+ if doc.GetFilename() == project:
+ for filename in doc.GetFiles():
+ if filename not in filenames:
+ filenames.append(filename)
+ filenames.sort(self.BasenameCaseInsensitiveCompare)
+
+ # ask user if dirty files should be saved first
openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for doc in openDocs:
- if doc.GetFilename() == project:
- for filename in doc.GetFiles():
- if filename not in filenames:
- filenames.append(filename)
- filenames.sort(self.BasenameCaseInsensitiveCompare)
-
- # ask user if dirty files should be saved first
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for filename in filenames:
- for doc in openDocs:
- if doc.GetFilename() == filename and doc.IsModified():
- yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
- _("'%s' has unsaved modifications. Save it before commit?") % os.path.basename(filename),
- _("SVN Commit"),
- wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
- yesNoMsg.CenterOnParent()
- status = yesNoMsg.ShowModal()
- yesNoMsg.Destroy()
- if status == wx.ID_YES:
- doc.Save()
- elif status == wx.ID_NO:
- pass
- else: # elif status == wx.CANCEL:
- return True
- break
-
- shortFilenames = []
- for i, filename in enumerate(filenames):
- shortFilename = os.path.basename(filename)
- shortFilenames.append(shortFilename)
-
- dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Commit"))
-
- sizer = wx.BoxSizer(wx.VERTICAL)
- sizer.Add(wx.StaticText(dlg, -1, _("Comment:")), 0, wx.ALIGN_CENTER_VERTICAL)
- commentText = wx.TextCtrl(dlg, -1, size=(250,-1), style=wx.TE_MULTILINE)
- sizer.Add(commentText, 1, wx.EXPAND|wx.TOP, HALF_SPACE)
-
- sizer.Add(wx.StaticText(dlg, -1, _("Files:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE)
- fileList = wx.CheckListBox(dlg, -1, choices = shortFilenames)
- for i in range(fileList.GetCount()):
- fileList.Check(i, True)
- sizer.Add(fileList, 0, wx.EXPAND|wx.TOP, HALF_SPACE)
-
- buttonSizer = wx.StdDialogButtonSizer()
- okBtn = wx.Button(dlg, wx.ID_OK)
- okBtn.SetDefault()
- buttonSizer.AddButton(okBtn)
- buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
- buttonSizer.Realize()
-
- contentSizer = wx.BoxSizer(wx.VERTICAL)
- contentSizer.Add(sizer, 0, wx.ALL, SPACE)
- contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
-
- dlg.SetSizer(contentSizer)
- dlg.Fit()
- dlg.Layout()
-
- dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_OK:
+ for filename in filenames:
+ for doc in openDocs:
+ if doc.GetFilename() == filename and doc.IsModified():
+ yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
+ _("'%s' has unsaved modifications. Save it before commit?") % os.path.basename(filename),
+ _("SVN Commit"),
+ wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
+ yesNoMsg.CenterOnParent()
+ status = yesNoMsg.ShowModal()
+ yesNoMsg.Destroy()
+ if status == wx.ID_YES:
+ doc.Save()
+ elif status == wx.ID_NO:
+ pass
+ else: # elif status == wx.CANCEL:
+ return True
+ break
+
+ shortFilenames = []
+ for i, filename in enumerate(filenames):
+ shortFilename = os.path.basename(filename)
+ shortFilenames.append(shortFilename)
+
+ dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Commit"))
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(wx.StaticText(dlg, -1, _("Comment:")), 0, wx.ALIGN_CENTER_VERTICAL)
+ commentText = wx.TextCtrl(dlg, -1, size=(250,-1), style=wx.TE_MULTILINE)
+ sizer.Add(commentText, 1, wx.EXPAND|wx.TOP, HALF_SPACE)
+
+ sizer.Add(wx.StaticText(dlg, -1, _("Files:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE)
+ fileList = wx.CheckListBox(dlg, -1, choices = shortFilenames)
+ for i in range(fileList.GetCount()):
+ fileList.Check(i, True)
+ sizer.Add(fileList, 0, wx.EXPAND|wx.TOP, HALF_SPACE)
+
+ buttonSizer = wx.StdDialogButtonSizer()
+ okBtn = wx.Button(dlg, wx.ID_OK)
+ okBtn.SetDefault()
+ buttonSizer.AddButton(okBtn)
+ buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
+ buttonSizer.Realize()
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+ contentSizer.Add(sizer, 0, wx.ALL, SPACE)
+ contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
+
+ dlg.SetSizer(contentSizer)
+ dlg.Fit()
+ dlg.Layout()
+
+ dlg.CenterOnParent()
+ if dlg.ShowModal() == wx.ID_OK:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ messageService.ShowWindow()
+
+ view = messageService.GetView()
+ view.ClearLines()
+ view.AddLines(_("SVN Commit:\n"))
+
+ try:
+ selFilenames = []
+ for i in range(fileList.GetCount()):
+ if fileList.IsChecked(i):
+ selFilenames.append(filenames[i])
+ view.AddLines("%s\n" % filenames[i])
+
+ if len(selFilenames):
+ comment = commentText.GetValue()
+ status = self._client.checkin(selFilenames, comment)
+
+ if status is None:
+ view.AddLines(_("Nothing to commit.\n"))
+ elif status.number > 0:
+ view.AddLines(_("Committed as revision %s.\n") % status.number)
+ else:
+ view.AddLines(_("Commit failed.\n"))
+
+ except pysvn.ClientError, e:
+ view.AddLines("%s\n" % str(e))
+ wx.MessageBox(str(e), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+ except:
+ extype, ex, tb = sys.exc_info()
+ view.AddLines("Commit failed: (%s) %s\n" % (extype, str(ex)))
+ for line in traceback.format_tb(tb):
+ view.AddLines(line)
+ wx.MessageBox(_("Commit failed."), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ dlg.Destroy()
+ return True
+
+
+ elif id == SVNService.SVN_CHECKIN_ID:
+ filenames = self.GetCurrentDocuments()[:]
+ filenames.sort(self.BasenameCaseInsensitiveCompare)
+
+ # ask user if dirty files should be saved first
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for filename in filenames:
+ for doc in openDocs:
+ if doc.GetFilename() == filename and doc.IsModified():
+ yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
+ _("'%s' has unsaved modifications. Save it before commit?") % os.path.basename(filename),
+ _("SVN Commit"),
+ wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
+ yesNoMsg.CenterOnParent()
+ status = yesNoMsg.ShowModal()
+ yesNoMsg.Destroy()
+ if status == wx.ID_YES:
+ doc.Save()
+ elif status == wx.ID_NO:
+ pass
+ else: # elif status == wx.CANCEL:
+ return True
+ break
+
+ shortFilenames = []
+ for i, filename in enumerate(filenames):
+ shortFilename = os.path.basename(filename)
+ shortFilenames.append(shortFilename)
+
+ dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Commit"))
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(wx.StaticText(dlg, -1, _("Comment:")), 0, wx.ALIGN_CENTER_VERTICAL)
+ commentText = wx.TextCtrl(dlg, -1, size=(250,-1), style=wx.TE_MULTILINE)
+ sizer.Add(commentText, 1, wx.EXPAND|wx.TOP, HALF_SPACE)
+
+ sizer.Add(wx.StaticText(dlg, -1, _("Files:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE)
+ fileList = wx.CheckListBox(dlg, -1, choices = shortFilenames)
+ for i in range(fileList.GetCount()):
+ fileList.Check(i, True)
+ sizer.Add(fileList, 0, wx.EXPAND|wx.TOP, HALF_SPACE)
+
+ buttonSizer = wx.StdDialogButtonSizer()
+ okBtn = wx.Button(dlg, wx.ID_OK)
+ okBtn.SetDefault()
+ buttonSizer.AddButton(okBtn)
+ buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
+ buttonSizer.Realize()
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+ contentSizer.Add(sizer, 0, wx.ALL, SPACE)
+ contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
+
+ dlg.SetSizer(contentSizer)
+ dlg.Fit()
+ dlg.Layout()
+
+ dlg.CenterOnParent()
+ if dlg.ShowModal() == wx.ID_OK:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ messageService.ShowWindow()
+
+ view = messageService.GetView()
+ view.ClearLines()
+ view.AddLines(_("SVN Commit:\n"))
+
+ try:
+ selFilenames = []
+ for i in range(fileList.GetCount()):
+ if fileList.IsChecked(i):
+ selFilenames.append(filenames[i])
+ view.AddLines("%s\n" % filenames[i])
+
+ if len(selFilenames):
+ comment = commentText.GetValue()
+ status = self._client.checkin(selFilenames, comment)
+
+ if status is None:
+ view.AddLines(_("Nothing to commit.\n"))
+ elif status.number > 0:
+ view.AddLines(_("Committed as revision %s.\n") % status.number)
+ else:
+ view.AddLines(_("Commit failed.\n"))
+
+ except pysvn.ClientError, e:
+ view.AddLines("%s\n" % str(e))
+ wx.MessageBox(str(e), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+ except:
+ extype, ex, tb = sys.exc_info()
+ view.AddLines("Commit failed: (%s) %s\n" % (extype, str(ex)))
+ for line in traceback.format_tb(tb):
+ view.AddLines(line)
+ wx.MessageBox(_("Commit failed."), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ dlg.Destroy()
+ return True
+
+ elif id == SVNService.SVN_CHECKOUT_ID:
+ config = wx.ConfigBase_Get()
+ svnUrl = config.Read(SVN_REPOSITORY_URL, self._defaultURL)
+
+ dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Checkout"))
+
+ gridSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
+ gridSizer.Add(wx.StaticText(dlg, -1, _("Repository URL:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, HALF_SPACE)
+ svnUrlList = ReadSvnUrlList()
+ svnURLCombobox = wx.ComboBox(dlg, -1, size=(200, -1), choices=svnUrlList, style=wx.CB_DROPDOWN)
+ if len(svnUrlList):
+ svnURLCombobox.SetToolTipString(svnUrlList[0])
+ svnURLCombobox.SetStringSelection(svnUrlList[0])
+ else:
+ svnURLCombobox.SetToolTipString(_("Set Repository URL"))
+ gridSizer.Add(svnURLCombobox, 0)
+
+ gridSizer.Add(wx.StaticText(dlg, -1, _("Checkout to dir:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, HALF_SPACE)
+ localPath = wx.TextCtrl(dlg, -1, size = (200, -1))
+ localPath.SetToolTipString(_("Path in local file system where files will be located."))
+ findDirButton = wx.Button(dlg, -1, _("Browse..."))
+
+ def OnBrowseButton(event):
+ dirDlg = wx.DirDialog(wx.GetApp().GetTopWindow(), _("Choose a directory:"), style=wx.DD_DEFAULT_STYLE)
+ dir = localPath.GetValue()
+ if len(dir):
+ dirDlg.SetPath(dir)
+ dirDlg.CenterOnParent()
+ if dirDlg.ShowModal() == wx.ID_OK:
+ localPath.SetValue(dirDlg.GetPath())
+ localPath.SetToolTipString(localPath.GetValue())
+ localPath.SetInsertionPointEnd()
+ dirDlg.Destroy()
+ wx.EVT_BUTTON(findDirButton, -1, OnBrowseButton)
+
+ sizer = wx.BoxSizer(wx.HORIZONTAL)
+ sizer.Add(localPath, 1, wx.EXPAND)
+ sizer.Add(findDirButton, 0, wx.LEFT, HALF_SPACE)
+ gridSizer.Add(sizer, 0)
+
+ buttonSizer = wx.StdDialogButtonSizer()
+ okBtn = wx.Button(dlg, wx.ID_OK)
+ okBtn.SetDefault()
+ buttonSizer.AddButton(okBtn)
+ buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
+ buttonSizer.Realize()
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+ contentSizer.Add(gridSizer, 0, wx.ALL, SPACE)
+ contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
+
+ dlg.SetSizer(contentSizer)
+ dlg.Fit()
+ dlg.Layout()
+
+ dlg.CenterOnParent()
+ if dlg.ShowModal() == wx.ID_OK:
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+ WriteSvnUrlList(svnURLCombobox)
+
+ messageService = wx.GetApp().GetService(MessageService.MessageService)
+ messageService.ShowWindow()
+
+ view = messageService.GetView()
+ view.ClearLines()
+ view.AddLines(_("SVN Checkout:\n"))
+
+ svnUrl = svnURLCombobox.GetValue()
+ toLocation = localPath.GetValue()
+ try:
+ self._client.checkout(svnUrl, toLocation)
+ view.AddLines(_("Checkout completed.\n"))
+ except pysvn.ClientError, e:
+ view.AddLines(_("Checkout failed. %s\n") % str(e))
+ wx.MessageBox(_("Checkout failed. %s") % str(e), _("SVN Checkout"), wx.OK | wx.ICON_EXCLAMATION)
+ except:
+ extype, ex, tb = sys.exc_info()
+ view.AddLines("Checkout failed: (%s) %s\n" % (extype, str(ex)))
+ for line in traceback.format_tb(tb):
+ view.AddLines(line)
+ wx.MessageBox(_("Checkout failed."), _("SVN Checkout"), wx.OK | wx.ICON_EXCLAMATION)
+
+ wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ dlg.Destroy()
+ return True
+
+ elif id == SVNService.SVN_REVERT_ID:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
+
+ filenames = self.GetCurrentDocuments()
+
messageService = wx.GetApp().GetService(MessageService.MessageService)
messageService.ShowWindow()
-
+
view = messageService.GetView()
view.ClearLines()
- view.AddLines(_("SVN Commit:\n"))
-
+ view.AddLines(_("SVN Revert:\n"))
+ for filename in filenames:
+ view.AddLines("%s\n" % filename)
+
try:
- selFilenames = []
- for i in range(fileList.GetCount()):
- if fileList.IsChecked(i):
- selFilenames.append(filenames[i])
- view.AddLines("%s\n" % filenames[i])
-
- if len(selFilenames):
- comment = commentText.GetValue()
- status = self._client.checkin(selFilenames, comment)
-
- if status is None:
- view.AddLines(_("Nothing to commit.\n"))
- elif status.number > 0:
- view.AddLines(_("Committed as revision %s.\n") % status.number)
- else:
- view.AddLines(_("Commit failed.\n"))
-
+ self._client.revert(filenames)
+ view.AddLines(_("Revert completed.\n"))
+
+ openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+ for doc in openDocs[:]: # need to make a copy of the list otherwise ordinality changes as we close the files
+ if doc.GetFilename() in filenames:
+ yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
+ _("Reverted file '%s' is currently open. Close it?") % os.path.basename(doc.GetFilename()),
+ _("Close File"),
+ wx.YES_NO|wx.ICON_QUESTION)
+ yesNoMsg.CenterOnParent()
+ status = yesNoMsg.ShowModal()
+ yesNoMsg.Destroy()
+ if status == wx.ID_YES:
+ doc.DeleteAllViews()
+
except pysvn.ClientError, e:
view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+ wx.MessageBox(str(e), _("SVN Revert"), wx.OK | wx.ICON_EXCLAMATION)
except:
extype, ex, tb = sys.exc_info()
- view.AddLines("Commit failed: (%s) %s\n" % (extype, str(ex)))
+ view.AddLines("Revert failed: (%s) %s\n" % (extype, str(ex)))
for line in traceback.format_tb(tb):
view.AddLines(line)
- wx.MessageBox(_("Commit failed."), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
-
+ wx.MessageBox(_("Revert failed."), _("SVN Revert"), wx.OK | wx.ICON_EXCLAMATION)
+
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- dlg.Destroy()
- return True
-
-
- elif id == SVNService.SVN_CHECKIN_ID:
- filenames = self.GetCurrentDocuments()[:]
- filenames.sort(self.BasenameCaseInsensitiveCompare)
-
- # ask user if dirty files should be saved first
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for filename in filenames:
- for doc in openDocs:
- if doc.GetFilename() == filename and doc.IsModified():
- yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
- _("'%s' has unsaved modifications. Save it before commit?") % os.path.basename(filename),
- _("SVN Commit"),
- wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
- yesNoMsg.CenterOnParent()
- status = yesNoMsg.ShowModal()
- yesNoMsg.Destroy()
- if status == wx.ID_YES:
- doc.Save()
- elif status == wx.ID_NO:
- pass
- else: # elif status == wx.CANCEL:
- return True
- break
-
- shortFilenames = []
- for i, filename in enumerate(filenames):
- shortFilename = os.path.basename(filename)
- shortFilenames.append(shortFilename)
-
- dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Commit"))
-
- sizer = wx.BoxSizer(wx.VERTICAL)
- sizer.Add(wx.StaticText(dlg, -1, _("Comment:")), 0, wx.ALIGN_CENTER_VERTICAL)
- commentText = wx.TextCtrl(dlg, -1, size=(250,-1), style=wx.TE_MULTILINE)
- sizer.Add(commentText, 1, wx.EXPAND|wx.TOP, HALF_SPACE)
-
- sizer.Add(wx.StaticText(dlg, -1, _("Files:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.TOP, SPACE)
- fileList = wx.CheckListBox(dlg, -1, choices = shortFilenames)
- for i in range(fileList.GetCount()):
- fileList.Check(i, True)
- sizer.Add(fileList, 0, wx.EXPAND|wx.TOP, HALF_SPACE)
-
- buttonSizer = wx.StdDialogButtonSizer()
- okBtn = wx.Button(dlg, wx.ID_OK)
- okBtn.SetDefault()
- buttonSizer.AddButton(okBtn)
- buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
- buttonSizer.Realize()
-
- contentSizer = wx.BoxSizer(wx.VERTICAL)
- contentSizer.Add(sizer, 0, wx.ALL, SPACE)
- contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
-
- dlg.SetSizer(contentSizer)
- dlg.Fit()
- dlg.Layout()
-
- dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_OK:
+ return True
+
+ elif id == SVNService.SVN_ADD_ID:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
+
+ filenames = self.GetCurrentDocuments()
+
messageService = wx.GetApp().GetService(MessageService.MessageService)
messageService.ShowWindow()
-
+
view = messageService.GetView()
view.ClearLines()
- view.AddLines(_("SVN Commit:\n"))
-
+ view.AddLines(_("SVN Add:\n"))
+ for filename in filenames:
+ view.AddLines("%s\n" % filename)
+
try:
- selFilenames = []
- for i in range(fileList.GetCount()):
- if fileList.IsChecked(i):
- selFilenames.append(filenames[i])
- view.AddLines("%s\n" % filenames[i])
-
- if len(selFilenames):
- comment = commentText.GetValue()
- status = self._client.checkin(selFilenames, comment)
-
- if status is None:
- view.AddLines(_("Nothing to commit.\n"))
- elif status.number > 0:
- view.AddLines(_("Committed as revision %s.\n") % status.number)
- else:
- view.AddLines(_("Commit failed.\n"))
-
+ self._client.add(filenames)
+ view.AddLines(_("Add completed.\n"))
except pysvn.ClientError, e:
view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
+ wx.MessageBox(str(e), _("SVN Add"), wx.OK | wx.ICON_EXCLAMATION)
except:
extype, ex, tb = sys.exc_info()
- view.AddLines("Commit failed: (%s) %s\n" % (extype, str(ex)))
+ view.AddLines("Add failed: (%s) %s\n" % (extype, str(ex)))
for line in traceback.format_tb(tb):
view.AddLines(line)
- wx.MessageBox(_("Commit failed."), _("SVN Commit"), wx.OK | wx.ICON_EXCLAMATION)
-
+ wx.MessageBox(_("Add failed."), _("SVN Add"), wx.OK | wx.ICON_EXCLAMATION)
+
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- dlg.Destroy()
- return True
-
- elif id == SVNService.SVN_CHECKOUT_ID:
- config = wx.ConfigBase_Get()
- svnUrl = config.Read(SVN_REPOSITORY_URL, self._defaultURL)
-
- dlg = wx.Dialog(wx.GetApp().GetTopWindow(), -1, _("SVN Checkout"))
-
- gridSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
- gridSizer.Add(wx.StaticText(dlg, -1, _("Repository URL:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, HALF_SPACE)
- svnUrlList = ReadSvnUrlList()
- svnURLCombobox = wx.ComboBox(dlg, -1, size=(200, -1), choices=svnUrlList, style=wx.CB_DROPDOWN)
- if len(svnUrlList):
- svnURLCombobox.SetToolTipString(svnUrlList[0])
- svnURLCombobox.SetStringSelection(svnUrlList[0])
- else:
- svnURLCombobox.SetToolTipString(_("Set Repository URL"))
- gridSizer.Add(svnURLCombobox, 0)
-
- gridSizer.Add(wx.StaticText(dlg, -1, _("Checkout to dir:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, HALF_SPACE)
- localPath = wx.TextCtrl(dlg, -1, size = (200, -1))
- localPath.SetToolTipString(_("Path in local file system where files will be located."))
- findDirButton = wx.Button(dlg, -1, _("Browse..."))
-
- def OnBrowseButton(event):
- dirDlg = wx.DirDialog(wx.GetApp().GetTopWindow(), _("Choose a directory:"), style=wx.DD_DEFAULT_STYLE)
- dir = localPath.GetValue()
- if len(dir):
- dirDlg.SetPath(dir)
- dirDlg.CenterOnParent()
- if dirDlg.ShowModal() == wx.ID_OK:
- localPath.SetValue(dirDlg.GetPath())
- localPath.SetToolTipString(localPath.GetValue())
- localPath.SetInsertionPointEnd()
- dirDlg.Destroy()
- wx.EVT_BUTTON(findDirButton, -1, OnBrowseButton)
-
- sizer = wx.BoxSizer(wx.HORIZONTAL)
- sizer.Add(localPath, 1, wx.EXPAND)
- sizer.Add(findDirButton, 0, wx.LEFT, HALF_SPACE)
- gridSizer.Add(sizer, 0)
-
- buttonSizer = wx.StdDialogButtonSizer()
- okBtn = wx.Button(dlg, wx.ID_OK)
- okBtn.SetDefault()
- buttonSizer.AddButton(okBtn)
- buttonSizer.AddButton(wx.Button(dlg, wx.ID_CANCEL))
- buttonSizer.Realize()
-
- contentSizer = wx.BoxSizer(wx.VERTICAL)
- contentSizer.Add(gridSizer, 0, wx.ALL, SPACE)
- contentSizer.Add(buttonSizer, 0, wx.ALL|wx.ALIGN_RIGHT, SPACE)
-
- dlg.SetSizer(contentSizer)
- dlg.Fit()
- dlg.Layout()
-
- dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_OK:
+ return True
+
+ elif id == SVNService.SVN_DELETE_ID:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- WriteSvnUrlList(svnURLCombobox)
-
+
+ filenames = self.GetCurrentDocuments()
+
messageService = wx.GetApp().GetService(MessageService.MessageService)
messageService.ShowWindow()
-
+
view = messageService.GetView()
view.ClearLines()
- view.AddLines(_("SVN Checkout:\n"))
-
- svnUrl = svnURLCombobox.GetValue()
- toLocation = localPath.GetValue()
+ view.AddLines(_("SVN Delete:\n"))
+ for filename in filenames:
+ view.AddLines("%s\n" % filename)
+
try:
- self._client.checkout(svnUrl, toLocation)
- view.AddLines(_("Checkout completed.\n"))
+ self._client.remove(filenames)
+ view.AddLines(_("Delete completed.\n"))
except pysvn.ClientError, e:
- view.AddLines(_("Checkout failed. %s\n") % str(e))
- wx.MessageBox(_("Checkout failed. %s") % str(e), _("SVN Checkout"), wx.OK | wx.ICON_EXCLAMATION)
+ view.AddLines("%s\n" % str(e))
+ wx.MessageBox(str(e), _("SVN Delete"), wx.OK | wx.ICON_EXCLAMATION)
except:
extype, ex, tb = sys.exc_info()
- view.AddLines("Checkout failed: (%s) %s\n" % (extype, str(ex)))
+ view.AddLines("Delete failed: (%s) %s\n" % (extype, str(ex)))
for line in traceback.format_tb(tb):
view.AddLines(line)
- wx.MessageBox(_("Checkout failed."), _("SVN Checkout"), wx.OK | wx.ICON_EXCLAMATION)
-
+ wx.MessageBox(_("Delete failed."), _("SVN Delete"), wx.OK | wx.ICON_EXCLAMATION)
+
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- dlg.Destroy()
- return True
-
- elif id == SVNService.SVN_REVERT_ID:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- filenames = self.GetCurrentDocuments()
-
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
-
- view = messageService.GetView()
- view.ClearLines()
- view.AddLines(_("SVN Revert:\n"))
- for filename in filenames:
- view.AddLines("%s\n" % filename)
-
- try:
- self._client.revert(filenames)
- view.AddLines(_("Revert completed.\n"))
-
- openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
- for doc in openDocs[:]: # need to make a copy of the list otherwise ordinality changes as we close the files
- if doc.GetFilename() in filenames:
- yesNoMsg = wx.MessageDialog(wx.GetApp().GetTopWindow(),
- _("Reverted file '%s' is currently open. Close it?") % os.path.basename(doc.GetFilename()),
- _("Close File"),
- wx.YES_NO|wx.ICON_QUESTION)
- yesNoMsg.CenterOnParent()
- status = yesNoMsg.ShowModal()
- yesNoMsg.Destroy()
- if status == wx.ID_YES:
- doc.DeleteAllViews()
-
- except pysvn.ClientError, e:
- view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Revert"), wx.OK | wx.ICON_EXCLAMATION)
- except:
- extype, ex, tb = sys.exc_info()
- view.AddLines("Revert failed: (%s) %s\n" % (extype, str(ex)))
- for line in traceback.format_tb(tb):
- view.AddLines(line)
- wx.MessageBox(_("Revert failed."), _("SVN Revert"), wx.OK | wx.ICON_EXCLAMATION)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- return True
-
- elif id == SVNService.SVN_ADD_ID:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- filenames = self.GetCurrentDocuments()
-
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
-
- view = messageService.GetView()
- view.ClearLines()
- view.AddLines(_("SVN Add:\n"))
- for filename in filenames:
- view.AddLines("%s\n" % filename)
-
- try:
- self._client.add(filenames)
- view.AddLines(_("Add completed.\n"))
- except pysvn.ClientError, e:
- view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Add"), wx.OK | wx.ICON_EXCLAMATION)
- except:
- extype, ex, tb = sys.exc_info()
- view.AddLines("Add failed: (%s) %s\n" % (extype, str(ex)))
- for line in traceback.format_tb(tb):
- view.AddLines(line)
- wx.MessageBox(_("Add failed."), _("SVN Add"), wx.OK | wx.ICON_EXCLAMATION)
-
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- return True
-
- elif id == SVNService.SVN_DELETE_ID:
- wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
-
- filenames = self.GetCurrentDocuments()
-
- messageService = wx.GetApp().GetService(MessageService.MessageService)
- messageService.ShowWindow()
-
- view = messageService.GetView()
- view.ClearLines()
- view.AddLines(_("SVN Delete:\n"))
- for filename in filenames:
- view.AddLines("%s\n" % filename)
-
- try:
- self._client.remove(filenames)
- view.AddLines(_("Delete completed.\n"))
- except pysvn.ClientError, e:
- view.AddLines("%s\n" % str(e))
- wx.MessageBox(str(e), _("SVN Delete"), wx.OK | wx.ICON_EXCLAMATION)
- except:
- extype, ex, tb = sys.exc_info()
- view.AddLines("Delete failed: (%s) %s\n" % (extype, str(ex)))
- for line in traceback.format_tb(tb):
- view.AddLines(line)
- wx.MessageBox(_("Delete failed."), _("SVN Delete"), wx.OK | wx.ICON_EXCLAMATION)
-
+ return True
+
+ return False
+ finally:
wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
- return True
-
- return False
def ProcessUpdateUIEvent(self, event):
#
# Created: 3/10/05
# CVS-ID: $Id$
-# Copyright: (c) 2005 ActiveGrid, Inc.
+# Copyright: (c) 2005-2006 ActiveGrid, Inc.
# License: wxWindows License
#----------------------------------------------------------------------------
import wx
import string
import ProjectEditor
-import activegrid.util.sysutils as sysutils
-import activegrid.util.strutils as strutils
import activegrid.util.appdirs as appdirs
+import activegrid.util.fileutils as fileutils
+import activegrid.util.strutils as strutils
+import activegrid.util.sysutils as sysutils
+import activegrid.util.xmlutils as xmlutils
_ = wx.GetTranslation
-def CreateDirectoryControl( parent, fileLabel=_("File Name:"), dirLabel=_("Directory"), fileExtension="*", startingName="", startingDirectory=None, choiceDirs=None, appDirDefaultStartDir=False, returnAll=False):
+def CreateDirectoryControl( parent, fileLabel=_("File Name:"), dirLabel=_("Directory:"), fileExtension="*", startingName="", startingDirectory=None, choiceDirs=None, appDirDefaultStartDir=False, returnAll=False, useDirDialog=False):
if not choiceDirs:
choiceDirs = []
if os.getcwd() not in choiceDirs:
choiceDirs.append(os.getcwd())
- if appdirs.documents_folder not in choiceDirs:
- choiceDirs.append(appdirs.documents_folder)
+ if appdirs.getSystemDir() not in choiceDirs:
+ choiceDirs.append(appdirs.getSystemDir())
if not startingDirectory:
startingDirectory = os.getcwd()
else:
name = _("%s.%s") % (nameCtrlValue, fileExtension)
- dlg = wx.FileDialog(parent, _("Choose a filename and directory"),
+ if not useDirDialog:
+ dlg = wx.FileDialog(parent, _("Choose a filename and directory"),
defaultDir = dirControl.GetValue().strip(),
defaultFile = name,
wildcard= "*.%s" % fileExtension,
style=wx.SAVE|wx.CHANGE_DIR)
-
+ else:
+ dlg = wx.DirDialog(wx.GetApp().GetTopWindow(),
+ _("Choose a directory:"),
+ defaultPath=dirControl.GetValue().strip(),
+ style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON)
+
if dlg.ShowModal() != wx.ID_OK:
dlg.Destroy()
return
dlg.Destroy()
if path:
- dir, filename = os.path.split(path)
- if dirControl.FindString(dir) == wx.NOT_FOUND:
- dirControl.Insert(dir, 0)
- dirControl.SetValue(dir)
- dirControl.SetToolTipString(dir)
- nameControl.SetValue(filename)
+ if not useDirDialog:
+ dir, filename = os.path.split(path)
+ if dirControl.FindString(dir) == wx.NOT_FOUND:
+ dirControl.Insert(dir, 0)
+ dirControl.SetValue(dir)
+ dirControl.SetToolTipString(dir)
+ nameControl.SetValue(filename)
+ else:
+ dirControl.SetValue(path)
+ dirControl.SetToolTipString(path)
parent.Bind(wx.EVT_BUTTON, OnFindDirClick, button)
- def Validate(allowOverwriteOnPrompt=False, infoString='', noFirstCharDigit=False):
+ def Validate(allowOverwriteOnPrompt=False, infoString='', validClassName=False, ignoreFileConflicts=False):
projName = nameControl.GetValue().strip()
if projName == "":
wx.MessageBox(_("Please provide a %sfile name.") % infoString, _("Provide a File Name"))
return False
- if noFirstCharDigit and projName[0].isdigit():
- wx.MessageBox(_("File name cannot start with a number. Please enter a different name."), _("Invalid File Name"))
- return False
if projName.find(' ') != -1:
wx.MessageBox(_("Please provide a %sfile name that does not contains spaces.") % infoString, _("Spaces in File Name"))
return False
- if not os.path.exists(dirControl.GetValue()):
- wx.MessageBox(_("That %sdirectory does not exist. Please choose an existing directory.") % infoString, _("Provide a Valid Directory"))
+ if validClassName:
+ if projName[0].isdigit():
+ wx.MessageBox(_("File name cannot start with a number. Please enter a different name."), _("Invalid File Name"))
+ return False
+ if projName.endswith(".agp"):
+ projName2 = projName[:-4]
+ else:
+ projName2 = projName
+ if not projName2.replace("_", "a").isalnum(): # [a-zA-Z0-9_] note '_' is allowed and ending '.agp'.
+ wx.MessageBox(_("Name must be alphanumeric ('_' allowed). Please enter a valid name."), _("Project Name"))
+ return False
+
+ dirName = dirControl.GetValue().strip()
+ if dirName == "":
+ wx.MessageBox(_("No directory. Please provide a directory."), _("Provide a Directory"))
return False
-
- filePath = os.path.join(dirControl.GetValue(), MakeNameEndInExtension(projName, "." + fileExtension))
- if os.path.exists(filePath):
- if allowOverwriteOnPrompt:
- res = wx.MessageBox(_("That %sfile already exists. Would you like to overwrite it.") % infoString, "File Exists", style=wx.YES_NO|wx.NO_DEFAULT)
- return (res == wx.YES)
- else:
- wx.MessageBox(_("That %sfile already exists. Please choose a different name.") % infoString, "File Exists")
- return False
+ if os.sep == "\\" and dirName.find("/") != -1:
+ wx.MessageBox(_("Wrong delimiter '/' found in directory path. Use '%s' as delimiter.") % os.sep, _("Provide a Valid Directory"))
+ return False
+ if not os.path.exists(dirName):
+ wx.MessageBox(_("That %sdirectory does not exist. Please choose an existing directory.") % infoString, _("Provide a Valid Directory"))
+ return False
+ if not ignoreFileConflicts:
+ filePath = os.path.join(dirName, MakeNameEndInExtension(projName, "." + fileExtension))
+ if os.path.exists(filePath):
+ if allowOverwriteOnPrompt:
+ res = wx.MessageBox(_("That %sfile already exists. Would you like to overwrite it.") % infoString, "File Exists", style=wx.YES_NO|wx.NO_DEFAULT)
+ return (res == wx.YES)
+ else:
+ wx.MessageBox(_("That %sfile already exists. Please choose a different name.") % infoString, "File Exists")
+ return False
+
return True
HALF_SPACE = 5
flexGridSizer = wx.FlexGridSizer(cols = 3, vgap = HALF_SPACE, hgap = HALF_SPACE)
flexGridSizer.AddGrowableCol(1,1)
- flexGridSizer.Add(nameLabelText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
- flexGridSizer.Add(nameControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
- flexGridSizer.Add(button, flag=wx.ALIGN_RIGHT|wx.LEFT, border=HALF_SPACE)
+ if not useDirDialog:
+ flexGridSizer.Add(nameLabelText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+ flexGridSizer.Add(nameControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
+ flexGridSizer.Add(button, flag=wx.ALIGN_RIGHT|wx.LEFT, border=HALF_SPACE)
+ flexGridSizer.Add(dirLabelText, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+ flexGridSizer.Add(dirControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
+ flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+ else:
+ flexGridSizer.Add(nameLabelText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+ flexGridSizer.Add(nameControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
+ flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+ flexGridSizer.Add(dirLabelText, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+ flexGridSizer.Add(dirControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
+ flexGridSizer.Add(button, flag=wx.ALIGN_RIGHT|wx.LEFT, border=HALF_SPACE)
- flexGridSizer.Add(dirLabelText, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
- flexGridSizer.Add(dirControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND)
- flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
if returnAll:
return nameControl, dirControl, flexGridSizer, Validate, allControls
else:
if os.getcwd() not in choiceDirs:
choiceDirs.append(os.getcwd())
- if appdirs.documents_folder not in choiceDirs:
- choiceDirs.append(appdirs.documents_folder)
+ if appdirs.getSystemDir() not in choiceDirs:
+ choiceDirs.append(appdirs.getSystemDir())
if not startingDirectory:
if dirName == "":
wx.MessageBox(_("Please provide a directory."), _("Provide a Directory"))
return False
+ if os.sep == "\\" and dirName.find("/") != -1:
+ wx.MessageBox(_("Wrong delimiter '/' found in directory path. Use '%s' as delimiter.") % os.sep, _("Provide a Valid Directory"))
+ return False
if not os.path.exists(dirName):
wx.MessageBox(_("That directory does not exist. Please choose an existing directory."), _("Provide a Valid Directory"))
return False
fileLabelText = wx.StaticText(parent, -1, fileLabel)
nameControl = wx.TextCtrl(parent, -1, startingName, size=(-1,-1))
- def Validate(allowOverwriteOnPrompt=False, noFirstCharDigit=False):
+ def Validate(allowOverwriteOnPrompt=False, validClassName=False):
projName = nameControl.GetValue().strip()
if projName == "":
wx.MessageBox(_("Blank name. Please enter a valid name."), _("Project Name"))
return False
- if noFirstCharDigit and projName[0].isdigit():
- wx.MessageBox(_("Name cannot start with a number. Please enter a valid name."), _("Project Name"))
- return False
if projName.find(' ') != -1:
- wx.MessageBox(_("Spaces in name. Name cannot have spaces.") % infoString, _("Project Name"))
+ wx.MessageBox(_("Spaces in name. Name cannot have spaces."), _("Project Name"))
return False
+ if validClassName:
+ if projName[0].isdigit():
+ wx.MessageBox(_("Name cannot start with a number. Please enter a valid name."), _("Project Name"))
+ return False
+ if projName.endswith(".agp"):
+ projName2 = projName[:-4]
+ else:
+ projName2 = projName
+ if not projName2.replace("_", "a").isalnum(): # [a-zA-Z0-9_] note '_' is allowed and ending '.agp'.
+ wx.MessageBox(_("Name must be alphanumeric ('_' allowed). Please enter a valid name."), _("Project Name"))
+ return False
path = os.path.join(startingDirectoryControl.GetValue().strip(), projName)
if os.path.exists(path):
if os.path.isdir(path):
return nameControl, flexGridSizer, Validate
+def ValidateName(name, ext=None, hint="name"):
+ """ Returns an error string if there is something wrong with the name.
+ Otherwise it returns None
+ """
+ if name == "":
+ return _("Blank %s. Please enter a valid %s.") % (hint, hint)
+
+ if name.find(' ') != -1:
+ return _("Spaces in %s. %s cannot have spaces.") % (hint, hint.title())
+
+ if name[0].isdigit():
+ return _("%s cannot start with a number. Please enter a valid %s.") % (hint.title(), hint)
+
+ if ext and name.endswith(ext): # strip extension if provided
+ lenExt = len(ext)
+ name = name[:-lenExt]
+
+ if not name.replace("_", "a").isalnum(): # [a-zA-Z0-9_] note '_' is allowed and ext ending.
+ return _("%s must be alphanumeric ('_' allowed). Please enter a valid %s.") % (hint.title(), hint)
+
+ return None
+
+
def GetCurrentProject():
projectDocument = None
projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
return pythonExecPath
+def GetPHPExecPath():
+ PHPExecPath = wx.ConfigBase_Get().Read("ActiveGridPHPLocation")
+ return PHPExecPath
+
+
+def GetPHPINIPath():
+ PHPINIPath = wx.ConfigBase_Get().Read("ActiveGridPHPINILocation")
+ return PHPINIPath
+
+
def _DoRemoveRecursive(path, skipFile=None, skipped=False):
if path == skipFile:
skipped = True
def GetAnnotation(model, elementName):
""" Get an object's annotation used for tooltips """
- if hasattr(model, "__xsdcomplextype__"):
+ if hasattr(model, "_complexType"):
+ ct = model._complexType
+ elif hasattr(model, "__xsdcomplextype__"):
ct = model.__xsdcomplextype__
- if ct:
- el = ct.findElement(elementName)
- if el and el.annotation:
- return el.annotation
+ else:
+ ct = None
+
+ if ct:
+ el = ct.findElement(elementName)
+ if el and el.annotation:
+ return el.annotation
return ""
+def GetDisplayName(doc, name):
+ if name:
+ appDocMgr = doc.GetAppDocMgr()
+ if appDocMgr:
+ name = appDocMgr.toDisplayTypeName(name)
+ else:
+ namespace, name = xmlutils.splitType(name)
+ if namespace and hasattr(doc.GetModel(), "getXmlNamespaces"):
+ for xmlkey, xmlval in doc.GetModel().getXmlNamespaces().iteritems():
+ if xmlval == namespace:
+ name = "%s:%s" % (xmlkey, name)
+ break
+
+ if name:
+ import activegrid.model.schema as schemalib
+ baseTypeName = schemalib.mapXsdType(name)
+ if baseTypeName:
+ name = baseTypeName
+
+ return name
+
+
+def GetInternalName(doc, name):
+ if name:
+ appDocMgr = doc.GetAppDocMgr()
+ if appDocMgr:
+ name = appDocMgr.toInternalTypeName(name)
+ else:
+ namespace, name = xmlutils.splitType(name)
+ if namespace and hasattr(doc.GetModel(), "getXmlNamespaces"):
+ for xmlkey, xmlval in doc.GetModel().getXmlNamespaces().iteritems():
+ if xmlkey == namespace:
+ name = "%s:%s" % (xmlval, name)
+ break
+
+ import activegrid.model.schema as schemalib
+ name = schemalib.mapAGType(name)
+
+ return name
+
+
#----------------------------------------------------------------------------
# Methods for finding application level info
#----------------------------------------------------------------------------
def GetAppInfoLanguage(doc=None):
- from activegrid.server.deployment import LANGUAGE_DEFAULT
+ from activegrid.server.projectmodel import LANGUAGE_DEFAULT
+
if doc:
language = doc.GetAppInfo().language
else:
doc.GetAppInfo().language = language # once it is selected, it must be set.
return language
+
+def AddWsdlAgToProjectFromWsdlRegistration(wsdlRegistration):
+ """Add wsdl ag for registry entry."""
+
+ wsdlPath = wsdlRegistration.path
+ rootPath = None
+ serviceRefName = wsdlRegistration.name
+
+ agwsDoc = _InitWsdlAg(wsdlPath, rootPath, serviceRefName)
+
+ if (agwsDoc == None):
+ return
+
+ serviceRef = agwsDoc.GetModel()
+
+ serviceRef.serviceType = wsdlRegistration.type
+
+ import activegrid.server.deployment as deployment
+
+ if (serviceRef.serviceType == deployment.SERVICE_LOCAL):
+ serviceRef.localService = deployment.LocalService(
+ wsdlRegistration.codeFile)
+
+ elif (serviceRef.serviceType == deployment.SERVICE_DATABASE):
+ serviceRef.databaseService = deployment.DatabaseService(
+ wsdlRegistration.datasourceName)
+
+ elif (serviceRef.serviceType == deployment.SERVICE_SOAP):
+ pass
+
+ elif (serviceRef.serviceType == deployment.SERVICE_RSS):
+ serviceRef.rssService = deployment.RssService(wsdlRegistration.feedUrl)
+
+ elif (serviceRef.serviceType == deployment.SERVICE_REST):
+ serviceRef.restService = deployment.RestService(
+ wsdlRegistration.baseUrl)
+ else:
+ raise AssertionError("Unknown service type")
+
+ _AddToProject(agwsDoc, addWsdl=True)
+
+
+def AddWsdlAgToProject(wsdlPath, rootPath=fileutils.AG_SYSTEM_STATIC_VAR_REF,
+ serviceRefName=None, className=None, serviceType=None,
+ dataSourceName=None):
+ """
+ wsdlPath: path to wsdl from rootPath. If wsdlPath is absolute, rootPath
+ is ignored. rootPath is also ignored when rootPath is set to None.
+ rootPath: defaults to ${AG_SYSTEM_STATIC}.
+ serviceRefName: If None, it will be set to the wsdl file name without
+ the .wsdl file extension.
+ className: if not None, will be used for the the wsdlag's ClassName.
+ serviceType: defaults to local.
+ dataSourceName: if serviceType is deployment.DATABASE, the ds must be
+ provided.
+ """
+ import WsdlAgEditor
+ import XFormWizard
+ import activegrid.model.basedocmgr as basedocmgr
+ import activegrid.server.deployment as deployment
+
+ if (serviceType == None):
+ serviceType = deployment.SERVICE_LOCAL
+
+
+ agwsDoc = _InitWsdlAg(wsdlPath, rootPath, serviceRefName)
+
+ if (agwsDoc == None):
+ return
+
+ serviceRef = agwsDoc.GetModel()
+
+ serviceRef.serviceType = serviceType
+
+ if (serviceType == deployment.SERVICE_DATABASE and dataSourceName != None):
+ serviceRef.databaseService = deployment.DatabaseService(dataSourceName)
+ else:
+ serviceRef.localService = deployment.LocalService(className=className)
+
+ _AddToProject(agwsDoc)
+
+
+def _AddToProject(agwsDoc, addWsdl=False):
+ import activegrid.model.basedocmgr as basedocmgr
+ projectDoc = GetCurrentProject()
+ agwsDoc.OnSaveDocument(agwsDoc.GetFilename())
+
+ files = [agwsDoc.fileName]
+ types = [basedocmgr.FILE_TYPE_SERVICE]
+ names = [agwsDoc.GetModel().name]
+ if (addWsdl):
+ m = agwsDoc.GetModel()
+ wsdlName = os.path.splitext(os.path.basename(m.filePath))[0]
+ appDocMgr = projectDoc.GetAppDocMgr()
+ if (appDocMgr.findService(wsdlName) == None):
+ m = agwsDoc.GetModel()
+ files.append(m.filePath)
+ types.append(None)
+ names.append(wsdlName)
+
+ ProjectEditor.ProjectAddFilesCommand(projectDoc, files, types=types,
+ names=names).Do()
+
+
+def _InitWsdlAg(wsdlPath, rootPath=fileutils.AG_SYSTEM_STATIC_VAR_REF,
+ serviceRefName=None):
+
+ projectDoc = GetCurrentProject()
+ appDocMgr = projectDoc.GetAppDocMgr()
+
+ if (serviceRefName == None):
+ serviceRefName = os.path.splitext(os.path.basename(wsdlPath))[0]
+
+ if (appDocMgr.findServiceRef(serviceRefName) != None):
+ return None
+
+ import WsdlAgEditor
+ import XFormWizard
+ import activegrid.server.deployment as deployment
+
+ template = XFormWizard.GetTemplate(WsdlAgEditor.WsdlAgDocument)
+ ext = template.GetDefaultExtension()
+ fullPath = os.path.join(appDocMgr.homeDir, serviceRefName + ext)
+
+ agwsDoc = template.CreateDocument(
+ fullPath, flags=(wx.lib.docview.DOC_NO_VIEW|wx.lib.docview.DOC_NEW|
+ wx.lib.docview.DOC_OPEN_ONCE))
+
+ serviceRef = agwsDoc.GetModel()
+ serviceRef.name = serviceRefName
+
+ if (rootPath == None or os.path.isabs(wsdlPath)):
+ serviceRef.filePath = wsdlPath
+ else:
+ # make sure to use forward slashes for the path to the .wsdl
+ wsdlPath = wsdlPath.replace("\\", "/")
+
+ if not wsdlPath.startswith("/"):
+ wsdlPath = "/%s" % wsdlPath
+ serviceRef.filePath = "%s%s" % (rootPath, wsdlPath)
+
+ agwsDoc.fileName = fullPath
+
+ return agwsDoc
+
+
+def GetSchemaName(schema):
+ return os.path.basename(schema.fileName)
+
+
+class AGChoice(wx.Choice):
+ """Extension to wx.Choice that fixes linux bug where first item of choices
+ passed into ctor would be visible, but not selected."""
+ def __init__(self, parent, id, choices=[]):
+ super(AGChoice, self).__init__(parent=parent, id=id)
+ self.AppendItems(choices)
import wx.xrc as xrc
import wx.wizard
-
+WHITE_COLOR = wx.Color(0xFF, 0xFF, 0xFF)
+LABEL_FONT = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Arial")
+SELECTED_LABEL_FONT = wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD, faceName="Arial")
+TINY_FONT = wx.Font(6, wx.SWISS, wx.NORMAL, wx.BOLD, faceName="Arial")
+ELLIPSIS_FONT = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Arial")
+ACTIVEGRID_ORANGE_COLOR = wx.Color(0xF9, 0x9E, 0x1B)
#----------------------------------------------------------------------------
# Classes
#----------------------------------------------------------------------------
def __init__(self, parent, title, pos=(-1,-1)):
- wizardBitMap = getWizardBitmap()
- wx.wizard.Wizard.__init__(self, parent, wx.NewId(), title, wizardBitMap, pos=pos)
+ self.bitmap = getWizardBGShorterBitmap()
+ self.title = title
+ wx.wizard.Wizard.__init__(self, parent, wx.NewId(), title, self.bitmap, pos=pos)
+ self.myDC = wx.MemoryDC()
+ self.crumbs = []
+ self.firstPage = None
+
def GetDocument(self):
if self.GetParent() and hasattr(self.GetParent(), 'GetDocument'):
return self.GetParent().GetDocument()
def SetPrevNext(self, prev, next):
prev.SetNext(next)
next.SetPrev(prev)
-
-
+
+ def RunWizard(self, firstPage):
+ self.firstPage = firstPage
+ return wx.wizard.Wizard.RunWizard(self, firstPage)
+
+ def BuildCrumbsList(self, onPage):
+ def PastThisPage(currentPage, pageToCheck):
+ foundPageToCheck = False
+ tempPage = self.firstPage
+ while hasattr(tempPage, '_next'):
+ if tempPage == currentPage:
+ return foundPageToCheck
+ if tempPage == pageToCheck:
+ foundPageToCheck = True
+ tempPage = tempPage._next
+ self.crumbs = []
+ currPage = self.firstPage
+ while hasattr(currPage, '_next'):
+ self.crumbs.append(currPage.title.GetLabel())
+ if currPage.pauseCrumbTrail and not PastThisPage(onPage, currPage):
+ self.crumbs.append('?')
+ return
+ currPage = currPage._next
+
+
+ def GetBreadcrumbsBitmap(self, page):
+ bitmap = getWizardBGShorterBitmap()
+ highlightText = page.title.GetLabel()
+ self.BuildCrumbsList(page)
+ self.myDC.BeginDrawing()
+ self.myDC.SelectObject(bitmap)
+ #self.myDC.SetFont(TINY_FONT)
+ #self.myDC.DrawText(self.title, 10, 35)
+ #print "Title was w=%i, h=%i" % self.myDC.GetTextExtent(self.title)
+ x = 20
+ y = 50
+ for crumb in self.crumbs:
+ if crumb == highlightText:
+ self.myDC.SetTextForeground(ACTIVEGRID_ORANGE_COLOR)
+ self.myDC.SetFont(SELECTED_LABEL_FONT)
+ else:
+ self.myDC.SetTextForeground(WHITE_COLOR)
+ self.myDC.SetFont(LABEL_FONT)
+ lines = self.BreakIntoLines(crumb)
+ offset = 0
+ w = h = 0
+ for line in lines:
+ offset += h + 3
+ if line == '? ':
+ decisionBM = getDecisionBitmap()
+ x1 = (bitmap.GetWidth() - decisionBM.GetWidth()) / 2
+ self.myDC.DrawBitmap(decisionBM, x1, y + offset, True)
+ else:
+ self.myDC.DrawText(line, x, y + offset)
+ w, h = self.myDC.GetTextExtent(line)
+ y += 30 + offset
+ self.myDC.EndDrawing()
+ self.myDC.SelectObject(wx.NullBitmap)
+ return bitmap
+
+ def CenterTextUnderParent(self, dc, parentWidth, parentX, text):
+ xbase = parentX + parentWidth / 2
+ w,h = dc.GetTextExtent(text)
+ return xbase - w / 2
+
+ def BreakIntoLines(self, text, maxLineLength=22):
+ words = text.split(' ')
+ retval = []
+ count = 0
+ currentLineLength = 0
+ currentLine = ''
+ for word in words:
+ if len(word) + currentLineLength >= maxLineLength:
+ retval.append(currentLine)
+ currentLine = word + ' '
+ currentLineLength = len(word) + 1
+ else:
+ currentLine += word + ' '
+ currentLineLength += len(word) + 1
+ if currentLine:
+ retval.append(currentLine)
+ return retval
+
class TitledWizardPage(wx.wizard.PyWizardPage):
- def __init__(self, parent, title=None):
+ def __init__(self, parent, title=None, pauseCrumbTrail=False):
+ self.pauseCrumbTrail = pauseCrumbTrail
self._prev = None
self._prevFunc = None
self._next = None
self.SetSizer(wx.BoxSizer(wx.VERTICAL))
self.MakePageTitle(title)
+ def GetBitmap(self):
+ return self.GetParent().GetBreadcrumbsBitmap(self)
+
def SetTitle(self, title):
if not title: title = ""
self.title.SetLabel(title)
stream = cStringIO.StringIO(getWizardData()) # NOTE: This reverts us to the bitmap Peter likes.
return ImageFromStream(stream)
+#----------------------------------------------------------------------
+def getWizardBackgroundData():
+ return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xac\x00\x00\x01\x87\x08\x02\
+\x00\x00\x00\xa2O.\xce\x00\x00\x00\x03sBIT\x08\x08\x08\xdb\xe1O\xe0\x00\x00 \
+\x00IDATx\x9c\xc5\xbdy\x94^\xc7u\x1fx\xeb\xeb\xd7\xdd@\xa3\xb1\x10\x04\x08\
+\x80\x04\x08\x82+\xb8\x8b\xa4HK\xa4<\x92M*\x8e#Y\x96\x9c\x13I\xf6$\xb2\x8e\
+\x97\xf1L&c+\x93\x8cg\xec\xe33\xf6\xc9\xccx\xc6g\x12\'>\x89c;\xb6\x13\xc7\
+\x1eG\x923^\x8emY\x96M\x91>\xb1\xb9J$ER\xe0N\x80\x00\x01\x02\x8d\xbd\x81\x06\
+\x1a\xbd\xd6\xfc\xf1^U\xdd\xb5\xaa\xde\xd7MM\x11\xec\xef\xbdZn\xdd\xaa\xfb\
+\xab{o-\xef=\xf7\xde\xef\xfdI\xd0\x82\x07\x9f\xae\xbdo\xa3x\x8c\xc8\xd9\x06\
+\x07\xae\xbbp\x0e\xe7\xe79=/H\xa9\x04"\x81\x9a\x91\xcbH\x8d\xd1\xd9J0{\x89\
+\xc3<c\x887T\x9b#\x9c\xe0K\xe7@\xb6\xc2n\x13\xcb)\xbb7\x17<)\xe2\x8b\r\x01\
+\x00\x80\x81\xc5P\xa6\xebu\x04x\xdf\xfd\xab\xe4\xbb\x8e\xbfoMp\x0cp\x16\xb0B\
+n5Z\xed\xf1\xbe\x08 y\\b\xc9\x81\xb3\xfee(;\xe7\xd4A\xc2\xe2\x9b\xae\x983G\
+\x0ck\x1b\x1f4!\x96\\;\xe7\xc1;p:\x12\xfb\x88\x7f\xa5j`u\x03\xadN\xf2\xe6\
+\xbd\xefX\xf2\x89\x07\x92M0\xd6\xa6\xb6\x9d\xe9P\x99t\xe1\x0b\x9d\xa0\xb0\
+\x89\xf2{\xf0V/\xc5\xf8F\xa6q\x86\xb4@\x14\x80\x92\x9cp\xa0\x14\xa9\t\x06~\
+\x03y\x83\x94\xc5\xb2\r\xf1*\x1e\xda\xea\xd41\xdd\x83\x1a\xbet\xac\x93s@\xb1\
+*\xack\x11\x06\x99\x15\x02\x08D\x1ea\xbf\x01\x00\xbc\xf7\xb5\xb2\x0c8P\xe2\
+\xab\x03\xee\x1a\x0cgS\x01\x18T\xac \xc1\x94\x18&\x06\xbe\xca;\xa9\xe4!\xaa\
+\xf7bN\x91\x12\xc6@\xcbd@v\x8d\x98\xd5\xeab\x11E\x13\x90|\xd8\xfcG\x04\x18\
+\x82\xf4\xac\x03\xd1\x00\xca\x94Jyh\xf9az\xdc\xe7:\x91\xe7\xad\x80#\xee\xf7\
+\x1a~\x12:M\x11\xbb\x94\xeaI\x82U\xb5E\xc4\x83W4\x9c\xe7Yy<\x8a\x89\n\xa9IiQ\
+\x04!-u\x13&-G\x8f6\xda\xd3@\xea\xeb\x00\xf6\x1a\xe52\xf4\xc1\x01*\xa4\xf9tX\
+\x0f\xf5B\x80\x1dS\x19Lw\xcf\xf3<\xdey\xf0\x86\x1ap\xc6u\x8c\t\xde\x86\xa9\t\
+\x18\x02:5P\x81\x80\x18\xcfz@\xe6\xe4]\xe4\\\x061x\xe0\x16:W\xe2@\xed"\xe7\
+\xf4\x89\xab\x96Se\xa3\x86\x1f\x05I\xf9\x99\x87\xe2\x1c\xb4z\xb1u"\x02\xb3R\
+\x97P*UF;Te\xfa\x048\xa8\xae@\xaf\x11^\xc8\x1c!c[\xdf\x81\x83\x91\xc1\xf2\
+\xc2\xd2\xa0G\xad\x1d\xf1\xde%0\x03L\xc0\xedm\xc1\x94\xac\xca\xdc\xc4\x01\
+\x1b&a\xe8"\x148\xef\xbc\xbb\xf3\x96\xdd\x9f\xf9\xc4\x87\x00\xe0O\x1f\xf9\
+\xfa\x13\xcf\xbd66\xda\xc9t\xe6\xc2\xec\xd8\xd8\xe8\xa5\xf9\x85\xe5\xe5\xe5\
+\xb1\xa6i\x9a\x91\x8b\x97\xe6\xd4\xaa\xcc\xd9A\xbcA\xd7\x9e\xfe\x92\xcc\xda|\
+)?\xb6\xcd\xc0H9\xe7\x9a\xc1\xd2\x87v\xbc\xbdu\xed\xec\x93S;\x0e\x9c\xdf\xd4\
+\x83V\xdd\xbcWe 7\xbe]\x9a\xfd\xf2l\x0e\x94x\x0f\xe0\x82ca\x18\xac\x8d\x93\
+\x13\xdb\xb6n\x9a:q\xf6\xdc\xcc\xac\xa5\xc9\x9dO\xf7\xb1\x01\x9b7N~\xe0\xbd{\
+\x01\xe0\xf1g_}\xff]7\xde\x7f\xcf^\x00\x98:y\xf6/\xfe\xfa\xf9\xef\xfb\xaeo{\
+\xf4\xc9o>\xf7\xd2\x81o\xbf\xf7\xe6\xebvo\xffw_\xf8K\xb59\x1c\x04\xda\x887\
+\xdc\xc0\x8a!\x86L\n\xc7J\xa5\xb9t\xce\x8d\x8f,~\xee\xf6g?\xbe\xe7M\x00\xf8\
+\x077\xbe\xf2\x8b\xcf\xdf\xf5\xf0\x91k\xb2e\x12\x8bC\x87\xa29\xd73\x88\xb8\
+\xe4Q2\x1c\xd0\xf0\x9e\x9b\xaf\xf9\xdcg?r\xfd5;\xde>z\xea\xd7\xbf\xf0\x97\
+\x8f>\xb5\x8fRm=A\x08j\xc0l\xdaKo\x1c\xbe\xf5\xc6\xabo\xbda\xe7\x9f>\xf2\xf5\
+\xcb6\xae\xfb\xfe\xef\xf9\xc0\xe2\xd2\xf2\x0b\xaf\x1e\xfa\xe4G\xee\xcf\xb4\
+\x85hWk\xb9\x91.!g\xa8AX5L\xff@\x83\x8b\xd2\x81\x82n\\\xd5\xfa\xf6\xed\x87\
+\xbf\xfb\xea\xb7\xda\xc8\xcb\xc6/\xfd\xfd\x1b_\x19\x1fY49\xe8\x89\x80\xcc\
+\x9a\xf7\xbb\x14d\x8dk\xc6\xc7>\xf3\x89\x0f\xddr\xc3\xae\xb1\xd1\xe6\xba\xab\
+\xb7}\xe6\x13\x1f\xdc\xb0n\xad,\xe8:\xde\xba_\x95\xcb\xa3\'\xce\xec{\xed\xd0\
+\xab\xfb\xdfy\xee\xa5\x03\x00\xf0\xec\xbe\xfdKKK\xdf\xf7\xb7\xbe\xed\xf0\xb1\
+\xd3\xfb^?l\xb1\xd4\xb4|\x81\x8a\x80\xec\xa4\x80\xf2gv\x1cn\xb3\x9a\xed}\xdb\
+\xa6\xee\xdcr\xb2\xbd\x9eY\x18\xfd\x93\x83\xd7\x9e\x9b\x1f\xc59\xb7\xae\x9d\
+\x1d\x1fY\x8a\xb7\x9b\xc6\xe7F\x07KsK\x9aK\xdbS|\x19e\x96\xd6\xfe\xeaC>;1\
+\x0ed\xc29\xd2\x0c&\xd6\x8e\xc5\xdb\xf1\xf1\xb1\xb5k\xc6\xce]\x98E\xf9S\xee\
+\xbcG\xd8\x86u\x13k\x06\x83\x01\x00\x1c>v\xfa\xf8\xa9s?\xf0\xb1\x0f\xfc\x9b\
+\xdf\xfe\xf2u\xbb\xb7\xeb\xb9\xbd4\x07b\x91\x18O\n\xa4z_yx\xff\xf6c\x9f\xbe\
+\xfe\xd5\xf6zni\xe4\xc9\xa9\x1d\xe7\xe6G\x01\x89\xe1\xc9\xa9\x1d\x9f\xba\xfe\
+\xb5mk/\xb6y\x9e8\xb6\xe3\xc2\xc2\x98B\xa8\xff\x00v\xa0,gE\t\x15p`\xad\x04XE\
+\xb0WO\xed\xe3\x85\x0b\x97~\xefK\x8f\xef\xd9y\xc5e\x1b\'/\xce\xce\xfd\xfe\
+\x9f?y\xfc\xf4\xb9\xb8Z\x12\n\x8bu\xc4\xb04\xcfbOO\xcf\x9c>{\xde/\xfb\x8b\
+\x97\xe6\xa7N\x9e}\xe3\xe0\xd1W\xde<rx\xea\xf4\xe6M\xeb\x19\x0f14\x8c\'\xce\
+\xb4\xde\x9c\xd4\x00VP\xed\xd6\xfa\xb0\xb0<\x88U\xc4\xb1r\xe0\xfc\xa6\x9f\
+\x7f\xe6\xde\xcf\xdc\xf4\xca\xe5kf\xbfqr\xeb/\xef\xbb\xd3\xc7\xf5yT\xf1p\x01\
+\xaf\xe0\xaa\xfeP\xa5>\xd0\xf7i\xaa\xd9z\xe4\xc9o\xbeyh\xea\xba\xab\xb7\x1d\
+\x9e:\xfd\xfa[G\x8b.\x93\x8b\xb8\x10n\xe9\x8b\xaf\xbe\xfd\xf2\x1bG\xbc\xf7o\
+\xbcu\xec\xe0\xe1\x13s\xf3\x0b/\xbczhyiy\xff\xdb\xc7\xbb<\x02\nD\x13\xe4\xd7\
+\x06\xd2\x82^\xcfU\x14\x96\x93\xb1}q1\xf1\xb0\xb0<rq\xb1IK\xce\xbe\xeb\xdc\
+\xa7O\\\xf9\xf4\x89+\x9b\xc1\xd2\xa2\x1f\xc1\x14S\x93z\x06\xbc\r\xc1\x16\xf0\
+\x19\x14\x8a8\xe0;\x90\xc3\x86\x83\xef\x9c8\xf4\xce\xc9\x96\x10\xaa\x1ex\x0c\
+\xaaY\x1dqKKK\x8bK\x8b\xe0`iiiii\xc9\x83_^\\\x06\x80\xf9\xf9ye\x83\n\x00"\
+\x08$=f\x17v\xac\x9b\xdd=y\xee\xaa\xc9\x995#K\x97\x96F\xce\xcf\x8f\xbdyn\xd3\
+\x1bg\xd7\xe7\x1b\xb6i\xcd\xd2-\x9bN\xc5RGf&_:{\xf9\xb9\xb9\x06\x006\x8c/\
+\xee\x9e<wqqt\xf3\xf8\xa5\x98\x7ft\xb0\xb4{\xf2\xdcD\xd3\xf9}\xef\\\x9c\xbc\
+\xb482:\xb2\xbck\xdd\xf96f\xa2Y8yi\xed\xd4\xa5\xc9\xf1\xc1\xe2\x95\x133\x13\
+\xcd\xc2\xc5\xc5\xd16\xf2\xf8\xec:\xcfw\xf9\xfc5\xeb\xa7c\x9e\x8b\x8b\xa3rz\
+\xb9~l\x9eqxpf\xc3\xb1\x8b\xebXW\xc8y\xe0X\xd3l\xbd|\xe3\x86\xf5\x13ss\x0bG\
+\x8e\x9fY\x98_P\xdao\xa3b\xc7\xd6\xcb6\xac\x9f\x98:qv\xfa\xfc\xc5\x90\x17/%y\
+\x1f\xd6\x88pX\xb3ft\xcb\xa6\xf5\xe3\xe3\xa3ss\x0bGO\x9cY\\\xf2bi\xd1\xf9\
+\xf6\xbf\xb0\xa8,7\xab\xa4a\xd1\xdc+\n\xae\xdb/?\xfd\xd1\xdd\x07\xde\xb3\xe5\
+\xc4\x8e\x89\x0b\xd8A\x9b\x9a\x9dx\xe2\xd8\x8e\xdfz\xf5\x96\xa3\x17\xd6J_\
+\xe1\xaa\xc9K\x1f\xbbf\xffw^\xf5\xf6\x965\xb3\x93\xa3]\x07\xcd,\x8c\x1e\xb90\
+\xf9\x95\xb7w\xff\xc1\x81\x1b>\xbc\xf3\xe0\x0f\xde\xf42\x00\xackR\xf7M\x8e.\
+\xfc\xdc\xbdO\xcd/\x0f\x00`v\xb1\xf9\xdf\x9f\xb9\xef\x9b\xa7\xb7\\>>\xfb\xb3\
+\xef}r\xd3x\xb7\xd0\xf1\xa5\x83{~\xfd\x95;6\x8d]\xfa\'w>\xbbs\xb2\x03\xc7\
+\x89\xd9\x89\x9fy\xfa\xfdS\xb3\x93]#=\x00\xc05\xeb\xa7\x7f\xfe\xdb\x1e[\x1b \
+\xf5\xa5\x83{~\xfd\xe5\x04\x82\xf1\x91\xc5\xef\xdb\xf3\xfaw\xed:x\xd5\xba\
+\x99\xc8\xe1\xdc\xd2\xc8\xd1\x8b\xeb\xfe\xf8\xadk\xbf\xf0\xc6\xdeeC\xc7l\xde\
+\xb4\xfe\xc3\x1f\xb8\xf3\xa1\x07n\xbf\xe2\xf2\x8dc\xa3\xcd\xfc\xc2\xe2\xf1S\
+\xd3\x0f?\xf6\xe2\x1f\x7f\xf5k;\xb6^\xf6\x13\x9f\xfd\xc8\xc4\xda\xb1\x87\x1f\
+{\xf1?\x7f\xf9\xf16\xff\xe8\xe8\xe8\x7f\xf3\xa9\x07\xef\xd8\xbb\xfb\xd5\xfd\
+\xef\xfc\xeb\xdf\xf9\xf3\xdbn\xd8\xf9\xc9\xbfs\xff\xde\xeb\xae\x1a\x1bm~\xeb\
+\xf7\xff\xea\xff\xfd\xf2\x13\xe0`l\xb4\xf9\x91O>x\xc7\xde\xdd\x87\x8f\x9d\
+\xfe\xd7\xff\xf1\xcf\x82W\x98\xd6]wn\xdf\xfc\x91\xef\xb8\xfb\xbe;\xae\xdf\
+\xb0~\x02W\xfa\x07\x7f\xf1\x94\xcee\x1b\xd0\xdapw\xab\x05\xd3\'p\xce9\xf0?r\
+\xf37?y\xddk\xb1\x8fp\xd8\xb6\xf6\xe2\xc7\xf7\xbcy\xfd\xc6\xe9\x9fy\xfa\xfd\
+\xef\xcc\xac\xc5D\xee\xdcr\xe6g\xeey\xfa\x9a\xf5\xe7X\x91\xc9\xd1\x85\x9b6\
+\x9d\xb9i\xd3\x99\xc9\xd1\x85\x99\x85\xd1\xe8\xeb\xe1p\x19R\x0cm\x98h\x16\
+\xafX;\x1b\xe3[\xcdqbn\xc3\xeb\xd3\x9b\xee\xd9:\x15\x99\xb9\x7f\xfb;\x7fx\
+\xe0\xc6\xb61mx\xdf\xb6\xa3\x91\x87\xb9\xa5\x91\xc7\x8f\xed\x884\xf7\xac?\
+\xfbO\xee|6\x16\x8fa|d\xe9\x9a\xf5\xe7~\xfc\xf6o\\=y\xfe_\xbcp\x8f\\\xa0\xbc\
+\xe7\xf6\xeb>\xf7\xd9\x8f\xdcr\xc3.\x1c\xb9s\xfb\xe5w\xdfz\xed\xdd\xb7\xeeyv\
+\xdf\x81v\xdd\xe6\xd5\xfd\xef\xc4\xd4\x81\x83\xfb\xee\xb8\xfe\x96\x1bvm\\\
+\xbf\xee\xe3GO}\xf6\xef~\xe8\x8a\xcb7\xb6IW\\\xbe\xa1\x15\xcc\xc8`\xd0\xe6\
+\xd9\xb3\xf3\x8a\xdf\xf8\xe2\xc3\xe7.\xcc\xe2\xb1\xf8\xa1\xfbn\xf9G\x9f\xf9\
+\xee\x9d\xdb/\x97\x95\xee\xbd\xee\xaaW\xde<"\xbb\x91l2\x19!\xe6i26ud\xb0\x0c\
+\x00\x11\x01S\xb3\x13\x87g\xd6\x03\xc0\xce\xc9\xf3Q~\xb7m>\xf9\xe9\xeb_\xfb\
+\xc5o\xdc\t\xc1\xa6\xdc\xb0iFE@\x0co\x9d\xdf\xf0\xf0\xe1\xab\xdf\xb7\xed\xa8\
+Y\xb1\x08\xf3\xcbI\x18\x97\x96\x1a\x00X\xf6\xcb\x0f\x1f\xde\xf5\xd1\xdd\xfb#\
+{\xf7n\x9d\xfa\xb3C\xd7\xc6\xa9\xa3\x03\xff\xc0\xf6T\xc57Ooy\xe9\xcc\x96\xf6\
+zrt\xee\xa7\xef\xfe\xdam\x9bO\xc6\xd4\xa9\xd9\x89\xd9\xc5\x06+\xad\x8f\xefy\
+\xf3\xd0\xcc\xfa\xcf\xbfq3f\xe3\xda\xab\xb7\xff\xaf\xff\xc3\xdfk\x85qqv\xee\
+\xd9}\x07\x0e\x1c>\xde\x8c\x0cv\xed\xd8r\xf7\xad{\x1ez\xe0\x8e{n\xbbv~aql\
+\xb4Y\\Z\xc6\x05/\xcd/\x00\xc0\xd6\xcd\x1b\xfe\xd1?\xf8\xdb\x13k\xc7\xcf\xcd\
+\xcc\x9e8}n|\xac9~\xaa\xeb%\xef\xbb<\x17f\xe7\x00\xf0l\xc0\xdf\xb1w\xf7O\xff\
+\xc3\xbf\xbbar-\x00\x9c\x99\x9e\xd9\xf7\xfa\xe1\xb7\x8f\x9e\x04\x80\x9b\xae\
+\xbd\xf2\xb6\x1b\xaf\xfe\xd8\x83\xef}\xdf{n\xe0\xfd\x15t\xa1\x93\x1e\xb4\x08\
+\x0e\x9c1\xdb\xf6\x00\x00\x0bK\x83\xdfx\xf9\xf6\x89f\xf1\xa3\xbb\xf7?|\xf8\
+\xea\xdf{\xf3\xc67\xa7\xd7\x03\xc0u\x1b\xcf\xff\xe8\xcd/~\xc7U\xdd\xe2\xc3\
+\xfb\xb6\x1d\xdd\xb8\xe6\xd6ss\x8d\xf70>\xb2\xfc\xa37\xbf\x88\x110\xb30\xfa\
+\xb5\xe3\xdb\xa6\xe7\xc7wM\xce\xdc\xb3ujjv\xe2\xa7\x9fz\xe0\xf5\xb3\x93[\xd7\
+l|\xf4\xc8\xce\xe9\xf9\xf1\x9b/;}\xd3\xa63m\xe6v\xbcN\xcf\x8f\xb7\xb7\'/)k&1\
+\xbc:\xbd\xf9k\xc7\xb7E6\xee\xbdb\xea\xca\x89\x99h\xf5\xafY?\x8d\xc5\xfcG\
+\x07\xae\x8d\x83\xe2\x87\xf7\xee\x8bI3\x0b\xa3\xff\xfe\x95[\x1f9\xb2\xeb\xc2\
+\xe2\xe8-\x9bN\xfd\xc3\xdb^\x88\xcc|\xea\xfa\xd7\x1e9\xb2\xab31\x00\xa3c\xa3\
+?\xf6\xfd\x1fn\x11\xf0\xe6\xa1\xa9_\xf8\xb5?|v\xdf~\x08\xe3\xe9\xc1\xfbo\xff\
+\xd1O\x7f\xf8\xba\xab\xb7\xa9\xbd\xdc^\xb4\x82\xfc\xe3\xaf~\xfdO\x1f\xf9\xfa\
+\x81\xb7\x8f\xaf]36{i\x1e,\x8f\xd2\x038\xd8\xb0n\xed\xe7>\xfb\x91\xb6\xe0\
+\xb3\xfb\xf6\xff\xea\x7f\xfa\x8b\x17^=\xd4\xa6\x8f\x8c\x0c\xbe\xf7\xc1{\xb1^\
+a\xb5\xea\xd7\xac\n\x00h\xcd\x01\xdf8Fa\xc9\xfb_\xdew\xe7\xc3\x87w\xbdxjs\
+\x8c|\xe3\xec\xfa\x9f\x7f\xee\xbe=\x1b\xce\xb5\xc2\xde\xb2f\xf6\x96M\xa7\x9e\
+8\xb6\r\x00\xee\xd9z2J\x05\x00\xde:\xbf\xe1_>\x7f\xd73\'\xb6\xcc-\x0e6\xad]\
+\xfa\xf0\xce\x83\xaf\x9d\xbd\xec\xf5\xb3\x93\x00\xf0\xc4\xb1mO\x1c\xdb\xe6\
+\x1c\xfc\xd8\xad\xfbb\xbf/,\x0f~\xe9\xc5\xbb\x8e^X\x0b\xd0\xc28\xe7o/\xfa\
+\x91G\x8e\xec\xba\x7f\xfb\xd1\xd6S\x99\x1c]xh\xe7\xa1h\xf5\x1f\xday(z0S\xb3\
+\x13O\x1e\xefl\xc1\x9e\xf5g\xbfk\xd7\xc1H\xe4\xdf\xee\xbb\xa33"\x00O\x9f\xb8\
+\xf2\xc4\xd7\'\xfe\xdd\x07\x1fn\xf5\x0111\x00w\xdct\xf5\x7fu\xdf-\x00p\xfc\
+\xd4\xf4\xcf\xfe\xd2\x17_y\xf30f\xef\xab\x8f\xbfxzz\xe6\x9f\xff\xd4\x0f\xb6\
+\x02\x03C\xb4\x0f?\xf6\xc2/\xfc\xea\x1f.,-\x01\xc0\xf4\x8cb\rY\xf8\xf0\x07\
+\xeelM\xcf\x9b\x87\xa6~\xee\x97~\xef\xf8\xe9\xf31iii\xf9\x0f\xbe\xf2\xd4\xe9\
+\xb3\xe7\xff\xd9?\xfet\xdc4\x02:\xe9\xb5\xc8\xa6c)\x00\x90|\x02\xba)\x84)\
+\xce/\xc1\xbe\xd3\x9b\xaf\xdbx\xfe=[\x8eoYsi\xa2Y\\3\xb2\x08\x00\xd1\xe1\x1a\
+\x1d,o\x18\x9bo\xaf\xe3\xda\x1f\x00\xcc-\x8d\xfc\xe6\xcb\xb7>~\xec\x8a\xf6\
+\xf6\xec\xec\xc8\x7f~\xe3\xdab\xb3\xe3\xd4 \x06<\x87\xec\xb8\xf2\xbeuw\x9e<\
+\xbe\xe3\xe8\xc5uQ\xf1<\xb0\xfd\x9d\xdf~\xed\x96\xb9\xa5f|d\xf1\xbe+\x92\xbd\
+\xff\xd2\xc1=3\x0b\x9dvy\xcf\x96\xe3\xd1\xbd\x98[\x1a\x01\x80\xfb\xb6&\xfb\
+\xbdal\xfe\xc2\xe2h4\n7n<\x1b\x93\xee\xbf\xfb\xa6\xb6\xaf\xbf\xfa\xf8\x8b\
+\xaf\xbc\xa9,\xc1>\xf7\xd2\x81\xbfzj\xdf\xc7\x1e|/\x004#\xcan\xe7\xc5\xd9\
+\xb9\xdf\xfb\xb3\xc7[\x04\xe0`\xad\xac4\x83\xc1\xde\xeb\xaej\xaf\xc3\n\x12\
+\x0f\x7f\xf5\xd4KO?\xffF\xeb\x88\xc4P8\xb6D\x0f-z\xf0Mfm\xa7\xdd\xbci]\xe88X\
+e\x18\x1fYZ?6\xef\x1c\x80\x87=\xeb\xa7c\xfc\xd9\xf9\xf18\xfe\x043i&$\xa5\xde\
+\xd6\r\xc5uI\x0f3\x0b\xe3\x8f\x1c\xd9\xf5C{\xbb\xed\x96k\xd6\x9f\xbbs\xf3\
+\xf1\xa7O\\y\xfd\x863\xd7\x04N\x98K\xb8eMr<\xc7G\x96~\xf2=\xcfdj\xd88\xd6MI\
+\x06#\x83\xe8\x97=\xff\xf2[V\xfe\xc7\x9fy\xa5\x05\x01\x80\xb2\x9dxzz\xe6\xcc\
+\xf4\x85\\\x8bh\x18\x1b\x1f\xdd\xb9}3\x00\xcc/,\xbeqPs\xa1\x1c\x80\x87\x97\
+\xdf<L@\xe0\x0c\'\xc0^y\x1e\x80\xbdO89:\xf7\xb3\xf7<\xf1\xe3\xb7\x7f#\x83\
+\x00\x1c\x06\x03\xd80\x96\xe6\x11g\xe7\xc6\t\x07\xab\xb7/\x83]\xde\x87\x0f_=\
+\xb30\xda^\x8f\x8f,\xb5\xc6\xe8\xfe\xedG\xe3h~\xfc\xd8\x8e\x833\x1bb~\x1dsF\
+\x88\xcdq\xe0\xd6\x8c\x8f\x01\xc0\xfc\xc2\xe2\xe1\xa9\xd3JV\x07\x0378==\x83"\
+\xf8\x91\xf0\xb9\xf9\xc5\xb9\xf9\x05\xfd\xa8\xb8\x16\x9a\xc1`\xcd\xd8(\x00\\\
+\x9a[83}\x01\xbc.\xdd7\x0f\x1e\xc3lt\x7f\xe5?t\x95r\xb6\x15\xa9\xedi+\xfb\
+\xfe\xeb_e\x06\xfe\xc9\xa9\x1d\x87f&\xcf\xcf\x8f\x01\xc0\x0f\xdf\xbc\x8fM\
+\x01\x96=\xb4\xcb\xfem\xd84>\xb7\xaeY8\x0b# B\xd5\x91=y8)\xa5\xa4\xd5\x9b\
+\xb7\xceo|\xf8\xf0\xd5\xedF3\x00\xbcg\xcb\x89mkg\xee\xbc<Y\xa5\xaf\x9d\xd8\
+\x16m\x01P\xe32\xb74\xf2\xd6\xf9\r3\xeaN\x04\xc0\xf8\xc8\xd2\xeb\xd3i]\xe1\
+\xd2\xdc<\x00\x8c\x8d6\x13k\xc6\x80\xa9\\\xd7q\xb5y\xe3d\xa1Q(d\x96\xd8Y\xfc\
+\x9a\xf1\xd1\xf1\xf1\xd1n\xc6\xe0\xc9P\xf6\x00\x9b7\xe1%;t\xae\x8f\xd4\x85\
+\x89sU\xd0d\x8eW<\xb0=\x19\xcbgNl\xfb\xdf\x9e\xb9/.\xa5\r\x1c||\xcf~9\x0f|\
+\xf1\xf4\x96\x88\x9bmk/~\xe7Uo\xff\xce\xab7\x00\xd6\xffq\xaf\xd2\xc0\xc1D\
+\xbbvT:\x8b\x12q\xe0\xc1=zdg\x04\xc1\x965\xb3?q\xfbsq\x11\xe9\xad\xf3\x1b\
+\x1e?v%.x\xe0\\\xd2\n\xe3#K\xff\xcfk{-\x9b\x05\x00\x0b\xcb\x1d\x82\x97\x96\
+\x97\x0e\x1f;\xd5^\xdf{\xc7\xf5\xed\xbc\x00\x87\xb6-w\xde|M\xe2\xb0\xeelj&\
+\xcc\xce\xcd\xbfs\xfcL\xbb\xc5|\xc7M\xbb_\x7f\xeb\xa8\x0ff2T\x01\x00\xfe\xee\
+[\xf7 6\xd0Pg]\x98\x1c\x82$\xffV\x1c\xb9\xd3ZW\xacM\xbb\x99\xcf\x9f\xda\x12\
+\'K\x00p\xef\xd6\xa37m\xa2Z\xd19\x00x\xe1\xd4\x96\xd6\xdbj\xc3\x0f\xed\xdd\
+\xf7\xf7\xae\xdf\xbfi\xcd\xd2\xc65\x8bWN\xce\xfe\xf4=\xcf\xfc\xed\xab\x93j\
+\x91\xe3`rt\xe1\xfe\xedG\xc7\x1a\x188\xd80\xbe86R\xb51\xf0\xd2\xd9\xcb\x9f9\
+\xb1-R\xf8\x8e\xab\x0e\xc7e\x8co\x9c\xdc\x8a\xd9\x06\x80\x17Oo\x99\x9a\x9d\
+\x88\xb7\x1f\xdf\xb3\x7f]\xb30\xb30\xde\xfe\x03\x80\xff\xfe\xd6\xe7\x7fx\xef\
+>\x00\x98Y\x18O\x1b\xd6\x1e\x1e\x7f\xb6\xdb\xea\xfc\xee\x0f\xdeu\xe5\x15\x9b\
+\xd1\xe1\xf4\xee\xf7\xc6k\xaf|\xf0\xfe\xdb\x8b\xdc\xe2\x93\x16\x99L\x000?\
+\xbf\xf8\xec\xbe\x03m\xc4G\xbf\xe3\xee\xb1\xb11<8Z\x04\xdc\xb9w\xf7}w\xa2u\
+\x02\xac\xf6\xb99 \xdc\xe2\xbar\xcb\xc6\xd3\xf3c\xd1\x91~`\xfb;\xcf\x9f\xdcr\
+pf\xc3D\xb3x\xdd\x86\xb3?|\xf3>u\x19\xf1\x953\x1b\xbf|\xe8\x9a8.\'G\x17~\xf2\
+=\xcf|\xf2\xba\xd7O]Z\xdb.1}\xf0\xca#\xe7\xe6\xc7\xe2\x94\xc1\x83g\xce\xff\
+\x7f}\xc3+{\xd6O\xb7\xeb\n\xbf\xb2\xef\xf6\x17O_\xce\xeb\x88l\x06e0\xb30\xfe\
+\xd8\xb1\x1dr\xf9oni\xe4\xd1#;Y\xe4\xd4\xec\xe4\x17\xdf\xb8\xf1\xc7o\xffF{{\
+\xcf\xd6\xa9_x\xdf\xdf|\xe5\xed\xdd\'f\xd7n];\xfb\x9dW\x1dn\x97\x10n\xdb|\
+\xea\xd7_\xba\xf5k\'vD\xff\xe3\xe57\x8e<\xfc\xd8\x0b\x0f=p\xc7\xce\xed\x97\
+\xff\xdcO|\xf2W\xff\xd3_\xec{\xed\xed\xf9\xc5E\x00\x18\x1f\x1b\xbd\xe5\xfa\
+\x9d\x9f\xfb\xecG\xf4)\xbb\xc5\x7f\xe64\x03x\xe7\x1d\x00\xfc\xcd\xd7_\xfe\
+\x81\x8f}`\xe7\xf6\xcbo\xb9a\xd7\xff\xf4#\xdf\xf3\x1b_|x\xea\xd4t,v\xe7\xde\
+\xdd\xff\xf3\x8f}"NJ\x01\x80n\xcfevt=\xfe\xcf~\xee\xc0\xc1\x93S;\xa2\xc2\xbf\
+i\xd3\x99\xff\xe3\xdb\x1e?ra\xb2]X5\x8a\xb8\x05\xdf\xfc\xd6\xab\xb7\\\xbf\
+\x91,\xd4\\\xb3\xfe\\,r\xd9\xf8\xa5\x9f\xbb\xf7\xa9_|\xfe\xae??\xd4\x89\xe7\
+\xf9\x93[0\x8d\xe8\xdc\x01\x00\xc0\xedy\xbb`\x9d9h\xc3\xeb\xd3\x97={JY\xbd\
+\xf9\x83\x037\xdc\xbe9\xadg\xb4+\xd9,\xcfm\x9bO~|\xcf\x9b\xcf\x9e\xda\xb6\
+\x18,\xc2\xc5\xd9K\xbf\xf6\xf9\xbf\xdc{\xddU\xedz\xed?\xff\xa9\x1f|\xfa\xf9\
+\xd7\x8f\x9e8\x0b\x00{v^q\xf7\xad{&\xd6\x8e\x1f>v\xaa\xddP\xc8\xf0l5\x84\xc7\
+\x80\x07\x80\xa3\'\xce\xfc\x9b\xdf\xfer\xbb\x12\xf0\xb1\x07\xdf{\xeb\r\xbb\
+\xfe\xfak/\xb5\xeb\x8c{\xaf\xbb\xea\xdb\xdf\xbb\xb7=\x820\xb1\xb6sz\xc48\x8f\
+\xba\xdf\x9a3\x00\x10\xc7P\xe4\xf9\xc2\x1b7\xde\xb6\xf9T\x14g\xbb\xf2\xdf^?z\
+d\xe7\x95\xeb.\xc4\xdb5#K\xd1\xcd9zq\xe2g\x9e~\xffO\xdc\xfe\x1cv*q\xb8l\xfc\
+\xd2\x9e\r\tF\xcf\x9c\xd8\xf2G\x07\xae\x8b\xca#\x86hV&\x9a\xc5\xb1AZ\x85]3\
+\xb2\xe8`\xd9\x07C\xd6\xe2\xe0\xc0\xf9MO\x1c\xdb\xc1\x88<rdg\x14!\xa1\xbc\
+\xdc\xfc\xfcs\xf7M\xcd\xae\x8b\x87Ydx\xf4\xc8\xce_z\xf1.V|\xff\xdb\xc7\xfe\
+\x97\xff\xfbw\xff\xc7\x1f\xfa\xe8\xdd\xb7^\xbbar\xedC\x0f\xdc\x81S\x1f~\xec\
+\x85\x87\x1f{\xe1\x9f\xfd\xe3O3R\xceA\xeb\xe4\x8f\x8fQp \xe9D?W\x02\xe8\xd1\
+\'\xf7\xfd\xab\xff\xf0\xa5\xff\xf6\x07\xfe\xd6\x86\xc9\xb5\xd7]\xbd\x8d-J>\
+\xbbo\xff3\xdf\xdc\xff\xa3\x9fzHm\x85\xa33\xa9T)\xd5C#;n\xba\x1f\xa7\xa5\xd2\
+\x00\x17\x16\xc7\x9e\x9e\xda\x06\xe0\xb6\xae\x9d\x1d\x1d,/\xf9\xc1\xa5\xa5\
+\xe6\x9d\x8b\x93\xbf\xfd\xda\xcd\xbf\xf9\xcam;&.\x8e\x0e\x96\x8f]\x9c<>;\xf1\
+\xf4\xf1m\xfb\xcfm\x8a~\xd0\xcc\xc2\xe8\x7f9\xb6\xeb\xc0\xb9\r\x0e`bt\x11\
+\x00<\xb8KK\xcd\x99\xf95_;\xbe\xed_\xbdp\xd7\x9f\x1d\xbcz\xc9w\x99\x97\xfd\
+\xe0\xa9\xe3\xdbf\x16\xc7\xb7M\\l\x06\xcb\x1e\\[\xd1\xf1\xd9\x89?\x7f\xfb\
+\x9a3s\xe3k\x9a\xe5k\xd6\x9f\x9b_\x1e9vq\xf2\xcc\xdc\xf8\xb3\'\xafx\xf1\xf4V\
+\xda\xcb\x0e\x00\xce\xce\x8d]\xb5\xee\xc2\xf9\x85\xb1\x96\xa5\xb7g\xd6\xff\
+\xc7Wo\xb9\xb0(<\x7f\x07\x000\xbf\xdc<}\xe2\xca\x17Nn^3\xb2\x149\\\xf2\x83\
+\xf3\x0b\xe3/\x9d\xb9\xfcW\xf6\xdd\xf1\x1f^\xbb\xed\xfc\xc2\x1aY\xf6\xe4\xe9\
+s\x8f<\xbe\xef\xc4\xe9s\xde\xc3`\xe0\x00\xdc\xf4\xf9\x8b\xaf\x1ex\xe7w\xfe\
+\xe8\xbf\xfc\xea\xe7\x1f^\xbfn\xcd\xc7?|\x1f\x00|\xf3\xb5CO~\xe3\xb5\xb6C\
+\x06\x83\xc1\xcd\xd7\xefl\x9a\x917\xde:\xfa\xd4\xf3\xaf\xcf\\\xbc$;\xdc\r\
+\x06w\xec\xdd\xdd4#\xaf\xee?\xf2WO\xed\xbb4\xb7\x10Y\xf5\xe0_~\xe3\xf0\x0b\
+\xaf\x1e\xf4\x00\x97m\\\xe7\x9c\xf3\x00s\xf3\x0bS\xa7\xa6\xbf\xf8\xa5\xc7\
+\xff\xc5o\xfe\xc9\xa5\xb9\xf9[n\xd8uf\xfa\xc2c_\x7f\xf9\xd0;\xa7\xb8\x13 \
+\x10\x90\x9eU\tW\xee\xee\x8f\xfdS\x0b\x04\xb1\xe4\xb6\xb53\xbb\'\xcf\x01\xc0\
+\x85\xc5\xd1\x833\x1bZ\x07\n\x9f\xf6\\\xf2\xae\xddp\x93Fh\xc7\xc4\xc5-kf\xd7\
+5\x0b\x17\x16GO^Z\xcb\xf6\x1bqX7\x06\xd7o8\xd3\xee,\xb7\x15\xb5\'\x0f\x00`t\
+\xb0\x14\xd7\xe0\x96\xbc\x9b_r\xea\xa9\xf0\xc8\xd2\xe8`iayD9\x87\xa8u\xcd\
+\xb653-\x87\x00p\xe2\xd2\xc4[\x17.\xf3\xd6.r\xbb\xe3\xef\x9c\x03\xe7\x06n\
+\xe3\xe4\xc4\xa6\r\xeb\xe6\xe6\x17N\x9d9\xbf\xb0\xb8\x04\x00\xdf\xf9\xfe\xdb\
+\xfe\xaf\x9f\xfc\xfb\x00\xf0[\xbf\xff\xe8\xbf\xfd\xdd\xafD&\xc7F\x9b\x91f\
+\xb0\xbc\xb4|i~A\xbe\xec\xa1\xa5<6\xda\x8c\x8e6\x8b\x0bKs1O\x9a\xd2wW\xdb\
+\xb7n\xda\xbeu\xd3\xe6\x8d\x93\xa7\xa7g\x0e\xbc}\xfc\xdc\x85K\x0e`\xa4\x19\
+\x8c\x8d6\xcd`07\xbfH\x96#y=Q\xfa\xc4!\x00\xf3\t$z\xa0ejv\x92\xf9\xd8\x00\
+\x10\xbb\x98<\x1b$\x9c\x91\xa3\x17\xd6v{\x01\xa5Z.\xcc\xc3\xf3\'/\xa3\x0f\
+\xa1v\x17\x0b\xcb#\x0b\xcb)\xd6z.`n\xa9{\xd2~n\xd9\xd8\x1d\xd5\x0e\xeaL]\x9a\
+\x9c\xba4I2I\x03\x8a\x96\xff\xe2\t\xc4\xb3\xe7/\x9c=\x7f\x013\x1c\x0fs\xb6\
+\xdb\xbbqSfn~\x01\xe6\x01\x0c\xf4\xc7<s\xf1d\nW\xe1\xddl\xf3\xd8\x89\xb3\xc7\
+N\x9c\x85x\xd8\xd8\x01\x80[ZZ\x9e]\x9aG1V\xb3\xcd0\xc8\xe51\x96\xa8V\x18<\
+\x83\xa8\xe7\xa9#\x03\x88K\xef\xe1\xbd\x17<(\x8f|\xd4/J\xd64J\x9bV]\xb5\xed\
+\xf2O\xfe\x9d\x07&\xd6\x8e\xab\x15m\xdf\xba\xe9\xbb?x\x17\x00\x1c?5\xfd\xe6!\
+2U\xa9\x9a\x16\x16X\xd6\x8b{\x90#\x9e\xa6VT\x9a;OP\x19r\x0f\xf5\x99\x8f*\xda\
+\xb7\x0e\x96:m\xec\x94\xd2rkq\xf5V\xa3\x95\x80\x88ON\xac\xfd\x91O=\xf4\xb1\
+\x07\xdf{\xf7\xad{~\xed\xf3\x7fy\xe8\xe8\xc9\xe5\xe5e\x00\xf0\xe0\x07np\xf5\
+\x95[~\xea\xbf\xfbD\xbb\xbf\xf0\xd5\xc7_<\xf4\xce\xc9\xd4(mj\x9e\xad\xb3b\
+\xdf$\xd2q\x1e\xc0\xd1\x0b\\\x05ZW\xb2;\xaa\xf7d\xa66\x0c\xfd6\x1a\x0f\xc1e\
+\xf4A\xed\x87\xa4x\xa4\xb3\xf4\x04x\x97\xb4\x1aj,\xd6\xf5\xfe\xbbnl7\x87\xda\
+\xc3#\x7f\xfd\xf5W^y\xf3\xc8\xe9\xb3\xe77oZ\xbfk\xc7\xe5\x0f\xde\x7f{\xbbH\
+\xf0\xd2\xebo\x7f\xfeO\xfef\xd9\xa7\xe9\x8c\xf3\x1a\x97tuW\xae-b((\x8f\xf8\
+\xa1\xe5\xc8\xb8\x9c\x1c\x8e\xcac\xc2\xa6\xc6\xed\xea\xf0\xce\x83ww\x7f\xcf?\
+%l\xf5\xef8\xe5\x056\xd9\x17\x18ps\x80\xd2\x13\x1f\xdd\x1f\x0e\x02\x87\xdf\
+\x0f\x85\xec4f\x83\xd3SCV\x7f(\x0f\x99\x83\xdb\xb4a\xdd\'?r\xff\xf7>tofE\xe8\
+\xd9}\xfb\xff\xcf_\xf9\xc3\xb7\x8e\x1c\xb72\xe8\xc7\xfd,\xaf<\xbb\x19HI\xc5W\
+:D\x18\xe0\xbef$\x89\x7f\x08i\x8a\x08f\xbf\xb4\x9d+\xfb\xa5\xf2\xcdX`A"\'\
+\x06\xa7\x80\x80\xf9\x83\xd1Q\x1f\xe2\x1d\x00F\t\x17\x02\xe6\xa3\xdb\xf1spi~\
+\xe1\x99o\xee\x7f\xe6\x9b\xfb\xcf\xcd\\\\\xf6~dd0:\xda\x8c\x8c\x0c\xe6\x17\
+\x16O\x9d=\xff\xdcKo}\xe1O\x1f\xfb\xe5\xdf\xfd\xca\xf1S\xd3\x00\xfa\xb0Kms"I\
+\xae\xef\x82\x16\xc9-ar\x0f\x1dK\xe5C\xcd\xb1\x04\x1c\x1a\x9e\x07\xd3\xa9{[\
+\x00\xa7\xdd\xfa\x07^Kb\x81hlO0k\xd80\x86\x00\x96T\xc6\xa5\r\x18&{V$*\xe7\
+\xd7\xf6\xbf\xf3\xda\xfew6\xae\x9f\xd8\xb4a\xdde\x1b\xbb\xed\xb43\xd3\x17\
+\x8e\x9f\x9aN\x0f~[N\x9aS\xaek\x1f\xd7\xa9\x81:1\x04\x19\xb7\xab\xbdO1\x15/\
+\xa9\x08\xb7\xf2\xbdQEOp\x15\x9fV\xab\t\x1c\x07\xcc-\xa8@\x80\x9ch\xe1\x18\
+\x17\x0c\xf1\xf4\xf9\x8b\xd3\xe7/f\xd4~-\xc3\xe0\x80b\x0e?\xdd%#Y`\x9eA\xfa\
+\xb5ZmH\xa3\x87cX\x1eg\xf4Y%\xdb\xf6\xbf\xcb.=\xab\xa8\xa8\x1d\xd8+\x92\xd0\
+\xd0\x8f\xb7\xcau\xbeR\xa8\xa8\xd7 Wq\xd2"e\xed\xb6\x9a2\x8f\xac\xf3\xf9B{I\
+\xdc\x06m5\xbb\xa5_\xf3J\x8e\x1c\x9f}\x0b\xba\xf0\xd8>\x19\x8b+|\x83\x11&\
+\x85j\xe2Mc\xfd\xce\xf4\x01?\x0cQ\xd9\xb2\x0c\xfe\xccG\xcby|M7\xc6-\xc7L\x0e\
+\x85ZZ=\xae{]\x8dU7\xbd/\x92\xc8L\x9c\xf3\xf1\x069\xed)\xc1\x15b\x97\xb9`\
+\x12\x1c\xdd"`e\x7f\xe1!\xd8\x1f\xcdz-^\\;\x92\xb3\xea$\x8b\xc7\x180\xcc\x81\
+\xda\x89x\xce\x9a\xdb\x0b\x97\x13\x1e\x0f\x1ce\xd2M\xe9\xb2jS \x10\x93\xc3R\
+\xa8\xc9\xa9\xe4Q}\xef\xca\x1ai\xb7\x90[\xe9aT\x84\x1e\xaa\x94\x0e.\xea(\xe8\
+\xddNv\x11\xcc\xa7\x92\xfb27\xf4\xc8\xc3\xac\x89P\x14\xe50\xaf\x92\xc8\x86\
+\xec\n\xbc\xac\x1e\xf2\x99\x87~P?W\xcaQY\xe2\x14\xdf1\x9f?\xae\xd2\xfe\xe0\
+\xe2\t\x04\xd6\xaco\x85\x07\xe5"\x87:KF\xaeB\xa5h\x8a%q`\x8e\x03\x933*x\xcd\
+\x10\xe8<\x90;\xbd\xae\xbe8`f\x1b\xb3\x84\x89\x1a\x86\xc2~1\x16]\x1dJ\'=\xbd\
+w\xce5@\xc5/9\xe6\x07&\xb3o(\xc4\xef0\x10\xa4x\x9bTn\xd3\xdb\x02\x92Tz\xd8\
+\x02\xb2\ti\xcf\xb2\xa2\xba^%\x88\x9b\xdc\x80\x17u\x05\xa5-\xf9IA\xed96OA\
+\x8f\x9dc\xe7\xa0\xc3\x01\'@Q@\x85\xde(\x08`*A\xbe\xa2\x98\xbd\xafv\xd8\xc0M\
+\xb0\x9e\xa9\xea\x88~ \xd1\x0f(\xfd\x94?*\xdf\xe1WE\xb1\x9a\xb9T\x97\xb1C\
+\xd8\xdb\xdd\xcb\xd9\x82\x80\x00\xb9\x02\x94\x1eM\xef\xaa\xc9\xbf\xa4vE\xb6_\
+\x1b\xf6\xc6\x1d\xdf(6\xde\x158\\\xb0\x14\x00\xda\xa6\x17\xd5y\x9e\xd3\x83\
+\xf9\xd4wol10yE\x90\xea<P\x99\xb2\x96\x97r\x94\x1c\xc8\'\xc8\x10Pe\x9f}Ye\
+\xc1\x10\xaeD\x8a\xf9\xae7\x0b\x15&5\\=\xb0\x8a4\x1c\xe0[\xbe\xd2\xd7\xb7\
+\x81r%\xa7\\\xa2\xdf\x80\xcc\xe4\xa7\xcf"\xaa\xd6=\xd3\x9e\xe1\x14\xc3\xd03\
+\xb1\xea\x8c\xea\xe2\xabR\x97t\xb8\xf8u\x8c\xa2o\x7f\xc1\x0b\x00\x92r\x9d\
+\x1fg\xf1\x1c\xefSY\xcd\x8d\xa8\xa5\x83\x93\x8c\xc7\x8e\x1bU\x90^\x00C{\x19}\
+\xae\x14\tb\xcd\xa40j\xa9K\x98\xc9\xa9\x86!feF-\x8eC d\x96\x0b\x00\xa6\'\x9f\
+\x89$\xa5MC^\x0e\x95 \xb3G\xac\xf6nc\xa1\x9a\x9c8\xe4#\xd1\xc0\xbdI9;\xa0\
+\xd3\xbf\xe2;\xd0S\xb9\x92\x07J6\xb7h\xa5\xe6,\xb7\xe4\x9f\x84\xe24\x05yyV\
+\xebz\x87\x1a1\xb3<\x8e\xc6\xdbU\x9b\x82\xc7\x90\xd5\x9f@2\xba\x92\xb2\x11:=\
+\xb7\xaa\xa1\xbc\xe5\xb0\xbb\xc8,\xb08yU\x15\x8a+$\xfd\x95J\x98M\x19^\xa4Zc\
+\xa6\x96U\xdbS\x95\xb3\x12a\x9e\x8c\xafO\xa1\xcc\xf1B\xbe\xbd\xcc\xa3\x05\
+\x05^\x07\x8a\xf4\xfci\xc7r\x17\xe7\x11`\xbc.ZY\x1e(\xee\x0e\xa8\xe2\xc9\xb0\
+\xa7\xbaxq\xf5\xad\x8bc\xde\xa2\xd0\x07C\xae7\xa0e\x15\xd0PR\x98%\x1a\x9dV\
+\xdet\xa0\x05\x9b8\x01\xcc\xed\xffzq%&Y\xb2;\x92)\xc1-\xa9\xec\xab\xd2\xc2>\
+\xcd+\x1c{^T\xc4\x07\xb1\xa6$\xd5\xa8\xa7\xa1\xa3cN\xf74\xbd~[\\=d\x8b?R\x1f\
+\x13n{MA\xb3\xa1]1\xa4U\x06-(\xbb\x05U\xed\xe3yP\x89\x86\xdaa\xa1:\x14\xaaD\
+\xd1e\xee\xbcSO\xfd\xac\'\x85%\x17\xb3\xc7\xf3\xfe/\x08\xf1\x18\xcaC\xab\xb9\
+ .\xce\xd5\x10D\xb4\x167\xfa\xd0\xa7\xb6\xde\xf6.\\J\xc6r2\\\x01\x9b3\xa1\
+\xf3;\xe7q\xa89\xb7\xacZ\x983\x1c2*79\x13\x96kfT+l4t\xe7><\xc5\x01r\xec+\x0f\
++\xe4\xfd\x1buM\xb0\xc8v\x13K(\x1d%T\x81\xe2\x1bdh\xe7\x83a\x18\x90\xebPE\
+\xd6\xda=\xc2Qj5\xc5\x90\x96\xd9\xf5~\xe7\x92vb8P\xf5\xa1M2c&Oi\th\x16]\xf5\
+\x94$\x11Pjl\xe3\xa9<\x19+z5\xf4Z:O}\x83\x94\xdcp\x1b\xc4\x16\x02\xc8\xcc\
+\x9e\x9a\xb4z\xe2l\x07\x88T\x19\x84\xef\xc4\xb0\x81\x90\xdaA\x00M2-\x06Tg\
+\xb3r7r\xb8\xd34\rs\x05".Qw%VT%\xe1!\xedc\x13\\w\xb6\x02\xbdF\\\x9b\xfe\xad\
+\x16\x02(\t|\xe9\x80\xe1\xc0\x0e\xba+\xe0\xb5\xb6$R\xa8\x05\x08\x05iL\x11C\
+\x80\xdc\xcc\x98\xb7\xbf\xe0\x98\xb0\x87~\x1f\x7fK\x87?\x86\xe6Q\x0f@l\xbe\
+\xce\xa5\x8f\x05\xd8\x9f\x94\x8e\xfa\xaer\x01\xa0/\x02\xf0c\x11\xcc\'\x95k\
+\xc6\xb5C?x\xca\xfa\xd9=\xcf@\x86S\x1c\x89qX\xf6\xf1O\xba\xe9^G.\xf2\xabL\r}\
+D%\xffE/\x00\x18\xf8\xf0\xa4\xa3\x8fk\x04\xf1_l\x1a\xe0\xeb\xee_\xb8\xd2\xfe\
+\xc4\xf2\x92g\xe4\xfc\xb7\xffVe\tE\xaaA\xb2\x0f\x14\xfe\xd5L[<zx4^p\x9a\xd0%\
+\x8bJ\x91\xa2p\xe1"V\x8e\xe2\x19<+\x83\xe50y\xef\xadg\x84j\x06\xd5 \x88\x8e8\
+A\x816\xfe\xe3c\xc3\xbd\x82\x0b\x1f\x8b\xd6\x88\xd4\xda\x82\x03)\xce\x15Z\
+\x06\xc5I\xa8}\x8d`bI\xda\xee\x04\x81\n:N\xb9\xc2\xfc\xf0\xcc\xd6?\xc4? \xe9\
+\xb2\xe7A\x1c\re\xf6\xd2\xdb\xcb\x90`\xf1P\xc0\x89)\x8bWD\x1d4(N\xaf\x02Eq\
+\x93\xb72\xe8_p[\x05-#h\xa2@\xe4Kf\x05\xae\x02\x1f\xd8\x99\xa8\xd0R\x14\n\
+\x10\xe4M8\x0c*!\x1f|\x08\xed\xed@\xaf:\xaa\x07\xd5\x14\xc4[Q\x08\xc7\xf7\
+\x15BR\xc2+8\xba\xa2\xac\xb8\xad\x16\x0e0\xcd\xce\xd7\x14yT\xd1\x0b-\xc2\x06\
+\x11>\xca\xc0\xfe)l$\x83b\xea3&\xe3b\x08\x9a\x00sO\x0c\x82\xe7\xb2\xa7\xed\
+\xd1\xa3\xaa\xfb\x9d\xa9\x1c+S/A2\x1c\x10(;\xd2\x895\xd4l7-VbpA\x13\xc5| \\\
+\x13\xfdQ[;\xf3r,\xb5\xaf?:@N\x8dx\xef\xfd@\xb3v\x8cq\xaef\xb9\xdf\xa0z\x11\
+\xd9 -N|%\tW\x06C\x8dc\xa6\x03\x14\xa3\x0e\x00}q\xa0C\x07\xf1i\x8f\x13\xa1\
+\x9d\xec*\x84\x1f\xc0\xb4\x02W\x00\x08\x07E\'\xc0R\x0f\xd4\'`\n=\xc4K\xb7Q\
+\x81\x82r\xa93\x92\x8b\xf4\x1e\xfc\xf0FAi\xa4\xc0\xb4\x1a,\xd5J"\xa9\x10\xbd\
+\x88\x11\xdc(\x08\xd0\xf9\xa0\xf0"\xcc\x084X\xc8H\xc4\x0c4\xf4:T\xe2\x05\xa3\
+\xddc\x8f\x16\tR.8\xaa\xca6ckL\x1d(\x87\xd6%\xbf\xccQZ\xe1\x1c\x811\x19X\\\
+\x11\x1d\xf4z\x18o3\xc8T\xa3&\xff\xe8d\x90w\x9ap\x7f\xd9)\x85\xdal\xe1\xd3g\
+\xdc\xbf\x96O\x91[\xa1\xe1\xf26\x1bb\xeaN)p\xd9)i[\xd9\x0b\x1cd\x8e\xaa\xfa\
+\xec4\x97\x95c\x98\xcbnf\xae\x04\x01>\x9c\xed\xf7N\xab\x02\r\x06f\x93\x8c\
+\xd0\xcd%<\x00y\'u\x87\r1\xa0\x11h\x88\xaf\x03\x00h\xa1I\x99\xc8\xd8\xe70\
+\x1aJ\'*9\xc1\xa6:\xb8\x91uP\x8ceH\x1b5\x99\x00\x00 \x00IDAT\xec\xa4\xd0/tQ6\
++\x06\xa6-\xbch\xb6R$0\xba\xda\xcf\xa6uA#\xc9\xe1\xee\xc5E\x96\x90\xe3\xb1\
+\xe8\x11s\x9c\xe6q6{F\xcd4D\xb8!\xef\xdf\xd0\xa0\xd0\x10\xa4:\x9d\xfb\xf4\
+\xf5w\xb6\x8cJ\xc4d\x8d<O\xc6\x85\xf3`\xcb\x88=\xd5B\x94\xc1pC\x17\x91\xca,\
+\xc7\x82\xecVOQ\xab\xaa\x99\xd6(\xf8\xa4\xe0d\x83$\xe7<\x1b\x15\xb6\x13\xb1D\
+CH\xe5\xc7\x0c\x01\x9a\xe2e\xe0\xc2\xde\xe6A\xcd\x81\xde\xd1\xb83<\x93\xbc\
+\xd6,\x12\x82\xd1\x8c\\\xb7\xa2m\x1dZ\x80\x8cF\xf0\xe9c\xb9`\xcf\x820\x9b\
+\xdd/{\xe9\x84\x9e\xd7^\xae\xee\x85\xb6V\xdfu\xaf\x890a\xc0y\xb4\x80 \x1d\
+\xafx\xd2\x9dZ\x01\xbe\x1f\xe6E\x8b\xa8\x81\x88\xe68]#\x1c\xc4\'\x90\xf4\x90\
+\x04Op@\xb3x\x02@^\x06\xb0\xfa\xe8,D\x1c>\x80\xa0\xaa\xd9\xd7\x0e\x07\x0e\
+\xf4\xf7\x11\xa9\xc8 \xe6 \xe3\xb3\x15\x03k7O$\xfa\xd1\t\x17\xa1\xe4\x03\x90\
+,\x9a\x02\x082\xf6\\\x17Y\x9e` \x81\xdc\x02\xe1+\x00\x1a\x00\xb1\x97\x82O@\
+\x9c\x81T\xa9&S\xa1\x1a\x81\x8e<\xa6\xfe\x13\xe1\x16\x10>\xee\xa0\xf1\xde\
+\x89\x14\xb1Q\x88\xfa@4\xb2WP6\xacC\'Zs]\xca\x93\x02&\x84\x8f\x88\xd6\x12\
+\xe8\x92\x06L\xba_Q\x0fq\xef\x11\x84z\x08\x1e\xb6d\xd5\xdc5\x15V\x9e\x01\xa8\
+\xd1|\xc1$>\x00\xaa\x00\xb8L\x8d\x16s\x0f9R\n\xf8t\xda\x93ul$E(\xd0\x87\x1c\
+\xb0f\xab\x0c\xc3\xe1&q"\x1f\xf9N@O\xceA\x8b\x03E\xa5\x9b\\\xb1\x1b\x14A\xf7\
+\x9d\x01;\x17.<\x03E\x7fT\xf8r\xabA[\x15S\x9b,\xf8\xdb_,/\x9f\x08R\xb5\xa0N\
+\x1e\xba\x0civ\x90(z\'\xb0DW#:\xb4b\x8f\x0b\xafw:hmDa\xf3\xb4\xbf\xf4U\x90%a\
+g\x8bv8\xc0\xf5\x1a\xe2\x11\x1c*\x8f\xdc\xfa\x84yd\x16\x84\xfdO8\x08\xbcj\
+\x95\x08C\x83\xe2\x1bbj\xf5\xc1\xcd4\x0fz!\x06@\xfbPj\x9c9+u\xcbYSZ|\x8a\xe9\
+\\\xed\x00\x12\x86\xeey\xcb\x05%\xa9\x89m\x04\x98\x90\x15\xf9\x18?\x90\xc7\
+\x87\x0b\xfa+\x15D\x12\x93\xfc8\xf4?\xb9\xec4\x81\xee\x02\x96\xc0HkP\xec v\
+\x17@\xcc\x0e:\x00R\xf3\x84\x18n#:\xa3\x0e-\xa3\x98\x1c\x0b\xfa3*\xc8\xb08$}\
+\xc0~G\xc8]\x9a\x81\x95\x16\x94\x10\xc7\xf5\x91y\xf3\x91[\x86\xf2Qy\xb7\x19\
+\x9c\xa3\xf6=\x8d\xf8\xae\xbd\xcc#\xa4\xeeBZ\x0c\x8c:4\xaa\x04O\x95=U\x1a6\
+\xeb\xa16\xe2.\x0c<@\xfc\xd7\xfdz\xf0\xd0\xf9K\x1eR2\xca\x06\xc8\xa3\no8W\
+\x1c\x0bT&\x11\t[G\xe8\x16@\xfc\x89\xec\x84\xb6e\xd6\x96\xd8n\x13\xdf|\x92\
+\xac\xc9\xaeA\xbdS\xeb@di\x1a\x89\x18\x01a#\xc0E\x05\xe0\xc2/\x82\x86\x8b\
+\x11)\xc1\x01\xca\xe3\x80\xe4\xce\x07#3\xd9@J}\xef\xc9=\xce\x13\xc5\xd8F\xc5\
+n\xf78\x07\xb1\x01\x1c\x05D\xfc\x12\x00\x95\xca\xaer\xf4G\x18\x19m)TV\x81\
+\x87\x92\xda\xe8r9\x8e\x00l\x03\\\xf8_\x12M\x82g\xba\x83\xf0W\x8f\x03P\x1a\
+\xd5\x04Nc\x97\xc5\x13\xb1\xe1\xde \xd5\xf9d\xdd\xb7\xab=uN4\x1b\xc1\x96\x19\
+\x88\xd5K~\x90\x13\x9e8\xe45\xb0\xce\x9bwhb\xd9\xf2\x08\xd1\x86!\x9a\xba\xa5\
+W\xab\x92>G]p\xf4\xca\xa9\x92\xcaSV\xdc\x00\xdc\xe7\xcce\xe8L\x83\xe5\x0c\
+\xca \xdfO\xc0\x07\xa3z\xfc\xb5\x15g\xf0\xcd<\x92],\xc6P@\xcd}p\xa3\x82\xdc\
+\x83\x97A\xf8\xe7\xbd\x9e<\x83\xfc\xaeR`\x8e\x96U\xec\xa5\xde5\x8a\x13j\xd6f\
+\x05\'.\xa8\x02\xc8\x0cgd\x02Y\xa6(p4=\xe4\x13\x83\x04\tVE\xee@o\x03H\x11$&"\
+O\x8a\xd7\x8fG*\x12?\x9f\xf9{\xde\xe1\t$Q\x19\xe0u#\xd7M\x1c\t\xaa\x80\xd44\
+\xdcH\xc4L\x14\x8f\xeb\xe7k\xb0\x94\x01\x8ft<:"\xc0\xcc\x84a\xa3J\xcb\xa5s\
+\xeaAC{\xb5\x80\xd4\xc1\xa9\xbc\x88\xf5\x00\x00\x83\xe0tI\x0b\xd9\xd9\xff\
+\xf6\xa0\x06ZWc\x96;)\xdd\xe0\xf3u\xff\x92k\x89\x08zTY\xf2\x0e\x02\x1d/o\xb5\
+\xf6\x90\x05\x83\xcaq\xaay\x00U\x07\xf14E\t`\xc1%\xd8\xfe8\xdc\x1d\xcd\xe9\
+\xe8\xff\xc8%$\xd6\x88k\n\x97.\xc2\x7f@\xfd\xc6\xbe\x01\xf7\xdb \xf9x\xf6\
+\xbf\x04\x08J!\xdez\x0e\x80(\xc8\xe4\x93\xf9\xa8r\xa2oH\xe4\x1d/4\x1c(\x8d\
+\xf0\x04\nX\x96\x82{\x89\xed\xca~Rr:vG$\x9cw\xfd4:\xc468\xe3\x9a\xde\n\x1c\
+\xa8,\xd6\xe0"\xf4r0\x07)V\x81zZ\x1bLJ\x99\xac\x16(\n\xa93Z\xd2\x9ap>\xbcsq\
+\xf5\xa8[\x7f\xc7v\xc1r\x0b\xd0}\xc1R\xa8\xb5GM\nb\xc3\xa9\x14\xac\xba\xb0^\
+\xc7\xc3Z\xd8\x86\x1a\x01E\xe5\xcf\xab\x08\x16?\xac\xba\xc6E\xf8\xe0\x1ct\
+\x1e0\x80\xe9\x06\x88\xc8\xc6@\x00\xbdN\x8bH\x1e\xa2\'b\x0b\x18\x89\x1f\xcd5\
+\x04\xba\x02~|\x87\x83xb\x0b\xef[\xb7\xff\xa3\xbdG\xadY\x88\x8c\x1d\xc8\xbb\
+\xed\x87;uR,$\x05\x9e\x8f\xcf\x91\x0c\xae`\x1b\x02j\xc9|\xc6#\x0f!.\xc4\x07\
+\x14\x10\xc7\x81\x06&\xb68E\xcc\x87Vq8$\x1dL\t+\xaaTK\x10-\xf0\x99\x82\\#\
+\x8e8\xc0\xdb\xb3\xe4\x08\x03Jj\xc7\x82\xd6y\x96\x81\x97_\xc6\x91\x88\xa9\
+\x9c+\xa2\xf5\x1an\x0bRm*\x13\xc4@\xe4r\xea\x82Cv"\xcd\x0c\x85F \x8c\xd2)d&\
+\xa8\xbb\x88\x00\ty4\x8eS\x8d\x1e<2\x01\xb1}\xde\xfb\xae\xe1\xcc\xcc\xf8\xf8\
+\x8bp`\rs6\x01\xf1\xc0z_\xec5\xb3\xe0\x80\x7f\xb6\x11cUZ\x04\xc9\x08\xe7\xcb\
+4\xc6j\xed\xc1E\xb0\xb2W\x08)C;M\xaat\x81#ub\xd7\xc2w\x11U_\x98A\x81\x883\
+\xf9\x8a\xc1\x04t\x99\\\xdc\x05f\x87\xa6<\xfe\xa5\xba?%"\x06\xd0\xfc\x13\xa9\
+\x07\xb6\xbb\xa8\x9eRB[\xcff\xd0p\x80\xb9\xc5C\x1f\xa5T\x99ua\x06\xb04\xc8\
+\xd3\xf9\x9c\xa5\x1a\xda]^\x17\x8f\xb8Q/\xa2\xb4\xcd\x9cH9\xe6\x18\xea\xb9\
+\xc3\x08\xe2/\xb1\xf5\x80\xdb\x12\x1f\xb3\xee\xd0\xe0}\xba&\x84\xd2\x1e\x1b\
+\xb52\xedj\x12v\xf5|\xd4\x1b$Sw\x9e\xcb\xd8eF\xfcE{\xc55?\xd5%\t\x07@\xc7\
+\xec\x10\x08\xe0i\t\n\xaa\xed\x901\x19\x81%\xbf0\x85\x14AV\x8e\x0c\xfd\xae\
+\x87\xb0N@g\\z\xb0::-\x0c\xc4\x03c>L\xaf\xd0J\x03\xe5\x1b\xcd\xbf\xd0J\x03\
+\xaa&\xcd/Q6\x9f.\xbc\xc5\x91\xc9$\x8f\xc3\x9eo\x15\x99\x1e\xdbK\nx\x88c\xac\
+\xe8\x1c\x9dJ!)i\xab8oD\xda\'\xf4\xb4\xd1\xc0@{\xa0%\xae $$\xf8 \xc6.A.3\x90\
+r\xf8\'Zpm\xc5\x08]\xf4\xc1\x01(P\xc8\xe1\xc0\xf1\x8b\x1eK2\x9dtR~\x8f\xfe\'\
+\xe9\x12\x0fe\xd2*20\n\x02\xaf\xb6ramQ_k\x9b5!\xe2Yh\x99J\xb4=>\x87\xcb=\x00\
+3\x04;\xc0<8\xa4\xc8\x0b\x04\n!\xcd\xab\xb0\tX\x01M\x07D\xa2\xf4\xc7\xe3N`95\
+J\x16\x1eY\xb27\n85\x89L\xe6\xe8\xe9\xa4Anph\xc1\xd3 S9\x152\x8e\x91\x82\x0f\
+\xc7\x108\x05\x92\x8d,\x1a\xcaa\xab*\x03\xb9|i\xb6\x05\x9d\x89\x88,w\xb6\xcb\
+(\xa5$d\xa49\\\xe8\xb5\x0c\xcc\xec\x0e\x03\x8c\xb5\xb6H\xf5\x1c1\x07+y1\x00\
+\x0fB\xd3Z8P\x8a\x86l(\xbfW \xb0J\xec1\xab\x05F?\x90l10\xf7\x7fu\x025\xee\
+\xd4\x0005\x80n\xb8\xed\x8f\xa2\xb7p\x10o\x13\x08V\x13\x016\xbb]\x8c\xa2\xa4\
+2\x14*\xea\xf2\xfc\xa2\x9c;\xeb*\x16&\x96m\x08\x08\xd0!`\x88\xa96\x84!.\xbcK\
+\xaf\x93\xe6\xd1\x88\x06\xc5AroC|\xb3\xea\xb2G\xdc\xf1\xa9w\xa5\xcdM\x8e\x80\
+\xb1~\x84\x86H\x99^n\x81\xd8\x98C\xa2\xfa\xf5\x87N\x8b!\xfa0-\x97\xc5 \x9e\
+\xce\xed&}\x89\x1c&\x8d\xb2DWZ\xd1\x03\x1e\xff\xe6\xc2*\xcf\x0e\xec\xa7\x0c\
+\xcd\x08:\x89\xe6\x8e\xc2\xea#\xb4\xef\xdeqO\xea\xe1\x8ff;b\xf5\xcc\x913Q\
+\xc2\x12\x92o\xe4\xa3\xeb\x93\xe23\xce\nQ\x06)*\x84\xd5\x04\x81\xbd4\xeaM\
+\xfd*K(B\xa2\xa8\x10\x94\xd0\xf9=q|??\n\xe3\xcb\x81\x14.\xb8\xa3P\x0ex0\xf6W\
+\xff\x98#\x83\xb6\x8c\xf3j\x87 r\xf1O\xb8\xd7\x88\xaf\xf6:\x01\x0b\xfa\xb0s\
+\x92\x19f\xe5\xacV\xd5t\xab\x0e\x08\x86\x14\xab\xb3E}R\xd1\xcaL\x9a9NIQ-\xd7\
+\x9b\x15nD\t?(6\r\xaeb\xc7\xf0>\xc7}\xac\x7f\xf9\xa4W(l\xe0"\x0fZ\xe4s8\x9fO\
+y*\x9d\x87\x0cK\xc6\xbd\\\x12F\x95[!\xeed\x07\xe6\xe4bG=\xc3\xaa\xd6a\x87\
+\x0eLf\xa4j\x1aV\xe3\x102\xef\xa6&p\x0e\x01$\xdfG\xf9f8-W\x95\xff\xde\xb1\
+\xc1\xc9\xf1\xd5\xdf\xe2\x99\x14s\xbc%%\x10\x07}f\xde[\x19\x9cq]\xc8\x9a\xc9\
+\x91\xcf\xb6\n \xc8\xcc/L/\x11[N\x90\xd7\xc6\xe2\x87M\xc8\x0c\x8e\x0b;#\xef\
+\x92J\xab\x1du\xf9\x81\\i\x17\x12/\x99\xcc\xc3\xadR\x08\xca\xef\xb2O\x80\x83\
+*1]\x8cF7Z\x98\xaa\x1beQ\xfc\xea\xfb\xbdz\x9d5\xb2<\x7fq\xa1\xf8\xbd\nc\xd8.\
+!\xdd"\x0f\xa6\xa9jU\x19\xea\xb6\x93\xa2\x86w\x13\x04^\x19\xe86o\xb2\xbf\xf2\
+ND\xa4\x9b!\xc9=\x00&\xfb\xe2;\x80\xd9\xe8\xc7\x1b\x9b\x95K\x98\xab\xbb\x94h\
+\x11\xd6U>B\x94gyiX\x1d\x10\x0c\xbb\xe2\xd4{\x1d\x86\xf7\x7f\x14\x8f\xdc#\
+\xd0\x10\xd0\xaf\x1a|\x1e\xc9\x13((8\x88\xe5<\xb9h\xab/\xd4\xd6\xbb\xf3\x107\
+\xae\xfbWa\xfaIx\xb7\xd6\td\x90\xad#\x1a\x8e\xae\x90\x8b\\\xa2\xb4\xb12\x82\
+\xf6\x81$!\x87\x11\xd0\xf7\xa5\xbf\xf8\xa9\x8bN\xf6\x85\xd5\x0e\xe6\x1e\x1a\
+\x1d\xd0e\xf0\xb4X\xcc\xd0\xb2\xaa4\xc7\xe3?\x1c\xf4\n\x0e<\xcb(\x07\x9d\x03\
+`S\xc4\xe5\xe5e\x96g0\xe8\x8d\x12z\x82[}\xa6\\;\xc6Eid@\xedX6\xdf\xc5\xfa\
+\xecC\xec9&AC\x1db!n7\x9b\xeb\xd8\x99\x8a,/7\x9a\xc7^\x0b\x08\xa4,\x0e\xae%%\
+\x0e\x19\x1b\x98E\xaf>p\xe0\x06\xcb(\xf4cEe/3\xc8\x92cF~\xb2\xe4\x8a\x89r\
+\xe6\x9cXa\x8c\xf5\xb6Y4;\xdf^b\x88QG\xad\xa6\x0e\xb0)\xc3j\xc3{\x12m\xf2a\
+\x86N\x11p\x98\nz1_\x0c\xab\xbfw`\x98\xde4/1\x98\xd5=\x04da\xb1\x1d\x8e\xe7\
+\x8e\x92-\x88\x89t\x13\xbb\xa2\x07k&c\x98\xa0E\x92\x0e>sz,\xe5\x82<\x1b\xb5B\
+\x96M\x16\x0c\x01\xb5\xa4n\xe1\xa3\xbd*\x80\xa0R=\x98>\x97\\+r\xf46rE\x8b\
+\xf1\x08\xca\xb8\xa7(\x00\x94Hp\xd0\x9e|D\xe7K\xca\x0cg\x83r\x88&\x0f2VINE\
+\xc7*\xa87\xe0\xe9\x1f\x95,\xbb\x97\xfbD\xf1^\x8e$\x0fP\xa3\tV\xc5L\x80&}\
+\xe2q\xeb\xfd\xa9E\x12\x0b\xc0\xfb\'\xe5\xf2\xf2\xaaB1\xe8b3B\xe8JJV\x81\xb9\
+^\xb2l\x18\x93I\xe1!L\n\xec t\xad6Z\xda\xd0\x81\xa0\xaf\xdbLj\xeb1\xaa\xa2\
+\xaf\xc5\xe4O\xf94\x0b\xf3\x8aT\x08\xe0\xc9\x02V\t\xc1so\x93<\xa3\xb2Z\x81\
+\xf9y\xab\xb4N AV\x1f\xe2|\x04\xf7\xb3\x8f\x89\x0e\xdc\x8a|\x82\xc2W\x16\x94\
+\x8d\x03t\xe0\x18\xb4\xde\xd7\x14&R]\xa6\xb84J\x02\n\xa1~\x85N\x06\x07FR\xf5\
+\xd6\x05\xa5\xa4:\x89\xc9OPhj\x0b\x00\x8e\xfe\x8bT\xa8\xb2\xa7\xd5\x98:s%\
+\x8eaA\x01\x88d!_d\xa1\xe3\x1f\xd5\x93E%X\xd7\xd3\x0e\xf2\xa9\'\x91\xd7\xc3\
+\xbd\xc5\x80\x03\xae\xf6L)\xa4\xc8\n\x9dg\xafz\x18Q\xd8\xcd\x110\x19\xe6PS\n\
+\x15.H[\xc9\x004\x7f\x07\x87!\x96\n\x00\x84\xa7\x8bdO\x1fF\x90\x05c\xae\x0cq\
+%\xc6\x07\x91\x935>\xe9-\x02\xc1A_#\xa8L\xc0X\x1a\x01\xa6X\xdc\xd5V\x05\x84\
+\xde\xf32Z\xab[$\x0b\xa2\x96\x7f\xc9(\x11\x01K\xa4\x0f\x89\x00\x1e<\xbf\xd55\
+\xb2\x84\xae\xed+\xaa\xe8\xb1iX8\x08w\xab\xea\x17(S\xf1\x14\x1d/\xb3\x12\xf5\
+\x8a\xf1X\xd5\x807T\x87\x97q\xa1\xe3\xd43[\xea\xa5Y\x81\xa6\xcf\xe2u\x7f=\
+\x99\xc7A1\xf0]\xc7\xf2\x9a\xa6>\xd1e9\xe3Z%\x01\xb0\xa7\x17!^\xe3\x15\xe7\
+\x1e&t\x8eaf\'-??4q\xd0k\xaf\xc6\\\x92Om\xcb85\xb4^\x03\x1c\xef\xde\x882\xc6\
+<\xcb\xd02\xc1\xc0\x9c\xc13N\x95\x17\x083"\x91\x8e\x9b\xac7\x928_\xa9\xb6\
+\xcf=\x8d\x94\xef|\xfd\x1c\'7e\x9ef\x05\xda\xe3$\xb79\x00yAu\t\xa1\x18\xf8k.\
+\xf2\xc8bO\x03\xa2b\xa5^!\x82M\xde\r\xd6\x12\x19\x1f\xb6\x8fn\x8b\xa1\x00\
+\x82\xa1}\x02\xaay\xbb }}^\xa4\x18\xf0\x84(W\x8a\x03~EAN\xd0\xf4<\xe5j\xa8\
+\x1fI\xae\xb5\xc2\xcc\xc7\x11\nQ\xf1\xb5\xb4x\xe9\xad\xd2\xfaVs\xef\x80+\x83\
+\xe1\xde\x0bd\x11\x0f\x7f\x88\x06\xc1\x8a\xa2\xa0y,\x93\x9a\x1eG\xcc3P\xef\
+\x13\xd4\x87\xdcx\xc6#\xde6\t"O\xfe\xb0\x8b\xce\xf3\xbbs\x9e\xa0\xdd{\xc5uV\
+\xf4\x98\x92Y\xee\x91K\xf3\x90b\xca\xaa\x04M\x07\x94x\x1d\x07|!\x9e\'\xf1\
+\xcdQJ7\xcbL\x99W$_}Y]\xec30\xfaxm\xb0\xfd\xe7\xe4)\x8e\x1c\x08V4?$=j(\xd0\
+\xe1\xc6\x92\x89\x03\x88n&\xef\x10\x84\x90<\x0e\xfa\x06\x84\xd2\xd8\xb7t\xd5\
+\xab\n\x9cJ\x90c^\xa4J\xd7\x86\xad\xc0\x98mbC\xeb[v\xc6\x10\x805\x85b\xa0\
+\xb0\xd2\xc6TB\x108Y\x1a\xaa\xeem\xe1\x8cxt\xc9,Z\xf8\xa5+\xe0\xe21o\xeb\t\
+\xa8J~\x98\x9c5\xb3\xdf\x07I\x96g\xc0C\xe0u\x95A\xc0\xa6\t\x08\x97b\x84V\xb0\
+\x18;\\\xd7\xb4C\x8d0=h\x07\xd3\xd4\xd9\x14\x08@\x82\xe9\x14f\x9e\xbc\xb7X\
+\xf7\n\x9c\x05@t\xd7F\xc6I\x96\x84K\xd8^\x9b X\x89-@\xaf/\xc2\xa0`8H\x8f\x04\
+S\x1e3V\x02\x9b\xbf\xfc\xd8\xaf\x1a\x90\xbd\xd7\t\x1d\xb9\x08\xb0\x18\xda\
+\xaa\xc9\x7f]\x8a5\xfc\xb3\x0e\xb0\xe1\xd0\x14\xb9s\x06\x08Ve\xb5\x18I_1Z\
+\x19\x9e\xf0\x8f\xa4\xaa\xfb\xcaJ\xb6\xd5\x0cD\r\xd8f\x8c\xad`\xc8<u\x13ID\
+\x02\xc1\x01\xdd\x0b\xf0Q\x14\x05MUS\xd3j,\x16\xe5\x83\x8e\x83\x0cG\xda+5Ju\
+\x0c\xe9\xd3A\xb5\x01w\xe1D\x1c\x89T\xd0\x9a!\xe7\x92h\x94x\xa3`\xd6\xa7\xca\
+\x07R\x9fV\t\xe6\xe6]\x7f\x02I\x99wu\x11\xd5\x8f\xe533 \x1dB\x96I\x92\x18\
+\xce[S\xf8\xa2 \x8d\xe3\xda\xd9\x85\xcc\xb1o\xca^\xda9\xad\x07\xad\x86\x8b\
+\xca+\xec\xc1\xb7\xf014\x93\x07}\xf4\x13\xd5\x1aO\x06i\x8e3U\x06\x9a\x8bG\
+\xc8U\xeb\x19s~\x95\x10\x10D\xacK\xd4eoQ5\x86D9UG\xb3\xaf\xc0\xe4Qt\xe8 X\
+\xads\x85$HG\x90\x06\x97OF\x84\xcc\xf6\xa7\rX\xdfQ\xaa\xd4\x01\xb5\x86!]tO\\\
+\x07(T\xb8_,\x93\x98\x18\x02v\x99\xa4\xf0\xc1\x98\x16\x94\xb9\xd6kC8\xf8\x96\
+j\x02sz\x0e\xc8\x1f\xccug\xed\xbc0\x93\t=\x96\x1a\xa3\\\x8c\'\xeb\x01m79\x87\
+_\xf6\xe2\x80\xdc\xe1W]\x10(\x10\x99\xd7\t\x8a\xbb6\x86\x1b\xe1J\x84\x1dC\r\
+\xc7\x99,\xf5\xad\x00\x01v\x0f[>\x14\xe6\xa3\x8a3{\xcc\xd0\xf3\xcaE\xd9\x07-\
+\xbd\xc6&f\x13\x8f\xb2\x13\x04\x94h\xe4s\xd0\xe9a\x1e)\xbau\x97*\xa8R\xf3\
+\x91\xc9\x86\xd7\xdfT\xb2J\x07\x8a\xf4\xda\xe5\xf7LP\x12t\xfd\xc1^\x82\xcd\
+\x03r\xc9T\x87\xc2C\xfc\x82F\xc1\x1b\x90j (\x00`\x9e \x99\x1d\xf6^\x19\xec8\
+\x82j9\x89\xe0x_D\xa8\xf6s\x0e\xb8\x03\xe2\xa1i\xe5\xdd:\x01\xef\xa2\xec\xb3\
+\x9f%\xe9\xc4\x85_\x17\x97\x01\x8b\x08\xea#\xb9\x89\x80\xa0\x923\x04N\x13?!n\
+\xac`R\x86\xd4\xd0a\x00\xe9)\xa1\xd7%L\x98\xe8\x1d\xce\xc3\xbc(\xa2\xfc\x1d\
+\x80\xe7\x7fx\x9ep\xd9i\x82wO\xfc<\xf8\xee\x13\xdb\x1d[\x8ew\x06\xfe\xf6\t\
+\xa0/-\xf2\x8e\x1dz<\x95\x10\xc0\xc6>\xaa\xb0B\xfc\x89\xb1\xa0\xc5D\x81a|z2\
+\xe6\xb9y\xca\xd3\x15\xfaC\x99\x89|\xeb\x1cC\xfc\xf2\xe0\xe4\x1ddKh\xbe\xb2C\
+\x83\x00\xc9\xc5\xe1b\x80f\x8d\xd9\xb5)\x1b\x01,[\xdd\x012\x12U\xb4\xf1\x9a\
+\xf8\xb8\x91\xe7\xdc8\xf1/^i{l\xe97\xdb\x17\xab\xf0\xf6\xb2!CF_\xd3\xe0\xe4\
+\xa5\xeb\xfe\xaa2\x13\x95\xa8D\xc3\x80\x8dr\xa7\x08P\xcdA\x9a"\x08\xce\xcc\
+\x18\x1a\xab\\\x15\x96z\xa26\x8fYs4\xd8\xb8\xaf\xdcb\xfd\xffq\xb1\x08\xb1\
+\xa60n\x07&\xf9\xe1\xfd\xact\xb6\x16\xcf\x0c\xbbq/\xcc\x0f\x9f\x140\xc3\xcd\
+\xc7p\x0f\x848\x94!\xdb\x9a\x94\xee \xacN8\xd4!\x943\xb6\xa4\x14\n+\x954\xd2\
+_\xab9\x84\x9f>\x1b\xb5\x82\x13\xfbH\r`OA\xc9\x06\xc8\xc6\x16\xc6>U\xa1TRZ)"\
+}F\x02\xa7\x81\x89\x00B\xc8\x93t\x0c\xd4\x0c\xcb\x0e\xd8\xd7\xec\x18\xc9D\
+\x8d\xbbHl\xe4\xbb\xf4\x15]\xcc\x97 \xce\xbcE\xc5\x1c\xc8\x0f\xc8\xc9\x0c\
+\x99\xd4\xd5-\x15\x1b@\x01\x82\xee\x8a\xc6\x84ITc\x8cM\x05q\xb1\x18\xa9L\x13\
+\x80F\x04y\x99\x08Q\xd9K\xe5\x8c\xcc\xb8F\xe7E\x82C\xcbn\xc4,$;B?\x95\'5\xad\
+\xee\x130\x1cd\xe4\x17\xdf\x05\x94\xcf\xa0\xa7\xe0\xbb8/ \xe2\x8a\xadK\xb1h9\
+A\xf9\x8e*\x15\xdd\xcaB\x1c\xfa\x04\x01\xca\xbb1#gN\x89\xafy\x9d\xa7\x95$\
+\xe7F\x15mr\xdd[h\x10?a\xfc#*\xf8\x03\xeb\r\xb8\xf0\x8d3\xfc\xa2\xa1\x9e\x03\
+\xd7\x82B\xff\'8\xbd\xf1Z O\xbf\x8e(p\xa0S\x8d?96\xbc\xef\xbe2\xc9\xd4\x7fD\
+\x00Q\x08\x9cet\xd3s\xd1F\r\x08Q\x848\xd57`\xdc%\xb9\xba\xf0\x11A\xc4\x9af\n\
+\x1c\x00@#\xc1\xed\x1d\xff\xf4]\xa8\xa2\xed\xf9!\x06\xbd\x96m\x88q\x1a\xd7\
+\x0e\xa4\xe4=\x918\xd6\x07C\x06\xe9\x07\x84x)\x1b\xaf\xdc9V\xc4\x96")\xac\
+\xda\x87\nf#\xe5T>.*D\x00\xe0O\xab\xb6Y\xdaR\x9aO\xd0\xe2\xc0\xaa\x86*\x8c\
+\xdeA\xfa\xa1\xac\xc3\xf2\xad\x8f\x9a\xc2\xa7\xfe\xd2>\xae\xcd\x8cJ05\x92x\
+\xeb\x9d\xda\x0b\x9a\x04Z\x8c\x86\xd2\xf7\xc0p\xa7\xdc\x88\x0f \x03A@\xb0\
+\xe2\xa5\xa9#\xaaNf\x95\xc6\xa0\xfb\x83\xafc\xeeF\xd545\xe6k\xa8\x95/\xe5\
+\xb5\x15l\x0c\xd0\xaa\xe9HK!\xe7\r\xd2\xb1\x8a+\xc7l\x94F\x9an\x97\x9cJ<\x01\
+\xc5\x0b=\x90\x1c\x0b\xdct\xac\xa5U\xbe]\x1e\x07\xb1\x84\xa7W\x14\x96\x1e\
+\xe7sQ?\xc4\x0f\x9e\x87iz\xc3\xb1\xca*\xd6e\x1e|\x08\xf9\xc8Q\xd5#\xaa+\xf7\
+\xd90\xe5LJ\xdb\xce\xaa\x97\x0f\x06\xff\xc2\xca\x1c\xfd\x84,\x0fN\xee\x12Q\
+\x04\xb0\x02\x045"\x0f\xf7\xe7\x14\x1aH\xa6\xd2U\xe0\x90@F\x80\xb8\x07\x8dcJ\
+\x89\xc2\x00Y\x01Z\xbf\xf7\xaazU\xfc\t>x\x8cU\xbeJX\xa8C<k\xfe\xb9\x12\xaf>?\
+\x92\xcdg\x9bx\xae\xd9u\x18\xf88\x84s\xd5\xd0\x11\x0bBT\x105<\xf3"\xbcsx\xd9\
+\rC\xa0\x8b\x89>\x02\xb4>\x81\x81RT^\xe4\xf1\x8e}\n\xdd\x0e\xe4\x1d\xa3\xc5u\
+^\xb9\x1e\xa0\xb3\x85\xa2\x15\x1b\x9d\x0fd\xcb\xa0\x8d\xc9\x1b\x08\x1dy\x91E\
+%\x19\x8b\x1f8\x02\x9c\xe5|\xb7\x91Z\x12\x91\x1d\xad\xdf!\x7f\x8f\xe6\x0f\
+\xf3\x03\x8fb0\xd31\x7f# \x1a(#\x8aH\xdc\xc8ip\xb4\xfd\xe1\xf9C\x05\x17\xb9w\
+\xcd\xa6\xa9B\xb7\x80\xe8\x9d6H\x0cC\x8fT\xb4\x13eX\x1d\xed\x15G\x00\xceoo\
+\x13\xb2\x81\x81F;\x19\xa8*\xbfZ\xd3\x1d\xf2\xb0\xdb\x1f\x8f3;\xde\xaf\x06#\
+\x89o/\xc1\x18\xcd\x04\x85\x82"\x9f\xc6T\xcbi\x16\xd0\x89\x9b\xbb\xbe1\xae\
+\xbd\x8b\xbaAC2^\xe1a\xfd.\x82\x99\x97\x14\x13\xfa\nE\xd2q\'H\xf0g\xcarj\x00\
+\xa1LI\x11H\xf2\x1e\'\xa9b\xd6\xca\xe3\x91)D\xa9\xb2\xc5\xfbX\xb6 \xca\xde!\
+\x8fP\r>h\x02\xa2\xdb\xc9\x88\xf0.\xfd/[\x11\xca\xa5\\\x0e<Qv9\x1bL\xd2\xa8\
+\x15\xc8\x96r\x91Im\xc1\xb7\xaa\xb6\x84\x00R\xbdc\xffU\xdb\x98T\x85G\x08\xc8\
+\xe1=\xa5S{\x8d\x90\xd1\x91d\x8by\xb25\xe6Ch\xae\xfd\x1a\xb4\xa2\x00\xf0}\
+\x13k\xa6L`C\xeb\x81k\xfet\x95\xec\x82\'8@\xe7\xc38o\x9a"NT-u\x1cK\x15\x10\
+\x90\x95Y\xda9D\x08\xd0\n\x0b\xf9\xa7\x81]t,s\xe9d\xbc\xd3I\xa31R\xb1\xe10+r\
+\x9aM\xf2\x01D.\xac\x89Z\xaa \xad\x13P\x1d\x1a)\x05\x86\xb9\xe4C\xbe\xa4\x06\
+\xe2\x94\x81\x1a\x10\xc1qw\xdbc\x84%\xb9u\x8e\xb6\xc3BSww\x0c\x1a\x88\x17\
+\x8e\x00\x02.R \x99\x04\x16U\xa8M\xebQe\x0e\x19\xd3\xdb\xf5~\xa9\x06\xcb\xd6\
+\x044\xff4\xed\xff\x04\xe89\xa6\xf1C\xc6\x86\xf5\x1e\xe7 JRH\x1e(B\x82\x15\
+\x80x\x0f\xdabzF?\n)Qx&a\xa1T*HT>\x14%D\xc5w\x8f\xa8\x0e@\xea\x05[s\x87\xffr\
+&1\xb5"\xac\x13\x9dr\x90S\x07e\x82\x88\xe0\xc1\xa8v\x06\xc4\x85o5\x88\xd7fa_\
+\x91;\x86\x92\xc3\xae"bVR\x1c\xa4+\xe7|\x07G\xe4F\xaa\n\x08KM\x18\x9f\x94\
+\x05\xba\x86\xb4?\xdd\r\xd1\x01\\!\xe0:\x9cu\xa3s\xc3\x11@P\xe4\xc8T_\x8er\
+\x85\xbc\xc8T\x08\xe5\xb5Z\xdeM\xb1\x06M\xcf;\x05Ff\xa0\xcb\xc6D_\x08:\xc4F0\
+~Z\xab\x90\xacCB\x030\xbf\x05\x8dF\xea=c\x92\x04"\x8c\x01u\xf42\x02\x02\x01\
+\xd2\x1b\xc0\x95Hg\x10\xd9\x17\xea\n\x94\x11`\xc8\xb1\x80\x8ad\xb3-\xf5i\xe8\
+\x01\xa8\x97\xb8\xb2\xfa\xd0-\x16IU\xe0U#\xa4\xf1-\x8a\xc5\xffI\x0e\'\xd68\
+\x88\xcd4T9\x16.\xefA.5P\x84\x81+bb\xa3\xe2Wi\x12E\xe4D\xbfc\xf2 \xc4$[U\x13\
+tY\xe2\x01_\x1f(\x9c\xc4t\x92./5\x9c}\xcf\xef\n\x8da&\x019\x87\xd2~\x04\x16\
+\xa8\rp\xa9\x1a\x96-\xc4(\xf2\xe5\xd0Ur$\xf1\xcb\xa5!M\r\xe8\xe5\xc3\x85\xa6\
+\xf1\x8b*\x1f\xebN\x0f`L\x98\n\x04$\n\xe8\xc2\x1d\xbeA8B\xa7;\xbc\xcc\xde\
+\xe6\xec\xe0\xd0\x00.,\x97\x06\xd5z4%m\x9a\x04\x99Sv:6v\x8eEsi\x01\xc9\xab\t\
+(\rO\x1d\x01\x1a\x17xN\xe8\xe2\xaf\x8a\x00\xbc\xdc\xac\xb7\x8fkl,<\xaf\xc4\
+\x0b*\x86\x19\x8e\r\xa1\xe9^^\x92w\x06Q\xad\xe0\xf0\xbaD\x1b\xdb\x98\x16%\
+\x88\\\x82\xc2\x91t\x1a\x9fT\x01\xab8]fl\xa6H\xc5\x136l\xa6\x13\xcdN$\xd2\
+\xed\xef\xa2\x85\x86\xe1\xdb\x99\t\x0b\xc8r`+ \xcd\x8dr\x95\x99\r\xe3\x0c.\
+\xdeU\xe8\xf6\x94\xd9D\x9bJ\xc5\xcb\x99@\xb6\x12\xe5YD\xa7B\xcbfQ\xc4\x9b\
+\x03\x05\xc5\xe7\x81@rQ\x13]R\xa7|\xf4czXC(\x16\x80\xd7\xab!\x80\xfeT\x05!o{\
+\xc4i\xdc\xe4\xed\x88\x1c\xc0\xe2\xed\xbb(\xaf\x9cF8\x00\xef:s`(\x03B\xa8\
+\xb2\xe1\x86\xee\xad*\xaf\xa8^,/v\x91\xa6\xf3\xd4\xfb\x93\x08p\xd4@ \xaa\xc2\
+\x11\xc48L\xe8\xa9n\x01\x00\xc4\x91\xeb\x85\xdeVW|\xa8\xb5\xd0+\xaa\xf5\'\
+\x04\xdc\xf4\tG\x10wg\x14\\\xd4\x04a\xc1\xcfc\x8f\xae\x8f?\xdaRecY\xa4g\xa8*\
+}\xad\xc8\x89\xabw\xdd\x01\xc4LT \x80\x1a#dl"y\xd1\x88|[\xba\xc0\xfb\x91\x8b\
+\x99\xa9\xdc\xb0G\xc0\xe1\x83 %q`\x0f\xe0\xfc\xf3G\x08\xe8\xc6chU\xd2\xd7-\
+\x13\x1d\xb4\x1am\xa7\x94T\xc1\xa3 \x00\xfd\x90\x89\x9ef\xfe\xdbH\xe6j\x9aZ@\
+B-\xfcU\x1a\xa2\xb5\xcd\x92\x866\xa24\xb7;9u|\xe3\xa5\xf7\xa4\x82U!\x0f\xbd\
+\xd1\xa0\x80\xa0\xef\xf0WG\xbe\xa9\x0c\xd2:c;\xc4\x10{\xcc(\x03\xa8rbkM\x16\
+\x02\xd8\xd6\x80\xa4\'t\x80\xf4\x12\xad\xb9iP\x02\xc2YVZ\x1b\xfe\xa8;\x81)\
+\x9b\xe7\xb7rm%\x13X-U\xd9Q\x88 (|\xc7\x84\xfe\x89\xb4\x84\xf5E\xd7$\x8a,\
+\x19\xe0\x82T\xe2\x9c\x9a\xc3\xe6\xbe\x80\x80\x94B\xcbH\x84!Q;\x12\x85U\x8c\
+\x18\x88E\xcf@\xa8\x02)\x1b\t\x05\xab\xdfQN\xdc]\x12\x19|.(\xa2\xf3q\x00\x01\
+\x04\\\x9f+\xb93~\xb9\xd4\xe2"\x89\x125\xc6=h\xa2\x92\xfa\xdfr\x03\xbb\xb1\
+\xeb\xb0\xd4\xb9!\x01$\xcf\xa0\x08\x1c\xba\n\xd4\x83\xcd\xe6\x9a\x19\xf7\x81\
+S\x0e\x82j}\xa7\xbc\xc3\xaf\xf0U\xef\xa4\x1dINR\x1132\xf6\xa4\x80\xf3\xa5\
+\xc86\xf3h\xba\xb4\xf4\xe2\x12\x80\xf4m\xf8e#\xc9i\xa7\x9f8\xa8\xec\xe1Z\xa5\
+\x00l\x048\xce\xa21\xfa\x05\x02z\x87\x16-\xfa\x18\x12y\xcd<\x01X.\xe5t\xb5\
+\x1f\x83\xd7\x08U0\x93\xa6\x886\xf3X^\x8aB0L\x02K\xc86\x01\x0fV\xe6\xa7a\xa1\
+\xc8\x13\x82\xa8\x16\xfc\\i,/\xb2I\x04\x98\xcb\xc3\xbc\xd7-\xff\\\xda;\xfd\
+\xf8Fq=\xa9G\xba,\x90y\x8a\xb8\x08\x83\x8c&H\xc6[\xd5\xdc1\x93\x96\xae\t\xbd\
+\x08\x03\xa1\xc0\xd9f\xa0\x8b;\x81\x82\x1a\x7f\xb2XPK2\xc5\xbe_r\r\xf3\xfb\
+\x00a jS\x7f\xbd5\x15\x8f\x8e\x98e\xf3<twCP61\xd2\x04\xf5\xe5\x90)"\x8f+\xca\
+\xce\xe3L\xab\x1e\x9d\xa9\xed\xd9\xc0q4\x97i\x02$\xb5\x90H]\x04\x05\x01\xf2|\
+@T&L\xd7\x14\x0c\xb5\xe3\x8cc>\x86\x94y\xa0]\xa8=\x93O\xba\x1c\x9a\x11`\x1dN\
+\xd2\x85&\x88\x18p\xd82Q\x8b@\xf3\x93\x1et<7\xbaW\xb437\x195N\x00E\x80E[ \
+\x80 \x01\xa9~\xbeFe\x9d\x0f\xe8\xa3\xa1\r5\x90\x04a%\xeb\x95`E\x04\xfa\x88&\
+{\x86\xb95\xa2\xc0\x01\xce\xd0\xa0\x18\xe7\xc2S\xe9x\x17\x88\x8ekU\x1f\xe4\
+\xc4\x9f"\x8d^t\xfc\x0f\x11r\x8c\x91\x0f\x8c\xf2yAN\x07\x90#!\x0e\xfd\xd5y\
+\x12\xf1\x04\x08z\xcf\xf6\x01I9\x88\xb5\xc5\x84\x03V#[\x1eP\xd6\x88\x8a\x01\
+\x9f6\x06\xf4`L\xac\xc0\x89\xde\x05\xd2`Er\xa4\x02\xcdZ\xd0\x15"\xaa\x0cT\'X\
+9\x10@\xd7\x82\x86C@Qn\xba\xfa\xeb\'\xee\xe2\x8b_B(9\x1c\xe2\xf1>/\xe2k\xde\
+\xd9\xc6\xa8\xb4>A\x90K0\x01Q\x0f\x00R\xa1\\\x19\x00\x93\x1e\x80\x94\xb42\
+\xc8e^JJ\xfa\x01\xaa3\xa8\xae\x06\x1a\x08\xa0\xea\xac\x1a\x01\x94C\xa6\x01*\
+\xc0\xa3>\x1aV\xf4\x1bj}\x03\x00e\x92\x99\xf9\xf0Ca!\xd0x]\r\xbdS\xb6`\x8c\
+\x02,]\xa2\x810\x16rs\x81\xb0\xfaT\x9e\x80\xf3D#\xa9\x06\xd7\xe6\x9b\x82\x7f\
+\x87o\xc4%\xf2\x9a\x87\x0f\x08L\x16\x19g\xde\xd8\x81\xad\x08\xe8\xb7\x19\xaa\
+\xca\x8a!g$\x8d:S\xf6@:[\xd4#\xa0\x83\x9e\x91\xe7xC\x85\xd2\xb0\xa5Y8\xc4\
+\x84E\xa0\xfa\x84!\x80d\x10\r\x92H\x8d?\x04\x10\xb5\xca \xb0\x8c-\xb7\x8d\
+\xa4,o"\xa8`*!@\x0bD\x13\x04\x8b\x10\x19\xe2\xe3\xc8\xe2\x90#@A\x8bSo\xd5y J\
+\xd5\x11@\xd4\x80\xddi\xd4~\xe5\xf87\x17\x82bb\xca\x93\xa1"\xeaO/\x94s|0\xa2\
+\xb3\xc5\xf5b\'L\x99\xfein\xa1\xca\x01\x92o\x88\x8d+\x86\x80V\x060\xaf\\\xb9\
+k\x1c\xd3\x91h\x01\x81\x95q\xfc\x86LC\xb55\xc1\x9e\x81\xb0\xc3\r\x84\x11\xe2\
+f\x01\xebH\x84\x83\x9e< \x1c\xb0W\xc2\xf5\xb2,m\x7f\xa8\x0ef/`\xa6\xcc\x88T\
+\x83\x98\x89P\xc0C\x88H>k\xa4c\x01\x85\x9f\x9c\x1b\xa6.\x06\x1b\xb5(\xf1B0\
+\x18\xc7xy\x90\xa2[\x92\xc2q\xb2\xb3e\xea\x90\xd8\xc4\xa1\x12\x05\xc6\x89\
+\x06\x8f(\x84\xe7~\xa4{\xa8\xcd\x1e\xd9NDX\'\x00\x04\x05m(;\xfa\x87\xdf\xa4\
+\xa2R\x81\xcb\x06\x90d\x1d\x01*\x1e\x84\xc2h\xdb\x9b^1C$\x1d\xd1A\x17\x858QR\
+Y\xcc$\x1f\xaa\x16+\x86+\x84\x82\x00@\x85C\xc0\xf4\x01\xfde\xde\x00\xa7M\xe3\
+\xc9"t\x83\xb5}\xd7\xa7\xba\xde\xd7\x10`\xb9\x08\xb5F\xb3\x16\x01|~\x02\xa6f\
+\xb6%-!\xc2K\xa8*\x17\x1d\xf9\x02\xf2\xc2\xd2\x0e\x03\xc3\xda\xf4>\x81\x9dBp\
+N\xb3\x0b\xb9\xa5b\x11Am\x1bs\x0c\x89\x90\xd1\xa5\xf9g\xc8@6\xeb\x94JA"C\x86\
+B\xff\x17T\x90\xcaN\xe9\x88\x87\x80\xc2\xf0\xc1\x81|\xe5\xc3*\x04\xbd\x01jl\
+\x8c\x0c\x8f\xa1\xa9\x965^\x1a\xe2/z\x7fz\x10Z\x9d\xd5g\x96\x93\x99z\x8dCKyT\
+S`\x07A\x83u\x90\xaf\x97\x10UD\xc5\x81\x07\xb1\xd0\xd1\xfd\x02-\xee\xe9\x9fB\
+\xa0\xb6M\x7f\x99e\x1e\x03\x90\x15\x7f\xde\x07\xc4\xc53c\xbd\xf2\xe5\xa8\x99\
+\xa0\xbb\x07Y\xd4T\xbe\x9d\x13\xcf *\xa8v\xd2J\x7fL\xba\x00}\xce\x15\xeaf\
+\xc1\x84\x81a\x1a\x1c@\xfa\xde\x81Z\xa9\xa3nb\xb8\x1a\x12\x01<\'\xcd\xba\x92\
+1Q \x13\xbe\xdd\r\xd4\xa1\xee\xd8\xb0\xc8x\x9aS\x8d\xc1Q\xd9\x96+FTs\xe1\x12\
+\xb9\xe1\xbb\x83j\x87\xf0\xcf\xe7HrM\x80\xb9\xadT\x00\xf5n`\xa4\x90\x1f\xe8\
+\xca\xab$\xf4|"&\x8dO\xc7sx\x00\xc7\x8f\x0cVC\xd6:\xb3]\xb4F+\x81\xb6I\x1c\
+\x0f\xff*\x05S\n\xf2\xcb\'\x8e(\x00\xe0\x08p\x8e\xb8\xa9z?*\xc7@J\x08\xe8\
+\xa5\xfe\x9d\x91?T2\x84\xe7\x96\xb3\x05R\x95*\x99-\x972\x8d\xef\xbc\xc5\xe6c\
+\x95*-\xa3\x9f-b\xc5\xdaH\x1f\xf2ecp\\\xf2\xe1\xd7aV\xca\x86\xdf\x18\xcdE\
+\x04(\xb3A#g\xca\x86\xd6\'L\xcf\xcf\xa5\x021\x94\x9c\x80\xe83\xe73\xc4\\>\
+\xc4\x85\x82\xdcq3J\xb3\xb4\xd2\xd8ESYE\x07\xa4\xa8j5\x94@\x80d\xaf#\xa0\x1c\
+\xb4\xc5\x1cs1\x98V,\xe2\x14i\x97\xf3P%0\x84>\xa0\xc1\x1cL\xf4\xb0\x9ftn<\
+\xb9\xc3\t\\>m\xaf\xa7\xc3\xd8\x84\x1c\xf5>\xf5@`\xa0na\x0b\x16\x19=\x9f^kK\
+\xbd\x16<4k:\xd2Z\xfc\xf7\xf4\xd6\xe2\x83\x11\x83Bf}\xf5\x1a/\x1bfBr\xc2s\
+\xdaLA\x80\x82\xa9$R\xd5?!y\xaa\xc7\xa5\xca\x1c;s\x9e\xa3\xd6\xf1\xde\x01"g~\
+Bh\x18\x02p\xdd|\xe5X\x19x\xaa\x9f8\xcc\xd0\xc7\xf93\xaf2U\xc5\xaf\x9c-\xa1\
+\x81\x8f\xa8\xae\x9b\x8b\x13\xbb\x00\x17\xcd\'`\xca\x80I\xae\n\x01\xb4~]Z\
+\x88u\xebA6:\xee\xb3N\xa2V/\xb0\x17Wu0@?5\xfesY\xe1\xf7\x0f\x12\x07\x96z\xd0\
+\x8e\x11D"\xe0\xc4\xffi\xbd\xcf\x80\x06\xa5\x81\xa0\x90\xe7\xd7\xd0\x05\xc6\
+\x90\x8d\xa2\xea@V?\x8b\xa8\xd6(d\x0f\x9b\xf3GA\xca\xa6\x88\t\x01\x15\x07\
+\xf0\x88\t0\xdf\n\x03\x14\x9aY\xb2\xe1\xad{\\\x1f\xf0\xed\x8c47\xcf\x1b+\x0f\
+\xe00\x16 =\xe8)\x14{q\xbaW\x08JyC`(g\xc9\x857\x08\xa3\xb4*\x00\x85\xa6\x1bI\
+\xca\xc71\xeb\x9d\x00t\x17\x84\xa4\x16u\xe8_E\xd0\xcfe\n\x95\x8f\xe7\x04h\
+\xae*\xe6Y\xf4R|\x9f\xd7W\x8c\xc4Z\xee\xc9\xe8.g,\xe7+\xd6G=\xcclM\xe0\xd1t\
+\x95$\x05MP#\x1ek3v\x95L\x00\'\x9b\xe5\t\xa5\xe2\xc3\xc4\x00q\xe4h#\xb3\xcb\
+\x87\x15\x04\x0f\xc5^\xc5UR\x8eR\xf9\x8c!\x10\xf5P\xae\r\xab\xa4R\xa0$\x1c\
+\x9f\x98\xd8\xb5\xf2v\xf3\xd7\xda\xf6\n\xa6\xfb&:\xa9\xd7\x87Tk\x8f\x15a\xa7\
+%\xdf\x00"\xd9\xf85V\xe0\xfb\x81U\x00\xc8! R\xa9G\x00r\xe4\xc8\xb4\x8e\xcd\
+\x06\x14\xfbe\xfb\x92\xda\xab\x89\xcc\xec\x80^k[\x82\x81\xba\xf5W\x1d\xaa\
+\x0f\xdfGn\x046\xd1\x08\xe1KC\x19U\xe8\xa4#\x0f\x00\xe1 \x0e\xdb\x07\x12\x85\
+i\x13lf)\x97\xd9\x86\xf2\xa3\x85\x8aF\x08$\xd3%\x93xv\x06\xe0\xc9\x8fZ\x0b/\
+\xdc\x94\xc5\x9f\xaan\x87^;\x9a:B\x85]\x80\xa4$=z\xdbr\x88d/\xda4\xd6\x12\
+\x86F^[\xfa\x03\x915\x00\x00 \x00IDAT18:\xb7b>\xa2<"\xa0\xd5\x96G\x00\x16J\
+\xee\x99s\xea,H\x7f\xa5\x18\xb06\xe7\xfac\x05\xceE8cHT\x8fw|\x04u!\xad\xe9\
+\xaa\xd3U\x1e\xe1\xf8\xb5-M\xf9l\x83$b\x94\x94Q\xf4\xfb#\xa1\xe7\xbc\x12\x03\
+\xa0\x7f\x10\xd1\xf2oI-\x80\xa1\xd3\xd6kIB\xac\x14""\xca\xa5R\x8dR\x86\x94\
+\xc5\xa6\xac\n\x0fH\xe5\xa03\x86\x99\xc0\x1e\x0bW2\xc8\x08\xb5k\x93b\x00\x81\
+$q\xac\xb9\x8c3%\xa4\xce\xa0\x1e\x16\x1d\xf4(\xbd\xc3\xfc\xb0~Q\xac\xb3/\x02\
+\xba\xe1\xacXo\x06-\xb3B\x83fK\xa6\xf2\xd9x\x0f\xe0\xf0\x19CH\\U~K0\xd6\xa8\
+\x11\xf7L\xa8\x98I\xc9#\xdd\xb6\xac\xad:c\xd2\xc3\x14\xc0\x13C\x00\xc95\xeel\
+D\xc2\xc00o\x03\x81"\x02P\xcd\xf2\x92\xba\x07JptR\x17\x8b\xf55%m\xbfZ\xd8i\
+\xd8\xf1:m\xfcQ5 $\xca\xf3\x96xi\xcbZ\x03}\x18\x05@\xbd,\xe2O\x85\xe9@"Fg\nm\
+?\x07\xbb\xa0\x1c\x1a\xb2\xab\x8c\x7fK\x9e \xbf\xe8\xee\x10\xb3\xf5z\x08\xbb\
+\x88\xbd\xbd\x01\xc5R$s\xd0\xde24Tred\xeb\xe7\xcda\xf1\xf7\x1c\x8c>J\x93\xcd\
+\xb1(\x0ep<n\\\xf7\xa5e\x00p\xed\x07\x93\xa3>\xb0m1\x8b\xd5\xfd{TA\xf8\xf1|\
+\x04yQ\x8a\x8f0\xfe]e\xd6\x0ea\x0c\xf2\x0e\x81\x95\xd4p\xe13\xf1\xf7\xf1\x03\
+z\x04\xd5\x87\xa8#ha\xa5b\xf5\x0c\xe5\x85\xf4\x81md\xb9:\x1c@\x80E\xa4V\xf5m\
+7\xc6E\xa8\t_T\xa9q\x9c\xe2H\toe\x8bV)\x9b\x8a\xc8\x92\xd4\xce\'\xf0\x10\x07\
+\x00in\xcdi\xb0\x90\x9d\xba\xf7\xa2&\xbd\x94\xc8\xcc\x9c\xc7\x8a@\xfd.Y\xa9$\
+\xe3\x82\xe0}\xbcM~\xa2\x0f>Br\x1e\x90\xa7V\xb3\x06@k\xf6\x82E\x94\xcc<c\xc9\
+l\xf4S\xd83\xa6\x94F^\x19\x15C\xfc\xdeAh$\x15\xc6\xf0\xcf\x04V"\xc0\x89\x18\
+\xed\xda\x0e\xaa\x19\xe0\xbdK\x86\x16$\x9f\xd1\x05U\x90\xd6\x12|\xb0\x0b\xf4\
+\x95\xfd\xe8\xd3\xa0\x16W\xea\xeb!p\x94\x92l\x02\x17i5\xe5\x05\x85\x86\xb2\
+\xc17\x02\x06\xa2 \xaa\xb4\xe1\x8b?\xd6\xd0_\x89\xf2\xd7\x82\x89\x80\xcayn\
+\xde\xf4\xa5\xd9 \xb6\x11]$\xc2AR;\xed\x0f~M\x87\xf3a\x12\x13?\x0b\x89\xbf\
+\xf3G\xfc\n\xcc\x91\xfa\xab!\x800l%\xeb\x02\xd7D\x9a\xc1\x9aF\x1b\x8d\x94\
+\xa0\t\xfaK\xddXd\xe1\x915g\x85\xb9\x99\xafr\xc8\xb2!v+2\xeb1\xcdK?(T\x9c\
+\xe6\x8a\xedG\x9b[m\x90\xac\x05\xf2+\xb9\xfd-\xcb\x9a1\x08\x91\xb5x\x9f\xe9s\
+\xeeMh*\xc5\x80\x01\x1f\x07\xb1\xaap\xa3\xbd\xbd\xac"TN\x05\xf5s`$\xa3\xe3\
+\x11\xe8\'h\xa7\\\xd7\x92\xe7\'C\x8cC#\x96\x94u4R\xf4\x8aO8h\xbf4\xed\x01\
+\xc8G\x82[PxN\x88\xf9\x03\x15\x031$\x90\xc7\x97$\x14\x14\xa2fGt\\\xa8j\xa9\
+\xbbI\xfd\x8a=\x1de+\xb9rk8?\xc4{\x8b\x1f\x99[\xa1\x11\x9co\xff\xe0>\xb2\x87\
+[\xce\x99\x08:P\xd7\x14\x18\x07\x10?\xdfN\x9e@\xf4@\x14\x03\x80&\xfe~\xaa+\
+\x8e_\xda\xef:\x11-V\x08\xdcV\x05ldt\x15\xa6O\xe2\xadT\xf6!Y\xbd.\x1a\x85\
+\xd2\xe9\x81n\xbc\xf7\xe6\x07\x00\xa2n\xc0\x11\xc2\x9b\x0c\xb6\x00\x92\xbd\
+\xe80\xc0\x9e@\xa4\x8eAIO\xb3\xb1\xed\xf5F(\x1ck\x11\x05]\xa03\xa1\xcb\xbd\
+\xbbo[\xda@\x10\x7f\xe9\xa0\x16\xd7\xbaJ\xc8\x08\xc9,\'4\x00swB\xf5.o\x15\
+\xea\x83$\x10\xcc\xb2\'8\x00\xe0P\x88\'\xd3\xf4\x91\x15\x89\xab\xc6\'&z\x92\
+\x82gC\n\x11\xc7#V\x1c\xb8\xc5\xf1\xbe\x9d\x1d\x109\xf5\xaa\x8b\xdb\xd4x\xe9\
+\xc8E\xb8+\x0cvv!\xd0\x10\xac\x02t7\xc5QFu\xb6\x89`V\x10Y\x08q\xce\x1b\xc3\
+\x02d\x8f\x82\xe8\x0f\xda\x1a/r\xf1\xe2\x8c3?\x8cT\x0cZ\xf60n\x14\xcf\\\xab\
+\xd1\\\xd2\xe7\x96]\x8c{\x86\x802\x10<\xb9\xc37\x08\x05I5D\x8e\x1dh\xc2\xec\
+\xb8\xf2\xe0=\x8d\x91\xb5k\xac\xf17\xca&X\xd8\xea\x80\xf3^\x1a\xcaY\xedZ+~\
+\xe9\x9d\xe2\xd21!y\xcd\xa8\xb3\xc5gr\xc1\xeaI#\x83\xee\xe2\xb1x\x89\x00KZ ;\
+\x95\xd8\x848c@)\xd8\xc1&f\xd3\x00t\xc8\x91\xdd)M\xeaA\xe2 U\xedpN\r\xbd%3\
+\xee\x8a\x9dm\xa5\t-d\xe4P(\xf1\xd9S\xfb\x04\x12\xf6\x94\x19\x93\x94\xa8\xea\
+\xc7\x95\xfc5\x89\x00J\x95\xa7[\x92qQ\x01x\x19\x8f\x83:4I\x1b\x95\xef\x17\
+\xc9\xef\x91H\xbb\x80\x90\x11\x99Of\x812o\xbar\xc2\x0b*\x84\xb2\x1b8D)\x92\
+\xcc\xcf\x13\xb4A_\xc0\xb7\x11\x90u\xfe\x0b-\xd5\x93\xd5X\xf2\n\xfe\x14\xa9]\
+\x0e\x17\xe8\xa6s$\xcap@\x93q\x07Q\xaeM#P\xd0P\x88zE\x9al\xbfG\x11e\x05\x02\
+\x00\x03,\xda\xf8\x0f\xb1k\x8b\xd0Q\x04\xb8\x14\x932\xb0C\x9e\x95!\xa3\n\x0c\
+\x96t\x83\x04\x82\xa52\'\xde\xd6\xab\xca*>-\x91\x0e\xf6\xe7\xdb*S\x8bE\x8c\
+\xcc\x1c\x01\x02\x06Y\x12\x81\x10~\xd5}\x08\\Y\x13E\xaa\x9f\x01T<A+\xd4\x9d\
+\xdaM\x9bg \xdb\x14XR\x92L\xca\x99\x17\x80\x1b>\xbevLY\x9cY\x15\x19\x14\xcaJ\
+H\xd6\xc4`\x82\x97,\xf8\x16\x9e\xfe\xc9V\xcb\xcfty\x87V\x0cU=\x94\xd5\\N\x11\
+\xbe\xa1\xcd\x1c\xfb\xd5S\xe3\r\xf6\x0f\xac7q$\xd3\x80]\xb5\x8e\x89*\x83\xd1\
+\xef\x14\x1du\x118\xd7@|\xa62\x1d\xd0@c(!\xd3\xbd \x19y\x06cc[\xc2\x0b\x7f0\
+\xbb\xba7:\x8a\xce\xeaD\xe9\xab\xdbuX\xe2GC"\x83\x83\x04\x12P\xbc\xb3\x82\
+\xd6w\xed/\xdeP\xce\x14`\x05=\xa4\x8a\x11\xbf\xbd\xbc\x12[\xfb\x8a\\^\xbb\
+\xc1\x9d\xc4\xd6\xaeu>\xacx\xebU\xf7\xc4\x04\xc4\x98\x8a \xa4j\xdf\xe2h\xd1\
+\x9dh\x80WA\x00\xf8\xd4\x12\x97b\xdd\xe6\xc3\xdfl\x9b\xccD1G\xa8\x1f@\x96\
+\xdc\x13\xcd\xac\xb5g\x89j\x9fT?\xa1\x9a\x98\xc0o*\xe13\x82\x0e\n\xea\xe1e\
+\xcdG3\r\x1aD\xcd\xa1\xf7\x96\xd0\xad.\xfc\xd5T\x89\xe7\x11\xec2p\xaf\xbc\
+\xb3#\x178g\x94\xb0T\xdbT\x86\xd6Ie\xcb\xc9\x84\x00_\xd5q\xe6\xd6]jr\x15\xd6\
+1\x8f\xb4\xc9\xd9\xc7\x97\xd5\xf7\x18"_\x0f\x93\xa3\xa43\x9b\x02\xfa\xb8\xf0\
+\xd0m\xd1\x9a\x05\x89)\x10z\x80\xe8\x86L\x08\xc5\xed\xb3\xf7\x92\x027B\x1a$2\
+\xe6;8\n\x9aR/\x80P\x93\x17h00<\x9b\x0cy\xa1](\xc3)\xf0\xe3e2S\xeaK\x1a)\xeb\
+$\x9e3\xa3\x8640\x1dE\xf1\xadR\xf8\x7f\x00\xe0\x12$f\xc1i\xe8\xec\x15\x18\
+\x94\xad\x1e\xc0\x86\xa3\x87V\x89\xc1v\xd8\x1d )\x99\x88\xb1\x9c\xbcb\x9d\
+\x06==\xff\x00]{\xd5\x9f\x04\xdaAl!A\x92\xd4\xeb-\xb8\x03\x1c\x014\x8b\x8b\
+\x7f\xb0\xf4j\x0c\xb16\x06T\xe74\xd0v\x94r\xc6\xa9\xa9t\x03\x02\x0c\x0c\xf1z\
+\xf2\x0f\xe7Kg\xd5k\x11\x90\xcf\xa7K\xcd\x03DMP\xf1\x9a\x86Z\xdfG1\x90\xfd\
+\x06\x90=\xba\x1d\xddB\x92\x85Huru9\r\xfaR{\x91{C\xd5\x98Y\xa9\x12\xea\xcfK\
+\xc7?\x00\xa4yx\xdeG\x1d\xb5a\xb4\x9f\x19\x8c\xcf\xe4\xf6\xae\x829\x85\x86\
+\xa3d;_\x8a\x1a \xb2"S\x00\x0e\x05\xe1Kp\xf6\x15w2/\xc0(\xf0`\x85b\xb4\x19r[\
+A\xa6\x93\x94~\xbd\x88\xd6\xfc\xc1^\xe2\xa7l\x9bK\xc8\xea\xec\xa0_\x08\xdde\
+\x8e_!x\xbd\x1a\xc3\x13\x13d\x89oP\x08\xda#\x8f*\x97\xdcE\'\x06\xbbX\x19o\
+\x91=MWjT\xdd@\x85\x14s \xa5Z\x1a6\xa4\xc5\xa2!\x11\xa0\x04\xa7\xfd\xa4T\x14\
+\x9f\\Bd\xe7UbA(\xd4-D\x83T\x1d\xf9\xd8{\xf4f\xfb\xb0C\x10\x83\xd7p`\x80Ww\
+\xec\x11\x1f(J\x1b\xee\xa4\xa4\x0e\x84r\x0c\xe1HSD\xd9"i+\xb9_ \xfe9sq\xad\
+\x02\xa0!\x80\'f\t\xa0\x89\n;`\xd2\xf9T\xc4\xbe\xb4\xb3D\xf53\x95\x06g($\x1c\
+T\xb4(\x1b\x02\x0cL\xbf,\\\xfa|\xbcr\x84:\x87\xec*\xb6Z\xb2\xfaVr\x15\xdd R3\
+\x05\xdda\xc5\x80\x1e;!z Fq?\x80$\xfb\xe0\x17%P8\x82\x03A*\x00\x06t\xb5k7\
+\xdfg\xd2\xebN\xe5z\xf3\x06G\xd4/\xf2\xa7\xea[\xb6V\xc9(\x88\x93E\x16\xf4y}\
+\xba\xec\xf9`\xd4\xdevB\xe25\xb3\xe1\x8c\xfeON \x86B\xd6\xd3\xd74\x06\xafK\
+\xf6\xa3\xc7\xa9\xe4\xc6*\xd1?\xf4D@Ju)\x039(\xc6(\xf7a\x11\x7f\x17\x11\n>W\
+\xaaO\x886\x98f>\xe2q**\x98\xe9\xc8\xb6\x12\x8a$J<^FU\xef\xe8\xd26u\xba\x1c\
+\x96\xbe\xed\x1b\xa4\xb2>\x15V\xe78\xfdu\x00\x8d)\x95\xaf\xf5\x0cj\x18\xd0]\
+\xa6\x90\x14\x00\xd7`\x89\xd6|\xa4\x85#\xc0\xe9\xd7\xe69c\x17\x13\xa9\xac$\
+\x8b\xec\xca\xa5\xb1c0h\xf2\xadC\xa0\xa0K\x89\xa3\xe3R\xfe\x8a\x17\x9a /\xa0\
+\x87\xec\xf2:\xc0\xd0\xa854\x8b\xa1\xa9\xc2\xb5\xc1\x87\x95\xa6X|\x1e\x85\
+\xcf\x8cd\xe8j\x10\xa3\xfa\xdfq\x070\x06\x02\x96\xe0A`zf\x0f\x99\xf0\xec\x10\
+\xc1\x17\xc8\xa5\x1b\xd7\xfe\xc9\x0e\xa5\xd8\xf8\xd4\x9e\xfa5\x00M\xcb\x02\
+\x89\xc9C\x8af\x10\x87J2!\x19$V\xbba\x1d\xd8\x14\x80\xdf\xd2\x0ePB\xa2\xc2\
+\xdeu\x97\x1d\xf1\xb9(\x81\x11PH\x15\xbb\x82BA\x12\xd1\xa1\xc3\x8b\xeb8\x182\
+h\xb6&\xe7X\xd0 >\x98][\xa1\xa3\x7f\x14\xc3\x1f"\x99CE\xffw\xfaN5\x0b\x02\
+\x07\x81$.\x1c\x06;\x9d\\\x08/\x00%+\xa2\xa3\x99s\xba/@\x01\xd3\x848\xc0Duz\
+\xa86\x18\xb9\'g\xd8\xad\xcch\x82\xa1K\xa0\'\x8b|-_\x8e]\x96<A\x92\xc1\xd1ZV\
+8\x10\x04\xdb\x8a_X\x0c\x1e]8NB\xe6J\x15q\x99\xa3\xbb\xbeM\xaa\x1c\x11\x9a\
+\xfe2\xd7\x12l6pE\xca\xd7\xd0L$0g\xcd\xd18~\xaf\x8f|\x94\xa3\x16\x07\xc5\xd7\
+\xe1\x06=A]\xbf\xa4\xeeu\x1cx\xe5\x0fv\x009\x8fR\xdc&_\xa9H\xcb\x96\xe2\xd4e\
+\xcb\xcb\xecbt\x05\n\xc3\xef,\xc4"\xde\x1b\x1bH\x05\x9e\xa8854(b\xe7\x1e\x01\
+0\xac\x91\x1b\x0f\xd97\x14\x92\xba\x00\xc2\x9b\x04\x08\x17\xe9\x14D\xc2\x81\
+\xec"\x1a\xedyC\xba\xa2\x1c,\xa5\xe0IS\xca\xc7Y\xe3(\xd0\xb6;l\x1f\x90VIz\
+\xb0\x0f\xceT\x10\xe4U\xa8\xf0\x01\xe2]\x94\xba\x18\xf5\x1a\x02\x94\xbaH\x0e\
+\x1f,\xaeVP\xb6Q\xa8\xf0\xf8\xb0b\xef\x93f\x9cR{\xed\xc5U\xa8F\x8d\xa7\x84(\
+\x0e\xac\x8c\x1d\xea\x0c\xcd!\x892\xd3C\xf9\xcd0\x93\xae\xbd\x07\x80\xc6#\
+\xdb\x8681\x03\xe7\x87\n=\xbd\xfb\x18\xd3*\xaf\xb1\x8a\x01P,\x91\x02u\x04\
+\x81\x81J\xf7\xbf5\xfd\x83\x06\x11\x1dU:\x16\xa0\x1b|\xf2\xac\xf7P\xad\x90\
+\xfcT\x07\x03\x9d\x81^+^/\x0f"\xc4\x17U5`x\x94\x96s\xa4gt\x08\x07(\x96\xd0\
+\xc9\x12@\x03w\x88\x9e\x93J\x9c%\x04\xca\x8e\xeai-\xc4QA\xa4\xed\x81\xc4a\
+\xfb\xc12\x85\x9a(w5\xbdP\xed\x1fcf\xb2\x08\xc0\xf1\xaa\xf8\xdb`\xf9\x04\xd4\
+\xbf\xb2\xacs\x9b3\n\x91\x1a\x85\xfea\xb8R\x1aG\xb2K\xd4H\xa6\xd7\x83V\xe5\
+\xb6#v\xba\xe1\x93\xcb\xe0\xd9\x13\xe0Z\x11\x89\x8c*\x1c\xd8\x08 _\x90\xb2\
+\xbfE \xdf\xc3\x1c\xbf\x8b\xc8\xb8\xc15r\xdb\x8eG\xb9\xa3\xb1C\xbd"\xbag\xe0\
+\xfe\x99\xa6\xb2HwR3\xa3\xf5\xb3\x98G(z\xc0|\x87\xb5\x1a\xed\x08\x0eT\xd1\
+\x9a\x0e#k\x10\xceRB\x80\xe43\xff\x8d\x896\x18\xcb\xc6I\x89:\x1e]\'e\xf6\x86\
+\x8f\xd5\x0b\x1e\n\x06Js\x8b5\xa9gF1\x04\t%]*\xbe\x9fUfS\xbc7U:=\x85\x89\x03\
+K\xf1\xba\xf8!\x03\xd0X\xd4\x8b\xef: \x17H3\x07N\xbd$9Z4T\x9eE`\xef\xcb]A\
+\x90*\x99\xd4\xc3\xee4g0\xcf\x84\xc75\xa0>\'\xf6 ?\x1dH}\xa2\xb9\x8d,\x15\
+\x04\x0eL\xe2\xc2\x0b\xf1\xe0u\xd9\x1b\x9e\x15\xc9L\xfb%\xcc\x0e\xb4b$\x94\
+\x91\x81(9\x99w5\xb5AEpD\xbb[\x08`\x19hG{^X\xf1\xb0d @\xd1\x86>IuZ\xa9,\xe5x\
+\xad \x80A\xde"&\xcae\x17\x8b\x84)\xa0w\xfd\xdc@\xbc\xfeo\xbdQ\xb4\x8a\x8e\
+\x16\xd9\x83\x8c\x06\x87\x18\xc7%\xcd\xd0 \x1c.,\t\xaco\x15=\x8f\x06(;\x12b\
+\xa1\xca:]^{\xe0@\xc5\x81V\xb4\x91\xb2\xa5yM!\x93\x84\xaay\xa0\x97\xfb@\xc6\
+\xceP\xef`\xcc\xeaE\xf0\xf4\x0f\x8eCj\xa0\x88\x00v\xcdb\xe2\x17^\x03o\xd8?\n\
+\xec\xf6\n\xac\xd3\xa4sk5\xba\xd4\xb7\xddb\x11\x8bu\xecW\r:0\xd080\x9f:\xf0\
+\xd5\xb3\x07\xa1I\xc9\xa5\xe22)\xbe^\xc9\x01\xa8\xcb\x97-^\xd1\x1cE\xd5#\x05\
+\x10>\xb9\xa1\x14\x91K\x935+\x9f\x85o\xcci9\xb57\x95\xd4\x07E\xe1dl\x11H\xd7\
+\xbd\xea#\x92\xce\x00D\xb6\x14#\xacL\xfc\xbb+s=\x98SU\x1d+\x8d5\xc5\x15\x97P\
+\xd0\x1c\xc3\x1ao#\x1f2\xfa)\xb1)\x0c\x19{\xb71\x1a\xc3\xdaT\x0b\xe5\xe4\x0c\
+\xa6w~\x86\xb9\x00{\xdf\x17\xb24E\xd0\xd9V\xb2kFW\x85]\x9e\xfe\x16\xb2\x19Uv\
+\xcee\x1f\xdd\x9b\xc5A&\xbfN\xb3\xa7\xd5HK\x05v\xc3\x1d(_)\xad\xdeED\x0e\x7f\
+e\xe3\xf0;\x81E\x91\x0e\x07B\x9c\xc3\x9d,\xc8oE\xc7?<\x92\xd7\xbar\xef$\xaf\
+\x07\x8d\xfc5S\x03\xa0\x8dl\xa7\xe7\xea\x141\xb4Ek\x8csj]\x83\x8e\x95R\xfd\
+\x1d\x8d\xb8W\x14\xffQ\x06ZS\x16^\xe3\xd57t\xba9\xfe\x0bU\xeb\x95e\xb8\xc5nz\
+\xfaS\xc1\x91\xaf\x7f\x06\xb8H\xca+\xb38\x85\xb8O\xfc\xa5\xd4>\xcd\x8d\xa1B\
+\xf7\xf8 "\xc2F\xc3\xb0\xcb\xbaO\xab\xaaX\x8f\x03\xe0\x87\x01\xea\xfa\x9f\
+\x8d\xc7\x8cG\xa3\x96\xcf\\\x0b\'PZ\xa4\xfcBT}`\r\x17sH}\':\xb8\x99|\x91\x91\
+1H\x83\xf9\x05\xea\xc2W\x1a=S\t\x83\\nT\x7f9=\r^\x99\x1b\xfbQd\x9c\'\x86\xe9\
+\xb4\xacwP \xa0\x8f\xba\xaa\xc6P\x1eW\xebAMK{\x93U)\x8fbB\xcf\x00\xee\x9e\
+\xd5\xf0\x1e\x98J\x18\x88\xc9V\xdf:\xa2g\x8b\xcb\xa6\xa5v\xf4!0\xad\x0b$n\
+\xd4\x8eb\xa3\xc7\xe5^\x80\xc0:M\x90\xa2x\xc5\xff\xe9\xb5\xf7\xd7\x10vv\x8f\
+\x02\xb9eP\xe0\x83\xc2v\xf4V\xb2c\x17p \xbf\x95\\z\xb8\xa2\xac\xadQN\xe1\xc6\
+\xebk\x85\xd9M\xd9P\xd2v\xba\xb0\xb2\xb7\xe0\xe4\xc9E\xbd\xc2\xe1[\x88\xab\
+\xe30\x00\xa1\x19\xaf\xbb%\x03c%\xb1\xde\xb5\xe9\xc3\x04\xe8o9\x07\xd1\xdd\
+\x9e\xfa\xfb\x92\xb5.X\xab\x94d\x1dI\xcb\x80\xae\xf9\x9a\x8arC\x01\x84\xe4\
+\xc3\xb6z\x8c@\x87XN\r\xe4\x11\xc0viy\x1d\xfd\x87h\x84\x029\x8b@`-\xba\xcf\
+\x98\x05\xe4\xdd\x026\xd0\xab\xa7\x88I\x9ar\t\x80fs\xb2\x94\xcc*\x87\xac\xea\
+I\x94>Z\x8c\xb7xM\xc1#\x93\x93\xf1<\x0c\x04\x0c\x1f0\xeb|,\x18\x95v\x8a3=k)\
+\xd9\x88\xc83w\x05;R\x85\xb5\x14<\xa0\x1bM\x96v\xb7\'\x95\x10\xa6\x00\xb4\
+\x04\x1d\xaeaf\xe9CV>\xa2b\xdfP\xa9\xa8\xc3_\xb2\xc2wzS<\xe5\x98]\xe7\x96\
+\x08\xd5\x1e_\xb5\xa0\rn\x9e\x01\xa0]\x00p\xc1\x81\xd7\xdd\xc9\x9aH~\xbaD\
+\xab\xd0\x03h\x9a\xa0\xd2\xd3h\xc7\x95\x96\x99Xq\x88\x18P\xed\x1b"\x8580EO\
+\xc0S\xa1\xf9u\xd0\x89L:\x01\xa5\xa3\xb5\xa5\xce\xdc\xe2\xa0\xaa\xdd\xb4Z\
+\x08\x05\x0f\xd6BP\xb1\x16\x04\xf0\xac9\x00\x0fh\xd6\x83A\xd0\xad\xcbTU\x99\
+\xe6\x04\xca\xe6AG.\xa8\r \xe2\x96\x9ck\xe5\xd0\x1d\xfb\xe2\x0c\x03B%\x02|\
+\xfc\x9f\x95\xf38O"VsVG^\xf7\xf2\xd5s\x14\xea]\x8a$t\xdc\xb9\xb6\x15\xc0m\
+\x0c\xdb\x16\xc41\xec\xd3\x02\x94[n\x0e\x000Y\x96EEc\xe4\xe7PPn\xd3\xacs\xb0\
+Q\x18\xf8\x94\t\xdb\x03\xa1\xa3*\xf6$\xcc\xf4\x9a-\x83l-\x89\x02\x1el\x15\
+\xf5B\x02x\x0f\x13\xd6\x16\xa9u\x0c\xc9\xf8W\x82\xdc\t!\xb2\xec\x01\x03<\xea\
+;\'\xd9\x13:\xd6"\x10#\xc5\x10\x10z\x89O \xa8\x0eX\xb9\x13P\x83\x03"3\x1a\\\
+\xf7\xbdn\xe4dU\xd4\x98\x11\x7f\xabH\xbd\xd63m}\x00\xd0\x0c3\x95\x019\xfaM\
+\x0es\xd1\xb6\xcf\x17R}\xfb%J\x87\xe0\xa4\xf8\x16J\x02\xd6\x05\xde{\x00\x82\
+\x02E\xfc\xc0\x10P\xea}s\xc9V\xc3Aa\x13\x01y\xea\xad\x8a\xceM>E]\x12\x01\x96\
+\x83\xa8\x0f\x1f\xfd\xa0\xa9\xac\x89\x14aWvn\xa8s\x86\xb1;\x808\xc5_\xa2\xc4\
+\x1f%\xad\x85\x01\x96r\xea\xa9\n\x05\xb0R]`\x06ix\xf8-\xda\\`{\xbe\xd6\xe3\
+\x03\x0c\x01YS\x86\x9d\xa3(\x18\x07\xed\xf1\xb2^\xad6\xa4\x9f\x1d\xf1\x96\
+\xca\x17I\xd4mA\x1f\x9e\xb2\xde*\xc2e\xc70Om\xbe\xb6\x98T#\xfe!lDT\x065{\xfc\
+\xb8Xw\xe1\xe8\xae\xb1\xc1\x83\x8a\x00^P\x1f%\x84\x9a\xfe\xf0\t\xad\xc9\x0c5\
+\x05\x9d\x82\x84\xe2\xa2I\x17\x99\xbe\x08P\xc6\x00\x00\x117p?\x80\xf9\x03\
+\xb6\xf8Wkm@\x17\x7f%q2%R\x1e#\x94\x04\x19\x02D\xbdV\xf5\xdey\xd7\x144\xb6\
+\x08\xc8\x1900@t\x8b\xde\xe80j\x95uQZ\x93\xa7\xcbL.W\x80\xaay\x1f*\'\xf2\x07\
+\xf0\xe0\x8b\xa3\x7fU\xd6\xe7\xb5\x81\xd8\x87,u\x14\xf2D\x08\x02Ls\xac\xec\
+\xd6\xb5<\xb2\xe3ez\xa7\x94\x9c\xddRV\xae\x02<I\xc8{\x0c\x11\x03i\xf3\xc2\
+\xccLL\x80q\x93\xd9\n\xd2\'Z\xf9\xe3\xf1\x99c\x9c\\O\xad\x18X\xa6\x1f*\xab\
+\x8bm\x14re4<x\xc7g\x07J5\x19\x04(\xfe\x81\xa77\xa2Ju\xe2\x9f\xcb\xe2\x8d\
+\x9cI#qW\x8f\r}\xc4\x8b\x89\x00>g\xab[x\xe1\xa9|gM\xc0\xa86\x91\xe6\x93G\xf0\
+\x94N\xc3\xed"\xdc\xd7\xcc\xfd\x1a\xae`{8\x8am\xc7\xb1\x0f\x18\x92\t=\x8a\
+\x07\xc0\x8e\x9eI\x91\xd0,T\x8e\xc8)\x83?\xd5-0\xa1y\x00\xf9\xa96\x14\xa5\
+\xa5\x96\x89\xd7\xb9s\xdf\x158P\xa9\xd6\xc9\xa9\x9c\xc9\xf3)b\xb7{\xa5\x93\
+\xd1\xf1\x11Wr|\xfa\xab\xfd\xc1\xef|\xc3\x05\x1c+\x16h\xe6\x97\x990\x9e\xb0\
+\xe5\x8f\x97 l\xa4\xfa\xb8.\xe0\xde\xcc\x8f\xb6"\x142\xf2,m\xecF\xca\x19\r\
+\xa1 \xc0\xa0\xaa\x08\n\xf7\xb5\x08\xdd\x19Cj\x12h^\x8c\tL>\xd1Ut,\x1d\x89\
+\x98.\x9d\xf3\xa7\xea(\xb2\xed\xb7\x04\xc6R\x18=^\xa9\x8d\xb0`\x1a\x02\x81\
+\x80\xcchsd\'\xc3\x0eC\x99\x7fk\x85G\xee\tk\x08\xf0\xf8\xca\xb4\xd1x<\xc7[u\
+\xb1HXmM\x03\x18\xa6\x9a\x0e=\x9e\xbbC<\xb7\xee\x1a\xa8\xa9\xb6\xd7\x02\xd7\
+\xe3\xa0\xf7\x8ei\x08P~E\xfc\x9cZ\x87__\xc0\xc1p\xe2\x07\x0fhO/C\xd2\xf3\xbe\
+\xa5?\x00\xfc\xd6\xd4\xa2\xe4\x02=\x8bh8\xf6\x96\x93\xc0P\x15b\x98+\xe0\x80\
+\tF\x18\x05\xd5\xb8\xb9XZ\xd6\xc7\xe3\x14\xe5\xcf\x0c\x83\xfa\\Nf\xa5%\x9d\
+\xf0\xc7X\xa78\x80\xbe^\x82\x16z\xcdEm\xef\xcf,P\x13\x9cx\x02\x89\xdd\x98\
+\x10\xc0=D|\x02\xea\x94\x99*\xc4y-M2\xa0\x97&\x976\x022\x85y\x8ajJdq\xe1\xfe\
+\x7f\x0b^\xcc\x02\\\xfc\x80\xb5 \x00\xb2\xd1\xa51c\xfa\x04\xd9E\x80\xccT\xc1\
++\xd7^\x17\xbd\xecS\x97\x99\xb9(J@r\xc1t\xbc\xe7,\xe0TM\r\xc4+`\x08\xc8\x8c\
+\x9e\xb8^\x102E\x95\x00\x15n\xddp\xa1 ~v\xad\xb8\x03f\xc0\x19\x1b\x14\x99\
+\xe0$\xfc1\x89\x86\xf2TR\xce\xd4cQ\xb6\x14\xaa=\xb1\x8b\xa2$\x02X\x15\x06#\
+\xf5\xca\xb6\x1e\x01\xacT\xc6\x90\xaf\x9a\xb1\xc8\x88_+S\x13\x18kx\xb1\x88s\
+\xedZUO\xde"\xefIRe\x9d\x9a\xdf\xe2\x1d\x97%\xedP\xef\x91\x0b\xc9T?.\xc5\x88\
+\xa0\\\xd4\x1b0\x94S\xad\'g\x0c\xb2\x84fi\xf7\xfaL\x18%\x98P\xeb\xf0\xcc\x15\
+\xec>\xaf\x00]\x18\xe6\x8cD\xc3\x19U\xaa\xe8p\x00\x01\x13\xe1\n\xd0d\xb2\xa7\
+V\xc0\x16\x01\xa9Y\x81\x03 Z\xa9\x87\x17\xd5\x96W\x10\xc0lA\r\x99\x8eKiw\xbb\
+\xe4Z}P\xbby\xa4b\x9e\x82VN\x9d\xb53\xea\x16\x06y\x046\x07\xed\xafbU\xbbt\
+\xaf\xfc\t9\xb0j\xa8|\x1d\x9f\x1c\xfd*\x97\n!\x93\xba\xa1$\xbaHcW7\xdd\xea\
+\xaa\xcd\xc9\xce\xe8\xa2\r~\xc8\x03\xb1\xd9~\xc8}\xf1\x82{*D\tH\xaa\xc1\x8a\
+\xdaX\xa5I\x98\x82<Yd\x8b\xd0V\xff\x11\x02\x8e\xf4\x92A\xa8\x8f\xffR\x19z\
+\xcd\xb5\x00\x00\xbb\x84$H\xc6\xbc\x1a[`&\xf3\xde\t\x15\xf7\xc2\x01D7\xd5J\
+\x909\x8b\xd69\x1cI\xaa;T\xc2\xd5:\x11\xa0\xa1\xf33\xaby\xf4NY\xaeaU\n\xc7\
+\xbbG\x90\xc6X\x1dR\xbd\x0ew\xe0P9C\xc2\xf9Ak\xb2f\xf5L\xdf\x99\xbb\x81\x1aD\
+J\xc8\xe4\xdc\xd9\xedn\xa2\x95\x8f\x94\xd9\xf3/v\xf1.[\xd2Y\xf9\xee\xf5\xe2\
+\x96\xbe\xf6s\x98PU\xf1\x8a\x83:\n\x8a\x8de\x83\x04A!#{\x95\xb6\x19\x9bw\xc9\
+\xb4T<\xbf\x8d\x91M\xb8M\xd2v\xf8\xd74\x93%\xe3`\x874\x14\x18\x0e\xcaE+\xd6C\
+\xacr\xf6\xb9Pf\xb6\xd4\xb9\xab\x813B\xb1\x1d\x13\x96\x89\x87\x98\xaa4\x81\
+\xd8{\x9ad\xdcf"E\xaa\xbap\x8eggr\x9d\xa0\x10z\x8fX\x8d\xb0\xc4A\x97\xd7\x9b\
+\x9d._\x00\x89\x93d~\xf5\x85M\xe1\xd1\xc5rK\xe5k\xca=\x1f\x12\x9cJ\xd1v\xc7\
+\x87\x8d9\xa7\x05^\xf4tuL\x9b\xd9TO\x12\xba\xce\xcc\x9f6\x8e+\x05A7T\xbd\x8d\
+\xbd\xa0\x06\xe2\x05{I\x07h\xb2\xc7\x05\xcb\xdf\x8d\xa8^\xb9PY\x02\xda\xa1\
+\xd9#E\xc6\xd2MVQ!\x8d\x97\xe3\x86\xd1)6(\xe3{f\xf7\x1b\x899\xc0e\xa3\xb4\
+\xed\xba\x1d\xc6\xbc\xa1\xc3\xb8\x07\xa9c\x87`\xd9\xaaOw\xd7e.\xfa%\x03c\x85\
+\xc0\x83\xb7\x06^f<\xf5x\xe3&w\x05\xa8\xb9\xf0\xfc\xa0\x8f\xb3\xc83/AS\xa5<\
+\'\x9d\xb2\xf6z\xbb\x8a8TB6v\x19\x07\x82\xb5\x9e\x86\xca\x1a\xe89\xcd\xa6\
+\xe9\x7f\xb5\x91=>\x1a\xd1\x12\xb1<\x1a7\xc4\xa2/\x12\x85a/\xba\x04\x8c\x828\
+\\\xaby\xe4\xd6]*\x9e\xbe\xf3,\x0f\xa0\x9d\'\xe0k#\x061\xb6e\xccx\xa9bB\x7f\
+\xecW2\x94\xecFO]\x1fGK:v\xaczk\xdc\x04\xa8\x08\xa0n\x01\xf9\x02\x9bZ\xb7\
+\xea\xd8\xb1u\xc7\xdcxQ\\C\xab\xb3-\x02jP\xdaVz\x02\x894\xd2u=I*\xca\xe9y\
+\x93C\xf3\xe0\xac\xe2\xcdu\x95P\xe6-\x7f\xde\xa2\xc9W\t\xfb\xbb\x0e2\xe8.o\
+\x85h\x88\x87\xa8.\x1d\x89;c\x83\xbeJq\x16C\xd914\xbc{\xcaMd4\xdb\xb9}5v\xac\
+\x8c;\r6 \xb2\x84<\xfd\xa5\xbce\xfb\xae\xb3\x11T\x19Ty\xa1\x9a\xa4\x11\x04\
+\xa0p\x1eXU\xb2\\\xaf\x10NU\xf3\x8d\x98P\x18\xea\xf5]D\xa9\x06\x18\x0cB\xa6\
+\xa1\x83\xe00\xbe\xb3Cn7\x93\x8b\x02U\xaf\xae\x13\xf72\xfcY\xf7\xd0R\n\x19\
+\x96\xc8e`\xc4i\x19p\xbf\x93Y\x89\xfenl\xc4Lb\x98\x0f#\xd2q\xe6{\x0c\xed\xfe\
+\xe1j\xc0W\x8d\x08J\\9K\x0b\x02_\xe1N\xd3\xe75\x05W=\xb8\xf8\xb1\x17\xcc\xbe\
+#?Z\xb12\xe5\xd0\x08\x9f5(\xe1<\xad\'\xc5$\xfa<\x80\xf7\x0c/*H\xbb\xb8\xa6<\
+\x9a4%\xcemS&\x14\x8d\x84QJ\xd5\x07`\xcd\xe5\xeaj\xa8\x9a\xec\x95v\x7fk\x8dZ\
+\xdd\xe4\x16g.\xb3\xa6\x9ag\xaa\'\x11\x880X\xb4ok\xb8n\xda:(\xb92\x89\xc5R\
+\xf0I\xde>\xfd\xb3V\xac\xaa^\x02\x8e\xeftK\x1e\xfea&Pq\xcd\xd3L\xff4\x82\xfd\
+\xdd*\xec\xa1\x82\xab$\xe1\xc1\xd7\x8f\x8d\xa0\x80\xd0\xffF\x1d\x1a\x02\xe2\
+\xad\xf8\x17d3`E\xa9\x1f*\x0c~\x9f\x90\xda\xe9\xe9\xed\xaa\x04\xaa\x12c\x8d\
+\xa0\n\x1e2b\x1f\xa2\xe6\x96\xbe\xae%Z)\xe5k2\xcf\x10\xe4\x03\xc6\x81B\x93#@\
+\x9ai\x0c\x81X\xbf\x07\x18\xf0R\x1a\x02\x88\xcd\xc5\x84\xb1\x1f\xc3\x06.\xb1\
+qzkW\xf4JV\xd0Z\x19\x11\x10\xe2\xd3w{{\xcd\x9cZ\x8b\xaa\x9cPMU\x8b\xf1\xdf\
+\xd5\x11%\xa5\x80N\xb7cBS\nY\x95\xf9\'\xf9\xd9\x98\x96\xc4p\x85\x1e<\xfeL.\
+\xd5\xa3B\x0b\xf8\xa2\xe1\xe0\xed\xb1\xc5\x1fC\xda\xd4\xa9^\xf6W\x97\x17\xcb\
++\x13m\x86\xbcSP\xf1\xed>\xab"\xec.:\xf0\xc1r;W\xec\xb5h\xd3WAK\xda\xd6\x80\
+\xdf\xe3\r|\xcf\xa7\x88^\\\x04>\x93\rQk\'K\xf7\t\x0f\xe5\x96\x11\x1c(\xdc\
+\xa3J\x82x\xd4w\xc6g\xde\xefX\xe7\xc7\x15\x10\xc0\xa7\x97\x99\xbb\x18\xe7!\
+\xa8\x06\xb6\x84\xcc]K\x1b[\x95\nL\x9a\x02\x91\xcc}\xa7\x04\xbcnv\xe0Y~\xcfo\
+\xbd\x8c\xe2U\x00\xcb9\xa4\x07\xa0B\xc1\x93T\xf62\x9f\xfcK\'z\xb0QB\x80\xe6\
+\xe4\x8a\x19cP\x00\x1ep[\x0c\x15Tb\xcd\x875\xf3l+\xbc\x14?\x83\x18bMY\x8bhx\
+\x11\xaf\xdc\x08\xc2\xd8D\x10%\x81~<h\xa3\xb3\xd6\x0f\x90[#\xf8\x9a\xce\x1e\
+\x95lC\x84J\x04\xc8\xe0\xc4\xadP\xbdA%\xd4\x1c\xd3gxG\xa7?\xa2\x19TIT\x1eL\
+\xf7\x8ca\x0f\xe9\xdd\xc6\x12\xe2X\xb2\x80\xe5\x8e\xd3\xb5\xa1\x1fs\x1a\xfa\
+\xb9\x9f?\xa86F@!\xd3\xb3\xe8\xc1\xc2\x1e\x9b\x83\xfa\xc3\xa9\x8c\xb0]\xa5\
+\xbe\xda.\xb9\xe2\xb94\xbcs\xb1i\x9czv\x8f\xa3\x1c\xbd\x8e\xf4R\x99\xc6\xb4 \
+\xa6Y\xd7\x0c\x01\x07L\xae\xfd\xab\xf6\xd6\xe8j2\x04\x07E\xaa+1(+\n\x16\xde3\
+>cD\x00\x93\xbca\xac\x11I\x1c\x1a\x9a\x98~\x85\xe7BQ\xeb\xc5\x7f\xb4\xa0n\
+\xa7+\xc6\xa1\xfcj_"S\xfd\xb2\xd0\x0c\x91lYz\x9b\xd1\xff\xb8;\xe4H\xcd\x8a\
+\x80\x10\xd7,\xb8\x92\xb7\xab\x95\xc5U\x00\xda^T\xc0\xa1\xd1\x8c\x00\x00aQ\
+\x0c\xa2\xe1\x10\x00e4X\x9fw\xcf\x8b3\xf7\xc8i\xe47\x1bj\xc5\x1fC\x06\x8aUr\
+\xb5rj\x8b\xbf]B\x84\x02^\xad\xc0\x05\x19\x9dD]\xeeN1\xee\x1a\x8d[\r\x08\xa8\
+0\x9e+\xc8\xd9`\xd5\xdbA\x0bv\xce\x0c\xb9w:\x8aP\xe1W\xb745"\xac\x90\xc5m\
+\xdb\xdbUc\x12\xdf\xf4\xb10Q\x0c.L51B\xba\xeb\xba\xa5j#[\x83\xd8!\x933\xd23\
+\xc4sL\xa3\x9f\xbc-\xb6-U\x83\x80\x15\x07!\xb0p\xcb?\x8fU\xab\x00\xb4\xb9_Jw\
+\x00\xfa\xa1\xbdb\xcf\xcb\xceP8\xc2\x838+N\xb9\x80\'\xc83\x97\xb4r\xa0!M\xe0\
+\xb0\x9c\x01\xe3\xd5q\x8d\xafM\x0b\xfb"\xa0\xbf2\xa0\xb3W\xad\x02\xeb\xb8H\
+\xcd"`\x1a^\x8e\xd1\xf2\x00\xe9\x90h\xdd^\x1a\x1a\xab\x99v\xaa\\\xa5H\xa7\
+\xa1\xa8\x18\x02\x0eJ\x93\xd18\xe4];;0\x96\x8dY\xdfH\x0c\x80\x85\x80\xca \
+\xfag\x88\xaf\xbaT\xd4\xa2\xe7\'\xd1>E8\xd1dH\xc7\xc1\xac\xa7G\x04aR\xde8\
+\x9f>TPa\xce\xa2\x11\x0e\x8c\x10\xf4Z\x9b\xa5\tb&.\t\xd7\x8f4N\x995\x0e=\xeb\
+\xab\x1e\'r\x8eWx\xe07\xbf\xe1\xc2\x0c\x81\xac\x96A\xa4\xeaQ9f\xaf\xbdg\x1e\
+\x9aQ\x9d\xd3\xae\r~\x05o]\xd0\xc6}F\x17xb\xdf\xe2\x87\xb0\x98\xbc\xd9\x1fS\
+\xfc`!\xa0zCHozd\xc7X\xfc\xa7\x96\'ZD\x873T\x9cX\xc0d\xf5%\xb3Z\x86\xf9p\xef\
+n)\x96t\xfa\xb6\xa4\x88\x8a\xc2\xc4)\xdbC\x85`\xdf\xa0{\x99%\x1fs\x80\xfbGA\
+\xc0j\xa85YeE6\r\x82\xe0\xe5\xb5\xfa\xf6\x90"&\x0c\x1b,\xb5?[\xbd\x11\xea?3p\
+\xa9<=\xac\\\xa05}\xa7\xe6I\xadhD\x16\xa1\x00\xc0\xd4\x01\x1d1\xebY\xcfze@\
+\x03\xfb\xee\xc7\x10\x0bv\xacH\xc4\x84\x89\x83d!\xf9\xa8c\xfe@\xcc\xc7\xa6\
+\xec\xa9\x80\x16)\x9a\xa0!\x00\x88\x96V\xd4\x00\xf4\xec\xd2B\xce\x84re\x03\t\
+\xa8\xf8\xd3m\xe8\x15\xccs\x81\x0f\xe4\x82\xe6\xf3*j\x9fw\x90\xee\r\x0cw\x8c\
+\xdd\n\xd6\xe6\x0c\x00`?\xcb\xc7);\xb0\xd6e\x11\x80\xd4\x85\xc9q\x9b[\xe5C\
+\xb1\x05\\&\xc2p\x00J7\xcdN\xc3(K1pK\xa6\xe6\xc4\x15\xc6\x11\x9c\xa6\xefF\
+\xed\x8c\x17\xf0x\xc6o\xd1_\xc9\xce\x83\xeaI\xc4\xfbP\xb5\xaa\x1b\x01\xa1\
+\x00=UH,\xbf2\x0b\xb0\xfc\x00\x8bAZ\x95\xb6\x8f!\xafpL\x9f\xce\x89\x00j\x82\
+\xae\xd7\x86\xbeU\x1b\xb3\xc4\xf4\xacG\x0c\x99W\x02\x10RQ\x07\x10\x11\xe5\
+\x10\xb0\xf2\xa1_X\x1f\x04\x88X\xc0r\rf\x80\xd8\x06\xcen\x01\x01\x19\xceu\
+\xdf\xc1TM\xd4\x99\xe0\xbcvM\x00\x92IU&\xed\xec \xb7\x01H\xc7}f!V\xdd\xda\
+\xa9\xc5A\x85P3\x93:\xc6\xaa`"K_\x95\xa53$\x92\xa0\xe0\x15\rkk\xd3\x9a\xaa\
+\xf5t"mg\xd9\xab\xea1\xa1\x17\x0f\xbb\x88\xded]u\xbf9\xeda\x8f\x8c\xaa/\x94\
+\xcb\xc0\xa6\xa4]\xf9\x80\xe4\x8b|\xd9\x0e\xd4U\xad\xaav\x95\xf9\x81\xc5^\
+\x8d\x80\x94\x01\xaau\xa8O\xd1\xa4\x84\xb5\xd7\xec-K\xccb\x9b\xbcbd\xbb\x82l\
+\x8b\xb6\xbd\x1d\x0e\x01\xe6\x0b\xe5\xc4x"\xea\x87Y\xdfb-\x80\x8f\xce\xf1BeH\
+X\xd5(\xb6\x9a\xb0W\xe4\xaa2\'\x00P\xb7\xaf\xaf\x1d\xcc\xe4OR\xeb\xd6\t\xe4\
+\x1cP]\x0fV\xbf\x1b\xd1\x17\x04\x99\x17\x89\xa9:_\xb1V\xf5]\x11|\xb8^:4c>\
+\xf4$\n\x1d\xae\xc2\xcd5\xc1\n(k~\xbd\x82@m~\xc0n\xa8\x90\x08[M\xdf\xadz5\
+\x8f\x9c\x11X\xc8\xa8\xf9P\x9c\xb6*E\xceRJ\xcfGr\x99|8f\xdc\x8c"\xd6,K\x91zv\
+\x10{\x96"T\xb2\x92A2\xa1S\x1fv\xe1\x853\x92\xaai\xaf\xd4\x15CE\x07\x98}&s\
+\x86\x98\xdc\x87dk\x1a\x83\xc5\x8en\xf5\x0e\x94\x92rZl\xb16zo\xd8J~\xac/KL\
+\xf0\xe1\x95\xffq\xe6h\xe8\r\xcep\xc8\xe9`\xe66\x80\x00$\xdd;(\x06\x99\xa7\
+\xe4\xc3@\x1eC}\x82O\x17e[\x90f1\xac+\xfb\xf3`yy\xc5=A^s\xce\x80\xe8 \xcb\
+\x92\xaf\n\xba\xbeQ \xe0\x00;\x86\xe6\xf4\xaf\xc8\x8d\xac\xc0\xa0P2= \x19\
+\x88\x8d\xb1w_\x94~l\x8fi\xcb\xc7zr\x16=\xe9J\xe6\x87\xea\x06\xbf\xa6W\xb4\
+\xe1\x07Y\x14\xadL\xf2DcJj.p\xe3\x89\xb5v\xc1\'p\xf6\n]\xaa\x81-c\xcb\xf3\
+\x80\xc3\xcd\x13\xf5\xe3!\xec\x87 \xa0<\x11\xf7\xc6\xf2@\xa6\xdf\xf5W(\xea\
+\x9a\x9fs\xd7\x05~\xfa(\xa0\xcb\x1b\xca\x9d\xd0\x969zvg\x154\xd5\xd0\xad\x13\
+\x14\xbf\xf6nY\xfd:\xde\x08\x85\x8a\x03B(\r\xa9\x02\xad8\xad\xa3\x0bN\x89c\
+\xdc\x88\xfb\xb8\x17@\x0c\x08\xd7J\x8es\xc5\x88E\xad\x1a\x01\x10\xa1\x85k\
+\xf4\xda\xb5\x98\x08\xe0\xcd$\x92h@Y\xaf#\xb6\xcf\x0e\xa5\xd7\xd5\x90\x0e\
+\x0f:\xb6>\xe4\x11\x10s)\x02N}\x9d\x95f\r\xf4\xb3\x9dC2\xb5\xe9\x19?"\x83\
+\x00\xb5F\xdf>Hff\x90f[\xe6\xeb\x0c\x9c\x99\xa7\x82!\x87\x8c\x1dH@\xc4C%i\
+\xd2_\xb0\xdc\x16\x14ll\x98\x04\xd5\x99a\x14\xbc\xde)\xa5n\x93%\x84\xa9\xe4\
+\xc9Z\x91"=\xab\x8c\xe8\x06\xdaa\x98\x15N\n\xcb\x19+3\x8c\x02\x9aAe\x88\x19\
+\x19\xc4\x90\x13\x17m\x08S\xc4\xb8\x1c\xf9n\x1e\x17F\x0b\x00htz\x94D\x10@\
+\xab\xc7\x93d\xa6!\x98\xea\xd5\xfbD\xed,\xfd\xa6\xa2!\x84/\x1ck\x1cG5\x17f\
+\xa5\xcc\x8d\xaa\xb8NR\x99\xa1\xc1\xe9\x83UD\xa5\xbd\x83\xc8\x8b\xa4\xcd\xbf\
+"\x88\x96\x86\x92[0\x14b\x12\x02\xb0\\\xb3gX2\xdd\xd2\x0e\xb96\x88\x81\x97\
+\xd3\x07J\rlh{6\xf6Pf\xee\xfb\xa3R\xb5u\xac(\x93\x16:$\x06@Zz\xaa\xcbA}\x02Q\
+\xa1\xfe5\xf9\xd6\xd6A\xb7\x8fPt\x0f-\x13\x83\x11\x10q\xc0\xb90\xac\xa0fT\
+\x01o\xa5d\x07\x9e\x19{\xe3[\xc0\x00\x00\x0c\x15IDAT\x14\xa0DkL\x8a"\xd5]\
+\xf1a\xf2DV\x98\xf6>fU\xc4\xa83\n\xc5\xfc\xab\x1eA\xc2Hc\xad\xff\x17\xbe&\
+\x8f\xde-\xd8\xefAc\xe9\x07P\x14\xf0V\x89\xcc\xa1\xebu\xea"\xc61c\xa0\xfb\'\
+\xc2\xc2\xa2^UJ\xd8\xef\x04\xf4h:\x9eg\xb0\xe7\x08Wq\xa4\xbe/\x1b\xdf;\xd4~\
+\xc7\x93b\xe6\xc6w\xdf}\xe5<\xf5:\xb8QXB\xd0\x08R\xb5\x82}\x82\x95\x06\xaa\
+\xfb\x88K`;\x9cI\x8aD\x118\x89\n\x80\xf4\xb6eY\xa7\xc1\x8f\xec\xdd\x1c\xc3r\
+\xb2hU\x81\x8a\xb2Q\x8e~\x1cPH:\xc7r\xb6\x07M\x153\xa0U)Y\xd0_\xa9^\xf9\x18\
+\x82\xb6yY\n\x0e\xbaM\xc1\x0e\xe1N\xefP6N\xb0\xc3\x81\xb3%\xaa\xa8\x1c\xf7#\
+\x0c\xc6\x1d\xef[&\x02:\xe65kh\xc4\x0c\xfd\xf6".\t]\x17q\x04\x80\x83fEg\xf6D\
+P\'\x90\xbaM\x11\xaci2ey\xe2\x8b\x1d\x1c~9\x147\xe3I\xf5S\xef\x90QF\n6\x9fQ\
+\xe5\xb6}\xb4\xc4;>\xf8(\xfd\xee\xca$(\xc0\x08\xc2\xbat\xf1\xec3\x92\xfc\xbb\
+s\x06\x93\xdaeR\x19\xa1\x9c\xb2XD\xce\xf2Z\xb6>\x9c\xdfV\x8f\x9d\x19K\xf4v\
+\xaf8\xd4*\x17\xbaA\xa3\x81\xce\x16\xeb8\xa0B\xd4\x9d\x0c1\xcc\xd9\x1a&V\xcf\
+\xea5\xf8\xce\xdc\xfa\xf4R!\xde\xd8D\x9f\xfe(\xf9H\xfb\xa4\xad\x8f\xa0 *=\
+\xf6\x98|\xd1n\x0e\x11Q\xf88S\\6VY\xacz\x8e\x07j\x1c\x88\xba\xd3c]\x88m\xb6r\
+\x06\xd9c\xaf-3\x8c\x89\x88\xf3\xd9B:\xd3\xe8\x8a\x1a\xe9\xc4\x8fM8\x9f-\x17\
+-\x82\xc1(\xeeN\xd5\xb1\x0f\xc8\xa7\xb6_\xc9\x07,\x01\x10\x02R\xa6&s\x80\x93\
+=\xc7#>\xa8k\x00\x84\x15\xe0{\x10<7\xe6Rr\xd0\xd3>\xe6\xbb]\x05\x8c\xeeR\xa8\
+\xf3>\xee\x0c\xb6J \xcd\x8e\xb0"\xaf\xd2\x03\x19\xfeQ\x0c\x97\xb4A\xcb\xb2Kx\
+\xdcG\xe0\xe0(\xd7(\x13\xb3\x04\xe8\xd2\xa7t\xf2\x83\xbab\xf9\x99\xd4\xab\
+\xb8\xc5F)\xde\xb3\xdc\xeb\xf7B\x02\xd4\xe8\xcb\x1b\x1c\x81\xab\x00\xdc\xb9\
+\xb4{\x10e\x82\x03\x85\xe2\xbb\x14\x14\xd9p$\x90\xcf\xba\xb02Q#h\x87JP[\x87\
+\xd942\x82\xf2\xe0\x876\x08\x15N\xf4R\xa9\x83\xf9\xb8\x96c\x9c\x8cl\xbbV]fQU\
+\xa6\xaf\xb7abx\xb5Di\x82\xa8\xa8\xd8\x95\x02j\x9e}ATP(\x90\x14\x9e\xa0\x8b\
+\xff\xb5A{q\x95\xd2V\x9bc\xd4i\x99NP\x0e\x8cD\x0b,\xaag\xf7\xea\xde\xb1p\xbc\
+\xb0\x1d\xe6\x08H`&ex\x11\xc6\x86\xe3\xf2\xf0qc\xc0\xe3>B8\x10]\xd6\xcf\x18\
+\xb0\x92\x12\x07\x88\x9d\x1c\xa8T9\xe0\xb7<3\xfd\xd0\xf0>\x10mL\x8c\x94\x9a"\
+\x9d\x04e\xf4\x83\x10\xbfJV\x82&\xe54\x9d;\xcf3\xb2<8\xda\x83`\x84\xc5\xf8\
+\xe0\xd3\xa0%\x81\x96-G\xfb\x084\xb3i\x18\xa4\xda\xc1U\x0c:\r\xc7\xee\xd0\
+\xd0\x07rA\xf4\x83|*\x99\x06\x8au;Wg5$P4A2\x13\xaeJ\x1a\x8c\x85\xa4t2\x81\
+\x11\xe0\xc8\xb0P\xa2\x1b\x8clh\xf9\xc5{eu\xdf\x1d\xac\xae\xa2\xe8\x0bi&\x19\
+/\tc2Z\x01n\x0cp\xa4\\\'pQ\xee1"\xe0\xc0\xe2\x8d-\x0f\xa0\xc34\x8atU\xcfN\
+\xe2\x81l\x13\xe0D2\x80\xb9\xcaW%\x9c\'\x113(2m5\x9b\xfe\xe2k>\x9e\x89b Uu\
+\xd4\xb1\xc0\x94\xedf4\x0f\xb2\xd0E\xaa\x0c\xbd\xec@\x9d\xc8hKB"!\xa67\x9e\
+\x14\xb5\x9b\x08Z>\xae\xe4\r\xb8p5\x0e\x19m\x9e=Z\xa2\x8fb!}=ID*-\x93\xa1\
+\x1b\xa4\xdd\xcaX\x8a\xe1\xac[\xabFP\xf4\xab\xf1\xc8\xb4\xd8\x91j\xdeJ\x03>\
+\'\xacXL"\x87J\xa4\xfd\xa4\x8b!\x90\xe2\x18-\x8f.\x94x&\x07s\xb1\x00\x99\x00\
+/\xa7y\x98\x92\x94l\r\xd2rJ\xa2&`\xaf\x87\xdb\x84\x96w\xd9x\x87obp\xca\x15\
+\x8e\xa2*^h\x0e>JqYE\x0bpC\xc0\xa8\x15\x9e;\x88\xa6A\x81\x80UPH\x98\rL\xa9\
+\xfc\x81*\x00\x94b\x0c\xea$D\x97\xb2U\x99\x03\xa5\xf2Z7MZ\x04t~\x10\xb9}\xbc\
+\x189f\xc8\xc4\xc9\'T\xe2\x96\xe4\x97V\x84\x05\xe2\tRe\xa0\x16\xf3\xd4\'\xb0\
+\xbbD\x1aC\xe0\x10\x10\x93)\xb5\xb4FHx\x7fd \xf3_ \xd2\x15D\x15\x13R\x1c\xf6\
+R\x08^\xbc\xf7\x90@\x97\x9d\x9e\x88\xd1E\x01\xa9z\xdb\xba\xf3l\xe4J\xe3\xae\
+\x95\xe4\x08P\x14\x00f<\xc6\xea\xa7\x8d\xb1s\xa0\x06\n\x015WY\xd7\xea\xa7\
+\x89\x14\xc9\xdbF \x95\xf28o*j\x97\x18>\xa8\xeb\x83\xe8C"\xaa\xa8\x14\x9b\
+\xac\xe1\xc6+I%\x04\x08z\x08\n\\S\xa9\x01\x81\xa0V-\xf2\xa0\xd9\'\x13<\xea\
+\xb0\x94\xe3\xd5P\xed\xd6\xa8\xee-\xee\xa44\xe8\x01\x18f\xaf\xec\xf2r\xdf\
+\x84?\x00\x07\xb8[\x98\x03Q%\x1a\x12\x8a9\x85\x15\xe8\x11\x02\x08\x8aEe\xcb8\
+\xec\xa5\xfa\x88^\xafT\xaf\x92\xb8\xa2\xc7U\x15\xa1J{\xf8C\x11\xe8x\x9d\x8a\
+\x00\x1b\xce!\x85\xc0\x08-P\x9b]\xea\xd1x\x8d\xbfX\x05\xe0\xa0\x9a\x199\xe7\
+\xc3|TX&\x9a\xa9Qs+-\x8fv?\xd7\xdd=E\xd1\xf6\xba\x82\x80\xf2\xc8\x16\xa9\x1c\
+\x92\xa9Sk\x98j\x97\x05\x95\xa5.\x0f \xdd7\xad,\xf1\rK\x12P\xb2HF\x99\xa5\
+\x17\x81\xce\x1b\x12\x0bj9\xbd\xc6.4\xf1\xa9\x08s\x08\xf0\xc2\xa8\xbb\x9d\
+\x8c\xd7\xcb\xc8\xc0\x16\x83\xd8\x80/\xebv=\x87ZF\x01\x82\x12\x95Q&5\x0b\xe6\
+\xcc\xb2\xe8n>-\x94\xf3\x0cK\xe3\x19#\x80\x1a\x9a\x1a\x83\xe0\xc8\x8f\xf5\
+\x18\x9a\xe5\x80\xe3\x1b\x1dg\xbaW\x84N\x01QjX\x05\xd7Zu\xcePnN\x92\xaeB\xb7\
+\xc5Z\xe2\xd0\xaf\xf8\x1c\xe2\x8aB\xa5{\xa6\x171\xd3\xda?\xcc\x04\x0cQ\x15t\
+\xe6\xa0\xaa\xf95\x03]q\x81\xa9^\x8eB\x10\xeb\x8a+p\xe5\xb1%0\x8a\xd3\x8d\
+\x93\xf8*x\xa4\xc6usP\x19\xd8\xf9]\xaa\x95-\xe7>3\xca\xeb4\x84\xe1\x04ht\xf3\
+\x9578\x9ay\xe8\xa6\x8d\x8a\xd9*\xc0\x90\t\xfa\x14\xa1,\x08\x1d\xb6\x8a~\xa0\
+\x16\xcd\x11@v\xff\xafJ\xc8\xd0\xe9\xe4\x84\xc4\xc4\x9d\x17J\xa8h\x12\xa8\
+\x1bX\xd5\x82\xa2ai\xd0u\xb4\xc6`+\xc7|\xad\x19\\h\xf6\x9b\xba\xe3\xb5z\x80\
+\xe8\x14[\x91\xd1\xce\xd6\xfc\xb0Z\x1dH\xa8\x1a\xcf\xea\xd8\xbcv\x7f\x8d\x81\
+Jt\'O\xd2\xa5\xa7\xc5Z\xda\x86\xbb\x8f!x\x92\xdat\x97\xe5\x97\x06(\xb4\xab\
+\x83p\xf7\x15[\xa0\xdf\x94Ig\xee\xab<\xa4~\xd5U\xbe\x9e3[#\xb9\x13\xe8LKN\
+\xa6\x0e\xd7V\x02\xed}"\r\x1b4\xa6q\xc5\x17D\xe7\xf4\x97\x9a\xdb|\xe5k~\xc6g\
+Eqc\x9fW\x001P\x86\xed\xcc\x88|\xda\x1c\xca\xd1V\x1e\xd9\x13\x15\x8a\xca\x83\
+J\xd0\xbc\xa6\x14!l\x87\xca\xaf\x93?VV\xa3.6\xc1\xd3|\x02\x9c\x91F\xb0\x035\
+\xb8:\xaf\\\x86\x8c\x9d\x9d\xf1\xe9B\xad\x08Ylsv\xc2j\x1dN\xa7\xc7\xd2]\x05\
+\x1eh7\x91\xd3\x82+\x19\xf7e\x1f\xad\xc6\x9d\xcf\x8e\xf7l]Z\x0e\x1f\xb4M\xaa\
+8\x9c6^Y\x7fb\x9a\x9a~\xf6\x04\n)\xdb\xca\xa6e*\xbf\xd9\xbeHr/\x11\x8e\xa1\
+\x12\x10\x8a\xeeM\x1a#\xa9\x81\xe0\xce\x13.\xeba\xd0\x13\x01\xaa\x8b\xa6\xe8\
+$\xfbu5\xc2\xbb\xa6\xd4E\xfd\x86q&2O\xbe\'V\x07\xdcY\xb0\xcd\xbc*\xf3\x95\
+\xcf\xef\xa9\xab\xd9\xc5\x88\xeaT\x04\x98\x93C\x87#\x91\x9aO\xe0\xe0\x9e\xab\
+\x81\x01\xcb\xd9\xcb\xe6\xe8\xe8\xc7\x11O\xe0 4\x0b\x01AP\x07\xc8\xf3V\xea\
+\xcawz\xd56A\x7f\xb9\xa9\xfa%1\xb6R\x8d\xd2\xd98:&\xad/\xcb\x91\xe99\xa2\x12\
+\x11\xe0$\x02\xd8YaV\xdc\x81)\xca\x9cf\xab\xdd-\xc2H\x90\xb6%.\x1b\xc7\x07=\
+\xdb&k\x03\xa1\xe8w+;\x84\xd9S\xa6\xe6s\xe2\xc5\x80\n"?B\xcf\xa7s\xac\xe7\
+\xcf\xca\x01\xaf&i=\x99~\xa9\xe3\x06\x0c\nH\xa53\xff\x8c]\xe7\xbd\xa1\xb2\ra\
+\x81\xe9\x04l\x9a\xb4\x83\xa6\xdd\xff\x80]\xbc\xbcc\xcf\xf2\x19/\x1b\xc0\xf3\
+\xc4\xcc:D\x1a|\t\x92\x15X1r\xa5\x8eV\x95\xa8\xc4\x89\x842\xe2\xccR\xfe)\xc6\
+\xb1D\xe5\xb8\x1f\xad\xd0I\x82]\x84w\xa5fW#@\x94#%\xa5O\xa0\xae\xf3;v\xc8\
+\x0c\xdb3\xd6k\xf8\x9c \x08|\xd8l\xf5H\x0f(M?J\t\x9c\xbd\xe4re\' \x89\x9c.o\
+\x8e\x06\xb2Dm#@\xe8f\x11W<\xa0\xda+Q\xb5a\x00\xe0=\x03\x01\x1fz\x8e\xa6\x81\
+\n\x05B\x11\xc8\xee\xa0\xb0\x10\xa9\xcfQU\xc8\x05\x91\xd6\x03W\x0e\tW\xb1I\
+\x1et\xc3\x9dJ\xca\xf1*jh\xcb[\xde\x13\xf0*2\xe3\x9b$E\x85\xab\xe8n\xfd\xf8\
+\xa0\xa2)\x86\x08\x9a\x86"\xe4\xd9\x19\x97&f\xf3\x86$\x12A.\xb4\x10\xa4\xe7\
+\xcf\x1c\x83\x84)|\xadT\x1290\xa5J\xcc\x94O\x11\xb2\x84.v\xda\xcf\x9e6\xac\
+\xa8\x8d\x1c\xf6\xaer\xe2\x87\x08\x01\xe7\x92Hbm\xd4a\x10l\x11\xa6K\xba\n@\
+\x11Z\x0f\xf8x\xf0x\xb1\xa8\xf5\x1b\x90\xa8\xf5\xaaB\xb7\x07\x1a@\x10\x10~\
+\x19\n\xe8\x05dd\xacA\xcd\xb1R\xd2\x168\x00\x0fj\x11\xdc>\xa9\x90k\x9dS\xa9\
+\xe1u\x04pd\xb4\x91d]@\\g\x14@\x84\x89\xa6\xc5y!}t1\xc5\x87\x94A\x8cn\x94\
+\x1dT\x07\xe6\xc6\xaa+J\x92o\x13\xe8~\x96l\x93\x96\xcd2l\x02\n\xc9\x8f4\x86\
+\x80y\xd6\x82\x83G\x80\xc8\x00\x16\x99\x04\x86$z\xaa\xc4\xe1b!Sj\xa7`HA\x82a\
+\xc4 \xf8\xa8\xa6\xc7\xe51\x01\x87#\xbd$\x05\rn\x90\xf7\xc1.\xb4\xfa,\xe7\
+\xc6c"\xba%\xa7\x0cJ7\\!$\xafD\x11[\x83\xe8f\x84\x8d5\xd1\x01Y\xbaNgW\xba~\
+\x96\x9f\x88\x06\xb1\x03\x07\xaes\xf8K\x8f2J\x05\x91\xd5\x85<\x9aR\x11A4\x95\
+.\x16!\xd2\xe4\x15Q\x8ctY\x7fV/\x07E|Tj\x0ca)"?\x02\x07L(\x00b\xe3\xa7\xe4\
+\x02\x18\xd1\x1cR|=X1\xf5!K\x04\x88\xbaiG\xc8:\x11C\xae\xc2\x86\x87\x1798A\
+\xdc[\xd2Qw\x000\x90]#\x16;bC\x86\r\xc4\xa8\x85\x8e\xf1\xaa\x17)\x9aP"\x178v\
+@\xfew\xed\x1f)\x1eW\xd3\x10>\xa0\x08\xf3R\xe5\x87\x0eC\x08p\xe0R\x0f*\x15\
+\xca\x05\x85T\x97\x03\x14g\xf1\xcb\x96-\x9d\xf8G\x82\xa7\xffP\xbc\x87\xce14\
+\x1d\x00\'\xdeT"\x8c\xb6\x07\xecQ\x86HJ\x11\xeb*\xc2\x1e\x9a\xa19\xec\xa4\
+\xa3|\xba\xe2q\xc0\x96\x08\x98>\xe0\xdd\x80q#t\t\xcfH~\x83\xed\x15\xc8Cj\x00\
+\xff"\xa0\x98\x11\x88oQ\xb34\x02*\x06<$\xb1\xc9\xc6h\x9d\xacV\n\x00^\xae\x18\
+\x82\xeaw\x06\x12T\xfdx\xfc\x8b\xb3\x87\xf3{\xc2\x8e\xd8\x81\xe2\xa0r\xb4\
+\xe6\xe5H\xa2\xb8\x02G\xbed\xfc#\x8b\x89\t\xbe\x10\x91\xe6\xd9\x1b\x8a<\xc7T\
+\xd2\x1f*\xd2l\x1aNn\x84\xd3re\xcf\x8c/\x16i\x8cz^\xd2\xa3?\x89\x9a\xf3\xce\
+\xb7\xef;V\xd7}Q\xb5\x96\x11H%\xd9d^c\xab>\x16:Ee\xf6\'kM\x1b\x97\x8cd\xf6\
+\xa8\x8f#\x17\xba\xfc\x94\xed\x02\x95\x0bd!\xcaA\x19\x8eJ\r\xb4\x9b\xbd\x0e\
+\x81t\xb2\x08\xef\x8e\xd0!\xcc\xd6\x91\xda\x15\x93tV\x17\xe5\xed\xbc\xdf\xf4\
+\xda\xd9\xa4\xb4\xb3\xad\x01\x02\xa7\x9a.\xa0\xe5\xfb\x14\xf6\xa2\xff\x08\
+\xb7m,~\xbd\x0f\xfe\xb5\xc4L\xa2y\x96\x92\x02$%\xc4\xda\x1e\xb6\xb36\tZ\xbf\
+\xf7\x1cl\xaa\xe3\x95\x9cD\xfc,"\xc1\x01 \xf1&\xab\xdf\x89,\xec/\x00\xea\xbb\
+\x00\x91\x16\x07a\x11\x0f\x8f0\xad\xf7W\x14\x88\xa2a\xb3y\xac\x8a\xaap\xe5\
+\x80v\xa8\xb6\xae\x0bRl\x00\xd8\xb5.\xc8\xdcb\x85\x98\x81"\x8d\x1e\x81M\xf2\
+\x91\xc6\xc0]\xdf\xe0Z\x83\\\t\x14\x10E\x04\x05d\xf2\xb9&\xedf\xc3\xcey\x00\
+\xe7=\xd1\x05H\xf8DMd\x01\xa1&&\x08\x02v)\xe5\xe0t\xbc\x0ce\x83\xe7\x89\x8d\
+\xc0\xf1\xe6:\x83\xe2%h0Q\x03&\xee\xd2E\xd0B\xf1\xd1\x988\x02\x8d\xf2)("\xd7\
+^\xc8 \x86\xc5\xff\x075\x19\xcc\xa5\xaa\xbc\x13V\x00\x00\x00\x00IEND\xaeB`\
+\x82'
+
+def getWizardBackgroundBitmap():
+ return BitmapFromImage(getWizardBackgroundImage())
+
+def getWizardBackgroundImage():
+ stream = cStringIO.StringIO(getWizardBackgroundData())
+ return ImageFromStream(stream)
+
+def getWizardBackgroundIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getWizardBackgroundBitmap())
+ return icon
+
+#----------------------------------------------------------------------
+def getWizardBGShorterData():
+ return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xac\x00\x00\x01,\x08\x02\
+\x00\x00\x00\xd1\x94\xcf\xe3\x00\x00\x00\x03sBIT\x08\x08\x08\xdb\xe1O\xe0\
+\x00\x00 \x00IDATx\x9c\xc5\xbdy\x94^\xc7u\x1fx\xeb\xeb\xaf\xbb\x81Fc!\x16\
+\x02 \x01\x82 \xb8\x80\xbbH\x8a\xb4D\xca#\xd9\xa4\xe28\x92e\xc99\x91dO"\xebx\
+\x19\xcfd2\xb62\xc9x\xc6>>c\x9f\xcc\x8cg|&q\xe2\x938\xb6c;q\xecq$9\xe3\xe5\
+\xd8\x96e\xd9\x14\xe9\x13\x9b\xabDR$\x05\x92 \t\x80\x00\x01\x02\x8d\xbd\x81\
+\x06\x1a\xbd|]\xf3\xc7{Uu\xd7\xaaz_75E\xb0\xbf\xf7j\xb9u\xab\xee\xaf\xee\xbd\
+\xb5\xbc\xf7\xdc{\xbf\xf7\'A\x0b\x1e|\xba\xf6\xbe\x89\xe21"g\x13\x1c\xb8\xf6\
+\xc29\x9c\x9f\xe7\xf4\xbc \xa5\x12\x88\x04jF.#5Fg+\xc1\xec%\x0e\xf3\x8c!\xde\
+Pm\x8ep\x82/\x9d\x03\xd9\n\xbbM,\xa7\xec\xde\\\xf0\xa4\x88/6\x04\x00\x00z\
+\x16C\x99\xae\xd7\x11\xe0}\xfb\xaf\x92\xef:\xfe\xbe5\xc11\xc0Y\xc0\n\xb9\xd5\
+h\xb5\xc7\xbb"\x80\xe4q\x89%\x07\xce\xfa\x97\xa1\xec\x9cS\x07\t\x8b\xef\xb7\
+\xc5\x9c9bX\xdb\xf8\xa0\t\xb1\xe4\xda9\x0f\xde\x81\xd3\x91\xd8E\xfc\xcbU\x03\
++\x1bhu\x927\xef}\xcb\x92O<\x90l\x82\xb1&\xb5\xe9L\x87\xca\xa4\x0b_\xe8\x04\
+\x85M\x94\xdf\x83\xb7z)\xc6\xf7e\x1agH\x0bD\x01(\xc9\t\x07J\x91\x9a`\xe07\
+\x907HY,\xdb\x10\xaf\xe2\xa1\xa9N\x1d\xd3\x1d\xa8\xe1K\xc7:9\x07\x14\xab\xc2\
+\xba\x16a\x90Y!\x80@\xe4\x11\xf6\x1b\x00\xc0{_+\xcb\x80\x03%\xbe:\xe0\xae\
+\xc1p6\x15\x80A\xc5\n\x12L\x89ab\xe0\xab\xbc\x93J\x1e\xa2z/\xe6\x14)a\x0c4L\
+\x06d\xd7\x88Y\xad.\x16Q4\x01\xc9\x87\xcd\x7fD\x80!H\xcf:\x10\r\xa0L\xa9\x94\
+\x87\x96\x1f\xa6\xc7}\xae\x13y\xde\n8\xe2~\xaf\xe1\'\xa1\xd3\x14\xb1K\xa9\
+\x9e$XU[D<xE\xc3y\x9e\x95\xc7\xa3\x98\xa8\x90\xfa)-\x8a \xa4\xa5n\xc2\xa4\
+\xe5\xe8\xd1F{\x1aH]\x1d\xc0N\xa3\\\x86.8@\x854\x9f\x0e\xeb\xa1N\x08\xb0c*\
+\x83\xe9\xeey\x9e\xc7;\x0f\xdeP\x03\xce\xb8\x8e1\xc1\xdb05\x01C@\xab\x06*\
+\x10\x10\xe3Y\x0f\xc8\x9c\xbc\x8b\x9c\xcb \x06\x0f\xdcB\xe7J\x1c\xa8]\xe4\
+\x9c>q\xd5r\xaal\xd4\xf0\xa3 )?\xf3P\x9c\x83F/6ND`V\xea\x12J\xa5\xcah\x87\
+\xaaL\x9f\x00\x07\xd5\x15\xe84\xc2\x0b\x99#dl\xeb\xdbs0\xd2[Z\x18\xf4:\xd4\
+\xda\x12\xef\\\x023\xc0\x04\xdc\xdc\x16L\xc9\x8a\xccM\x1c\xb0a\x12\x86.B\x81\
+\xf3\xce\xbb\xbbo\xdb\xf5\x99O|\x08\x00\xfe\xf4\xb1\xaf?\xf5\xc2\xebc\xa3\
+\xadLg.\xcd\x8e\x8d\x8d^\x99_XZZ\x1a\xeb\xf7\xfb\xfd\x91\xcbW\xe6\xd4\xaa\
+\xcc\xd9A\xbcA\xd7\x9e\xfe\x92\xcc\xda|)?\xb6\xcd\xc0H9\xe7\xfa\xbd\xc1\x87\
+\xb6\xbf\xbde\xf5\xec\xd3S\xdb\x0f]\xdc\xd0\x81V\xdd\xbcWe 7\xbe]\x9a\xfd\
+\xf2l\x0e\x94x\x0f\xe0\x82ca\x18\xac\xf5\x93\x13[\xb7l\x98:u\xfe\xc2\xcc\xac\
+\xa5\xc9\x9dO\xf7\xb1\x01\x1b\xd7O~\xe0\xbd{\x01\xe0\xc9\xe7\xf7\xbf\xff\x9e\
+\x9b\x1f\xbco/\x00L\x9d>\xff\x17\x7f\xfd\xe2\xf7}\xd7\xb7=\xfe\xf47_x\xe5\
+\xd0\xb7\xdf\x7f\xeb\x9e]\xdb\xfe\xdd\x17\xfeRm\x0e\x07\x816\xe2\r7\xb0b\x88\
+!\x93\xc2\xb1Ri.\x9ds\xe3#\x8b\x9f\xbb\xf3\xf9\x8f\xef>\x00\x00\xff\xe0\xe6\
+\xd7~\xf1\xc5{\x1e=v}\xb6Lbq\xe8P4\xe7z\x06\x11\x97<J\x86\x03\x1a\xdes\xeb\
+\xf5\x9f\xfb\xecGn\xbc~\xfb\xdb\xc7\xcf\xfc\xfa\x17\xfe\xf2\xf1g\xf6Q\xaa\
+\x8d\'\x08A\r\x98M{\xe5\xcd\xa3\xb7\xdf|\xdd\xed7\xed\xf8\xd3\xc7\xbe~\xd5\
+\xfa5\xdf\xff=\x1fX\x1c,\xbd\xb4\xff\xc8\'?\xf2`\xa6-D\xbbZ\xcb\x8dt\t9C\r\
+\xc2\xaaa\xfa\x07\x1a\\\x94\x0e\x14t\xe3\xaa\xd6\xb7o;\xfa\xdd\xd7\xbd\xd5D^\
+5~\xe5\xef\xdf\xfc\xda\xf8\xc8\xa2\xc9AG\x04d\xd6\xbc\xdf\xa5 k\\5>\xf6\x99O\
+|\xe8\xb6\x9bv\x8e\x8d\xf6\xf7\\\xb7\xf53\x9f\xf8\xe0\xba5\xabeA\xd7\xf2\xd6\
+\xfe\xaa\\\x1e?un\xdf\xebG\xf6\x1f|\xe7\x85W\x0e\x01\xc0\xf3\xfb\x0e\x0e\x06\
+\x83\xef\xfb[\xdfv\xf4\xc4\xd9}o\x1c\xb5X\xea7|\x81\x8a\x80\xec\xa4\x80\xf2g\
+v\x1cn\xb3\x9a\xed}[\xa7\xee\xde|\xba\xb9\x9eY\x18\xfd\x93\xc37\\\x98\x1f\
+\xc59\xb7\xac\x9e\x1d\x1f\x19\xc4\xdb\r\xe3s\xa3\xbd\xc1\xdc@si;\x8a/\xa3\
+\xcc\xd2\xda_}\xc8g\'\xc6\x81L8G\xfa\xbd\x89\xd5c\xf1v||l\xf5\xaa\xb1\x0b\
+\x97fQ\xfe\x94;\xef\x116a\xcd\xc4\xaa^\xaf\x07\x00GO\x9c=y\xe6\xc2\x0f|\xec\
+\x03\xff\xe6\xb7\xbf\xbcg\xd76=\xb7\x97\xe6@,\x12\xe3I\x81T\xef\xcb\x0f\xef\
+\xdfv\xe2\xd37\xeeo\xae\xe7\x06#OOm\xbf0?\nH\x0cOOm\xff\xd4\x8d\xafo]}\xb9\
+\xc9\xf3\xd4\x89\xed\x97\x16\xc6\x14B\xdd\x07\xb0\x03e9+J\xa8\x80\x03k%\xc0*\
+\x82\xbdzj\x1f/]\xba\xf2{_zr\xf7\x8e\xab\xafZ?yyv\xee\xf7\xff\xfc\xe9\x93g/\
+\xc4\xd5\x92PX\xac#\x86\xa5y\x16{vz\xe6\xec\xf9\x8b~\xc9_\xbe2?u\xfa\xfc\x9b\
+\x87\x8f\xbfv\xe0\xd8\xd1\xa9\xb3\x1b7\xace<\xc4\xd0g<q\xa6\xf5\xe6\xa4\x06\
+\xb0\x82j\xb7\xd6\x87\x85\xa5^\xac"\x8e\x95C\x177\xfc\xfcs\xf7\x7f\xe6\x96\
+\xd76\xad\x9a\xfd\xc6\xe9-\xbf\xbc\xefn\x1f\xd7\xe7Q\xc5\xc3\x05\xbc\x82\xab\
+\xfaC\x95\xfa@\xdf\xa7\xa9f\xeb\xb1\xa7\xbfy\xe0\xc8\xd4\x9e\xeb\xb6\x1e\x9d\
+:\xfb\xc6[\xc7\x8b.\x93\x8b\xb8\x10n\xe9\xcb\xfb\xdf~\xf5\xcdc\xde\xfb7\xdf:\
+q\xf8\xe8\xa9\xb9\xf9\x85\x97\xf6\x1fY\x1a,\x1d|\xfbd\x9bG@\x81h\x82\xfc\xda\
+@Z\xd0\xeb\xb8\x8a\xc2r2\xb6//&\x1e\x16\x96F./\xf6\xd3\x92\xb3o;\xf7\xd9S\
+\xd7<{\xea\x9a~o\xb0\xe8G0\xc5\xd4\xa4\x8e\x01oC\xb0\x05|\x06\x85"\x0e\xf8\
+\x0e\xe4\xb0\xe1\xf0;\xa7\x8e\xbcs\xba!\x84\xaa\x07\x1e\x83jVG\xdc`0X\x1c,\
+\x82\x83\xc1`0\x18\x0c<\xf8\xa5\xc5%\x00\x98\x9f\x9fW6\xa8\x00 \x82@\xd2cva\
+\xfb\x9a\xd9]\x93\x17\xae\x9d\x9cY52\xb82\x18\xb98?v\xe0\xc2\x867\xcf\xaf\
+\xcd7l\xc3\xaa\xc1m\x1b\xce\xc4R\xc7f&_9\xbf\xe9\xc2\\\x1f\x00\xd6\x8d/\xee\
+\x9a\xbcpyqt\xe3\xf8\x95\x98\x7f\xb47\xd85ya\xa2\xdf\xfa}\xef\\\x9e\xbc\xb28\
+2:\xb2\xb4s\xcd\xc5&f\xa2\xbfp\xfa\xca\xea\xa9+\x93\xe3\xbd\xc5k&f&\xfa\x0b\
+\x97\x17G\x9b\xc8\x93\xb3k<\xdf\xe5\xf3\xd7\xaf\x9d\x8ey./\x8e\xca\xe9\xe5\
+\xda\xb1y\xc6\xe1\xe1\x99u\'.\xafa]!\xe7\x81c\xfd\xfe\x96M\xeb\xd7\xad\x9d\
+\x98\x9b[8v\xf2\xdc\xc2\xfc\x82\xd2~\x1b\x15\xdb\xb7\\\xb5n\xed\xc4\xd4\xa9\
+\xf3\xd3\x17/\x87\xbcx)\xc9\xfb\xb0F\x84\xc3\xaaU\xa3\x9b7\xac\x1d\x1f\x1f\
+\x9d\x9b[8~\xea\xdc\xe2\xc0\x8b\xa5E\xe7\x9b\xff\xc2\xa2\xb2\xdc\xac\x92\x86\
+Es\xaf(\xb8\xee\xdct\xf6\xa3\xbb\x0e\xbdg\xf3\xa9\xed\x13\x97\xb0\x8365;\xf1\
+\xd4\x89\xed\xbf\xb5\xff\xb6\xe3\x97VK_\xe1\xda\xc9+\x1f\xbb\xfe\xe0w^\xfb\
+\xf6\xe6U\xb3\x93\xa3m\x07\xcd,\x8c\x1e\xbb4\xf9\x95\xb7w\xfd\xc1\xa1\x9b>\
+\xbc\xe3\xf0\x0f\xde\xf2*\x00\xac\xe9\xa7\xee\x9b\x1c]\xf8\xb9\xfb\x9f\x99_\
+\xea\x01\xc0\xecb\xff\x7f\x7f\xee\x81o\x9e\xdd\xbci|\xf6g\xdf\xfb\xf4\x86\
+\xf1v\xa1\xe3K\x87w\xff\xfakwm\x18\xbb\xf2O\xee~~\xc7d\x0b\x8eS\xb3\x13?\xf3\
+\xec\xfb\xa7f\'\xdbFz\x00\x80\xeb\xd7N\xff\xfc\xb7=\xb1:@\xeaK\x87w\xff\xfa\
+\xab\t\x04\xe3#\x8b\xdf\xb7\xfb\x8d\xef\xday\xf8\xda53\x91\xc3\xb9\xc1\xc8\
+\xf1\xcbk\xfe\xf8\xad\x1b\xbe\xf0\xe6\xde%C\xc7l\xdc\xb0\xf6\xc3\x1f\xb8\xfb\
+\x91\x87\xee\xbcz\xd3\xfa\xb1\xd1\xfe\xfc\xc2\xe2\xc93\xd3\x8f>\xf1\xf2\x1f\
+\x7f\xf5k\xdb\xb7\\\xf5\x13\x9f\xfd\xc8\xc4\xea\xb1G\x9fx\xf9?\x7f\xf9\xc9&\
+\xff\xe8\xe8\xe8\x7f\xf3\xa9\x87\xef\xda\xbbk\xff\xc1w\xfe\xf5\xef\xfc\xf9\
+\x1d7\xed\xf8\xe4\xdfyp\xef\x9ek\xc7F\xfb\xbf\xf5\xfb\x7f\xf5\xff~\xf9)p06\
+\xda\xff\x91O>|\xd7\xde]GO\x9c\xfd\xd7\xff\xf1\xcf\x82W\x98\xd6]wl\xdb\xf8\
+\x91\xef\xb8\xf7\x81\xbbn\\\xb7v\x02W\xfa\x07\x7f\xf1\x8c\xcee\x13\xd0\xdap{\
+\xab\x05\xd3\'p\xce9\xf0?r\xeb7?\xb9\xe7\xf5\xd8G8l]}\xf9\xe3\xbb\x0f\xdc\
+\xb8~\xfag\x9e}\xff;3\xab1\x91\xbb7\x9f\xfb\x99\xfb\x9e\xbd~\xed\x05Vdrt\xe1\
+\x96\r\xe7n\xd9pnrtafa4\xfaz8\\\x85\x14C\x13&\xfa\x8bW\xaf\x9e\x8d\xf1\x8d\
+\xe685\xb7\xee\x8d\xe9\r\xf7m\x99\x8a\xcc<\xb8\xed\x9d?<ts\xd3\x98&\xbco\xeb\
+\xf1\xc8\xc3\xdc`\xe4\xc9\x13\xdb#\xcd\xddk\xcf\xff\x93\xbb\x9f\x8f\xc5c\x18\
+\x1f\x19\\\xbf\xf6\xc2\x8f\xdf\xf9\x8d\xeb&/\xfe\x8b\x97\xee\x93\x0b\x94\xf7\
+\xdd\xb9\xe7s\x9f\xfd\xc8m7\xed\xc4\x91;\xb6m\xba\xf7\xf6\x1b\xee\xbd}\xf7\
+\xf3\xfb\x0e5\xeb6\xfb\x0f\xbe\x13S{\x0e\x1e\xb8\xeb\xc6\xdbn\xda\xb9~\xed\
+\x9a\x8f\x1f?\xf3\xd9\xbf\xfb\xa1\xab7\xado\x92\xae\xde\xb4\xae\x11\xccH\xaf\
+\xd7\xe4\xd9\xbd\xe3\xea\xdf\xf8\xe2\xa3\x17.\xcd\xe2\xb1\xf8\xa1\x07n\xfbG\
+\x9f\xf9\xee\x1d\xdb6\xc9J\xf7\xee\xb9\xf6\xb5\x03\xc7d7\x92M&#\xc4<\xfd\x8c\
+M\x1d\xe9-\x01@D\xc0\xd4\xec\xc4\xd1\x99\xb5\x00\xb0c\xf2b\x94\xdf\x1d\x1bO\
+\x7f\xfa\xc6\xd7\x7f\xf1\x1bwC\xb0)7m\x98Q\x11\x10\xc3[\x17\xd7=z\xf4\xba\
+\xf7m=nV,\xc2\xfcR\x12\xc6\x95A\x1f\x00\x96\xfc\xd2\xa3Gw~t\xd7\xc1\xc8\xde\
+\xfd[\xa6\xfe\xec\xc8\rq\xea\xe8\xc0?\xb4-U\xf1\xcd\xb3\x9b_9\xb7\xb9\xb9\
+\x9e\x1c\x9d\xfb\xe9{\xbfv\xc7\xc6\xd31ujvbv\xb1\x8f\x95\xd6\xc7w\x1f82\xb3\
+\xf6\xf3o\xde\x8a\xd9\xb8\xe1\xbam\xff\xeb\xff\xf0\xf7\x1aa\\\x9e\x9d{~\xdf\
+\xa1CGO\xf6Gz;\xb7o\xbe\xf7\xf6\xdd\x8f<t\xd7}w\xdc0\xbf\xb086\xda_\x1c,\xe1\
+\x82W\xe6\x17\x00`\xcb\xc6u\xff\xe8\x1f\xfc\xed\x89\xd5\xe3\x17ffO\x9d\xbd0>\
+\xd6?y\xa6\xed%\xef\xdb<\x97f\xe7\x00\xf0l\xc0\xdf\xb5w\xd7O\xff\xc3\xbf\xbb\
+nr5\x00\x9c\x9b\x9e\xd9\xf7\xc6\xd1\xb7\x8f\x9f\x06\x80[n\xb8\xe6\x8e\x9b\
+\xaf\xfb\xd8\xc3\xef}\xdf{n\xe2\xfd\x15t\xa1\x93\x1e\xb4\x08\x0e\x9c1\xdb\
+\xf6\x00\x00\x0b\x83\xdeo\xbcz\xe7D\x7f\xf1\xa3\xbb\x0e>z\xf4\xba\xdf;p\xf3\
+\x81\xe9\xb5\x00\xb0g\xfd\xc5\x1f\xbd\xf5\xe5\xef\xb8\xb6]|x\xdf\xd6\xe3\xeb\
+W\xdd~a\xae\xef=\x8c\x8f,\xfd\xe8\xad/c\x04\xcc,\x8c~\xed\xe4\xd6\xe9\xf9\
+\xf1\x9d\x933\xf7m\x99\x9a\x9a\x9d\xf8\xe9g\x1ez\xe3\xfc\xe4\x96U\xeb\x1f?\
+\xb6cz~\xfc\xd6\xab\xce\xde\xb2\xe1\\\x93\xb9\x19\xaf\xd3\xf3\xe3\xcd\xed\
+\xe9+\xca\x9aI\x0c\xfb\xa77~\xed\xe4\xd6\xc8\xc6\xfdWO]31\x13\xad\xfe\xf5k\
+\xa7\xb1\x98\xff\xe8\xd0\rqP\xfc\xf0\xde}1ifa\xf4\xdf\xbfv\xfbc\xc7v^Z\x1c\
+\xbdm\xc3\x99\x7fx\xc7K\x91\x99O\xdd\xf8\xfac\xc7v\xb6&\x06`tl\xf4\xc7\xbe\
+\xff\xc3\r\x02\x0e\x1c\x99\xfa\x85_\xfb\xc3\xe7\xf7\x1d\x840\x9e\x1e~\xf0\
+\xce\x1f\xfd\xf4\x87\xf7\\\xb7U\xed\xe5\xe6\xa2\x11\xe4\x1f\x7f\xf5\xeb\x7f\
+\xfa\xd8\xd7\x0f\xbd}r\xf5\xaa\xb1\xd9+\xf3`y\x94\x1e\xc0\xc1\xba5\xab?\xf7\
+\xd9\x8f4\x05\x9f\xdfw\xf0W\xff\xd3_\xbc\xb4\xffH\x93>2\xd2\xfb\xde\x87\xef\
+\xc7z\x85\xd5\xaa_\xb3*\x00\xa01\x07|\xe3\x18\x85\x81\xf7\xbf\xbc\xef\xeeG\
+\x8f\xee|\xf9\xcc\xc6\x18\xf9\xe6\xf9\xb5?\xff\xc2\x03\xbb\xd7]h\x84\xbdy\
+\xd5\xecm\x1b\xce<ub+\x00\xdc\xb7\xe5t\x94\n\x00\xbcuq\xdd\xbf|\xf1\x9e\xe7N\
+m\x9e[\xecmX=\xf8\xf0\x8e\xc3\xaf\x9f\xbf\xea\x8d\xf3\x93\x00\xf0\xd4\x89\
+\xadO\x9d\xd8\xea\x1c\xfc\xd8\xed\xfbb\xbf/,\xf5~\xe9\xe5{\x8e_Z\r\xd0\xc08\
+\xe7o/\xfa\x91\xc7\x8e\xed|p\xdb\xf1\xc6S\x99\x1c]xd\xc7\x91h\xf5\x1f\xd9q$z\
+0S\xb3\x13O\x9flm\xc1\xee\xb5\xe7\xbfk\xe7\xe1H\xe4\xdf\xee\xbb\xab5"\x00\
+\xcf\x9e\xba\xe6\xd4\xd7\'\xfe\xdd\x07\x1fm\xf4\x0111\x00w\xddr\xdd\x7f\xf5\
+\xc0m\x00p\xf2\xcc\xf4\xcf\xfe\xd2\x17_;p\x14\xb3\xf7\xd5\'_>;=\xf3\xcf\x7f\
+\xea\x07\x1b\x81\x81!\xdaG\x9fx\xe9\x17~\xf5\x0f\x17\x06\x03\x00\x98\x9eQ\
+\xac!\x0b\x1f\xfe\xc0\xdd\x8d\xe99pd\xea\xe7~\xe9\xf7N\x9e\xbd\x18\x93\x06\
+\x83\xa5?\xf8\xca3g\xcf_\xfcg\xff\xf8\xd3q\xd3\x08\xe8\xa4\xd7"\x9b\x8e\xa5\
+\x00@\xf2\t\xe8\xa6\x10\xa68?\x80}g7\xeeY\x7f\xf1=\x9bOn^ue\xa2\xbf\xb8jd\
+\x11\x00\xa2\xc35\xda[Z76\xdf\\\xc7\xb5?\x00\x98\x1b\x8c\xfc\xe6\xab\xb7?y\
+\xe2\xea\xe6\xf6\xfc\xec\xc8\x7f~\xf3\x86b\xb3\xe3\xd4 \x06<\x87l\xb9\xf2\
+\xbeqw\x9e>\xb9\xfd\xf8\xe55Q\xf1<\xb4\xed\x9d\xdf~\xfd\xb6\xb9A\x7f|d\xf1\
+\x81\xab\x93\xbd\xff\xd2\xe1\xdd3\x0b\xadvy\xcf\xe6\x93\xd1\xbd\x98\x1b\x8c\
+\x00\xc0\x03[\x92\xfd^76\x7fiq4\x1a\x85\x9b\xd7\x9f\x8fI\x0f\xde{K\xd3\xd7_}\
+\xf2\xe5\xd7\x0e(K\xb0/\xbcr\xe8\xaf\x9e\xd9\xf7\xb1\x87\xdf\x0b\x00\xfd\x11\
+e\xb7\xf3\xf2\xec\xdc\xef\xfd\xd9\x93\r\x02p\xb0VV\xfa\xbd\xde\xde=\xd76\xd7\
+a\x05\x89\x87\xbfz\xe6\x95g_|\xb3qDb(\x1c[\xa2\x87\x16=\xf8~fm\xa7\xd9\xbci\
+\\\xe88Xe\x18\x1f\x19\xac\x1d\x9bw\x0e\xc0\xc3\xee\xb5\xd31\xfe\xfc\xfcx\x1c\
+\x7f\x82\x994\x13\x92Ro\xea\x86\xe2\xba\xa4\x87\x99\x85\xf1\xc7\x8e\xed\xfc\
+\xa1\xbd\xedv\xcb\xf5k/\xdc\xbd\xf1\xe4\xb3\xa7\xae\xb9q\xdd\xb9\xeb\x03\'\
+\xcc%\xdc\xbc*9\x9e\xe3#\x83\x9f|\xcfs\x99\x1a\xd6\x8f\xb5S\x92\xdeH/\xfae/\
+\xbe\xfa\x96\x95\xff\xc9\xe7^k@\x00\xa0l\'\x9e\x9d\x9e97})\xd7"\x1a\xc6\xc6G\
+wl\xdb\x08\x00\xf3\x0b\x8bo\x1e\xd6\\(\x07\xe0\xe1\xd5\x03G\t\x08\x9c\xe1\
+\x04\xd8+\xcf=\xb0\xf7\t\'G\xe7~\xf6\xbe\xa7~\xfc\xceod\x10\x80C\xaf\x07\xeb\
+\xc6\xd2<\xe2\xfc\xdc8\xe1`\xe5\xf6e\xb0\xcb\xfb\xe8\xd1\xebf\x16F\x9b\xeb\
+\xf1\x91Ac\x8c\x1e\xdcv<\x8e\xe6\'Ol?<\xb3.\xe6\xd71g\x84\xd8\x1c\x07n\xd5\
+\xf8\x18\x00\xcc/,\x1e\x9d:\xabdu\xd0s\xbd\xb3\xd33(\x82\x1f\t\x9f\x9b_\x9c\
+\x9b_\xd0\x8f\x8ak\xa1\xdf\xeb\xad\x1a\x1b\x05\x80+s\x0b\xe7\xa6/\x81\xd7\
+\xa5{\xe0\xf0\t\xccF\xfbW\xfeCW)gS\x91\xda\x9e\xa6\xb2\xef\xbfq?3\xf0OOm?23y\
+q~\x0c\x00~\xf8\xd6}l\n\xb0\xe4\xa1Y\xf6o\xc2\x86\xf1\xb95\xfd\x85\xf30\x02"\
+T\x1d\xd9\x93\x87\x93RJZ\xbdy\xeb\xe2\xfaG\x8f^\xd7l4\x03\xc0{6\x9f\xda\xbaz\
+\xe6\xeeM\xc9*}\xed\xd4\xd6h\x0b\x80\x1a\x97\xb9\xc1\xc8[\x17\xd7\xcd\xa8;\
+\x11\x00\xe3#\x837\xa6\xd3\xba\xc2\x95\xb9y\x00\x18\x1b\xedO\xac\x1a\x03\xa6\
+r]\xcb\xd5\xc6\xf5\x93\x85F\xa1\x90Ybg\xf1\xab\xc6G\xc7\xc7G\xdb\x19\x83\'C\
+\xd9\x03l\xdc\x80\x97\xec\xd0\xb9>R\x17&\xceUA?s\xbc\xe2\xa1m\xc9X>wj\xeb\
+\xff\xf6\xdc\x03q)\xad\xe7\xe0\xe3\xbb\x0f\xcay\xe0\xcbg7G\xdcl]}\xf9;\xaf}\
+\xfbw\xf6\xdf\x04X\xff\xc7\xbdJ\x03\x07\x13\xcd\xdaQ\xe9,J\xc4\x81\x07\xf7\
+\xf8\xb1\x1d\x11\x04\x9bW\xcd\xfe\xc4\x9d/\xc4E\xa4\xb7.\xae{\xf2\xc45\xb8\
+\xe0\xa1\x0bI+\x8c\x8f\x0c\xfe\x9f\xd7\xf7Z6\x0b\x00\x16\x96Z\x04\x0f\x96\
+\x06GO\x9ci\xae\xef\xbf\xeb\xc6f^\x80C\xd3\x96\xbbo\xbd>qXw65\x13f\xe7\xe6\
+\xdf9y\xae\xd9b\xbe\xeb\x96]o\xbcu\xdc\x073\x19\xaa\x00\x00\x7f\xef\xed\xbb\
+\x11\x1bh\xa8\xb3.L\x0eA\x92\x7f#\x8e\xdci\xad\xabW\xa7\xdd\xcc\x17\xcfl\x8e\
+\x93%\x00\xb8\x7f\xcb\xf1[6P\xad\xe8\x1c\x00\xbctfs\xe3m5\xe1\x87\xf6\xee\
+\xfb{7\x1e\xdc\xb0j\xb0~\xd5\xe25\x93\xb3?}\xdfs\x7f\xfb\xba\xa4Z\xe48\x98\
+\x1c]xp\xdb\xf1\xb1>\xf4\x1c\xac\x1b_\x1c\x1b\xa9\xda\x18x\xe5\xfc\xa6\xe7Nm\
+\x8d\x14\xbe\xe3\xda\xa3q\x19\xe3\x1b\xa7\xb7`\xb6\x01\xe0\xe5\xb3\x9b\xa7f\
+\'\xe2\xed\xc7w\x1f\\\xd3_\x98Y\x18o\xfe\x01\xc0\x7f\x7f\xfb\x8b?\xbcw\x1f\
+\x00\xcc,\x8c\xa7\rk\x0fO>\xdfnu~\xf7\x07\xef\xb9\xe6\xea\x8d\xe8pz\xfb{\xf3\
+\r\xd7<\xfc\xe0\x9dEn\xf1I\x8bL&\x00\x98\x9f_|~\xdf\xa1&\xe2\xa3\xdfq\xef\
+\xd8\xd8\x18\x1e\x1c\r\x02\xee\xde\xbb\xeb\x81\xbb\xd1:\x01V\xfb\xdc\x1c\x10\
+nq]\xb9e\xe3\xe9\xf9\xb1\xe8H?\xb4\xed\x9d\x17Oo><\xb3n\xa2\xbf\xb8g\xdd\xf9\
+\x1f\xbeu\x9f\xba\x8c\xf8\xda\xb9\xf5_>r}\x1c\x97\x93\xa3\x0b?\xf9\x9e\xe7>\
+\xb9\xe7\x8d3WV7KL\x1f\xbc\xe6\xd8\x85\xf9\xb18e\xf0\xe0\x99\xf3\xff_\xdf\
+\xf4\xda\xee\xb5\xd3\xcd\xba\xc2\xaf\xec\xbb\xf3\xe5\xb3\x9bx\x1d\x91\xcd\
+\xa0\x0cf\x16\xc6\x9f8\xb1].\xff\xcd\rF\x1e?\xb6\x83EN\xcdN~\xf1\xcd\x9b\x7f\
+\xfc\xceo4\xb7\xf7m\x99\xfa\x85\xf7\xfd\xcdW\xde\xdeujv\xf5\x96\xd5\xb3\xdfy\
+\xed\xd1f\t\xe1\x8e\x8dg~\xfd\x95\xdb\xbfvj{\xf4?^}\xf3\xd8\xa3O\xbc\xf4\xc8\
+Cw\xed\xd8\xb6\xe9\xe7~\xe2\x93\xbf\xfa\x9f\xfeb\xdf\xebo\xcf/.\x02\xc0\xf8\
+\xd8\xe8m7\xee\xf8\xdcg?\xa2O\xd9-\xfe3\xa7\x19\xc0;\xef\x00\xe0o\xbe\xfe\
+\xea\x0f|\xec\x03;\xb6m\xba\xed\xa6\x9d\xff\xd3\x8f|\xcfo|\xf1\xd1\xa93\xd3\
+\xb1\xd8\xdd{w\xfd\xcf?\xf6\x898)\x05\x00\xba=\x97\xd9\xd1\xf5\xf8?\xfb\xb9\
+\x03\x07OOm\x8f\n\xff\x96\r\xe7\xfe\x8fo{\xf2\xd8\xa5\xc9fa\xd5(\xe2\x16|\
+\xff\xb7\xf6\xdfv\xe3z\xb2Ps\xfd\xda\x0b\xb1\xc8U\xe3W~\xee\xfeg~\xf1\xc5{\
+\xfe\xfcH+\x9e\x17Oo\xc64\xa2s\x07\x00\x00w\xe6\xed\x82u\xe6\xa0\toL_\xf5\
+\xfc\x19e\xf5\xe6\x0f\x0e\xddt\xe7\xc6\xb4\x9e\xd1\xacd\xb3<wl<\xfd\xf1\xdd\
+\x07\x9e?\xb3u1X\x84\xcb\xb3W~\xed\xf3\x7f\xb9w\xcf\xb5\xcdz\xed?\xff\xa9\
+\x1f|\xf6\xc57\x8e\x9f:\x0f\x00\xbbw\\}\xef\xed\xbb\'V\x8f\x1f=q\xa6\xd9P\
+\xc8\xf0l5\x84\xc7\x80\x07\x80\xe3\xa7\xce\xfd\x9b\xdf\xfer\xb3\x12\xf0\xb1\
+\x87\xdf{\xfbM;\xff\xfak\xaf4\xeb\x8c{\xf7\\\xfb\xed\xef\xdd\xdb\x1cA\x98X\
+\xdd:=b\x9cG\xddo\xcd\x19\x00\x88c(\xf2|\xe1\xcd\x9b\xef\xd8x&\x8a\xb3Y\xf9o\
+\xae\x1f?\xb6\xe3\x9a5\x97\xe2\xed\xaa\x91Ats\x8e_\x9e\xf8\x99g\xdf\xff\x13w\
+\xbe\x80\x9dJ\x1c\xae\x1a\xbf\xb2{]\x82\xd1s\xa76\xff\xd1\xa1=Qy\xc4\x10\xcd\
+\xcaD\x7fq\xac\x97VaW\x8d,:X\xf2\xc1\x90588tq\xc3S\'\xb63"\x8f\x1d\xdb\x11EH\
+(/\xf5\x7f\xfe\x85\x07\xa6f\xd7\xc4\xc3,2<~l\xc7/\xbd|\x0f+~\xf0\xed\x13\xff\
+\xcb\xff\xfd\xbb\xff\xe3\x0f}\xf4\xde\xdboX7\xb9\xfa\x91\x87\xee\xc2\xa9\x8f\
+>\xf1\xd2\xa3O\xbc\xf4\xcf\xfe\xf1\xa7\x19)\xe7\xa0q\xf2\xc7\xc7(8\x90t\xa2\
+\x9f+\x01\xf4\xf8\xd3\xfb\xfe\xd5\x7f\xf8\xd2\x7f\xfb\x03\x7fk\xdd\xe4\xea=\
+\xd7me\x8b\x92\xcf\xef;\xf8\xdc7\x0f\xfe\xe8\xa7\x1eQ[\xe1\xe8L*UJ\xf5\xd0\
+\xc8\xf6[\x1e\xc4i\xa94\xc0\xa5\xc5\xb1g\xa7\xb6\x02\xb8-\xabgG{K\x03\xdf\
+\xbb2\xe8\xbfsy\xf2\xb7_\xbf\xf57_\xbbc\xfb\xc4\xe5\xd1\xde\xd2\x89\xcb\x93\
+\'g\'\x9e=\xb9\xf5\xe0\x85\r\xd1\x0f\x9aY\x18\xfd/\'v\x1e\xba\xb0\xce\x01L\
+\x8c.\x02\x80\x07we\xd0?7\xbf\xeak\'\xb7\xfe\xab\x97\xee\xf9\xb3\xc3\xd7\r|\
+\x9by\xc9\xf7\x9e9\xb9ufq|\xeb\xc4\xe5~o\xc9\x83k*:9;\xf1\xe7o_\x7fnn|U\x7f\
+\xe9\xfa\xb5\x17\xe6\x97FN\\\x9e<77\xfe\xfc\xe9\xab_>\xbb\x85\xf6\xb2\x03\
+\x80\xf3sc\xd7\xae\xb9tqa\xaca\xe9\xed\x99\xb5\xffq\xffm\x97\x16\x85\xe7\xef\
+\x00\x00\xe6\x97\xfa\xcf\x9e\xba\xe6\xa5\xd3\x1bW\x8d\x0c"\x87\x03\xdf\xbb\
+\xb80\xfe\xca\xb9M\xbf\xb2\xef\xae\xff\xf0\xfa\x1d\x17\x17V\xc9\xb2\xa7\xcf^\
+x\xec\xc9}\xa7\xce^\xf0\x1ez=\x07\xe0\xa6/^\xde\x7f\xe8\x9d\xdf\xf9\xa3\xff\
+\xf2\xab\x9f\x7ft\xed\x9aU\x1f\xff\xf0\x03\x00\xf0\xcd\xd7\x8f<\xfd\x8d\xd7\
+\x9b\x0e\xe9\xf5z\xb7\xde\xb8\xa3\xdf\x1fy\xf3\xad\xe3\xcf\xbc\xf8\xc6\xcc\
+\xe5+\xb2\xc3]\xafw\xd7\xde]\xfd\xfe\xc8\xfe\x83\xc7\xfe\xea\x99}W\xe6\x16"\
+\xab\x1e\xfc\xabo\x1e}i\xffa\x0fp\xd5\xfa5\xce9\x0f07\xbf0uf\xfa\x8b_z\xf2_\
+\xfc\xe6\x9f\\\x99\x9b\xbf\xed\xa6\x9d\xe7\xa6/=\xf1\xf5W\x8f\xbcs\x86;\x01\
+\x02\x01\xe9Y\x95p\xe5\xee\xfd\xd8?\xb5@\x10Kn]=\xb3k\xf2\x02\x00\\Z\x1c=<\
+\xb3\xaeq\xa0\xf0i\xcf\x81w\xcd\x86\x9b4B\xdb\'.o^5\xbb\xa6\xbfpiq\xf4\xf4\
+\x95\xd5l\xbf\x11\x875cp\xe3\xbas\xcd\xcerSQs\xf2\x00\x00F{\x83\xb8\x067\xf0\
+n~\xe0\xd4S\xe1\x91\xa5\xd1\xde`aiD9\x87\xa8u\xcd\xd6U3\r\x87\x00p\xea\xca\
+\xc4[\x97\xae\xf2\xd6.r\xb3\xe3\xef\x9c\x03\xe7zn\xfd\xe4\xc4\x86uk\xe6\xe6\
+\x17\xce\x9c\xbb\xb8\xb08\x00\x80\xef|\xff\x1d\xff\xd7O\xfe}\x00\xf8\xad\xdf\
+\x7f\xfc\xdf\xfe\xeeW"\x93c\xa3\xfd\x91~oi\xb0te~A\xbe\xec\xa1\xa1<6\xda\x1f\
+\x1d\xed/.\x0c\xe6b\x9e4\xa5o\xaf\xb6m\xd9\xb0m\xcb\x86\x8d\xeb\'\xcfN\xcf\
+\x1cz\xfb\xe4\x85KW\x1c\xc0H\xbf76\xda\xef\xf7zs\xf3\x8bd9\x92\xd7\x13\xa5O\
+\x1c\x020\x9f@\xa2\x07Z\xa6f\'\x99\x8f\r\x00\xb1\x8b\xc9\xb3A\xc2\x199~iu\
+\xbb\x17P\xaa\xe5\xd2<\xbcx\xfa*\xfa\x10j{\xb1\xb04\xb2\xb0\x94b\xad\xe7\x02\
+\xe6\x06\xed\x93\xf6sK\xc6\xee\xa8vPg\xea\xca\xe4\xd4\x95I\x92I\x1aP\xb4\xfc\
+\x17O \x9e\xbfx\xe9\xfc\xc5K\x98\xe1x\x98\xb3\xd9\xde\x8d\x9b2s\xf3\x0b0\x0f\
+`\xa0?\xe6\x99\x8b\'S\xb8\nog\x9b\'N\x9d?q\xea<\xc4\xc3\xc6\x0e\x00\xdc`\xb0\
+4;\x98G1V\xb3\xcd\xd0\xcb\xe51\x96\xa8\x96\x19<\x83\xa8\xe7\xa9#=\x88K\xef\
+\xe1\xbd\x17<(\x8f|\xd4/J\xd64J\x9bV]\xbbu\xd3\'\xff\xceC\x13\xab\xc7\xd5\
+\x8a\xb6m\xd9\xf0\xdd\x1f\xbc\x07\x00N\x9e\x99>p\x84LU\xaa\xa6\x85\x05\x96\
+\xf5\xe2\x1e\xe4\x88\xa7\xa9\x15\x95\xe6\xce\x13T\x86\xdcC}\xe6\xa3\x8a\xf6\
+\xad\x83A\xab\x8d\x9dRZn-\xae\xdcj\xb4\x12\x10\xf1\xc9\x89\xd5?\xf2\xa9G>\
+\xf6\xf0{\xef\xbd}\xf7\xaf}\xfe/\x8f\x1c?\xbd\xb4\xb4\x04\x00\x1e|\xcf\xf5\
+\xae\xbbf\xf3O\xfdw\x9fh\xf6\x17\xbe\xfa\xe4\xcbG\xde9\x9d\x1a\xa5M\xcd\xb3u\
+V\xec\x9bD:\xce\x038z\x81\xab@\xebJvGu\x9e\xcc\xd4\x86\xa1\xdfF\xe3!\xb8\x8c\
+>\xa8\xfd\x90\x14\x8ft\x96\x9e\x00o\x93VB\x8d\xc5\xba\xde\x7f\xcf\xcd\xcd\
+\xe6Psx\xe4\xaf\xbf\xfe\xdak\x07\x8e\x9d=\x7fq\xe3\x86\xb5;\xb7oz\xf8\xc1;\
+\x9bE\x82W\xdex\xfb\xf3\x7f\xf27K>Mg\x9c\xd7\xb8\xa4\xab\xbbrm\x11CAy\xc4\
+\x0f-G\xc6\xe5\xe4pT\x1e\x1365n[\x87w\x1e\xbc\xbb\xf7{\xfe)a\xab{\xc7)/\xb0\
+\xc9\xbe\xc0\x80\x9b\x03\x94\x9e\xf8h\xffp\x108\xfc~(d\xa71\x1b\x9c\x9e\x1a\
+\xb2\xfaCy\xc8\x1c\xdc\x86uk>\xf9\x91\x07\xbf\xf7\x91\xfb3+B\xcf\xef;\xf8\
+\x7f\xfe\xca\x1f\xbeu\xec\xa4\x95A?\xeegy\xe5\xd9\xcd@J*\xbe\xd2!\xc2\x00\
+\xf75#I\xfcCHSD0\xfb\xa5\xe9\\\xd9/\x95o\xc6\x02\x0b\x12918\x05\x04\xcc\x1f\
+\x8c\x8e\xfa\x10\xef\x000J\xb8\x100\x1f\xed\x8e\x9f\x83+\xf3\x0b\xcf}\xf3\
+\xe0s\xdf<xa\xe6\xf2\x92\xf7##\xbd\xd1\xd1\xfe\xc8Ho~a\xf1\xcc\xf9\x8b/\xbc\
+\xf2\xd6\x17\xfe\xf4\x89_\xfe\xdd\xaf\x9c<3\r\xa0\x0f\xbb\xd46\'\x92\xe4\xfa\
+.h\x91\xdc\x12&\xf7\xd0\xb1T>\xd4\x1cK\xc0\xa1\xcf\xf3`:uo\x0b\xe0\xb4\x1b\
+\xff\xc0kI,\x10\x8d\xed\tf\r\x1b\xc6\x10\xc0\x92\xca\xb8\xb4\x01\xc3d\xcf\
+\x8aD\xe5\xfc\xfa\xc1w^?\xf8\xce\xfa\xb5\x13\x1b\xd6\xad\xb9j}\xbb\x9dvn\xfa\
+\xd2\xc93\xd3\xe9\xc1o\xcbIs\xcau\xed\xe3:5P\'\x86 \xe3v5\xf7)\xa6\xe2%\x15\
+\xe1V\xbe7\xaa\xe8\t\xae\xe0\xd3j5\x81\xe3\x80\xb9\x05\x15\x08\x90\x13-\x1c\
+\xe3\x82!\x9e\xbexy\xfa\xe2\xe5\x8c\xda\xafe\x18\x1cP\xcc\xe1\xa7\xbbd$\x0b\
+\xcc3H\xbfV\xab\ritp\x0c\xcb\xe3\x8c>\xabd\xdb\xfew\xd9\xa5g\x15\x15\xb5\x03\
+{E\x12\x1a\xfa\xf1V\xb9\xceW\n\x15\xf5\x1a\xe4*NZ\xa4\xac\xedVS\xe6\x91u>_h.\
+\x89\xdb\xa0\xadf7\xf4k^\xc9\x91\xe3\xb3kA\x17\x1e\xdb\'cq\x99o0\xc2\xa4PM\
+\xbci\xac\xdf\x99>\xe0\x87!*[\x96\xc1\x9f\xf9h9\x8f\xaf\xe9\xc6\xb8\xe5\x98\
+\xc9\xa1PK\xab\xc7u\xaf\xab\xb1\xea\xa6\xf7E\x12\x99\x89s>\xde \xa7=%\xb8L\
+\xec2\x17L\x82\xa3]\x04\xac\xec/<\x04\xbb\xa3Y\xaf\xc5\x8bkGrV\x9dd\xf1\x18\
+\x03\x869P;\x11\xcfYs{\xe1r\xc2\xe3\x81\xa3L\xba)mVm\n\x04brX\n59\x95<\xaa\
+\xef]Y#\xed\x16r+=\x8c\x8a\xd0A\x95\xd2\xc1E\x1d\x05\xbd\xdb\xc9.\x82\xf9TrW\
+\xe6\x86\x1ey\x985\x11\x8a\xa2\x1c\xe6U\x12\xd9\x90]\x81\x97\xd5C>\xf3\xd0\
+\x0f\xea\xe7J9*K\x9c\xe2[\xe6\xf3\xc7U\x9a\x1f\\<\x81\xc0\x9a\xf5-\xf3\xa0\\\
+\xe4Pg\xc9\xc8U\xa8\x14M\xb1$\x0e\xccq`rF\x05\xaf\x19\x02\x9d\x07r\xa7\xd7\
+\xd5\x15\x07\xcclc\x960Q\xc3P\xd8/\xc6\xa2\xabC\xe9\xa4\xa7\xf7\xce\xb9>P\
+\xf1K\x8e\xf9\x81\xc9\xec\x1b\n\xf1;\x0c\x04)\xde&\x95\xdb\xf4\xb6\x80$\x95\
+\x0e\xb6\x80lB\xda\xb3\xac\xa8\xaeW\x08\xe2&7\xe0E]AiK~RP{\x8e\xcdS\xd0c\xe7\
+\xd89hq\xc0\tP\x14P\xa1\xf7\x15\x040\x95 _Q\xcc\xdeW;l\xe0&X\xcfTuD?\x90\xe8\
+\x06\x94n\xca\x1f\x95o\xf1\xab\xa2X\xcd\\\xaa\xcb\xd8!\xec\xec\xee\xe5lA@\
+\x80\\\x01J\x8f\xa6\xb7\xd5\xe4_R\xbb,\xdb\xaf\r{\xe3\x8eo\x14\x1b\xef\n\x1c\
+.X\n\x00m\xd3\x8b\xea<\xcf\xe9\xc1|\xea\xbb3\xb6\x18\x98\xbc"Hu\x1e\xa8LY\
+\xcbK9J\x0e\xe4\x13d\x08\xa8\xb2\xcf\xbe\xac\xb2`\x08\x97#\xc5|\xd7\x9b\x85\
+\n\x93\x1a\xae\x1eXE\x1a\x0e\xf0-_\xe9\xeb\xda@\xb9\x92S.\xd1m@f\xf2\xd3g\
+\x11U\xeb\x9ei\xcfp\x8aa\xe8\x99XuFu\xf1U\xa9K:\\\xfc:F\xd1\xb7\xbf\xe0\x05\
+\x00I\xb9\xce\x8f\xb3x\x8e\xf7\xa9\xac\xe6F\xd4\xd2\xc1I\xc6c\xc7}U\x90^\x00\
+C{\x19}\xae\x14\tb\xcd\xa40j\xa9K\x98\xc9\xa9\x86!feF-\x8eC d\x96\x0b\x00\
+\xa6\'\x9f\x89$\xa5MC^\x0e\x95 \xb3G\xac\xf6nc\xa1\x9a\x9c8\xe4#\xd1\xc0\xbd\
+I9;\xa0\xd3\xbf\xe2;\xd0S\xb9\x92\x07J6\xb7h\xa5\xe6,\xb7\xe4\x9f\x84\xe24\
+\x05yyV\xeb:\x87\x1a1\xb3<\x8e\xc6\xdbU\x9b\x82\xc7\x90\xd5\x9f@2\xba\x92\
+\xb2\x11:=\xb7\xaa\xa1\xbc\xe5\xb0\xbd\xc8,\xb08yU\x15\x8a+$\xdd\x95J\x98M\
+\x19^\xa4Zc\xa6\x96\x15\xdbS\x95\xb3\x12a\x9e\x8c\xafO\xa1\xcc\xf1B\xbe\xbd\
+\xcc\xa3\x05\x05^\x07\x8a\xf4\xfci\xc7r\x17\xe7\x11`\xbc.ZY\x1e(\xee\x0e\xa8\
+\xe2\xc9\xb0\xa7\xbaxq\xf5\xad\x8dc\xde\xa2\xd0\x07C\xae7\xa0e\x15\xd0PR\x98\
+%\x1a\x9dV\xdet\xa0\x05\xfbq\x02\x98\xdb\xff\xf5\xe2JL\xb2dw$S\x82[R\xd9W\
+\xa5\x85}\x9aW8\xf6\xbc\xa8\x88\x0fbMI\xaaQOCG\xc7\x9c\xeeiz\xfd\xb6\xb8z\
+\xc8\x16\x7f\xa4>&\xdcv\x9a\x82fC\xb3bH\xab\x0cZPv\x0b\xaa\xda\xc7\xf3\xa0\
+\x12\r\xb5\xc3Bu(T\x89\xa2\xcb\xdcy\xa7\x8e\xfaYO\nK.f\x8f\xe7\xfd_\x10\xe21\
+\x94\x87VsA\\\x9c\xab!\x88h-\xee\xebC\x9f\xdaz\xdb\xbbp)\x19\xcb\xc9p\x05l\
+\xce\x84\xceo\x9d\xc7\xa1\xe6\xdc\xb2ja\xcep\xc8\xa8\xdc\xe4LX\xae\x99Q\xad\
+\xb0\xd1\xd0\x9e\xfb\xf0\x14\x07\xc8\xb1\xaf<\xac\x90\xf7o\xd45\xc1"\xdb\xfd\
+XB\xe9(\xa1\n\x14\xdf C;\x1f\x0c\xc3\x80\\\x87*\xb2\xd6\xee\x11\x8eR\xab)\
+\x86\xb4\xcc\xae\xf7;\x97\xb4\x13\xc3\x81\xaa\x0fm\x92\x193yJK@\xb3\xe8\xaa\
+\xa7$\x89\x80Rc\xfb\x9e\xca\x93\xb1\xa2WC\xaf\xa5\xf3\xd45H\xc9\r\xb7Al!\x80\
+\xcc\xec\xa9I\xab\'\xcev\x80H\x95A\xf8N\x0c\x1b\x08\xa9-\x04\xd0$\xd3b@u6+w#\
+\x87;M\xd3g\xae@\xc4%\xea\xae\xc4\x8a\xaa$<\xa4}l\x82\xeb\xd6V\xa0\xd7\x88k\
+\xd3\xbf\x95B\x00%\x81/\x1d0\x1c\xd8Aw\x05\xbc\xd6\x96D\n\xb5\x00\xa1 \x8d)b\
+\x08\x90\x9b\x19\xf3v\x17\x1c\x13\xf6\xd0\xef\xe3o\xe8\xf0\xc7\xd0<\xea\x01\
+\x88\xcd\xd7\xb9\xf4\xb1\x00\xfb\x93\xd2Q\xdfU.\x00tE\x00~,\x82\xf9\xa4r\xcd\
+\xb8v\xe8\x07OY?\xbb\xe7\x19\xc8p\x8a#1\x0e\xcb>\xfeI7\xed\xeb\xc8E~\x95\xa9\
+\xa1\x8f\xa8\xe4\xbf\xe8\x05\x00=\x1f\x9et\xf4q\x8d \xfe\x8bM\x03|\xdd\xfe\
+\x0bW\xda\x9fX^\xf2\x8c\x9c\xff\xe6\xdf\x8a,\xa1H5H\xf6\x81\xc2\xbf\x9ai\x8b\
+G\x0f\x8f\xc6\x0bN\x13\xdadQ)R\x14.\\\xc4\xcaQ<\x83ge\xb0\x1c&\xef\xbd\xf5\
+\x8cP\xcd\xa0\xea\x05\xd1\x11\'(\xd0\xc6\x7f|l\xb8Wp\xe1c\xd1\x1a\x91Z[p \
+\xc5\xb9L\xcb\xa08\t\xb5\xaf\x11L,I\xdb\x9d PA\xc7)W\x98\x1f\x9e\xd9\xfa\x87\
+\xf8\x07$]\xf6<\x88\xa3\xa1\xcc^z{\x19\x12,\x1e\n81e\xf1\x8a\xa8\x83\x06\xc5\
+\xe9U\xa0(n\xf2V\x06\xfd\x0bn+\xa0e\x04M\x14\x88|\xc9\xac\xc0U\xe0\x03;\x13\
+\x15Z\x8aB\x01\x82\xbc\t\x87A%\xe4\x83\x0f\xa1\xb9\xed\xe9UG\xf5\xa0\x9a\x82\
+x+\n\xe1\xf8\xaeBHJx\x19GW\x94\x15\xb7\x95\xc2\x01\xa6\xd9\xfa\x9a"\x8f*z\
+\xa1E\xd8 \xc2G\x19\xd8?\x85\x8ddPL}\xc6d\\\x0cA\x13`\xee\x89A\xf0\\\xf6\xb4\
+=zTu\xbf3\x95ce\xea$H\x86\x03\x02eG:\xb1\x86\x9a\xed\xa6\xc5J\x0c.h\xa2\x98\
+\x0f\x84k\xa2?jkg^\x8e\xa5\xf6\xf5G\x07\xc8\xa9\x11\xef\xbd\xefi\xd6\x8e1\
+\xce\xd5,\xf7\x1bT/"\x1b\xa4\xc5\x89\xaf$\xe1\xca`\xa8q\xcct\x80b\xd4\x01\
+\xa0+\x0et\xe8 >\xedq"\xb4\x93]\x85\xf0\x03\x98V\xe0\n\x00\xe1\xa0\xe8\x04X\
+\xea\x81\xfa\x04L\xa1\x87x\xe96*PP.uFr\x91\xde\x83\x1f\xde((\x8d\x14\x98V\
+\x83\xa5ZI$\x15\xa2\x171\x82\x1b\x05\x01:\x1f\x14^\x84\x19\x81\x06\x0b\x19\
+\x89\x98\x81\x86N\x87J\xbc`\xb4}\xec\xd1"A\xca\x05GU\xd9fl\x8c\xa9\x03\xe5\
+\xd0\xba\xe4\x979J\xcb\x9c#0&\x03\x8b\xcb\xa2\x83^\x0f\xe3m\x06\x99j\xd4\xe4\
+\x1f\x9d\x0c\xf2N\x13\xee/;\xa5P\x93-|\xfa\x8c\xfb\xd7\xf2)r+\xf4\xb9\xbc\
+\xcd\x86\x98\xbaS\n\\vJ\xdaV\xf6\x02\x07\x99\xa3\xaa>;\xcde\xe5\x18\xe6\xb2\
+\x9b\x99\xcbA\x80\x0fg\xfb\xbd\xd3\xaa@\x83\x81\xd9$#\xb4s\t\x0f@\xdeI\xddbC\
+\x0ch\x04\x1a\xe2\xeb\x00\x00ZhR&2\xf69\x8c>\xa5\x13\x95\x9c`S\x1d\xdc\xc8:(\
+\xc62vR\xe8\x17\xba\xc5>\x19C\x00\x00 \x00IDAT(\x9b\x15\x03\xd3\x16^4[)\x12\
+\x18]\xe9g\xd3\xda\xa0\x91\xe4p\xf7\xe2"K\xc8\xf1X\xf4\x889N\xf38\x9b=\xa3f\
+\x1a"\xdc\x90\xf7ohP\xe8\x13\xa4:\x9d\xfb\xf4\xf5w\xb6\x8cJ\xc4d\x8d<O\xc6\
+\x85\xf3`\xcb\x88=\xd5B\x94\xc1pC\x17\x91\xca,\xc7\x82\xecVOQ\xab\xaa\x99\
+\xc6(\xf8\xa4\xe0d\x83$\xe7<\x1b\x15\xb6\x13\xb1DCH\xe5\xc7\x0c\x01\x9a\xe2e\
+\xe0\xc2\xde\xe6A\xcd\x81\xde\xd1\xb83<\x93\xbc\xd6,\x12\x82\xd1\x8c\\7\xa2m\
+\x1cZ\x80\x8cF\xf0\xe9c\xb9`\xcf\x820\x9b\xed/{\xe9\x84\x9e\xd7^\xae\xee\x84\
+\xb6F\xdf\xb5\xaf\x890a\xc0y\xb4\x80 \x1d\xafx\xd2\x9dZ\x01\xbe\x1f\xe6E\x8b\
+\xa8\x81\x88\xe68]#\x1c\xc4\'\x90\xf4\x90\x04Op@\xb3x\x02@^\x06\xb0\xfah-D\
+\x1c>\x80\xa0\xaa\xd9\xd7\x16\x07\x0e\xf4\xf7\x11\xa9\xc8 \xe6 \xe3\xb3\x15\
+\x03k7O$\xfa\xd1\t\x17\xa1\xe4\x03\x90,\x9a\x02\x082\xf6\\\x17Y\x9e` \x81\
+\xdc\x02\xe1+\x00\x1a\x00\xb1\x97\x82O@\x9c\x81T\xa9&S\xa1\x1a\x81\x8e<\xa6\
+\xfe\x13\xe1\x06\x10>\xee\xa0\xf1\xde\x89\x14\xb1Q\x88\xfa@4\xb2SP6\xacC\'Zs\
+]\xca\x93\x02&\x84\x8f\x88\xd6\x12\xe8\x92\x06L\xba_Q\x0fq\xef\x11\x84z\x08\
+\x1e\xb6d\xd5\xdc5\x15V\x9e\x01\xa8\xaf\xf9\x82I|\x00T\x01p\x99\x1a-\xe6\x1e\
+r\xa4\x14\xf0\xe9\xb4\'\xeb\xd8H\x8aP\xa0\x0f9`\xcdV\x19\x86\xc3M\xe2D>\xf2\
+\x9d\x80\x9e\x9c\x83\x06\x07\x8aJ7\xb9b7(\x82\xee;\x03v.\\x\x06\x8a\xfe\xa8\
+\xf0\xe5V\x83\xb6*\xa6\xf6\xb3\xe0o~\xb1\xbc|"H\xd5\x82:yh3\xa4\xd9A\xa2\xe8\
+\x9d\xc0\x12]\x8dh\xd1\x8a=.\xbc\xde\xe9\xa0\xb1\x11\x85\xcd\xd3\xee\xd2WA\
+\x96\x84\x9d-\xda\xe2\x00\xd7k\x88Gp\xa8<r\xeb\x13\xe6\x91Y\x10\xf6?\xe1 \
+\xf0\xaaU"\x0c\r\x8a\xef\x13S\xab\x0fn\xa6y\xd0\x0b1\x00\x9a\x87R\xe3\xccY\
+\xa9[\xce\x9a\xd2\xe2SL\xe7j\x07\x900t\xcf[.(IMl#\xc0\x84\xac\xc8\xc7\xf8\
+\x81<>\\\xd0_\xa9 \x92\x98\xe4\xc7\xa1\xff\xc9e\xab\tt\x17\xb0\x04FZ\x83b\
+\x07\xb1\xbb\x00bv\xd0\x02\x90\x9a\'\xc4p\x13\xd1\x1auh\x18\xc5\xe4X\xd0\x9f\
+QA\x86\xc5!\xe9\x03\xf6;B\xee\xd2\x0c\xac\xb4\xa0\x848\xae\x8f\xcc\x9b\x8f\
+\xdc2\x94\x8f\xca\xbb\xc9\xe0\x1c\xb5\xefi\xc4\xb7\xede\x1e!u\x17\xd2b`\xd4\
+\xa1Q%x\xaa\xec\xa9\xd2\xb0Y\x0f\xb5\x11w\xa1\xe7\x01\xe2\xbf\xf6\xd7\x83\
+\x87\xd6_\xf2\x90\x92Q6@\x1eUx\xc3\xb9\xe2X\xa02\x89H\xd8:B\xb7\x00\xe2Od\'\
+\xb4-\xb3\xb6\xc4v\x9b\xf8\xe6\x93dMv\r\xea\x9dZ\x07"K\xd3H\xc4\x08\x08\x1b\
+\x01.*\x00\x17~\x114\\\x8cH\t\x0eP\x1e\x07$w>\x18\x99\xc9\x06R\xea{O\xeeq\
+\x9e(\xc6&*v\xbb\xc79\x88\r\xe0( \xe2\x97\x00\xa8Tv\x95\xa3?\xc2\xc8hK\xa1\
+\xb2\n<\x94\xd4F\x9b\xcbq\x04`\x1b\xe0\xc2\xff\x92h\x12<\xd3\x1d\x84\xbfz\
+\x1c\x80\xd2\xa8~\xe04vY<\x11\x1b\xee\rR\xadO\xd6~\xbb\xdaS\xe7D\xb3\x11l\
+\x99\x81X\xbd\xe4\x079\xe1\x89C^\x03\xeb\xbcy\x87&\x96\r\x8f\x10m\x18\xa2\
+\xa9[z\xb5*\xe9s\xd4\x05G\xaf\x9c*\xa9<e\xc5\r\xc0}\xce\\\x86\xd64X\xce\xa0\
+\x0c\xf2\xfd\x04|0\xaa\xc7_\x1bq\x06\xdf\xcc#\xd9\xc5b\x0c\x05\xd4\xdc\x077*\
+\xc8=x\x19\x84\x7f\xde\xeb\xc93\xc8\xef*\x05\xe6hY\xc5^\xea]\xa38\xa1fmVp\
+\xe2\x82*\x80\xccpF&\x90e\x8a\x02G\xd3C>1H\x90`U\xe4\x0e\xf4\xf6\x01)\x82\
+\xc4D\xe4I\xf1\xfa\xf1HE\xe2\xe73\x7f\xcf;<\x81$*\x03\xbcn\xe4\xda\x89#A\x15\
+\x90\x9a\x86\x1b\x89\x98\x89\xe2q\xfd|\r\x962\xe0\x91\x8eGG\x04\x98\x990lTi\
+\xb9tN=hh\xaf\x16\x90:8\x95\x17\xb1\x1e\x00\xa0\x17\x9c.i![\xfb\xdf\x1c\xd4@\
+\xebj\xccr\'\xa5\x1b|\xbe\xf6_r-\x11A\x8f*K\xdeA\xa0\xe3\xe5\xad\xd6\x1e\xb2\
+`P9N5\x0f\xa0\xea \x9e\xa6(\x01,\xb8\x04\xdb\x1f\x87\xbb\xa39\x1d\xfd\x1f\
+\xb9\x84\xc4\x1aqM\xe1\xd2E\xf8\x0f\xa8\xdf\xd85\xe0~\xeb%\x1f\xcf\xfe\x97\
+\x00A)\xc4[\xcf\x01\x10\x05\x99|2\x1fUN\xf4\r\x89\xbc\xe3\x85\x86\x03\xa5\
+\x11\x9e@\x01\xcbRp/\xb1]\xd9OJN\xc7\xee\x88\x84\xf3\xae\x9fF\x87\xd8\x06g\\\
+\xd3[\x81\x03\x95\xc5\x1a\\\x84^\x0e\xe6 \xc5*POk\x83I)\x93\xd5\x02E!\xb5FKZ\
+\x13\xce\x87w.\xae\x1e\xb5\xeb\xef\xd8.Xn\x01\xba/X\n\xb5\xf6\xa8IAl8\x95\
+\x82U\x17\xd6\xebxX\x0b\xdbP#\xa0\xa8\xfcy\x15\xc1\xe2\x87U\xd7\xb8\x08\x1f\
+\x9c\x83\xd6\x03\x060\xdd\x00\x11\xd97\x10@\xaf\xd3"\x92\x87\xe8\x89\xd8\x02\
+F\xe2Gs\r\x81\xae\x80\x1f\xdf\xe2 \x9e\xd8\xc2\xfb\xd6\xcd\xffh\xefQk\x16"c\
+\x07\xf2n\xfb\xe1N\x9d\x14\x0bI\x81\xe7\xe3s$\x83+\xd8\x84\x80Z2\x9f\xf1\xc8\
+C\x88\x0b\xf1\x01\x05\xc4q\xa0\x81\x89-N\x11\xf3\xa1Q\x1c\x0eI\x07S\xc2\x8a*\
+\xd5\x12D\x0b|\xa6 \xd7\x88#\x0e\xf0\xf6,9\xc2\x80\x92\x9a\xb1\xa0u\x9ee\xe0\
+\xe5\x97q$b*\xe7\x8ah\xbd\x86\xdb\x82T\x9b\xca\x041\x10\xb9\x9c\xba\xe0\x90\
+\x9dH3C\xa1\x11\x08\xa3t\n\x99\t\xea."@B\x1e\x8d\xe3T\xa3\x07\x8fL@l\x9f\xf7\
+\xbem833>\xfe"\x1cX\xc3\x9cM@<\xb0\xde\x17{\xcd,8\xe0\x9fm\xc4X\x95\x16A2\
+\xc2\xf92\x8d\xb1Z{p\x11\xac\xec\x15B\xca\xd0N\x93*]\xe0H\x9d\xd8\xb5\xf0]D\
+\xd5\x17fP \xe2L\xbeb0\x01m&\x17w\x81\xd9\xa1)\x8f\x7f\xa9\xeeO\x89\x88\x014\
+\xffD\xea\x81\xed.\xaa\xa7\x94\xd0\xd6\xb3\x194\x1c`n\xf1\xd0G)Uf]\x98\x01,\
+\r\xf2t>g\xa9\x86v\x9b\xd7\xc5#n\xd4\x8b(m3\'R\x8e9\x86z\xee0\x82\xf8Kl=\xe0\
+\xb6\xc4\xc7\xac[4x\x9f\xae\t\xa1\xb4\xc7F\xadL\xb3\x9a\x84]=\x1f\xf5\x06\
+\xc9\xd4\x9e\xe72v\x99\x11\x7f\xd1^q\xcdOuI\xc2\x01\xd01;\x04\x02xZ\x82\x82j\
+;dLF`\xc9/L!E\x90\x95#C\xbf\xeb!\xac\x13\xd0\x19\x97\x1e\xac\x8eN\x0b\x03\
+\xf1\xc0\x98\x0f\xd3+\xb4\xd2@\xf9F\xf3/\xb4\xd2\x80\xaaI\xf3K\x94\xcd\xa7\
+\x0boqd2\xc9\xe3\xb0\xe7[E\xa6\xc3\xf6\x92\x02\x1e\xe2\x18+:G\xa7RHJ\xda*\
+\xce\x1b\x91\xf6\t=m40\xd0\xeei\x89\xcb\x08\t\t>\x88\xb1M\x90\xcb\x0c\xa4\
+\x1c\xfe\x89\x16\\[1B\x17]p\x00\n\x14r8p\xfc\xa2\xc3\x92L+\x9d\x94\xdf\xa3\
+\xffI\xba\xc4C\x99\xb4\x8a\x0c\x8c\x82\xc0\xab\xad\\X[\xd4\xd7\xdafM\x88x\
+\x16Z\xa6\x12m\x8f\xcf\xe1r\x0f\xc0\x0c\xc1\x0e0\x0f\x0e)\xf2\x02\x81BH\xf3*\
+l\x02\x96A\xd3\x01\x91(\xfd\xf1\xb8\x13XN\x8d\x92\x85G\x96\xec\x8d\x02NM"\
+\x939z:\xa9\x97\x1b\x1cZ\xf04\xc8TN\x85\x8cc\xa4\xe0\xc31\x04N\x81d#\x8b\x86\
+r\xd8\xaa\xca@._\x9amAg""\xcb\xad\xed2J)\t\x19i\x0e\x17:-\x033\xbb\xc3\x00c\
+\xad-R=G\xcc\xc1r^\x0c\xc0\x83\xd0\xb4\x16\x0e\x94\xa2!\x1b\xca\xef\x15\x08\
+\xac\x10{\xccj\x81\xd1\x0f$[\x0c\xcc\xfd_\x99@\x8d;5\x00L\r\xa0\x1bn\xfb\xa3\
+\xe8-\x1c\xc4\xdb\x04\x82\x95D\x80\xcdn\x1b\xa3(\xa9\x0c\x85\x8a\xba<\xbf(\
+\xe7\xce\xba\x8a\x85\x89e\x13\x02\x02t\x08\x18b\xaa\ra\x88\x0b\xef\xd2\xeb\
+\xa4y4\xa2Aq\x90\xdc\xdb\x10\xdf_q\xd9#\xee\xf8\xd4\xbb\xd2\xe6&G\xc0X?BC\
+\xa4L/\xb7@l\xcc!Q\xfd\xfaC\xa7\xc5\x10}\x98\x86\xcbb\x10O\xe7\xb6\x93\xbeD\
+\x0e\x93FY\xa2+\xad\xe8\x01\x8f\x7fsa\x85g\x07\xf6S\x86f\x04\x9dDsGa\xe5\x11\
+\xdau\xef\xb8#\xf5\xf0G\xb3\x1d\xb1z\xe6\xc8\x99(a\t\xc97\xf2\xd1\xf5I\xf1\
+\x19g\x85(\x83\x14\x15\xc2J\x82\xc0^\x1a\xf5\xa6~\x95%\x14!QT\x08J\xe8\xfc\
+\x9e8\xbe\x9f\x1f\x85\xf1\xe5@\n\x17\xdcQ(\x07<\x18\xbb\xab\x7f\xcc\x91A[\
+\xc6y\xb5C\x10\xb9\xf8\'\xdck\xc4Wz\x9d\x80\x05}\xd89\xc9\x0c\xb3rV\xabj\xba\
+U\x07\x04C\x8a\xd5\xd9\xa2>\xa9he&\xcd\x1c\xa7\xa4\xa8\x96\xeb\xcd\n7\xa2\
+\x84\x1f\x14\x9b\x06W\xb1cx\x9f\xe3>\xd6\xbf|\xd2)\x146p\x91\x07-\xf29\x9c\
+\xcf\xa7<\x95\xceC\x86%\xe3^.\t\xa3\xca\xad\x10w\xb2\x03sr\xb1\xa3\x9eaU\xeb\
+\xb0C\x07&3R5\r\xabq\x08\x99wS\x138\x87\x00\x92\xef\xa3|3\x9c\x96\xab\xca\
+\x7fo\xd9\xe0\xe4\xf8\xeao\xf1L\x8a9\xde\x92\x12\x88\x83>3\xef\xad\x0c\xce\
+\xb8.d\xcd\xe4\xc8g[\x01\x10d\xe6\x17\xa6\x97\x88-\'\xc8kc\xf1\xc3&d\x06\xc7\
+\x85\x9d\x91wI\xa5\xd5\x8e\xba\xfc@\xae\xb4\x0b\x89\x97L\xe6\xe1V)\x04\xe5w\
+\xd9\'\xc0A\x95\x98.F\xa3\x1b-L\xd5\x8d\xb2(~\xf5\xfd^\x9d\xce\x1aY\x9e\xbf\
+\xb8P\xfc^\x851l\x97\x90n\x91\x07\xd3T\xb5\xaa\x0cu\xdbIQ\xc3\xbb\t\x02\xaf\
+\x0ct\x9b7\xd9_y\'"\xd2\xcd\x90\xe4\x1e\x00\x93}\xf1\x1d\xc0l\xf4\xe3\x8d\
+\xcd\xca%\xcc\x95]J\xb4\x08\xeb*\x1f!\xca\xb3\xbc4\xac\x0c\x08\x86]q\xea\xbc\
+\x0e\xc3\xfb?\x8aG\xee\x11h\x08\xe8V\r>\x8f\xe4\t\x14\x14\x1c\xc4r\x9e\\4\
+\xd5\x17j\xeb\xdcy\x88\x1b\xd7\xfe\xab0\xfd$\xbc[\xeb\x042\xc8\xd6\x11\rGW\
+\xc8E.Q\xdaX\x19A\xfb@\x92\x90\xc3\x08\xe8\xfa\xd2_\xfc\xd4E+\xfb\xc2j\x07s\
+\x0f\x8d\x0eh3xZ,fhXU\x9a\xe3\xf1\x1f\x0ez\x05\x07\x9ee\x94\x83\xce\x01\xb0)\
+\xe2\xd2\xd2\x12\xcb\xd3\xebuF\t=\xc1\xad>S\xae\x1d\xe3\xa242\xa0v,\x9boc}\
+\xf6!\xf6\x1c\x93\xa0\xa1\x0e\xb1\x10\xb7\x9b\xcdu\xecLE\x96\x97\x1b\xcdc\
+\xa7\x05\x04R\x16\x07\xd7\x90\x12\x87\x8c\r\xcc\xa2W\x1f8p\xbd%\x14\xba\xb1\
+\xa2\xb2\x97\x19d\xc91#?Yr\xc5D9sN\xac0\xc6:\xdb,\x9a\x9do/1\xc4\xa8\xa3VS\
+\x07\xd8\x94a\xb5\xe1=\x896\xf90C\xab\x088L\x05\xbd\x98/\x86\x95\xdf;0Lo\x9a\
+\x97\x18\xcc\xea\x1e\x02\xb2\xb0\xd8\x0e\xc7sG\xc9\x16\xc4D\xba\x89]\xd1\x83\
+5\x931L\xd0"I\x07\x9f9=\x96rA\x9e\x8dZ!\xcb&\x0b\x86\x80ZR\xb7\xf0\xd1\\\x15\
+@P\xa9\x1eL\x9fK\xae\x159z\x1b\xb9\xa2\xc5x\x04e\xdcS\x14\x00J$8hN>\xa2\xf3%\
+e\x86\xb3A9D\x93\x07\x19\xab$\xa7\xa2c\x15\xd4\x1b\xf0\xf4\x8fJ\x96\xdd\xcb}\
+\xa2x/G\x92\x07\xa8\xd1\x04+b&@\x93>\xf1\xb8\xf5\xfe\xd4"\x89\x05\xe0\xfd\
+\x93ryyU\xa1\x18t\xb1\x19!t%%\xab\xc0\\/Y6\x8c\xc9\xa4\xf0\x10&\x05v\x10\xba\
+V\x1b-MhA\xd0\xd5m&\xb5u\x18U\xd1\xd7b\xf2\xa7|\x9a\x85yE*\x04\xf0d\x01\xab\
+\x84\xe0\xb97I\x9eQY\xa9\xc0\xfc\xbc\x15Z\'\x90 \xab\x0fq>\x82\xfb\xd9\xc7D\
+\x07nY>A\xe1+\x0b\xca\xc6\x01:p\x0cZ\xefk\n\x13\xa9.S\\\x1a%\x01\x85P\xbfB\'\
+\x83\x03#\xa9z\xeb\x82RR\x9d\xc4\xe4\'(4\xb5\x05\x00G\xffE*T\xd9\xd3jL\x9d\
+\xb9\x1c\xc7\xb0\xa0\x00D\xb2\x90/\xb2\xd0\xf1\x8f\xea\xc9\xa2\x12\xac\xebi\
+\x07\xf9\xd4\x93\xc8\xeb\xe1\xdeb\xc0\x01W{\xa6\x14Rd\x85\xce\xb3W=\x8c(\xec\
+\xe6\x08\x98\x0cs\xa8)\x85\n\x17\xa4\xa9\xa4\x07\x9a\xbf\x83\xc3\x10K\x05\
+\x00\xc2\xd3E\xb2\xa7\x0f#\xc8\x821W\x86\xb8\x12\xe3\x83\xc8\xc9\x1a\x9f\xf4\
+\x16\x81\xe0\xa0\xab\x11T&`,\x8d\x00S,\xeej\xab\x02B\xefy\x19\xad\xd5-\x92\
+\x05Q\xcb\xbfd\x94\x88\x80%\xd2\x87D\x00\x0f\x9e\xdf\xea\x1aYB\xd7\xf6\x15U\
+\xf4\xd84,\x1c\x84\xbb\x15\xf5\x0b\x94\xa9x\x8a\x8e\x97Y\x89z\xc5x\xach\xc0\
+\x1b\xaa\xc3\xcb\xb8\xd0q\xea\x99-\xf5\xd2\xac@\xd3g\xf1\xba\xbb\x9e\xcc\xe3\
+\xa0\x18\xf8\xaecyMS\x9f\xe8\xb2\x9cq\xad\x92\x00\xd8\xd3\x8b\x10\xaf\xf1\
+\x8as\x0f\x13Z\xc70\xb3\x93\x96\x9f\x1f\x9a8\xe8\xb4Wc.\xc9\xa7\xb6e\x9c\x1a\
+Z\xaf\x01\x8ewoD\x19c\x9eeh\x98``\xce\xe0\x19\xa7\xca\x0b\x84\x19\x91H\xc7M\
+\xd6\x1bI\x9c/W\xdb\xe7\x9eF\xcaw\xbe~\x8e\x93\x9b2O\xb3\x02\xedq\x92\xdb\
+\x1c\x80\xbc\xa0\xba\x84P\x0c\xfc5\x17yd\xb1\xa7\x01Q\xb1R\xaf\x10\xc1&\xef\
+\x06k\x89\x8c\x0f\xdbE\xb7\xc5P\x00\xc1\xd0>\x01\xd5\xbcm\x90\xbe>/R\x0cxB\
+\x94+\xc5\x01\xbf\xac \'hz\x9er5\xd4\x8f$\xd7Za\xe6\xe3\x08\x85\xa8\xf8ZZ\
+\xbc\xf4Vi}+\xb9w\xc0\x95\xc1p\xef\x05\xb2\x88\x87?D\x83`EQ\xd0<\x96IM\x8f#\
+\xe6\x19\xa8\xf7\t\xeaCn<\xe3\x11o\x9b\x04\x91\'\x7f\xd8E\xe7\xf9\xdd9O\xd0\
+\xec\xbd\xe2:+zL\xc9,\xf7\xc8\xa5yH1eU\x82\xa6\x03J\xbc\x8e\x03\xbe\x10\xcf\
+\x93\xf8\xe6(\xa5\x9be\xa6\xcc+\x92\xaf\xbe\xac.\xf6\x19\x18}\xbc6\xd8\xfcs\
+\xf2\x14G\x0e\x04\xcb\x9a\x1f\x92\x1e5\x14\xe8pc\xc9\xc4\x01D7\x93w\x08BH\
+\x1e\x07]\x03Bi\xec[\xba\xeaU\x05N%\xc81/R\xa5k\xc3V`\xcc6\xb1\xa1\xf5-;c\
+\x08\xc0\x9aB1PXic*!\x08\x9c,\rU\xf7\xb6pF<\xbad\x16-\xfc\xd2\x15p\xf1\x98\
+\xb7\xf5\x04T%?L\xce\x9a\xd9\xef\x82$\xcb3\xe0!\xf0\xba\xc2 `\xd3\x04\x84K1B\
++X\x8c\x1d\xaek\xda\xa1F\x98\x1e\xb4\x83i\xeal\n\x04 \xc1t\n3O\xde[\xac{\x05\
+\xce\x02 \xbak#\xe3$K\xc2%l\xaeM\x10,\xc7\x16\xa0\xd7\x17aP0\x1c\xa4G\x82)\
+\x8f\x19+\x81\xcd_~\xecW\r\xc8\xce\xeb\x84\x8e\\\x04X\x0cm\xd5\xe4\xbf6\xc5\
+\x1a\xfeY\x07\xd8ph\x8a\xdc9\x03\x04+\xb2Z\x8c\xa4\xaf\x18\xad\x0cO\xf8GR\
+\xd5}e%\xdbJ\x06\xa2\x06l3\xc6V0d\x9e\xba\x89$"\x81\xe0\x80\xee\x05\xf8(\x8a\
+\x82\xa6\xaa\xa9i%\x16\x8b\xf2A\xc7A\x86#\xed\x95\x1a\xa5:\x86\xf4\xe9\xa0\
+\xda\x80\xbbp"\x8eD*h\xcd\x90sI4J\xbcQ0\xebS\xe5\x03\xa9O\xab\x04s\xf3\xae?\
+\x81\xa4\xcc\xbb\xda\x88\xea\xc7\xf2\x99\x19\x90\x0e!\xcb$I\x0c\xe7\xad)|Q\
+\x90\xc6q\xed\xecB\xe6\xd87e/\xed\x9c\xd6\x83V\xc3E\xe5\x15\xf6\xe0[\xf8\x18\
+\x9a\xc9\x83>\xfa\x89j\x8d\'\x834\xc7\x99*\x03\xcd\xc5#\xe4\xaa\xf5\x8c9\xbf\
+J\x08\x08"\xd6%\xea\xb2\xb7\xa8\x1aC\xa2\x9c\xaa\xa3\xd9\x97a\xf2(:t\x10\xac\
+\xd4\xb9B\x12\xa4#H\x83\xcb\'#Bf\xfb\xd3\x06\xaco)U\xea\x80Z\xc3\x90.\xda\'\
+\xae\x03\x14*\xdc/\x96IL\x0c\x01\xbbLR\xf8`L\x0b\xca\\\xeb\xb5!\x1c|K5\x819=\
+\x07\xe4\x0f\xe6\xba\xb3v^\x98\xc9\x84\x1eK\x8dQ.\xc6\x93\xf5\x80\xa6\x9b\
+\x9c\xc3/{q@\xee\xf0\xab.\x08\x14\x88\xcc\xeb\x04\xc5]\x1b\xc3\x8dp%\xc2\x8e\
+\xa1\x86\xe3L\x96\xfaV\x80\x00\xbb\x87\r\x1f\n\xf3Q\xc5\x99=f\xe8y\xe5\xa2\
+\xec\x83\x96^c\x13\xb3\x89G\xd9\t\x02J4\xf29\xe8\xf40\x8f\x14\xdd\xbaK\x15T\
+\xa9\xf9\xc8d\xc3\xebo*Y\xa1\x03Ez\xed\xf2{&(\t\xda\xfe`/\xc1\xe6\x01\xb9d\
+\xaaC\xe1!~A\xa3\xe0\rH5\x10\x14\x000O\x90\xcc\x0e;\xaf\x0c\xb6\x1cA\xb5\x9c\
+Dp\xbc/"T\xbb9\x07\xdc\x01\xf1\xd0o\xe4\xdd8\x01\xef\xa2\xec\xb3\x9f%i\xc5\
+\x85_\x17\x97\x01\x8b\x08\xea#\xb9\x89\x80\xa0\x923\x04N\x13?!n\xac`R\x86\
+\xd4\xd0b\x00\xe9)\xa1\xd7%L\x98\xe8\x1d\xce\xc3\xbc(\xa2\xfc\x1d\x80\xe7\
+\x7fx\x9ep\xd9j\x82wO\xfc<\xf8\xf6\x13\xdb-[\x8ew\x06\xfe\xf6\t\xa0/-\xf2\
+\x8e\x1dz<\x95\x10\xc0\xc6>\xaa\xb0B\xfc\x89\xb1\xa0\xc5D\x81a|z2\xe6\xb9y\
+\xca\xd3\x15\xfaC\x99\x89|\xeb\x1cC\xfc\xf2\xe0\xe4\x1ddKh\xbe\xb2C\x83\x00\
+\xc9\xc5\xe1b\x80f\x8d\xd9\xb5)\x1b\x01,[\xdd\x012\x12U\xb4\xf1\x9a\xf8\xb8\
+\x91\xe7\xdc8\xf1/^i{l\xe97\xdb\x17+\xf0\xf6\xb2!CF_\xd3\xe0\xe4\xa5k\xff\
+\xaa2\x13\x95\xa8D\xc3\x80\x8dr\xa7\x08P\xcdA\x9a"\x08\xce\xcc\x18\x1a\xab\\\
+\x15\x96z\xa26\x8fYs4\xd8\xb8\xaf\xdcb\xfd\xffq\xb1\x08\xb1\xa60n\x07&\xf9\
+\xe1\xfd\xact\xb6\x16\xcf\x0c\xdbq/\xcc\x0f\x9f\x140\xc3\xcd\xc7p\x07\x848\
+\x94!\xdb\x9a\x94\xee \xacN8\xd4!\x943\xb6\xa4\x14\n+\x95\xf4\xa5\xbfVs\x08?\
+}6j\x19\'\xf6\x91\x1a\xc0\x9e\x82\x92\r\x90\x8d-\x8c}\xaaB\xa9\xa4\xb4RD\xfa\
+\x8c\x04N\x03\x13\x01\x84\x90\'\xe9\x18\xa8\x19\x96\x1d\xb0\xaf\xd91\x92\x89\
+\x1aw\x91\xd8\xc8w\xe9+\xba\x98/A\x9cy\x8b\x8a9\x90\x1f\x90\x93\x192\xa9+[*6\
+\x80\x02\x04\xdd\x15\x8d\t\x93\xa8\xc6\x18\x9b\n\xe2b1R\x99&\x00\x8d\x08\xf2\
+2\x11\xa2\xb2\x97\xca\x19\x99q\x8d\xce\x8b\x04\x87\x96\xdd\x88YHv\x84~*OjZ\
+\xdd\'`8\xc8\xc8/\xbe\x0b(\x9fAO\xc1wq^@\xc4\x15[\x97b\xd1r\x82\xf2\x1dU*\
+\xba\xe5\x858\xf4\t\x02\x94wcF\xce\x9c\x12_\xf3:O+I\xce\x8d*\xda\xe4\xda\xb7\
+\xd0 ~\xc2\xf8GT\xf0\x07\xd6\xfb\xe0\xc27\xce\xf0\x8b\x86:\x0e\\\x0b\n\xdd\
+\x9f\xe0\xf4\xc6k\x81<\xfd:\xa2\xc0\x81N5\xfe\xe4\xd8\xf0\xbe\xfd\xca$S\xff\
+\x11\x01D!p\x96\xd1M\xc7E\x1b5 D\x11\xe2T\xdf\x80q\x97\xe4\xea\xc2G\x04\x11k\
+\x9a)p\x00\x00}\tn\xef\xf8\xa7\xefB\x15M\xcf\x0f1\xe8\xb5lC\x8c\xd3\xb8v %\
+\xef\x89\xc4\xb1>\x182H? \xc4K\xd9x\xe5\xce\xb1"\xb6\x14Ia\xd5>T0\x1b)\xa7\
+\xf2qQ!\x02\x00\x7fZ\xb5\xc9\xd2\x94\xd2|\x82\x06\x07V5Tat\x0e\xd2\x0fe\x1d\
+\x96o}\xd4\x14>\xf5\x97\xf6qmfT\x82\xa9\x91\xc4\x1b\xef\xd4^\xd0$\xd0b4\x94\
+\xbe\x07\x86;\xe5F|\x00\x19\x08\x02\x82\x15/M\x1dQu2\xab4\x06\xed\x1f|\x1ds\
+\xf7UMSc\xbe\x86Z\xf9R^[\xc1\xc6\x00\xad\x9a\x8e\xb4\x14r\xde \x1d\xab\xb8r\
+\xccFi\xa4\xe9v\xc9\xa9\xc4\x13P\xbc\xd0\x03\xc9\xb1\xc0M\xc7ZZ\xe5\xdb\xe5q\
+\x10KxzEa\xe9q>\x17\xf5C\xfc\xe0y\x98\xa6\xf79VY\xc5\xba\xcc\x83\x0f!\x1f9\
+\xaazDu\xf9>\x1b\xa6\x9cIi\xdaY\xf5\xf2\xc1\xe0_X\x99\xa3\x9f\x90\xe5\xc1\
+\xc9]"\x8a\x00V\x80\xa0F\xe4\xe1\xfe\x9cB\x03\xc9T\xba\n\x1c\x12\xc8\x08\x10\
+\xf7\xa0\xef\x98R\xa20@V\x80\xd6\xef\xbd\xaa^\x15\x7f\x82\x0f\x1ec\x95\xaf\
+\x12\x16\xea\x10\xcf\x9a\x7f\xae\xc4\xab\xcf\x8fd\xf3\xd9&\x9ekv\x1d\x06>\
+\x0e\xe1\\5t\xc4\x82\x10\x15D\r\xcf\xbc\x08\xef\x1c^v\xc3\x10hc\xa2\x8f\x00\
+\x8dO`\xa0\x14\x95\x17y\xbcc\x9fB\xb7\x03y\xc7hq\x9dW\xae\x07\xe8l\xa1h\xc5F\
+\xe7\x03\xd92hb\xf2\x06BG^dQI\xc6\xe2\x07\x8e\x00g9\xdfM\xa4\x96DdG\xebw\xc8\
+\xdf\xa3\xf9\xc3\xfc\xc0\xa3\x18\xcct\xcc\xdf\x17\x10\r\x94\x11E$n\xe448\xda\
+\xfe\xf0\xfc\xa1\x82\x8b\xdc\xbbf\xd3T\xa1]@\xf4N\x1b$\x86\xa1G*\xda\x892\
+\xac\x8e\xe6\x8a#\x00\xe7\xb7\xb7\t\xd9\xc0@\xa3\x9d\x0cT\x95_\xad\xe9\x0ey\
+\xd8\xcd\x8f\xc7\x99\x1d\xefW\x83\x91\xc4\xb7\x97`\x8cf\x82BA\x91O\xdfT\xcbi\
+\x16\xd0\x8a\x9b\xbb\xbe1\xae\xb9\x8b\xbaAC2^\xe1a\xfd.\x82\x99\x97\x14\x13\
+\xfa\nE\xd2q\'H\xf0g\xcarj\x00\xa1LI\x11H\xf2\x1e\'\xa9b\xd6\xca\xe3\x91)D\
+\xa9\xb2\xc5\xfbX\xb6 \xca\xde!\x8fP\r>h\x02\xa2\xdb\xc9\x88\xf0.\xfd/[\x11\
+\xca\xa5\\\x0e<Qv9\x1bL\xd2\xa8\x15\xc8\x96r\x91Im\xc1\xb7\xaa\xb6\x84\x00R\
+\xbdc\xffU\xdb\x98T\x85G\x08\xc8\xe1=\xa5S{\x8d\x90\xd1\x92d\x8by\xb25\xe6Ch\
+\xae\xf9\x1a\xb4\xa2\x00\xf0}?\xd6L\x99\xc0\x86\xd6\x03\xd7\xfc\xe9*\xd9\x05\
+Op\x80\xce\x87q\xde4E\x9c\xa8Z\xea8\x96* +\xb3\xb4s\x88\x10\xa0\x15\x16\xf2\
+O\x03\xbb\xe8X\xe6\xd2\xc9x\xa7\x93Fc\xa4b\xc3aV\xe44\x9b\xe4\x03\x88\\X\x13\
+\xb5TAZ\'\xa0:4R\n\x0cs\xc9\x87|I\r\xc4)\x035 \x82\xe3\xf6\xb6\xc3\x08Krk\
+\x1dm\x87\x85\xa6\xee\xee\x184\x10/\x1c\x01\x04\\\xa4@2\t,\xaaP\x9b\xd6\xa3\
+\xca\x1c2\xa67\xeb\xfdR\r\x96\xad\th\xfei\xda\xff\t\xd0sL\xe3\x87\x8c}\xd6{\
+\x9c\x83(I!y\xa0\x08\tV\x00\xe2=h\x8b\xe9\x19\xfd(\xa4D\xe1\x99\x84\x85R\xa9\
+ Q\xf9P\x94\x10\x15\xdf=\xa2:\x00\xa9\x17l\xcd\x1d\xfe\xcb\x99\xc4\xd4\x8a\
+\xb0Nt\xcaAN\x1d\x94\t"\x82\x07\xa3\xda\x1a\x10\x17\xbe\xd5 ^\x9b\x85}E\xee\
+\x18J\x0e\xdb\x8a\x88YIq\x90\xae\x9c\xf3-\x1c\x91\x1b\xa9* ,5a|R\x16h\x1b\
+\xd2\xfc\xb47D\x07p\x85\x80\xebp\xd6\x8d\xce\rG\x00A\x91#S}9\xca\x15\xf2"S!\
+\x94\xd7jy7\xc5\x1a4=\xef\x14\x18\x99\x81.\x1b\x13}!\xe8\x10\x1b\xc1\xf8i\
+\xacB\xb2\x0e\t\r\xc0\xfc\x164\x1a\xa9\xf7\x8cI\x12\x880\x06\xd4\xd1\xcb\x08\
+\x08\x04Ho\x00W"\x9dAd_\xa8+PF\x80!\xc7\x02*\x92\xcd\xb6\xd4\xa7\xa1\x07\xa0\
+^\xe2\xca\xeaC\xbbX$U\x81W\x8d\x90\xc6\xb7(\x16\xff\'9\x9cX\xe3 6\xd3P\xe5X\
+\xb8\xbc\x07\xb9\xd4@\x11\x06\xae\x88\x89\x8d\x8a_\xa5I\x14\x91\x13\xfd\x8e\
+\xc9\x83\x10\x93lUM\xd0e\x89\x07|}\xa0p\x12\xd3I\xba\xbc\xd4\xe7\xec{~Wh\x0c\
+3\t\xc89\x94\xf6#\xb0@m\x80K\xd5\xb0l!F\x91/\x87\xae\x92#\x89_.\rij@/\x1f.4\
+\x8d_T\xf9Xwz\x00c\xc2T Q@\x17\xee\xf0\r\xc2\x11:\xdd\xe1e\xf6&g\x0b\x87>\
+\xe0\xc2riP\xadGS\xd2\xa6I\x909e\xa7cc\xe7X4\x97\x16\x90\xbc\x9a\x80\xd2\xf0\
+\xd4\x11\xa0q\x81\xe7\x84.\xfe\xaa\x08\xc0\xcb\xcdz\xfb\xb8\xc6\xc6\xc2\xf3J\
+\xbc\xa0b\x98\xe1\xd8\x10\x9a\xee\xe5%yg\x10\xd5\n\x0e\xafK4\xb1}\xd3\xa2\
+\x04\x91KP8\x92N\xe3\x93*`\x15\xa7\xcb\x8c\xcd\x14\xa9x\xc2\x86\xcdt\xa2\xd9\
+\x8aD\xba\xfdm\xb4\xd00|;3a\x01Y\x0el\x05\xa4\xb9Q\xae2\xb3a\x9c\xc1\xc5\xbb\
+\n\xdd\x9e2\x9bhS\xa9x9\x13\xc8V\xa2<\x8b\xe8Th\xd9,\x8axs\xa0\xa0\xf8<\x10H\
+.j\xa2K\xea\x94\x8f~L\x0fk\x08\xc5\x02\xf0z5\x04\xd0\x9f\xaa \xe4m\x8f8\x8d\
+\x9b\xbc\x1d\x91\x03X\xbc}\x17\xe5\x95\xd3\x08\x07\xe0]k\x0e\x0ce@\x08U6\xdc\
+\xd0\xbdU\xe5\x15\xd5\x8b\xe5\xc5.\xd2t\x9ez\x7f\x12\x01\x8e\x1a\x08DU8\x82\
+\x18\x87\t=\xd5-\x00\x808r\xbd\xd0\xdb\xea\x8a\x0f\xb5\x16zE\xb5\xfe\x84\x80\
+\x9b>\xe1\x08\xe2n\x8d\x82\x8b\x9a ,\xf8y\xec\xd1u\xf1G\x1b\xaal,\x8b\xf4\
+\x0cU\xa5\xaf\x159q\xf5\xae;\x80\x98\x89\n\x04Pc\x84\x8cM$/\x1a\x91oK\x1bx?r\
+13\x95\x1b\xf6\x088|\x10\xa4$\x0e\xec\x01\x9c\x7f\xfe\x08\x01\xddx\x0c\xadJ\
+\xfa\xbae\xa2\x83V\xa3\xed\x94\x92*x\x14\x04\xa0\x1f2\xd1\xd3\xcc\x7f\x13\
+\xc9\\MS\x0bH\xa8\x85\xbfJC\xb4\xb6Y\xd2\xd0F\x94\xe6v\'\xa7\x8eo\xbct\x9eT\
+\xb0*\xe4\xa17\x1a\x14\x10t\x1d\xfe\xea\xc87\x95AZgl\x86\x18b\x8f\x19e\x00UN\
+l\xad\xc9B\x00\xdb\x1a\x90\xf4\x84\x0e\x90^\xa257\rJ@8\xcbJk\xc3\x1fu\'0e\
+\xf3\xfcV\xae\xadd\x02\xab\xa5*;\n\x11\x04\x85\xef\x98\xd0?\x91\x96\xb0\xbe\
+\xe8\x9aD\x91%\x03\\\x90J\x9cSs\xd8\xdc\x17\x10\x90Rh\x19\x890$jG\xa2\xb0\
+\x8a\x11\x03\xb1\xe8\x19\x08U e#\xa1`\xf5;\xca\x89\xbbK"\x83\xcf\x05Et>\x0e \
+\x80\x80\xebs%w\xc6/\x97Z\\$Q\xa2\xc6\xb8\x07MTR\xff[n`;v\x1d\x96:7$\x80\xe4\
+\x19\x14\x81CW\x81z\xb0\xd9\\3\xe3>p\xcaAP\xad\xef\x94w\xf8\x15\xbe\xea\x9d\
+\xb4#\xc9I*bF\xc6\x9e\x14p\xbe\x14\xd9f\x1eM\x97\x96^\\\x02\x90\xbe\r\xbfl$9\
+\xed\xf4\x13\x07\x95=\\\xab\x14\x80\x8d\x00\xc7Y4F\xbf@@\xe7\xd0\xa0E\x1fC"\
+\xaf\x99\'\x00\xcb\xa5\x9c\xae\xf6c\xf0\x1a\xa1\nf\xd2\x14\xd1f\x1e\xcbKQ\
+\x08\x86I`\t\xd9&\xe0\xc1\xca\xfc4,\x14yB\x10\xd5\x82\x9f+\x8d\xe5E6\x89\x00\
+sy\x98\xf7\xba\xe5\x9fK{\xa7\x1f\xdf(\xae\'uH\x97\x052O\x11\x17a\x90\xd1\x04\
+\xc9x\xab\x9a;f\xd2\xd25\xa1\x17a \x148\xdb\x0ctq\'PP\xe3O\x16\x0bjI\xa6\xd8\
+\xf7K\xaea~\x1f \x0cDm\xea\xaf\xb7\xa6\xe2\xd1\x11\xb3l\x9e\x87\xf6n\x08\xca\
+&F\xfaA}9d\x8a\xc8\xe3\x8a\xb2\xf38\xd3\xaaGgj{6p\x1c\xcde\x9a\x00I-$R\x17AA\
+\x80<\x1f\x10\x95\t\xd35\x05C\xed8\xe3\x98\x8f!e\x1eh\x17j\xcf\xe4\x93.\x87f\
+\x04X\x87\x93t\xa1\t"\x06\x1c\xb6L\xd4"\xd0\xfc\xa4\x07\x1d\xcf\x8d\xee\x15\
+\xed\xccMF\x8d\x13@\x11`\xd1\x16\x08 H@\xaa\x9f\xafQY\xe7\x03\xbahhC\r$AX\
+\xc9z%X\x11\x81>\xa2\xc9\x9ean\x8d(p\x803\xf4Q\x8cs\xe1\xa9t\xbc\x0bD\xc7\
+\xb5\xaa\x0fr\xe2O\x91F/:\xfe\x87\x089\xc6\xc8\x07F\xf9\xbc \xa7\x03\xc8\x91\
+\x10\x87\xfe\xea<\x89x\x02\x04\xbdg\xbb\x80\xa4\x1c\xc4\xdab\xc2\x01\xab\x91\
+-\x0f(kD\xc5\x80O\x1b\x03z0&V\xe0D\xef\x02i\xb0"9R\x81f-\xe8\n\x11U\x06\xaa\
+\x13\xac\x1c\x08\xa0kA\xc3!\xa0(7]\xfdu\x13w\xf1\xc5/!\x94\x1c\x0e\xf1x\x9f\
+\x17\xf15\xeflcT\x1a\x9f \xc8%\x98\x80\xa8\x07\x00\xa9P\xae\x0c\x80I\x0f@JZ\
+\x19\xe42/%%\xfd\x00\xd5\x19TW\x03\r\x04PuV\x8d\x00\xca!\xd3\x00\x15\xe0Q\
+\x1f\r+\xfa\r\xb5\xbe\x01\x802\xc9\xcc|\xf8\xa1\xb0\x10h\xbc\xae\x86\xde)[0F\
+\x01\x96.\xd1@\x18\x0b\xb9\xb9@X}*O\xc0y\xa2\x91T\x83k\xf3M\xc1\xbf\xc37\xe2\
+\x12y\xcd\xc3\x07\x04&\x8b\x8c3o\xec\xc0V\x04\xf4\xdb\x0cUe\xc5\x903\x92F\
+\x9d){ \x9d-\xea\x11\xd0A\xcf\xc8s\xbc\xa1Bi\xd8\xd2,\x1cb\xc2"P}\xc2\x10@2\
+\x88\x06I\xa4\xc6\x1f\x02\x88Ze\x10X\xc6\x96\xdbFR\x967\x11T0\x95\x10\xa0\
+\x05\xa2\t\x82E\x88\x0c\xf1qdq\xc8\x11\xa0\xa0\xc5\xa9\xb7\xea<\x10\xa5\xea\
+\x08 j\xc0\xee4j\xbfr\xfc\x9b\x0bA11\xe5\xc9P\x11\xf5\xa7\x17\xca9>\x18\xd1\
+\xd9\xe2z\xb1\x13\xa6L\xff4\xb7P\xe5\x00\xc97\xc4\xc6\x15C@+\x03\x98W\xae\
+\xdc5\x8e\xe9H\xb4\x80\xc0\xca8~C\xa6\xa1\xda\x9a`\xc7@\xd8\xe1\x06\xc2\x08q\
+\xb3\x80u$\xc2AG\x1e\x10\x0e\xd8+\xe1:Y\x96\xa6?T\x07\xb3\x130SfD\xaa\x8f\
+\x98\x89P\xc0C\x88H>k\xa4c\x01\x85\x9f\x9c\x1b\xa6.\x06\x1b\xb5(\xf1B0\x18\
+\xc7xy\x90\xa2[\x92\xc2q\xb2\xb3e\xea\x90\xd8\xc4\xa1\x12\x05\xc6\x89\x06\
+\x8f(\x84\xe7~\xa4{\xa8\xcd\x1e\xd9NDX\'\x00\x04\x05m(;\xfa\x87\xdf\xa4\xa2R\
+\x81\xcb\x06\x90d\x1d\x01*\x1e\x84\xc2h\xda\x9b^1C$\x1d\xd1A\x17\x858QRY\xcc\
+$\x1f\xaa\x16+\x86\xcb\x84\x82\x00@\x85C\xc0\xf4\x01\xfde\xde\x00\xa7M\xe3\
+\xc9"t\x1fk\xfb\xb6Ou\xbd\xaf!\xc0r\x11j\x8df-\x02\xf8\xfc\x04L\xcdlKZB\x84\
+\x97PU.:\xf2\x05\xe4\x85\xa5-\x06\x86\xb5\xe9]\x02;\x85\xe0\x9cf\x17rK\xc5"\
+\x82\xda6\xe6\x18\x12!\xa3K\xf3\xcf\x90\x81l\xd6)\x95\x82D\x86\x0c\x85\xfe/\
+\xa8 \x95\x9d\xd2\x11\x0f\x01\x85\xe1\x83\x03\xf9\xca\x87\x15\x08z\x03\xd4\
+\xd8\x18\x19\x1eCS-k\xbc4\xc4_\xf4\xfe\xf4 \xb4:\xab\xcf,\'3u\x1a\x87\x96\
+\xf2\xa8\xa6\xc0\x0e\x82\x06\xeb _/!\xaa\x88\x8a\x03\x0fb\xa1\xa3\xbb\x05Z\
+\xdc\xd3?\x85@m\x9b\xfe2\xcb<\x06 +\xfe\xbc\x0f\x88\x8bg\xc6z\xe5\xcbQ3Aw\
+\x0f\xb2\xa8\xa9|;\'\x9eATPm\xa5\x95\xfe\x98t\x01\xba\x9c+\xd4\xcd\x82\t\x03\
+\xc348\x80\xf4\xbd\x03\xb5RG\xdd\xc4p5$\x02xN\x9au9c\xa2@&|\xbb\x1b\xa8C\xdd\
+\xb2a\x91\xf14\xa7\x1a\x83\xa3\xb2-W\x8c\xa8\xe6\xc2%r\xc3w\x07\xd5\x0e\xe1\
+\x9f\xcf\x91\xe4\x9a\x00s[\xa9\x00\xea\xdd\xc0H!?\xd0\x95WI\xe8\xf9DL\x1a\
+\x9f\x8e\xe7\xf0\x00\x8e\x1f\x19\xac\x86\xacuf\xbbh\x8d\x96\x03m\x938\x1e\
+\xfeU\n\xa6\x14\xe4\x97O\x1cQ\x00\xc0\x11\xe0\x1cqS\xf5~T\x8e\x81\x94\x10\
+\xd0I\xfd;#\x7f\xa8d\x08\xcf-g\x0b\xa4*U2[.e\x1a\xdfy\x8b\xcd\xc7*UZF?[\xc4\
+\x8a\xb5\x91>\xe4\xcb\xc6\xe0\xb8\xe4\xc3\xaf\xc3\xac\x94\r\xbf1\x9a\x8b\x08\
+Pf\x83F\xce\x94\r\xadO\x98\x9e\x9fK\x05b(9\x01\xd1g\xceg\x88\xb9|\x88\x0b\
+\x05\xb9\xe3f\x94fi\xa5\xb1\x8b\xa6\xb2\x8a\x0eHQ\xd5j(\x81\x00\xc9^G@9h\x8b\
+9\xe6b0\xadX\xc4)\xd2.\xe7\xa1J`\x08}@\x839\x98\xe8a?\xe9\xdcxr\x87\x13\xb8|\
+\x9a^O\x87\xb1\t9\xea}\xea\x81\xc0@\xdd\xc2\x16,2z>\xbd\xd6\x96z-xh\xd6t\xa4\
+\xb5\xf8\xef\xe9\xad\xc5\x07#\x06\x85\xcc\xfa\xea5^6\xcc\x84\xe4\x84\xe7\xb4\
+\x99\x82\x00\x05SI\xa4\xaa\x7fB\xf2T\x8fK\x959v\xe6<G\xad\xe5\xbd\x05D\xce\
+\xfc\x84\xd0g\x08\xc0u\xf3\x95ce\xe0\xa9~\xe20C\x1f\xe7\xcf\xbc\xcaT\x15\xbf\
+r\xb6\x84\x06>\xa2\xdan.N\xec\x02\\4\x9f\x80)\x03&\xb9*\x04\xd0\xfaui!\xd6\
+\xad\x07\xd9\xe8\xb8\xcf:\x89Z\xbd\xc0^\\\xd5\xc2\x00\xfd\xd4\xf8\xcfe\x85\
+\xdf=H\x1cX\xeaA;F\x10\x89\x80\x13\xff\xa7\xf5>\x03\x1a\x94\x06\x82B\x9e_C\
+\x17\x18C6\x8a\xaa\x05Y\xfd,\xa2Z\xa3\x90=l\xce\x1f\x05)\x9b"&\x04T\x1c\xc0#\
+&\xc0|+\x0cPhf\xc9\x86\xb7\xeeq}\xc0\xb73\xd2\xdc<o\xac<\x80\xc3X\x80\xf4\
+\xa0\xa7P\xec\xc5\xe9^!(\xe5\r\x81\xa1\x9c%\x17\xde \x8c\xd2\xaa\x00\x14\x9a\
+n$)\x1f\xc7\xacw\x02\xd0]\x10\x92Z\xd4\xa1\x7f\x15A?\x97)T>\x9e\x13\xa0\xb9\
+\xaa\x98g\xd1K\xf1}^_1\x12k\xb9\'\xa3\xbb\x9c\xb1\x9c\xafX\x1f\xf50\xb35\x81\
+G\xd3U\x92\x144A\x8dx\xac\xcd\xd8\x152\x01\x9cl\x96\'\x94\x8a\x0f\x13\x03\
+\xc4\x91\xa3\x8d\xcc6\x1fV\x10<\x14{\x15WI9J\xe53\x86@\xd4C\xb96\xac\x92J\
+\x81\x92p|bb\xd7\xca\xdb\xcd_k\xdb)\x98\xee\x9b\xe8\xa4N\x1fR\xad=V\x84\x9d\
+\x96|\x03\x88d\xe3\xd7X\x81\xef\x07V\x01 \x87\x80H\xa5\x1e\x01\xc8\x91#\xd3:\
+6\x1bP\xec\x97\xedKj\xaf&2\xb3\x03z\xadm\t\x06\xea\xd6_u\xa8>|\x1f\xb9\x11\
+\xd8D#\x84/\reT\xa1\x93\x8e<\x00\x84\x838l\x1fH\x14\xa6M\xb0\x99\xa5\\f\x1b\
+\xca\x8f\x16*\x1a!\x90L\x97L\xe2\xd9\x19\x80\'?j-\xbcp\xbf,\xfeTu3\xf4\x9a\
+\xd1\xd4\x12*\xec\x02$%\xe9\xd1\xdb\x96C${\xd1\xa6\xb1\x9604\xf2\x9a\x8a\xc1\
+1\xcf\xa9\xf5\x00\x00\x16mIDAT\xd1\xb9\x15\xf3\x11\xe5\x11\x01\xad\xb6<\x02\
+\xb0Pr\xcf\x9cSgA\xfa+\xc5\x80\xb59\xd7\x1f\xcbp.\xc2\x19C\xa2z\xbc\xe3#\xa8\
+\riMW\x9d\xae\xf2\x08\xc7\xafmi\xcag\x1b$\x11\xa3\xa4\x8c\xa2\xdf\x1f\t=\xe7\
+\x95\x18\x00\xfd\x83\x88\x96\x7fKj\x01\x0c\x9d\xa6^K\x12b\xa5\x10\x11Q.\x95j\
+\x942\xa4,6eUx@*\x07\x9d1\xcc\x04\xf6X\xb8\x92AF\xa8]\x9b\x14\x03\x08$\x89c\
+\xcde\x9c)!u\x06\xf5\xb0\xe8\xa0G\xe9-\xe6\x87\xf5\x8bb\x9d]\x11\xd0\x0eg\
+\xc5z3h\x99\x15\x1a4\x1b2\x95\xcf\xc6{\x00\x87\xcf\x18B\xe2\xaa\xf2[\x82\xb1\
+F\x8d\xb8gB\xc5LJ\x1e\xe9\xb6em\xd5\x19\x93\x1e\xa6\x00\x9e\x18\x02H\xaeqk#\
+\x12\x06\x86y\x1b\x08\x14\x11\x80j\x96\x97\xd4=P\x82\xa3\x93\xbaX\xac\xab)i\
+\xfa\xd5\xc2N\x9f\x1d\xaf\xd3\xc6\x1fU\x03B\xa2<o\x89\x97\xa6\xac5\xd0\x87Q\
+\x00\xd4\xcb"\xfeT\x98\x0e$bt\xa6\xd0\xf4s\xb0\x0b\xca\xa1!\xbb\xca\xf8\xb7\
+\xe4\t\xf2\x8b\xf6\x0e1[\xaf\x87\xb0\x8b\xd8\xd9\x1bP,E2\x07\xcd-CC%WF\xb6n\
+\xde\x1c\x16\x7f\xc7\xc1\xe8\xa34\xd9\x1c\x8b\xe2\x00\xc7\xe3\xc6\xb5_Z\x06\
+\x00\xd7|09\xea\x03\xdb\x16\xb3X\xdd\xbfG\x15\x84\x1f\xcfG\x90\x17\xa5\xf8\
+\x08\xe3\xdfUf\xed\x10\xc6 \xef\x10XI}.|&\xfe.~@\x87\xa0\xfa\x10u\x04-\xacT\
+\xac\x9e\xa1\xbc\x90>\xb0\x8d,W\x8b\x03\x08\xb0\x88\xd4\xaa\xbe\xed\xc6\xb8\
+\x085\xe1\x8b*5\x8eS\x1c)\xe1\xadl\xd1*eS\x11Y\x92\xda\xfa\x04\x1e\xe2\x00 \
+\xcd\xad9\r\x16\xb2S\xf7^\xd4\xa4\x97\x12\x99\x99\xf3X\x11\xa8\xdf%+\x95d\\\
+\x10\xbc\x8f\xb7\xc9O\xf4\xc1GH\xce\x03\xf2\xd4j\xd6\x00h\xcd^\xb0\x88\x92\
+\x99g,\x99\x8d~\n{\xc6\x94\xd2\xc8+\xa3b\x88\xdf;\x08\x8d\xa4\xc2\x18\xfe\
+\x99\xc0J\x048\x11\xa3]\xdbA5\x03\xbcw\xc9\xd0\x82\xe43\xba\xa0\n\xd2Z\x82\
+\x0fv\x81\xbe\xb2\x1f}\x1a\xd4\xe2J}=\x04\x8eR\x92M\xe0"\xad\xa6\xbc\xa0\xd0\
+P6\xf8F\xc0@\x14D\x95\xf6\xf9\xe2\x8f5\xf4\x97\xa3\xfc\xb5`"\xa0r\x9e\x9b7}i\
+6\x88mD\x1b\x89p\x90\xd4N\xf3\x83_\xd3\xe1|\x98\xc4\xc4\xcfB\xe2\xef\xfc\x11\
+\xbf\x02s\xa4\xfej\x08 \x0c[\xc9\xba\xc05\x91f\xb0\xa6\xd1F#%h\x82\xeeR7\x16\
+Yxd\xcdYan\xe6\xab\x1c\xb2l\x88\xdd\x8a\xcczL\xf3\xd2\x0f\n\x15\xa7\xb9b\xf3\
+\xd1\xe6F\x1b$k\x81\xfcJn\x7f\xcb\xb2f\x0cBd-\xdeg\xfa\x9c{\x13\x9aJ1`\xc0\
+\xc7A\xac*\xdcho/\xab\x08\x95SA\xfd\x1c\x18\xc9\xe8x\x04\xfa\t\xda)\xd7\xb5\
+\xe4\xf9\xc9\x10\xe3\xd0\x88%e\x1d\x8d\x14\xbd\xe2\x13\x0e\x9a/M{\x00\xf2\
+\x91\xe0\x06\x14\x9e\x13b\xfe@\xc5@\x0c\t\xe4\xf1%\t\x05\x85\xa8\xd9\x11-\
+\x17\xaaZjoR\xbfbOG\xd9J\xae\xdc\x1a\xce\x0f\xf1\xce\xe2G\xe6Vh\x04\xe7\x9b?\
+\xb8\x8f\xec\xe1\x96s&\x82\x0e\xd45\x05\xc6\x01\xc4\xcf\xb7\x93\'\x10=\x10\
+\xc5\x00\xa0\x89\xbf\x9b\xea\x8a\xe3\x97\xf6\xbbND\x8b\x15\x02\xb7U\x01\x1b\
+\x19m\x85\xe9\x93x\xcb\x95}HV\xaf\x8bF\xa1tz\xa0\x1d\xef\x9d\xf9\x01\x80\xa8\
+\x1bp\x84\xf0&\x83-\x80d/Z\x0c\xb0\'\x10\xa9cP\xd2\xd3ll{\xbd\x11\n\xc7ZDA\
+\x17\xe8L\xe8ro\xef\x9b\x96\xf6!\x88\xbftP\x8bk]%d\x84d\x96\x13\x1a\x80\xb9;\
+\xa1z\x97\xb7\n\xf5A\x12\x08f\xd9\x13\x1c\x00p(\xc4\x93i\xfa\xc8\x8a\xc4U\
+\xe3\x13\x13=I\xc1\xb3!\x85\x88\xe3\x11\xcb\x0e\xdc\xe2x\xdf\xcc\x0e\x88\x9c\
+:\xd5\xc5mj\xbct\xe4"\xdc\x15\x06;\xbb\x10h\x08V\x01\xda\x9b\xe2(\xa3:\xdbD0\
++\x88,\x848\xe7\x8da\x01\xb2GA\xf4\x07m\x8d\x17\xb9xq\xc6\x99\x1fF*\x06-{\
+\x18\xf7\x15\xcf\\\xab\xd1\\\xd2\xe7\x96]\x8c{\x86\x802\x10<\xb9\xc37\x08\
+\x05I5D\x8e\x1dh\xc2l\xb9\xf2\xe0=\x8d\x91\xb5k\xac\xf17\xca&X\xd8\xea\x80\
+\xf3^\x1a\xcaY\xedZ+~\xe9\x9d\xe2\xd21!y\xcd\xa8\xb3\xc5gr\xc1\xeaI#\x83\xee\
+\xe2\xb1x\x89\x00KZ ;\x95\xd8\x848c@)\xd8\xc1&f\xd3\x00t\xc8\x91\xdd)M\xeaA\
+\xe2 U\xedpN\r\xbd%3\xee\x8a\x9dm\xa5\t-d\xe4P(\xf1\xd9S\xf3\x04\x12\xf6\x94\
+\x19\x93\x94\xa8\xea\xc7\x95\xfc5\x89\x00J\x95\xa7[\x92qQ\x01x\x19\x8f\x83:4\
+I\x1b\x95\xef\x17\xc9\xef\x91H\xbb\x80\x90\x11\x99Of\x812o\xbar\xc2\x0b*\x84\
+\xb2\x1b8D)\x92\xcc\xcf\x134A_\xc0\xb7\x11\x90u\xfe\x0b-\xd5\x93\xd5X\xf2\n\
+\xfe\x14\xa9]\x0e\x17\xe8\xa6s$\xcap@\x93q\x07Q\xaeM#P\xd0P\x88zE\x9al\xbfG\
+\x11e\x05\x02\x00=,\xda\xf8\x0f\xb1k\x8b\xd0Q\x04\xb8\x14\x932\xb0C\x9e\x95!\
+\xa3\n\x0c\x96t\x83\x04\x82\xa52\'\xde\xd6\xab\xca*>-\x91\x0e\xf6\xe7\xdb*S\
+\x8bE\x8c\xcc\x1c\x01\x02\x06Y\x12\x81\x10~\xd5}\x08\\Y\x13E\xaa\x9f\x01T<A+\
+\xd4\x9d\xdaM\x9bg \xdb\x14XR\x92L\xca\x99\x17\x80\x1b>\xbevLY\x9cY\x15\x19\
+\x14\xcaJH\xd6\xc4`\x82\x97,\xf8\x16\x9e\xfe\xc9V\xcb\xcfty\x87V\x0cU=\x94\
+\xd5\\N\x11\xbe\xa1\xcd\x1c\xfb\xd5S\xe3\r\xf6\x0f\xac7q$\xd3\x80]\xb5\x96\
+\x89*\x83\xd1\xed\x14\x1du\x118\xd7@|\xa62\x1d\xd0@c(!\xd3\xbd \x19y\x06cc[\
+\xc2\x0b\x7f0\xbb\xba7Z\x8a\xce\xeaD\xe9\xab\xdbuX\xe2GC"\x83\x83\x04\x12P\
+\xbc\xb3\x82\xd6w\xcd/\xdeP\xce\x14`\x05=\xa4\x8a\x11\xbf\x9d\xbc\x12[\xfb\
+\x8a\\^\xbb\xc1\x9d\xc4\xd6\xaeu>\xacx\xebU\xf7\xc4\x04\xc4\x98\x8a \xa4j\
+\xdf\xe2h\xd1\x9dh\x80WA\x00\xf8\xd4\x12\x97b\xdd\xe6\xc3\xdfl\x9b\xccD1G\
+\xa8\x1f@\x96\xdc\x13\xcd\xac\xb5g\x89j\x9fT?\xa1\x9a\x98\xc0o*\xe13\x82\x16\
+\n\xea\xe1e\xcdG3\r\x1aD\xcd\xa1\xf7\x96\xd0\xad.\xfc\xd5T\x89\xe7\x11\xec2p\
+\xaf\xbc\xb3#\x178g\x94\xb0T\xdbT\x86\xd6Ie\xcb\xc9\x84\x00_\xd5q\xe6\xd6]jr\
+\x15\xd61\x8f\xb4\xc9\xd9\xc7\x97\xd5\xf7\x18"_\x0f\x93\xa3\xa43\x9b\x02\xfa\
+\xb8\xf0\xd0n\xd1\x9a\x05\x89)\x10z\x80\xe8\x86L\x08\xc5\xed\xb3\xf7\x92\x02\
+7B\x1a$2\xe6;8\n\x9aR/\x80P\x93\x17h00<\x9b\x0cy\xa1](\xc3)\xf0\xe3e2S\xeaK\
+\x1a)\xeb$\x9e3\xa3\x8640\x1dE\xf1\xadR\xf8\x7f\x00\xe0\x12$f\xc1i\xe8\xec\
+\x14\x18\x94\xad\x1e\xc0\x86\xa3\x83V\x89\xc1v\xd8\x1d )\x99\x88\xb1\x9c\xbc\
+b\x9d\x06==\x7f\x0f]{\xd5\x9f\x04\xdaAl!A\x92\xd4\xeb-\xb8\x03\x1c\x014\x8b\
+\x8b\x7f\xb0\xf4j\x0c\xb16\x06T\xe74\xd0v\x94r\xc6\xa9\xa9t\x03\x02\x0c\x0c\
+\xf1z\xf2\x0f\xe7Kg\xd5k\x11\x90\xcf\xa7K\xcd\x03DMP\xf1\x9a\x86Z\xdfG1\x90\
+\xdd\x06\x90=\xba\x1d\xddB\x92\x85Huru9\r\xfaR{\x91{C\xd5\x98Y\xa9\x12\xea\
+\xcfK\xc7?\x00\xa4yx\xdeG\x1d\xb5a\xb4\x9f\x19\x8c\xcf\xe4v\xae\x829\x85\x86\
+\xa3d;_\x8a\x1a \xb2"S\x00\x0e\x05\xe1Kp\xf6\x15w2/\xc0(\xf0`\x85b\xb4\x19r[\
+A\xa6\x93\x94~\xbd\x88\xd6\xfc\xc1N\xe2\xa7l\x9bK\xc8\xea\xec\xa0[\x08\xdde\
+\x8e_!x\xbd\x1a\xc3\x13\x13d\x89oP\x08\xda#\x8f*\x97\xdcE\'\x06\xbbX\x19o\
+\x91=MWjT\xdd@\x85\x14s \xa5Z\x1a6\xa4\xc5\xa2!\x11\xa0\x04\xa7\xfd\xa4T\x14\
+\x9f\\Bd\xe7UbA(\xd4-D\x83T\x1d\xf9\xd8{\xf4f\xfb\xb0C\x10\x83\xd7p`\x80Ww\
+\xec\x11\x1f(J\x1b\xee\xa4\xa4\x0e\x84r\x0c\xe1HSD\xd9"i+\xb9[ \xfe9sq\xad\
+\x02\xa0!\x80\'f\t\xa0\x89\n;`\xd2\xfaT\xc4\xbe4\xb3D\xf53\x95\x06g($\x1cT\
+\xb4(\x1b\x02\x0cL\xbf,\\\xfa|\xbcr\x84:\x87\xec*\xb6\x1a\xb2\xfaVr\x15\xdd \
+R3\x05\xdda\xc5\x80\x1e;!z Fq?\x80$\xfb\xe0\x17%P8\x82\x03A*\x00\x06t\xb5k7\
+\xdfg\xd2\xebN\xe5z\xf3\x06G\xd4/\xf2\xa7\xea\x1b\xb6V\xc8(\x88\x93E\x16\xf4\
+y}\xba\xec\xf9`\xd4\xdevB\xe25\xb3\xe1\x8c\xfeON \x86B\xd6\xd3\xd74\x06\xafK\
+\xf6\xa3\xc7\xa9\xe4\xc6*\xd1=tD@Ju)\x039(\xc6(wa\x11\x7f\x17\x11\n>W\xaaO\
+\x886\x98f>\xe2q**\x98\xe9\xc8\xa6\x12\x8a$J<^FU\xef\xe8\xd26u\xba\x1c\x96\
+\xbe\xed\x1b\xa4\xb2>\x15V\xe78\xddu\x00\x8d)\x95\xaf\xf5\x0cj\x18\xd0]\xa6\
+\x90\x14\x00\xd7\xc7\x12\xad\xf9H\x0bG\x80\xd3\xaf\xcds\xc6.&RYI\x16\xd9\x95\
+Kc\xc7`\xd0\xe4[\x87@A\x97\x12G\xc7\xa5\xfc\x15/4A^@\x07\xd9\xe5u\x80\xa1Qkh\
+\x16C\xbf\n\xd7\x06\x1fV\x9ab\xf1y\x14>3\x92\xa1\xabA\x8c\xea\x7f\xc7\x1d\
+\xc0\x18\x08X\x82\x07\x81\xe9\x99=d\xc2\xb3E\x04_ \x97n\\\xf3\';\x94b\xe3S{\
+\xea\xd7\x004-\x0b$&\x0f)\x9aA\x1c*\xc9\x84d\x90X\xed\x86u`S\x00~K;@\t\x89\n\
+{\xd7]v\xc4\xe7\xa2\x04F@!U\xec\n\n\x05ID\x87\x0e/\xae\xe3`\xc8\xa0\xd9\x9a\
+\x9ccA\x83\xf8`vm\x85\x8e\xfeQ\x0c\x7f\x88d\x0e\x15\xfd\xdf\xe9;\xd5,\x08\
+\x1c\x04\x92\xb8p\x18\xectr!\xbc\x00\x94\xac\x88\x8ef\xce\xe9\xbe\x00\x05L\
+\x13\xe2\x00\x13\xd5\xe9\xa1\xda`\xe4\x9e\x9ca\xb72\xa3\t\x866\x81\x9e,\xf2\
+\xb5|9vY\xf2\x04I\x06GkY\xe6@\x10l+~a1xt\xe18\t\x99+U\xc4e\x8e\xee\xba6\xa9r\
+Dh\xfa\xcb\\K\xb0\xd9\xc0\x15)_C3\x91\xc0\x9c5G\xe3\xf8\xbd>\xf2Q\x8eZ\x1c\
+\x14_\x87\x1b\xf4\x04u\xfd\x92\xba\xd7q\xe0\x95?\xd8\x01\xe4<Jq\x9b|\xa5"\r[\
+\x8aS\x97-/\xb3\x8b\xd1\x15(\x0c\xbf\xb3\x10\x8bxol \x15x\xa2\xe2\xd4\xd0\
+\xa0\x88\x9d{\x04\xc0\xb0Fn<d\xdfPH\xea\x02\x08o\x12 \\\xa4S\x10\t\x07\xb2\
+\x8bh\xb4\xe7\ri\x8br\xb0\x94\x82\'M)\x1fg\x8d\xa3@\xdb\xee\xb0}@Z%\xe9\xc1.\
+8SA\x90W\xa1\xc2\x07\x88wQ\xeab\xd4k\x08P\xea"9|\xb0\xb8ZA\xd9F\xa1\xc2\xe3\
+\xc3\x8a\x9dO\x9aqJ\xcd\xb5\x17W\xa1\x1a5\x9e\x12\xa28\xb02\xb6\xa834\x87$\
+\xcaL\x0f\xe57\xc3L\xba\xf6\x1e\x00\xfa\x1e\xd96\xc4\x89\x198?T\xe8\xe9\xdd\
+\xc7\x98Vy\x8dU\x0c\x80b\x89\x14\xa8#\x08\x0cT\xba\xff\xad\xe9\x1f4\x88\xe8\
+\xa8\xd2\xb1\x00\xed\xe0\x93g\xbd\x87j\x85\xe4\xa7:\x18\xe8\x0c\xf4\x1a\xf1z\
+y\x10!\xbe\xa8\xaa\x0f\x86Gi9GzF\x87p\x80b\t\x9d,\x014p\x87\xe89\xa9\xc4YB\
+\xa0\xec\xa8\x9e\xd6B\x1c\x15D\xda\x1eH\x1c\xb6\x1f,S\xa8\x89rW\xd3\x0b\xd5\
+\xfe1f&\x8b\x00\x1c\xaf\x8a\xbf\t\x96O@\xfd+\xcb:79\xa3\x10\xa9Q\xe8\x1e\x86\
++\xa5q$\xbbD\x8ddz=hUn;b\xa7\x1b>\xb9\x0c\x9e=\x01\xae\x15\x91\xc8\xa8\xc2\
+\x81\x8d\x00\xf2\x05)\xfb[\x04\xf2=\xcc\xf1\xbb\x88\x8c\x1b\\#\xb7\xedx\x94;\
+\x1a;\xd4+\xa2;\x06\xee\x9fi*\x8bt\'53Z?\x8by\x84\xa2\x07\xccwX\xab\xd1\x8e\
+\xe0@\x15\xad\xe90\xb2\x06\xe1,%\x04H>\xf3\xdf\x98h\x82\xb1l\x9c\x94\xa8\xe3\
+\xd1uRfo\xf8X\xb9\xe0\xa1`\xa04\xb7X\x93zf\x14C\x90P\xd2\xa5\xe2\xfbYe6\xc5{\
+S\xa5\xd3S\x988\xb0\x14\xaf\x8b\x1f2\x00\x8dE\xbd\xf8\xae\x03r\x814s\xe0\xd4\
+K\x92\xa3AC\xe5Y\x04\xf6\xbe\xdce\x04\xa9\x92I=\xecNs\x06\xf3Lx\\\x03\xeasb\
+\x0f\xf2\xd3\x81\xd4\'\x9a\xdb\xc8RA\xe0\xc0$.\xbc\x10\x0f^\x97\xbd\xe1Y\x91\
+\xcc\xb4_\xc2\xec@+FB\x19\x19\x88\x92\x93yWR\x1bT\x04G\xb4\xbb\x85\x00\x96\
+\x81v\xb4\xe7\x85\x15\x0fK\x06\x02\x14m\xe8\x93T\xa7\x95\xcaR\x8e\xd7\n\x02\
+\x18\xe4-b\xa2\\v\xb1H\x98\x02z\xd7\xcd\r\xc4\xeb\xff\xd6\x1bE\xab\xe8h\x91\
+\x1d\xc8hp\x88q\\\xd2\x0c\r\xc2\xe1\xc2\x92\xc0\xfaV\xd1\xf3h\x80\xb2#!\x16\
+\xaa\xac\xd3\xe5\xb5\x07\x0eT\x1chE\xfbR\xb64\xaf)d\x92P5\x0f\xf4r\x1f\xc8\
+\xd8\x19\xea\x1c\x8cY\xbd\x08\x9e\xfe\xc1qH\r\x14\x11\xc0\xaeYL\xfc\xc2k\xe0\
+\r\xfbG\x81\xddN\x81u\x9atn\xadF\x97\xfa\xb6],b\xb1\x8e\xfd\xaaA\x07\x06\x1a\
+\x07\xe6S\x07\xbez\xf6 4)\xb9T\\&\xc5\xd7+9\x00u\xf9\xb2\xc5+\x9a\xa3\xa8z\
+\xa4\x00\xc2\'7\x94"ri\xb2f\xe5\xb3\xf0\x8d9-\xa7\xf6\xa6\x92\xfa\xa0(\x9c\
+\x8c-\x02\xe9\xbaW}D\xd2\x19\x80\xc8\x96b\x84\x95\x89\x7f{e\xae\x07s\xaa\xaa\
+c\xa5\xb1\xa6\xb8\xe2\x12\n\x9acX\xe3m\xe4CF?%6\x85!c\xef6FcX\x9bj\xa1\x9c\
+\x9c\xc1\xf4\xce\xcf0\x17`\xef\xfbB\x96\xa6\x08:\xdbJ\xb6\xcdh\xab\xb0\xcb\
+\xd3\xdfB6\xa3\xca\xd6\xb9\xec\xa2{\xb38\xc8\xe4\xd7iv\xb4\x1ai\xa9\xc0n\xb8\
+\x03\xe5+\xa5\xd5\xbb\x88\xc8\xe1\xafl\x1c~\'\xb0(\xd2\xe2@\x88s\xb8\x93\x05\
+\xf9\xad\xe8\xf8\x87G\xf2Z\x97\xef\x9d\xe4\xf5\xa0\x91\xbffj\x00\xb4\x91\xcd\
+\xf4\\\x9d"\x86\xb6h\x8dqN\xad\xab\xd7\xb2R\xaa\xbf\xa5\x11\xf7\x8a\xe2?\xca\
+@c\xca\xc2k\xbc\xba\x86V7\xc7\x7f\xa1j\xbd\xb2\x0c\xb7\xd8MO\x7f*8\xf2\xf5\
+\xcf\x00\x17Iye\x16\xa7\x10\xf7\x89\xbf\x94\xda\xa5\xb91T\xe8\x1e\x1fDD\xd8\
+\xe83\xec\xb2\xee\xd3\xaa*\xd6\xe3\x00\xf8a\x80\xba\xfeg\xe31\xe3\xd1\xa8\
+\xe53\xd7\xc2\t\x94\x16)\xbf\x10U\x1fX\xc3\xc5\x1cR\xdf\x89\x0en&_dd\x0c\xd2\
+`~\x81\xba\xf0\x95F\xcfTB/\x97\x1b\xd5_NO\x83W\xe6\xc6~\x14\x19\xe7\x89a:-\
+\xeb\x1c\x14\x08\xe8\xa3\xae\xaa1\x94\xc7\x95zP\xd3\xd2\xdedU\xca\xa3\x98\
+\xd03\x80\xbbg%\xbc\x07\xa6\x12zb\xb2\xd5\xb5\x8e\xe8\xd9\xe2\xb2i\xa9\x1d}\
+\x08L\xeb\x02\x89\x1b\xb5\xa3\xd8\xe8q\xb9\x17 \xb0N\x13\xa4(^\xf1\x7fz\xed\
+\xdd5\x84\x9d\xdd\xa3@n\x19\x14\xf8\xa0\xb0\x1d\xbd\xe5\xec\xd8\x05\x1c\xc8o\
+%\x97\x1e\xae(kk\x94S\xb8\xf1\xfaZavS6\x94\xb4\x9d.\xac\xec-8yrQ\xafp\xf8\
+\x16\xe2\xca8\x0c@h\xc6\xebv\xc9\xc0XI\xacwm\xba0\x01\xfa[\xceAt\xb7\xa7\xfe\
+\xbed\xad\r\xd6*%YG\xd22\xa0k\xbe\xa6\xa2\xdcP\x00!\xf9\xb0\xad\x1e#\xd0!\
+\x96S\x03y\x04\xb0]Z^G\xf7!\x1a\xa1@\xce"\x10X\x8b\xee3f\x01y\xb7\x80\r\xf4\
+\xea)b\x92\xa6\\\x02\xa0\xd9\x9c,%\xb3\xca!\xabz\x12\xa5\x8f\x16\xe3-^S\xf0\
+\xc8\xe4d<\x0f\x03\x01\xc3\x07\xcc:\x1f\x0bF\xa5\xad\xe2L\xcfZJ6"\xf2\xcc]\
+\xc1\x96Ta-\x05\x0f\xe8\xbe&K\xbb\xdb\x93J\x08S\x00Z\x82\x0e\xd70\xb3\xf4!+\
+\x1fQ\xb1o\xa8T\xd4\xe1/Y\xe1;\xbd)\x9er\xcc\xaesK\x84j\x8f\xafX\xd0\x067\
+\xcf\x00\xd0,\x00\xb8\xe0\xc0\xeb\xeedM$?]\xa2U\xe8\x014MP\xe9i4\xe3J\xcbL\
+\xac8D\x0c\xa8\xf6\r\x91B\x1c\x98\xa2\'\xe0\xa9\xd0\xfc:\xe8D&\x9d\x80\xd2\
+\xd1\xdaRgnqP\xd5nZ-\x84\x82\x07k!\xa8X\x0b\x02x\xd6\x1c\x80\x074\xeb\xc1 h\
+\xd7e\xaa\xaaLs\x02e\xf3\xa0%\x17\xd4\x06\x10qK\xce\xb5r\xe8\x8e}q\x86\x01\
+\xa1\x12\x01>\xfe\xcf\xcay\x9c\'\x11\xab9\xab#\xaf;\xf9\xea9\n\xf5.E\x12:\
+\xee\\\xdb\n\xe06\x86m\x0b\xe2\x18vi\x01\xca-7\x07\x00\x98,\xcb\xa2\xa21\xf2\
+s((\xb7i\xd69\xd8(\x0c|\xca\x84\xed\x81\xd0Q\x15{\x12fz\xcd\x96A\xb6\x96D\
+\x01\x0f\xb6\x8az!\x01\xbc\x83\tk\x8a\xd4:\x86d\xfc+A\xee\x84\x10Yv\x80\x01\
+\x1e\xf5\xad\x93\xec\t\x1dk\x11\x88\x91b\x08\x08\xbd\xc4\'\x10T\x07,\xdf\t\
+\xa8\xc1\x01\x91\x19\r\xae\xfd^7r\xb2*j\xcc\x88\xbfQ\xa4^\xeb\x99\xa6>\x00\
+\xe8\x0f3\x95\x019\xfaM\x0es\xd1\xb6\xcf\x17R}\xf3%J\x87\xe0\xa4\xf8\x16J\
+\x02\xd6\x05\xde{\x00\x82\x02E\xfc\xc0\x10P\xea}s\xc9V\xc3Aa\x13\x01y\xea\
+\x8d\x8a\xceM>E]\x12\x01\x96\x83\xa8\x0f\x1f\xfd\xa0\xa9\xac\x89\x14aWvn\xa8\
+s\x86\xb1;\x808\xc5_\xa2\xc4\x1f%\xad\x85\x01\x96r\xea\xa9\n\x05\xb0\\]`\x06\
+ix\xf8-\xda\\`{\xbe\xd6\xe3\x03\x0c\x01YS\x86\x9d\xa3(\x18\x07\xcd\xf1\xb2N\
+\xad6\xa4\x9f\x1d\xf1\x96\xca\x17I\xd4mA\x1f\x9e\xb2\xde*\xc2e\xc70Om\xbe\
+\xb6\x98T#\xfe!lDT\x065{\xfc\xb8X{\xe1\xe8\xae\xb1\xc1\x83\x8a\x00^P\x1f%\
+\x84\x9a\xfe\xf0\t\xad\xc9\x0c5\x05\x9d\x82\x84\xe2\xa2I\x1b\x99\xbe\x08P\
+\xc6\x00\x00\x117p?\x80\xf9\x03\xb6\xf8Wjm@\x17\x7f%q2%R\x1e#\x94\x04\x19\
+\x02D\xbdV\xf5\xdey\xd7/hl\x11\x903``\x80\xe8\x16\xbd\xd1a\xd4*\xeb\xa2\xb4&\
+O\x97\x99\\\xae\x00U\xf3>TN\xe4\x0f\xe0\xc1\x17G\xff\x8a\xac\xcfk\x03\xb1\
+\x0bY\xea(\xe4\x89\x10\x04\x98\xe6X\xd9\xadkxd\xc7\xcb\xf4N)9\xbb\xa5\xac\\\
+\x05x\x92\x90\xf7\x18"\x06\xd2\xe6\x85\x99\x99\x98\x00\xe3&\xb3\x15\xa4O\xb4\
+\xf2\xc7\xe33\xc78\xb9\x9eZ6\xb0L?TV\x17\xdb(\xe4\xcahx\xf0\x8e\xcf\x0e\x94j\
+2\x08P\xfc\x03OoD\x95\xea\xc4?\x97\xc5\x1b9\x93F\xe2\xae\x1e\x1b\xfa\x88\x17\
+\x13\x01|\xceV\xb7\xf0\xc2S\xf9\xce\x9a\x80Qm"\xcd\'\x8f\xe0)\x9d\x86\xdbE\
+\xb8\xaf\x99\xfb\xf5\xb9\x82\xed\xe0(6\x1d\xc7>`H&\xf4(\x1e\x00;z&EB\xb3P9"\
+\xa7\x0c\xfeT\xb7\xc0\x84\xe6\x01\xe4\xa7\xdaP\x94\x96Z&^\xe7\xce}W\xe0@\xa5\
+Z\'\xa7r&\xcf\xa7\x88\xed\xee\x95NF\xc7G\\\xc9\xf1\xe9\xaf\xf6\x07\xbf\xf3\r\
+\x17p\xacX\xa0\x99_f\xc2x\xc2\x96?^\x82\xb0\x91\xea\xe3\xba\x80{3?\xda\x8aP\
+\xc8\xc8\xb3\xb4\xb1\x1b)g4\x84\x82\x00\x83\xaa"(\xdc\xd7"\xb4g\x0c\xa9I\xa0\
+y1&0\xf9DW\xd1\xb1t$b\xbat\xce\x9f\xaa\xa3\xc8\xb6\xdf\x12\x18Ka\xf4x\xa56\
+\xc2\x82i\x08\x04\x022\xa3\xcd\x91\x9d\x0c;\x0ce\xfe\xad\x15\x1e\xb9\'\xac!\
+\xc0\xe3+\xd3F\xe3\xf1\x1co\xd5\xc5"a\xb55\r`\x98j:\xf4x\xee\x16\xf1\xdc\xba\
+k\xa0\xa6\xda^\x0b\\\x8f\x83\xde;\xa6!@\xf9\x15\xf1sj-~}\x01\x07\xc3\x89\x1f\
+<\xa0=\xbd\x0cI\xcf\xfb\x96\xfe\x00\xf0[S\x8b\x92\x0b\xf4,\xa2\xe1\xd8[N\x02\
+CU\x88a\xae\x80\x03&\x18a\x14T\xe3\xe6biY\x1f\x8fS\x94?3\x0c\xeas9\x99\x95\
+\x96t\xc2\x1fc\x9d\xe2\x00\xbaz\tZ\xe84\x17\xb5\xbd?\xb3@Mp\xe2\t$vcB\x00\
+\xf7\x10\xf1\t\xa8Sf\xaa\x10\xe7\xb54\xc9\x80^\x9a\\\xda\x08\xc8\x14\xe6)\
+\xaa)\x91\xc5\x85\xfb\xff-x1\x0bp\xf1\x03\xd6\x82\x00\xc8F\x97\xc6\x8c\xe9\
+\x13d\x17\x012S\x05\xaf\\{]\xf4\xb2O]f\xe6\xa2(\x01\xc9\x05\xd3\xf1\x9e\xb3\
+\x80S55\x10\xaf\x80! 3z\xe2zA\xc8\x14U\x02T\xb8u\xc3\x85\x82\xf8\xd9\xb5\xe2\
+\x0e\x98\x01g\xec\xa3\xc8\x04\'\xe1\x8fI4\x94\xa7\x92r\xa6\x1e\x8b\xb2\xa5P\
+\xed\x89]\x14%\x11\xc0\xaa0\x18\xa9W\xb6\xf5\x08`\xa52\x86|\xc5\x8cEF\xfcZ\
+\x99\x9a\xc0X\xc3\x8bE\x9ck\xd7\xa8z\xf2\x16yO\x92*\xeb\xd4\xfc\x16\xef\xb8,\
+i\x87z\x8f\\H\xa6\xfaq)F\x04\xe5\xa2\xde\x80\xa1\x9cj=9c\x90%4K\xbb\xd7e\xc2\
+(\xc1\x84Z\x87g\xae`\xf7y\x05\xe8\xc20g$\xfa\x9cQ\xa5\x8a\x16\x07\x100\x11\
+\xae\x00M&;j\x05l\x11\x90\x9a\x158\x00\xa2\x95:xQMy\x05\x01\xcc\x16\xd4\x90i\
+\xb9\x94v\xb7M\xae\xd5\x07\xb5\x9bG*\xe6)h\xe5\xd4Y;\xa3na\x90G`s\xd0\xfc*V\
+\xb5M\xf7\xca\x9f\x90\x03\xab\x86\xca\xd7\xf1\xc9\xd1\xafr\xa9\x102\xa9\x1bJ\
+\xa2\x8d4vu\xd3\xad\xae\xda\x9c\xec\x8c6\xda\xe0\x87<\x10\x9b\xed\x87\xdc\
+\x17/\xb8\xa7B\x94\x80\xa4\x1a\xac\xa8\x8dU\x9a\x84)\xc8\x93E\xb6\x08m\xf5\
+\x1f!\xe0H/\x19\x84\xba\xf8/\x95\xa1\xd3\\\x0b\x00\xb0KH\x82d\xcc\xab\xb1\
+\x05f2\xef\x9dPq/\x1c@tS\xad\x04\x99\xb3h\x9d\xc3\x91\xa4\xdaC%\\\xad\x13\
+\x01\x1a:?\xb3\x9aG\xef\x94\xe5\x1aV\xa5p\xbc;\x04i\x8c\xd5!\xd5\xe9p\x07\
+\x0e\x953$\x9c\x1f\xb4&kV\xcf\xf4\x9d\xb9\x1b\xa8A\xa4\x84L\xce\x9d\xdd\xee~\
+\xb4\xf2\x912{\xfe\xc5.\xdefK:+\xdf\xbd^\xdc\xd2\xd7~\x0e\x13\xaa*^vPGA\xb1\
+\xb1l\x90 (dd\xaf\xd26c\xf3.\x99\x96\x8a\xe7\xb71\xb2\x1fn\x93\xb4\x1d\xfe5\
+\xcdd\xc98\xd8!\r\x05\x86\x83r\xd1\x8a\xf5\x10\xab\x9c}.\x94\x99-u\xeej\xe0\
+\x8cPl\xc6\x84e\xe2!\xa6*M \xf6\x9e&\x19\xb7\x99H\x91\xaa.\x9c\xe3\xd9\x99\\\
+\'(\x84\xce#V#,q\xd0\xe6\xf5f\xa7\xcb\x17@\xe2$\x99_}aSxt\xb1\xdcR\xf9\x9ar\
+\xcf\x87\x04\xa7R\xb4\xdd\xf1ac\xcei\x81\x17=]\x1d\xd3f6\xd5\x93\x84\xb63\
+\xf3\xa7\x8d\xe3JA\xd0\rUoc/\xa8\x81x\xc1^\xd2\x01\x9a\xecq\xc1\xf2w#\xaaW.T\
+\x96\x80vh\xf6H\x91\xb1t\x93UTH\xe3\xe5\xb8at\x8a\r\xca\xf8\x9e\xd9\xfdFb\
+\x0ep\xd9(m\xbbn\x871o\xe80\xeeA\xea\xd8!X\xb6\xea\xd3\xddu\x99\x8b~\xc9\xc0\
+X!\xf0\xe0\xad\x81\x97\x19O\x1d\xde\xb8\xc9]\x01j.<?\xe8\xe3,\xf2\xccK\xd0T)\
+\xcfI\xa7\xac\x9d\xde\xae"\x0e\x95\x90\x8d]\xc6\x81`\xad\xa3\xa1\xb2\x06zN\
+\xb3i\xfa_md\x87\x8fF4D,\x8f\xc6\r\xb1\xe8\x8bDa\xd8\x8b6\x01\xa3 \x0e\xd7j\
+\x1e\xb9u\x97\x8a\xa7\xeb<\xcb\x038\xf8\xff\x00\xddb\xc0\x06\xa4k\x96\x16\
+\x00\x00\x00\x00IEND\xaeB`\x82'
+
+def getWizardBGShorterBitmap():
+ return BitmapFromImage(getWizardBGShorterImage())
+
+def getWizardBGShorterImage():
+ stream = cStringIO.StringIO(getWizardBGShorterData())
+ return ImageFromStream(stream)
+
+def getWizardBGShorterIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getWizardBGShorterBitmap())
+ return icon
+#----------------------------------------------------------------------
+def getDecisionData():
+ return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00A\x00\x00\x00+\x08\x06\x00\
+\x00\x00\'\x98\xe5y\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
+\x05kIDATh\x81\xed\x9a]lSe\x18\xc7\x7f\xa7_k\xd7n\xeb\xea\xbe`\x02]\xecpt\
+\x02c[\xa7|\x04\x13\x16\x13/\x0c\xc3\xa8\x89\x91\xc4xa4^3\x83\x04\tC \x18\
+\xaf\xf5B\x83\x89\xa0D\x83^h\xd4\x04T\x96\x19>6\xb6e\x1d n\x8e\x91-\x84\x91\
+\xc9\xa0v\xec\xbbk\xcf\xeb\xc5Y\xbb\xb6\xfb\xea\xd89-\xc6\xfd\xaf\x9a\x9esv\
+\xde\xfd\xf2\xff?\xcfs\xdeSI\xd2\xe9I\x95v\xbd}L\x84?\x7f\xff\xe9>)U\xeb\xd0\
+\xa5\xea\xc6\x8f\x92\x96!\xb0\x0c\x01X\x86\x00\x80\x94\xca\xc2\xf8\xa8h\xd9\
+\t,C\x00\xc0\x90\xca\x9bW\xec\xdc#\xca\xdcNzo\x0fp\xee\xab\x0f\xfe\x7fs\x82\
+\xa7\xa6V<\xbd\xd1\xc5S\xc5\xab\xa9\xda\xe8b\xcb\xcb\xef\x89\x85\xaf\xd2F))\
+\x8c\x955\xb5\xc2\xb3\xe1\t\xd6:WN}#\xb8\xf3\xb7\x1foG\x0f\xbf}y(\xe9\x8eH\
+\x89\x13b\x01\x00H\xac\xcc\xcf\xa6\xbc\xb4\x88\xcd/\xedM\xba#\x92\xea\x04\
+\xcf\x94\x03\x8a\xa3\x00\x8c\x8eM`1\x9b\x90$\xc5\x00\xfd\xf7\xfc\xb4^\xbb\
+\x99\xd4\x1a\x914\'\xcc\x05`l<\xc0\xf0\xc88B(\x06(\xc8\xb1\xb3\xc9]\xc4\x96W\
+\x92W#\x92\x06\xa1r\x0e\x00\x00\x81\xc9`\x0c\x88\xc2\xfcl\xb6{\xdcT\xef>\x90\
+\x14\x10\x9a\xc7\xa1\xb2\xa6VT\xcd\x03 Z&\xa3\x01\x9b\xd5\x1c\x89\xc6\xed\
+\xfe\xfb4z\xbb\xb8\xf4\xdd\x87\x9aFCS\x08\x9e\x9aZQ\x19W\x04\xe7\x02\x10V\
+\x9a\xc9\x805}\x1a\xc4]\xdf \x8dm]4|}D3\x10\x9a\xc5\xa1b\xe7\x9eE\x03\x00\
+\x98\x08\x04\x19\x19\x9d\x8eF\x9e#\x8bMn\'\xcf\xbe\xba_\xb3hh\xe2\x84\x8a\
+\x9d{D\xd5F\xd7\xbc\x00\xba{\xfb\xf0\xf9\x87\x18\x1e\x19\x03\xa0 \xcf\x81\
+\xcbY\x88\xc9\xa8\x0c\xb1\xf1\x8e\xb8\xf7\xcf\x03.\xb6v\xd2\xf0\xcdQ\xd5\x1d\
+\xa1:\x84D"\xd0\xdc\xdeIwo\xdf\x8ckMF\x03;\xb6\x96\xe3\xb0g\x00\n\x88tK\x1a:\
+\x9db\xd8[w\x06h\xbb\xde\xc3\xef*\x83\xd0K\x92z\x89H\x04@wo\x1f\xd7:{p\xd83\
+\xd8\\Q\xca\xe6\x8aR\x8aV\xad $\xcb\x0c\xf8\x06\xf1\xf9\x1f\xe0r\x16\x02\x10\
+\n\xc9\x08!0\x18\xf4H\x92DV\x86\x95\x1cG\x06\xc2^Rw\xeb\xfa\x85Cj\xad[\xd5\
+\x9a\x90H\r\x08;`\x9bg=\x05\xb9\x0e\x00lV\x0bUe%\x14\xe4:\xf0\xf9\x87\xf0\
+\xf9\x87"\xe7O\x04\x82\x8c\x8eM \xcbJI\xc8\xc9\xce\xa4j\x83\x8b\x1d*\xb6OU\
+\x9e"\x17\xd3\x05\nr\x1d8\xec\x99\xd8\xac\x96\x99\xc7\xf2\x1c\xf4\x0f\xf8\
+\x08LN\xc6|?\x11\x08\x02\xe3\xa4[\xcc\xe8t\x12\xabV\xe4`2\x19\tL\xee\x13\x17\
+N\x1f[r4\x96\xec\x04\xcf\x8c\x87\xa1\xf9\xbb@Y\xa9\x8b\xaa\xb2\x92Y\x8f\xf5\
+\xdf\xf5\x01\xe0\xb0g\xce8\xa68b<\xe2\x88\xfc\xc7\xb2x\xa6l\xad*\x8e0D\xef\
+\xfd\x87\x15\xfd\x0e`\xa1\xe3[\xca\x9f\xc4\xf9x^\xe4X"mp65\xb7w\xd2?\xe0\xa3\
+\xac\xd4\x15\xe9\x10\xf1\x8awDa\xbe\x834\x93\x81\xfaS\xd3\xe7<\xcc\xff\xb3\
+\xe48\xe4\xe7\xda#\x9fC!\x99\xc9`hQ\xd7\x07&\x83\xd4_l\xc3\xe7\x1f\xc2\xe5,\
+\xc4]\xbcf\x81\xf3C\x18\x8dA\xd2LF\x00r\xb23\x16\xbf\xe88-9\x0e\xf5\x8d\x7f0\
+<6\x0e\x80^\xaf\xc3jI\xc3`H\xbc\xed^h\xbe\x16\x010WL\xc2\x92\x00[\xba9\x02 \
+\x18\x92i\xf2\xdex\xe8\xb5G\xfe\xae\x1as\xc2\x0bo\x1e\x11\x9b\xdcE8\xb2l\x00\
+\x04\x83!F\xc6&\x08.\xe0\n\x9f\x7f\x883\r\xcd\xac.\xccc\x9bg\xfd\xfc\x0bE\
+\xe9"&\x93b\xde\x90,\xd3|\xa5\x9b\xd3\x1f\xbf\x9b\xfa\xc2\x08\xf0\xd3\xf1\
+\xf7\xa5\xe6+7"\x8e0\x18\xf4\t9\xa2\x7f@)\x84\xabW\xe6\xcf{^<\x00\x10\xaa\
+\x01\x00\x15\xe7\x84\xb3\'\xea\xa4&o\x17\xbe\xc1a 1\x10&\xa3\x81\x82\\\xc7\
+\xac\xed2,\t\xb0Z\xcd\x11\x00AY\xe6R\xdb_\xaa\x01\x00\x90^|\xe7#U\xdf\x0c?\
+\xff\xc6!\xb1\xdd\xe3\xc6b6\x01\x89Gc\xd6\xc5I\x12\xb6\xf4X\x00-*9 \xbaK\xa8\
+\xfe\x14y\xe6\x8b\x83R\xc3\xe5\xeb\xdc\x9f\x9a\xfa\xe6sDwo\x1f\xf5\x17\xbd1\
+\x13bX\xf1\x00d!T\x03\x10/M\x1e\xa5\x7f9Y\'y\xff\xeca$\xaaF\xd8\xd2\xcd3@\
+\x04&\x83\xb3N\x88\xf1\x00B\xb2\xcc\xe5\xf6\x1b\x9a\x00\x00\r\xf7\x13~\xfe\
+\xfc\x80t\xbe\xa5#R#\xf4z\x9d\x02B?}Kw\xf1\x1a^\xdbU\x1dy\x86\x80Y"\x10\x92\
+\xb9\xdc\xde\xa5\x19\x00\xd0x\x8f\xf1\xec\t\xc5\x11\xd1s\x84\xcdj\x89\x01\
+\x11\xad\xd9\x1c\xd0r\xb5\x9bo?\xd9\xfb\xdf\xdd^\x0b\xabz\xf7\x01\xb1\xadr\
+\x1d\xf6L+\xa0L\x96\xc3#c\x04Cr\xd4Bb\x01\x08!h\xd20\x02\xd1J\xcan\xf3\xb9S\
+\x87\xa5\xf6\x8e\x1e\x86\xa6v\x91\xe2\x1d\x11\x9e\x04\xa3#\xd0\xe8U\xb7\r\
+\xce\xa7\xa4\xbe|y\xee\xf5\x83bky\tY\x19\xe9\xc0\xb4#,\xe64M&\xc1D\xa5\xfa\
+\x9c\xb0\x90j\xde:*\xca\xd6\x15\xc5DC\x1f\xa9\x11\x82For"\xa0\xe9\x9c\xb0\
+\x90~\xf8l\xbft\xbe\xb5#&\x1a\xa08@\xedI0Q\xa5\xe4\x85l\xfd\xa9\xc3R\xcb\xd5\
+\x9b\xf8\x1f\x8c\x00\xca$\xd8|E\xfb.0\x97R\xf6\xfb\x84\x1f\x8f\xef\x97\x9a\
+\xda\xbb\x18\x1c\x1a\xa55\xc95 ^)\xfd\xa5\xca\xd9\x13u\x92,\xea\xc4\xaf\'\
+\xebR\x06\x00\xe0_Y\xc4y\xb5x\x97\x05\x86\x00\x00\x00\x00IEND\xaeB`\x82'
+
+def getDecisionBitmap():
+ return BitmapFromImage(getDecisionImage())
+
+def getDecisionImage():
+ stream = cStringIO.StringIO(getDecisionData())
+ return ImageFromStream(stream)
+
+def getDecisionIcon():
+ icon = EmptyIcon()
+ icon.CopyFromBitmap(getDecisionBitmap())
+ return icon
+
+#----------------------------------------------------------------------
+
def SetViewDefaults(self):
- CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Xml", hasWordWrap = True, hasTabs = True)
+ CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Xml", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetFontAndColorFromConfig(self):
class XmlOptionsPanel(STCTextEditor.TextOptionsPanel):
def __init__(self, parent, id):
- STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Xml", label = "XML", hasWordWrap = True, hasTabs = True)
+ STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Xml", label = "XML", hasWordWrap = True, hasTabs = True, hasFolding=True)
def GetIcon(self):
+Middle-Mouse-Click on the tab for an open file will close the file.
Ctrl-Space in any editor does code completion.
-Right-clicking on something in the 'Thing' column of the debugger's frame tab may allow you to introspect it for more information.
Right-Mouse-Click in Outline window allows you to change display sorting.
In an editor, you can add line markers via Ctrl-M and jump to the next marker with F4 or previous marker with Shift-F4.
In an editor. you can use the numpad + and - keys to toggle folding.
-In 'Find in Directory', if you specify a file, it will display all matches in the Message Window.
-Breakpoints for the debugger can be set while the process is running
\ No newline at end of file
+Breakpoints for the debugger can be set while the process is running.
+A split window can be closed by dragging the sash to one of its borders.
\ No newline at end of file
elif env:
uenv = {}
for key, val in env.items():
- uenv[unicode(key)] = unicode(val)
+ try:
+ uenv[unicode(key)] = unicode(val) # default encoding
+ except UnicodeError:
+ try:
+ uenv[unicode(key, 'iso-8859-1')] = unicode(val, 'iso-8859-1') # backup encoding
+ except UnicodeError:
+ log.warn('Skipping environment variable "%s" in execution process: unable to convert to unicode using either the default encoding or ISO-8859-1' % (key))
env = uenv
hProcess, hThread, processId, threadId\
= win32process.CreateProcess(appName, cmd, processSA,
import os
import os.path
import activegrid.util.xmlutils as xmlutils
-from IDE import ACTIVEGRID_BASE_IDE
+import activegrid.util.aglogging as aglogging
+
+# REVIEW 07-Mar-06 stoens@activegrid.com -- Ideally move the pieces required
+# to generate the .dpl file out of this module so there's no dependency on wx,
+# instead of doing this try/catch (IDE drags in wx).
+try:
+ from IDE import ACTIVEGRID_BASE_IDE
+except:
+ ACTIVEGRID_BASE_IDE = False
+
if not ACTIVEGRID_BASE_IDE:
import activegrid.model.basedocmgr as basedocmgr
import AppInfo
PROJECT_VERSION_050730 = '10'
PROJECT_VERSION_050826 = '11'
-
-#----------------------------------------------------------------------------
-# XML Marshalling Methods
-#----------------------------------------------------------------------------
-
-def load(fileObject):
- version = xmlutils.getAgVersion(fileObject.name)
- # most current versions on top
- if version == PROJECT_VERSION_050826:
- fileObject.seek(0)
- if ACTIVEGRID_BASE_IDE:
- KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile}
- else:
- KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile, "ag:appInfo" : AppInfo.AppInfo}
- project = xmlutils.load(fileObject.name, knownTypes=KNOWNTYPES, knownNamespaces=xmlutils.KNOWN_NAMESPACES)
- elif version == PROJECT_VERSION_050730:
- fileObject.seek(0)
- project = xmlutils.load(fileObject.name, knownTypes={"project" : Project_10})
- project = project.upgradeVersion()
- else:
- # assume it is old version without version number
- fileObject.seek(0)
- project = xmlutils.load(fileObject.name, knownTypes={"project" : Project_10})
- if project:
- project = project.upgradeVersion()
- else:
- print "Project, unknown version:", version
- return None
-
- if project:
- project._projectDir = os.path.dirname(fileObject.name)
- project.RelativeToAbsPath()
-
- return project
-
-
-def save(fileObject, project, productionDeployment=False):
- if not project._projectDir:
- project._projectDir = os.path.dirname(fileObject.name)
- project.AbsToRelativePath() # temporarily change it to relative paths for saving
- if ACTIVEGRID_BASE_IDE:
- KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile}
- else:
- KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile, "ag:appInfo" : AppInfo.AppInfo}
-
- savedHomeDir = project.homeDir
- if productionDeployment:
- # for deployments, we don't want an abs path in homeDir since that
- # would tie the app to the current filesystem. So unset it.
- project.homeDir = None
-
- xmlutils.save(fileObject.name, project, prettyPrint=True, knownTypes=KNOWNTYPES, knownNamespaces=xmlutils.KNOWN_NAMESPACES)
-
- if productionDeployment:
- project.homeDir = savedHomeDir
-
- project.RelativeToAbsPath() # swap it back to absolute path
-
#----------------------------------------------------------------------------
# Classes
class BaseProject(object):
__xmlname__ = "project"
- __xmlexclude__ = ('fileName', '_projectDir', '_getDocCallback')
+ __xmlexclude__ = ('fileName', '_projectDir', '_getDocCallback', '_cacheEnabled')
__xmlattributes__ = ("_homeDir", "version")
__xmlrename__ = { "_homeDir":"homeDir", "_appInfo":"appInfo" }
__xmlflattensequence__ = { "_files":("file",) }
self._files = []
self._projectDir = None # default for homeDir, set on load
self._homeDir = None # user set homeDir for use in calculating relative path
+ self._cacheEnabled = 0
if not ACTIVEGRID_BASE_IDE:
self._appInfo = AppInfo.AppInfo()
-
+
+
+ def initialize(self):
+ for file in self._files:
+ file._parentProj = self
+
def __copy__(self):
clone = Project()
clone._projectDir = self._projectDir
clone._homeDir = self._homeDir
if not ACTIVEGRID_BASE_IDE:
- clone._appInfo = self._appInfo
+ clone._appInfo = copy.copy(self._appInfo)
return clone
-
- def initialize(self):
- """ Required method for xmlmarshaller """
- pass
-
def GetAppInfo(self):
return self._appInfo
-
+
def AddFile(self, filePath=None, logicalFolder=None, type=None, name=None, file=None):
""" Usage: self.AddFile(filePath, logicalFolder, type, name) # used for initial generation of object
if file:
self._files.append(file)
else:
- self._files.append(ProjectFile(filePath, logicalFolder, type, name, getDocCallback=self._getDocCallback))
+ self._files.append(ProjectFile(self, filePath, logicalFolder, type, name, getDocCallback=self._getDocCallback))
def RemoveFile(self, file):
for file in self._files:
if file.filePath == filePath:
return file
-
+
return None
filePaths = property(_GetFilePaths)
+ def _GetProjectFiles(self):
+ return self._files
+ projectFiles = property(_GetProjectFiles)
+
def _GetLogicalFolders(self):
folders = []
logicalFolders = property(_GetLogicalFolders)
-
+
def _GetPhysicalFolders(self):
physicalFolders = []
return self._homeDir
else:
return self._projectDir
-
+
def _SetHomeDir(self, parentPath):
self._homeDir = parentPath
-
+
def _IsDefaultHomeDir(self):
return (self._homeDir == None)
if relFolder and relFolder not in relativeFolders:
relativeFolders.append(relFolder)
return relativeFolders
-
+
def AbsToRelativePath(self):
for file in self._files:
file.RelativeToAbsPath(self.homeDir)
+ def _SetCache(self, enable):
+ """
+ Only turn this on if your operation assumes files on disk won't change.
+ Once your operation is done, turn this back off.
+ Nested enables are allowed, only the last disable will disable the cache.
+
+ This bypasses the IsDocumentModificationDateCorrect call because the modification date check is too costly, it hits the disk and takes too long.
+ """
+ if enable:
+ if self._cacheEnabled == 0:
+ # clear old cache, don't want to accidentally return stale value
+ for file in self._files:
+ file.ClearCache()
+
+ self._cacheEnabled += 1
+ else:
+ self._cacheEnabled -= 1
+
+
+
+ def _GetCache(self):
+ return (self._cacheEnabled > 0)
+
+
+ cacheEnabled = property(_GetCache, _SetCache)
+
+
#----------------------------------------------------------------------------
# BaseDocumentMgr methods
#----------------------------------------------------------------------------
def fullPath(self, fileName):
fileName = super(BaseProject, self).fullPath(fileName)
-
+
if os.path.isabs(fileName):
absPath = fileName
elif self.homeDir:
def documentRefFactory(self, name, fileType, filePath):
- return ProjectFile(filePath=self.fullPath(filePath), type=fileType, name=name, getDocCallback=self._getDocCallback)
+ return ProjectFile(self, filePath=self.fullPath(filePath), type=fileType, name=name, getDocCallback=self._getDocCallback)
def findAllRefs(self):
if not xformdir:
xformdir = self.homeDir
return xformdir
-
-
+
+
def setRefs(self, files):
self._files = files
else:
class Project(BaseProject, basedocmgr.BaseDocumentMgr):
pass
-
+
class ProjectFile(object):
__xmlname__ = "file"
- __xmlexclude__ = ('_getDocCallback', '_docCallbackCacheReturnValue', '_docModelCallbackCacheReturnValue', '_doc',)
+ __xmlexclude__ = ('_parentProj', '_getDocCallback', '_docCallbackCacheReturnValue', '_docModelCallbackCacheReturnValue', '_doc',)
__xmlattributes__ = ["filePath", "logicalFolder", "type", "name"]
__xmldefaultnamespace__ = xmlutils.AG_NS_URL
- def __init__(self, filePath=None, logicalFolder=None, type=None, name=None, getDocCallback=None):
+ def __init__(self, parent=None, filePath=None, logicalFolder=None, type=None, name=None, getDocCallback=None):
+ self._parentProj = parent
self.filePath = filePath
self.logicalFolder = logicalFolder
self.type = type
def _GetDocumentModel(self):
- # possible bug is if document gets replaced outside of IDE, where we'll return previous doc.
- # originally added a timestamp check but that increased the time again to 4x
- if self._docModelCallbackCacheReturnValue: # accelerator for caching document, got 4x speed up.
+ if (self._docCallbackCacheReturnValue
+ and (self._parentProj.cacheEnabled or self._docCallbackCacheReturnValue.IsDocumentModificationDateCorrect())):
return self._docModelCallbackCacheReturnValue
-
+
if self._getDocCallback:
self._docCallbackCacheReturnValue, self._docModelCallbackCacheReturnValue = self._getDocCallback(self.filePath)
return self._docModelCallbackCacheReturnValue
-
+
return None
-
+
document = property(_GetDocumentModel)
-
+
def _GetDocument(self):
- # Hack, just return the IDE document wrapper that corresponds to the runtime document model
- # callers should have called ".document" before calling ".ideDocument"
- if self._docCallbackCacheReturnValue: # accelerator for caching document, got 4x speed up.
+ # Return the IDE document wrapper that corresponds to the runtime document model
+ if (self._docCallbackCacheReturnValue
+ and (self._parentProj.cacheEnabled or self._docCallbackCacheReturnValue.IsDocumentModificationDateCorrect())):
return self._docCallbackCacheReturnValue
+
+ if self._getDocCallback:
+ self._docCallbackCacheReturnValue, self._docModelCallbackCacheReturnValue = self._getDocCallback(self.filePath)
+ return self._docCallbackCacheReturnValue
+
return None
-
+
ideDocument = property(_GetDocument)
+ def ClearCache(self):
+ self._docCallbackCacheReturnValue = None
+ self._docModelCallbackCacheReturnValue = None
+
+
def _typeEnumeration(self):
return basedocmgr.FILE_TYPE_LIST
-
+
def _GetPhysicalFolder(self):
dir = None
physicalFolder = property(_GetPhysicalFolder)
-
+
def GetRelativeFolder(self, parentPath):
parentPathLen = len(parentPath)
dir = None
if self.filePath:
dir = os.path.dirname(self.filePath)
- if dir.startswith(parentPath):
+ if dir.startswith(parentPath + os.sep):
dir = "." + dir[parentPathLen:] # convert to relative path
if os.sep != '/':
dir = dir.replace(os.sep, '/') # always save out with '/' as path separator for cross-platform compatibility.
""" Used to convert path to relative path for saving (disk format) """
parentPathLen = len(parentPath)
- if self.filePath.startswith(parentPath):
+ if self.filePath.startswith(parentPath + os.sep):
self.filePath = "." + self.filePath[parentPathLen:] # convert to relative path
if os.sep != '/':
self.filePath = self.filePath.replace(os.sep, '/') # always save out with '/' as path separator for cross-platform compatibility.
def RelativeToAbsPath(self, parentPath):
""" Used to convert path to absolute path (for any necessary disk access) """
- if self.filePath.startswith("."): # relative to project file
- self.filePath = os.path.normpath(os.path.join(parentPath, self.filePath))
+ if self.filePath.startswith("./"): # relative to project file
+ self.filePath = os.path.normpath(os.path.join(parentPath, self.filePath)) # also converts '/' to os.sep
#----------------------------------------------------------------------------
import wx.lib.docview
if not self._doc:
docMgr = wx.GetApp().GetDocumentManager()
-
- doc = docMgr.CreateDocument(self.filePath, docMgr.GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW)
- if (doc == None): # already open
- docs = docMgr.GetDocuments()
- for d in docs:
- if d.GetFilename() == self.filePath:
- doc = d
- break
- self._doc = doc
+
+ try:
+ doc = docMgr.CreateDocument(self.filePath, docMgr.GetFlags()|wx.lib.docview.DOC_SILENT|wx.lib.docview.DOC_OPEN_ONCE|wx.lib.docview.DOC_NO_VIEW)
+ if (doc == None): # already open
+ docs = docMgr.GetDocuments()
+ for d in docs:
+ if d.GetFilename() == self.filePath:
+ doc = d
+ break
+ self._doc = doc
+ except Exception,e:
+ aglogging.reportException(e, stacktrace=True)
+
return self._doc
-
+
def _GetLocalServiceProcessName(self):
# HACK: temporary solution to getting process name from wsdlag file.
- return self._GetDoc().GetModel().processName
+ doc = self._GetDoc()
+ if doc:
+ return doc.GetModel().processName
+ else:
+ return None
processName = property(_GetLocalServiceProcessName)
localServiceClassName = property(_GetLocalServiceClassName, _SetLocalServiceClassName)
+ def getServiceParameter(self, message, part):
+ return self._GetDoc().GetModel().getServiceParameter(message, part)
+
# only activate this code if we programatically need to access these values
## def _GetRssServiceBaseURL(self):
## return self._GetDoc().GetModel().rssServiceBaseURL
##
-##
+##
## def _SetRssServiceBaseURL(self, baseURL):
## self._GetDoc().GetModel().rssServiceBaseURL = baseURL
-##
-##
+##
+##
## rssServiceBaseURL = property(_GetRssServiceBaseURL, _SetRssServiceBaseURL)
##
##
## def _GetRssServiceRssVersion(self):
## return self._GetDoc().GetModel().rssServiceRssVersion
-##
+##
##
## def _SetRssServiceRssVersion(self, rssVersion):
## self._GetDoc().GetModel().rssServiceRssVersion = rssVersion
-##
+##
##
## rssServiceRssVersion = property(_GetRssServiceRssVersion, _SetRssServiceRssVersion)
def _GetServiceRefServiceType(self):
# HACK: temporary solution to getting service type from wsdlag file.
- model = self._GetDoc().GetModel()
+ doc = self._GetDoc()
+ if not doc:
+ return None
+ model = doc.GetModel()
if hasattr(model, 'serviceType'):
return model.serviceType
else:
def _SetServiceRefServiceType(self, serviceType):
# HACK: temporary solution to getting service type from wsdlag file.
self._GetDoc().GetModel().serviceType = serviceType
-
+
serviceType = property(_GetServiceRefServiceType, _SetServiceRefServiceType)
def getExternalPackage(self):
# HACK: temporary solution to getting custom code filename from wsdlag file.
- import activegrid.server.deployment as deploymentlib
-
+ import activegrid.model.projectmodel as projectmodel
+ import wx
+ import ProjectEditor
+
appInfo = self._GetDoc().GetAppInfo()
if appInfo.language == None:
- language = deploymentlib.LANGUAGE_DEFAULT
+ language = wx.ConfigBase_Get().Read(ProjectEditor.APP_LAST_LANGUAGE, projectmodel.LANGUAGE_DEFAULT)
else:
language = appInfo.language
-
- if language == deploymentlib.LANGUAGE_PYTHON:
+
+ if language == projectmodel.LANGUAGE_PYTHON:
suffix = ".py"
- elif language == deploymentlib.LANGUAGE_PHP:
+ elif language == projectmodel.LANGUAGE_PHP:
suffix = ".php"
pyFilename = self.name + suffix
return self._GetDoc().GetAppDocMgr().fullPath(pyFilename)
def upgradeVersion(self):
currModel = Project()
for file in self._files:
- currModel._files.append(ProjectFile(file))
+ currModel._files.append(ProjectFile(currModel, file))
return currModel
+#----------------------------------------------------------------------------
+# XML Marshalling Methods
+#----------------------------------------------------------------------------
+
+if ACTIVEGRID_BASE_IDE:
+ KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile}
+else:
+ KNOWNTYPES = {"ag:project" : Project, "ag:file" : ProjectFile,
+ "ag:appInfo" : AppInfo.AppInfo,
+ "ag:deploymentDataSource" : AppInfo.DeploymentDataSource,
+ "ag:dataSourceBinding" : AppInfo.DataSourceBinding}
+
+def load(fileObject):
+ version = xmlutils.getAgVersion(fileObject.name)
+ # most current versions on top
+ if version == PROJECT_VERSION_050826:
+ fileObject.seek(0)
+ project = xmlutils.load(fileObject.name, knownTypes=KNOWNTYPES, knownNamespaces=xmlutils.KNOWN_NAMESPACES, createGenerics=True)
+ elif version == PROJECT_VERSION_050730:
+ fileObject.seek(0)
+ project = xmlutils.load(fileObject.name, knownTypes={"project" : Project_10}, createGenerics=True)
+ project = project.upgradeVersion()
+ else:
+ # assume it is old version without version number
+ fileObject.seek(0)
+ project = xmlutils.load(fileObject.name, knownTypes={"project" : Project_10}, createGenerics=True)
+ if project:
+ project = project.upgradeVersion()
+ else:
+ print "Project, unknown version:", version
+ return None
+
+ if project:
+ project._projectDir = os.path.dirname(fileObject.name)
+ project.RelativeToAbsPath()
+
+ return project
+
+
+def save(fileObject, project, productionDeployment=False):
+ if not project._projectDir:
+ project._projectDir = os.path.dirname(fileObject.name)
+ project.AbsToRelativePath() # temporarily change it to relative paths for saving
+ savedHomeDir = project.homeDir
+ if productionDeployment:
+ # for deployments, we don't want an abs path in homeDir since that
+ # would tie the app to the current filesystem. So unset it.
+ project.homeDir = None
+
+ xmlutils.save(fileObject.name, project, prettyPrint=True, knownTypes=KNOWNTYPES, knownNamespaces=xmlutils.KNOWN_NAMESPACES)
+
+ if productionDeployment:
+ project.homeDir = savedHomeDir
+
+ project.RelativeToAbsPath() # swap it back to absolute path
+
from activegrid.util.lang import *
import activegrid.util.objutils as objutils
import activegrid.util.sysutils as sysutils
+import activegrid.util.appdirs as appdirs
LEVEL_FATAL = logging.FATAL
LEVEL_ERROR = logging.ERROR
LEVEL_DEBUG = logging.DEBUG
EXCEPTION_INFO = 'exceptionInfo'
+loggingInitialized = False
LOG_MODE_IDE = 1
LOG_MODE_TESTRUN = 2
LOG_MODE_RUN = 3
-def initLogging(mode):
- configFile = None
- if (mode == LOG_MODE_IDE):
- configFile = os.getenv("AG_LOGCONFIG_IDE")
- elif (mode == LOG_MODE_TESTRUN):
- configFile = os.getenv("AG_LOGCONFIG_TESTRUN")
- else:
- configFile = os.getenv("AG_LOGCONFIG_RUN")
- if ((configFile == None) or not os.path.exists(configFile)):
+def initLogging(mode, force=False):
+ global ag_debugLogger, loggingInitialized
+ if (force or not loggingInitialized):
+ loggingInitialized = True
+ configFile = None
if (mode == LOG_MODE_IDE):
- configFile = "IDELog"
+ configFile = os.getenv("AG_LOGCONFIG_IDE")
elif (mode == LOG_MODE_TESTRUN):
- configFile = "TestRunLog"
+ configFile = os.getenv("AG_LOGCONFIG_PYTESTRUN")
else:
- configFile = "RunLog"
- configFile = sysutils.mainModuleDir + "/py" + configFile + ".ini"
- if (os.path.exists(configFile)):
- fileConfig(configFile)
- else:
- defaultStream = sys.stderr
- if (mode == LOG_MODE_RUN):
- defaultStream = sys.stdout
- handler = logging.StreamHandler(defaultStream)
- handler.setLevel(logging.INFO)
- handler.setFormatter(logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s"))
- logging.getLogger().addHandler(handler)
- return configFile
-
+ configFile = os.getenv("AG_LOGCONFIG_RUN")
+ if ((configFile == None) or not os.path.exists(configFile)):
+ if (mode == LOG_MODE_IDE):
+ configFile = "IDELog"
+ elif (mode == LOG_MODE_TESTRUN):
+ configFile = "TestRunLog"
+ else:
+ configFile = "RunLog"
+ configFile = os.path.join(appdirs.getSystemDir(appdirs.AG_LOGS_DIR), "py" + configFile + ".ini")
+ if (os.path.exists(configFile)):
+ print "Using logging configuration file: %s" % configFile
+ fileConfig(configFile)
+ else:
+ print "*** Cannot find logging configuration file (%s) -- setting default logging level to WARN ***" % (configFile)
+ defaultStream = sys.stderr
+ if (mode == LOG_MODE_RUN):
+ defaultStream = sys.stdout
+ handler = logging.StreamHandler(defaultStream)
+ handler.setLevel(logging.DEBUG)
+ handler.setFormatter(logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s"))
+ logging.getLogger().addHandler(handler)
+ logging.getLogger().setLevel(logging.WARN)
+ ag_debugLogger = logging.getLogger("activegrid.debug")
+ ag_debugLogger.setLevel(logging.DEBUG)
+ return configFile
+
ag_debugLogger = logging.getLogger("activegrid.debug")
def log(logger, level, msg, *params):
if not e.exceptionInfo.has_key(key): # Never overwrite exception info since we assume earlier info is more specific
e.exceptionInfo[key] = value
-def reportException(out=None, stacktrace=False, diffable=False, exception=None):
+def reportException(exception, out=None, stacktrace=False, diffable=False):
exstr = exceptionToString(exception, stacktrace, diffable)
if (out == None):
print exstr
else:
print >> out, exstr
-def exceptionToString(exception=None, stacktrace=False, diffable=False):
- if (exception == None):
- extype, val, t = sys.exc_info()
- else:
- extype = objutils.typeToString(exception)
- val = exception
- if (stacktrace):
- e,v,t = sys.exc_info()
+def exceptionToString(exception, stacktrace=False, diffable=False):
+ extype = objutils.typeToString(exception)
+ val = exception
+ if (stacktrace):
+ e,v,t = sys.exc_info()
if (diffable):
exstr = removeFileRefs(str(val))
else:
# Name: appdirs.py
# Purpose: Utilities for retrieving special application dirs
#
-# Author: Kevin Ollivier
+# Author: Kevin Ollivier, Jeff Norton
#
# Created: 8/27/05
# CVS-ID: $Id$
# License: wxWindows License
#----------------------------------------------------------------------------
-# NOTE: This was made a separate file because it depends upon the
-# wx.StandardPaths module, and thus, on wxWidgets, unlike other
-# utils modules. I wanted to ensure this module is never loaded
-# from the web server, etc.
-
+from activegrid.util.lang import *
import sys
import os
import string
-import wx
-
-def isWindows():
- return os.name == 'nt'
-
-def _generateDocumentsDir():
- path = ""
- if sys.platform == "win32":
- from win32com.shell import shell, shellcon
- path=shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
- elif sys.platform == "darwin":
- import macfs, MACFS
- fsspec_disk, fsspec_desktop = macfs.FindFolder( MACFS.kOnSystemDisk, MACFS.kDocumentsFolderType, 0)
- path = macfs.FSSpec((fsspec_disk, fsspec_desktop, '')).as_pathname()
-
- if path == "":
- path = os.path.expanduser("~")
-
- return path
+import activegrid.util.sysutils as sysutils
+
+def _getSystemDir(kind):
+ if (kind == AG_LOGS_DIR):
+ return os.path.join(getSystemDir(AG_SYSTEM_DIR) , "logs")
+ elif (kind == AG_DEMOS_DIR):
+ return os.path.join(getSystemDir(AG_SYSTEM_DIR), "demos")
+ else:
+ path = ""
+ if (sysutils.isServer()):
+ path = os.getenv("ACTIVEGRID_SERVER_HOME")
+ if ((path is None) or (len(path) < 1)):
+ path = sysutils.mainModuleDir
+ else:
+ path = os.getenv("AG_DOCUMENTS_DIR")
+ if ((path is None) or (len(path) < 1)):
+ if sysutils.isWindows():
+ ifDefPy()
+ try:
+ from win32com.shell import shell, shellcon
+ path = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
+ except:
+ pass
+ endIfDef()
+ if ((path is None) or (len(path) < 1)):
+ homedrive = asString(os.getenv("HOMEDRIVE"))
+ homepath = os.getenv("HOMEPATH")
+## if ((homedrive is not None) and (len(homedrive) > 0) and (homepath is not None) and (len(homepath) > 0)):
+ path = os.path.join(homedrive, homepath, "MYDOCU~1")
+ else:
+ ifDefPy()
+ if sys.platform == "darwin":
+ try:
+ import macfs
+ import MACFS
+ fsspec_disk, fsspec_desktop = macfs.FindFolder(MACFS.kOnSystemDisk, MACFS.kDocumentsFolderType, 0)
+ path = macfs.FSSpec((fsspec_disk, fsspec_desktop, '')).as_pathname()
+ except:
+ pass
+ endIfDef()
+
+ ifDefPy()
+ if ((path is None) or (len(path) < 1)):
+ path = os.path.expanduser("~")
+ endIfDef()
+ if ((path is None) or (len(path) < 1)):
+ path = "/"
+ path = os.path.join(path, "ActiveGrid")
-documents_folder = _generateDocumentsDir()
+ return path
+
+
+AG_SYSTEM_DIR = 0
+AG_LOGS_DIR = 1
+AG_DEMOS_DIR = 2
+
+__systemDir = None
+__logsDir = None
+__demosDir = None
+
+def getSystemDir(kind=0):
+ if (kind == AG_SYSTEM_DIR):
+ global __systemDir
+ if (__systemDir is None):
+ __systemDir = _getSystemDir(kind)
+ return __systemDir
+ elif (kind == AG_LOGS_DIR):
+ global __logsDir
+ if (__logsDir is None):
+ __logsDir = _getSystemDir(kind)
+ return __logsDir
+ elif (kind == AG_DEMOS_DIR):
+ global __demosDir
+ if (__demosDir is None):
+ __demosDir = _getSystemDir(kind)
+ return __demosDir
+ return None
+
# NOTE: We don't set this at startup because wxStandardPaths needs a running
# application object. This makes sure the wxApp will always be created when
# we get the folder.
+ifDefPy()
def getAppDataFolder():
- # wxStandardPaths requires a running app
- if wx.GetApp() and wx.Platform != "__WXGTK__":
- data_folder = wx.StandardPaths.Get().GetUserDataDir()
- if not os.path.exists(data_folder):
- os.mkdir(data_folder)
- return data_folder
- else:
- # wxBug: on *nix, it wants to point to ~/.appname, but
- # so does wxConfig... For now, redirect this to ~/.appbuilder
- # when this is fixed, we'll migrate settings to the correct place
- return os.path.join(os.path.expanduser("~"), ".appbuilder")
+ try:
+ # NOTE: cannot import wx from the server
+ import wx
+ # wxStandardPaths requires a running app
+ if wx.GetApp() and wx.Platform != "__WXGTK__":
+ data_folder = wx.StandardPaths.Get().GetUserDataDir()
+ if not os.path.exists(data_folder):
+ os.mkdir(data_folder)
+ return data_folder
+ except:
+ pass
+ # wxBug: on *nix, it wants to point to ~/.appname, but
+ # so does wxConfig... For now, redirect this to ~/.appbuilder
+ # when this is fixed, we'll migrate settings to the correct place
+ return os.path.join(os.path.expanduser("~"), ".appbuilder")
+endIfDef()
- return ""
+ifDefPy()
+def createSystemDirs():
+ if (not os.path.exists(getSystemDir())):
+ os.mkdir(getSystemDir())
+ if (not os.path.exists(getSystemDir(AG_LOGS_DIR))):
+ os.mkdir(getSystemDir(AG_LOGS_DIR))
+ if (not os.path.exists(getSystemDir(AG_DEMOS_DIR))):
+ os.mkdir(getSystemDir(AG_DEMOS_DIR))
+endIfDef()
--- /dev/null
+#----------------------------------------------------------------------------
+# Name: datetimeparser.py
+#
+# Purpose: - Instantiate datetime.datetime/date instance from a string
+# date representation.
+# Uses dateutil from http://labix.org/python-dateutil.
+#
+# - Creates string representation of datetime/date instance.
+#
+#
+# Author: Simon Toens
+#
+# Created: 28-Feb-06
+# CVS-ID:
+# Copyright: (c) 2005 ActiveGrid, Inc.
+# License: wxWindows License
+#----------------------------------------------------------------------------
+
+import datetime
+
+try:
+ import dateutil.parser
+ DATEUTIL_INSTALLED = True
+except ImportError:
+ DATEUTIL_INSTALLED = False
+
+ISO_8601_DATE_FORMAT = "%Y-%m-%d"
+ISO_8601_TIME_FORMAT = "%H:%M:%S"
+ISO_8601_DATETIME_FORMAT = "%s %s" %(ISO_8601_DATE_FORMAT,
+ ISO_8601_TIME_FORMAT)
+
+DEFAULT_DATETIME = datetime.datetime(1, 1, 1, 0, 0, 0, 0)
+
+
+def format(dateobj, formatstr=None):
+ if (formatstr != None and _isDateTimeObject(dateobj)):
+ return dateobj.strftime(str(formatstr))
+ return str(dateobj)
+
+
+def parse(datestr, formatstr=None, asdate=False, astime=False):
+ """Instantiates and returns a datetime instance from the datestr datetime
+ representation.
+
+ Optionally, a format string may be used. The format is only loosely
+ interpreted, its only purpose beeing to determine if the year is first
+ or last in datestr, and whether the day is in front or follows the
+ month. If no formatstr is passed in, dateutil tries its best to parse
+ the datestr. The default date format is YYYY-mm-dd HH:SS.
+
+ If asdate is True, returns a date instance instead of a datetime
+ instance, if astime is True, returns a time instance instead of a
+ datetime instance."""
+
+
+ dayfirst, yearfirst = _getDayFirstAndYearFirst(formatstr)
+
+ rtn = None
+
+ try:
+ if DATEUTIL_INSTALLED:
+ rtn = dateutil.parser.parse(str(datestr), fuzzy=True,
+ dayfirst=dayfirst, yearfirst=yearfirst,
+ default=DEFAULT_DATETIME)
+ else:
+ rtn = DEFAULT_DATETIME
+ except:
+ rtn = DEFAULT_DATETIME
+
+ if (asdate and isinstance(rtn, datetime.datetime)):
+ rtn = datetime.date(rtn.year, rtn.month, rtn.day)
+ elif (astime and isinstance(rtn, datetime.datetime)):
+ rtn = datetime.time(rtn.hour, rtn.minute, rtn.second, rtn.microsecond)
+
+ return rtn
+
+
+def _isDateTimeObject(obj):
+ return (isinstance(obj, datetime.datetime) or
+ isinstance(obj, datetime.date) or
+ isinstance(obj, datetime.time))
+
+
+def _getDayFirstAndYearFirst(formatstr):
+ dayFirst = False
+ yearFirst = False
+
+ gotYear = False
+ gotMonth = False
+ gotDay = False
+
+ if (formatstr == None):
+ formatstr = ""
+
+ for c in formatstr:
+ if (c.lower() == "y"):
+ if (gotYear):
+ continue
+ if (not gotDay and not gotMonth):
+ yearFirst = True
+ gotYear = True
+
+ elif (c.lower() == "m"):
+ if (gotMonth):
+ continue
+ if (not gotDay):
+ dayFirst = False
+ gotMonth = True
+
+ elif (c.lower() == "d"):
+ if (gotDay):
+ continue
+ if (not gotMonth):
+ dayFirst = True
+ gotDay = True
+
+
+ return dayFirst, yearFirst
import activegrid.util.aglogging as aglogging
import activegrid.util.sysutils as sysutils
+import activegrid.util.utillang as utillang
from activegrid.util.lang import *
global fileutilsLogger
aglogging.setLevelFatal(fileutilsLogger)
#logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
+def addRef(varname):
+ return "${%s}" % varname
+
+AG_SYSTEM_VAR_NAMES = [] # all AG System vars, with ${} syntax
+
+AG_SYSTEM_VAR = "AG_SYSTEM"
+AG_SYSTEM_VAR_REF = addRef(AG_SYSTEM_VAR)
+AG_SYSTEM_VAR_NAMES.append(AG_SYSTEM_VAR_REF)
+
+AG_SYSTEM_STATIC_VAR = "AG_SYSTEM_STATIC"
+AG_SYSTEM_STATIC_VAR_REF = addRef(AG_SYSTEM_STATIC_VAR)
+AG_SYSTEM_VAR_NAMES.append(AG_SYSTEM_STATIC_VAR_REF)
+
+AG_APP_VAR = "AG_APP"
+AG_APP_STATIC_VAR = "AG_APP_STATIC"
+
+# _initAGSystemVars needs to be called to initialize the following two
+# containers:
+EXPANDED_AG_SYSTEM_VARS = {} # ${varname} -> value (path)
+# ${varname}, ordered from longest to shortest path value
+AG_SYSTEM_VARS_LENGTH_ORDER = []
+
+def _initAGSystemVars():
+ if (len(EXPANDED_AG_SYSTEM_VARS) > 0):
+ return
+
+ for v in AG_SYSTEM_VAR_NAMES:
+ EXPANDED_AG_SYSTEM_VARS[v] = os.path.abspath(expandVars(v))
+ AG_SYSTEM_VARS_LENGTH_ORDER.append(v)
+
+ AG_SYSTEM_VARS_LENGTH_ORDER.sort(_sortByValLength)
+
+
+def parameterizePathWithAGSystemVar(inpath):
+ """Returns parameterized path if path starts with a known AG directory. Otherwise returns path as it was passed in."""
+ _initAGSystemVars()
+ path = inpath
+ if not sysutils.isWindows():
+ # ensure we have forward slashes
+ path = path.replace("\\", "/")
+
+ path = os.path.abspath(path)
+
+ for varname in AG_SYSTEM_VARS_LENGTH_ORDER:
+ varval = EXPANDED_AG_SYSTEM_VARS[varname]
+ if path.startswith(varval):
+ return path.replace(varval, varname)
+
+ return inpath
+
+def startsWithAgSystemVar(path):
+ """Returns True if path starts with a known AG system env var, False otherwise."""
+ for varname in AG_SYSTEM_VAR_NAMES:
+ if path.startswith(varname):
+ return True
+ return False
+
+def _sortByValLength(v1, v2):
+ return len(EXPANDED_AG_SYSTEM_VARS[v2]) - len(EXPANDED_AG_SYSTEM_VARS[v1])
def makeDirsForFile(filename):
d = os.path.dirname(filename)
f = file(filename, mode)
return f
-def compareFiles(file1, file2):
+def compareFiles(file1, file2, ignore=None):
## result = filecmp.cmp(file1, file2)
## if result:
## return 0
elif (len(line2) == 0):
return -1
elif (line1 != line2):
+ if (ignore != None):
+ if (line1.startswith(ignore) or line2.startswith(ignore)):
+ continue
line1 = line1.replace(" ", "")
line2 = line2.replace(" ", "")
if (line1 != line2):
continue
return -1
-def expandVars(value):
+def expandKnownAGVars(value):
+ return expandVars(value, includeEnv=False)
+
+def expandVars(value, includeEnv=True):
"""Syntax: ${myvar,default="default value"}"""
import activegrid.runtime as runtime
sx = value.find("${")
defaultValue = value[defsx+10:endx-1]
if (defaultValue == None):
varname = value[sx+2:endx]
- if (varname == "AG_SYSTEM"):
+ if (varname == AG_SYSTEM_VAR):
varval = runtime.appInfo.getSystemDir()
- elif (varname == "AG_SYSTEM_STATIC"):
+ elif (varname == AG_SYSTEM_STATIC_VAR):
varval = runtime.appInfo.getSystemStaticDir()
- elif (varname == "AG_APP"):
+ elif (varname == AG_APP_VAR):
varval = runtime.appInfo.getAppDir()
- elif (varname == "AG_APP_STATIC"):
+ elif (varname == AG_APP_STATIC_VAR):
varval = runtime.appInfo.getAppStaticDir()
else:
- varval = os.getenv(varname)
+ if (includeEnv):
+ varval = os.getenv(varname)
+ else:
+ varval = None
if ((varval == None) and (defaultValue != None)):
varval = defaultValue
if (varval == None):
return otherdir + path[ix+7:]
-def visit(directory, files, extension):
+def visit(directory, files, extension, maxLevel=None, level=1):
testdirs = os.listdir(directory)
for thing in testdirs:
fullpath = os.path.join(directory, thing)
- if (os.path.isdir(fullpath)):
- visit(fullpath, files, extension)
+ if (os.path.isdir(fullpath) and (maxLevel == None or level < maxLevel)):
+ visit(fullpath, files, extension, maxLevel, level+1)
elif thing.endswith(extension):
fullname = os.path.normpath(os.path.join(directory, thing))
if not fullname in files:
files.append(fullname)
-def listFilesByExtensionInPath(path=[], extension='.lyt'):
- #Collect input and output arguments into one bunch
+def listFilesByExtensionInPath(path=[], extension='.lyt', maxLevel=None):
retval = []
for directory in path:
- visit(directory, retval, extension)
+ visit(directory, retval, extension, maxLevel)
return retval
def getFileLastModificationTime(fileName):
shutil.rmtree(file)
endIfDef()
+def getUserTempDir():
+ systemTempDir = utillang.getSystemTempDir()
+ userName = sysutils.getUserName()
+ userNameNoSpace = userName.replace('_','__').replace(' ','_')
+ userTempDir = systemTempDir + os.sep + "activegrid_" + userNameNoSpace
+ return userTempDir
+
+def createUserTempDir():
+ userTempDir = getUserTempDir()
+ if not os.path.exists(userTempDir):
+ os.makedirs(userTempDir)
+ os.chmod(userTempDir, 0700)
+
+createUserTempDir()
+
ifDefPy()
import warnings
warnings.filterwarnings("ignore", message="tmpnam is a potential security risk to your program")
import os
import __builtin__
import types
-import xml.sax.saxutils as saxutils
+import activegrid.util.utillang as utillang
+import activegrid.util.datetimeparser as datetimeparser
from types import *
from activegrid.util.lang import *
classDesc = obj.__class__
setattr(classDesc, attr, value)
+def hasAttrFast(obj, name):
+ if hasRawAttr(obj, name):
+ return True
+ if hasattr(obj, '_complexType'):
+ complexType=obj._complexType
+ element=complexType.findElement(name)
+ if element:
+ return True
+ if hasattr(obj, name):
+ return True
+ return False
+
def moduleForName(moduleName):
module = None
pathList = moduleName.split('.')
if ((len(objargs) < 1) or (objargs[0].lower() == "false") or (not objargs[0])):
return False
return True
- if className == "str" or className == "unicode": # don"t strip: blanks are significant
+ if className == "str" or className == "unicode": # don't strip: blanks are significant
if len(objargs) > 0:
try:
- return saxutils.unescape(objargs[0]).encode()
+ return utillang.unescape(objargs[0]).encode()
except:
return "?"
else:
return ""
-
+
+ if className == "date":
+ return datetimeparser.parse(objargs[0], asdate=True)
+ if className == "datetime":
+ return datetimeparser.parse(objargs[0])
+ if className == "time":
+ return datetimeparser.parse(objargs[0], astime=True)
+
classtype = classForName(className)
if (classtype == None):
raise Exception("Could not find class %s" % className)
def getClassProperty(classType, propertyName):
return getattr(classType, propertyName)
-def toDiffableRepr(value, exclude=None):
+def toDiffableRepr(value, maxLevel=None):
if (value == None):
return "None"
+ if (maxLevel == None):
+ maxLevel = 8
+ maxLevel -= 1
+ if (maxLevel < 0):
+ return typeToString(value, PRINT_OBJ_DIFFABLE)
+## if ((exclude != None) and not isinstance(value, (basestring, int))):
+## for v in exclude:
+## if (v is value):
+## return "<recursive reference>"
+## exclude.append(value)
## elif (isinstance(value, ObjectType) and hasattr(value, "__dict__")):
## if (exclude == None):
## exclude = []
## s = "%s(%s)" % (type(value), toDiffableString(value.__dict__, exclude))
- elif (not isinstance(value, (BooleanType, ClassType, ComplexType, DictType, DictionaryType,
+ if (not isinstance(value, (BooleanType, ClassType, ComplexType, DictType, DictionaryType,
FloatType, IntType, ListType, LongType, StringType, TupleType,
UnicodeType, BufferType, BuiltinFunctionType, BuiltinMethodType,
CodeType, FrameType, FunctionType, GeneratorType, InstanceType,
LambdaType, MethodType, ModuleType, SliceType, TracebackType,
TypeType, XRangeType))):
- if (hasattr(value, "__str__")):
+ if (hasattr(value, "_toDiffableString")):
+ s = value._toDiffableString(maxLevel)
+ elif (hasattr(value, "__str__")):
s = str(value)
elif (hasattr(value, "__dict__")):
- s = "%s(%s)" % (type(value), toDiffableString(value.__dict__, exclude))
+ s = "%s(%s)" % (type(value), toDiffableString(value.__dict__, maxLevel))
else:
s = str(type(value))
ix2 = s.find(" object at 0x")
else:
items.append("'%s'" % v)
else:
- items.append(toDiffableString(v, exclude))
+ items.append(toDiffableString(v, maxLevel))
s = "[" + ", ".join(items) + "]"
elif (isinstance(value, dict)):
- if (exclude == None):
- exclude = []
items = []
for key, val in value.iteritems():
if (isinstance(val, UnicodeType)):
- items.append("'%s': u'%s'" % (key, toDiffableString(val, exclude)))
+ items.append("'%s': u'%s'" % (key, toDiffableString(val, maxLevel)))
elif (isinstance(val, basestring)):
- items.append("'%s': '%s'" % (key, toDiffableString(val, exclude)))
+ items.append("'%s': '%s'" % (key, toDiffableString(val, maxLevel)))
else:
- items.append("'%s': %s" % (key, toDiffableString(val, exclude)))
+ items.append("'%s': %s" % (key, toDiffableString(val, maxLevel)))
s = "{" + ", ".join(items) + "}"
else:
s = str(value)
return s
-def toDiffableString(value, exclude=None):
- if (value == None):
- return "None"
- if ((exclude != None) and not isinstance(value, (basestring, int))):
- for v in exclude:
- if (v is value):
- return "<recursive reference>"
- exclude.append(value)
- s = toDiffableRepr(value)
+def toDiffableString(value, maxLevel=None):
+## if (value == None):
+## return "None"
+## if ((exclude != None) and not isinstance(value, (basestring, int))):
+## for v in exclude:
+## if (v is value):
+## return "<recursive reference>"
+## exclude.append(value)
+ s = toDiffableRepr(value, maxLevel)
ds = ""
i = s.find(" at 0x")
start = 0
--- /dev/null
+#----------------------------------------------------------------------------
+# Name: parser.py
+# Purpose: parsing utilities
+#
+# Author: Jeff Norton
+#
+# Created: 8/9/05
+# CVS-ID: $Id$
+# Copyright: (c) 2004-2005 ActiveGrid, Inc.
+# License: wxWindows License
+#----------------------------------------------------------------------------
+
+import re
+from activegrid.util.lang import *
+ifDefPy()
+import string
+import array
+endIfDef()
+
+XPATH_ROOT_VAR = '__rootObj__'
+GETOBJECTPARTNAMES = ["primaryRef", "ref", "orderings", "limit"]
+
+class Tokenizer(object):
+
+ TOKEN_IDENT = 1
+ TOKEN_STRING = 2
+ TOKEN_OP = 3
+ TOKEN_WS = 4
+## TOKEN_PLACEHOLDER = 5
+
+ def __init__(self, text, identStart=None, tokenSep=None, ignoreWhitespace=True):
+ """
+Turn a string into individual tokens. Three types of tokens are recognized:
+ TOKEN_IDENT: identifiers (those that start with the identStart pattern)
+ TOKEN_STRING: quoted string
+ TOKEN_OP: everything else
+Tokens are separated by white space or the tokenSep pattern.
+Constructor parameters:
+ text: The string to tokenize
+ identStart: A regular expression describing characters which start an identifier
+ The default expression accepts letters, "_", and "/".
+ tokenSep: A regular expression describing the characters which end a token
+ (in addition to whitespace). The default expression accepts
+ anything except alpha-numerics, "_", "/", and ":".
+Usage:
+ Invoke getNextToken (or next) to get the next token. The instance variables
+ token, and tokenVal will be populated with the current token type (TOKEN_IDENT,
+ TOKEN_STRING, or TOEKN_OP) and value respectively. nextToken and nextTokenVal
+ will also be available for lookahead. The next method is similar to
+ getNextToken but also returns the token value. A value of None signals end
+ of stream.
+ """
+ self.ignoreWhitespace=ignoreWhitespace
+ ifDefPy()
+ if (isinstance(text, array.array)):
+ text = text.tostring()
+ endIfDef()
+ self.text = asString(text)
+ self.textIndex = 0
+ self.textLen = len(self.text)
+ self.token = None
+ self.tokenVal = None
+ self.nextToken = None
+ self.nextTokenVal = None
+ if (identStart == None):
+ identStart = "[a-zA-Z_/]"
+ if (tokenSep == None):
+ tokenSep = "[^a-zA-Z0-9_/:]"
+ self.identStart = re.compile(identStart)
+ self.tokenSep = re.compile(tokenSep)
+ self.getNextToken() # Prime the pump
+
+ def isEscaped(text, index):
+ if ((index > 0) and (text[index-1] == '\\') and ((index < 2) or (text[index-2] != '\\'))):
+ return True
+ return False
+ isEscaped = staticmethod(isEscaped)
+
+ def findClosingQuote(text, index, char):
+ index = index + 1
+ while True:
+ endIndex = text.find(char, index)
+ if (endIndex < 1):
+ return -1
+ if (Tokenizer.isEscaped(text, endIndex)):
+ index = endIndex+1
+ else:
+ break
+ return endIndex + 1
+ findClosingQuote = staticmethod(findClosingQuote)
+
+ def _findClosing(self, char):
+ if (self.textIndex >= self.textLen):
+ raise Exception("The text \"%s\" has an unmatched string starting at %d" % (self.text, self.textIndex))
+ index = Tokenizer.findClosingQuote(self.text, self.textIndex, char)
+ if (index < 0):
+ raise Exception("The text \"%s\" has an unmatched string starting at %d" % (self.text, self.textIndex-1))
+ return index
+
+ def next(self):
+ self.getNextToken()
+ if (self.token == None):
+ raise StopIteration()
+ return self.tokenVal
+
+ def getNextToken(self):
+ self.token = self.nextToken
+ self.tokenVal = self.nextTokenVal
+ while (self.textIndex < self.textLen):
+ c = self.text[self.textIndex]
+ if (c not in string.whitespace):
+ if (c == '"' or c == "'" or c == '`'):
+ endIndex = self._findClosing(c)
+ self.nextToken = self.TOKEN_STRING
+ self.nextTokenVal = self.text[self.textIndex:endIndex]
+ self.textIndex = endIndex
+ return
+ elif (self.identStart.search(c)):
+ endMatch = self.tokenSep.search(self.text, self.textIndex+1)
+ if (endMatch):
+ endIndex = endMatch.start()
+ else:
+ endIndex = self.textLen
+ self.nextToken = self.TOKEN_IDENT
+ self.nextTokenVal = self.text[self.textIndex:endIndex]
+ self.textIndex = endIndex
+ return
+ else:
+ self.nextToken = self.TOKEN_OP
+ endIndex = self.textIndex + 1
+ if (c == '<' or c == '>' or c == '!' or c == '='):
+ if ((endIndex < self.textLen) and (self.text[endIndex] == '=')):
+ endIndex += 1
+ elif ((c == '%') and (endIndex < self.textLen)):
+ c = self.text[endIndex]
+ if (c in ['d', 'i', 'o', 'u', 'x', 'X', 'e', 'E', 'f', 'F', 'g', 'G', 'c', 'r', 's', '%']):
+ endIndex += 1
+## self.nextToken = self.TOKEN_PLACEHOLDER # Should really be this but no one can handle it yet
+ self.nextTokenVal = self.text[self.textIndex:endIndex]
+ self.textIndex = endIndex
+ return
+ elif not self.ignoreWhitespace:
+ self.nextToken=self.TOKEN_WS
+ self.nextTokenVal=""
+ while c in string.whitespace:
+ self.nextTokenVal+=c
+ self.textIndex+=1
+ if self.textIndex==len(self.text):
+ break
+ c=self.text[self.textIndex]
+ return
+ self.textIndex += 1
+ self.nextToken = None
+ self.nextTokenVal = None
+
+def isXPathNonVar(var):
+ """Returns true iff var is a string ("foo" or 'foo') or a number."""
+ if (var.startswith("'") and var.endswith("'")) or \
+ (var.startswith('"') and var.endswith('"')):
+ return True
+
+ # list from XPathToCode, below
+ if var.lower() in ["count", "empty", "true", "false", "null", "and", "or", \
+ "like", "not"]:
+ return True
+
+ try:
+ t=int(var)
+ return True
+ except TypeError, e:
+ pass
+ except ValueError, e:
+ pass
+
+ return False
+
+def xpathToCode(xpaths, convertBracket=True):
+ if ((xpaths == None) or (len(xpaths) < 1)):
+ return "True"
+ if (not isinstance(xpaths, (list, tuple))):
+ xpaths = [xpaths]
+ result = []
+ for xpath in xpaths:
+ t = Tokenizer(xpath, "[a-zA-Z0-9_/:\.]", "[^a-zA-Z0-9_/:\.]", ignoreWhitespace=False)
+ expr = []
+ lastToken=None
+ while t.nextToken != None:
+ t.getNextToken()
+ if (t.token == Tokenizer.TOKEN_WS):
+ expr.append(" ")
+ elif (t.token == Tokenizer.TOKEN_OP):
+ if (t.tokenVal == "="):
+ expr.append("==")
+ elif (t.tokenVal == "[" and convertBracket):
+ expr.append("(")
+ elif (t.tokenVal == "]" and convertBracket):
+ expr.append(")")
+ else:
+ expr.append(t.tokenVal)
+ elif (t.token == Tokenizer.TOKEN_IDENT):
+ if (t.tokenVal == "and"):
+ expr.append(" and ")
+ elif (t.tokenVal == "or"):
+ expr.append(" or ")
+ elif (t.tokenVal == "not"):
+ expr.append(" not ")
+ elif (t.tokenVal == "like"):
+ # REVIEW stoens@activegrid.com 02-Nov-05 --
+ # This is very limited support for like:
+ # typically like queries look like this: "foo like 'blah%'".
+ # So translate this into "foo.startswith(blah)".
+ # We should use a regular expression to support '%'s in
+ # arbitrary places in the string. After 1.1.
+ if t.nextToken and t.nextTokenVal.endswith("%'"):
+ t.getNextToken() # throw away the "like" token
+ last = len(expr) - 1
+ expr[last] = "%s.startswith(%s')"\
+ % (expr[last], t.tokenVal[:-2])
+ else:
+ # old behavior
+ expr.append(t.tokenVal)
+
+ elif (t.tokenVal == "count"):
+ expr.append("len")
+ elif (t.tokenVal == 'empty'):
+ expr.append('ctx.isEmptyPath')
+ elif (t.tokenVal == 'true'):
+ expr.append(_parseConstantFunction(t, 'True'))
+ elif (t.tokenVal == 'false'):
+ expr.append(_parseConstantFunction(t, 'False'))
+ elif (t.tokenVal == 'null'):
+ expr.append(_parseConstantFunction(t, 'None'))
+ elif (-1!=t.tokenVal.find(':')):
+ serviceDef, args=_parseServiceFunction(t)
+
+ # XXX handle serviceDef, args being None
+
+ for i in range(len(args)):
+ args[i]=xpathToCode(args[i], False)
+ jargs="[%s]" % (",".join(args))
+
+ # XXX should be processmodel.DATASERVICE_PROCESS_NAME, not "dataservice"
+ if serviceDef[0]=='dataservice':
+ expr.append("runtimesupport.invokeDataServiceWrapper(%s, %s, ctx, locals())" % \
+ (serviceDef, jargs))
+ else:
+ expr.append("runtimesupport.invokeServiceWrapper(%s, %s, ctx)" % \
+ (serviceDef, jargs))
+ else:
+ if (lastToken==')' or lastToken==']'):
+ wasFunc=True
+ else:
+ wasFunc=False
+ if (t.tokenVal.startswith('/')) and not wasFunc:
+ expr.append(XPATH_ROOT_VAR)
+ expr.append(t.tokenVal.replace('/','.'))
+ lastToken=t.tokenVal
+ else:
+ expr.append(t.tokenVal)
+
+
+ if (len(expr) == 2 and expr[0]==" "):
+ expr = "".join(expr)
+ result.append(expr)
+ elif (len(expr) > 1):
+ expr = "".join(expr)
+ result.append("(%s)" % expr)
+ elif (len(expr) > 0):
+ result.append(expr[0])
+
+ return " and ".join(result)
+
+def _parseArgs(t):
+ args=[]
+ argcon=""
+
+ if t.tokenVal!='(':
+ return []
+ if t.nextTokenVal==')':
+ t.getNextToken()
+ return []
+
+ depth=1
+
+ while(depth!=0):
+ if not t.nextToken:
+ raise Exception("parameters list with no closing ) after token: %s" % t.tokenVal)
+ t.getNextToken()
+
+ if t.tokenVal=='(':
+ depth+=1
+ if t.tokenVal==')':
+ depth-=1
+
+ if depth==0 or (depth==1 and t.tokenVal==','):
+ args.append(argcon)
+ argcon=""
+ else:
+ argcon+=t.tokenVal
+ return args
+
+def _parseServiceFunction(t):
+ """Parses what appears to be a service function call into serviceDefs and args lists.
+
+ Returns None, None if the serviceFunction appears to be invalid.
+ """
+ if t.nextTokenVal!='(':
+ return t.tokenVal, None
+
+ serviceDef=t.tokenVal.split(':')
+ t.getNextToken()
+ args=_parseArgs(t)
+
+ return serviceDef, args
+
+def _parseConstantFunction(t, outputValue):
+ firstVal = t.tokenVal
+ if t.nextTokenVal != '(':
+ return firstVal
+ t.getNextToken()
+ if t.nextTokenVal != ')':
+ return "%s%s" % (firstVal, '(')
+ t.getNextToken()
+ return outputValue
+
+def parseDSPredicate(ctx, str, vars, valueList=None):
+ from activegrid.util.utillang import evalCode
+ from activegrid.util.utillang import ObjAsDict
+
+ if valueList == None:
+ valueList = []
+ indexVar=0
+ oldIndexVar=0
+ sourceStr=str
+ inlinedPredicate=[]
+ qualifications=[]
+ while True:
+ oldIndexVar = indexVar
+ dollarCurlForm = False
+ quoted = False
+ indexVar = sourceStr.find("bpws:getVariableData", indexVar)
+ if indexVar == -1:
+ indexVar = sourceStr.find("${", oldIndexVar)
+ if indexVar == -1:
+ break
+ dollarCurlForm = True
+ if indexVar > 0 and sourceStr[indexVar-1] in ('"',"'"):
+ quoted = True
+ if not dollarCurlForm:
+ openParen = sourceStr.find("(", indexVar)
+ if openParen == -1:
+ break
+ closeParen = sourceStr.find(")", openParen)
+ if closeParen == -1:
+ break
+ else:
+ openParen = indexVar+1
+ closeParen = sourceStr.find("}", openParen)
+ if closeParen == -1:
+ break
+ varRef = sourceStr[openParen+1: closeParen]
+ if varRef.startswith('"') or varRef.startswith("'"):
+ varRef = varRef[1:]
+ if varRef.endswith('"') or varRef.endswith("'"):
+ varRef = varRef[:-1]
+ if isinstance(vars, dict) or isinstance(vars, ObjAsDict):
+ varRefCode = xpathToCode(varRef)
+ value = evalCode(varRefCode, vars)
+ else:
+ value = ctx.evalPath(vars, varRef)
+ inlinedPredicate.append(sourceStr[oldIndexVar:indexVar])
+ if quoted:
+ inlinedPredicate.append("%s" % value)
+ else:
+ inlinedPredicate.append('%s')
+ valueList.append(value)
+ indexVar = closeParen+1
+ inlinedPredicate.append(sourceStr[oldIndexVar:])
+ qualifications.append(''.join(inlinedPredicate))
+ return qualifications, valueList
return -1
else:
return 1
+
+def multiSplit(stringList, tokenList=[" "]):
+ """Splits strings in stringList by tokens, returns list of string."""
+ if not stringList: return []
+ if isinstance(tokenList, basestring):
+ tokenList = [tokenList]
+ if isinstance(stringList, basestring):
+ stringList = [stringList]
+ rtnList = stringList
+ for token in tokenList:
+ rtnList = rtnList[:]
+ for string in rtnList:
+ if string.find(token) > -1:
+ rtnList.remove(string)
+ names = string.split(token)
+ for name in names:
+ name = name.strip()
+ if name:
+ rtnList.append(name)
+ return rtnList
+
+QUOTES = ("\"", "'")
+
+def _findArgStart(argStr):
+ i = -1
+ for c in argStr:
+ i += 1
+ if (c == " "):
+ continue
+ elif (c == ","):
+ continue
+ return i
+ return None
+
+def _findArgEnd(argStr):
+ quotedArg = True
+ argEndChar = argStr[0]
+ if (not argEndChar in QUOTES):
+ argEndChar = ","
+ quotedArg = False
+ i = -1
+ firstChar = True
+ for c in argStr:
+ i+= 1
+ if (firstChar):
+ firstChar = False
+ if (quotedArg):
+ continue
+ if (c == argEndChar):
+ if (quotedArg):
+ return min(i+1, len(argStr))
+ else:
+ return i
+ return i
+
+def parseArgs(argStr, stripQuotes=False):
+ """
+ Given a str representation of method arguments, returns list arguments (as
+ strings).
+
+ Input: "('[a,b]', 'c', 1)" -> Output: ["'[a,b]'", "'c'", "1"].
+
+ If stripQuotes, removes quotes from quoted arg.
+ """
+ if (argStr.startswith("(")):
+ argStr = argStr[1:]
+ if (argStr.endswith(")")):
+ argStr = argStr[:-1]
+ else:
+ raise AssertionError("Expected argStr to end with ')'")
+
+ rtn = []
+ argsStr = argStr.strip()
+ while (True):
+ startIndex = _findArgStart(argStr)
+ if (startIndex == None):
+ break
+ argStr = argStr[startIndex:]
+ endIndex = _findArgEnd(argStr)
+ if (endIndex == len(argStr) - 1):
+ rtn.append(argStr.strip())
+ break
+ t = argStr[:endIndex].strip()
+ if (stripQuotes and t[0] in QUOTES and t[-1] in QUOTES):
+ t = t[1:-1]
+ rtn.append(t)
+ argStr = argStr[endIndex:]
+ return rtn
import sys
import os
+import time
# this will be set to true in IDE.py when we are running release builds.
isRelease = False
MAINMODULE_DIR = "AG_MAINMODULE_DIR"
IS_RELEASE = "AG_IS_RELEASE"
+IS_COMMERCIAL = "AG_IS_COMMERCIAL"
+AG_SYSTEM_START_TIME_ENV_NAME = "AG_SYSTEM_START_TIME"
+
+def isCommercial():
+
+ return os.path.exists(os.path.join(mainModuleDir,"commercial.txt")) or 'true' == (str(os.getenv(IS_COMMERCIAL)).lower())
def isRelease():
return 'true' == (str(os.getenv(IS_RELEASE)).lower())
def isWindows():
return os.name == 'nt'
+__isServer = False
+def setServerMode(isServer):
+ global __isServer
+ __isServer = isServer
+
+def isServer():
+ global __isServer
+ return __isServer
+
def _generateMainModuleDir():
mainModuleDir = os.getenv(MAINMODULE_DIR)
if mainModuleDir: # if environment variable set, return it
return '"%s"' % execPath
return execPath
+def getUserName():
+ if isWindows():
+ return os.getenv('USERNAME')
+ else:
+ # 06-Feb-06 stoens@activegrid.com --
+ # this blows up the linux cc runs with "Inappropriate ioctl for device"
+ #return os.getlogin()
+ return os.getenv('USER')
+
+def getCurrentTimeAsFloat():
+ return time.time()
+
+systemStartTime = getCurrentTimeAsFloat()
--- /dev/null
+#----------------------------------------------------------------------------
+# Name: utillang.py
+# Purpose: Provide language specific utilities
+#
+# Author: Joel Hare
+#
+# Created: 8/23/05
+# CVS-ID: $Id$
+# Copyright: (c) 2004-2005 ActiveGrid, Inc.
+# License: wxWindows License
+#----------------------------------------------------------------------------
+
+import os
+import sys
+import UserDict
+import tempfile
+import xml.sax.saxutils as saxutils
+
+import activegrid.util.parser as parser
+
+PY2WEB_codepages = {
+ 'cp1251' : 'CP-1251',
+ 'koi8_r' : 'KOI8-R',
+}
+
+def evalXPath(xpath, data, specialEntries=None):
+ codeStr = parser.xpathToCode(xpath)
+ return evalCode(codeStr, data, specialEntries)
+
+def evalCode(codeStr, data, specialEntries=None):
+ if isinstance(data, ObjAsDict):
+ namespace = data
+ elif isinstance(data, dict):
+ namespace = dict(data)
+ else:
+ namespace = ObjAsDict(data)
+ if specialEntries:
+ for key, value in specialEntries.items():
+ namespace.addSpecialEntry(key, value)
+ return eval(codeStr, {}, namespace)
+
+def deriveCharset():
+ charset = None
+ encodingString = sys.getdefaultencoding()
+ if encodingString != 'ascii':
+ charset = PY2WEB_codepages.get(encodingString.lower())
+ if charset == None:
+ charset = encodingString
+ return charset
+
+def toUTF8(value):
+ """
+ Converts all unicode and non-string values to utf-8.
+ This assumes string instances are already encoded in utf-8.
+ Note that us-ascii is a subset of utf-8.
+ """
+ if isinstance(value, unicode):
+ return value.encode('utf-8')
+ return str(value)
+
+def toUnicode(value):
+ """
+ Converts all strings non-string values to unicode.
+ This assumes string instances are encoded in utf-8.
+ Note that us-ascii is a subset of utf-8.
+ """
+ if not isinstance(value, unicode):
+ if not isinstance(value, str):
+ return unicode(value)
+ return unicode(value, 'utf-8')
+ return value
+
+
+def getSystemTempDir():
+ return tempfile.gettempdir()
+
+def getEnvVar(name, defaultVal=None):
+ if os.environ.has_key(name):
+ return os.environ[name]
+ return defaultVal
+
+class ObjAsDict(UserDict.DictMixin):
+ """
+ Passing this to eval as the local variables dictionary allows the
+ evaluated code to access properties in the wrapped object
+ """
+ def __init__(self, obj):
+ self.obj = obj
+ self.specialEntries = {}
+
+ def __getitem__(self, key):
+ try:
+ return getattr(self.obj, key)
+ except AttributeError, e:
+ if self.specialEntries.has_key(key):
+ return self.specialEntries[key]
+ raise KeyError(e.args)
+ def __setitem__(self, key, item): setattr(self.obj, key, item)
+ def __delitem__(self, key): delattr(self.obj, key)
+ def keys(self):
+ ret=[]
+ for i in list(dir(self.obj)+self.specialEntries.keys()):
+ if i=="__doc__" or i=="__module__":
+ pass
+ elif i not in ret:
+ ret.append(i)
+ return ret
+
+ def addSpecialEntry(self, key, value):
+ self.specialEntries[key] = value
+
+global saxXMLescapeDoubleQuote
+saxXMLescapeDoubleQuote = {'"':'"'}
+
+global saxXMLescapesAllQuotes
+# IE doesn't support ' but it doesn't seem like we should need this escaped at all so I took it out.
+saxXMLescapesAllQuotes = {'"':'"', "'":"'"}
+
+global saxXMLunescapes
+saxXMLunescapes = {'"':'"', "'":"'"}
+
+def escape(data, extraEscapes=None):
+ """Escape ', ", &, <, and > in a string of data.
+
+ Basically, everything that saxutils.escape does (and this calls that, at
+ least for now), but with " and ' added as well.
+
+ TODO: make this faster; saxutils.escape() is really slow
+ """
+
+ global saxXMLescapeDoubleQuote
+ if (extraEscapes == None):
+ extraEscapes = saxXMLescapeDoubleQuote
+ return saxutils.escape(data, extraEscapes)
+
+def unescape(data):
+ """Unescape ', ", &, <, and > in a string of data.
+
+ Basically, everything that saxutils.unescape does (and this calls that, at
+ least for now), but with " and ' added as well.
+
+ TODO: make this faster; saxutils.unescape() is really slow
+ """
+
+ global saxXMLunescapes
+ return saxutils.unescape(data, saxXMLunescapes)
# Name: xmlmarshaller.py
# Purpose:
#
-# Authors: John Spurling, Joel Hare, Alan Mullendore
+# Authors: John Spurling, Joel Hare, Jeff Norton, Alan Mullendore
#
# Created: 7/28/04
# CVS-ID: $Id$
ifDefPy()
import xml.sax
import xml.sax.handler
+import xml.sax.saxutils
+import datetime
endIfDef()
-import xml.sax.saxutils as saxutils
+import activegrid.util.utillang as utillang
import activegrid.util.objutils as objutils
+import activegrid.util.sysutils as sysutils
import activegrid.util.aglogging as aglogging
MODULE_PATH = "__main__"
## ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
+##unboundedVal = 2147483647 # value used for maxOccurs == "unbounded"
"""
Special attributes that we recognize:
global xmlMarshallerLogger
xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal")
-xmlMarshallerLogger.setLevel(aglogging.LEVEL_WARN)
# INFO : low-level info
# DEBUG : debugging info
################################################################################
def setattrignorecase(object, name, value):
+## print "[setattrignorecase] name = %s, value = %s" % (name, value)
if (name not in object.__dict__):
namelow = name.lower()
for attr in object.__dict__:
object.__dict__[name] = value
def getComplexType(obj):
+ if (hasattr(obj, "_instancexsdcomplextype")):
+ return obj._instancexsdcomplextype
if (hasattr(obj, "__xsdcomplextype__")):
return obj.__xsdcomplextype__
return None
-def _objectfactory(objname, objargs=None, objclass=None):
- "dynamically create an object based on the objname and return it."
-## print "[objectfactory] objname [%s]" % (objname)
+def _objectfactory(objtype, objargs=None, objclass=None):
+ "dynamically create an object based on the objtype and return it."
if not isinstance(objargs, list):
objargs = [objargs]
if (objclass != None):
+ obj = None
if (len(objargs) > 0):
if (hasattr(objclass, "__xmlcdatacontent__")):
obj = objclass()
contentAttr = obj.__xmlcdatacontent__
obj.__dict__[contentAttr] = str(objargs[0])
- return obj
- return objclass(*objargs)
+ else:
+ obj = objclass(*objargs)
else:
- return objclass()
- return objutils.newInstance(objname, objargs)
+ obj = objclass()
+ if ((obj != None) and (hasattr(obj, 'postUnmarshal'))):
+ obj.postUnmarshal()
+ return obj
+ return objutils.newInstance(objtype, objargs)
+
+class GenericXMLObject(object):
+ def __init__(self, content=None):
+ if content != None:
+ self._content = content
+ self.__xmlcontent__ = '_content'
+
+ def __str__(self):
+ return "GenericXMLObject(%s)" % objutils.toDiffableString(self.__dict__)
+ def setXMLAttributes(self, xmlName, attrs=None, children=None, nsMap=None, defaultNS=None):
+ if xmlName != None:
+ i = xmlName.rfind(':')
+ if i < 0:
+ self.__xmlname__ = xmlName
+ if defaultNS != None:
+ self.__xmldefaultnamespace__ = str(defaultNS)
+ else:
+ self.__xmlname__ = xmlName[i+1:]
+ prefix = xmlName[:i]
+ if nsMap.has_key(prefix):
+ self.__xmldefaultnamespace__ = str(nsMap[prefix])
+ if attrs != None:
+ for attrname, attr in attrs.items():
+ attrname = str(attrname)
+ if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX):
+ pass
+ elif attrname == "objtype":
+ pass
+ else:
+ if not hasattr(self, '__xmlattributes__'):
+ self.__xmlattributes__ = []
+ i = attrname.rfind(':')
+ if i >= 0:
+ prefix = attrname[:i]
+ attrname = attrname[i+1:]
+ if not hasattr(self, '__xmlattrnamespaces__'):
+ self.__xmlattrnamespaces__ = {}
+ if self.__xmlattrnamespaces__.has_key(prefix):
+ alist = self.__xmlattrnamespaces__[prefix]
+ else:
+ alist = []
+ alist.append(attrname)
+ self.__xmlattrnamespaces__[prefix] = alist
+ self.__xmlattributes__.append(attrname)
+ if hasattr(self, '__xmlattributes__'):
+ self.__xmlattributes__.sort()
+ if children != None and len(children) > 0:
+ childList = []
+ flattenList = {}
+ for childname, child in children:
+ childstr = str(childname)
+ if childstr in childList:
+ if not flattenList.has_key(childstr):
+ flattenList[childstr] = (childstr,)
+ else:
+ childList.append(childstr)
+ if len(flattenList) > 0:
+ self.__xmlflattensequence__ = flattenList
+
+ def initialize(self, arg1=None):
+ pass
+
+
class Element:
def __init__(self, name, attrs=None, xsname=None):
self.name = name
self.children = []
self.objclass = None
self.xsname = xsname
+ self.objtype = None
def getobjtype(self):
- objtype = self.attrs.get("objtype")
+# objtype = self.attrs.get("objtype")
+ objtype = self.objtype
if (objtype == None):
if (len(self.children) > 0):
objtype = "dict"
self.targetNS = None
self.defaultNS = None
self.prefix = None
-
- def isEmpty(self):
- return ((self.nsMap == {}) and (self.targetNS == None) and (self.defaultNS == None))
+ def __str__(self):
+ if self.prefix == None:
+ strVal = 'prefix = None; '
+ else:
+ strVal = 'prefix = "%s"; ' % (self.prefix)
+ if self.targetNS == None:
+ strVal += 'targetNS = None; '
+ else:
+ strVal += 'targetNS = "%s"; ' % (self.targetNS)
+ if self.defaultNS == None:
+ strVal += 'defaultNS = None; '
+ else:
+ strVal += 'defaultNS = "%s"; ' % (self.defaultNS)
+ if len(self.nsMap) == 0:
+ strVal += 'nsMap = None; '
+ else:
+ strVal += 'nsMap = {'
+ for ik, iv in self.nsMap.iteritems():
+ strVal += '%s=%s; ' % (ik,iv)
+ strVal += '}'
+ return strVal
+
def setKnownTypes(self, masterKnownTypes, masterKnownNamespaces, parentNSE):
# if we're a nested element, extend our parent element's mapping
if parentNSE != None:
self.knownTypes = parentNSE.knownTypes.copy()
# but if we have a different default namespace, replace the parent's default mappings
- if parentNSE.defaultNS != self.defaultNS:
+ if (self.defaultNS != None) and (parentNSE.defaultNS != self.defaultNS):
newKT = self.knownTypes.copy()
for tag in newKT:
if tag.find(':') < 0:
self.knownTypes[knownTagName] = mapClass
else: # e.g. "ItemSearchRequest"
self.knownTypes[tag] = mapClass
-## print 'mapping <%s> to class "%s"' % (tag, mapClass.__name__)
def expandQName(self, eName, attrName, attrValue):
bigValue = attrValue
if shortNs == attrNS:
bigValue = '%s:%s' % (longNs, attrNCName)
break
-## print '[expandQName] input attrName = "%s" and attrValue "%s"; output = "%s"' % (attrName, attrValue, bigValue)
return bigValue
class XMLObjectFactory(xml.sax.ContentHandler):
- def __init__(self, knownTypes=None, knownNamespaces=None):
+ def __init__(self, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
self.rootelement = None
- if (knownTypes == None):
- self.knownTypes = {}
- else:
- self.knownTypes = knownTypes
- if (knownNamespaces == None):
- self.knownNamespaces = {}
+ if xmlSource == None:
+ self.xmlSource = "unknown"
else:
- self.knownNamespaces = knownNamespaces
+ self.xmlSource = xmlSource
+ self.createGenerics = createGenerics
self.skipper = False
self.elementstack = []
self.nsstack = []
self.collectContent = None
+ if (knownNamespaces == None):
+ self.knownNamespaces = {}
+ else:
+ self.knownNamespaces = knownNamespaces
+ self.reversedNamespaces = {}
+ for longns, shortns in self.knownNamespaces.iteritems():
+ self.reversedNamespaces[shortns] = longns
+ self.knownTypes = {}
+ if (knownTypes != None):
+ for tag, cls in knownTypes.iteritems():
+ i = tag.rfind(':')
+ if i >= 0:
+ shortns = tag[:i]
+ tag = tag[i+1:]
+ if not self.reversedNamespaces.has_key(shortns):
+ errorString = 'Error unmarshalling XML document from source "%s": knownTypes specifies an unmapped short namespace "%s" for element "%s"' % (self.xmlSource, shortns, tag)
+ raise UnmarshallerException(errorString)
+ longns = self.reversedNamespaces[shortns]
+ tag = '%s:%s' % (longns, tag)
+ self.knownTypes[tag] = cls
+ #printKnownTypes(self.knownTypes, 'Unmarshaller.XMLObjectFactory.__init__')
xml.sax.handler.ContentHandler.__init__(self)
def appendElementStack(self, newElement, newNS):
self.elementstack.append(newElement)
- if (newNS.isEmpty()):
- if (len(self.nsstack) > 0):
- newNS = self.nsstack[-1]
- else:
- newNS.knownTypes = self.knownTypes.copy()
- else:
- if (len(self.nsstack) > 0):
- newNS.setKnownTypes(self.knownTypes, self.knownNamespaces, self.nsstack[-1])
- else:
- newNS.setKnownTypes(self.knownTypes, self.knownNamespaces, None)
+ if (len(self.nsstack) > 0):
+ oldNS = self.nsstack[-1]
+ if newNS.defaultNS == None:
+ newNS.defaultNS = oldNS.defaultNS
+ if newNS.targetNS == None:
+ newNS.targetNS = oldNS.targetNS
+ if len(newNS.nsMap) == 0:
+ newNS.nsMap = oldNS.nsMap
+ elif len(oldNS.nsMap) > 0:
+ map = oldNS.nsMap.copy()
+ map.update(newNS.nsMap)
+ newNS.nsMap = map
self.nsstack.append(newNS)
return newNS
strVal += '>'
self.collectContent.content += strVal
xsname = name
- if name.find(':') > -1: # Strip namespace prefixes for now until actually looking them up in xsd
- name = name[name.rfind(":") + 1:]
+ i = name.rfind(':')
+ if i >= 0:
+ nsname = name[:i]
+ name = name[i+1:]
+ else:
+ nsname = None
element = Element(name, attrs.copy(), xsname=xsname)
# if the element has namespace attributes, process them and add them to our stack
nse = NsElement()
+ objtype = None
for k in attrs.getNames():
if k.startswith('xmlns'):
longNs = attrs[k]
nse.nsMap[shortNs] = longNs
elif k == 'targetNamespace':
nse.targetNS = attrs.getValue(k)
+ elif k == 'objtype':
+ objtype = attrs.getValue(k)
nse = self.appendElementStack(element, nse)
- element.objclass = nse.knownTypes.get(xsname)
+ if nsname != None:
+ if nse.nsMap.has_key(nsname):
+ longname = '%s:%s' % (nse.nsMap[nsname], name)
+## elif objtype == None:
+## errorString = 'Error unmarshalling XML document from source "%s": tag "%s" at line "%d", column "%d" has an undefined namespace' % (self.xmlSource, xsname, self._locator.getLineNumber(), self._locator.getColumnNumber())
+## raise UnmarshallerException(errorString)
+ elif self.reversedNamespaces.has_key(nsname):
+ longname = '%s:%s' % (self.reversedNamespaces[nsname], name)
+ else:
+ longname = xsname
+ elif nse.defaultNS != None:
+ longname = '%s:%s' % (nse.defaultNS, name)
+ else:
+ longname = name
+ element.objtype = objtype
+ element.objclass = self.knownTypes.get(longname)
+ if element.objclass == None and len(self.knownNamespaces) == 0:
+ # handles common case where tags are unqualified and knownTypes are too, but there's a defaultNS
+ element.objclass = self.knownTypes.get(name)
if (hasattr(element.objclass, "__xmlcontent__")):
self.collectContent = element
def endElement(self, name):
## print "[endElement] </%s>" % name
xsname = name
- if name.find(":") > -1: # Strip namespace prefixes for now until actually looking them up in xsd
- name = name[name.find(":") + 1:]
+ i = name.rfind(':')
+ if i >= 0: # Strip namespace prefixes for now until actually looking them up in xsd
+ name = name[i+1:]
if self.skipper:
if xsname == "xs:annotation" or xsname == "xsd:annotation": # here too
self.skipper = False
element, nse = self.popElementStack()
if ((len(self.elementstack) > 1) and (self.elementstack[-1].getobjtype() == "None")):
parentElement = self.elementstack[-2]
-## print "[endElement] %s: found parent with objtype==None: using its grandparent" % name
elif (len(self.elementstack) > 0):
parentElement = self.elementstack[-1]
objtype = element.getobjtype()
-## print "element objtype is: ", objtype
if (objtype == "None"):
-## print "[endElement] %s: skipping a (objtype==None) end tag" % name
return
constructorarglist = []
if (len(element.content) > 0):
strippedElementContent = element.content.strip()
if (len(strippedElementContent) > 0):
constructorarglist.append(element.content)
+ # If the element requires an object, but none is known, use the GenericXMLObject class
+ if ((element.objclass == None) and (element.attrs.get("objtype") == None) and ((len(element.attrs) > 0) or (len(element.children) > 0))):
+ if self.createGenerics:
+ element.objclass = GenericXMLObject
obj = _objectfactory(objtype, constructorarglist, element.objclass)
+ if element.objclass == GenericXMLObject:
+ obj.setXMLAttributes(str(xsname), element.attrs, element.children, nse.nsMap, nse.defaultNS)
complexType = getComplexType(obj)
if (obj != None):
if (hasattr(obj, "__xmlname__") and getattr(obj, "__xmlname__") == "sequence"):
self.elementstack[-1].children = oldChildren
return
if (len(element.attrs) > 0) and not isinstance(obj, list):
-## print "[endElement] %s: element has attrs and the obj is not a list" % name
for attrname, attr in element.attrs.items():
if attrname == XMLNS or attrname.startswith(XMLNS_PREFIX):
if attrname.startswith(XMLNS_PREFIX):
ns = attrname[XMLNS_PREFIX_LENGTH:]
else:
ns = ""
- if complexType != None:
+ if complexType != None or element.objclass == GenericXMLObject:
if not hasattr(obj, "__xmlnamespaces__"):
obj.__xmlnamespaces__ = {ns:attr}
elif ns not in obj.__xmlnamespaces__:
xsdElement = complexType.findElement(attrname)
if (xsdElement != None):
type = xsdElement.type
-## print 'Unmarshalling element "%s", attribute "%s" with type "%s"' % (name, xsdElement.name, type)
if (type != None):
if (type == TYPE_QNAME):
attr = nse.expandQName(name, attrname, attr)
### ToDO remove maxOccurs hack after bug 177 is fixed
if attrname == "maxOccurs" and attr == "unbounded":
attr = "-1"
- attr = _objectfactory(type, attr)
+ try:
+ attr = _objectfactory(type, attr)
+ except Exception, exceptData:
+ errorString = 'Error unmarshalling attribute "%s" at line %d, column %d in XML document from source "%s": %s' % (attrname, self._locator.getLineNumber(), self._locator.getColumnNumber(), self.xmlSource, str(exceptData))
+ raise UnmarshallerException(errorString)
try:
setattrignorecase(obj, _toAttrName(obj, attrname), attr)
except AttributeError:
- errorString = 'Error unmarshalling XML document at line %i, column %i: The object type of attribute "%s" of XML element "%s": not specified or known' % (self._locator.getLineNumber(), self._locator.getColumnNumber(), attrname, name)
+ errorString = 'Error setting value of attribute "%s" at line %d, column %d in XML document from source "%s": object type of XML element "%s" is not specified or known' % (attrname, self._locator.getLineNumber(), self._locator.getColumnNumber(), self.xmlSource, name)
raise UnmarshallerException(errorString)
## obj.__dict__[_toAttrName(obj, attrname)] = attr
# stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
flattenDict[str(xmlnametuple)] = sequencename
else:
for xmlname in xmlnametuple:
-## print "[endElement]: adding flattenDict[%s] = %s" % (xmlname, sequencename)
flattenDict[xmlname] = sequencename
else:
raise Exception("Invalid type for __xmlflattensequence___ : it must be a dict")
# reattach an object"s attributes to it
for childname, child in element.children:
-## print "[endElement] childname is: ", childname, "; child is: ", child
if (childname in flattenDict):
sequencename = _toAttrName(obj, flattenDict[childname])
if (not hasattr(obj, sequencename)):
else:
obj[childname] = child
else:
-## print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child))
- try:
- setattrignorecase(obj, _toAttrName(obj, childname), child)
- except AttributeError:
- raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname, name))
-## obj.__dict__[_toAttrName(obj, childname)] = child
+ # don't replace a good attribute value with a bad one
+ childAttrName = _toAttrName(obj, childname)
+ if (not hasattr(obj, childAttrName)) or (getattr(obj, childAttrName) == None) or (getattr(obj, childAttrName) == []) or (not isinstance(child, GenericXMLObject)):
+ try:
+ setattrignorecase(obj, childAttrName, child)
+ except AttributeError:
+ raise MarshallerException("Error unmarshalling child element \"%s\" of XML element \"%s\": object type not specified or known" % (childname, name))
if (complexType != None):
for element in complexType.elements:
if (len(self.elementstack) > 0):
## print "[endElement] appending child with name: ", name, "; objtype: ", objtype
parentElement.children.append((name, obj))
-## print "parentElement now has ", len(parentElement.children), " children"
else:
self.rootelement = obj
break
## if (name.startswith("__") and not name.endswith("__")):
## name = "_%s%s" % (obj.__class__.__name__, name)
- return name
+ return str(name)
+
+def printKnownTypes(kt, where):
+ print 'KnownTypes from %s' % (where)
+ for tag, cls in kt.iteritems():
+ print '%s => %s' % (tag, str(cls))
__typeMappingXsdToLang = {
"string": "str",
if xsdType.startswith(XMLSCHEMA_XSD_URL):
xsdType = xsdType[len(XMLSCHEMA_XSD_URL)+1:]
elif xsdType.startswith(AG_URL):
- xsdType = xsdType[len(AG_URL)+1:]
+ xsdType = xsdType[len(AG_URL)+1:]
langType = __typeMappingXsdToLang.get(xsdType)
if (langType == None):
raise Exception("Unknown xsd type %s" % xsdType)
else:
return str(langValue)
-def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None):
- objectfactory = XMLObjectFactory(knownTypes, knownNamespaces)
+def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
+ objectfactory = XMLObjectFactory(knownTypes, knownNamespaces, xmlSource, createGenerics)
+ # on Linux, pyXML's sax.parseString fails when passed unicode
+ if (not sysutils.isWindows()):
+ xmlstr = str(xmlstr)
try:
xml.sax.parseString(xmlstr, objectfactory)
except xml.sax.SAXParseException, errorData:
return objectfactory.getRootObject()
def marshal(obj, elementName=None, prettyPrint=False, marshalType=True, indent=0, knownTypes=None, knownNamespaces=None, encoding=-1):
-## print '[marshal] entered with elementName = "%s"' % (elementName)
worker = XMLMarshalWorker(prettyPrint=prettyPrint, marshalType=marshalType, knownTypes=knownTypes, knownNamespaces=knownNamespaces)
if obj != None and hasattr(obj, '__xmldeepexclude__'):
worker.xmldeepexclude = obj.__xmldeepexclude__
xmlstr = "".join(worker._marshal(obj, elementName, indent=indent))
- if (isinstance(encoding, basestring)):
- return '<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr.encode(encoding))
- elif (encoding == None):
+ aglogging.info(xmlMarshallerLogger, "marshal produced string of type %s", type(xmlstr))
+ if (encoding == None):
return xmlstr
- else:
- return '<?xml version="1.0" encoding="%s"?>\n%s' % (sys.getdefaultencoding(), xmlstr)
+ if (not isinstance(encoding, basestring)):
+ encoding = sys.getdefaultencoding()
+ if (not isinstance(xmlstr, unicode)):
+ xmlstr = xmlstr.decode()
+ xmlstr = u'<?xml version="1.0" encoding="%s"?>\n%s' % (encoding, xmlstr)
+ return xmlstr.encode(encoding)
class XMLMarshalWorker(object):
def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None):
newNS.prefix = self.nsstack[-1].prefix
else:
newNS.prefix = ''
- if hasattr(obj, "__xmldefaultnamespace__"):
+ if obj != None and hasattr(obj, "__xmldefaultnamespace__"):
longPrefixNS = getattr(obj, "__xmldefaultnamespace__")
if longPrefixNS == defaultLongNS:
newNS.prefix = ''
if v == longPrefixNS:
newNS.prefix = k + ':'
break;
-## print '[appendNSStack] found longPrefixNS in nameSpaces = "%s"' % (newNS.prefix)
except:
if (longPrefixNS in asDict(self.knownNamespaces)):
newNS.prefix = self.knownNamespaces[longPrefixNS] + ':'
else:
raise MarshallerException('Error marshalling __xmldefaultnamespace__ ("%s") not defined in namespace stack' % (longPrefixNS))
- if hasattr(obj, "targetNamespace"):
+ if obj != None and hasattr(obj, "targetNamespace"):
newNS.targetNS = obj.targetNamespace
elif len(self.nsstack) > 0:
newNS.targetNS = self.nsstack[-1].targetNS
def _marshal(self, obj, elementName=None, nameSpacePrefix="", indent=0):
if (obj != None):
- xmlMarshallerLogger.debug("--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d" % (nameSpacePrefix, elementName, type(obj), str(obj), indent))
+ aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, type=%s, obj=%s, indent=%d", nameSpacePrefix, elementName, type(obj), str(obj), indent)
else:
- xmlMarshallerLogger.debug("--> _marshal: elementName=%s%s, obj is None, indent=%d" % (nameSpacePrefix, elementName, indent))
+ aglogging.debug(xmlMarshallerLogger, "--> _marshal: elementName=%s%s, obj is None, indent=%d", nameSpacePrefix, elementName, indent)
+ if ((obj != None) and (hasattr(obj, 'preMarshal'))):
+ obj.preMarshal()
excludeAttrs = []
excludeAttrs.extend(self.xmldeepexclude)
if hasattr(obj, "__xmlexclude__"):
newline = ""
increment = 0
## Determine the XML element name. If it isn"t specified in the
- ## parameter list, look for it in the __xmlname__ Lang
- ## attribute, else use the default generic BASETYPE_ELEMENT_NAME.
+ ## parameter list, look for it in the __xmlname__ attribute,
+ ## else use the default generic BASETYPE_ELEMENT_NAME.
nameSpaceAttrs = self.appendNSStack(obj)
nameSpacePrefix = self.getNSPrefix()
if not elementName:
elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME
else:
elementName = nameSpacePrefix + elementName
-## print '[XMLMarshalWorker._marshal] elementName "%s"; nameSpaceAttrs is "%s"' % (elementName, nameSpaceAttrs)
if (hasattr(obj, "__xmlsequencer__")) and (obj.__xmlsequencer__ != None):
if (XMLSCHEMA_XSD_URL in self.nsstack[-1].nameSpaces.values()):
for kShort, vLong in self.nsstack[-1].nameSpaces.iteritems():
if vLong == XMLSCHEMA_XSD_URL:
- xsdPrefix = kShort + ':'
+ if kShort != DEFAULT_NAMESPACE_KEY:
+ xsdPrefix = kShort + ':'
+ else:
+ xsdPrefix = ''
break
else:
xsdPrefix = 'xs:'
else:
elementAdd = None
- ## print "marshal: entered with elementName: ", elementName
members_to_skip = []
## Add more members_to_skip based on ones the user has selected
## via the __xmlexclude__ and __xmldeepexclude__ attributes.
xmlattributes = obj.__xmlattributes__
members_to_skip.extend(xmlattributes)
for attr in xmlattributes:
-## print 'Processing element "%s"; attribute "%s"' % (elementName, attr)
internalAttrName = attr
ifDefPy()
if (attr.startswith("__") and not attr.endswith("__")):
endIfDef()
# Fail silently if a python attribute is specified to be
# an XML attribute but is missing.
-## print "marshal: processing attribute ", internalAttrName
attrNameSpacePrefix = ""
if hasattr(obj, "__xmlattrnamespaces__"):
for nameSpaceKey, nameSpaceAttributes in getattr(obj, "__xmlattrnamespaces__").iteritems():
else:
value = objutils.toDiffableRepr(value)
- objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, saxutils.escape(value))
- ## print "marshal: new objattrs is: ", objattrs
+ objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, utillang.escape(value))
if (obj == None):
xmlString = [""]
elif isinstance(obj, bool):
objTypeStr = self._genObjTypeStr("float")
xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
elif isinstance(obj, unicode): # have to check before basestring - unicode is instance of base string
- xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, saxutils.escape(obj.encode()), elementName, newline)]
+ xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj.encode()), elementName, newline)]
elif isinstance(obj, basestring):
- xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, saxutils.escape(obj), elementName, newline)]
+ xmlString = ['%s<%s>%s</%s>%s' % (prefix, elementName, utillang.escape(obj), elementName, newline)]
+ elif isinstance(obj, datetime.datetime):
+ objTypeStr = self._genObjTypeStr("datetime")
+ xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
+ elif isinstance(obj, datetime.date):
+ objTypeStr = self._genObjTypeStr("date")
+ xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
+ elif isinstance(obj, datetime.time):
+ objTypeStr = self._genObjTypeStr("time")
+ xmlString = ['%s<%s%s>%s</%s>%s' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)]
elif isinstance(obj, list):
if len(obj) < 1:
xmlString = ""
elif hasattr(obj, "__xmlcontent__"):
contentValue = getattr(obj, obj.__xmlcontent__)
if contentValue == None:
- contentValue = ''
+ xmlString = ["%s<%s%s%s/>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, newline)]
else:
- contentValue = saxutils.escape(contentValue)
- xmlString = ["%s<%s%s%s>%s</%s>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)]
+ contentValue = utillang.escape(contentValue)
+ xmlString = ["%s<%s%s%s>%s</%s>%s" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)]
else:
# Only add the objtype if the element tag is unknown to us.
- if (self.isKnownType(elementName) == True):
+ if (isinstance(obj, GenericXMLObject)):
+ objTypeStr = ""
+ elif (self.isKnownType(elementName) == True):
objTypeStr = ""
else:
objTypeStr = self._genObjTypeStr("%s.%s" % (obj.__class__.__module__, className))
if hasattr(obj, "__xmlbody__"):
xmlbody = getattr(obj, obj.__xmlbody__)
if xmlbody != None:
- xmlMemberString.append(xmlbody)
+ xmlMemberString.append(utillang.escape(xmlbody))
else:
if hasattr(obj, "__xmlattrgroups__"):
attrGroups = obj.__xmlattrgroups__.copy()
xmlname = None
if (len(xmlnametuple) == 1):
xmlname = xmlnametuple[0]
- ## ix = 0
if not isinstance(value, (list, tuple)):
value = [value]
for seqitem in value:
- ## xmlname = xmlnametuple[ix]
- ## ix += 1
- ## if (ix >= len(xmlnametuple)):
- ## ix = 0
xmlMemberString.extend(self._marshal(seqitem, xmlname, subElementNameSpacePrefix, indent=indent+increment))
else:
if (hasattr(obj, "__xmlrename__") and name in asDict(obj.__xmlrename__)):
xmlname = obj.__xmlrename__[name]
else:
xmlname = name
- xmlMemberString.extend(self._marshal(value, xmlname, subElementNameSpacePrefix, indent=indent+increment))
+ if (value != None):
+ xmlMemberString.extend(self._marshal(value, xmlname, subElementNameSpacePrefix, indent=indent+increment))
if (eName != "__nogroup__"):
xmlMemberString.append("%s</%s>%s" % (prefix, eName, newline))
prefix = prefix[:-increment]
xmlString.append("><![CDATA[%s]]></%s>%s" % (cdataContent, elementName, newline))
else:
xmlString.append("/>%s" % newline)
- ## return xmlString
- xmlMarshallerLogger.debug("<-- _marshal: %s" % str(xmlString))
+ if aglogging.isEnabledForDebug(xmlMarshallerLogger):
+ aglogging.debug(xmlMarshallerLogger, "<-- _marshal: %s", objutils.toDiffableString(xmlString))
#print "<-- _marshal: %s" % str(xmlString)
self.popNSStack()
return xmlString
xmlLogger = logging.getLogger("activegrid.util.xml")
-def load(fileName, knownTypes=None, knownNamespaces=None):
+def load(fileName, knownTypes=None, knownNamespaces=None, createGenerics=False):
loadedObject = None
fileObject = file(fileName)
timeStart = time.time()
+ xml = ""
try:
xml = fileObject.read()
- loadedObject = unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=fileName)
+ loadedObject = unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=fileName, createGenerics=createGenerics)
loadedObject.fileName = os.path.abspath(fileName)
if hasattr(loadedObject, 'initialize'):
loadedObject.initialize()
finally:
fileObject.close()
- timeDone = time.time()
- aglogging.info(xmlLogger, ('Load statistics for file %s: elapsed time = %f secs' % (fileName, timeDone-timeStart)))
+ if xmlLogger.isEnabledFor(aglogging.LEVEL_INFO):
+ timeDone = time.time()
+ aglogging.info(xmlLogger, ('Load statistics for file %s (%d bytes): elapsed time = %f secs' % (fileName, len(xml), timeDone-timeStart)))
return loadedObject
-def loadURI(uri, knownTypes=None, knownNamespaces=None, xmlSource=None):
+def loadURI(uri, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
loadedObject = None
- xml = urllib.urlopen(uri).read()
- loadedObject = unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=xmlSource)
- loadedObject.fileName = uri
- if hasattr(loadedObject, 'initialize'):
- loadedObject.initialize()
+ timeStart = time.time()
+ xml = ""
+ try:
+ xml = urllib.urlopen(uri).read()
+ loadedObject = unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=xmlSource, createGenerics=createGenerics)
+ loadedObject.fileName = uri
+ if hasattr(loadedObject, 'initialize'):
+ loadedObject.initialize()
+ finally:
+ if xmlLogger.isEnabledFor(aglogging.LEVEL_INFO):
+ timeDone = time.time()
+ aglogging.info(xmlLogger, ('Load statistics for URI %s (%d bytes): elapsed time = %f secs' % (uri, len(xml), timeDone-timeStart)))
return loadedObject
-def unmarshal(xml, knownTypes=None, knownNamespaces=None, xmlSource=None):
+def unmarshal(xml, knownTypes=None, knownNamespaces=None, xmlSource=None, createGenerics=False):
if (knownTypes == None):
knownTypes, knownNamespaces = getAgKnownTypes()
- return xmlmarshaller.unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=xmlSource)
+ return xmlmarshaller.unmarshal(xml, knownTypes=knownTypes, knownNamespaces=knownNamespaces, xmlSource=xmlSource, createGenerics=createGenerics)
def save(fileName, objectToSave, prettyPrint=True, marshalType=True, knownTypes=None, knownNamespaces=None, encoding='utf-8'):
if hasattr(objectToSave, '_xmlReadOnly') and objectToSave._xmlReadOnly == True:
version = xml[i+1:j]
return version
-def escape(data):
- """Escape ', ", &, <, and > in a string of data.
-
- Basically, everything that saxutils.escape does (and this calls that, at
- least for now), but with " added as well.
-
- XXX TODO make this faster; saxutils.escape() is really slow
- """
-
- import xml.sax.saxutils as saxutils
-
- data=saxutils.escape(data)
- data=data.replace("\"", """)
-
- # IE doesn't support '
- # data=data.replace("\'", "'")
- data=data.replace("\'", "'")
-
- return data
-
-def unescape(data):
- """Unescape ', ", &, <, and > in a string of data.
-
- Basically, everything that saxutils.unescape does (and this calls that, at
- least for now), but with " added as well.
-
- XXX TODO make this faster; saxutils.unescape() is really slow
- """
-
- import xml.sax.saxutils as saxutils
-
- data=data.replace(""", "\"")
- data=data.replace("'", "\'")
- return saxutils.unescape(data)
-
AG_NS_URL = "http://www.activegrid.com/ag.xsd"
BPEL_NS_URL = "http://schemas.xmlsoap.org/ws/2003/03/business-process"
MIME_WSDL_NS_URL = "http://schemas.xmlsoap.org/wsdl/mime/"
SOAP_NS_URL = "http://schemas.xmlsoap.org/wsdl/soap/"
SOAP12_NS_URL = "http://schemas.xmlsoap.org/wsdl/soap12/"
+SOAP_NS_ENCODING = "http://schemas.xmlsoap.org/soap/encoding/"
WSDL_NS_URL = "http://schemas.xmlsoap.org/wsdl/"
+WSSE_NS_URL = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
XFORMS_NS_URL = "http://www.w3c.org/xform.xsd"
XMLSCHEMA_NS_URL = "http://www.w3.org/2001/XMLSchema"
XSI_NS_URL = "http://www.w3.org/2001/XMLSchema-instance"
MIME_WSDL_NS_URL : "mime",
SOAP_NS_URL : "soap",
SOAP12_NS_URL : "soap12",
- WSDL_NS_URL : "wsdl",
+ WSDL_NS_URL : "wsdl",
+ WSSE_NS_URL : "wsse",
XFORMS_NS_URL : "xforms",
XMLSCHEMA_NS_URL : "xs",
XACML_NS_URL : "xacml",
"ag:body" : "activegrid.model.processmodel.Body",
"ag:category_substitutions" : "activegrid.server.layoutrenderer.CategorySubstitutions",
"ag:command" : "activegrid.model.wsdl.Command",
+ "ag:setElement" : "activegrid.model.processmodel.SetElementOperation",
"ag:css" : "activegrid.server.layoutrenderer.CSS",
- "ag:cssRule" : "activegrid.model.processmodel.CssRule",
"ag:databaseService" : "activegrid.server.deployment.DatabaseService",
"ag:datasource" : "activegrid.data.dataservice.DataSource",
"ag:dataObjectList" : "activegrid.data.datalang.DataObjectList",
"ag:debug" : "activegrid.model.processmodel.DebugOperation",
"ag:deployment" : "activegrid.server.deployment.Deployment",
+ "ag:formData" : "activegrid.model.processmodel.FormData",
+ "ag:formVar" : "activegrid.model.processmodel.FormVar",
"ag:generator" : "activegrid.server.layoutrenderer.SerializableGenerator",
"ag:head" : "activegrid.server.layoutrenderer.Head",
"ag:hr" : "activegrid.model.processmodel.HorizontalRow",
"ag:identity" : "activegrid.model.identitymodel.Identity",
"ag:identityref" : "activegrid.server.deployment.IdentityRef",
"ag:image" : "activegrid.model.processmodel.Image",
+ "ag:inputPart" : "activegrid.model.processmodel.InputPart",
+ "ag:keystore" : "activegrid.model.identitymodel.KeyStore",
"ag:label" : "activegrid.model.processmodel.Label",
"ag:layout" : "activegrid.server.layoutrenderer.Layout",
"ag:layouts" : "activegrid.server.layoutrenderer.Layouts",
"ag:localService" : "activegrid.server.deployment.LocalService",
"ag:parameter" : "activegrid.server.layoutrenderer.Parameter",
"ag:parameters" : "activegrid.server.layoutrenderer.Parameters",
+ "ag:postInitialize" : "activegrid.model.processmodel.PostInitialize",
"ag:processref" : "activegrid.server.deployment.ProcessRef",
"ag:query" : "activegrid.model.processmodel.Query",
"ag:soapService" : "activegrid.server.deployment.SoapService",
+ "ag:redirect" : "activegrid.server.layoutrenderer.Redirect",
"ag:requiredFile" : "activegrid.server.layoutrenderer.RequiredFile",
"ag:resource" : "activegrid.model.identitymodel.IDResource",
"ag:restService" : "activegrid.server.deployment.RestService",
"xforms:xforms" : "activegrid.model.processmodel.XFormsRoot",
"xs:all" : "activegrid.model.schema.XsdSequence",
"xs:any" : "activegrid.model.schema.XsdAny",
+ "xs:anyAttribute" : "activegrid.model.schema.XsdAnyAttribute",
"xs:attribute" : "activegrid.model.schema.XsdAttribute",
+ "xs:choice" : "activegrid.model.schema.XsdChoice",
"xs:complexContent" : "activegrid.model.schema.XsdComplexContent",
"xs:complexType" : "activegrid.model.schema.XsdComplexType",
+ "xs:documentation" : "activegrid.model.schema.XsdDocumentation",
"xs:element" : "activegrid.model.schema.XsdElement",
- "xs:enumeration" : "activegrid.model.schema.XsdEnumeration",
+ "xs:enumeration" : "activegrid.model.schema.XsdFacetEnumeration",
"xs:extension" : "activegrid.model.schema.XsdExtension",
+ "xs:fractionDigits" : "activegrid.model.schema.XsdFacetFractionDigits",
"xs:field" : "activegrid.model.schema.XsdKeyField",
"xs:import" : "activegrid.model.schema.XsdInclude",
"xs:include" : "activegrid.model.schema.XsdInclude",
"xs:key" : "activegrid.model.schema.XsdKey",
"xs:keyref" : "activegrid.model.schema.XsdKeyRef",
- "xs:length" : "activegrid.model.schema.XsdLength",
+ "xs:length" : "activegrid.model.schema.XsdFacetLength",
"xs:list" : "activegrid.model.schema.XsdList",
- "xs:maxLength" : "activegrid.model.schema.XsdMaxLength",
+ "xs:maxExclusive" : "activegrid.model.schema.XsdFacetMaxExclusive",
+ "xs:maxInclusive" : "activegrid.model.schema.XsdFacetMaxInclusive",
+ "xs:maxLength" : "activegrid.model.schema.XsdFacetMaxLength",
+ "xs:minExclusive" : "activegrid.model.schema.XsdFacetMinExclusive",
+ "xs:minInclusive" : "activegrid.model.schema.XsdFacetMinInclusive",
+ "xs:minLength" : "activegrid.model.schema.XsdFacetMinLength",
+ "xs:pattern" : "activegrid.model.schema.XsdFacetPattern",
"xs:restriction" : "activegrid.model.schema.XsdRestriction",
"xs:schema" : "activegrid.model.schema.Schema",
"xs:selector" : "activegrid.model.schema.XsdKeySelector",
"xs:sequence" : "activegrid.model.schema.XsdSequence",
"xs:simpleContent" : "activegrid.model.schema.XsdSimpleContent",
"xs:simpleType" : "activegrid.model.schema.XsdSimpleType",
- "xs:totalDigits" : "activegrid.model.schema.XsdTotalDigits",
+ "xs:totalDigits" : "activegrid.model.schema.XsdFacetTotalDigits",
+ "xs:whiteSpace" : "activegrid.model.schema.XsdFacetWhiteSpace",
}
return agXsdToClassName
self._findDialog = None
self._replaceDialog = FindReplaceDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Replace"), size=(320,200), findString=findString)
+ self._replaceDialog.CenterOnParent()
self._replaceDialog.Show(True)
else:
if self._replaceDialog != None:
self._replaceDialog = None
self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString)
+ self._findDialog.CenterOnParent()
self._findDialog.Show(True)
""" Display Goto Line Number dialog box """
line = -1
dialog = wx.TextEntryDialog(parent, _("Enter line number to go to:"), _("Go to Line"))
+ dialog.CenterOnParent()
if dialog.ShowModal() == wx.ID_OK:
try:
line = int(dialog.GetValue())
wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
cancelBtn = wx.Button(self, wx.ID_CANCEL)
wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
- buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+ BTM_SPACE = HALF_SPACE
+ if wx.Platform == "__WXMAC__":
+ BTM_SPACE = SPACE
+ buttonSizer.Add(findBtn, 0, wx.BOTTOM, BTM_SPACE)
buttonSizer.Add(cancelBtn, 0)
gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
wx.EVT_BUTTON(self, FindService.REPLACEONE_ID, self.OnActionEvent)
replaceAllBtn = wx.Button(self, FindService.REPLACEALL_ID, _("Replace All"))
wx.EVT_BUTTON(self, FindService.REPLACEALL_ID, self.OnActionEvent)
- buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
- buttonSizer.Add(replaceBtn, 0, wx.BOTTOM, HALF_SPACE)
- buttonSizer.Add(replaceAllBtn, 0, wx.BOTTOM, HALF_SPACE)
+
+ BTM_SPACE = HALF_SPACE
+ if wx.Platform == "__WXMAC__":
+ BTM_SPACE = SPACE
+
+ buttonSizer.Add(findBtn, 0, wx.BOTTOM, BTM_SPACE)
+ buttonSizer.Add(replaceBtn, 0, wx.BOTTOM, BTM_SPACE)
+ buttonSizer.Add(replaceAllBtn, 0, wx.BOTTOM, BTM_SPACE)
buttonSizer.Add(cancelBtn, 0)
gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
return \
'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
-\x00\x00\x81IDAT8\x8d\xa5S\xc1\x16\xc0\x10\x0ckk\xff\xff\xc7d\x87\xad^U\r\
-\x93S\xe5U$\n\xb3$:\xc1e\x17(\x19Z\xb3$\x9e\xf1DD\xe2\x15\x01x\xea\x93\xef\
-\x04\x989\xea\x1b\xf2U\xc0\xda\xb4\xeb\x11\x1f:\xd8\xb5\xff8\x93\xd4\xa9\xae\
-@/S\xaaUwJ3\x85\xc0\x81\xee\xeb.q\x17C\x81\xd5XU \x1a\x93\xc6\x18\x8d\x90\
-\xe8}\x89\x00\x9a&\x9b_k\x94\x0c\xdf\xd78\xf8\x0b\x99Y\xb4\x08c\x9e\xfe\xc6\
-\xe3\x087\xf9\xd0D\x180\xf1#\x8e\x00\x00\x00\x00IEND\xaeB`\x82'
+\x00\x01\xb1IDAT8\x8d\xa5\x93=o\xd3P\x14\x86\x1f\xa7\x11\x95<\xdc\xc6\xecN+5\
+[\x86B\x99\xacLQ2Zr[\x89\xa1\xfd\x0b%\x95\x90\x00\xf1\x03\x80\x01\x98\x80\
+\x19G\xac\x0cm\xff@Y\xd9:\xd9Ck\x94\xd6\xddb\x94\x9b\x98\xc8\xd2e1C\xe5\x8b\
+\xdd\x14\x96\xbe\xdb=\x1f\xefy\xef\xf90\x8c\xda\x12wA\xbd\xfc\x18\xfa\x9fs\
+\x80\xf9|\x0e\xc0\x93\xc1\x81\x01\xf0\xe6\xf5\xab\x1c`:\x9d\x02\xf0\xf6\xdd{\
+\xa3\xc8\xa9\xddd\xec\xf5z\xb4Z\xeb\x00\x1c\x1f\x1d\xe6\x85\xdd\xf3<\x06\x83\
+\xc1\x82\xbd\xa2 \x0cCL\xd3d<\x1e\x13\xc71\xb6m\x030\x1a\x8d\x08\x82\x00\x80\
+\xb3\xb3s:\x9d\x8e\xce\xa9(h6\x9b8\x8e\x83m\xdb4\x1a\r\x82 \xe0\xc5\xf3g\xb9\
+eY\xb4\xdbm\x1c\xc7Y\xe8\x81&\xf8\xf4\xf1C\xde\xedv+\xce\x97Owx\xfc\xe8k\xc5\
+\xb6\xb7\xb7\x8b\xef\x0foW \x84\xe0\xea\xea\x02\xa5\x94n\x18\x80\x94\x92\xd9\
+l\x02@\x96e\x95>\xd4nVO\xd3\xb9\x0e\xba\r\xa6i\xd2\xef\xf7\xf0\xfd!\xc7G\x87\
+y\xed:)\xd5\x01J\xfd\xd6c\xfc~\x9a\xfc\x93\xe8\xf2\xf2\x02(Ma6\x9b \x84@)\
+\xa5\t}\xff\x0b\xd0\'I~R\x14\xca\xb2L\xfb\x97\x97\xef-\xeeA!_J\x89\xeb\xba\
+\xb8\xae\xab\xbf\x06\x7f\x97\xacP[\x87\xeb9\x0b!H\x92\ta\x18"\xa5\xd4U\xbd\
+\xadm\xe3\xe1\x83\x8d<\x8a~\x90\xa6\xbf\x88\xe3\x18)\xa5&\xa9\x03X\x96E\xab\
+\xb5\x8em7\xf5\xc2\x94\xb1\xba\xba\xc6\xe6\xe6\x06++\xf7\x89\xa2\xa8\xe2\xd3\
+=89\xf9Va.\x14\x14\xd8\xdf?X VJa\x14\xd7X\xde\xef2\xbc\xadm\xe3\x7f~\xe3\xae\
+\xe7\xfc\x07\x84;\xc5\x82\xa1m&\x95\x00\x00\x00\x00IEND\xaeB`\x82'
def getFindBitmap():
_ = wx.GetTranslation
class TextDocument(wx.lib.docview.Document):
+
+ def __init__(self):
+ wx.lib.docview.Document .__init__(self)
+ self._inModify = False
- def OnSaveDocument(self, filename):
+ def SaveObject(self, fileObject):
view = self.GetFirstView()
- if not view.GetTextCtrl().SaveFile(filename):
- return False
- self.Modify(False)
- self.SetDocumentSaved(True)
- #if wx.Platform == "__WXMAC__":
- # fn = wx.Filename(filename)
- # fn.MacSetDefaultTypeAndCreator()
+ fileObject.write(view.GetTextCtrl().GetValue())
return True
- def OnOpenDocument(self, filename):
+ def LoadObject(self, fileObject):
view = self.GetFirstView()
- if not view.GetTextCtrl().LoadFile(filename):
- return False
- self.SetFilename(filename, True)
- self.Modify(False)
- self.UpdateAllViews()
- self._savedYet = True
+ data = fileObject.read()
+ view.GetTextCtrl().SetValue(data)
return True
def IsModified(self):
view = self.GetFirstView()
if view and view.GetTextCtrl():
- return wx.lib.docview.Document.IsModified(self) or view.GetTextCtrl().IsModified()
- else:
- return wx.lib.docview.Document.IsModified(self)
+ return view.GetTextCtrl().IsModified()
+ return False
- def Modify(self, mod):
+ def Modify(self, modify):
+ if self._inModify:
+ return
+ self._inModify = True
+
view = self.GetFirstView()
- wx.lib.docview.Document.Modify(self, mod)
- if not mod and view and view.GetTextCtrl():
+ if not modify and view and view.GetTextCtrl():
view.GetTextCtrl().DiscardEdits()
+ wx.lib.docview.Document.Modify(self, modify) # this must called be after the DiscardEdits call above.
+
+ self._inModify = False
+
class TextView(wx.lib.docview.View):
sizer = wx.BoxSizer()
font, color = self._GetFontAndColorFromConfig()
self._textCtrl = self._BuildTextCtrl(frame, font, color = color)
+ self._textCtrl.Bind(wx.EVT_TEXT, self.OnModify)
sizer.Add(self._textCtrl, 1, wx.EXPAND, 0)
frame.SetSizer(sizer)
frame.Layout()
return True
+ def OnModify(self, event):
+ self.GetDocument().Modify(True)
+
+
def _BuildTextCtrl(self, parent, font, color = wx.BLACK, value = "", selection = [0, 0]):
if self._wordWrap:
wordWrapStyle = wx.TE_WORDWRAP
def OnUpdate(self, sender = None, hint = None):
+ if wx.lib.docview.View.OnUpdate(self, sender, hint):
+ return
+
if hint == "Word Wrap":
self.SetWordWrap(wx.ConfigBase_Get().ReadInt("TextEditorWordWrap", True))
elif hint == "Font":
#
# Created: 5/15/03
# CVS-ID: $Id$
-# Copyright: (c) 2003-2005 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al)
+# Copyright: (c) 2003-2006 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al)
# License: wxWindows license
#----------------------------------------------------------------------------
false otherwise. You may need to override this if your document view
maintains its own record of being modified (for example if using
xTextWindow to view and edit the document).
+ This method has been extended to notify its views that the dirty flag has changed.
"""
self._documentModified = modify
+ self.UpdateAllViews(hint=("modify", self, self._documentModified))
def SetDocumentModificationDate(self):
return self._documentModificationDate
+ def IsDocumentModificationDateCorrect(self):
+ """
+ Returns False if the file has been modified outside of the application.
+ This method has been added to wxPython and is not in wxWindows.
+ """
+ if not os.path.exists(self.GetFilename()): # document must be in memory only and can't be out of date
+ return True
+ return self._documentModificationDate == os.path.getmtime(self.GetFilename())
+
+
def GetViews(self):
"""
Returns the list whose elements are the views on the document.
Destructor. Removes itself from the document manager.
"""
self.DeleteContents()
+ self._documentModificationDate = None
if self.GetDocumentManager():
self.GetDocumentManager().RemoveDocument(self)
wx.EvtHandler.Destroy(self)
return True
""" check for file modification outside of application """
- if os.path.exists(self.GetFilename()) and os.path.getmtime(self.GetFilename()) != self.GetDocumentModificationDate():
+ if not self.IsDocumentModificationDateCorrect():
msgTitle = wx.GetApp().GetAppName()
if not msgTitle:
msgTitle = _("Application")
self.GetDocumentWindow())
return False
+ self.SetDocumentModificationDate()
self.SetFilename(filename, True)
self.Modify(False)
- self.SetDocumentModificationDate()
self.SetDocumentSaved(True)
#if wx.Platform == '__WXMAC__': # Not yet implemented in wxPython
# wx.FileName(file).MacSetDefaultTypeAndCreator()
self.GetDocumentWindow())
return False
+ self.SetDocumentModificationDate()
self.SetFilename(filename, True)
self.Modify(False)
- self.SetDocumentModificationDate()
self.SetDocumentSaved(True)
self.UpdateAllViews()
return True
return True
""" check for file modification outside of application """
- if os.path.exists(self.GetFilename()) and os.path.getmtime(self.GetFilename()) != self.GetDocumentModificationDate():
+ if not self.IsDocumentModificationDateCorrect():
msgTitle = wx.GetApp().GetAppName()
if not msgTitle:
msgTitle = _("Warning")
unused but may in future contain application-specific information for
making updating more efficient.
"""
- pass
-
+ if hint:
+ if hint[0] == "modify": # if dirty flag changed, update the view's displayed title
+ frame = self.GetFrame()
+ if frame and hasattr(frame, "OnTitleIsModified"):
+ frame.OnTitleIsModified()
+ return True
+ return False
+
def OnChangeFilename(self):
"""
Call this from your view frame's OnActivate member to tell the
framework which view is currently active. If your windowing system
doesn't call OnActivate, you may need to call this function from
- any place where you know the view must be active, and
+ OnMenuCommand or any place where you know the view must be active, and
the framework will need to get the current view.
The prepackaged view frame wxDocChildFrame calls wxView.Activate from
- its OnActivate member.
+ its OnActivate member and from its OnMenuCommand member.
"""
if self.GetDocument() and self.GetDocumentManager():
self.OnActivateView(activate, self, self.GetDocumentManager().GetCurrentView())
for document in self._docs:
if document.GetFilename() and os.path.normcase(document.GetFilename()) == os.path.normcase(path):
""" check for file modification outside of application """
- if os.path.exists(path) and os.path.getmtime(path) != document.GetDocumentModificationDate():
+ if not document.IsDocumentModificationDateCorrect():
msgTitle = wx.GetApp().GetAppName()
if not msgTitle:
msgTitle = _("Warning")
if len(descr) > 0:
descr = descr + _('|')
descr = descr + temp.GetDescription() + _(" (") + temp.GetFileFilter() + _(") |") + temp.GetFileFilter() # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
- descr = _("All (*.*)|*.*|%s") % descr # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
+ descr = _("All|*.*|%s") % descr # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
else:
descr = _("*.*")
self._childView.Activate(event.GetActive())
self._activated = 0
+
def OnCloseWindow(self, event):
"""
Closes and deletes the current view and document.
self._childView = view
+ def OnTitleIsModified(self):
+ """
+ Add/remove to the frame's title an indication that the document is dirty.
+ If the document is dirty, an '*' is appended to the title
+ This method has been added to wxPython and is not in wxWindows.
+ """
+ title = self.GetTitle()
+ if title:
+ if self.GetDocument().IsModified():
+ if title.endswith("*"):
+ return
+ else:
+ title = title + "*"
+ self.SetTitle(title)
+ else:
+ if title.endswith("*"):
+ title = title[:-1]
+ self.SetTitle(title)
+ else:
+ return
+
+
class DocPrintout(wx.Printout):
"""
DocPrintout is a default Printout that prints the first page of a document
return pageNum == 1
- def OnBeginDocument(self, startPage, endPage):
- """
- Not quite sure why this was overridden, but it was in wxWindows! :)
- """
- if not wx.Printout.OnBeginDocument(self, startPage, endPage):
- return False
- return True
-
-
def GetPageInfo(self):
"""
Indicates that the DocPrintout only has a single page.
# Name: pydocview.py
# Purpose: Python extensions to the wxWindows docview framework
#
-# Author: Peter Yared, Morgan Hua
+# Author: Peter Yared, Morgan Hua, Matt Fryer
#
# Created: 5/15/03
# CVS-ID: $Id$
-# Copyright: (c) 2003-2005 ActiveGrid, Inc.
+# Copyright: (c) 2003-2006 ActiveGrid, Inc.
# License: wxWindows license
#----------------------------------------------------------------------------
Class with common code used by DocMDIParentFrame, DocTabbedParentFrame, and
DocSDIFrame.
"""
-
+
def GetDocumentManager(self):
"""
if sdi:
if self.GetDocument() and self.GetDocument().GetCommandProcessor():
self.GetDocument().GetCommandProcessor().SetEditMenu(editMenu)
-
+
viewMenu = wx.Menu()
viewMenu.AppendCheckItem(VIEW_TOOLBAR_ID, _("&Toolbar"), _("Shows or hides the toolbar"))
wx.EVT_MENU(self, VIEW_TOOLBAR_ID, self.OnViewToolBar)
if sdi: # TODO: Is this really needed?
wx.EVT_COMMAND_FIND_CLOSE(self, -1, self.ProcessEvent)
-
+
return menuBar
Saves all of the currently open documents.
"""
docs = wx.GetApp().GetDocumentManager().GetDocuments()
-
+
# save child documents first
for doc in docs:
if isinstance(doc, wx.lib.pydocview.ChildDocument):
doc.Save()
-
+
# save parent and other documents later
for doc in docs:
if not isinstance(doc, wx.lib.pydocview.ChildDocument):
"""
Class with common code used by DocMDIParentFrame and DocTabbedParentFrame.
"""
-
+
def _GetPosSizeFromConfig(self, pos, size):
"""
if wx.Display_GetFromPoint(pos) == -1: # Check if the frame position is offscreen
pos = wx.DefaultPosition
-
+
if size == wx.DefaultSize:
size = wx.Size(config.ReadInt("MDIFrameXSize", 450), config.ReadInt("MDIFrameYSize", 300))
return pos, size
- def _InitFrame(self, embeddedWindows):
+ def _InitFrame(self, embeddedWindows, minSize):
"""
Initializes the frame and creates the default menubar, toolbar, and status bar.
"""
# wxBug: On maximize, statusbar leaves a residual that needs to be refereshed, happens even when user does it
self.Maximize()
- self.CreateEmbeddedWindows(embeddedWindows)
+ self.CreateEmbeddedWindows(embeddedWindows, minSize)
self._LayoutFrame()
if wx.Platform == '__WXMAC__':
if id == SAVEALL_ID:
self.OnFileSaveAll(event)
return True
-
+
return wx.GetApp().ProcessEvent(event)
if doc.IsModified():
filesModified = True
break
-
+
event.Enable(filesModified)
return True
else:
return wx.GetApp().ProcessUpdateUIEvent(event)
- def CreateEmbeddedWindows(self, windows=0):
+ def CreateEmbeddedWindows(self, windows=0, minSize=20):
"""
Create the specified embedded windows around the edges of the frame.
"""
frameSize = self.GetSize() # TODO: GetClientWindow.GetSize is still returning 0,0 since the frame isn't fully constructed yet, so using full frame size
- MIN_SIZE = 20
- defaultHSize = max(MIN_SIZE, int(frameSize[0] / 6))
- defaultVSize = max(MIN_SIZE, int(frameSize[1] / 7))
+ defaultHSize = max(minSize, int(frameSize[0] / 6))
+ defaultVSize = max(minSize, int(frameSize[1] / 7))
defaultSubVSize = int(frameSize[1] / 2)
config = wx.ConfigBase_Get()
if windows & (EMBEDDED_WINDOW_LEFT | EMBEDDED_WINDOW_TOPLEFT | EMBEDDED_WINDOW_BOTTOMLEFT):
- self._leftEmbWindow = self._CreateEmbeddedWindow(self, (max(MIN_SIZE,config.ReadInt("MDIEmbedLeftSize", defaultHSize)), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_LEFT, visible = config.ReadInt("MDIEmbedLeftVisible", 1), sash = wx.SASH_RIGHT)
+ self._leftEmbWindow = self._CreateEmbeddedWindow(self, (max(minSize,config.ReadInt("MDIEmbedLeftSize", defaultHSize)), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_LEFT, visible = config.ReadInt("MDIEmbedLeftVisible", 1), sash = wx.SASH_RIGHT)
else:
self._leftEmbWindow = None
if windows & EMBEDDED_WINDOW_TOPLEFT:
else:
self._bottomLeftEmbWindow = None
if windows & (EMBEDDED_WINDOW_RIGHT | EMBEDDED_WINDOW_TOPRIGHT | EMBEDDED_WINDOW_BOTTOMRIGHT):
- self._rightEmbWindow = self._CreateEmbeddedWindow(self, (max(MIN_SIZE,config.ReadInt("MDIEmbedRightSize", defaultHSize)), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_RIGHT, visible = config.ReadInt("MDIEmbedRightVisible", 1), sash = wx.SASH_LEFT)
+ self._rightEmbWindow = self._CreateEmbeddedWindow(self, (max(minSize,config.ReadInt("MDIEmbedRightSize", defaultHSize)), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_RIGHT, visible = config.ReadInt("MDIEmbedRightVisible", 1), sash = wx.SASH_LEFT)
else:
self._rightEmbWindow = None
if windows & EMBEDDED_WINDOW_TOPRIGHT:
else:
self._bottomRightEmbWindow = None
if windows & EMBEDDED_WINDOW_TOP:
- self._topEmbWindow = self._CreateEmbeddedWindow(self, (-1, max(MIN_SIZE,config.ReadInt("MDIEmbedTopSize", defaultVSize))), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopVisible", 1), sash = wx.SASH_BOTTOM)
+ self._topEmbWindow = self._CreateEmbeddedWindow(self, (-1, max(minSize,config.ReadInt("MDIEmbedTopSize", defaultVSize))), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopVisible", 1), sash = wx.SASH_BOTTOM)
else:
self._topEmbWindow = None
if windows & EMBEDDED_WINDOW_BOTTOM:
- self._bottomEmbWindow = self._CreateEmbeddedWindow(self, (-1, max(MIN_SIZE,config.ReadInt("MDIEmbedBottomSize", defaultVSize))), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomVisible", 1), sash = wx.SASH_TOP)
+ self._bottomEmbWindow = self._CreateEmbeddedWindow(self, (-1, max(minSize,config.ReadInt("MDIEmbedBottomSize", defaultVSize))), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomVisible", 1), sash = wx.SASH_TOP)
else:
self._bottomEmbWindow = None
def _CreateEmbeddedWindow(self, parent, size, orientation, alignment, visible=True, sash=None):
"""
- Creates the embedded window with the specified size, orientation, and alignment. If the
+ Creates the embedded window with the specified size, orientation, and alignment. If the
window is not visible it will retain the size with which it was last viewed.
"""
window = wx.SashLayoutWindow(parent, wx.NewId(), style = wx.NO_BORDER | wx.SW_3D)
if isinstance(window.GetParent(), wx.SashLayoutWindow): # It is a parent sashwindow with multiple embedded sashwindows
parentSashWindow = window.GetParent()
if show: # Make sure it is visible in case all of the subwindows were hidden
- parentSashWindow.Show()
+ parentSashWindow.Show()
if show and window._sizeBeforeHidden:
if window._sizeBeforeHidden[1] == parentSashWindow.GetClientSize()[1]:
if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).IsShown():
self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).SetDefaultSize(otherWindowSize)
elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT):
self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).SetDefaultSize(otherWindowSize)
-
+
if not show:
if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).IsShown() \
or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).IsShown() \
Dummy method since the icon of tabbed frames are managed by the notebook.
"""
return None
-
+
def SetIcon(self, icon):
"""
wx.GetApp().GetTopWindow().SetNotebookPageTitle(self, title)
+ def OnTitleIsModified(self):
+ """
+ Add/remove to the frame's title an indication that the document is dirty.
+ If the document is dirty, an '*' is appended to the title
+ """
+ title = self.GetTitle()
+ if title:
+ if self.GetDocument().IsModified():
+ if not title.endswith("*"):
+ title = title + "*"
+ self.SetTitle(title)
+ else:
+ if title.endswith("*"):
+ title = title[:-1]
+ self.SetTitle(title)
+
+
def ProcessEvent(event):
"""
Processes an event, searching event tables and calling zero or more
"""
- def __init__(self, docManager, frame, id, title, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = "DocTabbedParentFrame", embeddedWindows = 0):
+ def __init__(self, docManager, frame, id, title, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = "DocTabbedParentFrame", embeddedWindows = 0, minSize=20):
"""
Constructor. Note that the event table must be rebuilt for the
frame since the EvtHandler is not virtual.
# End From docview.MDIParentFrame
self.CreateNotebook()
- self._InitFrame(embeddedWindows)
-
+ self._InitFrame(embeddedWindows, minSize)
+
def _LayoutFrame(self):
"""
Lays out the frame.
"""
wx.LayoutAlgorithm().LayoutFrame(self, self._notebook)
-
+
def CreateNotebook(self):
"""
Creates the notebook to use for the tabbed document interface.
- """
+ """
if wx.Platform != "__WXMAC__":
self._notebook = wx.Notebook(self, wx.NewId())
else:
wx.EVT_LISTBOOK_PAGE_CHANGED(self, self._notebook.GetId(), self.OnNotebookPageChanged)
wx.EVT_RIGHT_DOWN(self._notebook, self.OnNotebookRightClick)
wx.EVT_MIDDLE_DOWN(self._notebook, self.OnNotebookMiddleClick)
-
+
# wxBug: wx.Listbook does not implement HitTest the same way wx.Notebook
# does, so for now don't fire MouseOver events.
if wx.Platform != "__WXMAC__":
print "Warning: icon for '%s' isn't 16x16, not crossplatform" % template._docTypeName
iconIndex = iconList.AddIcon(icon)
self._iconIndexLookup.append((template, iconIndex))
-
+
icon = getBlankIcon()
if icon.GetHeight() != 16 or icon.GetWidth() != 16:
icon.SetHeight(16)
Returns the notebook used by the tabbed document interface.
"""
return self._notebook
-
+
def GetActiveChild(self):
"""
def OnNotebookMouseOver(self, event):
# wxBug: On Windows XP the tooltips don't automatically disappear when you move the mouse and it is on a notebook tab, has nothing to do with this code!!!
index, type = self._notebook.HitTest(event.GetPosition())
-
+
if index > -1:
doc = self._notebook.GetPage(index).GetView().GetDocument()
- self._notebook.SetToolTip(wx.ToolTip(doc.GetFilename()))
+ # wxBug: Tooltips no longer appearing on tabs except on
+ # about a 2 pixel area between tab top and contents that will show tip.
+ self._notebook.GetParent().SetToolTip(wx.ToolTip(doc.GetFilename()))
else:
self._notebook.SetToolTip(wx.ToolTip(""))
event.Skip()
-
+
def OnNotebookMiddleClick(self, event):
"""
doc = self._notebook.GetPage(index).GetView().GetDocument()
if doc:
doc.DeleteAllViews()
-
+
def OnNotebookRightClick(self, event):
"""
Handles right clicks for the notebook, enabling users to either close
def OnRightMenuSelect(event):
self._notebook.SetSelection(selectIDs[event.GetId()])
wx.EVT_MENU(self, id, OnRightMenuSelect)
-
+
self._notebook.PopupMenu(menu, wx.Point(x, y))
menu.Destroy()
-
-
+
+
def AddNotebookPage(self, panel, title):
"""
Adds a document page to the notebook.
break
if not found:
self._notebook.SetPageImage(index, self._blankIconIndex)
-
+
# wxBug: the wxListbook used on Mac needs its tabs list resized
# whenever a new tab is added, but the only way to do this is
# to resize the entire control
content_size = self._notebook.GetSize()
self._notebook.SetSize((content_size.x+2, -1))
self._notebook.SetSize((content_size.x, -1))
-
+
self._notebook.Layout()
+ windowMenuService = wx.GetApp().GetService(WindowMenuService)
+ if windowMenuService:
+ windowMenuService.BuildWindowMenu(wx.GetApp().GetTopWindow()) # build file menu list when we open a file
def RemoveNotebookPage(self, panel):
"""
index = self.GetNotebookPageIndex(panel)
if index > -1:
+ if self._notebook.GetPageCount() == 1 or index < 2:
+ pass
+ elif index >= 1:
+ self._notebook.SetSelection(index - 1)
+ elif index < self._notebook.GetPageCount():
+ self._notebook.SetSelection(index + 1)
self._notebook.DeletePage(index)
+ self._notebook.GetParent().SetToolTip(wx.ToolTip(""))
+
+ windowMenuService = wx.GetApp().GetService(WindowMenuService)
+ if windowMenuService:
+ windowMenuService.BuildWindowMenu(wx.GetApp().GetTopWindow()) # build file menu list when we open a file
def ActivateNotebookPage(self, panel):
if index > -1:
self._notebook.SetFocus()
self._notebook.SetSelection(index)
-
+
def GetNotebookPageTitle(self, panel):
- return self._notebook.GetPageText(self.GetNotebookPageIndex(panel))
-
+ index = self.GetNotebookPageIndex(panel)
+ if index != -1:
+ return self._notebook.GetPageText(self.GetNotebookPageIndex(panel))
+ else:
+ return None
+
def SetNotebookPageTitle(self, panel, title):
self._notebook.SetPageText(self.GetNotebookPageIndex(panel), title)
-
+
def GetNotebookPageIndex(self, panel):
"""
index = i
break
return index
-
+
def ProcessEvent(self, event):
"""
self.Destroy()
else:
event.Veto()
-
+
class DocMDIChildFrame(wx.MDIChildFrame):
"""
self._childView.Activate(True)
+ def OnTitleIsModified(self):
+ """
+ Add/remove to the frame's title an indication that the document is dirty.
+ If the document is dirty, an '*' is appended to the title
+ """
+ title = self.GetTitle()
+ if title:
+ if self.GetDocument().IsModified():
+ if title.endswith("*"):
+ return
+ else:
+ title = title + "*"
+ self.SetTitle(title)
+ else:
+ if title.endswith("*"):
+ title = title[:-1]
+ self.SetTitle(title)
+ else:
+ return
+
+
def ProcessEvent(event):
"""
Processes an event, searching event tables and calling zero or more
self._childView = view
-
-
-
class DocService(wx.EvtHandler):
"""
An abstract class used to add reusable services to a docview application.
def ProcessEventBeforeWindows(self, event):
"""
- Processes an event before the main window has a chance to process the window.
+ Processes an event before the main window has a chance to process the window.
Override this method for a particular service.
"""
return False
toolsMenu.AppendSeparator()
toolsMenu.Append(self._toolOptionsID, _("&Options..."), _("Sets options"))
wx.EVT_MENU(frame, self._toolOptionsID, frame.ProcessEvent)
-
+
def ProcessEvent(self, event):
"""
Return the modes supported by the application. Use docview.DOC_SDI and
docview.DOC_MDI flags to check if SDI and/or MDI modes are supported.
"""
- return self._supportedModes
+ return self._supportedModes
def SetSupportedModes(self, _supportedModessupportedModes):
def AddOptionsPanel(self, optionsPanel):
"""
- Adds an options panel to the options dialog.
+ Adds an options panel to the options dialog.
"""
self._optionsPanels.append(optionsPanel)
"""
Initializes the options dialog with a notebook page that contains new
instances of the passed optionsPanelClasses.
- """
+ """
wx.Dialog.__init__(self, parent, -1, _("Options"))
self._optionsPanels = []
else:
optionsNotebook = wx.Notebook(self, wx.NewId(), style=wx.NB_MULTILINE) # NB_MULTILINE is windows platform only
sizer.Add(optionsNotebook, 0, wx.ALL | wx.EXPAND, SPACE)
-
+
if wx.Platform == "__WXMAC__":
iconList = wx.ImageList(16, 16, initialCount = len(optionsPanelClasses))
self._iconIndexLookup = []
-
+
for optionsPanelClass in optionsPanelClasses:
optionsPanel = optionsPanelClass(optionsNotebook, -1)
self._optionsPanels.append(optionsPanel)
-
+
# We need to populate the image list before setting notebook images
if hasattr(optionsPanel, "GetIcon"):
icon = optionsPanel.GetIcon()
print "Warning: icon for '%s' isn't 16x16, not crossplatform" % template._docTypeName
iconIndex = iconList.AddIcon(icon)
self._iconIndexLookup.append((optionsPanel, iconIndex))
-
+
else:
# use -1 to represent that this panel has no icon
self._iconIndexLookup.append((optionsPanel, -1))
-
+
optionsNotebook.AssignImageList(iconList)
-
+
# Add icons to notebook
for index in range(0, len(optionsPanelClasses)-1):
iconIndex = self._iconIndexLookup[index][1]
if iconIndex >= 0:
- optionsNotebook.SetPageImage(index, iconIndex)
+ optionsNotebook.SetPageImage(index, iconIndex)
else:
for optionsPanelClass in optionsPanelClasses:
optionsPanel = optionsPanelClass(optionsNotebook, -1)
def __init__(self, parent, id):
"""
Initializes the panel by adding an "Options" folder tab to the parent notebook and
- populating the panel with the generic properties of a pydocview application.
+ populating the panel with the generic properties of a pydocview application.
"""
wx.Panel.__init__(self, parent, id)
SPACE = 10
choices.append(self._mdiChoice)
if wx.Platform == "__WXMSW__":
choices.append(self._winMdiChoice)
- self._documentRadioBox = wx.RadioBox(self, -1, _("Document Interface"),
+ self._documentRadioBox = wx.RadioBox(self, -1, _("Document Display Style"),
choices = choices,
majorDimension=1,
)
self.SetSizer(optionsBorderSizer)
self.Layout()
self._documentInterfaceMessageShown = False
- parent.AddPage(self, _("Options"))
+ parent.AddPage(self, _("General"))
def _AllowModeChanges(self):
self._debug = False
if not hasattr(self, "_singleInstance"): # only set if not already initialized
self._singleInstance = True
-
+
# if _singleInstance is TRUE only allow one single instance of app to run.
# When user tries to run a second instance of the app, abort startup,
# But if user also specifies files to open in command line, send message to running app to open those files
break
else:
time.sleep(1) # give enough time for buffer to be available
-
+
return False
else:
self._timer = wx.PyTimer(self.DoBackgroundListenAndLoad)
self._timer.Start(250)
return True
-
+
def OpenMainFrame(self):
docManager = self.GetDocumentManager()
if self.GetUseTabbedMDI():
frame = wx.lib.pydocview.DocTabbedParentFrame(docManager, None, -1, self.GetAppName())
else:
- frame = wx.lib.pydocview.DocMDIParentFrame(docManager, None, -1, self.GetAppName())
+ frame = wx.lib.pydocview.DocMDIParentFrame(docManager, None, -1, self.GetAppName())
frame.Show(True)
def MacOpenFile(self, filename):
self.GetDocumentManager().CreateDocument(os.path.normpath(filename), wx.lib.docview.DOC_SILENT)
-
+
# force display of running app
topWindow = wx.GetApp().GetTopWindow()
if topWindow.IsIconized():
topWindow.Iconize(False)
else:
topWindow.Raise()
-
+
def DoBackgroundListenAndLoad(self):
"""
Open any files specified in the given command line argument passed in via shared memory
for arg in args:
if (wx.Platform != "__WXMSW__" or arg[0] != "/") and arg[0] != '-' and os.path.exists(arg):
self.GetDocumentManager().CreateDocument(os.path.normpath(arg), wx.lib.docview.DOC_SILENT)
-
+
# force display of running app
topWindow = wx.GetApp().GetTopWindow()
if topWindow.IsIconized():
topWindow.Iconize(False)
else:
topWindow.Raise()
-
-
+
+
self._timer.Start(1000) # 1 second interval
def ProcessEventBeforeWindows(self, event):
"""
Enables services to process an event before the main window has a chance to
- process the window.
+ process the window.
"""
for service in self._services:
if service.ProcessEventBeforeWindows(event):
service.OnExit()
config = wx.ConfigBase_Get()
self._docManager.FileHistorySave(config)
-
+
if hasattr(self, "_singleInstanceChecker"):
del self._singleInstanceChecker
-
+
def GetDefaultDocManagerFlags(self):
"""
Returns the default flags to use when creating the DocManager.
Returns True if Windows MDI should use folder tabs instead of child windows.
"""
return self._useTabbedMDI
-
+
def SetUseTabbedMDI(self, useTabbedMDI):
"""
def CreateDocumentFrame(self, view, doc, flags, id = -1, title = "", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE):
"""
- Called by the DocManager to create and return a new Frame for a Document.
+ Called by the DocManager to create and return a new Frame for a Document.
Chooses whether to create an MDIChildFrame or SDI Frame based on the
DocManager's flags.
"""
"""
Creates a child window of a document that edits an object. The child window
is managed by the parent document frame, so it will be prompted to close if its
- parent is closed, etc. Child Documents are useful when there are complicated
+ parent is closed, etc. Child Documents are useful when there are complicated
Views of a Document and users will need to tunnel into the View.
"""
for document in self.GetDocumentManager().GetDocuments()[:]: # Cloning list to make sure we go through all docs even as they are deleted
splash_bmp = image
else:
splash_bmp = wx.Image(image).ConvertToBitmap()
- self._splash = wx.SplashScreen(splash_bmp, wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_NO_TIMEOUT, 0, None, -1, style=wx.SIMPLE_BORDER|wx.FRAME_NO_TASKBAR)
+ self._splash = wx.SplashScreen(splash_bmp, wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_NO_TIMEOUT, 0, None, -1, style=wx.SIMPLE_BORDER|wx.FRAME_NO_TASKBAR)
self._splash.Show()
"""
if self._splash:
self._splash.Close(True)
-
-
+
+
class _DocFrameFileDropTarget(wx.FileDropTarget):
"""
Class used to handle drops into the document frame.
msgTitle = wx.GetApp().GetAppName()
if not msgTitle:
msgTitle = _("File Error")
- wx.MessageBox("Could not open '%s'. '%s'" % (docview.FileNameFromPath(file), sys.exc_value),
+ wx.MessageBox("Could not open '%s'. '%s'" % (wx.lib.docview.FileNameFromPath(file), sys.exc_value),
msgTitle,
wx.OK | wx.ICON_EXCLAMATION,
self._docManager.FindSuitableParent())
features such as a default menubar, toolbar, and status bar, and a mechanism to manage embedded windows
on the edges of the DocMDIParentFrame.
"""
-
- def __init__(self, docManager, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="DocMDIFrame", embeddedWindows=0):
+
+ def __init__(self, docManager, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="DocMDIFrame", embeddedWindows=0, minSize=20):
"""
Initializes the DocMDIParentFrame with the default menubar, toolbar, and status bar. Use the
optional embeddedWindows parameter with the embedded window constants to create embedded
"""
pos, size = self._GetPosSizeFromConfig(pos, size)
wx.lib.docview.DocMDIParentFrame.__init__(self, docManager, parent, id, title, pos, size, style, name)
- self._InitFrame(embeddedWindows)
+ self._InitFrame(embeddedWindows, minSize)
def _LayoutFrame(self):
"""
wx.LayoutAlgorithm().LayoutMDIFrame(self)
self.GetClientWindow().Refresh()
-
+
def ProcessEvent(self, event):
"""
wx.IDM_WINDOWPREV = 4006 # wxBug: Not defined for some reason
windowMenu.Enable(wx.IDM_WINDOWPREV, has2OrMoreWindows)
windowMenu.Enable(wx.IDM_WINDOWNEXT, has2OrMoreWindows)
-
+
def OnSize(self, event):
The DocSDIFrame host DocManager Document windows. It offers features such as a default menubar,
toolbar, and status bar.
"""
-
+
def __init__(self, doc, view, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="DocSDIFrame"):
"""
Lays out the Frame.
"""
self.Layout()
-
+
def OnExit(self, event):
"""
if doc.IsModified():
filesModified = True
break
-
+
event.Enable(filesModified)
return True
else:
else:
self._dlg = AboutDialog # use default AboutDialog
self._image = image
-
+
def ShowAbout(self):
"""
"""
Opens an AboutDialog. Shared by DocMDIParentFrame and DocSDIFrame.
"""
-
+
def __init__(self, parent, image=None):
"""
Initializes the about dialog.
imageItem = wx.StaticBitmap(self, -1, image.ConvertToBitmap(), (0,0), (image.GetWidth(), image.GetHeight()))
sizer.Add(imageItem, 0, wx.ALIGN_CENTER|wx.ALL, 0)
sizer.Add(wx.StaticText(self, -1, wx.GetApp().GetAppName()), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
-
+
btn = wx.Button(self, wx.ID_OK)
sizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
-
+
self.SetSizer(sizer)
sizer.Fit(self)
-
+
class FilePropertiesService(DocService):
"""
A ChildDocument is a document that represents a portion of a Document. The child
document is managed by the parent document, so it will be prompted to close if its
- parent is closed, etc. Child Documents are useful when there are complicated
+ parent is closed, etc. Child Documents are useful when there are complicated
Views of a Document and users will need to tunnel into the View.
"""
def SetParentDocument(self, parentDocument):
"""
Sets the parent Document of the ChildDocument.
- """
+ """
self._parentDocument = parentDocument
Called when the ChildDocument is saved and does the minimum such that the
ChildDocument looks like a real Document to the framework.
"""
- return self.OnSaveDocument(self._documentFile)
+ return self.OnSaveDocument(self._documentFile)
class ChildDocTemplate(wx.lib.docview.DocTemplate):
that represents a portion of a Document. The child document is managed by the parent document,
so it will be prompted to close if its parent is closed, etc. Child Documents are useful
when there are complicated Views of a Document and users will need to tunnel into the View.
- """
+ """
def __init__(self, manager, description, filter, dir, ext, docTypeName, viewTypeName, docType, viewType, flags=wx.lib.docview.TEMPLATE_INVISIBLE, icon=None):
by the DocSDIFrame. The MDIFrame automatically includes a Window menu and does not use
the WindowMenuService.
"""
-
+
+ #----------------------------------------------------------------------------
+ # Constants
+ #----------------------------------------------------------------------------
+ ARRANGE_WINDOWS_ID = wx.NewId()
+ SELECT_MORE_WINDOWS_ID = wx.NewId()
+ SELECT_NEXT_WINDOW_ID = wx.NewId()
+ SELECT_PREV_WINDOW_ID = wx.NewId()
+ CLOSE_CURRENT_WINDOW_ID = wx.NewId()
+
def __init__(self):
"""
Initializes the WindowMenu and its globals.
"""
- self.ARRANGE_WINDOWS_ID = wx.NewId()
- self.SELECT_WINDOW_1_ID = wx.NewId()
- self.SELECT_WINDOW_2_ID = wx.NewId()
- self.SELECT_WINDOW_3_ID = wx.NewId()
- self.SELECT_WINDOW_4_ID = wx.NewId()
- self.SELECT_WINDOW_5_ID = wx.NewId()
- self.SELECT_WINDOW_6_ID = wx.NewId()
- self.SELECT_WINDOW_7_ID = wx.NewId()
- self.SELECT_WINDOW_8_ID = wx.NewId()
- self.SELECT_WINDOW_9_ID = wx.NewId()
- self.SELECT_MORE_WINDOWS_ID = wx.NewId()
+ self._selectWinIds = []
+ for i in range(0, 9):
+ self._selectWinIds.append(wx.NewId())
def InstallControls(self, frame, menuBar=None, toolBar=None, statusBar=None, document=None):
Installs the Window menu.
"""
- if not self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
- return # Only need windows menu for SDI mode, MDI frame automatically creates one
-
- if not _WINDOWS: # Arrange All and window navigation doesn't work on Linux
- return
+ windowMenu = None
+ if hasattr(frame, "GetWindowMenu"):
+ windowMenu = frame.GetWindowMenu()
+ if not windowMenu:
+ needWindowMenu = True
+ windowMenu = wx.Menu()
+ else:
+ needWindowMenu = False
- windowMenu = wx.Menu()
- item = windowMenu.Append(self.ARRANGE_WINDOWS_ID, _("&Arrange All"), _("Arrange the open windows"))
- windowMenu.AppendSeparator()
-
- wx.EVT_MENU(frame, self.ARRANGE_WINDOWS_ID, frame.ProcessEvent)
- wx.EVT_UPDATE_UI(frame, self.ARRANGE_WINDOWS_ID, frame.ProcessUpdateUIEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_1_ID, frame.ProcessEvent) # wxNewId may have been nonsequential, so can't use EVT_MENU_RANGE
- wx.EVT_MENU(frame, self.SELECT_WINDOW_2_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_3_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_4_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_5_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_6_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_7_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_8_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_WINDOW_9_ID, frame.ProcessEvent)
- wx.EVT_MENU(frame, self.SELECT_MORE_WINDOWS_ID, frame.ProcessEvent)
-
- helpMenuIndex = menuBar.FindMenu(_("&Help"))
- menuBar.Insert(helpMenuIndex, windowMenu, _("&Window"))
+ if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+ if not _WINDOWS: # Arrange All and window navigation doesn't work on Linux
+ return
+
+ item = windowMenu.Append(self.ARRANGE_WINDOWS_ID, _("&Arrange All"), _("Arrange the open windows"))
+ wx.EVT_MENU(frame, self.ARRANGE_WINDOWS_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, self.ARRANGE_WINDOWS_ID, frame.ProcessUpdateUIEvent)
+ windowMenu.AppendSeparator()
+
+ for i, id in enumerate(self._selectWinIds):
+ wx.EVT_MENU(frame, id, frame.ProcessEvent)
+ wx.EVT_MENU(frame, self.SELECT_MORE_WINDOWS_ID, frame.ProcessEvent)
+ elif wx.GetApp().GetUseTabbedMDI():
+ item = windowMenu.Append(self.SELECT_PREV_WINDOW_ID, _("Previous"), _("Previous Tab"))
+ wx.EVT_MENU(frame, self.SELECT_PREV_WINDOW_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, self.SELECT_PREV_WINDOW_ID, frame.ProcessUpdateUIEvent)
+ item = windowMenu.Append(self.SELECT_NEXT_WINDOW_ID, _("Next"), _("Next Tab"))
+ wx.EVT_MENU(frame, self.SELECT_NEXT_WINDOW_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, self.SELECT_NEXT_WINDOW_ID, frame.ProcessUpdateUIEvent)
+ item = windowMenu.Append(self.CLOSE_CURRENT_WINDOW_ID, _("Close Current\tCtrl+F4"), _("Close Current Tab"))
+ wx.EVT_MENU(frame, self.CLOSE_CURRENT_WINDOW_ID, frame.ProcessEvent)
+ wx.EVT_UPDATE_UI(frame, self.CLOSE_CURRENT_WINDOW_ID, frame.ProcessUpdateUIEvent)
+ self._sep = None
+
+ for i, id in enumerate(self._selectWinIds):
+ wx.EVT_MENU(frame, id, self.OnCtrlKeySelect)
+
+ if needWindowMenu:
+ helpMenuIndex = menuBar.FindMenu(_("&Help"))
+ menuBar.Insert(helpMenuIndex, windowMenu, _("&Window"))
self._lastFrameUpdated = None
+ def OnCtrlKeySelect(self, event):
+ i = self._selectWinIds.index(event.GetId())
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ if i < notebook.GetPageCount():
+ notebook.SetSelection(i)
+
+
def ProcessEvent(self, event):
"""
Processes a Window menu event.
elif id == self.SELECT_MORE_WINDOWS_ID:
self.OnSelectMoreWindows(event)
return True
- elif id == self.SELECT_WINDOW_1_ID or id == self.SELECT_WINDOW_2_ID or id == self.SELECT_WINDOW_3_ID or id == self.SELECT_WINDOW_4_ID or id == self.SELECT_WINDOW_5_ID or id == self.SELECT_WINDOW_6_ID or id == self.SELECT_WINDOW_7_ID or id == self.SELECT_WINDOW_8_ID or id == self.SELECT_WINDOW_9_ID:
+ elif id in self._selectWinIds:
self.OnSelectWindowMenu(event)
return True
+ elif wx.GetApp().GetUseTabbedMDI():
+ if id == self.SELECT_NEXT_WINDOW_ID:
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ i = notebook.GetSelection()
+ notebook.SetSelection(i+1)
+ return True
+ elif id == self.SELECT_PREV_WINDOW_ID:
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ i = notebook.GetSelection()
+ notebook.SetSelection(i-1)
+ return True
+ elif id == self.CLOSE_CURRENT_WINDOW_ID:
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ i = notebook.GetSelection()
+ if i != -1:
+ doc = notebook.GetPage(i).GetView().GetDocument()
+ wx.GetApp().GetDocumentManager().CloseDocument(doc, False)
+ return True
else:
return False
self.BuildWindowMenu(frame) # It's a new frame, so update the windows menu... this is as if the View::OnActivateMethod had been invoked
self._lastFrameUpdated = frame
return True
+ elif wx.GetApp().GetUseTabbedMDI():
+ if id == self.SELECT_NEXT_WINDOW_ID:
+ self.BuildWindowMenu(event.GetEventObject()) # build file list only when we are updating the windows menu
+
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ i = notebook.GetSelection()
+ if i == -1:
+ event.Enable(False)
+ return True
+ i += 1
+ if i >= notebook.GetPageCount():
+ event.Enable(False)
+ return True
+ event.Enable(True)
+ return True
+ elif id == self.SELECT_PREV_WINDOW_ID:
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ i = notebook.GetSelection()
+ if i == -1:
+ event.Enable(False)
+ return True
+ i -= 1
+ if i < 0:
+ event.Enable(False)
+ return True
+ event.Enable(True)
+ return True
+ elif id == self.CLOSE_CURRENT_WINDOW_ID:
+ event.Enable(wx.GetApp().GetTopWindow()._notebook.GetSelection() != -1)
+ return True
+
+ return False
else:
return False
"""
Builds the Window menu and adds menu items for all of the open documents in the DocManager.
"""
+ if wx.GetApp().GetUseTabbedMDI():
+ currentFrame = wx.GetApp().GetTopWindow()
+
windowMenuIndex = currentFrame.GetMenuBar().FindMenu(_("&Window"))
windowMenu = currentFrame.GetMenuBar().GetMenu(windowMenuIndex)
- ids = self._GetWindowMenuIDList()
- frames = self._GetWindowMenuFrameList(currentFrame)
- max = WINDOW_MENU_NUM_ITEMS
- if max > len(frames):
- max = len(frames)
- i = 0
- for i in range(0, max):
- frame = frames[i]
- item = windowMenu.FindItemById(ids[i])
- label = '&' + str(i + 1) + ' ' + frame.GetTitle()
- if not item:
- item = windowMenu.AppendCheckItem(ids[i], label)
- else:
- windowMenu.SetLabel(ids[i], label)
- windowMenu.Check(ids[i], (frame == currentFrame))
- if len(frames) > WINDOW_MENU_NUM_ITEMS: # Add the more items item
- if not windowMenu.FindItemById(self.SELECT_MORE_WINDOWS_ID):
- windowMenu.Append(self.SELECT_MORE_WINDOWS_ID, _("&More Windows..."))
- else: # Remove any extra items
- if windowMenu.FindItemById(self.SELECT_MORE_WINDOWS_ID):
- windowMenu.Remove(self.SELECT_MORE_WINDOWS_ID)
-
-
- for j in range(i + 1, WINDOW_MENU_NUM_ITEMS):
- if windowMenu.FindItemById(ids[j]):
- windowMenu.Remove(ids[j])
+ if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+ frames = self._GetWindowMenuFrameList(currentFrame)
+ max = WINDOW_MENU_NUM_ITEMS
+ if max > len(frames):
+ max = len(frames)
+ i = 0
+ for i in range(0, max):
+ frame = frames[i]
+ item = windowMenu.FindItemById(self._selectWinIds[i])
+ label = '&' + str(i + 1) + ' ' + frame.GetTitle()
+ if not item:
+ item = windowMenu.AppendCheckItem(self._selectWinIds[i], label)
+ else:
+ windowMenu.SetLabel(self._selectWinIds[i], label)
+ windowMenu.Check(self._selectWinIds[i], (frame == currentFrame))
+ if len(frames) > WINDOW_MENU_NUM_ITEMS: # Add the more items item
+ if not windowMenu.FindItemById(self.SELECT_MORE_WINDOWS_ID):
+ windowMenu.Append(self.SELECT_MORE_WINDOWS_ID, _("&More Windows..."))
+ else: # Remove any extra items
+ if windowMenu.FindItemById(self.SELECT_MORE_WINDOWS_ID):
+ windowMenu.Remove(self.SELECT_MORE_WINDOWS_ID)
+
+ for j in range(i + 1, WINDOW_MENU_NUM_ITEMS):
+ if windowMenu.FindItemById(self._selectWinIds[j]):
+ windowMenu.Remove(self._selectWinIds[j])
+
+ elif wx.GetApp().GetUseTabbedMDI():
+ notebook = wx.GetApp().GetTopWindow()._notebook
+ numPages = notebook.GetPageCount()
+
+ for id in self._selectWinIds:
+ item = windowMenu.FindItemById(id)
+ if item:
+ windowMenu.DeleteItem(item)
+ if numPages == 0 and self._sep:
+ windowMenu.DeleteItem(self._sep)
+ self._sep = None
+
+ if numPages > len(self._selectWinIds):
+ for i in range(len(self._selectWinIds), numPages):
+ self._selectWinIds.append(wx.NewId())
+ wx.EVT_MENU(currentFrame, self._selectWinIds[i], self.OnCtrlKeySelect)
+
+ for i in range(0, numPages):
+ if i == 0 and not self._sep:
+ self._sep = windowMenu.AppendSeparator()
+ if i < 9:
+ menuLabel = "%s\tCtrl+%s" % (notebook.GetPageText(i), i+1)
+ else:
+ menuLabel = notebook.GetPageText(i)
+ windowMenu.Append(self._selectWinIds[i], menuLabel)
def _GetWindowMenuIDList(self):
"""
Returns a list of the Window menu item IDs.
"""
- return [self.SELECT_WINDOW_1_ID, self.SELECT_WINDOW_2_ID, self.SELECT_WINDOW_3_ID, self.SELECT_WINDOW_4_ID, self.SELECT_WINDOW_5_ID, self.SELECT_WINDOW_6_ID, self.SELECT_WINDOW_7_ID, self.SELECT_WINDOW_8_ID, self.SELECT_WINDOW_9_ID]
+ return self._selectWinIds
def _GetWindowMenuFrameList(self, currentFrame=None):
Frame to the front of the desktop.
"""
id = event.GetId()
- index = self._GetWindowMenuIDList().index(id)
+ index = self._selectWinIds.index(id)
if index > -1:
currentFrame = event.GetEventObject()
frame = self._GetWindowMenuFrameList(currentFrame)[index]
\x902\xb2@\xc8\xc2\x8b\xd9\xbcX\xc0\x045\xac\xc1 Jg\xe6\x08\xe8)\xa7o\xd5\
\xb0\xbf\xcb\nd\x86x\x0b\x9c+p\x0b\x0c\xa9\x16~\xbc_\xeb\x9d\xd3\x03\xcb3q\
\xefo\xbc\xfa/\x14\xd9\x19\x1f\xfb\x8aa\x87\xf2\xf7\x16\x00\x00\x00\x00IEND\
-\xaeB`\x82"
+\xaeB`\x82"
def getNewBitmap():
return BitmapFromImage(getNewImage())
\xad\xb1\xab\x99\x98bdb\xd4q\xa7\xefd\xbb\x05\xa7\xdd\x8f\x0e/\x9d\x01\x85\
\xbc\nX+8K\\\x99\xe5\x02x\x16\xf6\xba\x02$\xc9\xe56\x1fF[\xda\x8bn\x9er\xa7\
\x02\xc1\x90\xedoH\xed\xdf\x18\x8fE\xc5o\x0c\x8e\x80\xbf\xea\x13\xa8\x18\x89\
-5\xe7L\xb3:\x00\x00\x00\x00IEND\xaeB`\x82'
+5\xe7L\xb3:\x00\x00\x00\x00IEND\xaeB`\x82'
def getOpenBitmap():
return BitmapFromImage(getOpenImage())
\xb9\xe7\x1fE\xae\xb7\\\xb6\x1f\xe0\x8d\x15H$\x99\x1b?\x12@\xd7\xdf\xd0\x0f\
\nN!\x91\x98\x9e\xd8\x0c\x10\xbd>\xdeU\xeco\np\xf7\xf8\xebK\x14fvF\xc8ds\xce\
\xff\xbd\xb6u(\xbc\x89\xbc\x17\xf6\x9f\x14E\x8d\x04\x8a\xdeDa\xcads\xca\x1f\
-\x0cI\xd4\xda\x88E\x9d\xc4\x00\x00\x00\x00IEND\xaeB`\x82'
+\x0cI\xd4\xda\x88E\x9d\xc4\x00\x00\x00\x00IEND\xaeB`\x82'
def getCopyBitmap():
return BitmapFromImage(getCopyImage())
\xb0\x98\x1f\x00x-\xd5\xb0\xce\xc3\xd1~LW\x98\x15\xab\xccM\x8f\xfe\xaf\x03\
\x00w0\xccS\xfdgm\xfb\xc3\xd7\xf7++w\xd5\x16\x0f\x92\t\xe4\xe9zN\x86\xbe\xa7\
1\xaa\xfbLY\xb1:\x10 (\xe3\x0c?\x03\xf2_\xb9W=\xc2\x17\x1c\xf8\x87\x9a\x03\
-\x12\xd7\xb9\x00\x00\x00\x00IEND\xaeB`\x82"
+\x12\xd7\xb9\x00\x00\x00\x00IEND\xaeB`\x82"
def getPasteBitmap():
return BitmapFromImage(getPasteImage())
g\x1f!U\xac\xe0y^\xe62\xc6p\xd6h\x14\x8e4s\x89\xc6\xa4\xcb[\xa9V\xffG\xa0\
\xb5\xce\x8a\x97j[\xb4\xe3\xb8\x90@)\'\xfd\xbe\xd7\xf5\xe2\x83\xeau\xec~w\'\
\x9a\x12\x00\\6\xc3\xd2\xab,\xec`^|\x03\xb6\xdf|Q.\xa7\x15\x89\x00\x00\x00\
-\x00IEND\xaeB`\x82'
+\x00IEND\xaeB`\x82'
def getSaveBitmap():
return BitmapFromImage(getSaveImage())
\xa9&\xb3\x86c\xd3r![\xe47\x14 |\x14\xcf\xb7\x13JNZ7\xab\xc2\xe9\xddn7\x9e\
\xbb>\xcb\x01\x98\xc9\xa0T\x93Y\x93\xdbH\xa2\xaa*4MC\xb5Z\xcdt \x84\x98\xfa(\
S\xf2\xf9\xfc\xdc+0&\xc9\xa9\xc1\x86\xf3}\x1d\xbf\r\xacm\x84\xf5\xc2\x02\x00\
-Pw\xefR\x99d\xf1\x05z\x94\xd0b\xcb S\xf3\x00\x00\x00\x00IEND\xaeB`\x82'
+Pw\xefR\x99d\xf1\x05z\x94\xd0b\xcb S\xf3\x00\x00\x00\x00IEND\xaeB`\x82'
def getSaveAllBitmap():
return BitmapFromImage(getSaveAllImage())
\xe95n4\xea\x01\xab\x9dN\xc7\xe3"9\x1fGr>\xeeYs\x8fr:\x9d\x06c\x0c\x86ax\nL\
\xcb;\xbb\x1f\x84\xd0\x10*\xe5\x12WU\x15\xcd7`f\xf2\xc7z\x00\x80\xae\xeb\xc8\
\xe5rXI\xad\x12"nc\xa5\\\xe2{G*\xba\xef\xfa\xaf\x02\xa2\xd9u \xe0?\xe7\xdfA4\
-\x03\xc0\'\xe3\x82\xc9\x18g\x90\x8e]\x00\x00\x00\x00IEND\xaeB`\x82'
+\x03\xc0\'\xe3\x82\xc9\x18g\x90\x8e]\x00\x00\x00\x00IEND\xaeB`\x82'
def getPrintBitmap():
return BitmapFromImage(getPrintImage())
\xf2#u\xc3\xb2m`t\x00&\x07E4\xcb]x.QH\xa6\xec$\x13\xf83q^\xb44^\x8f\xb8\xa5"\
p\x9c\x88\xa3\x91\xe1\x9d5\x00\x14Eu\xc9y\x9c\xa4\xeb\xba\xe5}\xb6\x9a\x01`\
\xc1\x07\xf39\x97\xa2(\xaa\xab\x17+\xd5]\xe0\xf5dC\x9a\xfc\xcb\xc0\xc9\xd00\
-\xf9\x011\xc9\x87\xf3\xb4\xd1t\xaf\x00\x00\x00\x00IEND\xaeB`\x82'
+\xf9\x011\xc9\x87\xf3\xb4\xd1t\xaf\x00\x00\x00\x00IEND\xaeB`\x82'
def getPrintPreviewBitmap():
return BitmapFromImage(getPrintPreviewImage())
\x00,\xa6\x9f\x00\x14o+\xec\x9f\x15X\xba\x97\xf1\tTC\x1c\xfe]e\x80v\xa9\xcc\
\xb8\xeb2\xfb\xf8\xe2\xf5\xaeA\xbbT\xd6\xea"c\x1c\xf4{r\xfbe\xf5Y?\xa7\xd5\
\x80W\xd1w\n7k\xa3\xd4\xee\x81\x8a\x18\x16\xea8\x80_\\\xa2\x8b\x88!\xd2S\x08\
-\x00\x00\x00\x00IEND\xaeB`\x82'
+\x00\x00\x00\x00IEND\xaeB`\x82'
def getCutBitmap():
return BitmapFromImage(getCutImage())
\x06#\x13\x0c}\x1a\x06 \xdc\xfc\xc87\xf0?\xb8\x1e\xc1\n\xa1\xac\x10Zk\xe9\
\x18k\x95\x9fGS\xf2\xa58*\x9f7S\xd2\x92\x0c\x8b\xd6Z\xccL\xd0\xf6\x1d\xb4\
\xd6\xd2\x92\x0c\xcb\xea\xdf\x0f\r\xc1w\x047%\x8d\xc0\x81\x02#i\x04VV\x88k\
-\x82\xbe\xde\xc2\xb0\xb2\xea\xa7\x00\x00\x00\x00IEND\xaeB`\x82"
+\x82\xbe\xde\xc2\xb0\xb2\xea\xa7\x00\x00\x00\x00IEND\xaeB`\x82"
def getUndoBitmap():
return BitmapFromImage(getUndoImage())
\xbd\xd1\xfe\x10=\xfc\xe8\x1eg\x91\xbc\xfc\x06\x81\xa0\xc2\xd2\x13\xa789\xbe\
\x91\xde\xce\x14\x07\x82\nC\xaf\xeb\xd6\xe0\x9c\x93/9Lj%L\xa9\xf2\x1c\xa5\
\xcas\xe4\r\xb9m\xaf\xf0'\xc0\x84xCnR+\xe1_\xe2\xbe\x00V\x88\xec\x9f\xf4\x05\
-0!\xb2\xfc\x0f\xe0\xc4\xb6\xad\x97R\xe5z\x00\x00\x00\x00IEND\xaeB`\x82"
+0!\xb2\xfc\x0f\xe0\xc4\xb6\xad\x97R\xe5z\x00\x00\x00\x00IEND\xaeB`\x82"
def getRedoBitmap():
return BitmapFromImage(getRedoImage())
def getRedoImage():
stream = cStringIO.StringIO(getRedoData())
return ImageFromStream(stream)
-
+
#----------------------------------------------------------------------------
def getBlankData():
\xa8X:\xd4\x13\x03:\x1b\x01\xa45T\xd4\xefBsh\xd7Hk\xdc\x02\x00@\x8a\x19$\xa1\
9\x14A,\x95\xf3\x82G)\xd3\x00\xf24\xf7\x90\x1ev\x07\xee\x1e\xf4:\xc1J?\xe0\
\x0b\x80\xc7\x1d\xf8\x1dg\xc4\xea7\x96G8\x00\xa8\x91\x19(\x85#P\x7f\x00\x00\
-\x00\x00IEND\xaeB`\x82'
+\x00\x00IEND\xaeB`\x82'
def getBlankBitmap():
def getBlankIcon():
return wx.IconFromBitmap(getBlankBitmap())
-
+