eric7/Preferences/ConfigurationDialog.py

Tue, 08 Feb 2022 16:21:09 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 08 Feb 2022 16:21:09 +0100
branch
eric7
changeset 8943
23f9c7b9e18e
parent 8888
e374ffbc1484
permissions
-rw-r--r--

Implemented some performance improvements.

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

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

"""
Module implementing a dialog for the configuration of eric.
"""

import contextlib
import enum
import os
import time
import types

from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QMetaObject, QRect
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import (
    QSizePolicy, QSpacerItem, QWidget, QTreeWidget, QStackedWidget, QDialog,
    QSplitter, QScrollArea, QApplication, QDialogButtonBox, QFrame,
    QVBoxLayout, QTreeWidgetItem, QLabel, QAbstractScrollArea, QLineEdit
)

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox
from EricWidgets.EricMainWindow import EricMainWindow

from Globals import isMacPlatform, getWebBrowserSupport

import Preferences

import UI.PixmapCache

from eric7config import getConfig


class ConfigurationPageItem(QTreeWidgetItem):
    """
    Class implementing a QTreeWidgetItem holding the configuration page data.
    """
    def __init__(self, parent, text, pageName, iconFile):
        """
        Constructor
        
        @param parent parent widget of the item (QTreeWidget or
            QTreeWidgetItem)
        @param text text to be displayed (string)
        @param pageName name of the configuration page (string)
        @param iconFile file name of the icon to be shown (string)
        """
        super().__init__(parent, [text])
        self.setIcon(0, UI.PixmapCache.getIcon(iconFile))
        
        self.__pageName = pageName
        
    def getPageName(self):
        """
        Public method to get the name of the associated configuration page.
        
        @return name of the configuration page (string)
        """
        return self.__pageName


class ConfigurationMode(enum.Enum):
    """
    Class defining the various modes of the configuration widget.
    """
    DEFAULTMODE = 0
    TRAYSTARTERMODE = 1
    HEXEDITORMODE = 2
    WEBBROWSERMODE = 3
    EDITORMODE = 4


