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