]> git.saurik.com Git - wxWidgets.git/commitdiff
Added the ActiveGrid IDE as a sample application
authorRobin Dunn <robin@alldunn.com>
Fri, 8 Apr 2005 22:54:02 +0000 (22:54 +0000)
committerRobin Dunn <robin@alldunn.com>
Fri, 8 Apr 2005 22:54:02 +0000 (22:54 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@33440 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

42 files changed:
wxPython/distrib/DIRLIST
wxPython/distrib/make_installer.py
wxPython/docs/CHANGES.txt
wxPython/samples/ide/ActiveGridIDE.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/__init__.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/AboutDialog.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/AbstractEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/CodeEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/DebuggerHarness.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/DebuggerService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/FindInDirService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/FindService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/HtmlEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/IDE.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/IDEFindService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/ImageEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/MarkerService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/MessageService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/OutlineService.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/PHPEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/PerlEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/ProjectEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/PythonEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/STCTextEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/Service.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/TabbedView.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/UICommon.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/Wizard.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/XmlEditor.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/__init__.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/checker.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/data/tips.txt [new file with mode: 0644]
wxPython/samples/ide/activegrid/tool/process.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/__init__.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/aglogging.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/cachedloader.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/dependencymgr.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/fileutils.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/gettersetter.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/xmlmarshaller.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/xmlmarshallertests.py [new file with mode: 0644]
wxPython/samples/ide/activegrid/util/xmlprettyprinter.py [new file with mode: 0644]

index a4a065fc38d3a48e6c52d128bc7d268db5f7303e..8a749638f06ff32b7e284526c3d5f5cabae04e4c 100644 (file)
@@ -61,10 +61,12 @@ wxPython/licence
 wxPython/samples
 wxPython/samples/StyleEditor
 wxPython/samples/docview
-wxPython/samples/docview/activegrid
-wxPython/samples/docview/activegrid/tool
-wxPython/samples/docview/activegrid/tool/data
-wxPython/samples/docview/activegrid/tool/images
+wxPython/samples/pydocview
+wxPython/samples/ide
+wxPython/samples/ide/activegrid
+wxPython/samples/ide/activegrid/tool
+wxPython/samples/ide/activegrid/tool/data
+wxPython/samples/ide/activegrid/util
 wxPython/samples/doodle
 wxPython/samples/embedded
 wxPython/samples/frogedit
index ed0e57dfd4e256676239b8e2170f26437b1a3c2f..96672b8ddf3f9d066dbffbc4ca8ef9c7579ad999 100644 (file)
@@ -409,11 +409,16 @@ Source: "samples\doodle\*.bat";             DestDir: "{app}\samples\doodle";
 Source: "samples\doodle\sample.ddl";        DestDir: "{app}\samples\doodle"; 
 Source: "samples\doodle\superdoodle.iss";   DestDir: "{app}\samples\doodle"; 
 
-Source: "samples\docview\*.py";                          DestDir: "{app}\samples\docview"; 
-Source: "samples\docview\activegrid\*.py";               DestDir: "{app}\samples\docview\activegrid"; 
-Source: "samples\docview\activegrid\tool\*.py";          DestDir: "{app}\samples\docview\activegrid\tool"; 
-Source: "samples\docview\activegrid\tool\data\*.txt";    DestDir: "{app}\samples\docview\activegrid\tool\data"; 
-Source: "samples\docview\activegrid\tool\images\*.jpg";  DestDir: "{app}\samples\docview\activegrid\tool\images"; 
+Source: "samples\docview\*.py";                DestDir: "{app}\samples\docview"; 
+Source: "samples\pydocview\*.py";              DestDir: "{app}\samples\pydocview";
+Source: "samples\pydocview\*.jpg";             DestDir: "{app}\samples\pydocview";
+Source: "samples\pydocview\*.txt";             DestDir: "{app}\samples\pydocview";
+
+Source: "samples\ide\*.py";                       DestDir: "{app}\samples\ide"; 
+Source: "samples\ide\activegrid\*.py";            DestDir: "{app}\samples\ide\activegrid"; 
+Source: "samples\ide\activegrid\tool\*.py";       DestDir: "{app}\samples\ide\activegrid\tool"; 
+Source: "samples\ide\activegrid\tool\data\*.txt"; DestDir: "{app}\samples\ide\activegrid\tool\data"; 
+Source: "samples\ide\activegrid\util\*.py";       DestDir: "{app}\samples\ide\activegrid\util"; 
 
 Source: "samples\embedded\*.py";            DestDir: "{app}\samples\embedded"; 
 Source: "samples\embedded\*.cpp";           DestDir: "{app}\samples\embedded"; 
index 454eb6dbd2e89810f7fe5f60a107a3cfd88a80cd..426c834fc6c0b87230b5abc0b59a21eb124f56a8 100644 (file)
@@ -85,6 +85,7 @@ list of top-level windows that currently exist in the application.
 Updated docview library modules and sample apps from the ActiveGrid
 folks. 
 
+Added the ActiveGrid IDE as a sample application.
 
 
 
diff --git a/wxPython/samples/ide/ActiveGridIDE.py b/wxPython/samples/ide/ActiveGridIDE.py
new file mode 100644 (file)
index 0000000..868acca
--- /dev/null
@@ -0,0 +1,33 @@
+#----------------------------------------------------------------------------
+# Name:         ActiveGridIDE.py
+# Purpose:
+#
+# Author:       Lawrence Bruhmuller
+#
+# Created:      3/30/05
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+import wx.lib.pydocview
+import activegrid.tool.IDE
+
+import os
+import sys
+sys.stdout = sys.stderr
+
+# This is here as the base IDE entry point.  Only difference is that -baseide is passed.
+
+sys.argv.append('-baseide');
+
+# Put activegrid dir in path so python files can be found from py2exe
+# This code should never do anything when run from the python interpreter
+execDir = os.path.dirname(sys.executable)
+try:
+    sys.path.index(execDir)
+except ValueError:
+    sys.path.append(execDir)
+app = activegrid.tool.IDE.IDEApplication(redirect = False)
+app.GetTopWindow().Raise()  # sometimes it shows up beneath other windows.  e.g. running self in debugger
+app.MainLoop()
+
diff --git a/wxPython/samples/ide/activegrid/__init__.py b/wxPython/samples/ide/activegrid/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/wxPython/samples/ide/activegrid/tool/AboutDialog.py b/wxPython/samples/ide/activegrid/tool/AboutDialog.py
new file mode 100644 (file)
index 0000000..ce1e0cd
--- /dev/null
@@ -0,0 +1,119 @@
+#----------------------------------------------------------------------------
+# Name:         AboutDialog.py
+# Purpose:      AboutBox which has copyright notice, license information, and credits
+#
+# Author:       Morgan Hua
+#
+# Created:      3/22/05
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# CVS-ID:       $Id$
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+from IDE import ACTIVEGRID_BASE_IDE, getSplashBitmap
+_ = wx.GetTranslation
+
+#----------------------------------------------------------------------------
+# Package License Data for AboutDialog
+#   Package, License, URL
+#   If no information is available, put a None as a place holder.
+#----------------------------------------------------------------------------
+
+
+licenseData = [
+    ("ActiveGrid", "ASL 2.0", "http://apache.org/licenses/LICENSE-2.0"),
+    ("Python 2.3", "Python Software Foundation License", "http://www.python.org/2.3/license.html"),
+    ("wxPython 2.5", "wxWidgets 2 - LGPL", "http://wxwidgets.org/newlicen.htm"),
+    ("wxWidgets", "wxWindows Library License 3", "http://www.wxwidgets.org/manuals/2.5.4/wx_wxlicense.html"),
+    ("pychecker", "MetaSlash - BSD", "http://pychecker.sourceforge.net/COPYRIGHT"), 
+    ("process.py", "See file", "http://starship.python.net/~tmick/"),
+]
+
+if not ACTIVEGRID_BASE_IDE:    # add licenses for database connections only if not the base IDE
+    licenseData += [
+        ("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://http://www.computronix.com/download/License(cxOracle).txt"), 
+        ("SQLite", "Public Domain", "http://www.sqlite.org/copyright.html"),
+        ("PyGreSQL", "BSD", "http://www.pygresql.org"),
+    ]
+
+if wx.Platform == '__WXMSW__':
+    licenseData += [("pywin32", "Python Software Foundation License", "http://sourceforge.net/projects/pywin32/")]
+
+class AboutDialog(wx.Dialog):
+
+    def __init__(self, parent):
+        """
+        Initializes the about dialog.
+        """
+        wx.Dialog.__init__(self, parent, -1, _("About ") + wx.GetApp().GetAppName(), style = wx.DEFAULT_DIALOG_STYLE)
+
+        nb = wx.Notebook(self, -1)
+
+        aboutPage = wx.Panel(nb, -1)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        splash_bmp = getSplashBitmap()
+        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() + _("\nVersion 0.6 Early Access\n\nCopyright (c) 2003-2005 ActiveGrid Incorporated and Contributors.  All rights reserved.")), 0, wx.ALIGN_LEFT|wx.ALL, 10)
+        sizer.Add(wx.StaticText(aboutPage, -1, _("http://www.activegrid.com")), 0, wx.ALIGN_LEFT|wx.LEFT|wx.BOTTOM, 10)
+        aboutPage.SetSizer(sizer)
+        nb.AddPage(aboutPage, _("Copyright"))
+
+        licensePage = wx.Panel(nb, -1)
+        grid = wx.grid.Grid(licensePage, -1)
+        grid.CreateGrid(len(licenseData), 2)
+        
+        dc = wx.ClientDC(grid)
+        dc.SetFont(grid.GetLabelFont())
+        grid.SetColLabelValue(0, _("License"))
+        grid.SetColLabelValue(1, _("URL"))
+        w, maxHeight = dc.GetTextExtent(_("License"))
+        w, h = dc.GetTextExtent(_("URL"))
+        if h > maxHeight:
+            maxHeight = h
+        grid.SetColLabelSize(maxHeight + 6)  # add a 6 pixel margin
+
+        for row, data in enumerate(licenseData):
+            package = data[0]
+            license = data[1]
+            url = data[2]
+            if package:
+                grid.SetRowLabelValue(row, package)
+            if license:
+                grid.SetCellValue(row, 0, license)
+            if url:
+                grid.SetCellValue(row, 1, url)
+        
+        grid.EnableEditing(False)
+        grid.EnableDragGridSize(False)
+        grid.EnableDragColSize(False)
+        grid.EnableDragRowSize(False)
+        grid.SetRowLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
+        grid.SetLabelBackgroundColour(wx.WHITE)
+        grid.AutoSizeColumn(0, 100)
+        grid.AutoSizeColumn(1, 100)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(grid, 1, wx.EXPAND|wx.ALL, 10)
+        licensePage.SetSizer(sizer)
+        nb.AddPage(licensePage, _("Licenses"))
+
+        creditsPage = wx.Panel(nb, -1)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(wx.StaticText(creditsPage, -1, _("ActiveGrid Development Team:\n\nLawrence Bruhmuller\nEric Chu\nMatt Fryer\nJoel Hare\nMorgan Hua\nAlan Mullendore\nJeff Norton\nKevin Wang\nPeter Yared")), 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)
+        sizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
+
+        self.SetSizer(sizer)
+        self.SetAutoLayout(True)
+        sizer.Fit(self)
+        
+
diff --git a/wxPython/samples/ide/activegrid/tool/AbstractEditor.py b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py
new file mode 100644 (file)
index 0000000..8bda446
--- /dev/null
@@ -0,0 +1,641 @@
+#----------------------------------------------------------------------------
+# Name:         AbstractEditor.py
+# Purpose:      Non-text editor for DataModel and Process
+#
+# Author:       Peter Yared, Morgan Hua
+#
+# Created:      7/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+
+import wx
+import wx.lib.docview
+import wx.lib.ogl as ogl
+import PropertyService
+_ = wx.GetTranslation
+
+
+SELECT_BRUSH = wx.Brush("BLUE", wx.SOLID)
+SHAPE_BRUSH = wx.Brush("WHEAT", wx.SOLID)
+LINE_BRUSH = wx.BLACK_BRUSH
+
+
+def GetRawModel(model):
+    if hasattr(model, "GetRawModel"):
+        rawModel = model.GetRawModel()
+    else:
+        rawModel = model
+    return rawModel
+
+
+class CanvasView(wx.lib.docview.View):
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+
+    def __init__(self, brush = SHAPE_BRUSH):
+        wx.lib.docview.View.__init__(self)
+        self._brush = brush
+        self._canvas = None
+        self._pt1 = None
+        self._pt2 = None
+        self._needEraseLasso = False
+        self._propShape = None
+
+
+    def OnCreate(self, doc, flags):
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
+        frame.Show()
+        sizer = wx.BoxSizer()
+        self._CreateCanvas(frame)
+        sizer.Add(self._canvas, 1, wx.EXPAND, 0)
+        frame.SetSizer(sizer)
+        frame.Layout()        
+        self.Activate()
+        return True
+
+
+    def OnActivateView(self, activate, activeView, deactiveView):
+        if activate and self._canvas:
+            # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
+            if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+                self._canvas.SetFocus()
+            else:
+                wx.CallAfter(self._canvas.SetFocus)
+
+
+    def OnClose(self, deleteWindow = True):
+        statusC = wx.GetApp().CloseChildDocuments(self.GetDocument())
+        statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow)
+        if hasattr(self, "ClearOutline"):
+            wx.CallAfter(self.ClearOutline)  # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter
+        if not (statusC and statusP):
+            return False
+        self.Activate(False)
+        if deleteWindow and self.GetFrame():
+            self.GetFrame().Destroy()
+        return True
+
+
+    def _CreateCanvas(self, parent):
+        self._canvas = ogl.ShapeCanvas(parent)
+        wx.EVT_LEFT_DOWN(self._canvas, self.OnLeftClick)
+        wx.EVT_LEFT_UP(self._canvas, self.OnLeftUp)
+        wx.EVT_MOTION(self._canvas, self.OnLeftDrag)
+        wx.EVT_LEFT_DCLICK(self._canvas, self.OnLeftDoubleClick)
+        wx.EVT_KEY_DOWN(self._canvas, self.OnKeyPressed)
+
+        maxWidth = 2000
+        maxHeight = 16000
+        self._canvas.SetScrollbars(20, 20, maxWidth / 20, maxHeight / 20)
+        
+        self._canvas.SetBackgroundColour(wx.WHITE)
+        self._diagram = ogl.Diagram()
+        self._canvas.SetDiagram(self._diagram)
+        self._diagram.SetCanvas(self._canvas)
+
+
+    def OnKeyPressed(self, event):
+        key = event.KeyCode()
+        if key == wx.WXK_DELETE:
+            self.OnClear(event)
+        else:
+            event.Skip()
+
+
+    def OnLeftClick(self, event):
+        self.EraseRubberBand()
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+
+        # keep track of mouse down for group select
+        self._pt1 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+        self._pt2 = None
+
+        shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0]
+        if shape:
+            self.BringToFront(shape)
+
+            self._pt1 = None
+            event.Skip()  # pass on event to shape handler to take care of selection
+
+            return
+        elif event.ControlDown() or event.ShiftDown():    # extend select, don't deselect
+            pass
+        else:
+            # click on empty part of canvas, deselect everything
+            needRefresh = False
+            for shape in self._diagram.GetShapeList():
+                if hasattr(shape, "GetModel"):
+                    if shape.Selected():
+                        needRefresh = True
+                        shape.Select(False, dc)
+            if needRefresh:
+                self._canvas.Redraw(dc)
+
+            self.SetPropertyModel(None)
+
+        if len(self.GetSelection()) == 0:
+            self.SetPropertyShape(None)
+
+
+
+    def OnLeftDoubleClick(self, event):
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if propertyService:
+            propertyService.ShowWindow()
+
+
+    def OnLeftDrag(self, event):
+        # draw lasso for group select
+        if self._pt1 and event.LeftIsDown():   # we are in middle of lasso selection
+            self.EraseRubberBand()
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            self._pt2 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+            self.DrawRubberBand()
+        else:
+            event.Skip()
+
+
+    def OnLeftUp(self, event):
+        # do group select
+        if self._needEraseLasso:
+            self.EraseRubberBand()
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            x1, y1 = self._pt1
+            x2, y2 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+
+            tol = self._diagram.GetMouseTolerance()
+            if abs(x1 - x2) > tol or abs(y1 - y2) > tol:
+                # make sure x1 < x2 and y1 < y2 to make comparison test easier
+                if x1 > x2:
+                    temp = x1
+                    x1 = x2
+                    x2 = temp
+                if y1 > y2:
+                    temp = y1
+                    y1 = y2
+                    y2 = temp
+
+                for shape in self._diagram.GetShapeList():
+                    if not shape.GetParent() and hasattr(shape, "GetModel"):  # if part of a composite, don't select it
+                        x, y = shape.GetX(), shape.GetY()
+                        width, height = shape.GetBoundingBoxMax()
+                        selected = x1 < x - width/2 and x2 > x + width/2 and y1 < y - height/2 and y2 > y + height/2
+                        if event.ControlDown() or event.ShiftDown():    # extend select, don't deselect
+                            if selected:
+                                shape.Select(selected, dc)
+                        else:   # select items in lasso and deselect items out of lasso
+                            shape.Select(selected, dc)
+                self._canvas.Redraw(dc)
+            else:
+                event.Skip()
+        else:
+            event.Skip()
+
+
+    def EraseRubberBand(self):
+        if self._needEraseLasso:
+            self._needEraseLasso = False
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            dc.SetLogicalFunction(wx.XOR)
+            pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
+            dc.SetPen(pen)
+            brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
+            dc.SetBrush(brush)
+            dc.ResetBoundingBox()
+            dc.BeginDrawing()
+
+            x1, y1 = self._pt1
+            x2, y2 = self._pt2
+
+            # make sure x1 < x2 and y1 < y2
+            # this will make (x1, y1) = upper left corner
+            if x1 > x2:
+                temp = x1
+                x1 = x2
+                x2 = temp
+            if y1 > y2:
+                temp = y1
+                y1 = y2
+                y2 = temp
+
+            # erase previous outline
+            dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
+            dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
+            dc.EndDrawing()
+
+
+    def DrawRubberBand(self):
+        self._needEraseLasso = True
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        dc.SetLogicalFunction(wx.XOR)
+        pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
+        dc.SetPen(pen)
+        brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
+        dc.SetBrush(brush)
+        dc.ResetBoundingBox()
+        dc.BeginDrawing()
+
+        x1, y1 = self._pt1
+        x2, y2 = self._pt2
+
+        # make sure x1 < x2 and y1 < y2
+        # this will make (x1, y1) = upper left corner
+        if x1 > x2:
+            temp = x1
+            x1 = x2
+            x2 = temp
+        if y1 > y2:
+            temp = y1
+            y1 = y2
+            y2 = temp
+
+        # draw outline
+        dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
+        dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
+        dc.EndDrawing()
+
+
+    def FindParkingSpot(self, width, height):
+        """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """
+        offset = 30  # space between shapes
+        x = offset
+        y = offset
+        maxX = 700  # max distance to the right where we'll place tables
+        noParkingSpot = True
+
+        while noParkingSpot:
+            point = self.isSpotOccupied(x, y, width, height)
+            if point:
+                x = point[0] + offset
+                if x > maxX:
+                    x = offset
+                    y = point[1] + offset
+            else:
+                noParkingSpot = False
+
+        return x, y
+
+
+    def isSpotOccupied(self, x, y, width, height):
+        """ returns None if at x,y,width,height no object occupies that rectangle,
+            otherwise returns lower right corner of object that occupies given x,y position
+        """
+        x2 = x + width
+        y2 = y + height
+
+        for shape in self._diagram.GetShapeList():
+            if isinstance(shape, ogl.RectangleShape) or isinstance(shape, ogl.EllipseShape):
+                if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+                    # skip, part of a composite shape
+                    continue
+
+                if hasattr(shape, "GetModel"):
+                    other_x, other_y, other_width, other_height = shape.GetModel().getEditorBounds()
+                    other_x2 = other_x + other_width
+                    other_y2 = other_y + other_height
+                else:
+                    # shapes x,y are at the center of the shape, need to transform to upper left coordinate
+                    other_width, other_height = shape.GetBoundingBoxMax()
+                    other_x = shape.GetX() - other_width/2
+                    other_y = shape.GetY() - other_height/2
+
+                other_x2 = other_x + other_width
+                other_y2 = other_y + other_height
+                # intersection check
+                if ((other_x2 < other_x or other_x2 > x) and
+                    (other_y2 < other_y or other_y2 > y) and
+                    (x2 < x or x2 > other_x) and
+                    (y2 < y or y2 > other_y)):
+                    return (other_x2, other_y2)
+        return None
+
+
+    #----------------------------------------------------------------------------
+    # Canvas methods
+    #----------------------------------------------------------------------------
+
+    def AddShape(self, shape, x = None, y = None, pen = None, brush = None, text = None, eventHandler = None):
+        if isinstance(shape, ogl.CompositeShape):
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            shape.Move(dc, x, y)
+        else:
+            shape.SetDraggable(True, True)
+        shape.SetCanvas(self._canvas)
+
+        if x:
+            shape.SetX(x)
+        if y:
+            shape.SetY(y)
+        shape.SetCentreResize(False)
+        if pen:
+            shape.SetPen(pen)
+        if brush:
+            shape.SetBrush(brush)
+        if text:
+            shape.AddText(text)
+        shape.SetShadowMode(ogl.SHADOW_RIGHT)
+        self._diagram.AddShape(shape)
+        shape.Show(True)
+        if not eventHandler:
+            eventHandler = EditorCanvasShapeEvtHandler(self)
+        eventHandler.SetShape(shape)
+        eventHandler.SetPreviousHandler(shape.GetEventHandler())
+        shape.SetEventHandler(eventHandler)
+        return shape
+
+
+    def RemoveShape(self, model = None, shape = None):
+        if not model and not shape:
+            return
+
+        if not shape:
+            shape = self.GetShape(model)
+
+        if shape:
+            shape.Select(False)
+            self._diagram.RemoveShape(shape)
+            if isinstance(shape, ogl.CompositeShape):
+                shape.RemoveFromCanvas(self._canvas)
+
+
+    def UpdateShape(self, model):
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel") and shape.GetModel() == model:
+                x, y, w, h = model.getEditorBounds()
+                newX = x + w / 2
+                newY = y + h / 2
+                changed = False
+                if isinstance(shape, ogl.CompositeShape):
+                    if shape.GetX() != newX or shape.GetY() != newY:
+                        dc = wx.ClientDC(self._canvas)
+                        self._canvas.PrepareDC(dc)
+                        shape.SetSize(w, h, True)  # wxBug: SetSize must be before Move because links won't go to the right place
+                        shape.Move(dc, newX, newY)  # wxBug: Move must be before SetSize because links won't go to the right place
+                        changed = True
+                else:
+                    oldw, oldh = shape.GetBoundingBoxMax()
+                    oldx = shape.GetX()
+                    oldy = shape.GetY()
+                    if oldw != w or oldh != h or oldx != newX or oldy != newY:
+                        shape.SetSize(w, h)
+                        shape.SetX(newX)
+                        shape.SetY(newY)
+                        changed = True
+                if changed:
+                    shape.ResetControlPoints()
+                    self._canvas.Refresh()
+                break
+
+
+    def GetShape(self, model):
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel") and shape.GetModel() == model:
+                return shape
+        return None
+
+
+    def GetSelection(self):
+        return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList())
+
+
+    def SetSelection(self, models, extendSelect = False):
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        update = False
+        if not isinstance(models, type([])) and not isinstance(models, type(())):
+            models = [models]
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel"):
+                if shape.Selected() and not shape.GetModel() in models:  # was selected, but not in new list, so deselect, unless extend select
+                    if not extendSelect:
+                        shape.Select(False, dc)
+                        update = True
+                elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select
+                    shape.Select(True, dc)
+                    update = True
+                elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect
+                    shape.Select(False, dc)
+                    update = True
+        if update:
+            self._canvas.Redraw(dc)
+
+
+    def BringToFront(self, shape):
+        if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+            self._diagram.RemoveShape(shape.GetParent())
+            self._diagram.AddShape(shape.GetParent())
+        else:
+            self._diagram.RemoveShape(shape)
+            self._diagram.AddShape(shape)
+
+
+    def SendToBack(self, shape):
+        if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+            self._diagram.RemoveShape(shape.GetParent())
+            self._diagram.InsertShape(shape.GetParent())
+        else:
+            self._diagram.RemoveShape(shape)
+            self._diagram.InsertShape(shape)
+
+
+    def ScrollVisible(self, shape):
+        xUnit, yUnit = shape._canvas.GetScrollPixelsPerUnit()
+        scrollX, scrollY = self._canvas.GetViewStart()  # in scroll units
+        scrollW, scrollH = self._canvas.GetSize()  # in pixels
+        w, h = shape.GetBoundingBoxMax() # in pixels
+        x = shape.GetX() - w/2  # convert to upper left coordinate from center
+        y = shape.GetY() - h/2  # convert to upper left coordinate from center
+
+        if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW:  # don't scroll if already visible
+            x = -1
+        else:
+            x = x/xUnit
+
+        if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH:  # don't scroll if already visible
+            y = -1
+        else:
+            y = y/yUnit
+
+        self._canvas.Scroll(x, y)  # in scroll units
+
+
+    def SetPropertyShape(self, shape):
+        # no need to highlight if no PropertyService is running
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if not propertyService:
+            return
+
+        if shape == self._propShape:
+            return
+
+        if hasattr(shape, "GetPropertyShape"):
+            shape = shape.GetPropertyShape()
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        dc.BeginDrawing()
+
+        # erase old selection if it still exists
+        if self._propShape and self._propShape in self._diagram.GetShapeList():
+            self._propShape.SetBrush(self._brush)
+            if (self._propShape._textColourName in ["BLACK", "WHITE"]):  # Would use GetTextColour() but it is broken
+                self._propShape.SetTextColour("BLACK", 0)
+            self._propShape.Draw(dc)
+
+        # set new selection
+        self._propShape = shape
+
+        # draw new selection
+        if self._propShape and self._propShape in self._diagram.GetShapeList():
+            self._propShape.SetBrush(SELECT_BRUSH)
+            if (self._propShape._textColourName in ["BLACK", "WHITE"]):  # Would use GetTextColour() but it is broken
+                self._propShape.SetTextColour("WHITE", 0)
+            self._propShape.Draw(dc)
+
+        dc.EndDrawing()
+
+
+    #----------------------------------------------------------------------------
+    # Property Service methods
+    #----------------------------------------------------------------------------
+
+    def GetPropertyModel(self):
+        if hasattr(self, "_propModel"):
+            return self._propModel
+        return None
+        
+
+    def SetPropertyModel(self, model):
+        # no need to set the model if no PropertyService is running
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if not propertyService:
+            return
+            
+        if hasattr(self, "_propModel") and model == self._propModel:
+            return
+            
+        self._propModel = model
+        propertyService.LoadProperties(self._propModel, self.GetDocument())
+
+
+class EditorCanvasShapeMixin:
+
+    def GetModel(self):
+        return self._model
+
+
+    def SetModel(self, model):
+        self._model = model
+
+
+class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler):
+
+    """ wxBug: Bug in OLG package.  With wxShape.SetShadowMode() turned on, when we set the size,
+        the width/height is larger by 6 pixels.  Need to subtract this value from width and height when we
+        resize the object.
+    """
+    SHIFT_KEY = 1
+    CONTROL_KEY = 2
+
+    def __init__(self, view):
+        ogl.ShapeEvtHandler.__init__(self)
+        self._view = view
+
+
+    def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+        shape = self.GetShape()
+        if hasattr(shape, "GetModel"):  # Workaround, on drag, we should deselect all other objects and select the clicked on object
+            model = shape.GetModel()
+        else:
+            shape = shape.GetParent()
+            if shape:
+                model = shape.GetModel()
+
+        self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
+        self._view.SetPropertyShape(shape)
+        self._view.SetPropertyModel(model)
+
+
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
+        shape = self.GetShape()
+        if hasattr(shape, "GetModel"):  # Workaround, on drag, we should deselect all other objects and select the clicked on object
+            model = shape.GetModel()
+        else:
+            parentShape = shape.GetParent()
+            if parentShape:
+                model = parentShape.GetModel()
+        self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
+
+
+    def OnMovePost(self, dc, x, y, oldX, oldY, display):
+        if x == oldX and y == oldY:
+            return
+        if not self._view.GetDocument():
+            return
+        shape = self.GetShape()
+        if isinstance(shape, EditorCanvasShapeMixin) and shape.Draggable():
+            model = shape.GetModel()
+            if hasattr(model, "getEditorBounds") and model.getEditorBounds():
+                x, y, w, h = model.getEditorBounds()
+                newX = shape.GetX() - shape.GetBoundingBoxMax()[0] / 2
+                newY = shape.GetY() - shape.GetBoundingBoxMax()[1] / 2
+                newWidth = shape.GetBoundingBoxMax()[0]
+                newHeight = shape.GetBoundingBoxMax()[1]
+                if shape._shadowMode != ogl.SHADOW_NONE:
+                    newWidth -= shape._shadowOffsetX
+                    newHeight -= shape._shadowOffsetY
+                newbounds = (newX, newY, newWidth, newHeight)
+    
+                if x != newX or y != newY or w != newWidth or h != newHeight:
+                    self._view.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self._view.GetDocument(), model, newbounds))
+
+
+    def Draw(self, dc):
+        pass
+
+
+class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command):
+
+
+    def __init__(self, canvasDocument, model, newbounds):
+        wx.lib.docview.Command.__init__(self, canUndo = True)
+        self._canvasDocument = canvasDocument
+        self._model = model
+        self._oldbounds = model.getEditorBounds()
+        self._newbounds = newbounds
+
+
+    def GetName(self):
+        name = self._canvasDocument.GetNameForObject(self._model)
+        if not name:
+            name = ""
+            print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model
+        return _("Move/Resize %s") % name
+
+
+    def Do(self):
+        return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds)
+
+
+    def Undo(self):
+        return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds)
+
diff --git a/wxPython/samples/ide/activegrid/tool/CodeEditor.py b/wxPython/samples/ide/activegrid/tool/CodeEditor.py
new file mode 100644 (file)
index 0000000..09a96ab
--- /dev/null
@@ -0,0 +1,1014 @@
+#----------------------------------------------------------------------------
+# Name:         CodeEditor.py
+# Purpose:      Abstract Code Editor for pydocview tbat uses the Styled Text Control
+#
+# Author:       Peter Yared
+#
+# Created:      8/10/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+
+import STCTextEditor
+import wx
+import wx.lib.docview
+import OutlineService
+import os
+import re
+import string
+import sys
+import DebuggerService
+import MarkerService
+_ = wx.GetTranslation
+if wx.Platform == '__WXMSW__':
+    _WINDOWS = True
+else:
+    _WINDOWS = False
+
+
+EXPAND_TEXT_ID = wx.NewId()
+COLLAPSE_TEXT_ID = wx.NewId()
+EXPAND_TOP_ID = wx.NewId()
+COLLAPSE_TOP_ID = wx.NewId()
+EXPAND_ALL_ID = wx.NewId()
+COLLAPSE_ALL_ID = wx.NewId()
+CHECK_CODE_ID = wx.NewId()
+AUTO_COMPLETE_ID = wx.NewId()
+CLEAN_WHITESPACE = wx.NewId()
+COMMENT_LINES_ID = wx.NewId()
+UNCOMMENT_LINES_ID = wx.NewId()
+INDENT_LINES_ID = wx.NewId()
+DEDENT_LINES_ID = wx.NewId()
+USE_TABS_ID = wx.NewId()
+SET_INDENT_WIDTH_ID = wx.NewId()
+FOLDING_ID = wx.NewId()
+
+
+class CodeDocument(STCTextEditor.TextDocument):
+    pass    
+
+
+class CodeView(STCTextEditor.TextView):
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return CodeCtrl
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == EXPAND_TEXT_ID:
+            self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
+            return True
+        elif id == COLLAPSE_TEXT_ID:
+            self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine())
+            return True
+        elif id == EXPAND_TOP_ID:
+            self.GetCtrl().ToggleFoldAll(expand = True, topLevelOnly = True)
+            return True
+        elif id == COLLAPSE_TOP_ID:
+            self.GetCtrl().ToggleFoldAll(expand = False, topLevelOnly = True)
+            return True
+        elif id == EXPAND_ALL_ID:
+            self.GetCtrl().ToggleFoldAll(expand = True)
+            return True
+        elif id == COLLAPSE_ALL_ID:
+            self.GetCtrl().ToggleFoldAll(expand = False)
+            return True
+        elif id == CHECK_CODE_ID:
+            self.OnCheckCode()
+            return True
+        elif id == AUTO_COMPLETE_ID:
+            self.OnAutoComplete()
+            return True
+        elif id == CLEAN_WHITESPACE:
+            self.OnCleanWhiteSpace()
+            return True
+        elif id == SET_INDENT_WIDTH_ID:
+            self.OnSetIndentWidth()
+            return True
+        elif id == USE_TABS_ID:
+            self.GetCtrl().SetUseTabs(not self.GetCtrl().GetUseTabs())
+            return True
+        elif id == INDENT_LINES_ID:
+            self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_TAB)
+            return True
+        elif id == DEDENT_LINES_ID:
+            self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_BACKTAB)
+            return True
+        elif id == COMMENT_LINES_ID:
+            self.OnCommentLines()
+            return True
+        elif id == UNCOMMENT_LINES_ID:
+            self.OnUncommentLines()
+            return True
+        else:
+            return STCTextEditor.TextView.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if not self.GetCtrl():
+            return False
+        hasText = self.GetCtrl().GetTextLength() > 0
+        id = event.GetId()
+        if id == EXPAND_TEXT_ID:
+            event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine()))
+            return True
+        elif id == COLLAPSE_TEXT_ID:
+            event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine()))
+            return True
+        elif id == EXPAND_TOP_ID:
+            event.Enable(hasText)
+            return True
+        elif id == COLLAPSE_TOP_ID:
+            event.Enable(hasText)
+            return True
+        elif id == EXPAND_ALL_ID:
+            event.Enable(hasText)
+            return True
+        elif id == COLLAPSE_ALL_ID:
+            event.Enable(hasText)
+            return True
+        elif id == CHECK_CODE_ID:
+            event.Enable(False)
+            return True
+        elif id == AUTO_COMPLETE_ID:
+            event.Enable(hasText)
+            return True
+        elif id == CLEAN_WHITESPACE:
+            event.Enable(hasText)
+            return True
+        elif id == SET_INDENT_WIDTH_ID:
+            event.Enable(True)
+            return True
+        elif id == USE_TABS_ID:
+            event.Enable(True)
+            event.Check(self.GetCtrl().GetUseTabs())
+            return True
+        elif id == INDENT_LINES_ID:
+            event.Enable(hasText)
+            return True
+        elif id == DEDENT_LINES_ID:
+            event.Enable(hasText)
+            return True
+        elif id == COMMENT_LINES_ID:
+            event.Enable(hasText)
+            return True
+        elif id == UNCOMMENT_LINES_ID:
+            event.Enable(hasText)
+            return True
+        elif id == FOLDING_ID:
+            event.Enable(True)
+            return True
+        else:
+            return STCTextEditor.TextView.ProcessUpdateUIEvent(self, event)
+
+
+    #----------------------------------------------------------------------------
+    # Methods for OutlineService
+    #----------------------------------------------------------------------------
+
+    def OnChangeFilename(self):
+        wx.lib.docview.View.OnChangeFilename(self)
+        self.LoadOutline(force=True)
+        
+
+    def ClearOutline(self):
+        outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
+        if not outlineService:
+            return
+
+        outlineView = outlineService.GetView()
+        if not outlineView:
+            return
+
+        outlineView.ClearTreeCtrl()
+
+
+    def LoadOutline(self, force=False):
+        outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
+        if not outlineService:
+            return
+        outlineService.LoadOutline(self, force=force)
+
+
+    def DoLoadOutlineCallback(self, force=False):
+        outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
+        if not outlineService:
+            return False
+
+        outlineView = outlineService.GetView()
+        if not outlineView:
+            return False
+
+        treeCtrl = outlineView.GetTreeCtrl()
+        if not treeCtrl:
+            return False
+
+        view = treeCtrl.GetCallbackView()
+        newCheckSum = self.GenCheckSum()
+        if not force:
+            if view and view is self:
+                if self._checkSum == newCheckSum:
+                    return False
+        self._checkSum = newCheckSum
+
+        treeCtrl.DeleteAllItems()
+
+        document = self.GetDocument()
+        if not document:
+            return True
+
+        filename = document.GetFilename()
+        if filename:
+            rootItem = treeCtrl.AddRoot(os.path.basename(filename))
+            treeCtrl.SetDoSelectCallback(rootItem, self, None)
+        else:
+            return True
+
+        text = self.GetValue()
+        if not text:
+            return True
+
+        CLASS_PATTERN = 'class[ \t]+\w+.*?:'
+        DEF_PATTERN = 'def[ \t]+\w+\(.*?\)'
+        classPat = re.compile(CLASS_PATTERN, re.M|re.S)
+        defPat= re.compile(DEF_PATTERN, re.M|re.S)
+        pattern = re.compile('^[ \t]*((' + CLASS_PATTERN + ')|('+ DEF_PATTERN +'.*?:)).*?$', re.M|re.S)
+
+        iter = pattern.finditer(text)
+        indentStack = [(0, rootItem)]
+        for pattern in iter:
+            line = pattern.string[pattern.start(0):pattern.end(0)]
+            classLine = classPat.search(line)
+            if classLine:
+                indent = classLine.start(0)
+                itemStr = classLine.string[classLine.start(0):classLine.end(0)-1]  # don't take the closing ':'
+            else:
+                defLine = defPat.search(line)
+                if defLine:
+                    indent = defLine.start(0)
+                    itemStr = defLine.string[defLine.start(0):defLine.end(0)]
+
+            if indent == 0:
+                parentItem = rootItem
+            else:
+                lastItem = indentStack.pop()
+                while lastItem[0] >= indent:
+                    lastItem = indentStack.pop()
+                indentStack.append(lastItem)
+                parentItem = lastItem[1]
+
+            item = treeCtrl.AppendItem(parentItem, itemStr)
+            treeCtrl.SetDoSelectCallback(item, self, (pattern.end(0), pattern.start(0) + indent))  # select in reverse order because we want the cursor to be at the start of the line so it wouldn't scroll to the right
+            indentStack.append((indent, item))
+
+        treeCtrl.Expand(rootItem)
+
+        return True
+
+
+    def DoSelectCallback(self, data):
+        if data:
+            self.EnsureVisibleEnforcePolicy(self.LineFromPosition(data[0]))
+            # wxBug: need to select in reverse order (end, start) to place cursor at begining of line,
+            #        otherwise, display is scrolled over to the right hard and is hard to view
+            self.SetSelection(data[1], data[0])
+
+
+##    def checksum(self, bytes):        
+##        def rotate_right(c):
+##            if c&1:
+##                return (c>>1)|0x8000
+##            else:
+##                return c>>1
+##                
+##        result = 0
+##        for ch in bytes:
+##            ch = ord(ch) & 0xFF
+##            result = (rotate_right(result)+ch) & 0xFFFF
+##        return result
+##
+
+    def GenCheckSum(self):
+        """ Poor man's checksum.  We'll assume most changes will change the length of the file.
+        """
+        text = self.GetValue()
+        if text:
+            return len(text)
+        else:
+            return 0
+
+
+    #----------------------------------------------------------------------------
+    # Format methods
+    #----------------------------------------------------------------------------
+
+    def OnCheckCode(self):
+        """ Need to overshadow this for each specific subclass """
+        if 0:
+            try:
+                code = self.GetCtrl().GetText()
+                codeObj = compile(code, self.GetDocument().GetFilename(), 'exec')
+                self._GetParentFrame().SetStatusText(_("The file successfully compiled"))
+            except SyntaxError, (message, (fileName, line, col, text)):
+                pos = self.GetCtrl().PositionFromLine(line - 1) + col - 1
+                self.GetCtrl().SetSelection(pos, pos)
+                self._GetParentFrame().SetStatusText(_("Syntax Error: %s") % message)
+            except:
+                self._GetParentFrame().SetStatusText(sys.exc_info()[0])
+
+
+    def OnAutoComplete(self):
+        self.GetCtrl().AutoCompCancel()
+        self.GetCtrl().AutoCompSetAutoHide(0)
+        self.GetCtrl().AutoCompSetChooseSingle(True)
+        self.GetCtrl().AutoCompSetIgnoreCase(True)
+        context, hint = self.GetAutoCompleteHint()
+        replaceList, replaceLen = self.GetAutoCompleteKeywordList(context, hint)
+        if replaceList and len(replaceList) != 0: 
+            self.GetCtrl().AutoCompShow(replaceLen, replaceList)
+
+
+    def GetAutoCompleteHint(self):
+        """ Replace this method with Editor specific method """
+        pos = self.GetCtrl().GetCurrentPos()
+        if pos == 0:
+            return None, None
+        if chr(self.GetCtrl().GetCharAt(pos - 1)) == '.':
+            pos = pos - 1
+            hint = None
+        else:
+            hint = ''
+            
+        validLetters = string.letters + string.digits + '_.'
+        word = ''
+        while (True):
+            pos = pos - 1
+            if pos < 0:
+                break
+            char = chr(self.GetCtrl().GetCharAt(pos))
+            if char not in validLetters:
+                break
+            word = char + word
+            
+        context = word
+        if hint is not None:            
+            lastDot = word.rfind('.')
+            if lastDot != -1:
+                context = word[0:lastDot]
+                hint = word[lastDot+1:]
+                    
+        return context, hint
+        
+
+    def GetAutoCompleteDefaultKeywords(self):
+        """ Replace this method with Editor specific keywords """
+        return ['Put', 'Editor Specific', 'Keywords', 'Here']
+
+
+    def CaseInsensitiveCompare(self, s1, s2):
+        """ GetAutoCompleteKeywordList() method used to show keywords in case insensitive order """
+        s1L = s1.lower()
+        s2L = s2.lower()
+        if s1L == s2L:
+            return 0
+        elif s1L < s2L:
+            return -1
+        else:
+            return 1
+
+
+    def GetAutoCompleteKeywordList(self, context, hint):            
+        """ Replace this method with Editor specific keywords """
+        kw = self.GetAutoCompleteDefaultKeywords()
+        
+        if hint and len(hint):
+            lowerHint = hint.lower()
+            filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw)  # remove variables and methods that don't match hint
+            kw = filterkw
+
+        if hint:
+            replaceLen = len(hint)
+        else:
+            replaceLen = 0
+            
+        kw.sort(self.CaseInsensitiveCompare)
+        return " ".join(kw), replaceLen
+        
+
+    def OnCleanWhiteSpace(self):
+        newText = ""
+        for lineNo in self._GetSelectedLineNumbers():
+            lineText = string.rstrip(self.GetCtrl().GetLine(lineNo))
+            indent = 0
+            lstrip = 0
+            for char in lineText:
+                if char == '\t':
+                    indent = indent + self.GetCtrl().GetIndent()
+                    lstrip = lstrip + 1
+                elif char in string.whitespace:
+                    indent = indent + 1
+                    lstrip = lstrip + 1
+                else:
+                    break
+            if self.GetCtrl().GetUseTabs():
+                indentText = (indent / self.GetCtrl().GetIndent()) * '\t' + (indent % self.GetCtrl().GetIndent()) * ' '
+            else:
+                indentText = indent * ' '
+            lineText = indentText + lineText[lstrip:] + '\n'
+            newText = newText + lineText
+        self._ReplaceSelectedLines(newText)
+
+
+    def OnSetIndentWidth(self):
+        dialog = wx.TextEntryDialog(self._GetParentFrame(), _("Enter new indent width (2-10):"), _("Set Indent Width"), "%i" % self.GetCtrl().GetIndent())
+        if dialog.ShowModal() == wx.ID_OK:
+            try:
+                indent = int(dialog.GetValue())
+                if indent >= 2 and indent <= 10:
+                    self.GetCtrl().SetIndent(indent)
+                    self.GetCtrl().SetTabWidth(indent)
+            except:
+                pass
+        dialog.Destroy()
+
+
+    def GetIndentWidth(self):
+        return self.GetCtrl().GetIndent()
+                
+
+    def OnCommentLines(self):
+        newText = ""
+        for lineNo in self._GetSelectedLineNumbers():
+            lineText = self.GetCtrl().GetLine(lineNo)
+            if (len(lineText) > 1 and lineText[0] == '#') or (len(lineText) > 2 and lineText[:2] == '##'):
+                newText = newText + lineText
+            else:
+                newText = newText + "##" + lineText
+        self._ReplaceSelectedLines(newText)
+
+
+    def OnUncommentLines(self):
+        newText = ""
+        for lineNo in self._GetSelectedLineNumbers():
+            lineText = self.GetCtrl().GetLine(lineNo)
+            if len(lineText) >= 2 and lineText[:2] == "##":
+                lineText = lineText[2:]
+            elif len(lineText) >= 1 and lineText[:1] == "#":
+                lineText = lineText[1:]
+            newText = newText + lineText
+        self._ReplaceSelectedLines(newText)
+
+
+    def _GetSelectedLineNumbers(self):
+        selStart, selEnd = self._GetPositionsBoundingSelectedLines()
+        return range(self.GetCtrl().LineFromPosition(selStart), self.GetCtrl().LineFromPosition(selEnd))
+
+
+    def _GetPositionsBoundingSelectedLines(self):
+        startPos = self.GetCtrl().GetCurrentPos()
+        endPos = self.GetCtrl().GetAnchor()
+        if startPos > endPos:
+            temp = endPos
+            endPos = startPos
+            startPos = temp
+        if endPos == self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos)):
+            endPos = endPos - 1  # If it's at the very beginning of a line, use the line above it as the ending line
+        selStart = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(startPos))
+        selEnd = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos) + 1)
+        return selStart, selEnd
+
+
+    def _ReplaceSelectedLines(self, text):
+        if len(text) == 0:
+            return
+        selStart, selEnd = self._GetPositionsBoundingSelectedLines()
+        self.GetCtrl().SetSelection(selStart, selEnd)
+        self.GetCtrl().ReplaceSelection(text)
+        self.GetCtrl().SetSelection(selStart + len(text), selStart)
+
+
+    def OnUpdate(self, sender = None, hint = None):
+        if hint == "ViewStuff":
+            self.GetCtrl().SetViewDefaults()
+        elif hint == "Font":
+            font, color = self.GetFontAndColorFromConfig()
+            self.GetCtrl().SetFont(font)
+            self.GetCtrl().SetFontColor(color)
+        else:
+            dbg_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
+            if dbg_service:
+                dbg_service.SetCurrentBreakpointMarkers(self)
+
+
+class CodeService(STCTextEditor.TextService):
+
+
+    def __init__(self):
+        STCTextEditor.TextService.__init__(self)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        # TODO NEED TO DO INSTANCEOF CHECK HERE FOR SDI
+        #if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument:
+        #    return
+        if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            return
+
+        viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
+        isWindows = (wx.Platform == '__WXMSW__')
+
+        if not menuBar.FindItemById(EXPAND_TEXT_ID):  # check if below menu items have been already been installed
+            foldingMenu = wx.Menu()
+            if isWindows:
+                foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand\tNumpad-Plus"), _("Expands a collapsed block of text"))
+            else:
+                foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand"), _("Expands a collapsed block of text"))
+
+            wx.EVT_MENU(frame, EXPAND_TEXT_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, EXPAND_TEXT_ID, frame.ProcessUpdateUIEvent)
+            
+            if isWindows:
+                foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse\tNumpad+Minus"), _("Collapse a block of text"))
+            else:
+                foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse"), _("Collapse a block of text"))
+            wx.EVT_MENU(frame, COLLAPSE_TEXT_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, COLLAPSE_TEXT_ID, frame.ProcessUpdateUIEvent)
+            
+            if isWindows:
+                foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level\tCtrl+Numpad+Plus"), _("Expands the top fold levels in the document"))
+            else:
+                foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level"), _("Expands the top fold levels in the document"))
+            wx.EVT_MENU(frame, EXPAND_TOP_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, EXPAND_TOP_ID, frame.ProcessUpdateUIEvent)
+            
+            if isWindows:
+                foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level\tCtrl+Numpad+Minus"), _("Collapses the top fold levels in the document"))
+            else:
+                foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level"), _("Collapses the top fold levels in the document"))
+            wx.EVT_MENU(frame, COLLAPSE_TOP_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, COLLAPSE_TOP_ID, frame.ProcessUpdateUIEvent)
+            
+            if isWindows:
+                foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All\tShift+Numpad+Plus"), _("Expands all of the fold levels in the document"))
+            else:
+                foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All"), _("Expands all of the fold levels in the document"))
+            wx.EVT_MENU(frame, EXPAND_ALL_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, EXPAND_ALL_ID, frame.ProcessUpdateUIEvent)
+            
+            if isWindows:
+                foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All\tShift+Numpad+Minus"), _("Collapses all of the fold levels in the document"))
+            else:
+                foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All"), _("Collapses all of the fold levels in the document"))
+            wx.EVT_MENU(frame, COLLAPSE_ALL_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, COLLAPSE_ALL_ID, frame.ProcessUpdateUIEvent)
+            
+            viewMenu.AppendMenu(FOLDING_ID, _("&Folding"), foldingMenu)
+            wx.EVT_UPDATE_UI(frame, FOLDING_ID, frame.ProcessUpdateUIEvent)
+
+        formatMenuIndex = menuBar.FindMenu(_("&Format"))
+        if formatMenuIndex > -1:
+            formatMenu = menuBar.GetMenu(formatMenuIndex)
+        else:
+            formatMenu = wx.Menu()
+        if not menuBar.FindItemById(CHECK_CODE_ID):  # check if below menu items have been already been installed
+            formatMenu.AppendSeparator()
+            formatMenu.Append(CHECK_CODE_ID, _("&Check Code"), _("Checks the document for syntax and indentation errors"))
+            wx.EVT_MENU(frame, CHECK_CODE_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, CHECK_CODE_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(AUTO_COMPLETE_ID, _("&Auto Complete\tCtrl+Space"), _("Provides suggestions on how to complete the current statement"))
+            wx.EVT_MENU(frame, AUTO_COMPLETE_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, AUTO_COMPLETE_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(CLEAN_WHITESPACE, _("Clean &Whitespace"), _("Converts leading spaces to tabs or vice versa per 'use tabs' and clears trailing spaces"))
+            wx.EVT_MENU(frame, CLEAN_WHITESPACE, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, CLEAN_WHITESPACE, frame.ProcessUpdateUIEvent)
+            formatMenu.AppendSeparator()
+            formatMenu.Append(INDENT_LINES_ID, _("&Indent Lines\tTab"), _("Indents the selected lines one indent width"))
+            wx.EVT_MENU(frame, INDENT_LINES_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, INDENT_LINES_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(DEDENT_LINES_ID, _("&Dedent Lines\tShift+Tab"), _("Dedents the selected lines one indent width"))
+            wx.EVT_MENU(frame, DEDENT_LINES_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, DEDENT_LINES_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(COMMENT_LINES_ID, _("Comment &Lines\tCtrl+Q"), _("Comments out the selected lines be prefixing each one with a comment indicator"))
+            wx.EVT_MENU(frame, COMMENT_LINES_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, COMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(UNCOMMENT_LINES_ID, _("&Uncomment Lines\tCtrl+Shift+Q"), _("Removes comment prefixes from each of the selected lines"))
+            wx.EVT_MENU(frame, UNCOMMENT_LINES_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, UNCOMMENT_LINES_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.AppendSeparator()
+            formatMenu.AppendCheckItem(USE_TABS_ID, _("Use &Tabs"), _("Toggles use of tabs or whitespaces for indents"))
+            wx.EVT_MENU(frame, USE_TABS_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, USE_TABS_ID, frame.ProcessUpdateUIEvent)
+            formatMenu.Append(SET_INDENT_WIDTH_ID, _("&Set Indent Width..."), _("Sets the indent width"))
+            wx.EVT_MENU(frame, SET_INDENT_WIDTH_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, SET_INDENT_WIDTH_ID, frame.ProcessUpdateUIEvent)
+        if formatMenuIndex == -1:
+            viewMenuIndex = menuBar.FindMenu(_("&View"))
+            menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format"))
+
+##        accelTable = wx.AcceleratorTable([
+##            (wx.ACCEL_NORMAL, wx.WXK_TAB, INDENT_LINES_ID),
+##            (wx.ACCEL_SHIFT, wx.WXK_TAB, DEDENT_LINES_ID),
+##            eval(_("wx.ACCEL_CTRL, ord('Q'), COMMENT_LINES_ID")),
+##            eval(_("wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord('Q'), UNCOMMENT_LINES_ID"))
+##            ])
+##        frame.SetAcceleratorTable(accelTable)
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == EXPAND_TEXT_ID:
+            event.Enable(False)
+            return True
+        elif id == COLLAPSE_TEXT_ID:
+            event.Enable(False)
+            return True
+        elif id == EXPAND_TOP_ID:
+            event.Enable(False)
+            return True
+        elif id == COLLAPSE_TOP_ID:
+            event.Enable(False)
+            return True
+        elif id == EXPAND_ALL_ID:
+            event.Enable(False)
+            return True
+        elif id == COLLAPSE_ALL_ID:
+            event.Enable(False)
+            return True
+        elif id == CHECK_CODE_ID:
+            event.Enable(False)
+            return True
+        elif id == AUTO_COMPLETE_ID:
+            event.Enable(False)
+            return True
+        elif id == CLEAN_WHITESPACE:
+            event.Enable(False)
+            return True
+        elif id == SET_INDENT_WIDTH_ID:
+            event.Enable(False)
+            return True
+        elif id == USE_TABS_ID:
+            event.Enable(False)
+            return True
+        elif id == INDENT_LINES_ID:
+            event.Enable(False)
+            return True
+        elif id == DEDENT_LINES_ID:
+            event.Enable(False)
+            return True
+        elif id == COMMENT_LINES_ID:
+            event.Enable(False)
+            return True
+        elif id == UNCOMMENT_LINES_ID:
+            event.Enable(False)
+            return True
+        elif id == FOLDING_ID:
+            event.Enable(False)
+            return True
+        else:
+            return STCTextEditor.TextService.ProcessUpdateUIEvent(self, event)
+
+
+class CodeCtrl(STCTextEditor.TextCtrl):
+    CURRENT_LINE_MARKER_NUM = 2
+    BREAKPOINT_MARKER_NUM = 1
+    CURRENT_LINE_MARKER_MASK = 0x4
+    BREAKPOINT_MARKER_MASK = 0x2
+    
+            
+    def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE):
+        if ID == -1:
+            ID = wx.NewId()
+        STCTextEditor.TextCtrl.__init__(self, parent, ID, style)
+        
+        self.UsePopUp(False)
+        self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
+        self.SetProperty("fold", "1")
+
+        # Setup a margin to hold fold markers
+        #self.SetFoldFlags(16)  ###  WHAT IS THIS VALUE?  WHAT ARE THE OTHER FLAGS?  DOES IT MATTER?
+        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)
+        
+        self.SetMarginSensitive(0, True)
+        self.SetMarginType(0, wx.stc.STC_MARGIN_SYMBOL)
+        self.SetMarginMask(0, 0x3)
+        self.SetMarginWidth(0, 12)
+
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND,     wx.stc.STC_MARK_BOXPLUSCONNECTED,  "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER,  "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL,    wx.stc.STC_MARK_LCORNER,  "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB,     wx.stc.STC_MARK_VLINE,    "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER,        wx.stc.STC_MARK_BOXPLUS,  "white", "black")
+        self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN,    wx.stc.STC_MARK_BOXMINUS, "white", "black")
+        # Define the current line marker
+        self.MarkerDefine(CodeCtrl.CURRENT_LINE_MARKER_NUM, wx.stc.STC_MARK_SHORTARROW, wx.BLACK, (255,255,128))
+        # 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
+            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, ID, self.OnMarginClick)
+        wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
+        if self.GetMatchingBraces(): 
+            wx.stc.EVT_STC_UPDATEUI(self, ID, self.OnUpdateUI)
+
+        self.StyleClearAll()
+        self.UpdateStyles()
+        
+
+    def OnRightUp(self, event):
+        #Hold onto the current line number, no way to get it later.
+        self._rightClickPosition = self.PositionFromPoint(event.GetPosition())
+        self._rightClickLine = self.LineFromPosition(self._rightClickPosition)
+        self.PopupMenu(self.CreatePopupMenu(), event.GetPosition())
+        self._rightClickLine = -1
+        self._rightClickPosition = -1
+        
+
+    def CreatePopupMenu(self):
+        TOGGLEBREAKPOINT_ID = wx.NewId()
+        TOGGLEMARKER_ID = wx.NewId()
+        SYNCTREE_ID = wx.NewId()
+        
+        menu = wx.Menu()
+        
+        self.Bind(wx.EVT_MENU, self.OnPopSyncOutline, id=SYNCTREE_ID)
+        item = wx.MenuItem(menu, SYNCTREE_ID, _("Find in Outline View"))
+        menu.AppendItem(item)
+        menu.AppendSeparator()
+        self.Bind(wx.EVT_MENU, self.OnPopToggleBP, id=TOGGLEBREAKPOINT_ID)
+        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"))
+        menu.AppendItem(item)
+        menu.AppendSeparator()
+                
+        itemIDs = [wx.ID_UNDO, wx.ID_REDO, None,
+                   wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL]
+
+        menuBar = wx.GetApp().GetTopWindow().GetMenuBar()
+        for itemID in itemIDs:
+            if not itemID:
+                menu.AppendSeparator()
+            else:
+                item = menuBar.FindItemById(itemID)
+                if item:
+                    menu.Append(itemID, item.GetLabel())
+
+        return menu
+                
+
+    def OnPopToggleBP(self, event):
+        """ Toggle break point on right click line, not current line """
+        wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine)
+      
+  
+    def OnPopToggleMarker(self, event):
+        """ Toggle marker on right click line, not current line """
+        wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle(lineNum = self._rightClickLine)
+
+
+    def OnPopSyncOutline(self, event):
+        wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(wx.GetApp().GetDocumentManager().GetCurrentView(), position=self._rightClickPosition)
+          
+
+    def HasSelection(self):
+        return self.GetSelectionStart() - self.GetSelectionEnd() != 0  
+
+
+    def ClearCurrentLineMarkers(self):
+        self.MarkerDeleteAll(CodeCtrl.CURRENT_LINE_MARKER_NUM)
+        
+
+    def ClearCurrentBreakpoinMarkers(self):
+        self.MarkerDeleteAll(CodeCtrl.BREAKPOINT_MARKER_NUM)
+
+
+    def GetDefaultFont(self):
+        if wx.Platform == '__WXMSW__':
+            font = "Courier New"
+        else:
+            font = "Courier"
+        return wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font)
+
+
+    def GetMatchingBraces(self):
+        """ Overwrite this method for language specific braces """
+        return "[]{}()"
+
+
+    def CanWordWrap(self):
+        return False
+        
+
+    def SetFont(self, font):
+        self._font = font
+
+
+    def SetFontColor(self, fontColor):
+        self._fontColor = fontColor
+
+
+    def UpdateStyles(self):
+
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+        # Global default styles for all languages
+        self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT,     "face:%(font)s,fore:#FFFFFF,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,  "face:%(font)s,back:#C0C0C0,face:%(font)s,size:%(size2)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, "face:%(font)s" % faces)
+        self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,  "face:%(font)s,fore:#000000,back:#70FFFF,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,    "face:%(font)s,fore:#000000,back:#FF0000,size:%(size)d" % faces)
+
+
+    def OnKeyPressed(self, event):
+        if self.CallTipActive():
+            self.CallTipCancel()
+        key = event.KeyCode()
+        if False:  # key == wx.WXK_SPACE and event.ControlDown():
+            pos = self.GetCurrentPos()
+            # Tips
+            if event.ShiftDown():
+                self.CallTipSetBackground("yellow")
+                self.CallTipShow(pos, 'param1, param2')
+            # Code completion
+            else:
+                #lst = []
+                #for x in range(50000):
+                #    lst.append('%05d' % x)
+                #st = string.join(lst)
+                #print len(st)
+                #self.AutoCompShow(0, st)
+
+                kw = keyword.kwlist[:]
+                kw.append("zzzzzz")
+                kw.append("aaaaa")
+                kw.append("__init__")
+                kw.append("zzaaaaa")
+                kw.append("zzbaaaa")
+                kw.append("this_is_a_longer_value")
+                kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")
+
+                kw.sort()  # Python sorts are case sensitive
+                self.AutoCompSetIgnoreCase(False)  # so this needs to match
+
+                self.AutoCompShow(0, string.join(kw))
+        elif key == wx.WXK_RETURN:
+            self.DoIndent()
+        else:
+            STCTextEditor.TextCtrl.OnKeyPressed(self, event)
+
+
+    def DoIndent(self):
+        self.AddText('\n')
+        # Need to do a default one for all languges
+
+
+    def OnMarginClick(self, evt):
+        # fold and unfold as needed
+        if evt.GetMargin() == 2:
+            if evt.GetShift() and evt.GetControl():
+                lineCount = self.GetLineCount()
+                expanding = True
+
+                # find out if we are folding or unfolding
+                for lineNum in range(lineCount):
+                    if self.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG:
+                        expanding = not self.GetFoldExpanded(lineNum)
+                        break;
+
+                self.ToggleFoldAll(expanding)
+            else:
+                lineClicked = self.LineFromPosition(evt.GetPosition())
+                if self.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
+                    if evt.GetShift():
+                        self.SetFoldExpanded(lineClicked, True)
+                        self.Expand(lineClicked, True, True, 1)
+                    elif evt.GetControl():
+                        if self.GetFoldExpanded(lineClicked):
+                            self.SetFoldExpanded(lineClicked, False)
+                            self.Expand(lineClicked, False, True, 0)
+                        else:
+                            self.SetFoldExpanded(lineClicked, True)
+                            self.Expand(lineClicked, True, True, 100)
+                    else:
+                        self.ToggleFold(lineClicked)
+
+        elif evt.GetMargin() == 0:
+            #This is used to toggle breakpoints via the debugger service.
+            db_service = wx.GetApp().GetService(DebuggerService.DebuggerService)
+            if db_service:
+                db_service.OnToggleBreakpoint(evt, line=self.LineFromPosition(evt.GetPosition()))
+            
+
+    def OnUpdateUI(self, evt):
+        braces = self.GetMatchingBraces()
+        
+        # check for matching braces
+        braceAtCaret = -1
+        braceOpposite = -1
+        charBefore = None
+        caretPos = self.GetCurrentPos()
+        if caretPos > 0:
+            charBefore = self.GetCharAt(caretPos - 1)
+            styleBefore = self.GetStyleAt(caretPos - 1)
+
+        # check before
+        if charBefore and chr(charBefore) in braces:
+            braceAtCaret = caretPos - 1
+
+        # check after
+        if braceAtCaret < 0:
+            charAfter = self.GetCharAt(caretPos)
+            styleAfter = self.GetStyleAt(caretPos)
+            if charAfter and chr(charAfter) in braces:
+                braceAtCaret = caretPos
+
+        if braceAtCaret >= 0:
+            braceOpposite = self.BraceMatch(braceAtCaret)
+
+        if braceAtCaret != -1  and braceOpposite == -1:
+            self.BraceBadLight(braceAtCaret)
+        else:
+            self.BraceHighlight(braceAtCaret, braceOpposite)
+
+        evt.Skip()
+
+
+    def ToggleFoldAll(self, expand = True, topLevelOnly = False):
+        i = 0
+        lineCount = self.GetLineCount()
+        while i < lineCount:
+            if not topLevelOnly or (topLevelOnly and self.GetFoldLevel(i) & wx.stc.STC_FOLDLEVELNUMBERMASK  == wx.stc.STC_FOLDLEVELBASE):
+                if (expand and self.CanLineExpand(i)) or (not expand and self.CanLineCollapse(i)):
+                    self.ToggleFold(i)
+            i = i + 1
+
+
+    def CanLineExpand(self, line):
+        return not self.GetFoldExpanded(line)
+
+
+    def CanLineCollapse(self, line):
+        return self.GetFoldExpanded(line) and self.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG
+
+
+    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
+        lastChild = self.GetLastChild(line, level)
+        line = line + 1
+        while line <= lastChild:
+            if force:
+                if visLevels > 0:
+                    self.ShowLines(line, line)
+                else:
+                    self.HideLines(line, line)
+            else:
+                if doExpand:
+                    self.ShowLines(line, line)
+
+            if level == -1:
+                level = self.GetFoldLevel(line)
+
+            if level & wx.stc.STC_FOLDLEVELHEADERFLAG:
+                if force:
+                    if visLevels > 1:
+                        self.SetFoldExpanded(line, True)
+                    else:
+                        self.SetFoldExpanded(line, False)
+                    line = self.Expand(line, doExpand, force, visLevels-1)
+
+                else:
+                    if doExpand and self.GetFoldExpanded(line):
+                        line = self.Expand(line, True, force, visLevels-1)
+                    else:
+                        line = self.Expand(line, False, force, visLevels-1)
+            else:
+                line = line + 1;
+
+        return line
+
+
diff --git a/wxPython/samples/ide/activegrid/tool/DebuggerHarness.py b/wxPython/samples/ide/activegrid/tool/DebuggerHarness.py
new file mode 100644 (file)
index 0000000..a12b8e5
--- /dev/null
@@ -0,0 +1,690 @@
+#----------------------------------------------------------------------------
+# Name:         DebuggerHarness.py
+# Purpose:      
+#
+# Author:       Matt Fryer
+#
+# Created:      7/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+import bdb
+import sys
+import SimpleXMLRPCServer
+import threading
+import xmlrpclib
+import os
+import types
+import Queue
+import traceback
+import inspect
+from xml.dom.minidom import getDOMImplementation
+import atexit
+import pickle
+
+if sys.platform.startswith("win"):
+    import win32api
+    _WINDOWS = True
+else:
+    _WINDOWS = False
+    
+_VERBOSE = False
+_DEBUG_DEBUGGER = False
+
+class Adb(bdb.Bdb):
+
+    def __init__(self, harness, queue):
+        bdb.Bdb.__init__(self)
+        self._harness = harness
+        self._userBreak = False
+        self._queue = queue
+        self._knownCantExpandFiles = {} 
+        self._knownExpandedFiles = {} 
+    
+    def getLongName(self, filename):
+        if not _WINDOWS:
+            return filename
+        if self._knownCantExpandFiles.get(filename):
+            return filename
+        if self._knownExpandedFiles.get(filename):
+            return self._knownExpandedFiles.get(filename)
+        try:
+            newname = win32api.GetLongPathName(filename)
+            self._knownExpandedFiles[filename] = newname
+            return newname
+        except:
+            self._knownCantExpandFiles[filename] = filename
+            return filename
+            
+    def canonic(self, orig_filename):
+        if orig_filename == "<" + orig_filename[1:-1] + ">":
+            return orig_filename
+        filename = self.getLongName(orig_filename)    
+
+        canonic = self.fncache.get(filename)
+        if not canonic:
+            canonic = os.path.abspath(filename)
+            canonic = os.path.normcase(canonic)
+            self.fncache[filename] = canonic
+        return canonic
+    
+     
+    # Overriding this so that we continue to trace even if no breakpoints are set.
+    def set_continue(self):
+        self.stopframe = self.botframe
+        self.returnframe = None
+        self.quitting = 0  
+                         
+    def do_clear(self, arg):
+        bdb.Breakpoint.bpbynumber[int(arg)].deleteMe()
+        
+    def user_line(self, frame):
+        if self.in_debugger_code(frame):
+            self.set_step()
+            return
+        message = self.__frame2message(frame)
+        self._harness.interaction(message, frame, "")
+        
+    def user_call(self, frame, argument_list):
+        if self.in_debugger_code(frame):
+            self.set_step()
+            return
+        if self.stop_here(frame):
+            message = self.__frame2message(frame)
+            self._harness.interaction(message, frame, "")
+        
+    def user_return(self, frame, return_value):
+        if self.in_debugger_code(frame):
+            self.set_step()
+            return
+        message = self.__frame2message(frame)
+        self._harness.interaction(message, frame, "")
+        
+    def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
+        frame.f_locals['__exception__'] = exc_type, exc_value
+        if type(exc_type) == type(''):
+            exc_type_name = exc_type
+        else: 
+            exc_type_name = exc_type.__name__
+        message = "Exception occured: " + repr(exc_type_name) + " See locals.__exception__ for details."
+        traceback.print_exception(exc_type, exc_value, exc_traceback)
+        self._harness.interaction(message, frame, message)
+
+    def in_debugger_code(self, frame):
+        if _DEBUG_DEBUGGER: return False
+        message = self.__frame2message(frame)
+        return message.count('DebuggerHarness') > 0
+        
+    def frame2message(self, frame):
+        return self.__frame2message(frame)
+        
+    def __frame2message(self, frame):
+        code = frame.f_code
+        filename = code.co_filename
+        lineno = frame.f_lineno
+        basename = os.path.basename(filename)
+        message = "%s:%s" % (basename, lineno)
+        if code.co_name != "?":
+            message = "%s: %s()" % (message, code.co_name)
+        return message
+        
+    def runFile(self, fileName):
+        self.reset()
+        #global_dict = {}
+        #global_dict['__name__'] = '__main__'
+        try:
+            fileToRun = open(fileName, mode='r')
+            if _VERBOSE: print "Running file ", fileName
+            sys.settrace(self.trace_dispatch)
+            import __main__
+            exec fileToRun in __main__.__dict__,__main__.__dict__
+        except SystemExit:
+            pass
+        except:
+            tp, val, tb = sys.exc_info()
+            traceback.print_exception(tp, val, tb)
+           
+        sys.settrace(None)
+        self.quitting = 1
+        #global_dict.clear()
+    def trace_dispatch(self, frame, event, arg):
+        if self.quitting:
+            return # None
+        # Check for ui events
+        self.readQueue()
+        if event == 'line':
+            return self.dispatch_line(frame)
+        if event == 'call':
+            return self.dispatch_call(frame, arg)
+        if event == 'return':
+            return self.dispatch_return(frame, arg)
+        if event == 'exception':
+            return self.dispatch_exception(frame, arg)
+        print 'Adb.dispatch: unknown debugging event:', `event`
+        return self.trace_dispatch
+     
+    def readQueue(self):
+        while self._queue.qsize():
+            try:
+                item = self._queue.get_nowait()
+                if item.kill():
+                    self._harness.do_exit(kill=True)
+                elif item.breakHere():
+                    self._userBreak = True
+                elif item.hasBreakpoints():
+                    self.set_all_breakpoints(item.getBreakpoints())
+            except Queue.Empty:
+                pass
+                                  
+    def set_all_breakpoints(self, dict):
+        self.clear_all_breaks()
+        for fileName in dict.keys():
+            lineList = dict[fileName]
+            for lineNumber in lineList:
+                
+                if _VERBOSE: print "Setting break at line ", str(lineNumber), " in file ", self.canonic(fileName)
+                self.set_break(fileName, int(lineNumber))
+        return ""
+                
+    def stop_here(self, frame):
+        if( self._userBreak ):
+            return True
+        
+
+        # (CT) stopframe may now also be None, see dispatch_call.
+        # (CT) the former test for None is therefore removed from here.
+        if frame is self.stopframe:
+            return True
+        while frame is not None and frame is not self.stopframe:
+            if frame is self.botframe:
+                return True
+            frame = frame.f_back
+        return False
+
+class BreakNotify(object):
+    def __init__(self, bps=None, break_here=False, kill=False):
+        self._bps = bps
+        self._break_here = break_here
+        self._kill = kill
+        
+    def breakHere(self):
+        return self._break_here
+        
+    def kill(self):
+        return self._kill
+        
+    def getBreakpoints(self):
+        return self._bps
+    
+    def hasBreakpoints(self):
+        return (self._bps != None)
+
+class AGXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
+    def __init__(self, address, logRequests=0):
+        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests)               
+        
+class BreakListenerThread(threading.Thread):
+    def __init__(self, host, port, queue):
+        threading.Thread.__init__(self)
+        self._host = host
+        self._port = int(port)
+        self._keepGoing = True
+        self._queue = queue
+        self._server = AGXMLRPCServer((self._host, self._port), logRequests=0)
+        self._server.register_function(self.update_breakpoints)
+        self._server.register_function(self.break_requested)
+        self._server.register_function(self.die)
+    
+    def break_requested(self):
+        bn = BreakNotify(break_here=True)
+        self._queue.put(bn)
+        return ""
+        
+    def update_breakpoints(self, pickled_Binary_bpts):
+        dict = pickle.loads(pickled_Binary_bpts.data)
+        bn = BreakNotify(bps=dict)
+        self._queue.put(bn)
+        return ""
+        
+    def die(self):
+        bn = BreakNotify(kill=True)
+        self._queue.put(bn)
+        return ""
+            
+    def run(self):
+        while self._keepGoing:
+            try:
+                self._server.handle_request() 
+            except:
+                if _VERBOSE:
+                    tp, val, tb = sys.exc_info()
+                    print "Exception in BreakListenerThread.run():", str(tp), str(val)
+                self._keepGoing = False
+       
+    def AskToStop(self):
+        self._keepGoing = False
+        if type(self._server) is not types.NoneType:
+            if _VERBOSE: print "Before calling server close on breakpoint server"
+            self._server.server_close()
+            if _VERBOSE: print "Calling server close on breakpoint server"
+                           
+        
+class DebuggerHarness(object):
+    
+    def __init__(self):
+        # Host and port for debugger-side RPC server
+        self._hostname = sys.argv[1]
+        self._portNumber = int(sys.argv[2])
+        # Name the gui proxy object is registered under
+        self._breakPortNumber = int(sys.argv[3])
+        # Host and port where the gui proxy can be found.
+        self._guiHost = sys.argv[4]
+        self._guiPort = int(sys.argv[5])
+        # Command to debug.
+        self._command = sys.argv[6]
+        # Strip out the harness' arguments so that the process we run will see argv as if
+        # it was called directly.
+        sys.argv = sys.argv[6:]
+        self._currentFrame = None
+        self._wait = False
+        # Connect to the gui-side RPC server.
+        self._guiServerUrl = 'http://' + self._guiHost + ':' + str(self._guiPort) + '/'
+        if _VERBOSE: print "Connecting to gui server at ", self._guiServerUrl
+        self._guiServer = xmlrpclib.ServerProxy(self._guiServerUrl,allow_none=1)
+    
+        # Start the break listener
+        self._breakQueue = Queue.Queue(50)
+        self._breakListener = BreakListenerThread(self._hostname, self._breakPortNumber, self._breakQueue)        
+        self._breakListener.start()
+        # Create the debugger.
+        self._adb = Adb(self, self._breakQueue)
+        
+        # Create the debugger-side RPC Server and register functions for remote calls.
+        self._server = AGXMLRPCServer((self._hostname, self._portNumber), logRequests=0)
+        self._server.register_function(self.set_step)
+        self._server.register_function(self.set_continue)
+        self._server.register_function(self.set_next)
+        self._server.register_function(self.set_return)
+        self._server.register_function(self.set_breakpoint)
+        self._server.register_function(self.clear_breakpoint)
+        self._server.register_function(self.set_all_breakpoints)
+        self._server.register_function(self.attempt_introspection)
+        self._server.register_function(self.add_watch)
+        
+        self.message_frame_dict = {}
+        self.introspection_list = []
+        atexit.register(self.do_exit)
+        
+    def run(self):
+        self._adb.runFile(self._command)
+        self.do_exit(kill=True)
+
+        
+    def do_exit(self, kill=False):
+        self._adb.set_quit()
+        self._breakListener.AskToStop()
+        self._server.server_close()
+        try:
+            self._guiServer.quit()
+        except:
+            pass
+        if kill:
+            try:
+                sys.exit()
+            except:
+                pass
+        
+    def set_breakpoint(self, fileName, lineNo):
+        self._adb.set_break(fileName, lineNo)
+        return ""
+        
+    def set_all_breakpoints(self, dict):
+        self._adb.clear_all_breaks()
+        for fileName in dict.keys():
+            lineList = dict[fileName]
+            for lineNumber in lineList:
+                self._adb.set_break(fileName, int(lineNumber))
+                if _VERBOSE: print "Setting break at ", str(lineNumber), " in file ", fileName
+        return ""
+                                        
+    def clear_breakpoint(self, fileName, lineNo):
+        self._adb.clear_break(fileName, lineNo)
+        return ""
+
+    def add_watch(self,  name,  text, frame_message, run_once): 
+        if len(frame_message) > 0:
+            frame = self.message_frame_dict[frame_message] 
+            try:
+                item = eval(text, frame.f_globals, frame.f_locals)
+                return self.get_watch_document(item, name)
+            except: 
+                tp, val, tb = sys.exc_info()
+                return self.get_exception_document(tp, val, tb) 
+        return ""
+                
+    def attempt_introspection(self, frame_message, chain):
+        try:
+            frame = self.message_frame_dict[frame_message]
+            if frame:
+                name = chain.pop(0)
+                if name == 'globals':
+                    item = frame.f_globals
+                elif name == 'locals':
+                    item = frame.f_locals
+                for name in chain:
+                    item = self.getNextItem(item, name)
+                return self.get_introspection_document(item, name)
+        except:
+            tp, val, tb = sys.exc_info()
+            traceback.print_exception(tp, val, tb)
+        return self.get_empty_introspection_document()   
+        
+    def getNextItem(self, link, identifier):
+        tp = type(link)
+        if self.isTupleized(identifier):
+            return self.deTupleize(link, identifier)
+        else:
+            if tp == types.DictType or tp == types.DictProxyType:
+                return link[identifier]
+            else:
+                if hasattr(link, identifier):
+                    return getattr(link, identifier)
+            if _VERBOSE or True: print "Failed to find link ", identifier, " on thing: ", self.saferepr(link), " of type ", repr(type(link))
+            return None
+        
+    def isPrimitive(self, item):
+        tp = type(item)
+        return tp is types.IntType or tp is types.LongType or tp is types.FloatType \
+            or tp is types.BooleanType or tp is types.ComplexType \
+            or tp is types.StringType  
+    
+    def isTupleized(self, value):
+        return value.count('[')
+                             
+    def deTupleize(self, link, string1):
+        try:
+            start = string1.find('[')
+            end = string1.find(']')
+            num = int(string1[start+1:end])
+            return link[num]
+        except:
+            tp,val,tb = sys.exc_info()
+            if _VERBOSE: print "Got exception in deTupleize: ", val
+            return None
+                        
+    def wrapAndCompress(self, stringDoc):
+        import bz2
+        return xmlrpclib.Binary(bz2.compress(stringDoc))
+
+    def get_empty_introspection_document(self):
+        doc = getDOMImplementation().createDocument(None, "replacement", None)
+        return self.wrapAndCompress(doc.toxml())
+
+    def get_watch_document(self, item, identifier):   
+        doc = getDOMImplementation().createDocument(None, "watch", None)
+        top_element = doc.documentElement
+        self.addAny(top_element, identifier, item, doc, 2)
+        return self.wrapAndCompress(doc.toxml())
+        
+    def get_introspection_document(self, item, identifier):   
+        doc = getDOMImplementation().createDocument(None, "replacement", None)
+        top_element = doc.documentElement
+        self.addAny(top_element, identifier, item, doc, 2)
+        return self.wrapAndCompress(doc.toxml())
+   
+    def get_exception_document(self, name, tp, val, tb):                  
+        stack = traceback.format_exception(tp, val, tb)
+        wholeStack = ""
+        for line in stack:
+            wholeStack += line
+        doc = getDOMImplementation().createDocument(None, "watch", None)
+        top_element = doc.documentElement
+        item_node = doc.createElement("dict_nv_element")  
+        item_node.setAttribute('value', wholeStack)
+        item_node.setAttribute('name', str(name))    
+        top_element.appendChild(item_node)
+     
+    def addAny(self, top_element, name, item, doc, ply):
+        tp = type(item)
+        if ply < 1:
+            self.addNode(top_element,name, self.saferepr(item), doc)
+        elif tp is types.TupleType or tp is types.ListType:
+            self.addTupleOrList(top_element, name, item, doc, ply - 1)           
+        elif tp is types.DictType or tp is types.DictProxyType: 
+            self.addDict(top_element, name, item, doc, ply -1)
+        elif inspect.ismodule(item): 
+            self.addModule(top_element, name, item, doc, ply -1)
+        elif inspect.isclass(item) or tp is types.InstanceType:
+            self.addClass(top_element, name, item, doc, ply -1)
+        #elif hasattr(item, '__dict__'):
+        #    self.addDictAttr(top_element, name, item, doc, ply -1)
+        elif hasattr(item, '__dict__'):
+            self.addDict(top_element, name, item.__dict__, doc, ply -1)
+        else:
+            self.addNode(top_element,name, self.saferepr(item), doc) 
+            
+    def addTupleOrList(self, top_node, name, tupple, doc, ply):
+        tupleNode = doc.createElement('tuple')
+        tupleNode.setAttribute('name', str(name))
+        tupleNode.setAttribute('value', str(type(tupple)))      
+        top_node.appendChild(tupleNode)
+        count = 0
+        for item in tupple:
+            self.addAny(tupleNode, name +'[' + str(count) + ']',item, doc, ply -1)
+            count += 1
+            
+        
+    def getFrameXML(self, base_frame):
+        doc = getDOMImplementation().createDocument(None, "stack", None)
+        top_element = doc.documentElement
+
+        stack = []
+        frame = base_frame
+        while frame is not None:
+            if((frame.f_code.co_filename.count('DebuggerHarness.py') == 0) or _DEBUG_DEBUGGER):
+                stack.append(frame)
+            frame = frame.f_back
+        stack.reverse()
+        self.message_frame_dict = {}
+        for f in stack:
+            self.addFrame(f,top_element, doc)
+        return doc.toxml()
+        
+    def addFrame(self, frame, root_element, document):
+        frameNode = document.createElement('frame')
+        root_element.appendChild(frameNode)
+        
+        code = frame.f_code
+        filename = code.co_filename
+        frameNode.setAttribute('file', str(filename))    
+        frameNode.setAttribute('line', str(frame.f_lineno))
+        message = self._adb.frame2message(frame)
+        frameNode.setAttribute('message', message)
+        #print "Frame: %s %s %s" %(message, frame.f_lineno, filename)
+        self.message_frame_dict[message] = frame
+        self.addDict(frameNode, "locals", frame.f_locals, document, 2)        
+        self.addNode(frameNode, "globals", "",  document)
+                    
+    def getRepr(self, varName, globals, locals):
+        try:
+            return repr(eval(varName, globals, locals))
+        except:
+            return 'Error: Could not recover value.'
+            
+    def addNode(self, parent_node, name, value, document):
+        item_node = document.createElement("dict_nv_element")  
+        item_node.setAttribute('value', self.saferepr(value))
+        item_node.setAttribute('name', str(name))    
+        parent_node.appendChild(item_node)
+        
+    def addDictAttr(self, root_node, name, thing, document, ply):
+        dict_node = document.createElement('thing') 
+        root_node.setAttribute('name', name)
+        root_node.setAttribute('value', str(type(dict)) + " add attr")
+        self.addDict(root_node, name, thing.__dict__, document, ply) # Not decreminting ply
+    
+    def saferepr(self, thing):
+        try:
+            return repr(thing)
+        except:
+            tp, val, tb = sys.exc_info()
+            return repr(val)
+                    
+    def addDict(self, root_node, name, dict, document, ply):
+        dict_node = document.createElement('dict') 
+        dict_node.setAttribute('name', name)
+        dict_node.setAttribute('value', str(type(dict)) + " add dict")
+        root_node.appendChild(dict_node)
+        for key in dict.keys():
+            strkey = str(key)
+            try:
+                self.addAny(dict_node, strkey, dict[key], document, ply-1)
+            except:
+                tp,val,tb=sys.exc_info()
+                if _VERBOSE:
+                    print "Error recovering key: ", str(key), " from node ", str(name), " Val = ", str(val)
+                    traceback.print_exception(tp, val, tb)
+                self.addAny(dict_node, strkey, "Exception getting " + str(name) + "[" + strkey + "]: " + str(val), document, ply -1)
+                    
+    def addClass(self, root_node, name, class_item, document, ply):
+         item_node = document.createElement('class') 
+         item_node.setAttribute('name', str(name)) 
+         root_node.appendChild(item_node)
+         try:
+             if hasattr(class_item, '__dict__'):
+                self.addAny(item_node, '__dict__', class_item.__dict__, document, ply -1)
+         except:
+             tp,val,tb=sys.exc_info()
+             if _VERBOSE:
+                traceback.print_exception(tp, val, tb)
+             self.addAny(item_node, '__dict__', "Exception getting __dict__: " + str(val), document, ply -1)
+         try:
+             if hasattr(class_item, '__name__'):
+                self.addAny(item_node,'__name__',class_item.__name__, document, ply -1)
+         except:
+             tp,val,tb=sys.exc_info()
+             if _VERBOSE:
+                traceback.print_exception(tp, val, tb)
+             self.addAny(item_node,'__name__',"Exception getting class.__name__: " + val, document, ply -1)
+         try:
+             if hasattr(class_item, '__module__'):
+                self.addAny(item_node, '__module__', class_item.__module__, document, ply -1)
+         except:
+             tp,val,tb=sys.exc_info()
+             if _VERBOSE:
+                traceback.print_exception(tp, val, tb)
+             self.addAny(item_node, '__module__', "Exception getting class.__module__: " + val, document, ply -1)
+         try:
+             if hasattr(class_item, '__doc__'):
+                self.addAny(item_node, '__doc__', class_item.__doc__, document, ply -1)
+         except:
+             tp,val,tb=sys.exc_info()
+             if _VERBOSE:
+                traceback.print_exception(tp, val, tb)
+             self.addAny(item_node, '__doc__', "Exception getting class.__doc__: " + val, document, ply -1)
+         try:
+             if hasattr(class_item, '__bases__'):
+                self.addAny(item_node, '__bases__', class_item.__bases__, document, ply -1)
+         except:
+             tp,val,tb=sys.exc_info()
+             if _VERBOSE:
+                traceback.print_exception(tp, val, tb)
+             self.addAny(item_node, '__bases__', "Exception getting class.__bases__: " + val, document, ply -1)
+         
+    def addModule(self, root_node, name, module_item, document, ply):
+         item_node = document.createElement('module') 
+         item_node.setAttribute('name', str(name)) 
+         root_node.appendChild(item_node)
+         try:
+             if hasattr(module_item, '__file__'):
+                self.addAny(item_node, '__file__', module_item.__file__, document, ply -1)
+         except:
+             pass
+         try:
+             if hasattr(module_item, '__doc__'):
+                self.addAny(item_node,'__doc__', module_item.__doc__, document, ply -1)
+         except:
+             pass
+                   
+    # The debugger calls this method when it reaches a breakpoint.
+    def interaction(self, message, frame, info):
+        if _VERBOSE:
+            print 'hit debug side interaction'
+        self._userBreak = False
+
+        self._currentFrame = frame
+        done = False
+        while not done:
+            try:
+                import bz2
+                xml = self.getFrameXML(frame)
+                arg = xmlrpclib.Binary(bz2.compress(xml))
+                if _VERBOSE:
+                    print '============== calling gui side interaction============'
+                self._guiServer.interaction(xmlrpclib.Binary(message), arg, info)
+                if _VERBOSE:
+                    print 'after interaction'
+                done = True
+            except:
+                tp, val, tb = sys.exc_info()
+                if True or _VERBOSE:
+                    print 'Error contacting GUI server!: '
+                    try:
+                        traceback.print_exception(tp, val, tb)
+                    except:
+                        print "Exception printing traceback", 
+                        tp, val, tb = sys.exc_info()
+                        traceback.print_exception(tp, val, tb)
+                done = False
+        # Block while waiting to be called back from the GUI. Eventually, self._wait will
+        # be set false by a function on this side. Seems pretty lame--I'm surprised it works.
+        self.waitForRPC()
+        
+
+    def waitForRPC(self):
+        self._wait = True
+        while self._wait :
+            try:
+                if _VERBOSE:
+                    print "+++ in harness wait for rpc, before handle_request"
+                self._server.handle_request()
+                if _VERBOSE:
+                    print "+++ in harness wait for rpc, after handle_request"
+            except:
+                if _VERBOSE:
+                    tp, val, tb = sys.exc_info()
+                    print "Got waitForRpc exception : ", repr(tp), ": ", val
+            #time.sleep(0.1)
+    
+    def set_step(self):
+        self._adb.set_step()
+        self._wait = False
+        return ""
+        
+    def set_continue(self):
+        self._adb.set_continue()
+        self._wait = False
+        return ""
+        
+    def set_next(self):
+        self._adb.set_next(self._currentFrame)
+        self._wait = False
+        return ""
+        
+    def set_return(self):
+        self._adb.set_return(self._currentFrame)
+        self._wait = False
+        return ""        
+     
+if __name__ == '__main__':
+    try:
+        harness = DebuggerHarness()
+        harness.run()
+    except SystemExit:
+        print "Exiting..."
+    except:
+        tp, val, tb = sys.exc_info()
+        traceback.print_exception(tp, val, tb)
diff --git a/wxPython/samples/ide/activegrid/tool/DebuggerService.py b/wxPython/samples/ide/activegrid/tool/DebuggerService.py
new file mode 100644 (file)
index 0000000..ef4a37a
--- /dev/null
@@ -0,0 +1,2275 @@
+#----------------------------------------------------------------------------
+# Name:         DebuggerService.py
+# Purpose:      Debugger Service for Python.
+#
+# Author:       Matt Fryer
+#
+# Created:      12/9/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.intctrl
+import wx.lib.docview
+import wx.lib.dialogs
+import wx.gizmos
+import wx._core
+import wx.lib.pydocview
+import Service
+import STCTextEditor
+import CodeEditor
+import PythonEditor
+from IDE import ACTIVEGRID_BASE_IDE
+if not ACTIVEGRID_BASE_IDE:
+    import ProcessModelEditor
+import wx.lib.scrolledpanel as scrolled
+import sys
+import time
+import SimpleXMLRPCServer
+import xmlrpclib
+import os
+import threading
+import process
+import Queue
+import SocketServer
+import ProjectEditor
+import types
+from xml.dom.minidom import parse, parseString
+import bz2
+import pickle
+import DebuggerHarness
+import traceback
+import StringIO
+if wx.Platform == '__WXMSW__':
+    import win32api
+    _WINDOWS = True
+else:
+    _WINDOWS = False
+_ = wx.GetTranslation
+
+_VERBOSE = False
+_WATCHES_ON = False
+
+# 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
+#       read.
+class OutputReaderThread(threading.Thread):
+    def __init__(self, file, callback_function, callbackOnExit=None, accumulate=True):
+        threading.Thread.__init__(self)
+        self._file = file
+        self._callback_function = callback_function
+        self._keepGoing = True
+        self._lineCount = 0
+        self._accumulate = accumulate
+        self._callbackOnExit = callbackOnExit
+         
+    def run(self):
+        file = self._file
+        start = time.time()
+        output = ""
+        while self._keepGoing:
+            try:
+                # This could block--how to handle that?
+                text = file.readline()
+                if text == '' or text == None:
+                    self._keepGoing = False
+                elif not self._accumulate:
+                    self._callback_function(text)
+                else:
+                    # 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 
+                # send the first hundred lines back as they come in.
+                if self._lineCount < 100:    
+                    self._callback_function(output)
+                    self._lineCount += 1
+                    output = "" 
+                elif time.time() - start > 0.25:
+                    try:
+                        self._callback_function(output) 
+                    except wx._core.PyDeadObjectError:
+                        # GUI was killed while we were blocked.
+                        self._keepGoing = False
+                    start = time.time()
+                    output = "" 
+            except:
+                tp, val, tb = sys.exc_info()
+                print "Exception in OutputReaderThread.run():", tp, val
+                self._keepGoing = False
+        if self._callbackOnExit:
+            try:
+                self._callbackOnExit()
+            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:
+    
+    def GetPythonExecutablePath():
+        config = wx.ConfigBase_Get()
+        path = config.Read("ActiveGridPythonLocation")
+        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"))
+        return None
+    GetPythonExecutablePath = staticmethod(GetPythonExecutablePath)   
+     
+    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 + '\"'
+        #Better way to do this? Quotes needed for windows file paths.
+        if(arg1 != None):
+            self._cmd += ' \"' + arg1 + '\"'
+        if(arg2 != None):
+            self._cmd += ' \"' + arg2 + '\"'
+        if(arg3 != None):
+            self._cmd += ' \"' + arg3 + '\"'
+        if(arg4 != None):
+            self._cmd += ' \"' + arg4 + '\"'
+        if(arg5 != None):
+            self._cmd += ' \"' + arg5 + '\"'
+        if(arg6 != None):
+            self._cmd += ' \"' + arg6 + '\"'
+        if(arg7 != None):
+            self._cmd += ' \"' + arg7 + '\"'
+        if(arg8 != None):
+            self._cmd += ' \"' + arg8 + '\"'
+        if(arg9 != None):
+            self._cmd += ' \"' + 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
+        #stdinput = process.IOBuffer()
+        #self._process = process.ProcessProxy(command, mode='b', cwd=startIn, stdin=stdinput)
+        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. 
+        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):
+        if(self._process != None):
+            self._process.kill()
+            self._process.close()
+            self._process = None
+        if(self._stdOutReader != None):
+            self._stdOutReader.AskToStop()
+        if(self._stdErrReader != None):
+            self._stdErrReader.AskToStop()
+        
+class RunCommandUI(wx.Panel):
+    
+    def __init__(self, parent, id, fileName):
+        wx.Panel.__init__(self, parent, id)
+        self._noteBook = parent
+        
+        self.KILL_PROCESS_ID = wx.NewId()
+        self.CLOSE_TAB_ID = wx.NewId()
+        
+        self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded)
+        
+        # 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" )
+        tb.SetToolBitmapSize((16,16))
+        sizer.Add(tb, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1)
+
+        close_bmp = getCloseBitmap()
+        tb.AddSimpleTool( self.CLOSE_TAB_ID, close_bmp, _('Close Window'))
+        wx.EVT_TOOL(self, self.CLOSE_TAB_ID, self.OnToolClicked)
+        
+        stop_bmp = getStopBitmap()
+        tb.AddSimpleTool(self.KILL_PROCESS_ID, stop_bmp, _("Stop the Run."))
+        wx.EVT_TOOL(self, self.KILL_PROCESS_ID, self.OnToolClicked)
+        
+        tb.Realize()
+        self._textCtrl = STCTextEditor.TextCtrl(self, wx.NewId()) #id)
+        sizer.Add(self._textCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
+        self._textCtrl.SetViewLineNumbers(False)
+        self._textCtrl.SetReadOnly(True)
+        if wx.Platform == '__WXMSW__':
+            font = "Courier New"
+        else:
+            font = "Courier"
+        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)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        
+        # Executor initialization
+        self._executor = Executor(fileName, self, callbackOnExit=self.ExecutorFinished)
+        self.Bind(EVT_UPDATE_STDTEXT, self.AppendText)
+        self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText)
+        
+    def __del__(self):
+        self._executor.DoStopExecution()
+                 
+    def Execute(self, initialArgs, startIn, environment):
+        self._executor.Execute(initialArgs, startIn, environment)
+    
+    def ExecutorFinished(self):
+        self._tb.EnableTool(self.KILL_PROCESS_ID, False)
+        nb = self.GetParent()
+        for i in range(0,nb.GetPageCount()):
+            if self == nb.GetPage(i):
+                text = nb.GetPageText(i)
+                newText = text.replace("Running", "Finished")
+                nb.SetPageText(i, newText)
+                break
+           
+    def StopExecution(self):
+        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.StyleClearAll()
+        self._textCtrl.AddText(event.value)
+        self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
+        self._textCtrl.SetFontColor(wx.BLACK)   
+        self._textCtrl.StyleClearAll()
+        self._textCtrl.SetReadOnly(True)
+        
+    #------------------------------------------------------------------------------
+    # Event handling
+    #-----------------------------------------------------------------------------
+    
+    def OnToolClicked(self, event):
+        id = event.GetId()
+        
+        if id == self.KILL_PROCESS_ID:
+            self._executor.DoStopExecution()
+            
+        elif id == self.CLOSE_TAB_ID:
+            self._executor.DoStopExecution()
+            index = self._noteBook.GetSelection()
+            self._noteBook.GetPage(index).Show(False)
+            self._noteBook.RemovePage(index)
+            
+    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)
+            
+        # FACTOR THIS INTO DocManager
+        openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+        for openDoc in openDocs:  
+            if(isinstance(openDoc.GetFirstView(), CodeEditor.CodeView)): 
+                openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers()    
+                
+        foundView.GetCtrl().MarkerAdd(lineNum -1, CodeEditor.CodeCtrl.CURRENT_LINE_MARKER_NUM)
+
+    def OnProcessEnded(self, evt):
+        self._executor.DoStopExecution()
+
+DEFAULT_PORT = 32032
+DEFAULT_HOST = 'localhost' 
+PORT_COUNT = 21      
+
+class DebugCommandUI(wx.Panel):
+    debuggerPortList = None   
+    debuggers = []
+    
+    def NotifyDebuggersOfBreakpointChange():
+        for debugger in DebugCommandUI.debuggers:
+            debugger.BreakPointChange()
+            
+    NotifyDebuggersOfBreakpointChange = staticmethod(NotifyDebuggersOfBreakpointChange)
+    def DebuggerRunning():
+        for debugger in DebugCommandUI.debuggers:
+            if debugger._executor:
+                return True
+        return False
+    DebuggerRunning = staticmethod(DebuggerRunning)
+    
+    def ShutdownAllDebuggers():
+        for debugger in DebugCommandUI.debuggers:
+            debugger.StopExecution()
+            
+    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)
+        if port in range(startingPort, startingPort + PORT_COUNT):
+            DebugCommandUI.debuggerPortList.append(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())
+
+        wx.Panel.__init__(self, parent, id)
+        
+        self._parentNoteBook = parent
+        self._command = command
+        self._textCtrl = None
+        self._service = service
+        self._executor = None
+        self.STEP_ID = wx.NewId()
+        self.CONTINUE_ID = wx.NewId()
+        self.STEP_OUT_ID = wx.NewId()
+        self.NEXT_ID = wx.NewId()
+        self.KILL_PROCESS_ID = wx.NewId()
+        self.CLOSE_WINDOW_ID = wx.NewId()
+        self.BREAK_INTO_DEBUGGER_ID = wx.NewId()
+        self.CLEAR_ID = wx.NewId()
+        self.ADD_WATCH_ID = wx.NewId()
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        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)
+         
+        tb.AddSeparator()
+               
+        next_bmp = getNextBitmap()
+        tb.AddSimpleTool( self.NEXT_ID, next_bmp, _("Step to next line"))
+        wx.EVT_TOOL(self, self.NEXT_ID, self.OnNext)
+        
+        step_bmp = getStepInBitmap()
+        tb.AddSimpleTool( self.STEP_ID, step_bmp, _("Step in"))
+        wx.EVT_TOOL(self, self.STEP_ID, self.OnSingleStep)
+        
+        stepOut_bmp = getStepReturnBitmap()
+        tb.AddSimpleTool(self.STEP_OUT_ID, stepOut_bmp, _("Stop at function return"))
+        wx.EVT_TOOL(self, self.STEP_OUT_ID, self.OnStepOut)
+
+        tb.AddSeparator()
+        if _WATCHES_ON:
+            watch_bmp = getAddWatchBitmap()
+            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)
+        
+        tb.Realize()
+        self.framesTab = None
+        self.DisableWhileDebuggerRunning()
+        self._notebook = wx.Notebook(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.LB_DEFAULT, "Debugger")
+        sizer.Add(self._notebook, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
+        self.consoleTab = self.MakeConsoleTab(self._notebook, wx.NewId(), None)
+        self.framesTab = self.MakeFramesTab(self._notebook, wx.NewId(), None)
+        self.breakPointsTab = self.MakeBreakPointsTab(self._notebook, wx.NewId(), None)
+        self._notebook.AddPage(self.consoleTab, "Output")
+        self._notebook.AddPage(self.framesTab, "Frames")
+        self._notebook.AddPage(self.breakPointsTab, "Break Points")
+        
+        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)
+        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.breakPointsTab.PopulateBPList()
+        
+    def __del__(self):
+        if self in DebugCommandUI.debuggers:
+            DebugCommandUI.debuggers.remove(self)
+        
+    def SwitchToOutputTab(self):
+        self._notebook.SetSelection(0)
+        
+    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.GetFirstView(), CodeEditor.CodeView)): 
+                openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers() 
+        if self.framesTab:   
+            self.framesTab.ClearWhileRunning()
+        #wx.GetApp().ProcessPendingEvents() #Yield(True)
+        
+    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)
+        if _WATCHES_ON:
+            self._tb.EnableTool(self.ADD_WATCH_ID, True)
+        self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False)
+        #if _WINDOWS:
+        #    wx.GetApp().GetTopWindow().RequestUserAttention()
+    def ExecutorFinished(self):          
+        if _VERBOSE: print "In ExectorFinished"
+        try:          
+            self.DisableAfterStop()
+        except wx._core.PyDeadObjectError:
+            pass
+        try:
+            nb = self.GetParent()
+            for i in range(0, nb.GetPageCount()):
+                if self == nb.GetPage(i):
+                    text = nb.GetPageText(i)
+                    newText = text.replace("Debugging", "Finished")
+                    nb.SetPageText(i, newText)
+                    if _VERBOSE: print "In ExectorFinished, changed tab title."
+                    break
+        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 SynchCurrentLine(self, filename, lineNum):
+        # FACTOR THIS INTO DocManager
+        self.DeleteCurrentLineMarkers()
+        
+        # Filename will be <string> if we're in a bit of code that was executed from
+        # a string (rather than a file). I haven't been able to get the original string
+        # for display.
+        if filename == '<string>':
+            return
+        foundView = None
+        openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+        for openDoc in openDocs:
+            # This ugliness to prevent comparison failing because the drive letter
+            # gets lowercased occasionally. Don't know why that happens or why  it
+            # only happens occasionally.
+            if DebuggerService.ComparePaths(openDoc.GetFilename(),filename):
+                foundView = openDoc.GetFirstView()
+                break
+                
+        if not foundView:
+            if _VERBOSE:
+                print "filename=", filename
+            doc = wx.GetApp().GetDocumentManager().CreateDocument(DebuggerService.ExpandPath(filename), wx.lib.docview.DOC_SILENT)
+            foundView = doc.GetFirstView()
+
+        if foundView:
+            foundView.GetFrame().SetFocus()
+            foundView.Activate()
+            foundView.GotoLine(lineNum)
+            startPos = foundView.PositionFromLine(lineNum)
+                
+        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.GetFirstView(), CodeEditor.CodeView)): 
+                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):
+        self._stopped = True
+        self.DisableAfterStop()
+        try:
+            self._callback.ServerClose()
+        except:
+            pass 
+        try:
+            if self._executor:
+                self._executor.DoStopExecution()
+                self._executor = None
+        except:
+            pass
+        self.DeleteCurrentLineMarkers()
+        DebugCommandUI.ReturnPortToPool(self._debuggerPort)
+        DebugCommandUI.ReturnPortToPool(self._guiPort)
+        DebugCommandUI.ReturnPortToPool(self._debuggerBreakPort)
+
+    def StopAndRemoveUI(self, event):
+        self.StopExecution(None)
+        if self in DebugCommandUI.debuggers:
+            DebugCommandUI.debuggers.remove(self)
+        index = self._parentNoteBook.GetSelection()
+        self._parentNoteBook.GetPage(index).Show(False)
+        self._parentNoteBook.RemovePage(index)
+
+    def GetConsoleTextControl(self):
+        return self._textCtrl
+        
+    def OnClearOutput(self, event):
+        self._textCtrl.SetReadOnly(False)
+        self._textCtrl.ClearAll()  
+        self._textCtrl.SetReadOnly(True)
+
+    def OnAddWatch(self, event):
+        if self.framesTab:
+            self.framesTab.OnWatch(event)
+                  
+    def MakeConsoleTab(self, parent, id, debugger):
+        panel = wx.Panel(parent, id)
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self._textCtrl = STCTextEditor.TextCtrl(panel, wx.NewId())
+        sizer.Add(self._textCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
+        self._textCtrl.SetViewLineNumbers(False)
+        self._textCtrl.SetReadOnly(True)
+        if wx.Platform == '__WXMSW__':
+            font = "Courier New"
+        else:
+            font = "Courier"
+        self._textCtrl.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font))
+        self._textCtrl.SetFontColor(wx.BLACK)
+        self._textCtrl.StyleClearAll()
+        panel.SetSizer(sizer)
+        sizer.Fit(panel)
+
+        return panel
+        
+    def MakeFramesTab(self, parent, id, debugger):
+        panel = FramesUI(parent, id, self)
+        return panel
+        
+    def MakeBreakPointsTab(self, parent, id, debugger):
+        panel = BreakpointsUI(parent, id, self)
+        return panel
+        
+    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.StyleClearAll()
+        self._textCtrl.AddText(event.value)
+        self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount())
+        self._textCtrl.SetFontColor(wx.BLACK)   
+        self._textCtrl.StyleClearAll()
+        self._textCtrl.SetReadOnly(True)
+        
+class BreakpointsUI(wx.Panel):
+    def __init__(self, parent, id, ui):
+        wx.Panel.__init__(self, parent, id)
+        self._ui = ui
+        self.currentItem = None
+        self.clearBPID = wx.NewId()
+        self.Bind(wx.EVT_MENU, self.ClearBreakPoint, id=self.clearBPID)
+        self.syncLineID = wx.NewId()
+        self.Bind(wx.EVT_MENU, self.SyncBPLine, id=self.syncLineID)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        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.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)
+
+        self.PopulateBPList()
+
+        p1.SetSizer(sizer)
+        sizer.Fit(p1)
+        p1.Layout()
+        
+    def PopulateBPList(self):
+        list = self._bpListCtrl
+        list.DeleteAllItems()
+
+        bps = wx.GetApp().GetService(DebuggerService).GetMasterBreakpointDict()
+        index = 0
+        for fileName in bps.keys():
+            shortFile = os.path.basename(fileName)
+            lines = bps[fileName]
+            if lines:
+                for line in lines:
+                    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")
+        menu.AppendItem(item)
+        item = wx.MenuItem(menu, self.syncLineID, "Goto Source Line")
+        menu.AppendItem(item)
+        self.PopupMenu(menu, event.GetPosition())
+        menu.Destroy()
+
+    def SyncBPLine(self, event):
+        if self.currentItem != -1:
+            list = self._bpListCtrl
+            fileName = list.GetItem(self.currentItem, 2).GetText()
+            lineNumber = list.GetItem(self.currentItem, 1).GetText()
+            self._ui.SynchCurrentLine( fileName, int(lineNumber) )  
+
+    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 )  
+        
+    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._show_code = show_code
+               
+class WatchDialog(wx.Dialog):
+    WATCH_ALL_FRAMES = "Watch in all frames"
+    WATCH_THIS_FRAME = "Watch in this frame only"
+    WATCH_ONCE = "Watch once and delete"
+    def __init__(self, parent, title, chain):
+        wx.Dialog.__init__(self, parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE)
+        self._chain = chain
+        self.label_2 = wx.StaticText(self, -1, "Watch Name:")
+        self._watchNameTextCtrl = wx.TextCtrl(self, -1, "")
+        self.label_3 = wx.StaticText(self, -1, "eval(", style=wx.ALIGN_RIGHT)
+        self._watchValueTextCtrl = wx.TextCtrl(self, -1, "")
+        self.label_4 = wx.StaticText(self, -1, ",frame.f_globals, frame.f_locals)")
+        self.radio_box_1 = wx.RadioBox(self, -1, "Watch Information", choices=[WatchDialog.WATCH_ALL_FRAMES, WatchDialog.WATCH_THIS_FRAME, WatchDialog.WATCH_ONCE], majorDimension=0, style=wx.RA_SPECIFY_ROWS)
+
+        self._okButton = wx.Button(self, wx.ID_OK, "OK", size=(75,-1))
+        self._okButton.SetDefault()
+        self._okButton.SetHelpText(_("The OK button completes the dialog"))
+        def OnOkClick(event):
+            if self._watchNameTextCtrl.GetValue() == "":
+                wx.MessageBox(_("You must enter a name for the watch."), _("Add Watch"))
+                return
+            if self._watchValueTextCtrl.GetValue() == "":
+                wx.MessageBox(_("You must enter some code to run for the watch."), _("Add Watch"))
+                return
+            self.EndModal(wx.ID_OK)
+        self.Bind(wx.EVT_BUTTON, OnOkClick, self._okButton)
+            
+        self._cancelButton = wx.Button(self, wx.ID_CANCEL, _("Cancel"), size=(75,-1))
+        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))
+        self.radio_box_1.SetSelection(0)
+
+    def __do_layout(self):
+        sizer_1 = wx.BoxSizer(wx.VERTICAL)
+        grid_sizer_4 = wx.FlexGridSizer(1, 3, 5, 5)
+        grid_sizer_2 = wx.FlexGridSizer(1, 2, 5, 5)
+        grid_sizer_2.Add(self.label_2, 0, wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE, 0)
+        grid_sizer_2.Add(self._watchNameTextCtrl, 0, wx.EXPAND, 0)
+        grid_sizer_2.AddGrowableCol(1)
+        sizer_1.Add(grid_sizer_2, 1, wx.EXPAND, 0)
+        grid_sizer_4.Add(self.label_3, 0, wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE, 0)
+        grid_sizer_4.Add(self._watchValueTextCtrl, 0, wx.EXPAND, 0)
+        grid_sizer_4.AddGrowableCol(1)
+        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.SetAutoLayout(True)
+        self.SetSizer(sizer_1)
+        self.Layout()
+        
+class FramesUI(wx.SplitterWindow):
+    def __init__(self, parent, id, ui):
+        wx.SplitterWindow.__init__(self, parent, id, style = wx.SP_3D)
+        self._ui = ui
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        self._p1 = p1 = wx.ScrolledWindow(self, -1)
+        p1.Bind(wx.EVT_SIZE, self.OnSize)
+
+        self._framesListCtrl = wx.ListCtrl(p1, -1, pos=wx.DefaultPosition, size=(250,150), style=wx.LC_REPORT)
+        sizer.Add(self._framesListCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
+        self._framesListCtrl.InsertColumn(0, "Frame")   
+        self._framesListCtrl.SetColumnWidth(0, 250)
+        self._framesListCtrl.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListRightClick)
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.ListItemSelected, self._framesListCtrl)
+        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.ListItemDeselected, self._framesListCtrl)
+
+        sizer2 = wx.BoxSizer(wx.VERTICAL)
+        self._p2 = p2 = wx.ScrolledWindow(self, -1)
+        p2.Bind(wx.EVT_SIZE, self.OnSize)
+
+        self._treeCtrl = wx.gizmos.TreeListCtrl(p2, -1, size=(530,250), style=wx.TR_DEFAULT_STYLE| wx.TR_FULL_ROW_HIGHLIGHT)
+        self._treeCtrl.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnRightClick)
+        sizer2.Add(self._framesListCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1)
+        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.SetItemText(self._root, "", 1)
+
+        self.SetMinimumPaneSize(20)
+        self.SplitVertically(p1, p2, 250)
+        self.currentItem = None
+        self.Layout()
+     
+    def OnRightClick(self, event):
+        #Refactor this...
+        self._introspectItem = event.GetItem()
+        self._parentChain = self.GetItemChain(event.GetItem())
+        watchOnly = len(self._parentChain) < 1
+        if not _WATCHES_ON and watchOnly:
+            return
+        menu = wx.Menu()
+        if not watchOnly:
+            if not hasattr(self, "introspectID"):
+                self.introspectID = wx.NewId()
+                self.Bind(wx.EVT_MENU, self.OnIntrospect, id=self.introspectID)
+            item = wx.MenuItem(menu, self.introspectID, "Attempt Introspection")
+            menu.AppendItem(item)
+            menu.AppendSeparator()
+        if _WATCHES_ON:
+            if not hasattr(self, "watchID"):
+                self.watchID = wx.NewId()
+                self.Bind(wx.EVT_MENU, self.OnWatch, id=self.watchID)
+            item = wx.MenuItem(menu, self.watchID, "Create a Watch")
+            menu.AppendItem(item)
+            menu.AppendSeparator()
+        if not watchOnly:
+            if not hasattr(self, "viewID"):
+                self.viewID = wx.NewId()
+                self.Bind(wx.EVT_MENU, self.OnView, id=self.viewID)
+            item = wx.MenuItem(menu, self.viewID, "View in Dialog")
+            menu.AppendItem(item)
+        offset = wx.Point(x=0, y=20)
+        menuSpot = event.GetPoint() + offset
+        self._treeCtrl.PopupMenu(menu, menuSpot)
+        menu.Destroy()
+        self._parentChain = None
+        self._introspectItem = None
+        
+    def GetItemChain(self, item):
+        parentChain = []
+        if item:
+            if _VERBOSE: print 'Exploding: %s' % self._treeCtrl.GetItemText(item, 0)
+            while item != self._root:
+                text = self._treeCtrl.GetItemText(item, 0)
+                if _VERBOSE: print "Appending ", text
+                parentChain.append(text)
+                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 OnWatch(self, event):
+        try:
+            if hasattr(self, '_parentChain'):
+                wd = WatchDialog(wx.GetApp().GetTopWindow(), "Add a Watch", self._parentChain)
+            else:
+                wd = WatchDialog(wx.GetApp().GetTopWindow(), "Add a Watch", None)
+            if wd.ShowModal() == wx.ID_OK:
+                name, text, send_frame, run_once = wd.GetSettings()
+                if send_frame:
+                    frameNode = self._stack[int(self.currentItem)]
+                    message = frameNode.getAttribute("message") 
+                else:
+                    message = ""
+                binType = self._ui._callback._debuggerServer.add_watch(name, text, message, run_once)
+                xmldoc = bz2.decompress(binType.data)
+                domDoc = parseString(xmldoc)
+                nodeList = domDoc.getElementsByTagName('watch')
+                if len(nodeList) == 1:
+                    watchValue = nodeList.item(0).getAttribute("message")
+        except:
+            tp, val, tb = sys.exc_info()
+            traceback.print_exception(tp, val, tb)   
+            
+    def OnIntrospect(self, event):
+        wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
+
+        try:
+            list = self._framesListCtrl
+            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)
+                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 OnSize(self, event):
+        self._treeCtrl.SetSize(self._p2.GetSize())
+        w,h = self._p1.GetClientSizeTuple()
+        self._framesListCtrl.SetDimensions(0, 0, w, h)
+
+    def ClearWhileRunning(self):
+        list = self._framesListCtrl
+        list.DeleteAllItems()
+        tree = self._treeCtrl     
+        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()
+
+    def OnSyncFrame(self, event):
+        list = self._framesListCtrl
+        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))
+        try:
+            domDoc = parseString(framesXML)
+            list = self._framesListCtrl
+            list.DeleteAllItems()
+            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.InsertStringItem(index, message)
+                self._stack.append(frameNode)
+                frame_count += 1
+            list.Select(frame_count) 
+            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 ListItemDeselected(self, event):
+        pass
+        
+    def ListItemSelected(self, event):
+        self.currentItem = event.m_itemIndex
+        frameNode = self._stack[int(self.currentItem)]
+        self.PopulateTreeFromFrameNode(frameNode)
+        # Temporarily doing this to test out automatically swicting to source line.
+        self.OnSyncFrame(None)
+        
+    def PopulateTreeFromFrameNode(self, frameNode):
+        tree = self._treeCtrl 
+        tree.Show(True)
+        root = self._root   
+        tree.DeleteChildren(root)
+        children = frameNode.childNodes
+        firstChild = None
+        for index in range(0, children.length):
+            subNode = children.item(index)
+            treeNode = self.AppendSubTreeFromNode(subNode, subNode.getAttribute('name'), root)
+            if not firstChild:
+                firstChild = treeNode
+        tree.Expand(root)
+        tree.Expand(firstChild)
+        self._p2.FitInside()
+            
+    def AppendSubTreeFromNode(self, node, name, parent, insertBefore=None):
+        tree = self._treeCtrl 
+        if insertBefore != None:
+            treeNode = tree.InsertItem(parent, insertBefore, name)
+        else: 
+            treeNode = tree.AppendItem(parent, name)
+        children = node.childNodes
+        if children.length == 0:
+            tree.SetItemText(treeNode, self.StripOuterSingleQuotes(node.getAttribute("value")), 1)
+        for index in range(0, children.length):
+            subNode = children.item(index)
+            if self.HasChildren(subNode):
+                self.AppendSubTreeFromNode(subNode, subNode.getAttribute("name"), treeNode)      
+            else:
+                name = subNode.getAttribute("name")
+                value = self.StripOuterSingleQuotes(subNode.getAttribute("value"))
+                n = tree.AppendItem(treeNode, name)
+                tree.SetItemText(n, value, 1)
+        return treeNode
+        
+    def StripOuterSingleQuotes(self, string):
+        if string.startswith("'") and string.endswith("'"):
+            return string[1:-1]
+        elif type(string) == types.UnicodeType:
+            return string[1:-1]
+        else:
+            return string
+            
+    def HasChildren(self, node):
+        try:
+            return node.childNodes.length > 0
+        except:
+            tp,val,tb=sys.exc_info()
+            return False
+                    
+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)
+    
+    #------------------------------------------------------------------------------
+    # Class methods
+    #-----------------------------------------------------------------------------
+
+class Interaction:
+    def __init__(self, message, framesXML,  info=None, quit=False):
+        self._framesXML = framesXML
+        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  
+         
+class AGXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
+    def __init__(self, address, logRequests=0):
+        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests)               
+                           
+class RequestHandlerThread(threading.Thread):
+    def __init__(self, queue, address):
+        threading.Thread.__init__(self)
+        self._keepGoing = True
+        self._queue = queue
+        self._address = address
+        self._server = AGXMLRPCServer(self._address,logRequests=0)
+        self._server.register_function(self.interaction)
+        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() 
+            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:
+            try:
+                # This is a really ugly way to make sure this thread isn't blocked in
+                # handle_request.
+                url = 'http://' + self._address[0] + ':' + str(self._address[1]) + '/'
+                tempServer = xmlrpclib.ServerProxy(url, allow_none=1)
+                tempServer.dummyOperation()
+            except:
+                tp, val, tb = sys.exc_info()
+                traceback.print_exception(tp, val, tb)
+            self._server.server_close()
+
+
+class RequestBreakThread(threading.Thread):
+        def __init__(self, server, interrupt=False, pushBreakpoints=False, breakDict=None, kill=False):
+            threading.Thread.__init__(self)
+            self._server = server
+
+            self._interrupt = interrupt
+            self._pushBreakpoints = pushBreakpoints
+            self._breakDict = breakDict
+            self._kill = kill
+            
+        def run(self):
+            try:
+                if _VERBOSE: print "RequestBreakThread, before call"
+                if self._interrupt:
+                    self._server.break_requested()
+                if self._pushBreakpoints:
+                    self._server.update_breakpoints(xmlrpclib.Binary(pickle.dumps(self._breakDict)))
+                if self._kill:
+                    try:
+                        self._server.die()
+                    except:
+                        pass
+                if _VERBOSE: print "RequestBreakThread, after call"
+            except:
+                tp,val,tb = sys.exc_info()
+                traceback.print_exception(tp, val, tb)
+
+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:
+                self._function()
+            except:
+                tp,val,tb = sys.exc_info()
+                traceback.print_exception(tp, val, tb)
+            if _VERBOSE: print "In DOT, after call"
+                
+class DebuggerCallback:
+
+    def __init__(self, host, port, debugger_url, break_url, debuggerUI):
+        if _VERBOSE: print "+++++++ Creating server on port, ", str(port)
+        
+        self._queue = Queue.Queue(50)
+        self._host = host
+        self._port = int(port)
+        threading._VERBOSE = _VERBOSE
+        self._serverHandlerThread = RequestHandlerThread(self._queue, (self._host, self._port))
+        
+        self._debugger_url = debugger_url
+        self._debuggerServer = None
+        self._waiting = False
+        self._service = wx.GetApp().GetService(DebuggerService)
+        self._debuggerUI = debuggerUI
+        self._break_url = break_url
+        self._breakServer = None
+        self._firstInteraction = True
+        self._pendingBreak = False
+        
+    def start(self):
+        self._serverHandlerThread.start()
+    
+    def ServerClose(self):
+        rbt = RequestBreakThread(self._breakServer, kill=True)
+        rbt.start()
+        self.setWaiting(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()
+        #dot = DebuggerOperationThread(self._debuggerServer.set_step)
+        #dot.start()
+        self._debuggerServer.set_step() # Figure out where to set allowNone
+        self.waitForRPC()
+
+    def Next(self):
+        self._debuggerUI.DisableWhileDebuggerRunning()
+        #dot = DebuggerOperationThread(self._debuggerServer.set_next)
+        #dot.start()
+        self._debuggerServer.set_next()
+        self.waitForRPC()
+        
+    def Continue(self):
+        self._debuggerUI.DisableWhileDebuggerRunning()
+        #dot = DebuggerOperationThread(self._debuggerServer.set_continue)
+        #dot.start()
+        self._debuggerServer.set_continue()
+        self.waitForRPC()
+        
+    def Return(self):
+        self._debuggerUI.DisableWhileDebuggerRunning()
+        #dot = DebuggerOperationThread(self._debuggerServer.set_return)
+        #dot.start()
+        self._debuggerServer.set_return()
+        self.waitForRPC()
+        
+    def setWaiting(self, value):
+        self._waiting = value
+        
+    def getWaiting(self):
+        return self._waiting
+            
+    def readQueue(self):
+        if self._queue.qsize():
+            try:
+                item = self._queue.get_nowait()
+                if item.getQuit():
+                    self.interaction(None, None, None, True)
+                else:
+                    data = bz2.decompress(item.getFramesXML().data)
+                    self.interaction(item.getMessage().data, data, item.getInfo(), False)               
+            except Queue.Empty:
+                pass
+                
+    def pushBreakpoints(self):
+        rbt = RequestBreakThread(self._breakServer, pushBreakpoints=True, breakDict=self._service.GetMasterBreakpointDict())
+        rbt.start()
+
+                               
+    def waitForRPC(self):
+        self.setWaiting(True)
+        while self.getWaiting():
+            try:
+                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."
+        
+    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)
+        if _VERBOSE: print "+"*40
+        if(quit):
+            self._debuggerUI.StopExecution(None)
+            return ""
+        if(info != ""):
+            if _VERBOSE: print "Hit interaction with exception"
+            #self._debuggerUI.StopExecution(None)
+            #self._debuggerUI.SetStatusText("Got exception: " + str(info))
+            self._debuggerUI.SwitchToOutputTab()
+        else:
+            if _VERBOSE: print "Hit interaction no exception"
+        self._debuggerUI.SetStatusText(message)
+        self._debuggerUI.LoadFramesListXML(frameXML)        
+        self._debuggerUI.EnableWhileDebuggerStopped()
+        if _VERBOSE: print "+"*40
+        
+class DebuggerService(Service.Service):
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    TOGGLE_BREAKPOINT_ID = wx.NewId()
+    CLEAR_ALL_BREAKPOINTS = wx.NewId()
+    RUN_ID = wx.NewId()
+    DEBUG_ID = wx.NewId()
+
+    def ComparePaths(first, second):
+        one = DebuggerService.ExpandPath(first)
+        two = DebuggerService.ExpandPath(second)
+        if _WINDOWS:
+            return one.lower() == two.lower()
+        else:
+            return one == two
+    ComparePaths = staticmethod(ComparePaths)
+            
+    # Make sure we're using an expanded path on windows.
+    def ExpandPath(path):
+        if _WINDOWS:
+            try:
+                return win32api.GetLongPathName(path)
+            except:
+                print "Cannot get long path for %s" % path
+                
+        return path
+        
+    ExpandPath = staticmethod(ExpandPath)
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_LEFT):
+        Service.Service.__init__(self, serviceName, embeddedWindowLocation)
+        self.BREAKPOINT_DICT_STRING = "MasterBreakpointDict"
+        config = wx.ConfigBase_Get()
+        pickledbps = config.Read(self.BREAKPOINT_DICT_STRING)
+        if pickledbps:
+            try:
+                self._masterBPDict = pickle.loads(pickledbps.encode('ascii'))
+            except:
+                tp, val, tb = sys.exc_info()
+                traceback.print_exception(tp,val,tb)
+                self._masterBPDict = {}
+        else:
+            self._masterBPDict = {}
+            
+    def OnCloseFrame(self, event):
+        # IS THIS THE RIGHT PLACE?
+        try:
+            config = wx.ConfigBase_Get()
+            config.Write(self.BREAKPOINT_DICT_STRING, pickle.dumps(self._masterBPDict))
+        except:
+            tp,val,tb = sys.exc_info()
+            traceback.print_exception(tp, val, tb)
+        return True
+        
+    def _CreateView(self):
+        return DebuggerView(self)
+
+
+    #----------------------------------------------------------------------------
+    # Service specific methods
+    #----------------------------------------------------------------------------
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        #Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        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.AppendSeparator()
+            
+            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"))
+
+        return True
+
+
+            
+    #----------------------------------------------------------------------------
+    # Event Processing Methods
+    #----------------------------------------------------------------------------
+
+    def ProcessEventBeforeWindows(self, event):
+        return False
+
+
+    def ProcessEvent(self, event):
+        if Service.Service.ProcessEvent(self, event):
+            return True
+
+        an_id = event.GetId()
+        if an_id == DebuggerService.TOGGLE_BREAKPOINT_ID:
+            self.OnToggleBreakpoint(event)
+            return True
+        elif an_id == DebuggerService.CLEAR_ALL_BREAKPOINTS:
+            self.ClearAllBreakpoints()
+            return True
+        elif an_id == DebuggerService.RUN_ID:
+            self.OnRunProject(event)
+            return True
+        elif an_id == DebuggerService.DEBUG_ID:
+            self.OnDebugProject(event)
+            return True
+        return False
+        
+    def ProcessUpdateUIEvent(self, event):
+        if Service.Service.ProcessUpdateUIEvent(self, event):
+            return True
+
+        an_id = event.GetId()
+        if an_id == DebuggerService.TOGGLE_BREAKPOINT_ID:
+            currentView = self.GetDocumentManager().GetCurrentView()
+            event.Enable(isinstance(currentView, PythonEditor.PythonView))
+            return True
+        elif an_id == DebuggerService.CLEAR_ALL_BREAKPOINTS:
+            event.Enable(self.HasBreakpointsSet())
+            return True
+        elif an_id == DebuggerService.RUN_ID:
+            event.Enable(self.HasAnyFiles())
+            return True
+        elif an_id == DebuggerService.DEBUG_ID:
+            event.Enable(self.HasAnyFiles())
+            return True
+        else:
+            return False
+
+    #----------------------------------------------------------------------------
+    # Class Methods
+    #----------------------------------------------------------------------------                    
+   
+    def OnDebugProject(self, event):
+        if not Executor.GetPythonExecutablePath():
+            return
+        if DebugCommandUI.DebuggerRunning():
+            wx.MessageBox(_("A debugger is already running. Please shut down the other debugger first."), _("Debugger Running"))
+            return
+        self.ShowWindow(True)
+        projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+        project = projectService.GetView().GetDocument()
+        dlg = CommandPropertiesDialog(self.GetView().GetFrame(), 'Debug Python File', projectService, None, pythonOnly=True, okButtonName="Debug", debugging=True)
+        if dlg.ShowModal() == wx.ID_OK:
+            fileToDebug, initialArgs, startIn, isPython, environment = dlg.GetSettings()
+            dlg.Destroy()
+        else:
+            dlg.Destroy()
+            return
+        self.PromptToSaveFiles()
+
+        shortFile = os.path.basename(fileToDebug)
+        fileToDebug = DebuggerService.ExpandPath(fileToDebug)
+        try:
+            page = DebugCommandUI(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 HasAnyFiles(self):
+        docs = wx.GetApp().GetDocumentManager().GetDocuments()
+        return len(docs) > 0
+        
+    def PromptToSaveFiles(self, running=True):
+        filesModified = False
+        docs = wx.GetApp().GetDocumentManager().GetDocuments()
+        for doc in docs:
+            if doc.IsModified():
+                filesModified = True
+                break
+        if filesModified:
+            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
+                          )
+            else:              
+                yesNoMsg = wx.MessageDialog(frame,
+                          _("Files have been modified.\nWould you like to save all files before debugging?"),
+                          _("Debug"),
+                          wx.YES_NO
+                          )
+            if yesNoMsg.ShowModal() == wx.ID_YES:
+                docs = wx.GetApp().GetDocumentManager().GetDocuments()
+                for doc in docs:
+                    doc.Save()
+
+    def OnExit(self):
+        DebugCommandUI.ShutdownAllDebuggers()
+        
+    def OnRunProject(self, event):
+        if not Executor.GetPythonExecutablePath():
+            return
+        projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+        project = projectService.GetView().GetDocument()
+        dlg = CommandPropertiesDialog(self.GetView().GetFrame(), 'Run', projectService, None)
+        if dlg.ShowModal() == wx.ID_OK:
+            fileToRun, initialArgs, startIn, isPython, environment = dlg.GetSettings()
+            
+
+            dlg.Destroy()
+        else:
+            dlg.Destroy()
+            return
+        self.PromptToSaveFiles()
+        # This will need to change when we can run more than .py and .bpel files.
+        if not isPython:
+            projectService.RunProcessModel(fileToRun)
+            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)
+
+    def OnToggleBreakpoint(self, event, line=-1, fileName=None):
+        if not fileName:
+            view = wx.GetApp().GetDocumentManager().GetCurrentView()
+            # Test to make sure we aren't the project view.
+            if not hasattr(view, 'MarkerExists'):
+                return
+            fileName = wx.GetApp().GetDocumentManager().GetCurrentDocument().GetFilename()
+            if line < 0:
+                line = view.GetCtrl().GetCurrentLine()
+        if  self.BreakpointSet(fileName, line + 1):
+            self.ClearBreak(fileName, line + 1)
+        else:
+            self.SetBreak(fileName, line + 1)
+        # Now refresh all the markers icons in all the open views.
+        self.ClearAllBreakpointMarkers()
+        self.SetAllBreakpointMarkers()    
+                 
+    def SilentToggleBreakpoint(self, fileName, line):
+        found = False
+        for lineNumber in self.GetBreakpointList(fileName):
+            if int(lineNumber) == int(line):
+                found = True
+                break
+        if found:
+            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
+        self.NotifyDebuggersOfBreakpointChange()
+    
+    def NotifyDebuggersOfBreakpointChange(self):
+        DebugCommandUI.NotifyDebuggersOfBreakpointChange()
+        
+    def GetBreakpointList(self, fileName):
+        expandedName = DebuggerService.ExpandPath(fileName)
+        if not self._masterBPDict.has_key(expandedName):
+            return []
+        else:
+            return self._masterBPDict[expandedName] 
+
+    def BreakpointSet(self, fileName, line):
+        expandedName = DebuggerService.ExpandPath(fileName)
+        if not self._masterBPDict.has_key(expandedName):
+            return False
+        else:
+            newList = []
+            for number in self._masterBPDict[expandedName]:
+                if(int(number) == int(line)):
+                    return True
+        return False
+        
+    def ClearBreak(self, fileName, line):
+        expandedName = DebuggerService.ExpandPath(fileName)
+        if not self._masterBPDict.has_key(expandedName):
+            print "In ClearBreak: no key"
+            return
+        else:
+            newList = []
+            for number in self._masterBPDict[expandedName]:
+                if(int(number) != int(line)):
+                    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.GetFirstView(), CodeEditor.CodeView)): 
+                openDoc.GetFirstView().MarkerDeleteAll(CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM) 
+
+    def GetMasterBreakpointDict(self):
+        return self._masterBPDict
+        
+    def SetAllBreakpointMarkers(self):
+        openDocs = wx.GetApp().GetDocumentManager().GetDocuments()
+        for openDoc in openDocs:  
+            if(isinstance(openDoc.GetFirstView(), CodeEditor.CodeView)): 
+                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()): 
+                view.MarkerAdd(lineNum=int(linenum) - 1, marker_index=CodeEditor.CodeCtrl.BREAKPOINT_MARKER_NUM)
+
+class DebuggerOptionsPanel(wx.Panel):
+
+
+    def __init__(self, parent, id):
+        wx.Panel.__init__(self, parent, id)
+        SPACE = 10
+        config = wx.ConfigBase_Get()
+        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")) 
+        startingPort=config.ReadInt("DebuggerStartingPort", DEFAULT_PORT)
+        self._PortNumberTextCtrl = wx.lib.intctrl.IntCtrl(self, -1, startingPort, size = (50, -1))
+        self._PortNumberTextCtrl.SetMin(1)#What are real values?
+        self._PortNumberTextCtrl.SetMax(65514) #What are real values?
+        self.Bind(wx.lib.intctrl.EVT_INT, self.MinPortChange, self._PortNumberTextCtrl)
+
+        self._EndPortNumberTextCtrl = wx.lib.intctrl.IntCtrl(self, -1, startingPort + PORT_COUNT, size = (50, -1))
+        self._EndPortNumberTextCtrl.SetMin(22)#What are real values?
+        self._EndPortNumberTextCtrl.SetMax(65535)#What are real values?
+        self._EndPortNumberTextCtrl.Enable( False )
+        debuggerPanelBorderSizer = wx.BoxSizer(wx.VERTICAL)
+        debuggerPanelSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        debuggerPanelSizer.Add( localHostStaticText, (0,0), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+        debuggerPanelSizer.Add( self._LocalHostTextCtrl, (0,1), (1,3), flag=wx.EXPAND|wx.ALIGN_CENTER)
+        debuggerPanelSizer.Add( portNumberStaticText, (1,0), flag=wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
+        debuggerPanelSizer.Add( self._PortNumberTextCtrl, (1,1), flag=wx.ALIGN_CENTER)
+        debuggerPanelSizer.Add( dashStaticText, (1,2), flag=wx.ALIGN_CENTER)
+        debuggerPanelSizer.Add( self._EndPortNumberTextCtrl, (1,3), flag=wx.ALIGN_CENTER)
+        FLUSH_PORTS_ID = wx.NewId()
+        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()
+        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())
+
+class CommandPropertiesDialog(wx.Dialog):
+    
+    def __init__(self, parent, title, projectService, currentProjectDocument, pythonOnly=False, okButtonName="Run", debugging=False):
+        if _WINDOWS:
+            wx.Dialog.__init__(self, parent, -1, title)
+        else:
+            wx.Dialog.__init__(self, parent, -1, title, size=(390,270)) 
+        self._projService = projectService
+        self._pmext = None
+        self._pyext = None
+        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
+        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")
+        cpPanelBorderSizer = wx.BoxSizer(wx.VERTICAL)
+        self._projectNameList, self._projectDocumentList, selectedIndex = self.GetProjectList()
+        self._projList = wx.Choice(self, -1, (200,-1), choices=self._projectNameList)
+        self.Bind(wx.EVT_CHOICE, self.EvtListBox, self._projList)
+        HALF_SPACE = 5
+        flexGridSizer = wx.FlexGridSizer(cols = 3, vgap = 10, hgap = 10)
+        
+        flexGridSizer.Add(projStaticText, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+        flexGridSizer.Add(self._projList, 1, flag=wx.EXPAND)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        
+        flexGridSizer.Add(fileStaticText, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+        self._fileList = wx.Choice(self, -1, (200,-1))
+        self.Bind(wx.EVT_CHOICE, self.OnFileSelected, self._fileList)
+        flexGridSizer.Add(self._fileList, 1, flag=wx.EXPAND)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        
+        config = wx.ConfigBase_Get()
+        self._lastArguments = config.Read("LastRunArguments")
+        self._argsEntry = wx.TextCtrl(self, -1, str(self._lastArguments))
+        self._argsEntry.SetToolTipString(str(self._lastArguments))
+        def TextChanged(event):
+            self._argsEntry.SetToolTipString(event.GetString())
+        self.Bind(wx.EVT_TEXT, TextChanged, self._argsEntry)
+
+        flexGridSizer.Add(argsStaticText, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+        flexGridSizer.Add(self._argsEntry, 1, flag=wx.EXPAND)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        
+        flexGridSizer.Add(startInStaticText, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT)
+        self._lastStartIn = config.Read("LastRunStartIn")
+        if not self._lastStartIn:
+            self._lastStartIn = str(os.getcwd())
+        self._startEntry = wx.TextCtrl(self, -1, self._lastStartIn)
+        self._startEntry.SetToolTipString(self._lastStartIn)
+        def TextChanged2(event):
+            self._startEntry.SetToolTipString(event.GetString())
+        self.Bind(wx.EVT_TEXT, TextChanged2, self._startEntry)
+
+        flexGridSizer.Add(self._startEntry, 1, wx.EXPAND)
+        self._findDir = wx.Button(self, -1, _("Browse..."), size=(60,-1))
+        self.Bind(wx.EVT_BUTTON, self.OnFindDirClick, self._findDir)
+        flexGridSizer.Add(self._findDir, 0, wx.RIGHT, 10)
+        
+        flexGridSizer.Add(pythonPathStaticText, 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._pythonPathEntry = wx.TextCtrl(self, -1, self._lastPythonPath)
+        self._pythonPathEntry.SetToolTipString(self._lastPythonPath)
+        flexGridSizer.Add(self._pythonPathEntry, 1, wx.EXPAND)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        if debugging:
+            self._postpendCheckBox = wx.CheckBox(self, -1, postpendStaticText)
+            checked = bool(config.ReadInt("PythonPathPostpend", 1))
+            self._postpendCheckBox.SetValue(checked)
+            flexGridSizer.Add(self._postpendCheckBox, 1, wx.EXPAND)
+        flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+        cpPanelBorderSizer.Add(flexGridSizer, 0, wx.ALL, 10)
+        
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        self._okButton = wx.Button(self, wx.ID_OK, okButtonName, size=(75,-1))
+        self._okButton.SetDefault()
+        self._okButton.SetHelpText(_("The ") + okButtonName + _(" button completes the dialog"))
+        box.Add(self._okButton, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
+        self.Bind(wx.EVT_BUTTON, self.OnOKClick, self._okButton)
+        btn = wx.Button(self, wx.ID_CANCEL, _("Cancel"), size=(75,-1))
+        btn.SetHelpText(_("The Cancel button cancels the dialog."))
+        box.Add(btn, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
+        cpPanelBorderSizer.Add(box, 0, wx.ALIGN_RIGHT|wx.BOTTOM, 5)
+        
+        self.SetSizer(cpPanelBorderSizer)
+        if _WINDOWS:
+            self.GetSizer().Fit(self)
+        
+        self.Layout()
+        
+        # Set up selections based on last values used.
+        self._fileNameList = None
+        self._selectedFileIndex = 0
+        lastProject = config.Read("LastRunProject")
+        lastFile = config.Read("LastRunFile")
+
+        if lastProject in self._projectNameList:
+            selectedIndex = self._projectNameList.index(lastProject)
+        elif selectedIndex < 0:
+            selectedIndex = 0
+        self._projList.Select(selectedIndex)
+        self._selectedProjectIndex = selectedIndex
+        self._selectedProjectDocument = self._projectDocumentList[selectedIndex]
+        self.PopulateFileList(self._selectedProjectDocument, lastFile)
+            
+    def OnOKClick(self, event):
+        startIn = self._startEntry.GetValue()
+        fileToRun = self._fileList.GetStringSelection()
+        if not fileToRun:
+            wx.MessageBox(_("You must select a file to proceed. Note that not all projects have files that can be run or debugged."))
+            return
+        isPython = fileToRun.endswith(self._pyext)
+        if isPython and not os.path.exists(startIn):
+            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)
+        # 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())
+            if hasattr(self, "_postpendCheckBox"):
+                config.WriteInt("PythonPathPostpend", int(self._postpendCheckBox.GetValue()))
+
+        self.EndModal(wx.ID_OK)
+            
+    def GetSettings(self):  
+        filename = self._fileNameList[self._selectedFileIndex]
+        args = self._argsEntry.GetValue() 
+        startIn = self._startEntry.GetValue()
+        isPython = filename.endswith(self._pyext)
+        env = os.environ
+        if hasattr(self, "_postpendCheckBox"):
+            postpend = self._postpendCheckBox.GetValue()
+        else:
+            postpend = False
+        if postpend:
+            env['PYTHONPATH'] = self._pythonPathEntry.GetValue() + os.pathsep + os.path.join(os.getcwd(), "3rdparty", "pywin32")
+        else:
+            env['PYTHONPATH'] = self._pythonPathEntry.GetValue()
+            
+        return 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)
+        self._startEntry.Enable(show)
+        self._findDir.Enable(show)
+        self._argsEntry.Enable(show)
+
+        if not show:
+            self._lastStartIn = self._startEntry.GetValue()
+            self._startEntry.SetValue("")
+            self._lastArguments = self._argsEntry.GetValue()
+            self._argsEntry.SetValue("")  
+        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)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            self._startEntry.SetValue(dlg.GetPath())
+            
+        dlg.Destroy() 
+           
+    def EvtListBox(self, event):
+        if event.GetString():
+            index = self._projectNameList.index(event.GetString())
+            self._selectedProjectDocument = self._projectDocumentList[index]
+            self._selectedProjectIndex = index
+            self.PopulateFileList(self._selectedProjectDocument)
+            
+    def FilterFileList(self, list):
+        if self._pythonOnly:
+            files = filter(lambda f: f.endswith(self._pyext), list)
+        else:
+            files = filter(lambda f: (self._pmext and f.endswith(self._pmext)) or f.endswith(self._pyext), list)
+        return files
+                 
+    def PopulateFileList(self, project, shortNameToSelect=None):
+        self._fileNameList = self.FilterFileList(project.GetFiles()[:])
+        self._fileList.Clear()
+        if not self._fileNameList:
+            return
+        self._fileNameList.sort(lambda a, b: cmp(os.path.basename(a).lower(), os.path.basename(b).lower()))
+        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._fileList.Hide()
+        self._fileList.AppendItems(strings)
+        self._fileList.Show()
+        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 = []
+        found = False
+        index = -1
+        count = 0
+        for document in self._projService.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectEditor.ProjectDocument and len(document.GetFiles()):
+                docList.append(document)
+                nameList.append(os.path.basename(document.GetFilename()))
+                if document == self._currentProj:
+                    found = True
+                    index = count
+                count += 1
+        return nameList, docList, index
+#----------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+#----------------------------------------------------------------------
+def getBreakData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x11\x08\x06\
+\x00\x00\x00\xd4\xaf,\xc4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
+\x00\x00\x97IDAT8\x8d\xbdSA\x0e\xc3 \x0c\x8b\x13\xfe=\x1e^\xe2\x1dF\xbb\x8c\
+\xd2\x0c\xa9\xda,q\x88\x05\x8e\x1d\x00P\x93;\xd0[\xa7W\x04\xe8\x8d\x0f\xdfxU\
+c%\x02\xbd\xbd\x05HQ+Xv\xb0\xa3VN\xf9\xd4\x01\xbd\x11j\x18\x1d\x00\x10\xa8AD\
+\xa4\xa4\xd6_\x9b\x19\xbb\x03\xd8c|\x8f\x00\xe0\x93\xa8g>\x15 C\xee:\xe7\x8f\
+\x08\xdesj\xcf\xe6\xde(\xddn\xec\x18f0w\xe0m;\x8d\x9b\xe4\xb1\xd4\n\xa6\x0e2\
+\xc4{\x1f\xeb\xdf?\xe5\xff\t\xa8\x1a$\x0cg\xac\xaf\xb0\xf4\x992<\x01\xec\xa0\
+U9V\xf9\x18\xc8\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getBreakBitmap():
+    return BitmapFromImage(getBreakImage())
+
+def getBreakImage():
+    stream = cStringIO.StringIO(getBreakData())
+    return ImageFromStream(stream)
+
+def getBreakIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getBreakBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getClearOutputData():
+    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\xb4IDAT8\x8d\xa5\x92\xdd\r\x03!\x0c\x83\xbf\xa0n\xd4\x9d\xda5\xb81\
+\xbaS\xbb\x12\xee\x03?\xe5\x08\xe5N\xba\xbc Db\xec\xd8p\xb1l\xb8\xa7\x83\xfe\
+\xb0\x02H\x92F\xc0_\xa3\x99$\x99\x99\xedznc\xe36\x81\x88\x98"\xb2\x02\xa2\
+\x1e\xc4Q\x9aUD\x161\xcd\xde\x1c\x83\x15\x084)\x8d\xc5)\x06\xab\xaaZ\x92\xee\
+\xce\x11W\xdbGD\x0cIT\x06\xe7\x00\xdeY\xfe\xcc\x89\x06\xf0\xf2\x99\x00\xe0\
+\x91\x7f\xab\x83\xed\xa4\xc8\xafK\x0c\xcf\x92\x83\x99\x8d\xe3p\xef\xe4\xa1\
+\x0b\xe57j\xc8:\x06\t\x08\x87.H\xb2n\xa8\xc9\xa9\x12vQ\xfeG"\xe3\xacw\x00\
+\x10$M\xd3\x86_\xf0\xe5\xfc\xb4\xfa\x02\xcb\x13j\x10\xc5\xd7\x92D\x00\x00\
+\x00\x00IEND\xaeB`\x82' 
+
+def getClearOutputBitmap():
+    return BitmapFromImage(getClearOutputImage())
+
+def getClearOutputImage():
+    stream = cStringIO.StringIO(getClearOutputData())
+    return ImageFromStream(stream)
+
+def getClearOutputIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getClearOutputBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getCloseData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x12\x00\x00\x00\x12\x08\x06\
+\x00\x00\x00V\xce\x8eW\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
+\x00\x86IDAT8\x8d\xed\x90\xb1\r\x80 \x10E_"c\xd80\x02-\x138\x87;8\x8f\x8d\
+\x8b\xb0\x02\xa5\xad\rS\x88\xcd\x11) \x82\xb6\xbe\xea\xf2\xc9\xbd\xfc\x03~\
+\xdeb\x81\xb9\x90\xabJ^%\x00N\x849\x0e\xf0\x85\xbc\x8a\x12YZR2\xc7\x1eIB\xcb\
+\xb2\xcb\x9at\x9d\x95c\xa5Y|\x92\x0c\x0f\xa2\r8e\x1e\x81\x1d8z\xdb8\xee?Ig\
+\xfa\xb7\x92)\xcb\xacd\x01XZ$QD\xba\xf0\xa6\x80\xd5\x18cZ\x1b\x95$?\x1f\xb9\
+\x00\x1d\x94\x1e*e_\x8a~\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getCloseBitmap():
+    return BitmapFromImage(getCloseImage())
+
+def getCloseImage():
+    stream = cStringIO.StringIO(getCloseData())
+    return ImageFromStream(stream)
+
+def getCloseIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getCloseBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getContinueData():
+    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\x9eIDAT8\x8d\xbd\x92\xd1\r\x83@\x0cC\xed\x8a\x11X\xac\x1b\xddF,\xd6\
+\x11\x90\xdc\x0f\x9a\x93s)\x14Z\xa9\x91\x10\n\x97\xbc\xd89\x80?\x84\x1a\xa4\
+\x83\xfc\x1c$\x1e)7\xdf<Y0\xaf\x0b\xe6\xf5\x1d\xa1\xb5\x13C\x03 !\xaa\xfd\
+\xed\n:mr\xc0\x1d\x8f\xc9\x9a!\t$\xe5\xd3I\xe2\xe5B$\x99\x00[\x01\xe8\xc5\
+\xd9G\xfaN`\xd8\x81I\xed\x8c\xb19\x94\x8d\xcbL\x00;t\xcf\x9fwPh\xdb\x0e\xe8\
+\xd3,\x17\x8b\xc7\x9d\xbb>\x8a \xec5\x94\tc\xc4\x12\xab\x94\xeb\x7fkWr\xc9B%\
+\xfc\xd2\xfcM<\x01\xf6tn\x12O3c\xe6\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getContinueBitmap():
+    return BitmapFromImage(getContinueImage())
+
+def getContinueImage():
+    stream = cStringIO.StringIO(getContinueData())
+    return ImageFromStream(stream)
+
+def getContinueIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getContinueBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getNextData():
+    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\x8eIDAT8\x8d\xa5SA\x12\xc4 \x08K\xb0\xff\xde\xe9\xbf\xb7\xa6\x87\
+\x1d:\xba\xa2tZn(\x84`"i\x05obk\x13\xd5CmN+\xcc\x00l\xd6\x0c\x00\xf5\xf8\x0e\
+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' 
+
+def getNextBitmap():
+    return BitmapFromImage(getNextImage())
+
+def getNextImage():
+    stream = cStringIO.StringIO(getNextData())
+    return ImageFromStream(stream)
+
+def getNextIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getNextBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getStepInData():
+    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\x87IDAT8\x8d\xadSA\x12\x84 \x0ck\x8a\xffv\xfc\xb74{X\xeb0P@\x07s\
+\x84\xa4$\x01\x00M\xb2\x02]R\x8b\xc86\xda\xdc\xedd\xb4~\xe8\x86\xc6\x01-\x93\
+\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' 
+
+def getStepInBitmap():
+    return BitmapFromImage(getStepInImage())
+
+def getStepInImage():
+    stream = cStringIO.StringIO(getStepInData())
+    return ImageFromStream(stream)
+
+def getStepInIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getStepInBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getStopData():
+    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\x00FIDAT8\x8d\xed\x91\xc1\t\xc00\x0c\x03O!\x0b\xa9\xfb\xef\xa6\xfcB\xa1\
+N\t\xf4Wr\xa0\x8f\xb1\x0f\x81\xe1\x97\xe4-\xb6}_V%\xc8\xc2, \t\x92\xe6]\xfbZ\
+\xf7\x08\xa0W\xc3\xea5\xdb\rl_IX\xe5\xf0d\x00\xfa\x8d#\x7f\xc4\xf7'\xab\x00\
+\x00\x00\x00IEND\xaeB`\x82" 
+
+def getStopBitmap():
+    return BitmapFromImage(getStopImage())
+
+def getStopImage():
+    stream = cStringIO.StringIO(getStopData())
+    return ImageFromStream(stream)
+
+def getStopIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getStopBitmap())
+    return icon
+
+#----------------------------------------------------------------------
+def getStepReturnData():
+    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\x8dIDAT8\x8d\xa5S\xd1\x0e\xc4 \x08\xa3\xb0\xff\xbe\xdc\x7fO\xba'6\
+\xf1\xf44\xb3O$Phk\x04\xd4d\x07\xba\xc5\x16\x91#\nza\xdb\x84\x1a\xa2\xfe\xf8\
+\x99\xfa_=p+\xe8\x91ED\xbc<\xa4 \xb4\x0b\x01\xb5{\x01\xf9\xbbG-\x13\x87\x16f\
+\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" 
+
+def getStepReturnBitmap():
+    return BitmapFromImage(getStepReturnImage())
+
+def getStepReturnImage():
+    stream = cStringIO.StringIO(getStepReturnData())
+    return ImageFromStream(stream)
+
+def getStepReturnIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getStepReturnBitmap())
+    return icon
+    
+def getAddWatchData():
+    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\x85IDAT8\x8dc`\x18h\xc0\x88.\xd0\xc0\xf0\xff?*\x9f\x11C\rN\x80\xae\
+\x19\x97\x18\xd1\x9a\x896\x84\x18[p\xa9aA\xe6\xfc7f\xc0P\xc4x\x163\x9cp\x1a0\
+\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' 
+
+def getAddWatchBitmap():
+    return BitmapFromImage(getAddWatchImage())
+
+def getAddWatchImage():
+    stream = cStringIO.StringIO(getAddWatchData())
+    return ImageFromStream(stream)
+
+def getAddWatchIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getAddWatchBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/FindInDirService.py b/wxPython/samples/ide/activegrid/tool/FindInDirService.py
new file mode 100644 (file)
index 0000000..f3e9da1
--- /dev/null
@@ -0,0 +1,446 @@
+#----------------------------------------------------------------------------
+# Name:         IDEFindService.py
+# Purpose:      Find Service for pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      8/15/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import os
+from os.path import join
+import re
+import ProjectEditor
+import MessageService
+import FindService
+import OutlineService
+_ = wx.GetTranslation
+
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+FILENAME_MARKER = _("Found in file: ")
+PROJECT_MARKER = _("Searching project: ")
+FIND_MATCHDIR = "FindMatchDir"
+FIND_MATCHDIRSUBFOLDERS = "FindMatchDirSubfolders"
+
+SPACE = 10
+HALF_SPACE = 5
+
+
+class FindInDirService(FindService.FindService):
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    FINDALL_ID = wx.NewId()         # for bringing up Find All dialog box
+    FINDDIR_ID = wx.NewId()         # for bringing up Find Dir dialog box
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        FindService.FindService.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+        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"))
+        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"))
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == FindInDirService.FINDALL_ID:
+            self.ShowFindAllDialog()
+            return True
+        elif id == FindInDirService.FINDDIR_ID:
+            self.ShowFindDirDialog()
+            return True
+        else:
+            return FindService.FindService.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == FindInDirService.FINDALL_ID:
+            projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+            view = projectService.GetView()
+            if view and view.GetDocument() and view.GetDocument().GetFiles():
+                event.Enable(True)
+            else:
+                event.Enable(False)
+            return True
+        elif id == FindInDirService.FINDDIR_ID:
+            event.Enable(True)
+        else:
+            return FindService.FindService.ProcessUpdateUIEvent(self, event)
+
+
+    def ShowFindDirDialog(self):
+        config = wx.ConfigBase_Get()
+
+        frame = wx.Dialog(None, -1, _("Find in Directory"), size= (320,200))
+        borderSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        contentSizer = wx.BoxSizer(wx.VERTICAL)
+        lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+        lineSizer.Add(wx.StaticText(frame, -1, _("Directory:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
+        dirCtrl = wx.TextCtrl(frame, -1, config.Read(FIND_MATCHDIR, ""), size=(200,-1))
+        dirCtrl.SetToolTipString(dirCtrl.GetValue())
+        lineSizer.Add(dirCtrl, 0, wx.LEFT, HALF_SPACE)
+        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()
+            if len(dir):
+                dlg.SetPath(dir)
+            if dlg.ShowModal() == wx.ID_OK:
+                dirCtrl.SetValue(dlg.GetPath())
+                dirCtrl.SetToolTipString(dirCtrl.GetValue())
+                dirCtrl.SetInsertionPointEnd()
+
+            dlg.Destroy()
+        wx.EVT_BUTTON(findDirButton, -1, OnBrowseButton)
+
+        subfolderCtrl = wx.CheckBox(frame, -1, _("Search in subfolders"))
+        subfolderCtrl.SetValue(config.ReadInt(FIND_MATCHDIRSUBFOLDERS, True))
+        contentSizer.Add(subfolderCtrl, 0, wx.BOTTOM, SPACE)
+
+        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)
+        
+        lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+        lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
+        findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), 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()
+        buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+        buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0)
+        borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE)
+
+        frame.SetSizer(borderSizer)
+        frame.Fit()
+
+        status = frame.ShowModal()
+
+        passedCheck = False
+        while status == wx.ID_OK and not passedCheck:
+            if not os.path.exists(dirCtrl.GetValue()):
+                dlg = wx.MessageDialog(frame,
+                                       _("'%s' does not exist.") % dirCtrl.GetValue(),
+                                       _("Find in Directory"),
+                                       wx.OK | wx.ICON_EXCLAMATION
+                                       )
+                dlg.ShowModal()
+                dlg.Destroy()
+
+                status = frame.ShowModal()
+            elif len(findCtrl.GetValue()) == 0:
+                dlg = wx.MessageDialog(frame,
+                                       _("'Find what:' cannot be empty."),
+                                       _("Find in Directory"),
+                                       wx.OK | wx.ICON_EXCLAMATION
+                                       )
+                dlg.ShowModal()
+                dlg.Destroy()
+
+                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)
+        
+        findString = findCtrl.GetValue()
+        matchCase = matchCaseCtrl.IsChecked()
+        wholeWord = wholeWordCtrl.IsChecked()
+        regExpr = regExprCtrl.IsChecked()
+        self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
+
+            
+        if status == wx.ID_OK:            
+            frame.Destroy()
+
+            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
+        
+                            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."))
+                wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+            return True
+        else:
+            frame.Destroy()
+            return False
+            
+
+    def SaveFindDirConfig(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 ShowFindAllDialog(self):
+        config = wx.ConfigBase_Get()
+
+        frame = wx.Dialog(None, -1, _("Find in Project"), size= (320,200))
+        borderSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        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)
+        findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), 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()
+        buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+        buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0)
+        borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE)
+
+        frame.SetSizer(borderSizer)
+        frame.Fit()
+
+        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)
+
+        if status == wx.ID_OK:
+            frame.Destroy()
+
+            messageService = wx.GetApp().GetService(MessageService.MessageService)
+            messageService.ShowWindow()
+
+            view = messageService.GetView()
+            if view:
+                view.ClearLines()
+                view.SetCallback(self.OnJumpToFoundLine)
+
+                projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+                projectFilenames = projectService.GetFilesFromCurrentProject()
+
+                projView = projectService.GetView()
+                if projView:
+                    projName = wx.lib.docview.FileNameFromPath(projView.GetDocument().GetFilename())
+                    view.AddLines(PROJECT_MARKER + projName + "\n\n")
+
+                # 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)
+
+            return True
+        else:
+            frame.Destroy()
+            return False
+
+
+    def OnJumpToFoundLine(self, event):
+        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:
+            return
+        lineEnd = lineText.find(":")
+        if lineEnd == -1:
+            return
+        else:
+            lineNum = int(lineText[0:lineEnd])
+
+        text = messageService.GetView().GetText()
+        curPos = messageService.GetView().GetCurrentPos()
+
+        startPos = text.rfind(FILENAME_MARKER, 0, curPos)
+        endPos = text.find("\n", startPos)
+        filename = text[startPos + len(FILENAME_MARKER):endPos]
+
+        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()
+            if hasattr(foundView, "GotoLine"):
+                foundView.GotoLine(lineNum)
+                startPos = foundView.PositionFromLine(lineNum)
+                # wxBug:  Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct
+                #         Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first
+                #         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/FindService.py b/wxPython/samples/ide/activegrid/tool/FindService.py
new file mode 100644 (file)
index 0000000..da5235b
--- /dev/null
@@ -0,0 +1,521 @@
+#----------------------------------------------------------------------------
+# Name:         FindService.py
+# Purpose:      Find Service for pydocview
+#
+# Author:       Peter Yared, Morgan Hua
+#
+# Created:      8/15/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2003-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import wx.lib.pydocview
+import re
+_ = wx.GetTranslation
+
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+FIND_MATCHPATTERN = "FindMatchPattern"
+FIND_MATCHREPLACE = "FindMatchReplace"
+FIND_MATCHCASE = "FindMatchCase"
+FIND_MATCHWHOLEWORD = "FindMatchWholeWordOnly"
+FIND_MATCHREGEXPR = "FindMatchRegularExpr"
+FIND_MATCHWRAP = "FindMatchWrap"
+FIND_MATCHUPDOWN = "FindMatchUpDown"
+
+FIND_SYNTAXERROR = -2
+
+SPACE = 10
+HALF_SPACE = 5
+
+
+#----------------------------------------------------------------------------
+# Classes
+#----------------------------------------------------------------------------
+
+class FindService(wx.lib.pydocview.DocService):
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    FIND_ID = wx.NewId()            # for bringing up Find dialog box
+    FINDONE_ID = wx.NewId()         # for doing Find
+    FIND_PREVIOUS_ID = wx.NewId()   # for doing Find Next
+    FIND_NEXT_ID = wx.NewId()       # for doing Find Prev
+    REPLACE_ID = wx.NewId()         # for bringing up Replace dialog box
+    REPLACEONE_ID = wx.NewId()      # for doing a Replace
+    REPLACEALL_ID = wx.NewId()      # for doing Replace All
+    GOTO_LINE_ID = wx.NewId()       # for bringing up Goto dialog box
+
+    # Extending bitmasks: wx.FR_WHOLEWORD, wx.FR_MATCHCASE, and wx.FR_DOWN
+    FR_REGEXP = max([wx.FR_WHOLEWORD, wx.FR_MATCHCASE, wx.FR_DOWN]) << 1
+    FR_WRAP = FR_REGEXP << 1
+
+
+    def __init__(self):
+        self._replaceDialog = None
+        self._findDialog = None
+        self._findReplaceData = wx.FindReplaceData()
+        self._findReplaceData.SetFlags(wx.FR_DOWN)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        """ Install Find Service Menu Items """
+        editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+        editMenu.AppendSeparator()
+        editMenu.Append(FindService.FIND_ID, _("&Find...\tCtrl+F"), _("Finds the specified text"))
+        wx.EVT_MENU(frame, FindService.FIND_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, FindService.FIND_ID, frame.ProcessUpdateUIEvent)
+        editMenu.Append(FindService.FIND_PREVIOUS_ID, _("Find &Previous\tShift+F3"), _("Finds the specified text"))
+        wx.EVT_MENU(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessUpdateUIEvent)
+        editMenu.Append(FindService.FIND_NEXT_ID, _("Find &Next\tF3"), _("Finds the specified text"))
+        wx.EVT_MENU(frame, FindService.FIND_NEXT_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, FindService.FIND_NEXT_ID, frame.ProcessUpdateUIEvent)
+        editMenu.Append(FindService.REPLACE_ID, _("R&eplace...\tCtrl+H"), _("Replaces specific text with different text"))
+        wx.EVT_MENU(frame, FindService.REPLACE_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, FindService.REPLACE_ID, frame.ProcessUpdateUIEvent)
+        editMenu.Append(FindService.GOTO_LINE_ID, _("&Go to Line...\tCtrl+G"), _("Goes to a certain line in the file"))
+        wx.EVT_MENU(frame, FindService.GOTO_LINE_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, FindService.GOTO_LINE_ID, frame.ProcessUpdateUIEvent)
+
+        # wxBug: wxToolBar::GetToolPos doesn't exist, need it to find cut tool and then insert find in front of it.
+        toolBar.InsertTool(6, FindService.FIND_ID, getFindBitmap(), shortHelpString = _("Find"), longHelpString = _("Finds the specified text"))
+        toolBar.InsertSeparator(6)
+        toolBar.Realize()
+
+        frame.Bind(wx.EVT_FIND, frame.ProcessEvent)
+        frame.Bind(wx.EVT_FIND_NEXT, frame.ProcessEvent)
+        frame.Bind(wx.EVT_FIND_REPLACE, frame.ProcessEvent)
+        frame.Bind(wx.EVT_FIND_REPLACE_ALL, frame.ProcessEvent)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == FindService.FIND_ID:
+            event.Enable(False)
+            return True
+        elif id == FindService.FIND_PREVIOUS_ID:
+            event.Enable(False)
+            return True
+        elif id == FindService.FIND_NEXT_ID:
+            event.Enable(False)
+            return True
+        elif id == FindService.REPLACE_ID:
+            event.Enable(False)
+            return True
+        elif id == FindService.GOTO_LINE_ID:
+            event.Enable(False)
+            return True
+        else:
+            return False
+
+
+    def ShowFindReplaceDialog(self, findString="", replace = False):
+        """ Display find/replace dialog box.
+        
+            Parameters: findString is the default value shown in the find/replace dialog input field.
+            If replace is True, the replace dialog box is shown, otherwise only the find dialog box is shown. 
+        """
+        if replace:
+            if self._findDialog != None:
+                # No reason to have both find and replace dialogs up at the same time
+                self._findDialog.DoClose()
+                self._findDialog = None
+
+            self._replaceDialog = FindReplaceDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Replace"), size=(320,200), findString=findString)
+            self._replaceDialog.Show(True)
+        else:
+            if self._replaceDialog != None:
+                # No reason to have both find and replace dialogs up at the same time
+                self._replaceDialog.DoClose()
+                self._replaceDialog = None
+
+            self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString)
+            self._findDialog.Show(True)
+
+
+
+    def OnFindClose(self, event):
+        """ Cleanup handles when find/replace dialog is closed """
+        if self._findDialog != None:
+            self._findDialog = None
+        elif self._replaceDialog != None:
+            self._replaceDialog = None
+
+
+    def GetCurrentDialog(self):
+        """ return handle to either the find or replace dialog """
+        if self._findDialog != None:
+            return self._findDialog
+        return self._replaceDialog
+
+
+    def GetLineNumber(self, parent):
+        """ Display Goto Line Number dialog box """
+        line = -1
+        dialog = wx.TextEntryDialog(parent, _("Enter line number to go to:"), _("Go to Line"))
+        if dialog.ShowModal() == wx.ID_OK:
+            try:
+                line = int(dialog.GetValue())
+                if line > 65535:
+                    line = 65535
+            except:
+                pass
+        dialog.Destroy()
+        # This one is ugly:  wx.GetNumberFromUser("", _("Enter line number to go to:"), _("Go to Line"), 1, min = 1, max = 65535, parent = parent)
+        return line
+
+
+    def DoFind(self, findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExpr = False, replace = False, replaceAll = False, wrap = False):
+        """ Do the actual work of the find/replace.
+        
+            Returns the tuple (count, start, end, newText).
+            count = number of string replacements
+            start = start position of found string
+            end = end position of found string
+            newText = new replaced text 
+        """
+        flags = 0
+        if regExpr:
+            pattern = findString
+        else:
+            pattern = re.escape(findString)  # Treat the strings as a literal string
+        if not matchCase:
+            flags = re.IGNORECASE
+        if wholeWord:
+            pattern = r"\b%s\b" % pattern
+            
+        try:
+            reg = re.compile(pattern, flags)
+        except:
+            # syntax error of some sort
+            import sys
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("Regular Expression Search")
+            wx.MessageBox(_("Invalid regular expression \"%s\". %s") % (pattern, sys.exc_value),
+                          msgTitle,
+                          wx.OK | wx.ICON_EXCLAMATION,
+                          self.GetView())
+            return FIND_SYNTAXERROR, None, None, None
+
+        if replaceAll:
+            newText, count = reg.subn(replaceString, text)
+            if count == 0:
+                return -1, None, None, None
+            else:
+                return count, None, None, newText
+
+        start = -1
+        if down:
+            match = reg.search(text, endLoc)
+            if match == None:
+                if wrap:  # try again, but this time from top of file
+                    match = reg.search(text, 0)
+                    if match == None:
+                        return -1, None, None, None
+                else:
+                    return -1, None, None, None
+            start = match.start()
+            end = match.end()
+        else:
+            match = reg.search(text)
+            if match == None:
+                return -1, None, None, None
+            found = None
+            i, j = match.span()
+            while i < startLoc and j <= startLoc:
+                found = match
+                if i == j:
+                    j = j + 1
+                match = reg.search(text, j)
+                if match == None:
+                    break
+                i, j = match.span()
+            if found == None:
+                if wrap:  # try again, but this time from bottom of file
+                    match = reg.search(text, startLoc)
+                    if match == None:
+                        return -1, None, None, None
+                    found = None
+                    i, j = match.span()
+                    end = len(text)
+                    while i < end and j <= end:
+                        found = match
+                        if i == j:
+                            j = j + 1
+                        match = reg.search(text, j)
+                        if match == None:
+                            break
+                        i, j = match.span()
+                    if found == None:
+                        return -1, None, None, None
+                else:
+                    return -1, None, None, None
+            start = found.start()
+            end = found.end()
+
+        if replace and start != -1:
+            newText, count = reg.subn(replaceString, text, 1)
+            return count, start, end, newText
+
+        return 0, start, end, None
+
+
+    def SaveFindConfig(self, findString, wholeWord, matchCase, regExpr = None, wrap = None, upDown = None, replaceString = None):
+        """ Save find/replace patterns and search flags to registry.
+        
+            findString = search pattern
+            wholeWord = match whole word only
+            matchCase = match case
+            regExpr = use regular expressions in search pattern
+            wrap = return to top/bottom of file on search
+            upDown = search up or down from current cursor position
+            replaceString = replace string
+        """
+        config = wx.ConfigBase_Get()
+
+        config.Write(FIND_MATCHPATTERN, findString)
+        config.WriteInt(FIND_MATCHCASE, matchCase)
+        config.WriteInt(FIND_MATCHWHOLEWORD, wholeWord)
+        if replaceString != None:
+            config.Write(FIND_MATCHREPLACE, replaceString)
+        if regExpr != None:
+            config.WriteInt(FIND_MATCHREGEXPR, regExpr)
+        if wrap != None:
+            config.WriteInt(FIND_MATCHWRAP, wrap)
+        if upDown != None:
+            config.WriteInt(FIND_MATCHUPDOWN, upDown)
+
+
+    def GetFindString(self):
+        """ Load the search pattern from registry """
+        return wx.ConfigBase_Get().Read(FIND_MATCHPATTERN, "")
+
+
+    def GetReplaceString(self):
+        """ Load the replace pattern from registry """
+        return wx.ConfigBase_Get().Read(FIND_MATCHREPLACE, "")
+
+
+    def GetFlags(self):
+        """ Load search parameters from registry """
+        config = wx.ConfigBase_Get()
+
+        flags = 0
+        if config.ReadInt(FIND_MATCHWHOLEWORD, False):
+            flags = flags | wx.FR_WHOLEWORD
+        if config.ReadInt(FIND_MATCHCASE, False):
+            flags = flags | wx.FR_MATCHCASE
+        if config.ReadInt(FIND_MATCHUPDOWN, False):
+            flags = flags | wx.FR_DOWN
+        if config.ReadInt(FIND_MATCHREGEXPR, False):
+            flags = flags | FindService.FR_REGEXP
+        if config.ReadInt(FIND_MATCHWRAP, False):
+            flags = flags | FindService.FR_WRAP
+        return flags
+
+
+class FindDialog(wx.Dialog):
+    """ Find Dialog with regular expression matching and wrap to top/bottom of file. """
+
+    def __init__(self, parent, id, title, size, findString=None):
+        wx.Dialog.__init__(self, parent, id, title, size=size)
+
+        config = wx.ConfigBase_Get()
+        borderSizer = wx.BoxSizer(wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(SPACE, SPACE)
+
+        lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+        lineSizer.Add(wx.StaticText(self, -1, _("Find what:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, SPACE)
+        if not findString:
+            findString = config.Read(FIND_MATCHPATTERN, "")
+        self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
+        lineSizer.Add(self._findCtrl, 0)
+        gridSizer.Add(lineSizer, pos=(0,0), span=(1,2))
+        choiceSizer = wx.BoxSizer(wx.VERTICAL)
+        self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
+        self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
+        self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
+        self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
+        self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
+        self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
+        self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
+        self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
+        choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._wrapCtrl, 0)
+        gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
+
+        self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
+        self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
+        gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
+
+        buttonSizer = wx.BoxSizer(wx.VERTICAL)
+        findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
+        findBtn.SetDefault()
+        wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
+        cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
+        wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
+        buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+        buttonSizer.Add(cancelBtn, 0)
+        gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
+
+        borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
+
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+
+        self.SetSizer(borderSizer)
+        self.Fit()
+        self._findCtrl.SetFocus()
+
+    def SaveConfig(self):
+        """ Save find patterns and search flags to registry. """
+        findService = wx.GetApp().GetService(FindService)
+        if findService:
+            findService.SaveFindConfig(self._findCtrl.GetValue(),
+                                       self._wholeWordCtrl.IsChecked(),
+                                       self._matchCaseCtrl.IsChecked(),
+                                       self._regExprCtrl.IsChecked(),
+                                       self._wrapCtrl.IsChecked(),
+                                       self._radioBox.GetSelection(),
+                                       )
+
+
+    def DoClose(self):
+        self.SaveConfig()
+        self.Destroy()
+
+
+    def OnClose(self, event):
+        findService = wx.GetApp().GetService(FindService)
+        if findService:
+            findService.OnFindClose(event)
+        self.DoClose()
+
+
+    def OnActionEvent(self, event):
+        self.SaveConfig()
+
+        if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_MDI:
+            if wx.GetApp().GetTopWindow().ProcessEvent(event):
+                return True
+        else:
+            view = wx.GetApp().GetDocumentManager().GetLastActiveView()
+            if view and view.ProcessEvent(event):
+                return True
+        return False
+
+
+class FindReplaceDialog(FindDialog):
+    """ Find/Replace Dialog with regular expression matching and wrap to top/bottom of file. """
+
+    def __init__(self, parent, id, title, size, findString=None):
+        wx.Dialog.__init__(self, parent, id, title, size=size)
+
+        config = wx.ConfigBase_Get()
+        borderSizer = wx.BoxSizer(wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(SPACE, SPACE)
+
+        gridSizer2 = wx.GridBagSizer(SPACE, SPACE)
+        gridSizer2.Add(wx.StaticText(self, -1, _("Find what:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(0,0))
+        if not findString:
+            findString = config.Read(FIND_MATCHPATTERN, "")
+        self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1))
+        gridSizer2.Add(self._findCtrl, pos=(0,1))
+        gridSizer2.Add(wx.StaticText(self, -1, _("Replace with:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(1,0))
+        self._replaceCtrl = wx.TextCtrl(self, -1, config.Read(FIND_MATCHREPLACE, ""), size=(200,-1))
+        gridSizer2.Add(self._replaceCtrl, pos=(1,1))
+        gridSizer.Add(gridSizer2, pos=(0,0), span=(1,2))
+        choiceSizer = wx.BoxSizer(wx.VERTICAL)
+        self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only"))
+        self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False))
+        self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case"))
+        self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False))
+        self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression"))
+        self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False))
+        self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap"))
+        self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False))
+        choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE)
+        choiceSizer.Add(self._wrapCtrl, 0)
+        gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1))
+
+        self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"])
+        self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1))
+        gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1))
+
+        buttonSizer = wx.BoxSizer(wx.VERTICAL)
+        findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next"))
+        findBtn.SetDefault()
+        wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent)
+        cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
+        wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose)
+        replaceBtn = wx.Button(self, FindService.REPLACEONE_ID, _("Replace"))
+        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)
+        buttonSizer.Add(cancelBtn, 0)
+        gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1))
+
+        borderSizer.Add(gridSizer, 0, wx.ALL, SPACE)
+
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+
+        self.SetSizer(borderSizer)
+        self.Fit()
+        self._findCtrl.SetFocus()
+        
+
+    def SaveConfig(self):
+        """ Save find/replace patterns and search flags to registry. """
+        findService = wx.GetApp().GetService(FindService)
+        if findService:
+            findService.SaveFindConfig(self._findCtrl.GetValue(),
+                                       self._wholeWordCtrl.IsChecked(),
+                                       self._matchCaseCtrl.IsChecked(),
+                                       self._regExprCtrl.IsChecked(),
+                                       self._wrapCtrl.IsChecked(),
+                                       self._radioBox.GetSelection(),
+                                       self._replaceCtrl.GetValue()
+                                       )
+
+
+#----------------------------------------------------------------------------
+# Menu Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+import cStringIO
+
+
+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' 
+
+
+def getFindBitmap():
+    return BitmapFromImage(getFindImage())
+
+
+def getFindImage():
+    stream = cStringIO.StringIO(getFindData())
+    return ImageFromStream(stream)
+
diff --git a/wxPython/samples/ide/activegrid/tool/HtmlEditor.py b/wxPython/samples/ide/activegrid/tool/HtmlEditor.py
new file mode 100644 (file)
index 0000000..e026fb9
--- /dev/null
@@ -0,0 +1,222 @@
+#----------------------------------------------------------------------------
+# Name:         HtmlEditor.py
+# Purpose:      Abstract Code Editor for pydocview tbat uses the Styled Text Control
+#
+# Author:       Peter Yared
+#
+# Created:      8/15/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+
+import wx
+import os.path
+import string
+import STCTextEditor
+import CodeEditor
+_ = wx.GetTranslation
+        
+
+class HtmlDocument(CodeEditor.CodeDocument):
+
+    pass
+
+
+class HtmlView(CodeEditor.CodeView):
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return HtmlCtrl
+
+
+    def GetAutoCompleteHint(self):
+        pos = self.GetCtrl().GetCurrentPos()
+        if pos == 0:
+            return None, None
+            
+        validLetters = string.letters + string.digits + '_!-'
+        word = ''
+        while (True):
+            pos = pos - 1
+            if pos < 0:
+                break
+            char = chr(self.GetCtrl().GetCharAt(pos))
+            if char not in validLetters:
+                break
+            word = char + word
+            
+        return None, word
+        
+
+    def GetAutoCompleteDefaultKeywords(self):
+        return HTMLKEYWORDS
+
+
+##    def _CreateControl(self, parent, id):
+##        import wx  # wxBug: When inlining the import of the appropriate html control below, have to specifically import wx for some reason
+##        self._notebook = wx.Notebook(parent, wx.NewId(), style = wx.NB_BOTTOM)
+##        self._textEditor = HtmlCtrl(self._notebook, id)
+##        if wx.Platform =='__WXMSW__':
+##            import wxPython.iewin
+##            self._browserCtrl = wxPython.iewin.wxIEHtmlWin(self._notebook, -1, style = wx.NO_FULL_REPAINT_ON_RESIZE)
+##        else:
+##            import wx.html
+##            self._browserCtrl = wx.html.HtmlWindow(self._notebook, -1, style = wx.NO_FULL_REPAINT_ON_RESIZE)
+##        self._notebook.AddPage(self._textEditor, _("Edit"))
+##        self._notebook.AddPage(self._browserCtrl, _("View"))
+##        self._insertMode = True
+##        wx.EVT_NOTEBOOK_PAGE_CHANGED(self._notebook, self._notebook.GetId(), self.OnNotebookChanging)
+##        return self._textEditor
+##
+##
+##    def _CreateSizer(self, frame):
+##        sizer = wx.BoxSizer(wx.HORIZONTAL)
+##        sizer.Add(self._notebook, 1, wx.EXPAND)
+##        frame.SetSizer(sizer)
+##        frame.SetAutoLayout(True)
+##
+##
+##    def OnNotebookChanging(self, event):
+##        if event.GetSelection() == 0:  # Going to the edit page
+##            pass  # self._textEditor.Refresh()
+##        elif event.GetSelection() == 1:  # Going to the browser page
+##            text = self._textEditor.GetText()
+##            if wx.Platform == '__WXMSW__':
+##                path = os.path.join(tempfile.gettempdir(), "temp.html")
+##                file = open(path, 'w')
+##                file.write(text)
+##                file.close()
+##                self._browserCtrl.Navigate("file://" + path)
+##            else:
+##                self._browserCtrl.SetPage(text)
+##        event.Skip()
+
+
+class HtmlService(CodeEditor.CodeService):
+
+
+    def __init__(self):
+        CodeEditor.CodeService.__init__(self)
+
+
+class HtmlCtrl(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_HTML)
+        self.SetProperty("fold.html", "1")
+
+    def GetMatchingBraces(self):
+        return "<>[]{}()"
+
+    def CanWordWrap(self):
+        return True
+
+
+    def SetViewDefaults(self):
+        CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Html", hasWordWrap = False, hasTabs = True)
+
+
+    def GetFontAndColorFromConfig(self):
+        return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Html")
+
+
+    def UpdateStyles(self):
+        CodeEditor.CodeCtrl.UpdateStyles(self)
+
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+        # White space
+        self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        # Comment
+        self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        # Number
+        self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        # String
+        self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        # Tag
+        self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+        # Attributes
+        self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+
+
+class HtmlOptionsPanel(STCTextEditor.TextOptionsPanel):
+
+    def __init__(self, parent, id):
+        STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Html", label = "HTML", hasWordWrap = True, hasTabs = True)
+
+
+HTMLKEYWORDS = [
+        "A", "ABBR", "ACRONYM", "ADDRESS", "APPLET", "AREA", "B", "BASE", "BASEFONT", "BDO", "BIG", "BLOCKQUOTE",
+        "BODY", "BR", "BUTTON", "CAPTION", "CENTER", "CITE", "CODE", "COL", "COLGROUP", "DD", "DEL", "DFN", "DIR",
+        "DIV", "DL", "DT", "EM", "FIELDSET", "FONT", "FORM", "FRAME", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6",
+        "HEAD", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT", "INS", "ISINDEX", "KBD", "LABEL", "LEGEND", "LI", "LINK",
+        "MAP", "MENU", "META", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", "OPTION", "P", "PARAM",
+        "PRE", "Q", "S", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRIKE", "STRONG", "STYLE", "SUB", "SUP",
+        "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR", "TT", "U", "UL", "VAR", "XML",
+        "XMLNS", "ACCEPT-CHARSET", "ACCEPT", "ACCESSKEY", "ACTION", "ALIGN", "ALINK", "ALT",
+        "ARCHIVE", "AXIS", "BACKGROUND", "BGCOLOR", "BORDER", "CELLPADDING", "CELLSPACING", "CHAR",
+        "CHAROFF", "CHARSET", "CHECKED", "CLASS", "CLASSID", "CLEAR", "CODEBASE", "CODETYPE",
+        "COLOR", "COLS", "COLSPAN", "COMPACT", "CONTENT", "COORDS", "DATA", "DATAFLD", "DATAFORMATAS",
+        "DATAPAGESIZE", "DATASRC", "DATETIME", "DECLARE", "DEFER", "DISABLED", "ENCTYPE",
+        "EVENT", "FACE", "FOR", "FRAMEBORDER", "HEADERS", "HEIGHT", "HREF", "HREFLANG", "HSPACE",
+        "HTTP-EQUIV", "ID", "ISMAP", "LANG", "LANGUAGE", "LEFTMARGIN", "LONGDESC",
+        "MARGINWIDTH", "MARGINHEIGHT", "MAXLENGTH", "MEDIA", "METHOD", "MULTIPLE", "NAME", "NOHREF",
+        "NORESIZE", "NOSHADE", "NOWRAP", "ONBLUR", "ONCHANGE", "ONCLICK", "ONDBLCLICK",
+        "ONFOCUS", "ONKEYDOWN", "ONKEYPRESS", "ONKEYUP", "ONLOAD", "ONMOUSEDOWN", "ONMOUSEMOVE",
+        "ONMOUSEOVER", "ONMOUSEOUT", "ONMOUSEUP", "ONRESET", "ONSELECT", "ONSUBMIT", "ONUNLOAD",
+        "PROFILE", "PROMPT", "READONLY", "REL", "REV", "ROWS", "ROWSPAN", "RULES", "SCHEME", "SCOPE",
+        "SELECTED", "SHAPE", "SIZE", "SRC", "STANDBY", "START", "SUMMARY", "TABINDEX",
+        "TARGET", "TOPMARGIN", "TYPE", "USEMAP", "VALIGN", "VALUE", "VALUETYPE",
+        "VERSION", "VLINK", "VSPACE", "WIDTH", "TEXT", "PASSWORD", "CHECKBOX", "RADIO", "SUBMIT", "RESET",
+        "FILE", "HIDDEN", "IMAGE", "PUBLIC", "!DOCTYPE",
+        "ADD_DATE", "LAST_MODIFIED", "LAST_VISIT"
+    ]
+
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getHTMLData():
+    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\xcdIDAT8\x8dcd`f\xf8\xcf@\x01`\xfc\x7f\xa3\x87"\x03X\xfe}\xbeI\x89~\
+\x06&\x8at\x0f\n\x03\x18\xe4\x954\xff\xc3\x00\x8c-\xaf\xa4\xf9_\xc7\xc0\xfc\
+\xbf\x93\xab\xf7\xff\xff\xff\xff\xff70\xb6\xfe\x7f\xed\xce\x93\xff\xd7\xee<\
+\xf9\xafc`\x0eW\xf3\xf5\xd7\xff\xff,\x0f\x1f^gPP\xd6B1\xf4\xc1\xddk\x0c\xf6\
+\xb6\x16\x0c{wma````x\xf7\xfc\x06\xc3\xea\xa5\xb3\x198\xd8X\x18\xbe~|\x06W\
+\xc7\xc5\xca\xc0\xc0\xc2\xc0\xc0\xc0P\\\x9c\xcf\xf0\xf4\xc5\x1b\x86\x15K\x97\
+\xc2%Y\xd9y\xe0lF\x0e1\x86C\x87\x8e0\x88\x88\x8a3\xfccD\x88\xe3\xf4\x026\xf6\
+\xa9c{\xfe_<s\x18\xc5\x9b\xf2J\x9a\xff\x19\xff\x9eN\xa5(!\r|4\x0e\x03\x03\
+\x00R\xe4{\xe74\x9e\xbb\xd1\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+
+def getHTMLBitmap():
+    return BitmapFromImage(getHTMLImage())
+
+def getHTMLImage():
+    stream = cStringIO.StringIO(getHTMLData())
+    return ImageFromStream(stream)
+
+def getHTMLIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getHTMLBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/IDE.py b/wxPython/samples/ide/activegrid/tool/IDE.py
new file mode 100644 (file)
index 0000000..ea58536
--- /dev/null
@@ -0,0 +1,2056 @@
+#----------------------------------------------------------------------------
+# Name:         IDE.py
+# Purpose:      IDE using Python extensions to the wxWindows docview framework
+#
+# Author:       Peter Yared
+#
+# Created:      5/15/03
+# Copyright:    (c) 2003-2005 ActiveGrid, Inc.
+# CVS-ID:       $Id$
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import wx.lib.pydocview
+import sys
+import wx.grid
+_ = wx.GetTranslation
+ACTIVEGRID_BASE_IDE = False
+
+#----------------------------------------------------------------------------
+# Classes
+#----------------------------------------------------------------------------
+
+class IDEApplication(wx.lib.pydocview.DocApp):
+
+    def __init__(self, redirect=False):
+        wx.lib.pydocview.DocApp.__init__(self, redirect=redirect)
+
+    def OnInit(self):
+        global ACTIVEGRID_BASE_IDE
+        
+        args = sys.argv
+        if "-h" in args or "-help" in args or "/help" in args:
+            print "Usage: ActiveGridAppBuilder.py [options] [filenames]\n"
+            print "    option '-multiple' or '/multiple' to allow multiple instances of application."
+            print "    option '-debug' or '/debug' for debug mode."
+            print "    option '-h' or '-help' or '/help' to show usage information for command."
+            print "    option '-baseide' or '/baseide' for base IDE mode."
+            print "    [filenames] is an optional list of files you want to open when application starts."
+            return False
+        elif "-dev" in args or "/dev" in args:
+            self.SetAppName(_("ActiveGrid Application Builder Dev"))
+            self.SetDebug(False)
+        elif "-debug" in args or "/debug" in args:
+            self.SetAppName(_("ActiveGrid Application Builder Debug"))
+            self.SetDebug(True)
+            self.SetSingleInstance(False)
+        elif "-baseide" in args or "/baseide" in args:
+            self.SetAppName(_("ActiveGrid IDE"))
+            ACTIVEGRID_BASE_IDE = True
+        else:
+            self.SetAppName(_("ActiveGrid Application Builder"))
+            self.SetDebug(False)
+        if "-multiple" in args or "/multiple" in args:
+            self.SetSingleInstance(False)
+            
+        if not wx.lib.pydocview.DocApp.OnInit(self):
+            return False
+
+        self.ShowSplash(getSplashBitmap())
+
+        import STCTextEditor
+        import FindInDirService
+        import MarkerService
+        import ProjectEditor
+        import PythonEditor
+        import OutlineService
+        import XmlEditor
+        import HtmlEditor
+        import TabbedView
+        import MessageService
+        import Service
+        import ImageEditor
+        import PerlEditor
+        import PHPEditor
+        import wx.lib.ogl as ogl
+        import DebuggerService
+        import AboutDialog
+        if not ACTIVEGRID_BASE_IDE:
+            import DataModelEditor
+            import ProcessModelEditor
+            import DeploymentService
+            import WebServerService
+            import WebBrowserService
+            import WelcomeService
+            import ViewEditor
+            import PropertyService
+            
+
+        # This creates some pens and brushes that the OGL library uses.
+        # It should be called after the app object has been created, but
+        # before OGL is used.
+        ogl.OGLInitialize()
+
+        config = wx.Config(self.GetAppName(), style = wx.CONFIG_USE_LOCAL_FILE)
+        if not config.Exists("MDIFrameMaximized"):  # Make the initial MDI frame maximize as default
+            config.WriteInt("MDIFrameMaximized", True)
+        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())
+        self.SetDocumentManager(docManager)
+
+        if not ACTIVEGRID_BASE_IDE:
+            dplTemplate = DeploymentService.DeploymentTemplate(docManager,
+                _("Deployment"),
+                "*.dpl",
+                _("Deployment"),
+                _(".dpl"),
+                _("Deployment Document"),
+                _("Deployment View"),
+                XmlEditor.XmlDocument,
+                XmlEditor.XmlView,
+                icon = getDPLIcon())
+            docManager.AssociateTemplate(dplTemplate)
+
+        htmlTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("HTML"),
+                "*.html;*.htm",
+                _("HTML"),
+                _(".html"),
+                _("HTML Document"),
+                _("HTML View"),
+                HtmlEditor.HtmlDocument,
+                HtmlEditor.HtmlView,
+                icon = HtmlEditor.getHTMLIcon())
+        docManager.AssociateTemplate(htmlTemplate)
+
+        imageTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("Image"),
+                "*.gif;*.jpg;*.jpeg",
+                _("Image"),
+                _(".gif"),
+                _("Image Document"),
+                _("Image View"),
+                ImageEditor.ImageDocument,
+                ImageEditor.ImageView,
+                icon = ImageEditor.getImageIcon())
+        docManager.AssociateTemplate(imageTemplate)
+
+        perlTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("Perl"),
+                "*.pl",
+                _("Perl"),
+                _(".pl"),
+                _("Perl Document"),
+                _("Perl View"),
+                PerlEditor.PerlDocument,
+                PerlEditor.PerlView,
+                icon = PerlEditor.getPerlIcon())
+        docManager.AssociateTemplate(perlTemplate)
+
+        phpTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("PHP"),
+                "*.php",
+                _("PHP"),
+                _(".php"),
+                _("PHP Document"),
+                _("PHP View"),
+                PHPEditor.PHPDocument,
+                PHPEditor.PHPView,
+                icon = PHPEditor.getPHPIcon())
+        docManager.AssociateTemplate(phpTemplate)
+
+        if not ACTIVEGRID_BASE_IDE:
+            processModelTemplate = ProcessModelEditor.ProcessModelTemplate(docManager,
+                _("Process"),
+                "*.bpel",
+                _("Process"),
+                _(".bpel"),
+                _("Process Document"),
+                _("Process View"),
+                ProcessModelEditor.ProcessModelDocument,
+                ProcessModelEditor.ProcessModelView,
+                icon = ProcessModelEditor.getProcessModelIcon())
+            docManager.AssociateTemplate(processModelTemplate)
+
+        projectTemplate = ProjectEditor.ProjectTemplate(docManager,
+                _("Project"),
+                "*.agp",
+                _("Project"),
+                _(".agp"),
+                _("Project Document"),
+                _("Project View"),
+                ProjectEditor.ProjectDocument,
+                ProjectEditor.ProjectView,
+                icon = ProjectEditor.getProjectIcon())
+        docManager.AssociateTemplate(projectTemplate)
+
+        pythonTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("Python"),
+                "*.py",
+                _("Python"),
+                _(".py"),
+                _("Python Document"),
+                _("Python View"),
+                PythonEditor.PythonDocument,
+                PythonEditor.PythonView,
+                icon = PythonEditor.getPythonIcon())
+        docManager.AssociateTemplate(pythonTemplate)
+
+        if not ACTIVEGRID_BASE_IDE:
+            dataModelTemplate = DataModelEditor.DataModelTemplate(docManager,
+                _("Schema"),
+                "*.xsd",
+                _("Schema"),
+                _(".xsd"),
+                _("Schema Document"),
+                _("Schema View"),
+                DataModelEditor.DataModelDocument,
+                DataModelEditor.DataModelView,
+                icon = DataModelEditor.getDataModelIcon())
+            docManager.AssociateTemplate(dataModelTemplate)
+
+        textTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("Text"),
+                "*.text;*.txt",
+                _("Text"),
+                _(".txt"),
+                _("Text Document"),
+                _("Text View"),
+                STCTextEditor.TextDocument,
+                STCTextEditor.TextView,
+                icon = STCTextEditor.getTextIcon())
+        docManager.AssociateTemplate(textTemplate)
+
+        xmlTemplate = wx.lib.docview.DocTemplate(docManager,
+                _("XML"),
+                "*.xml",
+                _("XML"),
+                _(".xml"),
+                _("XML Document"),
+                _("XML View"),
+                XmlEditor.XmlDocument,
+                XmlEditor.XmlView,
+                icon = XmlEditor.getXMLIcon())
+        docManager.AssociateTemplate(xmlTemplate)
+
+
+        if not ACTIVEGRID_BASE_IDE:
+            viewTemplate = wx.lib.pydocview.ChildDocTemplate(docManager,
+                _("View"),
+                "*.none",
+                _("View"),
+                _(".bpel"),
+                _("ViewEditor Document"),
+                _("ViewEditor View"),
+                ViewEditor.ViewEditorDocument,
+                ViewEditor.ViewEditorView,
+                icon = ProcessModelEditor.getProcessModelIcon())
+            docManager.AssociateTemplate(viewTemplate)
+
+            dataModelChildTemplate = wx.lib.pydocview.ChildDocTemplate(docManager,
+                _("Schema"),
+                "*.none",
+                _("Schema"),
+                _(".xsd"),
+                _("Schema Document"),
+                _("Schema View"),
+                DataModelEditor.DataModelChildDocument,
+                DataModelEditor.DataModelView,
+                icon = DataModelEditor.getDataModelIcon())
+            docManager.AssociateTemplate(dataModelChildTemplate)
+        
+
+        textService             = self.InstallService(STCTextEditor.TextService())
+        pythonService           = self.InstallService(PythonEditor.PythonService())
+        perlService             = self.InstallService(PerlEditor.PerlService())
+        phpService              = self.InstallService(PHPEditor.PHPService())
+        if not ACTIVEGRID_BASE_IDE:
+            propertyService     = self.InstallService(PropertyService.PropertyService("Property", embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_RIGHT))
+        projectService          = self.InstallService(ProjectEditor.ProjectService("Project", 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())
+        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())
+        messageService          = self.InstallService(MessageService.MessageService("Message", embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM))
+        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())
+            deploymentService   = self.InstallService(DeploymentService.DeploymentService())
+            dataModelService    = self.InstallService(DataModelEditor.DataModelService())
+            welcomeService      = self.InstallService(WelcomeService.WelcomeService())
+        optionsService          = self.InstallService(wx.lib.pydocview.DocOptionsService(allowModeChanges=False))
+        aboutService            = self.InstallService(wx.lib.pydocview.AboutService(AboutDialog.AboutDialog))
+
+        if not ACTIVEGRID_BASE_IDE:
+            projectService.AddRunHandler(processModelService)
+
+        optionsService.AddOptionsPanel(ProjectEditor.ProjectOptionsPanel)
+        if not ACTIVEGRID_BASE_IDE:
+            optionsService.AddOptionsPanel(WebServerService.WebServerOptionsPanel)
+            optionsService.AddOptionsPanel(DataModelEditor.SchemaOptionsPanel)
+            optionsService.AddOptionsPanel(DataModelEditor.DataSourceOptionsPanel)
+        optionsService.AddOptionsPanel(DebuggerService.DebuggerOptionsPanel)
+        if not ACTIVEGRID_BASE_IDE:
+            optionsService.AddOptionsPanel(WebBrowserService.WebBrowserOptionsPanel)
+        optionsService.AddOptionsPanel(PythonEditor.PythonOptionsPanel)
+        optionsService.AddOptionsPanel(XmlEditor.XmlOptionsPanel)
+        optionsService.AddOptionsPanel(PerlEditor.PerlOptionsPanel)
+        optionsService.AddOptionsPanel(PHPEditor.PHPOptionsPanel)
+        optionsService.AddOptionsPanel(STCTextEditor.TextOptionsPanel)
+        optionsService.AddOptionsPanel(HtmlEditor.HtmlOptionsPanel)
+
+        filePropertiesService.AddCustomEventHandler(projectService)
+
+        outlineService.AddTemplateForBackgroundHandler(pythonTemplate)
+        outlineService.AddTemplateForBackgroundHandler(phpTemplate)
+        outlineService.AddTemplateForBackgroundHandler(projectTemplate) # special case, don't clear outline if in project
+        if not ACTIVEGRID_BASE_IDE:
+            outlineService.AddTemplateForBackgroundHandler(dataModelTemplate)
+            outlineService.AddTemplateForBackgroundHandler(processModelTemplate)
+        outlineService.StartBackgroundTimer()
+        
+        if not ACTIVEGRID_BASE_IDE:
+            propertyService.StartBackgroundTimer()
+
+        self.SetDefaultIcon(getActiveGridIcon())
+        self.SetUseTabbedMDI(True)
+        if not ACTIVEGRID_BASE_IDE:
+            embeddedWindows = wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT | wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOMLEFT |wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM | wx.lib.pydocview.EMBEDDED_WINDOW_RIGHT
+        else:
+            embeddedWindows = wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT | wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOMLEFT |wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM
+        if self.GetUseTabbedMDI():
+            frame = IDEDocTabbedParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows)
+        else:
+            frame = IDEMDIParentFrame(docManager, None, -1, wx.GetApp().GetAppName(), embeddedWindows=embeddedWindows)
+        frame.Show(True)
+
+
+        wx.lib.pydocview.DocApp.CloseSplash(self)
+        self.OpenCommandLineArgs()
+
+        if not projectService.OpenSavedProjects() and not docManager.GetDocuments() and self.IsSDI():  # Have to open something if it's SDI and there are no projects...
+            projectTemplate.CreateDocument('', wx.lib.docview.DOC_NEW).OnNewDocument()
+
+        if not ACTIVEGRID_BASE_IDE:
+            if not welcomeService.RunWelcomeIfFirstTime():
+                wx.CallAfter(self.ShowTip, docManager.FindSuitableParent(), wx.CreateFileTipProvider("activegrid/tool/data/tips.txt", 0))
+
+        return True
+
+
+class IDEDocTabbedParentFrame(wx.lib.pydocview.DocTabbedParentFrame):
+    
+    # wxBug: Need this for linux. The status bar created in pydocview is
+    # replaced in IDE.py with the status bar for the code editor. On windows
+    # this works just fine, but on linux the pydocview status bar shows up near
+    # the top of the screen instead of disappearing. 
+    def CreateDefaultStatusBar(self):
+       pass
+       
+class IDEMDIParentFrame(wx.lib.pydocview.DocMDIParentFrame):
+    
+    # wxBug: Need this for linux. The status bar created in pydocview is
+    # replaced in IDE.py with the status bar for the code editor. On windows
+    # this works just fine, but on linux the pydocview status bar shows up near
+    # the top of the screen instead of disappearing. 
+    def CreateDefaultStatusBar(self):
+       pass
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+def getSplashData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x90\x00\x00\x00\xb4\x08\x06\
+\x00\x00\x00\xba\xf5zX\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
+ \x00IDATx\x9c\xec\xbdy\xb8eU}\xf7\xf9Yk\xed\xf1\x0c\xf7\xd6\xbduk.f(\xa0\
+\x98\x14\x84B\xa4\x04J\x01\x99\x14\xa3\xa2\xf8\x9a\xe45\t&\xfd\xb6\x99\xda<\
+\x1d\xcd\x9b\xf4\x9bt\x92N:\x1d\x9f7\xe9\xc4\x98\x8ei}}\xd4\xa45J\x82\x88A\
+\x01E\x19\x15\x19\xa4(\xa6\x82\x9a\xe7\xba\xe3\x99\xf7\xb4V\xff\xb1\xf6>\xe7\
+\xdc[\x05\xea5\xa6$\xec\xcf\xf3\xc0\xbdu\xee\x9e\xce>\xfb\xac\xef\xfa\x8dK\
+\x18c\x0c%%%%%%?"\xf2X_@IIII\xc9+\x93R@JJJJJ\x16E) %%%%%\x8b\xa2\x14\x90\x92\
+\x92\x92\x92\x92EQ\nHIIII\xc9\xa2(\x05\xa4\xa4\xa4\xa4\xa4dQ\x94\x02RRRRR\
+\xb2(J\x01)))))Y\x14\xa5\x80\x94\x94\x94\x94\x94,\x8aR@JJJJJ\x16E) %%%%%\x8b\
+\xa2\x14\x90\x92\x92\x92\x92\x92EQ\nHIIII\xc9\xa2(\x05\xa4\xa4\xa4\xa4\xa4dQ\
+\x94\x02RRRRR\xb2(J\x01)))))Y\x14\xa5\x80\x94\x94\x94\x94\x94,\x8aR@JJJJJ\
+\x16E) %%%%%\x8b\xa2\x14\x90\x92\x92\x92\x92\x92EQ\nHIIII\xc9\xa2(\x05\xa4\
+\xa4\xa4\xa4\xa4dQ\x94\x02RRRRR\xb2(J\x01)))))Y\x14\xa5\x80\x94\x94\x94\x94\
+\x94,\x8aR@JJJJJ\x16E) %%%%%\x8b\xa2\x14\x90\x92\x92\x92\x92\x92EQ\nHIIII\
+\xc9\xa2(\x05\xa4\xa4\xa4\xa4\xa4dQ\x94\x02RRRRR\xb2(J\x01)))))Y\x14\xa5\x80\
+\x94\x94\x94\x94\x94,\x8aR@JJJJJ\x16E) %%%%%\x8b\xa2\x14\x90\x92\x92\x92\x92\
+\x92EQ\nHIIII\xc9\xa2(\x05\xa4\xa4\xa4\xa4\xa4dQ\x94\x02RRRRR\xb2(J\x01)))))\
+Y\x14\xa5\x80\x94\x94\x94\x94\x94,\x8aR@JJJJJ\x16E) %%%%%\x8b\xa2\x14\x90\
+\x92\x92\x92\x92\x92EQ\nHIIII\xc9\xa2(\x05\xa4\xa4\xa4\xa4\xa4dQ8\xc7\xfa\
+\x02~|\xf4\x82\x7f\x1f\xa9\x89\xe6%\xf6\x14\xe8\xa1\xfde\xff\xa7\xe9\xff\xfd\
+h\xc7\x1f>\x8f\x06\xa3\xf33\x88|\'9\xff\x1a\x86O.\x8er1bh\xef\xa3]\xe8\xd0\
+\xa1\x8b\x9f\xf3\x0f\xa9\xe7o\x93\x9f{\xe1i\xfb\xef\xa3\x7f\xbd\xdan+\x14\
+\xe5<\xa2\xa4\xa4d1\xbc\xc2\x05D\x03\xd9\xd0\xbf\x8bQT\xce\x1b@\x8f.\x01\x1a\
+\x89F\x90\xe4[8\x80\xca\x87V\x99\x1fE\xe7"c\xf2\x81W\xe6\xa7\x10\x0c\x04\xa4\
+7\xf47\x95\x1f\xe7(\x83\xb8\x19z\xa1\xf8]\x82\x11\xfd\xa1\xdcn\xa3\x17\xec\
+\x18\x1bP\x02\xdc\xc1\xf6Y\xbe\x99\x02\x94\xc9\xec\xf9S\x03B\x80\xf402\xbf\
+\xea\xfc\xa58\xea!M\x8a\xef\xbb`\x12\x88\xbb`R\xf0B\x90\xd5\\\xf4JJJJ~4^\xe1\
+\x02\x02GZ\x10G\xff+\x0c&\xea\x06\x9dK\x8d\x99\xb7E!\x1e\x83\t\xbd\\p\x04\rF\
+\r\r\xb8\x12Pv\x94F\x82p@(\x8e\xa6\x03B\xd8\x01\xdf\x1e<\xb5\xa3\xbb\x04\x10\
+\x98\x85\x1fC!0\x02p\x8a\x1d\x0bkIb\x10\xc4\x1aD\x92P\x95\x1a\xa4\x01\xe9\
+\x80\x14\xfd+7\x80\x16\xd0\x8b\x0c\xbe\x1b\xe0JC\x96F()\xadp\x98\xd4n+()))Y\
+\x14\xc2\x18\xf3R\x1e\x9eW\x00\x85\x05\xd2\x9f\xc3\xe7?\xe5Q\xbdA\xc5V:\xb7>\
+\xc0\xe4\xe3\xa7\x1d\xc4\x178w\x10\x14\x83\xfe\xb0\xabk0\xd8\x1b18\xee\xb0h\
+\x1c\xe9\xa5J\x11$H2\x04q\xfe\xd3Z=\x86\x00\xf0\x10(0\xf2\xc8\x83\x88\x14D\
+\x06:A\x9b\x0c-\x15\xca\xf1\xd1\xa8\\Wr\x0b\xc4@\x92d\xc4Z`\xa4\x8b\x138 \
+\x16\xc8\x9f\x01\x93\x81o\xffD\x1c\xc5T|\xaf\xd4\x90\x92\x92\x92E\xf1\x1fD@`\
+X<\xe0\xa8\xa1\x86\xa1}\x06q\x8bb\xf6\xbf\xd0b(\xf6Q\xc3/.\x08,\x18 \x16\xf3\
+\x9dh\xc3\xc8\xfc\xc8\x82\x0cA\x9a\x8b\x96\x15\x0f\x99\xcb\x94\xc8]g\xe0X\
+\xeb\xc6\xc8!\x0b$E\xf7ZH\xa9\xc1S\xa0\x1c\nW[f$i\xa2\xf1\xbc\xf9\xd7\xaf\
+\x99\xff\x0e5\xd0\xcb\xac\x05\xe4-0\xd2\x14\xe0\xf3\x1f\xc2\x0c-))9\x06\xbc\
+\xe2\xc7\x8e\x81\r1\x9f#f\xd5\x06\x10\xc3C\xab\xe5h\xc2Q\xec/\x8f&\x1eC\xaf\
+\t1\xdf\xdb\x04\xb9\xc5\xc2@\xceD.j\x12\xf7\x88\x13\xd9pJ\x04$C/h\xeb"\xcb\
+\x0f*\xab!\x85$X+\xc9\x8a\x8d\x11\x90\xba\x92F\n\x99\x04%\xf3\x98\xc8\xd0\
+\xf15\xd0\xee\xc0\xae\xdd\xfb\x99\x9d\x9de||\x9c5\xc7\xaf\xa0\xea\xdb3\xea\
+\x08\x96\xf9Gy\xf3%%%%?\x04\xafp\x01\xf9!\x82\xbf\x85p\x14\x83s\x7f\x14\xb7a\
+\xe8a\x97\x15\x0c\r\xfc/cy\x0c\x8eko\xa0\x18\xdaO\x15\x9b,\xf4e-\x10\x0fS\
+\x1cka\x00[H\x0c\x12-\xb0?q\x88Im\x8c\x1c\x85\x12\x02\x91_s"\xa0\xe3X1\x90@\
+\n\xec\xde\x9e\xf0\xec\x93\x8f\xb1\xe5\xc9\xc78\xb0w;\xad\xd9in|\xdb\r\xdcx\
+\xc3\xf5\x8c\xd6\x14\x196\xde\x1e\np\xfc2\x04RRR\xb2x^\xe1.\xac\xa1P\xc1\x11\
+\x7f\xd1\xf37\xe8\xbf6$ B\x91\xe6\xb1\x8f"\x99\xd5\n\xc7\xf0\xber\xfe\xc1\ry\
+L$\xff\xdbB\x91\xe8_\xd0 \xe8\xdd\xdfv\x90$6@$6\xce\x81\x83\xc1\xed\xe7\x84\
+\x15?\xa3\xa3\\y+\x82\x83\x07`\xdf\x1c\xdc\xff\xfc~\xf6\xce\xcc\xb0k\xeb\xb3\
+\xecyq\x0b\xf1\xec\x01V\x8ez\x9cw\xeaq\xac;n\x82[~\xfefFC\x17Gd\xa4q\x8f \
+\x08P\xd2^H\xb7\x17Q\t\xc2#\xee\\III\xc9\x0f\xc3\xabH@\x86\xc2\xc9\xc2\xe4\
+\x16\x84\xe8\x0b\x88\x8b\rvS\xa4\xc5\xc2PM\xc7p\xadD.\x0c\xda\xd8(\xbaq\x8e\
+\xe2\x03K\xed\x89\xc5P\xea\xafP \xed1\xb2<n"\xc1\x9e\xddh\x8cpH\xb0\xc2\x11c\
+\x85#\x05\x0e\'\xb0{_\xc6\x96\xe7^\xe0\x99g\xb7\xb1g\xf7af\'\xdb\xb4[1\xcdT\
+\xd1\xaa\xd4I\x84@\xc4\r\xbc\xa4\xc9qc>\x97_\xb0\x9ew]s)\xaf9\xb9\x86\x8f\
+\x8ds@b3\xafLF\xda\xed\xe2\xb8.x\xc5_\xcb4\xde\x92\x92\x92\x1f\x9dW\xb8\x0b\
+\xeb\x87p\xc1,(\xb0\xb3\x0c,\x88^\x1c\x13x\x1eQ\xd4$\xf0\xa4\xad\x93\xd0)h\r\
+N\x00\xd2JK\x91\xf8\xdb?\x8a\xd4\x90i;(K\x99\xa7\xf2\x0eek\x89<\x96!\xa0\xdd\
+\xe9\xe2WF\x98j\xb7\xa9U\xab\xc4@\xd7@M@\x15I\x16C36\x04u\xc1\xde\x06<\xf4\
+\xfd\x03\xdc\xfd\xc0w\xd9\xfc\xe2v\xa6\xda=R\xe1\x10\xa5\x828\x01GU\x08\xbd\
+\x11\x8c\xe3\xd0\xc9b\x94\xe3\x91\xc6M\\\x93q\xe6\xbaS\xf8O7l\xe2\xc6K\'\xa8\
+\x00\xbd.\xd4C\xec{21E\xb8\xdf\t\x8b\xa2\x92\xfcz\xcb:\x90\x92\x92\x92E\xf0\
+\x8a\xb7@^\x9a\x81\xc5\xa15h\xad)\xde\xaa\xe38\x08!l\x16\x15\xa0\xd0d\xd1\
+\x1c\xbe/@\'\xd6\n\xc9\x0c\xb8!h\x8fD\x06h!\x07\xb2c\x0c\x0e\x19jH\xbdL?\xe7\
+\xca\xa6\x10\xa7\xd8\xe1:\xce\x7f\x17C\xaf\x1d\xea\xc1\xae\xdd-\xb6~\xffQ\
+\xe4\xe4n~\xedW\xde\xd7\xb7:\xa6Rx\xef\x07\xfe\x8a]\xd3\x1d2\xbf\xcaL\x14\
+\x93\xe1\xe2\x04!aPG\'\x82n+B\x18\x87Z(\xe9\xb5\xf7P\x0fR\xdet\xe9E\xfc\xd2\
+\xfb\xae\xe0\x8c\x89\xbc,\xa4\x0b\x13\x95\xc2\xc2\xc9\xad\x0fL^\x08S\xd4\x7f\
+8 r1))))\xf9\x11y\xc5[ /\xe5\xc32C\x83\xa2\x90\xd8\x02:\x86R\\\x8d\xd5\x08$\
+\xc4FS\xf1\x03\xfa\x11\x07Q\xb4%q\x88\xf0\xc8\x84\xec\xc7$\x00\x94\x10d\xf9\
+\xadK\x19\x88D\x11\x02\xc9\x18\xb8\xa2\x9e{\x11\xb6\xef\x99d\xcb\xb3\xdb\xd9\
+\xbe\xeb\x00\xcfo\xdd\x81\xf4\x02\xb4QLm\x7f\x92\x8f\xfd\xce\xcf\xd2\xe8B#\
+\x86\xa6\x86\xcf\xfc\xf3\x93\xec\x9a\x8c\xf0\x96\x1e\xcf\\l\xa8T|zi\x861\x82\
+\x04\x07%\xc1\xf5\r"\x13\xf8t9\xf7\xf8\x1a7]\xf7\x06\xder\xe5\xd9\x8c{\xf6\
+\x03\xf5\x04\xb8\x15Hz=\x1c?\xaf\x8c\x97\x1eE\xa8?\x13\x83:\x99~\xd0\xbf\xa4\
+\xa4\xa4\xe4G\xe4\x95/ \x05\xf3\xfaAY46P\x8cP(\xe5\xe0\x0c\x87+\xc4\xa0-H\
+\x92*B\x17\x84N\x07{*\x87,\xf3I\x95C\x04\xf4\xb0\xa2P\x9c\xc6\xcf\x7f6\x80\
+\x833\xb0ko\x8fm;\xf7\xb2}\xd7A^\xdc\xbd\x8f\x9d{\x0e39\xd3B\xb8\x01q*P\xd2g\
+dl\x9cf\xb6\x84\x8a\xa8\x10\xc5\t\xe7m\xd8\xc4u\xd7_\x80\x0b\xd4Bxq\x1b\xdc\
+\xfa\x95\xbb\xc0\xad\xd3M\x14\x8d\xb9\x16T\x05(\x05YD\xda\x9eeI%\xe4\xac\xd3\
+\x8e\xe7\xac\xd3O\xe7\xd4e\x01W\x9f7\xcaI\x13P\xf5\xec\xf5$\xbd\x0ccRT\xe8\
+\xa2\xfc\xfc\xe3\xcd+\xe53d\xbf\xec\xf2\x88b\xc9\x92\x92\x92\x92\x1f\x91\xff\
+8\x02\x02G\x15\x91 \xf0\xfb\xe5\x86QF?\xdfV\xe6\x9b\xa7\x00\xae\x9d\xdd\xbb\
+\xa2B\xd6i\xe0T\xaa }:\x99\x15\x8e\x060\x1d\xc3T\x03\x0e\x1c\x80m/nc\xc7\xb3\
+[\xd8\xb3\x7f\x86\xad;{t\xb3\x80\xc4\x80Q.\xd2\xaf\xa0\x9d\x90X\x1fO\x1c\xa4\
+\x04\x95\x90\xa8=\x87\x14\x9a\xac\x97\x11e1Q+"\xf0$7\xff\xdc{hjp$\xbc\xb0\
+\x1b\xfe\xf0O\xfeo\x12#\xe8\xf4b2\x13\xb3t\xf9\nzI\xc4\xd8h\xc8\x05\xe7\x9d\
+\xceY\xa7\x1dG\xe7\xd0\x1e\x0e\xecz\x81ucmnz\xf3\n\xd6\xb8\xe0kC\xdc\xe9\xe1\
+\x85>\x9eo\x88\xda=\xd2n\x82\x13\x04\xf9\xfd\x18\x88\xc7p\xc5\xfcK\xb5\x89,)\
+))\xf9ax\xe5\x0b\xc8Q\\X\x0b\x07\xc9BW\xa4\x9a_\x9d\xddK\xc0\r \xd107\xabY3\
+\xae\x88M\x05\xc7\xf8\xc4\x02\x9a\t\xfc\xf1_\xdc\xce\x8b\x87;<\xb3{\x9a\xfd3\
+-\xb4\xd6T<\x97\x9a\xaf\x902 \xf5\x97\x93j\x9fD\x0bR\xa1\xc8\xa4\x836\x8e=\
+\xabL\xe9\xa6\x80\xe3\xa3<\x85\xa7b*^\x05\x99v8s\xdd\x89lz\xad\x15\xb2\x0c\
+\xb8\xe3\xee\xbb\xd8\xfc\xcc\xb3T&N#\x0cC&\xd6\xaee\xed)\xa7p\xc9\xeb\x8fc\
+\xf2`\x87\xc7\x1e\xfc:\x9f\xfb\xfa\xa7\xd9t\xd1z~\xfb\x97\xde\xcd\tc\xe0\x01\
+\xa6\x17\x83/\xf1B\x97,\xea\xa2\x94\xc2\xafV\x01\xe8v;\x84\x95Z\xdf\x9dWt\
+\xf6R\x1cUkKJJJ~$^\xf9A\xf4b\x1a=\xd4\xf2|X8\xba)6\xe1H\x16\x8d\x14\xad`4\
+\x9a\xd0\xec\xc0\xf7\xb6\xec \xea\xf6h\x1f\xda\xce\x07\x7f\xf1\x1ad\x1e\xf1N\
+\x14\xecm\xc0\xe5\xef\xf80mo\x9c4X\x86SYB\x10\xd8F\x84I\xa7A\xd4m"=\x97TJ2\\\
+\x12Y\x04\xa5\xddAf\x93\xd0H\x99\xa1\x1b\xb3\xf8D\xb8i\x9b\x89\x11\x8f\xdf\
+\xf8\x95[\xb8\xfeR\x9f\n\xf0\x9d\xc7\x9e\xe3\x0f\xfe\xe4oX~\xdc\xb9\x9cy\xfe\
+\x9b8\xfbu\'\xf2\xdcvx\xfc\x89-<\xf1\xc4\x83,\x1f\x91l\xba\xf8\x0c\xdey\xf5\
+\x06\xceY\xeb z]B\xd3!\x0c\x03t\xe6apPC\x11\xfd,3\x08!\x8a\xac\xe1\x057\xeah\
+\xed\xef\xcb zII\xc9\x8f\x8e3\xbf\xf0\xad\x98\xd0\x0f\x05\xa0\x8b\x17E\xca\
+\xbc\xe9\xbe\xf91\x8c\x17\xa1\xed\x88>8a^+q\xb4A\xce\xe9oV\xb8\xa2\xcc\xd0\
+\xbf\xa5\x1cl\xbd\xb0\x17T\x06\xbc\xb0/\xe2\xc0\xe4\x0c/\xee\xd8\xcb\xf6\x1d\
+{\xd8\xb7\xff\x10\x87\xa7\xe6h4\xda\xb4\xba\t\xc2\xa9\x90%1\xa67\xcb;\xdf{\r\
+\xabB\xfa\x95\xde\xc6\x85`l%]1J\xa4}:\x9d\x84F\xc7v\xbfU\x08\x94\x1b \x84\
+\xc6\x08\x8d\x91\x9a\xdc!f\x7f\x8a|\x9e\x9f\xf6\x18\x19\xa9\xd2m\xb6\x98\xa8\
+(\xc2Xs\xea\xf2\x1a\xef\xbc\xd4\xefg\x815\xc5\x18\x7f\xf8\xd1\xbf\xe4\x89\'w\
+\xf1\xf0\xf7\x9e\xe1K\xb7}\x99V\xb3\xc3\xca\x95c\xfc\xec\xf5o\xe0\xcd\x97\
+\x9e\xcd\xf9\'A\x05\xf00\x84\x81\xce[\xed\xb6\x90\xc122\xc4\xbc\x8az\xa9\x06\
+\x1dy\xe7Y\x19F\xd3\xef\x19o\xf8\xb7I\xdf\x9dW\xa5\xaf\x87\xfe)\x07\xe7>Z%\
+\xffK\xbf\xfc\xf2\xa7\xe8\xfff?\xf5l\xc8\xba\x1at\x0f\xc8?\xfd\xa2\x0e\xc7\
+\xb65\x063h\x80Y<%\xa2\x14\xd0W//\xd5\xc3\xe8\xa8,\xec\xdam\x9f-\xf5*\x7fv\
+\x1c2\xec\x17M\xd9~L\x19\n\x8d\xec\x7f!\x15\xa0\x93\x04\xe9Et\x1b\x87\tG\xc6\
+\xec\xc8\x9ah\x90\x1eI\xd4\xc5u]prA)>\x94,\xb1\xf9\xb3\xae;\xff\x8c\xc6\x0c\
+\x84\xc2\x00ZB\xaa\xc1DPq\xe9E-\x82\xc0\xa7\x18\x0e\xa3^\x8c\xefW\x98\x9eM\
+\xa9\x8e\xd9\x80v\'\xffk\x0c$\x06\x0eO\xc2\xae\xbd\x9a\xdd{\xf6\xb1\xe7\xc0A\
+v\xee\xdd\xcb\xae\xbd\x87889C\xa6\\2\\2\xe1a\x8cK&\x0245\x1bX\x0f\xc11\x19A\
+\x00=g\x96]\x1dX\x16\x82\x9b\xd9\xb8u\xbb\x03M\x1d\xd3s\x05\xd2Ud\xe4\xef[\
+\x1b2%p\xbd:\x15\x15\xd0\xea\xf4H\xa3\x0c<\xc7V$\x9a\x14H\x11\xae\xa1VQ\xcc\
+\xee\xdc\xcc\xa9k\x96\xe1vg\x18\xa5\xcb\xef\xfe\xe2\x7faI\n-\x03\x7f\xff\xd5\
+\x83\xdc\xfd\xc8\xf7y\xfe\xe9\xa7qH\x08]\xcd\xea1\x9f\xab\xdf~\x05\xef\xb8v\
+\x03k\x96XW\x95\x87=\xb42\x06\x8cg\x05\xdc\xb5\xa6\x95\x14\xc3\xbd\xb7^\x8a\
+\xe1\x8az\xf5\x12\xf51?"f\xe8?i\x07d\x8d\xc1 \xf2\xeb\x91\x83uP\x86\xd5L\x80\
+A\xf7\x07\x7f\xb0M+\x8f\xb8\xf6\x05\xd6\xe4\xbc\xf7hl{\x9a\x1e\x1a\x81\x83\
+\x07\x90\x80\xa3\xb1\xff\x13]\x90\x89\x15\xf2\xcc!\x8a\x03\x1c\xcf\x01U\xc4\
+\xbe"$).a."%\xafJ\xb4\x86$\xb5\xfem\xd7\xa6\x93$\xa9F)\x89\xe8\xafv\x90?\xdb\
+&\xa3\x1b\xf5\xf0\x83\x80\x0cM;\xee2\xe6-\xf9\x89>?i\x9a\xe28\x0eY\x96\x91$\
+\tA\x10\x10E\x11RJ\\\xd7%\x8ec<\xcf#\xcb2\x94\xb2\xd7_\xbc\x06\x90e\x19q\x1c\
+\x13\x86Gv\x9c0\xc6`\x8c\xf5V\x00\x08!\xfa\xfb\xce\xcd\xcd1::\xfa\x03\xaf\
+\xcf\x19|\xb15f\xa8\xf5F\xe1+\xef\xb44\xa1\x07\x18M8R\x032\xb2^\x82TKA\x83\
+\x1b\x86\xfd\xb6\x1dY\x9c\xa2\xb5FJ\x89t\x1c\xc4B\xf1\xa08\x97\xc2\x18;\xce&\
+\x1d\xf0\xab\x928\x0bQ\x022\xe5\x93"\xf3\xcc \x07\'pi%\xe0\x8e9l\xd9\x07\xdf\
+}\xfa /\xee;\xc4\xa1\xd9\x19\x9ex\xfaY\xa6g\xbaD\xa9!N\r\x08\x17\xe9\x06h\
+\xe9\x12e\x1eQ\xb6\x1c\xd7\xab\xa2q0\xb90\xdaF\x85y\x9bC\x93\xe1\xeb\x88Tdte\
+\x85\xc9\xc8Z-\x1e1\x12\xcf\xc6L\x94$\xca\x0c&\x8e\xc1s\x90\x95\x11\xa4\x94\
+\x88\xb4C\x1a\xa7L\xcf\xb5\x11\xca\xc3\x0b}\x942\xa4Y\x97$\x9a\x83\xb4\x8d$\
+\xc2\t$\xe7\x9d\xb6\x86\xb3N>\x8ek/\xdb\xc8\xe5\xaf\r\xf1\xb0\xedH\xfe\xe8\
+\xbf\xdf\xc1?=\xf0<T\xc7\x10B\x128\x92\xcb_\x7f\x1e?\xf7\xce\xabX\xbf\x96~\
+\x15\xb9\xed\xbf\x9b\x0e\r\xd8\xca\x8a\xb8\x18\xba\xa5\xbc\x9cx\x14\xfc\x04\
+\x1et3\xffW;\xe0\xdb.^\xba\xff9\xce\xdf\xce\xb6\x99\xb7\x93\x88b\xb2\xf2R\
+\x0c\xb7p)\xc4d8k\xcc\xc9\xe7@:\xd7ok\xa2\xe6W\xa2\xf3\x15\xb8\x1c\x1f/\xcc\
+\xb7\xcb\x8d_\x07\x9d\xcb\\\xc9\xab\x9e\xf9\xbe^\x1cGZ]\x89\x13L\x96b\xb2\
+\x84\xb0VA\nI%\xa8\xd0\xca\xba\xb8\xca\xa7\xe2U\x8fx\x1e\xff\xad)DA)E\x1c\
+\xc7\x00\xf8\xbe\xed-\x11EQ_(\xba\xdd.\xb5Z-\x7f;\x92^/o[\xa4\x14a\x18\xd2h4\
+\x08\x82\xa0\xbf}!L\x85x\x14\xafy\x9e\x871\xe6\x87\x12\x0f\x00g\xd0\xd0o\xd0\
+\xaa\xa3\xdf\xbd\xc9@\xa5Z\xfc-&\x8b5\xcau\x91a\x1dm$F@\xa3\xd1\xc4q5^\xe0\
+\xe3\xf8\x1e\x06\xd9\xaf\x89H\xb077\xc3.\xac\xa7\x8d]\xf3Hb\xdbyh\x17\xc4\
+\x12\xbbm[\x83\xe9\xc2X\x18\x92\x01\x8a\x848\x81\'\x9f\xdd\xc9\x7f\xfd\xa3\
+\xbffF\x8f\xd3\xa2\xc6d;\xc6\r+\x8c\x8c\x06t\xa3\x8cXUH\x95\xc4\xb8.F\xfa\
+\x08\xe9cd\x00\xda\xc1\t\x04I\x927[\x17\x19\x92\x14a\x12{\x85"C\x88\x14#3\
+\x12R\xb4\xd3e\xba]\x18E\x11\xd6Y\x04&I\xa9U\xab\x04\xfe(q\xe6\xd0h\xa7\xa4Q\
+\xd7\xe65\xb9\x12\xaaU\xbc\xc0\xc1I;Ds\x87P\xf1\x1c\xeb\x96\xd7\xb9\xe8\xdcs\
+8\xe3\x94\xe38w\xfdi\x9ct\x12|\xff\xa9\x88\xa7\x9e\xdbJm\xf9\xd9\xacY\x03\
+\x1f\xfb\xdc3|\xe5\xe1-(\x932.`\xd3\x9b^\xcf{\xdeq1\xa7\xac\xc8\x17\x1f\xd4\
+\xb6\xfd\xfa\xc2\x0e\xbb\x14"\xf8\xd32\xf6\xbd\xacz\r\xf7\x02\x1b\xden\x90\
+\xe2\xa0\nk\xb7\xf8{\xbf\x8d\x0c}\xeb\xea\xa5:&\x83\xb5\xcc\xd2,\xc5U\xb2o\
+\x95\x90\xa4\x18\xed"\xfc\x00\x84\xcd>\xb3\xe9\xda \x94\xc6\xef\xcb\x96\xf9\
+\xa9\xb9\x8d%\xc7\x06\x93&\x08\xafX\x14\x0e\x92,\xc5Q\xb6\xdeJ\xf9\n2\x05\
+\xb8\x10%\xf4\x92\x087\xacPqjt\xb4\x9d,\xbb\xceO\xd6z\x15Bp\xf8\xf0a\x96-[\
+\xd6\xb7"\x92$\xc1u]\x84\x10}\x01\xa8T*dYF\xaf\xd7\xa3Z\xad\xe2\xe4\x1e\xa1\
+\xe9\xe9i\xc6\xc7\xc7\x19\x19\x19\xe9\x1f\xb3\xd3\xe9\xa0\x94\xc2q\x9c\xbe\
+\x90t\xbb\xddy\xc7\xf7<\x8fv\xbbM5O\xc8y)\x9c\x85\xdf\xa0\xc2d\x93F\x0e\xc5?\
+\x00m\x90^H\x8cC\x86\x87\x16\xb0\xe7 \xac]Q\x07l\t^\x9bAzl\x11\x11\xe8\xbb \
+\x04\xa4\x02Z\x1a\xf6\x1d\xd4\xec\xdc\xb1\x8b}\x87\xdalyf\x9a\xfd\x07&\t\x02\
+M=\xe8\xf2\xd1\xdf\x7f\x1fu \xc4\xc5s\xe1\xacsN`\xc9\xda\xd3y\xe0\xbb\xbb\
+\xa8\x1dw\x12\xd1\xa8C,\x04\xed\xb9Y\x94\xf2\x11^\x05-=\x8cV$FBZ\xac\xa7\x91\
+w\x9br<{%\xc6\xdaW\xca\xd8\xf59\x94\xb6"b\x84&5\x1a\x1c\xc5\xcc\\\x07M\xc5V\
+\xac\x0bH\xa2\x8c\xc0\xf3\xe8F]\xa6\xe6\xba\xe8\xd4\xc5\rjT\xaa!\xbeL\xc9t\
+\x8f\xcck\xd3j\xce\xe0\xbb\x8a+/}-7^}!\xebO\x81\xc3\xbb\xe1\xe9\'\xb7\xf1w\
+\x1f\xff\x0c\xbb\xf6\xec&\x13\x92\x8f\x7f\xea\xc3T\xaa\xf0\x7f}\xfc\xbb\xdcv\
+\xc7\xbf2Q\xafp\xd3\x9b\xaf\xe0\xdd\xd7\xbc\x9e\x93V\xdb{\x17u\xa1\x12\xda\
+\xd4\xde\xc9\x83\xd3\xac^1N\xbf\xa9#\xcc\x8f[\xfc\xb4\x8c~\xf9u\xe8#Vp\\\xb0\
+M_<t\xff\xa5y\xb1\x8b~k\x15\xac\x99 m\x8cb\xf8\xa8\xf6\xf9\xd2\x88\xfc>\x08\
+\x9d\xe1\nc]\xa0\xe8\xdc\x15\x11\xda\x02Q\x01\x9d\xc4\xbe$\xb0\x8b6\xaa<\xef\
+M H\xa3\x0c\xc7/\xddW\xafZ\x04\x08\xdf\xcd\xc7(Id4Ij M\t]\x07OH\x9a\xb3s\xd4\
+\x97\x8c\x82\xe3\x10\xb8a\xdf\xad\x9a\xc5\x82J\x10\xfc\xc4\x9d\x9fi\x9a\xf2\
+\xe7\x7f\xfe\xe7\x1c8p\x808\x8e\xc9\xb2\x8c \x08\xf8\xec\xe7\xfeAdib\x80y\
+\xee\xa6j\xb5\x8a1\x86V\xabE\xbd^g||\xbc\x7f\xac\xc2\x82q\x1c\x07\xcf\xf3\
+\x88\xa2\xa8o\xcd\x14\x96I\xbb\xdd&\x08\x82\xfe\xb1~\x10N\xf1\xc5\x1e8\x1d\
+\x86\x83\x8bX\x15p\x01\xe5\xda\xf5%\xb4DJ+\x18#+`*\xb3\x16\xa0,n,0\xd5\x86\
+\xfd\x87aj6f\xcb\xb3/\xb0\xff\xf0\x14/\xee\xde\xcb\xbe\x03\x87h\xb4\xbbdF\
+\x80Ph\x110\xb6j\x1d;wL3R5\xf8\xd9$\x0f|\x7f\x9a\xab\xce\x1b\'5v5\xd7\xb96l\
+\xba\xfa\x06\xee\xdd\xfdU\xba\xee8Y"\xf0Fj\xc43\x99m\x0c\xd8vA\x056\x06\xe3\
+\xc8|\xda.\xc0q\xed\x01:]{U\x99\xf5]\x98\xcc\xb1\x83\x93q\x11Z\x83\x12\x98,\
+\xc2\xf5\x04\xb3\xd3\x11\x92\n\xca\x04\xb6\xb1a/\xa2\xd7\x9e#\x18\x19c\xacZ!\
+\x89\r\xdd\xa4I\x96\xcc\xa0IpE\x93k/y-\x97]\xf16.:\xcb\xa3\x0b\xdc\xf3m\xf8\
+\xbd?{\x98m\xdb\xf7\x11G)q\x94\xa1\x98\xe0\xa3\x7f\xf6\x01\x96V\xe1\xd3\xff\
+\xf88\xdf\xfc\xe2\xdf\xf0\xc1\xff\xf43\xdc\xfc\xf6\xb7r\xca\x98\xbd<\'\xb3.\
+\xd8\xba\x07q3\xc1\xf5]V/\x1b\x1f\xca2+\x04d~\r\xc7\xcb\xc7=\xfe\x1d\xc8O>p_\
+\r\x0f\xf7\x85\x180\xd8H\x0c\x0b\x8c\xb1\r,\xe7\x91o\\$Z\x88\x81\x88\xf4\x8f\
+\xc9`^c\x1bZZ\xf3\xd5\x08\x83pF\x88$\xb43[\xb7S_28\xbd\x03xh\xdc,\xa6"}\x1c\
+\xd7\x1b\xa4\xe7\x95\xbc\xea\xb0\xe3\x95\xa4\xa732\x01\x8e\x90\xc8|BQ<\x95\
+\xb5\xf1Q0\xd0m\xf6\x08\xabA?\xef^e\xee\xbfK\x01\xae\xe38\xec\xdc\xb9\x93,\
+\xcb\x98\x98\x98 \xcb2\xe6\xe6\xe6\xf8\xfa\xd7\xee4\x85\xf5Q\x88\xc7\xa7?\
+\xfdi\x1c\xc7\xa1^\xaf\x13E\x11B\x08\x9a\xcd&Zkn\xba\xe9&\xda\xed6\x8f>\xfa(\
+\xadV\x8b+\xae\xb8\x82J\xa5\xd2\x17\x90$I\xb8\xfb\xee\xbb\x99\x9a\x9a\xe2\
+\xf4\xd3O\xe7\xfc\xf3\xcf\x9f\xe7\xdez\xc9\xeb+\xbe\\z\xe8[$0\x83Q*\x8f\x8d\
+\xa7\x1a\x12\xa9\x90\xd2\xa1\x0b<\xb37_\x7fbw\xcc\xd4\xe44\xbb\xf7\xeec\xd7\
+\xbe\xfd\x1c\x98\x9ca\xae\xd1\xa6\xddK\xe9\xa5\x1a\xbc\x80\xd4HR\x14F-E\x86>\
+\xda\x08\xe2,#N\xa1\xb1k\x12*Khdm\xaa\xa2\xca\xe7\xff\xe5\xeb\xbc\xf9\xbc\
+\xf7\xe0\n{\xeaeU\xd8x\xf1\nV\xfe\xcbR^\xdc;\x0b\xee\x08q7\x86\x14\xc2\xd11Z\
+]\x89\x11y\xddE\xa6!\xedYG\xb70\xd6,\xf5\xf2\x8f9\x1f\xb8\xb4R\xc4\xdaFR\x95\
+\xd1\xf8\x08D\xa2\xf1|\x87\xd9\xc9&\x821$\xae]L\xca\x18B\xdf\xa3\x95\xf6\x98\
+m\xb70\xc6\xb0z\xcdq\\\xfa\x86\r\\y\xd9j\xd6\xaf\xb5\x87\xfe\xce\xa33\xfc\
+\xee\x9f\xde\xc5\x13Omc\xb6\xa9\xd0\xaa\x02\xb2\x86\x14\x8aZ})\xb7\xfc\xdc\r\
+\x9c\xbf\x0e\xfe\xea\xaf\xbf\xc5\xcc\xbe\xa7\xb8\xe7\xf3\x7f\xcb\x8a1\x97\
+\x11\xa5\x11\xda\xa0\x93\x04%$\xae\xb4\xe5\xe4^\xc5\x9d\xaf\x12/\x91\xa2|\
+\xcc\x19\x16\x87y\x14\x99Qz(\xf3kx\xa7\xe1\x9f\xf9\xb3\xd6\x8fX\x8a\xc11\x86\
+2\xa6\x16\x9e\xaa\xef{6\xda\xc6<\x1c\x0f!\x14=\\\x9e\xdb\x9fr\xcfC\xbby\xe4\
+\xc9]\xdcu\xdf\xc3,\x19\xf79\xe7\xec\x15\\{\xf99\\s\xe9\xd9L\xa8*\x18h\xcd6\
+\xa8\x8d\x8dP\xf2\xeaD\x03\xed$F\xbav\xf6\xdd\x04\xb6\xee\xea\xb2s\xc7~z\xed\
+\x0e\x81\xebp\xf1k\xce`\xed\x04\xb8\xa3\x01\x8d\x86\xc1u\x04ah\x9b\x94\xf6:\
+\x10\x84\xfc\xc4\' Zk\x94R4\x1a\r\xdb\xc3o(\xf0]\xb8\x9eZ\xad\x16\xdf\xfe\
+\xf6\xb71\xc6P\xab\xd5\xd8\xb3g\x0fccc(\xa5\xa8\xd7\xeb\xfd\xed\xbf\xf0\x85/\
+p\xe8\xd0!\x1a\x8d\x06\xb7\xdcr\x0b\xc6\x18\x92$a\xd7\xae]|\xf6\xb3\x9f%\xcb\
+2\xf6\xee\xdd\xcb\x05\x17\\@\xb3\xd9\xa4^\xaf\xbf\xec\xb59z\xc8\x02Y\x98"\t\
+\xd8L\xd48A\x84.\x1aI\x0c\xec\x98\x83\xdf\xfe\xfd\xbfb\xcf\xc1.\xbd\xcc\x03\
+\xe1!\xa4\x83\x91\n-\x15\x86\x80\xc4HR#\xf1e\x8d\xc4@\xa2\x05:\x05R\'\xf7\
+\xe1+p5x=F\xc7\xaat;\x92\x8a\xe3\xf2\xfd\xa7_\xe0\xee\xfb\xb7\xf2\x9eKO\xc31\
+)J8\xac\xf4\xe0\xaa\rg\xf0\xd9\xaf?NV\r\xe9D)\xc4\x11\xdd\xa8\x83q\xbc|\xb6\
+\xaa\xed\x84\xd7h\xdb\x9a\xdd\x08Df\xd0\xbd\xd4\xb6O\x9727\x95d\xde\xbb]\x90\
+\xa5\x06\xa1\r2MQ\x99\xa215\x83\xe2x\x14\x02#\xc0w=\x82 \xe0\xe43\xcf\xe5\
+\x92\xd7o`\xc3\xc5\x0e\xcb+\xd0\x05\xee\xbfw\x9aO\xfc\xfd7xz\xfb>\xba\tH\xe1\
+\x13\xf8\xe38#\x82V\xa3\x8dJ\xa7\x18\t}\xae\xbex\x03?\xb3\t^x2\xe3\xfdo\xdd\
+\xc8ik.\xa3\xae\xa0\xd7:\x80_\xab\xd8\xeb\xf1\xf3\x0f!\xe9Y\x11t}\x8e\x08\
+\xcd\xe5S\xee\x9f\xba\xc9r!\xccG\x18\xf3E,\xc3\x0cb\x13\x80]0\xabXJx\xc8eU\
+\xa4\xd3\x1e\xf1\x06\xe7\xbb\xbc\x0c\xc5j\xf6\xb9\xa5\x93eV@\\\x9f\x08\x97-\
+\xfb{|\xee\xcb\x0f\xf3\xe5{\xb7\xb2\xfd`\x97%+\xd6\xb1\xaf7\xcb\xae\x87\xb6\
+\xf2\xe4\xf3\xcf\xb1u\xeb\x8b\xfc\xec\rWr\xfa\x8a\x8a\x15\x8f\x9f\xba\x1bZ\
+\xf2\xef\x89\xebzD\xc0\xe1\x0e\xdcv\xf7\xf7\xf9\xca\xdd\x0f\xf1\xe2\xaeC\xa4\
+\t\xb8B\xe3\x8b\x84\xff\xf2\xfe\x9b\xf9\xcf?s6\xc1\xa8\xc0D\xf6\x91S\x12\x82\
+\x7f\xa7\xd5<}\xdf\xc7u]\x1a\x8d\x06\xbe\xef\xe3y^?\xb8\x1e\x86!\xbd^\x8fZ\
+\xad\xc6\'>\xf1\t\xa4\x94|\xf3\x9b\xdf\xe4\x93\x9f\xfc$\xef\x7f\xff\xfb\xb9\
+\xf4\xd2K\xd9\xbbw/\xb5Z\x8d\xc3\x87\x0f399\xc9\x19g\x9c\xc1\x0b/\xbc\xd0\
+\x8fqx\x9e\xc7c\x8f=\xc6\xd2\xa5K\x99\x9a\x9a\xa2\xd5j\x01\xf4\x83\xf2/\x873\
+\x98\xd5\x15\xc1\xf2\xbc\xf2n\x08\xa5\xecJv\x1d\x9d\xd2\x96\x0e\x07\xa7#v\
+\x1d\x98DU\xd7"E\x95D\xf8\x18\r\x99\xd1d\xc6\x0e&B:\x08\xe5\xd0JR\x90\x0e\
+\xc2u\x10B\xd9\xf8\x82)FC\x83\xe3J\xe6\x0e\x1f\x02\x93\xd1\x0c$Z\xfb|\xe9\
+\x8eo\xf1\xceKO\xc3I4i\xd4fy\xbd\xca-\xefX\xc7\xedw~\x83=\x07Z\x10,\xc5\x1f\
+\x1d#\xc9R;@eI>\x13\x05O\x1a\\a\x90&\xc3\x08\x9b\xa6\x96\xe6\x99WY\xb1~\xa0\
+\xc9\x03\xd1\xa4\xb8"\x05\xd3De\x11Q{2OIM\x118\xf8\xbe\xcb\xa7\xff\xc7\x870\
+\x0e4R\xf8\xf6\x03p\xe7\xdd\xdfb\xfb\x0b\xcf\xa1\xd2\x06\xd2\xaf0\xc5\x18$6\
+\xda\x9df\n\x11\xb5\xf1L\x93\xe3\'\xea\x9cu\xe2R\xfe\xf7\xff\xf9L\\\xe0\xa4s\
+\x15\x8e\x86P\x82\x8e\x9a\xd4\xaa5\x9b:$\xb5\xbd\x96,\xb5~@\xc7\x03\x9d\x8b\
+\x87]g\xea\x88\x82\x0e\xebl,D\xfe\xd87\x138z:}\xe1\xca\xd2\xb9\xc0C\xe1p\xd3\
+E\xf68v\xf5E!\x06\xf5=\xc3)\xbbv\x15\x16\x89\x18\xb2\xb9\xc4P`>C\xa2\\\t\x19\
+\xa4\xb8\x1cl\xc3\xd7\x1ex\x96\x7f\xba\xeb{\xec\x99q\x18;\xed|\xa6\x0f\xcdP[\
+q*\xb2[c\xdb\xde-\xdcy\xdf3\x1c\xbfr-\xcb7]\xc0\xd2J\xd9L\xb2\x04\x1a=\xb8\
+\xf3\x9b[\xf8\xdc\xad\xdfd\xf3\x8e\x19F\x96\x9f\x8c\xb7t\t\x93\xb3S\xa8x\x8e\
+\xbf\xb9\xf5.\x1aq\x8f_z\xe7\xeb\x98\xf0A\xc7\xa0R\x9d\x7f\xf5~\xf21\xb48\
+\x8eq\x1c\x07\xc7q\x08\xc3\x90n\xb7K\xaf\xd7\xc3\x18C\xa7\xd3\xa1R\xa9\x006\
+\xfb\xca\x18\xc3\xaaU\xabh\xb5Z4\x1a\r\x00\xd6\xacY\xd3\xefF^\xa9T\xd8\xb8q#\
+\xff\xf8\x8f\xff\xc8C\x0f=\xc4\xa6M\x9b\x88\xa2\x88{\xee\xb9\x87k\xae\xb9\
+\x86\x87\x1ez\xa8\x9f\xf1U\xc4E^\x0e9\x98\xdfY\xfay\xcfF\xcf\xdbP\x03\x81t\
+\x90\xc0\x0b/ng\xc9\xd2\t\xe6\xba=ZB\xd3H#\x9a:\xa2\xe3\x18"\xd7\x90\xc8\x98\
+\xd8\x89Q\x15\x03n\x02\xaa\x8d\xa1\x891\xb3 \xe6@6@\xda\xdf\xd3\xd9\x03\xd4G\
+k\xe0\x86\xf4\x12\xc9L\x12\xf2\xc2\xc1\x1e\xb7}\xf3\x192\xcf\xc3\xaf\x84\xb8\
+\t\x9cT\x83\x1b7\xaegy\x90"\x93&\xda\x08t\xe6\xc2l\x8a\xaf\x03<\xedB\xa7C\
+\x14uq\xaa\nQ\x87\xa6\x99\xa5\xa3\xe6H\xe4,B\xcc\xe1\x9b\x06\xa1\xee\x12$\
+\x1d\x9cV\x13\xbfu\x98 \xd9K\xcd?D\xbd:\xc7yg/C\x99\x14\xe8 \x81z\x1d\x1e\
+\x7f\x02~\xef\xcf\x9e\xe1\xbd?\x7f+\x1f\xfb\xdb\x7f\xe6\xd9\xad\xb3\xb4:u\
+\xe2t)\x89X\r\xe68\xa8\x9c\x02\xfer\xe2L\x91\xf6f\x08\xf5$W\x9c\xb7\x94?\xf9\
+\xcd\x1bX\x06\xac@S7\t\xa1\x88 K\x90n\x08\xa6\x06\xc2\xa6\x0b\x18\\P\xbe]\
+\x7fD8\x03-w\x17|0\xc6~\x12\x02\x8d\xc2\xa00\xf9\x80z\xac(\xe2eG\xda\x1f\xc0\
+|\x8bVht\x9a`\xf2\x9e\\\xdd\xcc\xeac\xd7\xd8~c=l\x12F\xb7\xf8\xdd\xd8:\x1f\
+\x9bC7\xff\xe8\x854\x15\x9a4\xd7M\xe8\x02^\x15>\xf5\x85o\xd0RK\xf1O8\x83\x99\
+\xc33\x10\xd4ie>\xed\xb8\x06\xa3\xa7\xb1w\xd6\xe3\xcbw?J[\xd8\xe3\x1f\xcb\
+\xbbWr\xec\x91@%\x80\x7f\xf8\xfcW94\'\xd1\xde\xf1t\xc2\x139\xd4\xac\xd1[\xb2\
+\x8et\xf9\xe9\xec\xecz|\xe9\xde\x87\x99\xc5\xc6~\x1d\x0fp\x12\xc8:pD\x0c\xef\
+\xdf\x16c\x0cR\xca~yD\x1c\xc7\x18c\x08\x82\x00!\x04\x95J\x05!\x04Y\x96\xf5\
+\xf7I\xd3\x94 \x08\x90R\x92eY\xdf\xe5\x15\x04\x81m\xc5T\xa9p\xd1E\x17q\xef\
+\xbd\xf7"\xa5d\xc7\x8e\x1d4\x9bM6n\xdc\xc8\xcc\xcc\x0c###x\x9eG\x9a\xfe\xe0\
+\xf7\xf6C%\xa1\tG\x82I@H\\`\xd5\xca\xe5\xccMO\xe1W\xd6\x10\xb9\xfe\xa0XPJk\
+\x11\xa4)\xa41\xddV\xc3V\xe4\x01vE<\x18\xfe\xcaJ\xa3\x11\x95\x80v\xcf\xa6\
+\xcd\xa2<L\xd5c\xdb\xf4\x01n\x7f\xf0y6^|&K\x8c\xa4\x12@\x00\xbc\xeb\xea\xd7s\
+\xf7\xc3O2\x17G\x08?$\xd1)#\xe3\x01q\xa7\x85\x90\x86\xd1\xf1%t\x92.\xb3\x07\
+\x0f\x81#\xf0\xc7G!\x8d1I\x0f_f\xf8:\xa5;\xb7\x8f\xb4\xd3\xe5\xe4\x95\xabY\
+\xb7\xfe\x04\xd6\x9d\xb1\x96\xab\xae_\xcf\x8azQ\xac\x97@\xd2\xa5\x95:<\xf8\
+\x9d\xdd|\xe8\x0f\xff?\xbc\x15g\xd1\xed\xa4d\x9d\x04o\xe9*F\xc6G\x89f\x0f\
+\xd2\x9e\xe9\xc0\xb2\x15\x90&\x90vp\xb2&\xe7\x9e\xbe\x96\x9f\xbf\xf1\xdd\xdc\
+\xf8\x86\xe3\x18\x05lh=\xb1\xb3p\xed\x00\xae\xad\xe10\xf6~eB\x92\rUC\x081\
+\x94\x99\xd4\xcf\\\x1a~\xa2\x8a?\x16\xf7\xf1X\xf7\xd2-\xd6V\x1cdU\xd1\xff)\
+\xd1I\x0f\xe9\x00B ]\x97\xc4\xd8"z\xa5\xec\x97\xb1\xd8;\xc5\xfe\xbb\x9f\x14 \
+\x06\xbf+$\xc2\x14\xd5\xe5\xaa\x7f|\r\xcc\xb4\x1aTG\x96\xd2\xc8\xe0\xa9\x9d\
+\xd0\xd3!\x91\n\x88\xe2\x04V\xaf\x82\xc9\x86\xad\x07QKp\xab\x01\xba\x1d\xf3\
+\xe43/\xd0\x8ca"\xb4\x9fy\xc9\xab\x9b\xe9i8\xb0\x7f\x1a5~\x06K\x96\x9c\xc2l[\
+\x81\xaaC\x9c\x10%\x1ab\xc3L/\x01\xc7&g\xd4U\x86\xc8z6Y\xe7\x18G#\x8b\xd8\
+\x86\x94\xb2?\xe0k\xadI\xd3\x14\xdf\xf7\xfb\xae\xaen\xb7K\xa7\xd3\xe9\x17\
+\x0fn\xd8\xb0\x81\xcf|\xe63\xec\xdb\xb7\x8f\xfb\xee\xbb\x8f\xb3\xce:\x8be\
+\xcb\x96\xa1\x94\xe2\xe0\xc1\x83\xfdT\xe1\x1f\x84\xd3\x1f\xa8\xe6\xcd\xc5\
+\x8a\x82;\x86\xc7\x07\xc0\xe0 8y\xed8\x8eI\xc8\x8c&\xedi\x9b\xf1Td\xd8Hi\xf3\
+%\x85\xb4>}e\x03\xd6\xd2\x08D\x9e\x1e+s\xebF\x0b\x07\xe3x\xe8\x04\x9bI\x95\
+\x82\x18]A\xa7\x9d\xf2\x95G\xf6q\xc9#Mn~c\xdd\xd6\x84i8\xfb\x14\x97K/X\xcf\
+\xee{\x9f%BA\xb7I#\x9aC\x9a.\xdaxD-\x1f\xe1\xd4P#k1B\x92vR\\RTb \x9aatD\xf0\
+\xf6+\xcf\xe7\xbaM\x97p\xc6\xa9\x15;\xae\xf8\xb0\xa3\x01w|{\x8e\xc9\x17\xbe\
+\xc7\x1f\xbc\xf7\r\x8c\xf85B\xb7\xc2\x81\xc9\x19V\xad>\x8e\x03\xa9\xa122\x8a\
+\x1a\xf7\x99kw\x98\x9dm\x13:\x19K\xdc\x8clr\x0b:i\xb3\xee\xd45l\xbc\xf8\x12\
+\xce[\xbf\x96\xd7\xac\xaf!\x80Y@\x13\xe2\xe1\xe0\xa0Q\x12[\x048(\x83\xc0HH\
+\x87j!\x046\xb1\xa8H\x80\x90G\xab\xd0&\xdf\xff\x98\xaf$8\x1c\x9f\xb0"8oFo$\
+\xd2uA\xa7\x14\xb5F\xddD\xa3<\xc9T\x03\xbe\xf5\x9d\xa7\x10\xc1\x08\xd2\xaf\
+\xd1\xd3\x828\x03\xc7\xf3\x91:\xc5\xd1\x1d\xc6}\xcd\x15\xe7\xaf\xc6a\xc8Qg\
+\x06\xf7\x06\xc0+\xba-\x0b\x98\x9e\x05)<\x84\x91\x10u`\xfa ,[i\xcd\x1dc\xf0\
+\xbc\x90\xd0\x8c\xd2\x9aLAA\xcf@P\xfa\xaf^\xb5\x14\x86\xbe\'\xc1s]\x1aQ\x8a;\
+\xe2A\xd4\x83e\x13\xd0:\x0c\xe3c\xa0\xf7\xd2\x8dg\xe9\xe5\x1d\x8aL\xe1\x96=\
+\xe6\x937\xfa\x82P\xb8\xaf\\\xd7\xed\x8b\xc6\xb0\x00\x14E\x84\x85e\xb1a\xc3\
+\x06>\xf1\x89O\xf0\xe8\xa3\x8f\xf2\xe0\x83\x0fr\xd3M7\xf5\xf7)\xfe\x1b\x0e\
+\xd6\xbf\x14\xd6\x02\xc9g\x852\xaf!\x9e\xb7K.\xb0\x8e\x80\x9eNP\xd2cb\x14^\
+\xb3\xfeT\xbe\xb5\xf9 \xd4\xc6\xb1\xf5\xd2\x06\xe2\xc8n\x1f\xb8 ]+,\xd9 \xc1\
+\x13\xa3mv\x13\xda\xc6V\xa5D\xa7\xd2\x06\x8d3\x01\xa9\xc1\xe0!\xc7V37\x19\
+\xf3\xff|\xf1\x1b\\\xb6\xf1mL\x080m\xa8\xd4\xe0\xddo\xdb\xc4W\x15\xaf\xed@\
+\x00\x00 \x00IDAT\x1f\xdc\xc2\xbe\xee\x1c\x84\x1eh\x97z\xbdb\x83a\xdd\x98,\
+\xee"\xb3\x08\x93\xc4\xc4\x9d&g\x9ez\x12\x1b/~\x0b\x1b_\xbf\x8c\x13\xd7\xc2\
+\x84g?\xf6\x03S\xf0\xf4\xf3\x87\xf9\xfaC\xcf\xf3\x9d\'\x9fg\xaa9\xc79k+x\xbf\
+\xf0&\xd2$\xa2\rl\xdb\xb9\x8fF\xa7K\xd43DNd#gI\x8c[s\x19\xa9\x08\xb2\xe6\x14\
+\xab*p\xe3\xd5\x9b\xa8W\x1dj5x\xe3\xb95F\x1c[DY\xe9\xdfBE\x96\'\xa3\x8a"P\
+\x9cO\xd7\x17\x96\xda\x15\xb6\xc5\xf0L|\x90\xb2\x9ao\xa4\xf3\xc2\xa7\x05\xb1\
+\x91c\xc3@2\x84\x19\xbc\x97\xc1eIt\x96!\x04h!\xec:+\xc0\xf6]3|\xf2s\xff\xcc\
+\xfeFJ\xea\xd6\xe8f\x82\xd4H\x94\xe7"\xb2\x88\xaa\xe8\xb1\xa2j\xd8\xf8\xc9\
+\xdf\xcb\xcf\xe0\xd8\xd8\x990\xfd\xe3K\xc0w|b\xec p\xdc*\x18\t\x15\x07\xa7\
+\xa7\t&V\xd2\x8b#hMA3\x06\xe3\x12%\t\xbdx\x8au\xa7\x1cG\xbdb\xc5\xe3XKp\xc9\
+\xb1g|\t\xbca\xc3\xf9\xdc\xf1\xc06f\xf7\xed\x00S\xc5w&\x88t\x03ff\xa8\xfa)g\
+\x9f\xb4\x86@B\xa5xf\x94\x07if\xe3\x96\xc7\x90$Il\xe7\x8f\xfc?\xb0.\xac,\xcb\
+h4\x1a\xfd@y\xe1\xe6J\xd3\x94n\xb7\x8b\xef\xfb\x9c\x7f\xfe\xf9\xdc~\xfb\xedh\
+\xad9\xe7\x9cs\xc8\xb2\x8cN\xa7\xc3\xaaU\xab\x00~8\x01\x19$M.\xb0@\n\x04\xa0\
+u\xee\x9dJp\xf1\x08\x0c\xbc\xf5\xca7\xf2\xdd\xcd\xff`\xd7\xebv\xac?\xce\xf6\
+\x8aJl\x05\x97\x04\xebx\x02\xdbG\x06$\x86l\xe8,\xfd3J\xd7.\xe0\x1d\xf8\x98\
+\xe6,\x08I\xb8\xec$\x9e|f3\xff\xef\xad{\xf9\xf5w\xaca\xbcf\x07\xfe\xd7\x9c\
+\x08\x17\xac[\xce\xbe\xc7\xf6\xa2\x96\x9f\x8e\x96k\x98\x9b:\x0c\x9d\x03\xe0\
+\xc4,\xaf\xa4\xac[\xbd\x84k/\xbb\x90+/;\x8b\xe5\xe3\x83\x1a\x95\x18\xf8\xd6\
+\x16\xf8\xc2\x97\x1f\xe4\xd1\xa7^ \xd5.\x9df\x84F\x12\xd6\x8fc\xff\xd4\x0c\
+\xb3\x11\x98\x86\xa6\xb6\x0c\xa4S%\xc9\x0cc\xabW2;s\x18\xd7\x95\x1c\xbfn-\
+\x87\xf6oc\xf2\xd0\x0e6\xac?\x99\x0f\xff\xc2/\xf0\xad;nc\xe9\xc4\t\xbc\xe3\
+\xba\xf3m\x98\xde\xc0\xa8\x80\xa9\xc9\x03\xac\x98X\xdaw\xf0\xe8\\<\x8c\xb0^\
+\x95\xfe\x87p\x94\x0f\xe6\xa5?\xb6|G\xb3\xe03:\x96\x14\xf1\xb2\x85\xd6RnN\
+\x19#\xd0\x1aP\x0eRYq\xcdd\xc0\xb6\xddS\xf4\xea\xab\x89M\x9d\x8e\x96dF\xe2\
+\xa4\x0eR;h)\x19E\x932\xf4\x9c\x0c\xd5\x93\xc8!\xf1\xed\xb4\x1b\x84\xd5\x11N\
+Z\x05\x17\x9e\xb3\x8a}\xf7>\x8a\xe9y\x8c,]\xc1t\xbbK\x1a\xc0x\xe0\x90\xcdN\
+\xa1\xa2\xfdl\xba\xe4\n\xbc\xcc\xce\x9e\x8e\xf5\xad+9\xb6\xb4\xdbm\xbcj\x95w\
+\xdcp\x19\xbb\x0f\xb5xfw\x93\x8e2\x88\xd9\xe7\td\x8b\xb9]\x8fq\xde\xc5\xeb\
+\xf8\xa5\x1b/g\x85\xb4S\xe5f\xb3\xc9HX\xcf\xbf\x8a\xc76\x89\xa5\xa8H\x07\xfa\
+q\x92,\xcbX\xbe|9\xc6\x98~E:@\xab\xd5btt\x94z\xbdN\x9a\xa6\\\x7f\xfd\xf5\xdc\
+{\xef\xbd\\}\xf5\xd5\xac\\\xb9\x924M\xa9\xd5j\xf8\xbe?\xaf\xc8\xf0\xe5\xe8w\
+\xe3\x15\xc2\x0c}?\xc5 %\xdf\xc1\xa6\x96\xa2\xf3\xfeA\xe0\x1ax\xe3\x86S\xb8\
+\xe0\xccS\xb9g\xeb\x0c:QH\xd7\xc5\xf5\\\xeb\x92IR\xdb\xb7$w_\x91\x1fK/\xccC\
+\x95\x12<\xdf\xba\xbat\x8fe~\xc0\xe1\xfd\x870I\x86\x7f\xda\x19tk\x13\xdc\xfa\
+\xf5\xfby\xf7\r\xeff,\xb7\x1c*\xc0{\xde\xf2z\x1e\xdc\xfc\x05\xba\x9d\xbdt:1\
+\xabW/\xe7\x82\xf5\x1b\xb9\xe4\xfcu\\x\xa6\xe2\xa4\t\x08\x19\x18\x98\xf7<\
+\xbc\x9f\xaf\xde\xfb\x08[\xb6\x1ff\x7fCq\xb8#\xc9L\x00~\x157\xcc\x90R\x90\
+\x06\x1e\xd27H\x1fj\xcbB"\xe0\xb1\'\xb7\xb0|\xc5\x89l{q\'\xb5\x95K\xf1=\xc1\
+\x0b\x0f\xdc\xc7\xa9\x17\x9c\xc1\x07~\xf5wY=\xe2\xf0\xe7\x7f\xf2\xbb\xfc\xf2\
+\xcf\xdd\xcc\x95\x97\x9d\x83\x87m\n\xe8\t\x9b\xde\xb6z\xe9D^\x11\x0f\xa0PB\
+\x932h\xadQ\xc4\xc9\x07\x86\xb0\xbd\xcf&\xff\x14\xfa7\xce\xde\xac\x81[q\x98c\
+>\x02\x0e*\x89\xe0(\xd7\x87@yv"\x11\x03\xbd\x14\x8c\x03\xd2\r\xf1F\x96\xd1\
+\xf5\x97\x11{\xa3\xa4\xa9\x04\xa9H\x94B&-R\xd9\xc1\xa9\x0c\xd6\x91\xb7\xc7\
+\x15\xb9\x15\x92[;\xf9\xe34\x16zd\xd8g\xe3\xfd7mbzv\x92\xaf\xde\xf7$\xccL\
+\x13\x8e.\xa3\xd9h\x10\x870\xea\xf48\xfb\xcc\x95\xdct\xddF\xc6\\\x10\x89\x9d\
+\xbb\x94\xbcZ\xd1\xd4\xab\x1e1\xf0\x86\xf3\xc7\xd8w\xe0\x12\xbe\xf9\xd0V\x9e\
+{q\x1f\xcfo{\x82e\x13\x017\xbd\xfd\x8dlx\xcd\xf1\\\x7f\xfeI\xf8\x80\x8b\xa6\
+\x1b\x03E\xad\xd61f\xd8B(,\x86\xe5\xcb\x97s\xed\xb5\xd7r\xce9\xe7\xccscMLLp\
+\xc3\r7\xf0\x9a\xd7\xbc\x86N\xa7\xc3i\xa7\x9d\xc6M7\xdd\xc4%\x97\\\x82\xd6\
+\x1a\xdf\xf7\xb9\xfe\xfa\xeb\xfb\xd5\xec?\x0c\xceB7H\xdf\xa5"\x86^\xd0\xd6\
+\xb1\xe2\n\x8d&\xa5\xaa\x1c\x82:\xbc\xfb\xedW\xf3\xd4\'ncog\n\x1de8\xaa\x82r\
+=\xb2$\xb1=\xec\xdc\x904+\x8e(\x87\x9c\xd7\xc5\xd4\xcf\xc9\xcd\x83\x94\xbaL\
+\x08\xdb\x07\x183\x1d\xbaR\xd1\x9d\x9b"\xa8\xd79<\xb3\x8b\xdb\xeex\x8e5o9\
+\x8d\xd5A\x84g$W]p<\xef\xbb\xfa"\x9a1\xbc\xed\xb2\xd7p\xfa)#\x8c\xd5\x07k\
+\x93w\x80\xfb\x9f\x80{\x1f\xf8.\xdf\xfa\xcec\xe0V\x99\xebfL5\x12p]\xea\x13\
+\x13\x18\x1cZ\x8d\x06\x89c\x03\x12Qg\x8eY7#\x06k\x85x\xd0M\x12\xb6\xed\xda\
+\xc5\xf2S_\xcb\xa1\xdd\xdbP\xb5\n\xff\xf5\x8f\xfe\x80\x93\x8f\x87;o}\x80\xef\
+}\xfbv>\xf5\xb1\xdfg\xed\xb2\x00!s\x7f\xaa\xb0U\xe5\xbd\xb9\x0e\x81+!p\xf2\
+\xa2F\x032\xc3\x11Y\xff\xd6\xf6\xfb\\e\x0c\xf2\xe1DQ\xd2Y|0\xf6\xde\x99\xa1\
+\xfb6\xaf.\xefX\xba\xb1\xccQ~\x16\xff-H\x00(\xc2>\x19\x90d\x10\xa7\xa0E@\xcb\
+\xf8Dz\xd02\x12\r:U\xb4\x85\xa4\x9b\x89\xfe\x12\xbcGV\xb4\xd3\xef\xf4\xab\
+\x84A\x9a&\t\x0e\xe7\x9d\x10\xf2\xbe\xb7m\xc4w\xaa\xbc\xb0\xa7I32T\x03\xcd\
+\xb21\xc99\xebN\xe0\x86\xcb\xceg\xdd\xaa\xbcI\xe5\xb1\xcf\x80.9\xa6h\xda\xd1\
+\x0c\x8e_\xc5\xa3\xca\xbb\xaf=\x99\xb7\xbf\xe5d\x1as03\r\'\x1e\x0fd\x10\xc8\
+\x1e5\xdaDq\x17\xe5\x8d2\xb6d\x9cy\x0e\x9bcH!\x1ai\x9a\xf6\xc5b\xf9\xf2\xe5\
+\\y\xe5\x95\x84a\xd8O\xdf\x15B\xb0b\xc5\n\xae\xbb\xee\xba~\x17_\x80w\xbd\xeb\
+]x\x9eG\x92\xd8n\xec\x9b6m\x9ag\xb5\xfc \x9c\xf9\xbd0d\x1e\xce\x1dr\x8fHr?\
+\x9fD\xa2\xc8t\x82\'\x1dR\x03o\xb9t\x9c/?\xbc\x1ao\xe7a\xf6\x1eh \xc8\x10\
+\xa6\x8a!\x01\xed\x90\x89l \x1a\xc5\x00\xd0\x0f\xfc\xe6\xfdO\xe2\x0c\xa4\x8b\
+\x17V\x98=\xb4\x93\xf1\xb0\xca\xca\xfa([\x0f\xed\xc6\xafyxY\x87\xaf\xfc\xf3\
+\x97\xf8\xe05\xbf\x83\x14>Y\xd4a,\xf0\xf9\xb5\xf7\xbd\x91z\x1d\xbc\xbc\x05\
+\xc8d\x0c\xf7?\xde\xe4\xce\xfb\x1f\xe3\xd1\xe7vrh\xbaM\'\x83Dx\x18\xc7!s\x96\
+\xc0\x88\x02\x9d\xd1lE \x1a\x08_b\x92\x14g\xe98i\xaf\x8dN\x0f#\xb1\xeb\x8bw\
+\x80\xc9\xc3\x87\x98\x18]G{f?\xbf|\xcb\xfb\xb9\xe1z\xb8\xeb\xab\xf0\xdb\xbf\
+\xfe\x97\\\xb2~\r\x9f\xfd\xdb?e\xcdr{\xd8|\xf8\xa3\xd7\xd5\xd4<I0R\x19\xca:\
+\x1b\xae\x1d\xb7\xedb\xfa\xe21\xd4\xf5\xc3\x0e\xbaG\xf7\xca\x0f\xaf\xf8a$\
+\xf3\x0c\x94#bVEa\xa506h\xbf\xd02\x90`\x1dIE \xb0p\x8b-L\xa4\xe8o|\xf4Z\x0f1\
+\x94k\\\xbc\xc5\x97\xc8\x1e\xeb\xb5\xdb\xb8\xd5%T\x1c\x9b\xa6\xab\x05t\x12\
+\x8d\x08\\P\xf9\x02\\\xca\xb3\x8d\xab\x92\x18D\x86\x90}\xd9\xcc\xc9\x98\xd7z\
+$\x7f\xa6t\xa7\x8d\xacJ\xfc\xb8\x87\xf6|.\x7f\xdd\xf1\xbc\xe1\xa2\xe3yf\x07\
+\xec\xda\x1b\x11\x84\x925\xab\\N\\\x055 \x8d2\xdbF\xd1\x91\xfd\xc4\x8f\xc2U&\
+\x99_\x1b2\xb8\xcdEj\xbb}\x83\x99p\x86?\x82A\xe6\\\xfff\x14\xd7\'\x87\xd6 a\
+\xd0\xba\xc58y&\xde\xc2\xbb\xbapd:Z\x81f~\x1dF\x808b\x0e8\xef\xba\x17\x1e\
+\xb1\xa8\xc49\xf2\xd5\xa28s>\xf3R\xb1_\xf2\x9a^\xb9\xd4\xfc*\x19\x92\xe9\xc6\
+\x1cc#\xa3\xf8\x12F\x96\xc0\x84\x0f5\x07\x94C\xeeE\xc8\xf0\xa1\xff|km\xc3\
+\xbb\xeeO\xc9$Dk\xdd\x1f\xf8\x8d1\x84a\xd8\xcf\xa4*\xdcQ\xbd^\x8fJ\xa52\xcf=\
+U\xb4\x8a/\xb6+D\xa8p\x87\xfd \x9c\xc1\x97]!\xfa\xce\x94\xa1@\xaf!\xbfK\x12)\
+\x15^\xfe\xf0(ag\xfb\xff\xed\x7f\xb9\x9a\x9f\xff\xb5\xbf\xc6\x89\xa7\xf1\xc2\
+\x95$*\x1f\x90\xfc:F\xd6\xac\xf5\x92\xab\x1d\xaek\x9d\xff\x99-.\xc4\r\xb1\
+\xc1\xd1\x84)\r#\xe3kiE\r\xdaS{Y\xaeR\xaaZ\xf3\xda\xb3N\xe4\x9a\xcb.D\t\x88\
+\x90L5\x04\xcb\x03XS\x87f\x04;\xa7\x0c\x9f\xfa\xe2\xdd\xfc\xcb\x83\xdb8\x18\
+\xd7\xd1\xd5\tRV\x80;\x87tz\xb6H\xcd\x08\x8c\n\xc1\xad\xda\xfe\x03\x9d)p{T\
+\xebuZ8\xc8\xa0\x06\x87\x0e2>\xae\xf0#\x18ua\xcf\xbe\x84\n\x19\xaf]\xbf\x8a[\
+>\xf4V\xf6\xcc\xc2o|\xe8\xeb\xec{\xf1)\xceXU\xe1O\x7f\xe7\x9d\x1c?BQ\x9a\xd8\
+\xd7a?\xccoz!\x08H\xb28F\xb9n.\xa6\x8a4\x8d\x10RZ\xf33\x89\xecS*\x1c\xe2N\
+\x84\xf4B\x1cWa\x04\xc4Q\x82\xef\x17\xfd\x8c5E\x91\xa7@bDQ\x01b\xc8R\x8d4>N\
+\xbe\xa9N:H_\xe7\xb9\xae\xbe\xfd\xaf\x18Q\xf2\xf9\x81\xa1\rd\x18\xed#\xa5o\
+\x07\xc7(\xca\xd7D\x90\xb6w\x8d\xeb\x83\x90\xa4\xda\xea\xbc\x91\x83/\x8c\x04\
+2-Q\xd2G\t\x10$@\xdb\xa64k\x03~\xdd~\xc6\x00FS\x91\x1aL\x84\x10>\x87\x1b\xe0\
+\x8d@&\xc111t\x9bv0w\xaa\x90\x04`\\|7@d\xcd\xbe\x17\xd5\xaeL\x9fb\x9d\x93\
+\x82$\xcd\x90\xaeK\x96fxA\x00:\xc2q\x14\x98\x94\x9a\xf4\xe8\x00\xe7\x9e\x08\
+\xaf=\xd1G\x02]m\xad\x0e\t\x04\xbe\xc2MZ\xb9\xc8*\xa0J\x17\x9b\xa2\x19\xa8<\
+\xf9\xa1\x97\xe0\xf9\xae\xed(-@\xe9\x8c\xb4=\x83Su\xc08\xa4j\x846V>\xed:-\
+\xb6\xb1\x02\x06HzdY\x84\xaa\xd6\xc1\xd8\xe8b\xa2\xc1U\x1aE/Os\xaf\xf7S\xdc\
+\x07\x9a\xa8I\xba\x1d\xdc\xc0\xa7\xdbj\x13\xd6kt\xa3\x98\xc0\x0f\xf2\xdb\x98\
+b\xd2\x08\xe5b\x075-\xd0\xaaN\x9a\x9f\xbf\xf8^\x1a1\xdf\xe2\x03\x885\xe8\xd4\
+P\xf5\x04I\xd4\xc1d=*\x95\xb0\xff@\x14\xcb\x1d\x0c\xc7(\x0b1M\x93\x08L\x86\
+\x9b\x17\x96%I\x82\x11\x12\xd7q\xf3\x10\xa9\x9e\xd7\x19v!\xb3\xb3\xb3\xf8\
+\xbe\x7f\xc4\x9a\x14I\x92`\x8c9\xa2`\xad\x18\xf8\x8aN\xb1\xc0\xbcn\xb1\xc3\
+\xaf\x03\xfd\xb50\xb4\xb6\xdf\x88\x85\x03_\x92$(\xa5\xe6]\xa3\x9d\x95Kt\xa6p\
+\x95\xc3H \x07\x13\x07\x01\xa13\x88\x8fk\xed \xb4\x03\x8eO\x96j\x1c\x07\xa4\
+\x87m\x84\xa0\xcd\xbc\xd5@\x87\xaf\xa9H\xa5-\xdcA\xc3\xb1\x8a\xe2Z2\xa3\xc9\
+\xb2\x0c)%J\xda\xe7A\x9b\xfc\xef\xd8tL?\x0c\xe8t:\xf6>I\x81\x17\xf8t\xa3\x9e\
+My\xca\x8f9|\x0f\x0bq(\xc4\xa0\xf8wQp8\x1c\xdb\x18\xbeW/\xf5\xfa\xcb1\xd4\
+\x8d\xf7%v\x98\xf7\xf7A\x86\x96\xc2\xe6\xd0/\x95\xf0\xe1\xff\xe9\x97\xf8\xad\
+\xff\xed\xff$\x8ab\xa6[\x93\x10,\x03\xe1@7\x86J\x15\x02\x1f\x99e\xe8,\xb2\
+\x8f\xaa\xab\xd0:#\xebNC\x16\xe3T=T\xafAkv?+j\x927]\xfeZ\xae\xb9\xfc\xf5\xac\
+?\xcd%P\xb0\xb4\x06ss\xb6u\xc0\x92\xe5UR \xed%l\xde\xbc\x8d_\xfe\xc8_\x12\
+\x1c\xff:vL\x03cc\xa0CH\xba\xa0%\xb5\xd11\xa2v\x03)\x15:\x8e\xecB\xe8\x95\
+\x10g\xe5*T\xf7 \xadC\x93P_F\xfc\xc8#\xc8\xe3\xc6\x19\xf7%\xe3\x1e\xd0M\xe9\
+\xc4\x82?\xf8\x93\x8fr\xd2\x99\x8a/\x7f}\x86\xff\xfe\xf7\x9f\xc5\xf5\x14c~\
+\xc6_\xfc\xd1\xaf0\xe6\xd9\x14\x81y\x06\xdc0C\xb7S\xf9A^\x81o\x1f"\xd7)\xbeH\
+\xa9\x1d\x91\xd3\x04<\x1fU\xab\x91\x02\x93-\xfb\xe5\x1f\xa9\xd9\x06\x96\x18I\
+\xbe\xbb\x9d)\x0f\x05\x90\x9b3m\xc6\x97T\xf1l\x12\x1b\x08p\xc2\x1a\x88\x16\
+\xc4-p\x14\x08\x7f0\x81\x1c\xb6\x12\xd0$I\x84\xef\xe5\x02\xd3\xf7\x97:dIL\
+\xb7\x97\xa2*\x0e\xc6\xb5]\xad5\x83\xe2>7\x7f\x8f\x196\xde\xe3\xa5\n_\x83\
+\xf0C\x10\x92L84{\t\x9erp\x85\x8b\x14>q\'#\xf1!\x1c\x814\x82T\x16\x0f}\x9cg\
+\x95\x15\xcbf\x81\xd1\x8aT\xdb\xf9z\x0cxB"\xa4\x07&\xb4\xe2\x9a\x0f\xba\x9e\
+\x17\x00\td\x11\xa4\x1a\xed\x88\xc2\x13F\x82\x15\x9f"U3?S~\x86\x0c\xd2\x1ei\
+\x0cj\xa4\xcaLl\r\xa0\x04k!\x05\x81K\x94\xdf\xef\x14\x08\xa5\x8b\xe3\x07V\
+\xec\xbc\x90H\x15\xb7\xd3\xf6\x04p\x91\x83\xf5\x0b\\\x0f\xe5\xd9\x1b4\xd7\
+\xb1\x19\xea\xc6\x81\x04\x89\xc2\xc3w\x02T>YK\xf3{\x18wc\xea\xa1\x87\xeb{\
+\x90D\x84\xb5\x100\x04~\x80\x06:\xbd\x84 p\x91\x9e\x83ActL\xac\x05\xda\xc9\
+\x1b\x16\x88A=M\x96\xd9\xd6\xf5\x89\x06/\x84^\x04\xa1\x0f\xaeg\'\x1d\xa1_\
+\x03\xe32\xbd\x7f\x0f\xe3\xabV\x02\x0cM\x1e\xe7G\xb5\xda\xed\x16\xa3\xd5 \
+\x7fc\x06\xa3m\xaa\xa8F\xd0Kl\xd7Z\x18\xccX\x8d1\xa4i\xdaO-\xedv\xbb,Y\xb2\
+\x04\xb0\xc1\xe74M\x19\x1d\x1d\xb5k\xea\x08\xd1\x1f\xe4\x8aA4\xcb\xb2\xbe\
+\x108\x8e\xd3\x17\x93\xe1\x01r\xe1\xc2IJ\xa9\xfe~\x85\xf0t:\x1d\xc20\xec\xa7\
+\xa4\x16\x14\xb5\x10\xfdcj\x85P\x0e\x15o H\xb6\x9d\xf9`&np\x90\xf9\xe0\xee\
+\xc8\x81 Ie\xbf\xfd\xc6\x98\xfe\xfb\x17B\xf4{W)5tO\x872\x9a\x86So\xb3,\xc3u\
+\xec\xb9\xd2,%M\xd3~FUq\xdc\xa2H\xb0\xd7\xeb!\x84\xa0Z\xad\xda\xe0\xf9\xb1\
+\x0f\x80\xfex)\x04\x12\xa8\x03g\xac\x0c\xf8\x9d\x0f\xfe&\x7f\xfe\xf1\xcf\xe1\
+\x8c-\xa5\xa9+\xa8\xca\x12\xe6\x1a=h\xb4@\x1a\x1cW\xe3\x12\x93E\x1dt\xd2\xc1\
+WP\x0f2\x021\xc9\n\xdf\xe5\x92\x8d\xaf\xe3\xda\xab\xde\xc3\x19y\x83\xc2\xc2\
+\xb9b\x809`\xc7t\x93\xfb\x1ey\x8e\x1b\xdf\xfc:\xd2$a4\x10\\p\xe1\xe9\x9cp\
+\xf6F\xb66B\xc2\x15\x19\x91\x8a\xd0BC\xd6C\xc4\x11\xf1t\x86\xebz6\x07\xc0M\
+\xc0\x15@\x87\xb4\x95\x90&\n\x82eH\xcf\xc3;\xe3\x04*\xf1\x0c\xdd\xd9\x83LMu\
+\x98\x18\xaf\xb0\xfcd\xf8\xc6}\xf0\x7f\xfc\xd6\x17\xd9\xb9s\'\xab\x96\x8e\
+\xd3\x9a\xda\xcbG~\xed\xfd\x8c)\x08\xc5\xcb\x88\xc7\x10\xc5\xecH\x08qDq\x8eF\
+\x10g\t\xc6\xf3\xe8\x19\xe8d\xf9\xa4\xbdf\x07\x95\x83\xc0\xf3[\xa1\xd9\x88\
+\x99\x9a\x9af\xb6\xd5%5\x0e\x95\x91\xa5\x8c,\xad0V\x83\x0b\xd7U\xe9\x01SMHcX\
+\xb9\xd4\x0e"\xb3\xb33\xac\\R\xb7wP\xa4\x03\x97\x89(R\x85\xad\xf5\xe7\xfb.i\
+\x14a4\xb8A\x05\x90$)\xe8\x9ams\xdd\x05\xe6R\xd87\x05{\xf6w9<=E\xb7\xd7\xb6\
+\xe3\xa4r\x08\xc3\n\xe7\xad_\xc5I\x13\x92\x98*I\xde\x93D)\xc8\x02;\x83o\xa5\
+\x82j`\x85\xbf\x8b\xad\x8f\xd99\t\xb2:Nj\\\xa4\xc9\x1b\xb3\x98\x04L\nF\x93jA\
++sh\xe5\xcfY\x87\x9a\x1d$E\xbe\x12%\xfd\xe4q\xd28%P>x.\xb1pi0\x10:\xbb@\x98\
+\x15\x05\x034\x9a\xb0\xb6\x0e\x15\xb7\x0e\xca\xc1\xf1}Z\x19\xa4j\xe0\xd4K\
+\xf2\xeb\x8c\x13\xd0\xae\rO\x8d\x005o\x04\xa5$(E\x96Ym\x96\xa48\x08T\x91\x15\
+\xa7\xb1\xe9\xde\xd2#E\xe2\xd4\xe0`\x1b|\xa7\xb0#=k\xe1\x08;\xe8w\x13\xfb\
+\x9c\x8f\x84^.\x04\x12\xa3S\x84Q\x90e\xc4Y\nn\x88\n\\\xba@\xa4\xa1\x9bHB?\
+\x00\x05\x87Z\xb0\xff \xec\xdev\x98\x03\xfb\xf7a\xe2\x98\xa5\xe3\xe3\xacY\
+\xb3\x86\x89\x89\x80SO\xc9\xd7\xde\x01H\xa13\x07\x13K\x00\xe33>q\x9c\xb5d\
+\x84\xf5\x89\x16i\xfc\xc5\\C\x02#\xd5\x1a\x90\x12\xf5z\xf8\xaemOd/_\xcc\x9bQ\
+\x03\xfdY\xf5\xf0`?\xec\x12)\x1a\xf3eY\xc6\xec\xecl_X\n\xd7\x89\x10\xa2\xbfN\
+Eq\xccb\x7f!D\x7f\x91\xa4\xc2\xdf\xef\xba.Zk:\x9d\x0e\xb5Z\r\xa5\x14\xcdf\
+\x93Z\xad6o\xfd\x8b,\xcb\x10B \xa5\xec\xcf\xc2a`\xe9\x14B4\xbc\xce\x06\xd0?_\
+\xa7\xd3!MS*\x95\n\x8e\xe3\x1cQ`\x97\xa6i\x7f\xd5\xbf\xe2<\xc5\xa0\xaf\x94"I\
+\x92y\xd5\xe1\x85\xb0\x14\xe2\x91fv\xffJX\xc1Q\xf3\x87\xe4(\xb6\x8bF\x15\xe3\
+GQY\xae\xb5F\x1b\x8d:\xc6\xb5`?\x96\x80\xb8\xf9T\xe5\xc41\xa8^8\xc2\xf6m\x1b\
+\xf9\x8bO\xdd\x8a\xb7t-\x8dV\x03/\xa81\xb2\xb4B\xb71\x85L:\xd4=\x8d\x17\xf4\
+\xa8\x8d9\x9cu\xfa\xa9\\t\xeeI\\\x7f\xf9\xc9\xac\xa8\x0e\xbe\xb8\x02;\x00\
+\xced\xb0ek\xc4\x13\x9b\x9f\xe6\x9eo~\x83\xf6\xdc\x0c\x87\xf7o\xa7\xd1\xbe\
+\x85\x9f{\xdb\xe5\xc4\xd8\xca\xe5\x0f}\xf8f~\xe6\x03\x9f\xc2\x8cT\xd1\xbd.8\
+\n%$c\xf5\x1a\xddF\x9b,\x15\xc8\xc0\x03\xcf\xb3.\x8b,\x06#P\xaeC\xa8\xa0\xd5\
+\x9a\xa2Zw\x99\xde\xb3\x8d\xcb.<\x99\xb1\xa5\x15\x9a\t|\xec\xf6\xbd|\xfc\xd6\
+{\xd1q\x840\x92\xb4y\x88w]\xb5\x81w\xbfy\r5\xec,\xb6\xdd\xeaP\xabU\x8e~sr\
+\x86\x97\x9a\x1c~\xf0\xa2(B\xf9\x1e\xda\x1d\xb1\xef%\xf7\xd5\xecl\xc2]\xf7n\
+\xe5\xae{\x1e\xe4\x85\xed{\xf0\xfc:\xda8\xa4Z\x92\x1aE\x82O*<\x8c\xf2\xf1\
+\xe9\x11vvs\xf2\x8a\x11\xde\xfe\xb6\x1b\xb9r\xd3J&5T%T\x96\x1cG\x8f6\x0e\xc6\
+V\x8b*\x06n5\x83\xb5J\x8au\x1f}\xbb>{\x94A7\x05\xe9CS\xc3\x03\x8f5\xb8\xef{\
+\x9b\xd9up\x86\x17w\x1fdrn\x8e,\x9f]i\x93\xa2\x93\x84\xd0\xf10I\x8fz\xc5\xe3\
+\x9c3O\xe3\xcd\x9b6r\xe9\xc5\x13\xd4\x8b\xcf\xd2\xc0G\xff\xe2\xf3\xb4:1\xedD\
+\x10\xbb\xa3d\xe12\xf67\r\xb1\x08mk\x7f\x89M\x89"\xb5E\x87\xc6\xba\xc7\xda\
+\x89\xe2\x7f\xfd\xe8=T\x98C&=\x94V8j)q\n\xc2\x83U\xe3\x0e\xbf\xf7\x8b\x97S\
+\xf7B \xa1\xd3\xd5|\xf1\x1b\x0f\xf2\xaf\x0f=\xc5\x9c\xa9\x91\xe0P\x95\x19\
+\x95\xd0#N\x12\xa4\x82\xc0U\xbc\xf3\xea\x8dl\\7A\xcdQ\xcc5;\xfc\xf5gn\xe5\
+\x80\x1e\xe7\xb9}3\xac]{<\xddV\x13\x1du\xf0}\x9fN\x02K\xaa.\xabj\x9a\x0f\xff\
+\xca[\xf1"\x89\xe7B\x96&x\xa1;\x88i\xe0\x0e\xd2\xe94\xe08D\x06\x9e\xdc\x0e\
+\x7f\xf9\xc9\xcf\xd3\xc2\x10\x8cT0\xa2\x87\xa7Bf\xf6E\x8c\x8f\xd4\x19\t\xbb,\
+\x1bU|\xe8\x96\x1b\x88b\x18\x95\x02\xe9\x87\x98n\x0b\x11V\xf0\x1d\x9b\x11X,\
+\xe5\x9cH\xd8\x9f\xc2]w=\xc7\xd7\xeey\x88\xdd{\xda C<il\x96d\x96b\x92\xd8\
+\x0e6i\x8c \xe3\xed\xd7]\xc5\xd5Wl\xe0\xdc\xd3`l\xdc\xae9\x93\xb5\x9bT\xc6\
+\xaay\x1c\xa5(\x06\x9d\x1f\xff\x19\xc4K\xc0\x0f\x02\x8c\xd6\xc4Q\x82r=\x84\
+\x04\xd7\xb1ox\xef\xde\xbdl\xd9\xb2\x85\x1d;v077\x07XwI\xa5R!\x0cC6l\xd8\xc0\
+\t\'\x9c\x80\x94\x12\xc7q\xfa\x1db\x0b\xb7\x94R\xb6G^\x1c\xc7\xcc\xcd\xcd\
+\xb1l\xd9\xb2\xfe <\xdc\x93\xa9X\xa7"\x8a\xa2\xbe\x1b\xcbu]j\xb5Z\xbfSm\xd1\
+\xda\xa3\xd5j\xa1\x94\xe2\x91G\x1e\xe1\xe0\xc1\x83\xec\xdb\xb7\xaf/\x02g\x9f\
+}6\x17^xa\xffx\xc5\xf1\x8b\x86\x85A\x10\xf4-\x86\xa2\xf5\xc7B\xd24\xed\x8b\
+\xc2p\xf1^\xb1\xdf\xb0\xf5Qt\xd2}\xf2\xc9\'\xd9\xbcy3{\xf7\xee\xc5u]\xc6\xc6\
+\xc6hw;\x9cx\xe2\x89\x9cy\xe6\x99\xac^\xbd\x9a0\x0c\xfb\xf7I\n9\xcf*+V\x0b\
+\x04\xfab\xa2~\xc2\x0bZ\xfd ~,\x01\x11\x80i\xf7\x18\xa9\x06\x88\n\xfc\xda\
+\xfb\xcfF\xcb\x1e_\xbc\xf3>L\xa0\xe9\xc4\rh\'xY\x8be5\x87\xf3\xcf=\x857m\xbc\
+\x90\xd7\x9d{\x1c\xcb*v\x06\xe9\xa6\x905#\xbc\xaa\x8f\x94\xf0\xdc\x01\xf8\
+\x97\xaf=\xcc}\x8f=\xcbT\xc7p`\xba\x85R!\xa1Wa\xf9I\xab\xf8\x87/=\xc0uW]N\
+\xa8!\xac\xc2\t+a\xfd\xba\xb5<\xf6\xfcv\xeaKW\xd0\xeaDdi\x82\t@\x07\x86\xc4h\
+\xdb\xae9\x95\xd0\x8e\xc0s\xa8\x8fW\xf0z\xb3L\x1f\xdc\xc5\xd8X\r\x15\xb5\xf8\
+\xf4\xc7\xfe\x98+\xce\xb0\xb3\xc4\x9e\x82\xe7\x0fvh\xb5\x04+O8\x91\xc6\xce\'\
+\xd8\xf8\xda\xd3\xf9\xc3\x0f^I4\xd5!\x18\xb7\x0fT\xad\xfa\xf2\xe2\x01\xf6\
+\xe1\\\xe8\x9b\xed\xfbl\x114S\xeb\xed{v\x17\xfc\x8f\xcf\x7f\x95\xaf\x7f\xfb{\
+D\xda\xa3\xbad9\xa2v\x1c\x87\x1bm\x84\xe3#\\\x1f\x1c\x9f\x04\x87HKH\r\xedD1\
+\x1e\x1c\xcf\xde\xb6\xe6\x8f\xff\xe6\x8b\xfc\xe5\xa7\x0c\xef|\xfb\xe5\xbc\
+\xf7=\xe7\xb0\xca\x83\x99,fBY\x17\x88[\xf8\xd9\x87\xa2\xab\x06\x87\xc4@\xb7g\
+\x07\xc3X\xc1l\x06\xdf}\xb4\xc9]\x0f~\x9f{\x1e|\x8c\x96\xf6iD\x86(\x11\xe0/\
+\xa5Z\x1fAy\xb6\xd9Z\xdci\x92y.\xc6\xd1\xb4\x84`\xff\xd3s\xdc\xfb\xdc\x979\
+\xfd_W\xf3\x8ek\xaf\xe0\x9a7\xd8\xf8\xc8}\x9b\xf7\xb0\xe7\xd0,\x84\xe3\xf8c>\
+\xa9;\xc3\xa1)\xdbbD\x06^?q\xd9\xb6kN@\xb8d\xc6Z \xf7|o+Jvl\xb2\x83\tQ\xa2G\
+\x92h\x82Pp\xf2r\xc9t\x17\xfc\x00\x94p\xd1\x1e\xec\x9e\x8ex\xe4\x99\xbd\xb4\
+\xbd\x15d\xd2\xa35s\x98\x8a\xafH\x92\x08%\rK\xeb>\xaf\xdfp!\xa2\xe2`p\xf0G\
+\x02\xf0\xea\xdc\xf1\x95o1kjl=\xd0cv\xae\x85\xeb8(\xc7\xa1\xd9\x8dY>\xea3\
+\xc6\x0c?\xfb\xde\xb7r\xea\x92\n\x02\xa8\xe7s\x01\x85 \x8d;\xa0\xf2gA[\x1f\
+\xb6F\x92\nx\xe8\xa9\xed|\xfb\xfb\xdb\x98\xc9@TC\x122<\xb7J\xe7P\x8f\xf1\x91\
+\nNk+o{\xf3\x05\xfd\xc9S\xa2\r\xbe\x92\x08\xdbp\x89(\x83XY\x0bjO\x17>\xf3\
+\xe5\xa7\xf9\xec\xad\xff\xca\xdcL\x8b\x91\xb5\xa7\xd1\xf5}\xb2T\xa0{=\xc8R\
+\xa4\x11xN\x85 T\xb8\x8e \xe9\xb6\xf8\x87{\xee\xe7\x1f\xef\xb8\x9d\x8b\xce9\
+\x91\xdf\xf8\x85w\xb3\xe1\xcc:aX\xc7\xa4i>\xdb.li\xdd\x17\x91\x828\xea\xe2\
+\x15kF\xa46\xd5\xd3 \xd9\xb9g/\x8f?\xfe8\xb7}\xe9\x8b\xf3b\x19\xc5\x8c\xbb\
+\xd7\xeb\x91\xa6)+W\xae\xe4k_\xfb\x1a\xeb\xd6\xad\xe3\xaa\xab\xaeb\xfd\xfa\
+\xf5T*\x15z\xbd\x1e\x93\x93\x93|\xe4#\x1f\xe9[-Zkj\xb5\x1a\x1f\xfc\xe0\x079\
+\xe3\x8c3\xe6\xb9\x81\x8a\x81\xb4\xddn\xf3w\x7f\xf7wl\xde\xbc\x998\x8e\x91R2\
+99\xc9\xf5\xd7_\xcf\xaf\xfe\xea\xaf\x02\xb6\xb6\xe3\xbe\xfb\xee\xe3\xce;\xef\
+dnn\x0e\xad5\xcdf\xb3\x9fi477\xc7\xd9g\x9f\xdd\xb7.*\x95\n\xedv\x9bv\xbb\xcd\
+G>\xf2\x91~\xdb\x8f\xc2\xaa\xb9\xf9\xe6\x9b\xb9\xec\xb2\xcb\xfa\x96\x91\xeb\
+\xba\xfd\xe6\x86\x85\x90\x15\xdf\xed"\xbe\x01\xd6\x82\xb9\xff\xfe\xfby\xfc\
+\xf1\xc7y\xec\xb1\xc7h4\x1a\x8c\x8e\x8eR\xadV\x89\xe3\x98V\xab\x85t\x14\x9b7\
+o\xe6\xf6\xdbo\xa7^\xaf\xb3q\xe3F\xae\xbb\xee:<\xd7\xa3\xd5nQ\xa9T\xe8t:\xc4\
+qL\xadV\x9bg\xdd\x14\xae\xafc\xc9\x8f\x99C\x90\xe2\xab\x0e \xa8H\x9f\x14\xf8\
+\xe5\xf7\xbd\x8e\x17_|\x82G\x9f\xde\xca\xca\xb1q^\xf7\xba\xf3\xb9\xe2\x8d\
+\xaf\xe7\xc2\xb3j\x84\xd8\xc9Y\x11\xcc\xecb\xd7\xb1\xee)\x9fo<\xb8\x83;\xeey\
+\x88\'\xb7\x1d\xa4\xa1+\xcc$>S3\x11\xee\xd8\x1a\\\xd7ej\xf60\xb3\xa2\xc7\xea\
+\xea\x08\xb7\xdd\xf9"?\xfb\xb6Sht\xc1\x0f\xe1\xbf\xfd\xe6\x95\xfc\xe7_\xfd8\
+\xae\x11D\xc2\'u\x03\xa6\xba\x1dH\x12\x82\xf1\x11z\xad\x0e\x98\x10FF!j\xd1\
+\xdc\xf6\x1cU\xaf\xcd)+\xea\x9cx\xf2\t\xfc\xf6o]E\x08\xfc\xd3mO\xf1\xce\x8dg\
+\x93\x84\xf0\xf0\x13[\xa0V\xe3\xc0\xf6\xad,wS~\xf1\xe6\xeb\t\x81\xd1%\x1e$\
+\xad\xbc\xf1\xe1\xc2\xaf\xdbK\xdc\xa5!\xbf\xe6\xb0E\xd2\x03\x12\x07\xfe\xeaS\
+Or\xebW\xee&\x12\x1e\xd5\x15g\xd2\x9d\xed\xb2{\xa6\x8b\x1f\x06\x8c\xae8\x818\
+I\xe8$\x11q\x12\x81\xc8\xec\x9b\xaeU\xf1\xe4\x18\x9d\x999\x1a3-\xc6\x97\x9eD\
+W7\xf9\xfb/\xde\xc9do\x8a_\xff\xc0\xe5,Wc\xc4\xd8\xf5\xd4\x87\x1b/\xf4\x85D\
+\xe4\x1d\x08B\x97\x0e\xb0s\x1an\xbbg\x0b\xb7\xdd\xf5\x10\xcf\xed\x9d!V#h\xaf\
+\x8e\tk0j?\xb5\xb6\xc6.\xf3g\x14\xb8\x1e\x91\xab\xe8\xf6\xda6\x12\xecy\xd0nq\
+\xf8\x89\x1dL\xcd~\x99\xefoY\xc7\x07?p\x1e:\x18\xfb\xff\xd9{\xf3pI\xab\xfa\
+\xde\xf7\xb3\xd6;\xd7\xb8k\x0f\xbdwOt\xd3-\xf3\xd0\xcc  \x83\x08\x08\x8a\x03\
+\x12G\x8c\xc6\xf1\xe4\x98\xc4\'9\xc6L7\x9eDM\x8c1\xc6LG\x13\xa3GO\x1cbPDeP@@\
+\xe6\xa1\xa1\x81nh\xa0i\x9a\xa6\xe7\xee=V\xed\xaaz\xe7\xb5\xee\x1f\xeb}\xabw\
+\xa3\x89\xc4\xf6\x06\xee}\xeez\x9ez\xf6\xd0\xd5\xbb\xaa\xdea\xfd\xd6\xfa}\'d\
+\xab\xc1T\x1f\xf4T\n\x95\x0c\xec\x1a\xc1\xd0\x10ag\xceTP\x0cPk\xa9\xach\xa3H\
+\xa2\xdc\xc6\xf2\x86\xc8d\x8d\xdc\x02I\x80\xa5\xebd"A\xba\n\xe9[T\x83\xfd\
+\xb1\xc9q\x0e\xbd\xd4b__\x12\xe2\x91\xa5\x82\xe6\xe8* \x83$"KC:IB\'\x16\xf42\
+\xd3\xcf\x96\x0e\\t\xe9\xeb\xf8\xeb\x7f\xbd\x95eG\xafa*\x14\xf4,\x9b\xe1\xb1\
+\xc5\xcc\xcdw\x11M\x9b\x8e\x8c\xe9\xce\xceq\xe7\xba\x9d,\x7f\xe5R\xda\xed\
+\x98\xa1\x86G\x14\xf6hT\x0c>e\xecZJ<\xcb\x19x{\xddv\xff\xa3d\xc10^0D;S\x90\
+\xc6\xe8\xca0LX\xe42fi+\xe6\xf27\xbc\x11\x00\xcf5\x19\xefQ\xd8\xc3\xf7\xabt\
+\xba!v\xdd\xa1\x0b|\xef\xce=|\xee+\xdf\xe1\xe9gv\xe3\xad<\x86\xc6Qct\xf6\xcd\
+\x18g?W\x98`\n!PZ\x11\xeb\x9c(7\xac\xb1\xdaH\x934\xeaPk\xd4ylO\x9b?\xfd__\
+\xe2\xad\xaf}%\x97\xbfz\r\r\xc7.tH\x0b\xa8\x80\xcf\xcboq=\x8f,M\xc9\x15\xb8^\
+@\xa6r\xd6\xae\xbd\x8f\x1bo\xbe\x85\x8d\x1b\x1ec\xa8iDiQ\x14\x11\xc7\xf1`\
+\x87Q\xa9Tp\x1c\x87\xd9\xd9Y\xb2,c\xc3\x86\r<\xf6\xd8c\xacY\xb3\x86\xf7\xbf\
+\xff\xfd\x8c\x8d\x8d\xf1\xd8c\x8f\x1d\xa0\xa0\xeet:DQ\x84\xe7y?\x95\xd5]\x16\
+\x93F\xa3A\x9a\xa6\xf4\xfb}\xa4\x94x\x9e\xc7\xf8\xf8\xb8Y\xd0$\t\xd3\xd3\xd3\
+\xdcp\xc3\r\xdcp\xc3\rT*\x15\xba\xdd.\xd5j\x95z\xbd>\xa0\xb5\x8e\x8c\x8c\x0c\
+\xac\xcaK\xe08\x08\x02\xaa\xd5\xea\x00\xac\xb6,kP`\x86\x87\x87\x07\x00~\x89u\
+(\xa5\x06\xcc\xa7\x85\xdf\x97\xf7\xf8\xfd\xf7\xdf\xcf\x03\x0f<\xc0c\x8f=6x\
+\xff\x8dF\x83$I\x06-\xbbF\xa3\x01\xd2\xb4\xa6\x92$\xa1\xd3\xe9p\xd5UW\xb1n\
+\xdd:.\xbb\xec2\xce>\xeb\xec\xc1{{>\x1e\xf2B\xc2\x9e\xfe+\xc6\xc1\x15\x10\
+\xa1\xc1\x97\xa0\xe7\x89\xc2\x0e\x95\xca\x18\xd2\x81\x7f\xfa\xe4\xfb\xb8\xe3\
+\x91\xad\x9cq\xc2\xca\xc1S5f\xc2t0\xad\xaa\xe7\xda)\x8f=\xb6\x95\xeb\xae\x7f\
+\x80-\xcf\xcd\xb2gj\x0e\xbc&\xb2\xb2\x88\x9e\xae\x90R\x85\xf1:i7$u\x03*\x8b\
+\x86\x99\xdf\xb1\x89^ \xf9\xc2\xff\xf9\x1e\'\x1e\xf7?8\xfee\xe6\xbe=c)\x9c{\
+\xccrn|p\x1bye\t\xa2\xd92v\xae\xd5\x80H8\xa0"\x90\x8a\x96\x05\xe4!\xd2\xcb9}\
+\xcd*.8\xffL.>\x7f1;;\xf0\xb1?\xbf\x8apj\x1b\xefy\xfd\xb1l\xef`\xfa\xf1\xbd.\
++\x97\xd4\xf8\xe4\x87\xde\xcbI\xabl\xdcTC\xde\x05\xc7\xa6?7G\xa55\xfa\x82\
+\x0eS\x9e\xe7\x83mr\xd9+\x05x\xf8\xb1\x9d\xfc\xfe?\xfc\x90m\xf3>\xb3Y\x83\
+\x08\x07=\xab\xc8e\r\xab1\x84\xb6\x04\xfbff\xc1\x16\xd8\x0exU\x87\x0cM\x9e\
+\xf7\xa0\xd7!I\x05\x96=Bmb9\xfbf\xf6\x81T\x8c\x8e\xae\xe0\xfa\x9f<J\xbb3\xc5\
+\xc7~\xfb\n\x96YF\x02z\x80\xb6\x07\x83%\xa4@;1\xed\xa0\x8d;\xe0/?\xffun}\xe0\
+I\xec\xe62\x82\xc5G\x92\xa66:\xd6 }SiRe\xd0o!\xcc\xb2\xdf\xb3\x88\x92\x10\
+\xf0\xc1\xaf\xc3\xf00\x96J\xc9g\xb6\xf1\xcc\xb6\x9d\xcc\xec\xd9\xc6\xd2E\x13\
+<\xfd\xcc6\xaa\x13\x87S\x1f\x1b\xa6\x938\xa6\xaf\x95\xc4\x84\xedySx\xa4Y\x01\
+\x0b\x95!Q\xc6\xdd^\tr%\xc9\x13a\x04\x1b\xcaA\xc9\n*w!J\xe9\xd9\x90)\xe3\x02\
+\xac\xb5\x01\x8d-\x17d\xd0\xc4\nZx\xc3\xcb\xc8"h\xc7\xfd\x82\xbcP!p]r\xd1\
+\x03\xbb\x8am\x9b\xc3\x11\xa6\xb0d\xb1\xc3\x9a5k\xd82\xdb\xa7\x9bU@\xfb\xcc$\
+\xbe\xa9J~\x95\xce\xfc$5o\x98ko\xb9\x97W\x9dv\x05K\xea\x1e.\xe0V< \xc5\x92\
+\xce\x00\xb0K\xb5\xc1\xb1\x12\xe0\x91\xcdp\xff\xa3\x1b\xe9\xf9\x13D\xca-Z\
+\x88.yl\x83\xf0\xe8&]V\xaf^\xc2\xd1\xab=tnp#\x05$\xda\xc6\x16\x12\xeaU\xda\
+\xc0\xa7\xff\xf1f\xbe\xf1\xc3{\xa0\xbe\x98%\'\x9d\xcb\xae\xc96\xf1s\xfb\x0ci\
+$+\xa0~]\xd0&\xb5D+\xcb\x1cW-\xe8j\x17\xa2\x1e=?@\xa4\t[\x9e\xd8F\xd7\xba\
+\x071<\xce\xc5\xa7M0\x06\xf8\xc8\x05\xcb\xa0\xfc@:\xb7\xd2\xd8\x8e\x87\x85d\
+\xa6\xdd\xe1\x86\x1bn\xe0\xdak\xafefj\x1f\xe3\xe3\xe3\xf4\xfb}\x94R\x04A\x80\
+m\xdb\x83VT\xaf\xd7C\x081\x083*cV\xd7\xae]\xcb\xcc\xcc\x0c\x17\\p\x01\'\x9dt\
+\xd2`WP\xadV\x07\xf7E9\xa9\x97t\xd3\x92qU.\xbe\xca\xddN\x19\xcb\xaa\x94\xa2Z\
+\xad\xb2o\xdf>\xbe\xfe\xf5\xaf\xb3q\xe3\xc6\x01\x161>>N\x9a\xa6LMM\x11\x86\
+\xe1`\x87\x04\xfb1\x8e\xf2{\xc7q\x06\xed\xa32\x13\xbc|\x8d$1m\xc1\xf2\xf9\
+\x0bYJ%i\xc0\xb6m\xe6\xe7\xe7\xb9\xef\xbe\xfb\xb8\xf1\xc6\x1bY\xbf~\xfd \xcb\
+\xa3R\xa9\x0c\xc8\x03q\x1c\x0f\x1e\xf5f\x83\xd9\xd9Y\x1c\xc7a\xd9\xb2et\xbb]\
+\x1ey\xe4\x11&\'\'y\xe2\x89\'x\xe3\x1b\xdf8\x08\x89J\xd3\x948\x8e\xf1}\xff?\
+\xa5\xd5\xf8\x7fr\x1c\xe4\xbb\x10\x05\r\xc7\xa5\x1aX\x05\x1fE\x12G)\x17\x9d\
+\xb0\x92~f\xee\xfd\x92\x84\x1a\'p\xeb\xbam\\\x7f\xdb\x9d\xac\x7fb\x0b\x9d\
+\xc4\xa5\xaf\x87\x10\xce(jl\x82$\x03\x94m\xe8\x9cv\xcd\xacrk\xc62\xa0?\xd7\
+\xa6\xb9l%s\xb3\xcf\xd1\xb4\x02\xbe\xf4\xad[\xf9\xe4G_\xc92\x17\xe6\xe6\x14\
+\xbf\xf5\xee\xd7\xb2n\xe3?\xb0\xcfI\t\xb3\x14R\x01\xc3#\xb0g;\xceh\x13?\xea1\
+\xfb\xcc\x13,\x1fk\xf0+\xbfr\to\xbc\xeceL4\xe1\xb6\x875\x7f\xf2g\x7f\x89\xe5\
+\x05,\x1b\x1e"\x01\x9e\xda<\xcb\xde\xadO0q\xc8*\xde}\xe9\x99\\t\xbc]l\x97\n\
+\xe9r\x9aSi\r\x13F1A\xf0\xf3\xe5\xfe\x0bU\x9de;+\x8a"\xee\x7fh\x1d\xcfM\xcf\
+\xb2O\x8f\x92\xd8.\xb8\x15\x84\xed\x1b\x93\xf6L\x91\xab\x1c\xaf\x1a T\x8c\
+\xce#t\xda+|x\x04H\x1b\xe1\x07d\x9d9\xe4\xf0(\xf4=\x883R\xabA\xbb\xbd\x9bG\
+\x1e\xdb\xcb\x83\xebfYzjkA\xd7*\x1d\xf0\x14\x05\xb69[.\xfcd}\x9f/\x7f\xeb\
+\x1a\x1e\xd8\xb8\x1d\x7f\xd1j\xbaT\xe9\xce&\xc6\xe7\xda\xb6L\xf1P\x1a<\xcb\
+\xb0\xeaJ\xca\xef\\\x84v\xa4\xa1\xf8\xd8>\xb4\xe7\xc9\xbbm\x90)+C\xbe=\x98\
+\x00\x00 \x00IDAT\x96-\xe1\xc8eC\xbc\xfe\xd2q~p\xfd!l\x9e\xec\x10\xc6\x12\
+\xfcQp,CE\xd6\xa5\x10\xa2\xd4\xa2\xc8\x81\xc2\\\xa3\xc9unV\xd8v\x0e\xda3\xb8\
+\x8d\x16 5\x81\xd0\xf8\x85\xfe\x06e\xdef\n\xa4\x99`\xae\x97BU@j\x817\x04I\
+\x1f-\x12r\x99\x90\xa9\x84L\r\x9afx\x8e\x99\xf4\xdf\xf9\xd6+\xf8\xf0\xc7\xff\
+\x96`\xc9\xb1Dn\xcd\xd0\xce\x9a\x13\x06d\xce$\xc1\xc8b\xee~\xe8\x11\xb6\xed\
+\xb9\x82%\xc5\xc2E\n\x03*\xc9"I\x12\xad\xd1\x96E&`\xa6\x07\xf7>\xfc82\xa8cy5\
+T\xa4\xa0\xd2\x00\xcf\x83n\x17t\x82C\xcc\xd9\'\x1eOK\x16\x0b\xab\xcc\xc8`\
+\xdc\x8a\xc7\\n,\xcf~\xf7\xcf\xbf\xc3\xc3\xcf\xec\xa1\x9d\x07\xa4\xa1\x84}s\
+\xa6\x1d\xebW\xcd\xcd\xa5\xfb\x08\xcc\x04frd,S\xc1\xa4o(\xca\xd25\x8c\x8f4\
+\xc4\x19\x1e\xc5m\x8c\xf1\xd0\x13O\x92~\xebV\x86G\xde\xce\x05\xab\xf7O\x02\
+\x96\x96\x85\xe6\xa1\xd4\xb1\x98s$\x80\x9d{\xf6\xf2\xf5\xaf\x7f\x9d{\xef\xbd\
+\xd7\x84\xac\xadZe\xc0\xe5\xa2}U^\xe3\xe5J\xb9\x04\xc3\xdb\xed6\xbe\xef\x93$\
+\t\x96e\xd1j\xb5\xd8\xb1c\x07\xd7_\x7f=\xb3\xb3\xb3T\xab\xd5A{Ik}\x00\x0b\
+\xcb\xf3\xbc\x9f\x99KQb\x89Q\x14\xd1\xef\xf7Y\xb6l\x19SSS\\\x7f\xfd\xf5\xdcw\
+\xdf}\xd4j5|\xdf\'\xcfs\xa6\xa7\xa7\xa9V\xabT\xab\xd5\x81\x06\xa2d\x7f-\x04\
+\xf8+\x95\xca\xe0=\xcc\xcf\xcf\x0f\xec\xd2K\xc1]Y\xb0\xcaQ\x16\x1f\xc3\xd82\
+\xbf\xcf\xb2\x8c\x1f\xff\xf8\xc7\\}\xf5\xd5\xcc\xcc\xcc\xb0j\xd5\xaaA+O)\xc5\
+\xdc\xdc\xdc\x00\xbb\xb0m\x1b\xdf\xf7\x99\x9c\x9c\x1c\xecx\xa6\xa7\xa7\x11B0\
+11A\x9e\xe7\xdcq\xc7\x1d,Y\xb2\x84\xbd{\xf7\xb2x\xf1\xe2\xc1\xb1)C\xa5\xe2$\
+\xc6w\xff\x8bR\xad\xfe\x9dq\x90\x05\xc4\x06\xdb0)\xcaN\xaa\x03\x8c\xfaF\xe6_\
+\xb1\x0c\xec\xf0\xf0\xfa\x9d|\xf3{7\xf3\xc0\x93;\xe8Z\xc3\xcc\xe6>=y$\xaaR\
+\x83\xb8g\x84d\x96\x052/T\xd9\x89\xa1\xa1\n\t\xbd\x1e\x0c5\xc1\xee\xd1\xee\
+\xb7\x19\x1d\xa9\xb1w\xe7$k\xb7<\xcd\x0f\xef8\x827\x9f\xb0\x98E-\t\r8\xf1\
+\xf81\xee\xd8\xd2!\xcc$\xd4F\xa1\x9b\x83]\xa1\x1a\xf7\x10\x9d\xcd\x9cvt\x93\
+\x0f\xfe\xda\x95\x9csj\x8d\xc9>\xfc\xf5\x97\x9e\xe0\xbb\xd7~\x1f\xbf\xd9\xe2\
+\xb9\xbds\x1c{\xdc\x1a\x12`j\xcfN\xaa\xc9>\xae8\xebb~\xf3\xf5\x87\xd2\x00l?3\
+K]<\xf0$ZH\xdc\x17P<\x80\x03@\xb82\xa8\xde\xf7}\xde\xf5\xae\xcb\xb8\xbbW\xe7\
+\x07\x0f>M\x1e\xc5f\x82\xd69\xd2r!Op-\x01q\x9f%\x8bZ,\x19]\x8e\xc83\xa6\'\
+\xf729=K\xa6R\xa4\'\x99\xf5\x1c:\xfdI\x101\x95\xa5\x8bi\xef\xdb\x87W_F\x98\
+\x84|\xeeo\xbe\xc9\xc5_\xfb\x105i4%v>\x8fU1\x17{\xb7\x17#\xaa#\xcc\x03_\xfc\
+\xd6u\xac\xdf\xbc\x8f\xc8n\xd1O=\x82\x91Ed3\xa1\xa1\xfc\x88\x14\xbf^%\xe9\
+\xb6Qa\x9fZ\xcd\'\x8aB\xb2~\xc8\xd0h\x0bG\xc0\xe4\xdc>\xa8hP\nQ\x13\x8c\xfa\
+\x01\x87\x8c8\xfc\xe5\x1f]\x80\x0f\x84S\xdb\x18\xa9,\xa6\xadR\xe6\xbb{\xa9\
+\x0c\x8d\x10\xf6\xa6\xb0\x83\x06\x99r!\x08 \x8b\xd0y\x8e\xb4-D\x96\xa3\xd3\
+\x1c\xdb\x81,\x0fA\xa7X\xb6\xc6Q\xc6\xfdY\xe6}Z\xb6\xa0\x92\x1aA\xa6o\x19_;\
+\x81Y\xa5V\xebMz\xb9\x02\xa7fH\x13\x96\x03Z\x91\xe71J+\x1c\xcb\x1a\x08?\x05\
+\x86\x0cr\xca1\xc3\x1cq\xc88[\xbb=\xb0\x0c\x85\xd6\x08`b\x18\x1a\xa6\x1b\xcf\
+`[\x01\xd7\xdcp+\'\xfc\xc6+\xa9H\n\x9e\xac_\\\x17\x9a,\xcf\xb0\x02\x87\xf9\
+\xdc\xcc\xefW}\xf7z\xb4\x1c\xa2\x1f% *\xa6\x08\xc7)\xb8\x12G\xa6\xd4{s\\q\
+\xc1Q\xd4\x95\xa9\x9a\x8e\r\xbd\x0c\x12\xdb\xd4\xbe\xbf\xfe\x97\x07\xb9\xfd\
+\xb1\x1d\xec\xe9d8CK\xf1\xfc\x06\xdd^DI6\x90smZ2\x81\xa4\x87\x12\x12\xc7\xad\
+\x90j\x97n\x02\xa9\xa8\x18-\x8ep0\xd93>I\x1a\x90$\x12jG\xb2u\xc6\xe7\x0f\xfe\
+\xec+\xfc\xe8\x9f\x7f\rK\x98PL\x0b\x8cf\xcb)\xb0\xba~\x17\xab\xda$\xcf5W]\
+\xf5\x1d\xd6ox\x1c\xcbv\r\xb9\xa0\xdf\xc5\xb5\x1d\xf24\xc3u]*\x95\n\x8b\x16-\
+\xe2\xf0\xc3\x0fg\xe5\xca\x95X\x96E\xbb\xddf\xef\xde\xbdl\xd8\xb0\x81\xe7\
+\x9e{\x0e0\xe0o\xb9R\xbf\xf1\xc6\x1b\x07\xbb\xf3\xb2%S\xb2\xa1J0\xba\xc4F\
+\x9e\xcf^*w\x18\x8dF\x83\x1d;v\x90e\x19\x9b6mbxx\x98~\xbf\x8f\xef\xfb\xc4q<\
+\x88{\x1d\x19\x19\x19`3\xa5\xa8n\xe1n\xa2\xc4.\xb2,\x1b\x80\xd5e!<\xf7\xbc\
+\xf3\x85\xca3#\x0b.\xdeK\t\xb4/\xdc\t\xdcz\xeb\xad\\\x7f\xfd\xf5\x84ah\x00\
+\xf2^\x0f\xdf\xf7\xe9v\xbb\x83\xac\xf2z\xbd\xcea\x87\x1d\xc6\x11G\x1ca\xdec\
+\x9a\xb0w\xef^\x1e~\xf8a\xb6m\xdb6`\x9b\xe5yN\xa3\xd1\xe0k_\xfb\x1a\xc3\xc3\
+\xc3\xf4z=\x1a\x8d\xc6\xa0-(\x840\xf4\xfb\x17y\x1cT\x01)\xb5\xd1\x1a\xd3\xd5\
+\x90\xba\xcc\xa9f\x00\xd6\xd6$\xdc~\xd3ml}f\x07I\xe2\xb1\xbb\xaf\x90#\xcbP\
+\xee\x08 \xa0\xde\x07\xdd7\xf4M\xcfP\x17IB#\xf8\x13\x02*\x1e\x96\x15\x93\xcb\
+\x0cTJ\x84O\x16\xd4\xd8<\xdd\xe5\xdfn\xb8\x85+\xcf\xfeU\xf6M\xf6\xd1\xa3\x15\
+>\xfeGo\xe1\x95\xef\xf8\x14C\xf5*s;\x9e\xc5]\xb4\x1cK\x870\xb7\x87\xf7\xbe\
+\xf9"\xde\xf1+\'\xd1\xf0Ln\xc4?\x7f\xe3~\xee{h\x03\xd2k1\xdd\xcdh-Y\x8d\x0cZ\
+\xc4\xc0\xdd?\xb9\x89\xb7]\xfa\n\xde\xf3\xfa\xd3i\x02\x96\xea\x15\x1f\xd2\
+\x06i\x0f\xd2\xf3\xca\x8f\xfa\x8bv#%\xf0\xc7\xff\xfd<\xf6\xfd]\xc6\x86\x8dO3\
+;\xb7\x13r\x1b\xc7\xab\xe0\x08\xc1\x9b.\xb9\x84\xd7^p\x08K\x86`\x91\xb7_\xb4\
+\xb8e;|\xf7\xbbw\xf2\xdd[\xee\xa0V[L\'\x03\xe2\x84\xfe\xde\xd4(\xf4l\x87\xf6\
+\\\x84g\xb9\xac{\x08.<\x15<\xd71T^\x12\xd2$\xa1Zm\xd2\x01\xbe\xfa\x9d\r\xac\
+\xdf2\xc9t\xe2 \x82&(\xdfx\xfd\xb8>\x90c\x91\xc3\xfc^\x86D\x8a\xeb\xa5\xe8\
+\xb8C\xd3\x02\xea\x10wv095\xc3\xcb\x8e>\x9e\xcd\xcf=\x8b\xa8T\xa9K\x9b\x11[\
+\xf0w\x9f|\x1bu\xca\x05\x85f_<I\xddn\xa1\xb1\xb0B\x8dc\'$\xaaO\x86c\xda-\x85\
+\xd7\x97\x12`\x0b\x81-\x14\xae\xa5\x88\xb2\x0eZ\x84xy\x17\x07\x17\x9djd\x1a\
+\xe2\xf75t\xf3B\xd3\xa1\x90Zp\x80\x8ft)\xff.]\x8b\x07\xc7\xbc\x0c\xe4\xda\
+\xcf:r\x05,o\xc1\xab\xcf9\x8d\xcf~\xfd&\xec\xb1\x06\x99**\x92\xceAJ\xc2~\xc8\
+Dk\x82G\x9ez\x96N\x0cC\x0eH\xdb\x1d\\\xfcY\x9e"\\\x97\xb0`L?\xf8\x84&\xccl"\
+\xc0rlr\xe9\x15\x02\xc2>BD\xa0\xe6x\xcbkOgi\x05\xc8{\x90{\x08\xe9\x18\x18\
+\x05\xb8\xfe\xfey\xbe\xf7\x93\x87\x98\x17MT\xdd#\xb5\xab\x84\xb3\xf3\xe0H*\
+\xae\xa0?7\xc5\xf2!\x8f\x97\xb5,.>\xfb"N>\xf5h\x84\r{\xa6\xe0\xe9\x1d=\xeeZ\
+\xf7$w\xae{\x82(\xeab\xd7F\xc9m\x0f\xdd\x0b\xcd\xa7v\x1at\xfa1\xd5J\xc0w~\
+\xb0\x89\x0f\xbe\xf1pc\x89\x0fX\xaeiy\xe19X\x81Y\xf8\xfc\xe4\x8e\xdbyt\xfdz,\
+\xd7Ed)\xbd^\x8f\xb1V\x83\xdd{v\xd2\x1a\x1a\xe1\xac\xb3^\xc1\xf9\xe7\x9f\xcf\
+\xa1\x87\x1ez\x00\xfb\xa8\x1cQ\x14\xf1\xcc3\xcfp\xc3\r7\xf0\xd0C\x0f\r\x8a\
+\xc8\x0bI\xbc\xfb\xb9\xf7\x90\x94\x8c\x8c\x8c\xd0\xeb\xf5\x98\x9f\x9f\xc7\
+\xf3<\xb2,\xa3\xd5jq\xce9\xe7p\xe2\x89\'\xa2\xb5fll\x8c4M\xd9\xb5k\x17K\x97.\
+\x1d\x84+\xbdP\xc1\xdc\xbf7\x84\x10\xf4\xfb}\xa6\xa6\xa6\xb8\xfa\xea\xab\x07\
+\xe9\x81\xe5qh\xb7\xdbx\x9e\xc7q\xc7\x1d\xc7\x85\x17^\xc8\t\'\x9cp\x80\x8e\
+\xa5\x14g\xbe\xe1\ro`\xe7\xce\x9d\xac]\xbb\x96;\xee\xb8\x83]\xbbv\rh\xc5\xa5\
+Z<I\x12\x84\x10\x07\xd0\x9c_\xecq\xd0\x8d\xb4r2-\x15\x9c\xd6\x01\xf6\x1d6*\
+\x87?\xfe\xc3+\xf9\xec\x97\xef\xe6\xaa\x1f?\xc2Xm\x9c\xdd\x990\xed\x88~\x17l\
+\x05a\xd7\xf4r]\x1bO*T\x16!\x94\xc6\xb2]\xd2$\xc3\x91\x10J\x1b\x94$\xce}\x9c\
+\xc6R\xd2\xf99\x1e\xd84\xc9\xe7\xbey\x17\xbf\xf3\x9e\xb3\x99\x8d\xa1\xe5\xc1\
+%g\x9c\xc8=\x8fn\xc6\xaa\x0b\xe2\x99\xf5\x9c}\xe2\xd1|\xe0\xca\xb7p\xd4\xa1\
+\x06\xa4\xbc}m\x8f\xcf~\xf9\xdb<\xb9\x0f\xec`\x8c\x9a\x9b\xd2\x9d\x9c\xc4\
+\xc9\x04\x96\xe3B\x06\rWq\xe5\x15\x97p\xc4\x08X\xaa\x0b*4\xefW\xd8\x03aY\xd1\
+q>\xa8D\x00\x07\xa3\xa3\xf9\xc7\xdfz\x15\x1f\xfd\xe4.\x1e\x9d\xd9\x8a\x90\
+\x0eG\x1e:\xc6_}\xe2\x9d\xd41bE\x17\xb0\x94\xd1\x05V+\xb0t9\x9c\xf0\x9e\xb3\
+\xb9\xec\x82\xa3\xf9\xc0g\xbe\xc1|\xa2\xf0\x17\x8f\x12v\x14^\xad\x89\x88\x15\
+YV\xa5\xd2lr\xfb\x1d\xeb\xb8\xe0\x94\x93\xcc\x0bJI\x9e%\xe4B\xa2qxt\xcb\x14\
+\xd7\xdct\x17\x93\xfd\x1c\xed\xb5\xb0\x83!\xe8f\xd0\xed\x83\xef!\xb2\x10\xab\
+?\x85\xea\xee\xa11\xe4\xb2d\xb8\xc6P#\xe0\xc8#\x0fgxx\x98g\xb7\xee`\xd3\x96\
+\xad<\xbde\x03\xe3^\x80\xebf\x1c~\xc82>\xf5\xb17"C\xa8\x14\x00\xf7\x15\xaf>\
+\x93PT\xd1\x95Q\xba* \xcc`jv\x8e\x9b\xee~\x98p\xd02)Z \x85e\xa7c\xe5\xd4]\
+\xcd{\xdf|9"o\xe3\x03\xbep\x91\xb1D\xa415?e\xac\x96\x15\x99\xcc\n\x89@`!\n\
+\xab\x98\xc1\xf2\xa6\x8cP\xd6\x1aKgH\x9d!\xc9\x06\x8d30\x1b\xdf\x8a\r\x97_x\
+\n_\xbd\xfaFB\x99\xd0\x919d\xa19\xdb\x99\x06a\xa1,\x9b\xa7w\xec\xe2\xa1\x8d]\
+\x16\xad\xa9Q)-b\x04DI\x8ak\xbb\xf4\x8b\xfb\xfa\x86[\xee\xa5\x9f[\xa4R\x90k\
+\x81\x14\xa0\xd2\x98\xc0RX\xe9<u\xa7\xcd[.>\x1d[\xa5\x90u\xc1\x16d\x99\x8dr\
+\x04]\xe0\xea\x1b\xefb\xf3\xf6Y\x18^\x01~\x03\xa5M\xbb\xb8\xe2\t\x82<d\xe9X\
+\x8d7\x9c\xb3\x86\xff\xf6\xba\x138d\xd8|\xdc\\@\xb6\x1c\xe2\x13\xab\\|\xda\
+\xc9\\\x7f\xc78\xff\xfc\xaf\xd7\x12\xc5!Vm\x84\xe9\xb8\r\xae\x8f\x17x\xc4\
+\xd3\x1d\x12W\xf2\xed\x1f\xfc\x88\xb7\xbe\xeepZ\x96\xb9w-\x1c@\x1a\xb32\xd7\
+\xa5\xd7\xe9p\xf3M\xb7\xb0gr\x92zc\x88J\xa5J\xbf3G?\xec\xb2\xfa\xd0Cy\xd7\
+\xbb\xdf\xcb\xca\x95\xabh6\x9b\xe6\xfc\x15BB\xdb\xb6\x07\x16\xe2\x9e\xe7q\
+\xcc1\xc7\xb0|\xf9r\xee\xba\xeb.n\xbd\xf5V\x1e\x7f\xfcq\x96/_N\xbf\xdf?\x88;\
+\xc8\xbc\xde\xcc\xcc\x0c\xbe\xef3:j0\xc9\xd3O?\x9dK.\xb9\x84\xa5K\x97\xfe\
+\x94B~bbbP4~Y@t\xa5R\xe1\xfb\xdf\xff>\xdb\xb7o\xc7\xf7}\x16/^\xcc\xec\xec\
+\xec`\x17\xf5\xd6\xb7\xbe\x95K.\xb9d\x80\x11\x95\xc3u]\x92\xcc\x14S\xcf\xf5X\
+u\xe8*\xea\xf5:CCC\xdc{\xef\xbd\xac_\xbf\x9eZ\xad6(HI\x92\x0c\xf0\x8f\x85\t\
+\x84/\xe6\xf8%#1\xca\xb0\x84Df\xccbD\x8a\xe3\x05D\x1a~\xfd}ga\x0f/\xe6\x1f\
+\xaf\xb9\x93\x86\xe3\xd1\xee\xed\x80J\xbd\xc8\x101\xd2j\xd9\xef#\xc9\xf1\xa5\
+\xc0\x92\x0eB[\xccE)\x19i\xb1=\xd7\xa4\xdd\x0c\x82\x00\xbbQG:6\xfft\xcd\xcd\
+\xbc\xea\xd5g\xb3\xb80\xbe\xfd\xfd\xf7\xbf\x9a\xcb.\xff\r\x8e;\xfc\x18\x8e<\
+\xead\xdey\xe59\x0c\xd5\xa0f\xc1\xbf|\xfbI>\xf3O\xdf@\r-CU\x97\x10)I\x16\'x\
+\xcdQ\xe2N\x8ff-\xc0V\xf0\xb67\\\xcaq\xab\x1bd\xb1\x06\xbbtI:\xb0\xe2\x1f\
+\xcc\xce\xa3\x1c.\xd0P`\xa5\xf0\xf9?\xfcU>\xf2\xfb\x9fatd\x82?\xfe\xbdw\x9a\
+\xce]\x15\xf2\x10\x9aA\x11~V/\xb4\x12Z\xd1\xaaI\xce8v\x84W\x9e}\n_\xbb\xf5Q\
+\xa4\xce!\x8f\x89{]\xc3\xfb\xc4&J%\x8f?\xb1\x998;\t[@`\x0b\xa4]A\xe2\x11c\
+\xf3o\xdf\xfb\t\xbbf#\xfc\xe6\x12"*(\xe1\x9b\xa8\xce$E\x92`g\x1d\x16U2\xc6\
+\x87\x9b\xbc\xe9\xa2\xb3\xb8\xf2\x8a5\x04va\x8b\x01\xd8\xf2pv\xcd\xc0W\xbf\
+\xf6C\xee\x7f\xf0!\x1c\x99\xf0\x89\x0f\xbf\x91\x15\x01\x90\x1b\xb6\x9d\x05\\\
+y\xf9\xabp\xeb\x96q\xe3\xc5D\xd7>\xb6y\x057\xddv\'\xd8M\x03((\rZ\x9a\xc9VklK\
+Pq5\xef~\xcd"\x1c\x16\xe1a,F,m.1\xcb2m\x17\x13\x88\xa6QB\x03\x16B\xab\x05\
+\xbea\xa5\xfd\x8bB\x92\x15\xc5%7\x9em,\x90\xc4(\x03\xb3\x1c\xb1\x18.8c\r?|d\
+\x17^}\x84\xb8\xd73\xa2\xc0$\xc2\xae\xd4\x98\x99\x9f\xa2\xe14\xf8\xfe-ws\xe1\
+\xc9\x17\x9bH\xfb\xe2b\x90E-\xb1]\x98\x05\xeeZ\xbb\x81X\x06\xa4\x96K\x9e\x15\
+\xefE)\x9a\xbe\x83\x8ec^~\xd42\x0e\x9f\x00\xe2\xae\xf9\xecHRm\x0ca\xee^\x17\
+\xb2a\xf3v\xac\x91\xc5\xe4\xf8\x06\xcf\x88B\xbc\xe1atw\x1fi\xd4\xe6\xa2\x0b/\
+\xe6\xd7\xder$\xcb\x02\xb0u\n\xb9B\xa7\x1a[\xb8\xd8\x9e\xe4\xf0q\x98\xb8b\
+\x19\xdb\x9e;\x9ck\xefx\x08%c|\xcf%\x17\x1a\xcfu\x88\xad\x9cDk\xb6M\xce\xb1\
+\xfei\xcd\xe9G\x1a\xb5\xa1\'0,\xbf~\x07\\\x9f\xb5k\x1f\xe2\xd9m\xdb\x19\x1d\
+\x1bgrz\x86\xe6P\x1d2\x9f<\xeeq\xe5\xdb\xdf\xce\x9a5k(m\x17J\xe3\xbe\x85+\
+\xe6J\xa5\xc2\xde\xbd{\x19\x1f\x1f\xa7\xd1hp\xce9\xe7099\xc93\xcf<s\x90w\xcf\
+\xfeQ\x8a\xff\xb4\xd6\x9cp\xc2\t\\x\xe1\x85,_\xbe\x9c4M\x07\xad\xaer\xe2.[O\
+\xf0\xcb) Q\x14\x11E\x11w\xdey\'\xa3\xa3\xa3(\xa5\x06\x8a\xf14My\xeb[\xdf\
+\xca\xab^\xf5*l\xdb&\x8a"\x92$\x19\x88\x1c\x95R8\xb6C\x92&\x03\x1cilt\x8c\
+\x8b/\xba\x98j\xb5\xcaSO=5\xc0\x8eJvW\xa91)\x89\x00/\xf68H\x15\x8a\xc2"\xc5"\
+\xc62:Y41\x90\x82LA\xc4 z(\xdd\xa7\xdf\xcf\xf9\xd5\xcbW\xf1\x87\xbfy\x19\xf5\
+\xfci\x9a\xf6\xb3\x04\xd9s8q\x97\xba]\xa1\xe57\xa8j\x0f;wp\xa9 \x94O\xd8\x07\
+\xcf\x19"K<\xb2\xc4\x03]\x81\xd8\x82\xae&K-\x12Q\xa5\xed\r\xf3\xa9\xff\xfd}\
+\\\xd7\x98\x1a\xae\xa8\xc1o\xbd\xe55\xfc\xf6\xbb.\xe5\x8f>t\x0e\xe3M\x98\x8d\
+\xe1}\xbfw\r\x7f\xf3\xe5\xeb\xa9\x8f\x1fOf\r\x1b\xcai\xda\'\x0bs\xdc\xa0\x81\
+_\xf5\x18\xaf:T5\x9cu\xf2\xd1\x00hG\x98\xc8BY\x98\xfd\t5\x80{\x7f\x19k\x17[\
+\xc3\x08p\x88\x03\xcb\x80\xbf\xff\x83\xdf\xe5\x13\xbf\xf9N\xaa\n\xea\xb9q}\
+\x1a*V\xf1Y\xd2%\x8b\xbb\xe4YH\x92E$yD\n\xbc\xe7]g2\\k\xd0\x9b\x9b1\xbeZq\
+\x08\xbeC}\xa4I\xa7\xd7e_{\x96n\n\x91Rd\xd8$\x04\xc4x<\xb9\x13\xee\xb8\xff\
+\x19\x12\x15\x80]E\xa5\x8a<J\xc1\xf7!p\xb1UD\xa0\xfa\xe8\xf6.~\xff\x83\xef\
+\xe0\xbf\xbfu\r-\x01N\x0c\xc3\x12Z\x12\x92\x1e\xac\x1c\x86?\xfe\xf0%\\v\xf6\
+\xd1\xfc\xed\xff\xfcu\x8e\x9f0\xaa\xed\nfu\xa23\xcdh\xdd\xa2\x8e\xf9}\xf9u\
+\xa8\x0eq\xd8\xc1%1,9mn\x08%,\xb3\xc3\x13\x9a\xc0\xd2T1\xc7h\x14h\x01M\xccy\
+\xae\x88r\xf5\xa3\xd0\x08\xf4\xc0\xb1\xb8d\xa1\x96\xbb\x0f\x05\xda\x14\x8f\
+\xfd\xde\xbeja\x97\x15\xd7\x01\x92\x08\x1f\xb8\xfc\xd5\xe7\xa1\xc3i\xac\xacg\
+\x16Byd\xbc\xc3\x04dX\xe8\xca\x08\xb7\xad{\x8aY\x05\xfd\x05\x9e\x1f\x81\xef\
+\r\xca\xd6\x86\'\x14\xcf\xed\x9bC;\r2\xe9\x83\xf4\x90\x8eo\xbc\x93\xc2\x10O\
+\xc5\xbc\xf6\x95g\x18s>\xc7\x05\xb7\x0e2@9\x86\x08\xf0\x83\x1f\xdd\xcaT\xbb\
+\x87\xe37\xc0r\x90\xae\x0fQJ\xe0\xf9dQ\xc4\xd8P\x9d+^w$CAa\xbd\xa2R2Kc\x076\
+\xae\x0fin|o\x02\x01\xef\xbc\xf2\x02\x9a\x95\x8c$\xdcK\xbd\xa6\xc9\xd4<\xfd\
+\xa4\r\x01\xa4"\xc7\xa95\xb9\xed\xee\x87\x0cA\xcc2D\x17\xd3J\xb0@)\xee\xbe\
+\xf7~\xb4\x02\xd7\x0fp<o@\x9f}\xc59gq\xd2\xa9\'\x13\x85\xe1\x01:\xa7\xd2u\
+\xc1\xb6\xed\x01\xf67>>\x0e\x98\x96M\xb5Z\xe5\xc2\x0b/\xe4\xac\xb3\xcebzz\
+\xfa\xa0\xef\xa1n\xb7\xcb\xb2e\xcb\x06\x00\xf3\x9b\xdf\xfcf\x0e=\xf4P\xc0L\
+\xee%\xb5\xb6\xa4\x01\x97\xdfGQt\xd0\xaf\r\x06\xe8\xff\xde\xf7\xbe7(J\x8dF\
+\x83\x9d;wb\xdb6\x87\x1dv\xd8\xc0\xfd\xb6\xdf\xef\xef\xa7\xeer\xa0Y\xa1\xeb\
+\xb8x\xaew@\xfb\xef\xf8\xe3\x8f\xe7\x92K.\x01\x8c\xfdJyL\x17\n\x1c_\n\xe3\
+\xa0\n\x88i\xe1\x98\x9c\x10\xabp\xf2U8\xe48\xe8\xe2\x91\x03\xaee1R\xb1\xf0\
+\x80\xd7\x9e6\xcc\x97\xfe\xe2\xb78v<\xa1\xa5v\xa3\xfa\xb3$aD\x9a\xe4h\xe1`9\
+\x15\xb4\xed\x93\xe2\x10*\x89\xf4\xeah%\xd1Ij\xe89\xb5\x8aY&\xa5!y\x1c\x93W\
+\xc7\xb8g\xe3V\xbe\xf7\xa3mf\x0b\xde\x85\x0f\xff\xda%\\p\xea\nr\xe0\xee\'4\
+\x1f\xf8\xe8\xd7\xb9c\xe3N\xa6D\x83\x9d\x9d\x18\xa7\xdeDE}\x03\xdeV\x1b\xcc\
+\xcfu\xf0EN\x85\xc8xa\xe9\x98^b<\xa7\x12\x02\x90Uswi\x85 \xc6)z\xe8\xbf\x8c\
+\x91\xb4{\xe8^Ng:f\xa4\t\x95\n\xccw\xc1\xae\x18*h\x1f\xd8\x17\xc3\x1c5\xfa~\
+\x8b\x8e3\xcc\x8c3\xc2n\xd9b\x1a\xd8\xba\x0b\xf2D\x81\xb0\xf1\xc7\xc7\xc1\
+\xb3\x11\xae\xc4\xadz$2\xa5\x9ft\xe9\xc4 ]I\x82E\x17\xdbd\x9dl\x9cd>\xa9\x93\
+\x8b\x808\xd5E(y\x8c\x90\xc6\x900i\xcf\xd0p\x15\x9f\xfd\xc4\x1fr\xf6\xc9M\
+\x02\xc0Is*2%\x99\xef\x11\xf7cF\xab\xa6\xb8\xcd\xcf\xe4|\xf4\x83\x97s\xd2\
+\xaaa\xaa(|2|K\x13\xf6\xbax\xb6\xc6"CG\xf3\x88$\xc6\xc7\xe0\xb4\xfd.\x04\xbe\
+\x8d\xabc\xec\xbcHG/Dm\xb96\xad\tG\x1a\x07T\x17\xa8\xe8\x08/\x8fq\x15\xb8z\
+\xbfy\xa0\xe9\xec\xd8\xe4\xa2h\\\x15\x1e_\x86\x8a\xaa\x06\x00\xb7,\x7f\x16\
+\xfbo<\x85Y\xfc\xdb\x02<\xcf\xc2\xca\xe1\x94\xe3j\x1c\xb2\xa8E\xda\x9d\xc1\
+\xf1\x00b\x1cG\x90G1Vc\x98N.\xd9\xd7W\xdc\xb6\xb6g\n\xc8\xc0\xb1P\x93\x92\
+\xa3\x80\xbb\x1ex\x88\x04\x8f\xcc\xa9\x98\xddCP\xc3r\x03\x84\xb0\xe9\xcc\xb5\
+\x19o59\xfb\xd4\xd5\x85\x9f\x98\x0b\xd2\'\xca\rq0\x046<\xbe\t\x85M\x14\xc5P\
+\x8a\xfdTF\x1a\x87x\xb6\xc3\xd1G\x1f\xcd\xb2\x11\xc3T\x9f\x07\xbaV\x85\x19\
+\xed3\x95\xd9\xf4\x91([\xa0\x85\xd9\xe9-\x9f\x80\xe6\xc8\x1896X\x1e:\xd1da\
+\x0e^\x83X\xf8h\xb7\xc1=\x0fm +>J\x14\xa7\x80\x047\xa0\xd7\x9ec\xcb\x96-H\
+\xdbbr\xdf4CC\xc3\xb4\xdbm\xaa\xd5*\xefx\xc7;\x00\x85\x1fx\x83\x890I\x12\xfa\
+\xfd>Q\x14\r&\xb8n\xb7;8\xdeeqY\xb2d\tg\x9f}\xf6\x0b\xca\xdc\xfey\xc3\xb6m\
+\xa6\xa7\xa7\xb1m\x9b\xf3\xce;\x8f\x89\x89\x89\xc1\xbf\x95\xd6)\xb0\x7fwT~\
+\x0f\x0c2\xc4\x0ff\x08!\xb8\xe3\x8e;\x06\x8a\xf4\xdd\xbbw\xd3j\xb5\xb0,\x8b\
+\xcb/\xbf|\xc0F\xabT*\xfb\xf5_\xc5\xae\x05 \x8a#\xd2\xcc,\x9e\x1c\xdbA\x15\
+\x14\xeaF\xbd\xc1\xeb_\xffz\x96.]:\xb0/Y\xd8\xba\xfa\xff\x86\x0eDKc\x16\xa4\
+\xa5\xc1@\x05\x03\'\xd0\xf2\xd4\xcc\xf7\xba4\xaa\x0e\x1a\x8d\x87\xf9\xc7\x97\
+/\xb7\xf9\xe6g>\xcc\xef\x7f\xfa\x9b\xdc\xb3\xab\xc2sm\x88\xfb]p=*\xbeK7Sd\
+\x16\xd0\xa8\xd1\xd5\t\x04fRC\xcc\xe3\xb8\x12[\xa6\xe8\xb8K\xa43\x92\xbc\x86\
+J\\n\xfa\xf1\xb5\xbc\xeb\x82\x0f\xe1{\xf3\x90JfzU\xbe\xfc\xc3G\xf8\xf4\xbf\
+\xde\x8e\xb7\xf8e\x84\xb5\nA\xbd\x8ek)\xda\xdb\xb7\x81\xedA\xd0\xc4\xa95I\
+\xdbs\x04A\xcaQ\xcb\x87\x11*\x05R\x12\xe1\x91bn\xaa\n\x9e\xc9\xce\xd61\xa8\
+\x1c!\x14B\x04\x07oj-\xc0mU\xc9\x01\xafn1\x9d\x99\x9c\xee\xac\x01\xf7\xac\
+\xd7\xdc~\xd7Zv\xec\x99\xa4\x1b\xa5\xccwC\xe6z1\xd3\xbd\x98\xf9$GKC\xe5[>>A{\
+\xb2\x0b5\xe3\xd3C\xdcA\'9\xd3\xd2\xc6\x93\x19\xa1\x8e\xd97\x1b\xb3\xbc\xe5\
+\x91 \x89\x8b\x06\xcf\x83\xeb\xf7\xa0\xedq\x84P\xe4Yf\xda\x16Qb\x84py\ni\xc8\
+1\xabWs\xea\xf1\x0eu\x1b\\\x85\xe9\xb5\xd9\x02\xa7\xee\x92\xe30\x9f\xe6(a\
+\xb1d\xd8\xc2%\x03\x153\xbbs\x1b\xade\xcb\xb1\x85GPq\xa0\x80g\x1dO\xe0\x08E\
+\x82\xb1\xcb\xd7\x944\xcd\x18G\xdadZ\x19\x16\x860\xedL\x85B\xaa\x1c\x17\x08\
+\xc8\xb0E\xbf\x10K\x9b\xf6\x9c%\xcb\xbflv\x1e\xaap\'\xd6\x82\x05\xf6\xe9\xa6\
+`\xc8\x81\xb5=\x83\xe7,$\x7f\x98\x91A\x0eu\xd7\xe1\xf2\xcb.\xe6O\xbf\xf4\x03\
+\xbcZ\x0biK\xe2p\x1e\xec*\xb9tA\xa5h\xaf\xc9\xf7o\xbe\x8b\xf3\xd6\\\x8c\xb6\
+\xcc\xdf\x10(4\x9a\xc9\xae\xc5}k\x1f\xc6\xf2\x1b$\x14\xf4^\xcb#\x8d\x15\xd2\
+\xf1\xb1m\x973\xcf8\x95V\x11\xd6\x19+\xc7\x90\r\x85\x11\x1e>\xbd\x1d:\xfd\
+\x10\xdf\x1f#U\xa6\xe8\xe5a\x17|\x97,I\xa9\xfb>y\xa6\xb9\xe9.\xcd\xbe\xdd\
+\xcf\xd2Nb\xc6\xc6GiU<\x92\xde<qg\x0e\xcbr\x90\x8eO"\x03bQ%d\x9c\xcc\xae0\
+\x177\r3M8 \xaa\xe8l\x96\x9eh\xb3s\xdf\x1e\x93\xd3\x02\xd4k\xce\xe0\xda\xdc\
+\xf8\xf8\x93\xcc\xcf\xf7\xb0jM2\x1d\x91)cx\xb8f\xcd\x1a\xea\xf5\x1a:M\x11\
+\x85\x1f\x95\x94\xf2\xa7\xa8\xae\xa5z\x1a\x18\xc4\xa8\x96b\xc3\x15+V\xb0z\
+\xf5\xea\x01;\xeb\x17\x1dCCC\xec\xda\xb5\x8b\x89\x89\t.\xbd\xf4R\xd2\xd4\x80\
+\xfcCCC\x07\xd8\x96\x97\xd8L\xb9\x13)\x19X\x07;fgg\x07\x1a\x91\xd2\x9eebb\
+\x82V\xab\xc5)\xa7\x9c2\x98\xf0\xb5\xd6\xf4\xfb\xfd\x03^;I\xf6;-\x97C\n\x89\
+\xc6\xbc\xd7f\xa3\xc9\x9a5k\xd8\xbd{\xf7@\x03S\xb6\x05\x7f\x19\x04\x84_\xc68\
+x\x0cd\xa1\x89\\i\x97\xb1\xa086\xaa5s\x0f)\x8d%\xc1+\x9c\xc2\xfd(\xe4\xef>\
+\xf6v\xfe\xe0\xcb\xeb\xb8m\xfd\x0e\xb6<\xdb\xc5\xb1,\xb4U!\x0b\x0b`\xad^5\
+\xcb\xf1z\xcdl\x07\xba\x1d\xd2$F\xd8)\x96\xeeS\x93\x16Y\xaa9\xf9\xb0C\xf9\
+\xab\xff\xf9j|\x07T/EV\xea\xe8\x04~x\xe3M\xd4k\x8b\x99\x9e\xcb\xc1o\x12\xce\
+\xf7\x08\xf39h\xd5@U!\x83\xb4;K\xb5e\xb3jq\xc0+N\x1b*\x84r\x9a\x8acv\x00P\
+\x12\x05,\x84(\xed\x1d\xcd\xce\xeb\xa7\xf3\x13\xccA([\x19\xb0\xc0Oh\x90%a\
+\x81\x90\x030>\x05\xa6\x8c\xb6\x8d\x1b\xeez\x8eo\\}\x13[v\xcd1;\x9f34\xb6\
+\x94(\x15\xe4\xc2!J!V\x01\xc2\x1b\xc5\x1am\x90\xe5\x90\xcc\xcf\xf1\xc4\xd6}\
+\x8c/Z\xca\xde\xb0G\xaf\x9f\x98\x9e}\xe0A\xae\xf0\xdd\x1a\xd9\x9c`z\xa6\x8d\
+\\\xb5\x08A\x8a\x8fG\x08<\xb3u/a\xea\x929\n\xb2\x1c\xbb\x12\x90\x8512OqEN\
+\xa5\x19\xf0\xf2S\x8e\xa7f\x1b\x93F\x1c\xc0\xd6\xc4\xfd.\x96g\xf4!\x9ec\r\
+\xc0\xe8$\xec\xe3\xe8\x94\xd6\xb2\xe5\x90k:Q\x97J\xadE\x9a\x85\xb8vI\x0b\x8d\
+\x91\x95\x00\x17\x10iF\xd2\x9dG\xb8Fek\x12\xb9\n\xcb{a4 \xaa0\x17\x1f0\xfb\
+\xf2\xdc\xb4\xba\n\xc39\xbd\xf0\xc2;\xe0z\x94\xa6`\x08\xb3\x94Q\x80\xa5E\xc1\
+B+\xd9h\x0c\xfeF?N\xa8\xba\xa6E\x89\x86_y\xcd\xa1\xfc\xd5\x17\xe7Q\xd1,Au\
+\x88x\xef\x1c\xc1\xea\xe5\x84Ss\x10T\xd1:\xe5\xfe\r\xcf0\x9f\x19\xba\xad+\
+\x8dI\xa2F\xb0o\x066=\xb7\x17]Yi\x8a\xa2%MAn\xcf\xe3\xd7*\x8c\rU8\xf7\xaccp\
+\x01\xa9#\xa4r\xd1R\xe2Xf\xb7\xb9\xf1\xe9=\xc4\x1a\x94\x94H\xdbAY>\xf4{\xc8j\
+\x8d<K\xe9\xe5\x92\xfb\xd6?\xc3\xfd\x0f<\x82\x17\xb8\xa4^@\x94\xc4\xa8\xb0\
+\x8f\xa3\x14\x81k\xe19\x0ei\xae\t3H\x84\x8bUk\x91\xd95c{Pm\x99\xcf\xaf-Hzd\
+\xb6M\x9a[\xcc\xc5\xe6P\x0e\x95v\x11\x1a\x1e\xdf\xb8\x814O\xf0\x1c\xc9\xa2Ec\
+\xec\xda\xbd\x87E\xad!N=\xf5tz\x9d>\xd5z\r\x90?%\xaa+\xed>\\\xd7\x1d\xd0e\
+\xcb\xf6Q\xd9BZ\xbcx1\xa3\xa3\xa3\x07]@z\xbd\x1e\xa3\xa3\xa3\xb8\xae\xcb\xa2\
+E\x8b\x00STJ\xbd\xc4Bo\xaa\x85\x96\xefp\xa0E\xfc/:6m\xda\x84\xe7y\xa4i\xca\
+\xf4\xf44\xabW\xaff\xfb\xf6\xed\x9cq\xc6\x19\x03\x16U\xf9\xb5Z\xad\x0eD\x89\
+\xa5\xf6#\x8cB\x02\xdf\xbc\x87~\xd8\xa7\x12T\x0ep\xd9=\xe2\x88#\xb8\xed\xb6\
+\xdb\x06\x82\xcd\x12\xd7y\xa9\x08\t\x0f\xde\x89\xab\x04\x05\n^\xa4\x14\xc5\\\
+S<\xca\xef\x1d)\xca(\n\\\tc\xcd\x80:\xf0\xe9\xf7\x9e\xc4\x87.\\\xce\x9a\xe1\
+\x0c\xbb\xbd\x97\xb4\xd7\xc3j4a\xa8\x06I\x07j\x1a\xc2i\x08{\xf8\x8e\xc3Dc\
+\x18\xdd\xeb\xe3\x92\xd0\xa4\xcdG.=\x81\x7f\xfa\x9dW\xb3\xd8\x87\xb9>\xcc9\
+\xc3t\x84\x83_\x87\xff\xf6\xeboFM?\xc7\n\xdf\xa6\xd6\xcb\xa1\xa7\xf1F\x1a\
+\xc6\x92\xbb\xd223xw\x07\xcb\x1b1\xe7\x9fy4\x93s\x10\xdb\xc6\xac\xb0\xc6\x81\
+\xbd\xfc\x14\x9b\x9c\xaa\xe1\xf3\xe3\x80\xce\x11:\x1b\x00\xb1\x0b{\xeb)\x94H\
+P\xd1\xddP\xa6\xcf\xafB\xc8c\xf2\xcc\xb4*:\xc0n\x05W\xdd\xfe\x0co\xf8\xe0\
+\x17\xf8\x93\xcf\x7f\x97\x07\x9fK\x90\xa3\xc7\x90\x0f\xadf:\x1b\xa2\'\x9aD\
+\xb2\n^\x15;p\x11\xae&\xd7\xf3\xa0\xe6@\xf6\x11\x81\xc7L\xa2@\x04\xe8^b\x14\
+\xe2\xb6\x84\\\x93Y\x01~e\x11\xfd\xf9\x0c\x1f\xa8\xd2\xa3Blb\xeb\xd3\x1e\xa9\
+\xd6\xc4Q\x82\xd7\x1a%\x8b"\xb0$2\ti\x05.q{\x9aSN\\Z\x8a\xa7\x99\xcfS\xb4\
+\xb0q\xabM\x84\xed\x13f\xf9\xc0\xd2\xdf\x06\x9c\xa0A\x1a\x8c\x10\x8b\x1a;\
+\xbb\x02\xb7\xd6*\x80\xf6\x00\xb4\x03\x89\x85\xe5\x0f\xa1\x95\xb9TjJS\xb34\
+\xb9V\xc4\xc2\x02Q\x05Y1\xc5A*2+#C\x17&\x9b6d\x1eh\xb7\xc8Na\xb0;\x94\xe4\
+\xd8d8\xaa\xb8\x98s\xdbl1D\x86\xc9\xac\xcdA\xaab\x82\xf7\x90\xcaGh\xe7\x00\
+\x1c\xcb\xf1\\\xa28\xc7r<\xaa\x02&l8\xf7\x98%,\xb2\xfb\xb4\xa7\xf6\x10,YA\
+\xd8O\xa11\x02\x89\xc6\xb6\xaaD4\xf9\xe7o=\x82r \xd7\x8a\x14A\x02|\xe3\xdbw\
+\xe2\xd5\'H\x85B\xb8\x89q\x81\x8e;\xf8C\x15\xecp\x8a\x97-\xf69\xfb8\xc8\xfa\
+\x11\xbe\xc8p\xf2y\xdc\\Q\x1a\xd9o\xdf;I\xe6\x04\xa4n`\xb0um\x94\xe1\xaa\x1f\
+"\x83\x1aa\xe8\xd0\x0b\x961\xe3\xafb\xd2Z\xcedZa^6I\x82\xc5\xf4+\xcb\x99\x93\
+K\xd8\x9b\x8f1)\x17\xd1\xf6\x16\xd1\xd5.m\x05\xd4+\xe6\x98d!d\x11\xcc\xcd\
+\x82\xb0q\xa4G\x14+f\xe7\xa0\x1a\x98\x90$#8\xcd\x98\x9d\xdbG\xbd\xe1\x13\'}\
+\xa2\xa8O\xadZ%Oa\xf1\xa2%T\xab\xc3\x90\x88\xfd\x0b\xc8b\x94\xf4\xdc\xb2\xc5\
+\xb2\x10\xb4\x06\xa3./\x01\xe0\xd5\xabW\xff\x02\x13\xce\x81CJI\x9a\xa6\xacZ\
+\xb5\xca\xdc\x85\xcf\xf3\x9d{~\xabga\xb1;\xd8\xe2\x01&\xeb\x04\xcc\xe7\xf6<\
+\x8f0\x0cq\x1c\x87\xe5\xcb\x97\x0fZt\x0b[u\xa5\x08\x10LK\xaf\xe2\x07\x03BG5\
+\xa8@!\xdct\x8b\xac\x95U+\x0f\xa53\xd7\xa6\x1aT\x88\xfa!\xa3\xc3#\xa4qB\xd8\
+\xeb\xbf\x04\xcc\xdc\x0f\xb6\x80\x94\x9f\\\xee\xff~!\xbd\xd5Z\xf0\x94\x9f\
+\xf5pQTU\xc2\x07_{"\x9f\xfe\xe8\xfb9\xff\x84U\x04\xe9\x1cy{\x0f\xc4s\x05\x7f\
+U\x98\x9c\x8a~\x1f\xcf\x92Ln\xdf\xcaX`\xb1\xbcn\xf1\x17\x1f\xfd W^\xb8\x86\
+\x97\x8d\x98\xe8\x91-\x93\xf0?\xfe\xeckl\x9a5\x93\xf3E/_\xc9[.9\x97\xee\xaeg\
+p\x15\x08i\x13w\xfa\xb0x1\xccw\xa0Y\xa51\xe4A4\xcd\xe2\xe1*CM3\xf1\xc798\xa4\
+\xf8\xda\xe0\x1de\x910\xf9\xdc\x12\x13\x82E\xd1_/\x86V\x07\xe8\r\x06\xadq@\
+\xe7\xfb\xd9SH\x89\xb6\xcd\xeb<\xb8\x15\xfe\xf4\x7f\xdd\xc3\x97\xbe\x7f\x0f\
+\xebw\xa7$\xd5\xe5\xc4\xde"\xb6\xef\xe9\x93:-\xe2\\\x12\')J\x80ekT\x1e\xa2\
+\xba{\xd1\xdd}XVHm\xc4\xa4?j\xadq\x83\xa0\xf0\xe7\xda\xafP\xe9\')I&\xc83\xab\
+8\xe69\x92\x18\xdb\x86~\xdcGI\xc0+\xe2,\xb3\x1ciIl\x9d\x91\xf6;T\x1c\xc1h\
+\xcb|\x86\x0c\xb0lI\x86"G\xa3\xd1\x04\xb6c\xce\xafN\xc8\xc2y\xb2L\x91(\xf3\
+\xdc\xe6\x90i[\xe4\x19\x88\xe2\xb8\xe8\x01\xe4]\\:Jb)@\x08\xb4(V e\xd4\xa2\
+\x05B\xaaA8\xa3Q\xdf{\x85\xd7\xfd~\xca\xaf\xb9\xce\xb4aV\xe9\xf2b^pI\xcb\xdc\
+\xec*D\xb1K\xd1\x96)fz\xbfmny\x8e\x1c\xbfB\x1c\xa7X\x18\xf6\xd8\xeb.8\x03;nS\
+\xf7]t\xae`f\xd6\xd0\x9b\x85D\x08\x8b\xb9P\xb3\xf6\xd1\xa7\x98\x89 \xb3$\t\
+\xb0\xaf\r\xcf\xec\x98\xa3\x9f\xda\xe4B\xa0\xa2\x1et\xe7\xc1R\xb8:D\xa4s\x9c\
+w\xfa\xb1\xb8\xc0P\xc5)\xda\xb2\x1a\xd2\x88\xe2\x93\xa3\x94"\xd3\n-E\xb1k\
+\x15\xb8K\x96\x82\xef\x93\xb4\xe7\xcdq\xd5\x12\x1dkRm\x81\xed\x80\xe5\x92[\
+\x1eJ\x06dV\x95\xd4\x0e\xc8\xac\x80\xdcv\xb0\xc7\xc7\xc1\xb5`v/\x10\x83\x93\
+\x83L\tZU\xaa\xbe p,,\x01I\xb4\x1f\x132\xbdh\xc5\xd4\xd4\x94a\xfe(\x8dUL\xc4\
+B\x08\x1a\xf5!s\xf4\xad\x17\x86a,\xb4\xf2)3:\xf2<g\xe9\xd2\xa5/\xe8\xff\xbf\
+\x94G\xb7\xdb\x1d\x08\x1f\xe38\x06L\x91\x18\x1e\x1e\xfe\x85\xfe^\xc9&+\x1f\
+\xa5x\xf0\xf9\x18\xc8\xcf\xd2\xdb\xbc\x18\xe3E\xf5\x02\x16\x80\'m:\xd3mN;\
+\xca\xe1\xef?\xfeZ>\xf8\xa6\xb3h\x89i\xe8N\xe1:6\xb4#H\x04\xde\xe2e\xb4;m\
+\x86\x1b\x16\'\xbfl\x11\xd7}\xf1\xc3\\|b\x8b\xe5K`>\x87\xeb\xee\x9b\xe1]\x1f\
+\xfd\x0b\xae\x7f\xe0i>\xf5\xf7\xdf!\x00\xa2\x08\xae\xbc\xe2\x15\xb4\xaa9nU\
+\x9bi\xa7:\x02s\x11\xd8\x19V<\x8dL\xfa\x9c\xf5\xf23\x98\x99\x9a\xa4"\nPUZ\
+\x85\xe1`^\xd8\xfa\xed/z\x07~\x00\xb1\xb0f\x80\xd0\x05U\xf4g=\xdf\xa4\x0e"<"\
+`g\x07\xber\xd5\xad\\w\xcfcl\xde\xd6&S5B\xa7\x85\xb2L&G\xa3U\x07;\xc7\xf3\
+\x04\x1e}\x9c\xbcC\xdd\x8a\x18\n$M7G\xf6f\xe8\xee\xdcL\xd3\x85\xac;\x83L\xfa\
+fe\xa9\x8a\x14\x0c\xa9\x11:G\xa1\x07\xb9\x189\x1e\n\xc7\xe4P$9ZX\xd8\xb6\x85\
+JB\xc8\x13\x1c[\x80\xca\x89{]\x02\xc7\xc6w\xcd"\xc0\x07<4\x0e\x19"\xeda\xeb\
+\x14\x9b\x14\x8b\x14T\x82mk\x1c;\'\x90\n\x87\xfd\xbb\x12\xdf\xd2\x88b\x1f&\
+\xb4\xa24o\xcc\x80LZd\xd2E"M\xaa\x9964Wc\xa5!\x8c[\x0bz?]Z\x17%Yg\xa0S\xecR\
+\xb4\xfa<z\xf5\x7ffH@\xab\xbc\xd8\x19+\xa40\xab\xbf\\\xc1%\x17\x1e\xcd\xd2%\
+\x13XR\xe1X\x05\x9bK\x00\xb6m\xce\xb0\xb0x\xfa\xb9\x9d\xac\xdb\xb8\x9b\xb8\
+\xc0/\x1eyJ\xf1\xcc\xb6m\xc4Z#\xad\xc0\xa0\xe2I\x8e\xe3\xd8\xa8,d\xb8\x19p\
+\xd1\x05\xab\x8a5W\xb1z\xb7<\xd2\\P\x10\xa7p\x84\xc2N3\xc3\xd0\xca5\xec\xddK\
+\xd2\x0f\xa1o\xce\xaf\xdb\xf0\xa9\x8b\x1e\xae\x9ea\xcc\x8b\x90\xe9<"\xeb"\
+\xb3.V\xde\xd9\xff\xc8:\xd8\xd9<\xd9\xfc>\x08g@\xf5\xb0\x9d\x18\x19M\xc1\xd4\
+\x16d\x7f\x0fYg\'y8\x85o%\xd8\xc2\x9c7w\xc0m\x96\xd8\x96\x83\xce\xf5\xbe\xa5\
+\xa4=\x00\x00 \x00IDAT~\x87\xd9<G\xa9\x8c\xa0b\x15x\xd4\xcf\x07\xa1\x17\xee>\
+\x16\x1a\x89\xce\xcd\xcd\xd1j\xb5~\xe1s\xf7R\x19e\xcb\xae\x0c\xb5*?o\xbd^\
+\xff\x85A\xfa\xb2\x88\xe4y>\xc8\x10I\xd3t\xf0\x15\x18d\xa1\xbc\xd8\xe3\xc55\
+\x93G\x92\xa5)\x13#Mj\x02F\x1d\xf8\xf0;\x8f\xe2#\xefz-K\xaa9\xc9\x96\x8d`i\
+\xec\x9aO\xdc\xd9\x87\xe7\xc4\\\xf9\xe6K\xf9\xfbO\xbe\t\xd1\x85*f2\xfa\xe7\
+\xef\xac\xe3\x8f?\xf7%\xf6\xa4\r\xe4\xe8\xcb\xb8\xe9\x9e\x8d<\xf0xL\xdd\x87U\
+\x87\xc0\xeb_w\x1e\xdd\xdeN\x9c\xa61\xe5c\xb6\xc7H3 \x9f\xdf\x8bC\xc6\xdb\
+\xdfr\x06\x0f\xad]\x0b\x98\x05\xa1kI\xfe\xc3\x89\xe9y\xd5\xe1y\xbc\x9e\xc1\
+\x0e\xac<\xb8Z\x14\x0c\x03\xe1\x90\x0bC+\xbe\xea\xbaG\xb9\xee\xf6\x07\xe8[\
+\xc3\xb0\xe8e\xd0\\B\x98\x9a\nf\x8d4\xe8v\xf6"\xe2\x19\xecd/V\x7f\x07Vw;\xb5\
+d\x92\xe5\xb5\x94\x93W\x8dp\xf1)\x87\xf3\xdasO\xe5\xc2W\x9cL\xe0ht\xd4+\xf0\
+\x1bez\xee(\x13\xb5)\x05\xc2\xf2\n\x96\x8dG\x8a\t\x8a\xb2<\x1f-\npY\x99\x82#\
+\xb5B\xeb\x1c\x8d\x01K\xb7?g\nAN\x89\xec\x14\xf1\x9d\xc2Pc\x89\x0b\xacJJ\xd0\
+\x8a<\t!K\x0b\xb8\xa4h\xea\xe8\x04\x938h\x8eR^\x14\x90D\x1a\x9d;\xd2\xc2*\
+\xf7x*7\x0f\xa1q\x04E\x01d\x7f+G\x15\xaf+\xf2"n\xf4g\xa5x\xff\xe7\x86\'-\xd2\
+4C+\x85\xeb\x1a\xd3\xc9,1\x1e\xa1\x17\x9e\x7f\x0eI\x7f\x1e\x9d\x85Pu\x91\x81\
+\x03*%\x8b"\xfcj\reU\xf8\xd1\xed\xeb\xe8h\xd3\x92\xbc\xe5\xde\xfb\x99\x8d\
+\x13R!\xb1\x9c\x8a\xb1x\xf7\xeaX\xb9"\xeb\xcfp\xd6i\xc7\xb2l\xccp\x05L\xaf\
+\xd3\xb0\xe7r\xcbB\x14\xb4\xe4\x86\xe7b\xab\x04;/|\xcb\xc6\x97 \xa4\r\xbeOu\
+\xa4\xc5P \xc8\xba\xbb\xa9\xa8)D\xe7Y\x9a\xbaC\x9d\x0eu\xe6\xa9\xd2\xa6\xae\
+\xe7\xa8\xeb\x0eu\xda\xd4u\x87\x86j\x13\xe4s4\x1b\x9aa9O#\x9ba\xb4\x922,f\
+\x19\x96\x1dto7uO\x19m\rf\'B\x92@\x9c\xa1\x94Dk\x81\xd0\x12\xa1K\x07\xda\x8c\
+86\xda\x9a\\\xed?\xaf\xff\xdeX\x98\x8dQ\x02\xd9`V\xe9333\x07y\xf6^\xfcQ\xab\
+\xd5\x0e\xc0X\xb4\xd6\x84aH\x1c\xc7\x07\x14\xcf\xff\xecXH7.w7\xa5\xb6\x04\
+\xf8)Q\xe2\x8b5^\\$F\x83my\x90jl[\x10`\x04\xb0\xef\xbfl5\x87\xad\xfa\x10\x9f\
+\xfb\xd2U\xac\x7fv\x0f\x157\xc1\xad(>\xf2\xa1\xf7s\xd1\x89f\x9an\xd5\xa0\x13\
+\xc1U7o\xe4\xcb\xdf\xff1\x93\xaaFu\xd1J\xa6\xda\x1d\x86\x86W\xf2\xf1O\x7f\
+\x89\xaf\xfc\xed\x87\x18o\xc1[\xdev\x02\xffr\xcbOp\xbc\x16\xb3{\x12Ds\x14\
+\x19v\xa8\xe8>\xaf<\xe7|,\t\x9d\xb9Y\xd3V\x93\x05\xe8\x9d\xa7\xc5\xe4dv\x15\
+\x0c~*\x87\xa9 \x83\xdb\xa7`\xf9 \xf2b\x07RF}\xe6\xd8\x96e\x96\xb5\xd2\xec>f\
+b\xb8\xf6\xf6\xb5\xc4\xce\x10:\xf3\x8c\xfdH\xda+\x0c\x9d\x12|K\xd0\xdb\xba\
+\x91\xa3\x8e\\\xc1\x99\xc7\x1f\xc9\tG\xae\xe0\xb4c\x17\xb3|\xd4\xbcj\x8e\x01\
+\xf8C`{\x1b6\xac\x7f\x9c\xbd\x93]\xb4\x06iK\xa2$\x82L\x0c\xa2]\xb5e\x17\x8c%\
+\x1b]\x98w/Z4\xca\xb3\xb3s\xa84G\xb86R\x98\xf8P\x1b\xb0]\x9fT\xc7\xdcy\xefC\
+\x9cv\xf8\xc9\xc6]F\xa5xv\xc9\x922\xad\x15\xac\x92@\xa1\xc8\x92\x08\xa4\x85c\
+I\x04Y\xd1o/\x13\x06m@\x1f\xc0\xd0\xcb\x04d\xd2\x06]d\xc3k\xb3\xfbAK\xc3\x13\
+\xd39y\x9a\x19]t\xc9\xcc(c\x00K\xbc)\xcf\x8cD\xff I\xd5\xb6\xa4\xd8\xf9\x80@\
+R\xf5\r\x05\xf6\xc2\xf3\x0f\xe3K\xdf\t\xd8\xdd\xef \\\xdb0\xf4\xe2\x18\xd2\
+\x04\x19T\xd1N\x95\x9f<\xb8\x99\xb7\xb7\r4v\xdb\xbaG\xa1V#\xd5\x1e\x96\xb6AZ\
+x\xaeE\xda\xd9M\xd3\xcex\xcdE\xa7\x19\xbcH\x14\x1fA\xba -\xb4m\xca\xb3\r\x8c\
+7+\xf8dDQ\x88\x10\x0eZK\xf4\xf4\x1chE/O\xe8\xcd\xefbi]s\xe6\xc9\x87\xd1oO\
+\x92ZF9.\xb5\xb9\x04-e(\xbcJh\xf2\xa23\xd8\r\xfb\xd4\xea\x15:\xb3sx\r\x87\
+\xa5c\xe3D\xed>J\xd9dV\x9d\xaa/\xf0H\xd0\xca\xc5w0\xa0\x97v8\xea\xf0c\xd8\
+\xb2m\x92\\X\x85\xc9\xa5\xc0\xb1$\xbbv\xed`\xf5\xaa\xa5X\xce\xcf\x9f>J\x10\
+\xbbT\xa7\xf7\xfb}l\xdb\xa6V\xab\xf1\xd4SO\x1d\xd4y{)\x8cV\xab5p\xe3-\xdd|\
+\x93$a\xdf\xbe}\x1c}\xf4\xd1/\xf8\xef\xfc\xac<w!\x04{\xf7\xee\x1dX\x97\x94\
+\x11\xbf\xe5\xf8\xff\x0b\x08\x90\x84\x19\xaeo\x13v\xe7\xc0\x914}\x93\xd0w\
+\xea\n\x8bo~\xe6m|\xe1\xab\xf7\xb1\xee\xb1\r\xfc\xc9\'\xde\xcfD\xbd\xc8\x7f\
+\xc60V\xbe|\xf5\x03|\xfe\xaa\xeb\x98\xd1\r\x96\x1d}\x12[\xefz\x10\x0eY\xc1|\
+\n\xbb\xfb>\x9f\xfd\xc2]\xfc_\x7fx6\xad*\xbc\xf3\xed\x17\xf1\x85\xaf]\x87\
+\xe7\xae\xc4\x8aS\xd2\xfe$#\x8d\n\xbf\xfa\xf6S\xf9\xf6\xbf=\xc0\x91\x87\x1d\
+\xbe\x7f\xd70\xd0\x0c\x94w\xfa\x81\x13\xd4~\x15\xfa\xcfba\x99I\xa8\xdc\xc3X\
+\xd2\x022\xf2L\x83g\xb8\xfb\x1b6\xf7y|\xeb\x1e\xfcek\x08#\xdfT\xcd(\x86\x86\
+\x03"AF\x1dN\\\xb3\x82\xdf|\xd7\xe5\x9cq\x94\xc7\x98G\x11@k\x9e&0,\xe4\x1e@\
+\x13\x84N\xcd2])\x84\xd6\xa6XY6Bhr\x95\r&n\xa3\xc1\xd6h\x04G\x1f\xb1\x92\xb5\
+O\xde[\xf4\xbcm\x84e\x91%\t\xc2r\x10\x05u\xf5\xee\x07\x1f\xe5}W\x9e\xcc\x98\
+\x05X\x15r\x15a\xc9\xc2\x1a$\xc5\xf8e)\r\x8e\x8d\xe5\x18\'*\x05\xe4Y\x8c[\
+\x86\x91\xe7\x8a\x05ev\xc0P\xcb\x05\xe4\xc2\xe8\xc2\x85\xb0\n\xfe\xad\x1e\
+\x1cS\x95C?I\x07\x18\x8ckyf\x92\x97\x800g*\xd3\x07\xab\xc7Q(\x9d\xe3X\x16\
+\x02M\x1e\'d*\xc3\n|\xd2\x14\x964\xe1\xc2W\x9c\xc1\xbf\xddx\x1fi\x16\x91k\
+\xcb\x08\xec,\x9fTkre\xb3m&\xe3\xfe\x8d\xa0\x02\xd8:9Km|%:\xb2\xc82\tJb+E\
+\x12v9n\xcdrN;\xaa\xb0\x9d\xb7\x8b\xe3"]r%\x11\xd6~\xdc\xf0\xd0%c\x8cT\x1c\
+\xb6G}\xa4[\'\xb7\\#\x0er$"\x9f\xa7\xde\xac\xf0\xcas\x8e\xe7O>p\x02J\x99\xf4\
+\xc9\xe7\xc1\x90\x03\xfc-\xc7,2\x1cJ\xc2A\x11\xd9\x8e1\x80\xf0<\xb3\x10\xe9%\
+0\xe6\x9aV\xa5\x00H\x14:\x0e\xd1\xdaBJ\x07\xa9%\xe4&>\xd5um\x1e\xdf\xb8\x81\
+\xd5\x87N\x14\xf6\x08\xff\xf1(\x0b\xc8\xcf\x9a\xec6o\xde|Pg\xef\xa50\x86\x87\
+\x87\x07\x16\xf6%y\xc0u]6o\xde\xccy\xe7\x9d\xf7\x82\xff\xce\xc2\xcc\xf4\x85\
+\xe3\xa9\xa7\x9e\x1a(\xedKQdY\x90_\n\x05\xe4Ena\x81\xed\x18V\x8d_o\xe0\xfa\
+\x1e\xdd\xde\x0c2KYQ\x81\xc5)\xfc\xc1\xe5g\xf0\xa3\xcf\xbd\x9f\xc3d\xcc(f"m\
+\xe7\xf0\xc9/\xae\xe5c_\xb8\x86iw1b\xd1\xa1l}\xfa9\x9c\xa3\x8e\xc3\xad\xd6H\
+\x94GO\x8cs\xf7\xc6}|\xef\xb6\x19\xa6\x80w]v4\xa7\x1f9\xc2\xd2 GM\xef\xc1\
+\x939g\x9f\xf9r\x16\xb7\xe0\xbe{\xeea\xc5\x8a\x15@\xa9;\xd3\xc6W\xbb\x98\xee\
+J\x1c\xe4\xc0Qr\x96\x8b\xa1\x17~U\x83\x9b\xda\xfcJ\xa2\n\xean\xa2\xe1\x8e\
+\xb5\x8f\xa0\xbc\x06a\x86\x91A[\n\x1cp\xf2.\r\xd9\xa7\x92L\xf1;\xef}#\x97\
+\x9c\xe0q\x88g\x94\xd8\xd5<\xc4K\xba4\xed\xd4(\xb13\x901\xcc\x84\xb0{r\xca\
+\xb4B2Hb\x058\x08\xd7E\x93\x93\x17\x1c}\x89Y\xa1z\xc5\xde\xe8\xf4\xe3VRu\x13\
+l\xdbxpd\x08\x90\x0e\x99\x15\x90X\x15BQa\xf3\xee9\xae\xbe\xf1Y&3cA\x12I\x9f\
+\x8c\x80\\V\x08\xb5G&\x1cB\xcbe\x1e\xc9\xa6\xc9\x98>\xa6\xa8M\xcd\xa7\x803\
+\x98\xe8\x7f\xd6(\xf5\x1aJ\x83\xc2.\x8e\xb7A#4\x92D\t\xfa\x99fO\xdf,\x16\x12\
+\x018\xc6nE\x0b\xcfP\xac-\xffg\x9e\x99\x17:\x04FDX\xc6\xb6Z\xb6Dhcs\xe2\x16\
+\x98\xc0\xe5\xaf9\x89\x91\x9aK\xc52\xdeX\xd2\x16\xb8\x81G\x92\xa4\xa4\xc2Cy\
+\x8b\xb8\xee\x8e\xf5\\s\xf3CP\x1f\xa2\xa7\x058E~\x8a\x12\xe88\xa6\xee\x08.\
+\xbb\xf0\xe5\x04@\xd56\x01_\x86\x92l\xd6oR\xec/ \x87\xaf\xa8\xb1|l\x18\x9f\
+\x1cO\x08\xc3\x0e\xc9\x81X\xa1{1\x9dN\x8f\xc7\x9f|\x82]s\xd0\x90P\xc3(\xf4\
+\x87(T\xfa\xecW\xfc\xd7\x8a\xdfU\x80\x99}\x1a\x9d\x99\xfbG\xe60\xec\x99\xdf{\
+`p\xb4\x18\x92\xa8(\xf4\xb6\x8d\xa849\xff\xbc\x0bp-\x0f[\x1a,\xc4*4\xff\x0f<\
+p\x1f\xbd\xfe<\xfb\xedb\xfe\x83c\\L\x8a%\x13\xa9\x04\xd37m\xda\xc4\xce\x9d;\
+\x7f\xe1s\xf7R\x19K\x96,\x19d\x9e\xe4yN\x9e\xe7T*\x15\x1ey\xe4\x11\xf6\xec\
+\xd9\xf3\x82\xfeFY<\x9eO\x87\x0e\xc3\x90\x07\x1f|p`\xa2\xb8\xd0\x82\xa5\xb4\
+\x84\x7f\xb1\xc7\x8b^@J\xf7\xd1~\x9a\x12\xab\x84z5\xc0\xb7\x15"\xcd\xf1$\x0c\
+\xf91V\x16\xb2\xa8\x12R#Dh\xf8\xb3\xbf\xf8*_\xf8\x97o\xb3\xe2\xf83\xc9\x82!\
+\xe20\x83f\x8bTi\x92\xed\xbb\xa9\rO\xd0\xcd\x03v\xcc)>\xf7\xc5\xff\xc3T\xc7\
+\xac\xde?\xf0\xce7\x11\xee\xdb\xce\xb0\'\x18\x0el\xde\xf8\x9a\x93yn7XN\x80\
+\xd6\x82T\x1b\xc3T\xa3:gP\x14\xc4O5\xae\x8aq\x00\x85Q\x0c\x8a\x07P\x18\xfaA\
+\x96\x1a5\x89\x90\xb6iSH\xd8\xba}\'\x13\xcbWA\x94\xc2\xdc\x1cR\x19\xb0\\\xc4\
+}\\\x9d`%\xf3\xbc\xe2X\xbfXJj\x84\xea\r\xc0c#\xca0}d\xdf\x85m;a>\xce\xa86G\
+\xb0\xdd:yV\x90\xa5\xa5 \r{\x08\xa9\xcd\xaa\x05p\x84\xc4%\xa5\x06\x1c\xbb\
+\x12Znf\xdaRJ\x99\xb7-l\xb4\x1d\x10\xe2\x11\xd9U\xda\x99\xcf\xdf|\xe5*\xee{2\
+a\x1e\x98\xca\x8c\xf2\xb9\x07\x84\x9e\xc5<\xd0\x06\xb6\xf6\xe1\xaa\x9b\xd7\
+\xf2\xe8.\xf3o^\xabF\x8cM?\xca\rsJ\x98\xfd\xd8B\xd2\x9ea\xe8e\xe4\x85\r\xc9\
+\x01\\pa\x91h\x8b~n\xb1}\n&#\xb3\x92\xee\x03\xf3Z\xd2\xc3\x144%\x0e\x1e\x05\
+\x11\x08\xc2~\x1f\x95\xa6 -\xdc\xc0#\x8f\x15~qo\x1e\xb5\x0c\x1a\x9e\xa6\xe6(\
+,\x15A\x16\x93\xe7)\xc4\t\xda\xa9"\xeb\xe3\xdc\xf5\xd0&\xee^\xfb(Ak1:\xca\
+\xc0\xf5\xc0rp\xa4\x80\xb8\xc7H\xc5\xe2\xbc3\x16\x17\x84\x04c\xa9\xa2\n,`0g\
+\xe4\xa6`\x8dVa\xc9H\x83\x8a\x05\xae\xd4\xa6\xba\xd8\x8e\xb9QF\xc6\xb1[\xa3\
+\xac{\xec\tn\xb9{3\xf3\x05n\xe1.x8\xfb\x8f\xe2\xe0Z\xdd=\x03\x7f\xf2\xf1?\
+\xe3\xaa\xabo\'\x07\xdaaa<P\xcc\xfd\x01\xd0\xf0 (l\xdc\xd3\xae\xc1\xb6\x96\
+\xacX\x89\xeb\xfa8\x96\r*G\x17\x89xO>\xb9\x91N\xa7C\x9e\xff|\x90x\xe1\xaa\
+\xbat\xe1\x8d\xa2\x88\x87\x1f~x\x90\x9f\xfe\xff\xe6\xd1h4\x18\x1e\x1e\x1e\
+\xb8\xfb\x96\x9a\x8f\xa7\x9f~\xfa\x05\xfb}\xfd\xac\xddG\xa9\xea\x7f\xe2\x89\
+\'\xe8\xf5z\xa4iz\x80\xcd\xfd\xbf\xb7c\xf9\xaf\x1e/j\x01\xd1\xc2\xb42\x94\
+\x04\xc7\xf1p\xa5[L\xbc\x19He&LRH\xfb\xa0S\xb4\x8a\xb9\xe5\xc6\x9bx\xf2\xc9\
+\x8d\xd8\xb6M\xa7\xd3)\xda\xf0\xcaP\xae\xd2\x0c\xc6\xc6\xe9v#\xf0j\xe0\xd7\
+\xc0\xab\xf2\xf9\x7f\xfaW\xa6\x15\x9c|\xd8\x10\x17\x9ew\x16\x9e\n9\xf5\xd8\
+\xd5\x1c\xb7\x12\x1eyd\x9a\xdd{&Y\xba|%\n\xb3!\xc8\xe2\xd8L\xaa\xff\x91\xd5\
+\xf3\x0b<w\x8e\xe3\x90\xe5&\xd2\xb6\\\xab\xf9\xbeon\x1e)!\xf0\tlA\xdc\xebb\
+\x0bp\x1c\x0ba\xd9\xcc\xc6\xa6C\x10iA.\xab`\x05\xe4\xca\x07\xa7\n\xd2&\xca!\
+\x14\xf0\xe5o\xfe\x10\x194\xd85\xdd#U\x8e\xd1A\xb8UtlZ1\xa0H\xe3h@\x9b\x8ezS\
+\x04(\x16\xd7\xe0M\xaf>\xc3`/:\xc3\xf2+f\x92\x8a2r\xb7Nf5\x88\xbcafU\x9dO\
+\xfd\xe3\xb7\xf8\xcb\xaf?\xc2\x93\x93\xa6`\xb4\x81\xb6\x80\x1d9<2\x05\x9f\
+\xfa\xf2}|\xe2\x8b\xdf\xe5w\xfe\xfc\x8b<\xbc\x0b\xa6\xb5)$V\xd0`\x10ld\x19\n\
+n\xa9\r\xb2\xb2\x14\x91\x9a\x14\xb8$\xc9L\xb1\xf4+f\'\x92C.lb\x19\xf0\xd5\
+\xab\xefc\xdb\x9c)\\s@\xaf\x10\xdc\xf5\x8b\xf7\x91=\xef\x12\xfe\xcf\xdfT\x92\
+\xa0RA:\xa5j^\xe3\xba\x12\x87\xfdN\xc8\xef~\xcb\xeb\xb0\xd2y|\x91\xe2;\x90\
+\xf7\xe6\xa9,^\x8c\xca\x15\xddT\x18M\x91\x08\x08\xfb\x19\xf6\xe88\xf4C\x98\
+\x9f\xc3\xd2\x11\xbeN8\xef\xf4\x13YR1\x97\xb4M\n*F\x06\xc1\x80\xf6\xde\x9b\
+\xef\xe0Y\xe6\xb8H\x05\xef~\xdb\x9bH\xe7g\xa9y\x96\x896\xa8\x04\x06\x1f\xeb\
+\xc7\xf8\xad\t\x08F\xf8\xe2\xb7\xbe\xcf\xf7o\xddD\xbf\xe8&&\x14\x14tL\xb1\
+\x8d\x8a\xe3\xb3y\x06\xfe\xe83_e\xd3T\xc6?|\xed:>\xfa\xd9\xef\xd1\xb1a\n\x93\
+KU\x1aBZ\x14\xf0R\x9e\xe2\xd4k  \x89b^\xf7\xba\xd7\xb2w\xdfn@Q\x0b|\x84\xca\
+\xa9\xd7\x9b|\xe63\x9f\xc5\xb2\x1c\xe2\xf8\xa7M\xfdJf\x120\x08dZxn6m\xda\xc4\
+\xcd7\xdf\xfcKYA\x97\xb9\x1d\xa5hpaf\xf8\x7f\x95c\xed\xb9\xe7\x9e;\xb0&\x19\
+\x1b\x1bcff\x86\xe5\xcb\x97\xf3\xf9\xcf\x7f\x9e-[\xb6\x0c\x9e\xb7\x905\xb5\
+\xf0\xbd\x95\xe6\x8b\xe5\xf7`l\xe0\xbf\xf2\x95\xaf\x10\x86\xe1@y^\xd2y\xc30|\
+I\xec>\xe0%\x80\x81\xa8\xe2~/\x15\xdb\xa2\xe0\xfc\x98_\n\x10\x85\x99\xa1L\
+\xd1\xd8\x9c\x7f\xfe\xf9\x04\xa3\x87s\xd5\xb5\x0fr\xc3\xdd\xebQ\xd5:vc1\xda\
+\xf3\xc9\xa3\xc2\xab"\xa8\xe2)\x9b\xb4\x13\xd3\xed\xf6\xd8\xf0\xd8n\xee\xbc\
+\xf7\t\xdep\xd6Q\xbc\xef\xfdg\xb1\xfe\xa1Gy\xc7\xe5\xaf"O\xe1\xbe\xfb\xeeC\t\
+\x8f\xf1\xa5-\xac\x02+p\xbd*\xe8\x10\x15v\x91\x95\x1a\x14\xda\xd0\x9f*\'\x07\
+nG\x8a\x9f\x8b\x1e~\xf15\xc92<\xdb&\x0eC\xf0\xab&\xbc\xc7\x96\xa4\xfd\xb6iBg\
+)\xbd^\x17\xe9\xbb\xa4*\xa5\x13*jN\x83\xbb\xd6u8\xf7\xd4\x06\xe36L\x03\x81\
+\xb0\x91\x81m2\x9e$<\xb5\x13\xfe\xe6k\xd7\xf0\xd0\x13\xdb\xc9T\x9dj}\x04\xcb\
+\xaa\xd1\t\xe7\xa1\xdf\x03W!-\xb0m\x89.\xdcg\xd1P\xb3%\xd0g\xd8\xaf\xf1\xeaW\
+\x1c\xc3\xd7n\xb9\x1f\xc7\xab1\xf7\xe4FXq\x04\x95\xe1\t\xfa\x9b\x9e\x81f\x80\
+\xf6[\xc4\xbd\x94\xa7v\xcf\xb3\xed\x9a[\xb9\xfa\x86\xdbX\xd4j0:<B=h\xa2\xdd\
+\x80uOmc\xef\\\x9b\x91\x97\x9d\xc6\xb3s\x1d\xde\xff\x91\xcf\xf1/\x9f\xffm*Cf\
+\x1e\x9d\x08\\C\xd3\xc5L4Il\xf4\x80\xc3U\x1b\xb2\x1ei\xdc\xc7m\x8c\x90[\x1ey\
+\xbf_\xd8\x14\xb8\xd8\xfe\x10y*\xb9\xe6G\xb7\x13E}N?\xee0\x16\rU\xa8\xfa\x1e\
+\xd3\xd3\xd3l\xda\xfc4\xbd\xe9\xad|\xfec\xefc\xbf\xe2\xe6\x17\x1d\x12\x16^w\
+\x85\xb7\x9b\x8b\x99\x90_~\xc28\xa3U\x9b\xb8\x9b\xd1M{\xe0U\xe9w\xe7\x01\x8d\
+\xd6\x92\x04a\xcc\xcb\xb4&\x8b\x0b\xab`\x17|b|\x1dr\xdei\xc7\xe1\x02\xbeP\
+\x86\xc2 -\xc3\xc0\xb2$Z\xe74k>\xe8\xcc\xa4\x1cJ\x9bU\xcb\x03.\xbb\xe4<\xfe\
+\xf7u\xf7\xe3L\x1cK:\xbd\xdbh|\xb2\x8c\xee\x8eY\xa8\x8d\x90x)\xff\xf0\xf5\
+\x1f\xd0\xde}\x1a\x97\xbd\xf2LV.\xb3\x8d\xe7\x99cv\xb9\xd39\xfc\xe8\xb6\xdd\
+\\}\xc3m\xac\x7fb;\x9e\xdf\xa0\xd2\x1c\xe7\xab?\xb8\x9bM\x93!\xbf\xf3\x1boc\
+\xf5\x18\xac\xf2\xcc\xaeH\xe7f7h\xd9\x05\xe1\x03\x89[\xf7x\xf9Ygr\xd3m\xb7\
+\xb0e\xeb\xb38\x9eE\x1c\xf5\xa9\xd7\x03z\x8e\xe4w?\xf2{|\xfc\x13\x7f>02\xcc\
+\xb2\xec\x80\x08\xda2\x9c\xa9\xdb\xed\x0e\xa2]\x1f}\xf4Q\xbe\xfb\xdd\xef\xd2\
+\xef\xf7\xd1Z\xbfd\xf4\x0c\xbf\xe8\x98\x9b\x9b\xe3\xd2K/\xe5\xdb\xdf\xfe\xf6\
+ \x93\xbd\xcc\xf0\xf0}\x9fk\xae\xb9\x867\xbd\xe9M\xac\\\xb9\x12\xa5\xd4 \xe4\
+j\xe1\xe7\xaeT*\x83\xdd\x8b\xef\xfbDQ\xc4\xcd7\xdf\xcc\xc3\x0f?|@\x8e:0\xf0\
+\xd6*\xdbe/\xf6\xf1{\xd1\x0bHy\xcb\xda\x85L\xaf0B*\xa87&\xbc)\xb3 \xc3\xf4\
+\xbd\x1b\x1e\\t\xcaJ\x96\x04U\x8e\x1c\xf7\xf9\xc6Mw\xa0\xdc&\xdb\xc39Pu\x18\
+\x9a\x80\xf9\x0e\xf1\xec\x14\x8b\xc6}\xba\x93\x11\xb1\x05\x9f\xff\xca\xb5\
+\x1c}\xfcQ\xac\x1e\x85\xf7|\xe0]\x9cz8\xeci\xc3#\xeb\xd6\x11*\x9b\xdd{s\x8eX\
+bQ\x1b\xec\xfde\xd1\xa8\x97/l\xb71\xe0\xcf\x9b\xff[\x82\xc5J)\x04\x02\xdfu\
+\x083\x85\xedHN9\xe6p\xae\xbd\xeb1\xdc\xe6\x04Xu\xfaS\x19~s\x94(t\xe9\xf541\
+\x0e\x9f\xfe\xd25\xdc\xfd\xf01\xbc\xe3\xf2S8d\xc2P\x96\xdb1<\xb9\x19\xee\x7f\
+\xf01\xfe\xef\xf6\xde=^\xaf\xaa>\xf0\xfe\xee\xb5o\xcf\xf5\xdcON\x12B\x12!\
+\x01\x02\x89\x04\x10\n\xcc\x0b\xa8\xa5(W\xc1)h\xed\x14\xd1\xd6\xa9\xb5\xce\
+\x0c\xf3\xfavF{\x1bk-\xe3\xdbw\xea\xa5/N;~\xc6Z[\xdb*/\xbdX\x10\xc4(UD\x054\
+\x02\x12/\x90\x90\x0b\xc4\x84\xe4$\xe7\xf2\xdc\xf7u\xbd\x7f\xac\xbd\xf6s\xc9\
+\xc9\x85\x13\xe7\x93dX\xdf\xcf\xe7|\xcem?{\xaf\xbd\x9eg\xff~k\xfd\xae\xdf\
+\xdd\xbc\x85\xad{\xa7i\xb9\x05\x1c\xbfB\xab\x15b\x8bPI\x10\xdb\x05/!\x0e"\
+\x84+\xb2]P66aA\xd4\xc4sJlX%\xb8\xfd\x96k\xf8\xf8\xdf>\xc8\xd0\xea\xa5\xb4\
+\x92\x0e\xad\xad?\x86\xa9\xd3 \xe8d\xed\x8a+H\xbfH\x9c\xb6\x99\xee4\xa8\x1d\
+\x88\xd8S\xab\x11\x87s\xcc6:\xc8\x10\xac\xa5K\x99Ml\x82F\x83\xd1\xea(w\xfe\
+\xee_q\xf7\x1f\xdd\xce\xd9\xc3j5\xec\xc7\x10\xb5\xda\x94\xab\x1e\x0e\x12\xd7\
+\xb2Y\xb9\xd4b\xbc\xec\x91\x14|\xa6;M\xd5\x13\xc3\xf6\x95]\x0eI\x1c\xc4\xc4\
+\xa9`hj5\x8f\xff\xf0\x05\x1e}\xfc\xfbxV\xccp\xb9\xacJ\\{.\xe7\x9d^>N\xd5\xa1\
+Mg\xa8wKG\xd2\x91bI\x81c\xa9v5\xab&\xe05\x1b\xce\xe4\xa5G\x9f\xa6P\xa8\xd0\
+\x89C\xb5\xb0\xb1\x1de\xf3L\x048EUm\xa0\x15\x82m\xe3;\x11vPc\xe5\x92\x02\xd7\
+\\^ nA\xa1$\xba\xd7\xb5$R\xa6$I\xa8\xfc\x80Rb\xdb\x02a\xa92\xfe\xff\xe6\xd6_\
+\xe0\x1bO>\xcb\xdex\x9ezQ\xa8\x90g\xcf\x85\xf2\x18\xd4\xa690\xd3!\x19\x9a\
+\xe0O\xefy\x84\xbf\xfc\xe27X\xb5j%k\xcf^\x03\x8e\xc7s;v\xb3c\xcf\x0c\x8d\xc8\
+\xa1\x169H\x963z\xc6\xab\xd9?\xbd\x87\xd2\x19\x93ly\xa9\xc5/\xfe\xda\x87\xf8\
+O\xbfq+o\xbdp\x92\xb3&<\x8a\x85\x02\xc2\xca\x84\x91-\x88;\x11\x8e\xef"\x85\
+\xe4\xda\xeb\xae\xe1\x0b_\xf8\x02a\x1c051\xca\xbe\xe9\x97h5m\xe28\xe1\xbf\
+\xfc\x97?\xe0\xc6\x1bo\xe4\x8a+\xae\xc0u\xdd<\xd3\xbcT*\xe5\x8a\xc4q\x1c\\\
+\xd7\xe5\x9e{\xee\xe1\xc1\x07\x1f\xc4\xb2,\xea\xf5z_\x81\xc1S\x15\xdd\x07\
+\xe5\x97\x7f\xf9\x97\xf9\xe8G?\xca\xf2\xe5\xcb\x01\x95q?77\xc7\x03\x0f<\x00\
+\xc0u\xd7]\xc7Yg\x9dE\xb1X\xa4\x9dU0.\x97\xcb4\x1a\r\xca\xe5r^\xa2\xe4\xa5\
+\x97^\xe2\xef\xff\xfe\xef\xd9\xb4iS^[L\xfb;\xe28\xce[\xed\xc2\xc9\x91Lx\xc2\
+\x15Hwe/\xbb\x02\x1b\x1b\x84 \xb1P\xdd\xf6\xe86q\xd2\x99\xc2\xff\xc7y\x93\
+\xfc\xdcy7p\xfa\xd2a>\xf6\xf9\xafA\xa3\x89\x7f\xe6%\x04\xf5:\x04-\xdc\xf1\
+\x02\xcd\xc6~\x8a\xc52al\xd3n\x16\xf9\xc0\x07\xff\x89\xbf\xf9\x93\x9by\xc35e\
+<\xe0\x91\xaf|\x13i\xd9X^\x85\xaf|\xfdQ.{\xf5U$\x0e\xb4CI\xd1\xb5\x11\xa52\
+\x0b\xec;z\x18\xa8\xc34\x10\xf8+\x01\xdf\xf3\x89\xc2\x16\xaek#\xc2\x10\xc7-p\
+\xd5%k\x99\xfck\xc1\xc1\xfaA\xd2\x92\x0f\x96E\xab\x1d(\xa7\xaa7\x8cW-\xb2{\
+\xdb\x8f\xf8r\xfc<\xff\xf8\xe5\xef\xb0t|\x08\x0b\x15\xc6\xe7\x95\xaa\xbc\xf8\
+\xd3}\xd4\xe7\x9b\x8c\xad\\\x81\x8c\x1bXB"[-b\xdbA\x94<R\x0b\x84m\x91\x92\
+\xa8\xe2u\x9e\x9b\x85\xc2\n\xb5:n7\x91I\r\xbf0\xc2\xaf\xdd\xb4\x82g~|&\xf7m\
+\xfa6KWo`o\xd9A\xbe\xf4\x02\xfe\xea3\t\x1a\xf3`\xfb\xa4VJd[\xe0\xdbD\xc2\xa6\
+\xe3\xf8\xa4\x08d)\xa2\xbcb\x8af\xbdA\xd0\xa8#\xaaS\xcc\x07\xb3\x04{\xe6\xf8\
+\xfd\x0f\x7f\x8e\x8f\xfe\xee\xbfa\xed0\x08\xc7U\xe1\xda@\xd9\xb7\tPmVN\x9b\
+\xa8\xb0g\xba\x8e\x10.\xa9\xeb(\xefn\x1a\xd2\xad{eQ\xab\x85X\x0ex\xee(\x85\
+\x92OG\x08\xf6\x1d\xdc\x83\xe3\xc0\xea\xe5\xfeq\xa4\x11v#\xc2\xd4\xeeW\xc7\
+\xcdi\x7fW\xb6\x0b\xc9~\xbd\xfe\xe7\xaf\xe4_\x1e{\x12\xe1\xc0\\\xad\x81X2N\
+\x1aHesJS\x15\x91\xd6Qy,\x96\r\x04\rdp\x90+/\xbdB%\x05\xca\xac\x82\xb0\x95=r\
+\x8eM\x9c\xa4\xb8\xae\xa3\xa2\xcb\x12\xd4{c\xa9\\\x8c\xb3O\x87w\xbf\xfdf~\
+\xffc\x9fatRPK\x03\xc2\xf9\x04\xfc\xb22\xcf\n\x9bzlQ\x1c9\x93\x83a\x8b\xb9\
+\xbd\x01?\x9e}\x8e\x18\x8bz+ I]p*X\xd5%P\xb0xiO\x1dB\x9b\x96\')\x95\xca\x14&\
+O\xe3\xbf\x7f\xea\xd3\xac\xbe\xe3\xf5\xac\xfe\x85\x8b)\x17K\xc4i\x82-\\\xe5\
+\xces\x04\x08\x18\x1d\xafr\xd5UW\xf0\xedo?\xc2\xd3O?M\xb9X\xa0\xe0\x15ISh\
+\xd4;\x1c<\xf0\x1c\x9f\xfe\xf4\xa7y\xf0\xc1\x07Y\xbbv-\x17\\p\x01g\x9ey&\xb5\
+Z\x8d}\xfb\xf6Q\xab\xd5x\xf6\xd9g\xb9\xf7\xde{)\x97\xcb,[\xb6\x8c\xe7\x9e{\
+\x8eB\xa1\xc0\xe4\xe4d^\n\xe4TEGD]t\xd1E\\\x7f\xfd\xf5<\xf8\xe0\x83\xacX\xb1\
+\x82\x83\x07\x0f\xe6m~\xbf\xf9\xcdo\xf2\xdd\xef~\x97\xd7\xbe\xf6\xb5\\{\xed\
+\xb5y\xc0\x0e\xa8|\x18}\x8e/~\xf1\x8b<\xfc\xf0\xc3\x84a\xc8\xf8\xf88sss\x04A\
+@\xab\xd5\xca\x9be\t!\x08\x82\xe0\xa4H"\x84\x13\xac@\xba\xe1\x87i\x16\x92\
+\xe3\x91\x97\x9d\xb0t\t\re\xcf\x055X\x1fpc\xb0\xec6\x1ep\xfb\xf5W\x80]\xe1O\
+\xbe\xf8\x04\xdbv\xfd\x08\x96\xaeQ\xe59\xe6_\xc0\xb1c\x82\xc8\xc1q&\xf1\n\
+\x13\xfc\xe4\xa9\xad|\xe1\x81\x03\xfc\xfau\x13\xcc\x1c\x88x\xf8\xab\x9b\x10\
+\xde\x18\x95\xa1I\x9e\xfa\xc1OhEW\x91:\xca\x95Rt\xac,s\x1c\x16V"\x83E\xfc2\
+\x83vV\xe0^\xfb\xe0Ue\x08\x0bd\x8c\xef;\x04\xcd\x98\x95\xe3\x0e\xaf\xbbh\x1d\
+\x7f\xf7\xd5\xefc\xdbe\x9c\xa1Q\xe2N\x0b\xbc\xa22L;C0\xb6\x8a\xa6,P\x1c\xaf\
+\xb0\xeb\xe0\xb4j\xcf ;\xc8N\x8d\xf2\xd4RF\'W3\xb3\xf39\xecI\x1f\xd9\xa9\xe1\
+\x15\x96Q\xf4\xcat\x92\x80 \x0eH\x9d6\x8e\x0b\xadz[\xe5\x81\xe8\x1d\x12.\x14\
+\xcbX\x96\x8b\x83\x8a\xde\xf9\x9d_}=\xb2v\x80\x87\x9fx\x9a\xf1\xc9\xb54\xc7\
+\xca\xb4\x0f\xeeQa\xcc\x9e\r\xad\x0eI\xdc\x01G\x90X\x92\xa0\x19\x82]@x\xc34\
+\x0f\xcc*\xe3ye\x14dH\x1aAix\x8c\x9d/N\xf3{\x7f\xf8)~\xef7\xde\xca\x85g\x0e)\
+\x93^\xaa\xdc!\x0e0T\x84[o\xbe\x9eG\xff\xe4\x0b\x0cM\x8d\xe1\x14}f\xeb\xed\
+\xac\'I\x05J>\x84\x02\xbbP\xc4\xb1%q\xa7\xc9LG\xd5\xdeM\xec*\xa5\x91q\x86\
+\xc6\'\x8fK\x81@7\xe4\xd5F7\xc4\xcd\x94\x88L\xb1\x10\xf8\x02\xa2\x04.{u\x81\
+\x8d\xe7\x9d\xcd#?\xdc\r\xb6\xa7\x94\x86\xeeD\x15\xc6\x08J\xa4\x96\x03\x96\
+\x85\x88\x03\xd2`\x8e\xb1\n\\\xff\xf3\xafQ\xe5\xe8\x0b\xfa\x03a\x83P\xbe\x9b\
+T\xa8m\xa1L$\x16\x02)SRK\x90$1.\x0e\xb7]\xbd\x82\xf9\xdaM\xfc\xf9_\xff#\xe1l\
+\xc8\x923\xd63\xd3\xaa\x13S\xc0\xabN\x10\xee?H\xdd/\xe1W\xc6\x89\xd2\x0e\xb5\
+\xf9\x19\xb0R\xec\xd1)\x84(\x936"dG\xa8\x0e`A\x02\xc3U\xbct\x9e\xd6\xfe\xdd\
+\x8c\x8d\xda\x9c\xbf\xe1\\\xce:\xf3\x0cFG\xc6\x91\x08\x9a\xed\x16\xb6+\xf1=\
+\x8fTX\xd9g7\xc5/:\xbc\xe5\xd6\x7f\x8d \xe5\x89\'\xbeG\xb9:\xcc\xd2e+\xd8\
+\xb3g\x0fCC#\x84a\xc8\xb6m\xdb\xd8\xbau+\xf7\xddw\x1f\x96eQ(\x14(\x95JLOOS(\
+\x14X\xb9r%\xfb\xf6\xedc\xc7\x8e\x1d\x0c\x0f\x0fS,\x16\xb9\xfa\xea\xab\xb9\
+\xf7\xde{\x8f\xf3\x1d<\xb1\xe8\xf0\xddj\xb5\xcaM7\xdd\xc4\xbe}\xfb\xd8\xbcy3\
+cccT*\x15\x9a\xcd&ccc$I\xc2\xa6M\x9b\xf8\xf2\x97\xbf\xcc\xc4\xc4\x04SSS\xf8\
+\xbe\xcf\xd0\xd0\x10?\xfa\xd1\x8f\x98\x9e\x9e\xce+\x07\xeb\x9d\xc6\xe4\xe4$\
+\xcb\x97/g\xf3\xe6\xcd\xb9\x89K7\x93:\x19Bx\xe1\x84Ga\xa9\xfe\x1a\x99\xad\
+\x04\xe5F\xf4U\xc66J\xb18(\xf3M\x19eN\xf0$\xb86`\xa5\xc4\xed:%\x91r\xc7\x9b.\
+\xe0\xce\xdboa\xf9\xb8\x80\xb9\x17\xa0u\x00hA\xd9\xa7-*Db\x8c\xd9Y\xc9\x92\
+\x15gr\xdf\x17\xff\x8e\x14\x18\x9bp9\xeb\xdcW3_o\xd1\x8e`\xba\xd6\xe4\x07?\
+\xec\x90\x00n\xd1\xca"\xb1\xb2]\xd1\x11\xc6\xdfk\x04\xe9\x86\xa1\xe6\xc68\
+\x92T\x85*\xb7\xa7\xa7AF\x0c\x95\x1c*\x02\xde\xf9\xd6\x1b\xb8\xfc\xfc\xb3\
+\x90\xad\x03xI\x0b\x926\xbe# Ih\xd5Z`\x97\x89:\x92f\xc7\xc2-\x8eQ\x19^\xc2\
+\xd8\xe4\n\xbc\xb1I\x9a\xf5&\xb3ss\x14\x96Nr\xeb\xcd\xd71V\xf5\xf1d\x8c\x1d\
+\x07\x888\xc2s]\x88\x02\xd28\xc6\xf5=\x84\xed\x92\xb5M\'\x0f\xaf\xb5\x04\x0e\
+)i\xad\xcdy\x13p\xf7\x1f\xdc\xc6\xcdWn\xc4\xefL3b\x87\xb0\xffE\x90\x01\xae#\
+\xc0s\xa0Z\xa52\xbe\x84\xca\xd8R(V\xb3$8\x01\xa9dl|\x1c\xa2\x90\xb4\xd5b\xf5\
+\xda\xb39\xb0m\x17\x89\x14l\xdf\xf1"[w\xbcH,\xc1\xf5J\xc4Q\xa4\\E\xa9\n\x93}\
+\xd3/\x9c\xc3\xeb\xaf\xf89\x9c\xa4\xc9\x81\xed?\xc1IZ\x0c\x8f\r\xab\xf2\'\
+\xcd:\x96\xeb\x92\xb4Z\x04\x11$\x89Gb\x97\xf1+\x13\xd8CKiE\x0e;\x7f\xba\x9f\
+\xe3}\x8c\xd2\x9e\xaf\xfc\x9d\xd4\xa1\xdc2VM\xb1B\xf5\xe9\xbc\xfe\x9a\xd7c\
+\xcb\x88R\xd1\'m\xd6U\xa9\x1b\x11\x02\x91:>u\xb0l\x1f+\x8a\xf0E\xc8\xc5\x17\
+\x9c\xc5\xba\xd5\xca\x0fn\xe9\x0cP\x84\xb2z\x01\xa9\xae/e\xbb\xa4\x96\xa0\
+\xd1\xee \x81j\xd9!\xc9\x1a\x8a\xbd\xfb\xcd\xe7\xf3oo\xfdy6\xae\x1e\xc1oO\
+\xe3\xb4\xe7\xa1Q\'j\x8502\x05\xc51\x82\x83-\xc2\xf9\x00ob9\xde\xf8R\x92\xd9\
+\x1a\xe9\xfe\x03\xaaD\x7f\x18\x80\'\x18\x9e\x1a\x87\xb9\xfd\x0c\xc9\x0e\xa7\
+\r\xbb\xac(\xa5|\xe8}\xef\xe4\xb2\x0b\xcf\x01\x04i\n\xaeW\xc0\xf3<R \x94\xaa\
+\xbb\xc9\\m\x16HX\xb7a\x1d\xef|\xc7\xdb\xb9\xee\xba\xeb(\x16\xcb\xfc\xf4\xc5\
+\x97\x18\x1d\x1d\xcfk6\xe9\x96\xb2K\x96,\xa1X,\xe6\x1d\tW\xacX\x91\xe7E\x8c\
+\x8d\x8dQ,\x16\x19\x1a\x1a\xe2\x9d\xef|\'\xa7\x9f~\xfaq\xbe{\'\x9e\xde$\xc9\
+\xd1\xd1Q\xde\xf3\x9e\xf7p\xfe\xf9\xe7S\xab\xd5\xd8\xbbw/a\x18\xe6%\xdfGFF\
+\x18\x19\x19!\x8ecv\xef\xde\xcd\x96-[\xf8\xfa\xd7\xbf\x8em\xdby\xe9\x93\x91\
+\x91\x11\x86\x86\x86\xe8t:\xc4q\xcc-\xb7\xdc\xc2\xf0\xf00i\x16\x01gY\x16\xae\
+\xebr\xedu\xd7[\'\x83\x129\xe1\nD9W\x95\x9d*\x11Y\x86\xb2\xa5\xfe\xe7\xd1\
+\xa2\x94NS\xa6F9I\xa9\x84 ;\xea\xf9\x0e)Ci\x02\x84 \x9c\x87\xdb^\xbb\x84O\
+\xfc\xee\xafr\xda(\xd0>\xc0\xd8\xea\xd3h7\x9a\xa4\xe5e4\xe7m\x86\xcbKhN\x1f\
+\xe0\xdc3&9p\xb0I\x02\xdcx\xdb/r\xd6\xfa\x8b\x98\xad\xb5I\xb1\xb9\xffK\x0f\
+\xd1\x8a\xd5\xc3\x9e\xa4Y\xd6\xf5\xe1\xf4\xc7B\xef\x9dT!\xa9\xda\x10\xa2\x8a\
+\xe3\xa9\x03mK\xd7q\n(\tX\xbf\n~\xfdW~\x91\x8b\xcf]\x85\x17\xce`\xb5gq\xe2\
+\x1a\xd8\x12WH\x1c\xd7\xc5\x16\x0ev\n\xb6L\x99\xdf\xbb\x97\xc6\xccA\xc6\xca>\
+$m\x8aV\x87k\xae\xbc\x84w\xbcy\x05\xe3%\x17\xd9\xae\x13\xb5\xea\x10u\x18*\
+\xf9J\xa0\xa7)\x85B\x81 \x91D)$6\x80\xea-/\xb1\x89\xa3\x0e\xa3\x15(E\x11c\
+\xc0\xc7~\xef&\xfe\xc3;n\xc5\x0b\xe7X\xbba\r\x15_`\xc5MH\x03h\xb7hLO\xd3\xd8\
+\xbf_E\xbc\t\x81m\t\xc6\xc6F\x98\xdf\xb7\x07\x9au\xbc\xa2\xcf\xcem[Y\xb1\xee\
+\\\xe28\xe6\xdd\xefy\x0f\xe7o<\x0f\xcbRn\x19\xc7\xf5I\x13I\x18&4\x1a*\t\xf0\
+\xdf\xbe\xe3\x8d\\r\xc1:\\/E$m\nv\xacjlE\x1dd\x1c)!h\xb9*\xda"\x94\x04\xb1E"\
+\x1d\xa4\xf0\x95c\xf9\xb8>}\xa2o\xa7\xa8\xde\xa9\xec\x91\xb0Rt\xb1\xcc\xa2\
+\xabL\xa7\x17_0\xc4\xba\xb3\xd6fY\xeb)\xa4\x11B\x86 #\xac8\x85X\xe2\n\x17\
+\x19\x85L\x0c\x97\xb9\xe9\xba\xd7\xa9]\xb3H\xbb\xe9\x12\x12\x924[\xdd#\x88\
+\x92\x14p\x88S\x81S(\xe6\x87\xf9B\xcd\x8f\x9d\xc0\x9do\xbb\x82\xdf\xffw\xbf\
+\xc2\xda\xc9\x02\xa3n\xcc\x92\xb1*v\x92\xc0|\r\x0b\x1bkt\x0c\x86\x86\x88\xda\
+-\xc2f\x1d\xab\xe8a\x97\x1d\x9cN\r\xea\xd3,-@\xfb\xc5\x1fSjOS\xac\xbd\xc0\
+\xdb^w>\x0f\xfd\xf9or\xce\xb8\xda\xd1\'QJ\x98(\xff\x9c\x9e\x07\xdbq\x08\t\
+\x19\x1e\x1a\x02R\xa2N\x8b\x15\xabW\xf1\xaew\xbd\x8b\xeb\xaf\xbb\x81\xa1\xea\
+\x08\xb33\xf3\xaax\x80\x94t:\x1d\xe6\xe6\xe6\xa8\xd5jy\xd64\xc0\xee\xdd\xbbI\
+\x92\x84\xd5\xabW3==\x8d\x10\x82\xdbo\xbf\x9dK/\xbd47\xcb\x9c\xca\xe8\xfc\
+\x968\x8e\xa9T*LNN\xf2[\xbf\xf5[\xac[\xb7\x8ee\xcb\x96\xe5\xcd\xa5z\xab\xf5\
+\xee\xdb\xb7\x8f \x08\xf2\xf0\xdfZ\xadF\x18\x86\xb8\xae\xcb\xdc\xdc\x1c\x8dF\
+\x83\x8b/\xbe\x98\xdf\xfe\xed\xdff\xc3\x86\ry\xd8\xae6w\x01|\xfb[\x8f\xca\
+\x93\xc1\x8cu\xc2} \x9a\\\xe0f\xbf\xab\xb7%U\xcfs\x1a\x81\x0c\xb0\x9c"\xae\
+\x0e\xfdE\xe5\x008\xc4T\xca6RZ\xfc\xab3\xe13\x7f\xf4v~\xef\xa3\xf7\xf2\xf8\
+\xa3\x0fS]\xf7\xaf\xa8\xcf\x80=\xbc\x9c0h\xe3\xda\xb0\xe6\x8c%\xac\x18/\xf3\
+\xec\x9e6S\xcb\x8b\xbc\xf5\x97\xde\xc0\xb3\xff\xf5\x7fR\xf0\x1c\xbe\xf3\xf8c\
+\x04\xf1\x9bH\x1ch\xd4C\xc6G\xfc\xbe1vMnq&\\,U\xb8\x14\xf2\\\x87\xc1c\x1d\
+\xc7"j\x07x\xe3S\xca\xb8m\xc78\xaeO\xd0\x82\xab\xcfwx\xd5\xea7\xf1\xc7\x7fz?\
+On\xd9J\xa3\xb1\x0bd\x91\x12\t\xad@u\xa2\xb3\x92\x08a\xc5,[V%\xe8\xcc\xb1o\
+\xebOXw\xd6j\xee\xb8\xe3\x97x\xe3\xe5*)K\xce\xbc\xc0\xa8]\xa0P\x10\xcc5\xea\
+\x94:E\x9c\xc6n\xc6&\x8a\x14\xa2\x18\xd9\x9cF\xc8%8\xae\x9aY)c@\xe0\xb86\xa4\
+m\xa2\xd6\x1c\xe5\xe1e\x00\xbc\xfd\xc6e\\y\xd5\xaf\xf1\x99{~\xc8\xe7\xbf\xf8\
+ \x8ep\xa8\x94+\x88\x82O\x10\xa6\xc4\xa9\x85\xe3\xda\xa4I\x8bpn\x1f\x91\x88p\
+Z\xf3\xac:}\x05I\xd2\xe0\xe0\xec^6\x9c\xbe\x91?\xfc\xb3\xf71\xe2\xc0\x18\xaa\
+\xfaG\xc1S\x01\x05\xb6\xe3P\xb4\xed<\x93~\xe3r\xf8\xcd\xb7]\xc6\xd2\x92\xe4\
+\x81\xaf<\xcc\xf4s/2\xb6t\x05\xce\xd8\x08\xb3\xf5}D\x89\xc0\xf7\x8b\xc4B\x85\
+B{I@\xda\xaeQ\xf2\x0b\x8c\x17\xaa\x99~\xb7s\x0b\xa2/\x1bT\x939\xd2$%\x89\\\
+\xac4\xc6MSD\xea\xe0&\x11\x05\xd9\xa2\x98\xd6\x10\x8c\xd1\xff\x8e\xf5\xbcq\
+\xba\xd0a\xb6xp\x1d\x9531\xe5\xc3\x15\x1bV\xf3\xfd\xef}\x97\xa9\x89\x154\xc2\
+\x18/\t\x88A\x9e\xd0\xfa\x00\x00\x1fYIDAT\x13\x89\x1bC\'\x92T<\x9f \xda\xc7\
+\xf2\xaa\xcfU\x1bJ\x08\xa0\xe8\x0b\x15g\x9b\xfb\xebS\xac<\x87^\xfd1\x8ec\xbc\
+\xa2O\x94$8\xb6\xc0\xb7 \x95\x11\x05\xdb\xa5\x19J\xae\xbeh\x19\x97^\xf4\xeb\
+\xfc\xed?\xef\xe0\xf3\xf7?BZ\x9f&\xb0\x0b\x04\xad&RH,\x19b\xc9\x0e\x8e\x15\
+\xe3\xd9J\xb9\xdb\x08\nc\x05\xea\xbb\xbf\xc7R_\xf0\xda+\xce\xe7\xedo\xb9\x92\
+\x8dg\xa8\x1b/\x0b\x15)\xef8\x82Bf2nuB\x8a\x05\x0f\x1b\x1b\x1bI\x9cF\xd8H\\\
+\xcf\x03\xd4\xea\xf7\xd6\xdbn\xe1\x17\xae\xb9\x8e\xbf\xfc\xec_\xf0\xc4\x13\
+\xdf\xc1\xf3<\xa4\x94\xb9#X\x87\xd4\xb6\xdbm\xca\xe52B\x08\xa6\xa7\xa7\xb9\
+\xe1\x86\x1b\xb8\xf9\xe6\x9b\x19\x19\x19!I\x12j\xb5\xda\xb1\t\x87\x93\x1c\
+\xed\xe0\x06\xe5\xa3(\x95J|\xe4#\x1f\xe1K_\xfa\x12\xff\xf0\x0f\xff\x90\x87\
+\xdf\xea\xbc\x97\x95+WbY\x16/\xbe\xf8"SSS\xcc\xcd\xcdQ\xadVs3\xd5\x8d7\xde\
+\xc8m\xb7\xdd\x96\xcf\xe3\xcc\xcc\x0c\xc3\xc3\xc3Y\xc8\xbb\xca\x0f\xe9-\xe2x\
+"\xb1\xe4\t\xdd\x07\xf5f\xb2:}\x8bz++\xbe\x01i&\xa1U\': /\xcd!\x88\xb0h\x12\
+\x84!\x1d\xa7\x8a%T}\xffo~o\x86\xbf\xfe\xc7\x07y\xe8;;a\xfc*\xeaa\x81R!\xa4\
+\xecm\xe7}\xef9\x8f7_v\x01\x7f\xf9w\x9bX\xbd\xe626^\\\xe1?~\xe8A~\xb2\xf9q*V\
+\xc8]\xff\xf9?r\xf1\xfaI\xa6\xaa\xdd\x82\x88\xbaL\x84\xad\x15\x87\x8c\xb3zP\
+\x8eZZ[\xb9d\xa07\x02\x0bz}<z_\xd2\xdfP*\x04j\x11l\xfe\xc1O\xf9\xca7\x1e\xe7\
+;\xdf\xff\t\xfbf\x9b\x08\xb7B\xa1X\xa6\xd9\xa8\x11Gm*\x1e\x9c\xbbv\x15W\xbf\
+\xf6\xe7\xb8\xea\xf2\xf3Y1\xae\xae1\xd7\x84o=\xba\x85g\x7f\xb4\x83R\xa9B\xb5\
+Z\xa5\xd9\xae\xd1\t\xea\x14\x8a6\x95\x92\xc7\xa5\xe7\xbf\x9a\x0b\xceY\x8aC7\
+\x81\x0f N\x02\x1c\xdd\x1b\x16\x9f\x107\xcf)h\xa3\x84\xe6C_\xdd\xc9S\xcf<\
+\xcb\xe6\xa7~\xc8\xccl\x03\xdb)\x81p\t\xc2\x94\xa0S\xa3ZL(\x17%\xabV\x9c\xc6\
+\xc5\xaf\xb9\x80+.\x7f\r\xebV\xdb\xb8\x90\x7f\xe9\xeb\xf6\x95\xbb\xb7\xba}S\
+\xd2\xec\xfb\xd3?\x9ee\xd37\xbe\xc5c\x9b\x7f\xc0\xde\x03s\xc4\xd2\xa1\x13\'8\
+\x8e\xa7\xec\xc3iL\xd8\t(\x97\x8b\xbcz\xfdz.Y\xbf\x9aw\xdf|!z\x1f\xb2\x7f6\
+\xe4\xa9g_\xe0\x99m/2\xd3\x0c\xb0=\x9f4Q~\x934\x8c\xb0\x92\x88\xe5\x93\xc3l\
+\\\x7f&\xeb\xd7,\xa5Zp\xf3\xf7\x08z7\x9bi_\x92h\x9cU\xb3\x89$<\xbb}\x8e\'\
+\x9e\xdc\xc2\x9e}\x07\x10\x85\x12\x89c\x13%\x96*<\x98\x82L\x13F+.\x1b\xd6\
+\xbd\x8a\x8d\xebNg\xb2\x94\xdd\xff\xe0Sf\xa5\xf9.$\x9f\x13\x0e%\x95*n\xbb\
+\xb7<\xc9\xc1\x06<\xbde\'\xdf\xfb\xf1v\xee{d\x0b\xf3QB\x1a\x85\x08\xa1\xea\
+\x8f\xa5a\x07\x8b\x94\xa2\xe3p\xce\xda3x\xf5y\xe7p\xd5e\x97p\xf6\x1a//\x89\
+\xd3[\xd6\xc4\x1a\xb8~\xf7\xb3\xabvaz\xd5\xdbj6)\x95\xaa\xa4Y~c\x10\xaa\xee\
+\x7f\xcf?\xbf\x95g\x9ey\x86\xed\xdb\xb7333\x93\x87\xf3j\xa7\xf9\xe5\x97_\xce\
+\xd4\xd4\x14+W\xae\x04\xba~\x83\'\x9f|\x92\xbb\xef\xbe\x9bF\xa3\x91\x97\x00\
+\x91Rr\xd7]wq\xfa\xe9\xa7\xe7\xa6\xa1$I\xf2\x12\x1e\xf3\xf3\xf3<\xf3\xcc3\
+\xec\xda\xb5+WX\xae\xeb\xb2j\xd5*6l\xd8\xb0`\xcf\xf1#\xa1\xaf\x01\xb0o\xdf>\
+\x9ez\xea)v\xed\xda\x85\x94\x92j\xb5\x8a\xe7y\xac_\xbf\x9e5k\xd6\xe4\x15w\
+\xf5\xf1\xc7\x12F\xdbj\xb5\xd8\xb2e\x0b\xdf\xfa\xd6\xb7\xd8\xb2e\x0b\xb3\xb3\
+\xb3\xf9\x98\xf3\x9e\xe8\x9e\xc7\xf2\xe5\xcb\xd9\xb8q#\x97\\r\t\xabW\xaf\xce\
+\xef\xa1^\xaf\xb3y\xf3f\x9e\x7f\xfey\x1c\xc7\xc9\xbf\xd6\xad[\xc7\xc6\x8d\
+\x1b\x8fz\x7f\xff\xab9\xc1\n\xe4x\x89 \x99\x05\xdb!\xa4\xcc|;e\xacPDJx\xe4\
+\xbb{\xf9\xd4\xbd\x8f\xf1\x95\x1f\xa48c\xafbzn\x07\xc4?\xe4\x91/\xff>\xcbm\
+\xf8\xe7\x87\xb7\xf1\xd1O|\x8e\x0f}\xf4\x83x\x1e\xfc\xb7\x0f\x7f\x92`f\x9a\
+\xcb6\x9e\xcb]\xbfs\x1b\x05\xa0\xd3\x82\x91\x92V\x1e)6\xaa\xb9\x8f*=.\x94\
+\xe2\x10v\x8f\x02yyH Lb,\xdb\xc9\xbb\x13\xd6R\xd8?\x03\xd3s)\xb5\x86j\x03\
+\xbat\x89\xc3\xd2\xf1n=,\x1f\xbdC#\xefe\x1dD\xe0\xbb]a\xa0\x15\xdf\xc1\xd9\
+\x1a\x93\xa3CY\x99\xf2D\xf5\xd5\xf6}\x1c\xa1\xbd\xeaa\xfe\n\x89C\x9a)\x918U_\
+\x96\xa3\x9c\xc8\x9d\x10\xe6\xea0}\x00\x1a\xcd\x00\xb7\xe03<\xac\x9a\x10\x95\
+|(\x97t\x99\x8e^\xc5\xab\xcb_\xf4\x06\x1c\xf4\xf8\x19\xe8.\x06\xda\x11yK\x90\
+\x008P\x87\xd9ZB\x18\'\xcc\xcc\xd5\x88\xc3\x0ec#\xc3LMV)\x97\xa1\xe0\xaaD?;\
+\x82\xa2\x93f\xad@\xd5\x1c\xb6\xda\xca\xcd\xe3{\xfd\xf3\xa1\xa3\xf8@gj\xcbL\
+\xdd\xf7\xd2\xff^\xc6\x91\x12\x14\xbe\xdf\x15\x14:7N\xf8\xd0H\x95\xe9U\xbf\
+\x1fj\x9eU5_}\xbd\xee\xce\x15r\xf1\xac\xcb\xc3K\xa7Ws\x1d\x96$I\x88\xa2\x08\
+\xcf\xf3\xf2d\xb9}\xb5\x08g\xb4\xc0\xbe6\xec\xdd\x9bR\xaf\xcda%\x11\x95r\x91\
+\xa5\x13CL\x8dC\x12B\xc5\xef\x8e\xc3\x06\xc2D\xe5\xef{\xb6*ks\xa4\xcbk!\xd9+\
+\x90\xf5*xdd$\xff]g`k\x93\x8eV:\x8dF#ok\xab\x8f\xf5<\xd5\x83\xe6\xa9\xa7\x9e\
+\xe2\x13\x9f\xf8D\xde\xaf\xbcP(\xd0l6\xf9\xb3?\xfb3FFF\xf2\xeb\x86a\x98\x0b\
+\xce\xc1q\x01y\xabZ}\xcd(\x8a\xf2\x1c\x94\xa3\t\xf80\x0c\xf3J\xba\xfa\\:?E\
+\x08\xc1\xfe\xfd\xfb\x99\x9c\x9c\xec;\xb7\xbe\xc7cURz\xbcz\x87Q\xaf\xd7\x99\
+\x9d\x9d\xa5^\xaf\xb3t\xe9R|\xdf\xa7T*Q(\x14\xf2*\xbbzNt\x01\xca$I(\x16\x8bX\
+\x96\x95\xb7\x05>\x19\xda\xda\x9e4&\xac\xc5a\xa1\xdc\xeb\x02\xf0\xa9\x14\xb5\
+9\t.\xbch\x19w.\xbb\x85\xe7?|\x1f\xcf\xedy\x963\xa6J$q\x85e6\xcc\xc6)\xdb\
+\xb7\x1f I+|\xfa/\xbe\xc8\x87?\xfc&\xae\xbb\xeez>\xf3\xc9\xff\x97\xcd\xdf\
+\xdd\x8c\xcdm\xc4I\xd6\xc4\x8dnj\xa0\xd4\x85\xff,w\xd1Jcp\xf4\xaa\x8a\xad\
+\x83\xb4}\x10\x16U\x01\xe5\t8}B\x10R\xa6\x13B\xc5\xcbzkec\x91\xb1$NC\xa4%\
+\x98o\xa6T\x87}\n.\xdd\x02\x8e\xd9\xf9SI\xae<\x00\x1c!p\x0b\xc5\xeca\xd0\x82\
+L\x97\x0f\x11X\xd8\xe8\n`\xc2R_i\xaa\x82\xac\xfc\xa2\x8a\x9cZ\xb1\x04\x9a\
+\x1d\x1f\x84\xea\xf3\x95&Pt\xba\xa53\xe2\x14\xc20\xc1w$\xbe\xe3\xb0\xf0\xba:\
+\x0bZ\x93@\x92\x92\xa6\t\xae\xb4\xd4<d\xff_V\x85\x91\x8a\x8dm\xd9\xb4\x97O %\
+T\xbd~al\xa3\x95f\xf7\xbd\xb0QJ\x8d\xec\x0e;m\xd5\xa4\x11K\xa5l\xa4\xa8\x98\
+\x00dJ\'hS*\x1c\xb9+\x9d\xe3\x90\xad<U\x94-\x80~n\x13T\xf5\xe60\xbbK-\x8c\
+\x93\x18\xda\xa9\xc4s,\xdd}w\xd1\xf4\xb6d\x05r\x81e\xdb6\x13\xa36M`\xaa\x08S\
+g\x08,\xc6\xf2\xf7A\xcf\x8f\xe3\xf7+2$\x14mm@K\xfb\xe6n!\xb4\xe0\xd4\x02Y\
+\xb7\xa8\xed\x15\x9c:\xcf\xa3\xb7\n\x80*\xfd\x9e\xe6\xcaC\x0b[!D\x1eA\xb4}\
+\xfb\xf6>\xa5\xa8\x8f\x19\x1e\x1e\xce\x15\x84\x10\x82B\xa1\x90\'\xce\xe99\
+\xe9U&\xb6m\x13\xc7q~}\xcb\xb2\x8e\xb9\xdb\xa0\x16\xc2Z\t\xf5\x9eWJ\x99\xb7\
+\xc9\xd5\xf7\xd0[F\xe4X\x94\x87\xde%\xe9\xb1ig\xfb\xe4\xe4d>\x17\xbd\xf36\
+\xd8\xbb=MSJ\xa5R\xdf9\xf5\xef\xbd=\xdfO\x14\'\xd8\x89~\x9cH\x07("\x03\'w\\\
+\xa7@\xbd\x15\xe3\xd8p\xd6J\xf8\x9d\xff\xf3F6\xae)3\xbf\xe7)\xd6.\xab\xe2\
+\x02\x93\x8e`\xee`\xca\xd0\xc8*\x9e|z+\x1f\xbf\xfb\t\xde\xfc\xa6\xd5\xacz\
+\xd5Z\xb0l\x1e\xfb\xee4i\xd2\x15\xd8:\xabC\x19\xd5\x1c\x12\x04q\xe6\x8b9b\
+\x90\xd6QI)\x17\x0b\x94}\x8f\xa2cQ\x14\xbay\x93\xfa^\x01\x96x\xaa\xe8\x9dH\
+\xc1\x8a\xd5\xff\x8a\x8eE\xc9s)\xb9.\xe3#>\xae\xd5/T\xb5\x00I\xa2\x948\x96\
+\x84\x9d\x908\n\x11t?\xc8H\x8b4\xcc\x92\xe0\x12\x07\xd2l\xf9/\xc1\x91Ja\x15\
+\x84\xfa\xb2\xa5\xb2\x95\xebfQ#\x05\x18\xf1\xd4q\xa5\x9e\xe6\x8cV\xf6\x9ar\
+\xc1\xc6w\x1c\x04i^\xa8pA,\xb0d\x82\xebX\xf8\x9e\xc8\xfa\x84\xabT\x10KB\xd5R\
+\xf30\xe2\xc2\xa8\xd7\xbd~\xd2\x01+\xe9\x9a_\x04)2MT\xea\x88\xde fy\x81\xe5\
+\xa2*\x98\xa8wd\xb6\xc8~\xb6\xc8{Q\x1f\t\xbd?\xcf\xfa)\xe5\xbf\'\t\xb4ZIn\
+\x06\xca:\xcf\xaa9\xf0P%\xd2\x9d\xc1\xddG/\xd9\x88\x8e\xf2\xf9\xe9-y\xa1\x05\
+\x8a^\xc9v\xda\x91\x8e[\xcc?7\xbd?;R\xcd\x93\x9dv\xcdU\x9e\x95\xe2\x10c\x11a\
+\xe9`\x80# \x84\xa0\xddn\xe7\x82V\x97&\xd1+c}\xcc`\'=\xad\xf4\xb4\xdd_7Hr\
+\x1c\x87F\xa3\x81\xeb\xba\xec\xda\xb5+/U\xee\xba.\xadV\x8b\xa9\xa9\xa9Cv\x1b\
+\xfa\xde\xf5\xca\\\x0b^\xbd\xaa\xd7&!-\xa8_N\x99\x0f\xad\xccz\xcdRq\x1c\xe7>\
+\x06=\xd7\xbd\xf7\xfdr\xca\xe5\xe8\xact\xdb\xb6\xf3\x1dZ\xaf\xf9J\xfb\x8et\
+\xaft=\x17\x83\xcaIJI\xab\xd5\xca{\xa3\xeb99\xd1\x9c\xe2\n\x04H\x04\x96P=\
+\xae;\x89\xaa\x10Q.9xV\x80 \xe1\xd2s\xe0\x03\xef\xbd\x89u\xcb,.:kI\xde-oll%;\
+w\xcf\xe1\x95&\xd9\xf4\xc8\x13<\xf4(\xfc\xc1]\xef&L\x05\x9b\xbf\xff\x03U\x06\
+Kj\x93L\x92u\xd1P\xdd\xfd\xb4\x9f@\xb5m:>\xc2\xa0C\x14\xb6U\r"2\x9fA\n^\xac\
+\xf2]\xbc\x18\n)T\x842\x119H\xd2( \x0cZDQ\x0b\x1b\xb5\xd3\x08\xa3\xac\x10$]a\
+Y\xf0\x04\xae\xa3l\xcb\x02K\xb5f\x95\x02\x99*\xbf\x92p3c\x98\xcc\x94Go\xf8Xv\
+\x0e\xe2\x18GF\x14\xec\x04+\t\x90Q\x80H\x02\x1c\x19Q\x16J\xa0\xfb(\xe5F\xa4\
+\x84\xb6\x8d*\xe9\xdej\xd5\x8fz\xffi\xd8!i7\x88\xdbMd\xd8\xc6\x91)\x15\x0fJV\
+\xd7\x87b\xc5*\x10\x8cP\xa5]\x0c{P\xb5Um/\x19\x87 %\x8e\xb0\xf1lU\xc6\xc3\
+\x15jg`g;(\xbd\x10\xf0\x1c\xa5Ld\x9a\x12G\x11\x9dN\xb3g$\xfd\x01\xd9\x9a(\
+\x8e\x95\xa52\xcb\xc1\xd4\xb2\xc3\xb6\xa1RR\rWlIV\xfc03e%j\x17\xb2p)&\x1d\
+\xe5u\xd4\xa9\x01\xba\x82M\x97\xc1\xd09\x00\x8e\xe3P\xf6\xdd|\xfe\xed\xcc\
+\x91$\x03\x15\xb9\xe5K(Xj\\\xbe\xe8*\r\x920k8\x16s\xb4fP\x9a^A544\x94+\x8eR\
+\xa9\x94\x8fI+:-\xc4\xb5B\xd1\x99\xdaZ\xf9\x81\xea\xd6\xf7\xfc\xf3\xcf\xb3s\
+\xe7N<\xcf\xcb\xcbtt:\x1d\xce9\xe7\x1c5K\x99\x00\xd5Y\xdbz.\xb4p\xd5\n\xaaW\
+\xd0\xf6\x9a\xa2z[\xcc\x1e\x89^\x05\xad\xcf\xa9\x15\x98>\x97\xf6\xcf\xe8k\
+\xe9\xb2-\xc7B\x18\x86}\xb5\xc1\xf4\xdc\xe8s\x08!r%\xe3\xban\xdf\\\xeb\xda^\
+\xfau\xa5R\x89R\xa9\x94\xff\xedd\xa8\x87u\xea+\x90&\xb9\x14\xb7mH\xa4\xda\
+\x87\xd8\x04\xd8\xe9<%\xe05k\xe0#\x1fx\x177\xbf\xee5\xd8$\xcc7a\xff\xbe\x19\
+\xaa#KiK\x8f\xea\xf8i|\xe4c\x7f\xce\\\x1b\xde\xfc\xd6\xdb\xf9\xce\xf7\x9e\
+\xa2\xd5\xee\xae\xaa\xc9\n<\xda=\xc5\xc3\xb5C\xf3x\x1dH\x9e_\xc0\xf5<U\x142\
+\r\xb1\x92\x08WF\x14l(\xdb`G\x1d\xdc$\xc6!\xcd\xae\x9f\xe0:\x02\xcf\xf7\xf1\
+\\\xb5*\xb4%\x94\\\xf03\x1fD\x9a\xca|\xe5\xabW\xdd\xae\xe3b\t\x01R"u\x012\
+\x99\xad\x8d\xb5;\xa4Gy(\xa7\xbf\n\xa9uD\x8a\'b\nvJ\xc9\x8d(\xda1\xbe\x15\
+\xe0\x11`\xa5Jh\x96]\x15\xee\xaawC\x9ecS.\x95\x07Nz\xe8\xfb\'\x8a\x1ev\xc1\
+\xc7)x\x08\xd7Q\x82\x9al\'%\xd5\xb9\x0b6\x94DB\xc5\x95\xf8v\x8ame\x95\x89e\
+\x82\xb0R,K\xbd\xe7\x16\x90f}\xac\xa2P\x95(\'V\xc2\x1c\xd9\xddy\x08\xa1\x8aV\
+\x1e\xcb\x0e\xc4\xf3\xba\x0fi\x9aB\x10$\x84aJ\x925H\xf4\x84\xbag=f\x1b\xf57\
+\xdfV9\x98\x0b?`/\xff\xb1\xd3\x117\xbe\xef\xe7\xf6~[tw\xa5%\x01U_\x95\xfa)d\
+\xc9\x9a\xc4!Q\xbbA\x1auT\x15\xe7$V\x01 :0\xe5\x18>\xbc\xba\x9f\xf9\xce\x9d;\
+\xf9\xfc\xe7?O\x10\x04\xb9\tE\x87\x96j\xc17\xb82\xd7B\xb3\xddn\x03j5\xae#\
+\xaf\xee\xbf\xff~\x0e\x1c8\x90\x0b\xd3(\x8a\xa8T*l\xd8\xb0!\x7f]\x1c\xc7\x14\
+\x8b\xc5>\xc1\xad\xfd\x1bQ\x14\xd1\xe9th\xb7\xdb\xb9\x0f&\x0c\xc3>Ev,\xe6\
+\x9d\xde\x9d\x87nI\xab\xfb{\xb4Z-\x84\x10\x87\x08\xea^\xc1~4<\xcf\xcb\xfd\
+\x1a\xbd\t\x80\xfa\xbcz\xfcz\xee\xf4\xf5\xf5=\xe8\xb1\r\x16b\xec\xbd\xd7\x13\
+\xc9\x89Wa\xc7\x8b@\xb5\x00\'\xb3A\xdb\x16\xed\xb0E\xc5K\xa8\nAHL\xbb\xde\
+\xe1\xe7\xceYF\x94E\xe48e\xd8\xfd\xd3\x17\xf0\x8b%\x0e\xb4[\x1ch\xa6\xd8v\
+\x99\x7f\xff\x9f\xff\x8a/|\xeav\xbe\xfe\x90\xcd\xce\x9f\xb6\x99<G\x0b\x18]\
+\xa7Kb+\x11\xfe3\xd2\xbc\x99\xdd\xd7\x12\xa8~\xea\x96\x12\x86\x12\x08\xea`Y\
+\xb8\xbe\xab\xae\x1dFJ\xfa\xd9\xea\xc3(\xd3L\xf0\x0b\xa9\x04I\xcf\x92V&\x11q\
+\x02\xbe\xabl\xd6\xbe\xeb)\xe5\x01*SZ\xa5\xb5g\xa5X\x07\xb6\xc1\xda\xfe\xa5\
+\x7f\xb5\xad\xcc>\x13#e\x8a\xa5\xbb\x10\x92\x92d\x19q\x96\xedc;\xd9\x03\x1e\
+\x87$i\x84#lU\xdb\xe9(\xdb\xec\xb8\xd9\xc1q-p=\x90)I\x1c\x11K\xb0\x1d\x0f7\
+\x8b\xc0qm[Ii\x99\xa8e}\x12\xa1\xb6\x87B\xe5\x87\xd8j<RJ\\\xc7^\xf0C-%$q\x8a\
+\x94\x11\x16J\x18\xe7sr\x94wSf!\xdb\xc2\x06\x7f\x81\xfb\xb1\xe9\n\xa0t\x81\
+\x95\xf1\xc2\x1c\xdb\'H\xafR\x07\x85\x98\x16*\xae\xe3fv;I\x94\xad\xb8]\xcf\
+\xcb\xaa\xd6\x08l[\x0b\xd1\xde\xb8@ U\xbe\'\xe1\xba\x1c\t!\x04\xcdf\x93M\x9b\
+6\xf1\xf5\xaf\x7f\x1d)%\xb7\xdcrK\x9e\xbfP\xa9Tr\xff\x856\xf3\xe8zMZp\xf6\n\
+\xbar\xb9\xcc\x8e\x1d;\xd8\xbau+\xbe\xef\xe7}\xbf\x93$a\xed\xda\xb5\x9c}\xf6\
+\xd9\xf9\xdc\r\xce\xa1\xf6\xa1h\xdc\xc3\x8c\xfd\xe5\x96:\xd7\x0ek\xbd\xb3\
+\x01\xf2:T\xda\x1f\xa1\xcf\x0b/\xcft\xa4\xdf\xbf\xde\xdd\x91\xfe{\x14E\x87\
+\xf8j\xf4\xe7\xa8w\x87\xa5_\xd7\xdb\xb5\xf0d0_\xc1\xa9\xae@,\xd4\xf2KB;\x08H\
+|\x07\x0f\x9b\xb2WV2%\t\xb1\xd2&S\xd5\x12Ac\x8eb\xa5H\xa33\x8fSX\xc2p\xc5\
+\xa2\xbe\xe7\x00v\xa9D\xd2h\xe2\x0e\x8d\x92\x88\x88\x8f\xff\xf93\xbc\xeb\xdf\
+\xdf\xc9\x03\xff\xf256\x9e\xf3\xf3y\x7f\x05\x9d\xecha\xe1`\xe7}(\x8e\xcb\x05\
+\x02X\xc2!\x8c\x94`\xf4\xdc\xccs-\x13\x95|`\xa9d5\x12\xa9j\xba\xe8\x0f2\x8e\
+\x12~\x16\x99i-%Ie.P\xfc\xac\xd5h\x14\x05\x14\xfcn\xb2\x9d\xcc"L\x84\xeb*[\
+\x8c8\xec\x12\xb9\xe7\xc6\xb4\x93\xddB\xc6\x01i\x94`\xbb\xaa\x8d\xaa\xe3\xd8\
+\xaa]\xaf\x1a\x14B\xe8\x15{\xe6<\xcf\x03\xfc\x0e3K\x168\xc5"\x99\xdb\x1e\x84\
+\x85\xed\xbb\xd9|w\x9d\xc5\xe80\xe8\xac_\xba\xaa;\xae^/\xec\xcc\xd6\x88E\x92\
+\x99\x9b\xf4\xc3\x15E\t\x02\x1bi\xa5\xd8\xb6P\xcd\xb3\xe8\x96m?Vz\x1fh\xd0\
+\xf6|\xd5\x1f\xde\xca\xe6\xdf\xb6\x04v\xcfn\x05\x99d\x11G\xc5\xe3\xfa\x90\
+\xf4\n5\xe8\n;\xdb\xb6\xb1\x85\x8d\x8cC,G}\x16\xdcB\x7fTN\x12\x86\xd8\x9e\
+\x07R\x92f\xabk\xdb\xb6\xd5\xe7\xc8\x16\x88c\xe8(hY\x16\xf7\xdf\x7f?\x8f=\
+\xf6\x18\x96eq\xcf=\xf7\x10\x04\x017\xdcp\x03\x13\x13\x13\xb9\xf3\xda\xb6\
+\xedC\x84\xa1\x8e\xb8\xaaT*\xb9r\xe9t:\xdcu\xd7]4\x9b\xcd\xae)\xae\\\xa6\xdd\
+ns\xc6\x19g099\xa9\xa6/\x13\xe8\xda\xa9\xdc\xeb\xdb\xd0&!\xed\x8f\xe8\x15\
+\xd2\xbdst,&\x1e\x1dU\xa5\x15\x93\x16\xe0\xba\xfb_\xef\xcejP)\x1d\x8b\x13\
+\xbbw\x0c\xda\xa7\xa2\x17\x04\xbd\xe6I\x1d\xaa<\xa8h\xf4\x1c\xea9\xe9u\xc8\
+\x9bj\xbc?\x0b,\xc0N\xf1]\x082\xb1n\x01\xa4>\xe0\xa8\xa6>\x12\xfcb\x11\xa2\
+\x80J\xc1c\xd7\xec\x1cg\x9c^\xe5K_{\x0c\xeb\xac\x8d`9t"\x89\xb0\x05\xf7}\xf5\
+Q\xce?w-Vq\x8c\x00\x1d\xfd\xe4\x00\x01\xb2\xdd\xc2*\xaa\x04\xad\xf9\xb9\x0e\
+\xc3#\xa3\xc7\xb5\x13\xd1N\x7f\xe1\xda\xd8\xae\x16\xc4\xb1\x12\xecY\xb2\x1f\
+\x16\x99# \x8b\xf1\xb1\x1cUs2;\x87\xba_\xa9\xc2rE&l3\\\xb7\xe7w)\xb0\x1c\xa7\
+_\x96\x89n\x18m\xcf\x9f\xb2i\xed\xbd3u\x1e\xe1\xb8\xdd\x81\xe77\xa1c\xbf\xe8\
+~\xb7z<\xcf\x87\x91\x9e\x96\xcc\xfe%z\xea\x9f\xf5\xccK\xdf\xb1z\x1eD\xef\x11\
+\x03+jP\x89\x91t\'\xc7u\xecL\xb7twM\x8b\xf1Zu\x85\x80\x1a\xa7e\xc9L\x19\xa5=\
+v\xce^\x85\xa9\xf0|\xad\xac\xfa\xc7\xa9\xef@\xdd\xdb\x91Yh\x15\xde\x8b\xe58Z\
+\x7f\x1e\x82\xad\x17\x0f\x16\x08\xa1\x17\x1f\xf9\x99\x8ex\xfd8\x8e\xb3\xe6Q?\
+\xe1\x1b\xdf\xf8\x06\xf5z\x1d\xd7u)\x97\xcb|\xedk_\xe3\x89\'\x9e\xe0\xdak\
+\xaf\xe5\x9ak\xae\xe9\xb3\xeb\xf7\nL\xcb\xb2r\x01\x1d\x04\x01\x0f=\xf4\x10\
+\x0f?\xfc0333\x14\n\x85<,u\xc7\x8e\x1d\xbc\xe1\ro\xe0\xado}+\xa0\x04\xb3\x16\
+\x9a\xdaA\xde\xcb\xa0b\x18\xfc\xff\xcb\x11\xaa\x0b\xbd\xb6\xf7\xf5G\xda\xc9\
+\xbc\xdc\x08\xa8\xc1\x1d\xd4\xd1\xfe\x0e\xf4\x85\xea\x0e\xde\xd7\x89V\x1e\
+\xc0)\x9e\x07\x92\xa6Y\xc5\xc5\x88\xc0\x8e\x88\xb1q(\xe2e\xd1\'\xba4\xb7zV\
+\x12\x90\x1dpB\x1ai\x91\x7f\xfa\xd6\x01>\xff/\xdb\xf8\xf6\x8e\x19f\x9b`y\x05\
+J\x8e\x83\'\x03\xc2\x83/\xf2\xc9\xbb\xde\xcb\xaa!8\x7f\x15Te\x84#\xda\x10\
+\xb5\x01\x01N\x11\x19;XN\xe1\xb8V\x97:9L\x8b\xcfn\xa8\xa5\xce\xee\xe8\x15v\
+\xb6R\x1e\x87\xbcF\xf7P\xd1\x0c\x0e(\xfb`\xca\x81\x0fh\xb6\xe8OH\xfb:a\x80\
+\xc8\xcf\xd0U&=\xaf\xe9\xfd\x0ej$\x83\xceXk!\x896\xe0\xa4\xee9\xa47\x81\xadW\
+\x1d\x1d:\x86\x9e\x1c\x8a\xfc:}#\xe4\xb0\xf7\xdb7\xc6\xde\x9f\x8fe\ru8\xa5\
+\xa3\x93\\\xb5\xc2\xa4?,\xcf\xea\x19\xcf"\x15\xc8\x11\xc9\xdf\x87~\x05\xdc\
+\x7f\xfd\xfe\xf9\x1d\xbc\xf6\xc2\x11b\xdd\x95\xf8\x9dw\xde\xc9\x0b/\xbc\xc0\
+\xc4\xc4Dnw\xafV\xab4\x1a\rFGG\x89\xa2\x88s\xcf=\x97\xff\xeb\xb7\xfe\x93\xb5\
+c\xfb\xf3\xb2\\.\x03\xe4\x1d\xf4\x9e\x7f\xfey\xb6l\xd9\xc2\xb6m\xdb\xf2(\xa3\
+z\xbd\x9e\x17\x0c\x9c\x9f\x9fg\xc3\x86\r\xbc\xf9\xcdo\xe6\x82\x0b.\xc8\x0b\
+\x06\xea\xd0\xdc\x93\xc1Ql8<\xa7\xb6\x02\x91iVJ5 \xb0Z\xc4\xd8\xf8\x8c\xe0h\
+\x0f7tc[% \xdb\xe0\xb4\x80\x02;\x1ae\x9e\xd9\x07\xef\xff\xd8\x17\xf8\xf1\x0b\
+\xb3\x14\xab\x13\xb4k-H\x12\xd6\xacY\x01\xf5\x9f\xf2W\x7fz\x07k\xaa\xaa\xbft\
+\x81\x8eR@I\xac\x92\x08\x9d2\xba(\xe1bc\x11\x06\x95A\xf7\x81\xd6+\xe5\xdeG\
+\xdd\xc9U\x8a\xecyM\xb6\xa9\xef9\xeb\xa0\xa28\xc2\xd8,\x88\x89\xd1\xadB\xb4\
+\x90\x1fT\x1cB\x0f\x96\x1e\x99\xdd\x87\x16\xe8\xfa\x85\xdd\x8c\xfc\xc1q\xf5)\
+#\xab_Mj\x9d\xd2\xfb\xba\xc3*\xb3\xde\xeb\xf6r\xc4\xfb]H\x11\x1c\xee\xf8\x85\
+\x8e]\xe8z\x0b\x9c\xafw\x90\x87\xe9\'\xf33Q \x83\xd7?\x8a\xc2`\xe0\xef}\x8b\
+\x96\x01\xb4\x8f\xe5S\x9f\xfa\x14\x8f?\xfexn\xfe\xd2\xe6\x96\xb9\xb99\xc6\
+\xc6\xc6r\xf3\x9265\x01\xb9\xd9G;\xfe\x8b\xc5"A\x10033\x83eY\x8c\x8d\x8da\
+\xdb6\x8dF\x83e\xcb\x96q\xdbm\xb7q\xc5\x15W\x00\xfd\x89\x87\'C\x9e\x83\xe1\
+\xc8\xd8\x1f\xfc\xe0\x07?x\xa2\x07\xb1x2\x13\x89\x80\x18\x15\xe9\xe1Q\xc0J3\
+\xbb\x8c\x92\xbb$\xd93\xac,*\x01`\xe1{>\x13c\xb0\xeaU\xeb\xd9\xb6\xf59\xe2N@\
+;\xe8\xe0\x14\xcb\x1c88\x8beIv\xbd0\xcb\xe5\x97\xbd\x8a\x82\x804\x92xv\xf6\
+\xc8uB\x95\xa1\x858\xa2\x99\xe6e\xdeE\xfes\xff\xf9\xba\x99\x1d\xe9\xc0\xf1}V\
+\x9dA\xef\xb7\xecY[\xf6\x9f\xbc+h,\xb2\xec\x96\xee9\xc4\x02#\xb0\x16R\x1e\
+\xbd\x12\xd0\x92J\x91Z\xd9\xb6\xc6"+*)3SX\xf7l}35`>\xeb\x1d\xde\xe0\xac\xea\
+\xa0\xa1\xfe\x99\xd6G\xa9\xebZ:\xa2\xecpd\xef\x95\xd4\xdb\xaf\xbe\xd0\x83Azo\
+\xf6\x08>\x13\x8b\xec\xde\xad\xecK\xbf\\\xf4\xff~\xb8!\x1d\xf9\xdf\xc7N~\xdd\
+\xee\xcf\x0b)\x8e^\x05}\xa4\x1d\x88\xe6\xd2K/e\xef\xde\xbd\xbc\xf0\xc2\x0b\
+\x00\x8c\x8e\x8eR(\x14\xf2\xfe\xe6\x9e\xe7\xe5!\xa9\xb6mS*\x95\xf2\xc8)\xed\
+\xc7\xd0\x91W\xcb\x96-c||<\x0f\xfd\xadT*\xbc\xedmo\xe3\xc2\x0b/\xccM5\xfa\
+\xbb\xce\x870\x9c\xdc\x9c\xe2;\x10 UUf\x03\x0e"\x88)0\x02\xb1\x9fw(\r]\x95\
+\xafa\x93\xe5+H\xf5a\x96\xd6P\xde;\xfa[?\x8e\xf8\xd0\xc7?\xcblX`\xd7\\\x04\
+\xc2gb|\x88\x97\xb6=\xc9\x07~\xf3-\xbc\xe3\xe6\xb3Xj\xab\x84>[\xc6\xa4\xed6\
+\xc2-fN\xe8\xa3\x0b\x89\xa3\xde\x03\xfd\x96\x8f\xb4\xef\xa7\x01\x1bx\xf6}A\
+\xeb\xcd\x11.a\x1d\xe6{J\xdag\x17\x1f\\\xf1\xf7]kP\x81,\xa0\x98\x16\xdaQ\xf4\
+\x8d{\xe0<\x8bM\xc4<\xdc^b\xf0\xfc}\x0c\x8c\xaf\xef5\x87\xbdB\xef\xd1\x0b\
+\x9dXg\x99\xf4\x1fu\xb8\x9d\xd4\xcf\x9c#\\d\xa1\x9d\xc8\xe1\x14\xc8B\x91KZ1x\
+\x9e\xc7\xc1\x83\x07\xb9\xfb\xee\xbb\xd9\xb4i\x13K\x96,all\x8cF\xa3\xd1\x97\
+\x1f\xa1\x13\xe2\xa4\x94\x14\x8bE\xca\xe52\xb5Z-O\xa2\x8b\xa2\x88\xb9\xb99\
+\xca\xe52\xaf\x7f\xfd\xeby\xe3\x1b\xdf\xc8\x8a\x15+\xf2\x1aS\xbd\xd9\xdd\xad\
+V\xeb\x90\x0cl\xc3\xc9\xc7)\xad@\xa4Tn\x90\xc4\x86\x989l\xda\xf8T!.@\xea\x80\
+\x80\x96\xa3j+\xd9\xa8\x0c]O6U\x94S\xaaLP\x9d\x14B\x07\x9e\xde\rw\xfe\xe1g\
+\xd8\xdd*PK]:\xf5:K\xa6\xaa\x88\xda.\xfe\xdb\xef\xfc\x06\xd7\x9c_\xa2\x0c\
+\x14$\x08=c\xba\x8d\xc9b\xa5\xc3\xa1\xde\xe2>3\xd5\xc0\xbf\x0e/ \x17\xd2\x0e\
+\x0b\x1cv,\x1c\xe2\xef8\xdc\xef\xbd/\xe8\xb9\xde\xe1\xd6\xeaG\x1d\xff\x11\
+\x9d\xdbb\xc1!\x0c\x1a\xf9\xf4\x91\x87\xbb\xc6\xe0\xf8\x8e\xbc\x02\x1fT \x0b\
+]] q\xfb|7G\rj\xfbY\xd3\xab\xdc{\x15\xfa\xc0v\xedHo\x1f,\xac@\xf4\xdffgg)\
+\x97\xcbx\x9e\xc7\xcc\xccL\x1e\x95533C\x10\x04\xd8\xb6M\xa1P\xa0P(\xe4Ux;\
+\x9d\x0eI\x92044\x84\x10\x820\x0c\xf1}\x9f\x8d\x1b7r\xf5\xd5W\xb3n\xdd\xba\
+\xbe\x1a[\xda\xe7\xa2w \xc6|ujp\xca+\x90(UU8\x12\x1a8\xcc\xe3ST;\x10\xe9\x81\
+\xed2/T\xd6\xb8V %\x19d\tUEu\x02\xd9\x04\xb7BM\x08~8\x07w\xbc\xff^\xb6\xcfD\
+\xc4\xd2\xc6\xb1c\xe2\xd9\x1d\xdct\xe5z\xdew\xc7\x9b8\xff4pS\xf0E\x96\xf9\
+\x0b\x87\xdaZ^\x16Y\xcd\r)\xbb\xe6\x0eKW\xf3\x15\x03\x82.\xcd\x04\xa3N\x02\
+\x93\xe8\xd6\xbfp\xe4\x95\xfc\xe1D\xb3 \xdb]\x1cI\xba\x0c:\xcc\x07\xcf\xd0c\
+\x0e\xeb\xf5\xcf\x1cr\x1d\x06\x85{v\x1f\xc7\x98\r\xdd\xebk\xea]Y\xf7\xfa\x83\
+\x8e\xa6\xa4\x16Rp\x87\xf3\x01\x1c^\x81t\xc7+{\xbaO. \xb3\x81\x81\x9d\xd7\
+\xcf\x9a^\'\xba\x14\xdd\xb9<\xdc`\xf4\xb1}d\x15\xae\x0f\x93;\xd1\x1b*\xaa[\
+\xa9\x16\x8bE\xe28\xe6\xd9g\x9f\xcd+\xe3n\xdf\xbe\x9d\xf9\xf9\xf9<jJgrOMM144\
+\xc4\xfa\xf5\xeb\xb9\xf4\xd2K\x19\x1b\x1b\x03\xba\xe1\xb3\xba\xb6\x16\xf4W\
+\xc6}9\x85\n\r\'\x8eS^\x81$\xa9\xf2qH+\xc0\xa1\x81\x83\xab\xb6$\xd2\x03\xdb\
+\xa6i\x89|\x07bK(Em\xf5\xc1\x94:\xc42 \xa87\xb1\xc7\x96\xb0;\x849\x07\xde\
+\xf6\xde{\xd8=\x17\x13a3Tv\xd8\xbf\xed)\xde\xf7\x1b\xbf\xc4\xbf\xbb\xed\\\
+\xaa@1\x82b\x1e\\\x93.\xf0\xa0\x8a\xc3>\xc0\xfd\x16\x87#+\x90\xfe3.\xa0@,\
+\x0b\xddc\xfbh\nD\x0c|\xcfFyx\x05\x92/M{\x04\xce!\xc2\xbeG\xa8[\xfd\xc2y0xuA\
+\x05\xd2w\xe4Q\xc8Cq\x17V"/g\x07B\xcfk\x8e\xbe\x03\x19\xfcY\xf6\xfc\xa4{f.L\
+\x9f\x19\xf0\xb0G\x1d\x07}\xf3\xd8{\xb1\x1e\x852\xf8}\xc1Qv\x0b\x1c\x1en\x17\
+\xd2\xfb{\x1c\xc7\xb8\xae\xdb\'\xf0\xf5\xeb\xb5\x19K\x97\xff\x88\xe387E\xe9,\
+r\x9d/\xd2\xbb\xcb0\n\xe3\xd4\xe4\x94V \xc8L(d&\x10\xd5\x1f$Kn\xc8\x1e\x8c\
+\xb8\xc7Q\xabJM\xe8\xd0\xcbL\xc8\xe7!H\t\xb1c\x13\x00?\xdc\x07\xff\xfd\x9e\
+\xef\xf1\xd9\xff\xef_`\xeaUx\xd5"\xe1\xdcv\xfe\xf8w\xdf\xcd/]\xe82\x12K*\x8d\
+iU\x86\xd6\xf5I-\x81%EV*D%\xb0\xb9\xc2V\to=Q8\xbd+\xe0tp<\xf93\xba\xb0\xb9\
+\xa6\xfb\x08\xebPV\xfd\xc7\xa3?t\x0b\xf9>\xfa\xcey\xac\x9f\x80A\x014\x10at\
+\xb4\xd3\xf4\x89\xa6|\x10\xc7\x9a\x97\xb1\xf0}\x1e~\x9e\x16\xe6\xe5\x1e\xaf8\
+\xfc\x18\xe5\x11\xe6\xff\x7f\x99\xd9\xaa\x7f\x00\x0b\\\xf8\xe5*\x10\x83aq\
+\x9c\xda\n\xe4x\x91\xd0\xee@\xd1\x07\x88\x89\xdbu\x9cB\x95\x8e\xed\xf0\xcd\
+\xe7\xe1o6=\xc3g\x1fx\x02\x86\'(\x14$\xc3r\x86\xbf\xfc\xd0;\xb9b9\x94\xa3&4g\
+`x,\xdb\x05\xf4\xac\xb5\xe3\xac"\xa9\xd3\x93\x19lu\xf5\xd5\xb1\x84Q\x1a\x0c\
+\x06\xc3\xc9\xce+z\xf9!-p\x8bd\xc9\xd4\x0e\x8e;\n\xd2\xc1\x95p\xe1\x99\xf0\
+\xde;6p\xcb\xd5ka\xf6Y\x9cV\x9d\xb9}5>\xf9?\xfe\x99:\x10\xb8e\xd2\xea\x12\
+\xa4\xe5\x13#2\x93S\x16\x91c\xbbH\xcb\xee\xb3\xfeh\xba{#\x83\xc1`8\xb51r\x8c\
+\x94N\xdc!\x89\xb3\x8cu\t\x04)~\n\xab\x0bp\xe7\xaf\\\xc9\xad\xaf[\x8f\xd7\
+\x9af\xacT\xe2\xa9\xa7\x9f\xe7\xde\x87g\xd8\x0b\xcc\xd9>M\x1c\xe2\xd4&\x8c!\
+\x92B\x994,\x10\x8e\xcd\xa0O\xb2\xd7\xe6~\\\xbew\x83\xc1`8\tx\x85+\x90\x94\
+\x84\x08\xd7q\xb0\xfd\xcc\'\x12\x05\xd8nBE4)\xc6\r6\x8c\xc2\'\xdew\x1dW\xad_\
+A4\xf7\x12\x13SK\xf9\xc4g\xee\xe1\x99\x19\xd8\x8f\xca1\xb1\xb2f\x11I\xaa:\
+\xf2I8\xd4\x89\x9e\x19\n{w GK\xe22\x18\x0c\x86\x93\x99W\xb4\x0fD\x92\x92d\
+\x1e\t\x07\xa1\xaa\xbd\xca\x14\x88@\xc4`9$\x94hHA\xdb\x82\x0f|\xe4>\xbe\xfa\
+\xd8\x16di\x88\xc9\xd3W\xf2?\xff\xef\x1b9\x1d\x18\xca\xce\x97\xf9\xe2\xb1\
+\x85jf\xb4p\xd9\x0f\x8e\x12fi0\x18\x0c\xa7\x06\xaf\xf0\x1d\x88\xc0\xc2\xa1\
+\x13\x044;\xf3 \x02\xb0\x03H\x03\x88,H|\x92\xf9\x06C\x168\x01\xfc\xf1\xfbo\
+\xe4\xda\xd7\xbf\x9a\xe9};i5S\xfe\xf6\x1f\xa7\xa9\x87\xaa\x12U\x1e]eAz\xa4\
+\xdc\nz\xfe\xf7\x8aU\xdd\x06\x83\xe1\x7f\x07^\xd1;\x10\x80 \x8c\xf1<AJ\x9b\
+\x98\x00+\x95x\xa2\x00\xd2\'\xaeE8\xe5"\xcd\xfa<\xa5\xd1a\x1a\xc0\xde\x08\
+\xfe\x9fOm\xe2\x9e\x07\x9ed\xbcZ\xe1\x0b\x1f\x7f\x0f+\x86aH\xb7}HUu\x93\xbcO\
+\xf9\xb1\xcc\xae\xd9\x89\x18\x0c\x86S\x90W\xbc\x02\xd1="b"\x12R$\x0e\x16.\
+\x0e\x02;\x05\x19\x82\xe5\x02v@\x8cK\x0b\xc1\xee\x06\xfc\xc5\xe77\xf3\xb9\
+\xcf~\x8e\xcb6L\xf1\xc9?y?\xd5\xa2\xeaI^t\x94>\xb0\xe5Q\xccX\xbd\x18\x05b0\
+\x18NA^\xe1&\xac\xcc\xdf\x91\x15\xb5\x92\x14IPa\xb9\x11\x10\n\x95\xd4\xae\
+\xd2\xb7-D\x12Q\x02VU\xe0-Wo\xe4\xfd\xef}\x0b?\xdd\xb9\x95\xcf\xfd\xcd?\x91H\
+\xd5D\xb0\xd9V\xfa \x05\xa2H\x1e]9\x18\xe5a0\x18NQ^\xe1\xddZ\x12T\xa9E\x81E%\
+\x97\xe5\xba\x1b\x87\rx\xba\xa2t\xe2!\xc3\x04\xc7\x07G\xc0\x85\xabm\xce\x9c\
+\xba\x94\xb8\xbe\x9b\xfb\x1e\xb8\x8f\r\xe7\x9d\xcd\x95\x97\xad\xa3X\x84z+a\
+\xb8d\xd3\xea\xb4\xf0\xdcr\x7f\x06\xf0\x91\xfaU\x18\x0c\x06\xc3)\xc4+\xdc\
+\x84\x15\x01Y\x97AY!\x86\xbc\xf4\x89\x0e\x92rH\xb1\xb59+\x02+FURt;\xaaXc\xea\
+\xf2\x17\x7f\xf5\xcf\xfc\xe0\xe9\xef\xf3_\xff\xe8\x83\x0c\x952\xdfG\xaa\x14M\
+^\xc3J\xa3\xcb\x7f\x1cRO\xc4`0\x18N-^\xe1\xcba\x0b\xb5\xcfpA\x82#\xc1O\xc1\
+\x97\xaaw\x88CJB\xa2r;t\xddB\x1fU\xc40\x89\x80\x10_\xc0\xaf\xdeq\x13\xeb\xd7\
+\x9f\xcb\xc7>\xf6Q\x820\xb3xI]\xb4$\xa5\xafi\x93e\x82\xaf\x0c\x06\xc3\xff\
+\x1e\xbc\xc2\x15\x88\x03\x14\xb3\xca\xbc\x80T;\x07[\x82#S<\xc0\xc5\xc5\xb2T\
+\xa8n\xa2\xb3\xff\x1c\x0b\x1c\x07\xa4C\'Pj\xe8W\xdfq\x1b#\xc3C<\xf3\xcc\xd3\
+\xb4:!\xaem\xd3\xdd\xcb\xa8\xf2\x89\xbd\xd1\xbd\xd2:r\x05]\x83\xc1`8\xd9ye+\
+\x10\tR\x8a\xcc/\x11\x03M\xa0\x06i\x03\x92\x04\x12\x81\x93\x85\xe5JK\xa5\x86\
+\xc4\xba\xe4UZ\x82\xc4\xa7\xe0C,\x95\xb9\xea?\xbc\xf7\xd7\xd8\xb5k\x07\xb6c\
+\xd1h\xd5\x06/\x95\x93\xf6|\x99\xdd\x88\xc1`8Uye\xfb@d\xd6V\x03\xc8\xfd!2\
+\x06\xe9\xa8\x86S\x96\xab\xdau\xb8\x90d\xa6\'A\x82\x83\xad\xfc\xe2\x12\x02[)\
+\x10a\xa90^\xdf\x81m\xdb\x9e\xe3\xec5k\xb0Hz\\\x1c\xdd"&\xc7\xd6R\xd5`0\x18N\
+n^\xd9\n\x04\x06\xfaR$=6\xa6,~w\x01SS\xde\xb0h\xa0I\x11\x0c6\x11Z\xa8\x01\
+\xd3`S)\x83\xc1`851\n\xc4`0\x18\x0c\x8b\xe2\x95\xed\x031\x18\x0c\x06\xc3\xa2\
+1\n\xc4`0\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\x0c\x06\x83\xc1\xb0(\x8c\
+\x021\x18\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\
+\x0c\x06\x83\xc1\xb0(\x8c\x021\x18\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xc2(\
+\x10\x83\xc1`0,\n\xa3@\x0c\x06\x83\xc1\xb0(\x8c\x021\x18\x0c\x06\xc3\xa20\n\
+\xc4`0\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\x0c\x06\x83\xc1\xb0(\x8c\x021\
+\x18\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\x0c\x06\
+\x83\xc1\xb0(\x8c\x021\x18\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xc2(\x10\x83\
+\xc1`0,\n\xa3@\x0c\x06\x83\xc1\xb0(\x8c\x021\x18\x0c\x06\xc3\xa20\n\xc4`0\
+\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\x0c\x06\x83\xc1\xb0(\x8c\x021\x18\
+\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xc2(\x10\x83\xc1`0,\n\xa3@\x0c\x06\x83\
+\xc1\xb0(\x8c\x021\x18\x0c\x06\xc3\xa20\n\xc4`0\x18\x0c\x8b\xe2\xff\x07\xc1\
+\x94N90\x82\xbf\xc4\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getSplashBitmap():
+    return BitmapFromImage(getSplashImage())
+
+def getSplashImage():
+    stream = cStringIO.StringIO(getSplashData())
+    return ImageFromStream(stream)
+
+#----------------------------------------------------------------------
+def getActiveGridData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\
+\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x05cID\
+ATX\x85\xed\x97\xcbo\x1b\xd7\x15\xc6\x7fw^\x1c\x92C\x8a\xa4DQ\xa2)\xd9\xb2%?\
+\xe0\xa7\x82\xc0\x85\x83"\xa8\x1b\x17AwYtQtU\xb4\x8b\xa2\x7fH\xfe\x8c\xee\
+\x8b\xac\x124\xab\x02E\n\x17h\x01\x17\x86Q7\xaac7~I\xb2hZ\xe2sH\xce\x83s\xef\
+\xedB\xa6dI~))\x90E\xf2\x01\x17\x98\xb9s\xe7\x9c\xef\x9c\xf9\xce\xb9w\x840L\
+\xbeK\x18\xdf\xa9\xf7\x1f\x08\x00\xa2\xd5ji!\x04\xe3\xf1ma\x9a\x02!\x0c\xe0\
+\xa0-\xa55O\xb6\x06t\xfb\x11\xa0\x98\xca\x99\x880\x0c5\xf0R\xe7B\x08\xb4\xde\
+\xbd6\xc4\xd8\xee\x0bk\xb5\xde3\'\x84f\x10J\x06aB4R\xd8\x96\xc1\x97\x8f{\xfc\
+\xfbq\x97-?\xa2\xe49\x9c\x9d\xf38u\xc4\xa3\x945\xb0\xc6\x8e\r!`\x1f\t!\xd8C\
+\x004\x89\xd4\x04\xb1$\x88%#\xa9(f,\xd6\xdb!\xab\x9b\x01\x9b\xbd\x98 \x96\
+\xb4z\x11\xa6\x80\xea\x94K\x9ch\xfe\xf5\xa0\xcb\xfa\xd6\x90\xea\xa4\xcb\x99Z\
+\x8e\xead\x96\xa2\x97\xc2\x14\t\xd6s\xf3h\x04JC"\xf5\xf3\xa1\x00M.c\xd1\xe9\
+\'\xf4\x82\x11\xc3H\xd2\x0f\x13\xda~L\xcb\x8fI\x12\xc9\x953\x93\\\xff\xaa\
+\xc9\x17\xb7\xb7\xf8j\xdd\xa7\x13J\x82aB\xad\x94\xe2\x83\xe5)\xba\xc3\x84\
+\xde a\xa6\x98\xe2\xc3wf\xb8\xbcX\xa2\xe89(\xa5\x08\x82\xd1\x98\x00\x04qB/\
+\x1c\xd1\xf6Gl\xf6"\x9euc\x84\xd0\xfc\xf4\\\x99Oo\xd4\xf9\xe2\xf6\x16w\x9f\
+\x0chG\t\xbe\x1f\x13\xf9#\xa63\x16\x1f\xff\xee\x027\xefw\xb9\xf5\xb0K\xc7\
+\x8f\x11\xa6`a\xc6\xe5\xdc\xbc\xc7\xfcT\x06/msa~\x82\xa5\xd9\x1c\x8em`\x08\
+\xd0Z\xa1\x94\x02\xc0\xb2,\x8b\x8d\xe6\x90\xcfnl\xf0\xf9\xcd\x06\xf1H\x13E\
+\x92h0\xa2\x906\xe9\x0eF\xf4#I<\x88\xb9w\xa7I\x9cs\xc8\xa5-\xcae\x97\xa3\x93\
+i\xdc\x94\xa0\xe4\xd9\x143\x16\xfd~\xc4\xf4D\x8ak\x17\xa6\xb9z\xae\xcc\xd1r\
+\x06\xc76)dm\xb2)\x03\xa5\xf7jLk\xb0\xc6\x9f~\xbd\x19r}\xa5\xc9\xb0\x9fl?\
+\x1d)&2\x16n\xe9\x19?_.sf>\xcf\xbd\xc7>x6\xaeka\n0S&~\x980\x88\x12l[\xb08\
+\x9b\xe1\xda\xa5\nW\xcfW8;\x9f\'\xefZ;\x02\xd5Z\xa3\xb5~\xae\xa5\xdd\xaa\xb3\
+\x94R\x94<\x87\xc5\xaa\xc7\xe9#9V\xee\xb61\x1d\x13\xc7\xb3I\xa7L\xfe[\x1f\
+\xf0\xd1\xe5\x19\x96O\x97\x08\x84\xa6\xd1\x0c\xe9\r\x136\xfd\x98F7f\xbd\x19Q\
+\xefD\xa4]\x93\xf7O\x95\xf9\xed\xb5\x05\xa6\x0bi\xd0\xa0\xb5\x06\xa5w\x8a\
+\xe6\xc5J\x13B`Y\x16\x96\x94\n\xc76\xf9\xd9\xc5il\x03>\x1e\xc6\x94\x8b.\xc7g\
+2\xcc\x16]\xc2(a\xbd\x19\xa2\xd0,U\xb2\xfc\xf1\xcf\xab\xb4\xba#\xd2\x9eM\xed\
+H\x96 N\xa8\xe4m~\xb4X\xe47W\x8f\x92\xcf\xd8\xe8\xfd\xb9~\x05l\xdb\xde\x16\
+\xa1R\x8a\xa9\xbc\xc3\xd5\xf3\x15\x8a\x9e\xc3\xadG\x1dV\xd6|\xfe\xfa\xe5\x16\
+\x83@"\xa4f\xf9D\x9eKKE\xe6k9\xaa\x15I\xca1\xc9y\x16\xbd0ay\xa1\xc0\xf2B\x91\
+B\xd6\xd9\x8ez\x7f-\xbf\x04\xe3lX\xdb\xcdF\xe3\x98\x06\xd5\x92Kmj\x96l\xc6\
+\xa4\xd1\x89\xf8\xc7\x9d6O\x9e\x05\xa8 \xc1\x16P\x9b\xcd\xf2\xd1{U\xfe\xb3\
+\xda\xe5\xd1\xd3!A?\xa1\x92Oq\xf1X\x81\x93\xd5\xdc[E\xbd\x1f;e8f\xae\xb5\xe0\
+lm\x82\xa7\xa7c\xd67CB\x7fD\xa4!\x1a):\xc3\x84_\xfd\xf8\x08\x1b\xad!\x8f\x1a\
+CD\xa4x\xf7x\x81\xc5\x19\x8fl\xcaDJu\xe8v.\xe28\xd6cu\x8e\xb3\xa1\x81`\xa4y\
+\xd8\x18\xf0\xc9\xdf\xd6ht\x02\x0c\xd3`\xc2\xb3\t\xa5\xa2\xde\x8eX\xdb\n0\
+\x81?\xfc\xfe"\x8b3y,\xcb\xf8F\x04,8\xb8\x0f\x18B\xe0\xa5\x04K\xb3Y~\xf9\xfe\
+\x1c\xc3(\xe1\xc6\xd7m>\xffg\x9d\x87\xf7{,\x1d\xcfsr6K\xde5\x01\x81T\x1a\xeb\
+%v\xde\x9a\xc0\x9e\x94<7\xa2\xb5&e\x19\x9c\x9d\xcbo\xef\th\xee\xac\xf6xp\xb7\
+\x8b\x1f\x8c\xa8\x98i\xe6\xa6\\6\xfd\x98\xf2\xc4\xb6(w\xeb\xfc[\x10x\x81\xca\
+\x9e\xe6qy\xb1Dm2\x83e\x18\xdcZ\xed\xd2\xe8\x84,L\xbb\xdc\xaf\x0f\xa8\x163L\
+\xe6R\x87\x0b}\xec%\x8e\xe3\x9d\xba\xd9\xcf~,\xcc\xf1\xbc\xd2\xb0\xd9\r\xb8\
+\xf9\xa0\xc3\xdf\xef5Yy\xd2\xe7|-\xc7/\xae\xd4\xb8t\xac\x88\x94\xf2\xff\x99\
+\x81\x83\x84L\x01\xd5R\x1a\xcb2\t\x13\xcd\xd7\x8d!\xd7\xef\xb4x\xf7D\x89ss\
+\x13\x98\xc6\xee\xf9\xe1M\xd0Z\x93$\xc9\xe1\x8edZk\x94\x86r>\xc5\x85\xa3\x05\
+\xde;9\x89\xd2\xb0\xb2\xd6\xe3\xee\x86\x8fa\x18\xe3\x85oM\xe0\xb5\x198\x00!P\
+J\x03\x9a\xc5J\x86_\xff\xe4\x18\x00\xb7\x1ev\xf8\xd3\xcd\xa7,\xcd\xe6\xb0\
+\x0e\x11\x92R\xea\xf5\x1ax\x15\xf3\x9dk\xa0\xd9O\xf8\xcb\xed\x06\x1b\xed\x80\
+\x13\x95,\x1f\x9c\x9f\xc6s\xdf\x1c\xd7\xf6\x81$\xc08\xd0\xbb\xdf\x80=;\x1a0\
+\x9dw\xb8rj\x92w\x16\nH\xa9h\xf9\x11\xe1H\x1e \xfb*[\x96\x94r\xe7\xe6\xb0\n\
+\xd6Z\xa3\x94b\xae\x94"\x97\x12<\xde2\x08\xa2\x98 2\xb0\r\xe7\xb5}AJ\xb9]5\
+\xf5z]\x03\xbb\x02\xfa\x06\x10\x80m\x1b\x18\xa6\xc9\xda3\x1f\xd71\xc9\xb9\
+\xf6k\xdf\x91R\x12E\x11\xe2\x87\x7f\xc3\xef=\x81\xff\x01\x1d\xae\x83\xc3q\
+\xb9\xc6\x9f\x00\x00\x00\x00IEND\xaeB`\x82'
+
+def getActiveGridBitmap():
+    return BitmapFromImage(getActiveGridImage())
+
+def getActiveGridImage():
+    stream = cStringIO.StringIO(getActiveGridData())
+    return ImageFromStream(stream)
+
+def getActiveGridIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getActiveGridBitmap())
+    return icon
+    
+#----------------------------------------------------------------------
+def getDPLData():
+    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\x00LIDAT8\x8dc<p\xe0\xc0\x7f\x06\n\x00\x13%\x9a\x07\xa1\x01\x0e\x0e\xf6\
+\x0c\x0e\x0e\xf6x\xf9\x04]p\xe0\xc0A\x14\x85\xe8|\x82\x06\x90\nho\x80\x83\
+\x83=\xc3\x81\x03\x07\xc97\x00\x9ff\xa2\x0c \x040\x0c \xe4d\xf4\x18a\x1c\xcd\
+\x0b\x0c\x00\x81@\x19X*\x8dbG\x00\x00\x00\x00IEND\xaeB`\x82'
+
+def getDPLBitmap():
+    return BitmapFromImage(getDPLImage())
+
+def getDPLImage():
+    stream = cStringIO.StringIO(getDPLData())
+    return ImageFromStream(stream)
+
+def getDPLIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getDPLBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/IDEFindService.py b/wxPython/samples/ide/activegrid/tool/IDEFindService.py
new file mode 100644 (file)
index 0000000..cd1b74b
--- /dev/null
@@ -0,0 +1,445 @@
+#----------------------------------------------------------------------------
+# Name:         IDEFindService.py
+# Purpose:      Find Service for pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      8/15/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import os
+from os.path import join
+import re
+import ProjectEditor
+import MessageService
+import FindService
+import OutlineService
+_ = wx.GetTranslation
+
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+FILENAME_MARKER = _("Found in file: ")
+PROJECT_MARKER = _("Searching project: ")
+FIND_MATCHDIR = "FindMatchDir"
+FIND_MATCHDIRSUBFOLDERS = "FindMatchDirSubfolders"
+
+SPACE = 10
+HALF_SPACE = 5
+
+
+class IDEFindService(FindService.FindService):
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    FINDALL_ID = wx.NewId()         # for bringing up Find All dialog box
+    FINDDIR_ID = wx.NewId()         # for bringing up Find Dir dialog box
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        FindService.FindService.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+        wx.EVT_MENU(frame, IDEFindService.FINDALL_ID, self.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, IDEFindService.FINDALL_ID, self.ProcessUpdateUIEvent)
+        editMenu.Append(IDEFindService.FINDALL_ID, _("Find in Project...\tCtrl+Shift+F"), _("Searches for the specified text in all the files in the project"))
+        wx.EVT_MENU(frame, IDEFindService.FINDDIR_ID, self.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, IDEFindService.FINDDIR_ID, self.ProcessUpdateUIEvent)
+        editMenu.Append(IDEFindService.FINDDIR_ID, _("Find in Directory..."), _("Searches for the specified text in all the files in the directory"))
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == IDEFindService.FINDALL_ID:
+            self.ShowFindAllDialog()
+            return True
+        elif id == IDEFindService.FINDDIR_ID:
+            self.ShowFindDirDialog()
+            return True
+        else:
+            return FindService.FindService.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == IDEFindService.FINDALL_ID:
+            projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+            view = projectService.GetView()
+            if view and view.GetDocument() and view.GetDocument().GetFiles():
+                event.Enable(True)
+            else:
+                event.Enable(False)
+            return True
+        elif id == IDEFindService.FINDDIR_ID:
+            event.Enable(True)
+        else:
+            return FindService.FindService.ProcessUpdateUIEvent(self, event)
+
+
+    def ShowFindDirDialog(self):
+        config = wx.ConfigBase_Get()
+
+        frame = wx.Dialog(None, -1, _("Find in Directory"), size= (320,200))
+        borderSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        contentSizer = wx.BoxSizer(wx.VERTICAL)
+        lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+        lineSizer.Add(wx.StaticText(frame, -1, _("Directory:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
+        dirCtrl = wx.TextCtrl(frame, -1, config.Read(FIND_MATCHDIR, ""), size=(200,-1))
+        dirCtrl.SetToolTipString(dirCtrl.GetValue())
+        lineSizer.Add(dirCtrl, 0, wx.LEFT, HALF_SPACE)
+        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()
+            if len(dir):
+                dlg.SetPath(dir)
+            if dlg.ShowModal() == wx.ID_OK:
+                dirCtrl.SetValue(dlg.GetPath())
+                dirCtrl.SetToolTipString(dirCtrl.GetValue())
+                dirCtrl.SetInsertionPointEnd()
+
+            dlg.Destroy()
+        wx.EVT_BUTTON(findDirButton, -1, OnBrowseButton)
+
+        subfolderCtrl = wx.CheckBox(frame, -1, _("Search in subfolders"))
+        subfolderCtrl.SetValue(config.ReadInt(FIND_MATCHDIRSUBFOLDERS, True))
+        contentSizer.Add(subfolderCtrl, 0, wx.BOTTOM, SPACE)
+
+        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)
+        
+        lineSizer = wx.BoxSizer(wx.HORIZONTAL)
+        lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE)
+        findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), 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()
+        buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+        buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0)
+        borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE)
+
+        frame.SetSizer(borderSizer)
+        frame.Fit()
+
+        status = frame.ShowModal()
+
+        # save user choice state for this and other Find Dialog Boxes
+        dirString = dirCtrl.GetValue()
+        searchSubfolders = subfolderCtrl.IsChecked()
+        self.SaveFindDirConfig(dirString, searchSubfolders)
+        
+        findString = findCtrl.GetValue()
+        matchCase = matchCaseCtrl.IsChecked()
+        wholeWord = wholeWordCtrl.IsChecked()
+        regExpr = regExprCtrl.IsChecked()
+        self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
+        
+        while not os.path.exists(dirString):
+            dlg = wx.MessageDialog(frame,
+                                   _("'%s' does not exist.") % dirString,
+                                   _("Find in Directory"),
+                                   wx.OK | wx.ICON_EXCLAMATION
+                                   )
+            dlg.ShowModal()
+            dlg.Destroy()
+            
+            status = frame.ShowModal()
+
+            # save user choice state for this and other Find Dialog Boxes
+            dirString = dirCtrl.GetValue()
+            searchSubfolders = subfolderCtrl.IsChecked()
+            self.SaveFindDirConfig(dirString, searchSubfolders)
+            
+            findString = findCtrl.GetValue()
+            matchCase = matchCaseCtrl.IsChecked()
+            wholeWord = wholeWordCtrl.IsChecked()
+            regExpr = regExprCtrl.IsChecked()
+            self.SaveFindConfig(findString, wholeWord, matchCase, regExpr)
+            
+            if status == wx.ID_CANCEL:
+                break
+            
+
+        if status == wx.ID_OK:            
+            frame.Destroy()
+
+            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
+        
+                            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."))
+                wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+            return True
+        else:
+            frame.Destroy()
+            return False
+            
+
+    def SaveFindDirConfig(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 ShowFindAllDialog(self):
+        config = wx.ConfigBase_Get()
+
+        frame = wx.Dialog(None, -1, _("Find in Project"), size= (320,200))
+        borderSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        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)
+        findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), 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()
+        buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE)
+        buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0)
+        borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE)
+
+        frame.SetSizer(borderSizer)
+        frame.Fit()
+
+        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)
+
+        if status == wx.ID_OK:
+            frame.Destroy()
+
+            messageService = wx.GetApp().GetService(MessageService.MessageService)
+            messageService.ShowWindow()
+
+            view = messageService.GetView()
+            if view:
+                view.ClearLines()
+                view.SetCallback(self.OnJumpToFoundLine)
+
+                projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+                projectFilenames = projectService.GetFilesFromCurrentProject()
+
+                projView = projectService.GetView()
+                if projView:
+                    projName = wx.lib.docview.FileNameFromPath(projView.GetDocument().GetFilename())
+                    view.AddLines(PROJECT_MARKER + projName + "\n\n")
+
+                # 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)
+
+            return True
+        else:
+            frame.Destroy()
+            return False
+
+
+    def OnJumpToFoundLine(self, event):
+        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:
+            return
+        lineEnd = lineText.find(":")
+        if lineEnd == -1:
+            return
+        else:
+            lineNum = int(lineText[0:lineEnd])
+
+        text = messageService.GetView().GetText()
+        curPos = messageService.GetView().GetCurrentPos()
+
+        startPos = text.rfind(FILENAME_MARKER, 0, curPos)
+        endPos = text.find("\n", startPos)
+        filename = text[startPos + len(FILENAME_MARKER):endPos]
+
+        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()
+            if hasattr(foundView, "GotoLine"):
+                foundView.GotoLine(lineNum)
+                startPos = foundView.PositionFromLine(lineNum)
+                # wxBug:  Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct
+                #         Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first
+                #         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/ImageEditor.py b/wxPython/samples/ide/activegrid/tool/ImageEditor.py
new file mode 100644 (file)
index 0000000..a95b139
--- /dev/null
@@ -0,0 +1,93 @@
+#----------------------------------------------------------------------------
+# Name:         ImageEditor.py
+# Purpose:      Image Editor for pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      12/24/04
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# CVS-ID:       $Id$
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+import wx
+import wx.lib.docview
+_ = wx.GetTranslation
+
+
+class ImageDocument(wx.lib.docview.Document):
+    pass
+
+
+class ImageView(wx.lib.docview.View):
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self):
+        wx.lib.docview.View.__init__(self)
+        self._ctrl = None
+
+
+    def OnCreate(self, doc, flags):
+        if len(doc.GetFilename()) == 0:
+            wx.MessageBox(_("Cannot create a new image file.\n%s has no paint capability.") % wx.GetApp().GetAppName(),
+                                _("New Image File"),
+                                wx.OK | wx.ICON_EXCLAMATION)
+            return False
+
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
+        panel = wx.Panel(frame, -1)            
+        bitmap = wx.Image(doc.GetFilename()).ConvertToBitmap()
+        self._ctrl = wx.StaticBitmap(panel, -1, bitmap, (0,0), (bitmap.GetWidth(), bitmap.GetHeight()))
+        panel.SetClientSize(bitmap.GetSize())
+        frame.SetClientSize(panel.GetSize())
+        self.Activate()
+        return True
+
+
+    def OnClose(self, deleteWindow = True):
+        statusC = wx.GetApp().CloseChildDocuments(self.GetDocument())
+        statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow)
+        if not (statusC and statusP):
+            return False
+        self.Activate(False)
+        if deleteWindow:
+            self.GetFrame().Destroy()
+        return True
+
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getImageData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0f\x00\x00\x00\x0e\x08\x06\
+\x00\x00\x00\xf0\x8aF\xef\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
+\x00\x00\x97IDAT(\x91\x9d\x93Q\n\xc4 \x0cD\'\xda\xd3\xa9\xe9ac\xdb\x8bx\xa0\
+\xf4C"\xd6mAw@\x0c1/\t\x03\x123+\x16\x95s\x06\x00l\x00p\x9c\x17\xad\xc0\xe4<\
+R\x0c\xeaf\x81\x14\x83\xa6\x18\x1e[N\xc1)\x06\x15\x01Dj\xbc\x04\x7fi\x9b):\
+\xce\x8b\xf6\xbdN\xec\xfd\x99\x82G\xc8\xf4\xba\xf6\x9b9o\xfa\x81\xab9\x02\
+\x11i\xe6|6cf%\xe7A\xce\x83\x99\xd5\xc4\xccZJ\xd11\xd7\xd76\xd8\x8aJ)\xed\
+\xb6c\x8d,~\xc0\xe3\xe3L\xdc\xe0~\xcaJ\x03\xfa\xe7c\x98n\x01\x88\xc6k\xb1\
+\x83\x04\x87\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+
+def getImageBitmap():
+    return BitmapFromImage(getImageImage())
+
+def getImageImage():
+    stream = cStringIO.StringIO(getImageData())
+    return ImageFromStream(stream)
+
+def getImageIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getImageBitmap())
+    return icon
+
diff --git a/wxPython/samples/ide/activegrid/tool/MarkerService.py b/wxPython/samples/ide/activegrid/tool/MarkerService.py
new file mode 100644 (file)
index 0000000..54375dd
--- /dev/null
@@ -0,0 +1,91 @@
+#----------------------------------------------------------------------------
+# Name:         MarkerService.py
+# Purpose:      Adding and removing line markers in text for easy searching
+#
+# Author:       Morgan Hua
+#
+# Created:      10/6/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.stc
+import wx.lib.docview
+import wx.lib.pydocview
+import STCTextEditor
+_ = wx.GetTranslation
+
+
+class MarkerService(wx.lib.pydocview.DocService):
+    MARKERTOGGLE_ID = wx.NewId()
+    MARKERDELALL_ID = wx.NewId()
+    MARKERNEXT_ID = wx.NewId()
+    MARKERPREV_ID = wx.NewId()
+
+
+    def __init__(self):
+        pass
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        if document and document.GetDocumentTemplate().GetDocumentType() != STCTextEditor.TextDocument:
+            return
+        if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            return
+
+        editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+        editMenu.AppendSeparator()
+        editMenu.Append(MarkerService.MARKERTOGGLE_ID, _("Toggle &Marker\tCtrl+M"), _("Toggles a jump marker to 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"))
+        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"))
+        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"))
+        wx.EVT_MENU(frame, MarkerService.MARKERPREV_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, MarkerService.MARKERPREV_ID, frame.ProcessUpdateUIEvent)
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == MarkerService.MARKERTOGGLE_ID:
+            wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle()
+            return True
+        elif id == MarkerService.MARKERDELALL_ID:
+            wx.GetApp().GetDocumentManager().GetCurrentView().MarkerDeleteAll()
+            return True
+        elif id == MarkerService.MARKERNEXT_ID:
+            wx.GetApp().GetDocumentManager().GetCurrentView().MarkerNext()
+            return True
+        elif id == MarkerService.MARKERPREV_ID:
+            wx.GetApp().GetDocumentManager().GetCurrentView().MarkerPrevious()
+            return True
+        else:
+            return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == MarkerService.MARKERTOGGLE_ID:
+            view = wx.GetApp().GetDocumentManager().GetCurrentView()
+            event.Enable(hasattr(view, "MarkerToggle"))
+            return True
+        elif id == MarkerService.MARKERDELALL_ID:
+            view = wx.GetApp().GetDocumentManager().GetCurrentView()
+            event.Enable(hasattr(view, "MarkerDeleteAll") and view.GetMarkerCount())
+            return True
+        elif id == MarkerService.MARKERNEXT_ID:
+            view = wx.GetApp().GetDocumentManager().GetCurrentView()
+            event.Enable(hasattr(view, "MarkerNext") and view.GetMarkerCount())
+            return True
+        elif id == MarkerService.MARKERPREV_ID:
+            view = wx.GetApp().GetDocumentManager().GetCurrentView()
+            event.Enable(hasattr(view, "MarkerPrevious") and view.GetMarkerCount())
+            return True
+        else:
+            return False
+
diff --git a/wxPython/samples/ide/activegrid/tool/MessageService.py b/wxPython/samples/ide/activegrid/tool/MessageService.py
new file mode 100644 (file)
index 0000000..288d40d
--- /dev/null
@@ -0,0 +1,143 @@
+#----------------------------------------------------------------------------
+# Name:         MessageService.py
+# Purpose:      Message View Service for pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      9/2/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import Service
+import STCTextEditor
+
+class MessageView(Service.ServiceView):
+    """ Reusable Message View for any document.
+        When an item is selected, the document view is called back (with DoSelectCallback) to highlight and display the corresponding item in the document view.
+    """
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def _CreateControl(self, parent, id):
+        txtCtrl = STCTextEditor.TextCtrl(parent, id)
+        txtCtrl.SetMarginWidth(1, 0)  # hide line numbers
+        txtCtrl.SetReadOnly(True)
+
+        if wx.Platform == '__WXMSW__':
+            font = "Courier New"
+        else:
+            font = "Courier"
+        txtCtrl.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font))
+        txtCtrl.SetFontColor(wx.BLACK)
+        txtCtrl.StyleClearAll()
+        txtCtrl.UpdateStyles()
+
+        return txtCtrl
+
+
+##    def ProcessEvent(self, event):
+##        stcControl = self.GetControl()
+##        if not isinstance(stcControl, wx.stc.StyledTextCtrl):
+##            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+##        id = event.GetId()
+##        if id == wx.ID_CUT:
+##            stcControl.Cut()
+##            return True
+##        elif id == wx.ID_COPY:
+##            stcControl.Copy()
+##            return True
+##        elif id == wx.ID_PASTE:
+##            stcControl.Paste()
+##            return True
+##        elif id == wx.ID_CLEAR:
+##            stcControl.Clear()
+##            return True
+##        elif id == wx.ID_SELECTALL:
+##            stcControl.SetSelection(0, -1)
+##            return True
+##
+##
+##    def ProcessUpdateUIEvent(self, event):
+##        stcControl = self.GetControl()
+##        if not isinstance(stcControl, wx.stc.StyledTextCtrl):
+##            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+##        id = event.GetId()
+##        if id == wx.ID_CUT:
+##            event.Enable(stcControl.CanCut())
+##            return True
+##        elif id == wx.ID_COPY:
+##            event.Enable(stcControl.CanCopy())
+##            return True
+##        elif id == wx.ID_PASTE:
+##            event.Enable(stcControl.CanPaste())
+##            return True
+##        elif id == wx.ID_CLEAR:
+##            event.Enable(True)  # wxBug: should be stcControl.CanCut()) but disabling clear item means del key doesn't work in control as expected
+##            return True
+##        elif id == wx.ID_SELECTALL:
+##            event.Enable(stcControl.GetTextLength() > 0)
+##            return True
+
+        
+    #----------------------------------------------------------------------------
+    # Service specific methods
+    #----------------------------------------------------------------------------
+
+    def ClearLines(self):
+        self.GetControl().SetReadOnly(False)
+        self.GetControl().ClearAll()
+        self.GetControl().SetReadOnly(True)
+
+
+    def AddLines(self, text):
+        self.GetControl().SetReadOnly(False)
+        self.GetControl().AddText(text)
+        self.GetControl().SetReadOnly(True)
+
+
+    def GetText(self):
+        return self.GetControl().GetText()
+
+
+    def GetCurrentPos(self):
+        return self.GetControl().GetCurrentPos()
+
+
+    def GetCurrLine(self):
+        return self.GetControl().GetCurLine()
+
+
+    #----------------------------------------------------------------------------
+    # Callback Methods
+    #----------------------------------------------------------------------------
+
+    def SetCallback(self, callback):
+        """ Sets in the event table for a doubleclick to invoke the given callback.
+            Additional calls to this method overwrites the previous entry and only the last set callback will be invoked.
+        """
+        wx.stc.EVT_STC_DOUBLECLICK(self.GetControl(), self.GetControl().GetId(), callback)
+
+
+
+class MessageService(Service.Service):
+
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    SHOW_WINDOW = wx.NewId()  # keep this line for each subclass, need unique ID for each Service
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def _CreateView(self):
+        return MessageView(self)
+        
+        
diff --git a/wxPython/samples/ide/activegrid/tool/OutlineService.py b/wxPython/samples/ide/activegrid/tool/OutlineService.py
new file mode 100644 (file)
index 0000000..95ccd9e
--- /dev/null
@@ -0,0 +1,520 @@
+#----------------------------------------------------------------------------
+# Name:         OutlineService.py
+# Purpose:      Outline View Service for pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      8/3/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import wx.lib.pydocview
+import Service
+_ = wx.GetTranslation
+
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+SORT_NONE = 0
+SORT_ASC = 1
+SORT_DESC = 2
+
+class OutlineView(Service.ServiceView):
+    """ Reusable Outline View for any document.
+        As a default, it uses a modified tree control (OutlineTreeCtrl) that allows sorting.
+        Subclass OutlineTreeCtrl to customize the tree control and call SetTreeCtrl to install a customized tree control.
+        When an item is selected, the document view is called back (with DoSelectCallback) to highlight and display the corresponding item in the document view.
+    """
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, service):
+        Service.ServiceView.__init__(self, service)
+        self._actionOnSelect = True
+
+
+    def _CreateControl(self, parent, id):
+        treeCtrl = OutlineTreeCtrl(parent, id)
+        wx.EVT_TREE_SEL_CHANGED(treeCtrl, treeCtrl.GetId(), self.DoSelection)
+        wx.EVT_SET_FOCUS(treeCtrl, self.DoSelection)
+        wx.EVT_ENTER_WINDOW(treeCtrl, treeCtrl.CallDoLoadOutlineCallback)
+        wx.EVT_RIGHT_DOWN(treeCtrl, self.OnRightClick)
+
+        return treeCtrl
+
+
+    #----------------------------------------------------------------------------
+    # Service specific methods
+    #----------------------------------------------------------------------------
+
+    def OnRightClick(self, event):
+        menu = wx.Menu()
+
+        menu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order"))
+        menu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order"))
+        menu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order"))
+
+        config = wx.ConfigBase_Get()
+        sort = config.ReadInt("OutlineSort", SORT_NONE)
+        if sort == SORT_NONE:
+            menu.Check(OutlineService.SORT_NONE, True)
+        elif sort == SORT_ASC:
+            menu.Check(OutlineService.SORT_ASC, True)
+        elif sort == SORT_DESC:
+            menu.Check(OutlineService.SORT_DESC, True)
+
+        self.GetControl().PopupMenu(menu, event.GetPosition())
+        menu.Destroy()
+
+
+    #----------------------------------------------------------------------------
+    # Tree Methods
+    #----------------------------------------------------------------------------
+
+    def DoSelection(self, event):
+        if not self._actionOnSelect:
+            return
+        item = self.GetControl().GetSelection()
+        if item:
+            self.GetControl().CallDoSelectCallback(item)
+
+
+    def ResumeActionOnSelect(self):
+        self._actionOnSelect = True
+
+
+    def StopActionOnSelect(self):
+        self._actionOnSelect = False
+
+
+    def SetTreeCtrl(self, tree):
+        self.SetControl(tree)
+        wx.EVT_TREE_SEL_CHANGED(self.GetControl(), self.GetControl().GetId(), self.DoSelection)
+        wx.EVT_ENTER_WINDOW(self.GetControl(), treeCtrl.CallDoLoadOutlineCallback)
+        wx.EVT_RIGHT_DOWN(self.GetControl(), self.OnRightClick)
+
+
+    def GetTreeCtrl(self):
+        return self.GetControl()
+
+
+    def OnSort(self, sortOrder):
+        treeCtrl = self.GetControl()
+        treeCtrl.SetSortOrder(sortOrder)
+        treeCtrl.SortAllChildren(treeCtrl.GetRootItem())
+
+
+    def ClearTreeCtrl(self):
+        if self.GetControl():
+            self.GetControl().DeleteAllItems()
+
+
+    def GetExpansionState(self):
+        expanded = []
+
+        treeCtrl = self.GetControl()
+        if not treeCtrl:
+            return expanded
+
+        parentItem = treeCtrl.GetRootItem()
+
+        if not parentItem:
+            return expanded
+
+        if not treeCtrl.IsExpanded(parentItem):
+            return expanded
+
+        expanded.append(treeCtrl.GetItemText(parentItem))
+
+        (child, cookie) = treeCtrl.GetFirstChild(parentItem)
+        while child.IsOk():
+            if treeCtrl.IsExpanded(child):
+                expanded.append(treeCtrl.GetItemText(child))
+            (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie)
+        return expanded
+
+
+    def SetExpansionState(self, expanded):
+        if not expanded or len(expanded) == 0:
+            return
+
+        treeCtrl = self.GetControl()
+        parentItem = treeCtrl.GetRootItem()
+        if expanded[0] != treeCtrl.GetItemText(parentItem):
+            return
+
+        (child, cookie) = treeCtrl.GetFirstChild(parentItem)
+        while child.IsOk():
+            if treeCtrl.GetItemText(child) in expanded:
+                treeCtrl.Expand(child)
+            (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie)
+
+        # wxBug: This causes a crash, tried using ScrollTo which crashed as well.  Then tried calling it with wx.CallAfter and that crashed as well, with both EnsureVisible and ScrollTo
+        # self.GetControl().EnsureVisible(self.GetControl().GetRootItem())
+        # So doing the following massive hack which forces the treectrl to scroll up to the top item
+        treeCtrl.Collapse(parentItem)
+        treeCtrl.Expand(parentItem)
+
+
+class OutlineTreeCtrl(wx.TreeCtrl):
+    """ Default Tree Control Class for OutlineView.
+        This class has the added functionality of sorting by the labels
+    """
+
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    ORIG_ORDER = 0
+    VIEW = 1
+    CALLBACKDATA = 2
+
+
+    #----------------------------------------------------------------------------
+    # Overridden Methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, parent, id, style=wx.TR_HAS_BUTTONS|wx.TR_DEFAULT_STYLE):
+        wx.TreeCtrl.__init__(self, parent, id, style = style)
+        self._origOrderIndex = 0
+        self._sortOrder = SORT_NONE
+
+
+    def DeleteAllItems(self):
+        self._origOrderIndex = 0
+        wx.TreeCtrl.DeleteAllItems(self)
+
+
+    #----------------------------------------------------------------------------
+    # Sort Methods
+    #----------------------------------------------------------------------------
+
+    def SetSortOrder(self, sortOrder = SORT_NONE):
+        """ Sort Order constants are defined at top of file """
+        self._sortOrder = sortOrder
+
+
+    def OnCompareItems(self, item1, item2):
+        if self._sortOrder == SORT_ASC:
+            return cmp(self.GetItemText(item1).lower(), self.GetItemText(item2).lower())  # sort A-Z
+        elif self._sortOrder == SORT_DESC:
+            return cmp(self.GetItemText(item2).lower(), self.GetItemText(item1).lower())  # sort Z-A
+        else:
+            return (self.GetPyData(item1)[self.ORIG_ORDER] > self.GetPyData(item2)[self.ORIG_ORDER]) # unsorted
+
+
+    def SortAllChildren(self, parentItem):
+        if parentItem and self.GetChildrenCount(parentItem, False):
+            self.SortChildren(parentItem)
+            (child, cookie) = self.GetFirstChild(parentItem)
+            while child.IsOk():
+                self.SortAllChildren(child)
+                (child, cookie) = self.GetNextChild(parentItem, cookie)
+
+
+    #----------------------------------------------------------------------------
+    # Select Callback Methods
+    #----------------------------------------------------------------------------
+
+    def CallDoSelectCallback(self, item):
+        """ Invoke the DoSelectCallback of the given view to highlight text in the document view
+        """
+        data = self.GetPyData(item)
+        if not data:
+            return
+
+        view = data[self.VIEW]
+        cbdata = data[self.CALLBACKDATA]
+        if view:
+            view.DoSelectCallback(cbdata)
+
+
+    def SelectClosestItem(self, position):
+        tree = self
+        distances = []
+        items = []
+        self.FindDistanceToTreeItems(tree.GetRootItem(), position, distances, items)
+        mindist = 1000000
+        mindex = -1
+        for index in range(0, len(distances)):
+            if distances[index] <= mindist:
+                mindist = distances[index]
+                mindex = index
+        if mindex != -1:
+            item = items[mindex]
+            self.EnsureVisible(item)
+            os_view = wx.GetApp().GetService(OutlineService).GetView()
+            if os_view:
+               os_view.StopActionOnSelect()
+            self.SelectItem(item)
+            if os_view:
+               os_view.ResumeActionOnSelect()
+
+
+    def FindDistanceToTreeItems(self, item, position, distances, items):
+        data = self.GetPyData(item)
+        this_dist = 1000000
+        if data and data[2]:
+            positionTuple = data[2]
+            if position >= positionTuple[1]:
+                items.append(item)
+                distances.append(position - positionTuple[1])
+
+        if self.ItemHasChildren(item):
+            child, cookie = self.GetFirstChild(item)
+            while child and child.IsOk():
+                self.FindDistanceToTreeItems(child, position, distances, items)
+                child, cookie = self.GetNextChild(item, cookie)
+        return False
+
+
+    def SetDoSelectCallback(self, item, view, callbackdata):
+        """ When an item in the outline view is selected,
+        a method is called to select the respective text in the document view.
+        The view must define the method DoSelectCallback(self, data) in order for this to work
+        """
+        self.SetPyData(item, (self._origOrderIndex, view, callbackdata))
+        self._origOrderIndex = self._origOrderIndex + 1
+
+
+    def CallDoLoadOutlineCallback(self, event):
+        """ Invoke the DoLoadOutlineCallback
+        """
+        rootItem = self.GetRootItem()
+        if rootItem:
+            data = self.GetPyData(rootItem)
+            if data:
+                view = data[self.VIEW]
+                if view and view.DoLoadOutlineCallback():
+                    self.SortAllChildren(self.GetRootItem())
+
+
+    def GetCallbackView(self):
+        rootItem = self.GetRootItem()
+        if rootItem:
+            return self.GetPyData(rootItem)[self.VIEW]
+        else:
+            return None
+
+
+class OutlineService(Service.Service):
+
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    SHOW_WINDOW = wx.NewId()  # keep this line for each subclass, need unique ID for each Service
+    SORT = wx.NewId()
+    SORT_ASC = wx.NewId()
+    SORT_DESC = wx.NewId()
+    SORT_NONE = wx.NewId()
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM):
+        Service.Service.__init__(self, serviceName, embeddedWindowLocation)
+        self._validTemplates = []
+
+
+    def _CreateView(self):
+        return OutlineView(self)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        wx.EVT_MENU(frame, OutlineService.SORT_ASC, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, OutlineService.SORT_ASC, frame.ProcessUpdateUIEvent)
+        wx.EVT_MENU(frame, OutlineService.SORT_DESC, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, OutlineService.SORT_DESC, frame.ProcessUpdateUIEvent)
+        wx.EVT_MENU(frame, OutlineService.SORT_NONE, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, OutlineService.SORT_NONE, frame.ProcessUpdateUIEvent)
+
+
+        if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            return True
+
+        viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
+        self._outlineSortMenu = wx.Menu()
+        self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order"))
+        self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order"))
+        self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order"))
+        viewMenu.AppendMenu(wx.NewId(), _("Outline Sort"), self._outlineSortMenu)
+
+        return True
+
+
+    #----------------------------------------------------------------------------
+    # Event Processing Methods
+    #----------------------------------------------------------------------------
+
+    def ProcessEvent(self, event):
+        if Service.Service.ProcessEvent(self, event):
+            return True
+
+        id = event.GetId()
+        if id == OutlineService.SORT_ASC:
+            self.OnSort(event)
+            return True
+        elif id == OutlineService.SORT_DESC:
+            self.OnSort(event)
+            return True
+        elif id == OutlineService.SORT_NONE:
+            self.OnSort(event)
+            return True
+        else:
+            return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if Service.Service.ProcessUpdateUIEvent(self, event):
+            return True
+
+        id = event.GetId()
+        if id == OutlineService.SORT_ASC:
+            event.Enable(True)
+
+            config = wx.ConfigBase_Get()
+            sort = config.ReadInt("OutlineSort", SORT_NONE)
+            if sort == SORT_ASC:
+                self._outlineSortMenu.Check(OutlineService.SORT_ASC, True)
+            else:
+                self._outlineSortMenu.Check(OutlineService.SORT_ASC, False)
+
+            return True
+        elif id == OutlineService.SORT_DESC:
+            event.Enable(True)
+
+            config = wx.ConfigBase_Get()
+            sort = config.ReadInt("OutlineSort", SORT_NONE)
+            if sort == SORT_DESC:
+                self._outlineSortMenu.Check(OutlineService.SORT_DESC, True)
+            else:
+                self._outlineSortMenu.Check(OutlineService.SORT_DESC, False)
+
+            return True
+        elif id == OutlineService.SORT_NONE:
+            event.Enable(True)
+
+            config = wx.ConfigBase_Get()
+            sort = config.ReadInt("OutlineSort", SORT_NONE)
+            if sort == SORT_NONE:
+                self._outlineSortMenu.Check(OutlineService.SORT_NONE, True)
+            else:
+                self._outlineSortMenu.Check(OutlineService.SORT_NONE, False)
+
+            return True
+        else:
+            return False
+
+
+    def OnSort(self, event):
+        id = event.GetId()
+        if id == OutlineService.SORT_ASC:
+            wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC)
+            self.GetView().OnSort(SORT_ASC)
+            return True
+        elif id == OutlineService.SORT_DESC:
+            wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC)
+            self.GetView().OnSort(SORT_DESC)
+            return True
+        elif id == OutlineService.SORT_NONE:
+            wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE)
+            self.GetView().OnSort(SORT_NONE)
+            return True
+
+
+    #----------------------------------------------------------------------------
+    # Service specific methods
+    #----------------------------------------------------------------------------
+
+    def LoadOutline(self, view, position=-1, force=False):
+        if not self.GetView():
+            return
+
+        self.SaveExpansionState()
+        if view.DoLoadOutlineCallback(force=force):
+            self.GetView().OnSort(wx.ConfigBase_Get().ReadInt("OutlineSort", SORT_NONE))
+            self.LoadExpansionState()
+        if position >= 0:
+            self.SyncToPosition(position)
+
+
+    def SyncToPosition(self, position):
+        if not self.GetView():
+            return
+        self.GetView().GetTreeCtrl().SelectClosestItem(position)
+
+
+    def OnCloseFrame(self, event):
+        Service.Service.OnCloseFrame(self, event)
+        self.SaveExpansionState(clear = True)
+
+        return True
+
+
+    def SaveExpansionState(self, clear = False):
+        if clear:
+            expanded = []
+        elif self.GetView():
+            expanded = self.GetView().GetExpansionState()
+        wx.ConfigBase_Get().Write("OutlineLastExpanded", expanded.__repr__())
+
+
+    def LoadExpansionState(self):
+        expanded = wx.ConfigBase_Get().Read("OutlineLastExpanded")
+        if expanded:
+            self.GetView().SetExpansionState(eval(expanded))
+
+
+    #----------------------------------------------------------------------------
+    # Timer Methods
+    #----------------------------------------------------------------------------
+
+    def StartBackgroundTimer(self):
+        self._timer = wx.PyTimer(self.DoBackgroundRefresh)
+        self._timer.Start(250)
+
+
+    def DoBackgroundRefresh(self):
+        """ Refresh the outline view periodically """
+        self._timer.Stop()
+        
+        foundRegisteredView = False
+        if self.GetView():
+            currView = wx.GetApp().GetDocumentManager().GetCurrentView()
+            if currView:
+                for template in self._validTemplates:
+                    type = template.GetViewType()
+                    if isinstance(currView, type):
+                        self.LoadOutline(currView)
+                        foundRegisteredView = True
+                        break
+
+                if not foundRegisteredView:
+                    self.GetView().ClearTreeCtrl()
+                    
+        self._timer.Start(1000) # 1 second interval
+
+
+    def AddTemplateForBackgroundHandler(self, template):
+        self._validTemplates.append(template)
+
+
+    def GetTemplatesForBackgroundHandler(self):
+        return self._validTemplates
+
+
+    def RemoveTemplateForBackgroundHandler(self, template):
+        self._validTemplates.remove(template)
+
diff --git a/wxPython/samples/ide/activegrid/tool/PHPEditor.py b/wxPython/samples/ide/activegrid/tool/PHPEditor.py
new file mode 100644 (file)
index 0000000..bf16594
--- /dev/null
@@ -0,0 +1,297 @@
+#----------------------------------------------------------------------------
+# Name:         PHPEditor.py
+# Purpose:      PHP Script Editor for pydocview tbat uses the Styled Text Control
+#
+# Author:       Morgan Hua
+#
+# Created:      1/4/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import string
+import STCTextEditor
+import CodeEditor
+import OutlineService
+import os
+import re
+
+
+class PHPDocument(CodeEditor.CodeDocument):
+
+    pass
+
+
+class PHPView(CodeEditor.CodeView):
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return PHPCtrl
+
+
+    def GetAutoCompleteHint(self):
+        pos = self.GetCtrl().GetCurrentPos()
+        if pos == 0:
+            return None, None
+            
+        validLetters = string.letters + string.digits + '_$'
+        word = ''
+        while (True):
+            pos = pos - 1
+            if pos < 0:
+                break
+            char = chr(self.GetCtrl().GetCharAt(pos))
+            if char not in validLetters:
+                break
+            word = char + word
+            
+        return None, word
+
+
+    def GetAutoCompleteDefaultKeywords(self):
+        return PHPKEYWORDS
+        
+    #----------------------------------------------------------------------------
+    # Methods for OutlineService
+    #----------------------------------------------------------------------------
+
+    def DoLoadOutlineCallback(self, force=False):
+        outlineService = wx.GetApp().GetService(OutlineService.OutlineService)
+        if not outlineService:
+            return False
+
+        outlineView = outlineService.GetView()
+        if not outlineView:
+            return False
+
+        treeCtrl = outlineView.GetTreeCtrl()
+        if not treeCtrl:
+            return False
+
+        view = treeCtrl.GetCallbackView()
+        newCheckSum = self.GenCheckSum()
+        if not force:
+            if view and view is self:
+                if self._checkSum == newCheckSum:
+                    return False
+        self._checkSum = newCheckSum
+
+        treeCtrl.DeleteAllItems()
+
+        document = self.GetDocument()
+        if not document:
+            return True
+
+        filename = document.GetFilename()
+        if filename:
+            rootItem = treeCtrl.AddRoot(os.path.basename(filename))
+            treeCtrl.SetDoSelectCallback(rootItem, self, None)
+        else:
+            return True
+
+        text = self.GetValue()
+        if not text:
+            return True
+
+        INTERFACE_PATTERN = 'interface[ \t]+\w+'
+        CLASS_PATTERN = '((final|abstract)[ \t]+)?((public|private|protected)[ \t]+)?(static[ \t]+)?class[ \t]+\w+((implements|extends)\w+)?'
+        FUNCTION_PATTERN = '(abstract[ \t]+)?((public|private|protected)[ \t]+)?(static[ \t]+)?function[ \t]+?\w+\(.*?\)'
+        interfacePat = re.compile(INTERFACE_PATTERN, re.M|re.S)
+        classPat = re.compile(CLASS_PATTERN, re.M|re.S)
+        funcPat= re.compile(FUNCTION_PATTERN, re.M|re.S)
+        pattern = re.compile('^[ \t]*('+ CLASS_PATTERN + '.*?{|' + FUNCTION_PATTERN + '|' + INTERFACE_PATTERN +'\s*?{).*?$', re.M|re.S)
+
+        iter = pattern.finditer(text)
+        indentStack = [(0, rootItem)]
+        for pattern in iter:
+            line = pattern.string[pattern.start(0):pattern.end(0)]
+            foundLine = classPat.search(line)
+            if foundLine:
+                indent = foundLine.start(0)
+                itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)]
+            else:
+                foundLine = funcPat.search(line)
+                if foundLine:
+                    indent = foundLine.start(0)
+                    itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)]
+                else:
+                    foundLine = interfacePat.search(line)
+                    if foundLine:
+                        indent = foundLine.start(0)
+                        itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)]
+
+            if indent == 0:
+                parentItem = rootItem
+            else:
+                lastItem = indentStack.pop()
+                while lastItem[0] >= indent:
+                    lastItem = indentStack.pop()
+                indentStack.append(lastItem)
+                parentItem = lastItem[1]
+
+            item = treeCtrl.AppendItem(parentItem, itemStr)
+            treeCtrl.SetDoSelectCallback(item, self, (pattern.end(0), pattern.start(0) + indent))  # select in reverse order because we want the cursor to be at the start of the line so it wouldn't scroll to the right
+            indentStack.append((indent, item))
+
+        treeCtrl.Expand(rootItem)
+
+        return True
+
+
+class PHPService(CodeEditor.CodeService):
+
+
+    def __init__(self):
+        CodeEditor.CodeService.__init__(self)
+
+
+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_HTML)
+        self.SetStyleBits(7)
+        self.SetKeyWords(4, string.join(PHPKEYWORDS))
+        self.SetProperty("fold.html", "1")
+
+
+    def CanWordWrap(self):
+        return True
+
+
+    def SetViewDefaults(self):
+        CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "PHP", hasWordWrap = True, hasTabs = True)
+
+
+    def GetFontAndColorFromConfig(self):
+        return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "PHP")
+
+
+    def UpdateStyles(self):
+        CodeEditor.CodeCtrl.UpdateStyles(self)
+        
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+
+        # HTML Styles
+        # White space
+        self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        # Comment
+        self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        # Number
+        self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        # String
+        self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        # Tag
+        self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+        # Attributes
+        self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+
+
+        # PHP Styles
+        self.StyleSetSpec(wx.stc.STC_HPHP_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_SIMPLESTRING, "face:%(font)s,fore:#7F007F,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_HSTRING, "face:%(font)s,fore7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_HSTRING_VARIABLE, "face:%(font)s,fore:#007F7F,italic,bold,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_VARIABLE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_OPERATOR, "face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_HPHP_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) # keyword
+        
+
+class PHPOptionsPanel(STCTextEditor.TextOptionsPanel):
+
+    def __init__(self, parent, id):
+        STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "PHP", label = "PHP", hasWordWrap = True, hasTabs = True)
+
+
+PHPKEYWORDS = [
+        "and", "or", "xor", "__FILE__", "exception", "__LINE__", "array", "as", "break", "case",
+        "class", "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif",
+        "empty", "enddeclare", "endfor", "endforeach", "endif", "endswith", "endwhile", "eval",
+        "exit", "extends", "for", "foreach", "function", "global", "if", "include", "include_once",
+        "isset", "list", "new", "print", "require", "require_once", "return", "static", "switch",
+        "unset", "use", "var", "while", "__FUNCTION__", "__CLASS__", "__METHOD__", "final", "php_user_filter",
+        "interface", "implements", "extends", "public", "private", "protected", "abstract", "clone", "try", "catch",
+        "throw", "cfunction", "old_function",
+        "$_SERVER", "$_ENV", "$_COOKIE", "$_GET", "$_POST", "$_FILES", "$_REQUEST", "$_SESSION", "$GLOBALS", "$php_errormsg",
+        "PHP_VERSION", "PHP_OS", "PHP_EOL", "DEFAULT_INCLUDE_PATH", "PEAR_INSTALL_DIR", "PEAR_EXTENSION_DIR",
+        "PHP_EXTENSION_DIR", "PHP_BINDIR", "PHP_LIBDIR", "PHP_DATADIR", "PHP_SYSCONFDIR", "PHP_LOCALSTATEDIR",
+        "PHP_CONFIG_FILE_PATH", "PHP_OUTPUT_HANDLER_START", "PHP_OUTPUT_HANDLER_CONT", "PHP_OUTPUT_HANDLER_END",
+        "E_ERROR", "E_WARNING", "E_PARSE", "E_NOTICE", "E_CORE_ERROR", "E_CORE_WARNING", "E_COMPILE_ERROR",
+        "E_COMPILE_WARNING", "E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_ALL", "E_STRICT",
+        "TRUE", "FALSE", "NULL", "ZEND_THREAD_SAFE",
+        "EXTR_OVERWRITE", "EXTR_SKIP", "EXTR_PREFIX_SAME", "EXTR_PREFIX_ALL", "EXTR_PREFIX_INVALID",
+        "EXTR_PREFIX_IF_EXISTS", "EXTR_IF_EXISTS", "SORT_ASC", "SORT_DESC", "SORT_REGULAR", "SORT_NUMERIC",
+        "SORT_STRING", "CASE_LOWER", "CASE_UPPER", "COUNT_NORMAL", "COUNT_RECURSIVE", "ASSERT_ACTIVE",
+        "ASSERT_CALLBACK", "ASSERT_BAIL", "ASSERT_WARNING", "ASSERT_QUIET_EVAL", "CONNECTION_ABORTED",
+        "CONNECTION_NORMAL", "CONNECTION_TIMEOUT", "INI_USER", "INI_PERDIR", "INI_SYSTEM", "INI_ALL",
+        "M_E", "M_LOG2E", "M_LOG10E", "M_LN2", "M_LN10", "M_PI", "M_PI_2", "M_PI_4", "M_1_PI", "M_2_PI",
+        "M_2_SQRTPI", "M_SQRT2", "M_SQRT1_2", "CRYPT_SALT_LENGTH", "CRYPT_STD_DES", "CRYPT_EXT_DES", "CRYPT_MD5",
+        "CRYPT_BLOWFISH", "DIRECTORY_SEPARATOR", "SEEK_SET", "SEEK_CUR", "SEEK_END", "LOCK_SH", "LOCK_EX", "LOCK_UN",
+        "LOCK_NB", "HTML_SPECIALCHARS", "HTML_ENTITIES", "ENT_COMPAT", "ENT_QUOTES", "ENT_NOQUOTES", "INFO_GENERAL",
+        "INFO_CREDITS", "INFO_CONFIGURATION", "INFO_MODULES", "INFO_ENVIRONMENT", "INFO_VARIABLES", "INFO_LICENSE",
+        "INFO_ALL", "CREDITS_GROUP", "CREDITS_GENERAL", "CREDITS_SAPI", "CREDITS_MODULES", "CREDITS_DOCS",
+        "CREDITS_FULLPAGE", "CREDITS_QA", "CREDITS_ALL", "STR_PAD_LEFT", "STR_PAD_RIGHT", "STR_PAD_BOTH",
+        "PATHINFO_DIRNAME", "PATHINFO_BASENAME", "PATHINFO_EXTENSION", "PATH_SEPARATOR", "CHAR_MAX", "LC_CTYPE",
+        "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_ALL", "LC_MESSAGES", "ABDAY_1", "ABDAY_2",
+        "ABDAY_3", "ABDAY_4", "ABDAY_5", "ABDAY_6", "ABDAY_7", "DAY_1", "DAY_2", "DAY_3", "DAY_4", "DAY_5",
+        "DAY_6", "DAY_7", "ABMON_1", "ABMON_2", "ABMON_3", "ABMON_4", "ABMON_5", "ABMON_6", "ABMON_7", "ABMON_8",
+        "ABMON_9", "ABMON_10", "ABMON_11", "ABMON_12", "MON_1", "MON_2", "MON_3", "MON_4", "MON_5", "MON_6", "MON_7",
+        "MON_8", "MON_9", "MON_10", "MON_11", "MON_12", "AM_STR", "PM_STR", "D_T_FMT", "D_FMT", "T_FMT", "T_FMT_AMPM",
+        "ERA", "ERA_YEAR", "ERA_D_T_FMT", "ERA_D_FMT", "ERA_T_FMT", "ALT_DIGITS", "INT_CURR_SYMBOL", "CURRENCY_SYMBOL",
+        "CRNCYSTR", "MON_DECIMAL_POINT", "MON_THOUSANDS_SEP", "MON_GROUPING", "POSITIVE_SIGN", "NEGATIVE_SIGN",
+        "INT_FRAC_DIGITS", "FRAC_DIGITS", "P_CS_PRECEDES", "P_SEP_BY_SPACE", "N_CS_PRECEDES", "N_SEP_BY_SPACE",
+        "P_SIGN_POSN", "N_SIGN_POSN", "DECIMAL_POINT", "RADIXCHAR", "THOUSANDS_SEP", "THOUSEP", "GROUPING",
+        "YESEXPR", "NOEXPR", "YESSTR", "NOSTR", "CODESET", "LOG_EMERG", "LOG_ALERT", "LOG_CRIT", "LOG_ERR",
+        "LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG", "LOG_KERN", "LOG_USER", "LOG_MAIL", "LOG_DAEMON",
+        "LOG_AUTH", "LOG_SYSLOG", "LOG_LPR", "LOG_NEWS", "LOG_UUCP", "LOG_CRON", "LOG_AUTHPRIV", "LOG_LOCAL0",
+        "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", "LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7",
+        "LOG_PID", "LOG_CONS", "LOG_ODELAY", "LOG_NDELAY", "LOG_NOWAIT", "LOG_PERROR"
+        ]
+
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getPHPData():
+    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{IDAT8\x8dclh8\xf0\x9f\x81\x02\xc0D\x89f\xaa\x18\xc0\x82M0<\\\x1c\
+\xce^\xb9\xf2%y.\xd0\xd4\xd4$\xde\x05\xf8l\x0c\x0f\x17\x87\x8baS\xc7x\xfd\
+\xfa\xf5\xff\xc8\xb6]\xbf~\x1d\xc3\x05\xf8\xc4\x98\x90\x05\xae_\xbf\x8e\xa1\
+\x88\x90\xd8 \x8aF\x98\x93`~\xc3\x05\xd0\xd5\xc1\r\x80\t\xc0B\xf7\xfa\xf5\
+\xeb(l\\\xeaP\xbc\x80\x1c\x85\xb8\xd8\xe8|&b\x9c\x8dn;2`\x1c\xf0\xdc\x08\x00\
+\x8e\xf2S\xed\xb0\xbe\xaa\xbc\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getPHPBitmap():
+    return BitmapFromImage(getPHPImage())
+
+def getPHPImage():
+    stream = cStringIO.StringIO(getPHPData())
+    return ImageFromStream(stream)
+
+def getPHPIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getPHPBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/PerlEditor.py b/wxPython/samples/ide/activegrid/tool/PerlEditor.py
new file mode 100644 (file)
index 0000000..e09f135
--- /dev/null
@@ -0,0 +1,425 @@
+#----------------------------------------------------------------------------
+# Name:         PerlEditor.py
+# Purpose:      Perl Script Editor for pydocview tbat uses the Styled Text Control
+#
+# Author:       Morgan Hua
+#
+# Created:      1/5/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import string
+import STCTextEditor
+import CodeEditor
+
+
+class PerlDocument(CodeEditor.CodeDocument):
+
+    pass
+
+
+class PerlView(CodeEditor.CodeView):
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return PerlCtrl
+
+
+    def GetAutoCompleteHint(self):
+        pos = self.GetCtrl().GetCurrentPos()
+        if pos == 0:
+            return None, None
+            
+        validLetters = string.letters + string.digits + '_/'
+        word = ''
+        while (True):
+            pos = pos - 1
+            if pos < 0:
+                break
+            char = chr(self.GetCtrl().GetCharAt(pos))
+            if char not in validLetters:
+                break
+            word = char + word
+            
+        return None, word
+
+
+    def GetAutoCompleteDefaultKeywords(self):
+        return PERLKEYWORDS
+
+
+class PerlService(CodeEditor.CodeService):
+
+
+    def __init__(self):
+        CodeEditor.CodeService.__init__(self)
+
+
+class PerlCtrl(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_PERL)
+        self.SetKeyWords(0, string.join(PERLKEYWORDS))
+
+
+    def CanWordWrap(self):
+        return True
+
+
+    def SetViewDefaults(self):
+        CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Perl", hasWordWrap = True, hasTabs = True)
+
+
+    def GetFontAndColorFromConfig(self):
+        return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Perl")
+
+
+    def UpdateStyles(self):
+        CodeEditor.CodeCtrl.UpdateStyles(self)
+        
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+        # Perl Styles
+        self.StyleSetSpec(wx.stc.STC_PL_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_CHARACTER, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING_Q, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING_QQ, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING_QX, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING_QR, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_STRING_QW, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_BACKTICKS, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) # keyword
+        self.StyleSetSpec(wx.stc.STC_PL_IDENTIFIER, "face:%(font)s,fore:#%(color)s,face:%(font)s,size:%(size)d" % faces)
+
+        # Default
+        self.StyleSetSpec(wx.stc.STC_PL_ARRAY, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_DATASECTION, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_ERROR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_HASH, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_HERE_DELIM, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_HERE_Q, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_HERE_QQ, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_HERE_QX, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_LONGQUOTE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_OPERATOR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_POD, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_PREPROCESSOR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_PUNCTUATION, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_REGEX, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_REGSUBST, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_SCALAR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_PL_SYMBOLTABLE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+
+
+class PerlOptionsPanel(STCTextEditor.TextOptionsPanel):
+
+    def __init__(self, parent, id):
+        STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Perl", label = "Perl", hasWordWrap = True, hasTabs = True)
+
+
+PERLKEYWORDS = [
+        "abs",
+        "accept",
+        "alarm",
+        "atan2",
+        "bind",
+        "binmode",
+        "bless",
+        "caller",
+        "chdir",
+        "chmod",
+        "chomp",
+        "chop",
+        "chown",
+        "chr",
+        "chroot",
+        "close",
+        "closedir",
+        "connect",
+        "continue",
+        "cos",
+        "crypt",
+        "dbmclose",
+        "dbmopen",
+        "defined",
+        "delete",
+        "die",
+        "do",
+        "dump",
+        "each",
+        "endgrent",
+        "endhostent",
+        "endnetent",
+        "endprotoent",
+        "endpwent",
+        "endservent",
+        "eof",
+        "eval",
+        "exec",
+        "exists",
+        "exit",
+        "exp",
+        "fcntl",
+        "fileno",
+        "flock",
+        "fork",
+        "format",
+        "formline",
+        "getc",
+        "getgrent",
+        "getgrgid",
+        "getgrnam",
+        "gethostbyaddr",
+        "gethostbyname",
+        "gethostent",
+        "getlogin",
+        "getnetbyaddr",
+        "getnetbyname",
+        "getnetent",
+        "getpeername",
+        "getpgrp",
+        "getppid",
+        "getpriority",
+        "getprotobyname",
+        "getprotobynumber",
+        "getprotoent",
+        "getpwent",
+        "getpwnam",
+        "getpwuid",
+        "getservbyname",
+        "getservbyport",
+        "getservent",
+        "getsockname",
+        "getsockopt",
+        "glob",
+        "gmtime",
+        "goto",
+        "grep",
+        "hex",
+        "import",
+        "index",
+        "int",
+        "ioctl",
+        "join",
+        "keys",
+        "kill",
+        "last",
+        "lc",
+        "lcfirst",
+        "length",
+        "link",
+        "listen",
+        "local",
+        "localtime",
+        "log",
+        "lstat",
+        "m//",
+        "map",
+        "mkdir",
+        "msgctl",
+        "msgget",
+        "msgrcv",
+        "msgsnd",
+        "my",
+        "next",
+        "no",
+        "oct",
+        "open",
+        "opendir",
+        "ord",
+        "pack",
+        "package",
+        "pipe",
+        "pop",
+        "pos",
+        "print",
+        "printf",
+        "prototype",
+        "push",
+        "q/STRING/",
+        "qq/STRING/",
+        "quotemeta",
+        "qw",
+        "qw/STRING/",
+        "qx",
+        "qx/STRING/",
+        "rand",
+        "read",
+        "readdir",
+        "readline",
+        "readlink",
+        "readpipe",
+        "recv",
+        "redo",
+        "ref",
+        "rename",
+        "require",
+        "reset",
+        "return",
+        "reverse",
+        "rewinddir",
+        "rindex",
+        "rmdir",
+        "s///",
+        "scalar",
+        "seek",
+        "seekdir",
+        "select",
+        "semctl",
+        "semget",
+        "semop",
+        "send",
+        "setgrent",
+        "sethostent",
+        "setnetent",
+        "setpgrp",
+        "setpriority",
+        "setprotoent",
+        "setpwent",
+        "setservent",
+        "setsockopt",
+        "shift",
+        "shmctl",
+        "shmget",
+        "shmread",
+        "shmwrite",
+        "shutdown",
+        "sin",
+        "sleep",
+        "socket",
+        "socketpair",
+        "sort",
+        "splice",
+        "split",
+        "sprintf",
+        "sqrt",
+        "srand",
+        "stat",
+        "study",
+        "sub",
+        "substr",
+        "symlink",
+        "syscall",
+        "sysopen",
+        "sysread",
+        "sysseek",
+        "system",
+        "syswrite",
+        "tell",
+        "telldir",
+        "tie",
+        "tied",
+        "times",
+        "tr///",
+        "truncate",
+        "uc",
+        "ucfirst",
+        "umask",
+        "undef",
+        "unlink",
+        "unpack",
+        "unshift",
+        "untie",
+        "use",
+        "utime",
+        "values",
+        "vec",
+        "wait",
+        "waitpid",
+        "wantarray",
+        "warn",
+        "write",
+        "y///",
+        "eq",
+        "ne",
+        "lt",
+        "le",
+        "gt",
+        "ge",
+        "cmp",
+        "if",
+        "else"
+        "not",
+        "and",
+        "xor",
+        "or",
+        "if",
+        "while",
+        "until",
+        "for",
+        "foreach",
+        "last",
+        "next",
+        "redo",
+        "goto",
+        "STDIN",
+        "STDOUT",
+        "STDERR",
+        "WHEncE",
+        "BEGIN",
+        "END",
+        "require",
+        "integer",
+        "less",
+        "sigtrap",
+        "strict",
+        "subs"
+    ]
+
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getPerlData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x10\x08\x06\
+\x00\x00\x00&\x94N:\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
+\x01mIDAT(\x91\x9d\x93/\x8e\xf30\x10\xc5\x7f[\xed\x05\xca\x82,Ef\xc6e\x01Vx{\
+\x89\x04\x16\x94\x97\x045\xa8\xa0\xa44G\x08\xc9\x01\xa2\x80\x1e $ld\xc9\xa8w\
+\x08\xf0\xa2x\x93~\x1f\xda\x91F\xb2\xfd\xe6=\xcd?\x7f\xcd\xf3\x1c\xf8\x83}/\
+\x87i\x9a\x000\xc6D\xb0\xeb:\x86a MS\xce\xe7\xf3\x968M\x13}\xdf\xe3\x9cCD\
+\xb8\xddn\x18c\xe8\xba\x8e\xb2,\xc9\xb2\x0c\x11\x01\xd8\x90w\xc6\x18\x94R\
+\xf1\xa1\xef{\xba\xae\xa3i\x1a\xb4\xd6h\xad)\x8a\x02\xe7\\\xccj\x93\xea\xa2\
+\nP\xd75\xd7\xeb\x15\x00\xef\xfdFt)e\xb7\x80\x8b\xbas\x8e$I\xe2}\xc1\x8b\xa2\
+\xd8\xf4b\x07\xa0\x94\xe2\xf5za\xad\xc5Z\xcb\xfb\xfdFD\xe8\xfb\x9e\x05\x17\
+\x11\x9cs4M\xf3K<\x9dNdY\xc60\x0cx\xef\x11\x11\xea\xbaF)\x85s\x8e\xba\xae)\
+\xcb\x12\x11!M\xd3_"\xc0\xfd~\xc7Z\x8bs\x0e\x80$I\xa2:@UU1u\x00\xe6y\x0ek\
+\x1f\xc71\x1c\x0e\x87\xd0\xb6m\xd8\xef\xf7\xe1\xf1x\x84\xcb\xe5\x12\xe6y\x0e\
+\xc7\xe31\xc6\xed\xf80\x11!\xcb2\xbc\xf7TUE\x9e\xe71=\xadul\xce\xf7\'Qk\x8d\
+\xf7\x9e<\xcf\x81\xed&Yk\xb7\xe3\xf84\xa5\x14\xc6\x18D\x84\xe7\xf3\x19\x83\
+\xd75\xfe\x97\xb8\x0eXo\xcc2\x9e\x7f\x9a3\x8ech\xdb6|6l\xf15\xf6\xf5\xd7o\
+\xf5\x03\xaf\x9f\xfa@\x02\xe4\xdc\xf9\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+
+def getPerlBitmap():
+    return BitmapFromImage(getPerlImage())
+
+def getPerlImage():
+    stream = cStringIO.StringIO(getPerlData())
+    return ImageFromStream(stream)
+
+def getPerlIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getPerlBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/ProjectEditor.py b/wxPython/samples/ide/activegrid/tool/ProjectEditor.py
new file mode 100644 (file)
index 0000000..763e74b
--- /dev/null
@@ -0,0 +1,1847 @@
+#----------------------------------------------------------------------------
+# Name:         ProjectEditor.py
+# Purpose:      IDE-style Project Editor for wx.lib.pydocview
+#
+# Author:       Peter Yared, Morgan Hua
+#
+# Created:      8/15/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2003, 2004, 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx.lib.docview
+import wx.lib.pydocview
+import types
+import os
+import os.path
+import wx
+from wxPython.lib.rcsizer import RowColSizer
+import time
+import Service
+import MessageService
+import DebuggerService
+import sys
+import activegrid.util.xmlmarshaller
+import UICommon
+from IDE import ACTIVEGRID_BASE_IDE
+if not ACTIVEGRID_BASE_IDE:
+    import ProcessModelEditor
+
+_ = wx.GetTranslation
+
+if wx.Platform == '__WXMSW__':
+    _WINDOWS = True
+else:
+    _WINDOWS = False
+
+
+#----------------------------------------------------------------------------
+# XML Marshalling Methods
+#----------------------------------------------------------------------------
+
+def load(fileObject):
+    xml = fileObject.read()
+    projectModel = activegrid.util.xmlmarshaller.unmarshal(xml)
+    return projectModel
+
+
+def save(fileObject, projectModel):
+    xml = activegrid.util.xmlmarshaller.marshal(projectModel, prettyPrint=True)
+    fileObject.write(xml)
+
+
+#----------------------------------------------------------------------------
+# Classes
+#----------------------------------------------------------------------------
+
+class ProjectModel:
+    __xmlname__ = "projectmodel"
+    __xmlrename__ = { "_files":"files", "_homepath":"homepath" }
+
+    def __init__(self):
+        self._homepath = None
+        self._files = []
+
+
+class ProjectDocument(wx.lib.docview.Document):
+
+    def __init__(self):
+        wx.lib.docview.Document.__init__(self)
+        self._projectModel = ProjectModel()
+
+
+    def GetModel(self):
+        return self._projectModel
+
+
+    def OnCreate(self, path, flags):
+        projectService = wx.GetApp().GetService(ProjectService)
+        if projectService.GetView():
+            view = projectService.GetView()
+            self.AddView(view)
+        else:
+            view = self.GetDocumentTemplate().CreateView(self, flags)
+            projectService.SetView(view)
+        return view
+
+
+    def LoadObject(self, fileObject):
+        self._projectModel = activegrid.tool.ProjectEditor.load(fileObject)
+        return True
+
+
+    def SaveObject(self, fileObject):
+        activegrid.tool.ProjectEditor.save(fileObject, self._projectModel)
+        return True
+
+
+    def OnSaveDocument(self, filename):
+        self._projectModel._homepath = wx.lib.docview.PathOnly(filename)
+        return wx.lib.docview.Document.OnSaveDocument(self, filename)
+
+
+    def OnOpenDocument(self, filename):
+        view = self.GetFirstView()
+        frame = view.GetFrame()
+
+        if not os.path.exists(filename):
+            wx.GetApp().CloseSplash()
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("File Error")
+            wx.MessageBox(_("Could not find '%s'.") % filename,
+                          msgTitle,
+                          wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP,
+                          frame)
+            return True  # if we return False, the Project View is destroyed, Service windows shouldn't be destroyed
+
+        fileObject = file(filename, 'r')
+        try:
+            self.LoadObject(fileObject)
+        except:
+            wx.GetApp().CloseSplash()
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("File Error")
+            wx.MessageBox(_("Could not open '%s'.  %s") % (wx.lib.docview.FileNameFromPath(filename), sys.exc_value),
+                          msgTitle,
+                          wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP,
+                          frame)
+            return True  # if we return False, the Project View is destroyed, Service windows shouldn't be destroyed
+
+        self.Modify(False)
+
+        # if the project file has moved, then ask the user if we should readjust the paths of all the files in the project
+        newHomepath = wx.lib.docview.PathOnly(filename)
+        if newHomepath != self._projectModel._homepath:
+            wx.GetApp().CloseSplash()
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("Project Moved")
+            projectService = wx.GetApp().GetService(activegrid.tool.ProjectEditor.ProjectService)
+            yesNoMsg = wx.MessageDialog(frame,
+                          _("The project file '%s' was moved from:\n    '%s'\nto:\n    '%s'.\n\nWould you like to automatically adjust the project contents accordingly?") % (wx.lib.docview.FileNameFromPath(filename), self._projectModel._homepath, wx.lib.docview.PathOnly(filename)),
+                          msgTitle,
+                          wx.YES_NO | wx.STAY_ON_TOP
+                          )
+            if projectService.GetSuppressOpenProjectMessages() or yesNoMsg.ShowModal() == wx.ID_YES:
+                if not projectService.GetSuppressOpenProjectMessages():
+                    messageService = wx.GetApp().GetService(MessageService.MessageService)
+                    messageService.ShowWindow()
+                    messageView = messageService.GetView()
+                    messageView.ClearLines()
+                    messageView.AddLines(_("The project file '%s' was moved from:\n    '%s'\nto:\n    '%s'\n") % (wx.lib.docview.FileNameFromPath(filename), self._projectModel._homepath, wx.lib.docview.PathOnly(filename)))
+                    messageView.AddLines(_("Updating file references:\n"))
+
+                for index, filepath in enumerate(self._projectModel._files):
+                    if filepath.startswith(self._projectModel._homepath + os.sep):
+                        newfile = newHomepath + filepath[len(self._projectModel._homepath):len(filepath)]
+                        if os.path.exists(newfile):
+                            self._projectModel._files[index] = newfile
+                            if not projectService.GetSuppressOpenProjectMessages():
+                                messageView.AddLines(_("    Success:    '%s' location changed from '%s' to '%s'\n") % (wx.lib.docview.FileNameFromPath(filepath), wx.lib.docview.PathOnly(filepath), newHomepath))
+                            self.Modify(True)
+                        else:
+                            if not projectService.GetSuppressOpenProjectMessages():
+                                messageView.AddLines(_("    Failure:    Couldn't find '%s', file wasn't located at '%s'\n") % (wx.lib.docview.FileNameFromPath(filepath), newHomepath))
+                    else:
+                        if not projectService.GetSuppressOpenProjectMessages():
+                            messageView.AddLines(_(    "    Unmodified: '%s' location wasn't relative to '%s'\n") % (filepath, self._projectModel._homepath))
+                self._projectModel._homepath = newHomepath
+                if not projectService.GetSuppressOpenProjectMessages():
+                    messageView.AddLines(_("Project file updated."))
+
+        self.SetFilename(filename, True)
+        view.AddProjectToView(self)
+        self.UpdateAllViews()
+        self._savedYet = True
+        view.Activate(True)
+        return True
+
+    def AddFile(self, file):
+        return self.AddFiles([file])
+
+
+    def AddFiles(self, files):
+        notAlreadyThereFiles = filter(lambda x: x not in self._projectModel._files, files)  # Filter to the files that are not already in the project
+        if len(notAlreadyThereFiles) == 0:
+            self.UpdateAllViews(hint = ("select", self, files))
+            return False
+        else:
+            self._projectModel._files = self._projectModel._files + notAlreadyThereFiles
+            self.UpdateAllViews(hint = ("add", self, notAlreadyThereFiles))
+            self.Modify(True)
+            return True
+
+
+    def RemoveFile(self, file):
+        return self.RemoveFiles([file])
+
+
+    def RemoveFiles(self, files):
+        for file in files:
+            self._projectModel._files.remove(file)
+        self.UpdateAllViews(hint = ("remove", self, files))
+        self.Modify(True)
+        return True
+
+
+    def RenameFile(self, oldFile, newFile, isProject = False):
+        try:
+            if oldFile == newFile:
+                return False
+
+            # projects don't have to exist yet, so not required to rename old file,
+            # but files must exist, so we'll try to rename and allow exceptions to occur if can't.
+            if not isProject or (isProject and os.path.exists(oldFile)):
+                os.rename(oldFile, newFile)
+
+            if isProject:
+                documents = self.GetDocumentManager().GetDocuments()
+                for document in documents:
+                    if document.GetFilename() == oldFile:  # If the renamed document is open, update it
+                        document.SetFilename(newFile)
+                        document.SetTitle(wx.lib.docview.FileNameFromPath(newFile))
+                        document.UpdateAllViews(hint = ("rename", document, newFile))
+            else:
+                self.RemoveFile(oldFile)
+                self.AddFile(newFile)
+                documents = self.GetDocumentManager().GetDocuments()
+                for document in documents:
+                    if document.GetFilename() == oldFile:  # If the renamed document is open, update it
+                        document.SetFilename(newFile, notifyViews = True)
+                        document.UpdateAllViews(hint = ("rename", document, newFile))
+            return True
+        except OSError, (code, message):
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("File Error")
+            wx.MessageBox("Could not rename '%s'.  '%s'" % (wx.lib.docview.FileNameFromPath(oldFile), message),
+                          msgTitle,
+                          wx.OK | wx.ICON_EXCLAMATION,
+                          self.GetFirstView().GetFrame())
+            return False
+
+
+    def GetFiles(self):
+        return self._projectModel._files
+
+
+    def IsFileInProject(self, filename):
+        return filename in self.GetFiles()
+
+
+import Wizard
+
+class NewProjectWizard(Wizard.BaseWizard):
+
+    WIZTITLE = _("New Project Wizard")
+
+    def __init__(self, parent):
+        self._parent = parent
+        self._fullProjectPath = None
+        Wizard.BaseWizard.__init__(self, parent, self.WIZTITLE)
+        self._projectLocationPage = self.CreateProjectLocation(self)
+        wx.wizard.EVT_WIZARD_PAGE_CHANGING(self, self.GetId(), self.OnWizPageChanging)
+                
+    def CreateProjectLocation(self,wizard):   
+        page = Wizard.TitledWizardPage(wizard, _("Project File 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, _("File Name:"), _("Directory:"), _("agp"), startingDirectory=os.getcwd())
+        page.GetSizer().Add(sizer, 1, flag=wx.EXPAND)
+
+        wizard.Layout()
+        wizard.FitToPage(page)
+        return page    
+
+    def RunWizard(self, existingTables = None, existingRelationships = None):
+        status = wx.wizard.Wizard.RunWizard(self, self._projectLocationPage)
+        if status:
+            docManager = wx.GetApp().GetTopWindow().GetDocumentManager()
+            if os.path.exists(self._fullProjectPath):
+                # What if the document is already open and we're overwriting it?
+                documents = docManager.GetDocuments()
+                for document in documents:
+                    if document.GetFilename() == self._fullProjectPath:  # If the renamed document is open, update it
+                        document.DeleteAllViews()
+                        break
+                os.remove(self._fullProjectPath)
+
+            for template in docManager.GetTemplates():
+                if template.GetDocumentType() == ProjectDocument:
+                    doc = template.CreateDocument(self._fullProjectPath, flags = wx.lib.docview.DOC_NEW)
+                    doc.OnSaveDocument(self._fullProjectPath) 
+                    view = doc.GetFirstView()
+                    view.AddProjectToView(doc)
+                    break
+
+        self.Destroy()
+        return status
+
+
+    def OnWizPageChanging(self, event):
+        if event.GetDirection():  # It's going forwards
+            if event.GetPage() == self._projectLocationPage:
+                if not self._fileValidation():
+                    event.Veto()
+                    return 
+                self._fullProjectPath = os.path.join(self._dirCtrl.GetValue(),UICommon.MakeNameEndInExtension(self._projectName.GetValue(),'.agp'))
+                    
+                
+    
+    def OnShowCreatePages(self):                    
+        self.Hide()
+        import DataModelEditor
+        requestedPos = self.GetPositionTuple()
+        projectService = wx.GetApp().GetService(ProjectService)
+        projectView = projectService.GetView()
+
+        wiz = DataModelEditor.ImportExportWizard(projectView.GetFrame(), pos=requestedPos)
+        if wiz.RunWizard(dontDestroy=True):
+           self._schemaName.SetValue(wiz.GetSchemaFileName())
+        wiz.Destroy()
+        self.Show(True)
+         
+class ProjectTemplate(wx.lib.docview.DocTemplate):
+
+    def CreateDocument(self, path, flags):
+        if path:
+            return wx.lib.docview.DocTemplate.CreateDocument(self, path, flags)
+        else:
+            wiz = NewProjectWizard(wx.GetApp().GetTopWindow())
+            wiz.RunWizard()
+            wiz.Destroy()
+            return None  # never return the doc, otherwise docview will think it is a new file and rename it
+
+class ProjectAddFilesCommand(wx.lib.docview.Command):
+
+    def __init__(self, projectDoc, files):
+        wx.lib.docview.Command.__init__(self, canUndo = True)
+        self._projectDoc = projectDoc
+        self._files = files
+
+
+    def GetName(self):
+        if len(self._files) == 1:
+            return _("Add File")
+        else:
+            return _("Add Files")
+
+
+    def Do(self):
+        return self._projectDoc.AddFiles(self._files)
+
+
+    def Undo(self):
+        return self._projectDoc.RemoveFiles(self._files)
+
+
+class ProjectRemoveFilesCommand(wx.lib.docview.Command):
+
+    def __init__(self, projectDoc, files):
+        wx.lib.docview.Command.__init__(self, canUndo = True)
+        self._projectDoc = projectDoc
+        self._files = files
+
+
+    def GetName(self):
+        if len(self._files) == 1:
+            return _("Remove File")
+        else:
+            return _("Remove Files")
+
+
+    def Do(self):
+        return self._projectDoc.RemoveFiles(self._files)
+
+
+    def Undo(self):
+        return self._projectDoc.AddFiles(self._files)
+
+
+class ProjectRenameFileCommand(wx.lib.docview.Command):
+
+    def __init__(self, projectDoc, oldFile, newFile, isProject = False):
+        wx.lib.docview.Command.__init__(self, canUndo = True)
+        self._projectDoc = projectDoc
+        self._oldFile = oldFile
+        self._newFile = newFile
+        self._isProject = isProject
+
+
+    def GetName(self):
+        return _("Rename File")
+
+
+    def Do(self):
+        return self._projectDoc.RenameFile(self._oldFile, self._newFile, self._isProject)
+
+
+    def Undo(self):
+        return self._projectDoc.RenameFile(self._newFile, self._oldFile, self._isProject)
+
+
+class ProjectTreeCtrl(wx.TreeCtrl):
+
+    def __init__(self, parent, id, style):
+        wx.TreeCtrl.__init__(self, parent, id, style = style)
+
+        templates = wx.GetApp().GetDocumentManager().GetTemplates()
+        iconList = wx.ImageList(16, 16, initialCount = len(templates))
+        self._iconIndexLookup = []
+        for template in templates:
+            icon = template.GetIcon()
+            if icon:
+                if icon.GetHeight() != 16:
+                    icon.SetHeight(16)  # wxBug: img2py.py uses EmptyIcon which is 32x32
+                if icon.GetWidth() != 16:
+                    icon.SetWidth(16)   # wxBug: img2py.py uses EmptyIcon which is 32x32
+                iconIndex = iconList.AddIcon(icon)
+                self._iconIndexLookup.append((template, iconIndex))
+                
+        icon = getBlankIcon()
+        if icon.GetHeight() != 16:
+            icon.SetHeight(16)  # wxBug: img2py.py uses EmptyIcon which is 32x32
+        if icon.GetWidth() != 16:
+            icon.SetWidth(16)   # wxBug: img2py.py uses EmptyIcon which is 32x32
+        self._blankIconIndex = iconList.AddIcon(icon)
+        self.AssignImageList(iconList)
+
+
+    def OnCompareItems(self, item1, item2):
+        return cmp(self.GetItemText(item1).lower(), self.GetItemText(item2).lower())
+
+
+    def AppendItem(self, parent, filepath):
+        item = wx.TreeCtrl.AppendItem(self, parent, filepath)
+
+        found = False
+        template = wx.GetApp().GetDocumentManager().FindTemplateForPath(filepath)
+        if not template and parent == self.GetRootItem(): # If the parent is a root it's a new project
+            template = wx.GetApp().GetDocumentManager().FindTemplateForPath('.agp')
+        if template:
+            for t, iconIndex in self._iconIndexLookup:
+                if t is template:
+                    self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Normal)
+                    self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Expanded)
+                    self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Selected)
+                    found = True
+                    break
+
+        if not found:
+            self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Normal)
+            self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Expanded)
+            self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Selected)
+
+        return item
+
+
+class ProjectView(wx.lib.docview.View):
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, service = None):
+        wx.lib.docview.View.__init__(self)
+        self._service = service  # not used, but kept to match other Services
+        self._lastDirectory = ""
+        self._treeCtrl = None
+        self._editingSoDontKillFocus = False
+        self._checkEditMenu = True
+
+
+    def Destroy(self):
+        projectService = wx.GetApp().GetService(ProjectService)
+        if projectService:
+            projectService.SetView(None)
+        wx.lib.docview.View.Destroy(self)
+
+
+    def GetDocument(self):
+        if not self._treeCtrl:
+            return None
+            
+        items = self._treeCtrl.GetSelections()
+        if not items: # No selection, so just return first project
+            item = self._treeCtrl.GetFirstVisibleItem()
+            if item.IsOk():
+                return self._GetItemProject(item)
+            else:
+                return None
+                
+        for item in items:
+            project = self._GetItemProject(item)
+            if project:
+                return project
+                
+        return None
+
+
+    def GetDocumentManager(self):  # Overshadow this since the superclass uses the view._viewDocument attribute directly, which the project editor doesn't use since it hosts multiple docs
+        return wx.GetApp().GetDocumentManager()
+
+
+    def OnChangeFilename(self):
+        if self.GetFrame():
+            title = _("Projects")
+            if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName():
+                title =  title + " - " + wx.GetApp().GetAppName()
+            self.GetFrame().SetTitle(title)
+        project = self.GetDocument()
+        if project:
+            projectItem = self._GetProjectItem(project)
+            name = self._treeCtrl.GetItemText(self._GetProjectItem(project))
+            name2 = self._MakeProjectName(project)
+            if name != name2:
+                self._treeCtrl.SetItemText(projectItem, name2)
+                self._treeCtrl.SortChildren(self._treeCtrl.GetRootItem())
+
+
+    def Activate(self, activate = True):
+        if not wx.GetApp().IsMDI():
+            if activate and not self.IsShown():
+                self.Show()
+
+        if self.IsShown():
+            wx.lib.docview.View.Activate(self, activate = activate)
+            if activate and self._treeCtrl:
+                self._treeCtrl.SetFocus()
+
+
+    def OnCreate(self, doc, flags):
+        config = wx.ConfigBase_Get()
+        if wx.GetApp().IsMDI():
+            self._embeddedWindow = wx.GetApp().GetTopWindow().GetEmbeddedWindow(wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT)
+            self.SetFrame(self._embeddedWindow)
+            frame = self._embeddedWindow
+        else:
+            self._embeddedWindow = None
+            pos = config.ReadInt("ProjectFrameXLoc", -1), config.ReadInt("ProjectFrameYLoc", -1)
+            # make sure frame is visible
+            screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
+            screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
+            if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight:
+                pos = wx.DefaultPosition
+
+            size = wx.Size(config.ReadInt("ProjectFrameXSize", -1), config.ReadInt("ProjectFrameYSize", -1))
+
+            title = _("Projects")
+            if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName():
+                title =  title + " - " + wx.GetApp().GetAppName()
+
+            frame = wx.GetApp().CreateDocumentFrame(self, doc, 0, title = title, pos = pos, size = size)
+            if config.ReadInt("ProjectFrameMaximized", False):
+                frame.Maximize(True)
+
+        sizer = wx.BoxSizer()
+        self._treeCtrl = ProjectTreeCtrl(frame, -1, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.TR_DEFAULT_STYLE | wx.TR_MULTIPLE)
+        self._treeCtrl.AddRoot(_("Projects"))
+
+        if self._embeddedWindow:
+            sizer.Add(self._treeCtrl)
+            sizer.Fit(frame)
+        else:
+            sizer.Add(self._treeCtrl, 1, wx.EXPAND, 0)
+        frame.SetSizer(sizer)
+        frame.Layout()
+        self.Activate()
+
+        if wx.GetApp().IsMDI():
+            wx.EVT_SET_FOCUS(self._treeCtrl, self.OnFocus)
+            wx.EVT_KILL_FOCUS(self._treeCtrl, self.OnKillFocus)
+
+        if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            wx.EVT_TREE_ITEM_ACTIVATED(self._treeCtrl, self._treeCtrl.GetId(), self.OnOpenSelectionSDI)
+        else:
+            wx.EVT_TREE_ITEM_ACTIVATED(self._treeCtrl, self._treeCtrl.GetId(), self.OnOpenSelection)
+        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_KEY_DOWN(self._treeCtrl, self.OnKeyPressed)
+        # wx.EVT_COMMAND_RIGHT_CLICK(self._treeCtrl, self._treeCtrl.GetId(), self.OnRightClick) # wxBug: This isn't working for some reason
+
+        # drag-and-drop support
+        dt = ProjectFileDropTarget(self)
+        self._treeCtrl.SetDropTarget(dt)
+
+        return True
+
+
+    def WriteProjectConfig(self):
+        frame = self.GetFrame()
+        config = wx.ConfigBase_Get()
+        if frame and not self._embeddedWindow:
+            if not frame.IsMaximized():
+                config.WriteInt("ProjectFrameXLoc", frame.GetPositionTuple()[0])
+                config.WriteInt("ProjectFrameYLoc", frame.GetPositionTuple()[1])
+                config.WriteInt("ProjectFrameXSize", frame.GetSizeTuple()[0])
+                config.WriteInt("ProjectFrameYSize", frame.GetSizeTuple()[1])
+            config.WriteInt("ProjectFrameMaximized", frame.IsMaximized())
+
+        if config.ReadInt("ProjectSaveDocs", True):
+            projectFileNames = []
+            projectExpanded = []
+            if self._treeCtrl:
+                for projectItem in self._GetChildItems(self._treeCtrl.GetRootItem()):
+                    project = self._GetItemProject(projectItem)
+                    if not project.OnSaveModified():
+                        return
+                    if project.GetDocumentSaved():  # Might be a new document and "No" selected to save it
+                        projectFileNames.append(str(project.GetFilename()))
+                        projectExpanded.append(self._treeCtrl.IsExpanded(projectItem))
+            config.Write("ProjectSavedDocs", projectFileNames.__repr__())
+            config.Write("ProjectExpandedSavedDocs", projectExpanded.__repr__())
+
+
+    def OnClose(self, deleteWindow = True):
+        if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            self.WriteProjectConfig()
+        project = self.GetDocument()
+        if not project:
+            return True
+        if not self.GetDocument().Close():
+            return True
+        self.Activate(False)
+        if project:
+            projectItem = self._GetProjectItem(project)
+            if projectItem:
+                self._treeCtrl.Delete(projectItem)
+        # We don't need to delete the window since it is a floater/embedded
+        return True
+
+
+    def _GetParentFrame(self):
+        return wx.GetTopLevelParent(self.GetFrame())
+
+
+    def OnUpdate(self, sender = None, hint = None):
+        wx.lib.docview.View.OnUpdate(self, sender, hint)
+        if hint:
+            if hint[0] == "add":
+                projectItem = self._GetProjectItem(hint[1])
+                files = hint[2]
+                self._treeCtrl.UnselectAll()
+                self._treeCtrl.Expand(projectItem)
+                for file in files:
+                    item = self._treeCtrl.AppendItem(projectItem, os.path.basename(file))
+                    self._treeCtrl.SetPyData(item, file)
+                    self._treeCtrl.SelectItem(item)
+                    self._treeCtrl.EnsureVisible(item)  # wxBug: Doesn't work
+                self._treeCtrl.SortChildren(projectItem)
+            elif hint[0] == "remove":
+                projectItem = self._GetProjectItem(hint[1])
+                files = hint[2]
+                children = self._GetChildItems(projectItem)
+                for child in children:
+                    if self._GetItemFile(child) in files:
+                        self._treeCtrl.Delete(child)
+            elif hint[0] == "select":
+                projectItem = self._GetProjectItem(hint[1])
+                files = hint[2]
+                self._treeCtrl.UnselectAll()
+                children = self._GetChildItems(projectItem)
+                for child in children:
+                    if self._GetItemFile(child) in files:
+                        self._treeCtrl.SelectItem(child)
+                        self._treeCtrl.EnsureVisible(child)  # wxBug:  Doesn't work
+            elif hint[0] == "rename":
+                projectItem = self._GetProjectItem(hint[1])
+                self._treeCtrl.SetItemText(projectItem, os.path.basename(hint[2]))
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == ProjectService.ADD_FILES_TO_PROJECT_ID:
+            self.OnAddFileToProject(event)
+            return True
+        elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
+            return False  # Implement this one in the service
+        elif id == ProjectService.RENAME_ID:
+            self.OnRename(event)
+            return True
+        elif id == wx.ID_CUT:
+            self.OnCut(event)
+            return True
+        elif id == wx.ID_COPY:
+            self.OnCopy(event)
+            return True
+        elif id == wx.ID_PASTE:
+            self.OnPaste(event)
+            return True
+        elif id == wx.ID_CLEAR or id == ProjectService.REMOVE_FROM_PROJECT:
+            self.OnClear(event)
+            return True
+        elif id == wx.ID_SELECTALL:
+            self.OnSelectAll(event)
+            return True
+        elif id == ProjectService.OPEN_SELECTION_ID:
+            self.OnOpenSelection(event)
+            return True
+        elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID:
+            self.OnProperties(event)
+            return True
+        else:
+            return False
+
+    def ProcessUpdateUIEvent(self, event):
+        # Hack: The edit menu is not being set for projects that are preloaded at startup, so make sure it is OK here
+        if self._checkEditMenu:
+            doc = self.GetDocument()
+            if doc and not doc.GetCommandProcessor().GetEditMenu():
+                doc.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame()))
+            self._checkEditMenu = False
+        id = event.GetId()
+        if id == ProjectService.ADD_FILES_TO_PROJECT_ID:
+            event.Enable(self._HasProjectsSelected() or self._HasFilesSelected())
+            return True
+        elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
+            event.Enable(False)
+            return True
+        elif id == ProjectService.RENAME_ID:
+            event.Enable(self._HasFilesSelected() or self._HasProjectsSelected())
+            return True
+        elif id == wx.ID_CUT:
+            event.Enable(self._AreSelectedItemsFromSameProject())
+            return True
+        elif id == wx.ID_COPY:
+            event.Enable(self._HasFilesSelected())
+            return True
+        elif id == wx.ID_PASTE:
+            event.Enable(self.CanPaste())
+            return True
+        elif id == wx.ID_CLEAR or id == ProjectService.REMOVE_FROM_PROJECT:
+            event.Enable(self._AreSelectedItemsFromSameProject())
+            return True
+        elif id == wx.ID_SELECTALL:
+            event.Enable(self._HasFiles())
+            return True
+        elif id == ProjectService.OPEN_SELECTION_ID:
+            event.Enable(self._HasFilesSelected())
+            return True
+        elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID:
+            event.Enable(self._HasProjectsSelected() or self._HasFilesSelected())
+            return True
+        else:
+            return False
+
+    #----------------------------------------------------------------------------
+    # Display Methods
+    #----------------------------------------------------------------------------
+
+    def IsShown(self):
+        if not self.GetFrame():
+            return False
+        return self.GetFrame().IsShown()
+
+
+    def Hide(self):
+        self.Show(False)
+
+
+    def Show(self, show = True):
+        self.GetFrame().Show(show)
+        if wx.GetApp().IsMDI():
+            mdiParentFrame = wx.GetApp().GetTopWindow()
+            mdiParentFrame.ShowEmbeddedWindow(self.GetFrame(), show)
+
+
+    #----------------------------------------------------------------------------
+    # Methods for ProjectDocument and ProjectService to call
+    #----------------------------------------------------------------------------
+
+    def SetExpandedProjects(self, expandedProjects):
+        self._treeCtrl.UnselectAll()
+        firstItem = None
+        for i, item in enumerate(self._GetChildItems(self._treeCtrl.GetRootItem())):
+            if i == 0:
+                firstItem = item
+            if expandedProjects[i]:
+                self._treeCtrl.Expand(item)
+            else:
+                self._treeCtrl.Collapse(item)
+        # wxBug: This causes a crash, tried using ScrollTo which crashed as well.  Then tried calling it with wx.CallAfter and that crashed as well, with both EnsureVisible and ScrollTo
+        # self._treeCtrl.EnsureVisible(self._treeCtrl.GetRootItem())
+        # So doing the following massive hack which forces the treectrl to scroll up to the top item
+        if firstItem:
+            if expandedProjects[i]:
+                self._treeCtrl.Collapse(firstItem)
+                self._treeCtrl.Expand(firstItem)
+            else:
+                self._treeCtrl.Expand(firstItem)
+                self._treeCtrl.Collapse(firstItem)
+
+    def GetSelectedFile(self):
+        for item in self._treeCtrl.GetSelections():
+            return self._GetItemFile(item)
+            
+    def AddProjectToView(self, document):
+        rootItem = self._treeCtrl.GetRootItem()
+        projectItem = self._treeCtrl.AppendItem(rootItem, self._MakeProjectName(document))
+        self._treeCtrl.SetPyData(projectItem, document)
+        for file in document.GetFiles():
+            fileItem = self._treeCtrl.AppendItem(projectItem, os.path.basename(file))
+            self._treeCtrl.SetPyData(fileItem, file)
+        self._treeCtrl.SortChildren(rootItem)
+        self._treeCtrl.SortChildren(projectItem)
+        self._treeCtrl.UnselectAll()
+        self._treeCtrl.Expand(projectItem)
+        self._treeCtrl.SelectItem(projectItem)
+        if self._embeddedWindow:
+            document.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame()))
+
+    #----------------------------------------------------------------------------
+    # Methods for OutlineService
+    #----------------------------------------------------------------------------
+    def DoLoadOutlineCallback(self, force=False):
+        """ Project Editor is a special case for the Outline Service.
+            You need to be able to be active in the Project Manager without clearing
+            the Outline View.  So we make the Project Editor a client of the Outline
+            Service, but we don't load anything in the Outline View, leaving the
+            contents of the Outline View alone (e.g. last document's outline view).
+        """
+        pass
+
+    #----------------------------------------------------------------------------
+    # Control events
+    #----------------------------------------------------------------------------
+
+    def OnProperties(self, event):
+        items = self._treeCtrl.GetSelections()
+        if not items:
+            return
+        item = items[0]
+        if self._IsItemProject(item):
+            projectPropertiesDialog = ProjectPropertiesDialog(wx.GetApp().GetTopWindow(), self._GetItemProject(item).GetFilename())
+            if projectPropertiesDialog.ShowModal() == wx.ID_OK:
+                pass  # Handle OK
+            projectPropertiesDialog.Destroy()
+        elif self._IsItemFile(item):
+            filePropertiesService = wx.GetApp().GetService(wx.lib.pydocview.FilePropertiesService)
+            filePropertiesService.ShowPropertiesDialog(self._GetItemFile(item))
+
+
+    def OnAddFileToProject(self, event):
+        if wx.Platform == "__WXMSW__" or wx.Platform == "__WXGTK__" or wx.Platform == "__WXMAC__":
+            allfilter = ''
+            descr = ''
+            for temp in self.GetDocumentManager()._templates:
+                if temp.IsVisible():
+                    if len(descr) > 0:
+                        descr = descr + _('|')
+                        allfilter = allfilter + _(';')
+                    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
+                    allfilter = allfilter + temp.GetFileFilter()
+            descr = _("All") + _(" (") + allfilter + _(") |") + allfilter + _('|') + descr  # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk
+            descr = descr + _("|") + _("Any (*.*) | *.*")
+        else:
+            descr = _("*.*")
+        if True or _WINDOWS:
+            dialog = wx.FileDialog(self.GetFrame(), _("Add Files"), self._lastDirectory, "", descr, wx.OPEN | wx.HIDE_READONLY | wx.MULTIPLE)
+            if dialog.ShowModal() != wx.ID_OK:
+                return
+            paths = dialog.GetPaths()
+            dialog.Destroy()
+        else:
+            paths = wx.FileSelector(_("Add Files"), self._lastDirectory, "", wildcard = descr, flags = wx.OPEN | wx.HIDE_READONLY | wx.MULTIPLE, parent=self.GetFrame())
+            if type(paths) == types.StringType:
+                paths = [paths]
+        if len(paths):
+            self._lastDirectory = wx.lib.docview.PathOnly(paths[0])
+            self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), paths))
+        self.Activate(True)  # after add, should put focus on project editor
+
+
+    def DoAddFilesToProject(self, filenames):
+        # method used by Drag-n-Drop to add files to current Project
+        self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), filenames))
+
+
+    def DoSelectFiles(self, filenames):
+        # method used by Drag-n-Drop to select files in current Project
+        for selection in self._treeCtrl.GetSelections():
+            self._treeCtrl.SelectItem(selection, False)
+        for file in filenames:
+            item = self._GetFileItem(longFileName=file)
+            if item:
+                self._treeCtrl.SelectItem(item, True)
+                self._treeCtrl.EnsureVisible(item)
+
+
+    def DoSelectProject(self, x, y):
+        # method used by Drag-n-Drop to set current Project based on cursor position
+        item, flag = self._treeCtrl.HitTest((x,y))
+        if not item:
+            return False
+
+        project = self._GetItemProject(item)
+        if not project:
+            return False
+
+        projectItem = self._GetProjectItem(project)
+        self._treeCtrl.UnselectAll()
+        self._treeCtrl.SelectItem(projectItem)
+        return True
+
+
+    def OnFocus(self, event):
+        wx.GetApp().GetDocumentManager().ActivateView(self)
+        event.Skip()
+
+
+    def OnKillFocus(self, event):
+        # Get the top MDI window and "activate" it since it is already active from the perspective of the MDIParentFrame
+        # wxBug: Would be preferable to call OnActivate, but have casting problem, so added Activate method to docview.DocMDIChildFrame
+        if not self._editingSoDontKillFocus:  # wxBug: This didn't used to happen, but now when you start to edit an item in a wxTreeCtrl it puts out a KILL_FOCUS event, so we need to detect it
+            childFrame = wx.GetApp().GetTopWindow().GetActiveChild()
+            if childFrame:
+                childFrame.Activate()
+        event.Skip()
+
+
+    def OnRightClick(self, event):
+        self.Activate(True)
+        if not self._treeCtrl.GetSelections():
+            return
+        if len(self._treeCtrl.GetSelections()) == 1 and self._IsItemRoot(self._treeCtrl.GetSelections()[0]):
+            return  # Don't do a menu if it's just the root item selected
+        menu = wx.Menu()
+        if self._HasFilesSelected():  # Files context
+            menu.Append(ProjectService.OPEN_SELECTION_ID, _("&Open"), _("Opens the selection"))
+            menu.Enable(ProjectService.OPEN_SELECTION_ID, True)
+            wx.EVT_MENU(self._GetParentFrame(), ProjectService.OPEN_SELECTION_ID, self.OnOpenSelection)
+            itemIDs = [None]
+            for item in self._treeCtrl.GetSelections():
+                if self._IsItemProcessModelFile(item):
+                    itemIDs = [None, ProjectService.RUN_SELECTED_PM_ID, None]
+                    break
+        else:  # Project context
+            itemIDs = [wx.ID_CLOSE, wx.ID_SAVE, wx.ID_SAVEAS, None]
+        menuBar = self._GetParentFrame().GetMenuBar()
+        itemIDs = itemIDs + [wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID, None, ProjectService.ADD_FILES_TO_PROJECT_ID, ProjectService.REMOVE_FROM_PROJECT, None, wx.ID_UNDO, wx.ID_REDO, None, wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL, None, ProjectService.RENAME_ID]
+        for itemID in itemIDs:
+            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)
+                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:
+                    item = menuBar.FindItemById(itemID)
+                    if item:
+                        menu.Append(itemID, item.GetLabel())
+        self._treeCtrl.PopupMenu(menu, wx.Point(event.GetX(), event.GetY()))
+        menu.Destroy()
+
+    def OnRunSelectedPM(self, event):
+        projectService = wx.GetApp().GetService(ProjectService)
+        projectService.OnRunProcessModel(event, runSelected=True)
+
+    def OnRename(self, event):
+        if self._treeCtrl.GetSelections():
+            self._treeCtrl.EditLabel(self._treeCtrl.GetSelections()[0])
+
+
+    def OnBeginLabelEdit(self, event):
+        self._editingSoDontKillFocus = True
+        item = event.GetItem()
+        if not self._IsItemFile(item) and not self._IsItemProject(item):
+            event.Veto()
+
+
+    def OnEndLabelEdit(self, event):
+        self._editingSoDontKillFocus = False
+        item = event.GetItem()
+        newName = event.GetLabel()
+        if not newName or (not self._IsItemFile(item) and not self._IsItemProject(item)):
+            event.Veto()
+            return
+        if self._IsItemFile(item):
+            oldFile = self._GetItemFile(item)
+            newFile = os.path.join(os.path.split(oldFile)[0], newName)
+            if not self._GetItemProject(item).GetCommandProcessor().Submit(ProjectRenameFileCommand(self.GetDocument(), oldFile, newFile)):
+                event.Veto()
+                return
+            self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(self._treeCtrl.GetSelections()[0]))
+        elif self._IsItemProject(item):
+            oldFile = self._GetItemProject(item).GetFilename()
+            newFile = os.path.join(os.path.split(oldFile)[0], newName)
+            if not self._GetItemProject(item).GetCommandProcessor().Submit(ProjectRenameFileCommand(self.GetDocument(), oldFile, newFile, True)):
+                event.Veto()
+                return
+            self._treeCtrl.SortChildren(self._treeCtrl.GetRootItem())
+
+
+    def CanPaste(self):
+        # wxBug: Should be able to use IsSupported/IsSupportedFormat here
+        #fileDataObject = wx.FileDataObject()
+        #hasFilesInClipboard = wx.TheClipboard.IsSupportedFormat(wx.FileDataObject)
+        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
+
+
+    def OnCut(self, event):
+        if self._AreSelectedItemsFromSameProject():
+            self.OnCopy(event)
+            self.OnClear(event)
+
+
+    def OnCopy(self, event):
+        fileDataObject = wx.FileDataObject()
+        items = self._treeCtrl.GetSelections()
+        for item in items:
+            if self._IsItemFile(item):
+                file = self._treeCtrl.GetPyData(item)
+                fileDataObject.AddFile(file)
+        if len(fileDataObject.GetFilenames()) > 0 and wx.TheClipboard.Open():
+            wx.TheClipboard.SetData(fileDataObject)
+            wx.TheClipboard.Close()
+
+
+    def OnPaste(self, event):
+        if wx.TheClipboard.Open():
+            fileDataObject = wx.FileDataObject()
+            if wx.TheClipboard.GetData(fileDataObject):
+                self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), fileDataObject.GetFilenames()))
+            wx.TheClipboard.Close()
+
+
+    def OnClear(self, event):
+        if self._AreSelectedItemsFromSameProject():
+            items = self._treeCtrl.GetSelections()
+            files = []
+            for item in items:
+                if self._IsItemFile(item):
+                    files.append(self._GetItemFile(item))
+            self.GetDocument().GetCommandProcessor().Submit(ProjectRemoveFilesCommand(self._GetItemProject(items[0]), files))
+
+
+    def OnKeyPressed(self, event):
+        key = event.KeyCode()
+        if key == wx.WXK_DELETE:
+            self.OnClear(event)
+        else:
+            event.Skip()
+
+
+    def OnSelectAll(self, event):
+        project = self.GetDocument()
+        if project:
+            self._treeCtrl.UnselectAll()
+            for child in self._GetChildItems(self._GetProjectItem(project)):
+                self._treeCtrl.SelectItem(child)
+
+
+    def OnOpenSelectionSDI(self, event):
+        # Do a call after so that the second mouseclick on a doubleclick doesn't reselect the project window
+        wx.CallAfter(self.OnOpenSelection, None)
+
+
+    def OnOpenSelection(self, event):
+        doc = None
+        try:
+            items = self._treeCtrl.GetSelections()
+            for item in items:
+                if self._IsItemFile(item):
+                    filepath = self._GetItemFile(item)
+                    if not os.path.exists(filepath):
+                        msgTitle = wx.GetApp().GetAppName()
+                        if not msgTitle:
+                            msgTitle = _("File Not Found")
+                        yesNoMsg = wx.MessageDialog(self.GetFrame(),
+                                      _("The file '%s' was not found in '%s'.\n\nWould you like to browse for the file?") % (wx.lib.docview.FileNameFromPath(filepath), wx.lib.docview.PathOnly(filepath)),
+                                      msgTitle,
+                                      wx.YES_NO
+                                      )
+                        if yesNoMsg.ShowModal() == wx.ID_NO:
+                            continue
+                        findFile = wx.FileDialog(self.GetFrame(),
+                                                 _("Choose a file"),
+                                                 wx.lib.docview.PathOnly(filepath),
+                                                 wx.lib.docview.FileNameFromPath(filepath),
+                                                 style = wx.OPEN
+                                                )
+                        if findFile.ShowModal() == wx.ID_OK and findFile.GetPath():
+                            newpath = findFile.GetPath()
+                        else:
+                            newpath = None
+                        findFile.Destroy()
+                        if newpath:
+                            # update Project Model with new location
+                            self.GetDocument().RemoveFile(filepath)
+                            self.GetDocument().AddFile(newpath)
+                            filepath = newpath
+
+                    doc = self.GetDocumentManager().CreateDocument(filepath, wx.lib.docview.DOC_SILENT)
+
+        except IOError, (code, message):
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("File Error")
+            wx.MessageBox("Could not open '%s'." % wx.lib.docview.FileNameFromPath(filepath),
+                          msgTitle,
+                          wx.OK | wx.ICON_EXCLAMATION,
+                          self.GetFrame())
+
+
+    #----------------------------------------------------------------------------
+    # Convenience methods
+    #----------------------------------------------------------------------------
+
+    def _HasFiles(self):
+        if not self._treeCtrl:
+            return False
+        return self._treeCtrl.GetCount() > 1    #  1 item = root item, don't count as having files
+
+
+    def _HasProjectsSelected(self):
+        if not self._treeCtrl:
+            return False
+        items = self._treeCtrl.GetSelections()
+        if not items:
+            return False
+        for item in items:
+            if self._IsItemProject(item):
+                return True
+        return False
+
+
+    def _HasFilesSelected(self):
+        if not self._treeCtrl:
+            return False
+        items = self._treeCtrl.GetSelections()
+        if not items:
+            return False
+        for item in items:
+            if not self._IsItemFile(item):
+                return False
+        return True
+
+
+    def _MakeProjectName(self, project):
+        return project.GetPrintableName()
+
+
+    # Return the tree item for a project
+    def _GetProjectItem(self, project):
+        children = self._GetChildItems(self._treeCtrl.GetRootItem())
+        for child in children:
+            if self._treeCtrl.GetPyData(child) == project:
+                return child
+        return None
+
+
+    # Returns the project for an item, either for a project item or a file that is part of a project
+    def _GetItemProject(self, item):
+        if self._IsItemRoot(item):
+            return None
+        if self._IsItemProject(item):
+            return self._treeCtrl.GetPyData(item)
+        if self._IsItemFile(item):
+            return self._treeCtrl.GetPyData(self._treeCtrl.GetItemParent(item))
+        return None
+
+
+    def _GetItemFile(self, item):
+        if self._IsItemFile(item):
+            return self._treeCtrl.GetPyData(item)
+        else:
+            return None
+
+
+    def _GetFileItem(self, shortFileName = None, longFileName = None):
+        """ Returns the tree item for a file given the short (display) or long (fullpath) file name. """
+        
+        if shortFileName:
+            project_children = self._GetChildItems(self._treeCtrl.GetRootItem())
+            for child in project_children:
+                file_children = self._GetChildItems(child)
+                for file_child in file_children:
+                    if self._treeCtrl.GetItemText(file_child) == shortFileName:
+                        return file_child
+            return None
+        else:
+            project_children = self._GetChildItems(self._treeCtrl.GetRootItem())
+            for child in project_children:
+                file_children = self._GetChildItems(child)
+                for file_child in file_children:
+                    if self._treeCtrl.GetPyData(file_child) == longFileName:
+                        return file_child
+            return None
+
+
+    def GetFilePathFromTreeName(self, shortFileName):
+        """
+        Returns the data object given a short (display) file name for a file. The data
+        object should be the full path.
+        """
+        return self._GetItemFile(self._GetFileItem(shortFileName))
+    
+
+    def SelectFileInTree(self, shortFileName):
+        item = self._GetFileItem(shortFileName)
+        if item:
+            for selection in self._treeCtrl.GetSelections():
+                self._treeCtrl.SelectItem(selection, False)
+            self._treeCtrl.SelectItem(item, True)
+            self._treeCtrl.EnsureVisible(item)
+
+
+    def _IsItemRoot(self, item):
+        return item == self._treeCtrl.GetRootItem()
+
+
+    def _IsItemProject(self, item):
+        return isinstance(self._treeCtrl.GetPyData(item), ProjectDocument)
+
+
+    def _IsItemFile(self, item):
+        return isinstance(self._treeCtrl.GetPyData(item), types.StringTypes)
+
+
+    def _IsItemProcessModelFile(self, item):
+        if ACTIVEGRID_BASE_IDE:
+            return False
+            
+        if isinstance(self._treeCtrl.GetPyData(item), types.StringTypes):
+            filename = self._treeCtrl.GetPyData(item)
+            ext = None
+            for template in self.GetDocumentManager().GetTemplates():
+                if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument:
+                    ext = template.GetDefaultExtension()
+                    break;
+            if not ext:
+                return False
+
+            if filename.endswith(ext):
+                return True
+
+        return False
+
+
+    def _AreSelectedItemsFromSameProject(self):
+        if not self._treeCtrl:
+            return False
+        items = self._treeCtrl.GetSelections()
+        if not items:
+            return False
+        project = self._GetItemProject(items[0])
+        if project == None:
+            return False
+        for item in items:
+            if not self._IsItemFile(item):
+                return False
+            if self._GetItemProject(item) != project:
+                return False
+        return True
+
+
+    def _GetChildItems(self, parentItem):
+        children = []
+        (child, cookie) = self._treeCtrl.GetFirstChild(parentItem)
+        while child.IsOk():
+            children.append(child)
+            (child, cookie) = self._treeCtrl.GetNextChild(parentItem, cookie)
+        return children
+        
+        
+
+class ProjectFileDropTarget(wx.FileDropTarget):
+
+    def __init__(self, view):
+        wx.FileDropTarget.__init__(self)
+        self._view = view
+
+
+    def OnDropFiles(self, x, y, filenames):
+        if self._view.DoSelectProject(x, y):
+            self._view.DoAddFilesToProject(filenames)
+            self._view.DoSelectFiles(filenames)
+            return True
+        return False
+
+
+    def OnDragOver(self, x, y, default):
+        if self._view.DoSelectProject(x,y):
+            return wx.DragCopy
+        return wx.DragNone
+
+
+class ProjectPropertiesDialog(wx.Dialog):
+
+
+    def __init__(self, parent, filename):
+        wx.Dialog.__init__(self, parent, -1, _("Project Properties"), size = (310, 330))
+
+        HALF_SPACE = 5
+        SPACE = 10
+
+        filePropertiesService = wx.GetApp().GetService(wx.lib.pydocview.FilePropertiesService)
+
+        notebook = wx.Notebook(self, -1)
+        tab = wx.Panel(notebook, -1)
+
+        gridSizer = RowColSizer()
+
+        gridSizer.Add(wx.StaticText(tab, -1, _("Filename:")), flag=wx.RIGHT, border=HALF_SPACE, row=0, col=0)
+        if os.path.isfile(filename):
+            gridSizer.Add(wx.StaticText(tab, -1, os.path.split(filename)[1]), row=0, col=1)
+
+            gridSizer.Add(wx.StaticText(tab, -1, _("Location:")), flag=wx.RIGHT, border=HALF_SPACE, row=1, col=0)
+            gridSizer.Add(wx.StaticText(tab, -1, filePropertiesService.chopPath(os.path.split(filename)[0])), flag=wx.BOTTOM, border=SPACE, row=1, col=1)
+
+            gridSizer.Add(wx.StaticText(tab, -1, _("Size:")), flag=wx.RIGHT, border=HALF_SPACE, row=2, col=0)
+            gridSizer.Add(wx.StaticText(tab, -1, str(os.path.getsize(filename)) + ' ' + _("bytes")), row=2, col=1)
+
+            lineSizer = wx.BoxSizer(wx.VERTICAL)    # let the line expand horizontally without vertical expansion
+            lineSizer.Add(wx.StaticLine(tab, -1, size = (10,-1)), 0, wx.EXPAND)
+            gridSizer.Add(lineSizer, flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.TOP, border=HALF_SPACE, row=3, col=0, colspan=2)
+
+            gridSizer.Add(wx.StaticText(tab, -1, _("Created:")), flag=wx.RIGHT, border=HALF_SPACE, row=4, col=0)
+            gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getctime(filename))), row=4, col=1)
+
+            gridSizer.Add(wx.StaticText(tab, -1, _("Modified:")), flag=wx.RIGHT, border=HALF_SPACE, row=5, col=0)
+            gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getmtime(filename))), row=5, col=1)
+
+            gridSizer.Add(wx.StaticText(tab, -1, _("Accessed:")), flag=wx.RIGHT, border=HALF_SPACE, row=6, col=0)
+            gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getatime(filename))), row=6, col=1)
+
+        else:
+            gridSizer.Add(wx.StaticText(tab, -1, os.path.split(filename)[1] + ' ' + _("[new project]")), row=0, col=1)
+
+        # add a border around the inside of the tab
+        spacerGrid = wx.BoxSizer(wx.VERTICAL)
+        spacerGrid.Add(gridSizer, 0, wx.ALL, SPACE);
+        tab.SetSizer(spacerGrid)
+        notebook.AddPage(tab, _("General"))
+        if wx.Platform == "__WXMSW__":
+            notebook.SetPageSize((310,200))
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(notebook, 0, wx.ALL | wx.EXPAND, SPACE)
+        sizer.Add(self.CreateButtonSizer(wx.OK), 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, HALF_SPACE)
+
+        sizer.Fit(self)
+        self.SetDimensions(-1, -1, 310, -1, wx.SIZE_USE_EXISTING)
+        self.SetSizer(sizer)
+        self.Layout()
+
+
+class ProjectOptionsPanel(wx.Panel):
+
+
+    def __init__(self, parent, id):
+        wx.Panel.__init__(self, parent, id)
+        self._useSashMessageShown = False
+        SPACE = 10
+        HALF_SPACE = 5
+        config = wx.ConfigBase_Get()
+        self._projSaveDocsCheckBox = wx.CheckBox(self, -1, _("Remember open projects"))
+        self._projSaveDocsCheckBox.SetValue(config.ReadInt("ProjectSaveDocs", True))
+        projectBorderSizer = wx.BoxSizer(wx.VERTICAL)
+        projectSizer = wx.BoxSizer(wx.VERTICAL)
+        projectSizer.Add(self._projSaveDocsCheckBox, 0, wx.ALL, HALF_SPACE)
+        self._projShowWelcomeCheckBox = wx.CheckBox(self, -1, _("Show Welcome Dialog"))
+        self._projShowWelcomeCheckBox.SetValue(config.ReadInt("RunWelcomeDialog", True))
+        projectSizer.Add(self._projShowWelcomeCheckBox, 0, wx.ALL, HALF_SPACE)        
+        projectBorderSizer.Add(projectSizer, 0, wx.ALL, SPACE)
+        self.SetSizer(projectBorderSizer)
+        self.Layout()
+        parent.AddPage(self, _("Project"))
+
+    def OnUseSashSelect(self, event):
+        if not self._useSashMessageShown:
+            msgTitle = wx.GetApp().GetAppName()
+            if not msgTitle:
+                msgTitle = _("Document Options")
+            wx.MessageBox("Project window embedded mode changes will not appear until the application is restarted.",
+                          msgTitle,
+                          wx.OK | wx.ICON_INFORMATION,
+                          self.GetParent())
+            self._useSashMessageShown = True
+
+
+    def OnOK(self, optionsDialog):
+        config = wx.ConfigBase_Get()
+        config.WriteInt("ProjectSaveDocs", self._projSaveDocsCheckBox.GetValue())
+        config.WriteInt("RunWelcomeDialog", self._projShowWelcomeCheckBox.GetValue())
+
+
+class ProjectService(Service.Service):
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    SHOW_WINDOW = wx.NewId()  # keep this line for each subclass, need unique ID for each Service
+    RUNPM_ID = wx.NewId()
+    RUN_SELECTED_PM_ID = wx.NewId()
+    RUN_CURRENT_PM_ID = wx.NewId()
+    ADD_FILES_TO_PROJECT_ID = wx.NewId()
+    ADD_CURRENT_FILE_TO_PROJECT_ID = wx.NewId()
+    RENAME_ID = wx.NewId()
+    OPEN_SELECTION_ID = wx.NewId()
+    REMOVE_FROM_PROJECT = wx.NewId()
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_LEFT):
+        Service.Service.__init__(self, serviceName, embeddedWindowLocation)
+        self._runHandlers = []
+        self._suppressOpenProjectMessages = False
+
+
+    def _CreateView(self):
+        return ProjectView(self)
+
+
+    def ShowWindow(self, show = True):
+        """ Force showing of saved projects on opening, otherwise empty Project Window is disconcerting for user """
+        Service.Service.ShowWindow(self, show)
+
+        if show:
+            project = self.GetView().GetDocument()
+            if not project:
+                self.OpenSavedProjects()
+
+
+    #----------------------------------------------------------------------------
+    # Service specific methods
+    #----------------------------------------------------------------------------
+
+    def GetSuppressOpenProjectMessages(self):
+        return self._suppressOpenProjectMessages
+
+
+    def SetSuppressOpenProjectMessages(self, suppressOpenProjectMessages):
+        self._suppressOpenProjectMessages = suppressOpenProjectMessages
+
+
+    def GetRunHandlers(self):
+        return self._runHandlers
+
+
+    def AddRunHandler(self, runHandler):
+        self._runHandlers.append(runHandler)
+
+
+    def RemoveRunHandler(self, runHandler):
+        self._runHandlers.remove(runHandler)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        config = wx.ConfigBase_Get()
+
+        projectMenu = wx.Menu()
+
+##            accelTable = wx.AcceleratorTable([
+##                eval(_("wx.ACCEL_CTRL, ord('R'), ProjectService.RUN_ID"))
+##                ])
+##            frame.SetAcceleratorTable(accelTable)
+        isProjectDocument = document and document.GetDocumentTemplate().GetDocumentType() == ProjectDocument
+        if wx.GetApp().IsMDI() or isProjectDocument:
+            if not menuBar.FindItemById(ProjectService.ADD_FILES_TO_PROJECT_ID):
+                projectMenu.Append(ProjectService.ADD_FILES_TO_PROJECT_ID, _("&Add Files to Project..."), _("Adds a document to the current project"))
+                wx.EVT_MENU(frame, ProjectService.ADD_FILES_TO_PROJECT_ID, frame.ProcessEvent)
+                wx.EVT_UPDATE_UI(frame, ProjectService.ADD_FILES_TO_PROJECT_ID, frame.ProcessUpdateUIEvent)
+            if not menuBar.FindItemById(ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID):
+                projectMenu.Append(ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, _("&Add Active File to Project..."), _("Adds the active document to a project"))
+                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)
+        viewMenuIndex = menuBar.FindMenu(_("&View"))
+        menuBar.Insert(viewMenuIndex + 1, projectMenu, _("&Project"))
+        editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit")))
+        if not menuBar.FindItemById(ProjectService.RENAME_ID):
+            editMenu.AppendSeparator()
+            editMenu.Append(ProjectService.RENAME_ID, _("&Rename"), _("Renames the active item"))
+            wx.EVT_MENU(frame, ProjectService.RENAME_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, ProjectService.RENAME_ID, frame.ProcessUpdateUIEvent)
+
+        return True
+
+
+    def OnCloseFrame(self, event):
+        if not self.GetView():
+            return True
+
+        if wx.GetApp().IsMDI():
+            # close all non-project documents first
+            for document in self.GetDocumentManager().GetDocuments()[:]:  # Cloning list to make sure we go through all docs even as they are deleted
+                if document.GetDocumentTemplate().GetDocumentType() != ProjectDocument:
+                    if not self.GetDocumentManager().CloseDocument(document, False):
+                        return False
+
+            # write project config afterwards because user may change filenames on closing of new documents
+            self.GetView().WriteProjectConfig()  # Called onCloseWindow in all of the other services but needed to be factored out for ProjectService since it is called elsewhere
+
+            # close all project documents after closing other documents
+            # because user may save a new document with a new name or cancel closing a document
+            for document in self.GetDocumentManager().GetDocuments()[:]:  # Cloning list to make sure we go through all docs even as they are deleted
+                if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                    if not document.OnSaveModified():
+                        return False
+
+        # This is called when any SDI frame is closed, so need to check if message window is closing or some other window
+        elif self.GetView() == event.GetEventObject().GetView():
+            self.SetView(None)
+        return True
+
+
+    #----------------------------------------------------------------------------
+    # Event Processing Methods
+    #----------------------------------------------------------------------------
+
+    def ProcessEventBeforeWindows(self, event):
+        id = event.GetId()
+        if id == wx.ID_CLOSE_ALL:
+            self.OnFileCloseAll(event)
+            return True
+        return False
+
+
+    def ProcessEvent(self, event):
+        if Service.Service.ProcessEvent(self, event):
+            return True
+
+        id = event.GetId()
+        if id == ProjectService.RUN_SELECTED_PM_ID:
+            self.OnRunProcessModel(event, runSelected=True)
+            return True
+        elif id == ProjectService.RUN_CURRENT_PM_ID:
+            self.OnRunProcessModel(event, runCurrentFile=True)
+            return True
+        elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
+            self.OnAddCurrentFileToProject(event)
+            return True
+        elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID:
+            if self.GetView():
+                return self.GetView().ProcessEvent(event)
+            else:
+                return False
+        else:
+            return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if Service.Service.ProcessUpdateUIEvent(self, event):
+            return True
+
+        id = event.GetId()
+        if id == ProjectService.RUNPM_ID or id == ProjectService.RUN_SELECTED_PM_ID or id == ProjectService.RUN_CURRENT_PM_ID:
+            event.Enable(self._HasOpenedProjects() and self._HasProcessModel())
+            return True
+        elif id == ProjectService.ADD_FILES_TO_PROJECT_ID:
+            event.Enable(False)
+            return True
+        elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID:
+            event.Enable(self._CanAddCurrentFileToProject())
+            return True
+        elif id == ProjectService.RENAME_ID:
+            event.Enable(False)
+            return True
+        elif id == ProjectService.OPEN_SELECTION_ID:
+            event.Enable(False)
+            return True
+        elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID:
+            if self.GetView():
+                return self.GetView().ProcessUpdateUIEvent(event)
+            else:
+                return False
+        else:
+            return False
+
+
+    def OnRunProcessModel(self, event, runSelected=False, runCurrentFile=False):
+        project = self.GetView().GetDocument()
+
+        if project:
+            ext = None
+            for template in self.GetDocumentManager().GetTemplates():
+                if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument:
+                    ext = template.GetDefaultExtension()
+                    break;
+            if not ext:
+                return
+
+            files = filter(lambda f: f.endswith(ext), project.GetFiles())
+            if not files:
+                return
+
+            docs = wx.GetApp().GetDocumentManager().GetDocuments()
+            for doc in docs:
+                if doc.GetFilename() in files and doc.GetDocumentTemplate().GetDocumentType() == ProcessModelEditor.ProcessModelDocument:
+                    if not doc.GetProcessModel().beginProcess:
+                        wx.MessageBox(_("Cannot run process.  No begin action found."), _("Run Process"))
+                        return
+
+            filesModified = False
+            for doc in docs:
+                if doc.IsModified():
+                    filesModified = True
+                    break
+            if filesModified:
+                frame = self.GetView().GetFrame()                
+                yesNoMsg = wx.MessageDialog(frame,
+                              _("Files have been modified.  Process may not reflect your current changes.\n\nWould you like to save all files before running?"),
+                              _("Run Process"),
+                              wx.YES_NO
+                              )
+                if yesNoMsg.ShowModal() == wx.ID_YES:
+                    wx.GetTopLevelParent(frame).OnFileSaveAll(None)
+            
+            if runCurrentFile:
+                fileToRun = self.GetDocumentManager().GetCurrentDocument().GetFilename()
+            elif runSelected:
+                fileToRun = self.GetView().GetSelectedFile()
+            elif len(files) > 1:
+                files.sort(lambda a, b: cmp(os.path.basename(a).lower(), os.path.basename(b).lower()))
+                strings = map(lambda file: os.path.basename(file), files)
+                res = wx.GetSingleChoiceIndex(_("Select a process to run:"),
+                                              _("Run"),
+                                              strings,
+                                              project.GetFirstView()._GetParentFrame())
+                if res == -1:
+                    return
+                fileToRun = files[res]
+            else:
+                fileToRun = files[0]
+
+            self.RunProcessModel(fileToRun)
+            
+
+    def RunProcessModel(self, fileToRun):
+        for runHandler in self.GetRunHandlers():
+            if runHandler.RunProjectFile(fileToRun):
+                return
+        os.system('"' + fileToRun + '"')
+
+
+    def _HasProcessModel(self):
+        project = self.GetView().GetDocument()
+
+        if project:
+            ext = None
+            for template in self.GetDocumentManager().GetTemplates():
+                if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument:
+                    ext = template.GetDefaultExtension()
+                    break;
+            if not ext:
+                return False
+
+            files = filter(lambda f: f.endswith(ext), project.GetFiles())
+            if not files:
+                return False
+
+            if len(files):
+                return True
+
+        return False
+
+
+    def _HasOpenedProjects(self):
+        for document in self.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                return True
+        return False
+
+
+    def _HasCurrentFile(self):
+        currentDoc = self.GetDocumentManager().GetCurrentDocument()
+        return currentDoc
+       
+    def _CanAddCurrentFileToProject(self):
+        currentDoc = self.GetDocumentManager().GetCurrentDocument()
+        if not currentDoc:
+            return False
+        if currentDoc.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+            return False
+        if not currentDoc._savedYet:
+            return False
+        for document in self.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                return True
+        return False  # There are no documents open
+
+
+    def GetFilesFromCurrentProject(self):
+        view = self.GetView()
+        if view:
+            project = view.GetDocument()
+            if project:
+                return project.GetFiles()
+        return None
+
+
+    def GetCurrentProject(self):
+        view = self.GetView()
+        if view:
+            return view.GetDocument()
+        return None
+       
+
+    def FindProjectByFile(self, filename):
+        for document in self.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                if document.GetFilename() == filename:
+                    return document
+                elif document.IsFileInProject(filename):
+                    return document
+        return None
+        
+
+    def GetCurrentProjectNames(self):
+        projects = []
+        for document in self.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                projects.append(document)
+        if not projects:
+            return
+        projects.sort(lambda a, b: cmp(a.GetPrintableName().lower(), b.GetPrintableName().lower()))
+        strings = map(lambda project: project.GetPrintableName(), projects)
+        return strings
+        
+    def OnAddCurrentFileToProject(self, event):
+        if not self._CanAddCurrentFileToProject():
+            return
+        projects = []
+        for document in self.GetDocumentManager().GetDocuments():
+            if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument:
+                projects.append(document)
+        if not projects:
+            return
+        projects.sort(lambda a, b: cmp(a.GetPrintableName().lower(), b.GetPrintableName().lower()))
+        strings = map(lambda project: project.GetPrintableName(), projects)
+        res = wx.GetSingleChoiceIndex(_("Select a project to add the file to:"),
+                                      _("Add to Project"),
+                                      strings,
+                                      self.GetDocumentManager().FindSuitableParent())
+        if res == -1:
+            return
+        file = self.GetDocumentManager().GetCurrentDocument().GetFilename()
+        projects[res].GetCommandProcessor().Submit(ProjectAddFilesCommand(projects[res], [file]))
+        self.GetView().Activate(True)  # after add, should put focus on project editor
+
+
+    def OnFileCloseAll(self, event):
+        for document in self.GetDocumentManager().GetDocuments()[:]:  # Cloning list to make sure we go through all docs even as they are deleted
+            if document.GetDocumentTemplate().GetDocumentType() != ProjectDocument:
+                if not self.GetDocumentManager().CloseDocument(document, False):
+                    return
+                # document.DeleteAllViews() # Implicitly delete the document when the last view is removed
+
+
+    def OpenSavedProjects(self):
+        config = wx.ConfigBase_Get()
+        openedDocs = False
+        if config.ReadInt("ProjectSaveDocs", True):
+            docString = config.Read("ProjectSavedDocs")
+            if docString:
+                doc = None
+                for fileName in eval(docString):
+                    if isinstance(fileName, types.StringTypes):
+                        if os.path.exists(fileName):
+                            doc = self.GetDocumentManager().CreateDocument(fileName, wx.lib.docview.DOC_SILENT)
+
+                if doc:
+                    openedDocs = True
+                    expandedString = config.Read("ProjectExpandedSavedDocs")
+                    if expandedString:
+                        view = doc.GetFirstView()
+                        view.SetExpandedProjects(eval(expandedString))
+        return openedDocs
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getProjectData():
+    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[IDAT8\x8d\xc5\x93\xc1\n\xc00\x08C\x8d\xf6\xff\xffX\xb3Sa-\xf6`;:O\n\
+\x12\x1fj\x0059\t\xed\t\xc3\xc9pn\x0b\x88\x88@\rU\x81\xf6.\x18N\xa8aE\x92\rh\
+YC\x85\xa4D\x90\x91\xdc%\xf8w\x07+\xd1\xfbW\x98\xc5\x8f\t\x86W\xee\x93+\xbe\
+\xc0gn\xdc\x8d\x07\xab"<iG\x8e\xa9\r\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getProjectBitmap():
+    return BitmapFromImage(getProjectImage())
+
+def getProjectImage():
+    stream = cStringIO.StringIO(getProjectData())
+    return ImageFromStream(stream)
+
+def getProjectIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getProjectBitmap())
+    return icon
+    
+
+#----------------------------------------------------------------------------
+
+def getBlankData():
+    return \
+"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\
+\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00\
+\x85IDATX\x85\xed\x97\xc9\n\xc0 \x0cD3\xda\xff\xffcMo\x96Z\xc4\xa5\x91\x14:9\
+\x8a\xe8\xcb\xd3\xb8\x00!\x8ag\x04\xd7\xd9E\xe4\xa8\x1b4'}3 B\xc4L\x7fs\x03\
+\xb3\t<\x0c\x94\x81tN\x04p%\xae9\xe9\xa8\x89m{`\xd4\x84\xfd\x12\xa8\x16{#\
+\x10\xdb\xab\xa0\x07a\x0e\x00\xe0\xb6\x1fz\x10\xdf;\x07V\xa3U5\xb5\x8d:\xdc\
+\r\x10\x80\x00\x04 \x00\x01\x08@\x80\xe6{\xa0w\x8f[\x85\xbb\x01\xfc\xfeoH\
+\x80\x13>\xf9(3zH\x1e\xfb\x00\x00\x00\x00IEND\xaeB`\x82" 
+
+
+def getBlankBitmap():
+    return BitmapFromImage(getBlankImage())
+
+def getBlankImage():
+    stream = cStringIO.StringIO(getBlankData())
+    return ImageFromStream(stream)
+
+def getBlankIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getBlankBitmap())
+    return icon
+    
diff --git a/wxPython/samples/ide/activegrid/tool/PythonEditor.py b/wxPython/samples/ide/activegrid/tool/PythonEditor.py
new file mode 100644 (file)
index 0000000..06236d1
--- /dev/null
@@ -0,0 +1,614 @@
+#----------------------------------------------------------------------------
+# Name:         PythonEditor.py
+# Purpose:      PythonEditor for wx.lib.pydocview tbat uses the Styled Text Control
+#
+# Author:       Peter Yared
+#
+# Created:      8/15/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import CodeEditor
+import wx
+import wx.lib.docview
+import wx.lib.pydocview
+import string
+import keyword  # So it knows what to hilite
+import wx.py  # For the Python interpreter
+import wx.stc # For the Python interpreter
+import cStringIO  # For indent
+import OutlineService
+import STCTextEditor
+import keyword # for GetAutoCompleteKeywordList
+import sys # for GetAutoCompleteKeywordList
+import MessageService # for OnCheckCode
+import OutlineService
+try:
+    import checker # for pychecker
+    _CHECKER_INSTALLED = True
+except ImportError:
+    _CHECKER_INSTALLED = False
+import os.path # for pychecker
+_ = wx.GetTranslation
+
+if wx.Platform == '__WXMSW__':
+    _WINDOWS = True
+else:
+    _WINDOWS = False
+
+
+VIEW_PYTHON_INTERPRETER_ID = wx.NewId()
+
+
+class PythonDocument(CodeEditor.CodeDocument):
+    pass
+
+
+class PythonView(CodeEditor.CodeView):
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if not self.GetCtrl():
+            return False
+            
+        id = event.GetId()
+        if id == CodeEditor.CHECK_CODE_ID:
+            hasText = self.GetCtrl().GetTextLength() > 0
+            event.Enable(hasText)
+            return True
+            
+        return CodeEditor.CodeView.ProcessUpdateUIEvent(self, event)
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return PythonCtrl
+
+
+    def OnActivateView(self, activate, activeView, deactiveView):
+        STCTextEditor.TextView.OnActivateView(self, activate, activeView, deactiveView)
+        if activate:
+            wx.CallAfter(self.LoadOutline)  # need CallAfter because document isn't loaded yet
+        
+
+    def OnClose(self, deleteWindow = True):
+        status = STCTextEditor.TextView.OnClose(self, deleteWindow)
+        wx.CallAfter(self.ClearOutline)  # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter
+        return status
+       
+
+    def GetAutoCompleteKeywordList(self, context, hint):
+        obj = None
+        try:
+            if context and len(context):
+                obj = eval(context, globals(), locals())
+        except:
+            if not hint or len(hint) == 0:  # context isn't valid, maybe it was the hint
+                hint = context
+            
+        if obj is None:
+            kw = keyword.kwlist[:]
+        else:
+            symTbl = dir(obj)
+            kw = filter(lambda item: item[0] != '_', symTbl)  # remove local variables and methods
+        
+        if hint and len(hint):
+            lowerHint = hint.lower()
+            filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw)  # remove variables and methods that don't match hint
+            kw = filterkw
+
+        kw.sort(self.CaseInsensitiveCompare)
+
+        if hint:
+            replaceLen = len(hint)
+        else:
+            replaceLen = 0
+            
+        return " ".join(kw), replaceLen
+
+
+    def OnCheckCode(self):
+        if not _CHECKER_INSTALLED:       
+            wx.MessageBox(_("pychecker not found.  Please install pychecker."), _("Check Code"))
+            return
+
+        filename = os.path.basename(self.GetDocument().GetFilename())
+
+        # pychecker only works on files, doesn't take a stream or string input
+        if self.GetDocument().IsModified():
+            dlg = wx.MessageDialog(self.GetFrame(), _("'%s' has been modfied and must be saved first.  Save file and check code?") % filename, _("Check Code"))
+            val = dlg.ShowModal()
+            dlg.Destroy()
+            if val == wx.ID_OK:
+                self.GetDocument().Save()
+            else:
+                return
+            
+        messageService = wx.GetApp().GetService(MessageService.MessageService)
+        messageService.ShowWindow()
+        view = messageService.GetView()
+        if not view:
+            return
+            
+        view.ClearLines()
+        view.SetCallback(self.OnJumpToFoundLine)
+        
+        # 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)
+
+        # Set cursor to Default cursor
+        wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+
+    def OnJumpToFoundLine(self, event):
+        messageService = wx.GetApp().GetService(MessageService.MessageService)
+        lineText, pos = messageService.GetView().GetCurrLine()
+        
+        lineEnd = lineText.find(".py:")
+        if lineEnd == -1:
+            return
+
+        lineStart = lineEnd + len(".py:")
+        lineEnd = lineText.find(":", lineStart)
+        lineNum = int(lineText[lineStart:lineEnd])
+
+        filename = lineText[0:lineStart - 1]
+
+        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)
+            endPos = foundView.GetLineEndPosition(lineNum)
+            # wxBug:  Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct
+            #         Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first
+            #         time, we don't see the selection, it is scrolled off screen
+            foundView.SetSelection(endPos, startPos)
+            wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos)
+
+        
+
+class PythonInterpreterView(wx.lib.docview.View):
+
+
+    def OnCreate(self, doc, flags):
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
+        sizer = wx.BoxSizer()        
+        self._pyCrust = wx.py.crust.Crust(frame)
+        sizer.Add(self._pyCrust, 1, wx.EXPAND, 0)
+        frame.SetSizer(sizer)
+        frame.Layout()
+        self.Activate()
+        frame.Show()
+        return True
+
+
+    def ProcessEvent(self, event):
+        if not hasattr(self, "_pyCrust") or not self._pyCrust:
+            return wx.lib.docview.View.ProcessEvent(self, event)
+        stcControl = wx.Window_FindFocus()
+        if not isinstance(stcControl, wx.stc.StyledTextCtrl):
+            return wx.lib.docview.View.ProcessEvent(self, event)
+        id = event.GetId()
+        if id == wx.ID_UNDO:
+            stcControl.Undo()
+            return True
+        elif id == wx.ID_REDO:
+            stcControl.Redo()
+            return True
+        elif id == wx.ID_CUT:
+            stcControl.Cut()
+            return True
+        elif id == wx.ID_COPY:
+            stcControl.Copy()
+            return True
+        elif id == wx.ID_PASTE:
+            stcControl.Paste()
+            return True
+        elif id == wx.ID_CLEAR:
+            stcControl.Clear()
+            return True
+        elif id == wx.ID_SELECTALL:
+            stcControl.SetSelection(0, -1)
+            return True
+        else:
+            return wx.lib.docview.View.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if not hasattr(self, "_pyCrust") or not self._pyCrust:
+            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+        stcControl = wx.Window_FindFocus()
+        if not isinstance(stcControl, wx.stc.StyledTextCtrl):
+            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+        id = event.GetId()
+        if id == wx.ID_UNDO:
+            event.Enable(stcControl.CanUndo())
+            return True
+        elif id == wx.ID_REDO:
+            event.Enable(stcControl.CanRedo())
+            return True
+        elif id == wx.ID_CUT:
+            event.Enable(stcControl.CanCut())
+            return True
+        elif id == wx.ID_COPY:
+            event.Enable(stcControl.CanCopy())
+            return True
+        elif id == wx.ID_PASTE:
+            event.Enable(stcControl.CanPaste())
+            return True
+        elif id == wx.ID_CLEAR:
+            event.Enable(True)  # wxBug: should be stcControl.CanCut()) but disabling clear item means del key doesn't work in control as expected
+            return True
+        elif id == wx.ID_SELECTALL:
+            event.Enable(stcControl.GetTextLength() > 0)
+            return True
+        else:
+            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+
+
+    def OnClose(self, deleteWindow=True):
+        if deleteWindow and self.GetFrame():
+            self.GetFrame().Destroy()
+        return True
+
+
+class PythonService(CodeEditor.CodeService):
+
+
+    def __init__(self):
+        CodeEditor.CodeService.__init__(self)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        CodeEditor.CodeService.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
+
+        if document and document.GetDocumentTemplate().GetDocumentType() != PythonDocument:
+            return
+        if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            return
+
+        viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
+
+        viewStatusBarItemPos = self.GetMenuItemPos(viewMenu, wx.lib.pydocview.VIEW_STATUSBAR_ID)
+        viewMenu.InsertCheckItem(viewStatusBarItemPos + 1, VIEW_PYTHON_INTERPRETER_ID, _("Python &Interpreter"), _("Shows or hides the Python interactive window"))
+        wx.EVT_MENU(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessUpdateUIEvent)
+
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == VIEW_PYTHON_INTERPRETER_ID:
+            self.OnViewPythonInterpreter(event)
+            return True
+        else:
+            return CodeEditor.CodeService.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == VIEW_PYTHON_INTERPRETER_ID:
+            event.Enable(True)
+            docManager = wx.GetApp().GetDocumentManager()
+            event.Check(False)
+            for doc in docManager.GetDocuments():
+                if isinstance(doc.GetFirstView(), PythonInterpreterView):
+                    event.Check(True)
+                    break
+            return True
+        else:
+            return CodeEditor.CodeService.ProcessUpdateUIEvent(self, event)
+
+
+    def OnViewPythonInterpreter(self, event):
+        for doc in wx.GetApp().GetDocumentManager().GetDocuments():
+            if isinstance(doc.GetFirstView(), PythonInterpreterView):
+                doc.GetFirstView().GetDocument().DeleteAllViews()
+                return
+                
+        docManager = self.GetDocumentManager()
+        template = wx.lib.docview.DocTemplate(docManager,
+                                          _("Python Interpreter"),
+                                          "*.Foobar",
+                                          "Foobar",
+                                          ".Foobar",
+                                          _("Python Interpreter Document"),
+                                          _("Python Interpreter View"),
+                                          wx.lib.docview.Document,
+                                          PythonInterpreterView,
+                                          flags = wx.lib.docview.TEMPLATE_INVISIBLE)
+        newDoc = template.CreateDocument('', wx.lib.docview.DOC_SILENT)
+        if newDoc:
+            newDoc.SetDocumentName(template.GetDocumentName())
+            newDoc.SetDocumentTemplate(template)
+            newDoc.OnNewDocument()
+            newDoc.SetWriteable(False)
+            newDoc.GetFirstView().GetFrame().SetTitle(_("Python Interpreter"))
+
+
+class PythonCtrl(CodeEditor.CodeCtrl):
+
+
+    def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE):
+        CodeEditor.CodeCtrl.__init__(self, parent, ID, style)
+        self.SetProperty("tab.timmy.whinge.level", "1")
+        self.SetProperty("fold.comment.python", "1")
+        self.SetProperty("fold.quotes.python", "1")
+        self.SetLexer(wx.stc.STC_LEX_PYTHON)
+        self.SetKeyWords(0, string.join(keyword.kwlist))
+
+
+    def SetViewDefaults(self):
+        CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Python", hasWordWrap = False, hasTabs = True)
+
+
+    def GetFontAndColorFromConfig(self):
+        return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Python")
+
+
+    def UpdateStyles(self):
+        CodeEditor.CodeCtrl.UpdateStyles(self)
+
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+        # Python styles
+        # White space
+        self.StyleSetSpec(wx.stc.STC_P_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        # Comment
+        self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        # Number
+        self.StyleSetSpec(wx.stc.STC_P_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        # String
+        self.StyleSetSpec(wx.stc.STC_P_STRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        # Single quoted string
+        self.StyleSetSpec(wx.stc.STC_P_CHARACTER, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        # Keyword
+        self.StyleSetSpec(wx.stc.STC_P_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+        # Triple quotes
+        self.StyleSetSpec(wx.stc.STC_P_TRIPLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces)
+        # Triple double quotes
+        self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces)
+        # Class name definition
+        self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, "face:%(font)s,fore:#0000FF,bold,size:%(size)d" % faces)
+        # Function or method name definition
+        self.StyleSetSpec(wx.stc.STC_P_DEFNAME, "face:%(font)s,fore:#007F7F,bold,size:%(size)d" % faces)
+        # Operators
+        self.StyleSetSpec(wx.stc.STC_P_OPERATOR, "face:%(font)s,size:%(size)d" % faces)
+        # Identifiers
+        self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, "face:%(font)s,fore:#%(color)s,face:%(font)s,size:%(size)d" % faces)
+        # Comment-blocks
+        self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, "face:%(font)s,fore:#7F7F7F,size:%(size)d" % faces)
+        # End of line where string is not closed
+        self.StyleSetSpec(wx.stc.STC_P_STRINGEOL, "face:%(font)s,fore:#000000,face:%(font)s,back:#E0C0E0,eol,size:%(size)d" % faces)
+
+
+    def OnUpdateUI(self, evt):
+        braces = self.GetMatchingBraces()
+        
+        # check for matching braces
+        braceAtCaret = -1
+        braceOpposite = -1
+        charBefore = None
+        caretPos = self.GetCurrentPos()
+        if caretPos > 0:
+            charBefore = self.GetCharAt(caretPos - 1)
+            styleBefore = self.GetStyleAt(caretPos - 1)
+
+        # check before
+        if charBefore and chr(charBefore) in braces and styleBefore == wx.stc.STC_P_OPERATOR:
+            braceAtCaret = caretPos - 1
+
+        # check after
+        if braceAtCaret < 0:
+            charAfter = self.GetCharAt(caretPos)
+            styleAfter = self.GetStyleAt(caretPos)
+            if charAfter and chr(charAfter) in braces and styleAfter == wx.stc.STC_P_OPERATOR:
+                braceAtCaret = caretPos
+
+        if braceAtCaret >= 0:
+            braceOpposite = self.BraceMatch(braceAtCaret)
+
+        if braceAtCaret != -1  and braceOpposite == -1:
+            self.BraceBadLight(braceAtCaret)
+        else:
+            self.BraceHighlight(braceAtCaret, braceOpposite)
+
+        evt.Skip()
+
+
+    def DoIndent(self):
+        (text, caretPos) = self.GetCurLine()
+
+        self._tokenizerChars = {}  # This is really too much, need to find something more like a C array
+        for i in range(len(text)):
+            self._tokenizerChars[i] = 0
+
+        ctext = cStringIO.StringIO(text)
+        try:
+            tokenize.tokenize(ctext.readline, self)
+        except:
+            pass
+
+        # Left in for debugging purposes:
+        #for i in range(len(text)):
+        #    print i, text[i], self._tokenizerChars[i]
+
+        if caretPos == 0 or len(string.strip(text)) == 0:  # At beginning of line or within an empty line
+            self.AddText('\n')
+        else:
+            doExtraIndent = False
+            brackets = False
+            commentStart = -1
+            if caretPos > 1:
+                startParenCount = 0
+                endParenCount = 0
+                startSquareBracketCount = 0
+                endSquareBracketCount = 0
+                startCurlyBracketCount = 0
+                endCurlyBracketCount = 0
+                startQuoteCount = 0
+                endQuoteCount = 0
+                for i in range(caretPos - 1, -1, -1): # Go through each character before the caret
+                    if i >= len(text): # Sometimes the caret is at the end of the text if there is no LF
+                        continue
+                    if self._tokenizerChars[i] == 1:
+                        continue
+                    elif self._tokenizerChars[i] == 2:
+                        startQuoteCount = startQuoteCount + 1
+                    elif self._tokenizerChars[i] == 3:
+                        endQuoteCount = endQuoteCount + 1
+                    elif text[i] == '(': # Would be nice to use a dict for this, but the code is much more readable this way
+                        startParenCount = startParenCount + 1
+                    elif text[i] == ')':
+                        endParenCount = endParenCount + 1
+                    elif text[i] == "[":
+                        startSquareBracketCount = startSquareBracketCount + 1
+                    elif text[i] == "]":
+                        endSquareBracketCount = endSquareBracketCount + 1
+                    elif text[i] == "{":
+                        startCurlyBracketCount = startCurlyBracketCount + 1
+                    elif text[i] == "}":
+                        endCurlyBracketCount = endCurlyBracketCount + 1
+                    elif text[i] == "#":
+                        commentStart = i
+                        break
+                    if startQuoteCount > endQuoteCount or startParenCount > endParenCount or startSquareBracketCount > endSquareBracketCount or startCurlyBracketCount > endCurlyBracketCount:
+                        if i + 1 >= caretPos:  # Caret is right at the open paren, so just do indent as if colon was there
+                            doExtraIndent = True
+                            break
+                        else:
+                            spaces = " " * (i + 1)
+                            brackets = True
+                            break
+            if not brackets:
+                spaces = text[0:len(text) - len(string.lstrip(text))]
+                if caretPos < len(spaces):  # If within the opening spaces of a line
+                    spaces = spaces[:caretPos]
+
+                # strip comment off
+                if commentStart != -1:
+                    text = text[0:commentStart]
+
+                textNoTrailingSpaces = text[0:caretPos].rstrip()
+                if doExtraIndent or len(textNoTrailingSpaces) and textNoTrailingSpaces[-1] == ':':
+                    spaces = spaces + ' ' * self.GetIndent()
+            self.AddText('\n' + spaces)
+
+
+    # Callback for tokenizer in self.DoIndent
+    def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):
+        if toktype == tokenize.COMMENT:
+            for i in range(scol, ecol + 1):
+                self._validChars[i] = False
+        elif toktype == token.STRING:
+            self._tokenizerChars[scol] = 2 # Open quote
+            self._tokenizerChars[ecol - 1] = 3 # Close quote
+            for i in range(scol + 1, ecol - 2):
+                self._tokenizerChars[i] = 1 # Part of string, 1 == ignore the char
+
+
+class PythonOptionsPanel(wx.Panel):
+
+    def __init__(self, parent, id):
+        wx.Panel.__init__(self, parent, id)
+        pathLabel = wx.StaticText(self, -1, _("python.exe Path:"))
+        config = wx.ConfigBase_Get()
+        path = config.Read("ActiveGridPythonLocation")
+        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
+        pathSizer.Add(pathLabel, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, HALF_SPACE)
+        pathSizer.Add(self._pathTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE)
+        pathSizer.Add(choosePathButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE)
+        wx.EVT_BUTTON(self, choosePathButton.GetId(), self.OnChoosePath)
+        mainSizer = wx.BoxSizer(wx.VERTICAL)                
+        mainSizer.Add(pathSizer, 0, wx.LEFT | wx.RIGHT | wx.TOP, 10)
+
+        self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "Python", label = "Python", hasWordWrap = False, hasTabs = True, addPage=False)
+        mainSizer.Add(self._otherOptions)
+        self.SetSizer(mainSizer)
+        parent.AddPage(self, _("Python"))
+        
+    def OnChoosePath(self, event):
+        if _WINDOWS:
+            wildcard = _("*.exe")
+        else:
+            wildcard = _("*")
+        path = wx.FileSelector(_("Select a File"),
+                               _(""),
+                               _(""),
+                               wildcard = wildcard ,
+                               flags = wx.HIDE_READONLY,
+                               parent = wx.GetApp().GetTopWindow())
+        if path:  
+            self._pathTextCtrl.SetValue(path)
+            self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue())
+            self._pathTextCtrl.SetInsertionPointEnd()
+
+    def OnOK(self, optionsDialog):
+        if len(self._pathTextCtrl.GetValue()) > 0:
+            config = wx.ConfigBase_Get()
+            config.Write("ActiveGridPythonLocation", self._pathTextCtrl.GetValue())
+
+        self._otherOptions.OnOK(optionsDialog)
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getPythonData():
+    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\xd5IDAT8\x8d\x8d\x93Y\x0e\xc3 \x0cD\x9fM\xcf\xddNr2.\x96\xb8\x1f\
+\x05\n\x84.#Y\x10\xa3\x19o\xb1\x99'*\xe2<\x82\x0e\xe6\xc9\xf8\x01\xef?\xa4\
+\xf7)]\x05\x970O\xcdr\xce!\x119\xe7\x00\x02\x88\xfe}i\xb5\x848\x8f\xa8\x19\
+\xcc\x19}+\xc5\xcc\xd3\x92<CZ\x0b\x99\xc4\xb2N\x01<\x80\xad\xdc?\x88\xf8\x1c\
+X\x8f7\xe1\x1f\xdc*\xa9a+\xe1\xa3\xdc\xe7\xb4\xf6\xd1\xe5\xb6'\xc3@\xc5\xa0#\
+\xab\x94\xd1\x0bL\xf0\xe6\x17\xa8v\xc3\x8aS\xa0.\x8be\x13\xe3\x15\x8f\xe1\
+\xa5D\xee\xc9\xdb~%\xc7y\x84\xbb'sO\xd6\xd4\x17\xe4~\xc4\xf5\xef\xac\xa7\r\
+\xbbp?b&\x0f\x89i\x14\x93\xca\x14z\xc5oh\x02E\xc4<\xd92\x03\xe0:B^\xc4K#\xe7\
+\xe5\x00\x02\xfd\xb9H\x9ex\x02\x9a\x05a\xd2\xd3c\xc0\xcc\x00\x00\x00\x00IEND\
+\xaeB`\x82" 
+
+
+def getPythonBitmap():
+    return BitmapFromImage(getPythonImage())
+
+def getPythonImage():
+    stream = cStringIO.StringIO(getPythonData())
+    return ImageFromStream(stream)
+
+def getPythonIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getPythonBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/STCTextEditor.py b/wxPython/samples/ide/activegrid/tool/STCTextEditor.py
new file mode 100644 (file)
index 0000000..fca235a
--- /dev/null
@@ -0,0 +1,1293 @@
+#----------------------------------------------------------------------------
+# Name:         STCTextEditor.py
+# Purpose:      Text Editor for wx.lib.pydocview tbat uses the Styled Text Control
+#
+# Author:       Peter Yared, Morgan Hua
+#
+# Created:      8/10/03
+# CVS-ID:       $Id$
+# Copyright:    (c) 2003-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.stc
+import wx.lib.docview
+import wx.lib.multisash
+import wx.lib.pydocview
+import string
+import FindService
+import os
+import sys
+_ = wx.GetTranslation
+
+#----------------------------------------------------------------------------
+# Constants
+#----------------------------------------------------------------------------
+
+TEXT_ID = wx.NewId()
+VIEW_WHITESPACE_ID = wx.NewId()
+VIEW_EOL_ID = wx.NewId()
+VIEW_INDENTATION_GUIDES_ID = wx.NewId()
+VIEW_RIGHT_EDGE_ID = wx.NewId()
+VIEW_LINE_NUMBERS_ID = wx.NewId()
+ZOOM_ID = wx.NewId()
+ZOOM_NORMAL_ID = wx.NewId()
+ZOOM_IN_ID = wx.NewId()
+ZOOM_OUT_ID = wx.NewId()
+CHOOSE_FONT_ID = wx.NewId()
+WORD_WRAP_ID = wx.NewId()
+TEXT_STATUS_BAR_ID = wx.NewId()
+
+
+#----------------------------------------------------------------------------
+# Classes
+#----------------------------------------------------------------------------
+
+class TextDocument(wx.lib.docview.Document):
+
+
+    def OnSaveDocument(self, filename):
+        view = self.GetFirstView()
+        docFile = file(self._documentFile, "w")
+        docFile.write(view.GetValue())
+        docFile.close()
+        self.Modify(False)
+        self.SetDocumentSaved(True)
+        return True
+
+
+    def OnOpenDocument(self, filename):
+        view = self.GetFirstView()
+        docFile = file(self._documentFile, 'r')
+        data = docFile.read()
+        view.SetValue(data)
+        self.SetFilename(filename, True)
+        self.Modify(False)
+        self.UpdateAllViews()
+        self._savedYet = True
+        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)
+
+
+    def Modify(self, mod):
+        view = self.GetFirstView()
+        wx.lib.docview.Document.Modify(self, mod)
+        if not mod and view:
+            view.SetModifyFalse()
+
+
+    def OnCreateCommandProcessor(self):
+        # Don't create a command processor, it has its own
+        pass
+
+# Use this to override MultiClient.Select to prevent yellow background.
+def MultiClientSelectBGNotYellow(a):
+        a.GetParent().multiView.UnSelect()
+        a.selected = True
+        #a.SetBackgroundColour(wx.Colour(255,255,0)) # Yellow
+        a.Refresh()
+
+class TextView(wx.lib.docview.View):
+    MARKER_NUM = 0
+    MARKER_MASK = 0x1
+    
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self):
+        wx.lib.docview.View.__init__(self)
+        self._textEditor = None
+        self._markerCount = 0
+        self._commandProcessor = None
+        self._multiSash = None
+
+
+    def GetCtrlClass(self):
+        return TextCtrl
+
+
+    def GetCtrl(self):
+        # look for active one first
+        self._textEditor = self._GetActiveCtrl(self._multiSash)
+        if self._textEditor == None:  # it is possible none are active
+            # look for any existing one
+            self._textEditor = self._FindCtrl(self._multiSash)
+        return self._textEditor
+        
+
+##    def GetCtrls(self, parent = None):
+##        """ Walk through the MultiSash windows and find all Ctrls """
+##        controls = []
+##        if isinstance(parent, self.GetCtrlClass()):
+##            return [parent]
+##        if hasattr(parent, "GetChildren"):
+##            for child in parent.GetChildren():
+##                controls = controls + self.GetCtrls(child)
+##        return controls
+
+            
+    def OnCreate(self, doc, flags):
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags, style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
+        wx.lib.multisash.MultiClient.Select = MultiClientSelectBGNotYellow
+        self._multiSash = wx.lib.multisash.MultiSash(frame, -1)
+        self._multiSash.SetDefaultChildClass(self.GetCtrlClass()) # wxBug:  MultiSash instantiates the first TextCtrl with this call
+        self._textEditor = self.GetCtrl()  # wxBug: grab the TextCtrl from the MultiSash datastructure
+        self._CreateSizer(frame)
+        self.Activate()
+        frame.Show(True)
+        frame.Layout()
+        return True
+
+
+    def _GetActiveCtrl(self, parent):
+        """ Walk through the MultiSash windows and find the active Control """
+        if isinstance(parent, wx.lib.multisash.MultiClient) and parent.selected:
+            return parent.child
+        if hasattr(parent, "GetChildren"):
+            for child in parent.GetChildren():
+                found = self._GetActiveCtrl(child)
+                if found:
+                    return found
+        return None
+
+
+    def _FindCtrl(self, parent):
+        """ Walk through the MultiSash windows and find the first TextCtrl """
+        if isinstance(parent, self.GetCtrlClass()):
+            return parent
+        if hasattr(parent, "GetChildren"):
+            for child in parent.GetChildren():
+                found = self._FindCtrl(child)
+                if found:
+                    return found
+        return None
+
+
+    def _CreateSizer(self, frame):
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        sizer.Add(self._multiSash, 1, wx.EXPAND)
+        frame.SetSizer(sizer)
+        frame.SetAutoLayout(True)
+
+
+    def OnUpdate(self, sender = None, hint = None):
+        if hint == "ViewStuff":
+            self.GetCtrl().SetViewDefaults()
+        elif hint == "Font":
+            font, color = self.GetFontAndColorFromConfig()
+            self.GetCtrl().SetFont(font)
+            self.GetCtrl().SetFontColor(color)
+
+            
+    def OnActivateView(self, activate, activeView, deactiveView):
+        if activate and self.GetCtrl():
+            # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
+            if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+                self.GetCtrl().SetFocus()
+            else:
+                wx.CallAfter(self.GetCtrl().SetFocus)
+                   
+                                
+    def OnClose(self, deleteWindow = True):
+        if not wx.lib.docview.View.OnClose(self, deleteWindow):
+            return False
+        self.Activate(False)
+        if deleteWindow and self.GetFrame():
+            self.GetFrame().Destroy()
+        return True
+
+
+    def ProcessEvent(self, event):        
+        id = event.GetId()
+        if id == wx.ID_UNDO:
+            self.GetCtrl().Undo()
+            return True
+        elif id == wx.ID_REDO:
+            self.GetCtrl().Redo()
+            return True
+        elif id == wx.ID_CUT:
+            self.GetCtrl().Cut()
+            return True
+        elif id == wx.ID_COPY:
+            self.GetCtrl().Copy()
+            return True
+        elif id == wx.ID_PASTE:
+            self.GetCtrl().OnPaste()
+            return True
+        elif id == wx.ID_CLEAR:
+            self.GetCtrl().OnClear()
+            return True
+        elif id == wx.ID_SELECTALL:
+            self.GetCtrl().SetSelection(0, -1)
+            return True
+        elif id == VIEW_WHITESPACE_ID:
+            self.GetCtrl().SetViewWhiteSpace(not self.GetCtrl().GetViewWhiteSpace())
+            return True
+        elif id == VIEW_EOL_ID:
+            self.GetCtrl().SetViewEOL(not self.GetCtrl().GetViewEOL())
+            return True
+        elif id == VIEW_INDENTATION_GUIDES_ID:
+            self.GetCtrl().SetViewIndentationGuides(not self.GetCtrl().GetViewIndentationGuides())
+            return True
+        elif id == VIEW_RIGHT_EDGE_ID:
+            self.GetCtrl().SetViewRightEdge(not self.GetCtrl().GetViewRightEdge())
+            return True
+        elif id == VIEW_LINE_NUMBERS_ID:
+            self.GetCtrl().SetViewLineNumbers(not self.GetCtrl().GetViewLineNumbers())
+            return True
+        elif id == ZOOM_NORMAL_ID:
+            self.GetCtrl().SetZoom(0)
+            return True
+        elif id == ZOOM_IN_ID:
+            self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_ZOOMIN)
+            return True
+        elif id == ZOOM_OUT_ID:
+            self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_ZOOMOUT)
+            return True
+        elif id == CHOOSE_FONT_ID:
+            self.OnChooseFont()
+            return True
+        elif id == WORD_WRAP_ID:
+            self.GetCtrl().SetWordWrap(not self.GetCtrl().GetWordWrap())
+            return True
+        elif id == FindService.FindService.FIND_ID:
+            self.OnFind()
+            return True
+        elif id == FindService.FindService.FIND_PREVIOUS_ID:
+            self.DoFind(forceFindPrevious = True)
+            return True
+        elif id == FindService.FindService.FIND_NEXT_ID:
+            self.DoFind(forceFindNext = True)
+            return True
+        elif id == FindService.FindService.REPLACE_ID:
+            self.OnFind(replace = True)
+            return True
+        elif id == FindService.FindService.FINDONE_ID:
+            self.DoFind()
+            return True
+        elif id == FindService.FindService.REPLACEONE_ID:
+            self.DoFind(replace = True)
+            return True
+        elif id == FindService.FindService.REPLACEALL_ID:
+            self.DoFind(replaceAll = True)
+            return True
+        elif id == FindService.FindService.GOTO_LINE_ID:
+            self.OnGotoLine(event)
+            return True
+        else:
+            return wx.lib.docview.View.ProcessEvent(self, event)
+
+
+    def ProcessUpdateUIEvent(self, event):
+        if not self.GetCtrl():
+            return False
+
+        hasSelection = self.GetCtrl().GetSelectionStart() != self.GetCtrl().GetSelectionEnd()
+        hasText = self.GetCtrl().GetTextLength() > 0
+        notOnLastChar = self.GetCtrl().GetSelectionStart() != self.GetCtrl().GetTextLength()
+
+        id = event.GetId()
+        if id == wx.ID_UNDO:
+             event.Enable(self.GetCtrl().CanUndo())
+             event.SetText(_("Undo") + '\t' + _('Ctrl+Z'))
+             return True
+        elif id == wx.ID_REDO:
+            event.Enable(self.GetCtrl().CanRedo())
+            event.SetText(_("Redo") + '\t' + _('Ctrl+Y'))
+            return True
+        elif id == wx.ID_CUT:
+            event.Enable(hasSelection)
+            return True
+        elif id == wx.ID_COPY:
+            event.Enable(hasSelection)
+            return True
+        elif id == wx.ID_PASTE:
+            event.Enable(self.GetCtrl().CanPaste())
+            return True
+        elif id == wx.ID_CLEAR:
+            event.Enable(hasSelection)
+            return True
+        elif id == wx.ID_SELECTALL:
+            event.Enable(hasText)
+            return True
+        elif id == TEXT_ID:
+            event.Enable(True)
+            return True
+        elif id == VIEW_WHITESPACE_ID:
+            event.Enable(hasText)
+            event.Check(self.GetCtrl().GetViewWhiteSpace())
+            return True
+        elif id == VIEW_EOL_ID:
+            event.Enable(hasText)
+            event.Check(self.GetCtrl().GetViewEOL())
+            return True
+        elif id == VIEW_INDENTATION_GUIDES_ID:
+            event.Enable(hasText)
+            event.Check(self.GetCtrl().GetIndentationGuides())
+            return True
+        elif id == VIEW_RIGHT_EDGE_ID:
+            event.Enable(hasText)
+            event.Check(self.GetCtrl().GetViewRightEdge())
+            return True
+        elif id == VIEW_LINE_NUMBERS_ID:
+            event.Enable(hasText)
+            event.Check(self.GetCtrl().GetViewLineNumbers())
+            return True
+        elif id == ZOOM_ID:
+            event.Enable(True)
+            return True
+        elif id == ZOOM_NORMAL_ID:
+            event.Enable(self.GetCtrl().GetZoom() != 0)
+            return True
+        elif id == ZOOM_IN_ID:
+            event.Enable(self.GetCtrl().GetZoom() < 20)
+            return True
+        elif id == ZOOM_OUT_ID:
+            event.Enable(self.GetCtrl().GetZoom() > -10)
+            return True
+        elif id == CHOOSE_FONT_ID:
+            event.Enable(True)
+            return True
+        elif id == WORD_WRAP_ID:
+            event.Enable(self.GetCtrl().CanWordWrap())
+            event.Check(self.GetCtrl().CanWordWrap() and self.GetCtrl().GetWordWrap())
+            return True
+        elif id == FindService.FindService.FIND_ID:
+            event.Enable(hasText)
+            return True
+        elif id == FindService.FindService.FIND_PREVIOUS_ID:
+            event.Enable(hasText and
+                         self._FindServiceHasString() and
+                         self.GetCtrl().GetSelection()[0] > 0)
+            return True
+        elif id == FindService.FindService.FIND_NEXT_ID:
+            event.Enable(hasText and
+                         self._FindServiceHasString() and
+                         self.GetCtrl().GetSelection()[0] < self.GetCtrl().GetLength())
+            return True
+        elif id == FindService.FindService.REPLACE_ID:
+            event.Enable(hasText)
+            return True
+        elif id == FindService.FindService.GOTO_LINE_ID:
+            event.Enable(True)
+            return True
+        elif id == TEXT_STATUS_BAR_ID:
+            self.OnUpdateStatusBar(event)
+            return True
+        else:
+            return wx.lib.docview.View.ProcessUpdateUIEvent(self, event)
+
+
+    def _GetParentFrame(self):
+        return wx.GetTopLevelParent(self.GetFrame())
+
+
+    #----------------------------------------------------------------------------
+    # Methods for TextDocument to call
+    #----------------------------------------------------------------------------
+
+    def IsModified(self):
+        if not self.GetCtrl():
+            return False
+        return self.GetCtrl().GetModify()
+
+
+    def SetModifyFalse(self):
+        self.GetCtrl().SetSavePoint()
+
+
+    def GetValue(self):
+        if self.GetCtrl():
+            return self.GetCtrl().GetText()
+        else:
+            return None
+
+
+    def SetValue(self, value):
+        self.GetCtrl().SetText(value)
+        self.GetCtrl().UpdateLineNumberMarginWidth()
+        self.GetCtrl().EmptyUndoBuffer()
+
+
+    #----------------------------------------------------------------------------
+    # STC events
+    #----------------------------------------------------------------------------
+
+    def OnUpdateStatusBar(self, event):
+        statusBar = self._GetParentFrame().GetStatusBar()
+        statusBar.SetInsertMode(self.GetCtrl().GetOvertype() == 0)
+        statusBar.SetLineNumber(self.GetCtrl().GetCurrentLine() + 1)
+        statusBar.SetColumnNumber(self.GetCtrl().GetColumn(self.GetCtrl().GetCurrentPos()) + 1)
+
+
+    #----------------------------------------------------------------------------
+    # Format methods
+    #----------------------------------------------------------------------------
+
+    def OnChooseFont(self):
+        data = wx.FontData()
+        data.EnableEffects(True)
+        data.SetInitialFont(self.GetCtrl().GetFont())
+        data.SetColour(self.GetCtrl().GetFontColor())
+        fontDialog = wx.FontDialog(self.GetFrame(), data)
+        if fontDialog.ShowModal() == wx.ID_OK:
+            data = fontDialog.GetFontData()
+            self.GetCtrl().SetFont(data.GetChosenFont())
+            self.GetCtrl().SetFontColor(data.GetColour())
+            self.GetCtrl().UpdateStyles()
+        fontDialog.Destroy()
+
+
+    #----------------------------------------------------------------------------
+    # Find methods
+    #----------------------------------------------------------------------------
+
+    def OnFind(self, replace = False):
+        findService = wx.GetApp().GetService(FindService.FindService)
+        if findService:
+            findService.ShowFindReplaceDialog(findString = self.GetCtrl().GetSelectedText(), replace = replace)
+
+
+    def DoFind(self, forceFindNext = False, forceFindPrevious = False, replace = False, replaceAll = False):
+        findService = wx.GetApp().GetService(FindService.FindService)
+        if not findService:
+            return
+        findString = findService.GetFindString()
+        if len(findString) == 0:
+            return -1
+        replaceString = findService.GetReplaceString()
+        flags = findService.GetFlags()
+        startLoc, endLoc = self.GetCtrl().GetSelection()
+
+        wholeWord = flags & wx.FR_WHOLEWORD > 0
+        matchCase = flags & wx.FR_MATCHCASE > 0
+        regExp = flags & FindService.FindService.FR_REGEXP > 0
+        down = flags & wx.FR_DOWN > 0
+        wrap = flags & FindService.FindService.FR_WRAP > 0
+
+        if forceFindPrevious:   # this is from function keys, not dialog box
+            down = False
+            wrap = False        # user would want to know they're at the end of file
+        elif forceFindNext:
+            down = True
+            wrap = False        # user would want to know they're at the end of file
+
+        badSyntax = False
+        
+        # On replace dialog operations, user is allowed to replace the currently highlighted text to determine if it should be replaced or not.
+        # Typically, it is the text from a previous find operation, but we must check to see if it isn't, user may have moved the cursor or selected some other text accidentally.
+        # If the text is a match, then replace it.
+        if replace:
+            result, start, end, replText = findService.DoFind(findString, replaceString, self.GetCtrl().GetSelectedText(), 0, 0, True, matchCase, wholeWord, regExp, replace)
+            if result > 0:
+                self.GetCtrl().ReplaceSelection(replText)
+                self.GetDocument().Modify(True)
+                wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString)
+                if down:
+                    startLoc += len(replText)  # advance start location past replacement string to new text
+                endLoc = startLoc
+            elif result == FindService.FIND_SYNTAXERROR:
+                badSyntax = True
+                wx.GetApp().GetTopWindow().PushStatusText(_("Invalid regular expression \"%s\"") % findString)
+
+        if not badSyntax:
+            text = self.GetCtrl().GetText()
+    
+            # Find the next matching text occurance or if it is a ReplaceAll, replace all occurances
+            # Even if the user is Replacing, we should replace here, but only select the text and let the user replace it with the next Replace operation
+            result, start, end, text = findService.DoFind(findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExp, False, replaceAll, wrap)
+            if result > 0:
+                self.GetCtrl().SetTargetStart(0)
+                self.GetCtrl().SetTargetEnd(self.GetCtrl().GetLength())
+                self.GetCtrl().ReplaceTarget(text)  # Doing a SetText causes a clear document to be shown when undoing, so using replacetarget instead
+                self.GetDocument().Modify(True)
+                if result == 1:
+                    wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString)
+                else:
+                    wx.GetApp().GetTopWindow().PushStatusText(_("%i occurrences of \"%s\" replaced") % (result, findString))
+            elif result == 0:
+                self.GetCtrl().SetSelection(start, end)
+                self.GetCtrl().EnsureVisible(self.GetCtrl().LineFromPosition(end))  # show bottom then scroll up to top
+                self.GetCtrl().EnsureVisible(self.GetCtrl().LineFromPosition(start)) # do this after ensuring bottom is visible
+                wx.GetApp().GetTopWindow().PushStatusText(_("Found \"%s\".") % findString)
+            elif result == FindService.FIND_SYNTAXERROR:
+                # Dialog for this case gets popped up by the FindService.
+                wx.GetApp().GetTopWindow().PushStatusText(_("Invalid regular expression \"%s\"") % findString)
+            else:
+                wx.MessageBox(_("Can't find \"%s\".") % findString, "Find",
+                          wx.OK | wx.ICON_INFORMATION)
+
+
+    def _FindServiceHasString(self):
+        findService = wx.GetApp().GetService(FindService.FindService)
+        if not findService or not findService.GetFindString():
+            return False
+        return True
+
+
+    def OnGotoLine(self, event):
+        findService = wx.GetApp().GetService(FindService.FindService)
+        if findService:
+            line = findService.GetLineNumber(self.GetDocumentManager().FindSuitableParent())
+            if line > -1:
+                line = line - 1
+                self.GetCtrl().EnsureVisible(line)
+                self.GetCtrl().GotoLine(line)
+
+
+    def GotoLine(self, lineNum):
+        if lineNum > -1:
+            lineNum = lineNum - 1  # line numbering for editor is 0 based, we are 1 based.
+            self.GetCtrl().EnsureVisibleEnforcePolicy(lineNum)
+            self.GetCtrl().GotoLine(lineNum)
+
+
+    def SetSelection(self, start, end):
+        self.GetCtrl().SetSelection(start, end)
+
+
+    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.
+
+
+    def PositionFromLine(self, line):
+        return self.GetCtrl().PositionFromLine(line-1)  # line numbering for editor is 0 based, we are 1 based.
+
+
+    def GetLineEndPosition(self, line):
+        return self.GetCtrl().GetLineEndPosition(line-1)  # line numbering for editor is 0 based, we are 1 based.
+
+
+    def GetLine(self, lineNum):
+        return self.GetCtrl().GetLine(lineNum-1)  # line numbering for editor is 0 based, we are 1 based.
+
+    def MarkerDefine(self):
+        """ This must be called after the texteditor is instantiated """
+        self.GetCtrl().MarkerDefine(TextView.MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, wx.BLUE)
+
+
+    def MarkerToggle(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine()
+        if self.GetCtrl().MarkerGet(lineNum) & mask:
+            self.GetCtrl().MarkerDelete(lineNum, marker_index)
+            self._markerCount -= 1
+        else:
+            self.GetCtrl().MarkerAdd(lineNum, marker_index)
+            self._markerCount += 1
+
+    def MarkerAdd(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine()
+        self.GetCtrl().MarkerAdd(lineNum, marker_index)
+        self._markerCount += 1
+
+
+    def MarkerDelete(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine()
+        if self.GetCtrl().MarkerGet(lineNum) & mask:
+            self.GetCtrl().MarkerDelete(lineNum, marker_index)
+            self._markerCount -= 1
+
+    def MarkerDeleteAll(self, marker_num=MARKER_NUM):
+        self.GetCtrl().MarkerDeleteAll(marker_num)
+        if marker_num == self.MARKER_NUM:
+            self._markerCount = 0
+
+
+    def MarkerNext(self, lineNum = -1):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine() + 1  # start search below current line
+        foundLine = self.GetCtrl().MarkerNext(lineNum, self.MARKER_MASK)
+        if foundLine == -1:
+            # wrap to top of file
+            foundLine = self.GetCtrl().MarkerNext(0, self.MARKER_MASK)
+            if foundLine == -1:
+                wx.GetApp().GetTopWindow().PushStatusText(_("No markers"))
+                return
+        
+        self.GotoLine(foundLine + 1)
+
+
+    def MarkerPrevious(self, lineNum = -1):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine() - 1  # start search above current line
+            if lineNum == -1:
+                lineNum = self.GetCtrl().GetLineCount()
+
+        foundLine = self.GetCtrl().MarkerPrevious(lineNum, self.MARKER_MASK)
+        if foundLine == -1:
+            # wrap to bottom of file
+            foundLine = self.GetCtrl().MarkerPrevious(self.GetCtrl().GetLineCount(), self.MARKER_MASK)
+            if foundLine == -1:
+                wx.GetApp().GetTopWindow().PushStatusText(_("No markers"))
+                return
+
+        self.GotoLine(foundLine + 1)
+
+
+    def MarkerExists(self, lineNum = -1, mask=MARKER_MASK):
+        if lineNum == -1:
+            lineNum = self.GetCtrl().GetCurrentLine()
+        if self.GetCtrl().MarkerGet(lineNum) & mask:
+            return True
+        else:
+            return False
+
+
+    def GetMarkerCount(self):
+        return self._markerCount
+
+
+class TextService(wx.lib.pydocview.DocService):
+
+
+    def __init__(self):
+        wx.lib.pydocview.DocService.__init__(self)
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument:
+            return
+        if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+            return
+
+        statusBar = TextStatusBar(frame, TEXT_STATUS_BAR_ID)
+        frame.SetStatusBar(statusBar)
+        wx.EVT_UPDATE_UI(frame, TEXT_STATUS_BAR_ID, frame.ProcessUpdateUIEvent)
+
+        viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
+
+        viewMenu.AppendSeparator()
+        textMenu = wx.Menu()
+        textMenu.AppendCheckItem(VIEW_WHITESPACE_ID, _("&Whitespace"), _("Shows or hides whitespace"))
+        wx.EVT_MENU(frame, VIEW_WHITESPACE_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_WHITESPACE_ID, frame.ProcessUpdateUIEvent)
+        textMenu.AppendCheckItem(VIEW_EOL_ID, _("&End of Line Markers"), _("Shows or hides indicators at the end of each line"))
+        wx.EVT_MENU(frame, VIEW_EOL_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_EOL_ID, frame.ProcessUpdateUIEvent)
+        textMenu.AppendCheckItem(VIEW_INDENTATION_GUIDES_ID, _("&Indentation Guides"), _("Shows or hides indentations"))
+        wx.EVT_MENU(frame, VIEW_INDENTATION_GUIDES_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_INDENTATION_GUIDES_ID, frame.ProcessUpdateUIEvent)
+        textMenu.AppendCheckItem(VIEW_RIGHT_EDGE_ID, _("&Right Edge"), _("Shows or hides the right edge marker"))
+        wx.EVT_MENU(frame, VIEW_RIGHT_EDGE_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_RIGHT_EDGE_ID, frame.ProcessUpdateUIEvent)
+        textMenu.AppendCheckItem(VIEW_LINE_NUMBERS_ID, _("&Line Numbers"), _("Shows or hides the line numbers"))
+        wx.EVT_MENU(frame, VIEW_LINE_NUMBERS_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, VIEW_LINE_NUMBERS_ID, frame.ProcessUpdateUIEvent)
+        
+        viewMenu.AppendMenu(TEXT_ID, _("&Text"), textMenu)
+        wx.EVT_UPDATE_UI(frame, TEXT_ID, frame.ProcessUpdateUIEvent)
+        
+        isWindows = (wx.Platform == '__WXMSW__')
+
+        zoomMenu = wx.Menu()
+        zoomMenu.Append(ZOOM_NORMAL_ID, _("Normal Size"), _("Sets the document to its normal size"))
+        wx.EVT_MENU(frame, ZOOM_NORMAL_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, ZOOM_NORMAL_ID, frame.ProcessUpdateUIEvent)
+        if isWindows:
+            zoomMenu.Append(ZOOM_IN_ID, _("Zoom In\tCtrl+Page Up"), _("Zooms the document to a larger size"))
+        else:
+            zoomMenu.Append(ZOOM_IN_ID, _("Zoom In"), _("Zooms the document to a larger size"))
+        wx.EVT_MENU(frame, ZOOM_IN_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, ZOOM_IN_ID, frame.ProcessUpdateUIEvent)
+        if isWindows:
+            zoomMenu.Append(ZOOM_OUT_ID, _("Zoom Out\tCtrl+Page Down"), _("Zooms the document to a smaller size"))
+        else:
+            zoomMenu.Append(ZOOM_OUT_ID, _("Zoom Out"), _("Zooms the document to a smaller size"))
+        wx.EVT_MENU(frame, ZOOM_OUT_ID, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, ZOOM_OUT_ID, frame.ProcessUpdateUIEvent)
+        
+        viewMenu.AppendMenu(ZOOM_ID, _("&Zoom"), zoomMenu)
+        wx.EVT_UPDATE_UI(frame, ZOOM_ID, frame.ProcessUpdateUIEvent)
+
+        formatMenuIndex = menuBar.FindMenu(_("&Format"))
+        if formatMenuIndex > -1:
+            formatMenu = menuBar.GetMenu(formatMenuIndex)
+        else:
+            formatMenu = wx.Menu()
+        if not menuBar.FindItemById(CHOOSE_FONT_ID):
+            formatMenu.Append(CHOOSE_FONT_ID, _("&Font..."), _("Sets the font to use"))
+            wx.EVT_MENU(frame, CHOOSE_FONT_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, CHOOSE_FONT_ID, frame.ProcessUpdateUIEvent)
+        if not menuBar.FindItemById(WORD_WRAP_ID):
+            formatMenu.AppendCheckItem(WORD_WRAP_ID, _("Word Wrap"), _("Wraps text horizontally when checked"))
+            wx.EVT_MENU(frame, WORD_WRAP_ID, frame.ProcessEvent)
+            wx.EVT_UPDATE_UI(frame, WORD_WRAP_ID, frame.ProcessUpdateUIEvent)
+        if formatMenuIndex == -1:
+            viewMenuIndex = menuBar.FindMenu(_("&View"))
+            menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format"))
+
+        # wxBug: wxToolBar::GetToolPos doesn't exist, need it to find cut tool and then insert find in front of it.
+        toolBar.AddSeparator()
+        toolBar.AddTool(ZOOM_IN_ID, getZoomInBitmap(), shortHelpString = _("Zoom In"), longHelpString = _("Zooms the document to a larger size"))
+        toolBar.AddTool(ZOOM_OUT_ID, getZoomOutBitmap(), shortHelpString = _("Zoom Out"), longHelpString = _("Zooms the document to a smaller size"))
+        toolBar.Realize()
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == TEXT_ID:
+            event.Enable(False)
+            return True
+        elif id == VIEW_WHITESPACE_ID:
+            event.Enable(False)
+            return True
+        elif id == VIEW_EOL_ID:
+            event.Enable(False)
+            return True
+        elif id == VIEW_INDENTATION_GUIDES_ID:
+            event.Enable(False)
+            return True
+        elif id == VIEW_RIGHT_EDGE_ID:
+            event.Enable(False)
+            return True
+        elif id == VIEW_LINE_NUMBERS_ID:
+            event.Enable(False)
+            return True
+        elif id == ZOOM_ID:
+            event.Enable(False)
+            return True
+        elif id == ZOOM_NORMAL_ID:
+            event.Enable(False)
+            return True
+        elif id == ZOOM_IN_ID:
+            event.Enable(False)
+            return True
+        elif id == ZOOM_OUT_ID:
+            event.Enable(False)
+            return True
+        elif id == CHOOSE_FONT_ID:
+            event.Enable(False)
+            return True
+        elif id == WORD_WRAP_ID:
+            event.Enable(False)
+            return True
+        else:
+            return False
+
+
+class TextStatusBar(wx.StatusBar):
+
+    # wxBug: Would be nice to show num key status in statusbar, but can't figure out how to detect if it is enabled or disabled
+
+    def __init__(self, parent, id, style = wx.ST_SIZEGRIP, name = "statusBar"):
+        wx.StatusBar.__init__(self, parent, id, style, name)
+        self.SetFieldsCount(4)
+        self.SetStatusWidths([-1, 50, 50, 55])
+
+    def SetInsertMode(self, insert = True):
+        if insert:
+            newText = _("Ins")
+        else:
+            newText = _("")
+        if self.GetStatusText(1) != newText:     # wxBug: Need to check if the text has changed, otherwise it flickers under win32
+            self.SetStatusText(newText, 1)
+
+    def SetLineNumber(self, lineNumber):
+        newText = _("Ln %i") % lineNumber
+        if self.GetStatusText(2) != newText:
+            self.SetStatusText(newText, 2)
+
+    def SetColumnNumber(self, colNumber):
+        newText = _("Col %i") % colNumber
+        if self.GetStatusText(3) != newText:
+            self.SetStatusText(newText, 3)
+
+
+class TextOptionsPanel(wx.Panel):
+
+
+    def __init__(self, parent, id, configPrefix = "Text", label = "Text", hasWordWrap = True, hasTabs = False, addPage=True):
+        wx.Panel.__init__(self, parent, id)
+        self._configPrefix = configPrefix
+        self._hasWordWrap = hasWordWrap
+        self._hasTabs = hasTabs
+        SPACE = 10
+        HALF_SPACE   = 5
+        config = wx.ConfigBase_Get()
+        self._textFont = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)
+        fontData = config.Read(self._configPrefix + "EditorFont", "")
+        if fontData:
+            nativeFont = wx.NativeFontInfo()
+            nativeFont.FromString(fontData)
+            self._textFont.SetNativeFontInfo(nativeFont)
+        self._originalTextFont = self._textFont
+        self._textColor = wx.BLACK
+        colorData = config.Read(self._configPrefix + "EditorColor", "")
+        if colorData:
+            red = int("0x" + colorData[0:2], 16)
+            green = int("0x" + colorData[2:4], 16)
+            blue = int("0x" + colorData[4:6], 16)
+            self._textColor = wx.Color(red, green, blue)
+        self._originalTextColor = self._textColor
+        fontLabel = wx.StaticText(self, -1, _("Font:"))
+        self._sampleTextCtrl = wx.TextCtrl(self, -1, "", size = (125, 21))
+        self._sampleTextCtrl.SetEditable(False)
+        chooseFontButton = wx.Button(self, -1, _("Choose Font..."))
+        wx.EVT_BUTTON(self, chooseFontButton.GetId(), self.OnChooseFont)
+        if self._hasWordWrap:
+            self._wordWrapCheckBox = wx.CheckBox(self, -1, _("Wrap words inside text area"))
+            self._wordWrapCheckBox.SetValue(wx.ConfigBase_Get().ReadInt(self._configPrefix + "EditorWordWrap", False))
+        self._viewWhitespaceCheckBox = wx.CheckBox(self, -1, _("Show whitespace"))
+        self._viewWhitespaceCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewWhitespace", False))
+        self._viewEOLCheckBox = wx.CheckBox(self, -1, _("Show end of line markers"))
+        self._viewEOLCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewEOL", False))
+        self._viewIndentationGuideCheckBox = wx.CheckBox(self, -1, _("Show indentation guides"))
+        self._viewIndentationGuideCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewIndentationGuides", False))
+        self._viewRightEdgeCheckBox = wx.CheckBox(self, -1, _("Show right edge"))
+        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._hasTabs:
+            self._hasTabsCheckBox = wx.CheckBox(self, -1, _("Use spaces instead of tabs"))
+            self._hasTabsCheckBox.SetValue(not wx.ConfigBase_Get().ReadInt(self._configPrefix + "EditorUseTabs", False))
+            indentWidthLabel = wx.StaticText(self, -1, _("Indent Width:"))
+            self._indentWidthChoice = wx.Choice(self, -1, choices = ["2", "4", "6", "8", "10"])
+            self._indentWidthChoice.SetStringSelection(str(config.ReadInt(self._configPrefix + "EditorIndentWidth", 4)))
+        textPanelBorderSizer = wx.BoxSizer(wx.VERTICAL)
+        textPanelSizer = wx.BoxSizer(wx.VERTICAL)
+        textFontSizer = wx.BoxSizer(wx.HORIZONTAL)
+        textFontSizer.Add(fontLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE)
+        textFontSizer.Add(self._sampleTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE)
+        textFontSizer.Add(chooseFontButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE)
+        textPanelSizer.Add(textFontSizer, 0, wx.ALL, HALF_SPACE)
+        if self._hasWordWrap:
+            textPanelSizer.Add(self._wordWrapCheckBox, 0, wx.ALL, HALF_SPACE)
+        textPanelSizer.Add(self._viewWhitespaceCheckBox, 0, wx.ALL, HALF_SPACE)
+        textPanelSizer.Add(self._viewEOLCheckBox, 0, wx.ALL, HALF_SPACE)
+        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._hasTabs:
+            textPanelSizer.Add(self._hasTabsCheckBox, 0, wx.ALL, HALF_SPACE)
+            textIndentWidthSizer = wx.BoxSizer(wx.HORIZONTAL)
+            textIndentWidthSizer.Add(indentWidthLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE)
+            textIndentWidthSizer.Add(self._indentWidthChoice, 0, wx.ALIGN_LEFT | wx.EXPAND, HALF_SPACE)
+            textPanelSizer.Add(textIndentWidthSizer, 0, wx.ALL, HALF_SPACE)
+        textPanelBorderSizer.Add(textPanelSizer, 0, wx.ALL, SPACE)
+##        styleButton = wx.Button(self, -1, _("Choose Style..."))
+##        wx.EVT_BUTTON(self, styleButton.GetId(), self.OnChooseStyle)
+##        textPanelBorderSizer.Add(styleButton, 0, wx.ALL, SPACE)
+        self.SetSizer(textPanelBorderSizer)
+        self.UpdateSampleFont()
+        if addPage:
+            parent.AddPage(self, _(label))
+
+    def UpdateSampleFont(self):
+        nativeFont = wx.NativeFontInfo()
+        nativeFont.FromString(self._textFont.GetNativeFontInfoDesc())
+        font = wx.NullFont
+        font.SetNativeFontInfo(nativeFont)
+        font.SetPointSize(self._sampleTextCtrl.GetFont().GetPointSize())  # Use the standard point size
+        self._sampleTextCtrl.SetFont(font)
+        self._sampleTextCtrl.SetForegroundColour(self._textColor)
+        self._sampleTextCtrl.SetValue(str(self._textFont.GetPointSize()) + _(" pt. ") + self._textFont.GetFaceName())
+        self._sampleTextCtrl.Refresh()
+        self.Layout()
+
+
+##    def OnChooseStyle(self, event):
+##        import STCStyleEditor
+##        import os
+##        base = os.path.split(__file__)[0]
+##        config = os.path.abspath(os.path.join(base, 'stc-styles.rc.cfg'))
+##        
+##        dlg = STCStyleEditor.STCStyleEditDlg(None,
+##                                'Python', 'python',
+##                                #'HTML', 'html',
+##                                #'XML', 'xml',
+##                                config)
+##        try:
+##            dlg.ShowModal()
+##        finally:
+##            dlg.Destroy()
+
+
+    def OnChooseFont(self, event):
+        data = wx.FontData()
+        data.EnableEffects(True)
+        data.SetInitialFont(self._textFont)
+        data.SetColour(self._textColor)
+        fontDialog = wx.FontDialog(self, data)
+        if fontDialog.ShowModal() == wx.ID_OK:
+            data = fontDialog.GetFontData()
+            self._textFont = data.GetChosenFont()
+            self._textColor = data.GetColour()
+            self.UpdateSampleFont()
+        fontDialog.Destroy()
+
+
+    def OnOK(self, optionsDialog):
+        config = wx.ConfigBase_Get()
+        doViewStuffUpdate = config.ReadInt(self._configPrefix + "EditorViewWhitespace", False) != self._viewWhitespaceCheckBox.GetValue()
+        config.WriteInt(self._configPrefix + "EditorViewWhitespace", self._viewWhitespaceCheckBox.GetValue())
+        doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewEOL", False) != self._viewEOLCheckBox.GetValue()
+        config.WriteInt(self._configPrefix + "EditorViewEOL", self._viewEOLCheckBox.GetValue())
+        doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewIndentationGuides", False) != self._viewIndentationGuideCheckBox.GetValue()
+        config.WriteInt(self._configPrefix + "EditorViewIndentationGuides", self._viewIndentationGuideCheckBox.GetValue())
+        doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewRightEdge", False) != self._viewRightEdgeCheckBox.GetValue()
+        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._hasWordWrap:
+            doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorWordWrap", False) != self._wordWrapCheckBox.GetValue()
+            config.WriteInt(self._configPrefix + "EditorWordWrap", self._wordWrapCheckBox.GetValue())
+        if self._hasTabs:
+            doViewStuffUpdate = doViewStuffUpdate or not config.ReadInt(self._configPrefix + "EditorUseTabs", True) != self._hasTabsCheckBox.GetValue()
+            config.WriteInt(self._configPrefix + "EditorUseTabs", not self._hasTabsCheckBox.GetValue())
+            newIndentWidth = int(self._indentWidthChoice.GetStringSelection())
+            oldIndentWidth = config.ReadInt(self._configPrefix + "EditorIndentWidth", 4)
+            if newIndentWidth != oldIndentWidth:
+                doViewStuffUpdate = True
+                config.WriteInt(self._configPrefix + "EditorIndentWidth", newIndentWidth)
+        doFontUpdate = self._originalTextFont != self._textFont or self._originalTextColor != self._textColor
+        config.Write(self._configPrefix + "EditorFont", self._textFont.GetNativeFontInfoDesc())
+        config.Write(self._configPrefix + "EditorColor", "%02x%02x%02x" % (self._textColor.Red(), self._textColor.Green(), self._textColor.Blue()))
+        if doViewStuffUpdate or doFontUpdate:
+            for document in optionsDialog.GetDocManager().GetDocuments():
+                if issubclass(document.GetDocumentTemplate().GetDocumentType(), TextDocument):
+                    if doViewStuffUpdate:
+                        document.UpdateAllViews(hint = "ViewStuff")
+                    if doFontUpdate:
+                        document.UpdateAllViews(hint = "Font")
+
+
+class TextCtrl(wx.stc.StyledTextCtrl):
+
+    def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE):
+        if ID == -1:
+            ID = wx.NewId()
+        wx.stc.StyledTextCtrl.__init__(self, parent, ID, style = style)
+
+        self._font = None
+        self._fontColor = None
+        
+        self.SetVisiblePolicy(wx.stc.STC_VISIBLE_STRICT,0)
+        self.SetYCaretPolicy(0, 0)
+        
+        self.CmdKeyClear(wx.stc.STC_KEY_ADD, wx.stc.STC_SCMOD_CTRL)
+        self.CmdKeyClear(wx.stc.STC_KEY_SUBTRACT, wx.stc.STC_SCMOD_CTRL)
+        self.CmdKeyAssign(wx.stc.STC_KEY_PRIOR, wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN)
+        self.CmdKeyAssign(wx.stc.STC_KEY_NEXT, wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMOUT)
+        self.Bind(wx.stc.EVT_STC_ZOOM, self.OnUpdateLineNumberMarginWidth)  # auto update line num width on zoom
+        wx.EVT_KEY_DOWN(self, self.OnKeyPressed)
+        self.SetMargins(0,0)
+
+        self.SetUseTabs(0)
+        self.SetTabWidth(4)
+        self.SetIndent(4)
+
+        self.SetViewWhiteSpace(False)
+        self.SetEOLMode(wx.stc.STC_EOL_LF)
+        self.SetEdgeMode(wx.stc.STC_EDGE_NONE)
+        self.SetEdgeColumn(78)
+
+        self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
+        self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth())
+        self.UpdateStyles()
+
+        self.SetCaretForeground("BLACK")
+        
+        self.SetViewDefaults()
+        font, color = self.GetFontAndColorFromConfig()
+        self.SetFont(font)
+        self.SetFontColor(color)
+        self.MarkerDefineDefault()
+
+        # for multisash initialization
+        if isinstance(parent, wx.lib.multisash.MultiClient):
+            while parent.GetParent():
+                parent = parent.GetParent()
+                if hasattr(parent, "GetView"):
+                    break
+            if hasattr(parent, "GetView"):
+                textEditor = parent.GetView()._textEditor
+                if textEditor:
+                    doc = textEditor.GetDocPointer()
+                    if doc:
+                        self.SetDocPointer(doc)
+
+
+        
+    def SetViewDefaults(self, configPrefix = "Text", hasWordWrap = True, hasTabs = 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 hasWordWrap:
+            self.SetWordWrap(config.ReadInt(configPrefix + "EditorWordWrap", False))
+        if hasTabs:  # These methods do not exist in STCTextEditor and are meant for subclasses
+            self.SetUseTabs(config.ReadInt(configPrefix + "EditorUseTabs", False))
+            self.SetIndent(config.ReadInt(configPrefix + "EditorIndentWidth", 4))
+            self.SetTabWidth(config.ReadInt(configPrefix + "EditorIndentWidth", 4))
+        else:
+            self.SetUseTabs(True)
+            self.SetIndent(4)
+            self.SetTabWidth(4)
+
+
+        
+    def GetDefaultFont(self):
+        """ Subclasses should override this """
+        return wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL)
+        
+
+    def GetDefaultColor(self):
+        """ Subclasses should override this """
+        return wx.BLACK
+
+
+    def GetFontAndColorFromConfig(self, configPrefix = "Text"):
+        font = self.GetDefaultFont()
+        config = wx.ConfigBase_Get()
+        fontData = config.Read(configPrefix + "EditorFont", "")
+        if fontData:
+            nativeFont = wx.NativeFontInfo()
+            nativeFont.FromString(fontData)
+            font.SetNativeFontInfo(nativeFont)
+        color = self.GetDefaultColor()
+        colorData = config.Read(configPrefix + "EditorColor", "")
+        if colorData:
+            red = int("0x" + colorData[0:2], 16)
+            green = int("0x" + colorData[2:4], 16)
+            blue = int("0x" + colorData[4:6], 16)
+            color = wx.Color(red, green, blue)
+        return font, color
+
+
+    def GetFont(self):
+        return self._font
+        
+    def SetFont(self, font):
+        self._font = font
+        self.StyleSetFont(wx.stc.STC_STYLE_DEFAULT, self._font)
+
+
+    def GetFontColor(self):
+        return self._fontColor
+
+
+    def SetFontColor(self, fontColor = wx.BLACK):
+        self._fontColor = fontColor
+        self.StyleSetForeground(wx.stc.STC_STYLE_DEFAULT, "#%02x%02x%02x" % (self._fontColor.Red(), self._fontColor.Green(), self._fontColor.Blue()))
+
+
+    def UpdateStyles(self):
+        self.StyleClearAll()
+        return
+
+
+    def EstimatedLineNumberMarginWidth(self):
+        MARGIN = 4
+        baseNumbers = "000"
+        lineNum = self.GetLineCount()
+        lineNum = lineNum/100
+        while lineNum >= 10:
+            lineNum = lineNum/10
+            baseNumbers = baseNumbers + "0"
+
+        return self.TextWidth(wx.stc.STC_STYLE_LINENUMBER, baseNumbers) + MARGIN
+
+
+    def OnUpdateLineNumberMarginWidth(self, event):
+        self.UpdateLineNumberMarginWidth()
+
+            
+    def UpdateLineNumberMarginWidth(self):
+        if self.GetViewLineNumbers():
+            self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth())
+        
+    def MarkerDefineDefault(self):
+        """ This must be called after the textcontrol is instantiated """
+        self.MarkerDefine(TextView.MARKER_NUM, wx.stc.STC_MARK_ROUNDRECT, wx.BLACK, wx.BLUE)
+
+
+    def OnClear(self):
+        # Used when Delete key is hit.
+        sel = self.GetSelection()
+                
+        # Delete the selection or if no selection, the character after the caret.
+        if sel[0] == sel[1]:
+            self.SetSelection(sel[0], sel[0] + 1)
+        else:
+            # remove any folded lines also.
+            startLine = self.LineFromPosition(sel[0])
+            endLine = self.LineFromPosition(sel[1])
+            endLineStart = self.PositionFromLine(endLine)
+            if startLine != endLine and sel[1] - endLineStart == 0:
+                while not self.GetLineVisible(endLine):
+                    endLine += 1
+                self.SetSelectionEnd(self.PositionFromLine(endLine))
+            
+        self.Clear()
+
+
+    def OnPaste(self):
+        # replace any folded lines also.
+        sel = self.GetSelection()
+        startLine = self.LineFromPosition(sel[0])
+        endLine = self.LineFromPosition(sel[1])
+        endLineStart = self.PositionFromLine(endLine)
+        if startLine != endLine and sel[1] - endLineStart == 0:
+            while not self.GetLineVisible(endLine):
+                endLine += 1
+            self.SetSelectionEnd(self.PositionFromLine(endLine))
+                
+        self.Paste()
+        
+
+    def OnKeyPressed(self, event):
+        key = event.GetKeyCode()
+        if key == wx.WXK_NUMPAD_ADD:  #wxBug: For whatever reason, the key accelerators for numpad add and subtract with modifiers are not working so have to trap them here
+            if event.ControlDown():
+                self.ToggleFoldAll(expand = True, topLevelOnly = True)
+            elif event.ShiftDown():
+                self.ToggleFoldAll(expand = True)
+            else:
+                self.ToggleFold(self.GetCurrentLine())
+        elif key == wx.WXK_NUMPAD_SUBTRACT:
+            if event.ControlDown():
+                self.ToggleFoldAll(expand = False, topLevelOnly = True)
+            elif event.ShiftDown():
+                self.ToggleFoldAll(expand = False)
+            else:
+                self.ToggleFold(self.GetCurrentLine())
+        else:
+            event.Skip()
+
+
+    #----------------------------------------------------------------------------
+    # View Text methods
+    #----------------------------------------------------------------------------
+
+    def GetViewRightEdge(self):
+        return self.GetEdgeMode() != wx.stc.STC_EDGE_NONE
+
+
+    def SetViewRightEdge(self, viewRightEdge):
+        if viewRightEdge:
+            self.SetEdgeMode(wx.stc.STC_EDGE_LINE)
+        else:
+            self.SetEdgeMode(wx.stc.STC_EDGE_NONE)
+
+
+    def GetViewLineNumbers(self):
+        return self.GetMarginWidth(1) > 0
+
+
+    def SetViewLineNumbers(self, viewLineNumbers = True):
+        if viewLineNumbers:
+            self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth())
+        else:
+            self.SetMarginWidth(1, 0)
+
+
+    def CanWordWrap(self):
+        return True
+
+
+    def GetWordWrap(self):
+        return self.GetWrapMode() == wx.stc.STC_WRAP_WORD
+
+
+    def SetWordWrap(self, wordWrap):
+        if wordWrap:
+            self.SetWrapMode(wx.stc.STC_WRAP_WORD)
+        else:
+            self.SetWrapMode(wx.stc.STC_WRAP_NONE)
+
+        
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getTextData():
+    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`IDAT8\x8d\xed\x931\x0e\xc00\x08\x03m\x92\xff\xff8q\xa7JU!$\x12\x1d\
+\xeb\t\t8n\x81\xb4\x86J\xfa]h\x0ee\x83\xb4\xc6\x14\x00\x00R\xcc \t\xcd\xa1\
+\x08\xd2\xa3\xe1\x08*\t$\x1d\xc4\x012\x0b\x00\xce\xe4\xc8\xe0\t}\xf7\x8f\rV\
+\xd9\x1a\xec\xe0\xbf\xc1\xd7\x06\xd9\xf5UX\xfdF+m\x03\xb8\x00\xe4\xc74B"x\
+\xf1\xf4\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+
+def getTextBitmap():
+    return BitmapFromImage(getTextImage())
+
+def getTextImage():
+    stream = cStringIO.StringIO(getTextData())
+    return ImageFromStream(stream)
+
+def getTextIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getTextBitmap())
+    return icon
+
+
+#----------------------------------------------------------------------------
+# Menu Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+#----------------------------------------------------------------------
+def getZoomInData():
+    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\x00wIDAT8\x8d\xa5\x93Q\x12\x80 \x08D\xb5\xe9X\xee\xe9\xb7{\xd5Gc\xa9\
+\xacX\xca\x1f\xa0\x8fE0\x92<\xc3\x82\xed*\x08\xa0\xf2I~\x07\x000\x17T,\xdb\
+\xd6;\x08\xa4\x00\xa4GA\xab\xca\x00\xbc*\x1eD\xb4\x90\xa4O\x1e\xe3\x16f\xcc(\
+\xc8\x95F\x95\x8d\x02\xef\xa1n\xa0\xce\xc5v\x91zc\xacU\xbey\x03\xf0.\xa8\xb8\
+\x04\x8c\xac\x04MM\xa1lA\xfe\x85?\x90\xe5=X\x06\\\xebCA\xb3Q\xf34\x14\x00\
+\x00\x00\x00IEND\xaeB`\x82' 
+
+def getZoomInBitmap():
+    return BitmapFromImage(getZoomInImage())
+
+def getZoomInImage():
+    stream = cStringIO.StringIO(getZoomInData())
+    return ImageFromStream(stream)
+
+#----------------------------------------------------------------------
+def getZoomOutData():
+    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\x00qIDAT8\x8d\xa5\x92Q\x0e\xc0 \x08C-z\xff\x13O\xd9\xd7\x16"\x05\x8d\
+\xf6O\xa2\x8f"\x05\xa4\x96\x1b5V\xd4\xd1\xd5\x9e!\x15\xdb\x00\x1d]\xe7\x07\
+\xac\xf6Iv.B*fW\x0e\x90u\xc9 d\x84\x87v\x82\xb4\xf5\x08\'r\x0e\xa2N\x91~\x07\
+\xd9G\x95\xe2W\xeb\x00\x19\xc4\xd6\\FX\x12\xa3 \xb1:\x05\xacdAG[\xb0y9r`u\
+\x9d\x83k\xc0\x0b#3@0A\x0c"\x93\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getZoomOutBitmap():
+    return BitmapFromImage(getZoomOutImage())
+
+def getZoomOutImage():
+    stream = cStringIO.StringIO(getZoomOutData())
+    return ImageFromStream(stream)
+
+
+
diff --git a/wxPython/samples/ide/activegrid/tool/Service.py b/wxPython/samples/ide/activegrid/tool/Service.py
new file mode 100644 (file)
index 0000000..a55702f
--- /dev/null
@@ -0,0 +1,327 @@
+#----------------------------------------------------------------------------
+# Name:         Service.py
+# Purpose:      Basic Reusable Service View for wx.lib.pydocview
+#
+# Author:       Morgan Hua
+#
+# Created:      11/4/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+import wx.lib.pydocview
+_ = wx.GetTranslation
+
+
+FLOATING_MINIFRAME = -1
+
+
+class ServiceView(wx.EvtHandler):
+    """ Basic Service View.
+    """
+    bottomTab = None
+    
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self, service):
+        wx.EvtHandler.__init__(self)
+        self._viewFrame = None
+        self._service = service
+        self._control = None
+        self._embeddedWindow = None
+
+
+    def Destroy(self):
+        wx.EvtHandler.Destroy(self)
+
+
+    def GetFrame(self):
+        return self._viewFrame
+
+
+    def SetFrame(self, frame):
+        self._viewFrame = frame
+
+
+    def _CreateControl(self, parent, id):
+        return None
+        
+    
+    def GetControl(self):
+        return self._control
+
+
+    def SetControl(self, control):
+        self._control = control
+
+
+    def OnCreate(self, doc, flags):
+        config = wx.ConfigBase_Get()
+        windowLoc = self._service.GetEmbeddedWindowLocation()
+        if windowLoc == FLOATING_MINIFRAME:
+            pos = config.ReadInt(self._service.GetServiceName() + "FrameXLoc", -1), config.ReadInt(self._service.GetServiceName() + "FrameYLoc", -1)
+            # make sure frame is visible
+            screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
+            screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
+            if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight:
+                pos = wx.DefaultPosition
+
+            size = wx.Size(config.ReadInt(self._service.GetServiceName() + "FrameXSize", -1), config.ReadInt(self._service.GetServiceName() + "FrameYSize", -1))
+            title = _(self._service.GetServiceName())
+            if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName():
+                title =  title + " - " + wx.GetApp().GetAppName()
+            frame = wx.MiniFrame(wx.GetApp().GetTopWindow(), -1, title, pos = pos, size = size, style = wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU)
+            wx.EVT_CLOSE(frame, self.OnCloseWindow)
+        elif wx.GetApp().IsMDI():
+            self._embeddedWindow = wx.GetApp().GetTopWindow().GetEmbeddedWindow(windowLoc)
+            frame = self._embeddedWindow
+        else:
+            pos = config.ReadInt(self._service.GetServiceName() + "FrameXLoc", -1), config.ReadInt(self._service.GetServiceName() + "FrameYLoc", -1)
+            # make sure frame is visible
+            screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
+            screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
+            if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight:
+                pos = wx.DefaultPosition
+
+            size = wx.Size(config.ReadInt(self._service.GetServiceName() + "FrameXSize", -1), config.ReadInt(self._service.GetServiceName() + "FrameYSize", -1))
+            title = _(self._service.GetServiceName())
+            if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName():
+                title =  title + " - " + wx.GetApp().GetAppName()
+            frame = wx.GetApp().CreateDocumentFrame(self, doc, flags, pos = pos, size = size)
+            frame.SetTitle(title)
+            if config.ReadInt(self._service.GetServiceName() + "FrameMaximized", False):
+                frame.Maximize(True)
+            wx.EVT_CLOSE(frame, self.OnCloseWindow)
+
+        self.SetFrame(frame)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        windowLoc = self._service.GetEmbeddedWindowLocation()
+        if self._embeddedWindow or windowLoc == FLOATING_MINIFRAME:
+            if (self._service.GetEmbeddedWindowLocation() == wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM):
+                if ServiceView.bottomTab == None:
+                    ServiceView.bottomTab = wx.Notebook(frame, wx.NewId(), (0,0), (100,100), wx.LB_DEFAULT, "Bottom Tab")
+                    sizer.Add(ServiceView.bottomTab, 1, wx.TOP|wx.EXPAND, 4)
+                    def OnFrameResize(event):
+                        ServiceView.bottomTab.SetSize(ServiceView.bottomTab.GetParent().GetSize())
+                    frame.Bind(wx.EVT_SIZE, OnFrameResize)
+                # Factor this out.
+                self._control = self._CreateControl(ServiceView.bottomTab, wx.NewId())
+                if self._control != None:
+                    ServiceView.bottomTab.AddPage(self._control, self._service.GetServiceName())
+                ServiceView.bottomTab.Layout()
+            else:
+                # Factor this out.
+                self._control = self._CreateControl(frame, wx.NewId())
+                sizer.Add(self._control)
+        else:
+            # Factor this out.
+            self._control = self._CreateControl(frame, wx.NewId())
+            sizer.Add(self._control, 1, wx.EXPAND, 0)
+        frame.SetSizer(sizer)
+        frame.Layout()
+
+        return True
+
+
+    def OnCloseWindow(self, event):
+        frame = self.GetFrame()
+        config = wx.ConfigBase_Get()
+        if frame and not self._embeddedWindow:
+            if not frame.IsMaximized():
+                config.WriteInt(self._service.GetServiceName() + "FrameXLoc", frame.GetPositionTuple()[0])
+                config.WriteInt(self._service.GetServiceName() + "FrameYLoc", frame.GetPositionTuple()[1])
+                config.WriteInt(self._service.GetServiceName() + "FrameXSize", frame.GetSizeTuple()[0])
+                config.WriteInt(self._service.GetServiceName() + "FrameYSize", frame.GetSizeTuple()[1])
+            config.WriteInt(self._service.GetServiceName() + "FrameMaximized", frame.IsMaximized())
+
+        if not self._embeddedWindow:
+            windowLoc = self._service.GetEmbeddedWindowLocation()
+            if windowLoc == FLOATING_MINIFRAME:
+                # don't destroy it, just hide it
+                frame.Hide()
+            else:
+                # Call the original OnCloseWindow, could have subclassed SDIDocFrame and MDIDocFrame but this is easier since it will work for both SDI and MDI frames without subclassing both
+                frame.OnCloseWindow(event)
+
+
+    def Activate(self, activate = True):
+        """ Dummy function for SDI mode """
+        pass
+
+
+    def Close(self, deleteWindow = True):
+        """
+        Closes the view by calling OnClose. If deleteWindow is true, this
+        function should delete the window associated with the view.
+        """
+        if deleteWindow:
+            self.Destroy()
+
+        return True
+
+
+    #----------------------------------------------------------------------------
+    # Callback Methods
+    #----------------------------------------------------------------------------
+
+    def SetCallback(self, callback):
+        """ Sets in the event table for a doubleclick to invoke the given callback.
+            Additional calls to this method overwrites the previous entry and only the last set callback will be invoked.
+        """
+        wx.stc.EVT_STC_DOUBLECLICK(self.GetControl(), self.GetControl().GetId(), callback)
+
+
+    #----------------------------------------------------------------------------
+    # Display Methods
+    #----------------------------------------------------------------------------
+
+    def IsShown(self):
+        if not self.GetFrame():
+            return False
+        return self.GetFrame().IsShown()
+
+
+    def Hide(self):
+        self.Show(False)
+
+
+    def Show(self, show = True):
+        self.GetFrame().Show(show)
+        if self._embeddedWindow:
+            mdiParentFrame = wx.GetApp().GetTopWindow()
+            mdiParentFrame.ShowEmbeddedWindow(self.GetFrame(), show)
+
+
+class Service(wx.lib.pydocview.DocService):
+
+
+    #----------------------------------------------------------------------------
+    # Constants
+    #----------------------------------------------------------------------------
+    SHOW_WINDOW = wx.NewId()  # keep this line for each subclass, need unique ID for each Service
+
+
+    def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_LEFT):
+        self._serviceName = serviceName
+        self._embeddedWindowLocation = embeddedWindowLocation
+        self._view = None
+
+
+    def GetEmbeddedWindowLocation(self):
+        return self._embeddedWindowLocation
+
+
+    def SetEmbeddedWindowLocation(self, embeddedWindowLocation):
+        self._embeddedWindowLocation = embeddedWindowLocation
+
+
+    def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
+        viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
+        menuItemPos = self.GetMenuItemPos(viewMenu, viewMenu.FindItem(_("&Status Bar"))) + 1
+
+        viewMenu.InsertCheckItem(menuItemPos, self.SHOW_WINDOW, self.GetMenuString(), self.GetMenuDescr())
+        wx.EVT_MENU(frame, self.SHOW_WINDOW, frame.ProcessEvent)
+        wx.EVT_UPDATE_UI(frame, self.SHOW_WINDOW, frame.ProcessUpdateUIEvent)
+
+        return True
+
+
+    def GetServiceName(self):
+        """ String used to save out Service View configuration information """
+        return self._serviceName
+
+
+    def GetMenuString(self):
+        """ Need to override this method to provide menu item for showing Service View """
+        return _(self.GetServiceName())
+
+
+    def GetMenuDescr(self):
+        """ Need to override this method to provide menu item for showing Service View """
+        return _("Show or hides the %s window") % self.GetMenuString()
+
+
+    #----------------------------------------------------------------------------
+    # Event Processing Methods
+    #----------------------------------------------------------------------------
+
+    def ProcessEvent(self, event):
+        id = event.GetId()
+        if id == self.SHOW_WINDOW:
+            self.ToggleWindow(event)
+            return True
+        else:
+            return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        id = event.GetId()
+        if id == self.SHOW_WINDOW:
+            event.Check(self._view != None and self._view.IsShown())
+            event.Enable(True)
+            return True
+        else:
+            return False
+
+
+    #----------------------------------------------------------------------------
+    # View Methods
+    #----------------------------------------------------------------------------
+
+    def _CreateView(self):
+        """ This method needs to be overridden with corresponding ServiceView """
+        return ServiceView(self)
+
+
+    def GetView(self):
+        # Window Menu Service Method
+        return self._view
+
+
+    def SetView(self, view):
+        self._view = view
+
+
+    def ShowWindow(self, show = True):
+        if show:
+            if self._view:
+                if not self._view.IsShown():
+                    self._view.Show()
+            else:
+                view = self._CreateView()
+                view.OnCreate(None, flags = 0)
+                self.SetView(view)
+        else:
+            if self._view:
+                if self._view.IsShown():
+                    self._view.Hide()
+
+
+    def HideWindow(self):
+        self.ShowWindow(False)
+
+
+    def ToggleWindow(self, event):
+        show = event.IsChecked()
+        wx.ConfigBase_Get().WriteInt(self.GetServiceName()+"Shown", show)
+        self.ShowWindow(show)
+
+
+    def OnCloseFrame(self, event):
+        if not self._view:
+            return True
+
+        if wx.GetApp().IsMDI():
+            self._view.OnCloseWindow(event)
+        # This is called when any SDI frame is closed, so need to check if message window is closing or some other window
+        elif self._view == event.GetEventObject().GetView():
+            self.SetView(None)
+        
+        return True
+        
diff --git a/wxPython/samples/ide/activegrid/tool/TabbedView.py b/wxPython/samples/ide/activegrid/tool/TabbedView.py
new file mode 100644 (file)
index 0000000..6ac51bf
--- /dev/null
@@ -0,0 +1,48 @@
+#----------------------------------------------------------------------------
+# Name:         TabbedView.py
+# Purpose:
+#
+# Author:       Peter Yared
+#
+# Created:      8/17/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import wx
+import wx.lib.docview
+
+class TabbedView(dict, wx.lib.docview.View):
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+    def __init__(self):
+        wx.lib.docview.View.__init__(self)
+        self._views = {}
+        self._currentView = None
+
+
+    def OnCreate(self, doc, flags):
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
+        sizer = wx.BoxSizer()
+        self._notebook = wx.Notebook(frame, -1, style = wx.NB_BOTTOM)
+        self.Activate()
+        return True
+
+
+    def AddView(self, viewName, view):
+        self._notebook.AddPage(wx.Panel(self._notebook, -1), viewName)
+        self._currentView = view
+        self._views[viewName] = view
+
+
+    def __getattr__(self, attrname):
+        return getattr(self._currentView, attrname)
+
+
+    def SetView(self, viewName):
+        self._currentview = self._views[viewName]
+
diff --git a/wxPython/samples/ide/activegrid/tool/UICommon.py b/wxPython/samples/ide/activegrid/tool/UICommon.py
new file mode 100644 (file)
index 0000000..0f83b65
--- /dev/null
@@ -0,0 +1,115 @@
+#----------------------------------------------------------------------------
+# Name:         UICommon.py
+# Purpose:      Shared UI stuff
+#
+# Author:       Matt Fryer
+#
+# Created:      3/10/05
+# CVS-ID:       $Id$
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import os
+import os.path
+import wx
+import ProjectEditor
+_ = wx.GetTranslation
+
+def CreateDirectoryControl( parent, fileLabel, dirLabel, fileExtension, startingName="", startingDirectory=""):
+
+    nameControl = wx.TextCtrl(parent, -1, startingName, size=(-1,-1))
+    nameLabelText = wx.StaticText(parent, -1, fileLabel)
+    dirLabelText = wx.StaticText(parent, -1, dirLabel)
+    dirControl = wx.TextCtrl(parent, -1, startingDirectory, size=(-1,-1))
+    dirControl.SetToolTipString(startingDirectory)
+    button = wx.Button(parent, -1, _("Browse..."), size=(60,-1))
+    
+    def OnFindDirClick(event): 
+        name = ""  
+        nameCtrlValue = nameControl.GetValue()
+        if nameCtrlValue:
+            root, ext = os.path.splitext( nameCtrlValue )
+            if ext == '.' + fileExtension:
+                name = nameCtrlValue
+            else:
+                name = _("%s.%s") % (nameCtrlValue, fileExtension)
+        path = wx.FileSelector(_("Choose a filename and directory"),
+                       "",
+                       "%s" % name,
+                       wildcard=_("*.%s") % fileExtension ,
+                       flags=wx.SAVE,
+                       parent=parent)
+
+        if path:
+            dir, filename = os.path.split(path)
+            dirControl.SetValue(dir)
+            dirControl.SetToolTipString(dir)
+            nameControl.SetValue(filename)
+
+    parent.Bind(wx.EVT_BUTTON, OnFindDirClick, button)
+    
+    def Validate(allowOverwriteOnPrompt=False):
+        if nameControl.GetValue() == "":
+            wx.MessageBox(_("Please provide a filename."), _("Provide a Filename"))            
+            return False
+        if nameControl.GetValue().find(' ') != -1:
+            wx.MessageBox(_("Please provide a filename that does not contains spaces."), _("Spaces in Filename"))            
+            return False
+        filePath = os.path.join(dirControl.GetValue(), MakeNameEndInExtension(nameControl.GetValue(), "." + fileExtension))
+        if os.path.exists(filePath):
+            if allowOverwriteOnPrompt:
+                res = wx.MessageBox(_("That file already exists. Would you like to overwrite it."), "File Exists", style=wx.YES_NO|wx.NO_DEFAULT)
+                return (res == wx.YES)  
+            else:                
+                wx.MessageBox(_("That file already exists. Please choose a different name."), "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|wx.TOP|wx.RIGHT, HALF_SPACE)
+    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|wx.TOP|wx.RIGHT, border=HALF_SPACE)
+    flexGridSizer.Add(dirControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border=HALF_SPACE)
+    flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0)
+    return nameControl, dirControl, flexGridSizer, Validate
+
+def AddFilesToCurrentProject(paths, save=False):
+    projectService = wx.GetApp().GetService(ProjectEditor.ProjectService)
+    if projectService:
+        projectDocument = projectService.GetCurrentProject()
+        if projectDocument:
+            files = projectDocument.GetFiles()
+            for path in paths:
+                if path in files:
+                    paths.remove(path)
+            if paths:
+                projectDocument.GetCommandProcessor().Submit(ProjectEditor.ProjectAddFilesCommand(projectDocument, paths))
+                if save:
+                    projectDocument.OnSaveDocument(projectDocument.GetFilename())
+
+def MakeNameEndInExtension(name, extension):
+    if not name:
+        return name
+    root, ext = os.path.splitext(name)
+    if ext == extension:
+        return name
+    else:
+        return name + extension
+# Lame
+def PluralName(name):
+    if not name:
+        return name
+    if name.endswith('us'):
+        return name[0:-2] + 'ii'
+    elif name.endswith('s'):
+        return name
+    elif name.endswith('y'):
+        return name[0:-1] + 'ies'
+    else:
+        return name + 's'
+           
diff --git a/wxPython/samples/ide/activegrid/tool/Wizard.py b/wxPython/samples/ide/activegrid/tool/Wizard.py
new file mode 100644 (file)
index 0000000..98d84ee
--- /dev/null
@@ -0,0 +1,967 @@
+#----------------------------------------------------------------------------
+# Name:         Wizard.py
+# Purpose:      
+#
+# Author:       Peter Yared
+#
+# Created:      10/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+import wx
+import wx.xrc as xrc
+import wx.wizard
+
+
+#----------------------------------------------------------------------------
+# Classes
+#----------------------------------------------------------------------------
+
+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)
+
+    def GetDocument(self):
+        if self.GetParent() and hasattr(self.GetParent(), 'GetDocument'):
+            return self.GetParent().GetDocument()
+        else:
+            return None
+
+    def SetPrevNext(self, prev, next):
+        prev.SetNext(next)
+        next.SetPrev(prev)
+
+
+class TitledWizardPage(wx.wizard.PyWizardPage):
+
+
+    def __init__(self, parent, title):
+        self._prev = None
+        self._prevFunc = None
+        self._next = None
+        self._nextFunc = None
+        wx.wizard.PyWizardPage.__init__(self, parent)
+        self.SetSizer(wx.BoxSizer(wx.VERTICAL))
+        self.MakePageTitle(title)
+
+
+    def MakePageTitle(self, title):
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        title = wx.StaticText(self, -1, title)
+        title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
+        sizer.Add(title, 0, wx.ALIGN_LEFT | wx.ALL, 5)
+        sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 5)
+        self.GetSizer().Add(sizer)
+
+
+    def GetPrev(self):
+        if self._prevFunc:
+            self._prev = self._prevFunc()
+        return self._prev
+        
+
+    def SetPrev(self, prev):
+        self._prev = prev
+        self._prevFunc = None
+
+
+    def GetPrevFunc(self):
+        return self._prevFunc
+
+
+    def SetPrevFunc(self, prevFunc):
+        self._prevFunc = prevFunc
+        self._prev = None
+        
+
+    def GetNext(self):
+        if self._nextFunc:
+            self._next = self._nextFunc()
+        return self._next
+
+
+    def SetNext(self, next):
+        self._next = next
+        self._nextFunc = None
+        
+
+    def GetNextFunc(self):
+        return self._nextFunc
+
+
+    def SetNextFunc(self, nextFunc):
+        self._nextFunc = nextFunc
+        self._next = None
+        
+
+    def SetPrevNext(self, prev, next):
+        self._prev = prev
+        self._next = next
+        self._nextFunc = None
+        self._prevFunc = None
+
+
+
+#----------------------------------------------------------------------------
+# Menu Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+import cStringIO
+
+
+def getWizardData():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00}\x00\x00\x00\xfa\x08\x06\
+\x00\x00\x00\x8c5HE\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00 \
+\x00IDATx\x9c\xec\x9dy\x9cdUy\xf7\xbf\xe7\xdc\xbd\x96\xdegzVfe`\x86u\xd8WY\
+\x14\x90M\x10\x11D\x11W4\x89Fc\xe4M\xd4\xa8\xd1\x88\x91D\r\x1a\xb7\x98\x98\
+\xa8o\xd4\x08\xae\x08\x18EDE@\x04E\xf6}\xf6\xadgz\xaf\xae\xedn\xe7\xbc\x7f\
+\x9c[\xd5\xd53=\xc3$\x8c\xd0\xf3v\xff>\x9f\xea\xea\xbau\xebn\xbf\xf3<\xe79\
+\xcfr\x8e\xf8\xe4\'?\xa9\x99\xc1\xb4\xc15\xd7\\\x83\r\xf0\xc0\x03\x0f\xbc\
+\xd8\xd72\x83\x17\x00\xdf\xbe\xe1Fq\xcd5\xd7h\xbb\xb1\xe1[\xdf\xfa\xd6\x8by=\
+3x\x01\xf0\xed\x1bn\x04@\xbe\xc8\xd71\x83\x17\x013\xa4OC\xcc\x90>\r1C\xfa4\
+\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\
+\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\
+\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\
+\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\
+\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\
+\x103\xa4OC\xcc\x90>\ra?\xf7.S\x05j\xa7\xcf\xbb\xb6\xd7\xddM})P-\xbf\x97\xcd\
+w\xdd\xfc~\xb2\xe3\xb7\x9eG\x81V\xd9\x19D\xf6#9\xf1\x1aZO.&\xb9\x18\xd1\xf2\
+\xeb\xc9.\xb4\xe5\xd0\x8d\xf7\x89\x87T\x13\xf7\xc9\xce\xbd\xf3i\x9b\xf7\xa1\
+\x15\x88\xc9\xe9\xddOHW@\xda\xf2\xb9q\xe7r\xc2MON\x9bB\xa2\x10\xc4\xd9\x1e6`\
+\xa1\xb3o\xccQT\xd60tF\xae\xccN!\x18\'\xbd\xde\xf2\x9d\x95\x1dg\x92\x07\xaf[\
+64\xfe\x97\xa0\x859\xbbl\xec\xa3v\xfaa\xa4\xc1\x12\xe0\x8c\xef\x9ff\xbbY\x80\
+\xa5Ss\xfeD\x83\x10 ]\xb4\xcc\xae:\xdb\x14\x85u\xa4N\xf0<\x07t\x0c\xa20\xe9\
+\x13\xd9OH\x87]%u\xf2oa\\ 4*k\x1ez\xc2\x1e\r\xc2\xc7\x05G\xeet\x04\x05\xda\
+\xca\xa4\xb9\xf1\xbde\x9e,\xd2H\x90\xb0\x98\x8c;!\x0cI\xe6\xe0\x89aD\x02\x08\
+\xf4\xce\x8f\xbb\xd1(\x04`7~\xd8\xd0J\x12\x8d R \xe2\x98\xbcT 5H\x1b\xa4h^\
+\xb9\x06\x94\x80z\xa8\xf1\x1c\x1fGj\xd2$\xc4\x92\xbb\xef\xb9\xf7#\xd2w&f\xd7\
+o\x1bhH\x94yo\xfc\xc6\xa5\xf1\xe0\x1bD\xb5\xeegag\x92\xdc\xd0"v\x93M-$\xc8`\
+\xc2\xefZ\x05\xba\x01A\x82 F\x93"D\x84$E\x88\x86v\xf1\xb1\xd0\x08\xb2\xc6\
+\xd4P"\x8d\x83\xc8\x04D\n*F\xa5)JZ\xd8\xb6\x87\x94\x16\x96g\x83\xca$]+\xe2zJ\
+\xa4b\xb4t\xb0}\x1b\x04\xd8\x9e \x05R\x04\xca\xf2\xd1)\x14w\xc3\xfb~D:d\xca1\
+{\x9f\xfc\x8e\x04\x99\xc0\xa0\xb0\x9a\xf4\x8cKY\xabdN8\x82nl\x91\x93\xf6\xcf\
+\x91\x98\xd8\xc1\xec|U\x13\x8f\xdch\\2kt\x12\x81\x06\x92\x96\xe3\xb6\x9c]$\
+\xa8z\x19)\x15\xb8\x16\xd2\xb2\x91\xd84\x0c\x810V\xb8\xae\xd3<\x8b\xb0M/\xa0\
+Z\xce\xaa\x80zj4\x8d+\xd9#\xb3\xfb\r\xe9:\x93@\xb1\xd3\xf6\x9d?\x1b\x8e[\x1f\
+\x87\xc1\xcej\xb8\xf5\xf7\xb2\xf5\x0b\xbd\xd3;\xe6A\xb6jbh4\xac\xf1&(\xb2\
+\x06#qv9\x911\x0fB n\xd9\xa0\x0c\xf1\xd9Ae>\xa0A\xa3i0\xc6n\xd0\x02\x12GRJ \
+\x95`\xc9\xac\x8fo9\xbe\x02*U\xd8\xb8i\x1b###tuu1\xff\x80^\xda\xbcIn\x98\xfd\
+\x86\xf4\xbd\x18Y6\xc8n<\xd0\xe6\x937\xca\xbbU\x9dC\x0bY\x93\x11\xde\xd8\xa1\
+y\\\x1ar\xd7\xfc\x9d\xd5\xd8eg=\xbf\x13\xe1\xbaq,\xb1\xd3=\x08\x89F\xa2\x04\
+\xe6\x1d\x9b\x88\xc4\xd8iXXB \xb2k\x8e\x05Tm\xd3d$F_lZ\x17\xf3\xe4\xc3\x0f\
+\xf0\xd8\xc3\x0f\xd0\xb7e\x1d\xe5\x91!.\xbe\xe8B.\xbe\xf0\x02\xda\x0b\xd6n\
+\xb5\x12\xec7\xa4\xc3\xee\x89o\xe9\xe7w?f\x9b\xf0\xef8\xd9\x8d\xe1Mv\xfcV\
+\xb5\xa1\xc9\xfax@Il\xdd\xf2\xb0Z\x89nj\x95\x16\xa3O\xec\xfa\xafi<\x99\xf4\
+\xe24\xc7\x12\x8d\xf7\x10\x00;k\x04\x06\xe5\x10\xb6\xf7\xc1\xd6Q\xb8\xeb\xe9\
+ml\x19\x1ef\xe33O\xb2y\xcdcD#}\xcciw9b\xf9B\x0e?\xe4P\xae~\xc3\x15\xb4\x07\
+\x0e\xb6HI\xaa\x15r\xbe\x0f\xb2U\x1f\x8cc?"}o\xd1h\x04\xc20\xab\'6\x16\xa3\
+\x8a\x13 \x1d\'\xb59\xe6\xb6h\x92\'\x94y\xa9l\x18\xa7\xedI\x1aUf\x9d\x8bL\
+\xb34U\x819F\x9a\xd9\x01\xa6\x8b\xb5@\x0b\xb4\xb0\x891dG\x18\xb2\x13\xa0?\
+\x86M[S\x1e{\xeaY\x9exr-\x9b7\xf532P\xa1R\x8e\x18K,\xca\xb9"\xb1\x10\x88\xa8\
+\x84\xabl\x16.Z\xc6\xe9G\xaf\xe2\xd5\xe7\x9e\xc2\x91K\x0bx\x80\xd7\xb8\x7fK\
+\x82\xae\x01\xfb\xf9\x90m\x97\xbe{\xb7;\xecl\xc7\x9bm\xf5(\xc2w]\xc2p\x0c\
+\xdf\x95f\x1c\xab\x12P\nl\x1f\xa4\x83q\x81\x88\x89G\x91\nR\x05:1d\x8a\x86\
+\xd2m4\x18\xd5\xb4\xc6+\xd5\x1a^\xae\x8d\xc1J\x85B>O\x04\xd44\x14\x04\xe4\
+\x91\xa4\x11\x8cE\x1a\xbf(\xd8R\x82\xdf<\xd4\xc7\xedw\xdf\xc7#k\xd61X\xa9\
+\x93\x08\x9b0\x11D1\xd8V\x8e\xc0mC\xdb6\xd54\xc2\xb2]\x92h\x0cG\xa7\xac\\\
+\xb1\x8c\xd7]x&\x17\x9f\xd2C\x0e\xa8\xd7\xa0\x18`\xeeIG\xec\xde\xe44\xd8oH\
+\xdf\x1b(\x05J)\xb46"i\xdb6B\x18\xbb\xd9q]@\x19)\x17"s^\xa4\x18\tU\x90jb)QBd\
+t\n\xa4\x16\xc6\x95c\x8f\xabI\x9di\x84\xc68?\xd3\x19D@\x92\xf3\xa8\x01:\x9fg\
+\x04\xd8Q\x87\x8d\x9b\xca<\xf3\xd0\xef\x91\x03\x9bx\xd7\x9f\\I\xe0\x19\xe9\
+\x969\xf8\xc2W\xbf\xc3\xc6\xa1*\xa9\x97g8tHq\xb0\xfd\x80\xa0\xadH\x12\x0bv\
+\x94C\x84V\x14\x02\x97\xda\xe0f\x8a~\xc2KO9\x8e\xb7^y\x06\x07\xf7\x18\x034\
+\xaaAOn\x12\xaf\xe3\x1e\xa4d\xff!}\xa2\xcf\xb4e\xf3\xb8d\x0bI\xd3)\xd1\xb4\
+\xdf5\xa4\x99W,\xd2\x8a\x9c\xe7\xd3\xe8A\x8d\xd4\x02\xc2&\xc4%\x15\xb2\xd9\
+\xc7\x02XB\x90f\x8f(!#\x96\xf1.=e\\M?\xb5\x06\xd6m\x1e\xe0\xb1\'\xd7\xb1nc\
+\x1fO?\xb3\x1e\xe9\xfa(m1\xb8\xeea\xbe\xf0\x81\xd7S\xaaA)\x821\x05\xff\xf9\
+\x83\x87\xd98\x10\xe2v\x1f\xc0h\xa4\xc9\xe5<\xeaI\x8a\xd6\x82\x18\x1bK\x82\
+\xe3iD*\xf0\xa8q\xf8\x01\x05.;\xffd^~\xd6\xa1t\xb9\x868W\x80\x93\x83\xb8^\
+\xc7\xf62\x0f\xa1t\x19\xb7\\&\xc7\xfeCz\x03\x13\xfc\xcf\x06\n\xa8\xd5C\x10\
+\x16\x96ec\xb7v\xbfb\xdc\xa5\x19\'\x16\x81\x03B%\xe3\xbf\xb4l\xd2\xd4#\xb1lB\
+\xa0\x8e!\xb2q\x1a/{/\x01\xdb\x87a\xe3\x96:k7la\xdd\xc6\xed\xac\xd9\xb4\x95\
+\r\x9b\xfb\x19\x18.#\x1c\x9f(\x11X\xd2\xa3\xad\xb3\x8b\xb1\xb4\x83\x9c\xc8\
+\x11F1G\x1c\x7f&\xe7_p4\x0eP\x08`\xcdZ\xf8\xfe-?\x03\xa7H-\xb6(\x8d\x96!/\
+\xc0\xb2 \rI*#t\xe4\x02\x0e9\xf0\x00\x0e9\xe8 \x96\xcf\xf29\xe7\x88v\x96\xf4\
+@\xde5\xd7\x13\xd7S\xb4N\xb0\x02\xc78o\xa0\xe91L\x91\xa4\x18w\xd4d\xd8\xffH\
+\x87I\x89\xf7}\xaf\xe9\xa1\x0fS\x9a\x8d\xbd\xe1\xf4J\x00\x1c#E\x8e\xc8\x91VK\
+\xd8\xb9<H\x8fjj\xc8.\x01C\x11\x0c\x96\xa0\xaf\x0f\xd6\xaeY\xcb\xfa\'\x1fc\
+\xf3\xb6a\x9e\xd9P\xa7\x96\xfa\xc4\x1a\xb4\xe5 \xbd\x1c\xca\x0e\x88\xd4\x01D\
+~\x82\x9f\x0b\x08+\xa3H\xa1H\xeb)a\x1a\x11\x96C|Wr\xc5U\xafaL\x81-\xe1\xd9M\
+\xf0\xb1O\xfc3\xb1\x16T\xeb\x11\xa9\x8e\xe8\x9e\xddK=\x0e\xe9l\x0f8\xfa\x88\
+\x838\xe4\xc0\x85Twl\xa6o\xe3\xb3\xac\xe8\xacp\xd9\xcbz\x99\xef\x80\xa74Q\
+\xb5\x8e\x1bx\xb8\x9e&\xac\xd4Ij1\xb6\xefg\xcfc\x9c\xf0=\xad\xbb\xb6\xff\x90\
+>\x89zou\x89\xb6\xc6\xc0\xa45\xd1KU\x8f\xc1\xf1!V0:\xa2\x98\xdfe\x11\xe9\x1c\
+\xb6\xf6\x88\x04\x8c\xc5\xf0\xf1\xcf\xdc\xcc\x9a\xfe*Ol\x1ab\xdbp\x19\xa5\
+\x149\xd7\xa1\xe0YH\xe9\x93x\xb3I\x94G\xac\x04\x89\xb0H\xa5\x8d\xd2\xb69\xab\
+L\xa8%\x80\xeda\xb9\x16\xae\x15\x91ss\xc8\xa4\xca\xca\x15\x8b9s\xb5i|)p\xeb\
+\xed?\xe3\x91\'\x9e$\xd7s A\x10\xd0\xb3`\x01\x0b\x96-\xe3\xa4\x13\x172\xb0\
+\xbd\xca\x03\xf7\xdc\xc67o\xfb:g\x1e\xb7\x8a\xbf~\xeb\xe5,\xea4\x12\xab\xeb\
+\x11x\x127pH\xc3\x1a\x96e\xe1\xe5\xf3\x00\xd4jU\x82\\\xa1\xd9\xd5\xed\xd9a\
+\xbd?\x91\xfe\x1c\x08\x13\x8c\x07U\x8eG6c\x05\xa52\x8cU\xe1w\x8f\xad\'\xac\
+\xd5\xa9\xecX\xc7;\xdfr.\xb6\xef\x11i\xa3\xca\xcb\x11\xfc\xe0\'wSq\xbbH\xfcY\
+\x04\xdds\xf0\xfd\x00tB\xbdZ"\xac\x8d!\xdda\x12K\x92Z\x0e\xb1\xb4A8\x803\xee\
+t\x11\n\xe9(\xe2\xd2\x10\x92\x10\x9dT\xe8is\xb9\xf4\xa5\'\xe3c\xba\x89\xbb\
+\x1ex\x8a[~|\x0b\xc7\x1cw4+\x8fz)\x87\x1e\xb3\x98\xa7\xd6\xc1\x1f\x1e|\x8c\
+\xcf}\xea\xdf\x98\xdd&9\xf3\x84\x83\xf9\xf8\x9f}\x88\xc3\x16\xd8\x88z\r\xbfV\
+%\x08|\x94\xe3\x92*\x0b\xcb\x12X\xbe\xa1-M5B\x08\x82\\\x9b\xb9\x04\xc8\\\xcf\
+0\x1eQ\xdc\x15\x93lm\x98*\xd9a\xf4\xf3h\x17B\x8d\x8f\x93[%U\xb4^\xd8\xc4KiHh\
+CE5=ir|\xef\xc66\xd5\xb2\xef\xb3[C\xfa\x06\x86Y\xb3~\x0b\xeb\xd6of\xeb\xb6\
+\x1d\xf4\x0f\x8eR*U(\xd7b\x84\x9d#\x8d#t}\x84K_{.s\x03\x9a\x1e/\xed\x80\xdf9\
+\x87\x9ah\'T\x1e\xd5jL\xa9j\xa2Z\x16\x02\xcb\xf1\x11B\xa1\x85BK\xc5\xb8\x0f=\
+\xc9\x1c.\x1a\x92:mmyjcezr\x16A\xa4X>\xbb\xc0\xa5\xa7xh\x8c\xb17&:\xf9\xd8\
+\xa7?\xcb\x83\x0fo\xe4\xde\xdf=\xc1\xf7n\xfa\x11\xe5\xb1*s\xe6t\xf2\xfa\x0bN\
+\xe6e\xa7\x1c\xcaQK \x07\xb8h\x02_e!\xb42\xd2\x9fE\x9ay\xf0\x1b\xae i\x8dG\
+\xda&\xf4vZ\xed\xe4j\x9c\x88I\x18\xadS+\xf5\x13\xb4u\x9a\xa7\x11+\x90.qX\xc3\
+q\x1c\xb0\xed\x89$\xa6\xb1\x19+9\xce\xc4\xc34\x9d\x16\r\x96$$\nt\x089\x87zX\
+\xc6\xf7\xbd\xe6-\x84\xf5\x08\xcf\xcb14\x92\x90\xef4FU5\xfb6\x02b\r\xfd\x03\
+\xb0q\x8bb\xd3\xe6\xadl\xee\xdb\xce\x86-[\xd8\xb8e\x07\xdb\x07\x86I-3\xe4I\
+\x85\x8b\xd6\x0e\xa9\xf0Q\x14\x8cq\x17\x80\xadS|\x1f\xea\xf6\x08\x1b\xab0+\
+\x00\'5\xb6S\xa5\nc*\xa2\xee\x08\xa4c\x91\x92\xdd\xb7\xd2\xa4\x96\xc0q\x8b\
+\xe4,\x9fr\xb5N\x12\xa6\xe0f\x11\x0f\x9d\x00\t\xc2\xd1\x14r\x16#\x1b\x1ea\
+\xf9\xfcY8\xb5a\xda\xa9\xf1\xc1\xb7\xfc\x19\x1d\t\x945|\xe5\xc7\xdb\xb9\xfd\
+\xfe\x87x\xfa\xf1\xc7\xb1\x89\t\x1c\xc5\xbcN\x8fs^y\x06\xaf:\xefx\xe6w\x185\
+\xeeb\x0emi\r\xda5B\xe7\x18\x15&E\xab\xaf\x7fw\xd8\xf3pmr\xd2\xb5"h+\x00)i=F\
+Z\xdd\xa0\xc0\t\x82\xa6\xcb1\x8d\x12\x94RH)\x91\xb6\x8d\xd8\x99p\x1aWe\xa1\
+\xb5y6q\x15\xbc\xbc$J\x03,\x01\xa9\xe5\x91 \xb3\xc0\x81\x8d\xed;\x94cp:m\x1e\
+\xdb\n\xf7=\xbe\x9d5[w\xb0cd\x98\x07\x1f\x7f\x92\xa1\xe1\x1aa\xa2\x89\x12\r\
+\xc2A:>J:\x84\xa9K\x98\xce\xc6q\xf3(lt6\x866\xc1\x8c,\x14\xa2S<\x15\x92\x88\
+\x94\x9a\xcc1\x10\x92Y\xb7\x11\x12\xd7\xd8\x00\x96$L5:\x8a\xc0\xb5\x91\xb96\
+\xa4\x94\x88\xa4J\x12%\x0c\x8dV\x10\x96\x8b\x1bxX\x96&Ik\xc4\xe1($\x15$!\xb6\
+/9\xe2\xc0\xf9\x1c\xb2t!\xe7\x9dv*\xa7\xaf\x0ep1\xae\xd4k\xaf\xbf\x95\xef\
+\xdc\xfd4\xe4;\x11B\xe2\xdb\x92\xd3O<\x82\xab.=\x9bU\x0bhz\xd3LzG2\xae\xe2\
+\xb4e\x04O\xb4<\xd2=\x12\xdeB\xfc\xff\x88t,\xd2Ha9\x0e2(\xa2\xb4D\x0b(\x95\
+\xc6\xb0\x1d\x85\xeb{\xd8\x9e\x8bF6;\x82\x18\xe3\xc0L1\t J\x9b8\xbf\xc4\xb8"\
+\x95\x03\xa2\xc3\xec[Q\xc6C\xd8\x19\x04\xa4\x80EL\x14\xc3\xc3On\xe0o\xae\xfd\
+<\xc3\xaa\x8b2\x05\x06*\x11N\x90\xa3\xad\xdd\xa7\x16\xa6DV\x8e\xc4\x92h\xc7A\
+K\x0f!=\xb4\xf4A\xd9\xd8\xbe \x8e3/\x94H\x91$\x08\x1d\x9b+\x14)B$h\x99\x12\
+\x93\xa0\xec\x1aC\x95\x86\xf2\t1\x8a\x14t\x9cP\xc8\xe7\xf1\xbdv\xa2\xd4\xa6T\
+IH\xc2\x9a\xb1\x87\x1d\t\xf9<\xaeoc\'U\xc2\xd1\x1dX\xd1(+f\x179\xee\xf0\xc38\
+x\xd9B\x0e_u K\x96\xc0C\x8f\x86<\xfa\xd43\x14f\x1f\xca\xfc\xf9\xf0\x85o>\xc1\
+-\xf7>\x86\xa5\x13\xba\x04\x9c\xf9\xd2\x13y\xcd\xabN`Yo\x96$\xa3L(t\xe7\xc8\
+\x19\x8d\x86\xfb\xdc\x0c\xff\x8f\xb1+\xe9J#\xdd\x80\x08\x9b\x14\x17%`\xf3vX\
+\xd0[\x04\x8c[\xa3\xc2\xf8P\xa8\xd1\xc35-h\x01\x89\x80\xb2\x82\xad\xdb\x15\
+\x1b\xd6od\xeb\x8e\n\x8f=1\xc4\xb6\xbe\x01|_Q\xf4k|\xfa#WR\x04\x02\x1c\\\x07\
+\x0e9l\x11\x1d\x0b\x0e\xe2\xee\xfb6RX\xb8\x84\xb0\xdd&\x12\x82\xca\xe8\x08\
+\x96\xe5!\xdc\x1cJ\xbahe\x11k\tI\x16\x96\xd4\x99w\xdbvi\xf8\xc2\x15`i\x85 \
+\xc5R\x86x-\x14\x89V`[\x0c\x8fVQ\xe4\x8c\xe7N@\x1c\xa6\xf8\xaeK-\xac18ZC%\
+\x0e\x8e_ \x97\x0f\xf0dB\xaa\xea\xa4n\x85\xf2\xd80\x9ecq\xd6)\xab\xb9\xf8\
+\x9ccY\xb5\x0c\xfa7\xc1\xe3\x0f\xaf\xe5_\xbf\xf4\x9fl\xdc\xbc\x89TH\xbe\xf4\
+\xd5\xf7\x91\xcb\xc3\'\xbft\x1f7\xdd\xfa\xdf\xf4\x14s\\\xf6\xb23\xb8\xfc\xdc\
+\x13Y2\xcf<\xbb\xb0\x06\xb9\xc0\x0c\xe3\x06\xb6\x0f1\xaf\xb7\xab%\xa6\xcfN\
+\xf1\xf6?6\xe9\x96c\xe2\xb3J"\xa5!\xb9\xad\x17\x06ScLI1\xee\x8d\x1a\xac\xc0\
+\xb6~\x18\x1c\x89x\xec\xc9g\xd9\xd6?\xc8\x9aM[\xd8\xda\xb7\x83R\xa5F\xaa\x05\
+\x08\x0b%|:\xe7\xae`\xc3\xfa!\xda\xf2\x1a/\x1d\xe0\xee\x87\x868\xfb\x88.\x12\
+m2\x85F+p\xe69\x17\xf2\xcbM?\xa6\xe6t\x91\xc6\x02\xb7\xad@4\x9c\x82NI*\x0eX\
+\xbe\xb1)l\x99\x89\x87\x00\xdb1\x07\xa8\xd6\xccU\xa5\xc6x\xd4\xa9m\xd4\xbbv\
+\x10J\x81%\xd0i\x88\xe3\nF\x86B$9,\xed\x9b\xe0G=\xa4^\x19\xc5o\xeb\xa43\x9f#\
+\x8e4\xb5x\x8c4\x1eF\x11\xe3\x881\xce;i5\xa7\x9dq\x11\xc7\x1d\xe2R\x03~~\'|\
+\xe8\x1f\xefe\xed\xba\xadDaB\x14\xa6X\xf4\xf0\xe9\x7f|\x1b\xddy\xf8\xfa\x7f\
+\xfd\x81_|\xf7\x8b\xbc\xf3u\x97p\xc5+_\xc1\xb2Nsyv\n\x8e\x05E\x17\xa2\xb1\
+\x18\xc7s\x987\xab\xab%N\xd4 }\\\x90\x1aMa_q\xbf\x0b\xe9\x89\x82XZHiS\x03\
+\x9e\xd8\x92\xc5o7E\x0c\x0e\x0c\xb1i\xcbV6n\xddF\xdf\xc00\xa3\xa5\n\x95zB=Q\
+\xe0\xfa$Z\x92`\xa1\xadnd\xe0\xa1\xb4 JS\xa2\x04J\x1b\x07 \xd7A)\xad\x90\x17\
+yn\xf8\xe1m\xbc\xec\x88\xd7\xe0d\xb1\xeaYy8\xf5\x84^\xe6\xfc\xb0\x9b5[F\xc0i\
+#\xaaE\x90@\xd0\xdeI\xb9&\xd1"\x1b\x17\xa7\n\x92\xba1 E\x96\x15\xe8f\xca13\
+\x1e\x95e\x11)3`\xb7\xb4\xc2C b\x85\xeb\xd9\x8c\x0c\x8c!\xe8D\xe2\x98\x04\n\
+\xad\t<\x97rRg\xa4RFk\xcd\xbc\xf9\x0b9\xe5\xe4\xe39\xeb\xb4y\xacZ`\x0e\xfd\
+\xdb\xdf\x0f\xf3\xc1\xeb~\xc6\x83\x8f\xaeed\xccBY9\x90\x05\xa4\xb0(\x14\xbb\
+\xb9\xfa\xaa\x0b9j\x05|\xee\xf3\xbfbx\xeb\xa3\xfc\xfc\x86\x7f\xa1\xb7\xd3\
+\xa1\xcdR\x08\xa5Qq\x8c%$\x8e4n57\xe7Ld\xb6\xa5\xd3\x9e,\xffn_a\x17\xd2\x85t\
+PH"`\xfd(\xfc\xf5G>\xc7\xe6\xed5\xea\xa9\x0b\xc2EH\x1b--\x94\xb4\xd0\xf8\xc4\
+Z\x92h\x89\'\x0b\xc4\x1ab%P\t\x90\xd8Y\x9fd\x81\xa3\xc0\xad\xd3\xde\x99\xa7V\
+\x95\xe4l\x87\x87\x1e\x7f\x96\xdb\xefz\x86\xd7\x9cr \xb6N\xb0\x84\xcd\x1c\
+\x17\xce>\xfe`\xbeq\xdb\x1fH\xf3\x01\xd50\x81(\xa4\x16V\xd1\xb6\x9b\r\x01\
+\x95I:\xd1\n\x07\x10Z R\x8d\xaa\'&\x94)e\xa6\x92\xa4\xe94\x85 M4Bid\x92`\xa5\
+\x16\xa5\xc1a,\x0e\xc0B\xa0\x05x\x8e\x8b\xef\xfb,]y8\'\x9dx<\xc7\x9f`3;\x075\
+\xe0\xae_\x0e\xf1o_\xb9\x83\xc7\xd7m\xa5\x16\x83\x14\x1e\xbe\xd7\x85\xdd&(\
+\x97*X\xc9 m\x81\xc79\'\x1c\xcf%g\xc2\xb3\x0f\xa7\xbc\xe9\x15\xa7r\xe0\xfc\
+\xd3(ZP/\xf7\xe1\x15r\xe6z\xbc,\xf6\x1a\xd7M\xc3u<v\xee\xc9\x1b\xe3\xaf?BW\
+\xbe{\xd25PU\t\x15i\xb3}(dc\xdf\x00V~\x01R\xe4\x89\x85\x87V\x90jE\xaaMF\xa9\
+\x906\xc2\xb2)\xc7\tH\x1b\xe1\xd8\x08a\x99\xfeR7\xee@c;\x92\xd1\xfe\x1d\xa0S\
+\xc6|\x89R\x1e\xdf\xbb\xf5W\\z\xca\x81\xd8\xb1"\t+\xcc.\xe6\xb9\xfaU+\xb8\
+\xf9\'w\xb0\xb9\xaf\x0c~7^{\'q\x9a\x18\x89N\xe3,\xb6\r\xae\xd48B#u\x8a\x16\
+\x1a\xad5If\xb1\xa7\xcd\xfc\xb2\xcc\x18"\xc1\x11\t\xe81\xac4$\xac\x0c\x98\
+\x1b\x95\t\x02\x1b\xcfs\xf8\xfa\xd7\xde\x8b\xb6\xa1\x94\xc0\x9dw\xc3On\xff\
+\x15\xeb\x9e}\n+)!\xbd\x1c\x83tBl,\xae$\xb5\x10a\x05W\x8fq@O\x91C\x16w\xf3w\
+\xefX\x89\x03,9\xdc\xc2V\x10HP\xe1\x18\x85|\xc1$5\xca,\x04\x9b&\xa6\x8f\xb4]\
+P\x19\xe1I\xc6\xc4N\x03n\x89B5\xd5\xc0\xbe\xf3\xa3\xedr$\x05\xf8\x99j\x7fv\
+\xcd::\xba{\xd8<\\\'\xc9\x07\x84I\x16\x9dr\xb2\x9f\xa5\x11\xd8\x8a \xb0\xa0\
+\x1a\x03\xa1q\x056.\xbey\x03)\xc9H\x89\xe2\xac^\xc6*\x11\xf58&\x8a\x03\x9e\
+\xdd^\xe7\xa6_<\xc1\xa5g\xac\xc4\xb3lT\x0cK\np\xf1\xa9\xab\xb8\xf1\x97O0\x10\
+\x8f\xa1\x9c.T\xea\xc0X\x84W(\xa0uLT\x1b%t \xe8\xcc\xa3I)\x8d\x8e -\x10B \
+\xb0\xf0\xb4\x83T.:\x15$\xb1\xc6J\xca\xf8n\x15\xcf\x1b!\xc8\x179\xe2\xd0\xa3\
+\xb0t\x02\xa2\x8a\xa4\x8db\x11~\xf5 |\xef\x8e\'\xf8\xc3CO\x90*A\xac%q\xadH \
+\x1d,\xbf\x13T\x07\xe4|`\x8c(\x1d\xc1\xaa\x0f\xd3\xa6\x868\xe3\x88\xc5\xbc\
+\xe7\xcd\x172\x0b\xf0P\xc6\xb0\x14\nR\x89t\x023\xce\x16\xc6\x85\xa4Q\x08+K\
+\xd4\xd0r|d\xe50\xd1y\x95\xa5~\tT\x96\xe8\xd5`f/\xd2\xc6\xfe7\xa4\x9b\x8b\
+\x968\xc0\xdc9\xb3\x19\x1d\x1a\xc4\xcb\xcd\'t\xbcq\x07\x8c\x94F\xf2\x92\x04\
+\x92\x88Z\xb9d\xbc\x1c\x00\xc4\xd9\r\x8c{\xdb\xa4V\x88\x9cO\xa5n\x86HX.:\xef\
+\xb2v\xa8\x8f\x9b\xefy\x9aSOXI\x87\x96\xe4|\xf0\x81W\x9fs"\xb7\xdf\xfb0\xa3Q\
+\x88\xf0\x02b\x95\xd0\xd6\xe5\x13U\xcb\x08\xa9i\xef\xea\xa0\x1a\xd7\x18\xd9\
+\xbe\x03l\x81\xd7\xd5\x0eI\x84\x8e\xebx2\xc5S\t\xb5\xd1\xad$\xd5\x1aK\xe7\
+\xccc\xc5\xaaE\xac8x\x01g_\xb0\x8a\xdeb\xc3\x01\x12C\\\xa3\x9c\xd8\xdc\xf3\
+\xdbM\xbc\xf7c\xdf\xc6\xed=\x84Z5!\xad\xc6\xb8\xddsi\xebj\'\x1c\xd9Ne\xb8\n\
+\xb3z!\x89!\xa9b\xa7c\x1c~\xd0\x02\xdep\xf1\xe5\\|\xf2B\xda\x01c\xde\xc5Y\
+\xb6\x8dm\x98\xd4Y\x9f-%\xa9\x90\xa4Y~,\x183\xa4\x99\xb65A@\x1a<4\xbel\x06z\
+\x9f\'\xd5\xe3\xd8\x8d\xce\xd0\xd8\x08\x96.\xe8\xc2\xd61\xa9V$ue,\xe5F\x02\
+\xa2\x94&\xf1^H\xd3GY\xc6h\x92Z \xb2\xa1\x90\xcc\xd2\x91\x94\xb0\xd1\xb6\x8b\
+\x8a1\x16x\x02\xa2\xbd\x97j%\xe1\x96\xfb\xb7r\xd2\xfdc\\\xf1\x92"hc\x9b\x1d\
+\xba\xcc\xe1\x94\xa3W\xb1\xe9\x97O\x12bAm\x8cR8\x8a\xd45\x94v\t\xcb\x1e\xc2.\
+`\xb5-@\x0bIRMpH\xb0b\r\xe10\xedm\x82W\x9eu\x14\xe7\x9fy\x12\x07/\xcf\xa1$X\
+\x1e\xac/\xc1\xadw\x8e2\xf0\xec\xef\xf8\xe8kO\xa6\xcd+\x1089\xfa\x06\x86\x99\
+;o!}\x89&\xd7\xd6\x8e\xd5\xe51Z\xa922R!\xb0S:\x9c\x94t\xe01T\\a\xc5\xf2\xf9\
+\x9cz\xc2I\x1c\xb1j\x01G\xae* \x80\x11@\x11\xe0b\x9b<V\x89q\xac\xb4D\x83\xb4\
+\x84$\xa3\xbc\xc1\xb1\x16\xe3!}\xa9w\xd3\x8fk\xd8%\xa9\xf2yb\x17\xd2m\x01u\
+\x15cI\x97\x9ev8r\xd5r~\xf5\xc8v(ta\xfcF\x1a\xa2\xd0\\\x8c\xef\x984#\xdb\xc9\
+2\x15\x1aw\xa8\xb2\xb4b\x95\xa5\xa9IT"\x8d\xe1\x92\nH4\x1a\x17\xd99\x8f\xd1\
+\x81\x88/\x7f\xf7\x0eN;\xf5"z\x04\xe8\n\xe4\np\xf9Eg\xf2\xe3{\x1eckm\x14\x02\
+\x17\x94C\xb1\x98\xc3q\\\xc2ZD\x1a\xd5\x90i\x88\x8e#\xa2\xea\x18+\x97/\xe1\
+\xd4\x13^\xce\xa9\'\xceb\xf1\x02\xe8q\x8dl\xf4\r\xc2\xe3O\xf7s\xdbo\x9e\xe6\
+\xb7\x0f?\xcd\xe0\xd8(\x87-\xc8\xe1\xbe\xf9\xa5$qH\x05X\xbba+\xa5j\x8d\xb0\
+\xae\t\xed\xd0\xe4\x98\xc5\x11N\xc1\xa1-\'H\xc7\x06\x99\x9b\x83\x8b\xcf9\x93\
+b\xde\xa6P\x80\x97\x1c^\xa0-\xcbP\xcd5\xf9\xb1H\x11H\xa4I\x89nI\x9f\xddY9\
+\xb7\xfa\xd0M\x8atK\x97\xde\xd4\xe8YU\xcd$\xa1\xe4}J\xbaQ91\x0e.\xbe\x86W\
+\x9c\xf5\x12\xee{\xe4[&\x7f\xcb\xf6\x11Bd\xbe\xe9\xd8T\x00H0J\xd9\x90\xac\
+\xb4)\xc8i\xcd\xd2j*z\xe9\x98\x84.\xdfC\x8f\x8d\x80\x90\x04\xb3\x96\xf0\xf0\
+\x13\x8f\xf0\xef\xdf\xdf\xc2\xbb_5\x9f\xae\x82!\xeb\xc8\xc5p\xf4\x8a\xd9l}`\
+\x0b\xd6\xec\x83Pr>\xa3\x83\xfdP\xed\x03;bv.a\xc5\xbc\x0e\xce;\xedX\xce:\xed\
+\x10fw\x8d\xfb\x10"\xe0W\x8f\xc1\x8d?\xba\x87\xdf?\xfa,\x89r\xa8\x8e\x85($Aq\
+!\xdb\x06\x87\x19\tA\x97\x14\x85Y \xed<q\xaa\xe9\x9c7\x87\x91\xe1~\x1cGr\xc0\
+\x8a\x05\xec\xd8\xb6\x96\x81\x1d\xeb9~\xd5R\xde\xf7\xe67\xf3\xab[o\xa2\xbbg\
+\x11\xaf:\xff(c*jh\x1708\xd0GoOwCqgn`#\xc9\xaa\x85\xe9\xc9\xd4\xea\x1e}\xe8\
+\xc0\x84\xc4\xce}D\xfc$\xd7\xa1\xb0\xb3\xf4@G\xc3K\x8e_\xc6\xd1+\x97\xf3\xf3\
+g\x86Q\xb1\x85t\x1c\x1c\xd71\xea*N\x8c\xcf5S\xed\x8d\x0bS;\x8f9\xa4\x04\xd73\
+\xdd\x80\xaa3\xcb\xf3\xe9\xdf\xb6\x03\x1d\xa7x\x07\x1eL\xad\xd0\xc3\xf7o\xbb\
+\x8b\xcb/\xbc\x9c\xceLBs\xc0k^~"\xf7<r#\xb5\xea\x16\xaa\xd5\x88y\xf3fs\xf4\
+\xaaS9\xe9\xa8\x15\x1c\xbb\xd2bI\x0f\x04\x8c\xf7v?\xbfw\x1b?\xfe\xe5\xfd<\
+\xb6\xae\x9fm%\x8b\xfe\xaa$\xd5>xy\x9c EJA\xe2\xbbHO#=(\xcc\n\x08\x81\x07\
+\x1e~\x8c\xd9\xbd\x8bY\xbbf\x03\x859\xddx\xae\xe0\xd9\xbb\x7f\xcd\xf2\xa3\
+\x0f\xe6m\x7f\xfeA\xe6\xb5\xd9|\xea\x13\x1f\xe4\xedW]\xc1Y\xa7\x1d\x86\x0bX\
+\xda\xa4+\xd9\x1a\xe6u\xf7d\x9eA\x00\x0bK(\x92,\x99\xa1\x91\r\xeb\xd0\xda+7\
+\x8a\x1a`\xd7D\x81F\xdc`\'Z\xfe\x98\x92\x0e\nG(\x14\ty\xcb\xc6/\xc2\xe5\xaf<\
+\x87G\xff\xed&\xb6T\x07Qa\x8am\xe5\xb0\x1c\x974\x8e\xd1\x02l\' I\x9b\x01\xbf\
+\xf1\x8e\xaa\x19\xf1\xc9\x8a\xeetBQ\xc6\x04\x95>:u\x95\x9a\xb4\xa8\x8d\x0e\
+\xe2\x17\x8b\xf4\x0fo\xe4\xa6[\x9fb\xfe\xcb\x0fd\x9e\x1f\xe2j\xc9\xd9G\x1f\
+\xc0\x95\xe7\x1c\xc7X\x04\x17\x9dv$\x07-k\xa3\xb38\x9e\xabV\x05\xeez\x10~y\
+\xf7}\xfc\xea\xb7\x0f\x80\x93g\xb4\x962X\x8a\xc1q(\xf6\xf4\xa0\xb1)\x97J\xc4\
+\xb6\xe9`\xc3\xea(#NJ\x04F\xda]\xa8\xc51k7nd\xf6\xf2\xd5\xec\xd8\xb4\x16\xab\
+\x90\xe3o\xae\xfd(K\x0f\x80\x9f|\xffn~w\xe7\xcd|\xf5\x0b\x1fa\xc1,\x1f\x91\
+\x19\xdd\xae0\xde\xb5\xfah\x15\xdf\x91\xe0\xdb\x99\xa3H\x83L\xb1E:\xa1\x1a\
+\xc6\x82\xac\x05d\x01a\xd1Z\\\x99\xa59\xa5\xae1\xf02\x9d\xaf[\x98\x17z\xdf\r\
+\xde\'!]"\xb1HU\x8c+m\x12\r/?\xa5\x8b\x1f\xdd;\x0fwC?[\xfaJ\x08R\x84\xce\xa3\
+\x89A\xd9\xa4"\x1d\'\xba9\xe4hI\xfe\x97\x02\xa2\x14\xa4\x83\x1b\xe4\x18\xd9\
+\xb1\x81\xae \xcf\x9cb;\xcf\xec\xd8\x84Wpq\xd3*\xb7\xfc\xe0{\xbc\xf3\xdc\x0f\
+ \x85G\x1aV\xe9\xf4=\xdeu\xe5K(\x16\xc1\xcd\xdc\x97\x03\x11\xdc\xf5\x871~r\
+\xd7\x03\xfc\xfe\xa9\r\xec\x18\xaaPM!\x16.\xda\xb6I\xed\x0eh\xb3@\xa5\x8c\
+\x95C\x10%\x84\'\xd1q\x82\xdd\xddER\xaf\xa0\x92~$&\xdf\xac\n\x0c\xf4\xef\xa0\
+\xa7}\x05\x95\xe1m\xbc\xfd\xea7q\xe1\x05\xf0\xb3\x1f\xc3_\xbf\xfb\xb3\x9c\
+\xb4j>\xdf\xf8\x97\xeb\x98?\xdb\x1c\xd6\xce\x08\xac\xd7\x14\x05W\xe2\xb7\xe5\
+ZF+\x13+kdV\x9c\xd4,F\xcd\xde\x13=IT\x92\xf1G\xa8\xb5I\x8e\x10b\xcf,k\xad\
+\x9b\x99\xbf\x8d\xfd\x1b\x9f\x1b\xdb\xf6\x92t\x07)-\xdc\xacO\xb1\x84\x91\xaa\
+\xbf\xfd\xcbsx\xc3\xbb>\x8f\x1d\r\xe1\x06s\x88\x1b\xe3M\xaf\x88\x96\x05\xa3\
+\xe6\xa3(;\x84c:\xb3\xd48lp\x02s*\x1d3\xa8\xa0\xadk\x01\xe5\xb0Dep\x0b\xb3\
+\xad\x84\xbcR\xac>d1\xe7\x9ev,\x96\x80\x10\xc9`I0\xdb\x87\xf9E\x18\x0ba\xc3\
+\xa0\xe6\xab\xdf\xbd\x9d\x1f\xde\xb3\x96\xedQ\x11\x95\xef!\xa1\x17\x9cQ\xa4]\
+G\x08H\xb5@[\x018y\xa8W\xa1:\x08N\x9d|\xb1H\x19\x1b\xe9\x17`\xc7v\xba\xba,\
+\xbc\x10\xda\x1d\xd8\xbc5&G\xca\xeaUs\xb9\xfa\xbd\xaf`\xf3\x08\xfc\xc5{oc\
+\xeb\x9aG9xn\x8e\xeb>p)\x07\xb4\x8d\x9755\x0c//\x90M\x12\x1b\xf9i;[\xd9\xa2\
+\xf5\x01?G\xdf\xfe\xbf\xc1d\r\xa3\xf1Y)\xf5?!\xdd\xa8\xe8V\xd5\xe4\x02\xdd\
+\x12\xde\xf7\xa7o\xe5\x9a\x0f\xff\x03a\x181T\x1e\x00\x7f\x16\x08\x1bj\x11\
+\xe4\xf2\xe0{\xc84E\xa5!\x12\x8d\xe5X(\x95\x92\xd6\x86 \x8d\xb0\xf3.V\xbdDyd\
+\x1b\xbd\x05\xc9KO_\xcd\xb9\xa7\x9f\xc8\xaa\x03\x1d|\x0b\xba\x0b0:\n\xbe\x07\
+\x1d\xb3\xf3$@R\x8fy\xe4\x91\xb5\xbc\xfd\xfd\x9f\xc5?\xe0\x18\xd6\x0f\x01\
+\x9d\x9d\xa0\x02\x88k\xa0$\x85\xf6N\xc2J\t)-T\x14\x9a\xc4\xb8\\\x80=g.Vm;\
+\xe5\x1d\x03P\x9cEt\xff\xfd\xc8\x85]ty\x92.\x17\xa8%T#\xc1G?\xf1i\x96\xac\
+\xb4\xf8\xd1m\xc3\\\xff\x95o\xe0\xb8\x16\x9d^\xcag\xae\xfd\x13:]c\xa6\xee6\
+\xe0\xb1oGS/\x08\xf6\xaa\xd1I\xa0\x08\x1c<\xc7\xe7\x03\xef|\x0f\x9f\xfa\xd27\
+\xb1;\xbb\x19S9\xac\\\x07\xa3\xa5\xbaIF\x93\x1a\xdbQ8D\xa4a\x15\x15W\xf1,(\
+\xfa)\xbe\x18\xa0\xd7s8\xe9\xd4c8\xef\xec\xd7pp\x16\xc4P\x98\x86\xa5\x81Q`\
+\xfd\xd0\x18\xbf\xbe\xff).~\xd91$qL\xbb/8\xfa\xd8\x83Xt\xe8\xa9<S\n\x08zSB+D\
+\t\x05i\x1d\x11\x85DC)\x8e\xe3\x92*\xc0\x89\xc1\x11@\x95\xa4\x1c\x93\xc4\x16\
+\xf8\xb3\x90\xae\x8b{\xf0"r\xd10\xb5\x91\xed\x0c\x0eV\xe9\xe9\xca1{)\xdc\xf1\
+k\xf8\xfbk\xbe\xcb\x86\r\x1b\x98\xdb\xddEyp\x0b\xef\x7f\xd7\x9b\xe8\xb4 \x10\
+{ \xbc\x05i\x9abY\xfb\xce\x81\xf2\xc7\xc4^\x91\xeed\xdd\xc4\xe2N\xc8\x1f\xdb\
+\xc6\xba\xb5\xa7\xf2\x99\xaf~\x1f\xb7{\x01\xa5r\t\xd7/\xd0\xd6\x9d\xa3V\x1aD\
+\xc6U\x8a\xae\xc2\xf5\xeb\x14:m\x0e9h9\xc7\x1d\xbe\x84\x0bN_Jo~\xbchO`\xba\
+\x8d\xe1\x14\x1e{&\xe4\xc1G\x1e\xe7\xe7\xbf\xb8\x83\xca\xe80\xfd\xdb\xd6Q\
+\xaa\\\xcdU\x17\x9d\xde\xac\xf7z\xef\xfb\xae\xe0\x92\xb7}\x15\xdd\x96G\xd5k`\
+[XB\xd2Y,P+UH\x13\x81\xf4]p]\xe3\x9fO#\xd0\x02\xcb\xb1\t,(\x97\x07\xc9\x17\
+\x1d\x866\xaf\xe5\xb4c\x97\xd2\xd9\x9dc,\x86/\xdc\xbc\x85/}\xff\x97\xa8(DhI2\
+\xb6\x83W\x9f}<\x97\xbfl>\x05\x8c\xd5])W)\x14r\x93?\x9c\x0cS\x8d\xf4=\xd9\
+\x03{E\xba\x00t\xa5N[\xdeG\xe4\xe0]o:\x14%\xeb|\xf7\'\xbfF\xfb\x8ajT\x82J\
+\x8c\x9b\x96\x99U\xb09\xea\xf0e\xbc\xf4\xd4c9\xe6\xf0\x85\xcc\xca\x19\x97\
+\x8e\x93@:\x16\xe2\xe6=\xa4\x84\xa7\xfa\xe0\x87?\xbd\x97_?\xf0$\x83UM\xdfP\
+\x19\xcb\n\x08\xdc\x1c\xb3\x97\xcc\xe5[\xdf\xbb\x9b\xf3\xcf>\x9d@A\x90\x87Es\
+`\xd5\x8a\x05<\xf0\xf4:\x8a\xdd\xbd\x94\xab!i\x12\xa3}P\xbe&\xd6\n\xe9\xb8&\
+\xb9\xa2\x12\x82kS\xec\xca\xe1\xd6G\x18\xda\xbe\x91\xce\xce\x02VX\xe6\xeb_\
+\xf88g\x1cl\x0c\xe9\xba\x05Oo\xafR.\x0b\xe6,ZLi\xc3\x83\x9c\xba\xfa >\xf6\
+\xce\xb3\x08\x07\xab\xf8]\x86\xe8B~\xcf\x84\x03\xb8\xee\xeeJ\x0b\xa6\x1e\xf6\
+\xd2\xa6H\xf0\xac* \xc8I\x8f\x04x\xfb\x95\xc7\xb0f\xcd\x83\xfc\xfe\xf1g\x98\
+\xd3\xd9\xc51\xc7\x1c\xc5\x19/9\x91c\x0f)\xd0(\xaf\xf70\xaa\xb1\x86\xc9%\xac\
+[\x1ew\xdc\xb3\x9e[\x7f\xfe\x1b\x1e^\xbb\x9d\x92\xca1\x1c{\x0c\x0e\x878\x9d\
+\xf3q\x1c\x87\xc1\x91~FD\x9dy\xf96n\xfa\xc9\x1a^\x7f\xd12J5\xf0\x02\xf8\xdb\
+\xf7\x9c\xc5\x1b\xff\xfcK8Z\x10\n\x8f\xc4\xf1\x19\xacU!\x8e\xf1\xbb\xda\xa8\
+\x97\xab\xa0\x03hk\x87\xb0\xcc\xd8\xda\xa7\xc8\xbb\x15\x96\xf5\x16Y\xbct\x11\
+\x7f}\xcd\xd9\x04\xc0wnz\x94KO=\x948\x80{\x1f|\x0c\n\x05\xfa\xd6=\xc3l\'\xe1\
+-W\\@\x00\xb4w\xb8\x10\x97Mq\xa30\xc9\xc5\xff\xbf`\xefH\x17\x1a|\tz\x8cz\xad\
+D.7\x0b\xe9\xc0\x97\xaf}+w>\xb8\x9e\x13\x8e\\\xdc\xdcUc\xaaE\x1c\x8c\x1a\xdf\
+0\x1a\xf3\xe8\xa3\xeb\xb9\xe5\xd6\xfbX\xbba\x98\xbe\x81\x11\xf0\xda\x91\xb9\
+\xd9Tt\x8e\x98<\xf4\x16\x89\xcb5b7 7\xbb\x8b\xb1\xcdOS\t$_\xfa\xfa\x0fY}\xd8\
+{9|\xb9)0=a>\x9cv\xc8B~\xfa\xbb\x8d\xa4\xb9y\x88\xf6N\xa8h\xc8\x07\xd4\x85\
+\x03\xaa\x0eR\xd1i\x01i\r\xe9\xa5\x1c\x7f\xc4R^z\xc6I\x9cs\xc6\\\xb6\x94\xe0\
+\xc3\x7f\x7f#\xb5\x81\x8d\xbc\xf9\xa2C\xd9T\x02t\x0c\x952\x8b\xe7\x15\xb8\
+\xf6\x1do\xe1\xa8\xa56n\xac!-\x83cS\x1d\x19!\xd7\xd9\xb3\xcf\x1f\xfc\x8b\x89\
+\xbd\x94ta\xf4\xa1\xed\x92\x0f\xac,\xc6+\t\xeb1g\x1f\xb9\x98jb\xb2\x98\x1aS`\
+\x85\x11\xdc\xf1\xc0Fn\xfd\xc5\xafy\xf8\x89\xb5\x94"\x97\xaa\xee@8=\xa8Ys\
+\x88\x12L$\xca\xce\x83]0\xc1\x89B\x11$TGFi_\xb0\x98\x91\xe1\r\xb4[\x01_\xf9\
+\xf6\x1d\\\xfbWg\xb2\xc0\x85\x91\x11\xc5\xbb\xdex\x01\x0f<\xfeyv81\xb5$6\xd3\
+4tuC\xdf&\x9c\x9ev\xfcz\x85\xe15O\xb0pV\x1b\xaf~\xf5\xb9\xbc\xf2\xc2\xe5\xcc\
+i\x87_\xfcA\xf3\x91\x8f\xff#\x96\x17\xb0\xa0\xab\xc3\x14\x1d>;\xcc\xf6\xf5O0\
+\xe7\x80\xa5\xbc\xf1\xbc\x938\xfbp;SK\xb1q\x19\xc7)\xb9\xce.j\xf5\x90 \xd8\
+\xcd\\\x1eS\x14\xa6\x06\x7fr\x7f\xc0^\x92n\x83\xdd\x91\x1dl\xdc\xad\xd8\xe3\
+\x9b\xa8[\xce2\xdd\xe8\x1f\x1e\xde\xc2\xb7~\xf83\xee{r3e\xab\x8b\xe1\xd4\xa7\
+"\x0fF\xe5\n\x10V\xc0r\x8c\xcbV\xa6\x99w*\x02Q6\xe3\xdbJ\x05:\xda\xc1\xae0Z\
+\x1d\xa5\xa7\xbb\xc0\xf6-\xfd\xdc\xbf\xf6\x19\xfe\xfb\xce\x83\xb8\xec\xc8\
+\xb9\xcc\xee\x94\xd0\x06\xab\x0f\x9f\xc5\x9dkK\xd4\x12\t\x85\x1e(\xa7`\xe7\
+\xc8\x87\x15D\xe9Y\x8e[\xd5\xce\xdb\xdf>^\xd7\xda\x00\x00 \x00IDATt%/9\xb6@\
+\x7f\x15\xfe\xe9+O\xf0\xfd\x9bo\xc2o\xefd\xc3\xf6\x11\x0e=\xec\x08"`\xa0o\
+\x0b\xf9h\x07\x97\x9e|\x0e\x7f~\xd1\x12\xda\x00\xdb\xcf&\x1a\xc0\x03O\xa2\
+\x85\xc4\xdd\xcf\x08\x070s\xdcLN\xfa^\x8d2\x1b\x89\x90\t&Hf\xf2\xce\xc0R\xe6\
+e+(H\xf8\xd5m\xbf`\xfd\x9a\xcdD\x91\xc7\xb6AEd-@yK\x81^(\xce1N\x1a!\xc1s\xc1\
+\xb3L.t}\x04\xc2a\xc8\t,+\x04\x99\x80\x8a\xa9#H\x82\x02\xcf\x0e\x96\xb9\xe1\
+\xc7?\xc7-Hv\xf4W\xd1\n\xfe\xeeo.G\xd6w\xd0\x11\xc4\xb0c\x1d\xae\x88\tt\rF\
+\xb6\xf0\x96W\x9f\xcd\x97\xaf\xffSN?\xb6\xc0\xe3\x1b\xe0\xef?\xf3[\xbe\xf3\
+\xdfw\xa3\xbdN\x06\xcb\t\x9d\xf3\x96!\x83NB\xe0\xee_\xde\xc6\x15\xe7\x9d\xca\
+\x9b/:\x9ev\xc0V\x15PY\xa2\x88\xb4\xb3\x18\xf8x\x1a\xfa\xfe\x85h\xb7\xdf\xec\
+\xb5k!my\xa9\xe6h`\xbc\xc4G\xa5\xf0\xa1\x0f\\\xc9Y/;\x83|>\xc7\xac\xd9s\x88\
+\xb4\x00\xe1A52\x81\xf2j\x19\xc6F\xa1V\xc6K*8I\x05WU\x08D\x84\x1dUp\xc3\xd0x\
+\xf0\xf0\x08S\x1f\xa7m>8\xb3\xb8\xef\xe9~\xae\xff\xd6]\xf8sr\xd4S\xe8\xb4\
+\xe1\xdc\x13V\xd3\x9d\x0c\xd0]\xac\xe0\x0e=\xcci+;\xf8\x8fO\xbe\x9b\xb7\\v\
+\x14\xed\x1e\xdcy\x7f\x85\xbf\xf9\xc4\xd7\xf8\xc9\xef\x9e\xa0\xec\xcfB\xe5\
+\xbb)G\x92r"\xb0\x1c\x17\x12hs\x15o\xb9\xf4\\\x0e\xea\x06K\x95AU\xcd\x1df^\
+\xc8\x98\xf1.k\x7fC\x9a\xec\x03\xd2\'B\x81\x88A\x86F\x8d\xc8\x1a\x8eg\xb4\
+\xe2\x9f\xbe\xf5d^{\xd9\xb9xv\x85\xb6\xa0\x0e\xd5\xcd\xe0\xa7Y\x0c\xde\x04\
+\x96e\xb5\x8a,W\xc8%\x8a\x82\xb0\xc9i\x0b]OH\xea1\x8e\x95\x03+O\\V\xc4*\xc0n\
+[\x88,\xce\xe5\xcb?\xf8\x19\x0fo59\xf5I\n\xef\xbb\xfa\xe5\xe8\xadOrX\xaf\xe4\
+\xcas\x8f\xe6C\xd7\x9c\xc3A\xcb\xa1\xe0\xc2\x7f}\xe7I\xde\xf3\xfe\xeb\xd88\
+\x10\xa2\xf2\xdd\xd4\xb1\x19\t5^{\x0fq\xa9B{!\xc0Vp\xc5\xc5\xe7q\xd8\xb26\
+\x920+\xc3\x990\x9b\x83\xc1\xfe\'\xe1\x06\xd2\xde\xfd0s/\xfbt\x85\xd5\xc8\
+\xf1\xca\x1e\x8a6s\x13fQ#\xb3\x8fR\x82j\xd5\xe3\xaaK\x96\xd2\xb5\xa0\x83\x8f\
+~\xf6k`\x07DI;I\x1a\xe0{yl\xcb!\xa9\x8c\x81\x8aq\xa5\x8bR\x16\xb5z\x8a\xe7uP\
+\x8b\x04\xe0\x99. \xac@\xaaIr\x16Z\xe4\x19\xf5\xba\xf8\xc4\x7f\xdc\xc4\xbf|\
+\xf0"\xf2\xc0\xac\x02\xbc\xeb\xf2\xf3Yr\xf8*\x8e:v\x11!\xd0W\x85\xbf\xfc\xe8\
+\x0fx\xe0\xa1\xb5\x14{\x0fg0\xc18k\xc6J$qJ\xd0\xdd\x89\xd0\x8a\xde\xbcC^\xc3\
+\xc9G\xaf\xa2\x02hG\x80\xf4\x8c6B\x820\x81\x92?V\n\xf2\x0b\x81\x88\x80\xddY"\
+{%\xe9f\x94\xaa\xb2)\xed$f\x1eDS0\xa8\xb3W\n\xb8\x96Ew\xce\xc2\x03.8\xae\x8b\
+\xaf\\\xf7.\x0e\xed\x8d\xe8T\xdbP\xd5a\xa2Z\x9d8J\xd1\xc2\xc1rrh\xdb\'\xc6\
+\xa1\xa6$\xd2+\xa2\x95DG\xb1\x89\xca\x15r&e8\xae\x91\x86!i~\x16\xf7<\xbe\x9e\
+\x1f\xfed\xa3\xb1\'\xca\xf0\xee7\x9d\xcbK\x8f]D\n\xdc\xfd\x84\xe6m\x7f\xf5\r\
+\xee||\x0b\x03\xa2\x8d-\xa5\x10\xa7\xd8\x8e\xaaW!\x08 \xdf\xc6\xd8H\t_\xa4\
+\xe4\xa8\x1b\xdf\xbb\x0e\xa9D&6\x14\x11\x80\xcc\x9b\x94m\xad\x10\x848Y\x94l\
+\x7fDy\x0f\xf2\xbcw\xea]KH\x1dH\x02P\x1eB{h<R\x02"\xf2D\xe4\x19\xaeh\x14\x0e\
+\x1am\n\xf2\x14\x9c\xb8\xd0\xe6[\x9f|7\xa7\x1d2\x97\x05\x0b\xba\x08\x1d(\x87\
+e\xca\x16D\x81\xcb\x88\x80\x92\x05\xba\xad@YG\x10X\xe0& \xc6p\xbc\x1aA\xae\
+\x86\xef\x0c\x82]\'J}\xca\x91\xcbm\xb7\xdf\x8cH\xc0\xf7\xc6 \xae04\x04\xff\
+\xfe\xcd\x07\xb9\xfa\xff\xfc3\x9b\xc2Nj\x85y\xf8\x0b\x17S\\0\x8b\xd1\xbe\x8d\
+P)C\xa4p\x82"\x841\x81\x8cY\xb9\xb0\x0b\xa1b\xd01\x910\xe1\xd52P\xc73\x01$RP\
+!B\x87\xcf1{\xcb\xd4E\xf8\xdc99{\x81F\xdaN\xa6\xf3v.\x7fn\xcb\x17\x10H,e<qm\
+\xd2\x10\xef\xd7k\xfc\xf3\x87_\xcbY\xc7\x1e\xc0\xb2\xd9 t\x19\xd7J\xd0\x96m2\
+o\xd2\x04r\x0e$\x15\xc8K\xc8I\x88*\xc4\xe5\x01\xd2\xea\x00"\x19\xa2 +\xf8\
+\xf1\x18\xc7\x1f\xb8\x84O]\xfb\x0e|\x07T\x14\x83\xeb\xa2\x1d\xf8\xef\x9f\xde\
+F\xb1\xd0\xc5\xd8H\nv;\xb5\xb1\x1a\xa3\x83\xdb\xa1\xb3\x00\xf9\x0eP\x82\xb8<\
+L\xbe\xd3f\xe9\xbc6N=\xae\x83\xc6\xfc\xed9g\xdc\xd7f,u\xcbH{\x16&\xb5&\x95\
+\xf6F\x177n\xdcN\xb5n\xc0\xdf\xc3w{Ozc^\xbdl~L)\xcc(\xd0\xce^\x8d\xff\x1d\
+\xd9\x98%\xd5\x94\x9b\xcdj\x0f(\x02\xff\xf0\x96\xa3x\xc7Y\x0b9\xa2+\xc1\x1e\
+\xddN\\\xa9`\xb5\xb5CG\x01\xa2\x12\x144\xd4\x06\xa1V\xc1w\x1c\xe6\xb4u\xa1+U\
+\\"\xda\x19\xe5\x9a\xf3\x8e\xe4\xcb\x7f\xf9r\xe6\xfa0R\x85\x11\xa7\x8b\x92p\
+\xf0\x8b\xf0\'\x7fz\x19jp\x03\x8b|\x9bB%\x85\x8a\xc6\xebn\x83\xda\x18\xe4:\
+\x8d)^\xde\xcc\xc2\xb6\x903NZE\xff\x08\x84\xb6G"\\\n@\x1b&=\xcb\x06blR\xf2 r\
+\xe6\xaet\x8a\xd0I37\xa45\xd5\xc9d\xfa\x8f[\xf9S\t\xb9\xc6\x0cZ\x93`\xefHo\
+\xc9\xeal\xfc\xdf\xd8\xd4\xc8\x0c\x11{x\xb9(\xf2*\xe2\xed\x17\xac\xe6\x1f\
+\xfe\xeaj\xce8r)A<B:\xda\x07\xe1\x88\t\xd8[\xc2$_T\xabx\x96\xa4\x7f\xd3zf\
+\x05\x16\x0b\x8b\x16\xd7\xfd\xd5\xdb\xb9\xf2\xac#X\xdemB\xf7k\xfb\xe1\xbd\
+\x1f\xffO\x9e\x1e6\x93\x03\x9d}\xe2b.?\xf74\xca[\xd7\xe0*\x10\xd2&,Ua\xee\\\
+\x18+A{\x9e\xb6\x0e\x0f\xea\x83\xcc\xed\xca\xd3\xd1n\xc8\nS\x93\xff\xeek\xd3\
+\x7f7\x885s\xc3I\x9a\xd3\x7f\xb7d\xa34*l\x9a\x1f\x99z\x84\x03\xc8\xe7M\xfa\
+\xf3\x84\x00<iS\x1a\x1c\xe5\xb8\x95\x0e\x9f\xfb\xbb\x0bx\xfb\xabN\xa6S\x0cBy\
+\x00\xd7\xb1a\xb4\x0e\x91\xc0\x9b\xbb\x80\xd1\xd2(]m\x16G/\x9f\xcd-\xff\xfan\
+\xceY\xdd\xc9\xc2y0\x96\xc2-\xf7\x0e\xf1\x86\xbf\xba\x8e[\xef{\x86O|\xee\xbb\
+\x04@\xbd\x0eW^z*\x9d\xf9\x147\x9fe\x96\xe5\xbba\xa4\x0ev\x82\x15\x0e"\xa3*\
+\'\x9fx\x02C\x03\xfd\xe4\x84I\xf4\xb1\xa5\x95\xa5\x1c\xa7H\x93\xc3:i\xdd\x01\
+\xd9\x04\x84\xe3\x9fu#\xe7u\xf2\xfd\xa7\x00\xd4n\xbcq\xf0\x82\xd9(\x92$\x8e\
+\x99\xd3\xddNA@\x8f\x03\xef~\xfdJ\xaey\xc3\x05\xcc\xcb\xa7Dk\x1f\x07Kc\x17|\
+\xc2\xd2\x0e<\'\xe4\xca\xcb\xce\xe3s\xd7\xbe\nQ\x86<F\xfa\xfe\xed\xbb\x0f\
+\xf0\xa1\xeb\xbfB_\xdc\x86\xecY\xcem\xf7<\xce}\x8f\x85\x14}Xz\x00\\\xf4\x8a\
+\xd3)W\xb6\xe0\xb4\xdbf&\x84\xe1\n\xdd\xed\x01\xe9\xd8v\x1c\x12^{\xf9\t\xfc\
+\xfe\xfe\xfb\x01c\x93\xb8\x96d\x8f\xae\x97\x9d\x18U\x13\xfeSMM7\x15\r\xbd\
+\xf8E\']\x83my\x10kl\r\x81\x86 \x86\xab/\\\xc6g\xde\xff\x0eN\\\xb5\x88|\xb5\
+\x8f\xceh\x07\xf3sU\xae\xfb\xc0\xd5\xbc\xf5\xd5\x07\xa0\x80\xb6\x02\x94\xea\
+\xf0\xb5\x9b\x1f\xe7\xdfo\xba\x9d~U@t/f t\xf1\xba\x16\xf3w\xff\xf0\x15\xfa\
+\x86\xcdi.\xbf\xe2H\x82\xa0D\xc1\xab\xc2\x8e~D{\x0f\xb2V"\xa7\xab\x9c\xf9\
+\x92S\xb0$\x94F\x86M\x97#M\x86:il\xca\xae\xd0M\x1fDv\xc9\x19\xb2\x9c\xb3\xe6\
+G\x9d}\x99\xcdx\x91\xed\xa1\xd4\xd4R\xf2\xbb,\x1d\xd2\x82\x17\xac\x91F5S\xad\
+Y+\x8f\x10\x87%\xda\x1d#\xc1\xc7.\xb2\xf8\xd6\'\xaf\xe0\x1d\x17\x9c\xc8\x11\
+\x1d\x117\\\xff&.\\\r\x8d\xa5q\xaa\xc0\xbf}\xef>>\xf1\x95o\xb3\xa9*Y\xb0\xf2\
+(F\x9fXKj\xe7\x19\x8b]\xb6\x95|>\xfd\xa5\xbb(\x01\x9dyx\xfdk\xcf\xa6>\xf2\
+\x0c\x9e+\x08\xc2\x98x\xb8\x9f\xee\xb6\x1cW\xbd\xf6X\xbes\xc3}\x1c|\xe0\x8aq\
+\xe9\xd4\x8dt\xe4\xd6\x9a\xb1q\x8c\x13\xbf\xf3cjH\xban\xaaxg\x1f\x97\x1e=_\
+\xa4{\x18O\xbc`Wj;f\xad\x11\xbf\xd8\x86\xeb{\x94+C\xc8$fQ\x0e\xe6\xc6\xf0\
+\xfeKN\xe0\'\xd7_\xcd\x812\xa4\x07S\xc40\x9a\xc2\xb5\xffz?\x1f\xfe\xd2\x0f\
+\x18t\xe7"f/a\xfd3\x1bpV\x1e\x86\x9b/\x10)\x8f\x8a\xe8\xe5\xee\xc7w\xf0\xc3_\
+\x0c1\x00\xbc\xe1\xc2U\x1c\x7fp7\xf3\x83\x145\xd8\x87\'SN9\xe9D\xe6v\xc2\xbd\
+\xf7\xdc\xc3\xa2E\x8b\x80\xcc6\xd3\x1a3\'\xba!\xbd\xd1\xaf\xef\xf2\x88Z;m\
+\xdd\xfa\xae\xa6\xec8\xde\xdb\'\xe3\xf4\xe7\tag\x05\nqL\xa8"\x8a\xf9\x00\xdf\
+V\x888\xc5\x93\xd0\xe1\x87XI\x8d\xd9\xb9\x1a\x05j\x08\r\x1f\xbf\xeek|\xe9\
+\xff~\x87E\x87\x9fD\x12t\x10\xd6\x12h\xef$V\x9ah\xd36\n]s(\xa7\x01\x9bG\x14\
+\xd7\xff\xeb\xd7\x19(\x19\xed\xf0\xb6\xd7\xbf\x8a\xda\x8eMty\x82\xae\xc0\xe6\
+\x95\xe7\x1f\xcd\x86m`9\x81\x99p7+\xb85\xde7\x9aD\x8a]\x94z\x86\ts\xc6\x8b\t\
+U\xb9\x12\x85T\x8a8\x8e\xff\x88O\xef\x7f\x0e\x97\xdd_\xcf\x0bB\xba\x16\xd9,S\
+\x12\x1c\xc7\xc3\x95.\xb2\x11\xa1\x93\xcad\xaf\x10\x9by\xc7t\x8cV!?\xff\xe9m\
+<\xf9\xe4\xe3\xd8\xb6M\xa9T2\x95\xa0(c\xaa\xc7\t\xcc\xea\xa5\\\xae\x83W\x00\
+\xbf\x00^\x9e/~\xf9\xbf\x18Tp\xf4\x81\x1d\x9cu\xfa\xc9x\xaa\xc6\xb1\x87.\xe3\
+\xb0\xc5\xf0\xe0\x83\x83l\xeb\xebg\xfe\xc2\xc5(\xc0u \tC\xe3o\xdf\xc3rV{k\
+\x9a;\x93M\xab\xf6"\xa2^\x19\xd8\xedw/\x98\xa47\xc2\xb1\xa6\x0f43?5EL\nSK\
+\xec\x14@\x06h\xe9s\xc6\x19g\xf0\xbew\xff\x19\xaf8\xed(j}O\xa1\x867c\xcb\x14\
++\xe7\x9b\xf1V\xaa!\xc8\xe3\xe5\x1dR\x15R.Wx\xe4\xd1\xa7\xf9\xf5o\x9e \x01\
+\xdez\xf5\xc9\xb4\xb7\xb9\xbc\xee\x92\x97\x91\xc6p\xef\xbd\xf7\xa2\x84G\xef\
+\xfcN,a\xe4\xd4\xf6\xf2 mT\xad\x96]\xa5\x98\\]O\x14\xfb\xf1\xe2\x06hv\x08SM\
+\xd2\x0b\xf6\xee\xa9}\xc1\xe6\x86m\\\x82\xdd\x9c\x16_e\xb5\xba\xa6\x0e$\x05\
+\x12\x0b\x12<"\xa0\xcd\x83\xb3\x8fY\xcc\xbc \xcf\xc1\xbd>\xdf\xbc\xedN\x94\
+\xdb\xce\xa6\xda\x08\xa8"t\xcc\x81\xb1\x12\xe1\xf0\x00\xb3{}\xca\xfduB\x0b\
+\xbe\xf8\xd5\x9bYu\xf8J\x96\xf5\xc0\x9b\xdf\xf6\x06\x8e]\x01}\xa3\xf0\xe0\
+\x03\x0fPS6\xdb\xb6\xa7\x1c4\xcf\xa2\xd0\x1c\x8eI\xa3\x8a\xb4\xdc;\xa9n\x96m\
+\x99\xdfj\x00\xadQj\x8aE\xdd\xe5T\xe8\xd3i\xf8\xb8uK\r\xafefi\xb0\xa0\xa4\
+\x8d\xa5\x9e\xa51\x00\xc6\x87\x7f\xca!\xb3\xf8\xdb?\xbb\x80\xf7]u!\xc1\xd8V\
+\x18\xde\x82\xd7\x93\xb9X\xc3*N\xb7O\xa5\xbc\x83 \xc8\x13%\x01\xa3\x95\x80\
+\xf7\x7f\xe4\x87\xc4\xc09g\xe7q\x81;o\xfb5ZX\x08\xb7\xc0m\xbf\xbc\x8b0\xcb\
+\xd6\xaaE\x1a\xb0\x90\xb9\xfcs<\x8a\x9d\t5\x8e\xe6\xd6A\x9e\xe7M\xad\x94\xaa\
+ZR\xdc\xedw/\x98G\xae\xa1\xd6\r\xe1.\x86R\xb7\x99\xa5b\xd9\xe3\x93\xeb[\x8c\
+\xe7\xca\x0b]\xc3\xd55\xae:\xffT\xde\xf3\xba\x8bX>\'G\xb8\xe1qS\xd1\xd8\xe5\
+\x12G;PVB\xa8l\x84=\x0b\xd7_\xcc\x93\x0f\xae\xe5\x86\x1f\x0f`\x03C\x031w\xdc\
+\xfe3\xa4\x9b\xa3\xd09\x8b\x07\x1f~\x92j\x9c\x15]$\x98\xeb\xc1\x9e\xa0\xb2\'\
+b\'\xc2\x9b\xd5\xa3V6-\xe9\xc4B\xc2\xa9\x02\xb9\x07;\xe5\x05\x92t\x13\x9f\
+\xceRj1\xe1\x19\x0f\x84\x89\xc3\x0b\x146f\xdc\x9e\xc7\xe4\xdc\xb9\xdaT\xa9"\
+\x14Im\x8c\x9cT\xbc\xf1\xa2\xd5\xfc\xc5U\xafd^\xb7\x84\x91\x8dP\x1d\x00\xaa\
+\x90\xf7\xa8\xc9\x02\xb1\xecbxX3{\xc12n\xbe\xe9\xbfP@W\x8f\xc3\x8aU\x873:V\
+\xa5\x16C\x7f\xa9\xc2\xc3\x8f\xd5I\x01\'\x10\x99\x05\x9fi\x9f=\\\x7f#\x88d\
+\xd0\x90\xf4,\xc26\x05I\xdfS\xb5\xcd\x0bF\xba\x91c\x93O\x97\xca,\xedI\x98\
+\xef\\\xaa\xe4T?yJ\xe4SE!\xc2,b\x0cD\xe4!\xd7\x03R\x12\x8d\xc2e\xa7\xcf\xe6\
+\xb3\x1f|\x0b\xf3;\x81\xda\x00]\x8b\xe7S+WP\xf9\xb9TF-\xda\xf3\xb3\xa9\xf4\
+\x0f\xb0j\xe9,\x06\x06+\xa4\xc0\x85\x97]\xca\x8aC\x8ff\xb8TCaq\xcb\xad?\xa5\
+\x9a\xad\xdf\x93*\xcd\x1e\xe7`\x9d\x8cKm\xdc2\x8d\xcc\x9a\xc8q\xa6\\\x9f>\
+\x05$}\x1c-3\xd3\xb4\xcc\xc3\xa2\xcc?*\x06\x1d\x9a\xb5D]c\xf1\x9b\xc9\xb8$\
+\x90P\xc8k\\\r\'/\x83\xaf~\xfc\r\x1c\xbf\xa2\x97\xa1{\xee\xa0\xd8\xde\x85.\
+\x83\xd5>\x8f(L\xb1-X\xbet6\x0b\xba\xf3<\xb5\xb5F\xef\\x\xcd\x15\xe7\xe0\xd9\
+)\xbeg\xf3\x9b\xdf\xdeK\x98\x18)\x1d\x1e\x8b\xd0\xc2Fc\xd3X\x19a\xbc;\xca\
+\xd2\xa1[\'\x01\xda\xa9\x814#\x8dS\xa8\x8e\r\xa6\x04\xe9\x12\x13\xd6w\xccR\
+\xd3\xc2\x14\x96\x9a\x95\xd0$\xc6\xff\xd6\x06\xa2\x90\xe5\xc6\x9b\x9f4\xf2\
+\xeb]b\xb0\xca\xc4\xaa\x1ft\x8d<p|\x0f|\xe8\x923\xb9\xfc\xd4\xe3\xb1\x9ey\
+\x96\x0e\x02\xa8*,K\x10tx\xf4,\xe8$\x02n\xfe\xd5]\xfc\xfc\xfe2g\x1f\x03\xc7\
+\x1d1\x9f\xa8\xda\x8f\xe3\n~\xf7\x87~\xb6\x8fA\xd0\xe1\xed\x12\x13\xb7\xb2Y\
+\xa4Q1\xa8\x08\xd24K\xa1j\xdc\x8b)\xe5\xce:)s\x1f{\x1a\xebO1\xbc\x80\xa47\
+\xd2-&\xc6\xda\xc7\xa9\xf5L\xba\xb4\xb0\'\xc4\xec\x9bk\x9a\xa6\x11\x9ek\x13H\
+IX\xab\x91\xd7p\xceQ]\xbc\xed\x15gr\xce\xd1\xab\x10\xa5\xedt\xe5$c\xa5m\xf4\
+\xf5\xad\xe5\xf8\xe3V\xa3\x81\xce\xde%\xfc\xed\xb5\x9f\xe2\xa15\xf0\xb67\x9d\
+\xcb\xbc9\xb3\xf0\x1c\x97\xdb\xee\xf8\x05\xf9\xa2\xe9pF\xab\xe3\xdag\xc2<``\
+\xaey\x17\xbf\xfa\xb8Fh\xcd\'\x98J\xa8V\xab\xbb\xfdn?i\x9e\x02c\xe2\x05\x80G\
+!\x08\x8c\xdd%\xe1\xa8\xa3\xe7\xf2\x17\x7f\xfeJ\x96-r\t\xc7\x9ebi\xaf\xcd\
+\xa2\xde\x02s-\x18N\x14k\xd7\x0e\x90\xaa\x02\xff\xfe\x1f71\x7f\x01\x9cw\xde\
+\xf9\x8c\x8c\x8e\xf1\xfb\xfb\x7f\x8f\x85I\xa7.\xe6\xc6U\xba\xc9\x8a\xc9\x0c<\
+\xe9\x19\xa7\x91t\xd8o\x1eU\x86=\x95a\xed\x1fw\xa2m @\x87\x13\x17\xbc\x1f\
+\xab&\xd8\x16\xac8\x00\xfe\xe6//\xe4\xc8\xe5yF\xb7>\xc8\x81s\x8b8\xc0,[22\
+\xa8h\xebX\xc4\x1f\x1ez\x86\xcf|\xfe>.\xb9h1\x8b\x96\x1c\x08\xc2\xe2\xde\xfb\
+\xfbQ\xe9xL\\dG7v\x84m\x82\xa7\x99\x0by\x8f\xc6\xfd\x14\xc4\x9e\xea\xd3\xf7\
+\x13\xd2\x81T"\xa4\x83\xc4\xcc.\x12+\xc8\xe7l\\\x11"I9\xe1`x\xff;_\xc1\xca\
+\xb9\x82\xa3W\xcc\xc6\xc5t&]]\x07\xb0~\xf3\x08nn\x16?\xbb\xf3>~z\x17|\xf4\
+\xef\xff\x84HI~\xff\xc0\xc3\xc6\xed\xae\x1bj:\xcdV;\xd2\x13VWl]\x95q\xbf\x81\
+~\xd1\r\xb9\xe7\t\x8dYN"{\xf2\x96\x05\xa96\xf2n\x11b\xa9Qr\xc01\xcb\xe1\xba\
+\xf7_\xcd\xc5g\x1c\x83E\xcah\x05vl\x1f\xa2\xd81\x87\x9av)v\xcf\xe7\xba\xeb\
+\xff\x85\x91\x1a\\\xf2\x9a\xab\xf8\xcd\xef\x1e\xa4Z3\x06\xfax\xaaD\xd2X\x05\
+\x06\xd87\xd9\xae:s\xd36^i\x9a6_\r\x84aH\x9a\xa6\xcd1\x7f\xeb\xfe\x90\x90\
+\x00\xa126H\xe3\x15)\xbdk\xad]\xe3\xc3\x1e\xfc\x06\xfb\x07\xe9`\xae4m1\t-A-2\
+\xab9\x14\xa5\xc4\'!\x1d+s\xfc\xc1s9b\xc9\x1c\xda\xd1\xcc\xc9\xc3\xe6-\x1b\
+\xf1\x82\x1ca"\x19\xa8(B+\xcf\xbb\xfe\xfa\xffr\xd6+VP\x8e-\xd6o\xa9\xb5<\x85\
+F\\ \xc6"\xdeg\xb1\xf2\xc6T_\r\x95\xdb\x98\x15J\x08\x01\xf51\x08\xcbx\x0eX"A\
+\xc4uD\x1a"E\xb6\x18\xb0NM\xe1\r\x1aK\xea\tF\xb0Nc\x928B\x00a\xbd\x8eNU\xebI\
+\xf7\xf8(\xa7>\xb2j\'\x04\xd4\xc2\x908\x1b\\\xe5\xdd<\x16.\xa4\x1a\x11W\xe8-\
+z\xa4\xe5\x11\x02+!\xae\x0fa\x03\xed\x05\xc1Xe\x00\xcb\xcf\x91\x96cd\xd0I\
+\xea\x16\xf9\xcc\xbf<\xc2\xd5\xef\xfa\x0b~\xfc\x8b{vJa6K\xe9\n\xa2\xac\xa2g\
+\xdfX\xe7\x96e\x91\xa6)q\x1c7\xbdwJ)3\x8d\xba#\xcc\xf00\x8e\xb24\xf3F\x94\
+\xc2FH\x07!m\xacl\xe6Y\xadb\xd2$D\xa2\xf0\x1c\x1b\xcf\xb1\x89\xe3\x10\xdf\
+\xf3\x9b\xc3F\x9d$\xa8=D\xfd\xf6\x0f\xd2\xc1<u[\xe1y`\xb5\xa4)\xa1< \x87#\
+\x03\xd0\xe0\x05\x01\xc4!\x05\xdfexx\x84\xa5\x0b\x8b\x946=\x8a\xd2\x11\x08\
+\x9bz\xac\xa9&\x92\x9bo\xbf\x8bu\xdb\xeb\x88\xa0\xab\xb9p}sA\xc0Z\x15#\xed!\
+\xd5\x91a\xf6\x85\xed\xae\xb5FJ\x89\xe7yMG\x8e\x94\rG\x8f4\xa3\x05\xc76A\x08\
+\x04\x08\xdbL\xa4,\xc6\xed\t\x81\xc6\x96\x02\xd7\xb6\x18_:[\xe18\x16f\x1dw\
+\xb3\xa7\xb0m\xe4\x1e\xe2\xfb\xfb\t\xe9\xd9\r\xc9\x18M\x8cl\xa8^\x8d\x99\xd1\
+By\xd9{f\x87\x0b\x01\xa4t\xb7\xbb\x1c\xb7z1\xe7_|\x06\x1d\x81\xce\x86\xdc\
+\x02ay\xb4\xcd\x9e\xc7\xa7\xbe\xf0\x15\x0e:j5\x8f\xae\x87\xaa\x86D\x99\xceC\
+\xd8\xd9\x84\xc7:\xa53\x1f`\xe9\xe7/\xe9\xad}5\x8c{\xccT\xa3`S\xca\xcc\x1f\
+\x90\x11\xcexN\xfd\xc4\xde\xf9\xf9W\xcb\xef\x1f\xa47:W\xa9\xc8\xca\x14\xc8b1\
+-\xa5\xa5\x8d\x87\xd6\xa8\xb5\x81\x82\xd4\x9c\xbc\xfa\x00\xde\xf6\xba3\x99\
+\x93O\xa1>\x80\xaf\xebT\x06w0\xdc?\xc4\xdc\xa5\x07q\xed\xf5_\xc3\xeb\xceb\
+\x01\xd2\xf8\xff\xb0=\xd3p\xe2\x08a\t\x8c\x1e\xd8w\xf6{\xeb\xd4\x9e\xe6\x9f\
+\xec\xda\x85\xd5$|\xe7\xb3\x89\tY\xf6\xcd\x072\xfe\xd2r\x8f\x16{+\xf6\x0f\
+\xd2\x1b\x84\n;+\x97\xce\x86V\xad\x8d\xde2\x81\x1c\xe3\xb4i\xe4\x86\xa4\xf4\
+\x16\xe0\xb8e\xf0\xe1w\\\xce\x11\x8b\xdah\x17U,\xea89\x9fg\xd7oa\xb8\xa6\xf9\
+\xc4\xe7\xef\xa0?1\xd3\xcdTb\xcbx\x06\xa5o\xe6\xb3\xddG\xd3PL0\xdev\xda\xa6\
+\x11\xe8\xac\xd3j%\xbc\xb5\xa8\xa8\xf9\x1cv\x8a\xf7M [0\xf1}7\xd8OH7H1+I\x99\
+|\xf3\x16CE@$\xcd\xacV\xa1\x04-\xc7\x97\xd2\xc8\xbc\xfa\x9c\xbd\x12>\xf6g\
+\xaffA>bqO\x0e\x9d\xd6\xb1\x1d\x17\xa7m\x167\xff\xecn\xbeq\xd3\xd3\x0c\xa4\
+\xa0\x1c\x87\x94\x00\x84\x8f\x92\xbe\xe92\xb4\xb5\xd7R\xb4;\x08!\xcc2\x9e-\
+\xc47\xc3\xb1\xdaj\xe6\xe26\x08oPkgS\xbdL\xdc\xda\xf2j\x11~\xcd\xc4\xf7\xdda\
+\xbf \xbd\xb5:\x14\x1aFL\x16\xaa\xcdn\xd8\xd8\xdc\xe3\xce\x140\x99\xae"I\xc9\
+\xa5\x90\x8b\xe1\xb4\x83\x1d\xfe\xe9\x83o\xa5\xdd\x89\xe9\xca\xfb\xd8\x9eK\
+\xdf\xb6~f/]\xc9Wo\xb8\x99\xfb\x1e\xad\x9a\x86\x83\x99\xf5^\xfaEcX\xa9\xe7\
+\xdb\xa3\xef\xe1\xde\xb2\xbe\xbeU\xa14\x15y\xab\x86\xd1\xbbyg\xa22\x9a\xe4\
+\xeb]\xb0_\x90\x0e\xe3s\xbf\x98\xca\r\x05\x8d\tDE\x0cr<Z\xdfxAf\xd8i\x01i\
+\x8a\xafJ\xb4i\xc5\x11\x0b\xe1\xf3\xff\xf8&:\n\x0eI\x12\x83\x9fg\xa8\x1c\xd1\
+7Z\xe7\xc6[~\xc6\x13[\xcc\xb1\xea\xdat\x17\xc0\xf3\x9e7pgg\x8bRj\xd7~\x9d\
+\x86:\xcff\xfb\xd0\x89y\xa9l\xd5jhf\x9aiZ\xde[\xce\xa3vz\xed\x0e\xfb\r\xe9\
+\x12\xd3\xf2-\x1c,|\x9a)N\x02\x10\xaa9\x0f\xbb\x85I\x96U\xb1\xa2\x99\xf7)LQC\
+82@\x0e\x98\x9b\x83\x1b\xbex)\x07\xf6X\x14\x9d\x04[Z\xcc>\xe0`~t\xfb\xef\xf8\
+\xd1\xdd\x8f3\x02\xd4%D\x8d\xfc\xadf\x9e\xfb$\x8fu_\xf5\xf7{\xdca\xef\x8dH\
+\xb9\xd3\xfb\x9e\xf6\x99\xd2\x10\x98\xb5tl\x01\x0e\x0e\x16\x05 \xc8\xa2`f\
+\x98\xe6Ac\xab\x99\xb9\xd9\xf5Ll\xde\x96\xd9\xd87\x8f\xd76\x1b;IY\xe0\xc2\
+\x81\x12\xbe\xf6\xb7\x97q\xc9\x89+\xa8o\xde\xc8\x8e\x11\x8d\xbb\xe4\x04>\xfd\
+\xed\x9fs\xe3\x03\xb1I\xd0\x14\x1a\xc6v@2\x04\xba\x86\xd2!Ze\xae\x1c\x9d\x12\
+Gu\xb3@`\xc3\xa8\xcc\xd0\xe8\x8e\x1a.W\x98h\xc8M\xf6\x1a\x0f\xfa4\xfa\xeal\
+\x18\'\xadl8G3\xdc\xbc\xbb\x97\xdc\xe9}w\xd8/HG\x98\x11\x94\x91d\x89h\xa4.\
+\x88\xc6\xd2\x9eF\xa6\x1b\x8b\xceg\xae\x8f\xf1,\x17\x01\xb5\x84\xcc\xf6\xd1P\
+\x19&\x9f&\x1c\xde\x0b\xaf;\xef\x18\xde\xf0\xda\x97C8J\x14\'\xf8\xdd\x0b\xf9\
+\xf4\x17\xff\x93G\xb7\x82\xb0\x05\xe4\xf3P\xab\x80\x00)\xe4x\xb2\x84\x108\
+\xb2\xb1\xd2d\xb6m\xe7~6S\xe1\xad+5\xeci\xc5\x86\x89[[\xc8\xdfK\x9a\xf6\xd2x\
+\xdfOH\x7f\x9e\xd0\xc2\xcc[\xa8%\xa0ll\xa7\x13\xb4\x8d\xa3\xe1\xa8e\xf0\xce7\
+\x1e\xc6+_v \x0c?\x85]\x1dcd{\x89/|\xf9G\x8c\x01\xa1\x93G\x15g\xa3\x85G\xd2\
+\xacv\xcb\xe4\xd2r\xd0\xc2BO\xa2}\x05\xec\x91\xe0\x17\x13\xd3\x82t\x03E=\xa9\
+\x93&Y>\x9e\x06B\x85\xa7`\xb1\x0f\x7f\xf1\xfa\x97\xf0\xea3\x0e\xc5\xad\xf6\
+\xd3\x95\xcb\xf1\xe0Ck\xf8\xee\x1dCl\x03F,\x8f\n6\x89\xb2\x88\x12\x88\xb54\
+\x8e`\x01\xd2\xb6v\x89m4U\xecsH\xf6\x8b\x85iB\xba"%\xc6\xb1m,O\x1a\xc2\xe3\
+\x10\xcbI)\xc8\nAR\xe6\xb0N\xf8\xec{\xcf\xe3\xb4C\x17\x10\x8f\xf4\xd1\xd3;\
+\x87\xcf~\xf5F\x1e\x19\x82\x1dd\x0b\x13H\t\x96eV\xf2nt\xe3\xcdj\x97\x0c\xcdb\
+\xc8\x19\xd2_tX\x8dq\x97P`\xc5&jG\x08:&oC\x1b\x8a\x9c\x86/~\xec2.8c5\x03\xdb\
+\xd7SKS>\xfc\x0f7Sa\xbc\x10\xa3\x91I\xd5(\xa7\x9b4\xa3f\x1fy\xf1\xfeX\x98&\
+\xa4K\x046\xf50\xa4R\x1f\x05\x19\x82\x15\x9a\xc9\x7fc\x01\xa9G:Z\xa6M\x80\
+\x1d\xc2?\xbe\xefB\xce=\xf3p\xfa\xb7\xaf\xa7ZQ|\xeb\x07\xfd\x8cE\xe3\x1exM\
+\x96\x9e\xfd\\\xe4NQ\xe2\xa7\x05\xe9\x02H\xa2\x84\xbc\x17\xe0\xfb.!\x11\x91\
+\x8a\xc1\xf1\xc1\xc9\x93\x94\x13\xdc|\x1b\xd5\xe1Q\xba=\x93\xac}\xcd\x9f\x9e\
+\xcfU\x97\x9cM\xdf\xba\xa7\xf8\xc1\r\xdfah\x08\xc6j\xe3\xf9yM\xe2\x9f+\x7fn\
+\n\x12?-H\x07\xf0\\\x89@e\x91x\x0f%\x0b\x84\x04\xa4\xc2\xc6.\x06\xe8\x04\xf2\
+m\xed\x08B\x02\x14s\x1cx\xcf\x1b\xce\xe2\xeaW\xbe\x94\xea\x96g\xf8\xfb\xbf\
+\xbb\xaeIx\x98dCg\xb1\xc7\xac\xa4qL1\xe2\xa7\t\xe9\r_\xbd\xa9\xa5\xd3\x04\
+\xa4\x98!X\x8c\t\xd6\xa4\r\xef\x88\x12\xc84&\x07,*\xc0\xe5/;\x92\xf7\xbd\xf3\
+r\xb6\xac\x7f\x86o|\xf3\x87\xa4\xda\xe4:TjF\x83( \x8e\xf7A\xc0\xfd\x05\xc4\
+\x0bV\x9f\xfe\xe2"\xc5\x84Q$\x82B\x93\x9f\x863\xd5\xc2\xccLavu\xd1Q\x8a\xed\
+\x19g\xdeQ\x8b-\x96\xf5\x9e@2\xb6\x99\x9b\x7f|3\x87\x1dr\x10/9q%A\x00c\xd5\
+\x94\xf6\x9cE\xb5^\xc5u\xf2\x13\xdd\xa5\xcf3*\xf7\xc7\xc44!}\x1c\x96\x1e\x0f\
+X\x08\xc65\xafB\x91"\xb1$H\xcb2qZKa9u\xba\x02\x87\xab\xdf|)\x96\xedr\xe3\x8d\
+7\xb0\xfa\x88\x8f\xd0\x96\x83|\xce\x0c\xdf\xf2\xc5|\x16\xe7\xdf\t\xad\'\x98B\
+\x98\xba\xcdq\x9f\xa2Q\x80db\xec\xb66\x93\x15{\xda\x8c\xdcl\x14)\xa9\x19{\
+\x9bl\xa5,\x11Sd\xbe\xf5\x08O\xc2[\xde\xf8\n\x0e=t\x15\xd7_\xffO\x84Y\x0e\
+\xa3\xd2\x8d\xf8\x9f\xca\xca$\xc6O9\x05\xf9\x06\xa6\r\xe9\xa6B\x86\xc6j\xc6:\
+\x1b\xaek\xb0\xb5\xc2\xc5\x04rD\x16\x97O\x9b\x19\x0c\xc2,C\xa5m\xea\xa1i:oy\
+\xd3et\xb4\xb7\xf1\xc8#\x0fQ\xadG8V\xebr\x00\xc6\xd4\x9b\x10\xdf\x9e\x82\xd5\
+1\xd3\x83t\r\xba\x99V\x94`*\'J\xa0\xca\xa6"5\x95\xd8\n\xb3\xc4\xbb0C\xf7\xa4\
+\xe1bW9H=|\x0f\x12m\xfa\xf9w\xbf\xf3\xadl\xd8\xb0\x0e\xcb\x16\x94\xab\xa5\
+\x9dO\xd5Dk\x10v*I\xfd\xf4 \x1d&\x96\x80\x88\x14D\xe6j\xc9\xc4R%\xe3\x99*\
+\xa6+\xce\xe6\\\x94\xa2\x19\xd2lD\xf0\xd2\x04.\xbf\xe4b6\xad_G1W@L)J\x9f\x1b\
+\xd3\xc3\x90\x13-#*aaT}\xf6Yg\x81\xd8,\xc9f\\\n\xb2\x19\xa2\xb3\xe8\x89\x8b\
+\xd1\xf6`\x96\x10\x95\xc0\xca\xe5+\x1a\x87\xdf\t\xb2\xe5/\xbb\xd9\xe7\xc5\
+\xc3\xf4 \x1dZ\x9ezKB\xe1\x84\xed\xbb!F\x8c\xbf\xfdOgq\x99JD\xb7b\xfa\xa8\
+\xf7\x1941C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\
+\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\
+\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1EH\xdf\xbfr\xcc\xf6wL\t\xd2\xe34\
+d\x86\xf8\x17\x0eS\x82\xf4hJ\xae\x82\xf2\xff/\xa6\x04\xe9\xbe\x9c\x91\xf2\
+\x17\x12S\x82t-\xfe\xb7\xb3\xf3M\xd5)\x1f\xa6\xe25\x8dcJ\xa4@\xdb\x93\xb5=\
+\xadI\x84\xc2V\x16J*\xd0\x12\x89&\x15\x02\x89"\xd5\x1a+M\x88\xa5\x8d\x14)\
+\xb60\xab\xbc\xbdX\xd0\xa9"\x15\x02\x91\xc6\xc4\x96\xc6\x93\xde\xa4W\xb3~\
+\xfdz\x1c\xc7a\xce\x9c9\x13\xb67\xe6\x8d}!0%$}2\xb2T\xa3\xd2@\x98\xd9^\x04)\
+\x1a\xcd\xffk\xef\xdc\x83\xa3\xaa\xee\x00\xfc\x9d{\xf6\xbd!\xbby@\x1e\xc4@B\
+\x91G\x84\x12\x9fT\xd1RPl3\xad\xb4\x96\xf1Q\x18t\xda\xd2Z\xb1\xd3\x8aN\xad:\
+\xe3L\x9d\xda\x87Z-\xed\xd4>\xa6\xd5"\x91\x82\x03\x96\xfa\xc0X\xa7(T#\x85\
+\x82$D\xb0\x89\x08I\x0c\xe4\x9d\x05\x93\xecn\xf6\xde{\xfa\xc7n\x96DQ\xab\xd3\
+Ml\xef\xf9f2\x9b=\xd9\xdc\xf3\xf8rN\xee9;\xfb\xfb\xc9\xd4g\xc5\x84er\xef\xd6\
+F\xae\xb9\xf7y\xdel\xef?\xed5\xc6\n\x05\xbc\xde;\xc0\xf7\xff\xb0\x93\x1d\x07\
+;pY\xa7\x17\x0eP]]MMM\r\'N\x9c\xe0\xd1G\x1f\xa5\xb1\xb1\x91G\x1ey\x84\xba\
+\xba\xba1k\xef\xc7D\xfa;IJN\xcem\x93\x96\x9e(\x1bw\x1de\xd3\xee\xa3\xe9\xb4\
+\x1b]\t\x17?\xdc\xf2\x1a[_\xf3\xf0\xa7\x1dM\xe3\xbe\xa0>\xb3\xfd ?\xdf.\xb8\
+\xfb\x99\x06\x94|\xff\xdd\x88\x10\x82p8Lqq1S\xa7N%\x1a\x8d\xd2\xd1\xd11fm\
+\xcd\xe8\xf2>\x94\xb0pK\x85e\x08\xa4J~TT\x90\n\xa5-\xe0T<\xae\x91\x03\x94\
+\x9c#B\xd9\x08\x1b,\xe1\xe2\x9fotp\xdd/^%\xe8J\xb0\xf4\xfc2\xfcJ\x91\xebMp\
+\xe3\xe5\xa5\xec>\x1c\xe1\xf3\x17\xccyG\xcd\n\xc5\xc8x\xab#\xeb\x19\xf98\xfc\
+\xddp\xfd\x1fm\xb5\x10(\x96]:\x8b!\xeb0\x8b\xcf\x9d\x82aI\xde/i\xfd0\x15\x15\
+\x15H)Y\xb6l\xd9\xa8\xccM\x99&\xa3\xd2=n\x89\xb2m\xa4\x82\x98ms\xe8\xf8 GZ"\
+\xc4\xf0p\xd6d?3J\xfc\xf8\x94\x1b%-\x94\xad\x88\x9a\x06\xbb\xdf\xec\xa3\xad;\
+BQ\xb6\x97\xca\x99\xc5\xf4\xf4D8\xdc\x97\xc06\x14\t\x95M}k\x07><TL\xf6s\xd5\
+\xa2O\xb0t!L\xce\xf5s\xe8X7\xfdC\x10\xf6IJ\'\xfa\x91\xc2\x83\xcbV\xd4\xb7\
+\xbf\x8d\x95\x18\xa2(7\x8b\xc2\xa0 nx9~r\x80\xba\xd7\xfbPq\x93Y3\x0b(\x0f\
+\xbbp{\rL\xdcH;\x81\xc2`\xa0\x7f\x80H$Bnn.>\x9f\x0f\xc30\xd2\xe1>G\x86\xff\
+\xec\xee\xee&\x18\x0cR\x9a\x93\xc5\xf7\xae\x9e\x9b\\:\x85\xc0\xb6m\x84\x10$\
+\x12\t:::\x08\x04\x83\x84\xc2\xa1d\xfax\x92\xf9\\JJJ\x00(**\xca\xa4\x86w\x91\
+\xf1\x1b9\x05\xf4\x0f\xc6\xf9\xea\xda\xbfSS\xd7\x8de\xc0\x90p\x13\x182\xf8\
+\xe6US\xb8\xe7\xaasp+\x83W\x8e\xf6p\xd3\xaf^\xa6\xfe\x98\x00\x97\xc4P6\xb3\n\
+\x0f\x92\xe3\x1f\xa2\xf6\x88\x0b\x0bA,\xa1\xb8\xe8\xce\xed\x84d\x90\xa3\x0f/\
+e\xf9\x8f\x9f\xa2\xa5g\x90_\x7f\xe7\x12ZZ\x8e\xf1\xe3\'\xdb(\tK\xf6\xfe\xe2J\
+\xbcF\x82\x9e\x98\xe4\xf2;\x9f\xa6\'\xe1\xe1\xe9\xdb?E\xce\xec"\xee\xdf\xba\
+\x97\x9f<\xf9&\x03\xa6@*APD\xf9\xc1\xf2O\xf2\xed\xcf\xcd\xc1\xad\x12\xf4\x0f\
+Fyx\xddz\xfeQ[\x0b@ \x10\xe0\x8a+\xae`\xe3\xc6\x8d\xdcr\xcb-TVV\xb2v\xed\xda\
+t\x1e\xb5\x83\x07\x0f\xb2r\xe5J\x16/^\xccu\xd7]\xc7\xea\xd5\xab\x99?\x7f>\
+\x00555l\xde\xbc\x99h4\x8a\x90\x06\xf3\xe7\xcf\'\x1e\x8f\x8f{\xae\xf5\x8cK7\
+\x0c\x83`\xd0O\xc1\x14\x1f_\xcc\x9d\xcc\xf2\x85g\xd1u\xbc\x9b\x1b\xffX\xc7\
+\xef6\xb7q\xd3\xc2\x99d\x07\x13\\\xff\xc0v\x0e\xf7e1=4\xc8\xf9\xb3\xf2\xd9\
+\xd3\xd0\xca\xea+?M\x7fg;\x86g\x80\x17\x1bM|\x86\xc9\x17..\xa7\xc0\xb6\xf0\t\
+E\xdc\x08\x11w\x05\x10\x18\\\xb3\xe4l~\xba\xad\x87\xa3\'\xa0\xb6\xfe-.\x9dW\
+\xc2\xb6\xbdo\xd0\x13\xf70\xbb\xc4\xcf\xe2Y\x93x\xf8\xa5f\xee\xde|\x84)9n~p\
+\xd5\\ZNJ~\xb4\xe9\x00\xb7\xafo\xe2\xe2\x8aR\xce)\xf4\xf2\xf0\xba\xc7\xa8\
+\xab\xdb\xcf\r7\xdc\xc0\xec\xd9\xb3iiia\xdd\xbau\xa3\xfa$\x84\xe0\xc0\x81\
+\x03,X\xb0\x805k\xd6P\\\\\x9c\xce\xc04\x1c!r\xd7\xae]<\xf6\xd8c,Y\xb2\x84\
+\xcb.\xbb\x8ch<\xc6\x96-[8r\xe4\x08eee\x99\x1e\xf6\xf7e\x0c\xb6l\n\x03\xc1=_\
+\xba\x88\xed\rm\x1c<t\x8c\xde\x84"/\xec\xe7\xf8\x90\xc1\x81\xd6n\xbaO(\x8ev{\
+\xc8\t\x98<{O\x15\x85a7\x83q\x93l\xaf@\x1a\x85\x94\x9c\xd1\xceK\x87^\xc2#\
+\x03\xfc~\xd5\xd9\xf8p\'\xc3v*\x13C\xc5\x10X\x94\xe5x\xf9\xec\xd9a\xb6\xee\
+\xe9e\xfd\xae6\x16W\x96\xf0\x97\xdd\xc7\xb1\xe4\x04\xae\xbfx\n\t\xe9\xa6\xfa\
+o\x87\xb0e\x16\xe7\x9d\x99C\xdc\x90\x14g+\xca\x8b\x0c\xea\x8f\xb9\xa9\xd9\
+\xd7\xcc\x8c\x85S\xa9\xdd\xb3\x8f\xab\xaf\xacb\xc1\x82\x8bP\n\xc2\xe10+V\xac\
+\xe0\xc1\x07\x1f\x1c\xd5\xab\xe2\xe2bV\xadZ\x85\x94\xf2\xb43\xf7\x85\x17^\
+\xa0\xbc\xbc\x9c\xe5\xcb\x97\'\x0b\x84\xe0\xa6\xd5\xab\xf9\xee\xcd7\xff\x7f\
+\xcf\xf4!\xc0m\xc7\xd9\x7f,\xc65\xf7\xee\xe0H\'\xb8U?\x81,\xc9@\xdc\x83\xe9\
+\xf6\x10\x8b\xda\xb4\xf7F\xb1\x85\x8b\x8aBIA\xbe\x17\xaf2\xf1\xf9$\t\xe1B*\
+\x0b\x9f\xa5\x007\xb64\x91J \xb0\x91\x02\xd4p\\8\xe5A!\xf8\xda\xc2)<\xb5\xbb\
+\x87\xa7_\xe9\xe4_\xcbb\xbcX\xdfF\x96p\xb3\xec\x92R\xb0-\x8e\xf5EQd\xb1\xb5\
+\xb6\x93-\xbbZ\x91\xb6A\xc2m\xe25\xfd\x9c8\x99\xa0\xab\xfb8\xb6m2m\xea\x0c\
+\x840Rq\xe2\x14\xd3\xa6M{W\xdfJJJ\x90R\x9eJ\xbe\xf3\x0e\x91]]]\xcc\x9b7/\xbd\
+\x02\x00\xf8\xbc>\x8a\n\n1\xc6\xf9\xc89\xa3\xd2]\x98\xd8\xb8\xf9\xe5\xb6\xfd\
+4\xf5x\xa9\xaa\x94\xfc\xe6[K\x08\x05}|\xee\xee\xed\xbc\xf2F\x84!\xe1\xa6p\
+\x92\x1b0y\xbd\xb5\x9f\xe6\x9e(\xd3s<Db6\xd9~\x13\xd4\x10\x83R"\x94\xc44\x87\
+\x18\xb4%R\xa6\x02\xfa\x8c\xfa\xec\xb8\xc9EsK\x99^P\xc7\xbf\x8e\x0b\xbe\xf1\
+\xd0+D\xd4\x04\xbe|n\x1e\x85\xd9~lC2=\xdb\xc5\xe1>\x0f\xd7.\x9e\xc07/=\x13\
+\x054\xf7\xbd\xcd\xe4\xf0\x04\xf2C\x01\xf2I\xe0\xb6\\tt\xb6\x02\xb3\xd2\x15\
+\xb4\xb5\xb5}\xe8\xbeggg\xd3\xde\xde\x9e~>\x9c\xd2\xa3\xa7\xa7\'}\x037^d|\
+\x9fn+\x85e&\x90v\x82\x81\xb8\xcd\xfe\xd6\x08\x0f=\xf9\x1a{\x9b\xdeF\xe1A\n\
+\x9b/\x9d\x13bj\xbe\x8bn\x95\xc7\xa5w>\xcb\xb2\x07j\xa9\xbc\xf9\x19~\xbb\xa3\
+\x19Sx\xc8\x91&\xca\x10\x0c\xaa +\xee\xdb\xcew\x7f\xb7sD\n\xdcTG\x0cI\x96\
+\x01+?3\x15\xa4E\xed\x1bCH\xcbdY\x1f\x03%\x00\x00\x04\xd0IDAT\xe5%e\xb8\x84\
+\x8d[%\xb8\xfe\xb3sq\xa9A\x9e\xd8\xd5\xc6\xb6=\xcd<U\xd7\xce\xad\xbf\xd9\xcb\
+\xdam\r\x14\x04\r\xb2\xc2!\xce={.[\x9f\xf83\xfb\xf6\xbdJ4\x1a\xa5\xa9\xa9\
+\x89\xea\xea\xeaw\xf5\xeb\x83N\xcf.\xbc\xf0B\x1a\x1a\x1ax\xee\xb9\xe70M\x93X\
+,\xc6\xfa\xf5\xeb\xe9\xed\xed\x1d\xf7\xa8\xd0\x19\x9d\xe9&.\xa4=\xc0\x8d\x97\
+\xcfb\xfb\xfe\x1d\xbc|\xd0`\xf7k;)\x9c\xe4ez\xa1\x97\xa6\xb7\xfa\xb0\x84 \
+\x14\xf0\xf0\xc4\xad\xe7\xb3\xea\xa1W\xd9\xff\x96\xcd\xb6\xbd\'q)\xc9\xa6\
+\x9azV\\0\x95\x0b+\xce\xe0\x82i\r\xec9<\xc0_\xeb\x13\x84}p\xff7@*\x13\xc9\
+\x10B\xd9\x98H\\\xca\xe4\xdaE\xb3\xb8\xf7\x89\x06N\x0c)\xce\xcc7X4\xa7\x08\
+\x13\x85\xa1L\xbe\xb0\xa0\x9c\xfb#\x11\xee~\xbc\x99\x1f\xfe\xe5\x18R\xc4\xf0\
+\xd9\x01\xcas\xb3\xf0\xba<\xc4%|\xed\xeb_\xe1\x0f\xbf\xdf\xc0\xcf~\xf6\x00B\
+\x80\xcb\xe5b\xe9\xd2\xa5\xb4\xb6\xb6~\xa8\xbe/Z\xb4\x88\xf6\xf6v\xaa\xab\
+\xab\xd9\xb0a\x03J)\xca\xcb\xcb)--\xcd\xd0h\xff\xe7\x88\xfb\xee\xbbO\xed\xdb\
+\xb7\x8f\r\x1b6d\xa8\x8a\xe4AIg\x7f\x94\x7f4v"%\xcc\x9f9\x99\x13o\xc7\xb0\
+\x12\t&\x86\xfc\x84\xfc\x12\x81"\x8a\x87\xfa\xc3]\xb4uE\x99\x98\xebb\xde\'\
+\x8a\xc8\x92\n0\x184m\xfe\xd9\xf4\x16]\'\xe3\x14\xe5eq^y!\xcd]\'1M\x8b\xe2\
+\x9c \xa1\x80\x07\x10\xd86\x1c\xef:I\x9fm3\xc1\xe7\xe1\x8c\x9c@*\xdbYre\xb0\
+\x95\xa2\xaf?N\xed\x9b\x9d\x08s\x889\xe5\xc5\x14\x85\xfdxD\xf2\x88F\xd9\x16\
+\xb6\xad\xe8\xeb\x8b\x10\x89\xf4QPP@,\x16c\xcd\x9a5\xdcv\xdbmTTT\x10\x89D\
+\x10B\x10\n\x85\xd2\xe93-\xcb\xa2\xb3\xb3\x93\xec\xecl\x02\x81@z6wvv\xd2\xda\
+\xdaJ0\x18d\xfa\xf4\xe9\xf4\xf5\xf5!\xa5$\'\'\'C\xe3\xfd\xde\x18\xd2%l\xcbTc\
+p\xf7\x9e<\x19\x9b\x18p\xf1\xf9O\x16\xa7\x8a\x14\xd9yA\x0c\x14\x86\x12\x88T\
+\x1e\x14\xbf\xa18\xaf<\x8bs\xa7\x05\xb0U \x95t/9\xa8\x01\x15\xe7\xe2\x99%\
+\xd8*\xf9\xff\x1baQ61\x94\xec\xcc\x88\xc4|B(\x8a&N\xa00u\xf0f\xa4\xda\x90F)r\
+\x82^\xaa\xe6\x94\x80R\x18\xc2D\xa88\x08/\x02\xc1\xbeW\xf7STTDaa!\xf9\xf9y(\
+\xa5x\xfe\xf9\xe7\xf1x<\x94\x95\x95\xa5\x8fP\x93u\x9d\xba\xaea\x18\x14\x14\
+\x14\xbck\xe9\xce\xcf\xcf\'???\xfd\xfa\xbc\xbc\xbc\xff\xe6\xe0~$\xc6\xec]6e\
+\xb8\x91*\x991\xcd\x12.\xdc\xe9S\xf4d\xf8\xa6\x84\x91\xcc\xadh\x08\x1f\x86\r\
+R(\x10F\xfa8U\xb9\xfd(\x92\xe5"\x15\xf2\'\x95\x03\x99tbZ\x14B$\xbfP*\x19\xf1\
+\x11c\xd4\x91\xec\xf0\xccD\x812\x04\n\x0f\x16"\x99\xed\xcd\xb6\xd9\xb4i\x13\
+\xbd\xbd\xbdTVV\x92\x97\x97Gss3\r\r\r\xac\\\xb9\x12\xbf\xdf\xff\x9e\x99\x1a\
+\xde\xab<]\xdf\xc7\x881\x93.\x95\x8d\x9dz\xdf\\*\x1b\x84\x1c\x95\x1e\xd2\x95\
+\n\xa0;\x1c\xcbMa`\xa5~,\xd5p\x16\xd2\xe1?\x04\x99\x0e\xe4\x0b\xc9\xcc\xab\
+\xa76F\xc9P}\xb6p3|\xf9\xf7\xbam\x1a.\x97\xa9\xe5\xdf0\x0c\xee\xba\xeb.v\xee\
+\xdc\xc9\xa1C\x87hlld\xd2\xa4I\xdcq\xc7\x1d\xcc\x981c\xdco\xc0\xfe[dT\xba\
+\xb2-\x84!1\x01\x97\x10\xa7\xf6\xa7\xa3\x1fF?I\r\xac\x18\xd9\xb8\xf4`\'\x15\
+\xa6\xc3{\xa5\x8aO\t\x1f\xfe-\xf5\xc1\xdb\x92\x11\xf5\x8c$++\x8b\xaa\xaa*\
+\xaa\xaa\xaa>\xe8\n\xff\xb3dt\xed\xb1\x85L\x07\xe0\x1d=\xbc\x1fe\xc6\xbc\xdf\
+\x9c=\xddk?\xec\xb5\x9dCF\xa5\'l\xc5\xe8,\xb1\x9a\x8f\x03\x19]\xde}\xf2t\xeb\
+\xb8f\xbc\xf9\xf8\xddZj2\x8e\x96\xee@\xb4t\x07\xa2\xa5;\x10-\xdd\x81h\xe9\
+\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\xa2\xa5;\x10-\xdd\x81h\
+\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\xa2\xa5;\x10-\xdd\
+\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\xa2\xa5;\x10-\
+\xdd\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\xa2\xa5;\
+\x10-\xdd\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\xa2\
+\xa5;\x10-\xdd\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\x07\
+\xa2\xa5;\x10-\xdd\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x96\xee@\xb4t\
+\x07\xa2\xa5;\x10-\xdd\x81h\xe9\x0eDKw Z\xba\x03\xd1\xd2\x1d\x88\x0b`\xe3\
+\xa6\xc7\xc5\xc6M\x8f\x8fw[4c\x84PJ\xa9\xf1n\x84fl\xf97\x87\xba&\x9e\x12\xca\
+GT\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getWizardDataOld():
+    return \
+'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00t\x00\x00\x01\x04\x08\x06\
+\x00\x00\x00\xf9\xcf\x10R\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
+\x00\x0f\xdfIDATx\x9c\xed]K\xb6\xe3(\x0cU\xf5\xa9MU\x86\xd9\xd6\xcb\xd0\xd9\
+\x96\x87\xaeeU\x0f\xfc\t\x06\x01\x12\x16\x1f+\xba\xe7\xf4\xe9\xd4\xb3-d\xf0E\
+ \t\xf8\xb5,\xcb\xbf\xc7c\x06\xc3\x05L\x00\xf0\xfa\xd9~\xbf\xcf\xbf\x81p\r\
+\xbb\xaf\xf0\xdao\xb1\x97\xfa6L\xdb\xff_?\x00/\xb8\xd6@\xdc\x0f q\xed?\x89w\
+\xfb\x0e\xfc\xac\x8d8m\xbf\x07\x8514\x8b\x9dU\xefj\xac\x92\xecr\x8d\xa1\x01~\
+\xe0`\xe3\xc0L\x8c\xc1\x18z\xc2O5\xe6$\xaf\t\xb2\xfd\xd7:\xca\xfd\xc3\x7fw\
+\xc3\x90\xd8\x18\xfa\xee\xabE\x17\x10\xd8X\xc2\x9c\xa3.\x192\xa5\xd8\x0eoc\
+\xa86|\x19C\xa5F\x9f\x992\xc8\xacz\xe7\x9fs\xef#80\x8c\xa1\xca\xa0\x9f\xa1\
+\xaeG\xe7\xca\xe83\xc9J\x80\x13\xfb\xb3\xb6\x17\xa9o\x9f}\xae^~\xd9\x13Du6\
+\x86*\x83b\x86\xd6\x9a\x1f\x12\xd8\x95\x1d\x01#\xe00t\x7f?c\xa8~\xe8bh\xec+\
+\xdf\xff\xdd\xcb\xc7J\xaa_\xc2(\x97\xf0\x9c1T\x19\x140\xb4\xd4\xff\x1a{ga\
+\x1fkvt\xbc\x81mC\x11\xbd\xcd\x86\xea\xc3\x8d\x19\xca\xf4\xbf\x96\xbec\x8a9~\
+\xd9\xa5\xd7\x04\xed\xfeM\xc3g?\xf1\x97E_\xfcBQ~z\xc9!s\xc0\xc1\xd7=\x03\xdc\
+?\xe8_\'\x00\x80\xe9\r\xcb\xf3y8\x87\xd6,\x03\x81"_ #\xa7\x01~\xdd+\xeb/1\
+\x00\xda\x1a\xf31;\xef"\xda\x08\xa5\x83/\xc25A\xb6\xdf\x88\xa183\xa3\xb8\t\
+\xa3\xa4q\x13\x1b\x9a\xff\xb2wv.\xcf\'\x00\x00<@\xb0\xd7\x99\x00\xf2Ib\x8e\
+\x9e\x1dm\xe8\r\x1a\x94\xc9\xcc*\xe5gF\xc8Go\xb0\xdb\xec\xb7\xf7\xf7\x8a\xf0\
+\xca\x1b\xdf\x86\xba\xe1/\x00\xba\r\x95\xaaL\xd2\xb4\xa5dJT\xc7I\xa1\xc6\xb1\
+\xb0,\x7f\xcf]\xae\x82w*\xc1\xc0\x8e\x05\xaa=\xdau\x7fz\xcfK\xbc\x13\x819Ruw\
+\xd9\xf5\xb7B\rC\xc3.\xb7\xb7\xed\xed\x83a\x18:M\x00/w\xa4\x08\x90\x19\xe5\
+\xe5$^x\xa7\t\xf0\x8f\xe3\x02s\xf2(\r\x9fyX\x96\xe5\x1f\xc0\xbf\xae\xff-\xcb\
+\xf2oY\x96\x7f0M\x97d\xc04}d]\xd4g\x97\xd5\xbbn\xb8\xff\xf5g\xe8\x04p\xb2\
+\x7f\xc1\xc8\x8e\xcaL\x01\x1b\xba13\xfc\xbb\x90\xa3?S\xb6\x1e\x1b\xba\xbd\
+\xc01B\x9dg\xbe\r\x14\xb0\xa1\xfbHy\xd7\xa5H\x8f\xce\xe8\xcfP\x00\x80\x17\
+\xc0\xb2<\xcf\x95\t\xc0\x9a\x87\x85\x9e"\xe6;M\x00\x8f9\xf2Q]\x9es\xd2\xca\
+\xd7\xc3\xd0\x1d\x17\x98Z<\x0f\xf5\x98\xbd<\x9f\xb7\x9e\xc3\x8e\xe1)r\xbcAh\
+\xb7\x07\x90\xffJ\x83\x91)\xad\xe8\xbdg8}D\x9b.\xe2\xb1\xd5$dF\xb9\xbf\x0fa=\
+\xe1T\xd2\xe3\xf1\x07`\xc2>\xb0\x9c\x8e\xfeKg\xee\xdf\x98\xe9be\xa6\xc3N\xbf\
+w\xa8mO\xa3\xe5\xd1\xcb\xed\xcc\xd0\xb87(\xe8B\xe79mC}\xa6e\xde)\xb0\xd9\xd8\
+\x9c\xb3\t37\xa8\xb4\xa1>\x1c\x16\xe5F\xafd\x1b\xca\x90yG\xf4c(i\x11\xd1\xe7\
+\xbe,S\x8964`\xe6c\xae\x18Q\xe1@\xc6\x86v\xecr\t\xe1\xa3\xedE\xd0F\xf0@\xear\
+\xa3\x1f\x07!5\xe4&]n\x9f\x14\x94)\x7f\x8b\xfbU\xfa\x8d\xb3,O\xa2\x8cOy\xcb\
+\xe2\x0f\x80\xe6S\x0f\x90\xc4\x8d\xd2Y\xfad, \xc1\xe9\xe37\x00\xfeUb\xce\x87\
+\xe9\xd3\xfd\xce\x1b;\x1f\xf3\x1c\xb4Q\xe0t8%\x92%\xc2t\xe4@\x80\x00\x92\td\
+\xeciKK\x10\x06 \x91\n<\xec\x1d\xac\r\xe8\xde\xb7Fjf\xf4\xf1\xd9i@\xb6y\xb9\
+\x11;\x01z\xd8P,\xa5\x84i\xab>\xa16F\xb1\xd3&\x9a\xb1\x1a\xba\xcd`\xc8\xd3c/\
+{\xd7\x85iC\x1b3T\xa6\x82\xb8\x8d\x19}\x86c\x87o\x82\xb6\x0c\x1dbz\xe0"7\xd2\
+n\xac\x8b\xdc\xb4eP\xc7\x82\x81\x8d\xba\xe13g\xde\xe7\x8eN\x01\x00u\xbb\x91\
+\xaf!\xdd\xe7n#\x97\xe7\xf3\x18\x04\xf9\xdd\xec>u\xc1d^\xd1K\xa4\x87\xbb\x83\
+\xeboY\xfe\x02@\xbc\xc2J\xaf\xa1\xfaR\xc2`Xx\x0e\xe2\x8dG\xd6k \xd7aU\x86\
+\xce3\xc0\x13K\xe9\xb8\x0c\x9a\xbeG(n\xfb\xc2\'\xa85\x0b\x11J\x195\x1b\xfa\
+\x01\x1a\x9d\x81\xfb\x07\xac\xb9h\x93\x82\x92\xdd\x9dD\xa2\x90xO\xe03uE\xca[\
+\xd5!%\xe7\x0e6\xb4)\\\x1b\xba\xc5S\x01\x10;9\x90\xbd\xab\x81\x06\x0c\xa5,i\
+\xe0I\xc4<E\xae\xbf\xf6\x01ox<\xe0\x94\x96\xe9^\xcb\xef2f\x0cm\x03/8=\x01\
+\xc0k\xab\x04\xd7\x86\xba\xef\x13\x1di+ej}\x86\xa6|\xa5\x00\xac\xaf/\x98\x96\
+\xcc3\xec\xba\xef)\x98\xeb\xdf\\g<\x04\xc1\xef5R\x93H\xe8\xee\xe2\x90\xff\
+\xa6Q.%m\xc4\xb3\xa1>\xfcl\xc2C\x962\xa66\xf0\xe5\xa6l(u>\xc9\xcbX\x88\xbd\
+\x0f\xe6):\x1a\xb5e\xec\x13\xc3W\xd8\xd0\xc2$\xb1\xd4\xfbhgj]\x86\xe6\x12\
+\xc1R_\x1ef\xf7Rz\xba\xf7g\xf6\x84\x8fOi\xb2oT\x11w\xc8XH\xae"K+\x9aL\x1b\
+\xc9\xdc\x9f\xdc\x01ez\xc3\xe3\xf1\x13\xb0?\xfb\\m\xa4\xd2`\x86H\x12\xcb\x06\
+\x8f#\x8d\x99J\xe8\x92\x04:pb&\x9f\r\x88z\x0c\xcd%\x82E\x1a\x14\x9f\x9a\xe4q\
+\xb0,w\xbf\xa3\x8b\xcb\xd4\xf3\xca\xb7\x0eL\x15J\x83\x19g\'\xb1V\xcc\xf4\xa1\
+\x8c\xa9\xf5\x18\xca\\\x89}5h\xcc\xb1\xa1\xbe.\x8f\xc7\x0f,\xcb\xfa\xa7X\x9a\
+hu\x0coCSp\x15\x8c1\xb31\xa2]\xf5\xcd\x98Zq\xdaB\x0bO\x1d\xe9\x95p1\x9d\x834\
+mI\x05\x08V\xbd\\\xe7\x03u\xe5\x9b\x0c\x84RIG\xd8\xe9c\xdaw\x1c\x11\xda\x05\
+\xe5\xb2N\xae>7\xdb\r\xa5\x9es>u8\x8d\xf7\xb5\xbbY\xef\xe5\xba\xb8\xddv|J\
+\xb4\x16\x98\xb1U/\x00X\xce\x8f\xe2ArA|\x85\xeb\x8f\x83\x8cs\xbe\x04\xa8Gi\
+\xf0\xba\xaa\x18>\xeb\x99\xe2\x11\x93\xcf\x0b\xb2?\x1e\xe1\x80\xad\x1eS\xbf)\
+|F\x00\xd59_*\x1b\xe0\x1e\xe9,}l(\xd4\xb8\x96\xd8\x8dl\x7f.\x1adO\xb1\xe3<\
+\xe2=\xbf\xa3SoW\xde\x87\xabW\xe2\x9a\x1a\x86\xd6\xb0\xa1>\xee\x90\xce\xa2\
+\x86\xa1\'O\x910C\xb3\xbe\xdf\xacW\xcc\x18\xcaFM\x1b\x1a+\x0b`\xbc y\xa7Q.\
+\x88_;G[\xfe\xc8\xf4\x04\x91\xb2\xe3\xbe\xdf\xb7HOp\xe5\x9a\x1a\x86\xb6\xb0\
+\xa1>Fdj=_nO\x1b\xeaV\xaa\xa0\r\xc5\xae%\xa74fC\xcb\xd1\xd2\x86\xc6\xca\x06\
+\x88l3\xd7\x10j<E\xb1D\xeb@\'\x00\x99\xa8\x86[\xb6\xe3Q:MiX~XQOQ\xe3.\xb7\
+\x82\x83\x9b\x92\x97Kv\xce\x97\x82\x9b\xa9\xe8\xeb&\xa0\xd78)(\x1a\x80T|\xeb\
+t\x96\xee\x01n10\xf2r\xa5\xbb\\\x1fh\x86~\xb6\x8ee\xf4\xea\xc4\xd0\xfe\x13\
+\xf0\x9a\x88-\xd5h\xc1\xd4\x8e\xd3\x16\xd9\xe2H6\x14\xd3\x0b@\xce\x86":\x01\
+\x103\xf4\xa5\xf4\xbaSz\x059m\xa4\xb7.\x88^\xad\x0e\xf7i\xb2OQ|\x92]\xa7\xd8\
+\xf4\xfb\xf0l\x95\x9f\xc4\x06\xc0\xdf\x8a\'\x94\xf9F\xb6\xaa\x13\x9d\xb6\x98\
+c!\x8a)\\\x01\'\xb1\x91V-\xa7C\xe5\x15\xdc97\x9ad\xb9\x84$1\x80\xa66\xd4\xc7\
+\xb1\xcd\xab{*\xaf\xb4^_mC\xb74M\xea}w\xa8\xab\xbe{,t\xb4\xa1k\xf7\xb7\xfe\
+\xe5\x93\xf8\x95\xef5p\xfb\'\x01\xb3\xa1\'\xb0l\xe8Vq\xc1\xfaP\xe2\xca\xef\
+\x11\x02\xd91\xdcj\x17\x94\x14h\xcey8<J\xeb3\xfe`\xe5\x0f\xc4\xea\xe2t\xd0]\
+\x95\xdeE\x86\xa1\xf5\x8f\xca:\xb2\xd1\xbd2\xc4\xbfr\xf7\xa5#\xb2\xa7L\xc5\
+\xb8\x83\xb7\xa4\xfcD\x19W\x10\xad#zY\xb7\xdcI\x0c\x83\xbf\x93X\x80\x083\xf7\
+\x7f\x1f\xbf\x01\x00\xab\x0f<1\xec\xba\xde\xae~\x12\xa3\xdc6\x87\xd9\xb9_^5\
+\xa6\xfeu~\x9fe\x9e\xec\x9fS\xee\x03>\x19\x07\x07",]7\xb0\xf2\xbbr\xc1zs\x1b\
+-U_\x194\xdas\x9e\x90J!\x19\x8b\xf4\xd6\x9f\x02D\x96\xfa;i$\xd4\xc8\x08\xd9g\
+\xcc\xc6]l\xe8\x8e\x143EX\x1a\xda\xd0\x143\xddc%\xc3#.S\xfa|\x18\xbd,\xc2^)\
+\x01\x1b\xda\xf0T\x88:\xe9\x1f;\x02\xe6 \xe7\x9c\x9dYuf\x84\x9f\xec\x15\xed1\
+\xbc\xac\x84u\x03\xc9K\xaa\x1fr%z\xafv\r\xda!\xfd\x03 \x11d\xc6\xc2{\xae\xdf\
+\x96\xb0\xc9\xd5!_dpt\xb7\x007\xe1\xc8\xe5\x9ae\xb3?\xda\x94>\x03\x1f\x9f\
+\xd5\xf8\xa8\xacL\xae\xea\x85n7v\x96v|\xf0\x12\xa6\xc8\xe0\x03\xa7H\x81N\x97\
+>\xcf\xf3\xf5nWe\x92\x984K\x93\xcc\x0c?\x1e\xea&W\xbb\xec\x1d\xcf\xe78\xfb\
+\x1au8\x106\x97\x01^\xc8\xd2\xa2\x14J<\xbcwrR\x10\xa60\xf4\xf22\xba\xdc\xca\
+\x86\x92!0\x85!\xdb\xcc|E\xf9K\xf2]\xf4;\x8c>\x8e>G6WH \xdbSE\xd8\xd3\x08,x\
+\xe0\xd9\xd3\x94L?\x11\xacx\xc0d\xbb\xa0T\x86\x9fz\x92r~\x9c\xa6;}\xeb\xb2\
+\xcdA<\x01RI\xd8\xd0$\x1d$\xd0\x05c\xc7\xc9\xcfK\xac\xa3b\xdd-\xc0]\x1fN |\
+\x9d\x9a\xe0,EW\x9fu\n\x82wb\xe8\x8e6\xa15\xb2\x1eA\xd9\x9f\xbf\xafS\x13\\\
+\x9f\xfd(\x913\x98uj6\xb4\r\xc2HL>M\xa5\xe7\x8ec\x9d\x19\n\xd0$\xb4F\xd5\x03\
+);\x9c\x00\xa4\xea\xea3\xc5Y\x96\xbf\xcci\x8d\xd9\xd0f\x08#1\xf1\x14\x97p\
+\xba\xd3\xd6\x96\xf6\x99\x87\xfa\xc8Eb\x00\xfa\xd8Rdn\nP+\x12c\x0cm\x0b\xc6\
+\xbc\xf4\xc4\xe8\xc6\x0c\x1d\xc0\x86\xee\xc8DbZ\xd8\xd2\x84\x07\x8b\x93$v$p?\
+\x9f0S\x13\xb3m\x94\xdb\x01\xce\xbc\x94\xe5=j\xc8\xd4\x81\x18\xbac\x00/R,9\
+\x1c\x08\xe9\xa2\xde}\xc7\xefl\x1d\x9b\r\xed\x02\xf4\xf0\xf6\xad\x01\xa6\xe9\
+\xbd\xeen\x8e\x06\xd9\xdb\xd4\xf1\x80\x0c\x05\xc8/C\x84\xcaL\x8d{\xb0\xfc\
+\x13\x9c\x0e\xf6m\xd1\x9ex^Sf&q\xbb$1\x0er+\xbf\x8f\x7f7\xc8\xf8\xc7\xca\x06\
+b\x16>+\xa4\xa66\xc0\r\xf4/\xb2j\xdaG\xa2\x12\t\x03\x9c\xe3\x98\xaf\xc6\tec2\
+tG\xac\x1b\xda\xff-\x90\\\x96F\xa6l\x7f$\xbb\xab\\\xd2\x88*\x93\xc4|tgj\xe6C\
+y\xfd\xc0<\xcf\'6\xd6Y\x0cL\xc7\xd8\x0c\xdd\x91\xfaz\x01\xea2\x95T\xb6D\xb9\
+\x9am\xa8\x0f\x16S\x85\'\xf0\xd4\x01M\xea\xda\x04\x1f\xdd\xdc\xdf\xfb\xbf\
+\xb3:\xd3\x9f\xbb\x07CwP6U\xae\xe5\xcc\'m\xe8\xec2\x89\xe0\xca\x0ct\xberm\
+\xfdm\x8e\x05e8;\x16Ze\x7fK-\x1b 1\xe0bY~\xb9{y"\xac\x8a\xe9\\~\xcd\x18\xaa\
+\x0c+C\'\xfa(\xaa*\xd8l*`\x87T"\xb4 \xab$\xd9n\x0cU\x86\xb1\x18\x8a\x813m\
+\xe0\xb0#%7\xbb\xb7\xd2\x85k\xa9\xa30I\xf3P\xb3\xa1_\x85\x0fC\xfd/\x96\xb2\
+\x82\xd9\xbf\xe7\x85\xfc\x8d\xf2\x1c\x05W\xd28\xf6\x7f\xc7\xae\x1d\x0c \x04\
+\xd7\xd9\xd7\xa8\xbd\xdf\x05O\x913\xfa6\x86*C\x18\xe0\xe6\xec-\x801\xb4DN\
+\xee~\xca\xf3\x01\xc6\x9a\x1f\x92X\xc6\x1d\x99#\xe9\xaf\xc6Pe\xa0\x8drs_H\
+\xea\xbe\xda\xde\'N6\x80\xc4\xfc\x90\xc2\x96\xa2\xb90\xc7\x86\xc6\xdf\xc7\
+\x18\xaa\x0cc2\xb4D\x0e\'\xc4&bC\x99\xf21\x99\xfe\x98#\xcb\xe8\xfc(|e(g;\x97\
+\xda\xc8M\xf8K\x9f\xc5d\x89\xb9\xe4<B\x90\xd6\xeap\xc2mo\\&"\x7f\x9c\x007%\
+\xa1*w\xcf\x11\x04&\x96\'\x96\xe1\xf0\xe3\x94\xfd\x93\xb9\x17y\x8e\x12\xe0&\
+\xe27\xa3\xf4q\x90s`\xb0\xf0n4mI\\\x13t1\x86\x0c\xad\x99\xe4\x94b\x18\xb7\
+\x81r\xb2X\xf2\xde\xb4\x1e\xe2\x0683\xf4\xe5\xfd\x1f\xe0:\x13nWIu\x98\x93\
+\xbc&\xc8\xf6|\x97\xcb\x19\xa4P\x1b/\xd6eN\x0c\x19~\x991y\x1c\xbdNpG\x97o\
+\xb8\xcb\x11\x99\xf7\xb4\xa1]\xb07p\xa1\x93"u\xcd\x02\xdc\x86\x18\xae\xad>+\
+\xedrc\xcf\x97\xca\xa1\xc8ma\xcbK\x03\xe3\x82l7\x86*C_\x86\xc6\xe4\\\x91\x95\
+\x92)!wp\x18C\x95a\x0c\x86b\xb2\xae\xca\x8b\xc9\x94\x90;0\x8c\xa1\xca06C\xaf\
+\xca\xac)wP\x18C\x95a\x1c\x86b\xf2F\x97; \x8c\xa1\xca0\x16C1\x995\xe5J\xc9\
+\x1e\x08\xc6Pe\xf8\x1e\x86\xd6\x96=\x08\xc6\xc9)JA*\xdd\xa4G\xdepc\x8c\xd7\
+\xa0\xca\x18\xd3\x1a\xe35h\x0c5\x99\xa4\x88\xa5c6h\xedD5\xc5\x18\xb3A{0F\tK\
+\xc7lP\xe5,\xaa\x891\x1b\xb46\x14\x7f0\xe6XP\x86\xf1\x1c\x0b1\xd9w\x93\xdf\t\
+\xc6Pe\x18\x97\xa1\x98|\xc92\x8c\xa1\x86;\xc0\x18ZCvG\x18C\x95\xc1\x18ZCvG\
+\x18C\x95a\xfc\xdd8[B\x01C\xd7\x06}\xddc1\xab!\x0fc\xa8\x0b\x05\x0c\xfdN\xe7\
+|\x0c\nBh[\x97[\xf8\xb4\x8dr\x87\x831\xd4\x85\x02\x86\xde\xabAk3\xc8\x18Z\
+\x11\n\xd8\xd2\x03\xe36\xa8\xa1\x08\xd6\xa0\xca`\xae?e\x18\xd79\xdfz\xca")\
+\xbf#\x8c\xa1\xca`\x0c\x95\x96\xdd\x19\xc6Pe\x18\x93\xa15m\x9cbv\x02\x18C\
+\xd5\xe1>\x0c\xad%[\x11;\x01\x8c\xa1\xea0\x1eCmt{\t\xc6Pe\xf8\x1e\x86*\xf6\
+\x0e\xb90\x86*\xc3X\x0cm\xc9Ne\xcc\xdc1N\xf8\xacV@\xfb\xcb\x02\xe5\xe34(\x86\
+Z\xbbp*\xc6\xd8\rZ\x0bJ\xbb[\x80Q\x1a\xd4\xf6\x9b\x17\xc3\x18\rj\x10C\xff\
+\x06\xada\xe7Fd\xe7~\xf0\xeb\xe9\xe0\xd8\x9f\xf0wp\x8d\x87\xbe\x87\xd9\xd5\
+\xa8\xf8a\x1a\xd3;\x16\xeb\x05\x02\x87\xd9\xe5\xa7\x97\xe6XP\x86\xcf\xea\xb3\
+\x92/\xf8\x8acA\x9aI\xa9\xee\xa9*;\x079\x10v{Gc\xa82\x9c\xd7\x87^=.\x92\xfa\
+\xbc$;\x9b3\x93z \xec\xfb\xfc\xcc\xe9\x1a\xac\xd7\x83\xe3)\x1d\xf9\xe0\\\xc3\
+\x8e\xd0\xb6\x03a\xbf\x03\xe3\xac\xe0\x96\xb0\xbf%\xb2(eQl!:\x02\xc5X\xec1\
+\xd7gm\xecZt\x9cs\xd6\xc5\x18\xaa\x0cc0tHv\x96\x8cV==\x83\xe7"e\xc4X\xee\xcb\
+\xc0\x9e\xf7\xf42\x86*\x03\x9d\xa1\x943O\xb0\xd1\x18\x17\xa5g\xabH2\x13 2\
+\x92\x8d<\x120)g;\xbd\xe7r\x0c>d@\xa2\x97X\x7f\x1bC\x95\xa1\x1eC\xb1\xeb\x92\
+\x10\x9fcF\x18\xc0\xb5\xef\xcd=E\xe7\xb63\x86*\x03\x9e$F\xf5\xe4\xc4\x18\xca\
+\xf1\xdepX\\\xc3\xf3\x83\xb2\x8a9\xeaGmhLF\xc1\x88\xd9/\xcb}\xce\xbboe\xe8<\
+\xd3\x074\xa9\xfb^\x8cgs\xe5\xe4\x9e\x15A\xda\xd1\xcd\x92S\xd4Ug\xa6-\xb1g\
+\x0e=\xc3\xe7>\xf1Pj\xe5\xa6\xee\xcb\xc9(\xb1\xa9\xb5"%SD\xb6DyU\xa3;o8\x1au\
+\x87\xf3.\x1f\x86\x8e\x86\xea\x01\xe9RVyp\xbb@\x00\x9e\x9c\xec\xb4\x85\xfa\
+\xdc\xa7\x8b\xef\x9f\x82\xe2\xe3\x05m\x1a\x13+\xb7\x9b\x1c&b>]\x18\x89\xa1\
+\xcdRD\xa8\x8eu\x02\xb2\xae9\xa2.\xc5z\x84\x8e\x8f\xf3n\x9c\xad\x12\x93\x87J\
+\xa5\x1c \xd2T\x0c\xdf\x9e\x1e\xbe\\\x8f\xa1\xb5\x1a\xb6wC^\xb1w(\x84\xa6;\
+\xa5\t\x06\xae\x0c\x00\x0bpk\xc4\xb5\xd5gw\xc2e{\xe7B\xca\x0e_\xb5\xa1\xa1.\
+\xc6Pe\xf8\x0e\x86J\xb8\xf7\x0ePSCJ\xf4*\x94\x03fC\xd5\xe2\x0b\x18\x9a\xf6}\
+\xb2Q\x93\xed\x02z\x19C\x95\xe1\x0b\x19z\xf1]\xc5\xec\xde\xa6\x9b1\xd4\x90\
+\x82r\x86\nET0\x99\x83\xb2\xdd\x18\xaa\x0c\xba\x19*\xea\x1d\x82l\xfa\x07\x1f\
+\x826t\x93g\x0cU\x06\xdd\x0c\xbd\x1a\r\xc1\xe4\x01\x0cjCWyx\xf8L\x0b\xbe\xae\
+\xcb\x1d1\x05E\n\xe2;\x85"\xe9&\x03B1C\x89\xebR\xa8`\xe5\xde2eJ\xe9\x08\x9a\
+\x19Z\x1dc\x8e;\xf42\x14M7a\xae!\xe1\xaeQ)\xbd&\xb1\x0b\xcav\xcd\x18\xaa\x0c\
+\xd6\xa0\xca`\x8e\x05eP\xecX\xa8=\xca\xbd(\xcf\x95)8\xca5\x86*\x83^\x86\xfa\
+\xa3\\\t\xb7_\xd5P\x1c\x80D;\x18C\x95\xe17\x00\xc0\xd4{\x9f"\x83\x18\xfe\x07\
+\xd2\x8b\x12\xe7\xc3\x8c\xd4\xb6\x00\x00\x00\x00IEND\xaeB`\x82' 
+
+def getWizardBitmap():
+    return BitmapFromImage(getWizardImage())
+
+
+def getWizardImage():
+    stream = cStringIO.StringIO(getWizardDataOld()) # NOTE: This reverts us to the bitmap Peter likes.
+    return ImageFromStream(stream)
+
diff --git a/wxPython/samples/ide/activegrid/tool/XmlEditor.py b/wxPython/samples/ide/activegrid/tool/XmlEditor.py
new file mode 100644 (file)
index 0000000..f82ed7d
--- /dev/null
@@ -0,0 +1,165 @@
+#----------------------------------------------------------------------------
+# Name:         XmlEditor.py
+# Purpose:      Abstract Code Editor for pydocview tbat uses the Styled Text Control
+#
+# Author:       Peter Yared
+#
+# Created:      8/15/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+
+import wx
+import string
+import STCTextEditor
+import CodeEditor
+
+
+class XmlDocument(CodeEditor.CodeDocument):
+
+    pass
+
+
+class XmlView(CodeEditor.CodeView):
+
+
+    def GetCtrlClass(self):
+        """ Used in split window to instantiate new instances """
+        return XmlCtrl
+
+
+    def GetAutoCompleteHint(self):
+        pos = self.GetCtrl().GetCurrentPos()
+        if pos == 0:
+            return None, None
+            
+        validLetters = string.letters + string.digits + '_:'
+        word = ''
+        while (True):
+            pos = pos - 1
+            if pos < 0:
+                break
+            char = chr(self.GetCtrl().GetCharAt(pos))
+            if char not in validLetters:
+                break
+            word = char + word
+            
+        return None, word
+
+
+    def GetAutoCompleteDefaultKeywords(self):
+        return XMLKEYWORDS
+
+
+class XmlService(CodeEditor.CodeService):
+
+
+    def __init__(self):
+        CodeEditor.CodeService.__init__(self)
+
+
+class XmlCtrl(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_XML)
+        self.SetProperty("fold.html", "1")
+
+
+    def GetMatchingBraces(self):
+        return "<>[]{}()"
+
+
+    def CanWordWrap(self):
+        return True
+
+
+    def SetViewDefaults(self):
+        CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Xml", hasWordWrap = True, hasTabs = True)
+
+
+    def GetFontAndColorFromConfig(self):
+        return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Xml")
+
+
+    def UpdateStyles(self):
+        CodeEditor.CodeCtrl.UpdateStyles(self)
+        
+        if not self.GetFont():
+            return
+
+        faces = { 'font' : self.GetFont().GetFaceName(),
+                  'size' : self.GetFont().GetPointSize(),
+                  'size2': self.GetFont().GetPointSize() - 2,
+                  'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue())
+                  }
+
+        # White space
+        self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces)
+        # Comment
+        self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces)
+        # Number
+        self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces)
+        # String
+        self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces)
+        # Tag
+        self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+        # Attributes
+        self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces)
+
+
+class XmlOptionsPanel(STCTextEditor.TextOptionsPanel):
+
+    def __init__(self, parent, id):
+        STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Xml", label = "XML", hasWordWrap = True, hasTabs = True)
+
+
+XMLKEYWORDS = [
+        "ag:connectionstring", "ag:datasource", "ag:editorBounds", "ag:label", "ag:name", "ag:shortLabel", "ag:type",
+        "element", "fractionDigits", "length", "minOccurs", "name", "objtype", "refer", "schema", "type", "xpath", "xmlns",
+        "xs:complexType", "xs:element", "xs:enumeration", "xs:field", "xs:key", "xs:keyref", "xs:schema", "xs:selector"
+    ]
+
+
+#----------------------------------------------------------------------------
+# Icon Bitmaps - generated by encode_bitmaps.py
+#----------------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+from wx import EmptyIcon
+import cStringIO
+
+
+def getXMLData():
+    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\x01\x18IDAT8\x8d\xed\x92=N\xc3P\x0c\xc7\x7f~\xc9K\xd2\xa0\x16\xa9\xdc\
+\x84+\xf4\x06\x1c\xa1\x12\x133\xa7`\xea\x05\xba\xc0\xc0\xd0\x93\x80*uc``e\t\
+\x82\xb6J\xf3Q?3D\x04\x81`\xea\xc2\x80\x17K\xb6\xfc\xff\xb0-ff\x1c\x10\xee\
+\x90\xe1\xbf\x01\x10s}\x0em\tu\t\xfb\x06\xcbFP\xad\x11\x17\x81\x196\x18!\xdb\
+\x02\xd2#hk\xc8\x8f\t\xc1p\x89g\xb9\\\x11\xdb\xfd-\xbcn\x91\xa8C\x94,\x81\
+\xaa\xe9\x19\xe4\x1b\xa3}R\xf3\xf0\x08\x0e\x9f\x81\xef\x9c\x94s\x83\xaa\xe92\
+P\xcf\nv\xa7g\xd4\xb3\xa2\xef\xaf\xc5#i\x04\x89#\x8a\x05\'m\r)\x84\r\xe4S\
+\xa1\x9c\x1b\xf9\xb4\xe3\xd5\xe1\x18?\xb9@\x87\xe3^\x81\xbe\xb5H\xab`\x013\
+\xc3\xa9\xf3h\x15pC\xfa\xe1\x0f\x05\x00\xf1\xd5\xe4\x8b\x85la\x10@[0q\x88]\
+\x9e\x18/\x05\xe8/k\xde\x01\x83\x1f\xea\x19,\x9e\x1c\xf1\xcdj\xc3\xae\x01jP\
+\x05\x9fv\x07q1\x88\x83(\x8f\xd0\x8d"1h\x05\xba\x077\x80$\x87\xbb\xe7\x80\
+\xfc\xbf\xf2\xe1\x00\xef\x8c\xb8x\x06\x07\xd1$\xff\x00\x00\x00\x00IEND\xaeB`\
+\x82' 
+
+
+def getXMLBitmap():
+    return BitmapFromImage(getXMLImage())
+
+def getXMLImage():
+    stream = cStringIO.StringIO(getXMLData())
+    return ImageFromStream(stream)
+
+def getXMLIcon():
+    icon = EmptyIcon()
+    icon.CopyFromBitmap(getXMLBitmap())
+    return icon
diff --git a/wxPython/samples/ide/activegrid/tool/__init__.py b/wxPython/samples/ide/activegrid/tool/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/wxPython/samples/ide/activegrid/tool/checker.py b/wxPython/samples/ide/activegrid/tool/checker.py
new file mode 100644 (file)
index 0000000..985222a
--- /dev/null
@@ -0,0 +1,896 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2001-2004, MetaSlash Inc.  All rights reserved.
+
+"""
+Copyright notice from pychecker:
+    
+Copyright (c) 2000-2001, MetaSlash Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+ - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the
+   distribution.
+
+ - Neither name of MetaSlash Inc. nor the names of contributors
+   may be used to endorse or promote products derived
+   from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+
+"""
+Check python source code files for possible errors and print warnings
+
+Contact Info:
+  http://pychecker.sourceforge.net/
+  pychecker-list@lists.sourceforge.net
+"""
+
+import string
+import types
+import sys
+import imp
+import os
+import glob
+import traceback
+import re
+import wx
+_ = wx.GetTranslation
+
+# see __init__.py for meaning, this must match the version there
+LOCAL_MAIN_VERSION = 1
+
+
+def setupNamespace(path) :
+    # remove pychecker if it's the first component, it needs to be last
+    if sys.path[0][-9:] == 'pychecker' :
+        del sys.path[0]
+
+    # make sure pychecker is last in path, so we can import
+    checker_path = os.path.dirname(os.path.dirname(path))
+    if checker_path not in sys.path :
+        sys.path.append(checker_path)
+
+if __name__ == '__main__' :
+    setupNamespace(sys.argv[0])
+
+from pychecker import utils
+from pychecker import printer
+from pychecker import warn
+from pychecker import OP
+from pychecker import Config
+from pychecker import function
+from pychecker.Warning import Warning
+
+# Globals for storing a dictionary of info about modules and classes
+_allModules = {}
+_cfg = None
+
+# Constants
+_DEFAULT_MODULE_TOKENS = ('__builtins__', '__doc__', '__file__', '__name__',
+                          '__path__')
+_DEFAULT_CLASS_TOKENS = ('__doc__', '__name__', '__module__')
+
+_VERSION_MISMATCH_ERROR = '''
+There seem to be two versions of PyChecker being used.
+One is probably in python/site-packages, the other in a local directory.
+If you want to run the local version, you must remove the version
+from site-packages.  Or you can install the current version
+by doing python setup.py install.
+'''
+
+def cfg() :
+    return utils.cfg()
+
+def _flattenList(list) :
+    "Returns a list which contains no lists"
+
+    new_list = []
+    for element in list :
+        if type(element) == types.ListType :
+            new_list.extend(_flattenList(element))
+        else :
+            new_list.append(element)
+
+    return new_list
+
+def getModules(arg_list) :
+    "Returns a list of module names that can be imported"
+
+    global _output
+    
+    new_arguments = []
+    for arg in arg_list :
+        # is this a wildcard filespec? (necessary for windows)
+        if '*' in arg or '?' in arg or '[' in arg :
+            arg = glob.glob(arg)
+        new_arguments.append(arg)
+
+    PY_SUFFIXES = ['.py']
+    PY_SUFFIX_LENS = [3]
+    if _cfg.quixote:
+        PY_SUFFIXES.append('.ptl')
+        PY_SUFFIX_LENS.append(4)
+        
+    modules = []
+    for arg in _flattenList(new_arguments) :
+        fullpath = arg
+        # is it a .py file?
+        for suf, suflen in zip(PY_SUFFIXES, PY_SUFFIX_LENS):
+            if len(arg) > suflen and arg[-suflen:] == suf:
+                arg_dir = os.path.dirname(arg)
+                if arg_dir and not os.path.exists(arg) :
+                    txt = _('File or pathname element does not exist: "%s"') % arg
+                    _output.AddLines(txt)
+                    continue
+
+                module_name = os.path.basename(arg)[:-suflen]
+                if arg_dir not in sys.path :
+                    sys.path.insert(0, arg_dir)
+                arg = module_name
+        modules.append((arg, fullpath))
+
+    return modules
+
+def _q_file(f):
+    # crude hack!!!
+    # imp.load_module requires a real file object, so we can't just
+    # fiddle def lines and yield them
+    import tempfile
+    fd, newfname = tempfile.mkstemp(suffix=".py", text=True)
+    newf = os.fdopen(fd, 'r+')
+    os.unlink(newfname)
+    for line in f:
+        mat = re.match(r'(\s*def\s+\w+\s*)\[(html|plain)\](.*)', line)
+        if mat is None:
+            newf.write(line)
+        else:
+            newf.write(mat.group(1)+mat.group(3)+'\n')
+    newf.seek(0)
+    return newf
+
+def _q_find_module(p, path):
+    if not _cfg.quixote:
+        return imp.find_module(p, path)
+    else:
+        for direc in path:
+            try:
+                return imp.find_module(p, [direc])
+            except ImportError:
+                f = os.path.join(direc, p+".ptl")
+                if os.path.exists(f):
+                    return _q_file(file(f)), f, ('.ptl', 'U', 1)
+
+def _findModule(name) :
+    """Returns the result of an imp.find_module(), ie, (file, filename, smt)
+       name can be a module or a package name.  It is *not* a filename."""
+
+    path = sys.path[:]
+    packages = string.split(name, '.')
+    for p in packages :
+        # smt = (suffix, mode, type)
+        file, filename, smt = _q_find_module(p, path)
+        if smt[-1] == imp.PKG_DIRECTORY :
+            try :
+                # package found - read path info from init file
+                m = imp.load_module(p, file, filename, smt)
+            finally :
+                if file is not None :
+                    file.close()
+
+            # importing xml plays a trick, which replaces itself with _xmlplus
+            # both have subdirs w/same name, but different modules in them
+            # we need to choose the real (replaced) version
+            if m.__name__ != p :
+                try :
+                    file, filename, smt = _q_find_module(m.__name__, path)
+                    m = imp.load_module(p, file, filename, smt)
+                finally :
+                    if file is not None :
+                        file.close()
+
+            new_path = m.__path__
+            if type(new_path) == types.ListType :
+                new_path = filename
+            if new_path not in path :
+                path.insert(1, new_path)
+        elif smt[-1] != imp.PY_COMPILED:
+            if p is not packages[-1] :
+                if file is not None :
+                    file.close()
+                raise ImportError, "No module named %s" % packages[-1]
+            return file, filename, smt
+
+    # in case we have been given a package to check
+    return file, filename, smt
+
+
+class Variable :
+    "Class to hold all information about a variable"
+
+    def __init__(self, name, type):
+        self.name = name
+        self.type = type
+        self.value = None
+
+    def __str__(self) :
+        return self.name
+
+    __repr__ = utils.std_repr
+
+
+def _filterDir(object, ignoreList) :
+    "Return a list of tokens (attributes) in a class, except for ignoreList"
+
+    tokens = dir(object)
+    for token in ignoreList :
+        if token in tokens :
+            tokens.remove(token)
+    return tokens
+
+def _getClassTokens(c) :
+    return _filterDir(c, _DEFAULT_CLASS_TOKENS)
+
+
+class Class :
+    "Class to hold all information about a class"
+
+    def __init__(self, name, module) :
+        self.name = name
+        self.classObject = getattr(module, name)
+
+        modname = getattr(self.classObject, '__module__', None)
+        if modname is None:
+            # hm, some ExtensionClasses don't have a __module__ attribute
+            # so try parsing the type output
+            typerepr = repr(type(self.classObject))
+            mo = re.match("^<type ['\"](.+)['\"]>$", typerepr)
+            if mo:
+                modname = ".".join(mo.group(1).split(".")[:-1])
+
+        self.module = sys.modules.get(modname)
+        if not self.module:
+            self.module = module
+            
+            global _output
+            txt = _("warning: couldn't find real module for class %s (module name: %s)\n") % (self.classObject, modname)
+            _output.AddLines(txt)
+            
+        self.ignoreAttrs = 0
+        self.methods = {}
+        self.members = { '__class__': types.ClassType,
+                         '__doc__': types.StringType,
+                         '__dict__': types.DictType, }
+        self.memberRefs = {}
+        self.statics = {}
+        self.lineNums = {}
+
+    def __str__(self) :
+        return self.name
+
+    __repr__ = utils.std_repr
+
+    def getFirstLine(self) :
+        "Return first line we can find in THIS class, not any base classes"
+
+        lineNums = []
+        classDir = dir(self.classObject)
+        for m in self.methods.values() :
+            if m != None and m.function.func_code.co_name in classDir:
+                lineNums.append(m.function.func_code.co_firstlineno)
+        if lineNums :
+            return min(lineNums)
+        return 0
+
+
+    def allBaseClasses(self, c = None) :
+        "Return a list of all base classes for this class and it's subclasses"
+
+        baseClasses = []
+        if c == None :
+            c = self.classObject
+        for base in c.__bases__ :
+            baseClasses = baseClasses + [ base ] + self.allBaseClasses(base)
+        return baseClasses
+
+    def __getMethodName(self, func_name, className = None) :
+        if func_name[0:2] == '__' and func_name[-2:] != '__' :
+            if className == None :
+                className = self.name
+            if className[0] != '_' :
+                className = '_' + className
+            func_name = className + func_name
+        return func_name
+
+    def addMethod(self, method, methodName = None) :
+        if type(method) == types.StringType :
+            self.methods[method] = None
+        else :
+            assert methodName is not None, "must supply methodName"
+            self.methods[methodName] = function.Function(method, 1)
+                
+    def addMethods(self, classObject) :
+        for classToken in _getClassTokens(classObject) :
+            token = getattr(classObject, classToken, None)
+            if token is None:
+                continue
+
+            # Looks like a method.  Need to code it this way to
+            # accommodate ExtensionClass and Python 2.2.  Yecchh.
+            if (hasattr(token, "func_code") and
+                hasattr(token.func_code, "co_argcount")): 
+                self.addMethod(token, token.__name__)
+
+            elif hasattr(token, '__get__') and \
+                 not hasattr(token, '__set__') and \
+                 type(token) is not types.ClassType :
+                self.addMethod(getattr(token, '__name__', classToken))
+            else :
+                self.members[classToken] = type(token)
+                self.memberRefs[classToken] = None
+
+        self.cleanupMemberRefs()
+        # add standard methods
+        for methodName in ('__class__',) :
+            self.addMethod(methodName, classObject.__name__)
+
+    def addMembers(self, classObject) :
+        if not cfg().onlyCheckInitForMembers :
+            for classToken in _getClassTokens(classObject) :
+                method = getattr(classObject, classToken, None)
+                if type(method) == types.MethodType :
+                    self.addMembersFromMethod(method.im_func)
+        else:
+            try:
+                self.addMembersFromMethod(classObject.__init__.im_func)
+            except AttributeError:
+                pass
+
+    def addMembersFromMethod(self, method) :
+        if not hasattr(method, 'func_code') :
+            return
+
+        func_code, code, i, maxCode, extended_arg = OP.initFuncCode(method)
+        stack = []
+        while i < maxCode :
+            op, oparg, i, extended_arg = OP.getInfo(code, i, extended_arg)
+            if op >= OP.HAVE_ARGUMENT :
+                operand = OP.getOperand(op, func_code, oparg)
+                if OP.LOAD_CONST(op) or OP.LOAD_FAST(op) :
+                    stack.append(operand)
+                elif OP.STORE_ATTR(op) :
+                    if len(stack) > 0 :
+                        if stack[-1] == cfg().methodArgName:
+                            value = None
+                            if len(stack) > 1 :
+                                value = type(stack[-2])
+                            self.members[operand] = value
+                            self.memberRefs[operand] = None
+                        stack = []
+
+        self.cleanupMemberRefs()
+
+    def cleanupMemberRefs(self) :
+        try :
+            del self.memberRefs[Config.CHECKER_VAR]
+        except KeyError :
+            pass
+
+    def abstractMethod(self, m):
+        """Return 1 if method is abstract, None if not
+           An abstract method always raises an exception.
+        """
+        if not self.methods.get(m, None):
+            return None
+        func_code, bytes, i, maxCode, extended_arg = \
+                   OP.initFuncCode(self.methods[m].function)
+        # abstract if the first conditional is RAISE_VARARGS
+        while i < maxCode:
+            op, oparg, i, extended_arg = OP.getInfo(bytes, i, extended_arg)
+            if OP.RAISE_VARARGS(op):
+                return 1
+            if OP.conditional(op):
+                break
+        return None
+
+    def isAbstract(self):
+        """Return the method names that make a class abstract.
+           An abstract class has at least one abstract method."""
+        result = []
+        for m in self.methods.keys():
+            if self.abstractMethod(m):
+                result.append(m)
+        return result
+
+def _getLineInFile(moduleName, linenum):
+    line = ''
+    file, filename, smt = _findModule(moduleName)
+    try:
+        lines = file.readlines()
+        line = string.rstrip(lines[linenum - 1])
+    except (IOError, IndexError):
+        pass
+    file.close()
+    return line
+
+def importError(moduleName):
+    exc_type, exc_value, tb = sys.exc_info()
+
+    # First, try to get a nice-looking name for this exception type.
+    exc_name = getattr(exc_type, '__name__', None)
+    if not exc_name:
+        # either it's a string exception or a user-defined exception class
+        # show string or fully-qualified class name
+        exc_name = str(exc_type)
+        
+    # Print a traceback, unless this is an ImportError.  ImportError is
+    # presumably the most common import-time exception, so this saves
+    # the clutter of a traceback most of the time.  Also, the locus of
+    # the error is usually irrelevant for ImportError, so the lack of
+    # traceback shouldn't be a problem.
+    if exc_type is SyntaxError:
+        # SyntaxErrors are special, we want to control how we format
+        # the output and make it consistent for all versions of Python
+        e = exc_value
+        msg = '%s (%s, line %d)' % (e.msg, e.filename, e.lineno)
+        line = _getLineInFile(moduleName, e.lineno)
+        offset = e.offset
+        if type(offset) is not types.IntType:
+            offset = 0
+        exc_value = '%s\n    %s\n   %s^' % (msg, line, ' ' * offset)
+    elif exc_type is not ImportError:
+        global _output
+        txt = _("  Caught exception importing module %s:\n") % moduleName
+        _output.AddLines(txt)
+
+        try:
+            tbinfo = traceback.extract_tb(tb)
+        except:
+            tbinfo = []
+            txt = _("      Unable to format traceback\n")
+            _output.AddLines(txt)
+        for filename, line, func, text in tbinfo[1:]:
+            txt = _("    File \"%s\", line %d") % (filename, line)
+            _output.AddLines(txt)
+            if func != "?":
+                txt = _(", in %s()") % func
+                _output.AddLines(txt)
+            _output.AddLines("\n")
+            if text:
+                txt = _("      %s\n") % text
+                _output.AddLines(txt)
+
+    # And finally print the exception type and value.
+    # Careful formatting exc_value -- can fail for some user exceptions
+    txt = "  %s: " % exc_name
+    _output.AddLines(txt)
+    try:
+        txt = str(exc_value) + '\n'
+        _output.AddLines(txt)
+    except:
+        txt = _('**error formatting exception value**\n')
+        _output.AddLines(txt)
+
+
+def _getPyFile(filename):
+    """Return the file and '.py' filename from a filename which could
+    end with .py, .pyc, or .pyo"""
+
+    if filename[-1] in 'oc' and filename[-4:-1] == '.py':
+        return filename[:-1]
+    return filename
+
+class Module :
+    "Class to hold all information for a module"
+
+    def __init__(self, moduleName, check = 1, fullpath = None) :
+        self.moduleName = moduleName
+        self.variables = {}
+        self.functions = {}
+        self.classes = {}
+        self.modules = {}
+        self.moduleLineNums = {}
+        self.attributes = [ '__dict__' ]
+        self.main_code = None
+        self.module = None
+        self.check = check
+        self.fullpath = fullpath
+        _allModules[moduleName] = self
+
+    def __str__(self) :
+        return self.moduleName
+
+    __repr__ = utils.std_repr
+
+    def addVariable(self, var, varType) :
+        self.variables[var] = Variable(var, varType)
+
+    def addFunction(self, func) :
+        self.functions[func.__name__] = function.Function(func)
+
+    def __addAttributes(self, c, classObject) :
+        for base in classObject.__bases__ :
+            self.__addAttributes(c, base)
+        c.addMethods(classObject)
+        c.addMembers(classObject)
+
+    def addClass(self, name) :
+        self.classes[name] = c = Class(name, self.module)
+        try:
+            objName = str(c.classObject)
+        except TypeError:
+            # this can happen if there is a goofy __getattr__
+            c.ignoreAttrs = 1
+        else:
+            packages = string.split(objName, '.')
+            c.ignoreAttrs = packages[0] in cfg().blacklist
+        if not c.ignoreAttrs :
+            self.__addAttributes(c, c.classObject)
+
+    def addModule(self, name) :
+        module = _allModules.get(name, None)
+        if module is None :
+            self.modules[name] = module = Module(name, 0)
+            if imp.is_builtin(name) == 0 :
+                module.load()
+            else :
+                globalModule = globals().get(name)
+                if globalModule :
+                    module.attributes.extend(dir(globalModule))
+        else :
+            self.modules[name] = module
+
+    def filename(self) :
+        try :
+            filename = self.module.__file__
+        except AttributeError :
+            filename = self.moduleName
+        return _getPyFile(filename)
+
+    def load(self, warnings = None):
+        try :
+            # there's no need to reload modules we already have
+            global _output, _statusDlg, _count
+            txt = _("Loading Module %s\n") % self.moduleName
+            _output.AddLines(txt)
+            _count += 1
+            if _count == 100:
+                _count = 95
+            _statusDlg.Update(_count, txt)
+            
+            module = sys.modules.get(self.moduleName)
+            if module :
+                if not _allModules[self.moduleName].module :
+                    return self._initModule(module)
+                return 1
+
+            return self._initModule(self.setupMainCode())
+        except (SystemExit, KeyboardInterrupt) :
+            exc_type, exc_value, exc_tb = sys.exc_info()
+            raise exc_type, exc_value
+        except SyntaxError, (message, (fileName, line, col, text)):
+            # ActiveGrid: added this for better feedback when module couldn't be loaded.
+            w = Warning(self.fullpath, line, _("Syntax Error: %s\n%s\n%s^error near here") % (message, text, ' '*(col-1)))
+            warnings.append(w)
+            return 0
+        except:
+            w = Warning(self.moduleName, 1, sys.exc_info()[0] + " NOT PROCESSED UNABLE TO IMPORT")
+            warnings.append(w)
+            importError(self.moduleName)
+            return 0
+
+    def initModule(self, module) :
+        if not self.module:
+            filename = _getPyFile(module.__file__)
+            if string.lower(filename[-3:]) == '.py':
+                try:
+                    file = open(filename)
+                except IOError:
+                    pass
+                else:
+                    self._setupMainCode(file, filename, module)
+            return self._initModule(module)
+        return 1
+
+    def _initModule(self, module):
+        self.module = module
+        self.attributes = dir(self.module)
+
+        pychecker_attr = getattr(module, Config.CHECKER_VAR, None)
+        if pychecker_attr is not None :
+            utils.pushConfig()
+            utils.updateCheckerArgs(pychecker_attr, 'suppressions', 0, [])
+
+        for tokenName in _filterDir(self.module, _DEFAULT_MODULE_TOKENS) :
+            token = getattr(self.module, tokenName)
+            if isinstance(token, types.ModuleType) :
+                # get the real module name, tokenName could be an alias
+                self.addModule(token.__name__)
+            elif isinstance(token, types.FunctionType) :
+                self.addFunction(token)
+            elif isinstance(token, types.ClassType) or \
+                 hasattr(token, '__bases__') :
+                self.addClass(tokenName)
+            else :
+                self.addVariable(tokenName, type(token))
+
+        if pychecker_attr is not None :
+            utils.popConfig()
+        return 1
+
+    def setupMainCode(self) :
+        file, filename, smt = _findModule(self.moduleName)
+        # FIXME: if the smt[-1] == imp.PKG_DIRECTORY : load __all__
+        module = imp.load_module(self.moduleName, file, filename, smt)
+        self._setupMainCode(file, filename, module)
+        return module
+
+    def _setupMainCode(self, file, filename, module):
+        try :
+            self.main_code = function.create_from_file(file, filename, module)
+        finally :
+            if file != None :
+                file.close()
+
+
+def getAllModules() :
+    "Returns a list of all modules that should be checked."
+    modules = []
+    for module in _allModules.values() :
+        if module.check :
+            modules.append(module)
+    return modules
+
+_BUILTIN_MODULE_ATTRS = { 'sys': [ 'ps1', 'ps2', 'tracebacklimit', 
+                                   'exc_type', 'exc_value', 'exc_traceback',
+                                   'last_type', 'last_value', 'last_traceback',
+                                 ],
+                        }
+
+def fixupBuiltinModules(needs_init=0):
+    for moduleName in sys.builtin_module_names :
+        if needs_init:
+            _ = Module(moduleName, 0)
+        module = _allModules.get(moduleName, None)
+        if module is not None :
+            try :
+                m = imp.init_builtin(moduleName)
+            except ImportError :
+                pass
+            else :
+                extra_attrs = _BUILTIN_MODULE_ATTRS.get(moduleName, [])
+                module.attributes = [ '__dict__' ] + dir(m) + extra_attrs
+
+
+def _printWarnings(warnings, stream=None):
+    if stream is None:
+        stream = sys.stdout
+    
+    warnings.sort()
+    lastWarning = None
+    for warning in warnings :
+        if lastWarning != None :
+            # ignore duplicate warnings
+            if cmp(lastWarning, warning) == 0 :
+                continue
+            # print blank line between files
+            if lastWarning.file != warning.file :
+                global _output
+                _output.AddLines("\n")
+
+        lastWarning = warning
+        _output.AddLines(warning.format() + "\n")
+
+
+def processFiles(files, cfg = None, pre_process_cb = None) :
+    # insert this here, so we find files in the local dir before std library
+    if sys.path[0] != '' :
+        sys.path.insert(0, '')
+
+    # ensure we have a config object, it's necessary
+    global _cfg
+    if cfg is not None :
+        _cfg = cfg
+    elif _cfg is None :
+        _cfg = Config.Config()
+
+    warnings = []
+    utils.initConfig(_cfg)
+    for moduleName, filename in getModules(files) :
+        if callable(pre_process_cb) :
+            pre_process_cb(moduleName)
+        module = Module(moduleName, fullpath = filename)
+            
+        module.load(warnings)
+    utils.popConfig()
+    return warnings
+
+
+def getWarnings(files, cfg = None, suppressions = None):
+    warnings = processFiles(files, cfg)
+    fixupBuiltinModules()
+    return warnings + warn.find(getAllModules(), _cfg, suppressions)
+
+
+def _print_processing(name) :
+    if not _cfg.quiet :
+        global _output, _statusDlg, _count
+        txt = _("Processing %s...\n") % name
+        _output.AddLines(txt)
+        _count += 1
+        _statusDlg.Update(_count, txt)
+        
+
+
+def checkSyntax(filename, messageView):
+    """ Massively hacked version of main for ActiveGrid IDE integration """
+    global _cfg
+    _cfg, files, suppressions = Config.setupFromArgs([filename])
+    if not files :
+        return 0
+        
+    global _output, _statusDlg, _count
+    _output = messageView
+    # wxBug:  Need to show progress dialog box, or message window never gets updated until the method returns    
+    _statusDlg = wx.ProgressDialog(_("Check Code"), _("Checking %s") % filename, maximum = 100, style = wx.PD_AUTO_HIDE | wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)  
+    _count = 0
+
+    # insert this here, so we find files in the local dir before std library
+    sys.path.insert(0, '')
+
+    importWarnings = processFiles(files, _cfg, _print_processing)
+    fixupBuiltinModules()
+    if _cfg.printParse :
+        for module in getAllModules() :
+            printer.module(module)
+
+    warnings = warn.find(getAllModules(), _cfg, suppressions)
+    
+    _statusDlg.Update(100, _("Done"))
+    _statusDlg.Destroy()
+    
+    if not _cfg.quiet :
+        _output.AddLines(_("\nWarnings and Errors...\n"))
+    if warnings or importWarnings :
+        _printWarnings(importWarnings + warnings)
+        return 1
+
+    if not _cfg.quiet :
+        _output.AddLines(_("No Syntax Errors"))
+    return 0
+    
+##
+##
+##def main(argv) :
+##    __pychecker__ = 'no-miximport'
+##    import pychecker
+##    if LOCAL_MAIN_VERSION != pychecker.MAIN_MODULE_VERSION :
+##        sys.stderr.write(_VERSION_MISMATCH_ERROR)
+##        sys.exit(100)
+##
+##    # remove empty arguments
+##    argv = filter(None, argv)
+##        
+##    # if the first arg starts with an @, read options from the file
+##    # after the @ (this is mostly for windows)
+##    if len(argv) >= 2 and argv[1][0] == '@':
+##        # read data from the file
+##        command_file = argv[1][1:]
+##        try:
+##            f = open(command_file, 'r')
+##            command_line = f.read()
+##            f.close()
+##        except IOError, err:
+##            sys.stderr.write("Unable to read commands from file: %s\n  %s\n" % \
+##                             (command_file, err))
+##            sys.exit(101)
+##
+##        # convert to an argv list, keeping argv[0] and the files to process
+##        argv = argv[:1] + string.split(command_line) + argv[2:]
+## 
+##    global _cfg
+##    _cfg, files, suppressions = Config.setupFromArgs(argv[1:])
+##    if not files :
+##        return 0
+##
+##    # insert this here, so we find files in the local dir before std library
+##    sys.path.insert(0, '')
+##
+##    importWarnings = processFiles(files, _cfg, _print_processing)
+##    fixupBuiltinModules()
+##    if _cfg.printParse :
+##        for module in getAllModules() :
+##            printer.module(module)
+##
+##    warnings = warn.find(getAllModules(), _cfg, suppressions)
+##    if not _cfg.quiet :
+##        print "\nWarnings...\n"
+##    if warnings or importWarnings :
+##        _printWarnings(importWarnings + warnings)
+##        return 1
+##
+##    if not _cfg.quiet :
+##        print "None"
+##    return 0
+##
+##
+##if __name__ == '__main__' :
+##    try :
+##        sys.exit(main(sys.argv))
+##    except Config.UsageError :
+##        sys.exit(127)
+##
+##else :
+##    _orig__import__ = None
+##    _suppressions = None
+##    _warnings_cache = {}
+##
+##    def _get_unique_warnings(warnings):
+##        for i in range(len(warnings)-1, -1, -1):
+##            w = warnings[i].format()
+##            if _warnings_cache.has_key(w):
+##                del warnings[i]
+##            else:
+##                _warnings_cache[w] = 1
+##        return warnings
+##
+##    def __import__(name, globals=None, locals=None, fromlist=None):
+##        if globals is None:
+##            globals = {}
+##        if locals is None:
+##            locals = {}
+##        if fromlist is None:
+##            fromlist = []
+##
+##        check = not sys.modules.has_key(name) and name[:10] != 'pychecker.'
+##        pymodule = _orig__import__(name, globals, locals, fromlist)
+##        if check :
+##            try :
+##                module = Module(pymodule.__name__)
+##                if module.initModule(pymodule):
+##                    warnings = warn.find([module], _cfg, _suppressions)
+##                    _printWarnings(_get_unique_warnings(warnings))
+##                else :
+##                    print 'Unable to load module', pymodule.__name__
+##            except Exception:
+##                name = getattr(pymodule, '__name__', str(pymodule))
+##                importError(name)
+##
+##        return pymodule
+##
+##    def _init() :
+##        global _cfg, _suppressions, _orig__import__
+##
+##        args = string.split(os.environ.get('PYCHECKER', ''))
+##        _cfg, files, _suppressions = Config.setupFromArgs(args)
+##        utils.initConfig(_cfg)
+##        fixupBuiltinModules(1)
+##
+##        # keep the orig __import__ around so we can call it
+##        import __builtin__
+##        _orig__import__ = __builtin__.__import__
+##        __builtin__.__import__ = __import__
+##
+##    if not os.environ.get('PYCHECKER_DISABLED') :
+##        _init()
+##
diff --git a/wxPython/samples/ide/activegrid/tool/data/tips.txt b/wxPython/samples/ide/activegrid/tool/data/tips.txt
new file mode 100644 (file)
index 0000000..1d2008f
--- /dev/null
@@ -0,0 +1,7 @@
+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
diff --git a/wxPython/samples/ide/activegrid/tool/process.py b/wxPython/samples/ide/activegrid/tool/process.py
new file mode 100644 (file)
index 0000000..a521f0c
--- /dev/null
@@ -0,0 +1,2364 @@
+#!/usr/bin/env python
+# Copyright (c) 2002-2003 ActiveState
+# See LICENSE.txt for license details.
+""" Contents of LICENSE.txt:
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+r"""
+    Python interface for process control.
+
+    This module defines three Process classes for spawning,
+    communicating and control processes. They are: Process, ProcessOpen,
+    ProcessProxy. All of the classes allow one to specify the command (cmd),
+    starting working directory (cwd), and environment to create for the
+    new process (env) and to "wait" for termination of the child and
+    "kill" the child.
+
+    Process:
+        Use this class to simply launch a process (either a GUI app or a
+        console app in a new console) with which you do not intend to
+        communicate via it std handles.
+
+    ProcessOpen:
+        Think of this as a super version of Python's os.popen3() method.
+        This spawns the given command and sets up pipes for
+        stdin/stdout/stderr which can then be used to communicate with
+        the child.
+
+    ProcessProxy:
+        This is a heavy-weight class that, similar to ProcessOpen,
+        spawns the given commands and sets up pipes to the child's
+        stdin/stdout/stderr. However, it also starts three threads to
+        proxy communication between each of the child's and parent's std
+        handles. At the parent end of this communication are, by
+        default, IOBuffer objects. You may specify your own objects here
+        (usually sub-classing from IOBuffer, which handles some
+        synchronization issues for you). The result is that it is
+        possible to have your own IOBuffer instance that gets, say, a
+        .write() "event" for every write that the child does on its
+        stdout.
+
+        Understanding ProcessProxy is pretty complex. Some examples
+        below attempt to help show some uses. Here is a diagram of the
+        comminucation:
+
+                            <parent process>
+               ,---->->->------'   ^   `------>->->----,
+               |                   |                   v
+           IOBuffer             IOBuffer            IOBuffer        
+           (p.stdout)           (p.stderr)          (p.stdin)
+               |                   |                   |
+           _OutFileProxy        _OutFileProxy       _InFileProxy
+           thread               thread              thread
+               |                   ^                   |
+               `----<-<-<------,   |   ,------<-<-<----'
+                            <child process>
+
+    Usage:
+        import process
+        p = process.<Process class>(cmd='echo hi', ...)
+        #... use the various methods and attributes
+
+    Examples:
+      A simple 'hello world':
+        >>> import process
+        >>> p = process.ProcessOpen(['echo', 'hello'])
+        >>> p.stdout.read()
+        'hello\r\n'
+        >>> p.wait()   # .wait() returns the child's exit status
+        0
+
+      Redirecting the stdout handler:
+        >>> import sys
+        >>> p = process.ProcessProxy(['echo', 'hello'], stdout=sys.stdout)
+        hello
+
+      Using stdin (need to use ProcessProxy here because it defaults to
+      text-mode translation on Windows, ProcessOpen does not support
+      this):
+        >>> p = process.ProcessProxy(['sort'])
+        >>> p.stdin.write('5\n')
+        >>> p.stdin.write('2\n')
+        >>> p.stdin.write('7\n')
+        >>> p.stdin.close()
+        >>> p.stdout.read()
+        '2\n5\n7\n'
+
+      Specifying environment variables:
+        >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}'])
+        >>> p.stdout.read()
+        ''
+        >>> p = process.ProcessOpen(['perl', '-e', 'print $ENV{FOO}'],
+        ...                         env={'FOO':'bar'})
+        >>> p.stdout.read()
+        'bar'
+
+      Killing a long running process (On Linux, to poll you must use
+      p.wait(os.WNOHANG)):
+        >>> p = ProcessOpen(['perl', '-e', 'while (1) {}'])
+        >>> try:
+        ...     p.wait(os.WNOHANG)  # poll to see if is process still running
+        ... except ProcessError, ex:
+        ...     if ex.errno == ProcessProxy.WAIT_TIMEOUT:
+        ...             print "process is still running"
+        ...
+        process is still running
+        >>> p.kill(42)
+        >>> p.wait()
+        42
+
+      Providing objects for stdin/stdout/stderr:
+        XXX write this, mention IOBuffer subclassing.
+"""
+#TODO:
+#   - Discuss the decision to NOT have the stdout/stderr _OutFileProxy's
+#     wait for process termination before closing stdin. It will just
+#     close stdin when stdout is seen to have been closed. That is
+#     considered Good Enough (tm). Theoretically it would be nice to
+#     only abort the stdin proxying when the process terminates, but
+#     watching for process termination in any of the parent's thread
+#     adds the undesired condition that the parent cannot exit with the
+#     child still running. That sucks.
+#     XXX Note that I don't even know if the current stdout proxy even
+#         closes the stdin proxy at all.
+#   - DavidA: if I specify "unbuffered" for my stdin handler (in the
+#     ProcessProxy constructor) then the stdin IOBuffer should do a
+#     fparent.read() rather than a fparent.readline(). TrentM: can I do
+#     that? What happens?
+#
+
+import os
+import sys
+import threading
+import types
+import pprint 
+if sys.platform.startswith("win"):
+    import msvcrt
+    import win32api
+    import win32file
+    import win32pipe
+    import pywintypes
+    import win32process
+    import win32event
+    # constants pulled from win32con to save memory
+    VER_PLATFORM_WIN32_WINDOWS = 1
+    CTRL_BREAK_EVENT = 1
+    SW_SHOWDEFAULT = 10
+    WM_CLOSE = 0x10
+    DUPLICATE_SAME_ACCESS = 2
+    
+else:
+    import signal
+
+
+#---- exceptions
+
+class ProcessError(Exception):
+    def __init__(self, msg, errno=-1):
+        Exception.__init__(self, msg)
+        self.errno = errno
+
+
+#---- internal logging facility
+
+class Logger:
+    DEBUG, INFO, WARN, ERROR, FATAL = range(5)
+    def __init__(self, name, level=None, streamOrFileName=sys.stderr):
+        self.name = name
+        if level is None:
+            self.level = self.WARN
+        else:
+            self.level = level
+        if type(streamOrFileName) == types.StringType:
+            self.stream = open(streamOrFileName, 'w')
+            self._opennedStream = 1
+        else:
+            self.stream = streamOrFileName
+            self._opennedStream = 0
+    def __del__(self):
+        if self._opennedStream:
+            self.stream.close()
+    def _getLevelName(self, level):
+        levelNameMap = {
+            self.DEBUG: "DEBUG",
+            self.INFO: "INFO",
+            self.WARN: "WARN",
+            self.ERROR: "ERROR",
+            self.FATAL: "FATAL",
+        }
+        return levelNameMap[level]
+    def log(self, level, msg, *args):
+        if level < self.level:
+            return
+        message = "%s: %s:" % (self.name, self._getLevelName(level).lower())
+        message = message + (msg % args) + "\n"
+        self.stream.write(message)
+        self.stream.flush()
+    def debug(self, msg, *args):
+        self.log(self.DEBUG, msg, *args)
+    def info(self, msg, *args):
+        self.log(self.INFO, msg, *args)
+    def warn(self, msg, *args):
+        self.log(self.WARN, msg, *args)
+    def error(self, msg, *args):
+        self.log(self.ERROR, msg, *args)
+    def fatal(self, msg, *args):
+        self.log(self.FATAL, msg, *args)
+
+# Loggers:
+#   - 'log' to log normal process handling
+#   - 'logres' to track system resource life
+#   - 'logfix' to track wait/kill proxying in _ThreadFixer
+if 1:   # normal/production usage
+    log = Logger("process", Logger.WARN)
+else:   # development/debugging usage
+    log = Logger("process", Logger.DEBUG, sys.stdout)
+if 1:   # normal/production usage
+    logres = Logger("process.res", Logger.WARN)
+else:   # development/debugging usage
+    logres = Logger("process.res", Logger.DEBUG, sys.stdout)
+if 1:   # normal/production usage
+    logfix = Logger("process.waitfix", Logger.WARN)
+else:   # development/debugging usage
+    logfix = Logger("process.waitfix", Logger.DEBUG, sys.stdout)
+
+
+
+#---- globals
+
+_version_ = (0, 5, 0)
+
+# List of registered processes (see _(un)registerProcess).
+_processes = []
+
+
+
+#---- internal support routines
+
+def _escapeArg(arg):
+    """Escape the given command line argument for the shell."""
+    #XXX There is a probably more that we should escape here.
+    return arg.replace('"', r'\"')
+
+
+def _joinArgv(argv):
+    r"""Join an arglist to a string appropriate for running.
+
+        >>> import os
+        >>> _joinArgv(['foo', 'bar "baz'])
+        'foo "bar \\"baz"'
+    """
+    cmdstr = ""
+    for arg in argv:
+        if ' ' in arg or ';' in arg:
+            cmdstr += '"%s"' % _escapeArg(arg)
+        else:
+            cmdstr += _escapeArg(arg)
+        cmdstr += ' '
+    if cmdstr.endswith(' '): cmdstr = cmdstr[:-1]  # strip trailing space
+    return cmdstr
+
+
+def _getPathFromEnv(env):
+    """Return the PATH environment variable or None.
+
+    Do the right thing for case sensitivity per platform.
+    XXX Icky. This guarantee of proper case sensitivity of environment
+        variables should be done more fundamentally in this module.
+    """
+    if sys.platform.startswith("win"):
+        for key in env.keys():
+            if key.upper() == "PATH":
+                return env[key]
+        else:
+            return None
+    else:
+        if env.has_key("PATH"):
+            return env["PATH"]
+        else:
+            return None
+
+
+def _whichFirstArg(cmd, env=None):
+    """Return the given command ensuring that the first arg (the command to
+    launch) is a full path to an existing file.
+
+    Raise a ProcessError if no such executable could be found.
+    """
+    # Parse out the first arg.
+    if cmd.startswith('"'):
+        # The .replace() is to ensure it does not mistakenly find the
+        # second '"' in, say (escaped quote):
+        #           "C:\foo\"bar" arg1 arg2
+        idx = cmd.replace('\\"', 'XX').find('"', 1)
+        if idx == -1:
+            raise ProcessError("Malformed command: %r" % cmd)
+        first, rest = cmd[1:idx], cmd[idx+1:]
+        rest = rest.lstrip()
+    else:
+        if ' ' in cmd:
+            first, rest = cmd.split(' ', 1)
+        else:
+            first, rest = cmd, ""
+
+    # Ensure the first arg is a valid path to the appropriate file.
+    import which
+    if os.sep in first:
+        altpath = [os.path.dirname(first)]
+        firstbase = os.path.basename(first)
+        candidates = list(which.which(firstbase, path=altpath))
+    elif env:
+        altpath = _getPathFromEnv(env)
+        if altpath:
+            candidates = list(which.which(first, altpath.split(os.pathsep)))
+        else:
+            candidates = list(which.which(first))
+    else:
+        candidates = list(which.which(first))
+    if candidates:
+        return _joinArgv( [candidates[0]] ) + ' ' + rest
+    else:
+        raise ProcessError("Could not find an appropriate leading command "\
+                           "for: %r" % cmd)
+
+
+if sys.platform.startswith("win"):
+    def _SaferCreateProcess(appName,        # app name
+                            cmd,            # command line 
+                            processSA,      # process security attributes 
+                            threadSA,       # thread security attributes 
+                            inheritHandles, # are handles are inherited
+                            creationFlags,  # creation flags 
+                            env,            # environment
+                            cwd,            # current working directory
+                            si):            # STARTUPINFO pointer
+        """If CreateProcess fails from environment type inconsistency then
+        fix that and try again.
+        
+        win32process.CreateProcess requires that all environment keys and
+        values either be all ASCII or all unicode. Try to remove this burden
+        from the user of process.py.
+        """
+        isWin9x = win32api.GetVersionEx()[3] == VER_PLATFORM_WIN32_WINDOWS
+        # On Win9x all keys and values of 'env' must be ASCII (XXX
+        # Actually this is probably only true if the Unicode support
+        # libraries, which are not installed by default, are not
+        # installed). On other Windows flavours all keys and values of
+        # 'env' must all be ASCII *or* all Unicode. We will try to
+        # automatically convert to the appropriate type, issuing a
+        # warning if such an automatic conversion is necessary.
+
+        #XXX Komodo 2.0 Beta 1 hack. This requirement should be
+        #    pushed out to Komodo code using process.py. Or should it?
+        if isWin9x and env:
+            aenv = {}
+            for key, value in env.items():
+                aenv[str(key)] = str(value)
+            env = aenv
+        
+        log.debug("""\
+_SaferCreateProcess(appName=%r,
+                    cmd=%r,
+                    env=%r,
+                    cwd=%r)
+    os.getcwd(): %r
+""", appName, cmd, env, cwd, os.getcwd())
+        try:
+            hProcess, hThread, processId, threadId\
+                = win32process.CreateProcess(appName, cmd, processSA,
+                                             threadSA, inheritHandles,
+                                             creationFlags, env, cwd, si)
+        except TypeError, ex:
+            if ex.args == ('All dictionary items must be strings, or all must be unicode',):
+                # Try again with an all unicode environment.
+                #XXX Would be nice if didn't have to depend on the error
+                #    string to catch this.
+                #XXX Removing this warning for 2.3 release. See bug
+                #    23215. The right fix is to correct the PHPAppInfo
+                #    stuff to heed the warning.
+                #import warnings
+                #warnings.warn('env: ' + str(ex), stacklevel=4)
+                if isWin9x and env:
+                    aenv = {}
+                    try:
+                        for key, value in env.items():
+                            aenv[str(key)] = str(value)
+                    except UnicodeError, ex:
+                        raise ProcessError(str(ex))
+                    env = aenv
+                elif env:
+                    uenv = {}
+                    for key, val in env.items():
+                        uenv[unicode(key)] = unicode(val)
+                    env = uenv
+                hProcess, hThread, processId, threadId\
+                    = win32process.CreateProcess(appName, cmd, processSA,
+                                                 threadSA, inheritHandles,
+                                                 creationFlags, env, cwd,
+                                                 si)
+            else:
+                raise
+        return hProcess, hThread, processId, threadId
+
+
+# Maintain references to all spawned ProcessProxy objects to avoid hangs.
+#   Otherwise, if the user lets the a ProcessProxy object go out of
+#   scope before the process has terminated, it is possible to get a
+#   hang (at least it *used* to be so when we had the
+#   win32api.CloseHandle(<stdin handle>) call in the __del__() method).
+#   XXX Is this hang possible on Linux as well?
+# A reference is removed from this list when the process's .wait or
+# .kill method is called.
+# XXX Should an atexit() handler be registered to kill all curently
+#     running processes? Else *could* get hangs, n'est ce pas?
+def _registerProcess(process):
+    global _processes
+    log.info("_registerprocess(process=%r)", process)
+
+    # Clean up zombie processes.
+    #   If the user does not call .wait() or .kill() on processes then
+    #   the ProcessProxy object will not get cleaned up until Python
+    #   exits and _processes goes out of scope. Under heavy usage that
+    #   is a big memory waste. Cleaning up here alleviates that.
+    for p in _processes[:]: # use copy of _process, because we may modifiy it
+        try:
+            # poll to see if is process still running
+            if sys.platform.startswith("win"):
+                timeout = 0
+            else:
+                timeout = os.WNOHANG
+            p.wait(timeout)
+            _unregisterProcess(p)
+        except ProcessError, ex:
+            if ex.errno == ProcessProxy.WAIT_TIMEOUT:
+                pass
+            else:
+                raise
+        
+    _processes.append(process)
+
+def _unregisterProcess(process):
+    global _processes
+    log.info("_unregisterProcess(process=%r)", process)
+    try:
+        _processes.remove(process)
+        del process
+    except ValueError:
+        pass
+
+
+def _fixupCommand(cmd, env=None):
+    """Fixup the command string so it is launchable via CreateProcess.
+
+    One cannot just launch, say "python", via CreateProcess. A full path
+    to an executable is required. In general there are two choices:
+        1. Launch the command string via the shell. The shell will find
+           the fullpath to the appropriate executable. This shell will
+           also be able to execute special shell commands, like "dir",
+           which don't map to an actual executable.
+        2. Find the fullpath to the appropriate executable manually and
+           launch that exe.
+
+    Option (1) is preferred because you don't have to worry about not
+    exactly duplicating shell behaviour and you get the added bonus of
+    being able to launch "dir" and friends.
+
+    However, (1) is not always an option. Doing so when the shell is
+    command.com (as on all Win9x boxes) or when using WinNT's cmd.exe,
+    problems are created with .kill() because these shells seem to eat
+    up Ctrl-C's and Ctrl-Break's sent via
+    win32api.GenerateConsoleCtrlEvent().  Strangely this only happens
+    when spawn via this Python interface. For example, Ctrl-C get
+    through to hang.exe here:
+      C:\> ...\w9xpopen.exe "C:\WINDOWS\COMMAND.COM /c hang.exe"
+      ^C
+    but not here:
+      >>> p = ProcessOpen('hang.exe')
+      # This results in the same command to CreateProcess as
+      # above.
+      >>> p.kill()
+
+    Hence, for these platforms we fallback to option (2).  Cons:
+      - cannot spawn shell commands like 'dir' directly
+      - cannot spawn batch files
+    """
+    if sys.platform.startswith("win"):
+        # Fixup the command string to spawn.  (Lifted from
+        # posixmodule.c::_PyPopenCreateProcess() with some modifications)
+        comspec = os.environ.get("COMSPEC", None)
+        win32Version = win32api.GetVersion()
+        if comspec is None:
+            raise ProcessError("Cannot locate a COMSPEC environment "\
+                               "variable to use as the shell")
+        # Explicitly check if we are using COMMAND.COM.  If we
+        # are then use the w9xpopen hack.
+        elif (win32Version & 0x80000000L == 0) and\
+             (win32Version &        0x5L >= 5) and\
+             os.path.basename(comspec).lower() != "command.com":
+            # 2000/XP and not using command.com.
+            if '"' in cmd or "'" in cmd:
+                cmd = comspec + ' /c "%s"' % cmd
+            else:
+                cmd = comspec + ' /c ' + cmd
+        elif (win32Version & 0x80000000L == 0) and\
+             (win32Version &        0x5L  < 5) and\
+             os.path.basename(comspec).lower() != "command.com":
+            # NT and not using command.com.
+            try:
+                cmd = _whichFirstArg(cmd, env)
+            except ProcessError:
+                raise ProcessError("Could not find a suitable executable "\
+                    "to launch for '%s'. On WinNT you must manually prefix "\
+                    "shell commands and batch files with 'cmd.exe /c' to "\
+                    "have the shell run them." % cmd)
+        else:
+            # Oh gag, we're on Win9x and/or using COMMAND.COM. Use the
+            # workaround listed in KB: Q150956
+            w9xpopen = os.path.join(
+                os.path.dirname(win32api.GetModuleFileName(0)),
+                'w9xpopen.exe')
+            if not os.path.exists(w9xpopen):
+                # Eeek - file-not-found - possibly an embedding
+                # situation - see if we can locate it in sys.exec_prefix
+                w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
+                                        'w9xpopen.exe')
+                if not os.path.exists(w9xpopen):
+                    raise ProcessError(\
+                        "Can not locate 'w9xpopen.exe' which is needed "\
+                        "for ProcessOpen to work with your shell or "\
+                        "platform.")
+            ## This would be option (1):
+            #cmd = '%s "%s /c %s"'\
+            #      % (w9xpopen, comspec, cmd.replace('"', '\\"'))
+            try:
+                cmd = _whichFirstArg(cmd, env)
+            except ProcessError:
+                raise ProcessError("Could not find a suitable executable "\
+                    "to launch for '%s'. On Win9x you must manually prefix "\
+                    "shell commands and batch files with 'command.com /c' "\
+                    "to have the shell run them." % cmd)
+            cmd = '%s "%s"' % (w9xpopen, cmd.replace('"', '\\"'))
+    return cmd
+
+class _FileWrapper:
+    """Wrap a system file object, hiding some nitpicky details.
+    
+    This class provides a Python file-like interface to either a Python
+    file object (pretty easy job), a file descriptor, or an OS-specific
+    file handle (e.g.  Win32 handles to file objects on Windows). Any or
+    all of these object types may be passed to this wrapper. If more
+    than one is specified this wrapper prefers to work with certain one
+    in this order:
+        - file descriptor (because usually this allows for
+          return-immediately-on-read-if-anything-available semantics and
+          also provides text mode translation on Windows)
+        - OS-specific handle (allows for the above read semantics)
+        - file object (buffering can cause difficulty for interacting
+          with spawned programs)
+
+    It also provides a place where related such objects can be kept
+    alive together to prevent premature ref-counted collection. (E.g. on
+    Windows a Python file object may be associated with a Win32 file
+    handle. If the file handle is not kept alive the Python file object
+    will cease to function.)
+    """
+    def __init__(self, file=None, descriptor=None, handle=None):
+        self._file = file
+        self._descriptor = descriptor
+        self._handle = handle
+        self._closed = 0
+        if self._descriptor is not None or self._handle is not None:
+            self._lineBuf = "" # to support .readline()
+
+    def __del__(self):
+        self.close()
+
+    def __getattr__(self, name):
+        """Forward to the underlying file object."""
+        if self._file is not None:
+            return getattr(self._file, name)
+        else:
+            raise ProcessError("no file object to pass '%s' attribute to"
+                               % name)
+
+    def _win32Read(self, nBytes):
+        try:
+            log.info("[%s] _FileWrapper.read: waiting for read on pipe",
+                     id(self))
+            errCode, text = win32file.ReadFile(self._handle, nBytes)
+        except pywintypes.error, ex:
+            # Ignore errors for now, like "The pipe is being closed.",
+            # etc. XXX There *may* be errors we don't want to avoid.
+            log.info("[%s] _FileWrapper.read: error reading from pipe: %s",
+                     id(self), ex)
+            return ""
+        assert errCode == 0,\
+               "Why is 'errCode' from ReadFile non-zero? %r" % errCode
+        if not text:
+            # Empty text signifies that the pipe has been closed on
+            # the parent's end.
+            log.info("[%s] _FileWrapper.read: observed close of parent",
+                     id(self))
+            # Signal the child so it knows to stop listening.
+            self.close()
+            return ""
+        else:
+            log.info("[%s] _FileWrapper.read: read %d bytes from pipe: %r",
+                     id(self), len(text), text)
+        return text
+
+    def read(self, nBytes=-1):
+        # nBytes <= 0 means "read everything"
+        #   Note that we are changing the "read everything" cue to
+        #   include 0, because actually doing
+        #   win32file.ReadFile(<handle>, 0) results in every subsequent
+        #   read returning 0, i.e. it shuts down the pipe.
+        if self._descriptor is not None:
+            if nBytes <= 0:
+                text, self._lineBuf = self._lineBuf, ""
+                while 1:
+                    t = os.read(self._descriptor, 4092)
+                    if not t:
+                        break
+                    else:
+                        text += t
+            else:
+                if len(self._lineBuf) >= nBytes:
+                    text, self._lineBuf =\
+                        self._lineBuf[:nBytes], self._lineBuf[nBytes:]
+                else:
+                    nBytesToGo = nBytes - len(self._lineBuf)
+                    text = self._lineBuf + os.read(self._descriptor,
+                                                   nBytesToGo)
+                    self._lineBuf = ""
+            return text
+        elif self._handle is not None:
+            if nBytes <= 0:
+                text, self._lineBuf = self._lineBuf, ""
+                while 1:
+                    t = self._win32Read(4092)
+                    if not t:
+                        break
+                    else:
+                        text += t
+            else:
+                if len(self._lineBuf) >= nBytes:
+                    text, self._lineBuf =\
+                        self._lineBuf[:nBytes], self._lineBuf[nBytes:]
+                else:
+                    nBytesToGo = nBytes - len(self._lineBuf)
+                    text, self._lineBuf =\
+                        self._lineBuf + self._win32Read(nBytesToGo), ""
+            return text
+        elif self._file is not None:
+            return self._file.read(nBytes)
+        else:   
+            raise "FileHandle.read: no handle to read with"
+
+    def readline(self):
+        if self._descriptor is not None or self._handle is not None:
+            while 1:
+                #XXX This is not portable to the Mac.
+                idx = self._lineBuf.find('\n')
+                if idx != -1:
+                    line, self._lineBuf =\
+                        self._lineBuf[:idx+1], self._lineBuf[idx+1:]
+                    break
+                else:
+                    lengthBefore = len(self._lineBuf)
+                    t = self.read(4092)
+                    if len(t) <= lengthBefore: # no new data was read
+                        line, self._lineBuf = self._lineBuf, ""
+                        break
+                    else:
+                        self._lineBuf += t
+            return line
+        elif self._file is not None:
+            return self._file.readline()
+        else:
+            raise "FileHandle.readline: no handle to read with"
+
+    def readlines(self):
+        if self._descriptor is not None or self._handle is not None:
+            lines = []
+            while 1:
+                line = self.readline()
+                if line:
+                    lines.append(line)
+                else:
+                    break
+            return lines
+        elif self._file is not None:
+            return self._file.readlines()
+        else:
+            raise "FileHandle.readline: no handle to read with"
+
+    def write(self, text):
+        if self._descriptor is not None:
+            os.write(self._descriptor, text)
+        elif self._handle is not None:
+            try:
+                errCode, nBytesWritten = win32file.WriteFile(self._handle, text)
+            except pywintypes.error, ex:
+                # Ingore errors like "The pipe is being closed.", for
+                # now.
+                log.info("[%s] _FileWrapper.write: error writing to pipe, "\
+                         "ignored", id(self))
+                return
+            assert errCode == 0,\
+                   "Why is 'errCode' from WriteFile non-zero? %r" % errCode
+            if not nBytesWritten:
+                # No bytes written signifies that the pipe has been
+                # closed on the child's end.
+                log.info("[%s] _FileWrapper.write: observed close of pipe",
+                         id(self))
+                return
+            else:
+                log.info("[%s] _FileWrapper.write: wrote %d bytes to pipe: %r",
+                         id(self), len(text), text)
+        elif self._file is not None:
+            self._file.write(text)
+        else:   
+            raise "FileHandle.write: nothing to write with"
+
+    def close(self):
+        """Close all associated file objects and handles."""
+        log.debug("[%s] _FileWrapper.close()", id(self))
+        if not self._closed:
+            self._closed = 1
+            if self._file is not None:
+                log.debug("[%s] _FileWrapper.close: close file", id(self))
+                self._file.close()
+                log.debug("[%s] _FileWrapper.close: done file close", id(self))
+            if self._descriptor is not None:
+                try:
+                    os.close(self._descriptor)
+                except OSError, ex:
+                    if ex.errno == 9:
+                        # Ignore: OSError: [Errno 9] Bad file descriptor
+                        # XXX *Should* we be ignoring this? It appears very
+                        #     *in*frequently in test_wait.py.
+                        log.debug("[%s] _FileWrapper.close: closing "\
+                                  "descriptor raised OSError", id(self))
+                    else:
+                        raise
+            if self._handle is not None:
+                log.debug("[%s] _FileWrapper.close: close handle", id(self))
+                try:
+                    win32api.CloseHandle(self._handle)
+                except win32api.error:
+                    log.debug("[%s] _FileWrapper.close: closing handle raised",
+                              id(self))
+                    pass
+                log.debug("[%s] _FileWrapper.close: done closing handle",
+                          id(self))
+
+    def __repr__(self):
+        return "<_FileWrapper: file:%r fd:%r os_handle:%r>"\
+               % (self._file, self._descriptor, self._handle)
+
+
+class _CountingCloser:
+    """Call .close() on the given object after own .close() is called
+    the precribed number of times.
+    """
+    def __init__(self, objectsToClose, count):
+        """
+        "objectsToClose" is a list of object on which to call .close().
+        "count" is the number of times this object's .close() method
+            must be called before .close() is called on the given objects.
+        """
+        self.objectsToClose = objectsToClose
+        self.count = count
+        if self.count <= 0:
+            raise ProcessError("illegal 'count' value: %s" % self.count)
+
+    def close(self):
+        self.count -= 1
+        log.debug("[%d] _CountingCloser.close(): count=%d", id(self),
+                  self.count)
+        if self.count == 0:
+            for objectToClose in self.objectsToClose:
+                objectToClose.close()
+
+
+
+#---- public interface
+
+class Process:
+    """Create a process.
+
+    One can optionally specify the starting working directory, the
+    process environment, and std handles to have the child process
+    inherit (all defaults are the parent's current settings). 'wait' and
+    'kill' method allow for control of the child's termination.
+    """
+    # TODO:
+    #   - Rename this or merge it with ProcessOpen somehow.
+    #
+    if sys.platform.startswith("win"):
+        # .wait() argument constants
+        INFINITE = win32event.INFINITE
+        # .wait() return error codes
+        WAIT_FAILED = win32event.WAIT_FAILED
+        WAIT_TIMEOUT = win32event.WAIT_TIMEOUT
+        # creation "flags" constants
+        # XXX Should drop these and just document usage of
+        #     win32process.CREATE_* constants on windows.
+        CREATE_NEW_CONSOLE = win32process.CREATE_NEW_CONSOLE
+    else:
+        # .wait() argument constants
+        INFINITE = 0
+        # .wait() return error codes
+        WAIT_TIMEOUT = 258
+        WAIT_FAILED = -1
+        # creation "flags" constants
+        CREATE_NEW_CONSOLE = 0x10 # same as win32process.CREATE_NEW_CONSOLE
+
+    def __init__(self, cmd, cwd=None, env=None, flags=0):
+        """Create a child process.
+
+        "cmd" is a command string or argument vector to spawn.
+        "cwd" is a working directory in which to start the child process.
+        "env" is an environment dictionary for the child.
+        "flags" are system-specific process creation flags. On Windows
+            this can be a bitwise-OR of any of the win32process.CREATE_*
+            constants (Note: win32process.CREATE_NEW_PROCESS_GROUP is always
+            OR'd in). On Unix, this is currently ignored.
+        """
+        log.info("Process.__init__(cmd=%r, cwd=%r, env=%r, flags=%r)",
+                 cmd, cwd, env, flags)
+        self._cmd = cmd
+        if not self._cmd:
+            raise ProcessError("You must specify a command.")
+        self._cwd = cwd
+        self._env = env
+        self._flags = flags
+        if sys.platform.startswith("win"):
+            self._flags |= win32process.CREATE_NEW_PROCESS_GROUP
+
+        if sys.platform.startswith("win"):
+            self._startOnWindows()
+        else:
+            self.__retvalCache = None
+            self._startOnUnix()
+
+    def _runChildOnUnix(self):
+        #XXX Errors running the child do *not* get communicated back.
+
+        #XXX Perhaps we should *always* prefix with '/bin/sh -c'? There is a
+        #    disparity btwn how this works on Linux and Windows.
+        if isinstance(self._cmd, types.StringTypes):
+            # This is easier than trying to reproduce shell interpretation to
+            # separate the arguments.
+            cmd = ['/bin/sh', '-c', self._cmd]
+        else:
+            cmd = self._cmd
+
+        # Close all file descriptors (except std*) inherited from the parent.
+        MAXFD = 256 # Max number of file descriptors (os.getdtablesize()???)
+        for i in range(3, MAXFD):
+            try:
+                os.close(i)
+            except OSError:
+                pass
+
+        try:
+            if self._env:
+                os.execvpe(cmd[0], cmd, self._env)
+            else:
+                os.execvp(cmd[0], cmd)
+        finally:
+            os._exit(1)  # Should never get here.
+
+    def _forkAndExecChildOnUnix(self):
+        """Fork and start the child process.
+
+        Sets self._pid as a side effect.
+        """
+        pid = os.fork()
+        if pid == 0: # child
+            self._runChildOnUnix()
+        # parent
+        self._pid = pid
+
+    def _startOnUnix(self):
+        if self._cwd:
+            oldDir = os.getcwd()
+            try:
+                os.chdir(self._cwd)
+            except OSError, ex:
+                raise ProcessError(msg=str(ex), errno=ex.errno)
+        self._forkAndExecChildOnUnix()
+
+        # parent
+        if self._cwd:
+            os.chdir(oldDir)
+
+    def _startOnWindows(self):
+        if type(self._cmd) in (types.ListType, types.TupleType):
+            # And arg vector was passed in.
+            cmd = _joinArgv(self._cmd)
+        else:
+            cmd = self._cmd
+
+        si = win32process.STARTUPINFO() 
+        si.dwFlags = win32process.STARTF_USESHOWWINDOW
+        si.wShowWindow = SW_SHOWDEFAULT
+
+        if not (self._flags & self.CREATE_NEW_CONSOLE):
+            #XXX This is hacky.
+            # We cannot then use _fixupCommand because this will cause a
+            # shell to be openned as the command is launched. Therefore need
+            # to ensure be have the full path to the executable to launch.
+            try:
+                cmd = _whichFirstArg(cmd, self._env)
+            except ProcessError:
+                # Could not find the command, perhaps it is an internal
+                # shell command -- fallback to _fixupCommand
+                cmd = _fixupCommand(cmd, self._env)
+        else:
+            cmd = _fixupCommand(cmd, self._env)
+        log.debug("cmd = %r", cmd)
+
+        # Start the child process.
+        try:
+            self._hProcess, self._hThread, self._processId, self._threadId\
+                = _SaferCreateProcess(
+                    None,           # app name
+                    cmd,            # command line 
+                    None,           # process security attributes 
+                    None,           # primary thread security attributes 
+                    0,              # handles are inherited 
+                    self._flags,    # creation flags 
+                    self._env,      # environment
+                    self._cwd,      # current working directory
+                    si)             # STARTUPINFO pointer 
+            win32api.CloseHandle(self._hThread)
+        except win32api.error, ex:
+            raise ProcessError(msg="Error creating process for '%s': %s"\
+                                   % (cmd, ex.args[2]),
+                               errno=ex.args[0])
+
+    def wait(self, timeout=None): 
+        """Wait for the started process to complete.
+        
+        "timeout" (on Windows) is a floating point number of seconds after
+            which to timeout.  Default is win32event.INFINITE.
+        "timeout" (on Unix) is akin to the os.waitpid() "options" argument
+            (os.WNOHANG may be used to return immediately if the process has
+            not exited). Default is 0, i.e. wait forever.
+
+        If the wait time's out it will raise a ProcessError. Otherwise it
+        will return the child's exit value (on Windows) or the child's exit
+        status excoded as per os.waitpid() (on Linux):
+            "a 16-bit number, whose low byte is the signal number that killed
+            the process, and whose high byte is the exit status (if the
+            signal number is zero); the high bit of the low byte is set if a
+            core file was produced."
+        In the latter case, use the os.W*() methods to interpret the return
+        value.
+        """
+        # XXX Or should returning the exit value be move out to another
+        #     function as on Win32 process control? If so, then should
+        #     perhaps not make WaitForSingleObject semantic transformation.
+        if sys.platform.startswith("win"):
+            if timeout is None:
+                timeout = win32event.INFINITE
+            else:
+                timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs
+
+            rc = win32event.WaitForSingleObject(self._hProcess, timeout)
+            if rc == win32event.WAIT_FAILED:
+                raise ProcessError("'WAIT_FAILED' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+            elif rc == win32event.WAIT_TIMEOUT:
+                raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+
+            retval = win32process.GetExitCodeProcess(self._hProcess)
+        else:
+            # os.waitpid() will raise:
+            #       OSError: [Errno 10] No child processes
+            # on subsequent .wait() calls. Change these semantics to have
+            # subsequent .wait() calls return the exit status and return
+            # immediately without raising an exception.
+            # (XXX It would require synchronization code to handle the case
+            # of multiple simultaneous .wait() requests, however we can punt
+            # on that because it is moot while Linux still has the problem
+            # for which _ThreadFixer() exists.)
+            if self.__retvalCache is not None:
+                retval = self.__retvalCache
+            else:
+                if timeout is None:
+                    timeout = 0
+                pid, sts = os.waitpid(self._pid, timeout)
+                if pid == self._pid:
+                    self.__retvalCache = retval = sts
+                else:
+                    raise ProcessError("Wait for process timed out.",
+                                       self.WAIT_TIMEOUT)
+        return retval
+
+    def kill(self, exitCode=0, gracePeriod=1.0, sig=None):
+        """Kill process.
+        
+        "exitCode" [deprecated, not supported] (Windows only) is the
+            code the terminated process should exit with.
+        "gracePeriod" (Windows only) is a number of seconds the process is
+            allowed to shutdown with a WM_CLOSE signal before a hard
+            terminate is called.
+        "sig" (Unix only) is the signal to use to kill the process. Defaults
+            to signal.SIGKILL. See os.kill() for more information.
+
+        Windows:
+            Try for an orderly shutdown via WM_CLOSE.  If still running
+            after gracePeriod (1 sec. default), terminate.
+        """
+        if sys.platform.startswith("win"):
+            import win32gui
+            # Send WM_CLOSE to windows in this process group.
+            win32gui.EnumWindows(self._close_, 0)
+
+            # Send Ctrl-Break signal to all processes attached to this
+            # console. This is supposed to trigger shutdown handlers in
+            # each of the processes.
+            try:
+                win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
+                                                  self._processId)
+            except AttributeError:
+                log.warn("The win32api module does not have "\
+                         "GenerateConsoleCtrlEvent(). This may mean that "\
+                         "parts of this process group have NOT been killed.")
+            except win32api.error, ex:
+                if ex.args[0] not in (6, 87):
+                    # Ignore the following:
+                    #   api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.')
+                    #   api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.')
+                    # Get error 6 if there is no console.
+                    raise
+            
+            # Last resort: call TerminateProcess if it has not yet.
+            retval = 0
+            try:
+                self.wait(gracePeriod)
+            except ProcessError, ex:
+                log.info("[%s] Process.kill: calling TerminateProcess", id(self))
+                win32process.TerminateProcess(self._hProcess, -1)
+                win32api.Sleep(100) # wait for resources to be released
+
+        else:
+            if sig is None:
+                sig = signal.SIGKILL
+            try:
+                os.kill(self._pid, sig)
+            except OSError, ex:
+                if ex.errno != 3:
+                    # Ignore:   OSError: [Errno 3] No such process
+                    raise
+
+    def _close_(self, hwnd, dummy):
+        """Callback used by .kill() on Windows.
+
+        EnumWindows callback - sends WM_CLOSE to any window owned by this
+        process.
+        """
+        threadId, processId = win32process.GetWindowThreadProcessId(hwnd)
+        if processId == self._processId:
+            import win32gui
+            win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0)
+
+
+class ProcessOpen(Process):
+    """Create a process and setup pipes to it standard handles.
+
+    This is a super popen3.
+    """
+    # TODO:
+    #   - Share some implementation with Process and ProcessProxy.
+    #
+
+    def __init__(self, cmd, mode='t', cwd=None, env=None):
+        """Create a Process with proxy threads for each std handle.
+
+        "cmd" is the command string or argument vector to run.
+        "mode" (Windows only) specifies whether the pipes used to communicate
+            with the child are openned in text, 't', or binary, 'b', mode.
+            This is ignored on platforms other than Windows. Default is 't'.
+        "cwd" optionally specifies the directory in which the child process
+            should be started. Default is None, a.k.a. inherits the cwd from
+            the parent.
+        "env" is optionally a mapping specifying the environment in which to
+            start the child. Default is None, a.k.a. inherits the environment
+            of the parent.
+        """
+        # Keep a reference to ensure it is around for this object's destruction.
+        self.__log = log
+        log.info("ProcessOpen.__init__(cmd=%r, mode=%r, cwd=%r, env=%r)",
+                 cmd, mode, cwd, env)
+        self._cmd = cmd
+        if not self._cmd:
+            raise ProcessError("You must specify a command.")
+        self._cwd = cwd
+        self._env = env
+        self._mode = mode
+        if self._mode not in ('t', 'b'):
+            raise ProcessError("'mode' must be 't' or 'b'.")
+        self._closed = 0
+
+        if sys.platform.startswith("win"):
+            self._startOnWindows()
+        else:
+            self.__retvalCache = None
+            self._startOnUnix()
+
+        _registerProcess(self)
+
+    def __del__(self):
+        #XXX Should probably not rely upon this.
+        logres.info("[%s] ProcessOpen.__del__()", id(self))
+        self.close()
+        del self.__log # drop reference
+
+    def close(self):
+        if not self._closed:
+            self.__log.info("[%s] ProcessOpen.close()" % id(self))
+
+            # Ensure that all IOBuffer's are closed. If they are not, these
+            # can cause hangs. 
+            try:
+                self.__log.info("[%s] ProcessOpen: closing stdin (%r)."\
+                                % (id(self), self.stdin))
+                self.stdin.close()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stdin, etc.
+                pass
+            try:
+                self.__log.info("[%s] ProcessOpen: closing stdout (%r)."\
+                                % (id(self), self.stdout))
+                self.stdout.close()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stdout, etc.
+                pass
+            try:
+                self.__log.info("[%s] ProcessOpen: closing stderr (%r)."\
+                                % (id(self), self.stderr))
+                self.stderr.close()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stderr, etc.
+                pass
+
+            self._closed = 1
+
+    def _forkAndExecChildOnUnix(self, fdChildStdinRd, fdChildStdoutWr,
+                                fdChildStderrWr):
+        """Fork and start the child process.
+
+        Sets self._pid as a side effect.
+        """
+        pid = os.fork()
+        if pid == 0: # child
+            os.dup2(fdChildStdinRd, 0)
+            os.dup2(fdChildStdoutWr, 1)
+            os.dup2(fdChildStderrWr, 2)
+            self._runChildOnUnix()
+        # parent
+        self._pid = pid
+
+    def _startOnUnix(self):
+        # Create pipes for std handles.
+        fdChildStdinRd, fdChildStdinWr = os.pipe()
+        fdChildStdoutRd, fdChildStdoutWr = os.pipe()
+        fdChildStderrRd, fdChildStderrWr = os.pipe()
+
+        if self._cwd:
+            oldDir = os.getcwd()
+            try:
+                os.chdir(self._cwd)
+            except OSError, ex:
+                raise ProcessError(msg=str(ex), errno=ex.errno)
+        self._forkAndExecChildOnUnix(fdChildStdinRd, fdChildStdoutWr,
+                                     fdChildStderrWr)
+        if self._cwd:
+            os.chdir(oldDir)
+
+        os.close(fdChildStdinRd)
+        os.close(fdChildStdoutWr)
+        os.close(fdChildStderrWr)
+
+        self.stdin = _FileWrapper(descriptor=fdChildStdinWr)
+        logres.info("[%s] ProcessOpen._start(): create child stdin: %r",
+                    id(self), self.stdin)
+        self.stdout = _FileWrapper(descriptor=fdChildStdoutRd)
+        logres.info("[%s] ProcessOpen._start(): create child stdout: %r",
+                    id(self), self.stdout)
+        self.stderr = _FileWrapper(descriptor=fdChildStderrRd)
+        logres.info("[%s] ProcessOpen._start(): create child stderr: %r",
+                    id(self), self.stderr)
+
+    def _startOnWindows(self):
+        if type(self._cmd) in (types.ListType, types.TupleType):
+            # An arg vector was passed in.
+            cmd = _joinArgv(self._cmd)
+        else:
+            cmd = self._cmd
+
+        # Create pipes for std handles.
+        # (Set the bInheritHandle flag so pipe handles are inherited.)
+        saAttr = pywintypes.SECURITY_ATTRIBUTES()
+        saAttr.bInheritHandle = 1
+        #XXX Should maybe try with os.pipe. Dunno what that does for
+        #    inheritability though.
+        hChildStdinRd, hChildStdinWr = win32pipe.CreatePipe(saAttr, 0) 
+        hChildStdoutRd, hChildStdoutWr = win32pipe.CreatePipe(saAttr, 0) 
+        hChildStderrRd, hChildStderrWr = win32pipe.CreatePipe(saAttr, 0) 
+
+        try:
+            # Duplicate the parent ends of the pipes so they are not
+            # inherited. 
+            hChildStdinWrDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStdinWr,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStdinWr)
+            self._hChildStdinWr = hChildStdinWrDup
+            hChildStdoutRdDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStdoutRd,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStdoutRd)
+            self._hChildStdoutRd = hChildStdoutRdDup
+            hChildStderrRdDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStderrRd,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStderrRd)
+            self._hChildStderrRd = hChildStderrRdDup
+
+            # Set the translation mode and buffering.
+            if self._mode == 't':
+                flags = os.O_TEXT
+            else:
+                flags = 0
+            fdChildStdinWr = msvcrt.open_osfhandle(self._hChildStdinWr, flags)
+            fdChildStdoutRd = msvcrt.open_osfhandle(self._hChildStdoutRd, flags)
+            fdChildStderrRd = msvcrt.open_osfhandle(self._hChildStderrRd, flags)
+
+            self.stdin = _FileWrapper(descriptor=fdChildStdinWr,
+                                      handle=self._hChildStdinWr)
+            logres.info("[%s] ProcessOpen._start(): create child stdin: %r",
+                        id(self), self.stdin)
+            self.stdout = _FileWrapper(descriptor=fdChildStdoutRd,
+                                       handle=self._hChildStdoutRd)
+            logres.info("[%s] ProcessOpen._start(): create child stdout: %r",
+                        id(self), self.stdout)
+            self.stderr = _FileWrapper(descriptor=fdChildStderrRd,
+                                       handle=self._hChildStderrRd)
+            logres.info("[%s] ProcessOpen._start(): create child stderr: %r",
+                        id(self), self.stderr)
+
+            # Start the child process.
+            si = win32process.STARTUPINFO() 
+            si.dwFlags = win32process.STARTF_USESHOWWINDOW
+            si.wShowWindow = 0 # SW_HIDE
+            si.hStdInput = hChildStdinRd
+            si.hStdOutput = hChildStdoutWr
+            si.hStdError = hChildStderrWr
+            si.dwFlags |= win32process.STARTF_USESTDHANDLES
+
+            cmd = _fixupCommand(cmd, self._env)
+
+            creationFlags = win32process.CREATE_NEW_PROCESS_GROUP
+            try:
+                self._hProcess, hThread, self._processId, threadId\
+                    = _SaferCreateProcess(
+                        None,           # app name
+                        cmd,            # command line 
+                        None,           # process security attributes 
+                        None,           # primary thread security attributes 
+                        1,              # handles are inherited 
+                        creationFlags,  # creation flags 
+                        self._env,      # environment
+                        self._cwd,      # current working directory
+                        si)             # STARTUPINFO pointer 
+            except win32api.error, ex:
+                raise ProcessError(msg=ex.args[2], errno=ex.args[0])
+            win32api.CloseHandle(hThread)
+
+        finally:
+            # Close child ends of pipes on the parent's side (the
+            # parent's ends of the pipe are closed in the _FileWrappers.)
+            win32file.CloseHandle(hChildStdinRd)
+            win32file.CloseHandle(hChildStdoutWr)
+            win32file.CloseHandle(hChildStderrWr)
+
+    def wait(self, timeout=None): 
+        """Wait for the started process to complete.
+        
+        "timeout" (on Windows) is a floating point number of seconds after
+            which to timeout.  Default is win32event.INFINITE.
+        "timeout" (on Unix) is akin to the os.waitpid() "options" argument
+            (os.WNOHANG may be used to return immediately if the process has
+            not exited). Default is 0, i.e. wait forever.
+
+        If the wait time's out it will raise a ProcessError. Otherwise it
+        will return the child's exit value (on Windows) or the child's exit
+        status excoded as per os.waitpid() (on Linux):
+            "a 16-bit number, whose low byte is the signal number that killed
+            the process, and whose high byte is the exit status (if the
+            signal number is zero); the high bit of the low byte is set if a
+            core file was produced."
+        In the latter case, use the os.W*() methods to interpret the return
+        value.
+        """
+        # XXX Or should returning the exit value be move out to another
+        #    function as on Win32 process control? If so, then should
+        #    perhaps not make WaitForSingleObject semantic
+        #    transformation.
+        # TODO:
+        #   - Need to rationalize the .wait() API for Windows vs. Unix.
+        #     It is a real pain in the current situation.
+        if sys.platform.startswith("win"):
+            if timeout is None:
+                timeout = win32event.INFINITE
+            else:
+                timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs
+
+            #rc = win32event.WaitForSingleObject(self._hProcess, timeout)
+            rc = win32event.WaitForSingleObject(self._hProcess, int(timeout)) # MATT -- Making timeout an integer
+            if rc == win32event.WAIT_FAILED:
+                raise ProcessError("'WAIT_FAILED' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+            elif rc == win32event.WAIT_TIMEOUT:
+                raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+
+            retval = win32process.GetExitCodeProcess(self._hProcess)
+        else:
+            # os.waitpid() will raise:
+            #       OSError: [Errno 10] No child processes
+            # on subsequent .wait() calls. Change these semantics to have
+            # subsequent .wait() calls return the exit status and return
+            # immediately without raising an exception.
+            # (XXX It would require synchronization code to handle the case
+            # of multiple simultaneous .wait() requests, however we can punt
+            # on that because it is moot while Linux still has the problem
+            # for which _ThreadFixer() exists.)
+            if self.__retvalCache is not None:
+                retval = self.__retvalCache
+            else:
+                if timeout is None:
+                    timeout = 0
+                pid, sts = os.waitpid(self._pid, timeout)
+                if pid == self._pid:
+                    self.__retvalCache = retval = sts
+                else:
+                    raise ProcessError("Wait for process timed out.",
+                                       self.WAIT_TIMEOUT)
+        _unregisterProcess(self)
+        return retval
+
+    def kill(self, exitCode=0, gracePeriod=1.0, sig=None):
+        """Kill process.
+        
+        "exitCode" [deprecated, not supported] (Windows only) is the
+            code the terminated process should exit with.
+        "gracePeriod" (Windows only) is a number of seconds the process is
+            allowed to shutdown with a WM_CLOSE signal before a hard
+            terminate is called.
+        "sig" (Unix only) is the signal to use to kill the process. Defaults
+            to signal.SIGKILL. See os.kill() for more information.
+
+        Windows:
+            Try for an orderly shutdown via WM_CLOSE.  If still running
+            after gracePeriod (1 sec. default), terminate.
+        """
+        if sys.platform.startswith("win"):
+            import win32gui
+            # Send WM_CLOSE to windows in this process group.
+            win32gui.EnumWindows(self._close_, 0)
+
+            # Send Ctrl-Break signal to all processes attached to this
+            # console. This is supposed to trigger shutdown handlers in
+            # each of the processes.
+            try:
+                win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
+                                                  self._processId)
+            except AttributeError:
+                log.warn("The win32api module does not have "\
+                         "GenerateConsoleCtrlEvent(). This may mean that "\
+                         "parts of this process group have NOT been killed.")
+            except win32api.error, ex:
+                if ex.args[0] not in (6, 87):
+                    # Ignore the following:
+                    #   api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.')
+                    #   api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.')
+                    # Get error 6 if there is no console.
+                    raise
+            
+            # Last resort: call TerminateProcess if it has not yet.
+            retval = 0
+            try:
+                self.wait(gracePeriod)
+            except ProcessError, ex:
+                log.info("[%s] Process.kill: calling TerminateProcess", id(self))
+                win32process.TerminateProcess(self._hProcess, -1)
+                win32api.Sleep(100) # wait for resources to be released
+
+        else:
+            if sig is None:
+                sig = signal.SIGKILL
+            try:
+                os.kill(self._pid, sig)
+            except OSError, ex:
+                if ex.errno != 3:
+                    # Ignore:   OSError: [Errno 3] No such process
+                    raise
+
+        _unregisterProcess(self)
+
+    def _close_(self, hwnd, dummy):
+        """Callback used by .kill() on Windows.
+
+        EnumWindows callback - sends WM_CLOSE to any window owned by this
+        process.
+        """
+        threadId, processId = win32process.GetWindowThreadProcessId(hwnd)
+        if processId == self._processId:
+            import win32gui
+            win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0)
+
+
+class ProcessProxy(Process):
+    """Create a process and proxy communication via the standard handles.
+    """
+    #XXX To add to docstring:
+    #   - stdout/stderr proxy handling
+    #   - stdin proxy handling
+    #   - termination
+    #   - how to .start(), i.e. basic usage rules
+    #   - mention that pased in stdin/stdout/stderr objects have to
+    #     implement at least .write (is .write correct for stdin)?
+    #   - if you pass in stdin, stdout, and/or stderr streams it is the
+    #     user's responsibility to close them afterwards.
+    #   - 'cmd' arg can be a command string or an arg vector
+    #   - etc.
+    #TODO:
+    #   - .suspend() and .resume()? See Win32::Process Perl module.
+    #
+    def __init__(self, cmd, mode='t', cwd=None, env=None,
+                 stdin=None, stdout=None, stderr=None):
+        """Create a Process with proxy threads for each std handle.
+
+        "cmd" is the command string or argument vector to run.
+        "mode" (Windows only) specifies whether the pipes used to communicate
+            with the child are openned in text, 't', or binary, 'b', mode.
+            This is ignored on platforms other than Windows. Default is 't'.
+        "cwd" optionally specifies the directory in which the child process
+            should be started. Default is None, a.k.a. inherits the cwd from
+            the parent.
+        "env" is optionally a mapping specifying the environment in which to
+            start the child. Default is None, a.k.a. inherits the environment
+            of the parent.
+        "stdin", "stdout", "stderr" can be used to specify objects with
+            file-like interfaces to handle read (stdout/stderr) and write
+            (stdin) events from the child. By default a process.IOBuffer
+            instance is assigned to each handler. IOBuffer may be
+            sub-classed. See the IOBuffer doc string for more information.
+        """
+        # Keep a reference to ensure it is around for this object's destruction.
+        self.__log = log
+        log.info("ProcessProxy.__init__(cmd=%r, mode=%r, cwd=%r, env=%r, "\
+                 "stdin=%r, stdout=%r, stderr=%r)",
+                 cmd, mode, cwd, env, stdin, stdout, stderr)
+        self._cmd = cmd
+        if not self._cmd:
+            raise ProcessError("You must specify a command.")
+        self._mode = mode
+        if self._mode not in ('t', 'b'):
+            raise ProcessError("'mode' must be 't' or 'b'.")
+        self._cwd = cwd
+        self._env = env
+        if stdin is None:
+            self.stdin = IOBuffer(name='<stdin>')
+        else:
+            self.stdin = stdin
+        if stdout is None:
+            self.stdout = IOBuffer(name='<stdout>')
+        else:
+            self.stdout = stdout
+        if stderr is None:
+            self.stderr = IOBuffer(name='<stderr>')
+        else:
+            self.stderr = stderr
+        self._closed = 0
+
+        if sys.platform.startswith("win"):
+            self._startOnWindows()
+        else:
+            self.__retvalCache = None
+            self._startOnUnix()
+
+        _registerProcess(self)
+
+    def __del__(self):
+        #XXX Should probably not rely upon this.
+        logres.info("[%s] ProcessProxy.__del__()", id(self))
+        self.close()
+        del self.__log # drop reference
+
+    def close(self):
+        if not self._closed:
+            self.__log.info("[%s] ProcessProxy.close()" % id(self))
+
+            # Ensure that all IOBuffer's are closed. If they are not, these
+            # can cause hangs. 
+            self.__log.info("[%s] ProcessProxy: closing stdin (%r)."\
+                            % (id(self), self.stdin))
+            try:
+                self.stdin.close()
+                self._stdinProxy.join()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stdin, etc.
+                pass
+            self.__log.info("[%s] ProcessProxy: closing stdout (%r)."\
+                            % (id(self), self.stdout))
+            try:
+                self.stdout.close()
+                if self._stdoutProxy is not threading.currentThread():
+                    self._stdoutProxy.join()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stdout, etc.
+                pass
+            self.__log.info("[%s] ProcessProxy: closing stderr (%r)."\
+                            % (id(self), self.stderr))
+            try:
+                self.stderr.close()
+                if self._stderrProxy is not threading.currentThread():
+                    self._stderrProxy.join()
+            except AttributeError:
+                # May not have gotten far enough in the __init__ to set
+                # self.stderr, etc.
+                pass
+
+            self._closed = 1
+
+    def _forkAndExecChildOnUnix(self, fdChildStdinRd, fdChildStdoutWr,
+                                fdChildStderrWr):
+        """Fork and start the child process.
+
+        Sets self._pid as a side effect.
+        """
+        pid = os.fork()
+        if pid == 0: # child
+            os.dup2(fdChildStdinRd, 0)
+            os.dup2(fdChildStdoutWr, 1)
+            os.dup2(fdChildStderrWr, 2)
+            self._runChildOnUnix()
+        # parent
+        self._pid = pid
+
+    def _startOnUnix(self):
+        # Create pipes for std handles.
+        fdChildStdinRd, fdChildStdinWr = os.pipe()
+        fdChildStdoutRd, fdChildStdoutWr = os.pipe()
+        fdChildStderrRd, fdChildStderrWr = os.pipe()
+
+        if self._cwd:
+            oldDir = os.getcwd()
+            try:
+                os.chdir(self._cwd)
+            except OSError, ex:
+                raise ProcessError(msg=str(ex), errno=ex.errno)
+        self._forkAndExecChildOnUnix(fdChildStdinRd, fdChildStdoutWr,
+                                     fdChildStderrWr)
+        if self._cwd:
+            os.chdir(oldDir)
+
+        os.close(fdChildStdinRd)
+        os.close(fdChildStdoutWr)
+        os.close(fdChildStderrWr)
+
+        childStdin = _FileWrapper(descriptor=fdChildStdinWr)
+        logres.info("[%s] ProcessProxy._start(): create child stdin: %r",
+                    id(self), childStdin)
+        childStdout = _FileWrapper(descriptor=fdChildStdoutRd)
+        logres.info("[%s] ProcessProxy._start(): create child stdout: %r",
+                    id(self), childStdout)
+        childStderr = _FileWrapper(descriptor=fdChildStderrRd)
+        logres.info("[%s] ProcessProxy._start(): create child stderr: %r",
+                    id(self), childStderr)
+
+        # Create proxy threads for the out pipes.
+        self._stdinProxy = _InFileProxy(self.stdin, childStdin, name='<stdin>')
+        self._stdinProxy.start()
+        # Clean up the parent's side of <stdin> when it is observed that
+        # the child has closed its side of <stdout> and <stderr>. (This
+        # is one way of determining when it is appropriate to clean up
+        # this pipe, with compromises. See the discussion at the top of
+        # this module.)
+        closer = _CountingCloser([self.stdin, childStdin, self], 2)
+        self._stdoutProxy = _OutFileProxy(childStdout, self.stdout, 
+                                          [closer],
+                                          name='<stdout>')
+        self._stdoutProxy.start()
+        self._stderrProxy = _OutFileProxy(childStderr, self.stderr,
+                                          [closer],
+                                          name='<stderr>')
+        self._stderrProxy.start()
+
+    def _startOnWindows(self):
+        if type(self._cmd) in (types.ListType, types.TupleType):
+            # An arg vector was passed in.
+            cmd = _joinArgv(self._cmd)
+        else:
+            cmd = self._cmd
+
+        # Create pipes for std handles.
+        # (Set the bInheritHandle flag so pipe handles are inherited.)
+        saAttr = pywintypes.SECURITY_ATTRIBUTES()
+        saAttr.bInheritHandle = 1
+        #XXX Should maybe try with os.pipe. Dunno what that does for
+        #    inheritability though.
+        hChildStdinRd, hChildStdinWr = win32pipe.CreatePipe(saAttr, 0) 
+        hChildStdoutRd, hChildStdoutWr = win32pipe.CreatePipe(saAttr, 0) 
+        hChildStderrRd, hChildStderrWr = win32pipe.CreatePipe(saAttr, 0) 
+
+        try:
+            # Duplicate the parent ends of the pipes so they are not
+            # inherited. 
+            hChildStdinWrDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStdinWr,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStdinWr)
+            self._hChildStdinWr = hChildStdinWrDup
+            hChildStdoutRdDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStdoutRd,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStdoutRd)
+            self._hChildStdoutRd = hChildStdoutRdDup
+            hChildStderrRdDup = win32api.DuplicateHandle(
+                win32api.GetCurrentProcess(),
+                hChildStderrRd,
+                win32api.GetCurrentProcess(),
+                0,
+                0, # not inherited
+                DUPLICATE_SAME_ACCESS)
+            win32api.CloseHandle(hChildStderrRd)
+            self._hChildStderrRd = hChildStderrRdDup
+
+            # Set the translation mode.
+            if self._mode == 't':
+                flags = os.O_TEXT
+                mode = ''
+            else:
+                flags = 0
+                mode = 'b'
+            fdChildStdinWr = msvcrt.open_osfhandle(self._hChildStdinWr, flags)
+            fdChildStdoutRd = msvcrt.open_osfhandle(self._hChildStdoutRd, flags)
+            fdChildStderrRd = msvcrt.open_osfhandle(self._hChildStderrRd, flags)
+
+            childStdin = _FileWrapper(descriptor=fdChildStdinWr,
+                                      handle=self._hChildStdinWr)
+            logres.info("[%s] ProcessProxy._start(): create child stdin: %r",
+                        id(self), childStdin)
+            childStdout = _FileWrapper(descriptor=fdChildStdoutRd,
+                                       handle=self._hChildStdoutRd)
+            logres.info("[%s] ProcessProxy._start(): create child stdout: %r",
+                        id(self), childStdout)
+            childStderr = _FileWrapper(descriptor=fdChildStderrRd,
+                                       handle=self._hChildStderrRd)
+            logres.info("[%s] ProcessProxy._start(): create child stderr: %r",
+                        id(self), childStderr)
+
+            # Start the child process.
+            si = win32process.STARTUPINFO() 
+            si.dwFlags = win32process.STARTF_USESHOWWINDOW
+            si.wShowWindow = 0 # SW_HIDE
+            si.hStdInput = hChildStdinRd
+            si.hStdOutput = hChildStdoutWr
+            si.hStdError = hChildStderrWr
+            si.dwFlags |= win32process.STARTF_USESTDHANDLES
+
+            cmd = _fixupCommand(cmd, self._env)
+            log.debug("cmd = %r", cmd)
+
+            creationFlags = win32process.CREATE_NEW_PROCESS_GROUP
+            try:
+                self._hProcess, hThread, self._processId, threadId\
+                    = _SaferCreateProcess(
+                        None,           # app name
+                        cmd,            # command line 
+                        None,           # process security attributes 
+                        None,           # primary thread security attributes 
+                        1,              # handles are inherited 
+                        creationFlags,  # creation flags 
+                        self._env,      # environment
+                        self._cwd,      # current working directory
+                        si)             # STARTUPINFO pointer 
+            except win32api.error, ex:
+                raise ProcessError(msg=ex.args[2], errno=ex.args[0])
+            win32api.CloseHandle(hThread)
+
+        finally:
+            # Close child ends of pipes on the parent's side (the
+            # parent's ends of the pipe are closed in the _FileWrappers.)
+            win32file.CloseHandle(hChildStdinRd)
+            win32file.CloseHandle(hChildStdoutWr)
+            win32file.CloseHandle(hChildStderrWr)
+
+        # Create proxy threads for the pipes.
+        self._stdinProxy = _InFileProxy(self.stdin, childStdin, name='<stdin>')
+        self._stdinProxy.start()
+        # Clean up the parent's side of <stdin> when it is observed that
+        # the child has closed its side of <stdout>. (This is one way of
+        # determining when it is appropriate to clean up this pipe, with
+        # compromises. See the discussion at the top of this module.)
+        self._stdoutProxy = _OutFileProxy(childStdout, self.stdout, 
+                                          [self.stdin, childStdin, self],
+                                          name='<stdout>')
+        self._stdoutProxy.start()
+        self._stderrProxy = _OutFileProxy(childStderr, self.stderr,
+                                          name='<stderr>')
+        self._stderrProxy.start()
+
+    def wait(self, timeout=None): 
+        """Wait for the started process to complete.
+        
+        "timeout" (on Windows) is a floating point number of seconds after
+            which to timeout.  Default is win32event.INFINITE.
+        "timeout" (on Unix) is akin to the os.waitpid() "options" argument
+            (os.WNOHANG may be used to return immediately if the process has
+            not exited). Default is 0, i.e. wait forever.
+
+        If the wait time's out it will raise a ProcessError. Otherwise it
+        will return the child's exit value (on Windows) or the child's exit
+        status excoded as per os.waitpid() (on Linux):
+            "a 16-bit number, whose low byte is the signal number that killed
+            the process, and whose high byte is the exit status (if the
+            signal number is zero); the high bit of the low byte is set if a
+            core file was produced."
+        In the latter case, use the os.W*() methods to interpret the return
+        value.
+        """
+        # XXX Or should returning the exit value be move out to another
+        #     function as on Win32 process control? If so, then should
+        #     perhaps not make WaitForSingleObject semantic transformation.
+        if sys.platform.startswith("win"):
+            if timeout is None:
+                timeout = win32event.INFINITE
+            else:
+                timeout = timeout * 1000.0 # Win32 API's timeout is in millisecs
+
+            rc = win32event.WaitForSingleObject(self._hProcess, timeout)
+            if rc == win32event.WAIT_FAILED:
+                raise ProcessError("'WAIT_FAILED' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+            elif rc == win32event.WAIT_TIMEOUT:
+                raise ProcessError("'WAIT_TIMEOUT' when waiting for process to "\
+                                   "terminate: %r" % self._cmd, rc)
+
+            retval = win32process.GetExitCodeProcess(self._hProcess)
+        else:
+            # os.waitpid() will raise:
+            #       OSError: [Errno 10] No child processes
+            # on subsequent .wait() calls. Change these semantics to have
+            # subsequent .wait() calls return the exit status and return
+            # immediately without raising an exception.
+            # (XXX It would require synchronization code to handle the case
+            # of multiple simultaneous .wait() requests, however we can punt
+            # on that because it is moot while Linux still has the problem
+            # for which _ThreadFixer() exists.)
+            if self.__retvalCache is not None:
+                retval = self.__retvalCache
+            else:
+                if timeout is None:
+                    timeout = 0
+                pid, sts = os.waitpid(self._pid, timeout)
+                if pid == self._pid:
+                    self.__retvalCache = retval = sts
+                else:
+                    raise ProcessError("Wait for process timed out.",
+                                       self.WAIT_TIMEOUT)
+        _unregisterProcess(self)
+        return retval
+
+    def kill(self, exitCode=0, gracePeriod=1.0, sig=None):
+        """Kill process.
+        
+        "exitCode" [deprecated, not supported] (Windows only) is the
+            code the terminated process should exit with.
+        "gracePeriod" (Windows only) is a number of seconds the process is
+            allowed to shutdown with a WM_CLOSE signal before a hard
+            terminate is called.
+        "sig" (Unix only) is the signal to use to kill the process. Defaults
+            to signal.SIGKILL. See os.kill() for more information.
+
+        Windows:
+            Try for an orderly shutdown via WM_CLOSE.  If still running
+            after gracePeriod (1 sec. default), terminate.
+        """
+        if sys.platform.startswith("win"):
+            import win32gui
+            # Send WM_CLOSE to windows in this process group.
+            win32gui.EnumWindows(self._close_, 0)
+
+            # Send Ctrl-Break signal to all processes attached to this
+            # console. This is supposed to trigger shutdown handlers in
+            # each of the processes.
+            try:
+                win32api.GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
+                                                  self._processId)
+            except AttributeError:
+                log.warn("The win32api module does not have "\
+                         "GenerateConsoleCtrlEvent(). This may mean that "\
+                         "parts of this process group have NOT been killed.")
+            except win32api.error, ex:
+                if ex.args[0] not in (6, 87):
+                    # Ignore the following:
+                    #   api_error: (87, 'GenerateConsoleCtrlEvent', 'The parameter is incorrect.')
+                    #   api_error: (6, 'GenerateConsoleCtrlEvent', 'The handle is invalid.')
+                    # Get error 6 if there is no console.
+                    raise
+            
+            # Last resort: call TerminateProcess if it has not yet.
+            retval = 0
+            try:
+                self.wait(gracePeriod)
+            except ProcessError, ex:
+                log.info("[%s] Process.kill: calling TerminateProcess", id(self))
+                win32process.TerminateProcess(self._hProcess, -1)
+                win32api.Sleep(100) # wait for resources to be released
+
+        else:
+            if sig is None:
+                sig = signal.SIGKILL
+            try:
+                os.kill(self._pid, sig)
+            except OSError, ex:
+                if ex.errno != 3:
+                    # Ignore:   OSError: [Errno 3] No such process
+                    raise
+
+        _unregisterProcess(self)
+
+    def _close_(self, hwnd, dummy):
+        """Callback used by .kill() on Windows.
+
+        EnumWindows callback - sends WM_CLOSE to any window owned by this
+        process.
+        """
+        threadId, processId = win32process.GetWindowThreadProcessId(hwnd)
+        if processId == self._processId:
+            import win32gui
+            win32gui.PostMessage(hwnd, WM_CLOSE, 0, 0)
+
+
+class IOBuffer:
+    """Want to be able to both read and write to this buffer from
+    difference threads and have the same read/write semantics as for a
+    std handler.
+
+    This class is subclass-able. _doRead(), _doWrite(), _doReadline(),
+    _doClose(), _haveLine(), and _haveNumBytes() can be overridden for
+    specific functionality. The synchronization issues (block on read
+    until write provides the needed data, termination) are handled for
+    free.
+
+    Cannot support:
+        .seek()     # Because we are managing *two* positions (one each
+        .tell()     #   for reading and writing), these do not make
+                    #   sense.
+    """
+    #TODO:
+    #   - Is performance a problem? This will likely be slower that
+    #     StringIO.StringIO().
+    #
+    def __init__(self, mutex=None, stateChange=None, name=None):
+        """'name' can be set for debugging, it will be used in log messages."""
+        if name is not None:
+            self._name = name
+        else:
+            self._name = id(self)
+        log.info("[%s] IOBuffer.__init__()" % self._name)
+
+        self.__buf = ''
+        # A state change is defined as the buffer being closed or a
+        # write occuring.
+        if mutex is not None:
+            self._mutex = mutex
+        else:
+            self._mutex = threading.Lock()
+        if stateChange is not None:
+            self._stateChange = stateChange
+        else:
+            self._stateChange = threading.Condition()
+        self._closed = 0
+
+    def _doWrite(self, s):
+        self.__buf += s  # Append to buffer.
+
+    def write(self, s):
+        log.info("[%s] IOBuffer.write(s=%r)", self._name, s)
+        # Silently drop writes after the buffer has been close()'d.
+        if self._closed:
+            return
+        # If empty write, close buffer (mimicking behaviour from
+        # koprocess.cpp.)
+        if not s:
+            self.close()
+            return
+
+        self._mutex.acquire()
+        self._doWrite(s)
+        self._stateChange.acquire()
+        self._stateChange.notifyAll()   # Notify of the write().
+        self._stateChange.release()
+        self._mutex.release()
+
+    def writelines(self, list):
+        self.write(''.join(list))
+
+    def _doRead(self, n):
+        """Pop 'n' bytes from the internal buffer and return them."""
+        if n < 0:
+            idx = len(self.__buf)
+        else:
+            idx = min(n, len(self.__buf))
+        retval, self.__buf = self.__buf[:idx], self.__buf[idx:]
+        return retval
+
+    def read(self, n=-1):
+        log.info("[%s] IOBuffer.read(n=%r)" % (self._name, n))
+        log.info("[%s] IOBuffer.read(): wait for data" % self._name)
+        if n < 0:
+            # Wait until the buffer is closed, i.e. no more writes will
+            # come.
+            while 1:
+                if self._closed: break
+                #log.debug("[%s]     <<< IOBuffer.read: state change .wait()"\
+                #          % self._name)
+                self._stateChange.acquire()
+                self._stateChange.wait()
+                self._stateChange.release()
+                #log.debug("[%s]     >>> IOBuffer.read: done change .wait()"\
+                #          % self._name)
+        else:
+            # Wait until there are the requested number of bytes to read
+            # (or until the buffer is closed, i.e. no more writes will
+            # come).
+            # XXX WARNING: I *think* there is a race condition around
+            #     here whereby self.fparent.read() in _InFileProxy can
+            #     hang. *Sometime* test_stdin::test_stdin_buffer() will
+            #     hang. This was *before* I moved the
+            #     _stateChange.acquire() and .release() calls out side
+            #     of the 'while 1:' here. ...and now they are back
+            #     inside.
+            while 1:
+                if self._closed: break
+                if self._haveNumBytes(n): break
+                #log.debug("[%s]     <<< IOBuffer.read: state change .wait()"\
+                #          % self._name)
+                self._stateChange.acquire()
+                self._stateChange.wait()
+                self._stateChange.release()
+                #log.debug("[%s]     >>> IOBuffer.read: done change .wait()"\
+                #          % self._name)
+        log.info("[%s] IOBuffer.read(): done waiting for data" % self._name)
+
+        self._mutex.acquire()
+        retval = self._doRead(n)
+        self._mutex.release()
+        return retval
+
+    def _doReadline(self, n):
+        """Pop the front line (or n bytes of it, whichever is less) from
+        the internal buffer and return it.
+        """
+        idx = self.__buf.find('\n')
+        if idx == -1:
+            idx = len(self.__buf)
+        else:
+            idx += 1 # include the '\n'
+        if n is not None:
+            idx = min(idx, n) 
+        retval, self.__buf = self.__buf[:idx], self.__buf[idx:]
+        return retval
+
+    def _haveLine(self):
+        return self.__buf.find('\n') != -1
+
+    def _haveNumBytes(self, n=None):
+        return len(self.__buf) >= n
+
+    def readline(self, n=None):
+        # Wait until there is a full line (or at least 'n' bytes)
+        # in the buffer or until the buffer is closed, i.e. no more
+        # writes will come.
+        log.info("[%s] IOBuffer.readline(n=%r)" % (self._name, n))
+
+        log.info("[%s] IOBuffer.readline(): wait for data" % self._name)
+        while 1:
+            if self._closed: break
+            if self._haveLine(): break
+            if n is not None and self._haveNumBytes(n): break
+            self._stateChange.acquire()
+            self._stateChange.wait()
+            self._stateChange.release()
+        log.info("[%s] IOBuffer.readline(): done waiting for data"\
+                 % self._name)
+
+        self._mutex.acquire()
+        retval = self._doReadline(n)
+        self._mutex.release()
+        return retval
+
+    def readlines(self):
+        lines = []
+        while 1:
+            line = self.readline()
+            if line:
+                lines.append(line)
+            else:
+                break
+        return lines
+
+    def _doClose(self):
+        pass
+
+    def close(self):
+        if not self._closed:
+            log.info("[%s] IOBuffer.close()" % self._name)
+            self._doClose()
+            self._closed = 1
+            self._stateChange.acquire()
+            self._stateChange.notifyAll()   # Notify of the close().
+            self._stateChange.release()
+
+    def flush(self):
+        log.info("[%s] IOBuffer.flush()" % self._name)
+        #XXX Perhaps flush() should unwedged possible waiting .read()
+        #    and .readline() calls that are waiting for more data???
+
+
+class _InFileProxy(threading.Thread):
+    """A thread to proxy stdin.write()'s from the parent to the child."""
+    def __init__(self, fParent, fChild, name=None):
+        """
+        "fParent" is a Python file-like object setup for writing.
+        "fChild" is a Win32 handle to the a child process' output pipe.
+        "name" can be set for debugging, it will be used in log messages.
+        """
+        log.info("[%s, %s] _InFileProxy.__init__(fChild=%r, fParent=%r)",
+                 name, id(self), fChild, fParent)
+        threading.Thread.__init__(self, name=name)
+        self.fChild = fChild
+        self.fParent = fParent
+
+    def run(self):
+        log.info("[%s] _InFileProxy: start" % self.getName())
+        try:
+            self._proxyFromParentToChild()
+        finally:
+            log.info("[%s] _InFileProxy: closing parent (%r)"\
+                     % (self.getName(), self.fParent))
+            try:
+                self.fParent.close()
+            except IOError:
+                pass # Ignore: IOError: [Errno 4] Interrupted system call
+        log.info("[%s] _InFileProxy: done" % self.getName())
+
+    def _proxyFromParentToChild(self):
+        CHUNKSIZE = 4096
+        # Read output from the child process, and (for now) just write
+        # it out.
+        while 1:
+            log.info("[%s] _InFileProxy: waiting for read on parent (%r)"\
+                     % (self.getName(), self.fParent))
+            # XXX Get hangs here (!) even with
+            #     self.stdin.close() in ProcessProxy' __del__() under this
+            #     cond:
+            #           p = ProcessProxy([...], stdin=sys.stdin)
+            #     The user must manually send '\n' via <Enter> or EOF
+            #     via <Ctrl-Z> to unlock this. How to get around that?
+            #     See cleanOnTermination note in _OutFileProxy.run()
+            #     below.
+            #log.debug("XXX          -> start read on %r" % self.fParent)
+            try:
+                text = self.fParent.read(CHUNKSIZE)
+            except ValueError, ex:
+                # ValueError is raised with trying to write to a closed
+                # file/pipe.
+                text = None
+            #log.debug("XXX          <- done read on %r" % self.fParent)
+            if not text:
+                # Empty text signifies that the pipe has been closed on
+                # the parent's end.
+                log.info("[%s] _InFileProxy: observed close of parent (%r)"\
+                         % (self.getName(), self.fParent))
+                # Signal the child so it knows to stop listening.
+                try:
+                    logres.info("[%s] _InFileProxy: closing child after "\
+                                "observing parent's close: %r", self.getName(),
+                                self.fChild)
+                    try:
+                        self.fChild.close()
+                    except IOError:
+                        pass # Ignore: IOError: [Errno 4] Interrupted system call
+                except IOError, ex:
+                    # Ignore: IOError: [Errno 9] Bad file descriptor
+                    # XXX Do we *know* we want to do that?
+                    pass
+                break
+            else:
+                log.info("[%s] _InFileProxy: read %d bytes from parent: %r"\
+                         % (self.getName(), len(text), text))
+
+            log.info("[%s, %s] _InFileProxy: writing %r to child (%r)",
+                     self.getName(), id(self), text, self.fChild)
+            try:
+                self.fChild.write(text)
+            except (OSError, IOError), ex:
+                # Ignore errors for now. For example:
+                # - Get this on Win9x when writing multiple lines to "dir":
+                #   OSError: [Errno 32] Broken pipe
+                #XXX There *may* be errors we don't want to avoid.
+                #XXX Should maybe just ignore EnvironmentError (base class).
+                log.info("[%s] _InFileProxy: error writing to child (%r), "\
+                         "closing: %s" % (self.getName(), self.fParent, ex))
+                break
+            log.info("[%s] _InFileProxy: wrote %d bytes to child: %r"\
+                     % (self.getName(), len(text), text))
+
+
+class _OutFileProxy(threading.Thread):
+    """A thread to watch an "out" file from the spawned child process
+    and pass on write's to the parent.
+    """
+    def __init__(self, fChild, fParent, toClose=[], name=None):
+        """
+        "fChild" is a Win32 handle to the a child process' output pipe.
+        "fParent" is a Python file-like object setup for writing.
+        "toClose" is a list of objects on which to call .close when this
+            proxy is terminating.
+        "name" can be set for debugging, it will be used in log messages.
+        """
+        log.info("[%s] _OutFileProxy.__init__(fChild=%r, fParent=%r, "\
+                 "toClose=%r)", name, fChild, fParent, toClose)
+        threading.Thread.__init__(self, name=name)
+        self.fChild = fChild
+        self.fParent = fParent
+        self.toClose = toClose
+
+    def run(self):
+        log.info("[%s] _OutFileProxy: start" % self.getName())
+        try:
+            self._proxyFromChildToParent()
+        finally:
+            logres.info("[%s] _OutFileProxy: terminating, close child (%r)",
+                        self.getName(), self.fChild)
+            try:
+                self.fChild.close()
+            except IOError:
+                pass # Ignore: IOError: [Errno 4] Interrupted system call
+            log.info("[%s] _OutFileProxy: closing parent (%r)",
+                     self.getName(), self.fParent)
+            try:
+                self.fParent.close()
+            except IOError:
+                pass # Ignore: IOError: [Errno 4] Interrupted system call
+            while self.toClose:
+                logres.info("[%s] _OutFileProxy: closing %r after "\
+                            "closing parent", self.getName(), self.toClose[0])
+                try:
+                    self.toClose[0].close()
+                except IOError:
+                    pass # Ignore: IOError: [Errno 4] Interrupted system call
+                del self.toClose[0]
+        log.info("[%s] _OutFileProxy: done" % self.getName())
+
+    def _proxyFromChildToParent(self):
+        CHUNKSIZE = 4096
+        # Read output from the child process, and (for now) just write
+        # it out.
+        while 1:
+            text = None
+            try:
+                log.info("[%s] _OutFileProxy: waiting for read on child (%r)"\
+                         % (self.getName(), self.fChild))
+                text = self.fChild.read(CHUNKSIZE)
+            except IOError, ex:
+                # Ignore: IOError: [Errno 9] Bad file descriptor
+                # XXX Do we *know* we want to do that?
+                log.info("[%s] _OutFileProxy: error reading from child (%r), "\
+                         "shutting down: %s", self.getName(), self.fChild, ex)
+                break
+            if not text:
+                # Empty text signifies that the pipe has been closed on
+                # the child's end.
+                log.info("[%s] _OutFileProxy: observed close of child (%r)"\
+                         % (self.getName(), self.fChild))
+                break
+
+            log.info("[%s] _OutFileProxy: text(len=%d): %r",
+                     self.getName(), len(text), text)
+            self.fParent.write(text)
+
+
+
+if sys.platform.startswith("linux"):
+    class _ThreadFixer:
+        """Mixin class for various classes in the Process hierarchy to
+        work around the known LinuxThreads bug where one cannot .wait()
+        on a created process from a subthread of the thread that created
+        the process.
+
+        Usage:
+            class ProcessXXX(_ThreadFixer, BrokenProcessXXX):
+                _pclass = BrokenProcessXXX
+
+        Details:
+            Because we must do all real os.wait() calls on the child
+            process from the thread that spawned it, we use a proxy
+            thread whose only responsibility is just that. The proxy
+            thread just starts the child and then immediately wait's for
+            the child to terminate. On termination is stores the exit
+            status (for use by the main thread) and notifies any thread
+            waiting for this termination (possibly the main thread). The
+            overriden .wait() uses this stored exit status and the
+            termination notification to simulate the .wait().
+        """
+        def __init__(self, *args, **kwargs):
+            # Keep a reference to 'log' ensure it is around for this object's
+            # destruction.
+            self.__log = log
+            self.__waiter = None
+            self.__hasTerminated = threading.Condition()
+            self.__terminationResult = None
+            self.__childStarted = threading.Condition()
+            self._pclass.__init__(self, *args, **kwargs)
+
+        def _forkAndExecChildOnUnix(self, *args, **kwargs):
+            """Fork and start the child process do it in a special subthread
+            that will negotiate subsequent .wait()'s.
+
+            Sets self._pid as a side effect.
+            """
+            self.__waiter = threading.Thread(
+                target=self.__launchAndWait, args=args, kwargs=kwargs)
+
+            # Start subthread that will launch child and wait until it
+            # *has* started.
+            self.__childStarted.acquire()
+            self.__waiter.start()
+            self.__childStarted.wait()
+            self.__childStarted.release()
+
+        def __launchAndWait(self, *args, **kwargs):
+            """Launch the given command and wait for it to terminate.
+
+            When the process has terminated then store its exit value
+            and finish.
+            """
+            logfix.info("start child in thread %s",
+                        threading.currentThread().getName())
+
+            # Spawn the child process and notify the main thread of
+            # this.
+            self.__childStarted.acquire()
+            self._pclass._forkAndExecChildOnUnix(self, *args, **kwargs)
+            self.__childStarted.notifyAll()
+            self.__childStarted.release()
+
+            # Wait on the thread and store appropriate results when
+            # finished.
+            try:
+                waitResult = self._pclass.wait(self)
+            except ProcessError, ex:
+                waitResult = ex
+            self.__hasTerminated.acquire()
+            self.__terminationResult = waitResult
+            self.__hasTerminated.notifyAll()
+            self.__hasTerminated.release()
+
+            self.__waiter = None # drop ref that would keep instance alive
+        
+        def wait(self, timeout=None): 
+            # If the process __hasTerminated then return the exit
+            # status. Otherwise simulate the wait as appropriate.
+            # Note:
+            #   - This class is only used on linux so 'timeout' has the
+            #     Unix 'timeout' semantics.
+            self.__hasTerminated.acquire()
+            if self.__terminationResult is None:
+                if timeout == os.WNOHANG:   # Poll.
+                    self.__hasTerminated.wait(0)
+                else:                       # Block until process finishes.
+                    self.__hasTerminated.wait()
+            terminationResult = self.__terminationResult
+            self.__hasTerminated.release()
+
+            if terminationResult is None:
+                # process has not finished yet
+                raise ProcessError("Wait for process timed out.",
+                                   self.WAIT_TIMEOUT)
+            elif isinstance(terminationResult, Exception):
+                # some error waiting for process termination
+                raise terminationResult
+            else:
+                # the process terminated
+                return terminationResult
+
+    _ThreadBrokenProcess = Process
+    class Process(_ThreadFixer, _ThreadBrokenProcess):
+        _pclass = _ThreadBrokenProcess
+
+    _ThreadBrokenProcessOpen = ProcessOpen
+    class ProcessOpen(_ThreadFixer, _ThreadBrokenProcessOpen):
+        _pclass = _ThreadBrokenProcessOpen
+
+    _ThreadBrokenProcessProxy = ProcessProxy
+    class ProcessProxy(_ThreadFixer, _ThreadBrokenProcessProxy):
+        _pclass = _ThreadBrokenProcessProxy
+
+
diff --git a/wxPython/samples/ide/activegrid/util/__init__.py b/wxPython/samples/ide/activegrid/util/__init__.py
new file mode 100644 (file)
index 0000000..905f93a
--- /dev/null
@@ -0,0 +1,72 @@
+import logging
+import cStringIO
+import traceback
+import sys
+import string
+import os
+
+def classForName(className):
+    pathList = className.split('.')
+    moduleName = string.join(pathList[:-1], '.')
+    code = __import__(moduleName)
+    for name in pathList[1:]:
+        code = code.__dict__[name]
+    return code
+
+def hasattrignorecase(object, name):
+    for attr in dir(object):
+        if attr.lower() == name.lower():
+            return True
+    for attr in dir(object):
+        if attr.lower() == '_' + name.lower():
+            return True
+    return False
+
+
+def setattrignorecase(object, name, value):
+    for attr in object.__dict__:
+        if attr.lower() == name.lower():
+            object.__dict__[attr] = value
+            return
+##    for attr in dir(object):
+##        if attr.lower() == '_' + name.lower():
+##            object.__dict__[attr] = value
+##            return
+    object.__dict__[name] = value
+
+def getattrignorecase(object, name):
+    for attr in object.__dict__:
+        if attr.lower() == name.lower():
+            return object.__dict__[attr]
+##    for attr in dir(object):
+##        if attr.lower() == '_' + name.lower():
+##            return object.__dict__[attr]
+    return object.__dict__[name]
+
+
+def defaultLoad(fileObject):
+    xml = fileObject.read()
+    loadedObject = xmlmarshaller.unmarshal(xml)
+    if hasattr(fileObject, 'name'):
+        loadedObject.fileName = os.path.abspath(fileObject.name)
+    loadedObject.initialize()
+    return loadedObject
+
+def defaultSave(fileObject, objectToSave):
+    xml = xmlmarshaller.marshal(objectToSave, prettyPrint=True)
+    fileObject.write(xml)
+   
+def clone(objectToClone):
+    xml = xmlmarshaller.marshal(objectToClone, prettyPrint=True)
+    clonedObject = xmlmarshaller.unmarshal(xml)
+    if hasattr(objectToClone, 'fileName'):
+        clonedObject.fileName = objectToClone.fileName
+    clonedObject.initialize()
+    return clonedObject
+    
+def exceptionToString(e):
+    sio = cStringIO.StringIO()
+    traceback.print_exception(e.__class__, e, sys.exc_traceback, file=sio)
+    return sio.getvalue()
+
diff --git a/wxPython/samples/ide/activegrid/util/aglogging.py b/wxPython/samples/ide/activegrid/util/aglogging.py
new file mode 100644 (file)
index 0000000..880ff82
--- /dev/null
@@ -0,0 +1,84 @@
+#----------------------------------------------------------------------------
+# Name:         aglogging.py
+# Purpose:      Utilities to help with logging
+#
+# Author:       Jeff Norton
+#
+# Created:      01/04/05
+# CVS-ID:       $Id$
+# Copyright:    (c) 2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import sys
+import os
+import re
+import traceback
+
+global agTestMode
+agTestMode = False
+
+def setTestMode(mode):
+    global agTestMode
+    if (mode):
+        agTestMode = True
+    else:
+        agTestMode = False
+        
+def getTestMode():
+    global agTestMode
+    return agTestMode
+    
+def testMode(normalObj, testObj=None):
+    if getTestMode():
+        return testObj
+    return normalObj
+
+def toDiffableString(value):
+    s = repr(value)
+    ds = ""
+    i = s.find(" at 0x") 
+    start = 0
+    while (i >= 0):
+        j = s.find(">", i)
+        if (j < i):
+            break
+        ds += s[start:i]
+        start = j
+        i = s.find(" at 0x", start) 
+    return ds + s[start:]
+    
+def removeFileRefs(str):
+    str = re.sub(r'(?<=File ")[^"]*(\\[^\\]*")(, line )[0-9]*', _fileNameReplacement, str)
+    return str
+    
+def _fileNameReplacement(match):
+    return "...%s" % match.group(1)
+
+def getTraceback():
+    extype, val, tb = sys.exc_info()
+    tbs = "\n"
+    for s in traceback.format_tb(tb):
+        tbs += s
+    return tbs
+
+def reportException(out=None, stacktrace=False, diffable=False):
+    extype, val, t = sys.exc_info()
+    if (diffable):
+        exstr = removeFileRefs(str(val))
+    else:
+        exstr = str(val)
+    if (out == None):
+        print "Got Exception = %s: %s" % (extype, exstr)
+    else:
+        print >> out, "Got Exception = %s: %s" % (extype, exstr)
+    if (stacktrace):
+        fmt = traceback.format_exception(extype, val, t)
+        for s in fmt:
+            if (diffable):
+                s = removeFileRefs(s)
+            if (out == None):
+                print s
+            else:
+                print >> out, s
+            
diff --git a/wxPython/samples/ide/activegrid/util/cachedloader.py b/wxPython/samples/ide/activegrid/util/cachedloader.py
new file mode 100644 (file)
index 0000000..f0fc69d
--- /dev/null
@@ -0,0 +1,87 @@
+#----------------------------------------------------------------------------
+# Name:         cachedloader.py
+# Purpose:
+#
+# Author:       Joel Hare
+#
+# Created:      8/31/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import copy
+import os.path
+import string
+import cStringIO
+
+import time
+
+# TODO: Instantiate the database and create a pool
+
+
+class CachedLoader(object):
+    def __init__(self):
+        self.cache = {}
+        self.baseLoadDir = None
+        
+    def fullPath(self, fileName):
+        if os.path.isabs(fileName):
+            absPath = fileName
+        elif self.baseLoadDir:
+            absPath = os.path.join(self.baseLoadDir, fileName)
+        else:
+            absPath = os.path.abspath(fileName)
+        return absPath
+        
+    def setPrototype(self, fileName, loadedFile):
+        absPath = self.fullPath(fileName)
+        mtime = time.time() + 31536000.0 # Make sure prototypes aren't replaced by files on disk
+        self.cache[absPath] = (mtime, loadedFile)
+
+    def update(self, loader):
+        self.cache.update(loader.cache)
+        
+    def clear(self):
+        self.cache.clear()
+
+    def delete(self, fileName):
+        absPath = self.fullPath(fileName)
+        del self.cache[absPath]
+        
+    def needsLoad(self, fileName):
+        absPath = self.fullPath(fileName)
+        try:
+            cached = self.cache[absPath]
+            cachedTime = cached[0]
+            if cachedTime >= os.path.getmtime(absPath):
+                return False
+        except KeyError:
+            pass
+        return True
+        
+    def load(self, fileName, loader):
+        absPath = self.fullPath(fileName)
+        loadedFile = None
+        try:
+            cached = self.cache[absPath]
+        except KeyError:
+            cached = None
+    
+        if cached:
+            cachedTime = cached[0]
+            # ToDO We might need smarter logic for checking if a file needs to be reloaded
+            # ToDo We need a way to disable checking if this is a production server
+            if cachedTime >= os.path.getmtime(absPath):
+                loadedFile = cached[1]
+    
+        if not loadedFile:
+            targetFile = file(absPath)
+            try:
+                mtime = os.path.getmtime(absPath)
+                loadedFile = loader(targetFile)
+                self.cache[absPath] = (mtime, loadedFile)
+            finally:
+                targetFile.close()
+        return loadedFile
+
diff --git a/wxPython/samples/ide/activegrid/util/dependencymgr.py b/wxPython/samples/ide/activegrid/util/dependencymgr.py
new file mode 100644 (file)
index 0000000..272e8eb
--- /dev/null
@@ -0,0 +1,145 @@
+#----------------------------------------------------------------------------
+# Name:         dependencymgr.py
+# Purpose:      Dependency Manager
+#
+# Author:       Jeff Norton
+#
+# Created:      01/28/05
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+#----------------------------------------------------------------------------
+
+DM_NO_ID = 0
+DM_ID_ATTR = "_DependencyMgr__ID"
+
+##class ManageableObject(object):
+##    
+##    def __init__(self):
+##        self.__id = DM_NO_ID
+##        
+##    def __repr__(self):
+##        return "<ManageableObject id = %s>" % self.__id
+##        
+##    def __getID(self):
+##        return self.__id
+##        
+##    def __setID(self, value):
+##        if (self.__id != DM_NO_ID):
+##            raise DependencyMgrException("Cannot set the dependency ID on object %s to \"%s\" because it already has one (\"%s\")." % (repr(self), value, self.__id))
+##        self.__id = value
+##        
+##    _DependencyMgr__ID = property(__getID, __setID)
+        
+class DependencyMgr(object):
+    
+    def __init__(self):
+        self.clear()
+        
+    def clear(self):
+        self.__dependencies = {}
+        self.__lastID = DM_NO_ID
+        
+    def addDependency(self, parent, child):
+        pid = self._initObjectID(parent)
+        try:
+            parentCollection = self.__dependencies[pid]
+        except KeyError:
+            parentCollection = self._newDependencyCollection()
+            self.__dependencies[pid] = parentCollection
+        if (child not in parentCollection):
+            parentCollection.append(child)
+            
+    def removeDependency(self, parent, child):
+        pid = self._getObjectID(parent)
+        if (pid != DM_NO_ID):
+            try:
+                parentCollection = self.__dependencies[pid]
+                parentCollection.remove(child)
+                if (len(parentCollection) == 0):
+                    del self.__dependencies[pid]
+            except KeyError, ValueError:
+                pass
+
+    def clearDependencies(self, parent):
+        "Returns a list of objects or an empty list if no dependencies exist as for getDependencies, and then removes the dependency list."
+        pid = self._getObjectID(parent)
+        try:
+            deps = self.__dependencies[pid]
+            del self.__dependencies[pid]
+            return deps
+        except KeyError:
+            return []
+
+    def hasDependency(self, parent):
+        "Returns a boolean"
+        return (self._getObjectID(parent) in self.__dependencies)
+        
+    def getDependencies(self, parent):
+        "Returns a list of objects or an empty list if no dependencies exist."
+        try:
+            return self.__dependencies[self._getObjectID(parent)]
+        except KeyError:
+            return []
+            
+    def dumpState(self, out):
+        "Writes the state of the dependency manager (as reported by getState) to out"
+        for line in self.getState():
+            print >> out, line
+        
+    def getState(self):
+        "Returns the state of the dependency manager including all managed objects as a list of strings"
+        out = []
+        out.append("DependencyMgr %s has %i parent objects, last id assigned is %i" % (repr(self), len(self.__dependencies), self.__lastID))
+        for key, val in self.__dependencies.iteritems():
+            out.append("Object %s has dependents: %s " % (repr(key), ", ".join([repr(d) for d in val])))
+        return out
+
+    def _initObjectID(self, obj):
+        try:
+            id = getattr(obj, DM_ID_ATTR)
+        except AttributeError:
+            id = DM_NO_ID
+        if (id == DM_NO_ID):
+            id = self._newID()
+            setattr(obj, DM_ID_ATTR, id)
+        return id
+        
+    def _getObjectID(self, obj):
+        try:
+            id = getattr(obj, DM_ID_ATTR)
+        except AttributeError:
+            id = DM_NO_ID
+        return id
+    
+    def _newID(self):
+        self.__lastID += 1
+        return self.__lastID
+        
+    def _newDependencyCollection(self):
+        return []
+        
+globalDM = DependencyMgr()
+
+def addDependency(parent, child):
+    getGlobalDM().addDependency(parent, child)
+
+def removeDependency(parent, child):
+    getGlobalDM().removeDependency(parent, child)
+
+def clearDependencies(parent):
+    return getGlobalDM().clearDependencies(parent)
+    
+def hasDependency(parent):
+    return getGlobalDM().hasDependency(parent)
+    
+def getDependencies(parent):
+    return getGlobalDM().getDependencies(parent)
+    
+def getState():
+    return getGlobalDM().getState()
+    
+def dumpState(out):
+    getGlobalDM().dumpState(out)
+
+def getGlobalDM():
+    return globalDM
diff --git a/wxPython/samples/ide/activegrid/util/fileutils.py b/wxPython/samples/ide/activegrid/util/fileutils.py
new file mode 100644 (file)
index 0000000..7d334c7
--- /dev/null
@@ -0,0 +1,39 @@
+#----------------------------------------------------------------------------
+# Name:         fileutils.py
+# Purpose:      Active grid miscellaneous utilities
+#
+# Author:       Jeff Norton
+#
+# Created:      12/10/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import os
+
+def createFile(filename, mode='w'):
+    f = None
+    try:
+        f = file(filename, mode)
+    except:
+        os.makedirs(filename[:filename.rindex(os.sep)])
+        f = file(filename, mode)
+    return f
+
+def compareFiles(file1, file2):
+    file1.seek(0)
+    file2.seek(0)
+    while True:
+        line1 = file1.readline()
+        line2 = file2.readline()
+        if (len(line1) == 0):
+            if (len(line2) == 0):
+                return 0
+            else:
+                return -1
+        elif (len(line2) == 0):
+            return -1
+        elif (line1 != line2):
+            return -1
+
diff --git a/wxPython/samples/ide/activegrid/util/gettersetter.py b/wxPython/samples/ide/activegrid/util/gettersetter.py
new file mode 100644 (file)
index 0000000..34d6248
--- /dev/null
@@ -0,0 +1,36 @@
+#----------------------------------------------------------------------------
+# Name:         gettersetter.py
+# Purpose:
+#
+# Author:       Peter Yared
+#
+# Created:      7/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+def gettersetter(list):
+    for attr in list:
+        lowercase = attr[0].lower() + attr[1:]
+        uppercase = attr[0].upper() + attr[1:]
+        print "    def get%s(self):" % uppercase
+        print "        return self._%s" % lowercase
+        print
+        print "    def set%s(self, %s):" % (uppercase, lowercase)
+        print "        self._%s = %s" % (lowercase, lowercase)
+        print
+
+def listgettersetter(list):
+    for attr in list:
+        lowercase = attr[0].lower() + attr[1:]
+        uppercase = attr[0].upper() + attr[1:]
+        print "    def get%s(self):" % uppercase
+        print "        return self._%s" % lowercase
+        print
+        print "    def add%s(self, %s):" % (uppercase[:-1], lowercase[:-1])
+        print "        self._%s.append(%s)" % (lowercase, lowercase[:-1])
+        print
+        print "    def remove%s(self, %s):" % (uppercase[:-1], lowercase[:-1])
+        print "        self._%s.remove(%s)" % (lowercase, lowercase[:-1])
+        print
+        
diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshaller.py b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py
new file mode 100644 (file)
index 0000000..f305f0a
--- /dev/null
@@ -0,0 +1,649 @@
+#----------------------------------------------------------------------------
+# Name:         xmlmarshaller.py
+# Purpose:
+#
+# Author:       John Spurling
+#
+# Created:      7/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+from activegrid import util
+import inspect
+from types import *
+import xml.sax
+import xml.sax.handler
+import __builtin__
+from xml.sax import saxutils
+
+### ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed
+
+"""
+
+More documentation later, but here are some special Python attributes
+that McLane recognizes:
+
+name: __xmlname__
+type: string
+description: the name of the xml element for the marshalled object
+
+name: __xmlattributes__
+type: tuple or list
+description: the name(s) of the Python string attribute(s) to be
+marshalled as xml attributes instead of nested xml elements. currently
+these can only be strings since there's not a way to get the type
+information back when unmarshalling.
+
+name: __xmlexclude__
+type: tuple or list
+description: the name(s) of the python attribute(s) to skip when
+marshalling.
+
+name: __xmlrename__
+type: dict
+description: describes an alternate Python <-> XML name mapping.  
+Normally the name mapping is the identity function.  __xmlrename__
+overrides that.  The keys are the Python names, the values are their
+associated XML names.
+
+name: __xmlflattensequence__
+type: dict, tuple, or list
+description: the name(s) of the Python sequence attribute(s) whose
+items are to be marshalled as a series of xml elements (with an
+optional keyword argument that specifies the element name to use) as
+opposed to containing them in a separate sequence element, e.g.:
+
+myseq = (1, 2)
+<!-- normal way of marshalling -->
+<myseq>
+  <item objtype='int'>1</item>
+  <item objtype='int'>2</item>
+</myseq>
+<!-- with __xmlflattensequence__ set to {'myseq': 'squish'} -->
+<squish objtype='int'>1</squish>
+<squish objtype='int'>2</squish>
+
+name: __xmlnamespaces__
+type: dict
+description: a dict of the namespaces that the object uses.  Each item
+in the dict should consist of a prefix,url combination where the key is
+the prefix and url is the value, e.g.:
+
+__xmlnamespaces__ = { "xsd":"http://www.w3c.org/foo.xsd" }
+
+name: __xmldefaultnamespace__
+type: String
+description: the prefix of a namespace defined in __xmlnamespaces__ that
+should be used as the default namespace for the object.
+
+name: __xmlattrnamespaces__
+type: dict
+description: a dict assigning the Python object's attributes to the namespaces
+defined in __xmlnamespaces__.  Each item in the dict should consist of a
+prefix,attributeList combination where the key is the prefix and the value is
+a list of the Python attribute names.  e.g.:
+
+__xmlattrnamespaces__ = { "ag":["firstName", "lastName", "addressLine1", "city"] }
+
+
+"""
+
+################################################################################
+#
+# module exceptions
+#
+################################################################################
+
+class Error(Exception):
+    """Base class for errors in this module."""
+    pass
+
+class UnhandledTypeException(Error):
+    """Exception raised when attempting to marshal an unsupported
+    type.
+    """
+    def __init__(self, typename):
+        self.typename = typename
+    def __str__(self):
+        return "%s is not supported for marshalling." % str(self.typename)
+
+class XMLAttributeIsNotStringType(Error):
+    """Exception raised when an object's attribute is specified to be
+    marshalled as an XML attribute of the enclosing object instead of
+    a nested element.
+    """
+    def __init__(self, attrname, typename):
+        self.attrname = attrname
+        self.typename = typename
+    def __str__(self):
+        return """%s was set to be marshalled as an XML attribute
+        instead of a nested element, but the object's type is %s, not
+        string.""" % (self.attrname, self.typename)
+
+################################################################################
+#
+# constants and such
+#
+################################################################################
+
+XMLNS = 'xmlns'
+XMLNS_PREFIX = XMLNS + ':'
+XMLNS_PREFIX_LENGTH = len(XMLNS_PREFIX)
+
+BASETYPE_ELEMENT_NAME = 'item'
+MEMBERS_TO_SKIP = ('__module__', '__doc__', '__xmlname__', '__xmlattributes__',
+                   '__xmlexclude__', '__xmlflattensequence__', '__xmlnamespaces__',
+                   '__xmldefaultnamespace__', '__xmlattrnamespaces__')
+
+WELL_KNOWN_OBJECTS = { "xs:element"     : "activegrid.model.schema.XsdElement",
+                       "xs:complexType" : "activegrid.model.schema.XsdComplexType",
+                       "xs:complexType" : "activegrid.model.schema.XsdComplexType",
+                       "xs:element"     : "activegrid.model.schema.XsdElement",
+                       "xs:key"         : "activegrid.model.schema.XsdKey",
+                       "xs:field"       : "activegrid.model.schema.XsdKeyField",
+                       "xs:keyref"      : "activegrid.model.schema.XsdKeyRef",
+                       "xs:selector"    : "activegrid.model.schema.XsdKeySelector",              
+                       "xs:schema"      : "activegrid.model.schema.Schema",
+                       "ag:schemaOptions":"activegrid.model.schema.SchemaOptions",
+                       "ag:debug"       : "activegrid.model.processmodel.DebugOperation",
+                     }
+
+
+################################################################################
+#
+# classes and functions
+#
+################################################################################
+
+def _objectfactory(objname, objargs=None, xsname=None):
+    try:
+        '''dynamically create an object based on the objname and return
+        it. look it up in the BASETYPE_ELEMENT_MAP first.
+        '''
+##        print "_objectfactory creating an object of type %s and value %s, xsname=%s" % (objname, objargs, xsname)
+        # split the objname into the typename and module path,
+        # importing the module if need be.
+        if not isinstance(objargs, list):
+            objargs = [objargs]
+            
+        if (xsname):
+            try:
+                objname = WELL_KNOWN_OBJECTS[xsname]
+            except KeyError:
+                pass
+
+        objtype = objname.split('.')[-1]
+        pathlist = objname.split('.')
+        modulename = '.'.join(pathlist[0:-1])
+
+##        print "[objectfactory] objtype is %s" % objtype
+##        print "[objectfactory] objargs is %s" % `objargs`
+
+        ## since the bool constructor will turn a string of non-zero
+        ## length into True, we call it with no argument (yielding a
+        ## False) if the string contains 'false'
+        if objtype == 'bool' and objargs[0].lower() == 'false':
+            objargs = None
+
+##        if objtype == 'str':
+##            print type(objargs)
+##            print "string we're unescaping: '%s'" % objargs[0]
+##            objargs = saxutils.unescape(objargs[0])
+        if objtype in ('float', 'int', 'str', 'long'):
+            objargs = [x.strip() for x in objargs]
+
+        if objtype == 'str':
+            objargs = [saxutils.unescape(x) for x in objargs]
+
+        if __builtin__.__dict__.has_key(objname):
+            module = __builtin__
+        else:
+            if modulename:
+                module = __import__(modulename)
+            for name in pathlist[1:-1]:
+                module = module.__dict__[name]
+        if objargs:
+            return module.__dict__[objtype](*objargs)
+        else:
+            if objtype == 'None':
+                return None
+            return module.__dict__[objtype]()
+    except KeyError:
+        raise KeyError("Could not find class %s" % objname)
+
+class Element:
+    def __init__(self, name, attrs=None):
+        self.name = name
+        self.attrs = attrs
+        self.content = ''
+        self.children = []
+    def getobjtype(self):
+        if self.attrs.has_key('objtype'):
+            return self.attrs.getValue('objtype')
+        else:
+            return 'str'
+
+
+class XMLObjectFactory(xml.sax.ContentHandler):
+    def __init__(self):
+        self.rootelement = None
+        self.elementstack = []
+        xml.sax.handler.ContentHandler.__init__(self)
+
+    ## ContentHandler methods
+    def startElement(self, name, attrs):
+        if name.find(':') > -1:  # Strip namespace prefixes for now until actually looking them up in xsd
+            name = name[name.index(':') + 1:]
+##        for attrname in attrs.getNames():
+##            print "%s: %s" % (attrname, attrs.getValue(attrname))
+        element = Element(name, attrs.copy())
+        self.elementstack.append(element)
+##        print self.elementstack
+
+    def characters(self, content):
+##        print "got content: %s" % content
+        if content:
+            self.elementstack[-1].content += content
+
+    def endElement(self, name):
+##        print "[endElement] name of element we're at the end of: %s" % name
+        xsname = name
+        if name.find(':') > -1:  # Strip namespace prefixes for now until actually looking them up in xsd
+            name = name[name.index(':') + 1:]
+        element = self.elementstack.pop()
+        objtype = element.getobjtype()
+        constructorarglist = []
+        if element.content:
+            strippedElementContent = element.content.strip()
+            if strippedElementContent:
+                constructorarglist.append(element.content)
+        obj = _objectfactory(objtype, constructorarglist, xsname)
+        complexType = None
+        if hasattr(obj, '__xsdcomplextype__'):
+            complexType = getattr(obj, '__xsdcomplextype__')
+        if len(self.elementstack) > 0:
+            self.elementstack[-1].children.append((name, obj))
+        else:
+            self.rootelement = obj
+        if element.attrs and not isinstance(obj, list):
+            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 not hasattr(obj, '__xmlnamespaces__'):
+                        obj.__xmlnamespaces__ = {ns:attr}
+                    elif ns not in obj.__xmlnamespaces__:
+                        if (hasattr(obj.__class__, '__xmlnamespaces__') 
+                           and obj.__xmlnamespaces__ is obj.__class__.__xmlnamespaces__):
+                                obj.__xmlnamespaces__ = dict(obj.__xmlnamespaces__)
+                        obj.__xmlnamespaces__[ns] = attr
+                elif not attrname == 'objtype':
+                    if attrname.find(':') > -1:  # Strip namespace prefixes for now until actually looking them up in xsd
+                        attrname = attrname[attrname.index(':') + 1:]
+                    if complexType:
+                        xsdElement = complexType.findElement(attrname)
+                        if xsdElement:
+                            type = xsdElement.type
+                            if type:
+                                type = xsdToPythonType(type)
+                                ### ToDO remove maxOccurs hack after bug 177 is fixed
+                                if attrname == "maxOccurs" and attr == "unbounded":
+                                    attr = "-1"
+                                attr = _objectfactory(type, attr)
+                    util.setattrignorecase(obj, _toAttrName(obj, attrname), attr)
+##                    obj.__dict__[_toAttrName(obj, attrname)] = attr
+        # stuff any child attributes meant to be in a sequence via the __xmlflattensequence__
+        flattenDict = {}
+        if hasattr(obj, '__xmlflattensequence__'):
+            for sequencename, xmlnametuple in obj.__xmlflattensequence__.items():
+                for xmlname in xmlnametuple:
+                    flattenDict[xmlname] = sequencename
+
+        # reattach an object's attributes to it
+        for childname, child in element.children:
+            if flattenDict.has_key(childname):
+                sequencename = _toAttrName(obj, flattenDict[childname])
+                try:
+                    sequencevalue = obj.__dict__[sequencename]
+                except AttributeError:
+                    sequencevalue = None
+                if sequencevalue == None:
+                    sequencevalue = []
+                    obj.__dict__[sequencename] = sequencevalue
+                sequencevalue.append(child)
+            elif isinstance(obj, list):
+                obj.append(child)
+            else:
+##                print "childname = %s, obj = %s, child = %s" % (childname, repr(obj), repr(child))
+                util.setattrignorecase(obj, _toAttrName(obj, childname), child)
+##                obj.__dict__[_toAttrName(obj, childname)] = child
+
+        if complexType:
+            for element in complexType.elements:
+                if element.default:
+                    elementName = _toAttrName(obj, element.name)
+                    if ((elementName not in obj.__dict__) or (obj.__dict__[elementName] == None)):
+                        pythonType = xsdToPythonType(element.type)
+                        defaultValue = _objectfactory(pythonType, element.default)
+                        obj.__dict__[elementName] = defaultValue
+
+    def getRootObject(self):
+        return self.rootelement
+
+def _toAttrName(obj, name):
+    if (hasattr(obj, "__xmlrename__")):
+        for key, val in obj.__xmlrename__.iteritems():
+            if (name == val):
+                name = key
+                break
+##    if (name.startswith("__") and not name.endswith("__")):
+##        name = "_%s%s" % (obj.__class__.__name__, name)
+    return name
+
+__typeMappingXsdToPython = {
+    "string": "str",
+    "char": "str",
+    "varchar": "str",
+    "date": "str", # ToDO Need to work out how to create python date types
+    "boolean": "bool",
+    "decimal": "float", # ToDO Does python have a better fixed point type?
+    "int": "int",
+    "long": "long",
+    "float": "float",
+    "bool": "bool",
+    "str": "str",
+    "unicode": "unicode",
+    }    
+
+def xsdToPythonType(xsdType):
+    try:
+        return __typeMappingXsdToPython[xsdType]
+    except KeyError:
+        raise Exception("Unknown xsd type %s" % xsdType)
+
+def _getXmlValue(pythonValue):
+    if (isinstance(pythonValue, bool)):
+        return str(pythonValue).lower()
+    else:
+        return str(pythonValue)
+
+def unmarshal(xmlstr):
+    objectfactory = XMLObjectFactory()
+    xml.sax.parseString(xmlstr, objectfactory)
+    return objectfactory.getRootObject()
+
+
+def marshal(obj, elementName=None, nameSpacePrefix='', nameSpaces=None, prettyPrint=False, indent=0):
+    if prettyPrint or indent:
+        prefix = ' '*indent
+        newline = '\n'
+        increment = 4
+    else:
+        prefix = ''
+        newline = ''
+        increment = 0
+
+    ## Determine the XML element name. If it isn't specified in the
+    ## parameter list, look for it in the __xmlname__ Python
+    ## attribute, else use the default generic BASETYPE_ELEMENT_NAME.
+    if not nameSpaces: nameSpaces = {}  # Need to do this since if the {} is a default parameter it gets shared by all calls into the function
+    nameSpaceAttrs = ''
+    if hasattr(obj, '__xmlnamespaces__'):
+        for nameSpaceKey, nameSpaceUrl in getattr(obj, '__xmlnamespaces__').items():
+            if nameSpaceUrl in nameSpaces:
+                nameSpaceKey = nameSpaces[nameSpaceUrl]
+            else:
+##                # TODO: Wait to do this until there is shared state for use when going through the object graph
+##                origNameSpaceKey = nameSpaceKey  # Make sure there is no key collision, ie: same key referencing two different URL's
+##                i = 1
+##                while nameSpaceKey in nameSpaces.values():
+##                    nameSpaceKey = origNameSpaceKey + str(i)
+##                    i += 1
+                nameSpaces[nameSpaceUrl] = nameSpaceKey
+                if nameSpaceKey == '':
+                    nameSpaceAttrs += ' xmlns="%s" ' % (nameSpaceUrl)
+                else:
+                    nameSpaceAttrs += ' xmlns:%s="%s" ' % (nameSpaceKey, nameSpaceUrl)
+        nameSpaceAttrs = nameSpaceAttrs.rstrip()
+    if hasattr(obj, '__xmldefaultnamespace__'):
+        nameSpacePrefix = getattr(obj, '__xmldefaultnamespace__') + ':'
+    if not elementName:
+        if hasattr(obj, '__xmlname__'):
+            elementName = nameSpacePrefix + obj.__xmlname__
+        else:
+            elementName = nameSpacePrefix + BASETYPE_ELEMENT_NAME
+    else:
+        elementName = nameSpacePrefix + elementName
+        
+    members_to_skip = []
+    ## Add more members_to_skip based on ones the user has selected
+    ## via the __xmlexclude__ attribute.
+    if hasattr(obj, '__xmlexclude__'):
+        members_to_skip += list(obj.__xmlexclude__)
+    # Marshal the attributes that are selected to be XML attributes.
+    objattrs = ''
+    className = obj.__class__.__name__
+    classNamePrefix = "_" + className
+    if hasattr(obj, '__xmlattributes__'):
+        xmlattributes = obj.__xmlattributes__
+        members_to_skip += xmlattributes
+        for attr in xmlattributes:
+            internalAttrName = attr
+            if (attr.startswith("__") and not attr.endswith("__")): 
+                internalAttrName = classNamePrefix + attr
+            # Fail silently if a python attribute is specified to be
+            # an XML attribute but is missing.
+            try:
+                value = obj.__dict__[internalAttrName]
+            except KeyError:
+                continue
+##                # But, check and see if it is a property first:
+##                if (hasPropertyValue(obj, attr)):
+##                    value = getattr(obj, attr)
+##                else:
+##                    continue
+            xsdElement = None
+            if hasattr(obj, '__xsdcomplextype__'):
+                complexType = getattr(obj, '__xsdcomplextype__')
+                xsdElement = complexType.findElement(attr)
+            if xsdElement:
+                default = xsdElement.default
+                if default == value or default == _getXmlValue(value):
+                    continue
+            elif value == None:
+                continue
+
+            # ToDO remove maxOccurs hack after bug 177 is fixed
+            if attr == "maxOccurs" and value == -1:
+                value = "unbounded"
+
+            if isinstance(value, bool):
+               if value == True:
+                   value = "true"
+               else:
+                   value = "false"
+
+            attrNameSpacePrefix = ''
+            if hasattr(obj, '__xmlattrnamespaces__'):
+                for nameSpaceKey, nameSpaceAttributes in getattr(obj, '__xmlattrnamespaces__').items():
+                    if nameSpaceKey == nameSpacePrefix[:-1]: # Don't need to specify attribute namespace if it is the same as it selement
+                        continue
+                    if attr in nameSpaceAttributes:
+                        attrNameSpacePrefix = nameSpaceKey + ':'
+                        break
+##            if attr.startswith('_'):
+##                attr = attr[1:]
+            if (hasattr(obj, "__xmlrename__") and attr in obj.__xmlrename__):
+                attr = obj.__xmlrename__[attr]
+
+            objattrs += ' %s%s="%s"' % (attrNameSpacePrefix, attr, value)
+
+    objtype = type(obj)
+    if isinstance(obj, NoneType):
+        return ''
+#        return '%s<%s objtype="None"/>%s' % (prefix, elementName, newline)
+    elif isinstance(obj, bool):
+        return '%s<%s objtype="bool">%s</%s>%s' % (prefix, elementName, obj, elementName, newline)
+    elif isinstance(obj, int):
+        return '''%s<%s objtype="int">%s</%s>%s''' % (prefix, elementName, str(obj), elementName, newline)
+    elif isinstance(obj, long):
+        return '%s<%s objtype="long">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)
+    elif isinstance(obj, float):
+        return '%s<%s objtype="float">%s</%s>%s' % (prefix, elementName, str(obj), elementName, newline)
+    elif isinstance(obj, basestring):
+        return '''%s<%s>%s</%s>%s''' % (prefix, elementName, saxutils.escape(obj), elementName, newline)
+##    elif isinstance(obj, unicode):
+##        return '''%s<%s>%s</%s>%s''' % (prefix, elementName, obj, elementName, newline)
+    elif isinstance(obj, list):
+        if len(obj) < 1:
+            return ''
+        xmlString = '%s<%s objtype="list">%s' % (prefix, elementName, newline)
+        for item in obj:
+            xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment)
+        xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+        return xmlString
+    elif isinstance(obj, tuple):
+        if len(obj) < 1:
+            return ''
+        xmlString = '%s<%s objtype="list" mutable="false">%s' % (prefix, elementName, newline)
+        for item in obj:
+            xmlString += marshal(item, nameSpaces=nameSpaces, indent=indent+increment)
+        xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+        return xmlString
+    elif isinstance(obj, dict):
+        xmlString = '%s<%s objtype="dict">%s' % (prefix, elementName, newline)
+        subprefix = prefix + ' '*increment
+        subindent = indent + 2*increment
+        for key, val in obj.iteritems():
+            xmlString += "%s<key>%s%s%s</key>%s%s<value>%s%s%s</value>%s" \
+                         % (subprefix, newline, marshal(key, indent=subindent), subprefix, newline, subprefix, newline, marshal(val, nameSpaces=nameSpaces, indent=subindent), subprefix, newline)
+        xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+        return xmlString
+    else:
+        moduleName = obj.__class__.__module__
+        if (moduleName == "activegrid.model.schema"):
+            xmlString = '%s<%s%s%s' % (prefix, elementName, nameSpaceAttrs, objattrs)
+        else:
+            xmlString = '%s<%s%s%s objtype="%s.%s"' % (prefix, elementName, nameSpaceAttrs, objattrs, moduleName, className)
+        # get the member, value pairs for the object, filtering out
+        # the types we don't support.
+        xmlMemberString = ''
+        if hasattr(obj, '__xmlbody__'):
+            xmlMemberString = getattr(obj, obj.__xmlbody__)
+        else:
+            entryList = obj.__dict__.items()
+##            # Add in properties
+##            for key in obj.__class__.__dict__.iterkeys():
+##                if (key not in members_to_skip and key not in obj.__dict__
+##                    and hasPropertyValue(obj, key)):
+##                    value = getattr(obj, key)
+##                    entryList.append((key, value))
+            entryList.sort()
+            for name, value in entryList:
+##                # special name handling for private "__*" attributes:
+##                # remove the _<class-name> added by Python
+##                if name.startswith(classNamePrefix): name = name[len(classNamePrefix):]
+                if name in members_to_skip: continue
+                if name.startswith('__') and name.endswith('__'): continue
+##                idx = name.find('__')
+##                if idx > 0:
+##                    newName = name[idx+2:]
+##                    if newName:
+##                        name = newName
+                subElementNameSpacePrefix = nameSpacePrefix
+                if hasattr(obj, '__xmlattrnamespaces__'):
+                    for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items():
+                        if name in nameSpaceValues:
+                            subElementNameSpacePrefix = nameSpaceKey + ':'
+                            break
+                # handle sequences listed in __xmlflattensequence__
+                # specially: instead of listing the contained items inside
+                # of a separate list, as god intended, list them inside
+                # the object containing the sequence.
+                if hasattr(obj, '__xmlflattensequence__') and name in obj.__xmlflattensequence__ and value:
+                    try:
+                        xmlnametuple = obj.__xmlflattensequence__[name]
+                        xmlname = None
+                        if len(xmlnametuple) == 1:
+                            xmlname = xmlnametuple[0]
+                    except:
+                        xmlname = name
+##                        xmlname = name.lower()
+                    for seqitem in value:
+                        xmlMemberString += marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment)
+                else:
+                    if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__):
+                        xmlname = obj.__xmlrename__[name]
+                    else:
+                        xmlname = name
+##                    xmlname = name.lower()
+##                    # skip
+##                    if xmlname.startswith('_') and not xmlname.startswith('__'):
+##                        xmlname = xmlname[1:]
+##                    if (indent > 30):
+##                        print "getting pretty deep, xmlname = ", xmlname
+                    xmlMemberString += marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment)
+        # if we have nested elements, add them here, otherwise close the element tag immediately.
+        if xmlMemberString:
+            xmlString += '>'
+            if hasattr(obj, '__xmlbody__'):
+                xmlString += xmlMemberString
+                xmlString += '</%s>%s' % (elementName, newline)
+            else:
+                xmlString += newline
+                xmlString += xmlMemberString
+                xmlString += '%s</%s>%s' % (prefix, elementName, newline)
+        else:
+            xmlString = xmlString + '/>%s' % newline
+        return xmlString
+
+# We don't use this anymore but in case we want to get properties this is how
+# you do it
+def hasPropertyValue(obj, attr):
+    hasProp = False
+    try:
+        prop = obj.__class__.__dict__[attr]
+        if (isinstance(prop, property)):
+            hasProp = hasattr(obj, attr)
+            if (hasProp):
+                # It's a property and it has a value but sometimes we don't want it.
+                # If there is a _hasattr method execute it and the
+                # result will tell us whether to include this value
+                try:
+                    hasProp = obj._hasattr(attr)
+                except:
+                    pass
+    except KeyError:
+        pass
+    return hasProp
+
+if __name__ == '__main__':
+    from xmlmarshallertests import Person, marshalledint, marshalledlist
+
+    l = [1, 2, 3]
+    d = {'1': 1, '2': 2}
+    outerlist = [l]
+    xmlstr = marshal(d, "d", prettyPrint=True)
+    print xmlstr
+
+    person = Person()
+    person.firstName = "Albert"
+    person.lastName = "Camus"
+    person.addressLine1 = "23 Absurd St."
+    person.city = "Ennui"
+    person.state = "MO"
+    person.zip = "54321"
+    person._phoneNumber = "808-303-2323"
+    person.favoriteWords = ['angst', 'ennui', 'existence']
+    person.weight = 150
+
+    xmlstring = marshal(person, 'person', prettyPrint=True)
+    print xmlstring
+
+    obj = unmarshal(marshalledlist)
+    print "obj has type %s and value %s" % (type(obj), str(obj))
+    for item in obj:
+        print "item: %s" % str(item)
diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshallertests.py b/wxPython/samples/ide/activegrid/util/xmlmarshallertests.py
new file mode 100644 (file)
index 0000000..b02b0fb
--- /dev/null
@@ -0,0 +1,183 @@
+#----------------------------------------------------------------------------
+# Name:         xmlmarshallertests.py
+# Purpose:
+#
+# Author:       John Spurling
+#
+# Created:      8/16/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+import unittest
+import xmlmarshaller
+from xmlprettyprinter import xmlprettyprint
+
+marshalledPersonObject = """
+<person objtype="Person">
+  <firstName>Albert</firstName>
+  <lastName>Camus</lastName>
+  <address>23 Absurd St.</address>
+  <city>Ennui</city>
+  <state>MO</state>
+  <zip>54321</zip>
+  <_phoneNumber>808-303-2323</_phoneNumber>
+  <favoriteWords objtype="list">
+    <item>angst</item>
+    <item>ennui</item>
+    <item>existence</item>
+  </favoriteWords>
+  <weight objtype="float">150</weight>
+</person>
+"""
+
+marshalledint = '''
+<item objtype="int">23</item>
+'''
+
+marshalledlist = '''
+<mylist objtype="list">
+    <item>foo</item>
+    <item>bar</item>
+</mylist>
+'''
+
+## a dummy class taken from the old XmlMarshaller module.
+## class Person:
+##     def __init__(self):
+##         # These are not necessary but are nice if you want to tailor
+##         # the Python object <-> XML binding
+
+##         # The xml element name to use for this object, otherwise it
+##         # will use a fully qualified Python name like __main__.Person
+##         # which can be ugly.
+##         self.__xmlname__ = "person"
+##         self.firstName = None
+##         self.lastName = None
+##         self.addressLine1 = None
+##         self.addressLine2 = None
+##         self.city = None
+##         self.state = None
+##         self.zip = None
+##         self._phoneNumber = None
+##         self.favoriteWords = None
+##         self.weight = None
+class Person:
+    __xmlflattensequence__ = {'asequence': ('the_earth_is_flat',)}
+
+class XmlMarshallerTestFunctions(unittest.TestCase):
+
+    def setUp(self):
+        '''common setup code goes here.'''
+        pass
+
+    def testInt(self):
+        xml = xmlmarshaller.marshal(1)
+        print "\n#########################################"
+        print  "# testString test case                  #"
+        print  "#########################################"
+        print "marshalled int object:\n"
+        print xmlprettyprint(xml)
+
+    def testDict(self):
+        xml = xmlmarshaller.marshal({'one': 1,
+                                     'two': 2,
+                                     'three': 3})
+        print "\n#########################################"
+        print  "# testString test case                  #"
+        print  "#########################################"
+        print "marshalled dict object:\n"
+        print xmlprettyprint(xml)
+
+    def testBool(self):
+        xmltrue = xmlmarshaller.marshal(True)
+        xmlfalse = xmlmarshaller.marshal(False)
+        print "\n#########################################"
+        print  "# testBool test case                    #"
+        print  "#########################################"
+        print "marshalled boolean true object:\n"
+        print xmlprettyprint(xmltrue)
+        print "\nmarshalled boolean false object:\n"
+        print xmlprettyprint(xmlfalse)
+        pytrue = xmlmarshaller.unmarshal(xmltrue)
+        assert pytrue is True
+        pyfalse = xmlmarshaller.unmarshal(xmlfalse)
+        assert pyfalse is False
+
+    def testString(self):
+        xml = xmlmarshaller.marshal(
+            "all your marshalled objects are belong to us")
+        print "\n#########################################"
+        print  "# testString test case                  #"
+        print  "#########################################"
+        print xmlprettyprint(xml)
+
+    def testEmptyElement(self):
+        person = Person()
+        person.firstName = "Albert"
+        person.__xmlattributes__ = ('firstName',)
+        xml = xmlmarshaller.marshal(person, 'person')
+        print "\n#########################################"
+        print  "# testEmptyElement test case            #"
+        print  "#########################################"
+        print xml
+        assert (xml == """<person objtype="__main__.Person" firstName="Albert"/>""")
+
+    def testXMLFlattenSequence(self):
+        person = Person()
+        person.asequence = ('one', 'two')
+        xml = xmlmarshaller.marshal(person, 'person')
+        print "\n#########################################"
+        print  "# testXMLFlattenSequence test case      #"
+        print  "#########################################"
+        print xml
+        assert (xml == """<person objtype="__main__.Person"><the_earth_is_flat>one</the_earth_is_flat><the_earth_is_flat>two</the_earth_is_flat></person>""")
+        unmarshalledperson = xmlmarshaller.unmarshal(xml)
+        assert(hasattr(unmarshalledperson, 'asequence'))
+        assert(len(unmarshalledperson.asequence) == 2)
+
+    def testInstance(self):
+        print "\n#########################################"
+        print  "# testInstance test case                #"
+        print  "#########################################"
+        class Foo:
+            def __init__(self):
+                self.alist = [1,2]
+                self.astring = 'f00'
+        f = Foo()
+        xml = xmlmarshaller.marshal(f, 'foo')
+        print xml
+
+    def testPerson(self):
+        person = Person()
+        person.firstName = "Albert"
+        person.lastName = "Camus"
+        person.addressLine1 = "23 Absurd St."
+        person.city = "Ennui"
+        person.state = "MO"
+        person.zip = "54321"
+        person._phoneNumber = "808-303-2323"
+        person.favoriteWords = ['angst', 'ennui', 'existence']
+        person.weight = 150
+#        __xmlattributes__ = ('fabulousness',)
+        person.fabulousness = "tres tres"
+        xml = xmlmarshaller.marshal(person)
+        print "\n#########################################"
+        print  "# testPerson test case                  #"
+        print  "#########################################"
+        print "Person object marshalled into XML:\n"
+        print xml
+        # When encountering a "person" element, use the Person class
+##         elementMappings = { "person" : Person }
+##         obj = unmarshal(xml, elementMappings = elementMappings)
+##         print "Person object recreated from XML with attribute types indicated:"
+##         print obj.person.__class__
+##         for (attr, value) in obj.person.__dict__.items():
+##            if not attr.startswith("__"):
+##                print attr, "=", value, type(value)
+##         print
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/wxPython/samples/ide/activegrid/util/xmlprettyprinter.py b/wxPython/samples/ide/activegrid/util/xmlprettyprinter.py
new file mode 100644 (file)
index 0000000..29dbf16
--- /dev/null
@@ -0,0 +1,63 @@
+#----------------------------------------------------------------------------
+# Name:         xmlprettyprinter.py
+# Purpose:
+#
+# Author:       John Spurling
+#
+# Created:      9/21/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+import xml.sax
+import xml.sax.handler
+
+
+class XMLPrettyPrinter(xml.sax.ContentHandler):
+    def __init__(self, indentationChar='    ', newlineChar='\n'):
+        self.xmlOutput = ''
+        self.indentationLevel = 0
+        self.indentationChar = indentationChar
+        self.elementStack = []
+        self.newlineChar = newlineChar
+        self.hitCharData = False
+
+    ## ContentHandler methods
+    def startElement(self, name, attrs):
+        indentation = self.newlineChar + (self.indentationLevel * self.indentationChar)
+        # build attribute string
+        attrstring = ''
+        for attr in attrs.getNames():
+            value = attrs[attr]
+            attrstring += ' %s="%s"' % (attr, value)
+        self.xmlOutput += '%s<%s%s>' % (indentation, name, attrstring)
+        self.indentationLevel += 1
+        self.elementStack.append(name)
+        self.hitCharData = False
+
+    def characters(self, content):
+        self.xmlOutput += content
+        self.hitCharData = True
+
+    def endElement(self, name):
+        self.indentationLevel -= 1
+        indentation = ''
+        if not self.hitCharData:
+##            indentation += self.newlineChar + (self.indentationLevel * self.indentationChar)
+            indentation += self.indentationLevel * self.indentationChar
+        else:
+            self.hitCharData = False
+        self.xmlOutput += '%s</%s>%s' % (indentation, self.elementStack.pop(), self.newlineChar)
+
+    def getXMLString(self):
+        return self.xmlOutput[1:]
+
+def xmlprettyprint(xmlstr, spaces=4):
+    xpp = XMLPrettyPrinter(indentationChar=' ' * spaces)
+    xml.sax.parseString(xmlstr, xpp)
+    return xpp.getXMLString()
+
+if __name__ == '__main__':
+    simpleTestString = """<one>some text<two anattr="booga">two's data</two></one>"""
+    print prettyprint(simpleTestString)
+