eric7/Graphics/UMLGraphicsView.py

branch
eric7
changeset 8312
800c432b34c8
parent 8295
3f5e8b0a338e
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a subclass of E5GraphicsView for our diagrams.
8 """
9
10 from PyQt5.QtCore import (
11 pyqtSignal, Qt, QSignalMapper, QFileInfo, QEvent, QRectF
12 )
13 from PyQt5.QtWidgets import QGraphicsView, QAction, QToolBar, QDialog
14 from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
15
16 from E5Graphics.E5GraphicsView import E5GraphicsView
17
18 from E5Gui import E5MessageBox, E5FileDialog
19 from E5Gui.E5ZoomWidget import E5ZoomWidget
20
21 from .UMLItem import UMLItem
22
23 import UI.Config
24 import UI.PixmapCache
25
26 import Preferences
27
28
29 class UMLGraphicsView(E5GraphicsView):
30 """
31 Class implementing a specialized E5GraphicsView for our diagrams.
32
33 @signal relayout() emitted to indicate a relayout of the diagram
34 is requested
35 """
36 relayout = pyqtSignal()
37
38 def __init__(self, scene, parent=None):
39 """
40 Constructor
41
42 @param scene reference to the scene object
43 @type QGraphicsScene
44 @param parent parent widget of the view
45 @type QWidget
46 """
47 E5GraphicsView.__init__(self, scene, parent)
48 self.setObjectName("UMLGraphicsView")
49 self.setViewportUpdateMode(
50 QGraphicsView.ViewportUpdateMode.FullViewportUpdate)
51
52 self.diagramName = "Unnamed"
53 self.__itemId = -1
54
55 self.border = 10
56 self.deltaSize = 100.0
57
58 self.__zoomWidget = E5ZoomWidget(
59 UI.PixmapCache.getPixmap("zoomOut"),
60 UI.PixmapCache.getPixmap("zoomIn"),
61 UI.PixmapCache.getPixmap("zoomReset"), self)
62 parent.statusBar().addPermanentWidget(self.__zoomWidget)
63 self.__zoomWidget.setMapping(
64 E5GraphicsView.ZoomLevels, E5GraphicsView.ZoomLevelDefault)
65 self.__zoomWidget.valueChanged.connect(self.setZoom)
66 self.zoomValueChanged.connect(self.__zoomWidget.setValue)
67
68 self.__initActions()
69
70 scene.changed.connect(self.__sceneChanged)
71
72 self.grabGesture(Qt.GestureType.PinchGesture)
73
74 def __initActions(self):
75 """
76 Private method to initialize the view actions.
77 """
78 self.alignMapper = QSignalMapper(self)
79 try:
80 self.alignMapper.mappedInt.connect(self.__alignShapes)
81 except AttributeError:
82 # pre Qt 5.15
83 self.alignMapper.mapped[int].connect(self.__alignShapes)
84
85 self.deleteShapeAct = QAction(
86 UI.PixmapCache.getIcon("deleteShape"),
87 self.tr("Delete shapes"), self)
88 self.deleteShapeAct.triggered.connect(self.__deleteShape)
89
90 self.incWidthAct = QAction(
91 UI.PixmapCache.getIcon("sceneWidthInc"),
92 self.tr("Increase width by {0} points").format(
93 self.deltaSize),
94 self)
95 self.incWidthAct.triggered.connect(self.__incWidth)
96
97 self.incHeightAct = QAction(
98 UI.PixmapCache.getIcon("sceneHeightInc"),
99 self.tr("Increase height by {0} points").format(
100 self.deltaSize),
101 self)
102 self.incHeightAct.triggered.connect(self.__incHeight)
103
104 self.decWidthAct = QAction(
105 UI.PixmapCache.getIcon("sceneWidthDec"),
106 self.tr("Decrease width by {0} points").format(
107 self.deltaSize),
108 self)
109 self.decWidthAct.triggered.connect(self.__decWidth)
110
111 self.decHeightAct = QAction(
112 UI.PixmapCache.getIcon("sceneHeightDec"),
113 self.tr("Decrease height by {0} points").format(
114 self.deltaSize),
115 self)
116 self.decHeightAct.triggered.connect(self.__decHeight)
117
118 self.setSizeAct = QAction(
119 UI.PixmapCache.getIcon("sceneSize"),
120 self.tr("Set size"), self)
121 self.setSizeAct.triggered.connect(self.__setSize)
122
123 self.rescanAct = QAction(
124 UI.PixmapCache.getIcon("rescan"),
125 self.tr("Re-Scan"), self)
126 self.rescanAct.triggered.connect(self.__rescan)
127
128 self.relayoutAct = QAction(
129 UI.PixmapCache.getIcon("relayout"),
130 self.tr("Re-Layout"), self)
131 self.relayoutAct.triggered.connect(self.__relayout)
132
133 self.alignLeftAct = QAction(
134 UI.PixmapCache.getIcon("shapesAlignLeft"),
135 self.tr("Align Left"), self)
136 self.alignMapper.setMapping(
137 self.alignLeftAct, Qt.AlignmentFlag.AlignLeft)
138 self.alignLeftAct.triggered.connect(self.alignMapper.map)
139
140 self.alignHCenterAct = QAction(
141 UI.PixmapCache.getIcon("shapesAlignHCenter"),
142 self.tr("Align Center Horizontal"), self)
143 self.alignMapper.setMapping(
144 self.alignHCenterAct, Qt.AlignmentFlag.AlignHCenter)
145 self.alignHCenterAct.triggered.connect(self.alignMapper.map)
146
147 self.alignRightAct = QAction(
148 UI.PixmapCache.getIcon("shapesAlignRight"),
149 self.tr("Align Right"), self)
150 self.alignMapper.setMapping(
151 self.alignRightAct, Qt.AlignmentFlag.AlignRight)
152 self.alignRightAct.triggered.connect(self.alignMapper.map)
153
154 self.alignTopAct = QAction(
155 UI.PixmapCache.getIcon("shapesAlignTop"),
156 self.tr("Align Top"), self)
157 self.alignMapper.setMapping(
158 self.alignTopAct, Qt.AlignmentFlag.AlignTop)
159 self.alignTopAct.triggered.connect(self.alignMapper.map)
160
161 self.alignVCenterAct = QAction(
162 UI.PixmapCache.getIcon("shapesAlignVCenter"),
163 self.tr("Align Center Vertical"), self)
164 self.alignMapper.setMapping(
165 self.alignVCenterAct, Qt.AlignmentFlag.AlignVCenter)
166 self.alignVCenterAct.triggered.connect(self.alignMapper.map)
167
168 self.alignBottomAct = QAction(
169 UI.PixmapCache.getIcon("shapesAlignBottom"),
170 self.tr("Align Bottom"), self)
171 self.alignMapper.setMapping(
172 self.alignBottomAct, Qt.AlignmentFlag.AlignBottom)
173 self.alignBottomAct.triggered.connect(self.alignMapper.map)
174
175 def setLayoutActionsEnabled(self, enable):
176 """
177 Public method to enable or disable the layout related actions.
178
179 @param enable flag indicating the desired enable state
180 @type bool
181 """
182 self.rescanAct.setEnabled(enable)
183 self.relayoutAct.setEnabled(enable)
184
185 def __checkSizeActions(self):
186 """
187 Private slot to set the enabled state of the size actions.
188 """
189 diagramSize = self._getDiagramSize(10)
190 sceneRect = self.scene().sceneRect()
191 if (sceneRect.width() - self.deltaSize) < diagramSize.width():
192 self.decWidthAct.setEnabled(False)
193 else:
194 self.decWidthAct.setEnabled(True)
195 if (sceneRect.height() - self.deltaSize) < diagramSize.height():
196 self.decHeightAct.setEnabled(False)
197 else:
198 self.decHeightAct.setEnabled(True)
199
200 def __sceneChanged(self, areas):
201 """
202 Private slot called when the scene changes.
203
204 @param areas list of rectangles that contain changes
205 @type list of QRectF
206 """
207 if len(self.scene().selectedItems()) > 0:
208 self.deleteShapeAct.setEnabled(True)
209 else:
210 self.deleteShapeAct.setEnabled(False)
211
212 sceneRect = self.scene().sceneRect()
213 newWidth = width = sceneRect.width()
214 newHeight = height = sceneRect.height()
215 rect = self.scene().itemsBoundingRect()
216 # calculate with 10 pixel border on each side
217 if sceneRect.right() - 10 < rect.right():
218 newWidth = rect.right() + 10
219 if sceneRect.bottom() - 10 < rect.bottom():
220 newHeight = rect.bottom() + 10
221
222 if newHeight != height or newWidth != width:
223 self.setSceneSize(newWidth, newHeight)
224 self.__checkSizeActions()
225
226 def initToolBar(self):
227 """
228 Public method to populate a toolbar with our actions.
229
230 @return the populated toolBar
231 @rtype QToolBar
232 """
233 toolBar = QToolBar(self.tr("Graphics"), self)
234 toolBar.setIconSize(UI.Config.ToolBarIconSize)
235 toolBar.addAction(self.deleteShapeAct)
236 toolBar.addSeparator()
237 toolBar.addAction(self.alignLeftAct)
238 toolBar.addAction(self.alignHCenterAct)
239 toolBar.addAction(self.alignRightAct)
240 toolBar.addAction(self.alignTopAct)
241 toolBar.addAction(self.alignVCenterAct)
242 toolBar.addAction(self.alignBottomAct)
243 toolBar.addSeparator()
244 toolBar.addAction(self.incWidthAct)
245 toolBar.addAction(self.incHeightAct)
246 toolBar.addAction(self.decWidthAct)
247 toolBar.addAction(self.decHeightAct)
248 toolBar.addAction(self.setSizeAct)
249 toolBar.addSeparator()
250 toolBar.addAction(self.rescanAct)
251 toolBar.addAction(self.relayoutAct)
252
253 return toolBar
254
255 def filteredItems(self, items, itemType=UMLItem):
256 """
257 Public method to filter a list of items.
258
259 @param items list of items as returned by the scene object
260 @type QGraphicsItem
261 @param itemType type to be filtered
262 @type class
263 @return list of interesting collision items
264 @rtype QGraphicsItem
265 """
266 return [itm for itm in items if isinstance(itm, itemType)]
267
268 def selectItems(self, items):
269 """
270 Public method to select the given items.
271
272 @param items list of items to be selected
273 @type list of QGraphicsItemItem
274 """
275 # step 1: deselect all items
276 self.unselectItems()
277
278 # step 2: select all given items
279 for itm in items:
280 if isinstance(itm, UMLItem):
281 itm.setSelected(True)
282
283 def selectItem(self, item):
284 """
285 Public method to select an item.
286
287 @param item item to be selected
288 @type QGraphicsItemItem
289 """
290 if isinstance(item, UMLItem):
291 item.setSelected(not item.isSelected())
292
293 def __deleteShape(self):
294 """
295 Private method to delete the selected shapes from the display.
296 """
297 for item in self.scene().selectedItems():
298 item.removeAssociations()
299 item.setSelected(False)
300 self.scene().removeItem(item)
301 del item
302
303 def __incWidth(self):
304 """
305 Private method to handle the increase width context menu entry.
306 """
307 self.resizeScene(self.deltaSize, True)
308 self.__checkSizeActions()
309
310 def __incHeight(self):
311 """
312 Private method to handle the increase height context menu entry.
313 """
314 self.resizeScene(self.deltaSize, False)
315 self.__checkSizeActions()
316
317 def __decWidth(self):
318 """
319 Private method to handle the decrease width context menu entry.
320 """
321 self.resizeScene(-self.deltaSize, True)
322 self.__checkSizeActions()
323
324 def __decHeight(self):
325 """
326 Private method to handle the decrease height context menu entry.
327 """
328 self.resizeScene(-self.deltaSize, False)
329 self.__checkSizeActions()
330
331 def __setSize(self):
332 """
333 Private method to handle the set size context menu entry.
334 """
335 from .UMLSceneSizeDialog import UMLSceneSizeDialog
336 rect = self._getDiagramRect(10)
337 sceneRect = self.scene().sceneRect()
338 dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(),
339 rect.width(), rect.height(), self)
340 if dlg.exec() == QDialog.DialogCode.Accepted:
341 width, height = dlg.getData()
342 self.setSceneSize(width, height)
343 self.__checkSizeActions()
344
345 def autoAdjustSceneSize(self, limit=False):
346 """
347 Public method to adjust the scene size to the diagram size.
348
349 @param limit flag indicating to limit the scene to the
350 initial size
351 @type bool
352 """
353 super().autoAdjustSceneSize(limit=limit)
354 self.__checkSizeActions()
355
356 def saveImage(self):
357 """
358 Public method to handle the save context menu entry.
359 """
360 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
361 self,
362 self.tr("Save Diagram"),
363 "",
364 self.tr("Portable Network Graphics (*.png);;"
365 "Scalable Vector Graphics (*.svg)"),
366 "",
367 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
368 if fname:
369 ext = QFileInfo(fname).suffix()
370 if not ext:
371 ex = selectedFilter.split("(*")[1].split(")")[0]
372 if ex:
373 fname += ex
374 if QFileInfo(fname).exists():
375 res = E5MessageBox.yesNo(
376 self,
377 self.tr("Save Diagram"),
378 self.tr("<p>The file <b>{0}</b> already exists."
379 " Overwrite it?</p>").format(fname),
380 icon=E5MessageBox.Warning)
381 if not res:
382 return
383
384 success = super().saveImage(
385 fname, QFileInfo(fname).suffix().upper())
386 if not success:
387 E5MessageBox.critical(
388 self,
389 self.tr("Save Diagram"),
390 self.tr(
391 """<p>The file <b>{0}</b> could not be saved.</p>""")
392 .format(fname))
393
394 def __relayout(self):
395 """
396 Private slot to handle the re-layout context menu entry.
397 """
398 self.__itemId = -1
399 self.scene().clear()
400 self.relayout.emit()
401
402 def __rescan(self):
403 """
404 Private slot to handle the re-scan context menu entry.
405 """
406 # 1. save positions of all items and names of selected items
407 itemPositions = {}
408 selectedItems = []
409 for item in self.filteredItems(self.scene().items(), UMLItem):
410 name = item.getName()
411 if name:
412 itemPositions[name] = (item.x(), item.y())
413 if item.isSelected():
414 selectedItems.append(name)
415
416 # 2. save
417
418 # 2. re-layout the diagram
419 self.__relayout()
420
421 # 3. move known items to the saved positions
422 for item in self.filteredItems(self.scene().items(), UMLItem):
423 name = item.getName()
424 if name in itemPositions:
425 item.setPos(*itemPositions[name])
426 if name in selectedItems:
427 item.setSelected(True)
428
429 def printDiagram(self):
430 """
431 Public slot called to print the diagram.
432 """
433 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution)
434 printer.setFullPage(True)
435 if Preferences.getPrinter("ColorMode"):
436 printer.setColorMode(QPrinter.ColorMode.Color)
437 else:
438 printer.setColorMode(QPrinter.ColorMode.GrayScale)
439 if Preferences.getPrinter("FirstPageFirst"):
440 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst)
441 else:
442 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst)
443 printer.setPageMargins(
444 Preferences.getPrinter("LeftMargin") * 10,
445 Preferences.getPrinter("TopMargin") * 10,
446 Preferences.getPrinter("RightMargin") * 10,
447 Preferences.getPrinter("BottomMargin") * 10,
448 QPrinter.Unit.Millimeter
449 )
450 printerName = Preferences.getPrinter("PrinterName")
451 if printerName:
452 printer.setPrinterName(printerName)
453
454 printDialog = QPrintDialog(printer, self)
455 if printDialog.exec():
456 super().printDiagram(
457 printer, self.diagramName)
458
459 def printPreviewDiagram(self):
460 """
461 Public slot called to show a print preview of the diagram.
462 """
463 from PyQt5.QtPrintSupport import QPrintPreviewDialog
464
465 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution)
466 printer.setFullPage(True)
467 if Preferences.getPrinter("ColorMode"):
468 printer.setColorMode(QPrinter.ColorMode.Color)
469 else:
470 printer.setColorMode(QPrinter.ColorMode.GrayScale)
471 if Preferences.getPrinter("FirstPageFirst"):
472 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst)
473 else:
474 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst)
475 printer.setPageMargins(
476 Preferences.getPrinter("LeftMargin") * 10,
477 Preferences.getPrinter("TopMargin") * 10,
478 Preferences.getPrinter("RightMargin") * 10,
479 Preferences.getPrinter("BottomMargin") * 10,
480 QPrinter.Unit.Millimeter
481 )
482 printerName = Preferences.getPrinter("PrinterName")
483 if printerName:
484 printer.setPrinterName(printerName)
485
486 preview = QPrintPreviewDialog(printer, self)
487 preview.paintRequested[QPrinter].connect(self.__printPreviewPrint)
488 preview.exec()
489
490 def __printPreviewPrint(self, printer):
491 """
492 Private slot to generate a print preview.
493
494 @param printer reference to the printer object
495 @type QPrinter
496 """
497 super().printDiagram(printer, self.diagramName)
498
499 def setDiagramName(self, name):
500 """
501 Public slot to set the diagram name.
502
503 @param name diagram name
504 @type str
505 """
506 self.diagramName = name
507
508 def __alignShapes(self, alignment):
509 """
510 Private slot to align the selected shapes.
511
512 @param alignment alignment type
513 @type Qt.AlignmentFlag
514 """
515 # step 1: get all selected items
516 items = self.scene().selectedItems()
517 if len(items) <= 1:
518 return
519
520 # step 2: find the index of the item to align in relation to
521 amount = None
522 for i, item in enumerate(items):
523 rect = item.sceneBoundingRect()
524 if alignment == Qt.AlignmentFlag.AlignLeft:
525 if amount is None or rect.x() < amount:
526 amount = rect.x()
527 index = i
528 elif alignment == Qt.AlignmentFlag.AlignRight:
529 if amount is None or rect.x() + rect.width() > amount:
530 amount = rect.x() + rect.width()
531 index = i
532 elif alignment == Qt.AlignmentFlag.AlignHCenter:
533 if amount is None or rect.width() > amount:
534 amount = rect.width()
535 index = i
536 elif alignment == Qt.AlignmentFlag.AlignTop:
537 if amount is None or rect.y() < amount:
538 amount = rect.y()
539 index = i
540 elif alignment == Qt.AlignmentFlag.AlignBottom:
541 if amount is None or rect.y() + rect.height() > amount:
542 amount = rect.y() + rect.height()
543 index = i
544 elif alignment == Qt.AlignmentFlag.AlignVCenter:
545 # __IGNORE_WARNING_Y102__
546 if amount is None or rect.height() > amount:
547 amount = rect.height()
548 index = i
549 rect = items[index].sceneBoundingRect()
550
551 # step 3: move the other items
552 for i, item in enumerate(items):
553 if i == index:
554 continue
555 itemrect = item.sceneBoundingRect()
556 xOffset = yOffset = 0
557 if alignment == Qt.AlignmentFlag.AlignLeft:
558 xOffset = rect.x() - itemrect.x()
559 elif alignment == Qt.AlignmentFlag.AlignRight:
560 xOffset = (
561 (rect.x() + rect.width()) -
562 (itemrect.x() + itemrect.width())
563 )
564 elif alignment == Qt.AlignmentFlag.AlignHCenter:
565 xOffset = (
566 (rect.x() + rect.width() // 2) -
567 (itemrect.x() + itemrect.width() // 2)
568 )
569 elif alignment == Qt.AlignmentFlag.AlignTop:
570 yOffset = rect.y() - itemrect.y()
571 elif alignment == Qt.AlignmentFlag.AlignBottom:
572 yOffset = (
573 (rect.y() + rect.height()) -
574 (itemrect.y() + itemrect.height())
575 )
576 elif alignment == Qt.AlignmentFlag.AlignVCenter:
577 yOffset = (
578 (rect.y() + rect.height() // 2) -
579 (itemrect.y() + itemrect.height() // 2)
580 )
581 item.moveBy(xOffset, yOffset)
582
583 self.scene().update()
584
585 def __itemsBoundingRect(self, items):
586 """
587 Private method to calculate the bounding rectangle of the given items.
588
589 @param items list of items to operate on
590 @type list of UMLItem
591 @return bounding rectangle
592 @rtype QRectF
593 """
594 rect = self.scene().sceneRect()
595 right = rect.left()
596 bottom = rect.top()
597 left = rect.right()
598 top = rect.bottom()
599 for item in items:
600 rect = item.sceneBoundingRect()
601 left = min(rect.left(), left)
602 right = max(rect.right(), right)
603 top = min(rect.top(), top)
604 bottom = max(rect.bottom(), bottom)
605 return QRectF(left, top, right - left, bottom - top)
606
607 def keyPressEvent(self, evt):
608 """
609 Protected method handling key press events.
610
611 @param evt reference to the key event
612 @type QKeyEvent
613 """
614 key = evt.key()
615 if key in [Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left,
616 Qt.Key.Key_Right]:
617 items = self.filteredItems(self.scene().selectedItems())
618 if items:
619 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
620 stepSize = 50
621 else:
622 stepSize = 5
623 if key == Qt.Key.Key_Up:
624 dx = 0
625 dy = -stepSize
626 elif key == Qt.Key.Key_Down:
627 dx = 0
628 dy = stepSize
629 elif key == Qt.Key.Key_Left:
630 dx = -stepSize
631 dy = 0
632 else:
633 dx = stepSize
634 dy = 0
635 for item in items:
636 item.moveBy(dx, dy)
637 evt.accept()
638 return
639
640 super().keyPressEvent(evt)
641
642 def wheelEvent(self, evt):
643 """
644 Protected method to handle wheel events.
645
646 @param evt reference to the wheel event
647 @type QWheelEvent
648 """
649 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
650 delta = evt.angleDelta().y()
651 if delta < 0:
652 self.zoomOut()
653 elif delta > 0:
654 self.zoomIn()
655 evt.accept()
656 return
657
658 super().wheelEvent(evt)
659
660 def event(self, evt):
661 """
662 Public method handling events.
663
664 @param evt reference to the event
665 @type QEvent
666 @return flag indicating, if the event was handled
667 @rtype bool
668 """
669 if evt.type() == QEvent.Type.Gesture:
670 self.gestureEvent(evt)
671 return True
672
673 return super().event(evt)
674
675 def gestureEvent(self, evt):
676 """
677 Protected method handling gesture events.
678
679 @param evt reference to the gesture event
680 @type QGestureEvent
681 """
682 pinch = evt.gesture(Qt.GestureType.PinchGesture)
683 if pinch:
684 if pinch.state() == Qt.GestureState.GestureStarted:
685 pinch.setTotalScaleFactor(self.zoom() / 100.0)
686 elif pinch.state() == Qt.GestureState.GestureUpdated:
687 self.setZoom(int(pinch.totalScaleFactor() * 100))
688 evt.accept()
689
690 def getItemId(self):
691 """
692 Public method to get the ID to be assigned to an item.
693
694 @return item ID
695 @rtype int
696 """
697 self.__itemId += 1
698 return self.__itemId
699
700 def findItem(self, itemId):
701 """
702 Public method to find an UML item based on the ID.
703
704 @param itemId of the item to search for
705 @type int
706 @return item found or None
707 @rtype UMLItem
708 """
709 for item in self.scene().items():
710 try:
711 if item.getId() == itemId:
712 return item
713 except AttributeError:
714 continue
715
716 return None
717
718 def findItemByName(self, name):
719 """
720 Public method to find an UML item based on its name.
721
722 @param name name to look for
723 @type str
724 @return item found or None
725 @rtype UMLItem
726 """
727 for item in self.scene().items():
728 try:
729 if item.getName() == name:
730 return item
731 except AttributeError:
732 continue
733
734 return None
735
736 def getPersistenceData(self):
737 """
738 Public method to get a list of data to be persisted.
739
740 @return list of data to be persisted
741 @rtype list of str
742 """
743 lines = [
744 "diagram_name: {0}".format(self.diagramName),
745 ]
746
747 for item in self.filteredItems(self.scene().items(), UMLItem):
748 lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format(
749 item.getId(), item.x(), item.y(), item.getItemType(),
750 item.buildItemDataString()))
751
752 from .AssociationItem import AssociationItem
753 for item in self.filteredItems(self.scene().items(), AssociationItem):
754 lines.append("association: {0}".format(
755 item.buildAssociationItemDataString()))
756
757 return lines
758
759 def parsePersistenceData(self, version, data):
760 """
761 Public method to parse persisted data.
762
763 @param version version of the data
764 @type str
765 @param data persisted data to be parsed
766 @type list of str
767 @return tuple of flag indicating success (boolean) and faulty line
768 number
769 @rtype int
770 """
771 umlItems = {}
772
773 if not data[0].startswith("diagram_name:"):
774 return False, 0
775 self.diagramName = data[0].split(": ", 1)[1].strip()
776
777 from .ClassItem import ClassItem
778 from .ModuleItem import ModuleItem
779 from .PackageItem import PackageItem
780 from .AssociationItem import AssociationItem
781
782 for linenum, line in enumerate(data[1:], start=1):
783 if not line.startswith(("item:", "association:")):
784 return False, linenum
785
786 key, value = line.split(": ", 1)
787 if key == "item":
788 itemId, x, y, itemType, itemData = value.split(", ", 4)
789 try:
790 itemId = int(itemId.split("=", 1)[1].strip())
791 x = float(x.split("=", 1)[1].strip())
792 y = float(y.split("=", 1)[1].strip())
793 itemType = itemType.split("=", 1)[1].strip()
794 if itemType == ClassItem.ItemType:
795 itm = ClassItem(x=0, y=0, scene=self.scene(),
796 colors=self.getDrawingColors())
797 elif itemType == ModuleItem.ItemType:
798 itm = ModuleItem(x=0, y=0, scene=self.scene(),
799 colors=self.getDrawingColors())
800 elif itemType == PackageItem.ItemType:
801 itm = PackageItem(x=0, y=0, scene=self.scene(),
802 colors=self.getDrawingColors())
803 itm.setPos(x, y)
804 itm.setId(itemId)
805 umlItems[itemId] = itm
806 if not itm.parseItemDataString(version, itemData):
807 return False, linenum
808 except ValueError:
809 return False, linenum
810 elif key == "association":
811 srcId, dstId, assocType, topToBottom = (
812 AssociationItem.parseAssociationItemDataString(
813 value.strip())
814 )
815 assoc = AssociationItem(umlItems[srcId], umlItems[dstId],
816 assocType, topToBottom)
817 self.scene().addItem(assoc)
818
819 return True, -1
820
821 def toDict(self):
822 """
823 Public method to collect data to be persisted.
824
825 @return dictionary containing data to be persisted
826 @rtype dict
827 """
828 items = [
829 item.toDict()
830 for item in self.filteredItems(self.scene().items(), UMLItem)
831 ]
832
833 from .AssociationItem import AssociationItem
834 associations = [
835 assoc.toDict()
836 for assoc in self.filteredItems(self.scene().items(),
837 AssociationItem)
838 ]
839
840 data = {
841 "diagram_name": self.diagramName,
842 "items": items,
843 "associations": associations,
844 }
845
846 return data
847
848 def fromDict(self, version, data):
849 """
850 Public method to populate the class with data persisted by 'toDict()'.
851
852 @param version version of the data
853 @type str
854 @param data dictionary containing the persisted data
855 @type dict
856 @return flag indicating success
857 @rtype bool
858 """
859 from .UMLItem import UMLItem
860 from .ClassItem import ClassItem
861 from .ModuleItem import ModuleItem
862 from .PackageItem import PackageItem
863 from .AssociationItem import AssociationItem
864
865 umlItems = {}
866
867 try:
868 self.diagramName = data["diagram_name"]
869 for itemData in data["items"]:
870 if itemData["type"] == UMLItem.ItemType:
871 itm = UMLItem.fromDict(
872 itemData, colors=self.getDrawingColors())
873 elif itemData["type"] == ClassItem.ItemType:
874 itm = ClassItem.fromDict(
875 itemData, colors=self.getDrawingColors())
876 elif itemData["type"] == ModuleItem.ItemType:
877 itm = ModuleItem.fromDict(
878 itemData, colors=self.getDrawingColors())
879 elif itemData["type"] == PackageItem.ItemType:
880 itm = PackageItem.fromDict(
881 itemData, colors=self.getDrawingColors())
882 if itm is not None:
883 umlItems[itm.getId()] = itm
884 self.scene().addItem(itm)
885
886 for assocData in data["associations"]:
887 assoc = AssociationItem.fromDict(
888 assocData, umlItems, colors=self.getDrawingColors())
889 self.scene().addItem(assoc)
890
891 return True
892 except KeyError:
893 return False

eric ide

mercurial