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) |
---|