comparison python/apps/diagrameditor.py @ 81:49161141d765

Added icons and improved stability
author windel
date Fri, 16 Nov 2012 12:10:34 +0100
parents d1925eb3bbd5
children a47f6fec5435
comparison
equal deleted inserted replaced
80:d1925eb3bbd5 81:49161141d765
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 from PyQt4.QtGui import * 3 from PyQt4.QtGui import *
4 from PyQt4.QtCore import * 4 from PyQt4.QtCore import *
5 import sys, json 5 import sys, json, base64
6 6
7 """ 7 """
8 Author: Windel Bouwman 8 Author: Windel Bouwman
9 Year: 2012 9 Year: 2012
10 Description: This script implements a diagram editor. 10 Description: This script implements a diagram editor.
11 run with python 3.x as: 11 run with python 3.x as:
12 $ python [thisfile.py] 12 $ python [thisfile.py]
13 """ 13 """
14 saveicon = b'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\nveAfetNmUUsM6bsAAAAASUVORK5CYII=\n'
15
16 loadicon = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVR42u2Xf0hTURTHv++ZczlD\n6SeUREVRiRT9gFWM1rbAwr+iYAathPqjMosIQQqJQAhLLDI0CMwflRa1vyQst0oNLKxhCGWR9UfB\nwFCRaqC2vc6d7+1tr/7YnuwW5IEv95y79875vHvP7t4E/GUTpgGmAaL8vaRzpDlx3iuRXpEOkz5N\nFWDhrHR8bL+LtGXLKUqRJcoKyeUk1Q8Fges3gLIKeGlm+1QBHA4rPJ7WxG4eHqLlWoJRcvfJaPHY\nV1LPbwB5Dnja7if+BFUVQEdX7OqETdL48vD+C/DODze5e9iUCmAjgDt6FzJ+m/gG5GyD9OEz1lPY\nqwLQFrQ1JR+Amc0JPO0O941XBbAQQB0nABcBvNACbCGAWk4ABwngpRbATACXEksU7rsJGlnzUSZB\nkEdAbW9lXgnJtx0jAJ8WYCMBnE8MIESFx7+rRQRRLiaPESANmL2EAF6jgaLSCIA9Bx53sZokJpEY\nm4zFzP85BgTn0mUZUL9y7CPpz8CCvAJdA0DrE6D6NnxsLp9UaV2KVfW7Jy8QWcGUyUJhP0piFJA0\nmw6iAn19EAgAmdkICpRw5EEdsjabgXQDYo9gRZENVxVeIJO8Gjqs7y2wxoJ+IXUGxkcHkDrTqC+R\nXrt5D3AVoYU9yJs+L1bnruQLUFJO+34NpxlAc2MVCly7+ALkHQAedSKfAZwq3o/KK2X8ikvUT4ss\nkPyDWMwAtm5ai47uZn4Ag8PAAgvoxxzzGUCGMQ1DI89gMBr4ADymtwHHIXSSa1UOIt/zBqwz5/IB\nqG4Bjl9EDblFCkDN5ZM4csLJB+DoBaDWDTp3cVUBcDntaLx1hg/AzlKgvQc7yH2oAGSbjOg/WwjT\nvMzkFvdT65U34UdgDCtYGP1avoFUSMpK8gKw7q8n9bLgn/pj8n8C/ALihrxpNKi7hQAAAABJRU5E\nrkJggg==\n'
17
18 newicon = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADSUlEQVR42r2XO0xaURjHLzYmdXRi\nYXBwdDScJl3djQNLw0TSAR88FR9JU6P4QKMCaTqQdGgXsQQMQXFwcGXhCCG+4iNEo6mDMZL45kK/\n76THUO+9Qi/efskJcm4O3+/+v8f51Al/LBaLmTs6Or43gAkq7eHhoXh2dmZva2v7WusZHf9jZGTE\nPz4+blfjuFwuC6IoCsViUbi8vCwDhM1oNH75V4AAANjUAJRKJeYc183NjXB1dVU+Pj7+CIp+qxlg\neHg44PV6VQHg2z8+PjKAu7s7oampSQAVxL29PUtnZ+eP/waA6/r6WtDr9UyJ09NTcXd319LV1aUI\n8QQwNDQUmJiYqBsA5BcMBgPLC4Q4OTkRt7e3LSaTSRbiCWBwcDAwOTmpOgfQOVSBcHFxIbS0tLB9\nDgH5IGYyGYvZbJZAvAoAOsL4IwTEngGxH9fp2MJnhUJBTKfT5t7e3rAsgMfjYQB4oB4V+OJAuI+A\naGtra6nz8/P3o6OjJQnAwMBAYGpqSjUAh8Aw8JLE3OBqIMTGxgbNZrPE5/MVFQFUe6+A4M5xoWMe\nIg4ASksB+vv761aAG3eKMFwBBFhfX6ebm5sEyl0eAHOgjqvgRSBUA3KAghHouFIAt9vNFNACgIci\nmUxSKEcyNjYmBXC5XK8Wgudvz8OAAJgDigD1lGE155UAsiFwOp2vUgVKzisBZJMQAdR2wlqco62s\nrNBcLkfgzpECOByOuhXg5cc733NLJBIMQLYP2O12BqB0uJrjyk8li8fjdGtri4AfeQAMAUpXayJW\ne2MlANlWbLPZGAB2LPagCoQapZaXl+nOzg6ZmZmRB8CBhANoYTB5U5iQyOzsrDwAjmR4hWpl0WiU\nwpxI5ubmpAAwKLAQ4HWqlUUiEbq/v0/m5+flATAEWiqwtLREDw4OyMLCgjyA1iEIh8P08PCQ+P1+\nKUBPTw8D0DIEi4uL9OjoiASDQSlAd3c3A7i/v9cUAKsgFApJAaxWa2B6etp2e3v71yGlen++X60v\n4EwAAOlUKvUO+oEUoLW19UNfX9/nxsbGhsoOxy+Wl75X2+drdXX1J/zX9AkmI+lU3N7e/hYSxAAT\n0Rs+FdX69rXsA1y5ubn5Vz6fL1Q++w30VO4/0/9IewAAAABJRU5ErkJggg==\n'
14 19
15 def buildPath(pts): 20 def buildPath(pts):
16 path = QPainterPath(pts[0]) 21 path = QPainterPath(pts[0])
17 for pt in pts[1:]: path.lineTo(pt) 22 for pt in pts[1:]: path.lineTo(pt)
18 return path 23 return path
312 self.setScene(d) 317 self.setScene(d)
313 self.delShort.activated.connect(d.deleteItems) 318 self.delShort.activated.connect(d.deleteItems)
314 def getModel(self): return self._model 319 def getModel(self): return self._model
315 def setModel(self, m): 320 def setModel(self, m):
316 self._model = m 321 self._model = m
317 self.treeView.setModel(m) 322 if m:
318 self.diagram = m.rootDiagram 323 self.treeView.setModel(m)
324 self.diagram = m.rootDiagram
319 model = property(getModel, setModel) 325 model = property(getModel, setModel)
320 diagram = property(lambda s: s.scene(), setDiagram) 326 diagram = property(lambda s: s.scene(), setDiagram)
321 def save(self): 327 def save(self):
322 filename = 'diagram4.usd' 328 if self.model:
323 with open(filename, 'w') as f: 329 if not self.model.filename:
324 f.write(json.dumps(self.model.Dict)) 330 self.model.filename = QFileDialog.getSaveFileName(self)
331 if self.model.filename:
332 with open(self.model.filename, 'w') as f:
333 f.write(json.dumps(self.model.Dict))
325 def load(self): 334 def load(self):
326 filename = QFileDialog.getOpenFileName(self) 335 filename = QFileDialog.getOpenFileName(self)
327 self.model = loadModel(filename) 336 if filename:
337 self.model = loadModel(filename)
338 def newModel(self):
339 self.model = ModelHierarchyModel()
328 def goUp(self): 340 def goUp(self):
329 if hasattr(self.diagram, 'containingBlock'): 341 if hasattr(self.diagram, 'containingBlock'):
330 self.diagram = self.diagram.containingBlock.scene() 342 self.diagram = self.diagram.containingBlock.scene()
331 self.zoomAll() 343 self.zoomAll()
332 def zoomAll(self): 344 def zoomAll(self):
364 376
365 class ModelHierarchyModel(QAbstractItemModel): 377 class ModelHierarchyModel(QAbstractItemModel):
366 def __init__(self): 378 def __init__(self):
367 super(ModelHierarchyModel, self).__init__() 379 super(ModelHierarchyModel, self).__init__()
368 self.rootDiagram = DiagramScene() 380 self.rootDiagram = DiagramScene()
381 self.filename = None
369 def setDict(self, d): 382 def setDict(self, d):
370 self.rootDiagram.Dict = d 383 self.rootDiagram.Dict = d
371 self.modelReset.emit() 384 self.modelReset.emit()
372 Dict = property(lambda s: s.rootDiagram.Dict, setDict) 385 Dict = property(lambda s: s.rootDiagram.Dict, setDict)
373 def index(self, row, column, parent=None): 386 def index(self, row, column, parent=None):
479 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name)) 492 self.libraryModel.appendRow(QStandardItem(QIcon(pixmap), name))
480 self.setModel(self.libraryModel) 493 self.setModel(self.libraryModel)
481 self.setViewMode(self.IconMode) 494 self.setViewMode(self.IconMode)
482 self.setDragDropMode(self.DragOnly) 495 self.setDragDropMode(self.DragOnly)
483 496
497 def warning(txt):
498 QMessageBox.warning(None, "Warning", txt)
499
484 def loadModel(filename): 500 def loadModel(filename):
485 m = ModelHierarchyModel()
486 try: 501 try:
502 m = ModelHierarchyModel()
487 with open(filename, 'r') as f: data = f.read() 503 with open(filename, 'r') as f: data = f.read()
504 m.filename = filename
488 m.Dict = json.loads(data) 505 m.Dict = json.loads(data)
489 except FileNotFoundError: pass 506 return m
490 return m 507 except KeyError:
508 warning('Corrupt model: {0}'.format(filename))
509 except ValueError:
510 warning('Corrupt model: {0}'.format(filename))
511 except FileNotFoundError:
512 warning('File [{0}] not found'.format(filename))
491 513
492 class Main(QMainWindow): 514 class Main(QMainWindow):
493 def __init__(self): 515 def __init__(self):
494 super(Main, self).__init__(None) 516 super(Main, self).__init__(None)
495 self.editor = EditorGraphicsView() 517 self.editor = EditorGraphicsView()
496 self.setCentralWidget(self.editor) 518 self.setCentralWidget(self.editor)
497 self.setWindowTitle("Diagram editor") 519 self.setWindowTitle("Diagram editor")
520 def buildIcon(b64):
521 icon = base64.decodestring(b64)
522 pm = QPixmap()
523 pm.loadFromData(icon)
524 return QIcon(pm)
498 toolbar = self.addToolBar('Tools') 525 toolbar = self.addToolBar('Tools')
499 def act(name, shortcut, callback): 526 toolbar.setObjectName('Tools')
500 a = QAction(name, self) 527 def act(name, shortcut, callback, icon=None):
528 a = QAction(icon, name, self) if icon else QAction(name, self)
501 a.setShortcuts(shortcut) 529 a.setShortcuts(shortcut)
502 a.triggered.connect(callback) 530 a.triggered.connect(callback)
503 toolbar.addAction(a) 531 toolbar.addAction(a)
504 act('Save', QKeySequence.Save, self.editor.save) 532 act('New', QKeySequence.New, self.editor.newModel, buildIcon(newicon))
505 act('Load', QKeySequence.Open, self.editor.load) 533 act('Save', QKeySequence.Save, self.editor.save, buildIcon(saveicon))
534 act('Load', QKeySequence.Open, self.editor.load, buildIcon(loadicon))
506 act('Full screen', QKeySequence("F11"), self.toggleFullScreen) 535 act('Full screen', QKeySequence("F11"), self.toggleFullScreen)
507 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll) 536 act('Fit in view', QKeySequence("F8"), self.editor.zoomAll)
508 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp) 537 act('Go up', QKeySequence(Qt.Key_Up), self.editor.goUp)
509 def addDock(name, widget): 538 def addDock(name, widget):
510 dock = QDockWidget(name, self) 539 dock = QDockWidget(name, self)
540 dock.setObjectName(name)
511 dock.setWidget(widget) 541 dock.setWidget(widget)
512 self.addDockWidget(Qt.LeftDockWidgetArea, dock) 542 self.addDockWidget(Qt.LeftDockWidgetArea, dock)
513 addDock('Library', LibraryWidget()) 543 addDock('Library', LibraryWidget())
514 addDock('Model tree', self.editor.treeView) 544 addDock('Model tree', self.editor.treeView)
545 self.settings = QSettings('windelsoft', 'diagrameditor')
546 self.loadSettings()
515 def toggleFullScreen(self): 547 def toggleFullScreen(self):
516 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) 548 self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
517 self.editor.zoomAll() 549 self.editor.zoomAll()
550 def loadSettings(self):
551 if self.settings.contains('mainwindowstate'):
552 self.restoreState(self.settings.value('mainwindowstate'))
553 if self.settings.contains('mainwindowgeometry'):
554 self.restoreGeometry(self.settings.value('mainwindowgeometry'))
555 if self.settings.contains('openedmodel'):
556 modelfile = self.settings.value('openedmodel')
557 self.editor.model = loadModel(modelfile)
558 def closeEvent(self, ev):
559 self.settings.setValue('mainwindowstate', self.saveState())
560 self.settings.setValue('mainwindowgeometry', self.saveGeometry())
561 if self.editor.model and self.editor.model.filename:
562 self.settings.setValue('openedmodel', self.editor.model.filename)
563 # TODO: ask for save of opened files
564 else:
565 self.settings.remove('openedmodel')
566 ev.accept()
518 567
519 if __name__ == '__main__': 568 if __name__ == '__main__':
520 if sys.version_info.major != 3: 569 if sys.version_info.major != 3:
521 print('Please use python 3.x') 570 print('Please use python 3.x')
522 sys.exit(1) 571 sys.exit(1)
523 app = QApplication(sys.argv) 572 app = QApplication(sys.argv)
524 main = Main() 573 main = Main()
525 main.show() 574 main.show()
526 main.resize(700, 500)
527 main.editor.model = loadModel('diagram4.usd')
528 app.exec_() 575 app.exec_()
529 576