src/eric7/Preferences/ConfigurationPages/EditorHighlightingStylesPage.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9153
506e35e424d5
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Preferences/ConfigurationPages/EditorHighlightingStylesPage.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,873 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2006 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Editor Highlighting Styles configuration page.
+"""
+
+import pathlib
+
+from PyQt6.QtCore import pyqtSlot, Qt, QFile, QIODevice
+from PyQt6.QtGui import QFont, QColor
+from PyQt6.QtWidgets import (
+    QColorDialog, QFontDialog, QInputDialog, QMenu, QTreeWidgetItem, QDialog
+)
+
+from .ConfigurationPageBase import ConfigurationPageBase
+from .Ui_EditorHighlightingStylesPage import Ui_EditorHighlightingStylesPage
+from ..SubstyleDefinitionDialog import SubstyleDefinitionDialog
+
+from EricWidgets import EricMessageBox, EricFileDialog
+
+import UI.PixmapCache
+
+
+class EditorHighlightingStylesPage(ConfigurationPageBase,
+                                   Ui_EditorHighlightingStylesPage):
+    """
+    Class implementing the Editor Highlighting Styles configuration page.
+    """
+    FAMILYONLY = 0
+    SIZEONLY = 1
+    FAMILYANDSIZE = 2
+    FONT = 99
+    
+    StyleRole = Qt.ItemDataRole.UserRole + 1
+    SubstyleRole = Qt.ItemDataRole.UserRole + 2
+    
+    def __init__(self, lexers):
+        """
+        Constructor
+        
+        @param lexers reference to the lexers dictionary
+        """
+        super().__init__()
+        self.setupUi(self)
+        self.setObjectName("EditorHighlightingStylesPage")
+        
+        self.defaultSubstylesButton.setIcon(UI.PixmapCache.getIcon("editUndo"))
+        self.addSubstyleButton.setIcon(UI.PixmapCache.getIcon("plus"))
+        self.deleteSubstyleButton.setIcon(UI.PixmapCache.getIcon("minus"))
+        self.editSubstyleButton.setIcon(UI.PixmapCache.getIcon("edit"))
+        self.copySubstyleButton.setIcon(UI.PixmapCache.getIcon("editCopy"))
+        
+        self.__fontButtonMenu = QMenu()
+        act = self.__fontButtonMenu.addAction(self.tr("Font"))
+        act.setData(self.FONT)
+        self.__fontButtonMenu.addSeparator()
+        act = self.__fontButtonMenu.addAction(
+            self.tr("Family and Size only"))
+        act.setData(self.FAMILYANDSIZE)
+        act = self.__fontButtonMenu.addAction(self.tr("Family only"))
+        act.setData(self.FAMILYONLY)
+        act = self.__fontButtonMenu.addAction(self.tr("Size only"))
+        act.setData(self.SIZEONLY)
+        self.__fontButtonMenu.triggered.connect(self.__fontButtonMenuTriggered)
+        self.fontButton.setMenu(self.__fontButtonMenu)
+        
+        self.__allFontsButtonMenu = QMenu()
+        act = self.__allFontsButtonMenu.addAction(self.tr("Font"))
+        act.setData(self.FONT)
+        self.__allFontsButtonMenu.addSeparator()
+        act = self.__allFontsButtonMenu.addAction(
+            self.tr("Family and Size only"))
+        act.setData(self.FAMILYANDSIZE)
+        act = self.__allFontsButtonMenu.addAction(self.tr("Family only"))
+        act.setData(self.FAMILYONLY)
+        act = self.__allFontsButtonMenu.addAction(self.tr("Size only"))
+        act.setData(self.SIZEONLY)
+        self.__allFontsButtonMenu.triggered.connect(
+            self.__allFontsButtonMenuTriggered)
+        self.allFontsButton.setMenu(self.__allFontsButtonMenu)
+        
+        self.lexer = None
+        self.lexers = lexers
+        
+        # set initial values
+        import QScintilla.Lexers
+        languages = sorted([''] + list(self.lexers.keys()))
+        for language in languages:
+            self.lexerLanguageComboBox.addItem(
+                QScintilla.Lexers.getLanguageIcon(language, False),
+                language)
+        self.on_lexerLanguageComboBox_activated(0)
+    
+    def save(self):
+        """
+        Public slot to save the Editor Highlighting Styles configuration.
+        """
+        for lexer in list(self.lexers.values()):
+            lexer.writeSettings()
+    
+    @pyqtSlot(int)
+    def on_lexerLanguageComboBox_activated(self, index):
+        """
+        Private slot to fill the style combo of the source page.
+        
+        @param index index of the selected entry
+        @type int
+        """
+        language = self.lexerLanguageComboBox.itemText(index)
+        
+        self.styleElementList.clear()
+        self.styleGroup.setEnabled(False)
+        self.lexer = None
+        
+        if not language:
+            return
+        
+        try:
+            self.lexer = self.lexers[language]
+        except KeyError:
+            return
+        
+        self.styleGroup.setEnabled(True)
+        for description, styleNo, subStyleNo in self.lexer.getStyles():
+            if subStyleNo >= 0:
+                parent = self.styleElementList.findItems(
+                    self.lexer.description(styleNo),
+                    Qt.MatchFlag.MatchExactly)[0]
+                parent.setExpanded(True)
+            else:
+                parent = self.styleElementList
+            itm = QTreeWidgetItem(parent, [description])
+            itm.setData(0, self.StyleRole, styleNo)
+            itm.setData(0, self.SubstyleRole, subStyleNo)
+        self.__styleAllItems()
+        self.styleElementList.setCurrentItem(
+            self.styleElementList.topLevelItem(0))
+    
+    def __stylesForItem(self, itm):
+        """
+        Private method to get the style and sub-style number of the given item.
+        
+        @param itm reference to the item to extract the styles from
+        @type QTreeWidgetItem
+        @return tuple containing the style and sub-style numbers
+        @rtype tuple of (int, int)
+        """
+        style = itm.data(0, self.StyleRole)
+        substyle = itm.data(0, self.SubstyleRole)
+
+        return (style, substyle)
+    
+    def __currentStyles(self):
+        """
+        Private method to get the styles of the current item.
+        
+        @return tuple containing the style and sub-style numbers
+        @rtype tuple of (int, int)
+        """
+        itm = self.styleElementList.currentItem()
+        # return default style, if no current item
+        styles = (0, -1) if itm is None else self.__stylesForItem(itm)
+        
+        return styles
+    
+    def __styleOneItem(self, item, style, substyle):
+        """
+        Private method to style one item of the style element list.
+        
+        @param item reference to the item to be styled
+        @type QTreeWidgetItem
+        @param style base style number
+        @type int
+        @param substyle sub-style number
+        @type int
+        """
+        colour = self.lexer.color(style, substyle)
+        paper = self.lexer.paper(style, substyle)
+        font = self.lexer.font(style, substyle)
+        eolfill = self.lexer.eolFill(style, substyle)
+        
+        item.setFont(0, font)
+        item.setBackground(0, paper)
+        item.setForeground(0, colour)
+        if eolfill:
+            item.setCheckState(0, Qt.CheckState.Checked)
+        else:
+            item.setCheckState(0, Qt.CheckState.Unchecked)
+    
+    def __styleAllItems(self):
+        """
+        Private method to style all items of the style element list.
+        """
+        itm = self.styleElementList.topLevelItem(0)
+        while itm is not None:
+            style, substyle = self.__stylesForItem(itm)
+            self.__styleOneItem(itm, style, substyle)
+            itm = self.styleElementList.itemBelow(itm)
+    
+    def __styleSample(self, color, paper, font=None):
+        """
+        Private method to style the sample text.
+        
+        @param color foreground color for the sample
+        @type QColor
+        @param paper background color for the sample
+        @type QColor
+        @param font font for the sample (defaults to None)
+        @type QFont (optional)
+        """
+        if font:
+            self.sampleText.setFont(font)
+        
+        self.sampleText.setStyleSheet(
+            "QLineEdit {{ color: {0}; background-color: {1}; }}".format(
+                color.name(), paper.name()
+            )
+        )
+    
+    @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
+    def on_styleElementList_currentItemChanged(self, current, previous):
+        """
+        Private method to handle a change of the current row.
+        
+        @param current reference to the current item
+        @type QTreeWidgetItem
+        @param previous reference to the previous item
+        @type QTreeWidgetItem
+        """
+        if current is None:
+            return
+        
+        style, substyle = self.__stylesForItem(current)
+        colour = self.lexer.color(style, substyle)
+        paper = self.lexer.paper(style, substyle)
+        eolfill = self.lexer.eolFill(style, substyle)
+        font = self.lexer.font(style, substyle)
+        
+        self.__styleSample(colour, paper, font=font)
+        self.eolfillCheckBox.setChecked(eolfill)
+        
+        selectedOne = len(self.styleElementList.selectedItems()) == 1
+        self.defaultSubstylesButton.setEnabled(
+            selectedOne and substyle < 0 and self.lexer.isBaseStyle(style))
+        self.addSubstyleButton.setEnabled(
+            selectedOne and substyle < 0 and self.lexer.isBaseStyle(style))
+        self.deleteSubstyleButton.setEnabled(selectedOne and substyle >= 0)
+        self.editSubstyleButton.setEnabled(selectedOne and substyle >= 0)
+        self.copySubstyleButton.setEnabled(selectedOne and substyle >= 0)
+    
+    @pyqtSlot()
+    def on_foregroundButton_clicked(self):
+        """
+        Private method used to select the foreground colour of the selected
+        style and lexer.
+        """
+        style, substyle = self.__currentStyles()
+        colour = QColorDialog.getColor(self.lexer.color(style, substyle))
+        if colour.isValid():
+            paper = self.lexer.paper(style, substyle)
+            self.__styleSample(colour, paper)
+            for selItem in self.styleElementList.selectedItems():
+                style, substyle = self.__stylesForItem(selItem)
+                self.lexer.setColor(colour, style, substyle)
+                selItem.setForeground(0, colour)
+    
+    @pyqtSlot()
+    def on_backgroundButton_clicked(self):
+        """
+        Private method used to select the background colour of the selected
+        style and lexer.
+        """
+        style, substyle = self.__currentStyles()
+        paper = QColorDialog.getColor(self.lexer.paper(style, substyle))
+        if paper.isValid():
+            colour = self.lexer.color(style, substyle)
+            self.__styleSample(colour, paper)
+            for selItem in self.styleElementList.selectedItems():
+                style, substyle = self.__stylesForItem(selItem)
+                self.lexer.setPaper(paper, style, substyle)
+                selItem.setBackground(0, paper)
+    
+    @pyqtSlot()
+    def on_allBackgroundColoursButton_clicked(self):
+        """
+        Private method used to select the background colour of all styles of a
+        selected lexer.
+        """
+        style, substyle = self.__currentStyles()
+        paper = QColorDialog.getColor(self.lexer.paper(style, substyle))
+        if paper.isValid():
+            colour = self.lexer.color(style, substyle)
+            self.__styleSample(colour, paper)
+            
+            itm = self.styleElementList.topLevelItem(0)
+            while itm is not None:
+                style, substyle = self.__stylesForItem(itm)
+                self.lexer.setPaper(paper, style, substyle)
+                itm = self.styleElementList.itemBelow(itm)
+            self.__styleAllItems()
+    
+    def __changeFont(self, doAll, familyOnly, sizeOnly):
+        """
+        Private slot to change the highlighter font.
+        
+        @param doAll flag indicating to change the font for all styles
+            (boolean)
+        @param familyOnly flag indicating to set the font family only (boolean)
+        @param sizeOnly flag indicating to set the font size only (boolean
+        """
+        def setFont(font, style, substyle, familyOnly, sizeOnly):
+            """
+            Local function to set the font.
+            
+            @param font font to be set
+            @type QFont
+            @param style style number
+            @type int
+            @param substyle sub-style number
+            @type int
+            @param familyOnly flag indicating to set the font family only
+            @type bool
+            @param sizeOnly flag indicating to set the font size only
+            @type bool
+            """
+            if familyOnly or sizeOnly:
+                newFont = QFont(self.lexer.font(style))
+                if familyOnly:
+                    newFont.setFamily(font.family())
+                if sizeOnly:
+                    newFont.setPointSize(font.pointSize())
+                self.lexer.setFont(newFont, style, substyle)
+            else:
+                self.lexer.setFont(font, style, substyle)
+        
+        def setSampleFont(font, familyOnly, sizeOnly):
+            """
+            Local function to set the font of the sample text.
+            
+            @param font font to be set (QFont)
+            @param familyOnly flag indicating to set the font family only
+                (boolean)
+            @param sizeOnly flag indicating to set the font size only (boolean
+            """
+            if familyOnly or sizeOnly:
+                style, substyle = self.__currentStyles()
+                newFont = QFont(self.lexer.font(style, substyle))
+                if familyOnly:
+                    newFont.setFamily(font.family())
+                if sizeOnly:
+                    newFont.setPointSize(font.pointSize())
+                self.sampleText.setFont(newFont)
+            else:
+                self.sampleText.setFont(font)
+        
+        style, substyle = self.__currentStyles()
+        options = (
+            QFontDialog.FontDialogOption.MonospacedFonts
+            if self.monospacedButton.isChecked() else
+            QFontDialog.FontDialogOption(0)
+        )
+        font, ok = QFontDialog.getFont(self.lexer.font(style, substyle), self,
+                                       "", options)
+        if ok:
+            setSampleFont(font, familyOnly, sizeOnly)
+            if doAll:
+                itm = self.styleElementList.topLevelItem(0)
+                while itm is not None:
+                    style, substyle = self.__stylesForItem(itm)
+                    setFont(font, style, substyle, familyOnly, sizeOnly)
+                    itm = self.styleElementList.itemBelow(itm)
+                self.__styleAllItems()
+            else:
+                for selItem in self.styleElementList.selectedItems():
+                    style, substyle = self.__stylesForItem(selItem)
+                    setFont(font, style, substyle, familyOnly, sizeOnly)
+                    itmFont = self.lexer.font(style, substyle)
+                    selItem.setFont(0, itmFont)
+    
+    def __fontButtonMenuTriggered(self, act):
+        """
+        Private slot used to select the font of the selected style and lexer.
+        
+        @param act reference to the triggering action (QAction)
+        """
+        if act is None:
+            return
+        
+        familyOnly = act.data() in [self.FAMILYANDSIZE, self.FAMILYONLY]
+        sizeOnly = act.data() in [self.FAMILYANDSIZE, self.SIZEONLY]
+        self.__changeFont(False, familyOnly, sizeOnly)
+    
+    def __allFontsButtonMenuTriggered(self, act):
+        """
+        Private slot used to change the font of all styles of a selected lexer.
+        
+        @param act reference to the triggering action (QAction)
+        """
+        if act is None:
+            return
+        
+        familyOnly = act.data() in [self.FAMILYANDSIZE, self.FAMILYONLY]
+        sizeOnly = act.data() in [self.FAMILYANDSIZE, self.SIZEONLY]
+        self.__changeFont(True, familyOnly, sizeOnly)
+    
+    @pyqtSlot(bool)
+    def on_eolfillCheckBox_clicked(self, on):
+        """
+        Private method used to set the eolfill for the selected style and
+        lexer.
+        
+        @param on flag indicating enabled or disabled state (boolean)
+        """
+        style, substyle = self.__currentStyles()
+        checkState = Qt.CheckState.Checked if on else Qt.CheckState.Unchecked
+        for selItem in self.styleElementList.selectedItems():
+            style, substyle = self.__stylesForItem(selItem)
+            self.lexer.setEolFill(on, style, substyle)
+            selItem.setCheckState(0, checkState)
+    
+    @pyqtSlot()
+    def on_allEolFillButton_clicked(self):
+        """
+        Private method used to set the eolfill for all styles of a selected
+        lexer.
+        """
+        on = self.tr("Enabled")
+        off = self.tr("Disabled")
+        selection, ok = QInputDialog.getItem(
+            self,
+            self.tr("Fill to end of line"),
+            self.tr("Select fill to end of line for all styles"),
+            [on, off],
+            0, False)
+        if ok:
+            enabled = selection == on
+            self.eolfillCheckBox.setChecked(enabled)
+            
+            itm = self.styleElementList.topLevelItem(0)
+            while itm is not None:
+                style, substyle = self.__stylesForItem(itm)
+                self.lexer.setEolFill(enabled, style, substyle)
+                itm = self.styleElementList.itemBelow(itm)
+            self.__styleAllItems()
+    
+    @pyqtSlot()
+    def on_defaultButton_clicked(self):
+        """
+        Private method to set the current style to its default values.
+        """
+        for selItem in self.styleElementList.selectedItems():
+            style, substyle = self.__stylesForItem(selItem)
+            self.__setToDefault(style, substyle)
+        self.on_styleElementList_currentItemChanged(
+            self.styleElementList.currentItem(), None)
+        self.__styleAllItems()
+    
+    @pyqtSlot()
+    def on_allDefaultButton_clicked(self):
+        """
+        Private method to set all styles to their default values.
+        """
+        itm = self.styleElementList.topLevelItem(0)
+        while itm is not None:
+            style, substyle = self.__stylesForItem(itm)
+            self.__setToDefault(style, substyle)
+            itm = self.styleElementList.itemBelow(itm)
+        self.on_styleElementList_currentItemChanged(
+            self.styleElementList.currentItem(), None)
+        self.__styleAllItems()
+    
+    def __setToDefault(self, style, substyle):
+        """
+        Private method to set a specific style to its default values.
+        
+        @param style style number
+        @type int
+        @param substyle sub-style number
+        @type int
+        """
+        self.lexer.setColor(self.lexer.defaultColor(style, substyle),
+                            style, substyle)
+        self.lexer.setPaper(self.lexer.defaultPaper(style, substyle),
+                            style, substyle)
+        self.lexer.setFont(self.lexer.defaultFont(style, substyle),
+                           style, substyle)
+        self.lexer.setEolFill(self.lexer.defaultEolFill(style, substyle),
+                              style, substyle)
+    
+    #######################################################################
+    ## Importing and exporting of styles
+    #######################################################################
+    
+    @pyqtSlot()
+    def on_importButton_clicked(self):
+        """
+        Private slot to import styles to be selected.
+        """
+        self.__importStyles(importAll=False)
+    
+    @pyqtSlot()
+    def on_exportButton_clicked(self):
+        """
+        Private slot to export styles to be selected.
+        """
+        self.__exportStyles(exportAll=False)
+    
+    @pyqtSlot()
+    def on_importAllButton_clicked(self):
+        """
+        Private slot to import the styles of all lexers.
+        """
+        self.__importStyles(importAll=True)
+    
+    @pyqtSlot()
+    def on_exportAllButton_clicked(self):
+        """
+        Private slot to export the styles of all lexers.
+        """
+        self.__exportStyles(exportAll=True)
+    
+    def __exportStyles(self, exportAll=False):
+        """
+        Private method to export the styles of selectable lexers.
+        
+        @param exportAll flag indicating to export all styles without asking
+            (defaults to False)
+        @type bool (optional)
+        """
+        from eric7config import getConfig
+        stylesDir = getConfig("ericStylesDir")
+        
+        lexerNames = list(self.lexers.keys())
+        if not exportAll:
+            if self.lexer:
+                preselect = [self.lexer.language()]
+            else:
+                preselect = []
+            from .EditorHighlightingStylesSelectionDialog import (
+                EditorHighlightingStylesSelectionDialog)
+            dlg = EditorHighlightingStylesSelectionDialog(
+                lexerNames, forImport=False, preselect=preselect)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                lexerNames = dlg.getLexerNames()
+            else:
+                # Cancelled by user
+                return
+        
+        lexers = [self.lexers[name] for name in lexerNames]
+        
+        fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("Export Highlighting Styles"),
+            stylesDir,
+            self.tr("Highlighting Styles File (*.ehj)"),
+            "",
+            EricFileDialog.DontConfirmOverwrite)
+        
+        if not fn:
+            return
+        
+        fpath = pathlib.Path(fn)
+        if not fpath.suffix:
+            ex = selectedFilter.split("(*")[1].split(")")[0]
+            if ex:
+                fpath = fpath.with_suffix(ex)
+        
+        ok = (
+            EricMessageBox.yesNo(
+                self,
+                self.tr("Export Highlighting Styles"),
+                self.tr("""<p>The highlighting styles file <b>{0}</b> exists"""
+                        """ already. Overwrite it?</p>""").format(fpath))
+            if fpath.exists() else
+            True
+        )
+        
+        if ok:
+            from Preferences.HighlightingStylesFile import (
+                HighlightingStylesFile
+            )
+            highlightingStylesFile = HighlightingStylesFile()
+            highlightingStylesFile.writeFile(str(fpath), lexers)
+    
+    def __importStyles(self, importAll=False):
+        """
+        Private method to import the styles of lexers to be selected.
+        
+        @param importAll flag indicating to import all styles without asking
+            (defaults to False)
+        @type bool (optional)
+        """
+        from eric7config import getConfig
+        stylesDir = getConfig("ericStylesDir")
+        
+        fn = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Import Highlighting Styles"),
+            stylesDir,
+            self.tr("Highlighting Styles File (*.ehj);;"
+                    "XML Highlighting Styles File (*.e6h *.e4h)"))
+        
+        if not fn:
+            return
+        
+        if fn.endswith(".ehj"):
+            # new JSON based file
+            from Preferences.HighlightingStylesFile import (
+                HighlightingStylesFile
+            )
+            highlightingStylesFile = HighlightingStylesFile()
+            styles = highlightingStylesFile.readFile(fn)
+            if not styles:
+                return
+        else:
+            # old XML based file
+            f = QFile(fn)
+            if f.open(QIODevice.OpenModeFlag.ReadOnly):
+                from EricXML.HighlightingStylesReader import (
+                    HighlightingStylesReader
+                )
+                reader = HighlightingStylesReader(f, self.lexers)
+                styles = reader.readXML()
+                f.close()
+                if not styles:
+                    return
+            else:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Import Highlighting Styles"),
+                    self.tr(
+                        "<p>The highlighting styles file <b>{0}</b> could not"
+                        " be read.</p><p>Reason: {1}</p>"
+                    ).format(fn, f.errorString())
+                )
+                return
+        
+        self.__applyStyles(styles, importAll=importAll)
+        self.on_lexerLanguageComboBox_activated(
+            self.lexerLanguageComboBox.currentIndex())
+    
+    def __applyStyles(self, stylesList, importAll=False):
+        """
+        Private method to apply the imported styles to this dialog.
+        
+        @param stylesList list of imported lexer styles
+        @type list of dict
+        @param importAll flag indicating to import all styles without asking
+            (defaults to False)
+        @type bool (optional)
+        """
+        lexerNames = [d["name"]
+                      for d in stylesList
+                      if d["name"] in self.lexers]
+        
+        if not importAll:
+            from .EditorHighlightingStylesSelectionDialog import (
+                EditorHighlightingStylesSelectionDialog)
+            dlg = EditorHighlightingStylesSelectionDialog(
+                lexerNames, forImport=True)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                lexerNames = dlg.getLexerNames()
+            else:
+                # Cancelled by user
+                return
+        
+        for lexerDict in stylesList:
+            if lexerDict["name"] in lexerNames:
+                lexer = self.lexers[lexerDict["name"]]
+                for styleDict in lexerDict["styles"]:
+                    style = styleDict["style"]
+                    substyle = styleDict["substyle"]
+                    lexer.setColor(QColor(styleDict["color"]), style, substyle)
+                    lexer.setPaper(QColor(styleDict["paper"]), style, substyle)
+                    font = QFont()
+                    font.fromString(styleDict["font"])
+                    lexer.setFont(font, style, substyle)
+                    lexer.setEolFill(styleDict["eolfill"], style, substyle)
+                    if substyle >= 0:
+                        # description and words can only be set for sub-styles
+                        lexer.setDescription(styleDict["description"],
+                                             style, substyle)
+                        lexer.setWords(styleDict["words"], style, substyle)
+    
+    #######################################################################
+    ## Methods to save and restore the state
+    #######################################################################
+    
+    def saveState(self):
+        """
+        Public method to save the current state of the widget.
+        
+        @return list containing the index of the selected lexer language
+            and a tuple containing the index of the parent selected lexer
+            entry and the index of the selected entry
+        @rtype list of int and tuple of (int, int)
+        """
+        itm = self.styleElementList.currentItem()
+        if itm:
+            parent = itm.parent()
+            if parent is None:
+                currentData = (
+                    None, self.styleElementList.indexOfTopLevelItem(itm))
+            else:
+                currentData = (
+                    self.styleElementList.indexOfTopLevelItem(parent),
+                    parent.indexOfChild(itm)
+                )
+            
+            savedState = [
+                self.lexerLanguageComboBox.currentIndex(),
+                currentData,
+            ]
+        else:
+            savedState = []
+        return savedState
+    
+    def setState(self, state):
+        """
+        Public method to set the state of the widget.
+        
+        @param state state data generated by saveState
+        """
+        if state:
+            self.lexerLanguageComboBox.setCurrentIndex(state[0])
+            self.on_lexerLanguageComboBox_activated(
+                self.lexerLanguageComboBox.currentIndex())
+            
+            parentIndex, index = state[1]
+            if parentIndex is None:
+                itm = self.styleElementList.topLevelItem(index)
+            else:
+                parent = self.styleElementList.topLevelItem(parentIndex)
+                itm = parent.child(index)
+            self.styleElementList.setCurrentItem(itm)
+    
+    #######################################################################
+    ## Methods to add, delete and edit sub-styles and their definitions
+    #######################################################################
+    
+    @pyqtSlot()
+    def on_addSubstyleButton_clicked(self):
+        """
+        Private slot to add a new sub-style.
+        """
+        style, substyle = self.__currentStyles()
+        dlg = SubstyleDefinitionDialog(
+            self.lexer, style, substyle, parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            description, words = dlg.getData()
+            substyle = self.lexer.addSubstyle(style)
+            self.lexer.setDescription(description, style, substyle)
+            self.lexer.setWords(words, style, substyle)
+            
+            parent = self.styleElementList.findItems(
+                self.lexer.description(style), Qt.MatchFlag.MatchExactly)[0]
+            parent.setExpanded(True)
+            itm = QTreeWidgetItem(parent, [description])
+            itm.setData(0, self.StyleRole, style)
+            itm.setData(0, self.SubstyleRole, substyle)
+            self.__styleOneItem(itm, style, substyle)
+    
+    @pyqtSlot()
+    def on_deleteSubstyleButton_clicked(self):
+        """
+        Private slot to delete the selected sub-style.
+        """
+        style, substyle = self.__currentStyles()
+        ok = EricMessageBox.yesNo(
+            self,
+            self.tr("Delete Sub-Style"),
+            self.tr("""<p>Shall the sub-style <b>{0}</b> really be"""
+                    """ deleted?</p>""").format(
+                self.lexer.description(style, substyle))
+        )
+        if ok:
+            self.lexer.delSubstyle(style, substyle)
+            
+            itm = self.styleElementList.currentItem()
+            parent = itm.parent()
+            index = parent.indexOfChild(itm)
+            parent.takeChild(index)
+            del itm
+    
+    @pyqtSlot()
+    def on_editSubstyleButton_clicked(self):
+        """
+        Private slot to edit the selected sub-style entry.
+        """
+        style, substyle = self.__currentStyles()
+        dlg = SubstyleDefinitionDialog(
+            self.lexer, style, substyle, parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            description, words = dlg.getData()
+            self.lexer.setDescription(description, style, substyle)
+            self.lexer.setWords(words, style, substyle)
+            
+            itm = self.styleElementList.currentItem()
+            itm.setText(0, description)
+    
+    @pyqtSlot()
+    def on_copySubstyleButton_clicked(self):
+        """
+        Private slot to copy the selected sub-style.
+        """
+        style, substyle = self.__currentStyles()
+        newSubstyle = self.lexer.addSubstyle(style)
+        
+        description = self.tr("{0} - Copy").format(
+            self.lexer.description(style, substyle))
+        self.lexer.setDescription(description, style, newSubstyle)
+        self.lexer.setWords(self.lexer.words(style, substyle),
+                            style, newSubstyle)
+        self.lexer.setColor(self.lexer.color(style, substyle),
+                            style, newSubstyle)
+        self.lexer.setPaper(self.lexer.paper(style, substyle),
+                            style, newSubstyle)
+        self.lexer.setFont(self.lexer.font(style, substyle),
+                           style, newSubstyle)
+        self.lexer.setEolFill(self.lexer.eolFill(style, substyle),
+                              style, newSubstyle)
+        
+        parent = self.styleElementList.findItems(
+            self.lexer.description(style), Qt.MatchFlag.MatchExactly)[0]
+        parent.setExpanded(True)
+        itm = QTreeWidgetItem(parent, [description])
+        itm.setData(0, self.StyleRole, style)
+        itm.setData(0, self.SubstyleRole, newSubstyle)
+        self.__styleOneItem(itm, style, newSubstyle)
+    
+    @pyqtSlot()
+    def on_defaultSubstylesButton_clicked(self):
+        """
+        Private slot to reset all substyles to default values.
+        """
+        style, substyle = self.__currentStyles()
+        ok = EricMessageBox.yesNo(
+            self,
+            self.tr("Reset Sub-Styles to Default"),
+            self.tr("<p>Do you really want to reset all defined sub-styles of"
+                    " <b>{0}</b> to the default values?</p>""")
+            .format(self.lexer.description(style, substyle))
+        )
+        if ok:
+            # 1. reset sub-styles
+            self.lexer.loadDefaultSubStyles(style)
+            
+            # 2. delete all existing sub-style items
+            parent = self.styleElementList.currentItem()
+            while parent.childCount() > 0:
+                itm = parent.takeChild(0)     # __IGNORE_WARNING__
+                del itm
+            
+            # 3. create the new list of sub-style items
+            for description, _, substyle in self.lexer.getSubStyles(style):
+                itm = QTreeWidgetItem(parent, [description])
+                itm.setData(0, self.StyleRole, style)
+                itm.setData(0, self.SubstyleRole, substyle)
+                self.__styleOneItem(itm, style, substyle)
+
+
+def create(dlg):
+    """
+    Module function to create the configuration page.
+    
+    @param dlg reference to the configuration dialog
+    @return reference to the instantiated page (ConfigurationPageBase)
+    """
+    page = EditorHighlightingStylesPage(dlg.getLexers())
+    return page

eric ide

mercurial