controls/DebugVariablePanel/DebugVariablePanel.py
changeset 1784 64beb9e9c749
parent 1783 3311eea28d56
child 1828 396da88d7b5c
equal deleted inserted replaced
1729:31e63e25b4cc 1784:64beb9e9c749
    28 
    28 
    29 import wx
    29 import wx
    30 import wx.lib.buttons
    30 import wx.lib.buttons
    31 
    31 
    32 import matplotlib
    32 import matplotlib
    33 matplotlib.use('WX')
    33 matplotlib.use('WX')   # noqa
    34 import matplotlib.pyplot
    34 import matplotlib.pyplot
    35 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
    35 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
       
    36 
    36 
    37 
    37 from editors.DebugViewer import DebugViewer
    38 from editors.DebugViewer import DebugViewer
    38 from util.BitmapLibrary import GetBitmap
    39 from util.BitmapLibrary import GetBitmap
    39 
    40 
    40 from DebugVariableItem import DebugVariableItem
    41 from DebugVariableItem import DebugVariableItem
    41 from DebugVariableTextViewer import DebugVariableTextViewer
    42 from DebugVariableTextViewer import DebugVariableTextViewer
    42 from DebugVariableGraphicViewer import *
    43 from DebugVariableGraphicViewer import *
    43 
    44 
    44 MILLISECOND = 1000000       # Number of nanosecond in a millisecond
    45 
    45 SECOND = 1000 * MILLISECOND # Number of nanosecond in a second
    46 MILLISECOND = 1000000        # Number of nanosecond in a millisecond
    46 MINUTE = 60 * SECOND        # Number of nanosecond in a minute
    47 SECOND = 1000 * MILLISECOND  # Number of nanosecond in a second
    47 HOUR = 60 * MINUTE          # Number of nanosecond in a hour
    48 MINUTE = 60 * SECOND         # Number of nanosecond in a minute
    48 DAY = 24 * HOUR             # Number of nanosecond in a day
    49 HOUR = 60 * MINUTE           # Number of nanosecond in a hour
       
    50 DAY = 24 * HOUR              # Number of nanosecond in a day
    49 
    51 
    50 # List of values possible for graph range
    52 # List of values possible for graph range
    51 # Format is [(time_in_plain_text, value_in_nanosecond),...]
    53 # Format is [(time_in_plain_text, value_in_nanosecond),...]
    52 RANGE_VALUES = \
    54 RANGE_VALUES = \
    53     [(_("%dms") % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
    55     [(_("%dms") % i, i * MILLISECOND) for i in (10, 20, 50, 100, 200, 500)] + \
    56     [(_("%dh") % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
    58     [(_("%dh") % i, i * HOUR) for i in (1, 2, 3, 6, 12, 24)]
    57 
    59 
    58 # Scrollbar increment in pixel
    60 # Scrollbar increment in pixel
    59 SCROLLBAR_UNIT = 10
    61 SCROLLBAR_UNIT = 10
    60 
    62 
       
    63 
    61 def compute_mask(x, y):
    64 def compute_mask(x, y):
    62     return [(xp if xp == yp else "*")
    65     return [(xp if xp == yp else "*")
    63             for xp, yp in zip(x, y)]
    66             for xp, yp in zip(x, y)]
       
    67 
    64 
    68 
    65 def NextTick(variables):
    69 def NextTick(variables):
    66     next_tick = None
    70     next_tick = None
    67     for item, data in variables:
    71     for item, data in variables:
    68         if len(data) == 0:
    72         if len(data) == 0:
    69             continue
    73             continue
    70         
    74 
    71         next_tick = (data[0][0]
    75         next_tick = (data[0][0]
    72                      if next_tick is None
    76                      if next_tick is None
    73                      else min(next_tick, data[0][0]))
    77                      else min(next_tick, data[0][0]))
    74     
    78 
    75     return next_tick
    79     return next_tick
    76 
    80 
    77 #-------------------------------------------------------------------------------
    81 # -------------------------------------------------------------------------------
    78 #                    Debug Variable Graphic Panel Drop Target
    82 #                    Debug Variable Graphic Panel Drop Target
    79 #-------------------------------------------------------------------------------
    83 # -------------------------------------------------------------------------------
    80 
    84 
    81 """
       
    82 Class that implements a custom drop target class for Debug Variable Graphic
       
    83 Panel
       
    84 """
       
    85 
    85 
    86 class DebugVariableDropTarget(wx.TextDropTarget):
    86 class DebugVariableDropTarget(wx.TextDropTarget):
    87     
    87     """
       
    88     Class that implements a custom drop target class for Debug Variable Graphic
       
    89     Panel
       
    90     """
       
    91 
    88     def __init__(self, window):
    92     def __init__(self, window):
    89         """
    93         """
    90         Constructor
    94         Constructor
    91         @param window: Reference to the Debug Variable Panel
    95         @param window: Reference to the Debug Variable Panel
    92         """
    96         """
    93         wx.TextDropTarget.__init__(self)
    97         wx.TextDropTarget.__init__(self)
    94         self.ParentWindow = window
    98         self.ParentWindow = window
    95     
    99 
    96     def __del__(self):
   100     def __del__(self):
    97         """
   101         """
    98         Destructor
   102         Destructor
    99         """
   103         """
   100         # Remove reference to Debug Variable Panel
   104         # Remove reference to Debug Variable Panel
   101         self.ParentWindow = None
   105         self.ParentWindow = None
   102     
   106 
   103     def OnDragOver(self, x, y, d):
   107     def OnDragOver(self, x, y, d):
   104         """
   108         """
   105         Function called when mouse is dragged over Drop Target
   109         Function called when mouse is dragged over Drop Target
   106         @param x: X coordinate of mouse pointer
   110         @param x: X coordinate of mouse pointer
   107         @param y: Y coordinate of mouse pointer
   111         @param y: Y coordinate of mouse pointer
   108         @param d: Suggested default for return value
   112         @param d: Suggested default for return value
   109         """
   113         """
   110        # Signal Debug Variable Panel to refresh highlight giving mouse position
   114         # Signal Debug Variable Panel to refresh highlight giving mouse position
   111         self.ParentWindow.RefreshHighlight(x, y)
   115         self.ParentWindow.RefreshHighlight(x, y)
   112         return wx.TextDropTarget.OnDragOver(self, x, y, d)
   116         return wx.TextDropTarget.OnDragOver(self, x, y, d)
   113         
   117 
   114     def OnDropText(self, x, y, data):
   118     def OnDropText(self, x, y, data):
   115         """
   119         """
   116         Function called when mouse is released in Drop Target
   120         Function called when mouse is released in Drop Target
   117         @param x: X coordinate of mouse pointer
   121         @param x: X coordinate of mouse pointer
   118         @param y: Y coordinate of mouse pointer
   122         @param y: Y coordinate of mouse pointer
   119         @param data: Text associated to drag'n drop
   123         @param data: Text associated to drag'n drop
   120         """
   124         """
   121         # Signal Debug Variable Panel to reset highlight
   125         # Signal Debug Variable Panel to reset highlight
   122         self.ParentWindow.ResetHighlight()
   126         self.ParentWindow.ResetHighlight()
   123         
   127 
   124         message = None
   128         message = None
   125         
   129 
   126         # Check that data is valid regarding DebugVariablePanel
   130         # Check that data is valid regarding DebugVariablePanel
   127         try:
   131         try:
   128             values = eval(data)
   132             values = eval(data)
   129             if not isinstance(values, TupleType):
   133             if not isinstance(values, TupleType):
   130                 raise ValueError
   134                 raise ValueError
   131         except:
   135         except Exception:
   132             message = _("Invalid value \"%s\" for debug variable")%data
   136             message = _("Invalid value \"%s\" for debug variable") % data
   133             values = None
   137             values = None
   134             
   138 
   135         # Display message if data is invalid
   139         # Display message if data is invalid
   136         if message is not None:
   140         if message is not None:
   137             wx.CallAfter(self.ShowMessage, message)
   141             wx.CallAfter(self.ShowMessage, message)
   138         
   142 
   139         # Data contain a reference to a variable to debug
   143         # Data contain a reference to a variable to debug
   140         elif values[1] == "debug":
   144         elif values[1] == "debug":
   141             
   145 
   142             # Drag'n Drop is an internal is an internal move inside Debug
   146             # Drag'n Drop is an internal is an internal move inside Debug
   143             # Variable Panel 
   147             # Variable Panel
   144             if len(values) > 2 and values[2] == "move":
   148             if len(values) > 2 and values[2] == "move":
   145                 self.ParentWindow.MoveValue(values[0])
   149                 self.ParentWindow.MoveValue(values[0])
   146             
   150 
   147             # Drag'n Drop was initiated by another control of Beremiz
   151             # Drag'n Drop was initiated by another control of Beremiz
   148             else:
   152             else:
   149                 self.ParentWindow.InsertValue(values[0], force=True)
   153                 self.ParentWindow.InsertValue(values[0], force=True)
   150     
   154 
   151     def OnLeave(self):
   155     def OnLeave(self):
   152         """
   156         """
   153         Function called when mouse is leave Drop Target
   157         Function called when mouse is leave Drop Target
   154         """
   158         """
   155         # Signal Debug Variable Panel to reset highlight
   159         # Signal Debug Variable Panel to reset highlight
   156         self.ParentWindow.ResetHighlight()
   160         self.ParentWindow.ResetHighlight()
   157         return wx.TextDropTarget.OnLeave(self)
   161         return wx.TextDropTarget.OnLeave(self)
   158     
   162 
   159     def ShowMessage(self, message):
   163     def ShowMessage(self, message):
   160         """
   164         """
   161         Show error message in Error Dialog
   165         Show error message in Error Dialog
   162         @param message: Error message to display
   166         @param message: Error message to display
   163         """
   167         """
   164         dialog = wx.MessageDialog(self.ParentWindow, 
   168         dialog = wx.MessageDialog(self.ParentWindow,
   165                                   message, 
   169                                   message,
   166                                   _("Error"), 
   170                                   _("Error"),
   167                                   wx.OK|wx.ICON_ERROR)
   171                                   wx.OK | wx.ICON_ERROR)
   168         dialog.ShowModal()
   172         dialog.ShowModal()
   169         dialog.Destroy()
   173         dialog.Destroy()
   170 
   174 
   171 
   175 
   172 #-------------------------------------------------------------------------------
       
   173 #                      Debug Variable Graphic Panel Class
       
   174 #-------------------------------------------------------------------------------
       
   175 
       
   176 """
       
   177 Class that implements a Viewer that display variable values as a graphs
       
   178 """
       
   179 
       
   180 class DebugVariablePanel(wx.Panel, DebugViewer):
   176 class DebugVariablePanel(wx.Panel, DebugViewer):
   181     
   177     """
       
   178     Class that implements a Viewer that display variable values as a graphs
       
   179     """
       
   180 
   182     def __init__(self, parent, producer, window):
   181     def __init__(self, parent, producer, window):
   183         """
   182         """
   184         Constructor
   183         Constructor
   185         @param parent: Reference to the parent wx.Window
   184         @param parent: Reference to the parent wx.Window
   186         @param producer: Object receiving debug value and dispatching them to
   185         @param producer: Object receiving debug value and dispatching them to
   187         consumers
   186         consumers
   188         @param window: Reference to Beremiz frame
   187         @param window: Reference to Beremiz frame
   189         """
   188         """
   190         wx.Panel.__init__(self, parent, style=wx.SP_3D|wx.TAB_TRAVERSAL)
   189         wx.Panel.__init__(self, parent, style=wx.SP_3D | wx.TAB_TRAVERSAL)
   191         
   190 
   192         # Save Reference to Beremiz frame
   191         # Save Reference to Beremiz frame
   193         self.ParentWindow = window
   192         self.ParentWindow = window
   194         
   193 
   195         # Variable storing flag indicating that variable displayed in table
   194         # Variable storing flag indicating that variable displayed in table
   196         # received new value and then table need to be refreshed
   195         # received new value and then table need to be refreshed
   197         self.HasNewData = False
   196         self.HasNewData = False
   198         
   197 
   199         # Variable storing flag indicating that refresh has been forced, and
   198         # Variable storing flag indicating that refresh has been forced, and
   200         # that next time refresh is possible, it will be done even if no new
   199         # that next time refresh is possible, it will be done even if no new
   201         # data is available
   200         # data is available
   202         self.Force = False
   201         self.Force = False
   203         
   202 
   204         self.SetBackgroundColour(wx.WHITE)
   203         self.SetBackgroundColour(wx.WHITE)
   205         
   204 
   206         main_sizer = wx.BoxSizer(wx.VERTICAL)
   205         main_sizer = wx.BoxSizer(wx.VERTICAL)
   207         
   206 
   208         self.Ticks = numpy.array([]) # List of tick received
   207         self.Ticks = numpy.array([])  # List of tick received
   209         self.StartTick = 0           # Tick starting range of data displayed
   208         self.StartTick = 0            # Tick starting range of data displayed
   210         self.Fixed = False           # Flag that range of data is fixed
   209         self.Fixed = False            # Flag that range of data is fixed
   211         self.CursorTick = None       # Tick of cursor for displaying values
   210         self.CursorTick = None        # Tick of cursor for displaying values
   212         
   211 
   213         self.DraggingAxesPanel = None
   212         self.DraggingAxesPanel = None
   214         self.DraggingAxesBoundingBox = None
   213         self.DraggingAxesBoundingBox = None
   215         self.DraggingAxesMousePos = None
   214         self.DraggingAxesMousePos = None
   216         self.VetoScrollEvent = False
   215         self.VetoScrollEvent = False
   217         
   216 
   218         self.VariableNameMask = []
   217         self.VariableNameMask = []
   219         
   218 
   220         self.GraphicPanels = []
   219         self.GraphicPanels = []
   221         
   220 
   222         graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
   221         graphics_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
   223         main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW|wx.ALL)
   222         main_sizer.AddSizer(graphics_button_sizer, border=5, flag=wx.GROW | wx.ALL)
   224         
   223 
   225         range_label = wx.StaticText(self, label=_('Range:'))
   224         range_label = wx.StaticText(self, label=_('Range:'))
   226         graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
   225         graphics_button_sizer.AddWindow(range_label, flag=wx.ALIGN_CENTER_VERTICAL)
   227         
   226 
   228         self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
   227         self.CanvasRange = wx.ComboBox(self, style=wx.CB_READONLY)
   229         self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
   228         self.Bind(wx.EVT_COMBOBOX, self.OnRangeChanged, self.CanvasRange)
   230         graphics_button_sizer.AddWindow(self.CanvasRange, 1, 
   229         graphics_button_sizer.AddWindow(self.CanvasRange, 1,
   231               border=5, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
   230                                         border=5,
   232         
   231                                         flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL)
       
   232 
   233         self.CanvasRange.Clear()
   233         self.CanvasRange.Clear()
   234         default_range_idx = 0
   234         default_range_idx = 0
   235         for idx, (text, value) in enumerate(RANGE_VALUES):
   235         for idx, (text, value) in enumerate(RANGE_VALUES):
   236             self.CanvasRange.Append(text)
   236             self.CanvasRange.Append(text)
   237             if text == "1s":
   237             if text == "1s":
   238                 default_range_idx = idx
   238                 default_range_idx = idx
   239         self.CanvasRange.SetSelection(default_range_idx)
   239         self.CanvasRange.SetSelection(default_range_idx)
   240         
   240 
   241         for name, bitmap, help in [
   241         for name, bitmap, help in [
   242             ("CurrentButton", "current", _("Go to current value")),
   242                 ("CurrentButton",     "current",      _("Go to current value")),
   243             ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
   243                 ("ExportGraphButton", "export_graph", _("Export graph values to clipboard"))]:
   244             button = wx.lib.buttons.GenBitmapButton(self, 
   244             button = wx.lib.buttons.GenBitmapButton(
   245                   bitmap=GetBitmap(bitmap), 
   245                 self, bitmap=GetBitmap(bitmap),
   246                   size=wx.Size(28, 28), style=wx.NO_BORDER)
   246                 size=wx.Size(28, 28), style=wx.NO_BORDER)
   247             button.SetToolTipString(help)
   247             button.SetToolTipString(help)
   248             setattr(self, name, button)
   248             setattr(self, name, button)
   249             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
   249             self.Bind(wx.EVT_BUTTON, getattr(self, "On" + name), button)
   250             graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
   250             graphics_button_sizer.AddWindow(button, border=5, flag=wx.LEFT)
   251         
   251 
   252         self.CanvasPosition = wx.ScrollBar(self, 
   252         self.CanvasPosition = wx.ScrollBar(
   253               size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
   253             self, size=wx.Size(0, 16), style=wx.SB_HORIZONTAL)
   254         self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK, 
   254         self.CanvasPosition.Bind(wx.EVT_SCROLL_THUMBTRACK,
   255               self.OnPositionChanging, self.CanvasPosition)
   255                                  self.OnPositionChanging, self.CanvasPosition)
   256         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP, 
   256         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEUP,
   257               self.OnPositionChanging, self.CanvasPosition)
   257                                  self.OnPositionChanging, self.CanvasPosition)
   258         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN, 
   258         self.CanvasPosition.Bind(wx.EVT_SCROLL_LINEDOWN,
   259               self.OnPositionChanging, self.CanvasPosition)
   259                                  self.OnPositionChanging, self.CanvasPosition)
   260         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP, 
   260         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEUP,
   261               self.OnPositionChanging, self.CanvasPosition)
   261                                  self.OnPositionChanging, self.CanvasPosition)
   262         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN, 
   262         self.CanvasPosition.Bind(wx.EVT_SCROLL_PAGEDOWN,
   263               self.OnPositionChanging, self.CanvasPosition)
   263                                  self.OnPositionChanging, self.CanvasPosition)
   264         main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
   264         main_sizer.AddWindow(self.CanvasPosition, border=5, flag=wx.GROW | wx.LEFT | wx.RIGHT | wx.BOTTOM)
   265         
   265 
   266         self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
   266         self.TickSizer = wx.BoxSizer(wx.HORIZONTAL)
   267         main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL|wx.GROW)
   267         main_sizer.AddSizer(self.TickSizer, border=5, flag=wx.ALL | wx.GROW)
   268         
   268 
   269         self.TickLabel = wx.StaticText(self)
   269         self.TickLabel = wx.StaticText(self)
   270         self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
   270         self.TickSizer.AddWindow(self.TickLabel, border=5, flag=wx.RIGHT)
   271         
   271 
   272         self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY|wx.TE_CENTER|wx.NO_BORDER)
   272         self.MaskLabel = wx.TextCtrl(self, style=wx.TE_READONLY | wx.TE_CENTER | wx.NO_BORDER)
   273         self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT|wx.GROW)
   273         self.TickSizer.AddWindow(self.MaskLabel, 1, border=5, flag=wx.RIGHT | wx.GROW)
   274         
   274 
   275         self.TickTimeLabel = wx.StaticText(self)
   275         self.TickTimeLabel = wx.StaticText(self)
   276         self.TickSizer.AddWindow(self.TickTimeLabel)
   276         self.TickSizer.AddWindow(self.TickTimeLabel)
   277         
   277 
   278         self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL|wx.VSCROLL)
   278         self.GraphicsWindow = wx.ScrolledWindow(self, style=wx.HSCROLL | wx.VSCROLL)
   279         self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
   279         self.GraphicsWindow.SetBackgroundColour(wx.WHITE)
   280         self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
   280         self.GraphicsWindow.SetDropTarget(DebugVariableDropTarget(self))
   281         self.GraphicsWindow.Bind(wx.EVT_ERASE_BACKGROUND, self.OnGraphicsWindowEraseBackground)
   281         self.GraphicsWindow.Bind(wx.EVT_ERASE_BACKGROUND, self.OnGraphicsWindowEraseBackground)
   282         self.GraphicsWindow.Bind(wx.EVT_PAINT, self.OnGraphicsWindowPaint)
   282         self.GraphicsWindow.Bind(wx.EVT_PAINT, self.OnGraphicsWindowPaint)
   283         self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
   283         self.GraphicsWindow.Bind(wx.EVT_SIZE, self.OnGraphicsWindowResize)
   284         self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
   284         self.GraphicsWindow.Bind(wx.EVT_MOUSEWHEEL, self.OnGraphicsWindowMouseWheel)
   285         
   285 
   286         main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
   286         main_sizer.AddWindow(self.GraphicsWindow, 1, flag=wx.GROW)
   287         
   287 
   288         self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
   288         self.GraphicsSizer = wx.BoxSizer(wx.VERTICAL)
   289         self.GraphicsWindow.SetSizer(self.GraphicsSizer)
   289         self.GraphicsWindow.SetSizer(self.GraphicsSizer)
   290     
   290 
   291         DebugViewer.__init__(self, producer, True)
   291         DebugViewer.__init__(self, producer, True)
   292         
   292 
   293         self.SetSizer(main_sizer)
   293         self.SetSizer(main_sizer)
   294         self.SetTickTime()
   294         self.SetTickTime()
   295     
   295 
   296     def SetTickTime(self, ticktime=0):
   296     def SetTickTime(self, ticktime=0):
   297         """
   297         """
   298         Set Ticktime for calculate data range according to time range selected
   298         Set Ticktime for calculate data range according to time range selected
   299         @param ticktime: Ticktime to apply to range (default: 0)
   299         @param ticktime: Ticktime to apply to range (default: 0)
   300         """
   300         """
   301         # Save ticktime
   301         # Save ticktime
   302         self.Ticktime = ticktime
   302         self.Ticktime = ticktime
   303         
   303 
   304         # Set ticktime to millisecond if undefined
   304         # Set ticktime to millisecond if undefined
   305         if self.Ticktime == 0:
   305         if self.Ticktime == 0:
   306             self.Ticktime = MILLISECOND
   306             self.Ticktime = MILLISECOND
   307         
   307 
   308         # Calculate range to apply to data
   308         # Calculate range to apply to data
   309         self.CurrentRange = RANGE_VALUES[
   309         self.CurrentRange = RANGE_VALUES[
   310             self.CanvasRange.GetSelection()][1] / self.Ticktime
   310             self.CanvasRange.GetSelection()][1] / self.Ticktime
   311     
   311 
   312     def SetDataProducer(self, producer):
   312     def SetDataProducer(self, producer):
   313         """
   313         """
   314         Set Data Producer
   314         Set Data Producer
   315         @param producer: Data Producer
   315         @param producer: Data Producer
   316         """
   316         """
   317         DebugViewer.SetDataProducer(self, producer)
   317         DebugViewer.SetDataProducer(self, producer)
   318         
   318 
   319         # Set ticktime if data producer is available
   319         # Set ticktime if data producer is available
   320         if self.DataProducer is not None:
   320         if self.DataProducer is not None:
   321             self.SetTickTime(self.DataProducer.GetTicktime())
   321             self.SetTickTime(self.DataProducer.GetTicktime())
   322     
   322 
   323     def RefreshNewData(self):
   323     def RefreshNewData(self):
   324         """
   324         """
   325         Called to refresh Panel according to values received by variables
   325         Called to refresh Panel according to values received by variables
   326         Can receive any parameters (not used here)
   326         Can receive any parameters (not used here)
   327         """
   327         """
   328         # Refresh graphs if new data is available or refresh is forced
   328         # Refresh graphs if new data is available or refresh is forced
   329         if self.HasNewData or self.Force:
   329         if self.HasNewData or self.Force:
   330             self.HasNewData = False
   330             self.HasNewData = False
   331             self.RefreshView()
   331             self.RefreshView()
   332         
   332 
   333         DebugViewer.RefreshNewData(self)
   333         DebugViewer.RefreshNewData(self)
   334     
   334 
   335     def NewDataAvailable(self, ticks):
   335     def NewDataAvailable(self, ticks):
   336         """
   336         """
   337         Called by DataProducer for each tick captured or by panel to refresh
   337         Called by DataProducer for each tick captured or by panel to refresh
   338         graphs
   338         graphs
   339         @param tick: PLC tick captured
   339         @param tick: PLC tick captured
   340         All other parameters are passed to refresh function 
   340         All other parameters are passed to refresh function
   341         """
   341         """
   342         # If tick given
   342         # If tick given
   343         if ticks is not None:
   343         if ticks is not None:
   344             tick = ticks[-1]
   344             tick = ticks[-1]
   345             
   345 
   346             # Save tick as start tick for range if data is still empty
   346             # Save tick as start tick for range if data is still empty
   347             if len(self.Ticks) == 0:
   347             if len(self.Ticks) == 0:
   348                 self.StartTick = ticks[0]
   348                 self.StartTick = ticks[0]
   349             
   349 
   350             # Add tick to list of ticks received
   350             # Add tick to list of ticks received
   351             self.Ticks = numpy.append(self.Ticks, ticks)
   351             self.Ticks = numpy.append(self.Ticks, ticks)
   352             
   352 
   353             # Update start tick for range if range follow ticks received
   353             # Update start tick for range if range follow ticks received
   354             if not self.Fixed or tick < self.StartTick + self.CurrentRange:
   354             if not self.Fixed or tick < self.StartTick + self.CurrentRange:
   355                 self.StartTick = max(self.StartTick, tick - self.CurrentRange)
   355                 self.StartTick = max(self.StartTick, tick - self.CurrentRange)
   356             
   356 
   357             # Force refresh if graph is fixed because range of data received
   357             # Force refresh if graph is fixed because range of data received
   358             # is too small to fill data range selected
   358             # is too small to fill data range selected
   359             if self.Fixed and \
   359             if self.Fixed and \
   360                self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
   360                self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
   361                 self.Force = True
   361                 self.Force = True
   362             
   362 
   363             self.HasNewData = False
   363             self.HasNewData = False
   364             self.RefreshView()
   364             self.RefreshView()
   365             
   365 
   366         else:
   366         else:
   367             DebugViewer.NewDataAvailable(self, ticks)
   367             DebugViewer.NewDataAvailable(self, ticks)
   368     
   368 
   369     def ForceRefresh(self):
   369     def ForceRefresh(self):
   370         """
   370         """
   371         Called to force refresh of graphs
   371         Called to force refresh of graphs
   372         """
   372         """
   373         self.Force = True
   373         self.Force = True
   374         wx.CallAfter(self.NewDataAvailable, None)
   374         wx.CallAfter(self.NewDataAvailable, None)
   375     
   375 
   376     def SetCursorTick(self, cursor_tick):
   376     def SetCursorTick(self, cursor_tick):
   377         """
   377         """
   378         Set Cursor for displaying values of items at a tick given
   378         Set Cursor for displaying values of items at a tick given
   379         @param cursor_tick: Tick of cursor
   379         @param cursor_tick: Tick of cursor
   380         """
   380         """
   381         # Save cursor tick
   381         # Save cursor tick
   382         self.CursorTick = cursor_tick
   382         self.CursorTick = cursor_tick
   383         self.Fixed = cursor_tick is not None
   383         self.Fixed = cursor_tick is not None
   384         self.UpdateCursorTick() 
   384         self.UpdateCursorTick()
   385     
   385 
   386     def MoveCursorTick(self, move):
   386     def MoveCursorTick(self, move):
   387         if self.CursorTick is not None:
   387         if self.CursorTick is not None:
   388             cursor_tick = max(self.Ticks[0], 
   388             cursor_tick = max(self.Ticks[0],
   389                           min(self.CursorTick + move, 
   389                               min(self.CursorTick + move, self.Ticks[-1]))
   390                               self.Ticks[-1]))
       
   391             cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
   390             cursor_tick_idx = numpy.argmin(numpy.abs(self.Ticks - cursor_tick))
   392             if self.Ticks[cursor_tick_idx] == self.CursorTick:
   391             if self.Ticks[cursor_tick_idx] == self.CursorTick:
   393                 cursor_tick_idx = max(0, 
   392                 cursor_tick_idx = max(0,
   394                                   min(cursor_tick_idx + abs(move) / move, 
   393                                       min(cursor_tick_idx + abs(move) / move,
   395                                       len(self.Ticks) - 1))
   394                                           len(self.Ticks) - 1))
   396             self.CursorTick = self.Ticks[cursor_tick_idx]
   395             self.CursorTick = self.Ticks[cursor_tick_idx]
   397             self.StartTick = max(self.Ticks[
   396             self.StartTick = max(
   398                                 numpy.argmin(numpy.abs(self.Ticks - 
   397                 self.Ticks[numpy.argmin(
   399                                         self.CursorTick + self.CurrentRange))],
   398                     numpy.abs(self.Ticks - self.CursorTick + self.CurrentRange))],
   400                              min(self.StartTick, self.CursorTick))
   399                 min(self.StartTick, self.CursorTick))
   401             self.RefreshCanvasPosition()
   400             self.RefreshCanvasPosition()
   402             self.UpdateCursorTick() 
   401             self.UpdateCursorTick()
   403             
   402 
   404     def ResetCursorTick(self):
   403     def ResetCursorTick(self):
   405         self.CursorTick = None
   404         self.CursorTick = None
   406         self.Fixed = False
   405         self.Fixed = False
   407         self.UpdateCursorTick()
   406         self.UpdateCursorTick()
   408     
   407 
   409     def UpdateCursorTick(self):
   408     def UpdateCursorTick(self):
   410         for panel in self.GraphicPanels:
   409         for panel in self.GraphicPanels:
   411             if isinstance(panel, DebugVariableGraphicViewer):
   410             if isinstance(panel, DebugVariableGraphicViewer):
   412                 panel.SetCursorTick(self.CursorTick)
   411                 panel.SetCursorTick(self.CursorTick)
   413         self.ForceRefresh()
   412         self.ForceRefresh()
   414     
   413 
   415     def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
   414     def StartDragNDrop(self, panel, item, x_mouse, y_mouse, x_mouse_start, y_mouse_start):
   416         if len(panel.GetItems()) > 1:
   415         if len(panel.GetItems()) > 1:
   417             self.DraggingAxesPanel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   416             self.DraggingAxesPanel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   418             self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
   417             self.DraggingAxesPanel.SetCursorTick(self.CursorTick)
   419             width, height = panel.GetSize()
   418             width, height = panel.GetSize()
   422             self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
   421             self.DraggingAxesPanel.SetPosition(wx.Point(0, -height))
   423         else:
   422         else:
   424             self.DraggingAxesPanel = panel
   423             self.DraggingAxesPanel = panel
   425         self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(parent_coordinate=True)
   424         self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(parent_coordinate=True)
   426         self.DraggingAxesMousePos = wx.Point(
   425         self.DraggingAxesMousePos = wx.Point(
   427             x_mouse_start - self.DraggingAxesBoundingBox.x, 
   426             x_mouse_start - self.DraggingAxesBoundingBox.x,
   428             y_mouse_start - self.DraggingAxesBoundingBox.y)
   427             y_mouse_start - self.DraggingAxesBoundingBox.y)
   429         self.MoveDragNDrop(x_mouse, y_mouse)
   428         self.MoveDragNDrop(x_mouse, y_mouse)
   430         
   429 
   431     def MoveDragNDrop(self, x_mouse, y_mouse):
   430     def MoveDragNDrop(self, x_mouse, y_mouse):
   432         self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
   431         self.DraggingAxesBoundingBox.x = x_mouse - self.DraggingAxesMousePos.x
   433         self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
   432         self.DraggingAxesBoundingBox.y = y_mouse - self.DraggingAxesMousePos.y
   434         self.RefreshHighlight(x_mouse, y_mouse)
   433         self.RefreshHighlight(x_mouse, y_mouse)
   435     
   434 
   436     def RefreshHighlight(self, x_mouse, y_mouse):
   435     def RefreshHighlight(self, x_mouse, y_mouse):
   437         for idx, panel in enumerate(self.GraphicPanels):
   436         for idx, panel in enumerate(self.GraphicPanels):
   438             x, y = panel.GetPosition()
   437             x, y = panel.GetPosition()
   439             width, height = panel.GetSize()
   438             width, height = panel.GetSize()
   440             rect = wx.Rect(x, y, width, height)
   439             rect = wx.Rect(x, y, width, height)
   441             if (rect.InsideXY(x_mouse, y_mouse) or 
   440             if rect.InsideXY(x_mouse, y_mouse) or \
   442                 idx == 0 and y_mouse < 0 or
   441                idx == 0 and y_mouse < 0 or \
   443                 idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]):
   442                idx == len(self.GraphicPanels) - 1 and y_mouse > panel.GetPosition()[1]:
   444                 panel.RefreshHighlight(x_mouse - x, y_mouse - y)
   443                 panel.RefreshHighlight(x_mouse - x, y_mouse - y)
   445             else:
   444             else:
   446                 panel.SetHighlight(HIGHLIGHT_NONE)
   445                 panel.SetHighlight(HIGHLIGHT_NONE)
   447         if wx.Platform == "__WXMSW__":
   446         if wx.Platform == "__WXMSW__":
   448             self.RefreshView()
   447             self.RefreshView()
   449         else:
   448         else:
   450             self.ForceRefresh()
   449             self.ForceRefresh()
   451     
   450 
   452     def ResetHighlight(self):
   451     def ResetHighlight(self):
   453         for panel in self.GraphicPanels:
   452         for panel in self.GraphicPanels:
   454             panel.SetHighlight(HIGHLIGHT_NONE)
   453             panel.SetHighlight(HIGHLIGHT_NONE)
   455         if wx.Platform == "__WXMSW__":
   454         if wx.Platform == "__WXMSW__":
   456             self.RefreshView()
   455             self.RefreshView()
   457         else:
   456         else:
   458             self.ForceRefresh()
   457             self.ForceRefresh()
   459     
   458 
   460     def IsDragging(self):
   459     def IsDragging(self):
   461         return self.DraggingAxesPanel is not None
   460         return self.DraggingAxesPanel is not None
   462     
   461 
   463     def GetDraggingAxesClippingRegion(self, panel):
   462     def GetDraggingAxesClippingRegion(self, panel):
   464         x, y = panel.GetPosition()
   463         x, y = panel.GetPosition()
   465         width, height = panel.GetSize()
   464         width, height = panel.GetSize()
   466         bbox = wx.Rect(x, y, width, height)
   465         bbox = wx.Rect(x, y, width, height)
   467         bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
   466         bbox = bbox.Intersect(self.DraggingAxesBoundingBox)
   468         bbox.x -= x
   467         bbox.x -= x
   469         bbox.y -= y
   468         bbox.y -= y
   470         return bbox
   469         return bbox
   471     
   470 
   472     def GetDraggingAxesPosition(self, panel):
   471     def GetDraggingAxesPosition(self, panel):
   473         x, y = panel.GetPosition()
   472         x, y = panel.GetPosition()
   474         return wx.Point(self.DraggingAxesBoundingBox.x - x,
   473         return wx.Point(self.DraggingAxesBoundingBox.x - x,
   475                         self.DraggingAxesBoundingBox.y - y)
   474                         self.DraggingAxesBoundingBox.y - y)
   476     
   475 
   477     def StopDragNDrop(self, variable, x_mouse, y_mouse):
   476     def StopDragNDrop(self, variable, x_mouse, y_mouse):
   478         if self.DraggingAxesPanel not in self.GraphicPanels:
   477         if self.DraggingAxesPanel not in self.GraphicPanels:
   479             self.DraggingAxesPanel.Destroy()
   478             self.DraggingAxesPanel.Destroy()
   480         self.DraggingAxesPanel = None
   479         self.DraggingAxesPanel = None
   481         self.DraggingAxesBoundingBox = None
   480         self.DraggingAxesBoundingBox = None
   502                     else:
   501                     else:
   503                         if y_mouse > yw + height / 2:
   502                         if y_mouse > yw + height / 2:
   504                             idx += 1
   503                             idx += 1
   505                         wx.CallAfter(self.MoveValue, variable, idx, True)
   504                         wx.CallAfter(self.MoveValue, variable, idx, True)
   506                 self.ForceRefresh()
   505                 self.ForceRefresh()
   507                 return 
   506                 return
   508         width, height = self.GraphicsWindow.GetVirtualSize()
   507         width, height = self.GraphicsWindow.GetVirtualSize()
   509         rect = wx.Rect(0, 0, width, height)
   508         rect = wx.Rect(0, 0, width, height)
   510         if rect.InsideXY(x_mouse, y_mouse):
   509         if rect.InsideXY(x_mouse, y_mouse):
   511             wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
   510             wx.CallAfter(self.MoveValue, variable, len(self.GraphicPanels), True)
   512         self.ForceRefresh()
   511         self.ForceRefresh()
   513     
   512 
   514     def RefreshGraphicsSizer(self):
   513     def RefreshGraphicsSizer(self):
   515         self.GraphicsSizer.Clear()
   514         self.GraphicsSizer.Clear()
   516         
   515 
   517         for panel in self.GraphicPanels:
   516         for panel in self.GraphicPanels:
   518             self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
   517             self.GraphicsSizer.AddWindow(panel, flag=wx.GROW)
   519             
   518 
   520         self.GraphicsSizer.Layout()
   519         self.GraphicsSizer.Layout()
   521         self.RefreshGraphicsWindowScrollbars()
   520         self.RefreshGraphicsWindowScrollbars()
   522     
   521 
   523     def RefreshView(self):
   522     def RefreshView(self):
   524         self.RefreshCanvasPosition()
   523         self.RefreshCanvasPosition()
   525         
   524 
   526         width, height = self.GraphicsWindow.GetVirtualSize()
   525         width, height = self.GraphicsWindow.GetVirtualSize()
   527         bitmap = wx.EmptyBitmap(width, height)
   526         bitmap = wx.EmptyBitmap(width, height)
   528         dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
   527         dc = wx.BufferedDC(wx.ClientDC(self.GraphicsWindow), bitmap)
   529         dc.Clear()
   528         dc.Clear()
   530         dc.BeginDrawing()
   529         dc.BeginDrawing()
   531         if self.DraggingAxesPanel is not None:
   530         if self.DraggingAxesPanel is not None:
   532             destBBox = self.DraggingAxesBoundingBox
   531             destBBox = self.DraggingAxesBoundingBox
   533             srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
   532             srcBBox = self.DraggingAxesPanel.GetAxesBoundingBox()
   534             
   533 
   535             srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
   534             srcBmp = _convert_agg_to_wx_bitmap(self.DraggingAxesPanel.get_renderer(), None)
   536             srcDC = wx.MemoryDC()
   535             srcDC = wx.MemoryDC()
   537             srcDC.SelectObject(srcBmp)
   536             srcDC.SelectObject(srcBmp)
   538                 
   537 
   539             dc.Blit(destBBox.x, destBBox.y, 
   538             dc.Blit(destBBox.x, destBBox.y,
   540                     int(destBBox.width), int(destBBox.height), 
   539                     int(destBBox.width), int(destBBox.height),
   541                     srcDC, srcBBox.x, srcBBox.y)
   540                     srcDC, srcBBox.x, srcBBox.y)
   542         dc.EndDrawing()
   541         dc.EndDrawing()
   543         
   542 
   544         if not self.Fixed or self.Force:
   543         if not self.Fixed or self.Force:
   545             self.Force = False
   544             self.Force = False
   546             refresh_graphics = True
   545             refresh_graphics = True
   547         else:
   546         else:
   548             refresh_graphics = False
   547             refresh_graphics = False
   549         
   548 
   550         if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
   549         if self.DraggingAxesPanel is not None and self.DraggingAxesPanel not in self.GraphicPanels:
   551             self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
   550             self.DraggingAxesPanel.RefreshViewer(refresh_graphics)
   552         for panel in self.GraphicPanels:
   551         for panel in self.GraphicPanels:
   553             if isinstance(panel, DebugVariableGraphicViewer):
   552             if isinstance(panel, DebugVariableGraphicViewer):
   554                 panel.RefreshViewer(refresh_graphics)
   553                 panel.RefreshViewer(refresh_graphics)
   555             else:
   554             else:
   556                 panel.RefreshViewer()
   555                 panel.RefreshViewer()
   557         
   556 
   558         if self.CursorTick is not None:
   557         if self.CursorTick is not None:
   559             tick = self.CursorTick
   558             tick = self.CursorTick
   560         elif len(self.Ticks) > 0:
   559         elif len(self.Ticks) > 0:
   561             tick = self.Ticks[-1]
   560             tick = self.Ticks[-1]
   562         else:
   561         else:
   568             duration = ""
   567             duration = ""
   569             for value, format in [(tick_duration / DAY, _("%dd")),
   568             for value, format in [(tick_duration / DAY, _("%dd")),
   570                                   ((tick_duration % DAY) / HOUR, _("%dh")),
   569                                   ((tick_duration % DAY) / HOUR, _("%dh")),
   571                                   ((tick_duration % HOUR) / MINUTE, _("%dm")),
   570                                   ((tick_duration % HOUR) / MINUTE, _("%dm")),
   572                                   ((tick_duration % MINUTE) / SECOND, _("%ds"))]:
   571                                   ((tick_duration % MINUTE) / SECOND, _("%ds"))]:
   573                 
   572 
   574                 if value > 0 or not_null:
   573                 if value > 0 or not_null:
   575                     duration += format % value
   574                     duration += format % value
   576                     not_null = True
   575                     not_null = True
   577             
   576 
   578             duration += _("%03gms") % (float(tick_duration % SECOND) / MILLISECOND) 
   577             duration += _("%03gms") % (float(tick_duration % SECOND) / MILLISECOND)
   579             self.TickTimeLabel.SetLabel("t: %s" % duration)
   578             self.TickTimeLabel.SetLabel("t: %s" % duration)
   580         else:
   579         else:
   581             self.TickLabel.SetLabel("")
   580             self.TickLabel.SetLabel("")
   582             self.TickTimeLabel.SetLabel("")
   581             self.TickTimeLabel.SetLabel("")
   583         self.TickSizer.Layout()
   582         self.TickSizer.Layout()
   584     
   583 
   585     def SubscribeAllDataConsumers(self):
   584     def SubscribeAllDataConsumers(self):
   586         DebugViewer.SubscribeAllDataConsumers(self)
   585         DebugViewer.SubscribeAllDataConsumers(self)
   587         
   586 
   588         if self.DataProducer is not None:
   587         if self.DataProducer is not None:
   589             if self.DataProducer is not None:
   588             if self.DataProducer is not None:
   590                 self.SetTickTime(self.DataProducer.GetTicktime())
   589                 self.SetTickTime(self.DataProducer.GetTicktime())
   591         
   590 
   592         self.ResetCursorTick()
   591         self.ResetCursorTick()
   593         
   592 
   594         for panel in self.GraphicPanels[:]:
   593         for panel in self.GraphicPanels[:]:
   595             panel.SubscribeAllDataConsumers()
   594             panel.SubscribeAllDataConsumers()
   596             if panel.ItemsIsEmpty():
   595             if panel.ItemsIsEmpty():
   597                 if panel.HasCapture():
   596                 if panel.HasCapture():
   598                     panel.ReleaseMouse()
   597                     panel.ReleaseMouse()
   599                 self.GraphicPanels.remove(panel)
   598                 self.GraphicPanels.remove(panel)
   600                 panel.Destroy()
   599                 panel.Destroy()
   601         
   600 
   602         self.ResetVariableNameMask()
   601         self.ResetVariableNameMask()
   603         self.RefreshGraphicsSizer()
   602         self.RefreshGraphicsSizer()
   604         self.ForceRefresh()
   603         self.ForceRefresh()
   605     
   604 
   606     def ResetView(self):
   605     def ResetView(self):
   607         self.UnsubscribeAllDataConsumers()
   606         self.UnsubscribeAllDataConsumers()
   608         
   607 
   609         self.Fixed = False
   608         self.Fixed = False
   610         for panel in self.GraphicPanels:
   609         for panel in self.GraphicPanels:
   611             panel.Destroy()
   610             panel.Destroy()
   612         self.GraphicPanels = []
   611         self.GraphicPanels = []
   613         self.ResetVariableNameMask()
   612         self.ResetVariableNameMask()
   614         self.RefreshGraphicsSizer()
   613         self.RefreshGraphicsSizer()
   615     
   614 
   616     def SetCanvasPosition(self, tick):
   615     def SetCanvasPosition(self, tick):
   617         tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
   616         tick = max(self.Ticks[0], min(tick, self.Ticks[-1] - self.CurrentRange))
   618         self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
   617         self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - tick))]
   619         self.Fixed = True
   618         self.Fixed = True
   620         self.RefreshCanvasPosition()
   619         self.RefreshCanvasPosition()
   621         self.ForceRefresh()
   620         self.ForceRefresh()
   622     
   621 
   623     def RefreshCanvasPosition(self):
   622     def RefreshCanvasPosition(self):
   624         if len(self.Ticks) > 0:
   623         if len(self.Ticks) > 0:
   625             pos = int(self.StartTick - self.Ticks[0])
   624             pos = int(self.StartTick - self.Ticks[0])
   626             range = int(self.Ticks[-1] - self.Ticks[0])
   625             range = int(self.Ticks[-1] - self.Ticks[0])
   627         else:
   626         else:
   628             pos = 0
   627             pos = 0
   629             range = 0
   628             range = 0
   630         self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
   629         self.CanvasPosition.SetScrollbar(pos, self.CurrentRange, range, self.CurrentRange)
   631     
   630 
   632     def ChangeRange(self, dir, tick=None):
   631     def ChangeRange(self, dir, tick=None):
   633         current_range = self.CurrentRange
   632         current_range = self.CurrentRange
   634         current_range_idx = self.CanvasRange.GetSelection()
   633         current_range_idx = self.CanvasRange.GetSelection()
   635         new_range_idx = max(0, min(current_range_idx + dir, len(RANGE_VALUES) - 1))
   634         new_range_idx = max(0, min(current_range_idx + dir, len(RANGE_VALUES) - 1))
   636         if new_range_idx != current_range_idx:
   635         if new_range_idx != current_range_idx:
   642                 new_start_tick = min(tick - (tick - self.StartTick) * self.CurrentRange / current_range,
   641                 new_start_tick = min(tick - (tick - self.StartTick) * self.CurrentRange / current_range,
   643                                      self.Ticks[-1] - self.CurrentRange)
   642                                      self.Ticks[-1] - self.CurrentRange)
   644                 self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
   643                 self.StartTick = self.Ticks[numpy.argmin(numpy.abs(self.Ticks - new_start_tick))]
   645                 self.Fixed = new_start_tick < self.Ticks[-1] - self.CurrentRange
   644                 self.Fixed = new_start_tick < self.Ticks[-1] - self.CurrentRange
   646             self.ForceRefresh()
   645             self.ForceRefresh()
   647     
   646 
   648     def RefreshRange(self):
   647     def RefreshRange(self):
   649         if len(self.Ticks) > 0:
   648         if len(self.Ticks) > 0:
   650             if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
   649             if self.Fixed and self.Ticks[-1] - self.Ticks[0] < self.CurrentRange:
   651                 self.Fixed = False
   650                 self.Fixed = False
   652             if self.Fixed:
   651             if self.Fixed:
   653                 self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
   652                 self.StartTick = min(self.StartTick, self.Ticks[-1] - self.CurrentRange)
   654             else:
   653             else:
   655                 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
   654                 self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
   656         self.ForceRefresh()
   655         self.ForceRefresh()
   657     
   656 
   658     def OnRangeChanged(self, event):
   657     def OnRangeChanged(self, event):
   659         try:
   658         try:
   660             self.CurrentRange = RANGE_VALUES[self.CanvasRange.GetSelection()][1] / self.Ticktime
   659             self.CurrentRange = RANGE_VALUES[self.CanvasRange.GetSelection()][1] / self.Ticktime
   661         except ValueError, e:
   660         except ValueError, e:
   662             self.CanvasRange.SetValue(str(self.CurrentRange))
   661             self.CanvasRange.SetValue(str(self.CurrentRange))
   663         wx.CallAfter(self.RefreshRange)
   662         wx.CallAfter(self.RefreshRange)
   664         event.Skip()
   663         event.Skip()
   665     
   664 
   666     def OnCurrentButton(self, event):
   665     def OnCurrentButton(self, event):
   667         if len(self.Ticks) > 0:
   666         if len(self.Ticks) > 0:
   668             self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
   667             self.StartTick = max(self.Ticks[0], self.Ticks[-1] - self.CurrentRange)
   669             self.ResetCursorTick()
   668             self.ResetCursorTick()
   670         event.Skip()
   669         event.Skip()
   671     
   670 
   672     def CopyDataToClipboard(self, variables):
   671     def CopyDataToClipboard(self, variables):
   673         text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
   672         text = "tick;%s;\n" % ";".join([item.GetVariable() for item, data in variables])
   674         next_tick = NextTick(variables)
   673         next_tick = NextTick(variables)
   675         while next_tick is not None:
   674         while next_tick is not None:
   676             values = []
   675             values = []
   691                 else:
   690                 else:
   692                     values.append("")
   691                     values.append("")
   693             text += "%d;%s;\n" % (next_tick, ";".join(values))
   692             text += "%d;%s;\n" % (next_tick, ";".join(values))
   694             next_tick = NextTick(variables)
   693             next_tick = NextTick(variables)
   695         self.ParentWindow.SetCopyBuffer(text)
   694         self.ParentWindow.SetCopyBuffer(text)
   696     
   695 
   697     def OnExportGraphButton(self, event):
   696     def OnExportGraphButton(self, event):
   698         items = reduce(lambda x, y: x + y,
   697         items = reduce(lambda x, y: x + y,
   699                        [panel.GetItems() for panel in self.GraphicPanels],
   698                        [panel.GetItems() for panel in self.GraphicPanels],
   700                        [])
   699                        [])
   701         variables = [(item, [entry for entry in item.GetData()])
   700         variables = [(item, [entry for entry in item.GetData()])
   702                      for item in items
   701                      for item in items
   703                      if item.IsNumVariable()]
   702                      if item.IsNumVariable()]
   704         wx.CallAfter(self.CopyDataToClipboard, variables)
   703         wx.CallAfter(self.CopyDataToClipboard, variables)
   705         event.Skip()
   704         event.Skip()
   706     
   705 
   707     def OnPositionChanging(self, event):
   706     def OnPositionChanging(self, event):
   708         if len(self.Ticks) > 0:
   707         if len(self.Ticks) > 0:
   709             self.StartTick = self.Ticks[0] + event.GetPosition()
   708             self.StartTick = self.Ticks[0] + event.GetPosition()
   710             self.Fixed = True
   709             self.Fixed = True
   711             self.ForceRefresh()
   710             self.ForceRefresh()
   712         event.Skip()
   711         event.Skip()
   713     
   712 
   714     def GetRange(self):
   713     def GetRange(self):
   715         return self.StartTick, self.StartTick + self.CurrentRange
   714         return self.StartTick, self.StartTick + self.CurrentRange
   716     
   715 
   717     def GetViewerIndex(self, viewer):
   716     def GetViewerIndex(self, viewer):
   718         if viewer in self.GraphicPanels:
   717         if viewer in self.GraphicPanels:
   719             return self.GraphicPanels.index(viewer)
   718             return self.GraphicPanels.index(viewer)
   720         return None
   719         return None
   721     
   720 
   722     def IsViewerFirst(self, viewer):
   721     def IsViewerFirst(self, viewer):
   723         return viewer == self.GraphicPanels[0]
   722         return viewer == self.GraphicPanels[0]
   724     
   723 
   725     def HighlightPreviousViewer(self, viewer):
   724     def HighlightPreviousViewer(self, viewer):
   726         if self.IsViewerFirst(viewer):
   725         if self.IsViewerFirst(viewer):
   727             return
   726             return
   728         idx = self.GetViewerIndex(viewer)
   727         idx = self.GetViewerIndex(viewer)
   729         if idx is None:
   728         if idx is None:
   730             return
   729             return
   731         self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
   730         self.GraphicPanels[idx-1].SetHighlight(HIGHLIGHT_AFTER)
   732     
   731 
   733     def ResetVariableNameMask(self):
   732     def ResetVariableNameMask(self):
   734         items = []
   733         items = []
   735         for panel in self.GraphicPanels:
   734         for panel in self.GraphicPanels:
   736             items.extend(panel.GetItems())
   735             items.extend(panel.GetItems())
   737         if len(items) > 1:
   736         if len(items) > 1:
   738             self.VariableNameMask = reduce(compute_mask,
   737             self.VariableNameMask = reduce(
   739                 [item.GetVariable().split('.') for item in items])
   738                 compute_mask, [item.GetVariable().split('.') for item in items])
   740         elif len(items) > 0:
   739         elif len(items) > 0:
   741             self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
   740             self.VariableNameMask = items[0].GetVariable().split('.')[:-1] + ['*']
   742         else:
   741         else:
   743             self.VariableNameMask = []
   742             self.VariableNameMask = []
   744         self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
   743         self.MaskLabel.ChangeValue(".".join(self.VariableNameMask))
   745         self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
   744         self.MaskLabel.SetInsertionPoint(self.MaskLabel.GetLastPosition())
   746             
   745 
   747     def GetVariableNameMask(self):
   746     def GetVariableNameMask(self):
   748         return self.VariableNameMask
   747         return self.VariableNameMask
   749     
   748 
   750     def InsertValue(self, iec_path, idx = None, force=False, graph=False):
   749     def InsertValue(self, iec_path, idx=None, force=False, graph=False):
   751         for panel in self.GraphicPanels:
   750         for panel in self.GraphicPanels:
   752             if panel.GetItem(iec_path) is not None:
   751             if panel.GetItem(iec_path) is not None:
   753                 if graph and isinstance(panel, DebugVariableTextViewer):
   752                 if graph and isinstance(panel, DebugVariableTextViewer):
   754                     self.ToggleViewerType(panel)
   753                     self.ToggleViewerType(panel)
   755                 return
   754                 return
   756         if idx is None:
   755         if idx is None:
   757             idx = len(self.GraphicPanels)
   756             idx = len(self.GraphicPanels)
   758         item = DebugVariableItem(self, iec_path, True)
   757         item = DebugVariableItem(self, iec_path, True)
   759         result = self.AddDataConsumer(iec_path.upper(), item, True)
   758         result = self.AddDataConsumer(iec_path.upper(), item, True)
   760         if result is not None or force:
   759         if result is not None or force:
   761             
   760 
   762             self.Freeze()
   761             self.Freeze()
   763             if item.IsNumVariable() and graph:
   762             if item.IsNumVariable() and graph:
   764                 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   763                 panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   765                 if self.CursorTick is not None:
   764                 if self.CursorTick is not None:
   766                     panel.SetCursorTick(self.CursorTick)
   765                     panel.SetCursorTick(self.CursorTick)
   772                 self.GraphicPanels.append(panel)
   771                 self.GraphicPanels.append(panel)
   773             self.ResetVariableNameMask()
   772             self.ResetVariableNameMask()
   774             self.RefreshGraphicsSizer()
   773             self.RefreshGraphicsSizer()
   775             self.Thaw()
   774             self.Thaw()
   776             self.ForceRefresh()
   775             self.ForceRefresh()
   777     
   776 
   778     def MoveValue(self, iec_path, idx = None, graph=False):
   777     def MoveValue(self, iec_path, idx=None, graph=False):
   779         if idx is None:
   778         if idx is None:
   780             idx = len(self.GraphicPanels)
   779             idx = len(self.GraphicPanels)
   781         source_panel = None
   780         source_panel = None
   782         item = None
   781         item = None
   783         for panel in self.GraphicPanels:
   782         for panel in self.GraphicPanels:
   785             if item is not None:
   784             if item is not None:
   786                 source_panel = panel
   785                 source_panel = panel
   787                 break
   786                 break
   788         if source_panel is not None:
   787         if source_panel is not None:
   789             source_panel_idx = self.GraphicPanels.index(source_panel)
   788             source_panel_idx = self.GraphicPanels.index(source_panel)
   790             
   789 
   791             if (len(source_panel.GetItems()) == 1):
   790             if (len(source_panel.GetItems()) == 1):
   792                 
   791 
   793                 if source_panel_idx < idx:
   792                 if source_panel_idx < idx:
   794                     self.GraphicPanels.insert(idx, source_panel)
   793                     self.GraphicPanels.insert(idx, source_panel)
   795                     self.GraphicPanels.pop(source_panel_idx)
   794                     self.GraphicPanels.pop(source_panel_idx)
   796                 elif source_panel_idx > idx:
   795                 elif source_panel_idx > idx:
   797                     self.GraphicPanels.pop(source_panel_idx)
   796                     self.GraphicPanels.pop(source_panel_idx)
   798                     self.GraphicPanels.insert(idx, source_panel)
   797                     self.GraphicPanels.insert(idx, source_panel)
   799                 else:
   798                 else:
   800                     return
   799                     return
   801                 
   800 
   802             else:
   801             else:
   803                 source_panel.RemoveItem(item)
   802                 source_panel.RemoveItem(item)
   804                 source_size = source_panel.GetSize()
   803                 source_size = source_panel.GetSize()
   805                 if item.IsNumVariable() and graph:
   804                 if item.IsNumVariable() and graph:
   806                     panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   805                     panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, [item], GRAPH_PARALLEL)
   807                     panel.SetCanvasHeight(source_size.height)
   806                     panel.SetCanvasHeight(source_size.height)
   808                     if self.CursorTick is not None:
   807                     if self.CursorTick is not None:
   809                         panel.SetCursorTick(self.CursorTick)
   808                         panel.SetCursorTick(self.CursorTick)
   810                 
   809 
   811                 else:
   810                 else:
   812                     panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
   811                     panel = DebugVariableTextViewer(self.GraphicsWindow, self, [item])
   813                 
   812 
   814                 self.GraphicPanels.insert(idx, panel)
   813                 self.GraphicPanels.insert(idx, panel)
   815                 
   814 
   816                 if source_panel.ItemsIsEmpty():
   815                 if source_panel.ItemsIsEmpty():
   817                     if source_panel.HasCapture():
   816                     if source_panel.HasCapture():
   818                         source_panel.ReleaseMouse()
   817                         source_panel.ReleaseMouse()
   819                     source_panel.Destroy()
   818                     source_panel.Destroy()
   820                     self.GraphicPanels.remove(source_panel)
   819                     self.GraphicPanels.remove(source_panel)
   821                 
   820 
   822             self.ResetVariableNameMask()
   821             self.ResetVariableNameMask()
   823             self.RefreshGraphicsSizer()
   822             self.RefreshGraphicsSizer()
   824             self.ForceRefresh()
   823             self.ForceRefresh()
   825     
   824 
   826     def MergeGraphs(self, source, target_idx, merge_type, force=False):
   825     def MergeGraphs(self, source, target_idx, merge_type, force=False):
   827         source_item = None
   826         source_item = None
   828         source_panel = None
   827         source_panel = None
   829         for panel in self.GraphicPanels:
   828         for panel in self.GraphicPanels:
   830             source_item = panel.GetItem(source)
   829             source_item = panel.GetItem(source)
   844                 source_size = None
   843                 source_size = None
   845             target_panel = self.GraphicPanels[target_idx]
   844             target_panel = self.GraphicPanels[target_idx]
   846             graph_type = target_panel.GraphType
   845             graph_type = target_panel.GraphType
   847             if target_panel != source_panel:
   846             if target_panel != source_panel:
   848                 if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
   847                 if (merge_type == GRAPH_PARALLEL and graph_type != merge_type or
   849                     merge_type == GRAPH_ORTHOGONAL and 
   848                     merge_type == GRAPH_ORTHOGONAL and
   850                     (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
   849                     (graph_type == GRAPH_PARALLEL and len(target_panel.Items) > 1 or
   851                      graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
   850                      graph_type == GRAPH_ORTHOGONAL and len(target_panel.Items) >= 3)):
   852                     return
   851                     return
   853                 
   852 
   854                 if source_panel is not None:
   853                 if source_panel is not None:
   855                     source_panel.RemoveItem(source_item)
   854                     source_panel.RemoveItem(source_item)
   856                     if source_panel.ItemsIsEmpty():
   855                     if source_panel.ItemsIsEmpty():
   857                         if source_panel.HasCapture():
   856                         if source_panel.HasCapture():
   858                             source_panel.ReleaseMouse()
   857                             source_panel.ReleaseMouse()
   860                         self.GraphicPanels.remove(source_panel)
   859                         self.GraphicPanels.remove(source_panel)
   861             elif (merge_type != graph_type and len(target_panel.Items) == 2):
   860             elif (merge_type != graph_type and len(target_panel.Items) == 2):
   862                 target_panel.RemoveItem(source_item)
   861                 target_panel.RemoveItem(source_item)
   863             else:
   862             else:
   864                 target_panel = None
   863                 target_panel = None
   865                 
   864 
   866             if target_panel is not None:
   865             if target_panel is not None:
   867                 target_panel.AddItem(source_item)
   866                 target_panel.AddItem(source_item)
   868                 target_panel.GraphType = merge_type
   867                 target_panel.GraphType = merge_type
   869                 size = target_panel.GetSize()
   868                 size = target_panel.GetSize()
   870                 if merge_type == GRAPH_ORTHOGONAL:
   869                 if merge_type == GRAPH_ORTHOGONAL:
   872                 elif source_size is not None and source_panel != target_panel:
   871                 elif source_size is not None and source_panel != target_panel:
   873                     target_panel.SetCanvasHeight(size.height + source_size.height)
   872                     target_panel.SetCanvasHeight(size.height + source_size.height)
   874                 else:
   873                 else:
   875                     target_panel.SetCanvasHeight(size.height)
   874                     target_panel.SetCanvasHeight(size.height)
   876                 target_panel.ResetGraphics()
   875                 target_panel.ResetGraphics()
   877                 
   876 
   878                 self.ResetVariableNameMask()
   877                 self.ResetVariableNameMask()
   879                 self.RefreshGraphicsSizer()
   878                 self.RefreshGraphicsSizer()
   880                 self.ForceRefresh()
   879                 self.ForceRefresh()
   881     
   880 
   882     def DeleteValue(self, source_panel, item=None):
   881     def DeleteValue(self, source_panel, item=None):
   883         source_idx = self.GetViewerIndex(source_panel)
   882         source_idx = self.GetViewerIndex(source_panel)
   884         if source_idx is not None:
   883         if source_idx is not None:
   885             
   884 
   886             if item is None:
   885             if item is None:
   887                 source_panel.ClearItems()
   886                 source_panel.ClearItems()
   888                 source_panel.Destroy()
   887                 source_panel.Destroy()
   889                 self.GraphicPanels.remove(source_panel)
   888                 self.GraphicPanels.remove(source_panel)
   890                 self.ResetVariableNameMask()
   889                 self.ResetVariableNameMask()
   898                     self.RefreshGraphicsSizer()
   897                     self.RefreshGraphicsSizer()
   899             if len(self.GraphicPanels) == 0:
   898             if len(self.GraphicPanels) == 0:
   900                 self.Fixed = False
   899                 self.Fixed = False
   901                 self.ResetCursorTick()
   900                 self.ResetCursorTick()
   902             self.ForceRefresh()
   901             self.ForceRefresh()
   903     
   902 
   904     def ToggleViewerType(self, panel):
   903     def ToggleViewerType(self, panel):
   905         panel_idx = self.GetViewerIndex(panel)
   904         panel_idx = self.GetViewerIndex(panel)
   906         if panel_idx is not None:
   905         if panel_idx is not None:
   907             self.GraphicPanels.remove(panel)
   906             self.GraphicPanels.remove(panel)
   908             items = panel.GetItems()
   907             items = panel.GetItems()
   914                 new_panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
   913                 new_panel = DebugVariableGraphicViewer(self.GraphicsWindow, self, items, GRAPH_PARALLEL)
   915                 self.GraphicPanels.insert(panel_idx, new_panel)
   914                 self.GraphicPanels.insert(panel_idx, new_panel)
   916             panel.Destroy()
   915             panel.Destroy()
   917         self.RefreshGraphicsSizer()
   916         self.RefreshGraphicsSizer()
   918         self.ForceRefresh()
   917         self.ForceRefresh()
   919     
   918 
   920     def ResetGraphicsValues(self):
   919     def ResetGraphicsValues(self):
   921         self.Ticks = numpy.array([])
   920         self.Ticks = numpy.array([])
   922         self.StartTick = 0
   921         self.StartTick = 0
   923         for panel in self.GraphicPanels:
   922         for panel in self.GraphicPanels:
   924             panel.ResetItemsData()
   923             panel.ResetItemsData()
   929         window_size = self.GraphicsWindow.GetClientSize()
   928         window_size = self.GraphicsWindow.GetClientSize()
   930         vwidth, vheight = self.GraphicsSizer.GetMinSize()
   929         vwidth, vheight = self.GraphicsSizer.GetMinSize()
   931         posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
   930         posx = max(0, min(xstart, (vwidth - window_size[0]) / SCROLLBAR_UNIT))
   932         posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
   931         posy = max(0, min(ystart, (vheight - window_size[1]) / SCROLLBAR_UNIT))
   933         self.GraphicsWindow.Scroll(posx, posy)
   932         self.GraphicsWindow.Scroll(posx, posy)
   934         self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
   933         self.GraphicsWindow.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
   935                 vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT, posx, posy)
   934                                           vwidth / SCROLLBAR_UNIT, vheight / SCROLLBAR_UNIT,
   936     
   935                                           posx, posy)
       
   936 
   937     def OnGraphicsWindowEraseBackground(self, event):
   937     def OnGraphicsWindowEraseBackground(self, event):
   938         pass
   938         pass
   939     
   939 
   940     def OnGraphicsWindowPaint(self, event):
   940     def OnGraphicsWindowPaint(self, event):
   941         self.RefreshView()
   941         self.RefreshView()
   942         event.Skip()
   942         event.Skip()
   943     
   943 
   944     def OnGraphicsWindowResize(self, event):
   944     def OnGraphicsWindowResize(self, event):
   945         size = self.GetSize()
   945         size = self.GetSize()
   946         for panel in self.GraphicPanels:
   946         for panel in self.GraphicPanels:
   947             panel_size = panel.GetSize()
   947             panel_size = panel.GetSize()
   948             if (isinstance(panel, DebugVariableGraphicViewer) and 
   948             if isinstance(panel, DebugVariableGraphicViewer) and \
   949                 panel.GraphType == GRAPH_ORTHOGONAL and 
   949                panel.GraphType == GRAPH_ORTHOGONAL and \
   950                 panel_size.width == panel_size.height):
   950                panel_size.width == panel_size.height:
   951                 panel.SetCanvasHeight(size.width)
   951                 panel.SetCanvasHeight(size.width)
   952         self.RefreshGraphicsWindowScrollbars()
   952         self.RefreshGraphicsWindowScrollbars()
   953         self.GraphicsSizer.Layout()
   953         self.GraphicsSizer.Layout()
   954         event.Skip()
   954         event.Skip()
   955 
   955