lbessard@301: #!/usr/bin/env python lbessard@301: # -*- coding: utf-8 -*- lbessard@301: lbessard@301: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor lbessard@301: #based on the plcopen standard. lbessard@301: # lbessard@301: #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD lbessard@301: # lbessard@301: #See COPYING file for copyrights details. lbessard@301: # lbessard@301: #This library is free software; you can redistribute it and/or lbessard@301: #modify it under the terms of the GNU General Public lbessard@301: #License as published by the Free Software Foundation; either lbessard@301: #version 2.1 of the License, or (at your option) any later version. lbessard@301: # lbessard@301: #This library is distributed in the hope that it will be useful, lbessard@301: #but WITHOUT ANY WARRANTY; without even the implied warranty of lbessard@301: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU lbessard@301: #General Public License for more details. lbessard@301: # lbessard@301: #You should have received a copy of the GNU General Public lbessard@301: #License along with this library; if not, write to the Free Software lbessard@301: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA lbessard@301: lbessard@301: import wx lbessard@301: import wx.lib.plot as plot laurent@642: import numpy greg@361: from graphics.GraphicCommons import DebugViewer laurent@586: from controls import EditorPanel lbessard@301: lbessard@301: colours = ['blue', 'red', 'green', 'yellow', 'orange', 'purple', 'brown', 'cyan', lbessard@301: 'pink', 'grey'] lbessard@301: markers = ['circle', 'dot', 'square', 'triangle', 'triangle_down', 'cross', 'plus', 'circle'] lbessard@301: lbessard@301: lbessard@301: #------------------------------------------------------------------------------- lbessard@301: # Debug Variable Graphic Viewer class lbessard@301: #------------------------------------------------------------------------------- lbessard@301: laurent@632: SECOND = 1000000000 laurent@632: MINUTE = 60 * SECOND laurent@632: HOUR = 60 * MINUTE laurent@632: laurent@632: RANGE_VALUES = [(str(25 * 2 ** i), 25 * 2 ** i) for i in xrange(6)] laurent@640: TIME_RANGE_VALUES = [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ laurent@632: [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ laurent@632: [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)] lbessard@301: lbessard@301: [ID_GRAPHICVIEWER, ID_GRAPHICVIEWERCANVAS, lbessard@301: ID_GRAPHICVIEWERCANVASRANGE, ID_GRAPHICVIEWERCANVASPOSITION, lbessard@301: ID_GRAPHICVIEWERRESETBUTTON, ID_GRAPHICVIEWERCURRENTBUTTON, lbessard@301: ID_GRAPHICVIEWERSTATICTEXT1, ID_GRAPHICVIEWERSTATICTEXT2, lbessard@301: ] = [wx.NewId() for _init_ctrls in range(8)] lbessard@301: laurent@586: class GraphicViewer(EditorPanel, DebugViewer): lbessard@301: lbessard@301: def _init_coll_MainGridSizer_Items(self, parent): lbessard@301: # generated method, don't edit lbessard@301: parent.AddWindow(self.Canvas, 0, border=0, flag=wx.GROW) lbessard@301: parent.AddSizer(self.RangeSizer, 0, border=0, flag=wx.GROW) lbessard@301: lbessard@301: def _init_coll_MainGridSizer_Growables(self, parent): lbessard@301: # generated method, don't edit lbessard@301: parent.AddGrowableCol(0) lbessard@301: parent.AddGrowableRow(0) lbessard@301: lbessard@301: def _init_coll_RangeSizer_Items(self, parent): lbessard@301: # generated method, don't edit laurent@391: parent.AddWindow(self.staticbox1, 0, border=5, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL) lbessard@301: parent.AddWindow(self.CanvasRange, 0, border=5, flag=wx.ALL) laurent@391: parent.AddWindow(self.staticText2, 0, border=5, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL) lbessard@301: parent.AddWindow(self.CanvasPosition, 0, border=5, flag=wx.GROW|wx.ALL) lbessard@301: parent.AddWindow(self.ResetButton, 0, border=5, flag=wx.ALL) lbessard@301: parent.AddWindow(self.CurrentButton, 0, border=5, flag=wx.ALL) lbessard@301: lbessard@301: def _init_coll_RangeSizer_Growables(self, parent): lbessard@301: # generated method, don't edit lbessard@301: parent.AddGrowableCol(3) lbessard@301: parent.AddGrowableRow(0) lbessard@301: lbessard@301: def _init_sizers(self): lbessard@301: # generated method, don't edit lbessard@301: self.MainGridSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0) lbessard@301: self.RangeSizer = wx.FlexGridSizer(cols=6, hgap=0, rows=1, vgap=0) lbessard@301: lbessard@301: self._init_coll_MainGridSizer_Items(self.MainGridSizer) lbessard@301: self._init_coll_MainGridSizer_Growables(self.MainGridSizer) lbessard@301: self._init_coll_RangeSizer_Items(self.RangeSizer) lbessard@301: self._init_coll_RangeSizer_Growables(self.RangeSizer) lbessard@301: laurent@586: self.Editor.SetSizer(self.MainGridSizer) laurent@586: laurent@586: def _init_Editor(self, prnt): laurent@586: self.Editor = wx.Panel(prnt, ID_GRAPHICVIEWER, wx.DefaultPosition, lbessard@301: wx.DefaultSize, 0) lbessard@301: lbessard@301: self.Canvas = plot.PlotCanvas(id=ID_GRAPHICVIEWERCANVAS, laurent@586: name='Canvas', parent=self.Editor, pos=wx.Point(0, 0), lbessard@301: size=wx.Size(0, 0), style=0) lbessard@301: def _axisInterval(spec, lower, upper): lbessard@301: if spec == 'border': lbessard@301: if lower == upper: lbessard@301: return lower - 0.5, upper + 0.5 lbessard@301: else: lbessard@301: border = (upper - lower) * 0.05 lbessard@301: return lower - border, upper + border lbessard@301: else: lbessard@301: return plot.PlotCanvas._axisInterval(self.Canvas, spec, lower, upper) lbessard@301: self.Canvas._axisInterval = _axisInterval lbessard@301: self.Canvas.SetYSpec('border') lbessard@301: lbessard@301: self.staticbox1 = wx.StaticText(id=ID_GRAPHICVIEWERSTATICTEXT1, laurent@586: label=_('Range:'), name='staticText1', parent=self.Editor, laurent@391: pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) lbessard@301: lbessard@301: self.CanvasRange = wx.ComboBox(id=ID_GRAPHICVIEWERCANVASRANGE, laurent@586: name='CanvasRange', parent=self.Editor, pos=wx.Point(0, 0), laurent@640: size=wx.Size(100, 28), style=wx.CB_READONLY) lbessard@301: self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, id=ID_GRAPHICVIEWERCANVASRANGE) lbessard@301: lbessard@301: self.staticText2 = wx.StaticText(id=ID_GRAPHICVIEWERSTATICTEXT2, laurent@586: label=_('Position:'), name='staticText2', parent=self.Editor, laurent@391: pos=wx.Point(0, 0), size=wx.DefaultSize, style=0) lbessard@301: lbessard@301: self.CanvasPosition = wx.ScrollBar(id=ID_GRAPHICVIEWERCANVASPOSITION, laurent@586: name='Position', parent=self.Editor, pos=wx.Point(0, 0), lbessard@301: size=wx.Size(0, 16), style=wx.SB_HORIZONTAL) lbessard@301: self.CanvasPosition.SetScrollbar(0, 10, 100, 10) lbessard@301: self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnPositionChanging, lbessard@301: id = ID_GRAPHICVIEWERCANVASPOSITION) lbessard@301: self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, self.OnPositionChanging, lbessard@301: id = ID_GRAPHICVIEWERCANVASPOSITION) lbessard@301: self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, self.OnPositionChanging, lbessard@301: id = ID_GRAPHICVIEWERCANVASPOSITION) lbessard@301: self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, self.OnPositionChanging, lbessard@301: id = ID_GRAPHICVIEWERCANVASPOSITION) lbessard@301: self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, self.OnPositionChanging, lbessard@301: id = ID_GRAPHICVIEWERCANVASPOSITION) lbessard@301: lbessard@301: self.ResetButton = wx.Button(id=ID_GRAPHICVIEWERRESETBUTTON, label='Reset', laurent@586: name='ResetButton', parent=self.Editor, pos=wx.Point(0, 0), lbessard@301: size=wx.Size(72, 24), style=0) lbessard@301: self.Bind(wx.EVT_BUTTON, self.OnResetButton, id=ID_GRAPHICVIEWERRESETBUTTON) lbessard@301: lbessard@301: self.CurrentButton = wx.Button(id=ID_GRAPHICVIEWERCURRENTBUTTON, label='Current', laurent@586: name='CurrentButton', parent=self.Editor, pos=wx.Point(0, 0), lbessard@301: size=wx.Size(72, 24), style=0) lbessard@301: self.Bind(wx.EVT_BUTTON, self.OnCurrentButton, id=ID_GRAPHICVIEWERCURRENTBUTTON) lbessard@301: lbessard@301: self._init_sizers() lbessard@301: b@415: def __init__(self, parent, window, producer, instancepath = ""): laurent@586: EditorPanel.__init__(self, parent, "", window, None) b@415: DebugViewer.__init__(self, producer, True, False) greg@361: lbessard@301: self.InstancePath = instancepath laurent@632: self.RangeValues = None laurent@632: lbessard@301: self.Datas = [] laurent@642: self.StartValue = 0 laurent@642: self.EndValue = 0 laurent@642: self.Fixed = False laurent@632: self.Ticktime = self.DataProducer.GetTicktime() laurent@632: self.RefreshCanvasRange() lbessard@301: greg@361: self.AddDataConsumer(self.InstancePath.upper(), self) lbessard@301: lbessard@338: def __del__(self): greg@361: DebugViewer.__del__(self) greg@361: self.RemoveDataConsumer(self) lbessard@338: laurent@586: def GetTitle(self): laurent@586: if len(self.InstancePath) > 15: laurent@586: return "..." + self.InstancePath[-12:] laurent@586: return self.InstancePath laurent@586: lbessard@338: def ResetView(self): lbessard@338: self.Datas = [] laurent@642: self.StartValue = 0 laurent@642: self.EndValue = 0 laurent@642: self.Fixed = False laurent@632: self.Ticktime = self.DataProducer.GetTicktime() laurent@632: self.RefreshCanvasRange() lbessard@338: self.RefreshView() lbessard@338: laurent@642: def RefreshNewData(self, *args, **kwargs): laurent@642: self.RefreshView(*args, **kwargs) greg@374: DebugViewer.RefreshNewData(self) greg@361: laurent@632: def RefreshCanvasRange(self): laurent@632: if self.Ticktime == 0 and self.RangeValues != RANGE_VALUES: laurent@632: self.RangeValues = RANGE_VALUES laurent@632: self.RangeValues_dict = dict(RANGE_VALUES) laurent@632: self.CanvasRange.Clear() laurent@632: for text, value in RANGE_VALUES: laurent@632: self.CanvasRange.Append(text) laurent@632: self.CanvasRange.SetStringSelection(RANGE_VALUES[0][0]) laurent@632: self.CurrentRange = RANGE_VALUES[0][1] laurent@632: elif self.RangeValues != TIME_RANGE_VALUES: laurent@632: self.RangeValues = TIME_RANGE_VALUES laurent@632: self.RangeValues_dict = dict(TIME_RANGE_VALUES) laurent@632: self.CanvasRange.Clear() laurent@632: for text, value in TIME_RANGE_VALUES: laurent@632: self.CanvasRange.Append(text) laurent@632: self.CanvasRange.SetStringSelection(TIME_RANGE_VALUES[0][0]) laurent@632: self.CurrentRange = TIME_RANGE_VALUES[0][1] / self.Ticktime laurent@632: greg@361: def RefreshView(self, force=True): greg@361: self.Freeze() laurent@642: if force or not self.Fixed: greg@361: var_name = self.InstancePath.split(".")[-1] greg@361: laurent@642: self.VariableGraphic = plot.PolyLine(self.Datas[self.StartValue:self.EndValue + 1], greg@361: legend=var_name, colour=colours[0]) laurent@391: self.GraphicsObject = plot.PlotGraphics([self.VariableGraphic], _("%s Graphics") % var_name, _("Tick"), _("Values")) greg@361: datas_length = len(self.Datas) greg@361: if datas_length > 1: laurent@642: start = self.Datas[self.StartValue][0] lbessard@301: else: greg@361: start = 0. laurent@642: self.Canvas.Draw(self.GraphicsObject, xAxis=(start, start + self.CurrentRange)) lbessard@301: self.RefreshScrollBar() greg@361: self.Thaw() lbessard@326: lbessard@301: def GetInstancePath(self): lbessard@301: return self.InstancePath lbessard@301: lbessard@326: def IsViewing(self, tagname): lbessard@326: return self.InstancePath == tagname lbessard@326: laurent@642: def GetNearestData(self, tick, adjust): laurent@642: ticks = numpy.array(zip(*self.Datas)[0]) laurent@642: new_cursor = numpy.argmin(abs(ticks - tick)) laurent@642: if adjust == -1 and ticks[new_cursor] > tick and new_cursor > 0: laurent@642: new_cursor -= 1 laurent@642: elif adjust == 1 and ticks[new_cursor] < tick and new_cursor < len(self.Datas): laurent@642: new_cursor += 1 laurent@642: return new_cursor laurent@642: edouard@504: def NewValue(self, tick, value, forced=False): greg@361: self.Datas.append((float(tick), {True:1., False:0.}.get(value, float(value)))) laurent@642: if not self.Fixed: laurent@642: while int(self.Datas[self.StartValue][0]) < tick - self.CurrentRange: laurent@642: self.StartValue += 1 laurent@642: self.EndValue += 1 greg@361: self.NewDataAvailable() greg@361: lbessard@301: def RefreshScrollBar(self): laurent@642: if len(self.Datas) > 0: laurent@642: pos = int(self.Datas[self.StartValue][0] - self.Datas[0][0]) laurent@642: range = int(self.Datas[-1][0] - self.Datas[0][0]) laurent@642: else: laurent@642: pos = 0 laurent@642: range = 0 laurent@642: self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange) lbessard@301: lbessard@301: def OnRangeChanged(self, event): lbessard@301: old_range = self.CurrentRange lbessard@301: try: laurent@632: if self.Ticktime == 0: laurent@632: self.CurrentRange = self.RangeValues_dict[self.CanvasRange.GetValue()] laurent@632: else: laurent@632: self.CurrentRange = self.RangeValues_dict[self.CanvasRange.GetValue()] / self.Ticktime lbessard@301: except ValueError, e: lbessard@301: self.CanvasRange.SetValue(str(self.CurrentRange)) laurent@642: if self.Fixed and self.Datas[-1][0] - self.Datas[0][0] < self.CurrentRange: laurent@642: self.Fixed = False laurent@642: if self.Fixed: laurent@642: self.StartValue = min(self.StartValue, self.GetNearestData(self.Datas[-1][0] - self.CurrentRange, -1)) laurent@642: self.EndValue = self.GetNearestData(self.StartValue + self.CurrentRange, 1) laurent@642: else: laurent@642: self.StartValue = self.GetNearestData(self.Datas[-1][0] - self.CurrentRange - 1, -1) laurent@642: self.EndValue = len(self.Datas) - 1 laurent@642: self.NewDataAvailable(True) lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnPositionChanging(self, event): laurent@642: self.StartValue = self.GetNearestData(self.Datas[0][0] + event.GetPosition(), -1) laurent@642: self.EndValue = self.GetNearestData(self.Datas[self.StartValue][0] + self.CurrentRange, 1) laurent@642: self.Fixed = True laurent@642: self.NewDataAvailable(True) lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnResetButton(self, event): laurent@642: self.Fixed = False laurent@642: self.ResteView() lbessard@301: event.Skip() lbessard@301: lbessard@301: def OnCurrentButton(self, event): laurent@642: self.StartValue = self.GetNearestData(self.Datas[-1][0] - self.CurrentRange, -1) laurent@642: self.EndValue = self.GetNearestData(self.Datas[self.StartValue][0] + self.CurrentRange, 1) laurent@642: self.Fixed = False laurent@642: self.NewDataAvailable(True) laurent@642: event.Skip() laurent@642: