diff --git a/Chapter-01/bare.py b/Chapter-01/bare.py new file mode 100644 index 0000000..b9417f9 --- /dev/null +++ b/Chapter-01/bare.py @@ -0,0 +1,11 @@ +import wx + +class App(wx.App): + + def OnInit(self): + frame = wx.Frame(parent=None, title='Bare') + frame.Show() + return True + +app = App() +app.MainLoop() diff --git a/Chapter-01/hello.py b/Chapter-01/hello.py new file mode 100644 index 0000000..5338b3f --- /dev/null +++ b/Chapter-01/hello.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +"""Hello, wxPython! program.""" + +import wx + +class Frame(wx.Frame): + """Frame class that displays an image.""" + + def __init__(self, image, parent=None, id=-1, + pos=wx.DefaultPosition, title='Hello, wxPython!'): + """Create a Frame instance and display image.""" + temp = image.ConvertToBitmap() + size = temp.GetWidth(), temp.GetHeight() + wx.Frame.__init__(self, parent, id, title, pos, size) + self.bmp = wx.StaticBitmap(parent=self, bitmap=temp) + self.SetClientSize(size) + +class App(wx.App): + """Application class.""" + + def OnInit(self): + image = wx.Image('wxPython.jpg', wx.BITMAP_TYPE_JPEG) + self.frame = Frame(image) + self.frame.Show() + self.SetTopWindow(self.frame) + return True + +def main(): + app = App() + app.MainLoop() + +if __name__ == '__main__': + main() + diff --git a/Chapter-01/python_compare.py b/Chapter-01/python_compare.py new file mode 100644 index 0000000..ed21258 --- /dev/null +++ b/Chapter-01/python_compare.py @@ -0,0 +1,36 @@ +import wx + +class MyApp(wx.App): + + def OnInit(self): + frame = MyFrame("Hello World", (50, 60), (450, 340)) + frame.Show() + self.SetTopWindow(frame) + return True + +class MyFrame(wx.Frame): + + def __init__(self, title, pos, size): + wx.Frame.__init__(self, None, -1, title, pos, size) + menuFile = wx.Menu() + menuFile.Append(1, "&About...") + menuFile.AppendSeparator() + menuFile.Append(2, "E&xit") + menuBar = wx.MenuBar() + menuBar.Append(menuFile, "&File") + self.SetMenuBar(menuBar) + self.CreateStatusBar() + self.SetStatusText("Welcome to wxPython!") + self.Bind(wx.EVT_MENU, self.OnAbout, id=1) + self.Bind(wx.EVT_MENU, self.OnQuit, id=2) + + def OnQuit(self, event): + self.Close() + + def OnAbout(self, event): + wx.MessageBox("This is a wxPython Hello world sample", + "About Hello World", wx.OK | wx.ICON_INFORMATION, self) + +if __name__ == '__main__': + app = MyApp(False) + app.MainLoop() diff --git a/Chapter-01/sample.py b/Chapter-01/sample.py new file mode 100644 index 0000000..9503294 --- /dev/null +++ b/Chapter-01/sample.py @@ -0,0 +1,21 @@ +#!/bin/env python +import wx + +class MyFrame(wx.Frame): + + def __init__(self): + wx.Frame.__init__(self, None, -1, "My Frame", size=(300, 300)) + panel = wx.Panel(self, -1) + panel.Bind(wx.EVT_MOTION, self.OnMove) + wx.StaticText(panel, -1, "Pos:", pos=(10, 12)) + self.posCtrl = wx.TextCtrl(panel, -1, "", pos=(40, 10)) + + def OnMove(self, event): + pos = event.GetPosition() + self.posCtrl.SetValue("%s, %s" % (pos.x, pos.y)) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show(True) + app.MainLoop() diff --git a/Chapter-01/spare.py b/Chapter-01/spare.py new file mode 100644 index 0000000..756e5c5 --- /dev/null +++ b/Chapter-01/spare.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +"""Spare.py is a starting point for simple wxPython programs.""" + +import wx + +class Frame(wx.Frame): + pass + +class App(wx.App): + + def OnInit(self): + self.frame = Frame(parent=None, title='Spare') + self.frame.Show() + self.SetTopWindow(self.frame) + return True + +if __name__ == '__main__': + app = App() + app.MainLoop() diff --git a/Chapter-01/wxPython.jpg b/Chapter-01/wxPython.jpg new file mode 100644 index 0000000..9770d4e Binary files /dev/null and b/Chapter-01/wxPython.jpg differ diff --git a/Chapter-02/dialog_scratch.py b/Chapter-02/dialog_scratch.py new file mode 100644 index 0000000..d96c38f --- /dev/null +++ b/Chapter-02/dialog_scratch.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import wx +import images + +class App(wx.App): + + def __init__(self, redirect=True, filename=None): + wx.App.__init__(self, redirect, filename) + + def OnInit(self): + dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!', + 'MessageDialog', wx.YES_NO | wx.ICON_QUESTION) + result = dlg.ShowModal() + dlg.Destroy() + + dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?", + 'A Question', 'Cary Grant') + if dlg.ShowModal() == wx.ID_OK: + response = dlg.GetValue() + dlg.Destroy() + + dlg = wx.SingleChoiceDialog(None, + 'What version of Python are you using?', 'Single Choice', + ['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1']) + if dlg.ShowModal() == wx.ID_OK: + response = dlg.GetStringSelection() + dlg.Destroy() + + return True + + +if __name__ == '__main__': + app = App(False, "output") + fred = app.MainLoop() + diff --git a/Chapter-02/images.py b/Chapter-02/images.py new file mode 100644 index 0000000..63e5c8f --- /dev/null +++ b/Chapter-02/images.py @@ -0,0 +1,24 @@ +#---------------------------------------------------------------------- +# This file was generated by encode_bitmaps.py +# +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + +def getNewData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x0f\x08\x06\ +\x00\x00\x00\xedsO/\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\ +\x00YIDATx\x9c\xed\xd31\n@!\x0c\x03\xd0\xa4\xfe\xfb\xdfX\xe3\xf0\x97R\xa5(.\ +\x0ef\x13\xe45\xa2\x92Vp\x92\xcf/\xd4\xaa\xb2\xcd\xb4\xc2\x14\x00\x00in\x90\ +\x84ZUDl\xa9\xa7\xc3c\xcb-\x80\xfc\x87{d8B6=B\xdb\rfy\xc0\r\xc0\xf0\x0e\xfc\ +\x1d\xaf\x84\xa7\xbf\xb1\x03\xe1,\x19&\x93\x9a\xd2\x97\x00\x00\x00\x00IEND\ +\xaeB`\x82' + +def getNewBitmap(): + return BitmapFromImage(getNewImage()) + +def getNewImage(): + stream = cStringIO.StringIO(getNewData()) + return ImageFromStream(stream) + diff --git a/Chapter-02/images.pyc b/Chapter-02/images.pyc new file mode 100644 index 0000000..ddad36c Binary files /dev/null and b/Chapter-02/images.pyc differ diff --git a/Chapter-02/insert.py b/Chapter-02/insert.py new file mode 100644 index 0000000..313945a --- /dev/null +++ b/Chapter-02/insert.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import wx + +class InsertFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Frame With Button', + size=(300, 100)) + panel = wx.Panel(self) + button = wx.Button(panel, label="Close", pos=(125, 10), + size=(50, 50)) + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = InsertFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-02/startup.py b/Chapter-02/startup.py new file mode 100644 index 0000000..2d6bcc1 --- /dev/null +++ b/Chapter-02/startup.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +import wx +import sys + +class Frame(wx.Frame): + + def __init__(self, parent, id, title): + print "Frame __init__" + wx.Frame.__init__(self, parent, id, title) + +class App(wx.App): + + def __init__(self, redirect=True, filename=None): + print "App __init__" + wx.App.__init__(self, redirect, filename) + + def OnInit(self): + print "OnInit" + self.frame = Frame(parent=None, id=-1, title='Startup Window') + self.frame.Show() + self.SetTopWindow(self.frame) + print >> sys.stderr, "A pretend error message" + print "app name: <", self.GetVendorName(), ">" + return True + + def OnExit(self): + print "OnExit" + +if __name__ == '__main__': + app = App(redirect=True) + print "before MainLoop" + fred = app.MainLoop() + print "after MainLoop", fred + diff --git a/Chapter-02/toolbar.py b/Chapter-02/toolbar.py new file mode 100644 index 0000000..9a9619d --- /dev/null +++ b/Chapter-02/toolbar.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +import wx +import images + +class ToolbarFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Toolbars', + size=(300, 200)) + panel = wx.Panel(self) + panel.SetBackgroundColour('White') + statusBar = self.CreateStatusBar() + toolbar = self.CreateToolBar() + toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(), + "New", "Long help for 'New'") + toolbar.Realize() + menuBar = wx.MenuBar() + menu1 = wx.Menu() + menuBar.Append(menu1, "&File") + menu2 = wx.Menu() + menu2.Append(wx.NewId(), "&Copy", "Copy in status bar") + menu2.Append(wx.NewId(), "C&ut", "") + menu2.Append(wx.NewId(), "Paste", "") + menu2.AppendSeparator() + menu2.Append(wx.NewId(), "&Options...", "Display Options") + menuBar.Append(menu2, "&Edit") + self.SetMenuBar(menuBar) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = ToolbarFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-02/toolbar.pyc b/Chapter-02/toolbar.pyc new file mode 100644 index 0000000..43321e7 Binary files /dev/null and b/Chapter-02/toolbar.pyc differ diff --git a/Chapter-03/customEvent.py b/Chapter-03/customEvent.py new file mode 100644 index 0000000..1592332 --- /dev/null +++ b/Chapter-03/customEvent.py @@ -0,0 +1,64 @@ +import wx + +class TwoButtonEvent(wx.PyCommandEvent): + def __init__(self, evtType, id): + wx.PyCommandEvent.__init__(self, evtType, id) + self.clickCount = 0 + + def GetClickCount(self): + return self.clickCount + + def SetClickCount(self, count): + self.clickCount = count + +myEVT_TWO_BUTTON = wx.NewEventType() +EVT_TWO_BUTTON = wx.PyEventBinder(myEVT_TWO_BUTTON, 1) + +class TwoButtonPanel(wx.Panel): + def __init__(self, parent, id=-1, leftText="Left", + rightText="Right"): + wx.Panel.__init__(self, parent, id) + self.leftButton = wx.Button(self, label=leftText) + self.rightButton = wx.Button(self, label=rightText, + pos=(100,0)) + self.leftClick = False + self.rightClick = False + self.clickCount = 0 + self.leftButton.Bind(wx.EVT_LEFT_DOWN, self.OnLeftClick) + self.rightButton.Bind(wx.EVT_LEFT_DOWN, self.OnRightClick) + + def OnLeftClick(self, event): + self.leftClick = True + self.OnClick() + event.Skip() + + def OnRightClick(self, event): + self.rightClick = True + self.OnClick() + event.Skip() + + def OnClick(self): + self.clickCount += 1 + if self.leftClick and self.rightClick: + self.leftClick = False + self.rightClick = False + evt = TwoButtonEvent(myEVT_TWO_BUTTON, self.GetId()) + evt.SetClickCount(self.clickCount) + self.GetEventHandler().ProcessEvent(evt) + + +class CustomEventFrame(wx.Frame): + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Click Count: 0', + size=(300, 100)) + panel = TwoButtonPanel(self) + self.Bind(EVT_TWO_BUTTON, self.OnTwoClick, panel) + + def OnTwoClick(self, event): + self.SetTitle("Click Count: %s" % event.GetClickCount()) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = CustomEventFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() diff --git a/Chapter-03/double_event_one.py b/Chapter-03/double_event_one.py new file mode 100644 index 0000000..4cf5955 --- /dev/null +++ b/Chapter-03/double_event_one.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import wx + +class DoubleEventFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Frame With Button', + size=(300, 100)) + self.panel = wx.Panel(self, -1) + self.button = wx.Button(self.panel, -1, "Click Me", pos=(100, 15)) + self.Bind(wx.EVT_BUTTON, self.OnButtonClick, self.button) + self.button.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) + + def OnButtonClick(self, event): + self.panel.SetBackgroundColour('Green') + self.panel.Refresh() + + def OnMouseDown(self, event): + self.button.SetLabel("Again!") + event.Skip() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = DoubleEventFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-03/menu_event.py b/Chapter-03/menu_event.py new file mode 100644 index 0000000..da133f6 --- /dev/null +++ b/Chapter-03/menu_event.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import wx + +class MenuEventFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Menus', size=(300, 200)) + menuBar = wx.MenuBar() + menu1 = wx.Menu() + menuItem = menu1.Append(-1, "&Exit...") + menuBar.Append(menu1, "&File") + self.SetMenuBar(menuBar) + self.Bind(wx.EVT_MENU, self.OnCloseMe, menuItem) + + def OnCloseMe(self, event): + self.Close(True) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = MenuEventFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-03/mouse_event.py b/Chapter-03/mouse_event.py new file mode 100644 index 0000000..9d31983 --- /dev/null +++ b/Chapter-03/mouse_event.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +import wx + +class MouseEventFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Frame With Button', + size=(300, 100)) + self.panel = wx.Panel(self) + self.button = wx.Button(self.panel, label="Not Over", pos=(100, 15)) + self.Bind(wx.EVT_BUTTON, self.OnButtonClick, self.button) + self.button.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterWindow) + self.button.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + + def OnButtonClick(self, event): + self.panel.SetBackgroundColour('Green') + self.panel.Refresh() + + def OnEnterWindow(self, event): + self.button.SetLabel("Over Me!") + event.Skip() + + def OnLeaveWindow(self, event): + self.button.SetLabel("Not Over") + event.Skip() + + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = MouseEventFrame(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-04/PyWrap.py b/Chapter-04/PyWrap.py new file mode 100644 index 0000000..89e7459 --- /dev/null +++ b/Chapter-04/PyWrap.py @@ -0,0 +1,48 @@ +"""PyWrap is a command line utility that runs a wxPython program with +additional runtime-tools, such as PyCrust.""" + +__author__ = "Patrick K. O'Brien " +__cvsid__ = "$Id: PyWrap.py,v 1.6 2004/03/15 13:42:37 PKO Exp $" +__revision__ = "$Revision: 1.6 $"[11:-2] + +import wx +from wx import py + +import os +import sys + +def wrap(app): + wx.InitAllImageHandlers() + frame = py.crust.CrustFrame() + frame.SetSize((750, 525)) + frame.Show(True) + frame.shell.interp.locals['app'] = app + app.MainLoop() + +def main(modulename=None): + sys.path.insert(0, os.curdir) + if not modulename: + if len(sys.argv) < 2: + print "Please specify a module name." + raise SystemExit + modulename = sys.argv[1] + if modulename.endswith('.py'): + modulename = modulename[:-3] + module = __import__(modulename) + # Find the App class. + App = None + d = module.__dict__ + for item in d.keys(): + try: + if issubclass(d[item], wx.App): + App = d[item] + except (NameError, TypeError): + pass + if App is None: + print "No App class was found." + raise SystemExit + app = App() + wrap(app) + +if __name__ == '__main__': + main() diff --git a/Chapter-04/images.py b/Chapter-04/images.py new file mode 100644 index 0000000..63e5c8f --- /dev/null +++ b/Chapter-04/images.py @@ -0,0 +1,24 @@ +#---------------------------------------------------------------------- +# This file was generated by encode_bitmaps.py +# +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + +def getNewData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x0f\x08\x06\ +\x00\x00\x00\xedsO/\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\ +\x00YIDATx\x9c\xed\xd31\n@!\x0c\x03\xd0\xa4\xfe\xfb\xdfX\xe3\xf0\x97R\xa5(.\ +\x0ef\x13\xe45\xa2\x92Vp\x92\xcf/\xd4\xaa\xb2\xcd\xb4\xc2\x14\x00\x00in\x90\ +\x84ZUDl\xa9\xa7\xc3c\xcb-\x80\xfc\x87{d8B6=B\xdb\rfy\xc0\r\xc0\xf0\x0e\xfc\ +\x1d\xaf\x84\xa7\xbf\xb1\x03\xe1,\x19&\x93\x9a\xd2\x97\x00\x00\x00\x00IEND\ +\xaeB`\x82' + +def getNewBitmap(): + return BitmapFromImage(getNewImage()) + +def getNewImage(): + stream = cStringIO.StringIO(getNewData()) + return ImageFromStream(stream) + diff --git a/Chapter-04/pycrust-foundation.py b/Chapter-04/pycrust-foundation.py new file mode 100644 index 0000000..f9a5db6 --- /dev/null +++ b/Chapter-04/pycrust-foundation.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +import wx +from wx.py.shell import ShellFrame +from wx.py.filling import FillingFrame +import images + +class ToolbarFrame(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Toolbars', + size=(300, 200)) + panel = wx.Panel(self, -1) + panel.SetBackgroundColour('White') + statusBar = self.CreateStatusBar() + toolbar = self.CreateToolBar() + toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(), + "New", "Long help for 'New'") + toolbar.Realize() + menuBar = wx.MenuBar() + menu1 = wx.Menu() + menuBar.Append(menu1, "&File") + menu2 = wx.Menu() + menu2.Append(wx.NewId(), "&Copy", "Copy in status bar") + menu2.Append(wx.NewId(), "C&ut", "") + menu2.Append(wx.NewId(), "Paste", "") + menu2.AppendSeparator() + menu2.Append(wx.NewId(), "&Options...", "Display Options") + menuBar.Append(menu2, "&Edit") + + menu3 = wx.Menu() + shell = menu3.Append(-1, "&Python shell", + "Open Python shell frame") + filling = menu3.Append(-1, "&Namespace viewer", + "Open namespace viewer frame") + menuBar.Append(menu3, "&Debug") + self.Bind(wx.EVT_MENU, self.OnShell, shell) + self.Bind(wx.EVT_MENU, self.OnFilling, filling) + + self.SetMenuBar(menuBar) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + def OnShell(self, event): + frame = ShellFrame(parent=self) + frame.Show() + + def OnFilling(self, event): + frame = FillingFrame(parent=self) + frame.Show() + +if __name__ == '__main__': + app = wx.PySimpleApp() + app.frame = ToolbarFrame(parent=None, id=-1) + app.frame.Show() + app.MainLoop() diff --git a/Chapter-04/pywrap b/Chapter-04/pywrap new file mode 100644 index 0000000..3bd7ef1 --- /dev/null +++ b/Chapter-04/pywrap @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +from wx.py.PyWrap import main +main() diff --git a/Chapter-04/spare.py b/Chapter-04/spare.py new file mode 100644 index 0000000..7cb63b1 --- /dev/null +++ b/Chapter-04/spare.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +"""Spare.py is a starting point for simple wxPython programs.""" + +import wx + +class Frame(wx.Frame): + pass + +class App(wx.App): + + def OnInit(self): + self.frame = Frame(parent=None, id=-1, title='Spare') + self.frame.Show() + self.SetTopWindow(self.frame) + return True + +if __name__ == '__main__': + app = App() + app.MainLoop() diff --git a/Chapter-05/abstractmodel.py b/Chapter-05/abstractmodel.py new file mode 100644 index 0000000..80c8176 --- /dev/null +++ b/Chapter-05/abstractmodel.py @@ -0,0 +1,15 @@ +class AbstractModel(object): + + def __init__(self): + self.listeners = [] + + def addListener(self, listenerFunc): + self.listeners.append(listenerFunc) + + def removeListener(self, listenerFunc): + self.listeners.remove(listenerFunc) + + def update(self): + for eachFunc in self.listeners: + eachFunc(self) + diff --git a/Chapter-05/abstractmodel.pyc b/Chapter-05/abstractmodel.pyc new file mode 100644 index 0000000..1436fe5 Binary files /dev/null and b/Chapter-05/abstractmodel.pyc differ diff --git a/Chapter-05/badExample.py b/Chapter-05/badExample.py new file mode 100644 index 0000000..6a57f5b --- /dev/null +++ b/Chapter-05/badExample.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import wx + +class RefactorExample(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Refactor Example', + size=(340, 200)) + panel = wx.Panel(self, -1) + panel.SetBackgroundColour("White") + prevButton = wx.Button(panel, -1, "<< PREV", pos=(80, 0)) + self.Bind(wx.EVT_BUTTON, self.OnPrev, prevButton) + nextButton = wx.Button(panel, -1, "NEXT >>", pos=(160, 0)) + self.Bind(wx.EVT_BUTTON, self.OnNext, nextButton) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + menuBar = wx.MenuBar() + menu1 = wx.Menu() + openMenuItem = menu1.Append(-1, "&Open", "Copy in status bar") + self.Bind(wx.EVT_MENU, self.OnOpen, openMenuItem) + quitMenuItem = menu1.Append(-1, "&Quit", "Quit") + self.Bind(wx.EVT_MENU, self.OnCloseWindow, quitMenuItem) + menuBar.Append(menu1, "&File") + menu2 = wx.Menu() + copyItem = menu2.Append(-1, "&Copy", "Copy") + self.Bind(wx.EVT_MENU, self.OnCopy, copyItem) + cutItem = menu2.Append(-1, "C&ut", "Cut") + self.Bind(wx.EVT_MENU, self.OnCut, cutItem) + pasteItem = menu2.Append(-1, "Paste", "Paste") + self.Bind(wx.EVT_MENU, self.OnPaste, pasteItem) + menuBar.Append(menu2, "&Edit") + self.SetMenuBar(menuBar) + + static = wx.StaticText(panel, wx.NewId(), "First Name", + pos=(10, 50)) + static.SetBackgroundColour("White") + text = wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), + pos=(80, 50)) + + static2 = wx.StaticText(panel, wx.NewId(), "Last Name", + pos=(10, 80)) + static2.SetBackgroundColour("White") + text2 = wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), + pos=(80, 80)) + + firstButton = wx.Button(panel, -1, "FIRST") + self.Bind(wx.EVT_BUTTON, self.OnFirst, firstButton) + + menu2.AppendSeparator() + optItem = menu2.Append(-1, "&Options...", "Display Options") + self.Bind(wx.EVT_MENU, self.OnOptions, optItem) + + lastButton = wx.Button(panel, -1, "LAST", pos=(240, 0)) + self.Bind(wx.EVT_BUTTON, self.OnLast, lastButton) + + + # Just grouping the empty event handlers together + def OnPrev(self, event): pass + def OnNext(self, event): pass + def OnLast(self, event): pass + def OnFirst(self, event): pass + def OnOpen(self, event): pass + def OnCopy(self, event): pass + def OnCut(self, event): pass + def OnPaste(self, event): pass + def OnOptions(self, event): pass + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = RefactorExample(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-05/generictable.py b/Chapter-05/generictable.py new file mode 100644 index 0000000..88643bd --- /dev/null +++ b/Chapter-05/generictable.py @@ -0,0 +1,34 @@ +import wx +import wx.grid + +class GenericTable(wx.grid.PyGridTableBase): + + def __init__(self, data, rowLabels=None, colLabels=None): + wx.grid.PyGridTableBase.__init__(self) + self.data = data + self.rowLabels = rowLabels + self.colLabels = colLabels + + def GetNumberRows(self): + return len(self.data) + + def GetNumberCols(self): + return len(self.data[0]) + + def GetColLabelValue(self, col): + if self.colLabels: + return self.colLabels[col] + + def GetRowLabelValue(self, row): + if self.rowLabels: + return self.rowLabels[row] + + def IsEmptyCell(self, row, col): + return False + + def GetValue(self, row, col): + return self.data[row][col] + + def SetValue(self, row, col, value): + pass + diff --git a/Chapter-05/goodExample.py b/Chapter-05/goodExample.py new file mode 100644 index 0000000..adb7e54 --- /dev/null +++ b/Chapter-05/goodExample.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +import wx + +class RefactorExample(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Refactor Example', + size=(340, 200)) + panel = wx.Panel(self, -1) + panel.SetBackgroundColour("White") + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.createMenuBar() + self.createButtonBar(panel) + self.createTextFields(panel) + + def menuData(self): + return (("&File", + ("&Open", "Open in status bar", self.OnOpen), + ("&Quit", "Quit", self.OnCloseWindow)), + ("&Edit", + ("&Copy", "Copy", self.OnCopy), + ("C&ut", "Cut", self.OnCut), + ("&Paste", "Paste", self.OnPaste), + ("", "", ""), + ("&Options...", "DisplayOptions", self.OnOptions))) + + def createMenuBar(self): + menuBar = wx.MenuBar() + for eachMenuData in self.menuData(): + menuLabel = eachMenuData[0] + menuItems = eachMenuData[1:] + menuBar.Append(self.createMenu(menuItems), menuLabel) + self.SetMenuBar(menuBar) + + def createMenu(self, menuData): + menu = wx.Menu() + for eachLabel, eachStatus, eachHandler in menuData: + if not eachLabel: + menu.AppendSeparator() + continue + menuItem = menu.Append(-1, eachLabel, eachStatus) + self.Bind(wx.EVT_MENU, eachHandler, menuItem) + return menu + + def buttonData(self): + return (("First", self.OnFirst), + ("<< PREV", self.OnPrev), + ("NEXT >>", self.OnNext), + ("Last", self.OnLast)) + + def createButtonBar(self, panel, yPos = 0): + xPos = 0 + for eachLabel, eachHandler in self.buttonData(): + pos = (xPos, yPos) + button = self.buildOneButton(panel, eachLabel, eachHandler, pos) + xPos += button.GetSize().width + + def buildOneButton(self, parent, label, handler, pos=(0,0)): + button = wx.Button(parent, -1, label, pos) + self.Bind(wx.EVT_BUTTON, handler, button) + return button + + def textFieldData(self): + return (("First Name", (10, 50)), + ("Last Name", (10, 80))) + + def createTextFields(self, panel): + for eachLabel, eachPos in self.textFieldData(): + self.createCaptionedText(panel, eachLabel, eachPos) + + def createCaptionedText(self, panel, label, pos): + static = wx.StaticText(panel, wx.NewId(), label, pos) + static.SetBackgroundColour("White") + textPos = (pos[0] + 75, pos[1]) + wx.TextCtrl(panel, wx.NewId(), "", size=(100, -1), pos=textPos) + + # Just grouping the empty event handlers together + def OnPrev(self, event): pass + def OnNext(self, event): pass + def OnLast(self, event): pass + def OnFirst(self, event): pass + def OnOpen(self, event): pass + def OnCopy(self, event): pass + def OnCut(self, event): pass + def OnPaste(self, event): pass + def OnOptions(self, event): pass + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = RefactorExample(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-05/gridGeneric.py b/Chapter-05/gridGeneric.py new file mode 100644 index 0000000..73c7940 --- /dev/null +++ b/Chapter-05/gridGeneric.py @@ -0,0 +1,34 @@ +import wx +import wx.grid +import generictable + + +data = (("Bob", "Dernier"), ("Ryne", "Sandberg"), + ("Gary", "Matthews"), ("Leon", "Durham"), + ("Keith", "Moreland"), ("Ron", "Cey"), + ("Jody", "Davis"), ("Larry", "Bowa"), + ("Rick", "Sutcliffe")) + +colLabels = ("Last", "First") +rowLabels = ("CF", "2B", "LF", "1B", "RF", "3B", "C", "SS", "P") + + +class SimpleGrid(wx.grid.Grid): + def __init__(self, parent): + wx.grid.Grid.__init__(self, parent, -1) + tableBase = generictable.GenericTable(data, rowLabels, + colLabels) + self.SetTable(tableBase) + +class TestFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "A Grid", + size=(275, 275)) + grid = SimpleGrid(self) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = TestFrame(None) + frame.Show(True) + app.MainLoop() + diff --git a/Chapter-05/gridModel.py b/Chapter-05/gridModel.py new file mode 100644 index 0000000..d8a990b --- /dev/null +++ b/Chapter-05/gridModel.py @@ -0,0 +1,54 @@ +import wx +import wx.grid + +class LineupTable(wx.grid.PyGridTableBase): + + data = (("CF", "Bob", "Dernier"), ("2B", "Ryne", "Sandberg"), + ("LF", "Gary", "Matthews"), ("1B", "Leon", "Durham"), + ("RF", "Keith", "Moreland"), ("3B", "Ron", "Cey"), + ("C", "Jody", "Davis"), ("SS", "Larry", "Bowa"), + ("P", "Rick", "Sutcliffe")) + + colLabels = ("Last", "First") + + def __init__(self): + wx.grid.PyGridTableBase.__init__(self) + + def GetNumberRows(self): + return len(self.data) + + def GetNumberCols(self): + return len(self.data[0]) - 1 + + def GetColLabelValue(self, col): + return self.colLabels[col] + + def GetRowLabelValue(self, row): + return self.data[row][0] + + def IsEmptyCell(self, row, col): + return False + + def GetValue(self, row, col): + return self.data[row][col + 1] + + def SetValue(self, row, col, value): + pass + +class SimpleGrid(wx.grid.Grid): + def __init__(self, parent): + wx.grid.Grid.__init__(self, parent, -1) + self.SetTable(LineupTable()) + +class TestFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "A Grid", + size=(275, 275)) + grid = SimpleGrid(self) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = TestFrame(None) + frame.Show(True) + app.MainLoop() + diff --git a/Chapter-05/gridNoModel.py b/Chapter-05/gridNoModel.py new file mode 100644 index 0000000..faf411c --- /dev/null +++ b/Chapter-05/gridNoModel.py @@ -0,0 +1,49 @@ +import wx +import wx.grid + +class SimpleGrid(wx.grid.Grid): + def __init__(self, parent): + wx.grid.Grid.__init__(self, parent, -1) + self.CreateGrid(9, 2) + self.SetColLabelValue(0, "First") + self.SetColLabelValue(1, "Last") + self.SetRowLabelValue(0, "CF") + self.SetCellValue(0, 0, "Bob") + self.SetCellValue(0, 1, "Dernier") + self.SetRowLabelValue(1, "2B") + self.SetCellValue(1, 0, "Ryne") + self.SetCellValue(1, 1, "Sandberg") + self.SetRowLabelValue(2, "LF") + self.SetCellValue(2, 0, "Gary") + self.SetCellValue(2, 1, "Matthews") + self.SetRowLabelValue(3, "1B") + self.SetCellValue(3, 0, "Leon") + self.SetCellValue(3, 1, "Durham") + self.SetRowLabelValue(4, "RF") + self.SetCellValue(4, 0, "Keith") + self.SetCellValue(4, 1, "Moreland") + self.SetRowLabelValue(5, "3B") + self.SetCellValue(5, 0, "Ron") + self.SetCellValue(5, 1, "Cey") + self.SetRowLabelValue(6, "C") + self.SetCellValue(6, 0, "Jody") + self.SetCellValue(6, 1, "Davis") + self.SetRowLabelValue(7, "SS") + self.SetCellValue(7, 0, "Larry") + self.SetCellValue(7, 1, "Bowa") + self.SetRowLabelValue(8, "P") + self.SetCellValue(8, 0, "Rick") + self.SetCellValue(8, 1, "Sutcliffe") + +class TestFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "A Grid", + size=(275, 275)) + grid = SimpleGrid(self) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = TestFrame(None) + frame.Show(True) + app.MainLoop() + diff --git a/Chapter-05/lineuptable.py b/Chapter-05/lineuptable.py new file mode 100644 index 0000000..f0925e9 --- /dev/null +++ b/Chapter-05/lineuptable.py @@ -0,0 +1,41 @@ +import wx +import wx.grid + +class LineupEntry: + + def __init__(self, pos, first, last): + self.pos = pos + self.first = first + self.last = last + +class LineupTable(wx.grid.PyGridTableBase): + + colLabels = ("First", "Last") + colAttrs = ("first", "last") + + def __init__(self, entries): + wx.grid.PyGridTableBase.__init__(self) + self.entries = entries + + def GetNumberRows(self): + return len(self.entries) + + def GetNumberCols(self): + return 2 + + def GetColLabelValue(self, col): + return self.colLabels[col] + + def GetRowLabelValue(self, col): + return self.entries[row].pos + + def IsEmptyCell(self, row, col): + return False + + def GetValue(self, row, col): + entry = self.entries[row] + return getattr(entry, self.colAttrs[col]) + + def SetValue(self, row, col, value): + pass + diff --git a/Chapter-05/modelExample.py b/Chapter-05/modelExample.py new file mode 100644 index 0000000..c9c53ec --- /dev/null +++ b/Chapter-05/modelExample.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +import wx +import abstractmodel + +class SimpleName(abstractmodel.AbstractModel): + + def __init__(self, first="", last=""): + abstractmodel.AbstractModel.__init__(self) + self.set(first, last) + + def set(self, first, last): + self.first = first + self.last = last + self.update() + +class ModelExample(wx.Frame): + + def __init__(self, parent, id): + wx.Frame.__init__(self, parent, id, 'Flintstones', + size=(340, 200)) + panel = wx.Panel(self) + #panel.SetBackgroundColour("Brown") + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.textFields = {} + self.createTextFields(panel) + self.model = SimpleName() + self.model.addListener(self.OnUpdate) + self.createButtonBar(panel) + + def buttonData(self): + return (("Fredify", self.OnFred), + ("Wilmafy", self.OnWilma), + ("Barnify", self.OnBarney), + ("Bettify", self.OnBetty)) + + def createButtonBar(self, panel, yPos = 0): + xPos = 0 + for eachLabel, eachHandler in self.buttonData(): + pos = (xPos, yPos) + button = self.buildOneButton(panel, eachLabel, eachHandler, pos) + xPos += button.GetSize().width + + def buildOneButton(self, parent, label, handler, pos=(0,0)): + button = wx.Button(parent, -1, label, pos) + self.Bind(wx.EVT_BUTTON, handler, button) + return button + + def textFieldData(self): + return (("First Name", (10, 50)), + ("Last Name", (10, 80))) + + def createTextFields(self, panel): + for eachLabel, eachPos in self.textFieldData(): + self.createCaptionedText(panel, eachLabel, eachPos) + + def createCaptionedText(self, panel, label, pos): + static = wx.StaticText(panel, wx.NewId(), label, pos) + static.SetBackgroundColour("White") + textPos = (pos[0] + 75, pos[1]) + self.textFields[label] = wx.TextCtrl(panel, wx.NewId(), + "", size=(100, -1), pos=textPos, + style=wx.TE_READONLY) + + def OnUpdate(self, model): + self.textFields["First Name"].SetValue(model.first) + self.textFields["Last Name"].SetValue(model.last) + + def OnFred(self, event): + self.model.set("Fred", "Flintstone") + + def OnBarney(self, event): + self.model.set("Barney", "Rubble") + + def OnWilma(self, event): + self.model.set("Wilma", "Flintstone") + + def OnBetty(self, event): + self.model.set("Betty", "Rubble") + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = ModelExample(parent=None, id=-1) + frame.Show() + app.MainLoop() + diff --git a/Chapter-05/modelExample.pyc b/Chapter-05/modelExample.pyc new file mode 100644 index 0000000..a3b49a2 Binary files /dev/null and b/Chapter-05/modelExample.pyc differ diff --git a/Chapter-05/testEventExample.py b/Chapter-05/testEventExample.py new file mode 100644 index 0000000..321a4b4 --- /dev/null +++ b/Chapter-05/testEventExample.py @@ -0,0 +1,37 @@ +import unittest +import modelExample +import wx + +class TestExample(unittest.TestCase): + + def setUp(self): + self.app = wx.PySimpleApp() + self.frame = modelExample.ModelExample(parent=None, id=-1) + + def tearDown(self): + self.frame.Destroy() + + def testModel(self): + self.frame.OnBarney(None) + self.assertEqual("Barney", self.frame.model.first, + msg="First is wrong") + self.assertEqual("Rubble", self.frame.model.last) + + def testEvent(self): + panel = self.frame.GetChildren()[0] + for each in panel.GetChildren(): + if each.GetLabel() == "Wilmafy": + wilma = each + break + event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, wilma.GetId()) + wilma.GetEventHandler().ProcessEvent(event) + self.assertEqual("Wilma", self.frame.model.first) + self.assertEqual("Flintstone", self.frame.model.last) + +def suite(): + suite = unittest.makeSuite(TestExample, 'test') + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') + diff --git a/Chapter-05/testExample.py b/Chapter-05/testExample.py new file mode 100644 index 0000000..9015ceb --- /dev/null +++ b/Chapter-05/testExample.py @@ -0,0 +1,26 @@ +import unittest +import modelExample +import wx + +class TestExample(unittest.TestCase): + + def setUp(self): + self.app = wx.PySimpleApp() + self.frame = modelExample.ModelExample(parent=None, id=-1) + + def tearDown(self): + self.frame.Destroy() + + def testModel(self): + self.frame.OnBarney(None) + self.assertEqual("Barney", self.frame.model.first, + msg="First is wrong") + self.assertEqual("Rubble", self.frame.model.last) + +def suite(): + suite = unittest.makeSuite(TestExample, 'test') + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') + diff --git a/Chapter-06/example1.py b/Chapter-06/example1.py new file mode 100644 index 0000000..c3e6d75 --- /dev/null +++ b/Chapter-06/example1.py @@ -0,0 +1,104 @@ +import wx + + +class SketchWindow(wx.Window): + def __init__(self, parent, ID): + wx.Window.__init__(self, parent, ID) + self.SetBackgroundColour("White") + self.color = "Black" + self.thickness = 1 + self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) + self.lines = [] + self.curLine = [] + self.pos = (0, 0) + self.InitBuffer() + + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_IDLE, self.OnIdle) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def InitBuffer(self): + size = self.GetClientSize() + self.buffer = wx.EmptyBitmap(size.width, size.height) + dc = wx.BufferedDC(None, self.buffer) + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + self.DrawLines(dc) + self.reInitBuffer = False + + def GetLinesData(self): + return self.lines[:] + + def SetLinesData(self, lines): + self.lines = lines[:] + self.InitBuffer() + self.Refresh() + + def OnLeftDown(self, event): + self.curLine = [] + self.pos = event.GetPositionTuple() + self.CaptureMouse() + + def OnLeftUp(self, event): + if self.HasCapture(): + self.lines.append((self.color, + self.thickness, + self.curLine)) + self.curLine = [] + self.ReleaseMouse() + + def OnMotion(self, event): + if event.Dragging() and event.LeftIsDown(): + dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) + self.drawMotion(dc, event) + event.Skip() + + def drawMotion(self, dc, event): + dc.SetPen(self.pen) + newPos = event.GetPositionTuple() + coords = self.pos + newPos + self.curLine.append(coords) + dc.DrawLine(*coords) + self.pos = newPos + + def OnSize(self, event): + self.reInitBuffer = True + + def OnIdle(self, event): + if self.reInitBuffer: + self.InitBuffer() + self.Refresh(False) + + def OnPaint(self, event): + dc = wx.BufferedPaintDC(self, self.buffer) + + def DrawLines(self, dc): + for colour, thickness, line in self.lines: + pen = wx.Pen(colour, thickness, wx.SOLID) + dc.SetPen(pen) + for coords in line: + dc.DrawLine(*coords) + + def SetColor(self, color): + self.color = color + self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) + + def SetThickness(self, num): + self.thickness = num + self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) + + +class SketchFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Sketch Frame", + size=(800,600)) + self.sketch = SketchWindow(self, -1) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example1.pyc b/Chapter-06/example1.pyc new file mode 100644 index 0000000..1586e3c Binary files /dev/null and b/Chapter-06/example1.pyc differ diff --git a/Chapter-06/example2.py b/Chapter-06/example2.py new file mode 100644 index 0000000..9b0ddea --- /dev/null +++ b/Chapter-06/example2.py @@ -0,0 +1,21 @@ +import wx +from example1 import SketchWindow + + +class SketchFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Sketch Frame", + size=(800,600)) + self.sketch = SketchWindow(self, -1) + self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) + self.statusbar = self.CreateStatusBar() + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText(str(event.GetPositionTuple())) + event.Skip() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example3.py b/Chapter-06/example3.py new file mode 100644 index 0000000..240fa38 --- /dev/null +++ b/Chapter-06/example3.py @@ -0,0 +1,27 @@ +import wx +from example1 import SketchWindow + +class SketchFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Sketch Frame", + size=(800,600)) + self.sketch = SketchWindow(self, -1) + self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) + self.statusbar = self.CreateStatusBar() + self.statusbar.SetFieldsCount(3) + self.statusbar.SetStatusWidths([-1, -2, -3]) + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText("Pos: %s" % + str(event.GetPositionTuple()), 0) + self.statusbar.SetStatusText("Current Pts: %s" % + len(self.sketch.curLine), 1) + self.statusbar.SetStatusText("Line Count: %s" % + len(self.sketch.lines), 2) + event.Skip() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example4.py b/Chapter-06/example4.py new file mode 100644 index 0000000..05bc7d7 --- /dev/null +++ b/Chapter-06/example4.py @@ -0,0 +1,92 @@ +import wx +from example1 import SketchWindow + + +class SketchFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Sketch Frame", + size=(800,600)) + self.sketch = SketchWindow(self, -1) + self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) + self.initStatusBar() + self.createMenuBar() + + def initStatusBar(self): + self.statusbar = self.CreateStatusBar() + self.statusbar.SetFieldsCount(3) + self.statusbar.SetStatusWidths([-1, -2, -3]) + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText("Pos: %s" % + str(event.GetPositionTuple()), 0) + self.statusbar.SetStatusText("Current Pts: %s" % + len(self.sketch.curLine), 1) + self.statusbar.SetStatusText("Line Count: %s" % + len(self.sketch.lines), 2) + event.Skip() + + def menuData(self): + return [("&File", ( + ("&New", "New Sketch file", self.OnNew), + ("&Open", "Open sketch file", self.OnOpen), + ("&Save", "Save sketch file", self.OnSave), + ("", "", ""), + ("&Color", ( + ("&Black", "", self.OnColor, + wx.ITEM_RADIO), + ("&Red", "", self.OnColor, + wx.ITEM_RADIO), + ("&Green", "", self.OnColor, + wx.ITEM_RADIO), + ("&Blue", "", self.OnColor, + wx.ITEM_RADIO))), + ("", "", ""), + ("&Quit", "Quit", self.OnCloseWindow)))] + + def createMenuBar(self): + menuBar = wx.MenuBar() + for eachMenuData in self.menuData(): + menuLabel = eachMenuData[0] + menuItems = eachMenuData[1] + menuBar.Append(self.createMenu(menuItems), menuLabel) + self.SetMenuBar(menuBar) + + def createMenu(self, menuData): + menu = wx.Menu() + for eachItem in menuData: + if len(eachItem) == 2: + label = eachItem[0] + subMenu = self.createMenu(eachItem[1]) + menu.AppendMenu(wx.NewId(), label, subMenu) + else: + self.createMenuItem(menu, *eachItem) + return menu + + def createMenuItem(self, menu, label, status, handler, + kind=wx.ITEM_NORMAL): + if not label: + menu.AppendSeparator() + return + menuItem = menu.Append(-1, label, status, kind) + self.Bind(wx.EVT_MENU, handler, menuItem) + + def OnNew(self, event): pass + def OnOpen(self, event): pass + def OnSave(self, event): pass + + def OnColor(self, event): + menubar = self.GetMenuBar() + itemId = event.GetId() + item = menubar.FindItemById(itemId) + color = item.GetLabel() + self.sketch.SetColor(color) + + def OnCloseWindow(self, event): + self.Destroy() + + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example5.py b/Chapter-06/example5.py new file mode 100644 index 0000000..cbe0213 --- /dev/null +++ b/Chapter-06/example5.py @@ -0,0 +1,132 @@ +import wx +from example1 import SketchWindow + + +class SketchFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "Sketch Frame", + size=(800,600)) + self.sketch = SketchWindow(self, -1) + self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) + self.initStatusBar() + self.createMenuBar() + self.createToolBar() + + def initStatusBar(self): + self.statusbar = self.CreateStatusBar() + self.statusbar.SetFieldsCount(3) + self.statusbar.SetStatusWidths([-1, -2, -3]) + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText("Pos: %s" % + str(event.GetPositionTuple()), 0) + self.statusbar.SetStatusText("Current Pts: %s" % + len(self.sketch.curLine), 1) + self.statusbar.SetStatusText("Line Count: %s" % + len(self.sketch.lines), 2) + event.Skip() + + def menuData(self): + return [("&File", ( + ("&New", "New Sketch file", self.OnNew), + ("&Open", "Open sketch file", self.OnOpen), + ("&Save", "Save sketch file", self.OnSave), + ("", "", ""), + ("&Color", ( + ("&Black", "", self.OnColor, wx.ITEM_RADIO), + ("&Red", "", self.OnColor, wx.ITEM_RADIO), + ("&Green", "", self.OnColor, wx.ITEM_RADIO), + ("&Blue", "", self.OnColor, wx.ITEM_RADIO))), + ("", "", ""), + ("&Quit", "Quit", self.OnCloseWindow)))] + + def createMenuBar(self): + menuBar = wx.MenuBar() + for eachMenuData in self.menuData(): + menuLabel = eachMenuData[0] + menuItems = eachMenuData[1] + menuBar.Append(self.createMenu(menuItems), menuLabel) + self.SetMenuBar(menuBar) + + def createMenu(self, menuData): + menu = wx.Menu() + for eachItem in menuData: + if len(eachItem) == 2: + label = eachItem[0] + subMenu = self.createMenu(eachItem[1]) + menu.AppendMenu(wx.NewId(), label, subMenu) + else: + self.createMenuItem(menu, *eachItem) + return menu + + def createMenuItem(self, menu, label, status, handler, kind=wx.ITEM_NORMAL): + if not label: + menu.AppendSeparator() + return + menuItem = menu.Append(-1, label, status, kind) + self.Bind(wx.EVT_MENU, handler, menuItem) + + def createToolBar(self): + toolbar = self.CreateToolBar() + for each in self.toolbarData(): + self.createSimpleTool(toolbar, *each) + toolbar.AddSeparator() + for each in self.toolbarColorData(): + self.createColorTool(toolbar, each) + toolbar.Realize() + + def createSimpleTool(self, toolbar, label, filename, help, handler): + if not label: + toolbar.AddSeparator() + return + bmp = wx.Image(filename, wx.BITMAP_TYPE_BMP).ConvertToBitmap() + tool = toolbar.AddSimpleTool(-1, bmp, label, help) + self.Bind(wx.EVT_MENU, handler, tool) + + def toolbarData(self): + return (("New", "new.bmp", "Create new sketch", self.OnNew), + ("", "", "", ""), + ("Open", "open.bmp", "Open existing sketch", self.OnOpen), + ("Save", "save.bmp", "Save existing sketch", self.OnSave)) + + def createColorTool(self, toolbar, color): + bmp = self.MakeBitmap(color) + tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color) + self.Bind(wx.EVT_MENU, self.OnColor, tool) + + def MakeBitmap(self, color): + bmp = wx.EmptyBitmap(16, 15) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + dc.SetBackground(wx.Brush(color)) + dc.Clear() + dc.SelectObject(wx.NullBitmap) + return bmp + + def toolbarColorData(self): + return ("Black", "Red", "Green", "Blue") + + def OnNew(self, event): pass + def OnOpen(self, event): pass + def OnSave(self, event): pass + + def OnColor(self, event): + menubar = self.GetMenuBar() + itemId = event.GetId() + item = menubar.FindItemById(itemId) + if not item: + toolbar = self.GetToolBar() + item = toolbar.FindById(itemId) + color = item.GetShortHelp() + else: + color = item.GetLabel() + self.sketch.SetColor(color) + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example6.py b/Chapter-06/example6.py new file mode 100644 index 0000000..9d58f06 --- /dev/null +++ b/Chapter-06/example6.py @@ -0,0 +1,192 @@ +import wx +import cPickle +import os + +from example1 import SketchWindow + + +class SketchFrame(wx.Frame): + def __init__(self, parent): + self.title = "Sketch Frame" + wx.Frame.__init__(self, parent, -1, self.title, + size=(800,600)) + self.filename = "" + self.sketch = SketchWindow(self, -1) + self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) + self.initStatusBar() + self.createMenuBar() + self.createToolBar() + + def initStatusBar(self): + self.statusbar = self.CreateStatusBar() + self.statusbar.SetFieldsCount(3) + self.statusbar.SetStatusWidths([-1, -2, -3]) + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText("Pos: %s" % + str(event.GetPositionTuple()), 0) + self.statusbar.SetStatusText("Current Pts: %s" % + len(self.sketch.curLine), 1) + self.statusbar.SetStatusText("Line Count: %s" % + len(self.sketch.lines), 2) + event.Skip() + + def menuData(self): + return [("&File", ( + ("&New", "New Sketch file", self.OnNew), + ("&Open", "Open sketch file", self.OnOpen), + ("&Save", "Save sketch file", self.OnSave), + ("", "", ""), + ("&Color", ( + ("&Black", "", self.OnColor, wx.ITEM_RADIO), + ("&Red", "", self.OnColor, wx.ITEM_RADIO), + ("&Green", "", self.OnColor, wx.ITEM_RADIO), + ("&Blue", "", self.OnColor, wx.ITEM_RADIO), + ("&Other...", "", self.OnOtherColor, wx.ITEM_RADIO))), + ("", "", ""), + ("&Quit", "Quit", self.OnCloseWindow)))] + + def createMenuBar(self): + menuBar = wx.MenuBar() + for eachMenuData in self.menuData(): + menuLabel = eachMenuData[0] + menuItems = eachMenuData[1] + menuBar.Append(self.createMenu(menuItems), menuLabel) + self.SetMenuBar(menuBar) + + def createMenu(self, menuData): + menu = wx.Menu() + for eachItem in menuData: + if len(eachItem) == 2: + label = eachItem[0] + subMenu = self.createMenu(eachItem[1]) + menu.AppendMenu(wx.NewId(), label, subMenu) + else: + self.createMenuItem(menu, *eachItem) + return menu + + def createMenuItem(self, menu, label, status, handler, kind=wx.ITEM_NORMAL): + if not label: + menu.AppendSeparator() + return + menuItem = menu.Append(-1, label, status, kind) + self.Bind(wx.EVT_MENU, handler, menuItem) + + def createToolBar(self): + toolbar = self.CreateToolBar() + for each in self.toolbarData(): + self.createSimpleTool(toolbar, *each) + toolbar.AddSeparator() + for each in self.toolbarColorData(): + self.createColorTool(toolbar, each) + toolbar.Realize() + + def createSimpleTool(self, toolbar, label, filename, help, handler): + if not label: + toolbar.AddSeparator() + return + bmp = wx.Image(filename, wx.BITMAP_TYPE_BMP).ConvertToBitmap() + tool = toolbar.AddSimpleTool(-1, bmp, label, help) + self.Bind(wx.EVT_MENU, handler, tool) + + def toolbarData(self): + return (("New", "new.bmp", "Create new sketch", self.OnNew), + ("", "", "", ""), + ("Open", "open.bmp", "Open existing sketch", self.OnOpen), + ("Save", "save.bmp", "Save existing sketch", self.OnSave)) + + def createColorTool(self, toolbar, color): + bmp = self.MakeBitmap(color) + tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color) + self.Bind(wx.EVT_MENU, self.OnColor, tool) + + def MakeBitmap(self, color): + bmp = wx.EmptyBitmap(16, 15) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + dc.SetBackground(wx.Brush(color)) + dc.Clear() + dc.SelectObject(wx.NullBitmap) + return bmp + + def toolbarColorData(self): + return ("Black", "Red", "Green", "Blue") + + def OnNew(self, event): pass + + def OnColor(self, event): + menubar = self.GetMenuBar() + itemId = event.GetId() + item = menubar.FindItemById(itemId) + if not item: + toolbar = self.GetToolBar() + item = toolbar.FindById(itemId) + color = item.GetShortHelp() + else: + color = item.GetLabel() + self.sketch.SetColor(color) + + def OnCloseWindow(self, event): + self.Destroy() + + def SaveFile(self): + if self.filename: + data = self.sketch.GetLinesData() + f = open(self.filename, 'w') + cPickle.dump(data, f) + f.close() + + def ReadFile(self): + if self.filename: + try: + f = open(self.filename, 'r') + data = cPickle.load(f) + f.close() + self.sketch.SetLinesData(data) + except cPickle.UnpicklingError: + wx.MessageBox("%s is not a sketch file." % self.filename, + "oops!", style=wx.OK|wx.ICON_EXCLAMATION) + + wildcard = "Sketch files (*.sketch)|*.sketch|All files (*.*)|*.*" + + def OnOpen(self, event): + dlg = wx.FileDialog(self, "Open sketch file...", os.getcwd(), + style=wx.OPEN, wildcard=self.wildcard) + if dlg.ShowModal() == wx.ID_OK: + self.filename = dlg.GetPath() + self.ReadFile() + self.SetTitle(self.title + ' -- ' + self.filename) + dlg.Destroy() + + def OnSave(self, event): + if not self.filename: + self.OnSaveAs(event) + else: + self.SaveFile() + + def OnSaveAs(self, event): + dlg = wx.FileDialog(self, "Save sketch as...", os.getcwd(), + style=wx.SAVE | wx.OVERWRITE_PROMPT, + wildcard = self.wildcard) + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + if not os.path.splitext(filename)[1]: + filename = filename + '.sketch' + self.filename = filename + self.SaveFile() + self.SetTitle(self.title + ' -- ' + self.filename) + dlg.Destroy() + + def OnOtherColor(self, event): + dlg = wx.ColourDialog(frame) + dlg.GetColourData().SetChooseFull(True) + if dlg.ShowModal() == wx.ID_OK: + self.sketch.SetColor(dlg.GetColourData().GetColour()) + dlg.Destroy() + + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SketchFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/Chapter-06/example7.py b/Chapter-06/example7.py new file mode 100644 index 0000000..626827f --- /dev/null +++ b/Chapter-06/example7.py @@ -0,0 +1,331 @@ +import wx +import wx.html +import cPickle +import os +from wx.lib import buttons + +from example1 import SketchWindow + +class SketchFrame(wx.Frame): + def __init__(self, parent): + self.title = "Sketch Frame" + wx.Frame.__init__(self, parent, -1, self.title, + size=(800,600)) + self.filename = "" + self.sketch = SketchWindow(self, -1) + wx.EVT_MOTION(self.sketch, self.OnSketchMotion) + self.initStatusBar() + self.createMenuBar() + self.createToolBar() + self.createPanel() + + def createPanel(self): + controlPanel = ControlPanel(self, -1, self.sketch) + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(controlPanel, 0, wx.EXPAND) + box.Add(self.sketch, 1, wx.EXPAND) + self.SetSizer(box) + + def initStatusBar(self): + self.statusbar = self.CreateStatusBar() + self.statusbar.SetFieldsCount(3) + self.statusbar.SetStatusWidths([-1, -2, -3]) + + def OnSketchMotion(self, event): + self.statusbar.SetStatusText("Pos: %s" % + str(event.GetPositionTuple()), 0) + self.statusbar.SetStatusText("Current Pts: %s" % + len(self.sketch.curLine), 1) + self.statusbar.SetStatusText("Line Count: %s" % + len(self.sketch.lines), 2) + event.Skip() + + def menuData(self): + return [("&File", ( + ("&New", "New Sketch file", self.OnNew), + ("&Open", "Open sketch file", self.OnOpen), + ("&Save", "Save sketch file", self.OnSave), + ("", "", ""), + ("&Color", ( + ("&Black", "", self.OnColor, wx.ITEM_RADIO), + ("&Red", "", self.OnColor, wx.ITEM_RADIO), + ("&Green", "", self.OnColor, wx.ITEM_RADIO), + ("&Blue", "", self.OnColor, wx.ITEM_RADIO), + ("&Other...", "", self.OnOtherColor, wx.ITEM_RADIO))), + ("", "", ""), + ("About...", "Show about window", self.OnAbout), + ("&Quit", "Quit", self.OnCloseWindow)))] + + def createMenuBar(self): + menuBar = wx.MenuBar() + for eachMenuData in self.menuData(): + menuLabel = eachMenuData[0] + menuItems = eachMenuData[1] + menuBar.Append(self.createMenu(menuItems), menuLabel) + self.SetMenuBar(menuBar) + + def createMenu(self, menuData): + menu = wx.Menu() + for eachItem in menuData: + if len(eachItem) == 2: + label = eachItem[0] + subMenu = self.createMenu(eachItem[1]) + menu.AppendMenu(wx.NewId(), label, subMenu) + else: + self.createMenuItem(menu, *eachItem) + return menu + + def createMenuItem(self, menu, label, status, handler, kind=wx.ITEM_NORMAL): + if not label: + menu.AppendSeparator() + return + menuItem = menu.Append(-1, label, status, kind) + self.Bind(wx.EVT_MENU, handler, menuItem) + + def createToolBar(self): + toolbar = self.CreateToolBar() + for each in self.toolbarData(): + self.createSimpleTool(toolbar, *each) + toolbar.AddSeparator() + for each in self.toolbarColorData(): + self.createColorTool(toolbar, each) + toolbar.Realize() + + def createSimpleTool(self, toolbar, label, filename, help, handler): + if not label: + toolbar.AddSeparator() + return + bmp = wx.Image(filename, wx.BITMAP_TYPE_BMP).ConvertToBitmap() + tool = toolbar.AddSimpleTool(-1, bmp, label, help) + self.Bind(wx.EVT_MENU, handler, tool) + + def toolbarData(self): + return (("New", "new.bmp", "Create new sketch", self.OnNew), + ("", "", "", ""), + ("Open", "open.bmp", "Open existing sketch", self.OnOpen), + ("Save", "save.bmp", "Save existing sketch", self.OnSave)) + + def createColorTool(self, toolbar, color): + bmp = self.MakeBitmap(color) + tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color) + self.Bind(wx.EVT_MENU, self.OnColor, tool) + + def MakeBitmap(self, color): + bmp = wx.EmptyBitmap(16, 15) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + dc.SetBackground(wx.Brush(color)) + dc.Clear() + dc.SelectObject(wx.NullBitmap) + return bmp + + def toolbarColorData(self): + return ("Black", "Red", "Green", "Blue") + + def OnNew(self, event): pass + + def OnColor(self, event): + menubar = self.GetMenuBar() + itemId = event.GetId() + item = menubar.FindItemById(itemId) + if not item: + toolbar = self.GetToolBar() + item = toolbar.FindById(itemId) + color = item.GetShortHelp() + else: + color = item.GetLabel() + self.sketch.SetColor(color) + + def OnCloseWindow(self, event): + self.Destroy() + + def SaveFile(self): + if self.filename: + data = self.sketch.GetLinesData() + f = open(self.filename, 'w') + cPickle.dump(data, f) + f.close() + + def ReadFile(self): + if self.filename: + try: + f = open(self.filename, 'r') + data = cPickle.load(f) + f.close() + self.sketch.SetLinesData(data) + except cPickle.UnpicklingError: + wx.MessageBox("%s is not a sketch file." % self.filename, + "oops!", style=wx.OK|wx.ICON_EXCLAMATION) + + wildcard = "Sketch files (*.sketch)|*.sketch|All files (*.*)|*.*" + + def OnOpen(self, event): + dlg = wx.FileDialog(self, "Open sketch file...", os.getcwd(), + style=wx.OPEN, wildcard=self.wildcard) + if dlg.ShowModal() == wx.ID_OK: + self.filename = dlg.GetPath() + self.ReadFile() + self.SetTitle(self.title + ' -- ' + self.filename) + dlg.Destroy() + + def OnSave(self, event): + if not self.filename: + self.OnSaveAs(event) + else: + self.SaveFile() + + def OnSaveAs(self, event): + dlg = wx.FileDialog(self, "Save sketch as...", os.getcwd(), + style=wx.SAVE | wx.OVERWRITE_PROMPT, + wildcard = self.wildcard) + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + if not os.path.splitext(filename)[1]: + filename = filename + '.sketch' + self.filename = filename + self.SaveFile() + self.SetTitle(self.title + ' -- ' + self.filename) + dlg.Destroy() + + def OnOtherColor(self, event): + dlg = wx.ColourDialog(frame) + dlg.GetColourData().SetChooseFull(True) + if dlg.ShowModal() == wx.ID_OK: + self.sketch.SetColor(dlg.GetColourData().GetColour()) + dlg.Destroy() + + def OnAbout(self, event): + dlg = SketchAbout(self) + dlg.ShowModal() + dlg.Destroy() + + +class SketchAbout(wx.Dialog): + text = ''' + + +
+ + + +

Sketch!

+
+

Sketch is a demonstration program for wxPython In Action +Chapter 7. It is based on the SuperDoodle demo included with wxPython, +available at http://www.wxpython.org/ +

+ +

SuperDoodle and wxPython are brought to you by +Robin Dunn and Total Control Software, Copyright +© 1997-2006.

+ + +''' + + def __init__(self, parent): + wx.Dialog.__init__(self, parent, -1, 'About Sketch', + size=(440, 400) ) + + html = wx.html.HtmlWindow(self) + html.SetPage(self.text) + button = wx.Button(self, wx.ID_OK, "Okay") + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5) + sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5) + + self.SetSizer(sizer) + self.Layout() + + + +class ControlPanel(wx.Panel): + + BMP_SIZE = 16 + BMP_BORDER = 3 + NUM_COLS = 4 + SPACING = 4 + + colorList = ('Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple', + 'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', + 'Goldenrod', 'Cyan', 'Orange', 'Navy', 'Dark Grey', + 'Light Grey') + maxThickness = 16 + + def __init__(self, parent, ID, sketch): + wx.Panel.__init__(self, parent, ID, style=wx.RAISED_BORDER) + self.sketch = sketch + buttonSize = (self.BMP_SIZE + 2 * self.BMP_BORDER, + self.BMP_SIZE + 2 * self.BMP_BORDER) + colorGrid = self.createColorGrid(parent, buttonSize) + thicknessGrid = self.createThicknessGrid(buttonSize) + self.layout(colorGrid, thicknessGrid) + + def createColorGrid(self, parent, buttonSize): + self.colorMap = {} + self.colorButtons = {} + colorGrid = wx.GridSizer(cols=self.NUM_COLS, hgap=2, vgap=2) + for eachColor in self.colorList: + bmp = parent.MakeBitmap(eachColor) + b = buttons.GenBitmapToggleButton(self, -1, bmp, size=buttonSize) + b.SetBezelWidth(1) + b.SetUseFocusIndicator(False) + self.Bind(wx.EVT_BUTTON, self.OnSetColour, b) + colorGrid.Add(b, 0) + self.colorMap[b.GetId()] = eachColor + self.colorButtons[eachColor] = b + self.colorButtons[self.colorList[0]].SetToggle(True) + return colorGrid + + def createThicknessGrid(self, buttonSize): + self.thicknessIdMap = {} + self.thicknessButtons = {} + thicknessGrid = wx.GridSizer(cols=self.NUM_COLS, hgap=2, vgap=2) + for x in range(1, self.maxThickness + 1): + b = buttons.GenToggleButton(self, -1, str(x), size=buttonSize) + b.SetBezelWidth(1) + b.SetUseFocusIndicator(False) + self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b) + thicknessGrid.Add(b, 0) + self.thicknessIdMap[b.GetId()] = x + self.thicknessButtons[x] = b + self.thicknessButtons[1].SetToggle(True) + return thicknessGrid + + def layout(self, colorGrid, thicknessGrid): + box = wx.BoxSizer(wx.VERTICAL) + box.Add(colorGrid, 0, wx.ALL, self.SPACING) + box.Add(thicknessGrid, 0, wx.ALL, self.SPACING) + self.SetSizer(box) + box.Fit(self) + + def OnSetColour(self, event): + color = self.colorMap[event.GetId()] + if color != self.sketch.color: + self.colorButtons[self.sketch.color].SetToggle(False) + self.sketch.SetColor(color) + + def OnSetThickness(self, event): + thickness = self.thicknessIdMap[event.GetId()] + if thickness != self.sketch.thickness: + self.thicknessButtons[self.sketch.thickness].SetToggle(False) + self.sketch.SetThickness(thickness) + + +class SketchApp(wx.App): + + def OnInit(self): + bmp = wx.Image("splash.png").ConvertToBitmap() + wx.SplashScreen(bmp, wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT, + 1000, None, -1) + wx.Yield() + + frame = SketchFrame(None) + frame.Show(True) + self.SetTopWindow(frame) + return True + +if __name__ == '__main__': + app = SketchApp(False) + app.MainLoop() diff --git a/Chapter-06/new.bmp b/Chapter-06/new.bmp new file mode 100644 index 0000000..d66feb2 Binary files /dev/null and b/Chapter-06/new.bmp differ diff --git a/Chapter-06/open.bmp b/Chapter-06/open.bmp new file mode 100644 index 0000000..1c38e97 Binary files /dev/null and b/Chapter-06/open.bmp differ diff --git a/Chapter-06/save.bmp b/Chapter-06/save.bmp new file mode 100644 index 0000000..9217b49 Binary files /dev/null and b/Chapter-06/save.bmp differ diff --git a/Chapter-06/splash.png b/Chapter-06/splash.png new file mode 100644 index 0000000..a2ae34c Binary files /dev/null and b/Chapter-06/splash.png differ diff --git a/Chapter-07/bitmap.bmp b/Chapter-07/bitmap.bmp new file mode 100644 index 0000000..b7eb51c Binary files /dev/null and b/Chapter-07/bitmap.bmp differ diff --git a/Chapter-07/bitmap_button.py b/Chapter-07/bitmap_button.py new file mode 100644 index 0000000..15a1777 --- /dev/null +++ b/Chapter-07/bitmap_button.py @@ -0,0 +1,25 @@ +import wx + +class BitmapButtonFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Bitmap Button Example', + size=(200, 150)) + panel = wx.Panel(self, -1) + bmp = wx.Image("bitmap.bmp", wx.BITMAP_TYPE_BMP).ConvertToBitmap() + self.button = wx.BitmapButton(panel, -1, bmp, pos=(10, 20)) + self.Bind(wx.EVT_BUTTON, self.OnClick, self.button) + self.button.SetDefault() + self.button2 = wx.BitmapButton(panel, -1, bmp, pos=(100, 20), + style=0) + self.Bind(wx.EVT_BUTTON, self.OnClick, self.button2) + + def OnClick(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = BitmapButtonFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-07/button.py b/Chapter-07/button.py new file mode 100644 index 0000000..009789a --- /dev/null +++ b/Chapter-07/button.py @@ -0,0 +1,21 @@ +import wx + +class ButtonFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Button Example', + size=(300, 100)) + panel = wx.Panel(self, -1) + self.button = wx.Button(panel, -1, "Hello", pos=(50, 20)) + self.Bind(wx.EVT_BUTTON, self.OnClick, self.button) + self.button.SetDefault() + + def OnClick(self, event): + self.button.SetLabel("Clicked") + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = ButtonFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-07/checkbox.py b/Chapter-07/checkbox.py new file mode 100644 index 0000000..d03ab83 --- /dev/null +++ b/Chapter-07/checkbox.py @@ -0,0 +1,16 @@ +import wx + +class CheckBoxFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Checkbox Example', + size=(150, 200)) + panel = wx.Panel(self, -1) + wx.CheckBox(panel, -1, "Alpha", (35, 40), (150, 20)) + wx.CheckBox(panel, -1, "Beta", (35, 60), (150, 20)) + wx.CheckBox(panel, -1, "Gamma", (35, 80), (150, 20)) + +if __name__ == '__main__': + app = wx.PySimpleApp() + CheckBoxFrame().Show() + app.MainLoop() + diff --git a/Chapter-07/choice.py b/Chapter-07/choice.py new file mode 100644 index 0000000..704fd3b --- /dev/null +++ b/Chapter-07/choice.py @@ -0,0 +1,16 @@ +import wx + +class ChoiceFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Choice Example', + size=(250, 200)) + panel = wx.Panel(self, -1) + sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', + 'six', 'seven', 'eight'] + wx.StaticText(panel, -1, "Select one:", (15, 20)) + wx.Choice(panel, -1, (85, 18), choices=sampleList) + +if __name__ == '__main__': + app = wx.PySimpleApp() + ChoiceFrame().Show() + app.MainLoop() diff --git a/Chapter-07/combo_box.py b/Chapter-07/combo_box.py new file mode 100644 index 0000000..3e36f25 --- /dev/null +++ b/Chapter-07/combo_box.py @@ -0,0 +1,19 @@ +import wx + +class ComboBoxFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Combo Box Example', + size=(350, 300)) + panel = wx.Panel(self, -1) + sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', + 'six', 'seven', 'eight'] + wx.StaticText(panel, -1, "Select one:", (15, 15)) + wx.ComboBox(panel, -1, "default value", (15, 30), wx.DefaultSize, + sampleList, wx.CB_DROPDOWN) + wx.ComboBox(panel, -1, "default value", (150, 30), wx.DefaultSize, + sampleList, wx.CB_SIMPLE) + +if __name__ == '__main__': + app = wx.PySimpleApp() + ComboBoxFrame().Show() + app.MainLoop() diff --git a/Chapter-07/gauge.py b/Chapter-07/gauge.py new file mode 100644 index 0000000..9a3b5cf --- /dev/null +++ b/Chapter-07/gauge.py @@ -0,0 +1,23 @@ +import wx + +class GaugeFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Gauge Example', + size=(350, 150)) + panel = wx.Panel(self, -1) + self.count = 0 + self.gauge = wx.Gauge(panel, -1, 50, (20, 50), (250, 25)) + self.gauge.SetBezelFace(3) + self.gauge.SetShadowWidth(3) + self.Bind(wx.EVT_IDLE, self.OnIdle) + + def OnIdle(self, event): + self.count = self.count + 1 + if self.count >= 50: + self.count = 0 + self.gauge.SetValue(self.count) + +if __name__ == '__main__': + app = wx.PySimpleApp() + GaugeFrame().Show() + app.MainLoop() diff --git a/Chapter-07/generic_button.py b/Chapter-07/generic_button.py new file mode 100644 index 0000000..e2a7605 --- /dev/null +++ b/Chapter-07/generic_button.py @@ -0,0 +1,56 @@ +import wx +import wx.lib.buttons as buttons + +class GenericButtonFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Generic Button Example', + size=(500, 350)) + panel = wx.Panel(self, -1) + + sizer = wx.FlexGridSizer(1, 3, 20, 20) + b = wx.Button(panel, -1, "A wx.Button") + b.SetDefault() + sizer.Add(b) + + b = wx.Button(panel, -1, "non-default wx.Button") + sizer.Add(b) + sizer.Add((10,10)) + + b = buttons.GenButton(panel, -1, 'Genric Button') + sizer.Add(b) + + b = buttons.GenButton(panel, -1, 'disabled Generic') + b.Enable(False) + sizer.Add(b) + + b = buttons.GenButton(panel, -1, 'bigger') + b.SetFont(wx.Font(20, wx.SWISS, wx.NORMAL, wx.BOLD, False)) + b.SetBezelWidth(5) + b.SetBackgroundColour("Navy") + b.SetForegroundColour("white") + b.SetToolTipString("This is a BIG button...") + sizer.Add(b) + + bmp = wx.Image("bitmap.bmp", wx.BITMAP_TYPE_BMP).ConvertToBitmap() + b = buttons.GenBitmapButton(panel, -1, bmp) + sizer.Add(b) + + b = buttons.GenBitmapToggleButton(panel, -1, bmp) + sizer.Add(b) + + b = buttons.GenBitmapTextButton(panel, -1, bmp, "Bitmapped Text", + size=(175, 75)) + b.SetUseFocusIndicator(False) + sizer.Add(b) + + b = buttons.GenToggleButton(panel, -1, "Toggle Button") + sizer.Add(b) + + panel.SetSizer(sizer) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = GenericButtonFrame() + frame.Show() + app.MainLoop() + diff --git a/Chapter-07/list_box.py b/Chapter-07/list_box.py new file mode 100644 index 0000000..e7438d7 --- /dev/null +++ b/Chapter-07/list_box.py @@ -0,0 +1,21 @@ +import wx + +class ListBoxFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'List Box Example', + size=(250, 200)) + panel = wx.Panel(self, -1) + + sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', + 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', + 'twelve', 'thirteen', 'fourteen'] + + listBox = wx.ListBox(panel, -1, (20, 20), (80, 120), sampleList, + wx.LB_SINGLE) + listBox.SetSelection(3) + +if __name__ == '__main__': + app = wx.PySimpleApp() + ListBoxFrame().Show() + app.MainLoop() + diff --git a/Chapter-07/radio.py b/Chapter-07/radio.py new file mode 100644 index 0000000..beb0efd --- /dev/null +++ b/Chapter-07/radio.py @@ -0,0 +1,32 @@ +import wx + +class RadioButtonFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Radio Example', + size=(200, 200)) + panel = wx.Panel(self, -1) + radio1 = wx.RadioButton(panel, -1, "Elmo", pos=(20, 50), style=wx.RB_GROUP) + radio2 = wx.RadioButton(panel, -1, "Ernie", pos=(20, 80)) + radio3 = wx.RadioButton(panel, -1, "Bert", pos=(20, 110)) + text1 = wx.TextCtrl(panel, -1, "", pos=(80, 50)) + text2 = wx.TextCtrl(panel, -1, "", pos=(80, 80)) + text3 = wx.TextCtrl(panel, -1, "", pos=(80, 110)) + self.texts = {"Elmo": text1, "Ernie": text2, "Bert": text3} + for eachText in [text2, text3]: + eachText.Enable(False) + for eachRadio in [radio1, radio2, radio3]: + self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, eachRadio) + self.selectedText = text1 + + def OnRadio(self, event): + if self.selectedText: + self.selectedText.Enable(False) + radioSelected = event.GetEventObject() + text = self.texts[radioSelected.GetLabel()] + text.Enable(True) + self.selectedText = text + +if __name__ == '__main__': + app = wx.PySimpleApp() + RadioButtonFrame().Show() + app.MainLoop() diff --git a/Chapter-07/radio_box.py b/Chapter-07/radio_box.py new file mode 100644 index 0000000..c7ae037 --- /dev/null +++ b/Chapter-07/radio_box.py @@ -0,0 +1,19 @@ +import wx + +class RadioBoxFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Radio Box Example', + size=(350, 200)) + panel = wx.Panel(self, -1) + sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', + 'six', 'seven', 'eight'] + wx.RadioBox(panel, -1, "A Radio Box", (10, 10), wx.DefaultSize, + sampleList, 2, wx.RA_SPECIFY_COLS) + + wx.RadioBox(panel, -1, "", (150, 10), wx.DefaultSize, + sampleList, 3, wx.RA_SPECIFY_COLS | wx.NO_BORDER) + +if __name__ == '__main__': + app = wx.PySimpleApp() + RadioBoxFrame().Show() + app.MainLoop() diff --git a/Chapter-07/slider.py b/Chapter-07/slider.py new file mode 100644 index 0000000..65945b9 --- /dev/null +++ b/Chapter-07/slider.py @@ -0,0 +1,22 @@ +import wx + +class SliderFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Slider Example', + size=(300, 350)) + panel = wx.Panel(self, -1) + self.count = 0 + slider = wx.Slider(panel, 100, 25, 1, 100, pos=(10, 10), + size=(250, -1), + style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) + slider.SetTickFreq(5, 1) + slider = wx.Slider(panel, 100, 25, 1, 100, pos=(125, 70), + size=(-1, 250), + style=wx.SL_VERTICAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) + slider.SetTickFreq(20, 1) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = SliderFrame() + frame.Show() + app.MainLoop() diff --git a/Chapter-07/spinner.py b/Chapter-07/spinner.py new file mode 100644 index 0000000..92995fa --- /dev/null +++ b/Chapter-07/spinner.py @@ -0,0 +1,15 @@ +import wx + +class SpinnerFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Spinner Example', + size=(100, 100)) + panel = wx.Panel(self, -1) + sc = wx.SpinCtrl(panel, -1, "", (30, 20), (80, -1)) + sc.SetRange(1,100) + sc.SetValue(5) + +if __name__ == '__main__': + app = wx.PySimpleApp() + SpinnerFrame().Show() + app.MainLoop() diff --git a/Chapter-07/static_text.py b/Chapter-07/static_text.py new file mode 100644 index 0000000..66ae3df --- /dev/null +++ b/Chapter-07/static_text.py @@ -0,0 +1,38 @@ +import wx + +class StaticTextFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Static Text Example', + size=(400, 300)) + panel = wx.Panel(self, -1) + wx.StaticText(panel, -1, "This is an example of static text", + (100, 10)) + rev = wx.StaticText(panel, -1, "Static Text With Reversed Colors", + (100, 30)) + rev.SetForegroundColour('white') + rev.SetBackgroundColour('black') + center = wx.StaticText(panel, -1, "align center", (100, 50), + (160, -1), wx.ALIGN_CENTER) + center.SetForegroundColour('white') + center.SetBackgroundColour('black') + right = wx.StaticText(panel, -1, "align right", (100, 70), + (160, -1), wx.ALIGN_RIGHT) + right.SetForegroundColour('white') + right.SetBackgroundColour('black') + str = "You can also change the font." + text = wx.StaticText(panel, -1, str, (20, 100)) + font = wx.Font(18, wx.DECORATIVE, wx.ITALIC, wx.NORMAL) + text.SetFont(font) + wx.StaticText(panel, -1, "Your text\ncan be split\n" + "over multiple lines\n\neven blank ones", (20,150)) + wx.StaticText(panel, -1, "Multi-line text\ncan also\n" + "be right aligned\n\neven with a blank", (220,150), + style=wx.ALIGN_RIGHT) + + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = StaticTextFrame() + frame.Show() + app.MainLoop() + diff --git a/Chapter-07/text_ctrl.py b/Chapter-07/text_ctrl.py new file mode 100644 index 0000000..ada733b --- /dev/null +++ b/Chapter-07/text_ctrl.py @@ -0,0 +1,25 @@ +import wx + +class TextFrame(wx.Frame): + + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Text Entry Example', + size=(300, 100)) + panel = wx.Panel(self, -1) + basicLabel = wx.StaticText(panel, -1, "Basic Control:") + basicText = wx.TextCtrl(panel, -1, "I've entered some text!", + size=(175, -1)) + basicText.SetInsertionPoint(0) + + pwdLabel = wx.StaticText(panel, -1, "Password:") + pwdText = wx.TextCtrl(panel, -1, "password", size=(175, -1), + style=wx.TE_PASSWORD) + sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6) + sizer.AddMany([basicLabel, basicText, pwdLabel, pwdText]) + panel.SetSizer(sizer) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = TextFrame() + frame.Show() + app.MainLoop() diff --git a/Chapter-07/text_ctrl_multiple.py b/Chapter-07/text_ctrl_multiple.py new file mode 100644 index 0000000..04abf7f --- /dev/null +++ b/Chapter-07/text_ctrl_multiple.py @@ -0,0 +1,33 @@ +import wx + +class TextFrame(wx.Frame): + + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Text Entry Example', + size=(300, 250)) + panel = wx.Panel(self, -1) + multiLabel = wx.StaticText(panel, -1, "Multi-line") + multiText = wx.TextCtrl(panel, -1, + "Here is a looooooooooooooong line of text set in the control.\n\n" + "See that it wrapped, and that this line is after a blank", + size=(200, 100), style=wx.TE_MULTILINE) + multiText.SetInsertionPoint(0) + + richLabel = wx.StaticText(panel, -1, "Rich Text") + richText = wx.TextCtrl(panel, -1, + "If supported by the native control, this is reversed, and this is a different font.", + size=(200, 100), style=wx.TE_MULTILINE|wx.TE_RICH2) + richText.SetInsertionPoint(0) + richText.SetStyle(44, 52, wx.TextAttr("white", "black")) + points = richText.GetFont().GetPointSize() + f = wx.Font(points + 3, wx.ROMAN, wx.ITALIC, wx.BOLD, True) + richText.SetStyle(68, 82, wx.TextAttr("blue", wx.NullColour, f)) + sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6) + sizer.AddMany([multiLabel, multiText, richLabel, richText]) + panel.SetSizer(sizer) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = TextFrame() + frame.Show() + app.MainLoop() diff --git a/Chapter-08/frame_subclass.py b/Chapter-08/frame_subclass.py new file mode 100644 index 0000000..6662720 --- /dev/null +++ b/Chapter-08/frame_subclass.py @@ -0,0 +1,21 @@ +import wx + +class SubclassFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Frame Subclass', + size=(300, 100)) + panel = wx.Panel(self, -1) + button = wx.Button(panel, -1, "Close Me", pos=(15, 15)) + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + SubclassFrame().Show() + app.MainLoop() diff --git a/Chapter-08/help_context.py b/Chapter-08/help_context.py new file mode 100644 index 0000000..3a7aed9 --- /dev/null +++ b/Chapter-08/help_context.py @@ -0,0 +1,16 @@ +import wx + +class HelpFrame(wx.Frame): + + def __init__(self): + pre = wx.PreFrame() + pre.SetExtraStyle(wx.FRAME_EX_CONTEXTHELP) + pre.Create(None, -1, "Help Context", size=(300, 100), + style=wx.DEFAULT_FRAME_STYLE ^ + (wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX)) + self.PostCreate(pre) + +if __name__ == '__main__': + app = wx.PySimpleApp() + HelpFrame().Show() + app.MainLoop() diff --git a/Chapter-08/images.py b/Chapter-08/images.py new file mode 100644 index 0000000..19d7806 --- /dev/null +++ b/Chapter-08/images.py @@ -0,0 +1,1424 @@ +#---------------------------------------------------------------------- +# This file was generated by encode_bitmaps.py +# +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + + +def getVippiData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01^\x00\x00\x01O\x08\x06\x00\ +\x00\x00\x05\xa1\xa6$\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00 \ +\x00IDATx\x9c\xec\x9dy`T\xd5\xd9\xff?\xe7\xced%!\x0b\x81@ \x10@\xf6M\x84\x80\ +"(\xae\x05\x14\x85\xba\xaf\xa8m\xad\xbb\xb5\xaf\xbe\xb6\xda\xfe\xaa]\xb4X\ +\xdf\xd6\xd6\xd6\x05\xad\xadR\xb7\x96\x8a\xe2\x8e; \n\x02\x82 \n\xc8\x1e aMH\ +B\x96\x99{\xcf\xef\x8fsg\xcdd\x9b\xcc\x96\xe4|tH\xe6n\xe7\xdc\x9b\xb9\xdfy\ +\xees\x9e\xe79B\x18\x0e:\x13\xd22\xa5\xff{a8D\xac\xda\x8cE[\x1a\x8d&\xf1q\ +\xc6\xbb\x03\xb1"Xp!\xf6B\xa8\x05X\xa3\xd1@\'\x10\xdeD\x10\xdc`\xb4\x00k4\ +\x9d\x1b\xd1Q]\r\x89,\xb8\xfe\xc4\xbbO\x1a\x8d&\xf6t8\xe1\r%n\x908\x02\xa7\ +\xc5W\xa3\xd1t\x18\xe1Mt\xc1\r&\x1e\x83|\x1a\x8d&1\xe8\x10\xc2\xdb^\xadH-\ +\xbe\x1aM\xe7\xa4]\x0bo{\xb3rC\xa1\xc5W\xa3\xe9|\xb4[\xe1m\xafVn(:\xd2\xb9h4\ +\x9a\xe6iw\xc2\xdb\x11\xac\xdc\xc6\xd0\xd6\xafF\xd390\xe2\xdd\x81\xd6\xd0\ +\x91E\x17\x1a\x9eGc\xe7\xab\xd1h\xda7\xedFx;\xba\xe8z\xd0\xe2\xab\xd1t|\xda\ +\x8d\xf0\x06#\x0c\x87\xe8h\xa2\xeb\xa1\xa3\x9e\x97F\xa3Q\xb4\x0b\xe1\xed\x8c\ +\xbeO\xffs\xd4V\xafF\xd3\xb1hW\xb5\x1a:\x83\xe0\xfa\xe39_-\xbc\x1aM\xc7\xa2\ +\xddE5h4\x1aM{\xa7]Y\xbc\xe1\xd2\x1a\x8b1ZVug\x19\x1c\xd4h4\xcd\xd3\xa1-\xde\ +\xb6>\xa2\x87#\x8a\x91v\x0bha\xd6h:\x1e\x1dNx\xa3\xe5\x0fmJ\x00c\xe9\x83\xd5\ +B\xac\xd1\xb4\x7f:\x8c\xf0\xb6D\xfcrss\xb9\xf8\xe2\x8b9\xfd\xf4\xd3\xc9\xc9\ +\xc9\x01\xa0\xb6\xb6\x96g\x9f}\x96\xf7\xde{\x8f\x8a\x8a\x8a&\xf7O\xb4\x18[-\ +\xc2\x1aM\xfb\xa4\xdd\x0bos\xe2\x97\x9b\x9b\xcb\xe5\x97_\xce\xa9\xa7\x9e\xca\ +I\'\x9dDAA\x01\x00B(\xcd\x92R\xed~\xe8\xd0!^~\xf9e^x\xe1\x05V\xacX\x81i\x9a\ +\x11\xebcff&III\x8c\x1e=\x9a^\xbdz1b\xc4\x08\x00\n\n\n(**b\xd7\xae]\x94\x94\ +\x94\xb0y\xf3fv\xed\xda\xc5\xa1C\x87\xd8\xb2e\x0b\xb5\xb5\xb5-:\xbe\x16`\x8d\ +\xa6}\xd1n\x85\xb7)\xc1\x15B0f\xcc\x18\xae\xbb\xee:\xe6\xcc\x99Cff\xa6wy\xa3\ +\xc7\xb3\x05\xd8\xb2,\xbe\xf9\xe6\x1b\x96.]\xca\xd2\xa5Ky\xff\xfd\xf79p\xe0@\ +\x8b\xfb\x95\x9c\x9cLZZ\x1a\x93&Mb\xca\x94)\x9cz\xea\xa9\x1c\x7f\xfc\xf1\x18\ +\x86AJJJ@\x1f\xa5\x94\r~\x02\x98\xa6Iii)\x1b7nd\xd1\xa2E,[\xb6\x8c\r\x1b6\ +\xb4\xe8\xcb@\x8b\xb0F\x93\xf8\xb4;\xe1mNp\xc7\x8f\x1f\xcf/~\xf1\x0b\xa6O\ +\x9f\x8e\xd3\xe9lRl\x9bl\xc7\x16\xc1\xd2\xd2R\xa6O\x9f\xce\xbau\xeb\x9a\xdc>\ +%%\x85K.\xb9\x84{\xee\xb9\x87\xa2\xa2"\x92\x93\x93\xbd}j\x0bRJL\xd3d\xd5\xaa\ +U,]\xba\x94\xf9\xf3\xe7\xb3~\xfd\xfa&\xf7\xd1\xe2\xab\xd1$6\xedFx\x9b\x13\ +\xdc\xe2\xe2b\x1e|\xf0AN9\xe5\x14\x1c\x0eG\x9b\x05\xcf\xdb\xae\x94\xbc\xf0\ +\xc2\x0b\\y\xe5\x95!\xd7\xf7\xef\xdf\x9f\x193fp\xf3\xcd73t\xe8Po\x7f\xa2\x81\ +\x94\x12\x97\xcb\xc5\x92%Kx\xe3\x8d7\xf8\xe4\x93O\xd8\xb0a\x03n\xb7\xbb\xc1\ +\xb6Z|5\x9a\xc4\xa5]\x08oS\xa2[TT\xc4}\xf7\xdd\xc7\xe5\x97_\xde&\x0b\xb7)v\ +\xef\xde\xcd\xa8Q\xa3\x1a\x0c\xbe\x15\x17\x17\xf3\xe6\x9bo\x92\x97\x97\x07DO\ +p\x83\xf1wI,^\xbc\x98\x9f\xfc\xe4\'l\xd9\xb2\xa5\xc1vZ|5\x9a\xc4$\xe1k54&\ +\xbaIII\xfc\xf0\x87?d\xe5\xca\x95\\}\xf5\xd5$%%EM\xf8\xfa\xf4\xe9\xc3\x85\ +\x17^\xd8`\xf9\x19g\x9cA^^\x1eB\x88\x98\x89.\xe0m\xcf\xe9t2}\xfat>\xff\xfcsf\ +\xcd\x9a\xd5`;i\x992\xde\x91\x17\x1a\x8d\xa6!\t+\xbcM\x89FQQ\x11\x0b\x17.\ +\xe4\xc9\'\x9f\xf4\n_\xb49\xfb\xec\xb3\x1b,[\xbdzuL\x057\x14B\x08rrrx\xfe\ +\xf9\xe7\x99={v\\\xfb\xa2\xd1hZFB\no\xa3\xe9\xb5Bp\xe5\x95W\xb2b\xc5\nf\xcc\ +\x98\x81a\x181\x11>!\x8472!xy" \x84 --\x8d\x7f\xfd\xeb_\x8dZ\xbeq\xe8\x96F\ +\xa3i\x84\x84\x13\xde\xc6D\xc20\x0c\xfe\xfa\xd7\xbf\xf2\xcc3\xcf\xd0\xbd{\ +\xf7\x98\x8b^QQ\x11\x85\x85\x85\x01\xcb,\xcb\xf2\xfa[\xe3\x8dG|\x9f\x7f\xfey\ +-\xbe\x1aM\x82\x93P\xc2\xdb\x988\xe4\xe4\xe4\xb0h\xd1"n\xbc\xf1\xc6\x88\xf9r\ +\x83\x05SJ\xd9\xa4\x88\x86\x8a\x94X\xb9re\x8b\x93\x1cb\x81\xbf\xf8N\x9d:5\ +\xde\xdd\xd1h4\x8d\x900\xd5\xc9\x1a\x13\xdd\xdc\xdc\\^\x7f\xfduN:\xe9\xa46\t\ +\xae\x94\x12\xb7\xdb\xcd\x8a\x15+8z\xf4(G\x8e\x1c\xe1\x9bo\xbe\xf1\xaeOKKc\ +\xec\xd8\xb1\x0c\x1e<\x98\xfe\xfd\xfb{\xdd\nM\xb5\x99H\x16\xaf\x07\x8f\xf8>\ +\xf9\xe4\x93L\x980! \x12CZ\xa6\xd4\x91\x0e\x1aM\xfcI\x08\xe1mLt\xfb\xf4\xe9\ +\xc3\xe2\xc5\x8b\x19:th\x9bE\xf7\xe0\xc1\x83\xdcu\xd7]\xcc\x9f?\x1f\xcb\xb2\ +\x1a\xdd6%%\x85Q\xa3F1c\xc6\x0cf\xcd\x9a\xc5\x88\x11#HJJ\xc20\x0cF\x8e\x1c\ +\xc9\x8e\x1d;\xc2\xeeG\xac\x10B0h\xd0 ~\xf3\x9b\xdfp\xdbm\xb7\x05\xac\xd3\ +\xe2\xab\xd1\xc4\x9f\xb8\xbb\x1a\x1a\x13\xdd!C\x86\xb0p\xe1\xc26\x8b.@EE\x05\ +3f\xcc\xe0\xd9g\x9fmRt\x01\xea\xea\xeaX\xb5j\x15\xbf\xfe\xf5\xaf\x19?~k\xd6\xac\tXo\x9a\ +&\x87\x0f\x1f\xe6\x1f\xff\xf8\x07\xcf=\xf7\x1c\xb3f\xcd\xe2\x81\x07\x1e`\xd0\ +\xa0A1\x13_\x8f\xcb!Xx5\x1aM\xfc\x88\x8b\xc5\xdb\x98\xb5{\xd5UW\xf1\xf4\xd3O\ +{+{\xb5\xa9\r\xfb\xb1\x7f\xc8\x90!\xec\xdf\xbf\x1f\x00Gf&C\x9e~\x9a\xbc\x0b/\ +\x84f\xd2|\xa5\x94`Y\x1c]\xbe\x9cm?\xfb\x19G\x97/o]\x07\xec\xf03\t\xe0W\xc4&\ +55\x95\xb9s\xe7r\xeb\xad\xb7\x02\xb1I\xc2X\xb3f\r\xe3\xc6\x8d\x0bX\xa6-^\x8d\ +&~\xc4\xdc\xe2mLt\xc7\x8d\x1b\xc7\xbcy\xf3""\xba\x1e\xe6\xcd\x9b\xe7\x15]\ +\x84`\xc8\xbcy\xe4]tQ\x80\xd8I)\xc1\xed\xa6f\xebV\xac\xba:\xd2\x07\x0eD\xa4\ +\xa6*\xe1t8\xe8:y2c>\xfc\x90\x83\x0b\x16\xb0\xf5\xee\xbb\xa9\xdf\xb3\'d[\x8e\ +\xae]\xe9>{6\xa9\x03\x07\x929v,\xe9\xc3\x86\xe1\xcc\xcd\x05\xb7\x9b\xc3\xef\ +\xbe\xcb\x81\x05\x0b8\xf4\xd6[\xd4\xd6\xd6r\xc7\x1dw\x00p\xcb-\xb7\xc4\xcd\ +\xf7\xab\xa3\x1b4\x9a\xf8\x11s\x8b7\x94\xf0&\'\'\xb3d\xc9\x12&L\x98\x101!\ +\xaa\xad\xade\xd4\xa8Q|\xf7\xddw\x00\xa4\r\x1aD\xf1\x86\r\x08?a\x97Rb\x1e9\ +\xc27W\\\xc1\x91\x0f>@\x9a&\x8e\xccL\xba\x0c\x1dJ\xc1\x8d7\xd2\xfd\x92K\x10)\ +)\xde"\xe5\xdf^~9\xfb_z\xa9A[\xd9\xa7\x9f\xce\xf0\xe7\x9f\xc7\x99\x9f\xdfh\ +\xff\xa5eq\xe4\xed\xb7\xf9\xfa\xd2K\xb1\xaa\xaap8\x1c<\xf2\xc8#\xdc|\xf3\xcd\ +Q\x17\xdf\x92\x92\x12\x06\x0f\x1eLMMM\xc0r-\xbc\x1aM|\x88i8Yc\xd6\xee\x9dw\ +\xde\x19Q\xd1\x95R\xf2\xdf\xff\xfe7 \x02!\xb9\xa0\x00\x91\x94\x14\xb0\x9du\ +\xec\x18\xdf^s\r\x87\xdfy\x07\xe9r\x81eaVTpt\xc5\n\xbe\xbd\xe6\x1a\xd6\x9dy&\ +\xa6\x9d\x80`VTP\xb9zu\xc0\xfe"%\x85\x01\x0f=\xc4\xa8\xb7\xdejRt\x01\x84a\ +\x903c\x06\xc3\xff\xf5/\x10\x02\xd34\xb9\xf7\xde{\xd9\xb0aC\xd4\x930z\xf4\ +\xe8\xe1\x8d\xbc\xd0h4\xf1\'\xeeq\xbc\xc7\x1dw\x9c\xf7\xd1;\x12H)\xd9\xb3g\ +\x0fw\xdduW\x80\xa0\xf5\xb8\xf0Bd\xd0v{\xff\xfaW\x0e\xbd\xfez\xa3\xc7:\xfa\ +\xe9\xa7\x1cy\xfbm\xa4\xdb\xcd\xf6{\xee\xa1&\xa8\xe6m\xf6\x94)\x14\xdey\'\ +\x86m\x157\x87\x10\x82n3g\xd2e\xe4Hu\xfc\xa3G\x993gN\xd4\xd3\x8e\x1d\x0eG\ +\x83:\x13\x1a\x8d&~\xc4=\xaa\xe1\xfa\xeb\xaf\xa7[\xb7n\x11}\xdc\xbe\xf7\xde{\ +\xd9\xb7o\x9fo\x81\xc3A\xde\xf9\xe7\x07\xb6Q_\xcf\xde\xa7\x9ej\xf6X{\x9fx\ +\x02wy9{\x9fx\xa2\xc1\xba\xa4\xee\xdd\x01;\x1d\xb9\xac\x8c\n\xbf\xc8\x01##\ +\x83\xec\xd3N\xf3\xba*\x1d\xe1WR\xb2\xf0\xa7?\xe5\xe0+\xaf\ +P\xb5v-\x96e\xf1\xd5W_Q\\\\\xdc\x8a3\xd4h4\xed\x99\xb8\xba\x1a\xee\xbc\xf3N\ +\xd2\xd3\xd3#n\xe9\xf5\xe8\xd1#\xe0}r\xcf\x9e8sr\xbc\xef\x05\xb0\xff\xe5\x97\ +\x1b\x8d\xcf\r\xc0\xed\xc6\n\xe1\nH\xee\xdd\x9b\xdc\x193\xd8q\xcf=l\xb9\xe9\ +\xa6\x06\xa2\x0bP\xbd~=\xeb\xcf?\x9f\xefn\xbe\x19\xeb\xd81\xefl\xc2"=\x9d\ +\xc1\x8f?\xae\xa2\'\x80\x8f>\xfa\xa8u\'\xa8\xd1h\xda5q\x13\xde\xc2\xc2B\xae\ +\xb8\xe2\x8a\x88\x8b\xae\x10\x82\x81\x03\x07\x06,\xeb:a\x02\x86\xdf\xe0\x924\ +M\xdcA\xf3\xa7\xb5\xb2\x11\x06>\xf4\x10\xae\xfd\xfb\xd9\xf3\xb7\xbf5\xbd\xad\ +i\xb2\xf7\x89\'\xd8v\xcf=\xde\x04\r!\x04\x19c\xc7\xe2\xb4k?\x94\x95\x95\x85\ +\xdf\x17\x8dF\xd3\xee\x88\x89\xf0\x86r3\\u\xd5UQ\x19i\x97R2j\xd4\xa8\x80e\ +\x8e\xf4\xf4\x80\x815WY\x19\xd5\x1b7\x86\xddF\xd6\xe4\xc9\xf4\xb8\xf4R\xf6\ +\xbf\xf4\x122\xc4\x0c\xbf\xa1\xa8\xdf\xb3\'\xc0\xdd\xe1:t\x083(\xbcK\xa3\xd1\ +t\x0e\xe2b\xf1\x1a\x86\xc1Yg\x9d\x15\x95c\x0b!\xc8\xcf\xcf\x0fXV\xb1|9\xd4\ +\xd7\xfb\xdaOM\xc5\x91\x99\x19^\x03\x0e\x07\xfd~\xf1\x0b\x10\x82\xaa\xb5k[\ +\xbc[\xfa\xb0a^\xd7\x86\x94\x92#\xef\xbf\x8fU]\xad\xfac\xc4=\xb8D\xa3\xd1\ +\xc4\x90\xb8\xdc\xf13f\xcc`\xca\x94)Q\x1b\xc5?\xf1\xc4\x13)((\xf0\xbe\xaf\ +\xdd\xbe\x9dC\xaf\xbf\xee\r/s\xe4\xe62p\xee\\p\xb4>y\xa4\xf7\r7\x90}\xe6\x99\ +\x00-\xb6v\x01\x92\xf2\xf2\xc0N\xc4\xb0\xaa\xab\xd9\xf3\xe8\xa3\x01\xfd\x8d&\ +\x96eQYY\x19\xb0L\'Oh4\xf1#.\xc2{\xc6\x19g\xe0\x08C\xf4ZJVV\x16\x17]tQ\xc0\ +\xb2\x9d\x0f>\x88\xb4\x1f\xed\x85\x10\xe4_s\r\x03\xff\xf0\x07h\x85\xb5\xe9\ +\xc8\xca\xa2\xdf/\x7f\xa9\x04\x14\xe89gN\x8b\xf6K\xe9\xd3\x87<\xbfy\xd0\xca?\ +\xfc\x90\xcaU\xab\xd41\x1d\x8e\xa8\xd7\xcc\xad\xa8\xa8`\xbd\x1d\xbe\xa6\xd1h\ +\xe2O\xd4\x857\xd8\xbfk\x18\x06\xe7\x9e{nT\xdb\x14Bp\xcb-\xb7\x04\xd4}\xa8Z\ +\xb3\x86]\x0f>\x88\xb4\x0b\xa1\x0b\xc3\xa0\xf7\xed\xb7\xb7J|\xb3&M\xc2iO\xb4\ +)\x84 w\xe6L\xf2\x9a\x99R=\xb9woF\xbd\xf5\x16\xc9v\x02\x83\x00\xb6\xfd\xfc\ +\xe7\xde\xf5\xf9\xf9\xf9L\x9c81j\xd6\xbf\x94\x92\xc5\x8b\x17s\xf4\xe8\xd1\ +\xa8\x1c_\xa3\xd1\xb4\x9e\x98[\xbc\x85\x85\x85\x14\x15\x15E\xbd\x9d\x01\x03\ +\x060c\xc6\x8c\x80e\xbb\xe6\xce\xa5|\xf1b\xaf\xcbA\x08A\xef\x9f\xfc\xa4\xc5\ +\xe2\x9b\x92\x9f\x1f0@&\x92\x92\x18\xfc\xe4\x93dM\x9e\xdc\xc0m\xe1\xc8\xca"o\ +\xf6lN\xf8\xec3\xd2G\x8e\xf4\xd6{\xa8\\\xb5\x8a\x9a\xcd\x9b\xbd\xdb\xfd\xe2\ +\x17\xbf\x88j:\xaf\xcb\xe5\xe2\xf7\xbf\xff}\xd4\x8e\xaf\xd1hZO\xd4\x8b\xe4\ +\x04[\xbcW_}5\xff\xfc\xe7?\xa3^\x18FJ\xc9\xae]\xbb\x18=zt\x80\xb5\xe7\xc8\ +\xc8`\xf8\xcb/\x933m\x9a\xb74\xa4\x94\x92\x8d\xb3gs\xf0\xb5\xd7\x9a\x9c\xd5\xabW\x93j\xc7\xf3F\x1a)%O?\xfd4\ +\xd7_\x7f}\xc0r\xed\xdf\xd5h\xe2K\xcc-\xdeh\x0f$y\x10B\xd0\xb7o_n\xbc\xf1\ +\xc6\x80\xe5fU\x15\xeb\xcf;\x8f\xefn\xbd\x15\xf3\xf0a\xafp\x1aii\xcd\x1e\xb3\ +f\xdb6\xca?\xfe8\xa0\x06\x84\x10\x02\x84 m\xc8\x10\xba\x8c\x1e\xed}9\xb2\xb2\ +\xbc\xeb<\xa2\xbb\xfdg?\xf3\x8anAA\x01/\xbd\xf4\x12)))\x11=o\x0f\x9ez\xc4\ +\xf7\xdf\x7f\x7fT\x8e\xaf\xd1h\xc2\'\xe6\xc2\x9b\x96\x96\x16\xd3\xd9\x17~\ +\xfb\xdb\xdfr\xce9\xe7\x04\xae0M\xf6>\xf6\x18+G\x8c`\xef_\xfeB\xe9\xd3OS\xbe\ +dI\xb3\xc7\x93uul\xbb\xfbn\xcc#GZ\\QLJI\xf5\x9a5|5m\x9a7\xd9"55\x95\xc7\x1e{\ +\x8c\x91\xb6\x0b"\x1a\xb8\\.\xee\xb8\xe3\x0e\xf6\x04\xd5\x0f\xd6\xd6\xaeF\ +\x13\x7fb\xeejX\xb7n\x1d\xa3G\x8f\x8ej\x9b\x01\xedK\xc9\x81\x03\x07\xb8\xf9\ +\xe6\x9bY\xb0`AD\x8e\x99;c\x06\xc3\x9e}\x16G#\xc5}\xa4\x94 %\xf5{\xf6P\xfa\ +\xcc3\xec\xfc\xfd\xef\x91v\xdaqrr2\xf3\xe7\xcf\xe7\xa2\xa0\x82\xec\x91\xc44M\ +\x1e{\xec1n\xbf\xfd\xf6@\xeb\\\x8b\xaeF\x93\x10tx\xe1\x05%\x84\xc7\x8e\x1d\ +\xe3o\x7f\xfb\x1b\xf7\xde{/\xeeV\xc4\xdf\xa6w\x07\xc3\x01U\xa5\x81\xcbS\xfa\ +\xf4\xa1\xe8W\xbf"\xef\x82\x0bpfg\x83\x94X\xf5\xf5X\xb5\xb5\x1cz\xf5U*>\xfd\ +\x94\xb2\x17_\xf4&I\x00\xa4\xa4\xa4\xf0\xec\xb3\xcfr\xf1\xc5\x17GEt\xa5\x94H\ +)y\xf8\xe1\x87\xb9\xe7\x9e{\x1aN\xf0\xa9\x85W\xa3I\x08b.\xbcK\x97.e\xf2\xe4\ +\xc9Qm3d?lQ\x9a7o\x1e\xb7\xdezk\x8b\xc4\xf7\xb8\xb3`\xf6\xb3p\xec \xcc\x9b\ +\x08\xae\x10\x19\xbe)}\xfb\xd2e\xe8P,\x97\x8b\xaa\xf5\xeb\x91n7fyy\x83\xed\n\ +\n\nx\xfc\xf1\xc7\x999sf\xd4D\xf7\xf0\xe1\xc3\xdcw\xdf}<\xf1\xc4\x13\r\xceO\ +\x8b\xaeF\x9388\x84\x88\xb2\x9bW\xca\xfb\xfc\xdf\x16\x17\x17\xc7\xa5\x04\xa2\ +\'\xf6\xf6\x84\x13N\xa0[\xb7n\xbc\xfb\xee\xbb\x8d\xfai\r\'L\xfb?\xf8\xde#\ +\x90\xd4UY\xbd\xd9\x05\xf0m\x88\x9a\xe9fE\x055[\xb7R\xbbc\x87\xaa@\x16\xa2\ +\x92\xd9\xa4I\x93x\xf1\xc5\x17\xa3\x96\xad\'\xa5d\xcb\x96-\xcc\x9e=\x9b\xd7^\ +{\r\xcb\x8eU\xf6\xa0EW\xa3I,b>\xb8\xb6}\xfb\xf6\xa8Ou\x13\n)%\x96e\xf1\xe9\ +\xa7\x9f\xf2\xa7?\xfd\xa9\x818yH\xef\x0eW\xbf\x03\xc5\xb7\x83\x91\xa4\xc2v\ +\x85\x01\xa3\xae\x81\xd3~\x89o\x0e\xf8\x16\xe0p8\xb8\xf5\xd6[\xf9\xe4\x93O\ +\x185jTDE\xd7c\xc1\x97\x96\x96r\xc7\x1dw0q\xe2DV\x07MM\x04Zt5\x9aD$\xe6\xae\ +\x86\xd1\xa3G\xb3l\xd922\xc3-R\xd3\xda\xf6m\x91?x\xf0 \xf3\xe6\xcd\xe3\xfe\ +\xfb\xef\xc7\xe5r\x85\xdc\xb6\xe0\x04\xb8\xe8e\xc8\x1a\x18\x90\'\xe1;\x96\ +\x1b\xde\xff_X\xfeg \xb4n{),,d\xfe\xfc\xf9L\x992%\xa2Ep<\x82\xbbw\xef^\xfe\ +\xf0\x87?\xf0\xc2\x0b/p0D-`\x0fZx5\x9a\xc4#\xe6\xc2\x0b\xf0\xcb_\xfe\x92\xfb\ +\xef\xbf?\xaaae\x1e\xc1\xdd\xb7o\x1f\x0f?\xfc0\x0b\x17.d\xc7\x8e\x1d\x8dn?h:\ +\xccz\x06\xd2\xf2C\x8b\xae\xf7\xb8\x16|\xbb\x00\x16^\x07\xae\xea\xc6\xb7\x1b\ +0`\x00\'\x9ex"c\xc6\x8ca\xea\xd4\xa9\x8c\x1f?\xde\xeb\xeeh\xedy\xf8[\xb7\xaf\ +\xbe\xfa*\x8b\x16-b\xe9\xd2\xa5\rf\r\x0e\x85\x16^\x8d&\xf1\x88\x8b\xf0&\'\'\ +\xf3\xd4SOq\xe5\x95W\x86%FM\xb6g\x0b\xee\x17_|\xc1\x03\x0f<\xc0\x92%K8r\xe4H\ +\xa3\xdb\'g\xc0\xe4\xbb\xe0\xe4\x9f\x81HjZt}\x8d\xc0\x81\xaf\xe0?\x97\xc1\ +\x81o\x9a\xdf<))\x89\xef\x7f\xff\xfbL\x9b6\x8d\xc9\x93\'\x93\x94\x94D\x9f>}\ +\x1a\x9cwUU\x15\xfb\xf7\xef\x07`\xcb\x96-\x1c:t\x88\xca\xcaJ^}\xf5Uv\xed\xda\ +EII\tUUU-\xe8\xa0\x0f-\xbc\x1aM\xe2\x11\x17\xe1\x05%Fs\xe6\xcc\xe1\xc6\x1bod\ +\xec\xd8\xb1vg\xc2\xd7\x08\xcb\xb2p\xb9\\|\xf8\xe1\x87<\xf1\xc4\x13\xbc\xfb\ +\xee\xbb\xd4\xf9\xa5\xf0\x86\xa2\xc7\x08\x98\xf9$\xf4\x9e\xd4B\xc1\xf5CJpU\ +\xc2;\xb7\xc1\xfa\x7f\x83\xbb\x855\xcd\r\xc3\xf0\no0\xd5\xd5\xd5\xde\xd9(\ +\xc2\xf5\x83\x0f<\x13z\x8d\x85e\x7f\xf0-\xd3\xe2\xab\xd1$\x16a\x0b\xaf\xbf\ +\xa06uc7&\xbc\x1eRSS\xb9\xfc\xf2\xcb9\xed\xb4\xd38\xf3\xcc3\xbdE\xcc\x9b\x12\ +a\x8f(Y\x96EII\t\x1b7n\xe4\xa5\x97^\xe2\xfd\xf7\xdf\xa7\xb4\xb4\xb4\xd1\x813\ +o\x7f\x9d0\xf5^8\xf1\x0e\x15\xb5\x10\xae\xdeK\tB\xc2\xfe\xaf\xe0\xb5\x1f\xc0\ +\xde/\x81\xd8\x8f\x1b\x02\x90\xdc\x05&\xdd\x01\'\xdf\x03X\xf0\xe4x8\xf8\xado\ +}c\x7f\xa3\xe0\xbf\x8f\x16i\x8d&\xfa\x84%\xbc\xa1\xc44\xd4\r\xdb\x9c\xe8\x06\ +\x93\x95\x95\xc5)\xa7\x9c\xc2\xc4\x89\x139\xfe\xf8\xe3\x1b\xcc$\x01\xb0c\xc7\ +\x0e\xb6o\xdf\xce\xb2e\xcb\xf8\xf2\xcb/9p\xe0\x00\xb5!B\xb8B!\x9c\xd0o2\x9c\ +\xf7\x04d\x0fR\xd1\n\x91@J\xc0\r\xbb>\x81O\x1e\x80\xed\x1f\x133\x016\x9cp\ +\xf2\xff\xc0\xb8\x1fC\xd7"\xf5%"%|\xf5\x0c\xbc\xfaC\xdfv\xad\xfd\xfbh\x01\ +\xd6h\xa2G\xab\x84\xb757jkE7\x9a\x08\x07\xf4;\x19\xa6\xfe\n\xfaM\x05)\xc2\ +\xb7rC\x11\xe0\x150a\xdfJX\xf5$|\xfb\x06\xd4\x1c\x8e\\;\x1e\x8c$\xe81\x0c\ +\xc6^\x0b\x83\xcfQQ\x18\x04\x9dS\xdda\xf8\xcb`8v\xc8\xb7,\x9c\xbf\x91\x16`\ +\x8d&\xf2\xb4Hx[{\x83\x86\xda\xbe\xd7\tP\xba\x0e\xa4\x19\xbc&z\x08\x07\x14\ +\x9d\x02\xa7\xfc\x1c\x8a\xce\x88\xbc\xe0z0k\xa1t\x15\xe4\x1f\x0f\xce.\xca\ +\xd8\x15\xc0\xb1\xfd\xb0\xf2/\xb0}\t\x94\xae\x05\xd71\x15\x15\xd1\x1a\x8c$p$\ +A\xd7B\xe8{\x12\xf4?\x15\n\xa7\xa8\xf7M\r\x06J\tK\xfe\x1f|\xfc[\xdf\xb2\xb6|\ +9j\x01\xd6h"G\x93\xc2\x1b\x8eE\x14j\x9f\xa2S\xe0\xaaw`\xf5S\xf0\xfe\xbdP\xdf\ +\xba\x81\xf9\xd6! \xbb\x1f\x8c\xbd\x1a\x86_\x08y#\xa2\'\xb8^$\xb8\x8e\xc2\ +\xaeeP\xf2\x19d\xf6\x86>\'A\xee p\xa6\xab\xf5f\r\xec\xdf\x08\xbb\x96\xc2\xbe\ +5P\xd5\xc4\x8c\xee\xdd\x87BFOu\x9c\xc2\x93\xd4O#Y\x89pk\x128\x0e}\rO\x8c\x07\ +\xb7\xed\x89\x89\x84;H\x0b\xb0F\xd3vB\noKo\xc6\x96\xdc\xc8\xce4\xb8i\xad\xf2\ +\xa9\x02T\xee\x84\xb7o\x87-\xef\x80Y\x1f\xbcw\xf8\xa4d\xc3\x90\xe90\xf6:(<\ +\x19\x8c\xd4(\x8bm\x08<\xbe\xde\xef\xde\x86es\xe1\xe8^8\xeeL\xe8w\n\xf4\x9d\ +\xa2\x04\x14\xa7\xad\x9dM\xf5M6\xb3\xbe\xc5\x1d\x82E\xd7\xc0\x97\xcf\xf9\x16\ +\xb5t\x90\xad%h\x11\xd6h\xc2#@x\xdb\xfa\xe8\xd9`\x7f\x013\xff\x06co\xf0\x89\ +\xa0\'\x12`\xcfr\xf8\xf4a5\x10U[\x11^\xe73\xf2!o(\x8c\xba\x04\x86_\x0c\xa9\ +\xb9\xb6f\xc5Y\x0e<\x02\xbc\xf5\x1d\xf8\xf2\x9f\xb0\xe9\r\xb5,w\x00\x8c\xb9\ +\x12\xc6^\x03\xe9\xf9j\xb0/\xda\xfd=\xfc\r<6\x16L\xbf\xc8:-\xbe\x1aM|\x11\ +\xc2pD\xec\x86\x0b>\xce\x84\x1b\xe0{\x7f\xb1\x1f\x91\x830\xeb\xd4\xe3\xb94\ +\x95\xf8~\xf5/(\xdd\x00\x08\xa8\xdc\x03VPVoJ\x16\xa4f\xabQ\xfc!\xd3a\xc8\xf9\ +\xd0\xe7Dp\xa4\xa9\xe8\x04O\xc3\x06P{\x18\xea* %WY\xc2\xb1FJe\xb0J\xd4?G6\ +\xc3\xf6\x8f\xe0\xdb\x85\xb0\xed#\xd5\xc9\x9c\xfe\xd0s\x84\x8a\xb9\xcd\x1f\ +\x0b=\x867u@H\xef\xa5\x9e\x1eZ\xdf\x19\x95\xe6\xfc\xe9\xc3\xbeE\x91\x12^-\ +\xba\x1aMxx\xf5\xa1U;\xb5@t3{\xc3u\x1fC\xf6q\xa1\x8fa\xd6\xc2\xe2\x9fB\xd9\ +\x06(\x18\xab\x92\x19\x0c\xa1\x1e\xc9\xabJ\xc1rC\xd7\xdeJh\x01R\xba*\xe1\x05\ +\x90\x0e\xf5\x18o\xd5+\x9f\xaa\xbbNe\x90\xd5\x1c\x84\x9d\x9f\x82#YY\x95\'\ +\xfc\x08RrZ{v\x11@\xc2\xe2\x9f\xc0\xa0\x19jP\x0f\xfb\xa1BH\xa8,\x81\xfd\xeba\ +\xdfZ([\x07{\xbe\x80\x8a=\xca\xea\xb5\x9a\x18x\xbc\xe6}\xe8;5\xbc\xee\x1c\ +\xda\x00\x7f\x1b\xe3\x1b\xd8k\xad\xf0\n\xc3!Z\x1aB\xa8\xd1h\x9a\'j\xc2{\xe9\ +\x02\x18\xfc\xfd\xa6G\xdd\xad:\xf8t.|\xfck\x7fQ\xf0m\x93\xd1SE&\x04\x93\x92\ +\xa5\xc4\xd5r\xa9\xe3\x14\x8eW#\xff\x00\xbd\xc6\xc3\xf0\x8b 9;\x8e.\x07\t/\ +\x9d\x0b\x9b\xdeVq\xc3g\xfe\x0e\xfaL\x0er+x\xae\x96\x05\x95\xf6\x97\xc8\x9e/\ +\xa0\xba\x14\x0el\x84\xca28\xb4U}\x99T\x95\xc1\xb5\x1fB\xdf\xd3\xc2\xec\x8e\ +\t\xf3\xcfRV\xb7\x87\xd6D8x\xb6mi\xd2\x8cF\xa3i\x9aF]\r\xad\xb1r\x82\xb7\xeb\ +9\x16~\xfc\x05^K\xafI,\xd8\xf91\xd4\x1e\x81\xd4\x1c\xe8\xde\xd4#\xb7\x8d3%\ +\xd0\x85 \xc1;\x18%eb\xf8x\xb7\xbd\xa5"8<\xb5\x1cF^\x08\x13o\x87\x9e\'\xd8_&\ +M|!\t\xfb\xebP\xa0\n\xf1\xd4W)\xcb\xdd\x11\xe6\xbc\x98R\xc2\x9a\'\xe0\x8d\ +\x9b|\xcbZ*\xbcZ`5\x9a\xc8\xe3\x1d\\\xf3\xdcx\xcd\xdd\x90\xcd\xde\xb0\xc2\ +\xb6vg\xc7V\x00=I\x0c\xf1\x16]\x0fR*\x7f\xf3\x91m\xb0\xe4\xb7\xb0\xf5}\xe5\ +\x1e\xe9S\x0c=G\xc3\xe8+\xa0\xdb\x90\xe6\xab\xa1E\x8a}+`^\xd0\x04\xcf\xcd\ +\xc5^\x07o\xa3\xd1h"C\xb3\t\x14\xad\x15\xde\xfcQp\xc3\x97\xb4\xcc\xda\r\x13\ +\xaf\xc8\x02\xeej(\xf9\x1c\xa4\x0b\xfaO#2aX\x11\xc43\xd0V\x7f\x14>\xfb?\x15\ +\xe5P\xb1[\xadK\xee\n#fCA1\x8c\xb8\x10\xd2z\xf8<\x10\x91\x16c\xab\x16\x9e\ +\x1c\xa7b\x89=4\'\xbcZt5\x9a\xe8\xd0j\xe1\x85\xd0>?\x0f\x97\xfe7\xf2\xd6\xae\ +G\xbc\xdc5P]\xa6\x8a\xbf\xec\xfe\x14\xf6\xaeV)\xb1\x13o\x81\x91\x97\xab\xf0\ +\xac\x84F*\x01\xde\xf2&\xec\\\x02_>\xebKnH\xce\x80>\x13a\xd8L\x188\x1dr\x06\ +\x824"w\x1d\xa5\x84O\x7f\x03\x1f\xfc*pyc.%\xcf\xba\xc8\xb4\xae\xd1h\xfc\x89\ +\xa8\xf0\xe6\r\x85\x1f\xafRi\xb3\x91BZ\xb0\xeb#8\xf05\x1c\xfa\x0e*K\x95\x00\ +\xa7w\x87\xa2)0\xecBpf$\x8e\x8b\xa1\xc5H8\xf4\x8d\xf2m\xef^\xae&\xd4\xf4X\ +\xa3\xceT(\x18\x07C\xce\x83\xdca\x9197)a\xcf\xa7\xf0\xf7)\x81\xcbu\xc4\x82F\ +\x13{\xc2\xaa\xd5\xd0\xd8\xcdz\xea=0\xf5\xb7D\xf4q\xdfc\xed\x86\\\xe7\xe9O;\ +\x96\x08\xff\xc1\xb4\x90\xeb\x89\xdc\xf9\xb9*\xe0\x8f\xfd\x95\xd87\x85\x16]\ +\x8d&\xbaDl20\xe1\x80\xa1\xb3\x89\xb8\x8fU\x08\xfb\x98!^"\xda5\x18b\x80\xb7\ +\xffM\x9cc\xa4H\xea\xaab\xa65\x1aM|\tKxCY\xbb=F\xa8\xea\\\x9a\xc4E\x02\xfd\ +\x9b\x89\x05\xd6\xd6\xaeF\x13}Z$\xbc-\xb9\x19O\xb86t\xb2\x83&q\x10\x02z\x8f\ +\x8fw/4\x1aMD\\\r]z\xc0\xa8+H\xb8P.MCz\x8e\x0b\xb3\xe6\x83F\xa3\x89\x18-\x16\ +\xde\xa6\xac\xde\xe1\xb3!-/2\x1d\xd2D\x97\xb4nj\xf6\x8aPh7\x83F\x13\x1b\xdal\ +\xf1:S\xe1\xc4\xdb\xe36\xc7\xa3\xa6\xb5\x08U\x11M\xa3\xd1\xc4\x8f6\x0bo\xd1\ +\x14\xc8\x19\xd4\xfe\xa3\x0b:\x0b\x12-\xbc\x1aM\xbci\xb3\xf0\x1e?\x87\xa8\ +\xa6\x07k"\x8b\x10\x90= \xc4r\xedf\xd0hbF\x9b\x84\xb7\xdb`\x959\xa6\xad\xdd\ +\xf6E\xaf\xe3UYM\x8dF\x13\x1f\xda$\xbc\xc7_\xad&a\xd4\xb4/\xd2\xf2 7\xa8@}8\ +\xb3\x90h4\x9a\xf0\x08[x\xbb\xf4\x80\xf17\xe8A\xb5\xf6\x88\x91\xa4\x06E5\x1a\ +M|\x08[xO\xbcU\xcdi\xa6\xdd\x0c\xed\x0f)\xb5\xf0j4\xf1$,\xe1M\xef\x0e\xc57i\ +\xd1m\xafH\xa0\xf7\t\xf1\xee\x85F\xd3y\tKxG\xcc\x86\xe4xL"\xa9\x89\x08\xfa\ +\x0bS\xa3\x89/a\to\xd1\xe9\x91\xee\x86F\xa3\xd1t\x1eZ/\xbc\x86\x9a\xdeG[M\ +\x1a\x8dF\x13\x1e\xad\x16^\x01\xa4fE\xa1\'\x1a\x8dF\xd3Ih\xb1\xf0\xea8O\x8dF\ +\xa3\x89\x0c\xad\xb6x\xa5T\x13Lj4\x1a\x8d&\xac{&\xf6\xedw\x14\x0c\x01\xbbV\xc4\xbb\x17\ +\x1aM\xe7%,\xe1\xdd\xb5"B\xb3d\xb6\x02)a\xd7\x12(\xdf\x0e\xd2\x82\xc2I\r\xd7\ +K\x89*D\x10\xeb/\x06\xbf6\xdb\x835)-\xb0\\\xf1\xee\x85F\xd3yq\x86\xb3\x93\ +\xb4"\xdd\x8d\xe6\xb1\\\xb0n\xbe\xaa\xaau\xfe<\xc8\x1d\xee\xd7\x1f\t\xfb\xd7\ +\xc2\xc6\x97a\xf7J\xc8\x1f\x01\x03\xcf\x86\xfeg\x82\x91\x1a\xfd,;\xb3\x0e\ +\xde\xff\x19$\xa7\xc1\xf1\xd7A\xf6q\x89\x9d\xd9Ws\x00\x0e~\xeb{\xaf#\x1a4\ +\x9a\xd8"\x84\xd1\xf2y{@BW\x88\xdf\xb7\ +\xce\'\xba\x1a\x8d&\xf6\x84\xe5j\x80\xd8\xdd\xb8R\xc2\x8e\x0fa\xdfZH\xea\x02\ +\xdd\x87\x05\xae\xdb\xf0\xa2\x12\xd8F\xf7\xb7\xe0\xd0f\xb5m8\x8f\xff\x9eH\ +\x8a\x0f\xee\x86\r\x0b\xa0\xaa\x14,\xb7Z\x97\x92\x05\xe3\xae\x85\xd3\x1f\x04\ +#E\x89\xef\x0f\x96\xc1k\xd7\xaa\x81\xc0\xb4\xfc\xd6\xb7\x17m\xa4\x84\xea\xb2\ +x\xf7B\xa3\xe9\xdc\x84\x17\xd5`AI\x8cF\xc5\x05\xb0\xe9U\xf5{\xc1\t\x90\x92\ +\x1d\xb8n\xcf\x17\xcd\x1f\xa3\xe4\x8b\xb6\r\x06.{\x00\x96?\x02GK|\xa2\x0bPW\ +\xa1\x96\xbfx\x1eT\xefU\xcb\x1c\xe9p\xf6\xff\xc1\x91mmh0\x8a\x08\xa0\xf4K\ +\xbf\xf7\xda\xcd\xa0\xd1\xc4\x9c\xb0\xf5(fi\xc3\x02*\xf7\xa9_\xfbM&0~\xd8\ +\x82\xdd\x9f7\x7f\x08w\x98q\xc7R\xc2\xce\xf7\xe1\x93\x07\x9a\xden\xeb{\xf0\ +\xaf\xe9P_\xae\xdeg\xf4\x81\xae\x85\xado/&H(Y\x19\xefNh4\x9d\x9bV\t\xaf\xbfu\ +T\xbe\x9d\xd8\xf81%t\xed\xa3~u\xa6\xd0@@\xcd\xba\xe85m\xd6\xc0\xe2\xbb[\x16z\ +U\xb6\x1e\xde\xbc\x19\xacz\xdb\xe7\xdb\'z\xfdj\x0b\xd5eP\xd6\x84kF\xa3\xd1D\ +\x9f\xb0-\xde\x92\x151J^\x13\xd0\xd3\x9e\x1f\xac\xf0\xe4\x86\xeb\xba\xb6@\ +\xe0\xba\xf6n}|\xad\xc7\x7f\xbc\xef\xcb\xe6\xb7\xf5\xb0\xfe%8V\xa6\xf6-\xdfJ\ +B\x0e\xb0\x95~\t\xaej\xf5\xbbv3h4\xf1!l\xe1=\xb2\x13\xea\xab"\xd9\x95\xc6\ +\xe9;I\x85\x839\x92\x82V\x08(:\xa5\x99\x9d\x05\x8c\xb9\xaa\xf5\x1a(\xeba\xed\ +?[\xb7O\xff\xa9\x90Q\xa0~\xff\xfa\xc5\xe8Z\xe3aa\xc1\xd7\xff\x8ew\'4\x1aM\ +\xd8\xc2[W\x01\x15;c\x93\xa9\x957\x1c\xfaLP\xd6Z@{\x02\x8ao\x86\xe3\xce\x06#\ +D|\x86\xe1\x84\xa9\xbf\x80QW\xb6>\xa2\xa1\xae\n\x8c`\xa1o\x02a\xc0)?\x07\x1c\ +*\xa6w\xf9\x9f\x13Kx\xa5\x84\xbd+a\xfd\xcb\xf1\xee\x89F\xa3i\xfd\xd4?\xf6\ +\xe3\xa9\xe5\x86o\x17F\xbeC!\xb1\xe7y\xfb\xe6u (k.\xa3\x0f\\\xf6:\xfc`\t\x14\ +_\xafB\xbc\x8c$\x18\xfe}\xf8\xd1r8\xe5W\x84\xf5\xf5\x92\x9a\x0bW\xbe\r\xa7\ +\xdc\xd3\x82\xe4\x0b\x01\'\\\x0b\xfdNW\xee\x97\xcf\xfe\x0f\xea\xab[\xdff4\ +\x11\x12\xbex\xcc\x1ehD\xbb\x194\x9ax\xd2\xaa\xcc5\x0f\x9e\x0c\xb6~\xa7\xc2\ +\xb5\x1f\x81\x8c\xf2-,%\xacy\x0c\xde\xbc\r\xae|\x03\xfaOkh\xc1J\xa9D\xaf\xee\ +\xb0J\xa6\xc8\x19\x02\x88\xb6\xa5\xeezj?\x94\xad\x86\xa5sa\xd3\x1b\xa1\xad\ +\xd8a\xb3\xe0\xa2\x7f\x03N\xd8\xfb\x19<3U\r\x04\xfe\xcf\x1eH\xee\x1a~\xfb\ +\x91BJ\xd8\xfe.\xfc\xeb\\_U7-\xbc\x1aM\xfchS\xad\x9b\xf2\x1d\xb1y\x9c\x16\ +\x02F_\xad\x92\'^\xbf\x11J\x96\x02V\xa0\xdbA\x08@@J7\xc8\x1d\xa6\x1e\xfd\xfd\ +E\xd7#\xa2\xadq\x8dx\x8e\xd9s<\\\xf82\xdc\xf4%\x8c\x9d\x03\x8e\x14\xdf6\x85\ +\'\xc19\x8f\x01NpW\xc2\x7f.SQ\x10\xdd\x87AJf\x9bN;"H\xa92\xec\xde\xbcE\x8b\ +\xaeF\x93(\xb4\xc9\xe2E\xc0e\xaf\xc0\xa0\xf3\xa3_\x14FJ8\xb2\x19^\xb9\x12\ +\xf6~\tc\xae\x84\xf1?\x86^\'\xd8\xbe\xd8\xe0\xaf\x10\x89\xd7-ql\xbf\xca|\x93\ +.8n&\xad\n\xc70ka\xf9\x83\x907\x12\x86\x9c\xaf\xda\xaa*\x81\x83\x9bTB\xc5\ +\xf0\x8b\xc1\x91\x06\xd2\ro\xdf\x02\xab\xe6\xa9\xfdz\x17\xc3\x8fV\xc6?\xb0\ +\xc1\xaa\x83\xe7g\xc0\xb6\x0f}\xcb\xb4\xf0j4\xf1%\xac\x94aa8\x84\xb4L\x89\ +\x84\r/\xc3\xe0\xf3#\xdd\xad\x10m\n\xc8\x19\x0c\xd7|\x0c\xab\x1e\x83/\xe6\ +\xc1\xda\xe7 \xabP\xbdr\xfa\x07no\xb9\xa0d\x15 \x95?z\xfc\x8f`\xe2O\x94\x10\ +\xb6Fu\x8c\x14\xe83\t\x9e?\x1f\x06}\x0ff<\n\x99\x85*=\xd8\x83\x94\xb0\xf1EX\ +\xf5\x94oYf/\xdb\xba\x8e\xa3\xc4I\t_>\x15(\xba\x1a\x8d&\xfe\x84e\xf1\x82\xcf\ +\xeaM\xeb\x06\xb7m\x82\xd4n\x11\xedW\xd3mK0\x8f\xc1\x96\xd7a\xd9\xc3*yA\xa2\ +\x92\x17@E3\x08\x03\xb2\xfa\xc2\xc4\x9ba\xe4\xa5\x90\x9eo\x8bn8B(\xe1\xc3\ +\x9f+?oF>\x9c\xf5\x00\x8c\xba\n\xef\xd7VM\x19\x84M\xaf\xb5\x8f\xc9\x1e;*R\xc2\xc6\x97\xe0\xab\x17\x03\x97kkW\xa3I,":\ +Y\xf0\xc7\xf7A\xed!-\xbe\xf1@J\xd8\xff%\xbc\xf6c\x02\x82\x87\xb5\xe8j4\x89GD\ +\x84\xd7ss\x97m\x80\xa5\xbf\x8dk\xe8j\xa7\xc5U\t\x0b.\x8b]\xc58\x8dF\x13>\ +\x11\xb5x\x01V<\x06[\xdf\xd2Vo,q\x1f\x83\x0f~\x06\x077\x07.\xd7\xd6\xaeF\x93\ +\x98D\\x-\x17\xbcr\r\x94~\xa1\xc57\x16HK\xcd\t\xb7\xf2\xf1\xc0\xe5Zt5\x9a\ +\xc4%\xe2\xc2\x0bp\xec \xfc\xeb\x1c8\xbcQ\x8boT\xb1S\x95\x97>\x14\xb8X\x8b\ +\xaeF\x93\xd8\xb4)\x8e\xd7\x83\x7f<\xaf?\x99\xbd\xe0\xf2E\x90?\xae\xf3\xc4\ +\xf7\xc6\n)a\xcf2xn\xbao*\x1f\x0f\x1dMx/\xbau\xa1\xdc\xb9\xe7(i\xe9\xc9`\x97\ +\xff\x94\xc2\x1eK\x90~\xa9\xe0R"\x85\xb0\x17H\x04\xeawO\xd9R\x81\xdf\xb8\xa3\ +\xf4[\xd0\x8e\xaf\x96\x90 \x85D\n\x89a\t\x84pP~\xa0\x84\xe21\xb9\xcc{\xe8\ +\xdav|f\x1d\x9b\xb0\x8a\xe4\xb4\x94\xca}\xf0\x9fK\xe0\xc2\x17\xa1g\xb1Z\xa6\ +\x058\x02H8\xb4^\xd5\xd7\xed\xe8\xa2\x0b\xf0\xedw\x87\xd8\xbc\xed0)\x19\xaa\ +\x1e\xa7\x12\\\x89D \x84\x92Ri\xab\xa7\xf0\x08\xa9\x00\x89\xaa\x05*,\xb5@z\ +\xfe\xf5\\!)}\x01\xd9\xed\xf9\xc9\xcc>Wa\x810\x9cT\xee\xddKN\xd7\xfax\xf7J\ +\xd3\x04Q\x15^\x80\xc3\xdb\xe0\xf9sa\xe6\x130dv\xb4[\xeb\xf8H\t\x876\xc2\xfc\ +s\xa0\xeeh\xe0\xba\x8e(\xba\x00i\x19\xc9\xa4uM%3C\xcd\xc5$\x10H\x0b\xdb\xaa\ +\xb5\x0b-\xe3\x00\x0c\x90\x16RX\x08\xfb\x1b\xdeS _Y\x86\xc2+\xc6\x08\xe1\x95\ +b\x84\x9f\xc7\xad\xddY\x06\x96:})@Z\x08\xc3I]u\x1a\xe9]R\xe3\xdd1M\x13D\xd4\ +\xc7\xdb\xb3gO\xfe\xfb\xdf\xffr\xddu\xd7\x91\x93\x93\xe3]^}\x00^\xfd\x01\xac\ +{F\r\xbei\xbfoxH\t\x15[\xe1\x85\xf3\xfc\x8a\xf2\xd8tT\xd1\xf5G\xd8\xff\x81\ +\xd2G\x03\x10\x9e\x0f\x93\x04,\x894l+W\x82\x90\x06\x02\xbb"\xbe\x90\x08\xbb@\ +\xb3\x94\x1e\x0b\xd7@\x99\x8b\xaaB~P"b;y\x19\x08a\xa8Sl\xcf>\x93NFD\x85799\ +\x99\x993g\xf2\xf4\xd3O\xb3~\xfdzn\xb9\xe5\x16\xaf\xe5Q[\x0e\xaf]\x0fo\xdd\ +\xa8\xa6\xe7\xd1\xb4\x0e)a\xffjx\xee{pd[\xe0\xba\xce \xba\r\x91\x08)\x10\xc2\ +\xc0\xb2\x0c\xea]\x165\xaez\xea\xea\xdd\xb8\xdc M\xa1\xdc\x0e\xb6\x85+\xa5\ +\xb0\xbf\xf0%\xc2P\xeb\xa4E\x03\x17C\xfc\x854\xfc\x97\xa6\xfd\xd0f\xe1\xf5\ +\x1fX\x1b=z4N\xa7\x13!\x04\xbd{\xf7\xe6\x91G\x1e\xe1\x7f\xff\xf7\x7f\xfd\xb6\ +\x85\xd5\x7f\x87\xe7\xceVs\x80\xc9VN\xc5\xd3Y\x91\x12v}\xa0\x06\xd2\x82E\xb7\ +s\xa2T\xd3\x14\x92:\x0b\x10\x82\xf44\']\xbb\xa4\x90\x99\x9aLJ\xb2\x03SH\\\ +\xd2Bb!\xa5\xc7\xf5`\xd8\n\xa5,_C\x80D\xa9\xafhw.\x06M{&\xa2\x16\xef\x80\x01\ +\x03\x02\xde;\x1c\x0e\xee\xba\xeb.\xbat\xe9\x12\xb0|\xdf\x1ax\xfe\xbc\x1fv,\x83s\x1eU\xb3\x02k\ +\xa3\xc3\x87\x94 ,X\xfd\x18\xbc\xf5\x13\xfb\xb1X\xe3\x8b\xfe2\x05\x96e2\xeb\ +\xb4\xbe\\z\xd6@\xc6\x0c\xce!;;\x15i\xc1\x81\xc3\xf9\x9cQ\xdc\x87\xff,\xfe\ +\x8ew>\xdd\x8d\xc4\x890,\xe5U\x90\xf6\x83\xb9@]T)\xf4\x07O\x13s"*\xbc\x83\ +\x07\x0f\x0exd\x93R\xb2r\xe5J\x8e\x1d;\x16z\x07\t\xdb\xde\x87\x7f\x9c\x0e\ +\xdf{\x08F\\\xa2z\xd4\xd9\xef\x03\xcf\xd4F\x0b\xaf\x86\x8d\x0bi\xdf\xa1NQ\ +\xc02%N\x03\x86\r\xe8\xc6\x0fg\rc\xd2\x84B\x844\xc1TB\xda\xb7o\x1a\x85}\xbb\ +\x93\x9e\xe2d\xdf\xfe\x1a\xd6o+\xc74M\x1c\x0e\x03p\xa8\xc8\x06\xff\xb83\xf4g\ +N\x13["\xe6j\x10B\xd0\xad[\xe0\xc4k\xa6i\xf2\xf8\xe3\x8f7\xb2\x87\x8f\xaa}\ +\xf0\xdf\xab\xe0\xef\x93\xa0l\x95m\x88tR\xb1\x91\x12*w\xc0\xbf/\x84\x8d\xaf\ +\x10 \xba\xc9\xc9\xc9\x0c\x1a4(^]K\x08\x04\xe02-\x9c\xc9\x823N\xea\xcd\x90\ +\xbeY\x08\xcb\xc4\xacuc\xd5K\xa4\xcbB\xd6\xba\x11f=\xc7\x0f\xcbc\xe6\xe9\x03\ +\x11\x02\xdcn%\xb4\x86m\xec\xaap^\x87WqC=\x95i4\xd1"b\xc2\xdb\xa3G\x8f\x00Q\ +\x90R\xf2\xe7?\xff\x997\xdex\xa3\xc5\xc7\xd8\xb3\n\x9e:\x19\x16\\d\x0bp\'*\ +\xac\x0e*\xd4\xee\xabg\xe0\xf1q\xb0\xe5\x9d\xc0u\xa7\x9ez*K\x97.\xe5\xb9\xe7\ +\x9e\x8bO\xe7\x12\x08K\x82\xc3\xe9\xa4o~\x172\xbb8\xc1\x94\x18R\x00\x12)\xd4\ +`\x99\xe52I\xcfLe\xd8\xc0\xae\xb6uk\xd8\xf37Y\xbe\xfc\t\xcf\xb7\x9a@\x0f\xae\ +ibJ\x9b\x84\xd7?\xa2!;;\x1b\xa7Sy.\xa4\x94|\xf5\xd5W\xfc\xeaW\xbfj\xf51-\x97\ +\xb2\xf4\x9e:\x19\xde\xbd\r\x8e\xeeD\rbw`\x83DJ\xa8\xdc\r/\xcf\x86W\x7f\x04\ +\xb5G\x02\xd7\xff\xf4\xa7?e\xf1\xe2\xc5\x14\x17\x173|\xf8p\xf2\xf3\xf3\xe3\ +\xd3\xd1\x04B\xc5(x\xc4R%M\xe0\xcdb\xb3\xecm\x0c\x1c~\xb1\xbf``\x19\xc2\x9bB\ +\xec{\xb4\xf2\xb8\x1b\xb4\xf8jbC\xc4,\xde\xe1\xc3\x87\xe3t:\x91RRRR\xc2\x8c\ +\x193\xa8\xae\xaent\xfb\xff\x9d<\x99\xc5s\xe6P\x94\x1dz\x86L\xcb\xa5JL>6\x06\ +\xde\xfb\xa9J\x1c\xb0\xea;\x98\x00\xdb\xb1\xa4\xbb?\x86\xf9\xd3`\xf3\x9b4\ +\xf0\xe7\x9ew\xdey<\xf8\xe0\x83$\'\'#\x84 33\x93\xf1\xe3\xc7\xc7\xa3\xb7\t\ +\x83\xc7U`y\x92\xd6<\x0b\r?_\xad\xfd\xd3\xf2/\n/|\x1bx2\xdet\x1c\xac&\x1eDLx\ +\xfb\xf7\xef\x0f@yy9W\\q\x05{\xf7\xee\xf5\xae\x0b\x0e\xf0\xef\x99\x91\xc1\ +\xcf&O\xe6\xcc~\xfdX\xfa\x83\x1fp\xf1\xc8\x91\x8d\x1e\xb7\xae\x02\x96?\x02\ +\x8f\x0e\x87gOW.\x08wU\x07\x10`\t\xc7\xca\xe0\xbd;\xe0\xd9\xef\xc1\x81\x8d\ +\xa17+..&99\xd9\xfb^\x08AAAA\x8c:\xd9~Pv\xad\x01\xc2\xc00\x8c\x16dq\x89\x80\ +\x1f\x1aM,\x89XTCAA\x01\xe5\xe5\xe5\x9c\x7f\xfe\xf9,]\xba\xd4\xbbuuu\xdcr\xcb-\ +\x14\x17\x17SPP@\xbf~\xfd\xd8\xb6M\x97*\xd3h\xda#\x11\x15\xde\xc6Ds\xee\xd9g\ +3(;\xbb\x81\xb5[Y_\xcf\xcf>\xf8\x80\xc7W\xaeTO\xf8m\xf0S\x86\xda\xb75b\x1c\ +\x0b\xba\x0f\x87\x11\x17\xc0\x88\x8b w\x08\x88$\xdb\xefl)\x9ft\xc9g\xbem=~[\ +\x7f\xabw\xef\xde\xbd\xfc\xf4\xa7?\xe5\xe5\x97_f\xc0\x80\x01|\xf4\xd1Gq:\x13\ +\x8dF\xd3\x16"&\xbc\x8dY\xa0C\xf3\xf2\xb8t\xc4\x88\x06\xa2\xeb\x96\x92{?\xfa\ +\x88\xc7V\xae\x0c\xb9\x7f\xa4\xfa\x14o\xf1u\xa6B\xe1D\x18u\x19\x8c\xbc\x1c\ +\x9c\x19v\xdf\xfc\xceV\n\x18:=Px\x1b\xe3\xb5\xd7^c\xf5\xea\xd5\xba\xb6\x80F\ +\xd3\x8e\x89\xea\x9ckI\x86\xc1o\xce8\x83\xd4\xa0\x015\t<\xfa\xc5\x17<\xfa\ +\xf9\xe7\xd1l\xbeQ\x84\x80\xd1\xa3\xbb\xb0m[-\x95\x95*2 )\x03\x1cI-?Fj\x16\ +\xf4\n\xf2\x0fw\x1b\x04]z\xf8m\xd3\x15\x06N\x83\x8c>\xd8\xf5\x00\x1a?\xde\ +\xe8\xab\xe1\xe3\x07B\x0f\xca\xf9\x7f\x81\xd4\xd7\xd7\xf3\xfb\xdf\xff\x9e\ +\xd1\xa3G\xb7\xbc\xb3\x1a\x8d&\xa1\x88\x88\xf06f\xed\x9eu\xdcq\\0th`\xd0\x80\ +\x94\xfcg\xd3&\xee~\xf7\xddF\xf7\x8f$\xa1\xac^!\xe0\xef\x7f\x1fJ~~2#G\xae\ +\xa4\xa2\xc2\xe4\xfc\xc7\xed\xb2\x94->0j~\xc5F""Z\xd5G\x01\x99}a\xc8\xb9\xf0\ +\xf5\x82F\xb6\xf1;\x8fO>\xf9\x84\t\x13&\xb4\xbe!\x8dF\x93\x10\xb49e\xb81\xd1\ +4\x84\xe0\x8e\x93Nj \xba\x9b\x8e\x1c\xe1\xf67\xdf\xc4eYx|\x98m\xedCk\xfbhY\ +\xb0~}5\xbd{\'SX\xa8\xa6\x0cw\xd7\x83t\x02I-|9i(\xb2m9\x13\x01\xbd\xc6\x06.j\ +\xccMr\xf0\xe0A\x1e}\xf4\xd164\xa6\xd1h\xe2IX\xc2\xeb\x11\x84\xa6\x06\xb4N-*\ +\xe2\xb4~\xfd\x02\xd6\x1d\xae\xab\xe3\xbaW_\xa5\xb4\xaa*x\xb7\x98\xb3`\xc1\ +\x81\x80z\x0f%\x9f\xc5?\xe5x\xccUM\xaf\xf7\xbf\xde%%%Mm\xaa\xd1h\x12\x98\xb0\ +-\xde\xe6,\xd5[&N\x0c8\xb8KJnx\xe3\r>\xdb\xbd\xbbE\xfbG\x9b\xcf>\xab\xa0\xce\ +/|\xab\xb2\x94\xb8\xcf\xf4\x90\xde]\xf9\x895\x1aM\xc7&,\xe1mL4\xfd#\x19\xa6\ +\x1fw\\@a\x97\xdf/[\xc6\x02\xbb\x06C\xbcE\x17\xd4\x8c\x04\xb5\xb5\xbeJ\xeb\ +\x87\xb7\x11w\xe1\x15)0\xe0\xb4f\xb6I\x80k\xa7\xd1h\xdaFDg\x19\xf6p\xc3\x84\ +\t\xdeH\x06)%+KKy`\xc9\x12 ~\xc2\x11\xdc\xee\xd1\xa3&;v\xd4&T\xf1k!\xec\x01\ +\xbef\xba\xa4\xc5W\xa3i\xdfD\\x\x05pB\xcf\x9e>A\x13\x82\xbf\xae\\I\xad\xdb\ +\x1d\xe9\xa6\xda\x8c\x10\x92\t\x13\xba\xc6\xbb\x1b\x01t\x1f\x06Ii\xad\xdf/\ +\xde\xf1\xca\x1a\x8d\xa6\xe5DLx=7\xfe\xe9\x03\x06pra\xa1Z&%\xefm\xdf\xceK_}\ +\x05$\xa6\xa5\xe6H\xb0.\xa5\xe7CVa\xbc{\xa1\xd1h\xa2I\xc4-\xde\x9f\x9fr\n\ +\xc2\x0e\x17pI\xc9\x1d\xef\xbc\x83\xdb\x0e\x1d\x8bt[m\xc5\xe5J@#Q\xc0\xe0\ +\x19\xbe\xb7\x8dY\xb2\x89x=5\x1aM\xcb\x88\xa8\xf0\xa69\x9d\x0c\xcb\xcbC\x08\ +\x81\x94\x92\xa5\xbbw\xb3\xe9\xc0\x81H6\x11Q>\xfd\xf4h\xbc\xbb\xd0\x00\t\x14\ +\x9d\x1a\xef^h4\x9ah\x12\x11\xe1\xf5XeW\x8c\x19C\xcf.]\xd4B!\xf8\xdb\xca\x95\ +\x98R&\xacu&\xa5\xa4\xb883\xce\x9dP\xb3jH\xb7za\xaab:-I\xc6H\xd4\xeb\xaa\xd1\ +h\x9a&\xa2\xb5\x1a\xa6\x0f\x1a\xa4\xdc\x0cBp\xa0\xa6\x86\xf7\xbe\xfb.\x92\ +\x87\x8f\n\x19\x19\x8e\x98\xb6\xe7I\xda\xb0j\xa1d\xb9\x9aG\xee\x9b\xd7\xa0\ +\xce\xcf\xf8\xb6\\\xc4=\xb4M\xa3\xd1D\x8f\x88\tozR\x12\xe3\n\n\xbcn\x86\x0f\ +\xb6o\xa7\xca\x9e\x91B\xe3\x9b\xcd\xa2\xfe0,\xfb=\xac\xff7T\xec\xa6\xcd\x02\ +\x9b\x08\x15\xd84\x1aM\xeb\x88\x98\xf0vMI\xa1\x97\x9f\x9b\xe1\xa5\xf5\xeb\ +\xd5\xaf\xfaq\x18)\xa1f?,\xfd\x1dl\xf8w\xd0\xcc\xc2)@\x010\x04\xb0\x80\x15@+\ +\xe7g\xd3\xd7X\xa3i_\xb4Yx=\xd6VA\xd7\xae\x18v\xec\xee\xc1\x9a\x1a>\xde\xde\ +\x8a\xa9t\xe3\xc8\xd1\xa3*\xbe89\x83\xa8\xccL,%l~\x05\xde\xbf\x07\x0en\xb6\ +\x17\n`$0\x1e\xe8\t$\xfb\xed0\x04x\x1a\xa8\x89|_4\x89\x81r7\xb5\xfe!\xa5\xa9\ +d\x1f\xfd\xc8\xd3\xbe\x88\x98\xc5{r\xdf\xbe8\xec\x0f\xc6\xfec\xc78Z\xd7\xc4<\ +6\t\xc4\xaaU\xaa`O\xaf\xd1aWu\x0c\x89\x94 ]\xb0\xfa\tx\xe7\x7f\xc0\xf2\xe4\ +\x8f\xf4\x02f\xda?C5\x98\x03L\x00>\x89PG4\x89\x85\x04\xa4D\n\xcf\x1bT%|\xfb\ +\xbd\xe7\xe3 \xfd?\x1b\xb2)\xd1\x95\xde\x9f\xd2o\x7fMb\x131\xe1\xed\x97\x9d\ +\xed\xfd\xa3\xaf/+kr\xdbDAJ\xc1\xee\xdd\xbe\xca\xe3\x91\xca\x1e\x96R\r\x9e\ +\xbd};\xac~\xca^\xe8\x04\xce\x03F\xe0\xbb\xa1\xfcn\xac\x80e\xb9\x91\xe9\x87&\ +\xf1\x90\xf6+\xa04\x1e\xd2\xa7\xc1A\x1bK)\xfcD\xb8)\xbbV\x80%\x10\x18HK \xa5\ +\x96\xe0D&b\xc2;0\'\x07P!Z;\xca\xcb\x81\xf6\xe1{,)\xb1\x857\x82\xcfj\xd2\x05\ +\xf3\xcf\x86\x9d\xcb\xec\x05N\xe0B\x94\x1b!`C\xc0\r\xec\x06\xf6\x02\xf5@\x17\ +\xa0[\xe4\xfa\xa2I0\x84\xb4\x7f\xa8Ah\x89\x85\x10\x12\xa4\x01\x18 L\xdbru\ +\xe2\x90\x82c\xf5I\xd4[\x0eD\xb3\x1fP\x89\xb0\x04\xc2pb\x1eK\xa2\xbe.5\xea\ +\xa7\xa2\t\x9f\xc8Y\xbcYY\x80z$Z\xbf\x7f\x7f\xa4\x0e\x1bU\x06\r\xf2\x15E((\ +\xb6#\x0f\xda\xf0U!%\xb8*\xe1\xdd\xdb\xfdD7\x19\xb8\x14(\xf2\xdf\x10e\xc1|\ +\x07\xbc\x05\x94\x03\xd9(q>\x0f(\x04^@;\xee:,\xb6\xf8\x02\x12\x03\xa4\xa5\ +\xdc\x0fH\x04\x02!\xc0m\x9a\xb8j+8e\xf4&\x8a\n\x0eSW\x9f\x14\xda\x0f&\x95\ +\x93\x01a!L\x03!\x04U\xd5G\xe9\xdb3\x8d\x87>\xbfG\xa6\xa4\xa5\xe02-\x84\xc7\ +\r\x91\xf0\xa6P\xdb\xb0\xccZ\x92\x92\xb2\x19>\xe6\x9e\x84>\xd3\x88\x08o\x97\ +\xa4$\xf232"q\xa8\x98\xd2\xa7O\x8a\xf7\xf7\xe4\xcc\xb6\x8b\xaet\xc3\xe2\xff\ +\x815\xff\xb4\x17:\x81\xef\x03\xfd\xfd7DE/\xbc\x03\xacAE5<\n\\\x04\xf4@\xdd\ +\x18/\x12 \xba\x9e\x19\x87\xc3\xef\x9d&\xb1\x10J,m\xa4-\xb6\xea\x0fl \x10\ +\x98\xee:\xcc\xba\x83\xcc:i\x153F\x97QS\x99\xd2\xd8\xc1\x1a\xe0\x90\x0e\\\ +\x98\xec?\xfa!UU\x16\x864pZ\x06\xf5N\x17)\xeedD\x07V\xdfzj \xb5w\xbc\xbb\xd1\ +,\x11\x11\xdeT\xa7\x93\x9c\xb40Jj\xc5\x90\xe0X\xd7\xe4dAr\xb2Ay\xb9\x1b\xe1h\ +8\xedN\xab\x8e-\xa1\xf6\x10,\xfa!|\xfb\x9a\xdf\x8aSh\xe8^\x10(\xc1]\x85\x8aj\ +x\x08\x98J\xa0%R\x1d~_4\x89\x8e\xf0\xca\xae\xf4z|\xa5_\xedj\xd4`\x9b%0\x04\ +\xe4d\xa4\x93v\xb4+\xa94\x1d\xd5\x10\xd4\x04R:\xe9\xe6\xc8\xf7\xbe\xf7\xe6\ +\xa8:\xe9\xd0\xc2+\x85\x05\xddz\x02\xdb\xe2\xdd\x95&i\x93\xf06\x17\xb8\x9f\ +\xc8\x96ZF\x86\x83\x8c\x0c\x07%%\xf58R\xecp\xb20\x11\x12\xde\xba9Ht\x8f\x03N\ +\x0e\xdaP\x02\xfb\x80\xf7\x80\xd3\x80\x85@W:\xfc\xe3\x9f\xc6\x1fi\x0b\xae\ +\xb0\x9f\xb0\xa4zZ\xb2$R(W\x03\xc2P.\x08\xcb\x89e)\xc5lm\xdd\xe8D\xaa3\x1dK\ +\x04\x06X\xb1\xcdF\r\x87\xb0k5\xf8\x8b\xee\xc5\xa3F\x91\xe2\xf9\xc6F\xb9\x1e\ +\x12\x9d\xf1\xe3\xbb\xe2v[\x00t\xed\x05]\xfb\x84w\x1c)\xa1b\x87J\xfb\xf5\x92\ +\x8d\xf2\xd5\xfa_]\tT\x02\xff\x02n\x03\xdeD\x8bn\'FH\xc0\xf2H\xb0C\xcd\xb4*\ +\x1d |n\x07)eP\xf4\x83\xa6%\xb4\x87+\xd6\xa6\xc9.\x01r\xd3\xd2\xf8\xe9I\'y\ +\x93\'\x90\x92\xe2\x82\x82\xc8\xf4.\x8a\xf4\xef\x9f\xca\x9a5*\x867\xabP\xb9]\ +\xc3e\xdd\xb3`\xfa\x87-\x9f\x01\x04[\xd0\x02\xf8\x0c\x95\xa5\xf6\x1b \r-\xba\ +\x9d\x12\x81gX\xcd\x8e\x17C\x19\xb9\xc2+\xb6\x16\xca\xfa\xc5\x90\xde(\x08MkH\ +\xfck\xd6jWC\xb0\xe8\xbe~\xc5\x15\x0c\xb4#\x1a@=\xe2\xf4\xee\x9aX\xb3:\x84b\ +\xe0\xc0T\x96/W\xb9\xb9E\xa7\x86\x9fq\xfc\ +\xf1\x99t\xe9\xe2\xa0\xa6\xc6"\xab/\xf4\x18\x15\xdeq\x84\x80\x92\xcf\xfd\x16\ +d\xa2\\\r\xfeT\xa2\xc2\xc7r\x81\xd14/\x18\x02\xe5\x96\xd0x1IB`I\xc9\x1fW\xac`h^\x1e\xd3\x06\x0e\xc4\x19\xf4G\x1d\ +\x98\x9bKvj*\xe5\xb5\xb5\x8d\x1c)>L\x98\xa0B\xdd\xde{\xef\x08)]\xa1\xd7\t\ +\x112\x08\x04\xa1-^Od\xddJ\xc0E`\xd1\xf3PT\x02A\x93\x1f\x87\x93\xfd\xb7\xeb\ +\xc1\x07\xa9\xdd[\x86\xd3ax\xbb\x17\xd1\x82\xc3Q\xc2\xa33I@\x95e\xb1\xd2\x99\ +M\xed\xe8;02\xfb\x81\xbbF}\x81XV{\x0elh9\x1e\xc38\xe4:\x81\xcc\x1c\x88t\xf6C\ +Xuv\x9a\xb1\x81\x14\x02\x84\x85!M\xa4\x14\x08\xe38DfOd\xd9\xaeX\xf6\\\xd3\ +\x0c\xad\x16\xde\xc9\xfd\xfa\x01\xf0eY\x19?\x7f\xef=L\xcb\xe2\xee)S\xf8\xedi\ +\xa7a\xe0KUt\x1a\x86\xb70z\xac\xd3\xb4I\x15K\xfc\xfc0\xd2\x81\x10\xe04 \ +YZ$\x1b\x867\x99@\x08\x81%\xa4\xb7\xdaA\x87\xc6\x9b\x08\xda\xf8\xdfGH\x07\ +\x86%\xec\x1a\xe8n\x84a }RX\xb6\xac\x1c\xc3\t\xc7Mo\x9b\ +\xc6U\x97\xfa\xcd\xa3\x06\xb0\x9f\x86\x07\xec\x85O|\x97\x00\x1f\x87\xd8\xc6\ +\x9fO\x9aY\xdfB,!pKpK\x81\x89\xaa\xb7nJ\xc3N\x8d\xf6\xccx [\xf1\x8a&\xc2O{\r\ +,\x04nia\xaa2\xe1\xde\xae\n\t\x0e\x8f\x9b:H\x8f<\xf1\xb1\xbeW\xa8v\x1a.\x94\ +\x01?\x13L\x9e\x9a\xb9\xf4*\xa3\xd8.\xa6\x0e\xca\xb7k\x00\x9e\xf8`\xe99L\x82\ +\x9d\x97\xa6u\xc2\x9b\x95\x9aJfJ\n\x0f\xfaY\xbb\xfel?r\x845\xfb\xf6%l\xf0\ +\xf6\xb9\xe7v\xc30\xa0\xbc\xdcMV_\xc8\x1d\xd86\xff\xee\xd1\x12\xbf\xb9\xd4@E\ +0\x04\x1fO\xa0\xcaC\x1a(\xab\xf7\x87\xa8\x8au\xa1.\x91D\xc5\xfcF\x08K\xd89\ +\xffvM\x00)\xecY\x0e\xbc\xb1\xa3 \xf1Y\x90\xb2\xd1\xffZ\'\xd1a\xbd\xecLY\xcf\ +\x85\t\x1c\x03\x14HCb\tK\xb9M\x84\xbf\xa8\xf8]>?\xe1\rP-\x8f\xf5h_\x87\x80\ +\xfd\xbc\xd6`cb\x1d\'\xec\x07\x80&?\x9f\xc2\xb2\xaf\x99@H\xc3\xbe^R\x85\xdfy\ +N\xda\x90m(\x85\xa5\x89\x16\xad\xfa\x938\r\x83\xd5\xa5\xa5\xbc\xe1g\xed\n\ +\xc3!\xfc\xfd\xb7Kv\xeeL\x98\xf0\x15\x7f\xff\xae\xd3)\xb8\xec\xb2\x1el\xd9R\ +\xcb\xd7_W3\xe4\x1c\x10\xcd\rt5C\xaf\xf10|\xb6\xdf\x82#!6\x12\xc0@T*1\xc0\ +\x0eT\xe5\xb2P\xe2[\x8f\x9a\xde=\x12\xf8\xdd\xb8\x06\x86\xfaC\xdb\xb5\x00$ \ +\xa5\x05.7\xb2\xd6\x85U\xe3\xc2\xaaw\xa9p-a\x8b\xb2\x90>q\x16\xb1{y\n\xc4\ +\x18\x08e\xd1y\xaf\xa3\xf0\xaeo\xf0\x95\xef\xd9\xc8\x16Q\xff\xcb*\xa4DX\x9e\ +\x8d\xfc\x0f\xe8\xc9\x80\xb3\xbf\x94\xda\xe1\x1ce\xde/+\xf5\x07\xf5~Y\t\xa4J\ +\xc4\xb0\x97k\x12\x8fV\t\xef\x84\xc2B\x1eZ\xb2\x84z\xdb\xda\xf5\x17\\\xcf\ +\xef\x1f\xd9\xd3\xba\x07|\xf8\x13\xa0&\xef\xc4\x89\xca\xcd\xf0\xee\xbb\x87\ +\xa9\xaf\x97\xf4;\x85\xb6\xbb7\x1d0\xfd/~\xb1\xc0e4\xfeh8\r8\xc1ns#0\x06x\ +\x15\xa8\x00L\xd4\xf4?\xabQ\xf3\xafE\x00ik\x8aa)+\xd7\x12\x80t\x83e\x82i\x82\ +\xcb\xc4HI\xc6\xd1\xbd\x07\x8e\xfc\x1e\x88\xae]\x90\xa6\x84z\x0bC\xaa\xfc0\ +\x7f\x83Q\x88(\xbe\x90\x08)\x94hx\x85\x10{\x84^\x89\x88\x90\x02\x03\x87\xaa\ +\xb7\xea\xb1Z\x85e\xfb\xae=\x16\xb0\xf0\xfc\x1f\x80\xd2 \xdb\x92\x0e\xf8`z\ +\xd5)2\x17=\x92\xb4\xa4k\xb6\xfbEY\xbe\xbe\xa7\x13\x95Zm\xf9.K\x02\x9e^g\xa7\ +U\x83k\xfb++y\x7f\xeb\xd6&\xb7\xd9|\xf0 Un7\xa9\x0e\x07\xdf\x1d>\xdc\xa6\xce\ +E\x92i\xd3\xba\xe1p\xc0\xe2\xc5\x87I\xcf\x83\xfeg\xb4\xfd\x98B@z/\x98\xfd\ +\x0c\xcc?\x07%\x9ee@>\xa1\xef\xfe\x99\xa8\xb2\x90o\xa1f\x99\xb8\x00\xc8\x03\ +\xba\xa3\xfc\xc3\x11\xbc\\\x12\x15\x0b+\x84i\xfbJ%\x06j0Fb r\xb31F\x8f\xc69f\ +,"\xc9\x89\xb9{\x07\xf5+V\xc2\xce\xdd\x08S"\x1dN\xfc\x8beF\xf5\x9b\xd3\xef\t\ +\xc9\xa3\x9fM\x89\x85\xf0l)P\x96\xaa\xe5\xb4\xad?\x13\xc32T&\x18\x00\x06\xd2\ +0\x94\x15\x8d\x89\x90\x86\xaa\x93\x80z\n\xb0\x0c\xec\xba\t\t\xf3\x90\x16\x82\ +\xa6;\xa6\xbc.\rS/\xbc\x0f\x01Zt\x13\x92\x16\x0boVj*\x0e\x87\xc3{?4f\xc5\x1e\ +\xed\x0c\ +\xe8\x92\x81\xac\xad\x81\xdaZ\xa8\xab\x85\xdaZ\xa4\xe5&i\xc2\xc9\xa4\\r\x15"\ ++[\x99\x81V\xe2=\xab:\x1c\x82\x1a\x97\xc9\xd2/K)=T\x0b\x8e\x14\x8c$\xc3v\x19\ +Hp\x1a\x88\x94T\xf6\xec\xad\xe0\x9d\xa5;\xb1\xa4\xc4\xe1TWCH\xdfu\xf1\x0b\ +\xa5\xd0hbJX\xc2\xdb\xdc\xe3\xef\xee\x8a\n\xde\xb5\x07\xd9\xe2Mv\xb6\x83\x99\ +3s\xd9\xb6\xad\x8e\xd5\xab+\xc9\xea\x07\xd9\x03#\xdf\x8e\x10\x903\x14\xae\ +\xfd\x102\xcaQ1\xbbqDY\xf4\x12\xb0\xd4\xcc\xab\xa6\x84\xd4t\x1c\xa3\x8fG\xa4\ +\xa5#,K\r\xbcIC\r\xca\x18\x12Q\xefB\x18\x0e\x8c\xc2B\x1c\x85\xf9\xe00Tzn\x02\ +!\x00\x87\xc3\xa0\xce4\xf9\xf0\x8b\xbd\xbc\xf2\xfevv\xee<\x8c\xcbeB\x92\x81p\ +&Q_\x0f%{+\xf8\xcf\xe2\xad\xbc\xff\xd9\x1e\xa4\x00\xc3\x93:-\x85\x8at@\xda\ +\xa2\xeb\xf5\x86\xc6\xeb\x944\x9d\x90\x16\xb9\x1a<.\x84\x96\x1e\xb4\xda\xe5b\ +\xc3\x9e\xf8\x14\x95\r\xee\xe7\x15W\xf4$/\xcf\xc9\xdbo\xabh\x86\xe3\xbf\x07\ +\x8e\x94\xe8\xb4-\x04\xf48\x01\xae~\x07\x9e\x9e\x02\xf5\xe9\xa8)\xdc\xfd\xef\ +\xef\x18a\xa1J\x93:\xd4\xfc2*\xaa\xc1\x92Xu\x12\xe9\xb1b\r\x8f\xa1m`Y\x12\ +\xc3[\x92\xd0B\xa4$\xc7\xde\xe9\x19\xe0hm\xfc\xe3f\x08\xf5\xaaw\x99<\xf5\xea\ +\xb7l\xd9y\x94YS\xfb\xd1\xbf(\x8b\xa4$\'\xdbvU\xf0\xca\x07\xdbY\xb2j/5\xf5\ +\x12G\x92\xfd8a\x1b\xb8\x9e8s\x87!\xb0\xec\t\'\r?\xf7\xb2\x7f\x0f\xfc\xaf\ +\x80\x7f|z\xdb\x0b\xceh\xa1\xef\xcc\xb4iz\xf7\xc6\xb0\xa4\xf4\x86\x95\xc5\ +\x93\xd4T\x83\x1bnP\x13o.\\x\x10\xe1\x801WE\xd6\xbf\x1b\x8c\x10\x907\n~\xb8\ +\x04\x16]\x0f%\xaf\xa0|\xbe=h\xf5\xd5n[\x18\x9e\xb4\xbd\x04\xca\xc7\xeb\t\ +\x11sX.<\x05\xc5-$`\xaa\x9c~\xe1\'z\x16j\xa0*\x1e\x8e\xcff,l{\xe8\x0c\x90\ +\x18\x86\xc1\xb1z7\x1f\xac)\xe1\x8bo\xf7\x93\x92\xec@ \xa8s\xb99ZUG]\xbd\x85\ +\xd3)\xfc4Nz\x13\x0e\xc0\xf2M*\xe9u\xfe\xfa\x92E\x02\xdb\x94\xfeo"*\xba\x9ec\ +\xcb\xe0\xd8\x04\xad\xcb\x1d\x9a\xa8\x08/\x10\x97\x81\xb5`kw\xc8\x904\x86\rK\ +\xe3\xe8Q\x8be\xcb\xca\xc9\xec\t\xdd\x87G\xdf\x90\x13\x02\xf2F\xc3\x15o\xc2\ +\xab\xd7\xc0\xa6\xa7Q\xf5x\x8f\x03\xfa\x01\x83\x81t\xa2\xfb\r\x80\xaa\xad!\ +\xd4<\xe2 $\x96\x10H\xe17\x9f\x83}!\x84\xb0\xb3\xc1\xbcaX1\xce\xed\xf7\xc4\ +\xe5J\x19\xb2]\xd9\xe0\x17O\xbfUM\x90\xdaZ\x93\xa3U\xd5\x98\xa6\n\x19\x13\ +\x0eIr\x92\x83$\x87C\x15\x8b\x91\x02\x95,-T\x88\x87\xb2q\xbdS\xf0x\xdan\xd0\ +\xa0\x08\xeaOD/\x8ag\xce\t\xdf4=>\xf7\x90o P\xd31\x89\x9a\xf0z\x88g\xf2\xc4m\ +\xb7\xf5\xc10\xe0\x83\x0f\x8ep\xf0\xa0\x9bS~\x0e\xce\x18\x95\t\xf6\x0c\xb8]\ +\xbc\x10\xbe\xfa\'|x\x1fT\xae\x05\xd6\xa2JC\xf6GM\x07$\x80B\xfb\xe7\xde\x08\ +\xf7\xc1\xfb\x9b\x9d\xed%|b\xeb\x95\x14i\xf8\xa2\xb8<\xbe\xcf\x18\xdf\xf4R\ +\x08\xb0,;\xd4\xcb\x1bv\x80O\x9c\xf0%s\x08\xec\x08\x05[|\x11$;\x1d$%\x19\xa8\ +\x8f\xb3g\x94\xd2>\t\xe1\x8b\xf7\xf5/;\xee\xfb\x17oz\xad@\x82\xe5\xab{\x80\ +\x9f\xd5\xef\x9d9\xa2\xad\xe7\xea7\xa6\x03\xab\x9d\x00\x00 \x00IDAT\xe7\x9aD\ +\x15wG\xa8A?<\xa7\x06\xeazxS\x9f5\x1d\x8d\x16\x0bok\xfd\xbc\xb1&\xb8o\xa9\ +\xa9\x06g\x9c\x91\x83\x94\x82\xb9sw!\x1c0\xf2\xd2\xd8\xba-\x85\x00\x91\x04\ +\xc7\xff\x10\x86]\x08K\x1f\x80\xcf\xfe\x0cV=\xb0)\xcam#\xb0\xa4\xcf\x87\x0b\ +\x96\xa7J\x83\xbd^\xda\x05V\x14\xc1\xa2\x12\xeb\xa2\xd9^#\x13\x8f\x10\xfa\ +\xd5\x8f\xb03\xd9L!1\xf0I\xab\xaf\x9f\xd2\x8e\xe9\xf5\x1c\xc9c\xd5J\x8fk\x17\ +5s\x9b\x0c\xb2\xe5=\xd6\xae\xe5{/\x95\xe8z\x8e*\xed\xf40\xe1\x11\xfb\x08\\\ +\x16!\x84_L\xb1\xf0\xa6\xf9J{\xc0\xcf\xdf\xc2O\xd8\x1bN\xd3&\xa2n\xf1\xc6\ +\x8bK.\xe9Aaa2{\xf7\xaah\x86n\x83\x95\x9b!.\x08U\xf7\xf7\xcc\x87\xa0\xf8\x06\ +\xd8\xfd\x19\xec\xf8\x08\xccz\xb5\xbat\x1d\x94\xae\x0f\xda\xa5\xcdO\nJq<7y\ +\xe8~\x85\x9al&\xf6\x0f(\xc2\x13`\xa04N\t\x8ea\xf8"\xbd<\xc6\xa7\xb4ly20\xa4\ +\xc7\x12\r\xees\xa0U\xeb\xaf\xe8\r\xceVz\x9d\x0e*\xd1\x04\x810\x04>C\xd3\xd7\ +!\xf5\xcc\xe0\xa0\xcd\x11\x98B\xf8\xd2\xc9\xec\xac9i\xff\x1e\xe0\xda\x10\x0e\ +\xedj\xe8\xc0\xb4Jx[b\xf5\xfao\x13O7\xc3\r7\xa8\xd8\xddw\xde9\x82i\xc2\xa8\ +\x8bi\xdb\xa4\x96m\xc4c@f\r\x84\xae\x03`\xe4\x95\xf6\n\t\xef\xdf\xd9Px\xa3E\ +\xf0\x1f\xcf\xdf\xb2\x8dWU9\x9fCA\xda\xb5\x83\xd5#\xbe\x9d\x12\x81\x1a \xb4p\ +X\xa6\x8a\xd0\xf0\x96k\x10~\x83e\x8d\xd0\xd4\'P`\x1fHxS\x89\x85\x10*$D\xa8\ +\xb6\x0cK\rBZ\xc2\x81\xd1P\xba\xc3<[\xdf\xd4\xedRJ\x0c\xc3\xa1\xeadH\xcbv\ +\x8dXHa\x92\xc8S4i\xdaF\xab\xbf\xbe\x13\xa1\xe0M0\xc1_\x06C\x87\xa63zt\x06 X\ +\xb0\xe0\x00\xce4\x18qI\xe2<\xb6\x05<\xc5[\xb0#\xce1\xbf\xf1F\xf8U\xccQ\x95$\ +L\x04\xa6]8L\xd8\x95,\x05R8\x91\xd2iW\x1b\xc3+\xaam)\xd0\xe3\xf1[\x08!0\x0c\ +\x81\xe1\xf1\xcf\x08\x81\x90\x0e\x10\x06"\xe4\x93A\x9b\xcf\xda\xfe)\x91\x16\ +\xb6\xaf\xdd\xe1k\xcb\x12:\xb9\xc3\xc6[\xea\xd3\x90|\xbd\xab\x0e\x97)\xe3f$D\ +\x02i\x99\xed`\x02\xfa0\xb8\xfb\xee\xbe\xa4\xa5\tV\xac\xa8\xe4\xfd\xf7\x0f\ +\x93?\x12\xf2\x86\xc6\xd6\xbf\xdbR\xea\xab\x82\x8a\xa9wJ|\x83HB*\x83\xd3\xf7\ +\xb8\xaf|\xa0\x86\xf2\x9b4\xd0"\x11\x81\x97\xc7\xba\xb6\xa4\xb2\xb8=cr \xb1\ +\x84\xc4\x14>\xdf\xb0\xd7\'.\xc3\x7f\xd9\xa3}>\x0b\x1bSY\xd8\x98\xb6\x87\xc8\ +P\xb6\xbe\x16^\xa4\x94\xd4\x9b\x92G\x17\x1ea\xf4U;\x187g\x07\xcf\xbc]\x1e\ +\xefn\x85\x8d\xc7H\x8c\x8a\x8f7\x96Vq\xb0\xb5\x9b\x96f0yr\x16\x00\x8b\x16\ +\x1d\xc44a\xec\xd5x\x82\x89\x12\x8e\xfd\xeb\xa0.\x02\x93[\xb6k\xa4\x00,{`\ +\xcb\xf0\x8a\x9ba\x1b}\xfeC\x80\xca\x0f\xaa\x12=\x84\x9dp\xd1f\xebGH\xb0\xa4\ +\xb7\x0c\xa5\xea\x87\xb2~\x85\xf4\x84\xe5\xf9\x15L\x0f\x18\xde\x0b\x0f\xcf\ +\x10\'\xa8\x88b\x87]\xff\x18\xe9\xf1#\xbb\xc10\xdb\xdcN\xbbG\xc0\x9c\xdf\xee\ +c\xe1\xc7U\xdeE\x7fz\xe1\x08W\x9d\x9dEzr\xfb\xbdM:\xdc\xe0\xdae\x97\xe53p`*\ +\xe5\xe5n\xfe\xfb\xdf\x03\xa4f\xc3\xc8\xcb\x13\xd3\xda\x95R\r\xacuzl\xdd\xf5\ +\xfc.,\xa1\xe6\x0b\x13\x127Pc\xa4p\xcc\x91nOr\xd9 \xd5\xa0\xcdH,\x8c$\x01u.\ +\\G\xab\xb0\xecd\x12C\n\x84\xa5DX\x1aQ\x10@\xe9\xb3\xf5\r\xe5\xefP~^\xe9\xc6\ +U_G\xaai6[\xf2\xa3c#\xf9h\xdd1\xdeXZ\x15\xb0t\xfb^\x17o}^\xc5\x05S2c\x1e}\ +\xd3\x16\xfc\xc7\xbe\xda\xb5\xf0\x06[\xbb\x86\x017\xddT\x80\x10\xb0x\xf1\x11\ +6o\xaea\xf8\x05\x90\x92\x13\xaf\x1e6\x8d\x00v|\x12\xef^\xc4\x1f\xdf\xd0\x9a_\ +\x94\x01\x80\x10d\xe2\xa6\xf7\xb1=8\xa5\x8b4\xcb\xa5\x02\xe3\xa4\x8c\xec\xe3\ +\x8b\x90\xb8\xdcn\x9c\x19Yd\r\xed\x830\xdc\xca\xef*\x8c\x80v"9\xd6%\xedA=o|\ +\xb0\'\xb6\xd7\xd6\xdfc5uT\x94\x1e\xc4i\xb4\x1fa\x89\x06+6\xd4\xe22\x1b.\x9f\ +\xfb\xdca\xa6M\xcc #\xa5}\\\x9f\xe0\x80\x83v-\xbc\xc1\x9cyf\x0ec\xc6tAJx\xe1\ +\x852\x00\xc6\\\xe9\xbb\xb1\x13\r\xab\x1e\xca\xbe\x0e\\\xd6\xe9\xdc\x0c\xf8\ +\\\x06H\xe9\x15U\x13\x13\x87!\x18aVs\xf3\xb6g\xa9\x16\x02\x87\x1d\x91\x1c\ +\xd1\x04\x0f\tN\t[+k\xc9=g:7\xbd\xb0 a\xae\xff_\xfe4O\xf6\xc9\xfc(a?\xbf\xd1\ +FJ\x98:6\x1d\xa7\x03\xdcA\xe2\xfb\xd5wu,\xfe\xa2\x9a\xd9\'g\xb4\x1b\xab\xd7\ +\xff\xde\xeeP\xc2{\xfd\xf5\x058\x1cp\xf8\xb0\x9b\x95+U%\xb2\x81g\'\xa6\x9b\ +\x01\xa0\xe6\x10TDh\xaa\x9f\xf6\x8c/\x9bN\x89\xb0\x85J\xf70\x84\xa0\x07.\xba\ +\x99\x07\x03\x12,"\xecg \t\xe8\xe9\xb6p\xa4\xd4D\xf0\xc0m\xe7\xb6;\xae\x17\ +\xe5\x9b.\x93\xec\xd9o[\xc2\t\xfaA\x8e\x12B\x08N\x1c\x9e\xca_\xee\xcc\xe7\ +\xb6\x87\xcb\x1a\x88\xef?^/g\xd6\xe4\x8c\x84\xffR\x92\x96)\x83\r\xaav+\xbc\ +\xc1n\x86\x1e=\x92\x98:UM+\xf1\xdak\x87(-\xadg\xea/\xc1h\xaa y\x9c\xd9\xbb\n\ +\\\xd5\xf1\xeeE\xfc\xf1\x1f\xaa\x12\x9e\x18]\xbb\xa6\x82\xd3\x80d\x873(\xc97\ +x\xef6 !\tI\x86QG}\x02N\xc7\x9b\x9e\x91\x8d;\xc9\x85\xa8o\xe3\xcc\xac\xed\ +\x14\x81\xe0\xba\xe9Y\xac\xdd\\\xc7\xbc\x85\x81\xd1\x0cK\xbf\xaca\xefa\x93>\ +\xb9\x89-c\xa1\x9eb\x13\xbb\xc7\xad\xe0\xf6\xdb\xfb\x90\x9b\xeb\xa0\xaeN\xf2\ +\xc8#\xbb\x11\x0e\x18pVl\xad]\xaf\xeb\xd1\x84#\xdb`\xd72\x90~\xdf\xd2}N\x84\ +\xbc\xe1\xb6\xa6\x00\x077\xc6\xaeo\x89\x8e\'\xb4J\xe2\xff7S\xefC\xb8\xf8"\ +\x8e\x0b\xb0\x120|\xcb\xb2\xdc\x9dVt\xbdH\xf8\xd3\xad=p\x9b\x92g\x16Ux\x17\ +\xd7\xd4K\xfe\xba\xe00\x0f^\xdf=\xc2\xc3\xad\x91#\x94\xb5\x0b\xedTxCe\xcf\ +\x9du\x96\x1aA\xdb\xb1\xa3\x8eo\xbf=F\xcfQPxR\x83]\xa3\xd3\x1f\tXP\xb2L\xcd\ +\xbb\xb6s\x19\x94~E\x83H \xe1\x80\x9ec`\xe4E0x\x06\xecY\x15\xb4\xbe\x13\xfaw\ +\x83\xf1\xde@1\xba\x12\xd2\xdf\xdcND\xbcI"\x9d\xf7\xa3!\x84\xc0)\xe0\x0f7\ +\xf7`\xed\xe6Z\xd6|[\xe7]\xb7\xf0\xe3*~1\'\xaf\xdd\x0c\xb2yh\x97\xc2\x1b\xcc\ +\xb4i\xb9\x8c\x1b\xf7\xff\xd9;\xef8)\xaa\xac\r?\xb7\xbb\'\x07`\x98!\xe7\x9cs\ +\x10\x04\x04\x13( *\x86UQW\x17\xd6\x0c\xba\xba\xabkZPw\xd7\xb4&\xf4\x13P\x8c\ +\xa0\xae\x19Ae1\xa1\x02*9J\xce0C\x9c\x1c;\xdd\xef\x8fS5\xd3\xdd\xd3=\x01&t\ +\x0f\xfd\xfel\x99\xae\xae\xae\xbaU}\xef[\xe7\x9e{\xce{\xe2\x01x\xf3\xcd#8\ +\x1c\x9a^\xd7JHdu\xff\x1cZC^*,\xbe\x1b~\xff\xcc\xdb\xc2-\xb5\xaf\x0b\xd2\xd6\ +\xca\xeb\x9b\x07\t\xee\x01\x1fF\x18A\x86\xb8H\xc5\x9c\x07\x9a2\xe6\xee\x83\ +\x9c\xc8\x94\x81\xb6/\xd5\xc1\xbc%Y\xdc2\xae\xbe\x87\xc4gm\xb6\xd2\x1b\x81\ +\x8c\xa9\xe0sj\x9d\x02n\xba\xa9\t\x16\x8b\xa2\xb0P3o\xde\x11\xa2\xeaA\xaf\ +\xeb\xaa\xdf\xcd\xa05l\xff\x08f\x0f\x84-\x1f{\x90\xae\xcd\xe7\x15\x0ft\x01F\ +\x02\x7f\x00\x86!%\xdd\xc3\x08#\x8c\nC)E\x8f6\x91<;\xb5\x116\x0f\xdd\x95\xe7\ +\xdfK\'\xcf\x1e|i\xc4e\xe9\xda\x84\x9c\xc5\xeb{1M\x9aDr\xfe\xf9\r\xd0Z\xf3\ +\xeb\xaf\xd9\x1c=j\xa7\xc3\x18\x88\xf1Wb\xbd\n\xe1\xb6\xc3\xaa\x97a\xc9\xfd\ +\xe0v\x1a\x1b\x9b\x02\xe7\x01\xcd\xf0~\xa4Y\x90\xa5s\xb3\xe5\x9d\x80Q\xc0;\ +\xc0\xfe\xeamg\x18a\xd4%(\x14W\x8dJ\xe0\x8d\x85\x99\xfc\xb4N\xa2P\xf6\xa59\ +\x99\xf9I\x06\x0f\\\x93t\xda\xc7\x0f\xe4\x93\xadj\x84\xbc\xc5{\xe9\xa5\xc94h\ +`\x05\x14\xaf\xbe\x9a\x8a\xcb\r}\xfeH\xb5^\x99\xdb\x01_\xde\x0e\x8b\xef5H7\ +\x02!\xd2\x9b\x80vHi\xf7(\x8fW\x84\xf1EO\x81\x00\x0b\x10]r\xcc\xb0\x7f7\x8c0\ +*\x06\x0b0\xfb\xfe&\xc4z\xf8u_\xfd8\x03\x8a\x87PpY\xbe\xfe\x10\xd2\xc4\x1b\ +\x17g\xe1\xe6\x9b\x9b\x00p\xfc\xb8\x83\xa5K3\x89I\xaa\xfa\xf2\xed\x9e\xd0\ +\x1a\x96\xff\x1b\xd6\xce564\x01n\x06F \x04[\xd1\xf3\xba\x81\xd0\xd5\xfa\x08#\ +\x8cZ\x83R\x8a\xb6M"\x18;,\xbex\xdb\xd1t\x17s\xbe\xc8\xc0\xad\\`\xa9\xfdD\ +\xeb\xf2\xe4sC\x8ax}/\xa6{\xf78\xfa\xf5\x93|\xed\x15+\xb29~\xdc\xc1\x80\x9b\ +\xc1\x16WM\xe7\xd7pl-\xfc\xf4ocC=\xe0J\xc0\xd7\xada\xb6\xd2\xac4\x91\x01\x14\ +\xe1M\xcak\x80\xa3\xd5\xd3\xce0\xc2\xa8\xf3\xd0\xf0\xe2=\x8di\xd2\xb0\xc4\ +\xd9\xbb\xe0\xa7\\\\\x16\xd7i\xe9nWe\xa5\x9d\xb2f\xb1!\xe7\xe3\xf5\xc4\x88\ +\x11\xf5\xb1X\xa4z\xc0\xff\xfd_*\xca\n]&R-\xa1\x0cZ\x83=\x13\x16L\x01g!r\xe7\ +\xae\x06\x1a\xf89\x9f\x1b\xf8\x0e\xd8\x08\xe4\x19\x9fG\x02\x89\x94\x90rz\xd5\ +\xb71\x8c0\xce\x14(\xa5H\x8a\xb70nX<\xaf/\x90\xd8\xde\xefV\xe6\xb3tC>\xe7\ +\x9e\x1fv5T\x1b\x92\x93#\xb8\xe7\x9e\x16\x00\x14\x15\xc1o\xbfe\xd1\xb474\xe9\ +[M\'\xd4\xf0\xe3\x0cH[g\xbc?\x07YLS\xde\xfb\xe0\x02~\x04~AH\xd7\xdc^\x04\x1c\ +\x07N\x18/\x8f\xd9P\xd8\xbf\x1bF\x18\x95\x87B\xf1\xa7K\xeac\xf5`\xb1\xe5\x1b\ +\x0b\xaa\xa6NH5\xd7\x97\x0c\x19\xe2\xf5\xbd\x11C\x87&\xd2\xa4\x89\xacZ}\xf6\ +\xd9q\xb2\xb3]\xf4\xbb\tT\x84\xdf\xaf\x9f\xde\xb95\xa4\xfe\x02\xbf\xbdbl\xe8\ +\x0f\x9c\xed\xbb\x13B\xa6\x0b\x80\x9f\x8dm6\xa4\x9apo\xe0\xcf\xc0\xad\xc0uU\ +\xdf\xbe0\xc28S\xd1\xbb]\x14\xe7\r*\xf1-~\xbf:\xbf\xd6\xc5Y*B\xda!\xe9jP\n\ +\xee\xbe\xbbEq\xdd\xc0\xaf\xbeJG\xd9\xa0\xe5\xb0\xea\xb9\xe7\nX\xff\xb6\x11\ +\xc1`\x01\xce\r\xb0\xe3\x97\xc0& \x16\xb8\x0b\xb8\x01\x89r\x880\xbe\xa7\x10\ +\xf7\xc3\xfc\xaaoc\x18a\x9c\x89\xb0\x00\x93\xc6$\xf2\xcd\xafyh`\xcdF\'k\x7f\ +\xb7\xd7v\xb3\xcaEH\x12o\x83\x066\xfa\xf4\x119\xb8\xbc<\x17_\x7f}\x92f}!\xb9\ +{\xf5\x9c/\xe7\x10l[d\xbc\xe9\x86\x84\x8b\xf9\xba\x18V\x02\xeb\x81^\xc0k\xc0\ +@J\xfb~5\xb0\xaaz\xda\x18F\x18g"\x94R\x8c\x1d\x12O\xe3\x86V\x8e\x9ct\xe1t\ +\xc2\xdb_\xe6T\xc9\xb1\xab3\xa67$\\\r\xbe\xa6\xfbu\xd75\xa6~}+Zk6m\xca#=\xdd\ +I\x9f\xeb\xa1Zd\xdd5\xecX\x08\xb9i@\x1cpA\xe9\xcf\xd9\x0f,\x01\x92\x80\xcf\ +\xf0O\xba&VTC\x1b\xc3\x08\xe3\x0cF\\\xa4\xe2\xb2\x91\t\xc5\xef\x7f\xf8\xb1\ +\xf6\xc3\x85\xca#\xec\x90 ^_\xdcxc\x93\xe2\xbf\xdf~\xfb(ZC\xa7q\xd5\xe7\xda\ +\xd9o\xfal\xdb#\x91\t\x9eq\xdaN\xe0s\xe3\xefg\x10\x9fnY\xed\xf0)\xf5\x13^X\ +\x0b#\x8c\xd3\x83R\x8a\x8b\x87\x96\xc4\xf4\xee\xdd\x9bQ\x8b\xad\xa9\x18B\x8e\ +x\x1b7\x8e\xa0{\xf7X\x94R8\x9d\xf0\xd5W\'I\xee\n\t-\xab\xe7|\x19\xbb`\xdb\ +\x17\x08\x99\x0e\xa2tR\xcc\xaf@\x160\t\xb8\x91\xb2I7\x03\xd8[\x1d\xad\x0c\ +\xa3\xce\xa2"k\xeb\xba\xa4\x04\xba\xf7\x0b\xcf\x02\xceu\x1ag\xf7\x8a!\xb9\ +\xbe\x04\xf0:\x9dU\x97@Q]\xd1\rAO\xbc\xbe\x17>hP"QQ\x16\xb4\xd6\xec\xdcY\xc0\ +\xe1\xc3E\xf4\xbc\x8a\xd3\n\x9a.\x0b\xc76\x83#\x1fH\x06\x9a\xe3M\xac.\xc4u\ +\x10\r\xfc\x8d\xf2\xe3\x87O"\xe4\x1bF\x18U\x82\x92\xea\xcbf\xdd6\x8d\x0f\xf1\ +\xc2\x19Q7(.\xd2B\xff.Q\xb5\xdd\x8c\n#\xe8\x89\xd7\x17\xe7\x9e\xdb\x00\xb3\ +\xc3\xcd\x9f\x7f\x0c\xb7\x866#\xab\xc7\xcd\xa05\xe4\x99\xee\xa2N\x94\xb6\x1c\ +\xd2\x80B$\xa6\xb7;ewp\r,\xf7s\x8c0\xc2(\x0bF\x9f\xf2\xa7\xbce\xc6\xabj\xad\ +\xc0m\x88\xc8k\xd9.\xe3\xc1\xa8\xd6v\x06\xf49\xed\xd6L\x18\x91\xe0\xf1\xbez\ +\xe3pO\x17!E\xbc\x16\x0b\x0c\x1bV\xcf\xa8\xc6\xaa\xf8\xe9\xa7L\x12\x9aB\xb3\ +\x01\xd5s>\x05l\xfb\xdcx\xe3O\xc6\xd1T%\x1bU\xc1\x83\xfdV\x15\xad\n\xe3\xcc\ +\x82F\xc7\x14\x96\xf1\xb9\xa8.\x15W\xad\xd3%\x95@\x15b<\xe83\x80y\x95R\x8c\ +\xe8\x13KD\x88\xc4i\x055\xf1\xfa>\xb5\xda\xb5\x8b\xa1gO\t\x96>q\xc2\xc1\xe6\ +\xcdyt\xb8\x10\xac\xd5\xa4\xcd\xa0\x80\xdcc>\x1b\xfc\xe1\xec2>3\xe1\xa7\xef\ +\x87\x17\xd6\xc2\xa8\x10\x02\x94$2\xdd\n(\x8dV\x86_S\x95X\xc1Z[\x82B0\xa6\ +\xa6\xd0\xa1y\x04\x1d[V}\x99\xa4S\xb1\x9e\xeb\x94H\xce\xd0\xa1\x89DF*\xb4\ +\xd6\xac[\x97KV\x96S\x94\xc8j\xaa\x01\xbe\xb7\xb2)\xe2[\xde\xe4\xe73\x7f\xd8\ +R\xfe.a\x84\xe1\x05\xad\xc0\xea\xbfs)\x14\xf2\x9fa\xe5j#KG\x99i\x94\xa0\xdc\ +\x16Th\r\xf3S\x87\x86\x8b\xcf\x8e/\x7f\xbf @H\xfd"#F\xd4\xc7\xf4[-[\x96\x855\ +\x1aZ\x8f\xa0\xda\x98\xd7\xed\x00{\xae\xf1\xe6\x98\x9f\xf3D!\xb2\x90\xdfW\ +\xe0`\x1a\xd1j\x08#\x8c\xca@iT^\x80E#mA\x19\xae\x05\xa5-(e\xae0k)\x83\xa34\ +\xfaLXY3\xa1\xe1\x12\x0f\xa9\xc8`\xf6\xf3\x86\x88GD\xd0\xa1C\x8c\xe1\xdf\xd5\ +\xec\xd9S@bs\x88kT}\xe7\xb3D@\xa4\xf9;\xe6\xfb\xd9A!q\xbb\xdf\x00G\x10\x12\ +\x0e\xd4\xcf\x1d\xc8B\\\x18\xb5\x02\xadK(\xc8\xd42\xd2@\xa1\xd6\xecpj\x0eip\ +\x9d\x0cF\xc98\x83X\xfd\xac\x1ek\xe5F+-\x06\xae\x92\xb81\x8d\x1b\xb92\xb1\ +\xa9\x94r\xe3\xa5\xc8T\x87\xa1\x94\xa2[\xdb(\x92\xebY8\x91U{\xd7\\\x11\x17b\ +\xd0\x12\xaf\xef\xd3\xcaf\x83\xe6\xcd\xc5\x7fST\xa4Y\xb1"\x9bN\xe3\xa89\x9b}\ +/\xb2\x98\xe6y\xc74\xd0\x11X\x06\xfc\x15)\xe5\x13\xe8\x96g\x00\x87J\xde\x86\ +\xfd\xbb\xd5\x0f\xe1#\xa1\xa2\xbd.\xcd*\x87\x9bT\x97f\xadS\xb3\xd2\xe1\xa6H\ +\x0b%\x15?\x0f\xbf\xfd\xd1\xfcZ\xb9\xa8\xb1\xdfO\x03\x167\xdam)E\xbeJic\x01M\ +\x1b\xe1\x0cN\xa3\xf5\xb2\xf8\xac\xb42|\xbegNW\x8b\x8fR\x9c;(\x8e\x0f\xbf\ +\xa9\x9a\xb4\xe1\xeaB\xd0\x12\xaf/Z\xb5\x8a\xa6uk\xa9\x95s\xfc\xb8\x83\xc3\ +\x87\x8b\x18r^q\xc0L\xb5@\x03\xcd\xfa\x19R\x90Y@*\xd0\xd2\xe3\x84\nh\x01\xb4\ +\x06\x16\x02;\x91\xb0\xb3@\r:s\xfa\x7f\xadAk\x8d\x05\xc8\xd3\xb0\xde\xe9fq\ +\x91\x9b\xe5\x0e7\xbf;uq\x10J\x95\x9c\xc7\xc70\xa8>"vCL\x11\xe4\xc5\x94\xfaD\ +i\x9b\xf7\xc2\x9b\xd9"\xb7EB\xca\xcc(\x07\x1dR\x1e\xc5\xd3\x83\x86\x8b\x87\ +\xd5;e\xe2\xad)\xf7D\xc8\xfc"\xc3\x86\xd5\xc7f<&V\xad\xca\xc1\xe9\xd64\xea^\ +\xbd\np\x1a\x18p+%\x84\xb9\x8d\xd2\xe4\xa9\x80\xb1\x88\xde\xee\xb3\x1e_\x0c\ +\xa3\xc6\xa0\xb5$\x0f\xb8\xb5f\xb7K\xf3\xa7l\x07\x03O\x161!\xd3\xc1\xab\x05.\ +6V1\xe9\xfam\x83\xdb\xa5\xcdW\x95\x1f;`T\x83\x1b\xb46,_\x0bJY(\x91\xc13}\xbc\ +\x85\xa0\x1cU\xdd\xa4\xa0\x85R\x8a\xb3\x06\xa5\x10\x1dm\xab\xd2\x87aU\xff\ +\xaeAi\xf1\xfa\xbb\xc8V\xad\xa2\x8a\xa7Z;v\x88\x7f\xb7^\xeb\xeam\x87R\xd0\ +\xa8\x17\xb4\x19\x01\xfb~\x04V\x03C\x80\x04\xcf\x9d\x90\x18\xdf\x11\xc0\xbbH\ +\xa4\xc3 D:\xd2W\xc5,\x8c*\x87\xd2\x9al\xad\x99\x99\xef\xe2\x7fEnv\xba\xaa\ +\x9fd\xcb\x83g\xff=\xed\xc1\xaf\xac\xa8\xfch?\x1fh\xdc:\x17e)\xc0\xe2\xb6\ +\xa1\x95<|\xe4;n\x14\x16\xb4v\xe2\xb4\xa4\x81\xf3\xcc*\xee\xd7\xbaE"E\xf6\ +\x00O\xab A\xc8X\xbc\xed\xdb\xcbTKk\xcd\xbe}\x85\xb4\x1aR=\xa2\xe7\xbeP\x110\ +\xfa?`\x8dD\x16\xc8\xd6\x06\xd8q0\xd0\x05x\x1c\x18\x0f\x0c\x00\xfe\x89D2\xb8\ +\x91\xa8\x883c\x8d\xa3\xdaa\xa6\xc4\xeer\xba\xb9%\xdb\xc1\xe0\x93v^\xc8w\xb1\ +5\x08H\xd7\x17\xa7o\t\xcb\xd7J\xf9w5\x90\xb1\x0e\xad\xd7\x81%\x02\xad\xac`\ +\xb8z\xb5\xd2\xb8\xb1\x89\xff\xd7\xf93\x1c\xdduz\x17\x11b\xa8e\x1d\xf4\n!$\ +\x887"B1th" \x1dp\xf5\xealZ\x0f\xaf\x99\x19\xbdR\xd0\xb8/\xf4\x9edlXO\xc9\ +\x92\xb8\',\xc0\x85@3\xe3\xfdV\xe0\x11\xc4\xe7{>0\xd5\xf8^\x18\xa7\x0c\xad5\ +\xb9n\xcd\xec|\'C\xd2\x8b\x18\x96n\xe7\x93"7\'B\xc4\xb5S\xa5\xae\x08\xa5\xe0\ +\xd0.\xdc9\xf3p\xebu\xa0\xdd(b\x80\x18\xf9W\x15\xe2\xd4+Pi_B~\x81\xdf\xa8\ +\x880j\x0fA\xe9j\xf0E\xbbv\xd1\xb4i#\xd3-m\xac\xa6%w\xab\xc1\'\x9b\x82\x91\ +\xd3a\xfb\x97\x86v\xc3/\xc0\xb0\xd2\xfb\x10\x83\xa8\x94\xbdNI1\xcbL\xe0\x87\ +\x1ajg\x1d\x84\x06\xd0\x9a|\r\xef\x168y\xa5\xc0\xc5\x91\xea\x9a9\x98\x01\x00\ +V\xa0s\x80}N \xb3\x17\xcf\x06V\x92JM\xf2\xad\xb0\x1b\xc2H\xfb-m\xf5j\xf4\x8e\ +\xe5\xb8\xda\xe4a\xab\x7f)X\xda\xa1u\x04\xb8\n\x81M\xb8S?A\x1d\xd9\x17\xf6v\ +\x05!B\x82x\x87\x0c\xa9GD\x84t\x9f\x93\'\x9d\xec\xda\x97\xcf\xc8N5w~\xa5 \ +\xbe\x05\x9c\xf3 |5\r!\xde\xb3(}\xf7\x14\xa2Tv#\xa2\xcb\x10\x85\xa8\x9a}\x8a\ +\x97\xb5\x1b\x0e%\xab \xb4\xe6\xb0[\xf3j\xbe\x93\xf7\x0b\xdd\xe4T\x90\xe0,\ +\x11\x90\xd2\t\xdcn8\xbe\xd5\xd8\xd8\x07\x89>\xf9\x1a\xb0#i\xde\x93\x003\x0e\ +\\#\xb5\xf1\xea#\xbfc\x12\xfe\xfd\xf3y@\x81\xc7\xfb\x1dH\x0c\xb7Y]\xe4\x17\ +\xe0\xa7\n^^E*\x1ch\x8d\x8e)B\x15\xfaI\xa2PJ\xc8w\xefZ\x9c\xd1[QQ\xf1\x10\ +\x19\x8d.\xca\x83\xc2\\T\x91\xddH\xa4\x08w\xb7`C\xd0\x11\xaf\xbf\xa9\xd8\xa5\ +\x97&c\x06\x8e\xb9\xdd`\x8d\xd1\xc4\xf9\x13\xad\xa9F(\x05\xfdo\x85\r\xf3\xe0\ +\xf0*`)\xe2B(\xb5#"\x96nV\xaa\xb0\x13^`\xab\x04L\x15\xae\xc3n\xcd\xac|\'\xf3\ +\n\xdc\xc5\xc5\x9a\xfd\xc1\x1a%!\x7f\r\xda@\x93\xde\xd0\xb8\x97,\xba6\xec\ +\x04[?\x82\x0f\xafEb\xad/A~\x87c\x089Z\x91\x02\xa4\x95u\xb6\xc5\x19/\x13\xc9\ +\x1e\x7f_\x0e\x87\x0f.5\xc8\x17D\xc7!\x03\xa9\xc1\x06%\ +\x16Y-\xd5\xbc\xd2~\xfe\x02o\x8e\x11\xce\xd5^\x1f\xaab&\xaa\xe6A\xae5\x07\\\ +\x9a\xeb\xb2\x1cl\xf7ch\xf4\xbd\x01F\xcc\xf0&\xdbJ\xb5\xc8\r\xb9\xa6\x1a\x9c\ +g\x18\x9f\x06z"\xbf\xd3A\xc4J-\xafzHE\x90\x01|d\xfc=\x04!\\\x13\xf9\xa0\xacp\ +\xc3\x97pd\x13\xac\x9a\r\xe9\xe5\x84\xd6j\xb7K\x0f\x18\xd0\x83\xc2B\x07\x91\ +\xd1\x01\x16\xd7\xc2\x08Y\x04u\x1c\xaf\xd5\n\xc3\x87\xd7\xf3\xb0xa\xd7\xae\ +\x02"\xabI\xf8\xbc\xa2P\n"\x0c\xf2\xed0\xc6H\xae\xf0\x85\xa6\xd6HW N\xcf\xd2\ +\x06\xad\x17\xf5z\x7fd\x08\xac\xc8\x03\xa3z\xcct\xad5\xa9.7\x0f\xe48\x18\x91\ +n\xf7K\xba\x00??\x0b\xbb\xbf>\xf5\xf3(\x1b\xb4\x1cd\xbcI\xc5\xc3\x01\x8c\xcc\ +F: i\xde\xff>\xf5s\x14C\x03K\x8c\xf3\xa4 \xc93\x9e\x9f\x1d\x85\xb8\x14h>\x14\ +\x06\xdf\x0b\xb7\xad\x87\xd1OCR\xfb\xb2\x0f\xbbz\xf5fn\xbec!En\x17"|#\x17a\ +\xd6S\x93\x97\xc7\xfd\x93r\x13\x1eo\xb5\xd7>\xbe\xef\xeb\x1a\xb4\xd6h\x8b\ +\x13l\xf6\xdanJ\xb9\x08j\x8b\xb7I\x93(:w\xf6\x16\x07\xc9\xc8\xb7\xd3\xb1E-5\ +\xc8\x03&\xf9N\xfa\n2\xf7\xc2\x81e\xbe;@\x8b\xc1\xb0\xfb\x7f\xf0\xe5\xd4Zh\ +\x1fP\xcc\xfcfD\x91[\xa3\xedE\xf2\xafR\xb8\r\x11m\xd1tu\xa3\xb4\x96\xfd\xec\ +\x0e\xdc\'\xd3\xc1\xed\xaa2\x1f\xa3\xe9\xc7\x9dW\xe0\xe4\xd9|\x17\xd9\xfe\ +\x12P\x86\x00}\x81\xfd\xe0\xfc\x06\x16\xde\x0e7,\x86\x06\x9dO\xa1\x19\x9eF\ +\xfbAJ?o\x06!\xa2F_!B\xf6=95\xabW\x03\xbb\x81;\x8c\xef_\x01Dx\x1c+\x0f\xc8\ +\x80\xa8\xf6\x10a\xac\x01\xd8\xe2`\xf0}0\xe0\x0e\xd8\xff#\xfc\xf0\x08\x1c^\ +\xe3\xff\xf0\x9f|}\x9c\x84$\x0b/\xdc\xd5\x8ehWt\xb1&\x8e\x8e-\x04\x87\x15e\ +\x8f,~^buI\xea\x9a\xd3VL\xc0:\xd2!\xfb(\x8f\xf7\x0e\x9bT\xa7\xa8\x83\xd0\t\ +\x19\xa0\n\xca\xdf\xd1\xf7{5\xac\xdd\x1bT\xc4\xeb{\xf1\x89\x89VbbJ\xca\x07\ +\xdb\xedn2s\x1dD7\xa8\xf1\xa6\xf9\x852V\xd2\xeb\xb5\x83\x9e\xed\xfc\xef\xd3\ +\xd1\n\x96\xfb\xc0]\x1b\x0fa%\xa4\xaaP\xb8m\x1a\nrp\xae\xfa\r\x9d\x9e\x8ej\ +\xd7\x1e\x8b\xd5\x06\x0e\x17\xe0F+\x85\x8a\x89\xc1\x9d\x93\x8d}\xd5\xaf\xb8\ +\x8e\x9eD\xbb5\xcazz\x03\xd4\xb4\xae\xd69\xdc\xfc1\xdb\xe1\xe5\xc7-i\'\x12\ +\x8a\xd5\xcdx\x9f\x04\xd4\x87\xacy\xf0\xf1\xb50\xf9\x17P\x95\x9cik\r\xcd\x07\ +\xc1\xc6\xf7\x11K\xd4S\xd2S\x01\xed\x80\xe1\xc0\x8f\xc0\x18$\xc9\xa5,e9\xbf\ +\'A|\xc5W"\xbe\xfchJ\xfbs\xa3\x81xh{\x0e^\xf3K\xa5$B\xa3\xdd\x18h5\x02v.\x84\ +\xc5\xf7BNj\xe9\xd3\xbc5\xdfMna\x0co\xbe~7\xd11\x1ey\xf2\x9eV|\xa0\xf6\x9din\ +a\x97\x1dT(t\x13\xd0\x0ei\x84\ +\x90\xae\xe7\xa9\xda\x02\xed\xe0\xe8&\xd8\xf1\x05t\xbe\xa2rMQ\n\xda_ \xc9\ +\x14n;\x12:\xe6\xe9\xcb\xd5\x88\xb0\xd1\x01Dky4\xf0\np1\x15#+\x8d\xb8*\xa6!i\ +\xe4\x18\xef\xb7R\xe2j\xd0`\xea\x92\xb7\x1f\xed\x9f\x07\x95\x82\x888\xe8z\ +\xb5\xb8\xad\xb6\xfc\x17\xbe\x9f\x0e\xb9G\xbc\xf7\xfb\xf8\x93M|\xf2\xd9\x9f\ +\xcf4\x1a\xad\x15\xd4k\rY\xfb\xab\xf7\x1cA=\xdf\x18>\xbc^\xf1\xdfZk\x96/\xcf\ +"\xb1\xb5F\x05\xf5\xe3\xc2\x1b\x96\x08H\xac%\xd7H\xb1`\x8a\xc5\x18\xfdV\x1b\ +\xb8\x0b)\xfc`>\xf9O=N\xd1g\x1f\xe2X\xb9\x0c\xe7\xba\xd58\x97\xfdH\xe1k\xb3(\ +|}6\xee-\xbbQ\x11V)\xeb|\nC\xdd\x14\xb1\xf9\xb0\xd0\xc5\xf0t;o\x96E\xba Z\ +\xc7\x19\x94\xf6\x87\x8f\x10o\xc7\xe2\xfb@\x9f\xc2\xf3\xb6a7\xe84\xc6x\xf3\ +\xb3\xcf\xf1MW\xc4U\x88\xc6\xf2~`"\xa2\xa9\xb1\x87\xf2\xfd\xf3v`2\xf0\xa1\ +\xf1>\nI\xa8\xe9\x8f\xb7%\xfa\rD[\xa5DUY\x0f\x0e\xa5 \xb2>\xf4\xf93\xdc\xba\ +\x06\x06\xdd\x06\x16\x9f~\x1e\xcc\xa5lB\x19\xa5\x8a.\xf8\x13\x83\xabb\x045\ +\x85%%\xd9\xbcBh\xf6\xec)$\xba\x9e\xac\x10\x87\n4\xd0\xb4\x17\xec\xfe\xa6f\ +\xcf\xeb\xc6-\xdc\xa2\xadh\xa5\r\xd9@\x0bX\xa2\xd0\xf6"\x9c_\x7f\x8d\xf3\xa7\ +\x9f\xa1YC,\t\xf5q\xa7\x1e\xc1}2\x13\xe5(\xc0\x12g\xc5\x8d\x0b\xb0\x80V\x95"\ +_\xad5\xbf;\xdd<\x9e\xebd\xa9C\x97\xd6\x05R\x88/\xf7\x1c\xc4\xda\xfc\x10)\ +\x01\xf15p\xad\xcf~-\x81f\x90u\x00\x8em\x86\xc6\x03*g\xf5j\xa0\xef\xcd\xb0m!\ +\x92\xb1\xf6\x03"\xd7i\x1eC!\x84y\x050\x17\xc8\x06^Fb{G\x00\x03\x81\xa1\x88\ +\xbb\xc0\x17s\x80\xf9H\x06\xdc\x10c\xdf\x04\xbc\xef\xd5j`\r4\x19\x011\xc9\ +\xa5\x0f\xe1\x0fJAlS\xb8\xe8eh\xd2\x0b\xbe\xbe\x17\x1c\x1ee\xa7*\x94f\x1c\ +\xc6)#\xaa\x1e\x14\x9c\xac\xfe\xf3\x04-\xf1Z\xad\xd0\xbd\xbb\xa7\xc3L\xb1|y\ +\x96\xffA\x10\xe4\xa8\xdf\xb6v\xce\xabQ(m\xc5\xa2\xdd\xc2\xa1\x16\xc0\xedFk\ +\x85\x1b\ryY\xb0+\x03m\xb1\x81\xcbm\xac\n\x83\xc5\xaa\xb0\xa0*\x15~\xac\xb5\ +\x90\xec{\x05.\x1e\xcau\xfa//\xa7\x80\x7f\x01\xf7\x177P\x08\xee\x0e`\x172\ +\xe5o\x8b71\x1a\xbfw\xeaj!\xde\xca@)\xe8p1\xb4\x1e\x0e\xfb\x7f\x06\x96#q\xb6\ +\xa3}\xce\x91\x00\xdc\x8a$:\xec\x00r\x91E\xb7\xaf\xca8x"\xa2\xd7a\xea3\xfb.\ +\xde\x19\xb1\xddJ\xc1\x05\xff\xae\xdc\xf3\xcb\xac\xd6\xd3\xe7\x16\xe8p\x11|p\ +\x85\\\x7f\xf1\xe1\xc3\xe4[mh\xd2\x0bNl\xab\xfe\xf3\x04\xad\xab!&\xc6Jl\xacw\ +\xf3\xdcA\x9c\x88P\x16Z\x0e+=m\xacnX\x8c\x88\\\xb1v\xc1\xed\xd6\xf2\xb2\xb8\ +\xd1\x16\r\x11\ne\xb5b\xd5\n\x8b\xcb\x89\xb2\x80\xc5f5\x16\xd3L\x96F\xfc\x15\ +e@\x1b\x95\x1fV:\xdc\x8cJ\xb7so \xd2\x8dDH\xf7o\xc6{%\xa7a\x8a\xb1\x1d`\x9f\ +\x9f\xef5\x91\x7f\xb6/\xac\xf8\xb5{BE\xc0\xf8W\xc1\x16\x83\x10\xe2o\x08\xa1\ +\x16\xe1\xed\x12\x88\x06\xaeF\xa2\x1d\xa2}^fbLg\x84l\'#\x0f\x8b\xd1\xf8\'\ +\xdd\xc3\xc0\xdbr\x8eVgC\xd3JZ\xea\xc5mW\x10\xdf\nn\xfe\tz\xfd\xc1\xfb\xb3\ +\xb0\xdb\xa1j\xe0{\x1f\x1b\xb4\x81\xfc\x8c\xd2\xfbU\xf5\x83.h-\xde\xc6\x8d#h\ +\xdc\xd8_\x80lhA)\xd1\x13H\xe9\x06G7\xca\xb6\x9a\xb3X4\x1a\x17n\xa5\x01\xabD\ +8h\x85.\xae<\xab\xd0V+XTq2\x852\n%VD\xbf\xdf\xcc<\xfbw\x9e\x839\x05e<\x15;#S\ +\xf9\xa1\xf8Y]\x02n\x07f#\x0bS\xe7\xe1\xed[5\xa2\x19\x0e\xae\x04\xe5\xa2\xd2\ +=V)H\xea\x06\x17\xfc\x13\xbe\xfe\x8b\xb1q5\xe2z\xb8\x140#d\xccv\x8dA\xd2}}a\ +\xae\x8c\xf9\xae\x90\xf9#\xdd\xf9\x80K\xe2\xbb\xcf\xffg\xe5\xdb\\|8\x89\x04$\ +c\x0fd\x1d\xf2\xf3y\xd8\xf2\xadR(\x0b$4\x01]\x03j\xfaAk\xf1B\x1dJS\xb7A\xef\ +\x1a\x8enQ\xc6\xff4R\xfe\xdb\x02R\x9bK\x83\xc2\x82\x05)\x88\xe8V\xe06\xe2\ +\x1a\xc0\x0c?\xc3(\x13\x8e\xdf\x05&\xb3\xc6\xd9*\x87\x9b\xd1\x19\xf6\xc0\xa4\ +\xab\x10\x12[\x8c\x7f\xd25\x11\x0b\xdc\x80,X\x05\x80#\x0fr\x0e\x97w\xd5\x811\ +\xe0vh\xef\xa9&w\x00\xf1\xd3\xae\xc6\xdb\xf2\r\xd4F\xe5\xf3\xaf/4\xe2*y\x17(\ +\x94\x05\x9a\x91\x0fC\xf3a\xa7\xd1\x8f\xdd\xb0\xec\x9f0g\x10\xec\xf7\x8d\x13\ +7O\x1b\xb6|\xab\x0cq)\xe2j\xa8\t\x045\xf1zBk(,\x0cM_\x83R0\xf0N\xe8ws\xc9\ +\xb6j\x1f0\xda$_+\n+\xca\xb0~\xb5\xc5\x83e\x94l\xd5\xb8\xb1\xa0\x8dD\n\x0b\ +\xaa8u\x18/\xd60\xa3\x15~w\xba\x99\x9a\xe3\xe0\xf2L\x07;\x02]F\n\xf0\x01\xf0\ +1\xa2\x0eV\x1e\xf9t/\xfbcg!\x14\xa4\x97\xbdO (\x05*\x12\xae\xfa\x18:\x8d\xf5\ +hK!\xa2&\xf6-\xa7\x9eahF?l@R\x86\r\x19\xd0\xf3\x1e\x83a\x0f\x9f\x06\xe9jX\ +\xf7\x1a|\xf7\xa8\xf7\xe2\x9a\xdf]\xc3\xe4[%hq\x16\xa4\x06Hd\xa9j\x84\x0c\ +\xf1\x16\x15i6n\xcc\xad\xedf\x9c2\xac10v\x16\x0c\xba\xbdd[u\x0e\x18\xad\x0c\ +\xd5\x05mP\xab\x02\xb0\xa0\xdc\x92\xf5\xe1\xd6n#\xf2A\xa1\xb4\x057\xe0Vn\xdc\ +\xca%\x0boX\x0cn6\x18\xdc\xa8\xe0{w\x8e\x83\xf32\x1c\xbc_\xe8\xa6\xc8\xdf\ +\x89-\xc8\x14\xfew$\xb1\xc0Tl\xabe\x14\xa7y\x7f\x06c_\x02\x9bgB\xc6\n\x84\ +\x80\x0f\x03\'\x91\xe8\x86\x8a\xc2\x0e,\x02\x16\x02\x85\x10\x19/\xd2\xa1\x83\ +\xef\xe5\xb4\xae\xdb\x9e\x05??\x89\xff\x108?\x08\x93o\xe5\xe1y\xcf\xac\x910\ +\xfc\xaf\x90\xb6\xb1f\xce\x1d\xb4>^_X\xad\x18>\xdf\xd0J\xa00\xa1\x14`\x85\ +\xb6#a\xe5\xff\xd5\xc0\xf9\xb4\x067X\xdcF,\xaf\x06\x0b\x16\xdc\xda\xed?\xe3\ +\xc9s\x9b\x87?S\xf2\x044\xef\x14\xbay*\xdf\x15\xb8\n\x84B\x08\xf7.$R\xc1\xac\ +2^Q\x04\x98JW%\x94\x02"\xa0\xff\x1d\x90\xd8\x1c\xbe\xf9;\x9c\xdci\xc8R\xac1^\ +\xc6\xef\x84\x99\xa9nE\xa4?\x03\xe1(R\x0e\x08\x89\x9e\x18\xff\xaa\xc4\x0f\ +\x9f\x0e\x0bj\r?>\x06\x99\xfb\x81\x86@+$\xda\xc3\xd0\x1d&\r\x89}^\x82\x97\ +\xf2]\xd8\xe7{\xeah\xda\x07\x1a\xf5\x94\xfeP\x13\x08\x19\xe2\x8d\x88P\xb4n\ +\x1dMZ\x88\x12\xaf\xab\x08V\xbe\x08\xdf<\xe8\xbd\xbd\xba\x06\x8b\xbb\xb0\x08\ +\xb7]\xa3\x9c\x0eSJ\xdb\x88\xcd\xd5~y\xb74\x14\x0e4\xffqj\xde\xd6\x92\xdf\ +\x10\x10]\x81\x07\x908\\k\xb9\x07\xf6w*\t\xf3\xf2\x85\x99\x1d\x86D\x85\xd8b\ +\xfc\xecs\nP\n:^\n\xedF\xc3\xd1u\xf0\xcb\x0b\xf0\xfbg\xa0\xcd"\xa6N \xc7\xe3\ +\x0beTGW\x16H\xea n\xa4\x81w\x82-^\xae\xe7\x94=\x0c\x1a\xf2Sa\xcd\xbbHB\xc6Y\ +\xc8=\xf5\\\xdc\x8bE\xdc8~\xc87\xc8-2\x0eR\xba\xc8>\xb6\x18$\xdc\x99\xaa\ +\x8d\xbeQ\xdapE\x99U\x8e\xcdc\x9b\x0f\xa7\xd7\x81|Dt=\t\xc9\xba\xf3C\xde\xa9\ +({\x00\x00 \x00IDAT\x16a\xf2\xad8.|\x12\xb0y\x08\xe7\xd7\x00B\x8ax\xadVEQ.\ +\xa7\xe7@\xab\x01\x98|\x95\xba\x02\xe6_\xe2\xb3\x1a\x1f\x85\x0c\x14\x9f\\\ +\xda`\x18(\xe6\xf4\xab\xb0\xa8T\xa2\xaf\xa0;\xb2r\xdf\xc5x_\x15\xad\xd5H\xe2\ +\xc4^\x84\xc8\xcdm\xd9\xc0w%\xbb\xf5\xbe\xb1z\':^b\xeb6\xc3e\x00$\xf7(\xe7{U\ +\xdc\x8e\xcc\xdd\x90\xb6\x01\xef\xf2\xf2\x1a\xb1p\xdfFH\xb7\x15\xa2+\x11\x87\ +\x14\xec\x0c\xb0f\x10\x0c}*\x18\xe1\xe9fh\xd8I\xc4\x89\xb4\x03v\x9e\x86\xfes\ +e\x112Q\r\xa0\x196,\x11W\x11\x14e\xd5v[\xca\xc7\x96y\x1e\xa4\xab\x10?\xe8-\ +\xc0}Hz\xaa\x9fism\xaeLW\xe8\xdc\x99\x80Yv\xa9\xaa\x86\xb3\x06f e\x92\xccr9\ +\n\x8940\xc2\xa8\x9a\xf5\x87N\x97\xd4\xa1\xb8\xee\x00\xd0\x1a\x8em\x91\x19])\ +\xac\xa1\xc4\xd1\xfe\x18\x921gE\x84\xdc\xef$\xa0\t\x15\x8ev(\x1b\x03&\x83\ +\x8a\x82\x03?\xc1\x89\x1d5w\xde\xa0"^\xcf\xa7\xb3\xcb\x05\x1b6x\x86\x8f)\xa2\ +\xa2,\x14eC\xf6\x81Zh\\E\xa1a\xdd,\xf8\xf4&\x0fKw\x0c\x12Z\xd5\x18\x19 \r\ +\x11\xbf\xa8\x1f\xdd\x89\x9a\x1c(\xda\xed\xd2\xe6\xabB_HE\xc2\xae\xaa\xaa\ +\x85\x1aI\xdf\xfd\x18\x11"7\xc9\xe3\x00\x92\xd8\x00\xa0`\xf8\x03\x04\xf1\xdc\ +\xac\xea\xa0\x08\x90\x1a\xad(^d\xa4%\x12=b\x8e\x94D\xe0\x05D\xd8\'\xc5\xffq\ +\xc3\xe4[\x02_k\xb7\xcf\x9f\xe4Vn\xfe\x80\x1a\x9dI\x07\x15\xf1\xfa"#\xc3\xe9\ +Q\xa6D3`@\x02Ps!\x1f\x95\x85\xd6\xb0\xf6U\xa98\xa1\xcd\xd9\xba\xa9reZ\x89\ +\xe6\xe5\x1c\xc2\xafo\x0e\xaa\x7f\xa0T\x8al\x13\x90\xf6_\x8cXX\xffB\xa6\xbd\ +\xa7\xdbB\x8d(\x92]\x83X\xbb\xe6B\x92\x19\x17k,h5\xeb\x0b\x9d\xc6\xd7}k\xd7\ +\x84\xa3\xbcP\xf5\xf1\x08\xd9z\xc2\x8aD\x94|\r\x04\xc8\xbc\n\x93oi\\\xf0/\ +\x88j \xfe\xfc\x9d\x8b\xfd\xefS]\xae\x9a\xa0&\xdeU\xab\xb21\x1f\xedJ)\xda\ +\xb7\x8f!"B\x91{\xb4\xc4\x8f\x1a,\xd0\x1av.\x80/\xa7y\xac\x8c6\xa4\x84t\x8bw\ +\x04\xbeARK\xcb\xc8\t\xaf\x8e\x81R)\xc2\x05\x11\xa8\xb9\x05!\xdd\x01H<\xeb&\ +\xe0\xe9\xd3i\x04P\x80,\x0e]\x8d\x84\x91\x8dC,Z\r\xfc\x0f0\x8aT\xc67\x81+\ +\xde+\x89\x18\xa8\xf3p\xc3\xa1\xd5~\xb6{:\xb7\x87\xe3\xdf\xcd\xa3\x80~H\x16\ +\xde=\xf8\x9d!\x9c\xe9\xe4\xeby\xfd-\x06A\xfb\x8b\xe4\x81\x9e\xb6\x06\xb2O#\ +\x1d\xfdT\x10\xd4\xc4\xfb\xfb\xef\xf9^\x85\xf9\x9a4\x89 >\xde\xca\xa1\xdf\ +\x82+\x9aLk\xc8\xd9\x0f\x0b&\xfb\x84\xa3\\\x80w)\x18\x8dL\xd7\x7fAb1gP"\xd2\ +\xe2\xef\xb8U0P*\xeaN\x88\x88\x85^\xd7\xc2\xe5o#\xa5\x95b\x80\x9b\x8cv\x9a8\ +\x1f\xa9\'\xf6\x1f\xe0U\xc0\x8c{\xadPC\x8c\xd7\xaf\x88\xdbe2R\x8f\xec<$\xa5X\ +\x03\x9b\x81u%\xed\x99\xf0\x1a4\xe8t\xe6X\xbbhp\xfaK\x07\xb4 B\xed\t\xc8o\ +\x10\x08\nHF~\x9f\xcf\x8d\xbf}Oq\x86\x93/\x805\n.xZ\xb2I\xd15\xbb\xa8f"\xa8\ +\x89w\xe7\xce\x02rsK\xa2\xc4\xe3\xe2\xact\xee\x1c\xcb\x91\r5\xa3 TQ(\r?\xfc\ +\xa3$\xec\x08\x80f\x94\xae\xe1\xe5F\xa6\x83 \x19^\x8f Ds#\xd0\xd4\xff\xb1+m\ +\xa5Rq\xb2\x05);3`\n\xdc\xbe\x1e.\x9b\x07\xbd\xae\x87\xc6]\x11k\xdc\xe1\xd1~\ +\x85X\xbc\xd7#\xbe\xe9;\x10\x01\x9c\x1d\x14\x97\xb7\xf1K\xc2\x9e\x84{#0\n\ +\xf1\x13+\xe0BdF`>\x90\xcc\xe9\x9e\x82\xf1\xff\x07\x1d\x03\x07\xc6\x17W\xa2\ +\xe8\xd93\x8e\xdf\xde\xc8&}\xa7\xa4f\x06\x054\x1c^\xe9\xb3\xcd\xb7t\xb7F\xa6\ +\xe9f\t\xf0\xbf"\x03\xa45\xf0&\xe27}\x14\x99\x82\xe7P\n\xde\x996\xa5\xfdN\ +\x95\x19L\xca\x02\xc9]\xa0\xff\xcd\xd0s\x12\xc46\xc2;^\xd4\x82\x90n.\xdeZ\ +\x0b\n\x89j\xb8\x03X\t\xacB\xa6\xb7\x03\x10\xcb\xfd,\xfc\x13\xe5N$\x0c\xcdL\ +\x92\x88D|\xbb\xa6\xa5\x9b\x8bH)\x16\xc8\xf7G=\n=\xae\x0fp\xac3\x19G\x10\xe9\ +\xcc>\x94\x7fo\x14\xa2-\xfc+\xf2\x80\\\xe7\xfd\xf1\x99\x1ajf\x8b\x81\xb3\xff\ +f\xd8\x08\x1a\xd6\xbfQ3\xc2\xe7\xa5\xdaQ\xf3\xa7\xac8\xdcn\xf8\xf1\xc7,\x06\ +\x0eL0\xb6h\xc6\x8fo\xc8k\xaf\xa7\x91\xb9W\x82\xde\x83a\x1a\x9aw\x0c\xd2\xf7\ +xlPx\x17V4\xb7\x99\xfb\\\x80\xb7\x8bA!\x0b&\xcf#\xfe\xb9\xe7(\t\x94\xf7\x83S\ +\xb5X"\x13\xa0\xdb\xa5\xd0\xf7&hq6\xa8\x88\xd2\xf7\xcf\r\xf4\xba\xce\x90!\ +\xdcH\xe9\xa9\xadBz\xcdP\x84hw \x95z\x8f\x03\x15\xc9\xccK\x06&P\xa2\x7f\x90\ +\x81\x94\xff1\x8a\xb2\x8d|\x18\x86?*\x0f\x870\xfc\xe0\x01Jf\x06\xe5A!\x85D\ +\x17#\xbf\xe3&\xef\x8f\xcf\x14\xf2\xf5\x1c/=&B\xb2YXU\xc3\xd6\xcf\x02\x7f\ +\xaf:\xefM\xd0w\xef\x9f\x7f.I\x94WJ\xd1\xa3G\x1cQ\x91\x8am\x0b\x82\xc8 \xd2>\ +\x8b}\x8d\x8d\x97\'\xdcH\x98\x14\x08\xf1\x06Z i\x8d\x84\x07}`\xfc]\x05\x88K\ +\x81Q\xd3a\xdav\x98\xf06\xb4\x1c)\x99\x7f\xfe\x1eZJA\x97\t\x10\xd3\x10\t\xe9\ +\xca+\xe3\xc0\x16$\xd0\xff\xcf\xc0mH\nq\x1b\xc4\x15\xe1\xd9\xb3l\xc84y8\xf0G\ +JH7\x1f\t%;*o;\x8d\x85\xb3\xff\x1e&]@\x84pJ\x02z$K\r\xe47\xd9\x85\x7f\xb7\ +\x8e?(d\x865\x973\xd2\xe7\xeby}\r\xda\xc1\xc8\x19\x14\x8b\xfc\x17\x9e\x84\ +\xfd+j\xa7]Am\xf1\x02\xacZ\x95Cn\xae\x8b\x84\x04\xc9\x17m\xdd:\x8a\xe1\xc3\ +\xeb\xb3}s\x06\xdaMP\x14\xbe\x8cL\x80\xb8\x86\x90c\x96\xe4n\xe6g\xa7\xdc\xf05$\ +\xb65|\xbb\x1a\xf6~\x07v?n\xbd\x9a@\xd0\xdb\x16G\x8f\x8a\x9f\xd7\x8cnP\n\xfa\ +\xf4\x89\xe7\xd8f8\xb9\xb5\x96\x1bg "\xce\x88\x040\xe1\xcfRM\xa5DM\xaa"*\xf7\ +\n\x19lS\x11?\xdd\x0fH\x04\x80\xe7\xe2\x8a\x8d2I<\xae\x11L\xfa\x12b=H\xb7"\ +\xd0\xc0\x85\xff\x81\x16\x83\x11\x0b\xab\xb2\xbd\xc4\x8a\x10qS\xe3\x15o\x1c\ +\xc3\x0c\x8b:\x88\xf8t\r\xd2\x8dn\x00c\xff\x0flq\xc1\xe1:\xaa-h\x0bt\x1ek\ +\xbc9\x82\xf8\xc4\xcd\xc5I\xcf\xe8\x98e\xc0,*\x17K\xad\x90\xf0\xbd\xce\xfe?\ +\xae\x8b\x96\xaf\xe75\x9d\xf3 \xd4\xefX\xd2\xbf\xdcvX3\xb7\xb6Z\x16\x84\xc4\ +\xeb\xfb\xe4\xd5\x1a\x16,(\t\x17PJ1n\\C\xec\xb9\xb0i^\xf0\xc4\xf3\x16\x0bk+\ +\x8a\x0b4z\xc1lg2%\xd6KE`\xfaT\x87 \xfa\xab;\x81\xed\xc6k\x07\xb2r\xedy\xc7\ +\xceF\xace\x84\xd0\xe2\x9aT\x9e\xcc\x94\x82\xe8d\x98\xf8.\xb4\xee\r\xbc\x88D\ +clF\xdc%\xa6\xefY{\xbc|\xe1\xfbY!2E~\x13\xd1\x1c(\x00,\xd0u\x02\\\xb7\x00\ +\xea\xb5;\xb3I\xd7D\xdf\x9b\rW\x8b\x13y\xe0\x9a\xf7d\x00\x92vn\xbe\x7f\x10Y\ +\x07pRq\x02\xb6Q\xa6\x95\\\x97\xc8\xd7\xf3ZzO*-L\x7f\xf0\'\xd8\xfdM\xe0\xefW\ +\xf7\x0c \xe8]\r\x00\xdf~\x9b\xce\x8c\x19\xad\xb1\x1an\x85!C\x12\xe9\xd0!\ +\x86\xad\x8b\n\x18\xf5O\xaaE\xb5\xaaRP\xd0r\x08\xa4\x99\xeaF\xfeB~\xcc\x00\ +\xed\x04\xbc\xad\x97J\x9c\xa3\x98\xd4=\x89\xddS\xb7\xa2\x0f\xb2\x88\xb2\xeb\ +\x14\x8e\xef{:\x05\xf5:\xc0\r\xdf\xc2\xe6y\xb0\xf2U8\xfc\x89\xf1\xa1\r\xf1\ +\x1b6F\xae%@\xaa*\x99\xc6+\x1d\xf1\xe3\x9a1\xaa\n\xda\x9c#\x0bi\xadF!\xc5.\ +\xc2\xa4\x8bR\xd0\xb8/\xb4\x1d\x05{\xbe\x03\xd6"\xe1v\xf5\x11\xc2\xbc\nq\xfd\ +,F\x1e\\\xb7 \x95/\xfe\r\x98\x0bF\x81`JJ\x96S>\xa9.\xb8\x1d\x08E\xb7\x83?\xd2-\xb5n\xa0a\xf7b8\xba\xa5\ +\x16\x1a\xe8\x01\xa5,\xb5=O\xf7\x0f\xdf\x1f>%%\x82\xfd\xfb\xcf"&F\x9e\x15Zkn\ +\xbcq;Kw\x1d\xe5\xe6\x9f\xa9uw\xc3\xb1u0k\xa0!\x8e\xf3GJ/\xb0-AR\x85\x93\x90\ +\xe9\xa2Y?\xebtP\x84H)\x1eD\x92\x1a\xccp\xa1\\\xe0yh\xda\x17\xfe\xbc\x92j{\ +\xbcj-\x97\xe0\xc8\x01g\x81\xff}\xa2\x1bH\xbcp\xf0\xc4\xfe\x95<\xa8\xcbk\x92\ +\xa6\xf6\x1e\x0e\xda\t\xf3F\xc3\x9e\xef\x8d\r]\x10W\x03\x944\xdc\x05\xfc\x86\ +\xff*\xc9\xc9\x88_8\n\xd1\xefm@\xe9\x0b^\x8dd\x11\x96\x83P\xb0~}Iw\xe2{\xfe\ +\x17k\xb5\x0b\xe6]\x08\'V[\xc9\xce\xf6?\r\xad\x89\xeb\r\tW\x03\xc0\xf1\xe3\ +\x0e\xbe\xfb.\x93\xb1c\x1b\x14g\xb1M\x9e\xdc\x94\x8f/:\xc6\xf6\xcf5\x9d\'\ +\xd6n\xfb\x92:Iy\x9a\xac\xfd\x88\xb0\xb7/\xf1\x9a\x8bo\xe9\x88\x06\xc2B\xbcu\ +\x10*\x0b\r|\x82\xa8\x9c\xd5\xa3$\xceSSl\xed4\xe9!S\xd7\xea\xeaEf\xa7\x8eH\ +\x94W0\xa3\xf8!\x91+\xe5}v/\x81\xcc=px\xad\xff\xfd\x9b\xf6\x91\x07W\xfd\xb6\ +\x92\xe5\x17\x99\x08\xa8\x1a$c+\\>\x0f\xe6\x8d\x81#\x1b\x81m\xc0\x7f\x91\x85\ +\xd38i\x0bV$\x91%\n!_\xb3\x1ca\x14\xd2\':!\x84\xeb)\xb2c\xc2\x14+\xaa\x00\ +\x82\xd9\xf5\xe0e\xa0)\xe8u\r\x8c\x9fS\xda\xbd\x00\xd2\x07Nn\x85\x83\xbfB\ +\x97\xf6\xd1l\xdaT:H\xbd\xa6\xae3d\x88\x17`\xee\xdc4\xc6\x8e\x95\xb8-\xa5\ +\x14g\x9f]\x8f\x8e\x1dbY\xf7v\x1e\x9d/\xa7V\xad*k,t\xbb\x04~\x99\x89\x7f\xdf\ +g\x1bJ\xdc\x00\xdf\x03\xd3\x80\x97)!\xe4\xca@#YbS\x8c\xbf[\xe2m\xd5n\'p\xb4\ +\xc1\x99\x06-d\xbb\xef\x1bP\xdb\xe3\xe8\x18\x91D\xefv\xf1\\\xde-J\xa6\xdc\ +\xe3Jv\xdd\xbb\xb7\x90={d\x05\xf0\xe4I;\x8b\xff\x91\xce\xfe\x03\x85\xa8hM\ +\xa3.\x12\x80\xdf\xe52hw>D\xd5\xaf^\x12VJ\xe2\xa9/\x99\x03\xef\x8e\x95\x87\ +\x05\xdb\x91\x0c\xc1K\xf0~\xb0\xf7G\xfa\xd7\xefH\xa6Z;\xbcGv \x9f\xafG\xf2@r\ +\'\x99\x9d\x1c^EI\x8d9\xcf\xaf\x18\x04\x17L\x04\xec;+\x1e\xf5\x88d=\x06Z;P\ +\xc0\xd2\xc7 \xb9~$\x19\x19\xfe\xd4\xe6k\x0eA\xebj\x80\xd276:Z\xb1i\xd3 :t\ +\x90\x98\x18\xad5\x0b\x16\xa4\xf3\x87\xeb7s\xcf~\x88J\xf2{\x98\x1a\x81\xd6\ +\x90\xb1\x1df\x0f\x02{.B\x8a\xbe~\xdf/(\xc9\x99WH\xaa\xecD*\xf7\xc00I\xf7ZJ,\ +\x96K\xf0\xce\xdf\xff\x0c\xd8\x08\xe7N\x87a\x8f\x9ey\xbeT\xad\x01\x17\xec\\\ +\x08\xe9?Gr\xe5\xc0\xd6\x0c\xeeY\x9f\xce\x9dc\xb0\x19\x84\xa4\xfc\xdc\x14\ +\xadu\xf1v\xad5\x0e\x87\x90\xf1g\x9f\x9d`\xce\x9cT\xf6\xee\x15\x932"VJ\xb9\ +\xf7\x9f"\xd1\x19\xd1\r\xa9\xb6\x87\xbe\xd6\x90\xf6\x1b\xbcw\x19\xe4\x99\t:\ +\x11\x88F\xc6PJ\xb4yM\xab\xd6\x9fu\xebu@\xc4E\xf5!Rn\th\xd8\x11&\xaf\x10m\ +\xda\xfd\xdf\xc1\'7B\xee\x91\xc0\x87\xa8m\xf2\xf5\xe5\x05k$\x8cx\x00F\xfc\ +\xc3\x98\xe1\x05h\x9d+\x1f^\xea\x02c\x86\xa4\xb0`\xc1\t\x8a\x8aJ[&5umV\x15\ +\xcc\xf9\x99ZO\xf7|\xebtJ\xdd\xb5\xd1\xa3\xc5\xdd\xa0\x94\xa2c\xc7\x18\xd6\ +\xac\xcce\xf3\xda\x02:\\D\xadY\xbdJ\x19\x03\xd0\x01{\x97"\x99j\x9e\xf5\xba4\ +\xe2\x8f-\xa2$\xb4l=B\x9a\xf5\xa8X\xbb5B\xde\x9e\xa4\xdb\x00\xb1\xda\xcc\x12\ +\xe0vD\xd3\xd6\x0e}n\x80&}\xfd\x1d\xa8nBk\xf17ox\x03\xd6S\x802HWkX\xf3*\xec\xff\xda\xca\xc0\x81\x89\ +\xac\\Y:e\xad&\x1f(!e\xf1\x82,\xb2m\xde<\x90F\x8d\xa4\xce\xb6\xd6\x9aU\xabr9\ +\x7f\xc2Z\xa6\xfc\x06\t\xadj\xbc\x99^(<\t\xaf\r6Ds\xaeET\xca\xcc\x9f\xd3\x1c\ +\x18{\x90E\x8dt$\x06\xf6]D"\x11\x02\x13\xb0\x06\xb6 \xab\xd2f\x02\x83\x15I\ +\x1fmJ\x89\xb5\xf3\x03\xf0\xb3\x10\xc0m\xeb!\xb9{\x15^\\\x90\xc2\xd3\xc2]\ +\xf6\x14t\x8ao\xc0\xec\xd9\x9dh\xd7.* \xc9\x06>\x96\xfcH\xe6\xd7\xb4\xf6\tE\ +\xd2\x1a\xad\x15\xfb\xf7\x17\xb2iS\x1e\xaf\xbd\x96\xc6\x0f?d\x90\x97\xef\xa6\ +\xe5Yp\xf6}"\xb0m\x8d\xae\xda\x99\x86\xd6\xa0\x1d\xf0\xf3\x13\xf0\xe3\xbf|\ +\x08Q!\x8bim\xf1v/e\x03\'\x90\xd01?\xee\x83\xc6=\xe0\xf2w \xa5\x8f\xef5\xca!\ +\x8f\xae\x81/n\x81\xd4\x00~\xf0\xe2\xd3W3a\xf9\xe3\x01e\x914\xf3\x8b_\xaeX\ +\xf2\x8d=K\xc6\xe5E\x83\x9a\xb0kW>\xbf\xfc\x92]j\x9f0\xf1\x1a\x08\x14\xd22}z\ +\x1b\x1e}\xb4\xb5\xd7\xe0\x189r=jX\x16\xe7\xd7\x06D\xa3F\x11\ +\xdcyg\x0b\xae\xbe:\x85\x8e\x1d\xa3\x8b\tx\xf7\xee"f\xcfN\xe5\xf9\xe7\x0f\ +\x12\xddP\xaa\xda\xf6\x98\x14X\x0b\xb9*P\x9c\x1c\xa2\xfc\xbf\xaf\x8a\xe3\xbb\ +s\xe1\xe5^\x90\xb9\xafj\x8eY."\x11\r\xeb_(\xd6\x9a\xb8\xf4u\xe8us\xf9\xd7\ +\xe5.\x84Wz\xc2u\x175g\xec\xd8\x86\x8c\x19\xb3\xb1\xd4>\xb5\x11\xa5\x11\xb2\ +\xc4\x0b\x92@1gNG\xaf\xe9\xde\xfc\xf9\xc7y\xf4\xf5\xad\xfc\xe1\x0b#\xe8\xbd\ +\x96\xa15d\xef\x85E\xb7\xc3\xae\xff\x19\x1b\xa3\x10\xab\xb5\x0b%.\x85\xf2B\ +\x81\xca\xfal=\xc5\xd5\x1f:\x8f\x87\xab?\xab\x98\xef+d\xa0!\xff(,\xbe\x076\ +\x7f\x08\xb11\x16f\xcf\xee\xcc\x95W&\x13\x11\xa1\x8a\x89NkX\xbd:\x97\xf7\xdf\ +?\xc6\xec\xd9\xa9\x14\x14T#\xdb\xfaA\x83\x066\xa6Nm\xc1\xf8\xf1\r\xe9\xd3\'\ +\x0e\x8bE~\xff\r\x1b\xf2x\xf0\xc1\xbd\xfc\xef\x7f\xe9\xb49\x07\xc6\xbd\n\r:\ +\x87\xe6\x83Qk\xd8\xf46|v3\xd5k\xf1Z\x90Yb\x1fd\x9cD \x95N~\x94\x8f\x9b\xf6\ +\x85)+\xcb\x9e\xd9j\r\xab^\x84uOE\xb2a\xc3\x00\xa6L\xd9\xce\x17_\x9c,\xb5_\ +\x98x\xcb\x80?\xf2\x8d\x89\xb1\xb0jU\x7f\xbau\x8b)&_\x87C3q\xe2\x16\xec\xc3O\ +r\xd6}\x04M\xaa\xaa3_\\\x0f?\xfe\x0b\x1cf\xc2\x8cB\x02\xdfG I\x10\x95\xf9)\ +\xcc\xbb\xf1;\x12\xb7\xeb\x12\xcd\x84)\xbf@B\xeb\xd0\x1c\xd4\xbe0}\x99kg\xc3\ +\xb7\x0fK\x12\xc1y\xe75\xe0\xc9\'\xdb\xd2\xbf\x7f|1\xe1\x82b\xc5\x8alf\xce<\ +\xccG\x1f\x1d;%\xeb6\xd0\xe0;\x15\xcd\x02\xab\x15&Nl\xc4c\x8f\xb5\xa1S\'\x89\ +9\xb7\xdb5O=u\x90W_M\xa50\xd2\xce\x95\x1f@\xd3\xb3B\xefw\xca9\x08\xaf\xf6\ +\x85\x82\x91\x94\xc4\x10\x83\xd4\x82\xfb\x15IS~\x10)\x1be7\xb6\x81\x08\xfa\ +\xecCB\xdb|}\xc4Q\x08\xb9\x0e\x03v#\xeakC\x90H\x1fO\x83#\r\x98#\x7fZ#\xc5\ +\xddPV\xdd\xc5\x8c\xed\xf0\x7f}\xe1\xf1G\xdbq\xc5\x15\xc9t\xeb\xb6\n\x87\xc3\ +\xfb\xe7\xac\xad\x98\xe4\x90&^\x80+\xaeH\xe1\x83\x0f\xbab\xb5\x96X\xbd\xbbv\ +\x15r\xd55[\x1883/h:\xb79\xe5;\xbcL\\\x0f\xa5\n\xec\xc5"\xa1b\x1d\x91\xc5\ +\xb3\xb2|\xd4\x99H\x16\xd3Z\xa4C#.\x86k>\x81\xf6\xe3\x82\xe3zO\x17ZC\x81i\ +\xe5~\x04\xd1\x91\x16\x1e~\xb85\x7f\xfb[\xcbbyP\x80%K2y\xe1\x85C|\xfbm\x06Ng\ +\xc59\xf2T\x07\\e\x8882R\xf1\xc0\x03\xad\xb9\xf3\xcef$\'\xcb\x0f\xbacG\x01S\ +\xa6\xec`\xf9/Y\x0c\xfb\x1b\x9c\xf3\x8fS\xf3\xb9\xd6\x06\xb4\x865/\xc3\x97w\ +\x03\xb7#i\xeaf\xbb\x17#\xba\x11\x0f\x03\x8fQ2\x833\xe1F\x08w;\xdeR\xa6 \xc6\ +\x87\xa9\xa8\xf7

-\xdcS\x80R\xa2\x9a\xd4\xefv\x98\xb6S\xea\xa1\ +\xf5\xbfI2\x88\x8a\x93\x08\xf3\x90iU\x1a\xd2\x99W \xa1b\xe66\x0f\xd2\x8dL\ +\x80\xcb\xde\xa8;\xa4[x\x12\xbe{\x00\x16\xdd\t\x99\xfb\xe1\xc2\x0b\x1b\xb0dI\ +\xefb\xd2\xcd\xcbs3}\xfa~z\xf6\\\xc5\x9c9\xa9\x15"]e\xb1*\xf3U\x95\xed\xad\ +\xe81\x0f\x1d*\xe2\xaa\xab\xb6p\xe3\x8d\xdb\xc8\xcar\x11\x1fo\xe1\xde{\x9b\ +\xb3`AO\xec\x9b\xa2y\xed,\xd8\xfa>\x94*\x9a\x1adp\x16A\xf6!?\x1f\xd8\x91\xac\ +8\x85\x7f\x15\xb4S\xc1\t?\xdb\xf2\xa8P\xddAw!|\xfaG\x98|cs\x86\x0eM\xe4\xc9\ +\'\x0f\x96"\xdd\xdaFm\x07]U\n\xcabU\xfe\xac\x8c\x83\x07\x8b\xb8\xe5\x96\x1d|\ +\xf0A7/\xab\xe1\xfe\xfb[\xf2\xe5\x88\x13\xacz\xa9\x80A\xf7\x10T\xc4\xa4\x94(\ +z\xb5\xbbX^\xa3s 7\x15R\xd7\xc8j\xf1\t\xa3\x8a\x84=\x1b\xd2\x02)h\xf5\x87Q3\ +\x0c?W\x10]\xdb)A\xc3\xd1\xb5\xb0\xe0fQ\xe3\x8a\x8cT\xdcs_K\x9ex\xa2M\xb1ka\ +\xdd\xba<\xa6N\xdd\xc9\xf2\xe5\xa5\xb3\x8e\xfc\xa1\xa6,\x1a\xcf\xf3\x04\xb2\ +\x82\xddnx\xef\xbdc\xacY\x93\xc3\xacY\x9d\x191"\x91\x0b/\xac\xcf\xaaU\xfd\ +\xb9\xf4\xd2\xcd|\xf2\xc7,\x8em\x11\xbd\x01\x82\xd4\xf5`\xcf\x81\xbc\xe3\xc6\ +\x9b\x86\x94\xf8_#\x90\x99\x99\x85\xd2V\xea\xa9\xc2\xdf\xf5\x17"\xba\xc4\x06\ +l~\xca\x18i\r[?\x06\xc7\xde\x08\xfe\xf1Ekv\xee,\xe0\xa3\x8fJO+k[o"\xa4\x88\ +\x17\x02\x93\xef\x82\x05\'x\xe9\xa5\xc3L\x9b\xd6\xac8\x1f?%\xc5\xc6\x92%\xbd\ +\x19B\xeeZC\xd6n\ +\xf8\xfa\x1e\x98~\x7fK\xa2\xa3-L\x9a\xb4\xb58\x0b\xb1\xe4\xbb\xb5\xaf\xb0V\'\ +\\\r \xab\xc6O?}\x80\x8c\x8c\x92\x9b\xac\x94\xa2e\xcbH\x9ey\xb2\x03\x0bnV\ +\x14\xf9\x9b\xbe\x04#<\xbb\x85\xc5\xc80\xf2\xf7\n\xf1\xd29ZK\xb5\xd7%\xf7\ +\xc0W\xd3\xa0\xe0\x04\\}u#6o\x1e\xc8\xd5W\x0b\xe9\x1e>l\xe7\xce;wr\xcf=\xbb\ +\xca%\xdd\xeap\'\x9c\x0e\x02\xb5\xa7\xa8H\xf3\xef\x7f\x1f\xa0\x7f\xff5\xec\ +\xdcYH|\xbc\x85\x17^h\xcf=w\xb7\xe0\xd0r\xc5\xcb=a\xc3\xeb\xf2@\n&D$@\x9cY_\ +\xcf\xacpa\xa2*\xb2E]H\xb8\x98\x05\x89\xf2\xf1\x84\x9b\x92\x08\t\xa0^+h\xda\ +\xcfg\x97"\xf8\xe6~\x18\xda\xb7\x01\xf7\xdc\xd3\x82\xaf\xbeJg\xed\xdaS\xc9\ +\xa9\xae~\x84$\xf1\x06\x1a\\iiv\xe6\xcc))\x11\x04B\xbe\xd7]\x97\xc2\xdd7\xb4\ +e\xd1\xed\xd2\x99\x83\xd9\x8fv&@kI;M\xdf"U\x16~{\x05\xfa\xf5\x8bg\xf1\xe2^\ +\xcc\x9b\xd7\x85&M$\x15\xfc\xd7_s\x18:t\x1d\xef\xbcS\xb6H@\xb0\x11\xae/\xfc\ +\xb5Mk\xd8\xb81\x8f\x8b.\xda\xc8\xa2E\x19X,\xf0\xec\xb3\xedX\xb4\xa8\'\xcd\ +\x93\xa3Yp\x0b\xfc4C\\M\xc1\xd2_\xadQ\xd0\xe1\x02\xe3\xcdJ\xbc\r\x84\x9e\x88\ +\x15\xbc\xf7\x14\x0f\xae\x91\xea\xd3\xab\x90\xd8\xddd\x9f\xcfR\x91P3\x03C\ +\xa6\x81%\xc6c\x17\r+_\x82\xb4\x1fl\xcc\x9c\xd9\x91\xdd\xbb\x0b\xb8\xff~\x8f\ +/\x18\x08\x96~\x12\x92\xc4\x0b\x81o\xe0\x0b/\x1cd\xd3\xa6\xfcR\xe4{\xef\xbd-\ +\xe8\x15\xd1\x88\xef\xfe\x86L\xdf\x83\xa43\x9fI0\xb3\xf92\xb6\xc3;\xe7I\x8c\ +\xe5\xde\xa50zt\x03\x96,\xe9\xc5\xf9\xe7\xd7\xc7j\x15\x7f\xe8\xcb/\xa7r\xd9e\ +[8x\xb0\xa8\xccc\x06\xcb@*\x0f\x81\x1e\x0e{\xf6\x142a\xc2&\x1e\x7f\xfc\x00.\ +\x17\\pA}V\xae\xec\xc7\x9dw4g\xc5\xd3\x8ayc }+\xb5\x96\x9e\xeb\t\r\xb4=\xd7x\ +\xb3\x1dY\xf85\xdb\x15\x85h\x92\xa4R\xf9\xb6j`\x130\xd5\xf8\xbb\x0b\xde\xe9\ +\xf0\xb9\x14k\x90\x00$\xb6\x84\xbe\x7f\xf2\xce\xce\xcb;\x04\xcb\x9f\x83;\xef\ +lN\xb3f\x91\\s\xcd\xd6b\xd1\xa3`D\xc8\x12/\xf8\x1ftG\x8f:\xb8\xe8\xa2\x8d\ +\x1c\x00G\x8e8\xb8\xfc\xf2-\xfc\xfa[6\xdd\'\ +\xc2\xf8\xd7E\x87\xa4\xb6\x16V]E\xf0\xc5M\xb0\xf1}Dk\xfa\x06J\xc7\xef\xba\ +\x90*\xc6?"\x84\xda\x1b\xc9\xc8\xf4\xc5\x02\x84X\x1b\x03\x93(\x89f\xc8B\xe4R\ +w\x95\xec\xda\xb8\'\\\xbf\x18b\x9b\xca\xb5k\r\x8e,\x11\xc19\x7fP2\x1f}\xd4\ +\x9d\xc7\x1e\xdb\xcf\x8c\x19\xfbJ\x9d&\xd8\xfaN\x9d&^\x80\x9bnj\xc2\x9c9\x9d\ +\xb0\xd9\xbc\xe5\x02\xd7\xad\xcbc\xec\xd8\x8d\xf4\xbc\xdb\xc1\xd0\xbfRf\xad\ +\xa60N\rZC\xea\n\xf8\xe4z\xc80\x16]\x9a7\x8f\xe4\xbb\xef\xfa\xd0\xa9S\x89\ +\x8a\xd7\xe6\xcd\xf9L\x98\xb0\xb9\xb8\xa6\x99?\x04\xdb\xc0\xa9J\xf8\xeb\xbfq\ +q\x16\x1ey\xa4\rw\xdf\xdd\x9c\xc8HEa\xa1\xe6\xd6[w\xf0\xce;Gi9\x04.\x7f\xb7b\ +\x95\x17\xaa\x0b\xb9\x86fC\xfeI\x84|/F\x8alZ\xf1\xf6\xcfn\x03\xbeB\xfc\xb4\ +\x81\xd0\xd5\xf8\xbeI\xba\xb9\xc0\x9bH\xe5\x0c\x03\x8d{\xc2\xa4\xaf!\xaeY\ +\xc95\xbb\xed0\xef"\xb0\x1d\x8ca\xd9\xb2\xbel\xd8\x90\xcb\xf8\xf1\x9bJ\xd5R\ +\x0b\xc6\xbeS\'\x88\x17\xca&\xdf;\xefl\xce\xf3\xcf\xb7\xc7j\xc5+\xad\xf8\xdb\ +o3\xb9\xe4\xd2M\xf4\xfe\xa3\xe6\xc2\x17\xa8\xb00t\x18\xe5C\xbb`\xe3\xdb\xf0\ +\xe5]\xe00R^\x9fd\x91\ +\xa8z0a\x16t\xbe\xdc[\xbdMk\xd8\xbb\x18\xde\xbb\x14.87\x89\x0f?\xec&Y\x7f\ +\xaf\x1c\xc6\x17\xc1\xdc\x8f\xea\x1c\xf1B\xd9\xe4\xfb\xc4\x13my\xf0\xc1\x96\ +\xa5J\xc4l\xdf^\xc8\x95Wn\xe1\xa8;\x8f\x91\x0fA\xb7k\x8c\xcc\xdd\xa0\xfd\xe9\ +\x82\x07f\x95\xdfU/\x8bn\xae#\x0f\x12\x12\xac\xdc\x7f\x7f+\xee\xbb\xaf\x05\ +\x91\x91\xaax\xbf\xa7\x9e:\xc8\xf4\xe9\xfb\xfc\x16\x1b\x84\xe0\x1e,5\t\xff\ +\x8bn\x16\x1e{\xac-w\xdf\xdd\x1c\x90\x0c\xb8\x9bo\xde\xceG\x9f\x1f\xe3\xaa\ +\xf7\xa0\xc3%\x1eBK5\r\r\xc77\xc2\xd2\xc7a\xebge\xa4\xb9W\x00\xcd\x07\xc2\ +\x95\xefC\xa2\x8f\x0f[k\x91U}\xe7"\xe8\xd0*\x96E\x8bz\xf2\xdcs\x87B\x8et\x01\ +\xac\xaa\xd6~\xa9\xea\x83R\x96\x19h=\xdd\xdfg\xcb\x96e\xd1\xb6m,={\xc6\x16\ +\x93\xafR\x8a\x86\rm\x9cuV=\xfe\xfb\xe6IV\xbd\xe7\xc2]\x08\xcd\xfaW}\x99\xee\ +\xba\x06\xad\xe1\xe4fY\xe4\xd8\xf0.\xb8\xecR\x89\xe1\x8b/z2iRc\xa3*\xaf\xc2\ +\xe9\x84\xa7\x9f>\xc8#\x8f\xec\xc5\xe9\xf4\x7f\xac`\x1f,5\t\x7f}\xd8n\xd7|\ +\xf3M\x06QQV\x06\x0eL :\xda\xc2\xd8\xb1\r\xc9\xcbv\xf3\xceC9D\xc4@\xf3A\xb5D\ +\xbe\n\xe2\x9a@\xf7+\xa0\xddy\x90\xd4V\xb6\xc57\x82\x888\x11\xd8)\xabH\xa6-\ +\x06:]\x04c_\x81\x913 :\xb94\xe9\x9e\xd8\x04\x1f]\x03\x11v+K\x97\xf6\xe5\xeb\ +\xaf\xd3C"\x82\xc1\x1f\xea\xa4\xc5k"\x90\xe5\x1b\x19\xa9x\xe1\x85\x8eL\x9e\ +\xdc\x04\x9b\xcd\xdb\xedp\xe4\x88\x83?\xfeq\x1bK\x96d\xd0z\xb8T\x89M\xe9\x1d\ +&__\x98E\x16\x7f{\x01~z\n\x8a\xb2\xc0b\x81\xa9S[\xf0\x97\xbf\xb4(\xae\xf6\ +\xab\xb5f\xcf\x9e"\xee\xbak\'\x8b\x17\xa7\xfb\x8d\x9b\x0e\x85\x81R[\x08\xd4\ +\x87\xaf\xb9\xa6\x11s\xe7v&:Zn\xdd\xf7\xdfgq\xf3\x9f\xb6\xd1xL\x11\x17>\x07\ +\xd6\x98\xda\xed\xb3Z\x1bI\x02J\x1e\xc6Y\xfb s\xaf\xc8Jf\xfa\x88\xe94\xeb\ +\x0b-\x86BL\xb2\xff\xe8"\xd3\x97\xfc\xe6\xb9\xa03\xac|\xf2I\x0f\xb2\xb3]\xfc\ +\xe1\x0f[J\xcd\x9cB\xa5/\xd5i\xe2\x85\xb2\xdd\x0ew\xdd\xd5\x9c\xe7\x9e+\xed\ +\xf3u8`\xea\xd4\x9d\xbc\xf5\xd6\x11\x9ch\x86\xde\x03\xc3\xee\x87\xc8z\x9c\ +\xf1\xbe_sE\xf9\xd02\xf8\xe6\x018\xb0B\xb6\'&Z\x99>\xbd-\xd3\xa65+\xf6\xe5:\ +\x1c\xf0\xca+\x87y\xfc\xf1\xfddd\xf87sCe\xa0\xd46\xfc\xf5\xe3\xb3\xcf\xae\ +\xc7\xb3\xcf\xb6c\xf0\xe0\x04\x00\x0e\x1c\xb03\xe9\xfa\xadd7\xcbb\xdck\x92\ +\xdd\x15\xf2\x06\x83\x86\xcc=\xf0\xc5d\xd8\xff\x13\xcc\x9a\xd5\x89\xf3\xcek\ +\xc0\xd9g\xaf\xe5\xc8\x91\xd2*B\xa1\xd2\x9f\xea<\xf1B\xf9\xe4\xfb\xe4\x93\ +\xed\x88\x89Q^\xe4\xebv\xc3\xaaU9\xdcx\xe36v\xec( \xa9\x83T\x89\xed8\x0e,g\ +\xa8\xfbAk\xc8;\x0c\xbf\xbe\x00+^\x90\xa9\xa3\xc5"\x04\xf0\xf2\xcb\x1d\xe9\ +\xd9\xd3\\E\x81\xb5k\xf3\x986m\'+Vd\x07\xcc\x0e\x0c\x95A\x12,\xf0\xd7\x8fM\ +\xb7\xce\xd9g\x0b\xf9\xba\\\xf0\xec\xb3\x07\xf9\xef\xe6\xbd\\\xfcjh\x93\xaf\ +\xd6\xb0\xff[\x115\xcfI\x85\'\x9fl\xc7\xb4i\xcd\xb9\xf0\xc2\x8d\xfc\xfcs\xe8\ +D0\xf8\xc3\x19A\xbcP6\xf9\x9esN=>\xff\xbc\'\xf5\xeaYJ-\xba\x1d;\xe6\xe0\xce;\ +w\xf1\xf1\xc7\xc7QVh\xd4\x1d\xce\xff\'\xb4\xbb\x00T5\x96\xe9\x0e&\x04*W\xd4\ +\xaaU\x14\xcf=WR\xd2\x06\xa4B\xc4\x8b/\x1ef\xfa\xf4}\x01\xabC\x84\xd2\x00\tF\ +\xf8\xf6\xe5\xa4$\x1b\x8f>\xda\x86\xdboo\x86\xcd&\xf1\xd1O?s\x909\x0b\xf6q\ +\xd5\x02MtJ\x08\xf6S\r\xe9\xdb$N8}\x0f\\ye\no\xbe\xd9\x85{\xee\xd9\xc5k\xaf\ +\x05\x7fJpy8c\x88\x17\xca&\xdf\x8e\x1dcx\xff\xfdn\xf4\xeb\x17W\x8a|\x1d\x0eI\ +\xb6\x98\xf8\xe0\x18\ +o\xbf}\x84c\xc7\x1c(\x0b4\xe9\r].\x81\xde\xd7K\x0e}(\xc6\x00k\r\xda\x0e\xfb\ +\xbe\x87\xe5\xcf\xc2\x9e\xefe{\xf3\xe6QL\x98\xd0\x90)S\x9a\xd2\xa3G\\\xb1[\ +\xe1\xe4I\'O\xbc>S\ +\xa64e\xec\xd8\x86\xc4\xc5\x954\xb6\xb0P\xf3\xfe\xfb\xc7x\xea\xa9\x03\xec\ +\xd8QP\xc6\xc1C\x7f`\x84\x1a\x02\xf5\xe3A\x83\x12x\xe4\x916\x8c\x1d\xdb\x80\ +\xbd{\x8b\x183f#\x873\n\x18\xfd\x0c\xf4\xbc\xa1\x163\xdd(\x89\x92Y\xf6$\xac\ +\x9c%Q2\xf1\xf1V\xfe\xf3\x9f\x0e\xec\xdbW\xc8\xcc\x99\x07\xfd\xce\xa4\xeaB\ +\xdf:\xe3\x89\x17\xca&_\x90\'\xf0\xf9\xe77\xe0\x81\x07Z1lX=\xaf\xa4\x8b\xe2c\ +\x18!h\xbbv\x15\xf2\xe5\x97\xe9|\xf4\xd1Q\xd6\xaf\xcf\x93\x02\x8d\n\xa2\xeb\ +\x89r\x7f\xeba\xd0\xf9\x12\xb1\x86#\xe2\xc0\x12I\x8d\x10\xb1i\xd9\xba\xed\ +\x12\x17\xb9q\x1e\xec\\\x0cG7C\x84E\xd1\xaf_\x02\xc3\x86\xd5\xe3\xfa\xeb\x9b\ +\xd0\xad[,J\xe9b\x0b77\xd7\xcd\xfc\xf9\xc7x\xfe\xf9\x83\xec\xdcYP\xaex|]\x18\ +\x18\xa1\n\x7f}9"Bq\xef\xbd-\xb9\xef\xbe\x16de\xb9\x183f#\xbbv\x170\xf4\x1e\ +\x18\xf1\x88h\xfb\xd6\xb41\xa05\xa4\xfd\n\x0b\xfe\x0c\xc76\xcb\xb6\x0b.h\xc0\ +\xfd\xf7\xb7\xe6\xc5\x17\x0f\xb2pai\xd7\x02\xd4\x9d\xbe\x15&^\x03\xe5\x91/\ +\x885\xd8\xbf\x7f\x02O=\xd5\x8e\x81\x03\x13\x88\x8aR\xa5\x08\x18\x84\x84].)C\ +\xb4xq:\xf3\xe6\x1de\xd5\xaal/Q\x98\xc8\x04\x88o\x0c-\x07C\x83\xb6\xd0l \xb4\ +\x1e\x01\xd6\x08\xb0\xc5\x1a\xc7\x81S\x1e\x10\x9eD\x9b\xb5\x0f\x0e\xae\x80\ +\xbd?\xc0\xc1_%s(&\xc2\xc2\xa0A\xf58\xe7\x9cz\xdcpC\x13Z\xb5\x8a\xf4\xb0\xe8\ +5ZCf\xa6\x8b\xcf>;\xc1\xb3\xcf\x1ed\xeb\xd6\xfcr\xcfYW\x06E(\xa3\xac~|\xd6Y\ +\x89<\xf4P+\xbav\x8d\xe3\x96[v\xf0\xddw\x19\xc45\x82K_\x87\x0e\xe3jf]\xc2\\\ +\xb8\xdd\xf2>|9\r\n3d\r\xe1\xbe\xfbZR\xaf\x9e\x8d\x87\x1f\xdeCjj\xdd\x97\x08\ +\r\x13\xaf\x07*B\xbe \x9d\xb3M\x9bh\x9ex\xa2-\xa3G\'\x19\xf5\xc2t@\x12\xd6\ +\x1a\x8e\x1ds\xf0\xd1G\xc7Y\xba4\x8bM\x9br\xd9\xb7\xaf\x10\x87\xc3\xfbt\xd6H\ +\x88I\x82\x06\xed\xe4}J\x17\x88K\x86\x96C\xc1b\x93\x92\xd6\xf1\xcd\x02\xb7\ +\xab(\x0bNl\x85\xfc\xe3pl\x0b\x9c\xdc%\xff\xa6\xef\x81\xa8\x08E\xbf~\x89\xf4\ +\xe9\x13G\x9f>\xf1\x8c\x19\x93D\x8b\x16Qf+\x8b\xad[\xad\x15\xfb\xf7\x172s\ +\xe6a\xdex#\x8d\xac\xac2\x12\xec=\xefI\x1d\x1a\x14u\x01eE\xee\\\x7f}\x13\x1e\ +~\xb85S\xa7\xee\x14\xcb\xd2\x02\xed\xcf\x83s\x1e\x86\xe6g\x01\xd5\xa0K\xad5\ +\xe0\x84\xdf?\x84eFhb\x84M1aB2\xe3\xc6%\xf3\xfe\xfbG\xf9\xe6\x9b\xf43F"4L\ +\xbc~PQ\x02\x06\x88\x89\xb10th=.\xbb,\x99+\xaeH\xa1Q#)\xda\x18\x88\x84A\xe1t\ +j\x0e\x1c(b\xf3\xe6<\xd6\xac\xc9a\xcb\x96<\xd6\xaf\xcf%\'\xc7Ez\xba\x03W\x00\ +\xaeSV\x08\xf8s)\xc0\r\tq6l6\xc5\x80\x01\t\xf4\xea\x15G\xa3F\x91\xf4\xec\x19\ +G\xd7\xae\xb14k\x16\x89\xc5\xf0\xe9yf\xe9\x81X\xb7_}\x95\xcek\xaf\xa5\xf1\ +\xcb/Y\x01\xd5\xc3J\x9d\xb6\x8e\r\x88\xba\x86@}9%%\x82\xc7\x1fo\xc7\xde\xbd\ +\x85<\xf7\xdc\x01\x1c\x0e\x8d\xb2BJW8\xe7A\xe8\xdeJ\xaf^R7\xa5m\xdb\x18bb,X,r\xdc\xb2\x1e\ +\x00\xb9\xb9.V\xaf\xce\xe1\xbd\xf7\x8e\xf1\xd1G\xc7*l\xddB\xdd\x1c\x0cu\x15e\ +\xf5\xe3n\xddb\x998\xb11\xef\xbf\x7f\x84]\xbbJ\x16L\x13\x9aC\xd7\xf1\xd0\xe3\ +\x0f\xd0\xb4?\xd8\xe2\xca\'a\xd3\xbd\x85\x1brSa\xefR\xf8\xf5EH]\x8b\x99INT\ +\x94\xe2\xd2KS\x88\x8f\xb7\xf1\xfe\xfbG\xc8\xcf?3\xc3\x10\xc3\xc4[\x0e*K\xbe\ +&,\x16\xa9\x1c0~|2\x13&4\xa4A\x03\x1b-ZDa\xb1\xa8Z\xaa\x16 \x97\x91\x9e\xeed\ +\xed\xda<\x16-:\xc1\xa2E\'\xd9\xb3\'p\x9d3\x7f\xa8\xcb\x83\xa1.\xa3\xac~\x1c\ +\x13ca\xf0\xe0D\x92\x93#\xf8\xf6\xdb\x0c23\xbd\x1f\xf6\x89-\xa0\xeb\x04h\xd6\ +\x0f\xea\xb7\x83\xc4V\x12\xb1\x13\x97,\x9fg\x1f\x06\xb7\x0b\xb2\x0f\x88e\xbb\ +\xffg\xb1l\x9d\x1e\x81/\xf1\xf1\x16:t\x88!3\xd3\xc9\xf1\xe3\x8e\x80"\xf8&\ +\xeaz?\x0b\x13o\x05q\xaa\x04\x0cB\xc2V\xab\xa2W\xafx&LhH\x93&\x91\x0c\x1c\ +\x98HB\x82\x95V\xad\xa2\x0c\xab\xb8l\xcb\xb8Rm5H\xd6\xe5\x82\x8c\x0c\'\x1b6\ +\xe4\xb2aC\x1eK\x97f\xb2lY\x16YY\xceJ\x97\xb5\xaf\xeb\x03\xe1LAY\xfd8!\xc1\ +\xca\x88\x11\xf5IM-b\xf7\xee\x02\xb2\xb3\xfd\xcc\x80\x8c\xe4\x8b\x88X\x88m(\ +\x9b\xb2S%\x14Lk\xf0,\xe3\x1e\x15\xa5\x185\xaa\x01n\xb7f\xe7\xce|\xf6\xed+\ +\xaaP\xbf;\x13\xfaZ\x98x+\x81\xd3!_\x7f\x88\x8cT4n\x1cI\xd7\xae\xb1\x8c\x18Q\ +\x9f\x86\r#\x182$\x11\xad\xe5\xb3\xf6\xedc\xd0Zc\xb1(\xfe\xbf\x9d\xbbWM\x18\ +\x8c\xc28\xfe\x18\x89\xa6Z\x89kwQ\x10\x15\x17\xaf\xc7\xdb\x14\\3(^\x80 \xf8\ +\xb5k\xd1P\x8d%\x89\xe9\x10\n\xd6Z\x13\xbfj\xce{\xce\x7f\x16\xcc\xf0\xf3\xe1\ +%\t\xea\xfa\xef\x93r\x10\x00\x9e\x17\xc0\xf7\x038\xce\x1e\xf3\xf9\'f3\x07\ +\xe3\xf1\x0e\xbd\x9e\x8d\xc9\xc4\xc1t\xba\xfb\xf3/\x19\xa3\xe2\xf0\x03\xe0X\ +\x1c\xc7\x95\xca\x0b\x0cC\x83\xe3\xec\xb1X\xb8\xb1nye\xb3)\x94J94\x9b\x05T\ +\xab9x\x1e`Y+t\xbb\xef\xb1\xaf\x8d\x8b9\x19\xde+\xba\xf7\x00\x1f\xf6=\xae\ +\xe9t\n\xc5b\xf8\xa0\xce4\xd3\xa8\xd7_O~~8\xdcb\xb9t\xe1\xba{\xd8\xb6\x7f\ +\xf1I\xf6\xc7w3A/\xc57\\.\xe7\xd0h\xe4aY+\xd4jy\xac\xd7\x1e\x06\x83\x0f\xe8z\ +\n\xed\xf6\x1b\x0cC\x83ah\xc8d4\xb4Z\x05\x8cF[t:K\xf4\xfb\xf6\xd9\xfb\xb7\ +\xc7q\xb3\'\xc3{\xd0#\x075\xe9q\x83\xcf\xbd[\xadk\x1a`\x9a\xe1\xc1`\xb3\xf1c\ +\xbf\x05\x13\x15\x17\x87\xac\x87\x97\xf3\xd0\x1e\xc7\x05<\xd7([W\xd1&\xbb\ +\xe1\xa5\x0c\xf0?S\x11;\xa7TwN\xdd\xa7\x0c\xaftU\xd4\xe1\xab\x94\x98\x0e\xa3\ +d\x92\xdd\xf0\x02\x02\xf5\x91Q\xc2O=q|\xbe$[d9\xbc\xe7\x12\xcc\xb7\x95d\xec\ +\x9c\xe2\xee8\xe9\x0eex/\x88;\xe6\xa8\x92\x8e]\nS\xd91\x15\x832\xbcwLU\xd0T0\ +K\xf7\x8d\x82g\xaa6ex\x9f\xdc3pS\xc5*\xd1\xe9V\xd7\xaa\x1b\xfd\x028\xc0\xaeS\ +5\x9dLZ\x00\x00\x00\x00IEND\xaeB`\x82' + +def getVippiBitmap(): + return BitmapFromImage(getVippiImage()) + +def getVippiImage(): + stream = cStringIO.StringIO(getVippiData()) + return ImageFromStream(stream) + diff --git a/Chapter-08/mdi.py b/Chapter-08/mdi.py new file mode 100644 index 0000000..5d15900 --- /dev/null +++ b/Chapter-08/mdi.py @@ -0,0 +1,29 @@ +import wx + +class MDIFrame(wx.MDIParentFrame): + def __init__(self): + wx.MDIParentFrame.__init__(self, None, -1, "MDI Parent", + size=(600,400)) + menu = wx.Menu() + menu.Append(5000, "&New Window") + menu.Append(5001, "E&xit") + menubar = wx.MenuBar() + menubar.Append(menu, "&File") + self.SetMenuBar(menubar) + self.Bind(wx.EVT_MENU, self.OnNewWindow, id=5000) + self.Bind(wx.EVT_MENU, self.OnExit, id=5001) + + def OnExit(self, evt): + self.Close(True) + + def OnNewWindow(self, evt): + win = wx.MDIChildFrame(self, -1, "Child Window") + win.Show(True) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = MDIFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-08/miniframe.py b/Chapter-08/miniframe.py new file mode 100644 index 0000000..91080f4 --- /dev/null +++ b/Chapter-08/miniframe.py @@ -0,0 +1,21 @@ +import wx + +class MiniFrame(wx.MiniFrame): + def __init__(self): + wx.MiniFrame.__init__(self, None, -1, 'Mini Frame', + size=(300, 100)) + panel = wx.Panel(self, -1, size=(300, 100)) + button = wx.Button(panel, -1, "Close Me", pos=(15, 15)) + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +if __name__ == '__main__': + app = wx.PySimpleApp() + MiniFrame().Show() + app.MainLoop() diff --git a/Chapter-08/scroll_window.py b/Chapter-08/scroll_window.py new file mode 100644 index 0000000..2ea57a7 --- /dev/null +++ b/Chapter-08/scroll_window.py @@ -0,0 +1,26 @@ +import wx + +class ScrollbarFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, 'Scrollbar Example', + size=(300, 200)) + self.scroll = wx.ScrolledWindow(self, -1) + self.scroll.SetScrollbars(1, 1, 600, 400) + self.button = wx.Button(self.scroll, -1, "Scroll Me", pos=(50, 20)) + self.Bind(wx.EVT_BUTTON, self.OnClickTop, self.button) + self.button2 = wx.Button(self.scroll, -1, "Scroll Back", pos=(500, 350)) + self.Bind(wx.EVT_BUTTON, self.OnClickBottom, self.button2) + + def OnClickTop(self, event): + self.scroll.Scroll(600, 400) + + def OnClickBottom(self, event): + self.scroll.Scroll(1, 1) + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = ScrollbarFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-08/shaped_frame.py b/Chapter-08/shaped_frame.py new file mode 100644 index 0000000..513ab46 --- /dev/null +++ b/Chapter-08/shaped_frame.py @@ -0,0 +1,43 @@ +import wx +import images + +class ShapedFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Shaped Window", + style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER | + wx.FRAME_NO_TASKBAR) + self.hasShape = False + self.bmp = images.getVippiBitmap() + self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight())) + dc = wx.ClientDC(self) + dc.DrawBitmap(self.bmp, 0,0, True) + self.SetWindowShape() + self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) + self.Bind(wx.EVT_RIGHT_UP, self.OnExit) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape) + + def SetWindowShape(self, evt=None): + r = wx.RegionFromBitmap(self.bmp) + self.hasShape = self.SetShape(r) + + def OnDoubleClick(self, evt): + if self.hasShape: + self.SetShape(wx.Region()) + self.hasShape = False + else: + self.SetWindowShape() + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + dc.DrawBitmap(self.bmp, 0,0, True) + + def OnExit(self, evt): + self.Close() + +if __name__ == '__main__': + app = wx.PySimpleApp() + ShapedFrame().Show() + app.MainLoop() + + diff --git a/Chapter-08/shaped_frame_mobile.py b/Chapter-08/shaped_frame_mobile.py new file mode 100644 index 0000000..9e592ed --- /dev/null +++ b/Chapter-08/shaped_frame_mobile.py @@ -0,0 +1,64 @@ +import wx +import images + +class ShapedFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Shaped Window", + style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER ) + self.hasShape = False + self.delta = wx.Point(0,0) + self.bmp = images.getVippiBitmap() + self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight())) + dc = wx.ClientDC(self) + dc.DrawBitmap(self.bmp, 0,0, True) + self.SetWindowShape() + self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMouseMove) + self.Bind(wx.EVT_RIGHT_UP, self.OnExit) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape) + + def SetWindowShape(self, evt=None): + r = wx.RegionFromBitmap(self.bmp) + self.hasShape = self.SetShape(r) + + def OnDoubleClick(self, evt): + if self.hasShape: + self.SetShape(wx.Region()) + self.hasShape = False + else: + self.SetWindowShape() + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + dc.DrawBitmap(self.bmp, 0,0, True) + + def OnExit(self, evt): + self.Close() + + def OnLeftDown(self, evt): + self.CaptureMouse() + pos = self.ClientToScreen(evt.GetPosition()) + origin = self.GetPosition() + self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y) + + def OnMouseMove(self, evt): + if evt.Dragging() and evt.LeftIsDown(): + pos = self.ClientToScreen(evt.GetPosition()) + newPos = (pos.x - self.delta.x, pos.y - self.delta.y) + self.Move(newPos) + + def OnLeftUp(self, evt): + if self.HasCapture(): + self.ReleaseMouse() + + + +if __name__ == '__main__': + app = wx.PySimpleApp() + ShapedFrame().Show() + app.MainLoop() + + diff --git a/Chapter-08/simple_frame.py b/Chapter-08/simple_frame.py new file mode 100644 index 0000000..acea15b --- /dev/null +++ b/Chapter-08/simple_frame.py @@ -0,0 +1,8 @@ +import wx + +if __name__ == '__main__': + app = wx.PySimpleApp() + frame = wx.Frame(None, -1, "A Frame", style=wx.DEFAULT_FRAME_STYLE, + size=(200, 100)) + frame.Show() + app.MainLoop() diff --git a/Chapter-08/splitter.py b/Chapter-08/splitter.py new file mode 100644 index 0000000..8699db6 --- /dev/null +++ b/Chapter-08/splitter.py @@ -0,0 +1,101 @@ +import wx + +class SplitterExampleFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, title=title) + self.MakeMenuBar() + self.minpane = 0 + self.initpos = 0 + self.sp = wx.SplitterWindow(self) + self.p1 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER) + self.p2 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER) + self.p1.SetBackgroundColour("pink") + self.p2.SetBackgroundColour("sky blue") + self.p1.Hide() + self.p2.Hide() + + self.sp.Initialize(self.p1) + + self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, + self.OnSashChanging, self.sp) + self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, + self.OnSashChanged, self.sp) + + + def MakeMenuBar(self): + menu = wx.Menu() + item = menu.Append(-1, "Split horizontally") + self.Bind(wx.EVT_MENU, self.OnSplitH, item) + self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanSplit, item) + item = menu.Append(-1, "Split vertically") + self.Bind(wx.EVT_MENU, self.OnSplitV, item) + self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanSplit, item) + item = menu.Append(-1, "Unsplit") + self.Bind(wx.EVT_MENU, self.OnUnsplit, item) + self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanUnsplit, item) + + menu.AppendSeparator() + item = menu.Append(-1, "Set initial sash position") + self.Bind(wx.EVT_MENU, self.OnSetPos, item) + item = menu.Append(-1, "Set minimum pane size") + self.Bind(wx.EVT_MENU, self.OnSetMin, item) + + menu.AppendSeparator() + item = menu.Append(wx.ID_EXIT, "E&xit") + self.Bind(wx.EVT_MENU, self.OnExit, item) + + mbar = wx.MenuBar() + mbar.Append(menu, "Splitter") + self.SetMenuBar(mbar) + + + def OnSashChanging(self, evt): + print "OnSashChanging:", evt.GetSashPosition() + + def OnSashChanged(self, evt): + print "OnSashChanged:", evt.GetSashPosition() + + + def OnSplitH(self, evt): + self.sp.SplitHorizontally(self.p1, self.p2, self.initpos) + + def OnSplitV(self, evt): + self.sp.SplitVertically(self.p1, self.p2, self.initpos) + + def OnCheckCanSplit(self, evt): + evt.Enable(not self.sp.IsSplit()) + + def OnCheckCanUnsplit(self, evt): + evt.Enable(self.sp.IsSplit()) + + def OnUnsplit(self, evt): + self.sp.Unsplit() + + def OnSetMin(self, evt): + minpane = wx.GetNumberFromUser( + "Enter the minimum pane size", + "", "Minimum Pane Size", self.minpane, + 0, 1000, self) + if minpane != -1: + self.minpane = minpane + self.sp.SetMinimumPaneSize(self.minpane) + + def OnSetPos(self, evt): + initpos = wx.GetNumberFromUser( + "Enter the initial sash position (to be used in the Split call)", + "", "Initial Sash Position", self.initpos, + -1000, 1000, self) + if initpos != -1: + self.initpos = initpos + + + def OnExit(self, evt): + self.Close() + + +app = wx.PySimpleApp(redirect=True) +frm = SplitterExampleFrame(None, "Splitter Example") +frm.SetSize((600,500)) +frm.Show() +app.SetTopWindow(frm) +app.MainLoop() diff --git a/Chapter-09/choice_box.py b/Chapter-09/choice_box.py new file mode 100644 index 0000000..17693fc --- /dev/null +++ b/Chapter-09/choice_box.py @@ -0,0 +1,12 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + choices = ["Alpha", "Baker", "Charlie", "Delta"] + dialog = wx.SingleChoiceDialog(None, "Pick A Word", "Choices", + choices) + if dialog.ShowModal() == wx.ID_OK: + print "You selected: %s\n" % dialog.GetStringSelection() + + dialog.Destroy() + diff --git a/Chapter-09/color_box.py b/Chapter-09/color_box.py new file mode 100644 index 0000000..94e709e --- /dev/null +++ b/Chapter-09/color_box.py @@ -0,0 +1,10 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + dialog = wx.ColourDialog(None) + dialog.GetColourData().SetChooseFull(True) + if dialog.ShowModal() == wx.ID_OK: + data = dialog.GetColourData() + print 'You selected: %s\n' % str(data.GetColour().Get()) + dialog.Destroy() diff --git a/Chapter-09/dir_box.py b/Chapter-09/dir_box.py new file mode 100644 index 0000000..36cc79b --- /dev/null +++ b/Chapter-09/dir_box.py @@ -0,0 +1,12 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + dialog = wx.DirDialog(None, "Choose a directory:", + style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON) + if dialog.ShowModal() == wx.ID_OK: + print dialog.GetPath() + dialog.Destroy() + + + diff --git a/Chapter-09/file_box.py b/Chapter-09/file_box.py new file mode 100644 index 0000000..ff46e39 --- /dev/null +++ b/Chapter-09/file_box.py @@ -0,0 +1,17 @@ +import wx +import os + +if __name__ == "__main__": + app = wx.PySimpleApp() + wildcard = "Python source (*.py)|*.py|" \ + "Compiled Python (*.pyc)|*.pyc|" \ + "All files (*.*)|*.*" + dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), + "", wildcard, wx.OPEN) + if dialog.ShowModal() == wx.ID_OK: + print dialog.GetPath() + + dialog.Destroy() + + + diff --git a/Chapter-09/font_box.py b/Chapter-09/font_box.py new file mode 100644 index 0000000..969e596 --- /dev/null +++ b/Chapter-09/font_box.py @@ -0,0 +1,16 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + dialog = wx.FontDialog(None, wx.FontData()) + if dialog.ShowModal() == wx.ID_OK: + data = dialog.GetFontData() + font = data.GetChosenFont() + colour = data.GetColour() + print 'You selected: "%s", %d points\n' % ( + font.GetFaceName(), font.GetPointSize()) + dialog.Destroy() + + + + diff --git a/Chapter-09/image_box.py b/Chapter-09/image_box.py new file mode 100644 index 0000000..b425400 --- /dev/null +++ b/Chapter-09/image_box.py @@ -0,0 +1,11 @@ +import wx +import wx.lib.imagebrowser as imagebrowser + +if __name__ == "__main__": + app = wx.PySimpleApp() + dialog = imagebrowser.ImageDialog(None) + if dialog.ShowModal() == wx.ID_OK: + print "You Selected File: " + dialog.GetFile() + dialog.Destroy() + + diff --git a/Chapter-09/message_box.py b/Chapter-09/message_box.py new file mode 100644 index 0000000..db9828e --- /dev/null +++ b/Chapter-09/message_box.py @@ -0,0 +1,16 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + dlg = wx.MessageDialog(None, "Is this explanation OK?", + 'A Message Box', + wx.YES_NO | wx.ICON_QUESTION) + retCode = dlg.ShowModal() + if (retCode == wx.ID_YES): + print "yes" + else: + print "no" + dlg.Destroy() + + retCode = wx.MessageBox("Is this way easier?", "Via Function", + wx.YES_NO | wx.ICON_QUESTION) diff --git a/Chapter-09/modal_dialog.py b/Chapter-09/modal_dialog.py new file mode 100644 index 0000000..f4dcd71 --- /dev/null +++ b/Chapter-09/modal_dialog.py @@ -0,0 +1,21 @@ +import wx + +class SubclassDialog(wx.Dialog): + def __init__(self): + wx.Dialog.__init__(self, None, -1, 'Dialog Subclass', + size=(300, 100)) + okButton = wx.Button(self, wx.ID_OK, "OK", pos=(15, 15)) + okButton.SetDefault() + cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel", + pos=(115, 15)) + +if __name__ == '__main__': + app = wx.PySimpleApp() + app.MainLoop() + dialog = SubclassDialog() + result = dialog.ShowModal() + if result == wx.ID_OK: + print "OK" + else: + print "Cancel" + dialog.Destroy() diff --git a/Chapter-09/progress_box.py b/Chapter-09/progress_box.py new file mode 100644 index 0000000..8be0411 --- /dev/null +++ b/Chapter-09/progress_box.py @@ -0,0 +1,16 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + progressMax = 100 + dialog = wx.ProgressDialog("A progress box", "Time remaining", progressMax, + style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME) + keepGoing = True + count = 0 + while keepGoing and count < progressMax: + count = count + 1 + wx.Sleep(1) + keepGoing = dialog.Update(count) + + dialog.Destroy() + diff --git a/Chapter-09/startup_tip.py b/Chapter-09/startup_tip.py new file mode 100644 index 0000000..8341060 --- /dev/null +++ b/Chapter-09/startup_tip.py @@ -0,0 +1,8 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + provider = wx.CreateFileTipProvider("tips.txt", 0) + wx.ShowTip(None, provider, True) + + diff --git a/Chapter-09/text_box.py b/Chapter-09/text_box.py new file mode 100644 index 0000000..ebad419 --- /dev/null +++ b/Chapter-09/text_box.py @@ -0,0 +1,11 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + dialog = wx.TextEntryDialog(None, + "What kind of text would you like to enter?", + "Text Entry", "Default Value", style=wx.OK|wx.CANCEL) + if dialog.ShowModal() == wx.ID_OK: + print "You entered: %s" % dialog.GetValue() + + dialog.Destroy() diff --git a/Chapter-09/tips.txt b/Chapter-09/tips.txt new file mode 100644 index 0000000..a3c4425 --- /dev/null +++ b/Chapter-09/tips.txt @@ -0,0 +1,2 @@ +You can do startup tips very easily. +Feel the force, Luke. diff --git a/Chapter-09/validator1.py b/Chapter-09/validator1.py new file mode 100644 index 0000000..af49ad0 --- /dev/null +++ b/Chapter-09/validator1.py @@ -0,0 +1,93 @@ +import wx + +about_txt = """\ +The validator used in this example will ensure that the text +controls are not empty when you press the Ok button, and +will not let you leave if any of the Validations fail.""" + + +class NotEmptyValidator(wx.PyValidator): + def __init__(self): + wx.PyValidator.__init__(self) + + def Clone(self): + """ + Note that every validator must implement the Clone() method. + """ + return NotEmptyValidator() + + def Validate(self, win): + textCtrl = self.GetWindow() + text = textCtrl.GetValue() + + if len(text) == 0: + wx.MessageBox("This field must contain some text!", "Error") + textCtrl.SetBackgroundColour("pink") + textCtrl.SetFocus() + textCtrl.Refresh() + return False + else: + textCtrl.SetBackgroundColour( + wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + textCtrl.Refresh() + return True + + def TransferToWindow(self): + return True + + def TransferFromWindow(self): + return True + + + +class MyDialog(wx.Dialog): + def __init__(self): + wx.Dialog.__init__(self, None, -1, "Validators: validating") + + # Create the text controls + about = wx.StaticText(self, -1, about_txt) + name_l = wx.StaticText(self, -1, "Name:") + email_l = wx.StaticText(self, -1, "Email:") + phone_l = wx.StaticText(self, -1, "Phone:") + + name_t = wx.TextCtrl(self, validator=NotEmptyValidator()) + email_t = wx.TextCtrl(self, validator=NotEmptyValidator()) + phone_t = wx.TextCtrl(self, validator=NotEmptyValidator()) + + # Use standard button IDs + okay = wx.Button(self, wx.ID_OK) + okay.SetDefault() + cancel = wx.Button(self, wx.ID_CANCEL) + + # Layout with sizers + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(about, 0, wx.ALL, 5) + sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) + + fgs = wx.FlexGridSizer(3, 2, 5, 5) + fgs.Add(name_l, 0, wx.ALIGN_RIGHT) + fgs.Add(name_t, 0, wx.EXPAND) + fgs.Add(email_l, 0, wx.ALIGN_RIGHT) + fgs.Add(email_t, 0, wx.EXPAND) + fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) + fgs.Add(phone_t, 0, wx.EXPAND) + fgs.AddGrowableCol(1) + sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) + + btns = wx.StdDialogButtonSizer() + btns.AddButton(okay) + btns.AddButton(cancel) + btns.Realize() + sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + + +app = wx.PySimpleApp() + +dlg = MyDialog() +dlg.ShowModal() +dlg.Destroy() + +app.MainLoop() diff --git a/Chapter-09/validator2.py b/Chapter-09/validator2.py new file mode 100644 index 0000000..e14d760 --- /dev/null +++ b/Chapter-09/validator2.py @@ -0,0 +1,91 @@ +import wx +import pprint + +about_txt = """\ +The validator used in this example shows how the validator +can be used to transfer data to and from each text control +automatically when the dialog is shown and dismissed.""" + + +class DataXferValidator(wx.PyValidator): + def __init__(self, data, key): + wx.PyValidator.__init__(self) + self.data = data + self.key = key + + def Clone(self): + """ + Note that every validator must implement the Clone() method. + """ + return DataXferValidator(self.data, self.key) + + def Validate(self, win): + return True + + def TransferToWindow(self): + textCtrl = self.GetWindow() + textCtrl.SetValue(self.data.get(self.key, "")) + return True + + def TransferFromWindow(self): + textCtrl = self.GetWindow() + self.data[self.key] = textCtrl.GetValue() + return True + + + +class MyDialog(wx.Dialog): + def __init__(self, data): + wx.Dialog.__init__(self, None, -1, "Validators: data transfer") + + # Create the text controls + about = wx.StaticText(self, -1, about_txt) + name_l = wx.StaticText(self, -1, "Name:") + email_l = wx.StaticText(self, -1, "Email:") + phone_l = wx.StaticText(self, -1, "Phone:") + + name_t = wx.TextCtrl(self, validator=DataXferValidator(data, "name")) + email_t = wx.TextCtrl(self, validator=DataXferValidator(data, "email")) + phone_t = wx.TextCtrl(self, validator=DataXferValidator(data, "phone")) + + # Use standard button IDs + okay = wx.Button(self, wx.ID_OK) + okay.SetDefault() + cancel = wx.Button(self, wx.ID_CANCEL) + + # Layout with sizers + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(about, 0, wx.ALL, 5) + sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) + + fgs = wx.FlexGridSizer(3, 2, 5, 5) + fgs.Add(name_l, 0, wx.ALIGN_RIGHT) + fgs.Add(name_t, 0, wx.EXPAND) + fgs.Add(email_l, 0, wx.ALIGN_RIGHT) + fgs.Add(email_t, 0, wx.EXPAND) + fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) + fgs.Add(phone_t, 0, wx.EXPAND) + fgs.AddGrowableCol(1) + sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) + + btns = wx.StdDialogButtonSizer() + btns.AddButton(okay) + btns.AddButton(cancel) + btns.Realize() + sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + + +app = wx.PySimpleApp() + +data = { "name" : "Jordyn Dunn" } +dlg = MyDialog(data) +dlg.ShowModal() +dlg.Destroy() + +wx.MessageBox("You entered these values:\n\n" + + pprint.pformat(data)) + +app.MainLoop() diff --git a/Chapter-09/validator3.py b/Chapter-09/validator3.py new file mode 100644 index 0000000..12a4a8e --- /dev/null +++ b/Chapter-09/validator3.py @@ -0,0 +1,92 @@ +import wx +import string + +about_txt = """\ +The validator used in this example will validate the input on the fly +instead of waiting until the okay button is pressed. The first field +will not allow digits to be typed, the second will allow anything +and the third will not allow alphabetic characters to be entered. +""" + + +class CharValidator(wx.PyValidator): + def __init__(self, flag): + wx.PyValidator.__init__(self) + self.flag = flag + self.Bind(wx.EVT_CHAR, self.OnChar) + + def Clone(self): + """ + Note that every validator must implement the Clone() method. + """ + return CharValidator(self.flag) + + def Validate(self, win): + return True + + def TransferToWindow(self): + return True + + def TransferFromWindow(self): + return True + + def OnChar(self, evt): + key = chr(evt.GetKeyCode()) + if self.flag == "no-alpha" and key in string.letters: + return + if self.flag == "no-digit" and key in string.digits: + return + evt.Skip() + + +class MyDialog(wx.Dialog): + def __init__(self): + wx.Dialog.__init__(self, None, -1, "Validators: behavior modification") + + # Create the text controls + about = wx.StaticText(self, -1, about_txt) + name_l = wx.StaticText(self, -1, "Name:") + email_l = wx.StaticText(self, -1, "Email:") + phone_l = wx.StaticText(self, -1, "Phone:") + + name_t = wx.TextCtrl(self, validator=CharValidator("no-digit")) + email_t = wx.TextCtrl(self, validator=CharValidator("any")) + phone_t = wx.TextCtrl(self, validator=CharValidator("no-alpha")) + + # Use standard button IDs + okay = wx.Button(self, wx.ID_OK) + okay.SetDefault() + cancel = wx.Button(self, wx.ID_CANCEL) + + # Layout with sizers + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(about, 0, wx.ALL, 5) + sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) + + fgs = wx.FlexGridSizer(3, 2, 5, 5) + fgs.Add(name_l, 0, wx.ALIGN_RIGHT) + fgs.Add(name_t, 0, wx.EXPAND) + fgs.Add(email_l, 0, wx.ALIGN_RIGHT) + fgs.Add(email_t, 0, wx.EXPAND) + fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) + fgs.Add(phone_t, 0, wx.EXPAND) + fgs.AddGrowableCol(1) + sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) + + btns = wx.StdDialogButtonSizer() + btns.AddButton(okay) + btns.AddButton(cancel) + btns.Realize() + sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + + +app = wx.PySimpleApp() + +dlg = MyDialog() +dlg.ShowModal() +dlg.Destroy() + +app.MainLoop() diff --git a/Chapter-09/wizard.py b/Chapter-09/wizard.py new file mode 100644 index 0000000..6c75621 --- /dev/null +++ b/Chapter-09/wizard.py @@ -0,0 +1,39 @@ +import wx +import wx.wizard + +class TitledPage(wx.wizard.WizardPageSimple): + def __init__(self, parent, title): + wx.wizard.WizardPageSimple.__init__(self, parent) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.sizer) + titleText = wx.StaticText(self, -1, title) + titleText.SetFont( + wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) + self.sizer.Add(titleText, 0, + wx.ALIGN_CENTRE | wx.ALL, 5) + self.sizer.Add(wx.StaticLine(self, -1), 0, + wx.EXPAND | wx.ALL, 5) + +if __name__ == "__main__": + app = wx.PySimpleApp() + wizard = wx.wizard.Wizard(None, -1, "Simple Wizard") + page1 = TitledPage(wizard, "Page 1") + page2 = TitledPage(wizard, "Page 2") + page3 = TitledPage(wizard, "Page 3") + page4 = TitledPage(wizard, "Page 4") + page1.sizer.Add(wx.StaticText(page1, -1, + "Testing the wizard")) + page4.sizer.Add(wx.StaticText(page4, -1, + "This is the last page.")) + wx.wizard.WizardPageSimple_Chain(page1, page2) + wx.wizard.WizardPageSimple_Chain(page2, page3) + wx.wizard.WizardPageSimple_Chain(page3, page4) + wizard.FitToPage(page1) + + if wizard.RunWizard(page1): + print "Success" + + wizard.Destroy() + + + diff --git a/Chapter-10/add_items.py b/Chapter-10/add_items.py new file mode 100644 index 0000000..2c5efe6 --- /dev/null +++ b/Chapter-10/add_items.py @@ -0,0 +1,50 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Add Menu Items") + p = wx.Panel(self) + self.txt = wx.TextCtrl(p, -1, "new item") + btn = wx.Button(p, -1, "Add Menu Item") + self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.txt, 0, wx.ALL, 20) + sizer.Add(btn, 0, wx.TOP|wx.RIGHT, 20) + p.SetSizer(sizer) + + self.menu = menu = wx.Menu() + simple = menu.Append(-1, "Simple menu item") + menu.AppendSeparator() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnSimple, simple) + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnExit(self, event): + self.Close() + + def OnAddItem(self, event): + item = self.menu.Append(-1, self.txt.GetValue()) + self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item) + + def OnNewItemSelected(self, event): + wx.MessageBox("You selected a new item") + + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/create_just_menu.py b/Chapter-10/create_just_menu.py new file mode 100644 index 0000000..dd9f207 --- /dev/null +++ b/Chapter-10/create_just_menu.py @@ -0,0 +1,22 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Simple Menu Example") + p = wx.Panel(self) + menuBar = wx.MenuBar() + menu = wx.Menu() + menuBar.Append(menu, "Left Menu") + menu2 = wx.Menu() + menuBar.Append(menu2, "Middle Menu") + menu3 = wx.Menu() + menuBar.Append(menu3, "Right Menu") + self.SetMenuBar(menuBar) + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/create_simple_menu.py b/Chapter-10/create_simple_menu.py new file mode 100644 index 0000000..24e3592 --- /dev/null +++ b/Chapter-10/create_simple_menu.py @@ -0,0 +1,29 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Simple Menu Example") + p = wx.Panel(self) + menu = wx.Menu() + simple = menu.Append(-1, "Simple menu item") + menu.AppendSeparator() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnSimple, simple) + self.Bind(wx.EVT_MENU, self.OnExit, exit) + menuBar = wx.MenuBar() + menuBar.Append(menu, "Simple Menu") + self.SetMenuBar(menuBar) + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnExit(self, event): + self.Close() + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/disable_item.py b/Chapter-10/disable_item.py new file mode 100644 index 0000000..78c7534 --- /dev/null +++ b/Chapter-10/disable_item.py @@ -0,0 +1,46 @@ +import wx + +ID_SIMPLE = wx.NewId() + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Enable/Disable Menu Example") + p = wx.Panel(self) + self.btn = wx.Button(p, -1, "Disable Item", (20,20)) + self.Bind(wx.EVT_BUTTON, self.OnToggleItem, self.btn) + + menu = wx.Menu() + menu.Append(ID_SIMPLE, "Simple menu item") + self.Bind(wx.EVT_MENU, self.OnSimple, id=ID_SIMPLE) + + menu.AppendSeparator() + menu.Append(wx.ID_EXIT, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnExit(self, event): + self.Close() + + def OnToggleItem(self, event): + menubar = self.GetMenuBar() + enabled = menubar.IsEnabled(ID_SIMPLE) + menubar.Enable(ID_SIMPLE, not enabled) + self.btn.SetLabel( + (enabled and "Enable" or "Disable") + " Item") + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/fancy_items.py b/Chapter-10/fancy_items.py new file mode 100644 index 0000000..8a42df8 --- /dev/null +++ b/Chapter-10/fancy_items.py @@ -0,0 +1,47 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Fancier Menu Example") + p = wx.Panel(self) + menu = wx.Menu() + + bmp = wx.Bitmap("open.png", wx.BITMAP_TYPE_PNG) + item = wx.MenuItem(menu, -1, "Has Open Bitmap") + item.SetBitmap(bmp) + menu.AppendItem(item) + + if True or 'wxMSW' in wx.PlatformInfo: + font = wx.SystemSettings.GetFont( + wx.SYS_DEFAULT_GUI_FONT) + font.SetWeight(wx.BOLD) + item = wx.MenuItem(menu, -1, "Has Bold Font") + item.SetFont(font) + menu.AppendItem(item) + + item = wx.MenuItem(menu, -1, "Has Red Text") + item.SetTextColour("red") + menu.AppendItem(item) + + + menu.AppendSeparator() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnExit(self, event): + self.Close() + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/find_item.py b/Chapter-10/find_item.py new file mode 100644 index 0000000..cf58c35 --- /dev/null +++ b/Chapter-10/find_item.py @@ -0,0 +1,52 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Find Item Example") + p = wx.Panel(self) + self.txt = wx.TextCtrl(p, -1, "new item") + btn = wx.Button(p, -1, "Add Menu Item") + self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.txt, 0, wx.ALL, 20) + sizer.Add(btn, 0, wx.TOP|wx.RIGHT, 20) + p.SetSizer(sizer) + + self.menu = menu = wx.Menu() + simple = menu.Append(-1, "Simple menu item") + menu.AppendSeparator() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnSimple, simple) + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnExit(self, event): + self.Close() + + def OnAddItem(self, event): + item = self.menu.Append(-1, self.txt.GetValue()) + self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item) + + def OnNewItemSelected(self, event): + item = self.GetMenuBar().FindItemById(event.GetId()) + text = item.GetText() + wx.MessageBox("You selected the '%s' item" % text) + + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/open.png b/Chapter-10/open.png new file mode 100644 index 0000000..c1f11bc Binary files /dev/null and b/Chapter-10/open.png differ diff --git a/Chapter-10/popupmenu.py b/Chapter-10/popupmenu.py new file mode 100644 index 0000000..d26ce9a --- /dev/null +++ b/Chapter-10/popupmenu.py @@ -0,0 +1,49 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Popup Menu Example") + self.panel = p = wx.Panel(self) + menu = wx.Menu() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + wx.StaticText(p, -1, + "Right-click on the panel to show a popup menu", + (25,25)) + + self.popupmenu = wx.Menu() + for text in "one two three four five".split(): + item = self.popupmenu.Append(-1, text) + self.Bind(wx.EVT_MENU, self.OnPopupItemSelected, item) + p.Bind(wx.EVT_CONTEXT_MENU, self.OnShowPopup) + + + def OnShowPopup(self, event): + pos = event.GetPosition() + pos = self.panel.ScreenToClient(pos) + self.panel.PopupMenu(self.popupmenu, pos) + + + def OnPopupItemSelected(self, event): + item = self.popupmenu.FindItemById(event.GetId()) + text = item.GetText() + wx.MessageBox("You selected item '%s'" % text) + + + def OnExit(self, event): + self.Close() + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/sub_menu.py b/Chapter-10/sub_menu.py new file mode 100644 index 0000000..6021508 --- /dev/null +++ b/Chapter-10/sub_menu.py @@ -0,0 +1,34 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Sub-menu Example") + p = wx.Panel(self) + menu = wx.Menu() + + submenu = wx.Menu() + submenu.Append(-1, "Sub-item 1") + submenu.Append(-1, "Sub-item 2") + menu.AppendMenu(-1, "Sub-menu", submenu) + + menu.AppendSeparator() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnExit(self, event): + self.Close() + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/toggle_items.py b/Chapter-10/toggle_items.py new file mode 100644 index 0000000..216e4c1 --- /dev/null +++ b/Chapter-10/toggle_items.py @@ -0,0 +1,36 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Toggle Items Example") + p = wx.Panel(self) + menuBar = wx.MenuBar() + menu = wx.Menu() + exit = menu.Append(-1, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, exit) + menuBar.Append(menu, "Menu") + + menu = wx.Menu() + menu.AppendCheckItem(-1, "Check Item 1") + menu.AppendCheckItem(-1, "Check Item 2") + menu.AppendCheckItem(-1, "Check Item 3") + menu.AppendSeparator() + menu.AppendRadioItem(-1, "Radio Item 1") + menu.AppendRadioItem(-1, "Radio Item 2") + menu.AppendRadioItem(-1, "Radio Item 3") + menuBar.Append(menu, "Toggle Items") + + self.SetMenuBar(menuBar) + + def OnExit(self, event): + self.Close() + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/update_ui.py b/Chapter-10/update_ui.py new file mode 100644 index 0000000..a33a6fe --- /dev/null +++ b/Chapter-10/update_ui.py @@ -0,0 +1,49 @@ +import wx + +ID_SIMPLE = wx.NewId() + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "UPDATE_UI Menu Example") + p = wx.Panel(self) + self.btn = wx.Button(p, -1, "Disable Item", (20,20)) + self.Bind(wx.EVT_BUTTON, self.OnToggleItem, self.btn) + + menu = wx.Menu() + menu.Append(ID_SIMPLE, "Simple menu item") + self.enabled = True + self.Bind(wx.EVT_MENU, self.OnSimple, id=ID_SIMPLE) + self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateSimple, id=ID_SIMPLE) + + menu.AppendSeparator() + menu.Append(wx.ID_EXIT, "Exit") + self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "Menu") + self.SetMenuBar(menuBar) + + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnExit(self, event): + self.Close() + + def OnToggleItem(self, event): + self.btn.SetLabel( + (self.enabled and "Enable" or "Disable") + " Item") + self.enabled = not self.enabled + + def OnUpdateSimple(self, event): + event.Enable(self.enabled) + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-10/with_accelerator.py b/Chapter-10/with_accelerator.py new file mode 100644 index 0000000..1e2b785 --- /dev/null +++ b/Chapter-10/with_accelerator.py @@ -0,0 +1,47 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Accelerator Example") + p = wx.Panel(self) + menu = wx.Menu() + simple = menu.Append(-1, "Simple &menu item") # with mnemonic + accel = menu.Append(-1, "&Accelerated\tCtrl-A") # with accelerator + + menu.AppendSeparator() + exit = menu.Append(-1, "E&xit") + + self.Bind(wx.EVT_MENU, self.OnSimple, simple) + self.Bind(wx.EVT_MENU, self.OnAccelerated, accel) + self.Bind(wx.EVT_MENU, self.OnExit, exit) + + menuBar = wx.MenuBar() + menuBar.Append(menu, "&Menu") + self.SetMenuBar(menuBar) + + # An alternate way to make accelerators + acceltbl = wx.AcceleratorTable( [ + (wx.ACCEL_CTRL, ord('Q'), exit.GetId()) + ]) + self.SetAcceleratorTable(acceltbl) + + + def OnSimple(self, event): + wx.MessageBox("You selected the simple menu item") + + def OnAccelerated(self, event): + wx.MessageBox("You selected the accelerated menu item") + + + def OnExit(self, event): + self.Close() + + +if __name__ == "__main__": + app = wx.PySimpleApp() + frame = MyFrame() + frame.Show() + app.MainLoop() + + diff --git a/Chapter-11/basicflexgridsizer.py b/Chapter-11/basicflexgridsizer.py new file mode 100644 index 0000000..08204bb --- /dev/null +++ b/Chapter-11/basicflexgridsizer.py @@ -0,0 +1,20 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "FlexGridSizer") + sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + sizer.Add(bw, 0, 0) + center = self.FindWindowByName("five") + center.SetMinSize((150,50)) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/basicgridsizer.py b/Chapter-11/basicgridsizer.py new file mode 100644 index 0000000..01e7e4c --- /dev/null +++ b/Chapter-11/basicgridsizer.py @@ -0,0 +1,18 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class GridSizerFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Basic Grid Sizer") + sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + sizer.Add(bw, 0, 0) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +GridSizerFrame().Show() +app.MainLoop() diff --git a/Chapter-11/blockwindow.py b/Chapter-11/blockwindow.py new file mode 100644 index 0000000..c506568 --- /dev/null +++ b/Chapter-11/blockwindow.py @@ -0,0 +1,20 @@ +import wx + +class BlockWindow(wx.Panel): + def __init__(self, parent, ID=-1, label="", + pos=wx.DefaultPosition, size=(100, 25)): + wx.Panel.__init__(self, parent, ID, pos, size, + wx.RAISED_BORDER, label) + self.label = label + self.SetBackgroundColour("white") + self.SetMinSize(size) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def OnPaint(self, evt): + sz = self.GetClientSize() + dc = wx.PaintDC(self) + w,h = dc.GetTextExtent(self.label) + dc.SetFont(self.GetFont()) + dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2) + + diff --git a/Chapter-11/blockwindow.pyc b/Chapter-11/blockwindow.pyc new file mode 100644 index 0000000..506f54f Binary files /dev/null and b/Chapter-11/blockwindow.pyc differ diff --git a/Chapter-11/bordergridsizer.py b/Chapter-11/bordergridsizer.py new file mode 100644 index 0000000..d03ba5b --- /dev/null +++ b/Chapter-11/bordergridsizer.py @@ -0,0 +1,23 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() +flags = {"one": wx.BOTTOM, "two": wx.ALL, "three": wx.TOP, + "four": wx.LEFT, "five": wx.ALL, "six": wx.RIGHT, + "seven": wx.BOTTOM | wx.TOP, "eight": wx.ALL, + "nine": wx.LEFT | wx.RIGHT} + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "GridSizer Borders") + sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + flag = flags.get(label, 0) + sizer.Add(bw, 0, flag, 10) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/boxsizer.py b/Chapter-11/boxsizer.py new file mode 100644 index 0000000..45b761c --- /dev/null +++ b/Chapter-11/boxsizer.py @@ -0,0 +1,78 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four".split() + +class TestFrame(wx.Frame): + title = "none" + def __init__(self): + wx.Frame.__init__(self, None, -1, self.title) + sizer = self.CreateSizerAndWindows() + self.SetSizer(sizer) + self.Fit() + +class VBoxSizerFrame(TestFrame): + title = "Vertical BoxSizer" + + def CreateSizerAndWindows(self): + sizer = wx.BoxSizer(wx.VERTICAL) + for label in labels: + bw = BlockWindow(self, label=label, size=(200,30)) + sizer.Add(bw, flag=wx.EXPAND) + return sizer + + +class HBoxSizerFrame(TestFrame): + title = "Horizontal BoxSizer" + + def CreateSizerAndWindows(self): + sizer = wx.BoxSizer(wx.HORIZONTAL) + for label in labels: + bw = BlockWindow(self, label=label, size=(75,30)) + sizer.Add(bw, flag=wx.EXPAND) + return sizer + +class VBoxSizerStretchableFrame(TestFrame): + title = "Stretchable BoxSizer" + + def CreateSizerAndWindows(self): + sizer = wx.BoxSizer(wx.VERTICAL) + for label in labels: + bw = BlockWindow(self, label=label, size=(200,30)) + sizer.Add(bw, flag=wx.EXPAND) + + # Add an item that takes all the free space + bw = BlockWindow(self, label="gets all free space", size=(200,30)) + sizer.Add(bw, 1, flag=wx.EXPAND) + return sizer + +class VBoxSizerMultiProportionalFrame(TestFrame): + title = "Proportional BoxSizer" + + def CreateSizerAndWindows(self): + sizer = wx.BoxSizer(wx.VERTICAL) + for label in labels: + bw = BlockWindow(self, label=label, size=(200,30)) + sizer.Add(bw, flag=wx.EXPAND) + + # Add an item that takes one share of the free space + bw = BlockWindow(self, + label="gets 1/3 of the free space", + size=(200,30)) + sizer.Add(bw, 1, flag=wx.EXPAND) + + # Add an item that takes 2 shares of the free space + bw = BlockWindow(self, + label="gets 2/3 of the free space", + size=(200,30)) + sizer.Add(bw, 2, flag=wx.EXPAND) + return sizer + +app = wx.PySimpleApp() +frameList = [VBoxSizerFrame, HBoxSizerFrame, + VBoxSizerStretchableFrame, + VBoxSizerMultiProportionalFrame] +for klass in frameList: + frame = klass() + frame.Show() +app.MainLoop() diff --git a/Chapter-11/gridbagsizer.py b/Chapter-11/gridbagsizer.py new file mode 100644 index 0000000..32f5b7a --- /dev/null +++ b/Chapter-11/gridbagsizer.py @@ -0,0 +1,34 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "GridBagSizer Test") + sizer = wx.GridBagSizer(hgap=5, vgap=5) + for col in range(3): + for row in range(3): + bw = BlockWindow(self, label=labels[row*3 + col]) + sizer.Add(bw, pos=(row,col)) + + # add a window that spans several rows + bw = BlockWindow(self, label="span 3 rows") + sizer.Add(bw, pos=(0,3), span=(3,1), flag=wx.EXPAND) + + # add a window that spans all columns + bw = BlockWindow(self, label="span all columns") + sizer.Add(bw, pos=(3,0), span=(1,4), flag=wx.EXPAND) + + # make the last row and col be stretchable + sizer.AddGrowableCol(3) + sizer.AddGrowableRow(3) + + self.SetSizer(sizer) + self.Fit() + + +app = wx.PySimpleApp() +TestFrame().Show() + +app.MainLoop() diff --git a/Chapter-11/mingridsizer.py b/Chapter-11/mingridsizer.py new file mode 100644 index 0000000..dc97915 --- /dev/null +++ b/Chapter-11/mingridsizer.py @@ -0,0 +1,19 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "GridSizer Test") + sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + sizer.Add(bw, 0, 0) + center = self.FindWindowByName("five") + center.SetMinSize((150,50)) + self.SetSizer(sizer) + self.Fit() +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/prependgridsizer.py b/Chapter-11/prependgridsizer.py new file mode 100644 index 0000000..77b8c19 --- /dev/null +++ b/Chapter-11/prependgridsizer.py @@ -0,0 +1,18 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class GridSizerFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Prepend Grid Sizer") + sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + sizer.Prepend(bw, 0, 0) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +GridSizerFrame().Show() +app.MainLoop() diff --git a/Chapter-11/realworld.py b/Chapter-11/realworld.py new file mode 100644 index 0000000..0069afa --- /dev/null +++ b/Chapter-11/realworld.py @@ -0,0 +1,95 @@ +import wx + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Real World Test") + panel = wx.Panel(self) + + # First create the controls + topLbl = wx.StaticText(panel, -1, "Account Information") + topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) + + nameLbl = wx.StaticText(panel, -1, "Name:") + name = wx.TextCtrl(panel, -1, ""); + + addrLbl = wx.StaticText(panel, -1, "Address:") + addr1 = wx.TextCtrl(panel, -1, ""); + addr2 = wx.TextCtrl(panel, -1, ""); + + cstLbl = wx.StaticText(panel, -1, "City, State, Zip:") + city = wx.TextCtrl(panel, -1, "", size=(150,-1)); + state = wx.TextCtrl(panel, -1, "", size=(50,-1)); + zip = wx.TextCtrl(panel, -1, "", size=(70,-1)); + + phoneLbl = wx.StaticText(panel, -1, "Phone:") + phone = wx.TextCtrl(panel, -1, ""); + + emailLbl = wx.StaticText(panel, -1, "Email:") + email = wx.TextCtrl(panel, -1, ""); + + saveBtn = wx.Button(panel, -1, "Save") + cancelBtn = wx.Button(panel, -1, "Cancel") + + # Now do the layout. + + # mainSizer is the top-level one that manages everything + mainSizer = wx.BoxSizer(wx.VERTICAL) + mainSizer.Add(topLbl, 0, wx.ALL, 5) + mainSizer.Add(wx.StaticLine(panel), 0, + wx.EXPAND|wx.TOP|wx.BOTTOM, 5) + + # addrSizer is a grid that holds all of the address info + addrSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5) + addrSizer.AddGrowableCol(1) + addrSizer.Add(nameLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(name, 0, wx.EXPAND) + addrSizer.Add(addrLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(addr1, 0, wx.EXPAND) + addrSizer.Add((10,10)) # some empty space + addrSizer.Add(addr2, 0, wx.EXPAND) + + addrSizer.Add(cstLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + + # the city, state, zip fields are in a sub-sizer + cstSizer = wx.BoxSizer(wx.HORIZONTAL) + cstSizer.Add(city, 1) + cstSizer.Add(state, 0, wx.LEFT|wx.RIGHT, 5) + cstSizer.Add(zip) + addrSizer.Add(cstSizer, 0, wx.EXPAND) + + addrSizer.Add(phoneLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(phone, 0, wx.EXPAND) + addrSizer.Add(emailLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(email, 0, wx.EXPAND) + + # now add the addrSizer to the mainSizer + mainSizer.Add(addrSizer, 0, wx.EXPAND|wx.ALL, 10) + + # The buttons sizer will put them in a row with resizeable + # gaps between and on either side of the buttons + btnSizer = wx.BoxSizer(wx.HORIZONTAL) + btnSizer.Add((20,20), 1) + btnSizer.Add(saveBtn) + btnSizer.Add((20,20), 1) + btnSizer.Add(cancelBtn) + btnSizer.Add((20,20), 1) + + mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10) + + panel.SetSizer(mainSizer) + + # Fit the frame to the needs of the sizer. The frame will + # automatically resize the panel as needed. Also prevent the + # frame from getting smaller than this size. + mainSizer.Fit(self) + mainSizer.SetSizeHints(self) + + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/resizeflexgridsizer.py b/Chapter-11/resizeflexgridsizer.py new file mode 100644 index 0000000..caceae0 --- /dev/null +++ b/Chapter-11/resizeflexgridsizer.py @@ -0,0 +1,26 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Resizing Flex Grid Sizer") + sizer = wx.FlexGridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + sizer.Add(bw, 0, 0) + center = self.FindWindowByName("five") + center.SetMinSize((150,50)) + sizer.AddGrowableCol(0, 1) + sizer.AddGrowableCol(1, 2) + sizer.AddGrowableCol(2, 1) + sizer.AddGrowableRow(0, 1) + sizer.AddGrowableRow(1, 5) + sizer.AddGrowableRow(2, 1) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/resizegridsizer.py b/Chapter-11/resizegridsizer.py new file mode 100644 index 0000000..ca512bf --- /dev/null +++ b/Chapter-11/resizegridsizer.py @@ -0,0 +1,22 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() +flags = {"one": wx.ALIGN_BOTTOM, "two": wx.ALIGN_CENTER, + "four": wx.ALIGN_RIGHT, "six": wx.EXPAND, "seven": wx.EXPAND, + "eight": wx.SHAPED} + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "GridSizer Resizing") + sizer = wx.GridSizer(rows=3, cols=3, hgap=5, vgap=5) + for label in labels: + bw = BlockWindow(self, label=label) + flag = flags.get(label, 0) + sizer.Add(bw, 0, flag) + self.SetSizer(sizer) + self.Fit() + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-11/staticboxsizer.py b/Chapter-11/staticboxsizer.py new file mode 100644 index 0000000..76cf4f7 --- /dev/null +++ b/Chapter-11/staticboxsizer.py @@ -0,0 +1,45 @@ +import wx +from blockwindow import BlockWindow + +labels = "one two three four five six seven eight nine".split() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "StaticBoxSizer Test") + self.panel = wx.Panel(self) + + # make three static boxes with windows positioned inside them + box1 = self.MakeStaticBoxSizer("Box 1", labels[0:3]) + box2 = self.MakeStaticBoxSizer("Box 2", labels[3:6]) + box3 = self.MakeStaticBoxSizer("Box 3", labels[6:9]) + + # We can also use a sizer to manage the placement of other + # sizers (and therefore the windows and sub-sizers that they + # manage as well.) + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(box1, 0, wx.ALL, 10) + sizer.Add(box2, 0, wx.ALL, 10) + sizer.Add(box3, 0, wx.ALL, 10) + + self.panel.SetSizer(sizer) + sizer.Fit(self) + + + def MakeStaticBoxSizer(self, boxlabel, itemlabels): + # first the static box + box = wx.StaticBox(self.panel, -1, boxlabel) + + # then the sizer + sizer = wx.StaticBoxSizer(box, wx.VERTICAL) + + # then add items to it like normal + for label in itemlabels: + bw = BlockWindow(self.panel, label=label) + sizer.Add(bw, 0, wx.ALL, 2) + + return sizer + + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-12/draw_image.py b/Chapter-12/draw_image.py new file mode 100644 index 0000000..12ae522 --- /dev/null +++ b/Chapter-12/draw_image.py @@ -0,0 +1,46 @@ +# This one shows how to draw images on a DC. + +import wx +import random +random.seed() + +class RandomImagePlacementWindow(wx.Window): + def __init__(self, parent, image): + wx.Window.__init__(self, parent) + self.photo = image.ConvertToBitmap() + + # choose some random positions to draw the image at: + self.positions = [(10,10)] + for x in range(50): + x = random.randint(0, 1000) + y = random.randint(0, 1000) + self.positions.append( (x,y) ) + + # Bind the Paint event + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def OnPaint(self, evt): + # create and clear the DC + dc = wx.PaintDC(self) + brush = wx.Brush("sky blue") + dc.SetBackground(brush) + dc.Clear() + + # draw the image in random locations + for x,y in self.positions: + dc.DrawBitmap(self.photo, x, y, True) + + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Loading Images", + size=(640,480)) + img = wx.Image("masked-portrait.png") + win = RandomImagePlacementWindow(self, img) + + +app = wx.PySimpleApp() +frm = TestFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-12/image.bmp b/Chapter-12/image.bmp new file mode 100644 index 0000000..bbab4cd Binary files /dev/null and b/Chapter-12/image.bmp differ diff --git a/Chapter-12/image.gif b/Chapter-12/image.gif new file mode 100644 index 0000000..34b3e03 Binary files /dev/null and b/Chapter-12/image.gif differ diff --git a/Chapter-12/image.jpg b/Chapter-12/image.jpg new file mode 100644 index 0000000..8ec8f48 Binary files /dev/null and b/Chapter-12/image.jpg differ diff --git a/Chapter-12/image.png b/Chapter-12/image.png new file mode 100644 index 0000000..5a4ce74 Binary files /dev/null and b/Chapter-12/image.png differ diff --git a/Chapter-12/images.py b/Chapter-12/images.py new file mode 100644 index 0000000..aaa9c5b --- /dev/null +++ b/Chapter-12/images.py @@ -0,0 +1,35 @@ +import wx + +filenames = ["image.bmp", "image.gif", "image.jpg", "image.png" ] + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Loading Images") + p = wx.Panel(self) + + fgs = wx.FlexGridSizer(cols=2, hgap=10, vgap=10) + for name in filenames: + # load the image from the file + img1 = wx.Image(name, wx.BITMAP_TYPE_ANY) + + # Scale the original to another wx.Image + w = img1.GetWidth() + h = img1.GetHeight() + img2 = img1.Scale(w/2, h/2) + + # turn them into static bitmap widgets + sb1 = wx.StaticBitmap(p, -1, wx.BitmapFromImage(img1)) + sb2 = wx.StaticBitmap(p, -1, wx.BitmapFromImage(img2)) + + # and put them into the sizer + fgs.Add(sb1) + fgs.Add(sb2) + + p.SetSizerAndFit(fgs) + self.Fit() + + +app = wx.PySimpleApp() +frm = TestFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-12/masked-portrait.png b/Chapter-12/masked-portrait.png new file mode 100644 index 0000000..652da99 Binary files /dev/null and b/Chapter-12/masked-portrait.png differ diff --git a/Chapter-12/radargraph.py b/Chapter-12/radargraph.py new file mode 100644 index 0000000..89908fd --- /dev/null +++ b/Chapter-12/radargraph.py @@ -0,0 +1,170 @@ +import wx +import math +import random + +class RadarGraph(wx.Window): + """ + A simple radar graph that plots a collection of values in the + range of 0-100 onto a polar coordinate system designed to easily + show outliers, etc. You might use this kind of graph to monitor + some sort of resource allocation metrics, and a quick glance at + the graph can tell you when conditions are good (within some + accepted tolerance level) or approaching critical levels (total + resource consumption). + """ + def __init__(self, parent, title, labels): + wx.Window.__init__(self, parent) + self.title = title + self.labels = labels + self.data = [0.0] * len(labels) + self.titleFont = wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD) + self.labelFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL) + + self.InitBuffer() + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def OnSize(self, evt): + # When the window size changes we need a new buffer. + self.InitBuffer() + + + def OnPaint(self, evt): + # This automatically Blits self.buffer to a wx.PaintDC when + # the dc is destroyed, and so nothing else needs done. + dc = wx.BufferedPaintDC(self, self.buffer) + + + def InitBuffer(self): + # Create the buffer bitmap to be the same size as the window, + # then draw our graph to it. Since we use wx.BufferedDC + # whatever is drawn to the buffer is also drawn to the window. + w, h = self.GetClientSize() + self.buffer = wx.EmptyBitmap(w, h) + dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) + self.DrawGraph(dc) + + + def GetData(self): + return self.data + + def SetData(self, newData): + assert len(newData) == len(self.data) + self.data = newData[:] + + # The data has changed, so update the buffer and the window + dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) + self.DrawGraph(dc) + + + def PolarToCartesian(self, radius, angle, cx, cy): + x = radius * math.cos(math.radians(angle)) + y = radius * math.sin(math.radians(angle)) + return (cx+x, cy-y) + + + def DrawGraph(self, dc): + spacer = 10 + scaledmax = 150.0 + + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + dw, dh = dc.GetSize() + + # Find out where to draw the title and do it + dc.SetFont(self.titleFont) + tw, th = dc.GetTextExtent(self.title) + dc.DrawText(self.title, (dw-tw)/2, spacer) + + # find the center of the space below the title + th = th + 2*spacer + cx = dw/2 + cy = (dh-th)/2 + th + + # calculate a scale factor to use for drawing the graph based + # on the minimum available width or height + mindim = min(cx, (dh-th)/2) + scale = mindim/scaledmax + + # draw the graph axis and "bulls-eye" with rings at scaled 25, + # 50, 75 and 100 positions + dc.SetPen(wx.Pen("black", 1)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.DrawCircle(cx,cy, 25*scale) + dc.DrawCircle(cx,cy, 50*scale) + dc.DrawCircle(cx,cy, 75*scale) + dc.DrawCircle(cx,cy, 100*scale) + + dc.SetPen(wx.Pen("black", 2)) + dc.DrawLine(cx-110*scale, cy, cx+110*scale, cy) + dc.DrawLine(cx, cy-110*scale, cx, cy+110*scale) + + # Now find the coordinates for each data point, draw the + # labels, and find the max data point + dc.SetFont(self.labelFont) + maxval = 0 + angle = 0 + polypoints = [] + for i, label in enumerate(self.labels): + val = self.data[i] + point = self.PolarToCartesian(val*scale, angle, cx, cy) + polypoints.append(point) + x, y = self.PolarToCartesian(125*scale, angle, cx,cy) + dc.DrawText(label, x, y) + if val > maxval: + maxval = val + angle = angle + 360/len(self.labels) + + # Set the brush color based on the max value (green is good, + # red is bad) + c = "forest green" + if maxval > 70: + c = "yellow" + if maxval > 95: + c = "red" + + # Finally, draw the plot data as a filled polygon + dc.SetBrush(wx.Brush(c)) + dc.SetPen(wx.Pen("navy", 3)) + dc.DrawPolygon(polypoints) + + + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Double Buffered Drawing", + size=(480,480)) + self.plot = RadarGraph(self, "Sample 'Radar' Plot", + ["A", "B", "C", "D", "E", "F", "G", "H"]) + + # Set some random initial data values + data = [] + for d in self.plot.GetData(): + data.append(random.randint(0, 75)) + self.plot.SetData(data) + + # Create a timer to update the data values + self.Bind(wx.EVT_TIMER, self.OnTimeout) + self.timer = wx.Timer(self) + self.timer.Start(500) + + + def OnTimeout(self, evt): + # simulate the positive or negative growth of each data value + data = [] + for d in self.plot.GetData(): + val = d + random.uniform(-5, 5) + if val < 0: + val = 0 + if val > 110: + val = 110 + data.append(val) + self.plot.SetData(data) + + +app = wx.PySimpleApp() +frm = TestFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-13/data.py b/Chapter-13/data.py new file mode 100644 index 0000000..76d2231 --- /dev/null +++ b/Chapter-13/data.py @@ -0,0 +1,41 @@ + +# Just some simple data structures for the report mode listctrl examples + +columns = ["Request ID", "Summary", "Date", "Submitted By"] + +rows = [ + ("987441", "additions to RTTI?", "2004-07-08 10:22", "g00fy"), + ("846368", "wxTextCtrl - disable auto-scrolling", "2003-11-20 21:25", "ryannpcs"), + ("846367", "Less flicker when resizing a window", "2003-11-20 21:24", "ryannpcs"), + ("846366", "Wishlist - wxDbGetConnection return value", "2003-11-20 21:23", "ryannpcs"), + ("846364", "wxPostscriptDC with floating point coordinates", "2003-11-20 21:22", "ryannpcs"), + ("846363", "Wishlist - Better wxString Formatting", "2003-11-20 21:22", "ryannpcs"), + ("846362", "Wishlist - Crossplatform wxRichText Widget", "2003-11-20 21:20", "ryannpcs"), + ("953341", "Support for paper trays when printing", "2004-05-13 08:01", "tonye"), + ("952466", "mac menu - Window menu", "2004-05-12 03:19", "pimbuur"), + ("928899", "FloatCanvas demo should work with numarray", "2004-04-03 08:30", "glchapman"), + ("912714", "wxGrid: Support for Search / Replace", "2004-03-09 05:46", "rclund"), + ("901061", "wxComboBox - add small icons as in MSW CComboBoxEx ", "2004-02-20 04:04", "tomash"), + ("900768", "Please add more codepages support to your source built", "2004-02-19 15:49", "jsat66"), + ("894921", "trigger on event-system creation", "2004-02-11 08:10", "g00fy"), + ("869808", "HitTest in wxCheckListBox", "2004-01-03 01:22", "dickkniep"), + ("863306", "wxGrid - Thaw/Freeze column/row", "2003-12-19 17:54", "zinit"), + ("975435", "wxMenu anchor right position in wxMenuBar", "2004-06-18 08:44", "jmt2715"), + ("969811", "wxColourEnumerator", "2004-06-09 11:41", "wyo"), + ("959849", "wx.Grid gridlines past max row/col", "2004-05-24 19:39", "dodywijaya"), + ("959158", "wxGrid: Arbitrary controls in the grid", "2004-05-23 16:56", "somecoder"), + ("953824", "mac menu - Window menu", "2004-05-14 02:06", "pimbuur"), + ("863301", "wxTextCtrl - edit mode", "2003-12-19 17:48", "zinit"), + ("855902", "virtual window classes", "2003-12-07 12:39", "cursorstar"), + ("852379", "wxGrid row/col size limits", "2003-12-01 15:47", "jbrouwers"), + ("846375", "wxGraphicsPath", "2003-11-20 21:30", "ryannpcs"), + ("846374", "wxToolBar - return tool at position", "2003-11-20 21:29", "ryannpcs"), + ("846373", "Scrolling improvements", "2003-11-20 21:28", "ryannpcs"), + ("846372", "Hooks for standard remote events", "2003-11-20 21:27", "ryannpcs"), + ("846370", "wxDial - dial widget", "2003-11-20 21:27", "ryannpcs"), + ("846369", "wxGird - auto-scrolling", "2003-11-20 21:26", "ryannpcs"), + ("846361", "Wishlist - wxStaticText that takes fonts, colors, etc.", "2003-11-20 21:19", "ryannpcs"), + ("819559", "wxListCtrl column widths in wxLC_ICON mode ", "2003-10-07 13:34", "nwmoriarty"), + ("817429", "OS X wheel mouse", "2003-10-03 14:05", "mdcowles"), +] + diff --git a/Chapter-13/data.pyc b/Chapter-13/data.pyc new file mode 100644 index 0000000..aa8e721 Binary files /dev/null and b/Chapter-13/data.pyc differ diff --git a/Chapter-13/icon01.png b/Chapter-13/icon01.png new file mode 100644 index 0000000..0825ab7 Binary files /dev/null and b/Chapter-13/icon01.png differ diff --git a/Chapter-13/icon02.png b/Chapter-13/icon02.png new file mode 100644 index 0000000..171b8b3 Binary files /dev/null and b/Chapter-13/icon02.png differ diff --git a/Chapter-13/icon03.png b/Chapter-13/icon03.png new file mode 100644 index 0000000..2b0e95f Binary files /dev/null and b/Chapter-13/icon03.png differ diff --git a/Chapter-13/icon04.png b/Chapter-13/icon04.png new file mode 100644 index 0000000..55bf370 Binary files /dev/null and b/Chapter-13/icon04.png differ diff --git a/Chapter-13/icon05.png b/Chapter-13/icon05.png new file mode 100644 index 0000000..e39b52f Binary files /dev/null and b/Chapter-13/icon05.png differ diff --git a/Chapter-13/icon06.png b/Chapter-13/icon06.png new file mode 100644 index 0000000..65c9f8b Binary files /dev/null and b/Chapter-13/icon06.png differ diff --git a/Chapter-13/icon07.png b/Chapter-13/icon07.png new file mode 100644 index 0000000..1459cfb Binary files /dev/null and b/Chapter-13/icon07.png differ diff --git a/Chapter-13/icon08.png b/Chapter-13/icon08.png new file mode 100644 index 0000000..97b4fda Binary files /dev/null and b/Chapter-13/icon08.png differ diff --git a/Chapter-13/list_icon.py b/Chapter-13/list_icon.py new file mode 100644 index 0000000..01a6f08 --- /dev/null +++ b/Chapter-13/list_icon.py @@ -0,0 +1,32 @@ +import wx +import sys, glob + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "wx.ListCtrl in wx.LC_ICON mode", + size=(600,400)) + + # load some images into an image list + il = wx.ImageList(32,32, True) + for name in glob.glob("icon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + + # create the list control + self.list = wx.ListCtrl(self, -1, + style=wx.LC_ICON | wx.LC_AUTOARRANGE) + + # assign the image list to it + self.list.AssignImageList(il, wx.IMAGE_LIST_NORMAL) + + # create some items for the list + for x in range(25): + img = x % (il_max+1) + self.list.InsertImageStringItem(x, + "This is item %02d" % x, img) + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/list_list.py b/Chapter-13/list_list.py new file mode 100644 index 0000000..23210dc --- /dev/null +++ b/Chapter-13/list_list.py @@ -0,0 +1,34 @@ +import wx +import sys, glob + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "wx.ListCtrl in wx.LC_LIST mode", + size=(600,400)) + + # load some images into an image list + il = wx.ImageList(16,16, True) + for name in glob.glob("smicon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + + # create the list control + self.list = wx.ListCtrl(self, -1, style=wx.LC_LIST) + + # assign the image list to it + self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) + + # create some items for the list + for x in range(25): + img = x % (il_max+1) + self.list.InsertImageStringItem(x, + "This is item %02d" % x, + img) + + + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/list_report.py b/Chapter-13/list_report.py new file mode 100644 index 0000000..0b20fe9 --- /dev/null +++ b/Chapter-13/list_report.py @@ -0,0 +1,42 @@ +import wx +import sys, glob, random +import data + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "wx.ListCtrl in wx.LC_REPORT mode", + size=(600,400)) + + il = wx.ImageList(16,16, True) + for name in glob.glob("smicon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT) + self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) + + # Add some columns + for col, text in enumerate(data.columns): + self.list.InsertColumn(col, text) + + # add the rows + for item in data.rows: + index = self.list.InsertStringItem(sys.maxint, item[0]) + for col, text in enumerate(item[1:]): + self.list.SetStringItem(index, col+1, text) + + # give each item a random image + img = random.randint(0, il_max) + self.list.SetItemImage(index, img, img) + + # set the width of the columns in various ways + self.list.SetColumnWidth(0, 120) + self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) + + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/list_report_colsort.py b/Chapter-13/list_report_colsort.py new file mode 100644 index 0000000..fbf98e3 --- /dev/null +++ b/Chapter-13/list_report_colsort.py @@ -0,0 +1,70 @@ +import wx +import wx.lib.mixins.listctrl +import sys, glob, random +import data + +class DemoFrame(wx.Frame, wx.lib.mixins.listctrl.ColumnSorterMixin): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "wx.ListCtrl with ColumnSorterMixin", + size=(600,400)) + + # load some images into an image list + il = wx.ImageList(16,16, True) + for name in glob.glob("smicon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + + # add some arrows for the column sorter + self.up = il.AddWithColourMask( + wx.Bitmap("sm_up.bmp", wx.BITMAP_TYPE_BMP), "blue") + self.dn = il.AddWithColourMask( + wx.Bitmap("sm_down.bmp", wx.BITMAP_TYPE_BMP), "blue") + + # create the list control + self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT) + + # assign the image list to it + self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) + + # Add some columns + for col, text in enumerate(data.columns): + self.list.InsertColumn(col, text) + + # add the rows + self.itemDataMap = {} + for item in data.rows: + index = self.list.InsertStringItem(sys.maxint, item[0]) + for col, text in enumerate(item[1:]): + self.list.SetStringItem(index, col+1, text) + + # give each item a data value, and map it back to the + # item values, for the column sorter + self.list.SetItemData(index, index) + self.itemDataMap[index] = item + + # give each item a random image + img = random.randint(0, il_max) + self.list.SetItemImage(index, img, img) + + # set the width of the columns in various ways + self.list.SetColumnWidth(0, 120) + self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) + + # initialize the column sorter + wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, + len(data.columns)) + + def GetListCtrl(self): + return self.list + + def GetSortImages(self): + return (self.dn, self.up) + + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/list_report_etc.py b/Chapter-13/list_report_etc.py new file mode 100644 index 0000000..92d0529 --- /dev/null +++ b/Chapter-13/list_report_etc.py @@ -0,0 +1,210 @@ +import wx +import sys, glob, random +import data + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Other wx.ListCtrl Stuff", + size=(700,500)) + self.list = None + self.editable = False + self.MakeMenu() + self.MakeListCtrl() + + + def MakeListCtrl(self, otherflags=0): + # if we already have a listctrl then get rid of it + if self.list: + self.list.Destroy() + + if self.editable: + otherflags |= wx.LC_EDIT_LABELS + + # load some images into an image list + il = wx.ImageList(16,16, True) + for name in glob.glob("smicon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + + # create the list control + self.list = wx.ListCtrl(self, -1, style=wx.LC_REPORT|otherflags) + + # assign the image list to it + self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) + + # Add some columns + for col, text in enumerate(data.columns): + self.list.InsertColumn(col, text) + + # add the rows + for row, item in enumerate(data.rows): + index = self.list.InsertStringItem(sys.maxint, item[0]) + for col, text in enumerate(item[1:]): + self.list.SetStringItem(index, col+1, text) + + # give each item a random image + img = random.randint(0, il_max) + self.list.SetItemImage(index, img, img) + + # set the data value for each item to be its position in + # the data list + self.list.SetItemData(index, row) + + + # set the width of the columns in various ways + self.list.SetColumnWidth(0, 120) + self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER) + + # bind some interesting events + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list) + self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.list) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self.list) + + # in case we are recreating the list tickle the frame a bit so + # it will redo the layout + self.SendSizeEvent() + + + def MakeMenu(self): + mbar = wx.MenuBar() + menu = wx.Menu() + item = menu.Append(-1, "E&xit\tAlt-X") + self.Bind(wx.EVT_MENU, self.OnExit, item) + mbar.Append(menu, "&File") + + menu = wx.Menu() + item = menu.Append(-1, "Sort ascending") + self.Bind(wx.EVT_MENU, self.OnSortAscending, item) + item = menu.Append(-1, "Sort descending") + self.Bind(wx.EVT_MENU, self.OnSortDescending, item) + item = menu.Append(-1, "Sort by submitter") + self.Bind(wx.EVT_MENU, self.OnSortBySubmitter, item) + + menu.AppendSeparator() + item = menu.Append(-1, "Show selected") + self.Bind(wx.EVT_MENU, self.OnShowSelected, item) + item = menu.Append(-1, "Select all") + self.Bind(wx.EVT_MENU, self.OnSelectAll, item) + item = menu.Append(-1, "Select none") + self.Bind(wx.EVT_MENU, self.OnSelectNone, item) + + menu.AppendSeparator() + item = menu.Append(-1, "Set item text colour") + self.Bind(wx.EVT_MENU, self.OnSetTextColour, item) + item = menu.Append(-1, "Set item background colour") + self.Bind(wx.EVT_MENU, self.OnSetBGColour, item) + + menu.AppendSeparator() + item = menu.Append(-1, "Enable item editing", kind=wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU, self.OnEnableEditing, item) + item = menu.Append(-1, "Edit current item") + self.Bind(wx.EVT_MENU, self.OnEditItem, item) + mbar.Append(menu, "&Demo") + + self.SetMenuBar(mbar) + + + + def OnExit(self, evt): + self.Close() + + + def OnItemSelected(self, evt): + item = evt.GetItem() + print "Item selected:", item.GetText() + + def OnItemDeselected(self, evt): + item = evt.GetItem() + print "Item deselected:", item.GetText() + + def OnItemActivated(self, evt): + item = evt.GetItem() + print "Item activated:", item.GetText() + + def OnSortAscending(self, evt): + # recreate the listctrl with a sort style + self.MakeListCtrl(wx.LC_SORT_ASCENDING) + + def OnSortDescending(self, evt): + # recreate the listctrl with a sort style + self.MakeListCtrl(wx.LC_SORT_DESCENDING) + + def OnSortBySubmitter(self, evt): + def compare_func(row1, row2): + # compare the values in the 4th col of the data + val1 = data.rows[row1][3] + val2 = data.rows[row2][3] + if val1 < val2: return -1 + if val1 > val2: return 1 + return 0 + + self.list.SortItems(compare_func) + + + + def OnShowSelected(self, evt): + print "These items are selected:" + index = self.list.GetFirstSelected() + if index == -1: + print "\tNone" + return + while index != -1: + item = self.list.GetItem(index) + print "\t%s" % item.GetText() + index = self.list.GetNextSelected(index) + + def OnSelectAll(self, evt): + for index in range(self.list.GetItemCount()): + self.list.Select(index, True) + + def OnSelectNone(self, evt): + index = self.list.GetFirstSelected() + while index != -1: + self.list.Select(index, False) + index = self.list.GetNextSelected(index) + + + def OnSetTextColour(self, evt): + dlg = wx.ColourDialog(self) + if dlg.ShowModal() == wx.ID_OK: + colour = dlg.GetColourData().GetColour() + index = self.list.GetFirstSelected() + while index != -1: + self.list.SetItemTextColour(index, colour) + index = self.list.GetNextSelected(index) + dlg.Destroy() + + def OnSetBGColour(self, evt): + dlg = wx.ColourDialog(self) + if dlg.ShowModal() == wx.ID_OK: + colour = dlg.GetColourData().GetColour() + index = self.list.GetFirstSelected() + while index != -1: + self.list.SetItemBackgroundColour(index, colour) + index = self.list.GetNextSelected(index) + dlg.Destroy() + + + def OnEnableEditing(self, evt): + self.editable = evt.IsChecked() + self.MakeListCtrl() + + def OnEditItem(self, evt): + index = self.list.GetFirstSelected() + if index != -1: + self.list.EditLabel(index) + + +class DemoApp(wx.App): + def OnInit(self): + frame = DemoFrame() + self.SetTopWindow(frame) + print "Program output appears here..." + frame.Show() + return True + +app = DemoApp(redirect=True) +app.MainLoop() diff --git a/Chapter-13/list_report_virtual.py b/Chapter-13/list_report_virtual.py new file mode 100644 index 0000000..caeb262 --- /dev/null +++ b/Chapter-13/list_report_virtual.py @@ -0,0 +1,66 @@ +import wx +import sys, glob, random +import data + +class DataSource: + """ + A simple data source class that just uses our sample data items. + A real data source class would manage fetching items from a + database or similar. + """ + def GetColumnHeaders(self): + return data.columns + + def GetCount(self): + return len(data.rows) + + def GetItem(self, index): + return data.rows[index] + + def UpdateCache(self, start, end): + pass + + +class VirtualListCtrl(wx.ListCtrl): + """ + A generic virtual listctrl that fetches data from a DataSource. + """ + def __init__(self, parent, dataSource): + wx.ListCtrl.__init__(self, parent, + style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VIRTUAL) + self.dataSource = dataSource + self.Bind(wx.EVT_LIST_CACHE_HINT, self.DoCacheItems) + self.SetItemCount(dataSource.GetCount()) + + columns = dataSource.GetColumnHeaders() + for col, text in enumerate(columns): + self.InsertColumn(col, text) + + + def DoCacheItems(self, evt): + self.dataSource.UpdateCache( + evt.GetCacheFrom(), evt.GetCacheTo()) + + def OnGetItemText(self, item, col): + data = self.dataSource.GetItem(item) + return data[col] + + def OnGetItemAttr(self, item): return None + def OnGetItemImage(self, item): return -1 + + + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "Virtual wx.ListCtrl", + size=(600,400)) + + self.list = VirtualListCtrl(self, DataSource()) + + + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/list_smicon.py b/Chapter-13/list_smicon.py new file mode 100644 index 0000000..9a81b0a --- /dev/null +++ b/Chapter-13/list_smicon.py @@ -0,0 +1,35 @@ +import wx +import sys, glob + +class DemoFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, + "wx.ListCtrl in wx.LC_SMALL_ICON mode", + size=(600,400)) + + # load some images into an image list + il = wx.ImageList(16,16, True) + for name in glob.glob("smicon??.png"): + bmp = wx.Bitmap(name, wx.BITMAP_TYPE_PNG) + il_max = il.Add(bmp) + + # create the list control + self.list = wx.ListCtrl(self, -1, + style=wx.LC_SMALL_ICON + | wx.LC_AUTOARRANGE + ) + + # assign the image list to it + self.list.AssignImageList(il, wx.IMAGE_LIST_SMALL) + + # create some items for the list + for x in range(25): + img = x % (il_max+1) + self.list.InsertImageStringItem(x, + "This is item %02d" % x, + img) + +app = wx.PySimpleApp() +frame = DemoFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-13/sm_down.bmp b/Chapter-13/sm_down.bmp new file mode 100644 index 0000000..df9e624 Binary files /dev/null and b/Chapter-13/sm_down.bmp differ diff --git a/Chapter-13/sm_up.bmp b/Chapter-13/sm_up.bmp new file mode 100644 index 0000000..8935c9f Binary files /dev/null and b/Chapter-13/sm_up.bmp differ diff --git a/Chapter-13/smicon01.png b/Chapter-13/smicon01.png new file mode 100644 index 0000000..fc6bcdb Binary files /dev/null and b/Chapter-13/smicon01.png differ diff --git a/Chapter-13/smicon02.png b/Chapter-13/smicon02.png new file mode 100644 index 0000000..3c112b5 Binary files /dev/null and b/Chapter-13/smicon02.png differ diff --git a/Chapter-13/smicon03.png b/Chapter-13/smicon03.png new file mode 100644 index 0000000..752707d Binary files /dev/null and b/Chapter-13/smicon03.png differ diff --git a/Chapter-13/smicon04.png b/Chapter-13/smicon04.png new file mode 100644 index 0000000..5b734cd Binary files /dev/null and b/Chapter-13/smicon04.png differ diff --git a/Chapter-13/smicon05.png b/Chapter-13/smicon05.png new file mode 100644 index 0000000..d67a6c7 Binary files /dev/null and b/Chapter-13/smicon05.png differ diff --git a/Chapter-13/smicon06.png b/Chapter-13/smicon06.png new file mode 100644 index 0000000..7b644d5 Binary files /dev/null and b/Chapter-13/smicon06.png differ diff --git a/Chapter-14/grid_attr.py b/Chapter-14/grid_attr.py new file mode 100644 index 0000000..ec4b5be --- /dev/null +++ b/Chapter-14/grid_attr.py @@ -0,0 +1,33 @@ +import wx +import wx.grid + +class TestFrame(wx.Frame): + + + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Attributes", + size=(600,300)) + grid = wx.grid.Grid(self) + grid.CreateGrid(10,6) + for row in range(10): + for col in range(6): + grid.SetCellValue(row, col, "(%s,%s)" % (row, col)) + + grid.SetCellTextColour(1, 1, "red") + grid.SetCellFont(1,1, wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + grid.SetCellBackgroundColour(2, 2, "light blue") + + attr = wx.grid.GridCellAttr() + attr.SetTextColour("navyblue") + attr.SetBackgroundColour("pink") + attr.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + + grid.SetAttr(4, 0, attr) + grid.SetAttr(5, 1, attr) + grid.SetRowAttr(8, attr) + + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_basic.py b/Chapter-14/grid_basic.py new file mode 100644 index 0000000..1f52aee --- /dev/null +++ b/Chapter-14/grid_basic.py @@ -0,0 +1,18 @@ +import wx +import wx.grid + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Simple Grid", + size=(640,480)) + grid = wx.grid.Grid(self) + grid.CreateGrid(50,50) + for row in range(20): + for col in range(6): + grid.SetCellValue(row, col, + "cell (%d,%d)" % (row, col)) + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_editor.py b/Chapter-14/grid_editor.py new file mode 100644 index 0000000..98d6296 --- /dev/null +++ b/Chapter-14/grid_editor.py @@ -0,0 +1,111 @@ +import wx +import wx.grid +import string + +class UpCaseCellEditor(wx.grid.PyGridCellEditor): + def __init__(self): + wx.grid.PyGridCellEditor.__init__(self) + + def Create(self, parent, id, evtHandler): + """ + Called to create the control, which must derive from wx.Control. + *Must Override* + """ + self._tc = wx.TextCtrl(parent, id, "") + self._tc.SetInsertionPoint(0) + self.SetControl(self._tc) + + if evtHandler: + self._tc.PushEventHandler(evtHandler) + + self._tc.Bind(wx.EVT_CHAR, self.OnChar) + + def SetSize(self, rect): + """ + Called to position/size the edit control within the cell rectangle. + If you don't fill the cell (the rect) then be sure to override + PaintBackground and do something meaningful there. + """ + self._tc.SetDimensions(rect.x, rect.y, rect.width+2, rect.height+2, + wx.SIZE_ALLOW_MINUS_ONE) + + def BeginEdit(self, row, col, grid): + """ + Fetch the value from the table and prepare the edit control + to begin editing. Set the focus to the edit control. + *Must Override* + """ + self.startValue = grid.GetTable().GetValue(row, col) + self._tc.SetValue(self.startValue) + self._tc.SetInsertionPointEnd() + self._tc.SetFocus() + self._tc.SetSelection(0, self._tc.GetLastPosition()) + + def EndEdit(self, row, col, grid): + """ + Complete the editing of the current cell. Returns True if the value + has changed. If necessary, the control may be destroyed. + *Must Override* + """ + changed = False + + val = self._tc.GetValue() + + if val != self.startValue: + changed = True + grid.GetTable().SetValue(row, col, val) # update the table + + self.startValue = '' + self._tc.SetValue('') + return changed + + def Reset(self): + """ + Reset the value in the control back to its starting value. + *Must Override* + """ + self._tc.SetValue(self.startValue) + self._tc.SetInsertionPointEnd() + + def Clone(self): + """ + Create a new object which is the copy of this one + *Must Override* + """ + return UpCaseCellEditor() + + def StartingKey(self, evt): + """ + If the editor is enabled by pressing keys on the grid, this will be + called to let the editor do something about that first key if desired. + """ + self.OnChar(evt) + if evt.GetSkipped(): + self._tc.EmulateKeyPress(evt) + + def OnChar(self, evt): + key = evt.GetKeyCode() + if key > 255: + evt.Skip() + return + char = chr(key) + if char in string.letters: + char = char.upper() + self._tc.WriteText(char) + else: + evt.Skip() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Editor", + size=(640,480)) + + grid = wx.grid.Grid(self) + grid.CreateGrid(50,50) + grid.SetDefaultEditor(UpCaseCellEditor()) + + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_headers.py b/Chapter-14/grid_headers.py new file mode 100644 index 0000000..2c43171 --- /dev/null +++ b/Chapter-14/grid_headers.py @@ -0,0 +1,24 @@ +import wx +import wx.grid + +class TestFrame(wx.Frame): + + rowLabels = ["uno", "dos", "tres", "quatro", "cinco"] + colLabels = ["homer", "marge", "bart", "lisa", "maggie"] + + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Headers", + size=(500,200)) + grid = wx.grid.Grid(self) + grid.CreateGrid(5,5) + for row in range(5): + grid.SetRowLabelValue(row, self.rowLabels[row]) + grid.SetColLabelValue(row, self.colLabels[row]) + for col in range(5): + grid.SetCellValue(row, col, + "(%s,%s)" % (self.rowLabels[row], self.colLabels[col])) + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_renderer.py b/Chapter-14/grid_renderer.py new file mode 100644 index 0000000..415fd50 --- /dev/null +++ b/Chapter-14/grid_renderer.py @@ -0,0 +1,59 @@ +import wx +import wx.grid +import random + +class RandomBackgroundRenderer(wx.grid.PyGridCellRenderer): + def __init__(self): + wx.grid.PyGridCellRenderer.__init__(self) + + + def Draw(self, grid, attr, dc, rect, row, col, isSelected): + text = grid.GetCellValue(row, col) + hAlign, vAlign = attr.GetAlignment() + dc.SetFont( attr.GetFont() ) + if isSelected: + bg = grid.GetSelectionBackground() + fg = grid.GetSelectionForeground() + else: + bg = random.choice(["pink", "sky blue", "cyan", "yellow", "plum"]) + fg = attr.GetTextColour() + + dc.SetTextBackground(bg) + dc.SetTextForeground(fg) + dc.SetBrush(wx.Brush(bg, wx.SOLID)) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(rect) + grid.DrawTextRectangle(dc, text, rect, hAlign, vAlign) + + + def GetBestSize(self, grid, attr, dc, row, col): + text = grid.GetCellValue(row, col) + dc.SetFont(attr.GetFont()) + w, h = dc.GetTextExtent(text) + return wx.Size(w, h) + + def Clone(self): + return RandomBackgroundRenderer() + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Renderer", + size=(640,480)) + + grid = wx.grid.Grid(self) + grid.CreateGrid(50,50) + + # Set this custom renderer just for row 4 + attr = wx.grid.GridCellAttr() + attr.SetRenderer(RandomBackgroundRenderer()) + grid.SetRowAttr(4, attr) + + for row in range(10): + for col in range(10): + grid.SetCellValue(row, col, + "cell (%d,%d)" % (row, col)) + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_size.py b/Chapter-14/grid_size.py new file mode 100644 index 0000000..d23c21e --- /dev/null +++ b/Chapter-14/grid_size.py @@ -0,0 +1,23 @@ +import wx +import wx.grid + +class TestFrame(wx.Frame): + + + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Sizes", + size=(600,300)) + grid = wx.grid.Grid(self) + grid.CreateGrid(5,5) + for row in range(5): + for col in range(5): + grid.SetCellValue(row, col, "(%s,%s)" % (row, col)) + + grid.SetCellSize(2, 2, 2, 3) + grid.SetColSize(1, 125) + grid.SetRowSize(1, 100) + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_table.py b/Chapter-14/grid_table.py new file mode 100644 index 0000000..ee1705c --- /dev/null +++ b/Chapter-14/grid_table.py @@ -0,0 +1,65 @@ +import wx +import wx.grid + +class TestTable(wx.grid.PyGridTableBase): + def __init__(self): + wx.grid.PyGridTableBase.__init__(self) + self.data = { (1,1) : "Here", + (2,2) : "is", + (3,3) : "some", + (4,4) : "data", + } + + self.odd=wx.grid.GridCellAttr() + self.odd.SetBackgroundColour("sky blue") + self.odd.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + + self.even=wx.grid.GridCellAttr() + self.even.SetBackgroundColour("sea green") + self.even.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + + + # these five are the required methods + def GetNumberRows(self): + return 50 + + def GetNumberCols(self): + return 50 + + def IsEmptyCell(self, row, col): + return self.data.get((row, col)) is not None + + def GetValue(self, row, col): + value = self.data.get((row, col)) + if value is not None: + return value + else: + return '' + + def SetValue(self, row, col, value): + self.data[(row,col)] = value + + + # the table can also provide the attribute for each cell + def GetAttr(self, row, col, kind): + attr = [self.even, self.odd][row % 2] + attr.IncRef() + return attr + + + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Table", + size=(640,480)) + + grid = wx.grid.Grid(self) + + table = TestTable() + grid.SetTable(table, True) + + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_table_basic.py b/Chapter-14/grid_table_basic.py new file mode 100644 index 0000000..60f0179 --- /dev/null +++ b/Chapter-14/grid_table_basic.py @@ -0,0 +1,42 @@ +import wx +import wx.grid + +class TestTable(wx.grid.PyGridTableBase): + def __init__(self): + wx.grid.PyGridTableBase.__init__(self) + self.rowLabels = ["uno", "dos", "tres", "quatro", "cinco"] + self.colLabels = ["homer", "marge", "bart", "lisa", "maggie"] + + def GetNumberRows(self): + return 5 + + def GetNumberCols(self): + return 5 + + def IsEmptyCell(self, row, col): + return False + + def GetValue(self, row, col): + return "(%s,%s)" % (self.rowLabels[row], self.colLabels[col]) + + def SetValue(self, row, col, value): + pass + + def GetColLabelValue(self, col): + return self.colLabels[col] + + def GetRowLabelValue(self, row): + return self.rowLabels[row] + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Table", + size=(500,200)) + grid = wx.grid.Grid(self) + table = TestTable() + grid.SetTable(table, True) + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-14/grid_table_header.py b/Chapter-14/grid_table_header.py new file mode 100644 index 0000000..8b2e295 --- /dev/null +++ b/Chapter-14/grid_table_header.py @@ -0,0 +1,64 @@ +import wx +import wx.grid + +class TestTable(wx.grid.PyGridTableBase): + def __init__(self): + wx.grid.PyGridTableBase.__init__(self) + self.data = { (1,1) : "Here", + (2,2) : "is", + (3,3) : "some", + (4,4) : "data", + } + + self.odd=wx.grid.GridCellAttr() + self.odd.SetBackgroundColour("sky blue") + self.odd.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + + self.even=wx.grid.GridCellAttr() + self.even.SetBackgroundColour("sea green") + self.even.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) + + + # these five are the required methods + def GetNumberRows(self): + return 50 + + def GetNumberCols(self): + return 50 + + def IsEmptyCell(self, row, col): + return self.data.get((row, col)) is not None + + def GetValue(self, row, col): + value = self.data.get((row, col)) + if value is not None: + return value + else: + return '' + + def SetValue(self, row, col, value): + self.data[(row,col)] = value + + + # the table can also provide the attribute for each cell + def GetAttr(self, row, col, kind): + attr = [self.even, self.odd][row % 2] + attr.IncRef() + return attr + + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Grid Table", + size=(640,480)) + + grid = wx.grid.Grid(self) + + table = TestTable() + grid.SetTable(table, True) + + +app = wx.PySimpleApp() +frame = TestFrame() +frame.Show() +app.MainLoop() diff --git a/Chapter-15/data.py b/Chapter-15/data.py new file mode 100644 index 0000000..ac5c0ca --- /dev/null +++ b/Chapter-15/data.py @@ -0,0 +1,403 @@ + +# Some sample data for the treectrl samples + +tree = [ + "wx.AcceleratorTable", + "wx.BrushList", + "wx.BusyInfo", + "wx.Clipboard", + "wx.Colour", + "wx.ColourData", + "wx.ColourDatabase", + "wx.ContextHelp", + ["wx.DC", [ + "wx.ClientDC", + ["wx.MemoryDC", [ + "wx.lib.colourchooser.canvas.BitmapBuffer", + ["wx.BufferedDC", [ + "wx.BufferedPaintDC", ]]]], + + "wx.MetaFileDC", + "wx.MirrorDC", + "wx.PaintDC", + "wx.PostScriptDC", + "wx.PrinterDC", + "wx.ScreenDC", + "wx.WindowDC",]], + "wx.DragImage", + "wx.Effects", + "wx.EncodingConverter", + ["wx.Event", [ + "wx.ActivateEvent", + "wx.CalculateLayoutEvent", + "wx.CloseEvent", + ["wx.CommandEvent", [ + "wx.calendar.CalendarEvent", + "wx.ChildFocusEvent", + "wx.ContextMenuEvent", + "wx.gizmos.DynamicSashSplitEvent", + "wx.gizmos.DynamicSashUnifyEvent", + "wx.FindDialogEvent", + "wx.grid.GridEditorCreatedEvent", + "wx.HelpEvent", + ["wx.NotifyEvent",[ + ["wx.BookCtrlEvent", [ + "wx.ListbookEvent", + "wx.NotebookEvent ",]], + "wx.grid.GridEvent", + "wx.grid.GridRangeSelectEvent", + "wx.grid.GridSizeEvent", + "wx.ListEvent", + "wx.SpinEvent", + "wx.SplitterEvent", + "wx.TreeEvent", + "wx.wizard.WizardEvent ",]], + ["wx.PyCommandEvent", [ + "wx.lib.colourselect.ColourSelectEvent", + "wx.lib.buttons.GenButtonEvent", + "wx.lib.gridmovers.GridColMoveEvent", + "wx.lib.gridmovers.GridRowMoveEvent", + "wx.lib.intctrl.IntUpdatedEvent", + "wx.lib.masked.combobox.MaskedComboBoxSelectEvent", + "wx.lib.masked.numctrl.NumberUpdatedEvent", + "wx.lib.masked.timectrl.TimeUpdatedEvent ",]], + "wx.SashEvent", + "wx.ScrollEvent", + "wx.stc.StyledTextEvent", + "wx.TextUrlEvent", + "wx.UpdateUIEvent", + "wx.WindowCreateEvent", + "wx.WindowDestroyEvent ",]], + "wx.DisplayChangedEvent", + "wx.DropFilesEvent", + "wx.EraseEvent", + "wx.FocusEvent", + "wx.IconizeEvent", + "wx.IdleEvent", + "wx.InitDialogEvent", + "wx.JoystickEvent", + "wx.KeyEvent", + "wx.MaximizeEvent", + "wx.MenuEvent", + "wx.MouseCaptureChangedEvent", + "wx.MouseEvent", + "wx.MoveEvent", + "wx.NavigationKeyEvent", + "wx.NcPaintEvent", + "wx.PaintEvent", + "wx.PaletteChangedEvent", + "wx.ProcessEvent", + ["wx.PyEvent", [ + "wx.lib.throbber.UpdateThrobberEvent ",]], + "wx.QueryLayoutInfoEvent", + "wx.QueryNewPaletteEvent", + "wx.ScrollWinEvent", + "wx.SetCursorEvent", + "wx.ShowEvent", + "wx.SizeEvent", + "wx.SysColourChangedEvent", + "wx.TaskBarIconEvent", + "wx.TimerEvent ",]], + ["wx.EvtHandler", [ + "wx.lib.gridmovers.GridColMover", + "wx.lib.gridmovers.GridRowMover", + "wx.html.HtmlHelpController", + "wx.Menu", + "wx.Process", + ["wx.PyApp", [ + ["wx.App", [ + "wx.py.PyAlaCarte.App", + "wx.py.PyAlaMode.App", + "wx.py.PyAlaModeTest.App", + "wx.py.PyCrust.App", + "wx.py.PyShell.App", + ["wx.py.filling.App", [ + "wx.py.PyFilling.App ",]], + ["wx.PySimpleApp", [ + "wx.lib.masked.maskededit.test",]], + "wx.PyWidgetTester ",]]]], + + "wx.TaskBarIcon", + ["wx.Timer", [ + "wx.PyTimer ",]], + ["wx.Validator", [ + ["wx.PyValidator",[ + "wx.lib.intctrl.IntValidator",]]]], + ["wx.Window", [ + ["wx.lib.colourchooser.canvas.Canvas", [ + "wx.lib.colourchooser.pycolourslider.PyColourSlider", + "wx.lib.colourchooser.pypalette.PyPalette",]], + "wx.lib.gridmovers.ColDragWindow", + ["wx.Control",[ + ["wx.BookCtrl", [ + "wx.Listbook", + ["wx.Notebook",[ + "wx.py.editor.EditorNotebook", + "wx.py.editor.EditorShellNotebook",]] ]], + ["wx.Button", [ + ["wx.BitmapButton",[ + "wx.lib.colourselect.ColourSelect", + "wx.ContextHelpButton", + "wx.lib.foldmenu.FoldOutMenu ",]] ]], + "wx.calendar.CalendarCtrl", + "wx.CheckBox", + ["wx.ComboBox",[ + ["wx.lib.masked.combobox.BaseMaskedComboBox", [ + "wx.lib.masked.combobox.ComboBox", + "wx.lib.masked.combobox.PreMaskedComboBox",]] ]], + ["wx.ControlWithItems", [ + ["wx.Choice",[ + "wx.DirFilterListCtrl ",]], + "wx.ListBox", + "wx.CheckListBox ",]], + "wx.Gauge", + "wx.GenericDirCtrl", + "wx.gizmos.LEDNumberCtrl", + ["wx.ListCtrl",[ + "wx.ListView ",]], + ["wx.PyControl",[ + "wx.lib.calendar.Calendar", + ["wx.lib.buttons.GenButton",[ + ["wx.lib.buttons.GenBitmapButton",[ + ["wx.lib.buttons.GenBitmapTextButton",[ + "wx.lib.buttons.GenBitmapTextToggleButton ",]], + "wx.lib.buttons.GenBitmapToggleButton ",]], + "wx.lib.buttons.GenToggleButton ",]], + "wx.lib.statbmp.GenStaticBitmap", + "wx.lib.stattext.GenStaticText", + "wx.lib.popupctl.PopButton", + "wx.lib.popupctl.PopupControl", + "wx.lib.ticker.Ticker ",]], + "wx.RadioBox", + "wx.RadioButton", + "wx.ScrollBar", + "wx.Slider", + "wx.SpinButton", + "wx.SpinCtrl", + ["wx.StaticBitmap",[ + "wx.lib.fancytext.StaticFancyText ",]], + "wx.StaticBox", + "wx.StaticLine", + "wx.StaticText", + ["wx.stc.StyledTextCtrl",[ + ["wx.py.editwindow.EditWindow",[ + "wx.py.crust.Display", + "wx.py.editor.EditWindow", + "wx.py.filling.FillingText", + "wx.py.shell.Shell",]], + "wx.lib.pyshell.PyShellWindow ",]], + ["wx.TextCtrl", [ + ["wx.lib.masked.textctrl.BaseMaskedTextCtrl",[ + "wx.lib.masked.ipaddrctrl.IpAddrCtrl", + "wx.lib.masked.numctrl.NumCtrl", + "wx.lib.masked.textctrl.PreMaskedTextCtrl", + "wx.lib.masked.textctrl.TextCtrl", + "wx.lib.masked.timectrl.TimeCtrl ",]], + "wx.py.crust.Calltip", + "wx.lib.sheet.CTextCellEditor", + "wx.py.crust.DispatcherListing", + "wx.lib.intctrl.IntCtrl", + "wx.lib.rightalign.RightTextCtrl", + "wx.py.crust.SessionListing",]], + "wx.ToggleButton", + "wx.ToolBar", + ["wx.TreeCtrl",[ + "wx.py.filling.FillingTree", + "wx.gizmos.RemotelyScrolledTreeCtrl ",]], + "wx.gizmos.TreeListCtrl ",]], + "wx.gizmos.DynamicSashWindow", + "wx.lib.multisash.EmptyChild", + "wx.glcanvas.GLCanvas", + "wx.lib.imagebrowser.ImageView", + "wx.MDIClientWindow", + "wx.MenuBar", + "wx.lib.multisash.MultiClient", + "wx.lib.multisash.MultiCloser", + "wx.lib.multisash.MultiCreator", + "wx.lib.multisash.MultiSash", + "wx.lib.multisash.MultiSizer", + "wx.lib.multisash.MultiSplit", + "wx.lib.multisash.MultiViewLeaf", + ["wx.Panel",[ + "wx.gizmos.EditableListBox", + ["wx.lib.filebrowsebutton.FileBrowseButton",[ + "wx.lib.filebrowsebutton.DirBrowseButton", + "wx.lib.filebrowsebutton.FileBrowseButtonWithHistory",]], + "wx.lib.floatcanvas.FloatCanvas.FloatCanvas", + "wx.lib.floatcanvas.NavCanvas.NavCanvas", + "wx.NotebookPage", + ["wx.PreviewControlBar",[ + "wx.PyPreviewControlBar ",]], + "wx.lib.colourchooser.pycolourbox.PyColourBox", + "wx.lib.colourchooser.pycolourchooser.PyColourChooser", + ["wx.PyPanel",[ + "wx.lib.throbber.Throbber",]], + "wx.lib.shell.PyShell", + "wx.lib.shell.PyShellInput", + "wx.lib.shell.PyShellOutput", + ["wx.ScrolledWindow",[ + "wx.lib.editor.editor.Editor", + ["wx.grid.Grid",[ + "wx.lib.sheet.CSheet ",]], + ["wx.html.HtmlWindow",[ + "wx.lib.ClickableHtmlWindow.PyClickableHtmlWindow",]], + "wx.PreviewCanvas", + "wx.lib.printout.PrintTableDraw", + ["wx.PyScrolledWindow",[ + "wx.lib.scrolledpanel.ScrolledPanel",]], + "wx.lib.ogl.ShapeCanvas", + "wx.gizmos.SplitterScrolledWindow ",]], + ["wx.VScrolledWindow",[ + ["wx.VListBox", [ + "wx.HtmlListBox ",]] ]], + ["wx.wizard.WizardPage", [ + "wx.wizard.PyWizardPage", + "wx.wizard.WizardPageSimple ",]], + "wx.lib.plot.PlotCanvas", + "wx.lib.wxPlotCanvas.PlotCanvas", + ["wx.PopupWindow",[ + "wx.lib.foldmenu.FoldOutWindow", + ["wx.PopupTransientWindow",[ + "wx.TipWindow ",]] ]], + ["wx.PyWindow", [ + "wx.lib.analogclock.AnalogClockWindow",]], + "wx.lib.gridmovers.RowDragWindow", + ["wx.SashWindow",[ + "wx.SashLayoutWindow ",]], + "wx.SplashScreenWindow", + ["wx.SplitterWindow",[ + "wx.py.crust.Crust", + "wx.py.filling.Filling", + "wx.gizmos.ThinSplitterWindow ",]], + "wx.StatusBar", + ["wx.TopLevelWindow",[ + ["wx.Dialog",[ + "wx.lib.calendar.CalenDlg", + "wx.ColourDialog", + "wx.DirDialog", + "wx.FileDialog", + "wx.FindReplaceDialog", + "wx.FontDialog", + "wx.lib.imagebrowser.ImageDialog", + "wx.MessageDialog", + "wx.MultiChoiceDialog", + "wx.lib.dialogs.MultipleChoiceDialog", + "wx.PageSetupDialog", + "wx.lib.popupctl.PopupDialog", + "wx.PrintDialog", + "wx.lib.dialogs.ScrolledMessageDialog", + "wx.SingleChoiceDialog", + "wx.TextEntryDialog", + "wx.wizard.Wizard ",]], + ["wx.Frame", [ + "wx.lib.analogclockopts.ACCustomizationFrame", + "wx.py.filling.FillingFrame", + ["wx.py.frame.Frame",[ + "wx.py.crust.CrustFrame", + ["wx.py.editor.EditorFrame",[ + "wx.py.editor.EditorNotebookFrame",]], + "wx.py.shell.ShellFrame",]], + "wx.html.HtmlHelpFrame", + "wx.MDIChildFrame", + "wx.MDIParentFrame", + "wx.MiniFrame", + ["wx.PreviewFrame",[ + "wx.PyPreviewFrame ",]], + "wx.ProgressDialog", + "wx.SplashScreen", + "wx.lib.splashscreen.SplashScreen", + "wx.lib.masked.maskededit.test2", + "wx.lib.plot.TestFrame ",]] ]], + "wx.gizmos.TreeCompanionWindow ",]] ]] ]], + "wx.FileHistory", + "wx.FileSystem", + "wx.FindReplaceData", + "wx.FontData", + "wx.FontList", + "wx.FSFile", + ["wx.GDIObject",[ + "wx.Bitmap", + "wx.Brush", + "wx.Cursor", + "wx.Font", + "wx.Icon", + "wx.Palette", + "wx.Pen", + "wx.Region ",]], + "wx.glcanvas.GLContext", + ["wx.grid.GridTableBase", [ + "wx.grid.GridStringTable", + "wx.grid.PyGridTableBase ",]], + ["wx.html.HtmlCell", [ + "wx.html.HtmlColourCell", + "wx.html.HtmlContainerCell", + "wx.html.HtmlFontCell", + "wx.html.HtmlWidgetCell", + "wx.html.HtmlWordCell ",]], + "wx.html.HtmlDCRenderer", + "wx.html.HtmlEasyPrinting", + "wx.html.HtmlFilter", + "wx.html.HtmlLinkInfo", + ["wx.html.HtmlParser", [ + "wx.html.HtmlWinParser ",]], + "wx.html.HtmlTag", + ["wx.html.HtmlTagHandler", [ + ["wx.html.HtmlWinTagHandler", [ + "wx.lib.wxpTag.wxpTagHandler ",]] ]], + "wx.Image", + ["wx.ImageHandler", [ + ["wx.BMPHandler", [ + ["wx.ICOHandler", [ + ["wx.CURHandler", [ + "wx.ANIHandler ",]] ]] ]], + "wx.GIFHandler", + "wx.JPEGHandler", + "wx.PCXHandler", + "wx.PNGHandler", + "wx.PNMHandler", + "wx.TIFFHandler", + "wx.XPMHandler ",]], + "wx.ImageList", + "wx.IndividualLayoutConstraint", + "wx.LayoutAlgorithm", + ["wx.LayoutConstraints", [ + "wx.lib.anchors.LayoutAnchors", + "wx.lib.layoutf.Layoutf",]], + "wx.ListItem", + "wx.Mask", + "wx.MenuItem", + "wx.MetaFile", + "wx.PageSetupDialogData", + "wx.PenList", + "wx.PrintData", + "wx.PrintDialogData", + "wx.Printer", + ["wx.Printout", [ + "wx.html.HtmlPrintout", + "wx.lib.plot.PlotPrintout", + "wx.lib.printout.SetPrintout ",]], + ["wx.PrintPreview", [ + "wx.PyPrintPreview ",]], + "wx.RegionIterator", + ["wx.Sizer", [ + "wx.BookCtrlSizer", + ["wx.BoxSizer", [ + "wx.StaticBoxSizer", ]], + ["wx.GridSizer", [ + ["wx.FlexGridSizer", [ + "wx.GridBagSizer",]] ]], + "wx.NotebookSizer", + "wx.PySizer",]], + ["wx.SizerItem", [ + "wx.GBSizerItem",]], + "wx.SystemOptions", + "wx.ToolBarToolBase", + "wx.ToolTip", + "wx.gizmos.TreeListColumnInfo", + "wx.xrc.XmlDocument", + "wx.xrc.XmlResource", + "wx.xrc.XmlResourceHandler ", +] + + + diff --git a/Chapter-15/tree_icons.py b/Chapter-15/tree_icons.py new file mode 100644 index 0000000..c7730b8 --- /dev/null +++ b/Chapter-15/tree_icons.py @@ -0,0 +1,65 @@ +import wx +import data + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, + title="simple tree with icons", size=(400,500)) + + # Create an image list + il = wx.ImageList(16,16) + + # Get some standard images from the art provider and add them + # to the image list + self.fldridx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FOLDER, + wx.ART_OTHER, (16,16))) + self.fldropenidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, + wx.ART_OTHER, (16,16))) + self.fileidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, + wx.ART_OTHER, (16,16))) + + + # Create the tree + self.tree = wx.TreeCtrl(self) + # Give it the image list + self.tree.AssignImageList(il) + root = self.tree.AddRoot("wx.Object") + self.tree.SetItemImage(root, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(root, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + self.AddTreeNodes(root, data.tree) + self.tree.Expand(root) + + + def AddTreeNodes(self, parentItem, items): + for item in items: + if type(item) == str: + newItem = self.tree.AppendItem(parentItem, item) + self.tree.SetItemImage(newItem, self.fileidx, + wx.TreeItemIcon_Normal) + else: + newItem = self.tree.AppendItem(parentItem, item[0]) + self.tree.SetItemImage(newItem, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(newItem, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + self.AddTreeNodes(newItem, item[1]) + + + def GetItemText(self, item): + if item: + return self.tree.GetItemText(item) + else: + return "" + +app = wx.PySimpleApp(redirect=True) +frame = TestFrame() +frame.Show() +app.MainLoop() + diff --git a/Chapter-15/tree_misc.py b/Chapter-15/tree_misc.py new file mode 100644 index 0000000..2df8e00 --- /dev/null +++ b/Chapter-15/tree_misc.py @@ -0,0 +1,155 @@ +import wx +import data + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="tree: misc tests", size=(400,500)) + + # Create an image list + il = wx.ImageList(16,16) + + # Get some standard images from the art provider and add them + # to the image list + self.fldridx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) + self.fldropenidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) + self.fileidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) + + + # Create the tree + self.tree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_EDIT_LABELS) + + # Give it the image list + self.tree.AssignImageList(il) + + + # Add a root node and assign it some images + root = self.tree.AddRoot("wx.Object") + self.tree.SetItemPyData(root, None) + self.tree.SetItemImage(root, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(root, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + # Add nodes from our data set + self.AddTreeNodes(root, data.tree) + + # Bind some interesting events + self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) + self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) + self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) + + self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnBeginEdit, self.tree) + + + # Expand the first level + self.tree.Expand(root) + + + menu = wx.Menu() + mi = menu.Append(-1, "Edit Item") + self.Bind(wx.EVT_MENU, self.OnEditItem, mi) + mi = menu.Append(-1, "Sort Children") + self.Bind(wx.EVT_MENU, self.OnSortChildren, mi) + mi = menu.Append(-1, "Delete Children") + self.Bind(wx.EVT_MENU, self.OnDeleteChildren, mi) + mi = menu.Append(-1, "Find all Children") + self.Bind(wx.EVT_MENU, self.OnFindChildren, mi) + mb = wx.MenuBar() + mb.Append(menu, "Tests") + self.SetMenuBar(mb) + + + def AddTreeNodes(self, parentItem, items): + """ + Recursively traverses the data structure, adding tree nodes to + match it. + """ + for item in items: + if type(item) == str: + newItem = self.tree.AppendItem(parentItem, item) + self.tree.SetItemPyData(newItem, None) + self.tree.SetItemImage(newItem, self.fileidx, + wx.TreeItemIcon_Normal) + else: + newItem = self.tree.AppendItem(parentItem, item[0]) + self.tree.SetItemPyData(newItem, None) + self.tree.SetItemImage(newItem, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(newItem, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + self.AddTreeNodes(newItem, item[1]) + + + def GetItemText(self, item): + if item: + return self.tree.GetItemText(item) + else: + return "" + + def OnItemExpanded(self, evt): + print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) + + def OnItemCollapsed(self, evt): + print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) + + def OnSelChanged(self, evt): + print "OnSelChanged: ", self.GetItemText(evt.GetItem()) + + def OnActivated(self, evt): + print "OnActivated: ", self.GetItemText(evt.GetItem()) + + + def OnBeginEdit(self, evt): + print "OnBeginEdit: ", self.GetItemText(evt.GetItem()) + # we can prevent nodes from being edited, for example let's + # not let the root node be edited... + item = evt.GetItem() + if item == self.tree.GetRootItem(): + evt.Veto() + print "*** Edit was vetoed!" + + + def OnEditItem(self, evt): + item = self.tree.GetSelection() + if item: + self.tree.EditLabel(item) + + def OnSortChildren(self, evt): + item = self.tree.GetSelection() + if item: + self.tree.SortChildren(item) + + def OnDeleteChildren(self, evt): + item = self.tree.GetSelection() + if item: + self.tree.DeleteChildren(item) + + def OnFindChildren(self, evt): + item = self.tree.GetSelection() + if item: + self.PrintAllItems(item) + + def PrintAllItems(self, parent, indent=0): + text = self.tree.GetItemText(parent) + print ' ' * indent, text + indent += 4 + item, cookie = self.tree.GetFirstChild(parent) + while item: + if self.tree.ItemHasChildren(item): + self.PrintAllItems(item, indent) + else: + text = self.tree.GetItemText(item) + print ' ' * indent, text + item, cookie = self.tree.GetNextChild(parent, cookie) + + +app = wx.PySimpleApp(redirect=True) +frame = TestFrame() +frame.Show() +app.MainLoop() + diff --git a/Chapter-15/tree_simple.py b/Chapter-15/tree_simple.py new file mode 100644 index 0000000..b04beab --- /dev/null +++ b/Chapter-15/tree_simple.py @@ -0,0 +1,62 @@ +import wx +import data + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="simple tree", size=(400,500)) + + # Create the tree + self.tree = wx.TreeCtrl(self) + + # Add a root node + root = self.tree.AddRoot("wx.Object") + + # Add nodes from our data set + self.AddTreeNodes(root, data.tree) + + # Bind some interesting events + self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) + self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) + self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) + + # Expand the first level + self.tree.Expand(root) + + + def AddTreeNodes(self, parentItem, items): + """ + Recursively traverses the data structure, adding tree nodes to + match it. + """ + for item in items: + if type(item) == str: + self.tree.AppendItem(parentItem, item) + else: + newItem = self.tree.AppendItem(parentItem, item[0]) + self.AddTreeNodes(newItem, item[1]) + + def GetItemText(self, item): + if item: + return self.tree.GetItemText(item) + else: + return "" + + def OnItemExpanded(self, evt): + print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) + + def OnItemCollapsed(self, evt): + print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) + + def OnSelChanged(self, evt): + print "OnSelChanged: ", self.GetItemText(evt.GetItem()) + + def OnActivated(self, evt): + print "OnActivated: ", self.GetItemText(evt.GetItem()) + + +app = wx.PySimpleApp(redirect=True) +frame = TestFrame() +frame.Show() +app.MainLoop() + diff --git a/Chapter-15/tree_treelist.py b/Chapter-15/tree_treelist.py new file mode 100644 index 0000000..e35bfc1 --- /dev/null +++ b/Chapter-15/tree_treelist.py @@ -0,0 +1,104 @@ +import wx +import wx.gizmos +import data + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="TreeListCtrl", size=(400,500)) + + # Create an image list + il = wx.ImageList(16,16) + + # Get some standard images from the art provider and add them + # to the image list + self.fldridx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) + self.fldropenidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) + self.fileidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) + + + # Create the tree + self.tree = wx.gizmos.TreeListCtrl(self, style = + wx.TR_DEFAULT_STYLE + | wx.TR_FULL_ROW_HIGHLIGHT) + + # Give it the image list + self.tree.AssignImageList(il) + + + # create some columns + self.tree.AddColumn("Class Name") + self.tree.AddColumn("Description") + self.tree.SetMainColumn(0) # the one with the tree in it... + self.tree.SetColumnWidth(0, 200) + self.tree.SetColumnWidth(1, 200) + + # Add a root node and assign it some images + root = self.tree.AddRoot("wx.Object") + self.tree.SetItemText(root, "A description of wx.Object", 1) + self.tree.SetItemImage(root, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(root, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + # Add nodes from our data set + self.AddTreeNodes(root, data.tree) + + # Bind some interesting events + self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) + self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) + self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) + + # Expand the first level + self.tree.Expand(root) + + + def AddTreeNodes(self, parentItem, items): + """ + Recursively traverses the data structure, adding tree nodes to + match it. + """ + for item in items: + if type(item) == str: + newItem = self.tree.AppendItem(parentItem, item) + self.tree.SetItemText(newItem, "A description of %s" % item, 1) + self.tree.SetItemImage(newItem, self.fileidx, + wx.TreeItemIcon_Normal) + else: + newItem = self.tree.AppendItem(parentItem, item[0]) + self.tree.SetItemText(newItem, "A description of %s" % item[0], 1) + self.tree.SetItemImage(newItem, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(newItem, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + self.AddTreeNodes(newItem, item[1]) + + + def GetItemText(self, item): + if item: + return self.tree.GetItemText(item) + else: + return "" + + def OnItemExpanded(self, evt): + print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) + + def OnItemCollapsed(self, evt): + print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) + + def OnSelChanged(self, evt): + print "OnSelChanged: ", self.GetItemText(evt.GetItem()) + + def OnActivated(self, evt): + print "OnActivated: ", self.GetItemText(evt.GetItem()) + + +app = wx.PySimpleApp(redirect=True) +frame = TestFrame() +frame.Show() +app.MainLoop() + diff --git a/Chapter-15/tree_virtual.py b/Chapter-15/tree_virtual.py new file mode 100644 index 0000000..795596b --- /dev/null +++ b/Chapter-15/tree_virtual.py @@ -0,0 +1,92 @@ +import wx +import data + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="virtual tree with icons", size=(400,500)) + il = wx.ImageList(16,16) + self.fldridx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) + self.fldropenidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) + self.fileidx = il.Add( + wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) + self.tree = wx.TreeCtrl(self) + self.tree.AssignImageList(il) + root = self.tree.AddRoot("wx.Object") + self.tree.SetItemImage(root, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(root, self.fldropenidx, + wx.TreeItemIcon_Expanded) + + # Instead of adding nodes for the whole tree, just attach some + # data to the root node so that it can find and add its child + # nodes when it is expanded, and mark it as having children so + # it will be expandable. + self.tree.SetItemPyData(root, data.tree) + self.tree.SetItemHasChildren(root, True) + + # Bind some interesting events + self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) + self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) + self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) + + self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding, self.tree) + self.tree.Expand(root) + + + def AddTreeNodes(self, parentItem): + """ + Add nodes for just the children of the parentItem + """ + items = self.tree.GetItemPyData(parentItem) + for item in items: + if type(item) == str: + # a leaf node + newItem = self.tree.AppendItem(parentItem, item) + self.tree.SetItemImage(newItem, self.fileidx, + wx.TreeItemIcon_Normal) + else: + # this item has children + newItem = self.tree.AppendItem(parentItem, item[0]) + self.tree.SetItemImage(newItem, self.fldridx, + wx.TreeItemIcon_Normal) + self.tree.SetItemImage(newItem, self.fldropenidx, + wx.TreeItemIcon_Expanded) + self.tree.SetItemPyData(newItem, item[1]) + self.tree.SetItemHasChildren(newItem, True) + + + + def GetItemText(self, item): + if item: + return self.tree.GetItemText(item) + else: + return "" + + def OnItemExpanded(self, evt): + print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) + + def OnItemExpanding(self, evt): + # When the item is about to be expanded add the first level of child nodes + print "OnItemExpanding:", self.GetItemText(evt.GetItem()) + self.AddTreeNodes(evt.GetItem()) + + def OnItemCollapsed(self, evt): + print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) + # And remove them when collapsed as we don't need them any longer + self.tree.DeleteChildren(evt.GetItem()) + + def OnSelChanged(self, evt): + print "OnSelChanged: ", self.GetItemText(evt.GetItem()) + + def OnActivated(self, evt): + print "OnActivated: ", self.GetItemText(evt.GetItem()) + + +app = wx.PySimpleApp(redirect=True) +frame = TestFrame() +frame.Show() +app.MainLoop() + diff --git a/Chapter-16/helpfiles/Index.hhk b/Chapter-16/helpfiles/Index.hhk new file mode 100644 index 0000000..1835a2d --- /dev/null +++ b/Chapter-16/helpfiles/Index.hhk @@ -0,0 +1,71 @@ + + + + + + +

    +
  • + + + +
  • + + + +
      +
    • + + + +
    • + + + +
        +
      • + + + +
      • + + + +
      • + + + +
      +
    • + + + +
    +
  • + + + +
  • + + + +
  • + + + +
      +
    • + + + +
    • + + + +
    +
  • + + + +
+ diff --git a/Chapter-16/helpfiles/another.hhc b/Chapter-16/helpfiles/another.hhc new file mode 100644 index 0000000..f9dd604 --- /dev/null +++ b/Chapter-16/helpfiles/another.hhc @@ -0,0 +1,18 @@ + + + + + + + +
    +
  • + + + +
  • + + + +
+ diff --git a/Chapter-16/helpfiles/another.hhp b/Chapter-16/helpfiles/another.hhp new file mode 100644 index 0000000..017ed24 --- /dev/null +++ b/Chapter-16/helpfiles/another.hhp @@ -0,0 +1,6 @@ +[OPTIONS] +Compatibility=1.1 +Contents file=another.hhc +Display compile progress=No +Title=Another book +Default topic=another.htm diff --git a/Chapter-16/helpfiles/another.htm b/Chapter-16/helpfiles/another.htm new file mode 100644 index 0000000..4532dc8 --- /dev/null +++ b/Chapter-16/helpfiles/another.htm @@ -0,0 +1,24 @@ + + + + Another HTML Help book + + + +

Another book...

+ Here's another book to demonstrate that +
    +
  • You can display multiple books in a help controller +
  • You can selectively search books. Try it! +
  • Index files are optional. This book doesn't supply an index, but it does supply + a contents (.hhc) file. You must always supply a contents file because +
      +
    • Contents trees rule :-) +
    • The search algorithm uses the contents tree to find out which pages are + available. +
    + Wanna know what a contents file looks like? Click here +
+ You can also view the project file for this book. + + diff --git a/Chapter-16/helpfiles/book1.htm b/Chapter-16/helpfiles/book1.htm new file mode 100644 index 0000000..fa470e3 --- /dev/null +++ b/Chapter-16/helpfiles/book1.htm @@ -0,0 +1,4 @@ +Book 1 +

Book 1.

+How do you enjoy book one?? + diff --git a/Chapter-16/helpfiles/book2.htm b/Chapter-16/helpfiles/book2.htm new file mode 100644 index 0000000..828723f --- /dev/null +++ b/Chapter-16/helpfiles/book2.htm @@ -0,0 +1,5 @@ +Book 1 +

Book 2.

+How do you enjoy book two?? +

Please click HERE + diff --git a/Chapter-16/helpfiles/contents.hhc b/Chapter-16/helpfiles/contents.hhc new file mode 100644 index 0000000..9ecf353 --- /dev/null +++ b/Chapter-16/helpfiles/contents.hhc @@ -0,0 +1,28 @@ + + + + + + + + + +

    +
  • + + + +
  • + + + + +
      +
    • + + + +
    +
+ + diff --git a/Chapter-16/helpfiles/main.htm b/Chapter-16/helpfiles/main.htm new file mode 100644 index 0000000..ca5275b --- /dev/null +++ b/Chapter-16/helpfiles/main.htm @@ -0,0 +1,5 @@ + +

This is main page.

+Book 1
+Book 2
+ diff --git a/Chapter-16/helpfiles/page2-b.htm b/Chapter-16/helpfiles/page2-b.htm new file mode 100644 index 0000000..f51dd6d --- /dev/null +++ b/Chapter-16/helpfiles/page2-b.htm @@ -0,0 +1,5 @@ + + +Hello, you're on sub page of page 2 !!! + + diff --git a/Chapter-16/helpfiles/testing.hhp b/Chapter-16/helpfiles/testing.hhp new file mode 100644 index 0000000..9d9d7fd --- /dev/null +++ b/Chapter-16/helpfiles/testing.hhp @@ -0,0 +1,16 @@ +[OPTIONS] +Compatibility=1.1 +Compiled file=testing.chm +Contents file=contents.hhc +Display compile progress=No +Index file=Index.hhk +Language=0x405 Česky +Title=Test HELPFILE +Default topic=main.htm + +[FILES] +main.htm +book1.htm +book2.htm +page2-b.htm + diff --git a/Chapter-16/html_help.py b/Chapter-16/html_help.py new file mode 100644 index 0000000..ae212aa --- /dev/null +++ b/Chapter-16/html_help.py @@ -0,0 +1,59 @@ +""" +wxPython can use html files for online help or other forms of +documentation for your application. The help can be organized as a +collection of books, and there is a help viewer available that enables +the user to browse by book, via an index or full text search. The +format of the contents and index files are similar to Microsoft +HtmlHelp. +""" + +import wx +import wx.html + +class MyHtmlFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title) + p = wx.Panel(self) + b1 = wx.Button(p, -1, "Show Help Contents") + b2 = wx.Button(p, -1, "Show Help Index") + b3 = wx.Button(p, -1, "Show Specific Help") + self.Bind(wx.EVT_BUTTON, self.OnShowHelpContents, b1) + self.Bind(wx.EVT_BUTTON, self.OnShowHelpIndex, b2) + self.Bind(wx.EVT_BUTTON, self.OnShowSpecificHelp, b3) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add((10,10)) + sizer.Add(b1, 0, wx.ALL, 10) + sizer.Add(b2, 0, wx.ALL, 10) + sizer.Add(b3, 0, wx.ALL, 10) + p.SetSizer(sizer) + + self.InitHelp() + + + def InitHelp(self): + def _addBook(filename): + if not self.help.AddBook(filename): + wx.MessageBox("Unable to open: " + filename, + "Error", wx.OK|wx.ICON_EXCLAMATION) + + self.help = wx.html.HtmlHelpController() + + _addBook("helpfiles/testing.hhp") + _addBook("helpfiles/another.hhp") + + + def OnShowHelpContents(self, evt): + self.help.DisplayContents() + + def OnShowHelpIndex(self, evt): + self.help.DisplayIndex() + + def OnShowSpecificHelp(self, evt): + self.help.Display("sub book") + + +app = wx.PySimpleApp() +frm = MyHtmlFrame(None, "HTML Help") +frm.Show() +app.MainLoop() diff --git a/Chapter-16/html_tag.py b/Chapter-16/html_tag.py new file mode 100644 index 0000000..771bb2a --- /dev/null +++ b/Chapter-16/html_tag.py @@ -0,0 +1,71 @@ +import wx +import wx.html + +page = """ + +This silly example shows how custom tags can be defined and used in a +wx.HtmlWindow. We've defined a new tag, <blue> that will change +the foreground color of the portions of the document that +it encloses to some shade of blue. The tag handler can also use +parameters specifed in the tag, for example: + +
    +
  • Sky Blue +
  • Midnight Blue +
  • Dark Blue +
  • Navy Blue +
+ + +""" + + +class BlueTagHandler(wx.html.HtmlWinTagHandler): + def __init__(self): + wx.html.HtmlWinTagHandler.__init__(self) + + def GetSupportedTags(self): + return "BLUE" + + def HandleTag(self, tag): + old = self.GetParser().GetActualColor() + clr = "#0000FF" + if tag.HasParam("SHADE"): + shade = tag.GetParam("SHADE") + if shade.upper() == "SKY": + clr = "#3299CC" + if shade.upper() == "MIDNIGHT": + clr = "#2F2F4F" + elif shade.upper() == "DARK": + clr = "#00008B" + elif shade.upper == "NAVY": + clr = "#23238E" + + self.GetParser().SetActualColor(clr) + self.GetParser().GetContainer().InsertCell(wx.html.HtmlColourCell(clr)) + + self.ParseInner(tag) + + self.GetParser().SetActualColor(old) + self.GetParser().GetContainer().InsertCell(wx.html.HtmlColourCell(old)) + + return True + + +wx.html.HtmlWinParser_AddTagHandler(BlueTagHandler) + + + +class MyHtmlFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title) + html = wx.html.HtmlWindow(self) + if "gtk2" in wx.PlatformInfo: + html.SetStandardFonts() + html.SetPage(page) + + +app = wx.PySimpleApp() +frm = MyHtmlFrame(None, "Custom HTML Tag Handler") +frm.Show() +app.MainLoop() diff --git a/Chapter-16/html_window.py b/Chapter-16/html_window.py new file mode 100644 index 0000000..9f73b68 --- /dev/null +++ b/Chapter-16/html_window.py @@ -0,0 +1,19 @@ +import wx +import wx.html + +class MyHtmlFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title) + html = wx.html.HtmlWindow(self) + if "gtk2" in wx.PlatformInfo: + html.SetStandardFonts() + + html.SetPage( + "Here is some formatted text " + "loaded from a string.") + + +app = wx.PySimpleApp() +frm = MyHtmlFrame(None, "Simple HTML") +frm.Show() +app.MainLoop() diff --git a/Chapter-16/html_window_loadpage.py b/Chapter-16/html_window_loadpage.py new file mode 100644 index 0000000..5789ce4 --- /dev/null +++ b/Chapter-16/html_window_loadpage.py @@ -0,0 +1,17 @@ +import wx +import wx.html + +class MyHtmlFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title, size=(600,400)) + html = wx.html.HtmlWindow(self) + if "gtk2" in wx.PlatformInfo: + html.SetStandardFonts() + + wx.CallAfter( + html.LoadPage, "http://wxwidgets.org/manuals/2.6.2/wx_wxbutton.html") + +app = wx.PySimpleApp() +frm = MyHtmlFrame(None, "Simple HTML Browser") +frm.Show() +app.MainLoop() diff --git a/Chapter-16/html_window_related.py b/Chapter-16/html_window_related.py new file mode 100644 index 0000000..4887b71 --- /dev/null +++ b/Chapter-16/html_window_related.py @@ -0,0 +1,21 @@ +import wx +import wx.html + +class MyHtmlFrame(wx.Frame): + def __init__(self, parent, title): + wx.Frame.__init__(self, parent, -1, title, size=(600,400)) + self.CreateStatusBar() + + html = wx.html.HtmlWindow(self) + if "gtk2" in wx.PlatformInfo: + html.SetStandardFonts() + html.SetRelatedFrame(self, self.GetTitle() + " -- %s") + html.SetRelatedStatusBar(0) + + wx.CallAfter( + html.LoadPage, "http://wxwidgets.org/manuals/2.6.2/wx_wxbutton.html") + +app = wx.PySimpleApp() +frm = MyHtmlFrame(None, "Simple HTML Browser") +frm.Show() +app.MainLoop() diff --git a/Chapter-17/printing.py b/Chapter-17/printing.py new file mode 100644 index 0000000..144f351 --- /dev/null +++ b/Chapter-17/printing.py @@ -0,0 +1,223 @@ +import wx +import os + +FONTSIZE = 10 + +class TextDocPrintout(wx.Printout): + """ + A printout class that is able to print simple text documents. + Does not handle page numbers or titles, and it assumes that no + lines are longer than what will fit within the page width. Those + features are left as an exercise for the reader. ;-) + """ + def __init__(self, text, title, margins): + wx.Printout.__init__(self, title) + self.lines = text.split('\n') + self.margins = margins + + + def HasPage(self, page): + return page <= self.numPages + + def GetPageInfo(self): + return (1, self.numPages, 1, self.numPages) + + + def CalculateScale(self, dc): + # Scale the DC such that the printout is roughly the same as + # the screen scaling. + ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() + ppiScreenX, ppiScreenY = self.GetPPIScreen() + logScale = float(ppiPrinterX)/float(ppiScreenX) + + # Now adjust if the real page size is reduced (such as when + # drawing on a scaled wx.MemoryDC in the Print Preview.) If + # page width == DC width then nothing changes, otherwise we + # scale down for the DC. + pw, ph = self.GetPageSizePixels() + dw, dh = dc.GetSize() + scale = logScale * float(dw)/float(pw) + + # Set the DC's scale. + dc.SetUserScale(scale, scale) + + # Find the logical units per millimeter (for calculating the + # margins) + self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4) + + + def CalculateLayout(self, dc): + # Determine the position of the margins and the + # page/line height + topLeft, bottomRight = self.margins + dw, dh = dc.GetSize() + self.x1 = topLeft.x * self.logUnitsMM + self.y1 = topLeft.y * self.logUnitsMM + self.x2 = dc.DeviceToLogicalXRel(dw) - bottomRight.x * self.logUnitsMM + self.y2 = dc.DeviceToLogicalYRel(dh) - bottomRight.y * self.logUnitsMM + + # use a 1mm buffer around the inside of the box, and a few + # pixels between each line + self.pageHeight = self.y2 - self.y1 - 2*self.logUnitsMM + font = wx.Font(FONTSIZE, wx.TELETYPE, wx.NORMAL, wx.NORMAL) + dc.SetFont(font) + self.lineHeight = dc.GetCharHeight() + self.linesPerPage = int(self.pageHeight/self.lineHeight) + + + def OnPreparePrinting(self): + # calculate the number of pages + dc = self.GetDC() + self.CalculateScale(dc) + self.CalculateLayout(dc) + self.numPages = len(self.lines) / self.linesPerPage + if len(self.lines) % self.linesPerPage != 0: + self.numPages += 1 + + + def OnPrintPage(self, page): + dc = self.GetDC() + self.CalculateScale(dc) + self.CalculateLayout(dc) + + # draw a page outline at the margin points + dc.SetPen(wx.Pen("black", 0)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + r = wx.RectPP((self.x1, self.y1), + (self.x2, self.y2)) + dc.DrawRectangleRect(r) + dc.SetClippingRect(r) + + # Draw the text lines for this page + line = (page-1) * self.linesPerPage + x = self.x1 + self.logUnitsMM + y = self.y1 + self.logUnitsMM + while line < (page * self.linesPerPage): + dc.DrawText(self.lines[line], x, y) + y += self.lineHeight + line += 1 + if line >= len(self.lines): + break + return True + + +class PrintFrameworkSample(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, size=(640, 480), + title="Print Framework Sample") + self.CreateStatusBar() + + # A text widget to display the doc and let it be edited + self.tc = wx.TextCtrl(self, -1, "", + style=wx.TE_MULTILINE|wx.TE_DONTWRAP) + self.tc.SetFont(wx.Font(FONTSIZE, wx.TELETYPE, wx.NORMAL, wx.NORMAL)) + filename = os.path.join(os.path.dirname(__file__), "sample-text.txt") + self.tc.SetValue(open(filename).read()) + self.tc.Bind(wx.EVT_SET_FOCUS, self.OnClearSelection) + wx.CallAfter(self.tc.SetInsertionPoint, 0) + + # Create the menu and menubar + menu = wx.Menu() + item = menu.Append(-1, "Page Setup...\tF5", + "Set up page margins and etc.") + self.Bind(wx.EVT_MENU, self.OnPageSetup, item) + item = menu.Append(-1, "Print Setup...\tF6", + "Set up the printer options, etc.") + self.Bind(wx.EVT_MENU, self.OnPrintSetup, item) + item = menu.Append(-1, "Print Preview...\tF7", + "View the printout on-screen") + self.Bind(wx.EVT_MENU, self.OnPrintPreview, item) + item = menu.Append(-1, "Print...\tF8", "Print the document") + self.Bind(wx.EVT_MENU, self.OnPrint, item) + menu.AppendSeparator() + item = menu.Append(-1, "E&xit", "Close this application") + self.Bind(wx.EVT_MENU, self.OnExit, item) + + menubar = wx.MenuBar() + menubar.Append(menu, "&File") + self.SetMenuBar(menubar) + + # initialize the print data and set some default values + self.pdata = wx.PrintData() + self.pdata.SetPaperId(wx.PAPER_LETTER) + self.pdata.SetOrientation(wx.PORTRAIT) + self.margins = (wx.Point(15,15), wx.Point(15,15)) + + + def OnExit(self, evt): + self.Close() + + + def OnClearSelection(self, evt): + evt.Skip() + wx.CallAfter(self.tc.SetInsertionPoint, + self.tc.GetInsertionPoint()) + + + def OnPageSetup(self, evt): + data = wx.PageSetupDialogData() + data.SetPrintData(self.pdata) + + data.SetDefaultMinMargins(True) + data.SetMarginTopLeft(self.margins[0]) + data.SetMarginBottomRight(self.margins[1]) + + dlg = wx.PageSetupDialog(self, data) + if dlg.ShowModal() == wx.ID_OK: + data = dlg.GetPageSetupData() + self.pdata = wx.PrintData(data.GetPrintData()) # force a copy + self.pdata.SetPaperId(data.GetPaperId()) + self.margins = (data.GetMarginTopLeft(), + data.GetMarginBottomRight()) + dlg.Destroy() + + + def OnPrintSetup(self, evt): + data = wx.PrintDialogData(self.pdata) + dlg = wx.PrintDialog(self, data) + dlg.GetPrintDialogData().SetSetupDialog(True) + dlg.ShowModal(); + data = dlg.GetPrintDialogData() + self.pdata = wx.PrintData(data.GetPrintData()) # force a copy + dlg.Destroy() + + + def OnPrintPreview(self, evt): + data = wx.PrintDialogData(self.pdata) + text = self.tc.GetValue() + printout1 = TextDocPrintout(text, "title", self.margins) + printout2 = None #TextDocPrintout(text, "title", self.margins) + preview = wx.PrintPreview(printout1, printout2, data) + if not preview.Ok(): + wx.MessageBox("Unable to create PrintPreview!", "Error") + else: + # create the preview frame such that it overlays the app frame + frame = wx.PreviewFrame(preview, self, "Print Preview", + pos=self.GetPosition(), + size=self.GetSize()) + frame.Initialize() + frame.Show() + + + def OnPrint(self, evt): + data = wx.PrintDialogData(self.pdata) + printer = wx.Printer(data) + text = self.tc.GetValue() + printout = TextDocPrintout(text, "title", self.margins) + useSetupDialog = True + if not printer.Print(self, printout, useSetupDialog) \ + and printer.GetLastError() == wx.PRINTER_ERROR: + wx.MessageBox( + "There was a problem printing.\n" + "Perhaps your current printer is not set correctly?", + "Printing Error", wx.OK) + else: + data = printer.GetPrintDialogData() + self.pdata = wx.PrintData(data.GetPrintData()) # force a copy + printout.Destroy() + + +app = wx.PySimpleApp() +frm = PrintFrameworkSample() +frm.Show() +app.MainLoop() diff --git a/Chapter-17/sample-text.txt b/Chapter-17/sample-text.txt new file mode 100644 index 0000000..8e902f3 --- /dev/null +++ b/Chapter-17/sample-text.txt @@ -0,0 +1,213 @@ +THE BROTHERS GRIMM +FAIRY TALES + + + +THE GOLDEN BIRD + +A certain king had a beautiful garden, and in the garden stood a tree +which bore golden apples. These apples were always counted, and about +the time when they began to grow ripe it was found that every night +one of them was gone. The king became very angry at this, and ordered +the gardener to keep watch all night under the tree. The gardener set +his eldest son to watch; but about twelve o'clock he fell asleep, and +in the morning another of the apples was missing. Then the second son +was ordered to watch; and at midnight he too fell asleep, and in the +morning another apple was gone. Then the third son offered to keep +watch; but the gardener at first would not let him, for fear some harm +should come to him: however, at last he consented, and the young man +laid himself under the tree to watch. As the clock struck twelve he +heard a rustling noise in the air, and a bird came flying that was of +pure gold; and as it was snapping at one of the apples with its beak, +the gardener's son jumped up and shot an arrow at it. But the arrow +did the bird no harm; only it dropped a golden feather from its tail, +and then flew away. The golden feather was brought to the king in the +morning, and all the council was called together. Everyone agreed that +it was worth more than all the wealth of the kingdom: but the king +said, 'One feather is of no use to me, I must have the whole bird.' + +Then the gardener's eldest son set out and thought to find the golden +bird very easily; and when he had gone but a little way, he came to a +wood, and by the side of the wood he saw a fox sitting; so he took his +bow and made ready to shoot at it. Then the fox said, 'Do not shoot +me, for I will give you good counsel; I know what your business is, +and that you want to find the golden bird. You will reach a village in +the evening; and when you get there, you will see two inns opposite to +each other, one of which is very pleasant and beautiful to look at: go +not in there, but rest for the night in the other, though it may +appear to you to be very poor and mean.' But the son thought to +himself, 'What can such a beast as this know about the matter?' So he +shot his arrow at the fox; but he missed it, and it set up its tail +above its back and ran into the wood. Then he went his way, and in the +evening came to the village where the two inns were; and in one of +these were people singing, and dancing, and feasting; but the other +looked very dirty, and poor. 'I should be very silly,' said he, 'if I +went to that shabby house, and left this charming place'; so he went +into the smart house, and ate and drank at his ease, and forgot the +bird, and his country too. + +Time passed on; and as the eldest son did not come back, and no +tidings were heard of him, the second son set out, and the same thing +happened to him. He met the fox, who gave him the good advice: but +when he came to the two inns, his eldest brother was standing at the +window where the merrymaking was, and called to him to come in; and he +could not withstand the temptation, but went in, and forgot the golden +bird and his country in the same manner. + +Time passed on again, and the youngest son too wished to set out into +the wide world to seek for the golden bird; but his father would not +listen to it for a long while, for he was very fond of his son, and +was afraid that some ill luck might happen to him also, and prevent +his coming back. However, at last it was agreed he should go, for he +would not rest at home; and as he came to the wood, he met the fox, +and heard the same good counsel. But he was thankful to the fox, and +did not attempt his life as his brothers had done; so the fox said, +'Sit upon my tail, and you will travel faster.' So he sat down, and +the fox began to run, and away they went over stock and stone so quick +that their hair whistled in the wind. + +When they came to the village, the son followed the fox's counsel, and +without looking about him went to the shabby inn and rested there all +night at his ease. In the morning came the fox again and met him as he +was beginning his journey, and said, 'Go straight forward, till you +come to a castle, before which lie a whole troop of soldiers fast +asleep and snoring: take no notice of them, but go into the castle and +pass on and on till you come to a room, where the golden bird sits in +a wooden cage; close by it stands a beautiful golden cage; but do not +try to take the bird out of the shabby cage and put it into the +handsome one, otherwise you will repent it.' Then the fox stretched +out his tail again, and the young man sat himself down, and away they +went over stock and stone till their hair whistled in the wind. + +Before the castle gate all was as the fox had said: so the son went in +and found the chamber where the golden bird hung in a wooden cage, and +below stood the golden cage, and the three golden apples that had been +lost were lying close by it. Then thought he to himself, 'It will be a +very droll thing to bring away such a fine bird in this shabby cage'; +so he opened the door and took hold of it and put it into the golden +cage. But the bird set up such a loud scream that all the soldiers +awoke, and they took him prisoner and carried him before the king. The +next morning the court sat to judge him; and when all was heard, it +sentenced him to die, unless he should bring the king the golden horse +which could run as swiftly as the wind; and if he did this, he was to +have the golden bird given him for his own. + +So he set out once more on his journey, sighing, and in great despair, +when on a sudden his friend the fox met him, and said, 'You see now +what has happened on account of your not listening to my counsel. I +will still, however, tell you how to find the golden horse, if you +will do as I bid you. You must go straight on till you come to the +castle where the horse stands in his stall: by his side will lie the +groom fast asleep and snoring: take away the horse quietly, but be +sure to put the old leathern saddle upon him, and not the golden one +that is close by it.' Then the son sat down on the fox's tail, and +away they went over stock and stone till their hair whistled in the +wind. + +All went right, and the groom lay snoring with his hand upon the +golden saddle. But when the son looked at the horse, he thought it a +great pity to put the leathern saddle upon it. 'I will give him the +good one,' said he; 'I am sure he deserves it.' As he took up the +golden saddle the groom awoke and cried out so loud, that all the +guards ran in and took him prisoner, and in the morning he was again +brought before the court to be judged, and was sentenced to die. But +it was agreed, that, if he could bring thither the beautiful princess, +he should live, and have the bird and the horse given him for his own. + +Then he went his way very sorrowful; but the old fox came and said, +'Why did not you listen to me? If you had, you would have carried away +both the bird and the horse; yet will I once more give you counsel. Go +straight on, and in the evening you will arrive at a castle. At twelve +o'clock at night the princess goes to the bathing-house: go up to her +and give her a kiss, and she will let you lead her away; but take care +you do not suffer her to go and take leave of her father and mother.' +Then the fox stretched out his tail, and so away they went over stock +and stone till their hair whistled again. + +As they came to the castle, all was as the fox had said, and at twelve +o'clock the young man met the princes going to the bath and gave her +the kiss, and she agreed to run away with him, but begged with many +tears that he would let her take leave of her father. At first he +refused, but she wept still more and more, and fell at his feet, till +at last he consented; but the moment she came to her father's house +the guards awoke and he was taken prisoner again. + +Then he was brought before the king, and the king said, 'You shall +never have my daughter unless in eight days you dig away the hill that +stops the view from my window.' Now this hill was so big that the +whole world could not take it away: and when he had worked for seven +days, and had done very little, the fox came and said. 'Lie down and +go to sleep; I will work for you.' And in the morning he awoke and the +hill was gone; so he went merrily to the king, and told him that now +that it was removed he must give him the princess. + +Then the king was obliged to keep his word, and away went the young +man and the princess; and the fox came and said to him, 'We will have +all three, the princess, the horse, and the bird.' 'Ah!' said the +young man, 'that would be a great thing, but how can you contrive it?' + +'If you will only listen,' said the fox, 'it can be done. When you +come to the king, and he asks for the beautiful princess, you must +say, "Here she is!" Then he will be very joyful; and you will mount +the golden horse that they are to give you, and put out your hand to +take leave of them; but shake hands with the princess last. Then lift +her quickly on to the horse behind you; clap your spurs to his side, +and gallop away as fast as you can.' + +All went right: then the fox said, 'When you come to the castle where +the bird is, I will stay with the princess at the door, and you will +ride in and speak to the king; and when he sees that it is the right +horse, he will bring out the bird; but you must sit still, and say +that you want to look at it, to see whether it is the true golden +bird; and when you get it into your hand, ride away.' + +This, too, happened as the fox said; they carried off the bird, the +princess mounted again, and they rode on to a great wood. Then the fox +came, and said, 'Pray kill me, and cut off my head and my feet.' But +the young man refused to do it: so the fox said, 'I will at any rate +give you good counsel: beware of two things; ransom no one from the +gallows, and sit down by the side of no river.' Then away he went. +'Well,' thought the young man, 'it is no hard matter to keep that +advice.' + +He rode on with the princess, till at last he came to the village +where he had left his two brothers. And there he heard a great noise +and uproar; and when he asked what was the matter, the people said, +'Two men are going to be hanged.' As he came nearer, he saw that the +two men were his brothers, who had turned robbers; so he said, 'Cannot +they in any way be saved?' But the people said 'No,' unless he would +bestow all his money upon the rascals and buy their liberty. Then he +did not stay to think about the matter, but paid what was asked, and +his brothers were given up, and went on with him towards their home. + +And as they came to the wood where the fox first met them, it was so +cool and pleasant that the two brothers said, 'Let us sit down by the +side of the river, and rest a while, to eat and drink.' So he said, +'Yes,' and forgot the fox's counsel, and sat down on the side of the +river; and while he suspected nothing, they came behind, and threw him +down the bank, and took the princess, the horse, and the bird, and +went home to the king their master, and said. 'All this have we won by +our labour.' Then there was great rejoicing made; but the horse would +not eat, the bird would not sing, and the princess wept. + +The youngest son fell to the bottom of the river's bed: luckily it was +nearly dry, but his bones were almost broken, and the bank was so +steep that he could find no way to get out. Then the old fox came once +more, and scolded him for not following his advice; otherwise no evil +would have befallen him: 'Yet,' said he, 'I cannot leave you here, so +lay hold of my tail and hold fast.' Then he pulled him out of the +river, and said to him, as he got upon the bank, 'Your brothers have +set watch to kill you, if they find you in the kingdom.' So he dressed +himself as a poor man, and came secretly to the king's court, and was +scarcely within the doors when the horse began to eat, and the bird to +sing, and princess left off weeping. Then he went to the king, and +told him all his brothers' roguery; and they were seized and punished, +and he had the princess given to him again; and after the king's death +he was heir to his kingdom. + +A long while after, he went to walk one day in the wood, and the old +fox met him, and besought him with tears in his eyes to kill him, and +cut off his head and feet. And at last he did so, and in a moment the +fox was changed into a man, and turned out to be the brother of the +princess, who had been lost a great many many years. + diff --git a/Chapter-18/clipboard.py b/Chapter-18/clipboard.py new file mode 100644 index 0000000..3ddf43c --- /dev/null +++ b/Chapter-18/clipboard.py @@ -0,0 +1,77 @@ +import wx + +t1_text = """\ +The whole contents of this control +will be placed in the system's +clipboard when you click the copy +button below. +""" + +t2_text = """\ +If the clipboard contains a text +data object then it will be placed +in this control when you click +the paste button below. Try +copying to and pasting from +other applications too! +""" + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Clipboard", + size=(500,300)) + p = wx.Panel(self) + + # create the controls + self.t1 = wx.TextCtrl(p, -1, t1_text, + style=wx.TE_MULTILINE|wx.HSCROLL) + self.t2 = wx.TextCtrl(p, -1, t2_text, + style=wx.TE_MULTILINE|wx.HSCROLL) + copy = wx.Button(p, -1, "Copy") + paste = wx.Button(p, -1, "Paste") + + # setup the layout with sizers + fgs = wx.FlexGridSizer(2, 2, 5, 5) + fgs.AddGrowableRow(0) + fgs.AddGrowableCol(0) + fgs.AddGrowableCol(1) + fgs.Add(self.t1, 0, wx.EXPAND) + fgs.Add(self.t2, 0, wx.EXPAND) + fgs.Add(copy, 0, wx.EXPAND) + fgs.Add(paste, 0, wx.EXPAND) + border = wx.BoxSizer() + border.Add(fgs, 1, wx.EXPAND|wx.ALL, 5) + p.SetSizer(border) + + # Bind events + self.Bind(wx.EVT_BUTTON, self.OnDoCopy, copy) + self.Bind(wx.EVT_BUTTON, self.OnDoPaste, paste) + + def OnDoCopy(self, evt): + data = wx.TextDataObject() + data.SetText(self.t1.GetValue()) + if wx.TheClipboard.Open(): + wx.TheClipboard.SetData(data) + wx.TheClipboard.Close() + else: + wx.MessageBox("Unable to open the clipboard", "Error") + + def OnDoPaste(self, evt): + success = False + data = wx.TextDataObject() + if wx.TheClipboard.Open(): + success = wx.TheClipboard.GetData(data) + wx.TheClipboard.Close() + + if success: + self.t2.SetValue(data.GetText()) + else: + wx.MessageBox( + "There is no data in the clipboard in the required format", + "Error") + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/customcomposite.py b/Chapter-18/customcomposite.py new file mode 100644 index 0000000..c396322 --- /dev/null +++ b/Chapter-18/customcomposite.py @@ -0,0 +1,152 @@ +""" +This sample shows how to put multiple objects in the clipboard, one of +which uses a custom data format. In this case we use a Python +dictionary of values for our custom format, and we also put a textual +representation of the dictionary. To test this, run two instances of +this program, enter data in one and click the copy button. Then click +the paste button in the other instance. Also paste into a text editor +to see the data in the standard text format. +""" + + +import wx +import cPickle +import pprint + +class TestFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, -1, "Copy/Paste Test") + panel = wx.Panel(self) + + # First create the controls + topLbl = wx.StaticText(panel, -1, "Account Information") + topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) + + nameLbl = wx.StaticText(panel, -1, "Name:") + self.name = wx.TextCtrl(panel, -1, ""); + + addrLbl = wx.StaticText(panel, -1, "Address:") + self.addr1 = wx.TextCtrl(panel, -1, ""); + self.addr2 = wx.TextCtrl(panel, -1, ""); + + cstLbl = wx.StaticText(panel, -1, "City, State, Zip:") + self.city = wx.TextCtrl(panel, -1, "", size=(150,-1)); + self.state = wx.TextCtrl(panel, -1, "", size=(50,-1)); + self.zip = wx.TextCtrl(panel, -1, "", size=(70,-1)); + + phoneLbl = wx.StaticText(panel, -1, "Phone:") + self.phone = wx.TextCtrl(panel, -1, ""); + + emailLbl = wx.StaticText(panel, -1, "Email:") + self.email = wx.TextCtrl(panel, -1, ""); + + copyBtn = wx.Button(panel, -1, "Copy") + pasteBtn = wx.Button(panel, -1, "Paste") + self.Bind(wx.EVT_BUTTON, self.OnCopy, copyBtn) + self.Bind(wx.EVT_BUTTON, self.OnPaste, pasteBtn) + + # Now do the layout. + + # mainSizer is the top-level one that manages everything + mainSizer = wx.BoxSizer(wx.VERTICAL) + mainSizer.Add(topLbl, 0, wx.ALL, 5) + mainSizer.Add(wx.StaticLine(panel), 0, + wx.EXPAND|wx.TOP|wx.BOTTOM, 5) + + # addrSizer is a grid that holds all of the address info + addrSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5) + addrSizer.AddGrowableCol(1) + addrSizer.Add(nameLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(self.name, 0, wx.EXPAND) + addrSizer.Add(addrLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(self.addr1, 0, wx.EXPAND) + addrSizer.Add((10,10)) # some empty space + addrSizer.Add(self.addr2, 0, wx.EXPAND) + + addrSizer.Add(cstLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + + # the city, state, zip fields are in a sub-sizer + cstSizer = wx.BoxSizer(wx.HORIZONTAL) + cstSizer.Add(self.city, 1) + cstSizer.Add(self.state, 0, wx.LEFT|wx.RIGHT, 5) + cstSizer.Add(self.zip) + addrSizer.Add(cstSizer, 0, wx.EXPAND) + + addrSizer.Add(phoneLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(self.phone, 0, wx.EXPAND) + addrSizer.Add(emailLbl, 0, + wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) + addrSizer.Add(self.email, 0, wx.EXPAND) + + # now add the addrSizer to the mainSizer + mainSizer.Add(addrSizer, 0, wx.EXPAND|wx.ALL, 10) + + # The buttons sizer will put them in a row with resizeable + # gaps between and on either side of the buttons + btnSizer = wx.BoxSizer(wx.HORIZONTAL) + btnSizer.Add((20,20), 1) + btnSizer.Add(copyBtn) + btnSizer.Add((20,20), 1) + btnSizer.Add(pasteBtn) + btnSizer.Add((20,20), 1) + + mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10) + + panel.SetSizer(mainSizer) + + # Fit the frame to the needs of the sizer. The frame will + # automatically resize the panel as needed. Also prevent the + # frame from getting smaller than this size. + mainSizer.Fit(self) + self.SetMinSize(self.GetSize()) + + + fieldNames = ["name", "addr1", "addr2", + "city", "state", "zip", "phone", "email"] + + def OnCopy(self, evt): + # make a dictionary of values + fieldData = {} + for name in self.fieldNames: + tc = getattr(self, name) + fieldData[name] = tc.GetValue() + + # pickle it and put in a custom data object + cdo = wx.CustomDataObject("ContactDictFormat") + cdo.SetData(cPickle.dumps(fieldData)) + + # also make a text representaion + tdo = wx.TextDataObject(pprint.pformat(fieldData)) + + # and put them both in the clipboard + dataobj = wx.DataObjectComposite() + dataobj.Add(cdo) + dataobj.Add(tdo) + if wx.TheClipboard.Open(): + wx.TheClipboard.SetData(dataobj) + wx.TheClipboard.Close() + + + def OnPaste(self, evt): + # Get the custom format object and put it into + # the entry fields + cdo = wx.CustomDataObject("ContactDictFormat") + if wx.TheClipboard.Open(): + success = wx.TheClipboard.GetData(cdo) + wx.TheClipboard.Close() + if success: + data = cdo.GetData() + fieldData = cPickle.loads(data) + for name in self.fieldNames: + tc = getattr(self, name) + tc.SetValue(fieldData[name]) + + + +app = wx.PySimpleApp() +TestFrame().Show() +app.MainLoop() diff --git a/Chapter-18/drop_source.py b/Chapter-18/drop_source.py new file mode 100644 index 0000000..d854947 --- /dev/null +++ b/Chapter-18/drop_source.py @@ -0,0 +1,70 @@ +import wx + +class DragController(wx.Control): + """ + Just a little control to handle dragging the text from a text + control. We use a separate control so as to not interfere with + the native drag-select functionality of the native text control. + """ + def __init__(self, parent, source, size=(25,25)): + wx.Control.__init__(self, parent, -1, size=size, + style=wx.SIMPLE_BORDER) + self.source = source + self.SetMinSize(size) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + + def OnPaint(self, evt): + # draw a simple arrow + dc = wx.BufferedPaintDC(self) + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + w, h = dc.GetSize() + y = h/2 + dc.SetPen(wx.Pen("dark blue", 2)) + dc.DrawLine(w/8, y, w-w/8, y) + dc.DrawLine(w-w/8, y, w/2, h/4) + dc.DrawLine(w-w/8, y, w/2, 3*h/4) + + def OnLeftDown(self, evt): + text = self.source.GetValue() + data = wx.TextDataObject(text) + dropSource = wx.DropSource(self) + dropSource.SetData(data) + result = dropSource.DoDragDrop(wx.Drag_AllowMove) + + # if the user wants to move the data then we should delete it + # from the source + if result == wx.DragMove: + self.source.SetValue("") + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Drop Source") + p = wx.Panel(self) + + # create the controls + label1 = wx.StaticText(p, -1, "Put some text in this control:") + label2 = wx.StaticText(p, -1, + "Then drag from the neighboring bitmap and\n" + "drop in an application that accepts dropped\n" + "text, such as MS Word.") + text = wx.TextCtrl(p, -1, "Some text") + dragctl = DragController(p, text) + + # setup the layout with sizers + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(label1, 0, wx.ALL, 5) + hrow = wx.BoxSizer(wx.HORIZONTAL) + hrow.Add(text, 1, wx.RIGHT, 5) + hrow.Add(dragctl, 0) + sizer.Add(hrow, 0, wx.EXPAND|wx.ALL, 5) + sizer.Add(label2, 0, wx.ALL, 5) + p.SetSizer(sizer) + sizer.Fit(self) + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/drop_target.py b/Chapter-18/drop_target.py new file mode 100644 index 0000000..bec7653 --- /dev/null +++ b/Chapter-18/drop_target.py @@ -0,0 +1,40 @@ +import wx + +class MyFileDropTarget(wx.FileDropTarget): + def __init__(self, window): + wx.FileDropTarget.__init__(self) + self.window = window + + def OnDropFiles(self, x, y, filenames): + self.window.AppendText("\n%d file(s) dropped at (%d,%d):\n" % + (len(filenames), x, y)) + for file in filenames: + self.window.AppendText("\t%s\n" % file) + + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Drop Target", + size=(500,300)) + p = wx.Panel(self) + + # create the controls + label = wx.StaticText(p, -1, "Drop some files here:") + text = wx.TextCtrl(p, -1, "", + style=wx.TE_MULTILINE|wx.HSCROLL) + + # setup the layout with sizers + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(label, 0, wx.ALL, 5) + sizer.Add(text, 1, wx.EXPAND|wx.ALL, 5) + p.SetSizer(sizer) + + # make the text control be a drop target + dt = MyFileDropTarget(text) + text.SetDropTarget(dt) + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/sound.py b/Chapter-18/sound.py new file mode 100644 index 0000000..9242fc9 --- /dev/null +++ b/Chapter-18/sound.py @@ -0,0 +1,38 @@ +import wx +from wx.lib.filebrowsebutton import FileBrowseButton + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="wx.Sound", + size=(500,100)) + p = wx.Panel(self) + + # create the controls + self.fbb = FileBrowseButton(p, + labelText="Select WAV file:", + fileMask="*.wav") + btn = wx.Button(p, -1, "Play") + self.Bind(wx.EVT_BUTTON, self.OnPlaySound, btn) + + # setup the layout with sizers + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.fbb, 1, wx.ALIGN_CENTER_VERTICAL) + sizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL) + border = wx.BoxSizer(wx.VERTICAL) + border.Add(sizer, 0, wx.EXPAND|wx.ALL, 15) + p.SetSizer(border) + + + def OnPlaySound(self, evt): + filename = self.fbb.GetValue() + self.sound = wx.Sound(filename) + if self.sound.IsOk(): + self.sound.Play(wx.SOUND_ASYNC) + else: + wx.MessageBox("Invalid sound file", "Error") + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/sound1.wav b/Chapter-18/sound1.wav new file mode 100644 index 0000000..c8bdd59 Binary files /dev/null and b/Chapter-18/sound1.wav differ diff --git a/Chapter-18/sound2.wav b/Chapter-18/sound2.wav new file mode 100644 index 0000000..4236138 Binary files /dev/null and b/Chapter-18/sound2.wav differ diff --git a/Chapter-18/timer.py b/Chapter-18/timer.py new file mode 100644 index 0000000..879a40c --- /dev/null +++ b/Chapter-18/timer.py @@ -0,0 +1,39 @@ +import wx +import time + +class ClockWindow(wx.Window): + def __init__(self, parent): + wx.Window.__init__(self, parent) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer) + self.timer.Start(1000) + + def Draw(self, dc): + t = time.localtime(time.time()) + st = time.strftime("%I:%M:%S", t) + w, h = self.GetClientSize() + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + dc.SetFont(wx.Font(30, wx.SWISS, wx.NORMAL, wx.NORMAL)) + tw, th = dc.GetTextExtent(st) + dc.DrawText(st, (w-tw)/2, (h)/2 - th/2) + + def OnTimer(self, evt): + dc = wx.BufferedDC(wx.ClientDC(self)) + self.Draw(dc) + + def OnPaint(self, evt): + dc = wx.BufferedPaintDC(self) + self.Draw(dc) + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="wx.Timer") + ClockWindow(self) + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/worker_threads.py b/Chapter-18/worker_threads.py new file mode 100644 index 0000000..0ae091c --- /dev/null +++ b/Chapter-18/worker_threads.py @@ -0,0 +1,101 @@ +import wx +import threading +import random + +class WorkerThread(threading.Thread): + """ + This just simulates some long-running task that periodically sends + a message to the GUI thread. + """ + def __init__(self, threadNum, window): + threading.Thread.__init__(self) + self.threadNum = threadNum + self.window = window + self.timeToQuit = threading.Event() + self.timeToQuit.clear() + self.messageCount = random.randint(10,20) + self.messageDelay = 0.1 + 2.0 * random.random() + + def stop(self): + self.timeToQuit.set() + + def run(self): + msg = "Thread %d iterating %d times with a delay of %1.4f\n" \ + % (self.threadNum, self.messageCount, self.messageDelay) + wx.CallAfter(self.window.LogMessage, msg) + + for i in range(1, self.messageCount+1): + self.timeToQuit.wait(self.messageDelay) + if self.timeToQuit.isSet(): + break + msg = "Message %d from thread %d\n" % (i, self.threadNum) + wx.CallAfter(self.window.LogMessage, msg) + else: + wx.CallAfter(self.window.ThreadFinished, self) + + + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="Multi-threaded GUI") + self.threads = [] + self.count = 0 + + panel = wx.Panel(self) + startBtn = wx.Button(panel, -1, "Start a thread") + stopBtn = wx.Button(panel, -1, "Stop all threads") + self.tc = wx.StaticText(panel, -1, "Worker Threads: 00") + self.log = wx.TextCtrl(panel, -1, "", + style=wx.TE_RICH|wx.TE_MULTILINE) + + inner = wx.BoxSizer(wx.HORIZONTAL) + inner.Add(startBtn, 0, wx.RIGHT, 15) + inner.Add(stopBtn, 0, wx.RIGHT, 15) + inner.Add(self.tc, 0, wx.ALIGN_CENTER_VERTICAL) + main = wx.BoxSizer(wx.VERTICAL) + main.Add(inner, 0, wx.ALL, 5) + main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5) + panel.SetSizer(main) + + self.Bind(wx.EVT_BUTTON, self.OnStartButton, startBtn) + self.Bind(wx.EVT_BUTTON, self.OnStopButton, stopBtn) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + self.UpdateCount() + + def OnStartButton(self, evt): + self.count += 1 + thread = WorkerThread(self.count, self) + self.threads.append(thread) + self.UpdateCount() + thread.start() + + def OnStopButton(self, evt): + self.StopThreads() + self.UpdateCount() + + def OnCloseWindow(self, evt): + self.StopThreads() + self.Destroy() + + def StopThreads(self): + while self.threads: + thread = self.threads[0] + thread.stop() + self.threads.remove(thread) + + def UpdateCount(self): + self.tc.SetLabel("Worker Threads: %d" % len(self.threads)) + + def LogMessage(self, msg): + self.log.AppendText(msg) + + def ThreadFinished(self, thread): + self.threads.remove(thread) + self.UpdateCount() + + +app = wx.PySimpleApp() +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/xrcsample.py b/Chapter-18/xrcsample.py new file mode 100644 index 0000000..6dc1dcf --- /dev/null +++ b/Chapter-18/xrcsample.py @@ -0,0 +1,43 @@ +""" +XRC is an XML-based resource format for wxPython. With it you can +define the layout of widgets, and then load that XRC at runtime to +create the layout. There are several GUI designers available that +understand the XRC format, a simple one called XRCed comes with +wxPython. +""" + +import wx +import wx.xrc + + +class MyFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="XRC Sample", + size=(400,225)) + res = wx.xrc.XmlResource("xrcsample.xrc") + panel = res.LoadPanel(self, "ID_PANEL") + + self.Bind(wx.EVT_BUTTON, self.OnOk, + wx.xrc.XRCCTRL(self, "ID_OK")) + self.Bind(wx.EVT_BUTTON, self.OnCancel, + wx.xrc.XRCCTRL(self, "ID_CANCEL")) + + + def OnOk(self, evt): + namectrl = wx.xrc.XRCCTRL(self, "ID_NAME") + name = namectrl.GetValue() + emailctrl = wx.xrc.XRCCTRL(self, "ID_EMAIL") + email = emailctrl.GetValue() + phonectrl = wx.xrc.XRCCTRL(self, "ID_PHONE") + phone = phonectrl.GetValue() + print "You entered:\n name: %s\n email: %s\n phone: %s\n" \ + % (name, email, phone) + + def OnCancel(self, evt): + self.Close() + + +app = wx.PySimpleApp(redirect=True) +frm = MyFrame() +frm.Show() +app.MainLoop() diff --git a/Chapter-18/xrcsample.xrc b/Chapter-18/xrcsample.xrc new file mode 100644 index 0000000..c1d211f --- /dev/null +++ b/Chapter-18/xrcsample.xrc @@ -0,0 +1,94 @@ + + + + + + + + + wxALIGN_RIGHT + 1,1 + + 5 + 5 + + + 200,-1 + + wxEXPAND + 1,2 + + + + + + wxALIGN_RIGHT + 2,1 + + + + wxEXPAND + 2,2 + + + + + + wxALIGN_RIGHT + 3,1 + + + + wxEXPAND + 3,2 + + 2 + + + wxVERTICAL + + + + + wxEXPAND|wxALIGN_CENTRE_VERTICAL + + + wxEXPAND + 5,0 + 1,4 + + + + wxHORIZONTAL + + 10,10 + + + + + + 1 + + + + 10,10 + + + + + + + + + + 10,10 + + + + wxEXPAND + 6,0 + 1,4 + + + + diff --git a/mylab/canvas.py b/mylab/canvas.py new file mode 100644 index 0000000..9632c73 --- /dev/null +++ b/mylab/canvas.py @@ -0,0 +1,45 @@ +import wx + +class MyFrame(wx.Frame): + def __init__(self, parent=None, id=-1, title=None): + wx.Frame.__init__(self, parent, id, title, size=(380,400)) + self.statbmp = wx.StaticBitmap(self) + self.draw_image() + self.save_image() + + def draw_image(self): + # select the width and height of the blank bitmap + # should fit the client frame + w, h = 340, 340 + # create the blank bitmap as a draw background + draw_bmp = wx.EmptyBitmap(w, h) + # create the canvas on top of the draw_bmp + canvas_dc = wx.MemoryDC(draw_bmp) + # fill the canvas white + canvas_dc.SetBrush(wx.Brush('white')) + canvas_dc.Clear() + + # draw a bunch of circles ... + # pen colour + canvas_dc.SetPen(wx.Pen('red', 1)) + # fill colour + canvas_dc.SetBrush(wx.Brush('yellow')) + for x in range(10, 180, 10): + y = x + r = x + canvas_dc.DrawCircle(x, y, r) + + # now put the canvas drawing into a bitmap to display it + # remember the canvas is on top of the draw_bmp + self.statbmp.SetBitmap(draw_bmp) + + def save_image(self): + """save the drawing""" + finished_image = self.statbmp.GetBitmap() + #finished_image.SaveFile("mydrawing.png", wx.BITMAP_TYPE_PNG) + finished_image.SaveFile("mydrawing.jpg", wx.BITMAP_TYPE_JPEG) + + +app = wx.App(0) +MyFrame(title='canvas draw and save').Show() +app.MainLoop() \ No newline at end of file diff --git a/mylab/images.py b/mylab/images.py new file mode 100644 index 0000000..63e5c8f --- /dev/null +++ b/mylab/images.py @@ -0,0 +1,24 @@ +#---------------------------------------------------------------------- +# This file was generated by encode_bitmaps.py +# +from wx import ImageFromStream, BitmapFromImage +from wx import EmptyIcon +import cStringIO + +def getNewData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x0f\x08\x06\ +\x00\x00\x00\xedsO/\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\ +\x00YIDATx\x9c\xed\xd31\n@!\x0c\x03\xd0\xa4\xfe\xfb\xdfX\xe3\xf0\x97R\xa5(.\ +\x0ef\x13\xe45\xa2\x92Vp\x92\xcf/\xd4\xaa\xb2\xcd\xb4\xc2\x14\x00\x00in\x90\ +\x84ZUDl\xa9\xa7\xc3c\xcb-\x80\xfc\x87{d8B6=B\xdb\rfy\xc0\r\xc0\xf0\x0e\xfc\ +\x1d\xaf\x84\xa7\xbf\xb1\x03\xe1,\x19&\x93\x9a\xd2\x97\x00\x00\x00\x00IEND\ +\xaeB`\x82' + +def getNewBitmap(): + return BitmapFromImage(getNewImage()) + +def getNewImage(): + stream = cStringIO.StringIO(getNewData()) + return ImageFromStream(stream) + diff --git a/mylab/images.pyc b/mylab/images.pyc new file mode 100644 index 0000000..df606f3 Binary files /dev/null and b/mylab/images.pyc differ diff --git a/mylab/mdi.py b/mylab/mdi.py new file mode 100644 index 0000000..2fafe0b --- /dev/null +++ b/mylab/mdi.py @@ -0,0 +1,49 @@ +import wx +import images + +class MDIFrame(wx.MDIParentFrame): + def __init__(self): + wx.MDIParentFrame.__init__(self, None, -1, "MDI Parent", + size=(600,400)) + + menu = wx.Menu() + menu.Append(5000, "&New Window") + menu.Append(5001, "&Upload Traffic") + menu.Append(5002, "E&xit") + menubar = wx.MenuBar() + menubar.Append(menu, "&File") + self.SetMenuBar(menubar) + self.Bind(wx.EVT_MENU, self.OnNewWindow, id=5000) + self.Bind(wx.EVT_MENU, self.OnExit, id=5002) + + # Status bar and Tool bar + statusBar = self.CreateStatusBar() + toolbar = self.CreateToolBar() + toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(), + "New", "Long help for 'New'") + toolbar.Realize() + + def OnExit(self, evt): + self.Close(True) + + def OnNewWindow(self, evt): + win = wx.MDIChildFrame(self, -1, "Child Window") + win.Show(True) + topLbl = wx.StaticText(win, -1, "Account Information") + topLbl.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) + + nameLbl = wx.StaticText(win, -1, "Name:") + name = wx.TextCtrl(win, -1, ""); + + saveBtn = wx.Button(win, -1, "Save") + cancelBtn = wx.Button(win, -1, "Cancel") + + +if __name__ == '__main__': + app = wx.App(redirect=True) + frame = MDIFrame() + frame.Maximize() + frame.Show() + app.MainLoop() + + diff --git a/mylab/mydrawing.jpg b/mylab/mydrawing.jpg new file mode 100644 index 0000000..fd8b2c5 Binary files /dev/null and b/mylab/mydrawing.jpg differ diff --git a/mylab/progressbar.py b/mylab/progressbar.py new file mode 100644 index 0000000..8be0411 --- /dev/null +++ b/mylab/progressbar.py @@ -0,0 +1,16 @@ +import wx + +if __name__ == "__main__": + app = wx.PySimpleApp() + progressMax = 100 + dialog = wx.ProgressDialog("A progress box", "Time remaining", progressMax, + style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME) + keepGoing = True + count = 0 + while keepGoing and count < progressMax: + count = count + 1 + wx.Sleep(1) + keepGoing = dialog.Update(count) + + dialog.Destroy() +