controls/DebugVariablePanel/DebugVariableGraphicPanel.py
changeset 1215 786f2533200a
parent 1214 2ef048b5383c
child 1216 598ff0043ad3
equal deleted inserted replaced
1214:2ef048b5383c 1215:786f2533200a
    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):
   714                 if source_panel is not None:
   826                 if source_panel is not None:
   715                     source_panel.RemoveItem(source_item)
   827                     source_panel.RemoveItem(source_item)
   716                     if source_panel.ItemsIsEmpty():
   828                     if source_panel.ItemsIsEmpty():
   717                         if source_panel.HasCapture():
   829                         if source_panel.HasCapture():
   718                             source_panel.ReleaseMouse()
   830                             source_panel.ReleaseMouse()
       
   831                         if isinstance(source_panel, DebugVariableGraphicViewer):
       
   832                             source_panel.Destroy()
   719                         self.GraphicPanels.remove(source_panel)
   833                         self.GraphicPanels.remove(source_panel)
   720                         source_panel.Destroy()
       
   721             elif (merge_type != graph_type and len(target_panel.Items) == 2):
   834             elif (merge_type != graph_type and len(target_panel.Items) == 2):
   722                 target_panel.RemoveItem(source_item)
   835                 target_panel.RemoveItem(source_item)
   723             else:
   836             else:
   724                 target_panel = None
   837                 target_panel = None
   725                 
   838                 
   743         source_idx = self.GetViewerIndex(source_panel)
   856         source_idx = self.GetViewerIndex(source_panel)
   744         if source_idx is not None:
   857         if source_idx is not None:
   745             
   858             
   746             if item is None:
   859             if item is None:
   747                 source_panel.ClearItems()
   860                 source_panel.ClearItems()
   748                 source_panel.Destroy()
   861                 if isinstance(source_panel, DebugVariableGraphicViewer):
       
   862                     source_panel.Destroy()
   749                 self.GraphicPanels.remove(source_panel)
   863                 self.GraphicPanels.remove(source_panel)
   750                 self.ResetVariableNameMask()
   864                 self.ResetVariableNameMask()
   751                 self.RefreshGraphicsSizer()
   865                 self.RefreshGraphicsSizer()
   752             else:
   866             else:
   753                 source_panel.RemoveItem(item)
   867                 source_panel.RemoveItem(item)
   754                 if source_panel.ItemsIsEmpty():
   868                 if source_panel.ItemsIsEmpty():
   755                     source_panel.Destroy()
   869                     if isinstance(source_panel, DebugVariableGraphicViewer):
       
   870                         source_panel.Destroy()
   756                     self.GraphicPanels.remove(source_panel)
   871                     self.GraphicPanels.remove(source_panel)
   757                     self.ResetVariableNameMask()
   872                     self.ResetVariableNameMask()
   758                     self.RefreshGraphicsSizer()
   873                     self.RefreshGraphicsSizer()
   759             self.ForceRefresh()
   874             self.ForceRefresh()
   760     
   875