src/eric7/EricGraphics/EricGraphicsView.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
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 canvas view class.
8 """
9
10 import sys
11
12 from PyQt6.QtCore import pyqtSignal, QRectF, QSize, QSizeF, Qt
13 from PyQt6.QtGui import QBrush, QPainter, QPixmap, QFont, QColor
14 from PyQt6.QtWidgets import QGraphicsView
15
16 from EricWidgets.EricApplication import ericApp
17
18 import Preferences
19
20
21 class EricGraphicsView(QGraphicsView):
22 """
23 Class implementing a graphics view.
24
25 @signal zoomValueChanged(int) emitted to signal a change of the zoom value
26 """
27 zoomValueChanged = pyqtSignal(int)
28
29 ZoomLevels = [
30 1, 3, 5, 7, 9,
31 10, 20, 30, 50, 67, 80, 90,
32 100,
33 110, 120, 133, 150, 170, 200, 240, 300, 400,
34 500, 600, 700, 800, 900, 1000,
35 ]
36 ZoomLevelDefault = 100
37
38 def __init__(self, scene, parent=None):
39 """
40 Constructor
41
42 @param scene reference to the scene object (QGraphicsScene)
43 @param parent parent widget (QWidget)
44 """
45 super().__init__(scene, parent)
46 self.setObjectName("EricGraphicsView")
47
48 self.__initialSceneSize = self.scene().sceneRect().size()
49 self.setBackgroundBrush(QBrush(self.getBackgroundColor()))
50 self.setRenderHint(QPainter.RenderHint.Antialiasing, True)
51 self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
52 self.setAlignment(
53 Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
54 self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
55 self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
56 self.setViewportUpdateMode(
57 QGraphicsView.ViewportUpdateMode.SmartViewportUpdate)
58
59 self.setWhatsThis(self.tr(
60 "<b>Graphics View</b>\n"
61 "<p>This graphics view is used to show a diagram. \n"
62 "There are various actions available to manipulate the \n"
63 "shown items.</p>\n"
64 "<ul>\n"
65 "<li>Clicking on an item selects it.</li>\n"
66 "<li>Ctrl-clicking adds an item to the selection.</li>\n"
67 "<li>Ctrl-clicking a selected item deselects it.</li>\n"
68 "<li>Clicking on an empty spot of the canvas resets the selection."
69 "</li>\n"
70 "<li>Dragging the mouse over the canvas spans a rubberband to \n"
71 "select multiple items.</li>\n"
72 "<li>Dragging the mouse over a selected item moves the \n"
73 "whole selection.</li>\n"
74 "</ul>\n"
75 ))
76
77 def getDrawingColors(self):
78 """
79 Public method to get the configured drawing colors.
80
81 @return tuple containing the foreground and background colors
82 @rtype tuple of (QColor, QColor)
83 """
84 drawingMode = Preferences.getGraphics("DrawingMode")
85 if drawingMode == "automatic":
86 if ericApp().usesDarkPalette():
87 drawingMode = "white_black"
88 else:
89 drawingMode = "black_white"
90
91 if drawingMode == "white_black":
92 return (QColor("#ffffff"), QColor("#262626"))
93 else:
94 return (QColor("#000000"), QColor("#ffffff"))
95
96 def getForegroundColor(self):
97 """
98 Public method to get the configured foreground color.
99
100 @return foreground color
101 @rtype QColor
102 """
103 return self.getDrawingColors()[0]
104
105 def getBackgroundColor(self):
106 """
107 Public method to get the configured background color.
108
109 @return background color
110 @rtype QColor
111 """
112 return self.getDrawingColors()[1]
113
114 def __levelForZoom(self, zoom):
115 """
116 Private method determining the zoom level index given a zoom factor.
117
118 @param zoom zoom factor (integer)
119 @return index of zoom factor (integer)
120 """
121 try:
122 index = EricGraphicsView.ZoomLevels.index(zoom)
123 except ValueError:
124 for index in range(len(EricGraphicsView.ZoomLevels)):
125 if zoom <= EricGraphicsView.ZoomLevels[index]:
126 break
127 return index
128
129 def zoomIn(self):
130 """
131 Public method to zoom in.
132 """
133 index = self.__levelForZoom(self.zoom())
134 if index < len(EricGraphicsView.ZoomLevels) - 1:
135 self.setZoom(EricGraphicsView.ZoomLevels[index + 1])
136
137 def zoomOut(self):
138 """
139 Public method to zoom out.
140 """
141 index = self.__levelForZoom(self.zoom())
142 if index > 0:
143 self.setZoom(EricGraphicsView.ZoomLevels[index - 1])
144
145 def zoomReset(self):
146 """
147 Public method to handle the reset the zoom value.
148 """
149 self.setZoom(
150 EricGraphicsView.ZoomLevels[EricGraphicsView.ZoomLevelDefault])
151
152 def setZoom(self, value):
153 """
154 Public method to set the zoom value in percent.
155
156 @param value zoom value in percent (integer)
157 """
158 if value != self.zoom():
159 self.resetTransform()
160 factor = value / 100.0
161 self.scale(factor, factor)
162 self.zoomValueChanged.emit(value)
163
164 def zoom(self):
165 """
166 Public method to get the current zoom factor in percent.
167
168 @return current zoom factor in percent (integer)
169 """
170 return int(self.transform().m11() * 100.0)
171
172 def resizeScene(self, amount, isWidth=True):
173 """
174 Public method to resize the scene.
175
176 @param amount size increment (integer)
177 @param isWidth flag indicating width is to be resized (boolean)
178 """
179 sceneRect = self.scene().sceneRect()
180 width = sceneRect.width()
181 height = sceneRect.height()
182 if isWidth:
183 width += amount
184 else:
185 height += amount
186 rect = self._getDiagramRect(10)
187 if width < rect.width():
188 width = rect.width()
189 if height < rect.height():
190 height = rect.height()
191
192 self.setSceneSize(width, height)
193
194 def setSceneSize(self, width, height):
195 """
196 Public method to set the scene size.
197
198 @param width width for the scene (real)
199 @param height height for the scene (real)
200 """
201 rect = self.scene().sceneRect()
202 rect.setHeight(height)
203 rect.setWidth(width)
204 self.scene().setSceneRect(rect)
205
206 def autoAdjustSceneSize(self, limit=False):
207 """
208 Public method to adjust the scene size to the diagram size.
209
210 @param limit flag indicating to limit the scene to the
211 initial size (boolean)
212 """
213 size = self._getDiagramSize(10)
214 if limit:
215 newWidth = max(size.width(), self.__initialSceneSize.width())
216 newHeight = max(size.height(), self.__initialSceneSize.height())
217 else:
218 newWidth = size.width()
219 newHeight = size.height()
220 self.setSceneSize(newWidth, newHeight)
221
222 def _getDiagramRect(self, border=0):
223 """
224 Protected method to calculate the minimum rectangle fitting the
225 diagram.
226
227 @param border border width to include in the calculation (integer)
228 @return the minimum rectangle (QRectF)
229 """
230 startx = sys.maxsize
231 starty = sys.maxsize
232 endx = 0
233 endy = 0
234 items = self.filteredItems(list(self.scene().items()))
235 for itm in items:
236 rect = itm.sceneBoundingRect()
237 itmEndX = rect.x() + rect.width()
238 itmEndY = rect.y() + rect.height()
239 itmStartX = rect.x()
240 itmStartY = rect.y()
241 if startx >= itmStartX:
242 startx = itmStartX
243 if starty >= itmStartY:
244 starty = itmStartY
245 if endx <= itmEndX:
246 endx = itmEndX
247 if endy <= itmEndY:
248 endy = itmEndY
249 if border:
250 startx -= border
251 starty -= border
252 endx += border
253 endy += border
254
255 return QRectF(startx, starty, endx - startx + 1, endy - starty + 1)
256
257 def _getDiagramSize(self, border=0):
258 """
259 Protected method to calculate the minimum size fitting the diagram.
260
261 @param border border width to include in the calculation (integer)
262 @return the minimum size (QSizeF)
263 """
264 endx = 0
265 endy = 0
266 items = self.filteredItems(list(self.scene().items()))
267 for itm in items:
268 rect = itm.sceneBoundingRect()
269 itmEndX = rect.x() + rect.width()
270 itmEndY = rect.y() + rect.height()
271 if endx <= itmEndX:
272 endx = itmEndX
273 if endy <= itmEndY:
274 endy = itmEndY
275 if border:
276 endx += border
277 endy += border
278
279 return QSizeF(endx + 1, endy + 1)
280
281 def __getDiagram(self, rect, imageFormat="PNG", filename=None):
282 """
283 Private method to retrieve the diagram from the scene fitting it
284 in the minimum rectangle.
285
286 @param rect minimum rectangle fitting the diagram
287 @type QRectF
288 @param imageFormat format for the image file
289 @type str
290 @param filename name of the file for non pixmaps
291 str
292 @return paint device containing the diagram
293 @rtype QPixmap or QSvgGenerator
294 """
295 selectedItems = self.scene().selectedItems()
296
297 # step 1: deselect all widgets
298 if selectedItems:
299 for item in selectedItems:
300 item.setSelected(False)
301
302 # step 2: grab the diagram
303 if imageFormat == "PNG":
304 paintDevice = QPixmap(int(rect.width()), int(rect.height()))
305 paintDevice.fill(self.backgroundBrush().color())
306 else:
307 from PyQt6.QtSvg import QSvgGenerator
308 paintDevice = QSvgGenerator()
309 paintDevice.setResolution(100) # 100 dpi
310 paintDevice.setSize(QSize(int(rect.width()), int(rect.height())))
311 paintDevice.setViewBox(rect)
312 paintDevice.setFileName(filename)
313 painter = QPainter(paintDevice)
314 painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
315 self.scene().render(painter, QRectF(), rect)
316
317 # step 3: reselect the widgets
318 if selectedItems:
319 for item in selectedItems:
320 item.setSelected(True)
321
322 return paintDevice
323
324 def saveImage(self, filename, imageFormat="PNG"):
325 """
326 Public method to save the scene to a file.
327
328 @param filename name of the file to write the image to (string)
329 @param imageFormat format for the image file (string)
330 @return flag indicating success (boolean)
331 """
332 rect = self._getDiagramRect(self.border)
333 if imageFormat == "SVG":
334 self.__getDiagram(rect, imageFormat=imageFormat, filename=filename)
335 return True
336 else:
337 pixmap = self.__getDiagram(rect)
338 return pixmap.save(filename, imageFormat)
339
340 def printDiagram(self, printer, diagramName=""):
341 """
342 Public method to print the diagram.
343
344 @param printer reference to a ready configured printer object
345 (QPrinter)
346 @param diagramName name of the diagram (string)
347 """
348 painter = QPainter(printer)
349
350 font = QFont(["times"], 10)
351 painter.setFont(font)
352 fm = painter.fontMetrics()
353 fontHeight = fm.lineSpacing()
354 marginX = (
355 printer.pageLayout().paintRectPixels(printer.resolution()).x() -
356 printer.pageLayout().fullRectPixels(printer.resolution()).x()
357 )
358 marginX = (
359 Preferences.getPrinter("LeftMargin") *
360 int(printer.resolution() / 2.54) - marginX
361 )
362 marginY = (
363 printer.pageLayout().paintRectPixels(printer.resolution()).y() -
364 printer.pageLayout().fullRectPixels(printer.resolution()).y()
365 )
366 marginY = (
367 Preferences.getPrinter("TopMargin") *
368 int(printer.resolution() / 2.54) - marginY
369 )
370
371 width = (
372 printer.width() - marginX -
373 Preferences.getPrinter("RightMargin") *
374 int(printer.resolution() / 2.54)
375 )
376 height = (
377 printer.height() - fontHeight - 4 - marginY -
378 Preferences.getPrinter("BottomMargin") *
379 int(printer.resolution() / 2.54)
380 )
381
382 self.scene().render(painter,
383 target=QRectF(marginX, marginY, width, height))
384
385 # write a foot note
386 tc = QColor(50, 50, 50)
387 painter.setPen(tc)
388 painter.drawRect(marginX, marginY, width, height)
389 painter.drawLine(marginX, marginY + height + 2,
390 marginX + width, marginY + height + 2)
391 painter.setFont(font)
392 painter.drawText(marginX, marginY + height + 4, width,
393 fontHeight, Qt.AlignmentFlag.AlignRight, diagramName)
394
395 painter.end()
396
397 ###########################################################################
398 ## The methods below should be overridden by subclasses to get special
399 ## behavior.
400 ###########################################################################
401
402 def filteredItems(self, items):
403 """
404 Public method to filter a list of items.
405
406 @param items list of items as returned by the scene object
407 (QGraphicsItem)
408 @return list of interesting collision items (QGraphicsItem)
409 """
410 # just return the list unchanged
411 return items

eric ide

mercurial