39 |
39 |
40 from DebugVariableItem import DebugVariableItem |
40 from DebugVariableItem import DebugVariableItem |
41 from DebugVariableTextViewer import DebugVariableTextViewer |
41 from DebugVariableTextViewer import DebugVariableTextViewer |
42 from DebugVariableGraphicViewer import * |
42 from DebugVariableGraphicViewer import * |
43 |
43 |
|
44 MILLISECOND = 1000000 # Number of nanosecond in a millisecond |
|
45 SECOND = 1000 * MILLISECOND # Number of nanosecond in a second |
|
46 MINUTE = 60 * SECOND # Number of nanosecond in a minute |
|
47 HOUR = 60 * MINUTE # Number of nanosecond in a hour |
|
48 DAY = 24 * HOUR # Number of nanosecond in a day |
|
49 |
|
50 # List of values possible for graph range |
|
51 # Format is [(time_in_plain_text, value_in_nanosecond),...] |
|
52 RANGE_VALUES = \ |
|
53 [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \ |
|
54 [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ |
|
55 [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ |
|
56 [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)] |
|
57 |
|
58 # Scrollbar increment in pixel |
|
59 SCROLLBAR_UNIT = 10 |
|
60 |
|
61 def compute_mask(x, y): |
|
62 return [(xp if xp == yp else "*") |
|
63 for xp, yp in zip(x, y)] |
|
64 |
|
65 def NextTick(variables): |
|
66 next_tick = None |
|
67 for item, data in variables: |
|
68 if len(data) == 0: |
|
69 continue |
|
70 |
|
71 next = (data[0][0] |
|
72 if next_tick is None |
|
73 else min(next_tick, data[0][0])) |
|
74 |
|
75 return next_tick |
|
76 |
|
77 #------------------------------------------------------------------------------- |
|
78 # Debug Variable Graphic Panel Drop Target |
|
79 #------------------------------------------------------------------------------- |
|
80 |
|
81 """ |
|
82 Class that implements a custom drop target class for Debug Variable Graphic |
|
83 Panel |
|
84 """ |
|
85 |
44 class DebugVariableDropTarget(wx.TextDropTarget): |
86 class DebugVariableDropTarget(wx.TextDropTarget): |
45 |
87 |
46 def __init__(self, parent): |
88 def __init__(self, window): |
|
89 """ |
|
90 Constructor |
|
91 @param window: Reference to the Debug Variable Panel |
|
92 """ |
47 wx.TextDropTarget.__init__(self) |
93 wx.TextDropTarget.__init__(self) |
48 self.ParentWindow = parent |
94 self.ParentWindow = window |
49 |
95 |
50 def __del__(self): |
96 def __del__(self): |
|
97 """ |
|
98 Destructor |
|
99 """ |
|
100 # Remove reference to Debug Variable Panel |
51 self.ParentWindow = None |
101 self.ParentWindow = None |
52 |
102 |
53 def OnDragOver(self, x, y, d): |
103 def OnDragOver(self, x, y, d): |
|
104 """ |
|
105 Function called when mouse is dragged over Drop Target |
|
106 @param x: X coordinate of mouse pointer |
|
107 @param y: Y coordinate of mouse pointer |
|
108 @param d: Suggested default for return value |
|
109 """ |
|
110 # Signal Debug Variable Panel to refresh highlight giving mouse position |
54 self.ParentWindow.RefreshHighlight(x, y) |
111 self.ParentWindow.RefreshHighlight(x, y) |
55 return wx.TextDropTarget.OnDragOver(self, x, y, d) |
112 return wx.TextDropTarget.OnDragOver(self, x, y, d) |
56 |
113 |
57 def OnDropText(self, x, y, data): |
114 def OnDropText(self, x, y, data): |
|
115 """ |
|
116 Function called when mouse is released in Drop Target |
|
117 @param x: X coordinate of mouse pointer |
|
118 @param y: Y coordinate of mouse pointer |
|
119 @param data: Text associated to drag'n drop |
|
120 """ |
58 message = None |
121 message = None |
|
122 |
|
123 # Check that data is valid regarding DebugVariablePanel |
59 try: |
124 try: |
60 values = eval(data) |
125 values = eval(data) |
61 if not isinstance(values, TupleType): |
126 if not isinstance(values, TupleType): |
62 raise ValueError |
127 raise ValueError |
63 except: |
128 except: |
64 message = _("Invalid value \"%s\" for debug variable")%data |
129 message = _("Invalid value \"%s\" for debug variable")%data |
65 values = None |
130 values = None |
66 |
131 |
|
132 # Display message if data is invalid |
67 if message is not None: |
133 if message is not None: |
68 wx.CallAfter(self.ShowMessage, message) |
134 wx.CallAfter(self.ShowMessage, message) |
69 |
135 |
|
136 # Data contain a reference to a variable to debug |
70 elif values[1] == "debug": |
137 elif values[1] == "debug": |
|
138 |
|
139 # Drag'n Drop is an internal is an internal move inside Debug |
|
140 # Variable Panel |
71 if len(values) > 2 and values[2] == "move": |
141 if len(values) > 2 and values[2] == "move": |
72 self.ParentWindow.MoveValue(values[0]) |
142 self.ParentWindow.MoveValue(values[0]) |
|
143 |
|
144 # Drag'n Drop was initiated by another control of Beremiz |
73 else: |
145 else: |
74 self.ParentWindow.InsertValue(values[0], force=True) |
146 self.ParentWindow.InsertValue(values[0], force=True) |
75 |
147 |
76 def OnLeave(self): |
148 def OnLeave(self): |
|
149 """ |
|
150 Function called when mouse is leave Drop Target |
|
151 """ |
|
152 # Signal Debug Variable Panel to reset highlight |
77 self.ParentWindow.ResetHighlight() |
153 self.ParentWindow.ResetHighlight() |
78 return wx.TextDropTarget.OnLeave(self) |
154 return wx.TextDropTarget.OnLeave(self) |
79 |
155 |
80 def ShowMessage(self, message): |
156 def ShowMessage(self, message): |
|
157 """ |
|
158 Show error message in Error Dialog |
|
159 @param message: Error message to display |
|
160 """ |
81 dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) |
161 dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR) |
82 dialog.ShowModal() |
162 dialog.ShowModal() |
83 dialog.Destroy() |
163 dialog.Destroy() |
84 |
164 |
85 MILLISECOND = 1000000 |
165 |
86 SECOND = 1000 * MILLISECOND |
166 #------------------------------------------------------------------------------- |
87 MINUTE = 60 * SECOND |
167 # Debug Variable Graphic Panel Class |
88 HOUR = 60 * MINUTE |
168 #------------------------------------------------------------------------------- |
89 DAY = 24 * HOUR |
169 |
90 |
170 """ |
91 ZOOM_VALUES = map(lambda x:("x %.1f" % x, x), [math.sqrt(2) ** i for i in xrange(8)]) |
171 Class that implements a Viewer that display variable values as a graphs |
92 RANGE_VALUES = \ |
172 """ |
93 [("%dms" % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \ |
|
94 [("%ds" % i, i * SECOND) for i in (1, 2, 5, 10, 20, 30)] + \ |
|
95 [("%dm" % i, i * MINUTE) for i in (1, 2, 5, 10, 20, 30)] + \ |
|
96 [("%dh" % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)] |
|
97 |
|
98 SCROLLBAR_UNIT = 10 |
|
99 |
|
100 def compute_mask(x, y): |
|
101 mask = [] |
|
102 for xp, yp in zip(x, y): |
|
103 if xp == yp: |
|
104 mask.append(xp) |
|
105 else: |
|
106 mask.append("*") |
|
107 return mask |
|
108 |
|
109 def NextTick(variables): |
|
110 next_tick = None |
|
111 for item, data in variables: |
|
112 if len(data) > 0: |
|
113 if next_tick is None: |
|
114 next_tick = data[0][0] |
|
115 else: |
|
116 next_tick = min(next_tick, data[0][0]) |
|
117 return next_tick |
|
118 |
173 |
119 class DebugVariableGraphicPanel(wx.Panel, DebugViewer): |
174 class DebugVariableGraphicPanel(wx.Panel, DebugViewer): |
120 |
175 |
121 def __init__(self, parent, producer, window): |
176 def __init__(self, parent, producer, window): |
|
177 """ |
|
178 Constructor |
|
179 @param parent: Reference to the parent wx.Window |
|
180 @param producer: Object receiving debug value and dispatching them to |
|
181 consumers |
|
182 @param window: Reference to Beremiz frame |
|
183 """ |
122 wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL) |
184 wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL) |
123 |
185 |
|
186 # Save Reference to Beremiz frame |
124 self.ParentWindow = window |
187 self.ParentWindow = window |
125 |
188 |
|
189 # Variable storing flag indicating that variable displayed in table |
|
190 # received new value and then table need to be refreshed |
126 self.HasNewData = False |
191 self.HasNewData = False |
|
192 |
|
193 # Variable storing flag indicating that refresh has been forced, and |
|
194 # that next time refresh is possible, it will be done even if no new |
|
195 # data is available |
127 self.Force = False |
196 self.Force = False |
128 |
197 |
129 self.SetBackgroundColour(wx.WHITE) |
198 self.SetBackgroundColour(wx.WHITE) |
130 |
199 |
131 main_sizer = wx.BoxSizer(wx.VERTICAL) |
200 main_sizer = wx.BoxSizer(wx.VERTICAL) |
132 |
201 |
133 self.Ticks = numpy.array([]) |
202 self.Ticks = numpy.array([]) # List of tick received |
134 self.StartTick = 0 |
203 self.StartTick = 0 # Tick starting range of data displayed |
135 self.Fixed = False |
204 self.Fixed = False # Flag that range of data is fixed |
136 self.CursorTick = None |
205 self.CursorTick = None # Tick of cursor for displaying values |
|
206 |
|
207 # |
137 self.DraggingAxesPanel = None |
208 self.DraggingAxesPanel = None |
138 self.DraggingAxesBoundingBox = None |
209 self.DraggingAxesBoundingBox = None |
139 self.DraggingAxesMousePos = None |
210 self.DraggingAxesMousePos = None |
140 self.VetoScrollEvent = False |
211 self.VetoScrollEvent = False |
|
212 |
141 self.VariableNameMask = [] |
213 self.VariableNameMask = [] |
142 |
214 |
143 self.GraphicPanels = [] |
215 self.GraphicPanels = [] |
144 |
216 |
145 graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL) |
217 graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL) |
213 |
285 |
214 DebugViewer.__init__(self, producer, True) |
286 DebugViewer.__init__(self, producer, True) |
215 |
287 |
216 self.SetSizer(main_sizer) |
288 self.SetSizer(main_sizer) |
217 |
289 |
218 def __del__(self): |
|
219 DebugViewer.__del__(self) |
|
220 |
|
221 def SetTickTime(self, ticktime=0): |
290 def SetTickTime(self, ticktime=0): |
|
291 """ |
|
292 Set Ticktime for calculate data range according to time range selected |
|
293 @param ticktime: Ticktime to apply to range (default: 0) |
|
294 """ |
|
295 # Save ticktime |
222 self.Ticktime = ticktime |
296 self.Ticktime = ticktime |
|
297 |
|
298 # Set ticktime to millisecond if undefined |
223 if self.Ticktime == 0: |
299 if self.Ticktime == 0: |
224 self.Ticktime = MILLISECOND |
300 self.Ticktime = MILLISECOND |
|
301 |
|
302 # Calculate range to apply to data |
225 self.CurrentRange = RANGE_VALUES[ |
303 self.CurrentRange = RANGE_VALUES[ |
226 self.CanvasRange.GetSelection()][1] / self.Ticktime |
304 self.CanvasRange.GetSelection()][1] / self.Ticktime |
227 |
305 |
228 def SetDataProducer(self, producer): |
306 def SetDataProducer(self, producer): |
|
307 """ |
|
308 Set Data Producer |
|
309 @param producer: Data Producer |
|
310 """ |
229 DebugViewer.SetDataProducer(self, producer) |
311 DebugViewer.SetDataProducer(self, producer) |
230 |
312 |
|
313 # Set ticktime if data producer is available |
231 if self.DataProducer is not None: |
314 if self.DataProducer is not None: |
232 self.SetTickTime(self.DataProducer.GetTicktime()) |
315 self.SetTickTime(self.DataProducer.GetTicktime()) |
233 |
316 |
234 def RefreshNewData(self, *args, **kwargs): |
317 def RefreshNewData(self, *args, **kwargs): |
|
318 """ |
|
319 Called to refresh Panel according to values received by variables |
|
320 Can receive any parameters (not used here) |
|
321 """ |
|
322 # Refresh graphs if new data is available or refresh is forced |
235 if self.HasNewData or self.Force: |
323 if self.HasNewData or self.Force: |
236 self.HasNewData = False |
324 self.HasNewData = False |
237 self.RefreshView() |
325 self.RefreshView() |
|
326 |
238 DebugViewer.RefreshNewData(self, *args, **kwargs) |
327 DebugViewer.RefreshNewData(self, *args, **kwargs) |
239 |
328 |
240 def NewDataAvailable(self, tick, *args, **kwargs): |
329 def NewDataAvailable(self, tick, *args, **kwargs): |
|
330 """ |
|
331 Called by DataProducer for each tick captured or by panel to refresh |
|
332 graphs |
|
333 @param tick: PLC tick captured |
|
334 All other parameters are passed to refresh function |
|
335 """ |
|
336 # If tick given |
241 if tick is not None: |
337 if tick is not None: |
|
338 |
|
339 # Save tick as start tick for range if data is still empty |
242 if len(self.Ticks) == 0: |
340 if len(self.Ticks) == 0: |
243 self.StartTick = tick |
341 self.StartTick = tick |
|
342 |
|
343 # Add tick to list of ticks received |
244 self.Ticks = numpy.append(self.Ticks, [tick]) |
344 self.Ticks = numpy.append(self.Ticks, [tick]) |
|
345 |
|
346 # Update start tick for range if range follow ticks received |
245 if not self.Fixed or tick < self.StartTick + self.CurrentRange: |
347 if not self.Fixed or tick < self.StartTick + self.CurrentRange: |
246 self.StartTick = max(self.StartTick, tick - self.CurrentRange) |
348 self.StartTick = max(self.StartTick, tick - self.CurrentRange) |
247 if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange: |
349 |
|
350 # Force refresh if graph is fixed because range of data received |
|
351 # is too small to fill data range selected |
|
352 if self.Fixed and \ |
|
353 self.Ticks[-1] - self.Ticks[0] < self.CurrentRange: |
248 self.Force = True |
354 self.Force = True |
|
355 |
249 DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) |
356 DebugViewer.NewDataAvailable(self, tick, *args, **kwargs) |
250 |
357 |
251 def ForceRefresh(self): |
358 def ForceRefresh(self): |
|
359 """ |
|
360 Called to force refresh of graphs |
|
361 """ |
252 self.Force = True |
362 self.Force = True |
253 wx.CallAfter(self.NewDataAvailable, None, True) |
363 wx.CallAfter(self.NewDataAvailable, None, True) |
254 |
364 |
255 def RefreshGraphicsSizer(self): |
365 def RefreshGraphicsSizer(self): |
256 self.GraphicsSizer.Clear() |
366 self.GraphicsSizer.Clear() |
666 if item.IsNumVariable() and graph: |
776 if item.IsNumVariable() and graph: |
667 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) |
777 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL) |
668 panel.SetCanvasSize(source_size.width, source_size.height) |
778 panel.SetCanvasSize(source_size.width, source_size.height) |
669 if self.CursorTick is not None: |
779 if self.CursorTick is not None: |
670 panel.SetCursorTick(self.CursorTick) |
780 panel.SetCursorTick(self.CursorTick) |
|
781 |
671 else: |
782 else: |
672 panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item]) |
783 panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item]) |
673 |
784 |
674 self.GraphicPanels.insert(idx, panel) |
785 self.GraphicPanels.insert(idx, panel) |
675 |
786 |
676 if source_panel.ItemsIsEmpty(): |
787 if source_panel.ItemsIsEmpty(): |
677 if source_panel.HasCapture(): |
788 if source_panel.HasCapture(): |
678 source_panel.ReleaseMouse() |
789 source_panel.ReleaseMouse() |
|
790 if isinstance(source_panel, DebugVariableGraphicViewer): |
|
791 source_panel.Destroy() |
679 self.GraphicPanels.remove(source_panel) |
792 self.GraphicPanels.remove(source_panel) |
680 source_panel.Destroy() |
793 |
681 |
|
682 self.ResetVariableNameMask() |
794 self.ResetVariableNameMask() |
683 self.RefreshGraphicsSizer() |
795 self.RefreshGraphicsSizer() |
684 self.ForceRefresh() |
796 self.ForceRefresh() |
685 |
797 |
686 def MergeGraphs(self, source, target_idx, merge_type, force=False): |
798 def MergeGraphs(self, source, target_idx, merge_type, force=False): |