comparison python/other/diagrameditor.py @ 290:7b38782ed496

File moves
author Windel Bouwman
date Sun, 24 Nov 2013 11:24:15 +0100
parents python/diagrameditor.py@6efbeb903777
children b77f3290ac79
comparison
equal deleted inserted replaced
289:bd2593de3ff8 290:7b38782ed496
1 #!/usr/bin/python
2
3 from PyQt4.QtGui import *
4 from PyQt4.QtCore import *
5 import sys, json, base64
6
7 from diagramitems import Connection, ResizeSelectionHandle, Block, DiagramScene, CodeBlock
8 from icons import newicon, saveicon, loadicon
9 import diagramitems
10
11 """
12 Author: Windel Bouwman
13 Year: 2012
14 Description: This script implements a diagram editor.
15 run with python 3.x as:
16 $ python [thisfile.py]
17 """
18 def indent(lines):
19 return [' ' + line for line in lines]
20
21 class ParameterDialog(QDialog):
22 def __init__(self, block, parent = None):
23 super(ParameterDialog, self).__init__(parent)
24 self.block = block
25 self.button = QPushButton('Ok', self)
26 self.nameEdit = QLineEdit(self.block.name)
27 self.codeEdit = QTextEdit(self)
28 self.codeEdit.setPlainText(self.block.code)
29 l = QFormLayout(self)
30 l.addRow('Name:', self.nameEdit)
31 l.addRow('Code:', self.codeEdit)
32 l.addWidget(self.button)
33 self.button.clicked.connect(self.OK)
34 def OK(self):
35 self.block.name = self.nameEdit.text()
36 self.block.code = self.codeEdit.toPlainText()
37 self.close()
38
39 class EditorGraphicsView(QGraphicsView):
40 def __init__(self, parent=None):
41 QGraphicsView.__init__(self, parent)
42 self.setDragMode(QGraphicsView.RubberBandDrag)
43 self.delShort = QShortcut(QKeySequence.Delete, self)
44 self._model = None
45 self.treeView = QTreeView()
46 self.treeView.clicked.connect(self.itemActivated)
47 def itemActivated(self, idx):
48 b = idx.internalPointer()
49 s = b.scene()
50 s.clearSelection()
51 b.setSelected(True)
52 def setDiagram(self, d):
53 self.setScene(d)
54 self.delShort.activated.connect(d.deleteItems)
55 def getModel(self):
56 return self._model
57 def setModel(self, m):
58 self._model = m
59 if m:
60 self.treeView.setModel(m)
61 self.diagram = m.rootDiagram
62 self.model.modelReset.connect(self.treeView.expandAll)
63 model = property(getModel, setModel)
64 diagram = property(lambda s: s.scene(), setDiagram)
65 def save(self):
66 if self.model:
67 if not self.model.filename:
68 self.model.filename = QFileDialog.getSaveFileName(self)
69 if self.model.filename:
70 with open(self.model.filename, 'w') as f:
71 f.write(json.dumps(self.model.Dict, indent=2))
72 def load(self):
73 filename = QFileDialog.getOpenFileName(self)
74 if filename:
75 self.model = loadModel(filename)
76 def newModel(self):
77 self.model = ModelHierarchyModel()
78 def goUp(self):
79 if hasattr(self.diagram, 'containingBlock'):
80 self.diagram = self.diagram.containingBlock.scene()
81 self.zoomAll()
82 def showCode(self):
83 if self.model:
84 c = self.model.gencode()
85 c = '\n'.join(c)
86 d = QDialog()
87 l = QFormLayout(d)
88 codeview = QTextEdit()
89 codeview.setPlainText(c)
90 l.addRow('code', codeview)
91 runButton = QPushButton('Run')
92 outputview = QTextEdit()
93 l.addRow('Output', outputview)
94 l.addWidget(runButton)
95 def print2(txt):
96 txt2 = outputview.toPlainText()
97 outputview.setPlainText(txt2 + '\n' + txt)
98 def runIt():
99 outputview.clear()
100 globs = {'print': print2}
101 exec(codeview.toPlainText(), globs)
102 runButton.clicked.connect(runIt)
103 d.exec_()
104 def zoomAll(self):
105 """ zoom to fit all items """
106 rect = self.diagram.itemsBoundingRect()
107 self.fitInView(rect, Qt.KeepAspectRatio)
108 def wheelEvent(self, event):
109 pos = event.pos()
110 posbefore = self.mapToScene(pos)
111 degrees = event.delta() / 8.0
112 sx = (100.0 + degrees) / 100.0
113 self.scale(sx, sx)
114 event.accept()
115 def dragEnterEvent(self, event):
116 if event.mimeData().hasFormat('component/name'):
117 event.accept()
118 def dragMoveEvent(self, event):
119 if event.mimeData().hasFormat('component/name'): event.accept()
120 def dropEvent(self, event):
121 if event.mimeData().hasFormat('component/name'):
122 name = bytes(event.mimeData().data('component/name')).decode()
123 kind, name = name.split(':')
124 pos = self.mapToScene(event.pos())
125 s = self.scene()
126 if not s:
127 return
128 print(kind, 'name:', name)
129 kind = getattr(diagramitems, kind)
130 print(kind)
131 b = kind(s.uniqify(name))
132 b.setPos(pos)
133 s.addItem(b)
134
135 class LibraryModel(QStandardItemModel):
136 mimeTypes = lambda self: ['component/name']
137 def mimeData(self, idxs):
138 mimedata = QMimeData()
139 for idx in idxs:
140 if idx.isValid():
141 txt = self.data(idx, Qt.DisplayRole)
142 mimedata.setData('component/name', txt)
143 return mimedata
144
145 class ModelHierarchyModel(QAbstractItemModel):
146 def __init__(self):
147 super(ModelHierarchyModel, self).__init__()
148 self.rootDiagram = DiagramScene()
149 self.rootDiagram.structureChanged.connect(self.handlechange)
150 self.filename = None
151 def handlechange(self):
152 self.modelReset.emit()
153 def setDict(self, d):
154 self.rootDiagram.Dict = d
155 self.modelReset.emit()
156 def getDict(self):
157 return self.rootDiagram.Dict
158 Dict = property(getDict, setDict)
159 def gencode(self):
160 c = ['def topLevel():']
161 c += indent(self.rootDiagram.gencode())
162 c.append('print("Running model")')
163 c.append('topLevel()')
164 c.append('print("Done")')
165 return c
166 def index(self, row, column, parent=None):
167 if parent.isValid():
168 parent = parent.internalPointer().subModel
169 else:
170 parent = self.rootDiagram
171 blocks = sorted(parent.blocks, key=lambda b: b.name)
172 block = blocks[row]
173 # Store the index to retrieve it later in the parent function.
174 # TODO: solve this in a better way.
175 block.index = self.createIndex(row, column, block)
176 return block.index
177 def parent(self, index):
178 if index.isValid():
179 block = index.internalPointer()
180 if block.scene() == self.rootDiagram:
181 return QModelIndex()
182 else:
183 print(block)
184 outerBlock = block.scene().containingBlock
185 return outerBlock.index
186 print('parent: No valid index')
187 def data(self, index, role):
188 if index.isValid() and role == Qt.DisplayRole:
189 b = index.internalPointer()
190 if index.column() == 0:
191 return b.name
192 elif index.column() == 1:
193 return str(type(b))
194 def headerData(self, section, orientation, role):
195 if orientation == Qt.Horizontal and role == Qt.DisplayRole:
196 if section == 0:
197 return "Element"
198 elif section == 1:
199 return "Type"
200 else:
201 return "x"
202 def rowCount(self, parent):
203 if parent.column() > 0:
204 return 0
205 if parent.isValid():
206 block = parent.internalPointer()
207 if hasattr(block, 'subModel'):
208 return len(block.subModel.blocks)
209 else:
210 return 0
211 else:
212 return len(self.rootDiagram.blocks)
213 def columnCount(self, parent):
214 return 2
215
216 class LibraryWidget(QListView):
217 def __init__(self):
218 super(LibraryWidget, self).__init__(None)
219 self.libraryModel = LibraryModel(self)
220 self.libraryModel.setColumnCount(1)
221 # Create an icon with an icon:
222 pixmap = QPixmap(60, 60)
223 pixmap.fill()
224 painter = QPainter(pixmap)
225 painter.fillRect(10, 10, 40, 40, Qt.blue)
226 painter.setBrush(Qt.yellow)
227 painter.drawEllipse(20, 20, 20, 20)
228 painter.end()
229 # Fill library:
230 for name in ['CodeBlock:codeBlock', 'DiagramBlock:submod', 'Block:blk']:
231 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name))
232 self.setModel(self.libraryModel)
233 self.setViewMode(self.IconMode)
234 self.setDragDropMode(self.DragOnly)
235
236 def warning(txt):
237 QMessageBox.warning(None, "Warning", txt)
238
239 def loadModel(filename):
240 try:
241 m = ModelHierarchyModel()
242 with open(filename, 'r') as f: data = f.read()
243 m.filename = filename
244 m.Dict = json.loads(data)
245 return m
246 except KeyError:
247 warning('Corrupt model: {0}'.format(filename))
248 except ValueError:
249 warning('Corrupt model: {0}'.format(filename))
250 except FileNotFoundError:
251 warning('File [{0}] not found'.format(filename))
252
253 class Main(QMainWindow):
254 def __init__(self):
255 super(Main, self).__init__(None)
256 self.editor = EditorGraphicsView()
257 self.setCentralWidget(self.editor)
258 self.setWindowTitle("Diagram editor")
259 def buildIcon(b64):
260 icon = base64.decodestring(b64)
261 pm = QPixmap()
262 pm.loadFromData(icon)
263 return QIcon(pm)
264 toolbar = self.addToolBar('Tools')
265 toolbar.setObjectName('Tools')
266 def act(name, shortcut, callback, icon=None):
267 a = QAction(icon, name, self) if icon else QAction(name, self)
268 a.setShortcuts(shortcut)
269 a.triggered.connect(callback)
270 toolbar.addAction(a)
271 act('New', QKeySequence.New, self.editor.newModel, buildIcon(newicon))
272 act('Save', QKeySequence.Save, self.editor.save, buildIcon(saveicon))
273 act('Load', QKeySequence.Open, self.editor.load, buildIcon(loadicon))
274 act('Full screen', QKeySequence("F11"), self.toggleFullScreen)
275 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll)
276 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp)
277 act('Model code', QKeySequence("F7"), self.editor.showCode)
278 def addDock(name, widget):
279 dock = QDockWidget(name, self)
280 dock.setObjectName(name)
281 dock.setWidget(widget)
282 self.addDockWidget(Qt.LeftDockWidgetArea, dock)
283 addDock('Library', LibraryWidget())
284 addDock('Model tree', self.editor.treeView)
285 self.settings = QSettings('windelsoft', 'diagrameditor')
286 self.loadSettings()
287 def toggleFullScreen(self):
288 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
289 self.editor.zoomAll()
290 def loadSettings(self):
291 if self.settings.contains('mainwindowstate'):
292 self.restoreState(self.settings.value('mainwindowstate'))
293 if self.settings.contains('mainwindowgeometry'):
294 self.restoreGeometry(self.settings.value('mainwindowgeometry'))
295 if self.settings.contains('openedmodel'):
296 modelfile = self.settings.value('openedmodel')
297 self.editor.model = loadModel(modelfile)
298 def closeEvent(self, ev):
299 self.settings.setValue('mainwindowstate', self.saveState())
300 self.settings.setValue('mainwindowgeometry', self.saveGeometry())
301 if self.editor.model and self.editor.model.filename:
302 self.settings.setValue('openedmodel', self.editor.model.filename)
303 # TODO: ask for save of opened files
304 else:
305 self.settings.remove('openedmodel')
306 ev.accept()
307
308 if __name__ == '__main__':
309 if sys.version_info.major != 3:
310 print('Please use python 3.x')
311 sys.exit(1)
312 app = QApplication(sys.argv)
313 main = Main()
314 main.show()
315 app.exec_()
316