graphics/FBD_Objects.py
author laurent
Sun, 08 Jan 2012 19:16:58 +0100
changeset 616 8a60ffcfd70b
parent 566 6014ef82a98a
child 625 b7062a7018ec
permissions -rw-r--r--
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])