src/eric7/Tools/UIPreviewer.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
diff -r 3fc8dfeb6ebe -r b99e7fd55fd3 src/eric7/Tools/UIPreviewer.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Tools/UIPreviewer.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,612 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2004 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the UI Previewer main window.
+"""
+
+import contextlib
+import pathlib
+
+from PyQt6.QtCore import QDir, QEvent, QSize, Qt
+from PyQt6.QtGui import QAction, QKeySequence, QImageWriter, QPainter
+from PyQt6.QtWidgets import (
+    QSizePolicy, QSpacerItem, QWidget, QHBoxLayout, QWhatsThis, QDialog,
+    QScrollArea, QApplication, QStyleFactory, QFrame, QMainWindow,
+    QComboBox, QVBoxLayout, QLabel
+)
+from PyQt6.QtPrintSupport import QPrinter, QPrintDialog
+from PyQt6 import uic
+
+
+from EricWidgets import EricMessageBox, EricFileDialog
+from EricWidgets.EricMainWindow import EricMainWindow
+from EricWidgets.EricApplication import ericApp
+from EricGui.EricOverrideCursor import EricOverrideCursor
+
+import Preferences
+import UI.PixmapCache
+import UI.Config
+
+
+class UIPreviewer(EricMainWindow):
+    """
+    Class implementing the UI Previewer main window.
+    """
+    def __init__(self, filename=None, parent=None, name=None):
+        """
+        Constructor
+        
+        @param filename name of a UI file to load
+        @param parent parent widget of this window (QWidget)
+        @param name name of this window (string)
+        """
+        self.mainWidget = None
+        self.currentFile = QDir.currentPath()
+        
+        super().__init__(parent)
+        if not name:
+            self.setObjectName("UIPreviewer")
+        else:
+            self.setObjectName(name)
+        
+        self.setStyle(Preferences.getUI("Style"),
+                      Preferences.getUI("StyleSheet"))
+        
+        self.resize(QSize(600, 480).expandedTo(self.minimumSizeHint()))
+        self.statusBar()
+        
+        self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
+        self.setWindowTitle(self.tr("UI Previewer"))
+
+        self.cw = QWidget(self)
+        self.cw.setObjectName("centralWidget")
+        
+        self.UIPreviewerLayout = QVBoxLayout(self.cw)
+        self.UIPreviewerLayout.setContentsMargins(6, 6, 6, 6)
+        self.UIPreviewerLayout.setSpacing(6)
+        self.UIPreviewerLayout.setObjectName("UIPreviewerLayout")
+
+        self.styleLayout = QHBoxLayout()
+        self.styleLayout.setContentsMargins(0, 0, 0, 0)
+        self.styleLayout.setSpacing(6)
+        self.styleLayout.setObjectName("styleLayout")
+
+        self.styleLabel = QLabel(self.tr("Select GUI Theme"), self.cw)
+        self.styleLabel.setObjectName("styleLabel")
+        self.styleLayout.addWidget(self.styleLabel)
+
+        self.styleCombo = QComboBox(self.cw)
+        self.styleCombo.setObjectName("styleCombo")
+        self.styleCombo.setEditable(False)
+        self.styleCombo.setToolTip(self.tr("Select the GUI Theme"))
+        self.styleLayout.addWidget(self.styleCombo)
+        self.styleCombo.addItems(sorted(QStyleFactory().keys()))
+        currentStyle = Preferences.getSettings().value('UIPreviewer/style')
+        if currentStyle is not None:
+            self.styleCombo.setCurrentIndex(int(currentStyle))
+        
+        styleSpacer = QSpacerItem(
+            40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+        self.styleLayout.addItem(styleSpacer)
+        self.UIPreviewerLayout.addLayout(self.styleLayout)
+
+        self.previewSV = QScrollArea(self.cw)
+        self.previewSV.setObjectName("preview")
+        self.previewSV.setFrameShape(QFrame.Shape.NoFrame)
+        self.previewSV.setFrameShadow(QFrame.Shadow.Plain)
+        self.previewSV.setSizePolicy(
+            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+        self.UIPreviewerLayout.addWidget(self.previewSV)
+
+        self.setCentralWidget(self.cw)
+        
+        self.styleCombo.activated[int].connect(self.__guiStyleSelected)
+        
+        self.__initActions()
+        self.__initMenus()
+        self.__initToolbars()
+        
+        self.__updateActions()
+        
+        # defere loading of a UI file until we are shown
+        self.fileToLoad = filename
+        
+    def show(self):
+        """
+        Public slot to show this dialog.
+        
+        This overloaded slot loads a UI file to be previewed after
+        the main window has been shown. This way, previewing a dialog
+        doesn't interfere with showing the main window.
+        """
+        super().show()
+        if self.fileToLoad is not None:
+            fn, self.fileToLoad = (self.fileToLoad, None)
+            self.__loadFile(fn)
+            
+    def __initActions(self):
+        """
+        Private method to define the user interface actions.
+        """
+        self.openAct = QAction(
+            UI.PixmapCache.getIcon("openUI"),
+            self.tr('&Open File'), self)
+        self.openAct.setShortcut(
+            QKeySequence(self.tr("Ctrl+O", "File|Open")))
+        self.openAct.setStatusTip(self.tr('Open a UI file for display'))
+        self.openAct.setWhatsThis(self.tr(
+            """<b>Open File</b>"""
+            """<p>This opens a new UI file for display.</p>"""
+        ))
+        self.openAct.triggered.connect(self.__openFile)
+        
+        self.printAct = QAction(
+            UI.PixmapCache.getIcon("print"),
+            self.tr('&Print'), self)
+        self.printAct.setShortcut(
+            QKeySequence(self.tr("Ctrl+P", "File|Print")))
+        self.printAct.setStatusTip(self.tr('Print a screen capture'))
+        self.printAct.setWhatsThis(self.tr(
+            """<b>Print</b>"""
+            """<p>Print a screen capture.</p>"""
+        ))
+        self.printAct.triggered.connect(self.__printImage)
+        
+        self.printPreviewAct = QAction(
+            UI.PixmapCache.getIcon("printPreview"),
+            self.tr('Print Preview'), self)
+        self.printPreviewAct.setStatusTip(self.tr(
+            'Print preview a screen capture'))
+        self.printPreviewAct.setWhatsThis(self.tr(
+            """<b>Print Preview</b>"""
+            """<p>Print preview a screen capture.</p>"""
+        ))
+        self.printPreviewAct.triggered.connect(self.__printPreviewImage)
+        
+        self.imageAct = QAction(
+            UI.PixmapCache.getIcon("screenCapture"),
+            self.tr('&Screen Capture'), self)
+        self.imageAct.setShortcut(
+            QKeySequence(self.tr("Ctrl+S", "File|Screen Capture")))
+        self.imageAct.setStatusTip(self.tr(
+            'Save a screen capture to an image file'))
+        self.imageAct.setWhatsThis(self.tr(
+            """<b>Screen Capture</b>"""
+            """<p>Save a screen capture to an image file.</p>"""
+        ))
+        self.imageAct.triggered.connect(self.__saveImage)
+        
+        self.exitAct = QAction(
+            UI.PixmapCache.getIcon("exit"), self.tr('&Quit'), self)
+        self.exitAct.setShortcut(
+            QKeySequence(self.tr("Ctrl+Q", "File|Quit")))
+        self.exitAct.setStatusTip(self.tr('Quit the application'))
+        self.exitAct.setWhatsThis(self.tr(
+            """<b>Quit</b>"""
+            """<p>Quit the application.</p>"""
+        ))
+        self.exitAct.triggered.connect(ericApp().closeAllWindows)
+        
+        self.copyAct = QAction(
+            UI.PixmapCache.getIcon("editCopy"), self.tr('&Copy'), self)
+        self.copyAct.setShortcut(
+            QKeySequence(self.tr("Ctrl+C", "Edit|Copy")))
+        self.copyAct.setStatusTip(
+            self.tr('Copy screen capture to clipboard'))
+        self.copyAct.setWhatsThis(self.tr(
+            """<b>Copy</b>"""
+            """<p>Copy screen capture to clipboard.</p>"""
+        ))
+        self.copyAct.triggered.connect(self.__copyImageToClipboard)
+        
+        self.whatsThisAct = QAction(
+            UI.PixmapCache.getIcon("whatsThis"),
+            self.tr('&What\'s This?'), self)
+        self.whatsThisAct.setShortcut(QKeySequence(self.tr("Shift+F1")))
+        self.whatsThisAct.setStatusTip(self.tr('Context sensitive help'))
+        self.whatsThisAct.setWhatsThis(self.tr(
+            """<b>Display context sensitive help</b>"""
+            """<p>In What's This? mode, the mouse cursor shows an arrow"""
+            """ with a question mark, and you can click on the interface"""
+            """ elements to get a short description of what they do and"""
+            """ how to use them. In dialogs, this feature can be accessed"""
+            """ using the context help button in the titlebar.</p>"""
+        ))
+        self.whatsThisAct.triggered.connect(self.__whatsThis)
+
+        self.aboutAct = QAction(self.tr('&About'), self)
+        self.aboutAct.setStatusTip(self.tr(
+            'Display information about this software'))
+        self.aboutAct.setWhatsThis(self.tr(
+            """<b>About</b>"""
+            """<p>Display some information about this software.</p>"""
+        ))
+        self.aboutAct.triggered.connect(self.__about)
+                     
+        self.aboutQtAct = QAction(self.tr('About &Qt'), self)
+        self.aboutQtAct.setStatusTip(
+            self.tr('Display information about the Qt toolkit'))
+        self.aboutQtAct.setWhatsThis(self.tr(
+            """<b>About Qt</b>"""
+            """<p>Display some information about the Qt toolkit.</p>"""
+        ))
+        self.aboutQtAct.triggered.connect(self.__aboutQt)
+
+    def __initMenus(self):
+        """
+        Private method to create the menus.
+        """
+        mb = self.menuBar()
+
+        menu = mb.addMenu(self.tr('&File'))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.openAct)
+        menu.addAction(self.imageAct)
+        menu.addSeparator()
+        menu.addAction(self.printPreviewAct)
+        menu.addAction(self.printAct)
+        menu.addSeparator()
+        menu.addAction(self.exitAct)
+        
+        menu = mb.addMenu(self.tr("&Edit"))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.copyAct)
+        
+        mb.addSeparator()
+        
+        menu = mb.addMenu(self.tr('&Help'))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.aboutAct)
+        menu.addAction(self.aboutQtAct)
+        menu.addSeparator()
+        menu.addAction(self.whatsThisAct)
+
+    def __initToolbars(self):
+        """
+        Private method to create the toolbars.
+        """
+        filetb = self.addToolBar(self.tr("File"))
+        filetb.setIconSize(UI.Config.ToolBarIconSize)
+        filetb.addAction(self.openAct)
+        filetb.addAction(self.imageAct)
+        filetb.addSeparator()
+        filetb.addAction(self.printPreviewAct)
+        filetb.addAction(self.printAct)
+        filetb.addSeparator()
+        filetb.addAction(self.exitAct)
+        
+        edittb = self.addToolBar(self.tr("Edit"))
+        edittb.setIconSize(UI.Config.ToolBarIconSize)
+        edittb.addAction(self.copyAct)
+        
+        helptb = self.addToolBar(self.tr("Help"))
+        helptb.setIconSize(UI.Config.ToolBarIconSize)
+        helptb.addAction(self.whatsThisAct)
+
+    def __whatsThis(self):
+        """
+        Private slot called in to enter Whats This mode.
+        """
+        QWhatsThis.enterWhatsThisMode()
+        
+    def __guiStyleSelected(self, index):
+        """
+        Private slot to handle the selection of a GUI style.
+        
+        @param index index of the selected entry
+        @type int
+        """
+        selectedStyle = self.styleCombo.itemText(index)
+        if self.mainWidget:
+            self.__updateChildren(selectedStyle)
+    
+    def __about(self):
+        """
+        Private slot to show the about information.
+        """
+        EricMessageBox.about(
+            self,
+            self.tr("UI Previewer"),
+            self.tr(
+                """<h3> About UI Previewer </h3>"""
+                """<p>The UI Previewer loads and displays Qt User-Interface"""
+                """ files with various styles, which are selectable via a"""
+                """ selection list.</p>"""
+            )
+        )
+    
+    def __aboutQt(self):
+        """
+        Private slot to show info about Qt.
+        """
+        EricMessageBox.aboutQt(self, self.tr("UI Previewer"))
+
+    def __openFile(self):
+        """
+        Private slot to load a new file.
+        """
+        fn = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Select UI file"),
+            self.currentFile,
+            self.tr("Qt User-Interface Files (*.ui)"))
+        if fn:
+            self.__loadFile(fn)
+        
+    def __loadFile(self, fn):
+        """
+        Private slot to load a ui file.
+        
+        @param fn name of the ui file to be laoded
+        @type str
+        """
+        if self.mainWidget:
+            self.mainWidget.close()
+            self.previewSV.takeWidget()
+            del self.mainWidget
+            self.mainWidget = None
+            
+        # load the file
+        with contextlib.suppress(Exception):
+            self.mainWidget = uic.loadUi(fn)
+        
+        if self.mainWidget:
+            self.currentFile = fn
+            self.__updateChildren(self.styleCombo.currentText())
+            if isinstance(self.mainWidget, (QDialog, QMainWindow)):
+                self.mainWidget.show()
+                self.mainWidget.installEventFilter(self)
+            else:
+                self.previewSV.setWidget(self.mainWidget)
+                self.mainWidget.show()
+        else:
+            EricMessageBox.warning(
+                self,
+                self.tr("Load UI File"),
+                self.tr(
+                    """<p>The file <b>{0}</b> could not be loaded.</p>""")
+                .format(fn))
+        self.__updateActions()
+    
+    def __updateChildren(self, sstyle):
+        """
+        Private slot to change the style of the show UI.
+        
+        @param sstyle name of the selected style (string)
+        """
+        with EricOverrideCursor():
+            qstyle = QStyleFactory.create(sstyle)
+            self.mainWidget.setStyle(qstyle)
+            
+            lst = self.mainWidget.findChildren(QWidget)
+            for obj in lst:
+                with contextlib.suppress(AttributeError):
+                    obj.setStyle(qstyle)
+            del lst
+            
+            self.mainWidget.hide()
+            self.mainWidget.show()
+            
+            self.lastQStyle = qstyle
+            self.lastStyle = sstyle
+            Preferences.getSettings().setValue(
+                'UIPreviewer/style', self.styleCombo.currentIndex())
+    
+    def __updateActions(self):
+        """
+        Private slot to update the actions state.
+        """
+        if self.mainWidget:
+            self.imageAct.setEnabled(True)
+            self.printAct.setEnabled(True)
+            if self.printPreviewAct:
+                self.printPreviewAct.setEnabled(True)
+            self.copyAct.setEnabled(True)
+            self.styleCombo.setEnabled(True)
+        else:
+            self.imageAct.setEnabled(False)
+            self.printAct.setEnabled(False)
+            if self.printPreviewAct:
+                self.printPreviewAct.setEnabled(False)
+            self.copyAct.setEnabled(False)
+            self.styleCombo.setEnabled(False)
+
+    def __handleCloseEvent(self):
+        """
+        Private slot to handle the close event of a viewed QMainWidget.
+        """
+        if self.mainWidget:
+            self.mainWidget.removeEventFilter(self)
+            del self.mainWidget
+            self.mainWidget = None
+        self.__updateActions()
+    
+    def eventFilter(self, obj, ev):
+        """
+        Public method called to filter an event.
+        
+        @param obj object, that generated the event (QObject)
+        @param ev the event, that was generated by object (QEvent)
+        @return flag indicating if event was filtered out
+        """
+        if obj == self.mainWidget:
+            if ev.type() == QEvent.Type.Close:
+                self.__handleCloseEvent()
+            return True
+        else:
+            if isinstance(self.mainWidget, QDialog):
+                return QDialog.eventFilter(self, obj, ev)
+            elif isinstance(self.mainWidget, QMainWindow):
+                return QMainWindow.eventFilter(self, obj, ev)
+            else:
+                return False
+    
+    def __saveImage(self):
+        """
+        Private slot to handle the Save Image menu action.
+        """
+        if self.mainWidget is None:
+            EricMessageBox.critical(
+                self,
+                self.tr("Save Image"),
+                self.tr("""There is no UI file loaded."""))
+            return
+        
+        defaultExt = "PNG"
+        filters = ""
+        formats = QImageWriter.supportedImageFormats()
+        for imageFormat in formats:
+            filters = "{0}*.{1} ".format(
+                filters, bytes(imageFormat).decode().lower())
+        fileFilter = self.tr("Images ({0})").format(filters[:-1])
+        
+        fname = EricFileDialog.getSaveFileName(
+            self,
+            self.tr("Save Image"),
+            "",
+            fileFilter)
+        if not fname:
+            return
+        
+        fpath = pathlib.Path(fname)
+        ext = fpath.suffix.upper().replace(".", "")
+        if not ext:
+            ext = defaultExt
+            fpath = fpath.with_suffix(".{0}".format(defaultExt.lower()))
+        
+        pix = self.mainWidget.grab()
+        self.__updateChildren(self.lastStyle)
+        if not pix.save(str(fpath), str(ext)):
+            EricMessageBox.critical(
+                self,
+                self.tr("Save Image"),
+                self.tr(
+                    """<p>The file <b>{0}</b> could not be saved.</p>""")
+                .format(fpath))
+
+    def __copyImageToClipboard(self):
+        """
+        Private slot to handle the Copy Image menu action.
+        """
+        if self.mainWidget is None:
+            EricMessageBox.critical(
+                self,
+                self.tr("Save Image"),
+                self.tr("""There is no UI file loaded."""))
+            return
+        
+        cb = QApplication.clipboard()
+        cb.setPixmap(self.mainWidget.grab())
+        self.__updateChildren(self.lastStyle)
+    
+    def __printImage(self):
+        """
+        Private slot to handle the Print Image menu action.
+        """
+        if self.mainWidget is None:
+            EricMessageBox.critical(
+                self,
+                self.tr("Print Image"),
+                self.tr("""There is no UI file loaded."""))
+            return
+        
+        settings = Preferences.getSettings()
+        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
+        printer.setFullPage(True)
+        
+        printerName = Preferences.getPrinter("UIPreviewer/printername")
+        if printerName:
+            printer.setPrinterName(printerName)
+        printer.setPageSize(
+            QPrinter.PageSize(int(settings.value("UIPreviewer/pagesize"))))
+        printer.setPageOrder(
+            QPrinter.PageOrder(int(settings.value("UIPreviewer/pageorder"))))
+        printer.setOrientation(QPrinter.Orientation(
+            int(settings.value("UIPreviewer/orientation"))))
+        printer.setColorMode(
+            QPrinter.ColorMode(int(settings.value("UIPreviewer/colormode"))))
+        
+        printDialog = QPrintDialog(printer, self)
+        if printDialog.exec() == QDialog.DialogCode.Accepted:
+            self.statusBar().showMessage(self.tr("Printing the image..."))
+            self.__print(printer)
+            
+            settings.setValue("UIPreviewer/printername", printer.printerName())
+            settings.setValue("UIPreviewer/pagesize", printer.pageSize())
+            settings.setValue("UIPreviewer/pageorder", printer.pageOrder())
+            settings.setValue("UIPreviewer/orientation", printer.orientation())
+            settings.setValue("UIPreviewer/colormode", printer.colorMode())
+        
+        self.statusBar().showMessage(
+            self.tr("Image sent to printer..."), 2000)
+
+    def __printPreviewImage(self):
+        """
+        Private slot to handle the Print Preview menu action.
+        """
+        from PyQt6.QtPrintSupport import QPrintPreviewDialog
+        
+        if self.mainWidget is None:
+            EricMessageBox.critical(
+                self,
+                self.tr("Print Preview"),
+                self.tr("""There is no UI file loaded."""))
+            return
+        
+        settings = Preferences.getSettings()
+        printer = QPrinter(QPrinter.PrinterMode.HighResolution)
+        printer.setFullPage(True)
+        
+        printerName = Preferences.getPrinter("UIPreviewer/printername")
+        if printerName:
+            printer.setPrinterName(printerName)
+        printer.setPageSize(
+            QPrinter.PageSize(int(settings.value("UIPreviewer/pagesize"))))
+        printer.setPageOrder(
+            QPrinter.PageOrder(int(settings.value("UIPreviewer/pageorder"))))
+        printer.setOrientation(QPrinter.Orientation(
+            int(settings.value("UIPreviewer/orientation"))))
+        printer.setColorMode(
+            QPrinter.ColorMode(int(settings.value("UIPreviewer/colormode"))))
+        
+        preview = QPrintPreviewDialog(printer, self)
+        preview.paintRequested.connect(self.__print)
+        preview.exec()
+        
+    def __print(self, printer):
+        """
+        Private slot to the actual printing.
+        
+        @param printer reference to the printer object (QPrinter)
+        """
+        p = QPainter(printer)
+        marginX = (
+            printer.pageLayout().paintRectPixels(printer.resolution()).x() -
+            printer.pageLayout().fullRectPixels(printer.resolution()).x()
+        ) // 2
+        marginY = (
+            printer.pageLayout().paintRectPixels(printer.resolution()).y() -
+            printer.pageLayout().fullRectPixels(printer.resolution()).y()
+        ) // 2
+
+        # double the margin on bottom of page
+        if printer.orientation() == QPrinter.Orientation.Portrait:
+            width = printer.width() - marginX * 2
+            height = printer.height() - marginY * 3
+        else:
+            marginX *= 2
+            width = printer.width() - marginX * 2
+            height = printer.height() - marginY * 2
+        img = self.mainWidget.grab().toImage()
+        self.__updateChildren(self.lastStyle)
+        p.drawImage(
+            marginX, marginY, img.scaled(
+                width, height,
+                Qt.AspectRatioMode.KeepAspectRatio,
+                Qt.TransformationMode.SmoothTransformation
+            )
+        )
+        p.end()

eric ide

mercurial