eric6/Tools/UIPreviewer.py

Sat, 27 Feb 2021 12:08:23 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 27 Feb 2021 12:08:23 +0100
changeset 8138
169e65a6787c
parent 7923
91e843545d9a
child 8143
2c730d5fd177
permissions
-rw-r--r--

Shell: added functionality to show a prompt when the main client process has exited (e.g. a script ended).

# -*- coding: utf-8 -*-

# Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the UI Previewer main window.
"""

from PyQt5.QtCore import QDir, QFileInfo, QEvent, QSize, Qt
from PyQt5.QtGui import QKeySequence, QImageWriter, QPainter
from PyQt5.QtWidgets import (
    QSizePolicy, QSpacerItem, QWidget, QHBoxLayout, QWhatsThis, QDialog,
    QScrollArea, QApplication, QStyleFactory, QFrame, QMainWindow,
    QComboBox, QVBoxLayout, QAction, QLabel
)
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
from PyQt5 import uic


from E5Gui import E5MessageBox, E5FileDialog
from E5Gui.E5MainWindow import E5MainWindow
from E5Gui.E5Application import e5App
from E5Gui.E5OverrideCursor import E5OverrideCursor

import Preferences
import UI.PixmapCache
import UI.Config


class UIPreviewer(E5MainWindow):
    """
    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(UIPreviewer, self).__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.Prefs.settings.value('UIPreviewer/style')
        if currentStyle is not None:
            self.styleCombo.setCurrentIndex(int(currentStyle))
        
        styleSpacer = QSpacerItem(
            40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.styleLayout.addItem(styleSpacer)
        self.UIPreviewerLayout.addLayout(self.styleLayout)

        self.previewSV = QScrollArea(self.cw)
        self.previewSV.setObjectName("preview")
        self.previewSV.setFrameShape(QFrame.NoFrame)
        self.previewSV.setFrameShadow(QFrame.Plain)
        self.previewSV.setSizePolicy(
            QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.UIPreviewerLayout.addWidget(self.previewSV)

        self.setCentralWidget(self.cw)
        
        self.styleCombo.activated[str].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(UIPreviewer, self).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(e5App().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, selectedStyle):
        """
        Private slot to handle the selection of a GUI style.
        
        @param selectedStyle name of the selected style (string)
        """
        if self.mainWidget:
            self.__updateChildren(selectedStyle)
    
    def __about(self):
        """
        Private slot to show the about information.
        """
        E5MessageBox.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.
        """
        E5MessageBox.aboutQt(self, self.tr("UI Previewer"))

    def __openFile(self):
        """
        Private slot to load a new file.
        """
        fn = E5FileDialog.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 (string)
        """
        if self.mainWidget:
            self.mainWidget.close()
            self.previewSV.takeWidget()
            del self.mainWidget
            self.mainWidget = None
            
        # load the file
        try:
            self.mainWidget = uic.loadUi(fn)
        except Exception:       # secok
            pass
        
        if self.mainWidget:
            self.currentFile = fn
            self.__updateChildren(self.styleCombo.currentText())
            if (
                isinstance(self.mainWidget, QDialog) or
                isinstance(self.mainWidget, QMainWindow)
            ):
                self.mainWidget.show()
                self.mainWidget.installEventFilter(self)
            else:
                self.previewSV.setWidget(self.mainWidget)
                self.mainWidget.show()
        else:
            E5MessageBox.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 E5OverrideCursor():
            qstyle = QStyleFactory.create(sstyle)
            self.mainWidget.setStyle(qstyle)
            
            lst = self.mainWidget.findChildren(QWidget)
            for obj in lst:
                try:
                    obj.setStyle(qstyle)
                except AttributeError:
                    pass
            del lst
            
            self.mainWidget.hide()
            self.mainWidget.show()
            
            self.lastQStyle = qstyle
            self.lastStyle = sstyle
            Preferences.Prefs.settings.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.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:
            E5MessageBox.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 = E5FileDialog.getSaveFileName(
            self,
            self.tr("Save Image"),
            "",
            fileFilter)
        if not fname:
            return
            
        ext = QFileInfo(fname).suffix().upper()
        if not ext:
            ext = defaultExt
            fname.append(".{0}".format(defaultExt.lower()))
        
        pix = self.mainWidget.grab()
        self.__updateChildren(self.lastStyle)
        if not pix.save(fname, str(ext)):
            E5MessageBox.critical(
                self,
                self.tr("Save Image"),
                self.tr(
                    """<p>The file <b>{0}</b> could not be saved.</p>""")
                .format(fname))

    def __copyImageToClipboard(self):
        """
        Private slot to handle the Copy Image menu action.
        """
        if self.mainWidget is None:
            E5MessageBox.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:
            E5MessageBox.critical(
                self,
                self.tr("Print Image"),
                self.tr("""There is no UI file loaded."""))
            return
        
        settings = Preferences.Prefs.settings
        printer = QPrinter(QPrinter.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.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 PyQt5.QtPrintSupport import QPrintPreviewDialog
        
        if self.mainWidget is None:
            E5MessageBox.critical(
                self,
                self.tr("Print Preview"),
                self.tr("""There is no UI file loaded."""))
            return
        
        settings = Preferences.Prefs.settings
        printer = QPrinter(QPrinter.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.pageRect().x() - printer.paperRect().x()) // 2
        marginY = (printer.pageRect().y() - printer.paperRect().y()) // 2

        # double the margin on bottom of page
        if printer.orientation() == QPrinter.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.KeepAspectRatio, Qt.SmoothTransformation))
        p.end()

eric ide

mercurial