Laurent@1236: #!/usr/bin/env python Laurent@1236: # -*- coding: utf-8 -*- Laurent@1236: 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) 2013: Edouard TISSERANT and Laurent BESSARD andrej@1696: # Copyright (C) 2017: Andrey Skvortsov 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@1236: Laurent@1236: import wx Laurent@1236: Laurent@1236: from plcopen.structures import TestIdentifier, IEC_KEYWORDS Laurent@1236: from graphics.GraphicCommons import FREEDRAWING_MODE Laurent@1236: Laurent@1236: #------------------------------------------------------------------------------- Laurent@1236: # Dialog with preview for graphic block Laurent@1236: #------------------------------------------------------------------------------- Laurent@1236: Laurent@1236: Laurent@1236: class BlockPreviewDialog(wx.Dialog): andrej@1736: """ andrej@1736: Class that implements a generic dialog containing a preview panel for displaying andrej@1736: graphic created by dialog andrej@1736: """ andrej@1730: andrej@1696: def __init__(self, parent, controller, tagname, title): Laurent@1242: """ Laurent@1242: Constructor Laurent@1242: @param parent: Parent wx.Window of dialog for modal Laurent@1242: @param controller: Reference to project controller Laurent@1242: @param tagname: Tagname of project POU edited Laurent@1242: @param title: Title of dialog frame Laurent@1242: """ andrej@1696: wx.Dialog.__init__(self, parent, title=title) andrej@1730: Laurent@1242: # Save reference to Laurent@1236: self.Controller = controller Laurent@1236: self.TagName = tagname andrej@1730: Laurent@1242: # Label for preview Laurent@1236: self.PreviewLabel = wx.StaticText(self, label=_('Preview:')) andrej@1730: Laurent@1242: # Create Preview panel Laurent@1236: self.Preview = wx.Panel(self, style=wx.SIMPLE_BORDER) Laurent@1236: self.Preview.SetBackgroundColour(wx.WHITE) andrej@1730: Laurent@1242: # Add function to preview panel so that it answers to graphic elements Laurent@1242: # like Viewer andrej@1740: setattr(self.Preview, "GetDrawingMode", lambda: FREEDRAWING_MODE) andrej@1740: setattr(self.Preview, "GetScaling", lambda: None) Laurent@1236: setattr(self.Preview, "GetBlockType", controller.GetBlockType) Laurent@1236: setattr(self.Preview, "IsOfType", controller.IsOfType) andrej@1730: Laurent@1242: # Bind paint event on Preview panel Laurent@1236: self.Preview.Bind(wx.EVT_PAINT, self.OnPaint) andrej@1730: Laurent@1242: # Add default dialog buttons sizer andrej@1745: self.ButtonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL | wx.CENTRE) andrej@1730: self.Bind(wx.EVT_BUTTON, self.OnOK, Laurent@1236: self.ButtonSizer.GetAffirmativeButton()) andrej@1730: Laurent@1244: self.Element = None # Graphic element to display in preview Laurent@1244: self.MinElementSize = None # Graphic element minimal size andrej@1730: Laurent@1244: # Variable containing the graphic element name when dialog is opened Laurent@1244: self.DefaultElementName = None andrej@1696: self.Fit() andrej@1730: Laurent@1246: # List of variables defined in POU {var_name: (var_class, var_type),...} Laurent@1246: self.VariableList = {} andrej@1730: Laurent@1236: def __del__(self): Laurent@1242: """ Laurent@1242: Destructor Laurent@1242: """ Laurent@1242: # Remove reference to project controller Laurent@1236: self.Controller = None andrej@1730: andrej@1767: def _init_sizers(self, andrej@1767: main_rows, main_growable_row, andrej@1767: left_rows, left_growable_row, andrej@1767: right_rows, right_growable_row): Laurent@1250: """ Laurent@1250: Initialize common sizers Laurent@1250: @param main_rows: Number of rows in main sizer Laurent@1250: @param main_growable_row: Row that is growable in main sizer, None if no Laurent@1250: row is growable Laurent@1250: @param left_rows: Number of rows in left grid sizer Laurent@1250: @param left_growable_row: Row that is growable in left grid sizer, None Laurent@1250: if no row is growable Laurent@1250: @param right_rows: Number of rows in right grid sizer Laurent@1250: @param right_growable_row: Row that is growable in right grid sizer, Laurent@1250: None if no row is growable Laurent@1250: """ Laurent@1250: # Create dialog main sizer andrej@1730: self.MainSizer = wx.FlexGridSizer(cols=1, hgap=0, Laurent@1250: rows=main_rows, vgap=10) Laurent@1250: self.MainSizer.AddGrowableCol(0) Laurent@1250: if main_growable_row is not None: Laurent@1250: self.MainSizer.AddGrowableRow(main_growable_row) andrej@1730: Laurent@1250: # Create a sizer for dividing parameters in two columns andrej@1696: self.ColumnSizer = wx.BoxSizer(wx.HORIZONTAL) andrej@1730: self.MainSizer.AddSizer(self.ColumnSizer, border=20, andrej@1745: flag=wx.GROW | wx.TOP | wx.LEFT | wx.RIGHT) andrej@1730: Laurent@1250: # Create a sizer for left column andrej@1730: self.LeftGridSizer = wx.FlexGridSizer(cols=1, hgap=0, Laurent@1250: rows=left_rows, vgap=5) Laurent@1250: self.LeftGridSizer.AddGrowableCol(0) Laurent@1250: if left_growable_row is not None: Laurent@1250: self.LeftGridSizer.AddGrowableRow(left_growable_row) andrej@1730: self.ColumnSizer.AddSizer(self.LeftGridSizer, 1, border=5, andrej@1745: flag=wx.GROW | wx.RIGHT | wx.EXPAND) andrej@1730: Laurent@1250: # Create a sizer for right column andrej@1730: self.RightGridSizer = wx.FlexGridSizer(cols=1, hgap=0, Laurent@1250: rows=right_rows, vgap=0) Laurent@1250: self.RightGridSizer.AddGrowableCol(0) Laurent@1250: if right_growable_row is not None: Laurent@1250: self.RightGridSizer.AddGrowableRow(right_growable_row) andrej@1730: self.ColumnSizer.AddSizer(self.RightGridSizer, 1, border=5, andrej@1745: flag=wx.GROW | wx.LEFT) andrej@1730: Laurent@1250: self.SetSizer(self.MainSizer) andrej@1730: Laurent@1244: def SetMinElementSize(self, size): Laurent@1242: """ Laurent@1242: Define minimal graphic element size Laurent@1244: @param size: Tuple containing minimal size (width, height) Laurent@1244: """ Laurent@1244: self.MinElementSize = size andrej@1730: Laurent@1249: def GetMinElementSize(self): Laurent@1249: """ Laurent@1249: Get minimal graphic element size Laurent@1249: @return: Tuple containing minimal size (width, height) or None if no Laurent@1249: element defined Laurent@1249: May be overridden by inherited classes Laurent@1249: """ Laurent@1249: if self.Element is None: Laurent@1249: return None andrej@1730: Laurent@1249: return self.Element.GetMinSize() andrej@1730: Laurent@1236: def SetPreviewFont(self, font): Laurent@1242: """ Laurent@1242: Set font of Preview panel Laurent@1242: @param font: wx.Font object containing font style Laurent@1242: """ Laurent@1236: self.Preview.SetFont(font) andrej@1730: Laurent@1246: def RefreshVariableList(self): Laurent@1246: """ Laurent@1246: Extract list of variables defined in POU Laurent@1246: """ Laurent@1246: # Get list of variables defined in POU Laurent@1246: self.VariableList = { Laurent@1347: var.Name: (var.Class, var.Type) Laurent@1246: for var in self.Controller.GetEditedElementInterfaceVars( Laurent@1246: self.TagName) Laurent@1347: if var.Edit} andrej@1730: andrej@1730: # Add POU name to variable list if POU is a function Laurent@1246: returntype = self.Controller.GetEditedElementInterfaceReturnType( Laurent@1347: self.TagName) Laurent@1246: if returntype is not None: Laurent@1246: self.VariableList[ Laurent@1246: self.Controller.GetEditedElementName(self.TagName)] = \ Laurent@1246: ("Output", returntype) andrej@1730: Laurent@1246: # Add POU name if POU is a transition Laurent@1246: words = self.TagName.split("::") Laurent@1246: if words[0] == "T": Laurent@1246: self.VariableList[words[2]] = ("Output", "BOOL") andrej@1730: Laurent@1244: def TestElementName(self, element_name): Laurent@1242: """ Laurent@1245: Test displayed graphic element name Laurent@1244: @param element_name: Graphic element name Laurent@1242: """ Laurent@1242: # Variable containing error message format Laurent@1242: message_format = None Laurent@1242: # Get graphic element name in upper case Laurent@1244: uppercase_element_name = element_name.upper() andrej@1730: Laurent@1242: # Test if graphic element name is a valid identifier Laurent@1244: if not TestIdentifier(element_name): Laurent@1242: message_format = _("\"%s\" is not a valid identifier!") andrej@1730: Laurent@1242: # Test that graphic element name isn't a keyword Laurent@1244: elif uppercase_element_name in IEC_KEYWORDS: Laurent@1242: message_format = _("\"%s\" is a keyword. It can't be used!") andrej@1730: Laurent@1242: # Test that graphic element name isn't a POU name Laurent@1244: elif uppercase_element_name in self.Controller.GetProjectPouNames(): Laurent@1242: message_format = _("\"%s\" pou already exists!") andrej@1730: Laurent@1242: # Test that graphic element name isn't already used in POU by a variable Laurent@1242: # or another graphic element andrej@1730: elif ((self.DefaultElementName is None or andrej@1730: self.DefaultElementName.upper() != uppercase_element_name) and andrej@1764: uppercase_element_name in self.Controller.GetEditedElementVariables(self.TagName)): Laurent@1242: message_format = _("\"%s\" element for this pou already exists!") andrej@1730: Laurent@1242: # If an error have been identify, show error message dialog Laurent@1242: if message_format is not None: Laurent@1244: self.ShowErrorMessage(message_format % element_name) Laurent@1242: # Test failed Laurent@1236: return False andrej@1730: Laurent@1242: # Test succeed Laurent@1236: return True andrej@1730: Laurent@1236: def ShowErrorMessage(self, message): Laurent@1242: """ Laurent@1242: Show an error message dialog over this dialog Laurent@1242: @param message: Error message to display Laurent@1242: """ andrej@1730: dialog = wx.MessageDialog(self, message, andrej@1730: _("Error"), andrej@1745: wx.OK | wx.ICON_ERROR) Laurent@1236: dialog.ShowModal() Laurent@1236: dialog.Destroy() andrej@1730: Laurent@1236: def OnOK(self, event): Laurent@1242: """ Laurent@1242: Called when dialog OK button is pressed Laurent@1242: Need to be overridden by inherited classes to check that dialog values Laurent@1242: are valid Laurent@1242: @param event: wx.Event from OK button Laurent@1242: """ Laurent@1242: # Close dialog Laurent@1236: self.EndModal(wx.ID_OK) andrej@1730: Laurent@1236: def RefreshPreview(self): Laurent@1242: """ Laurent@1242: Refresh preview panel of graphic element Laurent@1242: May be overridden by inherited classes Laurent@1242: """ Laurent@1242: # Init preview panel paint device context Laurent@1236: dc = wx.ClientDC(self.Preview) Laurent@1236: dc.SetFont(self.Preview.GetFont()) Laurent@1236: dc.Clear() andrej@1730: Laurent@1242: # Return immediately if no graphic element defined Laurent@1244: if self.Element is None: Laurent@1242: return andrej@1730: Laurent@1242: # Calculate block size according to graphic element min size due to its Laurent@1242: # parameters and graphic element min size defined Laurent@1249: min_width, min_height = self.GetMinElementSize() Laurent@1244: width = max(self.MinElementSize[0], min_width) Laurent@1244: height = max(self.MinElementSize[1], min_height) Laurent@1244: self.Element.SetSize(width, height) andrej@1730: Laurent@1253: # Get element position and bounding box to center in preview Laurent@1253: posx, posy = self.Element.GetPosition() Laurent@1252: bbox = self.Element.GetBoundingBox() andrej@1730: Laurent@1242: # Get Preview panel size Laurent@1242: client_size = self.Preview.GetClientSize() andrej@1730: Laurent@1242: # If graphic element is too big to be displayed in preview panel, Laurent@1242: # calculate preview panel scale so that graphic element fit inside andrej@1767: k = 1.1 if (bbox.width * 1.1 > client_size.width or andrej@1767: bbox.height * 1.1 > client_size.height) \ andrej@1767: else 1.0 andrej@1730: scale = (max(float(bbox.width) / client_size.width, andrej@1767: float(bbox.height) / client_size.height) * k) Laurent@1242: dc.SetUserScale(1.0 / scale, 1.0 / scale) andrej@1730: Laurent@1242: # Center graphic element in preview panel Laurent@1253: x = int(client_size.width * scale - bbox.width) / 2 + posx - bbox.x Laurent@1253: y = int(client_size.height * scale - bbox.height) / 2 + posy - bbox.y Laurent@1244: self.Element.SetPosition(x, y) andrej@1730: Laurent@1242: # Draw graphic element Laurent@1244: self.Element.Draw(dc) andrej@1730: Laurent@1236: def OnPaint(self, event): Laurent@1242: """ Laurent@1242: Called when Preview panel need to be redraw Laurent@1242: @param event: wx.PaintEvent Laurent@1242: """ Laurent@1236: self.RefreshPreview() Laurent@1236: event.Skip()