graphics/FBD_Objects.py
changeset 814 5743cbdff669
child 852 1009f956d2ee
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 
       
    27 from GraphicCommons import *
       
    28 from plcopen.structures import *
       
    29 
       
    30 #-------------------------------------------------------------------------------
       
    31 #                         Function Block Diagram Block
       
    32 #-------------------------------------------------------------------------------
       
    33 
       
    34 """
       
    35 Class that implements the graphic representation of a function block
       
    36 """
       
    37 
       
    38 def TestConnectorName(name, block_type):
       
    39     return name in ["OUT", "MN", "MX"] or name.startswith("IN") and (block_type, name) != ("EXPT", "IN2")
       
    40 
       
    41 class FBD_Block(Graphic_Element):
       
    42     
       
    43     # Create a new block
       
    44     def __init__(self, parent, type, name, id = None, extension = 0, inputs = None, connectors = {}, executionControl = False, executionOrder = 0):
       
    45         Graphic_Element.__init__(self, parent)
       
    46         self.Type = None
       
    47         self.Description = None
       
    48         self.Extension = None
       
    49         self.ExecutionControl = False
       
    50         self.Id = id
       
    51         self.SetName(name)
       
    52         self.SetExecutionOrder(executionOrder)
       
    53         self.Inputs = []
       
    54         self.Outputs = []
       
    55         self.Colour = wx.BLACK
       
    56         self.Pen = MiterPen(wx.BLACK)
       
    57         self.SetType(type, extension, inputs, connectors, executionControl)
       
    58         self.Highlights = {}
       
    59     
       
    60     # Make a clone of this FBD_Block
       
    61     def Clone(self, parent, id = None, name = "", pos = None):
       
    62         if self.Name != "" and name == "":
       
    63             name = self.Name
       
    64         block = FBD_Block(parent, self.Type, name, id, self.Extension)
       
    65         block.SetSize(self.Size[0], self.Size[1])
       
    66         if pos is not None:
       
    67             block.SetPosition(pos.x, pos.y)
       
    68         else:
       
    69             block.SetPosition(self.Pos.x, self.Pos.y)
       
    70         block.Inputs = [input.Clone(block) for input in self.Inputs]
       
    71         block.Outputs = [output.Clone(block) for output in self.Outputs]
       
    72         return block
       
    73     
       
    74     def GetConnectorTranslation(self, element):
       
    75         return dict(zip(self.Inputs + self.Outputs, element.Inputs + element.Outputs))
       
    76     
       
    77     def Flush(self):
       
    78         for input in self.Inputs:
       
    79             input.Flush()
       
    80         self.Inputs = []
       
    81         for output in self.Outputs:
       
    82             output.Flush()
       
    83         self.Outputs = []
       
    84     
       
    85     # Returns the RedrawRect
       
    86     def GetRedrawRect(self, movex = 0, movey = 0):
       
    87         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
       
    88         if movex != 0 or movey != 0:
       
    89             for input in self.Inputs:
       
    90                 if input.IsConnected():
       
    91                     rect = rect.Union(input.GetConnectedRedrawRect(movex, movey))
       
    92             for output in self.Outputs:
       
    93                 if output.IsConnected():
       
    94                     rect = rect.Union(output.GetConnectedRedrawRect(movex, movey))
       
    95         return rect
       
    96     
       
    97     # Delete this block by calling the appropriate method
       
    98     def Delete(self):
       
    99         self.Parent.DeleteBlock(self)
       
   100     
       
   101     # Unconnect all inputs and outputs
       
   102     def Clean(self):
       
   103         for input in self.Inputs:
       
   104             input.UnConnect(delete = True)
       
   105         for output in self.Outputs:
       
   106             output.UnConnect(delete = True)
       
   107     
       
   108     # Refresh the size of text for name
       
   109     def RefreshNameSize(self):
       
   110         self.NameSize = self.Parent.GetTextExtent(self.Name)
       
   111     
       
   112     # Refresh the size of text for execution order
       
   113     def RefreshExecutionOrderSize(self):
       
   114         self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
       
   115     
       
   116     # Returns if the point given is in the bounding box
       
   117     def HitTest(self, pt, connectors=True):
       
   118         if self.Name != "":
       
   119             test_text = self.GetTextBoundingBox().InsideXY(pt.x, pt.y)
       
   120         else:
       
   121             test_text = False
       
   122         test_block = self.GetBlockBoundingBox(connectors).InsideXY(pt.x, pt.y)
       
   123         return test_text or test_block
       
   124     
       
   125     # Returns the bounding box of the name outside the block
       
   126     def GetTextBoundingBox(self):
       
   127         # Calculate the size of the name outside the block
       
   128         text_width, text_height = self.NameSize
       
   129         return wx.Rect(self.Pos.x + (self.Size[0] - text_width) / 2,
       
   130                        self.Pos.y - (text_height + 2),
       
   131                        text_width,
       
   132                        text_height)
       
   133     
       
   134     # Returns the bounding box of function block without name outside
       
   135     def GetBlockBoundingBox(self, connectors=True):
       
   136         bbx_x, bbx_y = self.Pos.x, self.Pos.y
       
   137         bbx_width, bbx_height = self.Size
       
   138         if connectors:
       
   139             bbx_x -= min(1, len(self.Inputs)) * CONNECTOR_SIZE
       
   140             bbx_width += (min(1, len(self.Inputs)) + min(1, len(self.Outputs))) * CONNECTOR_SIZE
       
   141         if self.ExecutionOrder != 0:
       
   142             bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
       
   143             bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
       
   144             bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
       
   145         return wx.Rect(bbx_x, bbx_y, bbx_width + 1, bbx_height + 1)
       
   146     
       
   147     # Refresh the block bounding box
       
   148     def RefreshBoundingBox(self):
       
   149         self.BoundingBox = self.GetBlockBoundingBox()
       
   150         if self.Name != "":
       
   151             self.BoundingBox.Union(self.GetTextBoundingBox())
       
   152     
       
   153     # Refresh the positions of the block connectors
       
   154     def RefreshConnectors(self):
       
   155         scaling = self.Parent.GetScaling()
       
   156         # Calculate the size for the connector lines
       
   157         lines = max(len(self.Inputs), len(self.Outputs))
       
   158         if lines > 0:
       
   159             linesize = max((self.Size[1] - BLOCK_LINE_SIZE) / lines, BLOCK_LINE_SIZE)
       
   160             # Update inputs and outputs positions
       
   161             position = BLOCK_LINE_SIZE + linesize / 2
       
   162             for i in xrange(lines):
       
   163                 if scaling is not None:
       
   164                     ypos = round_scaling(self.Pos.y + position, scaling[1]) - self.Pos.y
       
   165                 else:
       
   166                     ypos = position
       
   167                 if i < len(self.Inputs):
       
   168                     self.Inputs[i].SetPosition(wx.Point(0, ypos))
       
   169                 if i < len(self.Outputs):
       
   170                     self.Outputs[i].SetPosition(wx.Point(self.Size[0], ypos))
       
   171                 position += linesize
       
   172         self.RefreshConnected()
       
   173     
       
   174     # Refresh the positions of wires connected to inputs and outputs
       
   175     def RefreshConnected(self, exclude = []):
       
   176         for input in self.Inputs:
       
   177             input.MoveConnected(exclude)
       
   178         for output in self.Outputs:
       
   179             output.MoveConnected(exclude)
       
   180     
       
   181     # Returns the block connector that starts with the point given if it exists 
       
   182     def GetConnector(self, position, name = None):
       
   183         # if a name is given
       
   184         if name is not None:
       
   185             # Test each input and output connector
       
   186             #for input in self.Inputs:
       
   187             #    if name == input.GetName():
       
   188             #        return input
       
   189             for output in self.Outputs:
       
   190                 if name == output.GetName():
       
   191                     return output
       
   192         return self.FindNearestConnector(position, self.Inputs + self.Outputs)
       
   193         
       
   194     def GetInputTypes(self):
       
   195         return tuple([input.GetType(True) for input in self.Inputs if input.GetName() != "EN"])
       
   196     
       
   197     def SetOutputValues(self, values):
       
   198         for output in self.Outputs:
       
   199             output.SetValue(values.get(ouput.getName(), None))
       
   200     
       
   201     def GetConnectionResultType(self, connector, connectortype):
       
   202         if not TestConnectorName(connector.GetName(), self.Type):
       
   203             return connectortype
       
   204         resulttype = connectortype
       
   205         for input in self.Inputs:
       
   206             if input != connector and input.GetType(True) == "ANY" and TestConnectorName(input.GetName(), self.Type):
       
   207                 inputtype = input.GetConnectedType()
       
   208                 if resulttype is None or inputtype is not None and self.IsOfType(inputtype, resulttype):
       
   209                     resulttype = inputtype
       
   210         for output in self.Outputs:
       
   211             if output != connector and output.GetType(True) == "ANY" and TestConnectorName(output.GetName(), self.Type):
       
   212                 outputtype = output.GetConnectedType()
       
   213                 if resulttype is None or outputtype is not None and self.IsOfType(outputtype, resulttype):
       
   214                     resulttype = outputtype
       
   215         return resulttype
       
   216         
       
   217     # Returns all the block connectors
       
   218     def GetConnectors(self):
       
   219         return {"inputs" : self.Inputs, "outputs" : self.Outputs}
       
   220     
       
   221     # Test if point given is on one of the block connectors
       
   222     def TestConnector(self, pt, direction = None, exclude = True):
       
   223         # Test each input connector
       
   224         for input in self.Inputs:
       
   225             if input.TestPoint(pt, direction, exclude):
       
   226                 return input
       
   227         # Test each output connector
       
   228         for output in self.Outputs:
       
   229             if output.TestPoint(pt, direction, exclude):
       
   230                 return output
       
   231         return None
       
   232     
       
   233     # Changes the block type
       
   234     def SetType(self, type, extension, inputs = None, connectors = {}, executionControl = False):
       
   235         if type != self.Type or self.Extension != extension or executionControl != self.ExecutionControl: 
       
   236             if type != self.Type:
       
   237                 self.Type = type
       
   238                 self.TypeSize = self.Parent.GetTextExtent(self.Type)
       
   239             self.Extension = extension
       
   240             self.ExecutionControl = executionControl
       
   241             # Find the block definition from type given and create the corresponding
       
   242             # inputs and outputs
       
   243             blocktype = self.Parent.GetBlockType(type, inputs)
       
   244             if blocktype:
       
   245                 self.Colour = wx.BLACK
       
   246                 inputs = [input for input in blocktype["inputs"]]
       
   247                 outputs = [output for output in blocktype["outputs"]]
       
   248                 if blocktype["extensible"]:
       
   249                     start = int(inputs[-1][0].replace("IN", ""))
       
   250                     for i in xrange(self.Extension - len(blocktype["inputs"])):
       
   251                         start += 1
       
   252                         inputs.append(("IN%d"%start, inputs[-1][1], inputs[-1][2]))
       
   253                 comment = blocktype["comment"]
       
   254                 self.Description = _(comment) + blocktype.get("usage", "")
       
   255             else:
       
   256                 self.Colour = wx.RED
       
   257                 inputs = connectors.get("inputs", [])
       
   258                 outputs = connectors.get("outputs", [])
       
   259                 self.Description = None
       
   260             if self.ExecutionControl:
       
   261                 inputs.insert(0, ("EN","BOOL","none"))
       
   262                 outputs.insert(0, ("ENO","BOOL","none"))
       
   263             self.Pen = MiterPen(self.Colour)
       
   264             
       
   265             # Extract the inputs properties and create or modify the corresponding connector
       
   266             idx = 0
       
   267             for idx, (input_name, input_type, input_modifier) in enumerate(inputs):
       
   268                 if idx < len(self.Inputs):
       
   269                     connector = self.Inputs[idx]
       
   270                     connector.SetName(input_name)
       
   271                     connector.SetType(input_type)
       
   272                 else:
       
   273                     connector = Connector(self, input_name, input_type, wx.Point(0, 0), WEST, onlyone = True)
       
   274                     self.Inputs.append(connector)
       
   275                 if input_modifier == "negated":
       
   276                     connector.SetNegated(True)
       
   277                 elif input_modifier != "none":
       
   278                     connector.SetEdge(input_modifier)
       
   279             for i in xrange(idx + 1, len(self.Inputs)):
       
   280                 self.Inputs[i].UnConnect(delete = True)
       
   281             self.Inputs = self.Inputs[:idx + 1]
       
   282             
       
   283             # Extract the outputs properties and create or modify the corresponding connector
       
   284             idx = 0
       
   285             for idx, (output_name, output_type, output_modifier) in enumerate(outputs):
       
   286                 if idx < len(self.Outputs):
       
   287                     connector = self.Outputs[idx]
       
   288                     connector.SetName(output_name)
       
   289                     connector.SetType(output_type)
       
   290                 else:
       
   291                     connector = Connector(self, output_name, output_type, wx.Point(0, 0), EAST)
       
   292                     self.Outputs.append(connector)
       
   293                 if output_modifier == "negated":
       
   294                     connector.SetNegated(True)
       
   295                 elif output_modifier != "none":
       
   296                     connector.SetEdge(output_modifier)
       
   297             for i in xrange(idx + 1, len(self.Outputs)):
       
   298                 self.Outputs[i].UnConnect(delete = True)
       
   299             self.Outputs = self.Outputs[:idx + 1]
       
   300                 
       
   301             self.RefreshMinSize()
       
   302             self.RefreshConnectors()
       
   303             self.RefreshBoundingBox()
       
   304     
       
   305     # Returns the block type
       
   306     def GetType(self):
       
   307         return self.Type
       
   308     
       
   309     # Changes the block name
       
   310     def SetName(self, name):
       
   311         self.Name = name
       
   312         self.RefreshNameSize()
       
   313     
       
   314     # Returs the block name
       
   315     def GetName(self):
       
   316         return self.Name
       
   317     
       
   318     # Changes the extension name
       
   319     def SetExtension(self, extension):
       
   320         self.Extension = extension
       
   321     
       
   322     # Returs the extension name
       
   323     def GetExtension(self):
       
   324         return self.Extension
       
   325     
       
   326     # Changes the execution order
       
   327     def SetExecutionOrder(self, executionOrder):
       
   328         self.ExecutionOrder = executionOrder
       
   329         self.RefreshExecutionOrderSize()
       
   330     
       
   331     # Returs the execution order
       
   332     def GetExecutionOrder(self):
       
   333         return self.ExecutionOrder
       
   334     
       
   335     # Returs the execution order
       
   336     def GetExecutionControl(self):
       
   337         return self.ExecutionControl
       
   338     
       
   339     # Refresh the block minimum size
       
   340     def RefreshMinSize(self):
       
   341         # Calculate the inputs maximum width
       
   342         max_input = 0
       
   343         for input in self.Inputs:
       
   344             w, h = input.GetNameSize()
       
   345             max_input = max(max_input, w)
       
   346         # Calculate the outputs maximum width
       
   347         max_output = 0
       
   348         for output in self.Outputs:
       
   349             w, h = output.GetNameSize()
       
   350             max_output = max(max_output, w)
       
   351         width = max(self.TypeSize[0] + 10, max_input + max_output + 15)
       
   352         height = (max(len(self.Inputs), len(self.Outputs)) + 1) * BLOCK_LINE_SIZE
       
   353         self.MinSize = width, height
       
   354     
       
   355     # Returns the block minimum size
       
   356     def GetMinSize(self):
       
   357         return self.MinSize
       
   358     
       
   359     # Changes the negated property of the connector handled
       
   360     def SetConnectorNegated(self, negated):
       
   361         handle_type, handle = self.Handle
       
   362         if handle_type == HANDLE_CONNECTOR:
       
   363             handle.SetNegated(negated)
       
   364             self.RefreshModel(False)
       
   365     
       
   366     # Changes the edge property of the connector handled
       
   367     def SetConnectorEdge(self, edge):
       
   368         handle_type, handle = self.Handle
       
   369         if handle_type == HANDLE_CONNECTOR:
       
   370             handle.SetEdge(edge)
       
   371             self.RefreshModel(False)
       
   372     
       
   373 ##    # Method called when a Motion event have been generated
       
   374 ##    def OnMotion(self, event, dc, scaling):
       
   375 ##        if not event.Dragging():
       
   376 ##            pos = event.GetLogicalPosition(dc)
       
   377 ##            for input in self.Inputs:
       
   378 ##                rect = input.GetRedrawRect()
       
   379 ##                if rect.InsideXY(pos.x, pos.y):
       
   380 ##                    print "Find input"
       
   381 ##                    tip = wx.TipWindow(self.Parent, "Test")
       
   382 ##                    tip.SetBoundingRect(rect)
       
   383 ##        return Graphic_Element.OnMotion(self, event, dc, scaling)
       
   384     
       
   385     # Method called when a LeftDClick event have been generated
       
   386     def OnLeftDClick(self, event, dc, scaling):
       
   387         # Edit the block properties
       
   388         self.Parent.EditBlockContent(self)
       
   389     
       
   390     # Method called when a RightUp event have been generated
       
   391     def OnRightUp(self, event, dc, scaling):
       
   392         pos = GetScaledEventPosition(event, dc, scaling)
       
   393         # Popup the menu with special items for a block and a connector if one is handled
       
   394         connector = self.TestConnector(pos, exclude=False)
       
   395         if connector:
       
   396             self.Handle = (HANDLE_CONNECTOR, connector)
       
   397             self.Parent.PopupBlockMenu(connector)
       
   398         else:
       
   399             self.Parent.PopupBlockMenu()
       
   400     
       
   401     # Refreshes the block model
       
   402     def RefreshModel(self, move=True):
       
   403         self.Parent.RefreshBlockModel(self)
       
   404         # If block has moved, refresh the model of wires connected to outputs
       
   405         if move:
       
   406             for output in self.Outputs:
       
   407                 output.RefreshWires()
       
   408     
       
   409     def GetToolTipValue(self):
       
   410         return self.Description
       
   411     
       
   412     # Adds an highlight to the block
       
   413     def AddHighlight(self, infos, start, end ,highlight_type):
       
   414         if infos[0] in ["type", "name"] and start[0] == 0 and end[0] == 0:
       
   415             highlights = self.Highlights.setdefault(infos[0], [])
       
   416             AddHighlight(highlights, (start, end, highlight_type))
       
   417         elif infos[0] == "input" and infos[1] < len(self.Inputs):
       
   418             self.Inputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
       
   419         elif infos[0] == "output" and infos[1] < len(self.Outputs):
       
   420             self.Outputs[infos[1]].AddHighlight(infos[2:], start, end, highlight_type)
       
   421     
       
   422     # Removes an highlight from the block
       
   423     def RemoveHighlight(self, infos, start, end, highlight_type):
       
   424         if infos[0] in ["type", "name"]:
       
   425             highlights = self.Highlights.get(infos[0], [])
       
   426             if RemoveHighlight(highlights, (start, end, highlight_type)) and len(highlights) == 0:
       
   427                 self.Highlights.pop(infos[0])
       
   428         elif infos[0] == "input" and infos[1] < len(self.Inputs):
       
   429             self.Inputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
       
   430         elif infos[0] == "output" and infos[1] < len(self.Outputs):
       
   431             self.Outputs[infos[1]].RemoveHighlight(infos[2:], start, end, highlight_type)
       
   432             
       
   433     # Removes all the highlights of one particular type from the block
       
   434     def ClearHighlight(self, highlight_type=None):
       
   435         if highlight_type is None:
       
   436             self.Highlights = {}
       
   437         else:
       
   438             highlight_items = self.Highlights.items()
       
   439             for name, highlights in highlight_items:
       
   440                 highlights = ClearHighlights(highlights, highlight_type)
       
   441                 if len(highlights) == 0:
       
   442                     self.Highlights.pop(name)
       
   443         for input in self.Inputs:
       
   444             input.ClearHighlights(highlight_type)
       
   445         for output in self.Outputs:
       
   446             output.ClearHighlights(highlight_type)
       
   447     
       
   448     # Draws block
       
   449     def Draw(self, dc):
       
   450         Graphic_Element.Draw(self, dc)
       
   451         dc.SetPen(self.Pen)
       
   452         dc.SetBrush(wx.WHITE_BRUSH)
       
   453         dc.SetTextForeground(self.Colour)
       
   454         
       
   455         if getattr(dc, "printing", False):
       
   456             name_size = dc.GetTextExtent(self.Name)
       
   457             type_size = dc.GetTextExtent(self.Type)
       
   458             executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
       
   459         else:
       
   460             name_size = self.NameSize
       
   461             type_size = self.TypeSize
       
   462             executionorder_size = self.ExecutionOrderSize
       
   463             
       
   464         # Draw a rectangle with the block size
       
   465         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
   466         # Draw block name and block type
       
   467         name_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2,
       
   468                     self.Pos.y - (name_size[1] + 2))
       
   469         type_pos = (self.Pos.x + (self.Size[0] - type_size[0]) / 2,
       
   470                     self.Pos.y + 5)
       
   471         dc.DrawText(self.Name, name_pos[0], name_pos[1])
       
   472         dc.DrawText(self.Type, type_pos[0], type_pos[1])
       
   473         # Draw inputs and outputs connectors
       
   474         for input in self.Inputs:
       
   475             input.Draw(dc)
       
   476         for output in self.Outputs:
       
   477             output.Draw(dc)
       
   478         if self.ExecutionOrder != 0:
       
   479             # Draw block execution order
       
   480             dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
       
   481                     self.Pos.y + self.Size[1] + 2)
       
   482         
       
   483         if not getattr(dc, "printing", False):
       
   484             DrawHighlightedText(dc, self.Name, self.Highlights.get("name", []), name_pos[0], name_pos[1])
       
   485             DrawHighlightedText(dc, self.Type, self.Highlights.get("type", []), type_pos[0], type_pos[1])
       
   486         
       
   487 
       
   488 #-------------------------------------------------------------------------------
       
   489 #                        Function Block Diagram Variable
       
   490 #-------------------------------------------------------------------------------
       
   491 
       
   492 """
       
   493 Class that implements the graphic representation of a variable
       
   494 """
       
   495 
       
   496 class FBD_Variable(Graphic_Element):
       
   497 
       
   498     # Create a new variable
       
   499     def __init__(self, parent, type, name, value_type, id = None, executionOrder = 0):
       
   500         Graphic_Element.__init__(self, parent)
       
   501         self.Type = None
       
   502         self.ValueType = None
       
   503         self.Id = id
       
   504         self.SetName(name)
       
   505         self.SetExecutionOrder(executionOrder)
       
   506         self.Input = None
       
   507         self.Output = None
       
   508         self.SetType(type, value_type)
       
   509         self.Highlights = []
       
   510     
       
   511     # Make a clone of this FBD_Variable
       
   512     def Clone(self, parent, id = None, pos = None):
       
   513         variable = FBD_Variable(parent, self.Type, self.Name, self.ValueType, id)
       
   514         variable.SetSize(self.Size[0], self.Size[1])
       
   515         if pos is not None:
       
   516             variable.SetPosition(pos.x, pos.y)
       
   517         else:
       
   518             variable.SetPosition(self.Pos.x, self.Pos.y)
       
   519         if self.Input:
       
   520             variable.Input = self.Input.Clone(variable)
       
   521         if self.Output:
       
   522             variable.Output = self.Output.Clone(variable)
       
   523         return variable
       
   524     
       
   525     def GetConnectorTranslation(self, element):
       
   526         connectors = {}
       
   527         if self.Input is not None:
       
   528             connectors[self.Input] = element.Input
       
   529         if self.Output is not None:
       
   530             connectors[self.Output] = element.Output
       
   531         return connectors
       
   532     
       
   533     def Flush(self):
       
   534         if self.Input is not None:
       
   535             self.Input.Flush()
       
   536             self.Input = None
       
   537         if self.Output is not None:
       
   538             self.Output.Flush()
       
   539             self.Output = None
       
   540     
       
   541     # Returns the RedrawRect
       
   542     def GetRedrawRect(self, movex = 0, movey = 0):
       
   543         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
       
   544         if movex != 0 or movey != 0:
       
   545             if self.Input and self.Input.IsConnected():
       
   546                 rect = rect.Union(self.Input.GetConnectedRedrawRect(movex, movey))
       
   547             if self.Output and self.Output.IsConnected():
       
   548                 rect = rect.Union(self.Output.GetConnectedRedrawRect(movex, movey))
       
   549         return rect
       
   550     
       
   551     # Unconnect connector
       
   552     def Clean(self):
       
   553         if self.Input:
       
   554             self.Input.UnConnect(delete = True)
       
   555         if self.Output:
       
   556             self.Output.UnConnect(delete = True)
       
   557     
       
   558     # Delete this variable by calling the appropriate method
       
   559     def Delete(self):
       
   560         self.Parent.DeleteVariable(self)
       
   561     
       
   562     # Refresh the size of text for name
       
   563     def RefreshNameSize(self):
       
   564         self.NameSize = self.Parent.GetTextExtent(self.Name)
       
   565     
       
   566     # Refresh the size of text for execution order
       
   567     def RefreshExecutionOrderSize(self):
       
   568         self.ExecutionOrderSize = self.Parent.GetTextExtent(str(self.ExecutionOrder))
       
   569     
       
   570     # Refresh the variable bounding box
       
   571     def RefreshBoundingBox(self):
       
   572         if self.Type in (OUTPUT, INOUT):
       
   573             bbx_x = self.Pos.x - CONNECTOR_SIZE
       
   574         else:
       
   575             bbx_x = self.Pos.x
       
   576         if self.Type == INOUT:
       
   577             bbx_width = self.Size[0] + 2 * CONNECTOR_SIZE
       
   578         else:
       
   579             bbx_width = self.Size[0] + CONNECTOR_SIZE
       
   580         bbx_x = min(bbx_x, self.Pos.x + (self.Size[0] - self.NameSize[0]) / 2)
       
   581         bbx_width = max(bbx_width, self.NameSize[0])
       
   582         bbx_height = self.Size[1]
       
   583         if self.ExecutionOrder != 0:
       
   584             bbx_x = min(bbx_x, self.Pos.x + self.Size[0] - self.ExecutionOrderSize[0])
       
   585             bbx_width = max(bbx_width, bbx_width + self.Pos.x + self.ExecutionOrderSize[0] - bbx_x - self.Size[0])
       
   586             bbx_height = bbx_height + (self.ExecutionOrderSize[1] + 2)
       
   587         self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width + 1, bbx_height + 1)
       
   588     
       
   589     # Refresh the position of the variable connector
       
   590     def RefreshConnectors(self):
       
   591         scaling = self.Parent.GetScaling()
       
   592         if scaling is not None:
       
   593             position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
       
   594         else:
       
   595             position = self.Size[1] / 2
       
   596         if self.Input:
       
   597             self.Input.SetPosition(wx.Point(0, position))
       
   598         if self.Output:
       
   599             self.Output.SetPosition(wx.Point(self.Size[0], position))
       
   600         self.RefreshConnected()
       
   601     
       
   602     # Refresh the position of wires connected to connector
       
   603     def RefreshConnected(self, exclude = []):
       
   604         if self.Input:
       
   605             self.Input.MoveConnected(exclude)
       
   606         if self.Output:
       
   607             self.Output.MoveConnected(exclude)
       
   608         
       
   609     # Test if point given is on the variable connector
       
   610     def TestConnector(self, pt, direction = None, exclude=True):
       
   611         if self.Input and self.Input.TestPoint(pt, direction, exclude):
       
   612             return self.Input
       
   613         if self.Output and self.Output.TestPoint(pt, direction, exclude):
       
   614             return self.Output
       
   615         return None
       
   616     
       
   617     # Returns the block connector that starts with the point given if it exists 
       
   618     def GetConnector(self, position, name = None):
       
   619         # if a name is given
       
   620         if name is not None:
       
   621             # Test input and output connector if they exists
       
   622             #if self.Input and name == self.Input.GetName():
       
   623             #    return self.Input
       
   624             if self.Output and name == self.Output.GetName():
       
   625                 return self.Output
       
   626         connectors = []
       
   627         # Test input connector if it exists
       
   628         if self.Input:
       
   629             connectors.append(self.Input)
       
   630         # Test output connector if it exists
       
   631         if self.Output:
       
   632             connectors.append(self.Output)
       
   633         return self.FindNearestConnector(position, connectors)
       
   634     
       
   635     # Returns all the block connectors 
       
   636     def GetConnectors(self):
       
   637         connectors = {"inputs": [], "outputs": []}
       
   638         if self.Input:
       
   639             connectors["inputs"].append(self.Input)
       
   640         if self.Output:
       
   641             connectors["outputs"].append(self.Output)
       
   642         return connectors
       
   643     
       
   644     # Changes the negated property of the variable connector if handled
       
   645     def SetConnectorNegated(self, negated):
       
   646         handle_type, handle = self.Handle
       
   647         if handle_type == HANDLE_CONNECTOR:
       
   648             handle.SetNegated(negated)
       
   649             self.RefreshModel(False)
       
   650     
       
   651     # Changes the variable type
       
   652     def SetType(self, type, value_type):
       
   653         if type != self.Type:
       
   654             self.Type = type
       
   655             self.Clean()
       
   656             self.Input = None
       
   657             self.Output = None
       
   658             # Create an input or output connector according to variable type
       
   659             if self.Type != INPUT:
       
   660                 self.Input = Connector(self, "", value_type, wx.Point(0, 0), WEST, onlyone = True)
       
   661             if self.Type != OUTPUT:
       
   662                 self.Output = Connector(self, "", value_type, wx.Point(0, 0), EAST)
       
   663             self.RefreshConnectors()
       
   664         elif value_type != self.ValueType:
       
   665             if self.Input:
       
   666                 self.Input.SetType(value_type)
       
   667             if self.Output:
       
   668                 self.Output.SetType(value_type)            
       
   669         self.RefreshConnectors()
       
   670     
       
   671     # Returns the variable type
       
   672     def GetType(self):
       
   673         return self.Type
       
   674     
       
   675     # Changes the variable name
       
   676     def SetName(self, name):
       
   677         self.Name = name
       
   678         self.RefreshNameSize()
       
   679     
       
   680     # Returns the variable name
       
   681     def GetName(self):
       
   682         return self.Name
       
   683     
       
   684     # Changes the execution order
       
   685     def SetExecutionOrder(self, executionOrder):
       
   686         self.ExecutionOrder = executionOrder
       
   687         self.RefreshExecutionOrderSize()
       
   688     
       
   689     # Returs the execution order
       
   690     def GetExecutionOrder(self):
       
   691         return self.ExecutionOrder
       
   692     
       
   693     # Changes the element size
       
   694     def SetSize(self, width, height):
       
   695         scaling = self.Parent.GetScaling()
       
   696         min_width, min_height = self.GetMinSize()
       
   697         if width < min_width:
       
   698             if self.Type == INPUT:
       
   699                 posx = max(0, self.Pos.x + width - min_width)
       
   700                 if scaling is not None:
       
   701                     posx = round_scaling(posx, scaling[0])
       
   702                 self.Pos.x = posx
       
   703             elif self.Type == OUTPUT:
       
   704                 posx = max(0, self.Pos.x + (width - min_width) / 2)
       
   705                 if scaling is not None:
       
   706                     posx = round_scaling(posx, scaling[0])
       
   707                 self.Pos.x = posx
       
   708             width = min_width
       
   709             if scaling is not None:
       
   710                 width = round_scaling(width, scaling[0], 1)
       
   711         if height < min_height:
       
   712             posy = max(0, self.Pos.y + (height - min_height) / 2)
       
   713             if scaling is not None:
       
   714                 posy = round_scaling(posy, scaling[1])
       
   715             self.Pos.y = posy
       
   716             height = min_height
       
   717             if scaling is not None:
       
   718                 height = round_scaling(height, scaling[1], 1)
       
   719         Graphic_Element.SetSize(self, width, height)
       
   720     
       
   721     # Returns the variable minimum size
       
   722     def GetMinSize(self):
       
   723         return self.NameSize[0] + 10, self.NameSize[1] + 10
       
   724     
       
   725     # Method called when a LeftDClick event have been generated
       
   726     def OnLeftDClick(self, event, dc, scaling):
       
   727         # Edit the variable properties
       
   728         self.Parent.EditVariableContent(self)
       
   729     
       
   730     # Method called when a RightUp event have been generated
       
   731     def OnRightUp(self, event, dc, scaling):
       
   732         self.Parent.PopupDefaultMenu()
       
   733     
       
   734     # Refreshes the variable model
       
   735     def RefreshModel(self, move=True):
       
   736         self.Parent.RefreshVariableModel(self)
       
   737         # If variable has moved and variable is not of type OUTPUT, refresh the model
       
   738         # of wires connected to output connector
       
   739         if move and self.Type != OUTPUT:
       
   740             if self.Output:
       
   741                 self.Output.RefreshWires()
       
   742     
       
   743     # Adds an highlight to the variable
       
   744     def AddHighlight(self, infos, start, end, highlight_type):
       
   745         if infos[0] == "expression" and start[0] == 0 and end[0] == 0:
       
   746             AddHighlight(self.Highlights, (start, end, highlight_type))
       
   747     
       
   748     # Removes an highlight from the variable
       
   749     def RemoveHighlight(self, infos, start, end, highlight_type):
       
   750         if infos[0] == "expression":
       
   751             RemoveHighlight(self.Highlights, (start, end, highlight_type))
       
   752     
       
   753     # Removes all the highlights of one particular type from the variable
       
   754     def ClearHighlight(self, highlight_type=None):
       
   755         ClearHighlights(self.Highlights, highlight_type)
       
   756     
       
   757     # Draws variable
       
   758     def Draw(self, dc):
       
   759         Graphic_Element.Draw(self, dc)
       
   760         dc.SetPen(MiterPen(wx.BLACK))
       
   761         dc.SetBrush(wx.WHITE_BRUSH)
       
   762         
       
   763         if getattr(dc, "printing", False):
       
   764             name_size = dc.GetTextExtent(self.Name)
       
   765             executionorder_size = dc.GetTextExtent(str(self.ExecutionOrder))
       
   766         else:
       
   767             name_size = self.NameSize
       
   768             executionorder_size = self.ExecutionOrderSize
       
   769         
       
   770         text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2, 
       
   771                     self.Pos.y + (self.Size[1] - name_size[1]) / 2)
       
   772         # Draw a rectangle with the variable size
       
   773         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
   774         # Draw variable name
       
   775         dc.DrawText(self.Name, text_pos[0], text_pos[1])
       
   776         # Draw connectors
       
   777         if self.Input:
       
   778             self.Input.Draw(dc)
       
   779         if self.Output:    
       
   780             self.Output.Draw(dc)
       
   781         if self.ExecutionOrder != 0:
       
   782             # Draw variable execution order
       
   783             dc.DrawText(str(self.ExecutionOrder), self.Pos.x + self.Size[0] - executionorder_size[0],
       
   784                     self.Pos.y + self.Size[1] + 2)
       
   785         if not getattr(dc, "printing", False):
       
   786             DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
       
   787             
       
   788 #-------------------------------------------------------------------------------
       
   789 #                        Function Block Diagram Connector
       
   790 #-------------------------------------------------------------------------------
       
   791 
       
   792 """
       
   793 Class that implements the graphic representation of a connection
       
   794 """
       
   795 
       
   796 class FBD_Connector(Graphic_Element):
       
   797 
       
   798     # Create a new connection
       
   799     def __init__(self, parent, type, name, id = None):
       
   800         Graphic_Element.__init__(self, parent)
       
   801         self.Type = type
       
   802         self.Id = id
       
   803         self.SetName(name)
       
   804         self.Pos = wx.Point(0, 0)
       
   805         self.Size = wx.Size(0, 0)
       
   806         self.Highlights = []
       
   807         # Create an input or output connector according to connection type
       
   808         if self.Type == CONNECTOR:
       
   809             self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
       
   810         else:
       
   811             self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
       
   812         self.RefreshConnectors()
       
   813         self.RefreshNameSize()
       
   814     
       
   815     def Flush(self):
       
   816         if self.Connector:
       
   817             self.Connector.Flush()
       
   818             self.Connector = None
       
   819     
       
   820     # Returns the RedrawRect
       
   821     def GetRedrawRect(self, movex = 0, movey = 0):
       
   822         rect = Graphic_Element.GetRedrawRect(self, movex, movey)
       
   823         if movex != 0 or movey != 0:
       
   824             if self.Connector and self.Connector.IsConnected():
       
   825                 rect = rect.Union(self.Connector.GetConnectedRedrawRect(movex, movey))
       
   826         return rect
       
   827     
       
   828     # Make a clone of this FBD_Connector
       
   829     def Clone(self, parent, id = None, pos = None):
       
   830         connection = FBD_Connector(parent, self.Type, self.Name, id)
       
   831         connection.SetSize(self.Size[0], self.Size[1])
       
   832         if pos is not None:
       
   833             connection.SetPosition(pos.x, pos.y)
       
   834         else:
       
   835             connection.SetPosition(self.Pos.x, self.Pos.y)
       
   836         connection.Connector = self.Connector.Clone(connection)
       
   837         return connection
       
   838     
       
   839     def GetConnectorTranslation(self, element):
       
   840         return {self.Connector : element.Connector}
       
   841 
       
   842     # Unconnect connector
       
   843     def Clean(self):
       
   844         if self.Connector:
       
   845             self.Connector.UnConnect(delete = True)
       
   846     
       
   847     # Delete this connection by calling the appropriate method
       
   848     def Delete(self):
       
   849         self.Parent.DeleteConnection(self)
       
   850     
       
   851     # Refresh the size of text for name
       
   852     def RefreshNameSize(self):
       
   853         self.NameSize = self.Parent.GetTextExtent(self.Name)
       
   854     
       
   855     # Refresh the connection bounding box
       
   856     def RefreshBoundingBox(self):
       
   857         if self.Type == CONNECTOR:
       
   858             bbx_x = self.Pos.x - CONNECTOR_SIZE
       
   859         else:
       
   860             bbx_x = self.Pos.x
       
   861         bbx_width = self.Size[0] + CONNECTOR_SIZE
       
   862         self.BoundingBox = wx.Rect(bbx_x, self.Pos.y, bbx_width, self.Size[1])
       
   863     
       
   864     # Refresh the position of the connection connector
       
   865     def RefreshConnectors(self):
       
   866         scaling = self.Parent.GetScaling()
       
   867         if scaling is not None:
       
   868             position = round_scaling(self.Pos.y + self.Size[1] / 2, scaling[1]) - self.Pos.y
       
   869         else:
       
   870             position = self.Size[1] / 2
       
   871         if self.Type == CONNECTOR:
       
   872             self.Connector.SetPosition(wx.Point(0, position))
       
   873         else:
       
   874             self.Connector.SetPosition(wx.Point(self.Size[0], position))
       
   875         self.RefreshConnected()
       
   876     
       
   877     # Refresh the position of wires connected to connector
       
   878     def RefreshConnected(self, exclude = []):
       
   879         if self.Connector:
       
   880             self.Connector.MoveConnected(exclude)
       
   881     
       
   882     # Test if point given is on the connection connector
       
   883     def TestConnector(self, pt, direction = None, exclude=True):
       
   884         if self.Connector and self.Connector.TestPoint(pt, direction, exclude):
       
   885             return self.Connector
       
   886         return None
       
   887     
       
   888     # Returns the connection connector
       
   889     def GetConnector(self, position = None, name = None):
       
   890         return self.Connector
       
   891     
       
   892         # Returns all the block connectors 
       
   893     def GetConnectors(self):
       
   894         connectors = {"inputs": [], "outputs": []}
       
   895         if self.Type == CONNECTOR:
       
   896             connectors["inputs"].append(self.Connector)
       
   897         else:
       
   898             connectors["outputs"].append(self.Connector)
       
   899         return connectors
       
   900     
       
   901     # Changes the variable type
       
   902     def SetType(self, type):
       
   903         if type != self.Type:
       
   904             self.Type = type
       
   905             self.Clean()
       
   906             # Create an input or output connector according to connection type
       
   907             if self.Type == CONNECTOR:
       
   908                 self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), WEST, onlyone = True)
       
   909             else:
       
   910                 self.Connector = Connector(self, "", "ANY", wx.Point(0, 0), EAST)
       
   911             self.RefreshConnectors()
       
   912     
       
   913     # Returns the connection type
       
   914     def GetType(self):
       
   915         return self.Type
       
   916     
       
   917     def GetConnectionResultType(self, connector, connectortype):
       
   918         if self.Type == CONTINUATION:
       
   919             connector = self.Parent.GetConnectorByName(self.Name)
       
   920             if connector is not None:
       
   921                 return connector.Connector.GetConnectedType()
       
   922         return connectortype
       
   923     
       
   924     # Changes the connection name
       
   925     def SetName(self, name):
       
   926         self.Name = name
       
   927         self.RefreshNameSize()
       
   928         
       
   929     # Returns the connection name
       
   930     def GetName(self):
       
   931         return self.Name
       
   932     
       
   933     # Changes the element size
       
   934     def SetSize(self, width, height):
       
   935         scaling = self.Parent.GetScaling()
       
   936         min_width, min_height = self.GetMinSize()
       
   937         if width < min_width:
       
   938             if self.Type == CONTINUATION:
       
   939                 posx = max(0, self.Pos.x + width - min_width)
       
   940                 if scaling is not None:
       
   941                     posx = round_scaling(posx, scaling[0])
       
   942                 self.Pos.x = posx
       
   943             width = min_width
       
   944             if scaling is not None:
       
   945                 width = round_scaling(width, scaling[0], 1)
       
   946         if height < min_height:
       
   947             posy = max(0, self.Pos.y + (height - min_height) / 2)
       
   948             if scaling is not None:
       
   949                 posy = round_scaling(posy, scaling[1])
       
   950             self.Pos.y = posy
       
   951             height = min_height
       
   952             if scaling is not None:
       
   953                 height = round_scaling(height, scaling[1], 1)
       
   954         Graphic_Element.SetSize(self, width, height)
       
   955     
       
   956     # Returns the connection minimum size
       
   957     def GetMinSize(self):
       
   958         text_width, text_height = self.NameSize
       
   959         if text_height % 2 == 1:
       
   960             text_height += 1
       
   961         return text_width + text_height + 20, text_height + 10
       
   962     
       
   963     # Method called when a LeftDClick event have been generated
       
   964     def OnLeftDClick(self, event, dc, scaling):
       
   965         # Edit the connection properties
       
   966         self.Parent.EditConnectionContent(self)
       
   967     
       
   968     # Method called when a RightUp event have been generated
       
   969     def OnRightUp(self, event, dc, scaling):
       
   970         # Popup the default menu
       
   971         self.Parent.PopupDefaultMenu()
       
   972     
       
   973     # Refreshes the connection model
       
   974     def RefreshModel(self, move=True):
       
   975         self.Parent.RefreshConnectionModel(self)
       
   976         # If connection has moved and connection is of type CONTINUATION, refresh
       
   977         # the model of wires connected to connector
       
   978         if move and self.Type == CONTINUATION:
       
   979             if self.Connector:
       
   980                 self.Connector.RefreshWires()
       
   981     
       
   982     # Adds an highlight to the connection
       
   983     def AddHighlight(self, infos, start, end, highlight_type):
       
   984         if infos[0] == "name" and start[0] == 0 and end[0] == 0:
       
   985             AddHighlight(self.Highlights, (start, end, highlight_type))
       
   986     
       
   987     # Removes an highlight from the connection
       
   988     def RemoveHighlight(self, infos, start, end, highlight_type):
       
   989         if infos[0] == "name":
       
   990             RemoveHighlight(self.Highlights, (start, end, highlight_type))
       
   991     
       
   992     # Removes all the highlights of one particular type from the connection
       
   993     def ClearHighlight(self, highlight_type=None):
       
   994         ClearHighlights(self.Highlights, highlight_type)
       
   995     
       
   996     # Draws connection
       
   997     def Draw(self, dc):
       
   998         Graphic_Element.Draw(self, dc)
       
   999         dc.SetPen(MiterPen(wx.BLACK))
       
  1000         dc.SetBrush(wx.WHITE_BRUSH)
       
  1001         
       
  1002         if getattr(dc, "printing", False):
       
  1003             name_size = dc.GetTextExtent(self.Name)
       
  1004         else:
       
  1005             name_size = self.NameSize
       
  1006         
       
  1007         # Draw a rectangle with the connection size with arrows inside
       
  1008         dc.DrawRectangle(self.Pos.x, self.Pos.y, self.Size[0] + 1, self.Size[1] + 1)
       
  1009         arrowsize = min(self.Size[1] / 2, (self.Size[0] - name_size[0] - 10) / 2)
       
  1010         dc.DrawLine(self.Pos.x, self.Pos.y, self.Pos.x + arrowsize, 
       
  1011                 self.Pos.y + self.Size[1] / 2)
       
  1012         dc.DrawLine(self.Pos.x + arrowsize, self.Pos.y + self.Size[1] / 2, 
       
  1013                 self.Pos.x, self.Pos.y + self.Size[1])
       
  1014         dc.DrawLine(self.Pos.x + self.Size[0] - arrowsize, self.Pos.y, 
       
  1015                 self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2)
       
  1016         dc.DrawLine(self.Pos.x + self.Size[0], self.Pos.y + self.Size[1] / 2, 
       
  1017                 self.Pos.x + self.Size[0] - arrowsize, self.Pos.y + self.Size[1])
       
  1018         # Draw connection name
       
  1019         text_pos = (self.Pos.x + (self.Size[0] - name_size[0]) / 2, 
       
  1020                     self.Pos.y + (self.Size[1] - name_size[1]) / 2)
       
  1021         dc.DrawText(self.Name, text_pos[0], text_pos[1])
       
  1022         # Draw connector
       
  1023         if self.Connector:
       
  1024             self.Connector.Draw(dc)
       
  1025         
       
  1026         if not getattr(dc, "printing", False):
       
  1027             DrawHighlightedText(dc, self.Name, self.Highlights, text_pos[0], text_pos[1])
       
  1028