Mercurial > lcfOS
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'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGGklEQVR42s2XeWxURRjAv7d3L9ay\nlJBCbAx/SIIpCWilhNLSFsrR0qUgyCmnnKVFa/xDjNbUKGIUSrlaLhEREWlZaQoC24VymBivoGJB\n/MuEtEpjoNt2z/H7Zt+8fdPdGk2MOu23MzvvzXy/+Y6ZWQX+46L8bwC2vLTlZte9e6MMRiMYjQYY\nPWYsZGXngM1iBkUxoERej1RYayJmYGrF1Dbj/31+P1xtuwQ3vvmaP6M/h8Nx69Xq6kclgDWrn2W3\nbt+G5KQkSE9Ph/E5eTB1xixIQID+yIraEMoVJYLGay4KGNS2ty8Aza4muHqpVZvClmCDHbW1igSw\neuUq1tPTA2EWRohkmJCbD9OLS+Fu14M4ZlP6M8lwSvSdEUNTwNXYCFc87iiAzQY7d9XJACuXr2Dd\n3d1gNpvBarXCxMmFUDzLCUk2S3zfKTrlqmIlYgqtTfWDXh80NZ6CNvdFnQUSYNfuXTLAimXLmV5B\nTn4hlDrLICUxHoAC+tHSTNz1jIcCw48HvX60AAK0RgGsaIE9e/fIAMuWPiMB5BZMAWfZHBiUaI1r\nAaZ+Mi32VKVqX5h/ZzwGzpxuRAtckFywt36fDLB08RIZoHAqlDhnQ0eXdwDlosFAP1APRGVoahK0\nYBBedp+XLNCwv0EGWLxwUT+AIphZ6ozNghhlEQCtrZqeqRC+QAjOftqEFogCkAX2HzwgAyxasFAC\nmFQwFaaVEIAJ5P2K6T5ln2uu0LnEHwzBZ2ea4Epr1AUU5AcPH5IBFsx/Wg5CBCgqdoLVbBxg5Xov\nDGyFQCgMF5tPI8B5CeDwkfdkgPlPzYsBmDLTCffu98b3f78OzS4s+g7Vg5Ot4G5xwdV+AEeOvi8D\nzJszV5p7IgLkT3equ9zAAPo4EMpFAIpnnrMuuKYDsCDA0WMfyABzy+bIAPlTIG9aKfzZecV0y5dc\nIdosMvzyORdc90RjwGKxwLHjH8oAZc7ZEoBj6DAYOeoxPJiMYKIDymTkbYPBoB5CkRpABByDcDjM\nJUQSDEIwFIIwyk83b0DXrx0SwPETH8kAs2eVxrhXKsrfPLnZwNOZEeDEyY9lgNLiEvYXpv1HCgGc\nPPWJDFAys/jfA8AD71RTowxQPH3GgAAK+t2IQv7X4oC+q5cSKiIORCyEyP9qLfr1AI2u0zLAjKJp\ncQHWblgPGRkZ0Q7G4uwFch8d6xXlm3jw0qEUCARgOF5yRDGZTOBqPiMDFBUUxsxLh8aa9evAbrfz\nVYuVRvVG2uKZ6COrvFj1Ao92fiL2eME+yK4pM6EFms+2yAAFeZNjAawWWF9eDqmpqVBT/ZqWZn1+\nH7y5dat2LxRmp1qY/pUtL/NgY9ju6e3lVz29Bc5dOC8D5OVMigNghfLKCg7wXOVm3kd53e31Qv3+\nBq6clAUx5/v7/I2a17mvOXBfHyTgLUgDQNdc8LTKADnZE2IAiHRz1fPgGDIEKjeW8z7aZPwBP9TW\n1WlK9QAC4p1tb2sAPp8PbLgYunFb8HgPBILgabssA0zIejIGgHy4uaoKARywacPGCABO7sOr9rs7\ntsdEOwnBkNRu34EAJnQBAz++TwB8V0WoIAZl2/VrMkDWuMdjAJISE2EjuoCCsBKjOuJrVISTbt32\nlqZMrJyiXQDt2VnHldGYYDCA7rSBGS0awvEmowmufH5dBhibOeYu+nSYHiABAdZhGg4igIoKbkqa\nnLKjpqZGWrneErTihn31XGEEIAgJOIZixqim5hdffRkFSElOUfDX0LgR6cNbMECGaBbAyF21dg2v\n+UBdkFGu6wH0z0jhgfp6vlIeuPiMrmGiUNr+0P5jcp/P5xUWoDoN/bT8kYczqjH4rCILFi5ZAvaH\n7JKPRZtMLtJPCE1Oz44cOsSh1X0KrBaLHiD47fffjcTmL/ojLhFlNCovtJjNDmRi2dnjk57IyhqM\nsWBBf1pxQjMJzmgke6rKmULJwVgQm36E8nd2dNxv9Xh+//nOHR9Ivxj4WjGRQu24+mb88psegNqE\nSQlrJH9lZmYacnNzjWlpaQpmBBdKLXXHU9QNiIkdEa0T9nq94c7OzpDb7Q63t7crTD6WFRXEj0J3\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 |