class ConfigurationWidget(QWidget):
    """
    Class implementing a dialog for the configuration of eric.
    
    @signal preferencesChanged() emitted after settings have been changed
    @signal masterPasswordChanged(str, str) emitted after the master
        password has been changed with the old and the new password
    @signal accepted() emitted to indicate acceptance of the changes
    @signal rejected() emitted to indicate rejection of the changes
    """
    preferencesChanged = pyqtSignal()
    masterPasswordChanged = pyqtSignal(str, str)
    accepted = pyqtSignal()
    rejected = pyqtSignal()
    
    def __init__(self, parent=None, fromEric=True,
                 displayMode=ConfigurationMode.DEFAULTMODE,
                 expandedEntries=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        @param fromEric flag indicating a dialog generation from within the
            eric IDE
        @type bool
        @param displayMode mode of the configuration dialog
        @type ConfigurationMode
        @param expandedEntries list of entries to be shown expanded
        @type list of str
        """
        super().__init__(parent)
        
        self.fromEric = fromEric
        self.displayMode = displayMode
        self.__webEngine = getWebBrowserSupport() == "QtWebEngine"
        expandedEntries = [] if expandedEntries is None else expandedEntries[:]
        
        self.__setupUi()
        
        self.itmDict = {}
        
        if not fromEric:
            from PluginManager.PluginManager import PluginManager
            try:
                self.pluginManager = ericApp().getObject("PluginManager")
            except KeyError:
                self.pluginManager = PluginManager(self)
                ericApp().registerObject("PluginManager", self.pluginManager)
            
            from VirtualEnv.VirtualenvManager import VirtualenvManager
            try:
                self.virtualenvManager = ericApp().getObject(
                    "VirtualEnvManager")
            except KeyError:
                self.virtualenvManager = VirtualenvManager(self)
                ericApp().registerObject("VirtualEnvManager",
                                         self.virtualenvManager)
        
        if displayMode == ConfigurationMode.DEFAULTMODE:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "applicationPage":
                [self.tr("Application"), "preferences-application",
                 "ApplicationPage", None, None],
                "condaPage":
                [self.tr("Conda"), "miniconda",
                 "CondaPage", None, None],
                "cooperationPage":
                [self.tr("Cooperation"), "preferences-cooperation",
                 "CooperationPage", None, None],
                "corbaPage":
                [self.tr("CORBA"), "preferences-orbit",
                 "CorbaPage", None, None],
                "diffPage":
                [self.tr("Diff"), "diffFiles",
                 "DiffColoursPage", None, None],
                "emailPage":
                [self.tr("Email"), "preferences-mail_generic",
                 "EmailPage", None, None],
                "graphicsPage":
                [self.tr("Graphics"), "preferences-graphics",
                 "GraphicsPage", None, None],
                "hexEditorPage":
                [self.tr("Hex Editor"), "hexEditor",
                 "HexEditorPage", None, None],
                "iconsPage":
                [self.tr("Icons"), "preferences-icons",
                 "IconsPage", None, None],
                "ircPage":
                [self.tr("IRC"), "irc",
                 "IrcPage", None, None],
                "logViewerPage":
                [self.tr("Log-Viewer"), "preferences-logviewer",
                 "LogViewerPage", None, None],
                "microPythonPage":
                [self.tr("MicroPython"), "micropython",
                 "MicroPythonPage", None, None],
                "mimeTypesPage":
                [self.tr("Mimetypes"), "preferences-mimetypes",
                 "MimeTypesPage", None, None],
                "networkPage":
                [self.tr("Network"), "preferences-network",
                 "NetworkPage", None, None],
                "notificationsPage":
                [self.tr("Notifications"),
                 "preferences-notifications",
                 "NotificationsPage", None, None],
                "pipPage":
                [self.tr("Python Package Management"), "pypi",
                 "PipPage", None, None],
                "pluginManagerPage":
                [self.tr("Plugin Manager"),
                 "preferences-pluginmanager",
                 "PluginManagerPage", None, None],
                "printerPage":
                [self.tr("Printer"), "preferences-printer",
                 "PrinterPage", None, None],
                "protobufPage":
                [self.tr("Protobuf"), "protobuf",
                 "ProtobufPage", None, None],
                "pythonPage":
                [self.tr("Python"), "preferences-python",
                 "PythonPage", None, None],
                "qtPage":
                [self.tr("Qt"), "preferences-qtlogo",
                 "QtPage", None, None],
                "securityPage":
                [self.tr("Security"), "preferences-security",
                 "SecurityPage", None, None],
                "shellPage":
                [self.tr("Shell"), "preferences-shell",
                 "ShellPage", None, None],
                "tasksPage":
                [self.tr("Tasks"), "task",
                 "TasksPage", None, None],
                "templatesPage":
                [self.tr("Templates"), "preferences-template",
                 "TemplatesPage", None, None],
                "trayStarterPage":
                [self.tr("Tray Starter"), "erict",
                 "TrayStarterPage", None, None],
                "vcsPage":
                [self.tr("Version Control Systems"),
                 "preferences-vcs",
                 "VcsPage", None, None],
                
                "0debuggerPage":
                [self.tr("Debugger"), "preferences-debugger",
                 None, None, None],
                "debuggerGeneralPage":
                [self.tr("General"), "preferences-debugger",
                 "DebuggerGeneralPage", "0debuggerPage", None],
                "debuggerPython3Page":
                [self.tr("Python3"), "preferences-pyDebugger",
                 "DebuggerPython3Page", "0debuggerPage", None],
                
                "0editorPage":
                [self.tr("Editor"), "preferences-editor",
                 None, None, None],
                "editorAPIsPage":
                [self.tr("APIs"), "preferences-api",
                 "EditorAPIsPage", "0editorPage", None],
                "editorDocViewerPage":
                [self.tr("Documentation Viewer"), "codeDocuViewer",
                 "EditorDocViewerPage", "0editorPage", None],
                "editorGeneralPage":
                [self.tr("General"), "preferences-general",
                 "EditorGeneralPage", "0editorPage", None],
                "editorFilePage":
                [self.tr("Filehandling"),
                 "preferences-filehandling",
                 "EditorFilePage", "0editorPage", None],
                "editorSearchPage":
                [self.tr("Searching"), "preferences-search",
                 "EditorSearchPage", "0editorPage", None],
                "editorSpellCheckingPage":
                [self.tr("Spell checking"),
                 "preferences-spellchecking",
                 "EditorSpellCheckingPage", "0editorPage", None],
                "editorStylesPage":
                [self.tr("Style"), "preferences-styles",
                 "EditorStylesPage", "0editorPage", None],
                "editorSyntaxPage":
                [self.tr("Code Checkers"), "preferences-debugger",
                 "EditorSyntaxPage", "0editorPage", None],
                "editorTypingPage":
                [self.tr("Typing"), "preferences-typing",
                 "EditorTypingPage", "0editorPage", None],
                "editorExportersPage":
                [self.tr("Exporters"), "preferences-exporters",
                 "EditorExportersPage", "0editorPage", None],
                
                "1editorAutocompletionPage":
                [self.tr("Autocompletion"),
                 "preferences-autocompletion",
                 "EditorAutocompletionPage", "0editorPage", None],
                "editorAutocompletionQScintillaPage":
                [self.tr("QScintilla"), "qscintilla",
                 "EditorAutocompletionQScintillaPage",
                 "1editorAutocompletionPage", None],
                "editorAutocompletionJediPage":
                [self.tr("Jedi"), "jedi",
                 "EditorAutoCompletionJediPage",
                 "1editorAutocompletionPage", None],
                
                "1editorCalltipsPage":
                [self.tr("Calltips"), "preferences-calltips",
                 "EditorCalltipsPage", "0editorPage", None],
                "editorCalltipsQScintillaPage":
                [self.tr("QScintilla"), "qscintilla",
                 "EditorCalltipsQScintillaPage", "1editorCalltipsPage", None],
                "editorCalltipsJediPage":
                [self.tr("Jedi"), "jedi",
                 "EditorCallTipsJediPage", "1editorCalltipsPage", None],
                
                "1editorLexerPage":
                [self.tr("Highlighters"),
                 "preferences-highlighting-styles",
                 None, "0editorPage", None],
                "editorHighlightersPage":
                [self.tr("Filetype Associations"),
                 "preferences-highlighter-association",
                 "EditorHighlightersPage", "1editorLexerPage", None],
                "editorHighlightingStylesPage":
                [self.tr("Styles"),
                 "preferences-highlighting-styles",
                 "EditorHighlightingStylesPage", "1editorLexerPage", None],
                "editorKeywordsPage":
                [self.tr("Keywords"), "preferences-keywords",
                 "EditorKeywordsPage", "1editorLexerPage", None],
                "editorPropertiesPage":
                [self.tr("Properties"), "preferences-properties",
                 "EditorPropertiesPage", "1editorLexerPage", None],
                
                "1editorMouseClickHandlers":
                [self.tr("Mouse Click Handlers"),
                 "preferences-mouse-click-handler",
                 "EditorMouseClickHandlerPage", "0editorPage", None],
                "editorMouseClickHandlerJediPage":
                [self.tr("Jedi"), "jedi",
                 "EditorMouseClickHandlerJediPage",
                 "1editorMouseClickHandlers", None],
                
                "0helpPage":
                [self.tr("Help"), "preferences-help",
                 None, None, None],
                "helpDocumentationPage":
                [self.tr("Help Documentation"),
                 "preferences-helpdocumentation",
                 "HelpDocumentationPage", "0helpPage", None],
                "helpViewersPage":
                [self.tr("Help Viewers"),
                 "preferences-helpviewers",
                 "HelpViewersPage", "0helpPage", None],
                
                "0projectPage":
                [self.tr("Project"), "preferences-project",
                 None, None, None],
                "projectBrowserPage":
                [self.tr("Project Viewer"), "preferences-project",
                 "ProjectBrowserPage", "0projectPage", None],
                "projectPage":
                [self.tr("Project"), "preferences-project",
                 "ProjectPage", "0projectPage", None],
                "multiProjectPage":
                [self.tr("Multiproject"),
                 "preferences-multiproject",
                 "MultiProjectPage", "0projectPage", None],
                
                "0interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 None, None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 "InterfacePage", "0interfacePage", None],
                "viewmanagerPage":
                [self.tr("Viewmanager"), "preferences-viewmanager",
                 "ViewmanagerPage", "0interfacePage", None],
            }
            if self.__webEngine:
                self.configItems.update({
                    "0webBrowserPage":
                    [self.tr("Web Browser"), "ericWeb",
                     None, None, None],
                    "webBrowserAppearancePage":
                    [self.tr("Appearance"), "preferences-styles",
                     "WebBrowserAppearancePage", "0webBrowserPage", None],
                    "webBrowserPage":
                    [self.tr("eric Web Browser"), "ericWeb",
                     "WebBrowserPage", "0webBrowserPage", None],
                    "webBrowserVirusTotalPage":
                    [self.tr("VirusTotal Interface"), "virustotal",
                     "WebBrowserVirusTotalPage", "0webBrowserPage", None],
                    "webBrowserSpellCheckingPage":
                    [self.tr("Spell checking"),
                     "preferences-spellchecking",
                     "WebBrowserSpellCheckingPage", "0webBrowserPage",
                     None],
                })
            
            self.configItems.update(
                ericApp().getObject("PluginManager").getPluginConfigData())
        
        elif displayMode == ConfigurationMode.EDITORMODE:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "iconsPage":
                [self.tr("Icons"), "preferences-icons",
                 "IconsPage", None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 "InterfaceLightPage", None, None],
                "printerPage":
                [self.tr("Printer"), "preferences-printer",
                 "PrinterPage", None, None],
                
                "0editorPage":
                [self.tr("Editor"), "preferences-editor",
                 None, None, None],
                "editorGeneralPage":
                [self.tr("General"), "preferences-general",
                 "EditorGeneralPage", "0editorPage", None],
                "editorFilePage":
                [self.tr("Filehandling"),
                 "preferences-filehandling",
                 "EditorFilePage", "0editorPage", None],
                "editorSearchPage":
                [self.tr("Searching"), "preferences-search",
                 "EditorSearchPage", "0editorPage", None],
                "editorSpellCheckingPage":
                [self.tr("Spell checking"),
                 "preferences-spellchecking",
                 "EditorSpellCheckingPage", "0editorPage", None],
                "editorStylesPage":
                [self.tr("Style"), "preferences-styles",
                 "EditorStylesPage", "0editorPage", None],
                "editorTypingPage":
                [self.tr("Typing"), "preferences-typing",
                 "EditorTypingPage", "0editorPage", None],
                
                "1editorLexerPage":
                [self.tr("Highlighters"),
                 "preferences-highlighting-styles",
                 None, "0editorPage", None],
                "editorHighlightersPage":
                [self.tr("Filetype Associations"),
                 "preferences-highlighter-association",
                 "EditorHighlightersPage", "1editorLexerPage", None],
                "editorHighlightingStylesPage":
                [self.tr("Styles"),
                 "preferences-highlighting-styles",
                 "EditorHighlightingStylesPage", "1editorLexerPage", None],
                "editorKeywordsPage":
                [self.tr("Keywords"), "preferences-keywords",
                 "EditorKeywordsPage", "1editorLexerPage", None],
                "editorPropertiesPage":
                [self.tr("Properties"), "preferences-properties",
                 "EditorPropertiesPage", "1editorLexerPage", None],
            }
        
        elif displayMode == ConfigurationMode.WEBBROWSERMODE:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "iconsPage":
                [self.tr("Icons"), "preferences-icons",
                 "IconsPage", None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 "InterfaceLightPage", None, None],
                "networkPage":
                [self.tr("Network"), "preferences-network",
                 "NetworkPage", None, None],
                "printerPage":
                [self.tr("Printer"), "preferences-printer",
                 "PrinterPage", None, None],
                "securityPage":
                [self.tr("Security"), "preferences-security",
                 "SecurityPage", None, None],
                
                "helpDocumentationPage":
                [self.tr("Help Documentation"),
                 "preferences-helpdocumentation",
                 "HelpDocumentationPage", None, None],
                
                "webBrowserAppearancePage":
                [self.tr("Appearance"), "preferences-styles",
                 "WebBrowserAppearancePage", None, None],
                "webBrowserPage":
                [self.tr("eric Web Browser"), "ericWeb",
                 "WebBrowserPage", None, None],
                
                "webBrowserVirusTotalPage":
                [self.tr("VirusTotal Interface"), "virustotal",
                 "WebBrowserVirusTotalPage", None, None],
                
                "webBrowserSpellCheckingPage":
                [self.tr("Spell checking"),
                 "preferences-spellchecking",
                 "WebBrowserSpellCheckingPage", None, None],
            }
        
        elif displayMode == ConfigurationMode.TRAYSTARTERMODE:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "trayStarterPage":
                [self.tr("Tray Starter"), "erict",
                 "TrayStarterPage", None, None],
            }
        
        elif displayMode == ConfigurationMode.HEXEDITORMODE:
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "iconsPage":
                [self.tr("Icons"), "preferences-icons",
                 "IconsPage", None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 "InterfaceLightPage", None, None],
                "hexEditorPage":
                [self.tr("Hex Editor"), "hexEditor",
                 "HexEditorPage", None, None],
            }
        
        else:
            # display mode for generic use
            self.configItems = {
                # key : [display string, pixmap name, dialog module name or
                #        page creation function, parent key,
                #        reference to configuration page (must always be last)]
                # The dialog module must have the module function 'create' to
                # create the configuration page. This must have the method
                # 'save' to save the settings.
                "iconsPage":
                [self.tr("Icons"), "preferences-icons",
                 "IconsPage", None, None],
                "interfacePage":
                [self.tr("Interface"), "preferences-interface",
                 "InterfaceLightPage", None, None],
            }
        
        # generate the list entries
        self.__expandedEntries = []
        for key in sorted(self.configItems.keys()):
            pageData = self.configItems[key]
            if pageData[3]:
                if pageData[3] in self.itmDict:
                    pitm = self.itmDict[pageData[3]]  # get the parent item
                else:
                    continue
            else:
                pitm = self.configList
            self.itmDict[key] = ConfigurationPageItem(pitm, pageData[0], key,
                                                      pageData[1])
            self.itmDict[key].setData(0, Qt.ItemDataRole.UserRole, key)
            if (
                not self.fromEric or
                displayMode != ConfigurationMode.DEFAULTMODE or
                key in expandedEntries
            ):
                self.itmDict[key].setExpanded(True)
        self.configList.sortByColumn(0, Qt.SortOrder.AscendingOrder)
        
        # set the initial size of the splitter
        self.configSplitter.setSizes([200, 600])
        self.configSplitter.splitterMoved.connect(self.__resizeConfigStack)
        
        self.configList.itemActivated.connect(self.__showConfigurationPage)
        self.configList.itemClicked.connect(self.__showConfigurationPage)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.rejected)
        
        if displayMode in [ConfigurationMode.TRAYSTARTERMODE,
                           ConfigurationMode.HEXEDITORMODE,
                           ConfigurationMode.WEBBROWSERMODE]:
            self.configListSearch.hide()
        
        if displayMode not in [ConfigurationMode.TRAYSTARTERMODE,
                               ConfigurationMode.HEXEDITORMODE]:
            self.__initLexers()
        
    def accept(self):
        """
        Public slot to accept the buttonBox accept signal.
        """
        if not isMacPlatform():
            wdg = self.focusWidget()
            if wdg == self.configList:
                return
        
        self.accepted.emit()
        
    def __setupUi(self):
        """
        Private method to perform the general setup of the configuration
        widget.
        """
        self.setObjectName("ConfigurationDialog")
        self.resize(900, 750)
        self.verticalLayout_2 = QVBoxLayout(self)
        self.verticalLayout_2.setSpacing(6)
        self.verticalLayout_2.setContentsMargins(6, 6, 6, 6)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        
        self.configSplitter = QSplitter(self)
        self.configSplitter.setOrientation(Qt.Orientation.Horizontal)
        self.configSplitter.setObjectName("configSplitter")
        
        self.configListWidget = QWidget(self.configSplitter)
        self.leftVBoxLayout = QVBoxLayout(self.configListWidget)
        self.leftVBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.leftVBoxLayout.setSpacing(0)
        self.leftVBoxLayout.setObjectName("leftVBoxLayout")
        self.configListSearch = QLineEdit(self)
        self.configListSearch.setPlaceholderText(
            self.tr("Enter search text..."))
        self.configListSearch.setClearButtonEnabled(True)
        self.configListSearch.setObjectName("configListSearch")
        self.configListSearch.setClearButtonEnabled(True)
        self.leftVBoxLayout.addWidget(self.configListSearch)
        self.configList = QTreeWidget()
        self.configList.setObjectName("configList")
        self.leftVBoxLayout.addWidget(self.configList)
        self.configListSearch.textChanged.connect(self.__searchTextChanged)
        
        self.scrollArea = QScrollArea(self.configSplitter)
        self.scrollArea.setFrameShape(QFrame.Shape.NoFrame)
        self.scrollArea.setVerticalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.scrollArea.setHorizontalScrollBarPolicy(
            Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.scrollArea.setWidgetResizable(False)
        self.scrollArea.setSizeAdjustPolicy(
            QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
        self.scrollArea.setObjectName("scrollArea")
        
        self.configStack = QStackedWidget()
        self.configStack.setFrameShape(QFrame.Shape.Box)
        self.configStack.setFrameShadow(QFrame.Shadow.Sunken)
        self.configStack.setObjectName("configStack")
        self.scrollArea.setWidget(self.configStack)
        
        self.emptyPage = QWidget()
        self.emptyPage.setGeometry(QRect(0, 0, 372, 591))
        self.emptyPage.setObjectName("emptyPage")
        self.vboxlayout = QVBoxLayout(self.emptyPage)
        self.vboxlayout.setSpacing(6)
        self.vboxlayout.setContentsMargins(6, 6, 6, 6)
        self.vboxlayout.setObjectName("vboxlayout")
        spacerItem = QSpacerItem(
            20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
        self.vboxlayout.addItem(spacerItem)
        self.emptyPagePixmap = QLabel(self.emptyPage)
        self.emptyPagePixmap.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.emptyPagePixmap.setObjectName("emptyPagePixmap")
        self.emptyPagePixmap.setPixmap(
            QPixmap(os.path.join(getConfig('ericPixDir'), 'eric.png')))
        self.vboxlayout.addWidget(self.emptyPagePixmap)
        self.textLabel1 = QLabel(self.emptyPage)
        self.textLabel1.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.textLabel1.setObjectName("textLabel1")
        self.vboxlayout.addWidget(self.textLabel1)
        spacerItem1 = QSpacerItem(
            20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
        self.vboxlayout.addItem(spacerItem1)
        self.configStack.addWidget(self.emptyPage)
        
        self.verticalLayout_2.addWidget(self.configSplitter)
        
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
        self.buttonBox.setStandardButtons(
            QDialogButtonBox.StandardButton.Apply |
            QDialogButtonBox.StandardButton.Cancel |
            QDialogButtonBox.StandardButton.Ok |
            QDialogButtonBox.StandardButton.Reset
        )
        self.buttonBox.setObjectName("buttonBox")
        if (
            not self.fromEric and
            self.displayMode == ConfigurationMode.DEFAULTMODE
        ):
            self.buttonBox.button(QDialogButtonBox.StandardButton.Apply).hide()
        self.buttonBox.button(
            QDialogButtonBox.StandardButton.Apply).setEnabled(False)
        self.buttonBox.button(
            QDialogButtonBox.StandardButton.Reset).setEnabled(False)
        self.verticalLayout_2.addWidget(self.buttonBox)

        self.setWindowTitle(self.tr("Preferences"))
        
        self.configList.header().hide()
        self.configList.header().setSortIndicator(
            0, Qt.SortOrder.AscendingOrder)
        self.configList.setSortingEnabled(True)
        self.textLabel1.setText(
            self.tr("Please select an entry of the list \n"
                    "to display the configuration page."))
        
        QMetaObject.connectSlotsByName(self)
        self.setTabOrder(self.configList, self.configStack)
        
        self.configStack.setCurrentWidget(self.emptyPage)
        
        self.configList.setFocus()
    
    def __searchTextChanged(self, text):
        """
        Private slot to handle a change of the search text.
        
        @param text text to search for (string)
        """
        self.__searchChildItems(self.configList.invisibleRootItem(), text)
    
    def __searchChildItems(self, parent, text):
        """
        Private method to enable child items based on a search string.
        
        @param parent reference to the parent item (QTreeWidgetItem)
        @param text text to search for (string)
        @return flag indicating an enabled child item (boolean)
        """
        childEnabled = False
        text = text.lower()
        for index in range(parent.childCount()):
            itm = parent.child(index)
            enable = (
                (self.__searchChildItems(itm, text) or
                 text == "" or
                 text in itm.text(0).lower())
                if itm.childCount() > 0 else
                (text == "" or text in itm.text(0).lower())
            )
            if enable:
                childEnabled = True
            itm.setDisabled(not enable)
        
        return childEnabled
    
    def __initLexers(self):
        """
        Private method to initialize the dictionary of preferences lexers.
        """
        import QScintilla.Lexers
        from .PreferencesLexer import (
            PreferencesLexer, PreferencesLexerLanguageError
        )
        
        self.lexers = {}
        for language in QScintilla.Lexers.getSupportedLanguages():
            if language not in self.lexers:
                with contextlib.suppress(PreferencesLexerLanguageError):
                    self.lexers[language] = PreferencesLexer(language, self)
        
    def __importConfigurationPage(self, name):
        """
        Private method to import a configuration page module.
        
        @param name name of the configuration page module (string)
        @return reference to the configuration page module
        """
        modName = "Preferences.ConfigurationPages.{0}".format(name)
        try:
            mod = __import__(modName)
            components = modName.split('.')
            for comp in components[1:]:
                mod = getattr(mod, comp)
            return mod
        except ImportError:
            EricMessageBox.critical(
                self,
                self.tr("Configuration Page Error"),
                self.tr("""<p>The configuration page <b>{0}</b>"""
                        """ could not be loaded.</p>""").format(name))
            return None
        
    def __showConfigurationPage(self, itm, column):
        """
        Private slot to show a selected configuration page.
        
        @param itm reference to the selected item (QTreeWidgetItem)
        @param column column that was selected (integer) (ignored)
        """
        pageName = itm.getPageName()
        self.showConfigurationPageByName(pageName, setCurrent=False)
        
    def __initPage(self, pageData):
        """
        Private method to initialize a configuration page.
        
        @param pageData data structure for the page to initialize
        @return reference to the initialized page
        """
        page = None
        if isinstance(pageData[2], types.FunctionType):
            page = pageData[2](self)
        else:
            mod = self.__importConfigurationPage(pageData[2])
            if mod:
                page = mod.create(self)
        if page is not None:
            self.configStack.addWidget(page)
            pageData[-1] = page
            with contextlib.suppress(AttributeError):
                page.setMode(self.displayMode)
        return page
        
    def showConfigurationPageByName(self, pageName, setCurrent=True):
        """
        Public slot to show a named configuration page.
        
        @param pageName name of the configuration page to show (string)
        @param setCurrent flag indicating to set the current item (boolean)
        """
        if pageName == "empty" or pageName not in self.configItems:
            page = self.emptyPage
        else:
            pageData = self.configItems[pageName]
            if pageData[-1] is None and pageData[2] is not None:
                # the page was not loaded yet, create it
                page = self.__initPage(pageData)
            else:
                page = pageData[-1]
            if page is None:
                page = self.emptyPage
            elif setCurrent:
                items = self.configList.findItems(
                    pageData[0],
                    Qt.MatchFlag.MatchFixedString |
                    Qt.MatchFlag.MatchRecursive)
                for item in items:
                    if item.data(0, Qt.ItemDataRole.UserRole) == pageName:
                        self.configList.setCurrentItem(item)
        self.configStack.setCurrentWidget(page)
        self.__resizeConfigStack()
        
        if page != self.emptyPage:
            page.polishPage()
            self.buttonBox.button(
                QDialogButtonBox.StandardButton.Apply).setEnabled(True)
            self.buttonBox.button(
                QDialogButtonBox.StandardButton.Reset).setEnabled(True)
        else:
            self.buttonBox.button(
                QDialogButtonBox.StandardButton.Apply).setEnabled(False)
            self.buttonBox.button(
                QDialogButtonBox.StandardButton.Reset).setEnabled(False)
        
        # reset scrollbars
        for sb in [self.scrollArea.horizontalScrollBar(),
                   self.scrollArea.verticalScrollBar()]:
            if sb:
                sb.setValue(0)
        
        self.__currentConfigurationPageName = pageName
    
    def resizeEvent(self, evt):
        """
        Protected method to handle the resizing of the widget.
        
        @param evt reference to the event object
        @type QResizeEvent
        """
        self.__resizeConfigStack()
    
    def __resizeConfigStack(self):
        """
        Private method to resize the stack of configuration pages.
        """
        ssize = self.scrollArea.size()
        if self.scrollArea.horizontalScrollBar():
            ssize.setHeight(
                ssize.height() -
                self.scrollArea.horizontalScrollBar().height() - 2)
        if self.scrollArea.verticalScrollBar():
            ssize.setWidth(
                ssize.width() -
                self.scrollArea.verticalScrollBar().width() - 2)
        psize = self.configStack.currentWidget().minimumSizeHint()
        self.configStack.resize(max(ssize.width(), psize.width()),
                                max(ssize.height(), psize.height()))
    
    def getConfigurationPageName(self):
        """
        Public method to get the page name of the current page.
        
        @return page name of the current page (string)
        """
        return self.__currentConfigurationPageName
        
    def calledFromEric(self):
        """
        Public method to check, if invoked from within eric.
        
        @return flag indicating invocation from within eric (boolean)
        """
        return self.fromEric
        
    def getPage(self, pageName):
        """
        Public method to get a reference to the named page.
        
        @param pageName name of the configuration page (string)
        @return reference to the page or None, indicating page was
            not loaded yet
        """
        return self.configItems[pageName][-1]
        
    def getLexers(self):
        """
        Public method to get a reference to the lexers dictionary.
        
        @return reference to the lexers dictionary
        """
        return self.lexers
        
    def setPreferences(self):
        """
        Public method called to store the selected values into the preferences
        storage.
        """
        now = time.monotonic()
        for pageData in self.configItems.values():
            if pageData[-1]:
                pageData[-1].save()
                # page was loaded (and possibly modified)
                if time.monotonic() - now > 0.01:
                    QApplication.processEvents()    # ensure HMI is responsive
                    now = time.monotonic()
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.buttonBox.button(
            QDialogButtonBox.StandardButton.Apply
        ):
            self.on_applyButton_clicked()
        elif button == self.buttonBox.button(
            QDialogButtonBox.StandardButton.Reset
        ):
            self.on_resetButton_clicked()
        
    @pyqtSlot()
    def on_applyButton_clicked(self):
        """
        Private slot called to apply the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            page = self.configStack.currentWidget()
            savedState = page.saveState()
            page.save()
            self.preferencesChanged.emit()
            if savedState is not None:
                page.setState(savedState)
            page.polishPage()
        
    @pyqtSlot()
    def on_resetButton_clicked(self):
        """
        Private slot called to reset the settings of the current page.
        """
        if self.configStack.currentWidget() != self.emptyPage:
            currentPage = self.configStack.currentWidget()
            savedState = currentPage.saveState()
            pageName = self.configList.currentItem().getPageName()
            self.configStack.removeWidget(currentPage)
            if pageName == "editorHighlightingStylesPage":
                self.__initLexers()
            self.configItems[pageName][-1] = None
            
            self.showConfigurationPageByName(pageName)
            if savedState is not None:
                self.configStack.currentWidget().setState(savedState)
        
    def getExpandedEntries(self):
        """
        Public method to get a list of expanded entries.
        
        @return list of expanded entries (list of string)
        """
        return self.__expandedEntries
    
    @pyqtSlot(QTreeWidgetItem)
    def on_configList_itemCollapsed(self, item):
        """
        Private slot handling a list entry being collapsed.
        
        @param item reference to the collapsed item (QTreeWidgetItem)
        """
        pageName = item.data(0, Qt.ItemDataRole.UserRole)
        if pageName in self.__expandedEntries:
            self.__expandedEntries.remove(pageName)
    
    @pyqtSlot(QTreeWidgetItem)
    def on_configList_itemExpanded(self, item):
        """
        Private slot handling a list entry being expanded.
        
        @param item reference to the expanded item (QTreeWidgetItem)
        """
        pageName = item.data(0, Qt.ItemDataRole.UserRole)
        if pageName not in self.__expandedEntries:
            self.__expandedEntries.append(pageName)
    
    def isUsingWebEngine(self):
        """
        Public method to get an indication, if QtWebEngine is being used.
        
        @return flag indicating the use of QtWebEngine
        @rtype bool
        """
        return (
            self.__webEngine or
            self.displayMode == ConfigurationMode.WEBBROWSERMODE
        )


class ConfigurationDialog(QDialog):
    """
    Class for the dialog variant.
    
    @signal preferencesChanged() emitted after settings have been changed
    @signal masterPasswordChanged(str, str) emitted after the master
        password has been changed with the old and the new password
    """
    preferencesChanged = pyqtSignal()
    masterPasswordChanged = pyqtSignal(str, str)
    
    def __init__(self, parent=None, name=None, modal=False,
                 fromEric=True,
                 displayMode=ConfigurationMode.DEFAULTMODE,
                 expandedEntries=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        @param name name of the dialog
        @type str
        @param modal flag indicating a modal dialog
        @type bool
        @param fromEric flag indicating a dialog generation from within the
            eric IDE
        @type bool
        @param displayMode mode of the configuration dialog
        @type ConfigurationMode
        @param expandedEntries list of entries to be shown expanded
        @type list of str
        """
        super().__init__(parent)
        if name:
            self.setObjectName(name)
        self.setModal(modal)
        self.setWindowFlags(Qt.WindowType.Window)
        
        self.layout = QVBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        
        self.cw = ConfigurationWidget(self, fromEric=fromEric,
                                      displayMode=displayMode,
                                      expandedEntries=expandedEntries)
        size = self.cw.size()
        self.layout.addWidget(self.cw)
        self.resize(size)
        self.setWindowTitle(self.cw.windowTitle())
        
        self.cw.accepted.connect(self.accept)
        self.cw.rejected.connect(self.reject)
        self.cw.preferencesChanged.connect(self.__preferencesChanged)
        self.cw.masterPasswordChanged.connect(self.__masterPasswordChanged)
        
    def __preferencesChanged(self):
        """
        Private slot to handle a change of the preferences.
        """
        self.preferencesChanged.emit()
        
    def __masterPasswordChanged(self, oldPassword, newPassword):
        """
        Private slot to handle the change of the master password.
        
        @param oldPassword current master password (string)
        @param newPassword new master password (string)
        """
        self.masterPasswordChanged.emit(oldPassword, newPassword)
        
    def showConfigurationPageByName(self, pageName):
        """
        Public slot to show a named configuration page.
        
        @param pageName name of the configuration page to show (string)
        """
        self.cw.showConfigurationPageByName(pageName)
        
    def getConfigurationPageName(self):
        """
        Public method to get the page name of the current page.
        
        @return page name of the current page (string)
        """
        return self.cw.getConfigurationPageName()
        
    def getExpandedEntries(self):
        """
        Public method to get a list of expanded entries.
        
        @return list of expanded entries (list of string)
        """
        return self.cw.getExpandedEntries()
        
    def setPreferences(self):
        """
        Public method called to store the selected values into the preferences
        storage.
        """
        self.cw.setPreferences()
    
    def accept(self):
        """
        Public method to accept the dialog.
        """
        super().accept()


class ConfigurationWindow(EricMainWindow):
    """
    Main window class for the standalone dialog.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super().__init__(parent)
        
        self.cw = ConfigurationWidget(self, fromEric=False)
        size = self.cw.size()
        self.setCentralWidget(self.cw)
        self.resize(size)
        self.setWindowTitle(self.cw.windowTitle())
        
        self.setStyle(Preferences.getUI("Style"),
                      Preferences.getUI("StyleSheet"))
        
        self.cw.accepted.connect(self.accept)
        self.cw.rejected.connect(self.close)
        
    def showConfigurationPageByName(self, pageName):
        """
        Public slot to show a named configuration page.
        
        @param pageName name of the configuration page to show (string)
        """
        self.cw.showConfigurationPageByName(pageName)
        
    def accept(self):
        """
        Public slot called by the Ok button.
        """
        self.cw.setPreferences()
        Preferences.saveResetLayout()
        Preferences.syncPreferences()
        self.close()

eric ide

mercurial