controls/VariablePanel.py
author laurent
Sun, 08 Jan 2012 19:16:58 +0100
changeset 616 8a60ffcfd70b
parent 606 d65122c61eaf
child 623 e747685e4241
permissions -rw-r--r--
Adding support for drag'n dropping variable from global defined in configurations and resources to POU variable panel or body editor for declaring external variables
Adding support for drag'n dropping located variables from topology panel to configurations and resources variable panel for declaring global located variables
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard. 
#
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#General Public License for more details.
#
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os
import wx, wx.grid

from types import TupleType, StringType, UnicodeType

from plcopen.structures import LOCATIONDATATYPES, TestIdentifier, IEC_KEYWORDS
from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD
from dialogs.ArrayTypeDialog import ArrayTypeDialog
from CustomGrid import CustomGrid
from CustomTable import CustomTable
from LocationCellEditor import LocationCellEditor

# Compatibility function for wx versions < 2.6
def AppendMenu(parent, help, id, kind, text):
    if wx.VERSION >= (2, 6, 0):
        parent.Append(help=help, id=id, kind=kind, text=text)
    else:
        parent.Append(helpString=help, id=id, kind=kind, item=text)

[TITLE, TOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU, TYPESTREE, 
 INSTANCESTREE, LIBRARYTREE, SCALING
] = range(9)

#-------------------------------------------------------------------------------
#                            Variables Editor Panel
#-------------------------------------------------------------------------------

def GetVariableTableColnames(location):
    _ = lambda x : x
    if location:
    	return ["#", _("Name"), _("Class"), _("Type"), _("Location"), _("Initial Value"), _("Option"), _("Documentation")]
    return ["#", _("Name"), _("Class"), _("Type"), _("Initial Value"), _("Option"), _("Documentation")]

def GetOptions(constant=True, retain=True, non_retain=True):
    _ = lambda x : x
    options = [""]
    if constant:
        options.append(_("Constant"))
    if retain:
        options.append(_("Retain"))
    if non_retain:
        options.append(_("Non-Retain"))
    return options
OPTIONS_DICT = dict([(_(option), option) for option in GetOptions()])

def GetFilterChoiceTransfer():
    _ = lambda x : x
    return {_("All"): _("All"), _("Interface"): _("Interface"), 
            _("   Input"): _("Input"), _("   Output"): _("Output"), _("   InOut"): _("InOut"), 
            _("   External"): _("External"), _("Variables"): _("Variables"), _("   Local"): _("Local"),
            _("   Temp"): _("Temp"), _("Global"): _("Global")}#, _("Access") : _("Access")}
VARIABLE_CHOICES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().iterkeys()])
VARIABLE_CLASSES_DICT = dict([(_(_class), _class) for _class in GetFilterChoiceTransfer().itervalues()])

CheckOptionForClass = {"Local": lambda x: x,
                       "Temp": lambda x: "",
                       "Input": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
                       "InOut": lambda x: "",
                       "Output": lambda x: {"Retain": "Retain", "Non-Retain": "Non-Retain"}.get(x, ""),
                       "Global": lambda x: {"Constant": "Constant", "Retain": "Retain"}.get(x, ""),
                       "External": lambda x: {"Constant": "Constant"}.get(x, "")
                      }

