Outils pour utilisateurs

Outils du site


pyqt4_colorisation_syntaxe_qtextedit

Créer une colorisation syntaxique d'un QTextEdit (exemple: script SQL)

Objectif

Quand on écrit un texte dans un langage informatique, on améliore sa lisibilité en affichant certains mots ou signes caractéristiques du langage en couleur.

L'objectif de cette page est de décrire comment on fait, en s'appuyant sur l'exemple d'un script SQL (langage de requêtes pour un système de gestion de base de données) conforme à la syntaxe de sqlite3 (http://www.sqlite.org/), affiché dans un QTextEdit.

Principe

La colorisation est définie en sous-classant la classe QtGui.QSyntaxHighlighter, et en surchargeant sa méthode highlightBlock.

L'initialisation de cette nouvelle classe consiste à définir une liste de règles, chaque règle étant construite comme un couple [regex, format]:

  • Le 1er élément “regex” est un QtCore.QRegExp avec le motif à rechercher,
  • le 2ème élément “format” est un QtGui.QTextCharFormat avec la couleur à appliquer.

La méthode highlightBlock va recevoir chaque ligne, va tester chaque règle et en déduire le format à appliquer.

Petite subtilité: l'ordre des règles dans la liste est important: il est évident, par exemple, qu'un mot clé trouvé dans une ligne de commentaire ne doit pas être colorisé comme un mot clé!

Une fois cette classe définie (ColorSyntaxSQL par exemple), la colorisation sera mise en place sur le texte à afficher dans le QTextEdit (self.edit par exemple) par une seule ligne:

        self.colorSyntaxSQL = ColorSyntaxSQL(self.edit.document())

Classe pour coloriser un script SQL

On place le script ci-dessous dans un fichier “bibcolorsyntaxsql.py” qui sera importé comme module par le programme utilisateur

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import division
#Python 2.7
 
import os, sys
 
from PyQt4 import QtCore, QtGui
 
#############################################################################
class ColorSyntaxSQL(QtGui.QSyntaxHighlighter):
 
    #========================================================================
    def __init__(self, parent=None):
        super(ColorSyntaxSQL, self).__init__(parent)
 
        # liste des règles: [[regex, format], [regex, format], ...]
        self.regles = []
 
        #--------------------------------------------------------------------
        # coloration des mots clés SQL de sqlite3
        motcles_format = QtGui.QTextCharFormat()
        motcles_format.setForeground(QtCore.Qt.blue) # mots clés en bleu
        motcles_format.setFontWeight(QtGui.QFont.Bold) # pour mise en gras
        # liste des mots à considérer
        motcles_motifs = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", 
        "ANALYZE", "AND", "AS", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", 
        "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", 
        "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", 
        "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", 
        "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", 
        "DISTINCT", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", 
        "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FOR", "FOREIGN", "FROM", 
        "FULL", "GLOB", "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN", 
        "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", 
        "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", 
        "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL", "NULL", "OF", 
        "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", 
        "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", 
        "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", 
        "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", 
        "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE", "USING", 
        "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", 
        "WITHOUT"]
        motcles_motifs += ["TEXT", "INTEGER", "REAL", "NUMERIC", "NONE", "BLOB"]
        motcles_motifs += ["TRUE", "FALSE"]
        # enregistrement dans la liste des règles
        for motcles_motif in motcles_motifs:
            motcles_regex = QtCore.QRegExp("\\b" + motcles_motif + "\\b", 
                                                    QtCore.Qt.CaseInsensitive)
            self.regles.append([motcles_regex, motcles_format])
 
        #--------------------------------------------------------------------
        # nombre entier ou flottant
        nombre_format = QtGui.QTextCharFormat()
        nombre_format.setForeground(QtCore.Qt.darkGreen)
        nombre_motif =  "\\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\\b"
        nombre_regex = QtCore.QRegExp(nombre_motif)
        nombre_regex.setMinimal(True)
        self.regles.append([nombre_regex, nombre_format])
 
        #--------------------------------------------------------------------
        # chaine de caractères simple quote: '...'
        chaine1_format = QtGui.QTextCharFormat()
        chaine1_format.setForeground(QtCore.Qt.green)#red)
        chaine1_motif = "\'.*\'"
        chaine1_regex = QtCore.QRegExp(chaine1_motif)
        chaine1_regex.setMinimal(True)
        self.regles.append([chaine1_regex, chaine1_format])
 
        #--------------------------------------------------------------------
        # chaine de caractères double quotes: "..."
        chaine2_format = QtGui.QTextCharFormat()
        chaine2_format.setForeground(QtCore.Qt.red)
        chaine2_motif = '\".*\"'
        chaine2_regex = QtCore.QRegExp(chaine2_motif)
        chaine2_regex.setMinimal(True)
        self.regles.append([chaine2_regex, chaine2_format])
 
        #--------------------------------------------------------------------
        # delimiteur: parenthèses, crochets, accolades
        delimiteur_format = QtGui.QTextCharFormat()
        delimiteur_format.setForeground(QtCore.Qt.red)
        delimiteur_motif = "[\)\(]+|[\{\}]+|[][]+"
        delimiteur_regex = QtCore.QRegExp(delimiteur_motif)
        self.regles.append([delimiteur_regex, delimiteur_format])
 
        #--------------------------------------------------------------------
        # commentaire sur une seule ligne et jusqu'à fin de ligne: --...\n
        comment_format = QtGui.QTextCharFormat()
        comment_format.setForeground(QtCore.Qt.gray)
        comment_motif = "--[^\n]*"
        comment_regex = QtCore.QRegExp(comment_motif)
        self.regles.append([comment_regex, comment_format])
 
        #--------------------------------------------------------------------
        # commentaires multi-lignes: /*...*/        
        self.commentml_format = QtGui.QTextCharFormat()
        self.commentml_format.setForeground(QtCore.Qt.gray)
 
        self.commentml_deb_regex = QtCore.QRegExp("/\\*")
        self.commentml_fin_regex = QtCore.QRegExp("\\*/")
 
    #========================================================================
    def highlightBlock(self, text):
        """analyse chaque ligne et applique les règles"""
 
        # analyse des lignes avec les règles
        for expression, tformat in self.regles:
            index = expression.indexIn(text)
            while index >= 0:
                length = expression.matchedLength()
                self.setFormat(index, length, tformat)
                index = expression.indexIn(text, index + length)
 
        self.setCurrentBlockState(0)
 
        #pour les commentaires multilignes: /* ... */ 
        startIndex = 0
        if self.previousBlockState()!=1:
            startIndex = self.commentml_deb_regex.indexIn(text)
 
        while startIndex>=0:
            endIndex = self.commentml_fin_regex.indexIn(text, startIndex)
            if endIndex==-1:
                self.setCurrentBlockState(1)
                commentml_lg = len(text)-startIndex
            else:
                commentml_lg = endIndex-startIndex + \
                                       self.commentml_fin_regex.matchedLength()
 
            self.setFormat(startIndex, commentml_lg, self.commentml_format)
 
            startIndex = self.commentml_deb_regex.indexIn(text, 
                                                       startIndex+commentml_lg)

