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: