# HG changeset patch # User Laurent Bessard # Date 1370900888 -7200 # Node ID ec2c415fc65e0d409b1f7de9fa387a69076afb73 # Parent 368f8516706c28731da661c038eeb96879d21a0f Rewrite FBDBlockDialog and BlockPreviewDialog diff -r 368f8516706c -r ec2c415fc65e dialogs/BlockPreviewDialog.py --- a/dialogs/BlockPreviewDialog.py Mon Jun 10 21:42:30 2013 +0200 +++ b/dialogs/BlockPreviewDialog.py Mon Jun 10 23:48:08 2013 +0200 @@ -39,60 +39,111 @@ class BlockPreviewDialog(wx.Dialog): def __init__(self, parent, controller, tagname, size, title): + """ + Constructor + @param parent: Parent wx.Window of dialog for modal + @param controller: Reference to project controller + @param tagname: Tagname of project POU edited + @param size: wx.Size object containing size of dialog + @param title: Title of dialog frame + """ wx.Dialog.__init__(self, parent, size=size, title=title) + # Save reference to self.Controller = controller self.TagName = tagname + # Label for preview self.PreviewLabel = wx.StaticText(self, label=_('Preview:')) + # Create Preview panel self.Preview = wx.Panel(self, style=wx.SIMPLE_BORDER) self.Preview.SetBackgroundColour(wx.WHITE) + + # Add function to preview panel so that it answers to graphic elements + # like Viewer setattr(self.Preview, "GetDrawingMode", lambda:FREEDRAWING_MODE) setattr(self.Preview, "GetScaling", lambda:None) setattr(self.Preview, "GetBlockType", controller.GetBlockType) setattr(self.Preview, "IsOfType", controller.IsOfType) + + # Bind paint event on Preview panel self.Preview.Bind(wx.EVT_PAINT, self.OnPaint) + # Add default dialog buttons sizer self.ButtonSizer = self.CreateButtonSizer(wx.OK|wx.CANCEL|wx.CENTRE) self.Bind(wx.EVT_BUTTON, self.OnOK, self.ButtonSizer.GetAffirmativeButton()) - self.Block = None - self.DefaultBlockName = None - self.MinBlockSize = None - + self.Block = None # Graphic element to display in preview + self.MinBlockSize = None # Graphic element minimal size + self.DefaultBlockName = None # Graphic element name when opening dialog + def __del__(self): + """ + Destructor + """ + # Remove reference to project controller self.Controller = None def SetMinBlockSize(self, size): + """ + Define minimal graphic element size + @param size: wx.Size object containing minimal size + """ self.MinBlockSize = size def SetPreviewFont(self, font): + """ + Set font of Preview panel + @param font: wx.Font object containing font style + """ self.Preview.SetFont(font) def TestBlockName(self, block_name): - format = None + """ + Text displayed graphic element name + @param block_name: Graphic element name + """ + # Variable containing error message format + message_format = None + # Get graphic element name in upper case uppercase_block_name = block_name.upper() + + # Test if graphic element name is a valid identifier if not TestIdentifier(block_name): - format = _("\"%s\" is not a valid identifier!") + message_format = _("\"%s\" is not a valid identifier!") + + # Test that graphic element name isn't a keyword elif uppercase_block_name in IEC_KEYWORDS: - format = _("\"%s\" is a keyword. It can't be used!") + message_format = _("\"%s\" is a keyword. It can't be used!") + + # Test that graphic element name isn't a POU name elif uppercase_block_name in self.Controller.GetProjectPouNames(): - format = _("\"%s\" pou already exists!") + message_format = _("\"%s\" pou already exists!") + + # Test that graphic element name isn't already used in POU by a variable + # or another graphic element elif ((self.DefaultBlockName is None or self.DefaultBlockName.upper() != uppercase_block_name) and uppercase_block_name in self.Controller.GetEditedElementVariables( self.TagName)): - format = _("\"%s\" element for this pou already exists!") - - if format is not None: - self.ShowErrorMessage(format % block_name) + message_format = _("\"%s\" element for this pou already exists!") + + # If an error have been identify, show error message dialog + if message_format is not None: + self.ShowErrorMessage(message_format % block_name) + # Test failed return False + # Test succeed return True def ShowErrorMessage(self, message): + """ + Show an error message dialog over this dialog + @param message: Error message to display + """ dialog = wx.MessageDialog(self, message, _("Error"), wx.OK|wx.ICON_ERROR) @@ -100,34 +151,61 @@ dialog.Destroy() def OnOK(self, event): + """ + Called when dialog OK button is pressed + Need to be overridden by inherited classes to check that dialog values + are valid + @param event: wx.Event from OK button + """ + # Close dialog self.EndModal(wx.ID_OK) def RefreshPreview(self): + """ + Refresh preview panel of graphic element + May be overridden by inherited classes + """ + # Init preview panel paint device context dc = wx.ClientDC(self.Preview) dc.SetFont(self.Preview.GetFont()) dc.Clear() - if self.Block is not None: - min_width, min_height = self.Block.GetMinSize() - width = max(self.MinBlockSize[0], min_width) - height = max(self.MinBlockSize[1], min_height) - self.Block.SetSize(width, height) - client_size = self.Preview.GetClientSize() - if (width * 1.2 > client_size.width or - height * 1.2 > client_size.height): - scale = max(float(width) / client_size.width, - float(height) / client_size.height) * 1.2 - x = int(client_size.width * scale - width) / 2 - y = int(client_size.height * scale - height) / 2 - else: - x = (client_size.width - width) / 2 - y = (client_size.height - height) / 2 - scale = 1.0 - dc.SetUserScale(1.0 / scale, 1.0 / scale) - self.Block.SetPosition(x, y) - self.Block.Draw(dc) + # Return immediately if no graphic element defined + if self.Block is None: + return + + # Calculate block size according to graphic element min size due to its + # parameters and graphic element min size defined + min_width, min_height = self.Block.GetMinSize() + width = max(self.MinBlockSize[0], min_width) + height = max(self.MinBlockSize[1], min_height) + self.Block.SetSize(width, height) + + # Get Preview panel size + client_size = self.Preview.GetClientSize() + + # If graphic element is too big to be displayed in preview panel, + # calculate preview panel scale so that graphic element fit inside + scale = (max(float(width) / client_size.width, + float(height) / client_size.height) * 1.2 + if width * 1.2 > client_size.width or + height * 1.2 > client_size.height + else 1.0) + dc.SetUserScale(1.0 / scale, 1.0 / scale) + + # Center graphic element in preview panel + x = int(client_size.width * scale - width) / 2 + y = int(client_size.height * scale - height) / 2 + self.Block.SetPosition(x, y) + + # Draw graphic element + self.Block.Draw(dc) def OnPaint(self, event): + """ + Called when Preview panel need to be redraw + @param event: wx.PaintEvent + """ self.RefreshPreview() event.Skip() \ No newline at end of file diff -r 368f8516706c -r ec2c415fc65e dialogs/FBDBlockDialog.py --- a/dialogs/FBDBlockDialog.py Mon Jun 10 21:42:30 2013 +0200 +++ b/dialogs/FBDBlockDialog.py Mon Jun 10 23:48:08 2013 +0200 @@ -31,77 +31,108 @@ from BlockPreviewDialog import BlockPreviewDialog #------------------------------------------------------------------------------- -# Create New Block Dialog +# Set Block Parameters Dialog #------------------------------------------------------------------------------- +""" +Class that implements a dialog for defining parameters of a FBD block graphic +element +""" + class FBDBlockDialog(BlockPreviewDialog): def __init__(self, parent, controller, tagname): + """ + Constructor + @param parent: Parent wx.Window of dialog for modal + @param controller: Reference to project controller + @param tagname: Tagname of project POU edited + """ BlockPreviewDialog.__init__(self, parent, controller, tagname, size=wx.Size(600, 450), title=_('Block Properties')) + # Create dialog main sizer main_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=4, vgap=10) main_sizer.AddGrowableCol(0) main_sizer.AddGrowableRow(0) + # Create a sizer for dividing FBD block parameters in two columns column_sizer = wx.BoxSizer(wx.HORIZONTAL) main_sizer.AddSizer(column_sizer, border=20, flag=wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT) + # Create static box around library panel type_staticbox = wx.StaticBox(self, label=_('Type:')) left_staticboxsizer = wx.StaticBoxSizer(type_staticbox, wx.VERTICAL) column_sizer.AddSizer(left_staticboxsizer, 1, border=5, flag=wx.GROW|wx.RIGHT) + # Create Library panel and add it to static box self.LibraryPanel = LibraryPanel(self) + # Set function to call when selection in Library panel changed setattr(self.LibraryPanel, "_OnTreeItemSelected", self.OnLibraryTreeItemSelected) left_staticboxsizer.AddWindow(self.LibraryPanel, 1, border=5, flag=wx.GROW|wx.TOP) + # Create sizer for other block parameters and preview panle right_gridsizer = wx.FlexGridSizer(cols=1, hgap=0, rows=3, vgap=5) right_gridsizer.AddGrowableCol(0) right_gridsizer.AddGrowableRow(2) column_sizer.AddSizer(right_gridsizer, 1, border=5, flag=wx.GROW|wx.LEFT) + # Create sizer for other block parameters top_right_gridsizer = wx.FlexGridSizer(cols=2, hgap=0, rows=4, vgap=5) top_right_gridsizer.AddGrowableCol(1) right_gridsizer.AddSizer(top_right_gridsizer, flag=wx.GROW) + # Create label for block name name_label = wx.StaticText(self, label=_('Name:')) top_right_gridsizer.AddWindow(name_label, flag=wx.ALIGN_CENTER_VERTICAL) + # Create text control for defining block name self.BlockName = wx.TextCtrl(self) self.Bind(wx.EVT_TEXT, self.OnNameChanged, self.BlockName) top_right_gridsizer.AddWindow(self.BlockName, flag=wx.GROW) + # Create label for extended block input number inputs_label = wx.StaticText(self, label=_('Inputs:')) top_right_gridsizer.AddWindow(inputs_label, flag=wx.ALIGN_CENTER_VERTICAL) + # Create spin control for defining extended block input number self.Inputs = wx.SpinCtrl(self, min=2, max=20, style=wx.SP_ARROW_KEYS) self.Bind(wx.EVT_SPINCTRL, self.OnInputsChanged, self.Inputs) top_right_gridsizer.AddWindow(self.Inputs, flag=wx.GROW) - execution_order_label = wx.StaticText(self, label=_('Execution Order:')) + # Create label for block execution order + execution_order_label = wx.StaticText(self, + label=_('Execution Order:')) top_right_gridsizer.AddWindow(execution_order_label, flag=wx.ALIGN_CENTER_VERTICAL) + # Create spin control for defining block execution order self.ExecutionOrder = wx.SpinCtrl(self, min=0, style=wx.SP_ARROW_KEYS) - self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, self.ExecutionOrder) + self.Bind(wx.EVT_SPINCTRL, self.OnExecutionOrderChanged, + self.ExecutionOrder) top_right_gridsizer.AddWindow(self.ExecutionOrder, flag=wx.GROW) - execution_control_label = wx.StaticText(self, label=_('Execution Control:')) + # Create label for block execution control + execution_control_label = wx.StaticText(self, + label=_('Execution Control:')) top_right_gridsizer.AddWindow(execution_control_label, flag=wx.ALIGN_CENTER_VERTICAL) + # Create check box to enable block execution control self.ExecutionControl = wx.CheckBox(self) - self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, self.ExecutionControl) + self.Bind(wx.EVT_CHECKBOX, self.OnExecutionOrderChanged, + self.ExecutionControl) top_right_gridsizer.AddWindow(self.ExecutionControl, flag=wx.GROW) + # Add preview panel and associated label to sizers right_gridsizer.AddWindow(self.PreviewLabel, flag=wx.GROW) right_gridsizer.AddWindow(self.Preview, flag=wx.GROW) @@ -110,65 +141,124 @@ self.SetSizer(main_sizer) + self.ParamsControl = { + "extension": self.Inputs, + "executionOrder": self.ExecutionOrder, + "executionControl": self.ExecutionControl + } + + # Init controls value and sensibility self.BlockName.SetValue("") self.BlockName.Enable(False) self.Inputs.Enable(False) + + # Variable containing last name typed self.CurrentBlockName = None + # Refresh Library panel values self.LibraryPanel.SetBlockList(controller.GetBlockTypes(tagname)) self.LibraryPanel.SetFocus() def OnOK(self, event): + """ + Called when dialog OK button is pressed + Test if parameters defined are valid + @param event: wx.Event from OK button + """ message = None + + # Get block type selected selected = self.LibraryPanel.GetSelectedBlock() + + # Get block type name and if block is a function block block_name = self.BlockName.GetValue() name_enabled = self.BlockName.IsEnabled() + + # Test that a type has been selected for block if selected is None: message = _("Form isn't complete. Valid block type must be selected!") + + # Test, if block is a function block, that a name have been defined elif name_enabled and block_name == "": message = _("Form isn't complete. Name must be filled!") + + # Show error message if an error is detected if message is not None: self.ShowMessage(message) + + # Test block name validity if necessary elif not name_enabled or self.TestBlockName(block_name): BlockPreviewDialog.OnOK(self, event) - + def SetValues(self, values): + """ + Set default block parameters + @param values: Block parameters values + """ + # Extract block type defined in parameters blocktype = values.get("type", None) - default_name_model = re.compile("%s[0-9]+" % blocktype) + + # Define regular expression for determine if block name is block + # default name + default_name_model = re.compile( + "%s[0-9]+" % blocktype if blocktype is not None else ".*") + + # Select block type in library panel if blocktype is not None: self.LibraryPanel.SelectTreeItem(blocktype, values.get("inputs", None)) + + # For each parameters defined, set corresponding control value for name, value in values.items(): if name == "name": + # Parameter is block name if value != "": + # Set default block name for testing self.DefaultBlockName = value + + # Test if block name is type default block name and save + # block name if not (name have been typed by user) if default_name_model.match(value) is None: self.CurrentBlockName = value + self.BlockName.ChangeValue(value) - elif name == "extension": - self.Inputs.SetValue(value) - elif name == "executionOrder": - self.ExecutionOrder.SetValue(value) - elif name == "executionControl": - self.ExecutionControl.SetValue(value) + + else: + control = self.ParamsControl.get(name, None) + if control is not None: + control.SetValue(value) + + # Refresh preview panel self.RefreshPreview() def GetValues(self): + """ + Return block parameters defined in dialog + @return: {parameter_name: parameter_value,...} + """ values = self.LibraryPanel.GetSelectedBlock() if self.BlockName.IsEnabled() and self.BlockName.GetValue() != "": values["name"] = self.BlockName.GetValue() values["width"], values["height"] = self.Block.GetSize() - values["extension"] = self.Inputs.GetValue() - values["executionOrder"] = self.ExecutionOrder.GetValue() - values["executionControl"] = self.ExecutionControl.GetValue() + values.update({ + name: control.GetValue() + for name, control in self.ParamsControl.iteritems()}) return values def OnLibraryTreeItemSelected(self, event): + """ + Called when block type selected in library panel + @param event: wx.TreeEvent + """ + # Get type selected in library panel values = self.LibraryPanel.GetSelectedBlock() + + # Get block type informations blocktype = (self.Controller.GetBlockType(values["type"], values["inputs"]) if values is not None else None) + # Set input number spin control according to block type informations if blocktype is not None: self.Inputs.SetValue(len(blocktype["inputs"])) self.Inputs.Enable(blocktype["extensible"]) @@ -176,6 +266,8 @@ self.Inputs.SetValue(2) self.Inputs.Enable(False) + # Update block name with default value if block type is a function and + # current block name wasn't typed by user if blocktype is not None and blocktype["type"] != "function": self.BlockName.Enable(True) self.BlockName.ChangeValue( @@ -187,40 +279,70 @@ self.BlockName.Enable(False) self.BlockName.ChangeValue("") + # Refresh preview panel self.RefreshPreview() def OnNameChanged(self, event): + """ + Called when block name value changed + @param event: wx.TextEvent + """ if self.BlockName.IsEnabled(): + # Save block name typed by user self.CurrentBlockName = self.BlockName.GetValue() self.RefreshPreview() event.Skip() def OnInputsChanged(self, event): + """ + Called when block inputs number changed + @param event: wx.SpinEvent + """ if self.Inputs.IsEnabled(): self.RefreshPreview() event.Skip() def OnExecutionOrderChanged(self, event): + """ + Called when block execution order value changed + @param event: wx.SpinEvent + """ self.RefreshPreview() event.Skip() def OnExecutionControlChanged(self, event): + """ + Called when block execution control value changed + @param event: wx.SpinEvent + """ self.RefreshPreview() event.Skip() def RefreshPreview(self): + """ + Refresh preview panel of graphic element + Override BlockPreviewDialog function + """ + # Get type selected in library panel values = self.LibraryPanel.GetSelectedBlock() + + # If a block type is selected in library panel if values is not None: - if self.BlockName.IsEnabled(): - blockname = self.BlockName.GetValue() - else: - blockname = "" + blockname = (self.BlockName.GetValue() + if self.BlockName.IsEnabled() + else "") + + # Set graphic element displayed, creating a FBD block element self.Block = FBD_Block(self.Preview, values["type"], blockname, extension = self.Inputs.GetValue(), inputs = values["inputs"], executionControl = self.ExecutionControl.GetValue(), executionOrder = self.ExecutionOrder.GetValue()) + + # Reset graphic element displayed else: self.Block = None + + # Call BlockPreviewDialog function BlockPreviewDialog.RefreshPreview(self)