graphics/SFC_Objects.py
author Edouard Tisserant
Tue, 26 Jan 2021 11:17:08 +0100
branchsvghmi
changeset 3119 17a9c7a334f7
parent 2457 9deec258ab1a
child 3303 0ffb41625592
permissions -rw-r--r--
SVGHMI: Fix browser side exception when some widget are not used, and are then discarded and not present in final SVG. In that case JS code was still making reference to discarded widget elements and was raising exception at init.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
# See COPYING file for copyrights details.
#
# This program 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
# of the License, or (at your option) any later version.
#
# This program 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 program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


from __future__ import absolute_import
from __future__ import division
from future.builtins import round

import wx
from six.moves import xrange

from graphics.GraphicCommons import *
from graphics.DebugDataConsumer import DebugDataConsumer
from plcopen.structures import *


def GetWireSize(block):
    if isinstance(block, SFC_Step):
        return SFC_WIRE_MIN_SIZE + block.GetActionExtraLineNumber() * SFC_ACTION_MIN_SIZE[1]
    else:
        return SFC_WIRE_MIN_SIZE

# -------------------------------------------------------------------------------
#                         Sequencial Function Chart Step
# -------------------------------------------------------------------------------


class SFC_Step(Graphic_Element, DebugDataConsumer):
    """
    Class that implements the graphic representation of a step
    """

    # Create a new step
    def __init__(self, parent, name, initial=False, id=None):
        Graphic_Element.__init__(self, parent)
        DebugDataConsumer.__init__(self)
        self.SetName(name)
        self.Initial = initial
        self.Id = id
        self.Highlights = []
        self.Size = wx.Size(SFC_STEP_DEFAULT_SIZE[0], SFC_STEP_DEFAULT_SIZE[1])
        # Create an input and output connector
        if not self.Initial:
            self.Input = Connector(self, "", None, wx.Point(self.Size[0] // 2, 0), NORTH)
        else:
            self.Input = None
        self.Output = None
        self.Action = None
        self.PreviousValue = None
        self.PreviousSpreading = False

    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
        if self.Action is not None:
            self.Action.Flush()
            self.Action = None

    def SetForced(self, forced):
        if self.Forced != forced:
            self.Forced = forced
            if self.Visible:
                self.Parent.ElementNeedRefresh(self)

    def SetValue(self, value):
        self.PreviousValue = self.Value
        self.Value = value
        if self.Value != self.PreviousValue:
            if self.Visible:
                self.Parent.ElementNeedRefresh(self)
            self.SpreadCurrent()

    def SpreadCurrent(self):
        if self.Parent.Debug:
            spreading = self.Value
            if spreading and not self.PreviousSpreading:
                if self.Output is not None:
                    self.Output.SpreadCurrent(True)
                if self.Action is not None:
                    self.Action.SpreadCurrent(True)
            elif not spreading and self.PreviousSpreading:
                if self.Output is not None:
                    self.Output.SpreadCurrent(False)
                if self.Action is not None:
                    self.Action.SpreadCurrent(False)
            self.PreviousSpreading = spreading

    # Make a clone of this SFC_Step
    def Clone(self, parent, id=None, name="Step", pos=None):
        step = SFC_Step(parent, name, self.Initial, id)
        step.SetSize(self.Size[0], self.Size[1])
        if pos is not None:
            step.SetPosition(pos.x, pos.y)
        else:
            step.SetPosition(self.Pos.x, self.Pos.y)
        if self.Input:
            step.Input = self.Input.Clone(step)
        if self.Output:
            step.Output = self.Output.Clone(step)
        if self.Action:
            step.Action = self.Action.Clone(step)
        return step

    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
        if self.Action is not None:
            connectors[self.Action] = element.Action
        return connectors

    # Returns the RedrawRect
    def GetRedrawRect(self, movex=0, movey=0):
        rect = Graphic_Element.GetRedrawRect(self, movex, movey)
        if self.Input:
            rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
        if self.Output:
            rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
        if self.Action:
            rect = rect.Union(self.Action.GetRedrawRect(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))
            if self.Action and self.Action.IsConnected():
                rect = rect.Union(self.Action.GetConnectedRedrawRect(movex, movey))
        return rect

    # Delete this step by calling the appropriate method
    def Delete(self):
        self.Parent.DeleteStep(self)

    # Unconnect input and output
    def Clean(self):
        if self.Input:
            self.Input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
        if self.Output:
            self.Output.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
        if self.Action:
            self.Action.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)

    # Refresh the size of text for name
    def RefreshNameSize(self):
        self.NameSize = self.Parent.GetTextExtent(self.Name)

    # Add output connector to step
    def AddInput(self):
        if not self.Input:
            self.Input = Connector(self, "", None, wx.Point(self.Size[0] // 2, 0), NORTH)
            self.RefreshBoundingBox()

    # Remove output connector from step
    def RemoveInput(self):
        if self.Input:
            self.Input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
            self.Input = None
            self.RefreshBoundingBox()

    # Add output connector to step
    def AddOutput(self):
        if not self.Output:
            self.Output = Connector(self, "", None, wx.Point(self.Size[0] // 2, self.Size[1]), SOUTH, onlyone=True)
            self.RefreshBoundingBox()

    # Remove output connector from step
    def RemoveOutput(self):
        if self.Output:
            self.Output.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
            self.Output = None
            self.RefreshBoundingBox()

    # Add action connector to step
    def AddAction(self):
        if not self.Action:
            self.Action = Connector(self, "", None, wx.Point(self.Size[0], self.Size[1] // 2), EAST, onlyone=True)
            self.RefreshBoundingBox()

    # Remove action connector from step
    def RemoveAction(self):
        if self.Action:
            self.Action.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
            self.Action = None
            self.RefreshBoundingBox()

    # Refresh the step bounding box
    def RefreshBoundingBox(self):
        # TODO: check and remove dead coded
        #
        # Calculate the bounding box size
        # if self.Action:
        #     bbx_width = self.Size[0] + CONNECTOR_SIZE
        # else:
        #     bbx_width = self.Size[0]
        # if self.Initial:
        #     bbx_y = self.Pos.y
        #     bbx_height = self.Size[1]
        #     if self.Output:
        #         bbx_height += CONNECTOR_SIZE
        # else:
        #     bbx_y = self.Pos.y - CONNECTOR_SIZE
        #     bbx_height = self.Size[1] + CONNECTOR_SIZE
        #     if self.Output:
        #         bbx_height += CONNECTOR_SIZE
        # self.BoundingBox = wx.Rect(self.Pos.x, bbx_y, bbx_width + 1, bbx_height + 1)
        self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)

    # Refresh the positions of the step connectors
    def RefreshConnectors(self):
        scaling = self.Parent.GetScaling()
        horizontal_pos = self.Size[0] // 2
        vertical_pos = self.Size[1] // 2
        if scaling is not None:
            horizontal_pos = round((self.Pos.x + horizontal_pos) / scaling[0]) * scaling[0] - self.Pos.x
            vertical_pos = round((self.Pos.y + vertical_pos) / scaling[1]) * scaling[1] - self.Pos.y
        # Update input position if it exists
        if self.Input:
            self.Input.SetPosition(wx.Point(horizontal_pos, 0))
        # Update output position
        if self.Output:
            self.Output.SetPosition(wx.Point(horizontal_pos, self.Size[1]))
        # Update action position if it exists
        if self.Action:
            self.Action.SetPosition(wx.Point(self.Size[0], vertical_pos))
        self.RefreshConnected()

    # Refresh the position of wires connected to step
    def RefreshConnected(self, exclude=None):
        if self.Input:
            self.Input.MoveConnected(exclude)
        if self.Output:
            self.Output.MoveConnected(exclude)
        if self.Action:
            self.Action.MoveConnected(exclude)

    # Returns the step connector that starts with the point given if it exists
    def GetConnector(self, position, name=None):
        # if a name is given
        if name is not None:
            # Test input, output and action 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
            if self.Action and name == self.Action.GetName():
                return self.Action
        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)
        # Test action connector if it exists
        if self.Action:
            connectors.append(self.Action)
        return self.FindNearestConnector(position, connectors)

    # Returns action step connector
    def GetActionConnector(self):
        return self.Action

    # Returns input and output step 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

    # Test if point given is on step input or output connector
    def TestConnector(self, pt, direction=None, exclude=True):
        # Test input connector if it exists
        if self.Input and self.Input.TestPoint(pt, direction, exclude):
            return self.Input
        # Test output connector
        if self.Output and self.Output.TestPoint(pt, direction, exclude):
            return self.Output
        # Test action connector
        if self.Action and self.Action.TestPoint(pt, direction, exclude):
            return self.Action
        return None

    # Changes the step name
    def SetName(self, name):
        self.Name = name
        self.RefreshNameSize()

    # Returns the step name
    def GetName(self):
        return self.Name

    # Returns the step initial property
    def GetInitial(self):
        return self.Initial

    # Returns the connector connected to input
    def GetPreviousConnector(self):
        if self.Input:
            wires = self.Input.GetWires()
            if len(wires) == 1:
                return wires[0][0].GetOtherConnected(self.Input)
        return None

    # Returns the connector connected to output
    def GetNextConnector(self):
        if self.Output:
            wires = self.Output.GetWires()
            if len(wires) == 1:
                return wires[0][0].GetOtherConnected(self.Output)
        return None

    # Returns the connector connected to action
    def GetActionConnected(self):
        if self.Action:
            wires = self.Action.GetWires()
            if len(wires) == 1:
                return wires[0][0].GetOtherConnected(self.Action)
        return None

    # Returns the number of action line
    def GetActionExtraLineNumber(self):
        if self.Action:
            wires = self.Action.GetWires()
            if len(wires) != 1:
                return 0
            action_block = wires[0][0].GetOtherConnected(self.Action).GetParentBlock()
            return max(0, action_block.GetLineNumber() - 1)
        return 0

    # Returns the step minimum size
    def GetMinSize(self):
        text_width, text_height = self.Parent.GetTextExtent(self.Name)
        if self.Initial:
            return text_width + 14, text_height + 14
        else:
            return text_width + 10, text_height + 10

    # Updates the step size
    def UpdateSize(self, width, height):
        diffx = self.Size.GetWidth() // 2 - width // 2
        diffy = height - self.Size.GetHeight()
        self.Move(diffx, 0)
        Graphic_Element.SetSize(self, width, height)
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            self.RefreshConnected()
        else:
            self.RefreshOutputPosition((0, diffy))

    # Align input element with this step
    def RefreshInputPosition(self):
        if self.Input:
            current_pos = self.Input.GetPosition(False)
            input = self.GetPreviousConnector()
            if input:
                input_pos = input.GetPosition(False)
                diffx = current_pos.x - input_pos.x
                input_block = input.GetParentBlock()
                if isinstance(input_block, SFC_Divergence):
                    input_block.MoveConnector(input, diffx)
                else:
                    if isinstance(input_block, SFC_Step):
                        input_block.MoveActionBlock((diffx, 0))
                    input_block.Move(diffx, 0)
                    input_block.RefreshInputPosition()

    # Align output element with this step
    def RefreshOutputPosition(self, move=None):
        if self.Output:
            wires = self.Output.GetWires()
            if len(wires) != 1:
                return
            current_pos = self.Output.GetPosition(False)
            output = wires[0][0].GetOtherConnected(self.Output)
            output_pos = output.GetPosition(False)
            diffx = current_pos.x - output_pos.x
            output_block = output.GetParentBlock()
            wire_size = SFC_WIRE_MIN_SIZE + self.GetActionExtraLineNumber() * SFC_ACTION_MIN_SIZE[1]
            diffy = wire_size - output_pos.y + current_pos.y
            if diffy != 0:
                if isinstance(output_block, SFC_Step):
                    output_block.MoveActionBlock((diffx, diffy))
                wires[0][0].SetPoints([wx.Point(current_pos.x, current_pos.y + wire_size),
                                       wx.Point(current_pos.x, current_pos.y)])
                if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
                    output_block.Move(diffx, diffy, self.Parent.Wires)
                    output_block.RefreshOutputPosition((diffx, diffy))
                else:
                    output_block.RefreshPosition()
            elif move:
                if isinstance(output_block, SFC_Step):
                    output_block.MoveActionBlock(move)
                wires[0][0].Move(move[0], move[1], True)
                if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
                    output_block.Move(move[0], move[1], self.Parent.Wires)
                    output_block.RefreshOutputPosition(move)
                else:
                    output_block.RefreshPosition()
            elif isinstance(output_block, SFC_Divergence):
                output_block.MoveConnector(output, diffx)
            else:
                if isinstance(output_block, SFC_Step):
                    output_block.MoveActionBlock((diffx, 0))
                output_block.Move(diffx, 0)
                output_block.RefreshOutputPosition()

    # Refresh action element with this step
    def MoveActionBlock(self, move):
        if self.Action:
            wires = self.Action.GetWires()
            if len(wires) != 1:
                return
            action_block = wires[0][0].GetOtherConnected(self.Action).GetParentBlock()
            action_block.Move(move[0], move[1], self.Parent.Wires)
            wires[0][0].Move(move[0], move[1], True)

    # Resize the divergence from position and size given
    def Resize(self, x, y, width, height):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            self.UpdateSize(width, height)
        else:
            Graphic_Element.Resize(self, x, y, width, height)

    # Method called when a LeftDClick event have been generated
    def OnLeftDClick(self, event, dc, scaling):
        # Edit the step properties
        self.Parent.EditStepContent(self)

    # Method called when a RightUp event have been generated
    def OnRightUp(self, event, dc, scaling):
        # Popup the menu with special items for a step
        self.Parent.PopupDefaultMenu()

    # Refreshes the step state according to move defined and handle selected
    def ProcessDragging(self, movex, movey, event, scaling):
        handle_type, _handle = self.Handle
        if handle_type == HANDLE_MOVE:
            movex = max(-self.BoundingBox.x, movex)
            movey = max(-self.BoundingBox.y, movey)
            if scaling is not None:
                movex = round((self.Pos.x + movex) / scaling[0]) * scaling[0] - self.Pos.x
                movey = round((self.Pos.y + movey) / scaling[1]) * scaling[1] - self.Pos.y
            if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
                self.Move(movex, movey)
                self.RefreshConnected()
                return movex, movey
            elif self.Initial:
                self.MoveActionBlock((movex, movey))
                self.Move(movex, movey, self.Parent.Wires)
                self.RefreshOutputPosition((movex, movey))
                return movex, movey
            else:
                self.MoveActionBlock((movex, 0))
                self.Move(movex, 0)
                self.RefreshInputPosition()
                self.RefreshOutputPosition()
                return movex, 0
        else:
            return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)

    # Refresh input element model
    def RefreshInputModel(self):
        if self.Input:
            input = self.GetPreviousConnector()
            if input:
                input_block = input.GetParentBlock()
                input_block.RefreshModel(False)
                if not isinstance(input_block, SFC_Divergence):
                    input_block.RefreshInputModel()

    # Refresh output element model
    def RefreshOutputModel(self, move=False):
        if self.Output:
            output = self.GetNextConnector()
            if output:
                output_block = output.GetParentBlock()
                output_block.RefreshModel(False)
                if not isinstance(output_block, SFC_Divergence) or move:
                    output_block.RefreshOutputModel(move)

    # Refreshes the step model
    def RefreshModel(self, move=True):
        self.Parent.RefreshStepModel(self)
        if self.Action:
            action = self.GetActionConnected()
            if action:
                action_block = action.GetParentBlock()
                action_block.RefreshModel(False)
        # If step has moved, refresh the model of wires connected to output
        if move:
            if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
                self.RefreshInputModel()
                self.RefreshOutputModel(self.Initial)
            elif self.Output:
                self.Output.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 step
    def Draw(self, dc):
        Graphic_Element.Draw(self, dc)
        if self.Value:
            if self.Forced:
                dc.SetPen(MiterPen(wx.CYAN))
            else:
                dc.SetPen(MiterPen(wx.GREEN))
        elif self.Forced:
            dc.SetPen(MiterPen(wx.BLUE))
        else:
            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 two rectangles for representing the step
        dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
        if self.Initial:
            dc.DrawRectangle(self.Pos.x + 2, self.Pos.y + 2, self.Size[0] - 3, self.Size[1] - 3)
        # Draw step name
        name_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, name_pos[0], name_pos[1])
        # Draw input and output connectors
        if self.Input:
            self.Input.Draw(dc)
        if self.Output:
            self.Output.Draw(dc)
        if self.Action:
            self.Action.Draw(dc)

        if not getattr(dc, "printing", False):
            DrawHighlightedText(dc, self.Name, self.Highlights, name_pos[0], name_pos[1])


# -------------------------------------------------------------------------------
#                       Sequencial Function Chart Transition
# -------------------------------------------------------------------------------


class SFC_Transition(Graphic_Element, DebugDataConsumer):
    """
    Class that implements the graphic representation of a transition
    """

    # Create a new transition
    def __init__(self, parent, type="reference", condition=None, priority=0, id=None):
        Graphic_Element.__init__(self, parent)
        DebugDataConsumer.__init__(self)
        self.Type = None
        self.Id = id
        self.Priority = 0
        self.Size = wx.Size(SFC_TRANSITION_SIZE[0], SFC_TRANSITION_SIZE[1])
        # Create an input and output connector
        self.Input = Connector(self,  "", None, wx.Point(self.Size[0] // 2, 0),            NORTH, onlyone=True)
        self.Output = Connector(self, "", None, wx.Point(self.Size[0] // 2, self.Size[1]), SOUTH, onlyone=True)
        self.SetType(type, condition)
        self.SetPriority(priority)
        self.Highlights = {}
        self.PreviousValue = None
        self.PreviousSpreading = False

    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
        if self.Type == "connection" and self.Condition is not None:
            self.Condition.Flush()
            self.Condition = None

    def SetForced(self, forced):
        if self.Forced != forced:
            self.Forced = forced
            if self.Visible:
                self.Parent.ElementNeedRefresh(self)

    def SetValue(self, value):
        self.PreviousValue = self.Value
        self.Value = value
        if self.Value != self.PreviousValue:
            if self.Visible:
                self.Parent.ElementNeedRefresh(self)
            self.SpreadCurrent()

    def SpreadCurrent(self):
        if self.Parent.Debug:
            if self.Value is None:
                self.Value = False
            spreading = self.Input.ReceivingCurrent() & self.Value
            if spreading and not self.PreviousSpreading:
                self.Output.SpreadCurrent(True)
            elif not spreading and self.PreviousSpreading:
                self.Output.SpreadCurrent(False)
            self.PreviousSpreading = spreading

    # Make a clone of this SFC_Transition
    def Clone(self, parent, id=None, pos=None):
        transition = SFC_Transition(parent, self.Type, self.Condition, self.Priority, id)
        transition.SetSize(self.Size[0], self.Size[1])
        if pos is not None:
            transition.SetPosition(pos.x, pos.y)
        else:
            transition.SetPosition(self.Pos.x, self.Pos.y)
        transition.Input = self.Input.Clone(transition)
        transition.Output = self.Output.Clone(transition)
        if self.Type == "connection":
            transition.Condition = self.Condition.Clone(transition)
        return transition

    def GetConnectorTranslation(self, element):
        connectors = {self.Input: element.Input, self.Output: element.Output}
        if self.Type == "connection" and self.Condition is not None:
            connectors[self.Condition] = element.Condition
        return connectors

    # Returns the RedrawRect
    def GetRedrawRect(self, movex=0, movey=0):
        rect = Graphic_Element.GetRedrawRect(self, movex, movey)
        if self.Input:
            rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
        if self.Output:
            rect = rect.Union(self.Output.GetRedrawRect(movex, movey))
        if movex != 0 or movey != 0:
            if self.Input.IsConnected():
                rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
            if self.Output.IsConnected():
                rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
            if self.Type == "connection" and self.Condition.IsConnected():
                rect = rect.Union(self.Condition.GetConnectedRedrawRect(movex, movey))
        return rect

    # Forbids to change the transition size
    def SetSize(self, width, height):
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            Graphic_Element.SetSize(self, width, height)

    # Forbids to resize the transition
    def Resize(self, x, y, width, height):
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            Graphic_Element.Resize(self, x, y, width, height)

    # Refresh the size of text for name
    def RefreshConditionSize(self):
        if self.Type != "connection":
            if self.Condition != "":
                self.ConditionSize = self.Parent.GetTextExtent(self.Condition)
            else:
                self.ConditionSize = self.Parent.GetTextExtent("Transition")

    # Refresh the size of text for name
    def RefreshPrioritySize(self):
        if self.Priority != "":
            self.PrioritySize = self.Parent.GetTextExtent(str(self.Priority))
        else:
            self.PrioritySize = None

    # Delete this transition by calling the appropriate method
    def Delete(self):
        self.Parent.DeleteTransition(self)

    # Unconnect input and output
    def Clean(self):
        self.Input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
        self.Output.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
        if self.Type == "connection":
            self.Condition.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)

    # Returns if the point given is in the bounding box
    def HitTest(self, pt, connectors=True):
        if self.Type != "connection":
            # Calculate the bounding box of the condition outside the transition
            text_width, text_height = self.ConditionSize
            text_bbx = wx.Rect(self.Pos.x + self.Size[0] + 5,
                               self.Pos.y + (self.Size[1] - text_height) // 2,
                               text_width,
                               text_height)
            test_text = text_bbx.InsideXY(pt.x, pt.y)
        else:
            test_text = False
        return test_text or Graphic_Element.HitTest(self, pt, connectors)

    # Refresh the transition bounding box
    def RefreshBoundingBox(self):
        bbx_x, bbx_y, bbx_width, bbx_height = self.Pos.x, self.Pos.y, self.Size[0], self.Size[1]
        if self.Priority != 0:
            bbx_y = self.Pos.y - self.PrioritySize[1] - 2
            bbx_width = max(self.Size[0], self.PrioritySize[0])
            bbx_height = self.Size[1] + self.PrioritySize[1] + 2
        if self.Type == "connection":
            bbx_x = self.Pos.x - CONNECTOR_SIZE
            bbx_width = bbx_width + CONNECTOR_SIZE
        else:
            text_width, text_height = self.ConditionSize
            # Calculate the bounding box size
            bbx_width = max(bbx_width, self.Size[0] + 5 + text_width)
            bbx_y = min(bbx_y, self.Pos.y - max(0, (text_height - self.Size[1]) // 2))
            bbx_height = max(bbx_height, self.Pos.y - bbx_y + (self.Size[1] + text_height) // 2)
        self.BoundingBox = wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)

    # Returns the connector connected to input
    def GetPreviousConnector(self):
        wires = self.Input.GetWires()
        if len(wires) == 1:
            return wires[0][0].GetOtherConnected(self.Input)
        return None

    # Returns the connector connected to output
    def GetNextConnector(self):
        wires = self.Output.GetWires()
        if len(wires) == 1:
            return wires[0][0].GetOtherConnected(self.Output)
        return None

    # Refresh the positions of the transition connectors
    def RefreshConnectors(self):
        scaling = self.Parent.GetScaling()
        horizontal_pos = self.Size[0] // 2
        vertical_pos = self.Size[1] // 2
        if scaling is not None:
            horizontal_pos = round((self.Pos.x + horizontal_pos) / scaling[0]) * scaling[0] - self.Pos.x
            vertical_pos = round((self.Pos.y + vertical_pos) / scaling[1]) * scaling[1] - self.Pos.y
        # Update input position
        self.Input.SetPosition(wx.Point(horizontal_pos, 0))
        # Update output position
        self.Output.SetPosition(wx.Point(horizontal_pos, self.Size[1]))
        if self.Type == "connection":
            self.Condition.SetPosition(wx.Point(0, vertical_pos))
        self.RefreshConnected()

    # Refresh the position of the wires connected to transition
    def RefreshConnected(self, exclude=None):
        self.Input.MoveConnected(exclude)
        self.Output.MoveConnected(exclude)
        if self.Type == "connection":
            self.Condition.MoveConnected(exclude)

    # Returns the transition connector that starts with the point given if it exists
    def GetConnector(self, position, name=None):
        # if a name is given
        if name is not None:
            # Test input and output connector
            # if name == self.Input.GetName():
            #    return self.Input
            if name == self.Output.GetName():
                return self.Output
            if self.Type == "connection" and name == self.Condition.GetName():
                return self.Condition
        connectors = [self.Input, self.Output]
        if self.Type == "connection":
            connectors.append(self.Condition)
        return self.FindNearestConnector(position, connectors)

    # Returns the transition condition connector
    def GetConditionConnector(self):
        if self.Type == "connection":
            return self.Condition
        return None

    # Returns input and output transition connectors
    def GetConnectors(self):
        return {"inputs": [self.Input], "outputs": [self.Output]}

    # Test if point given is on transition input or output connector
    def TestConnector(self, pt, direction=None, exclude=True):
        # Test input connector
        if self.Input.TestPoint(pt, direction, exclude):
            return self.Input
        # Test output connector
        if self.Output.TestPoint(pt, direction, exclude):
            return self.Output
        # Test condition connector
        if self.Type == "connection" and self.Condition.TestPoint(pt, direction, exclude):
            return self.Condition
        return None

    # Changes the transition type
    def SetType(self, type, condition=None):
        if self.Type != type:
            if self.Type == "connection":
                self.Condition.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
            self.Type = type
            if type == "connection":
                self.Condition = Connector(self, "", "BOOL", wx.Point(0, self.Size[1] // 2), WEST)
            else:
                if condition is None:
                    condition = ""
                self.Condition = condition
                self.RefreshConditionSize()
        elif self.Type != "connection":
            if condition is None:
                condition = ""
            self.Condition = condition
            self.RefreshConditionSize()
        self.RefreshBoundingBox()

    # Returns the transition type
    def GetType(self):
        return self.Type

    # Changes the transition priority
    def SetPriority(self, priority):
        self.Priority = priority
        self.RefreshPrioritySize()
        self.RefreshBoundingBox()

    # Returns the transition type
    def GetPriority(self):
        return self.Priority

    # Returns the transition condition
    def GetCondition(self):
        if self.Type != "connection":
            return self.Condition
        return None

    # Returns the transition minimum size
    def GetMinSize(self):
        return SFC_TRANSITION_SIZE

    # Align input element with this step
    def RefreshInputPosition(self):
        current_pos = self.Input.GetPosition(False)
        input = self.GetPreviousConnector()
        if input:
            input_pos = input.GetPosition(False)
            diffx = current_pos.x - input_pos.x
            input_block = input.GetParentBlock()
            if isinstance(input_block, SFC_Divergence):
                input_block.MoveConnector(input, diffx)
            else:
                if isinstance(input_block, SFC_Step):
                    input_block.MoveActionBlock((diffx, 0))
                input_block.Move(diffx, 0)
                input_block.RefreshInputPosition()

    # Align output element with this step
    def RefreshOutputPosition(self, move=None):
        wires = self.Output.GetWires()
        if len(wires) != 1:
            return
        current_pos = self.Output.GetPosition(False)
        output = wires[0][0].GetOtherConnected(self.Output)
        output_pos = output.GetPosition(False)
        diffx = current_pos.x - output_pos.x
        output_block = output.GetParentBlock()
        if move:
            if isinstance(output_block, SFC_Step):
                output_block.MoveActionBlock(move)
            wires[0][0].Move(move[0], move[1], True)
            if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
                output_block.Move(move[0], move[1], self.Parent.Wires)
                output_block.RefreshOutputPosition(move)
            else:
                output_block.RefreshPosition()
        elif isinstance(output_block, SFC_Divergence):
            output_block.MoveConnector(output, diffx)
        else:
            if isinstance(output_block, SFC_Step):
                output_block.MoveActionBlock((diffx, 0))
            output_block.Move(diffx, 0)
            output_block.RefreshOutputPosition()

    # Method called when a LeftDClick event have been generated
    def OnLeftDClick(self, event, dc, scaling):
        # Edit the transition properties
        self.Parent.EditTransitionContent(self)

    # Method called when a RightUp event have been generated
    def OnRightUp(self, event, dc, scaling):
        # Popup the menu with special items for a step
        self.Parent.PopupDefaultMenu()

    # Refreshes the transition state according to move defined and handle selected
    def ProcessDragging(self, movex, movey, event, scaling):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            movex = max(-self.BoundingBox.x, movex)
            if scaling is not None:
                movex = round((self.Pos.x + movex) / scaling[0]) * scaling[0] - self.Pos.x
            self.Move(movex, 0)
            self.RefreshInputPosition()
            self.RefreshOutputPosition()
            return movex, 0
        else:
            return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, width_fac=2, height_fac=2)

    # Refresh input element model
    def RefreshInputModel(self):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            input = self.GetPreviousConnector()
            if input:
                input_block = input.GetParentBlock()
                input_block.RefreshModel(False)
                if not isinstance(input_block, SFC_Divergence):
                    input_block.RefreshInputModel()

    # Refresh output element model
    def RefreshOutputModel(self, move=False):
        output = self.GetNextConnector()
        if output:
            output_block = output.GetParentBlock()
            output_block.RefreshModel(False)
            if not isinstance(output_block, SFC_Divergence) or move:
                output_block.RefreshOutputModel(move)

    # Refreshes the transition model
    def RefreshModel(self, move=True):
        self.Parent.RefreshTransitionModel(self)
        # If transition has moved, refresh the model of wires connected to output
        if move:
            if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
                self.RefreshInputModel()
                self.RefreshOutputModel()
            else:
                self.Output.RefreshWires()

    # Adds an highlight to the block
    def AddHighlight(self, infos, start, end, highlight_type):
        if infos[0] in ["reference", "inline", "priority"] and start[0] == 0 and end[0] == 0:
            highlights = self.Highlights.setdefault(infos[0], [])
            AddHighlight(highlights, (start, end, highlight_type))

    # Removes an highlight from the block
    def RemoveHighlight(self, infos, start, end, highlight_type):
        if infos[0] in ["reference", "inline", "priority"]:
            highlights = self.Highlights.get(infos[0], [])
            if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
                self.Highlights.pop(infos[0])

    # 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)

    # Draws transition
    def Draw(self, dc):
        Graphic_Element.Draw(self, dc)
        if self.Value:
            if self.Forced:
                dc.SetPen(MiterPen(wx.CYAN))
                dc.SetBrush(wx.CYAN_BRUSH)
            else:
                dc.SetPen(MiterPen(wx.GREEN))
                dc.SetBrush(wx.GREEN_BRUSH)
        elif self.Forced:
            dc.SetPen(MiterPen(wx.BLUE))
            dc.SetBrush(wx.BLUE_BRUSH)
        else:
            dc.SetPen(MiterPen(wx.BLACK))
            dc.SetBrush(wx.BLACK_BRUSH)

        if getattr(dc, "printing", False):
            if self.Type != "connection":
                condition_size = dc.GetTextExtent(self.Condition)
            if self.Priority != 0:
                priority_size = dc.GetTextExtent(str(self.Priority))
        else:
            if self.Type != "connection":
                condition_size = self.ConditionSize
            if self.Priority != 0:
                priority_size = self.PrioritySize

        # Draw plain rectangle for representing the transition
        dc.DrawRectangle(self.Pos.x,
                         self.Pos.y + (self.Size[1] - SFC_TRANSITION_SIZE[1]) // 2,
                         self.Size[0] + 1,
                         SFC_TRANSITION_SIZE[1] + 1)
        vertical_line_x = self.Input.GetPosition()[0]
        dc.DrawLine(vertical_line_x, self.Pos.y, vertical_line_x, self.Pos.y + self.Size[1] + 1)
        # Draw transition condition
        if self.Type != "connection":
            if self.Condition != "":
                condition = self.Condition
            else:
                condition = "Transition"
            condition_pos = (self.Pos.x + self.Size[0] + 5,
                             self.Pos.y + (self.Size[1] - condition_size[1]) // 2)
            dc.DrawText(condition, condition_pos[0], condition_pos[1])
        # Draw priority number
        if self.Priority != 0:
            priority_pos = (self.Pos.x, self.Pos.y - priority_size[1] - 2)
            dc.DrawText(str(self.Priority), priority_pos[0], priority_pos[1])
        # Draw input and output connectors
        self.Input.Draw(dc)
        self.Output.Draw(dc)
        if self.Type == "connection":
            self.Condition.Draw(dc)

        if not getattr(dc, "printing", False):
            for name, highlights in self.Highlights.iteritems():
                if name == "priority":
                    DrawHighlightedText(dc, str(self.Priority), highlights, priority_pos[0], priority_pos[1])
                else:
                    DrawHighlightedText(dc, condition, highlights, condition_pos[0], condition_pos[1])


# -------------------------------------------------------------------------------
#                Sequencial Function Chart Divergence and Convergence
# -------------------------------------------------------------------------------


class SFC_Divergence(Graphic_Element):
    """
    Class that implements the graphic representation of a divergence or convergence,
    selection or simultaneous
    """

    # Create a new divergence
    def __init__(self, parent, type, number=2, id=None):
        Graphic_Element.__init__(self, parent)
        self.Type = type
        self.Id = id
        self.RealConnectors = None
        number = max(2, number)
        self.Size = wx.Size((number - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL, self.GetMinSize()[1])
        # Create an input and output connector
        if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
            self.Inputs = [Connector(self, "", None, wx.Point(self.Size[0] // 2, 0), NORTH, onlyone=True)]
            self.Outputs = []
            for i in xrange(number):
                self.Outputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH, onlyone=True))
        elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            self.Inputs = []
            for i in xrange(number):
                self.Inputs.append(Connector(self, "", None, wx.Point(i * SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH, onlyone=True))
            self.Outputs = [Connector(self, "", None, wx.Point(self.Size[0] // 2, self.Size[1]), SOUTH, onlyone=True)]
        self.Value = None
        self.PreviousValue = None

    def Flush(self):
        for input in self.Inputs:
            input.Flush()
        self.Inputs = []
        for output in self.Outputs:
            output.Flush()
        self.Outputs = []

    def SpreadCurrent(self):
        if self.Parent.Debug:
            self.PreviousValue = self.Value
            if self.Type == SELECTION_CONVERGENCE:
                self.Value = False
                for input in self.Inputs:
                    self.Value |= input.ReceivingCurrent()
            elif self.Type == SIMULTANEOUS_CONVERGENCE:
                self.Value = True
                for input in self.Inputs:
                    self.Value &= input.ReceivingCurrent()
            elif self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
                self.Value = self.Inputs[0].ReceivingCurrent()
            else:
                self.Value = False
            if self.Value and not self.PreviousValue:
                if self.Visible:
                    self.Parent.ElementNeedRefresh(self)
                for output in self.Outputs:
                    output.SpreadCurrent(True)
            elif not self.Value and self.PreviousValue:
                if self.Visible:
                    self.Parent.ElementNeedRefresh(self)
                for output in self.Outputs:
                    output.SpreadCurrent(False)

    # Make a clone of this SFC_Divergence
    def Clone(self, parent, id=None, pos=None):
        divergence = SFC_Divergence(parent, self.Type, max(len(self.Inputs), len(self.Outputs)), id)
        divergence.SetSize(self.Size[0], self.Size[1])
        if pos is not None:
            divergence.SetPosition(pos.x, pos.y)
        else:
            divergence.SetPosition(self.Pos.x, self.Pos.y)
        divergence.Inputs = [input.Clone(divergence) for input in self.Inputs]
        divergence.Outputs = [output.Clone(divergence) for output in self.Outputs]
        return divergence

    def GetConnectorTranslation(self, element):
        return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.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

    # Forbids to resize the divergence
    def Resize(self, x, y, width, height):
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            Graphic_Element.Resize(self, x, 0, width, self.GetMinSize()[1])

    # Delete this divergence by calling the appropriate method
    def Delete(self):
        self.Parent.DeleteDivergence(self)

    # Returns the divergence type
    def GetType(self):
        return self.Type

    # Unconnect input and output
    def Clean(self):
        for input in self.Inputs:
            input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)
        for output in self.Outputs:
            output.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)

    # Add a branch to the divergence
    def AddBranch(self):
        if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
            maxx = 0
            for output in self.Outputs:
                pos = output.GetRelPosition()
                maxx = max(maxx, pos.x)
            connector = Connector(self, "", None, wx.Point(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, self.Size[1]), SOUTH, onlyone=True)
            self.Outputs.append(connector)
            self.MoveConnector(connector, 0)
        elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            maxx = 0
            for input in self.Inputs:
                pos = input.GetRelPosition()
                maxx = max(maxx, pos.x)
            connector = Connector(self, "", None, wx.Point(maxx + SFC_DEFAULT_SEQUENCE_INTERVAL, 0), NORTH, onlyone=True)
            self.Inputs.append(connector)
            self.MoveConnector(connector, SFC_DEFAULT_SEQUENCE_INTERVAL)

    # Remove a branch from the divergence
    def RemoveBranch(self, connector):
        if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
            if connector in self.Outputs and len(self.Outputs) > 2:
                self.Outputs.remove(connector)
                self.MoveConnector(self.Outputs[0], 0)
        elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            if connector in self.Inputs and len(self.Inputs) > 2:
                self.Inputs.remove(connector)
                self.MoveConnector(self.Inputs[0], 0)

    # Remove the handled branch from the divergence
    def RemoveHandledBranch(self):
        handle_type, handle = self.Handle
        if handle_type == HANDLE_CONNECTOR:
            handle.UnConnect(delete=True)
            self.RemoveBranch(handle)

    # Return the number of branches for the divergence
    def GetBranchNumber(self):
        if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
            return len(self.Outputs)
        elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            return len(self.Inputs)

    # Returns if the point given is in the bounding box
    def HitTest(self, pt, connectors=True):
        return self.BoundingBox.InsideXY(pt.x, pt.y) or self.TestConnector(pt, exclude=False) is not None

    # Refresh the divergence bounding box
    def RefreshBoundingBox(self):
        if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
            self.BoundingBox = wx.Rect(self.Pos.x,       self.Pos.y,
                                       self.Size[0] + 1, self.Size[1] + 1)
        elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            self.BoundingBox = wx.Rect(
                self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA,           self.Pos.y,
                self.Size[0] + 2 * SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Size[1] + 1)

    # Refresh the position of wires connected to divergence
    def RefreshConnected(self, exclude=None):
        for input in self.Inputs:
            input.MoveConnected(exclude)
        for output in self.Outputs:
            output.MoveConnected(exclude)

    # Moves the divergence connector given
    def MoveConnector(self, connector, movex):
        position = connector.GetRelPosition()
        connector.SetPosition(wx.Point(position.x + movex, position.y))
        minx = self.Size[0]
        maxx = 0
        for input in self.Inputs:
            input_pos = input.GetRelPosition()
            minx = min(minx, input_pos.x)
            maxx = max(maxx, input_pos.x)
        for output in self.Outputs:
            output_pos = output.GetRelPosition()
            minx = min(minx, output_pos.x)
            maxx = max(maxx, output_pos.x)
        if minx != 0:
            for input in self.Inputs:
                input_pos = input.GetRelPosition()
                input.SetPosition(wx.Point(input_pos.x - minx, input_pos.y))
            for output in self.Outputs:
                output_pos = output.GetRelPosition()
                output.SetPosition(wx.Point(output_pos.x - minx, output_pos.y))
        self.Inputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
        self.Outputs.sort(lambda x, y: cmp(x.Pos.x, y.Pos.x))
        self.Pos.x += minx
        self.Size[0] = maxx - minx
        connector.MoveConnected()
        self.RefreshBoundingBox()

    # Returns the divergence connector that starts with the point given if it exists
    def GetConnector(self, position, name=None):
        # if a name is given
        if name is not None:
            # 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)

    # Returns input and output divergence connectors
    def GetConnectors(self):
        return {"inputs": self.Inputs, "outputs": self.Outputs}

    # Test if point given is on divergence input or output connector
    def TestConnector(self, pt, direction=None, exclude=True):
        # Test input connector
        for input in self.Inputs:
            if input.TestPoint(pt, direction, exclude):
                return input
        # Test output connector
        for output in self.Outputs:
            if output.TestPoint(pt, direction, exclude):
                return output
        return None

    # Changes the divergence size
    def SetSize(self, width, height):
        height = self.GetMinSize()[1]
        for i, input in enumerate(self.Inputs):
            position = input.GetRelPosition()
            if self.RealConnectors:
                input.SetPosition(wx.Point(int(round(self.RealConnectors["Inputs"][i] * width)), 0))
            else:
                input.SetPosition(wx.Point(int(round(position.x*width / self.Size[0])), 0))
            input.MoveConnected()
        for i, output in enumerate(self.Outputs):
            position = output.GetRelPosition()
            if self.RealConnectors:
                output.SetPosition(wx.Point(int(round(self.RealConnectors["Outputs"][i] * width)), height))
            else:
                output.SetPosition(wx.Point(int(round(position.x*width / self.Size[0])), height))
            output.MoveConnected()
        self.Size = wx.Size(width, height)
        self.RefreshBoundingBox()

    # Returns the divergence minimum size
    def GetMinSize(self, default=False):
        width = 0
        if default:
            if self.Type in [SELECTION_DIVERGENCE, SIMULTANEOUS_DIVERGENCE]:
                width = (len(self.Outputs) - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL
            elif self.Type in [SELECTION_CONVERGENCE, SIMULTANEOUS_CONVERGENCE]:
                width = (len(self.Inputs) - 1) * SFC_DEFAULT_SEQUENCE_INTERVAL
        if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
            return width, 1
        elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            return width, 3
        return 0, 0

    # Refresh the position of the block connected to connector
    def RefreshConnectedPosition(self, connector):
        wires = connector.GetWires()
        if len(wires) != 1:
            return
        current_pos = connector.GetPosition(False)
        next = wires[0][0].GetOtherConnected(connector)
        next_pos = next.GetPosition(False)
        diffx = current_pos.x - next_pos.x
        next_block = next.GetParentBlock()
        if isinstance(next_block, SFC_Divergence):
            next_block.MoveConnector(next, diffx)
        else:
            next_block.Move(diffx, 0)
            if connector in self.Inputs:
                next_block.RefreshInputPosition()
            else:
                next_block.RefreshOutputPosition()

    # Refresh the position of this divergence
    def RefreshPosition(self):
        y = 0
        for input in self.Inputs:
            wires = input.GetWires()
            if len(wires) != 1:
                return
            previous = wires[0][0].GetOtherConnected(input)
            previous_pos = previous.GetPosition(False)
            y = max(y, previous_pos.y + GetWireSize(previous.GetParentBlock()))
        diffy = y - self.Pos.y
        if diffy != 0:
            self.Move(0, diffy, self.Parent.Wires)
            self.RefreshOutputPosition((0, diffy))
        for input in self.Inputs:
            input.MoveConnected()

    # Align output element with this divergence
    def RefreshOutputPosition(self, move=None):
        if move:
            for output_connector in self.Outputs:
                wires = output_connector.GetWires()
                if len(wires) != 1:
                    return
                output = wires[0][0].GetOtherConnected(self.Output)
                output_block = output.GetParentBlock()
                if isinstance(output_block, SFC_Step):
                    output_block.MoveActionBlock(move)
                wires[0][0].Move(move[0], move[1], True)
                if not isinstance(output_block, SFC_Divergence) or output_block.GetConnectors()["inputs"].index(output) == 0:
                    output_block.Move(move[0], move[1], self.Parent.Wires)
                    output_block.RefreshOutputPosition(move)

    # Method called when a LeftDown event have been generated
    def OnLeftDown(self, event, dc, scaling):
        self.RealConnectors = {"Inputs": [], "Outputs": []}
        for input in self.Inputs:
            position = input.GetRelPosition()
            self.RealConnectors["Inputs"].append(position.x / self.Size[0])
        for output in self.Outputs:
            position = output.GetRelPosition()
            self.RealConnectors["Outputs"].append(position.x / self.Size[0])
        Graphic_Element.OnLeftDown(self, event, dc, scaling)

    # Method called when a LeftUp event have been generated
    def OnLeftUp(self, event, dc, scaling):
        Graphic_Element.OnLeftUp(self, event, dc, scaling)
        self.RealConnectors = None

    # Method called when a RightDown event have been generated
    def OnRightDown(self, event, dc, scaling):
        pos = GetScaledEventPosition(event, dc, scaling)
        # Test if a connector have been handled
        connector = self.TestConnector(pos, exclude=False)
        if connector:
            self.Handle = (HANDLE_CONNECTOR, connector)
            wx.CallAfter(self.Parent.SetCurrentCursor, 1)
            self.Selected = False
            # Initializes the last position
            self.oldPos = GetScaledEventPosition(event, dc, scaling)
        else:
            Graphic_Element.OnRightDown(self, event, dc, scaling)

    # Method called when a RightUp event have been generated
    def OnRightUp(self, event, dc, scaling):
        pos = GetScaledEventPosition(event, dc, scaling)
        handle_type, handle = self.Handle
        if handle_type == HANDLE_CONNECTOR and self.Dragging and self.oldPos:
            wires = handle.GetWires()
            if len(wires) == 1:
                block = wires[0][0].GetOtherConnected(handle).GetParentBlock()
                block.RefreshModel(False)
                if not isinstance(block, SFC_Divergence):
                    if handle in self.Inputs:
                        block.RefreshInputModel()
                    else:
                        block.RefreshOutputModel()
            Graphic_Element.OnRightUp(self, event, dc, scaling)
        else:
            # 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.PopupDivergenceMenu(True)
            else:
                # Popup the divergence menu without delete branch
                self.Parent.PopupDivergenceMenu(False)

    # Refreshes the divergence state according to move defined and handle selected
    def ProcessDragging(self, movex, movey, event, scaling):
        handle_type, handle = self.Handle
        # A connector has been handled
        if handle_type == HANDLE_CONNECTOR:
            movex = max(-self.BoundingBox.x, movex)
            if scaling is not None:
                movex = round((self.Pos.x + movex) / scaling[0]) * scaling[0] - self.Pos.x
            self.MoveConnector(handle, movex)
            if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
                self.RefreshConnectedPosition(handle)
            return movex, 0
        elif self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
        return 0, 0

    # Refresh output element model
    def RefreshOutputModel(self, move=False):
        if move and self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            for output in self.Outputs:
                wires = output.GetWires()
                if len(wires) != 1:
                    return
                output_block = wires[0][0].GetOtherConnected(output).GetParentBlock()
                output_block.RefreshModel(False)
                if not isinstance(output_block, SFC_Divergence) or move:
                    output_block.RefreshOutputModel(move)

    # Refreshes the divergence model
    def RefreshModel(self, move=True):
        self.Parent.RefreshDivergenceModel(self)
        # If divergence has moved, refresh the model of wires connected to outputs
        if move:
            if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
                self.RefreshOutputModel()
            else:
                for output in self.Outputs:
                    output.RefreshWires()

    # Draws the highlightment of this element if it is highlighted
    def DrawHighlightment(self, dc):
        scalex, scaley = dc.GetUserScale()
        dc.SetUserScale(1, 1)
        dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
        dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
        dc.SetLogicalFunction(wx.AND)
        # Draw two rectangles for representing the contact
        posx = self.Pos.x
        width = self.Size[0]
        if self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            posx -= SFC_SIMULTANEOUS_SEQUENCE_EXTRA
            width += SFC_SIMULTANEOUS_SEQUENCE_EXTRA * 2
        dc.DrawRectangle(int(round((posx - 1) * scalex)) - 2,
                         int(round((self.Pos.y - 1) * scaley)) - 2,
                         int(round((width + 3) * scalex)) + 5,
                         int(round((self.Size.height + 3) * scaley)) + 5)
        dc.SetLogicalFunction(wx.COPY)
        dc.SetUserScale(scalex, scaley)

    # Draws divergence
    def Draw(self, dc):
        Graphic_Element.Draw(self, dc)
        if self.Value:
            dc.SetPen(MiterPen(wx.GREEN))
            dc.SetBrush(wx.GREEN_BRUSH)
        else:
            dc.SetPen(MiterPen(wx.BLACK))
            dc.SetBrush(wx.BLACK_BRUSH)
        # Draw plain rectangle for representing the divergence
        if self.Type in [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE]:
            dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
        elif self.Type in [SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE]:
            dc.DrawLine(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y,
                        self.Pos.x + self.Size[0] + SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Pos.y)
            dc.DrawLine(self.Pos.x - SFC_SIMULTANEOUS_SEQUENCE_EXTRA, self.Pos.y + self.Size[1],
                        self.Pos.x + self.Size[0] + SFC_SIMULTANEOUS_SEQUENCE_EXTRA + 1, self.Pos.y + self.Size[1])
        # Draw inputs and outputs connectors
        for input in self.Inputs:
            input.Draw(dc)
        for output in self.Outputs:
            output.Draw(dc)


# -------------------------------------------------------------------------------
#                   Sequencial Function Chart Jump to Step
# -------------------------------------------------------------------------------


class SFC_Jump(Graphic_Element):
    """
    Class that implements the graphic representation of a jump to step
    """

    # Create a new jump
    def __init__(self, parent, target, id=None):
        Graphic_Element.__init__(self, parent)
        self.SetTarget(target)
        self.Id = id
        self.Size = wx.Size(SFC_JUMP_SIZE[0], SFC_JUMP_SIZE[1])
        self.Highlights = []
        # Create an input and output connector
        self.Input = Connector(self, "", None, wx.Point(self.Size[0] // 2, 0), NORTH, onlyone=True)
        self.Value = None
        self.PreviousValue = None

    def Flush(self):
        if self.Input is not None:
            self.Input.Flush()
            self.Input = None

    def SpreadCurrent(self):
        if self.Parent.Debug:
            self.PreviousValue = self.Value
            self.Value = self.Input.ReceivingCurrent()
            if self.Value != self.PreviousValue and self.Visible:
                self.Parent.ElementNeedRefresh(self)

    # Make a clone of this SFC_Jump
    def Clone(self, parent, id=None, pos=None):
        jump = SFC_Jump(parent, self.Target, id)
        jump.SetSize(self.Size[0], self.Size[1])
        if pos is not None:
            jump.SetPosition(pos.x, pos.y)
        else:
            jump.SetPosition(self.Pos.x, self.Pos.y)
        jump.Input = self.Input.Clone(jump)
        return jump

    def GetConnectorTranslation(self, element):
        return {self.Input: element.Input}

    # Returns the RedrawRect
    def GetRedrawRect(self, movex=0, movey=0):
        rect = Graphic_Element.GetRedrawRect(self, movex, movey)
        if self.Input:
            rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
        if movex != 0 or movey != 0:
            if self.Input.IsConnected():
                rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
        return rect

    # Forbids to change the jump size
    def SetSize(self, width, height):
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            Graphic_Element.SetSize(self, width, height)

    # Forbids to resize jump
    def Resize(self, x, y, width, height):
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            Graphic_Element.Resize(self, x, y, width, height)

    # Delete this jump by calling the appropriate method
    def Delete(self):
        self.Parent.DeleteJump(self)

    # Unconnect input
    def Clean(self):
        self.Input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)

    # Refresh the size of text for target
    def RefreshTargetSize(self):
        self.TargetSize = self.Parent.GetTextExtent(self.Target)

    # Returns if the point given is in the bounding box
    def HitTest(self, pt, connectors=True):
        # Calculate the bounding box of the condition outside the transition
        text_width, text_height = self.TargetSize
        text_bbx = wx.Rect(self.Pos.x + self.Size[0] + 2,
                           self.Pos.y + (self.Size[1] - text_height) // 2,
                           text_width,
                           text_height)
        return text_bbx.InsideXY(pt.x, pt.y) or Graphic_Element.HitTest(self, pt, connectors)

    # Refresh the jump bounding box
    def RefreshBoundingBox(self):
        text_width, _text_height = self.Parent.GetTextExtent(self.Target)
        # Calculate the bounding box size
        bbx_width = self.Size[0] + 2 + text_width
        self.BoundingBox = wx.Rect(self.Pos.x,    self.Pos.y - CONNECTOR_SIZE,
                                   bbx_width + 1, self.Size[1] + CONNECTOR_SIZE + 1)

    # Returns the connector connected to input
    def GetPreviousConnector(self):
        wires = self.Input.GetWires()
        if len(wires) == 1:
            return wires[0][0].GetOtherConnected(self.Input)
        return None

    # Refresh the element connectors position
    def RefreshConnectors(self):
        scaling = self.Parent.GetScaling()
        horizontal_pos = self.Size[0] // 2
        if scaling is not None:
            horizontal_pos = round((self.Pos.x + horizontal_pos) / scaling[0]) * scaling[0] - self.Pos.x
        self.Input.SetPosition(wx.Point(horizontal_pos, 0))
        self.RefreshConnected()

    # Refresh the position of wires connected to jump
    def RefreshConnected(self, exclude=None):
        if self.Input:
            self.Input.MoveConnected(exclude)

    # Returns input jump connector
    def GetConnector(self, position=None, name=None):
        return self.Input

    # Returns all the jump connectors
    def GetConnectors(self):
        return {"inputs": [self.Input], "outputs": []}

    # Test if point given is on jump input connector
    def TestConnector(self, pt, direction=None, exclude=True):
        # Test input connector
        if self.Input and self.Input.TestPoint(pt, direction, exclude):
            return self.Input
        return None

    # Changes the jump target
    def SetTarget(self, target):
        self.Target = target
        self.RefreshTargetSize()
        self.RefreshBoundingBox()

    # Returns the jump target
    def GetTarget(self):
        return self.Target

    # Returns the jump minimum size
    def GetMinSize(self):
        return SFC_JUMP_SIZE

    # Align input element with this jump
    def RefreshInputPosition(self):
        if self.Input:
            current_pos = self.Input.GetPosition(False)
            input = self.GetPreviousConnector()
            if input:
                input_pos = input.GetPosition(False)
                diffx = current_pos.x - input_pos.x
                input_block = input.GetParentBlock()
                if isinstance(input_block, SFC_Divergence):
                    input_block.MoveConnector(input, diffx)
                else:
                    if isinstance(input_block, SFC_Step):
                        input_block.MoveActionBlock((diffx, 0))
                    input_block.Move(diffx, 0)
                    input_block.RefreshInputPosition()

    # Can't align output element, because there is no output
    def RefreshOutputPosition(self, move=None):
        pass

    # Method called when a LeftDClick event have been generated
    def OnLeftDClick(self, event, dc, scaling):
        # Edit the jump properties
        self.Parent.EditJumpContent(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 jump state according to move defined and handle selected
    def ProcessDragging(self, movex, movey, event, scaling):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            movex = max(-self.BoundingBox.x, movex)
            if scaling is not None:
                movex = round((self.Pos.x + movex) / scaling[0]) * scaling[0] - self.Pos.x
            self.Move(movex, 0)
            self.RefreshInputPosition()
            return movex, 0
        else:
            return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling, width_fac=2)

    # Refresh input element model
    def RefreshInputModel(self):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            input = self.GetPreviousConnector()
            if input:
                input_block = input.GetParentBlock()
                input_block.RefreshModel(False)
                if not isinstance(input_block, SFC_Divergence):
                    input_block.RefreshInputModel()

    # Refresh output element model
    def RefreshOutputModel(self, move=False):
        pass

    # Refreshes the jump model
    def RefreshModel(self, move=True):
        self.Parent.RefreshJumpModel(self)
        if move:
            if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
                self.RefreshInputModel()

    # Adds an highlight to the variable
    def AddHighlight(self, infos, start, end, highlight_type):
        if infos[0] == "target" 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] == "target":
            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 the highlightment of this element if it is highlighted
    def DrawHighlightment(self, dc):
        scalex, scaley = dc.GetUserScale()
        dc.SetUserScale(1, 1)
        dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
        dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
        dc.SetLogicalFunction(wx.AND)
        points = [wx.Point(int(round((self.Pos.x - 2) * scalex)) - 3,
                           int(round((self.Pos.y - 2) * scaley)) - 2),
                  wx.Point(int(round((self.Pos.x + self.Size[0] + 2) * scalex)) + 4,
                           int(round((self.Pos.y - 2) * scaley)) - 2),
                  wx.Point(int(round((self.Pos.x + self.Size[0] / 2) * scalex)),
                           int(round((self.Pos.y + self.Size[1] + 3) * scaley)) + 4)]
        dc.DrawPolygon(points)
        dc.SetLogicalFunction(wx.COPY)
        dc.SetUserScale(scalex, scaley)

    # Draws divergence
    def Draw(self, dc):
        Graphic_Element.Draw(self, dc)
        if self.Value:
            dc.SetPen(MiterPen(wx.GREEN))
            dc.SetBrush(wx.GREEN_BRUSH)
        else:
            dc.SetPen(MiterPen(wx.BLACK))
            dc.SetBrush(wx.BLACK_BRUSH)

        if getattr(dc, "printing", False):
            target_size = dc.GetTextExtent(self.Target)
        else:
            target_size = self.TargetSize

        # Draw plain rectangle for representing the divergence
        dc.DrawLine(self.Pos.x + self.Size[0] // 2, self.Pos.y, self.Pos.x + self.Size[0] // 2, self.Pos.y + self.Size[1])
        points = [wx.Point(self.Pos.x, self.Pos.y),
                  wx.Point(self.Pos.x + self.Size[0] // 2, self.Pos.y + self.Size[1] // 3),
                  wx.Point(self.Pos.x + self.Size[0], self.Pos.y),
                  wx.Point(self.Pos.x + self.Size[0] // 2, self.Pos.y + self.Size[1])]
        dc.DrawPolygon(points)
        target_pos = (self.Pos.x + self.Size[0] + 2,
                      self.Pos.y + (self.Size[1] - target_size[1]) // 2)
        dc.DrawText(self.Target, target_pos[0], target_pos[1])
        # Draw input connector
        if self.Input:
            self.Input.Draw(dc)

        if not getattr(dc, "printing", False):
            DrawHighlightedText(dc, self.Target, self.Highlights, target_pos[0], target_pos[1])


# -------------------------------------------------------------------------------
#                   Sequencial Function Chart Action Block
# -------------------------------------------------------------------------------


class SFC_ActionBlock(Graphic_Element):
    """
    Class that implements the graphic representation of an action block
    """

    # Create a new action block
    def __init__(self, parent, actions=None, id=None):
        Graphic_Element.__init__(self, parent)
        self.Id = id
        self.Size = wx.Size(SFC_ACTION_MIN_SIZE[0], SFC_ACTION_MIN_SIZE[1])
        self.MinSize = wx.Size(SFC_ACTION_MIN_SIZE[0], SFC_ACTION_MIN_SIZE[1])
        self.Highlights = {}
        # Create an input and output connector
        self.Input = Connector(self, "", None, wx.Point(0, SFC_ACTION_MIN_SIZE[1] // 2), WEST, onlyone=True)
        self.SetActions(actions)
        self.Value = None
        self.PreviousValue = None

    def Flush(self):
        if self.Input is not None:
            self.Input.Flush()
            self.Input = None

    def SpreadCurrent(self):
        if self.Parent.Debug:
            self.PreviousValue = self.Value
            self.Value = self.Input.ReceivingCurrent()
            if self.Value != self.PreviousValue and self.Visible:
                self.Parent.ElementNeedRefresh(self)

    # Make a clone of this SFC_ActionBlock
    def Clone(self, parent, id=None, pos=None):
        actions = [action.copy() for action in self.Actions]
        action_block = SFC_ActionBlock(parent, actions, id)
        action_block.SetSize(self.Size[0], self.Size[1])
        if pos is not None:
            action_block.SetPosition(pos.x, pos.y)
        else:
            action_block.SetPosition(self.Pos.x, self.Pos.y)
        action_block.Input = self.Input.Clone(action_block)
        return action_block

    def GetConnectorTranslation(self, element):
        return {self.Input: element.Input}

    # Returns the RedrawRect
    def GetRedrawRect(self, movex=0, movey=0):
        rect = Graphic_Element.GetRedrawRect(self, movex, movey)
        if self.Input:
            rect = rect.Union(self.Input.GetRedrawRect(movex, movey))
        if movex != 0 or movey != 0:
            if self.Input.IsConnected():
                rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
        return rect

    # Returns the number of action lines
    def GetLineNumber(self):
        return len(self.Actions)

    def GetLineSize(self):
        if len(self.Actions) > 0:
            return self.Size[1] // len(self.Actions)
        else:
            return SFC_ACTION_MIN_SIZE[1]

    # Forbids to resize the action block
    def Resize(self, x, y, width, height):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            if x == 0:
                self.SetSize(width, self.Size[1])
        else:
            Graphic_Element.Resize(self, x, y, width, height)

    # Delete this action block by calling the appropriate method
    def Delete(self):
        self.Parent.DeleteActionBlock(self)

    # Unconnect input and output
    def Clean(self):
        self.Input.UnConnect(delete=self.Parent.GetDrawingMode() == FREEDRAWING_MODE)

    # Refresh the action block bounding box
    def RefreshBoundingBox(self):
        self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)

    # Refresh the position of wires connected to action block
    def RefreshConnected(self, exclude=None):
        self.Input.MoveConnected(exclude)

    # Returns input action block connector
    def GetConnector(self, position=None, name=None):
        return self.Input

    # Returns all the action block connectors
    def GetConnectors(self):
        return {"inputs": [self.Input], "outputs": []}

    # Test if point given is on action block input connector
    def TestConnector(self, pt, direction=None, exclude=True):
        # Test input connector
        if self.Input.TestPoint(pt, direction, exclude):
            return self.Input
        return None

    # Refresh the element connectors position
    def RefreshConnectors(self):
        scaling = self.Parent.GetScaling()
        vertical_pos = SFC_ACTION_MIN_SIZE[1] // 2
        if scaling is not None:
            vertical_pos = round((self.Pos.y + vertical_pos) / scaling[1]) * scaling[1] - self.Pos.y
        self.Input.SetPosition(wx.Point(0, vertical_pos))
        self.RefreshConnected()

    # Changes the action block actions
    def SetActions(self, actions=None):
        actions = [] if actions is None else actions
        self.Actions = actions
        self.ColSize = [0, 0, 0]
        min_height = 0
        for action in self.Actions:
            width, height = self.Parent.GetTextExtent(
                action.qualifier if action.qualifier != "" else "N")
            self.ColSize[0] = max(self.ColSize[0], width + 10)
            row_height = height
            if action.duration != "":
                width, height = self.Parent.GetTextExtent(action.duration)
                row_height = max(row_height, height)
                self.ColSize[0] = max(self.ColSize[0], width + 10)
            width, height = self.Parent.GetTextExtent(action.value)
            row_height = max(row_height, height)
            self.ColSize[1] = max(self.ColSize[1], width + 10)
            if action.indicator != "":
                width, height = self.Parent.GetTextExtent(action.indicator)
                row_height = max(row_height, height)
                self.ColSize[2] = max(self.ColSize[2], width + 10)
            min_height += row_height + 5
        if self.Parent.GetDrawingMode() == FREEDRAWING_MODE:
            self.Size = wx.Size(self.ColSize[0] + self.ColSize[1] + self.ColSize[2], max(min_height, SFC_ACTION_MIN_SIZE[1], self.Size[1]))
            self.MinSize = max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
                               SFC_ACTION_MIN_SIZE[0]), max(SFC_ACTION_MIN_SIZE[1], min_height)
            self.RefreshBoundingBox()
        else:
            self.Size = wx.Size(max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
                                    SFC_ACTION_MIN_SIZE[0]),
                                len(self.Actions) * SFC_ACTION_MIN_SIZE[1])
            self.MinSize = max(self.ColSize[0] + self.ColSize[1] + self.ColSize[2],
                               SFC_ACTION_MIN_SIZE[0]), len(self.Actions) * SFC_ACTION_MIN_SIZE[1]
            self.RefreshBoundingBox()
            if self.Input is not None:
                wires = self.Input.GetWires()
                if len(wires) == 1:
                    input_block = wires[0][0].GetOtherConnected(self.Input).GetParentBlock()
                    input_block.RefreshOutputPosition()
                    input_block.RefreshOutputModel(True)

    # Returns the action block actions
    def GetActions(self):
        return self.Actions

    # Returns the action block minimum size
    def GetMinSize(self):
        return self.MinSize

    # Method called when a LeftDClick event have been generated
    def OnLeftDClick(self, event, dc, scaling):
        # Edit the action block properties
        self.Parent.EditActionBlockContent(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 action block state according to move defined and handle selected
    def ProcessDragging(self, movex, movey, event, scaling):
        if self.Parent.GetDrawingMode() != FREEDRAWING_MODE:
            handle_type, _handle = self.Handle
            if handle_type == HANDLE_MOVE:
                movex = max(-self.BoundingBox.x, movex)
                if scaling is not None:
                    movex = round((self.Pos.x + movex) / scaling[0]) * scaling[0] - self.Pos.x
                wires = self.Input.GetWires()
                if len(wires) == 1:
                    input_pos = wires[0][0].GetOtherConnected(self.Input).GetPosition(False)
                    if self.Pos.x - input_pos.x + movex >= SFC_WIRE_MIN_SIZE:
                        self.Move(movex, 0)
                        return movex, 0
                return 0, 0
            else:
                return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
        else:
            return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)

    # Refreshes the action block model
    def RefreshModel(self, move=True):
        self.Parent.RefreshActionBlockModel(self)

    # Adds an highlight to the variable
    def AddHighlight(self, infos, start, end, highlight_type):
        if infos[0] == "action" and infos[1] < len(self.Actions):
            action_highlights = self.Highlights.setdefault(infos[1], {})
            attribute_highlights = action_highlights.setdefault(infos[2], [])
            AddHighlight(attribute_highlights, (start, end, highlight_type))

    # Removes an highlight from the block
    def RemoveHighlight(self, infos, start, end, highlight_type):
        if infos[0] == "action" and infos[1] < len(self.Actions):
            action_highlights = self.Highlights.get(infos[1], {})
            attribute_highlights = action_highlights.setdefault(infos[2], [])
            if RemoveHighlight(attribute_highlights, (start, end, highlight_type)) and len(attribute_highlights) == 0:
                action_highlights.pop(infos[2])
                if len(action_highlights) == 0:
                    self.Highlights.pop(infos[1])

    # 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 number, action_highlights in highlight_items:
                action_highlight_items = action_highlights.items()
                for name, attribute_highlights in action_highlight_items:
                    attribute_highlights = ClearHighlights(attribute_highlights, highlight_type)
                    if len(attribute_highlights) == 0:
                        action_highlights.pop(name)
                if len(action_highlights) == 0:
                    self.Highlights.pop(number)

    # Draws divergence
    def Draw(self, dc):
        Graphic_Element.Draw(self, dc)
        if self.Value:
            dc.SetPen(MiterPen(wx.GREEN))
        else:
            dc.SetPen(MiterPen(wx.BLACK))
        dc.SetBrush(wx.WHITE_BRUSH)
        colsize = [self.ColSize[0], self.Size[0] - self.ColSize[0] - self.ColSize[2], self.ColSize[2]]
        # Draw plain rectangle for representing the action block
        dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
        dc.DrawLine(self.Pos.x + colsize[0], self.Pos.y,
                    self.Pos.x + colsize[0], self.Pos.y + self.Size[1])
        dc.DrawLine(self.Pos.x + colsize[0] + colsize[1], self.Pos.y,
                    self.Pos.x + colsize[0] + colsize[1], self.Pos.y + self.Size[1])
        line_size = self.GetLineSize()
        for i, action in enumerate(self.Actions):
            if i != 0:
                dc.DrawLine(self.Pos.x, self.Pos.y + i * line_size,
                            self.Pos.x + self.Size[0], self.Pos.y + i * line_size)
            qualifier_size = dc.GetTextExtent(action.qualifier)
            if action.duration != "":
                qualifier_pos = (self.Pos.x + (colsize[0] - qualifier_size[0]) // 2,
                                 self.Pos.y + i * line_size + line_size // 2 - qualifier_size[1])
                duration_size = dc.GetTextExtent(action.duration)
                duration_pos = (self.Pos.x + (colsize[0] - duration_size[0]) // 2,
                                self.Pos.y + i * line_size + line_size // 2)
                dc.DrawText(action.duration, duration_pos[0], duration_pos[1])
            else:
                qualifier_pos = (self.Pos.x + (colsize[0] - qualifier_size[0]) // 2,
                                 self.Pos.y + i * line_size + (line_size - qualifier_size[1]) // 2)
            dc.DrawText(action.qualifier, qualifier_pos[0], qualifier_pos[1])
            content_size = dc.GetTextExtent(action.value)
            content_pos = (self.Pos.x + colsize[0] + (colsize[1] - content_size[0]) // 2,
                           self.Pos.y + i * line_size + (line_size - content_size[1]) // 2)
            dc.DrawText(action.value, content_pos[0], content_pos[1])
            if action.indicator != "":
                indicator_size = dc.GetTextExtent(action.indicator)
                indicator_pos = (self.Pos.x + colsize[0] + colsize[1] + (colsize[2] - indicator_size[0]) // 2,
                                 self.Pos.y + i * line_size + (line_size - indicator_size[1]) // 2)
                dc.DrawText(action.indicator, indicator_pos[0], indicator_pos[1])

            if not getattr(dc, "printing", False):
                action_highlights = self.Highlights.get(i, {})
                for name, attribute_highlights in action_highlights.iteritems():
                    if name == "qualifier":
                        DrawHighlightedText(dc, action.qualifier, attribute_highlights, qualifier_pos[0], qualifier_pos[1])
                    elif name == "duration":
                        DrawHighlightedText(dc, action.duration, attribute_highlights, duration_pos[0], duration_pos[1])
                    elif name in ["reference", "inline"]:
                        DrawHighlightedText(dc, action.value, attribute_highlights, content_pos[0], content_pos[1])
                    elif name == "indicator":
                        DrawHighlightedText(dc, action.indicator, attribute_highlights, indicator_pos[0], indicator_pos[1])

        # Draw input connector
        self.Input.Draw(dc)