# HG changeset patch # User windel # Date 1331839813 -3600 # Node ID 7964065400b74189a389297fec5a332407493e72 # Parent 8a52263d67c450ee2fe186fb78a7d704cdee8217 Added xml save function diff -r 8a52263d67c4 -r 7964065400b7 applications/lab/diagrameditor.py --- a/applications/lab/diagrameditor.py Sat Feb 18 17:09:21 2012 +0100 +++ b/applications/lab/diagrameditor.py Thu Mar 15 20:30:13 2012 +0100 @@ -1,9 +1,9 @@ #!/usr/bin/python -from PyQt4 import QtGui, QtCore from PyQt4.QtGui import * from PyQt4.QtCore import * import sys +from elementtree.SimpleXMLWriter import XMLWriter """ This script implements a basic diagram editor. @@ -16,65 +16,110 @@ - toPort """ def __init__(self, fromPort, toPort): - self.fromPort = fromPort self.pos1 = None self.pos2 = None - self.p1dir = None - self.p2dir = None + self.pathItem = QGraphicsPathItem() + pen = QPen() + pen.setWidth(4) + pen.setColor(Qt.blue) + pen.setCapStyle(Qt.RoundCap) + self.pathItem.setPen(pen) + #self.pathItem.setFlags(self.pathItem.ItemIsSelectable) + editor.diagramScene.addItem(self.pathItem) self.setFromPort(fromPort) - self.toPort = toPort - # Create arrow item: - self.linePieces = [] + self.setToPort(toPort) def setFromPort(self, fromPort): self.fromPort = fromPort if self.fromPort: - self.pos1 = fromPort.scenePos() + self.setBeginPos(fromPort.scenePos()) self.fromPort.posCallbacks.append(self.setBeginPos) def setToPort(self, toPort): self.toPort = toPort if self.toPort: - self.pos2 = toPort.scenePos() + self.setEndPos(toPort.scenePos()) self.toPort.posCallbacks.append(self.setEndPos) def setBeginPos(self, pos1): self.pos1 = pos1 - if self.pos1 and self.pos2: - self.updateLineStukken() + self.updateLineStukken() def setEndPos(self, endpos): self.pos2 = endpos - if self.pos1 and self.pos2: - self.updateLineStukken() + self.updateLineStukken() def updateLineStukken(self): """ This algorithm determines the optimal routing of all signals. TODO: implement nice automatic line router """ - # TODO: create pieces of lines. - - # Determine the current amount of linestukken: - x1, y1 = self.pos1.x(), self.pos1.y() - x2, y2 = self.pos2.x(), self.pos2.y() + if self.pos1 is None or self.pos2 is None: + return ds = editor.diagramScene - if y1 == y2 or x1 == x2: - pass + # TODO: create pieces of lines. + + # Determine the begin and end positions: + p1 = self.pos1 + p4 = self.pos2 + x1, y1 = self.pos1.x(), self.pos1.y() + x4, y4 = self.pos2.x(), self.pos2.y() + + def stripHits(hits): + """ Helper function that removes object hits """ + hits = [hit for hit in hits if type(hit) is BlockItem] + if self.pathItem in hits: + hits.remove(self.pathItem) + if self.fromPort in hits: + hits.remove(self.fromPort) + if self.toPort in hits: + hits.remove(self.toPort) + return hits + + def viaPoints(pA, pB): + # Construct intermediate points: + + pAB1 = QPointF(pA.x(), pB.y()) + pAB2 = QPointF(pB.x(), pA.y()) + path1 = QPainterPath(pA) + path1.lineTo(pAB1) + path2 = QPainterPath(pA) + path2.lineTo(pAB2) + if len(stripHits(ds.items(path1))) > len(stripHits(ds.items(path2))): + pAB = pAB2 + else: + pAB = pAB1 + return [pAB] + + # Determine left or right: + dx = QPointF(10, 0) + if len(stripHits(ds.items(p1 + dx))) > len(stripHits(ds.items(p1 - dx))): + p2 = p1 - dx else: - # We require two lijnstukken to make one corner! - while len(self.linePieces) < 2: - lp = LinePieceItem() - ds.addItem(lp) - self.linePieces.append(lp) - lp1 = self.linePieces[0] - lp2 = self.linePieces[1] - lp1.setLine(QLineF(x1, y1, x2, y1)) - lp2.setLine(QLineF(x2, y1, x2, y2)) + p2 = p1 + dx + + if len(stripHits(ds.items(p4 + dx))) > len(stripHits(ds.items(p4 - dx))): + p3 = p4 - dx + else: + p3 = p4 + dx + + path = QPainterPath(p1) + path.lineTo(p2) + # Now move from p2 to p3 without hitting blocks: + pts = viaPoints(p2, p3) + for pt in pts: + path.lineTo(pt) + path.lineTo(p3) + path.lineTo(p4) + + hits = stripHits(ds.items(path)) + + #print('Items:', hits) + self.pathItem.setPath(path) def delete(self): - editor.diagramScene.removeItem(self.arrow) + editor.diagramScene.removeItem(self.pathItem) # Remove position update callbacks: class ParameterDialog(QDialog): - def __init__(self, parent=None): + def __init__(self, parent = None): super(ParameterDialog, self).__init__(parent) self.button = QPushButton('Ok', self) l = QVBoxLayout(self) @@ -87,7 +132,7 @@ """ Represents a port to a subsystem """ def __init__(self, name, parent=None): QGraphicsEllipseItem.__init__(self, QRectF(-6,-6,12.0,12.0), parent) - self.setCursor(QCursor(QtCore.Qt.CrossCursor)) + self.setCursor(QCursor(Qt.CrossCursor)) # Properties: self.setBrush(QBrush(Qt.red)) # Name: @@ -96,7 +141,6 @@ self.setFlag(self.ItemSendsScenePositionChanges, True) def itemChange(self, change, value): if change == self.ItemScenePositionHasChanged: - #value = value.toPointF() # Required in python2??! for cb in self.posCallbacks: cb(value) return value @@ -110,10 +154,9 @@ def __init__(self, parent=None): super(HandleItem, self).__init__(QRectF(-4.0,-4.0,8.0,8.0), parent) self.posChangeCallbacks = [] - self.setBrush(QtGui.QBrush(Qt.white)) - self.setFlag(self.ItemIsMovable, True) - self.setFlag(self.ItemSendsScenePositionChanges, True) - self.setCursor(QtGui.QCursor(Qt.SizeFDiagCursor)) + self.setBrush(QBrush(Qt.white)) + self.setFlags(self.ItemIsMovable | self.ItemSendsScenePositionChanges) + self.setCursor(QCursor(Qt.SizeFDiagCursor)) def itemChange(self, change, value): if change == self.ItemPositionChange: @@ -145,10 +188,10 @@ w = 60.0 h = 40.0 # Properties of the rectangle: - self.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) - self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray)) + self.setPen(QPen(Qt.blue, 2)) + self.setBrush(QBrush(Qt.lightGray)) self.setFlags(self.ItemIsSelectable | self.ItemIsMovable) - self.setCursor(QCursor(QtCore.Qt.PointingHandCursor)) + self.setCursor(QCursor(Qt.PointingHandCursor)) # Label: self.label = QGraphicsTextItem(name, self) # Create corner for resize: @@ -173,11 +216,16 @@ def contextMenuEvent(self, event): menu = QMenu() - menu.addAction('Delete') + da = menu.addAction('Delete') + da.triggered.connect(self.delete) pa = menu.addAction('Parameters') pa.triggered.connect(self.editParameters) menu.exec_(event.screenPos()) + def delete(self): + editor.diagramScene.removeItem(self) + # TODO: remove connections + def changeSize(self, w, h): """ Resize block function """ # Limit the block size: @@ -211,14 +259,6 @@ y += dy return w, h -class LinePieceItem(QGraphicsLineItem): - def __init__(self): - super(LinePieceItem, self).__init__(None) - self.setPen(QtGui.QPen(QtCore.Qt.red,2)) - self.setFlag(self.ItemIsSelectable, True) - def x(self): - pass - class EditorGraphicsView(QGraphicsView): def __init__(self, scene, parent=None): QGraphicsView.__init__(self, scene, parent) @@ -261,12 +301,12 @@ class DiagramEditor(QWidget): def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) + QWidget.__init__(self, parent) self.setWindowTitle("Diagram editor") # Widget layout and child widgets: - self.horizontalLayout = QtGui.QHBoxLayout(self) - self.libraryBrowserView = QtGui.QListView(self) + self.horizontalLayout = QHBoxLayout(self) + self.libraryBrowserView = QListView(self) self.libraryBrowserView.setMaximumWidth(160) self.libraryModel = LibraryModel(self) self.libraryModel.setColumnCount(1) @@ -282,9 +322,9 @@ painter.end() self.libItems = [] - self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Block') ) - self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Uber Unit') ) - self.libItems.append( QtGui.QStandardItem(QIcon(pixmap), 'Device') ) + self.libItems.append( QStandardItem(QIcon(pixmap), 'Block') ) + self.libItems.append( QStandardItem(QIcon(pixmap), 'Uber Unit') ) + self.libItems.append( QStandardItem(QIcon(pixmap), 'Device') ) for i in self.libItems: self.libraryModel.appendRow(i) self.libraryBrowserView.setModel(self.libraryModel) @@ -296,17 +336,52 @@ self.horizontalLayout.addWidget(self.libraryBrowserView) self.horizontalLayout.addWidget(self.diagramView) - # Populate the diagram scene: - b1 = BlockItem('SubSystem1') - b1.setPos(50,100) - self.diagramScene.addItem(b1) - b2 = BlockItem('Unit2') - b2.setPos(-250,0) - self.diagramScene.addItem(b2) - self.startedConnection = None fullScreenShortcut = QShortcut(QKeySequence("F11"), self) fullScreenShortcut.activated.connect(self.toggleFullScreen) + saveShortcut = QShortcut(QKeySequence(QKeySequence.Save), self) + saveShortcut.activated.connect(self.save) + self.loadDiagram('diagram.txt') + + def save(self): + self.saveDiagram2('diagram2.txt') + def saveDiagram2(self, filename): + items = self.diagramScene.items() + blocks = [item for item in items if type(item) is BlockItem] + with open(filename, 'w') as f: + w = XMLWriter(f) + model = w.start('model') + for block in blocks: + w.element("block") + + w.close(model) + def saveDiagram(self, filename): + items = self.diagramScene.items() + blocks = [item for item in items if type(item) is BlockItem] + with open(filename, 'w') as f: + for block in blocks: + rect = block.rect() + x, y, w, h = block.scenePos().x(), block.scenePos().y(), rect.width(), rect.height() + f.write('B({0};{1};{2};{3}) '.format(x,y,w,h)) # (x;y;w;h) + + def loadDiagram(self, filename): + try: + with open(filename, 'r') as f: + txt = f.read() + except IOError as e: + print('{0} not found'.format(filename)) + return + parts = txt.split(' ') + for part in parts: + if len(part) < 1: + continue + if part[0] == 'B': + part = part[2:-1] + x,y,w,h = [float(val) for val in part.split(';')] + block = BlockItem('Loaded') # TODO: store name. + self.diagramScene.addItem(block) + block.setPos(x, y) + block.sizer.setPos(w, h) def toggleFullScreen(self): self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) def startConnection(self, port): @@ -318,13 +393,14 @@ def sceneMouseReleaseEvent(self, event): # Clear the actual connection: if self.startedConnection: - pos = event.scenePos() - items = self.diagramScene.items(pos) + items = self.diagramScene.items(event.scenePos()) for item in items: if type(item) is PortItem: self.startedConnection.setToPort(item) - if self.startedConnection.toPort == None: - self.startedConnection.delete() + self.startedConnection = None + return + self.startedConnection.delete() + del self.startedConnection self.startedConnection = None if __name__ == '__main__': @@ -332,7 +408,7 @@ print('Please use python 3.x') sys.exit(1) - app = QtGui.QApplication(sys.argv) + app = QApplication(sys.argv) global editor editor = DiagramEditor() editor.show()