Laurent@1236: #!/usr/bin/env python Laurent@1236: # -*- coding: utf-8 -*- Laurent@1236: Laurent@1236: #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor Laurent@1236: #based on the plcopen standard. Laurent@1236: # Laurent@1236: #Copyright (C) 2013: Edouard TISSERANT and Laurent BESSARD Laurent@1236: # Laurent@1236: #See COPYING file for copyrights details. Laurent@1236: # Laurent@1236: #This library is free software; you can redistribute it and/or Laurent@1236: #modify it under the terms of the GNU General Public Laurent@1236: #License as published by the Free Software Foundation; either Laurent@1236: #version 2.1 of the License, or (at your option) any later version. Laurent@1236: # Laurent@1236: #This library is distributed in the hope that it will be useful, Laurent@1236: #but WITHOUT ANY WARRANTY; without even the implied warranty of Laurent@1236: #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Laurent@1236: #General Public License for more details. Laurent@1236: # Laurent@1236: #You should have received a copy of the GNU General Public Laurent@1236: #License along with this library; if not, write to the Free Software Laurent@1236: #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 that implements a generic dialog containing a preview panel for displaying Laurent@1236: graphic created by dialog Laurent@1236: """ Laurent@1236: Laurent@1236: class BlockPreviewDialog(wx.Dialog): Laurent@1250: Laurent@1236: def __init__(self, parent, controller, tagname, size, 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 size: wx.Size object containing size of dialog Laurent@1242: @param title: Title of dialog frame Laurent@1242: """ Laurent@1236: wx.Dialog.__init__(self, parent, size=size, title=title) Laurent@1236: Laurent@1242: # Save reference to Laurent@1236: self.Controller = controller Laurent@1236: self.TagName = tagname Laurent@1236: Laurent@1242: # Label for preview Laurent@1236: self.PreviewLabel = wx.StaticText(self, label=_('Preview:')) Laurent@1236: Laurent@1242: # Create Preview panel Laurent@1236: self.Preview = wx.Panel(self, style=wx.SIMPLE_BORDER) Laurent@1236: self.Preview.SetBackgroundColour(wx.WHITE) Laurent@1242: Laurent@1242: # Add function to preview panel so that it answers to graphic elements Laurent@1242: # like Viewer Laurent@1236: setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE) Laurent@1236: setattr(self.Preview, "GetScaling", lambda:None) Laurent@1236: setattr(self.Preview, "GetBlockType", controller.GetBlockType) Laurent@1236: setattr(self.Preview, "IsOfType", controller.IsOfType) Laurent@1242: Laurent@1242: # Bind paint event on Preview panel Laurent@1236: self.Preview.Bind(wx.EVT_PAINT, self.OnPaint) Laurent@1236: Laurent@1242: # Add default dialog buttons sizer Laurent@1236: self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) Laurent@1236: self.Bind(wx.EVT_BUTTON, self.OnOK, Laurent@1236: self.ButtonSizer.GetAffirmativeButton()) Laurent@1236: Laurent@1244: self.Element = None # Graphic element to display in preview Laurent@1244: self.MinElementSize = None # Graphic element minimal size Laurent@1244: Laurent@1244: # Variable containing the graphic element name when dialog is opened Laurent@1244: self.DefaultElementName = None Laurent@1242: Laurent@1246: # List of variables defined in POU {var_name: (var_class, var_type),...} Laurent@1246: self.VariableList = {} Laurent@1246: Laurent@1236: def __del__(self): Laurent@1242: """ Laurent@1242: Destructor Laurent@1242: """ Laurent@1242: # Remove reference to project controller Laurent@1236: self.Controller = None Laurent@1236: Laurent@1250: def _init_sizers(self, main_rows, main_growable_row, Laurent@1250: left_rows, left_growable_row, Laurent@1250: 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 Laurent@1250: 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) Laurent@1250: Laurent@1250: # Create a sizer for dividing parameters in two columns Laurent@1250: column_sizer = wx.BoxSizer(wx.HORIZONTAL) Laurent@1250: self.MainSizer.AddSizer(column_sizer, border=20, Laurent@1250: flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) Laurent@1250: Laurent@1250: # Create a sizer for left column Laurent@1250: 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) Laurent@1250: column_sizer.AddSizer(self.LeftGridSizer, 1, border=5, Laurent@1250: flag=wx.GROW|wx.RIGHT) Laurent@1250: Laurent@1250: # Create a sizer for right column Laurent@1250: 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) Laurent@1250: column_sizer.AddSizer(self.RightGridSizer, 1, border=5, Laurent@1250: flag=wx.GROW|wx.LEFT) Laurent@1250: Laurent@1250: self.SetSizer(self.MainSizer) Laurent@1250: 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 Laurent@1236: 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 Laurent@1249: Laurent@1249: return self.Element.GetMinSize() Laurent@1249: 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) Laurent@1236: 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} Laurent@1246: Laurent@1246: # 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) Laurent@1246: 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") Laurent@1246: 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() Laurent@1242: 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!") Laurent@1242: 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!") Laurent@1242: 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!") Laurent@1242: Laurent@1242: # Test that graphic element name isn't already used in POU by a variable Laurent@1242: # or another graphic element Laurent@1244: elif ((self.DefaultElementName is None or Laurent@1244: self.DefaultElementName.upper() != uppercase_element_name) and Laurent@1244: uppercase_element_name in self.Controller.\ Laurent@1244: GetEditedElementVariables(self.TagName)): Laurent@1242: message_format = _("\"%s\" element for this pou already exists!") Laurent@1242: 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 Laurent@1236: Laurent@1242: # Test succeed Laurent@1236: return True Laurent@1236: 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: """ Laurent@1236: dialog = wx.MessageDialog(self, message, Laurent@1236: _("Error"), Laurent@1236: wx.OK|wx.ICON_ERROR) Laurent@1236: dialog.ShowModal() Laurent@1236: dialog.Destroy() Laurent@1236: 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) Laurent@1236: 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() Laurent@1236: Laurent@1242: # Return immediately if no graphic element defined Laurent@1244: if self.Element is None: Laurent@1242: return Laurent@1242: 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) Laurent@1242: 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() Laurent@1252: Laurent@1242: # Get Preview panel size Laurent@1242: client_size = self.Preview.GetClientSize() Laurent@1242: 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 Laurent@1252: scale = (max(float(bbox.width) / client_size.width, Laurent@1253: float(bbox.height) / client_size.height) * 1.1 Laurent@1252: if bbox.width * 1.1 > client_size.width or Laurent@1252: bbox.height * 1.1 > client_size.height Laurent@1242: else 1.0) Laurent@1242: dc.SetUserScale(1.0 / scale, 1.0 / scale) Laurent@1242: 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) Laurent@1242: Laurent@1242: # Draw graphic element Laurent@1244: self.Element.Draw(dc) Laurent@1236: 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() Laurent@1236: