editors/DataTypeEditor.py
author Edouard Tisserant <edouard@beremiz.fr>
Thu, 14 Mar 2024 12:00:36 +0100
changeset 3908 32eb6e05008a
parent 3765 88fe6fc9fd38
permissions -rw-r--r--
IDE: Fix encrypted ERPC: newer TLS, better error handling, re-add explicit ERPCS scheme, use sslpsk better.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
# Copyright (C) 2017: Andrey Skvortsov
#
# See COPYING file for copyrights details.
#
# This program 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
# of the License, or (at your option) any later version.
#
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


import re

import wx
import wx.grid
import wx.lib.buttons
from plcopen.structures import IEC_KEYWORDS, TestIdentifier, DefaultType
from graphics.GraphicCommons import REFRESH_HIGHLIGHT_PERIOD
from controls import CustomEditableListBox, CustomGrid, CustomTable, CustomIntCtrl
from dialogs import ArrayTypeDialog
from editors.EditorPanel import EditorPanel
from util.BitmapLibrary import GetBitmap
from util.TranslationCatalogs import NoTranslate

# -------------------------------------------------------------------------------
#                                    Helpers
# -------------------------------------------------------------------------------

DIMENSION_MODEL = re.compile(r"([0-9]+)\.\.([0-9]+)$")


def AppendMenu(parent, help, kind, text):
    return parent.Append(wx.MenuItem(helpString=help, id=wx.ID_ANY, kind=kind, text=text))


def GetElementsTableColnames():
    _ = NoTranslate
    return ["#", _("Name"), _("Type"), _("Initial Value")]


def GetDatatypeTypes():
    _ = NoTranslate
    return [_("Directly"), _("Subrange"), _("Enumerated"), _("Array"), _("Structure")]


# -------------------------------------------------------------------------------
#                            Structure Elements Table
# -------------------------------------------------------------------------------


class ElementsTable(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 row + 1
            colname = self.GetColLabelValue(col, False)
            value = self.data[row].get(colname, "")

            if colname == "Type" and isinstance(value, tuple):
                if value[0] == "array":
                    return "ARRAY [%s] OF %s" % (",".join(map("..".join, value[2])), value[1])
            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]
            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()):
            row_highlights = self.Highlights.get(row, {})
            for col in range(self.GetNumberCols()):
                editor = None
                renderer = None
                colname = self.GetColLabelValue(col, False)
                if col != 0:
                    grid.SetReadOnly(row, col, False)
                    if colname == "Name":
                        editor = wx.grid.GridCellTextEditor()
                        renderer = wx.grid.GridCellStringRenderer()
                    elif colname == "Initial Value":
                        editor = wx.grid.GridCellTextEditor()
                        renderer = wx.grid.GridCellStringRenderer()
                    elif colname == "Type":
                        editor = wx.grid.GridCellTextEditor()
                else:
                    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)

    def AddHighlight(self, infos, highlight_type):
        row_highlights = self.Highlights.setdefault(infos[0], {})
        if infos[1] == "initial":
            col_highlights = row_highlights.setdefault("initial value", [])
        else:
            col_highlights = row_highlights.setdefault(infos[1], [])
        col_highlights.append(highlight_type)

# -------------------------------------------------------------------------------
#                          Datatype Editor class
# -------------------------------------------------------------------------------


