Adding support for drag'n dropping variable from global defined in configurations and resources to POU variable panel or body editor for declaring external variables
Adding support for drag'n dropping located variables from topology panel to configurations and resources variable panel for declaring global located variables
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
#based on the plcopen standard.
#
#Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#General Public License for more details.
#
#You should have received a copy of the GNU General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import wx
from time import time as gettime
from math import *
from types import *
import datetime
#-------------------------------------------------------------------------------
# Common constants
#-------------------------------------------------------------------------------
"""
Definition of constants for dimensions of graphic elements
"""
# FBD and SFC constants
MIN_MOVE = 5 # Minimum move before starting a element dragging
CONNECTOR_SIZE = 8 # Size of connectors
BLOCK_LINE_SIZE = 20 # Minimum size of each line in a block
HANDLE_SIZE = 6 # Size of the squares for handles
ANCHOR_DISTANCE = 5 # Distance where wire is automativally attached to a connector
POINT_RADIUS = 2 # Radius of the point of wire ends
MIN_SEGMENT_SIZE = 2 # Minimum size of the endling segments of a wire
# LD constants
LD_LINE_SIZE = 40 # Distance between two lines in a ladder rung
LD_ELEMENT_SIZE = (21, 15) # Size (width, height) of a ladder element (contact or coil)
LD_WIRE_SIZE = 30 # Size of a wire between two contact
LD_WIRECOIL_SIZE = 70 # Size of a wire between a coil and a contact
LD_POWERRAIL_WIDTH = 3 # Width of a Powerrail
LD_OFFSET = (10, 10) # Distance (x, y) between each comment and rung of the ladder
LD_COMMENT_DEFAULTSIZE = (600, 40) # Size (width, height) of a comment box
# SFC constants
SFC_STEP_DEFAULT_SIZE = (40, 30) # Default size of a SFC step
SFC_TRANSITION_SIZE = (20, 2) # Size of a SFC transition
SFC_DEFAULT_SEQUENCE_INTERVAL = 40 # Default size of the interval between two divergence branches
SFC_SIMULTANEOUS_SEQUENCE_EXTRA = 20 # Size of extra lines for simultaneous divergence and convergence
SFC_JUMP_SIZE = (12, 13) # Size of a SFC jump to step
SFC_WIRE_MIN_SIZE = 25 # Size of a wire between two elements
SFC_ACTION_MIN_SIZE = (100, 30) # Minimum size of an action block line
# Type definition constants for graphic elements
[INPUT, OUTPUT, INOUT] = range(3)
[CONNECTOR, CONTINUATION] = range(2)
[LEFTRAIL, RIGHTRAIL] = range(2)
[CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = range(4)
[COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET, COIL_RISING, COIL_FALLING] = range(6)
[SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = range(4)
# Constants for defining the type of dragging that has been selected
[HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = range(5)
# List of value for resize handle that are valid
VALID_HANDLES = [(1,1), (1,2), (1,3), (2,3), (3,3), (3,2), (3,1), (2,1)]
# Contants for defining the direction of a connector
[EAST, NORTH, WEST, SOUTH] = [(1,0), (0,-1), (-1,0), (0,1)]
# Contants for defining which mode is selected for each view
[MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT,
MODE_COIL, MODE_CONTACT, MODE_POWERRAIL, MODE_INITIALSTEP, MODE_STEP,
MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = range(15)
# Contants for defining alignment types for graphic group
[ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = range(6)
# Contants for defining which drawing mode is selected for app
[FREEDRAWING_MODE, DRIVENDRAWING_MODE] = [1, 2]
# Color for Highlighting
HIGHLIGHTCOLOR = wx.CYAN
# Define highlight types
ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0), wx.RED)
SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0), wx.WHITE)
# Define highlight refresh inhibition period in second
REFRESH_HIGHLIGHT_PERIOD = 0.1
HANDLE_CURSORS = {
(1, 1) : 2,
(3, 3) : 2,
(1, 3) : 3,
(3, 1) : 3,
(1, 2) : 4,
(3, 2) : 4,
(2, 1) : 5,
(2, 3) : 5
}
def round_scaling(x, n, constraint=0):
fraction = float(x) / float(n)
if constraint == - 1:
xround = int(fraction)
else:
xround = round(fraction)
if constraint == 1 and int(fraction) == xround:
xround += 1
return xround * n
"""
Basic vector operations for calculate wire points
"""
# Create a vector from two points and define if vector must be normal
def vector(p1, p2, normal = True):
vector = (p2.x - p1.x, p2.y - p1.y)
if normal:
return normalize(vector)
return vector
# Calculate the norm of a given vector
def norm(v):
return sqrt(v[0] * v[0] + v[1] * v[1])
# Normalize a given vector
def normalize(v):
v_norm = norm(v)
# Verifie if it is not a null vector
if v_norm > 0:
return (v[0] / v_norm, v[1] / v_norm)
else:
return v
# Calculate the scalar product of two vectors
def is_null_vector(v):
return v == (0, 0)
# Calculate the scalar product of two vectors
def add_vectors(v1, v2):
return (v1[0] + v2[0], v1[1] + v2[1])
# Calculate the scalar product of two vectors
def product(v1, v2):
return v1[0] * v2[0] + v1[1] * v2[1]
"""
Function that calculates the nearest point of the grid defined by scaling for the given point
"""
def GetScaledEventPosition(event, dc, scaling):
pos = event.GetLogicalPosition(dc)
if scaling:
pos.x = round(float(pos.x) / float(scaling[0])) * scaling[0]
pos.y = round(float(pos.y) / float(scaling[1])) * scaling[1]
return pos
"""
Function that choose a direction during the wire points generation
"""
def DirectionChoice(v_base, v_target, dir_target):
dir_product = product(v_base, v_target)
if dir_product < 0:
return (-v_base[0], -v_base[1])
elif dir_product == 0 and product(v_base, dir_target) != 0:
return dir_target
return v_base
SECOND = 1000000
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE
DAY = 24 * HOUR
def generate_time(value):
microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
negative = microseconds < 0
microseconds = abs(microseconds)
data = "T#"
not_null = False
if negative:
data += "-"
for val, format in [(int(microseconds) / DAY, "%dd"),
((int(microseconds) % DAY) / HOUR, "%dh"),
((int(microseconds) % HOUR) / MINUTE, "%dm"),
((int(microseconds) % MINUTE) / SECOND, "%ds")]:
if val > 0 or not_null:
data += format % val
not_null = True
data += "%gms" % (microseconds % SECOND / 1000.)
return data
def generate_date(value):
base_date = datetime.datetime(1970, 1, 1)
date = base_date + value
return date.strftime("DATE#%Y-%m-%d")
def generate_datetime(value):
base_date = datetime.datetime(1970, 1, 1)
date_time = base_date + value
return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
def generate_timeofday(value):
microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
negative = microseconds < 0
microseconds = abs(microseconds)
data = "TOD#"
for val, format in [(int(microseconds) / HOUR, "%2.2d:"),
((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),
((int(microseconds) % MINUTE) / SECOND, "%2.2d."),
(microseconds % SECOND, "%6.6d")]:
data += format % val
return data
TYPE_TRANSLATOR = {"TIME": generate_time,
"DATE": generate_date,
"DT": generate_datetime,
"TOD": generate_timeofday}
def MiterPen(colour, width=1, style=wx.SOLID):
pen = wx.Pen(colour, width, style)
pen.SetJoin(wx.JOIN_MITER)
pen.SetCap(wx.CAP_PROJECTING)
return pen
#-------------------------------------------------------------------------------
# Debug Data Consumer Class
#-------------------------------------------------------------------------------
class DebugDataConsumer:
def __init__(self):
self.LastValue = None
self.Value = None
self.DataType = None
self.LastForced = False
self.Forced = False
self.Inhibited = False
def Inhibit(self, inhibit):
self.Inhibited = inhibit
if not inhibit and self.LastValue is not None:
self.SetForced(self.LastForced)
self.SetValue(self.LastValue)
self.LastValue = None
def SetDataType(self, data_type):
self.DataType = data_type
def NewValue(self, tick, value, forced=False):
value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value)
if self.Inhibited:
self.LastValue = value
self.LastForced = forced
else:
self.SetForced(forced)
self.SetValue(value)
def SetValue(self, value):
self.Value = value
def GetValue(self):
return self.Value
def SetForced(self, forced):
self.Forced = forced
def IsForced(self):
return self.Forced
#-------------------------------------------------------------------------------
# Debug Viewer Class
#-------------------------------------------------------------------------------
REFRESH_PERIOD = 0.1
class DebugViewer:
def __init__(self, producer, debug, register_tick=True):
self.DataProducer = None
self.Debug = debug
self.RegisterTick = register_tick
self.Inhibited = False
self.DataConsumers = {}
self.LastRefreshTime = gettime()
self.RefreshTimer = wx.Timer(self, -1)
self.Bind(wx.EVT_TIMER, self.OnRefreshTimer, self.RefreshTimer)
self.SetDataProducer(producer)
def __del__(self):
self.DataProducer = None
self.DeleteDataConsumers()
self.RefreshTimer.Stop()
def SetDataProducer(self, producer):
if self.RegisterTick and self.Debug:
if producer is not None:
producer.SubscribeDebugIECVariable("__tick__", self)
if self.DataProducer is not None:
self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)
self.DataProducer = producer
def IsDebugging(self):
return self.Debug
def Inhibit(self, inhibit):
self.Inhibited = inhibit
for consumer, iec_path in self.DataConsumers.iteritems():
consumer.Inhibit(inhibit)
def AddDataConsumer(self, iec_path, consumer):
if self.DataProducer is None:
return False
result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer) is not None
if result is not None and consumer != self:
self.DataConsumers[consumer] = iec_path
consumer.SetDataType(self.GetDataType(iec_path))
return result
def RemoveDataConsumer(self, consumer):
iec_path = self.DataConsumers.pop(consumer, None)
if iec_path is not None:
self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
def GetDataType(self, iec_path):
if self.DataProducer is not None:
return self.DataProducer.GetDebugIECVariableType(iec_path)
return None
def ForceDataValue(self, iec_path, value):
if self.DataProducer is not None:
self.DataProducer.ForceDebugIECVariable(iec_path, value)
def ReleaseDataValue(self, iec_path):
if self.DataProducer is not None:
self.DataProducer.ReleaseDebugIECVariable(iec_path)
def DeleteDataConsumers(self):
if self.DataProducer is not None:
for consumer, iec_path in self.DataConsumers.iteritems():
self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
self.DataConsumers = {}
def OnRefreshTimer(self, event):
self.RefreshNewData()
event.Skip()
def NewDataAvailable(self):
self.RefreshTimer.Stop()
if not self.Inhibited:
current_time = gettime()
if current_time - self.LastRefreshTime > REFRESH_PERIOD:
self.LastRefreshTime = current_time
self.Inhibit(True)
wx.CallAfter(self.RefreshViewOnNewData)
def RefreshViewOnNewData(self):
if self:
self.RefreshNewData()
self.RefreshTimer.Start(int(REFRESH_PERIOD * 1000), oneShot=True)
def RefreshNewData(self):
self.Inhibit(False)
#-------------------------------------------------------------------------------
# Viewer Rubberband
#-------------------------------------------------------------------------------
"""
Class that implements a rubberband
"""
class RubberBand:
# Create a rubberband by indicated on which window it must be drawn
def __init__(self, viewer):
self.Viewer = viewer
self.drawingSurface = viewer.Editor
self.Reset()
# Method that initializes the internal attributes of the rubberband
def Reset(self):
self.startPoint = None
self.currentBox = None
self.lastBox = None
# Method that return if a box is currently edited
def IsShown(self):
return self.currentBox != None
# Method that returns the currently edited box
def GetCurrentExtent(self):
return self.currentBox
# Method called when a new box starts to be edited
def OnLeftDown(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
# Save the point for calculate the box position and size
self.startPoint = pos
self.currentBox = wx.Rect(pos.x, pos.y, 0, 0)
self.drawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
self.Redraw()
# Method called when dragging with a box edited
def OnMotion(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
# Save the last position and size of the box for erasing it
self.lastBox = wx.Rect(self.currentBox.x, self.currentBox.y, self.currentBox.width,
self.currentBox.height)
# Calculate new position and size of the box
if pos.x >= self.startPoint.x:
self.currentBox.x = self.startPoint.x
self.currentBox.width = pos.x - self.startPoint.x + 1
else:
self.currentBox.x = pos.x
self.currentBox.width = self.startPoint.x - pos.x + 1
if pos.y >= self.startPoint.y:
self.currentBox.y = self.startPoint.y
self.currentBox.height = pos.y - self.startPoint.y + 1
else:
self.currentBox.y = pos.y
self.currentBox.height = self.startPoint.y - pos.y + 1
self.Redraw()
# Method called when dragging is stopped
def OnLeftUp(self, event, dc, scaling):
self.drawingSurface.SetCursor(wx.NullCursor)
self.lastBox = self.currentBox
self.currentBox = None
self.Redraw()
# Method that erase the last box and draw the new box
def Redraw(self, dc = None):
if not dc:
dc = self.Viewer.GetLogicalDC()
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.lastBox:
# Erase last box
dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
self.lastBox.width * scalex, self.lastBox.height * scaley)
if self.currentBox:
# Draw current box
dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
self.currentBox.width * scalex, self.currentBox.height * scaley)
dc.SetUserScale(scalex, scaley)
# Erase last box
def Erase(self, dc = None):
if not dc:
dc = self.Viewer.GetLogicalDC()
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.lastBox:
dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley,
self.lastBox.width * scalex, self.lastBox.height * scalex)
dc.SetUserScale(scalex, scaley)
# Draw current box
def Draw(self, dc = None):
if not dc:
dc = self.Viewer.GetLogicalDC()
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.currentBox:
# Draw current box
dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley,
self.currentBox.width * scalex, self.currentBox.height * scaley)
dc.SetUserScale(scalex, scaley)
#-------------------------------------------------------------------------------
# Viewer ToolTip
#-------------------------------------------------------------------------------
"""
Class that implements a custom tool tip
"""
class ToolTip(wx.PopupWindow):
def __init__(self, parent, tip):
wx.PopupWindow.__init__(self, parent)
self.CurrentPosition = wx.Point(0, 0)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.SetTip(tip)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def SetTip(self, tip):
self.Tip = tip
wx.CallAfter(self.RefreshTip)
def MoveToolTip(self, pos):
self.CurrentPosition = pos
self.SetPosition(pos)
def RefreshTip(self):
if self:
w, h = self.GetTextExtent(self.Tip)
self.SetSize(wx.Size(w + 4, h + 4))
self.SetPosition(self.CurrentPosition)
self.Refresh()
def OnPaint(self, event):
dc = wx.AutoBufferedPaintDC(self)
dc.Clear()
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
dc.BeginDrawing()
w, h = dc.GetTextExtent(self.Tip)
dc.DrawRectangle(0, 0, w + 4, h + 4)
dc.DrawText(self.Tip, 2, 2)
dc.EndDrawing()
event.Skip()
#-------------------------------------------------------------------------------
# Helpers for highlighting text
#-------------------------------------------------------------------------------
def AddHighlight(highlights, infos):
RemoveHighlight(highlights, infos)
highlights.append(infos)
def RemoveHighlight(highlights, infos):
if infos in highlights:
highlights.remove(infos)
return True
return False
def ClearHighlight(highlights, highlight_type=None):
if highlight_type is not None:
return [highlight for highlight in highlights if highlight[2] != highlight_type]
return []
def DrawHighlightedText(dc, text, highlights, x, y):
current_pen = dc.GetPen()
dc.SetPen(wx.TRANSPARENT_PEN)
for start, end, highlight_type in highlights:
dc.SetBrush(wx.Brush(highlight_type[0]))
offset_width, offset_height = dc.GetTextExtent(text[:start[1]])
part = text[start[1]:end[1] + 1]
part_width, part_height = dc.GetTextExtent(part)
dc.DrawRectangle(x + offset_width, y, part_width, part_height)
dc.SetTextForeground(highlight_type[1])
dc.DrawText(part, x + offset_width, y)
dc.SetPen(current_pen)
dc.SetTextForeground(wx.BLACK)
#-------------------------------------------------------------------------------
# Graphic element base class
#-------------------------------------------------------------------------------
"""
Class that implements a generic graphic element
"""
class Graphic_Element:
# Create a new graphic element
def __init__(self, parent, id = None):
self.Parent = parent
self.Id = id
self.oldPos = None
self.StartPos = None
self.CurrentDrag = None
self.Handle = (None,None)
self.Dragging = False
self.Selected = False
self.Highlighted = False
self.Pos = wx.Point(0, 0)
self.Size = wx.Size(0, 0)
self.BoundingBox = wx.Rect(0, 0, 0, 0)
self.Visible = False
def GetDefinition(self):
return [self.Id], []
def TestVisible(self, screen):
self.Visible = self.GetRedrawRect().Intersects(screen)
def IsVisible(self):
return self.Visible
def SpreadCurrent(self):
pass
def GetConnectorTranslation(self, element):
return {}
def FindNearestConnector(self, position, connectors):
distances = []
for connector in connectors:
connector_pos = connector.GetRelPosition()
distances.append((sqrt((self.Pos.x + connector_pos.x - position.x) ** 2 +
(self.Pos.y + connector_pos.y - position.y) ** 2),
connector))
distances.sort()
if len(distances) > 0:
return distances[0][1]
return None
def IsOfType(self, type, reference):
return self.Parent.IsOfType(type, reference)
def IsEndType(self, type):
return self.Parent.IsEndType(type)
def GetDragging(self):
return self.Dragging
# Make a clone of this element
def Clone(self, parent):
return Graphic_Element(parent, self.Id)
# Changes the block position
def SetPosition(self, x, y):
self.Pos.x = x
self.Pos.y = y
self.RefreshConnected()
self.RefreshBoundingBox()
# Returns the block position
def GetPosition(self):
return self.Pos.x, self.Pos.y
# Changes the element size
def SetSize(self, width, height):
self.Size.SetWidth(width)
self.Size.SetHeight(height)
self.RefreshConnectors()
self.RefreshBoundingBox()
# Returns the element size
def GetSize(self):
return self.Size.GetWidth(), self.Size.GetHeight()
# Returns the minimum element size
def GetMinSize(self):
return 0, 0
# Refresh the element Bounding Box
def RefreshBoundingBox(self):
self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
# Refresh the element connectors position
def RefreshConnectors(self):
pass
# Refresh the position of wires connected to element inputs and outputs
def RefreshConnected(self):
pass
# Change the parent
def SetParent(self, parent):
self.Parent = parent
# Override this method for defining the method to call for deleting this element
def Delete(self):
pass
# Returns the Id
def GetId(self):
return self.Id
# Returns if the point given is in the bounding box
def HitTest(self, pt):
rect = self.BoundingBox
return rect.InsideXY(pt.x, pt.y)
# Returns if the point given is in the bounding box
def IsInSelection(self, rect):
return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
# Override this method for refreshing the bounding box
def RefreshBoundingBox(self):
pass
# Returns the bounding box
def GetBoundingBox(self):
return self.BoundingBox
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
dc = self.Parent.GetLogicalDC()
scalex, scaley = dc.GetUserScale()
rect = wx.Rect()
rect.x = self.BoundingBox.x - int(HANDLE_SIZE / scalex) - 3 - abs(movex)
rect.y = self.BoundingBox.y - int(HANDLE_SIZE / scaley) - 3 - abs(movey)
rect.width = self.BoundingBox.width + 2 * (int(HANDLE_SIZE / scalex) + abs(movex) + 1) + 4
rect.height = self.BoundingBox.height + 2 * (int(HANDLE_SIZE / scaley) + abs(movey) + 1) + 4
return rect
def Refresh(self, rect = None):
if self.Visible:
if rect is not None:
self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
else:
self.Parent.RefreshRect(self.Parent.GetScrolledRect(self.GetRedrawRect()), False)
# Change the variable that indicates if this element is selected
def SetSelected(self, selected):
self.Selected = selected
self.Refresh()
# Change the variable that indicates if this element is highlighted
def SetHighlighted(self, highlighted):
self.Highlighted = highlighted
self.Refresh()
# Test if the point is on a handle of this element
def TestHandle(self, event):
dc = self.Parent.GetLogicalDC()
scalex, scaley = dc.GetUserScale()
pos = event.GetPosition()
pt = wx.Point(*self.Parent.CalcUnscrolledPosition(pos.x, pos.y))
left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
extern_rect = wx.Rect(left, top, right + HANDLE_SIZE - left, bottom + HANDLE_SIZE - top)
intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
# Verify that this element is selected
if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
# Find if point is on a handle horizontally
if left <= pt.x < left + HANDLE_SIZE:
handle_x = 1
elif center <= pt.x < center + HANDLE_SIZE:
handle_x = 2
elif right <= pt.x < right + HANDLE_SIZE:
handle_x = 3
else:
handle_x = 0
# Find if point is on a handle vertically
if top <= pt.y < top + HANDLE_SIZE:
handle_y = 1
elif middle <= pt.y < middle + HANDLE_SIZE:
handle_y = 2
elif bottom <= pt.y < bottom + HANDLE_SIZE:
handle_y = 3
else:
handle_y = 0
# Verify that the result is valid
if (handle_x, handle_y) in VALID_HANDLES:
return handle_x, handle_y
return 0, 0
# Method called when a LeftDown event have been generated
def OnLeftDown(self, event, dc, scaling):
pos = event.GetLogicalPosition(dc)
# Test if an handle have been clicked
handle = self.TestHandle(event)
# Find which type of handle have been clicked,
# Save a resize event and change the cursor
cursor = HANDLE_CURSORS.get(handle, 1)
wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
if cursor > 1:
self.Handle = (HANDLE_RESIZE, handle)
else:
self.Handle = (HANDLE_MOVE, None)
self.SetSelected(False)
# Initializes the last position
self.oldPos = GetScaledEventPosition(event, dc, scaling)
self.StartPos = wx.Point(self.Pos.x, self.Pos.y)
self.CurrentDrag = wx.Point(0, 0)
# Method called when a LeftUp event have been generated
def OnLeftUp(self, event, dc, scaling):
# If a dragging have been initiated
if self.Dragging and self.oldPos:
self.RefreshModel()
self.Parent.RefreshBuffer()
wx.CallAfter(self.Parent.SetCurrentCursor, 0)
self.SetSelected(True)
self.oldPos = None
# Method called when a RightDown event have been generated
def OnRightDown(self, event, dc, scaling):
pass
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
if self.Dragging and self.oldPos:
self.RefreshModel()
self.Parent.RefreshBuffer()
wx.CallAfter(self.Parent.SetCurrentCursor, 0)
self.SetSelected(True)
self.oldPos = None
if self.Parent.Debug:
self.Parent.PopupForceMenu()
# Method called when a LeftDClick event have been generated
def OnLeftDClick(self, event, dc, scaling):
pass
# Method called when a Motion event have been generated
def OnMotion(self, event, dc, scaling):
# If the cursor is dragging and the element have been clicked
if event.Dragging() and self.oldPos:
# Calculate the movement of cursor
pos = event.GetLogicalPosition(dc)
movex = pos.x - self.oldPos.x
movey = pos.y - self.oldPos.y
# If movement is greater than MIN_MOVE then a dragging is initiated
if not self.Dragging and (abs(movex) > MIN_MOVE or abs(movey) > MIN_MOVE):
self.Dragging = True
# If a dragging have been initiated, refreshes the element state
if self.Dragging:
dragx, dragy = self.ProcessDragging(movex, movey, event, scaling)
if event.ControlDown() and self.Handle[0] == HANDLE_MOVE:
self.oldPos.x = self.StartPos.x + self.CurrentDrag.x
self.oldPos.y = self.StartPos.y + self.CurrentDrag.y
else:
self.oldPos.x += dragx
self.oldPos.y += dragy
return dragx, dragy
return movex, movey
# If cursor just pass over the element, changes the cursor if it is on a handle
else:
pos = event.GetLogicalPosition(dc)
handle = self.TestHandle(event)
# Find which type of handle have been clicked,
# Save a resize event and change the cursor
cursor = HANDLE_CURSORS.get(handle, 0)
wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
return 0, 0
# Moves the element
def Move(self, dx, dy, exclude = []):
self.Pos.x += dx
self.Pos.y += dy
self.RefreshConnected(exclude)
self.RefreshBoundingBox()
# Resizes the element from position and size given
def Resize(self, x, y, width, height):
self.Move(x, y)
self.SetSize(width, height)
# Moves and Resizes the element for fitting scaling
def AdjustToScaling(self, scaling):
if scaling is not None:
movex = round_scaling(self.Pos.x, scaling[0]) - self.Pos.x
movey = round_scaling(self.Pos.y, scaling[1]) - self.Pos.y
min_width, min_height = self.GetMinSize()
width = max(round_scaling(min_width, scaling[0], 1),
round_scaling(self.Size.width, scaling[0]))
height = max(round_scaling(min_height, scaling[1], 1),
round_scaling(self.Size.height, scaling[1]))
self.Resize(movex, movey, width, height)
return movex, movey
return 0, 0
# Refreshes the element state according to move defined and handle selected
def ProcessDragging(self, movex, movey, event, scaling, width_fac = 1, height_fac = 1):
handle_type, handle = self.Handle
# If it is a resize handle, calculate the values from resizing
if handle_type == HANDLE_RESIZE:
if scaling is not None:
scaling = (scaling[0] * width_fac, scaling[1] * height_fac)
x = y = start_x = start_y = 0
width, height = start_width, start_height = self.GetSize()
if handle[0] == 1:
movex = max(-self.BoundingBox.x, movex)
if scaling is not None:
movex = -(round_scaling(width - movex, scaling[0]) - width)
x = movex
if event.ShiftDown():
width -= 2 * movex
else:
width -= movex
elif handle[0] == 3:
if scaling is not None:
movex = round_scaling(width + movex, scaling[0]) - width
if event.ShiftDown():
movex = min(self.BoundingBox.x, movex)
x = -movex
width += 2 * movex
else:
width += movex
if handle[1] == 1:
movey = max(-self.BoundingBox.y, movey)
if scaling is not None:
movey = -(round_scaling(height - movey, scaling[1]) - height)
y = movey
if event.ShiftDown():
height -= 2 * movey
else:
height -= movey
elif handle[1] == 3:
if scaling is not None:
movey = round_scaling(height + movey, scaling[1]) - height
if event.ShiftDown():
movey = min(self.BoundingBox.y, movey)
y = -movey
height += 2 * movey
else:
height += movey
# Verify that new size is not lesser than minimum
min_width, min_height = self.GetMinSize()
if handle[0] != 2 and (width >= min_width or width > self.Size[0]):
start_x = x
start_width = width
else:
movex = 0
if handle[1] != 2 and (height >= min_height or height > self.Size[1]):
start_y = y
start_height = height
else:
movey = 0
if movex != 0 or movey != 0:
self.Resize(start_x, start_y, start_width, start_height)
return movex, movey
# If it is a move handle, Move this element
elif handle_type == HANDLE_MOVE:
movex = max(-self.BoundingBox.x, movex)
movey = max(-self.BoundingBox.y, movey)
if scaling is not None:
movex = round_scaling(self.Pos.x + movex, scaling[0]) - self.Pos.x
movey = round_scaling(self.Pos.y + movey, scaling[1]) - self.Pos.y
if event.ControlDown():
self.CurrentDrag.x = self.CurrentDrag.x + movex
self.CurrentDrag.y = self.CurrentDrag.y + movey
if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
movey = self.StartPos.y - self.Pos.y
else:
movex = self.StartPos.x - self.Pos.x
movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
self.Move(movex, movey)
return movex, movey
return 0, 0
# Override this method for defining the method to call for adding an highlight to this element
def AddHighlight(self, infos, start, end, highlight_type):
pass
# Override this method for defining the method to call for removing an highlight from this element
def RemoveHighlight(self, infos, start, end, highlight_type):
pass
# Override this method for defining the method to call for removing all the highlights of one particular type from this element
def ClearHighlight(self, highlight_type=None):
pass
# Override this method for defining the method to call for refreshing the model of this element
def RefreshModel(self, move=True):
pass
# Draws the highlightment of this element if it is highlighted (can be overwritten)
def DrawHighlightment(self, dc):
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
dc.SetLogicalFunction(wx.AND)
dc.DrawRectangle(int(round((self.Pos.x - 1) * scalex)) - 2,
int(round((self.Pos.y - 1) * scaley)) - 2,
int(round((self.Size.width + 3) * scalex)) + 5,
int(round((self.Size.height + 3) * scaley)) + 5)
dc.SetLogicalFunction(wx.COPY)
dc.SetUserScale(scalex, scaley)
# Draws the handles of this element if it is selected
def Draw(self, dc):
if not getattr(dc, "printing", False):
if self.Highlighted:
self.DrawHighlightment(dc)
if self.Selected:
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.BLACK_BRUSH)
left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
for x, y in [(left, top), (center, top), (right, top),
(left, middle), (right, middle),
(left, bottom), (center, bottom), (right, bottom)]:
dc.DrawRectangle(x, y, HANDLE_SIZE, HANDLE_SIZE)
dc.SetUserScale(scalex, scaley)
#-------------------------------------------------------------------------------
# Group of graphic elements
#-------------------------------------------------------------------------------
"""
Class that implements a group of graphic elements
"""
class Graphic_Group(Graphic_Element):
# Create a new group of graphic elements
def __init__(self, parent):
Graphic_Element.__init__(self, parent)
self.Elements = []
self.RefreshWireExclusion()
self.RefreshBoundingBox()
# Destructor
def __del__(self):
self.Elements = []
def GetDefinition(self):
blocks = []
wires = []
for element in self.Elements:
block, wire = element.GetDefinition()
blocks.extend(block)
wires.extend(wire)
return blocks, wires
# Make a clone of this element
def Clone(self, parent, pos = None):
group = Graphic_Group(parent)
connectors = {}
exclude_names = {}
wires = []
if pos is not None:
dx, dy = pos.x - self.BoundingBox.x, pos.y - self.BoundingBox.y
for element in self.Elements:
if isinstance(element, Wire):
wires.append(element)
else:
if pos is not None:
x, y = element.GetPosition()
new_pos = wx.Point(x + dx, y + dy)
newid = parent.GetNewId()
if parent.IsNamedElement(element):
name = parent.GenerateNewName(element, exclude_names)
exclude_names[name.upper()] = True
new_element = element.Clone(parent, newid, name, pos = new_pos)
else:
new_element = element.Clone(parent, newid, pos = new_pos)
new_element.AdjustToScaling(parent.Scaling)
else:
new_element = element.Clone(parent)
connectors.update(element.GetConnectorTranslation(new_element))
group.SelectElement(new_element)
for element in wires:
if pos is not None:
new_wire = element.Clone(parent, connectors, dx, dy)
else:
new_wire = element.Clone(parent, connectors)
if new_wire is not None:
if pos is not None:
parent.AddWire(new_wire)
group.SelectElement(new_wire)
if pos is not None:
for element in group.Elements:
if not isinstance(element, Wire):
parent.AddBlockInModel(element)
return group
def CanAddBlocks(self, parent):
valid = True
for element in self.Elements:
if not isinstance(element, Wire):
valid &= parent.CanAddElement(element)
return valid
def IsVisible(self):
for element in self.Elements:
if element.IsVisible():
return True
return False
# Refresh the list of wire excluded
def RefreshWireExclusion(self):
self.WireExcluded = []
for element in self.Elements:
if isinstance(element, Wire):
startblock = element.StartConnected.GetParentBlock()
endblock = element.EndConnected.GetParentBlock()
if startblock in self.Elements and endblock in self.Elements:
self.WireExcluded.append(element)
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = None
for element in self.Elements:
if rect is None:
rect = element.GetRedrawRect(movex, movey)
else:
rect = rect.Union(element.GetRedrawRect(movex, movey))
return rect
# Clean this group of elements
def Clean(self):
# Clean all the elements of the group
for element in self.Elements:
element.Clean()
# Delete this group of elements
def Delete(self):
# Delete all the elements of the group
for element in self.Elements:
element.Delete()
self.WireExcluded = []
# Returns if the point given is in the bounding box of one of the elements of this group
def HitTest(self, pt):
result = False
for element in self.Elements:
result |= element.HitTest(pt)
return result
# Returns if the element given is in this group
def IsElementIn(self, element):
return element in self.Elements
# Change the elements of the group
def SetElements(self, elements):
self.Elements = elements
self.RefreshWireExclusion()
self.RefreshBoundingBox()
# Returns the elements of the group
def GetElements(self):
return self.Elements
# Align the group elements
def AlignElements(self, horizontally, vertically):
minx = self.BoundingBox.x + self.BoundingBox.width
miny = self.BoundingBox.y + self.BoundingBox.height
maxx = self.BoundingBox.x
maxy = self.BoundingBox.y
for element in self.Elements:
if not isinstance(element, Wire):
posx, posy = element.GetPosition()
width, height = element.GetSize()
minx = min(minx, posx)
miny = min(miny, posy)
maxx = max(maxx, posx + width)
maxy = max(maxy, posy + height)
for element in self.Elements:
if not isinstance(element, Wire):
posx, posy = element.GetPosition()
width, height = element.GetSize()
movex = movey = 0
if horizontally == ALIGN_LEFT:
movex = minx - posx
elif horizontally == ALIGN_CENTER:
movex = (maxx + minx - width) / 2 - posx
elif horizontally == ALIGN_RIGHT:
movex = maxx - width - posx
if vertically == ALIGN_TOP:
movey = miny - posy
elif vertically == ALIGN_MIDDLE:
movey = (maxy + miny - height) / 2 - posy
elif vertically == ALIGN_BOTTOM:
movey = maxy - height - posy
if movex != 0 or movey != 0:
element.Move(movex, movey)
element.RefreshModel()
self.RefreshWireExclusion()
self.RefreshBoundingBox()
# Remove or select the given element if it is or not in the group
def SelectElement(self, element):
if element in self.Elements:
self.Elements.remove(element)
else:
self.Elements.append(element)
self.RefreshWireExclusion()
self.RefreshBoundingBox()
# Move this group of elements
def Move(self, movex, movey):
# Move all the elements of the group
for element in self.Elements:
if not isinstance(element, Wire):
element.Move(movex, movey, self.WireExcluded)
elif element in self.WireExcluded:
element.Move(movex, movey, True)
self.RefreshBoundingBox()
# Refreshes the bounding box of this group of elements
def RefreshBoundingBox(self):
if len(self.Elements) > 0:
bbox = self.Elements[0].GetBoundingBox()
minx, miny = bbox.x, bbox.y
maxx = bbox.x + bbox.width
maxy = bbox.y + bbox.height
for element in self.Elements[1:]:
bbox = element.GetBoundingBox()
minx = min(minx, bbox.x)
miny = min(miny, bbox.y)
maxx = max(maxx, bbox.x + bbox.width)
maxy = max(maxy, bbox.y + bbox.height)
self.BoundingBox = wx.Rect(minx, miny, maxx - minx, maxy - miny)
else:
self.BoundingBox = wx.Rect(0, 0, 0, 0)
self.Pos = wx.Point(self.BoundingBox.x, self.BoundingBox.y)
self.Size = wx.Size(self.BoundingBox.width, self.BoundingBox.height)
# Forbids to change the group position
def SetPosition(x, y):
pass
# Returns the position of this group
def GetPosition(self):
return self.BoundingBox.x, self.BoundingBox.y
# Forbids to change the group size
def SetSize(width, height):
pass
# Returns the size of this group
def GetSize(self):
return self.BoundingBox.width, self.BoundingBox.height
# Moves and Resizes the group elements for fitting scaling
def AdjustToScaling(self, scaling):
movex_max = movey_max = 0
for element in self.Elements:
movex, movey = element.AdjustToScaling(scaling)
movex_max = max(movex_max, abs(movex))
movey_max = max(movey_max, abs(movey))
return movex_max, movey_max
# Refreshes the group elements to move defined and handle selected
def ProcessDragging(self, movex, movey, event, scaling):
handle_type, handle = self.Handle
# If it is a move handle, Move this group elements
if handle_type == HANDLE_MOVE:
movex = max(-self.BoundingBox.x, movex)
movey = max(-self.BoundingBox.y, movey)
if scaling is not None:
movex = round_scaling(movex, scaling[0])
movey = round_scaling(movey, scaling[1])
if event.ControlDown():
self.CurrentDrag.x = self.CurrentDrag.x + movex
self.CurrentDrag.y = self.CurrentDrag.y + movey
if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
movey = self.StartPos.y - self.Pos.y
else:
movex = self.StartPos.x - self.Pos.x
movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
self.Move(movex, movey)
return movex, movey
return 0, 0
# Change the variable that indicates if this element is highlighted
def SetHighlighted(self, highlighted):
for element in self.Elements:
element.SetHighlighted(highlighted)
# Method called when a LeftDown event have been generated
def OnLeftDown(self, event, dc, scaling):
Graphic_Element.OnLeftDown(self, event, dc, scaling)
for element in self.Elements:
element.Handle = self.Handle
# Change the variable that indicates if the elemente is selected
def SetSelected(self, selected):
for element in self.Elements:
element.SetSelected(selected)
# Method called when a RightUp event has been generated
def OnRightUp(self, event, dc, scaling):
# Popup the menu with special items for a group
self.Parent.PopupGroupMenu()
# Refreshes the model of all the elements of this group
def RefreshModel(self):
for element in self.Elements:
element.RefreshModel()
#-------------------------------------------------------------------------------
# Connector for all types of blocks
#-------------------------------------------------------------------------------
"""
Class that implements a connector for any type of block
"""
class Connector:
# Create a new connector
def __init__(self, parent, name, type, position, direction, negated = False, edge = "none", onlyone = False):
self.ParentBlock = parent
self.Name = name
self.Type = type
self.Pos = position
self.Direction = direction
self.Wires = []
if self.ParentBlock.IsOfType("BOOL", type):
self.Negated = negated
self.Edge = edge
else:
self.Negated = False
self.Edge = "none"
self.OneConnected = onlyone
self.Valid = True
self.Value = None
self.Forced = False
self.Selected = False
self.Highlights = []
self.RefreshNameSize()
def Flush(self):
self.ParentBlock = None
for wire, handle in self.Wires:
wire.Flush()
self.Wires = []
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
parent_pos = self.ParentBlock.GetPosition()
x = min(parent_pos[0] + self.Pos.x, parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE)
y = min(parent_pos[1] + self.Pos.y, parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE)
if self.Direction[0] == 0:
width = 5
else:
width = CONNECTOR_SIZE
if self.Direction[1] == 0:
height = 5
else:
height = CONNECTOR_SIZE
return wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
# Change the connector selection
def SetSelected(self, selected):
self.Selected = selected
# Make a clone of the connector
def Clone(self, parent = None):
if parent is None:
parent = self.ParentBlock
return Connector(parent, self.Name, self.Type, wx.Point(self.Pos[0], self.Pos[1]),
self.Direction, self.Negated)
# Returns the connector parent block
def GetParentBlock(self):
return self.ParentBlock
# Returns the connector type
def GetType(self, raw = False):
if self.ParentBlock.IsEndType(self.Type) or raw:
return self.Type
elif (self.Negated or self.Edge != "none") and self.ParentBlock.IsOfType("BOOL", self.Type):
return "BOOL"
else:
return self.ParentBlock.GetConnectionResultType(self, self.Type)
# Returns the connector type
def GetConnectedType(self):
if self.ParentBlock.IsEndType(self.Type):
return self.Type
elif len(self.Wires) == 1:
return self.Wires[0][0].GetOtherConnectedType(self.Wires[0][1])
return self.Type
# Returns the connector type
def GetConnectedRedrawRect(self, movex, movey):
rect = None
for wire, handle in self.Wires:
if rect is None:
rect = wire.GetRedrawRect()
else:
rect = rect.Union(wire.GetRedrawRect())
return rect
# Returns if connector type is compatible with type given
def IsCompatible(self, type):
reference = self.GetType()
return self.ParentBlock.IsOfType(type, reference) or self.ParentBlock.IsOfType(reference, type)
# Changes the connector name
def SetType(self, type):
self.Type = type
# Returns the connector name
def GetName(self):
return self.Name
# Changes the connector name
def SetName(self, name):
self.Name = name
self.RefreshNameSize()
def RefreshForced(self):
self.Forced = False
for wire, handle in self.Wires:
self.Forced |= wire.IsForced()
def RefreshValue(self):
self.Value = self.ReceivingCurrent()
def RefreshValid(self):
self.Valid = True
for wire, handle in self.Wires:
self.Valid &= wire.GetValid()
def ReceivingCurrent(self):
current = False
for wire, handle in self.Wires:
value = wire.GetValue()
if current != "undefined" and isinstance(value, BooleanType):
current |= wire.GetValue()
elif value == "undefined":
current = "undefined"
return current
def SpreadCurrent(self, spreading):
for wire, handle in self.Wires:
wire.SetValue(spreading)
# Changes the connector name size
def RefreshNameSize(self):
if self.Name != "":
self.NameSize = self.ParentBlock.Parent.GetTextExtent(self.Name)
else:
self.NameSize = 0, 0
# Returns the connector name size
def GetNameSize(self):
return self.NameSize
# Returns the wires connected to the connector
def GetWires(self):
return self.Wires
# Returns the parent block Id
def GetBlockId(self):
return self.ParentBlock.GetId()
# Returns the connector relative position
def GetRelPosition(self):
return self.Pos
# Returns the connector absolute position
def GetPosition(self, size = True):
parent_pos = self.ParentBlock.GetPosition()
# If the position of the end of the connector is asked
if size:
x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
else:
x = parent_pos[0] + self.Pos.x
y = parent_pos[1] + self.Pos.y
return wx.Point(x, y)
# Change the connector relative position
def SetPosition(self, pos):
self.Pos = pos
# Returns the connector direction
def GetDirection(self):
return self.Direction
# Change the connector direction
def SetDirection(self, direction):
self.Direction = direction
# Connect a wire to this connector at the last place
def Connect(self, wire, refresh = True):
self.InsertConnect(len(self.Wires), wire, refresh)
# Connect a wire to this connector at the place given
def InsertConnect(self, idx, wire, refresh = True):
if wire not in self.Wires:
self.Wires.insert(idx, wire)
if refresh:
self.ParentBlock.RefreshModel(False)
# Returns the index of the wire given in the list of connected
def GetWireIndex(self, wire):
for i, (tmp_wire, handle) in enumerate(self.Wires):
if tmp_wire == wire:
return i
return None
# Unconnect a wire or all wires connected to the connector
def UnConnect(self, wire = None, unconnect = True, delete = False):
i = 0
found = False
while i < len(self.Wires) and not found:
if not wire or self.Wires[i][0] == wire:
# If Unconnect haven't been called from a wire, disconnect the connector in the wire
if unconnect:
if self.Wires[i][1] == 0:
self.Wires[i][0].UnConnectStartPoint(delete)
else:
self.Wires[i][0].UnConnectEndPoint(delete)
# Remove wire from connected
if wire:
self.Wires.pop(i)
found = True
i += 1
# If no wire defined, unconnect all wires
if not wire:
self.Wires = []
self.RefreshValid()
self.ParentBlock.RefreshModel(False)
# Returns if connector has one or more wire connected
def IsConnected(self):
return len(self.Wires) > 0
# Move the wires connected
def MoveConnected(self, exclude = []):
if len(self.Wires) > 0:
# Calculate the new position of the end point
parent_pos = self.ParentBlock.GetPosition()
x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
# Move the corresponding point on all the wires connected
for wire, index in self.Wires:
if wire not in exclude:
if index == 0:
wire.MoveStartPoint(wx.Point(x, y))
else:
wire.MoveEndPoint(wx.Point(x, y))
# Refreshes the model of all the wires connected
def RefreshWires(self):
for wire in self.Wires:
wire[0].RefreshModel()
# Refreshes the parent block model
def RefreshParentBlock(self):
self.ParentBlock.RefreshModel(False)
# Highlight the parent block
def HighlightParentBlock(self, highlight):
self.ParentBlock.SetHighlighted(highlight)
self.ParentBlock.Refresh()
# Returns all the blocks connected to this connector
def GetConnectedBlocks(self):
blocks = []
for wire, handle in self.Wires:
# Get other connector connected to each wire
if handle == 0:
connector = wire.GetEndConnected()
else:
connector = wire.GetStartConnected()
# Get parent block for this connector
if connector:
block = connector.GetParentBlock()
if block not in blocks:
blocks.append(block)
return blocks
# Returns the connector negated property
def IsNegated(self):
return self.Negated
# Changes the connector negated property
def SetNegated(self, negated):
if self.ParentBlock.IsOfType("BOOL", self.Type):
self.Negated = negated
self.Edge = "none"
# Returns the connector edge property
def GetEdge(self):
return self.Edge
# Changes the connector edge property
def SetEdge(self, edge):
if self.ParentBlock.IsOfType("BOOL", self.Type):
self.Edge = edge
self.Negated = False
# Tests if the point given is near from the end point of this connector
def TestPoint(self, pt, direction = None, exclude = True):
parent_pos = self.ParentBlock.GetPosition()
if (not (len(self.Wires) > 0 and self.OneConnected and exclude) or self.Type == "BOOL")\
and direction is None or self.Direction == direction:
# Calculate a square around the end point of this connector
x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE - ANCHOR_DISTANCE
y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE - ANCHOR_DISTANCE
width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
rect = wx.Rect(x, y, width, height)
return rect.InsideXY(pt.x, pt.y)
return False
# Draws the highlightment of this element if it is highlighted
def DrawHighlightment(self, dc):
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
pen = MiterPen(HIGHLIGHTCOLOR, 2 * scalex + 5)
pen.SetCap(wx.CAP_BUTT)
dc.SetPen(pen)
dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
dc.SetLogicalFunction(wx.AND)
parent_pos = self.ParentBlock.GetPosition()
posx = parent_pos[0] + self.Pos.x
posy = parent_pos[1] + self.Pos.y
xstart = parent_pos[0] + self.Pos.x
ystart = parent_pos[1] + self.Pos.y
if self.Direction[0] < 0:
xstart += 1
if self.Direction[1] < 0:
ystart += 1
xend = xstart + CONNECTOR_SIZE * self.Direction[0]
yend = ystart + CONNECTOR_SIZE * self.Direction[1]
dc.DrawLine(round((xstart + self.Direction[0]) * scalex), round((ystart + self.Direction[1]) * scaley),
round(xend * scalex), round(yend * scaley))
dc.SetLogicalFunction(wx.COPY)
dc.SetUserScale(scalex, scaley)
# Adds an highlight to the connector
def AddHighlight(self, infos, start, end, highlight_type):
if highlight_type == ERROR_HIGHLIGHT:
for wire, handle in self.Wires:
wire.SetValid(False)
AddHighlight(self.Highlights, (start, end, highlight_type))
# Removes an highlight from the connector
def RemoveHighlight(self, infos, start, end, highlight_type):
error = False
highlights = []
for highlight in self.Highlights:
if highlight != (start, end, highlight_type):
highlights.append(highlight)
error |= highlight == ERROR_HIGHLIGHT
self.Highlights = highlights
if not error:
for wire, handle in self.Wires:
wire.SetValid(wire.IsConnectedCompatible())
# Removes all the highlights of one particular type from the connector
def ClearHighlight(self, highlight_type=None):
error = False
if highlight_type is None:
self.Highlights = []
else:
highlights = []
for highlight in self.Highlights:
if highlight[2] != highlight_type:
highlights.append(highlight)
error |= highlight == ERROR_HIGHLIGHT
self.Highlights = highlights
if not error:
for wire, handle in self.Wires:
wire.SetValid(wire.IsConnectedCompatible())
# Draws the connector
def Draw(self, dc):
if self.Selected:
dc.SetPen(MiterPen(wx.BLUE, 3))
dc.SetBrush(wx.WHITE_BRUSH)
#elif len(self.Highlights) > 0:
# dc.SetPen(MiterPen(self.Highlights[-1][1]))
# dc.SetBrush(wx.Brush(self.Highlights[-1][0]))
else:
if not self.Valid:
dc.SetPen(MiterPen(wx.RED))
elif isinstance(self.Value, BooleanType) and self.Value:
if self.Forced:
dc.SetPen(MiterPen(wx.CYAN))
else:
dc.SetPen(MiterPen(wx.GREEN))
elif self.Value == "undefined":
dc.SetPen(MiterPen(wx.NamedColour("orange")))
elif self.Forced:
dc.SetPen(MiterPen(wx.BLUE))
else:
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.WHITE_BRUSH)
parent_pos = self.ParentBlock.GetPosition()
if getattr(dc, "printing", False):
name_size = dc.GetTextExtent(self.Name)
else:
name_size = self.NameSize
if self.Negated:
# If connector is negated, draw a circle
xcenter = parent_pos[0] + self.Pos.x + (CONNECTOR_SIZE * self.Direction[0]) / 2
ycenter = parent_pos[1] + self.Pos.y + (CONNECTOR_SIZE * self.Direction[1]) / 2
dc.DrawCircle(xcenter, ycenter, CONNECTOR_SIZE / 2)
else:
xstart = parent_pos[0] + self.Pos.x
ystart = parent_pos[1] + self.Pos.y
if self.Edge == "rising":
# If connector has a rising edge, draw a right arrow
dc.DrawLine(xstart, ystart, xstart - 4, ystart - 4)
dc.DrawLine(xstart, ystart, xstart - 4, ystart + 4)
elif self.Edge == "falling":
# If connector has a falling edge, draw a left arrow
dc.DrawLine(xstart, ystart, xstart + 4, ystart - 4)
dc.DrawLine(xstart, ystart, xstart + 4, ystart + 4)
if self.Direction[0] < 0:
xstart += 1
if self.Direction[1] < 0:
ystart += 1
if self.Selected:
xend = xstart + (CONNECTOR_SIZE - 2) * self.Direction[0]
yend = ystart + (CONNECTOR_SIZE - 2) * self.Direction[1]
dc.DrawLine(xstart + 2 * self.Direction[0], ystart + 2 * self.Direction[1], xend, yend)
else:
xend = xstart + CONNECTOR_SIZE * self.Direction[0]
yend = ystart + CONNECTOR_SIZE * self.Direction[1]
dc.DrawLine(xstart + self.Direction[0], ystart + self.Direction[1], xend, yend)
if self.Direction[0] != 0:
ytext = parent_pos[1] + self.Pos.y - name_size[1] / 2
if self.Direction[0] < 0:
xtext = parent_pos[0] + self.Pos.x + 5
else:
xtext = parent_pos[0] + self.Pos.x - (name_size[0] + 5)
if self.Direction[1] != 0:
xtext = parent_pos[0] + self.Pos.x - name_size[0] / 2
if self.Direction[1] < 0:
ytext = parent_pos[1] + self.Pos.y + 5
else:
ytext = parent_pos[1] + self.Pos.y - (name_size[1] + 5)
# Draw the text
dc.DrawText(self.Name, xtext, ytext)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
#-------------------------------------------------------------------------------
# Common Wire Element
#-------------------------------------------------------------------------------
"""
Class that implements a wire for connecting two blocks
"""
class Wire(Graphic_Element, DebugDataConsumer):
# Create a new wire
def __init__(self, parent, start = None, end = None):
Graphic_Element.__init__(self, parent)
DebugDataConsumer.__init__(self)
self.StartPoint = start
self.EndPoint = end
self.StartConnected = None
self.EndConnected = None
# If the start and end points are defined, calculate the wire
if start and end:
self.ResetPoints()
self.GeneratePoints()
else:
self.Points = []
self.Segments = []
self.SelectedSegment = None
self.Valid = True
self.ValueSize = None
self.ComputedValue = None
self.OverStart = False
self.OverEnd = False
self.ComputingType = False
self.ToolTip = None
self.Font = parent.GetMiniFont()
def GetDefinition(self):
if self.StartConnected is not None and self.EndConnected is not None:
startblock = self.StartConnected.GetParentBlock()
endblock = self.EndConnected.GetParentBlock()
return [], [(startblock.GetId(), endblock.GetId())]
return [], []
def Flush(self):
self.StartConnected = None
self.EndConnected = None
def CreateToolTip(self, pos):
if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
if isinstance(self.Value, StringType) and self.Value.find("#") == -1:
computed_value = "\"%s\""%self.Value
else:
computed_value = str(self.Value)
self.ToolTip = ToolTip(self.Parent, computed_value)
self.ToolTip.MoveToolTip(pos)
self.ToolTip.Show()
def MoveToolTip(self, pos):
if self.ToolTip is not None:
self.ToolTip.MoveToolTip(pos)
def ClearToolTip(self):
if self.ToolTip is not None:
self.ToolTip.Destroy()
self.ToolTip = None
# Returns the RedrawRect
def GetRedrawRect(self, movex = 0, movey = 0):
rect = Graphic_Element.GetRedrawRect(self, movex, movey)
if self.StartConnected:
rect = rect.Union(self.StartConnected.GetRedrawRect(movex, movey))
if self.EndConnected:
rect = rect.Union(self.EndConnected.GetRedrawRect(movex, movey))
if self.ValueSize is not None:
width, height = self.ValueSize
if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
x = self.Points[0].x + width * self.StartPoint[1][0] / 2
y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
rect = rect.Union(wx.Rect(x, y, width, height))
x = self.Points[-1].x + width * self.EndPoint[1][0] / 2
y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
rect = rect.Union(wx.Rect(x, y, width, height))
else:
middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
else:
y = self.Points[middle].y - height
rect = rect.Union(wx.Rect(x, y, width, height))
return rect
def Clone(self, parent, connectors = {}, dx = 0, dy = 0):
start_connector = connectors.get(self.StartConnected, None)
end_connector = connectors.get(self.EndConnected, None)
if start_connector is not None and end_connector is not None:
wire = Wire(parent)
wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
start_connector.Connect((wire, 0), False)
end_connector.Connect((wire, -1), False)
wire.ConnectStartPoint(start_connector.GetPosition(), start_connector)
wire.ConnectEndPoint(end_connector.GetPosition(), end_connector)
return wire
return None
# Forbids to change the wire position
def SetPosition(x, y):
pass
# Forbids to change the wire size
def SetSize(width, height):
pass
# Moves and Resizes the element for fitting scaling
def AdjustToScaling(self, scaling):
if scaling is not None:
movex_max = movey_max = 0
for idx, point in enumerate(self.Points):
if 0 < idx < len(self.Points) - 1:
movex = round_scaling(point.x, scaling[0]) - point.x
movey = round_scaling(point.y, scaling[1]) - point.y
if idx == 1:
if self.Segments[0][0] == 0:
movex = 0
elif (point.x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
if self.Segments[0][1] == 0:
movey = 0
elif (point.y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
movey = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - point.y
elif idx == len(self.Points) - 2:
if self.Segments[-1][0] == 0:
movex = 0
elif (self.Points[-1].x - (point.x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
movex = round_scaling(self.Points[-1].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
if self.Segments[-1][1] == 0:
movey = 0
elif (self.Points[-1].y - (point.y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
movey = round_scaling(self.Points[-1].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - point.y
movex_max = max(movex_max, movex)
movey_max = max(movey_max, movey)
point.x += movex
point.y += movey
return movex_max, movey_max
return 0, 0
# Returns connector to which start point is connected
def GetStartConnected(self):
return self.StartConnected
# Returns connector to which start point is connected
def GetStartConnectedType(self):
if self.StartConnected and not self.ComputingType:
self.ComputingType = True
computed_type = self.StartConnected.GetType()
self.ComputingType = False
return computed_type
return None
# Returns connector to which end point is connected
def GetEndConnected(self):
return self.EndConnected
# Returns connector to which end point is connected
def GetEndConnectedType(self):
if self.EndConnected and not self.ComputingType:
self.ComputingType = True
computed_type = self.EndConnected.GetType()
self.ComputingType = False
return computed_type
return None
def GetConnectionDirection(self):
if self.StartConnected is None and self.EndConnected is None:
return None
elif self.StartConnected is not None and self.EndConnected is None:
return (-self.StartPoint[1][0], -self.StartPoint[1][1])
elif self.StartConnected is None and self.EndConnected is not None:
return self.EndPoint
elif self.Handle is not None:
handle_type, handle = self.Handle
# A point has been handled
if handle_type == HANDLE_POINT:
if handle == 0:
return self.EndPoint
else:
return (-self.StartPoint[1][0], -self.StartPoint[1][1])
return None
def GetOtherConnected(self, connector):
if self.StartConnected == connector:
return self.EndConnected
else:
return self.StartConnected
def GetOtherConnectedType(self, handle):
if handle == 0:
return self.GetEndConnectedType()
else:
return self.GetStartConnectedType()
def IsConnectedCompatible(self):
if self.StartConnected:
return self.StartConnected.IsCompatible(self.GetEndConnectedType())
elif self.EndConnected:
return True
return False
def SetForced(self, forced):
if self.Forced != forced:
self.Forced = forced
if self.StartConnected:
self.StartConnected.RefreshForced()
if self.EndConnected:
self.EndConnected.RefreshForced()
if self.Visible:
self.Parent.UpdateRefreshRect(self.GetRedrawRect())
def SetValue(self, value):
if self.Value != value:
self.Value = value
if value is not None and not isinstance(value, BooleanType):
if isinstance(value, StringType) and value.find('#') == -1:
self.ComputedValue = "\"%s\""%value
else:
self.ComputedValue = str(value)
if self.ToolTip is not None:
self.ToolTip.SetTip(self.ComputedValue)
if len(self.ComputedValue) > 4:
self.ComputedValue = self.ComputedValue[:4] + "..."
if isinstance(self.ComputedValue, (StringType, UnicodeType)):
self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
else:
self.ValueSize = None
if self.StartConnected:
self.StartConnected.RefreshValue()
if self.EndConnected:
self.EndConnected.RefreshValue()
if self.Visible:
self.Parent.UpdateRefreshRect(self.GetRedrawRect())
if isinstance(value, BooleanType) and self.StartConnected is not None:
block = self.StartConnected.GetParentBlock()
block.SpreadCurrent()
# Unconnect the start and end points
def Clean(self):
if self.StartConnected:
self.UnConnectStartPoint()
if self.EndConnected:
self.UnConnectEndPoint()
# Delete this wire by calling the corresponding method
def Delete(self):
self.Parent.DeleteWire(self)
# Select a segment and not the whole wire. It's useful for Ladder Diagram
def SetSelectedSegment(self, segment):
# The last segment is indicated
if segment == -1:
segment = len(self.Segments) - 1
# The selected segment is reinitialised
if segment == None:
if self.StartConnected:
self.StartConnected.SetSelected(False)
if self.EndConnected:
self.EndConnected.SetSelected(False)
# The segment selected is the first
elif segment == 0:
if self.StartConnected:
self.StartConnected.SetSelected(True)
if self.EndConnected:
# There is only one segment
if len(self.Segments) == 1:
self.EndConnected.SetSelected(True)
else:
self.EndConnected.SetSelected(False)
# The segment selected is the last
elif segment == len(self.Segments) - 1:
if self.StartConnected:
self.StartConnected.SetSelected(False)
if self.EndConnected:
self.EndConnected.SetSelected(True)
self.SelectedSegment = segment
self.Refresh()
def SetValid(self, valid):
self.Valid = valid
if self.StartConnected:
self.StartConnected.RefreshValid()
if self.EndConnected:
self.EndConnected.RefreshValid()
def GetValid(self):
return self.Valid
# Reinitialize the wire points
def ResetPoints(self):
if self.StartPoint and self.EndPoint:
self.Points = [self.StartPoint[0], self.EndPoint[0]]
self.Segments = [self.StartPoint[1]]
else:
self.Points = []
self.Segments = []
# Refresh the wire bounding box
def RefreshBoundingBox(self):
if len(self.Points) > 0:
# If startpoint or endpoint is connected, save the point radius
start_radius = end_radius = 0
if not self.StartConnected:
start_radius = POINT_RADIUS
if not self.EndConnected:
end_radius = POINT_RADIUS
# Initialize minimum and maximum from the first point
minx, minbbxx = self.Points[0].x, self.Points[0].x - start_radius
maxx, maxbbxx = self.Points[0].x, self.Points[0].x + start_radius
miny, minbbxy = self.Points[0].y, self.Points[0].y - start_radius
maxy, maxbbxy = self.Points[0].y, self.Points[0].y + start_radius
# Actualize minimum and maximum with the other points
for point in self.Points[1:-1]:
minx, minbbxx = min(minx, point.x), min(minbbxx, point.x)
maxx, maxbbxx = max(maxx, point.x), max(maxbbxx, point.x)
miny, minbbxy = min(miny, point.y), min(minbbxy, point.y)
maxy, maxbbxy = max(maxy, point.y), max(maxbbxy, point.y)
if len(self.Points) > 1:
minx, minbbxx = min(minx, self.Points[-1].x), min(minbbxx, self.Points[-1].x - end_radius)
maxx, maxbbxx = max(maxx, self.Points[-1].x), max(maxbbxx, self.Points[-1].x + end_radius)
miny, minbbxy = min(miny, self.Points[-1].y), min(minbbxy, self.Points[-1].y - end_radius)
maxy, maxbbxy = max(maxy, self.Points[-1].y), max(maxbbxy, self.Points[-1].y + end_radius)
self.Pos.x, self.Pos.y = minx, miny
self.Size = wx.Size(maxx - minx, maxy - miny)
self.BoundingBox = wx.Rect(minbbxx, minbbxy, maxbbxx - minbbxx + 1, maxbbxy - minbbxy + 1)
# Refresh the realpoints that permits to keep the proportionality in wire during resizing
def RefreshRealPoints(self):
if len(self.Points) > 0:
self.RealPoints = []
# Calculate float relative position of each point with the minimum point
for point in self.Points:
self.RealPoints.append([float(point.x - self.Pos.x), float(point.y - self.Pos.y)])
# Returns the wire minimum size
def GetMinSize(self):
width = 1
height = 1
dir_product = product(self.StartPoint[1], self.EndPoint[1])
# The directions are opposed
if dir_product < 0:
if self.StartPoint[0] != 0:
width = MIN_SEGMENT_SIZE * 2
if self.StartPoint[1] != 0:
height = MIN_SEGMENT_SIZE * 2
# The directions are the same
elif dir_product > 0:
if self.StartPoint[0] != 0:
width = MIN_SEGMENT_SIZE
if self.StartPoint[1] != 0:
height = MIN_SEGMENT_SIZE
# The directions are perpendiculars
else:
width = MIN_SEGMENT_SIZE
height = MIN_SEGMENT_SIZE
return width + 1, height + 1
# Returns if the point given is on one of the wire segments
def HitTest(self, pt):
test = False
for i in xrange(len(self.Points) - 1):
rect = wx.Rect(0, 0, 0, 0)
x1, y1 = self.Points[i].x, self.Points[i].y
x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
# Calculate a rectangle around the segment
rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
test |= rect.InsideXY(pt.x, pt.y)
return test
# Returns the wire start or end point if the point given is on one of them
def TestPoint(self, pt):
# Test the wire start point
rect = wx.Rect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
if rect.InsideXY(pt.x, pt.y):
return 0
# Test the wire end point
if len(self.Points) > 1:
rect = wx.Rect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
if rect.InsideXY(pt.x, pt.y):
return -1
return None
# Returns the wire segment if the point given is on it
def TestSegment(self, pt, all=False):
for i in xrange(len(self.Segments)):
# If wire is not in a Ladder Diagram, first and last segments are excluded
if all or 0 < i < len(self.Segments) - 1:
x1, y1 = self.Points[i].x, self.Points[i].y
x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
# Calculate a rectangle around the segment
rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
if rect.InsideXY(pt.x, pt.y):
return i, self.Segments[i]
return None
# Define the wire points
def SetPoints(self, points, verify=True):
if len(points) > 1:
self.Points = [wx.Point(x, y) for x, y in points]
# Calculate the start and end directions
self.StartPoint = [None, vector(self.Points[0], self.Points[1])]
self.EndPoint = [None, vector(self.Points[-1], self.Points[-2])]
# Calculate the start and end points
self.StartPoint[0] = wx.Point(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0],
self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
self.EndPoint[0] = wx.Point(self.Points[-1].x + CONNECTOR_SIZE * self.EndPoint[1][0],
self.Points[-1].y + CONNECTOR_SIZE * self.EndPoint[1][1])
self.Points[0] = self.StartPoint[0]
self.Points[-1] = self.EndPoint[0]
# Calculate the segments directions
self.Segments = []
i = 0
while i < len(self.Points) - 1:
if verify and 0 < i < len(self.Points) - 2 and \
self.Points[i] == self.Points[i + 1] and \
self.Segments[-1] == vector(self.Points[i + 1], self.Points[i + 2]):
for j in xrange(2):
self.Points.pop(i)
else:
segment = vector(self.Points[i], self.Points[i + 1])
if is_null_vector(segment) and i > 0:
segment = (self.Segments[-1][1], self.Segments[-1][0])
if i < len(self.Points) - 2:
next = vector(self.Points[i + 1], self.Points[i + 2])
if next == segment or is_null_vector(add_vectors(segment, next)):
self.Points.insert(i + 1, wx.Point(self.Points[i + 1].x, self.Points[i + 1].y))
self.Segments.append(segment)
i += 1
self.RefreshBoundingBox()
self.RefreshRealPoints()
# Returns the position of the point indicated
def GetPoint(self, index):
if index < len(self.Points):
return self.Points[index].x, self.Points[index].y
return None
# Returns a list of the position of all wire points
def GetPoints(self, invert = False):
points = self.VerifyPoints()
points[0] = wx.Point(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0],
points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
points[-1] = wx.Point(points[-1].x - CONNECTOR_SIZE * self.EndPoint[1][0],
points[-1].y - CONNECTOR_SIZE * self.EndPoint[1][1])
# An inversion of the list is asked
if invert:
points.reverse()
return points
# Returns the position of the two selected segment points
def GetSelectedSegmentPoints(self):
if self.SelectedSegment != None and len(self.Points) > 1:
return self.Points[self.SelectedSegment:self.SelectedSegment + 2]
return []
# Returns if the selected segment is the first and/or the last of the wire
def GetSelectedSegmentConnections(self):
if self.SelectedSegment != None and len(self.Points) > 1:
return self.SelectedSegment == 0, self.SelectedSegment == len(self.Segments) - 1
return (True, True)
# Returns the connectors on which the wire is connected
def GetConnected(self):
connected = []
if self.StartConnected and self.StartPoint[1] == WEST:
connected.append(self.StartConnected)
if self.EndConnected and self.EndPoint[1] == WEST:
connected.append(self.EndConnected)
return connected
# Returns the id of the block connected to the first or the last wire point
def GetConnectedInfos(self, index):
if index == 0 and self.StartConnected:
return self.StartConnected.GetBlockId(), self.StartConnected.GetName()
elif index == -1 and self.EndConnected:
return self.EndConnected.GetBlockId(), self.EndConnected.GetName()
return None
# Update the wire points position by keeping at most possible the current positions
def GeneratePoints(self, realpoints = True):
i = 0
# Calculate the start enad end points with the minimum segment size in the right direction
end = wx.Point(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
start = wx.Point(self.StartPoint[0].x + self.StartPoint[1][0] * MIN_SEGMENT_SIZE,
self.StartPoint[0].y + self.StartPoint[1][1] * MIN_SEGMENT_SIZE)
# Evaluate the point till it's the last
while i < len(self.Points) - 1:
# The next point is the last
if i + 1 == len(self.Points) - 1:
# Calculate the direction from current point to end point
v_end = vector(self.Points[i], end)
# The current point is the first
if i == 0:
# If the end point is not in the start direction, a point is added
if v_end != self.Segments[0] or v_end == self.EndPoint[1]:
self.Points.insert(1, wx.Point(start.x, start.y))
self.Segments.insert(1, DirectionChoice((self.Segments[0][1],
self.Segments[0][0]), v_end, self.EndPoint[1]))
# The current point is the second
elif i == 1:
# The previous direction and the target direction are mainly opposed, a point is added
if product(v_end, self.Segments[0]) < 0:
self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
self.Segments[1][0]), v_end, self.EndPoint[1]))
# The previous direction and the end direction are the same or they are
# perpendiculars and the end direction points towards current segment
elif product(self.Segments[0], self.EndPoint[1]) >= 0 and product(self.Segments[1], self.EndPoint[1]) <= 0:
# Current point and end point are aligned
if self.Segments[0][0] != 0:
self.Points[1].x = end.x
if self.Segments[0][1] != 0:
self.Points[1].y = end.y
# If the previous direction and the end direction are the same, a point is added
if product(self.Segments[0], self.EndPoint[1]) > 0:
self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
self.Segments[1][0]), v_end, self.EndPoint[1]))
else:
# Current point is positioned in the middle of start point
# and end point on the current direction and a point is added
if self.Segments[0][0] != 0:
self.Points[1].x = (end.x + start.x) / 2
if self.Segments[0][1] != 0:
self.Points[1].y = (end.y + start.y) / 2
self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
self.Segments.insert(2, DirectionChoice((self.Segments[1][1],
self.Segments[1][0]), v_end, self.EndPoint[1]))
else:
# The previous direction and the end direction are perpendiculars
if product(self.Segments[i - 1], self.EndPoint[1]) == 0:
# The target direction and the end direction aren't mainly the same
if product(v_end, self.EndPoint[1]) <= 0:
# Current point and end point are aligned
if self.Segments[i - 1][0] != 0:
self.Points[i].x = end.x
if self.Segments[i - 1][1] != 0:
self.Points[i].y = end.y
# Previous direction is updated from the new point
if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
else:
test = True
# If the current point is the third, test if the second
# point can be aligned with the end point
if i == 2:
test_point = wx.Point(self.Points[1].x, self.Points[1].y)
if self.Segments[1][0] != 0:
test_point.y = end.y
if self.Segments[1][1] != 0:
test_point.x = end.x
vector_test = vector(self.Points[0], test_point, False)
test = norm(vector_test) > MIN_SEGMENT_SIZE and product(self.Segments[0], vector_test) > 0
# The previous point can be aligned
if test:
self.Points[i].x, self.Points[i].y = end.x, end.y
if self.Segments[i - 1][0] != 0:
self.Points[i - 1].y = end.y
if self.Segments[i - 1][1] != 0:
self.Points[i - 1].x = end.x
self.Segments[i] = (-self.EndPoint[1][0], -self.EndPoint[1][1])
else:
# Current point is positioned in the middle of previous point
# and end point on the current direction and a point is added
if self.Segments[1][0] != 0:
self.Points[2].x = (self.Points[1].x + end.x) / 2
if self.Segments[1][1] != 0:
self.Points[2].y = (self.Points[1].y + end.y) / 2
self.Points.insert(3, wx.Point(self.Points[2].x, self.Points[2].y))
self.Segments.insert(3, DirectionChoice((self.Segments[2][1],
self.Segments[2][0]), v_end, self.EndPoint[1]))
else:
# Current point is aligned with end point
if self.Segments[i - 1][0] != 0:
self.Points[i].x = end.x
if self.Segments[i - 1][1] != 0:
self.Points[i].y = end.y
# Previous direction is updated from the new point
if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
# If previous direction and end direction are opposed
if product(self.Segments[i - 1], self.EndPoint[1]) < 0:
# Current point is positioned in the middle of previous point
# and end point on the current direction
if self.Segments[i - 1][0] != 0:
self.Points[i].x = (end.x + self.Points[i - 1].x) / 2
if self.Segments[i - 1][1] != 0:
self.Points[i].y = (end.y + self.Points[i - 1].y) / 2
# A point is added
self.Points.insert(i + 1, wx.Point(self.Points[i].x, self.Points[i].y))
self.Segments.insert(i + 1, DirectionChoice((self.Segments[i][1],
self.Segments[i][0]), v_end, self.EndPoint[1]))
else:
# Current point is the first, and second is not mainly in the first direction
if i == 0 and product(vector(start, self.Points[1]), self.Segments[0]) < 0:
# If first and second directions aren't perpendiculars, a point is added
if product(self.Segments[0], self.Segments[1]) != 0:
self.Points.insert(1, wx.Point(start.x, start.y))
self.Segments.insert(1, DirectionChoice((self.Segments[0][1],
self.Segments[0][0]), vector(start, self.Points[1]), self.Segments[1]))
else:
self.Points[1].x, self.Points[1].y = start.x, start.y
else:
# Next point is aligned with current point
if self.Segments[i][0] != 0:
self.Points[i + 1].y = self.Points[i].y
if self.Segments[i][1] != 0:
self.Points[i + 1].x = self.Points[i].x
# Current direction is updated from the new point
if product(vector(self.Points[i], self.Points[i + 1]), self.Segments[i]) < 0:
self.Segments[i] = (-self.Segments[i][0], -self.Segments[i][1])
i += 1
self.RefreshBoundingBox()
if realpoints:
self.RefreshRealPoints()
# Verify that two consecutive points haven't the same position
def VerifyPoints(self):
points = [point for point in self.Points]
segments = [segment for segment in self.Segments]
i = 1
while i < len(points) - 1:
if points[i] == points[i + 1] and segments[i - 1] == segments[i + 1]:
for j in xrange(2):
points.pop(i)
segments.pop(i)
else:
i += 1
# If the wire isn't in a Ladder Diagram, save the new point list
if self.Parent.__class__.__name__ != "LD_Viewer":
self.Points = [point for point in points]
self.Segments = [segment for segment in segments]
self.RefreshBoundingBox()
self.RefreshRealPoints()
return points
# Moves all the wire points except the first and the last if they are connected
def Move(self, dx, dy, endpoints = False):
for i, point in enumerate(self.Points):
if endpoints or not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
point.x += dx
point.y += dy
self.StartPoint[0] = self.Points[0]
self.EndPoint[0] = self.Points[-1]
self.GeneratePoints()
# Resize the wire from position and size given
def Resize(self, x, y, width, height):
if len(self.Points) > 1:
# Calculate the new position of each point for testing the new size
minx, miny = self.Pos.x, self.Pos.y
lastwidth, lastheight = self.Size.width, self.Size.height
for i, point in enumerate(self.RealPoints):
# If start or end point is connected, it's not calculate
if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
if i == 0:
dir = self.StartPoint[1]
elif i == len(self.Points) - 1:
dir = self.EndPoint[1]
else:
dir = (0, 0)
pointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0] * width / float(max(lastwidth, 1)))),
width - dir[0] * MIN_SEGMENT_SIZE))
pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * height / float(max(lastheight, 1)))),
height - dir[1] * MIN_SEGMENT_SIZE))
self.Points[i] = wx.Point(minx + x + pointx, miny + y + pointy)
self.StartPoint[0] = self.Points[0]
self.EndPoint[0] = self.Points[-1]
self.GeneratePoints(False)
# Test if the wire position or size have changed
if x != 0 and minx == self.Pos.x:
x = 0
width = lastwidth
if y != 0 and miny == self.Pos.y:
y = 0
height = lastwidth
if width != lastwidth and lastwidth == self.Size.width:
width = lastwidth
if height != lastheight and lastheight == self.Size.height:
height = lastheight
# Calculate the real points from the new size, it's important for
# keeping a proportionality in the points position with the size
# during a resize dragging
for i, point in enumerate(self.RealPoints):
if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
point[0] = point[0] * width / float(max(lastwidth, 1))
point[1] = point[1] * height / float(max(lastheight, 1))
# Calculate the correct position of the points from real points
for i, point in enumerate(self.RealPoints):
if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
if i == 0:
dir = self.StartPoint[1]
elif i == len(self.Points) - 1:
dir = self.EndPoint[1]
else:
dir = (0, 0)
realpointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0])),
width - dir[0] * MIN_SEGMENT_SIZE))
realpointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1])),
height - dir[1] * MIN_SEGMENT_SIZE))
self.Points[i] = wx.Point(minx + x + realpointx, miny + y + realpointy)
self.StartPoint[0] = self.Points[0]
self.EndPoint[0] = self.Points[-1]
self.GeneratePoints(False)
# Moves the wire start point and update the wire points
def MoveStartPoint(self, point):
if len(self.Points) > 1:
self.StartPoint[0] = point
self.Points[0] = point
self.GeneratePoints()
# Changes the wire start direction and update the wire points
def SetStartPointDirection(self, dir):
if len(self.Points) > 1:
self.StartPoint[1] = dir
self.Segments[0] = dir
self.GeneratePoints()
# Rotates the wire start direction by an angle of 90 degrees anticlockwise
def RotateStartPoint(self):
self.SetStartPointDirection((self.StartPoint[1][1], -self.StartPoint[1][0]))
# Connects wire start point to the connector given and moves wire start point
# to given point
def ConnectStartPoint(self, point, connector):
if point:
self.MoveStartPoint(point)
self.StartConnected = connector
self.RefreshBoundingBox()
# Unconnects wire start point
def UnConnectStartPoint(self, delete = False):
if delete:
self.StartConnected = None
self.Delete()
elif self.StartConnected:
self.StartConnected.UnConnect(self, unconnect = False)
self.StartConnected = None
self.RefreshBoundingBox()
# Moves the wire end point and update the wire points
def MoveEndPoint(self, point):
if len(self.Points) > 1:
self.EndPoint[0] = point
self.Points[-1] = point
self.GeneratePoints()
# Changes the wire end direction and update the wire points
def SetEndPointDirection(self, dir):
if len(self.Points) > 1:
self.EndPoint[1] = dir
self.GeneratePoints()
# Rotates the wire end direction by an angle of 90 degrees anticlockwise
def RotateEndPoint(self):
self.SetEndPointDirection((self.EndPoint[1][1], -self.EndPoint[1][0]))
# Connects wire end point to the connector given and moves wire end point
# to given point
def ConnectEndPoint(self, point, connector):
if point:
self.MoveEndPoint(point)
self.EndConnected = connector
self.RefreshBoundingBox()
# Unconnects wire end point
def UnConnectEndPoint(self, delete = False):
if delete:
self.EndConnected = None
self.Delete()
elif self.EndConnected:
self.EndConnected.UnConnect(self, unconnect = False)
self.EndConnected = None
self.RefreshBoundingBox()
# Moves the wire segment given by its index
def MoveSegment(self, idx, movex, movey, scaling):
if 0 < idx < len(self.Segments) - 1:
if self.Segments[idx] in (NORTH, SOUTH):
start_x = self.Points[idx].x
if scaling is not None:
movex = round_scaling(self.Points[idx].x + movex, scaling[0]) - self.Points[idx].x
if idx == 1 and (self.Points[1].x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - self.Points[idx].x
elif idx == len(self.Segments) - 2 and (self.Points[-1].x - (self.Points[-2].x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
movex = round_scaling(self.Points[-1].x - MIN_SEGMENT_SIZE * self.Segments[-1][0], scaling[0], -self.Segments[-1][0]) - self.Points[idx].x
self.Points[idx].x += movex
self.Points[idx + 1].x += movex
self.GeneratePoints()
if start_x != self.Points[idx].x:
return self.Points[idx].x - start_x, 0
elif self.Segments[idx] in (EAST, WEST):
start_y = self.Points[idx].y
if scaling is not None:
movey = round_scaling(self.Points[idx].y + movey, scaling[1]) - self.Points[idx].y
if idx == 1 and (self.Points[1].y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
movex = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - self.Points[idx].y
elif idx == len(self.Segments) - 2 and (self.Points[-1].y - (self.Points[-2].y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
movey = round_scaling(self.Points[idx].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - self.Points[idx].y
self.Points[idx].y += movey
self.Points[idx + 1].y += movey
self.GeneratePoints()
if start_y != self.Points[idx].y:
return 0, self.Points[idx].y - start_y
return 0, 0
# Adds two points in the middle of the handled segment
def AddSegment(self):
handle_type, handle = self.Handle
if handle_type == HANDLE_SEGMENT:
segment, dir = handle
if len(self.Segments) > 1:
pointx = self.Points[segment].x
pointy = self.Points[segment].y
if dir[0] != 0:
pointx = (self.Points[segment].x + self.Points[segment + 1].x) / 2
if dir[1] != 0:
pointy = (self.Points[segment].y + self.Points[segment + 1].y) / 2
self.Points.insert(segment + 1, wx.Point(pointx, pointy))
self.Segments.insert(segment + 1, (dir[1], dir[0]))
self.Points.insert(segment + 2, wx.Point(pointx, pointy))
self.Segments.insert(segment + 2, dir)
else:
p1x = p2x = self.Points[segment].x
p1y = p2y = self.Points[segment].y
if dir[0] != 0:
p1x = (2 * self.Points[segment].x + self.Points[segment + 1].x) / 3
p2x = (self.Points[segment].x + 2 * self.Points[segment + 1].x) / 3
if dir[1] != 0:
p1y = (2 * self.Points[segment].y + self.Points[segment + 1].y) / 3
p2y = (self.Points[segment].y + 2 * self.Points[segment + 1].y) / 3
self.Points.insert(segment + 1, wx.Point(p1x, p1y))
self.Segments.insert(segment + 1, (dir[1], dir[0]))
self.Points.insert(segment + 2, wx.Point(p1x, p1y))
self.Segments.insert(segment + 2, dir)
self.Points.insert(segment + 3, wx.Point(p2x, p2y))
self.Segments.insert(segment + 3, (dir[1], dir[0]))
self.Points.insert(segment + 4, wx.Point(p2x, p2y))
self.Segments.insert(segment + 4, dir)
self.GeneratePoints()
# Delete the handled segment by removing the two segment points
def DeleteSegment(self):
handle_type, handle = self.Handle
if handle_type == HANDLE_SEGMENT:
segment, dir = handle
for i in xrange(2):
self.Points.pop(segment)
self.Segments.pop(segment)
self.GeneratePoints()
self.RefreshModel()
# Method called when a LeftDown event have been generated
def OnLeftDown(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
# Test if a point have been handled
#result = self.TestPoint(pos)
#if result != None:
# self.Handle = (HANDLE_POINT, result)
# wx.CallAfter(self.Parent.SetCurrentCursor, 1)
#else:
# Test if a segment have been handled
result = self.TestSegment(pos)
if result != None:
if result[1] in (NORTH, SOUTH):
wx.CallAfter(self.Parent.SetCurrentCursor, 4)
elif result[1] in (EAST, WEST):
wx.CallAfter(self.Parent.SetCurrentCursor, 5)
self.Handle = (HANDLE_SEGMENT, result)
# Execute the default method for a graphic element
else:
Graphic_Element.OnLeftDown(self, event, dc, scaling)
self.oldPos = pos
# Method called when a RightUp event has been generated
def OnRightUp(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
# Test if a segment has been handled
result = self.TestSegment(pos, True)
if result != None:
self.Handle = (HANDLE_SEGMENT, result)
# Popup the menu with special items for a wire
self.Parent.PopupWireMenu(0 < result[0] < len(self.Segments) - 1)
else:
# Execute the default method for a graphic element
Graphic_Element.OnRightUp(self, event, dc, scaling)
# Method called when a LeftDClick event has been generated
def OnLeftDClick(self, event, dc, scaling):
rect = self.GetRedrawRect()
if event.ControlDown():
direction = (self.StartPoint[1], self.EndPoint[1])
if direction in [(EAST, WEST), (WEST, EAST)]:
avgy = (self.StartPoint[0].y + self.EndPoint[0].y) / 2
if scaling is not None:
avgy = round(float(avgy) / scaling[1]) * scaling[1]
if self.StartConnected is not None:
movey = avgy - self.StartPoint[0].y
startblock = self.StartConnected.GetParentBlock()
startblock.Move(0, movey)
startblock.RefreshModel()
rect.Union(startblock.GetRedrawRect(0, movey))
else:
self.MoveStartPoint(wx.Point(self.StartPoint[0].x, avgy))
if self.EndConnected is not None:
movey = avgy - self.EndPoint[0].y
endblock = self.EndConnected.GetParentBlock()
endblock.Move(0, movey)
endblock.RefreshModel()
rect.Union(endblock.GetRedrawRect(0, movey))
else:
self.MoveEndPoint(wx.Point(self.EndPoint[0].x, avgy))
self.Parent.RefreshBuffer()
elif direction in [(NORTH, SOUTH), (SOUTH, NORTH)]:
avgx = (self.StartPoint[0].x + self.EndPoint[0].x) / 2
if scaling is not None:
avgx = round(float(avgx) / scaling[0]) * scaling[0]
if self.StartConnected is not None:
movex = avgx - self.StartPoint[0].x
startblock = self.StartConnected.GetParentBlock()
startblock.Move(movex, 0)
startblock.RefreshModel()
rect.Union(startblock.GetRedrawRect(movex, 0))
else:
self.MoveStartPoint(wx.Point(avgx, self.StartPoint[0].y))
if self.EndConnected is not None:
movex = avgx - self.EndPoint[0].x
endblock = self.EndConnected.GetParentBlock()
endblock.Move(movex, 0)
endblock.RefreshModel()
rect.Union(endblock.GetRedrawRect(movex, 0))
else:
self.MoveEndPoint(wx.Point(avgx, self.EndPoint[0].y))
self.Parent.RefreshBuffer()
else:
self.ResetPoints()
self.GeneratePoints()
self.RefreshModel()
self.Parent.RefreshBuffer()
rect.Union(self.GetRedrawRect())
self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
# Method called when a Motion event has been generated
def OnMotion(self, event, dc, scaling):
pos = GetScaledEventPosition(event, dc, scaling)
if not event.Dragging():
# Test if a segment has been handled
result = self.TestSegment(pos)
if result:
if result[1] in (NORTH, SOUTH):
wx.CallAfter(self.Parent.SetCurrentCursor, 4)
elif result[1] in (EAST, WEST):
wx.CallAfter(self.Parent.SetCurrentCursor, 5)
return 0, 0
else:
# Test if a point has been handled
#result = self.TestPoint(pos)
#if result != None:
# if result == 0 and self.StartConnected:
# self.OverStart = True
# elif result != 0 and self.EndConnected:
# self.OverEnd = True
#else:
# self.OverStart = False
# self.OverEnd = False
# Execute the default method for a graphic element
return Graphic_Element.OnMotion(self, event, dc, scaling)
else:
# Execute the default method for a graphic element
return Graphic_Element.OnMotion(self, event, dc, scaling)
# Refreshes the wire state according to move defined and handle selected
def ProcessDragging(self, movex, movey, event, scaling):
handle_type, handle = self.Handle
# A point has been handled
if handle_type == HANDLE_POINT:
movex = max(-self.Points[handle].x + POINT_RADIUS, movex)
movey = max(-self.Points[handle].y + POINT_RADIUS, movey)
if scaling is not None:
movex = round_scaling(self.Points[handle].x + movex, scaling[0]) - self.Points[handle].x
movey = round_scaling(self.Points[handle].y + movey, scaling[1]) - self.Points[handle].y
# Try to connect point to a connector
new_pos = wx.Point(self.Points[handle].x + movex, self.Points[handle].y + movey)
connector = self.Parent.FindBlockConnector(new_pos, self.GetConnectionDirection())
if connector:
if handle == 0 and self.EndConnected != connector:
connector.HighlightParentBlock(True)
connector.Connect((self, handle))
self.SetStartPointDirection(connector.GetDirection())
self.ConnectStartPoint(connector.GetPosition(), connector)
pos = connector.GetPosition()
movex = pos.x - self.oldPos.x
movey = pos.y - self.oldPos.y
if not connector.IsCompatible(self.GetEndConnectedType()):
self.SetValid(False)
self.Dragging = False
elif handle != 0 and self.StartConnected != connector:
connector.HighlightParentBlock(True)
connector.Connect((self, handle))
self.SetEndPointDirection(connector.GetDirection())
self.ConnectEndPoint(connector.GetPosition(), connector)
pos = connector.GetPosition()
movex = pos.x - self.oldPos.x
movey = pos.y - self.oldPos.y
if not connector.IsCompatible(self.GetStartConnectedType()):
self.SetValid(False)
self.Dragging = False
elif handle == 0:
self.MoveStartPoint(new_pos)
else:
self.MoveEndPoint(new_pos)
# If there is no connector, move the point
elif handle == 0:
self.SetValid(True)
if self.StartConnected:
self.StartConnected.HighlightParentBlock(False)
self.UnConnectStartPoint()
self.MoveStartPoint(new_pos)
else:
self.SetValid(True)
if self.EndConnected:
self.EndConnected.HighlightParentBlock(False)
self.UnConnectEndPoint()
self.MoveEndPoint(new_pos)
return movex, movey
# A segment has been handled, move a segment
elif handle_type == HANDLE_SEGMENT:
return self.MoveSegment(handle[0], movex, movey, scaling)
# Execute the default method for a graphic element
else:
return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
# Refreshes the wire model
def RefreshModel(self, move=True):
if self.StartConnected and self.StartPoint[1] in [WEST, NORTH]:
self.StartConnected.RefreshParentBlock()
if self.EndConnected and self.EndPoint[1] in [WEST, NORTH]:
self.EndConnected.RefreshParentBlock()
# Draws the highlightment of this element if it is highlighted
def DrawHighlightment(self, dc):
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(MiterPen(HIGHLIGHTCOLOR, (2 * scalex + 5)))
dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
dc.SetLogicalFunction(wx.AND)
# Draw the start and end points if they are not connected or the mouse is over them
if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
dc.DrawCircle(round(self.Points[0].x * scalex),
round(self.Points[0].y * scaley),
(POINT_RADIUS + 1) * scalex + 2)
if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
dc.DrawCircle(self.Points[-1].x * scalex, self.Points[-1].y * scaley, (POINT_RADIUS + 1) * scalex + 2)
# Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
if len(self.Points) > 1:
points = [wx.Point(round((self.Points[0].x - self.Segments[0][0]) * scalex),
round((self.Points[0].y - self.Segments[0][1]) * scaley))]
points.extend([wx.Point(round(point.x * scalex), round(point.y * scaley)) for point in self.Points[1:-1]])
points.append(wx.Point(round((self.Points[-1].x + self.Segments[-1][0]) * scalex),
round((self.Points[-1].y + self.Segments[-1][1]) * scaley)))
else:
points = []
dc.DrawLines(points)
dc.SetLogicalFunction(wx.COPY)
dc.SetUserScale(scalex, scaley)
if self.StartConnected is not None:
self.StartConnected.DrawHighlightment(dc)
self.StartConnected.Draw(dc)
if self.EndConnected is not None:
self.EndConnected.DrawHighlightment(dc)
self.EndConnected.Draw(dc)
# Draws the wire lines and points
def Draw(self, dc):
Graphic_Element.Draw(self, dc)
if not self.Valid:
dc.SetPen(MiterPen(wx.RED))
dc.SetBrush(wx.RED_BRUSH)
elif isinstance(self.Value, BooleanType) and self.Value:
if self.Forced:
dc.SetPen(MiterPen(wx.CYAN))
dc.SetBrush(wx.CYAN_BRUSH)
else:
dc.SetPen(MiterPen(wx.GREEN))
dc.SetBrush(wx.GREEN_BRUSH)
elif self.Value == "undefined":
dc.SetPen(MiterPen(wx.NamedColour("orange")))
dc.SetBrush(wx.Brush(wx.NamedColour("orange")))
elif self.Forced:
dc.SetPen(MiterPen(wx.BLUE))
dc.SetBrush(wx.BLUE_BRUSH)
else:
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.BLACK_BRUSH)
# Draw the start and end points if they are not connected or the mouse is over them
if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
dc.DrawCircle(self.Points[0].x, self.Points[0].y, POINT_RADIUS)
if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
dc.DrawCircle(self.Points[-1].x, self.Points[-1].y, POINT_RADIUS)
# Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
if len(self.Points) > 1:
points = [wx.Point(self.Points[0].x - self.Segments[0][0], self.Points[0].y - self.Segments[0][1])]
points.extend([point for point in self.Points[1:-1]])
points.append(wx.Point(self.Points[-1].x + self.Segments[-1][0], self.Points[-1].y + self.Segments[-1][1]))
else:
points = []
dc.DrawLines(points)
# Draw the segment selected in red
if not getattr(dc, "printing", False) and self.SelectedSegment is not None:
dc.SetPen(MiterPen(wx.BLUE, 3))
if self.SelectedSegment == len(self.Segments) - 1:
end = 0
else:
end = 1
dc.DrawLine(self.Points[self.SelectedSegment].x - 1, self.Points[self.SelectedSegment].y,
self.Points[self.SelectedSegment + 1].x + end, self.Points[self.SelectedSegment + 1].y)
if self.Value is not None and not isinstance(self.Value, BooleanType) and self.Value != "undefined":
dc.SetFont(self.Parent.GetMiniFont())
dc.SetTextForeground(wx.NamedColour("purple"))
if self.ValueSize is not None:
width, height = self.ValueSize
if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
x = self.Points[0].x + width * (self.StartPoint[1][0] - 1) / 2
y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
dc.DrawText(self.ComputedValue, x, y)
x = self.Points[-1].x + width * (self.EndPoint[1][0] - 1) / 2
y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
dc.DrawText(self.ComputedValue, x, y)
else:
middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
else:
y = self.Points[middle].y - height
dc.DrawText(self.ComputedValue, x, y)
dc.SetFont(self.Parent.GetFont())
dc.SetTextForeground(wx.BLACK)
#-------------------------------------------------------------------------------
# Graphic comment element
#-------------------------------------------------------------------------------
def FilterHighlightsByRow(highlights, row, length):
_highlights = []
for start, end, highlight_type in highlights:
if start[0] <= row and end[0] >= row:
if start[0] < row:
start = (row, 0)
if end[0] > row:
end = (row, length)
_highlights.append((start, end, highlight_type))
return _highlights
def FilterHighlightsByColumn(highlights, start_col, end_col):
_highlights = []
for start, end, highlight_type in highlights:
if end[1] > start_col and start[1] < end_col:
start = (start[0], max(start[1], start_col) - start_col)
end = (end[0], min(end[1], end_col) - start_col)
_highlights.append((start, end, highlight_type))
return _highlights
"""
Class that implements a comment
"""
class Comment(Graphic_Element):
# Create a new comment
def __init__(self, parent, content, id = None):
Graphic_Element.__init__(self, parent)
self.Id = id
self.Content = content
self.Pos = wx.Point(0, 0)
self.Size = wx.Size(0, 0)
self.Highlights = []
# Make a clone of this comment
def Clone(self, parent, id = None, pos = None):
comment = Comment(parent, self.Content, id)
if pos is not None:
comment.SetPosition(pos.x, pos.y)
comment.SetSize(self.Size[0], self.Size[1])
return comment
# Method for keeping compatibility with others
def Clean(self):
pass
# Delete this comment by calling the corresponding method
def Delete(self):
self.Parent.DeleteComment(self)
# Refresh the comment bounding box
def RefreshBoundingBox(self):
self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
# Changes the comment size
def SetSize(self, width, height):
self.Size.SetWidth(width)
self.Size.SetHeight(height)
self.RefreshBoundingBox()
# Returns the comment size
def GetSize(self):
return self.Size.GetWidth(), self.Size.GetHeight()
# Returns the comment minimum size
def GetMinSize(self):
dc = wx.ClientDC(self.Parent)
min_width = 0
min_height = 0
# The comment minimum size is the maximum size of words in the content
for line in self.Content.splitlines():
for word in line.split(" "):
wordwidth, wordheight = dc.GetTextExtent(word)
min_width = max(min_width, wordwidth)
min_height = max(min_height, wordheight)
return min_width + 20, min_height + 20
# Changes the comment position
def SetPosition(self, x, y):
self.Pos.x = x
self.Pos.y = y
self.RefreshBoundingBox()
# Changes the comment content
def SetContent(self, content):
self.Content = content
min_width, min_height = self.GetMinSize()
self.Size[0] = max(self.Size[0], min_width)
self.Size[1] = max(self.Size[1], min_height)
self.RefreshBoundingBox()
# Returns the comment content
def GetContent(self):
return self.Content
# Returns the comment position
def GetPosition(self):
return self.Pos.x, self.Pos.y
# Moves the comment
def Move(self, dx, dy, connected = True):
self.Pos.x += dx
self.Pos.y += dy
self.RefreshBoundingBox()
# Resizes the comment with the position and the size given
def Resize(self, x, y, width, height):
self.Move(x, y)
self.SetSize(width, height)
# Method called when a RightUp event have been generated
def OnRightUp(self, event, dc, scaling):
# Popup the default menu
self.Parent.PopupDefaultMenu()
# Refreshes the wire state according to move defined and handle selected
def ProcessDragging(self, movex, movey, event, scaling):
if self.Parent.GetDrawingMode() != FREEDRAWING_MODE and self.Parent.CurrentLanguage == "LD":
movex = movey = 0
return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
# Refreshes the comment model
def RefreshModel(self, move=True):
self.Parent.RefreshCommentModel(self)
# Method called when a LeftDClick event have been generated
def OnLeftDClick(self, event, dc, scaling):
# Edit the comment content
self.Parent.EditCommentContent(self)
# Adds an highlight to the comment
def AddHighlight(self, infos, start, end, highlight_type):
if infos[0] == "content":
AddHighlight(self.Highlights, (start, end, highlight_type))
# Removes an highlight from the comment
def RemoveHighlight(self, infos, start, end, highlight_type):
RemoveHighlight(self.Highlights, (start, end, highlight_type))
# Removes all the highlights of one particular type from the comment
def ClearHighlight(self, highlight_type=None):
self.Highlights = ClearHighlights(self.Highlights, highlight_type)
# Draws the highlightment of this element if it is highlighted
def DrawHighlightment(self, dc):
scalex, scaley = dc.GetUserScale()
dc.SetUserScale(1, 1)
dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
dc.SetLogicalFunction(wx.AND)
left = (self.Pos.x - 1) * scalex - 2
right = (self.Pos.x + self.Size[0] + 1) * scalex + 2
top = (self.Pos.y - 1) * scaley - 2
bottom = (self.Pos.y + self.Size[1] + 1) * scaley + 2
angle_top = (self.Pos.x + self.Size[0] - 9) * scalex + 2
angle_right = (self.Pos.y + 9) * scaley - 2
polygon = [wx.Point(left, top), wx.Point(angle_top, top),
wx.Point(right, angle_right), wx.Point(right, bottom),
wx.Point(left, bottom)]
dc.DrawPolygon(polygon)
dc.SetLogicalFunction(wx.COPY)
dc.SetUserScale(scalex, scaley)
# Draws the comment and its content
def Draw(self, dc):
Graphic_Element.Draw(self, dc)
dc.SetPen(MiterPen(wx.BLACK))
dc.SetBrush(wx.WHITE_BRUSH)
# Draws the comment shape
polygon = [wx.Point(self.Pos.x, self.Pos.y),
wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10),
wx.Point(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1]),
wx.Point(self.Pos.x, self.Pos.y + self.Size[1])]
dc.DrawPolygon(polygon)
lines = [wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10),
wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10)]
dc.DrawLines(lines)
# Draws the comment content
y = self.Pos.y + 10
for idx, line in enumerate(self.Content.splitlines()):
first = True
linetext = ""
words = line.split(" ")
if not getattr(dc, "printing", False):
highlights = FilterHighlightsByRow(self.Highlights, idx, len(line))
highlights_offset = 0
for i, word in enumerate(words):
if first:
text = word
else:
text = linetext + " " + word
wordwidth, wordheight = dc.GetTextExtent(text)
if y + wordheight > self.Pos.y + self.Size[1] - 10:
break
if wordwidth < self.Size[0] - 20:
if i < len(words) - 1:
linetext = text
first = False
else:
dc.DrawText(text, self.Pos.x + 10, y)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, text, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(text)), self.Pos.x + 10, y)
highlights_offset += len(text) + 1
y += wordheight + 5
else:
if not first:
dc.DrawText(linetext, self.Pos.x + 10, y)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, linetext, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(linetext)), self.Pos.x + 10, y)
highlights_offset += len(linetext) + 1
if first or i == len(words) - 1:
if not first:
y += wordheight + 5
if y + wordheight > self.Pos.y + self.Size[1] - 10:
break
dc.DrawText(word, self.Pos.x + 10, y)
if not getattr(dc, "printing", False):
DrawHighlightedText(dc, word, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(word)), self.Pos.x + 10, y)
highlights_offset += len(word) + 1
else:
linetext = word
y += wordheight + 5
if y + wordheight > self.Pos.y + self.Size[1] - 10:
break