QTextEdit utilisant la colorisation

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import division
# Python 2.7
 
import sys, os
from PyQt4 import QtCore, QtGui
from bibcolorsyntaxsql import ColorSyntaxSQL # pour coloration syntaxique SQL
 
#############################################################################
class Fenetre(QtGui.QWidget):
 
    def __init__(self, scriptsql, parent=None):
        super(Fenetre, self).__init__(parent)
        self.resize(800, 600)
        self.scriptsql = scriptsql
 
        self.edit = QtGui.QTextEdit(self)
 
        # met une police de caractère même largeur pour tous les caractères
        font = QtGui.QFont()
        font.setFamily(u"DejaVu Sans Mono") # police de Qt4
        font.setStyleHint(QtGui.QFont.Courier) # si la police est indisponible
        font.setPointSize(10)
        self.edit.setFont(font)
 
        # met en place la coloration syntaxique
        self.colorSyntaxSQL = ColorSyntaxSQL(self.edit.document())
 
        # affiche le script colorisé
        self.affiche(self.scriptsql)
 
        # positionne le QTextEdit dans la fenêtre
        posit = QtGui.QGridLayout()
        posit.addWidget(self.edit, 0, 0)
        self.setLayout(posit)
 
    #========================================================================
    def affiche(self, chaineunicode):
        """Ecrire dans le widget edit (QTextEdit)"""
        self.edit.append(chaineunicode)
        # fait bouger le curseur à la fin du texte
        self.edit.moveCursor(QtGui.QTextCursor.End, 
                                                 QtGui.QTextCursor.MoveAnchor)
        # force le rafraichissement pour affichage en temps réel
        QtCore.QCoreApplication.processEvents() 
 
#############################################################################
if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
 
    # script SQL à afficher
    scriptsql = u"""
-- **************************************************************************
-- controle des notifications
 
/* toutes les photos par auteur avec acceptations et prix/trophée "photos"
   script mis à jour 2/2014
*/
 
Select distinct photos.bord as bord,
       photos.section as section,
       photos.numphot as numphot,
       photos.photo as photo,
       photos.titre as titre,
       (CASE WHEN photos.accept='*' THEN 'Accepté' ELSE 'Refusé' END) AS acceptation,
 
       (CASE WHEN photos.photo in (select prixphotos.photo from prixphotos)
             THEN (select prixphotos.prix from prixphotos where prixphotos.photo=photos.photo)
             ELSE '' END) AS prix,
 
       (CASE WHEN photos.photo in (select prixphotos.photo from prixphotos)
             THEN (select prixphotos.trophee from prixphotos where prixphotos.photo=photos.photo)
             ELSE '' END) AS trophee
 
from photos, prixphotos
group by photos.photo
order by photos.bord, photos.section, photos.numphot
        """
 
    fen = Fenetre(scriptsql)
    fen.show()
    sys.exit(app.exec_())

Et voilà comment ce sera affiché:

Bien entendu, rien ne vous empêche d'appliquer cela sur n'importe quel langage! En fonction de la complexité de la syntaxe, ce ne sera pas forcément simple, mais ce sera possible.


Amusez-vous bien!

pyqt4_colorisation_syntaxe_qtextedit.txt · Dernière modification: 2014/04/17 18:10 de tyrtamos

Outils de la page