# -*- 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 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.
        """
        for pageData in self.configItems.values():
            if pageData[-1]:
                pageData[-1].save()
                # page was loaded (and possibly modified)
                QApplication.processEvents()    # ensure HMI is responsive
        
    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()
