src/eric7/Tools/UIPreviewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2004 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the UI Previewer main window.
8 """
9
10 import contextlib
11 import pathlib
12
13 from PyQt6.QtCore import QDir, QEvent, QSize, Qt
14 from PyQt6.QtGui import QAction, QKeySequence, QImageWriter, QPainter
15 from PyQt6.QtWidgets import (
16 QSizePolicy, QSpacerItem, QWidget, QHBoxLayout, QWhatsThis, QDialog,
17 QScrollArea, QApplication, QStyleFactory, QFrame, QMainWindow,
18 QComboBox, QVBoxLayout, QLabel
19 )
20 from PyQt6.QtPrintSupport import QPrinter, QPrintDialog
21 from PyQt6 import uic
22
23
24 from EricWidgets import EricMessageBox, EricFileDialog
25 from EricWidgets.EricMainWindow import EricMainWindow
26 from EricWidgets.EricApplication import ericApp
27 from EricGui.EricOverrideCursor import EricOverrideCursor
28
29 import Preferences
30 import UI.PixmapCache
31 import UI.Config
32
33
34 class UIPreviewer(EricMainWindow):
35 """
36 Class implementing the UI Previewer main window.
37 """
38 def __init__(self, filename=None, parent=None, name=None):
39 """
40 Constructor
41
42 @param filename name of a UI file to load
43 @param parent parent widget of this window (QWidget)
44 @param name name of this window (string)
45 """
46 self.mainWidget = None
47 self.currentFile = QDir.currentPath()
48
49 super().__init__(parent)
50 if not name:
51 self.setObjectName("UIPreviewer")
52 else:
53 self.setObjectName(name)
54
55 self.setStyle(Preferences.getUI("Style"),
56 Preferences.getUI("StyleSheet"))
57
58 self.resize(QSize(600, 480).expandedTo(self.minimumSizeHint()))
59 self.statusBar()
60
61 self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
62 self.setWindowTitle(self.tr("UI Previewer"))
63
64 self.cw = QWidget(self)
65 self.cw.setObjectName("centralWidget")
66
67 self.UIPreviewerLayout = QVBoxLayout(self.cw)
68 self.UIPreviewerLayout.setContentsMargins(6, 6, 6, 6)
69 self.UIPreviewerLayout.setSpacing(6)
70 self.UIPreviewerLayout.setObjectName("UIPreviewerLayout")
71
72 self.styleLayout = QHBoxLayout()
73 self.styleLayout.setContentsMargins(0, 0, 0, 0)
74 self.styleLayout.setSpacing(6)
75 self.styleLayout.setObjectName("styleLayout")
76
77 self.styleLabel = QLabel(self.tr("Select GUI Theme"), self.cw)
78 self.styleLabel.setObjectName("styleLabel")
79 self.styleLayout.addWidget(self.styleLabel)
80
81 self.styleCombo = QComboBox(self.cw)
82 self.styleCombo.setObjectName("styleCombo")
83 self.styleCombo.setEditable(False)
84 self.styleCombo.setToolTip(self.tr("Select the GUI Theme"))
85 self.styleLayout.addWidget(self.styleCombo)
86 self.styleCombo.addItems(sorted(QStyleFactory().keys()))
87 currentStyle = Preferences.getSettings().value('UIPreviewer/style')
88 if currentStyle is not None:
89 self.styleCombo.setCurrentIndex(int(currentStyle))
90
91 styleSpacer = QSpacerItem(
92 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
93 self.styleLayout.addItem(styleSpacer)
94 self.UIPreviewerLayout.addLayout(self.styleLayout)
95
96 self.previewSV = QScrollArea(self.cw)
97 self.previewSV.setObjectName("preview")
98 self.previewSV.setFrameShape(QFrame.Shape.NoFrame)
99 self.previewSV.setFrameShadow(QFrame.Shadow.Plain)
100 self.previewSV.setSizePolicy(
101 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
102 self.UIPreviewerLayout.addWidget(self.previewSV)
103
104 self.setCentralWidget(self.cw)
105
106 self.styleCombo.activated[int].connect(self.__guiStyleSelected)
107
108 self.__initActions()
109 self.__initMenus()
110 self.__initToolbars()
111
112 self.__updateActions()
113
114 # defere loading of a UI file until we are shown
115 self.fileToLoad = filename
116
117 def show(self):
118 """
119 Public slot to show this dialog.
120
121 This overloaded slot loads a UI file to be previewed after
122 the main window has been shown. This way, previewing a dialog
123 doesn't interfere with showing the main window.
124 """
125 super().show()
126 if self.fileToLoad is not None:
127 fn, self.fileToLoad = (self.fileToLoad, None)
128 self.__loadFile(fn)
129
130 def __initActions(self):
131 """
132 Private method to define the user interface actions.
133 """
134 self.openAct = QAction(
135 UI.PixmapCache.getIcon("openUI"),
136 self.tr('&Open File'), self)
137 self.openAct.setShortcut(
138 QKeySequence(self.tr("Ctrl+O", "File|Open")))
139 self.openAct.setStatusTip(self.tr('Open a UI file for display'))
140 self.openAct.setWhatsThis(self.tr(
141 """<b>Open File</b>"""
142 """<p>This opens a new UI file for display.</p>"""
143 ))
144 self.openAct.triggered.connect(self.__openFile)
145
146 self.printAct = QAction(
147 UI.PixmapCache.getIcon("print"),
148 self.tr('&Print'), self)
149 self.printAct.setShortcut(
150 QKeySequence(self.tr("Ctrl+P", "File|Print")))
151 self.printAct.setStatusTip(self.tr('Print a screen capture'))
152 self.printAct.setWhatsThis(self.tr(
153 """<b>Print</b>"""
154 """<p>Print a screen capture.</p>"""
155 ))
156 self.printAct.triggered.connect(self.__printImage)
157
158 self.printPreviewAct = QAction(
159 UI.PixmapCache.getIcon("printPreview"),
160 self.tr('Print Preview'), self)
161 self.printPreviewAct.setStatusTip(self.tr(
162 'Print preview a screen capture'))
163 self.printPreviewAct.setWhatsThis(self.tr(
164 """<b>Print Preview</b>"""
165 """<p>Print preview a screen capture.</p>"""
166 ))
167 self.printPreviewAct.triggered.connect(self.__printPreviewImage)
168
169 self.imageAct = QAction(
170 UI.PixmapCache.getIcon("screenCapture"),
171 self.tr('&Screen Capture'), self)
172 self.imageAct.setShortcut(
173 QKeySequence(self.tr("Ctrl+S", "File|Screen Capture")))
174 self.imageAct.setStatusTip(self.tr(
175 'Save a screen capture to an image file'))
176 self.imageAct.setWhatsThis(self.tr(
177 """<b>Screen Capture</b>"""
178 """<p>Save a screen capture to an image file.</p>"""
179 ))
180 self.imageAct.triggered.connect(self.__saveImage)
181
182 self.exitAct = QAction(
183 UI.PixmapCache.getIcon("exit"), self.tr('&Quit'), self)
184 self.exitAct.setShortcut(
185 QKeySequence(self.tr("Ctrl+Q", "File|Quit")))
186 self.exitAct.setStatusTip(self.tr('Quit the application'))
187 self.exitAct.setWhatsThis(self.tr(
188 """<b>Quit</b>"""
189 """<p>Quit the application.</p>"""
190 ))
191 self.exitAct.triggered.connect(ericApp().closeAllWindows)
192
193 self.copyAct = QAction(
194 UI.PixmapCache.getIcon("editCopy"), self.tr('&Copy'), self)
195 self.copyAct.setShortcut(
196 QKeySequence(self.tr("Ctrl+C", "Edit|Copy")))
197 self.copyAct.setStatusTip(
198 self.tr('Copy screen capture to clipboard'))
199 self.copyAct.setWhatsThis(self.tr(
200 """<b>Copy</b>"""
201 """<p>Copy screen capture to clipboard.</p>"""
202 ))
203 self.copyAct.triggered.connect(self.__copyImageToClipboard)
204
205 self.whatsThisAct = QAction(
206 UI.PixmapCache.getIcon("whatsThis"),
207 self.tr('&What\'s This?'), self)
208 self.whatsThisAct.setShortcut(QKeySequence(self.tr("Shift+F1")))
209 self.whatsThisAct.setStatusTip(self.tr('Context sensitive help'))
210 self.whatsThisAct.setWhatsThis(self.tr(
211 """<b>Display context sensitive help</b>"""
212 """<p>In What's This? mode, the mouse cursor shows an arrow"""
213 """ with a question mark, and you can click on the interface"""
214 """ elements to get a short description of what they do and"""
215 """ how to use them. In dialogs, this feature can be accessed"""
216 """ using the context help button in the titlebar.</p>"""
217 ))
218 self.whatsThisAct.triggered.connect(self.__whatsThis)
219
220 self.aboutAct = QAction(self.tr('&About'), self)
221 self.aboutAct.setStatusTip(self.tr(
222 'Display information about this software'))
223 self.aboutAct.setWhatsThis(self.tr(
224 """<b>About</b>"""
225 """<p>Display some information about this software.</p>"""
226 ))
227 self.aboutAct.triggered.connect(self.__about)
228
229 self.aboutQtAct = QAction(self.tr('About &Qt'), self)
230 self.aboutQtAct.setStatusTip(
231 self.tr('Display information about the Qt toolkit'))
232 self.aboutQtAct.setWhatsThis(self.tr(
233 """<b>About Qt</b>"""
234 """<p>Display some information about the Qt toolkit.</p>"""
235 ))
236 self.aboutQtAct.triggered.connect(self.__aboutQt)
237
238 def __initMenus(self):
239 """
240 Private method to create the menus.
241 """
242 mb = self.menuBar()
243
244 menu = mb.addMenu(self.tr('&File'))
245 menu.setTearOffEnabled(True)
246 menu.addAction(self.openAct)
247 menu.addAction(self.imageAct)
248 menu.addSeparator()
249 menu.addAction(self.printPreviewAct)
250 menu.addAction(self.printAct)
251 menu.addSeparator()
252 menu.addAction(self.exitAct)
253
254 menu = mb.addMenu(self.tr("&Edit"))
255 menu.setTearOffEnabled(True)
256 menu.addAction(self.copyAct)
257
258 mb.addSeparator()
259
260 menu = mb.addMenu(self.tr('&Help'))
261 menu.setTearOffEnabled(True)
262 menu.addAction(self.aboutAct)
263 menu.addAction(self.aboutQtAct)
264 menu.addSeparator()
265 menu.addAction(self.whatsThisAct)
266
267 def __initToolbars(self):
268 """
269 Private method to create the toolbars.
270 """
271 filetb = self.addToolBar(self.tr("File"))
272 filetb.setIconSize(UI.Config.ToolBarIconSize)
273 filetb.addAction(self.openAct)
274 filetb.addAction(self.imageAct)
275 filetb.addSeparator()
276 filetb.addAction(self.printPreviewAct)
277 filetb.addAction(self.printAct)
278 filetb.addSeparator()
279 filetb.addAction(self.exitAct)
280
281 edittb = self.addToolBar(self.tr("Edit"))
282 edittb.setIconSize(UI.Config.ToolBarIconSize)
283 edittb.addAction(self.copyAct)
284
285 helptb = self.addToolBar(self.tr("Help"))
286 helptb.setIconSize(UI.Config.ToolBarIconSize)
287 helptb.addAction(self.whatsThisAct)
288
289 def __whatsThis(self):
290 """
291 Private slot called in to enter Whats This mode.
292 """
293 QWhatsThis.enterWhatsThisMode()
294
295 def __guiStyleSelected(self, index):
296 """
297 Private slot to handle the selection of a GUI style.
298
299 @param index index of the selected entry
300 @type int
301 """
302 selectedStyle = self.styleCombo.itemText(index)
303 if self.mainWidget:
304 self.__updateChildren(selectedStyle)
305
306 def __about(self):
307 """
308 Private slot to show the about information.
309 """
310 EricMessageBox.about(
311 self,
312 self.tr("UI Previewer"),
313 self.tr(
314 """<h3> About UI Previewer </h3>"""
315 """<p>The UI Previewer loads and displays Qt User-Interface"""
316 """ files with various styles, which are selectable via a"""
317 """ selection list.</p>"""
318 )
319 )
320
321 def __aboutQt(self):
322 """
323 Private slot to show info about Qt.
324 """
325 EricMessageBox.aboutQt(self, self.tr("UI Previewer"))
326
327 def __openFile(self):
328 """
329 Private slot to load a new file.
330 """
331 fn = EricFileDialog.getOpenFileName(
332 self,
333 self.tr("Select UI file"),
334 self.currentFile,
335 self.tr("Qt User-Interface Files (*.ui)"))
336 if fn:
337 self.__loadFile(fn)
338
339 def __loadFile(self, fn):
340 """
341 Private slot to load a ui file.
342
343 @param fn name of the ui file to be laoded
344 @type str
345 """
346 if self.mainWidget:
347 self.mainWidget.close()
348 self.previewSV.takeWidget()
349 del self.mainWidget
350 self.mainWidget = None
351
352 # load the file
353 with contextlib.suppress(Exception):
354 self.mainWidget = uic.loadUi(fn)
355
356 if self.mainWidget:
357 self.currentFile = fn
358 self.__updateChildren(self.styleCombo.currentText())
359 if isinstance(self.mainWidget, (QDialog, QMainWindow)):
360 self.mainWidget.show()
361 self.mainWidget.installEventFilter(self)
362 else:
363 self.previewSV.setWidget(self.mainWidget)
364 self.mainWidget.show()
365 else:
366 EricMessageBox.warning(
367 self,
368 self.tr("Load UI File"),
369 self.tr(
370 """<p>The file <b>{0}</b> could not be loaded.</p>""")
371 .format(fn))
372 self.__updateActions()
373
374 def __updateChildren(self, sstyle):
375 """
376 Private slot to change the style of the show UI.
377
378 @param sstyle name of the selected style (string)
379 """
380 with EricOverrideCursor():
381 qstyle = QStyleFactory.create(sstyle)
382 self.mainWidget.setStyle(qstyle)
383
384 lst = self.mainWidget.findChildren(QWidget)
385 for obj in lst:
386 with contextlib.suppress(AttributeError):
387 obj.setStyle(qstyle)
388 del lst
389
390 self.mainWidget.hide()
391 self.mainWidget.show()
392
393 self.lastQStyle = qstyle
394 self.lastStyle = sstyle
395 Preferences.getSettings().setValue(
396 'UIPreviewer/style', self.styleCombo.currentIndex())
397
398 def __updateActions(self):
399 """
400 Private slot to update the actions state.
401 """
402 if self.mainWidget:
403 self.imageAct.setEnabled(True)
404 self.printAct.setEnabled(True)
405 if self.printPreviewAct:
406 self.printPreviewAct.setEnabled(True)
407 self.copyAct.setEnabled(True)
408 self.styleCombo.setEnabled(True)
409 else:
410 self.imageAct.setEnabled(False)
411 self.printAct.setEnabled(False)
412 if self.printPreviewAct:
413 self.printPreviewAct.setEnabled(False)
414 self.copyAct.setEnabled(False)
415 self.styleCombo.setEnabled(False)
416
417 def __handleCloseEvent(self):
418 """
419 Private slot to handle the close event of a viewed QMainWidget.
420 """
421 if self.mainWidget:
422 self.mainWidget.removeEventFilter(self)
423 del self.mainWidget
424 self.mainWidget = None
425 self.__updateActions()
426
427 def eventFilter(self, obj, ev):
428 """
429 Public method called to filter an event.
430
431 @param obj object, that generated the event (QObject)
432 @param ev the event, that was generated by object (QEvent)
433 @return flag indicating if event was filtered out
434 """
435 if obj == self.mainWidget:
436 if ev.type() == QEvent.Type.Close:
437 self.__handleCloseEvent()
438 return True
439 else:
440 if isinstance(self.mainWidget, QDialog):
441 return QDialog.eventFilter(self, obj, ev)
442 elif isinstance(self.mainWidget, QMainWindow):
443 return QMainWindow.eventFilter(self, obj, ev)
444 else:
445 return False
446
447 def __saveImage(self):
448 """
449 Private slot to handle the Save Image menu action.
450 """
451 if self.mainWidget is None:
452 EricMessageBox.critical(
453 self,
454 self.tr("Save Image"),
455 self.tr("""There is no UI file loaded."""))
456 return
457
458 defaultExt = "PNG"
459 filters = ""
460 formats = QImageWriter.supportedImageFormats()
461 for imageFormat in formats:
462 filters = "{0}*.{1} ".format(
463 filters, bytes(imageFormat).decode().lower())
464 fileFilter = self.tr("Images ({0})").format(filters[:-1])
465
466 fname = EricFileDialog.getSaveFileName(
467 self,
468 self.tr("Save Image"),
469 "",
470 fileFilter)
471 if not fname:
472 return
473
474 fpath = pathlib.Path(fname)
475 ext = fpath.suffix.upper().replace(".", "")
476 if not ext:
477 ext = defaultExt
478 fpath = fpath.with_suffix(".{0}".format(defaultExt.lower()))
479
480 pix = self.mainWidget.grab()
481 self.__updateChildren(self.lastStyle)
482 if not pix.save(str(fpath), str(ext)):
483 EricMessageBox.critical(
484 self,
485 self.tr("Save Image"),
486 self.tr(
487 """<p>The file <b>{0}</b> could not be saved.</p>""")
488 .format(fpath))
489
490 def __copyImageToClipboard(self):
491 """
492 Private slot to handle the Copy Image menu action.
493 """
494 if self.mainWidget is None:
495 EricMessageBox.critical(
496 self,
497 self.tr("Save Image"),
498 self.tr("""There is no UI file loaded."""))
499 return
500
501 cb = QApplication.clipboard()
502 cb.setPixmap(self.mainWidget.grab())
503 self.__updateChildren(self.lastStyle)
504
505 def __printImage(self):
506 """
507 Private slot to handle the Print Image menu action.
508 """
509 if self.mainWidget is None:
510 EricMessageBox.critical(
511 self,
512 self.tr("Print Image"),
513 self.tr("""There is no UI file loaded."""))
514 return
515
516 settings = Preferences.getSettings()
517 printer = QPrinter(QPrinter.PrinterMode.HighResolution)
518 printer.setFullPage(True)
519
520 printerName = Preferences.getPrinter("UIPreviewer/printername")
521 if printerName:
522 printer.setPrinterName(printerName)
523 printer.setPageSize(
524 QPrinter.PageSize(int(settings.value("UIPreviewer/pagesize"))))
525 printer.setPageOrder(
526 QPrinter.PageOrder(int(settings.value("UIPreviewer/pageorder"))))
527 printer.setOrientation(QPrinter.Orientation(
528 int(settings.value("UIPreviewer/orientation"))))
529 printer.setColorMode(
530 QPrinter.ColorMode(int(settings.value("UIPreviewer/colormode"))))
531
532 printDialog = QPrintDialog(printer, self)
533 if printDialog.exec() == QDialog.DialogCode.Accepted:
534 self.statusBar().showMessage(self.tr("Printing the image..."))
535 self.__print(printer)
536
537 settings.setValue("UIPreviewer/printername", printer.printerName())
538 settings.setValue("UIPreviewer/pagesize", printer.pageSize())
539 settings.setValue("UIPreviewer/pageorder", printer.pageOrder())
540 settings.setValue("UIPreviewer/orientation", printer.orientation())
541 settings.setValue("UIPreviewer/colormode", printer.colorMode())
542
543 self.statusBar().showMessage(
544 self.tr("Image sent to printer..."), 2000)
545
546 def __printPreviewImage(self):
547 """
548 Private slot to handle the Print Preview menu action.
549 """
550 from PyQt6.QtPrintSupport import QPrintPreviewDialog
551
552 if self.mainWidget is None:
553 EricMessageBox.critical(
554 self,
555 self.tr("Print Preview"),
556 self.tr("""There is no UI file loaded."""))
557 return
558
559 settings = Preferences.getSettings()
560 printer = QPrinter(QPrinter.PrinterMode.HighResolution)
561 printer.setFullPage(True)
562
563 printerName = Preferences.getPrinter("UIPreviewer/printername")
564 if printerName:
565 printer.setPrinterName(printerName)
566 printer.setPageSize(
567 QPrinter.PageSize(int(settings.value("UIPreviewer/pagesize"))))
568 printer.setPageOrder(
569 QPrinter.PageOrder(int(settings.value("UIPreviewer/pageorder"))))
570 printer.setOrientation(QPrinter.Orientation(
571 int(settings.value("UIPreviewer/orientation"))))
572 printer.setColorMode(
573 QPrinter.ColorMode(int(settings.value("UIPreviewer/colormode"))))
574
575 preview = QPrintPreviewDialog(printer, self)
576 preview.paintRequested.connect(self.__print)
577 preview.exec()
578
579 def __print(self, printer):
580 """
581 Private slot to the actual printing.
582
583 @param printer reference to the printer object (QPrinter)
584 """
585 p = QPainter(printer)
586 marginX = (
587 printer.pageLayout().paintRectPixels(printer.resolution()).x() -
588 printer.pageLayout().fullRectPixels(printer.resolution()).x()
589 ) // 2
590 marginY = (
591 printer.pageLayout().paintRectPixels(printer.resolution()).y() -
592 printer.pageLayout().fullRectPixels(printer.resolution()).y()
593 ) // 2
594
595 # double the margin on bottom of page
596 if printer.orientation() == QPrinter.Orientation.Portrait:
597 width = printer.width() - marginX * 2
598 height = printer.height() - marginY * 3
599 else:
600 marginX *= 2
601 width = printer.width() - marginX * 2
602 height = printer.height() - marginY * 2
603 img = self.mainWidget.grab().toImage()
604 self.__updateChildren(self.lastStyle)
605 p.drawImage(
606 marginX, marginY, img.scaled(
607 width, height,
608 Qt.AspectRatioMode.KeepAspectRatio,
609 Qt.TransformationMode.SmoothTransformation
610 )
611 )
612 p.end()

eric ide

mercurial