|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2004 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog showing a pixmap. |
|
8 """ |
|
9 |
|
10 from PyQt6.QtCore import Qt, QSize, QEvent, QMarginsF |
|
11 from PyQt6.QtGui import ( |
|
12 QPalette, QImage, QPixmap, QPainter, QFont, QColor, QAction, QPageLayout |
|
13 ) |
|
14 from PyQt6.QtWidgets import ( |
|
15 QLabel, QSizePolicy, QScrollArea, QMenu, QToolBar |
|
16 ) |
|
17 from PyQt6.QtPrintSupport import QPrinter, QPrintDialog |
|
18 |
|
19 from EricWidgets import EricMessageBox |
|
20 from EricWidgets.EricMainWindow import EricMainWindow |
|
21 from EricWidgets.EricZoomWidget import EricZoomWidget |
|
22 |
|
23 import UI.Config |
|
24 |
|
25 import Preferences |
|
26 |
|
27 |
|
28 class PixmapDiagram(EricMainWindow): |
|
29 """ |
|
30 Class implementing a dialog showing a pixmap. |
|
31 """ |
|
32 ZoomLevels = [ |
|
33 1, 3, 5, 7, 9, |
|
34 10, 20, 30, 50, 67, 80, 90, |
|
35 100, |
|
36 110, 120, 133, 150, 170, 200, 240, 300, 400, |
|
37 500, 600, 700, 800, 900, 1000, |
|
38 ] |
|
39 ZoomLevelDefault = 100 |
|
40 |
|
41 def __init__(self, pixmap, parent=None, name=None): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param pixmap filename of a graphics file to show |
|
46 @type str |
|
47 @param parent parent widget of the view |
|
48 @type QWidget |
|
49 @param name name of the view widget |
|
50 @type str |
|
51 """ |
|
52 super().__init__(parent) |
|
53 if name: |
|
54 self.setObjectName(name) |
|
55 else: |
|
56 self.setObjectName("PixmapDiagram") |
|
57 self.setWindowTitle(self.tr("Pixmap-Viewer")) |
|
58 |
|
59 self.pixmapLabel = QLabel() |
|
60 self.pixmapLabel.setObjectName("pixmapLabel") |
|
61 self.pixmapLabel.setBackgroundRole(QPalette.ColorRole.Base) |
|
62 self.pixmapLabel.setSizePolicy( |
|
63 QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored) |
|
64 self.pixmapLabel.setScaledContents(True) |
|
65 |
|
66 self.pixmapView = QScrollArea() |
|
67 self.pixmapView.setObjectName("pixmapView") |
|
68 self.pixmapView.setBackgroundRole(QPalette.ColorRole.Dark) |
|
69 self.pixmapView.setWidget(self.pixmapLabel) |
|
70 |
|
71 self.setCentralWidget(self.pixmapView) |
|
72 |
|
73 self.__zoomWidget = EricZoomWidget( |
|
74 UI.PixmapCache.getPixmap("zoomOut"), |
|
75 UI.PixmapCache.getPixmap("zoomIn"), |
|
76 UI.PixmapCache.getPixmap("zoomReset"), self) |
|
77 self.statusBar().addPermanentWidget(self.__zoomWidget) |
|
78 self.__zoomWidget.setMapping( |
|
79 PixmapDiagram.ZoomLevels, PixmapDiagram.ZoomLevelDefault) |
|
80 self.__zoomWidget.valueChanged.connect(self.__doZoom) |
|
81 |
|
82 # polish up the dialog |
|
83 self.resize(QSize(800, 600).expandedTo(self.minimumSizeHint())) |
|
84 |
|
85 self.pixmapfile = pixmap |
|
86 self.status = self.__showPixmap(self.pixmapfile) |
|
87 |
|
88 self.__initActions() |
|
89 self.__initContextMenu() |
|
90 self.__initToolBars() |
|
91 |
|
92 self.grabGesture(Qt.GestureType.PinchGesture) |
|
93 |
|
94 def __initActions(self): |
|
95 """ |
|
96 Private method to initialize the view actions. |
|
97 """ |
|
98 self.closeAct = QAction( |
|
99 UI.PixmapCache.getIcon("close"), |
|
100 self.tr("Close"), self) |
|
101 self.closeAct.triggered.connect(self.close) |
|
102 |
|
103 self.printAct = QAction( |
|
104 UI.PixmapCache.getIcon("print"), |
|
105 self.tr("Print"), self) |
|
106 self.printAct.triggered.connect(self.__printDiagram) |
|
107 |
|
108 self.printPreviewAct = QAction( |
|
109 UI.PixmapCache.getIcon("printPreview"), |
|
110 self.tr("Print Preview"), self) |
|
111 self.printPreviewAct.triggered.connect(self.__printPreviewDiagram) |
|
112 |
|
113 def __initContextMenu(self): |
|
114 """ |
|
115 Private method to initialize the context menu. |
|
116 """ |
|
117 self.__menu = QMenu(self) |
|
118 self.__menu.addAction(self.closeAct) |
|
119 self.__menu.addSeparator() |
|
120 self.__menu.addAction(self.printPreviewAct) |
|
121 self.__menu.addAction(self.printAct) |
|
122 |
|
123 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) |
|
124 self.customContextMenuRequested.connect(self.__showContextMenu) |
|
125 |
|
126 def __showContextMenu(self, coord): |
|
127 """ |
|
128 Private slot to show the context menu of the listview. |
|
129 |
|
130 @param coord the position of the mouse pointer |
|
131 @type QPoint |
|
132 """ |
|
133 self.__menu.popup(self.mapToGlobal(coord)) |
|
134 |
|
135 def __initToolBars(self): |
|
136 """ |
|
137 Private method to populate the toolbars with our actions. |
|
138 """ |
|
139 self.windowToolBar = QToolBar(self.tr("Window"), self) |
|
140 self.windowToolBar.setIconSize(UI.Config.ToolBarIconSize) |
|
141 self.windowToolBar.addAction(self.closeAct) |
|
142 |
|
143 self.graphicsToolBar = QToolBar(self.tr("Graphics"), self) |
|
144 self.graphicsToolBar.setIconSize(UI.Config.ToolBarIconSize) |
|
145 self.graphicsToolBar.addAction(self.printPreviewAct) |
|
146 self.graphicsToolBar.addAction(self.printAct) |
|
147 |
|
148 self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.windowToolBar) |
|
149 self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.graphicsToolBar) |
|
150 |
|
151 def __showPixmap(self, filename): |
|
152 """ |
|
153 Private method to show a file. |
|
154 |
|
155 @param filename name of the file to be shown |
|
156 @type str |
|
157 @return flag indicating success |
|
158 @rtype bool |
|
159 """ |
|
160 image = QImage(filename) |
|
161 if image.isNull(): |
|
162 EricMessageBox.warning( |
|
163 self, |
|
164 self.tr("Pixmap-Viewer"), |
|
165 self.tr( |
|
166 """<p>The file <b>{0}</b> cannot be displayed.""" |
|
167 """ The format is not supported.</p>""").format(filename)) |
|
168 return False |
|
169 |
|
170 self.pixmapLabel.setPixmap(QPixmap.fromImage(image)) |
|
171 self.pixmapLabel.adjustSize() |
|
172 return True |
|
173 |
|
174 def getDiagramName(self): |
|
175 """ |
|
176 Public method to retrieve a name for the diagram. |
|
177 |
|
178 @return name for the diagram |
|
179 @rtype str |
|
180 """ |
|
181 return self.pixmapfile |
|
182 |
|
183 def getStatus(self): |
|
184 """ |
|
185 Public method to retrieve the status of the canvas. |
|
186 |
|
187 @return flag indicating a successful pixmap loading |
|
188 @rtype bool |
|
189 """ |
|
190 return self.status |
|
191 |
|
192 def wheelEvent(self, evt): |
|
193 """ |
|
194 Protected method to handle wheel events. |
|
195 |
|
196 @param evt reference to the wheel event |
|
197 @type QWheelEvent |
|
198 """ |
|
199 if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: |
|
200 delta = evt.angleDelta().y() |
|
201 if delta < 0: |
|
202 self.__zoomOut() |
|
203 elif delta > 0: |
|
204 self.__zoomIn() |
|
205 evt.accept() |
|
206 return |
|
207 |
|
208 super().wheelEvent(evt) |
|
209 |
|
210 def event(self, evt): |
|
211 """ |
|
212 Public method handling events. |
|
213 |
|
214 @param evt reference to the event |
|
215 @type QEvent |
|
216 @return flag indicating, if the event was handled |
|
217 @rtype bool |
|
218 """ |
|
219 if evt.type() == QEvent.Type.Gesture: |
|
220 self.gestureEvent(evt) |
|
221 return True |
|
222 |
|
223 return super().event(evt) |
|
224 |
|
225 def gestureEvent(self, evt): |
|
226 """ |
|
227 Protected method handling gesture events. |
|
228 |
|
229 @param evt reference to the gesture event |
|
230 @type QGestureEvent |
|
231 """ |
|
232 pinch = evt.gesture(Qt.GestureType.PinchGesture) |
|
233 if pinch: |
|
234 if pinch.state() == Qt.GestureState.GestureStarted: |
|
235 pinch.setTotalScaleFactor(self.__zoom() / 100) |
|
236 elif pinch.state() == Qt.GestureState.GestureUpdated: |
|
237 self.__doZoom(int(pinch.totalScaleFactor() * 100)) |
|
238 evt.accept() |
|
239 |
|
240 ########################################################################### |
|
241 ## Private menu handling methods below. |
|
242 ########################################################################### |
|
243 |
|
244 def __adjustScrollBar(self, scrollBar, factor): |
|
245 """ |
|
246 Private method to adjust a scrollbar by a certain factor. |
|
247 |
|
248 @param scrollBar reference to the scrollbar object |
|
249 @type QScrollBar |
|
250 @param factor factor to adjust by |
|
251 @type float |
|
252 """ |
|
253 scrollBar.setValue(int(factor * scrollBar.value() + |
|
254 ((factor - 1) * scrollBar.pageStep() / 2))) |
|
255 |
|
256 def __levelForZoom(self, zoom): |
|
257 """ |
|
258 Private method determining the zoom level index given a zoom factor. |
|
259 |
|
260 @param zoom zoom factor |
|
261 @type int |
|
262 @return index of zoom factor |
|
263 @rtype int |
|
264 """ |
|
265 try: |
|
266 index = PixmapDiagram.ZoomLevels.index(zoom) |
|
267 except ValueError: |
|
268 for index in range(len(PixmapDiagram.ZoomLevels)): |
|
269 if zoom <= PixmapDiagram.ZoomLevels[index]: |
|
270 break |
|
271 return index |
|
272 |
|
273 def __doZoom(self, value): |
|
274 """ |
|
275 Private method to set the zoom value in percent. |
|
276 |
|
277 @param value zoom value in percent |
|
278 @type int |
|
279 """ |
|
280 oldValue = self.__zoom() |
|
281 if value != oldValue: |
|
282 self.pixmapLabel.resize( |
|
283 value / 100 * self.pixmapLabel.pixmap().size()) |
|
284 |
|
285 factor = value / oldValue |
|
286 self.__adjustScrollBar( |
|
287 self.pixmapView.horizontalScrollBar(), factor) |
|
288 self.__adjustScrollBar( |
|
289 self.pixmapView.verticalScrollBar(), factor) |
|
290 |
|
291 self.__zoomWidget.setValue(value) |
|
292 |
|
293 def __zoomIn(self): |
|
294 """ |
|
295 Private method to zoom into the pixmap. |
|
296 """ |
|
297 index = self.__levelForZoom(self.__zoom()) |
|
298 if index < len(PixmapDiagram.ZoomLevels) - 1: |
|
299 self.__doZoom(PixmapDiagram.ZoomLevels[index + 1]) |
|
300 |
|
301 def __zoomOut(self): |
|
302 """ |
|
303 Private method to zoom out of the pixmap. |
|
304 """ |
|
305 index = self.__levelForZoom(self.__zoom()) |
|
306 if index > 0: |
|
307 self.__doZoom(PixmapDiagram.ZoomLevels[index - 1]) |
|
308 |
|
309 def __zoomReset(self): |
|
310 """ |
|
311 Private method to reset the zoom value. |
|
312 """ |
|
313 self.__doZoom(PixmapDiagram.ZoomLevels[PixmapDiagram.ZoomLevelDefault]) |
|
314 |
|
315 def __zoom(self): |
|
316 """ |
|
317 Private method to get the current zoom factor in percent. |
|
318 |
|
319 @return current zoom factor in percent |
|
320 @rtype int |
|
321 """ |
|
322 return int(self.pixmapLabel.width() / |
|
323 self.pixmapLabel.pixmap().width() * 100.0) |
|
324 |
|
325 def __printDiagram(self): |
|
326 """ |
|
327 Private slot called to print the diagram. |
|
328 """ |
|
329 printer = QPrinter(mode=QPrinter.PrinterMode.ScreenResolution) |
|
330 printer.setFullPage(True) |
|
331 if Preferences.getPrinter("ColorMode"): |
|
332 printer.setColorMode(QPrinter.ColorMode.Color) |
|
333 else: |
|
334 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
|
335 if Preferences.getPrinter("FirstPageFirst"): |
|
336 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
|
337 else: |
|
338 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
|
339 printer.setPrinterName(Preferences.getPrinter("PrinterName")) |
|
340 |
|
341 printDialog = QPrintDialog(printer, self) |
|
342 if printDialog.exec(): |
|
343 self.__print(printer) |
|
344 |
|
345 def __printPreviewDiagram(self): |
|
346 """ |
|
347 Private slot called to show a print preview of the diagram. |
|
348 """ |
|
349 from PyQt6.QtPrintSupport import QPrintPreviewDialog |
|
350 |
|
351 printer = QPrinter(mode=QPrinter.PrinterMode.ScreenResolution) |
|
352 printer.setFullPage(True) |
|
353 if Preferences.getPrinter("ColorMode"): |
|
354 printer.setColorMode(QPrinter.ColorMode.Color) |
|
355 else: |
|
356 printer.setColorMode(QPrinter.ColorMode.GrayScale) |
|
357 if Preferences.getPrinter("FirstPageFirst"): |
|
358 printer.setPageOrder(QPrinter.PageOrder.FirstPageFirst) |
|
359 else: |
|
360 printer.setPageOrder(QPrinter.PageOrder.LastPageFirst) |
|
361 printer.setPageMargins(QMarginsF( |
|
362 Preferences.getPrinter("LeftMargin") * 10, |
|
363 Preferences.getPrinter("TopMargin") * 10, |
|
364 Preferences.getPrinter("RightMargin") * 10, |
|
365 Preferences.getPrinter("BottomMargin") * 10), |
|
366 QPageLayout.Unit.Millimeter |
|
367 ) |
|
368 printer.setPrinterName(Preferences.getPrinter("PrinterName")) |
|
369 |
|
370 preview = QPrintPreviewDialog(printer, self) |
|
371 preview.paintRequested[QPrinter].connect(self.__print) |
|
372 preview.exec() |
|
373 |
|
374 def __print(self, printer): |
|
375 """ |
|
376 Private slot to the actual printing. |
|
377 |
|
378 @param printer reference to the printer object |
|
379 @type QPrinter |
|
380 """ |
|
381 painter = QPainter() |
|
382 painter.begin(printer) |
|
383 |
|
384 # calculate margin and width of printout |
|
385 font = QFont(["times"], 10) |
|
386 painter.setFont(font) |
|
387 fm = painter.fontMetrics() |
|
388 fontHeight = fm.lineSpacing() |
|
389 marginX = ( |
|
390 printer.pageLayout().paintRectPixels(printer.resolution()).x() - |
|
391 printer.pageLayout().fullRectPixels(printer.resolution()).x() |
|
392 ) |
|
393 marginX = ( |
|
394 Preferences.getPrinter("LeftMargin") * |
|
395 int(printer.resolution() / 2.54) - marginX |
|
396 ) |
|
397 marginY = ( |
|
398 printer.pageLayout().paintRectPixels(printer.resolution()).y() - |
|
399 printer.pageLayout().fullRectPixels(printer.resolution()).y() |
|
400 ) |
|
401 marginY = ( |
|
402 Preferences.getPrinter("TopMargin") * |
|
403 int(printer.resolution() / 2.54) - marginY |
|
404 ) |
|
405 |
|
406 width = ( |
|
407 printer.width() - marginX - |
|
408 Preferences.getPrinter("RightMargin") * |
|
409 int(printer.resolution() / 2.54) |
|
410 ) |
|
411 height = ( |
|
412 printer.height() - fontHeight - 4 - marginY - |
|
413 Preferences.getPrinter("BottomMargin") * |
|
414 int(printer.resolution() / 2.54) |
|
415 ) |
|
416 |
|
417 # write a foot note |
|
418 s = self.tr("Diagram: {0}").format(self.getDiagramName()) |
|
419 tc = QColor(50, 50, 50) |
|
420 painter.setPen(tc) |
|
421 painter.drawRect(marginX, marginY, width, height) |
|
422 painter.drawLine(marginX, marginY + height + 2, |
|
423 marginX + width, marginY + height + 2) |
|
424 painter.setFont(font) |
|
425 painter.drawText(marginX, marginY + height + 4, width, |
|
426 fontHeight, Qt.AlignmentFlag.AlignRight, s) |
|
427 |
|
428 # render the diagram |
|
429 size = self.pixmapLabel.pixmap().size() |
|
430 size.scale(QSize(width - 10, height - 10), # 5 px inner margin |
|
431 Qt.AspectRatioMode.KeepAspectRatio) |
|
432 painter.setViewport(marginX + 5, marginY + 5, |
|
433 size.width(), size.height()) |
|
434 painter.setWindow(self.pixmapLabel.pixmap().rect()) |
|
435 painter.drawPixmap(0, 0, self.pixmapLabel.pixmap()) |
|
436 painter.end() |