1 import sys
2
3 WXVER = '2.8'
4 WX_GE_2_8 = True
5
6
7 if not hasattr(sys, "frozen"):
8 import wxversion
9 if wxversion.checkInstalled(WXVER):
10 wxversion.select(WXVER)
11
12 else:
13 import wx, webbrowser
14 app = wx.PySimpleApp()
15 retval = wx.MessageBox("This software is optimised for wxPython version %s, but you don't appear to have it installed.\nWould you like to install the latest version?" % WXVER, "wxPython Version Error", style = wx.YES_NO)
16 print "Return valur %s" % retval
17 if(retval == wx.YES):
18 app.MainLoop()
19 webbrowser.open("http://wxPython.org/")
20 sys.exit()
21 WX_GE_2_8 = False
22
23 import wx
24 import wx.html
25 import wx.lib.newevent
26 import wx.lib.colourdb
27 import random
28
29 try:
30 import numpy as N
31
32 except ImportError:
33 errorText = (
34 "The FloatCanvas requires the numpy module, version 1.* \n\n"
35 "You can get info about it at:\n"
36 "http://numpy.scipy.org/\n\n"
37 )
38
39 try:
40 from floatcanvas import NavCanvas, FloatCanvas
41 except ImportError:
42 from wx.lib.floatcanvas import NavCanvas, FloatCanvas
43
44 import thread
45 from mod_braun import *
46 from config import *
47 import braun_help
48
49 (UpdateLineEvent, EVT_UPDATE_GRAPH) = wx.lib.newevent.NewEvent()
53 """Convert paths to the platform-specific separator"""
54 import os
55 st = apply(os.path.join, tuple(path.split('/')))
56
57 if path.startswith('/'):
58 st = '/' + st
59 return st
60
61 if 0:
63 - def __init__(self, MainWindowClass, queue):
64 self.queue = queue
65 self.MainWindowClass = MainWindowClass
66 bmp = wx.Image(opj(SPLASH_PATH)).ConvertToBitmap()
67 style = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT
68 wx.SplashScreen.__init__(self, bmp, style, 3000, None, -1)
69 self.Bind(wx.EVT_CLOSE, self.OnClose)
70 wx.Yield()
71 self.ShowMain()
72
74 self.Hide()
75 evt.Skip()
76
78 frame = self.MainWindowClass(self.queue)
79 frame.Show()
80
82 "Class to display some HTML documentation in a dialog window"
83 text = braun_help.TEXT
85 wx.Dialog.__init__(self, parent, -1, 'Braun help')
86 html = wx.html.HtmlWindow(self, -1, size=(420, -1))
87 py_version = sys.version.split()[0]
88 html.SetPage(self.text)
89 btn = html.FindWindowById(wx.ID_OK)
90 ir = html.GetInternalRepresentation()
91 html.SetSize( (ir.GetWidth()+25, ir.GetHeight()+25) )
92 self.SetClientSize(html.GetSize())
93 self.CentreOnParent(wx.BOTH)
94
96 """
97
98 The SmoothLine class is identical to the Line class except that it uses a
99 GC rather than a DC.
100
101 """
102 - def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
103 Points = WorldToPixel(self.Points)
104 GC = wx.GraphicsContext.Create(dc)
105 c = wx.Color()
106 c.SetFromName(self.LineColor)
107 r,g,b = c.Get()
108 c = wx.Color(r,g,b,LINE_OPACITY)
109 GC.SetPen(wx.Pen(c, self.LineWidth, self.LineStyleList[self.LineStyle]))
110 GC.DrawLines(Points)
111
113 _highest_index = 0
115 self.index = 0
116 self.size = RING_BUFFER_SIZE
117 self.current_value_index = RING_BUFFER_SIZE - 1
118 self.changed = False
119 self.started = False
120 self.__content = [0] * self.size
122 self.index = (self.index + 1) % self.size
123
124 self.current_value_index = self.index
125 self.started = True
126 self.changed = True
127 self.__content[self.index] = value
128 self.set_highest_index()
129 - def maintain(self):
130 self.current_value = self.__content[self.index]
131 self.index = (self.index + 1) % self.size
132
133 self.__content[self.index] = self.current_value
135 self.current_value = self.__content[self.index]
136 self.index = (self.index + 1) % self.size
137 self.current_value_index = self.index
138 self.__content[self.index] = self.current_value
140 self.current_value = self.__content[self.index]
141 while self.index != self.highest_index():
142 self.index = (self.index + 1) % self.size
143 self.current_value_index = self.index
144 self.__content[self.index] = self.current_value
145 self.set_highest_index()
147 self.__content.insert(0, value)
148 self.__content.pop()
150 return self.current_value_index
155
156
158 return self.__content
159
165 event = UpdateLineEvent(id = self.id, value = self.value,\
166 realvalue = self.realvalue)
167 wx.PostEvent(self.frame, event)
168
169 -class MainAppWindow(wx.Frame):
170 '''Standard application main window with file, view and help menus'''
171 - def __init__(self, *args, **kwargs):
172 wx.Frame.__init__(self, style = wx.DEFAULT_FRAME_STYLE, *args, **kwargs)
173 self.SetExtraStyle(wx.FRAME_EX_METAL)
174 self.CreateStatusBar()
175
176 filemenu = wx.Menu()
177 helpmenu = wx.Menu()
178 viewmenu = wx.Menu()
179
180 filemenu.Append(wx.ID_SAVEAS,
181 "Save &As...\tctrl-s", "Save as PNG")
182 filemenu.Append(wx.ID_EXIT,
183 "E&xit\tctrl-q", "Terminate the program")
184
185 helpmenu.Append(wx.ID_ABOUT, "&About Braun...")
186 helpmenu.Append(wx.ID_HELP, "&Help\tctrl-?", "Help using Braun")
187 viewmenu.Append(wx.ID_ZOOM_IN, "Zoom in\tctrl-+")
188 viewmenu.Append(wx.ID_ZOOM_OUT, "Zoom out\tctrl--")
189 viewmenu.Append(ID_ZOOM_TO_FIT, "Zoom to fit\tctrl-0")
190
191 menubar = wx.MenuBar()
192 menubar.Append(filemenu, "&File")
193 menubar.Append(viewmenu, "&View")
194 menubar.Append(helpmenu, "&Help")
195 self.SetMenuBar(menubar)
196
197 wx.EVT_MENU(self, wx.ID_ZOOM_IN, self.on_zoom_in)
198 wx.EVT_MENU(self, wx.ID_ZOOM_OUT, self.on_zoom_out)
199 wx.EVT_MENU(self, ID_ZOOM_TO_FIT, self.on_zoom_to_fit)
200 wx.EVT_MENU(self, wx.ID_ABOUT, self.on_about)
201 wx.EVT_MENU(self, wx.ID_HELP, self.on_help)
202 wx.EVT_MENU(self, wx.ID_CLOSE, self.on_close)
203 wx.EVT_MENU(self, wx.ID_SAVEAS, self.on_saveas)
204 wx.EVT_MENU(self, wx.ID_EXIT, self.on_exit)
205
206 - def on_about(self, event):
207 d = wx.MessageDialog(self,
208 "Copyright Jamie Bullock 2007.\nLicensed under the GNU GPL version 3 or later", "About Braun %s" % VERSION, wx.OK)
209 d.ShowModal()
210 d.Destroy()
211
212 - def on_help(self, event):
213 helptext = ""
214 d = BraunHelp(None)
215 d.ShowModal()
216 d.Destroy()
217
218 - def on_close(self, event):
220
223
224 -class BraunMainWindow(MainAppWindow):
225 - def __init__(self, queue):
226 MainAppWindow.__init__(self, None, wx.ID_ANY, title="Braun",
227 size=(800,600))
228 self.queue = queue
229 self.fps = FPS
230 self.clock = None
231 self.lines = [None] * MAX_LINES
232 self.data = [None] * MAX_LINES
233 self.text_ids = [None] * MAX_LINES
234 self.text_names = [None] * MAX_LINES
235 self.text_values = [None] * MAX_LINES
236 self.real_values = [0.0] * MAX_LINES
237 self.Canvas = None
238 self.done_first_draw = False
239 self.tics = []
240 self.tic_values = [0] * N_TICS
241 self.redraw_tics = False
242 self.redraw_values = False
243 self.tic_max = GLOBAL_MAXIMUM
244 self.tic_min = GLOBAL_MINIMUM
245 self.do_resize = True
246 self.other_frame = False
247
248 wx.lib.colourdb.updateColourDB()
249 colours = wx.lib.colourdb.getColourList()
250 colours.remove('BLACK')
251 self.my_colour_list = ['TOMATO1']
252 for colour in self.my_colour_list:
253 colours.remove(colour)
254 for i, colour in enumerate(colours):
255 if i % 22 == 0 and 'GREY' not in colour:
256 self.my_colour_list.append(colour)
257 assert len(self.my_colour_list) >= MAX_LINES
258
259 self.buffers = [RingBuffer() for i in range(MAX_LINES)]
260
261 self.Bind(EVT_UPDATE_GRAPH, self.on_update)
262 Dispatcher(queue, self).start()
263 FloatCanvas.FloatCanvas.OnSize = self.on_size
264
265 NC = NavCanvas.NavCanvas(self,wx.ID_ANY,(500,300),
266 ProjectionFun = None,
267 Debug = 0,
268 BackgroundColor = "BLACK"
269 )
270
271 self.zoom_factor = 1.0
272 self.Canvas = NC.Canvas
273 self.Canvas.NumBetweenBlits = 1000
274
275 tb = NC.ToolBar
276 StopButton = wx.Button(tb, wx.ID_ANY, "Stop")
277 PlayButton = wx.Button(tb, wx.ID_ANY, "Run")
278 tb.AddSeparator()
279 tb.AddControl(StopButton)
280 tb.AddControl(PlayButton)
281 tb.Realize()
282
283 StopButton.Bind(wx.EVT_BUTTON, self.on_stop)
284 PlayButton.Bind(wx.EVT_BUTTON, self.on_run)
285
286
287 wx.CallAfter(self.hide_show_hack, StopButton)
288 wx.CallAfter(self.hide_show_hack, PlayButton)
289
290 self.Show(True)
291 self.Bind(wx.EVT_TIMER, self.on_timer)
292 self.clock = wx.Timer(self)
293 self.delay = 1000 / self.fps
294 self.run_braun()
295
296 - def hide_show_hack(self, object):
297
298 "This is a complete hack to work around a bug on OS-X"
299 object.Hide()
300 object.Show()
301
302 - def on_saveas(self, event=None):
303 import os
304 dlg = wx.FileDialog(
305 self, message="Save as PNG ...", defaultDir=os.getcwd(),
306 defaultFile="", wildcard="*.png", style=wx.SAVE
307 )
308 if dlg.ShowModal() == wx.ID_OK:
309 path = dlg.GetPath()
310 if not(path[-4:].lower() == ".png"):
311 path = path+".png"
312 self.Canvas.SaveAsImage(path)
313
314 - def on_exit(self, event):
316
317 - def on_timer(self, event):
318 for i,buffer in enumerate(self.buffers):
319 data = buffer.dump()
320 self.data[i][:,1] = movavg(data, SMOOTHING)
321 self.do_resize_zoom()
322 self.draw()
323
324 - def on_stop(self, event):
325 if self.clock is not None:
326 self.clock.Stop()
327
328 - def on_run(self, event):
329 self.redraw_values = True
330 self.run_braun()
331
332 - def on_size(self, event):
333 "Used to override FloatCanvas.OnSize"
334 if self.Canvas is not None:
335 self.Canvas.InitializePanel()
336 self.Canvas.SizeTimer.Start(50, oneShot=True)
337 self.Canvas.ZoomToBB()
338
339 - def on_zoom_to_fit(self, event):
340 self.Canvas.ZoomToBB()
341
342 - def on_zoom_in(self, event):
343 self.zoom_factor = 1.1
344 self.do_zoom()
345
346 - def on_zoom_out(self, event):
347 self.zoom_factor = 0.9
348 self.do_zoom()
349
350 - def on_update(self, event):
351 self.buffers[event.id].append(event.value)
352 for i,buffer in enumerate(self.buffers):
353 if i != event.id:
354 buffer.maintain_as_other()
355 self.real_values[event.id] = event.realvalue
356
358 zoom = self.zoom_factor
359 self.Canvas.Zoom(zoom)
360
361 - def run_braun(self):
362 Canvas = self.Canvas
363 if self.done_first_draw:
364 Canvas.ClearAll(ResetBB=False)
365 self.redraw_tics = True
366 else:
367 Canvas.ClearAll()
368 self.draw_text()
369 self.draw_axis()
370 self.do_resize_zoom()
371 self.update_text()
372 smooth = SMOOTHING - 1
373
374 if not self.done_first_draw:
375 self.time = \
376 GRAPH_WIDTH*N.pi*N.arange(RING_BUFFER_SIZE - \
377 smooth)/float(RING_BUFFER_SIZE - smooth)
378 for i in range(MAX_LINES):
379 self.data[i] = N.zeros((RING_BUFFER_SIZE - smooth, 2))
380 self.data[i][:,0] = self.time
381
382 for i,data in enumerate(self.data):
383 if self.buffers[i].started:
384 if WX_GE_2_8 and ANTIALIASED_LINES:
385 line = SmoothLine(data,
386 LineColor = self.my_colour_list[i],
387 LineWidth = LINE_WIDTH)
388 Canvas.AddObject(line)
389 self.lines[i] = line
390 else:
391 self.lines[i] = Canvas.AddLine(data,
392 LineColor = self.my_colour_list[i],
393 LineWidth = 1)
394
395 self.clock.Start(self.delay)
396 self.do_resize_zoom()
397 Canvas.Draw()
398 self.done_first_draw = True
399
400 - def do_resize_zoom(self):
401 if self.do_resize:
402 self.Canvas.ZoomToBB()
403 self.do_resize = False
404
406 if self.other_frame:
407 self.other_frame = False
408 return True
409 else:
410 self.other_frame = True
411 return False
412
414
415 if self.every_other_frame():
416 self.update_text()
417 for i,buffer in enumerate(self.buffers):
418 if buffer.changed:
419 self.buffers[i].changed = False
420 else:
421 self.buffers[i].maintain_as_current()
422 if self.lines[i] is not None:
423 self.lines[i].SetPoints(self.data[i])
424 elif buffer.started:
425 if WX_GE_2_8 and ANTIALIASED_LINES:
426 line = SmoothLine(self.data[i],
427 LineColor = self.my_colour_list[i],
428 LineWidth = LINE_WIDTH)
429 self.Canvas.AddObject(line)
430 self.lines[i] = line
431 else:
432 self.lines[i] = self.Canvas.AddLine(self.data[i],
433 LineColor = self.my_colour_list[i],
434 LineWidth = 1)
435 self.Canvas.Draw(Force=True)
436
437 - def draw_axis(self):
438 Canvas = self.Canvas
439 tic_x = SCALE_X_OFFSET
440 scale_multiplier = SCALE_MULTIPLIER * TIC_SCALE_MULTIPLIER
441 self.compute_tics()
442 self.tics = []
443 for i, tic_value in enumerate(self.tic_values):
444 tic_pos = i * TIC_DISTANCE
445 tic_y = tic_pos * scale_multiplier + SCALE_Y_OFFSET
446 tic_value = compact_number(tic_value, Y_AXIS_NUMBER_PRECISION)
447 mytic = self.Canvas.AddScaledText(tic_value,
448 (tic_x, tic_y),
449 Size = FONT_SIZE,
450 Color = "Grey",
451 Position = 'cr')
452 self.tics.append(mytic)
453
454
455
456 self.Canvas.AddRectangle((-0.3, -1.2 * SCALE_MULTIPLIER),
457 (GRAPH_WIDTH * N.pi + 1.5, 2.7 * SCALE_MULTIPLIER),
458 LineColor = None)
459
460 - def draw_text(self):
461 x = NAMESPACE_X_OFFSET
462 y = NAMESPACE_Y_OFFSET
463 for i in xrange(MAX_LINES):
464 Point = (x, y)
465 name = namespace[i][:10]
466 self.text_names[i] = \
467 self.Canvas.AddScaledText("%s:" % name, Point,
468 Size = FONT_SIZE, Color = self.my_colour_list[i])
469 x += NAMESPACE_NUMBER_PADDING
470 Point = (x, y)
471 self.text_values[i] = \
472 self.Canvas.AddScaledText("", Point, Size = FONT_SIZE,
473 Color = self.my_colour_list[i])
474 x += NAMESPACE_X_HOPSIZE
475
476 - def do_rescale(self, value):
477 "Rescale Y axis and return true if scale has changed"
478 scale_changed = False
479 if value > self.tic_max:
480 self.tic_max = value
481 scale_changed = True
482 if value < self.tic_min:
483 self.tic_min = value
484 scale_changed = True
485 return scale_changed
486
487 - def compute_tics(self):
488 tic_distance = (self.tic_max - self.tic_min) / float(N_TICS - 1)
489 for i in range(N_TICS):
490 self.tic_values[i] = (i * tic_distance)
491
492 @staticmethod
493 - def oversize_number(value):
494 if value > 10e3 or value < -10e3:
495 return True
496 else:
497 return False
498
499 - def update_text(self):
500 need_compute_tics = False
501 for i,value in enumerate(self.text_values):
502 real_value = self.real_values[i]
503
504
505 if not need_compute_tics:
506 need_compute_tics = self.do_rescale(real_value)
507
508
509 name = namespace[i][:8]
510
511 self.text_names[i].SetText("%s" % name)
512 if self.buffers[i].changed or self.redraw_values:
513 val = compact_number(real_value, 3)
514 value.SetText(val)
515 self.redraw_values = False
516
517 if need_compute_tics:
518 self.compute_tics()
519
520 for i, tic_value in enumerate(self.tic_values):
521 y = self.tic_values[i]
522 y = compact_number(y, Y_AXIS_NUMBER_PRECISION)
523 self.tics[i].SetText(y)
524
526
527 Dispatcher(self.queue, self).stop()
528 self.Destroy()
529
531 ''' returns an n period moving average for the time series s
532
533 s is a list ordered from oldest (index 0) to most recent (index -1)
534 n is an integer
535
536 returns a numeric array of the moving average
537 '''
538
539 s = N.array(s)
540 c = N.cumsum(s)
541 return (c[n-1:] - c[:-n+1]) / float(n)
542
545
546 D = N.array(s)
547 S = D.size
548 A = N.zeros(S-(n-1), float)
549 for j in range(n):
550 A += D[j:S-(n-1)+j]
551 A /= float(n * .05)
552
553 return A
554
556 "Return a number as a string compacted to a given number of digits"
557 import decimal
558 decimal.getcontext().prec = digits
559 x = decimal.Decimal(str(number))
560 x += 0
561 return x.to_eng_string()
562