diff --git a/build/bakefiles/wxpresets/presets/wx_win32.bkl b/build/bakefiles/wxpresets/presets/wx_win32.bkl
index 4749a24352..9aeb1a51f6 100644
--- a/build/bakefiles/wxpresets/presets/wx_win32.bkl
+++ b/build/bakefiles/wxpresets/presets/wx_win32.bkl
@@ -51,9 +51,8 @@ FIXME: this template has (at least) the following bugs:
-
If you want to make changes to any of the *.i files, (SWIG interface definition files,) or to regenerate the extension sources or renamer modules, then you will need an up to date version of SWIG, -plus some patches. Get the sources for version 1.3.22, and then apply +plus some patches. Get the sources for version 1.3.24, and then apply the patches in wxPython/SWIG and then build SWIG like normal. See the README.txt in the wxPython/SWIG dir for details about each patch and also info about those that may already have been applied to the SWIG @@ -39,7 +39,7 @@ to be the full path name of the executable and the wxPython build will use it. See below for an example.
In the text below I'll use WXDIR with environment variable syntax (either $WXDIR or %WXDIR%) to refer to the top level directory where -your wxWidgerts and wxPython sources are located. It will equate to +your wxWidgets and wxPython sources are located. It will equate to whereever you checked out the wxWidgets module from CVS, or untarred the wxPython-src tarball to. You can either substitute the $WXDIR text below with your actual dir, or set the value in the environment and @@ -135,6 +135,7 @@ all for me so I don't forget anything. This time it is called dir I don't lose my scripts too.) This is what it looks like:
make $* \ + && make -C contrib/src/animate $* \ && make -C contrib/src/gizmos $* \ && make -C contrib/src/stc $*@@ -354,7 +355,7 @@ clean up the build: executing nmake with a bunch of extra command line parameters. The base set are:
--f makefile.vc OFFICIAL_BUILD=1 SHARED=1 MONOLITHIC=1 USE_OPENGL=1 +nmake -f makefile.vc OFFICIAL_BUILD=1 SHARED=1 MONOLITHIC=1 USE_OPENGL=1
If doing a debug build then add:
@@ -373,6 +374,7 @@ selection of command-line flags as described above. Repeat this same command from the following directories in order to build the contrib libraries:+%WXDIR%\contrib\build\animate %WXDIR%\contrib\build\gizmos %WXDIR%\contrib\build\stcdiff --git a/wxPython/docs/CHANGES.html b/wxPython/docs/CHANGES.html index 19dbbcc70a..2c483d7add 100644 --- a/wxPython/docs/CHANGES.html +++ b/wxPython/docs/CHANGES.html @@ -11,7 +11,73 @@Recent Changes for wxPython
-+2.5.4.1
+2.5.5.0
+wxMSW: Fixed bug #1022383, 'several ComboBoxes appear selected'
+wx.grid.Grid: Fixed bug #1163384. Moved the code that handles +activating the cell editors to a EVT_CHAR event handler. This is done +so the character inserted into the editor will be the "cooked" char +value (including accented or composed keys) rather than the raw code +provided by the EVT_KEY_DOWN event.
+Added orient parameter to wx.MDIParentFrame.Tile()
+wxMSW: wxTextCtrl with wx.TE_RICH2 style now uses RichEdit 4.1 if +available.
+Added GetCount, GetCountRGB, and GetCountColour methods to +wx.ImageHistogram.
+wxMSW: wx.Window.Refresh changed to explicitly refresh all children as +well as the parent. Previously it was implicitly done because parents +did not clip their children by default. Now that they always clip +children then Refresh needed to be fixed to do a recursive refresh. +This also fixes the Freeze/Thaw problems that some people had with +2.5.4.1.
+wx.SplitterWindow: Send EVT_SPLITTER_SASH_POS_CHANGED only once after +end of dragging and not after each CHANGING event (modified patch +#1076226)
+wx.glcanvas.GLCanvas: applied patch fixing problems with X server +crash when using nVidia cards (patch 1155132)
++
+- wx.lib.mixins.listctrl: Patches from Toni Brkic:
+- +
+
+- Bugfix for TextEditMixin when the view can't be scrolled
+- Enhancement for ListCtrlAutoWidthMixin, allowing it to manage +the width of any column.
+wxMac: removal and reusing toolbar tools like the other platforms is +now possible.
+wxMac: Correct radio tool selection after calling Realize a 2nd time.
+wxMSW: Applied patch #1166587, removes all flicker from wx.StaticBox
+Added wx.lib.foldpanelbar, Andrea Gavana's port of Jorgen Bodde's C++ +wxFoldPanelBar classes to Python.
+wxGTK: Applied patch #1173802, reimplementation of GtkFileChooser +wxFileDialog by Mart Raudsepp. Note that this new file dialog is only +used on GTK2 >= 2.4. For earlier GTK2 versions and GTK1 then the +older generic file dialog is used.
+wxMSW: fixes to static box borders calculations (finalizes patch +#1166587)
+wx.Image: Use Python's buffer interface API for all image data and +alpha Set/Get methods and the ImageFromData* constructors. They all +still copy the buffer except for SetDataBuffer and SetAlphaBuffer, but +this gives more flexibility on where the data can come from.
+Added MDI support to XRC
+Added wx.animate module and a demo. The wx.animate module provides a +control that is able to display an animated GIF file.
+wx.lib.plot.py: Applied patch from Werner F. Bruhin that allows either +vertical and/or horizontal gridlines.
+wxMSW: Extra space given for top border of wx.StaticBoxSizer so the +upper line is not cliped when there is no label.
+wxMSW: Restored old behaviour of wx.StaticBox.SetBackgroundColour only +affecting the label.
+wxMSW: Fixed missing EVT_RIGHT_DOWN and EVT_TREE_ITEM_RIGHT_CLICK +events in a wx.TreeCtrl.
+Added wx.GetTopLevelWindows() function which returns a copy of the +list of top-level windows that currently exist in the application.
+Updated docview library modules and sample apps from the ActiveGrid +folks.
+Added the ActiveGrid IDE as a sample application.
++-2.5.4.1
wx.Sizer Add, Insert, and Prepend functions now return a reference to the wx.SizerItem that was added to the sizer, and the wx.SizerItem has a GetRect accessor to give the position of the item on the parent window.
@@ -241,8 +307,8 @@ wx.Image.-2.5.3.1
++-2.5.3.1
wxMac focus and border refreshes corrected.
Updated internal PNG library.
wxMac fix for metal appearance on wx.ToolBar.
@@ -395,8 +461,8 @@ it.wxPython on OSX can now be built in Unicode mode, can support multiple version installs, and comes with an uninstaller script.
-2.5.2.8
+ --2.5.2.7
++-2.5.2.7
wx.ADJUST_MINSIZE is now the default behaviour for window items in sizers. This means that the item's GetMinSize and/or GetBestSize will be called when calculating layout and the return value from that will @@ -520,8 +586,8 @@ in the PyShell:
wxGTK: Applied wxNO_BORDER patch (#1098374) for text control and combo box.
-2.5.1.5
++-2.5.1.5
(See also the MigrationGuide file for details about some of the big changes that have happened in this release and how you should adapt your code.)
@@ -602,8 +668,8 @@ migrating away from using activexwrapper as well. Please see the MigrationGuide for more details on using the new module.Floats are allowed again as function parameters where ints are expected.
-2.4.2.4
++-2.4.2.4
Use wxSTC in the demo for displaying the soucre code of the samples.
Lots of bug fixes and such from the wxWindows folks.
Added wxPython.lib.newevent from Miki Tebeka. Its usage is @@ -612,8 +678,8 @@ demonstrated in the Threads sample in the demo.
Added wxMaskedNumCtrl.
Added Chris Barker's FloatCanvas.
-2.4.1.2
++-2.4.1.2
Added wxScrolledPanel from Will Sadkin
Added SetShape method to top level windows (e.g. wxFrame.)
Changed wxSWIG to not generate Python code using apply, (since it will @@ -664,8 +730,8 @@ release,) SetItemMinSize can now take a wxSize (or 2-tuple) parameter, and Spacers can be specified with a wxSize (or 2-tuple) parameter
Added wxCursorFromBits.
-2.4.0.7
++-2.4.0.7
Gave up on generating a warning upon the use of the old true/false or TRUE/FALSE values.
Fixed wxGenericTreeCtrl (used on wxGTK and wxMac for wxTreeCtrl) so @@ -695,8 +761,8 @@ think I am testing in the future...
Updated pycolourchooser.
Updated to 0.9b of PyCrust.
-2.4.0.4
++-2.4.0.4
Added missing wxRect methods
Add OOR support for wxApp objects too.
Added wxCursorFromImage, which works on wxMSW and wxGTK so far.
@@ -752,25 +818,25 @@ doesn't have a standard place for them.Fixed typemaps for wxGridCellCoordsArray.
Updated to the 0.9a version of PyCrust
-2.4.0.2
++-2.4.0.2
Several bug fixes.
Added wxIntCtrl from Will Sadkin.
Added wxPyColourChooser by Michael Gilfix.
-2.4.0.1
++-2.4.0.1
No major new features since 2.3.4.2, mostly bug fixes and minor enhancements.
Added function wrappers for the common dialogs from Kevin Altis. See wxPython/lib/dialogs.py for more details.
-2.3.4.2
++-2.3.4.2
Various bug fixes.
-2.3.4.1
++-2.3.4.1
Updated XRCed and wxTimeCtrl contribs.
Show a couple new wxGrid features in the demo.
Several bug fixes in wxWindows.
@@ -824,8 +890,8 @@ windows when desired. HTMLHelp viewer does. Changed how the wxPythonDocs tarball is built and added a script to launch the doc viewer.-2.3.3.1
++-2.3.3.1
Added wxSplashScreen.
Added wxGenericDirCtrl.
Added wxMultiChoiceDialog.
@@ -967,15 +1033,15 @@ example.Added wxPython.lib.mixins.rubberband module from Robb Shecter.
Added wxTimeCtrl from Will Sadkin.
-2.3.2.1
++-2.3.2.1
Changed (again) how the Python global interpreter lock is handled as well as the Python thread state. This time it works on SMP machines without barfing and is also still compatible with Python debuggers.
Added some patches from library contributors.
-2.3.2
++-2.3.2
Added EVT_HELP, EVT_HELP_RANGE, EVT_DETAILED_HELP, EVT_DETAILED_HELP_RANGE, EVT_CONTEXT_MENU, wxHelpEvent, wxContextMenuEvent, wxContextHelp, wxContextHelpButton, wxTipWindow, @@ -1057,8 +1123,8 @@ SendCommand method, but it is still quite powerful. See wxPython/contrib/dllwidget and wxPython/demo/dllwidget for more details.
-2.3.1
++-2.3.1
Added EVT_GRID_EDITOR_CREATED and wxGridEditorCreatedEvent so the user code can get access to the edit control when it is created, (to push on a custom event handler for example.)
@@ -1071,8 +1137,8 @@ subclass wxXmlResourceHandler, etc...Fixed img2py to work correctly with Python 2.1.
Added enhanced wxVTKRenderWindow by Prabhu Ramachandran
-2.3.0
++-2.3.0
Removed initial startup dependency on the OpenGL DLLs so only the glcanvasc.pyd depends on them, (on wxMSW.)
Changed wxFont, wxPen, wxBrush to not implicitly use the @@ -1168,13 +1234,13 @@ please send it to me for inclusion in this package.
by having smaller functional apps to play with. They can be found in wxPython/samples.-2.2.6
++-2.2.6
No changes happened in the Python wrappers for this release, only changes and fixes in the wxWindows library.
-2.2.5
++2.2.5
New typemaps for wxString when compiling for Python 2.0 and beyond that allow Unicode objects to be passed as well as String objects. If a Unicode object is passed PyString_AsStringAndSize is used to convert diff --git a/wxPython/docs/CHANGES.txt b/wxPython/docs/CHANGES.txt index ac68fd1751..426c834fc6 100644 --- a/wxPython/docs/CHANGES.txt +++ b/wxPython/docs/CHANGES.txt @@ -70,7 +70,22 @@ control that is able to display an animated GIF file. wx.lib.plot.py: Applied patch from Werner F. Bruhin that allows either vertical and/or horizontal gridlines. +wxMSW: Extra space given for top border of wx.StaticBoxSizer so the +upper line is not cliped when there is no label. +wxMSW: Restored old behaviour of wx.StaticBox.SetBackgroundColour only +affecting the label. + +wxMSW: Fixed missing EVT_RIGHT_DOWN and EVT_TREE_ITEM_RIGHT_CLICK +events in a wx.TreeCtrl. + +Added wx.GetTopLevelWindows() function which returns a copy of the +list of top-level windows that currently exist in the application. + +Updated docview library modules and sample apps from the ActiveGrid +folks. + +Added the ActiveGrid IDE as a sample application. diff --git a/wxPython/docs/xml/wxPython-metadata.xml b/wxPython/docs/xml/wxPython-metadata.xml index 48ef3ad331..eb25717942 100644 --- a/wxPython/docs/xml/wxPython-metadata.xml +++ b/wxPython/docs/xml/wxPython-metadata.xml @@ -11369,7 +11369,7 @@ pixels from the begining of text to the coresponding character of *text*. The generic version simply builds a running total of the widths of each character using GetTextExtent, however if the various platforms have a native API function that is faster or more accurate -than the generic implementaiton then it will be used instead. +than the generic implementation then it will be used instead.
diff --git a/wxPython/samples/docview/DocViewDemo.py b/wxPython/samples/docview/DocViewDemo.py index c22b3539cc..62aeefbe6f 100644 --- a/wxPython/samples/docview/DocViewDemo.py +++ b/wxPython/samples/docview/DocViewDemo.py @@ -6,7 +6,7 @@ # # Created: 8/1/03 # CVS-ID: $Id$ -# Copyright: (c) 2003, 2004 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al) +# Copyright: (c) 2003-2005 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al) # License: wxWindows license #---------------------------------------------------------------------------- diff --git a/wxPython/samples/docview/PyDocViewDemo.py b/wxPython/samples/docview/PyDocViewDemo.py deleted file mode 100644 index 9243aea8fd..0000000000 --- a/wxPython/samples/docview/PyDocViewDemo.py +++ /dev/null @@ -1,85 +0,0 @@ -#---------------------------------------------------------------------------- -# Name: PyDocViewDemo.py -# Purpose: Demo of Python extensions to the wxWindows docview framework -# -# Author: Peter Yared, Morgan Hua -# -# Created: 5/15/03 -# CVS-ID: $Id$ -# Copyright: (c) 2003 ActiveGrid, Inc. -# License: wxWindows license -#---------------------------------------------------------------------------- - - -import sys -import wx -import wx.lib.docview -import wx.lib.pydocview -import activegrid.tool.TextEditor as TextEditor -import activegrid.tool.FindService as FindService -_ = wx.GetTranslation - - -#---------------------------------------------------------------------------- -# Classes -#---------------------------------------------------------------------------- - -class TextEditorApplication(wx.lib.pydocview.DocApp): - - - def OnInit(self): - wx.lib.pydocview.DocApp.OnInit(self) - - wx.lib.pydocview.DocApp.ShowSplash(self, "activegrid/tool/images/splash.jpg") - - self.SetAppName(_("wxPython PyDocView Demo")) - config = wx.Config(self.GetAppName(), style = wx.CONFIG_USE_LOCAL_FILE) - - docManager = wx.lib.docview.DocManager(flags = self.GetDefaultDocManagerFlags()) - self.SetDocumentManager(docManager) - - textTemplate = wx.lib.docview.DocTemplate(docManager, - _("Text"), - "*.text;*.txt", - _("Text"), - _(".txt"), - _("Text Document"), - _("Text View"), - TextEditor.TextDocument, - TextEditor.TextView) - docManager.AssociateTemplate(textTemplate) - - textService = self.InstallService(TextEditor.TextService()) - findService = self.InstallService(FindService.FindService()) - optionsService = self.InstallService(wx.lib.pydocview.DocOptionsService()) - windowMenuService = self.InstallService(wx.lib.pydocview.WindowMenuService()) - optionsService.AddOptionsPanel(TextEditor.TextOptionsPanel) - filePropertiesService = self.InstallService(wx.lib.pydocview.FilePropertiesService()) - aboutService = self.InstallService(wx.lib.pydocview.AboutService()) - -## self.SetDefaultIcon(getAppIcon()) # set this for your custom icon - - if docManager.GetFlags() & wx.lib.docview.DOC_MDI: - frame = wx.lib.pydocview.DocMDIParentFrame(docManager, None, -1, wx.GetApp().GetAppName()) - frame.Show(True) - - wx.lib.pydocview.DocApp.CloseSplash(self) - - self.OpenCommandLineArgs() - - if not docManager.GetDocuments() and docManager.GetFlags() & wx.lib.docview.DOC_SDI: - textTemplate.CreateDocument('', wx.lib.docview.DOC_NEW).OnNewDocument() - - wx.CallAfter(self.ShowTip, wx.GetApp().GetTopWindow(), wx.CreateFileTipProvider("activegrid/tool/data/tips.txt", 0)) - - return True - - -#---------------------------------------------------------------------------- -# Main -#---------------------------------------------------------------------------- - -sys.stdout = sys.stderr - -app = TextEditorApplication(redirect = False) -app.MainLoop() diff --git a/wxPython/samples/docview/activegrid/tool/data/tips.txt b/wxPython/samples/docview/activegrid/tool/data/tips.txt deleted file mode 100644 index f102916a03..0000000000 --- a/wxPython/samples/docview/activegrid/tool/data/tips.txt +++ /dev/null @@ -1,3 +0,0 @@ -Tips for PyDocViewDemo App -wxWindows rules! -Use the source, Luke! \ No newline at end of file diff --git a/wxPython/samples/ide/ActiveGridIDE.py b/wxPython/samples/ide/ActiveGridIDE.py new file mode 100644 index 0000000000..868acca0e7 --- /dev/null +++ b/wxPython/samples/ide/ActiveGridIDE.py @@ -0,0 +1,33 @@ +#---------------------------------------------------------------------------- +# Name: ActiveGridIDE.py +# Purpose: +# +# Author: Lawrence Bruhmuller +# +# Created: 3/30/05 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +import wx.lib.pydocview +import activegrid.tool.IDE + +import os +import sys +sys.stdout = sys.stderr + +# This is here as the base IDE entry point. Only difference is that -baseide is passed. + +sys.argv.append('-baseide'); + +# Put activegrid dir in path so python files can be found from py2exe +# This code should never do anything when run from the python interpreter +execDir = os.path.dirname(sys.executable) +try: + sys.path.index(execDir) +except ValueError: + sys.path.append(execDir) +app = activegrid.tool.IDE.IDEApplication(redirect = False) +app.GetTopWindow().Raise() # sometimes it shows up beneath other windows. e.g. running self in debugger +app.MainLoop() + diff --git a/wxPython/samples/docview/activegrid/__init__.py b/wxPython/samples/ide/activegrid/__init__.py similarity index 100% rename from wxPython/samples/docview/activegrid/__init__.py rename to wxPython/samples/ide/activegrid/__init__.py diff --git a/wxPython/samples/ide/activegrid/tool/AboutDialog.py b/wxPython/samples/ide/activegrid/tool/AboutDialog.py new file mode 100644 index 0000000000..ce1e0cdfa5 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/AboutDialog.py @@ -0,0 +1,119 @@ +#---------------------------------------------------------------------------- +# Name: AboutDialog.py +# Purpose: AboutBox which has copyright notice, license information, and credits +# +# Author: Morgan Hua +# +# Created: 3/22/05 +# Copyright: (c) 2005 ActiveGrid, Inc. +# CVS-ID: $Id$ +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +from IDE import ACTIVEGRID_BASE_IDE, getSplashBitmap +_ = wx.GetTranslation + +#---------------------------------------------------------------------------- +# Package License Data for AboutDialog +# Package, License, URL +# If no information is available, put a None as a place holder. +#---------------------------------------------------------------------------- + + +licenseData = [ + ("ActiveGrid", "ASL 2.0", "http://apache.org/licenses/LICENSE-2.0"), + ("Python 2.3", "Python Software Foundation License", "http://www.python.org/2.3/license.html"), + ("wxPython 2.5", "wxWidgets 2 - LGPL", "http://wxwidgets.org/newlicen.htm"), + ("wxWidgets", "wxWindows Library License 3", "http://www.wxwidgets.org/manuals/2.5.4/wx_wxlicense.html"), + ("pychecker", "MetaSlash - BSD", "http://pychecker.sourceforge.net/COPYRIGHT"), + ("process.py", "See file", "http://starship.python.net/~tmick/"), +] + +if not ACTIVEGRID_BASE_IDE: # add licenses for database connections only if not the base IDE + licenseData += [ + ("pydb2", "LGPL", "http://sourceforge.net/projects/pydb2"), + ("pysqlite", "Python License (CNRI)", "http://sourceforge.net/projects/pysqlite"), + ("mysql-python", "GPL, Python License (CNRI), Zope Public License", "http://sourceforge.net/projects/mysql-python"), + ("cx_Oracle", "Computronix", "http://http://www.computronix.com/download/License(cxOracle).txt"), + ("SQLite", "Public Domain", "http://www.sqlite.org/copyright.html"), + ("PyGreSQL", "BSD", "http://www.pygresql.org"), + ] + +if wx.Platform == '__WXMSW__': + licenseData += [("pywin32", "Python Software Foundation License", "http://sourceforge.net/projects/pywin32/")] + +class AboutDialog(wx.Dialog): + + def __init__(self, parent): + """ + Initializes the about dialog. + """ + wx.Dialog.__init__(self, parent, -1, _("About ") + wx.GetApp().GetAppName(), style = wx.DEFAULT_DIALOG_STYLE) + + nb = wx.Notebook(self, -1) + + aboutPage = wx.Panel(nb, -1) + sizer = wx.BoxSizer(wx.VERTICAL) + splash_bmp = getSplashBitmap() + image = wx.StaticBitmap(aboutPage, -1, splash_bmp, (0,0), (splash_bmp.GetWidth(), splash_bmp.GetHeight())) + sizer.Add(image, 0, wx.ALIGN_CENTER|wx.ALL, 0) + sizer.Add(wx.StaticText(aboutPage, -1, wx.GetApp().GetAppName() + _("\nVersion 0.6 Early Access\n\nCopyright (c) 2003-2005 ActiveGrid Incorporated and Contributors. All rights reserved.")), 0, wx.ALIGN_LEFT|wx.ALL, 10) + sizer.Add(wx.StaticText(aboutPage, -1, _("http://www.activegrid.com")), 0, wx.ALIGN_LEFT|wx.LEFT|wx.BOTTOM, 10) + aboutPage.SetSizer(sizer) + nb.AddPage(aboutPage, _("Copyright")) + + licensePage = wx.Panel(nb, -1) + grid = wx.grid.Grid(licensePage, -1) + grid.CreateGrid(len(licenseData), 2) + + dc = wx.ClientDC(grid) + dc.SetFont(grid.GetLabelFont()) + grid.SetColLabelValue(0, _("License")) + grid.SetColLabelValue(1, _("URL")) + w, maxHeight = dc.GetTextExtent(_("License")) + w, h = dc.GetTextExtent(_("URL")) + if h > maxHeight: + maxHeight = h + grid.SetColLabelSize(maxHeight + 6) # add a 6 pixel margin + + for row, data in enumerate(licenseData): + package = data[0] + license = data[1] + url = data[2] + if package: + grid.SetRowLabelValue(row, package) + if license: + grid.SetCellValue(row, 0, license) + if url: + grid.SetCellValue(row, 1, url) + + grid.EnableEditing(False) + grid.EnableDragGridSize(False) + grid.EnableDragColSize(False) + grid.EnableDragRowSize(False) + grid.SetRowLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) + grid.SetLabelBackgroundColour(wx.WHITE) + grid.AutoSizeColumn(0, 100) + grid.AutoSizeColumn(1, 100) + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(grid, 1, wx.EXPAND|wx.ALL, 10) + licensePage.SetSizer(sizer) + nb.AddPage(licensePage, _("Licenses")) + + creditsPage = wx.Panel(nb, -1) + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(wx.StaticText(creditsPage, -1, _("ActiveGrid Development Team:\n\nLawrence Bruhmuller\nEric Chu\nMatt Fryer\nJoel Hare\nMorgan Hua\nAlan Mullendore\nJeff Norton\nKevin Wang\nPeter Yared")), 0, wx.ALIGN_LEFT|wx.ALL, 10) + creditsPage.SetSizer(sizer) + nb.AddPage(creditsPage, _("Credits")) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(nb, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + btn = wx.Button(self, wx.ID_OK) + sizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + self.SetSizer(sizer) + self.SetAutoLayout(True) + sizer.Fit(self) + + diff --git a/wxPython/samples/ide/activegrid/tool/AbstractEditor.py b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py new file mode 100644 index 0000000000..8bda44696d --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py @@ -0,0 +1,641 @@ +#---------------------------------------------------------------------------- +# Name: AbstractEditor.py +# Purpose: Non-text editor for DataModel and Process +# +# Author: Peter Yared, Morgan Hua +# +# Created: 7/28/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + + +import wx +import wx.lib.docview +import wx.lib.ogl as ogl +import PropertyService +_ = wx.GetTranslation + + +SELECT_BRUSH = wx.Brush("BLUE", wx.SOLID) +SHAPE_BRUSH = wx.Brush("WHEAT", wx.SOLID) +LINE_BRUSH = wx.BLACK_BRUSH + + +def GetRawModel(model): + if hasattr(model, "GetRawModel"): + rawModel = model.GetRawModel() + else: + rawModel = model + return rawModel + + +class CanvasView(wx.lib.docview.View): + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + + def __init__(self, brush = SHAPE_BRUSH): + wx.lib.docview.View.__init__(self) + self._brush = brush + self._canvas = None + self._pt1 = None + self._pt2 = None + self._needEraseLasso = False + self._propShape = None + + + def OnCreate(self, doc, flags): + frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) + frame.Show() + sizer = wx.BoxSizer() + self._CreateCanvas(frame) + sizer.Add(self._canvas, 1, wx.EXPAND, 0) + frame.SetSizer(sizer) + frame.Layout() + self.Activate() + return True + + + def OnActivateView(self, activate, activeView, deactiveView): + if activate and self._canvas: + # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop + if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + self._canvas.SetFocus() + else: + wx.CallAfter(self._canvas.SetFocus) + + + def OnClose(self, deleteWindow = True): + statusC = wx.GetApp().CloseChildDocuments(self.GetDocument()) + statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow) + if hasattr(self, "ClearOutline"): + wx.CallAfter(self.ClearOutline) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter + if not (statusC and statusP): + return False + self.Activate(False) + if deleteWindow and self.GetFrame(): + self.GetFrame().Destroy() + return True + + + def _CreateCanvas(self, parent): + self._canvas = ogl.ShapeCanvas(parent) + wx.EVT_LEFT_DOWN(self._canvas, self.OnLeftClick) + wx.EVT_LEFT_UP(self._canvas, self.OnLeftUp) + wx.EVT_MOTION(self._canvas, self.OnLeftDrag) + wx.EVT_LEFT_DCLICK(self._canvas, self.OnLeftDoubleClick) + wx.EVT_KEY_DOWN(self._canvas, self.OnKeyPressed) + + maxWidth = 2000 + maxHeight = 16000 + self._canvas.SetScrollbars(20, 20, maxWidth / 20, maxHeight / 20) + + self._canvas.SetBackgroundColour(wx.WHITE) + self._diagram = ogl.Diagram() + self._canvas.SetDiagram(self._diagram) + self._diagram.SetCanvas(self._canvas) + + + def OnKeyPressed(self, event): + key = event.KeyCode() + if key == wx.WXK_DELETE: + self.OnClear(event) + else: + event.Skip() + + + def OnLeftClick(self, event): + self.EraseRubberBand() + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + + # keep track of mouse down for group select + self._pt1 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset + self._pt2 = None + + shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0] + if shape: + self.BringToFront(shape) + + self._pt1 = None + event.Skip() # pass on event to shape handler to take care of selection + + return + elif event.ControlDown() or event.ShiftDown(): # extend select, don't deselect + pass + else: + # click on empty part of canvas, deselect everything + needRefresh = False + for shape in self._diagram.GetShapeList(): + if hasattr(shape, "GetModel"): + if shape.Selected(): + needRefresh = True + shape.Select(False, dc) + if needRefresh: + self._canvas.Redraw(dc) + + self.SetPropertyModel(None) + + if len(self.GetSelection()) == 0: + self.SetPropertyShape(None) + + + + def OnLeftDoubleClick(self, event): + propertyService = wx.GetApp().GetService(PropertyService.PropertyService) + if propertyService: + propertyService.ShowWindow() + + + def OnLeftDrag(self, event): + # draw lasso for group select + if self._pt1 and event.LeftIsDown(): # we are in middle of lasso selection + self.EraseRubberBand() + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + self._pt2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset + self.DrawRubberBand() + else: + event.Skip() + + + def OnLeftUp(self, event): + # do group select + if self._needEraseLasso: + self.EraseRubberBand() + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + x1, y1 = self._pt1 + x2, y2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset + + tol = self._diagram.GetMouseTolerance() + if abs(x1 - x2) > tol or abs(y1 - y2) > tol: + # make sure x1 < x2 and y1 < y2 to make comparison test easier + if x1 > x2: + temp = x1 + x1 = x2 + x2 = temp + if y1 > y2: + temp = y1 + y1 = y2 + y2 = temp + + for shape in self._diagram.GetShapeList(): + if not shape.GetParent() and hasattr(shape, "GetModel"): # if part of a composite, don't select it + x, y = shape.GetX(), shape.GetY() + width, height = shape.GetBoundingBoxMax() + selected = x1 < x - width/2 and x2 > x + width/2 and y1 < y - height/2 and y2 > y + height/2 + if event.ControlDown() or event.ShiftDown(): # extend select, don't deselect + if selected: + shape.Select(selected, dc) + else: # select items in lasso and deselect items out of lasso + shape.Select(selected, dc) + self._canvas.Redraw(dc) + else: + event.Skip() + else: + event.Skip() + + + def EraseRubberBand(self): + if self._needEraseLasso: + self._needEraseLasso = False + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + dc.SetLogicalFunction(wx.XOR) + pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH) + dc.SetPen(pen) + brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) + dc.SetBrush(brush) + dc.ResetBoundingBox() + dc.BeginDrawing() + + x1, y1 = self._pt1 + x2, y2 = self._pt2 + + # make sure x1 < x2 and y1 < y2 + # this will make (x1, y1) = upper left corner + if x1 > x2: + temp = x1 + x1 = x2 + x2 = temp + if y1 > y2: + temp = y1 + y1 = y2 + y2 = temp + + # erase previous outline + dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) + dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) + dc.EndDrawing() + + + def DrawRubberBand(self): + self._needEraseLasso = True + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + dc.SetLogicalFunction(wx.XOR) + pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH) + dc.SetPen(pen) + brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) + dc.SetBrush(brush) + dc.ResetBoundingBox() + dc.BeginDrawing() + + x1, y1 = self._pt1 + x2, y2 = self._pt2 + + # make sure x1 < x2 and y1 < y2 + # this will make (x1, y1) = upper left corner + if x1 > x2: + temp = x1 + x1 = x2 + x2 = temp + if y1 > y2: + temp = y1 + y1 = y2 + y2 = temp + + # draw outline + dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) + dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) + dc.EndDrawing() + + + def FindParkingSpot(self, width, height): + """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """ + offset = 30 # space between shapes + x = offset + y = offset + maxX = 700 # max distance to the right where we'll place tables + noParkingSpot = True + + while noParkingSpot: + point = self.isSpotOccupied(x, y, width, height) + if point: + x = point[0] + offset + if x > maxX: + x = offset + y = point[1] + offset + else: + noParkingSpot = False + + return x, y + + + def isSpotOccupied(self, x, y, width, height): + """ returns None if at x,y,width,height no object occupies that rectangle, + otherwise returns lower right corner of object that occupies given x,y position + """ + x2 = x + width + y2 = y + height + + for shape in self._diagram.GetShapeList(): + if isinstance(shape, ogl.RectangleShape) or isinstance(shape, ogl.EllipseShape): + if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): + # skip, part of a composite shape + continue + + if hasattr(shape, "GetModel"): + other_x, other_y, other_width, other_height = shape.GetModel().getEditorBounds() + other_x2 = other_x + other_width + other_y2 = other_y + other_height + else: + # shapes x,y are at the center of the shape, need to transform to upper left coordinate + other_width, other_height = shape.GetBoundingBoxMax() + other_x = shape.GetX() - other_width/2 + other_y = shape.GetY() - other_height/2 + + other_x2 = other_x + other_width + other_y2 = other_y + other_height + # intersection check + if ((other_x2 < other_x or other_x2 > x) and + (other_y2 < other_y or other_y2 > y) and + (x2 < x or x2 > other_x) and + (y2 < y or y2 > other_y)): + return (other_x2, other_y2) + return None + + + #---------------------------------------------------------------------------- + # Canvas methods + #---------------------------------------------------------------------------- + + def AddShape(self, shape, x = None, y = None, pen = None, brush = None, text = None, eventHandler = None): + if isinstance(shape, ogl.CompositeShape): + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + shape.Move(dc, x, y) + else: + shape.SetDraggable(True, True) + shape.SetCanvas(self._canvas) + + if x: + shape.SetX(x) + if y: + shape.SetY(y) + shape.SetCentreResize(False) + if pen: + shape.SetPen(pen) + if brush: + shape.SetBrush(brush) + if text: + shape.AddText(text) + shape.SetShadowMode(ogl.SHADOW_RIGHT) + self._diagram.AddShape(shape) + shape.Show(True) + if not eventHandler: + eventHandler = EditorCanvasShapeEvtHandler(self) + eventHandler.SetShape(shape) + eventHandler.SetPreviousHandler(shape.GetEventHandler()) + shape.SetEventHandler(eventHandler) + return shape + + + def RemoveShape(self, model = None, shape = None): + if not model and not shape: + return + + if not shape: + shape = self.GetShape(model) + + if shape: + shape.Select(False) + self._diagram.RemoveShape(shape) + if isinstance(shape, ogl.CompositeShape): + shape.RemoveFromCanvas(self._canvas) + + + def UpdateShape(self, model): + for shape in self._diagram.GetShapeList(): + if hasattr(shape, "GetModel") and shape.GetModel() == model: + x, y, w, h = model.getEditorBounds() + newX = x + w / 2 + newY = y + h / 2 + changed = False + if isinstance(shape, ogl.CompositeShape): + if shape.GetX() != newX or shape.GetY() != newY: + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + shape.SetSize(w, h, True) # wxBug: SetSize must be before Move because links won't go to the right place + shape.Move(dc, newX, newY) # wxBug: Move must be before SetSize because links won't go to the right place + changed = True + else: + oldw, oldh = shape.GetBoundingBoxMax() + oldx = shape.GetX() + oldy = shape.GetY() + if oldw != w or oldh != h or oldx != newX or oldy != newY: + shape.SetSize(w, h) + shape.SetX(newX) + shape.SetY(newY) + changed = True + if changed: + shape.ResetControlPoints() + self._canvas.Refresh() + break + + + def GetShape(self, model): + for shape in self._diagram.GetShapeList(): + if hasattr(shape, "GetModel") and shape.GetModel() == model: + return shape + return None + + + def GetSelection(self): + return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList()) + + + def SetSelection(self, models, extendSelect = False): + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + update = False + if not isinstance(models, type([])) and not isinstance(models, type(())): + models = [models] + for shape in self._diagram.GetShapeList(): + if hasattr(shape, "GetModel"): + if shape.Selected() and not shape.GetModel() in models: # was selected, but not in new list, so deselect, unless extend select + if not extendSelect: + shape.Select(False, dc) + update = True + elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select + shape.Select(True, dc) + update = True + elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect + shape.Select(False, dc) + update = True + if update: + self._canvas.Redraw(dc) + + + def BringToFront(self, shape): + if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): + self._diagram.RemoveShape(shape.GetParent()) + self._diagram.AddShape(shape.GetParent()) + else: + self._diagram.RemoveShape(shape) + self._diagram.AddShape(shape) + + + def SendToBack(self, shape): + if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): + self._diagram.RemoveShape(shape.GetParent()) + self._diagram.InsertShape(shape.GetParent()) + else: + self._diagram.RemoveShape(shape) + self._diagram.InsertShape(shape) + + + def ScrollVisible(self, shape): + xUnit, yUnit = shape._canvas.GetScrollPixelsPerUnit() + scrollX, scrollY = self._canvas.GetViewStart() # in scroll units + scrollW, scrollH = self._canvas.GetSize() # in pixels + w, h = shape.GetBoundingBoxMax() # in pixels + x = shape.GetX() - w/2 # convert to upper left coordinate from center + y = shape.GetY() - h/2 # convert to upper left coordinate from center + + if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW: # don't scroll if already visible + x = -1 + else: + x = x/xUnit + + if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH: # don't scroll if already visible + y = -1 + else: + y = y/yUnit + + self._canvas.Scroll(x, y) # in scroll units + + + def SetPropertyShape(self, shape): + # no need to highlight if no PropertyService is running + propertyService = wx.GetApp().GetService(PropertyService.PropertyService) + if not propertyService: + return + + if shape == self._propShape: + return + + if hasattr(shape, "GetPropertyShape"): + shape = shape.GetPropertyShape() + + dc = wx.ClientDC(self._canvas) + self._canvas.PrepareDC(dc) + dc.BeginDrawing() + + # erase old selection if it still exists + if self._propShape and self._propShape in self._diagram.GetShapeList(): + self._propShape.SetBrush(self._brush) + if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken + self._propShape.SetTextColour("BLACK", 0) + self._propShape.Draw(dc) + + # set new selection + self._propShape = shape + + # draw new selection + if self._propShape and self._propShape in self._diagram.GetShapeList(): + self._propShape.SetBrush(SELECT_BRUSH) + if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken + self._propShape.SetTextColour("WHITE", 0) + self._propShape.Draw(dc) + + dc.EndDrawing() + + + #---------------------------------------------------------------------------- + # Property Service methods + #---------------------------------------------------------------------------- + + def GetPropertyModel(self): + if hasattr(self, "_propModel"): + return self._propModel + return None + + + def SetPropertyModel(self, model): + # no need to set the model if no PropertyService is running + propertyService = wx.GetApp().GetService(PropertyService.PropertyService) + if not propertyService: + return + + if hasattr(self, "_propModel") and model == self._propModel: + return + + self._propModel = model + propertyService.LoadProperties(self._propModel, self.GetDocument()) + + +class EditorCanvasShapeMixin: + + def GetModel(self): + return self._model + + + def SetModel(self, model): + self._model = model + + +class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler): + + """ wxBug: Bug in OLG package. With wxShape.SetShadowMode() turned on, when we set the size, + the width/height is larger by 6 pixels. Need to subtract this value from width and height when we + resize the object. + """ + SHIFT_KEY = 1 + CONTROL_KEY = 2 + + def __init__(self, view): + ogl.ShapeEvtHandler.__init__(self) + self._view = view + + + def OnLeftClick(self, x, y, keys = 0, attachment = 0): + shape = self.GetShape() + if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object + model = shape.GetModel() + else: + shape = shape.GetParent() + if shape: + model = shape.GetModel() + + self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) + self._view.SetPropertyShape(shape) + self._view.SetPropertyModel(model) + + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment) + shape = self.GetShape() + if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object + model = shape.GetModel() + else: + parentShape = shape.GetParent() + if parentShape: + model = parentShape.GetModel() + self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) + + + def OnMovePost(self, dc, x, y, oldX, oldY, display): + if x == oldX and y == oldY: + return + if not self._view.GetDocument(): + return + shape = self.GetShape() + if isinstance(shape, EditorCanvasShapeMixin) and shape.Draggable(): + model = shape.GetModel() + if hasattr(model, "getEditorBounds") and model.getEditorBounds(): + x, y, w, h = model.getEditorBounds() + newX = shape.GetX() - shape.GetBoundingBoxMax()[0] / 2 + newY = shape.GetY() - shape.GetBoundingBoxMax()[1] / 2 + newWidth = shape.GetBoundingBoxMax()[0] + newHeight = shape.GetBoundingBoxMax()[1] + if shape._shadowMode != ogl.SHADOW_NONE: + newWidth -= shape._shadowOffsetX + newHeight -= shape._shadowOffsetY + newbounds = (newX, newY, newWidth, newHeight) + + if x != newX or y != newY or w != newWidth or h != newHeight: + self._view.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self._view.GetDocument(), model, newbounds)) + + + def Draw(self, dc): + pass + + +class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command): + + + def __init__(self, canvasDocument, model, newbounds): + wx.lib.docview.Command.__init__(self, canUndo = True) + self._canvasDocument = canvasDocument + self._model = model + self._oldbounds = model.getEditorBounds() + self._newbounds = newbounds + + + def GetName(self): + name = self._canvasDocument.GetNameForObject(self._model) + if not name: + name = "" + print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model + return _("Move/Resize %s") % name + + + def Do(self): + return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds) + + + def Undo(self): + return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds) + diff --git a/wxPython/samples/ide/activegrid/tool/CodeEditor.py b/wxPython/samples/ide/activegrid/tool/CodeEditor.py new file mode 100644 index 0000000000..09a96abf53 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/CodeEditor.py @@ -0,0 +1,1014 @@ +#---------------------------------------------------------------------------- +# Name: CodeEditor.py +# Purpose: Abstract Code Editor for pydocview tbat uses the Styled Text Control +# +# Author: Peter Yared +# +# Created: 8/10/03 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + + +import STCTextEditor +import wx +import wx.lib.docview +import OutlineService +import os +import re +import string +import sys +import DebuggerService +import MarkerService +_ = wx.GetTranslation +if wx.Platform == '__WXMSW__': + _WINDOWS = True +else: + _WINDOWS = False + + +EXPAND_TEXT_ID = wx.NewId() +COLLAPSE_TEXT_ID = wx.NewId() +EXPAND_TOP_ID = wx.NewId() +COLLAPSE_TOP_ID = wx.NewId() +EXPAND_ALL_ID = wx.NewId() +COLLAPSE_ALL_ID = wx.NewId() +CHECK_CODE_ID = wx.NewId() +AUTO_COMPLETE_ID = wx.NewId() +CLEAN_WHITESPACE = wx.NewId() +COMMENT_LINES_ID = wx.NewId() +UNCOMMENT_LINES_ID = wx.NewId() +INDENT_LINES_ID = wx.NewId() +DEDENT_LINES_ID = wx.NewId() +USE_TABS_ID = wx.NewId() +SET_INDENT_WIDTH_ID = wx.NewId() +FOLDING_ID = wx.NewId() + + +class CodeDocument(STCTextEditor.TextDocument): + pass + + +class CodeView(STCTextEditor.TextView): + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return CodeCtrl + + + def ProcessEvent(self, event): + id = event.GetId() + if id == EXPAND_TEXT_ID: + self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine()) + return True + elif id == COLLAPSE_TEXT_ID: + self.GetCtrl().ToggleFold(self.GetCtrl().GetCurrentLine()) + return True + elif id == EXPAND_TOP_ID: + self.GetCtrl().ToggleFoldAll(expand = True, topLevelOnly = True) + return True + elif id == COLLAPSE_TOP_ID: + self.GetCtrl().ToggleFoldAll(expand = False, topLevelOnly = True) + return True + elif id == EXPAND_ALL_ID: + self.GetCtrl().ToggleFoldAll(expand = True) + return True + elif id == COLLAPSE_ALL_ID: + self.GetCtrl().ToggleFoldAll(expand = False) + return True + elif id == CHECK_CODE_ID: + self.OnCheckCode() + return True + elif id == AUTO_COMPLETE_ID: + self.OnAutoComplete() + return True + elif id == CLEAN_WHITESPACE: + self.OnCleanWhiteSpace() + return True + elif id == SET_INDENT_WIDTH_ID: + self.OnSetIndentWidth() + return True + elif id == USE_TABS_ID: + self.GetCtrl().SetUseTabs(not self.GetCtrl().GetUseTabs()) + return True + elif id == INDENT_LINES_ID: + self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_TAB) + return True + elif id == DEDENT_LINES_ID: + self.GetCtrl().CmdKeyExecute(wx.stc.STC_CMD_BACKTAB) + return True + elif id == COMMENT_LINES_ID: + self.OnCommentLines() + return True + elif id == UNCOMMENT_LINES_ID: + self.OnUncommentLines() + return True + else: + return STCTextEditor.TextView.ProcessEvent(self, event) + + + def ProcessUpdateUIEvent(self, event): + if not self.GetCtrl(): + return False + hasText = self.GetCtrl().GetTextLength() > 0 + id = event.GetId() + if id == EXPAND_TEXT_ID: + event.Enable(self.GetCtrl().CanLineExpand(self.GetCtrl().GetCurrentLine())) + return True + elif id == COLLAPSE_TEXT_ID: + event.Enable(self.GetCtrl().CanLineCollapse(self.GetCtrl().GetCurrentLine())) + return True + elif id == EXPAND_TOP_ID: + event.Enable(hasText) + return True + elif id == COLLAPSE_TOP_ID: + event.Enable(hasText) + return True + elif id == EXPAND_ALL_ID: + event.Enable(hasText) + return True + elif id == COLLAPSE_ALL_ID: + event.Enable(hasText) + return True + elif id == CHECK_CODE_ID: + event.Enable(False) + return True + elif id == AUTO_COMPLETE_ID: + event.Enable(hasText) + return True + elif id == CLEAN_WHITESPACE: + event.Enable(hasText) + return True + elif id == SET_INDENT_WIDTH_ID: + event.Enable(True) + return True + elif id == USE_TABS_ID: + event.Enable(True) + event.Check(self.GetCtrl().GetUseTabs()) + return True + elif id == INDENT_LINES_ID: + event.Enable(hasText) + return True + elif id == DEDENT_LINES_ID: + event.Enable(hasText) + return True + elif id == COMMENT_LINES_ID: + event.Enable(hasText) + return True + elif id == UNCOMMENT_LINES_ID: + event.Enable(hasText) + return True + elif id == FOLDING_ID: + event.Enable(True) + return True + else: + return STCTextEditor.TextView.ProcessUpdateUIEvent(self, event) + + + #---------------------------------------------------------------------------- + # Methods for OutlineService + #---------------------------------------------------------------------------- + + def OnChangeFilename(self): + wx.lib.docview.View.OnChangeFilename(self) + self.LoadOutline(force=True) + + + def ClearOutline(self): + outlineService = wx.GetApp().GetService(OutlineService.OutlineService) + if not outlineService: + return + + outlineView = outlineService.GetView() + if not outlineView: + return + + outlineView.ClearTreeCtrl() + + + def LoadOutline(self, force=False): + outlineService = wx.GetApp().GetService(OutlineService.OutlineService) + if not outlineService: + return + outlineService.LoadOutline(self, force=force) + + + def DoLoadOutlineCallback(self, force=False): + outlineService = wx.GetApp().GetService(OutlineService.OutlineService) + if not outlineService: + return False + + outlineView = outlineService.GetView() + if not outlineView: + return False + + treeCtrl = outlineView.GetTreeCtrl() + if not treeCtrl: + return False + + view = treeCtrl.GetCallbackView() + newCheckSum = self.GenCheckSum() + if not force: + if view and view is self: + if self._checkSum == newCheckSum: + return False + self._checkSum = newCheckSum + + treeCtrl.DeleteAllItems() + + document = self.GetDocument() + if not document: + return True + + filename = document.GetFilename() + if filename: + rootItem = treeCtrl.AddRoot(os.path.basename(filename)) + treeCtrl.SetDoSelectCallback(rootItem, self, None) + else: + return True + + text = self.GetValue() + if not text: + return True + + CLASS_PATTERN = 'class[ \t]+\w+.*?:' + DEF_PATTERN = 'def[ \t]+\w+\(.*?\)' + classPat = re.compile(CLASS_PATTERN, re.M|re.S) + defPat= re.compile(DEF_PATTERN, re.M|re.S) + pattern = re.compile('^[ \t]*((' + CLASS_PATTERN + ')|('+ DEF_PATTERN +'.*?:)).*?$', re.M|re.S) + + iter = pattern.finditer(text) + indentStack = [(0, rootItem)] + for pattern in iter: + line = pattern.string[pattern.start(0):pattern.end(0)] + classLine = classPat.search(line) + if classLine: + indent = classLine.start(0) + itemStr = classLine.string[classLine.start(0):classLine.end(0)-1] # don't take the closing ':' + else: + defLine = defPat.search(line) + if defLine: + indent = defLine.start(0) + itemStr = defLine.string[defLine.start(0):defLine.end(0)] + + if indent == 0: + parentItem = rootItem + else: + lastItem = indentStack.pop() + while lastItem[0] >= indent: + lastItem = indentStack.pop() + indentStack.append(lastItem) + parentItem = lastItem[1] + + item = treeCtrl.AppendItem(parentItem, itemStr) + treeCtrl.SetDoSelectCallback(item, self, (pattern.end(0), pattern.start(0) + indent)) # select in reverse order because we want the cursor to be at the start of the line so it wouldn't scroll to the right + indentStack.append((indent, item)) + + treeCtrl.Expand(rootItem) + + return True + + + def DoSelectCallback(self, data): + if data: + self.EnsureVisibleEnforcePolicy(self.LineFromPosition(data[0])) + # wxBug: need to select in reverse order (end, start) to place cursor at begining of line, + # otherwise, display is scrolled over to the right hard and is hard to view + self.SetSelection(data[1], data[0]) + + +## def checksum(self, bytes): +## def rotate_right(c): +## if c&1: +## return (c>>1)|0x8000 +## else: +## return c>>1 +## +## result = 0 +## for ch in bytes: +## ch = ord(ch) & 0xFF +## result = (rotate_right(result)+ch) & 0xFFFF +## return result +## + + def GenCheckSum(self): + """ Poor man's checksum. We'll assume most changes will change the length of the file. + """ + text = self.GetValue() + if text: + return len(text) + else: + return 0 + + + #---------------------------------------------------------------------------- + # Format methods + #---------------------------------------------------------------------------- + + def OnCheckCode(self): + """ Need to overshadow this for each specific subclass """ + if 0: + try: + code = self.GetCtrl().GetText() + codeObj = compile(code, self.GetDocument().GetFilename(), 'exec') + self._GetParentFrame().SetStatusText(_("The file successfully compiled")) + except SyntaxError, (message, (fileName, line, col, text)): + pos = self.GetCtrl().PositionFromLine(line - 1) + col - 1 + self.GetCtrl().SetSelection(pos, pos) + self._GetParentFrame().SetStatusText(_("Syntax Error: %s") % message) + except: + self._GetParentFrame().SetStatusText(sys.exc_info()[0]) + + + def OnAutoComplete(self): + self.GetCtrl().AutoCompCancel() + self.GetCtrl().AutoCompSetAutoHide(0) + self.GetCtrl().AutoCompSetChooseSingle(True) + self.GetCtrl().AutoCompSetIgnoreCase(True) + context, hint = self.GetAutoCompleteHint() + replaceList, replaceLen = self.GetAutoCompleteKeywordList(context, hint) + if replaceList and len(replaceList) != 0: + self.GetCtrl().AutoCompShow(replaceLen, replaceList) + + + def GetAutoCompleteHint(self): + """ Replace this method with Editor specific method """ + pos = self.GetCtrl().GetCurrentPos() + if pos == 0: + return None, None + if chr(self.GetCtrl().GetCharAt(pos - 1)) == '.': + pos = pos - 1 + hint = None + else: + hint = '' + + validLetters = string.letters + string.digits + '_.' + word = '' + while (True): + pos = pos - 1 + if pos < 0: + break + char = chr(self.GetCtrl().GetCharAt(pos)) + if char not in validLetters: + break + word = char + word + + context = word + if hint is not None: + lastDot = word.rfind('.') + if lastDot != -1: + context = word[0:lastDot] + hint = word[lastDot+1:] + + return context, hint + + + def GetAutoCompleteDefaultKeywords(self): + """ Replace this method with Editor specific keywords """ + return ['Put', 'Editor Specific', 'Keywords', 'Here'] + + + def CaseInsensitiveCompare(self, s1, s2): + """ GetAutoCompleteKeywordList() method used to show keywords in case insensitive order """ + s1L = s1.lower() + s2L = s2.lower() + if s1L == s2L: + return 0 + elif s1L < s2L: + return -1 + else: + return 1 + + + def GetAutoCompleteKeywordList(self, context, hint): + """ Replace this method with Editor specific keywords """ + kw = self.GetAutoCompleteDefaultKeywords() + + if hint and len(hint): + lowerHint = hint.lower() + filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw) # remove variables and methods that don't match hint + kw = filterkw + + if hint: + replaceLen = len(hint) + else: + replaceLen = 0 + + kw.sort(self.CaseInsensitiveCompare) + return " ".join(kw), replaceLen + + + def OnCleanWhiteSpace(self): + newText = "" + for lineNo in self._GetSelectedLineNumbers(): + lineText = string.rstrip(self.GetCtrl().GetLine(lineNo)) + indent = 0 + lstrip = 0 + for char in lineText: + if char == '\t': + indent = indent + self.GetCtrl().GetIndent() + lstrip = lstrip + 1 + elif char in string.whitespace: + indent = indent + 1 + lstrip = lstrip + 1 + else: + break + if self.GetCtrl().GetUseTabs(): + indentText = (indent / self.GetCtrl().GetIndent()) * '\t' + (indent % self.GetCtrl().GetIndent()) * ' ' + else: + indentText = indent * ' ' + lineText = indentText + lineText[lstrip:] + '\n' + newText = newText + lineText + self._ReplaceSelectedLines(newText) + + + def OnSetIndentWidth(self): + dialog = wx.TextEntryDialog(self._GetParentFrame(), _("Enter new indent width (2-10):"), _("Set Indent Width"), "%i" % self.GetCtrl().GetIndent()) + if dialog.ShowModal() == wx.ID_OK: + try: + indent = int(dialog.GetValue()) + if indent >= 2 and indent <= 10: + self.GetCtrl().SetIndent(indent) + self.GetCtrl().SetTabWidth(indent) + except: + pass + dialog.Destroy() + + + def GetIndentWidth(self): + return self.GetCtrl().GetIndent() + + + def OnCommentLines(self): + newText = "" + for lineNo in self._GetSelectedLineNumbers(): + lineText = self.GetCtrl().GetLine(lineNo) + if (len(lineText) > 1 and lineText[0] == '#') or (len(lineText) > 2 and lineText[:2] == '##'): + newText = newText + lineText + else: + newText = newText + "##" + lineText + self._ReplaceSelectedLines(newText) + + + def OnUncommentLines(self): + newText = "" + for lineNo in self._GetSelectedLineNumbers(): + lineText = self.GetCtrl().GetLine(lineNo) + if len(lineText) >= 2 and lineText[:2] == "##": + lineText = lineText[2:] + elif len(lineText) >= 1 and lineText[:1] == "#": + lineText = lineText[1:] + newText = newText + lineText + self._ReplaceSelectedLines(newText) + + + def _GetSelectedLineNumbers(self): + selStart, selEnd = self._GetPositionsBoundingSelectedLines() + return range(self.GetCtrl().LineFromPosition(selStart), self.GetCtrl().LineFromPosition(selEnd)) + + + def _GetPositionsBoundingSelectedLines(self): + startPos = self.GetCtrl().GetCurrentPos() + endPos = self.GetCtrl().GetAnchor() + if startPos > endPos: + temp = endPos + endPos = startPos + startPos = temp + if endPos == self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos)): + endPos = endPos - 1 # If it's at the very beginning of a line, use the line above it as the ending line + selStart = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(startPos)) + selEnd = self.GetCtrl().PositionFromLine(self.GetCtrl().LineFromPosition(endPos) + 1) + return selStart, selEnd + + + def _ReplaceSelectedLines(self, text): + if len(text) == 0: + return + selStart, selEnd = self._GetPositionsBoundingSelectedLines() + self.GetCtrl().SetSelection(selStart, selEnd) + self.GetCtrl().ReplaceSelection(text) + self.GetCtrl().SetSelection(selStart + len(text), selStart) + + + def OnUpdate(self, sender = None, hint = None): + if hint == "ViewStuff": + self.GetCtrl().SetViewDefaults() + elif hint == "Font": + font, color = self.GetFontAndColorFromConfig() + self.GetCtrl().SetFont(font) + self.GetCtrl().SetFontColor(color) + else: + dbg_service = wx.GetApp().GetService(DebuggerService.DebuggerService) + if dbg_service: + dbg_service.SetCurrentBreakpointMarkers(self) + + +class CodeService(STCTextEditor.TextService): + + + def __init__(self): + STCTextEditor.TextService.__init__(self) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + # TODO NEED TO DO INSTANCEOF CHECK HERE FOR SDI + #if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument: + # return + if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + return + + viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) + isWindows = (wx.Platform == '__WXMSW__') + + if not menuBar.FindItemById(EXPAND_TEXT_ID): # check if below menu items have been already been installed + foldingMenu = wx.Menu() + if isWindows: + foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand\tNumpad-Plus"), _("Expands a collapsed block of text")) + else: + foldingMenu.Append(EXPAND_TEXT_ID, _("&Expand"), _("Expands a collapsed block of text")) + + wx.EVT_MENU(frame, EXPAND_TEXT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, EXPAND_TEXT_ID, frame.ProcessUpdateUIEvent) + + if isWindows: + foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse\tNumpad+Minus"), _("Collapse a block of text")) + else: + foldingMenu.Append(COLLAPSE_TEXT_ID, _("&Collapse"), _("Collapse a block of text")) + wx.EVT_MENU(frame, COLLAPSE_TEXT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, COLLAPSE_TEXT_ID, frame.ProcessUpdateUIEvent) + + if isWindows: + foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level\tCtrl+Numpad+Plus"), _("Expands the top fold levels in the document")) + else: + foldingMenu.Append(EXPAND_TOP_ID, _("Expand &Top Level"), _("Expands the top fold levels in the document")) + wx.EVT_MENU(frame, EXPAND_TOP_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, EXPAND_TOP_ID, frame.ProcessUpdateUIEvent) + + if isWindows: + foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level\tCtrl+Numpad+Minus"), _("Collapses the top fold levels in the document")) + else: + foldingMenu.Append(COLLAPSE_TOP_ID, _("Collapse Top &Level"), _("Collapses the top fold levels in the document")) + wx.EVT_MENU(frame, COLLAPSE_TOP_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, COLLAPSE_TOP_ID, frame.ProcessUpdateUIEvent) + + if isWindows: + foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All\tShift+Numpad+Plus"), _("Expands all of the fold levels in the document")) + else: + foldingMenu.Append(EXPAND_ALL_ID, _("Expand &All"), _("Expands all of the fold levels in the document")) + wx.EVT_MENU(frame, EXPAND_ALL_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, EXPAND_ALL_ID, frame.ProcessUpdateUIEvent) + + if isWindows: + foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All\tShift+Numpad+Minus"), _("Collapses all of the fold levels in the document")) + else: + foldingMenu.Append(COLLAPSE_ALL_ID, _("Colla&pse All"), _("Collapses all of the fold levels in the document")) + wx.EVT_MENU(frame, COLLAPSE_ALL_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, COLLAPSE_ALL_ID, frame.ProcessUpdateUIEvent) + + viewMenu.AppendMenu(FOLDING_ID, _("&Folding"), foldingMenu) + wx.EVT_UPDATE_UI(frame, FOLDING_ID, frame.ProcessUpdateUIEvent) + + formatMenuIndex = menuBar.FindMenu(_("&Format")) + if formatMenuIndex > -1: + formatMenu = menuBar.GetMenu(formatMenuIndex) + else: + formatMenu = wx.Menu() + if not menuBar.FindItemById(CHECK_CODE_ID): # check if below menu items have been already been installed + formatMenu.AppendSeparator() + formatMenu.Append(CHECK_CODE_ID, _("&Check Code"), _("Checks the document for syntax and indentation errors")) + wx.EVT_MENU(frame, CHECK_CODE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, CHECK_CODE_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(AUTO_COMPLETE_ID, _("&Auto Complete\tCtrl+Space"), _("Provides suggestions on how to complete the current statement")) + wx.EVT_MENU(frame, AUTO_COMPLETE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, AUTO_COMPLETE_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(CLEAN_WHITESPACE, _("Clean &Whitespace"), _("Converts leading spaces to tabs or vice versa per 'use tabs' and clears trailing spaces")) + wx.EVT_MENU(frame, CLEAN_WHITESPACE, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, CLEAN_WHITESPACE, frame.ProcessUpdateUIEvent) + formatMenu.AppendSeparator() + formatMenu.Append(INDENT_LINES_ID, _("&Indent Lines\tTab"), _("Indents the selected lines one indent width")) + wx.EVT_MENU(frame, INDENT_LINES_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, INDENT_LINES_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(DEDENT_LINES_ID, _("&Dedent Lines\tShift+Tab"), _("Dedents the selected lines one indent width")) + wx.EVT_MENU(frame, DEDENT_LINES_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, DEDENT_LINES_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(COMMENT_LINES_ID, _("Comment &Lines\tCtrl+Q"), _("Comments out the selected lines be prefixing each one with a comment indicator")) + wx.EVT_MENU(frame, COMMENT_LINES_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, COMMENT_LINES_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(UNCOMMENT_LINES_ID, _("&Uncomment Lines\tCtrl+Shift+Q"), _("Removes comment prefixes from each of the selected lines")) + wx.EVT_MENU(frame, UNCOMMENT_LINES_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, UNCOMMENT_LINES_ID, frame.ProcessUpdateUIEvent) + formatMenu.AppendSeparator() + formatMenu.AppendCheckItem(USE_TABS_ID, _("Use &Tabs"), _("Toggles use of tabs or whitespaces for indents")) + wx.EVT_MENU(frame, USE_TABS_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, USE_TABS_ID, frame.ProcessUpdateUIEvent) + formatMenu.Append(SET_INDENT_WIDTH_ID, _("&Set Indent Width..."), _("Sets the indent width")) + wx.EVT_MENU(frame, SET_INDENT_WIDTH_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, SET_INDENT_WIDTH_ID, frame.ProcessUpdateUIEvent) + if formatMenuIndex == -1: + viewMenuIndex = menuBar.FindMenu(_("&View")) + menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format")) + +## accelTable = wx.AcceleratorTable([ +## (wx.ACCEL_NORMAL, wx.WXK_TAB, INDENT_LINES_ID), +## (wx.ACCEL_SHIFT, wx.WXK_TAB, DEDENT_LINES_ID), +## eval(_("wx.ACCEL_CTRL, ord('Q'), COMMENT_LINES_ID")), +## eval(_("wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord('Q'), UNCOMMENT_LINES_ID")) +## ]) +## frame.SetAcceleratorTable(accelTable) + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == EXPAND_TEXT_ID: + event.Enable(False) + return True + elif id == COLLAPSE_TEXT_ID: + event.Enable(False) + return True + elif id == EXPAND_TOP_ID: + event.Enable(False) + return True + elif id == COLLAPSE_TOP_ID: + event.Enable(False) + return True + elif id == EXPAND_ALL_ID: + event.Enable(False) + return True + elif id == COLLAPSE_ALL_ID: + event.Enable(False) + return True + elif id == CHECK_CODE_ID: + event.Enable(False) + return True + elif id == AUTO_COMPLETE_ID: + event.Enable(False) + return True + elif id == CLEAN_WHITESPACE: + event.Enable(False) + return True + elif id == SET_INDENT_WIDTH_ID: + event.Enable(False) + return True + elif id == USE_TABS_ID: + event.Enable(False) + return True + elif id == INDENT_LINES_ID: + event.Enable(False) + return True + elif id == DEDENT_LINES_ID: + event.Enable(False) + return True + elif id == COMMENT_LINES_ID: + event.Enable(False) + return True + elif id == UNCOMMENT_LINES_ID: + event.Enable(False) + return True + elif id == FOLDING_ID: + event.Enable(False) + return True + else: + return STCTextEditor.TextService.ProcessUpdateUIEvent(self, event) + + +class CodeCtrl(STCTextEditor.TextCtrl): + CURRENT_LINE_MARKER_NUM = 2 + BREAKPOINT_MARKER_NUM = 1 + CURRENT_LINE_MARKER_MASK = 0x4 + BREAKPOINT_MARKER_MASK = 0x2 + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + if ID == -1: + ID = wx.NewId() + STCTextEditor.TextCtrl.__init__(self, parent, ID, style) + + self.UsePopUp(False) + self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + self.SetProperty("fold", "1") + + # Setup a margin to hold fold markers + #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? + self.SetMarginType(2, wx.stc.STC_MARGIN_SYMBOL) + self.SetMarginMask(2, wx.stc.STC_MASK_FOLDERS) + self.SetMarginSensitive(2, True) + self.SetMarginWidth(2, 12) + + self.SetMarginSensitive(1, False) + self.SetMarginMask(1, 0x4) + + self.SetMarginSensitive(0, True) + self.SetMarginType(0, wx.stc.STC_MARGIN_SYMBOL) + self.SetMarginMask(0, 0x3) + self.SetMarginWidth(0, 12) + + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUSCONNECTED, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUSCONNECTED, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_TCORNER, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_LCORNER, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_VLINE, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS, "white", "black") + self.MarkerDefine(wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS, "white", "black") + # Define the current line marker + self.MarkerDefine(CodeCtrl.CURRENT_LINE_MARKER_NUM, wx.stc.STC_MARK_SHORTARROW, wx.BLACK, (255,255,128)) + # Define the breakpoint marker + self.MarkerDefine(CodeCtrl.BREAKPOINT_MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, (255,0,0)) + + if _WINDOWS: # should test to see if menu item exists, if it does, add this workaround + self.CmdKeyClear(wx.stc.STC_KEY_TAB, 0) # menu item "Indent Lines" from CodeService.InstallControls() generates another INDENT_LINES_ID event, so we'll explicitly disable the tab processing in the editor + + wx.stc.EVT_STC_MARGINCLICK(self, ID, self.OnMarginClick) + wx.EVT_KEY_DOWN(self, self.OnKeyPressed) + if self.GetMatchingBraces(): + wx.stc.EVT_STC_UPDATEUI(self, ID, self.OnUpdateUI) + + self.StyleClearAll() + self.UpdateStyles() + + + def OnRightUp(self, event): + #Hold onto the current line number, no way to get it later. + self._rightClickPosition = self.PositionFromPoint(event.GetPosition()) + self._rightClickLine = self.LineFromPosition(self._rightClickPosition) + self.PopupMenu(self.CreatePopupMenu(), event.GetPosition()) + self._rightClickLine = -1 + self._rightClickPosition = -1 + + + def CreatePopupMenu(self): + TOGGLEBREAKPOINT_ID = wx.NewId() + TOGGLEMARKER_ID = wx.NewId() + SYNCTREE_ID = wx.NewId() + + menu = wx.Menu() + + self.Bind(wx.EVT_MENU, self.OnPopSyncOutline, id=SYNCTREE_ID) + item = wx.MenuItem(menu, SYNCTREE_ID, _("Find in Outline View")) + menu.AppendItem(item) + menu.AppendSeparator() + self.Bind(wx.EVT_MENU, self.OnPopToggleBP, id=TOGGLEBREAKPOINT_ID) + item = wx.MenuItem(menu, TOGGLEBREAKPOINT_ID, _("Toggle Breakpoint")) + menu.AppendItem(item) + self.Bind(wx.EVT_MENU, self.OnPopToggleMarker, id=TOGGLEMARKER_ID) + item = wx.MenuItem(menu, TOGGLEMARKER_ID, _("Toggle Marker")) + menu.AppendItem(item) + menu.AppendSeparator() + + itemIDs = [wx.ID_UNDO, wx.ID_REDO, None, + wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL] + + menuBar = wx.GetApp().GetTopWindow().GetMenuBar() + for itemID in itemIDs: + if not itemID: + menu.AppendSeparator() + else: + item = menuBar.FindItemById(itemID) + if item: + menu.Append(itemID, item.GetLabel()) + + return menu + + + def OnPopToggleBP(self, event): + """ Toggle break point on right click line, not current line """ + wx.GetApp().GetService(DebuggerService.DebuggerService).OnToggleBreakpoint(event, line=self._rightClickLine) + + + def OnPopToggleMarker(self, event): + """ Toggle marker on right click line, not current line """ + wx.GetApp().GetDocumentManager().GetCurrentView().MarkerToggle(lineNum = self._rightClickLine) + + + def OnPopSyncOutline(self, event): + wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(wx.GetApp().GetDocumentManager().GetCurrentView(), position=self._rightClickPosition) + + + def HasSelection(self): + return self.GetSelectionStart() - self.GetSelectionEnd() != 0 + + + def ClearCurrentLineMarkers(self): + self.MarkerDeleteAll(CodeCtrl.CURRENT_LINE_MARKER_NUM) + + + def ClearCurrentBreakpoinMarkers(self): + self.MarkerDeleteAll(CodeCtrl.BREAKPOINT_MARKER_NUM) + + + def GetDefaultFont(self): + if wx.Platform == '__WXMSW__': + font = "Courier New" + else: + font = "Courier" + return wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font) + + + def GetMatchingBraces(self): + """ Overwrite this method for language specific braces """ + return "[]{}()" + + + def CanWordWrap(self): + return False + + + def SetFont(self, font): + self._font = font + + + def SetFontColor(self, fontColor): + self._fontColor = fontColor + + + def UpdateStyles(self): + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + # Global default styles for all languages + self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(font)s,fore:#FFFFFF,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER, "face:%(font)s,back:#C0C0C0,face:%(font)s,size:%(size2)d" % faces) + self.StyleSetSpec(wx.stc.STC_STYLE_CONTROLCHAR, "face:%(font)s" % faces) + self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT, "face:%(font)s,fore:#000000,back:#70FFFF,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD, "face:%(font)s,fore:#000000,back:#FF0000,size:%(size)d" % faces) + + + def OnKeyPressed(self, event): + if self.CallTipActive(): + self.CallTipCancel() + key = event.KeyCode() + if False: # key == wx.WXK_SPACE and event.ControlDown(): + pos = self.GetCurrentPos() + # Tips + if event.ShiftDown(): + self.CallTipSetBackground("yellow") + self.CallTipShow(pos, 'param1, param2') + # Code completion + else: + #lst = [] + #for x in range(50000): + # lst.append('%05d' % x) + #st = string.join(lst) + #print len(st) + #self.AutoCompShow(0, st) + + kw = keyword.kwlist[:] + kw.append("zzzzzz") + kw.append("aaaaa") + kw.append("__init__") + kw.append("zzaaaaa") + kw.append("zzbaaaa") + kw.append("this_is_a_longer_value") + kw.append("this_is_a_much_much_much_much_much_much_much_longer_value") + + kw.sort() # Python sorts are case sensitive + self.AutoCompSetIgnoreCase(False) # so this needs to match + + self.AutoCompShow(0, string.join(kw)) + elif key == wx.WXK_RETURN: + self.DoIndent() + else: + STCTextEditor.TextCtrl.OnKeyPressed(self, event) + + + def DoIndent(self): + self.AddText('\n') + # Need to do a default one for all languges + + + def OnMarginClick(self, evt): + # fold and unfold as needed + if evt.GetMargin() == 2: + if evt.GetShift() and evt.GetControl(): + lineCount = self.GetLineCount() + expanding = True + + # find out if we are folding or unfolding + for lineNum in range(lineCount): + if self.GetFoldLevel(lineNum) & wx.stc.STC_FOLDLEVELHEADERFLAG: + expanding = not self.GetFoldExpanded(lineNum) + break; + + self.ToggleFoldAll(expanding) + else: + lineClicked = self.LineFromPosition(evt.GetPosition()) + if self.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG: + if evt.GetShift(): + self.SetFoldExpanded(lineClicked, True) + self.Expand(lineClicked, True, True, 1) + elif evt.GetControl(): + if self.GetFoldExpanded(lineClicked): + self.SetFoldExpanded(lineClicked, False) + self.Expand(lineClicked, False, True, 0) + else: + self.SetFoldExpanded(lineClicked, True) + self.Expand(lineClicked, True, True, 100) + else: + self.ToggleFold(lineClicked) + + elif evt.GetMargin() == 0: + #This is used to toggle breakpoints via the debugger service. + db_service = wx.GetApp().GetService(DebuggerService.DebuggerService) + if db_service: + db_service.OnToggleBreakpoint(evt, line=self.LineFromPosition(evt.GetPosition())) + + + def OnUpdateUI(self, evt): + braces = self.GetMatchingBraces() + + # check for matching braces + braceAtCaret = -1 + braceOpposite = -1 + charBefore = None + caretPos = self.GetCurrentPos() + if caretPos > 0: + charBefore = self.GetCharAt(caretPos - 1) + styleBefore = self.GetStyleAt(caretPos - 1) + + # check before + if charBefore and chr(charBefore) in braces: + braceAtCaret = caretPos - 1 + + # check after + if braceAtCaret < 0: + charAfter = self.GetCharAt(caretPos) + styleAfter = self.GetStyleAt(caretPos) + if charAfter and chr(charAfter) in braces: + braceAtCaret = caretPos + + if braceAtCaret >= 0: + braceOpposite = self.BraceMatch(braceAtCaret) + + if braceAtCaret != -1 and braceOpposite == -1: + self.BraceBadLight(braceAtCaret) + else: + self.BraceHighlight(braceAtCaret, braceOpposite) + + evt.Skip() + + + def ToggleFoldAll(self, expand = True, topLevelOnly = False): + i = 0 + lineCount = self.GetLineCount() + while i < lineCount: + if not topLevelOnly or (topLevelOnly and self.GetFoldLevel(i) & wx.stc.STC_FOLDLEVELNUMBERMASK == wx.stc.STC_FOLDLEVELBASE): + if (expand and self.CanLineExpand(i)) or (not expand and self.CanLineCollapse(i)): + self.ToggleFold(i) + i = i + 1 + + + def CanLineExpand(self, line): + return not self.GetFoldExpanded(line) + + + def CanLineCollapse(self, line): + return self.GetFoldExpanded(line) and self.GetFoldLevel(line) & wx.stc.STC_FOLDLEVELHEADERFLAG + + + def Expand(self, line, doExpand, force=False, visLevels=0, level=-1): + lastChild = self.GetLastChild(line, level) + line = line + 1 + while line <= lastChild: + if force: + if visLevels > 0: + self.ShowLines(line, line) + else: + self.HideLines(line, line) + else: + if doExpand: + self.ShowLines(line, line) + + if level == -1: + level = self.GetFoldLevel(line) + + if level & wx.stc.STC_FOLDLEVELHEADERFLAG: + if force: + if visLevels > 1: + self.SetFoldExpanded(line, True) + else: + self.SetFoldExpanded(line, False) + line = self.Expand(line, doExpand, force, visLevels-1) + + else: + if doExpand and self.GetFoldExpanded(line): + line = self.Expand(line, True, force, visLevels-1) + else: + line = self.Expand(line, False, force, visLevels-1) + else: + line = line + 1; + + return line + + diff --git a/wxPython/samples/ide/activegrid/tool/DebuggerHarness.py b/wxPython/samples/ide/activegrid/tool/DebuggerHarness.py new file mode 100644 index 0000000000..a12b8e579b --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/DebuggerHarness.py @@ -0,0 +1,690 @@ +#---------------------------------------------------------------------------- +# Name: DebuggerHarness.py +# Purpose: +# +# Author: Matt Fryer +# +# Created: 7/28/04 +# CVS-ID: $Id$ +# Copyright: (c) 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +import bdb +import sys +import SimpleXMLRPCServer +import threading +import xmlrpclib +import os +import types +import Queue +import traceback +import inspect +from xml.dom.minidom import getDOMImplementation +import atexit +import pickle + +if sys.platform.startswith("win"): + import win32api + _WINDOWS = True +else: + _WINDOWS = False + +_VERBOSE = False +_DEBUG_DEBUGGER = False + +class Adb(bdb.Bdb): + + def __init__(self, harness, queue): + bdb.Bdb.__init__(self) + self._harness = harness + self._userBreak = False + self._queue = queue + self._knownCantExpandFiles = {} + self._knownExpandedFiles = {} + + def getLongName(self, filename): + if not _WINDOWS: + return filename + if self._knownCantExpandFiles.get(filename): + return filename + if self._knownExpandedFiles.get(filename): + return self._knownExpandedFiles.get(filename) + try: + newname = win32api.GetLongPathName(filename) + self._knownExpandedFiles[filename] = newname + return newname + except: + self._knownCantExpandFiles[filename] = filename + return filename + + def canonic(self, orig_filename): + if orig_filename == "<" + orig_filename[1:-1] + ">": + return orig_filename + filename = self.getLongName(orig_filename) + + canonic = self.fncache.get(filename) + if not canonic: + canonic = os.path.abspath(filename) + canonic = os.path.normcase(canonic) + self.fncache[filename] = canonic + return canonic + + + # Overriding this so that we continue to trace even if no breakpoints are set. + def set_continue(self): + self.stopframe = self.botframe + self.returnframe = None + self.quitting = 0 + + def do_clear(self, arg): + bdb.Breakpoint.bpbynumber[int(arg)].deleteMe() + + def user_line(self, frame): + if self.in_debugger_code(frame): + self.set_step() + return + message = self.__frame2message(frame) + self._harness.interaction(message, frame, "") + + def user_call(self, frame, argument_list): + if self.in_debugger_code(frame): + self.set_step() + return + if self.stop_here(frame): + message = self.__frame2message(frame) + self._harness.interaction(message, frame, "") + + def user_return(self, frame, return_value): + if self.in_debugger_code(frame): + self.set_step() + return + message = self.__frame2message(frame) + self._harness.interaction(message, frame, "") + + def user_exception(self, frame, (exc_type, exc_value, exc_traceback)): + frame.f_locals['__exception__'] = exc_type, exc_value + if type(exc_type) == type(''): + exc_type_name = exc_type + else: + exc_type_name = exc_type.__name__ + message = "Exception occured: " + repr(exc_type_name) + " See locals.__exception__ for details." + traceback.print_exception(exc_type, exc_value, exc_traceback) + self._harness.interaction(message, frame, message) + + def in_debugger_code(self, frame): + if _DEBUG_DEBUGGER: return False + message = self.__frame2message(frame) + return message.count('DebuggerHarness') > 0 + + def frame2message(self, frame): + return self.__frame2message(frame) + + def __frame2message(self, frame): + code = frame.f_code + filename = code.co_filename + lineno = frame.f_lineno + basename = os.path.basename(filename) + message = "%s:%s" % (basename, lineno) + if code.co_name != "?": + message = "%s: %s()" % (message, code.co_name) + return message + + def runFile(self, fileName): + self.reset() + #global_dict = {} + #global_dict['__name__'] = '__main__' + try: + fileToRun = open(fileName, mode='r') + if _VERBOSE: print "Running file ", fileName + sys.settrace(self.trace_dispatch) + import __main__ + exec fileToRun in __main__.__dict__,__main__.__dict__ + except SystemExit: + pass + except: + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb) + + sys.settrace(None) + self.quitting = 1 + #global_dict.clear() + + def trace_dispatch(self, frame, event, arg): + if self.quitting: + return # None + # Check for ui events + self.readQueue() + if event == 'line': + return self.dispatch_line(frame) + if event == 'call': + return self.dispatch_call(frame, arg) + if event == 'return': + return self.dispatch_return(frame, arg) + if event == 'exception': + return self.dispatch_exception(frame, arg) + print 'Adb.dispatch: unknown debugging event:', `event` + return self.trace_dispatch + + def readQueue(self): + while self._queue.qsize(): + try: + item = self._queue.get_nowait() + if item.kill(): + self._harness.do_exit(kill=True) + elif item.breakHere(): + self._userBreak = True + elif item.hasBreakpoints(): + self.set_all_breakpoints(item.getBreakpoints()) + except Queue.Empty: + pass + + def set_all_breakpoints(self, dict): + self.clear_all_breaks() + for fileName in dict.keys(): + lineList = dict[fileName] + for lineNumber in lineList: + + if _VERBOSE: print "Setting break at line ", str(lineNumber), " in file ", self.canonic(fileName) + self.set_break(fileName, int(lineNumber)) + return "" + + def stop_here(self, frame): + if( self._userBreak ): + return True + + + # (CT) stopframe may now also be None, see dispatch_call. + # (CT) the former test for None is therefore removed from here. + if frame is self.stopframe: + return True + while frame is not None and frame is not self.stopframe: + if frame is self.botframe: + return True + frame = frame.f_back + return False + +class BreakNotify(object): + def __init__(self, bps=None, break_here=False, kill=False): + self._bps = bps + self._break_here = break_here + self._kill = kill + + def breakHere(self): + return self._break_here + + def kill(self): + return self._kill + + def getBreakpoints(self): + return self._bps + + def hasBreakpoints(self): + return (self._bps != None) + +class AGXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): + def __init__(self, address, logRequests=0): + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, address, logRequests=logRequests) + +class BreakListenerThread(threading.Thread): + def __init__(self, host, port, queue): + threading.Thread.__init__(self) + self._host = host + self._port = int(port) + self._keepGoing = True + self._queue = queue + self._server = AGXMLRPCServer((self._host, self._port), logRequests=0) + self._server.register_function(self.update_breakpoints) + self._server.register_function(self.break_requested) + self._server.register_function(self.die) + + def break_requested(self): + bn = BreakNotify(break_here=True) + self._queue.put(bn) + return "" + + def update_breakpoints(self, pickled_Binary_bpts): + dict = pickle.loads(pickled_Binary_bpts.data) + bn = BreakNotify(bps=dict) + self._queue.put(bn) + return "" + + def die(self): + bn = BreakNotify(kill=True) + self._queue.put(bn) + return "" + + def run(self): + while self._keepGoing: + try: + self._server.handle_request() + except: + if _VERBOSE: + tp, val, tb = sys.exc_info() + print "Exception in BreakListenerThread.run():", str(tp), str(val) + self._keepGoing = False + + def AskToStop(self): + self._keepGoing = False + if type(self._server) is not types.NoneType: + if _VERBOSE: print "Before calling server close on breakpoint server" + self._server.server_close() + if _VERBOSE: print "Calling server close on breakpoint server" + + +class DebuggerHarness(object): + + def __init__(self): + # Host and port for debugger-side RPC server + self._hostname = sys.argv[1] + self._portNumber = int(sys.argv[2]) + # Name the gui proxy object is registered under + self._breakPortNumber = int(sys.argv[3]) + # Host and port where the gui proxy can be found. + self._guiHost = sys.argv[4] + self._guiPort = int(sys.argv[5]) + # Command to debug. + self._command = sys.argv[6] + # Strip out the harness' arguments so that the process we run will see argv as if + # it was called directly. + sys.argv = sys.argv[6:] + self._currentFrame = None + self._wait = False + # Connect to the gui-side RPC server. + self._guiServerUrl = 'http://' + self._guiHost + ':' + str(self._guiPort) + '/' + if _VERBOSE: print "Connecting to gui server at ", self._guiServerUrl + self._guiServer = xmlrpclib.ServerProxy(self._guiServerUrl,allow_none=1) + + # Start the break listener + self._breakQueue = Queue.Queue(50) + self._breakListener = BreakListenerThread(self._hostname, self._breakPortNumber, self._breakQueue) + self._breakListener.start() + # Create the debugger. + self._adb = Adb(self, self._breakQueue) + + # Create the debugger-side RPC Server and register functions for remote calls. + self._server = AGXMLRPCServer((self._hostname, self._portNumber), logRequests=0) + self._server.register_function(self.set_step) + self._server.register_function(self.set_continue) + self._server.register_function(self.set_next) + self._server.register_function(self.set_return) + self._server.register_function(self.set_breakpoint) + self._server.register_function(self.clear_breakpoint) + self._server.register_function(self.set_all_breakpoints) + self._server.register_function(self.attempt_introspection) + self._server.register_function(self.add_watch) + + self.message_frame_dict = {} + self.introspection_list = [] + atexit.register(self.do_exit) + + def run(self): + self._adb.runFile(self._command) + self.do_exit(kill=True) + + + def do_exit(self, kill=False): + self._adb.set_quit() + self._breakListener.AskToStop() + self._server.server_close() + try: + self._guiServer.quit() + except: + pass + if kill: + try: + sys.exit() + except: + pass + + def set_breakpoint(self, fileName, lineNo): + self._adb.set_break(fileName, lineNo) + return "" + + def set_all_breakpoints(self, dict): + self._adb.clear_all_breaks() + for fileName in dict.keys(): + lineList = dict[fileName] + for lineNumber in lineList: + self._adb.set_break(fileName, int(lineNumber)) + if _VERBOSE: print "Setting break at ", str(lineNumber), " in file ", fileName + return "" + + def clear_breakpoint(self, fileName, lineNo): + self._adb.clear_break(fileName, lineNo) + return "" + + def add_watch(self, name, text, frame_message, run_once): + if len(frame_message) > 0: + frame = self.message_frame_dict[frame_message] + try: + item = eval(text, frame.f_globals, frame.f_locals) + return self.get_watch_document(item, name) + except: + tp, val, tb = sys.exc_info() + return self.get_exception_document(tp, val, tb) + return "" + + def attempt_introspection(self, frame_message, chain): + try: + frame = self.message_frame_dict[frame_message] + if frame: + name = chain.pop(0) + if name == 'globals': + item = frame.f_globals + elif name == 'locals': + item = frame.f_locals + + for name in chain: + item = self.getNextItem(item, name) + return self.get_introspection_document(item, name) + except: + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb) + return self.get_empty_introspection_document() + + def getNextItem(self, link, identifier): + tp = type(link) + if self.isTupleized(identifier): + return self.deTupleize(link, identifier) + else: + if tp == types.DictType or tp == types.DictProxyType: + return link[identifier] + else: + if hasattr(link, identifier): + return getattr(link, identifier) + if _VERBOSE or True: print "Failed to find link ", identifier, " on thing: ", self.saferepr(link), " of type ", repr(type(link)) + return None + + def isPrimitive(self, item): + tp = type(item) + return tp is types.IntType or tp is types.LongType or tp is types.FloatType \ + or tp is types.BooleanType or tp is types.ComplexType \ + or tp is types.StringType + + def isTupleized(self, value): + return value.count('[') + + def deTupleize(self, link, string1): + try: + start = string1.find('[') + end = string1.find(']') + num = int(string1[start+1:end]) + return link[num] + except: + tp,val,tb = sys.exc_info() + if _VERBOSE: print "Got exception in deTupleize: ", val + return None + + def wrapAndCompress(self, stringDoc): + import bz2 + return xmlrpclib.Binary(bz2.compress(stringDoc)) + + def get_empty_introspection_document(self): + doc = getDOMImplementation().createDocument(None, "replacement", None) + return self.wrapAndCompress(doc.toxml()) + + def get_watch_document(self, item, identifier): + doc = getDOMImplementation().createDocument(None, "watch", None) + top_element = doc.documentElement + self.addAny(top_element, identifier, item, doc, 2) + return self.wrapAndCompress(doc.toxml()) + + def get_introspection_document(self, item, identifier): + doc = getDOMImplementation().createDocument(None, "replacement", None) + top_element = doc.documentElement + self.addAny(top_element, identifier, item, doc, 2) + return self.wrapAndCompress(doc.toxml()) + + def get_exception_document(self, name, tp, val, tb): + stack = traceback.format_exception(tp, val, tb) + wholeStack = "" + for line in stack: + wholeStack += line + doc = getDOMImplementation().createDocument(None, "watch", None) + top_element = doc.documentElement + item_node = doc.createElement("dict_nv_element") + item_node.setAttribute('value', wholeStack) + item_node.setAttribute('name', str(name)) + top_element.appendChild(item_node) + + def addAny(self, top_element, name, item, doc, ply): + tp = type(item) + if ply < 1: + self.addNode(top_element,name, self.saferepr(item), doc) + elif tp is types.TupleType or tp is types.ListType: + self.addTupleOrList(top_element, name, item, doc, ply - 1) + elif tp is types.DictType or tp is types.DictProxyType: + self.addDict(top_element, name, item, doc, ply -1) + elif inspect.ismodule(item): + self.addModule(top_element, name, item, doc, ply -1) + elif inspect.isclass(item) or tp is types.InstanceType: + self.addClass(top_element, name, item, doc, ply -1) + #elif hasattr(item, '__dict__'): + # self.addDictAttr(top_element, name, item, doc, ply -1) + elif hasattr(item, '__dict__'): + self.addDict(top_element, name, item.__dict__, doc, ply -1) + else: + self.addNode(top_element,name, self.saferepr(item), doc) + + def addTupleOrList(self, top_node, name, tupple, doc, ply): + tupleNode = doc.createElement('tuple') + tupleNode.setAttribute('name', str(name)) + tupleNode.setAttribute('value', str(type(tupple))) + top_node.appendChild(tupleNode) + count = 0 + for item in tupple: + self.addAny(tupleNode, name +'[' + str(count) + ']',item, doc, ply -1) + count += 1 + + + def getFrameXML(self, base_frame): + doc = getDOMImplementation().createDocument(None, "stack", None) + top_element = doc.documentElement + + stack = [] + frame = base_frame + while frame is not None: + if((frame.f_code.co_filename.count('DebuggerHarness.py') == 0) or _DEBUG_DEBUGGER): + stack.append(frame) + frame = frame.f_back + stack.reverse() + self.message_frame_dict = {} + for f in stack: + self.addFrame(f,top_element, doc) + return doc.toxml() + + def addFrame(self, frame, root_element, document): + frameNode = document.createElement('frame') + root_element.appendChild(frameNode) + + code = frame.f_code + filename = code.co_filename + frameNode.setAttribute('file', str(filename)) + frameNode.setAttribute('line', str(frame.f_lineno)) + message = self._adb.frame2message(frame) + frameNode.setAttribute('message', message) + #print "Frame: %s %s %s" %(message, frame.f_lineno, filename) + self.message_frame_dict[message] = frame + self.addDict(frameNode, "locals", frame.f_locals, document, 2) + self.addNode(frameNode, "globals", "", document) + + def getRepr(self, varName, globals, locals): + try: + return repr(eval(varName, globals, locals)) + except: + return 'Error: Could not recover value.' + + def addNode(self, parent_node, name, value, document): + item_node = document.createElement("dict_nv_element") + item_node.setAttribute('value', self.saferepr(value)) + item_node.setAttribute('name', str(name)) + parent_node.appendChild(item_node) + + def addDictAttr(self, root_node, name, thing, document, ply): + dict_node = document.createElement('thing') + root_node.setAttribute('name', name) + root_node.setAttribute('value', str(type(dict)) + " add attr") + self.addDict(root_node, name, thing.__dict__, document, ply) # Not decreminting ply + + def saferepr(self, thing): + try: + return repr(thing) + except: + tp, val, tb = sys.exc_info() + return repr(val) + + def addDict(self, root_node, name, dict, document, ply): + dict_node = document.createElement('dict') + dict_node.setAttribute('name', name) + dict_node.setAttribute('value', str(type(dict)) + " add dict") + root_node.appendChild(dict_node) + for key in dict.keys(): + strkey = str(key) + try: + self.addAny(dict_node, strkey, dict[key], document, ply-1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + print "Error recovering key: ", str(key), " from node ", str(name), " Val = ", str(val) + traceback.print_exception(tp, val, tb) + self.addAny(dict_node, strkey, "Exception getting " + str(name) + "[" + strkey + "]: " + str(val), document, ply -1) + + def addClass(self, root_node, name, class_item, document, ply): + item_node = document.createElement('class') + item_node.setAttribute('name', str(name)) + root_node.appendChild(item_node) + try: + if hasattr(class_item, '__dict__'): + self.addAny(item_node, '__dict__', class_item.__dict__, document, ply -1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + traceback.print_exception(tp, val, tb) + self.addAny(item_node, '__dict__', "Exception getting __dict__: " + str(val), document, ply -1) + try: + if hasattr(class_item, '__name__'): + self.addAny(item_node,'__name__',class_item.__name__, document, ply -1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + traceback.print_exception(tp, val, tb) + self.addAny(item_node,'__name__',"Exception getting class.__name__: " + val, document, ply -1) + try: + if hasattr(class_item, '__module__'): + self.addAny(item_node, '__module__', class_item.__module__, document, ply -1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + traceback.print_exception(tp, val, tb) + self.addAny(item_node, '__module__', "Exception getting class.__module__: " + val, document, ply -1) + try: + if hasattr(class_item, '__doc__'): + self.addAny(item_node, '__doc__', class_item.__doc__, document, ply -1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + traceback.print_exception(tp, val, tb) + self.addAny(item_node, '__doc__', "Exception getting class.__doc__: " + val, document, ply -1) + try: + if hasattr(class_item, '__bases__'): + self.addAny(item_node, '__bases__', class_item.__bases__, document, ply -1) + except: + tp,val,tb=sys.exc_info() + if _VERBOSE: + traceback.print_exception(tp, val, tb) + self.addAny(item_node, '__bases__', "Exception getting class.__bases__: " + val, document, ply -1) + + def addModule(self, root_node, name, module_item, document, ply): + item_node = document.createElement('module') + item_node.setAttribute('name', str(name)) + root_node.appendChild(item_node) + try: + if hasattr(module_item, '__file__'): + self.addAny(item_node, '__file__', module_item.__file__, document, ply -1) + except: + pass + try: + if hasattr(module_item, '__doc__'): + self.addAny(item_node,'__doc__', module_item.__doc__, document, ply -1) + except: + pass + + # The debugger calls this method when it reaches a breakpoint. + def interaction(self, message, frame, info): + if _VERBOSE: + print 'hit debug side interaction' + self._userBreak = False + + self._currentFrame = frame + done = False + while not done: + try: + import bz2 + xml = self.getFrameXML(frame) + arg = xmlrpclib.Binary(bz2.compress(xml)) + if _VERBOSE: + print '============== calling gui side interaction============' + self._guiServer.interaction(xmlrpclib.Binary(message), arg, info) + if _VERBOSE: + print 'after interaction' + done = True + except: + tp, val, tb = sys.exc_info() + if True or _VERBOSE: + print 'Error contacting GUI server!: ' + try: + traceback.print_exception(tp, val, tb) + except: + print "Exception printing traceback", + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb) + done = False + # Block while waiting to be called back from the GUI. Eventually, self._wait will + # be set false by a function on this side. Seems pretty lame--I'm surprised it works. + self.waitForRPC() + + + def waitForRPC(self): + self._wait = True + while self._wait : + try: + if _VERBOSE: + print "+++ in harness wait for rpc, before handle_request" + self._server.handle_request() + if _VERBOSE: + print "+++ in harness wait for rpc, after handle_request" + except: + if _VERBOSE: + tp, val, tb = sys.exc_info() + print "Got waitForRpc exception : ", repr(tp), ": ", val + #time.sleep(0.1) + + def set_step(self): + self._adb.set_step() + self._wait = False + return "" + + def set_continue(self): + self._adb.set_continue() + self._wait = False + return "" + + def set_next(self): + self._adb.set_next(self._currentFrame) + self._wait = False + return "" + + def set_return(self): + self._adb.set_return(self._currentFrame) + self._wait = False + return "" + +if __name__ == '__main__': + try: + harness = DebuggerHarness() + harness.run() + except SystemExit: + print "Exiting..." + except: + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb) diff --git a/wxPython/samples/ide/activegrid/tool/DebuggerService.py b/wxPython/samples/ide/activegrid/tool/DebuggerService.py new file mode 100644 index 0000000000..ef4a37a879 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/DebuggerService.py @@ -0,0 +1,2275 @@ +#---------------------------------------------------------------------------- +# Name: DebuggerService.py +# Purpose: Debugger Service for Python. +# +# Author: Matt Fryer +# +# Created: 12/9/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import wx.lib.intctrl +import wx.lib.docview +import wx.lib.dialogs +import wx.gizmos +import wx._core +import wx.lib.pydocview +import Service +import STCTextEditor +import CodeEditor +import PythonEditor +from IDE import ACTIVEGRID_BASE_IDE +if not ACTIVEGRID_BASE_IDE: + import ProcessModelEditor +import wx.lib.scrolledpanel as scrolled +import sys +import time +import SimpleXMLRPCServer +import xmlrpclib +import os +import threading +import process +import Queue +import SocketServer +import ProjectEditor +import types +from xml.dom.minidom import parse, parseString +import bz2 +import pickle +import DebuggerHarness +import traceback +import StringIO +if wx.Platform == '__WXMSW__': + import win32api + _WINDOWS = True +else: + _WINDOWS = False +_ = wx.GetTranslation + +_VERBOSE = False +_WATCHES_ON = False + +# Class to read from stdout or stderr and write the result to a text control. +# Args: file=file-like object +# callback_function= function that takes a single argument, the line of text +# read. +class OutputReaderThread(threading.Thread): + def __init__(self, file, callback_function, callbackOnExit=None, accumulate=True): + threading.Thread.__init__(self) + self._file = file + self._callback_function = callback_function + self._keepGoing = True + self._lineCount = 0 + self._accumulate = accumulate + self._callbackOnExit = callbackOnExit + + def run(self): + file = self._file + start = time.time() + output = "" + while self._keepGoing: + try: + # This could block--how to handle that? + text = file.readline() + if text == '' or text == None: + self._keepGoing = False + elif not self._accumulate: + self._callback_function(text) + else: + # Should use a buffer? StringIO? + output += text + # Seems as though the read blocks if we got an error, so, to be + # sure that at least some of the exception gets printed, always + # send the first hundred lines back as they come in. + if self._lineCount < 100: + self._callback_function(output) + self._lineCount += 1 + output = "" + elif time.time() - start > 0.25: + try: + self._callback_function(output) + except wx._core.PyDeadObjectError: + # GUI was killed while we were blocked. + self._keepGoing = False + start = time.time() + output = "" + except: + tp, val, tb = sys.exc_info() + print "Exception in OutputReaderThread.run():", tp, val + self._keepGoing = False + if self._callbackOnExit: + try: + self._callbackOnExit() + except wx._core.PyDeadObjectError: + pass + if _VERBOSE: print "Exiting OutputReaderThread" + + def AskToStop(self): + self._keepGoing = False + +import wx.lib.newevent +(UpdateTextEvent, EVT_UPDATE_STDTEXT) = wx.lib.newevent.NewEvent() +(UpdateErrorEvent, EVT_UPDATE_ERRTEXT) = wx.lib.newevent.NewEvent() + +class Executor: + + def GetPythonExecutablePath(): + config = wx.ConfigBase_Get() + path = config.Read("ActiveGridPythonLocation") + if path: + return path + wx.MessageBox(_("To proceed I need to know the location of the python.exe you would like to use.\nTo set this, go to Tools-->Options and use the 'Python' tab to enter a value.\n"), _("Python Executable Location Unknown")) + return None + GetPythonExecutablePath = staticmethod(GetPythonExecutablePath) + + def __init__(self, fileName, wxComponent, arg1=None, arg2=None, arg3=None, arg4=None, arg5=None, arg6=None, arg7=None, arg8=None, arg9=None, callbackOnExit=None): + self._fileName = fileName + self._stdOutCallback = self.OutCall + self._stdErrCallback = self.ErrCall + self._callbackOnExit = callbackOnExit + self._wxComponent = wxComponent + path = Executor.GetPythonExecutablePath() + self._cmd = '"' + path + '" -u \"' + fileName + '\"' + #Better way to do this? Quotes needed for windows file paths. + if(arg1 != None): + self._cmd += ' \"' + arg1 + '\"' + if(arg2 != None): + self._cmd += ' \"' + arg2 + '\"' + if(arg3 != None): + self._cmd += ' \"' + arg3 + '\"' + if(arg4 != None): + self._cmd += ' \"' + arg4 + '\"' + if(arg5 != None): + self._cmd += ' \"' + arg5 + '\"' + if(arg6 != None): + self._cmd += ' \"' + arg6 + '\"' + if(arg7 != None): + self._cmd += ' \"' + arg7 + '\"' + if(arg8 != None): + self._cmd += ' \"' + arg8 + '\"' + if(arg9 != None): + self._cmd += ' \"' + arg9 + '\"' + + self._stdOutReader = None + self._stdErrReader = None + self._process = None + + def OutCall(self, text): + evt = UpdateTextEvent(value = text) + wx.PostEvent(self._wxComponent, evt) + + def ErrCall(self, text): + evt = UpdateErrorEvent(value = text) + wx.PostEvent(self._wxComponent, evt) + + def Execute(self, arguments, startIn=None, environment=None): + if not startIn: + startIn = str(os.getcwd()) + startIn = os.path.abspath(startIn) + command = self._cmd + ' ' + arguments + #stdinput = process.IOBuffer() + #self._process = process.ProcessProxy(command, mode='b', cwd=startIn, stdin=stdinput) + self._process = process.ProcessOpen(command, mode='b', cwd=startIn, env=environment) + # Kick off threads to read stdout and stderr and write them + # to our text control. + self._stdOutReader = OutputReaderThread(self._process.stdout, self._stdOutCallback, callbackOnExit=self._callbackOnExit) + self._stdOutReader.start() + self._stdErrReader = OutputReaderThread(self._process.stderr, self._stdErrCallback, accumulate=False) + self._stdErrReader.start() + + + def DoStopExecution(self): + if(self._process != None): + self._process.kill() + self._process.close() + self._process = None + if(self._stdOutReader != None): + self._stdOutReader.AskToStop() + if(self._stdErrReader != None): + self._stdErrReader.AskToStop() + +class RunCommandUI(wx.Panel): + + def __init__(self, parent, id, fileName): + wx.Panel.__init__(self, parent, id) + self._noteBook = parent + + self.KILL_PROCESS_ID = wx.NewId() + self.CLOSE_TAB_ID = wx.NewId() + + self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded) + + # GUI Initialization follows + sizer = wx.BoxSizer(wx.HORIZONTAL) + self._tb = tb = wx.ToolBar(self, -1, wx.DefaultPosition, (30,1000), wx.TB_VERTICAL| wx.TB_FLAT, "Runner" ) + tb.SetToolBitmapSize((16,16)) + sizer.Add(tb, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1) + + close_bmp = getCloseBitmap() + tb.AddSimpleTool( self.CLOSE_TAB_ID, close_bmp, _('Close Window')) + wx.EVT_TOOL(self, self.CLOSE_TAB_ID, self.OnToolClicked) + + stop_bmp = getStopBitmap() + tb.AddSimpleTool(self.KILL_PROCESS_ID, stop_bmp, _("Stop the Run.")) + wx.EVT_TOOL(self, self.KILL_PROCESS_ID, self.OnToolClicked) + + tb.Realize() + self._textCtrl = STCTextEditor.TextCtrl(self, wx.NewId()) #id) + sizer.Add(self._textCtrl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1) + self._textCtrl.SetViewLineNumbers(False) + self._textCtrl.SetReadOnly(True) + if wx.Platform == '__WXMSW__': + font = "Courier New" + else: + font = "Courier" + self._textCtrl.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, faceName = font)) + self._textCtrl.SetFontColor(wx.BLACK) + self._textCtrl.StyleClearAll() + + #Disabling for now...may interfere with file open. wx.stc.EVT_STC_DOUBLECLICK(self._textCtrl, self._textCtrl.GetId(), self.OnDoubleClick) + + self.SetSizer(sizer) + sizer.Fit(self) + + # Executor initialization + self._executor = Executor(fileName, self, callbackOnExit=self.ExecutorFinished) + self.Bind(EVT_UPDATE_STDTEXT, self.AppendText) + self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText) + + def __del__(self): + self._executor.DoStopExecution() + + def Execute(self, initialArgs, startIn, environment): + self._executor.Execute(initialArgs, startIn, environment) + + def ExecutorFinished(self): + self._tb.EnableTool(self.KILL_PROCESS_ID, False) + nb = self.GetParent() + for i in range(0,nb.GetPageCount()): + if self == nb.GetPage(i): + text = nb.GetPageText(i) + newText = text.replace("Running", "Finished") + nb.SetPageText(i, newText) + break + + def StopExecution(self): + self.Unbind(EVT_UPDATE_STDTEXT) + self.Unbind(EVT_UPDATE_ERRTEXT) + self._executor.DoStopExecution() + + def AppendText(self, event): + self._textCtrl.SetReadOnly(False) + self._textCtrl.AddText(event.value) + self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount()) + self._textCtrl.SetReadOnly(True) + + def AppendErrorText(self, event): + self._textCtrl.SetReadOnly(False) + self._textCtrl.SetFontColor(wx.RED) + self._textCtrl.StyleClearAll() + self._textCtrl.AddText(event.value) + self._textCtrl.ScrollToLine(self._textCtrl.GetLineCount()) + self._textCtrl.SetFontColor(wx.BLACK) + self._textCtrl.StyleClearAll() + self._textCtrl.SetReadOnly(True) + + #------------------------------------------------------------------------------ + # Event handling + #----------------------------------------------------------------------------- + + def OnToolClicked(self, event): + id = event.GetId() + + if id == self.KILL_PROCESS_ID: + self._executor.DoStopExecution() + + elif id == self.CLOSE_TAB_ID: + self._executor.DoStopExecution() + index = self._noteBook.GetSelection() + self._noteBook.GetPage(index).Show(False) + self._noteBook.RemovePage(index) + + def OnDoubleClick(self, event): + # Looking for a stack trace line. + lineText, pos = self._textCtrl.GetCurLine() + fileBegin = lineText.find("File \"") + fileEnd = lineText.find("\", line ") + lineEnd = lineText.find(", in ") + if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1: + # Check the line before the one that was clicked on + lineNumber = self._textCtrl.GetCurrentLine() + if(lineNumber == 0): + return + lineText = self._textCtrl.GetLine(lineNumber - 1) + fileBegin = lineText.find("File \"") + fileEnd = lineText.find("\", line ") + lineEnd = lineText.find(", in ") + if lineText == "\n" or fileBegin == -1 or fileEnd == -1 or lineEnd == -1: + return + + filename = lineText[fileBegin + 6:fileEnd] + lineNum = int(lineText[fileEnd + 8:lineEnd]) + + foundView = None + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + for openDoc in openDocs: + if openDoc.GetFilename() == filename: + foundView = openDoc.GetFirstView() + break + + if not foundView: + doc = wx.GetApp().GetDocumentManager().CreateDocument(filename, wx.lib.docview.DOC_SILENT) + foundView = doc.GetFirstView() + + if foundView: + foundView.GetFrame().SetFocus() + foundView.Activate() + foundView.GotoLine(lineNum) + startPos = foundView.PositionFromLine(lineNum) + + # FACTOR THIS INTO DocManager + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + for openDoc in openDocs: + if(isinstance(openDoc.GetFirstView(), CodeEditor.CodeView)): + openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers() + + foundView.GetCtrl().MarkerAdd(lineNum -1, CodeEditor.CodeCtrl.CURRENT_LINE_MARKER_NUM) + + def OnProcessEnded(self, evt): + self._executor.DoStopExecution() + +DEFAULT_PORT = 32032 +DEFAULT_HOST = 'localhost' +PORT_COUNT = 21 + +class DebugCommandUI(wx.Panel): + debuggerPortList = None + debuggers = [] + + def NotifyDebuggersOfBreakpointChange(): + for debugger in DebugCommandUI.debuggers: + debugger.BreakPointChange() + + NotifyDebuggersOfBreakpointChange = staticmethod(NotifyDebuggersOfBreakpointChange) + + def DebuggerRunning(): + for debugger in DebugCommandUI.debuggers: + if debugger._executor: + return True + return False + DebuggerRunning = staticmethod(DebuggerRunning) + + def ShutdownAllDebuggers(): + for debugger in DebugCommandUI.debuggers: + debugger.StopExecution() + + ShutdownAllDebuggers = staticmethod(ShutdownAllDebuggers) + + def GetAvailablePort(): + for index in range( 0, len(DebugCommandUI.debuggerPortList)): + port = DebugCommandUI.debuggerPortList[index] + if DebugCommandUI.PortAvailable(port): + DebugCommandUI.debuggerPortList.pop(index) + return port + wx.MessageBox(_("Out of ports for debugging! Please restart the application builder.\nIf that does not work, check for and remove running instances of python."), _("Out of Ports")) + assert False, "Out of ports for debugger." + + GetAvailablePort = staticmethod(GetAvailablePort) + + def ReturnPortToPool(port): + config = wx.ConfigBase_Get() + startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT) + if port in range(startingPort, startingPort + PORT_COUNT): + DebugCommandUI.debuggerPortList.append(port) + + ReturnPortToPool = staticmethod(ReturnPortToPool) + + def PortAvailable(port): + config = wx.ConfigBase_Get() + hostname = config.Read("DebuggerHostName", DEFAULT_HOST) + try: + server = AGXMLRPCServer((hostname, port)) + server.server_close() + if _VERBOSE: print "Port ", str(port), " available." + return True + except: + tp,val,tb = sys.exc_info() + if _VERBOSE: traceback.print_exception(tp, val, tb) + if _VERBOSE: print "Port ", str(port), " unavailable." + return False + + PortAvailable = staticmethod(PortAvailable) + + def NewPortRange(): + config = wx.ConfigBase_Get() + startingPort = config.ReadInt("DebuggerStartingPort", DEFAULT_PORT) + DebugCommandUI.debuggerPortList = range(startingPort, startingPort + PORT_COUNT) + NewPortRange = staticmethod(NewPortRange) + + def __init__(self, parent, id, command, service): + # Check for ports before creating the panel. + if not DebugCommandUI.debuggerPortList: + DebugCommandUI.NewPortRange() + self._debuggerPort = str(DebugCommandUI.GetAvailablePort()) + self._guiPort = str(DebugCommandUI.GetAvailablePort()) + self._debuggerBreakPort = str(DebugCommandUI.GetAvailablePort()) + + wx.Panel.__init__(self, parent, id) + + self._parentNoteBook = parent + self._command = command + self._textCtrl = None + self._service = service + self._executor = None + self.STEP_ID = wx.NewId() + self.CONTINUE_ID = wx.NewId() + self.STEP_OUT_ID = wx.NewId() + self.NEXT_ID = wx.NewId() + self.KILL_PROCESS_ID = wx.NewId() + self.CLOSE_WINDOW_ID = wx.NewId() + self.BREAK_INTO_DEBUGGER_ID = wx.NewId() + self.CLEAR_ID = wx.NewId() + self.ADD_WATCH_ID = wx.NewId() + sizer = wx.BoxSizer(wx.VERTICAL) + self._tb = tb = wx.ToolBar(self, -1, wx.DefaultPosition, (1000,30), wx.TB_HORIZONTAL| wx.NO_BORDER| wx.TB_FLAT| wx.TB_TEXT, "Debugger" ) + sizer.Add(tb, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1) + tb.SetToolBitmapSize((16,16)) + + close_bmp = getCloseBitmap() + tb.AddSimpleTool( self.CLOSE_WINDOW_ID, close_bmp, _('Close Window')) + wx.EVT_TOOL(self, self.CLOSE_WINDOW_ID, self.StopAndRemoveUI) + + stop_bmp = getStopBitmap() + tb.AddSimpleTool( self.KILL_PROCESS_ID, stop_bmp, _("Stop Debugging")) + wx.EVT_TOOL(self, self.KILL_PROCESS_ID, self.StopExecution) + + tb.AddSeparator() + + break_bmp = getBreakBitmap() + tb.AddSimpleTool( self.BREAK_INTO_DEBUGGER_ID, break_bmp, _("Break into Debugger")) + wx.EVT_TOOL(self, self.BREAK_INTO_DEBUGGER_ID, self.BreakExecution) + + tb.AddSeparator() + + continue_bmp = getContinueBitmap() + tb.AddSimpleTool( self.CONTINUE_ID, continue_bmp, _("Continue Execution")) + wx.EVT_TOOL(self, self.CONTINUE_ID, self.OnContinue) + + tb.AddSeparator() + + next_bmp = getNextBitmap() + tb.AddSimpleTool( self.NEXT_ID, next_bmp, _("Step to next line")) + wx.EVT_TOOL(self, self.NEXT_ID, self.OnNext) + + step_bmp = getStepInBitmap() + tb.AddSimpleTool( self.STEP_ID, step_bmp, _("Step in")) + wx.EVT_TOOL(self, self.STEP_ID, self.OnSingleStep) + + stepOut_bmp = getStepReturnBitmap() + tb.AddSimpleTool(self.STEP_OUT_ID, stepOut_bmp, _("Stop at function return")) + wx.EVT_TOOL(self, self.STEP_OUT_ID, self.OnStepOut) + + tb.AddSeparator() + if _WATCHES_ON: + watch_bmp = getAddWatchBitmap() + tb.AddSimpleTool(self.ADD_WATCH_ID, watch_bmp, _("Add a watch")) + wx.EVT_TOOL(self, self.ADD_WATCH_ID, self.OnAddWatch) + tb.AddSeparator() + + clear_bmp = getClearOutputBitmap() + tb.AddSimpleTool(self.CLEAR_ID, clear_bmp, _("Clear output pane")) + wx.EVT_TOOL(self, self.CLEAR_ID, self.OnClearOutput) + + tb.Realize() + self.framesTab = None + self.DisableWhileDebuggerRunning() + self._notebook = wx.Notebook(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.LB_DEFAULT, "Debugger") + sizer.Add(self._notebook, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 1) + self.consoleTab = self.MakeConsoleTab(self._notebook, wx.NewId(), None) + self.framesTab = self.MakeFramesTab(self._notebook, wx.NewId(), None) + self.breakPointsTab = self.MakeBreakPointsTab(self._notebook, wx.NewId(), None) + self._notebook.AddPage(self.consoleTab, "Output") + self._notebook.AddPage(self.framesTab, "Frames") + self._notebook.AddPage(self.breakPointsTab, "Break Points") + + self._statusBar = wx.StatusBar( self, -1) + self._statusBar.SetFieldsCount(1) + sizer.Add(self._statusBar, 0, wx.EXPAND |wx.ALIGN_LEFT|wx.ALL, 1) + + self.SetStatusText("Starting debug...") + + self.SetSizer(sizer) + sizer.Fit(self) + config = wx.ConfigBase_Get() + self._debuggerHost = self._guiHost = config.Read("DebuggerHostName", DEFAULT_HOST) + url = 'http://' + self._debuggerHost + ':' + self._debuggerPort + '/' + self._breakURL = 'http://' + self._debuggerHost + ':' + self._debuggerBreakPort + '/' + self._callback = DebuggerCallback(self._guiHost, self._guiPort, url, self._breakURL, self) + if DebuggerHarness.__file__.find('library.zip') > 0: + try: + fname = DebuggerHarness.__file__ + parts = fname.split('library.zip') + path = os.path.join(parts[0],'activegrid', 'tool', 'DebuggerHarness.py') + except: + tp, val, tb = sys.exc_info() + traceback.print_exception(tp, val, tb) + + else: + print "Starting debugger on these ports: %s, %s, %s" % (str(self._debuggerPort) , str(self._guiPort) , str(self._debuggerBreakPort)) + path = DebuggerService.ExpandPath(DebuggerHarness.__file__) + self._executor = Executor(path, self, self._debuggerHost, \ + self._debuggerPort, self._debuggerBreakPort, self._guiHost, self._guiPort, self._command, callbackOnExit=self.ExecutorFinished) + + self.Bind(EVT_UPDATE_STDTEXT, self.AppendText) + self.Bind(EVT_UPDATE_ERRTEXT, self.AppendErrorText) + DebugCommandUI.debuggers.append(self) + self._stopped = False + + def OnSingleStep(self, event): + self._callback.SingleStep() + + def OnContinue(self, event): + self._callback.Continue() + + def OnStepOut(self, event): + self._callback.Return() + + def OnNext(self, event): + self._callback.Next() + + def BreakPointChange(self): + if not self._stopped: + self._callback.pushBreakpoints() + self.breakPointsTab.PopulateBPList() + + def __del__(self): + if self in DebugCommandUI.debuggers: + DebugCommandUI.debuggers.remove(self) + + def SwitchToOutputTab(self): + self._notebook.SetSelection(0) + + def DisableWhileDebuggerRunning(self): + self._tb.EnableTool(self.STEP_ID, False) + self._tb.EnableTool(self.CONTINUE_ID, False) + self._tb.EnableTool(self.STEP_OUT_ID, False) + self._tb.EnableTool(self.NEXT_ID, False) + self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, True) + if _WATCHES_ON: + self._tb.EnableTool(self.ADD_WATCH_ID, False) + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + for openDoc in openDocs: + if(isinstance(openDoc.GetFirstView(), CodeEditor.CodeView)): + openDoc.GetFirstView().GetCtrl().ClearCurrentLineMarkers() + if self.framesTab: + self.framesTab.ClearWhileRunning() + #wx.GetApp().ProcessPendingEvents() #Yield(True) + + def EnableWhileDebuggerStopped(self): + self._tb.EnableTool(self.STEP_ID, True) + self._tb.EnableTool(self.CONTINUE_ID, True) + self._tb.EnableTool(self.STEP_OUT_ID, True) + self._tb.EnableTool(self.NEXT_ID, True) + if _WATCHES_ON: + self._tb.EnableTool(self.ADD_WATCH_ID, True) + self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False) + #if _WINDOWS: + # wx.GetApp().GetTopWindow().RequestUserAttention() + + def ExecutorFinished(self): + if _VERBOSE: print "In ExectorFinished" + try: + self.DisableAfterStop() + except wx._core.PyDeadObjectError: + pass + try: + nb = self.GetParent() + for i in range(0, nb.GetPageCount()): + if self == nb.GetPage(i): + text = nb.GetPageText(i) + newText = text.replace("Debugging", "Finished") + nb.SetPageText(i, newText) + if _VERBOSE: print "In ExectorFinished, changed tab title." + break + except: + if _VERBOSE: print "In ExectorFinished, got exception" + + def DisableAfterStop(self): + self.DisableWhileDebuggerRunning() + self._tb.EnableTool(self.BREAK_INTO_DEBUGGER_ID, False) + self._tb.EnableTool(self.KILL_PROCESS_ID, False) + + def SynchCurrentLine(self, filename, lineNum): + # FACTOR THIS INTO DocManager + self.DeleteCurrentLineMarkers() + + # Filename will be 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 == ' ': + 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 \x8a \xec5\x94\tc\xc4\x12\xab\x94\xeb\x7fkWr\xc9B%\ +\xfc\xd2\xfcM<\x01\xf6tn\x12O3c\xe6\x00\x00\x00\x00IEND\xaeB`\x82' + +def getContinueBitmap(): + return BitmapFromImage(getContinueImage()) + +def getContinueImage(): + stream = cStringIO.StringIO(getContinueData()) + return ImageFromStream(stream) + +def getContinueIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getContinueBitmap()) + return icon + +#---------------------------------------------------------------------- +def getNextData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\x8eIDAT8\x8d\xa5SA\x12\xc4 \x08K\xb0\xff\xde\xe9\xbf\xb7\xa6\x87\ +\x1d:\xba\xa2tZn(\x84`"i\x05obk\x13\xd5CmN+\xcc\x00l\xd6\x0c\x00\xf5\xf8\x0e\ +gK\x06\x00 \xa5=k\x00\x00\xb0\xb2]\xd4?5f\xb1\xdb\xaf\xc6\xa2\xcb\xa8\xf0?\ +\x1c\x98\xae\x82\xbf\x81\xa4\x8eA\x16\xe1\n\xd1\xa4\x19\xb3\xe9\n\xce\xe8\ +\xf1\n\x9eg^\x18\x18\x90\xec<\x11\xf9#\x04XMZ\x19\xaac@+\x94\xd4\x99)SeP\xa1\ +)\xd6\x1dI\xe7*\xdc\xf4\x03\xdf~\xe7\x13T^Q?:X\x19d\x00\x00\x00\x00IEND\xaeB\ +`\x82' + +def getNextBitmap(): + return BitmapFromImage(getNextImage()) + +def getNextImage(): + stream = cStringIO.StringIO(getNextData()) + return ImageFromStream(stream) + +def getNextIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getNextBitmap()) + return icon + +#---------------------------------------------------------------------- +def getStepInData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\x87IDAT8\x8d\xadSA\x12\x84 \x0ck\x8a\xffv\xfc\xb74{X\xeb0P@\x07s\ +\x84\xa4$\x01\x00M\xb2\x02]R\x8b\xc86\xda\xdc\xedd\xb4~\xe8\x86\xc6\x01-\x93\ +\x96\xd9#\xf6\x06\xc3;p1I\xd1\x14\x0b#|\x17aF\xec\r\xeeF\xa0eB\xd34\xca\xd0A\ +]j\x84\xa6\x03\x00""\xb7\xb0tRZ\xf7x\xb7\x83\x91]\xcb\x7fa\xd9\x89\x0fC\xfd\ +\x94\x9d|9\x99^k\x13\xa1 \xb3\x16\x0f#\xd4\x88N~\x14\xe1-\x96\x7f\xe3\x0f\ +\x11\x91UC\x0cX\'\x1e\x00\x00\x00\x00IEND\xaeB`\x82' + +def getStepInBitmap(): + return BitmapFromImage(getStepInImage()) + +def getStepInImage(): + stream = cStringIO.StringIO(getStepInData()) + return ImageFromStream(stream) + +def getStepInIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getStepInBitmap()) + return icon + +#---------------------------------------------------------------------- +def getStopData(): + return \ +"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00FIDAT8\x8d\xed\x91\xc1\t\xc00\x0c\x03O!\x0b\xa9\xfb\xef\xa6\xfcB\xa1\ +N\t\xf4Wr\xa0\x8f\xb1\x0f\x81\xe1\x97\xe4-\xb6}_V%\xc8\xc2, \t\x92\xe6]\xfbZ\ +\xf7\x08\xa0W\xc3\xea5\xdb\rl_IX\xe5\xf0d\x00\xfa\x8d#\x7f\xc4\xf7'\xab\x00\ +\x00\x00\x00IEND\xaeB`\x82" + +def getStopBitmap(): + return BitmapFromImage(getStopImage()) + +def getStopImage(): + stream = cStringIO.StringIO(getStopData()) + return ImageFromStream(stream) + +def getStopIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getStopBitmap()) + return icon + +#---------------------------------------------------------------------- +def getStepReturnData(): + return \ +"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\x8dIDAT8\x8d\xa5S\xd1\x0e\xc4 \x08\xa3\xb0\xff\xbe\xdc\x7fO\xba'6\ +\xf1\xf44\xb3O$Phk\x04\xd4d\x07\xba\xc5\x16\x91#\nza\xdb\x84\x1a\xa2\xfe\xf8\ +\x99\xfa_=p+\xe8\x91ED\xbc<\xa4 \xb4\x0b\x01\xb5{\x01\xf9\xbbG-\x13\x87\x16f\ +\x84\xbf\x16V\xb0l\x01@\no\x86\xae\x82Q\xa8=\xa4\x0c\x80\xe70\xbd\x10jh\xbd\ +\x07R\x06#\xc9^N\xb6\xde\x03)\x83\x18\xaeU\x90\x9c>a\xb2P\r\xb3&/Y\xa8\xd1^^\ +\xb6\xf0\x16\xdb\xbf\xf1\x02\x81\xa5TK\x1d\x07\xde\x92\x00\x00\x00\x00IEND\ +\xaeB`\x82" + +def getStepReturnBitmap(): + return BitmapFromImage(getStepReturnImage()) + +def getStepReturnImage(): + stream = cStringIO.StringIO(getStepReturnData()) + return ImageFromStream(stream) + +def getStepReturnIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getStepReturnBitmap()) + return icon + +def getAddWatchData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\x85IDAT8\x8dc`\x18h\xc0\x88.\xd0\xc0\xf0\xff?*\x9f\x11C\rN\x80\xae\ +\x19\x97\x18\xd1\x9a\x896\x84\x18[p\xa9aA\xe6\xfc7f\xc0P\xc4x\x163\x9cp\x1a0\ +\xeb,!w\x100 \x1dK\xac\x10\r\x08\x05".yFL\x85\x8c\x18b\xa8|Ty\xa2\x13\x92\'\ +\xc3\xe4\xff\x9f\x18\x1e3\xb82t\xa2\x88\x13\xedg.\x06aa&\x06VV\x7f\x86\xb9\ +\xcfU\x19\xbc\xb0\xba\x86h\xe0\xc8\xd0\xfc\xbf\x80\xe1>q)\x94\xe6\x00\x00\ +\x85\x923_\xd22\xa4\xcd\x00\x00\x00\x00IEND\xaeB`\x82' + +def getAddWatchBitmap(): + return BitmapFromImage(getAddWatchImage()) + +def getAddWatchImage(): + stream = cStringIO.StringIO(getAddWatchData()) + return ImageFromStream(stream) + +def getAddWatchIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getAddWatchBitmap()) + return icon diff --git a/wxPython/samples/ide/activegrid/tool/FindInDirService.py b/wxPython/samples/ide/activegrid/tool/FindInDirService.py new file mode 100644 index 0000000000..f3e9da16c2 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/FindInDirService.py @@ -0,0 +1,446 @@ +#---------------------------------------------------------------------------- +# Name: IDEFindService.py +# Purpose: Find Service for pydocview +# +# Author: Morgan Hua +# +# Created: 8/15/03 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import wx.lib.docview +import os +from os.path import join +import re +import ProjectEditor +import MessageService +import FindService +import OutlineService +_ = wx.GetTranslation + + +#---------------------------------------------------------------------------- +# Constants +#---------------------------------------------------------------------------- +FILENAME_MARKER = _("Found in file: ") +PROJECT_MARKER = _("Searching project: ") +FIND_MATCHDIR = "FindMatchDir" +FIND_MATCHDIRSUBFOLDERS = "FindMatchDirSubfolders" + +SPACE = 10 +HALF_SPACE = 5 + + +class FindInDirService(FindService.FindService): + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + FINDALL_ID = wx.NewId() # for bringing up Find All dialog box + FINDDIR_ID = wx.NewId() # for bringing up Find Dir dialog box + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + FindService.FindService.InstallControls(self, frame, menuBar, toolBar, statusBar, document) + + editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit"))) + wx.EVT_MENU(frame, FindInDirService.FINDALL_ID, self.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindInDirService.FINDALL_ID, self.ProcessUpdateUIEvent) + editMenu.Append(FindInDirService.FINDALL_ID, _("Find in Project...\tCtrl+Shift+F"), _("Searches for the specified text in all the files in the project")) + wx.EVT_MENU(frame, FindInDirService.FINDDIR_ID, self.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindInDirService.FINDDIR_ID, self.ProcessUpdateUIEvent) + editMenu.Append(FindInDirService.FINDDIR_ID, _("Find in Directory..."), _("Searches for the specified text in all the files in the directory")) + + + def ProcessEvent(self, event): + id = event.GetId() + if id == FindInDirService.FINDALL_ID: + self.ShowFindAllDialog() + return True + elif id == FindInDirService.FINDDIR_ID: + self.ShowFindDirDialog() + return True + else: + return FindService.FindService.ProcessEvent(self, event) + + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == FindInDirService.FINDALL_ID: + projectService = wx.GetApp().GetService(ProjectEditor.ProjectService) + view = projectService.GetView() + if view and view.GetDocument() and view.GetDocument().GetFiles(): + event.Enable(True) + else: + event.Enable(False) + return True + elif id == FindInDirService.FINDDIR_ID: + event.Enable(True) + else: + return FindService.FindService.ProcessUpdateUIEvent(self, event) + + + def ShowFindDirDialog(self): + config = wx.ConfigBase_Get() + + frame = wx.Dialog(None, -1, _("Find in Directory"), size= (320,200)) + borderSizer = wx.BoxSizer(wx.HORIZONTAL) + + contentSizer = wx.BoxSizer(wx.VERTICAL) + lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer.Add(wx.StaticText(frame, -1, _("Directory:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE) + dirCtrl = wx.TextCtrl(frame, -1, config.Read(FIND_MATCHDIR, ""), size=(200,-1)) + dirCtrl.SetToolTipString(dirCtrl.GetValue()) + lineSizer.Add(dirCtrl, 0, wx.LEFT, HALF_SPACE) + findDirButton = wx.Button(frame, -1, "Browse...") + lineSizer.Add(findDirButton, 0, wx.LEFT, HALF_SPACE) + contentSizer.Add(lineSizer, 0, wx.BOTTOM, SPACE) + + def OnBrowseButton(event): + dlg = wx.DirDialog(frame, _("Choose a directory:"), style=wx.DD_DEFAULT_STYLE) + dir = dirCtrl.GetValue() + if len(dir): + dlg.SetPath(dir) + if dlg.ShowModal() == wx.ID_OK: + dirCtrl.SetValue(dlg.GetPath()) + dirCtrl.SetToolTipString(dirCtrl.GetValue()) + dirCtrl.SetInsertionPointEnd() + + dlg.Destroy() + wx.EVT_BUTTON(findDirButton, -1, OnBrowseButton) + + subfolderCtrl = wx.CheckBox(frame, -1, _("Search in subfolders")) + subfolderCtrl.SetValue(config.ReadInt(FIND_MATCHDIRSUBFOLDERS, True)) + contentSizer.Add(subfolderCtrl, 0, wx.BOTTOM, SPACE) + + lineSizer = wx.BoxSizer(wx.VERTICAL) # let the line expand horizontally without vertical expansion + lineSizer.Add(wx.StaticLine(frame, -1, size = (10,-1)), 0, flag=wx.EXPAND) + contentSizer.Add(lineSizer, flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.BOTTOM, border=HALF_SPACE) + + lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE) + findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), size=(200,-1)) + lineSizer.Add(findCtrl, 0, wx.LEFT, HALF_SPACE) + contentSizer.Add(lineSizer, 0, wx.BOTTOM, SPACE) + wholeWordCtrl = wx.CheckBox(frame, -1, _("Match whole word only")) + wholeWordCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHWHOLEWORD, False)) + matchCaseCtrl = wx.CheckBox(frame, -1, _("Match case")) + matchCaseCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHCASE, False)) + regExprCtrl = wx.CheckBox(frame, -1, _("Regular expression")) + regExprCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHREGEXPR, False)) + contentSizer.Add(wholeWordCtrl, 0, wx.BOTTOM, SPACE) + contentSizer.Add(matchCaseCtrl, 0, wx.BOTTOM, SPACE) + contentSizer.Add(regExprCtrl, 0, wx.BOTTOM, SPACE) + borderSizer.Add(contentSizer, 0, wx.TOP | wx.BOTTOM | wx.LEFT, SPACE) + + buttonSizer = wx.BoxSizer(wx.VERTICAL) + findBtn = wx.Button(frame, wx.ID_OK, _("Find")) + findBtn.SetDefault() + buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0) + borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE) + + frame.SetSizer(borderSizer) + frame.Fit() + + status = frame.ShowModal() + + passedCheck = False + while status == wx.ID_OK and not passedCheck: + if not os.path.exists(dirCtrl.GetValue()): + dlg = wx.MessageDialog(frame, + _("'%s' does not exist.") % dirCtrl.GetValue(), + _("Find in Directory"), + wx.OK | wx.ICON_EXCLAMATION + ) + dlg.ShowModal() + dlg.Destroy() + + status = frame.ShowModal() + elif len(findCtrl.GetValue()) == 0: + dlg = wx.MessageDialog(frame, + _("'Find what:' cannot be empty."), + _("Find in Directory"), + wx.OK | wx.ICON_EXCLAMATION + ) + dlg.ShowModal() + dlg.Destroy() + + status = frame.ShowModal() + else: + passedCheck = True + + + # save user choice state for this and other Find Dialog Boxes + dirString = dirCtrl.GetValue() + searchSubfolders = subfolderCtrl.IsChecked() + self.SaveFindDirConfig(dirString, searchSubfolders) + + findString = findCtrl.GetValue() + matchCase = matchCaseCtrl.IsChecked() + wholeWord = wholeWordCtrl.IsChecked() + regExpr = regExprCtrl.IsChecked() + self.SaveFindConfig(findString, wholeWord, matchCase, regExpr) + + + if status == wx.ID_OK: + frame.Destroy() + + messageService = wx.GetApp().GetService(MessageService.MessageService) + messageService.ShowWindow() + + view = messageService.GetView() + if view: + wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT)) + view.ClearLines() + view.SetCallback(self.OnJumpToFoundLine) + + view.AddLines(_("Searching for '%s' in '%s'\n\n") % (findString, dirString)) + + if os.path.isfile(dirString): + try: + docFile = file(dirString, 'r') + lineNum = 1 + needToDisplayFilename = True + line = docFile.readline() + while line: + count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr) + if count != -1: + if needToDisplayFilename: + view.AddLines(FILENAME_MARKER + dirString + "\n") + needToDisplayFilename = False + line = repr(lineNum).zfill(4) + ":" + line + view.AddLines(line) + line = docFile.readline() + lineNum += 1 + if not needToDisplayFilename: + view.AddLines("\n") + except IOError, (code, message): + print _("Warning, unable to read file: '%s'. %s") % (dirString, message) + else: + # do search in files on disk + for root, dirs, files in os.walk(dirString): + if not searchSubfolders and root != dirString: + break + + for name in files: + filename = os.path.join(root, name) + try: + docFile = file(filename, 'r') + except IOError, (code, message): + print _("Warning, unable to read file: '%s'. %s") % (filename, message) + continue + + lineNum = 1 + needToDisplayFilename = True + line = docFile.readline() + while line: + count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr) + if count != -1: + if needToDisplayFilename: + view.AddLines(FILENAME_MARKER + filename + "\n") + needToDisplayFilename = False + line = repr(lineNum).zfill(4) + ":" + line + view.AddLines(line) + line = docFile.readline() + lineNum += 1 + if not needToDisplayFilename: + view.AddLines("\n") + + view.AddLines(_("Search completed.")) + wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + return True + else: + frame.Destroy() + return False + + + def SaveFindDirConfig(self, dirString, searchSubfolders): + """ Save search dir patterns and flags to registry. + + dirString = search directory + searchSubfolders = Search subfolders + """ + config = wx.ConfigBase_Get() + config.Write(FIND_MATCHDIR, dirString) + config.WriteInt(FIND_MATCHDIRSUBFOLDERS, searchSubfolders) + + + def ShowFindAllDialog(self): + config = wx.ConfigBase_Get() + + frame = wx.Dialog(None, -1, _("Find in Project"), size= (320,200)) + borderSizer = wx.BoxSizer(wx.HORIZONTAL) + + contentSizer = wx.BoxSizer(wx.VERTICAL) + lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer.Add(wx.StaticText(frame, -1, _("Find what:")), 0, wx.ALIGN_CENTER | wx.RIGHT, HALF_SPACE) + findCtrl = wx.TextCtrl(frame, -1, config.Read(FindService.FIND_MATCHPATTERN, ""), size=(200,-1)) + lineSizer.Add(findCtrl, 0, wx.LEFT, HALF_SPACE) + contentSizer.Add(lineSizer, 0, wx.BOTTOM, SPACE) + wholeWordCtrl = wx.CheckBox(frame, -1, _("Match whole word only")) + wholeWordCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHWHOLEWORD, False)) + matchCaseCtrl = wx.CheckBox(frame, -1, _("Match case")) + matchCaseCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHCASE, False)) + regExprCtrl = wx.CheckBox(frame, -1, _("Regular expression")) + regExprCtrl.SetValue(config.ReadInt(FindService.FIND_MATCHREGEXPR, False)) + contentSizer.Add(wholeWordCtrl, 0, wx.BOTTOM, SPACE) + contentSizer.Add(matchCaseCtrl, 0, wx.BOTTOM, SPACE) + contentSizer.Add(regExprCtrl, 0, wx.BOTTOM, SPACE) + borderSizer.Add(contentSizer, 0, wx.TOP | wx.BOTTOM | wx.LEFT, SPACE) + + buttonSizer = wx.BoxSizer(wx.VERTICAL) + findBtn = wx.Button(frame, wx.ID_OK, _("Find")) + findBtn.SetDefault() + buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(wx.Button(frame, wx.ID_CANCEL, _("Cancel")), 0) + borderSizer.Add(buttonSizer, 0, wx.ALL, SPACE) + + frame.SetSizer(borderSizer) + frame.Fit() + + status = frame.ShowModal() + + # save user choice state for this and other Find Dialog Boxes + findString = findCtrl.GetValue() + matchCase = matchCaseCtrl.IsChecked() + wholeWord = wholeWordCtrl.IsChecked() + regExpr = regExprCtrl.IsChecked() + self.SaveFindConfig(findString, wholeWord, matchCase, regExpr) + + if status == wx.ID_OK: + frame.Destroy() + + messageService = wx.GetApp().GetService(MessageService.MessageService) + messageService.ShowWindow() + + view = messageService.GetView() + if view: + view.ClearLines() + view.SetCallback(self.OnJumpToFoundLine) + + projectService = wx.GetApp().GetService(ProjectEditor.ProjectService) + projectFilenames = projectService.GetFilesFromCurrentProject() + + projView = projectService.GetView() + if projView: + projName = wx.lib.docview.FileNameFromPath(projView.GetDocument().GetFilename()) + view.AddLines(PROJECT_MARKER + projName + "\n\n") + + # do search in open files first, open files may have been modified and different from disk because it hasn't been saved + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + openDocsInProject = filter(lambda openDoc: openDoc.GetFilename() in projectFilenames, openDocs) + for openDoc in openDocsInProject: + if isinstance(openDoc, ProjectEditor.ProjectDocument): # don't search project model + continue + + openDocView = openDoc.GetFirstView() + # some views don't have a in memory text object to search through such as the PM and the DM + # even if they do have a non-text searchable object, how do we display it in the message window? + if not hasattr(openDocView, "GetValue"): + continue + text = openDocView.GetValue() + + lineNum = 1 + needToDisplayFilename = True + start = 0 + end = 0 + count = 0 + while count != -1: + count, foundStart, foundEnd, newText = self.DoFind(findString, None, text, start, end, True, matchCase, wholeWord, regExpr) + if count != -1: + if needToDisplayFilename: + view.AddLines(FILENAME_MARKER + openDoc.GetFilename() + "\n") + needToDisplayFilename = False + + lineNum = openDocView.LineFromPosition(foundStart) + line = repr(lineNum).zfill(4) + ":" + openDocView.GetLine(lineNum) + view.AddLines(line) + + start = text.find("\n", foundStart) + if start == -1: + break + end = start + if not needToDisplayFilename: + view.AddLines("\n") + openDocNames = map(lambda openDoc: openDoc.GetFilename(), openDocs) + + # do search in closed files, skipping the open ones we already searched + filenames = filter(lambda filename: filename not in openDocNames, projectFilenames) + for filename in filenames: + try: + docFile = file(filename, 'r') + except IOError, (code, message): + print _("Warning, unable to read file: '%s'. %s") % (filename, message) + continue + + lineNum = 1 + needToDisplayFilename = True + line = docFile.readline() + while line: + count, foundStart, foundEnd, newText = self.DoFind(findString, None, line, 0, 0, True, matchCase, wholeWord, regExpr) + if count != -1: + if needToDisplayFilename: + view.AddLines(FILENAME_MARKER + filename + "\n") + needToDisplayFilename = False + line = repr(lineNum).zfill(4) + ":" + line + view.AddLines(line) + line = docFile.readline() + lineNum += 1 + if not needToDisplayFilename: + view.AddLines("\n") + + view.AddLines(_("Search for '%s' completed.") % findString) + + return True + else: + frame.Destroy() + return False + + + def OnJumpToFoundLine(self, event): + messageService = wx.GetApp().GetService(MessageService.MessageService) + lineText, pos = messageService.GetView().GetCurrLine() + if lineText == "\n" or lineText.find(FILENAME_MARKER) != -1 or lineText.find(PROJECT_MARKER) != -1: + return + lineEnd = lineText.find(":") + if lineEnd == -1: + return + else: + lineNum = int(lineText[0:lineEnd]) + + text = messageService.GetView().GetText() + curPos = messageService.GetView().GetCurrentPos() + + startPos = text.rfind(FILENAME_MARKER, 0, curPos) + endPos = text.find("\n", startPos) + filename = text[startPos + len(FILENAME_MARKER):endPos] + + foundView = None + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + for openDoc in openDocs: + if openDoc.GetFilename() == filename: + foundView = openDoc.GetFirstView() + break + + if not foundView: + doc = wx.GetApp().GetDocumentManager().CreateDocument(filename, wx.lib.docview.DOC_SILENT) + foundView = doc.GetFirstView() + + if foundView: + foundView.GetFrame().SetFocus() + foundView.Activate() + if hasattr(foundView, "GotoLine"): + foundView.GotoLine(lineNum) + startPos = foundView.PositionFromLine(lineNum) + # wxBug: Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct + # Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first + # time, we don't see the selection, it is scrolled off screen + foundView.SetSelection(startPos - 1 + len(lineText[lineEnd:].rstrip("\n")), startPos) + wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos) + + diff --git a/wxPython/samples/docview/activegrid/tool/FindService.py b/wxPython/samples/ide/activegrid/tool/FindService.py similarity index 99% rename from wxPython/samples/docview/activegrid/tool/FindService.py rename to wxPython/samples/ide/activegrid/tool/FindService.py index 4606a675a5..da5235b5d9 100644 --- a/wxPython/samples/docview/activegrid/tool/FindService.py +++ b/wxPython/samples/ide/activegrid/tool/FindService.py @@ -6,8 +6,8 @@ # # Created: 8/15/03 # CVS-ID: $Id$ -# Copyright: (c) 2003-2004 ActiveGrid, Inc. -# License: wxWindows license +# Copyright: (c) 2003-2005 ActiveGrid, Inc. +# License: wxWindows License #---------------------------------------------------------------------------- import wx diff --git a/wxPython/samples/ide/activegrid/tool/HtmlEditor.py b/wxPython/samples/ide/activegrid/tool/HtmlEditor.py new file mode 100644 index 0000000000..e026fb978b --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/HtmlEditor.py @@ -0,0 +1,222 @@ +#---------------------------------------------------------------------------- +# Name: HtmlEditor.py +# Purpose: Abstract Code Editor for pydocview tbat uses the Styled Text Control +# +# Author: Peter Yared +# +# Created: 8/15/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + + +import wx +import os.path +import string +import STCTextEditor +import CodeEditor +_ = wx.GetTranslation + + +class HtmlDocument(CodeEditor.CodeDocument): + + pass + + +class HtmlView(CodeEditor.CodeView): + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return HtmlCtrl + + + def GetAutoCompleteHint(self): + pos = self.GetCtrl().GetCurrentPos() + if pos == 0: + return None, None + + validLetters = string.letters + string.digits + '_!-' + word = '' + while (True): + pos = pos - 1 + if pos < 0: + break + char = chr(self.GetCtrl().GetCharAt(pos)) + if char not in validLetters: + break + word = char + word + + return None, word + + + def GetAutoCompleteDefaultKeywords(self): + return HTMLKEYWORDS + + +## def _CreateControl(self, parent, id): +## import wx # wxBug: When inlining the import of the appropriate html control below, have to specifically import wx for some reason +## self._notebook = wx.Notebook(parent, wx.NewId(), style = wx.NB_BOTTOM) +## self._textEditor = HtmlCtrl(self._notebook, id) +## if wx.Platform =='__WXMSW__': +## import wxPython.iewin +## self._browserCtrl = wxPython.iewin.wxIEHtmlWin(self._notebook, -1, style = wx.NO_FULL_REPAINT_ON_RESIZE) +## else: +## import wx.html +## self._browserCtrl = wx.html.HtmlWindow(self._notebook, -1, style = wx.NO_FULL_REPAINT_ON_RESIZE) +## self._notebook.AddPage(self._textEditor, _("Edit")) +## self._notebook.AddPage(self._browserCtrl, _("View")) +## self._insertMode = True +## wx.EVT_NOTEBOOK_PAGE_CHANGED(self._notebook, self._notebook.GetId(), self.OnNotebookChanging) +## return self._textEditor +## +## +## def _CreateSizer(self, frame): +## sizer = wx.BoxSizer(wx.HORIZONTAL) +## sizer.Add(self._notebook, 1, wx.EXPAND) +## frame.SetSizer(sizer) +## frame.SetAutoLayout(True) +## +## +## def OnNotebookChanging(self, event): +## if event.GetSelection() == 0: # Going to the edit page +## pass # self._textEditor.Refresh() +## elif event.GetSelection() == 1: # Going to the browser page +## text = self._textEditor.GetText() +## if wx.Platform == '__WXMSW__': +## path = os.path.join(tempfile.gettempdir(), "temp.html") +## file = open(path, 'w') +## file.write(text) +## file.close() +## self._browserCtrl.Navigate("file://" + path) +## else: +## self._browserCtrl.SetPage(text) +## event.Skip() + + +class HtmlService(CodeEditor.CodeService): + + + def __init__(self): + CodeEditor.CodeService.__init__(self) + + +class HtmlCtrl(CodeEditor.CodeCtrl): + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + CodeEditor.CodeCtrl.__init__(self, parent, ID, style) + self.SetLexer(wx.stc.STC_LEX_HTML) + self.SetProperty("fold.html", "1") + + def GetMatchingBraces(self): + return "<>[]{}()" + + def CanWordWrap(self): + return True + + + def SetViewDefaults(self): + CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Html", hasWordWrap = False, hasTabs = True) + + + def GetFontAndColorFromConfig(self): + return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Html") + + + def UpdateStyles(self): + CodeEditor.CodeCtrl.UpdateStyles(self) + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + # White space + self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + # Comment + self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + # Number + self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + # String + self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + # Tag + self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + # Attributes + self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + + +class HtmlOptionsPanel(STCTextEditor.TextOptionsPanel): + + def __init__(self, parent, id): + STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Html", label = "HTML", hasWordWrap = True, hasTabs = True) + + +HTMLKEYWORDS = [ + "A", "ABBR", "ACRONYM", "ADDRESS", "APPLET", "AREA", "B", "BASE", "BASEFONT", "BDO", "BIG", "BLOCKQUOTE", + "BODY", "BR", "BUTTON", "CAPTION", "CENTER", "CITE", "CODE", "COL", "COLGROUP", "DD", "DEL", "DFN", "DIR", + "DIV", "DL", "DT", "EM", "FIELDSET", "FONT", "FORM", "FRAME", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", + "HEAD", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT", "INS", "ISINDEX", "KBD", "LABEL", "LEGEND", "LI", "LINK", + "MAP", "MENU", "META", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", "OPTION", "P", "PARAM", + "PRE", "Q", "S", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRIKE", "STRONG", "STYLE", "SUB", "SUP", + "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR", "TT", "U", "UL", "VAR", "XML", + "XMLNS", "ACCEPT-CHARSET", "ACCEPT", "ACCESSKEY", "ACTION", "ALIGN", "ALINK", "ALT", + "ARCHIVE", "AXIS", "BACKGROUND", "BGCOLOR", "BORDER", "CELLPADDING", "CELLSPACING", "CHAR", + "CHAROFF", "CHARSET", "CHECKED", "CLASS", "CLASSID", "CLEAR", "CODEBASE", "CODETYPE", + "COLOR", "COLS", "COLSPAN", "COMPACT", "CONTENT", "COORDS", "DATA", "DATAFLD", "DATAFORMATAS", + "DATAPAGESIZE", "DATASRC", "DATETIME", "DECLARE", "DEFER", "DISABLED", "ENCTYPE", + "EVENT", "FACE", "FOR", "FRAMEBORDER", "HEADERS", "HEIGHT", "HREF", "HREFLANG", "HSPACE", + "HTTP-EQUIV", "ID", "ISMAP", "LANG", "LANGUAGE", "LEFTMARGIN", "LONGDESC", + "MARGINWIDTH", "MARGINHEIGHT", "MAXLENGTH", "MEDIA", "METHOD", "MULTIPLE", "NAME", "NOHREF", + "NORESIZE", "NOSHADE", "NOWRAP", "ONBLUR", "ONCHANGE", "ONCLICK", "ONDBLCLICK", + "ONFOCUS", "ONKEYDOWN", "ONKEYPRESS", "ONKEYUP", "ONLOAD", "ONMOUSEDOWN", "ONMOUSEMOVE", + "ONMOUSEOVER", "ONMOUSEOUT", "ONMOUSEUP", "ONRESET", "ONSELECT", "ONSUBMIT", "ONUNLOAD", + "PROFILE", "PROMPT", "READONLY", "REL", "REV", "ROWS", "ROWSPAN", "RULES", "SCHEME", "SCOPE", + "SELECTED", "SHAPE", "SIZE", "SRC", "STANDBY", "START", "SUMMARY", "TABINDEX", + "TARGET", "TOPMARGIN", "TYPE", "USEMAP", "VALIGN", "VALUE", "VALUETYPE", + "VERSION", "VLINK", "VSPACE", "WIDTH", "TEXT", "PASSWORD", "CHECKBOX", "RADIO", "SUBMIT", "RESET", + "FILE", "HIDDEN", "IMAGE", "PUBLIC", "!DOCTYPE", + "ADD_DATE", "LAST_MODIFIED", "LAST_VISIT" + ] + + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getHTMLData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\xcdIDAT8\x8dcd`f\xf8\xcf@\x01`\xfc\x7f\xa3\x87"\x03X\xfe}\xbeI\x89~\ +\x06&\x8at\x0f\n\x03\x18\xe4\x954\xff\xc3\x00\x8c-\xaf\xa4\xf9_\xc7\xc0\xfc\ +\xbf\x93\xab\xf7\xff\xff\xff\xff\xff70\xb6\xfe\x7f\xed\xce\x93\xff\xd7\xee<\ +\xf9\xafc`\x0eW\xf3\xf5\xd7\xff\xff,\x0f\x1f^gPP\xd6B1\xf4\xc1\xddk\x0c\xf6\ +\xb6\x16\x0c{wma````x\xf7\xfc\x06\xc3\xea\xa5\xb3\x198\xd8X\x18\xbe~|\x06W\ +\xc7\xc5\xca\xc0\xc0\xc2\xc0\xc0\xc0P\\\x9c\xcf\xf0\xf4\xc5\x1b\x86\x15K\x97\ +\xc2%Y\xd9y\xe0lF\x0e1\x86C\x87\x8e0\x88\x88\x8a3\xfccD\x88\xe3\xf4\x026\xf6\ +\xa9c{\xfe_ \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\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 \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_ \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 }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 \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 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 \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_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\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\x1d[\xa5\x90u\xc1\x16d\x99\x8dr\ +\x04]\xe0\xea\x1b\xefb\xf3\xf6Y\x18^\x01~\x03\xa5M\xbb\xb8\xe2\t\x82 \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 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\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 >\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 \ +\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 \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 \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 \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 \ +\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 \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 \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
\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:\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 \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 \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} \ +\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 \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\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 \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 \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 \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 \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 0) +## return True + + + #---------------------------------------------------------------------------- + # Service specific methods + #---------------------------------------------------------------------------- + + def ClearLines(self): + self.GetControl().SetReadOnly(False) + self.GetControl().ClearAll() + self.GetControl().SetReadOnly(True) + + + def AddLines(self, text): + self.GetControl().SetReadOnly(False) + self.GetControl().AddText(text) + self.GetControl().SetReadOnly(True) + + + def GetText(self): + return self.GetControl().GetText() + + + def GetCurrentPos(self): + return self.GetControl().GetCurrentPos() + + + def GetCurrLine(self): + return self.GetControl().GetCurLine() + + + #---------------------------------------------------------------------------- + # Callback Methods + #---------------------------------------------------------------------------- + + def SetCallback(self, callback): + """ Sets in the event table for a doubleclick to invoke the given callback. + Additional calls to this method overwrites the previous entry and only the last set callback will be invoked. + """ + wx.stc.EVT_STC_DOUBLECLICK(self.GetControl(), self.GetControl().GetId(), callback) + + + +class MessageService(Service.Service): + + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def _CreateView(self): + return MessageView(self) + + diff --git a/wxPython/samples/ide/activegrid/tool/OutlineService.py b/wxPython/samples/ide/activegrid/tool/OutlineService.py new file mode 100644 index 0000000000..95ccd9e357 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/OutlineService.py @@ -0,0 +1,520 @@ +#---------------------------------------------------------------------------- +# Name: OutlineService.py +# Purpose: Outline View Service for pydocview +# +# Author: Morgan Hua +# +# Created: 8/3/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import wx.lib.docview +import wx.lib.pydocview +import Service +_ = wx.GetTranslation + + +#---------------------------------------------------------------------------- +# Constants +#---------------------------------------------------------------------------- +SORT_NONE = 0 +SORT_ASC = 1 +SORT_DESC = 2 + +class OutlineView(Service.ServiceView): + """ Reusable Outline View for any document. + As a default, it uses a modified tree control (OutlineTreeCtrl) that allows sorting. + Subclass OutlineTreeCtrl to customize the tree control and call SetTreeCtrl to install a customized tree control. + When an item is selected, the document view is called back (with DoSelectCallback) to highlight and display the corresponding item in the document view. + """ + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self, service): + Service.ServiceView.__init__(self, service) + self._actionOnSelect = True + + + def _CreateControl(self, parent, id): + treeCtrl = OutlineTreeCtrl(parent, id) + wx.EVT_TREE_SEL_CHANGED(treeCtrl, treeCtrl.GetId(), self.DoSelection) + wx.EVT_SET_FOCUS(treeCtrl, self.DoSelection) + wx.EVT_ENTER_WINDOW(treeCtrl, treeCtrl.CallDoLoadOutlineCallback) + wx.EVT_RIGHT_DOWN(treeCtrl, self.OnRightClick) + + return treeCtrl + + + #---------------------------------------------------------------------------- + # Service specific methods + #---------------------------------------------------------------------------- + + def OnRightClick(self, event): + menu = wx.Menu() + + menu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order")) + menu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order")) + menu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order")) + + config = wx.ConfigBase_Get() + sort = config.ReadInt("OutlineSort", SORT_NONE) + if sort == SORT_NONE: + menu.Check(OutlineService.SORT_NONE, True) + elif sort == SORT_ASC: + menu.Check(OutlineService.SORT_ASC, True) + elif sort == SORT_DESC: + menu.Check(OutlineService.SORT_DESC, True) + + self.GetControl().PopupMenu(menu, event.GetPosition()) + menu.Destroy() + + + #---------------------------------------------------------------------------- + # Tree Methods + #---------------------------------------------------------------------------- + + def DoSelection(self, event): + if not self._actionOnSelect: + return + item = self.GetControl().GetSelection() + if item: + self.GetControl().CallDoSelectCallback(item) + + + def ResumeActionOnSelect(self): + self._actionOnSelect = True + + + def StopActionOnSelect(self): + self._actionOnSelect = False + + + def SetTreeCtrl(self, tree): + self.SetControl(tree) + wx.EVT_TREE_SEL_CHANGED(self.GetControl(), self.GetControl().GetId(), self.DoSelection) + wx.EVT_ENTER_WINDOW(self.GetControl(), treeCtrl.CallDoLoadOutlineCallback) + wx.EVT_RIGHT_DOWN(self.GetControl(), self.OnRightClick) + + + def GetTreeCtrl(self): + return self.GetControl() + + + def OnSort(self, sortOrder): + treeCtrl = self.GetControl() + treeCtrl.SetSortOrder(sortOrder) + treeCtrl.SortAllChildren(treeCtrl.GetRootItem()) + + + def ClearTreeCtrl(self): + if self.GetControl(): + self.GetControl().DeleteAllItems() + + + def GetExpansionState(self): + expanded = [] + + treeCtrl = self.GetControl() + if not treeCtrl: + return expanded + + parentItem = treeCtrl.GetRootItem() + + if not parentItem: + return expanded + + if not treeCtrl.IsExpanded(parentItem): + return expanded + + expanded.append(treeCtrl.GetItemText(parentItem)) + + (child, cookie) = treeCtrl.GetFirstChild(parentItem) + while child.IsOk(): + if treeCtrl.IsExpanded(child): + expanded.append(treeCtrl.GetItemText(child)) + (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie) + return expanded + + + def SetExpansionState(self, expanded): + if not expanded or len(expanded) == 0: + return + + treeCtrl = self.GetControl() + parentItem = treeCtrl.GetRootItem() + if expanded[0] != treeCtrl.GetItemText(parentItem): + return + + (child, cookie) = treeCtrl.GetFirstChild(parentItem) + while child.IsOk(): + if treeCtrl.GetItemText(child) in expanded: + treeCtrl.Expand(child) + (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie) + + # wxBug: This causes a crash, tried using ScrollTo which crashed as well. Then tried calling it with wx.CallAfter and that crashed as well, with both EnsureVisible and ScrollTo + # self.GetControl().EnsureVisible(self.GetControl().GetRootItem()) + # So doing the following massive hack which forces the treectrl to scroll up to the top item + treeCtrl.Collapse(parentItem) + treeCtrl.Expand(parentItem) + + +class OutlineTreeCtrl(wx.TreeCtrl): + """ Default Tree Control Class for OutlineView. + This class has the added functionality of sorting by the labels + """ + + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + ORIG_ORDER = 0 + VIEW = 1 + CALLBACKDATA = 2 + + + #---------------------------------------------------------------------------- + # Overridden Methods + #---------------------------------------------------------------------------- + + def __init__(self, parent, id, style=wx.TR_HAS_BUTTONS|wx.TR_DEFAULT_STYLE): + wx.TreeCtrl.__init__(self, parent, id, style = style) + self._origOrderIndex = 0 + self._sortOrder = SORT_NONE + + + def DeleteAllItems(self): + self._origOrderIndex = 0 + wx.TreeCtrl.DeleteAllItems(self) + + + #---------------------------------------------------------------------------- + # Sort Methods + #---------------------------------------------------------------------------- + + def SetSortOrder(self, sortOrder = SORT_NONE): + """ Sort Order constants are defined at top of file """ + self._sortOrder = sortOrder + + + def OnCompareItems(self, item1, item2): + if self._sortOrder == SORT_ASC: + return cmp(self.GetItemText(item1).lower(), self.GetItemText(item2).lower()) # sort A-Z + elif self._sortOrder == SORT_DESC: + return cmp(self.GetItemText(item2).lower(), self.GetItemText(item1).lower()) # sort Z-A + else: + return (self.GetPyData(item1)[self.ORIG_ORDER] > self.GetPyData(item2)[self.ORIG_ORDER]) # unsorted + + + def SortAllChildren(self, parentItem): + if parentItem and self.GetChildrenCount(parentItem, False): + self.SortChildren(parentItem) + (child, cookie) = self.GetFirstChild(parentItem) + while child.IsOk(): + self.SortAllChildren(child) + (child, cookie) = self.GetNextChild(parentItem, cookie) + + + #---------------------------------------------------------------------------- + # Select Callback Methods + #---------------------------------------------------------------------------- + + def CallDoSelectCallback(self, item): + """ Invoke the DoSelectCallback of the given view to highlight text in the document view + """ + data = self.GetPyData(item) + if not data: + return + + view = data[self.VIEW] + cbdata = data[self.CALLBACKDATA] + if view: + view.DoSelectCallback(cbdata) + + + def SelectClosestItem(self, position): + tree = self + distances = [] + items = [] + self.FindDistanceToTreeItems(tree.GetRootItem(), position, distances, items) + mindist = 1000000 + mindex = -1 + for index in range(0, len(distances)): + if distances[index] <= mindist: + mindist = distances[index] + mindex = index + if mindex != -1: + item = items[mindex] + self.EnsureVisible(item) + os_view = wx.GetApp().GetService(OutlineService).GetView() + if os_view: + os_view.StopActionOnSelect() + self.SelectItem(item) + if os_view: + os_view.ResumeActionOnSelect() + + + def FindDistanceToTreeItems(self, item, position, distances, items): + data = self.GetPyData(item) + this_dist = 1000000 + if data and data[2]: + positionTuple = data[2] + if position >= positionTuple[1]: + items.append(item) + distances.append(position - positionTuple[1]) + + if self.ItemHasChildren(item): + child, cookie = self.GetFirstChild(item) + while child and child.IsOk(): + self.FindDistanceToTreeItems(child, position, distances, items) + child, cookie = self.GetNextChild(item, cookie) + return False + + + def SetDoSelectCallback(self, item, view, callbackdata): + """ When an item in the outline view is selected, + a method is called to select the respective text in the document view. + The view must define the method DoSelectCallback(self, data) in order for this to work + """ + self.SetPyData(item, (self._origOrderIndex, view, callbackdata)) + self._origOrderIndex = self._origOrderIndex + 1 + + + def CallDoLoadOutlineCallback(self, event): + """ Invoke the DoLoadOutlineCallback + """ + rootItem = self.GetRootItem() + if rootItem: + data = self.GetPyData(rootItem) + if data: + view = data[self.VIEW] + if view and view.DoLoadOutlineCallback(): + self.SortAllChildren(self.GetRootItem()) + + + def GetCallbackView(self): + rootItem = self.GetRootItem() + if rootItem: + return self.GetPyData(rootItem)[self.VIEW] + else: + return None + + +class OutlineService(Service.Service): + + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service + SORT = wx.NewId() + SORT_ASC = wx.NewId() + SORT_DESC = wx.NewId() + SORT_NONE = wx.NewId() + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM): + Service.Service.__init__(self, serviceName, embeddedWindowLocation) + self._validTemplates = [] + + + def _CreateView(self): + return OutlineView(self) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document) + + wx.EVT_MENU(frame, OutlineService.SORT_ASC, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, OutlineService.SORT_ASC, frame.ProcessUpdateUIEvent) + wx.EVT_MENU(frame, OutlineService.SORT_DESC, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, OutlineService.SORT_DESC, frame.ProcessUpdateUIEvent) + wx.EVT_MENU(frame, OutlineService.SORT_NONE, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, OutlineService.SORT_NONE, frame.ProcessUpdateUIEvent) + + + if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + return True + + viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) + self._outlineSortMenu = wx.Menu() + self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order")) + self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order")) + self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order")) + viewMenu.AppendMenu(wx.NewId(), _("Outline Sort"), self._outlineSortMenu) + + return True + + + #---------------------------------------------------------------------------- + # Event Processing Methods + #---------------------------------------------------------------------------- + + def ProcessEvent(self, event): + if Service.Service.ProcessEvent(self, event): + return True + + id = event.GetId() + if id == OutlineService.SORT_ASC: + self.OnSort(event) + return True + elif id == OutlineService.SORT_DESC: + self.OnSort(event) + return True + elif id == OutlineService.SORT_NONE: + self.OnSort(event) + return True + else: + return False + + + def ProcessUpdateUIEvent(self, event): + if Service.Service.ProcessUpdateUIEvent(self, event): + return True + + id = event.GetId() + if id == OutlineService.SORT_ASC: + event.Enable(True) + + config = wx.ConfigBase_Get() + sort = config.ReadInt("OutlineSort", SORT_NONE) + if sort == SORT_ASC: + self._outlineSortMenu.Check(OutlineService.SORT_ASC, True) + else: + self._outlineSortMenu.Check(OutlineService.SORT_ASC, False) + + return True + elif id == OutlineService.SORT_DESC: + event.Enable(True) + + config = wx.ConfigBase_Get() + sort = config.ReadInt("OutlineSort", SORT_NONE) + if sort == SORT_DESC: + self._outlineSortMenu.Check(OutlineService.SORT_DESC, True) + else: + self._outlineSortMenu.Check(OutlineService.SORT_DESC, False) + + return True + elif id == OutlineService.SORT_NONE: + event.Enable(True) + + config = wx.ConfigBase_Get() + sort = config.ReadInt("OutlineSort", SORT_NONE) + if sort == SORT_NONE: + self._outlineSortMenu.Check(OutlineService.SORT_NONE, True) + else: + self._outlineSortMenu.Check(OutlineService.SORT_NONE, False) + + return True + else: + return False + + + def OnSort(self, event): + id = event.GetId() + if id == OutlineService.SORT_ASC: + wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC) + self.GetView().OnSort(SORT_ASC) + return True + elif id == OutlineService.SORT_DESC: + wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC) + self.GetView().OnSort(SORT_DESC) + return True + elif id == OutlineService.SORT_NONE: + wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE) + self.GetView().OnSort(SORT_NONE) + return True + + + #---------------------------------------------------------------------------- + # Service specific methods + #---------------------------------------------------------------------------- + + def LoadOutline(self, view, position=-1, force=False): + if not self.GetView(): + return + + self.SaveExpansionState() + if view.DoLoadOutlineCallback(force=force): + self.GetView().OnSort(wx.ConfigBase_Get().ReadInt("OutlineSort", SORT_NONE)) + self.LoadExpansionState() + if position >= 0: + self.SyncToPosition(position) + + + def SyncToPosition(self, position): + if not self.GetView(): + return + self.GetView().GetTreeCtrl().SelectClosestItem(position) + + + def OnCloseFrame(self, event): + Service.Service.OnCloseFrame(self, event) + self.SaveExpansionState(clear = True) + + return True + + + def SaveExpansionState(self, clear = False): + if clear: + expanded = [] + elif self.GetView(): + expanded = self.GetView().GetExpansionState() + wx.ConfigBase_Get().Write("OutlineLastExpanded", expanded.__repr__()) + + + def LoadExpansionState(self): + expanded = wx.ConfigBase_Get().Read("OutlineLastExpanded") + if expanded: + self.GetView().SetExpansionState(eval(expanded)) + + + #---------------------------------------------------------------------------- + # Timer Methods + #---------------------------------------------------------------------------- + + def StartBackgroundTimer(self): + self._timer = wx.PyTimer(self.DoBackgroundRefresh) + self._timer.Start(250) + + + def DoBackgroundRefresh(self): + """ Refresh the outline view periodically """ + self._timer.Stop() + + foundRegisteredView = False + if self.GetView(): + currView = wx.GetApp().GetDocumentManager().GetCurrentView() + if currView: + for template in self._validTemplates: + type = template.GetViewType() + if isinstance(currView, type): + self.LoadOutline(currView) + foundRegisteredView = True + break + + if not foundRegisteredView: + self.GetView().ClearTreeCtrl() + + self._timer.Start(1000) # 1 second interval + + + def AddTemplateForBackgroundHandler(self, template): + self._validTemplates.append(template) + + + def GetTemplatesForBackgroundHandler(self): + return self._validTemplates + + + def RemoveTemplateForBackgroundHandler(self, template): + self._validTemplates.remove(template) + diff --git a/wxPython/samples/ide/activegrid/tool/PHPEditor.py b/wxPython/samples/ide/activegrid/tool/PHPEditor.py new file mode 100644 index 0000000000..bf16594d3f --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/PHPEditor.py @@ -0,0 +1,297 @@ +#---------------------------------------------------------------------------- +# Name: PHPEditor.py +# Purpose: PHP Script Editor for pydocview tbat uses the Styled Text Control +# +# Author: Morgan Hua +# +# Created: 1/4/04 +# CVS-ID: $Id$ +# Copyright: (c) 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import string +import STCTextEditor +import CodeEditor +import OutlineService +import os +import re + + +class PHPDocument(CodeEditor.CodeDocument): + + pass + + +class PHPView(CodeEditor.CodeView): + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return PHPCtrl + + + def GetAutoCompleteHint(self): + pos = self.GetCtrl().GetCurrentPos() + if pos == 0: + return None, None + + validLetters = string.letters + string.digits + '_$' + word = '' + while (True): + pos = pos - 1 + if pos < 0: + break + char = chr(self.GetCtrl().GetCharAt(pos)) + if char not in validLetters: + break + word = char + word + + return None, word + + + def GetAutoCompleteDefaultKeywords(self): + return PHPKEYWORDS + + #---------------------------------------------------------------------------- + # Methods for OutlineService + #---------------------------------------------------------------------------- + + def DoLoadOutlineCallback(self, force=False): + outlineService = wx.GetApp().GetService(OutlineService.OutlineService) + if not outlineService: + return False + + outlineView = outlineService.GetView() + if not outlineView: + return False + + treeCtrl = outlineView.GetTreeCtrl() + if not treeCtrl: + return False + + view = treeCtrl.GetCallbackView() + newCheckSum = self.GenCheckSum() + if not force: + if view and view is self: + if self._checkSum == newCheckSum: + return False + self._checkSum = newCheckSum + + treeCtrl.DeleteAllItems() + + document = self.GetDocument() + if not document: + return True + + filename = document.GetFilename() + if filename: + rootItem = treeCtrl.AddRoot(os.path.basename(filename)) + treeCtrl.SetDoSelectCallback(rootItem, self, None) + else: + return True + + text = self.GetValue() + if not text: + return True + + INTERFACE_PATTERN = 'interface[ \t]+\w+' + CLASS_PATTERN = '((final|abstract)[ \t]+)?((public|private|protected)[ \t]+)?(static[ \t]+)?class[ \t]+\w+((implements|extends)\w+)?' + FUNCTION_PATTERN = '(abstract[ \t]+)?((public|private|protected)[ \t]+)?(static[ \t]+)?function[ \t]+?\w+\(.*?\)' + interfacePat = re.compile(INTERFACE_PATTERN, re.M|re.S) + classPat = re.compile(CLASS_PATTERN, re.M|re.S) + funcPat= re.compile(FUNCTION_PATTERN, re.M|re.S) + pattern = re.compile('^[ \t]*('+ CLASS_PATTERN + '.*?{|' + FUNCTION_PATTERN + '|' + INTERFACE_PATTERN +'\s*?{).*?$', re.M|re.S) + + iter = pattern.finditer(text) + indentStack = [(0, rootItem)] + for pattern in iter: + line = pattern.string[pattern.start(0):pattern.end(0)] + foundLine = classPat.search(line) + if foundLine: + indent = foundLine.start(0) + itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)] + else: + foundLine = funcPat.search(line) + if foundLine: + indent = foundLine.start(0) + itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)] + else: + foundLine = interfacePat.search(line) + if foundLine: + indent = foundLine.start(0) + itemStr = foundLine.string[foundLine.start(0):foundLine.end(0)] + + if indent == 0: + parentItem = rootItem + else: + lastItem = indentStack.pop() + while lastItem[0] >= indent: + lastItem = indentStack.pop() + indentStack.append(lastItem) + parentItem = lastItem[1] + + item = treeCtrl.AppendItem(parentItem, itemStr) + treeCtrl.SetDoSelectCallback(item, self, (pattern.end(0), pattern.start(0) + indent)) # select in reverse order because we want the cursor to be at the start of the line so it wouldn't scroll to the right + indentStack.append((indent, item)) + + treeCtrl.Expand(rootItem) + + return True + + +class PHPService(CodeEditor.CodeService): + + + def __init__(self): + CodeEditor.CodeService.__init__(self) + + +class PHPCtrl(CodeEditor.CodeCtrl): + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + CodeEditor.CodeCtrl.__init__(self, parent, ID, style) + self.SetLexer(wx.stc.STC_LEX_HTML) + self.SetStyleBits(7) + self.SetKeyWords(4, string.join(PHPKEYWORDS)) + self.SetProperty("fold.html", "1") + + + def CanWordWrap(self): + return True + + + def SetViewDefaults(self): + CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "PHP", hasWordWrap = True, hasTabs = True) + + + def GetFontAndColorFromConfig(self): + return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "PHP") + + + def UpdateStyles(self): + CodeEditor.CodeCtrl.UpdateStyles(self) + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + + # HTML Styles + # White space + self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + # Comment + self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + # Number + self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + # String + self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + # Tag + self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + # Attributes + self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + + + # PHP Styles + self.StyleSetSpec(wx.stc.STC_HPHP_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_SIMPLESTRING, "face:%(font)s,fore:#7F007F,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_HSTRING, "face:%(font)s,fore7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_HSTRING_VARIABLE, "face:%(font)s,fore:#007F7F,italic,bold,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_VARIABLE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_OPERATOR, "face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_HPHP_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) # keyword + + +class PHPOptionsPanel(STCTextEditor.TextOptionsPanel): + + def __init__(self, parent, id): + STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "PHP", label = "PHP", hasWordWrap = True, hasTabs = True) + + +PHPKEYWORDS = [ + "and", "or", "xor", "__FILE__", "exception", "__LINE__", "array", "as", "break", "case", + "class", "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif", + "empty", "enddeclare", "endfor", "endforeach", "endif", "endswith", "endwhile", "eval", + "exit", "extends", "for", "foreach", "function", "global", "if", "include", "include_once", + "isset", "list", "new", "print", "require", "require_once", "return", "static", "switch", + "unset", "use", "var", "while", "__FUNCTION__", "__CLASS__", "__METHOD__", "final", "php_user_filter", + "interface", "implements", "extends", "public", "private", "protected", "abstract", "clone", "try", "catch", + "throw", "cfunction", "old_function", + "$_SERVER", "$_ENV", "$_COOKIE", "$_GET", "$_POST", "$_FILES", "$_REQUEST", "$_SESSION", "$GLOBALS", "$php_errormsg", + "PHP_VERSION", "PHP_OS", "PHP_EOL", "DEFAULT_INCLUDE_PATH", "PEAR_INSTALL_DIR", "PEAR_EXTENSION_DIR", + "PHP_EXTENSION_DIR", "PHP_BINDIR", "PHP_LIBDIR", "PHP_DATADIR", "PHP_SYSCONFDIR", "PHP_LOCALSTATEDIR", + "PHP_CONFIG_FILE_PATH", "PHP_OUTPUT_HANDLER_START", "PHP_OUTPUT_HANDLER_CONT", "PHP_OUTPUT_HANDLER_END", + "E_ERROR", "E_WARNING", "E_PARSE", "E_NOTICE", "E_CORE_ERROR", "E_CORE_WARNING", "E_COMPILE_ERROR", + "E_COMPILE_WARNING", "E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_ALL", "E_STRICT", + "TRUE", "FALSE", "NULL", "ZEND_THREAD_SAFE", + "EXTR_OVERWRITE", "EXTR_SKIP", "EXTR_PREFIX_SAME", "EXTR_PREFIX_ALL", "EXTR_PREFIX_INVALID", + "EXTR_PREFIX_IF_EXISTS", "EXTR_IF_EXISTS", "SORT_ASC", "SORT_DESC", "SORT_REGULAR", "SORT_NUMERIC", + "SORT_STRING", "CASE_LOWER", "CASE_UPPER", "COUNT_NORMAL", "COUNT_RECURSIVE", "ASSERT_ACTIVE", + "ASSERT_CALLBACK", "ASSERT_BAIL", "ASSERT_WARNING", "ASSERT_QUIET_EVAL", "CONNECTION_ABORTED", + "CONNECTION_NORMAL", "CONNECTION_TIMEOUT", "INI_USER", "INI_PERDIR", "INI_SYSTEM", "INI_ALL", + "M_E", "M_LOG2E", "M_LOG10E", "M_LN2", "M_LN10", "M_PI", "M_PI_2", "M_PI_4", "M_1_PI", "M_2_PI", + "M_2_SQRTPI", "M_SQRT2", "M_SQRT1_2", "CRYPT_SALT_LENGTH", "CRYPT_STD_DES", "CRYPT_EXT_DES", "CRYPT_MD5", + "CRYPT_BLOWFISH", "DIRECTORY_SEPARATOR", "SEEK_SET", "SEEK_CUR", "SEEK_END", "LOCK_SH", "LOCK_EX", "LOCK_UN", + "LOCK_NB", "HTML_SPECIALCHARS", "HTML_ENTITIES", "ENT_COMPAT", "ENT_QUOTES", "ENT_NOQUOTES", "INFO_GENERAL", + "INFO_CREDITS", "INFO_CONFIGURATION", "INFO_MODULES", "INFO_ENVIRONMENT", "INFO_VARIABLES", "INFO_LICENSE", + "INFO_ALL", "CREDITS_GROUP", "CREDITS_GENERAL", "CREDITS_SAPI", "CREDITS_MODULES", "CREDITS_DOCS", + "CREDITS_FULLPAGE", "CREDITS_QA", "CREDITS_ALL", "STR_PAD_LEFT", "STR_PAD_RIGHT", "STR_PAD_BOTH", + "PATHINFO_DIRNAME", "PATHINFO_BASENAME", "PATHINFO_EXTENSION", "PATH_SEPARATOR", "CHAR_MAX", "LC_CTYPE", + "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_ALL", "LC_MESSAGES", "ABDAY_1", "ABDAY_2", + "ABDAY_3", "ABDAY_4", "ABDAY_5", "ABDAY_6", "ABDAY_7", "DAY_1", "DAY_2", "DAY_3", "DAY_4", "DAY_5", + "DAY_6", "DAY_7", "ABMON_1", "ABMON_2", "ABMON_3", "ABMON_4", "ABMON_5", "ABMON_6", "ABMON_7", "ABMON_8", + "ABMON_9", "ABMON_10", "ABMON_11", "ABMON_12", "MON_1", "MON_2", "MON_3", "MON_4", "MON_5", "MON_6", "MON_7", + "MON_8", "MON_9", "MON_10", "MON_11", "MON_12", "AM_STR", "PM_STR", "D_T_FMT", "D_FMT", "T_FMT", "T_FMT_AMPM", + "ERA", "ERA_YEAR", "ERA_D_T_FMT", "ERA_D_FMT", "ERA_T_FMT", "ALT_DIGITS", "INT_CURR_SYMBOL", "CURRENCY_SYMBOL", + "CRNCYSTR", "MON_DECIMAL_POINT", "MON_THOUSANDS_SEP", "MON_GROUPING", "POSITIVE_SIGN", "NEGATIVE_SIGN", + "INT_FRAC_DIGITS", "FRAC_DIGITS", "P_CS_PRECEDES", "P_SEP_BY_SPACE", "N_CS_PRECEDES", "N_SEP_BY_SPACE", + "P_SIGN_POSN", "N_SIGN_POSN", "DECIMAL_POINT", "RADIXCHAR", "THOUSANDS_SEP", "THOUSEP", "GROUPING", + "YESEXPR", "NOEXPR", "YESSTR", "NOSTR", "CODESET", "LOG_EMERG", "LOG_ALERT", "LOG_CRIT", "LOG_ERR", + "LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG", "LOG_KERN", "LOG_USER", "LOG_MAIL", "LOG_DAEMON", + "LOG_AUTH", "LOG_SYSLOG", "LOG_LPR", "LOG_NEWS", "LOG_UUCP", "LOG_CRON", "LOG_AUTHPRIV", "LOG_LOCAL0", + "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", "LOG_LOCAL4", "LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", + "LOG_PID", "LOG_CONS", "LOG_ODELAY", "LOG_NDELAY", "LOG_NOWAIT", "LOG_PERROR" + ] + + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getPHPData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00{IDAT8\x8dclh8\xf0\x9f\x81\x02\xc0D\x89f\xaa\x18\xc0\x82M0<\\\x1c\ +\xce^\xb9\xf2%y.\xd0\xd4\xd4$\xde\x05\xf8l\x0c\x0f\x17\x87\x8baS\xc7x\xfd\ +\xfa\xf5\xff\xc8\xb6]\xbf~\x1d\xc3\x05\xf8\xc4\x98\x90\x05\xae_\xbf\x8e\xa1\ +\x88\x90\xd8 \x8aF\x98\x93`~\xc3\x05\xd0\xd5\xc1\r\x80\t\xc0B\xf7\xfa\xf5\ +\xeb(l\\\xeaP\xbc\x80\x1c\x85\xb8\xd8\xe8|&b\x9c\x8dn;2`\x1c\xf0\xdc\x08\x00\ +\x8e\xf2S\xed\xb0\xbe\xaa\xbc\x00\x00\x00\x00IEND\xaeB`\x82' + +def getPHPBitmap(): + return BitmapFromImage(getPHPImage()) + +def getPHPImage(): + stream = cStringIO.StringIO(getPHPData()) + return ImageFromStream(stream) + +def getPHPIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getPHPBitmap()) + return icon diff --git a/wxPython/samples/ide/activegrid/tool/PerlEditor.py b/wxPython/samples/ide/activegrid/tool/PerlEditor.py new file mode 100644 index 0000000000..e09f1356f4 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/PerlEditor.py @@ -0,0 +1,425 @@ +#---------------------------------------------------------------------------- +# Name: PerlEditor.py +# Purpose: Perl Script Editor for pydocview tbat uses the Styled Text Control +# +# Author: Morgan Hua +# +# Created: 1/5/04 +# CVS-ID: $Id$ +# Copyright: (c) 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import string +import STCTextEditor +import CodeEditor + + +class PerlDocument(CodeEditor.CodeDocument): + + pass + + +class PerlView(CodeEditor.CodeView): + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return PerlCtrl + + + def GetAutoCompleteHint(self): + pos = self.GetCtrl().GetCurrentPos() + if pos == 0: + return None, None + + validLetters = string.letters + string.digits + '_/' + word = '' + while (True): + pos = pos - 1 + if pos < 0: + break + char = chr(self.GetCtrl().GetCharAt(pos)) + if char not in validLetters: + break + word = char + word + + return None, word + + + def GetAutoCompleteDefaultKeywords(self): + return PERLKEYWORDS + + +class PerlService(CodeEditor.CodeService): + + + def __init__(self): + CodeEditor.CodeService.__init__(self) + + +class PerlCtrl(CodeEditor.CodeCtrl): + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + CodeEditor.CodeCtrl.__init__(self, parent, ID, style) + self.SetLexer(wx.stc.STC_LEX_PERL) + self.SetKeyWords(0, string.join(PERLKEYWORDS)) + + + def CanWordWrap(self): + return True + + + def SetViewDefaults(self): + CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Perl", hasWordWrap = True, hasTabs = True) + + + def GetFontAndColorFromConfig(self): + return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Perl") + + + def UpdateStyles(self): + CodeEditor.CodeCtrl.UpdateStyles(self) + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + # Perl Styles + self.StyleSetSpec(wx.stc.STC_PL_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_CHARACTER, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING_Q, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING_QQ, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING_QX, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING_QR, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_STRING_QW, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_BACKTICKS, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) # keyword + self.StyleSetSpec(wx.stc.STC_PL_IDENTIFIER, "face:%(font)s,fore:#%(color)s,face:%(font)s,size:%(size)d" % faces) + + # Default + self.StyleSetSpec(wx.stc.STC_PL_ARRAY, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_DATASECTION, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_ERROR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_HASH, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_HERE_DELIM, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_HERE_Q, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_HERE_QQ, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_HERE_QX, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_LONGQUOTE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_OPERATOR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_POD, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_PREPROCESSOR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_PUNCTUATION, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_REGEX, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_REGSUBST, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_SCALAR, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_PL_SYMBOLTABLE, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + + +class PerlOptionsPanel(STCTextEditor.TextOptionsPanel): + + def __init__(self, parent, id): + STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Perl", label = "Perl", hasWordWrap = True, hasTabs = True) + + +PERLKEYWORDS = [ + "abs", + "accept", + "alarm", + "atan2", + "bind", + "binmode", + "bless", + "caller", + "chdir", + "chmod", + "chomp", + "chop", + "chown", + "chr", + "chroot", + "close", + "closedir", + "connect", + "continue", + "cos", + "crypt", + "dbmclose", + "dbmopen", + "defined", + "delete", + "die", + "do", + "dump", + "each", + "endgrent", + "endhostent", + "endnetent", + "endprotoent", + "endpwent", + "endservent", + "eof", + "eval", + "exec", + "exists", + "exit", + "exp", + "fcntl", + "fileno", + "flock", + "fork", + "format", + "formline", + "getc", + "getgrent", + "getgrgid", + "getgrnam", + "gethostbyaddr", + "gethostbyname", + "gethostent", + "getlogin", + "getnetbyaddr", + "getnetbyname", + "getnetent", + "getpeername", + "getpgrp", + "getppid", + "getpriority", + "getprotobyname", + "getprotobynumber", + "getprotoent", + "getpwent", + "getpwnam", + "getpwuid", + "getservbyname", + "getservbyport", + "getservent", + "getsockname", + "getsockopt", + "glob", + "gmtime", + "goto", + "grep", + "hex", + "import", + "index", + "int", + "ioctl", + "join", + "keys", + "kill", + "last", + "lc", + "lcfirst", + "length", + "link", + "listen", + "local", + "localtime", + "log", + "lstat", + "m//", + "map", + "mkdir", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "my", + "next", + "no", + "oct", + "open", + "opendir", + "ord", + "pack", + "package", + "pipe", + "pop", + "pos", + "print", + "printf", + "prototype", + "push", + "q/STRING/", + "qq/STRING/", + "quotemeta", + "qw", + "qw/STRING/", + "qx", + "qx/STRING/", + "rand", + "read", + "readdir", + "readline", + "readlink", + "readpipe", + "recv", + "redo", + "ref", + "rename", + "require", + "reset", + "return", + "reverse", + "rewinddir", + "rindex", + "rmdir", + "s///", + "scalar", + "seek", + "seekdir", + "select", + "semctl", + "semget", + "semop", + "send", + "setgrent", + "sethostent", + "setnetent", + "setpgrp", + "setpriority", + "setprotoent", + "setpwent", + "setservent", + "setsockopt", + "shift", + "shmctl", + "shmget", + "shmread", + "shmwrite", + "shutdown", + "sin", + "sleep", + "socket", + "socketpair", + "sort", + "splice", + "split", + "sprintf", + "sqrt", + "srand", + "stat", + "study", + "sub", + "substr", + "symlink", + "syscall", + "sysopen", + "sysread", + "sysseek", + "system", + "syswrite", + "tell", + "telldir", + "tie", + "tied", + "times", + "tr///", + "truncate", + "uc", + "ucfirst", + "umask", + "undef", + "unlink", + "unpack", + "unshift", + "untie", + "use", + "utime", + "values", + "vec", + "wait", + "waitpid", + "wantarray", + "warn", + "write", + "y///", + "eq", + "ne", + "lt", + "le", + "gt", + "ge", + "cmp", + "if", + "else" + "not", + "and", + "xor", + "or", + "if", + "while", + "until", + "for", + "foreach", + "last", + "next", + "redo", + "goto", + "STDIN", + "STDOUT", + "STDERR", + "WHEncE", + "BEGIN", + "END", + "require", + "integer", + "less", + "sigtrap", + "strict", + "subs" + ] + + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getPerlData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0e\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00&\x94N:\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\ +\x01mIDAT(\x91\x9d\x93/\x8e\xf30\x10\xc5\x7f[\xed\x05\xca\x82,Ef\xc6e\x01Vx{\ +\x89\x04\x16\x94\x97\x045\xa8\xa0\xa44G\x08\xc9\x01\xa2\x80\x1e $ld\xc9\xa8w\ +\x08\xf0\xa2x\x93~\x1f\xda\x91F\xb2\xfd\xe6=\xcd?\x7f\xcd\xf3\x1c\xf8\x83}/\ +\x87i\x9a\x000\xc6D\xb0\xeb:\x86a MS\xce\xe7\xf3\x968M\x13}\xdf\xe3\x9cCD\ +\xb8\xddn\x18c\xe8\xba\x8e\xb2,\xc9\xb2\x0c\x11\x01\xd8\x90w\xc6\x18\x94R\ +\xf1\xa1\xef{\xba\xae\xa3i\x1a\xb4\xd6h\xad)\x8a\x02\xe7\\\xccj\x93\xea\xa2\ +\nP\xd75\xd7\xeb\x15\x00\xef\xfdFt)e\xb7\x80\x8b\xbas\x8e$I\xe2}\xc1\x8b\xa2\ +\xd8\xf4b\x07\xa0\x94\xe2\xf5za\xad\xc5Z\xcb\xfb\xfdFD\xe8\xfb\x9e\x05\x17\ +\x11\x9cs4M\xf3K<\x9dNdY\xc60\x0cx\xef\x11\x11\xea\xbaF)\x85s\x8e\xba\xae)\ +\xcb\x12\x11!M\xd3_"\xc0\xfd~\xc7Z\x8bs\x0e\x80$I\xa2:@UU1u\x00\xe6y\x0ek\ +\x1f\xc71\x1c\x0e\x87\xd0\xb6m\xd8\xef\xf7\xe1\xf1x\x84\xcb\xe5\x12\xe6y\x0e\ +\xc7\xe31\xc6\xed\xf80\x11!\xcb2\xbc\xf7TUE\x9e\xe71=\xadul\xce\xf7\'Qk\x8d\ +\xf7\x9e<\xcf\x81\xed&Yk\xb7\xe3\xf84\xa5\x14\xc6\x18D\x84\xe7\xf3\x19\x83\ +\xd75\xfe\x97\xb8\x0eXo\xcc2\x9e\x7f\x9a3\x8ech\xdb6|6l\xf15\xf6\xf5\xd7o\ +\xf5\x03\xaf\x9f\xfa@\x02\xe4\xdc\xf9\x00\x00\x00\x00IEND\xaeB`\x82' + + +def getPerlBitmap(): + return BitmapFromImage(getPerlImage()) + +def getPerlImage(): + stream = cStringIO.StringIO(getPerlData()) + return ImageFromStream(stream) + +def getPerlIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getPerlBitmap()) + return icon diff --git a/wxPython/samples/ide/activegrid/tool/ProjectEditor.py b/wxPython/samples/ide/activegrid/tool/ProjectEditor.py new file mode 100644 index 0000000000..763e74bc0a --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/ProjectEditor.py @@ -0,0 +1,1847 @@ +#---------------------------------------------------------------------------- +# Name: ProjectEditor.py +# Purpose: IDE-style Project Editor for wx.lib.pydocview +# +# Author: Peter Yared, Morgan Hua +# +# Created: 8/15/03 +# CVS-ID: $Id$ +# Copyright: (c) 2003, 2004, 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx.lib.docview +import wx.lib.pydocview +import types +import os +import os.path +import wx +from wxPython.lib.rcsizer import RowColSizer +import time +import Service +import MessageService +import DebuggerService +import sys +import activegrid.util.xmlmarshaller +import UICommon +from IDE import ACTIVEGRID_BASE_IDE +if not ACTIVEGRID_BASE_IDE: + import ProcessModelEditor + +_ = wx.GetTranslation + +if wx.Platform == '__WXMSW__': + _WINDOWS = True +else: + _WINDOWS = False + + +#---------------------------------------------------------------------------- +# XML Marshalling Methods +#---------------------------------------------------------------------------- + +def load(fileObject): + xml = fileObject.read() + projectModel = activegrid.util.xmlmarshaller.unmarshal(xml) + return projectModel + + +def save(fileObject, projectModel): + xml = activegrid.util.xmlmarshaller.marshal(projectModel, prettyPrint=True) + fileObject.write(xml) + + +#---------------------------------------------------------------------------- +# Classes +#---------------------------------------------------------------------------- + +class ProjectModel: + __xmlname__ = "projectmodel" + __xmlrename__ = { "_files":"files", "_homepath":"homepath" } + + def __init__(self): + self._homepath = None + self._files = [] + + +class ProjectDocument(wx.lib.docview.Document): + + def __init__(self): + wx.lib.docview.Document.__init__(self) + self._projectModel = ProjectModel() + + + def GetModel(self): + return self._projectModel + + + def OnCreate(self, path, flags): + projectService = wx.GetApp().GetService(ProjectService) + if projectService.GetView(): + view = projectService.GetView() + self.AddView(view) + else: + view = self.GetDocumentTemplate().CreateView(self, flags) + projectService.SetView(view) + return view + + + def LoadObject(self, fileObject): + self._projectModel = activegrid.tool.ProjectEditor.load(fileObject) + return True + + + def SaveObject(self, fileObject): + activegrid.tool.ProjectEditor.save(fileObject, self._projectModel) + return True + + + def OnSaveDocument(self, filename): + self._projectModel._homepath = wx.lib.docview.PathOnly(filename) + return wx.lib.docview.Document.OnSaveDocument(self, filename) + + + def OnOpenDocument(self, filename): + view = self.GetFirstView() + frame = view.GetFrame() + + if not os.path.exists(filename): + wx.GetApp().CloseSplash() + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Error") + wx.MessageBox(_("Could not find '%s'.") % filename, + msgTitle, + wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP, + frame) + return True # if we return False, the Project View is destroyed, Service windows shouldn't be destroyed + + fileObject = file(filename, 'r') + try: + self.LoadObject(fileObject) + except: + wx.GetApp().CloseSplash() + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Error") + wx.MessageBox(_("Could not open '%s'. %s") % (wx.lib.docview.FileNameFromPath(filename), sys.exc_value), + msgTitle, + wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP, + frame) + return True # if we return False, the Project View is destroyed, Service windows shouldn't be destroyed + + self.Modify(False) + + # if the project file has moved, then ask the user if we should readjust the paths of all the files in the project + newHomepath = wx.lib.docview.PathOnly(filename) + if newHomepath != self._projectModel._homepath: + wx.GetApp().CloseSplash() + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("Project Moved") + projectService = wx.GetApp().GetService(activegrid.tool.ProjectEditor.ProjectService) + yesNoMsg = wx.MessageDialog(frame, + _("The project file '%s' was moved from:\n '%s'\nto:\n '%s'.\n\nWould you like to automatically adjust the project contents accordingly?") % (wx.lib.docview.FileNameFromPath(filename), self._projectModel._homepath, wx.lib.docview.PathOnly(filename)), + msgTitle, + wx.YES_NO | wx.STAY_ON_TOP + ) + if projectService.GetSuppressOpenProjectMessages() or yesNoMsg.ShowModal() == wx.ID_YES: + if not projectService.GetSuppressOpenProjectMessages(): + messageService = wx.GetApp().GetService(MessageService.MessageService) + messageService.ShowWindow() + messageView = messageService.GetView() + messageView.ClearLines() + messageView.AddLines(_("The project file '%s' was moved from:\n '%s'\nto:\n '%s'\n") % (wx.lib.docview.FileNameFromPath(filename), self._projectModel._homepath, wx.lib.docview.PathOnly(filename))) + messageView.AddLines(_("Updating file references:\n")) + + for index, filepath in enumerate(self._projectModel._files): + if filepath.startswith(self._projectModel._homepath + os.sep): + newfile = newHomepath + filepath[len(self._projectModel._homepath):len(filepath)] + if os.path.exists(newfile): + self._projectModel._files[index] = newfile + if not projectService.GetSuppressOpenProjectMessages(): + messageView.AddLines(_(" Success: '%s' location changed from '%s' to '%s'\n") % (wx.lib.docview.FileNameFromPath(filepath), wx.lib.docview.PathOnly(filepath), newHomepath)) + self.Modify(True) + else: + if not projectService.GetSuppressOpenProjectMessages(): + messageView.AddLines(_(" Failure: Couldn't find '%s', file wasn't located at '%s'\n") % (wx.lib.docview.FileNameFromPath(filepath), newHomepath)) + else: + if not projectService.GetSuppressOpenProjectMessages(): + messageView.AddLines(_( " Unmodified: '%s' location wasn't relative to '%s'\n") % (filepath, self._projectModel._homepath)) + self._projectModel._homepath = newHomepath + if not projectService.GetSuppressOpenProjectMessages(): + messageView.AddLines(_("Project file updated.")) + + self.SetFilename(filename, True) + view.AddProjectToView(self) + self.UpdateAllViews() + self._savedYet = True + view.Activate(True) + return True + + + def AddFile(self, file): + return self.AddFiles([file]) + + + def AddFiles(self, files): + notAlreadyThereFiles = filter(lambda x: x not in self._projectModel._files, files) # Filter to the files that are not already in the project + if len(notAlreadyThereFiles) == 0: + self.UpdateAllViews(hint = ("select", self, files)) + return False + else: + self._projectModel._files = self._projectModel._files + notAlreadyThereFiles + self.UpdateAllViews(hint = ("add", self, notAlreadyThereFiles)) + self.Modify(True) + return True + + + def RemoveFile(self, file): + return self.RemoveFiles([file]) + + + def RemoveFiles(self, files): + for file in files: + self._projectModel._files.remove(file) + self.UpdateAllViews(hint = ("remove", self, files)) + self.Modify(True) + return True + + + def RenameFile(self, oldFile, newFile, isProject = False): + try: + if oldFile == newFile: + return False + + # projects don't have to exist yet, so not required to rename old file, + # but files must exist, so we'll try to rename and allow exceptions to occur if can't. + if not isProject or (isProject and os.path.exists(oldFile)): + os.rename(oldFile, newFile) + + if isProject: + documents = self.GetDocumentManager().GetDocuments() + for document in documents: + if document.GetFilename() == oldFile: # If the renamed document is open, update it + document.SetFilename(newFile) + document.SetTitle(wx.lib.docview.FileNameFromPath(newFile)) + document.UpdateAllViews(hint = ("rename", document, newFile)) + else: + self.RemoveFile(oldFile) + self.AddFile(newFile) + documents = self.GetDocumentManager().GetDocuments() + for document in documents: + if document.GetFilename() == oldFile: # If the renamed document is open, update it + document.SetFilename(newFile, notifyViews = True) + document.UpdateAllViews(hint = ("rename", document, newFile)) + return True + except OSError, (code, message): + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Error") + wx.MessageBox("Could not rename '%s'. '%s'" % (wx.lib.docview.FileNameFromPath(oldFile), message), + msgTitle, + wx.OK | wx.ICON_EXCLAMATION, + self.GetFirstView().GetFrame()) + return False + + + def GetFiles(self): + return self._projectModel._files + + + def IsFileInProject(self, filename): + return filename in self.GetFiles() + + +import Wizard + +class NewProjectWizard(Wizard.BaseWizard): + + WIZTITLE = _("New Project Wizard") + + def __init__(self, parent): + self._parent = parent + self._fullProjectPath = None + Wizard.BaseWizard.__init__(self, parent, self.WIZTITLE) + self._projectLocationPage = self.CreateProjectLocation(self) + wx.wizard.EVT_WIZARD_PAGE_CHANGING(self, self.GetId(), self.OnWizPageChanging) + + def CreateProjectLocation(self,wizard): + page = Wizard.TitledWizardPage(wizard, _("Project File Location")) + + page.GetSizer().Add(wx.StaticText(page, -1, _("\nSelect the directory and filename for the project.\n\n"))) + self._projectName, self._dirCtrl, sizer, self._fileValidation = UICommon.CreateDirectoryControl(page, _("File Name:"), _("Directory:"), _("agp"), startingDirectory=os.getcwd()) + page.GetSizer().Add(sizer, 1, flag=wx.EXPAND) + + wizard.Layout() + wizard.FitToPage(page) + return page + + def RunWizard(self, existingTables = None, existingRelationships = None): + status = wx.wizard.Wizard.RunWizard(self, self._projectLocationPage) + if status: + docManager = wx.GetApp().GetTopWindow().GetDocumentManager() + if os.path.exists(self._fullProjectPath): + # What if the document is already open and we're overwriting it? + documents = docManager.GetDocuments() + for document in documents: + if document.GetFilename() == self._fullProjectPath: # If the renamed document is open, update it + document.DeleteAllViews() + break + os.remove(self._fullProjectPath) + + for template in docManager.GetTemplates(): + if template.GetDocumentType() == ProjectDocument: + doc = template.CreateDocument(self._fullProjectPath, flags = wx.lib.docview.DOC_NEW) + doc.OnSaveDocument(self._fullProjectPath) + view = doc.GetFirstView() + view.AddProjectToView(doc) + break + + self.Destroy() + return status + + + def OnWizPageChanging(self, event): + if event.GetDirection(): # It's going forwards + if event.GetPage() == self._projectLocationPage: + if not self._fileValidation(): + event.Veto() + return + self._fullProjectPath = os.path.join(self._dirCtrl.GetValue(),UICommon.MakeNameEndInExtension(self._projectName.GetValue(),'.agp')) + + + + def OnShowCreatePages(self): + self.Hide() + import DataModelEditor + requestedPos = self.GetPositionTuple() + projectService = wx.GetApp().GetService(ProjectService) + projectView = projectService.GetView() + + wiz = DataModelEditor.ImportExportWizard(projectView.GetFrame(), pos=requestedPos) + if wiz.RunWizard(dontDestroy=True): + self._schemaName.SetValue(wiz.GetSchemaFileName()) + wiz.Destroy() + self.Show(True) + +class ProjectTemplate(wx.lib.docview.DocTemplate): + + def CreateDocument(self, path, flags): + if path: + return wx.lib.docview.DocTemplate.CreateDocument(self, path, flags) + else: + wiz = NewProjectWizard(wx.GetApp().GetTopWindow()) + wiz.RunWizard() + wiz.Destroy() + return None # never return the doc, otherwise docview will think it is a new file and rename it + +class ProjectAddFilesCommand(wx.lib.docview.Command): + + def __init__(self, projectDoc, files): + wx.lib.docview.Command.__init__(self, canUndo = True) + self._projectDoc = projectDoc + self._files = files + + + def GetName(self): + if len(self._files) == 1: + return _("Add File") + else: + return _("Add Files") + + + def Do(self): + return self._projectDoc.AddFiles(self._files) + + + def Undo(self): + return self._projectDoc.RemoveFiles(self._files) + + +class ProjectRemoveFilesCommand(wx.lib.docview.Command): + + def __init__(self, projectDoc, files): + wx.lib.docview.Command.__init__(self, canUndo = True) + self._projectDoc = projectDoc + self._files = files + + + def GetName(self): + if len(self._files) == 1: + return _("Remove File") + else: + return _("Remove Files") + + + def Do(self): + return self._projectDoc.RemoveFiles(self._files) + + + def Undo(self): + return self._projectDoc.AddFiles(self._files) + + +class ProjectRenameFileCommand(wx.lib.docview.Command): + + def __init__(self, projectDoc, oldFile, newFile, isProject = False): + wx.lib.docview.Command.__init__(self, canUndo = True) + self._projectDoc = projectDoc + self._oldFile = oldFile + self._newFile = newFile + self._isProject = isProject + + + def GetName(self): + return _("Rename File") + + + def Do(self): + return self._projectDoc.RenameFile(self._oldFile, self._newFile, self._isProject) + + + def Undo(self): + return self._projectDoc.RenameFile(self._newFile, self._oldFile, self._isProject) + + +class ProjectTreeCtrl(wx.TreeCtrl): + + def __init__(self, parent, id, style): + wx.TreeCtrl.__init__(self, parent, id, style = style) + + templates = wx.GetApp().GetDocumentManager().GetTemplates() + iconList = wx.ImageList(16, 16, initialCount = len(templates)) + self._iconIndexLookup = [] + for template in templates: + icon = template.GetIcon() + if icon: + if icon.GetHeight() != 16: + icon.SetHeight(16) # wxBug: img2py.py uses EmptyIcon which is 32x32 + if icon.GetWidth() != 16: + icon.SetWidth(16) # wxBug: img2py.py uses EmptyIcon which is 32x32 + iconIndex = iconList.AddIcon(icon) + self._iconIndexLookup.append((template, iconIndex)) + + icon = getBlankIcon() + if icon.GetHeight() != 16: + icon.SetHeight(16) # wxBug: img2py.py uses EmptyIcon which is 32x32 + if icon.GetWidth() != 16: + icon.SetWidth(16) # wxBug: img2py.py uses EmptyIcon which is 32x32 + self._blankIconIndex = iconList.AddIcon(icon) + self.AssignImageList(iconList) + + + def OnCompareItems(self, item1, item2): + return cmp(self.GetItemText(item1).lower(), self.GetItemText(item2).lower()) + + + def AppendItem(self, parent, filepath): + item = wx.TreeCtrl.AppendItem(self, parent, filepath) + + found = False + template = wx.GetApp().GetDocumentManager().FindTemplateForPath(filepath) + if not template and parent == self.GetRootItem(): # If the parent is a root it's a new project + template = wx.GetApp().GetDocumentManager().FindTemplateForPath('.agp') + if template: + for t, iconIndex in self._iconIndexLookup: + if t is template: + self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Normal) + self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Expanded) + self.SetItemImage(item, iconIndex, wx.TreeItemIcon_Selected) + found = True + break + + if not found: + self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Normal) + self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Expanded) + self.SetItemImage(item, self._blankIconIndex, wx.TreeItemIcon_Selected) + + return item + + +class ProjectView(wx.lib.docview.View): + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self, service = None): + wx.lib.docview.View.__init__(self) + self._service = service # not used, but kept to match other Services + self._lastDirectory = "" + self._treeCtrl = None + self._editingSoDontKillFocus = False + self._checkEditMenu = True + + + def Destroy(self): + projectService = wx.GetApp().GetService(ProjectService) + if projectService: + projectService.SetView(None) + wx.lib.docview.View.Destroy(self) + + + def GetDocument(self): + if not self._treeCtrl: + return None + + items = self._treeCtrl.GetSelections() + if not items: # No selection, so just return first project + item = self._treeCtrl.GetFirstVisibleItem() + if item.IsOk(): + return self._GetItemProject(item) + else: + return None + + for item in items: + project = self._GetItemProject(item) + if project: + return project + + return None + + + def GetDocumentManager(self): # Overshadow this since the superclass uses the view._viewDocument attribute directly, which the project editor doesn't use since it hosts multiple docs + return wx.GetApp().GetDocumentManager() + + + def OnChangeFilename(self): + if self.GetFrame(): + title = _("Projects") + if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName(): + title = title + " - " + wx.GetApp().GetAppName() + self.GetFrame().SetTitle(title) + project = self.GetDocument() + if project: + projectItem = self._GetProjectItem(project) + name = self._treeCtrl.GetItemText(self._GetProjectItem(project)) + name2 = self._MakeProjectName(project) + if name != name2: + self._treeCtrl.SetItemText(projectItem, name2) + self._treeCtrl.SortChildren(self._treeCtrl.GetRootItem()) + + + def Activate(self, activate = True): + if not wx.GetApp().IsMDI(): + if activate and not self.IsShown(): + self.Show() + + if self.IsShown(): + wx.lib.docview.View.Activate(self, activate = activate) + if activate and self._treeCtrl: + self._treeCtrl.SetFocus() + + + def OnCreate(self, doc, flags): + config = wx.ConfigBase_Get() + if wx.GetApp().IsMDI(): + self._embeddedWindow = wx.GetApp().GetTopWindow().GetEmbeddedWindow(wx.lib.pydocview.EMBEDDED_WINDOW_TOPLEFT) + self.SetFrame(self._embeddedWindow) + frame = self._embeddedWindow + else: + self._embeddedWindow = None + pos = config.ReadInt("ProjectFrameXLoc", -1), config.ReadInt("ProjectFrameYLoc", -1) + # make sure frame is visible + screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) + screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) + if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight: + pos = wx.DefaultPosition + + size = wx.Size(config.ReadInt("ProjectFrameXSize", -1), config.ReadInt("ProjectFrameYSize", -1)) + + title = _("Projects") + if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName(): + title = title + " - " + wx.GetApp().GetAppName() + + frame = wx.GetApp().CreateDocumentFrame(self, doc, 0, title = title, pos = pos, size = size) + if config.ReadInt("ProjectFrameMaximized", False): + frame.Maximize(True) + + sizer = wx.BoxSizer() + self._treeCtrl = ProjectTreeCtrl(frame, -1, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.TR_DEFAULT_STYLE | wx.TR_MULTIPLE) + self._treeCtrl.AddRoot(_("Projects")) + + if self._embeddedWindow: + sizer.Add(self._treeCtrl) + sizer.Fit(frame) + else: + sizer.Add(self._treeCtrl, 1, wx.EXPAND, 0) + frame.SetSizer(sizer) + frame.Layout() + self.Activate() + + if wx.GetApp().IsMDI(): + wx.EVT_SET_FOCUS(self._treeCtrl, self.OnFocus) + wx.EVT_KILL_FOCUS(self._treeCtrl, self.OnKillFocus) + + if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + wx.EVT_TREE_ITEM_ACTIVATED(self._treeCtrl, self._treeCtrl.GetId(), self.OnOpenSelectionSDI) + else: + wx.EVT_TREE_ITEM_ACTIVATED(self._treeCtrl, self._treeCtrl.GetId(), self.OnOpenSelection) + wx.EVT_TREE_BEGIN_LABEL_EDIT(self._treeCtrl, self._treeCtrl.GetId(), self.OnBeginLabelEdit) + wx.EVT_TREE_END_LABEL_EDIT(self._treeCtrl, self._treeCtrl.GetId(), self.OnEndLabelEdit) + wx.EVT_RIGHT_DOWN(self._treeCtrl, self.OnRightClick) + wx.EVT_KEY_DOWN(self._treeCtrl, self.OnKeyPressed) + # wx.EVT_COMMAND_RIGHT_CLICK(self._treeCtrl, self._treeCtrl.GetId(), self.OnRightClick) # wxBug: This isn't working for some reason + + # drag-and-drop support + dt = ProjectFileDropTarget(self) + self._treeCtrl.SetDropTarget(dt) + + return True + + + def WriteProjectConfig(self): + frame = self.GetFrame() + config = wx.ConfigBase_Get() + if frame and not self._embeddedWindow: + if not frame.IsMaximized(): + config.WriteInt("ProjectFrameXLoc", frame.GetPositionTuple()[0]) + config.WriteInt("ProjectFrameYLoc", frame.GetPositionTuple()[1]) + config.WriteInt("ProjectFrameXSize", frame.GetSizeTuple()[0]) + config.WriteInt("ProjectFrameYSize", frame.GetSizeTuple()[1]) + config.WriteInt("ProjectFrameMaximized", frame.IsMaximized()) + + if config.ReadInt("ProjectSaveDocs", True): + projectFileNames = [] + projectExpanded = [] + if self._treeCtrl: + for projectItem in self._GetChildItems(self._treeCtrl.GetRootItem()): + project = self._GetItemProject(projectItem) + if not project.OnSaveModified(): + return + if project.GetDocumentSaved(): # Might be a new document and "No" selected to save it + projectFileNames.append(str(project.GetFilename())) + projectExpanded.append(self._treeCtrl.IsExpanded(projectItem)) + config.Write("ProjectSavedDocs", projectFileNames.__repr__()) + config.Write("ProjectExpandedSavedDocs", projectExpanded.__repr__()) + + + def OnClose(self, deleteWindow = True): + if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + self.WriteProjectConfig() + project = self.GetDocument() + if not project: + return True + if not self.GetDocument().Close(): + return True + self.Activate(False) + if project: + projectItem = self._GetProjectItem(project) + if projectItem: + self._treeCtrl.Delete(projectItem) + # We don't need to delete the window since it is a floater/embedded + return True + + + def _GetParentFrame(self): + return wx.GetTopLevelParent(self.GetFrame()) + + + def OnUpdate(self, sender = None, hint = None): + wx.lib.docview.View.OnUpdate(self, sender, hint) + if hint: + if hint[0] == "add": + projectItem = self._GetProjectItem(hint[1]) + files = hint[2] + self._treeCtrl.UnselectAll() + self._treeCtrl.Expand(projectItem) + for file in files: + item = self._treeCtrl.AppendItem(projectItem, os.path.basename(file)) + self._treeCtrl.SetPyData(item, file) + self._treeCtrl.SelectItem(item) + self._treeCtrl.EnsureVisible(item) # wxBug: Doesn't work + self._treeCtrl.SortChildren(projectItem) + elif hint[0] == "remove": + projectItem = self._GetProjectItem(hint[1]) + files = hint[2] + children = self._GetChildItems(projectItem) + for child in children: + if self._GetItemFile(child) in files: + self._treeCtrl.Delete(child) + elif hint[0] == "select": + projectItem = self._GetProjectItem(hint[1]) + files = hint[2] + self._treeCtrl.UnselectAll() + children = self._GetChildItems(projectItem) + for child in children: + if self._GetItemFile(child) in files: + self._treeCtrl.SelectItem(child) + self._treeCtrl.EnsureVisible(child) # wxBug: Doesn't work + elif hint[0] == "rename": + projectItem = self._GetProjectItem(hint[1]) + self._treeCtrl.SetItemText(projectItem, os.path.basename(hint[2])) + + + def ProcessEvent(self, event): + id = event.GetId() + if id == ProjectService.ADD_FILES_TO_PROJECT_ID: + self.OnAddFileToProject(event) + return True + elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID: + return False # Implement this one in the service + elif id == ProjectService.RENAME_ID: + self.OnRename(event) + return True + elif id == wx.ID_CUT: + self.OnCut(event) + return True + elif id == wx.ID_COPY: + self.OnCopy(event) + return True + elif id == wx.ID_PASTE: + self.OnPaste(event) + return True + elif id == wx.ID_CLEAR or id == ProjectService.REMOVE_FROM_PROJECT: + self.OnClear(event) + return True + elif id == wx.ID_SELECTALL: + self.OnSelectAll(event) + return True + elif id == ProjectService.OPEN_SELECTION_ID: + self.OnOpenSelection(event) + return True + elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID: + self.OnProperties(event) + return True + else: + return False + + def ProcessUpdateUIEvent(self, event): + # Hack: The edit menu is not being set for projects that are preloaded at startup, so make sure it is OK here + if self._checkEditMenu: + doc = self.GetDocument() + if doc and not doc.GetCommandProcessor().GetEditMenu(): + doc.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame())) + self._checkEditMenu = False + id = event.GetId() + if id == ProjectService.ADD_FILES_TO_PROJECT_ID: + event.Enable(self._HasProjectsSelected() or self._HasFilesSelected()) + return True + elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID: + event.Enable(False) + return True + elif id == ProjectService.RENAME_ID: + event.Enable(self._HasFilesSelected() or self._HasProjectsSelected()) + return True + elif id == wx.ID_CUT: + event.Enable(self._AreSelectedItemsFromSameProject()) + return True + elif id == wx.ID_COPY: + event.Enable(self._HasFilesSelected()) + return True + elif id == wx.ID_PASTE: + event.Enable(self.CanPaste()) + return True + elif id == wx.ID_CLEAR or id == ProjectService.REMOVE_FROM_PROJECT: + event.Enable(self._AreSelectedItemsFromSameProject()) + return True + elif id == wx.ID_SELECTALL: + event.Enable(self._HasFiles()) + return True + elif id == ProjectService.OPEN_SELECTION_ID: + event.Enable(self._HasFilesSelected()) + return True + elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID: + event.Enable(self._HasProjectsSelected() or self._HasFilesSelected()) + return True + else: + return False + + #---------------------------------------------------------------------------- + # Display Methods + #---------------------------------------------------------------------------- + + def IsShown(self): + if not self.GetFrame(): + return False + return self.GetFrame().IsShown() + + + def Hide(self): + self.Show(False) + + + def Show(self, show = True): + self.GetFrame().Show(show) + if wx.GetApp().IsMDI(): + mdiParentFrame = wx.GetApp().GetTopWindow() + mdiParentFrame.ShowEmbeddedWindow(self.GetFrame(), show) + + + #---------------------------------------------------------------------------- + # Methods for ProjectDocument and ProjectService to call + #---------------------------------------------------------------------------- + + def SetExpandedProjects(self, expandedProjects): + self._treeCtrl.UnselectAll() + firstItem = None + for i, item in enumerate(self._GetChildItems(self._treeCtrl.GetRootItem())): + if i == 0: + firstItem = item + if expandedProjects[i]: + self._treeCtrl.Expand(item) + else: + self._treeCtrl.Collapse(item) + # wxBug: This causes a crash, tried using ScrollTo which crashed as well. Then tried calling it with wx.CallAfter and that crashed as well, with both EnsureVisible and ScrollTo + # self._treeCtrl.EnsureVisible(self._treeCtrl.GetRootItem()) + # So doing the following massive hack which forces the treectrl to scroll up to the top item + if firstItem: + if expandedProjects[i]: + self._treeCtrl.Collapse(firstItem) + self._treeCtrl.Expand(firstItem) + else: + self._treeCtrl.Expand(firstItem) + self._treeCtrl.Collapse(firstItem) + + def GetSelectedFile(self): + for item in self._treeCtrl.GetSelections(): + return self._GetItemFile(item) + + def AddProjectToView(self, document): + rootItem = self._treeCtrl.GetRootItem() + projectItem = self._treeCtrl.AppendItem(rootItem, self._MakeProjectName(document)) + self._treeCtrl.SetPyData(projectItem, document) + for file in document.GetFiles(): + fileItem = self._treeCtrl.AppendItem(projectItem, os.path.basename(file)) + self._treeCtrl.SetPyData(fileItem, file) + self._treeCtrl.SortChildren(rootItem) + self._treeCtrl.SortChildren(projectItem) + self._treeCtrl.UnselectAll() + self._treeCtrl.Expand(projectItem) + self._treeCtrl.SelectItem(projectItem) + if self._embeddedWindow: + document.GetCommandProcessor().SetEditMenu(wx.GetApp().GetEditMenu(self._GetParentFrame())) + + #---------------------------------------------------------------------------- + # Methods for OutlineService + #---------------------------------------------------------------------------- + def DoLoadOutlineCallback(self, force=False): + """ Project Editor is a special case for the Outline Service. + You need to be able to be active in the Project Manager without clearing + the Outline View. So we make the Project Editor a client of the Outline + Service, but we don't load anything in the Outline View, leaving the + contents of the Outline View alone (e.g. last document's outline view). + """ + pass + + #---------------------------------------------------------------------------- + # Control events + #---------------------------------------------------------------------------- + + def OnProperties(self, event): + items = self._treeCtrl.GetSelections() + if not items: + return + item = items[0] + if self._IsItemProject(item): + projectPropertiesDialog = ProjectPropertiesDialog(wx.GetApp().GetTopWindow(), self._GetItemProject(item).GetFilename()) + if projectPropertiesDialog.ShowModal() == wx.ID_OK: + pass # Handle OK + projectPropertiesDialog.Destroy() + elif self._IsItemFile(item): + filePropertiesService = wx.GetApp().GetService(wx.lib.pydocview.FilePropertiesService) + filePropertiesService.ShowPropertiesDialog(self._GetItemFile(item)) + + + def OnAddFileToProject(self, event): + if wx.Platform == "__WXMSW__" or wx.Platform == "__WXGTK__" or wx.Platform == "__WXMAC__": + allfilter = '' + descr = '' + for temp in self.GetDocumentManager()._templates: + if temp.IsVisible(): + if len(descr) > 0: + descr = descr + _('|') + allfilter = allfilter + _(';') + descr = descr + temp.GetDescription() + _(" (") + temp.GetFileFilter() + _(") |") + temp.GetFileFilter() # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk + allfilter = allfilter + temp.GetFileFilter() + descr = _("All") + _(" (") + allfilter + _(") |") + allfilter + _('|') + descr # spacing is important, make sure there is no space after the "|", it causes a bug on wx_gtk + descr = descr + _("|") + _("Any (*.*) | *.*") + else: + descr = _("*.*") + if True or _WINDOWS: + dialog = wx.FileDialog(self.GetFrame(), _("Add Files"), self._lastDirectory, "", descr, wx.OPEN | wx.HIDE_READONLY | wx.MULTIPLE) + if dialog.ShowModal() != wx.ID_OK: + return + paths = dialog.GetPaths() + dialog.Destroy() + else: + paths = wx.FileSelector(_("Add Files"), self._lastDirectory, "", wildcard = descr, flags = wx.OPEN | wx.HIDE_READONLY | wx.MULTIPLE, parent=self.GetFrame()) + if type(paths) == types.StringType: + paths = [paths] + if len(paths): + self._lastDirectory = wx.lib.docview.PathOnly(paths[0]) + self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), paths)) + self.Activate(True) # after add, should put focus on project editor + + + def DoAddFilesToProject(self, filenames): + # method used by Drag-n-Drop to add files to current Project + self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), filenames)) + + + def DoSelectFiles(self, filenames): + # method used by Drag-n-Drop to select files in current Project + for selection in self._treeCtrl.GetSelections(): + self._treeCtrl.SelectItem(selection, False) + for file in filenames: + item = self._GetFileItem(longFileName=file) + if item: + self._treeCtrl.SelectItem(item, True) + self._treeCtrl.EnsureVisible(item) + + + def DoSelectProject(self, x, y): + # method used by Drag-n-Drop to set current Project based on cursor position + item, flag = self._treeCtrl.HitTest((x,y)) + if not item: + return False + + project = self._GetItemProject(item) + if not project: + return False + + projectItem = self._GetProjectItem(project) + self._treeCtrl.UnselectAll() + self._treeCtrl.SelectItem(projectItem) + return True + + + def OnFocus(self, event): + wx.GetApp().GetDocumentManager().ActivateView(self) + event.Skip() + + + def OnKillFocus(self, event): + # Get the top MDI window and "activate" it since it is already active from the perspective of the MDIParentFrame + # wxBug: Would be preferable to call OnActivate, but have casting problem, so added Activate method to docview.DocMDIChildFrame + if not self._editingSoDontKillFocus: # wxBug: This didn't used to happen, but now when you start to edit an item in a wxTreeCtrl it puts out a KILL_FOCUS event, so we need to detect it + childFrame = wx.GetApp().GetTopWindow().GetActiveChild() + if childFrame: + childFrame.Activate() + event.Skip() + + + def OnRightClick(self, event): + self.Activate(True) + if not self._treeCtrl.GetSelections(): + return + if len(self._treeCtrl.GetSelections()) == 1 and self._IsItemRoot(self._treeCtrl.GetSelections()[0]): + return # Don't do a menu if it's just the root item selected + menu = wx.Menu() + if self._HasFilesSelected(): # Files context + menu.Append(ProjectService.OPEN_SELECTION_ID, _("&Open"), _("Opens the selection")) + menu.Enable(ProjectService.OPEN_SELECTION_ID, True) + wx.EVT_MENU(self._GetParentFrame(), ProjectService.OPEN_SELECTION_ID, self.OnOpenSelection) + itemIDs = [None] + for item in self._treeCtrl.GetSelections(): + if self._IsItemProcessModelFile(item): + itemIDs = [None, ProjectService.RUN_SELECTED_PM_ID, None] + break + else: # Project context + itemIDs = [wx.ID_CLOSE, wx.ID_SAVE, wx.ID_SAVEAS, None] + menuBar = self._GetParentFrame().GetMenuBar() + itemIDs = itemIDs + [wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID, None, ProjectService.ADD_FILES_TO_PROJECT_ID, ProjectService.REMOVE_FROM_PROJECT, None, wx.ID_UNDO, wx.ID_REDO, None, wx.ID_CUT, wx.ID_COPY, wx.ID_PASTE, wx.ID_CLEAR, None, wx.ID_SELECTALL, None, ProjectService.RENAME_ID] + for itemID in itemIDs: + if not itemID: + menu.AppendSeparator() + else: + if itemID == ProjectService.RUN_SELECTED_PM_ID: + menu.Append(ProjectService.RUN_SELECTED_PM_ID, _("Run Process")) + wx.EVT_MENU(self._GetParentFrame(), ProjectService.RUN_SELECTED_PM_ID, self.OnRunSelectedPM) + elif itemID == ProjectService.REMOVE_FROM_PROJECT: + menu.Append(ProjectService.REMOVE_FROM_PROJECT, _("Remove Selected Files from Project")) + wx.EVT_MENU(self._GetParentFrame(), ProjectService.REMOVE_FROM_PROJECT, self.OnClear) + wx.EVT_UPDATE_UI(self._GetParentFrame(), ProjectService.REMOVE_FROM_PROJECT, self._GetParentFrame().ProcessUpdateUIEvent) + else: + item = menuBar.FindItemById(itemID) + if item: + menu.Append(itemID, item.GetLabel()) + self._treeCtrl.PopupMenu(menu, wx.Point(event.GetX(), event.GetY())) + menu.Destroy() + + def OnRunSelectedPM(self, event): + projectService = wx.GetApp().GetService(ProjectService) + projectService.OnRunProcessModel(event, runSelected=True) + + def OnRename(self, event): + if self._treeCtrl.GetSelections(): + self._treeCtrl.EditLabel(self._treeCtrl.GetSelections()[0]) + + + def OnBeginLabelEdit(self, event): + self._editingSoDontKillFocus = True + item = event.GetItem() + if not self._IsItemFile(item) and not self._IsItemProject(item): + event.Veto() + + + def OnEndLabelEdit(self, event): + self._editingSoDontKillFocus = False + item = event.GetItem() + newName = event.GetLabel() + if not newName or (not self._IsItemFile(item) and not self._IsItemProject(item)): + event.Veto() + return + if self._IsItemFile(item): + oldFile = self._GetItemFile(item) + newFile = os.path.join(os.path.split(oldFile)[0], newName) + if not self._GetItemProject(item).GetCommandProcessor().Submit(ProjectRenameFileCommand(self.GetDocument(), oldFile, newFile)): + event.Veto() + return + self._treeCtrl.SortChildren(self._treeCtrl.GetItemParent(self._treeCtrl.GetSelections()[0])) + elif self._IsItemProject(item): + oldFile = self._GetItemProject(item).GetFilename() + newFile = os.path.join(os.path.split(oldFile)[0], newName) + if not self._GetItemProject(item).GetCommandProcessor().Submit(ProjectRenameFileCommand(self.GetDocument(), oldFile, newFile, True)): + event.Veto() + return + self._treeCtrl.SortChildren(self._treeCtrl.GetRootItem()) + + + def CanPaste(self): + # wxBug: Should be able to use IsSupported/IsSupportedFormat here + #fileDataObject = wx.FileDataObject() + #hasFilesInClipboard = wx.TheClipboard.IsSupportedFormat(wx.FileDataObject) + if not wx.TheClipboard.IsOpened(): + if wx.TheClipboard.Open(): + fileDataObject = wx.FileDataObject() + hasFilesInClipboard = wx.TheClipboard.GetData(fileDataObject) + wx.TheClipboard.Close() + else: + hasFilesInClipboard = False + return hasFilesInClipboard + + + def OnCut(self, event): + if self._AreSelectedItemsFromSameProject(): + self.OnCopy(event) + self.OnClear(event) + + + def OnCopy(self, event): + fileDataObject = wx.FileDataObject() + items = self._treeCtrl.GetSelections() + for item in items: + if self._IsItemFile(item): + file = self._treeCtrl.GetPyData(item) + fileDataObject.AddFile(file) + if len(fileDataObject.GetFilenames()) > 0 and wx.TheClipboard.Open(): + wx.TheClipboard.SetData(fileDataObject) + wx.TheClipboard.Close() + + + def OnPaste(self, event): + if wx.TheClipboard.Open(): + fileDataObject = wx.FileDataObject() + if wx.TheClipboard.GetData(fileDataObject): + self.GetDocument().GetCommandProcessor().Submit(ProjectAddFilesCommand(self.GetDocument(), fileDataObject.GetFilenames())) + wx.TheClipboard.Close() + + + def OnClear(self, event): + if self._AreSelectedItemsFromSameProject(): + items = self._treeCtrl.GetSelections() + files = [] + for item in items: + if self._IsItemFile(item): + files.append(self._GetItemFile(item)) + self.GetDocument().GetCommandProcessor().Submit(ProjectRemoveFilesCommand(self._GetItemProject(items[0]), files)) + + + def OnKeyPressed(self, event): + key = event.KeyCode() + if key == wx.WXK_DELETE: + self.OnClear(event) + else: + event.Skip() + + + def OnSelectAll(self, event): + project = self.GetDocument() + if project: + self._treeCtrl.UnselectAll() + for child in self._GetChildItems(self._GetProjectItem(project)): + self._treeCtrl.SelectItem(child) + + + def OnOpenSelectionSDI(self, event): + # Do a call after so that the second mouseclick on a doubleclick doesn't reselect the project window + wx.CallAfter(self.OnOpenSelection, None) + + + def OnOpenSelection(self, event): + doc = None + try: + items = self._treeCtrl.GetSelections() + for item in items: + if self._IsItemFile(item): + filepath = self._GetItemFile(item) + if not os.path.exists(filepath): + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Not Found") + yesNoMsg = wx.MessageDialog(self.GetFrame(), + _("The file '%s' was not found in '%s'.\n\nWould you like to browse for the file?") % (wx.lib.docview.FileNameFromPath(filepath), wx.lib.docview.PathOnly(filepath)), + msgTitle, + wx.YES_NO + ) + if yesNoMsg.ShowModal() == wx.ID_NO: + continue + findFile = wx.FileDialog(self.GetFrame(), + _("Choose a file"), + wx.lib.docview.PathOnly(filepath), + wx.lib.docview.FileNameFromPath(filepath), + style = wx.OPEN + ) + if findFile.ShowModal() == wx.ID_OK and findFile.GetPath(): + newpath = findFile.GetPath() + else: + newpath = None + findFile.Destroy() + if newpath: + # update Project Model with new location + self.GetDocument().RemoveFile(filepath) + self.GetDocument().AddFile(newpath) + filepath = newpath + + doc = self.GetDocumentManager().CreateDocument(filepath, wx.lib.docview.DOC_SILENT) + + except IOError, (code, message): + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Error") + wx.MessageBox("Could not open '%s'." % wx.lib.docview.FileNameFromPath(filepath), + msgTitle, + wx.OK | wx.ICON_EXCLAMATION, + self.GetFrame()) + + + #---------------------------------------------------------------------------- + # Convenience methods + #---------------------------------------------------------------------------- + + def _HasFiles(self): + if not self._treeCtrl: + return False + return self._treeCtrl.GetCount() > 1 # 1 item = root item, don't count as having files + + + def _HasProjectsSelected(self): + if not self._treeCtrl: + return False + items = self._treeCtrl.GetSelections() + if not items: + return False + for item in items: + if self._IsItemProject(item): + return True + return False + + + def _HasFilesSelected(self): + if not self._treeCtrl: + return False + items = self._treeCtrl.GetSelections() + if not items: + return False + for item in items: + if not self._IsItemFile(item): + return False + return True + + + def _MakeProjectName(self, project): + return project.GetPrintableName() + + + # Return the tree item for a project + def _GetProjectItem(self, project): + children = self._GetChildItems(self._treeCtrl.GetRootItem()) + for child in children: + if self._treeCtrl.GetPyData(child) == project: + return child + return None + + + # Returns the project for an item, either for a project item or a file that is part of a project + def _GetItemProject(self, item): + if self._IsItemRoot(item): + return None + if self._IsItemProject(item): + return self._treeCtrl.GetPyData(item) + if self._IsItemFile(item): + return self._treeCtrl.GetPyData(self._treeCtrl.GetItemParent(item)) + return None + + + def _GetItemFile(self, item): + if self._IsItemFile(item): + return self._treeCtrl.GetPyData(item) + else: + return None + + + def _GetFileItem(self, shortFileName = None, longFileName = None): + """ Returns the tree item for a file given the short (display) or long (fullpath) file name. """ + + if shortFileName: + project_children = self._GetChildItems(self._treeCtrl.GetRootItem()) + for child in project_children: + file_children = self._GetChildItems(child) + for file_child in file_children: + if self._treeCtrl.GetItemText(file_child) == shortFileName: + return file_child + return None + else: + project_children = self._GetChildItems(self._treeCtrl.GetRootItem()) + for child in project_children: + file_children = self._GetChildItems(child) + for file_child in file_children: + if self._treeCtrl.GetPyData(file_child) == longFileName: + return file_child + return None + + + def GetFilePathFromTreeName(self, shortFileName): + """ + Returns the data object given a short (display) file name for a file. The data + object should be the full path. + """ + return self._GetItemFile(self._GetFileItem(shortFileName)) + + + def SelectFileInTree(self, shortFileName): + item = self._GetFileItem(shortFileName) + if item: + for selection in self._treeCtrl.GetSelections(): + self._treeCtrl.SelectItem(selection, False) + self._treeCtrl.SelectItem(item, True) + self._treeCtrl.EnsureVisible(item) + + + def _IsItemRoot(self, item): + return item == self._treeCtrl.GetRootItem() + + + def _IsItemProject(self, item): + return isinstance(self._treeCtrl.GetPyData(item), ProjectDocument) + + + def _IsItemFile(self, item): + return isinstance(self._treeCtrl.GetPyData(item), types.StringTypes) + + + def _IsItemProcessModelFile(self, item): + if ACTIVEGRID_BASE_IDE: + return False + + if isinstance(self._treeCtrl.GetPyData(item), types.StringTypes): + filename = self._treeCtrl.GetPyData(item) + ext = None + for template in self.GetDocumentManager().GetTemplates(): + if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument: + ext = template.GetDefaultExtension() + break; + if not ext: + return False + + if filename.endswith(ext): + return True + + return False + + + def _AreSelectedItemsFromSameProject(self): + if not self._treeCtrl: + return False + items = self._treeCtrl.GetSelections() + if not items: + return False + project = self._GetItemProject(items[0]) + if project == None: + return False + for item in items: + if not self._IsItemFile(item): + return False + if self._GetItemProject(item) != project: + return False + return True + + + def _GetChildItems(self, parentItem): + children = [] + (child, cookie) = self._treeCtrl.GetFirstChild(parentItem) + while child.IsOk(): + children.append(child) + (child, cookie) = self._treeCtrl.GetNextChild(parentItem, cookie) + return children + + + +class ProjectFileDropTarget(wx.FileDropTarget): + + def __init__(self, view): + wx.FileDropTarget.__init__(self) + self._view = view + + + def OnDropFiles(self, x, y, filenames): + if self._view.DoSelectProject(x, y): + self._view.DoAddFilesToProject(filenames) + self._view.DoSelectFiles(filenames) + return True + return False + + + def OnDragOver(self, x, y, default): + if self._view.DoSelectProject(x,y): + return wx.DragCopy + return wx.DragNone + + +class ProjectPropertiesDialog(wx.Dialog): + + + def __init__(self, parent, filename): + wx.Dialog.__init__(self, parent, -1, _("Project Properties"), size = (310, 330)) + + HALF_SPACE = 5 + SPACE = 10 + + filePropertiesService = wx.GetApp().GetService(wx.lib.pydocview.FilePropertiesService) + + notebook = wx.Notebook(self, -1) + tab = wx.Panel(notebook, -1) + + gridSizer = RowColSizer() + + gridSizer.Add(wx.StaticText(tab, -1, _("Filename:")), flag=wx.RIGHT, border=HALF_SPACE, row=0, col=0) + if os.path.isfile(filename): + gridSizer.Add(wx.StaticText(tab, -1, os.path.split(filename)[1]), row=0, col=1) + + gridSizer.Add(wx.StaticText(tab, -1, _("Location:")), flag=wx.RIGHT, border=HALF_SPACE, row=1, col=0) + gridSizer.Add(wx.StaticText(tab, -1, filePropertiesService.chopPath(os.path.split(filename)[0])), flag=wx.BOTTOM, border=SPACE, row=1, col=1) + + gridSizer.Add(wx.StaticText(tab, -1, _("Size:")), flag=wx.RIGHT, border=HALF_SPACE, row=2, col=0) + gridSizer.Add(wx.StaticText(tab, -1, str(os.path.getsize(filename)) + ' ' + _("bytes")), row=2, col=1) + + lineSizer = wx.BoxSizer(wx.VERTICAL) # let the line expand horizontally without vertical expansion + lineSizer.Add(wx.StaticLine(tab, -1, size = (10,-1)), 0, wx.EXPAND) + gridSizer.Add(lineSizer, flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.TOP, border=HALF_SPACE, row=3, col=0, colspan=2) + + gridSizer.Add(wx.StaticText(tab, -1, _("Created:")), flag=wx.RIGHT, border=HALF_SPACE, row=4, col=0) + gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getctime(filename))), row=4, col=1) + + gridSizer.Add(wx.StaticText(tab, -1, _("Modified:")), flag=wx.RIGHT, border=HALF_SPACE, row=5, col=0) + gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getmtime(filename))), row=5, col=1) + + gridSizer.Add(wx.StaticText(tab, -1, _("Accessed:")), flag=wx.RIGHT, border=HALF_SPACE, row=6, col=0) + gridSizer.Add(wx.StaticText(tab, -1, time.ctime(os.path.getatime(filename))), row=6, col=1) + + else: + gridSizer.Add(wx.StaticText(tab, -1, os.path.split(filename)[1] + ' ' + _("[new project]")), row=0, col=1) + + # add a border around the inside of the tab + spacerGrid = wx.BoxSizer(wx.VERTICAL) + spacerGrid.Add(gridSizer, 0, wx.ALL, SPACE); + tab.SetSizer(spacerGrid) + notebook.AddPage(tab, _("General")) + if wx.Platform == "__WXMSW__": + notebook.SetPageSize((310,200)) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(notebook, 0, wx.ALL | wx.EXPAND, SPACE) + sizer.Add(self.CreateButtonSizer(wx.OK), 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, HALF_SPACE) + + sizer.Fit(self) + self.SetDimensions(-1, -1, 310, -1, wx.SIZE_USE_EXISTING) + self.SetSizer(sizer) + self.Layout() + + +class ProjectOptionsPanel(wx.Panel): + + + def __init__(self, parent, id): + wx.Panel.__init__(self, parent, id) + self._useSashMessageShown = False + SPACE = 10 + HALF_SPACE = 5 + config = wx.ConfigBase_Get() + self._projSaveDocsCheckBox = wx.CheckBox(self, -1, _("Remember open projects")) + self._projSaveDocsCheckBox.SetValue(config.ReadInt("ProjectSaveDocs", True)) + projectBorderSizer = wx.BoxSizer(wx.VERTICAL) + projectSizer = wx.BoxSizer(wx.VERTICAL) + projectSizer.Add(self._projSaveDocsCheckBox, 0, wx.ALL, HALF_SPACE) + self._projShowWelcomeCheckBox = wx.CheckBox(self, -1, _("Show Welcome Dialog")) + self._projShowWelcomeCheckBox.SetValue(config.ReadInt("RunWelcomeDialog", True)) + projectSizer.Add(self._projShowWelcomeCheckBox, 0, wx.ALL, HALF_SPACE) + projectBorderSizer.Add(projectSizer, 0, wx.ALL, SPACE) + self.SetSizer(projectBorderSizer) + self.Layout() + parent.AddPage(self, _("Project")) + + def OnUseSashSelect(self, event): + if not self._useSashMessageShown: + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("Document Options") + wx.MessageBox("Project window embedded mode changes will not appear until the application is restarted.", + msgTitle, + wx.OK | wx.ICON_INFORMATION, + self.GetParent()) + self._useSashMessageShown = True + + + def OnOK(self, optionsDialog): + config = wx.ConfigBase_Get() + config.WriteInt("ProjectSaveDocs", self._projSaveDocsCheckBox.GetValue()) + config.WriteInt("RunWelcomeDialog", self._projShowWelcomeCheckBox.GetValue()) + + +class ProjectService(Service.Service): + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service + RUNPM_ID = wx.NewId() + RUN_SELECTED_PM_ID = wx.NewId() + RUN_CURRENT_PM_ID = wx.NewId() + ADD_FILES_TO_PROJECT_ID = wx.NewId() + ADD_CURRENT_FILE_TO_PROJECT_ID = wx.NewId() + RENAME_ID = wx.NewId() + OPEN_SELECTION_ID = wx.NewId() + REMOVE_FROM_PROJECT = wx.NewId() + + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_LEFT): + Service.Service.__init__(self, serviceName, embeddedWindowLocation) + self._runHandlers = [] + self._suppressOpenProjectMessages = False + + + def _CreateView(self): + return ProjectView(self) + + + def ShowWindow(self, show = True): + """ Force showing of saved projects on opening, otherwise empty Project Window is disconcerting for user """ + Service.Service.ShowWindow(self, show) + + if show: + project = self.GetView().GetDocument() + if not project: + self.OpenSavedProjects() + + + #---------------------------------------------------------------------------- + # Service specific methods + #---------------------------------------------------------------------------- + + def GetSuppressOpenProjectMessages(self): + return self._suppressOpenProjectMessages + + + def SetSuppressOpenProjectMessages(self, suppressOpenProjectMessages): + self._suppressOpenProjectMessages = suppressOpenProjectMessages + + + def GetRunHandlers(self): + return self._runHandlers + + + def AddRunHandler(self, runHandler): + self._runHandlers.append(runHandler) + + + def RemoveRunHandler(self, runHandler): + self._runHandlers.remove(runHandler) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document) + + config = wx.ConfigBase_Get() + + projectMenu = wx.Menu() + +## accelTable = wx.AcceleratorTable([ +## eval(_("wx.ACCEL_CTRL, ord('R'), ProjectService.RUN_ID")) +## ]) +## frame.SetAcceleratorTable(accelTable) + isProjectDocument = document and document.GetDocumentTemplate().GetDocumentType() == ProjectDocument + if wx.GetApp().IsMDI() or isProjectDocument: + if not menuBar.FindItemById(ProjectService.ADD_FILES_TO_PROJECT_ID): + projectMenu.Append(ProjectService.ADD_FILES_TO_PROJECT_ID, _("&Add Files to Project..."), _("Adds a document to the current project")) + wx.EVT_MENU(frame, ProjectService.ADD_FILES_TO_PROJECT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ProjectService.ADD_FILES_TO_PROJECT_ID, frame.ProcessUpdateUIEvent) + if not menuBar.FindItemById(ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID): + projectMenu.Append(ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, _("&Add Active File to Project..."), _("Adds the active document to a project")) + wx.EVT_MENU(frame, ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID, frame.ProcessUpdateUIEvent) + viewMenuIndex = menuBar.FindMenu(_("&View")) + menuBar.Insert(viewMenuIndex + 1, projectMenu, _("&Project")) + editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit"))) + if not menuBar.FindItemById(ProjectService.RENAME_ID): + editMenu.AppendSeparator() + editMenu.Append(ProjectService.RENAME_ID, _("&Rename"), _("Renames the active item")) + wx.EVT_MENU(frame, ProjectService.RENAME_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ProjectService.RENAME_ID, frame.ProcessUpdateUIEvent) + + return True + + + def OnCloseFrame(self, event): + if not self.GetView(): + return True + + if wx.GetApp().IsMDI(): + # close all non-project documents first + for document in self.GetDocumentManager().GetDocuments()[:]: # Cloning list to make sure we go through all docs even as they are deleted + if document.GetDocumentTemplate().GetDocumentType() != ProjectDocument: + if not self.GetDocumentManager().CloseDocument(document, False): + return False + + # write project config afterwards because user may change filenames on closing of new documents + self.GetView().WriteProjectConfig() # Called onCloseWindow in all of the other services but needed to be factored out for ProjectService since it is called elsewhere + + # close all project documents after closing other documents + # because user may save a new document with a new name or cancel closing a document + for document in self.GetDocumentManager().GetDocuments()[:]: # Cloning list to make sure we go through all docs even as they are deleted + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + if not document.OnSaveModified(): + return False + + # This is called when any SDI frame is closed, so need to check if message window is closing or some other window + elif self.GetView() == event.GetEventObject().GetView(): + self.SetView(None) + return True + + + #---------------------------------------------------------------------------- + # Event Processing Methods + #---------------------------------------------------------------------------- + + def ProcessEventBeforeWindows(self, event): + id = event.GetId() + if id == wx.ID_CLOSE_ALL: + self.OnFileCloseAll(event) + return True + return False + + + def ProcessEvent(self, event): + if Service.Service.ProcessEvent(self, event): + return True + + id = event.GetId() + if id == ProjectService.RUN_SELECTED_PM_ID: + self.OnRunProcessModel(event, runSelected=True) + return True + elif id == ProjectService.RUN_CURRENT_PM_ID: + self.OnRunProcessModel(event, runCurrentFile=True) + return True + elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID: + self.OnAddCurrentFileToProject(event) + return True + elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID: + if self.GetView(): + return self.GetView().ProcessEvent(event) + else: + return False + else: + return False + + + def ProcessUpdateUIEvent(self, event): + if Service.Service.ProcessUpdateUIEvent(self, event): + return True + + id = event.GetId() + if id == ProjectService.RUNPM_ID or id == ProjectService.RUN_SELECTED_PM_ID or id == ProjectService.RUN_CURRENT_PM_ID: + event.Enable(self._HasOpenedProjects() and self._HasProcessModel()) + return True + elif id == ProjectService.ADD_FILES_TO_PROJECT_ID: + event.Enable(False) + return True + elif id == ProjectService.ADD_CURRENT_FILE_TO_PROJECT_ID: + event.Enable(self._CanAddCurrentFileToProject()) + return True + elif id == ProjectService.RENAME_ID: + event.Enable(False) + return True + elif id == ProjectService.OPEN_SELECTION_ID: + event.Enable(False) + return True + elif id == wx.lib.pydocview.FilePropertiesService.PROPERTIES_ID: + if self.GetView(): + return self.GetView().ProcessUpdateUIEvent(event) + else: + return False + else: + return False + + + def OnRunProcessModel(self, event, runSelected=False, runCurrentFile=False): + project = self.GetView().GetDocument() + + if project: + ext = None + for template in self.GetDocumentManager().GetTemplates(): + if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument: + ext = template.GetDefaultExtension() + break; + if not ext: + return + + files = filter(lambda f: f.endswith(ext), project.GetFiles()) + if not files: + return + + docs = wx.GetApp().GetDocumentManager().GetDocuments() + for doc in docs: + if doc.GetFilename() in files and doc.GetDocumentTemplate().GetDocumentType() == ProcessModelEditor.ProcessModelDocument: + if not doc.GetProcessModel().beginProcess: + wx.MessageBox(_("Cannot run process. No begin action found."), _("Run Process")) + return + + filesModified = False + for doc in docs: + if doc.IsModified(): + filesModified = True + break + if filesModified: + frame = self.GetView().GetFrame() + yesNoMsg = wx.MessageDialog(frame, + _("Files have been modified. Process may not reflect your current changes.\n\nWould you like to save all files before running?"), + _("Run Process"), + wx.YES_NO + ) + if yesNoMsg.ShowModal() == wx.ID_YES: + wx.GetTopLevelParent(frame).OnFileSaveAll(None) + + if runCurrentFile: + fileToRun = self.GetDocumentManager().GetCurrentDocument().GetFilename() + elif runSelected: + fileToRun = self.GetView().GetSelectedFile() + elif len(files) > 1: + files.sort(lambda a, b: cmp(os.path.basename(a).lower(), os.path.basename(b).lower())) + strings = map(lambda file: os.path.basename(file), files) + res = wx.GetSingleChoiceIndex(_("Select a process to run:"), + _("Run"), + strings, + project.GetFirstView()._GetParentFrame()) + if res == -1: + return + fileToRun = files[res] + else: + fileToRun = files[0] + + self.RunProcessModel(fileToRun) + + + def RunProcessModel(self, fileToRun): + for runHandler in self.GetRunHandlers(): + if runHandler.RunProjectFile(fileToRun): + return + os.system('"' + fileToRun + '"') + + + def _HasProcessModel(self): + project = self.GetView().GetDocument() + + if project: + ext = None + for template in self.GetDocumentManager().GetTemplates(): + if template.GetDocumentType() == ProcessModelEditor.ProcessModelDocument: + ext = template.GetDefaultExtension() + break; + if not ext: + return False + + files = filter(lambda f: f.endswith(ext), project.GetFiles()) + if not files: + return False + + if len(files): + return True + + return False + + + def _HasOpenedProjects(self): + for document in self.GetDocumentManager().GetDocuments(): + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + return True + return False + + + def _HasCurrentFile(self): + currentDoc = self.GetDocumentManager().GetCurrentDocument() + return currentDoc + + + def _CanAddCurrentFileToProject(self): + currentDoc = self.GetDocumentManager().GetCurrentDocument() + if not currentDoc: + return False + if currentDoc.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + return False + if not currentDoc._savedYet: + return False + for document in self.GetDocumentManager().GetDocuments(): + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + return True + return False # There are no documents open + + + def GetFilesFromCurrentProject(self): + view = self.GetView() + if view: + project = view.GetDocument() + if project: + return project.GetFiles() + return None + + + def GetCurrentProject(self): + view = self.GetView() + if view: + return view.GetDocument() + return None + + + def FindProjectByFile(self, filename): + for document in self.GetDocumentManager().GetDocuments(): + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + if document.GetFilename() == filename: + return document + elif document.IsFileInProject(filename): + return document + return None + + + def GetCurrentProjectNames(self): + projects = [] + for document in self.GetDocumentManager().GetDocuments(): + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + projects.append(document) + if not projects: + return + projects.sort(lambda a, b: cmp(a.GetPrintableName().lower(), b.GetPrintableName().lower())) + strings = map(lambda project: project.GetPrintableName(), projects) + return strings + + def OnAddCurrentFileToProject(self, event): + if not self._CanAddCurrentFileToProject(): + return + projects = [] + for document in self.GetDocumentManager().GetDocuments(): + if document.GetDocumentTemplate().GetDocumentType() == ProjectDocument: + projects.append(document) + if not projects: + return + projects.sort(lambda a, b: cmp(a.GetPrintableName().lower(), b.GetPrintableName().lower())) + strings = map(lambda project: project.GetPrintableName(), projects) + res = wx.GetSingleChoiceIndex(_("Select a project to add the file to:"), + _("Add to Project"), + strings, + self.GetDocumentManager().FindSuitableParent()) + if res == -1: + return + file = self.GetDocumentManager().GetCurrentDocument().GetFilename() + projects[res].GetCommandProcessor().Submit(ProjectAddFilesCommand(projects[res], [file])) + self.GetView().Activate(True) # after add, should put focus on project editor + + + def OnFileCloseAll(self, event): + for document in self.GetDocumentManager().GetDocuments()[:]: # Cloning list to make sure we go through all docs even as they are deleted + if document.GetDocumentTemplate().GetDocumentType() != ProjectDocument: + if not self.GetDocumentManager().CloseDocument(document, False): + return + # document.DeleteAllViews() # Implicitly delete the document when the last view is removed + + + def OpenSavedProjects(self): + config = wx.ConfigBase_Get() + openedDocs = False + if config.ReadInt("ProjectSaveDocs", True): + docString = config.Read("ProjectSavedDocs") + if docString: + doc = None + for fileName in eval(docString): + if isinstance(fileName, types.StringTypes): + if os.path.exists(fileName): + doc = self.GetDocumentManager().CreateDocument(fileName, wx.lib.docview.DOC_SILENT) + + if doc: + openedDocs = True + expandedString = config.Read("ProjectExpandedSavedDocs") + if expandedString: + view = doc.GetFirstView() + view.SetExpandedProjects(eval(expandedString)) + return openedDocs + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getProjectData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00[IDAT8\x8d\xc5\x93\xc1\n\xc00\x08C\x8d\xf6\xff\xffX\xb3Sa-\xf6`;:O\n\ +\x12\x1fj\x0059\t\xed\t\xc3\xc9pn\x0b\x88\x88@\rU\x81\xf6.\x18N\xa8aE\x92\rh\ +YC\x85\xa4D\x90\x91\xdc%\xf8w\x07+\xd1\xfbW\x98\xc5\x8f\t\x86W\xee\x93+\xbe\ +\xc0gn\xdc\x8d\x07\xab"
\xf9(3zH\x1e\xfb\x00\x00\x00\x00IEND\xaeB`\x82" + + +def getBlankBitmap(): + return BitmapFromImage(getBlankImage()) + +def getBlankImage(): + stream = cStringIO.StringIO(getBlankData()) + return ImageFromStream(stream) + +def getBlankIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getBlankBitmap()) + return icon + diff --git a/wxPython/samples/ide/activegrid/tool/PythonEditor.py b/wxPython/samples/ide/activegrid/tool/PythonEditor.py new file mode 100644 index 0000000000..06236d126c --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/PythonEditor.py @@ -0,0 +1,614 @@ +#---------------------------------------------------------------------------- +# Name: PythonEditor.py +# Purpose: PythonEditor for wx.lib.pydocview tbat uses the Styled Text Control +# +# Author: Peter Yared +# +# Created: 8/15/03 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import CodeEditor +import wx +import wx.lib.docview +import wx.lib.pydocview +import string +import keyword # So it knows what to hilite +import wx.py # For the Python interpreter +import wx.stc # For the Python interpreter +import cStringIO # For indent +import OutlineService +import STCTextEditor +import keyword # for GetAutoCompleteKeywordList +import sys # for GetAutoCompleteKeywordList +import MessageService # for OnCheckCode +import OutlineService +try: + import checker # for pychecker + _CHECKER_INSTALLED = True +except ImportError: + _CHECKER_INSTALLED = False +import os.path # for pychecker +_ = wx.GetTranslation + +if wx.Platform == '__WXMSW__': + _WINDOWS = True +else: + _WINDOWS = False + + +VIEW_PYTHON_INTERPRETER_ID = wx.NewId() + + +class PythonDocument(CodeEditor.CodeDocument): + pass + + +class PythonView(CodeEditor.CodeView): + + + def ProcessUpdateUIEvent(self, event): + if not self.GetCtrl(): + return False + + id = event.GetId() + if id == CodeEditor.CHECK_CODE_ID: + hasText = self.GetCtrl().GetTextLength() > 0 + event.Enable(hasText) + return True + + return CodeEditor.CodeView.ProcessUpdateUIEvent(self, event) + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return PythonCtrl + + + def OnActivateView(self, activate, activeView, deactiveView): + STCTextEditor.TextView.OnActivateView(self, activate, activeView, deactiveView) + if activate: + wx.CallAfter(self.LoadOutline) # need CallAfter because document isn't loaded yet + + + def OnClose(self, deleteWindow = True): + status = STCTextEditor.TextView.OnClose(self, deleteWindow) + wx.CallAfter(self.ClearOutline) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter + return status + + + def GetAutoCompleteKeywordList(self, context, hint): + obj = None + try: + if context and len(context): + obj = eval(context, globals(), locals()) + except: + if not hint or len(hint) == 0: # context isn't valid, maybe it was the hint + hint = context + + if obj is None: + kw = keyword.kwlist[:] + else: + symTbl = dir(obj) + kw = filter(lambda item: item[0] != '_', symTbl) # remove local variables and methods + + if hint and len(hint): + lowerHint = hint.lower() + filterkw = filter(lambda item: item.lower().startswith(lowerHint), kw) # remove variables and methods that don't match hint + kw = filterkw + + kw.sort(self.CaseInsensitiveCompare) + + if hint: + replaceLen = len(hint) + else: + replaceLen = 0 + + return " ".join(kw), replaceLen + + + def OnCheckCode(self): + if not _CHECKER_INSTALLED: + wx.MessageBox(_("pychecker not found. Please install pychecker."), _("Check Code")) + return + + filename = os.path.basename(self.GetDocument().GetFilename()) + + # pychecker only works on files, doesn't take a stream or string input + if self.GetDocument().IsModified(): + dlg = wx.MessageDialog(self.GetFrame(), _("'%s' has been modfied and must be saved first. Save file and check code?") % filename, _("Check Code")) + val = dlg.ShowModal() + dlg.Destroy() + if val == wx.ID_OK: + self.GetDocument().Save() + else: + return + + messageService = wx.GetApp().GetService(MessageService.MessageService) + messageService.ShowWindow() + view = messageService.GetView() + if not view: + return + + view.ClearLines() + view.SetCallback(self.OnJumpToFoundLine) + + # Set cursor to Wait cursor + wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_WAIT)) + + # This takes a while for involved code + checker.checkSyntax(self.GetDocument().GetFilename(), view) + + # Set cursor to Default cursor + wx.GetApp().GetTopWindow().SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) + + + def OnJumpToFoundLine(self, event): + messageService = wx.GetApp().GetService(MessageService.MessageService) + lineText, pos = messageService.GetView().GetCurrLine() + + lineEnd = lineText.find(".py:") + if lineEnd == -1: + return + + lineStart = lineEnd + len(".py:") + lineEnd = lineText.find(":", lineStart) + lineNum = int(lineText[lineStart:lineEnd]) + + filename = lineText[0:lineStart - 1] + + foundView = None + openDocs = wx.GetApp().GetDocumentManager().GetDocuments() + for openDoc in openDocs: + if openDoc.GetFilename() == filename: + foundView = openDoc.GetFirstView() + break + + if not foundView: + doc = wx.GetApp().GetDocumentManager().CreateDocument(filename, wx.lib.docview.DOC_SILENT) + foundView = doc.GetFirstView() + + if foundView: + foundView.GetFrame().SetFocus() + foundView.Activate() + foundView.GotoLine(lineNum) + startPos = foundView.PositionFromLine(lineNum) + endPos = foundView.GetLineEndPosition(lineNum) + # wxBug: Need to select in reverse order, (end, start) to put cursor at head of line so positioning is correct + # Also, if we use the correct positioning order (start, end), somehow, when we open a edit window for the first + # time, we don't see the selection, it is scrolled off screen + foundView.SetSelection(endPos, startPos) + wx.GetApp().GetService(OutlineService.OutlineService).LoadOutline(foundView, position=startPos) + + + +class PythonInterpreterView(wx.lib.docview.View): + + + def OnCreate(self, doc, flags): + frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) + sizer = wx.BoxSizer() + self._pyCrust = wx.py.crust.Crust(frame) + sizer.Add(self._pyCrust, 1, wx.EXPAND, 0) + frame.SetSizer(sizer) + frame.Layout() + self.Activate() + frame.Show() + return True + + + def ProcessEvent(self, event): + if not hasattr(self, "_pyCrust") or not self._pyCrust: + return wx.lib.docview.View.ProcessEvent(self, event) + stcControl = wx.Window_FindFocus() + if not isinstance(stcControl, wx.stc.StyledTextCtrl): + return wx.lib.docview.View.ProcessEvent(self, event) + id = event.GetId() + if id == wx.ID_UNDO: + stcControl.Undo() + return True + elif id == wx.ID_REDO: + stcControl.Redo() + return True + elif id == wx.ID_CUT: + stcControl.Cut() + return True + elif id == wx.ID_COPY: + stcControl.Copy() + return True + elif id == wx.ID_PASTE: + stcControl.Paste() + return True + elif id == wx.ID_CLEAR: + stcControl.Clear() + return True + elif id == wx.ID_SELECTALL: + stcControl.SetSelection(0, -1) + return True + else: + return wx.lib.docview.View.ProcessEvent(self, event) + + + def ProcessUpdateUIEvent(self, event): + if not hasattr(self, "_pyCrust") or not self._pyCrust: + return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) + stcControl = wx.Window_FindFocus() + if not isinstance(stcControl, wx.stc.StyledTextCtrl): + return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) + id = event.GetId() + if id == wx.ID_UNDO: + event.Enable(stcControl.CanUndo()) + return True + elif id == wx.ID_REDO: + event.Enable(stcControl.CanRedo()) + return True + elif id == wx.ID_CUT: + event.Enable(stcControl.CanCut()) + return True + elif id == wx.ID_COPY: + event.Enable(stcControl.CanCopy()) + return True + elif id == wx.ID_PASTE: + event.Enable(stcControl.CanPaste()) + return True + elif id == wx.ID_CLEAR: + event.Enable(True) # wxBug: should be stcControl.CanCut()) but disabling clear item means del key doesn't work in control as expected + return True + elif id == wx.ID_SELECTALL: + event.Enable(stcControl.GetTextLength() > 0) + return True + else: + return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) + + + def OnClose(self, deleteWindow=True): + if deleteWindow and self.GetFrame(): + self.GetFrame().Destroy() + return True + + +class PythonService(CodeEditor.CodeService): + + + def __init__(self): + CodeEditor.CodeService.__init__(self) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + CodeEditor.CodeService.InstallControls(self, frame, menuBar, toolBar, statusBar, document) + + if document and document.GetDocumentTemplate().GetDocumentType() != PythonDocument: + return + if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + return + + viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) + + viewStatusBarItemPos = self.GetMenuItemPos(viewMenu, wx.lib.pydocview.VIEW_STATUSBAR_ID) + viewMenu.InsertCheckItem(viewStatusBarItemPos + 1, VIEW_PYTHON_INTERPRETER_ID, _("Python &Interpreter"), _("Shows or hides the Python interactive window")) + wx.EVT_MENU(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_PYTHON_INTERPRETER_ID, frame.ProcessUpdateUIEvent) + + + def ProcessEvent(self, event): + id = event.GetId() + if id == VIEW_PYTHON_INTERPRETER_ID: + self.OnViewPythonInterpreter(event) + return True + else: + return CodeEditor.CodeService.ProcessEvent(self, event) + + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == VIEW_PYTHON_INTERPRETER_ID: + event.Enable(True) + docManager = wx.GetApp().GetDocumentManager() + event.Check(False) + for doc in docManager.GetDocuments(): + if isinstance(doc.GetFirstView(), PythonInterpreterView): + event.Check(True) + break + return True + else: + return CodeEditor.CodeService.ProcessUpdateUIEvent(self, event) + + + def OnViewPythonInterpreter(self, event): + for doc in wx.GetApp().GetDocumentManager().GetDocuments(): + if isinstance(doc.GetFirstView(), PythonInterpreterView): + doc.GetFirstView().GetDocument().DeleteAllViews() + return + + docManager = self.GetDocumentManager() + template = wx.lib.docview.DocTemplate(docManager, + _("Python Interpreter"), + "*.Foobar", + "Foobar", + ".Foobar", + _("Python Interpreter Document"), + _("Python Interpreter View"), + wx.lib.docview.Document, + PythonInterpreterView, + flags = wx.lib.docview.TEMPLATE_INVISIBLE) + newDoc = template.CreateDocument('', wx.lib.docview.DOC_SILENT) + if newDoc: + newDoc.SetDocumentName(template.GetDocumentName()) + newDoc.SetDocumentTemplate(template) + newDoc.OnNewDocument() + newDoc.SetWriteable(False) + newDoc.GetFirstView().GetFrame().SetTitle(_("Python Interpreter")) + + +class PythonCtrl(CodeEditor.CodeCtrl): + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + CodeEditor.CodeCtrl.__init__(self, parent, ID, style) + self.SetProperty("tab.timmy.whinge.level", "1") + self.SetProperty("fold.comment.python", "1") + self.SetProperty("fold.quotes.python", "1") + self.SetLexer(wx.stc.STC_LEX_PYTHON) + self.SetKeyWords(0, string.join(keyword.kwlist)) + + + def SetViewDefaults(self): + CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Python", hasWordWrap = False, hasTabs = True) + + + def GetFontAndColorFromConfig(self): + return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Python") + + + def UpdateStyles(self): + CodeEditor.CodeCtrl.UpdateStyles(self) + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + # Python styles + # White space + self.StyleSetSpec(wx.stc.STC_P_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + # Comment + self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + # Number + self.StyleSetSpec(wx.stc.STC_P_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + # String + self.StyleSetSpec(wx.stc.STC_P_STRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + # Single quoted string + self.StyleSetSpec(wx.stc.STC_P_CHARACTER, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + # Keyword + self.StyleSetSpec(wx.stc.STC_P_WORD, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + # Triple quotes + self.StyleSetSpec(wx.stc.STC_P_TRIPLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces) + # Triple double quotes + self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, "face:%(font)s,fore:#7F0000,size:%(size)d" % faces) + # Class name definition + self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, "face:%(font)s,fore:#0000FF,bold,size:%(size)d" % faces) + # Function or method name definition + self.StyleSetSpec(wx.stc.STC_P_DEFNAME, "face:%(font)s,fore:#007F7F,bold,size:%(size)d" % faces) + # Operators + self.StyleSetSpec(wx.stc.STC_P_OPERATOR, "face:%(font)s,size:%(size)d" % faces) + # Identifiers + self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, "face:%(font)s,fore:#%(color)s,face:%(font)s,size:%(size)d" % faces) + # Comment-blocks + self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, "face:%(font)s,fore:#7F7F7F,size:%(size)d" % faces) + # End of line where string is not closed + self.StyleSetSpec(wx.stc.STC_P_STRINGEOL, "face:%(font)s,fore:#000000,face:%(font)s,back:#E0C0E0,eol,size:%(size)d" % faces) + + + def OnUpdateUI(self, evt): + braces = self.GetMatchingBraces() + + # check for matching braces + braceAtCaret = -1 + braceOpposite = -1 + charBefore = None + caretPos = self.GetCurrentPos() + if caretPos > 0: + charBefore = self.GetCharAt(caretPos - 1) + styleBefore = self.GetStyleAt(caretPos - 1) + + # check before + if charBefore and chr(charBefore) in braces and styleBefore == wx.stc.STC_P_OPERATOR: + braceAtCaret = caretPos - 1 + + # check after + if braceAtCaret < 0: + charAfter = self.GetCharAt(caretPos) + styleAfter = self.GetStyleAt(caretPos) + if charAfter and chr(charAfter) in braces and styleAfter == wx.stc.STC_P_OPERATOR: + braceAtCaret = caretPos + + if braceAtCaret >= 0: + braceOpposite = self.BraceMatch(braceAtCaret) + + if braceAtCaret != -1 and braceOpposite == -1: + self.BraceBadLight(braceAtCaret) + else: + self.BraceHighlight(braceAtCaret, braceOpposite) + + evt.Skip() + + + def DoIndent(self): + (text, caretPos) = self.GetCurLine() + + self._tokenizerChars = {} # This is really too much, need to find something more like a C array + for i in range(len(text)): + self._tokenizerChars[i] = 0 + + ctext = cStringIO.StringIO(text) + try: + tokenize.tokenize(ctext.readline, self) + except: + pass + + # Left in for debugging purposes: + #for i in range(len(text)): + # print i, text[i], self._tokenizerChars[i] + + if caretPos == 0 or len(string.strip(text)) == 0: # At beginning of line or within an empty line + self.AddText('\n') + else: + doExtraIndent = False + brackets = False + commentStart = -1 + if caretPos > 1: + startParenCount = 0 + endParenCount = 0 + startSquareBracketCount = 0 + endSquareBracketCount = 0 + startCurlyBracketCount = 0 + endCurlyBracketCount = 0 + startQuoteCount = 0 + endQuoteCount = 0 + for i in range(caretPos - 1, -1, -1): # Go through each character before the caret + if i >= len(text): # Sometimes the caret is at the end of the text if there is no LF + continue + if self._tokenizerChars[i] == 1: + continue + elif self._tokenizerChars[i] == 2: + startQuoteCount = startQuoteCount + 1 + elif self._tokenizerChars[i] == 3: + endQuoteCount = endQuoteCount + 1 + elif text[i] == '(': # Would be nice to use a dict for this, but the code is much more readable this way + startParenCount = startParenCount + 1 + elif text[i] == ')': + endParenCount = endParenCount + 1 + elif text[i] == "[": + startSquareBracketCount = startSquareBracketCount + 1 + elif text[i] == "]": + endSquareBracketCount = endSquareBracketCount + 1 + elif text[i] == "{": + startCurlyBracketCount = startCurlyBracketCount + 1 + elif text[i] == "}": + endCurlyBracketCount = endCurlyBracketCount + 1 + elif text[i] == "#": + commentStart = i + break + if startQuoteCount > endQuoteCount or startParenCount > endParenCount or startSquareBracketCount > endSquareBracketCount or startCurlyBracketCount > endCurlyBracketCount: + if i + 1 >= caretPos: # Caret is right at the open paren, so just do indent as if colon was there + doExtraIndent = True + break + else: + spaces = " " * (i + 1) + brackets = True + break + if not brackets: + spaces = text[0:len(text) - len(string.lstrip(text))] + if caretPos < len(spaces): # If within the opening spaces of a line + spaces = spaces[:caretPos] + + # strip comment off + if commentStart != -1: + text = text[0:commentStart] + + textNoTrailingSpaces = text[0:caretPos].rstrip() + if doExtraIndent or len(textNoTrailingSpaces) and textNoTrailingSpaces[-1] == ':': + spaces = spaces + ' ' * self.GetIndent() + self.AddText('\n' + spaces) + + + # Callback for tokenizer in self.DoIndent + def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line): + if toktype == tokenize.COMMENT: + for i in range(scol, ecol + 1): + self._validChars[i] = False + elif toktype == token.STRING: + self._tokenizerChars[scol] = 2 # Open quote + self._tokenizerChars[ecol - 1] = 3 # Close quote + for i in range(scol + 1, ecol - 2): + self._tokenizerChars[i] = 1 # Part of string, 1 == ignore the char + + +class PythonOptionsPanel(wx.Panel): + + def __init__(self, parent, id): + wx.Panel.__init__(self, parent, id) + pathLabel = wx.StaticText(self, -1, _("python.exe Path:")) + config = wx.ConfigBase_Get() + path = config.Read("ActiveGridPythonLocation") + self._pathTextCtrl = wx.TextCtrl(self, -1, path, size = (150, -1)) + self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue()) + self._pathTextCtrl.SetInsertionPointEnd() + choosePathButton = wx.Button(self, -1, _("Browse...")) + pathSizer = wx.BoxSizer(wx.HORIZONTAL) + HALF_SPACE = 5 + pathSizer.Add(pathLabel, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.TOP, HALF_SPACE) + pathSizer.Add(self._pathTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE) + pathSizer.Add(choosePathButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE) + wx.EVT_BUTTON(self, choosePathButton.GetId(), self.OnChoosePath) + mainSizer = wx.BoxSizer(wx.VERTICAL) + mainSizer.Add(pathSizer, 0, wx.LEFT | wx.RIGHT | wx.TOP, 10) + + self._otherOptions = STCTextEditor.TextOptionsPanel(self, -1, configPrefix = "Python", label = "Python", hasWordWrap = False, hasTabs = True, addPage=False) + mainSizer.Add(self._otherOptions) + self.SetSizer(mainSizer) + parent.AddPage(self, _("Python")) + + def OnChoosePath(self, event): + if _WINDOWS: + wildcard = _("*.exe") + else: + wildcard = _("*") + path = wx.FileSelector(_("Select a File"), + _(""), + _(""), + wildcard = wildcard , + flags = wx.HIDE_READONLY, + parent = wx.GetApp().GetTopWindow()) + if path: + self._pathTextCtrl.SetValue(path) + self._pathTextCtrl.SetToolTipString(self._pathTextCtrl.GetValue()) + self._pathTextCtrl.SetInsertionPointEnd() + + def OnOK(self, optionsDialog): + if len(self._pathTextCtrl.GetValue()) > 0: + config = wx.ConfigBase_Get() + config.Write("ActiveGridPythonLocation", self._pathTextCtrl.GetValue()) + + self._otherOptions.OnOK(optionsDialog) +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getPythonData(): + return \ +"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\xd5IDAT8\x8d\x8d\x93Y\x0e\xc3 \x0cD\x9fM\xcf\xddNr2.\x96\xb8\x1f\ +\x05\n\x84.#Y\x10\xa3\x19o\xb1\x99'*\xe2<\x82\x0e\xe6\xc9\xf8\x01\xef?\xa4\ +\xf7)]\x05\x970O\xcdr\xce!\x119\xe7\x00\x02\x88\xfe}i\xb5\x848\x8f\xa8\x19\ +\xcc\x19}+\xc5\xcc\xd3\x92 0 + notOnLastChar = self.GetCtrl().GetSelectionStart() != self.GetCtrl().GetTextLength() + + id = event.GetId() + if id == wx.ID_UNDO: + event.Enable(self.GetCtrl().CanUndo()) + event.SetText(_("Undo") + '\t' + _('Ctrl+Z')) + return True + elif id == wx.ID_REDO: + event.Enable(self.GetCtrl().CanRedo()) + event.SetText(_("Redo") + '\t' + _('Ctrl+Y')) + return True + elif id == wx.ID_CUT: + event.Enable(hasSelection) + return True + elif id == wx.ID_COPY: + event.Enable(hasSelection) + return True + elif id == wx.ID_PASTE: + event.Enable(self.GetCtrl().CanPaste()) + return True + elif id == wx.ID_CLEAR: + event.Enable(hasSelection) + return True + elif id == wx.ID_SELECTALL: + event.Enable(hasText) + return True + elif id == TEXT_ID: + event.Enable(True) + return True + elif id == VIEW_WHITESPACE_ID: + event.Enable(hasText) + event.Check(self.GetCtrl().GetViewWhiteSpace()) + return True + elif id == VIEW_EOL_ID: + event.Enable(hasText) + event.Check(self.GetCtrl().GetViewEOL()) + return True + elif id == VIEW_INDENTATION_GUIDES_ID: + event.Enable(hasText) + event.Check(self.GetCtrl().GetIndentationGuides()) + return True + elif id == VIEW_RIGHT_EDGE_ID: + event.Enable(hasText) + event.Check(self.GetCtrl().GetViewRightEdge()) + return True + elif id == VIEW_LINE_NUMBERS_ID: + event.Enable(hasText) + event.Check(self.GetCtrl().GetViewLineNumbers()) + return True + elif id == ZOOM_ID: + event.Enable(True) + return True + elif id == ZOOM_NORMAL_ID: + event.Enable(self.GetCtrl().GetZoom() != 0) + return True + elif id == ZOOM_IN_ID: + event.Enable(self.GetCtrl().GetZoom() < 20) + return True + elif id == ZOOM_OUT_ID: + event.Enable(self.GetCtrl().GetZoom() > -10) + return True + elif id == CHOOSE_FONT_ID: + event.Enable(True) + return True + elif id == WORD_WRAP_ID: + event.Enable(self.GetCtrl().CanWordWrap()) + event.Check(self.GetCtrl().CanWordWrap() and self.GetCtrl().GetWordWrap()) + return True + elif id == FindService.FindService.FIND_ID: + event.Enable(hasText) + return True + elif id == FindService.FindService.FIND_PREVIOUS_ID: + event.Enable(hasText and + self._FindServiceHasString() and + self.GetCtrl().GetSelection()[0] > 0) + return True + elif id == FindService.FindService.FIND_NEXT_ID: + event.Enable(hasText and + self._FindServiceHasString() and + self.GetCtrl().GetSelection()[0] < self.GetCtrl().GetLength()) + return True + elif id == FindService.FindService.REPLACE_ID: + event.Enable(hasText) + return True + elif id == FindService.FindService.GOTO_LINE_ID: + event.Enable(True) + return True + elif id == TEXT_STATUS_BAR_ID: + self.OnUpdateStatusBar(event) + return True + else: + return wx.lib.docview.View.ProcessUpdateUIEvent(self, event) + + + def _GetParentFrame(self): + return wx.GetTopLevelParent(self.GetFrame()) + + + #---------------------------------------------------------------------------- + # Methods for TextDocument to call + #---------------------------------------------------------------------------- + + def IsModified(self): + if not self.GetCtrl(): + return False + return self.GetCtrl().GetModify() + + + def SetModifyFalse(self): + self.GetCtrl().SetSavePoint() + + + def GetValue(self): + if self.GetCtrl(): + return self.GetCtrl().GetText() + else: + return None + + + def SetValue(self, value): + self.GetCtrl().SetText(value) + self.GetCtrl().UpdateLineNumberMarginWidth() + self.GetCtrl().EmptyUndoBuffer() + + + #---------------------------------------------------------------------------- + # STC events + #---------------------------------------------------------------------------- + + def OnUpdateStatusBar(self, event): + statusBar = self._GetParentFrame().GetStatusBar() + statusBar.SetInsertMode(self.GetCtrl().GetOvertype() == 0) + statusBar.SetLineNumber(self.GetCtrl().GetCurrentLine() + 1) + statusBar.SetColumnNumber(self.GetCtrl().GetColumn(self.GetCtrl().GetCurrentPos()) + 1) + + + #---------------------------------------------------------------------------- + # Format methods + #---------------------------------------------------------------------------- + + def OnChooseFont(self): + data = wx.FontData() + data.EnableEffects(True) + data.SetInitialFont(self.GetCtrl().GetFont()) + data.SetColour(self.GetCtrl().GetFontColor()) + fontDialog = wx.FontDialog(self.GetFrame(), data) + if fontDialog.ShowModal() == wx.ID_OK: + data = fontDialog.GetFontData() + self.GetCtrl().SetFont(data.GetChosenFont()) + self.GetCtrl().SetFontColor(data.GetColour()) + self.GetCtrl().UpdateStyles() + fontDialog.Destroy() + + + #---------------------------------------------------------------------------- + # Find methods + #---------------------------------------------------------------------------- + + def OnFind(self, replace = False): + findService = wx.GetApp().GetService(FindService.FindService) + if findService: + findService.ShowFindReplaceDialog(findString = self.GetCtrl().GetSelectedText(), replace = replace) + + + def DoFind(self, forceFindNext = False, forceFindPrevious = False, replace = False, replaceAll = False): + findService = wx.GetApp().GetService(FindService.FindService) + if not findService: + return + findString = findService.GetFindString() + if len(findString) == 0: + return -1 + replaceString = findService.GetReplaceString() + flags = findService.GetFlags() + startLoc, endLoc = self.GetCtrl().GetSelection() + + wholeWord = flags & wx.FR_WHOLEWORD > 0 + matchCase = flags & wx.FR_MATCHCASE > 0 + regExp = flags & FindService.FindService.FR_REGEXP > 0 + down = flags & wx.FR_DOWN > 0 + wrap = flags & FindService.FindService.FR_WRAP > 0 + + if forceFindPrevious: # this is from function keys, not dialog box + down = False + wrap = False # user would want to know they're at the end of file + elif forceFindNext: + down = True + wrap = False # user would want to know they're at the end of file + + badSyntax = False + + # On replace dialog operations, user is allowed to replace the currently highlighted text to determine if it should be replaced or not. + # Typically, it is the text from a previous find operation, but we must check to see if it isn't, user may have moved the cursor or selected some other text accidentally. + # If the text is a match, then replace it. + if replace: + result, start, end, replText = findService.DoFind(findString, replaceString, self.GetCtrl().GetSelectedText(), 0, 0, True, matchCase, wholeWord, regExp, replace) + if result > 0: + self.GetCtrl().ReplaceSelection(replText) + self.GetDocument().Modify(True) + wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString) + if down: + startLoc += len(replText) # advance start location past replacement string to new text + endLoc = startLoc + elif result == FindService.FIND_SYNTAXERROR: + badSyntax = True + wx.GetApp().GetTopWindow().PushStatusText(_("Invalid regular expression \"%s\"") % findString) + + if not badSyntax: + text = self.GetCtrl().GetText() + + # Find the next matching text occurance or if it is a ReplaceAll, replace all occurances + # Even if the user is Replacing, we should replace here, but only select the text and let the user replace it with the next Replace operation + result, start, end, text = findService.DoFind(findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExp, False, replaceAll, wrap) + if result > 0: + self.GetCtrl().SetTargetStart(0) + self.GetCtrl().SetTargetEnd(self.GetCtrl().GetLength()) + self.GetCtrl().ReplaceTarget(text) # Doing a SetText causes a clear document to be shown when undoing, so using replacetarget instead + self.GetDocument().Modify(True) + if result == 1: + wx.GetApp().GetTopWindow().PushStatusText(_("1 occurrence of \"%s\" replaced") % findString) + else: + wx.GetApp().GetTopWindow().PushStatusText(_("%i occurrences of \"%s\" replaced") % (result, findString)) + elif result == 0: + self.GetCtrl().SetSelection(start, end) + self.GetCtrl().EnsureVisible(self.GetCtrl().LineFromPosition(end)) # show bottom then scroll up to top + self.GetCtrl().EnsureVisible(self.GetCtrl().LineFromPosition(start)) # do this after ensuring bottom is visible + wx.GetApp().GetTopWindow().PushStatusText(_("Found \"%s\".") % findString) + elif result == FindService.FIND_SYNTAXERROR: + # Dialog for this case gets popped up by the FindService. + wx.GetApp().GetTopWindow().PushStatusText(_("Invalid regular expression \"%s\"") % findString) + else: + wx.MessageBox(_("Can't find \"%s\".") % findString, "Find", + wx.OK | wx.ICON_INFORMATION) + + + def _FindServiceHasString(self): + findService = wx.GetApp().GetService(FindService.FindService) + if not findService or not findService.GetFindString(): + return False + return True + + + def OnGotoLine(self, event): + findService = wx.GetApp().GetService(FindService.FindService) + if findService: + line = findService.GetLineNumber(self.GetDocumentManager().FindSuitableParent()) + if line > -1: + line = line - 1 + self.GetCtrl().EnsureVisible(line) + self.GetCtrl().GotoLine(line) + + + def GotoLine(self, lineNum): + if lineNum > -1: + lineNum = lineNum - 1 # line numbering for editor is 0 based, we are 1 based. + self.GetCtrl().EnsureVisibleEnforcePolicy(lineNum) + self.GetCtrl().GotoLine(lineNum) + + + def SetSelection(self, start, end): + self.GetCtrl().SetSelection(start, end) + + + def EnsureVisible(self, line): + self.GetCtrl().EnsureVisible(line-1) # line numbering for editor is 0 based, we are 1 based. + + def EnsureVisibleEnforcePolicy(self, line): + self.GetCtrl().EnsureVisibleEnforcePolicy(line-1) # line numbering for editor is 0 based, we are 1 based. + + def LineFromPosition(self, pos): + return self.GetCtrl().LineFromPosition(pos)+1 # line numbering for editor is 0 based, we are 1 based. + + + def PositionFromLine(self, line): + return self.GetCtrl().PositionFromLine(line-1) # line numbering for editor is 0 based, we are 1 based. + + + def GetLineEndPosition(self, line): + return self.GetCtrl().GetLineEndPosition(line-1) # line numbering for editor is 0 based, we are 1 based. + + + def GetLine(self, lineNum): + return self.GetCtrl().GetLine(lineNum-1) # line numbering for editor is 0 based, we are 1 based. + + def MarkerDefine(self): + """ This must be called after the texteditor is instantiated """ + self.GetCtrl().MarkerDefine(TextView.MARKER_NUM, wx.stc.STC_MARK_CIRCLE, wx.BLACK, wx.BLUE) + + + def MarkerToggle(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() + if self.GetCtrl().MarkerGet(lineNum) & mask: + self.GetCtrl().MarkerDelete(lineNum, marker_index) + self._markerCount -= 1 + else: + self.GetCtrl().MarkerAdd(lineNum, marker_index) + self._markerCount += 1 + + def MarkerAdd(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() + self.GetCtrl().MarkerAdd(lineNum, marker_index) + self._markerCount += 1 + + + def MarkerDelete(self, lineNum = -1, marker_index=MARKER_NUM, mask=MARKER_MASK): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() + if self.GetCtrl().MarkerGet(lineNum) & mask: + self.GetCtrl().MarkerDelete(lineNum, marker_index) + self._markerCount -= 1 + + def MarkerDeleteAll(self, marker_num=MARKER_NUM): + self.GetCtrl().MarkerDeleteAll(marker_num) + if marker_num == self.MARKER_NUM: + self._markerCount = 0 + + + def MarkerNext(self, lineNum = -1): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() + 1 # start search below current line + foundLine = self.GetCtrl().MarkerNext(lineNum, self.MARKER_MASK) + if foundLine == -1: + # wrap to top of file + foundLine = self.GetCtrl().MarkerNext(0, self.MARKER_MASK) + if foundLine == -1: + wx.GetApp().GetTopWindow().PushStatusText(_("No markers")) + return + + self.GotoLine(foundLine + 1) + + + def MarkerPrevious(self, lineNum = -1): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() - 1 # start search above current line + if lineNum == -1: + lineNum = self.GetCtrl().GetLineCount() + + foundLine = self.GetCtrl().MarkerPrevious(lineNum, self.MARKER_MASK) + if foundLine == -1: + # wrap to bottom of file + foundLine = self.GetCtrl().MarkerPrevious(self.GetCtrl().GetLineCount(), self.MARKER_MASK) + if foundLine == -1: + wx.GetApp().GetTopWindow().PushStatusText(_("No markers")) + return + + self.GotoLine(foundLine + 1) + + + def MarkerExists(self, lineNum = -1, mask=MARKER_MASK): + if lineNum == -1: + lineNum = self.GetCtrl().GetCurrentLine() + if self.GetCtrl().MarkerGet(lineNum) & mask: + return True + else: + return False + + + def GetMarkerCount(self): + return self._markerCount + + +class TextService(wx.lib.pydocview.DocService): + + + def __init__(self): + wx.lib.pydocview.DocService.__init__(self) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + if document and document.GetDocumentTemplate().GetDocumentType() != TextDocument: + return + if not document and wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: + return + + statusBar = TextStatusBar(frame, TEXT_STATUS_BAR_ID) + frame.SetStatusBar(statusBar) + wx.EVT_UPDATE_UI(frame, TEXT_STATUS_BAR_ID, frame.ProcessUpdateUIEvent) + + viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) + + viewMenu.AppendSeparator() + textMenu = wx.Menu() + textMenu.AppendCheckItem(VIEW_WHITESPACE_ID, _("&Whitespace"), _("Shows or hides whitespace")) + wx.EVT_MENU(frame, VIEW_WHITESPACE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_WHITESPACE_ID, frame.ProcessUpdateUIEvent) + textMenu.AppendCheckItem(VIEW_EOL_ID, _("&End of Line Markers"), _("Shows or hides indicators at the end of each line")) + wx.EVT_MENU(frame, VIEW_EOL_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_EOL_ID, frame.ProcessUpdateUIEvent) + textMenu.AppendCheckItem(VIEW_INDENTATION_GUIDES_ID, _("&Indentation Guides"), _("Shows or hides indentations")) + wx.EVT_MENU(frame, VIEW_INDENTATION_GUIDES_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_INDENTATION_GUIDES_ID, frame.ProcessUpdateUIEvent) + textMenu.AppendCheckItem(VIEW_RIGHT_EDGE_ID, _("&Right Edge"), _("Shows or hides the right edge marker")) + wx.EVT_MENU(frame, VIEW_RIGHT_EDGE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_RIGHT_EDGE_ID, frame.ProcessUpdateUIEvent) + textMenu.AppendCheckItem(VIEW_LINE_NUMBERS_ID, _("&Line Numbers"), _("Shows or hides the line numbers")) + wx.EVT_MENU(frame, VIEW_LINE_NUMBERS_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, VIEW_LINE_NUMBERS_ID, frame.ProcessUpdateUIEvent) + + viewMenu.AppendMenu(TEXT_ID, _("&Text"), textMenu) + wx.EVT_UPDATE_UI(frame, TEXT_ID, frame.ProcessUpdateUIEvent) + + isWindows = (wx.Platform == '__WXMSW__') + + zoomMenu = wx.Menu() + zoomMenu.Append(ZOOM_NORMAL_ID, _("Normal Size"), _("Sets the document to its normal size")) + wx.EVT_MENU(frame, ZOOM_NORMAL_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ZOOM_NORMAL_ID, frame.ProcessUpdateUIEvent) + if isWindows: + zoomMenu.Append(ZOOM_IN_ID, _("Zoom In\tCtrl+Page Up"), _("Zooms the document to a larger size")) + else: + zoomMenu.Append(ZOOM_IN_ID, _("Zoom In"), _("Zooms the document to a larger size")) + wx.EVT_MENU(frame, ZOOM_IN_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ZOOM_IN_ID, frame.ProcessUpdateUIEvent) + if isWindows: + zoomMenu.Append(ZOOM_OUT_ID, _("Zoom Out\tCtrl+Page Down"), _("Zooms the document to a smaller size")) + else: + zoomMenu.Append(ZOOM_OUT_ID, _("Zoom Out"), _("Zooms the document to a smaller size")) + wx.EVT_MENU(frame, ZOOM_OUT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, ZOOM_OUT_ID, frame.ProcessUpdateUIEvent) + + viewMenu.AppendMenu(ZOOM_ID, _("&Zoom"), zoomMenu) + wx.EVT_UPDATE_UI(frame, ZOOM_ID, frame.ProcessUpdateUIEvent) + + formatMenuIndex = menuBar.FindMenu(_("&Format")) + if formatMenuIndex > -1: + formatMenu = menuBar.GetMenu(formatMenuIndex) + else: + formatMenu = wx.Menu() + if not menuBar.FindItemById(CHOOSE_FONT_ID): + formatMenu.Append(CHOOSE_FONT_ID, _("&Font..."), _("Sets the font to use")) + wx.EVT_MENU(frame, CHOOSE_FONT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, CHOOSE_FONT_ID, frame.ProcessUpdateUIEvent) + if not menuBar.FindItemById(WORD_WRAP_ID): + formatMenu.AppendCheckItem(WORD_WRAP_ID, _("Word Wrap"), _("Wraps text horizontally when checked")) + wx.EVT_MENU(frame, WORD_WRAP_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, WORD_WRAP_ID, frame.ProcessUpdateUIEvent) + if formatMenuIndex == -1: + viewMenuIndex = menuBar.FindMenu(_("&View")) + menuBar.Insert(viewMenuIndex + 1, formatMenu, _("&Format")) + + # wxBug: wxToolBar::GetToolPos doesn't exist, need it to find cut tool and then insert find in front of it. + toolBar.AddSeparator() + toolBar.AddTool(ZOOM_IN_ID, getZoomInBitmap(), shortHelpString = _("Zoom In"), longHelpString = _("Zooms the document to a larger size")) + toolBar.AddTool(ZOOM_OUT_ID, getZoomOutBitmap(), shortHelpString = _("Zoom Out"), longHelpString = _("Zooms the document to a smaller size")) + toolBar.Realize() + + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == TEXT_ID: + event.Enable(False) + return True + elif id == VIEW_WHITESPACE_ID: + event.Enable(False) + return True + elif id == VIEW_EOL_ID: + event.Enable(False) + return True + elif id == VIEW_INDENTATION_GUIDES_ID: + event.Enable(False) + return True + elif id == VIEW_RIGHT_EDGE_ID: + event.Enable(False) + return True + elif id == VIEW_LINE_NUMBERS_ID: + event.Enable(False) + return True + elif id == ZOOM_ID: + event.Enable(False) + return True + elif id == ZOOM_NORMAL_ID: + event.Enable(False) + return True + elif id == ZOOM_IN_ID: + event.Enable(False) + return True + elif id == ZOOM_OUT_ID: + event.Enable(False) + return True + elif id == CHOOSE_FONT_ID: + event.Enable(False) + return True + elif id == WORD_WRAP_ID: + event.Enable(False) + return True + else: + return False + + +class TextStatusBar(wx.StatusBar): + + # wxBug: Would be nice to show num key status in statusbar, but can't figure out how to detect if it is enabled or disabled + + def __init__(self, parent, id, style = wx.ST_SIZEGRIP, name = "statusBar"): + wx.StatusBar.__init__(self, parent, id, style, name) + self.SetFieldsCount(4) + self.SetStatusWidths([-1, 50, 50, 55]) + + def SetInsertMode(self, insert = True): + if insert: + newText = _("Ins") + else: + newText = _("") + if self.GetStatusText(1) != newText: # wxBug: Need to check if the text has changed, otherwise it flickers under win32 + self.SetStatusText(newText, 1) + + def SetLineNumber(self, lineNumber): + newText = _("Ln %i") % lineNumber + if self.GetStatusText(2) != newText: + self.SetStatusText(newText, 2) + + def SetColumnNumber(self, colNumber): + newText = _("Col %i") % colNumber + if self.GetStatusText(3) != newText: + self.SetStatusText(newText, 3) + + +class TextOptionsPanel(wx.Panel): + + + def __init__(self, parent, id, configPrefix = "Text", label = "Text", hasWordWrap = True, hasTabs = False, addPage=True): + wx.Panel.__init__(self, parent, id) + self._configPrefix = configPrefix + self._hasWordWrap = hasWordWrap + self._hasTabs = hasTabs + SPACE = 10 + HALF_SPACE = 5 + config = wx.ConfigBase_Get() + self._textFont = wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL) + fontData = config.Read(self._configPrefix + "EditorFont", "") + if fontData: + nativeFont = wx.NativeFontInfo() + nativeFont.FromString(fontData) + self._textFont.SetNativeFontInfo(nativeFont) + self._originalTextFont = self._textFont + self._textColor = wx.BLACK + colorData = config.Read(self._configPrefix + "EditorColor", "") + if colorData: + red = int("0x" + colorData[0:2], 16) + green = int("0x" + colorData[2:4], 16) + blue = int("0x" + colorData[4:6], 16) + self._textColor = wx.Color(red, green, blue) + self._originalTextColor = self._textColor + fontLabel = wx.StaticText(self, -1, _("Font:")) + self._sampleTextCtrl = wx.TextCtrl(self, -1, "", size = (125, 21)) + self._sampleTextCtrl.SetEditable(False) + chooseFontButton = wx.Button(self, -1, _("Choose Font...")) + wx.EVT_BUTTON(self, chooseFontButton.GetId(), self.OnChooseFont) + if self._hasWordWrap: + self._wordWrapCheckBox = wx.CheckBox(self, -1, _("Wrap words inside text area")) + self._wordWrapCheckBox.SetValue(wx.ConfigBase_Get().ReadInt(self._configPrefix + "EditorWordWrap", False)) + self._viewWhitespaceCheckBox = wx.CheckBox(self, -1, _("Show whitespace")) + self._viewWhitespaceCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewWhitespace", False)) + self._viewEOLCheckBox = wx.CheckBox(self, -1, _("Show end of line markers")) + self._viewEOLCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewEOL", False)) + self._viewIndentationGuideCheckBox = wx.CheckBox(self, -1, _("Show indentation guides")) + self._viewIndentationGuideCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewIndentationGuides", False)) + self._viewRightEdgeCheckBox = wx.CheckBox(self, -1, _("Show right edge")) + self._viewRightEdgeCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewRightEdge", False)) + self._viewLineNumbersCheckBox = wx.CheckBox(self, -1, _("Show line numbers")) + self._viewLineNumbersCheckBox.SetValue(config.ReadInt(self._configPrefix + "EditorViewLineNumbers", True)) + if self._hasTabs: + self._hasTabsCheckBox = wx.CheckBox(self, -1, _("Use spaces instead of tabs")) + self._hasTabsCheckBox.SetValue(not wx.ConfigBase_Get().ReadInt(self._configPrefix + "EditorUseTabs", False)) + indentWidthLabel = wx.StaticText(self, -1, _("Indent Width:")) + self._indentWidthChoice = wx.Choice(self, -1, choices = ["2", "4", "6", "8", "10"]) + self._indentWidthChoice.SetStringSelection(str(config.ReadInt(self._configPrefix + "EditorIndentWidth", 4))) + textPanelBorderSizer = wx.BoxSizer(wx.VERTICAL) + textPanelSizer = wx.BoxSizer(wx.VERTICAL) + textFontSizer = wx.BoxSizer(wx.HORIZONTAL) + textFontSizer.Add(fontLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE) + textFontSizer.Add(self._sampleTextCtrl, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.RIGHT, HALF_SPACE) + textFontSizer.Add(chooseFontButton, 0, wx.ALIGN_RIGHT | wx.LEFT, HALF_SPACE) + textPanelSizer.Add(textFontSizer, 0, wx.ALL, HALF_SPACE) + if self._hasWordWrap: + textPanelSizer.Add(self._wordWrapCheckBox, 0, wx.ALL, HALF_SPACE) + textPanelSizer.Add(self._viewWhitespaceCheckBox, 0, wx.ALL, HALF_SPACE) + textPanelSizer.Add(self._viewEOLCheckBox, 0, wx.ALL, HALF_SPACE) + textPanelSizer.Add(self._viewIndentationGuideCheckBox, 0, wx.ALL, HALF_SPACE) + textPanelSizer.Add(self._viewRightEdgeCheckBox, 0, wx.ALL, HALF_SPACE) + textPanelSizer.Add(self._viewLineNumbersCheckBox, 0, wx.ALL, HALF_SPACE) + if self._hasTabs: + textPanelSizer.Add(self._hasTabsCheckBox, 0, wx.ALL, HALF_SPACE) + textIndentWidthSizer = wx.BoxSizer(wx.HORIZONTAL) + textIndentWidthSizer.Add(indentWidthLabel, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.TOP, HALF_SPACE) + textIndentWidthSizer.Add(self._indentWidthChoice, 0, wx.ALIGN_LEFT | wx.EXPAND, HALF_SPACE) + textPanelSizer.Add(textIndentWidthSizer, 0, wx.ALL, HALF_SPACE) + textPanelBorderSizer.Add(textPanelSizer, 0, wx.ALL, SPACE) +## styleButton = wx.Button(self, -1, _("Choose Style...")) +## wx.EVT_BUTTON(self, styleButton.GetId(), self.OnChooseStyle) +## textPanelBorderSizer.Add(styleButton, 0, wx.ALL, SPACE) + self.SetSizer(textPanelBorderSizer) + self.UpdateSampleFont() + if addPage: + parent.AddPage(self, _(label)) + + def UpdateSampleFont(self): + nativeFont = wx.NativeFontInfo() + nativeFont.FromString(self._textFont.GetNativeFontInfoDesc()) + font = wx.NullFont + font.SetNativeFontInfo(nativeFont) + font.SetPointSize(self._sampleTextCtrl.GetFont().GetPointSize()) # Use the standard point size + self._sampleTextCtrl.SetFont(font) + self._sampleTextCtrl.SetForegroundColour(self._textColor) + self._sampleTextCtrl.SetValue(str(self._textFont.GetPointSize()) + _(" pt. ") + self._textFont.GetFaceName()) + self._sampleTextCtrl.Refresh() + self.Layout() + + +## def OnChooseStyle(self, event): +## import STCStyleEditor +## import os +## base = os.path.split(__file__)[0] +## config = os.path.abspath(os.path.join(base, 'stc-styles.rc.cfg')) +## +## dlg = STCStyleEditor.STCStyleEditDlg(None, +## 'Python', 'python', +## #'HTML', 'html', +## #'XML', 'xml', +## config) +## try: +## dlg.ShowModal() +## finally: +## dlg.Destroy() + + + def OnChooseFont(self, event): + data = wx.FontData() + data.EnableEffects(True) + data.SetInitialFont(self._textFont) + data.SetColour(self._textColor) + fontDialog = wx.FontDialog(self, data) + if fontDialog.ShowModal() == wx.ID_OK: + data = fontDialog.GetFontData() + self._textFont = data.GetChosenFont() + self._textColor = data.GetColour() + self.UpdateSampleFont() + fontDialog.Destroy() + + + def OnOK(self, optionsDialog): + config = wx.ConfigBase_Get() + doViewStuffUpdate = config.ReadInt(self._configPrefix + "EditorViewWhitespace", False) != self._viewWhitespaceCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorViewWhitespace", self._viewWhitespaceCheckBox.GetValue()) + doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewEOL", False) != self._viewEOLCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorViewEOL", self._viewEOLCheckBox.GetValue()) + doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewIndentationGuides", False) != self._viewIndentationGuideCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorViewIndentationGuides", self._viewIndentationGuideCheckBox.GetValue()) + doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewRightEdge", False) != self._viewRightEdgeCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorViewRightEdge", self._viewRightEdgeCheckBox.GetValue()) + doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorViewLineNumbers", True) != self._viewLineNumbersCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorViewLineNumbers", self._viewLineNumbersCheckBox.GetValue()) + if self._hasWordWrap: + doViewStuffUpdate = doViewStuffUpdate or config.ReadInt(self._configPrefix + "EditorWordWrap", False) != self._wordWrapCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorWordWrap", self._wordWrapCheckBox.GetValue()) + if self._hasTabs: + doViewStuffUpdate = doViewStuffUpdate or not config.ReadInt(self._configPrefix + "EditorUseTabs", True) != self._hasTabsCheckBox.GetValue() + config.WriteInt(self._configPrefix + "EditorUseTabs", not self._hasTabsCheckBox.GetValue()) + newIndentWidth = int(self._indentWidthChoice.GetStringSelection()) + oldIndentWidth = config.ReadInt(self._configPrefix + "EditorIndentWidth", 4) + if newIndentWidth != oldIndentWidth: + doViewStuffUpdate = True + config.WriteInt(self._configPrefix + "EditorIndentWidth", newIndentWidth) + doFontUpdate = self._originalTextFont != self._textFont or self._originalTextColor != self._textColor + config.Write(self._configPrefix + "EditorFont", self._textFont.GetNativeFontInfoDesc()) + config.Write(self._configPrefix + "EditorColor", "%02x%02x%02x" % (self._textColor.Red(), self._textColor.Green(), self._textColor.Blue())) + if doViewStuffUpdate or doFontUpdate: + for document in optionsDialog.GetDocManager().GetDocuments(): + if issubclass(document.GetDocumentTemplate().GetDocumentType(), TextDocument): + if doViewStuffUpdate: + document.UpdateAllViews(hint = "ViewStuff") + if doFontUpdate: + document.UpdateAllViews(hint = "Font") + + +class TextCtrl(wx.stc.StyledTextCtrl): + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + if ID == -1: + ID = wx.NewId() + wx.stc.StyledTextCtrl.__init__(self, parent, ID, style = style) + + self._font = None + self._fontColor = None + + self.SetVisiblePolicy(wx.stc.STC_VISIBLE_STRICT,0) + self.SetYCaretPolicy(0, 0) + + self.CmdKeyClear(wx.stc.STC_KEY_ADD, wx.stc.STC_SCMOD_CTRL) + self.CmdKeyClear(wx.stc.STC_KEY_SUBTRACT, wx.stc.STC_SCMOD_CTRL) + self.CmdKeyAssign(wx.stc.STC_KEY_PRIOR, wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMIN) + self.CmdKeyAssign(wx.stc.STC_KEY_NEXT, wx.stc.STC_SCMOD_CTRL, wx.stc.STC_CMD_ZOOMOUT) + self.Bind(wx.stc.EVT_STC_ZOOM, self.OnUpdateLineNumberMarginWidth) # auto update line num width on zoom + wx.EVT_KEY_DOWN(self, self.OnKeyPressed) + self.SetMargins(0,0) + + self.SetUseTabs(0) + self.SetTabWidth(4) + self.SetIndent(4) + + self.SetViewWhiteSpace(False) + self.SetEOLMode(wx.stc.STC_EOL_LF) + self.SetEdgeMode(wx.stc.STC_EDGE_NONE) + self.SetEdgeColumn(78) + + self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) + self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth()) + self.UpdateStyles() + + self.SetCaretForeground("BLACK") + + self.SetViewDefaults() + font, color = self.GetFontAndColorFromConfig() + self.SetFont(font) + self.SetFontColor(color) + self.MarkerDefineDefault() + + # for multisash initialization + if isinstance(parent, wx.lib.multisash.MultiClient): + while parent.GetParent(): + parent = parent.GetParent() + if hasattr(parent, "GetView"): + break + if hasattr(parent, "GetView"): + textEditor = parent.GetView()._textEditor + if textEditor: + doc = textEditor.GetDocPointer() + if doc: + self.SetDocPointer(doc) + + + + def SetViewDefaults(self, configPrefix = "Text", hasWordWrap = True, hasTabs = False): + config = wx.ConfigBase_Get() + self.SetViewWhiteSpace(config.ReadInt(configPrefix + "EditorViewWhitespace", False)) + self.SetViewEOL(config.ReadInt(configPrefix + "EditorViewEOL", False)) + self.SetIndentationGuides(config.ReadInt(configPrefix + "EditorViewIndentationGuides", False)) + self.SetViewRightEdge(config.ReadInt(configPrefix + "EditorViewRightEdge", False)) + self.SetViewLineNumbers(config.ReadInt(configPrefix + "EditorViewLineNumbers", True)) + if hasWordWrap: + self.SetWordWrap(config.ReadInt(configPrefix + "EditorWordWrap", False)) + if hasTabs: # These methods do not exist in STCTextEditor and are meant for subclasses + self.SetUseTabs(config.ReadInt(configPrefix + "EditorUseTabs", False)) + self.SetIndent(config.ReadInt(configPrefix + "EditorIndentWidth", 4)) + self.SetTabWidth(config.ReadInt(configPrefix + "EditorIndentWidth", 4)) + else: + self.SetUseTabs(True) + self.SetIndent(4) + self.SetTabWidth(4) + + + + def GetDefaultFont(self): + """ Subclasses should override this """ + return wx.Font(10, wx.MODERN, wx.NORMAL, wx.NORMAL) + + + def GetDefaultColor(self): + """ Subclasses should override this """ + return wx.BLACK + + + def GetFontAndColorFromConfig(self, configPrefix = "Text"): + font = self.GetDefaultFont() + config = wx.ConfigBase_Get() + fontData = config.Read(configPrefix + "EditorFont", "") + if fontData: + nativeFont = wx.NativeFontInfo() + nativeFont.FromString(fontData) + font.SetNativeFontInfo(nativeFont) + color = self.GetDefaultColor() + colorData = config.Read(configPrefix + "EditorColor", "") + if colorData: + red = int("0x" + colorData[0:2], 16) + green = int("0x" + colorData[2:4], 16) + blue = int("0x" + colorData[4:6], 16) + color = wx.Color(red, green, blue) + return font, color + + + def GetFont(self): + return self._font + + def SetFont(self, font): + self._font = font + self.StyleSetFont(wx.stc.STC_STYLE_DEFAULT, self._font) + + + def GetFontColor(self): + return self._fontColor + + + def SetFontColor(self, fontColor = wx.BLACK): + self._fontColor = fontColor + self.StyleSetForeground(wx.stc.STC_STYLE_DEFAULT, "#%02x%02x%02x" % (self._fontColor.Red(), self._fontColor.Green(), self._fontColor.Blue())) + + + def UpdateStyles(self): + self.StyleClearAll() + return + + + def EstimatedLineNumberMarginWidth(self): + MARGIN = 4 + baseNumbers = "000" + lineNum = self.GetLineCount() + lineNum = lineNum/100 + while lineNum >= 10: + lineNum = lineNum/10 + baseNumbers = baseNumbers + "0" + + return self.TextWidth(wx.stc.STC_STYLE_LINENUMBER, baseNumbers) + MARGIN + + + def OnUpdateLineNumberMarginWidth(self, event): + self.UpdateLineNumberMarginWidth() + + + def UpdateLineNumberMarginWidth(self): + if self.GetViewLineNumbers(): + self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth()) + + def MarkerDefineDefault(self): + """ This must be called after the textcontrol is instantiated """ + self.MarkerDefine(TextView.MARKER_NUM, wx.stc.STC_MARK_ROUNDRECT, wx.BLACK, wx.BLUE) + + + def OnClear(self): + # Used when Delete key is hit. + sel = self.GetSelection() + + # Delete the selection or if no selection, the character after the caret. + if sel[0] == sel[1]: + self.SetSelection(sel[0], sel[0] + 1) + else: + # remove any folded lines also. + startLine = self.LineFromPosition(sel[0]) + endLine = self.LineFromPosition(sel[1]) + endLineStart = self.PositionFromLine(endLine) + if startLine != endLine and sel[1] - endLineStart == 0: + while not self.GetLineVisible(endLine): + endLine += 1 + self.SetSelectionEnd(self.PositionFromLine(endLine)) + + self.Clear() + + + def OnPaste(self): + # replace any folded lines also. + sel = self.GetSelection() + startLine = self.LineFromPosition(sel[0]) + endLine = self.LineFromPosition(sel[1]) + endLineStart = self.PositionFromLine(endLine) + if startLine != endLine and sel[1] - endLineStart == 0: + while not self.GetLineVisible(endLine): + endLine += 1 + self.SetSelectionEnd(self.PositionFromLine(endLine)) + + self.Paste() + + + def OnKeyPressed(self, event): + key = event.GetKeyCode() + if key == wx.WXK_NUMPAD_ADD: #wxBug: For whatever reason, the key accelerators for numpad add and subtract with modifiers are not working so have to trap them here + if event.ControlDown(): + self.ToggleFoldAll(expand = True, topLevelOnly = True) + elif event.ShiftDown(): + self.ToggleFoldAll(expand = True) + else: + self.ToggleFold(self.GetCurrentLine()) + elif key == wx.WXK_NUMPAD_SUBTRACT: + if event.ControlDown(): + self.ToggleFoldAll(expand = False, topLevelOnly = True) + elif event.ShiftDown(): + self.ToggleFoldAll(expand = False) + else: + self.ToggleFold(self.GetCurrentLine()) + else: + event.Skip() + + + #---------------------------------------------------------------------------- + # View Text methods + #---------------------------------------------------------------------------- + + def GetViewRightEdge(self): + return self.GetEdgeMode() != wx.stc.STC_EDGE_NONE + + + def SetViewRightEdge(self, viewRightEdge): + if viewRightEdge: + self.SetEdgeMode(wx.stc.STC_EDGE_LINE) + else: + self.SetEdgeMode(wx.stc.STC_EDGE_NONE) + + + def GetViewLineNumbers(self): + return self.GetMarginWidth(1) > 0 + + + def SetViewLineNumbers(self, viewLineNumbers = True): + if viewLineNumbers: + self.SetMarginWidth(1, self.EstimatedLineNumberMarginWidth()) + else: + self.SetMarginWidth(1, 0) + + + def CanWordWrap(self): + return True + + + def GetWordWrap(self): + return self.GetWrapMode() == wx.stc.STC_WRAP_WORD + + + def SetWordWrap(self, wordWrap): + if wordWrap: + self.SetWrapMode(wx.stc.STC_WRAP_WORD) + else: + self.SetWrapMode(wx.stc.STC_WRAP_NONE) + + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getTextData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00`IDAT8\x8d\xed\x931\x0e\xc00\x08\x03m\x92\xff\xff8q\xa7JU!$\x12\x1d\ +\xeb\t\t8n\x81\xb4\x86J\xfa]h\x0ee\x83\xb4\xc6\x14\x00\x00R\xcc \t\xcd\xa1\ +\x08\xd2\xa3\xe1\x08*\t$\x1d\xc4\x012\x0b\x00\xce\xe4\xc8\xe0\t}\xf7\x8f\rV\ +\xd9\x1a\xec\xe0\xbf\xc1\xd7\x06\xd9\xf5UX\xfdF+m\x03\xb8\x00\xe4\xc74B"x\ +\xf1\xf4\x00\x00\x00\x00IEND\xaeB`\x82' + + +def getTextBitmap(): + return BitmapFromImage(getTextImage()) + +def getTextImage(): + stream = cStringIO.StringIO(getTextData()) + return ImageFromStream(stream) + +def getTextIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getTextBitmap()) + return icon + + +#---------------------------------------------------------------------------- +# Menu Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +#---------------------------------------------------------------------- +def getZoomInData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00wIDAT8\x8d\xa5\x93Q\x12\x80 \x08D\xb5\xe9X\xee\xe9\xb7{\xd5Gc\xa9\ +\xacX\xca\x1f\xa0\x8fE0\x92<\xc3\x82\xed*\x08\xa0\xf2I~\x07\x000\x17T,\xdb\ +\xd6;\x08\xa4\x00\xa4GA\xab\xca\x00\xbc*\x1eD\xb4\x90\xa4O\x1e\xe3\x16f\xcc(\ +\xc8\x95F\x95\x8d\x02\xef\xa1n\xa0\xce\xc5v\x91zc\xacU\xbey\x03\xf0.\xa8\xb8\ +\x04\x8c\xac\x04MM\xa1lA\xfe\x85?\x90\xe5=X\x06\\\xebCA\xb3Q\xf34\x14\x00\ +\x00\x00\x00IEND\xaeB`\x82' + +def getZoomInBitmap(): + return BitmapFromImage(getZoomInImage()) + +def getZoomInImage(): + stream = cStringIO.StringIO(getZoomInData()) + return ImageFromStream(stream) + +#---------------------------------------------------------------------- +def getZoomOutData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00qIDAT8\x8d\xa5\x92Q\x0e\xc0 \x08C-z\xff\x13O\xd9\xd7\x16"\x05\x8d\ +\xf6O\xa2\x8f"\x05\xa4\x96\x1b5V\xd4\xd1\xd5\x9e!\x15\xdb\x00\x1d]\xe7\x07\ +\xac\xf6Iv.B*fW\x0e\x90u\xc9 d\x84\x87v\x82\xb4\xf5\x08\'r\x0e\xa2N\x91~\x07\ +\xd9G\x95\xe2W\xeb\x00\x19\xc4\xd6\\FX\x12\xa3 \xb1:\x05\xacdAG[\xb0y9r`u\ +\x9d\x83k\xc0\x0b#3@0A\x0c"\x93\x00\x00\x00\x00IEND\xaeB`\x82' + + +def getZoomOutBitmap(): + return BitmapFromImage(getZoomOutImage()) + +def getZoomOutImage(): + stream = cStringIO.StringIO(getZoomOutData()) + return ImageFromStream(stream) + + + diff --git a/wxPython/samples/ide/activegrid/tool/Service.py b/wxPython/samples/ide/activegrid/tool/Service.py new file mode 100644 index 0000000000..a55702f639 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/Service.py @@ -0,0 +1,327 @@ +#---------------------------------------------------------------------------- +# Name: Service.py +# Purpose: Basic Reusable Service View for wx.lib.pydocview +# +# Author: Morgan Hua +# +# Created: 11/4/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import wx.lib.docview +import wx.lib.pydocview +_ = wx.GetTranslation + + +FLOATING_MINIFRAME = -1 + + +class ServiceView(wx.EvtHandler): + """ Basic Service View. + """ + bottomTab = None + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self, service): + wx.EvtHandler.__init__(self) + self._viewFrame = None + self._service = service + self._control = None + self._embeddedWindow = None + + + def Destroy(self): + wx.EvtHandler.Destroy(self) + + + def GetFrame(self): + return self._viewFrame + + + def SetFrame(self, frame): + self._viewFrame = frame + + + def _CreateControl(self, parent, id): + return None + + + def GetControl(self): + return self._control + + + def SetControl(self, control): + self._control = control + + + def OnCreate(self, doc, flags): + config = wx.ConfigBase_Get() + windowLoc = self._service.GetEmbeddedWindowLocation() + if windowLoc == FLOATING_MINIFRAME: + pos = config.ReadInt(self._service.GetServiceName() + "FrameXLoc", -1), config.ReadInt(self._service.GetServiceName() + "FrameYLoc", -1) + # make sure frame is visible + screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) + screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) + if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight: + pos = wx.DefaultPosition + + size = wx.Size(config.ReadInt(self._service.GetServiceName() + "FrameXSize", -1), config.ReadInt(self._service.GetServiceName() + "FrameYSize", -1)) + title = _(self._service.GetServiceName()) + if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName(): + title = title + " - " + wx.GetApp().GetAppName() + frame = wx.MiniFrame(wx.GetApp().GetTopWindow(), -1, title, pos = pos, size = size, style = wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU) + wx.EVT_CLOSE(frame, self.OnCloseWindow) + elif wx.GetApp().IsMDI(): + self._embeddedWindow = wx.GetApp().GetTopWindow().GetEmbeddedWindow(windowLoc) + frame = self._embeddedWindow + else: + pos = config.ReadInt(self._service.GetServiceName() + "FrameXLoc", -1), config.ReadInt(self._service.GetServiceName() + "FrameYLoc", -1) + # make sure frame is visible + screenWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) + screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) + if pos[0] < 0 or pos[0] >= screenWidth or pos[1] < 0 or pos[1] >= screenHeight: + pos = wx.DefaultPosition + + size = wx.Size(config.ReadInt(self._service.GetServiceName() + "FrameXSize", -1), config.ReadInt(self._service.GetServiceName() + "FrameYSize", -1)) + title = _(self._service.GetServiceName()) + if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI and wx.GetApp().GetAppName(): + title = title + " - " + wx.GetApp().GetAppName() + frame = wx.GetApp().CreateDocumentFrame(self, doc, flags, pos = pos, size = size) + frame.SetTitle(title) + if config.ReadInt(self._service.GetServiceName() + "FrameMaximized", False): + frame.Maximize(True) + wx.EVT_CLOSE(frame, self.OnCloseWindow) + + self.SetFrame(frame) + sizer = wx.BoxSizer(wx.VERTICAL) + + windowLoc = self._service.GetEmbeddedWindowLocation() + if self._embeddedWindow or windowLoc == FLOATING_MINIFRAME: + if (self._service.GetEmbeddedWindowLocation() == wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM): + if ServiceView.bottomTab == None: + ServiceView.bottomTab = wx.Notebook(frame, wx.NewId(), (0,0), (100,100), wx.LB_DEFAULT, "Bottom Tab") + sizer.Add(ServiceView.bottomTab, 1, wx.TOP|wx.EXPAND, 4) + def OnFrameResize(event): + ServiceView.bottomTab.SetSize(ServiceView.bottomTab.GetParent().GetSize()) + frame.Bind(wx.EVT_SIZE, OnFrameResize) + # Factor this out. + self._control = self._CreateControl(ServiceView.bottomTab, wx.NewId()) + if self._control != None: + ServiceView.bottomTab.AddPage(self._control, self._service.GetServiceName()) + ServiceView.bottomTab.Layout() + else: + # Factor this out. + self._control = self._CreateControl(frame, wx.NewId()) + sizer.Add(self._control) + else: + # Factor this out. + self._control = self._CreateControl(frame, wx.NewId()) + sizer.Add(self._control, 1, wx.EXPAND, 0) + frame.SetSizer(sizer) + frame.Layout() + + return True + + + def OnCloseWindow(self, event): + frame = self.GetFrame() + config = wx.ConfigBase_Get() + if frame and not self._embeddedWindow: + if not frame.IsMaximized(): + config.WriteInt(self._service.GetServiceName() + "FrameXLoc", frame.GetPositionTuple()[0]) + config.WriteInt(self._service.GetServiceName() + "FrameYLoc", frame.GetPositionTuple()[1]) + config.WriteInt(self._service.GetServiceName() + "FrameXSize", frame.GetSizeTuple()[0]) + config.WriteInt(self._service.GetServiceName() + "FrameYSize", frame.GetSizeTuple()[1]) + config.WriteInt(self._service.GetServiceName() + "FrameMaximized", frame.IsMaximized()) + + if not self._embeddedWindow: + windowLoc = self._service.GetEmbeddedWindowLocation() + if windowLoc == FLOATING_MINIFRAME: + # don't destroy it, just hide it + frame.Hide() + else: + # Call the original OnCloseWindow, could have subclassed SDIDocFrame and MDIDocFrame but this is easier since it will work for both SDI and MDI frames without subclassing both + frame.OnCloseWindow(event) + + + def Activate(self, activate = True): + """ Dummy function for SDI mode """ + pass + + + def Close(self, deleteWindow = True): + """ + Closes the view by calling OnClose. If deleteWindow is true, this + function should delete the window associated with the view. + """ + if deleteWindow: + self.Destroy() + + return True + + + #---------------------------------------------------------------------------- + # Callback Methods + #---------------------------------------------------------------------------- + + def SetCallback(self, callback): + """ Sets in the event table for a doubleclick to invoke the given callback. + Additional calls to this method overwrites the previous entry and only the last set callback will be invoked. + """ + wx.stc.EVT_STC_DOUBLECLICK(self.GetControl(), self.GetControl().GetId(), callback) + + + #---------------------------------------------------------------------------- + # Display Methods + #---------------------------------------------------------------------------- + + def IsShown(self): + if not self.GetFrame(): + return False + return self.GetFrame().IsShown() + + + def Hide(self): + self.Show(False) + + + def Show(self, show = True): + self.GetFrame().Show(show) + if self._embeddedWindow: + mdiParentFrame = wx.GetApp().GetTopWindow() + mdiParentFrame.ShowEmbeddedWindow(self.GetFrame(), show) + + +class Service(wx.lib.pydocview.DocService): + + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service + + + def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_LEFT): + self._serviceName = serviceName + self._embeddedWindowLocation = embeddedWindowLocation + self._view = None + + + def GetEmbeddedWindowLocation(self): + return self._embeddedWindowLocation + + + def SetEmbeddedWindowLocation(self, embeddedWindowLocation): + self._embeddedWindowLocation = embeddedWindowLocation + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View"))) + menuItemPos = self.GetMenuItemPos(viewMenu, viewMenu.FindItem(_("&Status Bar"))) + 1 + + viewMenu.InsertCheckItem(menuItemPos, self.SHOW_WINDOW, self.GetMenuString(), self.GetMenuDescr()) + wx.EVT_MENU(frame, self.SHOW_WINDOW, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, self.SHOW_WINDOW, frame.ProcessUpdateUIEvent) + + return True + + + def GetServiceName(self): + """ String used to save out Service View configuration information """ + return self._serviceName + + + def GetMenuString(self): + """ Need to override this method to provide menu item for showing Service View """ + return _(self.GetServiceName()) + + + def GetMenuDescr(self): + """ Need to override this method to provide menu item for showing Service View """ + return _("Show or hides the %s window") % self.GetMenuString() + + + #---------------------------------------------------------------------------- + # Event Processing Methods + #---------------------------------------------------------------------------- + + def ProcessEvent(self, event): + id = event.GetId() + if id == self.SHOW_WINDOW: + self.ToggleWindow(event) + return True + else: + return False + + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == self.SHOW_WINDOW: + event.Check(self._view != None and self._view.IsShown()) + event.Enable(True) + return True + else: + return False + + + #---------------------------------------------------------------------------- + # View Methods + #---------------------------------------------------------------------------- + + def _CreateView(self): + """ This method needs to be overridden with corresponding ServiceView """ + return ServiceView(self) + + + def GetView(self): + # Window Menu Service Method + return self._view + + + def SetView(self, view): + self._view = view + + + def ShowWindow(self, show = True): + if show: + if self._view: + if not self._view.IsShown(): + self._view.Show() + else: + view = self._CreateView() + view.OnCreate(None, flags = 0) + self.SetView(view) + else: + if self._view: + if self._view.IsShown(): + self._view.Hide() + + + def HideWindow(self): + self.ShowWindow(False) + + + def ToggleWindow(self, event): + show = event.IsChecked() + wx.ConfigBase_Get().WriteInt(self.GetServiceName()+"Shown", show) + self.ShowWindow(show) + + + def OnCloseFrame(self, event): + if not self._view: + return True + + if wx.GetApp().IsMDI(): + self._view.OnCloseWindow(event) + # This is called when any SDI frame is closed, so need to check if message window is closing or some other window + elif self._view == event.GetEventObject().GetView(): + self.SetView(None) + + return True + diff --git a/wxPython/samples/ide/activegrid/tool/TabbedView.py b/wxPython/samples/ide/activegrid/tool/TabbedView.py new file mode 100644 index 0000000000..6ac51bfbc0 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/TabbedView.py @@ -0,0 +1,48 @@ +#---------------------------------------------------------------------------- +# Name: TabbedView.py +# Purpose: +# +# Author: Peter Yared +# +# Created: 8/17/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import wx +import wx.lib.docview + +class TabbedView(dict, wx.lib.docview.View): + + #---------------------------------------------------------------------------- + # Overridden methods + #---------------------------------------------------------------------------- + + def __init__(self): + wx.lib.docview.View.__init__(self) + self._views = {} + self._currentView = None + + + def OnCreate(self, doc, flags): + frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) + sizer = wx.BoxSizer() + self._notebook = wx.Notebook(frame, -1, style = wx.NB_BOTTOM) + self.Activate() + return True + + + def AddView(self, viewName, view): + self._notebook.AddPage(wx.Panel(self._notebook, -1), viewName) + self._currentView = view + self._views[viewName] = view + + + def __getattr__(self, attrname): + return getattr(self._currentView, attrname) + + + def SetView(self, viewName): + self._currentview = self._views[viewName] + diff --git a/wxPython/samples/ide/activegrid/tool/UICommon.py b/wxPython/samples/ide/activegrid/tool/UICommon.py new file mode 100644 index 0000000000..0f83b65e91 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/UICommon.py @@ -0,0 +1,115 @@ +#---------------------------------------------------------------------------- +# Name: UICommon.py +# Purpose: Shared UI stuff +# +# Author: Matt Fryer +# +# Created: 3/10/05 +# CVS-ID: $Id$ +# Copyright: (c) 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import os +import os.path +import wx +import ProjectEditor +_ = wx.GetTranslation + +def CreateDirectoryControl( parent, fileLabel, dirLabel, fileExtension, startingName="", startingDirectory=""): + + nameControl = wx.TextCtrl(parent, -1, startingName, size=(-1,-1)) + nameLabelText = wx.StaticText(parent, -1, fileLabel) + dirLabelText = wx.StaticText(parent, -1, dirLabel) + dirControl = wx.TextCtrl(parent, -1, startingDirectory, size=(-1,-1)) + dirControl.SetToolTipString(startingDirectory) + button = wx.Button(parent, -1, _("Browse..."), size=(60,-1)) + + def OnFindDirClick(event): + name = "" + nameCtrlValue = nameControl.GetValue() + if nameCtrlValue: + root, ext = os.path.splitext( nameCtrlValue ) + if ext == '.' + fileExtension: + name = nameCtrlValue + else: + name = _("%s.%s") % (nameCtrlValue, fileExtension) + path = wx.FileSelector(_("Choose a filename and directory"), + "", + "%s" % name, + wildcard=_("*.%s") % fileExtension , + flags=wx.SAVE, + parent=parent) + + if path: + dir, filename = os.path.split(path) + dirControl.SetValue(dir) + dirControl.SetToolTipString(dir) + nameControl.SetValue(filename) + + parent.Bind(wx.EVT_BUTTON, OnFindDirClick, button) + + def Validate(allowOverwriteOnPrompt=False): + if nameControl.GetValue() == "": + wx.MessageBox(_("Please provide a filename."), _("Provide a Filename")) + return False + if nameControl.GetValue().find(' ') != -1: + wx.MessageBox(_("Please provide a filename that does not contains spaces."), _("Spaces in Filename")) + return False + filePath = os.path.join(dirControl.GetValue(), MakeNameEndInExtension(nameControl.GetValue(), "." + fileExtension)) + if os.path.exists(filePath): + if allowOverwriteOnPrompt: + res = wx.MessageBox(_("That file already exists. Would you like to overwrite it."), "File Exists", style=wx.YES_NO|wx.NO_DEFAULT) + return (res == wx.YES) + else: + wx.MessageBox(_("That file already exists. Please choose a different name."), "File Exists") + return False + return True + HALF_SPACE = 5 + flexGridSizer = wx.FlexGridSizer(cols = 3, vgap = HALF_SPACE, hgap = HALF_SPACE) + flexGridSizer.AddGrowableCol(1,1) + flexGridSizer.Add(nameLabelText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT|wx.TOP|wx.RIGHT, HALF_SPACE) + flexGridSizer.Add(nameControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND) + flexGridSizer.Add(button, flag=wx.ALIGN_RIGHT|wx.LEFT, border=HALF_SPACE) + + flexGridSizer.Add(dirLabelText, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT|wx.TOP|wx.RIGHT, border=HALF_SPACE) + flexGridSizer.Add(dirControl, 2, flag=wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border=HALF_SPACE) + flexGridSizer.Add(wx.StaticText(parent, -1, ""), 0) + return nameControl, dirControl, flexGridSizer, Validate + +def AddFilesToCurrentProject(paths, save=False): + projectService = wx.GetApp().GetService(ProjectEditor.ProjectService) + if projectService: + projectDocument = projectService.GetCurrentProject() + if projectDocument: + files = projectDocument.GetFiles() + for path in paths: + if path in files: + paths.remove(path) + if paths: + projectDocument.GetCommandProcessor().Submit(ProjectEditor.ProjectAddFilesCommand(projectDocument, paths)) + if save: + projectDocument.OnSaveDocument(projectDocument.GetFilename()) + +def MakeNameEndInExtension(name, extension): + if not name: + return name + root, ext = os.path.splitext(name) + if ext == extension: + return name + else: + return name + extension + +# Lame +def PluralName(name): + if not name: + return name + if name.endswith('us'): + return name[0:-2] + 'ii' + elif name.endswith('s'): + return name + elif name.endswith('y'): + return name[0:-1] + 'ies' + else: + return name + 's' + diff --git a/wxPython/samples/ide/activegrid/tool/Wizard.py b/wxPython/samples/ide/activegrid/tool/Wizard.py new file mode 100644 index 0000000000..98d84eeb70 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/Wizard.py @@ -0,0 +1,967 @@ +#---------------------------------------------------------------------------- +# Name: Wizard.py +# Purpose: +# +# Author: Peter Yared +# +# Created: 10/28/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +import wx +import wx.xrc as xrc +import wx.wizard + + +#---------------------------------------------------------------------------- +# Classes +#---------------------------------------------------------------------------- + +class BaseWizard(wx.wizard.Wizard): + + + def __init__(self, parent, title, pos=(-1,-1)): + wizardBitMap = getWizardBitmap() + wx.wizard.Wizard.__init__(self, parent, wx.NewId(), title, wizardBitMap, pos=pos) + + def GetDocument(self): + if self.GetParent() and hasattr(self.GetParent(), 'GetDocument'): + return self.GetParent().GetDocument() + else: + return None + + def SetPrevNext(self, prev, next): + prev.SetNext(next) + next.SetPrev(prev) + + +class TitledWizardPage(wx.wizard.PyWizardPage): + + + def __init__(self, parent, title): + self._prev = None + self._prevFunc = None + self._next = None + self._nextFunc = None + wx.wizard.PyWizardPage.__init__(self, parent) + self.SetSizer(wx.BoxSizer(wx.VERTICAL)) + self.MakePageTitle(title) + + + def MakePageTitle(self, title): + sizer = wx.BoxSizer(wx.VERTICAL) + title = wx.StaticText(self, -1, title) + title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) + sizer.Add(title, 0, wx.ALIGN_LEFT | wx.ALL, 5) + sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 5) + self.GetSizer().Add(sizer) + + + def GetPrev(self): + if self._prevFunc: + self._prev = self._prevFunc() + return self._prev + + + def SetPrev(self, prev): + self._prev = prev + self._prevFunc = None + + + def GetPrevFunc(self): + return self._prevFunc + + + def SetPrevFunc(self, prevFunc): + self._prevFunc = prevFunc + self._prev = None + + + def GetNext(self): + if self._nextFunc: + self._next = self._nextFunc() + return self._next + + + def SetNext(self, next): + self._next = next + self._nextFunc = None + + + def GetNextFunc(self): + return self._nextFunc + + + def SetNextFunc(self, nextFunc): + self._nextFunc = nextFunc + self._next = None + + + def SetPrevNext(self, prev, next): + self._prev = prev + self._next = next + self._nextFunc = None + self._prevFunc = None + + + +#---------------------------------------------------------------------------- +# Menu Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +import cStringIO + + +def getWizardData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00}\x00\x00\x00\xfa\x08\x06\ +\x00\x00\x00\x8c5HE\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00 \ +\x00IDATx\x9c\xec\x9dy\x9cdUy\xf7\xbf\xe7\xdc\xbd\x96\xdegzVfe`\x86u\xd8WY\ +\x14\x90M\x10\x11D\x11W4\x89Fc\xe4M\xd4\xa8\xd1\x88\x91D\r\x1a\xb7\x98\x98\ +\xa8o\xd4\x08\xae\x08\x18EDE@\x04E\xf6}\xf6\xadgz\xaf\xae\xedn\xe7\xbc\x7f\ +\x9c[\xd5\xd53=\xc3$\x8c\xd0\xf3v\xff>\x9f\xea\xea\xbau\xebn\xbf\xf3<\xe79\ +\xcfr\x8e\xf8\xe4\'?\xa9\x99\xc1\xb4\xc15\xd7\\\x83\r\xf0\xc0\x03\x0f\xbc\ +\xd8\xd72\x83\x17\x00\xdf\xbe\xe1Fq\xcd5\xd7h\xbb\xb1\xe1[\xdf\xfa\xd6\x8by=\ +3x\x01\xf0\xed\x1bn\x04@\xbe\xc8\xd71\x83\x17\x013\xa4OC\xcc\x90>\r1C\xfa4\ +\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\ +\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\ +\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\ +\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\ +\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\x103\xa4OC\xcc\x90>\r1C\xfa4\xc4\x0c\xe9\xd3\ +\x103\xa4OC\xcc\x90>\ra?\xf7.S\x05j\xa7\xcf\xbb\xb6\xd7\xddM})P-\xbf\x97\xcd\ +w\xdd\xfc~\xb2\xe3\xb7\x9eG\x81V\xd9\x19D\xf6#9\xf1\x1aZO.&\xb9\x18\xd1\xf2\ +\xeb\xc9.\xb4\xe5\xd0\x8d\xf7\x89\x87T\x13\xf7\xc9\xce\xbd\xf3i\x9b\xf7\xa1\ +\x15\x88\xc9\xe9\xddOHW@\xda\xf2\xb9q\xe7r\xc2MON\x9bB\xa2\x10\xc4\xd9\x1e6`\ +\xa1\xb3o\xccQT\xd60tF\xae\xccN!\x18\'\xbd\xde\xf2\x9d\x95\x1dg\x92\x07\xaf[\ +64\xfe\x97\xa0\x859\xbbl\xec\xa3v\xfaa\xa4\xc1\x12\xe0\x8c\xef\x9ff\xbbY\x80\ +\xa5Ss\xfeD\x83\x10 ]\xb4\xcc\xae:\xdb\x14\x85u\xa4N\xf0<\x07t\x0c\xa20\xe9\ +\x13\xd9OH\x87]%u\xf2oa\\ 4*k\x1ez\xc2\x1e\r\xc2\xc7\x05G\xeet\x04\x05\xda\ +\xca\xa4\xb9\xf1\xbde\x9e,\xd2H\x90\xb0\x98\x8c;!\x0cI\xe6\xe0\x89aD\x02\x08\ +\xf4\xce\x8f\xbb\xd1(\x04`7~\xd8\xd0J\x12\x8d R \xe2\x98\xbcT 5H\x1b\xa4h^\ +\xb9\x06\x94\x80z\xa8\xf1\x1c\x1fGj\xd2$\xc4\x92\xbb\xef\xb9\xf7#\xd2w&f\xd7\ +o\x1bhH\x94yo\xfc\xc6\xa5\xf1\xe0\x1bD\xb5\xeegag\x92\xdc\xd0"v\x93M-$\xc8`\ +\xc2\xefZ\x05\xba\x01A\x82 F\x93"D\x84$E\x88\x86v\xf1\xb1\xd0\x08\xb2\xc6\ +\xd4P"\x8d\x83\xc8\x04D\n*F\xa5)JZ\xd8\xb6\x87\x94\x16\x96g\x83\xca$]+\xe2zJ\ +\xa4b\xb4t\xb0}\x1b\x04\xd8\x9e \x05R\x04\xca\xf2\xd1)\x14w\xc3\xfb~D:d\xca1\ +{\x9f\xfc\x8e\x04\x99\xc0\xa0\xb0\x9a\xf4\x8cKY\xabdN8\x82nl\x91\x93\xf6\xcf\ +\x91\x98\xd8\xc1\xec|U\x13\x8f\xdch\\2kt\x12\x81\x06\x92\x96\xe3\xb6\x9c]$\ +\xa8z\x19)\x15\xb8\x16\xd2\xb2\x91\xd84\x0c\x810V\xb8\xae\xd3<\x8b\xb0M/\xa0\ +Z\xce\xaa\x80zj4\x8d+\xd9#\xb3\xfb\r\xe9:\x93@\xb1\xd3\xf6\x9d?\x1b\x8e[\x1f\ +\x87\xc1\xcej\xb8\xf5\xf7\xb2\xf5\x0b\xbd\xd3;\xe6A\xb6jbh4\xac\xf1&(\xb2\ +\x06#qv9\x911\x0fB n\xd9\xa0\x0c\xf1\xd9Ae>\xa0A\xa3i0\xc6n\xd0\x02\x12GRJ \ +\x95`\xc9\xac\x8fo9\xbe\x02*U\xd8\xb8i\x1b###tuu1\xff\x80^\xda\xbcIn\x98\xfd\ +\x86\xf4\xbd\x18Y6\xc8n<\xd0\xe6\x937\xca\xbbU\x9dC\x0bY\x93\x11\xde\xd8\xa1\ +y\\\x1ar\xd7\xfc\x9d\xd5\xd8eg=\xbf\x13\xe1\xbaq,\xb1\xd3=\x08\x89F\xa2\x04\ +\xe6\x1d\x9b\x88\xc4\xd8iXXB \xb2k\x8e\x05Tm\xd3d$F_lZ\x17\xf3\xe4\xc3\x0f\ +\xf0\xd8\xc3\x0f\xd0\xb7e\x1d\xe5\x91!.\xbe\xe8B.\xbe\xf0\x02\xda\x0b\xd6n\ +\xb5\x12\xec7\xa4\xc3\xee\x89o\xe9\xe7w?f\x9b\xf0\xef8\xd9\x8d\xe1Mv\xfcV\ +\xb5\xa1\xc9\xfax@Il\xdd\xf2\xb0Z\x89nj\x95\x16\xa3O\xec\xfa\xafi<\x99\xf4\ +\xe24\xc7\x12\x8d\xf7\x10\x00;k\x04\x06\xe5\x10\xb6\xf7\xc1\xd6Q\xb8\xeb\xe9\ +ml\x19\x1ef\xe33O\xb2y\xcdcD#}\xcciw9b\xf9B\x0e?\xe4P\xae~\xc3\x15\xb4\x07\ +\x0e\xb6HI\xaa\x15r\xbe\x0f\xb2U\x1f\x8cc?"}o\xd1h\x04\xc20\xab\'6\x16\xa3\ +\x8a\x13 \x1d\'\xb59\xe6\xb6h\x92\'\x94y\xa9l\x18\xa7\xedI\x1aUf\x9d\x8bL\ +\xb34U\x819F\x9a\xd9\x01\xa6\x8b\xb5@\x0b\xb4\xb0\x891dG\x18\xb2\x13\xa0?\ +\x86M[S\x1e{\xeaY\x9exr-\x9b7\xf532P\xa1R\x8e\x18K,\xca\xb9"\xb1\x10\x88\xa8\ +\x84\xabl\x16.Z\xc6\xe9G\xaf\xe2\xd5\xe7\x9e\xc2\x91K\x0bx\x80\xd7\xb8\x7fK\ +\x82\xae\x01\xfb\xf9\x90m\x97\xbe{\xb7;\xecl\xc7\x9bm\xf5(\xc2w]\xc2p\x0c\ +\xdf\x95f\x1c\xab\x12P\nl\x1f\xa4\x83q\x81\x88\x89G\x91\nR\x05:1d\x8a\x86\ +\xd2m4\x18\xd5\xb4\xc6+\xd5\x1a^\xae\x8d\xc1J\x85B>O\x04\xd44\x14\x04\xe4\ +\x91\xa4\x11\x8cE\x1a\xbf(\xd8R\x82\xdf<\xd4\xc7\xedw\xdf\xc7#k\xd61X\xa9\ +\x93\x08\x9b0\x11D1\xd8V\x8e\xc0mC\xdb6\xd54\xc2\xb2]\x92h\x0cG\xa7\xac\\\ +\xb1\x8c\xd7]x&\x17\x9f\xd2C\x0e\xa8\xd7\xa0\x18`\xeeIG\xec\xde\xe44\xd8oH\ +\xdf\x1b(\x05J)\xb46"i\xdb6B\x18\xbb\xd9q]@\x19)\x17"s^\xa4\x18\tU\x90jb)QBd\ +t\n\xa4\x16\xc6\x95c\x8f\xabI\x9di\x84\xc68?\xd3\x19D@\x92\xf3\xa8\x01:\x9fg\ +\x04\xd8Q\x87\x8d\x9b\xca<\xf3\xd0\xef\x91\x03\x9bx\xd7\x9f\\I\xe0\x19\xe9\ +\x969\xf8\xc2W\xbf\xc3\xc6\xa1*\xa9\x97g8tHq\xb0\xfd\x80\xa0\xadH\x12\x0bv\ +\x94C\x84V\x14\x02\x97\xda\xe0f\x8a~\xc2KO9\x8e\xb7^y\x06\x07\xf7\x18\x034\ +\xaaAOn\x12\xaf\xe3\x1e\xa4d\xff!}\xa2\xcf\xb4e\xf3\xb8d\x0bI\xd3)\xd1\xb4\ +\xdf5\xa4\x99W,\xd2\x8a\x9c\xe7\xd3\xe8A\x8d\xd4\x02\xc2&\xc4%\x15\xb2\xd9\ +\xc7\x02XB\x90f\x8f(!#\x96\xf1.=e\\M?\xb5\x06\xd6m\x1e\xe0\xb1\'\xd7\xb1nc\ +\x1fO?\xb3\x1e\xe9\xfa(m1\xb8\xeea\xbe\xf0\x81\xd7S\xaaA)\x821\x05\xff\xf9\ +\x83\x87\xd98\x10\xe2v\x1f\xc0h\xa4\xc9\xe5<\xeaI\x8a\xd6\x82\x18\x1bK\x82\ +\xe3iD*\xf0\xa8q\xf8\x01\x05.;\xffd^~\xd6\xa1t\xb9\x868W\x80\x93\x83\xb8^\ +\xc7\xf62\x0f\xa1t\x19\xb7\\&\xc7\xfeCz\x03\x13\xfc\xcf\x06\n\xa8\xd5C\x10\ +\x16\x96ec\xb7v\xbfb\xdc\xa5\x19\'\x16\x81\x03B%\xe3\xbf\xb4l\xd2\xd4#\xb1lB\ +\xa0\x8e!\xb2q\x1a/{/\x01\xdb\x87a\xe3\x96:k7la\xdd\xc6\xed\xac\xd9\xb4\x95\ +\r\x9b\xfb\x19\x18.#\x1c\x9f(\x11X\xd2\xa3\xad\xb3\x8b\xb1\xb4\x83\x9c\xc8\ +\x11F1G\x1c\x7f&\xe7_p4\x0eP\x08`\xcdZ\xf8\xfe-?\x03\xa7H-\xb6(\x8d\x96!/\ +\xc0\xb2 \rI*#t\xe4\x02\x0e9\xf0\x00\x0e9\xe8 \x96\xcf\xf29\xe7\x88v\x96\xf4\ +@\xde5\xd7\x13\xd7S\xb4N\xb0\x02\xc78o\xa0\xe91L\x91\xa4\x18w\xd4d\xd8\xffH\ +\x87I\x89\xf7}\xaf\xe9\xa1\x0fS\x9a\x8d\xbd\xe1\xf4J\x00\x1c#E\x8e\xc8\x91VK\ +\xd8\xb9 \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 \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 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\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>\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 \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\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
\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> \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 \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 \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\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 \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 \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+\ +\xedrc\xcf\x97\xca\xa1\xc8ma\xcbK\x03\xe3\x82l7\x86*C_\x86\xc6\xe4\\\x91\x95\ +\x92)!wp\x18C\x95a\x0c\x86b\xb2\xae\xca\x8b\xc9\x94\x90;0\x8c\xa1\xca06C\xaf\ +\xca\xac)wP\x18C\x95a\x1c\x86b\xf2F\x97; \x8c\xa1\xca0\x16C1\x995\xe5J\xc9\ +\x1e\x08\xc6Pe\xf8\x1e\x86\xd6\x96=\x08\xc6\xc9)JA*\xdd\xa4G\xdepc\x8c\xd7\ +\xa0\xca\x18\xd3\x1a\xe35h\x0c5\x99\xa4\x88\xa5c6h\xedD5\xc5\x18\xb3A{0F\tK\ +\xc7lP\xe5,\xaa\x891\x1b\xb46\x14\x7f0\xe6XP\x86\xf1\x1c\x0b1\xd9w\x93\xdf\t\ +\xc6Pe\x18\x97\xa1\x98|\xc92\x8c\xa1\x86;\xc0\x18ZCvG\x18C\x95\xc1\x18ZCvG\ +\x18C\x95a\xfc\xdd8[B\x01C\xd7\x06}\xddc1\xab!\x0fc\xa8\x0b\x05\x0c\xfdN\xe7\ +|\x0c\nBh[\x97[\xf8\xb4\x8dr\x87\x831\xd4\x85\x02\x86\xde\xabAk3\xc8\x18Z\ +\x11\n\xd8\xd2\x03\xe36\xa8\xa1\x08\xd6\xa0\xca`\xae?e\x18\xd79\xdfz\xca")\ +\xbf#\x8c\xa1\xca`\x0c\x95\x96\xdd\x19\xc6Pe\x18\x93\xa15m\x9cbv\x02\x18C\ +\xd5\xe1>\x0c\xad%[\x11;\x01\x8c\xa1\xea0\x1eCmt{\t\xc6Pe\xf8\x1e\x86*\xf6\ +\x0e\xb90\x86*\xc3X\x0cm\xc9Ne\xcc\xdc1N\xf8\xacV@\xfb\xcb\x02\xe5\xe34(\x86\ +Z\xbbp*\xc6\xd8\rZ\x0bJ\xbb[\x80Q\x1a\xd4\xf6\x9b\x17\xc3\x18\rj\x10C\xff\ +\x06\xada\xe7Fd\xe7~\xf0\xeb\xe9\xe0\xd8\x9f\xf0wp\x8d\x87\xbe\x87\xd9\xd5\ +\xa8\xf8a\x1a\xd3;\x16\xeb\x05\x02\x87\xd9\xe5\xa7\x97\xe6XP\x86\xcf\xea\xb3\ +\x92/\xf8\x8acA\x9aI\xa9\xee\xa9*;\x079\x10v{Gc\xa82\x9c\xd7\x87^=.\x92\xfa\ +\xbc$;\x9b3\x93z \xec\xfb\xfc\xcc\xe9\x1a\xac\xd7\x83\xe3)\x1d\xf9\xe0\\\xc3\ +\x8e\xd0\xb6\x03a\xbf\x03\xe3\xac\xe0\x96\xb0\xbf%\xb2(eQl!:\x02\xc5X\xec1\ +\xd7gm\xecZt\x9cs\xd6\xc5\x18\xaa\x0cc0tHv\x96\x8cV==\x83\xe7"e\xc4X\xee\xcb\ +\xc0\x9e\xf7\xf42\x86*\x03\x9d\xa1\x943O\xb0\xd1\x18\x17\xa5g\xabH2\x13 2\ +\x92\x8d<\x120)g;\xbd\xe7r\x0c>d@\xa2\x97X\x7f\x1bC\x95\xa1\x1eC\xb1\xeb\x92\ +\x10\x9fcF\x18\xc0\xb5\xef\xcd=E\xe7\xb63\x86*\x03\x9e$F\xf5\xe4\xc4\x18\xca\ +\xf1\xdepX\\\xc3\xf3\x83\xb2\x8a9\xeaGmhLF\xc1\x88\xd9/\xcb}\xce\xbboe\xe8<\ +\xd3\x074\xa9\xfb^\x8cgs\xe5\xe4\x9e\x15A\xda\xd1\xcd\x92S\xd4Ug\xa6-\xb1g\ +\x0e=\xc3\xe7>\xf1Pj\xe5\xa6\xee\xcb\xc9(\xb1\xa9\xb5"%SD\xb6DyU\xa3;o8\x1au\ +\x87\xf3.\x1f\x86\x8e\x86\xea\x01\xe9RVyp\xbb@\x00\x9e\x9c\xec\xb4\x85\xfa\ +\xdc\xa7\x8b\xef\x9f\x82\xe2\xe3\x05m\x1a\x13+\xb7\x9b\x1c&b>]\x18\x89\xa1\ +\xcdRD\xa8\x8eu\x02\xb2\xae9\xa2.\xc5z\x84\x8e\x8f\xf3n\x9c\xad\x12\x93\x87J\ +\xa5\x1c \xd2T\x0c\xdf\x9e\x1e\xbe\\\x8f\xa1\xb5\x1a\xb6wC^\xb1w(\x84\xa6;\ +\xa5\t\x06\xae\x0c\x00\x0bpk\xc4\xb5\xd5gw\xc2e{\xe7B\xca\x0e_\xb5\xa1\xa1.\ +\xc6Pe\xf8\x0e\x86J\xb8\xf7\x0ePSCJ\xf4*\x94\x03fC\xd5\xe2\x0b\x18\x9a\xf6}\ +\xb2Q\x93\xed\x02z\x19C\x95\xe1\x0b\x19z\xf1]\xc5\xec\xde\xa6\x9b1\xd4\x90\ +\x82r\x86\nET0\x99\x83\xb2\xdd\x18\xaa\x0c\xba\x19*\xea\x1d\x82l\xfa\x07\x1f\ +\x826t\x93g\x0cU\x06\xdd\x0c\xbd\x1a\r\xc1\xe4\x01\x0cjCWyx\xf8L\x0b\xbe\xae\ +\xcb\x1d1\x05E\n\xe2;\x85"\xe9&\x03B1C\x89\xebR\xa8`\xe5\xde2eJ\xe9\x08\x9a\ +\x19Z\x1dc\x8e;\xf42\x14M7a\xae!\xe1\xaeQ)\xbd&\xb1\x0b\xcav\xcd\x18\xaa\x0c\ +\xd6\xa0\xca`\x8e\x05eP\xecX\xa8=\xca\xbd(\xcf\x95)8\xca5\x86*\x83^\x86\xfa\ +\xa3\\\t\xb7_\xd5P\x1c\x80D;\x18C\x95\xe17\x00\xc0\xd4{\x9f"\x83\x18\xfe\x07\ +\xd2\x8b\x12\xe7\xc3\x8c\xd4\xb6\x00\x00\x00\x00IEND\xaeB`\x82' + +def getWizardBitmap(): + return BitmapFromImage(getWizardImage()) + + +def getWizardImage(): + stream = cStringIO.StringIO(getWizardDataOld()) # NOTE: This reverts us to the bitmap Peter likes. + return ImageFromStream(stream) + diff --git a/wxPython/samples/ide/activegrid/tool/XmlEditor.py b/wxPython/samples/ide/activegrid/tool/XmlEditor.py new file mode 100644 index 0000000000..f82ed7d5c9 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/XmlEditor.py @@ -0,0 +1,165 @@ +#---------------------------------------------------------------------------- +# Name: XmlEditor.py +# Purpose: Abstract Code Editor for pydocview tbat uses the Styled Text Control +# +# Author: Peter Yared +# +# Created: 8/15/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + + +import wx +import string +import STCTextEditor +import CodeEditor + + +class XmlDocument(CodeEditor.CodeDocument): + + pass + + +class XmlView(CodeEditor.CodeView): + + + def GetCtrlClass(self): + """ Used in split window to instantiate new instances """ + return XmlCtrl + + + def GetAutoCompleteHint(self): + pos = self.GetCtrl().GetCurrentPos() + if pos == 0: + return None, None + + validLetters = string.letters + string.digits + '_:' + word = '' + while (True): + pos = pos - 1 + if pos < 0: + break + char = chr(self.GetCtrl().GetCharAt(pos)) + if char not in validLetters: + break + word = char + word + + return None, word + + + def GetAutoCompleteDefaultKeywords(self): + return XMLKEYWORDS + + +class XmlService(CodeEditor.CodeService): + + + def __init__(self): + CodeEditor.CodeService.__init__(self) + + +class XmlCtrl(CodeEditor.CodeCtrl): + + + def __init__(self, parent, ID = -1, style = wx.NO_FULL_REPAINT_ON_RESIZE): + CodeEditor.CodeCtrl.__init__(self, parent, ID, style) + self.SetLexer(wx.stc.STC_LEX_XML) + self.SetProperty("fold.html", "1") + + + def GetMatchingBraces(self): + return "<>[]{}()" + + + def CanWordWrap(self): + return True + + + def SetViewDefaults(self): + CodeEditor.CodeCtrl.SetViewDefaults(self, configPrefix = "Xml", hasWordWrap = True, hasTabs = True) + + + def GetFontAndColorFromConfig(self): + return CodeEditor.CodeCtrl.GetFontAndColorFromConfig(self, configPrefix = "Xml") + + + def UpdateStyles(self): + CodeEditor.CodeCtrl.UpdateStyles(self) + + if not self.GetFont(): + return + + faces = { 'font' : self.GetFont().GetFaceName(), + 'size' : self.GetFont().GetPointSize(), + 'size2': self.GetFont().GetPointSize() - 2, + 'color' : "%02x%02x%02x" % (self.GetFontColor().Red(), self.GetFontColor().Green(), self.GetFontColor().Blue()) + } + + # White space + self.StyleSetSpec(wx.stc.STC_H_DEFAULT, "face:%(font)s,fore:#000000,face:%(font)s,size:%(size)d" % faces) + # Comment + self.StyleSetSpec(wx.stc.STC_H_COMMENT, "face:%(font)s,fore:#007F00,italic,face:%(font)s,size:%(size)d" % faces) + # Number + self.StyleSetSpec(wx.stc.STC_H_NUMBER, "face:%(font)s,fore:#007F7F,size:%(size)d" % faces) + # String + self.StyleSetSpec(wx.stc.STC_H_SINGLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + self.StyleSetSpec(wx.stc.STC_H_DOUBLESTRING, "face:%(font)s,fore:#7F007F,face:%(font)s,size:%(size)d" % faces) + # Tag + self.StyleSetSpec(wx.stc.STC_H_TAG, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + # Attributes + self.StyleSetSpec(wx.stc.STC_H_ATTRIBUTE, "face:%(font)s,fore:#00007F,bold,size:%(size)d" % faces) + + +class XmlOptionsPanel(STCTextEditor.TextOptionsPanel): + + def __init__(self, parent, id): + STCTextEditor.TextOptionsPanel.__init__(self, parent, id, configPrefix = "Xml", label = "XML", hasWordWrap = True, hasTabs = True) + + +XMLKEYWORDS = [ + "ag:connectionstring", "ag:datasource", "ag:editorBounds", "ag:label", "ag:name", "ag:shortLabel", "ag:type", + "element", "fractionDigits", "length", "minOccurs", "name", "objtype", "refer", "schema", "type", "xpath", "xmlns", + "xs:complexType", "xs:element", "xs:enumeration", "xs:field", "xs:key", "xs:keyref", "xs:schema", "xs:selector" + ] + + +#---------------------------------------------------------------------------- +# Icon Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getXMLData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x01\x18IDAT8\x8d\xed\x92=N\xc3P\x0c\xc7\x7f~\xc9K\xd2\xa0\x16\xa9\xdc\ +\x84+\xf4\x06\x1c\xa1\x12\x133\xa7`\xea\x05\xba\xc0\xc0\xd0\x93\x80*uc``e\t\ +\x82\xb6J\xf3Q?3D\x04\x81`\xea\xc2\x80\x17K\xb6\xfc\xff\xb0-ff\x1c\x10\xee\ +\x90\xe1\xbf\x01\x10s}\x0em\tu\t\xfb\x06\xcbFP\xad\x11\x17\x81\x196\x18!\xdb\ +\x02\xd2#hk\xc8\x8f\t\xc1p\x89g\xb9\\\x11\xdb\xfd-\xbcn\x91\xa8C\x94,\x81\ +\xaa\xe9\x19\xe4\x1b\xa3}R\xf3\xf0\x08\x0e\x9f\x81\xef\x9c\x94s\x83\xaa\xe92\ +P\xcf\nv\xa7g\xd4\xb3\xa2\xef\xaf\xc5#i\x04\x89#\x8a\x05\'m\r)\x84\r\xe4S\ +\xa1\x9c\x1b\xf9\xb4\xe3\xd5\xe1\x18?\xb9@\x87\xe3^\x81\xbe\xb5H\xab`\x013\ +\xc3\xa9\xf3h\x15pC\xfa\xe1\x0f\x05\x00\xf1\xd5\xe4\x8b\x85la\x10@[0q\x88]\ +\x9e\x18/\x05\xe8/k\xde\x01\x83\x1f\xea\x19,\x9e\x1c\xf1\xcdj\xc3\xae\x01jP\ +\x05\x9fv\x07q1\x88\x83(\x8f\xd0\x8d"1h\x05\xba\x077\x80$\x87\xbb\xe7\x80\ +\xfc\xbf\xf2\xe1\x00\xef\x8c\xb8x\x06\x07\xd1$\xff\x00\x00\x00\x00IEND\xaeB`\ +\x82' + + +def getXMLBitmap(): + return BitmapFromImage(getXMLImage()) + +def getXMLImage(): + stream = cStringIO.StringIO(getXMLData()) + return ImageFromStream(stream) + +def getXMLIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getXMLBitmap()) + return icon diff --git a/wxPython/samples/docview/activegrid/tool/__init__.py b/wxPython/samples/ide/activegrid/tool/__init__.py similarity index 100% rename from wxPython/samples/docview/activegrid/tool/__init__.py rename to wxPython/samples/ide/activegrid/tool/__init__.py diff --git a/wxPython/samples/ide/activegrid/tool/checker.py b/wxPython/samples/ide/activegrid/tool/checker.py new file mode 100644 index 0000000000..985222a1dc --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/checker.py @@ -0,0 +1,896 @@ +#!/usr/bin/env python + +# Copyright (c) 2001-2004, MetaSlash Inc. All rights reserved. + +""" +Copyright notice from pychecker: + +Copyright (c) 2000-2001, MetaSlash Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + + - Neither name of MetaSlash Inc. nor the names of contributors + may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +""" +Check python source code files for possible errors and print warnings + +Contact Info: + http://pychecker.sourceforge.net/ + pychecker-list@lists.sourceforge.net +""" + +import string +import types +import sys +import imp +import os +import glob +import traceback +import re +import wx +_ = wx.GetTranslation + +# see __init__.py for meaning, this must match the version there +LOCAL_MAIN_VERSION = 1 + + +def setupNamespace(path) : + # remove pychecker if it's the first component, it needs to be last + if sys.path[0][-9:] == 'pychecker' : + del sys.path[0] + + # make sure pychecker is last in path, so we can import + checker_path = os.path.dirname(os.path.dirname(path)) + if checker_path not in sys.path : + sys.path.append(checker_path) + +if __name__ == '__main__' : + setupNamespace(sys.argv[0]) + +from pychecker import utils +from pychecker import printer +from pychecker import warn +from pychecker import OP +from pychecker import Config +from pychecker import function +from pychecker.Warning import Warning + +# Globals for storing a dictionary of info about modules and classes +_allModules = {} +_cfg = None + +# Constants +_DEFAULT_MODULE_TOKENS = ('__builtins__', '__doc__', '__file__', '__name__', + '__path__') +_DEFAULT_CLASS_TOKENS = ('__doc__', '__name__', '__module__') + +_VERSION_MISMATCH_ERROR = ''' +There seem to be two versions of PyChecker being used. +One is probably in python/site-packages, the other in a local directory. +If you want to run the local version, you must remove the version +from site-packages. Or you can install the current version +by doing python setup.py install. +''' + +def cfg() : + return utils.cfg() + +def _flattenList(list) : + "Returns a list which contains no lists" + + new_list = [] + for element in list : + if type(element) == types.ListType : + new_list.extend(_flattenList(element)) + else : + new_list.append(element) + + return new_list + +def getModules(arg_list) : + "Returns a list of module names that can be imported" + + global _output + + new_arguments = [] + for arg in arg_list : + # is this a wildcard filespec? (necessary for windows) + if '*' in arg or '?' in arg or '[' in arg : + arg = glob.glob(arg) + new_arguments.append(arg) + + PY_SUFFIXES = ['.py'] + PY_SUFFIX_LENS = [3] + if _cfg.quixote: + PY_SUFFIXES.append('.ptl') + PY_SUFFIX_LENS.append(4) + + modules = [] + for arg in _flattenList(new_arguments) : + fullpath = arg + # is it a .py file? + for suf, suflen in zip(PY_SUFFIXES, PY_SUFFIX_LENS): + if len(arg) > suflen and arg[-suflen:] == suf: + arg_dir = os.path.dirname(arg) + if arg_dir and not os.path.exists(arg) : + txt = _('File or pathname element does not exist: "%s"') % arg + _output.AddLines(txt) + continue + + module_name = os.path.basename(arg)[:-suflen] + if arg_dir not in sys.path : + sys.path.insert(0, arg_dir) + arg = module_name + modules.append((arg, fullpath)) + + return modules + +def _q_file(f): + # crude hack!!! + # imp.load_module requires a real file object, so we can't just + # fiddle def lines and yield them + import tempfile + fd, newfname = tempfile.mkstemp(suffix=".py", text=True) + newf = os.fdopen(fd, 'r+') + os.unlink(newfname) + for line in f: + mat = re.match(r'(\s*def\s+\w+\s*)\[(html|plain)\](.*)', line) + if mat is None: + newf.write(line) + else: + newf.write(mat.group(1)+mat.group(3)+'\n') + newf.seek(0) + return newf + +def _q_find_module(p, path): + if not _cfg.quixote: + return imp.find_module(p, path) + else: + for direc in path: + try: + return imp.find_module(p, [direc]) + except ImportError: + f = os.path.join(direc, p+".ptl") + if os.path.exists(f): + return _q_file(file(f)), f, ('.ptl', 'U', 1) + +def _findModule(name) : + """Returns the result of an imp.find_module(), ie, (file, filename, smt) + name can be a module or a package name. It is *not* a filename.""" + + path = sys.path[:] + packages = string.split(name, '.') + for p in packages : + # smt = (suffix, mode, type) + file, filename, smt = _q_find_module(p, path) + if smt[-1] == imp.PKG_DIRECTORY : + try : + # package found - read path info from init file + m = imp.load_module(p, file, filename, smt) + finally : + if file is not None : + file.close() + + # importing xml plays a trick, which replaces itself with _xmlplus + # both have subdirs w/same name, but different modules in them + # we need to choose the real (replaced) version + if m.__name__ != p : + try : + file, filename, smt = _q_find_module(m.__name__, path) + m = imp.load_module(p, file, filename, smt) + finally : + if file is not None : + file.close() + + new_path = m.__path__ + if type(new_path) == types.ListType : + new_path = filename + if new_path not in path : + path.insert(1, new_path) + elif smt[-1] != imp.PY_COMPILED: + if p is not packages[-1] : + if file is not None : + file.close() + raise ImportError, "No module named %s" % packages[-1] + return file, filename, smt + + # in case we have been given a package to check + return file, filename, smt + + +class Variable : + "Class to hold all information about a variable" + + def __init__(self, name, type): + self.name = name + self.type = type + self.value = None + + def __str__(self) : + return self.name + + __repr__ = utils.std_repr + + +def _filterDir(object, ignoreList) : + "Return a list of tokens (attributes) in a class, except for ignoreList" + + tokens = dir(object) + for token in ignoreList : + if token in tokens : + tokens.remove(token) + return tokens + +def _getClassTokens(c) : + return _filterDir(c, _DEFAULT_CLASS_TOKENS) + + +class Class : + "Class to hold all information about a class" + + def __init__(self, name, module) : + self.name = name + self.classObject = getattr(module, name) + + modname = getattr(self.classObject, '__module__', None) + if modname is None: + # hm, some ExtensionClasses don't have a __module__ attribute + # so try parsing the type output + typerepr = repr(type(self.classObject)) + mo = re.match("^ $", typerepr) + if mo: + modname = ".".join(mo.group(1).split(".")[:-1]) + + self.module = sys.modules.get(modname) + if not self.module: + self.module = module + + global _output + txt = _("warning: couldn't find real module for class %s (module name: %s)\n") % (self.classObject, modname) + _output.AddLines(txt) + + self.ignoreAttrs = 0 + self.methods = {} + self.members = { '__class__': types.ClassType, + '__doc__': types.StringType, + '__dict__': types.DictType, } + self.memberRefs = {} + self.statics = {} + self.lineNums = {} + + def __str__(self) : + return self.name + + __repr__ = utils.std_repr + + def getFirstLine(self) : + "Return first line we can find in THIS class, not any base classes" + + lineNums = [] + classDir = dir(self.classObject) + for m in self.methods.values() : + if m != None and m.function.func_code.co_name in classDir: + lineNums.append(m.function.func_code.co_firstlineno) + if lineNums : + return min(lineNums) + return 0 + + + def allBaseClasses(self, c = None) : + "Return a list of all base classes for this class and it's subclasses" + + baseClasses = [] + if c == None : + c = self.classObject + for base in c.__bases__ : + baseClasses = baseClasses + [ base ] + self.allBaseClasses(base) + return baseClasses + + def __getMethodName(self, func_name, className = None) : + if func_name[0:2] == '__' and func_name[-2:] != '__' : + if className == None : + className = self.name + if className[0] != '_' : + className = '_' + className + func_name = className + func_name + return func_name + + def addMethod(self, method, methodName = None) : + if type(method) == types.StringType : + self.methods[method] = None + else : + assert methodName is not None, "must supply methodName" + self.methods[methodName] = function.Function(method, 1) + + def addMethods(self, classObject) : + for classToken in _getClassTokens(classObject) : + token = getattr(classObject, classToken, None) + if token is None: + continue + + # Looks like a method. Need to code it this way to + # accommodate ExtensionClass and Python 2.2. Yecchh. + if (hasattr(token, "func_code") and + hasattr(token.func_code, "co_argcount")): + self.addMethod(token, token.__name__) + + elif hasattr(token, '__get__') and \ + not hasattr(token, '__set__') and \ + type(token) is not types.ClassType : + self.addMethod(getattr(token, '__name__', classToken)) + else : + self.members[classToken] = type(token) + self.memberRefs[classToken] = None + + self.cleanupMemberRefs() + # add standard methods + for methodName in ('__class__',) : + self.addMethod(methodName, classObject.__name__) + + def addMembers(self, classObject) : + if not cfg().onlyCheckInitForMembers : + for classToken in _getClassTokens(classObject) : + method = getattr(classObject, classToken, None) + if type(method) == types.MethodType : + self.addMembersFromMethod(method.im_func) + else: + try: + self.addMembersFromMethod(classObject.__init__.im_func) + except AttributeError: + pass + + def addMembersFromMethod(self, method) : + if not hasattr(method, 'func_code') : + return + + func_code, code, i, maxCode, extended_arg = OP.initFuncCode(method) + stack = [] + while i < maxCode : + op, oparg, i, extended_arg = OP.getInfo(code, i, extended_arg) + if op >= OP.HAVE_ARGUMENT : + operand = OP.getOperand(op, func_code, oparg) + if OP.LOAD_CONST(op) or OP.LOAD_FAST(op) : + stack.append(operand) + elif OP.STORE_ATTR(op) : + if len(stack) > 0 : + if stack[-1] == cfg().methodArgName: + value = None + if len(stack) > 1 : + value = type(stack[-2]) + self.members[operand] = value + self.memberRefs[operand] = None + stack = [] + + self.cleanupMemberRefs() + + def cleanupMemberRefs(self) : + try : + del self.memberRefs[Config.CHECKER_VAR] + except KeyError : + pass + + def abstractMethod(self, m): + """Return 1 if method is abstract, None if not + An abstract method always raises an exception. + """ + if not self.methods.get(m, None): + return None + func_code, bytes, i, maxCode, extended_arg = \ + OP.initFuncCode(self.methods[m].function) + # abstract if the first conditional is RAISE_VARARGS + while i < maxCode: + op, oparg, i, extended_arg = OP.getInfo(bytes, i, extended_arg) + if OP.RAISE_VARARGS(op): + return 1 + if OP.conditional(op): + break + return None + + def isAbstract(self): + """Return the method names that make a class abstract. + An abstract class has at least one abstract method.""" + result = [] + for m in self.methods.keys(): + if self.abstractMethod(m): + result.append(m) + return result + +def _getLineInFile(moduleName, linenum): + line = '' + file, filename, smt = _findModule(moduleName) + try: + lines = file.readlines() + line = string.rstrip(lines[linenum - 1]) + except (IOError, IndexError): + pass + file.close() + return line + +def importError(moduleName): + exc_type, exc_value, tb = sys.exc_info() + + # First, try to get a nice-looking name for this exception type. + exc_name = getattr(exc_type, '__name__', None) + if not exc_name: + # either it's a string exception or a user-defined exception class + # show string or fully-qualified class name + exc_name = str(exc_type) + + # Print a traceback, unless this is an ImportError. ImportError is + # presumably the most common import-time exception, so this saves + # the clutter of a traceback most of the time. Also, the locus of + # the error is usually irrelevant for ImportError, so the lack of + # traceback shouldn't be a problem. + if exc_type is SyntaxError: + # SyntaxErrors are special, we want to control how we format + # the output and make it consistent for all versions of Python + e = exc_value + msg = '%s (%s, line %d)' % (e.msg, e.filename, e.lineno) + line = _getLineInFile(moduleName, e.lineno) + offset = e.offset + if type(offset) is not types.IntType: + offset = 0 + exc_value = '%s\n %s\n %s^' % (msg, line, ' ' * offset) + elif exc_type is not ImportError: + global _output + txt = _(" Caught exception importing module %s:\n") % moduleName + _output.AddLines(txt) + + try: + tbinfo = traceback.extract_tb(tb) + except: + tbinfo = [] + txt = _(" Unable to format traceback\n") + _output.AddLines(txt) + for filename, line, func, text in tbinfo[1:]: + txt = _(" File \"%s\", line %d") % (filename, line) + _output.AddLines(txt) + if func != "?": + txt = _(", in %s()") % func + _output.AddLines(txt) + _output.AddLines("\n") + if text: + txt = _(" %s\n") % text + _output.AddLines(txt) + + # And finally print the exception type and value. + # Careful formatting exc_value -- can fail for some user exceptions + txt = " %s: " % exc_name + _output.AddLines(txt) + try: + txt = str(exc_value) + '\n' + _output.AddLines(txt) + except: + txt = _('**error formatting exception value**\n') + _output.AddLines(txt) + + +def _getPyFile(filename): + """Return the file and '.py' filename from a filename which could + end with .py, .pyc, or .pyo""" + + if filename[-1] in 'oc' and filename[-4:-1] == '.py': + return filename[:-1] + return filename + +class Module : + "Class to hold all information for a module" + + def __init__(self, moduleName, check = 1, fullpath = None) : + self.moduleName = moduleName + self.variables = {} + self.functions = {} + self.classes = {} + self.modules = {} + self.moduleLineNums = {} + self.attributes = [ '__dict__' ] + self.main_code = None + self.module = None + self.check = check + self.fullpath = fullpath + _allModules[moduleName] = self + + def __str__(self) : + return self.moduleName + + __repr__ = utils.std_repr + + def addVariable(self, var, varType) : + self.variables[var] = Variable(var, varType) + + def addFunction(self, func) : + self.functions[func.__name__] = function.Function(func) + + def __addAttributes(self, c, classObject) : + for base in classObject.__bases__ : + self.__addAttributes(c, base) + c.addMethods(classObject) + c.addMembers(classObject) + + def addClass(self, name) : + self.classes[name] = c = Class(name, self.module) + try: + objName = str(c.classObject) + except TypeError: + # this can happen if there is a goofy __getattr__ + c.ignoreAttrs = 1 + else: + packages = string.split(objName, '.') + c.ignoreAttrs = packages[0] in cfg().blacklist + if not c.ignoreAttrs : + self.__addAttributes(c, c.classObject) + + def addModule(self, name) : + module = _allModules.get(name, None) + if module is None : + self.modules[name] = module = Module(name, 0) + if imp.is_builtin(name) == 0 : + module.load() + else : + globalModule = globals().get(name) + if globalModule : + module.attributes.extend(dir(globalModule)) + else : + self.modules[name] = module + + def filename(self) : + try : + filename = self.module.__file__ + except AttributeError : + filename = self.moduleName + return _getPyFile(filename) + + def load(self, warnings = None): + try : + # there's no need to reload modules we already have + global _output, _statusDlg, _count + txt = _("Loading Module %s\n") % self.moduleName + _output.AddLines(txt) + _count += 1 + if _count == 100: + _count = 95 + _statusDlg.Update(_count, txt) + + module = sys.modules.get(self.moduleName) + if module : + if not _allModules[self.moduleName].module : + return self._initModule(module) + return 1 + + return self._initModule(self.setupMainCode()) + except (SystemExit, KeyboardInterrupt) : + exc_type, exc_value, exc_tb = sys.exc_info() + raise exc_type, exc_value + except SyntaxError, (message, (fileName, line, col, text)): + # ActiveGrid: added this for better feedback when module couldn't be loaded. + w = Warning(self.fullpath, line, _("Syntax Error: %s\n%s\n%s^error near here") % (message, text, ' '*(col-1))) + warnings.append(w) + return 0 + except: + w = Warning(self.moduleName, 1, sys.exc_info()[0] + " NOT PROCESSED UNABLE TO IMPORT") + warnings.append(w) + importError(self.moduleName) + return 0 + + def initModule(self, module) : + if not self.module: + filename = _getPyFile(module.__file__) + if string.lower(filename[-3:]) == '.py': + try: + file = open(filename) + except IOError: + pass + else: + self._setupMainCode(file, filename, module) + return self._initModule(module) + return 1 + + def _initModule(self, module): + self.module = module + self.attributes = dir(self.module) + + pychecker_attr = getattr(module, Config.CHECKER_VAR, None) + if pychecker_attr is not None : + utils.pushConfig() + utils.updateCheckerArgs(pychecker_attr, 'suppressions', 0, []) + + for tokenName in _filterDir(self.module, _DEFAULT_MODULE_TOKENS) : + token = getattr(self.module, tokenName) + if isinstance(token, types.ModuleType) : + # get the real module name, tokenName could be an alias + self.addModule(token.__name__) + elif isinstance(token, types.FunctionType) : + self.addFunction(token) + elif isinstance(token, types.ClassType) or \ + hasattr(token, '__bases__') : + self.addClass(tokenName) + else : + self.addVariable(tokenName, type(token)) + + if pychecker_attr is not None : + utils.popConfig() + return 1 + + def setupMainCode(self) : + file, filename, smt = _findModule(self.moduleName) + # FIXME: if the smt[-1] == imp.PKG_DIRECTORY : load __all__ + module = imp.load_module(self.moduleName, file, filename, smt) + self._setupMainCode(file, filename, module) + return module + + def _setupMainCode(self, file, filename, module): + try : + self.main_code = function.create_from_file(file, filename, module) + finally : + if file != None : + file.close() + + +def getAllModules() : + "Returns a list of all modules that should be checked." + modules = [] + for module in _allModules.values() : + if module.check : + modules.append(module) + return modules + +_BUILTIN_MODULE_ATTRS = { 'sys': [ 'ps1', 'ps2', 'tracebacklimit', + 'exc_type', 'exc_value', 'exc_traceback', + 'last_type', 'last_value', 'last_traceback', + ], + } + +def fixupBuiltinModules(needs_init=0): + for moduleName in sys.builtin_module_names : + if needs_init: + _ = Module(moduleName, 0) + module = _allModules.get(moduleName, None) + if module is not None : + try : + m = imp.init_builtin(moduleName) + except ImportError : + pass + else : + extra_attrs = _BUILTIN_MODULE_ATTRS.get(moduleName, []) + module.attributes = [ '__dict__' ] + dir(m) + extra_attrs + + +def _printWarnings(warnings, stream=None): + if stream is None: + stream = sys.stdout + + warnings.sort() + lastWarning = None + for warning in warnings : + if lastWarning != None : + # ignore duplicate warnings + if cmp(lastWarning, warning) == 0 : + continue + # print blank line between files + if lastWarning.file != warning.file : + global _output + _output.AddLines("\n") + + lastWarning = warning + _output.AddLines(warning.format() + "\n") + + +def processFiles(files, cfg = None, pre_process_cb = None) : + # insert this here, so we find files in the local dir before std library + if sys.path[0] != '' : + sys.path.insert(0, '') + + # ensure we have a config object, it's necessary + global _cfg + if cfg is not None : + _cfg = cfg + elif _cfg is None : + _cfg = Config.Config() + + warnings = [] + utils.initConfig(_cfg) + for moduleName, filename in getModules(files) : + if callable(pre_process_cb) : + pre_process_cb(moduleName) + module = Module(moduleName, fullpath = filename) + + module.load(warnings) + utils.popConfig() + return warnings + + +def getWarnings(files, cfg = None, suppressions = None): + warnings = processFiles(files, cfg) + fixupBuiltinModules() + return warnings + warn.find(getAllModules(), _cfg, suppressions) + + +def _print_processing(name) : + if not _cfg.quiet : + global _output, _statusDlg, _count + txt = _("Processing %s...\n") % name + _output.AddLines(txt) + _count += 1 + _statusDlg.Update(_count, txt) + + + +def checkSyntax(filename, messageView): + """ Massively hacked version of main for ActiveGrid IDE integration """ + global _cfg + _cfg, files, suppressions = Config.setupFromArgs([filename]) + if not files : + return 0 + + global _output, _statusDlg, _count + _output = messageView + # wxBug: Need to show progress dialog box, or message window never gets updated until the method returns + _statusDlg = wx.ProgressDialog(_("Check Code"), _("Checking %s") % filename, maximum = 100, style = wx.PD_AUTO_HIDE | wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME) + _count = 0 + + # insert this here, so we find files in the local dir before std library + sys.path.insert(0, '') + + importWarnings = processFiles(files, _cfg, _print_processing) + fixupBuiltinModules() + if _cfg.printParse : + for module in getAllModules() : + printer.module(module) + + warnings = warn.find(getAllModules(), _cfg, suppressions) + + _statusDlg.Update(100, _("Done")) + _statusDlg.Destroy() + + if not _cfg.quiet : + _output.AddLines(_("\nWarnings and Errors...\n")) + if warnings or importWarnings : + _printWarnings(importWarnings + warnings) + return 1 + + if not _cfg.quiet : + _output.AddLines(_("No Syntax Errors")) + return 0 + +## +## +##def main(argv) : +## __pychecker__ = 'no-miximport' +## import pychecker +## if LOCAL_MAIN_VERSION != pychecker.MAIN_MODULE_VERSION : +## sys.stderr.write(_VERSION_MISMATCH_ERROR) +## sys.exit(100) +## +## # remove empty arguments +## argv = filter(None, argv) +## +## # if the first arg starts with an @, read options from the file +## # after the @ (this is mostly for windows) +## if len(argv) >= 2 and argv[1][0] == '@': +## # read data from the file +## command_file = argv[1][1:] +## try: +## f = open(command_file, 'r') +## command_line = f.read() +## f.close() +## except IOError, err: +## sys.stderr.write("Unable to read commands from file: %s\n %s\n" % \ +## (command_file, err)) +## sys.exit(101) +## +## # convert to an argv list, keeping argv[0] and the files to process +## argv = argv[:1] + string.split(command_line) + argv[2:] +## +## global _cfg +## _cfg, files, suppressions = Config.setupFromArgs(argv[1:]) +## if not files : +## return 0 +## +## # insert this here, so we find files in the local dir before std library +## sys.path.insert(0, '') +## +## importWarnings = processFiles(files, _cfg, _print_processing) +## fixupBuiltinModules() +## if _cfg.printParse : +## for module in getAllModules() : +## printer.module(module) +## +## warnings = warn.find(getAllModules(), _cfg, suppressions) +## if not _cfg.quiet : +## print "\nWarnings...\n" +## if warnings or importWarnings : +## _printWarnings(importWarnings + warnings) +## return 1 +## +## if not _cfg.quiet : +## print "None" +## return 0 +## +## +##if __name__ == '__main__' : +## try : +## sys.exit(main(sys.argv)) +## except Config.UsageError : +## sys.exit(127) +## +##else : +## _orig__import__ = None +## _suppressions = None +## _warnings_cache = {} +## +## def _get_unique_warnings(warnings): +## for i in range(len(warnings)-1, -1, -1): +## w = warnings[i].format() +## if _warnings_cache.has_key(w): +## del warnings[i] +## else: +## _warnings_cache[w] = 1 +## return warnings +## +## def __import__(name, globals=None, locals=None, fromlist=None): +## if globals is None: +## globals = {} +## if locals is None: +## locals = {} +## if fromlist is None: +## fromlist = [] +## +## check = not sys.modules.has_key(name) and name[:10] != 'pychecker.' +## pymodule = _orig__import__(name, globals, locals, fromlist) +## if check : +## try : +## module = Module(pymodule.__name__) +## if module.initModule(pymodule): +## warnings = warn.find([module], _cfg, _suppressions) +## _printWarnings(_get_unique_warnings(warnings)) +## else : +## print 'Unable to load module', pymodule.__name__ +## except Exception: +## name = getattr(pymodule, '__name__', str(pymodule)) +## importError(name) +## +## return pymodule +## +## def _init() : +## global _cfg, _suppressions, _orig__import__ +## +## args = string.split(os.environ.get('PYCHECKER', '')) +## _cfg, files, _suppressions = Config.setupFromArgs(args) +## utils.initConfig(_cfg) +## fixupBuiltinModules(1) +## +## # keep the orig __import__ around so we can call it +## import __builtin__ +## _orig__import__ = __builtin__.__import__ +## __builtin__.__import__ = __import__ +## +## if not os.environ.get('PYCHECKER_DISABLED') : +## _init() +## diff --git a/wxPython/samples/ide/activegrid/tool/data/tips.txt b/wxPython/samples/ide/activegrid/tool/data/tips.txt new file mode 100644 index 0000000000..1d2008f10a --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/data/tips.txt @@ -0,0 +1,7 @@ +Ctrl-Space in any editor does code completion. +Right-clicking on something in the 'Thing' column of the debugger's frame tab may allow you to introspect it for more information. +Right-Mouse-Click in Outline window allows you to change display sorting. +In an editor, you can add line markers via Ctrl-M and jump to the next marker with F4 or previous marker with Shift-F4. +In an editor. you can use the numpad + and - keys to toggle folding. +In 'Find in Directory', if you specify a file, it will display all matches in the Message Window. +Breakpoints for the debugger can be set while the process is running \ No newline at end of file diff --git a/wxPython/samples/ide/activegrid/tool/process.py b/wxPython/samples/ide/activegrid/tool/process.py new file mode 100644 index 0000000000..a521f0cf50 --- /dev/null +++ b/wxPython/samples/ide/activegrid/tool/process.py @@ -0,0 +1,2364 @@ +#!/usr/bin/env python +# Copyright (c) 2002-2003 ActiveState +# See LICENSE.txt for license details. +""" Contents of LICENSE.txt: +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" + +r""" + Python interface for process control. + + This module defines three Process classes for spawning, + communicating and control processes. They are: Process, ProcessOpen, + ProcessProxy. All of the classes allow one to specify the command (cmd), + starting working directory (cwd), and environment to create for the + new process (env) and to "wait" for termination of the child and + "kill" the child. + + Process: + Use this class to simply launch a process (either a GUI app or a + console app in a new console) with which you do not intend to + communicate via it std handles. + + ProcessOpen: + Think of this as a super version of Python's os.popen3() method. + This spawns the given command and sets up pipes for + stdin/stdout/stderr which can then be used to communicate with + the child. + + ProcessProxy: + This is a heavy-weight class that, similar to ProcessOpen, + spawns the given commands and sets up pipes to the child's + stdin/stdout/stderr. However, it also starts three threads to + proxy communication between each of the child's and parent's std + handles. At the parent end of this communication are, by + default, IOBuffer objects. You may specify your own objects here + (usually sub-classing from IOBuffer, which handles some + synchronization issues for you). The result is that it is + possible to have your own IOBuffer instance that gets, say, a + .write() "event" for every write that the child does on its + stdout. + + Understanding ProcessProxy is pretty complex. Some examples + below attempt to help show some uses. Here is a diagram of the + comminucation: + + + ,---->->->------' ^ `------>->->----, + | | v + IOBuffer IOBuffer IOBuffer + (p.stdout) (p.stderr) (p.stdin) + | | | + _OutFileProxy _OutFileProxy _InFileProxy + thread thread thread + | ^ | + `----<-<-<------, | ,------<-<-<----' + + + Usage: + import process + p = process. (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( ) 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( , 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=' ') + else: + self.stdin = stdin + if stdout is None: + self.stdout = IOBuffer(name=' ') + else: + self.stdout = stdout + if stderr is None: + self.stderr = IOBuffer(name=' ') + 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=' ') + self._stdinProxy.start() + # Clean up the parent's side of when it is observed that + # the child has closed its side of and . (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=' ') + self._stdoutProxy.start() + self._stderrProxy = _OutFileProxy(childStderr, self.stderr, + [closer], + name=' ') + 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=' ') + self._stdinProxy.start() + # Clean up the parent's side of when it is observed that + # the child has closed its side of . (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=' ') + self._stdoutProxy.start() + self._stderrProxy = _OutFileProxy(childStderr, self.stderr, + name=' ') + 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 or EOF + # via to unlock this. How to get around that? + # See cleanOnTermination note in _OutFileProxy.run() + # below. + #log.debug("XXX -> start read on %r" % self.fParent) + try: + text = self.fParent.read(CHUNKSIZE) + except ValueError, ex: + # ValueError is raised with trying to write to a closed + # file/pipe. + text = None + #log.debug("XXX <- done read on %r" % self.fParent) + if not text: + # Empty text signifies that the pipe has been closed on + # the parent's end. + log.info("[%s] _InFileProxy: observed close of parent (%r)"\ + % (self.getName(), self.fParent)) + # Signal the child so it knows to stop listening. + try: + logres.info("[%s] _InFileProxy: closing child after "\ + "observing parent's close: %r", self.getName(), + self.fChild) + try: + self.fChild.close() + except IOError: + pass # Ignore: IOError: [Errno 4] Interrupted system call + except IOError, ex: + # Ignore: IOError: [Errno 9] Bad file descriptor + # XXX Do we *know* we want to do that? + pass + break + else: + log.info("[%s] _InFileProxy: read %d bytes from parent: %r"\ + % (self.getName(), len(text), text)) + + log.info("[%s, %s] _InFileProxy: writing %r to child (%r)", + self.getName(), id(self), text, self.fChild) + try: + self.fChild.write(text) + except (OSError, IOError), ex: + # Ignore errors for now. For example: + # - Get this on Win9x when writing multiple lines to "dir": + # OSError: [Errno 32] Broken pipe + #XXX There *may* be errors we don't want to avoid. + #XXX Should maybe just ignore EnvironmentError (base class). + log.info("[%s] _InFileProxy: error writing to child (%r), "\ + "closing: %s" % (self.getName(), self.fParent, ex)) + break + log.info("[%s] _InFileProxy: wrote %d bytes to child: %r"\ + % (self.getName(), len(text), text)) + + +class _OutFileProxy(threading.Thread): + """A thread to watch an "out" file from the spawned child process + and pass on write's to the parent. + """ + def __init__(self, fChild, fParent, toClose=[], name=None): + """ + "fChild" is a Win32 handle to the a child process' output pipe. + "fParent" is a Python file-like object setup for writing. + "toClose" is a list of objects on which to call .close when this + proxy is terminating. + "name" can be set for debugging, it will be used in log messages. + """ + log.info("[%s] _OutFileProxy.__init__(fChild=%r, fParent=%r, "\ + "toClose=%r)", name, fChild, fParent, toClose) + threading.Thread.__init__(self, name=name) + self.fChild = fChild + self.fParent = fParent + self.toClose = toClose + + def run(self): + log.info("[%s] _OutFileProxy: start" % self.getName()) + try: + self._proxyFromChildToParent() + finally: + logres.info("[%s] _OutFileProxy: terminating, close child (%r)", + self.getName(), self.fChild) + try: + self.fChild.close() + except IOError: + pass # Ignore: IOError: [Errno 4] Interrupted system call + log.info("[%s] _OutFileProxy: closing parent (%r)", + self.getName(), self.fParent) + try: + self.fParent.close() + except IOError: + pass # Ignore: IOError: [Errno 4] Interrupted system call + while self.toClose: + logres.info("[%s] _OutFileProxy: closing %r after "\ + "closing parent", self.getName(), self.toClose[0]) + try: + self.toClose[0].close() + except IOError: + pass # Ignore: IOError: [Errno 4] Interrupted system call + del self.toClose[0] + log.info("[%s] _OutFileProxy: done" % self.getName()) + + def _proxyFromChildToParent(self): + CHUNKSIZE = 4096 + # Read output from the child process, and (for now) just write + # it out. + while 1: + text = None + try: + log.info("[%s] _OutFileProxy: waiting for read on child (%r)"\ + % (self.getName(), self.fChild)) + text = self.fChild.read(CHUNKSIZE) + except IOError, ex: + # Ignore: IOError: [Errno 9] Bad file descriptor + # XXX Do we *know* we want to do that? + log.info("[%s] _OutFileProxy: error reading from child (%r), "\ + "shutting down: %s", self.getName(), self.fChild, ex) + break + if not text: + # Empty text signifies that the pipe has been closed on + # the child's end. + log.info("[%s] _OutFileProxy: observed close of child (%r)"\ + % (self.getName(), self.fChild)) + break + + log.info("[%s] _OutFileProxy: text(len=%d): %r", + self.getName(), len(text), text) + self.fParent.write(text) + + + +if sys.platform.startswith("linux"): + class _ThreadFixer: + """Mixin class for various classes in the Process hierarchy to + work around the known LinuxThreads bug where one cannot .wait() + on a created process from a subthread of the thread that created + the process. + + Usage: + class ProcessXXX(_ThreadFixer, BrokenProcessXXX): + _pclass = BrokenProcessXXX + + Details: + Because we must do all real os.wait() calls on the child + process from the thread that spawned it, we use a proxy + thread whose only responsibility is just that. The proxy + thread just starts the child and then immediately wait's for + the child to terminate. On termination is stores the exit + status (for use by the main thread) and notifies any thread + waiting for this termination (possibly the main thread). The + overriden .wait() uses this stored exit status and the + termination notification to simulate the .wait(). + """ + def __init__(self, *args, **kwargs): + # Keep a reference to 'log' ensure it is around for this object's + # destruction. + self.__log = log + self.__waiter = None + self.__hasTerminated = threading.Condition() + self.__terminationResult = None + self.__childStarted = threading.Condition() + self._pclass.__init__(self, *args, **kwargs) + + def _forkAndExecChildOnUnix(self, *args, **kwargs): + """Fork and start the child process do it in a special subthread + that will negotiate subsequent .wait()'s. + + Sets self._pid as a side effect. + """ + self.__waiter = threading.Thread( + target=self.__launchAndWait, args=args, kwargs=kwargs) + + # Start subthread that will launch child and wait until it + # *has* started. + self.__childStarted.acquire() + self.__waiter.start() + self.__childStarted.wait() + self.__childStarted.release() + + def __launchAndWait(self, *args, **kwargs): + """Launch the given command and wait for it to terminate. + + When the process has terminated then store its exit value + and finish. + """ + logfix.info("start child in thread %s", + threading.currentThread().getName()) + + # Spawn the child process and notify the main thread of + # this. + self.__childStarted.acquire() + self._pclass._forkAndExecChildOnUnix(self, *args, **kwargs) + self.__childStarted.notifyAll() + self.__childStarted.release() + + # Wait on the thread and store appropriate results when + # finished. + try: + waitResult = self._pclass.wait(self) + except ProcessError, ex: + waitResult = ex + self.__hasTerminated.acquire() + self.__terminationResult = waitResult + self.__hasTerminated.notifyAll() + self.__hasTerminated.release() + + self.__waiter = None # drop ref that would keep instance alive + + def wait(self, timeout=None): + # If the process __hasTerminated then return the exit + # status. Otherwise simulate the wait as appropriate. + # Note: + # - This class is only used on linux so 'timeout' has the + # Unix 'timeout' semantics. + self.__hasTerminated.acquire() + if self.__terminationResult is None: + if timeout == os.WNOHANG: # Poll. + self.__hasTerminated.wait(0) + else: # Block until process finishes. + self.__hasTerminated.wait() + terminationResult = self.__terminationResult + self.__hasTerminated.release() + + if terminationResult is None: + # process has not finished yet + raise ProcessError("Wait for process timed out.", + self.WAIT_TIMEOUT) + elif isinstance(terminationResult, Exception): + # some error waiting for process termination + raise terminationResult + else: + # the process terminated + return terminationResult + + _ThreadBrokenProcess = Process + class Process(_ThreadFixer, _ThreadBrokenProcess): + _pclass = _ThreadBrokenProcess + + _ThreadBrokenProcessOpen = ProcessOpen + class ProcessOpen(_ThreadFixer, _ThreadBrokenProcessOpen): + _pclass = _ThreadBrokenProcessOpen + + _ThreadBrokenProcessProxy = ProcessProxy + class ProcessProxy(_ThreadFixer, _ThreadBrokenProcessProxy): + _pclass = _ThreadBrokenProcessProxy + + diff --git a/wxPython/samples/ide/activegrid/util/__init__.py b/wxPython/samples/ide/activegrid/util/__init__.py new file mode 100644 index 0000000000..905f93a0ab --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/__init__.py @@ -0,0 +1,72 @@ +import logging +import cStringIO +import traceback +import sys +import string +import os + +def classForName(className): + pathList = className.split('.') + moduleName = string.join(pathList[:-1], '.') + code = __import__(moduleName) + for name in pathList[1:]: + code = code.__dict__[name] + return code + +def hasattrignorecase(object, name): + for attr in dir(object): + if attr.lower() == name.lower(): + return True + for attr in dir(object): + if attr.lower() == '_' + name.lower(): + return True + return False + + +def setattrignorecase(object, name, value): + for attr in object.__dict__: + if attr.lower() == name.lower(): + object.__dict__[attr] = value + return +## for attr in dir(object): +## if attr.lower() == '_' + name.lower(): +## object.__dict__[attr] = value +## return + object.__dict__[name] = value + +def getattrignorecase(object, name): + for attr in object.__dict__: + if attr.lower() == name.lower(): + return object.__dict__[attr] +## for attr in dir(object): +## if attr.lower() == '_' + name.lower(): +## return object.__dict__[attr] + return object.__dict__[name] + + +def defaultLoad(fileObject): + xml = fileObject.read() + loadedObject = xmlmarshaller.unmarshal(xml) + if hasattr(fileObject, 'name'): + loadedObject.fileName = os.path.abspath(fileObject.name) + loadedObject.initialize() + return loadedObject + +def defaultSave(fileObject, objectToSave): + xml = xmlmarshaller.marshal(objectToSave, prettyPrint=True) + fileObject.write(xml) + + +def clone(objectToClone): + xml = xmlmarshaller.marshal(objectToClone, prettyPrint=True) + clonedObject = xmlmarshaller.unmarshal(xml) + if hasattr(objectToClone, 'fileName'): + clonedObject.fileName = objectToClone.fileName + clonedObject.initialize() + return clonedObject + +def exceptionToString(e): + sio = cStringIO.StringIO() + traceback.print_exception(e.__class__, e, sys.exc_traceback, file=sio) + return sio.getvalue() + diff --git a/wxPython/samples/ide/activegrid/util/aglogging.py b/wxPython/samples/ide/activegrid/util/aglogging.py new file mode 100644 index 0000000000..880ff822f2 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/aglogging.py @@ -0,0 +1,84 @@ +#---------------------------------------------------------------------------- +# Name: aglogging.py +# Purpose: Utilities to help with logging +# +# Author: Jeff Norton +# +# Created: 01/04/05 +# CVS-ID: $Id$ +# Copyright: (c) 2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import sys +import os +import re +import traceback + +global agTestMode +agTestMode = False + +def setTestMode(mode): + global agTestMode + if (mode): + agTestMode = True + else: + agTestMode = False + +def getTestMode(): + global agTestMode + return agTestMode + +def testMode(normalObj, testObj=None): + if getTestMode(): + return testObj + return normalObj + +def toDiffableString(value): + s = repr(value) + ds = "" + i = s.find(" at 0x") + start = 0 + while (i >= 0): + j = s.find(">", i) + if (j < i): + break + ds += s[start:i] + start = j + i = s.find(" at 0x", start) + return ds + s[start:] + +def removeFileRefs(str): + str = re.sub(r'(?<=File ")[^"]*(\\[^\\]*")(, line )[0-9]*', _fileNameReplacement, str) + return str + +def _fileNameReplacement(match): + return "...%s" % match.group(1) + +def getTraceback(): + extype, val, tb = sys.exc_info() + tbs = "\n" + for s in traceback.format_tb(tb): + tbs += s + return tbs + +def reportException(out=None, stacktrace=False, diffable=False): + extype, val, t = sys.exc_info() + if (diffable): + exstr = removeFileRefs(str(val)) + else: + exstr = str(val) + if (out == None): + print "Got Exception = %s: %s" % (extype, exstr) + else: + print >> out, "Got Exception = %s: %s" % (extype, exstr) + if (stacktrace): + fmt = traceback.format_exception(extype, val, t) + for s in fmt: + if (diffable): + s = removeFileRefs(s) + if (out == None): + print s + else: + print >> out, s + diff --git a/wxPython/samples/ide/activegrid/util/cachedloader.py b/wxPython/samples/ide/activegrid/util/cachedloader.py new file mode 100644 index 0000000000..f0fc69de15 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/cachedloader.py @@ -0,0 +1,87 @@ +#---------------------------------------------------------------------------- +# Name: cachedloader.py +# Purpose: +# +# Author: Joel Hare +# +# Created: 8/31/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import copy +import os.path +import string +import cStringIO + +import time + +# TODO: Instantiate the database and create a pool + + +class CachedLoader(object): + def __init__(self): + self.cache = {} + self.baseLoadDir = None + + def fullPath(self, fileName): + if os.path.isabs(fileName): + absPath = fileName + elif self.baseLoadDir: + absPath = os.path.join(self.baseLoadDir, fileName) + else: + absPath = os.path.abspath(fileName) + return absPath + + def setPrototype(self, fileName, loadedFile): + absPath = self.fullPath(fileName) + mtime = time.time() + 31536000.0 # Make sure prototypes aren't replaced by files on disk + self.cache[absPath] = (mtime, loadedFile) + + def update(self, loader): + self.cache.update(loader.cache) + + def clear(self): + self.cache.clear() + + def delete(self, fileName): + absPath = self.fullPath(fileName) + del self.cache[absPath] + + def needsLoad(self, fileName): + absPath = self.fullPath(fileName) + try: + cached = self.cache[absPath] + cachedTime = cached[0] + if cachedTime >= os.path.getmtime(absPath): + return False + except KeyError: + pass + return True + + def load(self, fileName, loader): + absPath = self.fullPath(fileName) + loadedFile = None + try: + cached = self.cache[absPath] + except KeyError: + cached = None + + if cached: + cachedTime = cached[0] + # ToDO We might need smarter logic for checking if a file needs to be reloaded + # ToDo We need a way to disable checking if this is a production server + if cachedTime >= os.path.getmtime(absPath): + loadedFile = cached[1] + + if not loadedFile: + targetFile = file(absPath) + try: + mtime = os.path.getmtime(absPath) + loadedFile = loader(targetFile) + self.cache[absPath] = (mtime, loadedFile) + finally: + targetFile.close() + return loadedFile + diff --git a/wxPython/samples/ide/activegrid/util/dependencymgr.py b/wxPython/samples/ide/activegrid/util/dependencymgr.py new file mode 100644 index 0000000000..272e8ebb0e --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/dependencymgr.py @@ -0,0 +1,145 @@ +#---------------------------------------------------------------------------- +# Name: dependencymgr.py +# Purpose: Dependency Manager +# +# Author: Jeff Norton +# +# Created: 01/28/05 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +#---------------------------------------------------------------------------- + +DM_NO_ID = 0 +DM_ID_ATTR = "_DependencyMgr__ID" + +##class ManageableObject(object): +## +## def __init__(self): +## self.__id = DM_NO_ID +## +## def __repr__(self): +## return " " % self.__id +## +## def __getID(self): +## return self.__id +## +## def __setID(self, value): +## if (self.__id != DM_NO_ID): +## raise DependencyMgrException("Cannot set the dependency ID on object %s to \"%s\" because it already has one (\"%s\")." % (repr(self), value, self.__id)) +## self.__id = value +## +## _DependencyMgr__ID = property(__getID, __setID) + +class DependencyMgr(object): + + def __init__(self): + self.clear() + + def clear(self): + self.__dependencies = {} + self.__lastID = DM_NO_ID + + def addDependency(self, parent, child): + pid = self._initObjectID(parent) + try: + parentCollection = self.__dependencies[pid] + except KeyError: + parentCollection = self._newDependencyCollection() + self.__dependencies[pid] = parentCollection + if (child not in parentCollection): + parentCollection.append(child) + + def removeDependency(self, parent, child): + pid = self._getObjectID(parent) + if (pid != DM_NO_ID): + try: + parentCollection = self.__dependencies[pid] + parentCollection.remove(child) + if (len(parentCollection) == 0): + del self.__dependencies[pid] + except KeyError, ValueError: + pass + + def clearDependencies(self, parent): + "Returns a list of objects or an empty list if no dependencies exist as for getDependencies, and then removes the dependency list." + pid = self._getObjectID(parent) + try: + deps = self.__dependencies[pid] + del self.__dependencies[pid] + return deps + except KeyError: + return [] + + def hasDependency(self, parent): + "Returns a boolean" + return (self._getObjectID(parent) in self.__dependencies) + + def getDependencies(self, parent): + "Returns a list of objects or an empty list if no dependencies exist." + try: + return self.__dependencies[self._getObjectID(parent)] + except KeyError: + return [] + + def dumpState(self, out): + "Writes the state of the dependency manager (as reported by getState) to out" + for line in self.getState(): + print >> out, line + + def getState(self): + "Returns the state of the dependency manager including all managed objects as a list of strings" + out = [] + out.append("DependencyMgr %s has %i parent objects, last id assigned is %i" % (repr(self), len(self.__dependencies), self.__lastID)) + for key, val in self.__dependencies.iteritems(): + out.append("Object %s has dependents: %s " % (repr(key), ", ".join([repr(d) for d in val]))) + return out + + def _initObjectID(self, obj): + try: + id = getattr(obj, DM_ID_ATTR) + except AttributeError: + id = DM_NO_ID + if (id == DM_NO_ID): + id = self._newID() + setattr(obj, DM_ID_ATTR, id) + return id + + def _getObjectID(self, obj): + try: + id = getattr(obj, DM_ID_ATTR) + except AttributeError: + id = DM_NO_ID + return id + + def _newID(self): + self.__lastID += 1 + return self.__lastID + + def _newDependencyCollection(self): + return [] + +globalDM = DependencyMgr() + +def addDependency(parent, child): + getGlobalDM().addDependency(parent, child) + +def removeDependency(parent, child): + getGlobalDM().removeDependency(parent, child) + +def clearDependencies(parent): + return getGlobalDM().clearDependencies(parent) + +def hasDependency(parent): + return getGlobalDM().hasDependency(parent) + +def getDependencies(parent): + return getGlobalDM().getDependencies(parent) + +def getState(): + return getGlobalDM().getState() + +def dumpState(out): + getGlobalDM().dumpState(out) + +def getGlobalDM(): + return globalDM diff --git a/wxPython/samples/ide/activegrid/util/fileutils.py b/wxPython/samples/ide/activegrid/util/fileutils.py new file mode 100644 index 0000000000..7d334c7dde --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/fileutils.py @@ -0,0 +1,39 @@ +#---------------------------------------------------------------------------- +# Name: fileutils.py +# Purpose: Active grid miscellaneous utilities +# +# Author: Jeff Norton +# +# Created: 12/10/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import os + +def createFile(filename, mode='w'): + f = None + try: + f = file(filename, mode) + except: + os.makedirs(filename[:filename.rindex(os.sep)]) + f = file(filename, mode) + return f + +def compareFiles(file1, file2): + file1.seek(0) + file2.seek(0) + while True: + line1 = file1.readline() + line2 = file2.readline() + if (len(line1) == 0): + if (len(line2) == 0): + return 0 + else: + return -1 + elif (len(line2) == 0): + return -1 + elif (line1 != line2): + return -1 + diff --git a/wxPython/samples/ide/activegrid/util/gettersetter.py b/wxPython/samples/ide/activegrid/util/gettersetter.py new file mode 100644 index 0000000000..34d62481ad --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/gettersetter.py @@ -0,0 +1,36 @@ +#---------------------------------------------------------------------------- +# Name: gettersetter.py +# Purpose: +# +# Author: Peter Yared +# +# Created: 7/28/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +def gettersetter(list): + for attr in list: + lowercase = attr[0].lower() + attr[1:] + uppercase = attr[0].upper() + attr[1:] + print " def get%s(self):" % uppercase + print " return self._%s" % lowercase + print + print " def set%s(self, %s):" % (uppercase, lowercase) + print " self._%s = %s" % (lowercase, lowercase) + print + +def listgettersetter(list): + for attr in list: + lowercase = attr[0].lower() + attr[1:] + uppercase = attr[0].upper() + attr[1:] + print " def get%s(self):" % uppercase + print " return self._%s" % lowercase + print + print " def add%s(self, %s):" % (uppercase[:-1], lowercase[:-1]) + print " self._%s.append(%s)" % (lowercase, lowercase[:-1]) + print + print " def remove%s(self, %s):" % (uppercase[:-1], lowercase[:-1]) + print " self._%s.remove(%s)" % (lowercase, lowercase[:-1]) + print + diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshaller.py b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py new file mode 100644 index 0000000000..f305f0ada8 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/xmlmarshaller.py @@ -0,0 +1,649 @@ +#---------------------------------------------------------------------------- +# Name: xmlmarshaller.py +# Purpose: +# +# Author: John Spurling +# +# Created: 7/28/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +from activegrid import util +import inspect +from types import * +import xml.sax +import xml.sax.handler +import __builtin__ +from xml.sax import saxutils + +### ToDO remove maxOccurs "unbounded" resolves to -1 hacks after bug 177 is fixed + +""" + +More documentation later, but here are some special Python attributes +that McLane recognizes: + +name: __xmlname__ +type: string +description: the name of the xml element for the marshalled object + +name: __xmlattributes__ +type: tuple or list +description: the name(s) of the Python string attribute(s) to be +marshalled as xml attributes instead of nested xml elements. currently +these can only be strings since there's not a way to get the type +information back when unmarshalling. + +name: __xmlexclude__ +type: tuple or list +description: the name(s) of the python attribute(s) to skip when +marshalling. + +name: __xmlrename__ +type: dict +description: describes an alternate Python <-> XML name mapping. +Normally the name mapping is the identity function. __xmlrename__ +overrides that. The keys are the Python names, the values are their +associated XML names. + +name: __xmlflattensequence__ +type: dict, tuple, or list +description: the name(s) of the Python sequence attribute(s) whose +items are to be marshalled as a series of xml elements (with an +optional keyword argument that specifies the element name to use) as +opposed to containing them in a separate sequence element, e.g.: + +myseq = (1, 2) + + + + +- 1
+- 2
+1 +2 + +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%s%s%s %s%s%s%s%s %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 _added by Python +## if name.startswith(classNamePrefix): name = name[len(classNamePrefix):] + if name in members_to_skip: continue + if name.startswith('__') and name.endswith('__'): continue +## idx = name.find('__') +## if idx > 0: +## newName = name[idx+2:] +## if newName: +## name = newName + subElementNameSpacePrefix = nameSpacePrefix + if hasattr(obj, '__xmlattrnamespaces__'): + for nameSpaceKey, nameSpaceValues in getattr(obj, '__xmlattrnamespaces__').items(): + if name in nameSpaceValues: + subElementNameSpacePrefix = nameSpaceKey + ':' + break + # handle sequences listed in __xmlflattensequence__ + # specially: instead of listing the contained items inside + # of a separate list, as god intended, list them inside + # the object containing the sequence. + if hasattr(obj, '__xmlflattensequence__') and name in obj.__xmlflattensequence__ and value: + try: + xmlnametuple = obj.__xmlflattensequence__[name] + xmlname = None + if len(xmlnametuple) == 1: + xmlname = xmlnametuple[0] + except: + xmlname = name +## xmlname = name.lower() + for seqitem in value: + xmlMemberString += marshal(seqitem, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment) + else: + if (hasattr(obj, "__xmlrename__") and name in obj.__xmlrename__): + xmlname = obj.__xmlrename__[name] + else: + xmlname = name +## xmlname = name.lower() +## # skip +## if xmlname.startswith('_') and not xmlname.startswith('__'): +## xmlname = xmlname[1:] +## if (indent > 30): +## print "getting pretty deep, xmlname = ", xmlname + xmlMemberString += marshal(value, xmlname, subElementNameSpacePrefix, nameSpaces=nameSpaces, indent=indent+increment) + # if we have nested elements, add them here, otherwise close the element tag immediately. + if xmlMemberString: + xmlString += '>' + if hasattr(obj, '__xmlbody__'): + xmlString += xmlMemberString + xmlString += '%s>%s' % (elementName, newline) + else: + xmlString += newline + xmlString += xmlMemberString + xmlString += '%s%s>%s' % (prefix, elementName, newline) + else: + xmlString = xmlString + '/>%s' % newline + return xmlString + +# We don't use this anymore but in case we want to get properties this is how +# you do it +def hasPropertyValue(obj, attr): + hasProp = False + try: + prop = obj.__class__.__dict__[attr] + if (isinstance(prop, property)): + hasProp = hasattr(obj, attr) + if (hasProp): + # It's a property and it has a value but sometimes we don't want it. + # If there is a _hasattr method execute it and the + # result will tell us whether to include this value + try: + hasProp = obj._hasattr(attr) + except: + pass + except KeyError: + pass + return hasProp + +if __name__ == '__main__': + from xmlmarshallertests import Person, marshalledint, marshalledlist + + l = [1, 2, 3] + d = {'1': 1, '2': 2} + outerlist = [l] + xmlstr = marshal(d, "d", prettyPrint=True) + print xmlstr + + person = Person() + person.firstName = "Albert" + person.lastName = "Camus" + person.addressLine1 = "23 Absurd St." + person.city = "Ennui" + person.state = "MO" + person.zip = "54321" + person._phoneNumber = "808-303-2323" + person.favoriteWords = ['angst', 'ennui', 'existence'] + person.weight = 150 + + xmlstring = marshal(person, 'person', prettyPrint=True) + print xmlstring + + obj = unmarshal(marshalledlist) + print "obj has type %s and value %s" % (type(obj), str(obj)) + for item in obj: + print "item: %s" % str(item) diff --git a/wxPython/samples/ide/activegrid/util/xmlmarshallertests.py b/wxPython/samples/ide/activegrid/util/xmlmarshallertests.py new file mode 100644 index 0000000000..b02b0fb097 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/xmlmarshallertests.py @@ -0,0 +1,183 @@ +#---------------------------------------------------------------------------- +# Name: xmlmarshallertests.py +# Purpose: +# +# Author: John Spurling +# +# Created: 8/16/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- + +import unittest +import xmlmarshaller +from xmlprettyprinter import xmlprettyprint + +marshalledPersonObject = """ + + +""" + +marshalledint = ''' +Albert +Camus + 23 Absurd St. +Ennui +MO +54321 + <_phoneNumber>808-303-2323 ++ +- angst
+- ennui
+- existence
+150 +- 23
+''' + +marshalledlist = ''' ++ +''' + +## 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 == """- foo
+- bar
+""") + + 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 == """ """) + unmarshalledperson = xmlmarshaller.unmarshal(xml) + assert(hasattr(unmarshalledperson, 'asequence')) + assert(len(unmarshalledperson.asequence) == 2) + + def testInstance(self): + print "\n#########################################" + print "# testInstance test case #" + print "#########################################" + class Foo: + def __init__(self): + self.alist = [1,2] + self.astring = 'f00' + f = Foo() + xml = xmlmarshaller.marshal(f, 'foo') + print xml + + def testPerson(self): + person = Person() + person.firstName = "Albert" + person.lastName = "Camus" + person.addressLine1 = "23 Absurd St." + person.city = "Ennui" + person.state = "MO" + person.zip = "54321" + person._phoneNumber = "808-303-2323" + person.favoriteWords = ['angst', 'ennui', 'existence'] + person.weight = 150 +# __xmlattributes__ = ('fabulousness',) + person.fabulousness = "tres tres" + xml = xmlmarshaller.marshal(person) + print "\n#########################################" + print "# testPerson test case #" + print "#########################################" + print "Person object marshalled into XML:\n" + print xml + # When encountering a "person" element, use the Person class +## elementMappings = { "person" : Person } +## obj = unmarshal(xml, elementMappings = elementMappings) +## print "Person object recreated from XML with attribute types indicated:" +## print obj.person.__class__ +## for (attr, value) in obj.person.__dict__.items(): +## if not attr.startswith("__"): +## print attr, "=", value, type(value) +## print + + +if __name__ == "__main__": + unittest.main() diff --git a/wxPython/samples/ide/activegrid/util/xmlprettyprinter.py b/wxPython/samples/ide/activegrid/util/xmlprettyprinter.py new file mode 100644 index 0000000000..29dbf16be3 --- /dev/null +++ b/wxPython/samples/ide/activegrid/util/xmlprettyprinter.py @@ -0,0 +1,63 @@ +#---------------------------------------------------------------------------- +# Name: xmlprettyprinter.py +# Purpose: +# +# Author: John Spurling +# +# Created: 9/21/04 +# CVS-ID: $Id$ +# Copyright: (c) 2004-2005 ActiveGrid, Inc. +# License: wxWindows License +#---------------------------------------------------------------------------- +import xml.sax +import xml.sax.handler + + +class XMLPrettyPrinter(xml.sax.ContentHandler): + def __init__(self, indentationChar=' ', newlineChar='\n'): + self.xmlOutput = '' + self.indentationLevel = 0 + self.indentationChar = indentationChar + self.elementStack = [] + self.newlineChar = newlineChar + self.hitCharData = False + + ## ContentHandler methods + def startElement(self, name, attrs): + indentation = self.newlineChar + (self.indentationLevel * self.indentationChar) + # build attribute string + attrstring = '' + for attr in attrs.getNames(): + value = attrs[attr] + attrstring += ' %s="%s"' % (attr, value) + self.xmlOutput += '%s<%s%s>' % (indentation, name, attrstring) + self.indentationLevel += 1 + self.elementStack.append(name) + self.hitCharData = False + + def characters(self, content): + self.xmlOutput += content + self.hitCharData = True + + def endElement(self, name): + self.indentationLevel -= 1 + indentation = '' + if not self.hitCharData: +## indentation += self.newlineChar + (self.indentationLevel * self.indentationChar) + indentation += self.indentationLevel * self.indentationChar + else: + self.hitCharData = False + self.xmlOutput += '%s%s>%s' % (indentation, self.elementStack.pop(), self.newlineChar) + + def getXMLString(self): + return self.xmlOutput[1:] + +def xmlprettyprint(xmlstr, spaces=4): + xpp = XMLPrettyPrinter(indentationChar=' ' * spaces) + xml.sax.parseString(xmlstr, xpp) + return xpp.getXMLString() + +if __name__ == '__main__': + simpleTestString = """ one two some text """ + print prettyprint(simpleTestString) + diff --git a/wxPython/samples/pydocview/FindService.py b/wxPython/samples/pydocview/FindService.py new file mode 100644 index 0000000000..6a7e8c5b43 --- /dev/null +++ b/wxPython/samples/pydocview/FindService.py @@ -0,0 +1,521 @@ +#---------------------------------------------------------------------------- +# Name: FindService.py +# Purpose: Find Service for pydocview +# +# Author: Peter Yared, Morgan Hua +# +# Created: 8/15/03 +# CVS-ID: $Id$ +# Copyright: (c) 2003-2005 ActiveGrid, Inc. +# License: ASL 2.0 http://apache.org/licenses/LICENSE-2.0 +#---------------------------------------------------------------------------- + +import wx +import wx.lib.docview +import wx.lib.pydocview +import re +_ = wx.GetTranslation + + +#---------------------------------------------------------------------------- +# Constants +#---------------------------------------------------------------------------- +FIND_MATCHPATTERN = "FindMatchPattern" +FIND_MATCHREPLACE = "FindMatchReplace" +FIND_MATCHCASE = "FindMatchCase" +FIND_MATCHWHOLEWORD = "FindMatchWholeWordOnly" +FIND_MATCHREGEXPR = "FindMatchRegularExpr" +FIND_MATCHWRAP = "FindMatchWrap" +FIND_MATCHUPDOWN = "FindMatchUpDown" + +FIND_SYNTAXERROR = -2 + +SPACE = 10 +HALF_SPACE = 5 + + +#---------------------------------------------------------------------------- +# Classes +#---------------------------------------------------------------------------- + +class FindService(wx.lib.pydocview.DocService): + + #---------------------------------------------------------------------------- + # Constants + #---------------------------------------------------------------------------- + FIND_ID = wx.NewId() # for bringing up Find dialog box + FINDONE_ID = wx.NewId() # for doing Find + FIND_PREVIOUS_ID = wx.NewId() # for doing Find Next + FIND_NEXT_ID = wx.NewId() # for doing Find Prev + REPLACE_ID = wx.NewId() # for bringing up Replace dialog box + REPLACEONE_ID = wx.NewId() # for doing a Replace + REPLACEALL_ID = wx.NewId() # for doing Replace All + GOTO_LINE_ID = wx.NewId() # for bringing up Goto dialog box + + # Extending bitmasks: wx.FR_WHOLEWORD, wx.FR_MATCHCASE, and wx.FR_DOWN + FR_REGEXP = max([wx.FR_WHOLEWORD, wx.FR_MATCHCASE, wx.FR_DOWN]) << 1 + FR_WRAP = FR_REGEXP << 1 + + + def __init__(self): + self._replaceDialog = None + self._findDialog = None + self._findReplaceData = wx.FindReplaceData() + self._findReplaceData.SetFlags(wx.FR_DOWN) + + + def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None): + """ Install Find Service Menu Items """ + editMenu = menuBar.GetMenu(menuBar.FindMenu(_("&Edit"))) + editMenu.AppendSeparator() + editMenu.Append(FindService.FIND_ID, _("&Find...\tCtrl+F"), _("Finds the specified text")) + wx.EVT_MENU(frame, FindService.FIND_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindService.FIND_ID, frame.ProcessUpdateUIEvent) + editMenu.Append(FindService.FIND_PREVIOUS_ID, _("Find &Previous\tShift+F3"), _("Finds the specified text")) + wx.EVT_MENU(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindService.FIND_PREVIOUS_ID, frame.ProcessUpdateUIEvent) + editMenu.Append(FindService.FIND_NEXT_ID, _("Find &Next\tF3"), _("Finds the specified text")) + wx.EVT_MENU(frame, FindService.FIND_NEXT_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindService.FIND_NEXT_ID, frame.ProcessUpdateUIEvent) + editMenu.Append(FindService.REPLACE_ID, _("R&eplace...\tCtrl+H"), _("Replaces specific text with different text")) + wx.EVT_MENU(frame, FindService.REPLACE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindService.REPLACE_ID, frame.ProcessUpdateUIEvent) + editMenu.Append(FindService.GOTO_LINE_ID, _("&Go to Line...\tCtrl+G"), _("Goes to a certain line in the file")) + wx.EVT_MENU(frame, FindService.GOTO_LINE_ID, frame.ProcessEvent) + wx.EVT_UPDATE_UI(frame, FindService.GOTO_LINE_ID, frame.ProcessUpdateUIEvent) + + # wxBug: wxToolBar::GetToolPos doesn't exist, need it to find cut tool and then insert find in front of it. + toolBar.InsertTool(6, FindService.FIND_ID, getFindBitmap(), shortHelpString = _("Find"), longHelpString = _("Finds the specified text")) + toolBar.InsertSeparator(6) + toolBar.Realize() + + frame.Bind(wx.EVT_FIND, frame.ProcessEvent) + frame.Bind(wx.EVT_FIND_NEXT, frame.ProcessEvent) + frame.Bind(wx.EVT_FIND_REPLACE, frame.ProcessEvent) + frame.Bind(wx.EVT_FIND_REPLACE_ALL, frame.ProcessEvent) + + + def ProcessUpdateUIEvent(self, event): + id = event.GetId() + if id == FindService.FIND_ID: + event.Enable(False) + return True + elif id == FindService.FIND_PREVIOUS_ID: + event.Enable(False) + return True + elif id == FindService.FIND_NEXT_ID: + event.Enable(False) + return True + elif id == FindService.REPLACE_ID: + event.Enable(False) + return True + elif id == FindService.GOTO_LINE_ID: + event.Enable(False) + return True + else: + return False + + + def ShowFindReplaceDialog(self, findString="", replace = False): + """ Display find/replace dialog box. + + Parameters: findString is the default value shown in the find/replace dialog input field. + If replace is True, the replace dialog box is shown, otherwise only the find dialog box is shown. + """ + if replace: + if self._findDialog != None: + # No reason to have both find and replace dialogs up at the same time + self._findDialog.DoClose() + self._findDialog = None + + self._replaceDialog = FindReplaceDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Replace"), size=(320,200), findString=findString) + self._replaceDialog.Show(True) + else: + if self._replaceDialog != None: + # No reason to have both find and replace dialogs up at the same time + self._replaceDialog.DoClose() + self._replaceDialog = None + + self._findDialog = FindDialog(self.GetDocumentManager().FindSuitableParent(), -1, _("Find"), size=(320,200), findString=findString) + self._findDialog.Show(True) + + + + def OnFindClose(self, event): + """ Cleanup handles when find/replace dialog is closed """ + if self._findDialog != None: + self._findDialog = None + elif self._replaceDialog != None: + self._replaceDialog = None + + + def GetCurrentDialog(self): + """ return handle to either the find or replace dialog """ + if self._findDialog != None: + return self._findDialog + return self._replaceDialog + + + def GetLineNumber(self, parent): + """ Display Goto Line Number dialog box """ + line = -1 + dialog = wx.TextEntryDialog(parent, _("Enter line number to go to:"), _("Go to Line")) + if dialog.ShowModal() == wx.ID_OK: + try: + line = int(dialog.GetValue()) + if line > 65535: + line = 65535 + except: + pass + dialog.Destroy() + # This one is ugly: wx.GetNumberFromUser("", _("Enter line number to go to:"), _("Go to Line"), 1, min = 1, max = 65535, parent = parent) + return line + + + def DoFind(self, findString, replaceString, text, startLoc, endLoc, down, matchCase, wholeWord, regExpr = False, replace = False, replaceAll = False, wrap = False): + """ Do the actual work of the find/replace. + + Returns the tuple (count, start, end, newText). + count = number of string replacements + start = start position of found string + end = end position of found string + newText = new replaced text + """ + flags = 0 + if regExpr: + pattern = findString + else: + pattern = re.escape(findString) # Treat the strings as a literal string + if not matchCase: + flags = re.IGNORECASE + if wholeWord: + pattern = r"\b%s\b" % pattern + + try: + reg = re.compile(pattern, flags) + except: + # syntax error of some sort + import sys + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("Regular Expression Search") + wx.MessageBox(_("Invalid regular expression \"%s\". %s") % (pattern, sys.exc_value), + msgTitle, + wx.OK | wx.ICON_EXCLAMATION, + self.GetView()) + return FIND_SYNTAXERROR, None, None, None + + if replaceAll: + newText, count = reg.subn(replaceString, text) + if count == 0: + return -1, None, None, None + else: + return count, None, None, newText + + start = -1 + if down: + match = reg.search(text, endLoc) + if match == None: + if wrap: # try again, but this time from top of file + match = reg.search(text, 0) + if match == None: + return -1, None, None, None + else: + return -1, None, None, None + start = match.start() + end = match.end() + else: + match = reg.search(text) + if match == None: + return -1, None, None, None + found = None + i, j = match.span() + while i < startLoc and j <= startLoc: + found = match + if i == j: + j = j + 1 + match = reg.search(text, j) + if match == None: + break + i, j = match.span() + if found == None: + if wrap: # try again, but this time from bottom of file + match = reg.search(text, startLoc) + if match == None: + return -1, None, None, None + found = None + i, j = match.span() + end = len(text) + while i < end and j <= end: + found = match + if i == j: + j = j + 1 + match = reg.search(text, j) + if match == None: + break + i, j = match.span() + if found == None: + return -1, None, None, None + else: + return -1, None, None, None + start = found.start() + end = found.end() + + if replace and start != -1: + newText, count = reg.subn(replaceString, text, 1) + return count, start, end, newText + + return 0, start, end, None + + + def SaveFindConfig(self, findString, wholeWord, matchCase, regExpr = None, wrap = None, upDown = None, replaceString = None): + """ Save find/replace patterns and search flags to registry. + + findString = search pattern + wholeWord = match whole word only + matchCase = match case + regExpr = use regular expressions in search pattern + wrap = return to top/bottom of file on search + upDown = search up or down from current cursor position + replaceString = replace string + """ + config = wx.ConfigBase_Get() + + config.Write(FIND_MATCHPATTERN, findString) + config.WriteInt(FIND_MATCHCASE, matchCase) + config.WriteInt(FIND_MATCHWHOLEWORD, wholeWord) + if replaceString != None: + config.Write(FIND_MATCHREPLACE, replaceString) + if regExpr != None: + config.WriteInt(FIND_MATCHREGEXPR, regExpr) + if wrap != None: + config.WriteInt(FIND_MATCHWRAP, wrap) + if upDown != None: + config.WriteInt(FIND_MATCHUPDOWN, upDown) + + + def GetFindString(self): + """ Load the search pattern from registry """ + return wx.ConfigBase_Get().Read(FIND_MATCHPATTERN, "") + + + def GetReplaceString(self): + """ Load the replace pattern from registry """ + return wx.ConfigBase_Get().Read(FIND_MATCHREPLACE, "") + + + def GetFlags(self): + """ Load search parameters from registry """ + config = wx.ConfigBase_Get() + + flags = 0 + if config.ReadInt(FIND_MATCHWHOLEWORD, False): + flags = flags | wx.FR_WHOLEWORD + if config.ReadInt(FIND_MATCHCASE, False): + flags = flags | wx.FR_MATCHCASE + if config.ReadInt(FIND_MATCHUPDOWN, False): + flags = flags | wx.FR_DOWN + if config.ReadInt(FIND_MATCHREGEXPR, False): + flags = flags | FindService.FR_REGEXP + if config.ReadInt(FIND_MATCHWRAP, False): + flags = flags | FindService.FR_WRAP + return flags + + +class FindDialog(wx.Dialog): + """ Find Dialog with regular expression matching and wrap to top/bottom of file. """ + + def __init__(self, parent, id, title, size, findString=None): + wx.Dialog.__init__(self, parent, id, title, size=size) + + config = wx.ConfigBase_Get() + borderSizer = wx.BoxSizer(wx.VERTICAL) + gridSizer = wx.GridBagSizer(SPACE, SPACE) + + lineSizer = wx.BoxSizer(wx.HORIZONTAL) + lineSizer.Add(wx.StaticText(self, -1, _("Find what:")), 0, wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, SPACE) + if not findString: + findString = config.Read(FIND_MATCHPATTERN, "") + self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1)) + lineSizer.Add(self._findCtrl, 0) + gridSizer.Add(lineSizer, pos=(0,0), span=(1,2)) + choiceSizer = wx.BoxSizer(wx.VERTICAL) + self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only")) + self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False)) + self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case")) + self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False)) + self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression")) + self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False)) + self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap")) + self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False)) + choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._wrapCtrl, 0) + gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1)) + + self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"]) + self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1)) + gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1)) + + buttonSizer = wx.BoxSizer(wx.VERTICAL) + findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next")) + findBtn.SetDefault() + wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent) + cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel")) + wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose) + buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(cancelBtn, 0) + gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1)) + + borderSizer.Add(gridSizer, 0, wx.ALL, SPACE) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.SetSizer(borderSizer) + self.Fit() + self._findCtrl.SetFocus() + + def SaveConfig(self): + """ Save find patterns and search flags to registry. """ + findService = wx.GetApp().GetService(FindService) + if findService: + findService.SaveFindConfig(self._findCtrl.GetValue(), + self._wholeWordCtrl.IsChecked(), + self._matchCaseCtrl.IsChecked(), + self._regExprCtrl.IsChecked(), + self._wrapCtrl.IsChecked(), + self._radioBox.GetSelection(), + ) + + + def DoClose(self): + self.SaveConfig() + self.Destroy() + + + def OnClose(self, event): + findService = wx.GetApp().GetService(FindService) + if findService: + findService.OnFindClose(event) + self.DoClose() + + + def OnActionEvent(self, event): + self.SaveConfig() + + if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_MDI: + if wx.GetApp().GetTopWindow().ProcessEvent(event): + return True + else: + view = wx.GetApp().GetDocumentManager().GetLastActiveView() + if view and view.ProcessEvent(event): + return True + return False + + +class FindReplaceDialog(FindDialog): + """ Find/Replace Dialog with regular expression matching and wrap to top/bottom of file. """ + + def __init__(self, parent, id, title, size, findString=None): + wx.Dialog.__init__(self, parent, id, title, size=size) + + config = wx.ConfigBase_Get() + borderSizer = wx.BoxSizer(wx.VERTICAL) + gridSizer = wx.GridBagSizer(SPACE, SPACE) + + gridSizer2 = wx.GridBagSizer(SPACE, SPACE) + gridSizer2.Add(wx.StaticText(self, -1, _("Find what:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(0,0)) + if not findString: + findString = config.Read(FIND_MATCHPATTERN, "") + self._findCtrl = wx.TextCtrl(self, -1, findString, size=(200,-1)) + gridSizer2.Add(self._findCtrl, pos=(0,1)) + gridSizer2.Add(wx.StaticText(self, -1, _("Replace with:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(1,0)) + self._replaceCtrl = wx.TextCtrl(self, -1, config.Read(FIND_MATCHREPLACE, ""), size=(200,-1)) + gridSizer2.Add(self._replaceCtrl, pos=(1,1)) + gridSizer.Add(gridSizer2, pos=(0,0), span=(1,2)) + choiceSizer = wx.BoxSizer(wx.VERTICAL) + self._wholeWordCtrl = wx.CheckBox(self, -1, _("Match whole word only")) + self._wholeWordCtrl.SetValue(config.ReadInt(FIND_MATCHWHOLEWORD, False)) + self._matchCaseCtrl = wx.CheckBox(self, -1, _("Match case")) + self._matchCaseCtrl.SetValue(config.ReadInt(FIND_MATCHCASE, False)) + self._regExprCtrl = wx.CheckBox(self, -1, _("Regular expression")) + self._regExprCtrl.SetValue(config.ReadInt(FIND_MATCHREGEXPR, False)) + self._wrapCtrl = wx.CheckBox(self, -1, _("Wrap")) + self._wrapCtrl.SetValue(config.ReadInt(FIND_MATCHWRAP, False)) + choiceSizer.Add(self._wholeWordCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._matchCaseCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._regExprCtrl, 0, wx.BOTTOM, SPACE) + choiceSizer.Add(self._wrapCtrl, 0) + gridSizer.Add(choiceSizer, pos=(1,0), span=(2,1)) + + self._radioBox = wx.RadioBox(self, -1, _("Direction"), choices = ["Up", "Down"]) + self._radioBox.SetSelection(config.ReadInt(FIND_MATCHUPDOWN, 1)) + gridSizer.Add(self._radioBox, pos=(1,1), span=(2,1)) + + buttonSizer = wx.BoxSizer(wx.VERTICAL) + findBtn = wx.Button(self, FindService.FINDONE_ID, _("Find Next")) + findBtn.SetDefault() + wx.EVT_BUTTON(self, FindService.FINDONE_ID, self.OnActionEvent) + cancelBtn = wx.Button(self, wx.ID_CANCEL, _("Cancel")) + wx.EVT_BUTTON(self, wx.ID_CANCEL, self.OnClose) + replaceBtn = wx.Button(self, FindService.REPLACEONE_ID, _("Replace")) + wx.EVT_BUTTON(self, FindService.REPLACEONE_ID, self.OnActionEvent) + replaceAllBtn = wx.Button(self, FindService.REPLACEALL_ID, _("Replace All")) + wx.EVT_BUTTON(self, FindService.REPLACEALL_ID, self.OnActionEvent) + buttonSizer.Add(findBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(replaceBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(replaceAllBtn, 0, wx.BOTTOM, HALF_SPACE) + buttonSizer.Add(cancelBtn, 0) + gridSizer.Add(buttonSizer, pos=(0,2), span=(3,1)) + + borderSizer.Add(gridSizer, 0, wx.ALL, SPACE) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.SetSizer(borderSizer) + self.Fit() + self._findCtrl.SetFocus() + + + def SaveConfig(self): + """ Save find/replace patterns and search flags to registry. """ + findService = wx.GetApp().GetService(FindService) + if findService: + findService.SaveFindConfig(self._findCtrl.GetValue(), + self._wholeWordCtrl.IsChecked(), + self._matchCaseCtrl.IsChecked(), + self._regExprCtrl.IsChecked(), + self._wrapCtrl.IsChecked(), + self._radioBox.GetSelection(), + self._replaceCtrl.GetValue() + ) + + +#---------------------------------------------------------------------------- +# Menu Bitmaps - generated by encode_bitmaps.py +#---------------------------------------------------------------------------- +from wx import ImageFromStream, BitmapFromImage +import cStringIO + + +def getFindData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00\x81IDAT8\x8d\xa5S\xc1\x16\xc0\x10\x0ckk\xff\xff\xc7d\x87\xad^U\r\ +\x93S\xe5U$\n\xb3$:\xc1e\x17(\x19Z\xb3$\x9e\xf1DD\xe2\x15\x01x\xea\x93\xef\ +\x04\x989\xea\x1b\xf2U\xc0\xda\xb4\xeb\x11\x1f:\xd8\xb5\xff8\x93\xd4\xa9\xae\ +@/S\xaaUwJ3\x85\xc0\x81\xee\xeb.q\x17C\x81\xd5XU \x1a\x93\xc6\x18\x8d\x90\ +\xe8}\x89\x00\x9a&\x9b_k\x94\x0c\xdf\xd78\xf8\x0b\x99Y\xb4\x08c\x9e\xfe\xc6\ +\xe3\x087\xf9\xd0D\x180\xf1#\x8e\x00\x00\x00\x00IEND\xaeB`\x82' + + +def getFindBitmap(): + return BitmapFromImage(getFindImage()) + + +def getFindImage(): + stream = cStringIO.StringIO(getFindData()) + return ImageFromStream(stream) + diff --git a/wxPython/samples/pydocview/PyDocViewDemo.py b/wxPython/samples/pydocview/PyDocViewDemo.py new file mode 100644 index 0000000000..c898393744 --- /dev/null +++ b/wxPython/samples/pydocview/PyDocViewDemo.py @@ -0,0 +1,95 @@ +#---------------------------------------------------------------------------- +# Name: PyDocViewDemo.py +# Purpose: Demo of Python extensions to the wxWindows docview framework +# +# Author: Peter Yared, Morgan Hua +# +# Created: 5/15/03 +# CVS-ID: $Id$ +# Copyright: (c) 2003-2005 ActiveGrid, Inc. +# License: ASL 2.0 http://apache.org/licenses/LICENSE-2.0 +#---------------------------------------------------------------------------- + + +import sys +import wx +import wx.lib.docview as docview +import wx.lib.pydocview as pydocview +import TextEditor +import FindService +_ = wx.GetTranslation + + +#---------------------------------------------------------------------------- +# Classes +#---------------------------------------------------------------------------- + +class TextEditorApplication(pydocview.DocApp): + + + def OnInit(self): + # Call the super - this is important!!! + pydocview.DocApp.OnInit(self) + + # Show the splash dialog while everything is loading up + self.ShowSplash("splash.jpg") + + # Set the name and the icon + self.SetAppName(_("wxPython PyDocView Demo")) + self.SetDefaultIcon(pydocview.getBlankIcon()) + + # Initialize the document manager + docManager = docview.DocManager(flags = self.GetDefaultDocManagerFlags()) + self.SetDocumentManager(docManager) + + # Create a template for text documents and associate it with the docmanager + textTemplate = docview.DocTemplate(docManager, + _("Text"), + "*.text;*.txt", + _("Text"), + _(".txt"), + _("Text Document"), + _("Text View"), + TextEditor.TextDocument, + TextEditor.TextView, + icon=pydocview.getBlankIcon()) + docManager.AssociateTemplate(textTemplate) + + # Install services - these can install menu and toolbar items + textService = self.InstallService(TextEditor.TextService()) + findService = self.InstallService(FindService.FindService()) + optionsService = self.InstallService(pydocview.DocOptionsService()) + windowMenuService = self.InstallService(pydocview.WindowMenuService()) + filePropertiesService = self.InstallService(pydocview.FilePropertiesService()) + aboutService = self.InstallService(pydocview.AboutService(image=wx.Image("splash.jpg"))) + + # Install the TextEditor's option panel into the OptionsService + optionsService.AddOptionsPanel(TextEditor.TextOptionsPanel) + + # If it is an MDI app open the main frame + self.OpenMainFrame() + + # Open any files that were passed via the command line + self.OpenCommandLineArgs() + + # If nothing was opened and it is an SDI app, open up an empty text document + if not docManager.GetDocuments() and docManager.GetFlags() & wx.lib.docview.DOC_SDI: + textTemplate.CreateDocument('', docview.DOC_NEW).OnNewDocument() + + # Close the splash dialog + self.CloseSplash() + + # Show the tips dialog + wx.CallAfter(self.ShowTip, wx.GetApp().GetTopWindow(), wx.CreateFileTipProvider("tips.txt", 0)) + + # Tell the framework that everything is great + return True + + +#---------------------------------------------------------------------------- +# Main +#---------------------------------------------------------------------------- + +# Run the TextEditorApplication and do not redirect output to the wxPython error dialog +app = TextEditorApplication(redirect=False) +app.MainLoop() diff --git a/wxPython/samples/docview/activegrid/tool/TextEditor.py b/wxPython/samples/pydocview/TextEditor.py similarity index 98% rename from wxPython/samples/docview/activegrid/tool/TextEditor.py rename to wxPython/samples/pydocview/TextEditor.py index 9f0517e3cc..2e432cfb61 100644 --- a/wxPython/samples/docview/activegrid/tool/TextEditor.py +++ b/wxPython/samples/pydocview/TextEditor.py @@ -6,8 +6,8 @@ # # Created: 8/15/03 # CVS-ID: $Id$ -# Copyright: (c) 2003-2004 ActiveGrid, Inc. -# License: wxWindows license +# Copyright: (c) 2003-2005 ActiveGrid, Inc. +# License: ASL 2.0 http://apache.org/licenses/LICENSE-2.0 #---------------------------------------------------------------------------- import wx import wx.lib.docview @@ -142,7 +142,7 @@ class TextView(wx.lib.docview.View): if not wx.lib.docview.View.OnClose(self, deleteWindow): return False self.Activate(False) - if deleteWindow: + if deleteWindow and self.GetFrame(): self.GetFrame().Destroy() return True @@ -246,7 +246,7 @@ class TextView(wx.lib.docview.View): event.Enable(self._textCtrl.CanPaste()) return True elif id == wx.ID_CLEAR: - event.Enable(True) # wxBug: No matter what we do, the wxTextCtrl disables the Clear menu item and the menu item traps the Del key and the wxTextCtrl doesn't get it and can't delete the next character + event.Enable(self._textCtrl.CanCopy()) return True elif id == wx.ID_SELECTALL: event.Enable(hasText) diff --git a/wxPython/samples/docview/activegrid/tool/images/splash.jpg b/wxPython/samples/pydocview/splash.jpg similarity index 100% rename from wxPython/samples/docview/activegrid/tool/images/splash.jpg rename to wxPython/samples/pydocview/splash.jpg diff --git a/wxPython/samples/pydocview/tips.txt b/wxPython/samples/pydocview/tips.txt new file mode 100644 index 0000000000..e820ecd72e --- /dev/null +++ b/wxPython/samples/pydocview/tips.txt @@ -0,0 +1 @@ +wxPython is cool. \ No newline at end of file diff --git a/wxPython/samples/simple/old_simple.py b/wxPython/samples/simple/old_simple.py deleted file mode 100644 index 81d3570322..0000000000 --- a/wxPython/samples/simple/old_simple.py +++ /dev/null @@ -1,60 +0,0 @@ - -#---------------------------------------------------------------------- -# A very simple wxPython example. Just a wxFrame, wxPanel, -# wxStaticText, wxButton, and a wxBoxSizer, but it shows the basic -# structure of any wxPython application. -#---------------------------------------------------------------------- - -from wxPython.wx import * -print "wxVERSION_STRING = ", wxVERSION_STRING - -class MyFrame(wxFrame): - """ - This is MyFrame. It just shows a few controls on a wxPanel, - and has a simple menu. - """ - def __init__(self, parent, title): - wxFrame.__init__(self, parent, -1, title, size=(350, 200)) - - menuBar = wxMenuBar() - menu = wxMenu() - menu.Append(101, "E&xit\tAlt-X", "Exit demo") - EVT_MENU(self, 101, self.OnButton) - menuBar.Append(menu, "&File") - self.SetMenuBar(menuBar) - - panel = wxPanel(self, -1) - text = wxStaticText(panel, -1, "Hello World!") - text.SetFont(wxFont(14, wxSWISS, wxNORMAL, wxBOLD)) - text.SetSize(text.GetBestSize()) - btn = wxButton(panel, -1, "Close") - btn.SetDefault() - - btn2 = wxButton(panel, -1, "Just for fun...") - - sizer = wxBoxSizer(wxVERTICAL) - sizer.Add(text, 0, wxALL, 10) - sizer.Add(btn, 0, wxALL, 10) - sizer.Add(btn2, 0, wxALL, 10) - panel.SetSizer(sizer) - panel.SetAutoLayout(True) - panel.Layout() - - EVT_BUTTON(self, btn.GetId(), self.OnButton) - EVT_BUTTON(self, btn2.GetId(), self.OnFunButton) - - def OnButton(self, evt): - """Event handler for the button click.""" - print "OnButton" - self.Close() - - def OnFunButton(self, evt): - """Event handler for the button click.""" - print "Having fun yet?" - - -app = wxPySimpleApp() -frame = MyFrame(None, "Simple wxPython App") -frame.Show(True) -app.MainLoop() - diff --git a/wxPython/src/_dc.i b/wxPython/src/_dc.i index 64aa56c535..a220816d4b 100644 --- a/wxPython/src/_dc.i +++ b/wxPython/src/_dc.i @@ -577,7 +577,7 @@ pixels from the begining of text to the coresponding character of *text*. The generic version simply builds a running total of the widths of each character using GetTextExtent, however if the various platforms have a native API function that is faster or more accurate -than the generic implementaiton then it will be used instead.", ""); +than the generic implementation then it will be used instead.", ""); wxArrayInt GetPartialTextExtents(const wxString& text) { wxArrayInt widths; self->GetPartialTextExtents(text, widths); diff --git a/wxPython/src/_image.i b/wxPython/src/_image.i index 36cef9cdfa..196bb06994 100644 --- a/wxPython/src/_image.i +++ b/wxPython/src/_image.i @@ -128,6 +128,8 @@ key value from a RGB tripple.", ""); class wxImage : public wxObject { public: + %typemap(out) wxImage*; // turn off this typemap + DocCtorStr( wxImage( const wxString& name, long type = wxBITMAP_TYPE_ANY, int index = -1 ), "", ""); @@ -235,6 +237,10 @@ length of the data must be width*height*3.", "", // TODO: wxImage( char** xpmData ); + // Turn it back on again + %typemap(out) wxImage* { $result = wxPyMake_wxObject($1, $owner); } + + void Create( int width, int height ); void Destroy(); diff --git a/wxPython/src/_listctrl.i b/wxPython/src/_listctrl.i index 1505e755f9..15b4ef2b3a 100644 --- a/wxPython/src/_listctrl.i +++ b/wxPython/src/_listctrl.i @@ -666,7 +666,8 @@ details in the second return value (see wx.LIST_HITTEST flags.)", ""); %Rename(InsertImageStringItem, long, InsertItem(long index, const wxString& label, int imageIndex)); // For list view mode (only), inserts a column. - %Rename(InsertColumnInfo, long, InsertColumn(long col, wxListItem& info)); + %Rename(InsertColumnItem, long, InsertColumn(long col, wxListItem& info)); + %pythoncode { InsertColumnInfo = InsertColumnItem } long InsertColumn(long col, const wxString& heading, diff --git a/wxPython/src/_treectrl.i b/wxPython/src/_treectrl.i index d71dc7bb89..77a8647d7c 100644 --- a/wxPython/src/_treectrl.i +++ b/wxPython/src/_treectrl.i @@ -192,7 +192,6 @@ public: %constant wxEventType wxEVT_COMMAND_TREE_ITEM_MENU; %pythoncode { - EVT_TREE_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_DRAG , 1) EVT_TREE_BEGIN_RDRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_RDRAG , 1) EVT_TREE_BEGIN_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT , 1) @@ -213,7 +212,7 @@ EVT_TREE_ITEM_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLI EVT_TREE_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_END_DRAG , 1) EVT_TREE_STATE_IMAGE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK, 1) EVT_TREE_ITEM_GETTOOLTIP = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, 1) -EVT_COMMAND_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) +EVT_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) } diff --git a/wxPython/src/_window.i b/wxPython/src/_window.i index 668b6d5264..8861980054 100644 --- a/wxPython/src/_window.i +++ b/wxPython/src/_window.i @@ -2068,6 +2068,20 @@ wxWindow* wxFindWindowByLabel( const wxString& label, } %} +//--------------------------------------------------------------------------- + +DocStr(GetTopLevelWindows, +"Returns a list of the the application's top-level windows, (frames, +dialogs, etc.) NOTE: Currently this is a copy of the list maintained +by wxWidgets, and so it is only valid as long as no top-level windows +are closed or new top-level windows are created. +", ""); +%inline %{ + PyObject* GetTopLevelWindows() { + return wxPy_ConvertList(&wxTopLevelWindows); + } +%} + //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- diff --git a/wxPython/src/gtk/_controls.py b/wxPython/src/gtk/_controls.py index eff4002dda..6f06ba76bc 100644 --- a/wxPython/src/gtk/_controls.py +++ b/wxPython/src/gtk/_controls.py @@ -4763,10 +4763,11 @@ class ListCtrl(_core.Control): """InsertImageStringItem(self, long index, String label, int imageIndex) -> long""" return _controls_.ListCtrl_InsertImageStringItem(*args, **kwargs) - def InsertColumnInfo(*args, **kwargs): - """InsertColumnInfo(self, long col, ListItem info) -> long""" - return _controls_.ListCtrl_InsertColumnInfo(*args, **kwargs) + def InsertColumnItem(*args, **kwargs): + """InsertColumnItem(self, long col, ListItem info) -> long""" + return _controls_.ListCtrl_InsertColumnItem(*args, **kwargs) + InsertColumnInfo = InsertColumnItem def InsertColumn(*args, **kwargs): """ InsertColumn(self, long col, String heading, int format=LIST_FORMAT_LEFT, @@ -5148,7 +5149,7 @@ EVT_TREE_ITEM_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLI EVT_TREE_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_END_DRAG , 1) EVT_TREE_STATE_IMAGE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK, 1) EVT_TREE_ITEM_GETTOOLTIP = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, 1) -EVT_COMMAND_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) +EVT_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) class TreeEvent(_core.NotifyEvent): """Proxy of C++ TreeEvent class""" diff --git a/wxPython/src/gtk/_controls_wrap.cpp b/wxPython/src/gtk/_controls_wrap.cpp index 5b7a1f2699..dca73fc68d 100644 --- a/wxPython/src/gtk/_controls_wrap.cpp +++ b/wxPython/src/gtk/_controls_wrap.cpp @@ -26081,7 +26081,7 @@ static PyObject *_wrap_ListCtrl_InsertImageStringItem(PyObject *, PyObject *args } -static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyObject *kwargs) { +static PyObject *_wrap_ListCtrl_InsertColumnItem(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxPyListCtrl *arg1 = (wxPyListCtrl *) 0 ; long arg2 ; @@ -26094,7 +26094,7 @@ static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyO (char *) "self",(char *) "col",(char *) "info", NULL }; - if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnInfo",kwnames,&obj0,&obj1,&obj2)) goto fail; + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnItem",kwnames,&obj0,&obj1,&obj2)) goto fail; SWIG_Python_ConvertPtr(obj0, (void **)&arg1, SWIGTYPE_p_wxPyListCtrl, SWIG_POINTER_EXCEPTION | 0); if (SWIG_arg_fail(1)) SWIG_fail; { @@ -35523,7 +35523,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"ListCtrl_InsertStringItem", (PyCFunction) _wrap_ListCtrl_InsertStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageItem", (PyCFunction) _wrap_ListCtrl_InsertImageItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageStringItem", (PyCFunction) _wrap_ListCtrl_InsertImageStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, - { (char *)"ListCtrl_InsertColumnInfo", (PyCFunction) _wrap_ListCtrl_InsertColumnInfo, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"ListCtrl_InsertColumnItem", (PyCFunction) _wrap_ListCtrl_InsertColumnItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertColumn", (PyCFunction) _wrap_ListCtrl_InsertColumn, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_SetItemCount", (PyCFunction) _wrap_ListCtrl_SetItemCount, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_ScrollList", (PyCFunction) _wrap_ListCtrl_ScrollList, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/gtk/_core.py b/wxPython/src/gtk/_core.py index 15b96cc1d8..156482c38b 100644 --- a/wxPython/src/gtk/_core.py +++ b/wxPython/src/gtk/_core.py @@ -8277,6 +8277,18 @@ def FindWindowByLabel(*args, **kwargs): def Window_FromHWND(*args, **kwargs): """Window_FromHWND(Window parent, unsigned long _hWnd) -> Window""" return _core_.Window_FromHWND(*args, **kwargs) + +def GetTopLevelWindows(*args, **kwargs): + """ + GetTopLevelWindows() -> PyObject + + Returns a list of the the application's top-level windows, (frames, + dialogs, etc.) NOTE: Currently this is a copy of the list maintained + by wxWidgets, and so it is only valid as long as no top-level windows + are closed or new top-level windows are created. + + """ + return _core_.GetTopLevelWindows(*args, **kwargs) #--------------------------------------------------------------------------- class Validator(EvtHandler): diff --git a/wxPython/src/gtk/_core_wrap.cpp b/wxPython/src/gtk/_core_wrap.cpp index 9baf755dce..9e06d29628 100644 --- a/wxPython/src/gtk/_core_wrap.cpp +++ b/wxPython/src/gtk/_core_wrap.cpp @@ -2766,6 +2766,11 @@ wxWindow* wxFindWindowByLabel( const wxString& label, } + PyObject* GetTopLevelWindows() { + return wxPy_ConvertList(&wxTopLevelWindows); + } + + IMP_PYCALLBACK_BOOL_WXWIN(wxPyValidator, wxValidator, Validate); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferToWindow); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferFromWindow); @@ -32244,6 +32249,28 @@ static PyObject *_wrap_Window_FromHWND(PyObject *, PyObject *args, PyObject *kwa } +static PyObject *_wrap_GetTopLevelWindows(PyObject *, PyObject *args, PyObject *kwargs) { + PyObject *resultobj; + PyObject *result; + char *kwnames[] = { + NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)":GetTopLevelWindows",kwnames)) goto fail; + { + PyThreadState* __tstate = wxPyBeginAllowThreads(); + result = (PyObject *)GetTopLevelWindows(); + + wxPyEndAllowThreads(__tstate); + if (PyErr_Occurred()) SWIG_fail; + } + resultobj = result; + return resultobj; + fail: + return NULL; +} + + static PyObject *_wrap_new_Validator(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxValidator *result; @@ -46194,6 +46221,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"FindWindowByName", (PyCFunction) _wrap_FindWindowByName, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"FindWindowByLabel", (PyCFunction) _wrap_FindWindowByLabel, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Window_FromHWND", (PyCFunction) _wrap_Window_FromHWND, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"GetTopLevelWindows", (PyCFunction) _wrap_GetTopLevelWindows, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"new_Validator", (PyCFunction) _wrap_new_Validator, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Clone", (PyCFunction) _wrap_Validator_Clone, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Validate", (PyCFunction) _wrap_Validator_Validate, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/gtk/_gdi.py b/wxPython/src/gtk/_gdi.py index 3473162461..7d4d700df8 100644 --- a/wxPython/src/gtk/_gdi.py +++ b/wxPython/src/gtk/_gdi.py @@ -3219,7 +3219,7 @@ class DC(_core.Object): *text*. The generic version simply builds a running total of the widths of each character using GetTextExtent, however if the various platforms have a native API function that is faster or more accurate - than the generic implementaiton then it will be used instead. + than the generic implementation then it will be used instead. """ return _gdi_.DC_GetPartialTextExtents(*args, **kwargs) diff --git a/wxPython/src/gtk/_windows.py b/wxPython/src/gtk/_windows.py index 26c8a22902..6decbc9d9e 100644 --- a/wxPython/src/gtk/_windows.py +++ b/wxPython/src/gtk/_windows.py @@ -516,8 +516,8 @@ class Frame(TopLevelWindow): Command = ProcessCommand def CreateStatusBar(*args, **kwargs): """ - CreateStatusBar(self, int number=1, long style=wxDEFAULT_STATUSBAR_STYLE, - int winid=0, String name=StatusLineNameStr) -> StatusBar + CreateStatusBar(self, int number=1, long style=DEFAULT_STATUSBAR_STYLE, int winid=0, + String name=StatusLineNameStr) -> StatusBar """ return _windows_.Frame_CreateStatusBar(*args, **kwargs) @@ -857,7 +857,7 @@ class StatusBar(_core.Window): return "<%s.%s; proxy of C++ wxStatusBar instance at %s>" % (self.__class__.__module__, self.__class__.__name__, self.this,) def __init__(self, *args, **kwargs): """ - __init__(self, Window parent, int id=-1, long style=wxDEFAULT_STATUSBAR_STYLE, + __init__(self, Window parent, int id=-1, long style=DEFAULT_STATUSBAR_STYLE, String name=StatusLineNameStr) -> StatusBar """ newobj = _windows_.new_StatusBar(*args, **kwargs) diff --git a/wxPython/src/mac/_controls.py b/wxPython/src/mac/_controls.py index 95dfec8ca5..6259eb6413 100644 --- a/wxPython/src/mac/_controls.py +++ b/wxPython/src/mac/_controls.py @@ -4750,10 +4750,11 @@ class ListCtrl(_core.Control): """InsertImageStringItem(self, long index, String label, int imageIndex) -> long""" return _controls_.ListCtrl_InsertImageStringItem(*args, **kwargs) - def InsertColumnInfo(*args, **kwargs): - """InsertColumnInfo(self, long col, ListItem info) -> long""" - return _controls_.ListCtrl_InsertColumnInfo(*args, **kwargs) + def InsertColumnItem(*args, **kwargs): + """InsertColumnItem(self, long col, ListItem info) -> long""" + return _controls_.ListCtrl_InsertColumnItem(*args, **kwargs) + InsertColumnInfo = InsertColumnItem def InsertColumn(*args, **kwargs): """ InsertColumn(self, long col, String heading, int format=LIST_FORMAT_LEFT, @@ -5135,7 +5136,7 @@ EVT_TREE_ITEM_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLI EVT_TREE_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_END_DRAG , 1) EVT_TREE_STATE_IMAGE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK, 1) EVT_TREE_ITEM_GETTOOLTIP = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, 1) -EVT_COMMAND_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) +EVT_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) class TreeEvent(_core.NotifyEvent): """Proxy of C++ TreeEvent class""" diff --git a/wxPython/src/mac/_controls_wrap.cpp b/wxPython/src/mac/_controls_wrap.cpp index 716a3958cf..eb1d2e91c7 100644 --- a/wxPython/src/mac/_controls_wrap.cpp +++ b/wxPython/src/mac/_controls_wrap.cpp @@ -26013,7 +26013,7 @@ static PyObject *_wrap_ListCtrl_InsertImageStringItem(PyObject *, PyObject *args } -static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyObject *kwargs) { +static PyObject *_wrap_ListCtrl_InsertColumnItem(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxPyListCtrl *arg1 = (wxPyListCtrl *) 0 ; long arg2 ; @@ -26026,7 +26026,7 @@ static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyO (char *) "self",(char *) "col",(char *) "info", NULL }; - if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnInfo",kwnames,&obj0,&obj1,&obj2)) goto fail; + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnItem",kwnames,&obj0,&obj1,&obj2)) goto fail; SWIG_Python_ConvertPtr(obj0, (void **)&arg1, SWIGTYPE_p_wxPyListCtrl, SWIG_POINTER_EXCEPTION | 0); if (SWIG_arg_fail(1)) SWIG_fail; { @@ -35453,7 +35453,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"ListCtrl_InsertStringItem", (PyCFunction) _wrap_ListCtrl_InsertStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageItem", (PyCFunction) _wrap_ListCtrl_InsertImageItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageStringItem", (PyCFunction) _wrap_ListCtrl_InsertImageStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, - { (char *)"ListCtrl_InsertColumnInfo", (PyCFunction) _wrap_ListCtrl_InsertColumnInfo, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"ListCtrl_InsertColumnItem", (PyCFunction) _wrap_ListCtrl_InsertColumnItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertColumn", (PyCFunction) _wrap_ListCtrl_InsertColumn, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_SetItemCount", (PyCFunction) _wrap_ListCtrl_SetItemCount, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_ScrollList", (PyCFunction) _wrap_ListCtrl_ScrollList, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/mac/_core.py b/wxPython/src/mac/_core.py index 15b96cc1d8..156482c38b 100644 --- a/wxPython/src/mac/_core.py +++ b/wxPython/src/mac/_core.py @@ -8277,6 +8277,18 @@ def FindWindowByLabel(*args, **kwargs): def Window_FromHWND(*args, **kwargs): """Window_FromHWND(Window parent, unsigned long _hWnd) -> Window""" return _core_.Window_FromHWND(*args, **kwargs) + +def GetTopLevelWindows(*args, **kwargs): + """ + GetTopLevelWindows() -> PyObject + + Returns a list of the the application's top-level windows, (frames, + dialogs, etc.) NOTE: Currently this is a copy of the list maintained + by wxWidgets, and so it is only valid as long as no top-level windows + are closed or new top-level windows are created. + + """ + return _core_.GetTopLevelWindows(*args, **kwargs) #--------------------------------------------------------------------------- class Validator(EvtHandler): diff --git a/wxPython/src/mac/_core_wrap.cpp b/wxPython/src/mac/_core_wrap.cpp index 9baf755dce..9e06d29628 100644 --- a/wxPython/src/mac/_core_wrap.cpp +++ b/wxPython/src/mac/_core_wrap.cpp @@ -2766,6 +2766,11 @@ wxWindow* wxFindWindowByLabel( const wxString& label, } + PyObject* GetTopLevelWindows() { + return wxPy_ConvertList(&wxTopLevelWindows); + } + + IMP_PYCALLBACK_BOOL_WXWIN(wxPyValidator, wxValidator, Validate); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferToWindow); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferFromWindow); @@ -32244,6 +32249,28 @@ static PyObject *_wrap_Window_FromHWND(PyObject *, PyObject *args, PyObject *kwa } +static PyObject *_wrap_GetTopLevelWindows(PyObject *, PyObject *args, PyObject *kwargs) { + PyObject *resultobj; + PyObject *result; + char *kwnames[] = { + NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)":GetTopLevelWindows",kwnames)) goto fail; + { + PyThreadState* __tstate = wxPyBeginAllowThreads(); + result = (PyObject *)GetTopLevelWindows(); + + wxPyEndAllowThreads(__tstate); + if (PyErr_Occurred()) SWIG_fail; + } + resultobj = result; + return resultobj; + fail: + return NULL; +} + + static PyObject *_wrap_new_Validator(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxValidator *result; @@ -46194,6 +46221,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"FindWindowByName", (PyCFunction) _wrap_FindWindowByName, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"FindWindowByLabel", (PyCFunction) _wrap_FindWindowByLabel, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Window_FromHWND", (PyCFunction) _wrap_Window_FromHWND, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"GetTopLevelWindows", (PyCFunction) _wrap_GetTopLevelWindows, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"new_Validator", (PyCFunction) _wrap_new_Validator, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Clone", (PyCFunction) _wrap_Validator_Clone, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Validate", (PyCFunction) _wrap_Validator_Validate, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/mac/_gdi.py b/wxPython/src/mac/_gdi.py index 59802f4027..55ae2364fc 100644 --- a/wxPython/src/mac/_gdi.py +++ b/wxPython/src/mac/_gdi.py @@ -3219,7 +3219,7 @@ class DC(_core.Object): *text*. The generic version simply builds a running total of the widths of each character using GetTextExtent, however if the various platforms have a native API function that is faster or more accurate - than the generic implementaiton then it will be used instead. + than the generic implementation then it will be used instead. """ return _gdi_.DC_GetPartialTextExtents(*args, **kwargs) diff --git a/wxPython/src/mac/_windows.py b/wxPython/src/mac/_windows.py index 0f5d7c7646..5748999486 100644 --- a/wxPython/src/mac/_windows.py +++ b/wxPython/src/mac/_windows.py @@ -524,8 +524,8 @@ class Frame(TopLevelWindow): Command = ProcessCommand def CreateStatusBar(*args, **kwargs): """ - CreateStatusBar(self, int number=1, long style=wxDEFAULT_STATUSBAR_STYLE, - int winid=0, String name=StatusLineNameStr) -> StatusBar + CreateStatusBar(self, int number=1, long style=DEFAULT_STATUSBAR_STYLE, int winid=0, + String name=StatusLineNameStr) -> StatusBar """ return _windows_.Frame_CreateStatusBar(*args, **kwargs) @@ -865,7 +865,7 @@ class StatusBar(_core.Window): return "<%s.%s; proxy of C++ wxStatusBar instance at %s>" % (self.__class__.__module__, self.__class__.__name__, self.this,) def __init__(self, *args, **kwargs): """ - __init__(self, Window parent, int id=-1, long style=wxDEFAULT_STATUSBAR_STYLE, + __init__(self, Window parent, int id=-1, long style=DEFAULT_STATUSBAR_STYLE, String name=StatusLineNameStr) -> StatusBar """ newobj = _windows_.new_StatusBar(*args, **kwargs) diff --git a/wxPython/src/msw/_controls.py b/wxPython/src/msw/_controls.py index 16c781f41a..03d64bd9e3 100644 --- a/wxPython/src/msw/_controls.py +++ b/wxPython/src/msw/_controls.py @@ -4775,10 +4775,11 @@ class ListCtrl(_core.Control): """InsertImageStringItem(self, long index, String label, int imageIndex) -> long""" return _controls_.ListCtrl_InsertImageStringItem(*args, **kwargs) - def InsertColumnInfo(*args, **kwargs): - """InsertColumnInfo(self, long col, ListItem info) -> long""" - return _controls_.ListCtrl_InsertColumnInfo(*args, **kwargs) + def InsertColumnItem(*args, **kwargs): + """InsertColumnItem(self, long col, ListItem info) -> long""" + return _controls_.ListCtrl_InsertColumnItem(*args, **kwargs) + InsertColumnInfo = InsertColumnItem def InsertColumn(*args, **kwargs): """ InsertColumn(self, long col, String heading, int format=LIST_FORMAT_LEFT, @@ -5160,7 +5161,7 @@ EVT_TREE_ITEM_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLI EVT_TREE_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_TREE_END_DRAG , 1) EVT_TREE_STATE_IMAGE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK, 1) EVT_TREE_ITEM_GETTOOLTIP = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, 1) -EVT_COMMAND_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) +EVT_TREE_ITEM_MENU = wx.PyEventBinder(wxEVT_COMMAND_TREE_ITEM_MENU, 1) class TreeEvent(_core.NotifyEvent): """Proxy of C++ TreeEvent class""" diff --git a/wxPython/src/msw/_controls_wrap.cpp b/wxPython/src/msw/_controls_wrap.cpp index a377b89c65..52de0abb2a 100644 --- a/wxPython/src/msw/_controls_wrap.cpp +++ b/wxPython/src/msw/_controls_wrap.cpp @@ -26168,7 +26168,7 @@ static PyObject *_wrap_ListCtrl_InsertImageStringItem(PyObject *, PyObject *args } -static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyObject *kwargs) { +static PyObject *_wrap_ListCtrl_InsertColumnItem(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxPyListCtrl *arg1 = (wxPyListCtrl *) 0 ; long arg2 ; @@ -26181,7 +26181,7 @@ static PyObject *_wrap_ListCtrl_InsertColumnInfo(PyObject *, PyObject *args, PyO (char *) "self",(char *) "col",(char *) "info", NULL }; - if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnInfo",kwnames,&obj0,&obj1,&obj2)) goto fail; + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)"OOO:ListCtrl_InsertColumnItem",kwnames,&obj0,&obj1,&obj2)) goto fail; SWIG_Python_ConvertPtr(obj0, (void **)&arg1, SWIGTYPE_p_wxPyListCtrl, SWIG_POINTER_EXCEPTION | 0); if (SWIG_arg_fail(1)) SWIG_fail; { @@ -35735,7 +35735,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"ListCtrl_InsertStringItem", (PyCFunction) _wrap_ListCtrl_InsertStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageItem", (PyCFunction) _wrap_ListCtrl_InsertImageItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertImageStringItem", (PyCFunction) _wrap_ListCtrl_InsertImageStringItem, METH_VARARGS | METH_KEYWORDS, NULL}, - { (char *)"ListCtrl_InsertColumnInfo", (PyCFunction) _wrap_ListCtrl_InsertColumnInfo, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"ListCtrl_InsertColumnItem", (PyCFunction) _wrap_ListCtrl_InsertColumnItem, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_InsertColumn", (PyCFunction) _wrap_ListCtrl_InsertColumn, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_SetItemCount", (PyCFunction) _wrap_ListCtrl_SetItemCount, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"ListCtrl_ScrollList", (PyCFunction) _wrap_ListCtrl_ScrollList, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/msw/_core.py b/wxPython/src/msw/_core.py index d4fd3fbed8..6fca60e50f 100644 --- a/wxPython/src/msw/_core.py +++ b/wxPython/src/msw/_core.py @@ -8290,6 +8290,18 @@ def FindWindowByLabel(*args, **kwargs): def Window_FromHWND(*args, **kwargs): """Window_FromHWND(Window parent, unsigned long _hWnd) -> Window""" return _core_.Window_FromHWND(*args, **kwargs) + +def GetTopLevelWindows(*args, **kwargs): + """ + GetTopLevelWindows() -> PyObject + + Returns a list of the the application's top-level windows, (frames, + dialogs, etc.) NOTE: Currently this is a copy of the list maintained + by wxWidgets, and so it is only valid as long as no top-level windows + are closed or new top-level windows are created. + + """ + return _core_.GetTopLevelWindows(*args, **kwargs) #--------------------------------------------------------------------------- class Validator(EvtHandler): diff --git a/wxPython/src/msw/_core_wrap.cpp b/wxPython/src/msw/_core_wrap.cpp index 0937d1e410..48b4e030e9 100644 --- a/wxPython/src/msw/_core_wrap.cpp +++ b/wxPython/src/msw/_core_wrap.cpp @@ -2765,6 +2765,11 @@ wxWindow* wxFindWindowByLabel( const wxString& label, } + PyObject* GetTopLevelWindows() { + return wxPy_ConvertList(&wxTopLevelWindows); + } + + IMP_PYCALLBACK_BOOL_WXWIN(wxPyValidator, wxValidator, Validate); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferToWindow); IMP_PYCALLBACK_BOOL_(wxPyValidator, wxValidator, TransferFromWindow); @@ -32294,6 +32299,28 @@ static PyObject *_wrap_Window_FromHWND(PyObject *, PyObject *args, PyObject *kwa } +static PyObject *_wrap_GetTopLevelWindows(PyObject *, PyObject *args, PyObject *kwargs) { + PyObject *resultobj; + PyObject *result; + char *kwnames[] = { + NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args,kwargs,(char *)":GetTopLevelWindows",kwnames)) goto fail; + { + PyThreadState* __tstate = wxPyBeginAllowThreads(); + result = (PyObject *)GetTopLevelWindows(); + + wxPyEndAllowThreads(__tstate); + if (PyErr_Occurred()) SWIG_fail; + } + resultobj = result; + return resultobj; + fail: + return NULL; +} + + static PyObject *_wrap_new_Validator(PyObject *, PyObject *args, PyObject *kwargs) { PyObject *resultobj; wxValidator *result; @@ -46246,6 +46273,7 @@ static PyMethodDef SwigMethods[] = { { (char *)"FindWindowByName", (PyCFunction) _wrap_FindWindowByName, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"FindWindowByLabel", (PyCFunction) _wrap_FindWindowByLabel, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Window_FromHWND", (PyCFunction) _wrap_Window_FromHWND, METH_VARARGS | METH_KEYWORDS, NULL}, + { (char *)"GetTopLevelWindows", (PyCFunction) _wrap_GetTopLevelWindows, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"new_Validator", (PyCFunction) _wrap_new_Validator, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Clone", (PyCFunction) _wrap_Validator_Clone, METH_VARARGS | METH_KEYWORDS, NULL}, { (char *)"Validator_Validate", (PyCFunction) _wrap_Validator_Validate, METH_VARARGS | METH_KEYWORDS, NULL}, diff --git a/wxPython/src/msw/_gdi.py b/wxPython/src/msw/_gdi.py index 44fd817962..016d34c4fe 100644 --- a/wxPython/src/msw/_gdi.py +++ b/wxPython/src/msw/_gdi.py @@ -3315,7 +3315,7 @@ class DC(_core.Object): *text*. The generic version simply builds a running total of the widths of each character using GetTextExtent, however if the various platforms have a native API function that is faster or more accurate - than the generic implementaiton then it will be used instead. + than the generic implementation then it will be used instead. """ return _gdi_.DC_GetPartialTextExtents(*args, **kwargs) diff --git a/wxPython/src/msw/_windows.py b/wxPython/src/msw/_windows.py index dae6120186..3fb7bbd2c4 100644 --- a/wxPython/src/msw/_windows.py +++ b/wxPython/src/msw/_windows.py @@ -524,8 +524,8 @@ class Frame(TopLevelWindow): Command = ProcessCommand def CreateStatusBar(*args, **kwargs): """ - CreateStatusBar(self, int number=1, long style=wxDEFAULT_STATUSBAR_STYLE, - int winid=0, String name=StatusLineNameStr) -> StatusBar + CreateStatusBar(self, int number=1, long style=DEFAULT_STATUSBAR_STYLE, int winid=0, + String name=StatusLineNameStr) -> StatusBar """ return _windows_.Frame_CreateStatusBar(*args, **kwargs) @@ -865,7 +865,7 @@ class StatusBar(_core.Window): return "<%s.%s; proxy of C++ wxStatusBar instance at %s>" % (self.__class__.__module__, self.__class__.__name__, self.this,) def __init__(self, *args, **kwargs): """ - __init__(self, Window parent, int id=-1, long style=wxDEFAULT_STATUSBAR_STYLE, + __init__(self, Window parent, int id=-1, long style=DEFAULT_STATUSBAR_STYLE, String name=StatusLineNameStr) -> StatusBar """ newobj = _windows_.new_StatusBar(*args, **kwargs) diff --git a/wxPython/src/my_typemaps.i b/wxPython/src/my_typemaps.i index b0975a56c2..19d2fe76f0 100644 --- a/wxPython/src/my_typemaps.i +++ b/wxPython/src/my_typemaps.i @@ -334,6 +334,7 @@ MAKE_INT_ARRAY_TYPEMAPS(styles, styles_field) %typemap(out) wxFSFile* { $result = wxPyMake_wxObject($1, $owner); } %typemap(out) wxFileSystem* { $result = wxPyMake_wxObject($1, $owner); } %typemap(out) wxImageList* { $result = wxPyMake_wxObject($1, $owner); } +%typemap(out) wxImage* { $result = wxPyMake_wxObject($1, $owner); } %typemap(out) wxListItem* { $result = wxPyMake_wxObject($1, $owner); } %typemap(out) wxMenuItem* { $result = wxPyMake_wxObject($1, $owner); } %typemap(out) wxMouseEvent* { $result = wxPyMake_wxObject($1, $owner); } diff --git a/wxPython/wx/lib/docview.py b/wxPython/wx/lib/docview.py index aad8359e14..01c490c2dc 100644 --- a/wxPython/wx/lib/docview.py +++ b/wxPython/wx/lib/docview.py @@ -6,7 +6,7 @@ # # Created: 5/15/03 # CVS-ID: $Id$ -# Copyright: (c) 2003-2004 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al) +# Copyright: (c) 2003-2005 ActiveGrid, Inc. (Port of wxWindows classes by Julian Smart et al) # License: wxWindows license #---------------------------------------------------------------------------- @@ -91,6 +91,7 @@ class Document(wx.EvtHandler): self._documentTemplate = None self._commandProcessor = None self._savedYet = False + self._writeable = True self._documentTitle = None self._documentFile = None @@ -334,7 +335,7 @@ class Document(wx.EvtHandler): Saves the document by calling OnSaveDocument if there is an associated filename, or SaveAs if there is no filename. """ - if not self.IsModified() and self._savedYet: + if not self.IsModified(): # and self._savedYet: This was here, but if it is not modified who cares if it hasn't been saved yet? return True if not self._documentFile or not self._savedYet: @@ -646,6 +647,29 @@ class Document(wx.EvtHandler): view.OnChangeFilename() + def GetWriteable(self): + """ + Returns true if the document can be written to its accociated file path. + This method has been added to wxPython and is not in wxWindows. + """ + if not self._writeable: + return False + if not self._documentFile: # Doesn't exist, do a save as + return True + else: + return os.access(self._documentFile, os.W_OK) + + + def SetWriteable(self, writeable): + """ + Set to False if the document can not be saved. This will disable the ID_SAVE_AS + event and is useful for custom documents that should not be saveable. The ID_SAVE + event can be disabled by never Modifying the document. This method has been added + to wxPython and is not in wxWindows. + """ + self._writeable = writeable + + class View(wx.EvtHandler): """ The view class can be used to model the viewing and editing component of @@ -753,7 +777,7 @@ class View(wx.EvtHandler): else: return else: - if appName and not isinstance(self.GetFrame(), DocMDIChildFrame): # Don't need appname in title for MDI + if appName and isinstance(self.GetFrame(), DocChildFrame): # Only need app name in title for SDI title = appName + _(" - ") else: title = '' @@ -1478,7 +1502,7 @@ class DocManager(wx.EvtHandler): """ Updates the user interface for the File Save As command. """ - event.Enable(self.GetCurrentDocument() != None) + event.Enable(self.GetCurrentDocument() != None and self.GetCurrentDocument().GetWriteable()) def OnUpdateUndo(self, event): diff --git a/wxPython/wx/lib/masked/combobox.py b/wxPython/wx/lib/masked/combobox.py index 9b309710b0..7c08ed4265 100644 --- a/wxPython/wx/lib/masked/combobox.py +++ b/wxPython/wx/lib/masked/combobox.py @@ -26,7 +26,7 @@ from wx.lib.masked import * # be a good place to implement the 2.3 logger class from wx.tools.dbg import Logger ##dbg = Logger() -##dbg(enable=0) +##dbg(enable=1) ## ---------- ---------- ---------- ---------- ---------- ---------- ---------- ## Because calling SetSelection programmatically does not fire EVT_COMBOBOX @@ -197,6 +197,7 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ): Allow mixin to get the text selection of this control. REQUIRED by any class derived from MaskedEditMixin. """ +## dbg('MaskedComboBox::_GetSelection()') return self.GetMark() def _SetSelection(self, sel_start, sel_to): @@ -204,13 +205,22 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ): Allow mixin to set the text selection of this control. REQUIRED by any class derived from MaskedEditMixin. """ +## dbg('MaskedComboBox::_SetSelection: setting mark to (%d, %d)' % (sel_start, sel_to)) return self.SetMark( sel_start, sel_to ) def _GetInsertionPoint(self): - return self.GetInsertionPoint() +## dbg('MaskedComboBox::_GetInsertionPoint()', indent=1) +## ret = self.GetInsertionPoint() + # work around new bug in 2.5, in which the insertion point + # returned is always at the right side of the selection, + # rather than the start, as is the case with TextCtrl. + ret = self.GetMark()[0] +## dbg('returned', ret, indent=0) + return ret def _SetInsertionPoint(self, pos): +## dbg('MaskedComboBox::_SetInsertionPoint(%d)' % pos) self.SetInsertionPoint(pos) @@ -482,6 +492,12 @@ class BaseMaskedComboBox( wx.ComboBox, MaskedEditMixin ): ## dbg('computed selection:', sel_start, sel_to, indent=0, suspend=0) return sel_start, sel_to + else: + def GetMark(self): +## dbg('MaskedComboBox::GetMark()', indent = 1) + ret = wx.ComboBox.GetMark(self) +## dbg('returned', ret, indent=0) + return ret def SetSelection(self, index): diff --git a/wxPython/wx/lib/masked/maskededit.py b/wxPython/wx/lib/masked/maskededit.py index 5a99f9ec43..be2753eb8b 100644 --- a/wxPython/wx/lib/masked/maskededit.py +++ b/wxPython/wx/lib/masked/maskededit.py @@ -2952,6 +2952,7 @@ class MaskedEditMixin: if pos == year2dig and unadjusted[year2dig] != newstr[year2dig]: newpos = pos+2 +## dbg('queuing insertion point: (%d)' % newpos) wx.CallAfter(self._SetInsertionPoint, newpos) if match_field is not None: @@ -2964,6 +2965,8 @@ class MaskedEditMixin: else: newfield = self._FindField(newpos) if newfield != field and newfield._selectOnFieldEntry: +## dbg('queuing insertion point: (%d)' % newfield._extent[0]) + wx.CallAfter(self._SetInsertionPoint, newfield._extent[0]) ## dbg('queuing selection: (%d, %d)' % (newfield._extent[0], newfield._extent[1])) wx.CallAfter(self._SetSelection, newfield._extent[0], newfield._extent[1]) else: @@ -4022,7 +4025,9 @@ class MaskedEditMixin: ## dbg('match found:', choice) match = index break - else: dbg('choice: "%s" - no match' % choice) + else: +## dbg('choice: "%s" - no match' % choice) + pass if match is not None: ## dbg('matched', match) pass @@ -4936,7 +4941,7 @@ class MaskedEditMixin: old_right_signpos = text.find(')') if field._allowInsert and not field._insertRight and sel_to <= end and sel_start >= start: - # inserting within a left-insert-capable field +## dbg('inserting within a left-insert-capable field') field_len = end - start before = text[start:sel_start] after = text[sel_to:end].strip() @@ -4965,6 +4970,9 @@ class MaskedEditMixin: char = char.decode(self._defaultEncoding) newtext = left + char + right +#### dbg('left: "%s"' % left) +#### dbg('right: "%s"' % right) +#### dbg('newtext: "%s"' % newtext) if self._signOk and self._useParens: # Balance parentheses: @@ -5298,6 +5306,8 @@ class MaskedEditMixin: """ Handler for EVT_KILL_FOCUS event. """ ## dbg('MaskedEditMixin::_OnKillFocus', 'isDate=',self._isDate, indent=1) + if self.IsBeingDeleted() or self.GetParent().IsBeingDeleted(): + return if self._mask and self._IsEditable(): self._AdjustField(self._GetInsertionPoint()) self._CheckValid() ## Call valid handler diff --git a/wxPython/wx/lib/mixins/listctrl.py b/wxPython/wx/lib/mixins/listctrl.py index 935a66565b..9f334af49e 100644 --- a/wxPython/wx/lib/mixins/listctrl.py +++ b/wxPython/wx/lib/mixins/listctrl.py @@ -209,7 +209,7 @@ class ListCtrlAutoWidthMixin: 'minWidth' is the preferred minimum width for the last column. """ - self.resizeCloumn(self, minWidth) + self.resizeColumn(self, minWidth) def resizeColumn(self, minWidth): diff --git a/wxPython/wx/lib/ogl/_lines.py b/wxPython/wx/lib/ogl/_lines.py index ea4b9ea959..5bfd6bd4e6 100644 --- a/wxPython/wx/lib/ogl/_lines.py +++ b/wxPython/wx/lib/ogl/_lines.py @@ -1511,12 +1511,7 @@ class LineShape(Shape): labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight()) # Find position in line's region list - i = 0 - for region in self.GetRegions(): - if labelShape._shapeRegion == region: - self.GetRegions().remove(region) - else: - i += 1 + i = self._regions.index(labelShape._shapeRegion) xx, yy = self.GetLabelPosition(i) # Set the region's offset, relative to the default position for diff --git a/wxPython/wx/lib/pydocview.py b/wxPython/wx/lib/pydocview.py index 3e7b7b8fb8..41ed58dee7 100644 --- a/wxPython/wx/lib/pydocview.py +++ b/wxPython/wx/lib/pydocview.py @@ -2,11 +2,11 @@ # Name: pydocview.py # Purpose: Python extensions to the wxWindows docview framework # -# Author: Peter Yared +# Author: Peter Yared, Morgan Hua # # Created: 5/15/03 # CVS-ID: $Id$ -# Copyright: (c) 2003-2004 ActiveGrid, Inc. +# Copyright: (c) 2003-2005 ActiveGrid, Inc. # License: wxWindows license #---------------------------------------------------------------------------- @@ -21,11 +21,13 @@ import os.path import time import string import pickle -import getpass import tempfile import mmap _ = wx.GetTranslation - +if wx.Platform == '__WXMSW__': + _WINDOWS = True +else: + _WINDOWS = False #---------------------------------------------------------------------------- # Constants @@ -50,6 +52,1103 @@ SAVEALL_ID = wx.NewId() WINDOW_MENU_NUM_ITEMS = 9 +class DocFrameMixIn: + """ + Class with common code used by DocMDIParentFrame, DocTabbedParentFrame, and + DocSDIFrame. + """ + + + def GetDocumentManager(self): + """ + Returns the document manager associated with the DocMDIParentFrame. + """ + return self._docManager + + + def InitializePrintData(self): + """ + Initializes the PrintData that is used when printing. + """ + self._printData = wx.PrintData() + self._printData.SetPaperId(wx.PAPER_LETTER) + + + def CreateDefaultMenuBar(self, sdi=False): + """ + Creates the default MenuBar. Contains File, Edit, View, Tools, and Help menus. + """ + menuBar = wx.MenuBar() + + fileMenu = wx.Menu() + fileMenu.Append(wx.ID_NEW, _("&New...\tCtrl+N"), _("Creates a new document")) + fileMenu.Append(wx.ID_OPEN, _("&Open...\tCtrl+O"), _("Opens an existing document")) + fileMenu.Append(wx.ID_CLOSE, _("&Close"), _("Closes the active document")) + if not sdi: + fileMenu.Append(wx.ID_CLOSE_ALL, _("Close A&ll"), _("Closes all open documents")) + fileMenu.AppendSeparator() + fileMenu.Append(wx.ID_SAVE, _("&Save\tCtrl+S"), _("Saves the active document")) + fileMenu.Append(wx.ID_SAVEAS, _("Save &As..."), _("Saves the active document with a new name")) + fileMenu.Append(SAVEALL_ID, _("Save All\tCtrl+Shift+A"), _("Saves the all active documents")) + wx.EVT_MENU(self, SAVEALL_ID, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, SAVEALL_ID, self.ProcessUpdateUIEvent) + fileMenu.AppendSeparator() + fileMenu.Append(wx.ID_PRINT, _("&Print\tCtrl+P"), _("Prints the active document")) + fileMenu.Append(wx.ID_PREVIEW, _("Print Pre&view"), _("Displays full pages")) + fileMenu.Append(wx.ID_PRINT_SETUP, _("Page Set&up"), _("Changes page layout settings")) + fileMenu.AppendSeparator() + if wx.Platform == '__WXMAC__': + fileMenu.Append(wx.ID_EXIT, _("&Quit"), _("Closes this program")) + else: + fileMenu.Append(wx.ID_EXIT, _("E&xit"), _("Closes this program")) + self._docManager.FileHistoryUseMenu(fileMenu) + self._docManager.FileHistoryAddFilesToMenu() + menuBar.Append(fileMenu, _("&File")); + + editMenu = wx.Menu() + editMenu.Append(wx.ID_UNDO, _("&Undo\tCtrl+Z"), _("Reverses the last action")) + editMenu.Append(wx.ID_REDO, _("&Redo\tCtrl+Y"), _("Reverses the last undo")) + editMenu.AppendSeparator() + #item = wxMenuItem(self.editMenu, wxID_CUT, _("Cu&t\tCtrl+X"), _("Cuts the selection and puts it on the Clipboard")) + #item.SetBitmap(getCutBitmap()) + #editMenu.AppendItem(item) + editMenu.Append(wx.ID_CUT, _("Cu&t\tCtrl+X"), _("Cuts the selection and puts it on the Clipboard")) + wx.EVT_MENU(self, wx.ID_CUT, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, wx.ID_CUT, self.ProcessUpdateUIEvent) + editMenu.Append(wx.ID_COPY, _("&Copy\tCtrl+C"), _("Copies the selection and puts it on the Clipboard")) + wx.EVT_MENU(self, wx.ID_COPY, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, wx.ID_COPY, self.ProcessUpdateUIEvent) + editMenu.Append(wx.ID_PASTE, _("&Paste\tCtrl+V"), _("Inserts Clipboard contents")) + wx.EVT_MENU(self, wx.ID_PASTE, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, wx.ID_PASTE, self.ProcessUpdateUIEvent) + editMenu.Append(wx.ID_CLEAR, _("Cle&ar"), _("Erases the selection")) + wx.EVT_MENU(self, wx.ID_CLEAR, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, wx.ID_CLEAR, self.ProcessUpdateUIEvent) + editMenu.AppendSeparator() + editMenu.Append(wx.ID_SELECTALL, _("Select A&ll\tCtrl+A"), _("Selects all available data")) + wx.EVT_MENU(self, wx.ID_SELECTALL, self.ProcessEvent) + wx.EVT_UPDATE_UI(self, wx.ID_SELECTALL, self.ProcessUpdateUIEvent) + menuBar.Append(editMenu, _("&Edit")) + if sdi: + if self.GetDocument() and self.GetDocument().GetCommandProcessor(): + self.GetDocument().GetCommandProcessor().SetEditMenu(editMenu) + + viewMenu = wx.Menu() + viewMenu.AppendCheckItem(VIEW_TOOLBAR_ID, _("&Toolbar"), _("Shows or hides the toolbar")) + wx.EVT_MENU(self, VIEW_TOOLBAR_ID, self.OnViewToolBar) + wx.EVT_UPDATE_UI(self, VIEW_TOOLBAR_ID, self.OnUpdateViewToolBar) + viewMenu.AppendCheckItem(VIEW_STATUSBAR_ID, _("&Status Bar"), _("Shows or hides the status bar")) + wx.EVT_MENU(self, VIEW_STATUSBAR_ID, self.OnViewStatusBar) + wx.EVT_UPDATE_UI(self, VIEW_STATUSBAR_ID, self.OnUpdateViewStatusBar) + menuBar.Append(viewMenu, _("&View")) + + helpMenu = wx.Menu() + helpMenu.Append(wx.ID_ABOUT, _("&About" + " " + wx.GetApp().GetAppName()), _("Displays program information, version number, and copyright")) + wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) + menuBar.Append(helpMenu, _("&Help")) + + wx.EVT_UPDATE_UI(self, wx.ID_ABOUT, self.ProcessUpdateUIEvent) # Using ID_ABOUT to update the window menu, the window menu items are not triggering + + if sdi: # TODO: Is this really needed? + wx.EVT_COMMAND_FIND_CLOSE(self, -1, self.ProcessEvent) + + return menuBar + + + def CreateDefaultStatusBar(self): + """ + Creates the default StatusBar. + """ + wx.Frame.CreateStatusBar(self) + self.GetStatusBar().Show(wx.ConfigBase_Get().ReadInt("ViewStatusBar", True)) + self.UpdateStatus() + return self.GetStatusBar() + + + def CreateDefaultToolBar(self): + """ + Creates the default ToolBar. + """ + self._toolBar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT) + self._toolBar.AddSimpleTool(wx.ID_NEW, getNewBitmap(), _("New"), _("Creates a new document")) + self._toolBar.AddSimpleTool(wx.ID_OPEN, getOpenBitmap(), _("Open"), _("Opens an existing document")) + self._toolBar.AddSimpleTool(wx.ID_SAVE, getSaveBitmap(), _("Save"), _("Saves the active document")) + self._toolBar.AddSimpleTool(SAVEALL_ID, getSaveAllBitmap(), _("Save All"), _("Saves all the active documents")) + self._toolBar.AddSeparator() + self._toolBar.AddSimpleTool(wx.ID_PRINT, getPrintBitmap(), _("Print"), _("Displays full pages")) + self._toolBar.AddSimpleTool(wx.ID_PREVIEW, getPrintPreviewBitmap(), _("Print Preview"), _("Prints the active document")) + self._toolBar.AddSeparator() + self._toolBar.AddSimpleTool(wx.ID_CUT, getCutBitmap(), _("Cut"), _("Cuts the selection and puts it on the Clipboard")) + self._toolBar.AddSimpleTool(wx.ID_COPY, getCopyBitmap(), _("Copy"), _("Copies the selection and puts it on the Clipboard")) + self._toolBar.AddSimpleTool(wx.ID_PASTE, getPasteBitmap(), _("Paste"), _("Inserts Clipboard contents")) + self._toolBar.AddSimpleTool(wx.ID_UNDO, getUndoBitmap(), _("Undo"), _("Reverses the last action")) + self._toolBar.AddSimpleTool(wx.ID_REDO, getRedoBitmap(), _("Redo"), _("Reverses the last undo")) + self._toolBar.Realize() + self._toolBar.Show(wx.ConfigBase_Get().ReadInt("ViewToolBar", True)) + + return self._toolBar + + + def OnFileSaveAll(self, event): + """ + Saves all of the currently open documents. + """ + docs = wx.GetApp().GetDocumentManager().GetDocuments() + for doc in docs: + doc.Save() + + + def OnAbout(self, event): + """ + Invokes the about dialog. + """ + aboutService = wx.GetApp().GetService(AboutService) + if aboutService: + aboutService.ShowAbout() + + + def OnViewToolBar(self, event): + """ + Toggles whether the ToolBar is visible. + """ + self._toolBar.Show(not self._toolBar.IsShown()) + self._LayoutFrame() + + + def OnUpdateViewToolBar(self, event): + """ + Updates the View ToolBar menu item. + """ + event.Check(self.GetToolBar().IsShown()) + + + def OnViewStatusBar(self, event): + """ + Toggles whether the StatusBar is visible. + """ + self.GetStatusBar().Show(not self.GetStatusBar().IsShown()) + self._LayoutFrame() + + + def OnUpdateViewStatusBar(self, event): + """ + Updates the View StatusBar menu item. + """ + event.Check(self.GetStatusBar().IsShown()) + + + def UpdateStatus(self, message = _("Ready")): + """ + Updates the StatusBar. + """ + # wxBug: Menubar and toolbar help strings don't pop the status text back + if self.GetStatusBar().GetStatusText() != message: + self.GetStatusBar().PushStatusText(message) + + +class DocMDIParentFrameMixIn: + """ + Class with common code used by DocMDIParentFrame and DocTabbedParentFrame. + """ + + + def _GetPosSizeFromConfig(self, pos, size): + """ + Adjusts the position and size of the frame using the saved config position and size. + """ + config = wx.ConfigBase_Get() + if pos == wx.DefaultPosition and size == wx.DefaultSize and config.ReadInt("MDIFrameMaximized", False): + pos = [0, 0] + size = wx.DisplaySize() + # wxBug: Need to set to fill screen to get around bug where maximize is leaving shadow of statusbar, check out maximize call at end of this function + else: + if pos == wx.DefaultPosition: + pos = config.ReadInt("MDIFrameXLoc", -1), config.ReadInt("MDIFrameYLoc", -1) + + if wx.Display_GetFromPoint(pos) == -1: # Check if the frame position is offscreen + pos = wx.DefaultPosition + + if size == wx.DefaultSize: + size = wx.Size(config.ReadInt("MDIFrameXSize", 450), config.ReadInt("MDIFrameYSize", 300)) + return pos, size + + + def _InitFrame(self, embeddedWindows): + """ + Initializes the frame and creates the default menubar, toolbar, and status bar. + """ + self._embeddedWindows = [] + self.SetDropTarget(_DocFrameFileDropTarget(self._docManager, self)) + + if wx.GetApp().GetDefaultIcon(): + self.SetIcon(wx.GetApp().GetDefaultIcon()) + + wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) + wx.EVT_SIZE(self, self.OnSize) + + self.InitializePrintData() + + toolBar = self.CreateDefaultToolBar() + self.SetToolBar(toolBar) + menuBar = self.CreateDefaultMenuBar() + statusBar = self.CreateDefaultStatusBar() + + config = wx.ConfigBase_Get() + if config.ReadInt("MDIFrameMaximized", False): + # wxBug: On maximize, statusbar leaves a residual that needs to be refereshed, happens even when user does it + self.Maximize() + + self.CreateEmbeddedWindows(embeddedWindows) + self._LayoutFrame() + + wx.GetApp().SetTopWindow(self) # Need to do this here in case the services are looking for wx.GetApp().GetTopWindow() + for service in wx.GetApp().GetServices(): + service.InstallControls(self, menuBar = menuBar, toolBar = toolBar, statusBar = statusBar) + if hasattr(service, "ShowWindow"): + service.ShowWindow() # instantiate service windows for correct positioning, we'll hide/show them later based on user preference + + self.SetMenuBar(menuBar) # wxBug: Have to set the menubar at the very end or the automatic MDI "window" menu doesn't get put in the right place when the services add new menus to the menubar + + + def ProcessEvent(self, event): + """ + Processes an event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + id = event.GetId() + if id == SAVEALL_ID: + self.OnFileSaveAll(event) + return True + + return wx.GetApp().ProcessEvent(event) + + + def ProcessUpdateUIEvent(self, event): + """ + Processes a UI event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + id = event.GetId() + if id == wx.ID_CUT: + event.Enable(False) + return True + elif id == wx.ID_COPY: + event.Enable(False) + return True + elif id == wx.ID_PASTE: + event.Enable(False) + return True + elif id == wx.ID_CLEAR: + event.Enable(False) + return True + elif id == wx.ID_SELECTALL: + event.Enable(False) + return True + elif id == SAVEALL_ID: + filesModified = False + docs = wx.GetApp().GetDocumentManager().GetDocuments() + for doc in docs: + if doc.IsModified(): + filesModified = True + break + + event.Enable(filesModified) + return True + else: + return wx.GetApp().ProcessUpdateUIEvent(event) + + + def CreateEmbeddedWindows(self, windows = 0): + """ + Create the specified embedded windows around the edges of the frame. + """ + frameSize = self.GetSize() # TODO: GetClientWindow.GetSize is still returning 0,0 since the frame isn't fully constructed yet, so using full frame size + defaultHSize = int(frameSize[0] / 6) + defaultVSize = int(frameSize[1] / 7) + defaultSubVSize = int(frameSize[1] / 2) + config = wx.ConfigBase_Get() + if windows & (EMBEDDED_WINDOW_LEFT | EMBEDDED_WINDOW_TOPLEFT | EMBEDDED_WINDOW_BOTTOMLEFT): + self._leftEmbWindow = self._CreateEmbeddedWindow(self, (config.ReadInt("MDIEmbedLeftSize", defaultHSize), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_LEFT, visible = config.ReadInt("MDIEmbedLeftVisible", 1), sash = wx.SASH_RIGHT) + else: + self._leftEmbWindow = None + if windows & EMBEDDED_WINDOW_TOPLEFT: + self._topLeftEmbWindow = self._CreateEmbeddedWindow(self._leftEmbWindow, (-1, config.ReadInt("MDIEmbedTopLeftSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopLeftVisible", 1), sash = wx.SASH_BOTTOM) + else: + self._topLeftEmbWindow = None + if windows & EMBEDDED_WINDOW_BOTTOMLEFT: + self._bottomLeftEmbWindow = self._CreateEmbeddedWindow(self._leftEmbWindow, (-1, config.ReadInt("MDIEmbedBottomLeftSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomLeftVisible", 1)) + else: + self._bottomLeftEmbWindow = None + if windows & (EMBEDDED_WINDOW_RIGHT | EMBEDDED_WINDOW_TOPRIGHT | EMBEDDED_WINDOW_BOTTOMRIGHT): + self._rightEmbWindow = self._CreateEmbeddedWindow(self, (config.ReadInt("MDIEmbedRightSize", defaultHSize), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_RIGHT, visible = config.ReadInt("MDIEmbedRightVisible", 1), sash = wx.SASH_LEFT) + else: + self._rightEmbWindow = None + if windows & EMBEDDED_WINDOW_TOPRIGHT: + self._topRightEmbWindow = self._CreateEmbeddedWindow(self._rightEmbWindow, (-1, config.ReadInt("MDIEmbedTopRightSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopRightVisible", 1), sash = wx.SASH_BOTTOM) + else: + self._topRightEmbWindow = None + if windows & EMBEDDED_WINDOW_BOTTOMRIGHT: + self._bottomRightEmbWindow = self._CreateEmbeddedWindow(self._rightEmbWindow, (-1, config.ReadInt("MDIEmbedBottomRightSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomRightVisible", 1)) + else: + self._bottomRightEmbWindow = None + if windows & EMBEDDED_WINDOW_TOP: + self._topEmbWindow = self._CreateEmbeddedWindow(self, (-1, config.ReadInt("MDIEmbedTopSize", defaultVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopVisible", 1), sash = wx.SASH_BOTTOM) + else: + self._topEmbWindow = None + if windows & EMBEDDED_WINDOW_BOTTOM: + self._bottomEmbWindow = self._CreateEmbeddedWindow(self, (-1, config.ReadInt("MDIEmbedBottomSize", defaultVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomVisible", 1), sash = wx.SASH_TOP) + else: + self._bottomEmbWindow = None + + + def SaveEmbeddedWindowSizes(self): + """ + Saves the sizes of the embedded windows. + """ + config = wx.ConfigBase_Get() + if not self.IsMaximized(): + config.WriteInt("MDIFrameXLoc", self.GetPositionTuple()[0]) + config.WriteInt("MDIFrameYLoc", self.GetPositionTuple()[1]) + config.WriteInt("MDIFrameXSize", self.GetSizeTuple()[0]) + config.WriteInt("MDIFrameYSize", self.GetSizeTuple()[1]) + config.WriteInt("MDIFrameMaximized", self.IsMaximized()) + config.WriteInt("ViewToolBar", self._toolBar.IsShown()) + config.WriteInt("ViewStatusBar", self.GetStatusBar().IsShown()) + + if self._leftEmbWindow: + config.WriteInt("MDIEmbedLeftSize", self._leftEmbWindow.GetSize()[0]) + config.WriteInt("MDIEmbedLeftVisible", self._leftEmbWindow.IsShown()) + if self._topLeftEmbWindow: + if self._topLeftEmbWindow._sizeBeforeHidden: + size = self._topLeftEmbWindow._sizeBeforeHidden[1] + else: + size = self._topLeftEmbWindow.GetSize()[1] + config.WriteInt("MDIEmbedTopLeftSize", size) + config.WriteInt("MDIEmbedTopLeftVisible", self._topLeftEmbWindow.IsShown()) + if self._bottomLeftEmbWindow: + if self._bottomLeftEmbWindow._sizeBeforeHidden: + size = self._bottomLeftEmbWindow._sizeBeforeHidden[1] + else: + size = self._bottomLeftEmbWindow.GetSize()[1] + config.WriteInt("MDIEmbedBottomLeftSize", size) + config.WriteInt("MDIEmbedBottomLeftVisible", self._bottomLeftEmbWindow.IsShown()) + if self._rightEmbWindow: + config.WriteInt("MDIEmbedRightSize", self._rightEmbWindow.GetSize()[0]) + config.WriteInt("MDIEmbedRightVisible", self._rightEmbWindow.IsShown()) + if self._topRightEmbWindow: + if self._topRightEmbWindow._sizeBeforeHidden: + size = self._topRightEmbWindow._sizeBeforeHidden[1] + else: + size = self._topRightEmbWindow.GetSize()[1] + config.WriteInt("MDIEmbedTopRightSize", size) + config.WriteInt("MDIEmbedTopRightVisible", self._topRightEmbWindow.IsShown()) + if self._bottomRightEmbWindow: + if self._bottomRightEmbWindow._sizeBeforeHidden: + size = self._bottomRightEmbWindow._sizeBeforeHidden[1] + else: + size = self._bottomRightEmbWindow.GetSize()[1] + config.WriteInt("MDIEmbedBottomRightSize", size) + config.WriteInt("MDIEmbedBottomRightVisible", self._bottomRightEmbWindow.IsShown()) + if self._topEmbWindow: + config.WriteInt("MDIEmbedTopSize", self._topEmbWindow.GetSize()[1]) + config.WriteInt("MDIEmbedTopVisible", self._topEmbWindow.IsShown()) + if self._bottomEmbWindow: + config.WriteInt("MDIEmbedBottomSize", self._bottomEmbWindow.GetSize()[1]) + config.WriteInt("MDIEmbedBottomVisible", self._bottomEmbWindow.IsShown()) + + + def GetEmbeddedWindow(self, loc): + """ + Returns the instance of the embedded window specified by the embedded window location constant. + """ + if loc == EMBEDDED_WINDOW_TOP: + return self._topEmbWindow + elif loc == EMBEDDED_WINDOW_BOTTOM: + return self._bottomEmbWindow + elif loc == EMBEDDED_WINDOW_LEFT: + return self._leftEmbWindow + elif loc == EMBEDDED_WINDOW_RIGHT: + return self._rightEmbWindow + elif loc == EMBEDDED_WINDOW_TOPLEFT: + return self._topLeftEmbWindow + elif loc == EMBEDDED_WINDOW_BOTTOMLEFT: + return self._bottomLeftEmbWindow + elif loc == EMBEDDED_WINDOW_TOPRIGHT: + return self._topRightEmbWindow + elif loc == EMBEDDED_WINDOW_BOTTOMRIGHT: + return self._bottomRightEmbWindow + return None + + + def _CreateEmbeddedWindow(self, parent, size, orientation, alignment, visible = True, sash = None): + """ + Creates the embedded window with the specified size, orientation, and alignment. If the + window is not visible it will retain the size with which it was last viewed. + """ + window = wx.SashLayoutWindow(parent, wx.NewId(), style = wx.NO_BORDER | wx.SW_3D) + window.SetDefaultSize(size) + window.SetOrientation(orientation) + window.SetAlignment(alignment) + if sash != None: # wx.SASH_TOP is 0 so check for None instead of just doing "if sash:" + window.SetSashVisible(sash, True) + #### + def OnEmbeddedWindowSashDrag(event): + if event.GetDragStatus() == wx.SASH_STATUS_OUT_OF_RANGE: + return + sashWindow = event.GetEventObject() + if sashWindow.GetAlignment() == wx.LAYOUT_TOP or sashWindow.GetAlignment() == wx.LAYOUT_BOTTOM: + size = wx.Size(-1, event.GetDragRect().height) + else: + size = wx.Size(event.GetDragRect().width, -1) + event.GetEventObject().SetDefaultSize(size) + self._LayoutFrame() + sashWindow.Refresh() + if isinstance(sashWindow.GetParent(), wx.SashLayoutWindow): + sashWindow.Show() + parentSashWindow = sashWindow.GetParent() # Force a refresh + parentSashWindow.Layout() + parentSashWindow.Refresh() + parentSashWindow.SetSize((parentSashWindow.GetSize().width + 1, parentSashWindow.GetSize().height + 1)) + #### + wx.EVT_SASH_DRAGGED(window, window.GetId(), OnEmbeddedWindowSashDrag) + window._sizeBeforeHidden = None + if not visible: + window.Show(False) + if isinstance(parent, wx.SashLayoutWindow): # It's a window embedded in another sash window so remember its actual size to show it again + window._sizeBeforeHidden = size + return window + + + def ShowEmbeddedWindow(self, window, show = True): + """ + Shows or hides the embedded window specified by the embedded window location constant. + """ + window.Show(show) + if isinstance(window.GetParent(), wx.SashLayoutWindow): # It is a parent sashwindow with multiple embedded sashwindows + parentSashWindow = window.GetParent() + if show: # Make sure it is visible in case all of the subwindows were hidden + parentSashWindow.Show() + if show and window._sizeBeforeHidden: + if window._sizeBeforeHidden[1] == parentSashWindow.GetClientSize()[1]: + if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).IsShown(): + window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).GetSize()[1])) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).IsShown(): + window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).GetSize()[1])) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).IsShown(): + window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).GetSize()[1])) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).IsShown(): + window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).GetSize()[1])) + else: + window.SetDefaultSize(window._sizeBeforeHidden) + # If it is not the size of the full parent sashwindow set the other window's size so that if it gets shown it will have a cooresponding size + if window._sizeBeforeHidden[1] < parentSashWindow.GetClientSize()[1]: + otherWindowSize = (-1, parentSashWindow.GetClientSize()[1] - window._sizeBeforeHidden[1]) + if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT): + self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).SetDefaultSize(otherWindowSize) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT): + self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).SetDefaultSize(otherWindowSize) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT): + self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).SetDefaultSize(otherWindowSize) + elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT): + self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).SetDefaultSize(otherWindowSize) + + if not show: + if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).IsShown() \ + or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).IsShown() \ + or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).IsShown() \ + or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).IsShown(): + parentSashWindow.Hide() # Hide the parent sashwindow if all of the children are hidden + parentSashWindow.Layout() # Force a refresh + parentSashWindow.Refresh() + parentSashWindow.SetSize((parentSashWindow.GetSize().width + 1, parentSashWindow.GetSize().height + 1)) + self._LayoutFrame() + + + def HideEmbeddedWindow(self): + """ + Hides the embedded window specified by the embedded window location constant. + """ + self.ShowEmbeddedWindow(show = False) + + +class DocTabbedChildFrame(wx.Panel): + """ + The wxDocMDIChildFrame class provides a default frame for displaying + documents on separate windows. This class can only be used for MDI child + frames. + + The class is part of the document/view framework supported by wxWindows, + and cooperates with the wxView, wxDocument, wxDocManager and wxDocTemplate + classes. + """ + + + def __init__(self, doc, view, frame, id, title, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = "frame"): + """ + Constructor. Note that the event table must be rebuilt for the + frame since the EvtHandler is not virtual. + """ + wx.Panel.__init__(self, frame.GetNotebook(), id) + self._childDocument = doc + self._childView = view + frame.AddNotebookPage(self, doc.GetPrintableName()) + if view: + view.SetFrame(self) + + + def GetIcon(self): + """ + Dummy method since the icon of tabbed frames are managed by the notebook. + """ + return None + + + def SetIcon(self, icon): + """ + Dummy method since the icon of tabbed frames are managed by the notebook. + """ + pass + + + def Destroy(self): + """ + Removes the current notebook page. + """ + wx.GetApp().GetTopWindow().RemoveNotebookPage(self) + + + def SetFocus(self): + """ + Activates the current notebook page. + """ + wx.GetApp().GetTopWindow().ActivateNotebookPage(self) + + + def Activate(self): # Need this in case there are embedded sash windows and such, OnActivate is not getting called + """ + Activates the current view. + """ + # Called by Project Editor + if self._childView: + self._childView.Activate(True) + + + def GetTitle(self): + """ + Returns the frame's title. + """ + wx.GetApp().GetTopWindow().GetNotebookPageTitle(self) + + + def SetTitle(self, title): + """ + Sets the frame's title. + """ + wx.GetApp().GetTopWindow().SetNotebookPageTitle(self, title) + + + def ProcessEvent(event): + """ + Processes an event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + if not self._childView or not self._childView.ProcessEvent(event): + if not isinstance(event, wx.CommandEvent) or not self.GetParent() or not self.GetParent().ProcessEvent(event): + return False + else: + return True + else: + return True + + + def GetDocument(self): + """ + Returns the document associated with this frame. + """ + return self._childDocument + + + def SetDocument(self, document): + """ + Sets the document for this frame. + """ + self._childDocument = document + + + def GetView(self): + """ + Returns the view associated with this frame. + """ + return self._childView + + + def SetView(self, view): + """ + Sets the view for this frame. + """ + self._childView = view + + +class DocTabbedParentFrame(wx.Frame, DocFrameMixIn, DocMDIParentFrameMixIn): + """ + The DocTabbedParentFrame class provides a default top-level frame for + applications using the document/view framework. This class can only be + used for MDI parent frames that use a tabbed interface. + + It cooperates with the wxView, wxDocument, wxDocManager and wxDocTemplate + classes. + """ + + + def __init__(self, docManager, frame, id, title, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = "DocTabbedParentFrame", embeddedWindows = 0): + """ + Constructor. Note that the event table must be rebuilt for the + frame since the EvtHandler is not virtual. + """ + pos, size = self._GetPosSizeFromConfig(pos, size) + wx.Frame.__init__(self, frame, id, title, pos, size, style, name) + + # From docview.MDIParentFrame + self._docManager = docManager + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + wx.EVT_MENU(self, wx.ID_EXIT, self.OnExit) + wx.EVT_MENU_RANGE(self, wx.ID_FILE1, wx.ID_FILE9, self.OnMRUFile) + + wx.EVT_MENU(self, wx.ID_NEW, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_OPEN, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_CLOSE_ALL, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_CLOSE, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_REVERT, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_SAVE, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_SAVEAS, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_UNDO, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_REDO, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_PRINT, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_PRINT_SETUP, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_PREVIEW, self.ProcessEvent) + wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) + + wx.EVT_UPDATE_UI(self, wx.ID_NEW, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_OPEN, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_CLOSE_ALL, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_CLOSE, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_REVERT, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_SAVE, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_SAVEAS, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_UNDO, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_REDO, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_PRINT, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_PRINT_SETUP, self.ProcessUpdateUIEvent) + wx.EVT_UPDATE_UI(self, wx.ID_PREVIEW, self.ProcessUpdateUIEvent) + # End From docview.MDIParentFrame + + self.CreateNotebook() + self._InitFrame(embeddedWindows) + + + def _LayoutFrame(self): + """ + Lays out the frame. + """ + wx.LayoutAlgorithm().LayoutFrame(self, self._notebook) + + + def CreateNotebook(self): + """ + Creates the notebook to use for the tabbed document interface. + """ + self._notebook = wx.Notebook(self, wx.NewId()) + # self._notebook.SetSizer(wx.NotebookSizer(self._notebook)) + wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self._notebook.GetId(), self.OnNotebookPageChanged) + wx.EVT_RIGHT_DOWN(self._notebook, self.OnNotebookRightClick) + + 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._notebook.AssignImageList(iconList) + + + def GetNotebook(self): + """ + Returns the notebook used by the tabbed document interface. + """ + return self._notebook + + + def GetActiveChild(self): + """ + Returns the active notebook page, which to the framework is treated as + a document frame. + """ + index = self._notebook.GetSelection() + if index == -1: + return None + return self._notebook.GetPage(index) + + + def OnNotebookPageChanged(self, event): + """ + Activates a notebook page's view when it is selected. + """ + index = self._notebook.GetSelection() + if index > -1: + self._notebook.GetPage(index).GetView().Activate() + + + def OnNotebookRightClick(self, event): + """ + Handles right clicks for the notebook, enabling users to either close + a tab or select from the available documents if the user clicks on the + notebook's white space. + """ + index, type = self._notebook.HitTest(event.GetPosition()) + menu = wx.Menu() + x, y = event.GetX(), event.GetY() + if index > -1: + doc = self._notebook.GetPage(index).GetView().GetDocument() + id = wx.NewId() + menu.Append(id, _("Close")) + def OnRightMenuSelect(event): + doc.DeleteAllViews() + wx.EVT_MENU(self, id, OnRightMenuSelect) + if self._notebook.GetPageCount() > 1: + menu.AppendSeparator() + tabsMenu = wx.Menu() + menu.AppendMenu(wx.NewId(), _("Select Tab"), tabsMenu) + else: + y = y - 25 # wxBug: It is offsetting click events in the blank notebook area + tabsMenu = menu + + if self._notebook.GetPageCount() > 1: + selectIDs = {} + for i in range(0, self._notebook.GetPageCount()): + id = wx.NewId() + selectIDs[id] = i + tabsMenu.Append(id, self._notebook.GetPageText(i)) + def OnRightMenuSelect(event): + self._notebook.SetSelection(selectIDs[event.GetId()]) + wx.EVT_MENU(self, id, OnRightMenuSelect) + + self._notebook.PopupMenu(menu, wx.Point(x, y)) + menu.Destroy() + + + def AddNotebookPage(self, panel, title): + """ + Adds a document page to the notebook. + """ + self._notebook.AddPage(panel, title) + index = self._notebook.GetPageCount() - 1 + self._notebook.SetSelection(index) + + found = False # Now set the icon + template = panel.GetDocument().GetDocumentTemplate() + if template: + for t, iconIndex in self._iconIndexLookup: + if t is template: + self._notebook.SetPageImage(index, iconIndex) + found = True + break + if not found: + self._notebook.SetPageImage(index, self._blankIconIndex) + self._notebook.Layout() + + + def RemoveNotebookPage(self, panel): + """ + Removes a document page from the notebook. + """ + index = self.GetNotebookPageIndex(panel) + if index > -1: + self._notebook.DeletePage(index) + + + def ActivateNotebookPage(self, panel): + """ + Sets the notebook to the specified panel. + """ + index = self.GetNotebookPageIndex(panel) + if index > -1: + self._notebook.SetSelection(index) + + + def GetNotebookPageTitle(self, panel): + self._notebook.GetPageText(self.GetNotebookPageIndex(panel)) + + + def SetNotebookPageTitle(self, panel, title): + self._notebook.SetPageText(self.GetNotebookPageIndex(panel), title) + + + def GetNotebookPageIndex(self, panel): + """ + Returns the index of particular notebook panel. + """ + index = -1 + for i in range(self._notebook.GetPageCount()): + if self._notebook.GetPage(i) == panel: + index = i + break + return index + + + def ProcessEvent(self, event): + """ + Processes an event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + if wx.GetApp().ProcessEventBeforeWindows(event): + return True + if self._docManager and self._docManager.ProcessEvent(event): + return True + return DocMDIParentFrameMixIn.ProcessEvent(self, event) + + + def ProcessUpdateUIEvent(self, event): + """ + Processes a UI event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + if wx.GetApp().ProcessUpdateUIEventBeforeWindows(event): + return True + if self._docManager and self._docManager.ProcessUpdateUIEvent(event): + return True + return DocMDIParentFrameMixIn.ProcessUpdateUIEvent(self, event) + + + def OnExit(self, event): + """ + Called when File/Exit is chosen and closes the window. + """ + self.Close() + + + def OnMRUFile(self, event): + """ + Opens the appropriate file when it is selected from the file history + menu. + """ + n = event.GetId() - wx.ID_FILE1 + filename = self._docManager.GetHistoryFile(n) + if filename: + self._docManager.CreateDocument(filename, wx.lib.docview.DOC_SILENT) + else: + self._docManager.RemoveFileFromHistory(n) + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("File Error") + wx.MessageBox("The file '%s' doesn't exist and couldn't be opened.\nIt has been removed from the most recently used files list" % FileNameFromPath(file), + msgTitle, + wx.OK | wx.ICON_EXCLAMATION, + self) + + + def OnSize(self, event): + """ + Called when the frame is resized and lays out the client window. + """ + # Needed in case there are splitpanels around the mdi frame + self._LayoutFrame() + + + def OnCloseWindow(self, event): + """ + Called when the frame is closed. Remembers the frame size. + """ + self.SaveEmbeddedWindowSizes() + + # save and close services last + for service in wx.GetApp().GetServices(): + if not service.OnCloseFrame(event): + return + + # From docview.MDIParentFrame + if self._docManager.Clear(not event.CanVeto()): + self.Destroy() + else: + event.Veto() + + +class DocMDIChildFrame(wx.MDIChildFrame): + """ + The wxDocMDIChildFrame class provides a default frame for displaying + documents on separate windows. This class can only be used for MDI child + frames. + + The class is part of the document/view framework supported by wxWindows, + and cooperates with the wxView, wxDocument, wxDocManager and wxDocTemplate + classes. + """ + + + def __init__(self, doc, view, frame, id, title, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = "frame"): + """ + Constructor. Note that the event table must be rebuilt for the + frame since the EvtHandler is not virtual. + """ + wx.MDIChildFrame.__init__(self, frame, id, title, pos, size, style, name) + self._childDocument = doc + self._childView = view + if view: + view.SetFrame(self) + # self.Create(doc, view, frame, id, title, pos, size, style, name) + self._activeEvent = None + self._activated = 0 + wx.EVT_ACTIVATE(self, self.OnActivate) + wx.EVT_CLOSE(self, self.OnCloseWindow) + + if frame: # wxBug: For some reason the EVT_ACTIVATE event is not getting triggered for the first mdi client window that is opened so we have to do it manually + mdiChildren = filter(lambda x: isinstance(x, wx.MDIChildFrame), frame.GetChildren()) + if len(mdiChildren) == 1: + self.Activate() + + +## # Couldn't get this to work, but seems to work fine with single stage construction +## def Create(self, doc, view, frame, id, title, pos, size, style, name): +## self._childDocument = doc +## self._childView = view +## if wx.MDIChildFrame.Create(self, frame, id, title, pos, size, style, name): +## if view: +## view.SetFrame(self) +## return True +## return False + + + + def Activate(self): # Need this in case there are embedded sash windows and such, OnActivate is not getting called + """ + Activates the current view. + """ + if self._childView: + self._childView.Activate(True) + + + def ProcessEvent(event): + """ + Processes an event, searching event tables and calling zero or more + suitable event handler function(s). Note that the ProcessEvent + method is called from the wxPython docview framework directly since + wxPython does not have a virtual ProcessEvent function. + """ + if self._activeEvent == event: + return False + + self._activeEvent = event # Break recursion loops + + if self._childView: + self._childView.Activate(True) + + if not self._childView or not self._childView.ProcessEvent(event): + if not isinstance(event, wx.CommandEvent) or not self.GetParent() or not self.GetParent().ProcessEvent(event): + ret = False + else: + ret = True + else: + ret = True + + self._activeEvent = None + return ret + + + def OnActivate(self, event): + """ + Sets the currently active view to be the frame's view. You may need to + override (but still call) this function in order to set the keyboard + focus for your subwindow. + """ + if self._activated != 0: + return True + self._activated += 1 + wx.MDIChildFrame.Activate(self) + if event.GetActive() and self._childView: + self._childView.Activate(event.GetActive()) + self._activated = 0 + + + def OnCloseWindow(self, event): + """ + Closes and deletes the current view and document. + """ + if self._childView: + ans = False + if not event.CanVeto(): + ans = True + else: + ans = self._childView.Close(deleteWindow = False) + + if ans: + self._childView.Activate(False) + self._childView.Destroy() + self._childView = None + if self._childDocument: + self._childDocument.Destroy() # This isn't in the wxWindows codebase but the document needs to be disposed of somehow + self._childDocument = None + self.Destroy() + else: + event.Veto() + else: + event.Veto() + + + def GetDocument(self): + """ + Returns the document associated with this frame. + """ + return self._childDocument + + + def SetDocument(self, document): + """ + Sets the document for this frame. + """ + self._childDocument = document + + + def GetView(self): + """ + Returns the view associated with this frame. + """ + return self._childView + + + def SetView(self, view): + """ + Sets the view for this frame. + """ + self._childView = view + + + + + class DocService(wx.EvtHandler): """ An abstract class used to add reusable services to a docview application. @@ -158,14 +1257,17 @@ class DocOptionsService(DocService): """ - def __init__(self, showGeneralOptions = True): + def __init__(self, showGeneralOptions=True, allowModeChanges=True): """ Initializes the options service with the option of suppressing the default general options pane that is included with the options service by setting - showGeneralOptions to False. + showGeneralOptions to False. It allowModeChanges is set to False, the + default general options pane will allow users to change the document + interface mode between SDI and MDI modes. """ DocService.__init__(self) self.ClearOptionsPanels() + self._allowModeChanges = allowModeChanges self._toolOptionsID = wx.NewId() if showGeneralOptions: self.AddOptionsPanel(GeneralOptionsPanel) @@ -202,6 +1304,22 @@ class DocOptionsService(DocService): return False + def GetAllowModeChanges(self): + """ + Return true if the default general options pane should allow users to + change the document interface mode between SDI and MDI modes. + """ + return self._allowModeChanges + + + def SetAllowModeChanges(self, allowModeChanges): + """ + Set to true if the default general options pane should allow users to + change the document interface mode between SDI and MDI modes. + """ + self._allowModeChanges = allowModeChanges + + def ClearOptionsPanels(self): """ Clears all of the options panels that have been added into the @@ -251,7 +1369,7 @@ class OptionsDialog(wx.Dialog): sizer = wx.BoxSizer(wx.VERTICAL) - optionsNotebook = wx.Notebook(self, -1, size = (310, 375), style = wx.NB_MULTILINE) + optionsNotebook = wx.Notebook(self, -1, size = (310, 375)) sizer.Add(optionsNotebook, 0, wx.ALL | wx.EXPAND, SPACE) for optionsPanelClass in optionsPanelClasses: optionsPanel = optionsPanelClass(optionsNotebook, -1) @@ -302,48 +1420,55 @@ class GeneralOptionsPanel(wx.Panel): wx.Panel.__init__(self, parent, id) SPACE = 10 HALF_SPACE = 5 - backgroundColor = wx.WHITE config = wx.ConfigBase_Get() self._showTipsCheckBox = wx.CheckBox(self, -1, _("Show tips at start up")) - self._showTipsCheckBox.SetBackgroundColour(backgroundColor) # wxBUG: uses wrong background color self._showTipsCheckBox.SetValue(config.ReadInt("ShowTipAtStartup", True)) - self._documentRadioBox = wx.RadioBox(self, -1, _("Document interface"), - choices = [_("Show each document in its own window (SDI)"), - _("Show All documents in a single window (MDI)")], - majorDimension=1, - ) - if config.ReadInt("UseMDI", True): - self._documentRadioBox.SetSelection(1) - else: - self._documentRadioBox.SetSelection(0) - def OnDocumentInterfaceSelect(event): - if not self._documentInterfaceMessageShown: - msgTitle = wx.GetApp().GetAppName() - if not msgTitle: - msgTitle = _("Document Options") - wx.MessageBox("Document interface changes will not appear until the application is restarted.", - msgTitle, - wx.OK | wx.ICON_INFORMATION, - self.GetParent()) - self._documentInterfaceMessageShown = True - wx.EVT_RADIOBOX(self, self._documentRadioBox.GetId(), OnDocumentInterfaceSelect) + if wx.GetApp().GetService(DocOptionsService).GetAllowModeChanges(): + choices = [_("Show each document in its own window"), _("Show all documents in a single window with tabs")] + if wx.Platform == "__WXMSW__": + choices.append(_("Show all documents in a single window with child windows")) + self._documentRadioBox = wx.RadioBox(self, -1, _("Document Interface"), + choices = choices, + majorDimension=1, + ) + if config.ReadInt("UseWinMDI", False): + self._documentRadioBox.SetSelection(2) + elif config.ReadInt("UseMDI", True): + self._documentRadioBox.SetSelection(1) + else: + self._documentRadioBox.SetSelection(0) + def OnDocumentInterfaceSelect(event): + if not self._documentInterfaceMessageShown: + msgTitle = wx.GetApp().GetAppName() + if not msgTitle: + msgTitle = _("Document Options") + wx.MessageBox("Document interface changes will not appear until the application is restarted.", + msgTitle, + wx.OK | wx.ICON_INFORMATION, + self.GetParent()) + self._documentInterfaceMessageShown = True + wx.EVT_RADIOBOX(self, self._documentRadioBox.GetId(), OnDocumentInterfaceSelect) optionsBorderSizer = wx.BoxSizer(wx.VERTICAL) optionsSizer = wx.BoxSizer(wx.VERTICAL) + if wx.GetApp().GetService(DocOptionsService).GetAllowModeChanges(): + optionsSizer.Add(self._documentRadioBox, 0, wx.ALL, HALF_SPACE) optionsSizer.Add(self._showTipsCheckBox, 0, wx.ALL, HALF_SPACE) - optionsSizer.Add(self._documentRadioBox, 0, wx.ALL, HALF_SPACE) optionsBorderSizer.Add(optionsSizer, 0, wx.ALL, SPACE) self.SetSizer(optionsBorderSizer) self.Layout() self._documentInterfaceMessageShown = False parent.AddPage(self, _("Options")) + def OnOK(self, optionsDialog): """ Updates the config based on the selections in the options panel. """ config = wx.ConfigBase_Get() config.WriteInt("ShowTipAtStartup", self._showTipsCheckBox.GetValue()) - config.WriteInt("UseMDI", self._documentRadioBox.GetSelection()) + if wx.GetApp().GetService(DocOptionsService).GetAllowModeChanges(): + config.WriteInt("UseMDI", (self._documentRadioBox.GetSelection() == 1)) + config.WriteInt("UseWinMDI", (self._documentRadioBox.GetSelection() == 2)) class DocApp(wx.PySimpleApp): @@ -361,6 +1486,8 @@ class DocApp(wx.PySimpleApp): self._services = [] self._defaultIcon = None self._registeredCloseEvent = False + self._useTabbedMDI = True + if not hasattr(self, "_debug"): # only set if not already initialized self._debug = False if not hasattr(self, "_singleInstance"): # only set if not already initialized @@ -376,7 +1503,7 @@ class DocApp(wx.PySimpleApp): fno = tfile.fileno() self._sharedMemory = mmap.mmap(fno, 1024, "shared_memory") else: - tfile = file(os.path.join(tempfile.gettempdir(), tempfile.gettempprefix() + getpass.getuser() + "AGSharedMemory"), 'w+b') + tfile = file(os.path.join(tempfile.gettempdir(), tempfile.gettempprefix() + self.GetAppName() + '-' + wx.GetUserId() + "AGSharedMemory"), 'w+b') tfile.write("*") tfile.seek(1024) tfile.write(" ") @@ -384,31 +1511,23 @@ class DocApp(wx.PySimpleApp): fno = tfile.fileno() self._sharedMemory = mmap.mmap(fno, 1024) - self._singleInstanceChecker = wx.SingleInstanceChecker(self.GetAppName() + '-' + wx.GetUserId()) + self._singleInstanceChecker = wx.SingleInstanceChecker(self.GetAppName() + '-' + wx.GetUserId(), tempfile.gettempdir()) if self._singleInstanceChecker.IsAnotherRunning(): # have running single instance open file arguments - foundArgs = False - args = sys.argv[1:] - for arg in args: - if arg[0] != '/' and arg[0] != '-': - foundArgs = True - break - - if foundArgs: - data = pickle.dumps(args) - while 1: + data = pickle.dumps(sys.argv[1:]) + while 1: + self._sharedMemory.seek(0) + marker = self._sharedMemory.read_byte() + if marker == '\0' or marker == '*': # available buffer self._sharedMemory.seek(0) - marker = self._sharedMemory.read_byte() - if marker == '\0' or marker == '*': # available buffer - self._sharedMemory.seek(0) - self._sharedMemory.write_byte('-') # set writing marker - self._sharedMemory.write(data) # write files we tried to open to shared memory - self._sharedMemory.seek(0) - self._sharedMemory.write_byte('+') # set finished writing marker - self._sharedMemory.flush() - break - else: - time.sleep(1) # give enough time for buffer to be available + self._sharedMemory.write_byte('-') # set writing marker + self._sharedMemory.write(data) # write files we tried to open to shared memory + self._sharedMemory.seek(0) + self._sharedMemory.write_byte('+') # set finished writing marker + self._sharedMemory.flush() + break + else: + time.sleep(1) # give enough time for buffer to be available return False else: @@ -418,10 +1537,21 @@ class DocApp(wx.PySimpleApp): return True + def OpenMainFrame(self): + docManager = self.GetDocumentManager() + if docManager.GetFlags() & wx.lib.docview.DOC_MDI: + if self.GetUseTabbedMDI(): + frame = wx.lib.pydocview.DocTabbedParentFrame(docManager, None, -1, self.GetAppName()) + else: + frame = wx.lib.pydocview.DocMDIParentFrame(docManager, None, -1, self.GetAppName()) + frame.Show(True) + + def DoBackgroundListenAndLoad(self): """ Open any files specified in the given command line argument passed in via shared memory """ + self._timer.Stop() self._sharedMemory.seek(0) if self._sharedMemory.read_byte() == '+': # available data @@ -431,7 +1561,7 @@ class DocApp(wx.PySimpleApp): self._sharedMemory.flush() args = pickle.loads(data) for arg in args: - if arg[0] != '/' and arg[0] != '-': + if arg[0] != '/' and arg[0] != '-' and os.path.exists(arg): self.GetDocumentManager().CreateDocument(arg, wx.lib.docview.DOC_SILENT) # force display of running app @@ -452,7 +1582,7 @@ class DocApp(wx.PySimpleApp): """ args = sys.argv[1:] for arg in args: - if arg[0] != '/' and arg[0] != '-': + if arg[0] != '/' and arg[0] != '-' and os.path.exists(arg): self.GetDocumentManager().CreateDocument(arg, wx.lib.docview.DOC_SILENT) @@ -558,7 +1688,9 @@ class DocApp(wx.PySimpleApp): service.OnExit() config = wx.ConfigBase_Get() self._docManager.FileHistorySave(config) - del self._singleInstanceChecker + + if hasattr(self, "_singleInstanceChecker"): + del self._singleInstanceChecker def GetDefaultDocManagerFlags(self): @@ -566,8 +1698,10 @@ class DocApp(wx.PySimpleApp): Returns the default flags to use when creating the DocManager. """ config = wx.ConfigBase_Get() - if config.ReadInt("UseMDI", True): + if config.ReadInt("UseMDI", True) or config.ReadInt("UseWinMDI", False): flags = wx.lib.docview.DOC_MDI | wx.lib.docview.DOC_OPEN_ONCE + if config.ReadInt("UseWinMDI", False): + self.SetUseTabbedMDI(False) else: flags = wx.lib.docview.DOC_SDI | wx.lib.docview.DOC_OPEN_ONCE return flags @@ -582,7 +1716,7 @@ class DocApp(wx.PySimpleApp): showTip = config.ReadInt("ShowTipAtStartup", 1) if showTip: index = config.ReadInt("TipIndex", 0) - showTipResult = wx.ShowTip(frame, tipProvider, showAtStartup = showTip) + showTipResult = wx.ShowTip(wx.GetApp().GetTopWindow(), tipProvider, showAtStartup = showTip) if showTipResult != showTip: config.WriteInt("ShowTipAtStartup", showTipResult) @@ -600,6 +1734,20 @@ class DocApp(wx.PySimpleApp): return menuBar.GetMenu(editMenuIndex) + def GetUseTabbedMDI(self): + """ + Returns True if Windows MDI should use folder tabs instead of child windows. + """ + return self._useTabbedMDI + + + def SetUseTabbedMDI(self, useTabbedMDI): + """ + Set to True if Windows MDI should use folder tabs instead of child windows. + """ + self._useTabbedMDI = useTabbedMDI + + def CreateDocumentFrame(self, view, doc, flags, id = -1, title = "", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE): """ Called by the DocManager to create and return a new Frame for a Document. @@ -621,9 +1769,15 @@ class DocApp(wx.PySimpleApp): if doc and doc.GetCommandProcessor(): doc.GetCommandProcessor().SetEditMenu(self.GetEditMenu(frame)) elif docflags & wx.lib.docview.DOC_MDI: - frame = self.CreateMDIDocumentFrame(doc, view, id, title, pos, size, style) - if doc and doc.GetDocumentTemplate().GetIcon(): - frame.SetIcon(doc.GetDocumentTemplate().GetIcon()) + if self.GetUseTabbedMDI(): + frame = self.CreateTabbedDocumentFrame(doc, view, id, title, pos, size, style) + else: + frame = self.CreateMDIDocumentFrame(doc, view, id, title, pos, size, style) + if doc: + if doc.GetDocumentTemplate().GetIcon(): + frame.SetIcon(doc.GetDocumentTemplate().GetIcon()) + elif wx.GetApp().GetTopWindow().GetIcon(): + frame.SetIcon(wx.GetApp().GetTopWindow().GetIcon()) if doc and doc.GetCommandProcessor(): doc.GetCommandProcessor().SetEditMenu(self.GetEditMenu(wx.GetApp().GetTopWindow())) if not frame.GetIcon() and self._defaultIcon: @@ -640,6 +1794,14 @@ class DocApp(wx.PySimpleApp): return frame + def CreateTabbedDocumentFrame(self, doc, view, id = -1, title = "", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE): + """ + Creates and returns an MDI Document Frame for a Tabbed MDI view + """ + frame = DocTabbedChildFrame(doc, view, wx.GetApp().GetTopWindow(), id, title, pos, size, style) + return frame + + def CreateMDIDocumentFrame(self, doc, view, id = -1, title = "", pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE): """ Creates and returns an MDI Document Frame. @@ -866,7 +2028,7 @@ class _DocFrameFileDropTarget(wx.FileDropTarget): self._docManager.FindSuitableParent()) -class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): +class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame, DocFrameMixIn, DocMDIParentFrameMixIn): """ The DocMDIParentFrame is the primary frame which the DocApp uses to host MDI child windows. It offers features such as a default menubar, toolbar, and status bar, and a mechanism to manage embedded windows @@ -880,404 +2042,18 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): optional embeddedWindows parameter with the embedded window constants to create embedded windows around the edges of the DocMDIParentFrame. """ - config = wx.ConfigBase_Get() - if pos == wx.DefaultPosition and size == wx.DefaultSize and config.ReadInt("MDIFrameMaximized", False): - pos = [0, 0] - size = wx.DisplaySize() - # wxBug: Need to set to fill screen to get around bug where maximize is leaving shadow of statusbar, check out maximize call at end of this function - else: - if pos == wx.DefaultPosition: - pos = config.ReadInt("MDIFrameXLoc", -1), config.ReadInt("MDIFrameYLoc", -1) - - if wx.Display_GetFromPoint(pos) == -1: # Check if the frame position is offscreen - pos = wx.DefaultPosition - - if size == wx.DefaultSize: - size = wx.Size(config.ReadInt("MDIFrameXSize", 450), config.ReadInt("MDIFrameYSize", 300)) - + pos, size = self._GetPosSizeFromConfig(pos, size) wx.lib.docview.DocMDIParentFrame.__init__(self, docManager, parent, id, title, pos, size, style, name) - self._embeddedWindows = [] - self.SetDropTarget(_DocFrameFileDropTarget(docManager, self)) - - if wx.GetApp().GetDefaultIcon(): - self.SetIcon(wx.GetApp().GetDefaultIcon()) - - wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) - wx.EVT_SIZE(self, self.OnSize) - - self.InitializePrintData() - - toolBar = self.CreateDefaultToolBar() - self.SetToolBar(toolBar) - menuBar = self.CreateDefaultMenuBar() - statusBar = self.CreateDefaultStatusBar() - - if config.ReadInt("MDIFrameMaximized", False): - # wxBug: On maximize, statusbar leaves a residual that needs to be refereshed, happens even when user does it - self.Maximize() - - self.CreateEmbeddedWindows(embeddedWindows) - - wx.GetApp().SetTopWindow(self) # Need to do this here in case the services are looking for wx.GetApp().GetTopWindow() - for service in wx.GetApp().GetServices(): - service.InstallControls(self, menuBar = menuBar, toolBar = toolBar, statusBar = statusBar) - if hasattr(service, "ShowWindow"): - service.ShowWindow() # instantiate service windows for correct positioning, we'll hide/show them later based on user preference - - self.SetMenuBar(menuBar) # wxBug: Have to set the menubar at the very end or the automatic MDI "window" menu doesn't get put in the right place when the services add new menus to the menubar + self._InitFrame(embeddedWindows) - def CreateEmbeddedWindows(self, windows = 0): + def _LayoutFrame(self): """ - Create the specified embedded windows around the edges of the DocMDIParentFrame. + Lays out the frame. """ - frameSize = self.GetSize() # TODO: GetClientWindow.GetSize is still returning 0,0 since the frame isn't fully constructed yet, so using full frame size - defaultHSize = int(frameSize[0] / 6) - defaultVSize = int(frameSize[1] / 7) - defaultSubVSize = int(frameSize[1] / 2) - #print defaultHSize, defaultVSize, defaultSubVSize - config = wx.ConfigBase_Get() - if windows & (EMBEDDED_WINDOW_LEFT | EMBEDDED_WINDOW_TOPLEFT | EMBEDDED_WINDOW_BOTTOMLEFT): - self._leftEmbWindow = self._CreateEmbeddedWindow(self, (config.ReadInt("MDIEmbedLeftSize", defaultHSize), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_LEFT, visible = config.ReadInt("MDIEmbedLeftVisible", 1), sash = wx.SASH_RIGHT) - else: - self._leftEmbWindow = None - if windows & EMBEDDED_WINDOW_TOPLEFT: - self._topLeftEmbWindow = self._CreateEmbeddedWindow(self._leftEmbWindow, (-1, config.ReadInt("MDIEmbedTopLeftSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopLeftVisible", 1), sash = wx.SASH_BOTTOM) - else: - self._topLeftEmbWindow = None - if windows & EMBEDDED_WINDOW_BOTTOMLEFT: - self._bottomLeftEmbWindow = self._CreateEmbeddedWindow(self._leftEmbWindow, (-1, config.ReadInt("MDIEmbedBottomLeftSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomLeftVisible", 1)) - else: - self._bottomLeftEmbWindow = None - if windows & (EMBEDDED_WINDOW_RIGHT | EMBEDDED_WINDOW_TOPRIGHT | EMBEDDED_WINDOW_BOTTOMRIGHT): - self._rightEmbWindow = self._CreateEmbeddedWindow(self, (config.ReadInt("MDIEmbedRightSize", defaultHSize), -1), wx.LAYOUT_VERTICAL, wx.LAYOUT_RIGHT, visible = config.ReadInt("MDIEmbedRightVisible", 1), sash = wx.SASH_LEFT) - else: - self._rightEmbWindow = None - if windows & EMBEDDED_WINDOW_TOPRIGHT: - self._topRightEmbWindow = self._CreateEmbeddedWindow(self._rightEmbWindow, (-1, config.ReadInt("MDIEmbedTopRightSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopRightVisible", 1), sash = wx.SASH_BOTTOM) - else: - self._topRightEmbWindow = None - if windows & EMBEDDED_WINDOW_BOTTOMRIGHT: - self._bottomRightEmbWindow = self._CreateEmbeddedWindow(self._rightEmbWindow, (-1, config.ReadInt("MDIEmbedBottomRightSize", defaultSubVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomRightVisible", 1)) - else: - self._bottomRightEmbWindow = None - if windows & EMBEDDED_WINDOW_TOP: - self._topEmbWindow = self._CreateEmbeddedWindow(self, (-1, config.ReadInt("MDIEmbedTopSize", defaultVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_TOP, visible = config.ReadInt("MDIEmbedTopVisible", 1), sash = wx.SASH_BOTTOM) - else: - self._topEmbWindow = None - if windows & EMBEDDED_WINDOW_BOTTOM: - self._bottomEmbWindow = self._CreateEmbeddedWindow(self, (-1, config.ReadInt("MDIEmbedBottomSize", defaultVSize)), wx.LAYOUT_HORIZONTAL, wx.LAYOUT_BOTTOM, visible = config.ReadInt("MDIEmbedBottomVisible", 1), sash = wx.SASH_TOP) - else: - self._bottomEmbWindow = None wx.LayoutAlgorithm().LayoutMDIFrame(self) self.GetClientWindow().Refresh() - - - def SaveEmbeddedWindowSizes(self): - """ - Saves the sizes of the embedded windows. - """ - config = wx.ConfigBase_Get() - if self._leftEmbWindow: - config.WriteInt("MDIEmbedLeftSize", self._leftEmbWindow.GetSize()[0]) - config.WriteInt("MDIEmbedLeftVisible", self._leftEmbWindow.IsShown()) - if self._topLeftEmbWindow: - if self._topLeftEmbWindow._sizeBeforeHidden: - size = self._topLeftEmbWindow._sizeBeforeHidden[1] - else: - size = self._topLeftEmbWindow.GetSize()[1] - config.WriteInt("MDIEmbedTopLeftSize", size) - config.WriteInt("MDIEmbedTopLeftVisible", self._topLeftEmbWindow.IsShown()) - if self._bottomLeftEmbWindow: - if self._bottomLeftEmbWindow._sizeBeforeHidden: - size = self._bottomLeftEmbWindow._sizeBeforeHidden[1] - else: - size = self._bottomLeftEmbWindow.GetSize()[1] - config.WriteInt("MDIEmbedBottomLeftSize", size) - config.WriteInt("MDIEmbedBottomLeftVisible", self._bottomLeftEmbWindow.IsShown()) - if self._rightEmbWindow: - config.WriteInt("MDIEmbedRightSize", self._rightEmbWindow.GetSize()[0]) - config.WriteInt("MDIEmbedRightVisible", self._rightEmbWindow.IsShown()) - if self._topRightEmbWindow: - if self._topRightEmbWindow._sizeBeforeHidden: - size = self._topRightEmbWindow._sizeBeforeHidden[1] - else: - size = self._topRightEmbWindow.GetSize()[1] - config.WriteInt("MDIEmbedTopRightSize", size) - config.WriteInt("MDIEmbedTopRightVisible", self._topRightEmbWindow.IsShown()) - if self._bottomRightEmbWindow: - if self._bottomRightEmbWindow._sizeBeforeHidden: - size = self._bottomRightEmbWindow._sizeBeforeHidden[1] - else: - size = self._bottomRightEmbWindow.GetSize()[1] - config.WriteInt("MDIEmbedBottomRightSize", size) - config.WriteInt("MDIEmbedBottomRightVisible", self._bottomRightEmbWindow.IsShown()) - if self._topEmbWindow: - config.WriteInt("MDIEmbedTopSize", self._topEmbWindow.GetSize()[1]) - config.WriteInt("MDIEmbedTopVisible", self._topEmbWindow.IsShown()) - if self._bottomEmbWindow: - config.WriteInt("MDIEmbedBottomSize", self._bottomEmbWindow.GetSize()[1]) - config.WriteInt("MDIEmbedBottomVisible", self._bottomEmbWindow.IsShown()) - - - def GetEmbeddedWindow(self, loc): - """ - Returns the instance of the embedded window specified by the embedded window location constant. - """ - if loc == EMBEDDED_WINDOW_TOP: - return self._topEmbWindow - elif loc == EMBEDDED_WINDOW_BOTTOM: - return self._bottomEmbWindow - elif loc == EMBEDDED_WINDOW_LEFT: - return self._leftEmbWindow - elif loc == EMBEDDED_WINDOW_RIGHT: - return self._rightEmbWindow - elif loc == EMBEDDED_WINDOW_TOPLEFT: - return self._topLeftEmbWindow - elif loc == EMBEDDED_WINDOW_BOTTOMLEFT: - return self._bottomLeftEmbWindow - elif loc == EMBEDDED_WINDOW_TOPRIGHT: - return self._topRightEmbWindow - elif loc == EMBEDDED_WINDOW_BOTTOMRIGHT: - return self._bottomRightEmbWindow - return None - - - def _CreateEmbeddedWindow(self, parent, size, orientation, alignment, visible = True, sash = None): - """ - Creates the embedded window with the specified size, orientation, and alignment. If the - window is not visible it will retain the size with which it was last viewed. - """ - window = wx.SashLayoutWindow(parent, wx.NewId(), style = wx.NO_BORDER | wx.SW_3D) - window.SetDefaultSize(size) - window.SetOrientation(orientation) - window.SetAlignment(alignment) - if sash != None: # wx.SASH_TOP is 0 so check for None instead of just doing "if sash:" - window.SetSashVisible(sash, True) - #### - def OnEmbeddedWindowSashDrag(event): - if event.GetDragStatus() == wx.SASH_STATUS_OUT_OF_RANGE: - return - sashWindow = event.GetEventObject() - if sashWindow.GetAlignment() == wx.LAYOUT_TOP or sashWindow.GetAlignment() == wx.LAYOUT_BOTTOM: - size = wx.Size(-1, event.GetDragRect().height) - else: - size = wx.Size(event.GetDragRect().width, -1) - event.GetEventObject().SetDefaultSize(size) - wx.LayoutAlgorithm().LayoutMDIFrame(self) - self.GetClientWindow().Refresh() - if isinstance(sashWindow.GetParent(), wx.SashLayoutWindow): - sashWindow.Show() - parentSashWindow = sashWindow.GetParent() # Force a refresh - parentSashWindow.Layout() - parentSashWindow.Refresh() - parentSashWindow.SetSize((parentSashWindow.GetSize().width + 1, parentSashWindow.GetSize().height + 1)) - #### - wx.EVT_SASH_DRAGGED(window, window.GetId(), OnEmbeddedWindowSashDrag) - window._sizeBeforeHidden = None - if not visible: - window.Show(False) - if isinstance(parent, wx.SashLayoutWindow): # It's a window embedded in another sash window so remember its actual size to show it again - window._sizeBeforeHidden = size - return window - - - def ShowEmbeddedWindow(self, window, show = True): - """ - Shows or hides the embedded window specified by the embedded window location constant. - """ - window.Show(show) - if isinstance(window.GetParent(), wx.SashLayoutWindow): # It is a parent sashwindow with multiple embedded sashwindows - parentSashWindow = window.GetParent() - if show: # Make sure it is visible in case all of the subwindows were hidden - parentSashWindow.Show() - if show and window._sizeBeforeHidden: - if window._sizeBeforeHidden[1] == parentSashWindow.GetClientSize()[1]: - if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).IsShown(): - window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).GetSize()[1])) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).IsShown(): - window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).GetSize()[1])) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).IsShown(): - window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).GetSize()[1])) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT) and self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).IsShown(): - window.SetDefaultSize((window._sizeBeforeHidden[0], window._sizeBeforeHidden[0] - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).GetSize()[1])) - print window.GetSize() - else: - window.SetDefaultSize(window._sizeBeforeHidden) - # If it is not the size of the full parent sashwindow set the other window's size so that if it gets shown it will have a cooresponding size - print "Parent size, size before hidden ", parentSashWindow.GetClientSize()[1], window._sizeBeforeHidden[1] - if window._sizeBeforeHidden[1] < parentSashWindow.GetClientSize()[1]: - otherWindowSize = (-1, parentSashWindow.GetClientSize()[1] - window._sizeBeforeHidden[1]) - print "Other", otherWindowSize - if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT): - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).SetDefaultSize(otherWindowSize) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT): - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).SetDefaultSize(otherWindowSize) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT): - self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).SetDefaultSize(otherWindowSize) - elif window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT): - self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).SetDefaultSize(otherWindowSize) - - if not show: - if window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT).IsShown() \ - or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPRIGHT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMRIGHT).IsShown() \ - or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT).IsShown() \ - or window == self.GetEmbeddedWindow(EMBEDDED_WINDOW_TOPLEFT) and not self.GetEmbeddedWindow(EMBEDDED_WINDOW_BOTTOMLEFT).IsShown(): - parentSashWindow.Hide() # Hide the parent sashwindow if all of the children are hidden - parentSashWindow.Layout() # Force a refresh - parentSashWindow.Refresh() - parentSashWindow.SetSize((parentSashWindow.GetSize().width + 1, parentSashWindow.GetSize().height + 1)) - wx.LayoutAlgorithm().LayoutMDIFrame(self) - self.GetClientWindow().Refresh() - - - def HideEmbeddedWindow(self): - """ - Hides the embedded window specified by the embedded window location constant. - """ - self.ShowEmbeddedWindow(show = False) - - - def GetDocumentManager(self): - """ - Returns the document manager associated with the DocMDIParentFrame. - """ - return self._docManager - - - def InitializePrintData(self): - """ - Initializes the PrintData that is used when printing. - """ - self._printData = wx.PrintData() - self._printData.SetPaperId(wx.PAPER_LETTER) - - - def CreateDefaultStatusBar(self): - """ - Creates the default StatusBar. - """ - self.CreateStatusBar() - self.GetStatusBar().Show(wx.ConfigBase_Get().ReadInt("ViewStatusBar", True)) - self.UpdateStatus() - return self.GetStatusBar() - - - def CreateDefaultToolBar(self): - """ - Creates the default ToolBar. - """ - self._toolBar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT) - self._toolBar.AddSimpleTool(wx.ID_NEW, getNewBitmap(), _("New"), _("Creates a new document")) - self._toolBar.AddSimpleTool(wx.ID_OPEN, getOpenBitmap(), _("Open"), _("Opens an existing document")) - self._toolBar.AddSimpleTool(wx.ID_SAVE, getSaveBitmap(), _("Save"), _("Saves the active document")) - self._toolBar.AddSimpleTool(SAVEALL_ID, getSaveAllBitmap(), _("Save All"), _("Saves all the active documents")) - self._toolBar.AddSeparator() - self._toolBar.AddSimpleTool(wx.ID_PRINT, getPrintBitmap(), _("Print"), _("Displays full pages")) - self._toolBar.AddSimpleTool(wx.ID_PREVIEW, getPrintPreviewBitmap(), _("Print Preview"), _("Prints the active document")) - self._toolBar.AddSeparator() - self._toolBar.AddSimpleTool(wx.ID_CUT, getCutBitmap(), _("Cut"), _("Cuts the selection and puts it on the Clipboard")) - self._toolBar.AddSimpleTool(wx.ID_COPY, getCopyBitmap(), _("Copy"), _("Copies the selection and puts it on the Clipboard")) - self._toolBar.AddSimpleTool(wx.ID_PASTE, getPasteBitmap(), _("Paste"), _("Inserts Clipboard contents")) - self._toolBar.AddSimpleTool(wx.ID_UNDO, getUndoBitmap(), _("Undo"), _("Reverses the last action")) - self._toolBar.AddSimpleTool(wx.ID_REDO, getRedoBitmap(), _("Redo"), _("Reverses the last undo")) - self._toolBar.Realize() - self._toolBar.Show(wx.ConfigBase_Get().ReadInt("ViewToolBar", True)) - - return self._toolBar - - - def CreateDefaultMenuBar(self): - """ - Creates the default MenuBar. Contains File, Edit, View, Tools, and Help menus. - """ - menuBar = wx.MenuBar() - - fileMenu = wx.Menu() - fileMenu.Append(wx.ID_NEW, _("&New...\tCtrl+N"), _("Creates a new document")) - fileMenu.Append(wx.ID_OPEN, _("&Open...\tCtrl+O"), _("Opens an existing document")) - fileMenu.Append(wx.ID_CLOSE, _("&Close"), _("Closes the active document")) - fileMenu.Append(wx.ID_CLOSE_ALL, _("Close A&ll"), _("Closes all open documents")) - fileMenu.AppendSeparator() - fileMenu.Append(wx.ID_SAVE, _("&Save\tCtrl+S"), _("Saves the active document")) - fileMenu.Append(wx.ID_SAVEAS, _("Save &As..."), _("Saves the active document with a new name")) - fileMenu.Append(SAVEALL_ID, _("Save All\tCtrl+Shift+A"), _("Saves the all active documents")) - wx.EVT_MENU(self, SAVEALL_ID, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, SAVEALL_ID, self.ProcessUpdateUIEvent) - fileMenu.AppendSeparator() - fileMenu.Append(wx.ID_PRINT, _("&Print\tCtrl+P"), _("Prints the active document")) - fileMenu.Append(wx.ID_PREVIEW, _("Print Pre&view"), _("Displays full pages")) - fileMenu.Append(wx.ID_PRINT_SETUP, _("Page Set&up"), _("Changes page layout settings")) - fileMenu.AppendSeparator() - if wx.Platform == '__WXMAC__': - fileMenu.Append(wx.ID_EXIT, _("&Quit"), _("Closes this program")) - else: - fileMenu.Append(wx.ID_EXIT, _("E&xit"), _("Closes this program")) - self._docManager.FileHistoryUseMenu(fileMenu) - self._docManager.FileHistoryAddFilesToMenu() - menuBar.Append(fileMenu, _("&File")); - - editMenu = wx.Menu() - editMenu.Append(wx.ID_UNDO, _("&Undo\tCtrl+Z"), _("Reverses the last action")) - editMenu.Append(wx.ID_REDO, _("&Redo\tCtrl+Y"), _("Reverses the last undo")) - editMenu.AppendSeparator() - #item = wxMenuItem(self.editMenu, wxID_CUT, _("Cu&t\tCtrl+X"), _("Cuts the selection and puts it on the Clipboard")) - #item.SetBitmap(getCutBitmap()) - #editMenu.AppendItem(item) - editMenu.Append(wx.ID_CUT, _("Cu&t\tCtrl+X"), _("Cuts the selection and puts it on the Clipboard")) - wx.EVT_MENU(self, wx.ID_CUT, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_CUT, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_COPY, _("&Copy\tCtrl+C"), _("Copies the selection and puts it on the Clipboard")) - wx.EVT_MENU(self, wx.ID_COPY, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_COPY, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_PASTE, _("&Paste\tCtrl+V"), _("Inserts Clipboard contents")) - wx.EVT_MENU(self, wx.ID_PASTE, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_PASTE, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_CLEAR, _("Cle&ar\tDel"), _("Erases the selection")) - wx.EVT_MENU(self, wx.ID_CLEAR, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_CLEAR, self.ProcessUpdateUIEvent) - editMenu.AppendSeparator() - editMenu.Append(wx.ID_SELECTALL, _("Select A&ll\tCtrl+A"), _("Selects all available data")) - wx.EVT_MENU(self, wx.ID_SELECTALL, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_SELECTALL, self.ProcessUpdateUIEvent) - menuBar.Append(editMenu, _("&Edit")) - - viewMenu = wx.Menu() - viewMenu.AppendCheckItem(VIEW_TOOLBAR_ID, _("&Toolbar"), _("Shows or hides the toolbar")) - wx.EVT_MENU(self, VIEW_TOOLBAR_ID, self.OnViewToolBar) - wx.EVT_UPDATE_UI(self, VIEW_TOOLBAR_ID, self.OnUpdateViewToolBar) - viewMenu.AppendCheckItem(VIEW_STATUSBAR_ID, _("&Status Bar"), _("Shows or hides the status bar")) - wx.EVT_MENU(self, VIEW_STATUSBAR_ID, self.OnViewStatusBar) - wx.EVT_UPDATE_UI(self, VIEW_STATUSBAR_ID, self.OnUpdateViewStatusBar) - menuBar.Append(viewMenu, _("&View")) - - helpMenu = wx.Menu() - helpMenu.Append(wx.ID_ABOUT, _("&About" + " " + wx.GetApp().GetAppName()), _("Displays program information, version number, and copyright")) - wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) - menuBar.Append(helpMenu, _("&Help")) - -## windowMenu = wx.Menu() -## menuBar.Append(windowMenu, _("&Window")) -## # self.SetWindowMenu(windowMenu) -## - wx.EVT_UPDATE_UI(self, wx.ID_ABOUT, self.ProcessUpdateUIEvent) # Using ID_ABOUT to update the window menu, the window menu items are not triggering - - return menuBar - -## accelTable = wx.AcceleratorTable([ -## eval(_("wx.ACCEL_CTRL, ord('Z'), wx.ID_UNDO")), -## eval(_("wx.ACCEL_CTRL, ord('Y'), wx.ID_REDO")), -## eval(_("wx.ACCEL_CTRL, ord('X'), wx.ID_CUT")), -## eval(_("wx.ACCEL_CTRL, ord('C'), wx.ID_COPY")), -## eval(_("wx.ACCEL_CTRL, ord('V'), wx.ID_PASTE")), -## (wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_CLEAR), -## eval(_("wx.ACCEL_CTRL, ord('A'), wx.ID_SELECTALL")), -## eval(_("wx.ACCEL_CTRL, ord('N'), wx.ID_NEW")), -## eval(_("wx.ACCEL_CTRL, ord('O'), wx.ID_OPEN")), -## eval(_("wx.ACCEL_CTRL, ord('S'), wx.ID_SAVE")) -## ]) -## self.SetAcceleratorTable(accelTable) - + def ProcessEvent(self, event): """ @@ -1290,13 +2066,7 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): return True if wx.lib.docview.DocMDIParentFrame.ProcessEvent(self, event): return True - - id = event.GetId() - if id == SAVEALL_ID: - self.OnFileSaveAll(event) - return True - - return wx.GetApp().ProcessEvent(event) + return DocMDIParentFrameMixIn.ProcessEvent(self, event) def ProcessUpdateUIEvent(self, event): @@ -1310,37 +2080,10 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): return True if wx.lib.docview.DocMDIParentFrame.ProcessUpdateUIEvent(self, event): # Let the views handle the event before the services return True - id = event.GetId() - if id == wx.ID_CUT: - event.Enable(False) - return True - elif id == wx.ID_COPY: - event.Enable(False) - return True - elif id == wx.ID_PASTE: - event.Enable(False) - return True - elif id == wx.ID_CLEAR: - event.Enable(False) - return True - elif id == wx.ID_SELECTALL: - event.Enable(False) - return True - elif id == wx.ID_ABOUT: # Using ID_ABOUT to update the window menu, the window menu items are not triggering + if event.GetId() == wx.ID_ABOUT: # Using ID_ABOUT to update the window menu, the window menu items are not triggering self.UpdateWindowMenu() return True - elif id == SAVEALL_ID: - filesModified = False - docs = wx.GetApp().GetDocumentManager().GetDocuments() - for doc in docs: - if doc.IsModified(): - filesModified = True - break - - event.Enable(filesModified) - return True - else: - return wx.GetApp().ProcessUpdateUIEvent(event) + return DocMDIParentFrameMixIn.ProcessUpdateUIEvent(self, event) def UpdateWindowMenu(self): @@ -1354,39 +2097,30 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): has2OrMoreWindows = windowCount >= 2 windowMenu = self.GetWindowMenu() - windowMenu.Enable(wx.IDM_WINDOWTILE, hasWindow) - windowMenu.Enable(wx.IDM_WINDOWTILEHOR, hasWindow) - windowMenu.Enable(wx.IDM_WINDOWCASCADE, hasWindow) - windowMenu.Enable(wx.IDM_WINDOWICONS, hasWindow) - windowMenu.Enable(wx.IDM_WINDOWTILEVERT, hasWindow) - wx.IDM_WINDOWPREV = 4006 # wxBug: Not defined for some reason - windowMenu.Enable(wx.IDM_WINDOWPREV, has2OrMoreWindows) - windowMenu.Enable(wx.IDM_WINDOWNEXT, has2OrMoreWindows) + if windowMenu: + windowMenu.Enable(wx.IDM_WINDOWTILE, hasWindow) + windowMenu.Enable(wx.IDM_WINDOWTILEHOR, hasWindow) + windowMenu.Enable(wx.IDM_WINDOWCASCADE, hasWindow) + windowMenu.Enable(wx.IDM_WINDOWICONS, hasWindow) + windowMenu.Enable(wx.IDM_WINDOWTILEVERT, hasWindow) + wx.IDM_WINDOWPREV = 4006 # wxBug: Not defined for some reason + windowMenu.Enable(wx.IDM_WINDOWPREV, has2OrMoreWindows) + windowMenu.Enable(wx.IDM_WINDOWNEXT, has2OrMoreWindows) + - def OnFileSaveAll(self, event): + def OnSize(self, event): """ - Saves all of the currently open documents. + Called when the DocMDIParentFrame is resized and lays out the MDI client window. """ - docs = wx.GetApp().GetDocumentManager().GetDocuments() - for doc in docs: - doc.Save() - + # Needed in case there are splitpanels around the mdi frame + self._LayoutFrame() + def OnCloseWindow(self, event): """ Called when the DocMDIParentFrame is closed. Remembers the frame size. """ - config = wx.ConfigBase_Get() - if not self.IsMaximized(): - config.WriteInt("MDIFrameXLoc", self.GetPositionTuple()[0]) - config.WriteInt("MDIFrameYLoc", self.GetPositionTuple()[1]) - config.WriteInt("MDIFrameXSize", self.GetSizeTuple()[0]) - config.WriteInt("MDIFrameYSize", self.GetSizeTuple()[1]) - config.WriteInt("MDIFrameMaximized", self.IsMaximized()) - config.WriteInt("ViewToolBar", self._toolBar.IsShown()) - config.WriteInt("ViewStatusBar", self.GetStatusBar().IsShown()) - self.SaveEmbeddedWindowSizes() # save and close services last. @@ -1400,67 +2134,7 @@ class DocMDIParentFrame(wx.lib.docview.DocMDIParentFrame): wx.lib.docview.DocMDIParentFrame.OnCloseWindow(self, event) - def OnAbout(self, event): - """ - Invokes the about dialog. - """ - aboutService = wx.GetApp().GetService(AboutService) - if aboutService: - aboutService.ShowAbout() - - - def OnViewToolBar(self, event): - """ - Toggles whether the ToolBar is visible. - """ - self._toolBar.Show(not self._toolBar.IsShown()) - wx.LayoutAlgorithm().LayoutMDIFrame(self) - self.GetClientWindow().Refresh() - - - def OnUpdateViewToolBar(self, event): - """ - Updates the View ToolBar menu item. - """ - event.Check(self.GetToolBar().IsShown()) - - - def OnViewStatusBar(self, event): - """ - Toggles whether the StatusBar is visible. - """ - self.GetStatusBar().Show(not self.GetStatusBar().IsShown()) - self.Layout() - wx.LayoutAlgorithm().LayoutMDIFrame(self) - self.GetClientWindow().Refresh() - - - def OnUpdateViewStatusBar(self, event): - """ - Updates the View StatusBar menu item. - """ - event.Check(self.GetStatusBar().IsShown()) - - - def UpdateStatus(self, message = _("Ready")): - """ - Updates the StatusBar. - """ - # wxBug: Menubar and toolbar help strings don't pop the status text back - if self.GetStatusBar().GetStatusText() != message: - self.GetStatusBar().PushStatusText(message) - - - def OnSize(self, event): - """ - Called when the DocMDIParentFrame is resized and lays out the MDI client window. - """ - # Needed in case there are splitpanels around the mdi frame - wx.LayoutAlgorithm().LayoutMDIFrame(self) - self.GetClientWindow().Refresh() - - -class DocSDIFrame(wx.lib.docview.DocChildFrame): +class DocSDIFrame(wx.lib.docview.DocChildFrame, DocFrameMixIn): """ The DocSDIFrame host DocManager Document windows. It offers features such as a default menubar, toolbar, and status bar. @@ -1485,7 +2159,7 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame): self.InitializePrintData() - menuBar = self.CreateDefaultMenuBar() + menuBar = self.CreateDefaultMenuBar(sdi=True) toolBar = self.CreateDefaultToolBar() self.SetToolBar(toolBar) statusBar = self.CreateDefaultStatusBar() @@ -1496,12 +2170,12 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame): self.SetMenuBar(menuBar) # wxBug: Need to do this in SDI to mimic MDI... because have to set the menubar at the very end or the automatic MDI "window" menu doesn't get put in the right place when the services add new menus to the menubar - def GetDocumentManager(self): + def _LayoutFrame(self): """ - Returns the document manager associated with the DocSDIFrame. + Lays out the Frame. """ - return self._docManager - + self.Layout() + def OnExit(self, event): """ @@ -1533,135 +2207,6 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame): self) - def InitializePrintData(self): - """ - Initializes the PrintData that is used when printing. - """ - self._printData = wx.PrintData() - self._printData.SetPaperId(wx.PAPER_LETTER) - - - def CreateDefaultStatusBar(self): - """ - Creates the default StatusBar. - """ - wx.lib.docview.DocChildFrame.CreateStatusBar(self) - self.GetStatusBar().Show(wx.ConfigBase_Get().ReadInt("ViewStatusBar", True)) - self.UpdateStatus() - return self.GetStatusBar() - - - def CreateDefaultToolBar(self): - """ - Creates the default ToolBar. - """ - self._toolBar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT) - self._toolBar.AddSimpleTool(wx.ID_NEW, getNewBitmap(), _("New"), _("Creates a new document")) - self._toolBar.AddSimpleTool(wx.ID_OPEN, getOpenBitmap(), _("Open"), _("Opens an existing document")) - self._toolBar.AddSimpleTool(wx.ID_SAVE, getSaveBitmap(), _("Save"), _("Saves the active document")) - self._toolBar.AddSimpleTool(SAVEALL_ID, getSaveAllBitmap(), _("Save All"), _("Saves all the active documents")) - self._toolBar.AddSeparator() - self._toolBar.AddSimpleTool(wx.ID_PRINT, getPrintBitmap(), _("Print"), _("Displays full pages")) - self._toolBar.AddSimpleTool(wx.ID_PREVIEW, getPrintPreviewBitmap(), _("Print Preview"), _("Prints the active document")) - self._toolBar.AddSeparator() - self._toolBar.AddSimpleTool(wx.ID_CUT, getCutBitmap(), _("Cut"), _("Cuts the selection and puts it on the Clipboard")) - self._toolBar.AddSimpleTool(wx.ID_COPY, getCopyBitmap(), _("Copy"), _("Copies the selection and puts it on the Clipboard")) - self._toolBar.AddSimpleTool(wx.ID_PASTE, getPasteBitmap(), _("Paste"), _("Inserts Clipboard contents")) - self._toolBar.AddSimpleTool(wx.ID_UNDO, getUndoBitmap(), _("Undo"), _("Reverses the last action")) - self._toolBar.AddSimpleTool(wx.ID_REDO, getRedoBitmap(), _("Redo"), _("Reverses the last undo")) - self._toolBar.Realize() - self._toolBar.Show(wx.ConfigBase_Get().ReadInt("ViewToolBar", True)) - return self._toolBar - - - def CreateDefaultMenuBar(self): - """ - Creates the default MenuBar. Contains File, Edit, View, Tools, and Help menus. - """ - menuBar = wx.MenuBar() - - fileMenu = wx.Menu() - self._fileMenu = fileMenu - fileMenu.Append(wx.ID_NEW, _("&New...\tCtrl+N"), _("Creates a new document")) - fileMenu.Append(wx.ID_OPEN, _("&Open...\tCtrl+O"), _("Opens an existing document")) - fileMenu.Append(wx.ID_CLOSE, _("&Close"), _("Closes the active document")) - # fileMenu.Append(wx.ID_CLOSE_ALL, _("Close A&ll"), _("Closes all open documents")) - fileMenu.AppendSeparator() - fileMenu.Append(wx.ID_SAVE, _("&Save\tCtrl+S"), _("Saves the active document")) - fileMenu.Append(wx.ID_SAVEAS, _("Save &As..."), _("Saves the active document with a new name")) - fileMenu.Append(SAVEALL_ID, _("Save All\tCtrl+Shift+A"), _("Saves the all active documents")) - wx.EVT_MENU(self, SAVEALL_ID, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, SAVEALL_ID, self.ProcessUpdateUIEvent) - fileMenu.AppendSeparator() - fileMenu.Append(wx.ID_PRINT, _("&Print\tCtrl+P"), _("Prints the active document")) - fileMenu.Append(wx.ID_PREVIEW, _("Print Pre&view"), _("Displays full pages")) - fileMenu.Append(wx.ID_PRINT_SETUP, _("Page Set&up"), _("Changes page layout settings")) - fileMenu.AppendSeparator() - if wx.Platform == '__WXMAC__': - fileMenu.Append(wx.ID_EXIT, _("&Quit"), _("Closes this program")) - else: - fileMenu.Append(wx.ID_EXIT, _("E&xit"), _("Closes this program")) - if self._docManager: - self._docManager.FileHistoryUseMenu(fileMenu) - self._docManager.FileHistoryAddFilesToMenu(fileMenu) - menuBar.Append(fileMenu, _("&File")); - - editMenu = wx.Menu() - editMenu.Append(wx.ID_UNDO, _("&Undo\tCtrl+Z"), _("Reverses the last action")) - editMenu.Append(wx.ID_REDO, _("&Redo\tCtrl+Y"), _("Reverses the last undo")) - editMenu.AppendSeparator() - editMenu.Append(wx.ID_CUT, _("Cu&t\tCtrl+X"), _("Cuts the selection and puts it on the Clipboard")) - wx.EVT_MENU(self, wx.ID_CUT, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_CUT, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_COPY, _("&Copy\tCtrl+C"), _("Copies the selection and puts it on the Clipboard")) - wx.EVT_MENU(self, wx.ID_COPY, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_COPY, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_PASTE, _("&Paste\tCtrl+V"), _("Inserts Clipboard contents")) - wx.EVT_MENU(self, wx.ID_PASTE, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_PASTE, self.ProcessUpdateUIEvent) - editMenu.Append(wx.ID_CLEAR, _("Cle&ar\tDel"), _("Erases the selection")) - wx.EVT_MENU(self, wx.ID_CLEAR, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_CLEAR, self.ProcessUpdateUIEvent) - editMenu.AppendSeparator() - editMenu.Append(wx.ID_SELECTALL, _("Select A&ll\tCtrl+A"), _("Selects all available data")) - wx.EVT_MENU(self, wx.ID_SELECTALL, self.ProcessEvent) - wx.EVT_UPDATE_UI(self, wx.ID_SELECTALL, self.ProcessUpdateUIEvent) - menuBar.Append(editMenu, _("&Edit")) - if self.GetDocument() and self.GetDocument().GetCommandProcessor(): - self.GetDocument().GetCommandProcessor().SetEditMenu(editMenu) - - viewMenu = wx.Menu() - viewMenu.AppendCheckItem(VIEW_TOOLBAR_ID, _("&Toolbar"), _("Shows or hides the toolbar")) - wx.EVT_MENU(self, VIEW_TOOLBAR_ID, self.OnViewToolBar) - wx.EVT_UPDATE_UI(self, VIEW_TOOLBAR_ID, self.OnUpdateViewToolBar) - viewMenu.AppendCheckItem(VIEW_STATUSBAR_ID, _("&Status Bar"), _("Shows or hides the status bar")) - wx.EVT_MENU(self, VIEW_STATUSBAR_ID, self.OnViewStatusBar) - wx.EVT_UPDATE_UI(self, VIEW_STATUSBAR_ID, self.OnUpdateViewStatusBar) - menuBar.Append(viewMenu, _("&View")) - - helpMenu = wx.Menu() - helpMenu.Append(wx.ID_ABOUT, _("&About" + " " + wx.GetApp().GetAppName()), _("Displays program information, version number, and copyright")) - wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) - menuBar.Append(helpMenu, _("&Help")) - - wx.EVT_COMMAND_FIND_CLOSE(self, -1, self.ProcessEvent) - - return menuBar -## accelTable = wx.AcceleratorTable([ -## eval(_("wx.ACCEL_CTRL, ord('N'), wx.ID_NEW")), -## eval(_("wx.ACCEL_CTRL, ord('O'), wx.ID_OPEN")), -## eval(_("wx.ACCEL_CTRL, ord('S'), wx.ID_SAVE")), -## eval(_("wx.ACCEL_CTRL, ord('Z'), wx.ID_UNDO")), -## eval(_("wx.ACCEL_CTRL, ord('Y'), wx.ID_REDO")), -## eval(_("wx.ACCEL_CTRL, ord('X'), wx.ID_CUT")), -## eval(_("wx.ACCEL_CTRL, ord('C'), wx.ID_COPY")), -## eval(_("wx.ACCEL_CTRL, ord('Z'), wx.ID_PASTE")), -## (wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_CLEAR), -## eval(_("wx.ACCEL_CTRL, ord('A'), wx.ID_SELECTALL")) -## ]) -## self.SetAcceleratorTable(accelTable) - - def ProcessEvent(self, event): """ Processes an event, searching event tables and calling zero or more @@ -1732,63 +2277,6 @@ class DocSDIFrame(wx.lib.docview.DocChildFrame): return wx.GetApp().ProcessUpdateUIEvent(event) - def OnFileSaveAll(self, event): - """ - Saves all of the currently open documents. - """ - docs = wx.GetApp().GetDocumentManager().GetDocuments() - for doc in docs: - doc.Save() - - - def OnAbout(self, event): - """ - Invokes the about dialog. - """ - aboutService = wx.GetApp().GetService(AboutService) - if aboutService: - aboutService.ShowAbout() - - - def OnViewToolBar(self, event): - """ - Toggles whether the ToolBar is visible. - """ - self._toolBar.Show(not self._toolBar.IsShown()) - self.Layout() - - - def OnUpdateViewToolBar(self, event): - """ - Updates the View ToolBar menu item. - """ - event.Check(self.GetToolBar().IsShown()) - - - def OnViewStatusBar(self, event): - """ - Toggles whether the StatusBar is visible. - """ - self.GetStatusBar().Show(not self.GetStatusBar().IsShown()) - self.Layout() - - - def OnUpdateViewStatusBar(self, event): - """ - Updates the View StatusBar menu item. - """ - event.Check(self.GetStatusBar().IsShown()) - - - def UpdateStatus(self, message = _("Ready")): - """ - Updates the StatusBar. - """ - # wxBug: Menubar and toolbar help strings don't pop the status text back - if self.GetStatusBar().GetStatusText() != message: - self.GetStatusBar().PushStatusText(message) - - def OnCloseWindow(self, event): """ Called when the window is saved. Enables services to help close the frame. @@ -1805,21 +2293,26 @@ class AboutService(DocService): About Dialog Service that installs under the Help menu to show the properties of the current application. """ - def __init__(self, aboutDialog = None): + def __init__(self, aboutDialog=None, image=None): """ Initializes the AboutService. """ if aboutDialog: self._dlg = aboutDialog + self._image = None else: self._dlg = AboutDialog # use default AboutDialog + self._image = image def ShowAbout(self): """ Show the AboutDialog """ - dlg = self._dlg(wx.GetApp().GetTopWindow()) + if self._image: + dlg = self._dlg(wx.GetApp().GetTopWindow(), self._image) + else: + dlg = self._dlg(wx.GetApp().GetTopWindow()) dlg.CenterOnScreen() dlg.ShowModal() dlg.Destroy() @@ -1837,18 +2330,17 @@ class AboutDialog(wx.Dialog): Opens an AboutDialog. Shared by DocMDIParentFrame and DocSDIFrame. """ - def __init__(self, parent): + def __init__(self, parent, image=None): """ Initializes the about dialog. """ wx.Dialog.__init__(self, parent, -1, _("About ") + wx.GetApp().GetAppName(), style = wx.DEFAULT_DIALOG_STYLE) - self.SetBackgroundColour(wx.WHITE) sizer = wx.BoxSizer(wx.VERTICAL) - splash_bmp = wx.Image("activegrid/tool/images/splash.jpg").ConvertToBitmap() - image = wx.StaticBitmap(self, -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(self, -1, wx.GetApp().GetAppName()), 0, wx.ALIGN_LEFT|wx.ALL, 5) + if image: + imageItem = wx.StaticBitmap(self, -1, image.ConvertToBitmap(), (0,0), (image.GetWidth(), image.GetHeight())) + sizer.Add(imageItem, 0, wx.ALIGN_CENTER|wx.ALL, 0) + sizer.Add(wx.StaticText(self, -1, wx.GetApp().GetAppName()), 0, wx.ALIGN_CENTRE|wx.ALL, 5) btn = wx.Button(self, wx.ID_OK) sizer.Add(btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) @@ -2040,7 +2532,6 @@ class FilePropertiesDialog(wx.Dialog): spacerGrid.Add(gridSizer, 0, wx.ALL, SPACE); tab.SetSizer(spacerGrid) notebook.AddPage(tab, _("General")) - notebook.SetPageSize((310,200)) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(notebook, 0, wx.ALL | wx.EXPAND, SPACE) @@ -2112,6 +2603,22 @@ class ChildDocument(wx.lib.docview.Document): return True + def Save(self): + """ + Called when the ChildDocument is saved and does the minimum such that the + ChildDocument looks like a real Document to the framework. + """ + return self.OnSaveDocument(self._documentFile) + + + def SaveAs(self): + """ + Called when the ChildDocument is saved and does the minimum such that the + ChildDocument looks like a real Document to the framework. + """ + return self.OnSaveDocument(self._documentFile) + + class ChildDocTemplate(wx.lib.docview.DocTemplate): """ A ChildDocTemplate is a DocTemplate subclass that enables the creation of ChildDocuments @@ -2181,8 +2688,11 @@ class WindowMenuService(DocService): if not self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: return # Only need windows menu for SDI mode, MDI frame automatically creates one + if not _WINDOWS: # Arrange All and window navigation doesn't work on Linux + return + windowMenu = wx.Menu() - windowMenu.Append(self.ARRANGE_WINDOWS_ID, _("&Arrange All"), _("Arrange the open windows")) + item = windowMenu.Append(self.ARRANGE_WINDOWS_ID, _("&Arrange All"), _("Arrange the open windows")) windowMenu.AppendSeparator() wx.EVT_MENU(frame, self.ARRANGE_WINDOWS_ID, frame.ProcessEvent) @@ -2375,6 +2885,7 @@ class WindowMenuService(DocService): # File generated by encode_bitmaps.py #---------------------------------------------------------------------------- from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon import cStringIO #---------------------------------------------------------------------- @@ -2594,3 +3105,31 @@ def getRedoBitmap(): def getRedoImage(): stream = cStringIO.StringIO(getRedoData()) return ImageFromStream(stream) + +#---------------------------------------------------------------------------- + +def getBlankData(): + return \ +"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\ +\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00\ +\x85IDATX\x85\xed\x97\xc9\n\xc0 \x0cD3\xda\xff\xffcMo\x96Z\xc4\xa5\x91\x14:9\ +\x8a\xe8\xcb\xd3\xb8\x00!\x8ag\x04\xd7\xd9E\xe4\xa8\x1b4'}3 B\xc4L\x7fs\x03\ +\xb3\t<\x0c\x94\x81tN\x04p%\xae9\xe9\xa8\x89m{`\xd4\x84\xfd\x12\xa8\x16{#\ +\x10\xdb\xab\xa0\x07a\x0e\x00\xe0\xb6\x1fz\x10\xdf;\x07V\xa3U5\xb5\x8d:\xdc\ +\r\x10\x80\x00\x04 \x00\x01\x08@\x80\xe6{\xa0w\x8f[\x85\xbb\x01\xfc\xfeoH\ +\x80\x13>\xf9(3zH\x1e\xfb\x00\x00\x00\x00IEND\xaeB`\x82" + + +def getBlankBitmap(): + return BitmapFromImage(getBlankImage()) + +def getBlankImage(): + stream = cStringIO.StringIO(getBlankData()) + return ImageFromStream(stream) + +def getBlankIcon(): + icon = EmptyIcon() + icon.CopyFromBitmap(getBlankBitmap()) + return icon + + diff --git a/wxPython/wx/lib/rcsizer.py b/wxPython/wx/lib/rcsizer.py index 30a06ca0ef..b262aee4bd 100644 --- a/wxPython/wx/lib/rcsizer.py +++ b/wxPython/wx/lib/rcsizer.py @@ -94,8 +94,8 @@ class RowColSizer(wx.PySizer): #if rowspan > 1 or colspan > 1: # flag = flag | wx.EXPAND - wx.PySizer.Add(self, item, option, flag, border, - userData=(row, col, row+rowspan, col+colspan)) + return wx.PySizer.Add(self, item, option, flag, border, + userData=(row, col, row+rowspan, col+colspan)) #AddWindow = Add #AddSizer = Add @@ -113,8 +113,8 @@ class RowColSizer(wx.PySizer): assert row != -1, "Row must be specified" assert col != -1, "Column must be specified" - wx.PySizer.Add(self, (width, height), option, flag, border, - userData=(row, col, row+rowspan, col+colspan)) + return wx.PySizer.Add(self, (width, height), option, flag, border, + userData=(row, col, row+rowspan, col+colspan)) #-------------------------------------------------- def _add( self, size, dim ): diff --git a/wxPython/wxPython/_core.py b/wxPython/wxPython/_core.py index 8f9555e558..a7d6af7009 100644 --- a/wxPython/wxPython/_core.py +++ b/wxPython/wxPython/_core.py @@ -1034,6 +1034,7 @@ wxFindWindowById = wx._core.FindWindowById wxFindWindowByName = wx._core.FindWindowByName wxFindWindowByLabel = wx._core.FindWindowByLabel wxWindow_FromHWND = wx._core.Window_FromHWND +GetTopLevelWindows = wx._core.GetTopLevelWindows wxValidator = wx._core.Validator wxValidatorPtr = wx._core.ValidatorPtr wxValidator_IsSilent = wx._core.Validator_IsSilenttwo's data