class DataTypeEditor(EditorPanel):

    def _init_Editor(self, parent):
        self.Editor = wx.Panel(parent, style=wx.SUNKEN_BORDER)

        self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=10)
        self.MainSizer.AddGrowableCol(0)
        self.MainSizer.AddGrowableRow(1)

        top_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.MainSizer.Add(top_sizer, border=5,
                                flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT)

        derivation_type_label = wx.StaticText(self.Editor, label=_('Derivation Type:'))
        top_sizer.Add(derivation_type_label, border=5,
                            flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT)

        self.DerivationType = wx.ComboBox(self.Editor,
                                          size=wx.Size(200, -1), style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnDerivationTypeChanged, self.DerivationType)
        top_sizer.Add(self.DerivationType, border=5, flag=wx.GROW | wx.RIGHT)

        typeinfos_staticbox = wx.StaticBox(self.Editor, label=_('Type infos:'))
        typeinfos_sizer = wx.StaticBoxSizer(typeinfos_staticbox, wx.HORIZONTAL)
        self.MainSizer.Add(typeinfos_sizer, border=5,
                                flag=wx.GROW | wx.BOTTOM | wx.LEFT | wx.RIGHT)

        # Panel for Directly derived data types

        self.DirectlyPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
        typeinfos_sizer.Add(self.DirectlyPanel, 1)

        directly_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)

        directly_basetype_label = wx.StaticText(self.DirectlyPanel,
                                                label=_('Base Type:'))
        directly_panel_sizer.Add(directly_basetype_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.DirectlyBaseType = wx.ComboBox(self.DirectlyPanel, style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.DirectlyBaseType)
        directly_panel_sizer.Add(self.DirectlyBaseType, 1, border=5,
                                       flag=wx.GROW | wx.ALL)

        directly_initialvalue_label = wx.StaticText(self.DirectlyPanel,
                                                    label=_('Initial Value:'))
        directly_panel_sizer.Add(directly_initialvalue_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.DirectlyInitialValue = wx.TextCtrl(self.DirectlyPanel,
                                                style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.DirectlyInitialValue)
        directly_panel_sizer.Add(self.DirectlyInitialValue, 1, border=5,
                                       flag=wx.ALL)

        self.DirectlyPanel.SetSizer(directly_panel_sizer)

        # Panel for Subrange data types

        self.SubrangePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
        typeinfos_sizer.Add(self.SubrangePanel, 1)

        subrange_panel_sizer = wx.GridSizer(cols=4, hgap=5, rows=3, vgap=0)

        subrange_basetype_label = wx.StaticText(self.SubrangePanel,
                                                label=_('Base Type:'))
        subrange_panel_sizer.Add(subrange_basetype_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.SubrangeBaseType = wx.ComboBox(self.SubrangePanel, style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnSubrangeBaseTypeChanged,
                  self.SubrangeBaseType)
        subrange_panel_sizer.Add(self.SubrangeBaseType, 1, border=5,
                                       flag=wx.GROW | wx.ALL)

        subrange_initialvalue_label = wx.StaticText(self.SubrangePanel,
                                                    label=_('Initial Value:'))
        subrange_panel_sizer.Add(subrange_initialvalue_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.SubrangeInitialValue = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
        self.SubrangeInitialValue.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnInfosChanged)
        subrange_panel_sizer.Add(self.SubrangeInitialValue, 1, border=5,
                                       flag=wx.GROW | wx.ALL)

        subrange_minimum_label = wx.StaticText(self.SubrangePanel, label=_('Minimum:'))
        subrange_panel_sizer.Add(subrange_minimum_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.SubrangeMinimum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
        self.SubrangeMinimum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMinimumChanged)
        subrange_panel_sizer.Add(self.SubrangeMinimum, 1, border=5,
                                       flag=wx.GROW | wx.ALL)

        for dummy in range(2):
            subrange_panel_sizer.Add(wx.Size(0, 0), 1)

        subrange_maximum_label = wx.StaticText(self.SubrangePanel,
                                               label=_('Maximum:'))
        subrange_panel_sizer.Add(subrange_maximum_label, 1, border=5,
                                       flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.SubrangeMaximum = CustomIntCtrl(self.SubrangePanel, style=wx.TAB_TRAVERSAL)
        self.SubrangeMaximum.Bind(CustomIntCtrl.EVT_CUSTOM_INT, self.OnSubrangeMaximumChanged)

        subrange_panel_sizer.Add(self.SubrangeMaximum, 1, border=5,
                                       flag=wx.GROW | wx.ALL)

        self.SubrangePanel.SetSizer(subrange_panel_sizer)

        # Panel for Enumerated data types

        self.EnumeratedPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
        typeinfos_sizer.Add(self.EnumeratedPanel, 1)

        enumerated_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.EnumeratedValues = CustomEditableListBox(
            self.EnumeratedPanel,
            label=_("Values:"),
            style=(wx.adv.EL_ALLOW_NEW |
                   wx.adv.EL_ALLOW_EDIT |
                   wx.adv.EL_ALLOW_DELETE))
        setattr(self.EnumeratedValues, "_OnLabelEndEdit", self.OnEnumeratedValueEndEdit)
        for func in ["_OnAddButton", "_OnDelButton", "_OnUpButton", "_OnDownButton"]:
            setattr(self.EnumeratedValues, func, self.OnEnumeratedValuesChanged)
        enumerated_panel_sizer.Add(self.EnumeratedValues, 1, border=5,
                                         flag=wx.GROW | wx.ALL)

        enumerated_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
        enumerated_panel_sizer.Add(enumerated_panel_rightsizer, 1)

        enumerated_initialvalue_label = wx.StaticText(self.EnumeratedPanel,
                                                      label=_('Initial Value:'))
        enumerated_panel_rightsizer.Add(enumerated_initialvalue_label, 1,
                                              border=5,
                                              flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.EnumeratedInitialValue = wx.ComboBox(self.EnumeratedPanel,
                                                  style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.EnumeratedInitialValue)
        enumerated_panel_rightsizer.Add(self.EnumeratedInitialValue, 1,
                                              border=5, flag=wx.ALL)

        self.EnumeratedPanel.SetSizer(enumerated_panel_sizer)

        # Panel for Array data types

        self.ArrayPanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
        typeinfos_sizer.Add(self.ArrayPanel, 1)

        array_panel_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=2, vgap=0)
        array_panel_sizer.AddGrowableCol(0)
        array_panel_sizer.AddGrowableCol(1)
        array_panel_sizer.AddGrowableRow(1)

        array_panel_leftSizer = wx.BoxSizer(wx.HORIZONTAL)
        array_panel_sizer.Add(array_panel_leftSizer, flag=wx.GROW)

        array_basetype_label = wx.StaticText(self.ArrayPanel, label=_('Base Type:'))
        array_panel_leftSizer.Add(array_basetype_label, 1, border=5,
                                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.ArrayBaseType = wx.ComboBox(self.ArrayPanel, style=wx.CB_READONLY)
        self.Bind(wx.EVT_COMBOBOX, self.OnInfosChanged, self.ArrayBaseType)
        array_panel_leftSizer.Add(self.ArrayBaseType, 1, border=5,
                                        flag=wx.GROW | wx.ALL)

        array_panel_rightsizer = wx.BoxSizer(wx.HORIZONTAL)
        array_panel_sizer.Add(array_panel_rightsizer, flag=wx.GROW)

        array_initialvalue_label = wx.StaticText(self.ArrayPanel,
                                                 label=_('Initial Value:'))
        array_panel_rightsizer.Add(array_initialvalue_label, 1, border=5,
                                         flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL)

        self.ArrayInitialValue = wx.TextCtrl(self.ArrayPanel,
                                             style=wx.TE_PROCESS_ENTER | wx.TE_RICH)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnReturnKeyPressed, self.ArrayInitialValue)
        array_panel_rightsizer.Add(self.ArrayInitialValue, 1, border=5,
                                         flag=wx.ALL)

        self.ArrayDimensions = CustomEditableListBox(
            self.ArrayPanel,
            label=_("Dimensions:"),
            style=(wx.adv.EL_ALLOW_NEW |
                   wx.adv.EL_ALLOW_EDIT |
                   wx.adv.EL_ALLOW_DELETE))
        for func in ["_OnLabelEndEdit", "_OnAddButton", "_OnDelButton",
                     "_OnUpButton", "_OnDownButton"]:
            setattr(self.ArrayDimensions, func, self.OnDimensionsChanged)
        array_panel_sizer.Add(self.ArrayDimensions, 0, border=5,
                                    flag=wx.GROW | wx.ALL)

        self.ArrayPanel.SetSizer(array_panel_sizer)

        # Panel for Structure data types

        self.StructurePanel = wx.Panel(self.Editor, style=wx.TAB_TRAVERSAL)
        typeinfos_sizer.Add(self.StructurePanel, 1)

        structure_panel_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
        structure_panel_sizer.AddGrowableCol(0)
        structure_panel_sizer.AddGrowableRow(1)

        structure_button_sizer = wx.FlexGridSizer(cols=5, hgap=5, rows=1, vgap=0)
        structure_button_sizer.AddGrowableCol(0)
        structure_button_sizer.AddGrowableRow(0)
        structure_panel_sizer.Add(structure_button_sizer, 0, border=5,
                                       flag=wx.ALL | wx.GROW)

        structure_elements_label = wx.StaticText(self.StructurePanel,
                                                 label=_('Elements :'))
        structure_button_sizer.Add(structure_elements_label, flag=wx.ALIGN_BOTTOM)

        for name, bitmap, help in [
                ("StructureAddButton", "add_element", _("Add element")),
                ("StructureDeleteButton", "remove_element", _("Remove element")),
                ("StructureUpButton", "up", _("Move element up")),
                ("StructureDownButton", "down", _("Move element down"))]:
            button = wx.lib.buttons.GenBitmapButton(self.StructurePanel,
                                                    bitmap=GetBitmap(bitmap),
                                                    size=wx.Size(28, 28),
                                                    style=wx.NO_BORDER)
            button.SetToolTip(help)
            setattr(self, name, button)
            structure_button_sizer.Add(button)

        self.StructureElementsGrid = CustomGrid(self.StructurePanel,
                                                size=wx.Size(0, 150), style=wx.VSCROLL)
        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_CELL_CHANGED,
                                        self.OnStructureElementsGridCellChange)
        self.StructureElementsGrid.Bind(wx.grid.EVT_GRID_EDITOR_SHOWN,
                                        self.OnStructureElementsGridEditorShown)
        structure_panel_sizer.Add(self.StructureElementsGrid, flag=wx.GROW)

        self.StructurePanel.SetSizer(structure_panel_sizer)

        self.Editor.SetSizer(self.MainSizer)

    def __init__(self, parent, tagname, window, controler):
        EditorPanel.__init__(self, parent, tagname, window, controler)

        self.StructureElementDefaultValue = {"Name": "", "Type": DefaultType, "Initial Value": ""}
        self.StructureElementsTable = ElementsTable(self, [], GetElementsTableColnames())
        self.StructureColSizes = [40, 150, 100, 250]
        self.StructureColAlignements = [wx.ALIGN_CENTER, wx.ALIGN_LEFT, wx.ALIGN_LEFT, wx.ALIGN_LEFT]

        self.StructureElementsGrid.SetTable(self.StructureElementsTable)
        self.StructureElementsGrid.SetButtons({"Add": self.StructureAddButton,
                                               "Delete": self.StructureDeleteButton,
                                               "Up": self.StructureUpButton,
                                               "Down": self.StructureDownButton})

        self.DATATYPE_TYPES_DICT = dict([(_(datatype), datatype) for datatype in GetDatatypeTypes()])

        def _AddStructureElement(new_row):
            self.StructureElementsTable.InsertRow(new_row, self.StructureElementDefaultValue.copy())
            self.RefreshTypeInfos()
            self.StructureElementsTable.ResetView(self.StructureElementsGrid)
            return new_row
        setattr(self.StructureElementsGrid, "_AddRow", _AddStructureElement)

        def _DeleteStructureElement(row):
            self.StructureElementsTable.RemoveRow(row)
            self.RefreshTypeInfos()
            self.StructureElementsTable.ResetView(self.StructureElementsGrid)
        setattr(self.StructureElementsGrid, "_DeleteRow", _DeleteStructureElement)

        def _MoveStructureElement(row, move):
            new_row = self.StructureElementsTable.MoveRow(row, move)
            if new_row != row:
                self.RefreshTypeInfos()
                self.StructureElementsTable.ResetView(self.StructureElementsGrid)
            return new_row
        setattr(self.StructureElementsGrid, "_MoveRow", _MoveStructureElement)

        self.StructureElementsGrid.SetRowLabelSize(0)
        for col in range(self.StructureElementsTable.GetNumberCols()):
            attr = wx.grid.GridCellAttr()
            attr.SetAlignment(self.StructureColAlignements[col], wx.ALIGN_CENTRE)
            self.StructureElementsGrid.SetColAttr(col, attr)
            self.StructureElementsGrid.SetColMinimalWidth(col, self.StructureColSizes[col])
            self.StructureElementsGrid.AutoSizeColumn(col, False)
        self.StructureElementsGrid.RefreshButtons()

        for datatype in GetDatatypeTypes():
            self.DerivationType.Append(_(datatype))
        self.SubrangePanel.Hide()
        self.EnumeratedPanel.Hide()
        self.ArrayPanel.Hide()
        self.StructurePanel.Hide()
        self.CurrentPanel = "Directly"
        self.Highlights = []
        self.Initializing = False

        self.HighlightControls = {
            ("Directly", "base"): self.DirectlyBaseType,
            ("Directly", "initial"): self.DirectlyInitialValue,
            ("Subrange", "base"): self.SubrangeBaseType,
            ("Subrange", "lower"): self.SubrangeMinimum,
            ("Subrange", "upper"): self.SubrangeMaximum,
            ("Subrange", "initial"): self.SubrangeInitialValue,
            ("Enumerated", "value"): self.EnumeratedValues,
            ("Enumerated", "initial"): self.EnumeratedInitialValue,
            ("Array", "initial"): self.ArrayInitialValue,
            ("Array", "base"): self.ArrayBaseType,
            ("Array", "range"): self.ArrayDimensions,
        }

        self.RefreshHighlightsTimer = wx.Timer(self, -1)
        self.Bind(wx.EVT_TIMER, self.OnRefreshHighlightsTimer, self.RefreshHighlightsTimer)

    def GetBufferState(self):
        return self.Controler.GetBufferState()

    def Undo(self):
        self.Controler.LoadPrevious()
        self.ParentWindow.CloseTabsWithoutModel()

    def Redo(self):
        self.Controler.LoadNext()
        self.ParentWindow.CloseTabsWithoutModel()

    def HasNoModel(self):
        return self.Controler.GetEditedElement(self.TagName) is None

    def RefreshView(self):
        self.Initializing = True
        self.DirectlyBaseType.Clear()
        self.ArrayBaseType.Clear()
        for datatype in self.Controler.GetDataTypes(self.TagName):
            self.DirectlyBaseType.Append(datatype)
            self.ArrayBaseType.Append(datatype)
        self.DirectlyBaseType.SetSelection(0)
        self.SubrangeBaseType.Clear()
        words = self.TagName.split("::")
        for base_type in self.Controler.GetSubrangeBaseTypes(words[1]):
            self.SubrangeBaseType.Append(base_type)
        self.SubrangeBaseType.SetSelection(0)
        self.RefreshBoundsRange()
        type_infos = self.Controler.GetDataTypeInfos(self.TagName)
        if type_infos is not None:
            datatype = type_infos["type"]
            self.DerivationType.SetStringSelection(_(datatype))
            if type_infos["type"] == "Directly":
                self.DirectlyBaseType.SetStringSelection(type_infos["base_type"])
                self.DirectlyInitialValue.SetValue(type_infos["initial"])
            elif type_infos["type"] == "Subrange":
                self.SubrangeBaseType.SetStringSelection(type_infos["base_type"])
                self.RefreshBoundsRange()
                self.SubrangeMinimum.SetValue(int(type_infos["min"]))
                self.SubrangeMaximum.SetValue(int(type_infos["max"]))
                self.RefreshSubrangeInitialValueRange()
                if type_infos["initial"] != "":
                    self.SubrangeInitialValue.SetValue(int(type_infos["initial"]))
                else:
                    self.SubrangeInitialValue.SetValue(type_infos["min"])
            elif type_infos["type"] == "Enumerated":
                self.EnumeratedValues.SetStrings(type_infos["values"])
                self.RefreshEnumeratedValues()
                self.EnumeratedInitialValue.SetStringSelection(type_infos["initial"])
            elif type_infos["type"] == "Array":
                self.ArrayBaseType.SetStringSelection(type_infos["base_type"])
                self.ArrayDimensions.SetStrings(list(map("..".join, type_infos["dimensions"])))
                self.ArrayInitialValue.SetValue(type_infos["initial"])
            elif type_infos["type"] == "Structure":
                self.StructureElementsTable.SetData(type_infos["elements"])
            self.RefreshDisplayedInfos()
        self.ShowHighlights()
        self.StructureElementsTable.ResetView(self.StructureElementsGrid)
        self.StructureElementsGrid.RefreshButtons()
        self.Initializing = False

    def OnDerivationTypeChanged(self, event):
        wx.CallAfter(self.RefreshDisplayedInfos)
        wx.CallAfter(self.RefreshTypeInfos)
        event.Skip()

    def OnReturnKeyPressed(self, event):
        self.RefreshTypeInfos()

    def OnInfosChanged(self, event):
        self.RefreshTypeInfos()
        event.Skip()

    def OnSubrangeBaseTypeChanged(self, event):
        self.RefreshBoundsRange()
        self.RefreshTypeInfos()
        event.Skip()

    def OnSubrangeMinimumChanged(self, event):
        if not self.Initializing:
            wx.CallAfter(self.SubrangeMinimum.SetValue, min(self.SubrangeMaximum.GetValue(), self.SubrangeMinimum.GetValue()))
            wx.CallAfter(self.RefreshSubrangeInitialValueRange)
            wx.CallAfter(self.RefreshTypeInfos)
        event.Skip()

    def OnSubrangeMaximumChanged(self, event):
        if not self.Initializing:
            wx.CallAfter(self.SubrangeMaximum.SetValue, max(self.SubrangeMinimum.GetValue(), self.SubrangeMaximum.GetValue()))
            wx.CallAfter(self.RefreshSubrangeInitialValueRange)
            wx.CallAfter(self.RefreshTypeInfos)
        event.Skip()

    def OnDimensionsChanged(self, event):
        wx.CallAfter(self.RefreshTypeInfos)
        event.Skip()

    def OnEnumeratedValueEndEdit(self, event):
        text = event.GetText()
        values = self.EnumeratedValues.GetStrings()
        index = event.GetIndex()
        if index >= len(values) or values[index].upper() != text.upper():
            if text.upper() in [value.upper() for value in values]:
                message = wx.MessageDialog(self, _("\"%s\" value already defined!") % text, _("Error"), wx.OK | wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
                event.Veto()
            elif text.upper() in IEC_KEYWORDS:
                message = wx.MessageDialog(self, _("\"%s\" is a keyword. It can't be used!") % text, _("Error"), wx.OK | wx.ICON_ERROR)
                message.ShowModal()
                message.Destroy()
            else:
                initial_selected = None
                if index < len(values) and self.EnumeratedInitialValue.GetStringSelection() == values[index]:
                    initial_selected = text
                wx.CallAfter(self.RefreshEnumeratedValues, initial_selected)
                wx.CallAfter(self.RefreshTypeInfos)
                event.Skip()
        else:
            event.Skip()

    def OnEnumeratedValuesChanged(self, event):
        wx.CallAfter(self.RefreshEnumeratedValues)
        wx.CallAfter(self.RefreshTypeInfos)
        event.Skip()

    def ShowErrorMessage(self, message):
        dialog = wx.MessageDialog(self, message, _("Error"), wx.OK | wx.ICON_ERROR)
        dialog.ShowModal()
        dialog.Destroy()

    def OnStructureElementsGridCellChange(self, event):
        row, col = event.GetRow(), event.GetCol()
        colname = self.StructureElementsTable.GetColLabelValue(col, False)
        value = self.StructureElementsTable.GetValue(row, col)
        if colname == "Name":
            message = None
            if not TestIdentifier(value):
                message = _("\"%s\" is not a valid identifier!") % value
            elif value.upper() in IEC_KEYWORDS:
                message = _("\"%s\" is a keyword. It can't be used!") % value
#            elif value.upper() in self.PouNames:
#                message = _("A pou with \"%s\" as name exists!")%value
            elif value.upper() in [var["Name"].upper() for idx, var in enumerate(self.StructureElementsTable.GetData()) if idx != row]:
                message = _("An element named \"%s\" already exists in this structure!") % value
            else:
                self.RefreshTypeInfos()
                wx.CallAfter(self.StructureElementsTable.ResetView, self.StructureElementsGrid)
#                old_value = self.Table.GetOldValue()
#                if old_value != "":
#                    self.Controler.UpdateEditedElementUsedVariable(self.TagName, old_value, value)
#                self.Controler.BufferProject()
                event.Skip()

            if message is not None:
                event.Veto()
                wx.CallAfter(self.ShowErrorMessage, message)
        else:
            self.RefreshTypeInfos()
            wx.CallAfter(self.StructureElementsTable.ResetView, self.StructureElementsGrid)
            event.Skip()

    def OnStructureElementsGridSelectCell(self, event):
        wx.CallAfter(self.RefreshStructureButtons)
        event.Skip()

    def OnStructureElementsGridEditorShown(self, event):
        row, col = event.GetRow(), event.GetCol()
        if self.StructureElementsTable.GetColLabelValue(col, False) == "Type":
            type_menu = wx.Menu(title='')

            base_menu = wx.Menu(title='')
            for base_type in self.Controler.GetBaseTypes():
                new_entry = AppendMenu(base_menu, help='', kind=wx.ITEM_NORMAL, text=base_type)
                self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(base_type), new_entry)
            type_menu.AppendMenu(wx.ID_ANY, _("Base Types"), base_menu)

            datatype_menu = wx.Menu(title='')
            for datatype in self.Controler.GetDataTypes(self.TagName, False):
                new_entry = AppendMenu(datatype_menu, help='', kind=wx.ITEM_NORMAL, text=datatype)
                self.Bind(wx.EVT_MENU, self.GetElementTypeFunction(datatype), new_entry)
            type_menu.AppendMenu(wx.ID_ANY, _("User Data Types"), datatype_menu)

            new_entry = AppendMenu(type_menu, help='', kind=wx.ITEM_NORMAL, text=_("Array"))
            self.Bind(wx.EVT_MENU, self.ElementArrayTypeFunction, new_entry)

            rect = self.StructureElementsGrid.BlockToDeviceRect((row, col), (row, col))
            self.StructureElementsGrid.PopupMenu(type_menu, rect.x + rect.width, rect.y + self.StructureElementsGrid.GetColLabelSize())
            type_menu.Destroy()
            event.Veto()
        else:
            event.Skip()

    def GetElementTypeFunction(self, base_type):
        def ElementTypeFunction(event):
            row = self.StructureElementsGrid.GetGridCursorRow()
            self.StructureElementsTable.SetValueByName(row, "Type", base_type)
            self.RefreshTypeInfos()
            self.StructureElementsTable.ResetView(self.StructureElementsGrid)
        return ElementTypeFunction

    def ElementArrayTypeFunction(self, event):
        row = self.StructureElementsGrid.GetGridCursorRow()
        dialog = ArrayTypeDialog(self,
                                 self.Controler.GetDataTypes(self.TagName),
                                 self.StructureElementsTable.GetValueByName(row, "Type"))
        if dialog.ShowModal() == wx.ID_OK:
            self.StructureElementsTable.SetValueByName(row, "Type", dialog.GetValue())
            self.RefreshTypeInfos()
            self.StructureElementsTable.ResetView(self.StructureElementsGrid)
        dialog.Destroy()

    def RefreshDisplayedInfos(self):
        selected = self.DATATYPE_TYPES_DICT[self.DerivationType.GetStringSelection()]
        if selected != self.CurrentPanel:
            if self.CurrentPanel == "Directly":
                self.DirectlyPanel.Hide()
            elif self.CurrentPanel == "Subrange":
                self.SubrangePanel.Hide()
            elif self.CurrentPanel == "Enumerated":
                self.EnumeratedPanel.Hide()
            elif self.CurrentPanel == "Array":
                self.ArrayPanel.Hide()
            elif self.CurrentPanel == "Structure":
                self.StructurePanel.Hide()
            self.CurrentPanel = selected
            if selected == "Directly":
                self.DirectlyPanel.Show()
            elif selected == "Subrange":
                self.SubrangePanel.Show()
            elif selected == "Enumerated":
                self.EnumeratedPanel.Show()
            elif selected == "Array":
                self.ArrayPanel.Show()
            elif selected == "Structure":
                self.StructurePanel.Show()
            self.MainSizer.Layout()

    def RefreshEnumeratedValues(self, initial_selected=None):
        if initial_selected is None:
            initial_selected = self.EnumeratedInitialValue.GetStringSelection()
        self.EnumeratedInitialValue.Clear()
        self.EnumeratedInitialValue.Append("")
        for value in self.EnumeratedValues.GetStrings():
            self.EnumeratedInitialValue.Append(value)
        self.EnumeratedInitialValue.SetStringSelection(initial_selected)

    def RefreshBoundsRange(self):
        range = self.Controler.GetDataTypeRange(self.SubrangeBaseType.GetStringSelection())
        if range is not None:
            min_value, max_value = range
            self.SubrangeMinimum.SetBounds(min_value, max_value)
            self.SubrangeMinimum.SetValue(min(max(min_value, self.SubrangeMinimum.GetValue()), max_value))
            self.SubrangeMaximum.SetBounds(min_value, max_value)
            self.SubrangeMaximum.SetValue(min(max(min_value, self.SubrangeMaximum.GetValue()), max_value))

    def RefreshSubrangeInitialValueRange(self):
        self.SubrangeInitialValue.SetBounds(self.SubrangeMinimum.GetValue(), self.SubrangeMaximum.GetValue())

    def RefreshTypeInfos(self):
        selected = self.DATATYPE_TYPES_DICT[self.DerivationType.GetStringSelection()]
        infos = {"type": selected}
        if selected == "Directly":
            infos["base_type"] = self.DirectlyBaseType.GetStringSelection()
            infos["initial"] = self.DirectlyInitialValue.GetValue()
        elif selected == "Subrange":
            infos["base_type"] = self.SubrangeBaseType.GetStringSelection()
            infos["min"] = str(self.SubrangeMinimum.GetValue())
            infos["max"] = str(self.SubrangeMaximum.GetValue())
            initial_value = self.SubrangeInitialValue.GetValue()
            if initial_value == infos["min"]:
                infos["initial"] = ""
            else:
                infos["initial"] = str(initial_value)
        elif selected == "Enumerated":
            infos["values"] = self.EnumeratedValues.GetStrings()
            infos["initial"] = self.EnumeratedInitialValue.GetStringSelection()
        elif selected == "Array":
            infos["base_type"] = self.ArrayBaseType.GetStringSelection()
            infos["dimensions"] = []
            for dimensions in self.ArrayDimensions.GetStrings():
                result = DIMENSION_MODEL.match(dimensions)
                if result is None:
                    message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!") % dimensions, _("Error"), wx.OK | wx.ICON_ERROR)
                    message.ShowModal()
                    message.Destroy()
                    self.RefreshView()
                    return
                bounds = result.groups()
                if int(bounds[0]) >= int(bounds[1]):
                    message = wx.MessageDialog(self, _("\"%s\" value isn't a valid array dimension!\nRight value must be greater than left value.") % dimensions, _("Error"), wx.OK | wx.ICON_ERROR)
                    message.ShowModal()
                    message.Destroy()
                    self.RefreshView()
                    return
                infos["dimensions"].append(bounds)
            infos["initial"] = self.ArrayInitialValue.GetValue()
        elif selected == "Structure":
            infos["elements"] = self.StructureElementsTable.GetData()
            infos["initial"] = ""
        self.Controler.SetDataTypeInfos(self.TagName, infos)
        self.ParentWindow.RefreshTitle()
        self.ParentWindow.RefreshFileMenu()
        self.ParentWindow.RefreshEditMenu()

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

    def OnRefreshHighlightsTimer(self, event):
        self.RefreshView()
        event.Skip()

    def ClearHighlights(self, highlight_type=None):
        if highlight_type is None:
            self.Highlights = []
        else:
            self.Highlights = [(infos, start, end, highlight) for (infos, start, end, highlight) in self.Highlights if highlight != highlight_type]
        for control in self.HighlightControls.values():
            if isinstance(control, (wx.ComboBox, wx.SpinCtrl)):
                control.SetBackgroundColour(wx.NullColour)
                control.SetForegroundColour(wx.NullColour)
            elif isinstance(control, wx.TextCtrl):
                value = control.GetValueStr() if isinstance(control, CustomIntCtrl) else \
                        control.GetValue()
                control.SetStyle(0, len(value), wx.TextAttr(wx.NullColour))
            elif isinstance(control, wx.adv.EditableListBox):
                listctrl = control.GetListCtrl()
                for i in range(listctrl.GetItemCount()):
                    listctrl.SetItemBackgroundColour(i, wx.NullColour)
                    listctrl.SetItemTextColour(i, wx.NullColour)
        self.StructureElementsTable.ClearHighlights(highlight_type)
        self.RefreshView()

    def AddHighlight(self, infos, start, end, highlight_type):
        self.Highlights.append((infos, start, end, highlight_type))
        self.RefreshHighlightsTimer.Start(int(REFRESH_HIGHLIGHT_PERIOD * 1000), oneShot=True)

    def ShowHighlights(self):
        type_infos = self.Controler.GetDataTypeInfos(self.TagName)
        for infos, start, end, highlight_type in self.Highlights:
            if infos[0] == "struct":
                self.StructureElementsTable.AddHighlight(infos[1:], highlight_type)
            else:
                control = self.HighlightControls.get((type_infos["type"], infos[0]), None)
                if control is not None:
                    if isinstance(control, (wx.ComboBox, wx.SpinCtrl)):
                        control.SetBackgroundColour(highlight_type[0])
                        control.SetForegroundColour(highlight_type[1])
                    elif isinstance(control, wx.TextCtrl):
                        control.SetStyle(start[1], end[1] + 1, wx.TextAttr(highlight_type[1], highlight_type[0]))
                    elif isinstance(control, wx.adv.EditableListBox):
                        listctrl = control.GetListCtrl()
                        listctrl.SetItemBackgroundColour(infos[1], highlight_type[0])
                        listctrl.SetItemTextColour(infos[1], highlight_type[1])
                        listctrl.Select(listctrl.FocusedItem, False)