# HG changeset patch # User laurent # Date 1248426453 -7200 # Node ID ed27a676d5c9d02afafed8f93664404c309c7ac3 # Parent 25ffba02b6a8a53e1dc391639cab1fd8d91f1200 Changing Cut/Copy/Paste procedures for using wx.Clipboard with xml definition of copied elements diff -r 25ffba02b6a8 -r ed27a676d5c9 PLCControler.py --- 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 = "%s"%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 = {} diff -r 25ffba02b6a8 -r ed27a676d5c9 PLCOpenEditor.py --- 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) diff -r 25ffba02b6a8 -r ed27a676d5c9 Viewer.py --- 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) diff -r 25ffba02b6a8 -r ed27a676d5c9 graphics/GraphicCommons.py --- 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 diff -r 25ffba02b6a8 -r ed27a676d5c9 plcopen/__init__.py --- 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 * diff -r 25ffba02b6a8 -r ed27a676d5c9 plcopen/plcopen.py --- 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"))