etherlab/ConfigEditor.py
changeset 2165 02a2b5dee5e3
parent 2152 e6946c298a42
child 2353 8f1a2846b2f5
child 2641 c9deff128c37
equal deleted inserted replaced
2021:bcf346f558bd 2165:02a2b5dee5e3
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # This file is part of Beremiz
       
     5 #
       
     6 # Copyright (C) 2011-2014: Laurent BESSARD, Edouard TISSERANT
       
     7 #                          RTES Lab : CRKim, JBLee, youcu
       
     8 #                          Higen Motor : Donggu Kang
       
     9 #
       
    10 # See COPYING file for copyrights details.
       
    11 
       
    12 import os
       
    13 import re
       
    14 from types import TupleType
       
    15 
       
    16 import wx
       
    17 import wx.grid
       
    18 import wx.gizmos
       
    19 import wx.lib.buttons
       
    20 
       
    21 from plcopen.structures import IEC_KEYWORDS, TestIdentifier
       
    22 from controls import CustomGrid, CustomTable, FolderTree
       
    23 from editors.ConfTreeNodeEditor import ConfTreeNodeEditor, SCROLLBAR_UNIT
       
    24 from util.BitmapLibrary import GetBitmap
       
    25 from controls.CustomStyledTextCtrl import NAVIGATION_KEYS
       
    26 
       
    27 # -----------------------------------------------------------------------
       
    28 from EtherCATManagementEditor import EtherCATManagementTreebook, MasterStatePanelClass
       
    29 # -----------------------------------------------------------------------
       
    30 
       
    31 [ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE] = range(3)
       
    32 
       
    33 def AppendMenu(parent, help, id, kind, text):
       
    34     if wx.VERSION >= (2, 6, 0):
       
    35         parent.Append(help=help, id=id, kind=kind, text=text)
       
    36     else:
       
    37         parent.Append(helpString=help, id=id, kind=kind, item=text)
       
    38 
       
    39 def GetVariablesTableColnames(position=False):
       
    40     _ = lambda x : x
       
    41     colname = ["#"]
       
    42     if position:
       
    43         colname.append(_("Position"))
       
    44     return colname + [_("Name"), _("Index"), _("SubIndex"), _("Type"), _("Access")]
       
    45 
       
    46 ACCESS_TYPES = {
       
    47     'ro': 'R',
       
    48     'wo': 'W',
       
    49     'rw': 'R/W'}
       
    50 
       
    51 def GetAccessValue(access, pdo_mapping):
       
    52     value = "SDO: %s" % ACCESS_TYPES.get(access, "")
       
    53     if pdo_mapping != "":
       
    54         value += ", PDO: %s" % pdo_mapping
       
    55     return value
       
    56 
       
    57 VARIABLES_FILTERS = [
       
    58     (_("All"), (0x0000, 0xffff)),
       
    59     (_("Communication Parameters"), (0x1000, 0x1fff)),
       
    60     (_("Manufacturer Specific"), (0x2000, 0x5fff)),
       
    61     (_("Standardized Device Profile"), (0x6000, 0x9fff))]
       
    62 
       
    63 VARIABLE_INDEX_FILTER_FORMAT = _("Variable Index: #x%4.4X")
       
    64 
       
    65 ETHERCAT_INDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,4})$")
       
    66 ETHERCAT_SUBINDEX_MODEL = re.compile("#x([0-9a-fA-F]{0,2})$")
       
    67 LOCATION_MODEL = re.compile("(?:%[IQM](?:[XBWLD]?([0-9]+(?:\.[0-9]+)*)))$")
       
    68 
       
    69 class NodeVariablesSizer(wx.FlexGridSizer):
       
    70     
       
    71     def __init__(self, parent, controler, position_column=False):
       
    72         wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=2, vgap=5)
       
    73         self.AddGrowableCol(0)
       
    74         self.AddGrowableRow(1)
       
    75         
       
    76         self.Controler = controler
       
    77         self.PositionColumn = position_column
       
    78         
       
    79         self.VariablesFilter = wx.ComboBox(parent, style=wx.TE_PROCESS_ENTER)
       
    80         self.VariablesFilter.Bind(wx.EVT_COMBOBOX, self.OnVariablesFilterChanged)
       
    81         self.VariablesFilter.Bind(wx.EVT_TEXT_ENTER, self.OnVariablesFilterChanged)
       
    82         self.VariablesFilter.Bind(wx.EVT_CHAR, self.OnVariablesFilterKeyDown)
       
    83         self.AddWindow(self.VariablesFilter, flag=wx.GROW)
       
    84         
       
    85         self.VariablesGrid = wx.gizmos.TreeListCtrl(parent, 
       
    86                 style=wx.TR_DEFAULT_STYLE |
       
    87                       wx.TR_ROW_LINES |
       
    88                       wx.TR_COLUMN_LINES |
       
    89                       wx.TR_HIDE_ROOT |
       
    90                       wx.TR_FULL_ROW_HIGHLIGHT)
       
    91         self.VariablesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
       
    92             self.OnVariablesGridLeftClick)
       
    93         self.AddWindow(self.VariablesGrid, flag=wx.GROW)
       
    94         
       
    95         self.Filters = []
       
    96         for desc, value in VARIABLES_FILTERS:
       
    97             self.VariablesFilter.Append(desc)
       
    98             self.Filters.append(value)
       
    99         
       
   100         self.VariablesFilter.SetSelection(0)
       
   101         self.CurrentFilter = self.Filters[0]
       
   102         self.VariablesFilterFirstCharacter = True
       
   103         
       
   104         if position_column:
       
   105             for colname, colsize, colalign in zip(GetVariablesTableColnames(position_column),
       
   106                                                   [40, 80, 350, 80, 100, 80, 150],
       
   107                                                   [wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, 
       
   108                                                    wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT, 
       
   109                                                    wx.ALIGN_LEFT]):
       
   110                 self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
       
   111             self.VariablesGrid.SetMainColumn(2)
       
   112         else:
       
   113             for colname, colsize, colalign in zip(GetVariablesTableColnames(),
       
   114                                                   [40, 350, 80, 100, 80, 150],
       
   115                                                   [wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_RIGHT, 
       
   116                                                    wx.ALIGN_RIGHT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]):
       
   117                 self.VariablesGrid.AddColumn(_(colname), colsize, colalign)
       
   118             self.VariablesGrid.SetMainColumn(1)
       
   119     
       
   120     def RefreshView(self):
       
   121         entries = self.Controler.GetSlaveVariables(self.CurrentFilter)
       
   122         self.RefreshVariablesGrid(entries)
       
   123     
       
   124     def RefreshVariablesGrid(self, entries):
       
   125         root = self.VariablesGrid.GetRootItem()
       
   126         if not root.IsOk():
       
   127             root = self.VariablesGrid.AddRoot(_("Slave entries"))
       
   128         self.GenerateVariablesGridBranch(root, entries, GetVariablesTableColnames(self.PositionColumn))
       
   129         self.VariablesGrid.Expand(root)
       
   130         
       
   131     def GenerateVariablesGridBranch(self, root, entries, colnames, idx=0):
       
   132         item, root_cookie = self.VariablesGrid.GetFirstChild(root)
       
   133         
       
   134         no_more_items = not item.IsOk()
       
   135         for entry in entries:
       
   136             idx += 1
       
   137             if no_more_items:
       
   138                 item = self.VariablesGrid.AppendItem(root, "")
       
   139             for col, colname in enumerate(colnames):
       
   140                 if col == 0:
       
   141                     self.VariablesGrid.SetItemText(item, str(idx), 0)
       
   142                 else:
       
   143                     value = entry.get(colname, "")
       
   144                     if colname == "Access":
       
   145                         value = GetAccessValue(value, entry.get("PDOMapping", ""))
       
   146                     self.VariablesGrid.SetItemText(item, value, col)
       
   147             if entry["PDOMapping"] == "":
       
   148                 self.VariablesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
       
   149             else:
       
   150                 self.VariablesGrid.SetItemBackgroundColour(item, wx.WHITE)
       
   151             self.VariablesGrid.SetItemPyData(item, entry)
       
   152             idx = self.GenerateVariablesGridBranch(item, entry["children"], colnames, idx)
       
   153             if not no_more_items:
       
   154                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
       
   155                 no_more_items = not item.IsOk()
       
   156         
       
   157         if not no_more_items:
       
   158             to_delete = []
       
   159             while item.IsOk():
       
   160                 to_delete.append(item)
       
   161                 item, root_cookie = self.VariablesGrid.GetNextChild(root, root_cookie)
       
   162             for item in to_delete:
       
   163                 self.VariablesGrid.Delete(item)
       
   164         
       
   165         return idx
       
   166     
       
   167     def OnVariablesFilterChanged(self, event):
       
   168         filter = self.VariablesFilter.GetSelection()
       
   169         if filter != -1:
       
   170             self.CurrentFilter = self.Filters[filter]
       
   171             self.RefreshView()
       
   172         else:
       
   173             try:
       
   174                 value = self.VariablesFilter.GetValue()
       
   175                 if value == "":
       
   176                     self.CurrentFilter = self.Filters[0]
       
   177                     self.VariablesFilter.SetSelection(0)
       
   178                 else:
       
   179                     result = ETHERCAT_INDEX_MODEL.match(value)
       
   180                     if result is not None:
       
   181                         value = result.group(1)
       
   182                     index = int(value, 16)
       
   183                     self.CurrentFilter = (index, index)
       
   184                     self.VariablesFilter.SetValue(VARIABLE_INDEX_FILTER_FORMAT % index)
       
   185                 self.RefreshView()
       
   186             except:
       
   187                 if self.CurrentFilter in self.Filters:
       
   188                     self.VariablesFilter.SetSelection(self.Filters.index(self.CurrentFilter))
       
   189                 else:
       
   190                     self.VariablesFilter.SetValue(VARIABLE_INDEX_FILTER_FORMAT % self.CurrentFilter[0])
       
   191         self.VariablesFilterFirstCharacter = True
       
   192         event.Skip()
       
   193     
       
   194     def OnVariablesFilterKeyDown(self, event):
       
   195         if self.VariablesFilterFirstCharacter:
       
   196             keycode = event.GetKeyCode()
       
   197             if keycode not in [wx.WXK_RETURN, 
       
   198                                wx.WXK_NUMPAD_ENTER]:
       
   199                 self.VariablesFilterFirstCharacter = False
       
   200                 if keycode not in NAVIGATION_KEYS:
       
   201                     self.VariablesFilter.SetValue("")
       
   202             if keycode not in [wx.WXK_DELETE, 
       
   203                                wx.WXK_NUMPAD_DELETE, 
       
   204                                wx.WXK_BACK]:
       
   205                 event.Skip()
       
   206         else:
       
   207             event.Skip()
       
   208     
       
   209     def OnVariablesGridLeftClick(self, event):
       
   210         item, flags, col = self.VariablesGrid.HitTest(event.GetPosition())
       
   211         if item.IsOk():
       
   212             entry = self.VariablesGrid.GetItemPyData(item)
       
   213             data_type = entry.get("Type", "")
       
   214             data_size = self.Controler.GetSizeOfType(data_type)
       
   215             
       
   216             if col == -1 and data_size is not None:
       
   217                 pdo_mapping = entry.get("PDOMapping", "")
       
   218                 access = entry.get("Access", "")
       
   219                 entry_index = self.Controler.ExtractHexDecValue(entry.get("Index", "0"))
       
   220                 entry_subindex = self.Controler.ExtractHexDecValue(entry.get("SubIndex", "0"))
       
   221                 location = self.Controler.GetCurrentLocation()
       
   222                 if self.PositionColumn:
       
   223                     slave_pos = self.Controler.ExtractHexDecValue(entry.get("Position", "0"))
       
   224                     location += (slave_pos,)
       
   225                     node_name = self.Controler.GetSlaveName(slave_pos)
       
   226                 else:
       
   227                     node_name = self.Controler.CTNName()
       
   228                 
       
   229                 if pdo_mapping != "":
       
   230                     var_name = "%s_%4.4x_%2.2x" % (node_name, entry_index, entry_subindex)
       
   231                     if pdo_mapping == "T":
       
   232                         dir = "%I"
       
   233                     else:
       
   234                         dir = "%Q"
       
   235                     location = "%s%s" % (dir, data_size) + \
       
   236                                ".".join(map(lambda x:str(x), location + (entry_index, entry_subindex)))
       
   237                     
       
   238                     data = wx.TextDataObject(str((location, "location", data_type, var_name, "", access)))
       
   239                     dragSource = wx.DropSource(self.VariablesGrid)
       
   240                     dragSource.SetData(data)
       
   241                     dragSource.DoDragDrop()
       
   242                     return
       
   243                 
       
   244                 elif self.PositionColumn:
       
   245                     location = self.Controler.GetCurrentLocation() +\
       
   246                                (slave_pos, entry_index, entry_subindex)
       
   247                     data = wx.TextDataObject(str((location, "variable", access)))
       
   248                     dragSource = wx.DropSource(self.VariablesGrid)
       
   249                     dragSource.SetData(data)
       
   250                     dragSource.DoDragDrop()
       
   251                     return
       
   252         
       
   253         event.Skip()
       
   254 
       
   255 class NodeEditor(ConfTreeNodeEditor):
       
   256     
       
   257     CONFNODEEDITOR_TABS = [
       
   258         (_("Ethercat node"), "_create_EthercatNodeEditor"),
       
   259         # Add Notebook Tab for EtherCAT Management Treebook
       
   260         (_("EtherCAT Management"), "_create_EtherCATManagementEditor")
       
   261         ]
       
   262     
       
   263     def _create_EthercatNodeEditor(self, prnt):
       
   264         self.EthercatNodeEditor = wx.Panel(prnt, style=wx.TAB_TRAVERSAL)
       
   265         
       
   266         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   267         main_sizer.AddGrowableCol(0)
       
   268         main_sizer.AddGrowableRow(1)
       
   269         
       
   270         variables_label = wx.StaticText(self.EthercatNodeEditor,
       
   271               label=_('Variable entries:'))
       
   272         main_sizer.AddWindow(variables_label, border=10, flag=wx.TOP|wx.LEFT|wx.RIGHT)
       
   273         
       
   274         self.NodeVariables = NodeVariablesSizer(self.EthercatNodeEditor, self.Controler)
       
   275         main_sizer.AddSizer(self.NodeVariables, border=10, 
       
   276             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
       
   277                 
       
   278         self.EthercatNodeEditor.SetSizer(main_sizer)
       
   279 
       
   280         return self.EthercatNodeEditor
       
   281     
       
   282     def __init__(self, parent, controler, window):
       
   283         ConfTreeNodeEditor.__init__(self, parent, controler, window)
       
   284         
       
   285         # add Contoler for use EthercatSlave.py Method
       
   286         self.Controler = controler
       
   287         
       
   288     def GetBufferState(self):
       
   289         return False, False
       
   290         
       
   291     def RefreshView(self):
       
   292         ConfTreeNodeEditor.RefreshView(self)
       
   293     
       
   294         self.NodeVariables.RefreshView()
       
   295 
       
   296     # -------------------For EtherCAT Management ----------------------------------------------    
       
   297     def _create_EtherCATManagementEditor(self, prnt):
       
   298         self.EtherCATManagementEditor = wx.ScrolledWindow(prnt,
       
   299             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
   300         self.EtherCATManagementEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
   301 
       
   302         self.EtherCATManagermentEditor_Main_Sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   303         self.EtherCATManagermentEditor_Main_Sizer.AddGrowableCol(0)
       
   304         self.EtherCATManagermentEditor_Main_Sizer.AddGrowableRow(0)
       
   305         
       
   306         self.EtherCATManagementTreebook = EtherCATManagementTreebook(self.EtherCATManagementEditor, self.Controler, self)
       
   307           
       
   308         self.EtherCATManagermentEditor_Main_Sizer.AddSizer(self.EtherCATManagementTreebook, border=10, flag=wx.GROW)
       
   309 
       
   310         self.EtherCATManagementEditor.SetSizer(self.EtherCATManagermentEditor_Main_Sizer)
       
   311         return self.EtherCATManagementEditor
       
   312     
       
   313     def OnResize(self, event):
       
   314         self.EtherCATManagementEditor.GetBestSize()
       
   315         xstart, ystart = self.EtherCATManagementEditor.GetViewStart()
       
   316         window_size = self.EtherCATManagementEditor.GetClientSize()
       
   317         maxx, maxy = self.EtherCATManagementEditor.GetMinSize()
       
   318         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
   319         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
   320         self.EtherCATManagementEditor.Scroll(posx, posy)
       
   321         self.EtherCATManagementEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
   322                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
   323         event.Skip()
       
   324     # -------------------------------------------------------------------------------------------------------
       
   325 
       
   326 CIA402NodeEditor = NodeEditor
       
   327 
       
   328 
       
   329 def GetProcessVariablesTableColnames():
       
   330     _ = lambda x : x
       
   331     return ["#", _("Name"), 
       
   332             _("Read from (nodeid, index, subindex)"), 
       
   333             _("Write to (nodeid, index, subindex)"),
       
   334             _("Description")]
       
   335 
       
   336 class ProcessVariablesTable(CustomTable):
       
   337     
       
   338     def GetValue(self, row, col):
       
   339         if row < self.GetNumberRows():
       
   340             if col == 0:
       
   341                 return row + 1
       
   342             colname = self.GetColLabelValue(col, False)
       
   343             if colname.startswith("Read from"):
       
   344                 value = self.data[row].get("ReadFrom", "")
       
   345                 if value == "":
       
   346                     return value
       
   347                 return "%d, #x%0.4X, #x%0.2X" % value
       
   348             elif colname.startswith("Write to"):
       
   349                 value = self.data[row].get("WriteTo", "")
       
   350                 if value == "":
       
   351                     return value
       
   352                 return "%d, #x%0.4X, #x%0.2X" % value
       
   353             return self.data[row].get(colname, "")
       
   354     
       
   355     def SetValue(self, row, col, value):
       
   356         if col < len(self.colnames):
       
   357             colname = self.GetColLabelValue(col, False)
       
   358             if colname.startswith("Read from"):
       
   359                 self.data[row]["ReadFrom"] = value
       
   360             elif colname.startswith("Write to"):
       
   361                 self.data[row]["WriteTo"] = value
       
   362             else:
       
   363                 self.data[row][colname] = value
       
   364     
       
   365     def _updateColAttrs(self, grid):
       
   366         """
       
   367         wx.grid.Grid -> update the column attributes to add the
       
   368         appropriate renderer given the column name.
       
   369 
       
   370         Otherwise default to the default renderer.
       
   371         """
       
   372         for row in range(self.GetNumberRows()):
       
   373             for col in range(self.GetNumberCols()):
       
   374                 editor = None
       
   375                 renderer = None
       
   376                 colname = self.GetColLabelValue(col, False)
       
   377                 if colname in ["Name", "Description"]:
       
   378                     editor = wx.grid.GridCellTextEditor()
       
   379                     renderer = wx.grid.GridCellStringRenderer()
       
   380                     grid.SetReadOnly(row, col, False)
       
   381                 else:
       
   382                     grid.SetReadOnly(row, col, True)
       
   383                 
       
   384                 grid.SetCellEditor(row, col, editor)
       
   385                 grid.SetCellRenderer(row, col, renderer)
       
   386                 
       
   387             self.ResizeRow(grid, row)
       
   388 
       
   389 class ProcessVariableDropTarget(wx.TextDropTarget):
       
   390     
       
   391     def __init__(self, parent):
       
   392         wx.TextDropTarget.__init__(self)
       
   393         self.ParentWindow = parent
       
   394     
       
   395     def OnDropText(self, x, y, data):
       
   396         self.ParentWindow.Select()
       
   397         x, y = self.ParentWindow.ProcessVariablesGrid.CalcUnscrolledPosition(x, y)
       
   398         col = self.ParentWindow.ProcessVariablesGrid.XToCol(x)
       
   399         row = self.ParentWindow.ProcessVariablesGrid.YToRow(y - self.ParentWindow.ProcessVariablesGrid.GetColLabelSize())
       
   400         message = None
       
   401         try:
       
   402             values = eval(data)
       
   403         except:
       
   404             message = _("Invalid value \"%s\" for process variable")%data
       
   405             values = None
       
   406         if not isinstance(values, TupleType):
       
   407             message = _("Invalid value \"%s\" for process variable")%data
       
   408             values = None
       
   409         if values is not None and col != wx.NOT_FOUND and row != wx.NOT_FOUND and 2 <= col <= 3:
       
   410             location = None
       
   411             if values[1] == "location":
       
   412                 result = LOCATION_MODEL.match(values[0])
       
   413                 if result is not None:
       
   414                     location = map(int, result.group(1).split('.'))
       
   415                 master_location = self.ParentWindow.GetMasterLocation()
       
   416                 if (master_location == tuple(location[:len(master_location)]) and 
       
   417                     len(location) - len(master_location) == 3):
       
   418                     values = tuple(location[len(master_location):])
       
   419                     var_type = self.ParentWindow.Controler.GetSlaveVariableDataType(*values)
       
   420                     if col == 2:
       
   421                         other_values = self.ParentWindow.ProcessVariablesTable.GetValueByName(row, "WriteTo")
       
   422                     else:
       
   423                         other_values = self.ParentWindow.ProcessVariablesTable.GetValueByName(row, "ReadFrom")
       
   424                     if other_values != "":
       
   425                         other_type = self.ParentWindow.Controler.GetSlaveVariableDataType(*other_values)
       
   426                     else:
       
   427                         other_type = None
       
   428                     if other_type is None or var_type == other_type:
       
   429                         if col == 2:
       
   430                             self.ParentWindow.ProcessVariablesTable.SetValueByName(row, "ReadFrom", values)
       
   431                         else:
       
   432                             self.ParentWindow.ProcessVariablesTable.SetValueByName(row, "WriteTo", values)
       
   433                         self.ParentWindow.SaveProcessVariables()
       
   434                         self.ParentWindow.RefreshProcessVariables()
       
   435                     else:
       
   436                         message = _("'Read from' and 'Write to' variables types are not compatible")
       
   437                 else:
       
   438                     message = _("Invalid value \"%s\" for process variable")%data
       
   439                     
       
   440         if message is not None:
       
   441             wx.CallAfter(self.ShowMessage, message)
       
   442     
       
   443     def ShowMessage(self, message):
       
   444         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   445         message.ShowModal()
       
   446         message.Destroy()
       
   447 
       
   448 def GetStartupCommandsTableColnames():
       
   449     _ = lambda x : x
       
   450     return [_("Position"), _("Index"), _("Subindex"), _("Value"), _("Description")]
       
   451 
       
   452 class StartupCommandDropTarget(wx.TextDropTarget):
       
   453     
       
   454     def __init__(self, parent):
       
   455         wx.TextDropTarget.__init__(self)
       
   456         self.ParentWindow = parent
       
   457     
       
   458     def OnDropText(self, x, y, data):
       
   459         self.ParentWindow.Select()
       
   460         message = None
       
   461         try:
       
   462             values = eval(data)
       
   463         except:
       
   464             message = _("Invalid value \"%s\" for startup command")%data
       
   465             values = None
       
   466         if not isinstance(values, TupleType):
       
   467             message = _("Invalid value \"%s\" for startup command")%data
       
   468             values = None
       
   469         if values is not None:
       
   470             location = None
       
   471             if values[1] == "location":
       
   472                 result = LOCATION_MODEL.match(values[0])
       
   473                 if result is not None and len(values) > 5:
       
   474                     location = map(int, result.group(1).split('.'))
       
   475                     access = values[5]
       
   476             elif values[1] == "variable":
       
   477                 location = values[0]
       
   478                 access = values[2]
       
   479             if location is not None:
       
   480                 master_location = self.ParentWindow.GetMasterLocation()
       
   481                 if (master_location == tuple(location[:len(master_location)]) and 
       
   482                     len(location) - len(master_location) == 3):
       
   483                     if access in ["wo", "rw"]:
       
   484                         self.ParentWindow.AddStartupCommand(*location[len(master_location):])
       
   485                     else:
       
   486                         message = _("Entry can't be write through SDO")
       
   487                 else:
       
   488                     message = _("Invalid value \"%s\" for startup command")%data
       
   489                     
       
   490         if message is not None:
       
   491             wx.CallAfter(self.ShowMessage, message)
       
   492     
       
   493     def ShowMessage(self, message):
       
   494         message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   495         message.ShowModal()
       
   496         message.Destroy()
       
   497 
       
   498 class StartupCommandsTable(CustomTable):
       
   499 
       
   500     """
       
   501     A custom wx.grid.Grid Table using user supplied data
       
   502     """
       
   503     def __init__(self, parent, data, colnames):
       
   504         # The base class must be initialized *first*
       
   505         CustomTable.__init__(self, parent, data, colnames)
       
   506         self.old_value = None
       
   507 
       
   508     def GetValue(self, row, col):
       
   509         if row < self.GetNumberRows():
       
   510             colname = self.GetColLabelValue(col, False)
       
   511             value = self.data[row].get(colname, "")
       
   512             if colname == "Index":
       
   513                 return "#x%0.4X" % value
       
   514             elif colname == "Subindex":
       
   515                 return "#x%0.2X" % value
       
   516             return value
       
   517     
       
   518     def SetValue(self, row, col, value):
       
   519         if col < len(self.colnames):
       
   520             colname = self.GetColLabelValue(col, False)
       
   521             if colname in ["Index", "Subindex"]:
       
   522                 if colname == "Index":
       
   523                     result = ETHERCAT_INDEX_MODEL.match(value)
       
   524                 else:
       
   525                     result = ETHERCAT_SUBINDEX_MODEL.match(value)
       
   526                 if result is None:
       
   527                     return
       
   528                 value = int(result.group(1), 16)
       
   529             elif colname == "Value":
       
   530                 value = int(value)
       
   531             elif colname == "Position":
       
   532                 self.old_value = self.data[row][colname]
       
   533                 value = int(value)
       
   534             self.data[row][colname] = value
       
   535     
       
   536     def GetOldValue(self):
       
   537         return self.old_value
       
   538     
       
   539     def _updateColAttrs(self, grid):
       
   540         """
       
   541         wx.grid.Grid -> update the column attributes to add the
       
   542         appropriate renderer given the column name.
       
   543 
       
   544         Otherwise default to the default renderer.
       
   545         """
       
   546         for row in range(self.GetNumberRows()):
       
   547             for col in range(self.GetNumberCols()):
       
   548                 editor = None
       
   549                 renderer = None
       
   550                 colname = self.GetColLabelValue(col, False)
       
   551                 if colname in ["Position", "Value"]:
       
   552                     editor = wx.grid.GridCellNumberEditor()
       
   553                     renderer = wx.grid.GridCellNumberRenderer()
       
   554                 else:
       
   555                     editor = wx.grid.GridCellTextEditor()
       
   556                     renderer = wx.grid.GridCellStringRenderer()
       
   557                 
       
   558                 grid.SetCellEditor(row, col, editor)
       
   559                 grid.SetCellRenderer(row, col, renderer)
       
   560                 grid.SetReadOnly(row, col, False)
       
   561                 
       
   562             self.ResizeRow(grid, row)
       
   563     
       
   564     def GetCommandIndex(self, position, command_idx):
       
   565         for row, command in enumerate(self.data):
       
   566             if command["Position"] == position and command["command_idx"] == command_idx:
       
   567                 return row
       
   568         return None
       
   569 
       
   570 class MasterNodesVariablesSizer(NodeVariablesSizer):
       
   571     
       
   572     def __init__(self, parent, controler):
       
   573         NodeVariablesSizer.__init__(self, parent, controler, True)
       
   574         
       
   575         self.CurrentNodesFilter = {}
       
   576     
       
   577     def SetCurrentNodesFilter(self, nodes_filter):
       
   578         self.CurrentNodesFilter = nodes_filter
       
   579         
       
   580     def RefreshView(self):
       
   581         if self.CurrentNodesFilter is not None:
       
   582             args = self.CurrentNodesFilter.copy()
       
   583             args["limits"] = self.CurrentFilter
       
   584             entries = self.Controler.GetNodesVariables(**args)
       
   585             self.RefreshVariablesGrid(entries)
       
   586 
       
   587 NODE_POSITION_FILTER_FORMAT = _("Node Position: %d")
       
   588 
       
   589 class MasterEditor(ConfTreeNodeEditor):
       
   590     
       
   591     CONFNODEEDITOR_TABS = [
       
   592         (_("Network"), "_create_EthercatMasterEditor"),
       
   593         (_("Master State"), "_create_MasterStateEditor")
       
   594         ]
       
   595     
       
   596     def _create_MasterStateEditor(self, prnt):
       
   597         self.MasterStateEditor = wx.ScrolledWindow(prnt, style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
   598         self.MasterStateEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
   599         
       
   600         self.MasterStateEditor_Panel_Main_Sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
       
   601         self.MasterStateEditor_Panel_Main_Sizer.AddGrowableCol(0)
       
   602         self.MasterStateEditor_Panel_Main_Sizer.AddGrowableRow(0)
       
   603         
       
   604         self.MasterStateEditor_Panel = MasterStatePanelClass(self.MasterStateEditor, self.Controler)
       
   605         
       
   606         self.MasterStateEditor_Panel_Main_Sizer.AddSizer(self.MasterStateEditor_Panel, border=10, flag=wx.GROW)
       
   607          
       
   608         self.MasterStateEditor.SetSizer(self.MasterStateEditor_Panel_Main_Sizer)
       
   609         return self.MasterStateEditor
       
   610     
       
   611     def OnResize(self, event):
       
   612         self.MasterStateEditor.GetBestSize()
       
   613         xstart, ystart = self.MasterStateEditor.GetViewStart()
       
   614         window_size = self.MasterStateEditor.GetClientSize()
       
   615         maxx, maxy = self.MasterStateEditor.GetMinSize()
       
   616         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
   617         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
   618         self.MasterStateEditor.Scroll(posx, posy)
       
   619         self.MasterStateEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
   620                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
   621         event.Skip()
       
   622     
       
   623     def _create_EthercatMasterEditor(self, prnt):
       
   624         self.EthercatMasterEditor = wx.ScrolledWindow(prnt, 
       
   625             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
   626         self.EthercatMasterEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
   627         
       
   628         self.EthercatMasterEditorSizer = wx.BoxSizer(wx.VERTICAL)
       
   629         
       
   630         self.NodesFilter = wx.ComboBox(self.EthercatMasterEditor,
       
   631             style=wx.TE_PROCESS_ENTER)
       
   632         self.Bind(wx.EVT_COMBOBOX, self.OnNodesFilterChanged, self.NodesFilter)
       
   633         self.Bind(wx.EVT_TEXT_ENTER, self.OnNodesFilterChanged, self.NodesFilter)
       
   634         self.NodesFilter.Bind(wx.EVT_CHAR, self.OnNodesFilterKeyDown)
       
   635         
       
   636         process_variables_header = wx.BoxSizer(wx.HORIZONTAL)
       
   637         
       
   638         process_variables_label = wx.StaticText(self.EthercatMasterEditor,
       
   639               label=_("Process variables mapped between nodes:"))
       
   640         process_variables_header.AddWindow(process_variables_label, 1,
       
   641               flag=wx.ALIGN_CENTER_VERTICAL)
       
   642         
       
   643         for name, bitmap, help in [
       
   644                 ("AddVariableButton", "add_element", _("Add process variable")),
       
   645                 ("DeleteVariableButton", "remove_element", _("Remove process variable")),
       
   646                 ("UpVariableButton", "up", _("Move process variable up")),
       
   647                 ("DownVariableButton", "down", _("Move process variable down"))]:
       
   648             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), 
       
   649                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   650             button.SetToolTipString(help)
       
   651             setattr(self, name, button)
       
   652             process_variables_header.AddWindow(button, border=5, flag=wx.LEFT)
       
   653         
       
   654         self.ProcessVariablesGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
       
   655         self.ProcessVariablesGrid.SetMinSize(wx.Size(0, 150))
       
   656         self.ProcessVariablesGrid.SetDropTarget(ProcessVariableDropTarget(self))
       
   657         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   658               self.OnProcessVariablesGridCellChange)
       
   659         self.ProcessVariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, 
       
   660               self.OnProcessVariablesGridCellLeftClick)
       
   661         self.ProcessVariablesGrid.Bind(wx.EVT_KEY_DOWN, self.OnProcessVariablesGridKeyDown)
       
   662         
       
   663         startup_commands_header = wx.BoxSizer(wx.HORIZONTAL)
       
   664         
       
   665         startup_commands_label = wx.StaticText(self.EthercatMasterEditor,
       
   666               label=_("Startup service variables assignments:"))
       
   667         startup_commands_header.AddWindow(startup_commands_label, 1,
       
   668               flag=wx.ALIGN_CENTER_VERTICAL)
       
   669         
       
   670         for name, bitmap, help in [
       
   671                 ("AddCommandButton", "add_element", _("Add startup service variable")),
       
   672                 ("DeleteCommandButton", "remove_element", _("Remove startup service variable"))]:
       
   673             button = wx.lib.buttons.GenBitmapButton(self.EthercatMasterEditor, bitmap=GetBitmap(bitmap), 
       
   674                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
   675             button.SetToolTipString(help)
       
   676             setattr(self, name, button)
       
   677             startup_commands_header.AddWindow(button, border=5, flag=wx.LEFT)
       
   678         
       
   679         self.StartupCommandsGrid = CustomGrid(self.EthercatMasterEditor, style=wx.VSCROLL)
       
   680         self.StartupCommandsGrid.SetDropTarget(StartupCommandDropTarget(self))
       
   681         self.StartupCommandsGrid.SetMinSize(wx.Size(0, 150))
       
   682         self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, 
       
   683               self.OnStartupCommandsGridCellChange)
       
   684         self.StartupCommandsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, 
       
   685               self.OnStartupCommandsGridEditorShow)
       
   686         
       
   687         self.NodesVariables = MasterNodesVariablesSizer(self.EthercatMasterEditor, self.Controler)
       
   688         
       
   689         main_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Node filter:"))
       
   690         staticbox_sizer = wx.StaticBoxSizer(main_staticbox, wx.VERTICAL)
       
   691         self.EthercatMasterEditorSizer.AddSizer(staticbox_sizer, 0, border=10, flag=wx.GROW|wx.ALL)
       
   692         
       
   693         main_staticbox_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=6, vgap=0)
       
   694         main_staticbox_sizer.AddGrowableCol(0)
       
   695         main_staticbox_sizer.AddGrowableRow(2)
       
   696         main_staticbox_sizer.AddGrowableRow(4)
       
   697         main_staticbox_sizer.AddGrowableRow(5)
       
   698         staticbox_sizer.AddSizer(main_staticbox_sizer, 1, flag=wx.GROW)
       
   699         main_staticbox_sizer.AddWindow(self.NodesFilter, border=5, flag=wx.GROW|wx.ALL)
       
   700         main_staticbox_sizer.AddSizer(process_variables_header, border=5, 
       
   701               flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   702         main_staticbox_sizer.AddWindow(self.ProcessVariablesGrid, 1, 
       
   703               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   704         main_staticbox_sizer.AddSizer(startup_commands_header, 
       
   705               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   706         main_staticbox_sizer.AddWindow(self.StartupCommandsGrid, 1, 
       
   707               border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   708         
       
   709         second_staticbox = wx.StaticBox(self.EthercatMasterEditor, label=_("Nodes variables filter:"))
       
   710         second_staticbox_sizer = wx.StaticBoxSizer(second_staticbox, wx.VERTICAL)
       
   711         second_staticbox_sizer.AddSizer(self.NodesVariables, 1, border=5, flag=wx.GROW|wx.ALL)
       
   712         
       
   713         main_staticbox_sizer.AddSizer(second_staticbox_sizer, 1, 
       
   714             border=5, flag=wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM)
       
   715         
       
   716         self.EthercatMasterEditor.SetSizer(self.EthercatMasterEditorSizer)
       
   717         
       
   718         return self.EthercatMasterEditor
       
   719 
       
   720     def __init__(self, parent, controler, window):
       
   721         ConfTreeNodeEditor.__init__(self, parent, controler, window)
       
   722         
       
   723         # ------------------------------------------------------------------
       
   724         self.Controler = controler
       
   725         # ------------------------------------------------------------------
       
   726         
       
   727         self.ProcessVariables = []
       
   728         self.CellShown = None
       
   729         self.NodesFilterFirstCharacter = True
       
   730         
       
   731         self.ProcessVariablesDefaultValue = {"Name": "", "ReadFrom": "", "WriteTo": "", "Description": ""}
       
   732         self.ProcessVariablesTable = ProcessVariablesTable(self, [], GetProcessVariablesTableColnames())
       
   733         self.ProcessVariablesColSizes = [40, 100, 150, 150, 200]
       
   734         self.ProcessVariablesColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]
       
   735         
       
   736         self.ProcessVariablesGrid.SetTable(self.ProcessVariablesTable)
       
   737         self.ProcessVariablesGrid.SetButtons({"Add": self.AddVariableButton,
       
   738                                               "Delete": self.DeleteVariableButton,
       
   739                                               "Up": self.UpVariableButton,
       
   740                                               "Down": self.DownVariableButton})
       
   741         
       
   742         def _AddVariablesElement(new_row):
       
   743             self.ProcessVariablesTable.InsertRow(new_row, self.ProcessVariablesDefaultValue.copy())
       
   744             self.SaveProcessVariables()
       
   745             self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   746             return new_row
       
   747         setattr(self.ProcessVariablesGrid, "_AddRow", _AddVariablesElement)
       
   748         
       
   749         def _DeleteVariablesElement(row):
       
   750             self.ProcessVariablesTable.RemoveRow(row)
       
   751             self.SaveProcessVariables()
       
   752             self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   753         setattr(self.ProcessVariablesGrid, "_DeleteRow", _DeleteVariablesElement)
       
   754             
       
   755         def _MoveVariablesElement(row, move):
       
   756             new_row = self.ProcessVariablesTable.MoveRow(row, move)
       
   757             if new_row != row:
       
   758                 self.SaveProcessVariables()
       
   759                 self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   760             return new_row
       
   761         setattr(self.ProcessVariablesGrid, "_MoveRow", _MoveVariablesElement)
       
   762         
       
   763         _refresh_buttons = getattr(self.ProcessVariablesGrid, "RefreshButtons")
       
   764         def _RefreshButtons():
       
   765             if self.NodesFilter.GetSelection() == 0:
       
   766                 _refresh_buttons()
       
   767             else:
       
   768                 self.AddVariableButton.Enable(False)
       
   769                 self.DeleteVariableButton.Enable(False)
       
   770                 self.UpVariableButton.Enable(False)
       
   771                 self.DownVariableButton.Enable(False)
       
   772         setattr(self.ProcessVariablesGrid, "RefreshButtons", _RefreshButtons)
       
   773         
       
   774         self.ProcessVariablesGrid.SetRowLabelSize(0)
       
   775         for col in range(self.ProcessVariablesTable.GetNumberCols()):
       
   776             attr = wx.grid.GridCellAttr()
       
   777             attr.SetAlignment(self.ProcessVariablesColAlignements[col], wx.ALIGN_CENTRE)
       
   778             self.ProcessVariablesGrid.SetColAttr(col, attr)
       
   779             self.ProcessVariablesGrid.SetColMinimalWidth(col, self.ProcessVariablesColSizes[col])
       
   780             self.ProcessVariablesGrid.AutoSizeColumn(col, False)
       
   781         self.ProcessVariablesGrid.RefreshButtons()
       
   782     
       
   783         self.StartupCommandsDefaultValue = {"Position": 0, "Index": 0, "Subindex": 0, "Value": 0, "Description": ""}
       
   784         self.StartupCommandsTable = StartupCommandsTable(self, [], GetStartupCommandsTableColnames())
       
   785         self.StartupCommandsColSizes = [100, 100, 50, 100, 200]
       
   786         self.StartupCommandsColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_RIGHT, wx.ALIGN_LEFT]
       
   787         
       
   788         self.StartupCommandsGrid.SetTable(self.StartupCommandsTable)
       
   789         self.StartupCommandsGrid.SetButtons({"Add": self.AddCommandButton,
       
   790                                              "Delete": self.DeleteCommandButton})
       
   791         
       
   792         def _AddCommandsElement(new_row):
       
   793             command = self.StartupCommandsDefaultValue.copy()
       
   794             command_idx = self.Controler.AppendStartupCommand(command)
       
   795             self.RefreshStartupCommands()
       
   796             self.RefreshBuffer()
       
   797             return self.StartupCommandsTable.GetCommandIndex(command["Position"], command_idx)
       
   798         setattr(self.StartupCommandsGrid, "_AddRow", _AddCommandsElement)
       
   799         
       
   800         def _DeleteCommandsElement(row):
       
   801             command = self.StartupCommandsTable.GetRow(row)
       
   802             self.Controler.RemoveStartupCommand(command["Position"], command["command_idx"])
       
   803             self.RefreshStartupCommands()
       
   804             self.RefreshBuffer()
       
   805         setattr(self.StartupCommandsGrid, "_DeleteRow", _DeleteCommandsElement)
       
   806         
       
   807         self.StartupCommandsGrid.SetRowLabelSize(0)
       
   808         for col in range(self.StartupCommandsTable.GetNumberCols()):
       
   809             attr = wx.grid.GridCellAttr()
       
   810             attr.SetAlignment(self.StartupCommandsColAlignements[col], wx.ALIGN_CENTRE)
       
   811             self.StartupCommandsGrid.SetColAttr(col, attr)
       
   812             self.StartupCommandsGrid.SetColMinimalWidth(col, self.StartupCommandsColSizes[col])
       
   813             self.StartupCommandsGrid.AutoSizeColumn(col, False)
       
   814         self.StartupCommandsGrid.RefreshButtons()
       
   815     
       
   816     def RefreshBuffer(self):
       
   817         self.ParentWindow.RefreshTitle()
       
   818         self.ParentWindow.RefreshFileMenu()
       
   819         self.ParentWindow.RefreshEditMenu()
       
   820         self.ParentWindow.RefreshPageTitles()
       
   821     
       
   822     def GetBufferState(self):
       
   823         return self.Controler.GetBufferState()
       
   824     
       
   825     def Undo(self):
       
   826         self.Controler.LoadPrevious()
       
   827         self.RefreshView()
       
   828             
       
   829     def Redo(self):
       
   830         self.Controler.LoadNext()
       
   831         self.RefreshView()
       
   832     
       
   833     def RefreshView(self):
       
   834         ConfTreeNodeEditor.RefreshView(self)
       
   835         
       
   836         self.RefreshNodesFilter()
       
   837         self.RefreshProcessVariables()
       
   838         self.RefreshStartupCommands()
       
   839         self.NodesVariables.RefreshView()
       
   840     
       
   841     def RefreshNodesFilter(self):
       
   842         value = self.NodesFilter.GetValue()
       
   843         self.NodesFilter.Clear()
       
   844         self.NodesFilter.Append(_("All"))
       
   845         self.NodesFilterValues = [{}]
       
   846         for vendor_id, vendor_name in self.Controler.GetLibraryVendors():
       
   847             self.NodesFilter.Append(_("%s's nodes") % vendor_name)
       
   848             self.NodesFilterValues.append({"vendor": vendor_id})
       
   849         self.NodesFilter.Append(_("CIA402 nodes"))
       
   850         self.NodesFilterValues.append({"slave_profile": 402})
       
   851         if value in self.NodesFilter.GetStrings():
       
   852             self.NodesFilter.SetStringSelection(value)
       
   853         else:
       
   854             try:
       
   855                 int(value)
       
   856                 self.NodesFilter.SetValue(value)
       
   857             except:
       
   858                 self.NodesFilter.SetSelection(0)
       
   859         self.RefreshCurrentNodesFilter()
       
   860     
       
   861     def RefreshCurrentNodesFilter(self):
       
   862         filter = self.NodesFilter.GetSelection()
       
   863         if filter != -1:
       
   864             self.CurrentNodesFilter = self.NodesFilterValues[filter]
       
   865         else:
       
   866             try:
       
   867                 value = self.NodesFilter.GetValue()
       
   868                 if value == "":
       
   869                     self.CurrentNodesFilter = self.NodesFilterValues[0]
       
   870                     self.NodesFilter.SetSelection(0)
       
   871                 else:
       
   872                     position = int(self.NodesFilter.GetValue())
       
   873                     self.CurrentNodesFilter = {"slave_pos": position}
       
   874                     self.NodesFilter.SetValue(NODE_POSITION_FILTER_FORMAT % position)
       
   875             except:
       
   876                 if self.CurrentNodesFilter in self.NodesFilterValues:
       
   877                     self.NodesFilter.SetSelection(self.NodesFilterValues.index(self.CurrentNodesFilter))
       
   878                 else:
       
   879                     self.NodesFilter.SetValue(NODE_POSITION_FILTER_FORMAT % self.CurrentNodesFilter["slave_pos"])
       
   880         self.NodesFilterFirstCharacter = True
       
   881         self.NodesVariables.SetCurrentNodesFilter(self.CurrentNodesFilter)
       
   882     
       
   883     def RefreshProcessVariables(self):
       
   884         if self.CurrentNodesFilter is not None:
       
   885             self.ProcessVariables = self.Controler.GetProcessVariables()
       
   886             slaves = self.Controler.GetSlaves(**self.CurrentNodesFilter)
       
   887             data = []
       
   888             for variable in self.ProcessVariables:
       
   889                 if (variable["ReadFrom"] == "" or variable["ReadFrom"][0] in slaves or
       
   890                     variable["WriteTo"] == "" or variable["WriteTo"][0] in slaves):
       
   891                     data.append(variable)
       
   892             self.ProcessVariablesTable.SetData(data)
       
   893             self.ProcessVariablesTable.ResetView(self.ProcessVariablesGrid)
       
   894             self.ProcessVariablesGrid.RefreshButtons()
       
   895     
       
   896     def SaveProcessVariables(self):
       
   897         if self.CurrentNodesFilter is not None:
       
   898             if len(self.CurrentNodesFilter) > 0:
       
   899                 self.Controler.SetProcessVariables(self.ProcessVariables)
       
   900             else:
       
   901                 self.Controler.SetProcessVariables(self.ProcessVariablesTable.GetData())
       
   902             self.RefreshBuffer()
       
   903     
       
   904     def RefreshStartupCommands(self, position=None, command_idx=None):
       
   905         if self.CurrentNodesFilter is not None:
       
   906             col = max(self.StartupCommandsGrid.GetGridCursorCol(), 0)
       
   907             self.StartupCommandsTable.SetData(
       
   908                 self.Controler.GetStartupCommands(**self.CurrentNodesFilter))
       
   909             self.StartupCommandsTable.ResetView(self.StartupCommandsGrid)
       
   910             if position is not None and command_idx is not None:
       
   911                 self.SelectStartupCommand(position, command_idx, col)
       
   912     
       
   913     def SelectStartupCommand(self, position, command_idx, col):
       
   914         self.StartupCommandsGrid.SetSelectedCell(
       
   915             self.StartupCommandsTable.GetCommandIndex(position, command_idx),
       
   916             col)
       
   917     
       
   918     def GetMasterLocation(self):
       
   919         return self.Controler.GetCurrentLocation()
       
   920     
       
   921     def AddStartupCommand(self, position, index, subindex):
       
   922         col = max(self.StartupCommandsGrid.GetGridCursorCol(), 0)
       
   923         command = self.StartupCommandsDefaultValue.copy()
       
   924         command["Position"] = position
       
   925         command["Index"] = index
       
   926         command["Subindex"] = subindex
       
   927         command_idx = self.Controler.AppendStartupCommand(command)
       
   928         self.RefreshStartupCommands()
       
   929         self.RefreshBuffer()
       
   930         self.SelectStartupCommand(position, command_idx, col)
       
   931     
       
   932     def OnNodesFilterChanged(self, event):
       
   933         self.RefreshCurrentNodesFilter()
       
   934         if self.CurrentNodesFilter is not None:
       
   935             self.RefreshProcessVariables()
       
   936             self.RefreshStartupCommands()
       
   937             self.NodesVariables.RefreshView()
       
   938         event.Skip()
       
   939     
       
   940     def OnNodesFilterKeyDown(self, event):
       
   941         if self.NodesFilterFirstCharacter:
       
   942             keycode = event.GetKeyCode()
       
   943             if keycode not in [wx.WXK_RETURN, 
       
   944                                wx.WXK_NUMPAD_ENTER]:
       
   945                 self.NodesFilterFirstCharacter = False
       
   946                 if keycode not in NAVIGATION_KEYS:
       
   947                     self.NodesFilter.SetValue("")
       
   948             if keycode not in [wx.WXK_DELETE, 
       
   949                                wx.WXK_NUMPAD_DELETE, 
       
   950                                wx.WXK_BACK]:
       
   951                 event.Skip()
       
   952         else:
       
   953             event.Skip()
       
   954     
       
   955     def OnProcessVariablesGridCellChange(self, event):
       
   956         row, col = event.GetRow(), event.GetCol()
       
   957         colname = self.ProcessVariablesTable.GetColLabelValue(col, False)
       
   958         value = self.ProcessVariablesTable.GetValue(row, col)
       
   959         message = None
       
   960         if colname == "Name":
       
   961             if not TestIdentifier(value):
       
   962                 message = _("\"%s\" is not a valid identifier!") % value
       
   963             elif value.upper() in IEC_KEYWORDS:
       
   964                 message = _("\"%s\" is a keyword. It can't be used!") % value
       
   965             elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.ProcessVariablesTable.GetData()) if idx != row]:
       
   966                 message = _("An variable named \"%s\" already exists!") % value
       
   967         if message is None:
       
   968             self.SaveProcessVariables()
       
   969             wx.CallAfter(self.ProcessVariablesTable.ResetView, self.ProcessVariablesGrid)
       
   970             event.Skip()
       
   971         else:
       
   972             dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   973             dialog.ShowModal()
       
   974             dialog.Destroy()
       
   975             event.Veto()
       
   976     
       
   977     def OnProcessVariablesGridCellLeftClick(self, event):
       
   978         row = event.GetRow()
       
   979         if event.GetCol() == 0:
       
   980             var_name = self.ProcessVariablesTable.GetValueByName(row, "Name")
       
   981             var_type = self.Controler.GetSlaveVariableDataType(
       
   982                 *self.ProcessVariablesTable.GetValueByName(row, "ReadFrom"))
       
   983             data_size = self.Controler.GetSizeOfType(var_type)
       
   984             number = self.ProcessVariablesTable.GetValueByName(row, "Number")
       
   985             location = "%%M%s" % data_size + \
       
   986                        ".".join(map(lambda x:str(x), self.Controler.GetCurrentLocation() + (number,)))
       
   987             
       
   988             data = wx.TextDataObject(str((location, "location", var_type, var_name, "")))
       
   989             dragSource = wx.DropSource(self.ProcessVariablesGrid)
       
   990             dragSource.SetData(data)
       
   991             dragSource.DoDragDrop()
       
   992         event.Skip()
       
   993     
       
   994     def OnProcessVariablesGridKeyDown(self, event):
       
   995         keycode = event.GetKeyCode()
       
   996         col = self.ProcessVariablesGrid.GetGridCursorCol()
       
   997         row = self.ProcessVariablesGrid.GetGridCursorRow()
       
   998         colname = self.ProcessVariablesTable.GetColLabelValue(col, False)
       
   999         if (keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and 
       
  1000             (colname.startswith("Read from") or colname.startswith("Write to"))):
       
  1001             self.ProcessVariablesTable.SetValue(row, col, "")
       
  1002             self.SaveProcessVariables()
       
  1003             wx.CallAfter(self.ProcessVariablesTable.ResetView, self.ProcessVariablesGrid)
       
  1004         else:
       
  1005             event.Skip()
       
  1006     
       
  1007     def OnStartupCommandsGridEditorShow(self, event):
       
  1008         self.CellShown = event.GetRow(), event.GetCol()
       
  1009         event.Skip()
       
  1010     
       
  1011     def OnStartupCommandsGridCellChange(self, event):
       
  1012         row, col = event.GetRow(), event.GetCol()
       
  1013         if self.CellShown == (row, col):
       
  1014             self.CellShown = None
       
  1015             colname = self.StartupCommandsTable.GetColLabelValue(col, False)
       
  1016             value = self.StartupCommandsTable.GetValue(row, col)
       
  1017             message = None
       
  1018             if colname == "Position":
       
  1019                 if value not in self.Controler.GetSlaves():
       
  1020                     message = _("No slave defined at position %d!") % value
       
  1021                 old_value = self.StartupCommandsTable.GetOldValue()
       
  1022                 command = self.StartupCommandsTable.GetRow(row)
       
  1023                 if message is None and old_value != command["Position"]:
       
  1024                     self.Controler.RemoveStartupCommand(
       
  1025                         self.StartupCommandsTable.GetOldValue(),
       
  1026                         command["command_idx"], False)
       
  1027                     command_idx = self.Controler.AppendStartupCommand(command)
       
  1028                     wx.CallAfter(self.RefreshStartupCommands, command["Position"], command_idx)
       
  1029             else:
       
  1030                 command = self.StartupCommandsTable.GetRow(row)
       
  1031                 self.Controler.SetStartupCommandInfos(command)
       
  1032                 if colname in ["Index", "SubIndex"]: 
       
  1033                     wx.CallAfter(self.RefreshStartupCommands, command["Position"], command["command_idx"])
       
  1034             if message is None:
       
  1035                 self.RefreshBuffer()
       
  1036                 event.Skip()
       
  1037             else:
       
  1038                 dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
  1039                 dialog.ShowModal()
       
  1040                 dialog.Destroy()
       
  1041                 event.Veto()
       
  1042         else:
       
  1043             event.Veto()
       
  1044     
       
  1045     def OnResize(self, event):
       
  1046         self.EthercatMasterEditor.GetBestSize()
       
  1047         xstart, ystart = self.EthercatMasterEditor.GetViewStart()
       
  1048         window_size = self.EthercatMasterEditor.GetClientSize()
       
  1049         maxx, maxy = self.EthercatMasterEditorSizer.GetMinSize()
       
  1050         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
  1051         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
  1052         self.EthercatMasterEditor.Scroll(posx, posy)
       
  1053         self.EthercatMasterEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
  1054                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
  1055         event.Skip()
       
  1056         
       
  1057     #def OnButtonClick(self, event):
       
  1058     #    self.MasterState = self.Controler.getMasterState()
       
  1059     #    if self.MasterState:
       
  1060     #        self.Phase.SetValue(self.MasterState["phase"])
       
  1061     #        self.Active.SetValue(self.MasterState["active"])
       
  1062     #        self.SlaveCount.SetValue(self.MasterState["slave"])
       
  1063     #        self.MacAddress.SetValue(self.MasterState["MAC"])
       
  1064     #        self.LinkState.SetValue(self.MasterState["link"])
       
  1065     #        self.TxFrames.SetValue(self.MasterState["TXframe"])
       
  1066     #        self.RxFrames.SetValue(self.MasterState["RXframe"])
       
  1067     #        self.TxByte.SetValue(self.MasterState["TXbyte"])
       
  1068     #        self.TxError.SetValue(self.MasterState["TXerror"])
       
  1069     #        self.LostFrames.SetValue(self.MasterState["lost"])
       
  1070             
       
  1071     #        self.TxFrameRate1.SetValue(self.MasterState["TXframerate1"])
       
  1072     #        self.TxFrameRate2.SetValue(self.MasterState["TXframerate2"])
       
  1073     #        self.TxFrameRate3.SetValue(self.MasterState["TXframerate3"])
       
  1074     #        self.TxRate1.SetValue(self.MasterState["TXrate1"])
       
  1075     #        self.TxRate2.SetValue(self.MasterState["TXrate2"])
       
  1076     #        self.TxRate3.SetValue(self.MasterState["TXrate3"])
       
  1077     #        self.LossRate1.SetValue(self.MasterState["loss1"])
       
  1078     #        self.LossRate2.SetValue(self.MasterState["loss2"])
       
  1079     #        self.LossRate3.SetValue(self.MasterState["loss3"])
       
  1080     #        self.FrameLoss1.SetValue(self.MasterState["frameloss1"])
       
  1081     #        self.FrameLoss2.SetValue(self.MasterState["frameloss2"])
       
  1082     #        self.FrameLoss3.SetValue(self.MasterState["frameloss3"])
       
  1083     
       
  1084 class LibraryEditorSizer(wx.FlexGridSizer):
       
  1085     
       
  1086     def __init__(self, parent, module_library, buttons):
       
  1087         wx.FlexGridSizer.__init__(self, cols=1, hgap=0, rows=4, vgap=5)
       
  1088         
       
  1089         self.ModuleLibrary = module_library
       
  1090         self.ParentWindow = parent
       
  1091         
       
  1092         self.AddGrowableCol(0)
       
  1093         self.AddGrowableRow(1)
       
  1094         self.AddGrowableRow(3)
       
  1095         
       
  1096         ESI_files_label = wx.StaticText(parent, 
       
  1097             label=_("ESI Files:"))
       
  1098         self.AddWindow(ESI_files_label, border=10, 
       
  1099             flag=wx.TOP|wx.LEFT|wx.RIGHT)
       
  1100         
       
  1101         folder_tree_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=0)
       
  1102         folder_tree_sizer.AddGrowableCol(0)
       
  1103         folder_tree_sizer.AddGrowableRow(0)
       
  1104         self.AddSizer(folder_tree_sizer, border=10, 
       
  1105             flag=wx.GROW|wx.LEFT|wx.RIGHT)
       
  1106         
       
  1107         self.ESIFiles = FolderTree(parent, self.GetPath(), editable=False)
       
  1108         self.ESIFiles.SetFilter(".xml")
       
  1109         folder_tree_sizer.AddWindow(self.ESIFiles, flag=wx.GROW)
       
  1110         
       
  1111         buttons_sizer = wx.BoxSizer(wx.VERTICAL)
       
  1112         folder_tree_sizer.AddSizer(buttons_sizer, 
       
  1113             flag=wx.ALIGN_CENTER_VERTICAL)
       
  1114         
       
  1115         for idx, (name, bitmap, help, callback) in enumerate(buttons):
       
  1116             button = wx.lib.buttons.GenBitmapButton(parent, 
       
  1117                   bitmap=GetBitmap(bitmap), 
       
  1118                   size=wx.Size(28, 28), style=wx.NO_BORDER)
       
  1119             button.SetToolTipString(help)
       
  1120             setattr(self, name, button)
       
  1121             if idx > 0:
       
  1122                 flag = wx.TOP
       
  1123             else:
       
  1124                 flag = 0
       
  1125             if callback is None:
       
  1126                 callback = getattr(self, "On" + name, None)
       
  1127             if callback is not None:
       
  1128                 parent.Bind(wx.EVT_BUTTON, callback, button)
       
  1129             buttons_sizer.AddWindow(button, border=10, flag=flag)
       
  1130         
       
  1131         modules_label = wx.StaticText(parent, 
       
  1132             label=_("Modules library:"))
       
  1133         self.AddSizer(modules_label, border=10, 
       
  1134             flag=wx.LEFT|wx.RIGHT)
       
  1135         
       
  1136         self.ModulesGrid = wx.gizmos.TreeListCtrl(parent,
       
  1137               style=wx.TR_DEFAULT_STYLE |
       
  1138                     wx.TR_ROW_LINES |
       
  1139                     wx.TR_COLUMN_LINES |
       
  1140                     wx.TR_HIDE_ROOT |
       
  1141                     wx.TR_FULL_ROW_HIGHLIGHT)
       
  1142         self.ModulesGrid.GetMainWindow().Bind(wx.EVT_LEFT_DOWN,
       
  1143             self.OnModulesGridLeftDown)
       
  1144         self.ModulesGrid.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT,
       
  1145             self.OnModulesGridBeginLabelEdit)
       
  1146         self.ModulesGrid.Bind(wx.EVT_TREE_END_LABEL_EDIT,
       
  1147             self.OnModulesGridEndLabelEdit)
       
  1148         self.ModulesGrid.GetHeaderWindow().Bind(wx.EVT_MOTION, 
       
  1149             self.OnModulesGridHeaderMotion)
       
  1150         self.AddWindow(self.ModulesGrid, border=10, 
       
  1151             flag=wx.GROW|wx.BOTTOM|wx.LEFT|wx.RIGHT)
       
  1152         
       
  1153         for colname, colsize, colalign in zip(
       
  1154                 [_("Name")] + [param_infos["column_label"] 
       
  1155                                for param, param_infos in 
       
  1156                                self.ModuleLibrary.MODULES_EXTRA_PARAMS],
       
  1157                 [400] + [param_infos["column_size"] 
       
  1158                          for param, param_infos in 
       
  1159                          self.ModuleLibrary.MODULES_EXTRA_PARAMS],
       
  1160                 [wx.ALIGN_LEFT] + [wx.ALIGN_RIGHT] * len(self.ModuleLibrary.MODULES_EXTRA_PARAMS)):
       
  1161             self.ModulesGrid.AddColumn(_(colname), colsize, colalign, edit=True)
       
  1162         self.ModulesGrid.SetMainColumn(0)
       
  1163         
       
  1164         self.CurrentSelectedCol = None
       
  1165         self.LastToolTipCol = None
       
  1166     
       
  1167     def GetPath(self):
       
  1168         return self.ModuleLibrary.GetPath()
       
  1169     
       
  1170     def SetControlMinSize(self, size):
       
  1171         self.ESIFiles.SetMinSize(size)
       
  1172         self.ModulesGrid.SetMinSize(size)
       
  1173         
       
  1174     def GetSelectedFilePath(self):
       
  1175         return self.ESIFiles.GetPath()
       
  1176     
       
  1177     def RefreshView(self):
       
  1178         self.ESIFiles.RefreshTree()
       
  1179         self.RefreshModulesGrid()
       
  1180     
       
  1181     def RefreshModulesGrid(self):
       
  1182         root = self.ModulesGrid.GetRootItem()
       
  1183         if not root.IsOk():
       
  1184             root = self.ModulesGrid.AddRoot("Modules")
       
  1185         self.GenerateModulesGridBranch(root, 
       
  1186             self.ModuleLibrary.GetModulesLibrary(), 
       
  1187             GetVariablesTableColnames())
       
  1188         self.ModulesGrid.Expand(root)
       
  1189             
       
  1190     def GenerateModulesGridBranch(self, root, modules, colnames):
       
  1191         item, root_cookie = self.ModulesGrid.GetFirstChild(root)
       
  1192         
       
  1193         no_more_items = not item.IsOk()
       
  1194         for module in modules:
       
  1195             if no_more_items:
       
  1196                 item = self.ModulesGrid.AppendItem(root, "")
       
  1197             self.ModulesGrid.SetItemText(item, module["name"], 0)
       
  1198             if module["infos"] is not None:
       
  1199                 for param_idx, (param, param_infos) in enumerate(self.ModuleLibrary.MODULES_EXTRA_PARAMS):
       
  1200                     self.ModulesGrid.SetItemText(item, 
       
  1201                                                  str(module["infos"][param]), 
       
  1202                                                  param_idx + 1)
       
  1203             else:
       
  1204                 self.ModulesGrid.SetItemBackgroundColour(item, wx.LIGHT_GREY)
       
  1205             self.ModulesGrid.SetItemPyData(item, module["infos"])
       
  1206             self.GenerateModulesGridBranch(item, module["children"], colnames)
       
  1207             if not no_more_items:
       
  1208                 item, root_cookie = self.ModulesGrid.GetNextChild(root, root_cookie)
       
  1209                 no_more_items = not item.IsOk()
       
  1210         
       
  1211         if not no_more_items:
       
  1212             to_delete = []
       
  1213             while item.IsOk():
       
  1214                 to_delete.append(item)
       
  1215                 item, root_cookie = self.ModulesGrid.GetNextChild(root, root_cookie)
       
  1216             for item in to_delete:
       
  1217                 self.ModulesGrid.Delete(item)
       
  1218     
       
  1219     def OnImportButton(self, event):
       
  1220         dialog = wx.FileDialog(self.ParentWindow,
       
  1221              _("Choose an XML file"), 
       
  1222              os.getcwd(), "",  
       
  1223              _("XML files (*.xml)|*.xml|All files|*.*"), wx.OPEN)
       
  1224         
       
  1225         if dialog.ShowModal() == wx.ID_OK:
       
  1226             filepath = dialog.GetPath()
       
  1227             if self.ModuleLibrary.ImportModuleLibrary(filepath):
       
  1228                 wx.CallAfter(self.RefreshView)
       
  1229             else:
       
  1230                 message = wx.MessageDialog(self, 
       
  1231                     _("No such XML file: %s\n") % filepath, 
       
  1232                     _("Error"), wx.OK|wx.ICON_ERROR)
       
  1233                 message.ShowModal()
       
  1234                 message.Destroy()
       
  1235         dialog.Destroy()
       
  1236         
       
  1237         event.Skip()
       
  1238     
       
  1239     def OnDeleteButton(self, event):
       
  1240         filepath = self.GetSelectedFilePath()
       
  1241         if os.path.isfile(filepath):
       
  1242             folder, filename = os.path.split(filepath)
       
  1243             
       
  1244             dialog = wx.MessageDialog(self.ParentWindow, 
       
  1245                   _("Do you really want to delete the file '%s'?") % filename, 
       
  1246                   _("Delete File"), wx.YES_NO|wx.ICON_QUESTION)
       
  1247             remove = dialog.ShowModal() == wx.ID_YES
       
  1248             dialog.Destroy()
       
  1249             
       
  1250             if remove:
       
  1251                 os.remove(filepath)
       
  1252                 self.ModuleLibrary.LoadModules()
       
  1253                 wx.CallAfter(self.RefreshView)
       
  1254         event.Skip()
       
  1255     
       
  1256     def OnModulesGridLeftDown(self, event):
       
  1257         item, flags, col = self.ModulesGrid.HitTest(event.GetPosition())
       
  1258         if item.IsOk():
       
  1259             entry_infos = self.ModulesGrid.GetItemPyData(item)
       
  1260             if entry_infos is not None and col > 0:
       
  1261                 self.CurrentSelectedCol = col
       
  1262             else:
       
  1263                 self.CurrentSelectedCol = None
       
  1264         else:
       
  1265             self.CurrentSelectedCol = None
       
  1266         event.Skip()
       
  1267 
       
  1268     def OnModulesGridBeginLabelEdit(self, event):
       
  1269         item = event.GetItem()
       
  1270         if item.IsOk():
       
  1271             entry_infos = self.ModulesGrid.GetItemPyData(item)
       
  1272             if entry_infos is not None:
       
  1273                 event.Skip()
       
  1274             else:
       
  1275                 event.Veto()
       
  1276         else:
       
  1277             event.Veto()
       
  1278 
       
  1279     def OnModulesGridEndLabelEdit(self, event):
       
  1280         item = event.GetItem()
       
  1281         if item.IsOk() and self.CurrentSelectedCol is not None:
       
  1282             entry_infos = self.ModulesGrid.GetItemPyData(item)
       
  1283             if entry_infos is not None and self.CurrentSelectedCol > 0:
       
  1284                 param, param_infos = self.ModuleLibrary.MODULES_EXTRA_PARAMS[self.CurrentSelectedCol - 1]
       
  1285                 stripped_column_label = param_infos["column_label"].split('(')[0].strip()
       
  1286                 try:
       
  1287                     self.ModuleLibrary.SetModuleExtraParam(
       
  1288                         entry_infos["vendor"],
       
  1289                         entry_infos["product_code"],
       
  1290                         entry_infos["revision_number"],
       
  1291                         param,
       
  1292                         int(event.GetLabel()))
       
  1293                     wx.CallAfter(self.RefreshModulesGrid)
       
  1294                     event.Skip()
       
  1295                 except ValueError:
       
  1296                     message = wx.MessageDialog(self, 
       
  1297                         _("Module %s must be an integer!") % stripped_column_label, 
       
  1298                         _("Error"), wx.OK|wx.ICON_ERROR)
       
  1299                     message.ShowModal()
       
  1300                     message.Destroy()
       
  1301                     event.Veto()
       
  1302             else:
       
  1303                 event.Veto()
       
  1304         else:
       
  1305             event.Veto()
       
  1306                 
       
  1307     def OnModulesGridHeaderMotion(self, event):
       
  1308         item, flags, col = self.ModulesGrid.HitTest(event.GetPosition())
       
  1309         if col != self.LastToolTipCol and self.LastToolTipCol is not None:
       
  1310             self.ModulesGrid.GetHeaderWindow().SetToolTip(None)
       
  1311             self.LastToolTipCol = None
       
  1312         if col > 0 and self.LastToolTipCol != col:
       
  1313             self.LastToolTipCol = col
       
  1314             param, param_infos = self.ModuleLibrary.MODULES_EXTRA_PARAMS[col - 1]
       
  1315             wx.CallAfter(self.ModulesGrid.GetHeaderWindow().SetToolTipString, 
       
  1316                          param_infos["description"])
       
  1317         event.Skip()
       
  1318 
       
  1319 class DatabaseManagementDialog(wx.Dialog):
       
  1320     
       
  1321     def __init__(self, parent, database):
       
  1322         wx.Dialog.__init__(self, parent,
       
  1323               size=wx.Size(700, 500), title=_('ESI Files Database management'),
       
  1324               style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
       
  1325         
       
  1326         main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
       
  1327         main_sizer.AddGrowableCol(0)
       
  1328         main_sizer.AddGrowableRow(0)
       
  1329         
       
  1330         self.DatabaseSizer = LibraryEditorSizer(self, database,
       
  1331             [("ImportButton", "ImportESI", _("Import file to ESI files database"), None),
       
  1332              ("DeleteButton", "remove_element", _("Remove file from database"), None)])
       
  1333         self.DatabaseSizer.SetControlMinSize(wx.Size(0, 0))
       
  1334         main_sizer.AddSizer(self.DatabaseSizer, border=10,
       
  1335             flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT)
       
  1336         
       
  1337         button_sizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE)
       
  1338         button_sizer.GetAffirmativeButton().SetLabel(_("Add file to project"))
       
  1339         button_sizer.GetCancelButton().SetLabel(_("Close"))
       
  1340         main_sizer.AddSizer(button_sizer, border=10, 
       
  1341               flag=wx.ALIGN_RIGHT|wx.BOTTOM|wx.LEFT|wx.RIGHT)
       
  1342         
       
  1343         self.SetSizer(main_sizer)
       
  1344         
       
  1345         self.DatabaseSizer.RefreshView()
       
  1346         
       
  1347     def GetValue(self):
       
  1348         return self.DatabaseSizer.GetSelectedFilePath()
       
  1349 
       
  1350 class LibraryEditor(ConfTreeNodeEditor):
       
  1351     
       
  1352     CONFNODEEDITOR_TABS = [
       
  1353         (_("Modules Library"), "_create_ModuleLibraryEditor")]
       
  1354     
       
  1355     def _create_ModuleLibraryEditor(self, prnt):
       
  1356         self.ModuleLibraryEditor = wx.ScrolledWindow(prnt,
       
  1357             style=wx.TAB_TRAVERSAL|wx.HSCROLL|wx.VSCROLL)
       
  1358         self.ModuleLibraryEditor.Bind(wx.EVT_SIZE, self.OnResize)
       
  1359         
       
  1360         self.ModuleLibrarySizer = LibraryEditorSizer(self.ModuleLibraryEditor,
       
  1361             self.Controler.GetModulesLibraryInstance(),
       
  1362             [("ImportButton", "ImportESI", _("Import ESI file"), None),
       
  1363              ("AddButton", "ImportDatabase", _("Add file from ESI files database"), self.OnAddButton),
       
  1364              ("DeleteButton", "remove_element", _("Remove file from library"), None)])
       
  1365         self.ModuleLibrarySizer.SetControlMinSize(wx.Size(0, 200))
       
  1366         self.ModuleLibraryEditor.SetSizer(self.ModuleLibrarySizer)
       
  1367         
       
  1368         return self.ModuleLibraryEditor
       
  1369 
       
  1370     def __init__(self, parent, controler, window):
       
  1371         ConfTreeNodeEditor.__init__(self, parent, controler, window)
       
  1372     
       
  1373         self.RefreshView()
       
  1374     
       
  1375     def RefreshView(self):
       
  1376         ConfTreeNodeEditor.RefreshView(self)
       
  1377         self.ModuleLibrarySizer.RefreshView()
       
  1378 
       
  1379     def OnAddButton(self, event):
       
  1380         dialog = DatabaseManagementDialog(self, 
       
  1381             self.Controler.GetModulesDatabaseInstance())
       
  1382         
       
  1383         if dialog.ShowModal() == wx.ID_OK:
       
  1384             module_library = self.Controler.GetModulesLibraryInstance()
       
  1385             module_library.ImportModuleLibrary(dialog.GetValue())
       
  1386             
       
  1387         dialog.Destroy()
       
  1388         
       
  1389         wx.CallAfter(self.ModuleLibrarySizer.RefreshView)
       
  1390         
       
  1391         event.Skip()
       
  1392 
       
  1393     def OnResize(self, event):
       
  1394         self.ModuleLibraryEditor.GetBestSize()
       
  1395         xstart, ystart = self.ModuleLibraryEditor.GetViewStart()
       
  1396         window_size = self.ModuleLibraryEditor.GetClientSize()
       
  1397         maxx, maxy = self.ModuleLibraryEditor.GetMinSize()
       
  1398         posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
       
  1399         posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
       
  1400         self.ModuleLibraryEditor.Scroll(posx, posy)
       
  1401         self.ModuleLibraryEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT, 
       
  1402                 maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
       
  1403         event.Skip()
       
  1404