E5Graphics/E5GraphicsView.py

changeset 51
7d80b0f20ca6
child 96
9624a110667d
equal deleted inserted replaced
50:a36eecf45b2e 51:7d80b0f20ca6
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2010 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a canvas view class.
8 """
9
10 import sys
11
12 from PyQt4.QtCore import *
13 from PyQt4.QtGui import *
14
15 import Preferences
16
17 class E5GraphicsView(QGraphicsView):
18 """
19 Class implementing a graphics view.
20 """
21 def __init__(self, scene, parent = None):
22 """
23 Constructor
24
25 @param scene reference to the scene object (QGraphicsScene)
26 @param parent parent widget (QWidget)
27 """
28 QGraphicsView.__init__(self, scene, parent)
29 self.setObjectName("E5GraphicsView")
30
31 self.setBackgroundBrush(QBrush(Qt.white))
32 self.setRenderHint(QPainter.Antialiasing, True)
33 self.setDragMode(QGraphicsView.RubberBandDrag)
34 self.setAlignment(Qt.Alignment(Qt.AlignLeft | Qt.AlignTop))
35 self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
36 self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
37
38 # available as of Qt 4.3
39 try:
40 self.setViewportUpdateMode(QGraphicsView.SmartViewportUpdate)
41 except AttributeError:
42 pass
43
44 self.setWhatsThis(self.trUtf8("<b>Graphics View</b>\n"
45 "<p>This graphics view is used to show a diagram. \n"
46 "There are various actions available to manipulate the \n"
47 "shown items.</p>\n"
48 "<ul>\n"
49 "<li>Clicking on an item selects it.</li>\n"
50 "<li>Ctrl-clicking adds an item to the selection.</li>\n"
51 "<li>Ctrl-clicking a selected item deselects it.</li>\n"
52 "<li>Clicking on an empty spot of the canvas resets the selection.</li>\n"
53 "<li>Dragging the mouse over the canvas spans a rubberband to \n"
54 "select multiple items.</li>\n"
55 "<li>Dragging the mouse over a selected item moves the \n"
56 "whole selection.</li>\n"
57 "</ul>\n"
58 ))
59
60 def zoomIn(self):
61 """
62 Public method to zoom in.
63 """
64 self.scale(1.25, 1.25)
65
66 def zoomOut(self):
67 """
68 Public method to zoom out.
69 """
70 self.scale(0.8, 0.8)
71
72 def zoomReset(self):
73 """
74 Public method to handle the reset zoom context menu entry.
75 """
76 self.resetMatrix()
77
78 def setZoom(self, zoomFactor):
79 """
80 Public method to set the zoom factor.
81
82 @param zoomFactor new zoom factor (float)
83 """
84 self.zoomReset()
85 self.scale(zoomFactor, zoomFactor)
86
87 def zoom(self):
88 """
89 Public method to get the current zoom factor.
90
91 @return current zoom factor (float)
92 """
93 return self.matrix().m11()
94
95 def resizeScene(self, amount, isWidth = True):
96 """
97 Public method to resize the scene.
98
99 @param isWidth flag indicating width is to be resized (boolean)
100 @param amount size increment (integer)
101 """
102 sceneRect = self.scene().sceneRect()
103 width = sceneRect.width()
104 height = sceneRect.height()
105 if isWidth:
106 width += amount
107 else:
108 height += amount
109 rect = self._getDiagramRect(10)
110 if width < rect.width():
111 width = rect.width()
112 if height < rect.height():
113 height = rect.height()
114
115 self.setSceneSize(width, height)
116
117 def setSceneSize(self, width, height):
118 """
119 Public method to set the scene size.
120
121 @param width width for the scene (integer)
122 @param height height for the scene (integer)
123 """
124 rect = self.scene().sceneRect()
125 rect.setHeight(height)
126 rect.setWidth(width)
127 self.setSceneRect(rect)
128
129 def _getDiagramRect(self, border = 0):
130 """
131 Protected method to calculate the minimum rectangle fitting the diagram.
132
133 @param border border width to include in the calculation (integer)
134 @return the minimum rectangle (QRectF)
135 """
136 startx = sys.maxsize
137 starty = sys.maxsize
138 endx = 0
139 endy = 0
140 items = self.filteredItems(list(self.scene().items()))
141 for itm in items:
142 rect = itm.sceneBoundingRect()
143 itmEndX = rect.x() + rect.width()
144 itmEndY = rect.y()+ rect.height()
145 itmStartX = rect.x()
146 itmStartY = rect.y()
147 if startx >= itmStartX:
148 startx = itmStartX
149 if starty >= itmStartY:
150 starty = itmStartY
151 if endx <= itmEndX:
152 endx = itmEndX
153 if endy <= itmEndY:
154 endy = itmEndY
155 if border:
156 startx -= border
157 starty -= border
158 endx += border
159 endy += border
160
161 return QRectF(startx, starty, endx - startx + 1, endy - starty + 1)
162
163 def _getDiagramSize(self, border = 0):
164 """
165 Protected method to calculate the minimum size fitting the diagram.
166
167 @param border border width to include in the calculation (integer)
168 @return the minimum size (QSizeF)
169 """
170 endx = 0
171 endy = 0
172 items = self.filteredItems(list(self.scene().items()))
173 for itm in items:
174 rect = itm.sceneBoundingRect()
175 itmEndX = rect.x() + rect.width()
176 itmEndY = rect.y()+ rect.height()
177 if endx <= itmEndX:
178 endx = itmEndX
179 if endy <= itmEndY:
180 endy = itmEndY
181 if border:
182 endx += border
183 endy += border
184
185 return QSizeF(endx + 1, endy + 1)
186
187 def __getDiagram(self, rect, format = "PNG", filename = None):
188 """
189 Private method to retrieve the diagram from the scene fitting it
190 in the minimum rectangle.
191
192 @param rect minimum rectangle fitting the diagram (QRectF)
193 @param format format for the image file (string)
194 @param filename name of the file for non pixmaps (string)
195 @return diagram pixmap to receive the diagram (QPixmap)
196 """
197 selectedItems = self.scene().selectedItems()
198
199 # step 1: deselect all widgets
200 if selectedItems:
201 for item in selectedItems:
202 item.setSelected(False)
203
204 # step 2: grab the diagram
205 if format == "PNG":
206 paintDevice = QPixmap(int(rect.width()), int(rect.height()))
207 paintDevice.fill(self.backgroundBrush().color())
208 else:
209 from PyQt4.QtSvg import QSvgGenerator
210 paintDevice = QSvgGenerator()
211 paintDevice.setResolution(100) # 100 dpi
212 paintDevice.setSize(QSize(int(rect.width()), int(rect.height())))
213 paintDevice.setFileName(filename)
214 painter = QPainter(paintDevice)
215 painter.setRenderHint(QPainter.Antialiasing, True)
216 self.scene().render(painter, QRectF(), rect)
217
218 # step 3: reselect the widgets
219 if selectedItems:
220 for item in selectedItems:
221 item.setSelected(True)
222
223 return paintDevice
224
225 def saveImage(self, filename, format = "PNG"):
226 """
227 Public method to save the scene to a file.
228
229 @param filename name of the file to write the image to (string)
230 @param format format for the image file (string)
231 @return flag indicating success (boolean)
232 """
233 rect = self._getDiagramRect(self.border)
234 if format == "SVG":
235 svg = self.__getDiagram(rect, format = format, filename = filename)
236 return True
237 else:
238 pixmap = self.__getDiagram(rect)
239 return pixmap.save(filename, format)
240
241 def printDiagram(self, printer, diagramName = ""):
242 """
243 Public method to print the diagram.
244
245 @param printer reference to a ready configured printer object (QPrinter)
246 @param diagramName name of the diagram (string)
247 """
248 painter = QPainter()
249 painter.begin(printer)
250 offsetX = 0
251 offsetY = 0
252 widthX = 0
253 heightY = 0
254 font = QFont("times", 10)
255 painter.setFont(font)
256 fm = painter.fontMetrics()
257 fontHeight = fm.lineSpacing()
258 marginX = printer.pageRect().x() - printer.paperRect().x()
259 marginX = \
260 Preferences.getPrinter("LeftMargin") * int(printer.resolution() / 2.54) \
261 - marginX
262 marginY = printer.pageRect().y() - printer.paperRect().y()
263 marginY = \
264 Preferences.getPrinter("TopMargin") * int(printer.resolution() / 2.54) \
265 - marginY
266
267 width = printer.width() - marginX \
268 - Preferences.getPrinter("RightMargin") * int(printer.resolution() / 2.54)
269 height = printer.height() - fontHeight - 4 - marginY \
270 - Preferences.getPrinter("BottomMargin") * int(printer.resolution() / 2.54)
271
272 border = self.border == 0 and 5 or self.border
273 rect = self._getDiagramRect(border)
274 diagram = self.__getDiagram(rect)
275
276 finishX = False
277 finishY = False
278 page = 0
279 pageX = 0
280 pageY = 1
281 while not finishX or not finishY:
282 if not finishX:
283 offsetX = pageX * width
284 pageX += 1
285 elif not finishY:
286 offsetY = pageY * height
287 offsetX = 0
288 pageY += 1
289 finishX = False
290 pageX = 1
291 if (width + offsetX) > diagram.width():
292 finishX = True
293 widthX = diagram.width() - offsetX
294 else:
295 widthX = width
296 if diagram.width() < width:
297 widthX = diagram.width()
298 finishX = True
299 offsetX = 0
300 if (height + offsetY) > diagram.height():
301 finishY = True
302 heightY = diagram.height() - offsetY
303 else:
304 heightY = height
305 if diagram.height() < height:
306 finishY = True
307 heightY = diagram.height()
308 offsetY = 0
309
310 painter.drawPixmap(marginX, marginY, diagram,
311 offsetX, offsetY, widthX, heightY)
312 # write a foot note
313 s = self.trUtf8("Diagram: {0}, Page {1}").format(diagramName, page + 1)
314 tc = QColor(50, 50, 50)
315 painter.setPen(tc)
316 painter.drawRect(marginX, marginY, width, height)
317 painter.drawLine(marginX, marginY + height + 2,
318 marginX + width, marginY + height + 2)
319 painter.setFont(font)
320 painter.drawText(marginX, marginY + height + 4, width,
321 fontHeight, Qt.AlignRight, s)
322 if not finishX or not finishY:
323 printer.newPage()
324 page += 1
325
326 painter.end()
327
328 ############################################################################
329 ## The methods below should be overridden by subclasses to get special
330 ## behavior.
331 ############################################################################
332
333 def filteredItems(self, items):
334 """
335 Public method to filter a list of items.
336
337 @param items list of items as returned by the scene object
338 (QGraphicsItem)
339 @return list of interesting collision items (QGraphicsItem)
340 """
341 # just return the list unchanged
342 return items

eric ide

mercurial