class VariableTable(CustomTable):
    
    """
    A custom wx.grid.Grid Table using user supplied data
    """
    def __init__(self, parent, data, colnames):
        # The base class must be initialized *first*
        CustomTable.__init__(self, parent, data, colnames)
        self.old_value = None
    
    def GetValue(self, row, col):
        if row < self.GetNumberRows():
            if col == 0:
                return self.data[row]["Number"]
            colname = self.GetColLabelValue(col, False)
            value = self.data[row].get(colname, "")
            if colname == "Type" and isinstance(value, TupleType):
                if value[0] == "array":
                    return "ARRAY [%s] OF %s" % (",".join(map(lambda x : "..".join(x), value[2])), value[1])
            if not isinstance(value, (StringType, UnicodeType)):
                value = str(value)
            if colname in ["Class", "Option"]:
                return _(value)
            return value
    
    def SetValue(self, row, col, value):
        if col < len(self.colnames):
            colname = self.GetColLabelValue(col, False)
            if colname == "Name":
                self.old_value = self.data[row][colname]
            elif colname == "Class":
                value = VARIABLE_CLASSES_DICT[value]
                self.SetValueByName(row, "Option", CheckOptionForClass[value](self.GetValueByName(row, "Option")))
                if value == "External":
                    self.SetValueByName(row, "Initial Value", "")
            elif colname == "Option":
                value = OPTIONS_DICT[value]
            self.data[row][colname] = value

    def GetOldValue(self):
        return self.old_value

    def _updateColAttrs(self, grid):
        """
        wx.grid.Grid -> update the column attributes to add the
        appropriate renderer given the column name.

        Otherwise default to the default renderer.
        """
        for row in range(self.GetNumberRows()):
            var_class = self.GetValueByName(row, "Class")
            var_type = self.GetValueByName(row, "Type")
            row_highlights = self.Highlights.get(row, {})
            for col in range(self.GetNumberCols()):
                editor = None
                renderer = None
                colname = self.GetColLabelValue(col, False)
                if self.Parent.Debug:
                    grid.SetReadOnly(row, col, True)
                else:
                    if colname == "Option":
                        options = GetOptions(constant = var_class in ["Local", "External", "Global"],
                                             retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output", "Global"],
                                             non_retain = self.Parent.ElementType != "function" and var_class in ["Local", "Input", "Output"])
                        if len(options) > 1:
                            editor = wx.grid.GridCellChoiceEditor()
                            editor.SetParameters(",".join(map(_, options)))
                        else:
                            grid.SetReadOnly(row, col, True)
                    elif col != 0 and self.GetValueByName(row, "Edit"):
                        grid.SetReadOnly(row, col, False)
                        if colname == "Name":
                            if self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
                                grid.SetReadOnly(row, col, True)
                            else:
                                editor = wx.grid.GridCellTextEditor()
                                renderer = wx.grid.GridCellStringRenderer()
                        elif colname == "Initial Value":
                            if var_class != "External":
                                if self.Parent.Controler.IsEnumeratedType(var_type):
                                    editor = wx.grid.GridCellChoiceEditor()
                                    editor.SetParameters(",".join(self.Parent.Controler.GetEnumeratedDataValues(var_type)))
                                else:
                                    editor = wx.grid.GridCellTextEditor()
                                renderer = wx.grid.GridCellStringRenderer()
                            else:
                                grid.SetReadOnly(row, col, True)
                        elif colname == "Location":
                            if var_class in ["Local", "Global"] and self.Parent.Controler.IsLocatableType(var_type):
                                editor = LocationCellEditor(self, self.Parent.Controler)
                                renderer = wx.grid.GridCellStringRenderer()
                            else:
                                grid.SetReadOnly(row, col, True)
                        elif colname == "Class":
                            if len(self.Parent.ClassList) == 1 or self.Parent.PouIsUsed and var_class in ["Input", "Output", "InOut"]:
                                grid.SetReadOnly(row, col, True)
                            else:
                                editor = wx.grid.GridCellChoiceEditor()
                                excluded = []
                                if self.Parent.PouIsUsed:
                                    excluded.extend(["Input","Output","InOut"])
                                if self.Parent.IsFunctionBlockType(var_type):
                                    excluded.extend(["Local","Temp"])
                                editor.SetParameters(",".join([_(choice) for choice in self.Parent.ClassList if choice not in excluded]))
                    elif colname != "Documentation":
                        grid.SetReadOnly(row, col, True)
                
                grid.SetCellEditor(row, col, editor)
                grid.SetCellRenderer(row, col, renderer)
                
                highlight_colours = row_highlights.get(colname.lower(), [(wx.WHITE, wx.BLACK)])[-1]
                grid.SetCellBackgroundColour(row, col, highlight_colours[0])
                grid.SetCellTextColour(row, col, highlight_colours[1])
            self.ResizeRow(grid, row)
                    

