Export QTextImageHandler and add accessor for image
[profile/ivi/qtbase.git] / src / gui / text / qsyntaxhighlighter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtGui module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
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
234     \brief The QSyntaxHighlighter class allows you to define syntax
235     highlighting rules, and in addition you can use the class to query
236     a document's current formatting or user data.
237
238     \since 4.1
239
240     \ingroup richtext-processing
241
242     The QSyntaxHighlighter class is a base class for implementing
243     QTextEdit syntax highlighters.  A syntax highligher automatically
244     highlights parts of the text in a QTextEdit, or more generally in
245     a QTextDocument. Syntax highlighters are often used when the user
246     is entering text in a specific format (for example source code)
247     and help the user to read the text and identify syntax errors.
248
249     To provide your own syntax highlighting, you must subclass
250     QSyntaxHighlighter and reimplement highlightBlock().
251
252     When you create an instance of your QSyntaxHighlighter subclass,
253     pass it the QTextEdit or QTextDocument that you want the syntax
254     highlighting to be applied to. For example:
255
256     \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 0
257
258     After this your highlightBlock() function will be called
259     automatically whenever necessary. Use your highlightBlock()
260     function to apply formatting (e.g. setting the font and color) to
261     the text that is passed to it. QSyntaxHighlighter provides the
262     setFormat() function which applies a given QTextCharFormat on
263     the current text block. For example:
264
265     \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 1
266
267     Some syntaxes can have constructs that span several text
268     blocks. For example, a C++ syntax highlighter should be able to
269     cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
270     these cases it is necessary to know the end state of the previous
271     text block (e.g. "in comment").
272
273     Inside your highlightBlock() implementation you can query the end
274     state of the previous text block using the previousBlockState()
275     function. After parsing the block you can save the last state
276     using setCurrentBlockState().
277
278     The currentBlockState() and previousBlockState() functions return
279     an int value. If no state is set, the returned value is -1. You
280     can designate any other value to identify any given state using
281     the setCurrentBlockState() function. Once the state is set the
282     QTextBlock keeps that value until it is set set again or until the
283     corresponding paragraph of text is deleted.
284
285     For example, if you're writing a simple C++ syntax highlighter,
286     you might designate 1 to signify "in comment":
287
288     \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 2
289
290     In the example above, we first set the current block state to
291     0. Then, if the previous block ended within a comment, we higlight
292     from the beginning of the current block (\c {startIndex =
293     0}). Otherwise, we search for the given start expression. If the
294     specified end expression cannot be found in the text block, we
295     change the current block state by calling setCurrentBlockState(),
296     and make sure that the rest of the block is higlighted.
297
298     In addition you can query the current formatting and user data
299     using the format() and currentBlockUserData() functions
300     respectively. You can also attach user data to the current text
301     block using the setCurrentBlockUserData() function.
302     QTextBlockUserData can be used to store custom settings. In the
303     case of syntax highlighting, it is in particular interesting as
304     cache storage for information that you may figure out while
305     parsing the paragraph's text. For an example, see the
306     setCurrentBlockUserData() documentation.
307
308     \sa QTextEdit, {Syntax Highlighter Example}
309 */
310
311 /*!
312     Constructs a QSyntaxHighlighter with the given \a parent.
313
314     If the parent is a QTextEdit, it installs the syntaxhighlighter on the
315     parents document. The specified QTextEdit also becomes the owner of
316     the QSyntaxHighlighter.
317 */
318 QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent)
319     : QObject(*new QSyntaxHighlighterPrivate, parent)
320 {
321     if (parent->inherits("QTextEdit")) {
322         QTextDocument *doc = qobject_cast<QTextDocument *>(parent->property("document").value<QObject *>());
323         if (doc)
324             setDocument(doc);
325     }
326 }
327
328 /*!
329     Constructs a QSyntaxHighlighter and installs it on \a parent.
330     The specified QTextDocument also becomes the owner of the
331     QSyntaxHighlighter.
332 */
333 QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent)
334     : QObject(*new QSyntaxHighlighterPrivate, parent)
335 {
336     setDocument(parent);
337 }
338
339 /*!
340     Destructor. Uninstalls this syntax highlighter from the text document.
341 */
342 QSyntaxHighlighter::~QSyntaxHighlighter()
343 {
344     setDocument(0);
345 }
346
347 /*!
348     Installs the syntax highlighter on the given QTextDocument \a doc.
349     A QSyntaxHighlighter can only be used with one document at a time.
350 */
351 void QSyntaxHighlighter::setDocument(QTextDocument *doc)
352 {
353     Q_D(QSyntaxHighlighter);
354     if (d->doc) {
355         disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
356                    this, SLOT(_q_reformatBlocks(int,int,int)));
357
358         QTextCursor cursor(d->doc);
359         cursor.beginEditBlock();
360         for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next())
361             blk.layout()->clearAdditionalFormats();
362         cursor.endEditBlock();
363     }
364     d->doc = doc;
365     if (d->doc) {
366         connect(d->doc, SIGNAL(contentsChange(int,int,int)),
367                 this, SLOT(_q_reformatBlocks(int,int,int)));
368         d->rehighlightPending = true;
369         QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
370     }
371 }
372
373 /*!
374     Returns the QTextDocument on which this syntax highlighter is
375     installed.
376 */
377 QTextDocument *QSyntaxHighlighter::document() const
378 {
379     Q_D(const QSyntaxHighlighter);
380     return d->doc;
381 }
382
383 /*!
384     \since 4.2
385
386     Reapplies the highlighting to the whole document.
387
388     \sa rehighlightBlock()
389 */
390 void QSyntaxHighlighter::rehighlight()
391 {
392     Q_D(QSyntaxHighlighter);
393     if (!d->doc)
394         return;
395
396     QTextCursor cursor(d->doc);
397     d->rehighlight(cursor, QTextCursor::End);
398 }
399
400 /*!
401     \since 4.6
402
403     Reapplies the highlighting to the given QTextBlock \a block.
404     
405     \sa rehighlight()
406 */
407 void QSyntaxHighlighter::rehighlightBlock(const QTextBlock &block)
408 {
409     Q_D(QSyntaxHighlighter);
410     if (!d->doc || !block.isValid() || block.document() != d->doc)
411         return;
412
413     const bool rehighlightPending = d->rehighlightPending;
414
415     QTextCursor cursor(block);
416     d->rehighlight(cursor, QTextCursor::EndOfBlock);
417
418     if (rehighlightPending)
419         d->rehighlightPending = rehighlightPending;
420 }
421
422 /*!
423     \fn void QSyntaxHighlighter::highlightBlock(const QString &text)
424
425     Highlights the given text block. This function is called when
426     necessary by the rich text engine, i.e. on text blocks which have
427     changed.
428
429     To provide your own syntax highlighting, you must subclass
430     QSyntaxHighlighter and reimplement highlightBlock(). In your
431     reimplementation you should parse the block's \a text and call
432     setFormat() as often as necessary to apply any font and color
433     changes that you require. For example:
434
435     \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 3
436
437     Some syntaxes can have constructs that span several text
438     blocks. For example, a C++ syntax highlighter should be able to
439     cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
440     these cases it is necessary to know the end state of the previous
441     text block (e.g. "in comment").
442
443     Inside your highlightBlock() implementation you can query the end
444     state of the previous text block using the previousBlockState()
445     function. After parsing the block you can save the last state
446     using setCurrentBlockState().
447
448     The currentBlockState() and previousBlockState() functions return
449     an int value. If no state is set, the returned value is -1. You
450     can designate any other value to identify any given state using
451     the setCurrentBlockState() function. Once the state is set the
452     QTextBlock keeps that value until it is set set again or until the
453     corresponding paragraph of text gets deleted.
454
455     For example, if you're writing a simple C++ syntax highlighter,
456     you might designate 1 to signify "in comment". For a text block
457     that ended in the middle of a comment you'd set 1 using
458     setCurrentBlockState, and for other paragraphs you'd set 0.
459     In your parsing code if the return value of previousBlockState()
460     is 1, you would highlight the text as a C++ comment until you
461     reached the closing \c{*}\c{/}.
462
463     \sa previousBlockState(), setFormat(), setCurrentBlockState()
464 */
465
466 /*!
467     This function is applied to the syntax highlighter's current text
468     block (i.e. the text that is passed to the highlightBlock()
469     function).
470
471     The specified \a format is applied to the text from the \a start
472     position for a length of \a count characters (if \a count is 0,
473     nothing is done). The formatting properties set in \a format are
474     merged at display time with the formatting information stored
475     directly in the document, for example as previously set with
476     QTextCursor's functions. Note that the document itself remains
477     unmodified by the format set through this function.
478
479     \sa format(), highlightBlock()
480 */
481 void QSyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format)
482 {
483     Q_D(QSyntaxHighlighter);
484     if (start < 0 || start >= d->formatChanges.count())
485         return;
486
487     const int end = qMin(start + count, d->formatChanges.count());
488     for (int i = start; i < end; ++i)
489         d->formatChanges[i] = format;
490 }
491
492 /*!
493     \overload
494
495     The specified \a color is applied to the current text block from
496     the \a start position for a length of \a count characters.
497
498     The other attributes of the current text block, e.g. the font and
499     background color, are reset to default values.
500
501     \sa format(), highlightBlock()
502 */
503 void QSyntaxHighlighter::setFormat(int start, int count, const QColor &color)
504 {
505     QTextCharFormat format;
506     format.setForeground(color);
507     setFormat(start, count, format);
508 }
509
510 /*!
511     \overload
512
513     The specified \a font is applied to the current text block from
514     the \a start position for a length of \a count characters.
515
516     The other attributes of the current text block, e.g. the font and
517     background color, are reset to default values.
518
519     \sa format(), highlightBlock()
520 */
521 void QSyntaxHighlighter::setFormat(int start, int count, const QFont &font)
522 {
523     QTextCharFormat format;
524     format.setFont(font);
525     setFormat(start, count, format);
526 }
527
528 /*!
529     \fn QTextCharFormat QSyntaxHighlighter::format(int position) const
530
531     Returns the format at \a position inside the syntax highlighter's
532     current text block.
533 */
534 QTextCharFormat QSyntaxHighlighter::format(int pos) const
535 {
536     Q_D(const QSyntaxHighlighter);
537     if (pos < 0 || pos >= d->formatChanges.count())
538         return QTextCharFormat();
539     return d->formatChanges.at(pos);
540 }
541
542 /*!
543     Returns the end state of the text block previous to the
544     syntax highlighter's current block. If no value was
545     previously set, the returned value is -1.
546
547     \sa highlightBlock(), setCurrentBlockState()
548 */
549 int QSyntaxHighlighter::previousBlockState() const
550 {
551     Q_D(const QSyntaxHighlighter);
552     if (!d->currentBlock.isValid())
553         return -1;
554
555     const QTextBlock previous = d->currentBlock.previous();
556     if (!previous.isValid())
557         return -1;
558
559     return previous.userState();
560 }
561
562 /*!
563     Returns the state of the current text block. If no value is set,
564     the returned value is -1.
565 */
566 int QSyntaxHighlighter::currentBlockState() const
567 {
568     Q_D(const QSyntaxHighlighter);
569     if (!d->currentBlock.isValid())
570         return -1;
571
572     return d->currentBlock.userState();
573 }
574
575 /*!
576     Sets the state of the current text block to \a newState.
577
578     \sa highlightBlock()
579 */
580 void QSyntaxHighlighter::setCurrentBlockState(int newState)
581 {
582     Q_D(QSyntaxHighlighter);
583     if (!d->currentBlock.isValid())
584         return;
585
586     d->currentBlock.setUserState(newState);
587 }
588
589 /*!
590     Attaches the given \a data to the current text block.  The
591     ownership is passed to the underlying text document, i.e. the
592     provided QTextBlockUserData object will be deleted if the
593     corresponding text block gets deleted.
594
595     QTextBlockUserData can be used to store custom settings. In the
596     case of syntax highlighting, it is in particular interesting as
597     cache storage for information that you may figure out while
598     parsing the paragraph's text.
599
600     For example while parsing the text, you can keep track of
601     parenthesis characters that you encounter ('{[(' and the like),
602     and store their relative position and the actual QChar in a simple
603     class derived from QTextBlockUserData:
604
605     \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 4
606
607     During cursor navigation in the associated editor, you can ask the
608     current QTextBlock (retrieved using the QTextCursor::block()
609     function) if it has a user data object set and cast it to your \c
610     BlockData object. Then you can check if the current cursor
611     position matches with a previously recorded parenthesis position,
612     and, depending on the type of parenthesis (opening or closing),
613     find the next opening or closing parenthesis on the same level.
614
615     In this way you can do a visual parenthesis matching and highlight
616     from the current cursor position to the matching parenthesis. That
617     makes it easier to spot a missing parenthesis in your code and to
618     find where a corresponding opening/closing parenthesis is when
619     editing parenthesis intensive code.
620
621     \sa QTextBlock::setUserData()
622 */
623 void QSyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data)
624 {
625     Q_D(QSyntaxHighlighter);
626     if (!d->currentBlock.isValid())
627         return;
628
629     d->currentBlock.setUserData(data);
630 }
631
632 /*!
633     Returns the QTextBlockUserData object previously attached to the
634     current text block.
635
636     \sa QTextBlock::userData(), setCurrentBlockUserData()
637 */
638 QTextBlockUserData *QSyntaxHighlighter::currentBlockUserData() const
639 {
640     Q_D(const QSyntaxHighlighter);
641     if (!d->currentBlock.isValid())
642         return 0;
643
644     return d->currentBlock.userData();
645 }
646
647 /*!
648     \since 4.4
649
650     Returns the current text block.
651 */
652 QTextBlock QSyntaxHighlighter::currentBlock() const
653 {
654     Q_D(const QSyntaxHighlighter);
655     return d->currentBlock;
656 }
657
658 QT_END_NAMESPACE
659
660 #include "moc_qsyntaxhighlighter.cpp"
661
662 #endif // QT_NO_SYNTAXHIGHLIGHTER