Changing Cut/Copy/Paste procedures for using wx.Clipboard with xml definition of copied elements
--- a/PLCControler.py Fri Jul 24 10:47:35 2009 +0200
+++ b/PLCControler.py Fri Jul 24 11:07:33 2009 +0200
@@ -1580,6 +1580,148 @@
return self.GetProjectPouVariables(words[1], debug)
return []
+ def GetEditedElementCopy(self, tagname, debug = False):
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ name = element.__class__.__name__
+ return element.generateXMLText(name.split("_")[-1], 0)
+ return ""
+
+ def GetEditedElementInstancesCopy(self, tagname, blocks_id = None, wires = None, debug = False):
+ element = self.GetEditedElement(tagname, debug)
+ text = ""
+ if element is not None:
+ wires = dict([(wire, True) for wire in wires if wire[0] in blocks_id and wire[1] in blocks_id])
+ for id in blocks_id:
+ instance = element.getinstance(id)
+ if instance is not None:
+ instance_copy = self.Copy(instance)
+ instance_copy.filterConnections(wires)
+ name = instance_copy.__class__.__name__
+ text += instance_copy.generateXMLText(name.split("_")[-1], 0)
+ return text
+
+ def GenerateNewName(self, tagname, name, format, exclude={}, debug=False):
+ names = exclude.copy()
+ names.update(dict([(varname.upper(), True) for varname in self.GetEditedElementVariables(tagname, debug)]))
+ element = self.GetEditedElement(tagname, debug)
+ if element is not None:
+ for instance in element.getinstances():
+ if isinstance(instance, (plcopen.sfcObjects_step, plcopen.commonObjects_connector, plcopen.commonObjects_continuation)):
+ names[instance.getname()] = True
+ i = 1
+ while names.get(name.upper(), False):
+ name = (format%i)
+ i += 1
+ return name
+
+ CheckPasteCompatibility = {"SFC": lambda name: True,
+ "LD": lambda name: not name.startswith("sfcObjects"),
+ "FBD": lambda name: name.startswith("fbdObjects") or name.startswith("commonObjects")}
+
+ def PasteEditedElementInstances(self, tagname, text, new_pos, middle=False, debug=False):
+ element = self.GetEditedElement(tagname, debug)
+ element_name, element_type = self.GetEditedElementType(tagname, debug)
+ if element is not None:
+ bodytype = element.getbodyType()
+
+ # Get edited element type scaling
+ scaling = None
+ project = self.GetProject(debug)
+ if project is not None:
+ properties = project.getcontentHeader()
+ scaling = properties["scaling"][bodytype]
+
+ # Get ids already by all the instances in edited element
+ used_id = dict([(instance.getlocalId(), True) for instance in element.getinstances()])
+ new_id = {}
+
+ text = "<paste>%s</paste>"%text
+
+ try:
+ tree = minidom.parseString(text)
+ except:
+ return _("Invalid plcopen element(s)!!!")
+ instances = []
+ exclude = {}
+ for root in tree.childNodes:
+ if root.nodeType == tree.ELEMENT_NODE and root.nodeName == "paste":
+ for child in root.childNodes:
+ if child.nodeType == tree.ELEMENT_NODE:
+ classname = plcopen.ElementNameToClass[child.nodeName]
+ if not self.CheckPasteCompatibility[bodytype](classname):
+ return _("\"%s\" element can't be paste here!!!")%child.nodeName
+ classobj = getattr(plcopen, classname, None)
+ if classobj is not None:
+ instance = classobj()
+ instance.loadXMLTree(child)
+ if child.nodeName == "block":
+ blockname = instance.getinstanceName()
+ if blockname is not None:
+ blocktype = instance.gettypeName()
+ if element_type == "function":
+ return _("FunctionBlock \"%s\" can't be paste in a Function!!!")%blocktype
+ blockname = self.GenerateNewName(tagname, blockname, "Block%d", debug=debug)
+ exclude[blockname] = True
+ instance.setinstanceName(blockname)
+ self.AddEditedElementPouVar(tagname, blocktype, blockname)
+ elif child.nodeName == "step":
+ stepname = self.GenerateNewName(tagname, instance.getname(), "Step%d", exclude, debug)
+ exclude[stepname] = True
+ instance.setname(stepname)
+ localid = instance.getlocalId()
+ if not used_id.has_key(localid):
+ new_id[localid] = True
+ instances.append((child.nodeName, instance))
+
+ if len(instances) == 0:
+ return _("Invalid plcopen element(s)!!!")
+
+ idx = 1
+ translate_id = {}
+ bbox = plcopen.rect()
+ for name, instance in instances:
+ localId = instance.getlocalId()
+ bbox.union(instance.getBoundingBox())
+ if used_id.has_key(localId):
+ while used_id.has_key(idx) or new_id.has_key(idx):
+ idx += 1
+ new_id[idx] = True
+ instance.setlocalId(idx)
+ translate_id[localId] = idx
+
+ x, y, width, height = bbox.bounding_box()
+ if middle:
+ new_pos[0] -= width / 2
+ new_pos[1] -= height / 2
+ else:
+ new_pos = map(lambda x: x + 30, new_pos)
+ if scaling[0] != 0 and scaling[1] != 0:
+ min_pos = map(lambda x: 30 / x, scaling)
+ minx = round(min_pos[0])
+ if int(min_pos[0]) == round(min_pos[0]):
+ minx += 1
+ miny = round(min_pos[1])
+ if int(min_pos[1]) == round(min_pos[1]):
+ miny += 1
+ minx *= scaling[0]
+ miny *= scaling[1]
+ new_pos = (max(minx, round(new_pos[0] / scaling[0]) * scaling[0]),
+ max(miny, round(new_pos[1] / scaling[1]) * scaling[1]))
+ else:
+ new_pos = (max(30, new_pos[0]), max(30, new_pos[1]))
+ diff = (new_pos[0] - x, new_pos[1] - y)
+
+ connections = {}
+ for name, instance in instances:
+ connections.update(instance.updateConnectionsId(translate_id))
+ if getattr(instance, "setexecutionOrderId", None) is not None:
+ instance.setexecutionOrderId(0)
+ instance.translate(*diff)
+ element.addinstance(name, instance)
+
+ return new_id, connections
+
# Return the current pou editing informations
def GetEditedElementInstanceInfos(self, tagname, id = None, exclude = [], debug = False):
infos = {}
--- a/PLCOpenEditor.py Fri Jul 24 10:47:35 2009 +0200
+++ b/PLCOpenEditor.py Fri Jul 24 11:07:33 2009 +0200
@@ -177,8 +177,7 @@
class PLCOpenEditor(wx.Frame):
- CopyBuffer = None
-
+ # Compatibility function for wx versions < 2.6
if wx.VERSION < (2, 6, 0):
def Bind(self, event, function, id = None):
if id is not None:
@@ -689,10 +688,21 @@
event.Skip()
def GetCopyBuffer(self):
- return PLCOpenEditor.CopyBuffer
-
- def SetCopyBuffer(self, element):
- PLCOpenEditor.CopyBuffer = element
+ data = None
+ if wx.TheClipboard.Open():
+ dataobj = wx.TextDataObject()
+ if wx.TheClipboard.GetData(dataobj):
+ data = dataobj.GetText()
+ wx.TheClipboard.Close()
+ return data
+
+ def SetCopyBuffer(self, text):
+ if wx.TheClipboard.Open():
+ data = wx.TextDataObject()
+ data.SetText(text)
+ wx.TheClipboard.SetData(data)
+ wx.TheClipboard.Flush()
+ wx.TheClipboard.Close()
self.RefreshEditMenu()
def GetDrawingMode(self):
@@ -1071,7 +1081,7 @@
if self.TabsOpened.GetPageCount() > 0:
self.EditMenu.Enable(wx.ID_CUT, True)
self.EditMenu.Enable(wx.ID_COPY, True)
- if self.CopyBuffer is not None:
+ if self.GetCopyBuffer() is not None:
self.EditMenu.Enable(wx.ID_PASTE, True)
else:
self.EditMenu.Enable(wx.ID_PASTE, False)
--- a/Viewer.py Fri Jul 24 10:47:35 2009 +0200
+++ b/Viewer.py Fri Jul 24 11:07:33 2009 +0200
@@ -2564,7 +2564,9 @@
def Cut(self):
if not self.Debug and (self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement) or isinstance(self.SelectedElement, Graphic_Group)):
- self.ParentWindow.SetCopyBuffer(self.SelectedElement.Clone(self))
+ blocks, wires = self.SelectedElement.GetDefinition()
+ text = self.Controler.GetEditedElementInstancesCopy(self.TagName, blocks, wires, self.Debug)
+ self.ParentWindow.SetCopyBuffer(text)
rect = self.SelectedElement.GetRedrawRect(1, 1)
self.SelectedElement.Delete()
self.SelectedElement = None
@@ -2576,42 +2578,30 @@
def Copy(self):
if not self.Debug and (self.IsBlock(self.SelectedElement) or self.IsComment(self.SelectedElement) or isinstance(self.SelectedElement, Graphic_Group)):
- self.ParentWindow.SetCopyBuffer(self.SelectedElement.Clone(self))
+ blocks, wires = self.SelectedElement.GetDefinition()
+ text = self.Controler.GetEditedElementInstancesCopy(self.TagName, blocks, wires, self.Debug)
+ self.ParentWindow.SetCopyBuffer(text)
def Paste(self):
- element = self.ParentWindow.GetCopyBuffer()
- if not self.Debug and element is not None and self.CanAddElement(element):
+ if not self.Debug:
+ element = self.ParentWindow.GetCopyBuffer()
mouse_pos = self.ScreenToClient(wx.GetMousePosition())
- if wx.Rect(0, 0, *self.GetClientSize()).InsideXY(mouse_pos.x, mouse_pos.y):
+ middle = wx.Rect(0, 0, *self.GetClientSize()).InsideXY(mouse_pos.x, mouse_pos.y)
+ if middle:
x, y = self.CalcUnscrolledPosition(mouse_pos.x, mouse_pos.y)
- block_size = element.GetSize()
- x = int(float(x) / self.ViewScale[0]) - block_size[0] / 2
- y = int(float(y) / self.ViewScale[1]) - block_size[1] / 2
else:
x, y = self.CalcUnscrolledPosition(0, 0)
- x = int(x / self.ViewScale[0]) + 30
- y = int(y / self.ViewScale[1]) + 30
- if self.Scaling is not None:
- new_pos = wx.Point(max(round_scaling(30, self.Scaling[0], 1),
- round_scaling(x, self.Scaling[0])),
- max(round_scaling(30, self.Scaling[1], 1),
- round_scaling(y, self.Scaling[1])))
+ new_pos = [int(x / self.ViewScale[0]), int(y / self.ViewScale[1])]
+ result = self.Controler.PasteEditedElementInstances(self.TagName, element, new_pos, middle, self.Debug)
+ if not isinstance(result, (StringType, UnicodeType)):
+ self.RefreshBuffer()
+ self.RefreshView(result)
+ self.ParentWindow.RefreshVariablePanel(self.TagName)
+ self.ParentWindow.RefreshInstancesTree()
else:
- new_pos = wx.Point(max(30, x), max(30, y))
- block = self.CopyBlock(element, new_pos)
- self.RefreshVisibleElements()
- if self.SelectedElement is not None:
- self.SelectedElement.SetSelected(False)
- self.SelectedElement = block
- self.SelectedElement.SetSelected(True)
- self.RefreshBuffer()
- self.RefreshScrollBars()
- self.ParentWindow.RefreshVariablePanel(self.TagName)
- self.ParentWindow.RefreshInstancesTree()
- else:
- message = wx.MessageDialog(self, "You can't paste the element in buffer here!", "Error", wx.OK|wx.ICON_ERROR)
- message.ShowModal()
- message.Destroy()
+ message = wx.MessageDialog(self, result, "Error", wx.OK|wx.ICON_ERROR)
+ message.ShowModal()
+ message.Destroy()
def CanAddElement(self, block):
if isinstance(block, Graphic_Group):
@@ -2625,17 +2615,11 @@
return False
def GenerateNewName(self, element, exclude={}):
- names = exclude.copy()
- if isinstance(element, FBD_Block):
- names.update(dict([(varname.upper(), True) for varname in self.Controler.GetEditedElementVariables(self.TagName, self.Debug)]))
- format = "Block%d"
- elif isinstance(element, SFC_Step):
- names.update(dict([(block.GetName().upper(), True) for block in self.Blocks if isinstance(block, SFC_Step)]))
+ if isinstance(element, SFC_Step):
format = "Step%d"
- i = 1
- while names.get((format%i).upper(), False):
- i += 1
- return format%i
+ else:
+ format = "Block%d"
+ return self.Controler.GenerateNewName(self.TagName, element.GetName(), format, exclude, self.Debug)
def IsNamedElement(self, element):
return isinstance(element, FBD_Block) and element.GetName() != "" or isinstance(element, SFC_Step)
--- a/graphics/GraphicCommons.py Fri Jul 24 10:47:35 2009 +0200
+++ b/graphics/GraphicCommons.py Fri Jul 24 11:07:33 2009 +0200
@@ -459,6 +459,9 @@
self.BoundingBox = wx.Rect(0, 0, 0, 0)
self.Visible = False
+ def GetDefinition(self):
+ return [self.Id], []
+
def TestVisible(self, screen):
self.Visible = self.GetRedrawRect().Intersects(screen)
@@ -850,6 +853,15 @@
def __del__(self):
self.Elements = []
+ def GetDefinition(self):
+ blocks = []
+ wires = []
+ for element in self.Elements:
+ block, wire = element.GetDefinition()
+ blocks.extend(block)
+ wires.extend(wire)
+ return blocks, wires
+
# Make a clone of this element
def Clone(self, parent, pos = None):
group = Graphic_Group(parent)
@@ -1538,7 +1550,15 @@
self.OverEnd = False
self.ComputingType = False
self.ToolTip = None
-
+ self.Font = parent.GetMiniFont()
+
+ def GetDefinition(self):
+ if self.StartConnected is not None and self.EndConnected is not None:
+ startblock = self.StartConnected.GetParentBlock()
+ endblock = self.EndConnected.GetParentBlock()
+ return [], [(startblock.GetId(), endblock.GetId())]
+ return [], []
+
def Flush(self):
self.StartConnected = None
self.EndConnected = None
--- a/plcopen/__init__.py Fri Jul 24 10:47:35 2009 +0200
+++ b/plcopen/__init__.py Fri Jul 24 11:07:33 2009 +0200
@@ -28,6 +28,6 @@
import plcopen
for classname, cls in plcopen.PLCOpenClasses.items():
plcopen.__dict__[classname] = cls
-from plcopen import VarOrder
+from plcopen import VarOrder, ElementNameToClass, rect
from structures import *
--- a/plcopen/plcopen.py Fri Jul 24 10:47:35 2009 +0200
+++ b/plcopen/plcopen.py Fri Jul 24 11:07:33 2009 +0200
@@ -45,8 +45,54 @@
QualifierList = {"N" : False, "R" : False, "S" : False, "L" : True, "D" : True,
"P" : False, "P0" : False, "P1" : False, "SD" : True, "DS" : True, "SL" : True}
+
+def _init_and_compare(function, v1, v2):
+ if v1 is None:
+ return v2
+ if v2 is not None:
+ return function(v1, v2)
+ return v1
+
+"""
+Helper class for bounding_box calculation
+"""
+class rect:
+
+ def __init__(self, x=None, y=None, width=None, height=None):
+ self.x_min = x
+ self.x_max = None
+ self.y_min = y
+ self.y_max = None
+ if width is not None and x is not None:
+ self.x_max = x + width
+ if height is not None and y is not None:
+ self.y_max = y + height
+
+ def update(self, x, y):
+ self.x_min = _init_and_compare(min, self.x_min, x)
+ self.x_max = _init_and_compare(max, self.x_max, x)
+ self.y_min = _init_and_compare(min, self.y_min, y)
+ self.y_max = _init_and_compare(max, self.y_max, y)
+
+ def union(self, rect):
+ self.x_min = _init_and_compare(min, self.x_min, rect.x_min)
+ self.x_max = _init_and_compare(max, self.x_max, rect.x_max)
+ self.y_min = _init_and_compare(min, self.y_min, rect.y_min)
+ self.y_max = _init_and_compare(max, self.y_max, rect.y_max)
+
+ def bounding_box(self):
+ width = height = None
+ if self.x_min is not None and self.x_max is not None:
+ width = self.x_max - self.x_min
+ if self.y_min is not None and self.y_max is not None:
+ height = self.y_max - self.y_min
+ return self.x_min, self.y_min, width, height
+
+
PLCOpenClasses = GenerateClassesFromXSD(os.path.join(os.path.split(__file__)[0], "TC6_XML_V10_B.xsd"))
+ElementNameToClass = {}
+
cls = PLCOpenClasses.get("formattedText", None)
if cls:
def updateElementName(self, old_name, new_name):
@@ -1223,10 +1269,120 @@
def sety(self, y):
self.position.sety(y)
+def _getBoundingBox(self):
+ return rect(self.getx(), self.gety(), self.getwidth(), self.getheight())
+
+def _getConnectionsBoundingBox(connectionPointIn):
+ bbox = rect()
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ for x, y in connection.getpoints():
+ bbox.update(x, y)
+ return bbox
+
+def _getBoundingBoxSingle(self):
+ bbox = _getBoundingBox(self)
+ if self.connectionPointIn is not None:
+ bbox.union(_getConnectionsBoundingBox(self.connectionPointIn))
+ return bbox
+
+def _getBoundingBoxMultiple(self):
+ bbox = _getBoundingBox(self)
+ for connectionPointIn in self.getconnectionPointIn():
+ bbox.union(_getConnectionsBoundingBox(connectionPointIn))
+ return bbox
+
+def _filterConnections(connectionPointIn, localId, connections):
+ in_connections = connectionPointIn.getconnections()
+ if in_connections is not None:
+ to_delete = []
+ for i, connection in enumerate(in_connections):
+ connected = connection.getrefLocalId()
+ if not connections.has_key((localId, connected)) and \
+ not connections.has_key((connected, localId)):
+ to_delete.append(i)
+ to_delete.reverse()
+ for i in to_delete:
+ connectionPointIn.removeconnection(i)
+
+def _filterConnectionsSingle(self, connections):
+ if self.connectionPointIn is not None:
+ _filterConnections(self.connectionPointIn, self.localId, connections)
+
+def _filterConnectionsMultiple(self, connections):
+ for connectionPointIn in self.getconnectionPointIn():
+ _filterConnections(connectionPointIn, self.localId, connections)
+
+def _getconnectionsdefinition(instance, connections_end):
+ id = instance.getlocalId()
+ return dict([((id, end), True) for end in connections_end])
+
+def _updateConnectionsId(connectionPointIn, translation):
+ connections_end = []
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ refLocalId = connection.getrefLocalId()
+ new_reflocalId = translation.get(refLocalId, refLocalId)
+ connection.setrefLocalId(new_reflocalId)
+ connections_end.append(new_reflocalId)
+ return connections_end
+
+def _updateConnectionsIdSingle(self, translation):
+ connections_end = []
+ if self.connectionPointIn is not None:
+ connections_end = _updateConnectionsId(self.connectionPointIn, translation)
+ return _getconnectionsdefinition(self, connections_end)
+
+def _updateConnectionsIdMultiple(self, translation):
+ connections_end = []
+ for connectionPointIn in self.getconnectionPointIn():
+ connections_end.extend(_updateConnectionsId(connectionPointIn, translation))
+ return _getconnectionsdefinition(self, connections_end)
+
+def _translate(self, dx, dy):
+ self.setx(self.getx() + dx)
+ self.sety(self.gety() + dy)
+
+def _translateConnections(connectionPointIn, dx, dy):
+ connections = connectionPointIn.getconnections()
+ if connections is not None:
+ for connection in connections:
+ for position in connection.getposition():
+ position.setx(position.getx() + dx)
+ position.sety(position.gety() + dy)
+
+def _translateSingle(self, dx, dy):
+ _translate(self, dx, dy)
+ if self.connectionPointIn is not None:
+ _translateConnections(self.connectionPointIn, dx, dy)
+
+def _translateMultiple(self, dx, dy):
+ _translate(self, dx, dy)
+ for connectionPointIn in self.getconnectionPointIn():
+ _translateConnections(connectionPointIn, dx, dy)
+
def _updateElementName(self, old_name, new_name):
pass
+_connectionsFunctions = {
+ "bbox": {"none": _getBoundingBox,
+ "single": _getBoundingBoxSingle,
+ "multiple": _getBoundingBoxMultiple},
+ "translate": {"none": _translate,
+ "single": _translateSingle,
+ "multiple": _translateMultiple},
+ "filter": {"none": lambda self, connections: None,
+ "single": _filterConnectionsSingle,
+ "multiple": _filterConnectionsMultiple},
+ "update": {"none": lambda self, translation: None,
+ "single": _updateConnectionsIdSingle,
+ "multiple": _updateConnectionsIdMultiple}
+}
+
def _initElementClass(name, classname, connectionPointInType="none"):
+ ElementNameToClass[name] = classname
cls = PLCOpenClasses.get(classname, None)
if cls:
setattr(cls, "getx", getx)
@@ -1234,6 +1390,10 @@
setattr(cls, "setx", setx)
setattr(cls, "sety", sety)
setattr(cls, "updateElementName", _updateElementName)
+ setattr(cls, "getBoundingBox", _connectionsFunctions["bbox"][connectionPointInType])
+ setattr(cls, "translate", _connectionsFunctions["translate"][connectionPointInType])
+ setattr(cls, "filterConnections", _connectionsFunctions["filter"][connectionPointInType])
+ setattr(cls, "updateConnectionsId", _connectionsFunctions["update"][connectionPointInType])
return cls
def _getexecutionOrder(instance, specific_values):
@@ -1414,6 +1574,24 @@
self.typeName = new_name
setattr(cls, "updateElementName", updateElementName)
+ def filterConnections(self, connections):
+ for input in self.inputVariables.getvariable():
+ _filterConnections(input.connectionPointIn, self.localId, connections)
+ setattr(cls, "filterConnections", filterConnections)
+
+ def updateConnectionsId(self, translation):
+ connections_end = []
+ for input in self.inputVariables.getvariable():
+ connections_end.extend(_updateConnectionsId(input.connectionPointIn, translation))
+ return _getconnectionsdefinition(self, connections_end)
+ setattr(cls, "updateConnectionsId", updateConnectionsId)
+
+ def translate(self, dx, dy):
+ _translate(self, dx, dy)
+ for input in self.inputVariables.getvariable():
+ _translateConnections(input.connectionPointIn, dx, dy)
+ setattr(cls, "translate", translate)
+
cls = _initElementClass("leftPowerRail", "ldObjects_leftPowerRail")
if cls:
setattr(cls, "getinfos", _getpowerrailinfosFunction("leftPowerRail"))