Adding support for drag'n dropping variable from global defined in configurations and resources to POU variable panel or body editor for declaring external variables
Adding support for drag'n dropping located variables from topology panel to configurations and resources variable panel for declaring global located variables
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard.
#
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#General Public License for more details.
#
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import wx
from GraphicCommons import *
from plcopen.structures import *
#-------------------------------------------------------------------------------
# Function Block Diagram Block
#-------------------------------------------------------------------------------
"""
Class that implements the graphic representation of a function block
"""
class FBD_Block(Graphic_Element):
# Create a new block
def __init__(self, parent, type, name, id = None, extension = 0, inputs = None, connectors = {}, executionControl = False, executionOrder = 0):
Graphic_Element.__init__(self, parent)
self.Type = None
self.Extension = None
self.ExecutionControl = False
self.Id = id
self.SetName(name)
self.SetExecutionOrder(executionOrder)
self.Inputs = []
self.Outputs = []
self.Colour = wx.BLACK
self.Pen = MiterPen(wx.BLACK)
self.SetType(type, extension, inputs, connectors, executionControl)
self.Highlights = {}
# Make a clone of this FBD_Block
def Clone(self, parent, id = None, name = "", pos = None):
if self.Name != "" and name == "":
name = self.Name
block = FBD_Block(parent, self.Type, name, id, self.Extension)
block.SetSize(self.Size[0], self.Size[1])
if pos is not None:
block.SetPosition(pos.x, pos.y)
else:
block.SetPosition(self.Pos.x, self.Pos.y)
block.Inputs = [input.Clone(block) for input in self.Inputs]
block.Outputs = [output.Clone(block) for output in self.Outputs]
return block
def GetConnectorTranslation(self, element):
return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
def Flush(self):
for input in self.Inputs:
input.Flush()
self.Inputs = []
for output in self.Outputs:
output.Flush()
self.Outputs = []
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = Graphic_Element.GetRedrawRect(self, movex, movey)
if movex != 0 or movey != 0:
for input in self.Inputs:
if input.IsConnected():
rect = rect.Union(input.GetConnectedRedrawRect(movex, movey))
for output in self.Outputs:
if output.IsConnected():
rect = rect.Union(output.GetConnectedRedrawRect(movex, movey))
return rect
# Delete this block by calling the appropriate method
def Delete(self):
self.Parent.DeleteBlock(self)
# Unconnect all inputs and outputs
def Clean(self):
for input in self.Inputs:
input.UnConnect(delete = True)
for output in self.Outputs:
output.UnConnect(delete = True)
# Refresh the size of text for name
def RefreshNameSize(self):
self.NameSize = self.Parent.GetTextExtent(self.Name)
# Refresh the size of text for execution order
def RefreshExecutionOrderSize(self):
self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
# Refresh the block bounding box
def RefreshBoundingBox(self):
# Calculate the size of the name outside the block
text_width, text_height = self.NameSize
# Calculate the bounding box size
bbx_x = self.Pos.x - max(min(1, len(self.Inputs)) * CONNECTOR_SIZE, (text_width - self.Size[0]) / 2)
bbx_width = max(self.Size[0] + 1 + (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE, text_width)
if self.Name != "":
bbx_y = self.Pos.y - (text_height + 2)
bbx_height = self.Size[1] + (text_height + 2)
else:
bbx_y = self.Pos.y
bbx_height = self.Size[1]
if self.ExecutionOrder != 0:
bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
self.BoundingBox = wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
# Refresh the positions of the block connectors
def RefreshConnectors(self):
scaling = self.Parent.GetScaling()
# Calculate the size for the connector lines
lines = max(len(self.Inputs), len(self.Outputs))
if lines > 0:
linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE)
# Update inputs and outputs positions
position = BLOCK_LINE_SIZE + linesize / 2
for i in xrange(lines):
if scaling is not None:
ypos = round(float(self.Pos.y + position) / float(scaling[1])) * scaling[1] - self.Pos.y
else:
ypos = position
if i < len(self.Inputs):
self.Inputs[i].SetPosition(wx.Point(0, ypos))
if i < len(self.Outputs):
self.Outputs[i].SetPosition(wx.Point(self.Size[0], ypos))
position += linesize
self.RefreshConnected()
# Refresh the positions of wires connected to inputs and outputs
def RefreshConnected(self, exclude = []):
for input in self.Inputs:
input.MoveConnected(exclude)
for output in self.Outputs:
output.MoveConnected(exclude)
# Returns the block connector that starts with the point given if it exists
def GetConnector(self, position, name = None):
# if a name is given
if name:
# Test each input and output connector
for input in self.Inputs:
if name == input.GetName():
return input
for output in self.Outputs:
if name == output.GetName():
return output
return self.FindNearestConnector(position, self.Inputs + self.Outputs)
def GetInputTypes(self):
return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
def SetOutputValues(self, values):
for output in self.Outputs:
output.SetValue(values.get(ouput.getName(), None))
def GetConnectionResultType(self, connector, connectortype):
resulttype = connectortype
undefined = True
for input in self.Inputs:
name = input.GetName()
undefined = undefined and input.GetType(True) == "ANY"
if input != connector and (name.startswith("IN") or name in ["MN", "MX"]):
inputtype = input.GetConnectedType()
if resulttype is None or inputtype is not None and self.IsOfType(inputtype, resulttype):
resulttype = inputtype
for output in self.Outputs:
name = output.GetName()
undefined = undefined and output.GetType(True) == "ANY"
if output != connector and name == "OUT" and not self.IsEndType(output.GetType()):
outputtype = output.GetConnectedType()
if resulttype is None or outputtype is not None and self.IsOfType(outputtype, resulttype):
resulttype = outputtype
if not undefined:
return resulttype
return "ANY"
# Returns all the block connectors
def GetConnectors(self):
return {"inputs" : self.Inputs, "outputs" : self.Outputs}
# Test if point given is on one of the block connectors
def TestConnector(self, pt, direction = None, exclude = True):
# Test each input connector
for input in self.Inputs:
if input.TestPoint(pt, direction, exclude):
return input
# Test each output connector
for output in self.Outputs:
if output.TestPoint(pt, direction, exclude):
return output
return None
# Changes the block type
def SetType(self, type, extension, inputs = None, connectors = {}, executionControl = False):
if type != self.Type or self.Extension != extension or executionControl != self.ExecutionControl:
if type != self.Type:
self.Type = type
self.TypeSize = self.Parent.GetTextExtent(self.Type)
self.Extension = extension
self.ExecutionControl = executionControl
# Find the block definition from type given and create the corresponding
# inputs and outputs
blocktype = self.Parent.GetBlockType(type, inputs)
if blocktype:
self.Colour = wx.BLACK
inputs = [input for input in blocktype["inputs"]]
outputs = [output for output in blocktype["outputs"]]
if blocktype["extensible"]:
start = int(inputs[-1][0].replace("IN", ""))
for i in xrange(self.Extension - len(blocktype["inputs"])):
start += 1
inputs.append(("IN%d"%start, inputs[-1][1], inputs[-1][2]))
else:
self.Colour = wx.RED
inputs = connectors.get("inputs", [])
outputs = connectors.get("outputs", [])
if self.ExecutionControl:
inputs.insert(0, ("EN","BOOL","none"))
outputs.insert(0, ("ENO","BOOL","none"))
self.Pen = MiterPen(self.Colour)
self.Clean()
# Extract the inputs properties and create the corresponding connector
self.Inputs = []
for input_name, input_type, input_modifier in inputs:
connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
if input_modifier == "negated":
connector.SetNegated(True)
elif input_modifier != "none":
connector.SetEdge(input_modifier)
self.Inputs.append(connector)
# Extract the outputs properties and create the corresponding connector
self.Outputs = []
for output_name, output_type, output_modifier in outputs:
connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
if output_modifier == "negated":
connector.SetNegated(True)
elif output_modifier != "none":
connector.SetEdge(output_modifier)
self.Outputs.append(connector)
self.RefreshMinSize()
self.RefreshConnectors()
self.RefreshBoundingBox()
# Returns the block type
def GetType(self):
return self.Type
# Changes the block name
def SetName(self, name):
self.Name = name
self.RefreshNameSize()
# Returs the block name
def GetName(self):
return self.Name
# Changes the extension name
def SetExtension(self, extension):
self.Extension = extension
# Returs the extension name
def GetExtension(self):
return self.Extension
# Changes the execution order
def SetExecutionOrder(self, executionOrder):
self.ExecutionOrder = executionOrder
self.RefreshExecutionOrderSize()
# Returs the execution order
def GetExecutionOrder(self):
return self.ExecutionOrder
# Returs the execution order
def GetExecutionControl(self):
return self.ExecutionControl
# Refresh the block minimum size
def RefreshMinSize(self):
# Calculate the inputs maximum width
max_input = 0
for input in self.Inputs:
w, h = input.GetNameSize()
max_input = max(max_input, w)
# Calculate the outputs maximum width
max_output = 0
for output in self.Outputs:
w, h = output.GetNameSize()
max_output = max(max_output, w)
width = max(self.TypeSize[0] + 10, max_input + max_output + 15)
height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE
self.MinSize = width, height
# Returns the block minimum size
def GetMinSize(self):
return self.MinSize
# Changes the negated property of the connector handled
def SetConnectorNegated(self, negated):
handle_type, handle = self.Handle
if handle_type == HANDLE_CONNECTOR:
handle.SetNegated(negated)
self.RefreshModel(False)
# Changes the edge property of the connector handled
def SetConnectorEdge(self, edge):
handle_type, handle = self.Handle
if handle_type == HANDLE_CONNECTOR:
handle.SetEdge(edge)
self.RefreshModel(False)
## # Method called when a Motion event have been generated
## def OnMotion(self, event, dc, scaling):
## if not event.Dragging():
## pos = event.GetLogicalPosition(dc)
## for input in self.Inputs:
## rect = input.GetRedrawRect()
## if rect.InsideXY(pos.x, pos.y):
## print "Find input"
## tip = wx.TipWindow(self.Parent, "Test")
## tip.SetBoundingRect(rect)
## return Graphic_Element.OnMotion(self, event, dc, scaling)
# Method called when a LeftDClick event have been generated
def OnLeftDClick(self, event, dc, scaling):
# Edit the block properties
self.Parent.EditBlockContent(self)
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
# Popup the menu with special items for a block and a connector if one is handled
connector = self.TestConnector(pos, exclude=False)
if connector:
self.Handle = (HANDLE_CONNECTOR, connector)
self.Parent.PopupBlockMenu(connector)
else:
self.Parent.PopupBlockMenu()
# Refreshes the block model
def RefreshModel(self, move=True):
self.Parent.RefreshBlockModel(self)
# If block has moved, refresh the model of wires connected to outputs
if move:
for output in self.Outputs:
output.RefreshWires()
# Adds an highlight to the block
def AddHighlight(self, infos, start, end ,highlight_type):
if infos[0] in ["type", "name"] and start[0] == 0 and end[0] == 0:
highlights = self.Highlights.setdefault(infos[0], [])
AddHighlight(highlights, (start, end, highlight_type))
elif infos[0] == "input" and infos[1] < len(self.Inputs):
self.Inputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
elif infos[0] == "output" and infos[1] < len(self.Outputs):
self.Outputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
# Removes an highlight from the block
def RemoveHighlight(self, infos, start, end, highlight_type):
if infos[0] in ["type", "name"]:
highlights = self.Highlights.get(infos[0], [])
if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
self.Highlights.pop(infos[0])
elif infos[0] == "input" and infos[1] < len(self.Inputs):
self.Inputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
elif infos[0] == "output" and infos[1] < len(self.Outputs):
self.Outputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
# Removes all the highlights of one particular type from the block
def ClearHighlight(self, highlight_type=None):
if highlight_type is None:
self.Highlights = {}
else:
highlight_items = self.Highlights.items()
for name, highlights in highlight_items:
highlights = ClearHighlights(highlights, highlight_type)
if len(highlights) == 0:
self.Highlights.pop(name)
for input in self.Inputs:
input.ClearHighlights(highlight_type)
for output in self.Outputs:
output.ClearHighlights(highlight_type)
# Draws block
def Draw(self, dc):
Graphic_Element.Draw(self, dc)
dc.SetPen(self.Pen)
dc.SetBrush(wx.WHITE_BRUSH)
dc.SetTextForeground(self.Colour)
if getattr(dc, "printing", False):
name_size = dc.GetTextExtent(self.Name)
type_size = dc.GetTextExtent(self.Type)
executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
else:
name_size = self.NameSize
type_size = self.TypeSize
executionorder_size = self.ExecutionOrderSize
# Draw a rectangle with the block size
dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
# Draw block name and block type
name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
self.Pos.y - (name_size[1] + 2))
type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2,
self.Pos.y + 5)
dc.DrawText(self.Name, name_pos[0], name_pos[1])
dc.DrawText(self.Type, type_pos[0], type_pos[1])
# Draw inputs and outputs connectors
for input in self.Inputs:
input.Draw(dc)
for output in self.Outputs:
output.Draw(dc)
if self.ExecutionOrder != 0:
# Draw block execution order
dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
self.Pos.y + self.Size[1] + 2)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, self.Name, self.Highlights.get("name", []), name_pos[0], name_pos[1])
DrawHighlightedText(dc, self.Type, self.Highlights.get("type", []), type_pos[0], type_pos[1])
#-------------------------------------------------------------------------------
# Function Block Diagram Variable
#-------------------------------------------------------------------------------
"""
Class that implements the graphic representation of a variable
"""
class FBD_Variable(Graphic_Element):
# Create a new variable
def __init__(self, parent, type, name, value_type, id = None, executionOrder = 0):
Graphic_Element.__init__(self, parent)
self.Type = None
self.ValueType = None
self.Id = id
self.SetName(name)
self.SetExecutionOrder(executionOrder)
self.Input = None
self.Output = None
self.SetType(type, value_type)
self.Highlights = []
# Make a clone of this FBD_Variable
def Clone(self, parent, id = None, pos = None):
variable = FBD_Variable(parent, self.Type, self.Name, self.ValueType, id)
variable.SetSize(self.Size[0], self.Size[1])
if pos is not None:
variable.SetPosition(pos.x, pos.y)
else:
variable.SetPosition(self.Pos.x, self.Pos.y)
if self.Input:
variable.Input = self.Input.Clone(variable)
if self.Output:
variable.Output = self.Output.Clone(variable)
return variable
def GetConnectorTranslation(self, element):
connectors = {}
if self.Input is not None:
connectors[self.Input] = element.Input
if self.Output is not None:
connectors[self.Output] = element.Output
return connectors
def Flush(self):
if self.Input is not None:
self.Input.Flush()
self.Input = None
if self.Output is not None:
self.Output.Flush()
self.Output = None
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = Graphic_Element.GetRedrawRect(self, movex, movey)
if movex != 0 or movey != 0:
if self.Input and self.Input.IsConnected():
rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
if self.Output and self.Output.IsConnected():
rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
return rect
# Unconnect connector
def Clean(self):
if self.Input:
self.Input.UnConnect(delete = True)
if self.Output:
self.Output.UnConnect(delete = True)
# Delete this variable by calling the appropriate method
def Delete(self):
self.Parent.DeleteVariable(self)
# Refresh the size of text for name
def RefreshNameSize(self):
self.NameSize = self.Parent.GetTextExtent(self.Name)
# Refresh the size of text for execution order
def RefreshExecutionOrderSize(self):
self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
# Refresh the variable bounding box
def RefreshBoundingBox(self):
if self.Type in (OUTPUT, INOUT):
bbx_x = self.Pos.x - CONNECTOR_SIZE
else:
bbx_x = self.Pos.x
if self.Type == INOUT:
bbx_width = self.Size[0] + 2 * CONNECTOR_SIZE
else:
bbx_width = self.Size[0] + CONNECTOR_SIZE
bbx_x = min(bbx_x, self.Pos.x + (self.Size[0] - self.NameSize[0]) / 2)
bbx_width = max(bbx_width, self.NameSize[0])
bbx_height = self.Size[1]
if self.ExecutionOrder != 0:
bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width + 1, bbx_height + 1)
# Refresh the position of the variable connector
def RefreshConnectors(self):
scaling = self.Parent.GetScaling()
if scaling is not None:
position = round(float(self.Pos.y + self.Size[1] / 2) / float(scaling[1])) * scaling[1] - self.Pos.y
else:
position = self.Size[1] / 2
if self.Input:
self.Input.SetPosition(wx.Point(0, position))
if self.Output:
self.Output.SetPosition(wx.Point(self.Size[0], position))
self.RefreshConnected()
# Refresh the position of wires connected to connector
def RefreshConnected(self, exclude = []):
if self.Input:
self.Input.MoveConnected(exclude)
if self.Output:
self.Output.MoveConnected(exclude)
# Test if point given is on the variable connector
def TestConnector(self, pt, direction = None, exclude=True):
if self.Input and self.Input.TestPoint(pt, direction, exclude):
return self.Input
if self.Output and self.Output.TestPoint(pt, direction, exclude):
return self.Output
return None
# Returns the block connector that starts with the point given if it exists
def GetConnector(self, position, name = None):
# if a name is given
if name:
# Test input and output connector if they exists
if self.Input and name == self.Input.GetName():
return self.Input
if self.Output and name == self.Output.GetName():
return self.Output
connectors = []
# Test input connector if it exists
if self.Input:
connectors.append(self.Input)
# Test output connector if it exists
if self.Output:
connectors.append(self.Output)
return self.FindNearestConnector(position, connectors)
# Returns all the block connectors
def GetConnectors(self):
connectors = {"inputs": [], "outputs": []}
if self.Input:
connectors["inputs"].append(self.Input)
if self.Output:
connectors["outputs"].append(self.Output)
return connectors
# Changes the negated property of the variable connector if handled
def SetConnectorNegated(self, negated):
handle_type, handle = self.Handle
if handle_type == HANDLE_CONNECTOR:
handle.SetNegated(negated)
self.RefreshModel(False)
# Changes the variable type
def SetType(self, type, value_type):
if type != self.Type:
self.Type = type
self.Clean()
self.Input = None
self.Output = None
# Create an input or output connector according to variable type
if self.Type != INPUT:
self.Input = Connector(self, "", value_type, wx.Point(0, 0), WEST, onlyone = True)
if self.Type != OUTPUT:
self.Output = Connector(self, "", value_type, wx.Point(0, 0), EAST)
self.RefreshConnectors()
elif value_type != self.ValueType:
if self.Input:
self.Input.SetType(value_type)
if self.Output:
self.Output.SetType(value_type)
self.RefreshConnectors()
# Returns the variable type
def GetType(self):
return self.Type
# Changes the variable name
def SetName(self, name):
self.Name = name
self.RefreshNameSize()
# Returns the variable name
def GetName(self):
return self.Name
# Changes the execution order
def SetExecutionOrder(self, executionOrder):
self.ExecutionOrder = executionOrder
self.RefreshExecutionOrderSize()
# Returs the execution order
def GetExecutionOrder(self):
return self.ExecutionOrder
# Returns the variable minimum size
def GetMinSize(self):
return self.NameSize[0] + 10, self.NameSize[1] + 10
# Method called when a LeftDClick event have been generated
def OnLeftDClick(self, event, dc, scaling):
# Edit the variable properties
self.Parent.EditVariableContent(self)
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
self.Parent.PopupDefaultMenu()
# Refreshes the variable model
def RefreshModel(self, move=True):
self.Parent.RefreshVariableModel(self)
# If variable has moved and variable is not of type OUTPUT, refresh the model
# of wires connected to output connector
if move and self.Type != OUTPUT:
if self.Output:
self.Output.RefreshWires()
# Adds an highlight to the variable
def AddHighlight(self, infos, start, end, highlight_type):
if infos[0] == "expression" and start[0] == 0 and end[0] == 0:
AddHighlight(self.Highlights, (start, end, highlight_type))
# Removes an highlight from the variable
def RemoveHighlight(self, infos, start, end, highlight_type):
if infos[0] == "expression":
RemoveHighlight(self.Highlights, (start, end, highlight_type))
# Removes all the highlights of one particular type from the variable
def ClearHighlight(self, highlight_type=None):
ClearHighlights(self.Highlights, highlight_type)
# Draws variable
def Draw(self, dc):
Graphic_Element.Draw(self, dc)
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.WHITE_BRUSH)
if getattr(dc, "printing", False):
name_size = dc.GetTextExtent(self.Name)
executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
else:
name_size = self.NameSize
executionorder_size = self.ExecutionOrderSize
text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
self.Pos.y + (self.Size[1] - name_size[1]) / 2)
# Draw a rectangle with the variable size
dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
# Draw variable name
dc.DrawText(self.Name, text_pos[0], text_pos[1])
# Draw connectors
if self.Input:
self.Input.Draw(dc)
if self.Output:
self.Output.Draw(dc)
if self.ExecutionOrder != 0:
# Draw variable execution order
dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
self.Pos.y + self.Size[1] + 2)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
#-------------------------------------------------------------------------------
# Function Block Diagram Connector
#-------------------------------------------------------------------------------
"""
Class that implements the graphic representation of a connection
"""
class FBD_Connector(Graphic_Element):
# Create a new connection
def __init__(self, parent, type, name, id = None):
Graphic_Element.__init__(self, parent)
self.Type = type
self.Id = id
self.SetName(name)
self.Pos = wx.Point(0, 0)
self.Size = wx.Size(0, 0)
self.Highlights = []
# Create an input or output connector according to connection type
if self.Type == CONNECTOR:
self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
else:
self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
self.RefreshConnectors()
self.RefreshNameSize()
def Flush(self):
if self.Connector:
self.Connector.Flush()
self.Connector = None
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = Graphic_Element.GetRedrawRect(self, movex, movey)
if movex != 0 or movey != 0:
if self.Connector and self.Connector.IsConnected():
rect = rect.Union(self.Connector.GetConnectedRedrawRect(movex, movey))
return rect
# Make a clone of this FBD_Connector
def Clone(self, parent, id = None, pos = None):
connection = FBD_Connector(parent, self.Type, self.Name, id)
connection.SetSize(self.Size[0], self.Size[1])
if pos is not None:
connection.SetPosition(pos.x, pos.y)
else:
connection.SetPosition(self.Pos.x, self.Pos.y)
connection.Connector = self.Connector.Clone(connection)
return connection
def GetConnectorTranslation(self, element):
return {self.Connector : element.Connector}
# Unconnect connector
def Clean(self):
if self.Connector:
self.Connector.UnConnect(delete = True)
# Delete this connection by calling the appropriate method
def Delete(self):
self.Parent.DeleteConnection(self)
# Refresh the size of text for name
def RefreshNameSize(self):
self.NameSize = self.Parent.GetTextExtent(self.Name)
# Refresh the connection bounding box
def RefreshBoundingBox(self):
if self.Type == CONNECTOR:
bbx_x = self.Pos.x - CONNECTOR_SIZE
else:
bbx_x = self.Pos.x
bbx_width = self.Size[0] + CONNECTOR_SIZE
self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width, self.Size[1])
# Refresh the position of the connection connector
def RefreshConnectors(self):
scaling = self.Parent.GetScaling()
if scaling is not None:
position = round(float(self.Pos.y + self.Size[1] / 2) / float(scaling[1])) * scaling[1] - self.Pos.y
else:
position = self.Size[1] / 2
if self.Type == CONNECTOR:
self.Connector.SetPosition(wx.Point(0, position))
else:
self.Connector.SetPosition(wx.Point(self.Size[0], position))
self.RefreshConnected()
# Refresh the position of wires connected to connector
def RefreshConnected(self, exclude = []):
if self.Connector:
self.Connector.MoveConnected(exclude)
# Test if point given is on the connection connector
def TestConnector(self, pt, direction = None, exclude=True):
if self.Connector and self.Connector.TestPoint(pt, direction, exclude):
return self.Connector
return None
# Returns the connection connector
def GetConnector(self, position = None, name = None):
return self.Connector
# Returns all the block connectors
def GetConnectors(self):
connectors = {"inputs": [], "outputs": []}
if self.Type == CONNECTOR:
connectors["inputs"].append(self.Connector)
else:
connectors["outputs"].append(self.Connector)
return connectors
# Changes the variable type
def SetType(self, type):
if type != self.Type:
self.Type = type
self.Clean()
# Create an input or output connector according to connection type
if self.Type == CONNECTOR:
self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
else:
self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
self.RefreshConnectors()
# Returns the connection type
def GetType(self):
return self.Type
def GetConnectionResultType(self, connector, connectortype):
if self.Type == CONTINUATION:
connector = self.Parent.GetConnectorByName(self.Name)
if connector is not None:
return connector.Connector.GetConnectedType()
return connectortype
# Changes the connection name
def SetName(self, name):
self.Name = name
self.RefreshNameSize()
# Returns the connection name
def GetName(self):
return self.Name
# Returns the connection minimum size
def GetMinSize(self):
text_width, text_height = self.NameSize
if text_height % 2 == 1:
text_height += 1
return text_width + text_height + 20, text_height + 10
# Method called when a LeftDClick event have been generated
def OnLeftDClick(self, event, dc, scaling):
# Edit the connection properties
self.Parent.EditConnectionContent(self)
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
# Popup the default menu
self.Parent.PopupDefaultMenu()
# Refreshes the connection model
def RefreshModel(self, move=True):
self.Parent.RefreshConnectionModel(self)
# If connection has moved and connection is of type CONTINUATION, refresh
# the model of wires connected to connector
if move and self.Type == CONTINUATION:
if self.Connector:
self.Connector.RefreshWires()
# Adds an highlight to the connection
def AddHighlight(self, infos, start, end, highlight_type):
if infos[0] == "name" and start[0] == 0 and end[0] == 0:
AddHighlight(self.Highlights, (start, end, highlight_type))
# Removes an highlight from the connection
def RemoveHighlight(self, infos, start, end, highlight_type):
if infos[0] == "name":
RemoveHighlight(self.Highlights, (start, end, highlight_type))
# Removes all the highlights of one particular type from the connection
def ClearHighlight(self, highlight_type=None):
ClearHighlights(self.Highlights, highlight_type)
# Draws connection
def Draw(self, dc):
Graphic_Element.Draw(self, dc)
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.WHITE_BRUSH)
if getattr(dc, "printing", False):
name_size = dc.GetTextExtent(self.Name)
else:
name_size = self.NameSize
# Draw a rectangle with the connection size with arrows inside
dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
arrowsize = min(self.Size[1] / 2, (self.Size[0] - name_size[0] - 10) / 2)
dc.DrawLine(self.Pos.x, self.Pos.y, self.Pos.x + arrowsize,
self.Pos.y + self.Size[1] / 2)
dc.DrawLine(self.Pos.x + arrowsize, self.Pos.y + self.Size[1] / 2,
self.Pos.x, self.Pos.y + self.Size[1])
dc.DrawLine(self.Pos.x + self.Size[0] - arrowsize, self.Pos.y,
self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2)
dc.DrawLine(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2,
self.Pos.x + self.Size[0] - arrowsize, self.Pos.y + self.Size[1])
# Draw connection name
text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
self.Pos.y + (self.Size[1] - name_size[1]) / 2)
dc.DrawText(self.Name, text_pos[0], text_pos[1])
# Draw connector
if self.Connector:
self.Connector.Draw(dc)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])