src/eric7/Graphics/UMLGraphicsView.py

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

eric ide

mercurial