Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
Laurent@814: #
andrej@1571: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
Laurent@814: #
andrej@1571: # See COPYING file for copyrights details.
Laurent@814: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
Laurent@814: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
Laurent@814: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Laurent@814: 
andrej@1832: 
andrej@1881: from __future__ import absolute_import
andrej@2437: from __future__ import division
Laurent@814: from math import *
andrej@2457: from future.builtins import round
andrej@2450: from six import string_types
andrej@2432: from six.moves import xrange
Laurent@814: 
andrej@1832: import wx
Laurent@1170: from graphics.ToolTipProducer import ToolTipProducer
Laurent@1176: from graphics.DebugDataConsumer import DebugDataConsumer
Laurent@1169: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                               Common constants
andrej@1837: #
andrej@1837: #            Definition of constants for dimensions of graphic elements
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: # FBD and SFC constants
Laurent@814: MIN_MOVE = 5                            # Minimum move before starting a element dragging
Laurent@814: CONNECTOR_SIZE = 8                      # Size of connectors
Laurent@814: BLOCK_LINE_SIZE = 20                    # Minimum size of each line in a block
Laurent@814: HANDLE_SIZE = 6                         # Size of the squares for handles
Laurent@814: ANCHOR_DISTANCE = 5                     # Distance where wire is automativally attached to a connector
Laurent@814: POINT_RADIUS = 2                        # Radius of the point of wire ends
Laurent@814: MIN_SEGMENT_SIZE = 2                    # Minimum size of the endling segments of a wire
Laurent@814: 
Laurent@814: # LD constants
Laurent@814: LD_LINE_SIZE = 40                       # Distance between two lines in a ladder rung
Laurent@814: LD_ELEMENT_SIZE = (21, 15)              # Size (width, height) of a ladder element (contact or coil)
Laurent@814: LD_WIRE_SIZE = 30                       # Size of a wire between two contact
Laurent@814: LD_WIRECOIL_SIZE = 70                   # Size of a wire between a coil and a contact
Laurent@814: LD_POWERRAIL_WIDTH = 3                  # Width of a Powerrail
Laurent@814: LD_OFFSET = (10, 10)                    # Distance (x, y) between each comment and rung of the ladder
Laurent@814: LD_COMMENT_DEFAULTSIZE = (600, 40)      # Size (width, height) of a comment box
Laurent@814: 
Laurent@814: # SFC constants
Laurent@814: SFC_STEP_DEFAULT_SIZE = (40, 30)        # Default size of a SFC step
Laurent@814: SFC_TRANSITION_SIZE = (20, 2)           # Size of a SFC transition
Laurent@814: SFC_DEFAULT_SEQUENCE_INTERVAL = 40      # Default size of the interval between two divergence branches
Laurent@814: SFC_SIMULTANEOUS_SEQUENCE_EXTRA = 20    # Size of extra lines for simultaneous divergence and convergence
Laurent@814: SFC_JUMP_SIZE = (12, 13)                # Size of a SFC jump to step
Laurent@814: SFC_WIRE_MIN_SIZE = 25                  # Size of a wire between two elements
Laurent@814: SFC_ACTION_MIN_SIZE = (100, 30)         # Minimum size of an action block line
Laurent@814: 
Laurent@814: # Type definition constants for graphic elements
Laurent@814: [INPUT, OUTPUT, INOUT] = range(3)
Laurent@814: [CONNECTOR, CONTINUATION] = range(2)
Laurent@814: [LEFTRAIL, RIGHTRAIL] = range(2)
Laurent@814: [CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = range(4)
Laurent@814: [COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET, COIL_RISING, COIL_FALLING] = range(6)
Laurent@814: [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = range(4)
Laurent@814: 
Laurent@814: # Constants for defining the type of dragging that has been selected
Laurent@814: [HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = range(5)
Laurent@814: 
Laurent@814: # List of value for resize handle that are valid
andrej@1740: VALID_HANDLES = [(1, 1), (1, 2), (1, 3), (2, 3), (3, 3), (3, 2), (3, 1), (2, 1)]
Laurent@814: 
Laurent@814: # Contants for defining the direction of a connector
andrej@1740: [EAST, NORTH, WEST, SOUTH] = [(1, 0), (0, -1), (-1, 0), (0, 1)]
Laurent@814: 
andrej@1730: # Contants for defining which mode is selected for each view
andrej@1730: [MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT,
andrej@1730:  MODE_COIL, MODE_CONTACT, MODE_POWERRAIL, MODE_INITIALSTEP, MODE_STEP,
Laurent@814:  MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = range(15)
Laurent@814: 
andrej@1730: # Contants for defining alignment types for graphic group
Laurent@814: [ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = range(6)
Laurent@814: 
Laurent@814: # Contants for defining which drawing mode is selected for app
Laurent@814: [FREEDRAWING_MODE, DRIVENDRAWING_MODE] = [1, 2]
Laurent@814: 
Laurent@814: # Color for Highlighting
Laurent@814: HIGHLIGHTCOLOR = wx.CYAN
Laurent@814: 
Laurent@814: # Define highlight types
Laurent@814: ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0), wx.RED)
Laurent@814: SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0), wx.WHITE)
Laurent@814: 
Laurent@814: # Define highlight refresh inhibition period in second
Laurent@814: REFRESH_HIGHLIGHT_PERIOD = 0.1
Laurent@814: 
Laurent@814: HANDLE_CURSORS = {
andrej@1739:     (1, 1): 2,
andrej@1739:     (3, 3): 2,
andrej@1739:     (1, 3): 3,
andrej@1739:     (3, 1): 3,
andrej@1739:     (1, 2): 4,
andrej@1739:     (3, 2): 4,
andrej@1739:     (2, 1): 5,
andrej@1739:     (2, 3): 5
Laurent@814: }
Laurent@814: 
andrej@1736: 
Laurent@814: def round_scaling(x, n, constraint=0):
andrej@2437:     fraction = x / n
Laurent@852:     if constraint == -1:
Laurent@814:         xround = int(fraction)
Laurent@814:     else:
Laurent@814:         xround = round(fraction)
Laurent@852:         if constraint == 1 and xround < fraction:
andrej@1730:             xround += 1
Laurent@852:     return int(xround * n)
Laurent@814: 
andrej@1749: 
andrej@1837: # -------------------------------------------------------------------------------
andrej@1837: # Basic vector operations for calculate wire points
andrej@1837: # -------------------------------------------------------------------------------
andrej@1736: 
andrej@1744: def vector(p1, p2, normal=True):
andrej@1736:     """
andrej@1736:     Create a vector from two points and define if vector must be normal
andrej@1736:     """
Laurent@814:     vector = (p2.x - p1.x, p2.y - p1.y)
Laurent@814:     if normal:
Laurent@814:         return normalize(vector)
Laurent@814:     return vector
Laurent@814: 
andrej@1736: 
Laurent@814: def norm(v):
andrej@1736:     """
andrej@1736:     Calculate the norm of a given vector
andrej@1736:     """
Laurent@814:     return sqrt(v[0] * v[0] + v[1] * v[1])
Laurent@814: 
andrej@1736: 
Laurent@814: def normalize(v):
andrej@1736:     """
andrej@1736:     Normalize a given vector
andrej@1736:     """
Laurent@814:     v_norm = norm(v)
Laurent@814:     # Verifie if it is not a null vector
Laurent@814:     if v_norm > 0:
Laurent@814:         return (v[0] / v_norm, v[1] / v_norm)
Laurent@814:     else:
Laurent@814:         return v
Laurent@814: 
andrej@1736: 
Laurent@814: def is_null_vector(v):
andrej@1736:     """
andrej@1736:     Calculate the scalar product of two vectors
andrej@1736:     """
Laurent@814:     return v == (0, 0)
Laurent@814: 
andrej@1736: 
Laurent@814: def add_vectors(v1, v2):
andrej@1736:     """
andrej@1736:     Calculate the scalar product of two vectors
andrej@1736:     """
Laurent@814:     return (v1[0] + v2[0], v1[1] + v2[1])
Laurent@814: 
andrej@1736: 
Laurent@814: def product(v1, v2):
andrej@1736:     """
andrej@1736:     Calculate the scalar product of two vectors
andrej@1736:     """
Laurent@814:     return v1[0] * v2[0] + v1[1] * v2[1]
Laurent@814: 
Laurent@814: 
Laurent@814: def GetScaledEventPosition(event, dc, scaling):
andrej@1736:     """
andrej@1736:     Function that calculates the nearest point of the grid defined by scaling for the given point
andrej@1736:     """
Laurent@814:     pos = event.GetLogicalPosition(dc)
Laurent@814:     if scaling:
andrej@2437:         pos.x = round(pos.x / scaling[0]) * scaling[0]
andrej@2437:         pos.y = round(pos.y / scaling[1]) * scaling[1]
Laurent@814:     return pos
Laurent@814: 
Laurent@814: 
Laurent@814: def DirectionChoice(v_base, v_target, dir_target):
andrej@1736:     """
andrej@1736:     Function that choose a direction during the wire points generation
andrej@1736:     """
Laurent@814:     dir_product = product(v_base, v_target)
Laurent@814:     if dir_product < 0:
Laurent@814:         return (-v_base[0], -v_base[1])
Laurent@814:     elif dir_product == 0 and product(v_base, dir_target) != 0:
Laurent@814:         return dir_target
Laurent@814:     return v_base
Laurent@814: 
andrej@1736: 
Laurent@814: def MiterPen(colour, width=1, style=wx.SOLID):
Laurent@814:     pen = wx.Pen(colour, width, style)
Laurent@814:     pen.SetJoin(wx.JOIN_MITER)
Laurent@814:     pen.SetCap(wx.CAP_PROJECTING)
Laurent@814:     return pen
Laurent@814: 
andrej@1782: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                    Helpers for highlighting text
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
andrej@1736: 
Laurent@814: def AddHighlight(highlights, infos):
Laurent@814:     RemoveHighlight(highlights, infos)
Laurent@814:     highlights.append(infos)
Laurent@814: 
andrej@1736: 
Laurent@814: def RemoveHighlight(highlights, infos):
Laurent@814:     if infos in highlights:
Laurent@814:         highlights.remove(infos)
Laurent@814:         return True
Laurent@814:     return False
Laurent@814: 
andrej@1736: 
andrej@1872: def ClearHighlights(highlights, highlight_type=None):
Laurent@814:     if highlight_type is not None:
Laurent@814:         return [highlight for highlight in highlights if highlight[2] != highlight_type]
Laurent@814:     return []
Laurent@814: 
andrej@1736: 
Laurent@814: def DrawHighlightedText(dc, text, highlights, x, y):
Laurent@814:     current_pen = dc.GetPen()
Laurent@814:     dc.SetPen(wx.TRANSPARENT_PEN)
Laurent@814:     for start, end, highlight_type in highlights:
Laurent@814:         dc.SetBrush(wx.Brush(highlight_type[0]))
andrej@1847:         offset_width, _offset_height = dc.GetTextExtent(text[:start[1]])
Laurent@814:         part = text[start[1]:end[1] + 1]
Laurent@814:         part_width, part_height = dc.GetTextExtent(part)
Laurent@814:         dc.DrawRectangle(x + offset_width, y, part_width, part_height)
Laurent@814:         dc.SetTextForeground(highlight_type[1])
Laurent@814:         dc.DrawText(part, x + offset_width, y)
Laurent@814:     dc.SetPen(current_pen)
Laurent@814:     dc.SetTextForeground(wx.BLACK)
andrej@1730: 
andrej@1782: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                           Graphic element base class
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@1170: class Graphic_Element(ToolTipProducer):
andrej@1736:     """
andrej@1736:     Class that implements a generic graphic element
andrej@1736:     """
andrej@1730: 
Laurent@814:     # Create a new graphic element
andrej@1744:     def __init__(self, parent, id=None):
Laurent@1170:         ToolTipProducer.__init__(self, parent)
Laurent@814:         self.Parent = parent
Laurent@814:         self.Id = id
Laurent@814:         self.oldPos = None
Laurent@814:         self.StartPos = None
Laurent@814:         self.CurrentDrag = None
andrej@1740:         self.Handle = (None, None)
Laurent@814:         self.Dragging = False
Laurent@814:         self.Selected = False
Laurent@814:         self.Highlighted = False
Laurent@814:         self.Pos = wx.Point(0, 0)
Laurent@814:         self.Size = wx.Size(0, 0)
Laurent@814:         self.BoundingBox = wx.Rect(0, 0, 0, 0)
Laurent@814:         self.Visible = False
andrej@1730: 
Laurent@814:     def GetDefinition(self):
Laurent@814:         return [self.Id], []
andrej@1730: 
Laurent@814:     def TestVisible(self, screen):
Laurent@852:         self.Visible = self.Selected or self.GetRedrawRect().Intersects(screen)
andrej@1730: 
Laurent@814:     def IsVisible(self):
Laurent@814:         return self.Visible
andrej@1730: 
Laurent@814:     def SpreadCurrent(self):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     def GetConnectorTranslation(self, element):
Laurent@814:         return {}
andrej@1730: 
Laurent@814:     def FindNearestConnector(self, position, connectors):
Laurent@814:         distances = []
Laurent@814:         for connector in connectors:
Laurent@814:             connector_pos = connector.GetRelPosition()
Laurent@814:             distances.append((sqrt((self.Pos.x + connector_pos.x - position.x) ** 2 +
Laurent@814:                                    (self.Pos.y + connector_pos.y - position.y) ** 2),
Laurent@814:                               connector))
Laurent@814:         distances.sort()
Laurent@814:         if len(distances) > 0:
Laurent@814:             return distances[0][1]
Laurent@814:         return None
andrej@1730: 
Laurent@814:     def IsOfType(self, type, reference):
Laurent@814:         return self.Parent.IsOfType(type, reference)
andrej@1730: 
Laurent@814:     def IsEndType(self, type):
Laurent@814:         return self.Parent.IsEndType(type)
andrej@1730: 
Laurent@814:     def GetDragging(self):
Laurent@814:         return self.Dragging
andrej@1730: 
Laurent@814:     # Make a clone of this element
Laurent@814:     def Clone(self, parent):
Laurent@814:         return Graphic_Element(parent, self.Id)
andrej@1730: 
Laurent@814:     # Changes the block position
Laurent@814:     def SetPosition(self, x, y):
Laurent@814:         self.Pos.x = x
Laurent@814:         self.Pos.y = y
Laurent@814:         self.RefreshConnected()
Laurent@814:         self.RefreshBoundingBox()
Laurent@814: 
Laurent@814:     # Returns the block position
Laurent@814:     def GetPosition(self):
Laurent@814:         return self.Pos.x, self.Pos.y
andrej@1730: 
Laurent@814:     # Changes the element size
Laurent@814:     def SetSize(self, width, height):
Laurent@814:         self.Size.SetWidth(width)
Laurent@814:         self.Size.SetHeight(height)
Laurent@814:         self.RefreshConnectors()
Laurent@814:         self.RefreshBoundingBox()
Laurent@814: 
Laurent@814:     # Returns the element size
Laurent@814:     def GetSize(self):
Laurent@814:         return self.Size.GetWidth(), self.Size.GetHeight()
andrej@1730: 
Laurent@814:     # Returns the minimum element size
Laurent@814:     def GetMinSize(self):
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@852:     # Set size of the element to the minimum size
Laurent@852:     def SetBestSize(self, scaling, x_factor=0.5, y_factor=0.5):
Laurent@852:         width, height = self.GetSize()
Laurent@852:         posx, posy = self.GetPosition()
Laurent@852:         min_width, min_height = self.GetMinSize()
Laurent@852:         if width < min_width:
Laurent@852:             self.Pos.x = max(0, self.Pos.x - (width - min_width) * x_factor)
Laurent@852:             width = min_width
Laurent@852:         if height < min_height:
Laurent@852:             self.Pos.y = max(0, self.Pos.y - (height - min_height) * y_factor)
Laurent@852:             height = min_height
Laurent@852:         if scaling is not None:
Laurent@852:             self.Pos.x = round_scaling(self.Pos.x, scaling[0])
Laurent@852:             self.Pos.y = round_scaling(self.Pos.y, scaling[1])
Laurent@852:             width = round_scaling(width, scaling[0], 1)
Laurent@852:             height = round_scaling(height, scaling[1], 1)
Laurent@852:         self.SetSize(width, height)
Laurent@852:         return self.Pos.x - posx, self.Pos.y - posy
andrej@1730: 
Laurent@814:     # Refresh the element connectors position
Laurent@814:     def RefreshConnectors(self):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Refresh the position of wires connected to element inputs and outputs
andrej@1870:     def RefreshConnected(self, exclude=None):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Change the parent
Laurent@814:     def SetParent(self, parent):
Laurent@814:         self.Parent = parent
andrej@1730: 
Laurent@814:     # Override this method for defining the method to call for deleting this element
Laurent@814:     def Delete(self):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Returns the Id
Laurent@814:     def GetId(self):
Laurent@814:         return self.Id
andrej@1730: 
Laurent@814:     # Returns if the point given is in the bounding box
Laurent@814:     def HitTest(self, pt, connectors=True):
Laurent@814:         if connectors:
Laurent@814:             rect = self.BoundingBox
Laurent@814:         else:
Laurent@814:             rect = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
Laurent@814:         return rect.InsideXY(pt.x, pt.y)
andrej@1730: 
Laurent@814:     # Returns if the point given is in the bounding box
Laurent@814:     def IsInSelection(self, rect):
Laurent@814:         return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
andrej@1730: 
Laurent@814:     # Override this method for refreshing the bounding box
Laurent@814:     def RefreshBoundingBox(self):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Returns the bounding box
Laurent@814:     def GetBoundingBox(self):
Laurent@814:         return self.BoundingBox
andrej@1730: 
Laurent@814:     # Returns the RedrawRect
andrej@1744:     def GetRedrawRect(self, movex=0, movey=0):
Laurent@814:         scalex, scaley = self.Parent.GetViewScale()
Laurent@814:         rect = wx.Rect()
Laurent@814:         rect.x = self.BoundingBox.x - int(HANDLE_SIZE / scalex) - 3 - abs(movex)
Laurent@814:         rect.y = self.BoundingBox.y - int(HANDLE_SIZE / scaley) - 3 - abs(movey)
Laurent@814:         rect.width = self.BoundingBox.width + 2 * (int(HANDLE_SIZE / scalex) + abs(movex) + 1) + 4
Laurent@814:         rect.height = self.BoundingBox.height + 2 * (int(HANDLE_SIZE / scaley) + abs(movey) + 1) + 4
Laurent@814:         return rect
andrej@1730: 
andrej@1744:     def Refresh(self, rect=None):
Laurent@814:         if self.Visible:
Laurent@814:             if rect is not None:
Laurent@814:                 self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
Laurent@814:             else:
Laurent@814:                 self.Parent.RefreshRect(self.Parent.GetScrolledRect(self.GetRedrawRect()), False)
andrej@1730: 
Laurent@814:     # Change the variable that indicates if this element is selected
Laurent@814:     def SetSelected(self, selected):
Laurent@814:         self.Selected = selected
Laurent@814:         self.Refresh()
andrej@1730: 
Laurent@814:     # Change the variable that indicates if this element is highlighted
Laurent@814:     def SetHighlighted(self, highlighted):
Laurent@814:         self.Highlighted = highlighted
Laurent@814:         self.Refresh()
andrej@1730: 
Laurent@814:     # Test if the point is on a handle of this element
Laurent@814:     def TestHandle(self, event):
Laurent@814:         dc = self.Parent.GetLogicalDC()
Laurent@814:         scalex, scaley = dc.GetUserScale()
Laurent@814:         pos = event.GetPosition()
Laurent@814:         pt = wx.Point(*self.Parent.CalcUnscrolledPosition(pos.x, pos.y))
andrej@1730: 
Laurent@814:         left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
andrej@2437:         center = (self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2
Laurent@814:         right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
andrej@1730: 
Laurent@814:         top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
andrej@2437:         middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE // 2
Laurent@814:         bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
andrej@1730: 
Laurent@814:         extern_rect = wx.Rect(left, top, right + HANDLE_SIZE - left, bottom + HANDLE_SIZE - top)
Laurent@814:         intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
andrej@1730: 
Laurent@814:         # Verify that this element is selected
Laurent@814:         if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
Laurent@814:             # Find if point is on a handle horizontally
Laurent@814:             if left <= pt.x < left + HANDLE_SIZE:
Laurent@814:                 handle_x = 1
Laurent@814:             elif center <= pt.x < center + HANDLE_SIZE:
Laurent@814:                 handle_x = 2
Laurent@814:             elif right <= pt.x < right + HANDLE_SIZE:
Laurent@814:                 handle_x = 3
Laurent@814:             else:
Laurent@814:                 handle_x = 0
Laurent@814:             # Find if point is on a handle vertically
Laurent@814:             if top <= pt.y < top + HANDLE_SIZE:
Laurent@814:                 handle_y = 1
Laurent@814:             elif middle <= pt.y < middle + HANDLE_SIZE:
Laurent@814:                 handle_y = 2
Laurent@814:             elif bottom <= pt.y < bottom + HANDLE_SIZE:
Laurent@814:                 handle_y = 3
Laurent@814:             else:
Laurent@814:                 handle_y = 0
Laurent@814:             # Verify that the result is valid
Laurent@814:             if (handle_x, handle_y) in VALID_HANDLES:
Laurent@814:                 return handle_x, handle_y
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@814:     # Method called when a LeftDown event have been generated
Laurent@814:     def OnLeftDown(self, event, dc, scaling):
Laurent@814:         # Test if an handle have been clicked
Laurent@814:         handle = self.TestHandle(event)
Laurent@814:         # Find which type of handle have been clicked,
Laurent@814:         # Save a resize event and change the cursor
Laurent@814:         cursor = HANDLE_CURSORS.get(handle, 1)
Laurent@814:         wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
Laurent@814:         if cursor > 1:
Laurent@814:             self.Handle = (HANDLE_RESIZE, handle)
Laurent@814:         else:
Laurent@814:             self.Handle = (HANDLE_MOVE, None)
Laurent@814:             self.SetSelected(False)
Laurent@814:         # Initializes the last position
Laurent@814:         self.oldPos = GetScaledEventPosition(event, dc, scaling)
Laurent@814:         self.StartPos = wx.Point(self.Pos.x, self.Pos.y)
Laurent@814:         self.CurrentDrag = wx.Point(0, 0)
andrej@1730: 
Laurent@814:     # Method called when a LeftUp event have been generated
Laurent@814:     def OnLeftUp(self, event, dc, scaling):
Laurent@814:         # If a dragging have been initiated
Laurent@814:         if self.Dragging and self.oldPos:
Laurent@814:             self.RefreshModel()
Laurent@814:             self.Parent.RefreshBuffer()
Laurent@814:         wx.CallAfter(self.Parent.SetCurrentCursor, 0)
Laurent@814:         self.SetSelected(True)
Laurent@814:         self.oldPos = None
Laurent@814: 
Laurent@814:     # Method called when a RightDown event have been generated
Laurent@814:     def OnRightDown(self, event, dc, scaling):
Laurent@814:         pass
Laurent@814: 
Laurent@814:     # Method called when a RightUp event have been generated
Laurent@814:     def OnRightUp(self, event, dc, scaling):
Laurent@814:         if self.Dragging and self.oldPos:
Laurent@814:             self.RefreshModel()
Laurent@814:             self.Parent.RefreshBuffer()
Laurent@814:         wx.CallAfter(self.Parent.SetCurrentCursor, 0)
Laurent@814:         self.SetSelected(True)
Laurent@814:         self.oldPos = None
Laurent@814:         if self.Parent.Debug:
Laurent@814:             self.Parent.PopupForceMenu()
Laurent@814: 
Laurent@814:     # Method called when a LeftDClick event have been generated
Laurent@814:     def OnLeftDClick(self, event, dc, scaling):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Method called when a Motion event have been generated
Laurent@814:     def OnMotion(self, event, dc, scaling):
Laurent@814:         # If the cursor is dragging and the element have been clicked
Laurent@814:         if event.Dragging() and self.oldPos:
Laurent@814:             # Calculate the movement of cursor
Laurent@1258:             pos = GetScaledEventPosition(event, dc, scaling)
Laurent@814:             movex = pos.x - self.oldPos.x
Laurent@814:             movey = pos.y - self.oldPos.y
Laurent@814:             # If movement is greater than MIN_MOVE then a dragging is initiated
Laurent@814:             if not self.Dragging and (abs(movex) > MIN_MOVE or abs(movey) > MIN_MOVE):
Laurent@814:                 self.Dragging = True
Laurent@814:             # If a dragging have been initiated, refreshes the element state
Laurent@814:             if self.Dragging:
Laurent@814:                 dragx, dragy = self.ProcessDragging(movex, movey, event, scaling)
Laurent@814:                 if event.ControlDown() and self.Handle[0] == HANDLE_MOVE:
Laurent@814:                     self.oldPos.x = self.StartPos.x + self.CurrentDrag.x
Laurent@814:                     self.oldPos.y = self.StartPos.y + self.CurrentDrag.y
Laurent@814:                 else:
Laurent@814:                     self.oldPos.x += dragx
Laurent@814:                     self.oldPos.y += dragy
Laurent@814:                 return dragx, dragy
Laurent@814:             return movex, movey
Laurent@814:         # If cursor just pass over the element, changes the cursor if it is on a handle
Laurent@814:         else:
Laurent@814:             pos = event.GetLogicalPosition(dc)
Laurent@814:             handle = self.TestHandle(event)
Laurent@814:             # Find which type of handle have been clicked,
Laurent@814:             # Save a resize event and change the cursor
Laurent@814:             cursor = HANDLE_CURSORS.get(handle, 0)
Laurent@814:             wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
Laurent@814:             return 0, 0
Laurent@814: 
Laurent@814:     # Moves the element
andrej@1852:     def Move(self, dx, dy, exclude=None):
Laurent@814:         self.Pos.x += max(-self.BoundingBox.x, dx)
Laurent@814:         self.Pos.y += max(-self.BoundingBox.y, dy)
Laurent@814:         self.RefreshConnected(exclude)
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Resizes the element from position and size given
Laurent@814:     def Resize(self, x, y, width, height):
Laurent@814:         self.Move(x, y)
Laurent@814:         self.SetSize(width, height)
andrej@1730: 
Laurent@814:     # Refreshes the element state according to move defined and handle selected
andrej@1744:     def ProcessDragging(self, movex, movey, event, scaling, width_fac=1, height_fac=1):
Laurent@814:         handle_type, handle = self.Handle
Laurent@814:         # If it is a resize handle, calculate the values from resizing
Laurent@814:         if handle_type == HANDLE_RESIZE:
Laurent@814:             if scaling is not None:
Laurent@814:                 scaling = (scaling[0] * width_fac, scaling[1] * height_fac)
Laurent@814:             x = y = start_x = start_y = 0
Laurent@814:             width, height = start_width, start_height = self.GetSize()
Laurent@814:             if handle[0] == 1:
Laurent@814:                 movex = max(-self.BoundingBox.x, movex)
Laurent@814:                 if scaling is not None:
Laurent@814:                     movex = -(round_scaling(width - movex, scaling[0]) - width)
Laurent@814:                 x = movex
Laurent@814:                 if event.ShiftDown():
Laurent@814:                     width -= 2 * movex
Laurent@814:                 else:
Laurent@814:                     width -= movex
Laurent@814:             elif handle[0] == 3:
Laurent@814:                 if scaling is not None:
Laurent@814:                     movex = round_scaling(width + movex, scaling[0]) - width
Laurent@814:                 if event.ShiftDown():
Laurent@814:                     movex = min(self.BoundingBox.x, movex)
Laurent@814:                     x = -movex
Laurent@814:                     width += 2 * movex
Laurent@814:                 else:
Laurent@814:                     width += movex
Laurent@814:             if handle[1] == 1:
Laurent@814:                 movey = max(-self.BoundingBox.y, movey)
Laurent@814:                 if scaling is not None:
Laurent@814:                     movey = -(round_scaling(height - movey, scaling[1]) - height)
Laurent@814:                 y = movey
Laurent@814:                 if event.ShiftDown():
Laurent@814:                     height -= 2 * movey
Laurent@814:                 else:
Laurent@814:                     height -= movey
Laurent@814:             elif handle[1] == 3:
Laurent@814:                 if scaling is not None:
Laurent@814:                     movey = round_scaling(height + movey, scaling[1]) - height
Laurent@814:                 if event.ShiftDown():
Laurent@814:                     movey = min(self.BoundingBox.y, movey)
Laurent@814:                     y = -movey
Laurent@814:                     height += 2 * movey
Laurent@814:                 else:
Laurent@814:                     height += movey
Laurent@814:             # Verify that new size is not lesser than minimum
Laurent@814:             min_width, min_height = self.GetMinSize()
Laurent@814:             if handle[0] != 2 and (width >= min_width or width > self.Size[0]):
Laurent@814:                 start_x = x
Laurent@814:                 start_width = width
Laurent@814:             else:
Laurent@814:                 movex = 0
Laurent@814:             if handle[1] != 2 and (height >= min_height or height > self.Size[1]):
Laurent@814:                 start_y = y
Laurent@814:                 start_height = height
Laurent@814:             else:
Laurent@814:                 movey = 0
Laurent@814:             if movex != 0 or movey != 0:
Laurent@814:                 self.Resize(start_x, start_y, start_width, start_height)
Laurent@814:             return movex, movey
Laurent@814:         # If it is a move handle, Move this element
Laurent@814:         elif handle_type == HANDLE_MOVE:
Laurent@814:             movex = max(-self.BoundingBox.x, movex)
Laurent@814:             movey = max(-self.BoundingBox.y, movey)
Laurent@814:             if scaling is not None:
Laurent@814:                 movex = round_scaling(self.Pos.x + movex, scaling[0]) - self.Pos.x
Laurent@814:                 movey = round_scaling(self.Pos.y + movey, scaling[1]) - self.Pos.y
Laurent@814:             if event.ControlDown():
Laurent@814:                 self.CurrentDrag.x = self.CurrentDrag.x + movex
Laurent@814:                 self.CurrentDrag.y = self.CurrentDrag.y + movey
Laurent@814:                 if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
Laurent@814:                     movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
Laurent@814:                     movey = self.StartPos.y - self.Pos.y
Laurent@814:                 else:
Laurent@814:                     movex = self.StartPos.x - self.Pos.x
andrej@1730:                     movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
Laurent@814:             self.Move(movex, movey)
Laurent@814:             return movex, movey
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@814:     # Override this method for defining the method to call for adding an highlight to this element
Laurent@814:     def AddHighlight(self, infos, start, end, highlight_type):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Override this method for defining the method to call for removing an highlight from this element
Laurent@814:     def RemoveHighlight(self, infos, start, end, highlight_type):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Override this method for defining the method to call for removing all the highlights of one particular type from this element
Laurent@814:     def ClearHighlight(self, highlight_type=None):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Override this method for defining the method to call for refreshing the model of this element
Laurent@814:     def RefreshModel(self, move=True):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Draws the highlightment of this element if it is highlighted (can be overwritten)
Laurent@814:     def DrawHighlightment(self, dc):
Laurent@814:         scalex, scaley = dc.GetUserScale()
Laurent@814:         dc.SetUserScale(1, 1)
Laurent@814:         dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
Laurent@814:         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
Laurent@814:         dc.SetLogicalFunction(wx.AND)
andrej@1730:         dc.DrawRectangle(int(round((self.Pos.x - 1) * scalex)) - 2,
andrej@1730:                          int(round((self.Pos.y - 1) * scaley)) - 2,
andrej@1730:                          int(round((self.Size.width + 3) * scalex)) + 5,
Laurent@814:                          int(round((self.Size.height + 3) * scaley)) + 5)
Laurent@814:         dc.SetLogicalFunction(wx.COPY)
Laurent@814:         dc.SetUserScale(scalex, scaley)
andrej@1730: 
Laurent@814:     # Draws the handles of this element if it is selected
Laurent@814:     def Draw(self, dc):
Laurent@814:         if not getattr(dc, "printing", False):
Laurent@814:             if self.Highlighted:
Laurent@814:                 self.DrawHighlightment(dc)
Laurent@814:             if self.Selected:
Laurent@814:                 scalex, scaley = dc.GetUserScale()
Laurent@814:                 dc.SetUserScale(1, 1)
Laurent@814:                 dc.SetPen(MiterPen(wx.BLACK))
Laurent@814:                 dc.SetBrush(wx.BLACK_BRUSH)
andrej@1730: 
Laurent@814:                 left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
andrej@2437:                 center = (self.BoundingBox.x + self.BoundingBox.width // 2) * scalex - HANDLE_SIZE // 2
Laurent@814:                 right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
andrej@1730: 
Laurent@814:                 top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
andrej@2437:                 middle = (self.BoundingBox.y + self.BoundingBox.height // 2) * scaley - HANDLE_SIZE // 2
Laurent@814:                 bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
andrej@1730: 
Laurent@814:                 for x, y in [(left, top), (center, top), (right, top),
Laurent@814:                              (left, middle), (right, middle),
Laurent@814:                              (left, bottom), (center, bottom), (right, bottom)]:
Laurent@814:                     dc.DrawRectangle(x, y, HANDLE_SIZE, HANDLE_SIZE)
andrej@1730: 
Laurent@814:                 dc.SetUserScale(scalex, scaley)
Laurent@814: 
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                           Group of graphic elements
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@814: class Graphic_Group(Graphic_Element):
andrej@1736:     """
andrej@1736:     Class that implements a group of graphic elements
andrej@1736:     """
andrej@1730: 
Laurent@814:     # Create a new group of graphic elements
Laurent@814:     def __init__(self, parent):
Laurent@814:         Graphic_Element.__init__(self, parent)
Laurent@814:         self.Elements = []
Laurent@814:         self.RefreshWireExclusion()
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Destructor
Laurent@814:     def __del__(self):
Laurent@814:         self.Elements = []
andrej@1730: 
Laurent@814:     def GetDefinition(self):
andrej@1730:         blocks = []
Laurent@814:         wires = []
Laurent@814:         for element in self.Elements:
Laurent@814:             block, wire = element.GetDefinition()
Laurent@814:             blocks.extend(block)
Laurent@814:             wires.extend(wire)
Laurent@814:         return blocks, wires
andrej@1730: 
Laurent@814:     # Make a clone of this element
andrej@1744:     def Clone(self, parent, pos=None):
Laurent@814:         group = Graphic_Group(parent)
Laurent@814:         connectors = {}
Laurent@814:         exclude_names = {}
Laurent@814:         wires = []
Laurent@814:         if pos is not None:
Laurent@814:             dx, dy = pos.x - self.BoundingBox.x, pos.y - self.BoundingBox.y
Laurent@814:         for element in self.Elements:
Laurent@814:             if isinstance(element, Wire):
Laurent@814:                 wires.append(element)
Laurent@814:             else:
Laurent@814:                 if pos is not None:
Laurent@814:                     x, y = element.GetPosition()
Laurent@814:                     new_pos = wx.Point(x + dx, y + dy)
Laurent@814:                     newid = parent.GetNewId()
Laurent@814:                     if parent.IsNamedElement(element):
Laurent@814:                         name = parent.GenerateNewName(element, exclude_names)
Laurent@814:                         exclude_names[name.upper()] = True
andrej@1744:                         new_element = element.Clone(parent, newid, name, pos=new_pos)
Laurent@814:                     else:
andrej@1744:                         new_element = element.Clone(parent, newid, pos=new_pos)
Laurent@852:                     new_element.SetBestSize(parent.Scaling)
Laurent@814:                 else:
Laurent@814:                     new_element = element.Clone(parent)
Laurent@814:                 connectors.update(element.GetConnectorTranslation(new_element))
Laurent@814:                 group.SelectElement(new_element)
Laurent@814:         for element in wires:
Laurent@814:             if pos is not None:
Laurent@814:                 new_wire = element.Clone(parent, connectors, dx, dy)
Laurent@814:             else:
Laurent@814:                 new_wire = element.Clone(parent, connectors)
Laurent@814:             if new_wire is not None:
Laurent@814:                 if pos is not None:
Laurent@814:                     parent.AddWire(new_wire)
Laurent@814:                 group.SelectElement(new_wire)
Laurent@814:         if pos is not None:
Laurent@814:             for element in group.Elements:
Laurent@814:                 if not isinstance(element, Wire):
Laurent@814:                     parent.AddBlockInModel(element)
Laurent@814:         return group
andrej@1730: 
Laurent@814:     def CanAddBlocks(self, parent):
Laurent@814:         valid = True
Laurent@814:         for element in self.Elements:
Laurent@814:             if not isinstance(element, Wire):
Laurent@814:                 valid &= parent.CanAddElement(element)
Laurent@814:         return valid
andrej@1730: 
Laurent@814:     def IsVisible(self):
Laurent@814:         for element in self.Elements:
Laurent@814:             if element.IsVisible():
Laurent@814:                 return True
Laurent@814:         return False
andrej@1730: 
Laurent@814:     # Refresh the list of wire excluded
Laurent@814:     def RefreshWireExclusion(self):
Laurent@814:         self.WireExcluded = []
Laurent@814:         for element in self.Elements:
Laurent@814:             if isinstance(element, Wire):
Laurent@814:                 startblock = element.StartConnected.GetParentBlock()
Laurent@814:                 endblock = element.EndConnected.GetParentBlock()
Laurent@814:                 if startblock in self.Elements and endblock in self.Elements:
Laurent@814:                     self.WireExcluded.append(element)
andrej@1730: 
Laurent@814:     # Returns the RedrawRect
andrej@1744:     def GetRedrawRect(self, movex=0, movey=0):
Laurent@814:         rect = None
Laurent@814:         for element in self.Elements:
Laurent@814:             if rect is None:
Laurent@814:                 rect = element.GetRedrawRect(movex, movey)
Laurent@814:             else:
Laurent@814:                 rect = rect.Union(element.GetRedrawRect(movex, movey))
Laurent@814:         return rect
andrej@1730: 
Laurent@814:     # Clean this group of elements
Laurent@814:     def Clean(self):
Laurent@814:         # Clean all the elements of the group
Laurent@814:         for element in self.Elements:
Laurent@814:             element.Clean()
andrej@1730: 
Laurent@814:     # Delete this group of elements
Laurent@814:     def Delete(self):
Laurent@814:         # Delete all the elements of the group
Laurent@814:         for element in self.Elements:
Laurent@814:             element.Delete()
Laurent@814:         self.WireExcluded = []
andrej@1730: 
Laurent@814:     # Returns if the point given is in the bounding box of one of the elements of this group
Laurent@814:     def HitTest(self, pt, connectors=True):
Laurent@814:         result = False
Laurent@814:         for element in self.Elements:
Laurent@814:             result |= element.HitTest(pt, connectors)
Laurent@814:         return result
andrej@1730: 
Laurent@814:     # Returns if the element given is in this group
Laurent@814:     def IsElementIn(self, element):
Laurent@814:         return element in self.Elements
andrej@1730: 
Laurent@814:     # Change the elements of the group
Laurent@814:     def SetElements(self, elements):
Laurent@814:         self.Elements = elements
Laurent@814:         self.RefreshWireExclusion()
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Returns the elements of the group
Laurent@814:     def GetElements(self):
Laurent@814:         return self.Elements
andrej@1730: 
Laurent@814:     # Align the group elements
Laurent@814:     def AlignElements(self, horizontally, vertically):
Laurent@814:         minx = self.BoundingBox.x + self.BoundingBox.width
Laurent@814:         miny = self.BoundingBox.y + self.BoundingBox.height
Laurent@814:         maxx = self.BoundingBox.x
Laurent@814:         maxy = self.BoundingBox.y
Laurent@814:         for element in self.Elements:
Laurent@814:             if not isinstance(element, Wire):
Laurent@814:                 posx, posy = element.GetPosition()
Laurent@814:                 width, height = element.GetSize()
Laurent@814:                 minx = min(minx, posx)
Laurent@814:                 miny = min(miny, posy)
Laurent@814:                 maxx = max(maxx, posx + width)
Laurent@814:                 maxy = max(maxy, posy + height)
Laurent@814:         for element in self.Elements:
Laurent@814:             if not isinstance(element, Wire):
Laurent@814:                 posx, posy = element.GetPosition()
Laurent@814:                 width, height = element.GetSize()
Laurent@814:                 movex = movey = 0
Laurent@814:                 if horizontally == ALIGN_LEFT:
Laurent@814:                     movex = minx - posx
Laurent@814:                 elif horizontally == ALIGN_CENTER:
andrej@2437:                     movex = (maxx + minx - width) // 2 - posx
Laurent@814:                 elif horizontally == ALIGN_RIGHT:
Laurent@814:                     movex = maxx - width - posx
Laurent@814:                 if vertically == ALIGN_TOP:
Laurent@814:                     movey = miny - posy
Laurent@814:                 elif vertically == ALIGN_MIDDLE:
andrej@2437:                     movey = (maxy + miny - height) // 2 - posy
Laurent@814:                 elif vertically == ALIGN_BOTTOM:
Laurent@814:                     movey = maxy - height - posy
Laurent@814:                 if movex != 0 or movey != 0:
Laurent@814:                     element.Move(movex, movey)
Laurent@814:                     element.RefreshModel()
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@1226:     # Add the given element to the group of elements
Laurent@1226:     def AddElement(self, element):
Laurent@1226:         self.Elements.append(element)
andrej@1730: 
Laurent@814:     # Remove or select the given element if it is or not in the group
Laurent@814:     def SelectElement(self, element):
Laurent@814:         if element in self.Elements:
Laurent@814:             self.Elements.remove(element)
Laurent@814:         else:
Laurent@814:             self.Elements.append(element)
Laurent@814:         self.RefreshWireExclusion()
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Move this group of elements
Laurent@814:     def Move(self, movex, movey):
Laurent@814:         movex = max(-self.BoundingBox.x, movex)
Laurent@814:         movey = max(-self.BoundingBox.y, movey)
Laurent@814:         # Move all the elements of the group
Laurent@814:         for element in self.Elements:
Laurent@814:             if not isinstance(element, Wire):
Laurent@814:                 element.Move(movex, movey, self.WireExcluded)
Laurent@814:             elif element in self.WireExcluded:
Laurent@814:                 element.Move(movex, movey, True)
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Refreshes the bounding box of this group of elements
Laurent@814:     def RefreshBoundingBox(self):
Laurent@814:         if len(self.Elements) > 0:
Laurent@814:             bbox = self.Elements[0].GetBoundingBox()
Laurent@814:             minx, miny = bbox.x, bbox.y
Laurent@814:             maxx = bbox.x + bbox.width
Laurent@814:             maxy = bbox.y + bbox.height
Laurent@814:             for element in self.Elements[1:]:
Laurent@814:                 bbox = element.GetBoundingBox()
Laurent@814:                 minx = min(minx, bbox.x)
Laurent@814:                 miny = min(miny, bbox.y)
Laurent@814:                 maxx = max(maxx, bbox.x + bbox.width)
Laurent@814:                 maxy = max(maxy, bbox.y + bbox.height)
Laurent@814:             self.BoundingBox = wx.Rect(minx, miny, maxx - minx, maxy - miny)
Laurent@814:         else:
Laurent@814:             self.BoundingBox = wx.Rect(0, 0, 0, 0)
Laurent@814:         self.Pos = wx.Point(self.BoundingBox.x, self.BoundingBox.y)
Laurent@814:         self.Size = wx.Size(self.BoundingBox.width, self.BoundingBox.height)
Laurent@814: 
Laurent@814:     # Forbids to change the group position
andrej@1868:     def SetPosition(self, x, y):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Returns the position of this group
laurent@825:     def GetPosition(self, exclude_wires=False):
laurent@825:         if exclude_wires:
laurent@825:             posx = posy = None
laurent@825:             for element in self.Elements:
laurent@825:                 if not isinstance(element, Wire) or element in self.WireExcluded:
laurent@825:                     bbox = element.GetBoundingBox()
laurent@825:                     if posx is None and posy is None:
laurent@825:                         posx, posy = bbox.x, bbox.y
laurent@825:                     else:
laurent@825:                         posx = min(posx, bbox.x)
laurent@825:                         posy = min(posy, bbox.y)
laurent@825:             if posx is None and posy is None:
laurent@825:                 return 0, 0
laurent@825:             return posx, posy
Laurent@814:         return self.BoundingBox.x, self.BoundingBox.y
andrej@1730: 
Laurent@814:     # Forbids to change the group size
andrej@1868:     def SetSize(self, width, height):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Returns the size of this group
Laurent@814:     def GetSize(self):
Laurent@814:         return self.BoundingBox.width, self.BoundingBox.height
andrej@1730: 
Laurent@852:     # Set size of the group elements to their minimum size
Laurent@852:     def SetBestSize(self, scaling):
Laurent@852:         max_movex = max_movey = 0
Laurent@814:         for element in self.Elements:
Laurent@852:             movex, movey = element.SetBestSize(scaling)
Laurent@852:             max_movex = max(max_movex, movex)
Laurent@852:             max_movey = max(max_movey, movey)
Laurent@852:         return max_movex, max_movey
andrej@1730: 
Laurent@814:     # Refreshes the group elements to move defined and handle selected
Laurent@814:     def ProcessDragging(self, movex, movey, event, scaling):
andrej@1847:         handle_type, _handle = self.Handle
Laurent@814:         # If it is a move handle, Move this group elements
Laurent@814:         if handle_type == HANDLE_MOVE:
Laurent@814:             movex = max(-self.BoundingBox.x, movex)
Laurent@814:             movey = max(-self.BoundingBox.y, movey)
Laurent@814:             if scaling is not None:
Laurent@814:                 movex = round_scaling(movex, scaling[0])
Laurent@814:                 movey = round_scaling(movey, scaling[1])
Laurent@814:             if event.ControlDown():
Laurent@814:                 self.CurrentDrag.x = self.CurrentDrag.x + movex
Laurent@814:                 self.CurrentDrag.y = self.CurrentDrag.y + movey
laurent@825:                 posx, posy = self.GetPosition(True)
Laurent@814:                 if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
laurent@825:                     movex = self.StartPos.x + self.CurrentDrag.x - posx
laurent@825:                     movey = self.StartPos.y - posy
Laurent@814:                 else:
laurent@825:                     movex = self.StartPos.x - posx
laurent@825:                     movey = self.StartPos.y + self.CurrentDrag.y - posy
Laurent@814:             self.Move(movex, movey)
Laurent@814:             return movex, movey
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@814:     # Change the variable that indicates if this element is highlighted
Laurent@814:     def SetHighlighted(self, highlighted):
Laurent@814:         for element in self.Elements:
Laurent@814:             element.SetHighlighted(highlighted)
andrej@1730: 
Laurent@814:     def HighlightPoint(self, pos):
Laurent@814:         for element in self.Elements:
Laurent@814:             if isinstance(element, Wire):
Laurent@814:                 element.HighlightPoint(pos)
andrej@1730: 
Laurent@814:     # Method called when a LeftDown event have been generated
Laurent@814:     def OnLeftDown(self, event, dc, scaling):
Laurent@814:         Graphic_Element.OnLeftDown(self, event, dc, scaling)
laurent@825:         self.StartPos = wx.Point(*self.GetPosition(True))
Laurent@814:         for element in self.Elements:
Laurent@814:             element.Handle = self.Handle
Laurent@814: 
Laurent@814:     # Change the variable that indicates if the elemente is selected
Laurent@814:     def SetSelected(self, selected):
Laurent@814:         for element in self.Elements:
Laurent@814:             element.SetSelected(selected)
Laurent@814: 
Laurent@814:     # Method called when a RightUp event has been generated
Laurent@814:     def OnRightUp(self, event, dc, scaling):
Laurent@814:         # Popup the menu with special items for a group
Laurent@814:         self.Parent.PopupGroupMenu()
Laurent@814: 
Laurent@814:     # Refreshes the model of all the elements of this group
Laurent@1047:     def RefreshModel(self, move=True):
Laurent@814:         for element in self.Elements:
Laurent@1047:             element.RefreshModel(move)
Laurent@814: 
Laurent@1069:     # Draws the handles of this element if it is selected
Laurent@1069:     def Draw(self, dc):
Laurent@1069:         for element in self.Elements:
Laurent@1069:             element.Draw(dc)
Laurent@814: 
andrej@1782: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                         Connector for all types of blocks
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@1170: class Connector(DebugDataConsumer, ToolTipProducer):
andrej@1736:     """
andrej@1736:     Class that implements a connector for any type of block
andrej@1736:     """
andrej@1730: 
Laurent@814:     # Create a new connector
andrej@1744:     def __init__(self, parent, name, type, position, direction, negated=False, edge="none", onlyone=False):
Laurent@1166:         DebugDataConsumer.__init__(self)
Laurent@1170:         ToolTipProducer.__init__(self, parent.Parent)
Laurent@814:         self.ParentBlock = parent
Laurent@814:         self.Name = name
Laurent@814:         self.Type = type
Laurent@814:         self.Pos = position
Laurent@814:         self.Direction = direction
Laurent@814:         self.Wires = []
Laurent@814:         if self.ParentBlock.IsOfType("BOOL", type):
Laurent@814:             self.Negated = negated
Laurent@814:             self.Edge = edge
Laurent@814:         else:
Laurent@814:             self.Negated = False
Laurent@814:             self.Edge = "none"
Laurent@814:         self.OneConnected = onlyone
Laurent@814:         self.Valid = True
Laurent@814:         self.Value = None
Laurent@814:         self.Forced = False
Laurent@1166:         self.ValueSize = None
Laurent@1166:         self.ComputedValue = None
Laurent@814:         self.Selected = False
Laurent@814:         self.Highlights = []
Laurent@814:         self.RefreshNameSize()
andrej@1730: 
Laurent@814:     def Flush(self):
Laurent@814:         self.ParentBlock = None
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             wire.Flush()
Laurent@814:         self.Wires = []
andrej@1730: 
Laurent@814:     # Returns the RedrawRect
andrej@1744:     def GetRedrawRect(self, movex=0, movey=0):
Laurent@814:         parent_pos = self.ParentBlock.GetPosition()
Laurent@814:         x = min(parent_pos[0] + self.Pos.x, parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE)
Laurent@814:         y = min(parent_pos[1] + self.Pos.y, parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE)
Laurent@1377:         has_modifier = self.Negated or self.Edge != "none"
Laurent@814:         if self.Direction[0] == 0:
Laurent@1377:             width = 10 if has_modifier else 5
Laurent@814:         else:
Laurent@814:             width = CONNECTOR_SIZE
Laurent@1377:             if self.Edge == "rising" and self.Direction[0] == 1:
Laurent@1377:                 x -= 5
Laurent@1377:                 width += 5
Laurent@814:         if self.Direction[1] == 0:
Laurent@1377:             height = 10 if has_modifier else 5
Laurent@814:         else:
Laurent@814:             height = CONNECTOR_SIZE
Laurent@1377:             if self.Edge == "rising" and self.Direction[1] == 1:
Laurent@1377:                 y -= 5
Laurent@1377:                 height += 5
andrej@1758:         rect = wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
andrej@2450:         if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
Laurent@1166:             self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
Laurent@1166:         if self.ValueSize is not None:
Laurent@1166:             width, height = self.ValueSize
andrej@1777:             rect = rect.Union(
andrej@1777:                 wx.Rect(
andrej@1764:                     parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] +
andrej@2437:                     width * (self.Direction[0] - 1) // 2,
andrej@1764:                     parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] +
andrej@1777:                     height * (self.Direction[1] - 1),
Laurent@1166:                     width, height))
Laurent@1166:         return rect
andrej@1730: 
Laurent@814:     # Change the connector selection
Laurent@814:     def SetSelected(self, selected):
Laurent@814:         self.Selected = selected
andrej@1730: 
Laurent@814:     # Make a clone of the connector
andrej@1744:     def Clone(self, parent=None):
Laurent@814:         if parent is None:
Laurent@814:             parent = self.ParentBlock
Laurent@814:         return Connector(parent, self.Name, self.Type, wx.Point(self.Pos[0], self.Pos[1]),
andrej@1768:                          self.Direction, self.Negated)
andrej@1730: 
Laurent@814:     # Returns the connector parent block
Laurent@814:     def GetParentBlock(self):
Laurent@814:         return self.ParentBlock
andrej@1730: 
Laurent@814:     # Returns the connector type
andrej@1744:     def GetType(self, raw=False):
Laurent@814:         if self.ParentBlock.IsEndType(self.Type) or raw:
Laurent@814:             return self.Type
Laurent@814:         elif (self.Negated or self.Edge != "none") and self.ParentBlock.IsOfType("BOOL", self.Type):
Laurent@814:             return "BOOL"
Laurent@814:         else:
Laurent@814:             return self.ParentBlock.GetConnectionResultType(self, self.Type)
andrej@1730: 
Laurent@814:     # Returns the connector type
Laurent@814:     def GetConnectedType(self):
Laurent@814:         if self.ParentBlock.IsEndType(self.Type):
Laurent@814:             return self.Type
Laurent@814:         elif len(self.Wires) == 1:
Laurent@814:             return self.Wires[0][0].GetOtherConnectedType(self.Wires[0][1])
Laurent@814:         return self.Type
andrej@1730: 
Laurent@814:     # Returns the connector type
Laurent@814:     def GetConnectedRedrawRect(self, movex, movey):
Laurent@814:         rect = None
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             if rect is None:
Laurent@814:                 rect = wire.GetRedrawRect()
Laurent@814:             else:
Laurent@814:                 rect = rect.Union(wire.GetRedrawRect())
Laurent@814:         return rect
andrej@1730: 
Laurent@814:     # Returns if connector type is compatible with type given
Laurent@814:     def IsCompatible(self, type):
Laurent@814:         reference = self.GetType()
Laurent@814:         return self.ParentBlock.IsOfType(type, reference) or self.ParentBlock.IsOfType(reference, type)
andrej@1730: 
Laurent@814:     # Changes the connector name
Laurent@814:     def SetType(self, type):
Laurent@814:         self.Type = type
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             wire.SetValid(wire.IsConnectedCompatible())
andrej@1730: 
Laurent@814:     # Returns the connector name
Laurent@814:     def GetName(self):
Laurent@814:         return self.Name
andrej@1730: 
Laurent@814:     # Changes the connector name
Laurent@814:     def SetName(self, name):
Laurent@814:         self.Name = name
Laurent@814:         self.RefreshNameSize()
Laurent@814: 
Laurent@1166:     def SetForced(self, forced):
Laurent@1166:         if self.Forced != forced:
Laurent@1166:             self.Forced = forced
Laurent@1166:             if self.Visible:
Laurent@1166:                 self.Parent.ElementNeedRefresh(self)
andrej@1730: 
Laurent@1170:     def GetComputedValue(self):
andrej@2450:         if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, bool):
Laurent@1176:             return self.Value
Laurent@1170:         return None
andrej@1730: 
Laurent@1170:     def GetToolTipValue(self):
Laurent@1170:         return self.GetComputedValue()
andrej@1730: 
Laurent@1166:     def SetValue(self, value):
Laurent@1166:         if self.Value != value:
Laurent@1166:             self.Value = value
Laurent@1170:             computed_value = self.GetComputedValue()
Laurent@1170:             if computed_value is not None:
Laurent@1170:                 self.ComputedValue = computed_value
Laurent@1170:                 self.SetToolTipText(self.ComputedValue)
Laurent@1166:                 if len(self.ComputedValue) > 4:
Laurent@1166:                     self.ComputedValue = self.ComputedValue[:4] + "..."
Laurent@1166:             self.ValueSize = None
Laurent@1166:             if self.ParentBlock.Visible:
Laurent@1166:                 self.ParentBlock.Parent.ElementNeedRefresh(self)
andrej@1730: 
Laurent@814:     def RefreshForced(self):
Laurent@814:         self.Forced = False
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             self.Forced |= wire.IsForced()
Laurent@814: 
Laurent@814:     def RefreshValue(self):
Laurent@814:         self.Value = self.ReceivingCurrent()
andrej@1730: 
Laurent@814:     def RefreshValid(self):
Laurent@814:         self.Valid = True
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             self.Valid &= wire.GetValid()
andrej@1730: 
Laurent@814:     def ReceivingCurrent(self):
Laurent@814:         current = False
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             value = wire.GetValue()
andrej@2450:             if current != "undefined" and isinstance(value, bool):
Laurent@814:                 current |= wire.GetValue()
Laurent@814:             elif value == "undefined":
Laurent@814:                 current = "undefined"
Laurent@814:         return current
andrej@1730: 
Laurent@814:     def SpreadCurrent(self, spreading):
andrej@1847:         for wire, _handle in self.Wires:
Laurent@814:             wire.SetValue(spreading)
andrej@1730: 
Laurent@814:     # Changes the connector name size
Laurent@814:     def RefreshNameSize(self):
Laurent@814:         if self.Name != "":
Laurent@814:             self.NameSize = self.ParentBlock.Parent.GetTextExtent(self.Name)
Laurent@814:         else:
Laurent@814:             self.NameSize = 0, 0
andrej@1730: 
Laurent@814:     # Returns the connector name size
Laurent@814:     def GetNameSize(self):
Laurent@814:         return self.NameSize
andrej@1730: 
Laurent@814:     # Returns the wires connected to the connector
Laurent@814:     def GetWires(self):
Laurent@814:         return self.Wires
andrej@1730: 
Laurent@814:     # Returns the parent block Id
Laurent@814:     def GetBlockId(self):
Laurent@814:         return self.ParentBlock.GetId()
andrej@1730: 
Laurent@814:     # Returns the connector relative position
Laurent@814:     def GetRelPosition(self):
Laurent@814:         return self.Pos
andrej@1730: 
Laurent@814:     # Returns the connector absolute position
andrej@1744:     def GetPosition(self, size=True):
Laurent@814:         parent_pos = self.ParentBlock.GetPosition()
Laurent@814:         # If the position of the end of the connector is asked
Laurent@814:         if size:
Laurent@814:             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
Laurent@814:             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
Laurent@814:         else:
Laurent@814:             x = parent_pos[0] + self.Pos.x
Laurent@814:             y = parent_pos[1] + self.Pos.y
Laurent@814:         return wx.Point(x, y)
andrej@1730: 
Laurent@814:     # Change the connector relative position
Laurent@814:     def SetPosition(self, pos):
Laurent@814:         self.Pos = pos
andrej@1730: 
Laurent@814:     # Returns the connector direction
Laurent@814:     def GetDirection(self):
Laurent@814:         return self.Direction
andrej@1730: 
Laurent@814:     # Change the connector direction
Laurent@814:     def SetDirection(self, direction):
Laurent@814:         self.Direction = direction
andrej@1730: 
Laurent@814:     # Connect a wire to this connector at the last place
andrej@1744:     def Connect(self, wire, refresh=True):
Laurent@814:         self.InsertConnect(len(self.Wires), wire, refresh)
andrej@1730: 
Laurent@814:     # Connect a wire to this connector at the place given
andrej@1744:     def InsertConnect(self, idx, wire, refresh=True):
Laurent@814:         if wire not in self.Wires:
Laurent@814:             self.Wires.insert(idx, wire)
Laurent@1054:             if wire[1] == 0:
Laurent@1054:                 wire[0].ConnectStartPoint(None, self)
Laurent@1054:             else:
Laurent@1054:                 wire[0].ConnectEndPoint(None, self)
Laurent@814:             if refresh:
Laurent@814:                 self.ParentBlock.RefreshModel(False)
andrej@1730: 
Laurent@814:     # Returns the index of the wire given in the list of connected
Laurent@814:     def GetWireIndex(self, wire):
andrej@1847:         for i, (tmp_wire, _handle) in enumerate(self.Wires):
Laurent@814:             if tmp_wire == wire:
Laurent@814:                 return i
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Unconnect a wire or all wires connected to the connector
andrej@1744:     def UnConnect(self, wire=None, unconnect=True, delete=False):
Laurent@814:         i = 0
Laurent@814:         found = False
Laurent@814:         while i < len(self.Wires) and not found:
Laurent@814:             if not wire or self.Wires[i][0] == wire:
Laurent@814:                 # If Unconnect haven't been called from a wire, disconnect the connector in the wire
Laurent@814:                 if unconnect:
Laurent@814:                     if self.Wires[i][1] == 0:
Laurent@814:                         self.Wires[i][0].UnConnectStartPoint(delete)
Laurent@814:                     else:
Laurent@814:                         self.Wires[i][0].UnConnectEndPoint(delete)
Laurent@814:                 # Remove wire from connected
Laurent@814:                 if wire:
Laurent@814:                     self.Wires.pop(i)
Laurent@814:                     found = True
Laurent@814:             i += 1
Laurent@814:         # If no wire defined, unconnect all wires
Laurent@814:         if not wire:
Laurent@814:             self.Wires = []
Laurent@857:         if not delete:
Laurent@857:             self.RefreshValid()
Laurent@857:             self.ParentBlock.RefreshModel(False)
andrej@1730: 
Laurent@814:     # Returns if connector has one or more wire connected
Laurent@814:     def IsConnected(self):
Laurent@814:         return len(self.Wires) > 0
andrej@1730: 
Laurent@814:     # Move the wires connected
andrej@1852:     def MoveConnected(self, exclude=None):
Laurent@814:         if len(self.Wires) > 0:
Laurent@814:             # Calculate the new position of the end point
Laurent@814:             parent_pos = self.ParentBlock.GetPosition()
Laurent@814:             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
Laurent@814:             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
Laurent@814:             # Move the corresponding point on all the wires connected
Laurent@814:             for wire, index in self.Wires:
andrej@1852:                 if (exclude is None) or (wire not in exclude):
Laurent@814:                     if index == 0:
Laurent@814:                         wire.MoveStartPoint(wx.Point(x, y))
Laurent@814:                     else:
Laurent@814:                         wire.MoveEndPoint(wx.Point(x, y))
andrej@1730: 
Laurent@814:     # Refreshes the model of all the wires connected
Laurent@814:     def RefreshWires(self):
Laurent@814:         for wire in self.Wires:
Laurent@814:             wire[0].RefreshModel()
andrej@1730: 
Laurent@814:     # Refreshes the parent block model
Laurent@814:     def RefreshParentBlock(self):
Laurent@814:         self.ParentBlock.RefreshModel(False)
andrej@1730: 
Laurent@814:     # Highlight the parent block
Laurent@814:     def HighlightParentBlock(self, highlight):
Laurent@814:         self.ParentBlock.SetHighlighted(highlight)
Laurent@814:         self.ParentBlock.Refresh()
andrej@1730: 
Laurent@814:     # Returns all the blocks connected to this connector
Laurent@814:     def GetConnectedBlocks(self):
Laurent@814:         blocks = []
Laurent@814:         for wire, handle in self.Wires:
Laurent@814:             # Get other connector connected to each wire
Laurent@814:             if handle == 0:
Laurent@814:                 connector = wire.GetEndConnected()
Laurent@814:             else:
Laurent@814:                 connector = wire.GetStartConnected()
Laurent@814:             # Get parent block for this connector
Laurent@814:             if connector:
Laurent@814:                 block = connector.GetParentBlock()
Laurent@814:                 if block not in blocks:
Laurent@814:                     blocks.append(block)
Laurent@814:         return blocks
andrej@1730: 
Laurent@814:     # Returns the connector negated property
Laurent@814:     def IsNegated(self):
Laurent@814:         return self.Negated
andrej@1730: 
Laurent@814:     # Changes the connector negated property
Laurent@814:     def SetNegated(self, negated):
Laurent@814:         if self.ParentBlock.IsOfType("BOOL", self.Type):
Laurent@814:             self.Negated = negated
Laurent@814:             self.Edge = "none"
andrej@1730: 
Laurent@814:     # Returns the connector edge property
Laurent@814:     def GetEdge(self):
Laurent@814:         return self.Edge
andrej@1730: 
Laurent@814:     # Changes the connector edge property
Laurent@814:     def SetEdge(self, edge):
Laurent@814:         if self.ParentBlock.IsOfType("BOOL", self.Type):
andrej@1730:             self.Edge = edge
Laurent@814:             self.Negated = False
andrej@1730: 
surkovsv93@1544:     # assume that pointer is already inside of this connector
surkovsv93@1544:     def ConnectionAvailable(self, direction=None, exclude=True):
surkovsv93@1544:         wire_nums = len(self.Wires)
andrej@1730: 
andrej@1742:         connector_free = (wire_nums <= 0)
surkovsv93@1544:         connector_max_used = ((wire_nums > 0) and self.OneConnected)
surkovsv93@1544:         if (self.Parent.CurrentLanguage in ["SFC", "LD"]) and (self.Type == "BOOL"):
andrej@1752:             connector_max_used = False
surkovsv93@1544: 
surkovsv93@1544:         # connector is available for new connection
andrej@1754:         connect = connector_free or not connector_max_used
surkovsv93@1544:         return connect, connector_max_used
andrej@1730: 
Laurent@814:     # Tests if the point given is near from the end point of this connector
surkovsv93@1544:     def TestPoint(self, pt, direction=None, exclude=True):
andrej@1752:         inside = False
andrej@1752:         check_point = (not exclude) and (direction is None or self.Direction == direction)
surkovsv93@1544: 
surkovsv93@1544:         if check_point:
Laurent@814:             # Calculate a square around the end point of this connector
surkovsv93@1544:             parent_pos = self.ParentBlock.GetPosition()
Laurent@814:             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE - ANCHOR_DISTANCE
Laurent@814:             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE - ANCHOR_DISTANCE
Laurent@814:             width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
Laurent@814:             height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
Laurent@814:             rect = wx.Rect(x, y, width, height)
andrej@1752:             inside = rect.InsideXY(pt.x, pt.y)
andrej@1730: 
surkovsv93@1544:         return inside
andrej@1730: 
Laurent@814:     # Draws the highlightment of this element if it is highlighted
Laurent@814:     def DrawHighlightment(self, dc):
Laurent@814:         scalex, scaley = dc.GetUserScale()
Laurent@814:         dc.SetUserScale(1, 1)
Laurent@814:         pen = MiterPen(HIGHLIGHTCOLOR, 2 * scalex + 5)
Laurent@814:         pen.SetCap(wx.CAP_BUTT)
Laurent@814:         dc.SetPen(pen)
Laurent@814:         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
Laurent@814:         dc.SetLogicalFunction(wx.AND)
Laurent@814:         parent_pos = self.ParentBlock.GetPosition()
andrej@1730:         xstart = parent_pos[0] + self.Pos.x
Laurent@814:         ystart = parent_pos[1] + self.Pos.y
Laurent@814:         if self.Direction[0] < 0:
Laurent@814:             xstart += 1
Laurent@814:         if self.Direction[1] < 0:
Laurent@814:             ystart += 1
Laurent@814:         xend = xstart + CONNECTOR_SIZE * self.Direction[0]
Laurent@814:         yend = ystart + CONNECTOR_SIZE * self.Direction[1]
andrej@1730:         dc.DrawLine(round((xstart + self.Direction[0]) * scalex), round((ystart + self.Direction[1]) * scaley),
Laurent@814:                     round(xend * scalex), round(yend * scaley))
Laurent@814:         dc.SetLogicalFunction(wx.COPY)
Laurent@814:         dc.SetUserScale(scalex, scaley)
andrej@1730: 
Laurent@814:     # Adds an highlight to the connector
Laurent@814:     def AddHighlight(self, infos, start, end, highlight_type):
Laurent@814:         if highlight_type == ERROR_HIGHLIGHT:
andrej@1847:             for wire, _handle in self.Wires:
Laurent@814:                 wire.SetValid(False)
Laurent@814:         AddHighlight(self.Highlights, (start, end, highlight_type))
andrej@1730: 
Laurent@814:     # Removes an highlight from the connector
Laurent@814:     def RemoveHighlight(self, infos, start, end, highlight_type):
Laurent@814:         error = False
Laurent@814:         highlights = []
Laurent@814:         for highlight in self.Highlights:
Laurent@814:             if highlight != (start, end, highlight_type):
Laurent@814:                 highlights.append(highlight)
Laurent@814:                 error |= highlight == ERROR_HIGHLIGHT
Laurent@814:         self.Highlights = highlights
Laurent@814:         if not error:
andrej@1847:             for wire, _handle in self.Wires:
Laurent@814:                 wire.SetValid(wire.IsConnectedCompatible())
andrej@1730: 
Laurent@814:     # Removes all the highlights of one particular type from the connector
Laurent@814:     def ClearHighlight(self, highlight_type=None):
Laurent@814:         error = False
Laurent@814:         if highlight_type is None:
Laurent@814:             self.Highlights = []
Laurent@814:         else:
Laurent@814:             highlights = []
Laurent@814:             for highlight in self.Highlights:
Laurent@814:                 if highlight[2] != highlight_type:
Laurent@814:                     highlights.append(highlight)
Laurent@814:                     error |= highlight == ERROR_HIGHLIGHT
Laurent@814:             self.Highlights = highlights
Laurent@814:         if not error:
andrej@1847:             for wire, _handle in self.Wires:
Laurent@814:                 wire.SetValid(wire.IsConnectedCompatible())
andrej@1730: 
Laurent@814:     # Draws the connector
Laurent@814:     def Draw(self, dc):
Laurent@814:         if self.Selected:
Laurent@814:             dc.SetPen(MiterPen(wx.BLUE, 3))
Laurent@814:             dc.SetBrush(wx.WHITE_BRUSH)
andrej@1782:         # elif len(self.Highlights) > 0:
Laurent@814:         #    dc.SetPen(MiterPen(self.Highlights[-1][1]))
Laurent@814:         #    dc.SetBrush(wx.Brush(self.Highlights[-1][0]))
Laurent@814:         else:
Laurent@814:             if not self.Valid:
Laurent@814:                 dc.SetPen(MiterPen(wx.RED))
andrej@2450:             elif isinstance(self.Value, bool) and self.Value:
Laurent@814:                 if self.Forced:
Laurent@814:                     dc.SetPen(MiterPen(wx.CYAN))
Laurent@814:                 else:
Laurent@814:                     dc.SetPen(MiterPen(wx.GREEN))
Laurent@814:             elif self.Value == "undefined":
Laurent@814:                 dc.SetPen(MiterPen(wx.NamedColour("orange")))
Laurent@814:             elif self.Forced:
Laurent@814:                 dc.SetPen(MiterPen(wx.BLUE))
Laurent@814:             else:
Laurent@814:                 dc.SetPen(MiterPen(wx.BLACK))
Laurent@814:             dc.SetBrush(wx.WHITE_BRUSH)
Laurent@814:         parent_pos = self.ParentBlock.GetPosition()
andrej@1730: 
Laurent@814:         if getattr(dc, "printing", False):
Laurent@814:             name_size = dc.GetTextExtent(self.Name)
Laurent@814:         else:
Laurent@814:             name_size = self.NameSize
andrej@1730: 
Laurent@814:         if self.Negated:
Laurent@814:             # If connector is negated, draw a circle
andrej@2437:             xcenter = parent_pos[0] + self.Pos.x + (CONNECTOR_SIZE * self.Direction[0]) // 2
andrej@2437:             ycenter = parent_pos[1] + self.Pos.y + (CONNECTOR_SIZE * self.Direction[1]) // 2
andrej@2437:             dc.DrawCircle(xcenter, ycenter, CONNECTOR_SIZE // 2)
Laurent@814:         else:
andrej@1730:             xstart = parent_pos[0] + self.Pos.x
Laurent@814:             ystart = parent_pos[1] + self.Pos.y
Laurent@814:             if self.Edge == "rising":
Laurent@814:                 # If connector has a rising edge, draw a right arrow
Laurent@814:                 dc.DrawLine(xstart, ystart, xstart - 4, ystart - 4)
Laurent@814:                 dc.DrawLine(xstart, ystart, xstart - 4, ystart + 4)
Laurent@814:             elif self.Edge == "falling":
Laurent@814:                 # If connector has a falling edge, draw a left arrow
Laurent@814:                 dc.DrawLine(xstart, ystart, xstart + 4, ystart - 4)
Laurent@814:                 dc.DrawLine(xstart, ystart, xstart + 4, ystart + 4)
Laurent@814:             if self.Direction[0] < 0:
Laurent@814:                 xstart += 1
Laurent@814:             if self.Direction[1] < 0:
Laurent@814:                 ystart += 1
Laurent@814:             if self.Selected:
Laurent@814:                 xend = xstart + (CONNECTOR_SIZE - 2) * self.Direction[0]
Laurent@814:                 yend = ystart + (CONNECTOR_SIZE - 2) * self.Direction[1]
Laurent@814:                 dc.DrawLine(xstart + 2 * self.Direction[0], ystart + 2 * self.Direction[1], xend, yend)
Laurent@814:             else:
Laurent@814:                 xend = xstart + CONNECTOR_SIZE * self.Direction[0]
Laurent@814:                 yend = ystart + CONNECTOR_SIZE * self.Direction[1]
Laurent@814:                 dc.DrawLine(xstart + self.Direction[0], ystart + self.Direction[1], xend, yend)
Laurent@814:         if self.Direction[0] != 0:
andrej@2437:             ytext = parent_pos[1] + self.Pos.y - name_size[1] // 2
Laurent@814:             if self.Direction[0] < 0:
Laurent@814:                 xtext = parent_pos[0] + self.Pos.x + 5
Laurent@814:             else:
Laurent@814:                 xtext = parent_pos[0] + self.Pos.x - (name_size[0] + 5)
Laurent@814:         if self.Direction[1] != 0:
andrej@2437:             xtext = parent_pos[0] + self.Pos.x - name_size[0] // 2
Laurent@814:             if self.Direction[1] < 0:
Laurent@814:                 ytext = parent_pos[1] + self.Pos.y + 5
Laurent@814:             else:
Laurent@814:                 ytext = parent_pos[1] + self.Pos.y - (name_size[1] + 5)
Laurent@814:         # Draw the text
Laurent@814:         dc.DrawText(self.Name, xtext, ytext)
Laurent@814:         if not getattr(dc, "printing", False):
Laurent@814:             DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
Laurent@814: 
andrej@2450:         if self.Value is not None and not isinstance(self.Value, bool) and self.Value != "undefined":
Laurent@1166:             dc.SetFont(self.ParentBlock.Parent.GetMiniFont())
Laurent@1166:             dc.SetTextForeground(wx.NamedColour("purple"))
andrej@2450:             if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
Laurent@1166:                 self.ValueSize = self.ParentBlock.Parent.GetMiniTextExtent(self.ComputedValue)
Laurent@1166:             if self.ValueSize is not None:
Laurent@1166:                 width, height = self.ValueSize
andrej@1730:                 dc.DrawText(self.ComputedValue,
andrej@1767:                             parent_pos[0] + self.Pos.x + CONNECTOR_SIZE * self.Direction[0] +
andrej@2437:                             width * (self.Direction[0] - 1) // 2,
andrej@1767:                             parent_pos[1] + self.Pos.y + CONNECTOR_SIZE * self.Direction[1] +
andrej@1767:                             height * (self.Direction[1] - 1))
Laurent@1166:             dc.SetFont(self.ParentBlock.Parent.GetFont())
Laurent@1166:             dc.SetTextForeground(wx.BLACK)
Laurent@1166: 
andrej@1782: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                           Common Wire Element
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
Laurent@814: 
Laurent@814: class Wire(Graphic_Element, DebugDataConsumer):
andrej@1736:     """
andrej@1736:     Class that implements a wire for connecting two blocks
andrej@1736:     """
andrej@1730: 
Laurent@814:     # Create a new wire
andrej@1744:     def __init__(self, parent, start=None, end=None):
Laurent@814:         Graphic_Element.__init__(self, parent)
Laurent@814:         DebugDataConsumer.__init__(self)
Laurent@814:         self.StartPoint = start
Laurent@814:         self.EndPoint = end
Laurent@814:         self.StartConnected = None
Laurent@814:         self.EndConnected = None
Laurent@814:         # If the start and end points are defined, calculate the wire
Laurent@814:         if start and end:
Laurent@814:             self.ResetPoints()
Laurent@814:             self.GeneratePoints()
Laurent@814:         else:
Laurent@814:             self.Points = []
Laurent@814:             self.Segments = []
Laurent@814:         self.SelectedSegment = None
Laurent@814:         self.Valid = True
Laurent@1377:         self.Modifier = "none"
Laurent@1377:         self.PreviousValue = None
Laurent@814:         self.ValueSize = None
Laurent@814:         self.ComputedValue = None
Laurent@814:         self.OverStart = False
Laurent@814:         self.OverEnd = False
Laurent@814:         self.ComputingType = False
Laurent@814:         self.Font = parent.GetMiniFont()
surkovsv93@1544:         self.ErrHighlight = False
andrej@1730: 
Laurent@814:     def GetDefinition(self):
Laurent@814:         if self.StartConnected is not None and self.EndConnected is not None:
Laurent@814:             startblock = self.StartConnected.GetParentBlock()
Laurent@814:             endblock = self.EndConnected.GetParentBlock()
Laurent@814:             return [], [(startblock.GetId(), endblock.GetId())]
Laurent@814:         return [], []
andrej@1730: 
Laurent@814:     def Flush(self):
Laurent@814:         self.StartConnected = None
Laurent@814:         self.EndConnected = None
andrej@1730: 
Laurent@814:     # Returns the RedrawRect
andrej@1744:     def GetRedrawRect(self, movex=0, movey=0):
Laurent@814:         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
Laurent@814:         if self.StartConnected:
Laurent@814:             rect = rect.Union(self.StartConnected.GetRedrawRect(movex, movey))
Laurent@814:         if self.EndConnected:
Laurent@814:             rect = rect.Union(self.EndConnected.GetRedrawRect(movex, movey))
andrej@2450:         if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
Laurent@814:             self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
Laurent@814:         if self.ValueSize is not None:
Laurent@814:             width, height = self.ValueSize
Laurent@814:             if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
andrej@2437:                 x = self.Points[0].x + width * self.StartPoint[1][0] // 2
Laurent@814:                 y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
Laurent@814:                 rect = rect.Union(wx.Rect(x, y, width, height))
andrej@2437:                 x = self.Points[-1].x + width * self.EndPoint[1][0] // 2
Laurent@814:                 y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
Laurent@814:                 rect = rect.Union(wx.Rect(x, y, width, height))
Laurent@814:             else:
andrej@2437:                 middle = len(self.Segments) // 2 + len(self.Segments) % 2 - 1
andrej@2437:                 x = (self.Points[middle].x + self.Points[middle + 1].x - width) // 2
Laurent@814:                 if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
andrej@2437:                     y = (self.Points[middle].y + self.Points[middle + 1].y - height) // 2
Laurent@814:                 else:
Laurent@814:                     y = self.Points[middle].y - height
Laurent@814:                 rect = rect.Union(wx.Rect(x, y, width, height))
Laurent@814:         return rect
andrej@1730: 
andrej@1852:     def Clone(self, parent, connectors=None, dx=0, dy=0):
andrej@1852:         connectors = {} if connectors is None else connectors
Laurent@814:         start_connector = connectors.get(self.StartConnected, None)
Laurent@814:         end_connector = connectors.get(self.EndConnected, None)
Laurent@814:         if start_connector is not None and end_connector is not None:
Laurent@814:             wire = Wire(parent)
Laurent@814:             wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
Laurent@814:             start_connector.Connect((wire, 0), False)
Laurent@814:             end_connector.Connect((wire, -1), False)
Laurent@814:             wire.ConnectStartPoint(start_connector.GetPosition(), start_connector)
Laurent@814:             wire.ConnectEndPoint(end_connector.GetPosition(), end_connector)
Laurent@814:             return wire
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Forbids to change the wire position
andrej@1868:     def SetPosition(self, x, y):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Forbids to change the wire size
andrej@1868:     def SetSize(self, width, height):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Moves and Resizes the element for fitting scaling
Laurent@852:     def SetBestSize(self, scaling):
Laurent@814:         if scaling is not None:
Laurent@814:             movex_max = movey_max = 0
Laurent@814:             for idx, point in enumerate(self.Points):
Laurent@814:                 if 0 < idx < len(self.Points) - 1:
Laurent@814:                     movex = round_scaling(point.x, scaling[0]) - point.x
Laurent@814:                     movey = round_scaling(point.y, scaling[1]) - point.y
Laurent@814:                     if idx == 1:
Laurent@814:                         if self.Segments[0][0] == 0:
Laurent@814:                             movex = 0
Laurent@814:                         elif (point.x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
Laurent@814:                             movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
Laurent@814:                         if self.Segments[0][1] == 0:
Laurent@814:                             movey = 0
Laurent@814:                         elif (point.y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
Laurent@814:                             movey = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - point.y
Laurent@814:                     elif idx == len(self.Points) - 2:
Laurent@814:                         if self.Segments[-1][0] == 0:
Laurent@814:                             movex = 0
Laurent@814:                         elif (self.Points[-1].x - (point.x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
Laurent@814:                             movex = round_scaling(self.Points[-1].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
Laurent@814:                         if self.Segments[-1][1] == 0:
Laurent@814:                             movey = 0
Laurent@814:                         elif (self.Points[-1].y - (point.y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
Laurent@814:                             movey = round_scaling(self.Points[-1].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - point.y
Laurent@814:                     movex_max = max(movex_max, movex)
Laurent@814:                     movey_max = max(movey_max, movey)
Laurent@814:                     point.x += movex
Laurent@814:                     point.y += movey
Laurent@814:             return movex_max, movey_max
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@814:     # Returns connector to which start point is connected
Laurent@814:     def GetStartConnected(self):
Laurent@814:         return self.StartConnected
andrej@1730: 
Laurent@814:     # Returns connector to which start point is connected
Laurent@814:     def GetStartConnectedType(self):
Laurent@814:         if self.StartConnected and not self.ComputingType:
Laurent@814:             self.ComputingType = True
Laurent@814:             computed_type = self.StartConnected.GetType()
Laurent@814:             self.ComputingType = False
Laurent@814:             return computed_type
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Returns connector to which end point is connected
Laurent@814:     def GetEndConnected(self):
Laurent@814:         return self.EndConnected
andrej@1730: 
Laurent@814:     # Returns connector to which end point is connected
Laurent@814:     def GetEndConnectedType(self):
Laurent@814:         if self.EndConnected and not self.ComputingType:
Laurent@814:             self.ComputingType = True
Laurent@814:             computed_type = self.EndConnected.GetType()
Laurent@814:             self.ComputingType = False
Laurent@814:             return computed_type
Laurent@814:         return None
andrej@1730: 
Laurent@814:     def GetConnectionDirection(self):
Laurent@814:         if self.StartConnected is None and self.EndConnected is None:
Laurent@814:             return None
Laurent@814:         elif self.StartConnected is not None and self.EndConnected is None:
Laurent@814:             return (-self.StartPoint[1][0], -self.StartPoint[1][1])
Laurent@814:         elif self.StartConnected is None and self.EndConnected is not None:
Laurent@814:             return self.EndPoint
Laurent@814:         elif self.Handle is not None:
Laurent@814:             handle_type, handle = self.Handle
Laurent@814:             # A point has been handled
Laurent@814:             if handle_type == HANDLE_POINT:
Laurent@814:                 if handle == 0:
Laurent@814:                     return self.EndPoint
Laurent@814:                 else:
Laurent@814:                     return (-self.StartPoint[1][0], -self.StartPoint[1][1])
Laurent@814:         return None
andrej@1730: 
Laurent@814:     def GetOtherConnected(self, connector):
Laurent@814:         if self.StartConnected == connector:
Laurent@814:             return self.EndConnected
Laurent@814:         else:
Laurent@814:             return self.StartConnected
andrej@1730: 
Laurent@814:     def GetOtherConnectedType(self, handle):
Laurent@814:         if handle == 0:
Laurent@814:             return self.GetEndConnectedType()
Laurent@814:         else:
Laurent@814:             return self.GetStartConnectedType()
andrej@1730: 
Laurent@814:     def IsConnectedCompatible(self):
Laurent@814:         if self.StartConnected:
Laurent@814:             return self.StartConnected.IsCompatible(self.GetEndConnectedType())
Laurent@814:         elif self.EndConnected:
Laurent@814:             return True
Laurent@814:         return False
andrej@1730: 
Laurent@814:     def SetForced(self, forced):
Laurent@814:         if self.Forced != forced:
Laurent@814:             self.Forced = forced
Laurent@814:             if self.StartConnected:
Laurent@814:                 self.StartConnected.RefreshForced()
Laurent@814:             if self.EndConnected:
Laurent@814:                 self.EndConnected.RefreshForced()
Laurent@814:             if self.Visible:
Laurent@814:                 self.Parent.ElementNeedRefresh(self)
Laurent@814: 
Laurent@1170:     def GetComputedValue(self):
andrej@2450:         if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, bool):
Laurent@1176:             return self.Value
Laurent@1170:         return None
andrej@1730: 
Laurent@1170:     def GetToolTipValue(self):
Laurent@1170:         return self.GetComputedValue()
Laurent@1170: 
Laurent@1377:     def SetModifier(self, modifier):
Laurent@1377:         self.Modifier = modifier
Laurent@1377: 
Laurent@814:     def SetValue(self, value):
Laurent@1377:         if self.Modifier == "rising":
Laurent@1377:             value, self.PreviousValue = value and not self.PreviousValue, value
Laurent@1377:         elif self.Modifier == "falling":
Laurent@1377:             value, self.PreviousValue = not value and self.PreviousValue, value
Laurent@1377:         elif self.Modifier == "negated":
Laurent@1377:             value = not value
Laurent@814:         if self.Value != value:
Laurent@814:             self.Value = value
Laurent@1170:             computed_value = self.GetComputedValue()
Laurent@1170:             if computed_value is not None:
Laurent@1170:                 self.ComputedValue = computed_value
Laurent@1170:                 self.SetToolTipText(self.ComputedValue)
Laurent@814:                 if len(self.ComputedValue) > 4:
Laurent@814:                     self.ComputedValue = self.ComputedValue[:4] + "..."
Laurent@814:             self.ValueSize = None
Laurent@814:             if self.StartConnected:
Laurent@814:                 self.StartConnected.RefreshValue()
Laurent@814:             if self.EndConnected:
Laurent@814:                 self.EndConnected.RefreshValue()
Laurent@814:             if self.Visible:
Laurent@814:                 self.Parent.ElementNeedRefresh(self)
andrej@2450:             if isinstance(value, bool) and self.StartConnected is not None:
Laurent@814:                 block = self.StartConnected.GetParentBlock()
Laurent@814:                 block.SpreadCurrent()
andrej@1730: 
Laurent@814:     # Unconnect the start and end points
Laurent@814:     def Clean(self):
Laurent@814:         if self.StartConnected:
Laurent@814:             self.UnConnectStartPoint()
Laurent@814:         if self.EndConnected:
Laurent@814:             self.UnConnectEndPoint()
andrej@1730: 
Laurent@814:     # Delete this wire by calling the corresponding method
Laurent@814:     def Delete(self):
Laurent@814:         self.Parent.DeleteWire(self)
andrej@1730: 
Laurent@814:     # Select a segment and not the whole wire. It's useful for Ladder Diagram
Laurent@814:     def SetSelectedSegment(self, segment):
Laurent@814:         # The last segment is indicated
Laurent@814:         if segment == -1:
Laurent@814:             segment = len(self.Segments) - 1
Laurent@814:         # The selected segment is reinitialised
andrej@1743:         if segment is None:
Laurent@814:             if self.StartConnected:
Laurent@814:                 self.StartConnected.SetSelected(False)
Laurent@814:             if self.EndConnected:
Laurent@814:                 self.EndConnected.SetSelected(False)
Laurent@814:         # The segment selected is the first
Laurent@814:         elif segment == 0:
Laurent@814:             if self.StartConnected:
Laurent@814:                 self.StartConnected.SetSelected(True)
Laurent@814:             if self.EndConnected:
Laurent@814:                 # There is only one segment
Laurent@814:                 if len(self.Segments) == 1:
Laurent@814:                     self.EndConnected.SetSelected(True)
Laurent@814:                 else:
Laurent@814:                     self.EndConnected.SetSelected(False)
Laurent@814:         # The segment selected is the last
Laurent@814:         elif segment == len(self.Segments) - 1:
Laurent@814:             if self.StartConnected:
Laurent@814:                 self.StartConnected.SetSelected(False)
Laurent@814:             if self.EndConnected:
Laurent@814:                 self.EndConnected.SetSelected(True)
Laurent@814:         self.SelectedSegment = segment
Laurent@814:         self.Refresh()
andrej@1730: 
Laurent@814:     def SetValid(self, valid):
Laurent@814:         self.Valid = valid
Laurent@814:         if self.StartConnected:
Laurent@814:             self.StartConnected.RefreshValid()
Laurent@814:         if self.EndConnected:
Laurent@814:             self.EndConnected.RefreshValid()
andrej@1730: 
Laurent@814:     def GetValid(self):
Laurent@814:         return self.Valid
andrej@1730: 
Laurent@814:     # Reinitialize the wire points
Laurent@814:     def ResetPoints(self):
Laurent@814:         if self.StartPoint and self.EndPoint:
Laurent@814:             self.Points = [self.StartPoint[0], self.EndPoint[0]]
Laurent@814:             self.Segments = [self.StartPoint[1]]
Laurent@814:         else:
Laurent@814:             self.Points = []
Laurent@814:             self.Segments = []
andrej@1730: 
Laurent@814:     # Refresh the wire bounding box
Laurent@814:     def RefreshBoundingBox(self):
Laurent@814:         if len(self.Points) > 0:
Laurent@814:             # If startpoint or endpoint is connected, save the point radius
Laurent@814:             start_radius = end_radius = 0
Laurent@814:             if not self.StartConnected:
Laurent@814:                 start_radius = POINT_RADIUS
Laurent@814:             if not self.EndConnected:
Laurent@814:                 end_radius = POINT_RADIUS
Laurent@814:             # Initialize minimum and maximum from the first point
Laurent@814:             minx, minbbxx = self.Points[0].x, self.Points[0].x - start_radius
Laurent@814:             maxx, maxbbxx = self.Points[0].x, self.Points[0].x + start_radius
Laurent@814:             miny, minbbxy = self.Points[0].y, self.Points[0].y - start_radius
Laurent@814:             maxy, maxbbxy = self.Points[0].y, self.Points[0].y + start_radius
Laurent@814:             # Actualize minimum and maximum with the other points
Laurent@814:             for point in self.Points[1:-1]:
Laurent@814:                 minx, minbbxx = min(minx, point.x), min(minbbxx, point.x)
Laurent@814:                 maxx, maxbbxx = max(maxx, point.x), max(maxbbxx, point.x)
Laurent@814:                 miny, minbbxy = min(miny, point.y), min(minbbxy, point.y)
Laurent@814:                 maxy, maxbbxy = max(maxy, point.y), max(maxbbxy, point.y)
Laurent@814:             if len(self.Points) > 1:
Laurent@814:                 minx, minbbxx = min(minx, self.Points[-1].x), min(minbbxx, self.Points[-1].x - end_radius)
Laurent@814:                 maxx, maxbbxx = max(maxx, self.Points[-1].x), max(maxbbxx, self.Points[-1].x + end_radius)
Laurent@814:                 miny, minbbxy = min(miny, self.Points[-1].y), min(minbbxy, self.Points[-1].y - end_radius)
Laurent@814:                 maxy, maxbbxy = max(maxy, self.Points[-1].y), max(maxbbxy, self.Points[-1].y + end_radius)
Laurent@814:             self.Pos.x, self.Pos.y = minx, miny
Laurent@814:             self.Size = wx.Size(maxx - minx, maxy - miny)
Laurent@814:             self.BoundingBox = wx.Rect(minbbxx, minbbxy, maxbbxx - minbbxx + 1, maxbbxy - minbbxy + 1)
andrej@1730: 
Laurent@814:     # Refresh the realpoints that permits to keep the proportionality in wire during resizing
Laurent@814:     def RefreshRealPoints(self):
Laurent@814:         if len(self.Points) > 0:
Laurent@814:             self.RealPoints = []
Laurent@814:             # Calculate float relative position of each point with the minimum point
Laurent@814:             for point in self.Points:
Laurent@814:                 self.RealPoints.append([float(point.x - self.Pos.x), float(point.y - self.Pos.y)])
andrej@1730: 
andrej@1730:     # Returns the wire minimum size
Laurent@814:     def GetMinSize(self):
Laurent@814:         width = 1
Laurent@814:         height = 1
Laurent@814:         dir_product = product(self.StartPoint[1], self.EndPoint[1])
Laurent@814:         # The directions are opposed
Laurent@814:         if dir_product < 0:
Laurent@814:             if self.StartPoint[0] != 0:
Laurent@814:                 width = MIN_SEGMENT_SIZE * 2
Laurent@814:             if self.StartPoint[1] != 0:
Laurent@814:                 height = MIN_SEGMENT_SIZE * 2
Laurent@814:         # The directions are the same
Laurent@814:         elif dir_product > 0:
Laurent@814:             if self.StartPoint[0] != 0:
Laurent@814:                 width = MIN_SEGMENT_SIZE
Laurent@814:             if self.StartPoint[1] != 0:
Laurent@814:                 height = MIN_SEGMENT_SIZE
Laurent@814:         # The directions are perpendiculars
Laurent@814:         else:
Laurent@814:             width = MIN_SEGMENT_SIZE
Laurent@814:             height = MIN_SEGMENT_SIZE
Laurent@814:         return width + 1, height + 1
andrej@1730: 
Laurent@814:     # Returns if the point given is on one of the wire segments
Laurent@814:     def HitTest(self, pt, connectors=True):
Laurent@814:         test = False
Laurent@814:         for i in xrange(len(self.Points) - 1):
Laurent@814:             rect = wx.Rect(0, 0, 0, 0)
Laurent@814:             if i == 0 and self.StartConnected is not None:
Laurent@814:                 x1 = self.Points[i].x - self.Segments[0][0] * CONNECTOR_SIZE
Laurent@814:                 y1 = self.Points[i].y - self.Segments[0][1] * CONNECTOR_SIZE
Laurent@814:             else:
andrej@1730:                 x1, y1 = self.Points[i].x, self.Points[i].y
Laurent@814:             if i == len(self.Points) - 2 and self.EndConnected is not None:
Laurent@814:                 x2 = self.Points[i + 1].x + self.Segments[-1][0] * CONNECTOR_SIZE
Laurent@814:                 y2 = self.Points[i + 1].y + self.Segments[-1][1] * CONNECTOR_SIZE
Laurent@814:             else:
Laurent@814:                 x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
Laurent@814:             # Calculate a rectangle around the segment
Laurent@814:             rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
andrej@1768:                            abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
andrej@1730:             test |= rect.InsideXY(pt.x, pt.y)
Laurent@814:         return test
andrej@1730: 
andrej@1730:     # Returns the wire start or end point if the point given is on one of them
Laurent@814:     def TestPoint(self, pt):
Laurent@814:         # Test the wire start point
Laurent@814:         rect = wx.Rect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
andrej@1768:                        2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
Laurent@814:         if rect.InsideXY(pt.x, pt.y):
Laurent@814:             return 0
Laurent@814:         # Test the wire end point
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             rect = wx.Rect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
andrej@1768:                            2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
Laurent@814:             if rect.InsideXY(pt.x, pt.y):
Laurent@814:                 return -1
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Returns the wire segment if the point given is on it
Laurent@814:     def TestSegment(self, pt, all=False):
Laurent@814:         for i in xrange(len(self.Segments)):
Laurent@814:             # If wire is not in a Ladder Diagram, first and last segments are excluded
Laurent@814:             if all or 0 < i < len(self.Segments) - 1:
Laurent@814:                 x1, y1 = self.Points[i].x, self.Points[i].y
Laurent@814:                 x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
Laurent@814:                 # Calculate a rectangle around the segment
Laurent@814:                 rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
andrej@1768:                                abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
Laurent@814:                 if rect.InsideXY(pt.x, pt.y):
Laurent@814:                     return i, self.Segments[i]
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Define the wire points
Laurent@814:     def SetPoints(self, points, verify=True):
Laurent@814:         if len(points) > 1:
Laurent@814:             self.Points = [wx.Point(x, y) for x, y in points]
Laurent@814:             # Calculate the start and end directions
Laurent@814:             self.StartPoint = [None, vector(self.Points[0], self.Points[1])]
Laurent@814:             self.EndPoint = [None, vector(self.Points[-1], self.Points[-2])]
Laurent@814:             # Calculate the start and end points
andrej@1730:             self.StartPoint[0] = wx.Point(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0],
andrej@1768:                                           self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
andrej@1730:             self.EndPoint[0] = wx.Point(self.Points[-1].x + CONNECTOR_SIZE * self.EndPoint[1][0],
andrej@1768:                                         self.Points[-1].y + CONNECTOR_SIZE * self.EndPoint[1][1])
Laurent@814:             self.Points[0] = self.StartPoint[0]
Laurent@814:             self.Points[-1] = self.EndPoint[0]
Laurent@814:             # Calculate the segments directions
Laurent@814:             self.Segments = []
Laurent@814:             i = 0
Laurent@814:             while i < len(self.Points) - 1:
Laurent@814:                 if verify and 0 < i < len(self.Points) - 2 and \
Laurent@814:                    self.Points[i] == self.Points[i + 1] and \
Laurent@814:                    self.Segments[-1] == vector(self.Points[i + 1], self.Points[i + 2]):
andrej@1847:                     for dummy in xrange(2):
Laurent@814:                         self.Points.pop(i)
Laurent@814:                 else:
Laurent@814:                     segment = vector(self.Points[i], self.Points[i + 1])
Laurent@814:                     if is_null_vector(segment) and i > 0:
Laurent@814:                         segment = (self.Segments[-1][1], self.Segments[-1][0])
Laurent@814:                     if i < len(self.Points) - 2:
Laurent@814:                         next = vector(self.Points[i + 1], self.Points[i + 2])
Laurent@814:                         if next == segment or is_null_vector(add_vectors(segment, next)):
Laurent@814:                             self.Points.insert(i + 1, wx.Point(self.Points[i + 1].x, self.Points[i + 1].y))
Laurent@814:                     self.Segments.append(segment)
Laurent@814:                     i += 1
Laurent@814:             self.RefreshBoundingBox()
Laurent@814:             self.RefreshRealPoints()
andrej@1730: 
Laurent@814:     # Returns the position of the point indicated
Laurent@814:     def GetPoint(self, index):
Laurent@814:         if index < len(self.Points):
Laurent@814:             return self.Points[index].x, self.Points[index].y
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Returns a list of the position of all wire points
andrej@1744:     def GetPoints(self, invert=False):
Laurent@814:         points = self.VerifyPoints()
andrej@1730:         points[0] = wx.Point(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0],
andrej@1768:                              points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
andrej@1730:         points[-1] = wx.Point(points[-1].x - CONNECTOR_SIZE * self.EndPoint[1][0],
andrej@1768:                               points[-1].y - CONNECTOR_SIZE * self.EndPoint[1][1])
Laurent@814:         # An inversion of the list is asked
Laurent@814:         if invert:
Laurent@814:             points.reverse()
Laurent@814:         return points
andrej@1730: 
Laurent@814:     # Returns the position of the two selected segment points
Laurent@814:     def GetSelectedSegmentPoints(self):
andrej@1743:         if self.SelectedSegment is not None and len(self.Points) > 1:
Laurent@814:             return self.Points[self.SelectedSegment:self.SelectedSegment + 2]
Laurent@814:         return []
andrej@1730: 
Laurent@814:     # Returns if the selected segment is the first and/or the last of the wire
Laurent@814:     def GetSelectedSegmentConnections(self):
andrej@1743:         if self.SelectedSegment is not None and len(self.Points) > 1:
Laurent@814:             return self.SelectedSegment == 0, self.SelectedSegment == len(self.Segments) - 1
Laurent@814:         return (True, True)
andrej@1730: 
Laurent@814:     # Returns the connectors on which the wire is connected
Laurent@814:     def GetConnected(self):
Laurent@814:         connected = []
Laurent@814:         if self.StartConnected and self.StartPoint[1] == WEST:
Laurent@814:             connected.append(self.StartConnected)
Laurent@814:         if self.EndConnected and self.EndPoint[1] == WEST:
Laurent@814:             connected.append(self.EndConnected)
Laurent@814:         return connected
andrej@1730: 
Laurent@814:     # Returns the id of the block connected to the first or the last wire point
Laurent@814:     def GetConnectedInfos(self, index):
Laurent@814:         if index == 0 and self.StartConnected:
Laurent@814:             return self.StartConnected.GetBlockId(), self.StartConnected.GetName()
Laurent@814:         elif index == -1 and self.EndConnected:
Laurent@814:             return self.EndConnected.GetBlockId(), self.EndConnected.GetName()
Laurent@814:         return None
andrej@1730: 
Laurent@814:     # Update the wire points position by keeping at most possible the current positions
andrej@1744:     def GeneratePoints(self, realpoints=True):
Laurent@814:         i = 0
Laurent@814:         # Calculate the start enad end points with the minimum segment size in the right direction
Laurent@814:         end = wx.Point(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
andrej@1768:                        self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
andrej@1730:         start = wx.Point(self.StartPoint[0].x + self.StartPoint[1][0] * MIN_SEGMENT_SIZE,
andrej@1768:                          self.StartPoint[0].y + self.StartPoint[1][1] * MIN_SEGMENT_SIZE)
Laurent@814:         # Evaluate the point till it's the last
Laurent@814:         while i < len(self.Points) - 1:
Laurent@814:             # The next point is the last
Laurent@814:             if i + 1 == len(self.Points) - 1:
Laurent@814:                 # Calculate the direction from current point to end point
Laurent@814:                 v_end = vector(self.Points[i], end)
Laurent@814:                 # The current point is the first
Laurent@814:                 if i == 0:
Laurent@814:                     # If the end point is not in the start direction, a point is added
Laurent@814:                     if v_end != self.Segments[0] or v_end == self.EndPoint[1]:
Laurent@814:                         self.Points.insert(1, wx.Point(start.x, start.y))
andrej@1768:                         self.Segments.insert(1, DirectionChoice(
andrej@1768:                             (self.Segments[0][1],
andrej@1768:                              self.Segments[0][0]), v_end, self.EndPoint[1]))
Laurent@814:                 # The current point is the second
Laurent@814:                 elif i == 1:
Laurent@814:                     # The previous direction and the target direction are mainly opposed, a point is added
Laurent@814:                     if product(v_end, self.Segments[0]) < 0:
Laurent@814:                         self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
andrej@1768:                         self.Segments.insert(2, DirectionChoice(
andrej@1768:                             (self.Segments[1][1],
andrej@1768:                              self.Segments[1][0]), v_end, self.EndPoint[1]))
Laurent@814:                     # The previous direction and the end direction are the same or they are
Laurent@814:                     # perpendiculars and the end direction points towards current segment
Laurent@814:                     elif product(self.Segments[0], self.EndPoint[1]) >= 0 and product(self.Segments[1], self.EndPoint[1]) <= 0:
Laurent@814:                         # Current point and end point are aligned
Laurent@814:                         if self.Segments[0][0] != 0:
Laurent@814:                             self.Points[1].x = end.x
Laurent@814:                         if self.Segments[0][1] != 0:
Laurent@814:                             self.Points[1].y = end.y
Laurent@814:                         # If the previous direction and the end direction are the same, a point is added
Laurent@814:                         if product(self.Segments[0], self.EndPoint[1]) > 0:
Laurent@814:                             self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
andrej@1768:                             self.Segments.insert(2, DirectionChoice(
andrej@1768:                                 (self.Segments[1][1],
andrej@1768:                                  self.Segments[1][0]), v_end, self.EndPoint[1]))
Laurent@814:                     else:
Laurent@814:                         # Current point is positioned in the middle of start point
Laurent@814:                         # and end point on the current direction and a point is added
Laurent@814:                         if self.Segments[0][0] != 0:
andrej@2437:                             self.Points[1].x = (end.x + start.x) // 2
Laurent@814:                         if self.Segments[0][1] != 0:
andrej@2437:                             self.Points[1].y = (end.y + start.y) // 2
Laurent@814:                         self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
andrej@1768:                         self.Segments.insert(2, DirectionChoice(
andrej@1768:                             (self.Segments[1][1],
andrej@1768:                              self.Segments[1][0]), v_end, self.EndPoint[1]))
Laurent@814:                 else:
Laurent@814:                     # The previous direction and the end direction are perpendiculars
Laurent@814:                     if product(self.Segments[i - 1], self.EndPoint[1]) == 0:
Laurent@814:                         # The target direction and the end direction aren't mainly the same
Laurent@814:                         if product(v_end, self.EndPoint[1]) <= 0:
Laurent@814:                             # Current point and end point are aligned
Laurent@814:                             if self.Segments[i - 1][0] != 0:
Laurent@814:                                 self.Points[i].x = end.x
Laurent@814:                             if self.Segments[i - 1][1] != 0:
Laurent@814:                                 self.Points[i].y = end.y
Laurent@814:                             # Previous direction is updated from the new point
Laurent@814:                             if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
Laurent@814:                                 self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
Laurent@814:                         else:
Laurent@814:                             test = True
andrej@1730:                             # If the current point is the third, test if the second
Laurent@814:                             # point can be aligned with the end point
Laurent@814:                             if i == 2:
Laurent@814:                                 test_point = wx.Point(self.Points[1].x, self.Points[1].y)
Laurent@814:                                 if self.Segments[1][0] != 0:
Laurent@814:                                     test_point.y = end.y
Laurent@814:                                 if self.Segments[1][1] != 0:
Laurent@814:                                     test_point.x = end.x
Laurent@814:                                 vector_test = vector(self.Points[0], test_point, False)
Laurent@814:                                 test = norm(vector_test) > MIN_SEGMENT_SIZE and product(self.Segments[0], vector_test) > 0
Laurent@814:                             # The previous point can be aligned
Laurent@814:                             if test:
Laurent@814:                                 self.Points[i].x, self.Points[i].y = end.x, end.y
Laurent@814:                                 if self.Segments[i - 1][0] != 0:
Laurent@814:                                     self.Points[i - 1].y = end.y
Laurent@814:                                 if self.Segments[i - 1][1] != 0:
Laurent@814:                                     self.Points[i - 1].x = end.x
Laurent@814:                                 self.Segments[i] = (-self.EndPoint[1][0], -self.EndPoint[1][1])
Laurent@814:                             else:
Laurent@814:                                 # Current point is positioned in the middle of previous point
Laurent@814:                                 # and end point on the current direction and a point is added
Laurent@814:                                 if self.Segments[1][0] != 0:
andrej@2437:                                     self.Points[2].x = (self.Points[1].x + end.x) // 2
Laurent@814:                                 if self.Segments[1][1] != 0:
andrej@2437:                                     self.Points[2].y = (self.Points[1].y + end.y) // 2
Laurent@814:                                 self.Points.insert(3, wx.Point(self.Points[2].x, self.Points[2].y))
andrej@1768:                                 self.Segments.insert(
andrej@1768:                                     3,
andrej@1768:                                     DirectionChoice((self.Segments[2][1],
andrej@1768:                                                      self.Segments[2][0]),
andrej@1768:                                                     v_end,
andrej@1768:                                                     self.EndPoint[1]))
Laurent@814:                     else:
Laurent@814:                         # Current point is aligned with end point
Laurent@814:                         if self.Segments[i - 1][0] != 0:
Laurent@814:                             self.Points[i].x = end.x
Laurent@814:                         if self.Segments[i - 1][1] != 0:
Laurent@814:                             self.Points[i].y = end.y
Laurent@814:                         # Previous direction is updated from the new point
Laurent@814:                         if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
Laurent@814:                             self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
Laurent@814:                         # If previous direction and end direction are opposed
Laurent@814:                         if product(self.Segments[i - 1], self.EndPoint[1]) < 0:
Laurent@814:                             # Current point is positioned in the middle of previous point
Laurent@814:                             # and end point on the current direction
Laurent@814:                             if self.Segments[i - 1][0] != 0:
andrej@2437:                                 self.Points[i].x = (end.x + self.Points[i - 1].x) // 2
Laurent@814:                             if self.Segments[i - 1][1] != 0:
andrej@2437:                                 self.Points[i].y = (end.y + self.Points[i - 1].y) // 2
Laurent@814:                         # A point is added
Laurent@814:                         self.Points.insert(i + 1, wx.Point(self.Points[i].x, self.Points[i].y))
andrej@1768:                         self.Segments.insert(
andrej@1768:                             i + 1,
andrej@1768:                             DirectionChoice((self.Segments[i][1],
andrej@1768:                                              self.Segments[i][0]), v_end, self.EndPoint[1]))
Laurent@814:             else:
Laurent@814:                 # Current point is the first, and second is not mainly in the first direction
Laurent@814:                 if i == 0 and product(vector(start, self.Points[1]), self.Segments[0]) < 0:
andrej@1730:                     # If first and second directions aren't perpendiculars, a point is added
Laurent@814:                     if product(self.Segments[0], self.Segments[1]) != 0:
Laurent@814:                         self.Points.insert(1, wx.Point(start.x, start.y))
andrej@1768:                         self.Segments.insert(
andrej@1768:                             1,
andrej@1768:                             DirectionChoice((self.Segments[0][1],
andrej@1768:                                              self.Segments[0][0]),
andrej@1768:                                             vector(start, self.Points[1]),
andrej@1768:                                             self.Segments[1]))
Laurent@814:                     else:
Laurent@814:                         self.Points[1].x, self.Points[1].y = start.x, start.y
Laurent@814:                 else:
Laurent@814:                     # Next point is aligned with current point
Laurent@814:                     if self.Segments[i][0] != 0:
Laurent@814:                         self.Points[i + 1].y = self.Points[i].y
Laurent@814:                     if self.Segments[i][1] != 0:
Laurent@814:                         self.Points[i + 1].x = self.Points[i].x
Laurent@814:                     # Current direction is updated from the new point
Laurent@814:                     if product(vector(self.Points[i], self.Points[i + 1]), self.Segments[i]) < 0:
Laurent@814:                         self.Segments[i] = (-self.Segments[i][0], -self.Segments[i][1])
Laurent@814:             i += 1
Laurent@814:         self.RefreshBoundingBox()
Laurent@814:         if realpoints:
Laurent@814:             self.RefreshRealPoints()
andrej@1730: 
Laurent@814:     # Verify that two consecutive points haven't the same position
Laurent@814:     def VerifyPoints(self):
Laurent@814:         points = [point for point in self.Points]
Laurent@814:         segments = [segment for segment in self.Segments]
Laurent@814:         i = 1
Laurent@814:         while i < len(points) - 1:
Laurent@814:             if points[i] == points[i + 1] and segments[i - 1] == segments[i + 1]:
andrej@1847:                 for dummy in xrange(2):
Laurent@814:                     points.pop(i)
Laurent@814:                     segments.pop(i)
Laurent@814:             else:
Laurent@814:                 i += 1
Laurent@814:         # If the wire isn't in a Ladder Diagram, save the new point list
Laurent@814:         if self.Parent.__class__.__name__ != "LD_Viewer":
Laurent@814:             self.Points = [point for point in points]
Laurent@814:             self.Segments = [segment for segment in segments]
Laurent@814:             self.RefreshBoundingBox()
Laurent@814:             self.RefreshRealPoints()
Laurent@814:         return points
andrej@1730: 
Laurent@814:     # Moves all the wire points except the first and the last if they are connected
andrej@1744:     def Move(self, dx, dy, endpoints=False):
Laurent@814:         for i, point in enumerate(self.Points):
Laurent@814:             if endpoints or not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
Laurent@814:                 point.x += dx
Laurent@814:                 point.y += dy
Laurent@814:         self.StartPoint[0] = self.Points[0]
Laurent@814:         self.EndPoint[0] = self.Points[-1]
Laurent@814:         self.GeneratePoints()
andrej@1730: 
Laurent@814:     # Resize the wire from position and size given
Laurent@814:     def Resize(self, x, y, width, height):
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             # Calculate the new position of each point for testing the new size
Laurent@814:             minx, miny = self.Pos.x, self.Pos.y
Laurent@814:             lastwidth, lastheight = self.Size.width, self.Size.height
Laurent@814:             for i, point in enumerate(self.RealPoints):
Laurent@814:                 # If start or end point is connected, it's not calculate
Laurent@814:                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
Laurent@814:                     if i == 0:
Laurent@814:                         dir = self.StartPoint[1]
Laurent@814:                     elif i == len(self.Points) - 1:
Laurent@814:                         dir = self.EndPoint[1]
Laurent@814:                     else:
Laurent@814:                         dir = (0, 0)
andrej@2437:                     pointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0] * width / max(lastwidth, 1))),
andrej@1768:                                                                  width - dir[0] * MIN_SEGMENT_SIZE))
andrej@2437:                     pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * height / max(lastheight, 1))),
andrej@1768:                                                                  height - dir[1] * MIN_SEGMENT_SIZE))
Laurent@814:                     self.Points[i] = wx.Point(minx + x + pointx, miny + y + pointy)
Laurent@814:             self.StartPoint[0] = self.Points[0]
Laurent@814:             self.EndPoint[0] = self.Points[-1]
Laurent@814:             self.GeneratePoints(False)
Laurent@814:             # Test if the wire position or size have changed
Laurent@814:             if x != 0 and minx == self.Pos.x:
Laurent@814:                 x = 0
Laurent@814:                 width = lastwidth
Laurent@814:             if y != 0 and miny == self.Pos.y:
Laurent@814:                 y = 0
Laurent@814:                 height = lastwidth
Laurent@814:             if width != lastwidth and lastwidth == self.Size.width:
Laurent@814:                 width = lastwidth
Laurent@814:             if height != lastheight and lastheight == self.Size.height:
Laurent@814:                 height = lastheight
Laurent@814:             # Calculate the real points from the new size, it's important for
Laurent@814:             # keeping a proportionality in the points position with the size
Laurent@814:             # during a resize dragging
Laurent@814:             for i, point in enumerate(self.RealPoints):
Laurent@814:                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
andrej@2437:                     point[0] = point[0] * width / max(lastwidth, 1)
andrej@2437:                     point[1] = point[1] * height / max(lastheight, 1)
Laurent@814:             # Calculate the correct position of the points from real points
Laurent@814:             for i, point in enumerate(self.RealPoints):
Laurent@814:                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
Laurent@814:                     if i == 0:
Laurent@814:                         dir = self.StartPoint[1]
Laurent@814:                     elif i == len(self.Points) - 1:
Laurent@814:                         dir = self.EndPoint[1]
Laurent@814:                     else:
Laurent@814:                         dir = (0, 0)
andrej@1768:                     realpointx = max(-dir[0] * MIN_SEGMENT_SIZE,
andrej@1768:                                      min(int(round(point[0])),
andrej@1768:                                          width - dir[0] * MIN_SEGMENT_SIZE))
andrej@1768:                     realpointy = max(-dir[1] * MIN_SEGMENT_SIZE,
andrej@1768:                                      min(int(round(point[1])),
andrej@1768:                                          height - dir[1] * MIN_SEGMENT_SIZE))
Laurent@814:                     self.Points[i] = wx.Point(minx + x + realpointx, miny + y + realpointy)
Laurent@814:             self.StartPoint[0] = self.Points[0]
Laurent@814:             self.EndPoint[0] = self.Points[-1]
Laurent@814:             self.GeneratePoints(False)
andrej@1730: 
Laurent@814:     # Moves the wire start point and update the wire points
Laurent@814:     def MoveStartPoint(self, point):
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             self.StartPoint[0] = point
Laurent@814:             self.Points[0] = point
Laurent@814:             self.GeneratePoints()
andrej@1730: 
Laurent@814:     # Changes the wire start direction and update the wire points
Laurent@814:     def SetStartPointDirection(self, dir):
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             self.StartPoint[1] = dir
Laurent@814:             self.Segments[0] = dir
Laurent@814:             self.GeneratePoints()
andrej@1730: 
Laurent@814:     # Rotates the wire start direction by an angle of 90 degrees anticlockwise
Laurent@814:     def RotateStartPoint(self):
Laurent@814:         self.SetStartPointDirection((self.StartPoint[1][1], -self.StartPoint[1][0]))
andrej@1730: 
Laurent@814:     # Connects wire start point to the connector given and moves wire start point
Laurent@814:     # to given point
Laurent@814:     def ConnectStartPoint(self, point, connector):
Laurent@814:         if point:
Laurent@814:             self.MoveStartPoint(point)
Laurent@814:         self.StartConnected = connector
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Unconnects wire start point
andrej@1744:     def UnConnectStartPoint(self, delete=False):
Laurent@814:         if delete:
Laurent@814:             self.StartConnected = None
Laurent@814:             self.Delete()
Laurent@814:         elif self.StartConnected:
andrej@1744:             self.StartConnected.UnConnect(self, unconnect=False)
Laurent@814:             self.StartConnected = None
Laurent@814:             self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Moves the wire end point and update the wire points
Laurent@814:     def MoveEndPoint(self, point):
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             self.EndPoint[0] = point
Laurent@814:             self.Points[-1] = point
Laurent@814:             self.GeneratePoints()
Laurent@814: 
Laurent@814:     # Changes the wire end direction and update the wire points
Laurent@814:     def SetEndPointDirection(self, dir):
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             self.EndPoint[1] = dir
Laurent@814:             self.GeneratePoints()
andrej@1730: 
Laurent@814:     # Rotates the wire end direction by an angle of 90 degrees anticlockwise
Laurent@814:     def RotateEndPoint(self):
Laurent@814:         self.SetEndPointDirection((self.EndPoint[1][1], -self.EndPoint[1][0]))
Laurent@814: 
Laurent@814:     # Connects wire end point to the connector given and moves wire end point
Laurent@814:     # to given point
Laurent@814:     def ConnectEndPoint(self, point, connector):
Laurent@814:         if point:
Laurent@814:             self.MoveEndPoint(point)
Laurent@814:         self.EndConnected = connector
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Unconnects wire end point
andrej@1744:     def UnConnectEndPoint(self, delete=False):
Laurent@814:         if delete:
Laurent@814:             self.EndConnected = None
Laurent@814:             self.Delete()
Laurent@814:         elif self.EndConnected:
andrej@1744:             self.EndConnected.UnConnect(self, unconnect=False)
Laurent@814:             self.EndConnected = None
Laurent@814:             self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Moves the wire segment given by its index
Laurent@814:     def MoveSegment(self, idx, movex, movey, scaling):
Laurent@814:         if 0 < idx < len(self.Segments) - 1:
Laurent@814:             if self.Segments[idx] in (NORTH, SOUTH):
Laurent@814:                 start_x = self.Points[idx].x
Laurent@814:                 if scaling is not None:
Laurent@814:                     movex = round_scaling(self.Points[idx].x + movex, scaling[0]) - self.Points[idx].x
Laurent@814:                     if idx == 1 and (self.Points[1].x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
Laurent@814:                         movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - self.Points[idx].x
Laurent@814:                     elif idx == len(self.Segments) - 2 and (self.Points[-1].x - (self.Points[-2].x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
Laurent@814:                         movex = round_scaling(self.Points[-1].x - MIN_SEGMENT_SIZE * self.Segments[-1][0], scaling[0], -self.Segments[-1][0]) - self.Points[idx].x
Laurent@814:                 self.Points[idx].x += movex
Laurent@814:                 self.Points[idx + 1].x += movex
Laurent@814:                 self.GeneratePoints()
Laurent@814:                 if start_x != self.Points[idx].x:
Laurent@814:                     return self.Points[idx].x - start_x, 0
Laurent@814:             elif self.Segments[idx] in (EAST, WEST):
Laurent@814:                 start_y = self.Points[idx].y
Laurent@814:                 if scaling is not None:
Laurent@814:                     movey = round_scaling(self.Points[idx].y + movey, scaling[1]) - self.Points[idx].y
Laurent@814:                     if idx == 1 and (self.Points[1].y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
Laurent@814:                         movex = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - self.Points[idx].y
Laurent@814:                     elif idx == len(self.Segments) - 2 and (self.Points[-1].y - (self.Points[-2].y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
Laurent@814:                         movey = round_scaling(self.Points[idx].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - self.Points[idx].y
Laurent@814:                 self.Points[idx].y += movey
Laurent@814:                 self.Points[idx + 1].y += movey
Laurent@814:                 self.GeneratePoints()
Laurent@814:                 if start_y != self.Points[idx].y:
Laurent@814:                     return 0, self.Points[idx].y - start_y
Laurent@814:         return 0, 0
andrej@1730: 
Laurent@814:     # Adds two points in the middle of the handled segment
Laurent@814:     def AddSegment(self):
Laurent@814:         handle_type, handle = self.Handle
Laurent@814:         if handle_type == HANDLE_SEGMENT:
Laurent@814:             segment, dir = handle
Laurent@814:             if len(self.Segments) > 1:
Laurent@814:                 pointx = self.Points[segment].x
Laurent@814:                 pointy = self.Points[segment].y
Laurent@814:                 if dir[0] != 0:
andrej@2437:                     pointx = (self.Points[segment].x + self.Points[segment + 1].x) // 2
Laurent@814:                 if dir[1] != 0:
andrej@2437:                     pointy = (self.Points[segment].y + self.Points[segment + 1].y) // 2
Laurent@814:                 self.Points.insert(segment + 1, wx.Point(pointx, pointy))
Laurent@814:                 self.Segments.insert(segment + 1, (dir[1], dir[0]))
Laurent@814:                 self.Points.insert(segment + 2, wx.Point(pointx, pointy))
Laurent@814:                 self.Segments.insert(segment + 2, dir)
Laurent@814:             else:
Laurent@814:                 p1x = p2x = self.Points[segment].x
Laurent@814:                 p1y = p2y = self.Points[segment].y
Laurent@814:                 if dir[0] != 0:
andrej@2437:                     p1x = (2 * self.Points[segment].x + self.Points[segment + 1].x) // 3
andrej@2437:                     p2x = (self.Points[segment].x + 2 * self.Points[segment + 1].x) // 3
Laurent@814:                 if dir[1] != 0:
andrej@2437:                     p1y = (2 * self.Points[segment].y + self.Points[segment + 1].y) // 3
andrej@2437:                     p2y = (self.Points[segment].y + 2 * self.Points[segment + 1].y) // 3
Laurent@814:                 self.Points.insert(segment + 1, wx.Point(p1x, p1y))
Laurent@814:                 self.Segments.insert(segment + 1, (dir[1], dir[0]))
Laurent@814:                 self.Points.insert(segment + 2, wx.Point(p1x, p1y))
Laurent@814:                 self.Segments.insert(segment + 2, dir)
Laurent@814:                 self.Points.insert(segment + 3, wx.Point(p2x, p2y))
Laurent@814:                 self.Segments.insert(segment + 3, (dir[1], dir[0]))
Laurent@814:                 self.Points.insert(segment + 4, wx.Point(p2x, p2y))
Laurent@814:                 self.Segments.insert(segment + 4, dir)
Laurent@814:             self.GeneratePoints()
andrej@1730: 
Laurent@814:     # Delete the handled segment by removing the two segment points
Laurent@814:     def DeleteSegment(self):
Laurent@814:         handle_type, handle = self.Handle
Laurent@814:         if handle_type == HANDLE_SEGMENT:
andrej@1847:             segment, _dir = handle
andrej@1847:             for dummy in xrange(2):
Laurent@814:                 self.Points.pop(segment)
Laurent@814:                 self.Segments.pop(segment)
Laurent@814:             self.GeneratePoints()
Laurent@814:             self.RefreshModel()
andrej@1730: 
Laurent@814:     # Method called when a LeftDown event have been generated
Laurent@814:     def OnLeftDown(self, event, dc, scaling):
Laurent@814:         pos = GetScaledEventPosition(event, dc, scaling)
Laurent@814:         # Test if a point have been handled
andrej@1782:         # result = self.TestPoint(pos)
andrej@1782:         # if result != None:
Laurent@814:         #    self.Handle = (HANDLE_POINT, result)
Laurent@814:         #    wx.CallAfter(self.Parent.SetCurrentCursor, 1)
andrej@1782:         # else:
Laurent@814:         # Test if a segment have been handled
Laurent@814:         result = self.TestSegment(pos)
andrej@1743:         if result is not None:
Laurent@814:             if result[1] in (NORTH, SOUTH):
Laurent@814:                 wx.CallAfter(self.Parent.SetCurrentCursor, 4)
Laurent@814:             elif result[1] in (EAST, WEST):
Laurent@814:                 wx.CallAfter(self.Parent.SetCurrentCursor, 5)
Laurent@814:             self.Handle = (HANDLE_SEGMENT, result)
Laurent@814:         # Execute the default method for a graphic element
Laurent@814:         else:
Laurent@814:             Graphic_Element.OnLeftDown(self, event, dc, scaling)
Laurent@814:         self.oldPos = pos
andrej@1730: 
Laurent@814:     # Method called when a RightUp event has been generated
Laurent@814:     def OnRightUp(self, event, dc, scaling):
Laurent@814:         pos = GetScaledEventPosition(event, dc, scaling)
Laurent@814:         # Test if a segment has been handled
Laurent@814:         result = self.TestSegment(pos, True)
andrej@1743:         if result is not None:
Laurent@814:             self.Handle = (HANDLE_SEGMENT, result)
Laurent@814:             # Popup the menu with special items for a wire
Laurent@814:             self.Parent.PopupWireMenu(0 < result[0] < len(self.Segments) - 1)
Laurent@814:         else:
Laurent@814:             # Execute the default method for a graphic element
Laurent@814:             Graphic_Element.OnRightUp(self, event, dc, scaling)
andrej@1730: 
Laurent@814:     # Method called when a LeftDClick event has been generated
Laurent@814:     def OnLeftDClick(self, event, dc, scaling):
Laurent@814:         rect = self.GetRedrawRect()
Laurent@814:         if event.ControlDown():
Laurent@814:             direction = (self.StartPoint[1], self.EndPoint[1])
Laurent@814:             if direction in [(EAST, WEST), (WEST, EAST)]:
andrej@2437:                 avgy = (self.StartPoint[0].y + self.EndPoint[0].y) // 2
Laurent@814:                 if scaling is not None:
andrej@2437:                     avgy = round(avgy / scaling[1]) * scaling[1]
Laurent@814:                 if self.StartConnected is not None:
Laurent@814:                     movey = avgy - self.StartPoint[0].y
Laurent@814:                     startblock = self.StartConnected.GetParentBlock()
Laurent@814:                     startblock.Move(0, movey)
Laurent@814:                     startblock.RefreshModel()
Laurent@814:                     rect.Union(startblock.GetRedrawRect(0, movey))
Laurent@814:                 else:
Laurent@814:                     self.MoveStartPoint(wx.Point(self.StartPoint[0].x, avgy))
Laurent@814:                 if self.EndConnected is not None:
Laurent@814:                     movey = avgy - self.EndPoint[0].y
Laurent@814:                     endblock = self.EndConnected.GetParentBlock()
Laurent@814:                     endblock.Move(0, movey)
Laurent@814:                     endblock.RefreshModel()
Laurent@814:                     rect.Union(endblock.GetRedrawRect(0, movey))
Laurent@814:                 else:
Laurent@814:                     self.MoveEndPoint(wx.Point(self.EndPoint[0].x, avgy))
Laurent@814:                 self.Parent.RefreshBuffer()
Laurent@814:             elif direction in [(NORTH, SOUTH), (SOUTH, NORTH)]:
andrej@2437:                 avgx = (self.StartPoint[0].x + self.EndPoint[0].x) // 2
Laurent@814:                 if scaling is not None:
andrej@2437:                     avgx = round(avgx / scaling[0]) * scaling[0]
Laurent@814:                 if self.StartConnected is not None:
Laurent@814:                     movex = avgx - self.StartPoint[0].x
Laurent@814:                     startblock = self.StartConnected.GetParentBlock()
Laurent@814:                     startblock.Move(movex, 0)
Laurent@814:                     startblock.RefreshModel()
Laurent@814:                     rect.Union(startblock.GetRedrawRect(movex, 0))
Laurent@814:                 else:
Laurent@814:                     self.MoveStartPoint(wx.Point(avgx, self.StartPoint[0].y))
Laurent@814:                 if self.EndConnected is not None:
Laurent@814:                     movex = avgx - self.EndPoint[0].x
Laurent@814:                     endblock = self.EndConnected.GetParentBlock()
Laurent@814:                     endblock.Move(movex, 0)
Laurent@814:                     endblock.RefreshModel()
Laurent@814:                     rect.Union(endblock.GetRedrawRect(movex, 0))
Laurent@814:                 else:
Laurent@814:                     self.MoveEndPoint(wx.Point(avgx, self.EndPoint[0].y))
Laurent@814:                 self.Parent.RefreshBuffer()
Laurent@814:         else:
Laurent@814:             self.ResetPoints()
Laurent@814:             self.GeneratePoints()
Laurent@814:             self.RefreshModel()
Laurent@814:             self.Parent.RefreshBuffer()
Laurent@814:         rect.Union(self.GetRedrawRect())
Laurent@814:         self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
andrej@1730: 
Laurent@814:     # Method called when a Motion event has been generated
Laurent@814:     def OnMotion(self, event, dc, scaling):
Laurent@814:         pos = GetScaledEventPosition(event, dc, scaling)
Laurent@814:         if not event.Dragging():
Laurent@814:             # Test if a segment has been handled
Laurent@814:             result = self.TestSegment(pos)
Laurent@814:             if result:
Laurent@814:                 if result[1] in (NORTH, SOUTH):
Laurent@814:                     wx.CallAfter(self.Parent.SetCurrentCursor, 4)
Laurent@814:                 elif result[1] in (EAST, WEST):
Laurent@814:                     wx.CallAfter(self.Parent.SetCurrentCursor, 5)
Laurent@814:                 return 0, 0
Laurent@814:             else:
Laurent@814:                 # Execute the default method for a graphic element
Laurent@814:                 return Graphic_Element.OnMotion(self, event, dc, scaling)
Laurent@814:         else:
Laurent@814:             # Execute the default method for a graphic element
Laurent@814:             return Graphic_Element.OnMotion(self, event, dc, scaling)
andrej@1730: 
Laurent@814:     # Refreshes the wire state according to move defined and handle selected
Laurent@814:     def ProcessDragging(self, movex, movey, event, scaling):
Laurent@814:         handle_type, handle = self.Handle
Laurent@814:         # A point has been handled
Laurent@814:         if handle_type == HANDLE_POINT:
Laurent@814:             movex = max(-self.Points[handle].x + POINT_RADIUS, movex)
Laurent@814:             movey = max(-self.Points[handle].y + POINT_RADIUS, movey)
Laurent@814:             if scaling is not None:
Laurent@814:                 movex = round_scaling(self.Points[handle].x + movex, scaling[0]) - self.Points[handle].x
Laurent@814:                 movey = round_scaling(self.Points[handle].y + movey, scaling[1]) - self.Points[handle].y
Laurent@814:             # Try to connect point to a connector
Laurent@814:             new_pos = wx.Point(self.Points[handle].x + movex, self.Points[handle].y + movey)
Laurent@814:             connector = self.Parent.FindBlockConnector(new_pos, self.GetConnectionDirection())
Laurent@814:             if connector:
Laurent@814:                 if handle == 0 and self.EndConnected != connector:
Laurent@814:                     connector.HighlightParentBlock(True)
Laurent@814:                     connector.Connect((self, handle))
Laurent@814:                     self.SetStartPointDirection(connector.GetDirection())
Laurent@814:                     self.ConnectStartPoint(connector.GetPosition(), connector)
Laurent@814:                     pos = connector.GetPosition()
Laurent@814:                     movex = pos.x - self.oldPos.x
Laurent@814:                     movey = pos.y - self.oldPos.y
Laurent@814:                     if not connector.IsCompatible(self.GetEndConnectedType()):
Laurent@814:                         self.SetValid(False)
Laurent@814:                     self.Dragging = False
Laurent@814:                 elif handle != 0 and self.StartConnected != connector:
Laurent@814:                     connector.HighlightParentBlock(True)
Laurent@814:                     connector.Connect((self, handle))
Laurent@814:                     self.SetEndPointDirection(connector.GetDirection())
Laurent@814:                     self.ConnectEndPoint(connector.GetPosition(), connector)
Laurent@814:                     pos = connector.GetPosition()
Laurent@814:                     movex = pos.x - self.oldPos.x
Laurent@814:                     movey = pos.y - self.oldPos.y
Laurent@814:                     if not connector.IsCompatible(self.GetStartConnectedType()):
Laurent@814:                         self.SetValid(False)
Laurent@814:                     self.Dragging = False
Laurent@814:                 elif handle == 0:
Laurent@814:                     self.MoveStartPoint(new_pos)
Laurent@814:                 else:
Laurent@814:                     self.MoveEndPoint(new_pos)
Laurent@814:             # If there is no connector, move the point
Laurent@814:             elif handle == 0:
Laurent@814:                 self.SetValid(True)
Laurent@814:                 if self.StartConnected:
Laurent@814:                     self.StartConnected.HighlightParentBlock(False)
Laurent@814:                     self.UnConnectStartPoint()
Laurent@814:                 self.MoveStartPoint(new_pos)
Laurent@814:             else:
Laurent@814:                 self.SetValid(True)
Laurent@814:                 if self.EndConnected:
Laurent@814:                     self.EndConnected.HighlightParentBlock(False)
Laurent@814:                     self.UnConnectEndPoint()
Laurent@814:                 self.MoveEndPoint(new_pos)
Laurent@814:             return movex, movey
Laurent@814:         # A segment has been handled, move a segment
Laurent@814:         elif handle_type == HANDLE_SEGMENT:
Laurent@814:             return self.MoveSegment(handle[0], movex, movey, scaling)
Laurent@814:         # Execute the default method for a graphic element
Laurent@814:         else:
Laurent@814:             return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
andrej@1730: 
Laurent@814:     # Refreshes the wire model
Laurent@814:     def RefreshModel(self, move=True):
Laurent@814:         if self.StartConnected and self.StartPoint[1] in [WEST, NORTH]:
Laurent@814:             self.StartConnected.RefreshParentBlock()
Laurent@814:         if self.EndConnected and self.EndPoint[1] in [WEST, NORTH]:
Laurent@814:             self.EndConnected.RefreshParentBlock()
andrej@1730: 
Laurent@814:     # Change the variable that indicates if this element is highlighted
Laurent@814:     def SetHighlighted(self, highlighted):
Laurent@814:         self.Highlighted = highlighted
Laurent@814:         if not highlighted:
Laurent@814:             self.OverStart = False
Laurent@814:             self.OverEnd = False
Laurent@814:         self.Refresh()
andrej@1730: 
Laurent@814:     def HighlightPoint(self, pos):
Laurent@814:         start, end = self.OverStart, self.OverEnd
Laurent@814:         self.OverStart = False
Laurent@814:         self.OverEnd = False
Laurent@814:         # Test if a point has been handled
Laurent@814:         result = self.TestPoint(pos)
andrej@1743:         if result is not None:
Laurent@814:             if result == 0 and self.StartConnected is not None:
Laurent@814:                 self.OverStart = True
Laurent@814:             elif result != 0 and self.EndConnected is not None:
Laurent@814:                 self.OverEnd = True
Laurent@814:         if start != self.OverStart or end != self.OverEnd:
Laurent@814:             self.Refresh()
andrej@1730: 
Laurent@814:     # Draws the highlightment of this element if it is highlighted
Laurent@814:     def DrawHighlightment(self, dc):
Laurent@814:         scalex, scaley = dc.GetUserScale()
Laurent@814:         dc.SetUserScale(1, 1)
surkovsv93@1544:         # If user trying to connect wire with wrong input, highlight will become red.
andrej@1828:         if self.ErrHighlight and not self.EndConnected:
surkovsv93@1544:             highlightcolor = wx.RED
surkovsv93@1544:         else:
surkovsv93@1544:             highlightcolor = HIGHLIGHTCOLOR
surkovsv93@1544:         dc.SetPen(MiterPen(highlightcolor, (2 * scalex + 5)))
surkovsv93@1544:         dc.SetBrush(wx.Brush(highlightcolor))
Laurent@814:         dc.SetLogicalFunction(wx.AND)
Laurent@814:         # Draw the start and end points if they are not connected or the mouse is over them
Laurent@814:         if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
andrej@1730:             dc.DrawCircle(round(self.Points[0].x * scalex),
andrej@1730:                           round(self.Points[0].y * scaley),
Laurent@814:                           (POINT_RADIUS + 1) * scalex + 2)
Laurent@814:         if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
Laurent@814:             dc.DrawCircle(self.Points[-1].x * scalex, self.Points[-1].y * scaley, (POINT_RADIUS + 1) * scalex + 2)
Laurent@814:         # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
Laurent@814:         if len(self.Points) > 1:
andrej@1730:             points = [wx.Point(round((self.Points[0].x - self.Segments[0][0]) * scalex),
Laurent@814:                                round((self.Points[0].y - self.Segments[0][1]) * scaley))]
Laurent@814:             points.extend([wx.Point(round(point.x * scalex), round(point.y * scaley)) for point in self.Points[1:-1]])
andrej@1730:             points.append(wx.Point(round((self.Points[-1].x + self.Segments[-1][0]) * scalex),
Laurent@814:                                    round((self.Points[-1].y + self.Segments[-1][1]) * scaley)))
Laurent@814:         else:
Laurent@814:             points = []
Laurent@814:         dc.DrawLines(points)
Laurent@814:         dc.SetLogicalFunction(wx.COPY)
Laurent@814:         dc.SetUserScale(scalex, scaley)
andrej@1730: 
Laurent@814:         if self.StartConnected is not None:
Laurent@814:             self.StartConnected.DrawHighlightment(dc)
Laurent@814:             self.StartConnected.Draw(dc)
Laurent@814:         if self.EndConnected is not None:
Laurent@814:             self.EndConnected.DrawHighlightment(dc)
Laurent@814:             self.EndConnected.Draw(dc)
andrej@1730: 
Laurent@814:     # Draws the wire lines and points
Laurent@814:     def Draw(self, dc):
Laurent@814:         Graphic_Element.Draw(self, dc)
Laurent@814:         if not self.Valid:
Laurent@814:             dc.SetPen(MiterPen(wx.RED))
Laurent@814:             dc.SetBrush(wx.RED_BRUSH)
andrej@2450:         elif isinstance(self.Value, bool) and self.Value:
Laurent@814:             if self.Forced:
Laurent@814:                 dc.SetPen(MiterPen(wx.CYAN))
Laurent@814:                 dc.SetBrush(wx.CYAN_BRUSH)
Laurent@814:             else:
Laurent@814:                 dc.SetPen(MiterPen(wx.GREEN))
Laurent@814:                 dc.SetBrush(wx.GREEN_BRUSH)
Laurent@814:         elif self.Value == "undefined":
Laurent@814:             dc.SetPen(MiterPen(wx.NamedColour("orange")))
Laurent@814:             dc.SetBrush(wx.Brush(wx.NamedColour("orange")))
Laurent@814:         elif self.Forced:
Laurent@814:             dc.SetPen(MiterPen(wx.BLUE))
Laurent@814:             dc.SetBrush(wx.BLUE_BRUSH)
Laurent@814:         else:
Laurent@814:             dc.SetPen(MiterPen(wx.BLACK))
Laurent@814:             dc.SetBrush(wx.BLACK_BRUSH)
Laurent@814:         # Draw the start and end points if they are not connected or the mouse is over them
Laurent@814:         if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
Laurent@814:             dc.DrawCircle(self.Points[0].x, self.Points[0].y, POINT_RADIUS)
Laurent@814:         if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
Laurent@814:             dc.DrawCircle(self.Points[-1].x, self.Points[-1].y, POINT_RADIUS)
Laurent@814:         # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
Laurent@814:         if len(self.Points) > 1:
Laurent@814:             points = [wx.Point(self.Points[0].x - self.Segments[0][0], self.Points[0].y - self.Segments[0][1])]
Laurent@814:             points.extend([point for point in self.Points[1:-1]])
Laurent@814:             points.append(wx.Point(self.Points[-1].x + self.Segments[-1][0], self.Points[-1].y + self.Segments[-1][1]))
Laurent@814:         else:
Laurent@814:             points = []
Laurent@814:         dc.DrawLines(points)
Laurent@814:         # Draw the segment selected in red
Laurent@814:         if not getattr(dc, "printing", False) and self.SelectedSegment is not None:
Laurent@814:             dc.SetPen(MiterPen(wx.BLUE, 3))
Laurent@814:             if self.SelectedSegment == len(self.Segments) - 1:
Laurent@814:                 end = 0
Laurent@814:             else:
Laurent@814:                 end = 1
Laurent@814:             dc.DrawLine(self.Points[self.SelectedSegment].x - 1, self.Points[self.SelectedSegment].y,
Laurent@814:                         self.Points[self.SelectedSegment + 1].x + end, self.Points[self.SelectedSegment + 1].y)
andrej@2450:         if self.Value is not None and not isinstance(self.Value, bool) and self.Value != "undefined":
Laurent@814:             dc.SetFont(self.Parent.GetMiniFont())
Laurent@814:             dc.SetTextForeground(wx.NamedColour("purple"))
andrej@2450:             if self.ValueSize is None and isinstance(self.ComputedValue, string_types):
Laurent@814:                 self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
Laurent@814:             if self.ValueSize is not None:
Laurent@814:                 width, height = self.ValueSize
Laurent@814:                 if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
andrej@2437:                     x = self.Points[0].x + width * (self.StartPoint[1][0] - 1) // 2
Laurent@814:                     y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
Laurent@814:                     dc.DrawText(self.ComputedValue, x, y)
andrej@2437:                     x = self.Points[-1].x + width * (self.EndPoint[1][0] - 1) // 2
Laurent@814:                     y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
Laurent@814:                     dc.DrawText(self.ComputedValue, x, y)
Laurent@814:                 else:
andrej@2437:                     middle = len(self.Segments) // 2 + len(self.Segments) % 2 - 1
andrej@2437:                     x = (self.Points[middle].x + self.Points[middle + 1].x - width) // 2
Laurent@814:                     if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
andrej@2437:                         y = (self.Points[middle].y + self.Points[middle + 1].y - height) // 2
Laurent@814:                     else:
Laurent@814:                         y = self.Points[middle].y - height
Laurent@814:                     dc.DrawText(self.ComputedValue, x, y)
Laurent@814:             dc.SetFont(self.Parent.GetFont())
Laurent@814:             dc.SetTextForeground(wx.BLACK)
Laurent@814: 
Laurent@814: 
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: #                           Graphic comment element
andrej@1782: # -------------------------------------------------------------------------------
Laurent@814: 
andrej@1736: 
Laurent@814: def FilterHighlightsByRow(highlights, row, length):
Laurent@814:     _highlights = []
Laurent@814:     for start, end, highlight_type in highlights:
Laurent@814:         if start[0] <= row and end[0] >= row:
Laurent@814:             if start[0] < row:
Laurent@814:                 start = (row, 0)
Laurent@814:             if end[0] > row:
Laurent@814:                 end = (row, length)
Laurent@814:             _highlights.append((start, end, highlight_type))
Laurent@814:     return _highlights
Laurent@814: 
andrej@1736: 
Laurent@814: def FilterHighlightsByColumn(highlights, start_col, end_col):
Laurent@814:     _highlights = []
Laurent@814:     for start, end, highlight_type in highlights:
Laurent@814:         if end[1] > start_col and start[1] < end_col:
Laurent@814:             start = (start[0], max(start[1], start_col) - start_col)
Laurent@814:             end = (end[0], min(end[1], end_col) - start_col)
Laurent@814:             _highlights.append((start, end, highlight_type))
Laurent@814:     return _highlights
Laurent@814: 
Laurent@814: 
Laurent@814: class Comment(Graphic_Element):
andrej@1736:     """
andrej@1736:     Class that implements a comment
andrej@1736:     """
Laurent@814: 
Laurent@814:     # Create a new comment
andrej@1744:     def __init__(self, parent, content, id=None):
Laurent@814:         Graphic_Element.__init__(self, parent)
Laurent@814:         self.Id = id
Laurent@814:         self.Content = content
Laurent@814:         self.Pos = wx.Point(0, 0)
Laurent@814:         self.Size = wx.Size(0, 0)
Laurent@814:         self.Highlights = []
andrej@1730: 
Laurent@814:     # Make a clone of this comment
andrej@1744:     def Clone(self, parent, id=None, pos=None):
Laurent@814:         comment = Comment(parent, self.Content, id)
Laurent@814:         if pos is not None:
Laurent@814:             comment.SetPosition(pos.x, pos.y)
Laurent@814:         comment.SetSize(self.Size[0], self.Size[1])
Laurent@814:         return comment
andrej@1730: 
Laurent@814:     # Method for keeping compatibility with others
Laurent@814:     def Clean(self):
Laurent@814:         pass
andrej@1730: 
Laurent@814:     # Delete this comment by calling the corresponding method
Laurent@814:     def Delete(self):
Laurent@814:         self.Parent.DeleteComment(self)
andrej@1730: 
Laurent@814:     # Refresh the comment bounding box
Laurent@814:     def RefreshBoundingBox(self):
Laurent@814:         self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
andrej@1730: 
Laurent@814:     # Changes the comment size
Laurent@814:     def SetSize(self, width, height):
Laurent@814:         self.Size.SetWidth(width)
Laurent@814:         self.Size.SetHeight(height)
Laurent@814:         self.RefreshBoundingBox()
Laurent@814: 
Laurent@814:     # Returns the comment size
Laurent@814:     def GetSize(self):
Laurent@814:         return self.Size.GetWidth(), self.Size.GetHeight()
andrej@1730: 
Laurent@814:     # Returns the comment minimum size
Laurent@814:     def GetMinSize(self):
Laurent@814:         dc = wx.ClientDC(self.Parent)
Laurent@814:         min_width = 0
Laurent@814:         min_height = 0
Laurent@814:         # The comment minimum size is the maximum size of words in the content
Laurent@814:         for line in self.Content.splitlines():
Laurent@814:             for word in line.split(" "):
Laurent@814:                 wordwidth, wordheight = dc.GetTextExtent(word)
Laurent@814:                 min_width = max(min_width, wordwidth)
Laurent@814:                 min_height = max(min_height, wordheight)
Laurent@814:         return min_width + 20, min_height + 20
andrej@1730: 
Laurent@814:     # Changes the comment position
Laurent@814:     def SetPosition(self, x, y):
Laurent@814:         self.Pos.x = x
Laurent@814:         self.Pos.y = y
Laurent@814:         self.RefreshBoundingBox()
Laurent@814: 
Laurent@814:     # Changes the comment content
Laurent@814:     def SetContent(self, content):
Laurent@814:         self.Content = content
Laurent@814:         min_width, min_height = self.GetMinSize()
Laurent@814:         self.Size[0] = max(self.Size[0], min_width)
Laurent@814:         self.Size[1] = max(self.Size[1], min_height)
Laurent@814:         self.RefreshBoundingBox()
Laurent@814: 
Laurent@814:     # Returns the comment content
Laurent@814:     def GetContent(self):
Laurent@814:         return self.Content
Laurent@814: 
Laurent@814:     # Returns the comment position
Laurent@814:     def GetPosition(self):
Laurent@814:         return self.Pos.x, self.Pos.y
andrej@1730: 
Laurent@814:     # Moves the comment
andrej@1744:     def Move(self, dx, dy, connected=True):
Laurent@814:         self.Pos.x += dx
Laurent@814:         self.Pos.y += dy
Laurent@814:         self.RefreshBoundingBox()
andrej@1730: 
Laurent@814:     # Resizes the comment with the position and the size given
Laurent@814:     def Resize(self, x, y, width, height):
Laurent@814:         self.Move(x, y)
Laurent@814:         self.SetSize(width, height)
andrej@1730: 
Laurent@814:     # Method called when a RightUp event have been generated
Laurent@814:     def OnRightUp(self, event, dc, scaling):
Laurent@814:         # Popup the default menu
Laurent@814:         self.Parent.PopupDefaultMenu()
andrej@1730: 
Laurent@814:     # Refreshes the wire state according to move defined and handle selected
Laurent@814:     def ProcessDragging(self, movex, movey, event, scaling):
Laurent@814:         if self.Parent.GetDrawingMode() != FREEDRAWING_MODE and self.Parent.CurrentLanguage == "LD":
Laurent@814:             movex = movey = 0
Laurent@814:         return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
andrej@1730: 
Laurent@814:     # Refreshes the comment model
Laurent@814:     def RefreshModel(self, move=True):
Laurent@814:         self.Parent.RefreshCommentModel(self)
andrej@1730: 
Laurent@814:     # Method called when a LeftDClick event have been generated
Laurent@814:     def OnLeftDClick(self, event, dc, scaling):
Laurent@814:         # Edit the comment content
Laurent@814:         self.Parent.EditCommentContent(self)
andrej@1730: 
Laurent@814:     # Adds an highlight to the comment
Laurent@814:     def AddHighlight(self, infos, start, end, highlight_type):
Laurent@814:         if infos[0] == "content":
Laurent@814:             AddHighlight(self.Highlights, (start, end, highlight_type))
andrej@1730: 
Laurent@814:     # Removes an highlight from the comment
Laurent@814:     def RemoveHighlight(self, infos, start, end, highlight_type):
Laurent@814:         RemoveHighlight(self.Highlights, (start, end, highlight_type))
andrej@1730: 
Laurent@814:     # Removes all the highlights of one particular type from the comment
Laurent@814:     def ClearHighlight(self, highlight_type=None):
Laurent@814:         self.Highlights = ClearHighlights(self.Highlights, highlight_type)
andrej@1730: 
Laurent@814:     # Draws the highlightment of this element if it is highlighted
Laurent@814:     def DrawHighlightment(self, dc):
Laurent@814:         scalex, scaley = dc.GetUserScale()
Laurent@814:         dc.SetUserScale(1, 1)
Laurent@814:         dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
Laurent@814:         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
Laurent@814:         dc.SetLogicalFunction(wx.AND)
andrej@1730: 
Laurent@814:         left = (self.Pos.x - 1) * scalex - 2
Laurent@814:         right = (self.Pos.x + self.Size[0] + 1) * scalex + 2
Laurent@814:         top = (self.Pos.y - 1) * scaley - 2
Laurent@814:         bottom = (self.Pos.y + self.Size[1] + 1) * scaley + 2
Laurent@814:         angle_top = (self.Pos.x + self.Size[0] - 9) * scalex + 2
Laurent@814:         angle_right = (self.Pos.y + 9) * scaley - 2
andrej@1730: 
Laurent@814:         polygon = [wx.Point(left, top), wx.Point(angle_top, top),
Laurent@814:                    wx.Point(right, angle_right), wx.Point(right, bottom),
Laurent@814:                    wx.Point(left, bottom)]
Laurent@814:         dc.DrawPolygon(polygon)
andrej@1730: 
Laurent@814:         dc.SetLogicalFunction(wx.COPY)
Laurent@814:         dc.SetUserScale(scalex, scaley)
andrej@1730: 
Laurent@814:     # Draws the comment and its content
Laurent@814:     def Draw(self, dc):
Laurent@814:         Graphic_Element.Draw(self, dc)
Laurent@814:         dc.SetPen(MiterPen(wx.BLACK))
Laurent@814:         dc.SetBrush(wx.WHITE_BRUSH)
Laurent@814:         # Draws the comment shape
andrej@1730:         polygon = [wx.Point(self.Pos.x, self.Pos.y),
Laurent@814:                    wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
Laurent@814:                    wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10),
Laurent@814:                    wx.Point(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1]),
Laurent@814:                    wx.Point(self.Pos.x, self.Pos.y + self.Size[1])]
Laurent@814:         dc.DrawPolygon(polygon)
andrej@1631: 
andrej@1631:         # dc.SetBrush call is workaround for the issue with wx.PrinterDC
andrej@1631:         # with wxPython 3.0 on GNU/Linux (don't remove it)
andrej@1631:         dc.SetBrush(wx.WHITE_BRUSH)
Laurent@814:         lines = [wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
Laurent@814:                  wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10),
Laurent@814:                  wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10)]
Laurent@814:         dc.DrawLines(lines)
andrej@1631: 
Laurent@814:         # Draws the comment content
Laurent@814:         y = self.Pos.y + 10
Laurent@814:         for idx, line in enumerate(self.Content.splitlines()):
Laurent@814:             first = True
Laurent@814:             linetext = ""
Laurent@814:             words = line.split(" ")
Laurent@814:             if not getattr(dc, "printing", False):
Laurent@814:                 highlights = FilterHighlightsByRow(self.Highlights, idx, len(line))
Laurent@814:                 highlights_offset = 0
Laurent@814:             for i, word in enumerate(words):
Laurent@814:                 if first:
Laurent@814:                     text = word
Laurent@814:                 else:
Laurent@814:                     text = linetext + " " + word
Laurent@814:                 wordwidth, wordheight = dc.GetTextExtent(text)
Laurent@814:                 if y + wordheight > self.Pos.y + self.Size[1] - 10:
Laurent@814:                     break
Laurent@814:                 if wordwidth < self.Size[0] - 20:
Laurent@814:                     if i < len(words) - 1:
Laurent@814:                         linetext = text
Laurent@814:                         first = False
Laurent@814:                     else:
Laurent@814:                         dc.DrawText(text, self.Pos.x + 10, y)
Laurent@814:                         if not getattr(dc, "printing", False):
Laurent@814:                             DrawHighlightedText(dc, text, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(text)), self.Pos.x + 10, y)
Laurent@814:                             highlights_offset += len(text) + 1
Laurent@814:                         y += wordheight + 5
Laurent@814:                 else:
Laurent@814:                     if not first:
Laurent@814:                         dc.DrawText(linetext, self.Pos.x + 10, y)
Laurent@814:                         if not getattr(dc, "printing", False):
Laurent@814:                             DrawHighlightedText(dc, linetext, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(linetext)), self.Pos.x + 10, y)
Laurent@814:                             highlights_offset += len(linetext) + 1
Laurent@814:                     if first or i == len(words) - 1:
Laurent@814:                         if not first:
Laurent@814:                             y += wordheight + 5
Laurent@814:                             if y + wordheight > self.Pos.y + self.Size[1] - 10:
Laurent@814:                                 break
Laurent@814:                         dc.DrawText(word, self.Pos.x + 10, y)
Laurent@814:                         if not getattr(dc, "printing", False):
Laurent@814:                             DrawHighlightedText(dc, word, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(word)), self.Pos.x + 10, y)
Laurent@814:                             highlights_offset += len(word) + 1
Laurent@814:                     else:
Laurent@814:                         linetext = word
Laurent@814:                     y += wordheight + 5
Laurent@814:             if y + wordheight > self.Pos.y + self.Size[1] - 10:
Laurent@814:                 break