source: trunk/src/gui/text/qsyntaxhighlighter.cpp@ 624

Last change on this file since 624 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 20.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsyntaxhighlighter.h"
43
44#ifndef QT_NO_SYNTAXHIGHLIGHTER
45#include <private/qobject_p.h>
46#include <qtextdocument.h>
47#include <private/qtextdocument_p.h>
48#include <qtextlayout.h>
49#include <qpointer.h>
50#include <qtextobject.h>
51#include <qtextcursor.h>
52#include <qdebug.h>
53#include <qtextedit.h>
54#include <qtimer.h>
55
56QT_BEGIN_NAMESPACE
57
58class QSyntaxHighlighterPrivate : public QObjectPrivate
59{
60 Q_DECLARE_PUBLIC(QSyntaxHighlighter)
61public:
62 inline QSyntaxHighlighterPrivate() : rehighlightPending(false) {}
63
64 QPointer<QTextDocument> doc;
65
66 void _q_reformatBlocks(int from, int charsRemoved, int charsAdded);
67 void reformatBlock(QTextBlock block);
68
69 inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) {
70 QObject::disconnect(doc, SIGNAL(contentsChange(int,int,int)),
71 q_func(), SLOT(_q_reformatBlocks(int,int,int)));
72 cursor.beginEditBlock();
73 int from = cursor.position();
74 cursor.movePosition(operation);
75 _q_reformatBlocks(from, 0, cursor.position() - from);
76 cursor.endEditBlock();
77 QObject::connect(doc, SIGNAL(contentsChange(int,int,int)),
78 q_func(), SLOT(_q_reformatBlocks(int,int,int)));
79 }
80
81 inline void _q_delayedRehighlight() {
82 if (!rehighlightPending)
83 return;
84 rehighlightPending = false;
85 q_func()->rehighlight();
86 return;
87 }
88
89 void applyFormatChanges();
90 QVector<QTextCharFormat> formatChanges;
91 QTextBlock currentBlock;
92 bool rehighlightPending;
93};
94
95void QSyntaxHighlighterPrivate::applyFormatChanges()
96{
97 QTextLayout *layout = currentBlock.layout();
98
99 QList<QTextLayout::FormatRange> ranges = layout->additionalFormats();
100
101 const int preeditAreaStart = layout->preeditAreaPosition();
102 const int preeditAreaLength = layout->preeditAreaText().length();
103
104 QList<QTextLayout::FormatRange>::Iterator it = ranges.begin();
105 while (it != ranges.end()) {
106 if (it->start >= preeditAreaStart
107 && it->start + it->length <= preeditAreaStart + preeditAreaLength)
108 ++it;
109 else
110 it = ranges.erase(it);
111 }
112
113 QTextCharFormat emptyFormat;
114
115 QTextLayout::FormatRange r;
116 r.start = r.length = -1;
117
118 int i = 0;
119 while (i < formatChanges.count()) {
120
121 while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat)
122 ++i;
123
124 if (i >= formatChanges.count())
125 break;
126
127 r.start = i;
128 r.format = formatChanges.at(i);
129
130 while (i < formatChanges.count() && formatChanges.at(i) == r.format)
131 ++i;
132
133 if (i >= formatChanges.count())
134 break;
135
136 r.length = i - r.start;
137
138 if (r.start >= preeditAreaStart) {
139 r.start += preeditAreaLength;
140 } else if (r.start + r.length >= preeditAreaStart) {
141 r.length += preeditAreaLength;
142 }
143
144 ranges << r;
145 r.start = r.length = -1;
146 }
147
148 if (r.start != -1) {
149 r.length = formatChanges.count() - r.start;
150
151 if (r.start >= preeditAreaStart) {
152 r.start += preeditAreaLength;
153 } else if (r.start + r.length >= preeditAreaStart) {
154 r.length += preeditAreaLength;
155 }
156
157 ranges << r;
158 }
159
160 layout->setAdditionalFormats(ranges);
161}
162
163void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
164{
165 Q_UNUSED(charsRemoved);
166 rehighlightPending = false;
167
168 QTextBlock block = doc->findBlock(from);
169 if (!block.isValid())
170 return;
171
172 int endPosition;
173 QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0));
174 if (lastBlock.isValid())
175 endPosition = lastBlock.position() + lastBlock.length();
176 else
177 endPosition = doc->docHandle()->length();
178
179 bool forceHighlightOfNextBlock = false;
180
181 while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
182 const int stateBeforeHighlight = block.userState();
183
184 reformatBlock(block);
185
186 forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight);
187
188 block = block.next();
189 }
190
191 formatChanges.clear();
192}
193
194void QSyntaxHighlighterPrivate::reformatBlock(QTextBlock block)
195{
196 Q_Q(QSyntaxHighlighter);
197
198 Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively");
199
200 currentBlock = block;
201 QTextBlock previous = block.previous();
202
203 formatChanges.fill(QTextCharFormat(), block.length() - 1);
204 q->highlightBlock(block.text());
205 applyFormatChanges();
206
207 doc->markContentsDirty(block.position(), block.length());
208
209 currentBlock = QTextBlock();
210}
211
212/*!
213 \class QSyntaxHighlighter
214 \reentrant
215
216 \brief The QSyntaxHighlighter class allows you to define syntax
217 highlighting rules, and in addition you can use the class to query
218 a document's current formatting or user data.
219
220 \since 4.1
221
222 \ingroup richtext-processing
223
224 The QSyntaxHighlighter class is a base class for implementing
225 QTextEdit syntax highlighters. A syntax highligher automatically
226 highlights parts of the text in a QTextEdit, or more generally in
227 a QTextDocument. Syntax highlighters are often used when the user
228 is entering text in a specific format (for example source code)
229 and help the user to read the text and identify syntax errors.
230
231 To provide your own syntax highlighting, you must subclass
232 QSyntaxHighlighter and reimplement highlightBlock().
233
234 When you create an instance of your QSyntaxHighlighter subclass,
235 pass it the QTextEdit or QTextDocument that you want the syntax
236 highlighting to be applied to. For example:
237
238 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 0
239
240 After this your highlightBlock() function will be called
241 automatically whenever necessary. Use your highlightBlock()
242 function to apply formatting (e.g. setting the font and color) to
243 the text that is passed to it. QSyntaxHighlighter provides the
244 setFormat() function which applies a given QTextCharFormat on
245 the current text block. For example:
246
247 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 1
248
249 Some syntaxes can have constructs that span several text
250 blocks. For example, a C++ syntax highlighter should be able to
251 cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
252 these cases it is necessary to know the end state of the previous
253 text block (e.g. "in comment").
254
255 Inside your highlightBlock() implementation you can query the end
256 state of the previous text block using the previousBlockState()
257 function. After parsing the block you can save the last state
258 using setCurrentBlockState().
259
260 The currentBlockState() and previousBlockState() functions return
261 an int value. If no state is set, the returned value is -1. You
262 can designate any other value to identify any given state using
263 the setCurrentBlockState() function. Once the state is set the
264 QTextBlock keeps that value until it is set set again or until the
265 corresponding paragraph of text is deleted.
266
267 For example, if you're writing a simple C++ syntax highlighter,
268 you might designate 1 to signify "in comment":
269
270 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 2
271
272 In the example above, we first set the current block state to
273 0. Then, if the previous block ended within a comment, we higlight
274 from the beginning of the current block (\c {startIndex =
275 0}). Otherwise, we search for the given start expression. If the
276 specified end expression cannot be found in the text block, we
277 change the current block state by calling setCurrentBlockState(),
278 and make sure that the rest of the block is higlighted.
279
280 In addition you can query the current formatting and user data
281 using the format() and currentBlockUserData() functions
282 respectively. You can also attach user data to the current text
283 block using the setCurrentBlockUserData() function.
284 QTextBlockUserData can be used to store custom settings. In the
285 case of syntax highlighting, it is in particular interesting as
286 cache storage for information that you may figure out while
287 parsing the paragraph's text. For an example, see the
288 setCurrentBlockUserData() documentation.
289
290 \sa QTextEdit, {Syntax Highlighter Example}
291*/
292
293/*!
294 Constructs a QSyntaxHighlighter with the given \a parent.
295*/
296QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent)
297 : QObject(*new QSyntaxHighlighterPrivate, parent)
298{
299}
300
301/*!
302 Constructs a QSyntaxHighlighter and installs it on \a parent.
303 The specified QTextDocument also becomes the owner of the
304 QSyntaxHighlighter.
305*/
306QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent)
307 : QObject(*new QSyntaxHighlighterPrivate, parent)
308{
309 setDocument(parent);
310}
311
312/*!
313 Constructs a QSyntaxHighlighter and installs it on \a parent 's
314 QTextDocument. The specified QTextEdit also becomes the owner of
315 the QSyntaxHighlighter.
316*/
317QSyntaxHighlighter::QSyntaxHighlighter(QTextEdit *parent)
318 : QObject(*new QSyntaxHighlighterPrivate, parent)
319{
320 setDocument(parent->document());
321}
322
323/*!
324 Destructor. Uninstalls this syntax highlighter from the text document.
325*/
326QSyntaxHighlighter::~QSyntaxHighlighter()
327{
328 setDocument(0);
329}
330
331/*!
332 Installs the syntax highlighter on the given QTextDocument \a doc.
333 A QSyntaxHighlighter can only be used with one document at a time.
334*/
335void QSyntaxHighlighter::setDocument(QTextDocument *doc)
336{
337 Q_D(QSyntaxHighlighter);
338 if (d->doc) {
339 disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
340 this, SLOT(_q_reformatBlocks(int,int,int)));
341
342 QTextCursor cursor(d->doc);
343 cursor.beginEditBlock();
344 for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next())
345 blk.layout()->clearAdditionalFormats();
346 cursor.endEditBlock();
347 }
348 d->doc = doc;
349 if (d->doc) {
350 connect(d->doc, SIGNAL(contentsChange(int,int,int)),
351 this, SLOT(_q_reformatBlocks(int,int,int)));
352 QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
353 d->rehighlightPending = true;
354 }
355}
356
357/*!
358 Returns the QTextDocument on which this syntax highlighter is
359 installed.
360*/
361QTextDocument *QSyntaxHighlighter::document() const
362{
363 Q_D(const QSyntaxHighlighter);
364 return d->doc;
365}
366
367/*!
368 \since 4.2
369
370 Reapplies the highlighting to the whole document.
371
372 \sa rehighlightBlock()
373*/
374void QSyntaxHighlighter::rehighlight()
375{
376 Q_D(QSyntaxHighlighter);
377 if (!d->doc)
378 return;
379
380 QTextCursor cursor(d->doc);
381 d->rehighlight(cursor, QTextCursor::End);
382}
383
384/*!
385 \since 4.6