[3570545] | 1 | # This program is public domain |
---|
| 2 | """ |
---|
| 3 | Asynchronous monitoring service for wx applications. |
---|
| 4 | |
---|
| 5 | Define a monitor using park.wxmonitor.wxMonitor(panel) where panel is |
---|
| 6 | the window which will receive the monitor updates. |
---|
| 7 | |
---|
| 8 | In panel, be sure to have methods for onMonitorStart(message), |
---|
| 9 | onMonitorProgress(message), etc., for the kinds of monitor messages |
---|
| 10 | the application will send. The catch-all method is onMonitorMessage, |
---|
| 11 | which by default will print the messages on the console. If you |
---|
| 12 | don't catch onMonitorLog messages then the log messages will be |
---|
| 13 | sent to the standard python logger. |
---|
| 14 | |
---|
| 15 | See `park.monitor` for details on the message types. |
---|
| 16 | |
---|
| 17 | Example |
---|
| 18 | ======= |
---|
| 19 | |
---|
| 20 | The following defines a panel which responds to monitor messages:: |
---|
| 21 | |
---|
| 22 | import wx |
---|
| 23 | |
---|
| 24 | class Panel(wx.Panel): |
---|
| 25 | def __init__(self, *args, **kw): |
---|
| 26 | wx.Panel.__init__(self, *args, **kw) |
---|
| 27 | self.text = wx.TextCtrl(self, size=(200,100), style=wx.TE_MULTILINE) |
---|
| 28 | self.gauge = wx.Gauge(self, range=100) |
---|
| 29 | sizer = wx.BoxSizer(wx.VERTICAL) |
---|
| 30 | sizer.Add(self.text, 0, wx.LEFT | wx.EXPAND) |
---|
| 31 | sizer.Add(self.gauge, 0, wx.LEFT | wx.EXPAND) |
---|
| 32 | self.SetSizer(sizer) |
---|
| 33 | self.text.SetValue('starting value') |
---|
| 34 | def onMonitorMessage(self, message): |
---|
| 35 | self.text.SetValue(str(message)) |
---|
| 36 | def onMonitorStart(self, message): |
---|
| 37 | self.text.SetValue(str(message)) |
---|
| 38 | self.gauge.SetValue(0) |
---|
| 39 | def onMonitorProgress(self, message): |
---|
| 40 | self.text.SetValue(str(message)) |
---|
| 41 | self.gauge.SetValue(int(100*message.complete/message.total)) |
---|
| 42 | def onMonitorComplete(self, message): |
---|
| 43 | self.text.SetValue(str(message)) |
---|
| 44 | self.gauge.SetValue(100) |
---|
| 45 | |
---|
| 46 | We can put this panel in a simple app:: |
---|
| 47 | |
---|
| 48 | app = wx.PySimpleApp() |
---|
| 49 | frame = wx.Frame(None, -1, 'Test Monitor') |
---|
| 50 | panel = Panel(frame) |
---|
| 51 | frame.Show() |
---|
| 52 | |
---|
| 53 | Next we attach attach the monitor to this panel and feed some messages from |
---|
| 54 | another thread:: |
---|
| 55 | |
---|
| 56 | import time,thread |
---|
| 57 | import park.wxmonitor, park.monitor |
---|
| 58 | from park.monitor import Start, Progress, Improvement, Complete |
---|
| 59 | monitor = park.wxmonitor.wxMonitor(panel) |
---|
| 60 | msgs = [Start(), Progress(1,10), Progress(3,10), |
---|
| 61 | Improvement('Better!'), Progerss(6,10), Complete('Best!')]: |
---|
| 62 | def message_stream(monitor,msgs): |
---|
| 63 | time.sleep(1) |
---|
| 64 | for message in msgs: |
---|
| 65 | monitor.put(message) |
---|
| 66 | time.sleep(1) |
---|
| 67 | thread.start_new_thread(message_stream, (monitor,msgs)) |
---|
| 68 | app.MainLoop() |
---|
| 69 | |
---|
| 70 | You should see the progress bar jump from 10% to 30% to 60% then all the way |
---|
| 71 | to the end. |
---|
| 72 | """ |
---|
| 73 | import logging |
---|
| 74 | import time |
---|
| 75 | |
---|
| 76 | import wx |
---|
| 77 | import wx.lib.newevent |
---|
| 78 | |
---|
| 79 | import park.monitor |
---|
| 80 | |
---|
| 81 | (MonitorEvent, EVT_MONITOR) = wx.lib.newevent.NewEvent() |
---|
| 82 | |
---|
| 83 | # For wx on Mac OS X we need to sleep after posting a message from |
---|
| 84 | # a thread in order to give the GUI a chance to update itself. |
---|
| 85 | SLEEP_TIME = 0.01 |
---|
| 86 | class wxMonitor(park.monitor.Monitor): |
---|
| 87 | """ |
---|
| 88 | Attach a job monitor to a panel. |
---|
| 89 | |
---|
| 90 | The monitor will perform callbacks to onMonitorStart(message), |
---|
| 91 | onMonitorProgress(message), etc. if the associated method is |
---|
| 92 | defined. If the type specific method is not defined, then the |
---|
| 93 | monitor will call onMonitorMessage(message). Otherwise the |
---|
| 94 | message is dropped. |
---|
| 95 | |
---|
| 96 | See `park.monitor` for a description of the usual messages. |
---|
| 97 | """ |
---|
| 98 | def __init__(self, win): |
---|
| 99 | """ |
---|
| 100 | Window to receive the monitoring events. This is running in the |
---|
| 101 | GUI thread. |
---|
| 102 | """ |
---|
| 103 | self.win = win |
---|
| 104 | win.Bind(EVT_MONITOR, self.dispatch) |
---|
| 105 | |
---|
| 106 | def put(self, message): |
---|
| 107 | """ |
---|
| 108 | Intercept an event received from an asynchronous monitor. This is |
---|
| 109 | running in the asynchronous thread. |
---|
| 110 | """ |
---|
| 111 | #print "dispatch",message |
---|
| 112 | event = MonitorEvent(message=message) |
---|
| 113 | wx.PostEvent(self.win, event) |
---|
| 114 | time.sleep(SLEEP_TIME) |
---|
| 115 | |
---|
| 116 | def dispatch(self, event): |
---|
| 117 | """ |
---|
| 118 | Dispatch the event from the asynchronous monitor. This is running |
---|
| 119 | in the GUI thread. |
---|
| 120 | """ |
---|
| 121 | message = event.message |
---|
| 122 | #print "window dispatch",message |
---|
| 123 | |
---|
| 124 | # First check for a handler in the monitor window |
---|
| 125 | fn = getattr(self.win, 'onMonitor'+message.__class__.__name__, None) |
---|
| 126 | # If none, then check in our class (we have a default onMonitorLog) |
---|
| 127 | if fn is None: |
---|
| 128 | fn = getattr(self, 'onMonitor'+message.__class__.__name__, None) |
---|
| 129 | # If still none, then look for the generic handler |
---|
| 130 | if fn is None: |
---|
| 131 | fn = getattr(self.win, 'onMonitorMessage', self.onMonitorMessage) |
---|
| 132 | # Process the message |
---|
| 133 | fn(message) |
---|
| 134 | |
---|
| 135 | def onMonitorMessage(self, message): |
---|
| 136 | """ |
---|
| 137 | Generic message handler: do nothing. |
---|
| 138 | """ |
---|
| 139 | print ">",str(message) |
---|
| 140 | |
---|
| 141 | def onMonitorLog(self, message): |
---|
| 142 | """ |
---|
| 143 | Called when the job sends a logging record. |
---|
| 144 | |
---|
| 145 | The logging record contains a normal python logging record. |
---|
| 146 | |
---|
| 147 | The default behaviour is to tie into the application logging |
---|
| 148 | system using:: |
---|
| 149 | |
---|
| 150 | logger = logging.getLogger(message.record.name) |
---|
| 151 | logger.handle(message.record) |
---|
| 152 | |
---|
| 153 | Logging levels are set in the job controller. |
---|
| 154 | """ |
---|
| 155 | logging.basicConfig() |
---|
| 156 | logger = logging.getLogger(message.record.name) |
---|
| 157 | logger.handle(message.record) |
---|
| 158 | |
---|
| 159 | |
---|
| 160 | def demo(rate=0): |
---|
| 161 | import thread |
---|
| 162 | import time |
---|
| 163 | import sys |
---|
| 164 | import logging |
---|
| 165 | |
---|
| 166 | class Panel(wx.Panel): |
---|
| 167 | def __init__(self, *args, **kw): |
---|
| 168 | wx.Panel.__init__(self, *args, **kw) |
---|
| 169 | self.text = wx.TextCtrl(self, size=(200,100), style=wx.TE_MULTILINE) |
---|
| 170 | self.gauge = wx.Gauge(self, range=100) |
---|
| 171 | sizer = wx.BoxSizer(wx.VERTICAL) |
---|
| 172 | sizer.Add(self.text, 0, wx.LEFT | wx.EXPAND) |
---|
| 173 | sizer.Add(self.gauge, 0, wx.LEFT | wx.EXPAND) |
---|
| 174 | self.SetSizer(sizer) |
---|
| 175 | self.text.SetValue('starting value') |
---|
| 176 | def onMonitorMessage(self, message): |
---|
| 177 | self.text.SetValue(str(message)) |
---|
| 178 | def onMonitorStart(self, message): |
---|
| 179 | self.text.SetValue(str(message)) |
---|
| 180 | self.gauge.SetValue(0) |
---|
| 181 | def onMonitorProgress(self, message): |
---|
| 182 | self.text.SetValue(str(message)) |
---|
| 183 | self.gauge.SetValue(int(100*message.complete/message.total)) |
---|
| 184 | def onMonitorComplete(self, message): |
---|
| 185 | self.text.SetValue(str(message)) |
---|
| 186 | self.gauge.SetValue(100) |
---|
| 187 | |
---|
| 188 | app = wx.PySimpleApp() |
---|
| 189 | frame = wx.Frame(None, -1, 'Test Monitor') |
---|
| 190 | panel = Panel(frame) |
---|
| 191 | frame.Show() |
---|
| 192 | monitor = wxMonitor(panel) |
---|
| 193 | |
---|
| 194 | def messagestream(monitor,rate,stream): |
---|
| 195 | for m in stream: |
---|
| 196 | time.sleep(rate) |
---|
| 197 | monitor.put(m) |
---|
| 198 | time.sleep(rate) |
---|
| 199 | wx.CallAfter(wx.Exit) |
---|
| 200 | R = logging.LogRecord('hi',60,'hello.py',3,'log message',(),None,'here') |
---|
| 201 | try: raise Exception('Test exception') |
---|
| 202 | except: trace = sys.exc_info() |
---|
| 203 | stream=[park.monitor.Start(), |
---|
| 204 | park.monitor.Progress(1,10), |
---|
| 205 | park.monitor.Progress(2,10), |
---|
| 206 | park.monitor.Progress(3,10), |
---|
| 207 | park.monitor.Improvement('Better!'), |
---|
| 208 | park.monitor.Abort('Abandoned'), |
---|
| 209 | park.monitor.Start(), |
---|
| 210 | park.monitor.Progress(1,10,'seconds'), |
---|
| 211 | park.monitor.Improvement('Better!'), |
---|
| 212 | park.monitor.Progress(8,10), |
---|
| 213 | park.monitor.Complete('Best!'), |
---|
| 214 | park.monitor.Start(), |
---|
| 215 | park.monitor.Log(R), |
---|
| 216 | park.monitor.Progress(6,10), |
---|
| 217 | park.monitor.Error(trace)] |
---|
| 218 | thread.start_new_thread(messagestream, (monitor,rate,stream)) |
---|
| 219 | app.MainLoop() |
---|
| 220 | |
---|
| 221 | if __name__ == "__main__": demo(rate=1) |
---|