class VariableDropTarget(wx.TextDropTarget):
    '''
    This allows dragging a variable location from somewhere to the Location
    column of a variable row.
    
    The drag source should be a TextDataObject containing a Python tuple like:
        ('%ID0.0.0', 'location', 'REAL')
    
    c_ext/CFileEditor.py has an example of this (you can drag a C extension
    variable to the Location column of the variable panel).
    '''
    def __init__(self, parent):
        wx.TextDropTarget.__init__(self)
        self.ParentWindow = parent
    
    def OnDropText(self, x, y, data):
        x, y = self.ParentWindow.VariablesGrid.CalcUnscrolledPosition(x, y)
        col = self.ParentWindow.VariablesGrid.XToCol(x)
        row = self.ParentWindow.VariablesGrid.YToRow(y - self.ParentWindow.VariablesGrid.GetColLabelSize())
        message = None
        element_type = self.ParentWindow.ElementType
        try:
            values = eval(data)    
        except:
            message = _("Invalid value \"%s\" for variable grid element")%data
            values = None
        if not isinstance(values, TupleType):
            message = _("Invalid value \"%s\" for variable grid element")%data
            values = None
        if values is not None:
            if col != wx.NOT_FOUND and row != wx.NOT_FOUND:
                if self.ParentWindow.Table.GetColLabelValue(col, False) != "Location":
                    return
                if not self.ParentWindow.Table.GetValueByName(row, "Edit"):
                    message = _("Can't give a location to a function block instance")
                elif self.ParentWindow.Table.GetValueByName(row, "Class") not in ["Local", "Global"]:
                    message = _("Can only give a location to local or global variables")
                elif values is not None and values[1] == "location":
                    location = values[0]
                    variable_type = self.ParentWindow.Table.GetValueByName(row, "Type")
                    base_type = self.ParentWindow.Controler.GetBaseType(variable_type)
                    if location.startswith("%"):
                        if base_type != values[2]:
                            message = _("Incompatible data types between \"%s\" and \"%s\"")%(values[2], variable_type)
                        else:
                            self.ParentWindow.Table.SetValue(row, col, location)
                            self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
                            self.ParentWindow.SaveValues()
                    else:
                        if location[0].isdigit() and base_type != "BOOL":
                            message = _("Incompatible size of data between \"%s\" and \"BOOL\"")%location
                        elif location[0] not in LOCATIONDATATYPES:
                            message = _("Unrecognized data size \"%s\"")%location[0]
                        elif base_type not in LOCATIONDATATYPES[location[0]]:
                            message = _("Incompatible size of data between \"%s\" and \"%s\"")%(location, variable_type)
                        else:
                            dialog = wx.SingleChoiceDialog(self.ParentWindow, _("Select a variable class:"), _("Variable class"), ["Input", "Output", "Memory"], wx.OK|wx.CANCEL)
                            if dialog.ShowModal() == wx.ID_OK:
                                selected = dialog.GetSelection()
                                if selected == 0:
                                    location = "%I" + location
                                elif selected == 1:
                                    location = "%Q" + location
                                else:
                                    location = "%M" + location
                                self.ParentWindow.Table.SetValue(row, col, location)
                                self.ParentWindow.Table.ResetView(self.ParentWindow.VariablesGrid)
                                self.ParentWindow.SaveValues()
                            dialog.Destroy()
            elif (element_type not in ["config", "resource"] and values[1] == "Global" and self.ParentWindow.Filter in ["All", "Interface", "External"] or
                  element_type in ["config", "resource"] and values[1] == "location"):
                if values[1] == "location":
                    var_name = values[3]
                else:
                    var_name = values[0]
                tagname = self.ParentWindow.GetTagName()
                if var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetProjectPouNames(self.ParentWindow.Debug)]:
                    message = _("\"%s\" pou already exists!")%var_name
                elif not var_name.upper() in [name.upper() for name in self.ParentWindow.Controler.GetEditedElementVariables(tagname, self.ParentWindow.Debug)]:
                    var_infos = self.ParentWindow.DefaultValue.copy()
                    var_infos["Name"] = var_name
                    var_infos["Type"] = values[2]
                    if values[1] == "location":
                        var_infos["Class"] = "Global"
                        var_infos["Location"] = values[0]
                    else:
                        var_infos["Class"] = "External"
                    var_infos["Number"] = len(self.ParentWindow.Values)
                    self.ParentWindow.Values.append(var_infos)
                    self.ParentWindow.SaveValues()
                    self.ParentWindow.RefreshValues()
        
        if message is not None:
            wx.CallAfter(self.ShowMessage, message)
            
                
    def ShowMessage(self, message):
        message = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
        message.ShowModal()
        message.Destroy()

[ID_VARIABLEEDITORPANEL, ID_VARIABLEEDITORPANELVARIABLESGRID, 
 ID_VARIABLEEDITORCONTROLPANEL, ID_VARIABLEEDITORPANELRETURNTYPE, 
 ID_VARIABLEEDITORPANELCLASSFILTER, ID_VARIABLEEDITORPANELADDBUTTON, 
 ID_VARIABLEEDITORPANELDELETEBUTTON, ID_VARIABLEEDITORPANELUPBUTTON, 
 ID_VARIABLEEDITORPANELDOWNBUTTON, ID_VARIABLEEDITORPANELSTATICTEXT1, 
 ID_VARIABLEEDITORPANELSTATICTEXT2, ID_VARIABLEEDITORPANELSTATICTEXT3,
] = [wx.NewId() for _init_ctrls in range(12)]

