C++/QT : QTextEdit, ajouter un système de snippet

Partager cet article

Temps estimé pour la lecture de cet article : 32 min

Vous avez l’intention de créer un nouvel éditeur de texte afin de concurrencer le bloc note de Windows (comment ça, il n’a jamais évolué :3) ? Alors c’est parti, à travers cet article, je vous montre comment ajouter un système de snippet à un QTextEdit.

Commençons par le commencement, qu’est-ce qu’un snippet ? Selon wikipédia :

Snippet est un terme de programmation informatique pour une petite portion réutilisable de code source ou de texte.La gestion de snippets est une fonctionnalité de certains éditeurs de texte, des éditeurs de code source, des IDE…

En gros, vous lancer un nouveau projet web, mais comme moi vous ne vous rappelez pas du code minimal d’une page web (quoiqu’en HTML5, c’est beaucoup plus simple qu’à l’époque du XHTML), cela vous permet grâce à votre éditeur en tapant simplement html + un certain raccourci (généralement la touche tabulation), de remplacer le mot html, par le code que l’on désire. Si, vous ne comprenez toujours pas, comme un exemple vaut mieux qu’un long discours, voici donc ce que l’on va chercher à obtenir comme résultat :

Qt - Snippet

Passons au code, pour réaliser un tel système, il suffit de créer notre propre classe qui va hériter de la classe QTextEdit, on définira alors la fonction keyPressEvent(Event *e). Une fois défini, il nous suffira de reconnaître la touche tabulation et d’utiliser la classe QTextCursor afin de récupérer le mot qui précède le curseur de la souris. Si ce dernier correspond à une liste de snippet que nous avons nous-même prédéfini alors on remplace ce mot par son équivalent. On verra en passant, comment il est possible de régler la taille des tabulations. Let’s do this !

Tout d’abord, on peut se demander quelle sera le moyen le plus efficace pour ajouter de plus en plus de snippet au fur et à mesure. Pour ma part, j’ai décidé d’utiliser une map. C’est un élément de la librairie STL, une map permet d’associer un index à une donnée précise, ici c’est donc tout conseillé. On va en effet lier le mot que l’on veut à son code source associée. On aura donc quelque chose du genre :

std::map<std::string, QString> _snippets;

Voici le header de notre classe QTextEditSnippet :

#ifndef QTEXTEDITSNIPPET_H
#define QTEXTEDITSNIPPET_H
#include <QTextEdit>
#include <QString>
#include <map>
class QTextEditSnippet : public QTextEdit {
Q_OBJECT
public:
explicit QTextEditSnippet(QWidget *parent = 0);
void createSnippets();
void setTabSize(int tabSize);
protected:
void keyPressEvent(QKeyEvent *e);
private:
std::map<std::string, QString> _snippets;
signals:
public slots:
};
#endif // QTEXTEDITSNIPPET_H

On y retrouve donc notre constructeur, une méthode createSnippets() qui sevira à initialiser la liste, une méthode pour gérer la taille des tabulations et enfin la fameuse méthode keyPressEvent.

Débutons avec le constructeur :

QTextEditSnippet::QTextEditSnippet(QWidget *parent) : QTextEdit(parent) {
createSnippets();
setTabSize(4);
}

On appelle le constructeur de la classe QTextEdit, on initialise notre map et on règle la taille d’une tabulation à 4 espaces. Regardons maintenant la fonction setTabSize() :

void QTextEditSnippet::setTabSize(int tabSize) {
QFont font;
QFontMetrics metrics(font);
setTabStopWidth(tabSize * metrics.width(' '));
}

La fonction prend en paramètre le nombre d’espaces dont sera composé une tabulation. On utilise un objet QFont, en utilisant le constructeur par défaut, l’objet va retourner la font utilisée par le système par défaut. On crée alors un objet QFontMetrics, afin de pouvoir utiliser la fonction setTabStopWidth() d’un QTextEdit, on précise alors que cette dernière prendra tabSize * la valeur d’un espace.

Passons à la fonction createSnippets() :

void QTextEditSnippet::createSnippets() {
QString html5 = "<!doctype html>\n<html lang=\"fr\">\n\t<head>\n\t\t <meta charset=\"utf-8\">\n\t\t <title>Titre</title>\n\t\t </head>\n\t<body>\n\t</body>\n</html>";
QString input = "<input type=\"text\" name=\"\" />";
...
_snippets["html"] = html5;
_snippets["input"] = input;
...
}

Je ne mets pas le détail car elle est simple, à l’index « html » on associe le code du QString html5, à savoir le magnifique code source d’une page html5.
Remarque : On utilise les formats \n pour faire les retours à la ligne et \t pour réaliser une tabulation.

