comparison python/diagrameditor.py @ 95:4a37d6992bd3

movage
author windel
date Mon, 24 Dec 2012 13:24:59 +0100
parents python/apps/diagrameditor.py@f7ec7517cabb
children 6efbeb903777
comparison
equal deleted inserted replaced
94:1be00bcfaabb 95:4a37d6992bd3
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.activated.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 print(kind, 'name:', name)
127 kind = getattr(diagramitems, kind)
128 print(kind)
129 b = kind(s.uniqify(name))
130 b.setPos(pos)
131 s.addItem(b)
132
133 class LibraryModel(QStandardItemModel):
134 mimeTypes = lambda self: ['component/name']
135 def mimeData(self, idxs):
136 mimedata = QMimeData()
137 for idx in idxs:
138 if idx.isValid():
139 txt = self.data(idx, Qt.DisplayRole)
140 mimedata.setData('component/name', txt)
141 return mimedata
142
143 class ModelHierarchyModel(QAbstractItemModel):
144 def __init__(self):
145 super(ModelHierarchyModel, self).__init__()
146 self.rootDiagram = DiagramScene()
147 self.rootDiagram.structureChanged.connect(self.handlechange)
148 self.filename = None
149 def handlechange(self):
150 self.modelReset.emit()
151 def setDict(self, d):
152 self.rootDiagram.Dict = d
153 self.modelReset.emit()
154 def getDict(self):
155 return self.rootDiagram.Dict
156 Dict = property(getDict, setDict)
157 def gencode(self):
158 c = ['def topLevel():']
159 c += indent(self.rootDiagram.gencode())
160 c.append('print("Running model")')
161 c.append('topLevel()')
162 c.append('print("Done")')
163 return c
164 def index(self, row, column, parent=None):
165 if parent.isValid():
166 parent = parent.internalPointer().subModel
167 else:
168 parent = self.rootDiagram
169 blocks = sorted(parent.blocks, key=lambda b: b.name)
170 block = blocks[row]
171 # Store the index to retrieve it later in the parent function.
172 # TODO: solve this in a better way.
173 block.index = self.createIndex(row, column, block)
174 return block.index
175 def parent(self, index):
176 if index.isValid():
177 block = index.internalPointer()
178 if block.scene() == self.rootDiagram:
179 return QModelIndex()
180 else:
181 print(block)
182 outerBlock = block.scene().containingBlock
183 return outerBlock.index
184 print('parent: No valid index')
185 def data(self, index, role):
186 if index.isValid() and role == Qt.DisplayRole:
187 b = index.internalPointer()
188 if index.column() == 0:
189 return b.name
190 elif index.column() == 1:
191 return str(type(b))
192 def headerData(self, section, orientation, role):
193 if orientation == Qt.Horizontal and role == Qt.DisplayRole:
194 if section == 0:
195 return "Element"
196 elif section == 1:
197 return "Type"
198 else:
199 return "x"
200 def rowCount(self, parent):
201 if parent.column() > 0:
202 return 0
203 if parent.isValid():
204 block = parent.internalPointer()
205 if hasattr(block, 'subModel'):
206 return len(block.subModel.blocks)
207 else:
208 return 0
209 else:
210 return len(self.rootDiagram.blocks)
211 def columnCount(self, parent):
212 return 2
213
214 class LibraryWidget(QListView):
215 def __init__(self):
216 super(LibraryWidget, self).__init__(None)
217 self.libraryModel = LibraryModel(self)
218 self.libraryModel.setColumnCount(1)
219 # Create an icon with an icon:
220 pixmap = QPixmap(60, 60)
221 pixmap.fill()
222 painter = QPainter(pixmap)
223 painter.fillRect(10, 10, 40, 40, Qt.blue)
224 painter.setBrush(Qt.yellow)
225 painter.drawEllipse(20, 20, 20, 20)
226 painter.end()
227 # Fill library:
228 for name in ['CodeBlock:codeBlock', 'DiagramBlock:submod', 'Block:blk']:
229 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name))
230 self.setModel(self.libraryModel)
231 self.setViewMode(self.IconMode)
232 self.setDragDropMode(self.DragOnly)
233
234 def warning(txt):
235 QMessageBox.warning(None, "Warning", txt)
236
237 def loadModel(filename):
238 try:
239 m = ModelHierarchyModel()
240 with open(filename, 'r') as f: data = f.read()
241 m.filename = filename
242 m.Dict = json.loads(data)
243 return m
244 except KeyError:
245 warning('Corrupt model: {0}'.format(filename))
246 except ValueError:
247 warning('Corrupt model: {0}'.format(filename))
248 except FileNotFoundError:
249 warning('File [{0}] not found'.format(filename))
250
251 class Main(QMainWindow):
252 def __init__(self):
253 super(Main, self).__init__(None)
254 self.editor = EditorGraphicsView()
255 self.setCentralWidget(self.editor)
256 self.setWindowTitle("Diagram editor")
257 def buildIcon(b64):
258 icon = base64.decodestring(b64)
259 pm = QPixmap()
260 pm.loadFromData(icon)
261 return QIcon(pm)
262 toolbar = self.addToolBar('Tools')
263 toolbar.setObjectName('Tools')
264 def act(name, shortcut, callback, icon=None):
265 a = QAction(icon, name, self) if icon else QAction(name, self)
266 a.setShortcuts(shortcut)
267 a.triggered.connect(callback)
268 toolbar.addAction(a)
269 act('New', QKeySequence.New, self.editor.newModel, buildIcon(newicon))
270 act('Save', QKeySequence.Save, self.editor.save, buildIcon(saveicon))
271 act('Load', QKeySequence.Open, self.editor.load, buildIcon(loadicon))
272 act('Full screen', QKeySequence("F11"), self.toggleFullScreen)
273 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll)
274 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp)
275 act('Model code', QKeySequence("F7"), self.editor.showCode)
276 def addDock(name, widget):
277 dock = QDockWidget(name, self)
278 dock.setObjectName(name)
279 dock.setWidget(widget)
280 self.addDockWidget(Qt.LeftDockWidgetArea, dock)
281 addDock('Library', LibraryWidget())
282 addDock('Model tree', self.editor.treeView)
283 self.settings = QSettings('windelsoft', 'diagrameditor')
284 self.loadSettings()
285 def toggleFullScreen(self):
286 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
287 self.editor.zoomAll()
288 def loadSettings(self):
289 if self.settings.contains('mainwindowstate'):
290 self.restoreState(self.settings.value('mainwindowstate'))
291 if self.settings.contains('mainwindowgeometry'):
292 self.restoreGeometry(self.settings.value('mainwindowgeometry'))
293 if self.settings.contains('openedmodel'):
294 modelfile = self.settings.value('openedmodel')
295 self.editor.model = loadModel(modelfile)
296 def closeEvent(self, ev):
297 self.settings.setValue('mainwindowstate', self.saveState())
298 self.settings.setValue('mainwindowgeometry', self.saveGeometry())
299 if self.editor.model and self.editor.model.filename:
300 self.settings.setValue('openedmodel', self.editor.model.filename)
301 # TODO: ask for save of opened files
302 else:
303 self.settings.remove('openedmodel')
304 ev.accept()
305
306 if __name__ == '__main__':
307 if sys.version_info.major != 3:
308 print('Please use python 3.x')
309 sys.exit(1)
310 app = QApplication(sys.argv)
311 main = Main()
312 main.show()
313 app.exec_()
314