From aca310e5cc45db153540efe2df577d8f985ccfc1 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Thu, 20 Apr 2006 06:26:03 +0000 Subject: [PATCH] DocView and ActiveGrid IDE updates from Morgan Hua: New Features: In Tab-View mode, Ctrl-number will take the user to the numbered tab view. Modified files now show an '*' astrisk in the view title. Debugger framework can now support PHP debugging. Not important for python development, but at least that means the debugger framework is more generalized. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@38852 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- .../ide/activegrid/model/projectmodel.py | 20 + .../ide/activegrid/tool/AboutDialog.py | 33 +- .../ide/activegrid/tool/AbstractEditor.py | 42 +- .../samples/ide/activegrid/tool/CodeEditor.py | 43 +- .../ide/activegrid/tool/DebuggerService.py | 2200 ++++++++----- .../ide/activegrid/tool/ExtensionService.py | 85 +- .../ide/activegrid/tool/FindInDirService.py | 421 ++- .../samples/ide/activegrid/tool/HtmlEditor.py | 4 +- wxPython/samples/ide/activegrid/tool/IDE.py | 2824 +++++++++-------- .../ide/activegrid/tool/MarkerService.py | 8 +- .../ide/activegrid/tool/MessageService.py | 1 + .../ide/activegrid/tool/OutlineService.py | 9 +- .../ide/activegrid/tool/PHPDebugger.py | 2105 ++++++++++++ .../samples/ide/activegrid/tool/PHPEditor.py | 148 +- .../samples/ide/activegrid/tool/PerlEditor.py | 4 +- .../ide/activegrid/tool/ProjectEditor.py | 1321 +++++--- .../ide/activegrid/tool/PythonEditor.py | 51 +- .../ide/activegrid/tool/STCTextEditor.py | 64 +- .../samples/ide/activegrid/tool/SVNService.py | 953 +++--- .../samples/ide/activegrid/tool/UICommon.py | 380 ++- .../samples/ide/activegrid/tool/Wizard.py | 2146 ++++++++++++- .../samples/ide/activegrid/tool/XmlEditor.py | 4 +- .../activegrid/tool/bmp_source/activegrid.ico | Bin 0 -> 174246 bytes .../samples/ide/activegrid/tool/data/tips.txt | 6 +- .../samples/ide/activegrid/tool/process.py | 8 +- .../samples/ide/activegrid/tool/project.py | 311 +- .../samples/ide/activegrid/util/aglogging.py | 75 +- .../samples/ide/activegrid/util/appdirs.py | 143 +- .../ide/activegrid/util/datetimeparser.py | 118 + .../samples/ide/activegrid/util/fileutils.py | 109 +- .../samples/ide/activegrid/util/objutils.py | 76 +- .../samples/ide/activegrid/util/parser.py | 380 +++ .../samples/ide/activegrid/util/strutils.py | 88 + .../samples/ide/activegrid/util/sysutils.py | 29 + .../samples/ide/activegrid/util/utillang.py | 146 + .../ide/activegrid/util/xmlmarshaller.py | 348 +- .../samples/ide/activegrid/util/xmlutils.py | 100 +- wxPython/samples/pydocview/FindService.py | 43 +- wxPython/samples/pydocview/TextEditor.py | 50 +- wxPython/wx/lib/docview.py | 73 +- wxPython/wx/lib/pydocview.py | 516 +-- 41 files changed, 11446 insertions(+), 4039 deletions(-) create mode 100644 wxPython/samples/ide/activegrid/model/projectmodel.py create mode 100644 wxPython/samples/ide/activegrid/tool/PHPDebugger.py create mode 100644 wxPython/samples/ide/activegrid/tool/bmp_source/activegrid.ico create mode 100644 wxPython/samples/ide/activegrid/util/datetimeparser.py create mode 100644 wxPython/samples/ide/activegrid/util/parser.py create mode 100644 wxPython/samples/ide/activegrid/util/utillang.py diff --git a/wxPython/samples/ide/activegrid/model/projectmodel.py b/wxPython/samples/ide/activegrid/model/projectmodel.py new file mode 100644 index 0000000000..aafd8b8bf6 --- /dev/null +++ b/wxPython/samples/ide/activegrid/model/projectmodel.py @@ -0,0 +1,20 @@ +#---------------------------------------------------------------------------- +# 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] diff --git a/wxPython/samples/ide/activegrid/tool/AboutDialog.py b/wxPython/samples/ide/activegrid/tool/AboutDialog.py index 53a00402ca..c86043d91d 100644 --- a/wxPython/samples/ide/activegrid/tool/AboutDialog.py +++ b/wxPython/samples/ide/activegrid/tool/AboutDialog.py @@ -5,7 +5,7 @@ # Author: Morgan Hua # # Created: 3/22/05 -# Copyright: (c) 2005 ActiveGrid, Inc. +# Copyright: (c) 2005-2006 ActiveGrid, Inc. # CVS-ID: $Id$ # License: wxWindows License #---------------------------------------------------------------------------- @@ -30,23 +30,25 @@ licenseData = [ # add licenses for base IDE features ("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 @@ -70,7 +72,7 @@ class AboutDialog(wx.Dialog): 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') @@ -82,7 +84,7 @@ class AboutDialog(wx.Dialog): 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")) @@ -90,15 +92,14 @@ class AboutDialog(wx.Dialog): 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 @@ -115,7 +116,7 @@ class AboutDialog(wx.Dialog): grid.SetCellValue(row, 0, license) if url: grid.SetCellValue(row, 1, url) - + grid.EnableEditing(False) grid.EnableDragGridSize(False) grid.EnableDragColSize(False) @@ -132,10 +133,10 @@ class AboutDialog(wx.Dialog): 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) @@ -145,5 +146,5 @@ class AboutDialog(wx.Dialog): self.Layout() self.Fit() grid.ForceRefresh() # wxBug: Get rid of unnecessary scrollbars - + diff --git a/wxPython/samples/ide/activegrid/tool/AbstractEditor.py b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py index f795ecd6f8..8a2e9c5232 100644 --- a/wxPython/samples/ide/activegrid/tool/AbstractEditor.py +++ b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py @@ -36,6 +36,7 @@ PARKING_VERTICAL = 1 PARKING_HORIZONTAL = 2 PARKING_OFFSET = 30 # space between shapes +FORCE_REDRAW_METHOD = "ForceRedraw" def GetRawModel(model): if hasattr(model, "GetRawModel"): @@ -85,6 +86,7 @@ class CanvasView(wx.lib.docview.View): self._propShape = None self._maxWidth = 2000 self._maxHeight = 16000 + self._valetParking = False def OnDraw(self, dc): @@ -195,6 +197,16 @@ class CanvasView(wx.lib.docview.View): 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: @@ -211,6 +223,7 @@ class CanvasView(wx.lib.docview.View): 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 @@ -260,12 +273,15 @@ class CanvasView(wx.lib.docview.View): 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) @@ -274,7 +290,8 @@ class CanvasView(wx.lib.docview.View): if len(self.GetSelection()) == 0: self.SetPropertyShape(None) - + for shape in forceRedrawShapes: + shape.ForceRedraw() def OnLeftDoubleClick(self, event): propertyService = wx.GetApp().GetService(PropertyService.PropertyService) @@ -401,8 +418,20 @@ class CanvasView(wx.lib.docview.View): 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 @@ -422,6 +451,9 @@ class CanvasView(wx.lib.docview.View): else: noParkingSpot = False + if self._valetParking: + self._valetPosition = (x, y) + return x, y @@ -518,7 +550,8 @@ class CanvasView(wx.lib.docview.View): self._diagram.RemoveShape(line) line.Delete() - shape.RemoveFromCanvas(self._canvas) + if self._canvas: + shape.RemoveFromCanvas(self._canvas) self._diagram.RemoveShape(shape) shape.Delete() @@ -698,6 +731,9 @@ class CanvasView(wx.lib.docview.View): self._propShape.SetTextColour("WHITE", 0) self._propShape.Draw(dc) + if hasattr(self._propShape, FORCE_REDRAW_METHOD): + self._propShape.ForceRedraw() + dc.EndDrawing() diff --git a/wxPython/samples/ide/activegrid/tool/CodeEditor.py b/wxPython/samples/ide/activegrid/tool/CodeEditor.py index 7e245bcd1f..72a5dd2c4f 100644 --- a/wxPython/samples/ide/activegrid/tool/CodeEditor.py +++ b/wxPython/samples/ide/activegrid/tool/CodeEditor.py @@ -19,7 +19,6 @@ import os import re import string import sys -import DebuggerService import MarkerService from UICommon import CaseInsensitiveCompare _ = wx.GetTranslation @@ -120,16 +119,27 @@ class CodeView(STCTextEditor.TextView): 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 @@ -140,10 +150,12 @@ class CodeView(STCTextEditor.TextView): 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()) @@ -210,7 +222,7 @@ class CodeView(STCTextEditor.TextView): 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 @@ -232,11 +244,13 @@ class CodeView(STCTextEditor.TextView): 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 @@ -467,6 +481,9 @@ class CodeView(STCTextEditor.TextView): 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": @@ -474,6 +491,7 @@ class CodeView(STCTextEditor.TextView): 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) @@ -623,7 +641,7 @@ class CodeCtrl(STCTextEditor.TextCtrl): 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) @@ -635,7 +653,6 @@ class CodeCtrl(STCTextEditor.TextCtrl): 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) @@ -657,7 +674,7 @@ class CodeCtrl(STCTextEditor.TextCtrl): # 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) @@ -693,7 +710,7 @@ class CodeCtrl(STCTextEditor.TextCtrl): 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() @@ -715,6 +732,7 @@ class CodeCtrl(STCTextEditor.TextCtrl): 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) @@ -859,6 +877,7 @@ class CodeCtrl(STCTextEditor.TextCtrl): 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())) diff --git a/wxPython/samples/ide/activegrid/tool/DebuggerService.py b/wxPython/samples/ide/activegrid/tool/DebuggerService.py index 485853611b..67ed47fac5 100644 --- a/wxPython/samples/ide/activegrid/tool/DebuggerService.py +++ b/wxPython/samples/ide/activegrid/tool/DebuggerService.py @@ -1,6 +1,6 @@ #---------------------------------------------------------------------------- # Name: DebuggerService.py -# Purpose: Debugger Service for Python. +# Purpose: Debugger Service for Python and PHP # # Author: Matt Fryer # @@ -21,6 +21,9 @@ import Service 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 @@ -42,6 +45,10 @@ import DebuggerHarness import traceback import StringIO import UICommon +import activegrid.util.sysutils as sysutilslib +import subprocess +import shutil + if wx.Platform == '__WXMSW__': try: import win32api @@ -60,6 +67,11 @@ _ = wx.GetTranslation _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 @@ -74,11 +86,11 @@ class OutputReaderThread(threading.Thread): 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() @@ -95,20 +107,20 @@ class OutputReaderThread(threading.Thread): # 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: @@ -121,32 +133,61 @@ class OutputReaderThread(threading.Thread): 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("\""): @@ -171,34 +212,40 @@ class Executor: 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() @@ -207,30 +254,33 @@ class Executor: 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" ) @@ -240,11 +290,11 @@ class RunCommandUI(wx.Panel): 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) @@ -257,27 +307,27 @@ class RunCommandUI(wx.Panel): 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() @@ -287,27 +337,27 @@ class RunCommandUI(wx.Panel): 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) @@ -317,26 +367,26 @@ class RunCommandUI(wx.Panel): 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() @@ -344,11 +394,11 @@ class RunCommandUI(wx.Panel): 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]) @@ -368,103 +418,72 @@ class RunCommandUI(wx.Panel): 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() @@ -478,37 +497,37 @@ class DebugCommandUI(wx.Panel): 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) @@ -519,11 +538,12 @@ class DebugCommandUI(wx.Panel): 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) @@ -531,85 +551,72 @@ class DebugCommandUI(wx.Panel): 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 @@ -625,15 +632,21 @@ class DebugCommandUI(wx.Panel): 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 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. @@ -648,7 +661,7 @@ class DebugCommandUI(wx.Panel): if DebuggerService.ComparePaths(openDoc.GetFilename(),filename): foundView = openDoc.GetFirstView() break - + if not foundView: if _VERBOSE: print "filename=", filename @@ -660,96 +673,368 @@ class DebugCommandUI(wx.Panel): 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) @@ -763,19 +1048,19 @@ class BreakpointsUI(wx.Panel): 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() @@ -783,7 +1068,7 @@ class BreakpointsUI(wx.Panel): p1.SetSizer(sizer) sizer.Fit(p1) p1.Layout() - + def PopulateBPList(self): list = self._bpListCtrl list.DeleteAllItems() @@ -798,7 +1083,7 @@ class BreakpointsUI(wx.Panel): 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") @@ -813,32 +1098,32 @@ class BreakpointsUI(wx.Panel): 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" @@ -865,22 +1150,22 @@ class WatchDialog(wx.Dialog): 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)) @@ -900,15 +1185,15 @@ class WatchDialog(wx.Dialog): 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 @@ -917,7 +1202,7 @@ class FramesUI(wx.SplitterWindow): 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) @@ -925,21 +1210,20 @@ class FramesUI(wx.SplitterWindow): 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) @@ -953,18 +1237,59 @@ class FramesUI(wx.SplitterWindow): 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) @@ -979,92 +1304,312 @@ class FramesUI(wx.SplitterWindow): 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() @@ -1098,7 +1643,7 @@ class FramesUI(wx.SplitterWindow): menu.Destroy() self._parentChain = None self._introspectItem = None - + def GetItemChain(self, item): parentChain = [] if item: @@ -1110,18 +1655,18 @@ class FramesUI(wx.SplitterWindow): 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 @@ -1133,9 +1678,8 @@ class FramesUI(wx.SplitterWindow): value += item prevItem = item print value - self.ReplaceLastLine(value) self.ExecuteCommand(value) - + def OnWatch(self, event): try: if hasattr(self, '_parentChain'): @@ -1147,7 +1691,7 @@ class FramesUI(wx.SplitterWindow): 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) @@ -1159,106 +1703,90 @@ class FramesUI(wx.SplitterWindow): 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: @@ -1271,13 +1799,13 @@ class FramesUI(wx.SplitterWindow): 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 @@ -1290,9 +1818,9 @@ class FramesUI(wx.SplitterWindow): 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)) @@ -1303,16 +1831,16 @@ class FramesUI(wx.SplitterWindow): 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") @@ -1321,7 +1849,7 @@ class FramesUI(wx.SplitterWindow): 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")) @@ -1334,73 +1862,44 @@ class FramesUI(wx.SplitterWindow): 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 @@ -1412,23 +1911,23 @@ class Interaction: 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) @@ -1440,31 +1939,31 @@ class RequestHandlerThread(threading.Thread): 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: @@ -1489,7 +1988,7 @@ class RequestBreakThread(threading.Thread): self._pushBreakpoints = pushBreakpoints self._breakDict = breakDict self._kill = kill - + def run(self): try: if _VERBOSE: print "RequestBreakThread, before call" @@ -1511,7 +2010,7 @@ class DebuggerOperationThread(threading.Thread): def __init__(self, function): threading.Thread.__init__(self) self._function = function - + def run(self): if _VERBOSE: print "In DOT, before call" try: @@ -1519,19 +2018,46 @@ class DebuggerOperationThread(threading.Thread): 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 @@ -1541,49 +2067,44 @@ class DebuggerCallback: 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() @@ -1591,37 +2112,37 @@ class DebuggerCallback: 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) @@ -1633,11 +2154,45 @@ class DebuggerCallback: 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): #---------------------------------------------------------------------------- @@ -1647,9 +2202,12 @@ 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) @@ -1658,7 +2216,7 @@ class DebuggerService(Service.Service): else: return one == two ComparePaths = staticmethod(ComparePaths) - + # Make sure we're using an expanded path on windows. def ExpandPath(path): if _WINDOWS: @@ -1667,11 +2225,11 @@ class DebuggerService(Service.Service): except: if _VERBOSE: print "Cannot get long path for %s" % path - + return path - + ExpandPath = staticmethod(ExpandPath) - + #---------------------------------------------------------------------------- # Overridden methods #---------------------------------------------------------------------------- @@ -1690,7 +2248,12 @@ class DebuggerService(Service.Service): 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: @@ -1700,7 +2263,7 @@ class DebuggerService(Service.Service): tp,val,tb = sys.exc_info() traceback.print_exception(tp, val, tb) return True - + def _CreateView(self): return DebuggerView(self) @@ -1711,20 +2274,28 @@ class DebuggerService(Service.Service): 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")) @@ -1733,30 +2304,31 @@ class DebuggerService(Service.Service): 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 #---------------------------------------------------------------------------- @@ -1782,14 +2354,26 @@ class DebuggerService(Service.Service): 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 @@ -1803,7 +2387,9 @@ class DebuggerService(Service.Service): 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: @@ -1811,75 +2397,172 @@ class DebuggerService(Service.Service): #---------------------------------------------------------------------------- # 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() @@ -1888,14 +2571,14 @@ class DebuggerService(Service.Service): 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"), @@ -1909,10 +2592,10 @@ class DebuggerService(Service.Service): 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 @@ -1924,30 +2607,38 @@ class DebuggerService(Service.Service): 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: @@ -1970,8 +2661,8 @@ class DebuggerService(Service.Service): 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): @@ -1982,30 +2673,30 @@ class DebuggerService(Service.Service): 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): @@ -2016,7 +2707,7 @@ class DebuggerService(Service.Service): 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): @@ -2029,43 +2720,49 @@ class DebuggerService(Service.Service): 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): @@ -2076,7 +2773,7 @@ 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? @@ -2099,59 +2796,57 @@ class DebuggerOptionsPanel(wx.Panel): 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") @@ -2163,25 +2858,25 @@ class CommandPropertiesDialog(wx.Dialog): 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) @@ -2191,24 +2886,24 @@ class CommandPropertiesDialog(wx.Dialog): 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() @@ -2220,14 +2915,15 @@ class CommandPropertiesDialog(wx.Dialog): 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) @@ -2240,7 +2936,14 @@ class CommandPropertiesDialog(wx.Dialog): 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() @@ -2252,22 +2955,22 @@ class CommandPropertiesDialog(wx.Dialog): 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 @@ -2279,15 +2982,15 @@ class CommandPropertiesDialog(wx.Dialog): 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) @@ -2296,12 +2999,16 @@ class CommandPropertiesDialog(wx.Dialog): 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) @@ -2309,9 +3016,9 @@ class CommandPropertiesDialog(wx.Dialog): dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: self._startEntry.SetValue(dlg.GetPath()) - - dlg.Destroy() - + + dlg.Destroy() + def EvtListBox(self, event): if event.GetString(): @@ -2319,14 +3026,11 @@ class CommandPropertiesDialog(wx.Dialog): 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() @@ -2336,15 +3040,23 @@ class CommandPropertiesDialog(wx.Dialog): 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 = [] @@ -2365,25 +3077,25 @@ class CommandPropertiesDialog(wx.Dialog): 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 @@ -2400,7 +3112,7 @@ def getBreakData(): \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()) @@ -2426,7 +3138,7 @@ def getClearOutputData(): \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()) @@ -2452,7 +3164,7 @@ def getCloseData(): \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()) @@ -2477,7 +3189,7 @@ def getContinueData(): \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()) @@ -2500,7 +3212,7 @@ gK\x06\x00 \xa5=k\x00\x00\xb0\xb2]\xd4?5f\xb1\xdb\xaf\xc6\xa2\xcb\xa8\xf0?\ \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()) @@ -2522,7 +3234,7 @@ def getStepInData(): \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()) @@ -2542,7 +3254,7 @@ def getStopData(): \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()) @@ -2565,7 +3277,7 @@ def getStepReturnData(): \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()) @@ -2576,7 +3288,7 @@ def getStepReturnImage(): def getStepReturnIcon(): return wx.IconFromBitmap(getStepReturnBitmap()) - + #---------------------------------------------------------------------- def getAddWatchData(): return \ @@ -2587,7 +3299,7 @@ def getAddWatchData(): \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()) @@ -2598,7 +3310,7 @@ def getAddWatchImage(): def getAddWatchIcon(): return wx.IconFromBitmap(getAddWatchBitmap()) - + #---------------------------------------------------------------------- def getRunningManData(): return \ @@ -2619,7 +3331,7 @@ CF\xe2t\xef\x1b>\x1f\x8c3Q\xf0\x11\xd3p\xa2yf\x1a\xbc\xcb\n\xdee\x85\xdd>\ \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()) @@ -2655,7 +3367,7 @@ f?*4\xd1\xf6\xa2\x0f\x80\x93\xf4\x8e\xe1\xb8\xf2\xf1\xb5\x18\x9cH(\x80\xe4bT\ \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()) diff --git a/wxPython/samples/ide/activegrid/tool/ExtensionService.py b/wxPython/samples/ide/activegrid/tool/ExtensionService.py index 32e4fcffeb..535c46eee8 100644 --- a/wxPython/samples/ide/activegrid/tool/ExtensionService.py +++ b/wxPython/samples/ide/activegrid/tool/ExtensionService.py @@ -6,7 +6,7 @@ # # Created: 5/23/05 # CVS-ID: $ID:$ -# Copyright: (c) 2005 ActiveGrid, Inc. +# Copyright: (c) 2005-2006 ActiveGrid, Inc. # License: wxWindows License #---------------------------------------------------------------------------- @@ -17,27 +17,22 @@ import ProjectEditor 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 @@ -48,7 +43,7 @@ class Extension: self.commandPostArgs = '' self.fileExt = None self.opOnSelectedFile = True - + class ExtensionService(wx.lib.pydocview.DocService): @@ -60,8 +55,8 @@ class ExtensionService(wx.lib.pydocview.DocService): def __getExtensionKeyName(extensionName): return "%s/%s" % (ExtensionService.EXTENSIONS_KEY, extensionName) - - + + __getExtensionKeyName = staticmethod(__getExtensionKeyName) @@ -79,7 +74,7 @@ class ExtensionService(wx.lib.pydocview.DocService): cont, value, index = config.GetNextEntry(index) finally: config.SetPath(path) - + for extensionName in extensionNames: extensionData = config.Read(self.__getExtensionKeyName(extensionName)) if extensionData: @@ -112,10 +107,10 @@ class ExtensionService(wx.lib.pydocview.DocService): 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() @@ -192,7 +187,7 @@ class ExtensionService(wx.lib.pydocview.DocService): if extension.commandPostArgs: cmds.append(extension.commandPostArgs) os.spawnv(os.P_NOWAIT, extension.command, cmds) - + else: cmd = extension.command if extension.commandPreArgs: @@ -207,24 +202,24 @@ class ExtensionService(wx.lib.pydocview.DocService): 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) @@ -243,21 +238,21 @@ class ExtensionOptionsPanel(wx.Panel): 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) @@ -276,26 +271,26 @@ class ExtensionOptionsPanel(wx.Panel): 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) @@ -306,8 +301,8 @@ class ExtensionOptionsPanel(wx.Panel): self.OnListBoxSelect() self.Layout() - - parent.AddPage(self, _("External Tools")) + + parent.AddPage(self, _("External Tools")) def OnOK(self, optionsDialog): @@ -323,7 +318,7 @@ class ExtensionOptionsPanel(wx.Panel): msgTitle, wx.OK | wx.ICON_INFORMATION, self.GetParent()) - + def PopulateItems(self): extensionsService = wx.GetApp().GetService(ExtensionService) @@ -335,7 +330,7 @@ class ExtensionOptionsPanel(wx.Panel): self._currentItem = None self._currentItemIndex = -1 return len(self._extensions) - + def OnListBoxSelect(self, event=None): self.SaveCurrentItem() @@ -370,7 +365,7 @@ class ExtensionOptionsPanel(wx.Panel): else: extension.fileExt = fileExt.split(',') extension.opOnSelectedFile = self._selFileCtrl.GetValue() - + def LoadItem(self, extension): if extension: @@ -401,8 +396,8 @@ class ExtensionOptionsPanel(wx.Panel): self._fileExtTextCtrl.SetValue('') self._selFileCtrl.SetValue(True) self._extDetailPanel.Enable(False) - - + + def OnAdd(self, event): self.SaveCurrentItem() name = _("Untitled") @@ -417,11 +412,11 @@ class ExtensionOptionsPanel(wx.Panel): 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) diff --git a/wxPython/samples/ide/activegrid/tool/FindInDirService.py b/wxPython/samples/ide/activegrid/tool/FindInDirService.py index 1fd557b002..da4ed91e19 100644 --- a/wxPython/samples/ide/activegrid/tool/FindInDirService.py +++ b/wxPython/samples/ide/activegrid/tool/FindInDirService.py @@ -27,6 +27,7 @@ _ = wx.GetTranslation #---------------------------------------------------------------------------- FILENAME_MARKER = _("Found in file: ") PROJECT_MARKER = _("Searching project: ") +FILE_MARKER = _("Searching file: ") FIND_MATCHDIR = "FindMatchDir" FIND_MATCHDIRSUBFOLDERS = "FindMatchDirSubfolders" @@ -39,6 +40,7 @@ class FindInDirService(FindService.FindService): #---------------------------------------------------------------------------- # 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 @@ -47,29 +49,39 @@ class FindInDirService(FindService.FindService): 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) @@ -77,7 +89,14 @@ class FindInDirService(FindService.FindService): 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) @@ -90,7 +109,7 @@ class FindInDirService(FindService.FindService): 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)) @@ -105,7 +124,7 @@ class FindInDirService(FindService.FindService): 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() @@ -126,10 +145,10 @@ class FindInDirService(FindService.FindService): 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: @@ -192,13 +211,13 @@ class FindInDirService(FindService.FindService): 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() @@ -206,52 +225,23 @@ class FindInDirService(FindService.FindService): 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() @@ -259,7 +249,7 @@ class FindInDirService(FindService.FindService): 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) @@ -267,27 +257,186 @@ class FindInDirService(FindService.FindService): 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)) @@ -336,97 +485,80 @@ class FindInDirService(FindService.FindService): 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: @@ -435,7 +567,10 @@ class FindInDirService(FindService.FindService): 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) @@ -464,5 +599,3 @@ class FindInDirService(FindService.FindService): # 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) - - diff --git a/wxPython/samples/ide/activegrid/tool/HtmlEditor.py b/wxPython/samples/ide/activegrid/tool/HtmlEditor.py index bcdbe7ed7b..024a0b13f2 100644 --- a/wxPython/samples/ide/activegrid/tool/HtmlEditor.py +++ b/wxPython/samples/ide/activegrid/tool/HtmlEditor.py @@ -119,7 +119,7 @@ class HtmlCtrl(CodeEditor.CodeCtrl): 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): @@ -156,7 +156,7 @@ class HtmlCtrl(CodeEditor.CodeCtrl): 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): diff --git a/wxPython/samples/ide/activegrid/tool/IDE.py b/wxPython/samples/ide/activegrid/tool/IDE.py index a9c6dcc77b..b1567b4fd2 100644 --- a/wxPython/samples/ide/activegrid/tool/IDE.py +++ b/wxPython/samples/ide/activegrid/tool/IDE.py @@ -18,9 +18,17 @@ import wx.grid 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 #---------------------------------------------------------------------------- @@ -44,6 +52,19 @@ def isInArgs(argname, argv): 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 #---------------------------------------------------------------------------- @@ -55,9 +76,13 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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" @@ -81,6 +106,8 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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) @@ -120,20 +147,22 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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 @@ -158,9 +187,11 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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"), "*.*", @@ -184,6 +215,7 @@ class IDEApplication(wx.lib.pydocview.DocApp): _("Deployment View"), XmlEditor.XmlDocument, XmlEditor.XmlView, + wx.lib.docview.TEMPLATE_INVISIBLE, icon = DeploymentService.getDPLIcon()) docManager.AssociateTemplate(dplTemplate) @@ -199,6 +231,20 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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", @@ -224,7 +270,6 @@ class IDEApplication(wx.lib.pydocview.DocApp): # XmlEditor.XmlView, LayoutEditor.LayoutEditorDocument, LayoutEditor.LayoutEditorView, - wx.lib.docview.TEMPLATE_NO_CREATE, icon = LayoutEditor.getLytIcon()) docManager.AssociateTemplate(layoutTemplate) @@ -262,6 +307,7 @@ class IDEApplication(wx.lib.pydocview.DocApp): _("Process View"), ProcessModelEditor.ProcessModelDocument, ProcessModelEditor.ProcessModelView, + wx.lib.docview.TEMPLATE_NO_CREATE, icon = ProcessModelEditor.getProcessModelIcon()) docManager.AssociateTemplate(processModelTemplate) @@ -274,6 +320,7 @@ class IDEApplication(wx.lib.pydocview.DocApp): _("Project View"), ProjectEditor.ProjectDocument, ProjectEditor.ProjectView, + wx.lib.docview.TEMPLATE_NO_CREATE, icon = ProjectEditor.getProjectIcon()) docManager.AssociateTemplate(projectTemplate) @@ -302,6 +349,20 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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"), @@ -317,18 +378,18 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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"), @@ -343,33 +404,6 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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", @@ -379,9 +413,11 @@ class IDEApplication(wx.lib.pydocview.DocApp): _("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", @@ -389,10 +425,10 @@ class IDEApplication(wx.lib.pydocview.DocApp): _(".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, @@ -407,19 +443,22 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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", @@ -432,6 +471,7 @@ class IDEApplication(wx.lib.pydocview.DocApp): icon = ProcessModelEditor.getProcessModelIcon()) docManager.AssociateTemplate(bpelTemplate) + if not ACTIVEGRID_BASE_IDE: dataModelChildTemplate = wx.lib.pydocview.ChildDocTemplate(docManager, _("Schema"), "*.none", @@ -453,8 +493,8 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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()) @@ -462,7 +502,7 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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()) @@ -477,6 +517,8 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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: @@ -515,24 +557,26 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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) @@ -554,26 +598,36 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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()) @@ -582,9 +636,9 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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) @@ -599,12 +653,10 @@ class IDEApplication(wx.lib.pydocview.DocApp): # 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): @@ -613,22 +665,50 @@ class IDEApplication(wx.lib.pydocview.DocApp): 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): @@ -2129,723 +2209,720 @@ u\x9d\xe9\xa9\xeaTu\xb2\xd3\xe96\x9a\xcdN\xa7\xc79\x17R\xeaq\x85R\xe28N!\x9f\ \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\xf1B\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\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/\xbap\t\x00\xce\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\ -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\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/;(\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\ -af\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\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)NX\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\xb67H0\ -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@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\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\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\'\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?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\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_X9}\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\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=}\ +\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\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\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\xbbi\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\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\ +\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 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\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?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\xd10\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()) @@ -4330,562 +4407,563 @@ u\x9d\xe9\xa9\xeaTu\xb2\xd3\xe96\x9a\xcdN\xa7\xc79\x17R\xeaq\x85R\xe28N!\x9f\ \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>\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\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\x1e55U\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\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\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\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\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\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*\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;;\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\xfcq\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<\xa3G\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\xd1L6cyE\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\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$\ +\x9by,^\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\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()) diff --git a/wxPython/samples/ide/activegrid/tool/MarkerService.py b/wxPython/samples/ide/activegrid/tool/MarkerService.py index 54375dd482..5fd24b5c0f 100644 --- a/wxPython/samples/ide/activegrid/tool/MarkerService.py +++ b/wxPython/samples/ide/activegrid/tool/MarkerService.py @@ -36,16 +36,16 @@ class MarkerService(wx.lib.pydocview.DocService): 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) diff --git a/wxPython/samples/ide/activegrid/tool/MessageService.py b/wxPython/samples/ide/activegrid/tool/MessageService.py index 1dd15ebd88..4765a40ead 100644 --- a/wxPython/samples/ide/activegrid/tool/MessageService.py +++ b/wxPython/samples/ide/activegrid/tool/MessageService.py @@ -124,6 +124,7 @@ class MessageView(Service.ServiceView): def AddLines(self, text): + self.GetControl().SetCurrentPos(self.GetControl().GetTextLength()) self.GetControl().SetReadOnly(False) self.GetControl().AddText(text) self.GetControl().SetReadOnly(True) diff --git a/wxPython/samples/ide/activegrid/tool/OutlineService.py b/wxPython/samples/ide/activegrid/tool/OutlineService.py index 75fc67e0d2..289d4315cb 100644 --- a/wxPython/samples/ide/activegrid/tool/OutlineService.py +++ b/wxPython/samples/ide/activegrid/tool/OutlineService.py @@ -147,7 +147,11 @@ class OutlineView(Service.ServiceView): return treeCtrl = self.GetControl() + parentItem = treeCtrl.GetRootItem() + if not parentItem: + return + if expanded[0] != treeCtrl.GetItemText(parentItem): return @@ -157,8 +161,7 @@ class OutlineView(Service.ServiceView): treeCtrl.Expand(child) (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie) - if parentItem: - treeCtrl.EnsureVisible(parentItem) + treeCtrl.EnsureVisible(parentItem) class OutlineTreeCtrl(wx.TreeCtrl): @@ -267,7 +270,7 @@ 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 diff --git a/wxPython/samples/ide/activegrid/tool/PHPDebugger.py b/wxPython/samples/ide/activegrid/tool/PHPDebugger.py new file mode 100644 index 0000000000..8dd0d17cda --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/PHPDebugger.py @@ -0,0 +1,2105 @@ +#--------------------------------------------------------------------------- +# 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 + ": \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 = "<%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 = "" + + 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 = "" + + 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::""'. + # + 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::""::{}' + # + 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 diff --git a/wxPython/samples/ide/activegrid/tool/PHPEditor.py b/wxPython/samples/ide/activegrid/tool/PHPEditor.py index 5e78d118f6..840ad1fc44 100644 --- a/wxPython/samples/ide/activegrid/tool/PHPEditor.py +++ b/wxPython/samples/ide/activegrid/tool/PHPEditor.py @@ -17,6 +17,10 @@ import CodeEditor 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): @@ -153,18 +157,52 @@ class PHPCtrl(CodeEditor.CodeCtrl): 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): @@ -216,8 +254,110 @@ class PHPCtrl(CodeEditor.CodeCtrl): 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() diff --git a/wxPython/samples/ide/activegrid/tool/PerlEditor.py b/wxPython/samples/ide/activegrid/tool/PerlEditor.py index bc7c48f0c1..544d0fbd53 100644 --- a/wxPython/samples/ide/activegrid/tool/PerlEditor.py +++ b/wxPython/samples/ide/activegrid/tool/PerlEditor.py @@ -73,7 +73,7 @@ class PerlCtrl(CodeEditor.CodeCtrl): 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): @@ -130,7 +130,7 @@ class PerlCtrl(CodeEditor.CodeCtrl): 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): diff --git a/wxPython/samples/ide/activegrid/tool/ProjectEditor.py b/wxPython/samples/ide/activegrid/tool/ProjectEditor.py index af95b890d3..d826156d34 100644 --- a/wxPython/samples/ide/activegrid/tool/ProjectEditor.py +++ b/wxPython/samples/ide/activegrid/tool/ProjectEditor.py @@ -6,7 +6,7 @@ # # Created: 8/15/03 # CVS-ID: $Id$ -# Copyright: (c) 2003, 2004, 2005 ActiveGrid, Inc. +# Copyright: (c) 2003-2006 ActiveGrid, Inc. # License: wxWindows License #---------------------------------------------------------------------------- @@ -25,6 +25,7 @@ import time 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 @@ -36,14 +37,19 @@ if not ACTIVEGRID_BASE_IDE: 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 @@ -61,6 +67,10 @@ SPACE = 10 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. @@ -72,70 +82,85 @@ if wx.Platform == "__WXMAC__": 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() @@ -202,8 +227,82 @@ if not ACTIVEGRID_BASE_IDE: # 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) @@ -482,24 +581,22 @@ class ProjectDocument(wx.lib.docview.Document): 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) @@ -523,6 +620,9 @@ class ProjectDocument(wx.lib.docview.Document): # 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) @@ -530,19 +630,27 @@ class ProjectDocument(wx.lib.docview.Document): # 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: @@ -551,24 +659,36 @@ class ProjectDocument(wx.lib.docview.Document): 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() @@ -576,7 +696,7 @@ class ProjectDocument(wx.lib.docview.Document): 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) @@ -589,31 +709,81 @@ class ProjectDocument(wx.lib.docview.Document): 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): @@ -664,11 +834,6 @@ class ProjectDocument(wx.lib.docview.Document): 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 @@ -680,7 +845,19 @@ class ProjectDocument(wx.lib.docview.Document): 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 @@ -709,199 +886,117 @@ class ProjectDocument(wx.lib.docview.Document): 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): @@ -918,10 +1013,10 @@ 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() @@ -930,7 +1025,7 @@ class NewProjectWizard(Wizard.BaseWizard): 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() @@ -959,7 +1054,7 @@ class NewProjectWizard(Wizard.BaseWizard): 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)) @@ -1473,8 +1568,6 @@ class ProjectView(wx.lib.docview.View): 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: @@ -1484,7 +1577,7 @@ class ProjectView(wx.lib.docview.View): 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) @@ -1503,16 +1596,17 @@ class ProjectView(wx.lib.docview.View): 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 @@ -1625,9 +1719,11 @@ class ProjectView(wx.lib.docview.View): 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 @@ -1638,7 +1734,8 @@ class ProjectView(wx.lib.docview.View): 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": @@ -1648,54 +1745,56 @@ class ProjectView(wx.lib.docview.View): 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": @@ -1705,17 +1804,19 @@ class ProjectView(wx.lib.docview.View): 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": @@ -1724,10 +1825,12 @@ class ProjectView(wx.lib.docview.View): 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": @@ -1736,14 +1839,16 @@ class ProjectView(wx.lib.docview.View): 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 @@ -1776,9 +1881,23 @@ class ProjectView(wx.lib.docview.View): 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: @@ -1881,6 +2000,19 @@ class ProjectView(wx.lib.docview.View): 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: @@ -1979,61 +2111,70 @@ class ProjectView(wx.lib.docview.View): 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): @@ -2053,6 +2194,11 @@ class ProjectView(wx.lib.docview.View): 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 """ @@ -2070,25 +2216,32 @@ class ProjectView(wx.lib.docview.View): 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 @@ -2190,7 +2343,7 @@ class ProjectView(wx.lib.docview.View): 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 = _("*.*") @@ -2254,7 +2407,7 @@ class ProjectView(wx.lib.docview.View): 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.")) @@ -2301,63 +2454,54 @@ class ProjectView(wx.lib.docview.View): 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): @@ -2385,23 +2529,19 @@ class ProjectView(wx.lib.docview.View): 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() @@ -2444,17 +2584,39 @@ class ProjectView(wx.lib.docview.View): 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) @@ -2467,21 +2629,35 @@ class ProjectView(wx.lib.docview.View): 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() @@ -2490,16 +2666,19 @@ class ProjectView(wx.lib.docview.View): 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) @@ -2512,26 +2691,25 @@ class ProjectView(wx.lib.docview.View): "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 @@ -2660,7 +2838,12 @@ class ProjectView(wx.lib.docview.View): 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() @@ -2705,8 +2888,10 @@ class ProjectView(wx.lib.docview.View): 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() @@ -2813,11 +2998,7 @@ class ProjectView(wx.lib.docview.View): 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): @@ -3056,18 +3237,20 @@ class ProjectPropertiesDialog(wx.Dialog): 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) @@ -3092,13 +3275,13 @@ class ProjectOptionsPanel(wx.Panel): 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) @@ -3125,7 +3308,7 @@ class ProjectOptionsPanel(wx.Panel): 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()) @@ -3140,7 +3323,11 @@ class ProjectService(Service.Service): #---------------------------------------------------------------------------- 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() @@ -3163,6 +3350,7 @@ class ProjectService(Service.Service): self._runHandlers = [] self._suppressOpenProjectMessages = False self._logicalViewDefaults = [] + self._logicalViewOpenDefaults = [] self._fileTypeDefaults = [] self._nameDefaults = [] self._mapToProject = dict() @@ -3230,7 +3418,7 @@ class ProjectService(Service.Service): 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): @@ -3309,6 +3497,12 @@ class ProjectService(Service.Service): 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 #---------------------------------------------------------------------------- @@ -3324,6 +3518,18 @@ class ProjectService(Service.Service): 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 #---------------------------------------------------------------------------- @@ -3406,9 +3612,21 @@ class ProjectService(Service.Service): 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 @@ -3430,27 +3648,31 @@ class ProjectService(Service.Service): 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: @@ -3459,7 +3681,7 @@ class ProjectService(Service.Service): 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: @@ -3537,14 +3759,19 @@ class ProjectService(Service.Service): 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 + '"') @@ -3623,13 +3850,24 @@ class ProjectService(Service.Service): 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 @@ -3649,10 +3887,13 @@ class ProjectService(Service.Service): 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 @@ -3664,6 +3905,38 @@ class ProjectService(Service.Service): 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 #---------------------------------------------------------------------------- diff --git a/wxPython/samples/ide/activegrid/tool/PythonEditor.py b/wxPython/samples/ide/activegrid/tool/PythonEditor.py index ea5f6b71d1..abde6fd97b 100644 --- a/wxPython/samples/ide/activegrid/tool/PythonEditor.py +++ b/wxPython/samples/ide/activegrid/tool/PythonEditor.py @@ -25,6 +25,7 @@ import keyword # for GetAutoCompleteKeywordList import sys # for GetAutoCompleteKeywordList import MessageService # for OnCheckCode import OutlineService +import FindInDirService from UICommon import CaseInsensitiveCompare try: import checker # for pychecker @@ -143,11 +144,13 @@ class PythonView(CodeEditor.CodeView): # 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): @@ -369,8 +372,42 @@ class PythonCtrl(CodeEditor.CodeCtrl): 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): @@ -567,7 +604,7 @@ class PythonOptionsPanel(wx.Panel): 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")) @@ -577,7 +614,7 @@ class PythonOptionsPanel(wx.Panel): 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: diff --git a/wxPython/samples/ide/activegrid/tool/STCTextEditor.py b/wxPython/samples/ide/activegrid/tool/STCTextEditor.py index fd9b84a91a..f3ab9508e2 100644 --- a/wxPython/samples/ide/activegrid/tool/STCTextEditor.py +++ b/wxPython/samples/ide/activegrid/tool/STCTextEditor.py @@ -6,7 +6,7 @@ # # Created: 8/10/03 # CVS-ID: $Id$ -# Copyright: (c) 2003-2005 ActiveGrid, Inc. +# Copyright: (c) 2003-2006 ActiveGrid, Inc. # License: wxWindows License #---------------------------------------------------------------------------- @@ -47,9 +47,15 @@ TEXT_STATUS_BAR_ID = wx.NewId() 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 @@ -57,24 +63,31 @@ class TextDocument(wx.lib.docview.Document): 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 @@ -142,6 +155,8 @@ class TextView(wx.lib.docview.View): 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) @@ -149,6 +164,10 @@ class TextView(wx.lib.docview.View): 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) @@ -161,6 +180,9 @@ class TextView(wx.lib.docview.View): 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": @@ -571,9 +593,11 @@ class TextView(wx.lib.docview.View): 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. @@ -813,11 +837,12 @@ class TextStatusBar(wx.StatusBar): 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() @@ -854,6 +879,9 @@ class TextOptionsPanel(wx.Panel): 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)) @@ -874,6 +902,8 @@ class TextOptionsPanel(wx.Panel): 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) @@ -947,6 +977,9 @@ class TextOptionsPanel(wx.Panel): 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()) @@ -1062,13 +1095,15 @@ class TextCtrl(wx.stc.StyledTextCtrl): 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 @@ -1237,6 +1272,17 @@ class TextCtrl(wx.stc.StyledTextCtrl): 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 diff --git a/wxPython/samples/ide/activegrid/tool/SVNService.py b/wxPython/samples/ide/activegrid/tool/SVNService.py index 785acf3866..c5e2af652f 100644 --- a/wxPython/samples/ide/activegrid/tool/SVNService.py +++ b/wxPython/samples/ide/activegrid/tool/SVNService.py @@ -324,526 +324,529 @@ class SVNService(wx.lib.pydocview.DocService): 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): diff --git a/wxPython/samples/ide/activegrid/tool/UICommon.py b/wxPython/samples/ide/activegrid/tool/UICommon.py index cb16f7bbf5..a70cdf3bc4 100644 --- a/wxPython/samples/ide/activegrid/tool/UICommon.py +++ b/wxPython/samples/ide/activegrid/tool/UICommon.py @@ -6,7 +6,7 @@ # # Created: 3/10/05 # CVS-ID: $Id$ -# Copyright: (c) 2005 ActiveGrid, Inc. +# Copyright: (c) 2005-2006 ActiveGrid, Inc. # License: wxWindows License #---------------------------------------------------------------------------- @@ -15,12 +15,14 @@ import os.path 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 = [] @@ -61,8 +63,8 @@ def CreateDirectoryControl( parent, fileLabel=_("File Name:"), dirLabel=_("Direc 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() @@ -85,12 +87,18 @@ def CreateDirectoryControl( parent, fileLabel=_("File Name:"), dirLabel=_("Direc 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 @@ -98,49 +106,78 @@ def CreateDirectoryControl( parent, fileLabel=_("File Name:"), dirLabel=_("Direc 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: @@ -188,8 +225,8 @@ def CreateDirectoryOnlyControl( parent, dirLabel=_("Location:"), startingDirecto 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: @@ -221,6 +258,9 @@ def CreateDirectoryOnlyControl( parent, dirLabel=_("Location:"), startingDirecto 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 @@ -241,17 +281,25 @@ def CreateNameOnlyControl( parent, fileLabel, startingName="", startingDirectory 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): @@ -280,6 +328,29 @@ def CreateNameOnlyControl( parent, fileLabel, startingName="", startingDirectory 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) @@ -330,6 +401,16 @@ def GetPythonExecPath(): 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 @@ -362,16 +443,62 @@ def CaseInsensitiveCompare(s1, s2): 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 #---------------------------------------------------------------------------- @@ -435,7 +562,8 @@ def GetAppDocMgrForDoc(doc): 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: @@ -449,3 +577,159 @@ def GetAppInfoLanguage(doc=None): 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) diff --git a/wxPython/samples/ide/activegrid/tool/Wizard.py b/wxPython/samples/ide/activegrid/tool/Wizard.py index af2b9dad1b..ab70874adf 100644 --- a/wxPython/samples/ide/activegrid/tool/Wizard.py +++ b/wxPython/samples/ide/activegrid/tool/Wizard.py @@ -13,7 +13,12 @@ import wx 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 #---------------------------------------------------------------------------- @@ -22,9 +27,14 @@ class BaseWizard(wx.wizard.Wizard): 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() @@ -34,12 +44,94 @@ class BaseWizard(wx.wizard.Wizard): 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 @@ -48,6 +140,9 @@ class TitledWizardPage(wx.wizard.PyWizardPage): 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) @@ -291,3 +386,2042 @@ def getWizardImage(): 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\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\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\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\xb7\xaeY8\x0b# B\xd5\x91=y8)\xa5\xa4\xd5\x9b\ +\xb7\xceo|\xf8\xf0\xd5\xedF3\x00\xbcg\xcb\x89mkg\xee\xbc\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\ +\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\xc7tofE\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\ +\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[\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\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\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?\ +\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\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\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\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\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\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\'\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\x02g\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\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~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[Dq\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\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\ +\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?\ +\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;=\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\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\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\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\\\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*\xa91H\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\ +\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\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\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\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\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\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.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 + +#---------------------------------------------------------------------- + diff --git a/wxPython/samples/ide/activegrid/tool/XmlEditor.py b/wxPython/samples/ide/activegrid/tool/XmlEditor.py index 66fd95cf1e..4a6ed85ad1 100644 --- a/wxPython/samples/ide/activegrid/tool/XmlEditor.py +++ b/wxPython/samples/ide/activegrid/tool/XmlEditor.py @@ -78,7 +78,7 @@ class XmlCtrl(CodeEditor.CodeCtrl): 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): @@ -115,7 +115,7 @@ class XmlCtrl(CodeEditor.CodeCtrl): 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): diff --git a/wxPython/samples/ide/activegrid/tool/bmp_source/activegrid.ico b/wxPython/samples/ide/activegrid/tool/bmp_source/activegrid.ico new file mode 100644 index 0000000000000000000000000000000000000000..fb15dfe6b47a28e3aba0370cbfcca021c38be34e GIT binary patch literal 174246 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%0A(W6LAgfjT^AHG|vduF7SNW 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 W%E9j%!qW}({_Xs2f3Cyvng0jXsJ{UK literal 0 HcmV?d00001 diff --git a/wxPython/samples/ide/activegrid/tool/data/tips.txt b/wxPython/samples/ide/activegrid/tool/data/tips.txt index 1d2008f10a..7992a831f0 100644 --- a/wxPython/samples/ide/activegrid/tool/data/tips.txt +++ b/wxPython/samples/ide/activegrid/tool/data/tips.txt @@ -1,7 +1,7 @@ +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 diff --git a/wxPython/samples/ide/activegrid/tool/process.py b/wxPython/samples/ide/activegrid/tool/process.py index a521f0cf50..1de68e1bc9 100644 --- a/wxPython/samples/ide/activegrid/tool/process.py +++ b/wxPython/samples/ide/activegrid/tool/process.py @@ -407,7 +407,13 @@ _SaferCreateProcess(appName=%r, 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, diff --git a/wxPython/samples/ide/activegrid/tool/project.py b/wxPython/samples/ide/activegrid/tool/project.py index babdadea42..6b8035f335 100644 --- a/wxPython/samples/ide/activegrid/tool/project.py +++ b/wxPython/samples/ide/activegrid/tool/project.py @@ -14,7 +14,16 @@ import copy 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 @@ -27,64 +36,6 @@ if not ACTIVEGRID_BASE_IDE: 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 @@ -93,7 +44,7 @@ def save(fileObject, project, productionDeployment=False): 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",) } @@ -107,9 +58,15 @@ class BaseProject(object): 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() @@ -117,18 +74,13 @@ class BaseProject(object): 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 @@ -138,7 +90,7 @@ class BaseProject(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): @@ -150,7 +102,7 @@ class BaseProject(object): for file in self._files: if file.filePath == filePath: return file - + return None @@ -160,6 +112,10 @@ class BaseProject(object): filePaths = property(_GetFilePaths) + def _GetProjectFiles(self): + return self._files + projectFiles = property(_GetProjectFiles) + def _GetLogicalFolders(self): folders = [] @@ -170,7 +126,7 @@ class BaseProject(object): logicalFolders = property(_GetLogicalFolders) - + def _GetPhysicalFolders(self): physicalFolders = [] @@ -189,11 +145,11 @@ class BaseProject(object): return self._homeDir else: return self._projectDir - + def _SetHomeDir(self, parentPath): self._homeDir = parentPath - + def _IsDefaultHomeDir(self): return (self._homeDir == None) @@ -212,7 +168,7 @@ class BaseProject(object): if relFolder and relFolder not in relativeFolders: relativeFolders.append(relFolder) return relativeFolders - + def AbsToRelativePath(self): for file in self._files: @@ -224,6 +180,33 @@ class BaseProject(object): 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 #---------------------------------------------------------------------------- @@ -231,7 +214,7 @@ class BaseProject(object): def fullPath(self, fileName): fileName = super(BaseProject, self).fullPath(fileName) - + if os.path.isabs(fileName): absPath = fileName elif self.homeDir: @@ -242,7 +225,7 @@ class BaseProject(object): 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): @@ -256,8 +239,8 @@ class BaseProject(object): if not xformdir: xformdir = self.homeDir return xformdir - - + + def setRefs(self, files): self._files = files @@ -295,16 +278,17 @@ if ACTIVEGRID_BASE_IDE: 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 @@ -316,35 +300,44 @@ class ProjectFile(object): 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 @@ -356,7 +349,7 @@ class ProjectFile(object): physicalFolder = property(_GetPhysicalFolder) - + def GetRelativeFolder(self, parentPath): parentPathLen = len(parentPath) @@ -364,7 +357,7 @@ class ProjectFile(object): 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. @@ -375,7 +368,7 @@ class ProjectFile(object): """ 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. @@ -385,8 +378,8 @@ class ProjectFile(object): 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 #---------------------------------------------------------------------------- @@ -399,21 +392,29 @@ class ProjectFile(object): 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) @@ -458,33 +459,39 @@ class ProjectFile(object): 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: @@ -494,25 +501,27 @@ class ProjectFile(object): 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) @@ -543,7 +552,63 @@ class Project_10: 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 + diff --git a/wxPython/samples/ide/activegrid/util/aglogging.py b/wxPython/samples/ide/activegrid/util/aglogging.py index 227e9726fa..17ab688e1c 100644 --- a/wxPython/samples/ide/activegrid/util/aglogging.py +++ b/wxPython/samples/ide/activegrid/util/aglogging.py @@ -19,6 +19,7 @@ import logging.config 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 @@ -27,38 +28,47 @@ LEVEL_INFO = logging.INFO 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): @@ -181,21 +191,18 @@ def addExceptionInfo(e, key, value): 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: diff --git a/wxPython/samples/ide/activegrid/util/appdirs.py b/wxPython/samples/ide/activegrid/util/appdirs.py index 7b73d5c4f4..d3e038b04c 100644 --- a/wxPython/samples/ide/activegrid/util/appdirs.py +++ b/wxPython/samples/ide/activegrid/util/appdirs.py @@ -2,7 +2,7 @@ # 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$ @@ -10,50 +10,117 @@ # 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() diff --git a/wxPython/samples/ide/activegrid/util/datetimeparser.py b/wxPython/samples/ide/activegrid/util/datetimeparser.py new file mode 100644 index 0000000000..3731658c98 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/datetimeparser.py @@ -0,0 +1,118 @@ +#---------------------------------------------------------------------------- +# 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 diff --git a/wxPython/samples/ide/activegrid/util/fileutils.py b/wxPython/samples/ide/activegrid/util/fileutils.py index 2a75997aea..ca5ee1fb8f 100644 --- a/wxPython/samples/ide/activegrid/util/fileutils.py +++ b/wxPython/samples/ide/activegrid/util/fileutils.py @@ -19,6 +19,7 @@ import zipfile 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 @@ -31,6 +32,65 @@ fileutilsLogger = logging.getLogger("activegrid.util.fileutils") 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) @@ -44,7 +104,7 @@ def createFile(filename, mode='w'): 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 @@ -62,6 +122,9 @@ def compareFiles(file1, file2): 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): @@ -81,7 +144,10 @@ def compareFiles(file1, file2): 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("${") @@ -97,16 +163,19 @@ def expandVars(value): 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): @@ -148,22 +217,21 @@ def convertSourcePath(path, to, otherdir=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): @@ -311,6 +379,21 @@ def remove(file): 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") diff --git a/wxPython/samples/ide/activegrid/util/objutils.py b/wxPython/samples/ide/activegrid/util/objutils.py index 79cb02919e..96e96df38c 100644 --- a/wxPython/samples/ide/activegrid/util/objutils.py +++ b/wxPython/samples/ide/activegrid/util/objutils.py @@ -16,7 +16,8 @@ import sys 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 * @@ -65,6 +66,18 @@ def setStaticAttr(obj, attr, value): 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('.') @@ -114,15 +127,22 @@ def newInstance(className, objargs=None): 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) @@ -135,23 +155,35 @@ def newInstance(className, objargs=None): 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 "" +## 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") @@ -173,33 +205,31 @@ def toDiffableRepr(value, exclude=None): 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 "" - 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 "" +## exclude.append(value) + s = toDiffableRepr(value, maxLevel) ds = "" i = s.find(" at 0x") start = 0 diff --git a/wxPython/samples/ide/activegrid/util/parser.py b/wxPython/samples/ide/activegrid/util/parser.py new file mode 100644 index 0000000000..364099b990 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/parser.py @@ -0,0 +1,380 @@ +#---------------------------------------------------------------------------- +# 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 diff --git a/wxPython/samples/ide/activegrid/util/strutils.py b/wxPython/samples/ide/activegrid/util/strutils.py index b79bd2a474..f70d03ee54 100644 --- a/wxPython/samples/ide/activegrid/util/strutils.py +++ b/wxPython/samples/ide/activegrid/util/strutils.py @@ -21,3 +21,91 @@ def caseInsensitiveCompare(s1, s2): 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 diff --git a/wxPython/samples/ide/activegrid/util/sysutils.py b/wxPython/samples/ide/activegrid/util/sysutils.py index eb8bbf08f1..d7e68bd685 100644 --- a/wxPython/samples/ide/activegrid/util/sysutils.py +++ b/wxPython/samples/ide/activegrid/util/sysutils.py @@ -12,6 +12,7 @@ import sys import os +import time # this will be set to true in IDE.py when we are running release builds. isRelease = False @@ -26,6 +27,12 @@ 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()) @@ -39,7 +46,16 @@ def setRelease(value): 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 @@ -85,3 +101,16 @@ def getCommandNameForExecPath(execPath): 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() diff --git a/wxPython/samples/ide/activegrid/util/utillang.py b/wxPython/samples/ide/activegrid/util/utillang.py new file mode 100644 index 0000000000..fad7e20678 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/utillang.py @@ -0,0 +1,146 @@ +#---------------------------------------------------------------------------- +# 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) diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshaller.py b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py index 6a74d2c486..ee758350d7 100644 --- a/wxPython/samples/ide/activegrid/util/xmlmarshaller.py +++ b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py @@ -2,7 +2,7 @@ # 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$ @@ -16,14 +16,18 @@ import logging 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: @@ -109,7 +113,6 @@ __xmlcdatacontent__ = "messyContent" global xmlMarshallerLogger xmlMarshallerLogger = logging.getLogger("activegrid.util.xmlmarshaller.marshal") -xmlMarshallerLogger.setLevel(aglogging.LEVEL_WARN) # INFO : low-level info # DEBUG : debugging info @@ -184,6 +187,7 @@ DICT_ITEM_VALUE_NAME = "value" ################################################################################ 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__: @@ -193,27 +197,95 @@ def setattrignorecase(object, name, value): 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 @@ -222,9 +294,11 @@ class Element: 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" @@ -238,16 +312,35 @@ class NsElement(object): 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: @@ -283,7 +376,6 @@ class NsElement(object): 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 @@ -298,38 +390,57 @@ class NsElement(object): 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 @@ -353,11 +464,16 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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] @@ -371,8 +487,28 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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 @@ -387,8 +523,9 @@ class XMLObjectFactory(xml.sax.ContentHandler): def endElement(self, name): ## print "[endElement] " % 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 @@ -405,34 +542,36 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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__: @@ -447,7 +586,6 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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) @@ -455,11 +593,15 @@ class XMLObjectFactory(xml.sax.ContentHandler): ### 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__ @@ -474,14 +616,12 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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)): @@ -499,12 +639,13 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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: @@ -524,7 +665,6 @@ class XMLObjectFactory(xml.sax.ContentHandler): 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 @@ -539,7 +679,12 @@ def _toAttrName(obj, name): 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", @@ -569,7 +714,7 @@ def xsdToLangType(xsdType): 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) @@ -588,8 +733,11 @@ def _getXmlValue(langValue): 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: @@ -600,17 +748,19 @@ def unmarshal(xmlstr, knownTypes=None, knownNamespaces=None, xmlSource=None): 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 '\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 '\n%s' % (sys.getdefaultencoding(), xmlstr) + if (not isinstance(encoding, basestring)): + encoding = sys.getdefaultencoding() + if (not isinstance(xmlstr, unicode)): + xmlstr = xmlstr.decode() + xmlstr = u'\n%s' % (encoding, xmlstr) + return xmlstr.encode(encoding) class XMLMarshalWorker(object): def __init__(self, marshalType=True, prettyPrint=False, knownTypes=None, knownNamespaces=None): @@ -695,7 +845,7 @@ class XMLMarshalWorker(object): 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 = '' @@ -705,13 +855,12 @@ class XMLMarshalWorker(object): 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 @@ -749,9 +898,11 @@ class XMLMarshalWorker(object): 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__"): @@ -768,8 +919,8 @@ class XMLMarshalWorker(object): 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: @@ -779,13 +930,15 @@ class XMLMarshalWorker(object): 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:' @@ -793,7 +946,6 @@ class XMLMarshalWorker(object): 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. @@ -806,7 +958,6 @@ class XMLMarshalWorker(object): 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("__")): @@ -814,7 +965,6 @@ class XMLMarshalWorker(object): 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(): @@ -856,8 +1006,7 @@ class XMLMarshalWorker(object): 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): @@ -873,9 +1022,18 @@ class XMLMarshalWorker(object): objTypeStr = self._genObjTypeStr("float") xmlString = ['%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' % (prefix, elementName, saxutils.escape(obj.encode()), elementName, newline)] + xmlString = ['%s<%s>%s%s' % (prefix, elementName, utillang.escape(obj.encode()), elementName, newline)] elif isinstance(obj, basestring): - xmlString = ['%s<%s>%s%s' % (prefix, elementName, saxutils.escape(obj), elementName, newline)] + xmlString = ['%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' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] + elif isinstance(obj, datetime.date): + objTypeStr = self._genObjTypeStr("date") + xmlString = ['%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' % (prefix, elementName, objTypeStr, str(obj), elementName, newline)] elif isinstance(obj, list): if len(obj) < 1: xmlString = "" @@ -910,13 +1068,15 @@ class XMLMarshalWorker(object): 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" % (prefix, elementName, nameSpaceAttrs, objattrs, contentValue, elementName, newline)] + contentValue = utillang.escape(contentValue) + xmlString = ["%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)) @@ -929,7 +1089,7 @@ class XMLMarshalWorker(object): 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() @@ -975,21 +1135,17 @@ class XMLMarshalWorker(object): 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" % (prefix, eName, newline)) prefix = prefix[:-increment] @@ -1022,8 +1178,8 @@ class XMLMarshalWorker(object): xmlString.append(">%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 diff --git a/wxPython/samples/ide/activegrid/util/xmlutils.py b/wxPython/samples/ide/activegrid/util/xmlutils.py index 02c0c8b2ca..411d55e087 100644 --- a/wxPython/samples/ide/activegrid/util/xmlutils.py +++ b/wxPython/samples/ide/activegrid/util/xmlutils.py @@ -22,35 +22,44 @@ import activegrid.util.aglogging as aglogging 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: @@ -155,41 +164,6 @@ def getAgVersion(fileName): 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" @@ -197,7 +171,9 @@ HTTP_WSDL_NS_URL = "http://schemas.xmlsoap.org/wsdl/http/" 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" @@ -209,7 +185,8 @@ KNOWN_NAMESPACES = { AG_NS_URL : "ag", 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", @@ -226,19 +203,23 @@ def getAgXsdToClassName(): "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", @@ -246,9 +227,11 @@ def getAgXsdToClassName(): "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", @@ -355,27 +338,38 @@ def getAgXsdToClassName(): "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 diff --git a/wxPython/samples/pydocview/FindService.py b/wxPython/samples/pydocview/FindService.py index 2fac20da23..3d5e2e4083 100644 --- a/wxPython/samples/pydocview/FindService.py +++ b/wxPython/samples/pydocview/FindService.py @@ -121,6 +121,7 @@ class FindService(wx.lib.pydocview.DocService): 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: @@ -129,6 +130,7 @@ class FindService(wx.lib.pydocview.DocService): self._replaceDialog = None self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString) + self._findDialog.CenterOnParent() self._findDialog.Show(True) @@ -152,6 +154,7 @@ class FindService(wx.lib.pydocview.DocService): """ 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()) @@ -356,7 +359,10 @@ class FindDialog(wx.Dialog): 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)) @@ -455,9 +461,14 @@ class FindReplaceDialog(FindDialog): 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)) @@ -495,12 +506,24 @@ def getFindData(): 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(): diff --git a/wxPython/samples/pydocview/TextEditor.py b/wxPython/samples/pydocview/TextEditor.py index 496c489802..9fa41a92ca 100644 --- a/wxPython/samples/pydocview/TextEditor.py +++ b/wxPython/samples/pydocview/TextEditor.py @@ -17,45 +17,45 @@ import FindService _ = 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): @@ -75,6 +75,7 @@ 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() @@ -83,6 +84,10 @@ class TextView(wx.lib.docview.View): 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 @@ -131,6 +136,9 @@ class TextView(wx.lib.docview.View): 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": diff --git a/wxPython/wx/lib/docview.py b/wxPython/wx/lib/docview.py index 85b6253205..3d24b224d3 100644 --- a/wxPython/wx/lib/docview.py +++ b/wxPython/wx/lib/docview.py @@ -6,7 +6,7 @@ # # 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 #---------------------------------------------------------------------------- @@ -214,8 +214,10 @@ class Document(wx.EvtHandler): 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): @@ -236,6 +238,16 @@ class Document(wx.EvtHandler): 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. @@ -271,6 +283,7 @@ class Document(wx.EvtHandler): Destructor. Removes itself from the document manager. """ self.DeleteContents() + self._documentModificationDate = None if self.GetDocumentManager(): self.GetDocumentManager().RemoveDocument(self) wx.EvtHandler.Destroy(self) @@ -364,7 +377,7 @@ class Document(wx.EvtHandler): 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") @@ -485,9 +498,9 @@ class Document(wx.EvtHandler): 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() @@ -529,9 +542,9 @@ class Document(wx.EvtHandler): self.GetDocumentWindow()) return False + self.SetDocumentModificationDate() self.SetFilename(filename, True) self.Modify(False) - self.SetDocumentModificationDate() self.SetDocumentSaved(True) self.UpdateAllViews() return True @@ -614,7 +627,7 @@ class Document(wx.EvtHandler): 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") @@ -844,8 +857,14 @@ class View(wx.EvtHandler): 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): """ @@ -916,11 +935,11 @@ class View(wx.EvtHandler): 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()) @@ -1865,7 +1884,7 @@ class DocManager(wx.EvtHandler): 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") @@ -2148,7 +2167,7 @@ class DocManager(wx.EvtHandler): 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 = _("*.*") @@ -2791,6 +2810,7 @@ class DocMDIChildFrame(wx.MDIChildFrame): self._childView.Activate(event.GetActive()) self._activated = 0 + def OnCloseWindow(self, event): """ Closes and deletes the current view and document. @@ -2846,6 +2866,28 @@ class DocMDIChildFrame(wx.MDIChildFrame): 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 @@ -2892,15 +2934,6 @@ class DocPrintout(wx.Printout): 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. diff --git a/wxPython/wx/lib/pydocview.py b/wxPython/wx/lib/pydocview.py index 2ac99a3e6b..4e8d665c79 100644 --- a/wxPython/wx/lib/pydocview.py +++ b/wxPython/wx/lib/pydocview.py @@ -2,11 +2,11 @@ # 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 #---------------------------------------------------------------------------- @@ -57,7 +57,7 @@ class DocFrameMixIn: Class with common code used by DocMDIParentFrame, DocTabbedParentFrame, and DocSDIFrame. """ - + def GetDocumentManager(self): """ @@ -132,7 +132,7 @@ class DocFrameMixIn: 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) @@ -151,7 +151,7 @@ class DocFrameMixIn: if sdi: # TODO: Is this really needed? wx.EVT_COMMAND_FIND_CLOSE(self, -1, self.ProcessEvent) - + return menuBar @@ -194,12 +194,12 @@ class DocFrameMixIn: 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): @@ -258,7 +258,7 @@ class DocMDIParentFrameMixIn: """ Class with common code used by DocMDIParentFrame and DocTabbedParentFrame. """ - + def _GetPosSizeFromConfig(self, pos, size): """ @@ -275,13 +275,13 @@ class DocMDIParentFrameMixIn: 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. """ @@ -306,7 +306,7 @@ class DocMDIParentFrameMixIn: # 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__': @@ -333,7 +333,7 @@ class DocMDIParentFrameMixIn: if id == SAVEALL_ID: self.OnFileSaveAll(event) return True - + return wx.GetApp().ProcessEvent(event) @@ -367,25 +367,24 @@ class DocMDIParentFrameMixIn: 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: @@ -397,7 +396,7 @@ class DocMDIParentFrameMixIn: 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: @@ -409,11 +408,11 @@ class DocMDIParentFrameMixIn: 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 @@ -499,7 +498,7 @@ class DocMDIParentFrameMixIn: 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) @@ -544,7 +543,7 @@ class DocMDIParentFrameMixIn: 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(): @@ -568,7 +567,7 @@ class DocMDIParentFrameMixIn: 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() \ @@ -618,7 +617,7 @@ class DocTabbedChildFrame(wx.Panel): Dummy method since the icon of tabbed frames are managed by the notebook. """ return None - + def SetIcon(self, icon): """ @@ -664,6 +663,23 @@ class DocTabbedChildFrame(wx.Panel): 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 @@ -719,7 +735,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): """ - 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. @@ -764,20 +780,20 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): # 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: @@ -789,7 +805,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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__": @@ -808,7 +824,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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) @@ -824,7 +840,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): Returns the notebook used by the tabbed document interface. """ return self._notebook - + def GetActiveChild(self): """ @@ -849,14 +865,16 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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): """ @@ -868,7 +886,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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 @@ -911,11 +929,11 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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. @@ -934,7 +952,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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 @@ -942,9 +960,12 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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): @@ -953,7 +974,18 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): """ 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): @@ -964,15 +996,19 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): 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): """ @@ -984,7 +1020,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): index = i break return index - + def ProcessEvent(self, event): """ @@ -1065,7 +1101,7 @@ class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): self.Destroy() else: event.Veto() - + class DocMDIChildFrame(wx.MDIChildFrame): """ @@ -1121,6 +1157,27 @@ 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 @@ -1216,9 +1273,6 @@ class DocMDIChildFrame(wx.MDIChildFrame): self._childView = view - - - class DocService(wx.EvtHandler): """ An abstract class used to add reusable services to a docview application. @@ -1247,7 +1301,7 @@ class DocService(wx.EvtHandler): 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 @@ -1360,7 +1414,7 @@ class DocOptionsService(DocService): toolsMenu.AppendSeparator() toolsMenu.Append(self._toolOptionsID, _("&Options..."), _("Sets options")) wx.EVT_MENU(frame, self._toolOptionsID, frame.ProcessEvent) - + def ProcessEvent(self, event): """ @@ -1379,7 +1433,7 @@ class DocOptionsService(DocService): 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): @@ -1400,7 +1454,7 @@ class DocOptionsService(DocService): def AddOptionsPanel(self, optionsPanel): """ - Adds an options panel to the options dialog. + Adds an options panel to the options dialog. """ self._optionsPanels.append(optionsPanel) @@ -1429,7 +1483,7 @@ class OptionsDialog(wx.Dialog): """ 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 = [] @@ -1445,15 +1499,15 @@ class OptionsDialog(wx.Dialog): 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() @@ -1467,18 +1521,18 @@ class OptionsDialog(wx.Dialog): 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) @@ -1525,7 +1579,7 @@ class GeneralOptionsPanel(wx.Panel): 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 @@ -1544,7 +1598,7 @@ class GeneralOptionsPanel(wx.Panel): 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, ) @@ -1574,7 +1628,7 @@ class GeneralOptionsPanel(wx.Panel): self.SetSizer(optionsBorderSizer) self.Layout() self._documentInterfaceMessageShown = False - parent.AddPage(self, _("Options")) + parent.AddPage(self, _("General")) def _AllowModeChanges(self): @@ -1619,7 +1673,7 @@ class DocApp(wx.PySimpleApp): 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 @@ -1655,14 +1709,14 @@ class DocApp(wx.PySimpleApp): 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() @@ -1670,19 +1724,19 @@ class DocApp(wx.PySimpleApp): 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 @@ -1699,15 +1753,15 @@ class DocApp(wx.PySimpleApp): 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 @@ -1742,7 +1796,7 @@ class DocApp(wx.PySimpleApp): 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): @@ -1824,11 +1878,11 @@ class DocApp(wx.PySimpleApp): 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. @@ -1875,7 +1929,7 @@ class DocApp(wx.PySimpleApp): Returns True if Windows MDI should use folder tabs instead of child windows. """ return self._useTabbedMDI - + def SetUseTabbedMDI(self, useTabbedMDI): """ @@ -1886,7 +1940,7 @@ class DocApp(wx.PySimpleApp): 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. """ @@ -2057,7 +2111,7 @@ class DocApp(wx.PySimpleApp): """ 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 @@ -2117,7 +2171,7 @@ class DocApp(wx.PySimpleApp): 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() @@ -2127,8 +2181,8 @@ class DocApp(wx.PySimpleApp): """ if self._splash: self._splash.Close(True) - - + + class _DocFrameFileDropTarget(wx.FileDropTarget): """ Class used to handle drops into the document frame. @@ -2155,7 +2209,7 @@ class _DocFrameFileDropTarget(wx.FileDropTarget): 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()) @@ -2167,9 +2221,9 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame, DocFrameMixIn, DocMDIP 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 @@ -2177,7 +2231,7 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame, DocFrameMixIn, DocMDIP """ 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): @@ -2186,7 +2240,7 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame, DocFrameMixIn, DocMDIP """ wx.LayoutAlgorithm().LayoutMDIFrame(self) self.GetClientWindow().Refresh() - + def ProcessEvent(self, event): """ @@ -2239,7 +2293,7 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame, DocFrameMixIn, DocMDIP 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): @@ -2272,7 +2326,7 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame, DocFrameMixIn): 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"): """ @@ -2308,7 +2362,7 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame, DocFrameMixIn): Lays out the Frame. """ self.Layout() - + def OnExit(self, event): """ @@ -2403,7 +2457,7 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame, DocFrameMixIn): if doc.IsModified(): filesModified = True break - + event.Enable(filesModified) return True else: @@ -2436,7 +2490,7 @@ class AboutService(DocService): else: self._dlg = AboutDialog # use default AboutDialog self._image = image - + def ShowAbout(self): """ @@ -2462,7 +2516,7 @@ class AboutDialog(wx.Dialog): """ Opens an AboutDialog. Shared by DocMDIParentFrame and DocSDIFrame. """ - + def __init__(self, parent, image=None): """ Initializes the about dialog. @@ -2474,13 +2528,13 @@ class AboutDialog(wx.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): @@ -2680,7 +2734,7 @@ class ChildDocument(wx.lib.docview.Document): """ 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. """ @@ -2709,7 +2763,7 @@ class ChildDocument(wx.lib.docview.Document): def SetParentDocument(self, parentDocument): """ Sets the parent Document of the ChildDocument. - """ + """ self._parentDocument = parentDocument @@ -2749,7 +2803,7 @@ class ChildDocument(wx.lib.docview.Document): 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): @@ -2758,7 +2812,7 @@ 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): @@ -2794,23 +2848,24 @@ class WindowMenuService(DocService): 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): @@ -2818,35 +2873,56 @@ class WindowMenuService(DocService): 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. @@ -2858,9 +2934,27 @@ class WindowMenuService(DocService): 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 @@ -2876,6 +2970,38 @@ class WindowMenuService(DocService): 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 @@ -2884,42 +3010,70 @@ class WindowMenuService(DocService): """ 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): @@ -2988,7 +3142,7 @@ class WindowMenuService(DocService): 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] @@ -3036,7 +3190,7 @@ def getNewData(): \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()) @@ -3064,7 +3218,7 @@ D\xee\xf4\x88\xb2\xfa5)\xab(\x99"\x00\xb9\x87c\x0b;\x19\xf1\x0b\x80\xb9pZ\ \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()) @@ -3091,7 +3245,7 @@ def getCopyData(): \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()) @@ -3120,7 +3274,7 @@ pz\x8d\x82\x12\x0b\x82\xc1HM\xect-c\xf7\xaa!\x10\xc9\xe0]rR\xac\xb4\x01\xc8\ \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()) @@ -3146,7 +3300,7 @@ def getSaveData(): 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()) @@ -3173,7 +3327,7 @@ def getSaveAllData(): \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()) @@ -3203,7 +3357,7 @@ def getPrintData(): \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()) @@ -3230,7 +3384,7 @@ pz\x0e\xa2~\x91\x0bx\x00-m\xe9D-W>%h\xc0\x1f_\xbf\x15\xef\xeb\x90\xaf\xc1\ \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()) @@ -3257,7 +3411,7 @@ def getCutData(): \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()) @@ -3288,7 +3442,7 @@ def getUndoData(): \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()) @@ -3317,7 +3471,7 @@ Eqc~S\xec\xd7\x94\x18\xaa\xafY*e^l\x10\x87\xf5\xb4,W\xb1<\x98\x16q\x98W\xa1\ \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()) @@ -3325,7 +3479,7 @@ def getRedoBitmap(): def getRedoImage(): stream = cStringIO.StringIO(getRedoData()) return ImageFromStream(stream) - + #---------------------------------------------------------------------------- def getBlankData(): @@ -3336,7 +3490,7 @@ 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(): @@ -3348,5 +3502,5 @@ def getBlankImage(): def getBlankIcon(): return wx.IconFromBitmap(getBlankBitmap()) - + -- 2.45.2