class VariablePanel(wx.Panel):
    
    if wx.VERSION < (2, 6, 0):
        def Bind(self, event, function, id = None):
            if id is not None:
                event(self, id, function)
            else:
                event(self, function)
    
    def _init_coll_MainSizer_Items(self, parent):
        parent.AddWindow(self.ControlPanel, 0, border=5, flag=wx.GROW|wx.ALL)
        parent.AddWindow(self.VariablesGrid, 0, border=0, flag=wx.GROW)
        
    def _init_coll_MainSizer_Growables(self, parent):
        parent.AddGrowableCol(0)
        parent.AddGrowableRow(1)
    
    def _init_coll_ControlPanelSizer_Items(self, parent):
        parent.AddWindow(self.staticText1, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
        parent.AddWindow(self.ReturnType, 0, border=0, flag=0)
        parent.AddWindow(self.staticText2, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
        parent.AddWindow(self.ClassFilter, 0, border=0, flag=0)
        parent.AddWindow(self.AddButton, 0, border=0, flag=0)
        parent.AddWindow(self.DeleteButton, 0, border=0, flag=0)
        parent.AddWindow(self.UpButton, 0, border=0, flag=0)
        parent.AddWindow(self.DownButton, 0, border=0, flag=0)
        
    def _init_coll_ControlPanelSizer_Growables(self, parent):
        parent.AddGrowableCol(3)
        parent.AddGrowableRow(0)

    def _init_sizers(self):
        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=0)
        self.ControlPanelSizer = wx.FlexGridSizer(cols=8, hgap=5, rows=1, vgap=5)
        
        self._init_coll_MainSizer_Items(self.MainSizer)
        self._init_coll_MainSizer_Growables(self.MainSizer)
        self._init_coll_ControlPanelSizer_Items(self.ControlPanelSizer)
        self._init_coll_ControlPanelSizer_Growables(self.ControlPanelSizer)
        
        self.ControlPanel.SetSizer(self.ControlPanelSizer)
        self.SetSizer(self.MainSizer)
    
    def _init_ctrls(self, prnt):
        wx.Panel.__init__(self, id=ID_VARIABLEEDITORPANEL,
              name='VariableEditorPanel', parent=prnt, pos=wx.Point(0, 0),
              size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)

        self.VariablesGrid = CustomGrid(id=ID_VARIABLEEDITORPANELVARIABLESGRID,
              name='VariablesGrid', parent=self, pos=wx.Point(0, 0), 
              size=wx.Size(0, 0), style=wx.VSCROLL)
        self.VariablesGrid.SetDropTarget(VariableDropTarget(self))
        if wx.VERSION >= (2, 6, 0):
            self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGE, self.OnVariablesGridCellChange)
            self.VariablesGrid.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnVariablesGridCellLeftClick)
            self.VariablesGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN, self.OnVariablesGridEditorShown)
        else:
            wx.grid.EVT_GRID_CELL_CHANGE(self.VariablesGrid, self.OnVariablesGridCellChange)
            wx.grid.EVT_GRID_CELL_LEFT_CLICK(self.VariablesGrid, self.OnVariablesGridCellLeftClick)
            wx.grid.EVT_GRID_EDITOR_SHOWN(self.VariablesGrid, self.OnVariablesGridEditorShown)
        
        self.ControlPanel = wx.ScrolledWindow(id=ID_VARIABLEEDITORCONTROLPANEL,
              name='ControlPanel', parent=self, pos=wx.Point(0, 0),
              size=wx.Size(0, 0), style=wx.TAB_TRAVERSAL)
        self.ControlPanel.SetScrollRate(10, 0)
        
        self.staticText1 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT1,
              label=_('Return Type:'), name='staticText1', parent=self.ControlPanel,
              pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)

        self.ReturnType = wx.ComboBox(id=ID_VARIABLEEDITORPANELRETURNTYPE,
              name='ReturnType', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.Size(145, 28), style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnReturnTypeChanged, id=ID_VARIABLEEDITORPANELRETURNTYPE)

        self.staticText2 = wx.StaticText(id=ID_VARIABLEEDITORPANELSTATICTEXT2,
              label=_('Class Filter:'), name='staticText2', parent=self.ControlPanel,
              pos=wx.Point(0, 0), size=wx.DefaultSize, style=0)

        self.ClassFilter = wx.ComboBox(id=ID_VARIABLEEDITORPANELCLASSFILTER,
              name='ClassFilter', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.Size(145, 28), style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnClassFilter, id=ID_VARIABLEEDITORPANELCLASSFILTER)

        self.AddButton = wx.Button(id=ID_VARIABLEEDITORPANELADDBUTTON, label=_('Add'),
              name='AddButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.DefaultSize, style=0)
        
        self.DeleteButton = wx.Button(id=ID_VARIABLEEDITORPANELDELETEBUTTON, label=_('Delete'),
              name='DeleteButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.DefaultSize, style=0)
        
        self.UpButton = wx.Button(id=ID_VARIABLEEDITORPANELUPBUTTON, label='^',
              name='UpButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.Size(28, 28), style=0)
        
        self.DownButton = wx.Button(id=ID_VARIABLEEDITORPANELDOWNBUTTON, label='v',
              name='DownButton', parent=self.ControlPanel, pos=wx.Point(0, 0),
              size=wx.Size(28, 28), style=0)
        
        self._init_sizers()

    def __init__(self, parent, window, controler, element_type, debug=False):
        self._init_ctrls(parent)
        self.ParentWindow = window
        self.Controler = controler
        self.ElementType = element_type
        self.Debug = debug
        
        self.RefreshHighlightsTimer = wx.Timer(self, -1)
        self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)
        
        self.Filter = "All"
        self.FilterChoices = []
        self.FilterChoiceTransfer = GetFilterChoiceTransfer()
        
        self.DefaultValue = {   "Name" : "", "Class" : "", "Type" : "INT", "Location" : "",
                                "Initial Value" : "", "Option" : "",
                                "Documentation" : "", "Edit" : True
                            }

        if element_type in ["config", "resource"]:
            self.DefaultTypes = {"All" : "Global"}
        else:
            self.DefaultTypes = {"All" : "Local", "Interface" : "Input", "Variables" : "Local"}

        if element_type in ["config", "resource"] \
        or element_type in ["program", "transition", "action"]:
            # this is an element that can have located variables
            self.Table = VariableTable(self, [], GetVariableTableColnames(True))

            if element_type in ["config", "resource"]:
                self.FilterChoices = ["All", "Global"]#,"Access"]
            else:
                self.FilterChoices = ["All",
                                        "Interface", "   Input", "   Output", "   InOut", "   External",
                                        "Variables", "   Local", "   Temp"]#,"Access"]

            # these condense the ColAlignements list
            l = wx.ALIGN_LEFT
            c = wx.ALIGN_CENTER 

            #                      Num  Name    Class   Type    Loc     Init    Option   Doc
            self.ColSizes       = [40,  80,     70,     80,     80,     80,     100,     80]
            self.ColAlignements = [c,   l,      l,      l,      l,      l,      l,       l]

        else:
            # this is an element that cannot have located variables
            self.Table = VariableTable(self, [], GetVariableTableColnames(False))

            if element_type == "function":
                self.FilterChoices = ["All",
                                        "Interface", "   Input", "   Output", "   InOut",
                                        "Variables", "   Local"]
            else:
                self.FilterChoices = ["All",
                                        "Interface", "   Input", "   Output", "   InOut", "   External",
                                        "Variables", "   Local", "   Temp"]

            # these condense the ColAlignements list
            l = wx.ALIGN_LEFT
            c = wx.ALIGN_CENTER 

            #                      Num  Name    Class   Type    Init    Option   Doc
            self.ColSizes       = [40,  80,     70,     80,     80,     100,     160]
            self.ColAlignements = [c,   l,      l,      l,      l,      l,       l]

        for choice in self.FilterChoices:
            self.ClassFilter.Append(_(choice))

        reverse_transfer = {}
        for filter, choice in self.FilterChoiceTransfer.items():
            reverse_transfer[choice] = filter
        self.ClassFilter.SetStringSelection(_(reverse_transfer[self.Filter]))
        self.RefreshTypeList()

        self.VariablesGrid.SetTable(self.Table)
        self.VariablesGrid.SetButtons({"Add": self.AddButton,
                                       "Delete": self.DeleteButton,
                                       "Up": self.UpButton,
                                       "Down": self.DownButton})
        self.VariablesGrid.SetEditable(not self.Debug)
        
        def _AddVariable(new_row):
            if not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]:
                row_content = self.DefaultValue.copy()
                if self.Filter in self.DefaultTypes:
                    row_content["Class"] = self.DefaultTypes[self.Filter]
                else:
                    row_content["Class"] = self.Filter
                if self.Filter == "All" and len(self.Values) > 0:
                    self.Values.insert(new_row, row_content)
                else:
                    self.Values.append(row_content)
                    new_row = self.Table.GetNumberRows()
                self.SaveValues()
                self.RefreshValues()
                return new_row
            return self.VariablesGrid.GetGridCursorRow()
        setattr(self.VariablesGrid, "_AddRow", _AddVariable)
        
        def _DeleteVariable(row):
            if (self.Table.GetValueByName(row, "Edit") and 
                (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
                self.Values.remove(self.Table.GetRow(row))
                self.SaveValues()
                self.RefreshValues()
        setattr(self.VariablesGrid, "_DeleteRow", _DeleteVariable)
            
        def _MoveVariable(row, move):
            if (self.Filter == "All" and 
                (not self.PouIsUsed or self.Table.GetValueByName(row, "Class") not in ["Input", "Output", "InOut"])):
                new_row = max(0, min(row + move, len(self.Values) - 1))
                if new_row != row:
                    self.Values.insert(new_row, self.Values.pop(row))
                    self.SaveValues()
                    self.RefreshValues()
                return new_row
            return row
        setattr(self.VariablesGrid, "_MoveRow", _MoveVariable)
        
        def _RefreshButtons():
            table_length = len(self.Table.data)
            row_class = None
            row_edit = True
            row = 0
            if table_length > 0:
                row = self.VariablesGrid.GetGridCursorRow()
                row_edit = self.Table.GetValueByName(row, "Edit")
                if self.PouIsUsed:
                    row_class = self.Table.GetValueByName(row, "Class")
            self.AddButton.Enable(not self.Debug and (not self.PouIsUsed or self.Filter not in ["Interface", "Input", "Output", "InOut"]))
            self.DeleteButton.Enable(not self.Debug and (table_length > 0 and row_edit and row_class not in ["Input", "Output", "InOut"]))
            self.UpButton.Enable(not self.Debug and (table_length > 0 and row > 0 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
            self.DownButton.Enable(not self.Debug and (table_length > 0 and row < table_length - 1 and self.Filter == "All" and row_class not in ["Input", "Output", "InOut"]))
        setattr(self.VariablesGrid, "RefreshButtons", _RefreshButtons)
        
        self.VariablesGrid.SetRowLabelSize(0)
        for col in range(self.Table.GetNumberCols()):
            attr = wx.grid.GridCellAttr()
            attr.SetAlignment(self.ColAlignements[col], wx.ALIGN_CENTRE)
            self.VariablesGrid.SetColAttr(col, attr)
            self.VariablesGrid.SetColMinimalWidth(col, self.ColSizes[col])
            self.VariablesGrid.AutoSizeColumn(col, False)
    
    def __del__(self):
        self.RefreshHighlightsTimer.Stop()
    
    def SetTagName(self, tagname):
        self.TagName = tagname
    
    def GetTagName(self):
        return self.TagName
    
    def IsFunctionBlockType(self, name):
        bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
        pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
        if poutype != "function" and bodytype in ["ST", "IL"]:
            return False
        else:
            return name in self.Controler.GetFunctionBlockTypes(self.TagName)
    
    def RefreshView(self):
        self.PouNames = self.Controler.GetProjectPouNames(self.Debug)
        
        words = self.TagName.split("::")
        if self.ElementType == "config":
            self.PouIsUsed = False
            returnType = None
            self.Values = self.Controler.GetConfigurationGlobalVars(words[1], self.Debug)
        elif self.ElementType == "resource":
            self.PouIsUsed = False
            returnType = None
            self.Values = self.Controler.GetConfigurationResourceGlobalVars(words[1], words[2], self.Debug)
        else:
            if self.ElementType == "function":
                self.ReturnType.Clear()
                for base_type in self.Controler.GetDataTypes(self.TagName, True, debug=self.Debug):
                    self.ReturnType.Append(base_type)
                returnType = self.Controler.GetEditedElementInterfaceReturnType(self.TagName)
            else:
                returnType = None
            self.PouIsUsed = self.Controler.PouIsUsed(words[1])
            self.Values = self.Controler.GetEditedElementInterfaceVars(self.TagName, self.Debug)
        
        if returnType is not None:
            self.ReturnType.SetStringSelection(returnType)
            self.ReturnType.Enable(self.Debug)
            self.staticText1.Show()
            self.ReturnType.Show()
        else:
            self.ReturnType.Enable(False)
            self.staticText1.Hide()
            self.ReturnType.Hide()
        
        self.RefreshValues()
        self.VariablesGrid.RefreshButtons()
    
    def OnReturnTypeChanged(self, event):
        words = self.TagName.split("::")
        self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
        self.Controler.BufferProject()
        self.ParentWindow.RefreshView(variablepanel = False)
        self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
        event.Skip()
    
    def OnClassFilter(self, event):
        self.Filter = self.FilterChoiceTransfer[VARIABLE_CHOICES_DICT[self.ClassFilter.GetStringSelection()]]
        self.RefreshTypeList()
        self.RefreshValues()
        self.VariablesGrid.RefreshButtons()
        event.Skip()

    def RefreshTypeList(self):
        if self.Filter == "All":
            self.ClassList = [self.FilterChoiceTransfer[choice] for choice in self.FilterChoices if self.FilterChoiceTransfer[choice] not in ["All","Interface","Variables"]]
        elif self.Filter == "Interface":
            self.ClassList = ["Input","Output","InOut","External"]
        elif self.Filter == "Variables":
            self.ClassList = ["Local","Temp"]
        else:
            self.ClassList = [self.Filter]

    def OnVariablesGridCellChange(self, event):
        row, col = event.GetRow(), event.GetCol()
        colname = self.Table.GetColLabelValue(col, False)
        value = self.Table.GetValue(row, col)

        if colname == "Name" and value != "":
            if not TestIdentifier(value):
                message = wx.MessageDialog(self, _("\"%s\" is not a valid identifier!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
                event.Veto()
            elif value.upper() in IEC_KEYWORDS:
                message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
                event.Veto()
            elif value.upper() in self.PouNames:
                message = wx.MessageDialog(self, _("A POU named \"%s\" already exists!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
                event.Veto()
            elif value.upper() in [var["Name"].upper() for var in self.Values if var != self.Table.data[row]]:
                message = wx.MessageDialog(self, _("A variable with \"%s\" as name already exists in this pou!")%value, _("Error"), wx.OK|wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
                event.Veto()
            else:
                self.SaveValues(False)
                old_value = self.Table.GetOldValue()
                if old_value != "":
                    self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
                self.Controler.BufferProject()
                self.ParentWindow.RefreshView(variablepanel = False)
                self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
                event.Skip()
        else:
            self.SaveValues()
            if colname == "Class":
                self.ParentWindow.RefreshView(variablepanel = False)
            event.Skip()
    
    def OnVariablesGridEditorShown(self, event):
        row, col = event.GetRow(), event.GetCol() 

        label_value = self.Table.GetColLabelValue(col)
        if label_value == "Type":
            type_menu = wx.Menu(title='')   # the root menu

            # build a submenu containing standard IEC types
            base_menu = wx.Menu(title='')
            for base_type in self.Controler.GetBaseTypes():
                new_id = wx.NewId()
                AppendMenu(base_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=base_type)
                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(base_type), id=new_id)

            type_menu.AppendMenu(wx.NewId(), _("Base Types"), base_menu)

            # build a submenu containing user-defined types
            datatype_menu = wx.Menu(title='')
            
            # TODO : remove complextypes argument when matiec can manage complex types in pou interface
            datatypes = self.Controler.GetDataTypes(basetypes = False)
            for datatype in datatypes:
                new_id = wx.NewId()
                AppendMenu(datatype_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=datatype)
                self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(datatype), id=new_id)

            type_menu.AppendMenu(wx.NewId(), _("User Data Types"), datatype_menu)

            # build a submenu containing function block types
            bodytype = self.Controler.GetEditedElementBodyType(self.TagName)
            pouname, poutype = self.Controler.GetEditedElementType(self.TagName)
            classtype = self.Table.GetValueByName(row, "Class")
            
            new_id = wx.NewId()
            AppendMenu(type_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Array"))
            self.Bind(wx.EVT_MENU, self.VariableArrayTypeFunction, id=new_id)
            
            if classtype in ["Input", "Output", "InOut", "External", "Global"] or \
            poutype != "function" and bodytype in ["ST", "IL"]:
                functionblock_menu = wx.Menu(title='')
                fbtypes = self.Controler.GetFunctionBlockTypes(self.TagName)
                for functionblock_type in fbtypes:
                    new_id = wx.NewId()
                    AppendMenu(functionblock_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=functionblock_type)
                    self.Bind(wx.EVT_MENU, self.GetVariableTypeFunction(functionblock_type), id=new_id)

                type_menu.AppendMenu(wx.NewId(), _("Function Block Types"), functionblock_menu)

            rect = self.VariablesGrid.BlockToDeviceRect((row, col), (row, col))
            corner_x = rect.x + rect.width
            corner_y = rect.y + self.VariablesGrid.GetColLabelSize()

            # pop up this new menu
            self.VariablesGrid.PopupMenuXY(type_menu, corner_x, corner_y)
            event.Veto()
        else:
            event.Skip()
    
    def GetVariableTypeFunction(self, base_type):
        def VariableTypeFunction(event):
            row = self.VariablesGrid.GetGridCursorRow()
            self.Table.SetValueByName(row, "Type", base_type)
            self.Table.ResetView(self.VariablesGrid)
            self.SaveValues(False)
            self.ParentWindow.RefreshView(variablepanel = False)
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
        return VariableTypeFunction
    
    def VariableArrayTypeFunction(self, event):
        row = self.VariablesGrid.GetGridCursorRow()
        dialog = ArrayTypeDialog(self, 
                                 self.Controler.GetDataTypes(self.TagName), 
                                 self.Table.GetValueByName(row, "Type"))
        if dialog.ShowModal() == wx.ID_OK:
            self.Table.SetValueByName(row, "Type", dialog.GetValue())
            self.Table.ResetView(self.VariablesGrid)
            self.SaveValues(False)
            self.ParentWindow.RefreshView(variablepanel = False)
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)
        dialog.Destroy()
    
    def OnVariablesGridCellLeftClick(self, event):
        row = event.GetRow()
        if not self.Debug and (event.GetCol() == 0 and self.Table.GetValueByName(row, "Edit")):
            row = event.GetRow()
            var_name = self.Table.GetValueByName(row, "Name")
            var_class = self.Table.GetValueByName(row, "Class")
            var_type = self.Table.GetValueByName(row, "Type")
            data = wx.TextDataObject(str((var_name, var_class, var_type, self.TagName)))
            dragSource = wx.DropSource(self.VariablesGrid)
            dragSource.SetData(data)
            dragSource.DoDragDrop()
        event.Skip()
    
    def RefreshValues(self):
        data = []
        for num, variable in enumerate(self.Values):
            if variable["Class"] in self.ClassList:
                variable["Number"] = num + 1
                data.append(variable)
        self.Table.SetData(data)
        self.Table.ResetView(self.VariablesGrid)
            
    def SaveValues(self, buffer = True):
        words = self.TagName.split("::")
        if self.ElementType == "config":
            self.Controler.SetConfigurationGlobalVars(words[1], self.Values)
        elif self.ElementType == "resource":
            self.Controler.SetConfigurationResourceGlobalVars(words[1], words[2], self.Values)
        else:
            if self.ReturnType.IsEnabled():
                self.Controler.SetPouInterfaceReturnType(words[1], self.ReturnType.GetStringSelection())
            self.Controler.SetPouInterfaceVars(words[1], self.Values)
        if buffer:
            self.Controler.BufferProject()
            self.ParentWindow._Refresh(TITLE, FILEMENU, EDITMENU, INSTANCESTREE, LIBRARYTREE)            

#-------------------------------------------------------------------------------
#                        Highlights showing functions
#-------------------------------------------------------------------------------

    def OnRefreshHighlightsTimer(self, event):
        self.Table.ResetView(self.VariablesGrid)
        event.Skip()

    def AddVariableHighlight(self, infos, highlight_type):
        if isinstance(infos[0], TupleType):
            for i in xrange(*infos[0]):
                self.Table.AddHighlight((i,) + infos[1:], highlight_type)
        else:
            self.Table.AddHighlight(infos, highlight_type)
        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)

    def ClearHighlights(self, highlight_type=None):
        self.Table.ClearHighlights(highlight_type)
        self.Table.ResetView(self.VariablesGrid)