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