Enfin, la dernière fonction, la plus « compliquée » keyPressEvent() :

void QTextEditSnippet::keyPressEvent(QKeyEvent *e) {
bool tabPress = false;
switch (e->key()) {
case Qt::Key_Tab:
tabPress = true;
break;
default:
QTextEdit::keyPressEvent(e);
break;
}
QTextCursor tc = textCursor();
tc.select(QTextCursor::WordUnderCursor);
QString word = tc.selectedText();
if(tabPress) {
if(_snippets.find(word.toStdString()) != _snippets.end()) {
tc.removeSelectedText();
tc.insertText(_snippets.find(word.toStdString())->second);
} else {
QTextEdit::keyPressEvent(e);
}
}
}

On réalise un switch sur l’event keypress qui correspond à l’appui d’une touche par l’utilisateur, si la touche appuyée est la touche tabulation, on passe un boolean à true, sinon on appelle l’action keyPressEvent de la classe QTextEdit, afin d’afficher les touches pressées (si vous ne le faites pas, les utilisateurs auront beau taper leurs textes, ils ne verront rien s’afficher !). On récupère avec la classe QTextCursor le mot précédent le pointeur de la souris grâce à la fonction select(). On stocke la chaîne dans un QString.

Enfin, on utilise la fonction find() de la structure map afin de rechercher le mot qui nous intéresse, si il n’est pas présent dans l’index, la fonction find retourne le pointeur de fin de liste, dans ce cas on appelle la fonction keyPressEvent afin d’effectuer une tabulation. Sinon on supprime le mot précédent et on affiche le code source ardemment désiré. Félicitation votre système de snippets fonctionne pleinement !

Ce qui nous donne donc pour notre fichier qtexteditsnippet.cpp au complet :

#include "qtexteditsnippet.h"
#include <QKeyEvent>
#include <QFont>
#include <QFontMetrics>
QTextEditSnippet::QTextEditSnippet(QWidget *parent) : QTextEdit(parent) {
createSnippets();
setTabSize(4);
}
void QTextEditSnippet::setTabSize(int tabSize) {
QFont font;
QFontMetrics metrics(font);
setTabStopWidth(tabSize * metrics.width(' '));
}
void QTextEditSnippet::createSnippets() {
QString html5 = "<!doctype html>\n<html lang=\"fr\">\n\t<head>\n\t\t <meta charset=\"utf-8\">\n\t\t <title>Titre</title>\n\t\t </head>\n\t<body>\n\t</body>\n</html>";
QString input = "<input type=\"text\" name=\"\" />";
QString strong = "<strong></strong>";
QString ul = "<ul>\n\t<li></li>\n</ul>";
QString img = "<img src=\"\" alt=\"\" />";
QString link = "<a href=\"\"></a>";
QString italic = "<em></em>";
QString css = "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css\" />";
QString js = "<script type=\"text/javascript\" src=\"js/main.js\"></script>";
QString textarea = "<textarea name=\"\" cols=\"30\" rows=\"10\"></textarea>";
QString select = "<select>\n\t<option value=\"\"></option>\n</select>";
QString form = "<form action=\"#\" method=\"post\"></form>";
_snippets["html"] = html5;
_snippets["input"] = input;
_snippets["strong"] = strong;
_snippets["ul"] = ul;
_snippets["img"] = img;
_snippets["a"] = link;
_snippets["em"] = italic;
_snippets["css"] = css;
_snippets["js"] = js;
_snippets["texta"] = textarea;
_snippets["select"] = select;
_snippets["form"] = form;
}
void QTextEditSnippet::keyPressEvent(QKeyEvent *e) {
bool tabPress = false;
switch (e->key()) {
case Qt::Key_Tab:
tabPress = true;
break;
default:
QTextEdit::keyPressEvent(e);
break;
}
QTextCursor tc = textCursor();
tc.select(QTextCursor::WordUnderCursor);
QString word = tc.selectedText();
if(tabPress) {
if(_snippets.find(word.toStdString()) != _snippets.end()) {
tc.removeSelectedText();
tc.insertText(_snippets.find(word.toStdString())->second);
} else {
QTextEdit::keyPressEvent(e);
}
}
}

Après il ne vous reste plus qu’à appeler notre classe et Chocapic ! Pour ceux qui voudraient aller plus loin et qui sont bloqués pour par exemple ajouter des numéros de ligne à votre qtextedit, vous pouvez aller voir sur mon github, je développe actuellement un petit éditeur de texte. C’est d’ailleurs celui-ci que j’ai utilisé pour la capture d’écran au début de l’article. Vous y trouverez des fonctions comme : la coloration syntaxique pour le html ou encore un système de preview du HTML.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *