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: # etisserant@0: #Copyright (C): 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@0: #modify it under the terms of the GNU Lesser 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 etisserant@0: #Lesser General Public License for more details. etisserant@0: # etisserant@0: #You should have received a copy of the GNU Lesser 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: from wxPython.wx import * etisserant@0: import wx etisserant@0: from math import * etisserant@0: 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 etisserant@0: MIN_MOVE = 5 # Minimum move before starting a element dragging etisserant@0: CONNECTOR_SIZE = 8 # Size of connectors etisserant@0: BLOCK_LINE_SIZE = 20 # Minimum size of each line in a block etisserant@0: HANDLE_SIZE = 6 # Size of the squares for handles etisserant@0: ANCHOR_DISTANCE = 5 # Distance where wire is automativally attached to a connector etisserant@0: POINT_RADIUS = 2 # Radius of the point of wire ends etisserant@0: MIN_SEGMENT_SIZE = 2 # Minimum size of the endling segments of a wire etisserant@0: etisserant@0: # LD constants etisserant@0: LD_LINE_SIZE = 40 # Distance between two lines in a ladder rung etisserant@0: LD_ELEMENT_SIZE = (21, 15) # Size (width, height) of a ladder element (contact or coil) etisserant@0: LD_WIRE_SIZE = 30 # Size of a wire between two contact etisserant@0: LD_WIRECOIL_SIZE = 70 # Size of a wire between a coil and a contact etisserant@0: LD_OFFSET = (10, 10) # Distance (x, y) between each comment and rung of the ladder etisserant@0: LD_COMMENT_DEFAULTSIZE = (600, 40) # Size (width, height) of a comment box etisserant@0: etisserant@0: # SFC constants etisserant@0: SFC_STEP_DEFAULT_SIZE = (40, 30) # Default size of a SFC step etisserant@0: SFC_TRANSITION_SIZE = (20, 2) # Size of a SFC transition etisserant@0: SFC_DEFAULT_SEQUENCE_INTERVAL = 80 # Default size of the interval between two divergence branches etisserant@0: SFC_SIMULTANEOUS_SEQUENCE_EXTRA = 20 # Size of extra lines for simultaneous divergence and convergence etisserant@0: SFC_JUMP_SIZE = (12, 13) # Size of a SFC jump to step etisserant@0: SFC_WIRE_MIN_SIZE = 25 # Size of a wire between two elements etisserant@0: 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) etisserant@0: [COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET] = range(4) 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 etisserant@0: [MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT, MODE_WIRE, etisserant@0: MODE_INITIAL_STEP] = range(7) etisserant@0: etisserant@0: """ etisserant@0: Basic vector operations for calculate wire points etisserant@0: """ etisserant@0: etisserant@0: # Calculate the scalar product of two vectors etisserant@0: def product(v1, v2): etisserant@0: return v1[0] * v2[0] + v1[1] * v2[1] 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: 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: etisserant@0: def GetScaledEventPosition(event, scaling): etisserant@0: pos = event.GetPosition() 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: etisserant@0: etisserant@0: #------------------------------------------------------------------------------- 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 etisserant@0: def __init__(self, drawingSurface): etisserant@0: self.drawingSurface = drawingSurface 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): etisserant@0: return self.currentBox etisserant@0: etisserant@0: # Method called when a new box starts to be edited etisserant@0: def OnLeftDown(self, event, scaling): etisserant@0: pos = GetScaledEventPosition(event, scaling) etisserant@0: # Save the point for calculate the box position and size etisserant@0: self.startPoint = pos etisserant@0: self.currentBox = wxRect(pos.x, pos.y, 0, 0) etisserant@0: self.drawingSurface.SetCursor(wxStockCursor(wxCURSOR_CROSS)) etisserant@0: self.Redraw() etisserant@0: etisserant@0: # Method called when dragging with a box edited etisserant@0: def OnMotion(self, event, scaling): etisserant@0: pos = GetScaledEventPosition(event, scaling) etisserant@0: # Save the last position and size of the box for erasing it etisserant@0: self.lastBox = wxRect(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 etisserant@0: def OnLeftUp(self, event, scaling): etisserant@0: self.drawingSurface.SetCursor(wxNullCursor) 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 etisserant@0: def Redraw(self): etisserant@0: dc = wxClientDC(self.drawingSurface) etisserant@0: dc.SetPen(wxPen(wxWHITE, 1, wxDOT)) etisserant@0: dc.SetBrush(wxTRANSPARENT_BRUSH) etisserant@0: dc.SetLogicalFunction(wxXOR) etisserant@0: if self.lastBox: etisserant@0: # Erase last box etisserant@0: dc.DrawRectangle(self.lastBox.x, self.lastBox.y, self.lastBox.width, etisserant@0: self.lastBox.height) etisserant@0: if self.currentBox: etisserant@0: # Draw current box etisserant@0: dc.DrawRectangle(self.currentBox.x, self.currentBox.y, self.currentBox.width, etisserant@0: self.currentBox.height) etisserant@0: etisserant@0: etisserant@0: #------------------------------------------------------------------------------- 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 etisserant@0: self.Handle = False etisserant@0: self.Dragging = False etisserant@0: self.Selected = False etisserant@0: self.Pos = wxPoint(0, 0) etisserant@0: self.Size = wxSize(0, 0) etisserant@0: self.BoundingBox = wxRect(0, 0, 0, 0) etisserant@0: etisserant@0: # Make a clone of this element etisserant@0: def Clone(self): etisserant@0: return Graphic_Element(self.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: etisserant@0: # Refresh the element Bounding Box etisserant@0: def RefreshBoundingBox(self): etisserant@0: self.BoundingBox = wxRect(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 etisserant@0: def HitTest(self, pt): etisserant@0: rect = self.BoundingBox etisserant@0: return rect.InsideXY(pt.x, pt.y) etisserant@0: 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: etisserant@0: # Change the variable that indicates if this element is selected etisserant@0: def SetSelected(self, selected): etisserant@0: self.Selected = selected etisserant@0: etisserant@0: # Test if the point is on a handle of this element etisserant@0: def TestHandle(self, pt): etisserant@0: # Verify that this element is selected etisserant@0: if self.Selected: etisserant@0: # Find if point is on a handle horizontally etisserant@0: if self.BoundingBox.x - HANDLE_SIZE - 2 <= pt.x < self.BoundingBox.x - 2: etisserant@0: handle_x = 1 etisserant@0: elif self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2 <= pt.x < self.BoundingBox.x + (self.BoundingBox.width + HANDLE_SIZE) / 2: etisserant@0: handle_x = 2 etisserant@0: elif self.BoundingBox.x + self.BoundingBox.width + 2 <= pt.x < self.BoundingBox.x + self.BoundingBox.width + HANDLE_SIZE + 2: etisserant@0: handle_x = 3 etisserant@0: else: etisserant@0: handle_x = 0 etisserant@0: # Find if point is on a handle vertically etisserant@0: if self.BoundingBox.y - HANDLE_SIZE - 2 <= pt.y < self.BoundingBox.y - 2: etisserant@0: handle_y = 1 etisserant@0: elif self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2 <= pt.y < self.BoundingBox.y + (self.BoundingBox.height + HANDLE_SIZE) / 2: etisserant@0: handle_y = 2 etisserant@0: elif self.BoundingBox.y + self.BoundingBox.height - 2 <= pt.y < self.BoundingBox.y + self.BoundingBox.height + HANDLE_SIZE + 2: 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 etisserant@0: def OnLeftDown(self, event, scaling): etisserant@0: pos = event.GetPosition() etisserant@0: # Test if an handle have been clicked etisserant@0: result = self.TestHandle(pos) etisserant@0: # Find which type of handle have been clicked, etisserant@0: # Save a resize event and change the cursor etisserant@0: if result == (1, 1) or result == (3, 3): etisserant@0: self.Handle = (HANDLE_RESIZE, result) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENWSE)) etisserant@0: elif result == (1, 3) or result == (3, 1): etisserant@0: self.Handle = (HANDLE_RESIZE, result) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENESW)) etisserant@0: elif result == (1, 2) or result == (3, 2): etisserant@0: self.Handle = (HANDLE_RESIZE, result) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZEWE)) etisserant@0: elif result == (2, 1) or result == (2, 3): etisserant@0: self.Handle = (HANDLE_RESIZE, result) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENS)) etisserant@0: # If no handle have been clicked, save a move event, and change the cursor etisserant@0: else: etisserant@0: self.Handle = (HANDLE_MOVE, None) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_HAND)) etisserant@0: self.SetSelected(False) etisserant@0: # Initializes the last position etisserant@0: self.oldPos = GetScaledEventPosition(event, scaling) etisserant@0: etisserant@0: # Method called when a LeftUp event have been generated etisserant@0: def OnLeftUp(self, event, scaling): etisserant@0: # If a dragging have been initiated etisserant@0: if self.Dragging and self.oldPos: etisserant@0: # Calculate the movement of cursor and refreshes the element state etisserant@0: pos = GetScaledEventPosition(event, scaling) etisserant@0: movex = pos.x - self.oldPos.x etisserant@0: movey = pos.y - self.oldPos.y etisserant@0: self.ProcessDragging(movex, movey) etisserant@0: self.RefreshModel() etisserant@0: self.SetSelected(True) etisserant@0: self.oldPos = None etisserant@0: etisserant@0: # Method called when a RightUp event have been generated etisserant@0: def OnRightUp(self, event, scaling): etisserant@0: self.SetSelected(True) etisserant@0: self.oldPos = None etisserant@0: etisserant@0: # Method called when a LeftDClick event have been generated etisserant@0: def OnLeftDClick(self, event, scaling): etisserant@0: pass etisserant@0: etisserant@0: # Method called when a Motion event have been generated etisserant@0: def OnMotion(self, event, 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 etisserant@0: pos = GetScaledEventPosition(event, scaling) 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: etisserant@0: self.ProcessDragging(movex, movey) etisserant@0: self.oldPos = pos etisserant@0: # If cursor just pass over the element, changes the cursor if it is on a handle etisserant@0: else: etisserant@0: pos = event.GetPosition() etisserant@0: handle = self.TestHandle(pos) etisserant@0: if handle == (1, 1) or handle == (3, 3): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENWSE)) etisserant@0: elif handle == (1, 3) or handle == (3, 1): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENESW)) etisserant@0: elif handle == (1, 2) or handle == (3, 2): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZEWE)) etisserant@0: elif handle == (2, 1) or handle == (2, 3): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENS)) etisserant@0: else: etisserant@0: wxCallAfter(self.Parent.SetCursor, wxNullCursor) etisserant@0: etisserant@0: # Moves the element etisserant@0: def Move(self, dx, dy, exclude = []): etisserant@0: self.Pos.x += dx etisserant@0: self.Pos.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: etisserant@0: # Refreshes the element state according to move defined and handle selected etisserant@0: def ProcessDragging(self, movex, movey): 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@0: x, y = 0, 0 etisserant@0: width, height = self.GetSize() etisserant@0: if handle[0] == 1: etisserant@0: x = movex etisserant@0: width -= movex etisserant@0: elif handle[0] == 3: etisserant@0: width += movex etisserant@0: if handle[1] == 1: etisserant@0: y = movey etisserant@0: height -= movey etisserant@0: elif handle[1] == 3: etisserant@0: height += movey etisserant@0: # Verify that new size is not lesser than minimum etisserant@0: min_width, min_height = self.GetMinSize() etisserant@0: if width >= min_width and height >= min_height: etisserant@0: self.Resize(x, y, width, height) etisserant@0: # If it is a move handle, Move this element etisserant@0: elif handle_type == HANDLE_MOVE: etisserant@0: self.Move(movex, movey) etisserant@0: 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: etisserant@0: # Draws the handles of this element if it is selected etisserant@0: def Draw(self, dc): etisserant@0: if self.Selected: etisserant@0: dc.SetPen(wxBLACK_PEN) etisserant@0: dc.SetBrush(wxBLACK_BRUSH) etisserant@0: dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2, etisserant@0: self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2, etisserant@0: self.BoundingBox.y - HANDLE_SIZE - 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2, etisserant@0: self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x + self.BoundingBox.width + 2, etisserant@0: self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x + (self.BoundingBox.width - HANDLE_SIZE) / 2, etisserant@0: self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y + self.BoundingBox.height + 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.DrawRectangle(self.BoundingBox.x - HANDLE_SIZE - 2, self.BoundingBox.y + (self.BoundingBox.height - HANDLE_SIZE) / 2, HANDLE_SIZE, HANDLE_SIZE) etisserant@0: dc.SetBrush(wxWHITE_BRUSH) 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 = [] etisserant@0: self.RefreshBoundingBox() etisserant@0: etisserant@0: # Destructor etisserant@0: def __del__(self): etisserant@0: self.Elements = [] etisserant@0: etisserant@0: # Make a clone of this group etisserant@0: def Clone(self): etisserant@0: clone = Graphic_Group(self.Parent) etisserant@0: elements = [] etisserant@0: # Makes a clone of all the elements in this group etisserant@0: for element in self.Elements: etisserant@0: elements.append(element.Clone()) etisserant@0: clone.SetElements(elements) etisserant@0: return clone etisserant@0: 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() etisserant@0: etisserant@0: # Returns if the point given is in the bounding box of one of the elements of this group etisserant@0: def HitTest(self, pt): etisserant@0: result = False etisserant@0: for element in self.Elements: etisserant@0: result |= element.HitTest(pt) 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 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: 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) etisserant@0: self.RefreshBoundingBox() etisserant@0: etisserant@0: # Move this group of elements etisserant@0: def Move(self, movex, movey): etisserant@0: exclude = [] etisserant@0: for element in self.Elements: etisserant@0: if isinstance(element, Wire): etisserant@0: exclude.append(element) etisserant@0: # Move all the elements of the group etisserant@0: for element in self.Elements: etisserant@0: if isinstance(element, Wire): etisserant@0: element.Move(movex, movey, True) etisserant@0: else: etisserant@0: element.Move(movex, movey, exclude) 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) etisserant@0: self.BoundingBox = wxRect(minx, miny, maxx - minx, maxy - miny) etisserant@0: else: etisserant@0: self.BoundingBox = wxRect(0, 0, 0, 0) 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: 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: 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 etisserant@0: def __init__(self, parent, name, type, position, direction, negated = False, edge = "none"): 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 = [] etisserant@0: self.Negated = negated etisserant@0: self.Edge = edge etisserant@0: self.Pen = wxBLACK_PEN etisserant@0: etisserant@0: # Change the connector pen etisserant@0: def SetPen(self, pen): etisserant@0: self.Pen = pen etisserant@0: etisserant@0: # Make a clone of the connector etisserant@0: def Clone(self): etisserant@0: return Connector(self.Parent, self.Name, self.Type, wxPoint(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: etisserant@0: # 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 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 etisserant@0: return wxPoint(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 etisserant@0: def UnConnect(self, wire = None, unconnect = True): 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: etisserant@0: self.Wires[i][0].UnConnectStartPoint() etisserant@0: else: etisserant@0: self.Wires[i][0].UnConnectEndPoint() 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 = [] 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: etisserant@0: wire.MoveStartPoint(wxPoint(x, y)) etisserant@0: else: etisserant@0: wire.MoveEndPoint(wxPoint(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: 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): etisserant@0: self.Negated = negated etisserant@0: 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): etisserant@0: self.Edge = edge etisserant@0: self.Negated = False etisserant@0: etisserant@0: # Tests if the point given is near from the end point of this connector etisserant@0: def TestPoint(self, pt, exclude = True): etisserant@0: parent_pos = self.ParentBlock.GetPosition() etisserant@0: if not (len(self.Wires) > 0 and self.Direction == WEST and exclude): etisserant@0: # Calculate a square around the end point of this connector etisserant@0: x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE - ANCHOR_DISTANCE etisserant@0: y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE - ANCHOR_DISTANCE etisserant@0: width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE etisserant@0: height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE etisserant@0: rect = wxRect(x, y, width, height) etisserant@0: return rect.InsideXY(pt.x, pt.y) etisserant@0: return False etisserant@0: etisserant@0: # Draws the connector etisserant@0: def Draw(self, dc): etisserant@0: dc.SetPen(self.Pen) etisserant@0: dc.SetBrush(wxWHITE_BRUSH) etisserant@0: parent_pos = self.ParentBlock.GetPosition() 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: etisserant@0: 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) etisserant@0: xend = xstart + CONNECTOR_SIZE * self.Direction[0] etisserant@0: yend = ystart + CONNECTOR_SIZE * self.Direction[1] etisserant@0: dc.DrawLine(xstart + self.Direction[0], ystart + self.Direction[1], xend, yend) etisserant@0: # Calculate the position of the text etisserant@0: text_size = dc.GetTextExtent(self.Name) etisserant@0: if self.Direction[0] != 0: etisserant@0: ytext = parent_pos[1] + self.Pos.y - text_size[1] / 2 etisserant@0: if self.Direction[0] < 0: etisserant@0: xtext = parent_pos[0] + self.Pos.x + 5 etisserant@0: else: etisserant@0: xtext = parent_pos[0] + self.Pos.x - (text_size[0] + 5) etisserant@0: if self.Direction[1] != 0: etisserant@0: xtext = parent_pos[0] + self.Pos.x - text_size[0] / 2 etisserant@0: if self.Direction[1] < 0: etisserant@0: ytext = parent_pos[1] + self.Pos.y + 5 etisserant@0: else: etisserant@0: ytext = parent_pos[1] + self.Pos.y - (text_size[1] + 5) etisserant@0: # Draw the text etisserant@0: dc.DrawText(self.Name, xtext, ytext) etisserant@0: 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: etisserant@0: class Wire(Graphic_Element): 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) 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 etisserant@0: self.OverStart = False etisserant@0: self.OverEnd = False etisserant@0: etisserant@0: # Destructor of a wire etisserant@0: def __del__(self): etisserant@0: self.StartConnected = None etisserant@0: self.EndConnected = None etisserant@0: 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: 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: etisserant@0: self.StartConnected.SetPen(wxBLACK_PEN) etisserant@0: if self.EndConnected: etisserant@0: self.EndConnected.SetPen(wxBLACK_PEN) etisserant@0: # The segment selected is the first etisserant@0: elif segment == 0: etisserant@0: if self.StartConnected: etisserant@0: self.StartConnected.SetPen(wxRED_PEN) etisserant@0: if self.EndConnected: etisserant@0: # There is only one segment etisserant@0: if len(self.Segments) == 1: etisserant@0: self.EndConnected.SetPen(wxRED_PEN) etisserant@0: else: etisserant@0: self.EndConnected.SetPen(wxBLACK_PEN) etisserant@0: # The segment selected is the last etisserant@0: elif segment == len(self.Segments) - 1: etisserant@0: if self.StartConnected: etisserant@0: self.StartConnected.SetPen(wxBLACK_PEN) etisserant@0: if self.EndConnected: etisserant@0: self.EndConnected.SetPen(wxRED_PEN) etisserant@0: self.SelectedSegment = segment etisserant@0: 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) etisserant@0: self.Pos = wxPoint(minx, miny) etisserant@0: self.Size = wxSize(maxx -minx + 1, maxy - miny + 1) etisserant@0: self.BoundingBox = wxRect(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 etisserant@0: def HitTest(self, pt): etisserant@0: test = False etisserant@0: for i in xrange(len(self.Points) - 1): etisserant@0: rect = wxRect(0, 0, 0, 0) 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 etisserant@0: rect = wxRect(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 etisserant@0: rect = wxRect(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: etisserant@0: rect = wxRect(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 etisserant@0: if 0 < i < len(self.Segments) - 1 or all: 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 etisserant@0: rect = wxRect(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 etisserant@0: def SetPoints(self, points): etisserant@0: if len(points) > 1: etisserant@0: self.Points = [wxPoint(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 etisserant@0: self.StartPoint[0] = wxPoint(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0], etisserant@0: self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1]) etisserant@0: self.EndPoint[0] = wxPoint(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 = [] etisserant@0: for i in xrange(len(self.Points) - 1): etisserant@0: self.Segments.append(vector(self.Points[i], self.Points[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() etisserant@0: points[0] = wxPoint(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0], etisserant@0: points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1]) etisserant@0: points[-1] = wxPoint(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 etisserant@0: def GetConnectedId(self, index): etisserant@0: if index == 0 and self.StartConnected: etisserant@0: return self.StartConnected.GetBlockId() etisserant@0: elif index == -1 and self.EndConnected: etisserant@0: return self.EndConnected.GetBlockId() 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 etisserant@0: end = wxPoint(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE, etisserant@0: self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE) etisserant@0: start = wxPoint(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]: etisserant@0: self.Points.insert(1, wxPoint(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: etisserant@0: self.Points.insert(2, wxPoint(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: etisserant@0: self.Points.insert(2, wxPoint(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 etisserant@0: self.Points.insert(2, wxPoint(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: etisserant@0: test_point = wxPoint(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 etisserant@0: test = norm(vector(self.Points[0], test_point, False)) > MIN_SEGMENT_SIZE 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 etisserant@0: self.Points.insert(3, wxPoint(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 etisserant@0: self.Points.insert(i + 1, wxPoint(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: etisserant@0: self.Points.insert(1, wxPoint(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) etisserant@0: pointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0] * (width - 1) / float(lastwidth - 1))), etisserant@0: width - dir[0] * MIN_SEGMENT_SIZE - 1)) etisserant@0: pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * (height - 1) / float(lastheight - 1))), etisserant@0: height - dir[1] * MIN_SEGMENT_SIZE - 1)) etisserant@0: self.Points[i] = wxPoint(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 etisserant@0: # duringa 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): etisserant@0: point[0] = point[0] * (width - 1) / float(lastwidth - 1) etisserant@0: point[1] = point[1] * (height - 1) / float(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])), etisserant@0: width - dir[0] * MIN_SEGMENT_SIZE - 1)) etisserant@0: realpointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1])), etisserant@0: height - dir[1] * MIN_SEGMENT_SIZE - 1)) etisserant@0: self.Points[i] = wxPoint(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 etisserant@0: etisserant@0: # Unconnects wire start point etisserant@0: def UnConnectStartPoint(self): etisserant@0: self.StartConnected.UnConnect(self, False) etisserant@0: self.StartConnected = None 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 etisserant@0: etisserant@0: # Unconnects wire end point etisserant@0: def UnConnectEndPoint(self): etisserant@0: self.EndConnected.UnConnect(self, False) etisserant@0: self.EndConnected = None etisserant@0: etisserant@0: # Moves the wire segment given by its index etisserant@0: def MoveSegment(self, idx, movex, movey): etisserant@0: if 0 < idx < len(self.Segments) - 1: etisserant@0: if self.Segments[idx] in (NORTH, SOUTH): etisserant@0: self.Points[idx].x += movex etisserant@0: self.Points[idx + 1].x += movex etisserant@0: elif self.Segments[idx] in (EAST, WEST): etisserant@0: self.Points[idx].y += movey etisserant@0: self.Points[idx + 1].y += movey etisserant@0: self.GeneratePoints() 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 etisserant@0: pointx = self.Points[segment].x etisserant@0: pointy = self.Points[segment].y etisserant@0: if dir[0] != 0: etisserant@0: pointx = (self.Points[segment].x + self.Points[segment + 1].x) / 2 etisserant@0: if dir[1] != 0: etisserant@0: pointy = (self.Points[segment].y + self.Points[segment + 1].y) / 2 etisserant@0: self.Points.insert(segment + 1, wxPoint(pointx, pointy)) etisserant@0: self.Segments.insert(segment + 1, (dir[1], dir[0])) etisserant@0: self.Points.insert(segment + 2, wxPoint(pointx, pointy)) etisserant@0: self.Segments.insert(segment + 2, 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 etisserant@0: def OnLeftDown(self, event, scaling): etisserant@0: pos = GetScaledEventPosition(event, scaling) etisserant@0: # Test if a point have been handled etisserant@0: result = self.TestPoint(pos) etisserant@0: if result != None: etisserant@0: self.Handle = (HANDLE_POINT, result) etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_HAND)) etisserant@0: else: etisserant@0: # Test if a segment have been handled etisserant@0: result = self.TestSegment(pos) etisserant@0: if result != None: etisserant@0: if result[1] in (NORTH, SOUTH): etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZEWE)) etisserant@0: elif result[1] in (EAST, WEST): etisserant@0: self.Parent.SetCursor(wxStockCursor(wxCURSOR_SIZENS)) etisserant@0: self.Handle = (HANDLE_SEGMENT, result) etisserant@0: # Execute the default method for a graphic element etisserant@0: else: etisserant@0: Graphic_Element.OnLeftDown(self, event, scaling) etisserant@0: self.oldPos = pos etisserant@0: etisserant@0: # Method called when a RightUp event have been generated etisserant@0: def OnRightUp(self, event, scaling): etisserant@0: pos = GetScaledEventPosition(event, scaling) etisserant@0: # Test if a segment has been handled etisserant@0: result = self.TestSegment(pos) etisserant@0: if result != None: etisserant@0: self.Handle = (HANDLE_SEGMENT, result) etisserant@0: # Popup the menu with special items for a wire etisserant@0: self.Parent.PopupWireMenu() etisserant@0: else: etisserant@0: # Execute the default method for a graphic element etisserant@0: Graphic_Element.OnRightUp(self, event, scaling) etisserant@0: etisserant@0: # Method called when a LeftDClick event have been generated etisserant@0: def OnLeftDClick(self, event, scaling): etisserant@0: self.ResetPoints() etisserant@0: self.GeneratePoints() etisserant@0: etisserant@0: # Method called when a Motion event have been generated etisserant@0: def OnMotion(self, event, scaling): etisserant@0: pos = GetScaledEventPosition(event, 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): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZEWE)) etisserant@0: elif result[1] in (EAST, WEST): etisserant@0: wxCallAfter(self.Parent.SetCursor, wxStockCursor(wxCURSOR_SIZENS)) etisserant@0: else: etisserant@0: # Test if a point has been handled etisserant@0: result = self.TestPoint(pos) etisserant@0: if result != None: etisserant@0: if result == 0 and self.StartConnected: etisserant@0: self.OverStart = True etisserant@0: elif result != 0 and self.EndConnected: etisserant@0: self.OverEnd = True etisserant@0: else: etisserant@0: self.OverStart = False etisserant@0: self.OverEnd = False etisserant@0: # Execute the default method for a graphic element etisserant@0: Graphic_Element.OnMotion(self, event, scaling) etisserant@0: else: etisserant@0: # Execute the default method for a graphic element etisserant@0: Graphic_Element.OnMotion(self, event, scaling) etisserant@0: etisserant@0: # Refreshes the wire state according to move defined and handle selected etisserant@0: def ProcessDragging(self, movex, movey): etisserant@0: handle_type, handle = self.Handle etisserant@0: # A point has been handled etisserant@0: if handle_type == HANDLE_POINT: etisserant@0: # Try to connect point to a connector etisserant@0: new_pos = wxPoint(self.Points[handle].x + movex, self.Points[handle].y + movey) etisserant@0: connector = self.Parent.FindBlockConnector(new_pos) etisserant@0: if connector: etisserant@0: if handle == 0 and self.EndConnected != connector: etisserant@0: connector.Connect((self, handle)) etisserant@0: self.SetStartPointDirection(connector.GetDirection()) etisserant@0: self.ConnectStartPoint(connector.GetPosition(), connector) etisserant@0: self.Dragging = False etisserant@0: elif handle != 0 and self.StartConnected != connector: etisserant@0: connector.Connect((self, handle)) etisserant@0: self.SetEndPointDirection(connector.GetDirection()) etisserant@0: self.ConnectEndPoint(connector.GetPosition(), connector) etisserant@0: 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: etisserant@0: if self.StartConnected: etisserant@0: self.UnConnectStartPoint() etisserant@0: self.MoveStartPoint(new_pos) etisserant@0: else: etisserant@0: if self.EndConnected: etisserant@0: self.UnConnectEndPoint() etisserant@0: self.MoveEndPoint(new_pos) etisserant@0: self.RefreshModel() etisserant@0: # A segment has been handled, move a segment etisserant@0: elif handle_type == HANDLE_SEGMENT: etisserant@0: self.MoveSegment(handle[0], movex, movey) etisserant@0: # Execute the default method for a graphic element etisserant@0: else: etisserant@0: Graphic_Element.ProcessDragging(self, movex, movey) 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: etisserant@0: # Draws the wire lines and points etisserant@0: def Draw(self, dc): etisserant@0: dc.SetPen(wxBLACK_PEN) etisserant@0: dc.SetBrush(wxBLACK_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) etisserant@0: dc.DrawLines(self.Points) etisserant@0: dc.DrawPoint(self.Points[-1].x, self.Points[-1].y) etisserant@0: # Draw the segment selected in red etisserant@0: if self.SelectedSegment != None: etisserant@0: dc.SetPen(wxRED_PEN) etisserant@0: dc.DrawLine(self.Points[self.SelectedSegment].x, self.Points[self.SelectedSegment].y, etisserant@0: self.Points[self.SelectedSegment + 1].x, self.Points[self.SelectedSegment + 1].y) etisserant@0: if self.SelectedSegment == len(self.Segments) - 1: etisserant@0: dc.DrawPoint(self.Points[-1].x, self.Points[-1].y) etisserant@0: Graphic_Element.Draw(self, dc) etisserant@0: etisserant@0: etisserant@0: #------------------------------------------------------------------------------- etisserant@0: # Graphic comment element etisserant@0: #------------------------------------------------------------------------------- etisserant@0: 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 etisserant@0: self.Pos = wxPoint(0, 0) etisserant@0: self.Size = wxSize(0, 0) etisserant@0: 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): etisserant@0: self.BoundingBox = wxRect(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): etisserant@0: dc = wxClientDC(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 etisserant@0: def OnRightUp(self, event, scaling): etisserant@0: # Popup the default menu etisserant@0: self.Parent.PopupDefaultMenu() etisserant@0: 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 etisserant@0: def OnLeftDClick(self, event, scaling): etisserant@0: # Edit the comment content etisserant@0: self.Parent.EditCommentContent(self) etisserant@0: etisserant@0: # Draws the comment and its content etisserant@0: def Draw(self, dc): etisserant@0: dc.SetPen(wxBLACK_PEN) etisserant@0: dc.SetBrush(wxWHITE_BRUSH) etisserant@0: # Draws the comment shape etisserant@0: polygon = [wxPoint(self.Pos.x, self.Pos.y), etisserant@0: wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y), etisserant@0: wxPoint(self.Pos.x + self.Size[0], self.Pos.y + 10), etisserant@0: wxPoint(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] + 1), etisserant@0: wxPoint(self.Pos.x, self.Pos.y + self.Size[1] + 1)] etisserant@0: dc.DrawPolygon(polygon) etisserant@0: lines = [wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y), etisserant@0: wxPoint(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10), etisserant@0: wxPoint(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 etisserant@0: for line in self.Content.splitlines(): etisserant@0: first = True etisserant@0: words = line.split(" ") etisserant@0: for i, word in enumerate(words): etisserant@0: if first: etisserant@0: test = word etisserant@0: else: etisserant@0: test = linetext + " " + word etisserant@0: wordwidth, wordheight = dc.GetTextExtent(test) etisserant@0: if y + wordheight > self.Pos.y + self.Size[1] - 10: etisserant@0: break etisserant@0: if wordwidth < self.Size[0] - 20 and i < len(words) - 1: etisserant@0: linetext = test etisserant@0: first = False etisserant@0: else: etisserant@0: if wordwidth < self.Size[0] - 20 and i == len(words) - 1: etisserant@0: dc.DrawText(test, self.Pos.x + 10, y) etisserant@0: else: etisserant@0: dc.DrawText(linetext, self.Pos.x + 10, y) etisserant@0: if i == len(words) - 1: etisserant@0: y += wordheight + 5 etisserant@0: if y + wordheight > self.Pos.y + self.Size[1] - 10: etisserant@0: break etisserant@0: dc.DrawText(word, self.Pos.x + 10, y) etisserant@0: else: etisserant@0: linetext = word etisserant@0: y += wordheight + 5 etisserant@0: if y + wordheight > self.Pos.y + self.Size[1] - 10: etisserant@0: break etisserant@0: Graphic_Element.Draw(self, dc)