graphics/GraphicCommons.py
changeset 814 5743cbdff669
child 825 0623820aa14a
equal deleted inserted replaced
813:1460273f40ed 814:5743cbdff669
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 import wx
       
    26 from time import time as gettime
       
    27 from math import *
       
    28 from types import *
       
    29 import datetime
       
    30 from threading import Semaphore
       
    31 
       
    32 #-------------------------------------------------------------------------------
       
    33 #                               Common constants
       
    34 #-------------------------------------------------------------------------------
       
    35 
       
    36 """
       
    37 Definition of constants for dimensions of graphic elements
       
    38 """
       
    39 
       
    40 # FBD and SFC constants
       
    41 MIN_MOVE = 5                            # Minimum move before starting a element dragging
       
    42 CONNECTOR_SIZE = 8                      # Size of connectors
       
    43 BLOCK_LINE_SIZE = 20                    # Minimum size of each line in a block
       
    44 HANDLE_SIZE = 6                         # Size of the squares for handles
       
    45 ANCHOR_DISTANCE = 5                     # Distance where wire is automativally attached to a connector
       
    46 POINT_RADIUS = 2                        # Radius of the point of wire ends
       
    47 MIN_SEGMENT_SIZE = 2                    # Minimum size of the endling segments of a wire
       
    48 
       
    49 # LD constants
       
    50 LD_LINE_SIZE = 40                       # Distance between two lines in a ladder rung
       
    51 LD_ELEMENT_SIZE = (21, 15)              # Size (width, height) of a ladder element (contact or coil)
       
    52 LD_WIRE_SIZE = 30                       # Size of a wire between two contact
       
    53 LD_WIRECOIL_SIZE = 70                   # Size of a wire between a coil and a contact
       
    54 LD_POWERRAIL_WIDTH = 3                  # Width of a Powerrail
       
    55 LD_OFFSET = (10, 10)                    # Distance (x, y) between each comment and rung of the ladder
       
    56 LD_COMMENT_DEFAULTSIZE = (600, 40)      # Size (width, height) of a comment box
       
    57 
       
    58 # SFC constants
       
    59 SFC_STEP_DEFAULT_SIZE = (40, 30)        # Default size of a SFC step
       
    60 SFC_TRANSITION_SIZE = (20, 2)           # Size of a SFC transition
       
    61 SFC_DEFAULT_SEQUENCE_INTERVAL = 40      # Default size of the interval between two divergence branches
       
    62 SFC_SIMULTANEOUS_SEQUENCE_EXTRA = 20    # Size of extra lines for simultaneous divergence and convergence
       
    63 SFC_JUMP_SIZE = (12, 13)                # Size of a SFC jump to step
       
    64 SFC_WIRE_MIN_SIZE = 25                  # Size of a wire between two elements
       
    65 SFC_ACTION_MIN_SIZE = (100, 30)         # Minimum size of an action block line
       
    66 
       
    67 # Type definition constants for graphic elements
       
    68 [INPUT, OUTPUT, INOUT] = range(3)
       
    69 [CONNECTOR, CONTINUATION] = range(2)
       
    70 [LEFTRAIL, RIGHTRAIL] = range(2)
       
    71 [CONTACT_NORMAL, CONTACT_REVERSE, CONTACT_RISING, CONTACT_FALLING] = range(4)
       
    72 [COIL_NORMAL, COIL_REVERSE, COIL_SET, COIL_RESET, COIL_RISING, COIL_FALLING] = range(6)
       
    73 [SELECTION_DIVERGENCE, SELECTION_CONVERGENCE, SIMULTANEOUS_DIVERGENCE, SIMULTANEOUS_CONVERGENCE] = range(4)
       
    74 
       
    75 # Constants for defining the type of dragging that has been selected
       
    76 [HANDLE_MOVE, HANDLE_RESIZE, HANDLE_POINT, HANDLE_SEGMENT, HANDLE_CONNECTOR] = range(5)
       
    77 
       
    78 # List of value for resize handle that are valid
       
    79 VALID_HANDLES = [(1,1), (1,2), (1,3), (2,3), (3,3), (3,2), (3,1), (2,1)]
       
    80 
       
    81 # Contants for defining the direction of a connector
       
    82 [EAST, NORTH, WEST, SOUTH] = [(1,0), (0,-1), (-1,0), (0,1)]
       
    83 
       
    84 # Contants for defining which mode is selected for each view 
       
    85 [MODE_SELECTION, MODE_BLOCK, MODE_VARIABLE, MODE_CONNECTION, MODE_COMMENT, 
       
    86  MODE_COIL, MODE_CONTACT, MODE_POWERRAIL, MODE_INITIALSTEP, MODE_STEP, 
       
    87  MODE_TRANSITION, MODE_DIVERGENCE, MODE_JUMP, MODE_ACTION, MODE_MOTION] = range(15)
       
    88 
       
    89 # Contants for defining alignment types for graphic group 
       
    90 [ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM] = range(6)
       
    91 
       
    92 # Contants for defining which drawing mode is selected for app
       
    93 [FREEDRAWING_MODE, DRIVENDRAWING_MODE] = [1, 2]
       
    94 
       
    95 # Color for Highlighting
       
    96 HIGHLIGHTCOLOR = wx.CYAN
       
    97 
       
    98 # Define highlight types
       
    99 ERROR_HIGHLIGHT = (wx.Colour(255, 255, 0), wx.RED)
       
   100 SEARCH_RESULT_HIGHLIGHT = (wx.Colour(255, 165, 0), wx.WHITE)
       
   101 
       
   102 # Define highlight refresh inhibition period in second
       
   103 REFRESH_HIGHLIGHT_PERIOD = 0.1
       
   104 
       
   105 # Define tooltip wait for displaying period in second
       
   106 TOOLTIP_WAIT_PERIOD = 0.5
       
   107 
       
   108 HANDLE_CURSORS = {
       
   109     (1, 1) : 2,
       
   110     (3, 3) : 2,
       
   111     (1, 3) : 3,
       
   112     (3, 1) : 3,
       
   113     (1, 2) : 4,
       
   114     (3, 2) : 4,
       
   115     (2, 1) : 5,
       
   116     (2, 3) : 5
       
   117 }
       
   118 
       
   119 def round_scaling(x, n, constraint=0):
       
   120     fraction = float(x) / float(n)
       
   121     if constraint == - 1:
       
   122         xround = int(fraction)
       
   123     else:
       
   124         xround = round(fraction)
       
   125         if constraint == 1 and int(fraction) == xround:
       
   126             xround += 1
       
   127     return xround * n
       
   128 
       
   129 """
       
   130 Basic vector operations for calculate wire points
       
   131 """
       
   132 
       
   133 # Create a vector from two points and define if vector must be normal
       
   134 def vector(p1, p2, normal = True):
       
   135     vector = (p2.x - p1.x, p2.y - p1.y)
       
   136     if normal:
       
   137         return normalize(vector)
       
   138     return vector
       
   139 
       
   140 # Calculate the norm of a given vector
       
   141 def norm(v):
       
   142     return sqrt(v[0] * v[0] + v[1] * v[1])
       
   143 
       
   144 # Normalize a given vector
       
   145 def normalize(v):
       
   146     v_norm = norm(v)
       
   147     # Verifie if it is not a null vector
       
   148     if v_norm > 0:
       
   149         return (v[0] / v_norm, v[1] / v_norm)
       
   150     else:
       
   151         return v
       
   152 
       
   153 # Calculate the scalar product of two vectors
       
   154 def is_null_vector(v):
       
   155     return v == (0, 0)
       
   156 
       
   157 # Calculate the scalar product of two vectors
       
   158 def add_vectors(v1, v2):
       
   159     return (v1[0] + v2[0], v1[1] + v2[1])
       
   160 
       
   161 # Calculate the scalar product of two vectors
       
   162 def product(v1, v2):
       
   163     return v1[0] * v2[0] + v1[1] * v2[1]
       
   164 
       
   165 
       
   166 """
       
   167 Function that calculates the nearest point of the grid defined by scaling for the given point
       
   168 """
       
   169 
       
   170 def GetScaledEventPosition(event, dc, scaling):
       
   171     pos = event.GetLogicalPosition(dc)
       
   172     if scaling:
       
   173         pos.x = round(float(pos.x) / float(scaling[0])) * scaling[0]
       
   174         pos.y = round(float(pos.y) / float(scaling[1])) * scaling[1]
       
   175     return pos
       
   176 
       
   177 
       
   178 """
       
   179 Function that choose a direction during the wire points generation
       
   180 """
       
   181 
       
   182 def DirectionChoice(v_base, v_target, dir_target):
       
   183     dir_product = product(v_base, v_target)
       
   184     if dir_product < 0:
       
   185         return (-v_base[0], -v_base[1])
       
   186     elif dir_product == 0 and product(v_base, dir_target) != 0:
       
   187         return dir_target
       
   188     return v_base
       
   189 
       
   190 SECOND = 1000000
       
   191 MINUTE = 60 * SECOND
       
   192 HOUR = 60 * MINUTE
       
   193 DAY = 24 * HOUR
       
   194 
       
   195 def generate_time(value):
       
   196     microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
       
   197     negative = microseconds < 0
       
   198     microseconds = abs(microseconds)
       
   199     data = "T#"
       
   200     not_null = False
       
   201     if negative:
       
   202         data += "-"
       
   203     for val, format in [(int(microseconds) / DAY, "%dd"),
       
   204                         ((int(microseconds) % DAY) / HOUR, "%dh"),
       
   205                         ((int(microseconds) % HOUR) / MINUTE, "%dm"),
       
   206                         ((int(microseconds) % MINUTE) / SECOND, "%ds")]:
       
   207         if val > 0 or not_null:
       
   208             data += format % val
       
   209             not_null = True
       
   210     data += "%gms" % (microseconds % SECOND / 1000.)
       
   211     return data
       
   212 
       
   213 def generate_date(value):
       
   214     base_date = datetime.datetime(1970, 1, 1)
       
   215     date = base_date + value 
       
   216     return date.strftime("DATE#%Y-%m-%d")
       
   217 
       
   218 def generate_datetime(value):
       
   219     base_date = datetime.datetime(1970, 1, 1)
       
   220     date_time = base_date + value 
       
   221     return date_time.strftime("DT#%Y-%m-%d-%H:%M:%S.%f")
       
   222 
       
   223 def generate_timeofday(value):
       
   224     microseconds = float(value.days * DAY + value.seconds * SECOND + value.microseconds)
       
   225     negative = microseconds < 0
       
   226     microseconds = abs(microseconds)
       
   227     data = "TOD#"
       
   228     for val, format in [(int(microseconds) / HOUR, "%2.2d:"),
       
   229                         ((int(microseconds) % HOUR) / MINUTE, "%2.2d:"),
       
   230                         ((int(microseconds) % MINUTE) / SECOND, "%2.2d."),
       
   231                         (microseconds % SECOND, "%6.6d")]:
       
   232         data += format % val
       
   233     return data
       
   234 
       
   235 TYPE_TRANSLATOR = {"TIME": generate_time,
       
   236                    "DATE": generate_date,
       
   237                    "DT": generate_datetime,
       
   238                    "TOD": generate_timeofday}
       
   239 
       
   240 def MiterPen(colour, width=1, style=wx.SOLID):
       
   241     pen = wx.Pen(colour, width, style)
       
   242     pen.SetJoin(wx.JOIN_MITER)
       
   243     pen.SetCap(wx.CAP_PROJECTING)
       
   244     return pen
       
   245 
       
   246 #-------------------------------------------------------------------------------
       
   247 #                            Debug Data Consumer Class
       
   248 #-------------------------------------------------------------------------------
       
   249 
       
   250 class DebugDataConsumer:
       
   251     
       
   252     def __init__(self):
       
   253         self.LastValue = None
       
   254         self.Value = None
       
   255         self.DataType = None
       
   256         self.LastForced = False
       
   257         self.Forced = False
       
   258         self.Inhibited = False
       
   259     
       
   260     def Inhibit(self, inhibit):
       
   261         self.Inhibited = inhibit
       
   262         if not inhibit and self.LastValue is not None:
       
   263             self.SetForced(self.LastForced)
       
   264             self.SetValue(self.LastValue)
       
   265             self.LastValue = None
       
   266     
       
   267     def SetDataType(self, data_type):
       
   268         self.DataType = data_type
       
   269     
       
   270     def NewValue(self, tick, value, forced=False):
       
   271         value = TYPE_TRANSLATOR.get(self.DataType, lambda x:x)(value)
       
   272         if self.Inhibited:
       
   273             self.LastValue = value
       
   274             self.LastForced = forced
       
   275         else:
       
   276             self.SetForced(forced)
       
   277             self.SetValue(value)
       
   278     
       
   279     def SetValue(self, value):
       
   280         self.Value = value
       
   281     
       
   282     def GetValue(self):
       
   283         return self.Value
       
   284     
       
   285     def SetForced(self, forced):
       
   286         self.Forced = forced
       
   287     
       
   288     def IsForced(self):
       
   289         return self.Forced
       
   290 
       
   291 #-------------------------------------------------------------------------------
       
   292 #                               Debug Viewer Class
       
   293 #-------------------------------------------------------------------------------
       
   294 
       
   295 REFRESH_PERIOD = 0.1
       
   296 
       
   297 class DebugViewer:
       
   298     
       
   299     def __init__(self, producer, debug, register_tick=True):
       
   300         self.DataProducer = None
       
   301         self.Debug = debug
       
   302         self.RegisterTick = register_tick
       
   303         self.Inhibited = False
       
   304         
       
   305         self.DataConsumers = {}
       
   306         
       
   307         self.LastRefreshTime = gettime()
       
   308         self.RefreshLock = Semaphore()
       
   309         
       
   310         self.RefreshTimer = wx.Timer(self, -1)
       
   311         self.Bind(wx.EVT_TIMER, self.OnRefreshTimer, self.RefreshTimer)
       
   312         
       
   313         self.SetDataProducer(producer)
       
   314         
       
   315     def __del__(self):
       
   316         self.DataProducer = None
       
   317         self.DeleteDataConsumers()
       
   318         self.RefreshTimer.Stop()
       
   319     
       
   320     def SetDataProducer(self, producer):
       
   321         if self.RegisterTick and self.Debug:
       
   322             if producer is not None:
       
   323                 producer.SubscribeDebugIECVariable("__tick__", self)
       
   324             if self.DataProducer is not None:
       
   325                 self.DataProducer.UnsubscribeDebugIECVariable("__tick__", self)        
       
   326         self.DataProducer = producer
       
   327     
       
   328     def IsDebugging(self):
       
   329         return self.Debug
       
   330     
       
   331     def Inhibit(self, inhibit):
       
   332         for consumer, iec_path in self.DataConsumers.iteritems():
       
   333             consumer.Inhibit(inhibit)
       
   334         self.Inhibited = inhibit
       
   335     
       
   336     def AddDataConsumer(self, iec_path, consumer):
       
   337         if self.DataProducer is None:
       
   338             return None
       
   339         result = self.DataProducer.SubscribeDebugIECVariable(iec_path, consumer)
       
   340         if result is not None and consumer != self:
       
   341             self.DataConsumers[consumer] = iec_path
       
   342             consumer.SetDataType(self.GetDataType(iec_path))
       
   343         return result
       
   344     
       
   345     def RemoveDataConsumer(self, consumer):
       
   346         iec_path = self.DataConsumers.pop(consumer, None)
       
   347         if iec_path is not None:
       
   348             self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
       
   349     
       
   350     def GetDataType(self, iec_path):
       
   351         if self.DataProducer is not None:
       
   352             return self.DataProducer.GetDebugIECVariableType(iec_path)
       
   353         return None
       
   354     
       
   355     def ForceDataValue(self, iec_path, value):
       
   356         if self.DataProducer is not None:
       
   357             self.DataProducer.ForceDebugIECVariable(iec_path, value)
       
   358     
       
   359     def ReleaseDataValue(self, iec_path):
       
   360         if self.DataProducer is not None:
       
   361             self.DataProducer.ReleaseDebugIECVariable(iec_path)
       
   362     
       
   363     def DeleteDataConsumers(self):
       
   364         if self.DataProducer is not None:
       
   365             for consumer, iec_path in self.DataConsumers.iteritems():
       
   366                 self.DataProducer.UnsubscribeDebugIECVariable(iec_path, consumer)
       
   367         self.DataConsumers = {}
       
   368     
       
   369     def OnRefreshTimer(self, event):
       
   370         self.RefreshNewData()
       
   371         event.Skip()
       
   372     
       
   373     def NewDataAvailable(self, *args, **kwargs):
       
   374         self.RefreshTimer.Stop()
       
   375         if not self.Inhibited:
       
   376             current_time = gettime()
       
   377             if current_time - self.LastRefreshTime > REFRESH_PERIOD and self.RefreshLock.acquire(False):
       
   378                 self.LastRefreshTime = gettime()
       
   379                 self.Inhibit(True)
       
   380                 wx.CallAfter(self.RefreshViewOnNewData, *args, **kwargs)
       
   381             
       
   382     def RefreshViewOnNewData(self, *args, **kwargs):
       
   383         if self:
       
   384             self.RefreshNewData(*args, **kwargs)
       
   385             self.RefreshTimer.Start(int(REFRESH_PERIOD * 1000), oneShot=True)
       
   386     
       
   387     def RefreshNewData(self, *args, **kwargs):
       
   388         self.Inhibit(False)
       
   389         self.RefreshLock.release()
       
   390         
       
   391 #-------------------------------------------------------------------------------
       
   392 #                               Viewer Rubberband
       
   393 #-------------------------------------------------------------------------------
       
   394 
       
   395 """
       
   396 Class that implements a rubberband
       
   397 """
       
   398 
       
   399 class RubberBand:
       
   400     
       
   401     # Create a rubberband by indicated on which window it must be drawn
       
   402     def __init__(self, viewer):
       
   403         self.Viewer = viewer
       
   404         self.drawingSurface = viewer.Editor
       
   405         self.Reset()
       
   406     
       
   407     # Method that initializes the internal attributes of the rubberband
       
   408     def Reset(self):
       
   409         self.startPoint = None
       
   410         self.currentBox = None
       
   411         self.lastBox = None
       
   412     
       
   413     # Method that return if a box is currently edited
       
   414     def IsShown(self):
       
   415         return self.currentBox != None
       
   416     
       
   417     # Method that returns the currently edited box
       
   418     def GetCurrentExtent(self):
       
   419         if self.currentBox is None:
       
   420             return self.lastBox
       
   421         return self.currentBox
       
   422     
       
   423     # Method called when a new box starts to be edited
       
   424     def OnLeftDown(self, event, dc, scaling):
       
   425         pos = GetScaledEventPosition(event, dc, scaling)
       
   426         # Save the point for calculate the box position and size
       
   427         self.startPoint = pos
       
   428         self.currentBox = wx.Rect(pos.x, pos.y, 0, 0)
       
   429         self.drawingSurface.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
       
   430         self.Redraw()
       
   431     
       
   432     # Method called when dragging with a box edited
       
   433     def OnMotion(self, event, dc, scaling):
       
   434         pos = GetScaledEventPosition(event, dc, scaling)
       
   435         # Save the last position and size of the box for erasing it
       
   436         self.lastBox = wx.Rect(self.currentBox.x, self.currentBox.y, self.currentBox.width,
       
   437             self.currentBox.height)
       
   438         # Calculate new position and size of the box 
       
   439         if pos.x >= self.startPoint.x:
       
   440             self.currentBox.x = self.startPoint.x
       
   441             self.currentBox.width = pos.x - self.startPoint.x + 1
       
   442         else:
       
   443             self.currentBox.x = pos.x
       
   444             self.currentBox.width = self.startPoint.x - pos.x + 1
       
   445         if pos.y >= self.startPoint.y:
       
   446             self.currentBox.y = self.startPoint.y
       
   447             self.currentBox.height = pos.y - self.startPoint.y + 1
       
   448         else:
       
   449             self.currentBox.y = pos.y
       
   450             self.currentBox.height = self.startPoint.y - pos.y + 1
       
   451         self.Redraw()
       
   452     
       
   453     # Method called when dragging is stopped
       
   454     def OnLeftUp(self, event, dc, scaling):
       
   455         self.drawingSurface.SetCursor(wx.NullCursor)
       
   456         self.lastBox = self.currentBox
       
   457         self.currentBox = None
       
   458         self.Redraw()
       
   459 
       
   460     # Method that erase the last box and draw the new box
       
   461     def Redraw(self, dc = None):
       
   462         if dc is None:
       
   463             dc = self.Viewer.GetLogicalDC()
       
   464         scalex, scaley = dc.GetUserScale()
       
   465         dc.SetUserScale(1, 1)
       
   466         dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
       
   467         dc.SetBrush(wx.TRANSPARENT_BRUSH)
       
   468         dc.SetLogicalFunction(wx.XOR)
       
   469         if self.lastBox:
       
   470             # Erase last box
       
   471             dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley, 
       
   472                              self.lastBox.width * scalex, self.lastBox.height * scaley)
       
   473         if self.currentBox:
       
   474             # Draw current box
       
   475             dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley, 
       
   476                              self.currentBox.width * scalex, self.currentBox.height * scaley)
       
   477         dc.SetUserScale(scalex, scaley)
       
   478     
       
   479     # Erase last box
       
   480     def Erase(self, dc = None):
       
   481         if dc is None:
       
   482             dc = self.Viewer.GetLogicalDC()
       
   483         scalex, scaley = dc.GetUserScale()
       
   484         dc.SetUserScale(1, 1)
       
   485         dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
       
   486         dc.SetBrush(wx.TRANSPARENT_BRUSH)
       
   487         dc.SetLogicalFunction(wx.XOR)
       
   488         if self.lastBox:
       
   489             dc.DrawRectangle(self.lastBox.x * scalex, self.lastBox.y * scaley, 
       
   490                              self.lastBox.width * scalex, self.lastBox.height * scalex)
       
   491         dc.SetUserScale(scalex, scaley)
       
   492 
       
   493     # Draw current box
       
   494     def Draw(self, dc = None):
       
   495         if dc is None:
       
   496             dc = self.Viewer.GetLogicalDC()
       
   497         scalex, scaley = dc.GetUserScale()
       
   498         dc.SetUserScale(1, 1)
       
   499         dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
       
   500         dc.SetBrush(wx.TRANSPARENT_BRUSH)
       
   501         dc.SetLogicalFunction(wx.XOR)
       
   502         if self.currentBox:
       
   503             # Draw current box
       
   504             dc.DrawRectangle(self.currentBox.x * scalex, self.currentBox.y * scaley, 
       
   505                              self.currentBox.width * scalex, self.currentBox.height * scaley)
       
   506         dc.SetUserScale(scalex, scaley)
       
   507 
       
   508 #-------------------------------------------------------------------------------
       
   509 #                               Viewer ToolTip
       
   510 #-------------------------------------------------------------------------------
       
   511 
       
   512 """
       
   513 Class that implements a custom tool tip
       
   514 """
       
   515 
       
   516 if wx.Platform == '__WXMSW__':
       
   517     faces = { 'times': 'Times New Roman',
       
   518               'mono' : 'Courier New',
       
   519               'helv' : 'Arial',
       
   520               'other': 'Comic Sans MS',
       
   521               'size' : 10,
       
   522              }
       
   523 else:
       
   524     faces = { 'times': 'Times',
       
   525               'mono' : 'Courier',
       
   526               'helv' : 'Helvetica',
       
   527               'other': 'new century schoolbook',
       
   528               'size' : 12,
       
   529              }
       
   530 
       
   531 TOOLTIP_MAX_CHARACTERS = 30
       
   532 TOOLTIP_MAX_LINE = 5
       
   533 
       
   534 class ToolTip(wx.PopupWindow):
       
   535     
       
   536     def __init__(self, parent, tip):
       
   537         wx.PopupWindow.__init__(self, parent)
       
   538         
       
   539         self.CurrentPosition = wx.Point(0, 0)
       
   540         
       
   541         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
       
   542         self.SetTip(tip)
       
   543         
       
   544         self.Bind(wx.EVT_PAINT, self.OnPaint)
       
   545         
       
   546     def SetTip(self, tip):
       
   547         lines = []
       
   548         for line in tip.splitlines():
       
   549             if line != "":
       
   550                 words = line.split()
       
   551                 new_line = words[0]
       
   552                 for word in words[1:]:
       
   553                     if len(new_line + " " + word) <= TOOLTIP_MAX_CHARACTERS:
       
   554                         new_line += " " + word
       
   555                     else:
       
   556                         lines.append(new_line)
       
   557                         new_line = word
       
   558                 lines.append(new_line)
       
   559             else:
       
   560                 lines.append(line)
       
   561         if len(lines) > TOOLTIP_MAX_LINE:
       
   562             self.Tip = lines[:TOOLTIP_MAX_LINE]
       
   563             if len(self.Tip[-1]) < TOOLTIP_MAX_CHARACTERS - 3:
       
   564                 self.Tip[-1] += "..."
       
   565             else:
       
   566                 self.Tip[-1] = self.Tip[-1][:TOOLTIP_MAX_CHARACTERS - 3] + "..."
       
   567         else:
       
   568             self.Tip = lines
       
   569         wx.CallAfter(self.RefreshTip)
       
   570     
       
   571     def MoveToolTip(self, pos):
       
   572         self.CurrentPosition = pos
       
   573         self.SetPosition(pos)
       
   574     
       
   575     def GetTipExtent(self):
       
   576         max_width = 0
       
   577         max_height = 0
       
   578         for line in self.Tip:
       
   579             w, h = self.GetTextExtent(line)
       
   580             max_width = max(max_width, w)
       
   581             max_height += h
       
   582         return max_width, max_height
       
   583     
       
   584     def RefreshTip(self):
       
   585         if self:
       
   586             w, h = self.GetTipExtent()
       
   587             self.SetSize(wx.Size(w + 4, h + 4))
       
   588             self.SetPosition(self.CurrentPosition)
       
   589             self.Refresh()
       
   590         
       
   591     def OnPaint(self, event):
       
   592         dc = wx.AutoBufferedPaintDC(self)
       
   593         dc.Clear()
       
   594         dc.SetPen(MiterPen(wx.BLACK))
       
   595         dc.SetBrush(wx.Brush(wx.Colour(255, 238, 170)))
       
   596         dc.SetFont(wx.Font(faces["size"], wx.SWISS, wx.NORMAL, wx.NORMAL, faceName = faces["mono"]))
       
   597         dc.BeginDrawing()
       
   598         w, h = self.GetTipExtent()
       
   599         dc.DrawRectangle(0, 0, w + 4, h + 4)
       
   600         offset = 0
       
   601         for line in self.Tip:
       
   602             dc.DrawText(line, 2, offset + 2)
       
   603             w, h = dc.GetTextExtent(line)
       
   604             offset += h
       
   605         dc.EndDrawing()
       
   606         event.Skip()
       
   607 
       
   608 #-------------------------------------------------------------------------------
       
   609 #                    Helpers for highlighting text
       
   610 #-------------------------------------------------------------------------------
       
   611 
       
   612 def AddHighlight(highlights, infos):
       
   613     RemoveHighlight(highlights, infos)
       
   614     highlights.append(infos)
       
   615 
       
   616 def RemoveHighlight(highlights, infos):
       
   617     if infos in highlights:
       
   618         highlights.remove(infos)
       
   619         return True
       
   620     return False
       
   621 
       
   622 def ClearHighlight(highlights, highlight_type=None):
       
   623     if highlight_type is not None:
       
   624         return [highlight for highlight in highlights if highlight[2] != highlight_type]
       
   625     return []
       
   626 
       
   627 def DrawHighlightedText(dc, text, highlights, x, y):
       
   628     current_pen = dc.GetPen()
       
   629     dc.SetPen(wx.TRANSPARENT_PEN)
       
   630     for start, end, highlight_type in highlights:
       
   631         dc.SetBrush(wx.Brush(highlight_type[0]))
       
   632         offset_width, offset_height = dc.GetTextExtent(text[:start[1]])
       
   633         part = text[start[1]:end[1] + 1]
       
   634         part_width, part_height = dc.GetTextExtent(part)
       
   635         dc.DrawRectangle(x + offset_width, y, part_width, part_height)
       
   636         dc.SetTextForeground(highlight_type[1])
       
   637         dc.DrawText(part, x + offset_width, y)
       
   638     dc.SetPen(current_pen)
       
   639     dc.SetTextForeground(wx.BLACK)
       
   640     
       
   641 #-------------------------------------------------------------------------------
       
   642 #                           Graphic element base class
       
   643 #-------------------------------------------------------------------------------
       
   644 
       
   645 """
       
   646 Class that implements a generic graphic element
       
   647 """
       
   648 
       
   649 class Graphic_Element:
       
   650     
       
   651     # Create a new graphic element
       
   652     def __init__(self, parent, id = None):
       
   653         self.Parent = parent
       
   654         self.Id = id
       
   655         self.oldPos = None
       
   656         self.StartPos = None
       
   657         self.CurrentDrag = None
       
   658         self.Handle = (None,None)
       
   659         self.Dragging = False
       
   660         self.Selected = False
       
   661         self.Highlighted = False
       
   662         self.Pos = wx.Point(0, 0)
       
   663         self.Size = wx.Size(0, 0)
       
   664         self.BoundingBox = wx.Rect(0, 0, 0, 0)
       
   665         self.Visible = False
       
   666         self.ToolTip = None
       
   667         self.ToolTipPos = None
       
   668         self.ToolTipTimer = wx.Timer(self.Parent, -1)
       
   669         self.Parent.Bind(wx.EVT_TIMER, self.OnToolTipTimer, self.ToolTipTimer)
       
   670     
       
   671     def __del__(self):
       
   672         self.ToolTipTimer.Stop()
       
   673     
       
   674     def GetDefinition(self):
       
   675         return [self.Id], []
       
   676     
       
   677     def TestVisible(self, screen):
       
   678         self.Visible = self.GetRedrawRect().Intersects(screen)
       
   679     
       
   680     def IsVisible(self):
       
   681         return self.Visible
       
   682     
       
   683     def SpreadCurrent(self):
       
   684         pass
       
   685     
       
   686     def GetConnectorTranslation(self, element):
       
   687         return {}
       
   688     
       
   689     def FindNearestConnector(self, position, connectors):
       
   690         distances = []
       
   691         for connector in connectors:
       
   692             connector_pos = connector.GetRelPosition()
       
   693             distances.append((sqrt((self.Pos.x + connector_pos.x - position.x) ** 2 +
       
   694                                    (self.Pos.y + connector_pos.y - position.y) ** 2),
       
   695                               connector))
       
   696         distances.sort()
       
   697         if len(distances) > 0:
       
   698             return distances[0][1]
       
   699         return None
       
   700         
       
   701     def IsOfType(self, type, reference):
       
   702         return self.Parent.IsOfType(type, reference)
       
   703     
       
   704     def IsEndType(self, type):
       
   705         return self.Parent.IsEndType(type)
       
   706         
       
   707     def GetDragging(self):
       
   708         return self.Dragging
       
   709     
       
   710     # Make a clone of this element
       
   711     def Clone(self, parent):
       
   712         return Graphic_Element(parent, self.Id)
       
   713     
       
   714     # Changes the block position
       
   715     def SetPosition(self, x, y):
       
   716         self.Pos.x = x
       
   717         self.Pos.y = y
       
   718         self.RefreshConnected()
       
   719         self.RefreshBoundingBox()
       
   720 
       
   721     # Returns the block position
       
   722     def GetPosition(self):
       
   723         return self.Pos.x, self.Pos.y
       
   724     
       
   725     # Changes the element size
       
   726     def SetSize(self, width, height):
       
   727         self.Size.SetWidth(width)
       
   728         self.Size.SetHeight(height)
       
   729         self.RefreshConnectors()
       
   730         self.RefreshBoundingBox()
       
   731 
       
   732     # Returns the element size
       
   733     def GetSize(self):
       
   734         return self.Size.GetWidth(), self.Size.GetHeight()
       
   735     
       
   736     # Returns the minimum element size
       
   737     def GetMinSize(self):
       
   738         return 0, 0
       
   739     
       
   740     # Refresh the element Bounding Box
       
   741     def RefreshBoundingBox(self):
       
   742         self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
       
   743     
       
   744     # Refresh the element connectors position
       
   745     def RefreshConnectors(self):
       
   746         pass
       
   747     
       
   748     # Refresh the position of wires connected to element inputs and outputs
       
   749     def RefreshConnected(self):
       
   750         pass
       
   751     
       
   752     # Change the parent
       
   753     def SetParent(self, parent):
       
   754         self.Parent = parent
       
   755     
       
   756     # Override this method for defining the method to call for deleting this element
       
   757     def Delete(self):
       
   758         pass
       
   759     
       
   760     # Returns the Id
       
   761     def GetId(self):
       
   762         return self.Id
       
   763     
       
   764     # Returns if the point given is in the bounding box
       
   765     def HitTest(self, pt, connectors=True):
       
   766         if connectors:
       
   767             rect = self.BoundingBox
       
   768         else:
       
   769             rect = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0], self.Size[1])
       
   770         return rect.InsideXY(pt.x, pt.y)
       
   771     
       
   772     # Returns if the point given is in the bounding box
       
   773     def IsInSelection(self, rect):
       
   774         return rect.InsideXY(self.BoundingBox.x, self.BoundingBox.y) and rect.InsideXY(self.BoundingBox.x + self.BoundingBox.width, self.BoundingBox.y + self.BoundingBox.height)
       
   775     
       
   776     # Override this method for refreshing the bounding box
       
   777     def RefreshBoundingBox(self):
       
   778         pass
       
   779     
       
   780     # Returns the bounding box
       
   781     def GetBoundingBox(self):
       
   782         return self.BoundingBox
       
   783     
       
   784     # Returns the RedrawRect
       
   785     def GetRedrawRect(self, movex = 0, movey = 0):
       
   786         scalex, scaley = self.Parent.GetViewScale()
       
   787         rect = wx.Rect()
       
   788         rect.x = self.BoundingBox.x - int(HANDLE_SIZE / scalex) - 3 - abs(movex)
       
   789         rect.y = self.BoundingBox.y - int(HANDLE_SIZE / scaley) - 3 - abs(movey)
       
   790         rect.width = self.BoundingBox.width + 2 * (int(HANDLE_SIZE / scalex) + abs(movex) + 1) + 4
       
   791         rect.height = self.BoundingBox.height + 2 * (int(HANDLE_SIZE / scaley) + abs(movey) + 1) + 4
       
   792         return rect
       
   793     
       
   794     def Refresh(self, rect = None):
       
   795         if self.Visible:
       
   796             if rect is not None:
       
   797                 self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
       
   798             else:
       
   799                 self.Parent.RefreshRect(self.Parent.GetScrolledRect(self.GetRedrawRect()), False)
       
   800     
       
   801     # Change the variable that indicates if this element is selected
       
   802     def SetSelected(self, selected):
       
   803         self.Selected = selected
       
   804         self.Refresh()
       
   805     
       
   806     # Change the variable that indicates if this element is highlighted
       
   807     def SetHighlighted(self, highlighted):
       
   808         self.Highlighted = highlighted
       
   809         self.Refresh()
       
   810     
       
   811     # Test if the point is on a handle of this element
       
   812     def TestHandle(self, event):
       
   813         dc = self.Parent.GetLogicalDC()
       
   814         scalex, scaley = dc.GetUserScale()
       
   815         pos = event.GetPosition()
       
   816         pt = wx.Point(*self.Parent.CalcUnscrolledPosition(pos.x, pos.y))
       
   817         
       
   818         left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
       
   819         center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
       
   820         right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
       
   821         
       
   822         top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
       
   823         middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
       
   824         bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
       
   825         
       
   826         extern_rect = wx.Rect(left, top, right + HANDLE_SIZE - left, bottom + HANDLE_SIZE - top)
       
   827         intern_rect = wx.Rect(left + HANDLE_SIZE, top + HANDLE_SIZE, right - left - HANDLE_SIZE, bottom - top - HANDLE_SIZE)
       
   828         
       
   829         # Verify that this element is selected
       
   830         if self.Selected and extern_rect.InsideXY(pt.x, pt.y) and not intern_rect.InsideXY(pt.x, pt.y):
       
   831             # Find if point is on a handle horizontally
       
   832             if left <= pt.x < left + HANDLE_SIZE:
       
   833                 handle_x = 1
       
   834             elif center <= pt.x < center + HANDLE_SIZE:
       
   835                 handle_x = 2
       
   836             elif right <= pt.x < right + HANDLE_SIZE:
       
   837                 handle_x = 3
       
   838             else:
       
   839                 handle_x = 0
       
   840             # Find if point is on a handle vertically
       
   841             if top <= pt.y < top + HANDLE_SIZE:
       
   842                 handle_y = 1
       
   843             elif middle <= pt.y < middle + HANDLE_SIZE:
       
   844                 handle_y = 2
       
   845             elif bottom <= pt.y < bottom + HANDLE_SIZE:
       
   846                 handle_y = 3
       
   847             else:
       
   848                 handle_y = 0
       
   849             # Verify that the result is valid
       
   850             if (handle_x, handle_y) in VALID_HANDLES:
       
   851                 return handle_x, handle_y
       
   852         return 0, 0
       
   853     
       
   854     # Method called when a LeftDown event have been generated
       
   855     def OnLeftDown(self, event, dc, scaling):
       
   856         pos = event.GetLogicalPosition(dc)
       
   857         # Test if an handle have been clicked
       
   858         handle = self.TestHandle(event)
       
   859         # Find which type of handle have been clicked,
       
   860         # Save a resize event and change the cursor
       
   861         cursor = HANDLE_CURSORS.get(handle, 1)
       
   862         wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
       
   863         if cursor > 1:
       
   864             self.Handle = (HANDLE_RESIZE, handle)
       
   865         else:
       
   866             self.Handle = (HANDLE_MOVE, None)
       
   867             self.SetSelected(False)
       
   868         # Initializes the last position
       
   869         self.oldPos = GetScaledEventPosition(event, dc, scaling)
       
   870         self.StartPos = wx.Point(self.Pos.x, self.Pos.y)
       
   871         self.CurrentDrag = wx.Point(0, 0)
       
   872     
       
   873     # Method called when a LeftUp event have been generated
       
   874     def OnLeftUp(self, event, dc, scaling):
       
   875         # If a dragging have been initiated
       
   876         if self.Dragging and self.oldPos:
       
   877             self.RefreshModel()
       
   878             self.Parent.RefreshBuffer()
       
   879         wx.CallAfter(self.Parent.SetCurrentCursor, 0)
       
   880         self.SetSelected(True)
       
   881         self.oldPos = None
       
   882 
       
   883     # Method called when a RightDown event have been generated
       
   884     def OnRightDown(self, event, dc, scaling):
       
   885         pass
       
   886 
       
   887     # Method called when a RightUp event have been generated
       
   888     def OnRightUp(self, event, dc, scaling):
       
   889         if self.Dragging and self.oldPos:
       
   890             self.RefreshModel()
       
   891             self.Parent.RefreshBuffer()
       
   892         wx.CallAfter(self.Parent.SetCurrentCursor, 0)
       
   893         self.SetSelected(True)
       
   894         self.oldPos = None
       
   895         if self.Parent.Debug:
       
   896             self.Parent.PopupForceMenu()
       
   897 
       
   898     # Method called when a LeftDClick event have been generated
       
   899     def OnLeftDClick(self, event, dc, scaling):
       
   900         pass
       
   901     
       
   902     # Method called when a Motion event have been generated
       
   903     def OnMotion(self, event, dc, scaling):
       
   904         # If the cursor is dragging and the element have been clicked
       
   905         if event.Dragging() and self.oldPos:
       
   906             # Calculate the movement of cursor
       
   907             pos = event.GetLogicalPosition(dc)
       
   908             movex = pos.x - self.oldPos.x
       
   909             movey = pos.y - self.oldPos.y
       
   910             # If movement is greater than MIN_MOVE then a dragging is initiated
       
   911             if not self.Dragging and (abs(movex) > MIN_MOVE or abs(movey) > MIN_MOVE):
       
   912                 self.Dragging = True
       
   913             # If a dragging have been initiated, refreshes the element state
       
   914             if self.Dragging:
       
   915                 dragx, dragy = self.ProcessDragging(movex, movey, event, scaling)
       
   916                 if event.ControlDown() and self.Handle[0] == HANDLE_MOVE:
       
   917                     self.oldPos.x = self.StartPos.x + self.CurrentDrag.x
       
   918                     self.oldPos.y = self.StartPos.y + self.CurrentDrag.y
       
   919                 else:
       
   920                     self.oldPos.x += dragx
       
   921                     self.oldPos.y += dragy
       
   922                 return dragx, dragy
       
   923             return movex, movey
       
   924         # If cursor just pass over the element, changes the cursor if it is on a handle
       
   925         else:
       
   926             pos = event.GetLogicalPosition(dc)
       
   927             handle = self.TestHandle(event)
       
   928             # Find which type of handle have been clicked,
       
   929             # Save a resize event and change the cursor
       
   930             cursor = HANDLE_CURSORS.get(handle, 0)
       
   931             wx.CallAfter(self.Parent.SetCurrentCursor, cursor)
       
   932             return 0, 0
       
   933 
       
   934     # Moves the element
       
   935     def Move(self, dx, dy, exclude = []):
       
   936         self.Pos.x += max(-self.BoundingBox.x, dx)
       
   937         self.Pos.y += max(-self.BoundingBox.y, dy)
       
   938         self.RefreshConnected(exclude)
       
   939         self.RefreshBoundingBox()
       
   940     
       
   941     # Resizes the element from position and size given
       
   942     def Resize(self, x, y, width, height):
       
   943         self.Move(x, y)
       
   944         self.SetSize(width, height)
       
   945     
       
   946     # Moves and Resizes the element for fitting scaling
       
   947     def AdjustToScaling(self, scaling):
       
   948         if scaling is not None:
       
   949             movex = round_scaling(self.Pos.x, scaling[0]) - self.Pos.x
       
   950             movey = round_scaling(self.Pos.y, scaling[1]) - self.Pos.y
       
   951             min_width, min_height = self.GetMinSize()
       
   952             width = max(round_scaling(min_width, scaling[0], 1),
       
   953                         round_scaling(self.Size.width, scaling[0]))
       
   954             height = max(round_scaling(min_height, scaling[1], 1),
       
   955                          round_scaling(self.Size.height, scaling[1]))
       
   956             self.Resize(movex, movey, width, height)
       
   957             return movex, movey
       
   958         return 0, 0
       
   959     
       
   960     # Refreshes the element state according to move defined and handle selected
       
   961     def ProcessDragging(self, movex, movey, event, scaling, width_fac = 1, height_fac = 1):
       
   962         handle_type, handle = self.Handle
       
   963         # If it is a resize handle, calculate the values from resizing
       
   964         if handle_type == HANDLE_RESIZE:
       
   965             if scaling is not None:
       
   966                 scaling = (scaling[0] * width_fac, scaling[1] * height_fac)
       
   967             x = y = start_x = start_y = 0
       
   968             width, height = start_width, start_height = self.GetSize()
       
   969             if handle[0] == 1:
       
   970                 movex = max(-self.BoundingBox.x, movex)
       
   971                 if scaling is not None:
       
   972                     movex = -(round_scaling(width - movex, scaling[0]) - width)
       
   973                 x = movex
       
   974                 if event.ShiftDown():
       
   975                     width -= 2 * movex
       
   976                 else:
       
   977                     width -= movex
       
   978             elif handle[0] == 3:
       
   979                 if scaling is not None:
       
   980                     movex = round_scaling(width + movex, scaling[0]) - width
       
   981                 if event.ShiftDown():
       
   982                     movex = min(self.BoundingBox.x, movex)
       
   983                     x = -movex
       
   984                     width += 2 * movex
       
   985                 else:
       
   986                     width += movex
       
   987             if handle[1] == 1:
       
   988                 movey = max(-self.BoundingBox.y, movey)
       
   989                 if scaling is not None:
       
   990                     movey = -(round_scaling(height - movey, scaling[1]) - height)
       
   991                 y = movey
       
   992                 if event.ShiftDown():
       
   993                     height -= 2 * movey
       
   994                 else:
       
   995                     height -= movey
       
   996             elif handle[1] == 3:
       
   997                 if scaling is not None:
       
   998                     movey = round_scaling(height + movey, scaling[1]) - height
       
   999                 if event.ShiftDown():
       
  1000                     movey = min(self.BoundingBox.y, movey)
       
  1001                     y = -movey
       
  1002                     height += 2 * movey
       
  1003                 else:
       
  1004                     height += movey
       
  1005             # Verify that new size is not lesser than minimum
       
  1006             min_width, min_height = self.GetMinSize()
       
  1007             if handle[0] != 2 and (width >= min_width or width > self.Size[0]):
       
  1008                 start_x = x
       
  1009                 start_width = width
       
  1010             else:
       
  1011                 movex = 0
       
  1012             if handle[1] != 2 and (height >= min_height or height > self.Size[1]):
       
  1013                 start_y = y
       
  1014                 start_height = height
       
  1015             else:
       
  1016                 movey = 0
       
  1017             if movex != 0 or movey != 0:
       
  1018                 self.Resize(start_x, start_y, start_width, start_height)
       
  1019             return movex, movey
       
  1020         # If it is a move handle, Move this element
       
  1021         elif handle_type == HANDLE_MOVE:
       
  1022             movex = max(-self.BoundingBox.x, movex)
       
  1023             movey = max(-self.BoundingBox.y, movey)
       
  1024             if scaling is not None:
       
  1025                 movex = round_scaling(self.Pos.x + movex, scaling[0]) - self.Pos.x
       
  1026                 movey = round_scaling(self.Pos.y + movey, scaling[1]) - self.Pos.y
       
  1027             if event.ControlDown():
       
  1028                 self.CurrentDrag.x = self.CurrentDrag.x + movex
       
  1029                 self.CurrentDrag.y = self.CurrentDrag.y + movey
       
  1030                 if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
       
  1031                     movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
       
  1032                     movey = self.StartPos.y - self.Pos.y
       
  1033                 else:
       
  1034                     movex = self.StartPos.x - self.Pos.x
       
  1035                     movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y   
       
  1036             self.Move(movex, movey)
       
  1037             return movex, movey
       
  1038         return 0, 0
       
  1039     
       
  1040     def OnToolTipTimer(self, event):
       
  1041         value = self.GetToolTipValue()
       
  1042         if value is not None and self.ToolTipPos is not None:
       
  1043             self.ToolTip = ToolTip(self.Parent, value)
       
  1044             self.ToolTip.MoveToolTip(self.ToolTipPos)
       
  1045             self.ToolTip.Show()
       
  1046         
       
  1047     def GetToolTipValue(self):
       
  1048         return None
       
  1049     
       
  1050     def CreateToolTip(self, pos):
       
  1051         value = self.GetToolTipValue()
       
  1052         if value is not None:
       
  1053             self.ToolTipPos = pos
       
  1054             self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
       
  1055         
       
  1056     def MoveToolTip(self, pos):
       
  1057         if self.ToolTip is not None:
       
  1058             self.ToolTip.MoveToolTip(pos)
       
  1059         elif self.ToolTipPos is not None:
       
  1060             self.ToolTipPos = pos
       
  1061             self.ToolTipTimer.Start(int(TOOLTIP_WAIT_PERIOD * 1000), oneShot=True)
       
  1062     
       
  1063     def ClearToolTip(self):
       
  1064         self.ToolTipTimer.Stop()
       
  1065         self.ToolTipPos = None
       
  1066         if self.ToolTip is not None:
       
  1067             self.ToolTip.Destroy()
       
  1068             self.ToolTip = None
       
  1069     
       
  1070     # Override this method for defining the method to call for adding an highlight to this element
       
  1071     def AddHighlight(self, infos, start, end, highlight_type):
       
  1072         pass
       
  1073     
       
  1074     # Override this method for defining the method to call for removing an highlight from this element
       
  1075     def RemoveHighlight(self, infos, start, end, highlight_type):
       
  1076         pass
       
  1077     
       
  1078     # Override this method for defining the method to call for removing all the highlights of one particular type from this element
       
  1079     def ClearHighlight(self, highlight_type=None):
       
  1080         pass
       
  1081     
       
  1082     # Override this method for defining the method to call for refreshing the model of this element
       
  1083     def RefreshModel(self, move=True):
       
  1084         pass
       
  1085     
       
  1086     # Draws the highlightment of this element if it is highlighted (can be overwritten)
       
  1087     def DrawHighlightment(self, dc):
       
  1088         scalex, scaley = dc.GetUserScale()
       
  1089         dc.SetUserScale(1, 1)
       
  1090         dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
       
  1091         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
       
  1092         dc.SetLogicalFunction(wx.AND)
       
  1093         dc.DrawRectangle(int(round((self.Pos.x - 1) * scalex)) - 2, 
       
  1094                          int(round((self.Pos.y - 1) * scaley)) - 2, 
       
  1095                          int(round((self.Size.width + 3) * scalex)) + 5, 
       
  1096                          int(round((self.Size.height + 3) * scaley)) + 5)
       
  1097         dc.SetLogicalFunction(wx.COPY)
       
  1098         dc.SetUserScale(scalex, scaley)
       
  1099     
       
  1100     # Draws the handles of this element if it is selected
       
  1101     def Draw(self, dc):
       
  1102         if not getattr(dc, "printing", False):
       
  1103             if self.Highlighted:
       
  1104                 self.DrawHighlightment(dc)
       
  1105             if self.Selected:
       
  1106                 scalex, scaley = dc.GetUserScale()
       
  1107                 dc.SetUserScale(1, 1)
       
  1108                 dc.SetPen(MiterPen(wx.BLACK))
       
  1109                 dc.SetBrush(wx.BLACK_BRUSH)
       
  1110                 
       
  1111                 left = (self.BoundingBox.x - 2) * scalex - HANDLE_SIZE
       
  1112                 center = (self.BoundingBox.x + self.BoundingBox.width / 2) * scalex - HANDLE_SIZE / 2
       
  1113                 right = (self.BoundingBox.x + self.BoundingBox.width + 2) * scalex
       
  1114                 
       
  1115                 top = (self.BoundingBox.y - 2) * scaley - HANDLE_SIZE
       
  1116                 middle = (self.BoundingBox.y + self.BoundingBox.height / 2) * scaley - HANDLE_SIZE / 2
       
  1117                 bottom = (self.BoundingBox.y + self.BoundingBox.height + 2) * scaley
       
  1118                 
       
  1119                 for x, y in [(left, top), (center, top), (right, top),
       
  1120                              (left, middle), (right, middle),
       
  1121                              (left, bottom), (center, bottom), (right, bottom)]:
       
  1122                     dc.DrawRectangle(x, y, HANDLE_SIZE, HANDLE_SIZE)
       
  1123                 
       
  1124                 dc.SetUserScale(scalex, scaley)
       
  1125 
       
  1126 
       
  1127 #-------------------------------------------------------------------------------
       
  1128 #                           Group of graphic elements
       
  1129 #-------------------------------------------------------------------------------
       
  1130 
       
  1131 """
       
  1132 Class that implements a group of graphic elements
       
  1133 """
       
  1134 
       
  1135 class Graphic_Group(Graphic_Element):
       
  1136     
       
  1137     # Create a new group of graphic elements
       
  1138     def __init__(self, parent):
       
  1139         Graphic_Element.__init__(self, parent)
       
  1140         self.Elements = []
       
  1141         self.RefreshWireExclusion()
       
  1142         self.RefreshBoundingBox()
       
  1143     
       
  1144     # Destructor
       
  1145     def __del__(self):
       
  1146         self.Elements = []
       
  1147     
       
  1148     def GetDefinition(self):
       
  1149         blocks = [] 
       
  1150         wires = []
       
  1151         for element in self.Elements:
       
  1152             block, wire = element.GetDefinition()
       
  1153             blocks.extend(block)
       
  1154             wires.extend(wire)
       
  1155         return blocks, wires
       
  1156     
       
  1157     # Make a clone of this element
       
  1158     def Clone(self, parent, pos = None):
       
  1159         group = Graphic_Group(parent)
       
  1160         connectors = {}
       
  1161         exclude_names = {}
       
  1162         wires = []
       
  1163         if pos is not None:
       
  1164             dx, dy = pos.x - self.BoundingBox.x, pos.y - self.BoundingBox.y
       
  1165         for element in self.Elements:
       
  1166             if isinstance(element, Wire):
       
  1167                 wires.append(element)
       
  1168             else:
       
  1169                 if pos is not None:
       
  1170                     x, y = element.GetPosition()
       
  1171                     new_pos = wx.Point(x + dx, y + dy)
       
  1172                     newid = parent.GetNewId()
       
  1173                     if parent.IsNamedElement(element):
       
  1174                         name = parent.GenerateNewName(element, exclude_names)
       
  1175                         exclude_names[name.upper()] = True
       
  1176                         new_element = element.Clone(parent, newid, name, pos = new_pos)
       
  1177                     else:
       
  1178                         new_element = element.Clone(parent, newid, pos = new_pos)
       
  1179                     new_element.AdjustToScaling(parent.Scaling)
       
  1180                 else:
       
  1181                     new_element = element.Clone(parent)
       
  1182                 connectors.update(element.GetConnectorTranslation(new_element))
       
  1183                 group.SelectElement(new_element)
       
  1184         for element in wires:
       
  1185             if pos is not None:
       
  1186                 new_wire = element.Clone(parent, connectors, dx, dy)
       
  1187             else:
       
  1188                 new_wire = element.Clone(parent, connectors)
       
  1189             if new_wire is not None:
       
  1190                 if pos is not None:
       
  1191                     parent.AddWire(new_wire)
       
  1192                 group.SelectElement(new_wire)
       
  1193         if pos is not None:
       
  1194             for element in group.Elements:
       
  1195                 if not isinstance(element, Wire):
       
  1196                     parent.AddBlockInModel(element)
       
  1197         return group
       
  1198     
       
  1199     def CanAddBlocks(self, parent):
       
  1200         valid = True
       
  1201         for element in self.Elements:
       
  1202             if not isinstance(element, Wire):
       
  1203                 valid &= parent.CanAddElement(element)
       
  1204         return valid
       
  1205     
       
  1206     def IsVisible(self):
       
  1207         for element in self.Elements:
       
  1208             if element.IsVisible():
       
  1209                 return True
       
  1210         return False
       
  1211     
       
  1212     # Refresh the list of wire excluded
       
  1213     def RefreshWireExclusion(self):
       
  1214         self.WireExcluded = []
       
  1215         for element in self.Elements:
       
  1216             if isinstance(element, Wire):
       
  1217                 startblock = element.StartConnected.GetParentBlock()
       
  1218                 endblock = element.EndConnected.GetParentBlock()
       
  1219                 if startblock in self.Elements and endblock in self.Elements:
       
  1220                     self.WireExcluded.append(element)
       
  1221     
       
  1222     # Returns the RedrawRect
       
  1223     def GetRedrawRect(self, movex = 0, movey = 0):
       
  1224         rect = None
       
  1225         for element in self.Elements:
       
  1226             if rect is None:
       
  1227                 rect = element.GetRedrawRect(movex, movey)
       
  1228             else:
       
  1229                 rect = rect.Union(element.GetRedrawRect(movex, movey))
       
  1230         return rect
       
  1231     
       
  1232     # Clean this group of elements
       
  1233     def Clean(self):
       
  1234         # Clean all the elements of the group
       
  1235         for element in self.Elements:
       
  1236             element.Clean()
       
  1237     
       
  1238     # Delete this group of elements
       
  1239     def Delete(self):
       
  1240         # Delete all the elements of the group
       
  1241         for element in self.Elements:
       
  1242             element.Delete()
       
  1243         self.WireExcluded = []
       
  1244     
       
  1245     # Returns if the point given is in the bounding box of one of the elements of this group
       
  1246     def HitTest(self, pt, connectors=True):
       
  1247         result = False
       
  1248         for element in self.Elements:
       
  1249             result |= element.HitTest(pt, connectors)
       
  1250         return result
       
  1251     
       
  1252     # Returns if the element given is in this group
       
  1253     def IsElementIn(self, element):
       
  1254         return element in self.Elements
       
  1255     
       
  1256     # Change the elements of the group
       
  1257     def SetElements(self, elements):
       
  1258         self.Elements = elements
       
  1259         self.RefreshWireExclusion()
       
  1260         self.RefreshBoundingBox()
       
  1261     
       
  1262     # Returns the elements of the group
       
  1263     def GetElements(self):
       
  1264         return self.Elements
       
  1265     
       
  1266     # Align the group elements
       
  1267     def AlignElements(self, horizontally, vertically):
       
  1268         minx = self.BoundingBox.x + self.BoundingBox.width
       
  1269         miny = self.BoundingBox.y + self.BoundingBox.height
       
  1270         maxx = self.BoundingBox.x
       
  1271         maxy = self.BoundingBox.y
       
  1272         for element in self.Elements:
       
  1273             if not isinstance(element, Wire):
       
  1274                 posx, posy = element.GetPosition()
       
  1275                 width, height = element.GetSize()
       
  1276                 minx = min(minx, posx)
       
  1277                 miny = min(miny, posy)
       
  1278                 maxx = max(maxx, posx + width)
       
  1279                 maxy = max(maxy, posy + height)
       
  1280         for element in self.Elements:
       
  1281             if not isinstance(element, Wire):
       
  1282                 posx, posy = element.GetPosition()
       
  1283                 width, height = element.GetSize()
       
  1284                 movex = movey = 0
       
  1285                 if horizontally == ALIGN_LEFT:
       
  1286                     movex = minx - posx
       
  1287                 elif horizontally == ALIGN_CENTER:
       
  1288                     movex = (maxx + minx - width) / 2 - posx
       
  1289                 elif horizontally == ALIGN_RIGHT:
       
  1290                     movex = maxx - width - posx
       
  1291                 if vertically == ALIGN_TOP:
       
  1292                     movey = miny - posy
       
  1293                 elif vertically == ALIGN_MIDDLE:
       
  1294                     movey = (maxy + miny - height) / 2 - posy
       
  1295                 elif vertically == ALIGN_BOTTOM:
       
  1296                     movey = maxy - height - posy
       
  1297                 if movex != 0 or movey != 0:
       
  1298                     element.Move(movex, movey)
       
  1299                     element.RefreshModel()
       
  1300         self.RefreshWireExclusion()
       
  1301         self.RefreshBoundingBox()
       
  1302     
       
  1303     # Remove or select the given element if it is or not in the group
       
  1304     def SelectElement(self, element):
       
  1305         if element in self.Elements:
       
  1306             self.Elements.remove(element)
       
  1307         else:
       
  1308             self.Elements.append(element)
       
  1309         self.RefreshWireExclusion()
       
  1310         self.RefreshBoundingBox()
       
  1311     
       
  1312     # Move this group of elements
       
  1313     def Move(self, movex, movey):
       
  1314         movex = max(-self.BoundingBox.x, movex)
       
  1315         movey = max(-self.BoundingBox.y, movey)
       
  1316         # Move all the elements of the group
       
  1317         for element in self.Elements:
       
  1318             if not isinstance(element, Wire):
       
  1319                 element.Move(movex, movey, self.WireExcluded)
       
  1320             elif element in self.WireExcluded:
       
  1321                 element.Move(movex, movey, True)
       
  1322         self.RefreshBoundingBox()
       
  1323     
       
  1324     # Refreshes the bounding box of this group of elements
       
  1325     def RefreshBoundingBox(self):
       
  1326         if len(self.Elements) > 0:
       
  1327             bbox = self.Elements[0].GetBoundingBox()
       
  1328             minx, miny = bbox.x, bbox.y
       
  1329             maxx = bbox.x + bbox.width
       
  1330             maxy = bbox.y + bbox.height
       
  1331             for element in self.Elements[1:]:
       
  1332                 bbox = element.GetBoundingBox()
       
  1333                 minx = min(minx, bbox.x)
       
  1334                 miny = min(miny, bbox.y)
       
  1335                 maxx = max(maxx, bbox.x + bbox.width)
       
  1336                 maxy = max(maxy, bbox.y + bbox.height)
       
  1337             self.BoundingBox = wx.Rect(minx, miny, maxx - minx, maxy - miny)
       
  1338         else:
       
  1339             self.BoundingBox = wx.Rect(0, 0, 0, 0)
       
  1340         self.Pos = wx.Point(self.BoundingBox.x, self.BoundingBox.y)
       
  1341         self.Size = wx.Size(self.BoundingBox.width, self.BoundingBox.height)
       
  1342 
       
  1343     # Forbids to change the group position
       
  1344     def SetPosition(x, y):
       
  1345         pass
       
  1346     
       
  1347     # Returns the position of this group
       
  1348     def GetPosition(self):
       
  1349         return self.BoundingBox.x, self.BoundingBox.y
       
  1350     
       
  1351     # Forbids to change the group size
       
  1352     def SetSize(width, height):
       
  1353         pass
       
  1354     
       
  1355     # Returns the size of this group
       
  1356     def GetSize(self):
       
  1357         return self.BoundingBox.width, self.BoundingBox.height
       
  1358 
       
  1359     # Moves and Resizes the group elements for fitting scaling
       
  1360     def AdjustToScaling(self, scaling):
       
  1361         movex_max = movey_max = 0
       
  1362         for element in self.Elements:
       
  1363             movex, movey = element.AdjustToScaling(scaling)
       
  1364             movex_max = max(movex_max, abs(movex))
       
  1365             movey_max = max(movey_max, abs(movey))
       
  1366         return movex_max, movey_max
       
  1367     
       
  1368     # Refreshes the group elements to move defined and handle selected
       
  1369     def ProcessDragging(self, movex, movey, event, scaling):
       
  1370         handle_type, handle = self.Handle
       
  1371         # If it is a move handle, Move this group elements
       
  1372         if handle_type == HANDLE_MOVE:
       
  1373             movex = max(-self.BoundingBox.x, movex)
       
  1374             movey = max(-self.BoundingBox.y, movey)
       
  1375             if scaling is not None:
       
  1376                 movex = round_scaling(movex, scaling[0])
       
  1377                 movey = round_scaling(movey, scaling[1])
       
  1378             if event.ControlDown():
       
  1379                 self.CurrentDrag.x = self.CurrentDrag.x + movex
       
  1380                 self.CurrentDrag.y = self.CurrentDrag.y + movey
       
  1381                 if abs(self.CurrentDrag.x) > abs(self.CurrentDrag.y):
       
  1382                     movex = self.StartPos.x + self.CurrentDrag.x - self.Pos.x
       
  1383                     movey = self.StartPos.y - self.Pos.y
       
  1384                 else:
       
  1385                     movex = self.StartPos.x - self.Pos.x
       
  1386                     movey = self.StartPos.y + self.CurrentDrag.y - self.Pos.y
       
  1387             self.Move(movex, movey)
       
  1388             return movex, movey
       
  1389         return 0, 0
       
  1390     
       
  1391     # Change the variable that indicates if this element is highlighted
       
  1392     def SetHighlighted(self, highlighted):
       
  1393         for element in self.Elements:
       
  1394             element.SetHighlighted(highlighted)
       
  1395     
       
  1396     def HighlightPoint(self, pos):
       
  1397         for element in self.Elements:
       
  1398             if isinstance(element, Wire):
       
  1399                 element.HighlightPoint(pos)
       
  1400     
       
  1401     # Method called when a LeftDown event have been generated
       
  1402     def OnLeftDown(self, event, dc, scaling):
       
  1403         Graphic_Element.OnLeftDown(self, event, dc, scaling)
       
  1404         for element in self.Elements:
       
  1405             element.Handle = self.Handle
       
  1406 
       
  1407     # Change the variable that indicates if the elemente is selected
       
  1408     def SetSelected(self, selected):
       
  1409         for element in self.Elements:
       
  1410             element.SetSelected(selected)
       
  1411 
       
  1412     # Method called when a RightUp event has been generated
       
  1413     def OnRightUp(self, event, dc, scaling):
       
  1414         # Popup the menu with special items for a group
       
  1415         self.Parent.PopupGroupMenu()
       
  1416 
       
  1417     # Refreshes the model of all the elements of this group
       
  1418     def RefreshModel(self):
       
  1419         for element in self.Elements:
       
  1420             element.RefreshModel()
       
  1421 
       
  1422 
       
  1423 #-------------------------------------------------------------------------------
       
  1424 #                         Connector for all types of blocks
       
  1425 #-------------------------------------------------------------------------------
       
  1426 
       
  1427 """
       
  1428 Class that implements a connector for any type of block
       
  1429 """
       
  1430 
       
  1431 class Connector:
       
  1432     
       
  1433     # Create a new connector
       
  1434     def __init__(self, parent, name, type, position, direction, negated = False, edge = "none", onlyone = False):
       
  1435         self.ParentBlock = parent
       
  1436         self.Name = name
       
  1437         self.Type = type
       
  1438         self.Pos = position
       
  1439         self.Direction = direction
       
  1440         self.Wires = []
       
  1441         if self.ParentBlock.IsOfType("BOOL", type):
       
  1442             self.Negated = negated
       
  1443             self.Edge = edge
       
  1444         else:
       
  1445             self.Negated = False
       
  1446             self.Edge = "none"
       
  1447         self.OneConnected = onlyone
       
  1448         self.Valid = True
       
  1449         self.Value = None
       
  1450         self.Forced = False
       
  1451         self.Selected = False
       
  1452         self.Highlights = []
       
  1453         self.RefreshNameSize()
       
  1454     
       
  1455     def Flush(self):
       
  1456         self.ParentBlock = None
       
  1457         for wire, handle in self.Wires:
       
  1458             wire.Flush()
       
  1459         self.Wires = []
       
  1460     
       
  1461     # Returns the RedrawRect
       
  1462     def GetRedrawRect(self, movex = 0, movey = 0):
       
  1463         parent_pos = self.ParentBlock.GetPosition()
       
  1464         x = min(parent_pos[0] + self.Pos.x, parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE)
       
  1465         y = min(parent_pos[1] + self.Pos.y, parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE)
       
  1466         if self.Direction[0] == 0:
       
  1467             width = 5
       
  1468         else:
       
  1469             width = CONNECTOR_SIZE
       
  1470         if self.Direction[1] == 0:
       
  1471             height = 5
       
  1472         else:
       
  1473             height = CONNECTOR_SIZE
       
  1474         return wx.Rect(x - abs(movex), y - abs(movey), width + 2 * abs(movex), height + 2 * abs(movey))
       
  1475     
       
  1476     # Change the connector selection
       
  1477     def SetSelected(self, selected):
       
  1478         self.Selected = selected
       
  1479     
       
  1480     # Make a clone of the connector
       
  1481     def Clone(self, parent = None):
       
  1482         if parent is None:
       
  1483             parent = self.ParentBlock
       
  1484         return Connector(parent, self.Name, self.Type, wx.Point(self.Pos[0], self.Pos[1]),
       
  1485                 self.Direction, self.Negated)
       
  1486     
       
  1487     # Returns the connector parent block
       
  1488     def GetParentBlock(self):
       
  1489         return self.ParentBlock
       
  1490     
       
  1491     # Returns the connector type
       
  1492     def GetType(self, raw = False):
       
  1493         if self.ParentBlock.IsEndType(self.Type) or raw:
       
  1494             return self.Type
       
  1495         elif (self.Negated or self.Edge != "none") and self.ParentBlock.IsOfType("BOOL", self.Type):
       
  1496             return "BOOL"
       
  1497         else:
       
  1498             return self.ParentBlock.GetConnectionResultType(self, self.Type)
       
  1499     
       
  1500     # Returns the connector type
       
  1501     def GetConnectedType(self):
       
  1502         if self.ParentBlock.IsEndType(self.Type):
       
  1503             return self.Type
       
  1504         elif len(self.Wires) == 1:
       
  1505             return self.Wires[0][0].GetOtherConnectedType(self.Wires[0][1])
       
  1506         return self.Type
       
  1507     
       
  1508     # Returns the connector type
       
  1509     def GetConnectedRedrawRect(self, movex, movey):
       
  1510         rect = None
       
  1511         for wire, handle in self.Wires:
       
  1512             if rect is None:
       
  1513                 rect = wire.GetRedrawRect()
       
  1514             else:
       
  1515                 rect = rect.Union(wire.GetRedrawRect())
       
  1516         return rect
       
  1517     
       
  1518     # Returns if connector type is compatible with type given
       
  1519     def IsCompatible(self, type):
       
  1520         reference = self.GetType()
       
  1521         return self.ParentBlock.IsOfType(type, reference) or self.ParentBlock.IsOfType(reference, type)
       
  1522     
       
  1523     # Changes the connector name
       
  1524     def SetType(self, type):
       
  1525         self.Type = type
       
  1526         for wire, handle in self.Wires:
       
  1527             wire.SetValid(wire.IsConnectedCompatible())
       
  1528     
       
  1529     # Returns the connector name
       
  1530     def GetName(self):
       
  1531         return self.Name
       
  1532     
       
  1533     # Changes the connector name
       
  1534     def SetName(self, name):
       
  1535         self.Name = name
       
  1536         self.RefreshNameSize()
       
  1537 
       
  1538     def RefreshForced(self):
       
  1539         self.Forced = False
       
  1540         for wire, handle in self.Wires:
       
  1541             self.Forced |= wire.IsForced()
       
  1542 
       
  1543     def RefreshValue(self):
       
  1544         self.Value = self.ReceivingCurrent()
       
  1545     
       
  1546     def RefreshValid(self):
       
  1547         self.Valid = True
       
  1548         for wire, handle in self.Wires:
       
  1549             self.Valid &= wire.GetValid()
       
  1550     
       
  1551     def ReceivingCurrent(self):
       
  1552         current = False
       
  1553         for wire, handle in self.Wires:
       
  1554             value = wire.GetValue()
       
  1555             if current != "undefined" and isinstance(value, BooleanType):
       
  1556                 current |= wire.GetValue()
       
  1557             elif value == "undefined":
       
  1558                 current = "undefined"
       
  1559         return current
       
  1560     
       
  1561     def SpreadCurrent(self, spreading):
       
  1562         for wire, handle in self.Wires:
       
  1563             wire.SetValue(spreading)
       
  1564     
       
  1565     # Changes the connector name size
       
  1566     def RefreshNameSize(self):
       
  1567         if self.Name != "":
       
  1568             self.NameSize = self.ParentBlock.Parent.GetTextExtent(self.Name)
       
  1569         else:
       
  1570             self.NameSize = 0, 0
       
  1571     
       
  1572     # Returns the connector name size
       
  1573     def GetNameSize(self):
       
  1574         return self.NameSize
       
  1575     
       
  1576     # Returns the wires connected to the connector
       
  1577     def GetWires(self):
       
  1578         return self.Wires
       
  1579     
       
  1580     # Returns the parent block Id
       
  1581     def GetBlockId(self):
       
  1582         return self.ParentBlock.GetId()
       
  1583     
       
  1584     # Returns the connector relative position
       
  1585     def GetRelPosition(self):
       
  1586         return self.Pos
       
  1587     
       
  1588     # Returns the connector absolute position
       
  1589     def GetPosition(self, size = True):
       
  1590         parent_pos = self.ParentBlock.GetPosition()
       
  1591         # If the position of the end of the connector is asked
       
  1592         if size:
       
  1593             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
       
  1594             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
       
  1595         else:
       
  1596             x = parent_pos[0] + self.Pos.x
       
  1597             y = parent_pos[1] + self.Pos.y
       
  1598         return wx.Point(x, y)
       
  1599     
       
  1600     # Change the connector relative position
       
  1601     def SetPosition(self, pos):
       
  1602         self.Pos = pos
       
  1603     
       
  1604     # Returns the connector direction
       
  1605     def GetDirection(self):
       
  1606         return self.Direction
       
  1607     
       
  1608     # Change the connector direction
       
  1609     def SetDirection(self, direction):
       
  1610         self.Direction = direction
       
  1611     
       
  1612     # Connect a wire to this connector at the last place
       
  1613     def Connect(self, wire, refresh = True):
       
  1614         self.InsertConnect(len(self.Wires), wire, refresh)
       
  1615     
       
  1616     # Connect a wire to this connector at the place given
       
  1617     def InsertConnect(self, idx, wire, refresh = True):
       
  1618         if wire not in self.Wires:
       
  1619             self.Wires.insert(idx, wire)
       
  1620             if refresh:
       
  1621                 self.ParentBlock.RefreshModel(False)
       
  1622     
       
  1623     # Returns the index of the wire given in the list of connected
       
  1624     def GetWireIndex(self, wire):
       
  1625         for i, (tmp_wire, handle) in enumerate(self.Wires):
       
  1626             if tmp_wire == wire:
       
  1627                 return i
       
  1628         return None
       
  1629     
       
  1630     # Unconnect a wire or all wires connected to the connector
       
  1631     def UnConnect(self, wire = None, unconnect = True, delete = False):
       
  1632         i = 0
       
  1633         found = False
       
  1634         while i < len(self.Wires) and not found:
       
  1635             if not wire or self.Wires[i][0] == wire:
       
  1636                 # If Unconnect haven't been called from a wire, disconnect the connector in the wire
       
  1637                 if unconnect:
       
  1638                     if self.Wires[i][1] == 0:
       
  1639                         self.Wires[i][0].UnConnectStartPoint(delete)
       
  1640                     else:
       
  1641                         self.Wires[i][0].UnConnectEndPoint(delete)
       
  1642                 # Remove wire from connected
       
  1643                 if wire:
       
  1644                     self.Wires.pop(i)
       
  1645                     found = True
       
  1646             i += 1
       
  1647         # If no wire defined, unconnect all wires
       
  1648         if not wire:
       
  1649             self.Wires = []
       
  1650         self.RefreshValid()
       
  1651         self.ParentBlock.RefreshModel(False)
       
  1652     
       
  1653     # Returns if connector has one or more wire connected
       
  1654     def IsConnected(self):
       
  1655         return len(self.Wires) > 0
       
  1656     
       
  1657     # Move the wires connected
       
  1658     def MoveConnected(self, exclude = []):
       
  1659         if len(self.Wires) > 0:
       
  1660             # Calculate the new position of the end point
       
  1661             parent_pos = self.ParentBlock.GetPosition()
       
  1662             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE
       
  1663             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE
       
  1664             # Move the corresponding point on all the wires connected
       
  1665             for wire, index in self.Wires:
       
  1666                 if wire not in exclude:
       
  1667                     if index == 0:
       
  1668                         wire.MoveStartPoint(wx.Point(x, y))
       
  1669                     else:
       
  1670                         wire.MoveEndPoint(wx.Point(x, y))
       
  1671     
       
  1672     # Refreshes the model of all the wires connected
       
  1673     def RefreshWires(self):
       
  1674         for wire in self.Wires:
       
  1675             wire[0].RefreshModel()
       
  1676     
       
  1677     # Refreshes the parent block model
       
  1678     def RefreshParentBlock(self):
       
  1679         self.ParentBlock.RefreshModel(False)
       
  1680     
       
  1681     # Highlight the parent block
       
  1682     def HighlightParentBlock(self, highlight):
       
  1683         self.ParentBlock.SetHighlighted(highlight)
       
  1684         self.ParentBlock.Refresh()
       
  1685     
       
  1686     # Returns all the blocks connected to this connector
       
  1687     def GetConnectedBlocks(self):
       
  1688         blocks = []
       
  1689         for wire, handle in self.Wires:
       
  1690             # Get other connector connected to each wire
       
  1691             if handle == 0:
       
  1692                 connector = wire.GetEndConnected()
       
  1693             else:
       
  1694                 connector = wire.GetStartConnected()
       
  1695             # Get parent block for this connector
       
  1696             if connector:
       
  1697                 block = connector.GetParentBlock()
       
  1698                 if block not in blocks:
       
  1699                     blocks.append(block)
       
  1700         return blocks
       
  1701     
       
  1702     # Returns the connector negated property
       
  1703     def IsNegated(self):
       
  1704         return self.Negated
       
  1705     
       
  1706     # Changes the connector negated property
       
  1707     def SetNegated(self, negated):
       
  1708         if self.ParentBlock.IsOfType("BOOL", self.Type):
       
  1709             self.Negated = negated
       
  1710             self.Edge = "none"
       
  1711     
       
  1712     # Returns the connector edge property
       
  1713     def GetEdge(self):
       
  1714         return self.Edge
       
  1715     
       
  1716     # Changes the connector edge property
       
  1717     def SetEdge(self, edge):
       
  1718         if self.ParentBlock.IsOfType("BOOL", self.Type):
       
  1719             self.Edge = edge    
       
  1720             self.Negated = False
       
  1721     
       
  1722     # Tests if the point given is near from the end point of this connector
       
  1723     def TestPoint(self, pt, direction = None, exclude = True):
       
  1724         parent_pos = self.ParentBlock.GetPosition()
       
  1725         if (not (len(self.Wires) > 0 and self.OneConnected and exclude) or self.Type == "BOOL")\
       
  1726             and direction is None or self.Direction == direction:
       
  1727             # Calculate a square around the end point of this connector
       
  1728             x = parent_pos[0] + self.Pos.x + self.Direction[0] * CONNECTOR_SIZE - ANCHOR_DISTANCE
       
  1729             y = parent_pos[1] + self.Pos.y + self.Direction[1] * CONNECTOR_SIZE - ANCHOR_DISTANCE
       
  1730             width = ANCHOR_DISTANCE * 2 + abs(self.Direction[0]) * CONNECTOR_SIZE
       
  1731             height = ANCHOR_DISTANCE * 2 + abs(self.Direction[1]) * CONNECTOR_SIZE
       
  1732             rect = wx.Rect(x, y, width, height)
       
  1733             return rect.InsideXY(pt.x, pt.y)
       
  1734         return False
       
  1735     
       
  1736     # Draws the highlightment of this element if it is highlighted
       
  1737     def DrawHighlightment(self, dc):
       
  1738         scalex, scaley = dc.GetUserScale()
       
  1739         dc.SetUserScale(1, 1)
       
  1740         pen = MiterPen(HIGHLIGHTCOLOR, 2 * scalex + 5)
       
  1741         pen.SetCap(wx.CAP_BUTT)
       
  1742         dc.SetPen(pen)
       
  1743         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
       
  1744         dc.SetLogicalFunction(wx.AND)
       
  1745         parent_pos = self.ParentBlock.GetPosition()
       
  1746         posx = parent_pos[0] + self.Pos.x
       
  1747         posy = parent_pos[1] + self.Pos.y
       
  1748         xstart = parent_pos[0] + self.Pos.x 
       
  1749         ystart = parent_pos[1] + self.Pos.y
       
  1750         if self.Direction[0] < 0:
       
  1751             xstart += 1
       
  1752         if self.Direction[1] < 0:
       
  1753             ystart += 1
       
  1754         xend = xstart + CONNECTOR_SIZE * self.Direction[0]
       
  1755         yend = ystart + CONNECTOR_SIZE * self.Direction[1]
       
  1756         dc.DrawLine(round((xstart + self.Direction[0]) * scalex), round((ystart + self.Direction[1]) * scaley), 
       
  1757                     round(xend * scalex), round(yend * scaley))
       
  1758         dc.SetLogicalFunction(wx.COPY)
       
  1759         dc.SetUserScale(scalex, scaley)
       
  1760     
       
  1761     # Adds an highlight to the connector
       
  1762     def AddHighlight(self, infos, start, end, highlight_type):
       
  1763         if highlight_type == ERROR_HIGHLIGHT:
       
  1764             for wire, handle in self.Wires:
       
  1765                 wire.SetValid(False)
       
  1766         AddHighlight(self.Highlights, (start, end, highlight_type))
       
  1767     
       
  1768     # Removes an highlight from the connector
       
  1769     def RemoveHighlight(self, infos, start, end, highlight_type):
       
  1770         error = False
       
  1771         highlights = []
       
  1772         for highlight in self.Highlights:
       
  1773             if highlight != (start, end, highlight_type):
       
  1774                 highlights.append(highlight)
       
  1775                 error |= highlight == ERROR_HIGHLIGHT
       
  1776         self.Highlights = highlights
       
  1777         if not error:
       
  1778             for wire, handle in self.Wires:
       
  1779                 wire.SetValid(wire.IsConnectedCompatible())
       
  1780     
       
  1781     # Removes all the highlights of one particular type from the connector
       
  1782     def ClearHighlight(self, highlight_type=None):
       
  1783         error = False
       
  1784         if highlight_type is None:
       
  1785             self.Highlights = []
       
  1786         else:
       
  1787             highlights = []
       
  1788             for highlight in self.Highlights:
       
  1789                 if highlight[2] != highlight_type:
       
  1790                     highlights.append(highlight)
       
  1791                     error |= highlight == ERROR_HIGHLIGHT
       
  1792             self.Highlights = highlights
       
  1793         if not error:
       
  1794             for wire, handle in self.Wires:
       
  1795                 wire.SetValid(wire.IsConnectedCompatible())
       
  1796     
       
  1797     # Draws the connector
       
  1798     def Draw(self, dc):
       
  1799         if self.Selected:
       
  1800             dc.SetPen(MiterPen(wx.BLUE, 3))
       
  1801             dc.SetBrush(wx.WHITE_BRUSH)
       
  1802         #elif len(self.Highlights) > 0:
       
  1803         #    dc.SetPen(MiterPen(self.Highlights[-1][1]))
       
  1804         #    dc.SetBrush(wx.Brush(self.Highlights[-1][0]))
       
  1805         else:
       
  1806             if not self.Valid:
       
  1807                 dc.SetPen(MiterPen(wx.RED))
       
  1808             elif isinstance(self.Value, BooleanType) and self.Value:
       
  1809                 if self.Forced:
       
  1810                     dc.SetPen(MiterPen(wx.CYAN))
       
  1811                 else:
       
  1812                     dc.SetPen(MiterPen(wx.GREEN))
       
  1813             elif self.Value == "undefined":
       
  1814                 dc.SetPen(MiterPen(wx.NamedColour("orange")))
       
  1815             elif self.Forced:
       
  1816                 dc.SetPen(MiterPen(wx.BLUE))
       
  1817             else:
       
  1818                 dc.SetPen(MiterPen(wx.BLACK))
       
  1819             dc.SetBrush(wx.WHITE_BRUSH)
       
  1820         parent_pos = self.ParentBlock.GetPosition()
       
  1821         
       
  1822         if getattr(dc, "printing", False):
       
  1823             name_size = dc.GetTextExtent(self.Name)
       
  1824         else:
       
  1825             name_size = self.NameSize
       
  1826         
       
  1827         if self.Negated:
       
  1828             # If connector is negated, draw a circle
       
  1829             xcenter = parent_pos[0] + self.Pos.x + (CONNECTOR_SIZE * self.Direction[0]) / 2
       
  1830             ycenter = parent_pos[1] + self.Pos.y + (CONNECTOR_SIZE * self.Direction[1]) / 2
       
  1831             dc.DrawCircle(xcenter, ycenter, CONNECTOR_SIZE / 2)
       
  1832         else:
       
  1833             xstart = parent_pos[0] + self.Pos.x 
       
  1834             ystart = parent_pos[1] + self.Pos.y
       
  1835             if self.Edge == "rising":
       
  1836                 # If connector has a rising edge, draw a right arrow
       
  1837                 dc.DrawLine(xstart, ystart, xstart - 4, ystart - 4)
       
  1838                 dc.DrawLine(xstart, ystart, xstart - 4, ystart + 4)
       
  1839             elif self.Edge == "falling":
       
  1840                 # If connector has a falling edge, draw a left arrow
       
  1841                 dc.DrawLine(xstart, ystart, xstart + 4, ystart - 4)
       
  1842                 dc.DrawLine(xstart, ystart, xstart + 4, ystart + 4)
       
  1843             if self.Direction[0] < 0:
       
  1844                 xstart += 1
       
  1845             if self.Direction[1] < 0:
       
  1846                 ystart += 1
       
  1847             if self.Selected:
       
  1848                 xend = xstart + (CONNECTOR_SIZE - 2) * self.Direction[0]
       
  1849                 yend = ystart + (CONNECTOR_SIZE - 2) * self.Direction[1]
       
  1850                 dc.DrawLine(xstart + 2 * self.Direction[0], ystart + 2 * self.Direction[1], xend, yend)
       
  1851             else:
       
  1852                 xend = xstart + CONNECTOR_SIZE * self.Direction[0]
       
  1853                 yend = ystart + CONNECTOR_SIZE * self.Direction[1]
       
  1854                 dc.DrawLine(xstart + self.Direction[0], ystart + self.Direction[1], xend, yend)
       
  1855         if self.Direction[0] != 0:
       
  1856             ytext = parent_pos[1] + self.Pos.y - name_size[1] / 2
       
  1857             if self.Direction[0] < 0:
       
  1858                 xtext = parent_pos[0] + self.Pos.x + 5
       
  1859             else:
       
  1860                 xtext = parent_pos[0] + self.Pos.x - (name_size[0] + 5)
       
  1861         if self.Direction[1] != 0:
       
  1862             xtext = parent_pos[0] + self.Pos.x - name_size[0] / 2
       
  1863             if self.Direction[1] < 0:
       
  1864                 ytext = parent_pos[1] + self.Pos.y + 5
       
  1865             else:
       
  1866                 ytext = parent_pos[1] + self.Pos.y - (name_size[1] + 5)
       
  1867         # Draw the text
       
  1868         dc.DrawText(self.Name, xtext, ytext)
       
  1869         if not getattr(dc, "printing", False):
       
  1870             DrawHighlightedText(dc, self.Name, self.Highlights, xtext, ytext)
       
  1871 
       
  1872 #-------------------------------------------------------------------------------
       
  1873 #                           Common Wire Element
       
  1874 #-------------------------------------------------------------------------------
       
  1875 
       
  1876 """
       
  1877 Class that implements a wire for connecting two blocks
       
  1878 """
       
  1879 
       
  1880 class Wire(Graphic_Element, DebugDataConsumer):
       
  1881     
       
  1882     # Create a new wire
       
  1883     def __init__(self, parent, start = None, end = None):
       
  1884         Graphic_Element.__init__(self, parent)
       
  1885         DebugDataConsumer.__init__(self)
       
  1886         self.StartPoint = start
       
  1887         self.EndPoint = end
       
  1888         self.StartConnected = None
       
  1889         self.EndConnected = None
       
  1890         # If the start and end points are defined, calculate the wire
       
  1891         if start and end:
       
  1892             self.ResetPoints()
       
  1893             self.GeneratePoints()
       
  1894         else:
       
  1895             self.Points = []
       
  1896             self.Segments = []
       
  1897         self.SelectedSegment = None
       
  1898         self.Valid = True
       
  1899         self.ValueSize = None
       
  1900         self.ComputedValue = None
       
  1901         self.OverStart = False
       
  1902         self.OverEnd = False
       
  1903         self.ComputingType = False
       
  1904         self.Font = parent.GetMiniFont()
       
  1905     
       
  1906     def GetDefinition(self):
       
  1907         if self.StartConnected is not None and self.EndConnected is not None:
       
  1908             startblock = self.StartConnected.GetParentBlock()
       
  1909             endblock = self.EndConnected.GetParentBlock()
       
  1910             return [], [(startblock.GetId(), endblock.GetId())]
       
  1911         return [], []
       
  1912     
       
  1913     def Flush(self):
       
  1914         self.StartConnected = None
       
  1915         self.EndConnected = None
       
  1916     
       
  1917     def GetToolTipValue(self):
       
  1918         if self.Value is not None and self.Value != "undefined" and not isinstance(self.Value, BooleanType):
       
  1919             if isinstance(self.Value, StringType) and self.Value.find("#") == -1:
       
  1920                 return "\"%s\""%self.Value
       
  1921             else:
       
  1922                 return str(self.Value)
       
  1923         return None
       
  1924     
       
  1925     # Returns the RedrawRect
       
  1926     def GetRedrawRect(self, movex = 0, movey = 0):
       
  1927         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
       
  1928         if self.StartConnected:
       
  1929             rect = rect.Union(self.StartConnected.GetRedrawRect(movex, movey))
       
  1930         if self.EndConnected:
       
  1931             rect = rect.Union(self.EndConnected.GetRedrawRect(movex, movey))
       
  1932         if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
       
  1933             self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
       
  1934         if self.ValueSize is not None:
       
  1935             width, height = self.ValueSize
       
  1936             if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
       
  1937                 x = self.Points[0].x + width * self.StartPoint[1][0] / 2
       
  1938                 y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
       
  1939                 rect = rect.Union(wx.Rect(x, y, width, height))
       
  1940                 x = self.Points[-1].x + width * self.EndPoint[1][0] / 2
       
  1941                 y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
       
  1942                 rect = rect.Union(wx.Rect(x, y, width, height))
       
  1943             else:
       
  1944                 middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
       
  1945                 x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
       
  1946                 if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
       
  1947                     y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
       
  1948                 else:
       
  1949                     y = self.Points[middle].y - height
       
  1950                 rect = rect.Union(wx.Rect(x, y, width, height))
       
  1951         return rect
       
  1952     
       
  1953     def Clone(self, parent, connectors = {}, dx = 0, dy = 0):
       
  1954         start_connector = connectors.get(self.StartConnected, None)
       
  1955         end_connector = connectors.get(self.EndConnected, None)
       
  1956         if start_connector is not None and end_connector is not None:
       
  1957             wire = Wire(parent)
       
  1958             wire.SetPoints([(point.x + dx, point.y + dy) for point in self.Points])
       
  1959             start_connector.Connect((wire, 0), False)
       
  1960             end_connector.Connect((wire, -1), False)
       
  1961             wire.ConnectStartPoint(start_connector.GetPosition(), start_connector)
       
  1962             wire.ConnectEndPoint(end_connector.GetPosition(), end_connector)
       
  1963             return wire
       
  1964         return None
       
  1965     
       
  1966     # Forbids to change the wire position
       
  1967     def SetPosition(x, y):
       
  1968         pass
       
  1969     
       
  1970     # Forbids to change the wire size
       
  1971     def SetSize(width, height):
       
  1972         pass
       
  1973     
       
  1974     # Moves and Resizes the element for fitting scaling
       
  1975     def AdjustToScaling(self, scaling):
       
  1976         if scaling is not None:
       
  1977             movex_max = movey_max = 0
       
  1978             for idx, point in enumerate(self.Points):
       
  1979                 if 0 < idx < len(self.Points) - 1:
       
  1980                     movex = round_scaling(point.x, scaling[0]) - point.x
       
  1981                     movey = round_scaling(point.y, scaling[1]) - point.y
       
  1982                     if idx == 1:
       
  1983                         if self.Segments[0][0] == 0:
       
  1984                             movex = 0
       
  1985                         elif (point.x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
       
  1986                             movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
       
  1987                         if self.Segments[0][1] == 0:
       
  1988                             movey = 0
       
  1989                         elif (point.y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
       
  1990                             movey = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - point.y
       
  1991                     elif idx == len(self.Points) - 2:
       
  1992                         if self.Segments[-1][0] == 0:
       
  1993                             movex = 0
       
  1994                         elif (self.Points[-1].x - (point.x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
       
  1995                             movex = round_scaling(self.Points[-1].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - point.x
       
  1996                         if self.Segments[-1][1] == 0:
       
  1997                             movey = 0
       
  1998                         elif (self.Points[-1].y - (point.y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
       
  1999                             movey = round_scaling(self.Points[-1].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - point.y
       
  2000                     movex_max = max(movex_max, movex)
       
  2001                     movey_max = max(movey_max, movey)
       
  2002                     point.x += movex
       
  2003                     point.y += movey
       
  2004             return movex_max, movey_max
       
  2005         return 0, 0
       
  2006     
       
  2007     # Returns connector to which start point is connected
       
  2008     def GetStartConnected(self):
       
  2009         return self.StartConnected
       
  2010     
       
  2011     # Returns connector to which start point is connected
       
  2012     def GetStartConnectedType(self):
       
  2013         if self.StartConnected and not self.ComputingType:
       
  2014             self.ComputingType = True
       
  2015             computed_type = self.StartConnected.GetType()
       
  2016             self.ComputingType = False
       
  2017             return computed_type
       
  2018         return None
       
  2019     
       
  2020     # Returns connector to which end point is connected
       
  2021     def GetEndConnected(self):
       
  2022         return self.EndConnected
       
  2023     
       
  2024     # Returns connector to which end point is connected
       
  2025     def GetEndConnectedType(self):
       
  2026         if self.EndConnected and not self.ComputingType:
       
  2027             self.ComputingType = True
       
  2028             computed_type = self.EndConnected.GetType()
       
  2029             self.ComputingType = False
       
  2030             return computed_type
       
  2031         return None
       
  2032     
       
  2033     def GetConnectionDirection(self):
       
  2034         if self.StartConnected is None and self.EndConnected is None:
       
  2035             return None
       
  2036         elif self.StartConnected is not None and self.EndConnected is None:
       
  2037             return (-self.StartPoint[1][0], -self.StartPoint[1][1])
       
  2038         elif self.StartConnected is None and self.EndConnected is not None:
       
  2039             return self.EndPoint
       
  2040         elif self.Handle is not None:
       
  2041             handle_type, handle = self.Handle
       
  2042             # A point has been handled
       
  2043             if handle_type == HANDLE_POINT:
       
  2044                 if handle == 0:
       
  2045                     return self.EndPoint
       
  2046                 else:
       
  2047                     return (-self.StartPoint[1][0], -self.StartPoint[1][1])
       
  2048         return None
       
  2049     
       
  2050     def GetOtherConnected(self, connector):
       
  2051         if self.StartConnected == connector:
       
  2052             return self.EndConnected
       
  2053         else:
       
  2054             return self.StartConnected
       
  2055     
       
  2056     def GetOtherConnectedType(self, handle):
       
  2057         if handle == 0:
       
  2058             return self.GetEndConnectedType()
       
  2059         else:
       
  2060             return self.GetStartConnectedType()
       
  2061     
       
  2062     def IsConnectedCompatible(self):
       
  2063         if self.StartConnected:
       
  2064             return self.StartConnected.IsCompatible(self.GetEndConnectedType())
       
  2065         elif self.EndConnected:
       
  2066             return True
       
  2067         return False
       
  2068     
       
  2069     def SetForced(self, forced):
       
  2070         if self.Forced != forced:
       
  2071             self.Forced = forced
       
  2072             if self.StartConnected:
       
  2073                 self.StartConnected.RefreshForced()
       
  2074             if self.EndConnected:
       
  2075                 self.EndConnected.RefreshForced()
       
  2076             if self.Visible:
       
  2077                 self.Parent.ElementNeedRefresh(self)
       
  2078 
       
  2079     def SetValue(self, value):
       
  2080         if self.Value != value:
       
  2081             self.Value = value
       
  2082             if value is not None and not isinstance(value, BooleanType):
       
  2083                 if isinstance(value, StringType) and value.find('#') == -1:
       
  2084                     self.ComputedValue = "\"%s\""%value
       
  2085                 else:
       
  2086                     self.ComputedValue = str(value)
       
  2087                 if self.ToolTip is not None:
       
  2088                     self.ToolTip.SetTip(self.ComputedValue)
       
  2089                 if len(self.ComputedValue) > 4:
       
  2090                     self.ComputedValue = self.ComputedValue[:4] + "..."
       
  2091             self.ValueSize = None
       
  2092             if self.StartConnected:
       
  2093                 self.StartConnected.RefreshValue()
       
  2094             if self.EndConnected:
       
  2095                 self.EndConnected.RefreshValue()
       
  2096             if self.Visible:
       
  2097                 self.Parent.ElementNeedRefresh(self)
       
  2098             if isinstance(value, BooleanType) and self.StartConnected is not None:
       
  2099                 block = self.StartConnected.GetParentBlock()
       
  2100                 block.SpreadCurrent()
       
  2101     
       
  2102     # Unconnect the start and end points
       
  2103     def Clean(self):
       
  2104         if self.StartConnected:
       
  2105             self.UnConnectStartPoint()
       
  2106         if self.EndConnected:
       
  2107             self.UnConnectEndPoint()
       
  2108     
       
  2109     # Delete this wire by calling the corresponding method
       
  2110     def Delete(self):
       
  2111         self.Parent.DeleteWire(self)
       
  2112     
       
  2113     # Select a segment and not the whole wire. It's useful for Ladder Diagram
       
  2114     def SetSelectedSegment(self, segment):
       
  2115         # The last segment is indicated
       
  2116         if segment == -1:
       
  2117             segment = len(self.Segments) - 1
       
  2118         # The selected segment is reinitialised
       
  2119         if segment == None:
       
  2120             if self.StartConnected:
       
  2121                 self.StartConnected.SetSelected(False)
       
  2122             if self.EndConnected:
       
  2123                 self.EndConnected.SetSelected(False)
       
  2124         # The segment selected is the first
       
  2125         elif segment == 0:
       
  2126             if self.StartConnected:
       
  2127                 self.StartConnected.SetSelected(True)
       
  2128             if self.EndConnected:
       
  2129                 # There is only one segment
       
  2130                 if len(self.Segments) == 1:
       
  2131                     self.EndConnected.SetSelected(True)
       
  2132                 else:
       
  2133                     self.EndConnected.SetSelected(False)
       
  2134         # The segment selected is the last
       
  2135         elif segment == len(self.Segments) - 1:
       
  2136             if self.StartConnected:
       
  2137                 self.StartConnected.SetSelected(False)
       
  2138             if self.EndConnected:
       
  2139                 self.EndConnected.SetSelected(True)
       
  2140         self.SelectedSegment = segment
       
  2141         self.Refresh()
       
  2142     
       
  2143     def SetValid(self, valid):
       
  2144         self.Valid = valid
       
  2145         if self.StartConnected:
       
  2146             self.StartConnected.RefreshValid()
       
  2147         if self.EndConnected:
       
  2148             self.EndConnected.RefreshValid()
       
  2149     
       
  2150     def GetValid(self):
       
  2151         return self.Valid
       
  2152     
       
  2153     # Reinitialize the wire points
       
  2154     def ResetPoints(self):
       
  2155         if self.StartPoint and self.EndPoint:
       
  2156             self.Points = [self.StartPoint[0], self.EndPoint[0]]
       
  2157             self.Segments = [self.StartPoint[1]]
       
  2158         else:
       
  2159             self.Points = []
       
  2160             self.Segments = []
       
  2161     
       
  2162     # Refresh the wire bounding box
       
  2163     def RefreshBoundingBox(self):
       
  2164         if len(self.Points) > 0:
       
  2165             # If startpoint or endpoint is connected, save the point radius
       
  2166             start_radius = end_radius = 0
       
  2167             if not self.StartConnected:
       
  2168                 start_radius = POINT_RADIUS
       
  2169             if not self.EndConnected:
       
  2170                 end_radius = POINT_RADIUS
       
  2171             # Initialize minimum and maximum from the first point
       
  2172             minx, minbbxx = self.Points[0].x, self.Points[0].x - start_radius
       
  2173             maxx, maxbbxx = self.Points[0].x, self.Points[0].x + start_radius
       
  2174             miny, minbbxy = self.Points[0].y, self.Points[0].y - start_radius
       
  2175             maxy, maxbbxy = self.Points[0].y, self.Points[0].y + start_radius
       
  2176             # Actualize minimum and maximum with the other points
       
  2177             for point in self.Points[1:-1]:
       
  2178                 minx, minbbxx = min(minx, point.x), min(minbbxx, point.x)
       
  2179                 maxx, maxbbxx = max(maxx, point.x), max(maxbbxx, point.x)
       
  2180                 miny, minbbxy = min(miny, point.y), min(minbbxy, point.y)
       
  2181                 maxy, maxbbxy = max(maxy, point.y), max(maxbbxy, point.y)
       
  2182             if len(self.Points) > 1:
       
  2183                 minx, minbbxx = min(minx, self.Points[-1].x), min(minbbxx, self.Points[-1].x - end_radius)
       
  2184                 maxx, maxbbxx = max(maxx, self.Points[-1].x), max(maxbbxx, self.Points[-1].x + end_radius)
       
  2185                 miny, minbbxy = min(miny, self.Points[-1].y), min(minbbxy, self.Points[-1].y - end_radius)
       
  2186                 maxy, maxbbxy = max(maxy, self.Points[-1].y), max(maxbbxy, self.Points[-1].y + end_radius)
       
  2187             self.Pos.x, self.Pos.y = minx, miny
       
  2188             self.Size = wx.Size(maxx - minx, maxy - miny)
       
  2189             self.BoundingBox = wx.Rect(minbbxx, minbbxy, maxbbxx - minbbxx + 1, maxbbxy - minbbxy + 1)
       
  2190     
       
  2191     # Refresh the realpoints that permits to keep the proportionality in wire during resizing
       
  2192     def RefreshRealPoints(self):
       
  2193         if len(self.Points) > 0:
       
  2194             self.RealPoints = []
       
  2195             # Calculate float relative position of each point with the minimum point
       
  2196             for point in self.Points:
       
  2197                 self.RealPoints.append([float(point.x - self.Pos.x), float(point.y - self.Pos.y)])
       
  2198     
       
  2199     # Returns the wire minimum size 
       
  2200     def GetMinSize(self):
       
  2201         width = 1
       
  2202         height = 1
       
  2203         dir_product = product(self.StartPoint[1], self.EndPoint[1])
       
  2204         # The directions are opposed
       
  2205         if dir_product < 0:
       
  2206             if self.StartPoint[0] != 0:
       
  2207                 width = MIN_SEGMENT_SIZE * 2
       
  2208             if self.StartPoint[1] != 0:
       
  2209                 height = MIN_SEGMENT_SIZE * 2
       
  2210         # The directions are the same
       
  2211         elif dir_product > 0:
       
  2212             if self.StartPoint[0] != 0:
       
  2213                 width = MIN_SEGMENT_SIZE
       
  2214             if self.StartPoint[1] != 0:
       
  2215                 height = MIN_SEGMENT_SIZE
       
  2216         # The directions are perpendiculars
       
  2217         else:
       
  2218             width = MIN_SEGMENT_SIZE
       
  2219             height = MIN_SEGMENT_SIZE
       
  2220         return width + 1, height + 1
       
  2221     
       
  2222     # Returns if the point given is on one of the wire segments
       
  2223     def HitTest(self, pt, connectors=True):
       
  2224         test = False
       
  2225         for i in xrange(len(self.Points) - 1):
       
  2226             rect = wx.Rect(0, 0, 0, 0)
       
  2227             if i == 0 and self.StartConnected is not None:
       
  2228                 x1 = self.Points[i].x - self.Segments[0][0] * CONNECTOR_SIZE
       
  2229                 y1 = self.Points[i].y - self.Segments[0][1] * CONNECTOR_SIZE
       
  2230             else:
       
  2231                 x1, y1 = self.Points[i].x, self.Points[i].y    
       
  2232             if i == len(self.Points) - 2 and self.EndConnected is not None:
       
  2233                 x2 = self.Points[i + 1].x + self.Segments[-1][0] * CONNECTOR_SIZE
       
  2234                 y2 = self.Points[i + 1].y + self.Segments[-1][1] * CONNECTOR_SIZE
       
  2235             else:
       
  2236                 x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
       
  2237             # Calculate a rectangle around the segment
       
  2238             rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
       
  2239                 abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
       
  2240             test |= rect.InsideXY(pt.x, pt.y) 
       
  2241         return test
       
  2242     
       
  2243     # Returns the wire start or end point if the point given is on one of them 
       
  2244     def TestPoint(self, pt):
       
  2245         # Test the wire start point
       
  2246         rect = wx.Rect(self.Points[0].x - ANCHOR_DISTANCE, self.Points[0].y - ANCHOR_DISTANCE,
       
  2247             2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
       
  2248         if rect.InsideXY(pt.x, pt.y):
       
  2249             return 0
       
  2250         # Test the wire end point
       
  2251         if len(self.Points) > 1:
       
  2252             rect = wx.Rect(self.Points[-1].x - ANCHOR_DISTANCE, self.Points[-1].y - ANCHOR_DISTANCE,
       
  2253                 2 * ANCHOR_DISTANCE, 2 * ANCHOR_DISTANCE)
       
  2254             if rect.InsideXY(pt.x, pt.y):
       
  2255                 return -1
       
  2256         return None
       
  2257     
       
  2258     # Returns the wire segment if the point given is on it
       
  2259     def TestSegment(self, pt, all=False):
       
  2260         for i in xrange(len(self.Segments)):
       
  2261             # If wire is not in a Ladder Diagram, first and last segments are excluded
       
  2262             if all or 0 < i < len(self.Segments) - 1:
       
  2263                 x1, y1 = self.Points[i].x, self.Points[i].y
       
  2264                 x2, y2 = self.Points[i + 1].x, self.Points[i + 1].y
       
  2265                 # Calculate a rectangle around the segment
       
  2266                 rect = wx.Rect(min(x1, x2) - ANCHOR_DISTANCE, min(y1, y2) - ANCHOR_DISTANCE,
       
  2267                     abs(x1 - x2) + 2 * ANCHOR_DISTANCE, abs(y1 - y2) + 2 * ANCHOR_DISTANCE)
       
  2268                 if rect.InsideXY(pt.x, pt.y):
       
  2269                     return i, self.Segments[i]
       
  2270         return None
       
  2271     
       
  2272     # Define the wire points
       
  2273     def SetPoints(self, points, verify=True):
       
  2274         if len(points) > 1:
       
  2275             self.Points = [wx.Point(x, y) for x, y in points]
       
  2276             # Calculate the start and end directions
       
  2277             self.StartPoint = [None, vector(self.Points[0], self.Points[1])]
       
  2278             self.EndPoint = [None, vector(self.Points[-1], self.Points[-2])]
       
  2279             # Calculate the start and end points
       
  2280             self.StartPoint[0] = wx.Point(self.Points[0].x + CONNECTOR_SIZE * self.StartPoint[1][0], 
       
  2281                 self.Points[0].y + CONNECTOR_SIZE * self.StartPoint[1][1])
       
  2282             self.EndPoint[0] = wx.Point(self.Points[-1].x + CONNECTOR_SIZE * self.EndPoint[1][0], 
       
  2283                 self.Points[-1].y + CONNECTOR_SIZE * self.EndPoint[1][1])
       
  2284             self.Points[0] = self.StartPoint[0]
       
  2285             self.Points[-1] = self.EndPoint[0]
       
  2286             # Calculate the segments directions
       
  2287             self.Segments = []
       
  2288             i = 0
       
  2289             while i < len(self.Points) - 1:
       
  2290                 if verify and 0 < i < len(self.Points) - 2 and \
       
  2291                    self.Points[i] == self.Points[i + 1] and \
       
  2292                    self.Segments[-1] == vector(self.Points[i + 1], self.Points[i + 2]):
       
  2293                     for j in xrange(2):
       
  2294                         self.Points.pop(i)
       
  2295                 else:
       
  2296                     segment = vector(self.Points[i], self.Points[i + 1])
       
  2297                     if is_null_vector(segment) and i > 0:
       
  2298                         segment = (self.Segments[-1][1], self.Segments[-1][0])
       
  2299                     if i < len(self.Points) - 2:
       
  2300                         next = vector(self.Points[i + 1], self.Points[i + 2])
       
  2301                         if next == segment or is_null_vector(add_vectors(segment, next)):
       
  2302                             self.Points.insert(i + 1, wx.Point(self.Points[i + 1].x, self.Points[i + 1].y))
       
  2303                     self.Segments.append(segment)
       
  2304                     i += 1
       
  2305             self.RefreshBoundingBox()
       
  2306             self.RefreshRealPoints()
       
  2307     
       
  2308     # Returns the position of the point indicated
       
  2309     def GetPoint(self, index):
       
  2310         if index < len(self.Points):
       
  2311             return self.Points[index].x, self.Points[index].y
       
  2312         return None
       
  2313     
       
  2314     # Returns a list of the position of all wire points
       
  2315     def GetPoints(self, invert = False):
       
  2316         points = self.VerifyPoints()
       
  2317         points[0] = wx.Point(points[0].x - CONNECTOR_SIZE * self.StartPoint[1][0], 
       
  2318                 points[0].y - CONNECTOR_SIZE * self.StartPoint[1][1])
       
  2319         points[-1] = wx.Point(points[-1].x - CONNECTOR_SIZE * self.EndPoint[1][0], 
       
  2320                 points[-1].y - CONNECTOR_SIZE * self.EndPoint[1][1])
       
  2321         # An inversion of the list is asked
       
  2322         if invert:
       
  2323             points.reverse()
       
  2324         return points
       
  2325     
       
  2326     # Returns the position of the two selected segment points
       
  2327     def GetSelectedSegmentPoints(self):
       
  2328         if self.SelectedSegment != None and len(self.Points) > 1:
       
  2329             return self.Points[self.SelectedSegment:self.SelectedSegment + 2]
       
  2330         return []
       
  2331     
       
  2332     # Returns if the selected segment is the first and/or the last of the wire
       
  2333     def GetSelectedSegmentConnections(self):
       
  2334         if self.SelectedSegment != None and len(self.Points) > 1:
       
  2335             return self.SelectedSegment == 0, self.SelectedSegment == len(self.Segments) - 1
       
  2336         return (True, True)
       
  2337     
       
  2338     # Returns the connectors on which the wire is connected
       
  2339     def GetConnected(self):
       
  2340         connected = []
       
  2341         if self.StartConnected and self.StartPoint[1] == WEST:
       
  2342             connected.append(self.StartConnected)
       
  2343         if self.EndConnected and self.EndPoint[1] == WEST:
       
  2344             connected.append(self.EndConnected)
       
  2345         return connected
       
  2346     
       
  2347     # Returns the id of the block connected to the first or the last wire point
       
  2348     def GetConnectedInfos(self, index):
       
  2349         if index == 0 and self.StartConnected:
       
  2350             return self.StartConnected.GetBlockId(), self.StartConnected.GetName()
       
  2351         elif index == -1 and self.EndConnected:
       
  2352             return self.EndConnected.GetBlockId(), self.EndConnected.GetName()
       
  2353         return None
       
  2354     
       
  2355     # Update the wire points position by keeping at most possible the current positions
       
  2356     def GeneratePoints(self, realpoints = True):
       
  2357         i = 0
       
  2358         # Calculate the start enad end points with the minimum segment size in the right direction
       
  2359         end = wx.Point(self.EndPoint[0].x + self.EndPoint[1][0] * MIN_SEGMENT_SIZE,
       
  2360             self.EndPoint[0].y + self.EndPoint[1][1] * MIN_SEGMENT_SIZE)
       
  2361         start = wx.Point(self.StartPoint[0].x + self.StartPoint[1][0] * MIN_SEGMENT_SIZE, 
       
  2362             self.StartPoint[0].y + self.StartPoint[1][1] * MIN_SEGMENT_SIZE)
       
  2363         # Evaluate the point till it's the last
       
  2364         while i < len(self.Points) - 1:
       
  2365             # The next point is the last
       
  2366             if i + 1 == len(self.Points) - 1:
       
  2367                 # Calculate the direction from current point to end point
       
  2368                 v_end = vector(self.Points[i], end)
       
  2369                 # The current point is the first
       
  2370                 if i == 0:
       
  2371                     # If the end point is not in the start direction, a point is added
       
  2372                     if v_end != self.Segments[0] or v_end == self.EndPoint[1]:
       
  2373                         self.Points.insert(1, wx.Point(start.x, start.y))
       
  2374                         self.Segments.insert(1, DirectionChoice((self.Segments[0][1], 
       
  2375                             self.Segments[0][0]), v_end, self.EndPoint[1]))
       
  2376                 # The current point is the second
       
  2377                 elif i == 1:
       
  2378                     # The previous direction and the target direction are mainly opposed, a point is added
       
  2379                     if product(v_end, self.Segments[0]) < 0:
       
  2380                         self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
       
  2381                         self.Segments.insert(2, DirectionChoice((self.Segments[1][1], 
       
  2382                             self.Segments[1][0]), v_end, self.EndPoint[1]))
       
  2383                     # The previous direction and the end direction are the same or they are
       
  2384                     # perpendiculars and the end direction points towards current segment
       
  2385                     elif product(self.Segments[0], self.EndPoint[1]) >= 0 and product(self.Segments[1], self.EndPoint[1]) <= 0:
       
  2386                         # Current point and end point are aligned
       
  2387                         if self.Segments[0][0] != 0:
       
  2388                             self.Points[1].x = end.x
       
  2389                         if self.Segments[0][1] != 0:
       
  2390                             self.Points[1].y = end.y
       
  2391                         # If the previous direction and the end direction are the same, a point is added
       
  2392                         if product(self.Segments[0], self.EndPoint[1]) > 0:
       
  2393                             self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
       
  2394                             self.Segments.insert(2, DirectionChoice((self.Segments[1][1], 
       
  2395                                 self.Segments[1][0]), v_end, self.EndPoint[1]))
       
  2396                     else:
       
  2397                         # Current point is positioned in the middle of start point
       
  2398                         # and end point on the current direction and a point is added
       
  2399                         if self.Segments[0][0] != 0:
       
  2400                             self.Points[1].x = (end.x + start.x) / 2
       
  2401                         if self.Segments[0][1] != 0:
       
  2402                             self.Points[1].y = (end.y + start.y) / 2
       
  2403                         self.Points.insert(2, wx.Point(self.Points[1].x, self.Points[1].y))
       
  2404                         self.Segments.insert(2, DirectionChoice((self.Segments[1][1], 
       
  2405                             self.Segments[1][0]), v_end, self.EndPoint[1]))
       
  2406                 else:
       
  2407                     # The previous direction and the end direction are perpendiculars
       
  2408                     if product(self.Segments[i - 1], self.EndPoint[1]) == 0:
       
  2409                         # The target direction and the end direction aren't mainly the same
       
  2410                         if product(v_end, self.EndPoint[1]) <= 0:
       
  2411                             # Current point and end point are aligned
       
  2412                             if self.Segments[i - 1][0] != 0:
       
  2413                                 self.Points[i].x = end.x
       
  2414                             if self.Segments[i - 1][1] != 0:
       
  2415                                 self.Points[i].y = end.y
       
  2416                             # Previous direction is updated from the new point
       
  2417                             if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
       
  2418                                 self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
       
  2419                         else:
       
  2420                             test = True
       
  2421                             # If the current point is the third, test if the second 
       
  2422                             # point can be aligned with the end point
       
  2423                             if i == 2:
       
  2424                                 test_point = wx.Point(self.Points[1].x, self.Points[1].y)
       
  2425                                 if self.Segments[1][0] != 0:
       
  2426                                     test_point.y = end.y
       
  2427                                 if self.Segments[1][1] != 0:
       
  2428                                     test_point.x = end.x
       
  2429                                 vector_test = vector(self.Points[0], test_point, False)
       
  2430                                 test = norm(vector_test) > MIN_SEGMENT_SIZE and product(self.Segments[0], vector_test) > 0
       
  2431                             # The previous point can be aligned
       
  2432                             if test:
       
  2433                                 self.Points[i].x, self.Points[i].y = end.x, end.y
       
  2434                                 if self.Segments[i - 1][0] != 0:
       
  2435                                     self.Points[i - 1].y = end.y
       
  2436                                 if self.Segments[i - 1][1] != 0:
       
  2437                                     self.Points[i - 1].x = end.x
       
  2438                                 self.Segments[i] = (-self.EndPoint[1][0], -self.EndPoint[1][1])
       
  2439                             else:
       
  2440                                 # Current point is positioned in the middle of previous point
       
  2441                                 # and end point on the current direction and a point is added
       
  2442                                 if self.Segments[1][0] != 0:
       
  2443                                     self.Points[2].x = (self.Points[1].x + end.x) / 2
       
  2444                                 if self.Segments[1][1] != 0:
       
  2445                                     self.Points[2].y = (self.Points[1].y + end.y) / 2
       
  2446                                 self.Points.insert(3, wx.Point(self.Points[2].x, self.Points[2].y))
       
  2447                                 self.Segments.insert(3, DirectionChoice((self.Segments[2][1], 
       
  2448                                     self.Segments[2][0]), v_end, self.EndPoint[1]))
       
  2449                     else:
       
  2450                         # Current point is aligned with end point
       
  2451                         if self.Segments[i - 1][0] != 0:
       
  2452                             self.Points[i].x = end.x
       
  2453                         if self.Segments[i - 1][1] != 0:
       
  2454                             self.Points[i].y = end.y
       
  2455                         # Previous direction is updated from the new point
       
  2456                         if product(vector(self.Points[i - 1], self.Points[i]), self.Segments[i - 1]) < 0:
       
  2457                             self.Segments[i - 1] = (-self.Segments[i - 1][0], -self.Segments[i - 1][1])
       
  2458                         # If previous direction and end direction are opposed
       
  2459                         if product(self.Segments[i - 1], self.EndPoint[1]) < 0:
       
  2460                             # Current point is positioned in the middle of previous point
       
  2461                             # and end point on the current direction
       
  2462                             if self.Segments[i - 1][0] != 0:
       
  2463                                 self.Points[i].x = (end.x + self.Points[i - 1].x) / 2
       
  2464                             if self.Segments[i - 1][1] != 0:
       
  2465                                 self.Points[i].y = (end.y + self.Points[i - 1].y) / 2
       
  2466                         # A point is added
       
  2467                         self.Points.insert(i + 1, wx.Point(self.Points[i].x, self.Points[i].y))
       
  2468                         self.Segments.insert(i + 1, DirectionChoice((self.Segments[i][1], 
       
  2469                                 self.Segments[i][0]), v_end, self.EndPoint[1]))
       
  2470             else:
       
  2471                 # Current point is the first, and second is not mainly in the first direction
       
  2472                 if i == 0 and product(vector(start, self.Points[1]), self.Segments[0]) < 0:
       
  2473                     # If first and second directions aren't perpendiculars, a point is added 
       
  2474                     if product(self.Segments[0], self.Segments[1]) != 0:
       
  2475                         self.Points.insert(1, wx.Point(start.x, start.y))
       
  2476                         self.Segments.insert(1, DirectionChoice((self.Segments[0][1], 
       
  2477                             self.Segments[0][0]), vector(start, self.Points[1]), self.Segments[1]))
       
  2478                     else:
       
  2479                         self.Points[1].x, self.Points[1].y = start.x, start.y
       
  2480                 else:
       
  2481                     # Next point is aligned with current point
       
  2482                     if self.Segments[i][0] != 0:
       
  2483                         self.Points[i + 1].y = self.Points[i].y
       
  2484                     if self.Segments[i][1] != 0:
       
  2485                         self.Points[i + 1].x = self.Points[i].x
       
  2486                     # Current direction is updated from the new point
       
  2487                     if product(vector(self.Points[i], self.Points[i + 1]), self.Segments[i]) < 0:
       
  2488                         self.Segments[i] = (-self.Segments[i][0], -self.Segments[i][1])
       
  2489             i += 1
       
  2490         self.RefreshBoundingBox()
       
  2491         if realpoints:
       
  2492             self.RefreshRealPoints()
       
  2493     
       
  2494     # Verify that two consecutive points haven't the same position
       
  2495     def VerifyPoints(self):
       
  2496         points = [point for point in self.Points]
       
  2497         segments = [segment for segment in self.Segments]
       
  2498         i = 1
       
  2499         while i < len(points) - 1:
       
  2500             if points[i] == points[i + 1] and segments[i - 1] == segments[i + 1]:
       
  2501                 for j in xrange(2):
       
  2502                     points.pop(i)
       
  2503                     segments.pop(i)
       
  2504             else:
       
  2505                 i += 1
       
  2506         # If the wire isn't in a Ladder Diagram, save the new point list
       
  2507         if self.Parent.__class__.__name__ != "LD_Viewer":
       
  2508             self.Points = [point for point in points]
       
  2509             self.Segments = [segment for segment in segments]
       
  2510             self.RefreshBoundingBox()
       
  2511             self.RefreshRealPoints()
       
  2512         return points
       
  2513     
       
  2514     # Moves all the wire points except the first and the last if they are connected
       
  2515     def Move(self, dx, dy, endpoints = False):
       
  2516         for i, point in enumerate(self.Points):
       
  2517             if endpoints or not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
       
  2518                 point.x += dx
       
  2519                 point.y += dy
       
  2520         self.StartPoint[0] = self.Points[0]
       
  2521         self.EndPoint[0] = self.Points[-1]
       
  2522         self.GeneratePoints()
       
  2523     
       
  2524     # Resize the wire from position and size given
       
  2525     def Resize(self, x, y, width, height):
       
  2526         if len(self.Points) > 1:
       
  2527             # Calculate the new position of each point for testing the new size
       
  2528             minx, miny = self.Pos.x, self.Pos.y
       
  2529             lastwidth, lastheight = self.Size.width, self.Size.height
       
  2530             for i, point in enumerate(self.RealPoints):
       
  2531                 # If start or end point is connected, it's not calculate
       
  2532                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
       
  2533                     if i == 0:
       
  2534                         dir = self.StartPoint[1]
       
  2535                     elif i == len(self.Points) - 1:
       
  2536                         dir = self.EndPoint[1]
       
  2537                     else:
       
  2538                         dir = (0, 0)
       
  2539                     pointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0] * width / float(max(lastwidth, 1)))),
       
  2540                             width - dir[0] * MIN_SEGMENT_SIZE))
       
  2541                     pointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1] * height / float(max(lastheight, 1)))),
       
  2542                             height - dir[1] * MIN_SEGMENT_SIZE))
       
  2543                     self.Points[i] = wx.Point(minx + x + pointx, miny + y + pointy)
       
  2544             self.StartPoint[0] = self.Points[0]
       
  2545             self.EndPoint[0] = self.Points[-1]
       
  2546             self.GeneratePoints(False)
       
  2547             # Test if the wire position or size have changed
       
  2548             if x != 0 and minx == self.Pos.x:
       
  2549                 x = 0
       
  2550                 width = lastwidth
       
  2551             if y != 0 and miny == self.Pos.y:
       
  2552                 y = 0
       
  2553                 height = lastwidth
       
  2554             if width != lastwidth and lastwidth == self.Size.width:
       
  2555                 width = lastwidth
       
  2556             if height != lastheight and lastheight == self.Size.height:
       
  2557                 height = lastheight
       
  2558             # Calculate the real points from the new size, it's important for
       
  2559             # keeping a proportionality in the points position with the size
       
  2560             # during a resize dragging
       
  2561             for i, point in enumerate(self.RealPoints):
       
  2562                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
       
  2563                     point[0] = point[0] * width / float(max(lastwidth, 1))
       
  2564                     point[1] = point[1] * height / float(max(lastheight, 1))
       
  2565             # Calculate the correct position of the points from real points
       
  2566             for i, point in enumerate(self.RealPoints):
       
  2567                 if not (i == 0 and self.StartConnected) and not (i == len(self.Points) - 1 and self.EndConnected):
       
  2568                     if i == 0:
       
  2569                         dir = self.StartPoint[1]
       
  2570                     elif i == len(self.Points) - 1:
       
  2571                         dir = self.EndPoint[1]
       
  2572                     else:
       
  2573                         dir = (0, 0)
       
  2574                     realpointx = max(-dir[0] * MIN_SEGMENT_SIZE, min(int(round(point[0])),
       
  2575                             width - dir[0] * MIN_SEGMENT_SIZE))
       
  2576                     realpointy = max(-dir[1] * MIN_SEGMENT_SIZE, min(int(round(point[1])),
       
  2577                             height - dir[1] * MIN_SEGMENT_SIZE))
       
  2578                     self.Points[i] = wx.Point(minx + x + realpointx, miny + y + realpointy)
       
  2579             self.StartPoint[0] = self.Points[0]
       
  2580             self.EndPoint[0] = self.Points[-1]
       
  2581             self.GeneratePoints(False)
       
  2582     
       
  2583     # Moves the wire start point and update the wire points
       
  2584     def MoveStartPoint(self, point):
       
  2585         if len(self.Points) > 1:
       
  2586             self.StartPoint[0] = point
       
  2587             self.Points[0] = point
       
  2588             self.GeneratePoints()
       
  2589     
       
  2590     # Changes the wire start direction and update the wire points
       
  2591     def SetStartPointDirection(self, dir):
       
  2592         if len(self.Points) > 1:
       
  2593             self.StartPoint[1] = dir
       
  2594             self.Segments[0] = dir
       
  2595             self.GeneratePoints()
       
  2596     
       
  2597     # Rotates the wire start direction by an angle of 90 degrees anticlockwise
       
  2598     def RotateStartPoint(self):
       
  2599         self.SetStartPointDirection((self.StartPoint[1][1], -self.StartPoint[1][0]))
       
  2600     
       
  2601     # Connects wire start point to the connector given and moves wire start point
       
  2602     # to given point
       
  2603     def ConnectStartPoint(self, point, connector):
       
  2604         if point:
       
  2605             self.MoveStartPoint(point)
       
  2606         self.StartConnected = connector
       
  2607         self.RefreshBoundingBox()
       
  2608     
       
  2609     # Unconnects wire start point
       
  2610     def UnConnectStartPoint(self, delete = False):
       
  2611         if delete:
       
  2612             self.StartConnected = None
       
  2613             self.Delete()
       
  2614         elif self.StartConnected:
       
  2615             self.StartConnected.UnConnect(self, unconnect = False)
       
  2616             self.StartConnected = None
       
  2617             self.RefreshBoundingBox()
       
  2618     
       
  2619     # Moves the wire end point and update the wire points
       
  2620     def MoveEndPoint(self, point):
       
  2621         if len(self.Points) > 1:
       
  2622             self.EndPoint[0] = point
       
  2623             self.Points[-1] = point
       
  2624             self.GeneratePoints()
       
  2625 
       
  2626     # Changes the wire end direction and update the wire points
       
  2627     def SetEndPointDirection(self, dir):
       
  2628         if len(self.Points) > 1:
       
  2629             self.EndPoint[1] = dir
       
  2630             self.GeneratePoints()
       
  2631             
       
  2632     # Rotates the wire end direction by an angle of 90 degrees anticlockwise
       
  2633     def RotateEndPoint(self):
       
  2634         self.SetEndPointDirection((self.EndPoint[1][1], -self.EndPoint[1][0]))
       
  2635 
       
  2636     # Connects wire end point to the connector given and moves wire end point
       
  2637     # to given point
       
  2638     def ConnectEndPoint(self, point, connector):
       
  2639         if point:
       
  2640             self.MoveEndPoint(point)
       
  2641         self.EndConnected = connector
       
  2642         self.RefreshBoundingBox()
       
  2643     
       
  2644     # Unconnects wire end point
       
  2645     def UnConnectEndPoint(self, delete = False):
       
  2646         if delete:
       
  2647             self.EndConnected = None
       
  2648             self.Delete()
       
  2649         elif self.EndConnected:
       
  2650             self.EndConnected.UnConnect(self, unconnect = False)
       
  2651             self.EndConnected = None
       
  2652             self.RefreshBoundingBox()
       
  2653     
       
  2654     # Moves the wire segment given by its index
       
  2655     def MoveSegment(self, idx, movex, movey, scaling):
       
  2656         if 0 < idx < len(self.Segments) - 1:
       
  2657             if self.Segments[idx] in (NORTH, SOUTH):
       
  2658                 start_x = self.Points[idx].x
       
  2659                 if scaling is not None:
       
  2660                     movex = round_scaling(self.Points[idx].x + movex, scaling[0]) - self.Points[idx].x
       
  2661                     if idx == 1 and (self.Points[1].x + movex - self.Points[0].x) * self.Segments[0][0] < MIN_SEGMENT_SIZE:
       
  2662                         movex = round_scaling(self.Points[0].x + MIN_SEGMENT_SIZE * self.Segments[0][0], scaling[0], self.Segments[0][0]) - self.Points[idx].x
       
  2663                     elif idx == len(self.Segments) - 2 and (self.Points[-1].x - (self.Points[-2].x + movex)) * self.Segments[-1][0] < MIN_SEGMENT_SIZE:
       
  2664                         movex = round_scaling(self.Points[-1].x - MIN_SEGMENT_SIZE * self.Segments[-1][0], scaling[0], -self.Segments[-1][0]) - self.Points[idx].x
       
  2665                 self.Points[idx].x += movex
       
  2666                 self.Points[idx + 1].x += movex
       
  2667                 self.GeneratePoints()
       
  2668                 if start_x != self.Points[idx].x:
       
  2669                     return self.Points[idx].x - start_x, 0
       
  2670             elif self.Segments[idx] in (EAST, WEST):
       
  2671                 start_y = self.Points[idx].y
       
  2672                 if scaling is not None:
       
  2673                     movey = round_scaling(self.Points[idx].y + movey, scaling[1]) - self.Points[idx].y
       
  2674                     if idx == 1 and (self.Points[1].y + movey - self.Points[0].y) * self.Segments[0][1] < MIN_SEGMENT_SIZE:
       
  2675                         movex = round_scaling(self.Points[0].y + MIN_SEGMENT_SIZE * self.Segments[0][1], scaling[0], self.Segments[0][1]) - self.Points[idx].y
       
  2676                     elif idx == len(self.Segments) - 2 and (self.Points[-1].y - (self.Points[-2].y + movey)) * self.Segments[-1][1] < MIN_SEGMENT_SIZE:
       
  2677                         movey = round_scaling(self.Points[idx].y - MIN_SEGMENT_SIZE * self.Segments[-1][1], scaling[1], -self.Segments[-1][1]) - self.Points[idx].y
       
  2678                 self.Points[idx].y += movey
       
  2679                 self.Points[idx + 1].y += movey
       
  2680                 self.GeneratePoints()
       
  2681                 if start_y != self.Points[idx].y:
       
  2682                     return 0, self.Points[idx].y - start_y
       
  2683         return 0, 0
       
  2684     
       
  2685     # Adds two points in the middle of the handled segment
       
  2686     def AddSegment(self):
       
  2687         handle_type, handle = self.Handle
       
  2688         if handle_type == HANDLE_SEGMENT:
       
  2689             segment, dir = handle
       
  2690             if len(self.Segments) > 1:
       
  2691                 pointx = self.Points[segment].x
       
  2692                 pointy = self.Points[segment].y
       
  2693                 if dir[0] != 0:
       
  2694                     pointx = (self.Points[segment].x + self.Points[segment + 1].x) / 2
       
  2695                 if dir[1] != 0:
       
  2696                     pointy = (self.Points[segment].y + self.Points[segment + 1].y) / 2
       
  2697                 self.Points.insert(segment + 1, wx.Point(pointx, pointy))
       
  2698                 self.Segments.insert(segment + 1, (dir[1], dir[0]))
       
  2699                 self.Points.insert(segment + 2, wx.Point(pointx, pointy))
       
  2700                 self.Segments.insert(segment + 2, dir)
       
  2701             else:
       
  2702                 p1x = p2x = self.Points[segment].x
       
  2703                 p1y = p2y = self.Points[segment].y
       
  2704                 if dir[0] != 0:
       
  2705                     p1x = (2 * self.Points[segment].x + self.Points[segment + 1].x) / 3
       
  2706                     p2x = (self.Points[segment].x + 2 * self.Points[segment + 1].x) / 3
       
  2707                 if dir[1] != 0:
       
  2708                     p1y = (2 * self.Points[segment].y + self.Points[segment + 1].y) / 3
       
  2709                     p2y = (self.Points[segment].y + 2 * self.Points[segment + 1].y) / 3
       
  2710                 self.Points.insert(segment + 1, wx.Point(p1x, p1y))
       
  2711                 self.Segments.insert(segment + 1, (dir[1], dir[0]))
       
  2712                 self.Points.insert(segment + 2, wx.Point(p1x, p1y))
       
  2713                 self.Segments.insert(segment + 2, dir)
       
  2714                 self.Points.insert(segment + 3, wx.Point(p2x, p2y))
       
  2715                 self.Segments.insert(segment + 3, (dir[1], dir[0]))
       
  2716                 self.Points.insert(segment + 4, wx.Point(p2x, p2y))
       
  2717                 self.Segments.insert(segment + 4, dir)
       
  2718             self.GeneratePoints()
       
  2719     
       
  2720     # Delete the handled segment by removing the two segment points
       
  2721     def DeleteSegment(self):
       
  2722         handle_type, handle = self.Handle
       
  2723         if handle_type == HANDLE_SEGMENT:
       
  2724             segment, dir = handle
       
  2725             for i in xrange(2):
       
  2726                 self.Points.pop(segment)
       
  2727                 self.Segments.pop(segment)
       
  2728             self.GeneratePoints()
       
  2729             self.RefreshModel()
       
  2730             
       
  2731     # Method called when a LeftDown event have been generated
       
  2732     def OnLeftDown(self, event, dc, scaling):
       
  2733         pos = GetScaledEventPosition(event, dc, scaling)
       
  2734         # Test if a point have been handled
       
  2735         #result = self.TestPoint(pos)
       
  2736         #if result != None:
       
  2737         #    self.Handle = (HANDLE_POINT, result)
       
  2738         #    wx.CallAfter(self.Parent.SetCurrentCursor, 1)
       
  2739         #else:
       
  2740         # Test if a segment have been handled
       
  2741         result = self.TestSegment(pos)
       
  2742         if result != None:
       
  2743             if result[1] in (NORTH, SOUTH):
       
  2744                 wx.CallAfter(self.Parent.SetCurrentCursor, 4)
       
  2745             elif result[1] in (EAST, WEST):
       
  2746                 wx.CallAfter(self.Parent.SetCurrentCursor, 5)
       
  2747             self.Handle = (HANDLE_SEGMENT, result)
       
  2748         # Execute the default method for a graphic element
       
  2749         else:
       
  2750             Graphic_Element.OnLeftDown(self, event, dc, scaling)
       
  2751         self.oldPos = pos
       
  2752     
       
  2753     # Method called when a RightUp event has been generated
       
  2754     def OnRightUp(self, event, dc, scaling):
       
  2755         pos = GetScaledEventPosition(event, dc, scaling)
       
  2756         # Test if a segment has been handled
       
  2757         result = self.TestSegment(pos, True)
       
  2758         if result != None:
       
  2759             self.Handle = (HANDLE_SEGMENT, result)
       
  2760             # Popup the menu with special items for a wire
       
  2761             self.Parent.PopupWireMenu(0 < result[0] < len(self.Segments) - 1)
       
  2762         else:
       
  2763             # Execute the default method for a graphic element
       
  2764             Graphic_Element.OnRightUp(self, event, dc, scaling)
       
  2765     
       
  2766     # Method called when a LeftDClick event has been generated
       
  2767     def OnLeftDClick(self, event, dc, scaling):
       
  2768         rect = self.GetRedrawRect()
       
  2769         if event.ControlDown():
       
  2770             direction = (self.StartPoint[1], self.EndPoint[1])
       
  2771             if direction in [(EAST, WEST), (WEST, EAST)]:
       
  2772                 avgy = (self.StartPoint[0].y + self.EndPoint[0].y) / 2
       
  2773                 if scaling is not None:
       
  2774                     avgy = round(float(avgy) / scaling[1]) * scaling[1]
       
  2775                 if self.StartConnected is not None:
       
  2776                     movey = avgy - self.StartPoint[0].y
       
  2777                     startblock = self.StartConnected.GetParentBlock()
       
  2778                     startblock.Move(0, movey)
       
  2779                     startblock.RefreshModel()
       
  2780                     rect.Union(startblock.GetRedrawRect(0, movey))
       
  2781                 else:
       
  2782                     self.MoveStartPoint(wx.Point(self.StartPoint[0].x, avgy))
       
  2783                 if self.EndConnected is not None:
       
  2784                     movey = avgy - self.EndPoint[0].y
       
  2785                     endblock = self.EndConnected.GetParentBlock()
       
  2786                     endblock.Move(0, movey)
       
  2787                     endblock.RefreshModel()
       
  2788                     rect.Union(endblock.GetRedrawRect(0, movey))
       
  2789                 else:
       
  2790                     self.MoveEndPoint(wx.Point(self.EndPoint[0].x, avgy))
       
  2791                 self.Parent.RefreshBuffer()
       
  2792             elif direction in [(NORTH, SOUTH), (SOUTH, NORTH)]:
       
  2793                 avgx = (self.StartPoint[0].x + self.EndPoint[0].x) / 2
       
  2794                 if scaling is not None:
       
  2795                     avgx = round(float(avgx) / scaling[0]) * scaling[0]
       
  2796                 if self.StartConnected is not None:
       
  2797                     movex = avgx - self.StartPoint[0].x
       
  2798                     startblock = self.StartConnected.GetParentBlock()
       
  2799                     startblock.Move(movex, 0)
       
  2800                     startblock.RefreshModel()
       
  2801                     rect.Union(startblock.GetRedrawRect(movex, 0))
       
  2802                 else:
       
  2803                     self.MoveStartPoint(wx.Point(avgx, self.StartPoint[0].y))
       
  2804                 if self.EndConnected is not None:
       
  2805                     movex = avgx - self.EndPoint[0].x
       
  2806                     endblock = self.EndConnected.GetParentBlock()
       
  2807                     endblock.Move(movex, 0)
       
  2808                     endblock.RefreshModel()
       
  2809                     rect.Union(endblock.GetRedrawRect(movex, 0))
       
  2810                 else:
       
  2811                     self.MoveEndPoint(wx.Point(avgx, self.EndPoint[0].y))
       
  2812                 self.Parent.RefreshBuffer()
       
  2813         else:
       
  2814             self.ResetPoints()
       
  2815             self.GeneratePoints()
       
  2816             self.RefreshModel()
       
  2817             self.Parent.RefreshBuffer()
       
  2818         rect.Union(self.GetRedrawRect())
       
  2819         self.Parent.RefreshRect(self.Parent.GetScrolledRect(rect), False)
       
  2820         
       
  2821     # Method called when a Motion event has been generated
       
  2822     def OnMotion(self, event, dc, scaling):
       
  2823         pos = GetScaledEventPosition(event, dc, scaling)
       
  2824         if not event.Dragging():
       
  2825             # Test if a segment has been handled
       
  2826             result = self.TestSegment(pos)
       
  2827             if result:
       
  2828                 if result[1] in (NORTH, SOUTH):
       
  2829                     wx.CallAfter(self.Parent.SetCurrentCursor, 4)
       
  2830                 elif result[1] in (EAST, WEST):
       
  2831                     wx.CallAfter(self.Parent.SetCurrentCursor, 5)
       
  2832                 return 0, 0
       
  2833             else:
       
  2834                 # Execute the default method for a graphic element
       
  2835                 return Graphic_Element.OnMotion(self, event, dc, scaling)
       
  2836         else:
       
  2837             # Execute the default method for a graphic element
       
  2838             return Graphic_Element.OnMotion(self, event, dc, scaling)
       
  2839     
       
  2840     # Refreshes the wire state according to move defined and handle selected
       
  2841     def ProcessDragging(self, movex, movey, event, scaling):
       
  2842         handle_type, handle = self.Handle
       
  2843         # A point has been handled
       
  2844         if handle_type == HANDLE_POINT:
       
  2845             movex = max(-self.Points[handle].x + POINT_RADIUS, movex)
       
  2846             movey = max(-self.Points[handle].y + POINT_RADIUS, movey)
       
  2847             if scaling is not None:
       
  2848                 movex = round_scaling(self.Points[handle].x + movex, scaling[0]) - self.Points[handle].x
       
  2849                 movey = round_scaling(self.Points[handle].y + movey, scaling[1]) - self.Points[handle].y
       
  2850             # Try to connect point to a connector
       
  2851             new_pos = wx.Point(self.Points[handle].x + movex, self.Points[handle].y + movey)
       
  2852             connector = self.Parent.FindBlockConnector(new_pos, self.GetConnectionDirection())
       
  2853             if connector:
       
  2854                 if handle == 0 and self.EndConnected != connector:
       
  2855                     connector.HighlightParentBlock(True)
       
  2856                     connector.Connect((self, handle))
       
  2857                     self.SetStartPointDirection(connector.GetDirection())
       
  2858                     self.ConnectStartPoint(connector.GetPosition(), connector)
       
  2859                     pos = connector.GetPosition()
       
  2860                     movex = pos.x - self.oldPos.x
       
  2861                     movey = pos.y - self.oldPos.y
       
  2862                     if not connector.IsCompatible(self.GetEndConnectedType()):
       
  2863                         self.SetValid(False)
       
  2864                     self.Dragging = False
       
  2865                 elif handle != 0 and self.StartConnected != connector:
       
  2866                     connector.HighlightParentBlock(True)
       
  2867                     connector.Connect((self, handle))
       
  2868                     self.SetEndPointDirection(connector.GetDirection())
       
  2869                     self.ConnectEndPoint(connector.GetPosition(), connector)
       
  2870                     pos = connector.GetPosition()
       
  2871                     movex = pos.x - self.oldPos.x
       
  2872                     movey = pos.y - self.oldPos.y
       
  2873                     if not connector.IsCompatible(self.GetStartConnectedType()):
       
  2874                         self.SetValid(False)
       
  2875                     self.Dragging = False
       
  2876                 elif handle == 0:
       
  2877                     self.MoveStartPoint(new_pos)
       
  2878                 else:
       
  2879                     self.MoveEndPoint(new_pos)
       
  2880             # If there is no connector, move the point
       
  2881             elif handle == 0:
       
  2882                 self.SetValid(True)
       
  2883                 if self.StartConnected:
       
  2884                     self.StartConnected.HighlightParentBlock(False)
       
  2885                     self.UnConnectStartPoint()
       
  2886                 self.MoveStartPoint(new_pos)
       
  2887             else:
       
  2888                 self.SetValid(True)
       
  2889                 if self.EndConnected:
       
  2890                     self.EndConnected.HighlightParentBlock(False)
       
  2891                     self.UnConnectEndPoint()
       
  2892                 self.MoveEndPoint(new_pos)
       
  2893             return movex, movey
       
  2894         # A segment has been handled, move a segment
       
  2895         elif handle_type == HANDLE_SEGMENT:
       
  2896             return self.MoveSegment(handle[0], movex, movey, scaling)
       
  2897         # Execute the default method for a graphic element
       
  2898         else:
       
  2899             return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
       
  2900     
       
  2901     # Refreshes the wire model
       
  2902     def RefreshModel(self, move=True):
       
  2903         if self.StartConnected and self.StartPoint[1] in [WEST, NORTH]:
       
  2904             self.StartConnected.RefreshParentBlock()
       
  2905         if self.EndConnected and self.EndPoint[1] in [WEST, NORTH]:
       
  2906             self.EndConnected.RefreshParentBlock()
       
  2907     
       
  2908     # Change the variable that indicates if this element is highlighted
       
  2909     def SetHighlighted(self, highlighted):
       
  2910         self.Highlighted = highlighted
       
  2911         if not highlighted:
       
  2912             self.OverStart = False
       
  2913             self.OverEnd = False
       
  2914         self.Refresh()
       
  2915     
       
  2916     def HighlightPoint(self, pos):
       
  2917         refresh = False
       
  2918         start, end = self.OverStart, self.OverEnd
       
  2919         self.OverStart = False
       
  2920         self.OverEnd = False
       
  2921         # Test if a point has been handled
       
  2922         result = self.TestPoint(pos)
       
  2923         if result != None:
       
  2924             if result == 0 and self.StartConnected is not None:
       
  2925                 self.OverStart = True
       
  2926             elif result != 0 and self.EndConnected is not None:
       
  2927                 self.OverEnd = True
       
  2928         if start != self.OverStart or end != self.OverEnd:
       
  2929             self.Refresh()
       
  2930     
       
  2931     # Draws the highlightment of this element if it is highlighted
       
  2932     def DrawHighlightment(self, dc):
       
  2933         scalex, scaley = dc.GetUserScale()
       
  2934         dc.SetUserScale(1, 1)
       
  2935         dc.SetPen(MiterPen(HIGHLIGHTCOLOR, (2 * scalex + 5)))
       
  2936         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
       
  2937         dc.SetLogicalFunction(wx.AND)
       
  2938         # Draw the start and end points if they are not connected or the mouse is over them
       
  2939         if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
       
  2940             dc.DrawCircle(round(self.Points[0].x * scalex), 
       
  2941                           round(self.Points[0].y * scaley), 
       
  2942                           (POINT_RADIUS + 1) * scalex + 2)
       
  2943         if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
       
  2944             dc.DrawCircle(self.Points[-1].x * scalex, self.Points[-1].y * scaley, (POINT_RADIUS + 1) * scalex + 2)
       
  2945         # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
       
  2946         if len(self.Points) > 1:
       
  2947             points = [wx.Point(round((self.Points[0].x - self.Segments[0][0]) * scalex), 
       
  2948                                round((self.Points[0].y - self.Segments[0][1]) * scaley))]
       
  2949             points.extend([wx.Point(round(point.x * scalex), round(point.y * scaley)) for point in self.Points[1:-1]])
       
  2950             points.append(wx.Point(round((self.Points[-1].x + self.Segments[-1][0]) * scalex), 
       
  2951                                    round((self.Points[-1].y + self.Segments[-1][1]) * scaley)))
       
  2952         else:
       
  2953             points = []
       
  2954         dc.DrawLines(points)
       
  2955         dc.SetLogicalFunction(wx.COPY)
       
  2956         dc.SetUserScale(scalex, scaley)
       
  2957         
       
  2958         if self.StartConnected is not None:
       
  2959             self.StartConnected.DrawHighlightment(dc)
       
  2960             self.StartConnected.Draw(dc)
       
  2961         if self.EndConnected is not None:
       
  2962             self.EndConnected.DrawHighlightment(dc)
       
  2963             self.EndConnected.Draw(dc)
       
  2964     
       
  2965     # Draws the wire lines and points
       
  2966     def Draw(self, dc):
       
  2967         Graphic_Element.Draw(self, dc)
       
  2968         if not self.Valid:
       
  2969             dc.SetPen(MiterPen(wx.RED))
       
  2970             dc.SetBrush(wx.RED_BRUSH)
       
  2971         elif isinstance(self.Value, BooleanType) and self.Value:
       
  2972             if self.Forced:
       
  2973                 dc.SetPen(MiterPen(wx.CYAN))
       
  2974                 dc.SetBrush(wx.CYAN_BRUSH)
       
  2975             else:
       
  2976                 dc.SetPen(MiterPen(wx.GREEN))
       
  2977                 dc.SetBrush(wx.GREEN_BRUSH)
       
  2978         elif self.Value == "undefined":
       
  2979             dc.SetPen(MiterPen(wx.NamedColour("orange")))
       
  2980             dc.SetBrush(wx.Brush(wx.NamedColour("orange")))
       
  2981         elif self.Forced:
       
  2982             dc.SetPen(MiterPen(wx.BLUE))
       
  2983             dc.SetBrush(wx.BLUE_BRUSH)
       
  2984         else:
       
  2985             dc.SetPen(MiterPen(wx.BLACK))
       
  2986             dc.SetBrush(wx.BLACK_BRUSH)
       
  2987         # Draw the start and end points if they are not connected or the mouse is over them
       
  2988         if len(self.Points) > 0 and (not self.StartConnected or self.OverStart):
       
  2989             dc.DrawCircle(self.Points[0].x, self.Points[0].y, POINT_RADIUS)
       
  2990         if len(self.Points) > 1 and (not self.EndConnected or self.OverEnd):
       
  2991             dc.DrawCircle(self.Points[-1].x, self.Points[-1].y, POINT_RADIUS)
       
  2992         # Draw the wire lines and the last point (it seems that DrawLines stop before the last point)
       
  2993         if len(self.Points) > 1:
       
  2994             points = [wx.Point(self.Points[0].x - self.Segments[0][0], self.Points[0].y - self.Segments[0][1])]
       
  2995             points.extend([point for point in self.Points[1:-1]])
       
  2996             points.append(wx.Point(self.Points[-1].x + self.Segments[-1][0], self.Points[-1].y + self.Segments[-1][1]))
       
  2997         else:
       
  2998             points = []
       
  2999         dc.DrawLines(points)
       
  3000         # Draw the segment selected in red
       
  3001         if not getattr(dc, "printing", False) and self.SelectedSegment is not None:
       
  3002             dc.SetPen(MiterPen(wx.BLUE, 3))
       
  3003             if self.SelectedSegment == len(self.Segments) - 1:
       
  3004                 end = 0
       
  3005             else:
       
  3006                 end = 1
       
  3007             dc.DrawLine(self.Points[self.SelectedSegment].x - 1, self.Points[self.SelectedSegment].y,
       
  3008                         self.Points[self.SelectedSegment + 1].x + end, self.Points[self.SelectedSegment + 1].y)
       
  3009         if self.Value is not None and not isinstance(self.Value, BooleanType) and self.Value != "undefined":
       
  3010             dc.SetFont(self.Parent.GetMiniFont())
       
  3011             dc.SetTextForeground(wx.NamedColour("purple"))
       
  3012             if self.ValueSize is None and isinstance(self.ComputedValue, (StringType, UnicodeType)):
       
  3013                 self.ValueSize = self.Parent.GetMiniTextExtent(self.ComputedValue)
       
  3014             if self.ValueSize is not None:
       
  3015                 width, height = self.ValueSize
       
  3016                 if self.BoundingBox[2] > width * 4 or self.BoundingBox[3] > height * 4:
       
  3017                     x = self.Points[0].x + width * (self.StartPoint[1][0] - 1) / 2
       
  3018                     y = self.Points[0].y + height * (self.StartPoint[1][1] - 1)
       
  3019                     dc.DrawText(self.ComputedValue, x, y)
       
  3020                     x = self.Points[-1].x + width * (self.EndPoint[1][0] - 1) / 2
       
  3021                     y = self.Points[-1].y + height * (self.EndPoint[1][1] - 1)
       
  3022                     dc.DrawText(self.ComputedValue, x, y)
       
  3023                 else:
       
  3024                     middle = len(self.Segments) / 2 + len(self.Segments) % 2 - 1
       
  3025                     x = (self.Points[middle].x + self.Points[middle + 1].x - width) / 2
       
  3026                     if self.BoundingBox[3] > height and self.Segments[middle] in [NORTH, SOUTH]:
       
  3027                         y = (self.Points[middle].y + self.Points[middle + 1].y - height) / 2
       
  3028                     else:
       
  3029                         y = self.Points[middle].y - height
       
  3030                     dc.DrawText(self.ComputedValue, x, y)
       
  3031             dc.SetFont(self.Parent.GetFont())
       
  3032             dc.SetTextForeground(wx.BLACK)
       
  3033 
       
  3034 
       
  3035 #-------------------------------------------------------------------------------
       
  3036 #                           Graphic comment element
       
  3037 #-------------------------------------------------------------------------------
       
  3038 
       
  3039 def FilterHighlightsByRow(highlights, row, length):
       
  3040     _highlights = []
       
  3041     for start, end, highlight_type in highlights:
       
  3042         if start[0] <= row and end[0] >= row:
       
  3043             if start[0] < row:
       
  3044                 start = (row, 0)
       
  3045             if end[0] > row:
       
  3046                 end = (row, length)
       
  3047             _highlights.append((start, end, highlight_type))
       
  3048     return _highlights
       
  3049 
       
  3050 def FilterHighlightsByColumn(highlights, start_col, end_col):
       
  3051     _highlights = []
       
  3052     for start, end, highlight_type in highlights:
       
  3053         if end[1] > start_col and start[1] < end_col:
       
  3054             start = (start[0], max(start[1], start_col) - start_col)
       
  3055             end = (end[0], min(end[1], end_col) - start_col)
       
  3056             _highlights.append((start, end, highlight_type))
       
  3057     return _highlights
       
  3058 
       
  3059 """
       
  3060 Class that implements a comment
       
  3061 """
       
  3062 
       
  3063 class Comment(Graphic_Element):
       
  3064 
       
  3065     # Create a new comment
       
  3066     def __init__(self, parent, content, id = None):
       
  3067         Graphic_Element.__init__(self, parent)
       
  3068         self.Id = id
       
  3069         self.Content = content
       
  3070         self.Pos = wx.Point(0, 0)
       
  3071         self.Size = wx.Size(0, 0)
       
  3072         self.Highlights = []
       
  3073     
       
  3074     # Make a clone of this comment
       
  3075     def Clone(self, parent, id = None, pos = None):
       
  3076         comment = Comment(parent, self.Content, id)
       
  3077         if pos is not None:
       
  3078             comment.SetPosition(pos.x, pos.y)
       
  3079         comment.SetSize(self.Size[0], self.Size[1])
       
  3080         return comment
       
  3081     
       
  3082     # Method for keeping compatibility with others
       
  3083     def Clean(self):
       
  3084         pass
       
  3085     
       
  3086     # Delete this comment by calling the corresponding method
       
  3087     def Delete(self):
       
  3088         self.Parent.DeleteComment(self)
       
  3089     
       
  3090     # Refresh the comment bounding box
       
  3091     def RefreshBoundingBox(self):
       
  3092         self.BoundingBox = wx.Rect(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
  3093     
       
  3094     # Changes the comment size
       
  3095     def SetSize(self, width, height):
       
  3096         self.Size.SetWidth(width)
       
  3097         self.Size.SetHeight(height)
       
  3098         self.RefreshBoundingBox()
       
  3099 
       
  3100     # Returns the comment size
       
  3101     def GetSize(self):
       
  3102         return self.Size.GetWidth(), self.Size.GetHeight()
       
  3103     
       
  3104     # Returns the comment minimum size
       
  3105     def GetMinSize(self):
       
  3106         dc = wx.ClientDC(self.Parent)
       
  3107         min_width = 0
       
  3108         min_height = 0
       
  3109         # The comment minimum size is the maximum size of words in the content
       
  3110         for line in self.Content.splitlines():
       
  3111             for word in line.split(" "):
       
  3112                 wordwidth, wordheight = dc.GetTextExtent(word)
       
  3113                 min_width = max(min_width, wordwidth)
       
  3114                 min_height = max(min_height, wordheight)
       
  3115         return min_width + 20, min_height + 20
       
  3116     
       
  3117     # Changes the comment position
       
  3118     def SetPosition(self, x, y):
       
  3119         self.Pos.x = x
       
  3120         self.Pos.y = y
       
  3121         self.RefreshBoundingBox()
       
  3122 
       
  3123     # Changes the comment content
       
  3124     def SetContent(self, content):
       
  3125         self.Content = content
       
  3126         min_width, min_height = self.GetMinSize()
       
  3127         self.Size[0] = max(self.Size[0], min_width)
       
  3128         self.Size[1] = max(self.Size[1], min_height)
       
  3129         self.RefreshBoundingBox()
       
  3130 
       
  3131     # Returns the comment content
       
  3132     def GetContent(self):
       
  3133         return self.Content
       
  3134 
       
  3135     # Returns the comment position
       
  3136     def GetPosition(self):
       
  3137         return self.Pos.x, self.Pos.y
       
  3138     
       
  3139     # Moves the comment
       
  3140     def Move(self, dx, dy, connected = True):
       
  3141         self.Pos.x += dx
       
  3142         self.Pos.y += dy
       
  3143         self.RefreshBoundingBox()
       
  3144     
       
  3145     # Resizes the comment with the position and the size given
       
  3146     def Resize(self, x, y, width, height):
       
  3147         self.Move(x, y)
       
  3148         self.SetSize(width, height)
       
  3149     
       
  3150     # Method called when a RightUp event have been generated
       
  3151     def OnRightUp(self, event, dc, scaling):
       
  3152         # Popup the default menu
       
  3153         self.Parent.PopupDefaultMenu()
       
  3154     
       
  3155     # Refreshes the wire state according to move defined and handle selected
       
  3156     def ProcessDragging(self, movex, movey, event, scaling):
       
  3157         if self.Parent.GetDrawingMode() != FREEDRAWING_MODE and self.Parent.CurrentLanguage == "LD":
       
  3158             movex = movey = 0
       
  3159         return Graphic_Element.ProcessDragging(self, movex, movey, event, scaling)
       
  3160         
       
  3161     # Refreshes the comment model
       
  3162     def RefreshModel(self, move=True):
       
  3163         self.Parent.RefreshCommentModel(self)
       
  3164     
       
  3165     # Method called when a LeftDClick event have been generated
       
  3166     def OnLeftDClick(self, event, dc, scaling):
       
  3167         # Edit the comment content
       
  3168         self.Parent.EditCommentContent(self)
       
  3169     
       
  3170     # Adds an highlight to the comment
       
  3171     def AddHighlight(self, infos, start, end, highlight_type):
       
  3172         if infos[0] == "content":
       
  3173             AddHighlight(self.Highlights, (start, end, highlight_type))
       
  3174     
       
  3175     # Removes an highlight from the comment
       
  3176     def RemoveHighlight(self, infos, start, end, highlight_type):
       
  3177         RemoveHighlight(self.Highlights, (start, end, highlight_type))
       
  3178     
       
  3179     # Removes all the highlights of one particular type from the comment
       
  3180     def ClearHighlight(self, highlight_type=None):
       
  3181         self.Highlights = ClearHighlights(self.Highlights, highlight_type)
       
  3182     
       
  3183     # Draws the highlightment of this element if it is highlighted
       
  3184     def DrawHighlightment(self, dc):
       
  3185         scalex, scaley = dc.GetUserScale()
       
  3186         dc.SetUserScale(1, 1)
       
  3187         dc.SetPen(MiterPen(HIGHLIGHTCOLOR))
       
  3188         dc.SetBrush(wx.Brush(HIGHLIGHTCOLOR))
       
  3189         dc.SetLogicalFunction(wx.AND)
       
  3190         
       
  3191         left = (self.Pos.x - 1) * scalex - 2
       
  3192         right = (self.Pos.x + self.Size[0] + 1) * scalex + 2
       
  3193         top = (self.Pos.y - 1) * scaley - 2
       
  3194         bottom = (self.Pos.y + self.Size[1] + 1) * scaley + 2
       
  3195         angle_top = (self.Pos.x + self.Size[0] - 9) * scalex + 2
       
  3196         angle_right = (self.Pos.y + 9) * scaley - 2
       
  3197         
       
  3198         polygon = [wx.Point(left, top), wx.Point(angle_top, top),
       
  3199                    wx.Point(right, angle_right), wx.Point(right, bottom),
       
  3200                    wx.Point(left, bottom)]
       
  3201         dc.DrawPolygon(polygon)
       
  3202         
       
  3203         dc.SetLogicalFunction(wx.COPY)
       
  3204         dc.SetUserScale(scalex, scaley)
       
  3205         
       
  3206     # Draws the comment and its content
       
  3207     def Draw(self, dc):
       
  3208         Graphic_Element.Draw(self, dc)
       
  3209         dc.SetPen(MiterPen(wx.BLACK))
       
  3210         dc.SetBrush(wx.WHITE_BRUSH)
       
  3211         # Draws the comment shape
       
  3212         polygon = [wx.Point(self.Pos.x, self.Pos.y), 
       
  3213                    wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
       
  3214                    wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10),
       
  3215                    wx.Point(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1]),
       
  3216                    wx.Point(self.Pos.x, self.Pos.y + self.Size[1])]
       
  3217         dc.DrawPolygon(polygon)
       
  3218         lines = [wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y),
       
  3219                  wx.Point(self.Pos.x + self.Size[0] - 10, self.Pos.y + 10),
       
  3220                  wx.Point(self.Pos.x + self.Size[0], self.Pos.y + 10)]
       
  3221         dc.DrawLines(lines)
       
  3222         # Draws the comment content
       
  3223         y = self.Pos.y + 10
       
  3224         for idx, line in enumerate(self.Content.splitlines()):
       
  3225             first = True
       
  3226             linetext = ""
       
  3227             words = line.split(" ")
       
  3228             if not getattr(dc, "printing", False):
       
  3229                 highlights = FilterHighlightsByRow(self.Highlights, idx, len(line))
       
  3230                 highlights_offset = 0
       
  3231             for i, word in enumerate(words):
       
  3232                 if first:
       
  3233                     text = word
       
  3234                 else:
       
  3235                     text = linetext + " " + word
       
  3236                 wordwidth, wordheight = dc.GetTextExtent(text)
       
  3237                 if y + wordheight > self.Pos.y + self.Size[1] - 10:
       
  3238                     break
       
  3239                 if wordwidth < self.Size[0] - 20:
       
  3240                     if i < len(words) - 1:
       
  3241                         linetext = text
       
  3242                         first = False
       
  3243                     else:
       
  3244                         dc.DrawText(text, self.Pos.x + 10, y)
       
  3245                         if not getattr(dc, "printing", False):
       
  3246                             DrawHighlightedText(dc, text, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(text)), self.Pos.x + 10, y)
       
  3247                             highlights_offset += len(text) + 1
       
  3248                         y += wordheight + 5
       
  3249                 else:
       
  3250                     if not first:
       
  3251                         dc.DrawText(linetext, self.Pos.x + 10, y)
       
  3252                         if not getattr(dc, "printing", False):
       
  3253                             DrawHighlightedText(dc, linetext, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(linetext)), self.Pos.x + 10, y)
       
  3254                             highlights_offset += len(linetext) + 1
       
  3255                     if first or i == len(words) - 1:
       
  3256                         if not first:
       
  3257                             y += wordheight + 5
       
  3258                             if y + wordheight > self.Pos.y + self.Size[1] - 10:
       
  3259                                 break
       
  3260                         dc.DrawText(word, self.Pos.x + 10, y)
       
  3261                         if not getattr(dc, "printing", False):
       
  3262                             DrawHighlightedText(dc, word, FilterHighlightsByColumn(highlights, highlights_offset, highlights_offset + len(word)), self.Pos.x + 10, y)
       
  3263                             highlights_offset += len(word) + 1
       
  3264                     else:
       
  3265                         linetext = word
       
  3266                     y += wordheight + 5
       
  3267             if y + wordheight > self.Pos.y + self.Size[1] - 10:
       
  3268                 break
       
  3269