etherlab/ConfigEditor.py
changeset 2098 392791b5cc04
parent 2097 58d07e039896
child 2099 ea5384ab152c
equal deleted inserted replaced
2097:58d07e039896 2098:392791b5cc04
     1 import os
     1 import os
       
     2 import re
       
     3 from types import TupleType
     2 
     4 
     3 import wx
     5 import wx
     4 import wx.grid
     6 import wx.grid
     5 import wx.gizmos
     7 import wx.gizmos
     6 import wx.lib.buttons
     8 import wx.lib.buttons
     7 
     9 
       
    10 from plcopen.structures import IEC_KEYWORDS, TestIdentifier
     8 from controls import CustomGrid, CustomTable, FolderTree
    11 from controls import CustomGrid, CustomTable, FolderTree
     9 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
    12 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
    10 from util.BitmapLibrary import GetBitmap
    13 from util.BitmapLibrary import GetBitmap
    11 
    14 
    12 [ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3)
    15 [ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3)
    15     if wx.VERSION >= (2, 6, 0):
    18     if wx.VERSION >= (2, 6, 0):
    16         parent.Append(help=help, id=id, kind=kind, text=text)
    19         parent.Append(help=help, id=id, kind=kind, text=text)
    17     else:
    20     else:
    18         parent.Append(helpString=help, id=id, kind=kind, item=text)
    21         parent.Append(helpString=help, id=id, kind=kind, item=text)
    19 
    22 
    20 def GetVariablesTableColnames():
    23 def GetVariablesTableColnames(position=False):
    21     _ = lambda x : x
    24     _ = lambda x : x
    22     return ["#", _("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")]
    25     colname = ["#"]
       
    26     if position:
       
    27         colname.append(_("Position"))
       
    28     return colname + [_("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")]
    23 
    29 
    24 ACCESS_TYPES = {
    30 ACCESS_TYPES = {
    25     'ro': 'R',
    31     'ro': 'R',
    26     'wo': 'W',
    32     'wo': 'W',
    27     'rw': 'R/W'}
    33     'rw': 'R/W'}
    28 
    34 
    29 def GetAccessValue(access, pdo_mapping):
    35 def GetAccessValue(access, pdo_mapping):
    30     value = ACCESS_TYPES.get(access)
    36     value = ACCESS_TYPES.get(access, "")
    31     if pdo_mapping != "":
    37     if pdo_mapping != "":
    32         value += "/P"
    38         value += "/P"
    33     return value
    39     return value
    34 
    40 
    35 class NodeEditor(ConfTreeNodeEditor):
    41 VARIABLES_FILTERS = [
    36     
    42     (_("All"), (0x0000, 0xffff)),
    37     CONFNODEEDITOR_TABS = [
    43     (_("Communication Parameters"), (0x1000, 0x1fff)),
    38         (_("Ethercat node"), "_create_EthercatNodeEditor")]
    44     (_("Manufacturer Specific"), (0x2000, 0x5fff)),
    39     
    45     (_("Standardized Device Profile"), (0x6000, 0x9fff))]
    40     def _create_EthercatNodeEditor(self, prnt):
    46 
    41         self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL)
    47 ETHERCAT_INDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,4})$")
    42         
    48 ETHERCAT_SUBINDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,2})$")
    43         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
    49 LOCATION_MODEL = re.compile("(?:%[IQM](?:[XBWLD]?([0-9]+(?:\.[0-9]+)*)))$")
    44         main_sizer.AddGrowableCol(0)
    50 
    45         main_sizer.AddGrowableRow(1)
    51 class NodeVariablesSizer(wx.FlexGridSizer):
    46         
    52     
    47         variables_label = wx.StaticText(self.EthercatNodeEditor,
    53     def __init__(self, parent, controler, position_column=False):
    48               label=_('Variable entries:'))
    54         wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=2, vgap=5)
    49         main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT)
    55         self.AddGrowableCol(0)
    50         
    56         self.AddGrowableRow(1)
    51         self.VariablesGrid = wx.gizmos.TreeListCtrl(self.EthercatNodeEditor,
    57         
    52               style=wx.TR_DEFAULT_STYLE |
    58         self.Controler = controler
    53                     wx.TR_ROW_LINES |
    59         self.PositionColumn = position_column
    54                     wx.TR_COLUMN_LINES |
    60         
    55                     wx.TR_HIDE_ROOT |
    61         self.VariablesFilter = wx.ComboBox(parent)
    56                     wx.TR_FULL_ROW_HIGHLIGHT)
    62         self.VariablesFilter.Bind(wx.EVT_COMBOBOX, self.OnVariablesFilterChanged)
    57         self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN, 
    63         self.AddWindow(self.VariablesFilter, flag=wx.GROW)
       
    64         
       
    65         self.VariablesGrid = wx.gizmos.TreeListCtrl(parent, 
       
    66                 style=wx.TR_DEFAULT_STYLE |
       
    67                       wx.TR_ROW_LINES |
       
    68                       wx.TR_COLUMN_LINES |
       
    69                       wx.TR_HIDE_ROOT |
       
    70                       wx.TR_FULL_ROW_HIGHLIGHT)
       
    71         self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
    58             self.OnVariablesGridLeftClick)
    72             self.OnVariablesGridLeftClick)
    59         main_sizer.AddWindow(self.VariablesGrid, border=10, 
    73         self.AddWindow(self.VariablesGrid, flag=wx.GROW)
    60             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
    74         
    61                 
    75         self.Filters = []
    62         self.EthercatNodeEditor.SetSizer(main_sizer)
    76         for desc, value in VARIABLES_FILTERS:
    63 
    77             self.VariablesFilter.Append(desc)
    64         return self.EthercatNodeEditor
    78             self.Filters.append(value)
    65     
    79         
    66     def __init__(self, parent, controler, window):
    80         self.VariablesFilter.SetSelection(0)
    67         ConfTreeNodeEditor.__init__(self, parent, controler, window)
    81         self.CurrentFilter = self.Filters[0]
    68     
    82         
    69         for colname, colsize, colalign in zip(GetVariablesTableColnames(),
    83         if position_column:
    70                                               [40, 150, 100, 100, 150, 100],
    84             for colname, colsize, colalign in zip(GetVariablesTableColnames(position_column),
    71                                               [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, 
    85                                                   [40, 80, 350, 80, 100, 80, 80],
    72                                                wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]):
    86                                                   [wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, 
    73             self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
    87                                                    wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, 
    74         self.VariablesGrid.SetMainColumn(1)
    88                                                    wx.ALIGN_LEFT]):
    75     
    89                 self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
    76     def GetBufferState(self):
    90             self.VariablesGrid.SetMainColumn(2)
    77         return False, False
    91         else:
    78         
    92             for colname, colsize, colalign in zip(GetVariablesTableColnames(),
       
    93                                                   [40, 350, 80, 100, 80, 80],
       
    94                                                   [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, 
       
    95                                                    wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]):
       
    96                 self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
       
    97             self.VariablesGrid.SetMainColumn(1)
       
    98     
    79     def RefreshView(self):
    99     def RefreshView(self):
    80         ConfTreeNodeEditor.RefreshView(self)
   100         entries = self.Controler.GetSlaveVariables(self.CurrentFilter)
    81     
   101         self.RefreshVariablesGrid(entries)
    82         self.RefreshSlaveInfos()
       
    83         
       
    84     def RefreshSlaveInfos(self):
       
    85         slave_infos = self.Controler.GetSlaveInfos()
       
    86         if slave_infos is not None:
       
    87             self.RefreshVariablesGrid(slave_infos["entries"])
       
    88         else:
       
    89             self.RefreshVariablesGrid([])
       
    90     
   102     
    91     def RefreshVariablesGrid(self, entries):
   103     def RefreshVariablesGrid(self, entries):
    92         root = self.VariablesGrid.GetRootItem()
   104         root = self.VariablesGrid.GetRootItem()
    93         if not root.IsOk():
   105         if not root.IsOk():
    94             root = self.VariablesGrid.AddRoot("Slave entries")
   106             root = self.VariablesGrid.AddRoot(_("Slave entries"))
    95         self.GenerateVariablesGridBranch(root, entries, GetVariablesTableColnames())
   107         self.GenerateVariablesGridBranch(root, entries, GetVariablesTableColnames(self.PositionColumn))
    96         self.VariablesGrid.Expand(root)
   108         self.VariablesGrid.Expand(root)
    97         
   109         
    98     def GenerateVariablesGridBranch(self, root, entries, colnames, idx=0):
   110     def GenerateVariablesGridBranch(self, root, entries, colnames, idx=0):
    99         item, root_cookie = self.VariablesGrid.GetFirstChild(root)
   111         item, root_cookie = self.VariablesGrid.GetFirstChild(root)
   100         
   112         
   111                     if colname == "Access":
   123                     if colname == "Access":
   112                         value = GetAccessValue(value, entry.get("PDOMapping", ""))
   124                         value = GetAccessValue(value, entry.get("PDOMapping", ""))
   113                     self.VariablesGrid.SetItemText(item, value, col)
   125                     self.VariablesGrid.SetItemText(item, value, col)
   114             if entry["PDOMapping"] == "":
   126             if entry["PDOMapping"] == "":
   115                 self.VariablesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
   127                 self.VariablesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
       
   128             else:
       
   129                 self.VariablesGrid.SetItemBackgroundColour(item, wx.WHITE)
   116             self.VariablesGrid.SetItemPyData(item, entry)
   130             self.VariablesGrid.SetItemPyData(item, entry)
   117             idx = self.GenerateVariablesGridBranch(item, entry["children"], colnames, idx)
   131             idx = self.GenerateVariablesGridBranch(item, entry["children"], colnames, idx)
   118             if not no_more_items:
   132             if not no_more_items:
   119                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
   133                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
   120                 no_more_items = not item.IsOk()
   134                 no_more_items = not item.IsOk()
   126                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
   140                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
   127             for item in to_delete:
   141             for item in to_delete:
   128                 self.VariablesGrid.Delete(item)
   142                 self.VariablesGrid.Delete(item)
   129         
   143         
   130         return idx
   144         return idx
   131 
   145     
       
   146     def OnVariablesFilterChanged(self, event):
       
   147         filter = self.VariablesFilter.GetSelection()
       
   148         if filter != -1:
       
   149             self.CurrentFilter = self.Filters[filter]
       
   150             self.RefreshView()
       
   151         else:
       
   152             try:
       
   153                 value = self.VariablesFilter.GetValue()
       
   154                 result = ETHERCAT_INDEX_MODEL.match(value)
       
   155                 if result is not None:
       
   156                     value = result.group(1)
       
   157                 index = int(value)
       
   158                 self.CurrentFilter = (index, index)
       
   159                 self.RefreshView()
       
   160             except:
       
   161                 pass
       
   162         event.Skip()
       
   163     
   132     def OnVariablesGridLeftClick(self, event):
   164     def OnVariablesGridLeftClick(self, event):
   133         item, flags, col = self.VariablesGrid.HitTest(event.GetPosition())
   165         item, flags, col = self.VariablesGrid.HitTest(event.GetPosition())
   134         if item.IsOk():
   166         if item.IsOk():
   135             entry = self.VariablesGrid.GetItemPyData(item)
   167             entry = self.VariablesGrid.GetItemPyData(item)
   136             data_type = entry.get("Type", "")
   168             data_type = entry.get("Type", "")
   137             pdo_mapping = entry.get("PDOMapping", "")
   169             data_size = self.Controler.GetSizeOfType(data_type)
   138             
   170             
   139             if (col == -1 and pdo_mapping != "" and
   171             if col == -1 and data_size is not None:
   140                 self.Controler.GetSizeOfType(data_type) is not None):
   172                 pdo_mapping = entry.get("PDOMapping", "")
   141                 
   173                 access = entry.get("Access", "")
   142                 entry_index = self.Controler.ExtractHexDecValue(entry.get("Index", "0"))
   174                 entry_index = self.Controler.ExtractHexDecValue(entry.get("Index", "0"))
   143                 entry_subindex = self.Controler.ExtractHexDecValue(entry.get("SubIndex", "0"))
   175                 entry_subindex = self.Controler.ExtractHexDecValue(entry.get("SubIndex", "0"))
   144                 var_name = "%s_%4.4x_%2.2x" % (self.Controler.CTNName(), entry_index, entry_subindex)
   176                 if self.PositionColumn:
   145                 if pdo_mapping == "R":
   177                     slave_pos = self.Controler.ExtractHexDecValue(entry.get("Position", "0"))
   146                     dir = "%I"
       
   147                 else:
   178                 else:
   148                     dir = "%Q"
   179                     slave_pos = self.Controler.GetSlavePos()
   149                 location = "%s%s" % (dir, self.Controler.GetSizeOfType(data_type)) + \
       
   150                            ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + (self.Controler.GetSlavePos(), entry_index, entry_subindex)))
       
   151                 
   180                 
   152                 data = wx.TextDataObject(str((location, "location", data_type, var_name, "")))
   181                 if pdo_mapping != "":
   153                 dragSource = wx.DropSource(self.VariablesGrid)
   182                     var_name = "%s_%4.4x_%2.2x" % (self.Controler.CTNName(), entry_index, entry_subindex)
   154                 dragSource.SetData(data)
   183                     if pdo_mapping == "R":
   155                 dragSource.DoDragDrop()
   184                         dir = "%I"
   156                 return
   185                     else:
       
   186                         dir = "%Q"
       
   187                     location = "%s%s" % (dir, data_size) + \
       
   188                                ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + 
       
   189                                                              (slave_pos, entry_index, entry_subindex)))
       
   190                     
       
   191                     data = wx.TextDataObject(str((location, "location", data_type, var_name, "", access)))
       
   192                     dragSource = wx.DropSource(self.VariablesGrid)
       
   193                     dragSource.SetData(data)
       
   194                     dragSource.DoDragDrop()
       
   195                     return
       
   196                 
       
   197                 elif self.ColumnPosition:
       
   198                     location = self.Controler.GetCurrentLocation() +\
       
   199                                (slave_pos, entry_index, entry_subindex)
       
   200                     data = wx.TextDataObject(str((location, "variable", access)))
       
   201                     dragSource = wx.DropSource(self.VariablesGrid)
       
   202                     dragSource.SetData(data)
       
   203                     dragSource.DoDragDrop()
       
   204                     return
       
   205         
       
   206         event.Skip()
       
   207 
       
   208 class NodeEditor(ConfTreeNodeEditor):
       
   209     
       
   210     CONFNODEEDITOR_TABS = [
       
   211         (_("Ethercat node"), "_create_EthercatNodeEditor")]
       
   212     
       
   213     def _create_EthercatNodeEditor(self, prnt):
       
   214         self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL)
       
   215         
       
   216         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   217         main_sizer.AddGrowableCol(0)
       
   218         main_sizer.AddGrowableRow(1)
       
   219         
       
   220         variables_label = wx.StaticText(self.EthercatNodeEditor,
       
   221               label=_('Variable entries:'))
       
   222         main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT)
       
   223         
       
   224         self.NodeVariables = NodeVariablesSizer(self.EthercatNodeEditor, self.Controler)
       
   225         main_sizer.AddSizer(self.NodeVariables, border=10, 
       
   226             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
       
   227                 
       
   228         self.EthercatNodeEditor.SetSizer(main_sizer)
       
   229 
       
   230         return self.EthercatNodeEditor
       
   231     
       
   232     def __init__(self, parent, controler, window):
       
   233         ConfTreeNodeEditor.__init__(self, parent, controler, window)
       
   234         
       
   235     def GetBufferState(self):
       
   236         return False, False
       
   237         
       
   238     def RefreshView(self):
       
   239         ConfTreeNodeEditor.RefreshView(self)
       
   240     
       
   241         self.NodeVariables.RefreshView()
       
   242 
       
   243 CIA402NodeEditor = NodeEditor
       
   244 
       
   245 def GetProcessVariablesTableColnames():
       
   246     _ = lambda x : x
       
   247     return ["#", _("Name"), 
       
   248             _("Read from (nodeid, index, subindex)"), 
       
   249             _("Write to (nodeid, index, subindex)"),
       
   250             _("Description")]
       
   251 
       
   252 class ProcessVariablesTable(CustomTable):
       
   253     
       
   254     def GetValue(self, row, col):
       
   255         if row < self.GetNumberRows():
       
   256             if col == 0:
       
   257                 return row + 1
       
   258             colname = self.GetColLabelValue(col, False)
       
   259             if colname.startswith("Read from"):
       
   260                 value = self.data[row].get("ReadFrom", "")
       
   261                 if value == "":
       
   262                     return value
       
   263                 return "%d, #x%0.4x, #x%0.2x" % value
       
   264             elif colname.startswith("Write to"):
       
   265                 value = self.data[row].get("WriteTo", "")
       
   266                 if value == "":
       
   267                     return value
       
   268                 return "%d, #x%0.4x, #x%0.2x" % value
       
   269             return self.data[row].get(colname, "")
       
   270     
       
   271     def SetValue(self, row, col, value):
       
   272         if col < len(self.colnames):
       
   273             colname = self.GetColLabelValue(col, False)
       
   274             if colname.startswith("Read from"):
       
   275                 self.data[row]["ReadFrom"] = value
       
   276             elif colname.startswith("Write to"):
       
   277                 self.data[row]["WriteTo"] = value
       
   278             else:
       
   279                 self.data[row][colname] = value
       
   280     
       
   281     def _updateColAttrs(self, grid):
       
   282         """
       
   283         wx.grid.Grid -> update the column attributes to add the
       
   284         appropriate renderer given the column name.
       
   285 
       
   286         Otherwise default to the default renderer.
       
   287         """
       
   288         for row in range(self.GetNumberRows()):
       
   289             for col in range(self.GetNumberCols()):
       
   290                 editor = None
       
   291                 renderer = None
       
   292                 colname = self.GetColLabelValue(col, False)
       
   293                 if colname in ["Name", "Description"]:
       
   294                     editor = wx.grid.GridCellTextEditor()
       
   295                     renderer = wx.grid.GridCellStringRenderer()
       
   296                     grid.SetReadOnly(row, col, False)
       
   297                 else:
       
   298                     grid.SetReadOnly(row, col, True)
       
   299                 
       
   300                 grid.SetCellEditor(row, col, editor)
       
   301                 grid.SetCellRenderer(row, col, renderer)
       
   302                 
       
   303             self.ResizeRow(grid, row)
       
   304 
       
   305 class ProcessVariableDropTarget(wx.TextDropTarget):
       
   306     
       
   307     def __init__(self, parent):
       
   308         wx.TextDropTarget.__init__(self)
       
   309         self.ParentWindow = parent
       
   310     
       
   311     def OnDropText(self, x, y, data):
       
   312         self.ParentWindow.Select()
       
   313         x, y = self.ParentWindow.ProcessVariablesGrid.CalcUnscrolledPosition(x, y)
       
   314         col = self.ParentWindow.ProcessVariablesGrid.XToCol(x)
       
   315         row = self.ParentWindow.ProcessVariablesGrid.YToRow(y - self.ParentWindow.ProcessVariablesGrid.GetColLabelSize())
       
   316         message = None
       
   317         try:
       
   318             values = eval(data)
       
   319         except:
       
   320             message = _("Invalid value \"%s\" for process variable")%data
       
   321             values = None
       
   322         if not isinstance(values, TupleType):
       
   323             message = _("Invalid value \"%s\" for process variable")%data
       
   324             values = None
       
   325         if values is not None and 2 <= col <= 3:
       
   326             location = None
       
   327             if values[1] == "location":
       
   328                 result = LOCATION_MODEL.match(values[0])
       
   329                 if result is not None:
       
   330                     location = map(int, result.group(1).split('.'))
       
   331                 master_location = self.ParentWindow.GetMasterLocation()
       
   332                 if (master_location == tuple(location[:len(master_location)]) and 
       
   333                     len(location) - len(master_location) == 3):
       
   334                     if col == 2:
       
   335                         self.ParentWindow.ProcessVariablesTable.SetValueByName(
       
   336                             row, "ReadFrom", tuple(location[len(master_location):]))
       
   337                     else:
       
   338                         self.ParentWindow.ProcessVariablesTable.SetValueByName(
       
   339                             row, "WriteTo", tuple(location[len(master_location):]))
       
   340                     self.ParentWindow.SaveProcessVariables()
       
   341                     self.ParentWindow.RefreshProcessVariables()
       
   342                 else:
       
   343                     message = _("Invalid value \"%s\" for process variable")%data
       
   344                     
       
   345         if message is not None:
       
   346             wx.CallAfter(self.ShowMessage, message)
       
   347     
       
   348     def ShowMessage(self, message):
       
   349         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   350         message.ShowModal()
       
   351         message.Destroy()
       
   352 
       
   353 def GetStartupCommandsTableColnames():
       
   354     _ = lambda x : x
       
   355     return [_("Position"), _("Index"), _("Subindex"), _("Value"), _("Description")]
       
   356 
       
   357 class StartupCommandDropTarget(wx.TextDropTarget):
       
   358     
       
   359     def __init__(self, parent):
       
   360         wx.TextDropTarget.__init__(self)
       
   361         self.ParentWindow = parent
       
   362     
       
   363     def OnDropText(self, x, y, data):
       
   364         self.ParentWindow.Select()
       
   365         message = None
       
   366         try:
       
   367             values = eval(data)
       
   368         except:
       
   369             message = _("Invalid value \"%s\" for startup command")%data
       
   370             values = None
       
   371         if not isinstance(values, TupleType):
       
   372             message = _("Invalid value \"%s\" for startup command")%data
       
   373             values = None
       
   374         if values is not None:
       
   375             location = None
       
   376             if values[1] == "location":
       
   377                 result = LOCATION_MODEL.match(values[0])
       
   378                 if result is not None:
       
   379                     location = map(int, result.group(1).split('.'))
       
   380                     access = values[5]
       
   381             elif values[1] == "variable":
       
   382                 location = values[0]
       
   383                 access = values[2]
       
   384             if location is not None:
       
   385                 master_location = self.ParentWindow.GetMasterLocation()
       
   386                 if (master_location == tuple(location[:len(master_location)]) and 
       
   387                     len(location) - len(master_location) == 3):
       
   388                     if access in ["wo", "rw"]:
       
   389                         self.ParentWindow.AddStartupCommand(*location[len(master_location):])
       
   390                     else:
       
   391                         message = _("Entry can't be write through SDO")
       
   392                 else:
       
   393                     message = _("Invalid value \"%s\" for startup command")%data
       
   394                     
       
   395         if message is not None:
       
   396             wx.CallAfter(self.ShowMessage, message)
       
   397     
       
   398     def ShowMessage(self, message):
       
   399         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   400         message.ShowModal()
       
   401         message.Destroy()
       
   402 
       
   403 class StartupCommandsTable(CustomTable):
       
   404 
       
   405     """
       
   406     A custom wx.grid.Grid Table using user supplied data
       
   407     """
       
   408     def __init__(self, parent, data, colnames):
       
   409         # The base class must be initialized *first*
       
   410         CustomTable.__init__(self, parent, data, colnames)
       
   411         self.old_value = None
       
   412 
       
   413     def GetValue(self, row, col):
       
   414         if row < self.GetNumberRows():
       
   415             colname = self.GetColLabelValue(col, False)
       
   416             value = self.data[row].get(colname, "")
       
   417             if colname == "Index":
       
   418                 return "#x%0.4x" % value
       
   419             elif colname == "Subindex":
       
   420                 return "#x%0.2x" % value
       
   421             return value
       
   422     
       
   423     def SetValue(self, row, col, value):
       
   424         if col < len(self.colnames):
       
   425             colname = self.GetColLabelValue(col, False)
       
   426             if colname in ["Index", "Subindex"]:
       
   427                 if colname == "Index":
       
   428                     result = ETHERCAT_INDEX_MODEL.match(value)
       
   429                 else:
       
   430                     result = ETHERCAT_SUBINDEX_MODEL.match(value)
       
   431                 if result is None:
       
   432                     return
       
   433                 value = int(result.group(1), 16)
       
   434             elif colname == "Value":
       
   435                 value = int(value)
       
   436             elif colname == "Position":
       
   437                 self.old_value = self.data[row][colname]
       
   438                 value = int(value)
       
   439             self.data[row][colname] = value
       
   440     
       
   441     def GetOldValue(self):
       
   442         return self.old_value
       
   443     
       
   444     def _updateColAttrs(self, grid):
       
   445         """
       
   446         wx.grid.Grid -> update the column attributes to add the
       
   447         appropriate renderer given the column name.
       
   448 
       
   449         Otherwise default to the default renderer.
       
   450         """
       
   451         for row in range(self.GetNumberRows()):
       
   452             for col in range(self.GetNumberCols()):
       
   453                 editor = None
       
   454                 renderer = None
       
   455                 colname = self.GetColLabelValue(col, False)
       
   456                 if colname in ["Position", "Value"]:
       
   457                     editor = wx.grid.GridCellNumberEditor()
       
   458                     renderer = wx.grid.GridCellNumberRenderer()
       
   459                 else:
       
   460                     editor = wx.grid.GridCellTextEditor()
       
   461                     renderer = wx.grid.GridCellStringRenderer()
       
   462                 
       
   463                 grid.SetCellEditor(row, col, editor)
       
   464                 grid.SetCellRenderer(row, col, renderer)
       
   465                 grid.SetReadOnly(row, col, False)
       
   466                 
       
   467             self.ResizeRow(grid, row)
       
   468     
       
   469     def GetCommandIndex(self, position, command_idx):
       
   470         for row, command in enumerate(self.data):
       
   471             if command["Position"] == position and command["command_idx"] == command_idx:
       
   472                 return row
       
   473         return None
       
   474 
       
   475 class MasterNodesVariablesSizer(NodeVariablesSizer):
       
   476     
       
   477     def __init__(self, parent, controler):
       
   478         NodeVariablesSizer.__init__(self, parent, controler, True)
       
   479         
       
   480         self.CurrentNodesFilter = {}
       
   481     
       
   482     def SetCurrentNodesFilter(self, nodes_filter):
       
   483         self.CurrentNodesFilter = nodes_filter
       
   484         
       
   485     def RefreshView(self):
       
   486         if self.CurrentNodesFilter is not None:
       
   487             args = self.CurrentNodesFilter.copy()
       
   488             args["limits"] = self.CurrentFilter
       
   489             entries = self.Controler.GetNodesVariables(**args)
       
   490             self.RefreshVariablesGrid(entries)
       
   491 
       
   492 class MasterEditor(ConfTreeNodeEditor):
       
   493     
       
   494     CONFNODEEDITOR_TABS = [
       
   495         (_("Network"), "_create_EthercatMasterEditor")]
       
   496     
       
   497     def _create_EthercatMasterEditor(self, prnt):
       
   498         self.EthercatMasterEditor = wx.ScrolledWindow(prnt, 
       
   499             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
   500         self.EthercatMasterEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
   501         
       
   502         main_sizer = wx.BoxSizer(wx.VERTICAL)
       
   503         
       
   504         self.NodesFilter = wx.ComboBox(self.EthercatMasterEditor,
       
   505             style=wx.TE_PROCESS_ENTER)
       
   506         self.Bind(wx.EVT_COMBOBOX, self.OnNodesFilterChanged, self.NodesFilter)
       
   507         self.Bind(wx.EVT_TEXT_ENTER, self.OnNodesFilterChanged, self.NodesFilter)
       
   508         
       
   509         process_variables_header = wx.BoxSizer(wx.HORIZONTAL)
       
   510         
       
   511         process_variables_label = wx.StaticText(self.EthercatMasterEditor,
       
   512               label=_("Process variables mapped between nodes:"))
       
   513         process_variables_header.AddWindow(process_variables_label, 1,
       
   514               flag=wx.ALIGN_CENTER_VERTICAL)
       
   515         
       
   516         for name, bitmap, help in [
       
   517                 ("AddVariableButton", "add_element", _("Add process variable")),
       
   518                 ("DeleteVariableButton", "remove_element", _("Remove process variable")),
       
   519                 ("UpVariableButton", "up", _("Move process variable up")),
       
   520                 ("DownVariableButton", "down", _("Move process variable down"))]:
       
   521             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), 
       
   522                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   523             button.SetToolTipString(help)
       
   524             setattr(self, name, button)
       
   525             process_variables_header.AddWindow(button, border=5, flag=wx.LEFT)
       
   526         
       
   527         self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
       
   528         self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150))
       
   529         self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self))
       
   530         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   531               self.OnProcessVariablesGridCellChange)
       
   532         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
       
   533               self.OnProcessVariablesGridCellLeftClick)
       
   534         
       
   535         startup_commands_header = wx.BoxSizer(wx.HORIZONTAL)
       
   536         
       
   537         startup_commands_label = wx.StaticText(self.EthercatMasterEditor,
       
   538               label=_("Startup service variables assignments:"))
       
   539         startup_commands_header.AddWindow(startup_commands_label, 1,
       
   540               flag=wx.ALIGN_CENTER_VERTICAL)
       
   541         
       
   542         for name, bitmap, help in [
       
   543                 ("AddCommandButton", "add_element", _("Add startup service variable")),
       
   544                 ("DeleteCommandButton", "remove_element", _("Remove startup service variable"))]:
       
   545             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), 
       
   546                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   547             button.SetToolTipString(help)
       
   548             setattr(self, name, button)
       
   549             startup_commands_header.AddWindow(button, border=5, flag=wx.LEFT)
       
   550         
       
   551         self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
       
   552         self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self))
       
   553         self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150))
       
   554         self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   555               self.OnStartupCommandsGridCellChange)
       
   556         
       
   557         second_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Nodes variables filter:"))
       
   558         second_staticbox_sizer = wx.StaticBoxSizer(second_staticbox, wx.VERTICAL)
       
   559         
       
   560         self.NodesVariables = MasterNodesVariablesSizer(self.EthercatMasterEditor, self.Controler)
       
   561         second_staticbox_sizer.AddSizer(self.NodesVariables, 1, border=5, flag=wx.GROW|wx.ALL)
       
   562         
       
   563         main_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Node filter:"))
       
   564         staticbox_sizer = wx.StaticBoxSizer(main_staticbox, wx.VERTICAL)
       
   565         main_sizer.AddSizer(staticbox_sizer, border=10, flag=wx.GROW|wx.ALL)
       
   566         main_staticbox_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=5, vgap=0)
       
   567         main_staticbox_sizer.AddGrowableCol(0)
       
   568         main_staticbox_sizer.AddGrowableRow(1)
       
   569         main_staticbox_sizer.AddGrowableRow(3)
       
   570         staticbox_sizer.AddSizer(main_staticbox_sizer, 1, flag=wx.GROW)
       
   571         main_staticbox_sizer.AddWindow(self.NodesFilter, border=5, flag=wx.GROW|wx.ALL)
       
   572         main_staticbox_sizer.AddSizer(process_variables_header, border=5, 
       
   573               flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   574         main_staticbox_sizer.AddWindow(self.ProcessVariablesGrid, 1, 
       
   575               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   576         main_staticbox_sizer.AddSizer(startup_commands_header, 
       
   577               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   578         main_staticbox_sizer.AddWindow(self.StartupCommandsGrid, 1, 
       
   579               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   580         main_staticbox_sizer.AddSizer(second_staticbox_sizer, 1, 
       
   581             border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   582         
       
   583         self.EthercatMasterEditor.SetSizer(main_sizer)
       
   584         
       
   585         return self.EthercatMasterEditor
       
   586 
       
   587     def __init__(self, parent, controler, window):
       
   588         ConfTreeNodeEditor.__init__(self, parent, controler, window)
       
   589     
       
   590         self.ProcessVariablesDefaultValue = {"Name": "", "ReadFrom": "", "WriteTo": "", "Description": ""}
       
   591         self.ProcessVariablesTable = ProcessVariablesTable(self, [], GetProcessVariablesTableColnames())
       
   592         self.ProcessVariablesColSizes = [40, 100, 150, 150, 200]
       
   593         self.ProcessVariablesColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
       
   594         
       
   595         self.ProcessVariablesGrid.SetTable(self.ProcessVariablesTable)
       
   596         self.ProcessVariablesGrid.SetButtons({"Add": self.AddVariableButton,
       
   597                                               "Delete": self.DeleteVariableButton,
       
   598                                               "Up": self.UpVariableButton,
       
   599                                               "Down": self.DownVariableButton})
       
   600         
       
   601         def _AddVariablesElement(new_row):
       
   602             self.ProcessVariablesTable.InsertRow(new_row, self.ProcessVariablesDefaultValue.copy())
       
   603             self.SaveProcessVariables()
       
   604             self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   605             return new_row
       
   606         setattr(self.ProcessVariablesGrid, "_AddRow", _AddVariablesElement)
       
   607         
       
   608         def _DeleteVariablesElement(row):
       
   609             self.ProcessVariablesTable.RemoveRow(row)
       
   610             self.SaveProcessVariables()
       
   611             self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   612         setattr(self.ProcessVariablesGrid, "_DeleteRow", _DeleteVariablesElement)
   157             
   613             
       
   614         def _MoveVariablesElement(row, move):
       
   615             new_row = self.ProcessVariablesTable.MoveRow(row, move)
       
   616             if new_row != row:
       
   617                 self.SaveProcessVariables()
       
   618                 self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   619             return new_row
       
   620         setattr(self.ProcessVariablesGrid, "_MoveRow", _MoveVariablesElement)
       
   621         
       
   622         self.ProcessVariablesGrid.SetRowLabelSize(0)
       
   623         for col in range(self.ProcessVariablesTable.GetNumberCols()):
       
   624             attr = wx.grid.GridCellAttr()
       
   625             attr.SetAlignment(self.ProcessVariablesColAlignements[col], wx.ALIGN_CENTRE)
       
   626             self.ProcessVariablesGrid.SetColAttr(col, attr)
       
   627             self.ProcessVariablesGrid.SetColMinimalWidth(col, self.ProcessVariablesColSizes[col])
       
   628             self.ProcessVariablesGrid.AutoSizeColumn(col, False)
       
   629         self.ProcessVariablesGrid.RefreshButtons()
       
   630     
       
   631         self.StartupCommandsDefaultValue = {"Position": 0, "Index": 0, "Subindex": 0, "Value": 0, "Description": ""}
       
   632         self.StartupCommandsTable = StartupCommandsTable(self, [], GetStartupCommandsTableColnames())
       
   633         self.StartupCommandsColSizes = [100, 100, 50, 100, 200]
       
   634         self.StartupCommandsColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT]
       
   635         
       
   636         self.StartupCommandsGrid.SetTable(self.StartupCommandsTable)
       
   637         self.StartupCommandsGrid.SetButtons({"Add": self.AddCommandButton,
       
   638                                              "Delete": self.DeleteCommandButton})
       
   639         
       
   640         def _AddCommandsElement(new_row):
       
   641             command = self.StartupCommandsDefaultValue.copy()
       
   642             command_idx = self.Controler.AppendStartupCommand(command)
       
   643             self.RefreshStartupCommands()
       
   644             self.RefreshBuffer()
       
   645             return self.StartupCommandsTable.GetCommandIndex(command["Position"], command_idx)
       
   646         setattr(self.StartupCommandsGrid, "_AddRow", _AddCommandsElement)
       
   647         
       
   648         def _DeleteCommandsElement(row):
       
   649             command = self.StartupCommandsTable.GetRow(row)
       
   650             self.Controler.RemoveStartupCommand(command["Position"], command["command_idx"])
       
   651             self.RefreshStartupCommands()
       
   652             self.RefreshBuffer()
       
   653         setattr(self.StartupCommandsGrid, "_DeleteRow", _DeleteCommandsElement)
       
   654             
       
   655         self.StartupCommandsGrid.SetRowLabelSize(0)
       
   656         for col in range(self.StartupCommandsTable.GetNumberCols()):
       
   657             attr = wx.grid.GridCellAttr()
       
   658             attr.SetAlignment(self.StartupCommandsColAlignements[col], wx.ALIGN_CENTRE)
       
   659             self.StartupCommandsGrid.SetColAttr(col, attr)
       
   660             self.StartupCommandsGrid.SetColMinimalWidth(col, self.StartupCommandsColSizes[col])
       
   661             self.StartupCommandsGrid.AutoSizeColumn(col, False)
       
   662         self.StartupCommandsGrid.RefreshButtons()
       
   663     
       
   664     def RefreshBuffer(self):
       
   665         self.ParentWindow.RefreshTitle()
       
   666         self.ParentWindow.RefreshFileMenu()
       
   667         self.ParentWindow.RefreshEditMenu()
       
   668         self.ParentWindow.RefreshPageTitles()
       
   669     
       
   670     def RefreshView(self):
       
   671         ConfTreeNodeEditor.RefreshView(self)
       
   672         
       
   673         self.RefreshNodesFilter()
       
   674         self.RefreshProcessVariables()
       
   675         self.RefreshStartupCommands()
       
   676         self.NodesVariables.RefreshView()
       
   677     
       
   678     def RefreshNodesFilter(self):
       
   679         value = self.NodesFilter.GetValue()
       
   680         self.NodesFilter.Clear()
       
   681         self.NodesFilter.Append(_("All"))
       
   682         self.NodesFilterValues = [{}]
       
   683         for vendor_id, vendor_name in self.Controler.GetLibraryVendors():
       
   684             self.NodesFilter.Append(_("%s's nodes") % vendor_name)
       
   685             self.NodesFilterValues.append({"vendor": vendor_id})
       
   686         self.NodesFilter.Append(_("CIA402 nodes"))
       
   687         self.NodesFilterValues.append({"slave_profile": 402})
       
   688         if value in self.NodesFilter.GetStrings():
       
   689             self.NodesFilter.SetStringSelection(value)
       
   690         else:
       
   691             try:
       
   692                 int(value)
       
   693                 self.NodesFilter.SetValue(value)
       
   694             except:
       
   695                 self.NodesFilter.SetSelection(0)
       
   696         self.RefreshCurrentNodesFilter()
       
   697     
       
   698     def RefreshCurrentNodesFilter(self):
       
   699         filter = self.NodesFilter.GetSelection()
       
   700         if filter != -1:
       
   701             self.CurrentNodesFilter = self.NodesFilterValues[filter]
       
   702         else:
       
   703             try:
       
   704                 self.CurrentNodesFilter = {"slave_pos": int(self.NodesFilter.GetValue())}
       
   705             except:
       
   706                 self.CurrentNodesFilter = None
       
   707         self.NodesVariables.SetCurrentNodesFilter(self.CurrentNodesFilter)
       
   708     
       
   709     def RefreshProcessVariables(self):
       
   710         self.ProcessVariablesTable.SetData(
       
   711             self.Controler.GetProcessVariables())
       
   712         self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   713     
       
   714     def SaveProcessVariables(self):
       
   715         self.Controler.SetProcessVariables(
       
   716             self.ProcessVariablesTable.GetData())
       
   717         self.RefreshBuffer()
       
   718     
       
   719     def RefreshStartupCommands(self):
       
   720         if self.CurrentNodesFilter is not None:
       
   721             self.StartupCommandsTable.SetData(
       
   722                 self.Controler.GetStartupCommands(**self.CurrentNodesFilter))
       
   723             self.StartupCommandsTable.ResetView(self.StartupCommandsGrid)
       
   724     
       
   725     def SelectStartupCommand(self, position, command_idx):
       
   726         self.StartupCommandsGrid.SetSelectedRow(
       
   727             self.StartupCommandsTable.GetCommandIndex(position, command_idx))
       
   728     
       
   729     def GetMasterLocation(self):
       
   730         return self.Controler.GetCurrentLocation()
       
   731     
       
   732     def AddStartupCommand(self, position, index, subindex):
       
   733         command = self.StartupCommandsDefaultValue.copy()
       
   734         command["Position"] = position
       
   735         command["Index"] = index
       
   736         command["Subindex"] = subindex
       
   737         command_idx = self.Controler.AppendStartupCommand(command)
       
   738         self.RefreshStartupCommands()
       
   739         self.RefreshBuffer()
       
   740         self.StartupCommandsGrid.SetSelectedRow(
       
   741             self.StartupCommandsTable.GetCommandIndex(position, command_idx))
       
   742     
       
   743     def OnNodesFilterChanged(self, event):
       
   744         self.RefreshCurrentNodesFilter()
       
   745         if self.CurrentNodesFilter is not None:
       
   746             self.RefreshStartupCommands()
       
   747             self.NodesVariables.RefreshView()
   158         event.Skip()
   748         event.Skip()
   159 
   749     
   160 CIA402NodeEditor = NodeEditor
   750     def OnProcessVariablesGridCellChange(self, event):
   161 
   751         row, col = event.GetRow(), event.GetCol()
   162 
   752         colname = self.ProcessVariablesTable.GetColLabelValue(col, False)
       
   753         value = self.ProcessVariablesTable.GetValue(row, col)
       
   754         message = None
       
   755         if colname == "Name":
       
   756             if not TestIdentifier(value):
       
   757                 message = _("\"%s\" is not a valid identifier!") % value
       
   758             elif value.upper() in IEC_KEYWORDS:
       
   759                 message = _("\"%s\" is a keyword. It can't be used!") % value
       
   760             elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.ProcessVariablesTable.GetData()) if idx != row]:
       
   761                 message = _("An variable named \"%s\" already exists!") % value
       
   762         if message is None:
       
   763             self.SaveProcessVariables()
       
   764             wx.CallAfter(self.ProcessVariablesTable.ResetView, self.ProcessVariablesGrid)
       
   765             event.Skip()
       
   766         else:
       
   767             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   768             dialog.ShowModal()
       
   769             dialog.Destroy()
       
   770             event.Veto()
       
   771         
       
   772     def OnProcessVariablesGridCellLeftClick(self, event):
       
   773         event.Skip()
       
   774     
       
   775     def OnStartupCommandsGridCellChange(self, event):
       
   776         row, col = event.GetRow(), event.GetCol()
       
   777         colname = self.StartupCommandsTable.GetColLabelValue(col, False)
       
   778         value = self.StartupCommandsTable.GetValue(row, col)
       
   779         message = None
       
   780         if colname == "Position":
       
   781             if value not in self.Controler.GetSlaves():
       
   782                 message = _("No slave defined at position %d!") % value
       
   783             if message is None:
       
   784                 self.Controler.RemoveStartupCommand(
       
   785                     self.StartupCommandsTable.GetOldValue(),
       
   786                     self.StartupCommandsTable.GetValueByName(row, "command_idx"))
       
   787                 command = self.StartupCommandsTable.GetRow(row)
       
   788                 command_idx = self.Controler.AppendStartupCommand(command)
       
   789                 wx.CallAfter(self.RefreshStartupCommands)
       
   790                 wx.CallAfter(self.SelectStartupCommand, command["Position"], command_idx)
       
   791         else:
       
   792             self.Controler.SetStartupCommandInfos(self.StartupCommandsTable.GetRow(row))
       
   793         if message is None:
       
   794             self.RefreshBuffer()
       
   795             event.Skip()
       
   796         else:
       
   797             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   798             dialog.ShowModal()
       
   799             dialog.Destroy()
       
   800             event.Veto()
       
   801         
       
   802     def OnResize(self, event):
       
   803         self.EthercatMasterEditor.GetBestSize()
       
   804         xstart, ystart = self.EthercatMasterEditor.GetViewStart()
       
   805         window_size = self.EthercatMasterEditor.GetClientSize()
       
   806         maxx, maxy = self.EthercatMasterEditor.GetMinSize()
       
   807         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
   808         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
   809         self.EthercatMasterEditor.Scroll(posx, posy)
       
   810         self.EthercatMasterEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
   811                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
   812         event.Skip()
       
   813     
   163 def GetModulesTableColnames():
   814 def GetModulesTableColnames():
   164     _ = lambda x : x
   815     _ = lambda x : x
   165     return [_("Name"), _("PDO alignment (bits)")]
   816     return [_("Name"), _("PDO alignment (bits)")]
   166 
   817 
   167 class LibraryEditorPanel(wx.ScrolledWindow):
   818 class LibraryEditorSizer(wx.FlexGridSizer):
   168     
   819     
   169     def __init__(self, parent, module_library, buttons):
   820     def __init__(self, parent, module_library, buttons):
   170         wx.ScrolledWindow.__init__(self, parent,
   821         wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=4, vgap=5)
   171             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
   172         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   173         
   822         
   174         self.ModuleLibrary = module_library
   823         self.ModuleLibrary = module_library
   175     
   824     
   176         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=5)
   825         self.AddGrowableCol(0)
   177         main_sizer.AddGrowableCol(0)
   826         self.AddGrowableRow(1)
   178         main_sizer.AddGrowableRow(1)
   827         self.AddGrowableRow(3)
   179         main_sizer.AddGrowableRow(3)
   828         
   180         
   829         ESI_files_label = wx.StaticText(parent, 
   181         ESI_files_label = wx.StaticText(self, 
       
   182             label=_("ESI Files:"))
   830             label=_("ESI Files:"))
   183         main_sizer.AddWindow(ESI_files_label, border=10, 
   831         self.AddWindow(ESI_files_label, border=10, 
   184             flag=wx.TOP|wx.LEFT|wx.RIGHT)
   832             flag=wx.TOP|wx.LEFT|wx.RIGHT)
   185         
   833         
   186         folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
   834         folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
   187         folder_tree_sizer.AddGrowableCol(0)
   835         folder_tree_sizer.AddGrowableCol(0)
   188         folder_tree_sizer.AddGrowableRow(0)
   836         folder_tree_sizer.AddGrowableRow(0)
   189         main_sizer.AddSizer(folder_tree_sizer, border=10, 
   837         self.AddSizer(folder_tree_sizer, border=10, 
   190             flag=wx.GROW|wx.LEFT|wx.RIGHT)
   838             flag=wx.GROW|wx.LEFT|wx.RIGHT)
   191         
   839         
   192         self.ESIFiles = FolderTree(self, self.GetPath(), editable=False)
   840         self.ESIFiles = FolderTree(parent, self.GetPath(), editable=False)
   193         self.ESIFiles.SetFilter(".xml")
   841         self.ESIFiles.SetFilter(".xml")
   194         self.ESIFiles.SetMinSize(wx.Size(600, 300))
       
   195         folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
   842         folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
   196         
   843         
   197         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
   844         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
   198         folder_tree_sizer.AddSizer(buttons_sizer, 
   845         folder_tree_sizer.AddSizer(buttons_sizer, 
   199             flag=wx.ALIGN_CENTER_VERTICAL)
   846             flag=wx.ALIGN_CENTER_VERTICAL)
   200         
   847         
   201         for idx, (name, bitmap, help, callback) in enumerate(buttons):
   848         for idx, (name, bitmap, help, callback) in enumerate(buttons):
   202             button = wx.lib.buttons.GenBitmapButton(self, 
   849             button = wx.lib.buttons.GenBitmapButton(parent, 
   203                   bitmap=GetBitmap(bitmap), 
   850                   bitmap=GetBitmap(bitmap), 
   204                   size=wx.Size(28, 28), style=wx.NO_BORDER)
   851                   size=wx.Size(28, 28), style=wx.NO_BORDER)
   205             button.SetToolTipString(help)
   852             button.SetToolTipString(help)
   206             setattr(self, name, button)
   853             setattr(self, name, button)
   207             if idx > 0:
   854             if idx > 0:
   209             else:
   856             else:
   210                 flag = 0
   857                 flag = 0
   211             if callback is None:
   858             if callback is None:
   212                 callback = getattr(self, "On" + name, None)
   859                 callback = getattr(self, "On" + name, None)
   213             if callback is not None:
   860             if callback is not None:
   214                 self.Bind(wx.EVT_BUTTON, callback, button)
   861                 parent.Bind(wx.EVT_BUTTON, callback, button)
   215             buttons_sizer.AddWindow(button, border=10, flag=flag)
   862             buttons_sizer.AddWindow(button, border=10, flag=flag)
   216         
   863         
   217         modules_label = wx.StaticText(self, 
   864         modules_label = wx.StaticText(parent, 
   218             label=_("Modules library:"))
   865             label=_("Modules library:"))
   219         main_sizer.AddSizer(modules_label, border=10, 
   866         self.AddSizer(modules_label, border=10, 
   220             flag=wx.LEFT|wx.RIGHT)
   867             flag=wx.LEFT|wx.RIGHT)
   221         
   868         
   222         self.ModulesGrid = wx.gizmos.TreeListCtrl(self,
   869         self.ModulesGrid = wx.gizmos.TreeListCtrl(parent,
   223               style=wx.TR_DEFAULT_STYLE |
   870               style=wx.TR_DEFAULT_STYLE |
   224                     wx.TR_ROW_LINES |
   871                     wx.TR_ROW_LINES |
   225                     wx.TR_COLUMN_LINES |
   872                     wx.TR_COLUMN_LINES |
   226                     wx.TR_HIDE_ROOT |
   873                     wx.TR_HIDE_ROOT |
   227                     wx.TR_FULL_ROW_HIGHLIGHT)
   874                     wx.TR_FULL_ROW_HIGHLIGHT)
   228         self.ModulesGrid.SetMinSize(wx.Size(600, 300))
       
   229         self.ModulesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DCLICK,
   875         self.ModulesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DCLICK,
   230             self.OnModulesGridLeftDClick)
   876             self.OnModulesGridLeftDClick)
   231         main_sizer.AddWindow(self.ModulesGrid, border=10, 
   877         self.AddWindow(self.ModulesGrid, border=10, 
   232             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
   878             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
   233         
       
   234         self.SetSizer(main_sizer)
       
   235         
   879         
   236         for colname, colsize, colalign in zip(GetModulesTableColnames(),
   880         for colname, colsize, colalign in zip(GetModulesTableColnames(),
   237                                               [400, 150],
   881                                               [400, 150],
   238                                               [wx.ALIGN_LEFT, wx.ALIGN_RIGHT]):
   882                                               [wx.ALIGN_LEFT, wx.ALIGN_RIGHT]):
   239             self.ModulesGrid.AddColumn(_(colname), colsize, colalign)
   883             self.ModulesGrid.AddColumn(_(colname), colsize, colalign)
   240         self.ModulesGrid.SetMainColumn(0)
   884         self.ModulesGrid.SetMainColumn(0)
   241     
   885     
   242     def GetPath(self):
   886     def GetPath(self):
   243         return self.ModuleLibrary.GetPath()
   887         return self.ModuleLibrary.GetPath()
   244     
   888     
       
   889     def SetControlMinSize(self, size):
       
   890         self.ESIFiles.SetMinSize(size)
       
   891         self.ModulesGrid.SetMinSize(size)
       
   892         
   245     def GetSelectedFilePath(self):
   893     def GetSelectedFilePath(self):
   246         return self.ESIFiles.GetPath()
   894         return self.ESIFiles.GetPath()
   247     
   895     
   248     def RefreshView(self):
   896     def RefreshView(self):
   249         self.ESIFiles.RefreshTree()
   897         self.ESIFiles.RefreshTree()
   347                         message.Destroy()
   995                         message.Destroy()
   348                     
   996                     
   349                 dialog.Destroy()
   997                 dialog.Destroy()
   350         
   998         
   351         event.Skip()
   999         event.Skip()
   352     
       
   353     def OnResize(self, event):
       
   354         self.GetBestSize()
       
   355         xstart, ystart = self.GetViewStart()
       
   356         window_size = self.GetClientSize()
       
   357         maxx, maxy = self.GetMinSize()
       
   358         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
   359         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
   360         self.Scroll(posx, posy)
       
   361         self.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
   362                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
   363         event.Skip()
       
   364 
  1000 
   365 class DatabaseManagementDialog(wx.Dialog):
  1001 class DatabaseManagementDialog(wx.Dialog):
   366     
  1002     
   367     def __init__(self, parent, database):
  1003     def __init__(self, parent, database):
   368         wx.Dialog.__init__(self, parent,
  1004         wx.Dialog.__init__(self, parent,
   371         
  1007         
   372         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
  1008         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
   373         main_sizer.AddGrowableCol(0)
  1009         main_sizer.AddGrowableCol(0)
   374         main_sizer.AddGrowableRow(0)
  1010         main_sizer.AddGrowableRow(0)
   375         
  1011         
   376         self.DatabaseEditor = LibraryEditorPanel(self, database,
  1012         self.DatabaseSizer = LibraryEditorSizer(self, database,
   377             [("ImportButton", "ImportESI", _("Import file to ESI files database"), None),
  1013             [("ImportButton", "ImportESI", _("Import file to ESI files database"), None),
   378              ("DeleteButton", "remove_element", _("Remove file from database"), None)])
  1014              ("DeleteButton", "remove_element", _("Remove file from database"), None)])
   379         main_sizer.AddWindow(self.DatabaseEditor, border=10,
  1015         self.DatabaseSizer.SetControlMinSize(wx.Size(0, 0))
       
  1016         main_sizer.AddSizer(self.DatabaseSizer, border=10,
   380             flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
  1017             flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
   381         
  1018         
   382         button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
  1019         button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
   383         button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
  1020         button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
   384         button_sizer.GetCancelButton().SetLabel(_("Close"))
  1021         button_sizer.GetCancelButton().SetLabel(_("Close"))
   385         main_sizer.AddSizer(button_sizer, border=10, 
  1022         main_sizer.AddSizer(button_sizer, border=10, 
   386               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
  1023               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
   387         
  1024         
   388         self.SetSizer(main_sizer)
  1025         self.SetSizer(main_sizer)
   389         
  1026         
   390         self.DatabaseEditor.RefreshView()
  1027         self.DatabaseSizer.RefreshView()
   391         
  1028         
   392     def GetValue(self):
  1029     def GetValue(self):
   393         return self.DatabaseEditor.GetSelectedFilePath()
  1030         return self.DatabaseSizer.GetSelectedFilePath()
   394 
  1031 
   395 class LibraryEditor(ConfTreeNodeEditor):
  1032 class LibraryEditor(ConfTreeNodeEditor):
   396     
  1033     
   397     CONFNODEEDITOR_TABS = [
  1034     CONFNODEEDITOR_TABS = [
   398         (_("Modules Library"), "_create_ModuleLibraryEditor")]
  1035         (_("Modules Library"), "_create_ModuleLibraryEditor")]
   399     
  1036     
   400     def _create_ModuleLibraryEditor(self, prnt):
  1037     def _create_ModuleLibraryEditor(self, prnt):
   401         self.ModuleLibraryEditor = LibraryEditorPanel(prnt,
  1038         self.ModuleLibraryEditor = wx.ScrolledWindow(prnt,
       
  1039             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
  1040         self.ModuleLibraryEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
  1041         
       
  1042         self.ModuleLibrarySizer = LibraryEditorSizer(self.ModuleLibraryEditor,
   402             self.Controler.GetModulesLibraryInstance(),
  1043             self.Controler.GetModulesLibraryInstance(),
   403             [("ImportButton", "ImportESI", _("Import ESI file"), None),
  1044             [("ImportButton", "ImportESI", _("Import ESI file"), None),
   404              ("AddButton", "ImportDatabase", _("Add file from ESI files database"), self.OnAddButton),
  1045              ("AddButton", "ImportDatabase", _("Add file from ESI files database"), self.OnAddButton),
   405              ("DeleteButton", "remove_element", _("Remove file from library"), None)])
  1046              ("DeleteButton", "remove_element", _("Remove file from library"), None)])
       
  1047         self.ModuleLibrarySizer.SetControlMinSize(wx.Size(0, 200))
       
  1048         self.ModuleLibraryEditor.SetSizer(self.ModuleLibrarySizer)
   406         
  1049         
   407         return self.ModuleLibraryEditor
  1050         return self.ModuleLibraryEditor
   408 
  1051 
   409     def __init__(self, parent, controler, window):
  1052     def __init__(self, parent, controler, window):
   410         ConfTreeNodeEditor.__init__(self, parent, controler, window)
  1053         ConfTreeNodeEditor.__init__(self, parent, controler, window)
   411     
  1054     
   412         self.RefreshView()
  1055         self.RefreshView()
   413     
  1056     
   414     def RefreshView(self):
  1057     def RefreshView(self):
   415         ConfTreeNodeEditor.RefreshView(self)
  1058         ConfTreeNodeEditor.RefreshView(self)
   416         self.ModuleLibraryEditor.RefreshView()
  1059         self.ModuleLibrarySizer.RefreshView()
   417 
  1060 
   418     def OnAddButton(self, event):
  1061     def OnAddButton(self, event):
   419         dialog = DatabaseManagementDialog(self, 
  1062         dialog = DatabaseManagementDialog(self, 
   420             self.Controler.GetModulesDatabaseInstance())
  1063             self.Controler.GetModulesDatabaseInstance())
   421         
  1064         
   423             module_library = self.Controler.GetModulesLibraryInstance()
  1066             module_library = self.Controler.GetModulesLibraryInstance()
   424             module_library.ImportModuleLibrary(dialog.GetValue())
  1067             module_library.ImportModuleLibrary(dialog.GetValue())
   425             
  1068             
   426         dialog.Destroy()
  1069         dialog.Destroy()
   427         
  1070         
   428         wx.CallAfter(self.ModuleLibraryEditor.RefreshView)
  1071         wx.CallAfter(self.ModuleLibrarySizer.RefreshView)
   429         
  1072         
   430         event.Skip()
  1073         event.Skip()
   431 
  1074 
       
  1075     def OnResize(self, event):
       
  1076         self.ModuleLibraryEditor.GetBestSize()
       
  1077         xstart, ystart = self.ModuleLibraryEditor.GetViewStart()
       
  1078         window_size = self.ModuleLibraryEditor.GetClientSize()
       
  1079         maxx, maxy = self.ModuleLibraryEditor.GetMinSize()
       
  1080         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
  1081         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
  1082         self.ModuleLibraryEditor.Scroll(posx, posy)
       
  1083         self.ModuleLibraryEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
  1084                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
  1085         event.Skip()
       
  1086         
       
  1087