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
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";
Updated docview library modules and sample apps from the ActiveGrid
folks.
+Added the ActiveGrid IDE as a sample application.
--- /dev/null
+#----------------------------------------------------------------------------
+# 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()
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
+
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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]
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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'
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#!/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()
+##
--- /dev/null
+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
--- /dev/null
+#!/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
+
+
--- /dev/null
+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()
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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
+
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
--- /dev/null
+#----------------------------------------------------------------------------
+# 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()
--- /dev/null
+#----------------------------------------------------------------------------
+# 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)
+