Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
andrej@1571: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
andrej@1696: # Copyright (C) 2017: Andrey Skvortsov <andrej.skvortzov@gmail.com>
andrej@1571: #
andrej@1571: # See COPYING file for copyrights details.
andrej@1571: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
andrej@1571: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
andrej@1571: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1853: from __future__ import absolute_import
Laurent@814: import wx
Laurent@814: 
Laurent@1244: from graphics.GraphicCommons import INPUT, INOUT, OUTPUT
Laurent@1244: from graphics.FBD_Objects import FBD_Variable
andrej@1853: from dialogs.BlockPreviewDialog import BlockPreviewDialog
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                                    Helpers
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@1244: #                        Set Variable Parameters Dialog
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@1244: 
Laurent@1244: class FBDVariableDialog(BlockPreviewDialog):
andrej@1736:     """
andrej@1736:     Class that implements a dialog for defining parameters of a FBD variable graphic
andrej@1736:     element
andrej@1736:     """
Laurent@1244: 
Laurent@1259:     def __init__(self, parent, controller, tagname, exclude_input=False):
Laurent@1244:         """
Laurent@1244:         Constructor
Laurent@1244:         @param parent: Parent wx.Window of dialog for modal
Laurent@1244:         @param controller: Reference to project controller
Laurent@1244:         @param tagname: Tagname of project POU edited
Laurent@1259:         @param exclude_input: Exclude input from variable class selection
Laurent@1244:         """
Laurent@1244:         BlockPreviewDialog.__init__(self, parent, controller, tagname,
andrej@1768:                                     title=_('Variable Properties'))
andrej@1730: 
andrej@2301:         # Dictionaries containing correspondence between variable block class and string
andrej@2301:         # to be shown in Class combo box in both sense
andrej@2301:         self.VARIABLE_CLASSES_DICT = {
andrej@2301:             INPUT:  _("Input"),
andrej@2301:             INOUT:  _("InOut"),
andrej@2301:             OUTPUT: _("Output")
andrej@2301:         }
andrej@2301: 
andrej@2301:         self.VARIABLE_CLASSES_DICT_REVERSE = dict(
andrej@2301:             [(value, key) for key, value in self.VARIABLE_CLASSES_DICT.iteritems()])
andrej@2301: 
Laurent@1250:         # Init common sizers
Laurent@1250:         self._init_sizers(4, 2, 4, None, 3, 2)
andrej@1730: 
Laurent@1244:         # Create label for variable class
Laurent@814:         class_label = wx.StaticText(self, label=_('Class:'))
Laurent@1250:         self.LeftGridSizer.AddWindow(class_label, flag=wx.GROW)
andrej@1730: 
Laurent@1244:         # Create a combo box for defining variable class
Laurent@814:         self.Class = wx.ComboBox(self, style=wx.CB_READONLY)
Laurent@814:         self.Bind(wx.EVT_COMBOBOX, self.OnClassChanged, self.Class)
Laurent@1250:         self.LeftGridSizer.AddWindow(self.Class, flag=wx.GROW)
andrej@1730: 
Laurent@1244:         # Create label for variable execution order
andrej@1730:         execution_order_label = wx.StaticText(self,
andrej@1768:                                               label=_('Execution Order:'))
Laurent@1250:         self.LeftGridSizer.AddWindow(execution_order_label, flag=wx.GROW)
andrej@1730: 
Laurent@1244:         # Create spin control for defining variable execution order
Laurent@814:         self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS)
andrej@1730:         self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged,
Laurent@1244:                   self.ExecutionOrder)
Laurent@1250:         self.LeftGridSizer.AddWindow(self.ExecutionOrder, flag=wx.GROW)
andrej@1730: 
Laurent@1244:         # Create label for variable expression
Laurent@1182:         name_label = wx.StaticText(self, label=_('Expression:'))
andrej@1730:         self.RightGridSizer.AddWindow(name_label, border=5,
andrej@1768:                                       flag=wx.GROW | wx.BOTTOM)
andrej@1730: 
Laurent@1244:         # Create text control for defining variable expression
Laurent@1182:         self.Expression = wx.TextCtrl(self)
Laurent@1182:         self.Bind(wx.EVT_TEXT, self.OnExpressionChanged, self.Expression)
Laurent@1250:         self.RightGridSizer.AddWindow(self.Expression, flag=wx.GROW)
andrej@1730: 
Laurent@1244:         # Create a list box to selected variable expression in the list of
Laurent@1244:         # variables defined in POU
andrej@1740:         self.VariableName = wx.ListBox(self, size=wx.Size(-1, 120),
andrej@1768:                                        style=wx.LB_SINGLE | wx.LB_SORT)
Laurent@814:         self.Bind(wx.EVT_LISTBOX, self.OnNameChanged, self.VariableName)
andrej@1745:         self.RightGridSizer.AddWindow(self.VariableName, border=4, flag=wx.GROW | wx.TOP)
andrej@1730: 
Laurent@1244:         # Add preview panel and associated label to sizers
Laurent@1250:         self.MainSizer.AddWindow(self.PreviewLabel, border=20,
andrej@1768:                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
Laurent@1250:         self.MainSizer.AddWindow(self.Preview, border=20,
andrej@1768:                                  flag=wx.GROW | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@1244:         # Add buttons sizer to sizers
andrej@1768:         self.MainSizer.AddSizer(
andrej@1768:             self.ButtonSizer, border=20,
andrej@1768:             flag=wx.ALIGN_RIGHT | wx.BOTTOM | wx.LEFT | wx.RIGHT)
andrej@1730: 
Laurent@1244:         # Set options that can be selected in class combo box
andrej@2301:         for var_class, choice in self.VARIABLE_CLASSES_DICT.iteritems():
Laurent@1259:             if not exclude_input or var_class != INPUT:
Laurent@1259:                 self.Class.Append(choice)
Laurent@1259:         self.Class.SetSelection(0)
andrej@1730: 
Laurent@1246:         # Extract list of variables defined in POU
Laurent@1246:         self.RefreshVariableList()
andrej@1730: 
Laurent@1244:         # Refresh values in name list box
Laurent@814:         self.RefreshNameList()
andrej@1730: 
andrej@1696:         self.Preview.SetInitialSize(wx.Size(-1, 60))
andrej@1696:         self.Fit()
andrej@1696: 
Laurent@1244:         # Class combo box is default control having keyboard focus
Laurent@814:         self.Class.SetFocus()
Laurent@814: 
Laurent@814:     def RefreshNameList(self):
Laurent@1244:         """
Laurent@1244:         Called to refresh names in name list box
Laurent@1244:         """
Laurent@1244:         # Get variable class to select POU variable applicable
andrej@2301:         var_class = self.VARIABLE_CLASSES_DICT_REVERSE[
andrej@1878:             self.Class.GetStringSelection()]
andrej@1730: 
Laurent@1244:         # Refresh names in name list box by selecting variables in POU variables
Laurent@1244:         # list that can be applied to variable class
Laurent@814:         self.VariableName.Clear()
andrej@1847:         for name, (var_type, _value_type) in self.VariableList.iteritems():
Laurent@814:             if var_type != "Input" or var_class == INPUT:
Laurent@814:                 self.VariableName.Append(name)
andrej@1730: 
Laurent@1244:         # Get variable expression and select corresponding value in name list
Laurent@1244:         # box if it exists
Laurent@1244:         selected = self.Expression.GetValue()
andrej@1766:         if selected != "" and self.VariableName.FindString(selected) != wx.NOT_FOUND:
Laurent@814:             self.VariableName.SetStringSelection(selected)
Laurent@814:         else:
Laurent@1182:             self.VariableName.SetSelection(wx.NOT_FOUND)
andrej@1730: 
Laurent@1244:         # Disable name list box if no name present inside
Laurent@814:         self.VariableName.Enable(self.VariableName.GetCount() > 0)
andrej@1730: 
Laurent@814:     def SetValues(self, values):
Laurent@1244:         """
Laurent@1244:         Set default variable parameters
Laurent@1244:         @param values: Variable parameters values
Laurent@1244:         """
andrej@1730: 
Laurent@1244:         # Get class parameter value
Laurent@1244:         var_class = values.get("class", None)
Laurent@1244:         if var_class is not None:
Laurent@1244:             # Set class selected in class combo box
andrej@2301:             self.Class.SetStringSelection(self.VARIABLE_CLASSES_DICT[var_class])
Laurent@1244:             # Refresh names in name list box according to var class
Laurent@814:             self.RefreshNameList()
andrej@1730: 
Laurent@1244:         # For each parameters defined, set corresponding control value
Laurent@1244:         for name, value in values.items():
andrej@1730: 
Laurent@1244:             # Parameter is variable expression
Laurent@1244:             if name == "expression":
Laurent@1244:                 # Set expression text control value
Laurent@1244:                 self.Expression.ChangeValue(value)
Laurent@1244:                 # Select corresponding text in name list box if it exists
Laurent@1244:                 if self.VariableName.FindString(value) != wx.NOT_FOUND:
Laurent@1244:                     self.VariableName.SetStringSelection(value)
Laurent@1244:                 else:
Laurent@1244:                     self.VariableName.SetSelection(wx.NOT_FOUND)
andrej@1730: 
Laurent@1244:             # Parameter is variable execution order
Laurent@1244:             elif name == "executionOrder":
Laurent@1244:                 self.ExecutionOrder.SetValue(value)
andrej@1730: 
Laurent@1244:         # Refresh preview panel
Edouard@2591:         self.Refresh()
andrej@1696:         self.Fit()
andrej@1730: 
Laurent@814:     def GetValues(self):
Laurent@1244:         """
Laurent@1244:         Return block parameters defined in dialog
Laurent@1244:         @return: {parameter_name: parameter_value,...}
Laurent@1244:         """
Laurent@1244:         expression = self.Expression.GetValue()
Laurent@1244:         values = {
andrej@2301:             "class": self.VARIABLE_CLASSES_DICT_REVERSE[
andrej@1878:                 self.Class.GetStringSelection()],
Laurent@1244:             "expression": expression,
Laurent@1246:             "var_type": self.VariableList.get(expression, (None, None))[1],
Laurent@1244:             "executionOrder": self.ExecutionOrder.GetValue()}
Laurent@1244:         values["width"], values["height"] = self.Element.GetSize()
Laurent@814:         return values
Laurent@814: 
Laurent@814:     def OnOK(self, event):
Laurent@1244:         """
Laurent@1244:         Called when dialog OK button is pressed
Laurent@1244:         Test if parameters defined are valid
Laurent@1244:         @param event: wx.Event from OK button
Laurent@1244:         """
Laurent@814:         message = None
andrej@1730: 
Laurent@1244:         # Test that an expression have been selected or typed by user
Laurent@1182:         value = self.Expression.GetValue()
Laurent@814:         if value == "":
Laurent@814:             message = _("At least a variable or an expression must be selected!")
andrej@1730: 
Laurent@1244:         # Show error message if an error is detected
Laurent@814:         if message is not None:
Laurent@1244:             self.ShowErrorMessage(message)
andrej@1730: 
Laurent@814:         else:
Laurent@1244:             # Call BlockPreviewDialog function
Laurent@1244:             BlockPreviewDialog.OnOK(self, event)
Laurent@814: 
Laurent@814:     def OnClassChanged(self, event):
Laurent@1244:         """
Laurent@1244:         Called when variable class value changed
Laurent@1244:         @param event: wx.ComboBoxEvent
Laurent@1244:         """
Laurent@1244:         # Refresh name list box values
Laurent@814:         self.RefreshNameList()
andrej@1730: 
Edouard@2591:         self.Refresh()
Laurent@814:         event.Skip()
Laurent@814: 
Laurent@814:     def OnNameChanged(self, event):
Laurent@1244:         """
Laurent@1244:         Called when name selected in name list box changed
Laurent@1244:         @param event: wx.ListBoxEvent
Laurent@1244:         """
Laurent@1244:         # Change expression test control value to the value selected in name
Laurent@1244:         # list box if value selected is valid
Laurent@1244:         if self.VariableName.GetSelection() != wx.NOT_FOUND:
Laurent@1244:             self.Expression.ChangeValue(self.VariableName.GetStringSelection())
andrej@1730: 
Edouard@2591:         self.Refresh()
andrej@1730:         event.Skip()
andrej@1730: 
Laurent@814:     def OnExpressionChanged(self, event):
Laurent@1244:         """
Laurent@1244:         Called when expression text control is changed by user
Laurent@1244:         @param event: wx.ListBoxEvent
Laurent@1244:         """
Laurent@1244:         # Select the corresponding value in name list box if it exists
Laurent@1182:         self.VariableName.SetSelection(
Laurent@1244:             self.VariableName.FindString(self.Expression.GetValue()))
andrej@1730: 
Edouard@2591:         self.Refresh()
andrej@1730:         event.Skip()
andrej@1730: 
Laurent@814:     def OnExecutionOrderChanged(self, event):
Laurent@1244:         """
Laurent@1244:         Called when block execution control value changed
Laurent@1244:         @param event: wx.SpinEvent
Laurent@1244:         """
Edouard@2591:         self.Refresh()
Edouard@2587:         event.Skip()
Edouard@2587: 
Edouard@2587:     def DrawPreview(self):
Laurent@1244:         """
Laurent@1244:         Refresh preview panel of graphic element
Laurent@1244:         Override BlockPreviewDialog function
Laurent@1244:         """
Laurent@1244:         # Get expression value to put in FBD variable element
Laurent@1182:         name = self.Expression.GetValue()
andrej@1730: 
Laurent@1244:         # Set graphic element displayed, creating a FBD variable element
andrej@1768:         self.Element = FBD_Variable(
andrej@1768:             self.Preview,
andrej@2301:             self.VARIABLE_CLASSES_DICT_REVERSE[self.Class.GetStringSelection()],
andrej@1768:             name,
andrej@1768:             self.VariableList.get(name, ("", ""))[1],
andrej@1768:             executionOrder=self.ExecutionOrder.GetValue())
andrej@1730: 
Laurent@1244:         # Call BlockPreviewDialog function
Edouard@2587:         BlockPreviewDialog.DrawPreview(self)