Bonjour à tous.
Je cherche à colorer syntaxiquement certain mot d'un QTextEdit lorsqu'il sont rentrés, et de manière personnalisée. Comment-faire ?
Merci d'avance.
Version imprimable
Bonjour à tous.
Je cherche à colorer syntaxiquement certain mot d'un QTextEdit lorsqu'il sont rentrés, et de manière personnalisée. Comment-faire ?
Merci d'avance.
Salut,
Regarde ceci
http://qt.developpez.com/doc/latest/qsyntaxhighlighter/
et un exemple
http://qt.developpez.com/doc/latest/...axhighlighter/
Sinon il existe aussi la lib QScintilla, mais je ne connait pas trop.
salut, sinon tu peux aussi chercher sur le forum :
topic sur le forum parlant de QScintilla dans le cas d'une indentation automatique
Merci beaucoup.
Peut-on ajouter une marge de numéro de ligne sur un QTextEdit ?
Merci d'avance.
là comme ça je te dirais bien non je pense pas.
Il me semble Que QScintilla le fait
Merci, mais je n'ai que deux problème avec QScintilla :
-J'ai du mal avec la doc
-Je ne vois vraiment pas comment appliquer un lexer perso et non un lexer prédéfini.
Merci d'avance.
Salut, en cherchant un peu sur google "QTextEdit + line + numbers" je suis tombé sur un forum avec des bouts de code.
J'ai testé pour toi :
http://electronicsforfun.com/develop...ne_numbers.png
le lien :
Annotation of lines in a QTextEdit
Merci infiniment pour ta réponse
Avec QScintilla (et scintilla tout court), on peut soit utiliser un lexer prédéfinit qui est déjà compilé (par exemple pour cpp, bash etc) ou utiliser le "lexer in the container". Dans ce cas, scintilla demande à l'application d'appliquer elle même les styles à la volée. Pour sélectionner ce mode de lexer perso, il faut envoyer le message SCLEX_CONTAINER à scintilla avec
qui est définit dans QsciScintillaBase. Ensuite, QScintilla envoie le signalCode:SendScintilla(SCI_SETLEXER, SCLEX_CONTAINER)
quand necessaire. Tout est dans la doc de QScintilla et celle de scintilla, notamment ce petit exergue http://sphere.sourceforge.net/flik/d...ner_lexer.htmlCode:void QsciScintillaBase::SCN_STYLENEEDED(int position)
Tout est dans la doc... J'avoue que j'ai beaucoup de mal car les présentations sont trop générales et du coup j'avance très peu.
Quelqu'un saurait-il montrer un exemple tout bête "complet" qui mettrait en rouge les lignes commençant par # et tous les mots hum en bleu, et qui créerait des "folders" pour les commentaires de type "/* ... */" ? Avec ce type d'exemples, la prise en main serait plus rapide.
Mouais... Scintilla finalement c'est pas terrible et puis c'est un peu lourd.
Je suis revenu au bon vieux QTextEdit avec un QSyntaxHighlighter.
En cherchant un peu, et en manipulant beaucoup, je suis arrivé à faire des lignes 'rétractables' une marge avec des numéros et une marge pour mettre des marqueurs :
http://imagik.fr/thumb/321191.jpeg
Pour le code, 2 classes principales : CodeEdit et Lexer. Et comme je me suis (odieusement :oops: ) inspiré de Qt, une classe LineNumberArea (améliorée).
Voici le code : (je travail encore dessus) :
codeedit.h
codeedit.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 /* codeeditor.h ------------ Auteur: Crabe05 Description : Classe principale, elle permet de créer un widget basé sur QPlainTextEdit avec un 'Lexer' (illumination). Code sous licence LGPL. */ #ifndef CODEEDITOR_H #define CODEEDITOR_H //inclusions #include <QtGui> #include "lexer.h" //quelques données pour la largeur des marges... #define LNA_LEFT_MARGIN 10 #define LNA_RIGHT_MARGIN 5 #define MARKER_AREA_WIDTH 15 #define FOLDING_AREA_WIDTH 15 //pour l'auto-indentation inline QString getIndent(QString text) { QRegExp spaces("^(\\s*).*$"); if (spaces.indexIn(text) == -1) return ""; return spaces.cap(1); } //pré-déclaration de la classe LineNumberArea class LineNumberArea; //La classe principale, héritée de QPLainTextEdit //C'est un peu fait à l'arrache, il faut que je la retravaille class CodeEditor : public QPlainTextEdit { Q_OBJECT public: //cstr & dstr CodeEditor(QWidget *parent = 0); ~CodeEditor(); //bidouillage du langage void setLanguage(Lexer* lang); Lexer* language() const; // friend class LineNumberArea; public slots: //fonctions de bases : aller à, réduire, étendre void gotoLine(int l, bool select = false); void fold(int start, int end); void unfold(int start, int end); private slots: //trouver les lignes qui peuvent êtres réduites void findFoldableLines(); //auto-indentation void indent(); //pour les marges void updateLineNumberAreaWidth(int newBlockCount); void updateLineNumberArea(const QRect &, int); //enluminer la ligne actuelle void highlightCurrentLine(); protected: //pour les marges (bis) void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); int lineNumberAreaWidthPrimar(); //évènements void resizeEvent(QResizeEvent *event); void paintEvent(QPaintEvent* event); //(optionnel) en cas de bug, tout 'ré illuminer' void keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_U && event->modifiers() == Qt::ControlModifier) _lang->rehighlight(); QPlainTextEdit::keyPressEvent(event); } private: //fonctions internes pour LineNumberArea QMap<int,int> foldableLines() { return _foldableLines; } QMap<int,int> foldedLines() { return _foldedLines; } //pour les marges LineNumberArea *_lineNumberArea; QMap<int,int> _foldedLines; QMap<int,int> _foldableLines; //autres int _prevBlockNum; //numéro du block précédent Lexer* _lang; }; //classe qui permet d'afficher une marge class LineNumberArea : public QWidget { public: //cstr LineNumberArea(CodeEditor *editor) : QWidget(editor) { _codeEditor = editor; } QSize sizeHint() const { return QSize(_codeEditor->lineNumberAreaWidth(), 0); } //les marqueurs QList<int> markers() const { return _markers; } void drawMarker(QPainter* painter, QRect region) { painter->setBrush(QBrush(Qt::black)); painter->drawEllipse(QPoint(region.x() + region.width() / 2, region.y() + region.height() / 2), 5, 5); } //les lignes réductibles void drawFoldingItem(QPainter* painter, QRect region, bool expanded) { painter->setBrush(QBrush(Qt::white)); painter->drawRect(region.x() + region.width() / 2 - 5, region.y() + region.height() / 2 - 5, 10, 10); painter->drawLine(region.x() + region.width() / 2 - 3, region.y() + region.height() / 2, region.x() + region.width() / 2 + 3, region.y() + region.height() / 2); if (expanded) painter->drawLine(region.x() + region.width() / 2, region.y() + region.height() / 2 - 3, region.x() + region.width() / 2, region.y() + region.height() / 2 + 3); painter->drawLine(region.x() + region.width() / 2 + 1, region.y() + region.height() / 2 + 5, region.x() + region.width() / 2 + 1, region.bottom()); } protected: //évènements void paintEvent(QPaintEvent *event) { _codeEditor->lineNumberAreaPaintEvent(new QPaintEvent(rect())); } void mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { int line = _codeEditor->cursorForPosition(QPoint(width() + 1, event->pos().y())).block().blockNumber(); //si on est sur un numéro de ligne... if (event->pos().x() < _codeEditor->lineNumberAreaWidthPrimar()) { _codeEditor->gotoLine(line, true); } //si on est sur la marge des marqueurs... else if (event->pos().x() >= _codeEditor->lineNumberAreaWidthPrimar() && event->pos().x() < _codeEditor->lineNumberAreaWidthPrimar() + MARKER_AREA_WIDTH) { if (_markers.contains(line)) _markers.removeOne(line); else _markers.append(line); } //si on est sur la marge des réducteurs... else { if (_codeEditor->foldableLines().contains(line)) { if (_codeEditor->foldedLines().contains(line)) _codeEditor->unfold(line, _codeEditor->foldableLines()[line]); else _codeEditor->fold(line, _codeEditor->foldableLines()[line]); } } } update(); } private: //attributs CodeEditor *_codeEditor; QList<int> _markers; }; #endif //CODEEDITOR_H
lexer.hCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 /* codeeditor.cpp ------------ Auteur: Crabe05 Description : Implémentation de la classe principale Code sous licence LGPL. */ #include "codeedit.h" //cstr & dstr CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent) { _lang = NULL; _prevBlockNum = 0; _lineNumberArea = new LineNumberArea(this); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); updateLineNumberAreaWidth(0); highlightCurrentLine(); setLineWrapMode(NoWrap); setWordWrapMode(QTextOption::NoWrap); findFoldableLines(); } CodeEditor::~CodeEditor() { } //modification du lexer void CodeEditor::setLanguage(Lexer* lang) { _lang = lang; _lang->setDocument(document()); if (!_lang->initialized()) _lang->initialize(); setFont(_lang->font()); setTabStopWidth(fontMetrics().width(" ")); update(); connect(this, SIGNAL(blockCountChanged(int)), _lang, SLOT(rehighlight())); } Lexer* CodeEditor::language() const { return _lang; } //auto-indentation void CodeEditor::indent() { if (!textCursor().block().previous().isValid()) return; QTextBlock block = textCursor().block().previous(); QString indent = getIndent(block.text()); textCursor().movePosition(QTextCursor::StartOfLine); textCursor().insertText(indent); textCursor().movePosition(QTextCursor::EndOfLine); } //aler à void CodeEditor::gotoLine(int l, bool select) { QTextCursor tc = textCursor(); int currentLine = tc.block().blockNumber(); int offset = currentLine - l; if (offset > 0) for (int i = 0; i < offset; i++) tc.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor); else for (int i = 0; i < -offset; i++) tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor); if (select) tc.select(QTextCursor::LineUnderCursor); setTextCursor(tc); } //réduire void CodeEditor::fold(int start, int end) { _foldedLines.insert(start, end); for (int i = start + 1; i < end; i++) { document()->findBlockByNumber(i).setVisible(false); update(); } update(); //repaint(); resizeEvent(new QResizeEvent(QSize(0, 0), size())); } //étendre void CodeEditor::unfold(int start, int end) { if (!_foldedLines.keys().contains(start)) return; _foldedLines.remove(start); for (int i = start + 1; i < end; i++) { document()->findBlockByNumber(i).setVisible(true); update(); } update(); resizeEvent(new QResizeEvent(QSize(0, 0), size())); } //trouver les lignes réductibles en fonction du Lexer void CodeEditor::findFoldableLines() { if (_lang == NULL) return; _foldableLines.clear(); QStringList lines = toPlainText().split('\n'); for (int i = 0; i < lines.count(); i++) { QString line = lines[i]; foreach (Lexer::Block b, _lang->blocks()) { QRegExp sr = QRegExp(b.start); QRegExp er = QRegExp(b.end); if (sr.indexIn(line) != -1) { int start = i; int j = start; int encast = 0; if (er.indexIn(line) != -1) continue; while (!(er.indexIn(line) != -1 && encast == 0) && j < lines.count()) { // line = lines[j]; if (sr.indexIn(line) != -1) encast++; // if (er.indexIn(line) != -1) encast--; j++; } _foldableLines.insert(start, j - 1); } } } update(); } //largeur de la marge (en tout) int CodeEditor::lineNumberAreaWidth() { int space = lineNumberAreaWidthPrimar() + LNA_RIGHT_MARGIN + MARKER_AREA_WIDTH + FOLDING_AREA_WIDTH; return space; } //largeur de la marge des nombres int CodeEditor::lineNumberAreaWidthPrimar() { int digits = 1; int max = qMax(1, blockCount()); while (max >= 10) { max /= 10; ++digits; } int space = LNA_LEFT_MARGIN + fontMetrics().width(QLatin1Char('9')) * digits; return space; } //rafraîchire la largeur de la marge void CodeEditor::updateLineNumberAreaWidth(int newBlockCount) { if (newBlockCount > _prevBlockNum) { indent(); _prevBlockNum = newBlockCount; } setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } //rafraîchire la marge void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) { findFoldableLines(); if (dy) _lineNumberArea->scroll(0, dy); else _lineNumberArea->update(0, rect.y(), _lineNumberArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0); } //dessiner tout ça... void CodeEditor::paintEvent(QPaintEvent* event) { QPlainTextEdit::paintEvent(event); QMapIterator<int,int> it(_foldedLines); while (it.hasNext()) { it.next(); int bottom = (int)(blockBoundingGeometry(document()->findBlockByNumber(it.key())).translated(contentOffset()).top() + blockBoundingGeometry(document()->findBlockByNumber(it.key())).height()); QPainter painter(viewport()); painter.setPen(QPen(Qt::black)); painter.drawLine(0, bottom, width(), bottom); } } //lorsqu'on ajuste la taille... void CodeEditor::resizeEvent(QResizeEvent *e) { QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); _lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } //illuminer la ligne actuelle void CodeEditor::highlightCurrentLine() { QList<QTextEdit::ExtraSelection> extraSelections; if (!isReadOnly()) { QTextEdit::ExtraSelection selection; QColor lineColor = QColor(169, 169, 255, 50); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); extraSelections.append(selection); } setExtraSelections(extraSelections); update(); } //dessiner la marge void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(_lineNumberArea); painter.fillRect(event->rect(), QColor(228, 228, 228)); painter.setPen(QPen(QColor(243, 243, 243))); painter.setBrush(QColor(243, 243, 243)); painter.drawRect(_lineNumberArea->width() - FOLDING_AREA_WIDTH, event->rect().top(), _lineNumberArea->width(), event->rect().bottom()); QFont initialFont = painter.font(); QFont font(initialFont); font.setWeight(QFont::Bold); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + (int) blockBoundingRect(block).height(); QList<int> markers = _lineNumberArea->markers(); QStack<int> folding; while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { int number = blockNumber; if (textCursor().block().blockNumber() == number) painter.setFont(font); painter.setPen(Qt::black); painter.drawText(0, top, _lineNumberArea->width() - LNA_RIGHT_MARGIN - MARKER_AREA_WIDTH - FOLDING_AREA_WIDTH, fontMetrics().height(), Qt::AlignRight, QString::number(blockNumber + 1)); if (textCursor().block().blockNumber() == number) painter.setFont(initialFont); if (markers.contains(number)) _lineNumberArea->drawMarker(&painter, QRect(_lineNumberArea->width() - MARKER_AREA_WIDTH - FOLDING_AREA_WIDTH, top, MARKER_AREA_WIDTH, fontMetrics().height())); painter.setPen(QPen(Qt::black)); if (!folding.isEmpty() && _foldableLines.values().contains(number)) { painter.drawLine(_lineNumberArea->width() - FOLDING_AREA_WIDTH / 2, top + (bottom - top) / 2, _lineNumberArea->width(), top + (bottom - top) / 2); painter.drawLine(_lineNumberArea->width() - FOLDING_AREA_WIDTH / 2, top + (bottom - top) / 2, _lineNumberArea->width() - FOLDING_AREA_WIDTH / 2, top); folding.pop(); } if (!folding.isEmpty() && number > folding.top() && number < _foldableLines[folding.top()]) { painter.drawLine(_lineNumberArea->width() - FOLDING_AREA_WIDTH / 2, top, _lineNumberArea->width() - FOLDING_AREA_WIDTH / 2, bottom); } if (_foldableLines.keys().contains(number)) { if (_foldedLines.keys().contains(number)) { _lineNumberArea->drawFoldingItem(&painter, QRect(_lineNumberArea->width() - FOLDING_AREA_WIDTH, blockBoundingGeometry(block).translated(contentOffset()).top(), FOLDING_AREA_WIDTH, fontMetrics().height()), false); } else { _lineNumberArea->drawFoldingItem(&painter, QRect(_lineNumberArea->width() - FOLDING_AREA_WIDTH, blockBoundingGeometry(block).translated(contentOffset()).top(), FOLDING_AREA_WIDTH, fontMetrics().height()), true); } folding.push(number); } } block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); blockNumber++; } }
lexer.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 /* lexer.h ------------ Auteur: Crabe05 Description : Classe qui permet d'enluminer le code (Lexer). Basée sur QSyntaxHighlighter. Terminologie : Pour moi, - Une 'Entity' est un élément simple du type \bint\b, qui enluminera le mot 'int' si il n'est suivit ni précédé de rien, avec le style correspondant. - Un 'Block' est un élément qui possède un début et une fin comme \{ avec \} qui enluminera tout ce qui est contenu entre { et } avec le style correspondant. Code sous licence LGPL. */ #ifndef LANGUAGE_H #define LANGUAGE_H //inclusions #include <QSyntaxHighlighter> #include <QRegExp> #include <QString> #include <QVariant> #include <QTextCharFormat> #include <QTextDocument> //classe principale class Lexer: public QSyntaxHighlighter { public: //quelques structures, plus commode à utiliser struct Block { QString start; QString end; int style; }; struct Entity { QString pattern; int style; }; //pour l'instant ça ne sert à rien :p, mais pour l'instant... enum SyntaxType { SyntaxDefault = 0, SyntaxKeywords, SyntaxSingleLineGroup, SyntaxMultiLineGroup, SyntaxSymbol }; //énumération anonyme qui permet de s'y retrouver dans les styles... enum { Default = 0 }; //cstr & dstr Lexer(QString name, QTextDocument* document); Lexer(QString name); virtual ~Lexer() { } //infos générales QString name() const { return _name; } //initialisation virtual void initialize() = 0; //virtual pure bool initialized() const { return _init; } //font principale virtual QFont font() const { return _font; } //quelques getters... virtual QList<int> styles() const { return _styles; } virtual QTextCharFormat style(int num) const = 0; virtual QString styleName(int num) const = 0; virtual SyntaxType styleSyntaxType(int num) const = 0; virtual QList<Block> blocks() const { return _blocks; } virtual QList<Entity> entities() const { return _entities; } //THE fonction qui permet d'enluminer virtual void highlightBlock(const QString& text); //quelques fonctions qui rendent la vie plus facile... static QList<Entity> makeEntities(QStringList keywords, int style, bool autoBound = true, bool autoReplace = true); static QString autoBound(QString s, bool acceptSpaces = true, bool boundLeft = true, bool boundRight = true) { QString out; if (acceptSpaces) out += "\\s*"; if (boundLeft) out += "\\b"; out += s; if (boundRight) out += "\\b"; return out; } protected: //Idem virtual void highlightRegion(QString text, Block block); virtual void highlightEntity(QString text, Entity entity); void addBlock(Block b) { _blocks << b; } void addBlocks(QList<Block> b) { _blocks << b; } void addEntity(Entity e) { _entities << e; } void addEntities(QList<Entity> e) { _entities << e; } void setFont(QFont f) { _font = f; } void setInitialized(const bool i) { _init = i; } //attributs QString _name; QList<int> _styles; QList<Block> _blocks; QList<Entity> _entities; QFont _font; bool _init; }; #endif //LANGUAGE_H
Avec un p'tit exemple... disons... illuminer du C++ :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 /* lexer.cpp ------------ Auteur: Crabe05 Description : Implémentation de la classe Lexer Rappel : la classe Lexer est abstraite ! Code sous licence LGPL. */ #include "lexer.h" //cstr & dstr Lexer::Lexer(QString name, QTextDocument* document) : QSyntaxHighlighter(document) { _name = name; _init = false; } Lexer::Lexer(QString name) : QSyntaxHighlighter(new QObject()) { _name = name; _init = false; } //enlumination void Lexer::highlightBlock(const QString& text) { foreach (Entity e, entities()) highlightEntity(text, e); foreach (Block b, blocks()) highlightRegion(text, b); } //enlumination d'un block void Lexer::highlightRegion(QString text, Block block) { if (block.style == Default) return; int startId = 0; setCurrentBlockState(0); QRegExp start(block.start); QRegExp end(block.end); if (previousBlockState() != 1) startId = start.indexIn(text); while (startId >= 0) { int endId = end.indexIn(text, startId); int length; if (endId == -1) { setCurrentBlockState(1); length = text.length() - startId; } else { length = endId - startId + end.matchedLength(); } setFormat(startId, length, style(block.style)); startId = start.indexIn(text, startId + length); } } //enlumination d'une entitée void Lexer::highlightEntity(QString text, Entity entity) { if (entity.style == Default) return; QRegExp expression(entity.pattern); int index = expression.indexIn(text); while (index >= 0) { int length = expression.matchedLength(); setFormat(index, length, style(entity.style)); index = expression.indexIn(text, index + length); } } //fonction pratique qui permet de faire automatiquement des entitées avec une liste de mot-clés... QList<Lexer::Entity> Lexer::makeEntities(QStringList keywords, int style, bool autoBound, bool autoReplace) { QList<Entity> out; QStringList specKws; specKws << "\\" << "+" << "?" << "*" << "^" << "$" << "!" << "[" << "]" << "(" << ")" << "{" << "}" << "." << "|"; foreach (QString s, keywords) { Entity b; if (autoReplace) { foreach (QString exp, specKws) { if (s.contains(exp)) s.replace(exp, "\\" + exp); } } if (autoBound) b.pattern = "\\b" + s + "\\b"; else b.pattern = s; b.style = style; out << b; } return out; }
cpplexer.h
cpplexer.cppCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 /* cpplexer.h ------------ Auteur: Crabe05 Description : Un exemple : une classe qui illumine du C++ Code sous licence LGPL. */ #ifndef CPPLEXER_H #define CPPLEXER_H //inclusions #include "lexer.h" //classe principale class CppLexer: public Lexer { public: //Les divers styles... enum { Default = 0, /* Style par défaut [toujours présent]*/ Keywords = 1, /* Mots-clés */ Operators = 2, /* Opérateurs */ Number = 3, /* Nombres */ Comments = 4, /* Commentaires (simples) */ MComments = 5, /* Commentaires (multi-lignes) */ String = 6, /* Chaîne de caractères */ Character = 7, /* Caractère */ Preprocessor = 8, /* Pré-processeur */ }; //cstr & dstr CppLexer(QTextDocument* doc); CppLexer(); ~CppLexer() {} //fonction d'initialisation virtual void initialize(); //renseignement sur les styles virtual QTextCharFormat style(int num) const; virtual QString styleName(int num) const; virtual SyntaxType styleSyntaxType(int num) const; }; #endif
Voilà ! Si vous avez la moindre question, envoyez-moi un MP !Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 /* cpplexer.h ------------ Auteur: Crabe05 Description : Implémentation de la classe CppLexer Code sous licence LGPL. */ #include "cpplexer.h" //dstr & cstr CppLexer::CppLexer(QTextDocument* doc) : Lexer("C++", doc) { } CppLexer::CppLexer() : Lexer("C++") { } //initialisation : c'est ici que l'on définit les entitées et les blocks, ainsi que le style par défaut void CppLexer::initialize() { //style par défaut _font = QFont("Courier"); _font.setKerning(true); _font.setPointSize(10); //styles _styles << Default << Keywords << Operators << Number << Comments << String << Character;; //block entre {} Block block; block.start = "^[^{]*\\{.*$"; block.end = "^[^}]*\\}.*$"; block.style = Default; _blocks << block; //commentaires multi-lignes Block comments; comments.start = "/\\*"; comments.end = "\\*/"; comments.style = MComments; _blocks << comments; //mots-clés _entities << makeEntities(QStringList() << "and" << "and_eq" << "asm" << "auto" << "bitand" << "bitor" << "bool" << "break" << "case" << "catch" << "char" << "class" << "compl" << "const" << "const_cast" << "continue" << "default" << "delete" << "do" << "double" << "dynamic_cast" << "else" << "enum" << "explicit" << "export" << "extern" << "false" << "float" << "for" << "friend" << "goto" << "if" << "inline" << "int" << "long" << "mutable" << "namespace" << "new" << "not" << "not_eq" << "operator" << "or" << "or_eq" << "private" << "protected" << "public" << "register" << "reinterpret_cast" << "return" << "short" << "signed" << "sizeof" << "static" << "static_cast" << "struct" << "switch" << "size_t" << "template" << "this" << "throw" << "true" << "try" << "typedef" << "typeid" << "typename" << "union" << "unsigned" << "using" << "virtual" << "void" << "volatile" << "wchar_t" << "while" << "xor" << "xor_eq", Keywords); //numbers Entity num; num.pattern = "[0-9][^\\s]*"; num.style = Number; _entities << num; /** Remarque : cette méthode d'ajout est mieux : addEntity(num); **/ //operators _entities << makeEntities(QStringList() << "+" << "-" << "*" << "/" << "^" << "." << ">" << "<" << "(" << ")" << "[" << "]" << "%" << "!" << "=" << "{" << "}" << "&" << "," << "?" << ";" << ":" << "|", Operators, false); //commentaires Entity com; com.pattern = "//.*"; com.style = Comments; addEntity(com); //string Entity str; str.pattern = "\".*\""; str.style = String; addEntity(str); //char Entity chr; chr.pattern = "\'.\'"; chr.style = Character; addEntity(chr); //préprocesseur Entity preproc; preproc.pattern = "^\\s*#.*(\\\\\\n.*)*$"; preproc.style = Preprocessor; addEntity(preproc); setInitialized(true); } //retourne le style en fonction du nom QTextCharFormat CppLexer::style(int num) const { QTextCharFormat format; format.setFont(font()); switch (num) { case Keywords: format.setFontWeight(QFont::Bold); format.setForeground(QBrush(Qt::blue)); break; case Operators: format.setForeground(QBrush(Qt::red)); break; case Number: format.setForeground(QBrush(QColor(255, 128, 0))); break; case MComments: case Comments: format.setForeground(QBrush(QColor(0, 128, 0))); break; case String: case Character: format.setForeground(QBrush(QColor(128, 128, 128))); break; case Preprocessor: format.setForeground(QBrush(QColor(128, 64, 0))); break; default: break; } return format; } //nom du style QString CppLexer::styleName(int num) const { switch (num) { case Keywords: return "keywords"; case Operators: return "operators"; case Number: return "number"; case MComments: case Comments: return "comments"; case String: return "string"; case Character: return "character"; default: return "default"; } } //type de syntaxe (inutile... pour l'instant) CppLexer::SyntaxType CppLexer::styleSyntaxType(int num) const { switch(num) { case Keywords: return SyntaxKeywords; case Operators: return SyntaxSymbol; case Number: case Comments: case String: case Character: return SyntaxSingleLineGroup; case MComments: return SyntaxMultiLineGroup; default: return SyntaxDefault; } }
Crabe05.
Bonjour,
je suis très intéressé par ce code. J'aimerais l'utiliser avec PyQt pour un programme écrit en Python. D'où ma question : les parties "Numérotation" et "Folding" sont basés sur quels principes Qt.
Quelle partie veux-tu que je détail : l'affichage de la marge uniquement ou la recherche des données (numéros de ligne et lignes réductibles) ?
Bonsoir,
la partie dure dans ce que tu as fait c'est de mettre les numéros de ligne à gauche, et ensuite la gestion du "folding", ie du "déplier-replier". Une fois ceci expliqué, le reste est "facile".
Il faut savoir que je découpe ma marge en 5 parties :
- Une marge vide à gauche de 10px
- Une marge pour les nombres de largeur variable
- Une marge pour les marqueurs de 15px également
- Une marge pour les lignes réductibles de 15px encore
- Une marge vide à droite pour faire plus joli
Pour la marge numérotée :
On rafraîchit la taille de la marge à chaque fois que le nombre de block change (blockCountChanged()), on rafraîchit la marge elle-même lorsque c'est demandé (updateRequest()).
Pour changer la largeur de la marge, on utilise le slot updateLineNumberAreaWidth(int newBlockCount) [avec en paramètre le nouveau nombre de blocks]. La fonction modifie la marge du viewport du QPlainTextEdit (setViewportMargins(), une fonction de QAbstractScrollArea) avec une valeur pour left égale à la largeur totale de la marge (lineNumberAreaWidth())...
Justement, cette fonction : elle permet d'additionner toutes les largeurs des marges (réduction, gauche, droite, marqueurs, ...) avec la largeur de la marge numérotée donnée par lineNumberAreaWidthPrimar(), qui calcul la largeur de ladite marge comme suit :
En ce qui concerne updateLineNumberArea(QRect r, int dy), elle permet d'afficher les bons numéros en fonction du scroll de l'éditeur (dy) et de sa taille (r) :Code:
1
2
3
4
5
6
7
8 Soit d le nombre de chiffres Soit m le maximum entre 1 et le nombre de blocks [blockCount()] Tant que m >= 10: m -> m / 10 d -> d + 1 Fin. Renvoyer [marge de gauche] + d * [largeur du caractère '9']
Si dy > 0, on scroll la marge au même endroit avec scroll() de QWidget, sinon on se contente de rafraîchir la marge... Enfin, si le viewport est trop grand (que le viewport de la marge peut contenir celui de l'éditeur), on rafraîchit la largeur de la marge.
Sinon, pour dessiner la marge, on passe par la fonction lineNumberAreaPaintEvent(), détaillée plus loin.
Pour le folding:
La première étape consiste à trouver quelles lignes sont foldables, grâce à findFoldablesLines(), qui ne fait que chercher les débuts et fins de blocks, répertoriées dans le Lexer (cf: lexer.h, fonction blocks()), en faisant attention aux blocks imbriqués...
La fonction remplie l'attribut _foldableLines, qui n'est autre qu'un QMap<int,int>, qui associe à une ligne qui commence le block la ligne qui le termine. (Ce map va nous être utile dans la fonction d'affichage).
Par ailleurs, lorsque l'on clique sur la marge dans le secteur approprié, et que la ligne où on a cliqué existe dans le map _foldableLines, alors on ajoute à un autre QMap<int, int>, _foldedLines, qui regroupe les lignes réduites (si les lignes exstent déjà dans _foldedLines, on les supprime).
La fameuse fonction lineNumberAreaPaintEvent():
C'est THE gros morceau :
Elle commence par remplir la marge avec une couleur, et à créer trois variables : une pour le numéro du block, une pour la position en haut, et une autre pour en bas, ainsi qu'une QStack<int> qui contiendra les lignes réduites.
Pour chaque block valide et visible, on dessine le numéro de ligne; si la pile n'est pas vide, et que la ligne actuellement dessinée est une ligne de fin de block, on dessine un angle droit; si la pile n'est pas vide, que le numéro du block est supérieur au dessus de la pile et inférieur à la ligne de fin correspondante dans _foldableLines (que la ligne est bien dans le block en fait), on dessine une bar verticale; enfin, si la ligne actuellement dessinée est une ligne de départ, on dessine un + ou un - en fonction de si la ligne est contenue dans _foldedLines Ensuite, on avance toute les variables...
Pour la ligne qui apparaît entre les blocks réduits, tout se passe dans le paintEvent() de CodeEdit : pour chaque ligne de _foldedLines, on dessine une ligne horizontale en bas de la ligne de départ...
Et voilà... Si tu as une question, n'hésite pas !
Remarque importante : en refaisant quelques essais, je me suis aperçu d'un bug avec les blocks imbriqués réduits :
En effet, si l'on réduit un block (ex: le 3), puis que l'on réduit un block supérieur (le 2), deux lignes sont tracées au lieu d'une seule, et lorsqu'on étend le block réduit (2), le block inférieur (3) est mal détendu. Je vais travailler sur ce bug, et vous tiendrais au courent des modif' :) .Code:
1
2
3
4
5
6
7 {//(1) {//(2) {//(3) } } }
Merci pour ces précisions qui permettent d'y voir un peu plus clair.
:ccool:
Si je trouve du temps libre, j'essaierais de porter ton code en Python si c'est faisable. Si j'y arrive, je mettrais un lien ici.