30 |
28 |
31 |
29 |
32 class UMLGraphicsView(EricGraphicsView): |
30 class UMLGraphicsView(EricGraphicsView): |
33 """ |
31 """ |
34 Class implementing a specialized EricGraphicsView for our diagrams. |
32 Class implementing a specialized EricGraphicsView for our diagrams. |
35 |
33 |
36 @signal relayout() emitted to indicate a relayout of the diagram |
34 @signal relayout() emitted to indicate a relayout of the diagram |
37 is requested |
35 is requested |
38 """ |
36 """ |
|
37 |
39 relayout = pyqtSignal() |
38 relayout = pyqtSignal() |
40 |
39 |
41 def __init__(self, scene, parent=None): |
40 def __init__(self, scene, parent=None): |
42 """ |
41 """ |
43 Constructor |
42 Constructor |
44 |
43 |
45 @param scene reference to the scene object |
44 @param scene reference to the scene object |
46 @type QGraphicsScene |
45 @type QGraphicsScene |
47 @param parent parent widget of the view |
46 @param parent parent widget of the view |
48 @type QWidget |
47 @type QWidget |
49 """ |
48 """ |
50 EricGraphicsView.__init__(self, scene, parent) |
49 EricGraphicsView.__init__(self, scene, parent) |
51 self.setObjectName("UMLGraphicsView") |
50 self.setObjectName("UMLGraphicsView") |
52 self.setViewportUpdateMode( |
51 self.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate) |
53 QGraphicsView.ViewportUpdateMode.FullViewportUpdate) |
52 |
54 |
|
55 self.diagramName = "Unnamed" |
53 self.diagramName = "Unnamed" |
56 self.__itemId = -1 |
54 self.__itemId = -1 |
57 |
55 |
58 self.border = 10 |
56 self.border = 10 |
59 self.deltaSize = 100.0 |
57 self.deltaSize = 100.0 |
60 |
58 |
61 self.__zoomWidget = EricZoomWidget( |
59 self.__zoomWidget = EricZoomWidget( |
62 UI.PixmapCache.getPixmap("zoomOut"), |
60 UI.PixmapCache.getPixmap("zoomOut"), |
63 UI.PixmapCache.getPixmap("zoomIn"), |
61 UI.PixmapCache.getPixmap("zoomIn"), |
64 UI.PixmapCache.getPixmap("zoomReset"), self) |
62 UI.PixmapCache.getPixmap("zoomReset"), |
|
63 self, |
|
64 ) |
65 parent.statusBar().addPermanentWidget(self.__zoomWidget) |
65 parent.statusBar().addPermanentWidget(self.__zoomWidget) |
66 self.__zoomWidget.setMapping( |
66 self.__zoomWidget.setMapping( |
67 EricGraphicsView.ZoomLevels, EricGraphicsView.ZoomLevelDefault) |
67 EricGraphicsView.ZoomLevels, EricGraphicsView.ZoomLevelDefault |
|
68 ) |
68 self.__zoomWidget.valueChanged.connect(self.setZoom) |
69 self.__zoomWidget.valueChanged.connect(self.setZoom) |
69 self.zoomValueChanged.connect(self.__zoomWidget.setValue) |
70 self.zoomValueChanged.connect(self.__zoomWidget.setValue) |
70 |
71 |
71 self.__initActions() |
72 self.__initActions() |
72 |
73 |
73 scene.changed.connect(self.__sceneChanged) |
74 scene.changed.connect(self.__sceneChanged) |
74 |
75 |
75 self.grabGesture(Qt.GestureType.PinchGesture) |
76 self.grabGesture(Qt.GestureType.PinchGesture) |
76 |
77 |
77 def __initActions(self): |
78 def __initActions(self): |
78 """ |
79 """ |
79 Private method to initialize the view actions. |
80 Private method to initialize the view actions. |
80 """ |
81 """ |
81 self.alignMapper = QSignalMapper(self) |
82 self.alignMapper = QSignalMapper(self) |
82 try: |
83 try: |
83 self.alignMapper.mappedInt.connect(self.__alignShapes) |
84 self.alignMapper.mappedInt.connect(self.__alignShapes) |
84 except AttributeError: |
85 except AttributeError: |
85 # pre Qt 5.15 |
86 # pre Qt 5.15 |
86 self.alignMapper.mapped[int].connect(self.__alignShapes) |
87 self.alignMapper.mapped[int].connect(self.__alignShapes) |
87 |
88 |
88 self.deleteShapeAct = QAction( |
89 self.deleteShapeAct = QAction( |
89 UI.PixmapCache.getIcon("deleteShape"), |
90 UI.PixmapCache.getIcon("deleteShape"), self.tr("Delete shapes"), self |
90 self.tr("Delete shapes"), self) |
91 ) |
91 self.deleteShapeAct.triggered.connect(self.__deleteShape) |
92 self.deleteShapeAct.triggered.connect(self.__deleteShape) |
92 |
93 |
93 self.incWidthAct = QAction( |
94 self.incWidthAct = QAction( |
94 UI.PixmapCache.getIcon("sceneWidthInc"), |
95 UI.PixmapCache.getIcon("sceneWidthInc"), |
95 self.tr("Increase width by {0} points").format( |
96 self.tr("Increase width by {0} points").format(self.deltaSize), |
96 self.deltaSize), |
97 self, |
97 self) |
98 ) |
98 self.incWidthAct.triggered.connect(self.__incWidth) |
99 self.incWidthAct.triggered.connect(self.__incWidth) |
99 |
100 |
100 self.incHeightAct = QAction( |
101 self.incHeightAct = QAction( |
101 UI.PixmapCache.getIcon("sceneHeightInc"), |
102 UI.PixmapCache.getIcon("sceneHeightInc"), |
102 self.tr("Increase height by {0} points").format( |
103 self.tr("Increase height by {0} points").format(self.deltaSize), |
103 self.deltaSize), |
104 self, |
104 self) |
105 ) |
105 self.incHeightAct.triggered.connect(self.__incHeight) |
106 self.incHeightAct.triggered.connect(self.__incHeight) |
106 |
107 |
107 self.decWidthAct = QAction( |
108 self.decWidthAct = QAction( |
108 UI.PixmapCache.getIcon("sceneWidthDec"), |
109 UI.PixmapCache.getIcon("sceneWidthDec"), |
109 self.tr("Decrease width by {0} points").format( |
110 self.tr("Decrease width by {0} points").format(self.deltaSize), |
110 self.deltaSize), |
111 self, |
111 self) |
112 ) |
112 self.decWidthAct.triggered.connect(self.__decWidth) |
113 self.decWidthAct.triggered.connect(self.__decWidth) |
113 |
114 |
114 self.decHeightAct = QAction( |
115 self.decHeightAct = QAction( |
115 UI.PixmapCache.getIcon("sceneHeightDec"), |
116 UI.PixmapCache.getIcon("sceneHeightDec"), |
116 self.tr("Decrease height by {0} points").format( |
117 self.tr("Decrease height by {0} points").format(self.deltaSize), |
117 self.deltaSize), |
118 self, |
118 self) |
119 ) |
119 self.decHeightAct.triggered.connect(self.__decHeight) |
120 self.decHeightAct.triggered.connect(self.__decHeight) |
120 |
121 |
121 self.setSizeAct = QAction( |
122 self.setSizeAct = QAction( |
122 UI.PixmapCache.getIcon("sceneSize"), |
123 UI.PixmapCache.getIcon("sceneSize"), self.tr("Set size"), self |
123 self.tr("Set size"), self) |
124 ) |
124 self.setSizeAct.triggered.connect(self.__setSize) |
125 self.setSizeAct.triggered.connect(self.__setSize) |
125 |
126 |
126 self.rescanAct = QAction( |
127 self.rescanAct = QAction( |
127 UI.PixmapCache.getIcon("rescan"), |
128 UI.PixmapCache.getIcon("rescan"), self.tr("Re-Scan"), self |
128 self.tr("Re-Scan"), self) |
129 ) |
129 self.rescanAct.triggered.connect(self.__rescan) |
130 self.rescanAct.triggered.connect(self.__rescan) |
130 |
131 |
131 self.relayoutAct = QAction( |
132 self.relayoutAct = QAction( |
132 UI.PixmapCache.getIcon("relayout"), |
133 UI.PixmapCache.getIcon("relayout"), self.tr("Re-Layout"), self |
133 self.tr("Re-Layout"), self) |
134 ) |
134 self.relayoutAct.triggered.connect(self.__relayout) |
135 self.relayoutAct.triggered.connect(self.__relayout) |
135 |
136 |
136 self.alignLeftAct = QAction( |
137 self.alignLeftAct = QAction( |
137 UI.PixmapCache.getIcon("shapesAlignLeft"), |
138 UI.PixmapCache.getIcon("shapesAlignLeft"), self.tr("Align Left"), self |
138 self.tr("Align Left"), self) |
139 ) |
139 self.alignMapper.setMapping( |
140 self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignmentFlag.AlignLeft) |
140 self.alignLeftAct, Qt.AlignmentFlag.AlignLeft) |
|
141 self.alignLeftAct.triggered.connect(self.alignMapper.map) |
141 self.alignLeftAct.triggered.connect(self.alignMapper.map) |
142 |
142 |
143 self.alignHCenterAct = QAction( |
143 self.alignHCenterAct = QAction( |
144 UI.PixmapCache.getIcon("shapesAlignHCenter"), |
144 UI.PixmapCache.getIcon("shapesAlignHCenter"), |
145 self.tr("Align Center Horizontal"), self) |
145 self.tr("Align Center Horizontal"), |
146 self.alignMapper.setMapping( |
146 self, |
147 self.alignHCenterAct, Qt.AlignmentFlag.AlignHCenter) |
147 ) |
|
148 self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignmentFlag.AlignHCenter) |
148 self.alignHCenterAct.triggered.connect(self.alignMapper.map) |
149 self.alignHCenterAct.triggered.connect(self.alignMapper.map) |
149 |
150 |
150 self.alignRightAct = QAction( |
151 self.alignRightAct = QAction( |
151 UI.PixmapCache.getIcon("shapesAlignRight"), |
152 UI.PixmapCache.getIcon("shapesAlignRight"), self.tr("Align Right"), self |
152 self.tr("Align Right"), self) |
153 ) |
153 self.alignMapper.setMapping( |
154 self.alignMapper.setMapping(self.alignRightAct, Qt.AlignmentFlag.AlignRight) |
154 self.alignRightAct, Qt.AlignmentFlag.AlignRight) |
|
155 self.alignRightAct.triggered.connect(self.alignMapper.map) |
155 self.alignRightAct.triggered.connect(self.alignMapper.map) |
156 |
156 |
157 self.alignTopAct = QAction( |
157 self.alignTopAct = QAction( |
158 UI.PixmapCache.getIcon("shapesAlignTop"), |
158 UI.PixmapCache.getIcon("shapesAlignTop"), self.tr("Align Top"), self |
159 self.tr("Align Top"), self) |
159 ) |
160 self.alignMapper.setMapping( |
160 self.alignMapper.setMapping(self.alignTopAct, Qt.AlignmentFlag.AlignTop) |
161 self.alignTopAct, Qt.AlignmentFlag.AlignTop) |
|
162 self.alignTopAct.triggered.connect(self.alignMapper.map) |
161 self.alignTopAct.triggered.connect(self.alignMapper.map) |
163 |
162 |
164 self.alignVCenterAct = QAction( |
163 self.alignVCenterAct = QAction( |
165 UI.PixmapCache.getIcon("shapesAlignVCenter"), |
164 UI.PixmapCache.getIcon("shapesAlignVCenter"), |
166 self.tr("Align Center Vertical"), self) |
165 self.tr("Align Center Vertical"), |
167 self.alignMapper.setMapping( |
166 self, |
168 self.alignVCenterAct, Qt.AlignmentFlag.AlignVCenter) |
167 ) |
|
168 self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignmentFlag.AlignVCenter) |
169 self.alignVCenterAct.triggered.connect(self.alignMapper.map) |
169 self.alignVCenterAct.triggered.connect(self.alignMapper.map) |
170 |
170 |
171 self.alignBottomAct = QAction( |
171 self.alignBottomAct = QAction( |
172 UI.PixmapCache.getIcon("shapesAlignBottom"), |
172 UI.PixmapCache.getIcon("shapesAlignBottom"), self.tr("Align Bottom"), self |
173 self.tr("Align Bottom"), self) |
173 ) |
174 self.alignMapper.setMapping( |
174 self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignmentFlag.AlignBottom) |
175 self.alignBottomAct, Qt.AlignmentFlag.AlignBottom) |
|
176 self.alignBottomAct.triggered.connect(self.alignMapper.map) |
175 self.alignBottomAct.triggered.connect(self.alignMapper.map) |
177 |
176 |
178 def setLayoutActionsEnabled(self, enable): |
177 def setLayoutActionsEnabled(self, enable): |
179 """ |
178 """ |
180 Public method to enable or disable the layout related actions. |
179 Public method to enable or disable the layout related actions. |
181 |
180 |
182 @param enable flag indicating the desired enable state |
181 @param enable flag indicating the desired enable state |
183 @type bool |
182 @type bool |
184 """ |
183 """ |
185 self.rescanAct.setEnabled(enable) |
184 self.rescanAct.setEnabled(enable) |
186 self.relayoutAct.setEnabled(enable) |
185 self.relayoutAct.setEnabled(enable) |
187 |
186 |
188 def __checkSizeActions(self): |
187 def __checkSizeActions(self): |
189 """ |
188 """ |
190 Private slot to set the enabled state of the size actions. |
189 Private slot to set the enabled state of the size actions. |
191 """ |
190 """ |
192 diagramSize = self._getDiagramSize(10) |
191 diagramSize = self._getDiagramSize(10) |
197 self.decWidthAct.setEnabled(True) |
196 self.decWidthAct.setEnabled(True) |
198 if (sceneRect.height() - self.deltaSize) < diagramSize.height(): |
197 if (sceneRect.height() - self.deltaSize) < diagramSize.height(): |
199 self.decHeightAct.setEnabled(False) |
198 self.decHeightAct.setEnabled(False) |
200 else: |
199 else: |
201 self.decHeightAct.setEnabled(True) |
200 self.decHeightAct.setEnabled(True) |
202 |
201 |
203 def __sceneChanged(self, areas): |
202 def __sceneChanged(self, areas): |
204 """ |
203 """ |
205 Private slot called when the scene changes. |
204 Private slot called when the scene changes. |
206 |
205 |
207 @param areas list of rectangles that contain changes |
206 @param areas list of rectangles that contain changes |
208 @type list of QRectF |
207 @type list of QRectF |
209 """ |
208 """ |
210 if len(self.scene().selectedItems()) > 0: |
209 if len(self.scene().selectedItems()) > 0: |
211 self.deleteShapeAct.setEnabled(True) |
210 self.deleteShapeAct.setEnabled(True) |
212 else: |
211 else: |
213 self.deleteShapeAct.setEnabled(False) |
212 self.deleteShapeAct.setEnabled(False) |
214 |
213 |
215 sceneRect = self.scene().sceneRect() |
214 sceneRect = self.scene().sceneRect() |
216 newWidth = width = sceneRect.width() |
215 newWidth = width = sceneRect.width() |
217 newHeight = height = sceneRect.height() |
216 newHeight = height = sceneRect.height() |
218 rect = self.scene().itemsBoundingRect() |
217 rect = self.scene().itemsBoundingRect() |
219 # calculate with 10 pixel border on each side |
218 # calculate with 10 pixel border on each side |
220 if sceneRect.right() - 10 < rect.right(): |
219 if sceneRect.right() - 10 < rect.right(): |
221 newWidth = rect.right() + 10 |
220 newWidth = rect.right() + 10 |
222 if sceneRect.bottom() - 10 < rect.bottom(): |
221 if sceneRect.bottom() - 10 < rect.bottom(): |
223 newHeight = rect.bottom() + 10 |
222 newHeight = rect.bottom() + 10 |
224 |
223 |
225 if newHeight != height or newWidth != width: |
224 if newHeight != height or newWidth != width: |
226 self.setSceneSize(newWidth, newHeight) |
225 self.setSceneSize(newWidth, newHeight) |
227 self.__checkSizeActions() |
226 self.__checkSizeActions() |
228 |
227 |
229 def initToolBar(self): |
228 def initToolBar(self): |
230 """ |
229 """ |
231 Public method to populate a toolbar with our actions. |
230 Public method to populate a toolbar with our actions. |
232 |
231 |
233 @return the populated toolBar |
232 @return the populated toolBar |
234 @rtype QToolBar |
233 @rtype QToolBar |
235 """ |
234 """ |
236 toolBar = QToolBar(self.tr("Graphics"), self) |
235 toolBar = QToolBar(self.tr("Graphics"), self) |
237 toolBar.setIconSize(UI.Config.ToolBarIconSize) |
236 toolBar.setIconSize(UI.Config.ToolBarIconSize) |
250 toolBar.addAction(self.decHeightAct) |
249 toolBar.addAction(self.decHeightAct) |
251 toolBar.addAction(self.setSizeAct) |
250 toolBar.addAction(self.setSizeAct) |
252 toolBar.addSeparator() |
251 toolBar.addSeparator() |
253 toolBar.addAction(self.rescanAct) |
252 toolBar.addAction(self.rescanAct) |
254 toolBar.addAction(self.relayoutAct) |
253 toolBar.addAction(self.relayoutAct) |
255 |
254 |
256 return toolBar |
255 return toolBar |
257 |
256 |
258 def filteredItems(self, items, itemType=UMLItem): |
257 def filteredItems(self, items, itemType=UMLItem): |
259 """ |
258 """ |
260 Public method to filter a list of items. |
259 Public method to filter a list of items. |
261 |
260 |
262 @param items list of items as returned by the scene object |
261 @param items list of items as returned by the scene object |
263 @type QGraphicsItem |
262 @type QGraphicsItem |
264 @param itemType type to be filtered |
263 @param itemType type to be filtered |
265 @type class |
264 @type class |
266 @return list of interesting collision items |
265 @return list of interesting collision items |
267 @rtype QGraphicsItem |
266 @rtype QGraphicsItem |
268 """ |
267 """ |
269 return [itm for itm in items if isinstance(itm, itemType)] |
268 return [itm for itm in items if isinstance(itm, itemType)] |
270 |
269 |
271 def selectItems(self, items): |
270 def selectItems(self, items): |
272 """ |
271 """ |
273 Public method to select the given items. |
272 Public method to select the given items. |
274 |
273 |
275 @param items list of items to be selected |
274 @param items list of items to be selected |
276 @type list of QGraphicsItemItem |
275 @type list of QGraphicsItemItem |
277 """ |
276 """ |
278 # step 1: deselect all items |
277 # step 1: deselect all items |
279 self.unselectItems() |
278 self.unselectItems() |
280 |
279 |
281 # step 2: select all given items |
280 # step 2: select all given items |
282 for itm in items: |
281 for itm in items: |
283 if isinstance(itm, UMLItem): |
282 if isinstance(itm, UMLItem): |
284 itm.setSelected(True) |
283 itm.setSelected(True) |
285 |
284 |
286 def selectItem(self, item): |
285 def selectItem(self, item): |
287 """ |
286 """ |
288 Public method to select an item. |
287 Public method to select an item. |
289 |
288 |
290 @param item item to be selected |
289 @param item item to be selected |
291 @type QGraphicsItemItem |
290 @type QGraphicsItemItem |
292 """ |
291 """ |
293 if isinstance(item, UMLItem): |
292 if isinstance(item, UMLItem): |
294 item.setSelected(not item.isSelected()) |
293 item.setSelected(not item.isSelected()) |
295 |
294 |
296 def __deleteShape(self): |
295 def __deleteShape(self): |
297 """ |
296 """ |
298 Private method to delete the selected shapes from the display. |
297 Private method to delete the selected shapes from the display. |
299 """ |
298 """ |
300 for item in self.scene().selectedItems(): |
299 for item in self.scene().selectedItems(): |
301 item.removeAssociations() |
300 item.removeAssociations() |
302 item.setSelected(False) |
301 item.setSelected(False) |
303 self.scene().removeItem(item) |
302 self.scene().removeItem(item) |
304 del item |
303 del item |
305 |
304 |
306 def __incWidth(self): |
305 def __incWidth(self): |
307 """ |
306 """ |
308 Private method to handle the increase width context menu entry. |
307 Private method to handle the increase width context menu entry. |
309 """ |
308 """ |
310 self.resizeScene(self.deltaSize, True) |
309 self.resizeScene(self.deltaSize, True) |
311 self.__checkSizeActions() |
310 self.__checkSizeActions() |
312 |
311 |
313 def __incHeight(self): |
312 def __incHeight(self): |
314 """ |
313 """ |
315 Private method to handle the increase height context menu entry. |
314 Private method to handle the increase height context menu entry. |
316 """ |
315 """ |
317 self.resizeScene(self.deltaSize, False) |
316 self.resizeScene(self.deltaSize, False) |
318 self.__checkSizeActions() |
317 self.__checkSizeActions() |
319 |
318 |
320 def __decWidth(self): |
319 def __decWidth(self): |
321 """ |
320 """ |
322 Private method to handle the decrease width context menu entry. |
321 Private method to handle the decrease width context menu entry. |
323 """ |
322 """ |
324 self.resizeScene(-self.deltaSize, True) |
323 self.resizeScene(-self.deltaSize, True) |
325 self.__checkSizeActions() |
324 self.__checkSizeActions() |
326 |
325 |
327 def __decHeight(self): |
326 def __decHeight(self): |
328 """ |
327 """ |
329 Private method to handle the decrease height context menu entry. |
328 Private method to handle the decrease height context menu entry. |
330 """ |
329 """ |
331 self.resizeScene(-self.deltaSize, False) |
330 self.resizeScene(-self.deltaSize, False) |
332 self.__checkSizeActions() |
331 self.__checkSizeActions() |
333 |
332 |
334 def __setSize(self): |
333 def __setSize(self): |
335 """ |
334 """ |
336 Private method to handle the set size context menu entry. |
335 Private method to handle the set size context menu entry. |
337 """ |
336 """ |
338 from .UMLSceneSizeDialog import UMLSceneSizeDialog |
337 from .UMLSceneSizeDialog import UMLSceneSizeDialog |
|
338 |
339 rect = self._getDiagramRect(10) |
339 rect = self._getDiagramRect(10) |
340 sceneRect = self.scene().sceneRect() |
340 sceneRect = self.scene().sceneRect() |
341 dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(), |
341 dlg = UMLSceneSizeDialog( |
342 rect.width(), rect.height(), self) |
342 sceneRect.width(), sceneRect.height(), rect.width(), rect.height(), self |
|
343 ) |
343 if dlg.exec() == QDialog.DialogCode.Accepted: |
344 if dlg.exec() == QDialog.DialogCode.Accepted: |
344 width, height = dlg.getData() |
345 width, height = dlg.getData() |
345 self.setSceneSize(width, height) |
346 self.setSceneSize(width, height) |
346 self.__checkSizeActions() |
347 self.__checkSizeActions() |
347 |
348 |
348 def autoAdjustSceneSize(self, limit=False): |
349 def autoAdjustSceneSize(self, limit=False): |
349 """ |
350 """ |
350 Public method to adjust the scene size to the diagram size. |
351 Public method to adjust the scene size to the diagram size. |
351 |
352 |
352 @param limit flag indicating to limit the scene to the |
353 @param limit flag indicating to limit the scene to the |
353 initial size |
354 initial size |
354 @type bool |
355 @type bool |
355 """ |
356 """ |
356 super().autoAdjustSceneSize(limit=limit) |
357 super().autoAdjustSceneSize(limit=limit) |
357 self.__checkSizeActions() |
358 self.__checkSizeActions() |
358 |
359 |
359 def saveImage(self): |
360 def saveImage(self): |
360 """ |
361 """ |
361 Public method to handle the save context menu entry. |
362 Public method to handle the save context menu entry. |
362 """ |
363 """ |
363 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
364 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
364 self, |
365 self, |
365 self.tr("Save Diagram"), |
366 self.tr("Save Diagram"), |
366 "", |
367 "", |
367 self.tr("Portable Network Graphics (*.png);;" |
368 self.tr( |
368 "Scalable Vector Graphics (*.svg)"), |
369 "Portable Network Graphics (*.png);;" "Scalable Vector Graphics (*.svg)" |
|
370 ), |
369 "", |
371 "", |
370 EricFileDialog.DontConfirmOverwrite) |
372 EricFileDialog.DontConfirmOverwrite, |
|
373 ) |
371 if fname: |
374 if fname: |
372 fpath = pathlib.Path(fname) |
375 fpath = pathlib.Path(fname) |
373 if not fpath.suffix: |
376 if not fpath.suffix: |
374 ex = selectedFilter.split("(*")[1].split(")")[0] |
377 ex = selectedFilter.split("(*")[1].split(")")[0] |
375 if ex: |
378 if ex: |
376 fpath = fpath.with_suffix(ex) |
379 fpath = fpath.with_suffix(ex) |
377 if fpath.exists(): |
380 if fpath.exists(): |
378 res = EricMessageBox.yesNo( |
381 res = EricMessageBox.yesNo( |
379 self, |
382 self, |
380 self.tr("Save Diagram"), |
383 self.tr("Save Diagram"), |
381 self.tr("<p>The file <b>{0}</b> already exists." |
384 self.tr( |
382 " Overwrite it?</p>").format(fpath), |
385 "<p>The file <b>{0}</b> already exists." " Overwrite it?</p>" |
383 icon=EricMessageBox.Warning) |
386 ).format(fpath), |
|
387 icon=EricMessageBox.Warning, |
|
388 ) |
384 if not res: |
389 if not res: |
385 return |
390 return |
386 |
391 |
387 success = super().saveImage( |
392 success = super().saveImage(str(fpath), fpath.suffix.upper()) |
388 str(fpath), fpath.suffix.upper()) |
|
389 if not success: |
393 if not success: |
390 EricMessageBox.critical( |
394 EricMessageBox.critical( |
391 self, |
395 self, |
392 self.tr("Save Diagram"), |
396 self.tr("Save Diagram"), |
393 self.tr( |
397 self.tr( |
394 """<p>The file <b>{0}</b> could not be saved.</p>""") |
398 """<p>The file <b>{0}</b> could not be saved.</p>""" |
395 .format(fpath)) |
399 ).format(fpath), |
396 |
400 ) |
|
401 |
397 def __relayout(self): |
402 def __relayout(self): |
398 """ |
403 """ |
399 Private slot to handle the re-layout context menu entry. |
404 Private slot to handle the re-layout context menu entry. |
400 """ |
405 """ |
401 self.__itemId = -1 |
406 self.__itemId = -1 |
402 self.scene().clear() |
407 self.scene().clear() |
403 self.relayout.emit() |
408 self.relayout.emit() |
404 |
409 |
405 def __rescan(self): |
410 def __rescan(self): |
406 """ |
411 """ |
407 Private slot to handle the re-scan context menu entry. |
412 Private slot to handle the re-scan context menu entry. |
408 """ |
413 """ |
409 # 1. save positions of all items and names of selected items |
414 # 1. save positions of all items and names of selected items |
441 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
446 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
442 if Preferences.getPrinter("FirstPageFirst"): |
447 if Preferences.getPrinter("FirstPageFirst"): |
443 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
448 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
444 else: |
449 else: |
445 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
450 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
446 printer.setPageMargins(QMarginsF( |
451 printer.setPageMargins( |
447 Preferences.getPrinter("LeftMargin") * 10, |
452 QMarginsF( |
448 Preferences.getPrinter("TopMargin") * 10, |
453 Preferences.getPrinter("LeftMargin") * 10, |
449 Preferences.getPrinter("RightMargin") * 10, |
454 Preferences.getPrinter("TopMargin") * 10, |
450 Preferences.getPrinter("BottomMargin") * 10), |
455 Preferences.getPrinter("RightMargin") * 10, |
451 QPageLayout.Unit.Millimeter |
456 Preferences.getPrinter("BottomMargin") * 10, |
|
457 ), |
|
458 QPageLayout.Unit.Millimeter, |
452 ) |
459 ) |
453 printerName = Preferences.getPrinter("PrinterName") |
460 printerName = Preferences.getPrinter("PrinterName") |
454 if printerName: |
461 if printerName: |
455 printer.setPrinterName(printerName) |
462 printer.setPrinterName(printerName) |
456 |
463 |
457 printDialog = QPrintDialog(printer, self) |
464 printDialog = QPrintDialog(printer, self) |
458 if printDialog.exec(): |
465 if printDialog.exec(): |
459 super().printDiagram( |
466 super().printDiagram(printer, self.diagramName) |
460 printer, self.diagramName) |
467 |
461 |
|
462 def printPreviewDiagram(self): |
468 def printPreviewDiagram(self): |
463 """ |
469 """ |
464 Public slot called to show a print preview of the diagram. |
470 Public slot called to show a print preview of the diagram. |
465 """ |
471 """ |
466 from PyQt6.QtPrintSupport import QPrintPreviewDialog |
472 from PyQt6.QtPrintSupport import QPrintPreviewDialog |
467 |
473 |
468 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution) |
474 printer = QPrinter(mode=QPrinter.PrinterMode.PrinterResolution) |
469 printer.setFullPage(True) |
475 printer.setFullPage(True) |
470 if Preferences.getPrinter("ColorMode"): |
476 if Preferences.getPrinter("ColorMode"): |
471 printer.setColorMode(QPrinter.ColorMode.Color) |
477 printer.setColorMode(QPrinter.ColorMode.Color) |
472 else: |
478 else: |
473 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
479 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
474 if Preferences.getPrinter("FirstPageFirst"): |
480 if Preferences.getPrinter("FirstPageFirst"): |
475 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
481 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
476 else: |
482 else: |
477 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
483 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
478 printer.setPageMargins(QMarginsF( |
484 printer.setPageMargins( |
479 Preferences.getPrinter("LeftMargin") * 10, |
485 QMarginsF( |
480 Preferences.getPrinter("TopMargin") * 10, |
486 Preferences.getPrinter("LeftMargin") * 10, |
481 Preferences.getPrinter("RightMargin") * 10, |
487 Preferences.getPrinter("TopMargin") * 10, |
482 Preferences.getPrinter("BottomMargin") * 10), |
488 Preferences.getPrinter("RightMargin") * 10, |
483 QPageLayout.Unit.Millimeter |
489 Preferences.getPrinter("BottomMargin") * 10, |
|
490 ), |
|
491 QPageLayout.Unit.Millimeter, |
484 ) |
492 ) |
485 printerName = Preferences.getPrinter("PrinterName") |
493 printerName = Preferences.getPrinter("PrinterName") |
486 if printerName: |
494 if printerName: |
487 printer.setPrinterName(printerName) |
495 printer.setPrinterName(printerName) |
488 |
496 |
489 preview = QPrintPreviewDialog(printer, self) |
497 preview = QPrintPreviewDialog(printer, self) |
490 preview.paintRequested[QPrinter].connect(self.__printPreviewPrint) |
498 preview.paintRequested[QPrinter].connect(self.__printPreviewPrint) |
491 preview.exec() |
499 preview.exec() |
492 |
500 |
493 def __printPreviewPrint(self, printer): |
501 def __printPreviewPrint(self, printer): |
494 """ |
502 """ |
495 Private slot to generate a print preview. |
503 Private slot to generate a print preview. |
496 |
504 |
497 @param printer reference to the printer object |
505 @param printer reference to the printer object |
498 @type QPrinter |
506 @type QPrinter |
499 """ |
507 """ |
500 super().printDiagram(printer, self.diagramName) |
508 super().printDiagram(printer, self.diagramName) |
501 |
509 |
502 def setDiagramName(self, name): |
510 def setDiagramName(self, name): |
503 """ |
511 """ |
504 Public slot to set the diagram name. |
512 Public slot to set the diagram name. |
505 |
513 |
506 @param name diagram name |
514 @param name diagram name |
507 @type str |
515 @type str |
508 """ |
516 """ |
509 self.diagramName = name |
517 self.diagramName = name |
510 |
518 |
511 def __alignShapes(self, alignment): |
519 def __alignShapes(self, alignment): |
512 """ |
520 """ |
513 Private slot to align the selected shapes. |
521 Private slot to align the selected shapes. |
514 |
522 |
515 @param alignment alignment type |
523 @param alignment alignment type |
516 @type Qt.AlignmentFlag |
524 @type Qt.AlignmentFlag |
517 """ |
525 """ |
518 # step 1: get all selected items |
526 # step 1: get all selected items |
519 items = self.scene().selectedItems() |
527 items = self.scene().selectedItems() |
520 if len(items) <= 1: |
528 if len(items) <= 1: |
521 return |
529 return |
522 |
530 |
523 # step 2: find the index of the item to align in relation to |
531 # step 2: find the index of the item to align in relation to |
524 amount = None |
532 amount = None |
525 for i, item in enumerate(items): |
533 for i, item in enumerate(items): |
526 rect = item.sceneBoundingRect() |
534 rect = item.sceneBoundingRect() |
527 if alignment == Qt.AlignmentFlag.AlignLeft: |
535 if alignment == Qt.AlignmentFlag.AlignLeft: |
548 # __IGNORE_WARNING_Y102__ |
556 # __IGNORE_WARNING_Y102__ |
549 if amount is None or rect.height() > amount: |
557 if amount is None or rect.height() > amount: |
550 amount = rect.height() |
558 amount = rect.height() |
551 index = i |
559 index = i |
552 rect = items[index].sceneBoundingRect() |
560 rect = items[index].sceneBoundingRect() |
553 |
561 |
554 # step 3: move the other items |
562 # step 3: move the other items |
555 for i, item in enumerate(items): |
563 for i, item in enumerate(items): |
556 if i == index: |
564 if i == index: |
557 continue |
565 continue |
558 itemrect = item.sceneBoundingRect() |
566 itemrect = item.sceneBoundingRect() |
559 xOffset = yOffset = 0 |
567 xOffset = yOffset = 0 |
560 if alignment == Qt.AlignmentFlag.AlignLeft: |
568 if alignment == Qt.AlignmentFlag.AlignLeft: |
561 xOffset = rect.x() - itemrect.x() |
569 xOffset = rect.x() - itemrect.x() |
562 elif alignment == Qt.AlignmentFlag.AlignRight: |
570 elif alignment == Qt.AlignmentFlag.AlignRight: |
563 xOffset = ( |
571 xOffset = (rect.x() + rect.width()) - (itemrect.x() + itemrect.width()) |
564 (rect.x() + rect.width()) - |
|
565 (itemrect.x() + itemrect.width()) |
|
566 ) |
|
567 elif alignment == Qt.AlignmentFlag.AlignHCenter: |
572 elif alignment == Qt.AlignmentFlag.AlignHCenter: |
568 xOffset = ( |
573 xOffset = (rect.x() + rect.width() // 2) - ( |
569 (rect.x() + rect.width() // 2) - |
574 itemrect.x() + itemrect.width() // 2 |
570 (itemrect.x() + itemrect.width() // 2) |
|
571 ) |
575 ) |
572 elif alignment == Qt.AlignmentFlag.AlignTop: |
576 elif alignment == Qt.AlignmentFlag.AlignTop: |
573 yOffset = rect.y() - itemrect.y() |
577 yOffset = rect.y() - itemrect.y() |
574 elif alignment == Qt.AlignmentFlag.AlignBottom: |
578 elif alignment == Qt.AlignmentFlag.AlignBottom: |
575 yOffset = ( |
579 yOffset = (rect.y() + rect.height()) - ( |
576 (rect.y() + rect.height()) - |
580 itemrect.y() + itemrect.height() |
577 (itemrect.y() + itemrect.height()) |
|
578 ) |
581 ) |
579 elif alignment == Qt.AlignmentFlag.AlignVCenter: |
582 elif alignment == Qt.AlignmentFlag.AlignVCenter: |
580 yOffset = ( |
583 yOffset = (rect.y() + rect.height() // 2) - ( |
581 (rect.y() + rect.height() // 2) - |
584 itemrect.y() + itemrect.height() // 2 |
582 (itemrect.y() + itemrect.height() // 2) |
|
583 ) |
585 ) |
584 item.moveBy(xOffset, yOffset) |
586 item.moveBy(xOffset, yOffset) |
585 |
587 |
586 self.scene().update() |
588 self.scene().update() |
587 |
589 |
588 def __itemsBoundingRect(self, items): |
590 def __itemsBoundingRect(self, items): |
589 """ |
591 """ |
590 Private method to calculate the bounding rectangle of the given items. |
592 Private method to calculate the bounding rectangle of the given items. |
591 |
593 |
592 @param items list of items to operate on |
594 @param items list of items to operate on |
593 @type list of UMLItem |
595 @type list of UMLItem |
594 @return bounding rectangle |
596 @return bounding rectangle |
595 @rtype QRectF |
597 @rtype QRectF |
596 """ |
598 """ |
731 try: |
732 try: |
732 if item.getName() == name: |
733 if item.getName() == name: |
733 return item |
734 return item |
734 except AttributeError: |
735 except AttributeError: |
735 continue |
736 continue |
736 |
737 |
737 return None |
738 return None |
738 |
739 |
739 def parsePersistenceData(self, version, data): |
740 def parsePersistenceData(self, version, data): |
740 """ |
741 """ |
741 Public method to parse persisted data. |
742 Public method to parse persisted data. |
742 |
743 |
743 @param version version of the data |
744 @param version version of the data |
744 @type str |
745 @type str |
745 @param data persisted data to be parsed |
746 @param data persisted data to be parsed |
746 @type list of str |
747 @type list of str |
747 @return tuple of flag indicating success (boolean) and faulty line |
748 @return tuple of flag indicating success (boolean) and faulty line |
748 number |
749 number |
749 @rtype int |
750 @rtype int |
750 """ |
751 """ |
751 umlItems = {} |
752 umlItems = {} |
752 |
753 |
753 if not data[0].startswith("diagram_name:"): |
754 if not data[0].startswith("diagram_name:"): |
754 return False, 0 |
755 return False, 0 |
755 self.diagramName = data[0].split(": ", 1)[1].strip() |
756 self.diagramName = data[0].split(": ", 1)[1].strip() |
756 |
757 |
757 from .ClassItem import ClassItem |
758 from .ClassItem import ClassItem |
758 from .ModuleItem import ModuleItem |
759 from .ModuleItem import ModuleItem |
759 from .PackageItem import PackageItem |
760 from .PackageItem import PackageItem |
760 from .AssociationItem import AssociationItem |
761 from .AssociationItem import AssociationItem |
761 |
762 |
762 for linenum, line in enumerate(data[1:], start=1): |
763 for linenum, line in enumerate(data[1:], start=1): |
763 if not line.startswith(("item:", "association:")): |
764 if not line.startswith(("item:", "association:")): |
764 return False, linenum |
765 return False, linenum |
765 |
766 |
766 key, value = line.split(": ", 1) |
767 key, value = line.split(": ", 1) |
767 if key == "item": |
768 if key == "item": |
768 itemId, x, y, itemType, itemData = value.split(", ", 4) |
769 itemId, x, y, itemType, itemData = value.split(", ", 4) |
769 try: |
770 try: |
770 itemId = int(itemId.split("=", 1)[1].strip()) |
771 itemId = int(itemId.split("=", 1)[1].strip()) |
771 x = float(x.split("=", 1)[1].strip()) |
772 x = float(x.split("=", 1)[1].strip()) |
772 y = float(y.split("=", 1)[1].strip()) |
773 y = float(y.split("=", 1)[1].strip()) |
773 itemType = itemType.split("=", 1)[1].strip() |
774 itemType = itemType.split("=", 1)[1].strip() |
774 if itemType == ClassItem.ItemType: |
775 if itemType == ClassItem.ItemType: |
775 itm = ClassItem(x=0, y=0, scene=self.scene(), |
776 itm = ClassItem( |
776 colors=self.getDrawingColors()) |
777 x=0, y=0, scene=self.scene(), colors=self.getDrawingColors() |
|
778 ) |
777 elif itemType == ModuleItem.ItemType: |
779 elif itemType == ModuleItem.ItemType: |
778 itm = ModuleItem(x=0, y=0, scene=self.scene(), |
780 itm = ModuleItem( |
779 colors=self.getDrawingColors()) |
781 x=0, y=0, scene=self.scene(), colors=self.getDrawingColors() |
|
782 ) |
780 elif itemType == PackageItem.ItemType: |
783 elif itemType == PackageItem.ItemType: |
781 itm = PackageItem(x=0, y=0, scene=self.scene(), |
784 itm = PackageItem( |
782 colors=self.getDrawingColors()) |
785 x=0, y=0, scene=self.scene(), colors=self.getDrawingColors() |
|
786 ) |
783 itm.setPos(x, y) |
787 itm.setPos(x, y) |
784 itm.setId(itemId) |
788 itm.setId(itemId) |
785 umlItems[itemId] = itm |
789 umlItems[itemId] = itm |
786 if not itm.parseItemDataString(version, itemData): |
790 if not itm.parseItemDataString(version, itemData): |
787 return False, linenum |
791 return False, linenum |
788 except ValueError: |
792 except ValueError: |
789 return False, linenum |
793 return False, linenum |
790 elif key == "association": |
794 elif key == "association": |
791 srcId, dstId, assocType, topToBottom = ( |
795 ( |
792 AssociationItem.parseAssociationItemDataString( |
796 srcId, |
793 value.strip()) |
797 dstId, |
|
798 assocType, |
|
799 topToBottom, |
|
800 ) = AssociationItem.parseAssociationItemDataString(value.strip()) |
|
801 assoc = AssociationItem( |
|
802 umlItems[srcId], umlItems[dstId], assocType, topToBottom |
794 ) |
803 ) |
795 assoc = AssociationItem(umlItems[srcId], umlItems[dstId], |
|
796 assocType, topToBottom) |
|
797 self.scene().addItem(assoc) |
804 self.scene().addItem(assoc) |
798 |
805 |
799 return True, -1 |
806 return True, -1 |
800 |
807 |
801 def toDict(self): |
808 def toDict(self): |
802 """ |
809 """ |
803 Public method to collect data to be persisted. |
810 Public method to collect data to be persisted. |
804 |
811 |
805 @return dictionary containing data to be persisted |
812 @return dictionary containing data to be persisted |
806 @rtype dict |
813 @rtype dict |
807 """ |
814 """ |
808 items = [ |
815 items = [ |
809 item.toDict() |
816 item.toDict() for item in self.filteredItems(self.scene().items(), UMLItem) |
810 for item in self.filteredItems(self.scene().items(), UMLItem) |
|
811 ] |
817 ] |
812 |
818 |
813 from .AssociationItem import AssociationItem |
819 from .AssociationItem import AssociationItem |
|
820 |
814 associations = [ |
821 associations = [ |
815 assoc.toDict() |
822 assoc.toDict() |
816 for assoc in self.filteredItems(self.scene().items(), |
823 for assoc in self.filteredItems(self.scene().items(), AssociationItem) |
817 AssociationItem) |
|
818 ] |
824 ] |
819 |
825 |
820 data = { |
826 data = { |
821 "diagram_name": self.diagramName, |
827 "diagram_name": self.diagramName, |
822 "items": items, |
828 "items": items, |
823 "associations": associations, |
829 "associations": associations, |
824 } |
830 } |
825 |
831 |
826 return data |
832 return data |
827 |
833 |
828 def fromDict(self, version, data): |
834 def fromDict(self, version, data): |
829 """ |
835 """ |
830 Public method to populate the class with data persisted by 'toDict()'. |
836 Public method to populate the class with data persisted by 'toDict()'. |
831 |
837 |
832 @param version version of the data |
838 @param version version of the data |
833 @type str |
839 @type str |
834 @param data dictionary containing the persisted data |
840 @param data dictionary containing the persisted data |
835 @type dict |
841 @type dict |
836 @return flag indicating success |
842 @return flag indicating success |
839 from .UMLItem import UMLItem |
845 from .UMLItem import UMLItem |
840 from .ClassItem import ClassItem |
846 from .ClassItem import ClassItem |
841 from .ModuleItem import ModuleItem |
847 from .ModuleItem import ModuleItem |
842 from .PackageItem import PackageItem |
848 from .PackageItem import PackageItem |
843 from .AssociationItem import AssociationItem |
849 from .AssociationItem import AssociationItem |
844 |
850 |
845 umlItems = {} |
851 umlItems = {} |
846 |
852 |
847 try: |
853 try: |
848 self.diagramName = data["diagram_name"] |
854 self.diagramName = data["diagram_name"] |
849 for itemData in data["items"]: |
855 for itemData in data["items"]: |
850 if itemData["type"] == UMLItem.ItemType: |
856 if itemData["type"] == UMLItem.ItemType: |
851 itm = UMLItem.fromDict( |
857 itm = UMLItem.fromDict(itemData, colors=self.getDrawingColors()) |
852 itemData, colors=self.getDrawingColors()) |
|
853 elif itemData["type"] == ClassItem.ItemType: |
858 elif itemData["type"] == ClassItem.ItemType: |
854 itm = ClassItem.fromDict( |
859 itm = ClassItem.fromDict(itemData, colors=self.getDrawingColors()) |
855 itemData, colors=self.getDrawingColors()) |
|
856 elif itemData["type"] == ModuleItem.ItemType: |
860 elif itemData["type"] == ModuleItem.ItemType: |
857 itm = ModuleItem.fromDict( |
861 itm = ModuleItem.fromDict(itemData, colors=self.getDrawingColors()) |
858 itemData, colors=self.getDrawingColors()) |
|
859 elif itemData["type"] == PackageItem.ItemType: |
862 elif itemData["type"] == PackageItem.ItemType: |
860 itm = PackageItem.fromDict( |
863 itm = PackageItem.fromDict(itemData, colors=self.getDrawingColors()) |
861 itemData, colors=self.getDrawingColors()) |
|
862 if itm is not None: |
864 if itm is not None: |
863 umlItems[itm.getId()] = itm |
865 umlItems[itm.getId()] = itm |
864 self.scene().addItem(itm) |
866 self.scene().addItem(itm) |
865 |
867 |
866 for assocData in data["associations"]: |
868 for assocData in data["associations"]: |
867 assoc = AssociationItem.fromDict( |
869 assoc = AssociationItem.fromDict( |
868 assocData, umlItems, colors=self.getDrawingColors()) |
870 assocData, umlItems, colors=self.getDrawingColors() |
|
871 ) |
869 self.scene().addItem(assoc) |
872 self.scene().addItem(assoc) |
870 |
873 |
871 return True |
874 return True |
872 except KeyError: |
875 except KeyError: |
873 return False |
876 return False |