src/eric7/Tools/UIPreviewer.py

Thu, 07 Jul 2022 11:23:56 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 07 Jul 2022 11:23:56 +0200
branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
eric7/Tools/UIPreviewer.py@506e35e424d5
child 9221
bf71ee032bb4
permissions
-rw-r--r--

Reorganized the project structure to use the source layout in order to support up-to-date build systems with "pyproject.toml".

# -*- 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