DirectWrite font engine: don't leak the font table buffer
[profile/ivi/qtbase.git] / src / gui / text / qsyntaxhighlighter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
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 <qtimer.h>
54
55 QT_BEGIN_NAMESPACE
56
57 class QSyntaxHighlighterPrivate : public QObjectPrivate
58 {
59     Q_DECLARE_PUBLIC(QSyntaxHighlighter)
60 public:
61     inline QSyntaxHighlighterPrivate()
62         : rehighlightPending(false), inReformatBlocks(false)
63     {}
64
65     QPointer<QTextDocument> doc;
66
67     void _q_reformatBlocks(int from, int charsRemoved, int charsAdded);
68     void reformatBlocks(int from, int charsRemoved, int charsAdded);
69     void reformatBlock(const QTextBlock &block);
70
71     inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) {
72         inReformatBlocks = true;
73         cursor.beginEditBlock();
74         int from = cursor.position();
75         cursor.movePosition(operation);
76         reformatBlocks(from, 0, cursor.position() - from);
77         cursor.endEditBlock();
78         inReformatBlocks = false;
79     }
80
81     inline void _q_delayedRehighlight() {
82         if (!rehighlightPending)
83             return;
84         rehighlightPending = false;
85         q_func()->rehighlight();
86     }
87
88     void applyFormatChanges();
89     QVector<QTextCharFormat> formatChanges;
90     QTextBlock currentBlock;
91     bool rehighlightPending;
92     bool inReformatBlocks;
93 };
94
95 void QSyntaxHighlighterPrivate::applyFormatChanges()
96 {
97     bool formatsChanged = false;
98
99     QTextLayout *layout = currentBlock.layout();
100
101     QList<QTextLayout::FormatRange> ranges = layout->additionalFormats();
102
103     const int preeditAreaStart = layout->preeditAreaPosition();
104     const int preeditAreaLength = layout->preeditAreaText().length();
105
106     if (preeditAreaLength != 0) {
107         QList<QTextLayout::FormatRange>::Iterator it = ranges.begin();
108         while (it != ranges.end()) {
109             if (it->start >= preeditAreaStart
110                 && it->start + it->length <= preeditAreaStart + preeditAreaLength) {
111                 ++it;
112             } else {
113                 it = ranges.erase(it);
114                 formatsChanged = true;
115             }
116         }
117     } else if (!ranges.isEmpty()) {
118         ranges.clear();
119         formatsChanged = true;
120     }
121
122     QTextCharFormat emptyFormat;
123
124     QTextLayout::FormatRange r;
125     r.start = -1;
126
127     int i = 0;
128     while (i < formatChanges.count()) {
129
130         while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat)
131             ++i;
132
133         if (i >= formatChanges.count())
134             break;
135
136         r.start = i;
137         r.format = formatChanges.at(i);
138
139         while (i < formatChanges.count() && formatChanges.at(i) == r.format)
140             ++i;
141
142         if (i >= formatChanges.count())
143             break;
144
145         r.length = i - r.start;
146
147         if (preeditAreaLength != 0) {
148             if (r.start >= preeditAreaStart)
149                 r.start += preeditAreaLength;
150             else if (r.start + r.length >= preeditAreaStart)
151                 r.length += preeditAreaLength;
152         }
153
154         ranges << r;
155         formatsChanged = true;
156         r.start = -1;
157     }
158
159     if (r.start != -1) {
160         r.length = formatChanges.count() - r.start;
161
162         if (preeditAreaLength != 0) {
163             if (r.start >= preeditAreaStart)
164                 r.start += preeditAreaLength;
165             else if (r.start + r.length >= preeditAreaStart)
166                 r.length += preeditAreaLength;
167         }
168
169         ranges << r;
170         formatsChanged = true;
171     }
172
173     if (formatsChanged) {
174         layout->setAdditionalFormats(ranges);
175         doc->markContentsDirty(currentBlock.position(), currentBlock.length());
176     }
177 }
178
179 void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
180 {
181     if (!inReformatBlocks)
182         reformatBlocks(from, charsRemoved, charsAdded);
183 }
184
185 void QSyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded)
186 {
187     rehighlightPending = false;
188
189     QTextBlock block = doc->findBlock(from);
190     if (!block.isValid())
191         return;
192
193     int endPosition;
194     QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0));
195     if (lastBlock.isValid())
196         endPosition = lastBlock.position() + lastBlock.length();
197     else
198         endPosition = doc->docHandle()->length();
199
200     bool forceHighlightOfNextBlock = false;
201
202     while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
203         const int stateBeforeHighlight = block.userState();
204
205         reformatBlock(block);
206
207         forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight);
208
209         block = block.next();
210     }
211
212     formatChanges.clear();
213 }
214
215 void QSyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block)
216 {
217     Q_Q(QSyntaxHighlighter);
218
219     Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively");
220
221     currentBlock = block;
222
223     formatChanges.fill(QTextCharFormat(), block.length() - 1);
224     q->highlightBlock(block.text());
225     applyFormatChanges();
226
227     currentBlock = QTextBlock();
228 }
229
230 /*!
231     \class QSyntaxHighlighter
232     \reentrant
233     \inmodule QtGui
234
235     \brief The QSyntaxHighlighter class allows you to define syntax
236     highlighting rules, and in addition you can use the class to query
237     a document's current formatting or user data.
238
239     \since 4.1
240
241     \ingroup richtext-processing
242
243     The QSyntaxHighlighter class is a base class for implementing
244     QTextEdit syntax highlighters.  A syntax highligher automatically
245     highlights parts of the text in a QTextEdit, or more generally in
246     a QTextDocument. Syntax highlighters are often used when the user
247     is entering text in a specific format (for example source code)
248     and help the user to read the text and identify syntax errors.
249
250     To provide your own syntax highlighting, you must subclass
251     QSyntaxHighlighter and reimplement highlightBlock().
252
253     When you create an instance of your QSyntaxHighlighter subclass,
254     pass it the QTextEdit or QTextDocument that you want the syntax
255     highlighting to be applied to. For example:
256
257     \snippet code/src_gui_text_qsyntaxhighlighter.cpp 0
258
259     After this your highlightBlock() function will be called
260     automatically whenever necessary. Use your highlightBlock()
261     function to apply formatting (e.g. setting the font and color) to
262     the text that is passed to it. QSyntaxHighlighter provides the
263     setFormat() function which applies a given QTextCharFormat on
264     the current text block. For example:
265
266     \snippet code/src_gui_text_qsyntaxhighlighter.cpp 1
267
268     Some syntaxes can have constructs that span several text
269     blocks. For example, a C++ syntax highlighter should be able to
270     cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
271     these cases it is necessary to know the end state of the previous
272     text block (e.g. "in comment").
273
274     Inside your highlightBlock() implementation you can query the end
275     state of the previous text block using the previousBlockState()
276     function. After parsing the block you can save the last state
277     using setCurrentBlockState().
278
279     The currentBlockState() and previousBlockState() functions return
280     an int value. If no state is set, the returned value is -1. You
281     can designate any other value to identify any given state using
282     the setCurrentBlockState() function. Once the state is set the
283     QTextBlock keeps that value until it is set set again or until the
284     corresponding paragraph of text is deleted.
285
286     For example, if you're writing a simple C++ syntax highlighter,
287     you might designate 1 to signify "in comment":
288
289     \snippet code/src_gui_text_qsyntaxhighlighter.cpp 2
290
291     In the example above, we first set the current block state to
292     0. Then, if the previous block ended within a comment, we higlight
293     from the beginning of the current block (\c {startIndex =
294     0}). Otherwise, we search for the given start expression. If the
295     specified end expression cannot be found in the text block, we
296     change the current block state by calling setCurrentBlockState(),
297     and make sure that the rest of the block is higlighted.
298
299     In addition you can query the current formatting and user data
300     using the format() and currentBlockUserData() functions
301     respectively. You can also attach user data to the current text
302     block using the setCurrentBlockUserData() function.
303     QTextBlockUserData can be used to store custom settings. In the
304     case of syntax highlighting, it is in particular interesting as
305     cache storage for information that you may figure out while
306     parsing the paragraph's text. For an example, see the
307     setCurrentBlockUserData() documentation.
308
309     \sa QTextEdit, {Syntax Highlighter Example}
310 */
311
312 /*!
313     Constructs a QSyntaxHighlighter with the given \a parent.
314
315     If the parent is a QTextEdit, it installs the syntaxhighlighter on the
316     parents document. The specified QTextEdit also becomes the owner of
317     the QSyntaxHighlighter.
318 */
319 QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent)
320     : QObject(*new QSyntaxHighlighterPrivate, parent)
321 {
322     if (parent->inherits("QTextEdit")) {
323         QTextDocument *doc = qobject_cast<QTextDocument *>(parent->property("document").value<QObject *>());
324         if (doc)
325             setDocument(doc);
326     }
327 }
328
329 /*!
330     Constructs a QSyntaxHighlighter and installs it on \a parent.
331     The specified QTextDocument also becomes the owner of the
332     QSyntaxHighlighter.
333 */
334 QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent)
335     : QObject(*new QSyntaxHighlighterPrivate, parent)
336 {
337     setDocument(parent);
338 }
339
340 /*!
341     Destructor. Uninstalls this syntax highlighter from the text document.
342 */
343 QSyntaxHighlighter::~QSyntaxHighlighter()
344 {
345     setDocument(0);
346 }
347
348 /*!
349     Installs the syntax highlighter on the given QTextDocument \a doc.
350     A QSyntaxHighlighter can only be used with one document at a time.
351 */
352 void QSyntaxHighlighter::setDocument(QTextDocument *doc)
353 {
354     Q_D(QSyntaxHighlighter);
355     if (d->doc) {
356         disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
357                    this, SLOT(_q_reformatBlocks(int,int,int)));
358
359         QTextCursor cursor(d->doc);
360         cursor.beginEditBlock();
361         for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next())
362             blk.layout()->clearAdditionalFormats();
363         cursor.endEditBlock();
364     }
365     d->doc = doc;
366     if (d->doc) {
367         connect(d->doc, SIGNAL(contentsChange(int,int,int)),
368                 this, SLOT(_q_reformatBlocks(int,int,int)));
369         d->rehighlightPending = true;
370         QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
371     }
372 }
373
374 /*!
375     Returns the QTextDocument on which this syntax highlighter is
376     installed.
377 */
378 QTextDocument *QSyntaxHighlighter::document() const
379 {
380     Q_D(const QSyntaxHighlighter);
381     return d->doc;
382 }
383
384 /*!
385     \since 4.2
386
387     Reapplies the highlighting to the whole document.
388
389     \sa rehighlightBlock()
390 */
391 void QSyntaxHighlighter::rehighlight()
392 {
393     Q_D(QSyntaxHighlighter);
394     if (!d->doc)
395         return;
396
397     QTextCursor cursor(d->doc);
398     d->rehighlight(cursor, QTextCursor::End);
399 }
400
401 /*!
402     \since 4.6
403
404     Reapplies the highlighting to the given QTextBlock \a block.
405     
406     \sa rehighlight()
407 */
408 void QSyntaxHighlighter::rehighlightBlock(const QTextBlock &block)
409 {
410     Q_D(QSyntaxHighlighter);
411     if (!d->doc || !block.isValid() || block.document() != d->doc)
412         return;
413
414     const bool rehighlightPending = d->rehighlightPending;
415
416     QTextCursor cursor(block);
417     d->rehighlight(cursor, QTextCursor::EndOfBlock);
418
419     if (rehighlightPending)
420         d->rehighlightPending = rehighlightPending;
421 }
422
423 /*!
424     \fn void QSyntaxHighlighter::highlightBlock(const QString &text)
425
426     Highlights the given text block. This function is called when
427     necessary by the rich text engine, i.e. on text blocks which have
428     changed.
429
430     To provide your own syntax highlighting, you must subclass
431     QSyntaxHighlighter and reimplement highlightBlock(). In your
432     reimplementation you should parse the block's \a text and call
433     setFormat() as often as necessary to apply any font and color
434     changes that you require. For example:
435
436     \snippet code/src_gui_text_qsyntaxhighlighter.cpp 3
437
438     Some syntaxes can have constructs that span several text
439     blocks. For example, a C++ syntax highlighter should be able to
440     cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
441     these cases it is necessary to know the end state of the previous
442     text block (e.g. "in comment").
443
444     Inside your highlightBlock() implementation you can query the end
445     state of the previous text block using the previousBlockState()
446     function. After parsing the block you can save the last state
447     using setCurrentBlockState().
448
449     The currentBlockState() and previousBlockState() functions return
450     an int value. If no state is set, the returned value is -1. You
451     can designate any other value to identify any given state using
452     the setCurrentBlockState() function. Once the state is set the
453     QTextBlock keeps that value until it is set set again or until the
454     corresponding paragraph of text gets deleted.
455
456     For example, if you're writing a simple C++ syntax highlighter,
457     you might designate 1 to signify "in comment". For a text block
458     that ended in the middle of a comment you'd set 1 using
459     setCurrentBlockState, and for other paragraphs you'd set 0.
460     In your parsing code if the return value of previousBlockState()
461     is 1, you would highlight the text as a C++ comment until you
462     reached the closing \c{*}\c{/}.
463
464     \sa previousBlockState(), setFormat(), setCurrentBlockState()
465 */
466
467 /*!
468     This function is applied to the syntax highlighter's current text
469     block (i.e. the text that is passed to the highlightBlock()
470     function).
471
472     The specified \a format is applied to the text from the \a start
473     position for a length of \a count characters (if \a count is 0,
474     nothing is done). The formatting properties set in \a format are
475     merged at display time with the formatting information stored
476     directly in the document, for example as previously set with
477     QTextCursor's functions. Note that the document itself remains
478     unmodified by the format set through this function.
479
480     \sa format(), highlightBlock()
481 */
482 void QSyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format)
483 {
484     Q_D(QSyntaxHighlighter);
485     if (start < 0 || start >= d->formatChanges.count())
486         return;
487
488     const int end = qMin(start + count, d->formatChanges.count());
489     for (int i = start; i < end; ++i)
490         d->formatChanges[i] = format;
491 }
492
493 /*!
494     \overload
495
496     The specified \a color is applied to the current text block from
497     the \a start position for a length of \a count characters.
498
499     The other attributes of the current text block, e.g. the font and
500     background color, are reset to default values.
501
502     \sa format(), highlightBlock()
503 */
504 void QSyntaxHighlighter::setFormat(int start, int count, const QColor &color)
505 {
506     QTextCharFormat format;
507     format.setForeground(color);
508     setFormat(start, count, format);
509 }
510
511 /*!
512     \overload
513
514     The specified \a font is applied to the current text block from
515     the \a start position for a length of \a count characters.
516
517     The other attributes of the current text block, e.g. the font and
518     background color, are reset to default values.
519
520     \sa format(), highlightBlock()
521 */
522 void QSyntaxHighlighter::setFormat(int start, int count, const QFont &font)
523 {
524     QTextCharFormat format;
525     format.setFont(font);
526     setFormat(start, count, format);
527 }
528
529 /*!
530     \fn QTextCharFormat QSyntaxHighlighter::format(int position) const
531
532     Returns the format at \a position inside the syntax highlighter's
533     current text block.
534 */
535 QTextCharFormat QSyntaxHighlighter::format(int pos) const
536 {
537     Q_D(const QSyntaxHighlighter);
538     if (pos < 0 || pos >= d->formatChanges.count())
539         return QTextCharFormat();
540     return d->formatChanges.at(pos);
541 }
542
543 /*!
544     Returns the end state of the text block previous to the
545     syntax highlighter's current block. If no value was
546     previously set, the returned value is -1.
547
548     \sa highlightBlock(), setCurrentBlockState()
549 */
550 int QSyntaxHighlighter::previousBlockState() const
551 {
552     Q_D(const QSyntaxHighlighter);
553     if (!d->currentBlock.isValid())
554         return -1;
555
556     const QTextBlock previous = d->currentBlock.previous();
557     if (!previous.isValid())
558         return -1;
559
560     return previous.userState();
561 }
562
563 /*!
564     Returns the state of the current text block. If no value is set,
565     the returned value is -1.
566 */
567 int QSyntaxHighlighter::currentBlockState() const
568 {
569     Q_D(const QSyntaxHighlighter);
570     if (!d->currentBlock.isValid())
571         return -1;
572
573     return d->currentBlock.userState();
574 }
575
576 /*!
577     Sets the state of the current text block to \a newState.
578
579     \sa highlightBlock()
580 */
581 void QSyntaxHighlighter::setCurrentBlockState(int newState)
582 {
583     Q_D(QSyntaxHighlighter);
584     if (!d->currentBlock.isValid())
585         return;
586
587     d->currentBlock.setUserState(newState);
588 }
589
590 /*!
591     Attaches the given \a data to the current text block.  The
592     ownership is passed to the underlying text document, i.e. the
593     provided QTextBlockUserData object will be deleted if the
594     corresponding text block gets deleted.
595
596     QTextBlockUserData can be used to store custom settings. In the
597     case of syntax highlighting, it is in particular interesting as
598     cache storage for information that you may figure out while
599     parsing the paragraph's text.
600
601     For example while parsing the text, you can keep track of
602     parenthesis characters that you encounter ('{[(' and the like),
603     and store their relative position and the actual QChar in a simple
604     class derived from QTextBlockUserData:
605
606     \snippet code/src_gui_text_qsyntaxhighlighter.cpp 4
607
608     During cursor navigation in the associated editor, you can ask the
609     current QTextBlock (retrieved using the QTextCursor::block()
610     function) if it has a user data object set and cast it to your \c
611     BlockData object. Then you can check if the current cursor
612     position matches with a previously recorded parenthesis position,
613     and, depending on the type of parenthesis (opening or closing),
614     find the next opening or closing parenthesis on the same level.
615
616     In this way you can do a visual parenthesis matching and highlight
617     from the current cursor position to the matching parenthesis. That
618     makes it easier to spot a missing parenthesis in your code and to
619     find where a corresponding opening/closing parenthesis is when
620     editing parenthesis intensive code.
621
622     \sa QTextBlock::setUserData()
623 */
624 void QSyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data)
625 {
626     Q_D(QSyntaxHighlighter);
627     if (!d->currentBlock.isValid())
628         return;
629
630     d->currentBlock.setUserData(data);
631 }
632
633 /*!
634     Returns the QTextBlockUserData object previously attached to the
635     current text block.
636
637     \sa QTextBlock::userData(), setCurrentBlockUserData()
638 */
639 QTextBlockUserData *QSyntaxHighlighter::currentBlockUserData() const
640 {
641     Q_D(const QSyntaxHighlighter);
642     if (!d->currentBlock.isValid())
643         return 0;
644
645     return d->currentBlock.userData();
646 }
647
648 /*!
649     \since 4.4
650
651     Returns the current text block.
652 */
653 QTextBlock QSyntaxHighlighter::currentBlock() const
654 {
655     Q_D(const QSyntaxHighlighter);
656     return d->currentBlock;
657 }
658
659 QT_END_NAMESPACE
660
661 #include "moc_qsyntaxhighlighter.cpp"
662
663 #endif // QT_NO_SYNTAXHIGHLIGHTER