37 |
37 |
38 def __init__(self, scene, parent=None): |
38 def __init__(self, scene, parent=None): |
39 """ |
39 """ |
40 Constructor |
40 Constructor |
41 |
41 |
42 @param scene reference to the scene object (QGraphicsScene) |
42 @param scene reference to the scene object |
43 @param parent parent widget of the view (QWidget) |
43 @type QGraphicsScene |
|
44 @param parent parent widget of the view |
|
45 @type QWidget |
44 """ |
46 """ |
45 E5GraphicsView.__init__(self, scene, parent) |
47 E5GraphicsView.__init__(self, scene, parent) |
46 self.setObjectName("UMLGraphicsView") |
48 self.setObjectName("UMLGraphicsView") |
47 self.setViewportUpdateMode( |
49 self.setViewportUpdateMode( |
48 QGraphicsView.ViewportUpdateMode.FullViewportUpdate) |
50 QGraphicsView.ViewportUpdateMode.FullViewportUpdate) |
167 UI.PixmapCache.getIcon("shapesAlignBottom"), |
169 UI.PixmapCache.getIcon("shapesAlignBottom"), |
168 self.tr("Align Bottom"), self) |
170 self.tr("Align Bottom"), self) |
169 self.alignMapper.setMapping( |
171 self.alignMapper.setMapping( |
170 self.alignBottomAct, Qt.AlignmentFlag.AlignBottom) |
172 self.alignBottomAct, Qt.AlignmentFlag.AlignBottom) |
171 self.alignBottomAct.triggered.connect(self.alignMapper.map) |
173 self.alignBottomAct.triggered.connect(self.alignMapper.map) |
172 |
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 |
173 def __checkSizeActions(self): |
185 def __checkSizeActions(self): |
174 """ |
186 """ |
175 Private slot to set the enabled state of the size actions. |
187 Private slot to set the enabled state of the size actions. |
176 """ |
188 """ |
177 diagramSize = self._getDiagramSize(10) |
189 diagramSize = self._getDiagramSize(10) |
187 |
199 |
188 def __sceneChanged(self, areas): |
200 def __sceneChanged(self, areas): |
189 """ |
201 """ |
190 Private slot called when the scene changes. |
202 Private slot called when the scene changes. |
191 |
203 |
192 @param areas list of rectangles that contain changes (list of QRectF) |
204 @param areas list of rectangles that contain changes |
|
205 @type list of QRectF |
193 """ |
206 """ |
194 if len(self.scene().selectedItems()) > 0: |
207 if len(self.scene().selectedItems()) > 0: |
195 self.deleteShapeAct.setEnabled(True) |
208 self.deleteShapeAct.setEnabled(True) |
196 else: |
209 else: |
197 self.deleteShapeAct.setEnabled(False) |
210 self.deleteShapeAct.setEnabled(False) |
212 |
225 |
213 def initToolBar(self): |
226 def initToolBar(self): |
214 """ |
227 """ |
215 Public method to populate a toolbar with our actions. |
228 Public method to populate a toolbar with our actions. |
216 |
229 |
217 @return the populated toolBar (QToolBar) |
230 @return the populated toolBar |
|
231 @rtype QToolBar |
218 """ |
232 """ |
219 toolBar = QToolBar(self.tr("Graphics"), self) |
233 toolBar = QToolBar(self.tr("Graphics"), self) |
220 toolBar.setIconSize(UI.Config.ToolBarIconSize) |
234 toolBar.setIconSize(UI.Config.ToolBarIconSize) |
221 toolBar.addAction(self.deleteShapeAct) |
235 toolBar.addAction(self.deleteShapeAct) |
222 toolBar.addSeparator() |
236 toolBar.addSeparator() |
241 def filteredItems(self, items, itemType=UMLItem): |
255 def filteredItems(self, items, itemType=UMLItem): |
242 """ |
256 """ |
243 Public method to filter a list of items. |
257 Public method to filter a list of items. |
244 |
258 |
245 @param items list of items as returned by the scene object |
259 @param items list of items as returned by the scene object |
246 (QGraphicsItem) |
260 @type QGraphicsItem |
247 @param itemType type to be filtered (class) |
261 @param itemType type to be filtered |
248 @return list of interesting collision items (QGraphicsItem) |
262 @type class |
|
263 @return list of interesting collision items |
|
264 @rtype QGraphicsItem |
249 """ |
265 """ |
250 return [itm for itm in items if isinstance(itm, itemType)] |
266 return [itm for itm in items if isinstance(itm, itemType)] |
251 |
267 |
252 def selectItems(self, items): |
268 def selectItems(self, items): |
253 """ |
269 """ |
254 Public method to select the given items. |
270 Public method to select the given items. |
255 |
271 |
256 @param items list of items to be selected (list of QGraphicsItemItem) |
272 @param items list of items to be selected |
|
273 @type list of QGraphicsItemItem |
257 """ |
274 """ |
258 # step 1: deselect all items |
275 # step 1: deselect all items |
259 self.unselectItems() |
276 self.unselectItems() |
260 |
277 |
261 # step 2: select all given items |
278 # step 2: select all given items |
265 |
282 |
266 def selectItem(self, item): |
283 def selectItem(self, item): |
267 """ |
284 """ |
268 Public method to select an item. |
285 Public method to select an item. |
269 |
286 |
270 @param item item to be selected (QGraphicsItemItem) |
287 @param item item to be selected |
|
288 @type QGraphicsItemItem |
271 """ |
289 """ |
272 if isinstance(item, UMLItem): |
290 if isinstance(item, UMLItem): |
273 item.setSelected(not item.isSelected()) |
291 item.setSelected(not item.isSelected()) |
274 |
292 |
275 def __deleteShape(self): |
293 def __deleteShape(self): |
327 def autoAdjustSceneSize(self, limit=False): |
345 def autoAdjustSceneSize(self, limit=False): |
328 """ |
346 """ |
329 Public method to adjust the scene size to the diagram size. |
347 Public method to adjust the scene size to the diagram size. |
330 |
348 |
331 @param limit flag indicating to limit the scene to the |
349 @param limit flag indicating to limit the scene to the |
332 initial size (boolean) |
350 initial size |
|
351 @type bool |
333 """ |
352 """ |
334 super().autoAdjustSceneSize(limit=limit) |
353 super().autoAdjustSceneSize(limit=limit) |
335 self.__checkSizeActions() |
354 self.__checkSizeActions() |
336 |
355 |
337 def saveImage(self): |
356 def saveImage(self): |
409 |
428 |
410 def printDiagram(self): |
429 def printDiagram(self): |
411 """ |
430 """ |
412 Public slot called to print the diagram. |
431 Public slot called to print the diagram. |
413 """ |
432 """ |
414 printer = QPrinter(mode=QPrinter.PrinterMode.ScreenResolution) |
433 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution) |
415 printer.setFullPage(True) |
434 printer.setFullPage(True) |
416 if Preferences.getPrinter("ColorMode"): |
435 if Preferences.getPrinter("ColorMode"): |
417 printer.setColorMode(QPrinter.ColorMode.Color) |
436 printer.setColorMode(QPrinter.ColorMode.Color) |
418 else: |
437 else: |
419 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
438 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
441 """ |
460 """ |
442 Public slot called to show a print preview of the diagram. |
461 Public slot called to show a print preview of the diagram. |
443 """ |
462 """ |
444 from PyQt5.QtPrintSupport import QPrintPreviewDialog |
463 from PyQt5.QtPrintSupport import QPrintPreviewDialog |
445 |
464 |
446 printer = QPrinter(mode=QPrinter.PrinterMode.ScreenResolution) |
465 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution) |
447 printer.setFullPage(True) |
466 printer.setFullPage(True) |
448 if Preferences.getPrinter("ColorMode"): |
467 if Preferences.getPrinter("ColorMode"): |
449 printer.setColorMode(QPrinter.ColorMode.Color) |
468 printer.setColorMode(QPrinter.ColorMode.Color) |
450 else: |
469 else: |
451 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
470 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
470 |
489 |
471 def __printPreviewPrint(self, printer): |
490 def __printPreviewPrint(self, printer): |
472 """ |
491 """ |
473 Private slot to generate a print preview. |
492 Private slot to generate a print preview. |
474 |
493 |
475 @param printer reference to the printer object (QPrinter) |
494 @param printer reference to the printer object |
|
495 @type QPrinter |
476 """ |
496 """ |
477 super().printDiagram(printer, self.diagramName) |
497 super().printDiagram(printer, self.diagramName) |
478 |
498 |
479 def setDiagramName(self, name): |
499 def setDiagramName(self, name): |
480 """ |
500 """ |
481 Public slot to set the diagram name. |
501 Public slot to set the diagram name. |
482 |
502 |
483 @param name diagram name (string) |
503 @param name diagram name |
|
504 @type str |
484 """ |
505 """ |
485 self.diagramName = name |
506 self.diagramName = name |
486 |
507 |
487 def __alignShapes(self, alignment): |
508 def __alignShapes(self, alignment): |
488 """ |
509 """ |
489 Private slot to align the selected shapes. |
510 Private slot to align the selected shapes. |
490 |
511 |
491 @param alignment alignment type (Qt.AlignmentFlag) |
512 @param alignment alignment type |
|
513 @type Qt.AlignmentFlag |
492 """ |
514 """ |
493 # step 1: get all selected items |
515 # step 1: get all selected items |
494 items = self.scene().selectedItems() |
516 items = self.scene().selectedItems() |
495 if len(items) <= 1: |
517 if len(items) <= 1: |
496 return |
518 return |
562 |
584 |
563 def __itemsBoundingRect(self, items): |
585 def __itemsBoundingRect(self, items): |
564 """ |
586 """ |
565 Private method to calculate the bounding rectangle of the given items. |
587 Private method to calculate the bounding rectangle of the given items. |
566 |
588 |
567 @param items list of items to operate on (list of UMLItem) |
589 @param items list of items to operate on |
568 @return bounding rectangle (QRectF) |
590 @type list of UMLItem |
|
591 @return bounding rectangle |
|
592 @rtype QRectF |
569 """ |
593 """ |
570 rect = self.scene().sceneRect() |
594 rect = self.scene().sceneRect() |
571 right = rect.left() |
595 right = rect.left() |
572 bottom = rect.top() |
596 bottom = rect.top() |
573 left = rect.right() |
597 left = rect.right() |
582 |
606 |
583 def keyPressEvent(self, evt): |
607 def keyPressEvent(self, evt): |
584 """ |
608 """ |
585 Protected method handling key press events. |
609 Protected method handling key press events. |
586 |
610 |
587 @param evt reference to the key event (QKeyEvent) |
611 @param evt reference to the key event |
|
612 @type QKeyEvent |
588 """ |
613 """ |
589 key = evt.key() |
614 key = evt.key() |
590 if key in [Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left, |
615 if key in [Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Left, |
591 Qt.Key.Key_Right]: |
616 Qt.Key.Key_Right]: |
592 items = self.filteredItems(self.scene().selectedItems()) |
617 items = self.filteredItems(self.scene().selectedItems()) |
616 |
641 |
617 def wheelEvent(self, evt): |
642 def wheelEvent(self, evt): |
618 """ |
643 """ |
619 Protected method to handle wheel events. |
644 Protected method to handle wheel events. |
620 |
645 |
621 @param evt reference to the wheel event (QWheelEvent) |
646 @param evt reference to the wheel event |
|
647 @type QWheelEvent |
622 """ |
648 """ |
623 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: |
649 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: |
624 delta = evt.angleDelta().y() |
650 delta = evt.angleDelta().y() |
625 if delta < 0: |
651 if delta < 0: |
626 self.zoomOut() |
652 self.zoomOut() |
633 |
659 |
634 def event(self, evt): |
660 def event(self, evt): |
635 """ |
661 """ |
636 Public method handling events. |
662 Public method handling events. |
637 |
663 |
638 @param evt reference to the event (QEvent) |
664 @param evt reference to the event |
639 @return flag indicating, if the event was handled (boolean) |
665 @type QEvent |
|
666 @return flag indicating, if the event was handled |
|
667 @rtype bool |
640 """ |
668 """ |
641 if evt.type() == QEvent.Type.Gesture: |
669 if evt.type() == QEvent.Type.Gesture: |
642 self.gestureEvent(evt) |
670 self.gestureEvent(evt) |
643 return True |
671 return True |
644 |
672 |
646 |
674 |
647 def gestureEvent(self, evt): |
675 def gestureEvent(self, evt): |
648 """ |
676 """ |
649 Protected method handling gesture events. |
677 Protected method handling gesture events. |
650 |
678 |
651 @param evt reference to the gesture event (QGestureEvent |
679 @param evt reference to the gesture event |
|
680 @type QGestureEvent |
652 """ |
681 """ |
653 pinch = evt.gesture(Qt.GestureType.PinchGesture) |
682 pinch = evt.gesture(Qt.GestureType.PinchGesture) |
654 if pinch: |
683 if pinch: |
655 if pinch.state() == Qt.GestureState.GestureStarted: |
684 if pinch.state() == Qt.GestureState.GestureStarted: |
656 pinch.setTotalScaleFactor(self.zoom() / 100.0) |
685 pinch.setTotalScaleFactor(self.zoom() / 100.0) |
660 |
689 |
661 def getItemId(self): |
690 def getItemId(self): |
662 """ |
691 """ |
663 Public method to get the ID to be assigned to an item. |
692 Public method to get the ID to be assigned to an item. |
664 |
693 |
665 @return item ID (integer) |
694 @return item ID |
|
695 @rtype int |
666 """ |
696 """ |
667 self.__itemId += 1 |
697 self.__itemId += 1 |
668 return self.__itemId |
698 return self.__itemId |
669 |
699 |
670 def findItem(self, itemId): |
700 def findItem(self, itemId): |
671 """ |
701 """ |
672 Public method to find an UML item based on the ID. |
702 Public method to find an UML item based on the ID. |
673 |
703 |
674 @param itemId of the item to search for (integer) |
704 @param itemId of the item to search for |
675 @return item found (UMLItem) or None |
705 @type int |
|
706 @return item found or None |
|
707 @rtype UMLItem |
676 """ |
708 """ |
677 for item in self.scene().items(): |
709 for item in self.scene().items(): |
678 try: |
710 try: |
679 if item.getId() == itemId: |
711 if item.getId() == itemId: |
680 return item |
712 return item |
685 |
717 |
686 def findItemByName(self, name): |
718 def findItemByName(self, name): |
687 """ |
719 """ |
688 Public method to find an UML item based on its name. |
720 Public method to find an UML item based on its name. |
689 |
721 |
690 @param name name to look for (string) |
722 @param name name to look for |
691 @return item found (UMLItem) or None |
723 @type str |
|
724 @return item found or None |
|
725 @rtype UMLItem |
692 """ |
726 """ |
693 for item in self.scene().items(): |
727 for item in self.scene().items(): |
694 try: |
728 try: |
695 if item.getName() == name: |
729 if item.getName() == name: |
696 return item |
730 return item |
723 |
758 |
724 def parsePersistenceData(self, version, data): |
759 def parsePersistenceData(self, version, data): |
725 """ |
760 """ |
726 Public method to parse persisted data. |
761 Public method to parse persisted data. |
727 |
762 |
728 @param version version of the data (string) |
763 @param version version of the data |
729 @param data persisted data to be parsed (list of string) |
764 @type str |
|
765 @param data persisted data to be parsed |
|
766 @type list of str |
730 @return tuple of flag indicating success (boolean) and faulty line |
767 @return tuple of flag indicating success (boolean) and faulty line |
731 number (integer) |
768 number |
|
769 @rtype int |
732 """ |
770 """ |
733 umlItems = {} |
771 umlItems = {} |
734 |
772 |
735 if not data[0].startswith("diagram_name:"): |
773 if not data[0].startswith("diagram_name:"): |
736 return False, 0 |
774 return False, 0 |
752 itemId = int(itemId.split("=", 1)[1].strip()) |
790 itemId = int(itemId.split("=", 1)[1].strip()) |
753 x = float(x.split("=", 1)[1].strip()) |
791 x = float(x.split("=", 1)[1].strip()) |
754 y = float(y.split("=", 1)[1].strip()) |
792 y = float(y.split("=", 1)[1].strip()) |
755 itemType = itemType.split("=", 1)[1].strip() |
793 itemType = itemType.split("=", 1)[1].strip() |
756 if itemType == ClassItem.ItemType: |
794 if itemType == ClassItem.ItemType: |
757 itm = ClassItem(x=x, y=y, scene=self.scene(), |
795 itm = ClassItem(x=0, y=0, scene=self.scene(), |
758 colors=self.getDrawingColors()) |
796 colors=self.getDrawingColors()) |
759 elif itemType == ModuleItem.ItemType: |
797 elif itemType == ModuleItem.ItemType: |
760 itm = ModuleItem(x=x, y=y, scene=self.scene(), |
798 itm = ModuleItem(x=0, y=0, scene=self.scene(), |
761 colors=self.getDrawingColors()) |
799 colors=self.getDrawingColors()) |
762 elif itemType == PackageItem.ItemType: |
800 elif itemType == PackageItem.ItemType: |
763 itm = PackageItem(x=x, y=y, scene=self.scene(), |
801 itm = PackageItem(x=0, y=0, scene=self.scene(), |
764 colors=self.getDrawingColors()) |
802 colors=self.getDrawingColors()) |
|
803 itm.setPos(x, y) |
765 itm.setId(itemId) |
804 itm.setId(itemId) |
766 umlItems[itemId] = itm |
805 umlItems[itemId] = itm |
767 if not itm.parseItemDataString(version, itemData): |
806 if not itm.parseItemDataString(version, itemData): |
768 return False, linenum |
807 return False, linenum |
769 except ValueError: |
808 except ValueError: |
776 assoc = AssociationItem(umlItems[srcId], umlItems[dstId], |
815 assoc = AssociationItem(umlItems[srcId], umlItems[dstId], |
777 assocType, topToBottom) |
816 assocType, topToBottom) |
778 self.scene().addItem(assoc) |
817 self.scene().addItem(assoc) |
779 |
818 |
780 return True, -1 |
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 |