1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt3Support module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "q3textedit.h"
43 
44 #ifndef QT_NO_TEXTEDIT
45 
46 #include <private/q3richtext_p.h>
47 #include "qpainter.h"
48 #include "qpen.h"
49 #include "qbrush.h"
50 #include "qpixmap.h"
51 #include "qfont.h"
52 #include "qcolor.h"
53 #include "qstyle.h"
54 #include "qsize.h"
55 #include "qevent.h"
56 #include "qtimer.h"
57 #include "qapplication.h"
58 #include "q3listbox.h"
59 #include "qclipboard.h"
60 #include "qcolordialog.h"
61 #include "q3stylesheet.h"
62 #include "q3dragobject.h"
63 #include "qurl.h"
64 #include "qcursor.h"
65 #include "qregexp.h"
66 #include "q3popupmenu.h"
67 #include "qstack.h"
68 #include "qmetaobject.h"
69 #include "q3textbrowser.h"
70 #include "private/q3syntaxhighlighter_p.h"
71 #include "qtextformat.h"
72 #ifndef QT_NO_IM
73 #include <qinputcontext.h>
74 #endif
75 
76 #ifndef QT_NO_ACCEL
77 #include <qkeysequence.h>
78 #define ACCEL_KEY(k) QLatin1Char('\t') + QString(QKeySequence(Qt::CTRL | Qt::Key_ ## k))
79 #else
80 #define ACCEL_KEY(k) QLatin1Char('\t' )+ QString::fromLatin1("Ctrl+" #k)
81 #endif
82 
83 #ifdef QT_TEXTEDIT_OPTIMIZATION
84 #define LOGOFFSET(i) d->logOffset + i
85 #endif
86 
87 QT_BEGIN_NAMESPACE
88 
89 struct QUndoRedoInfoPrivate
90 {
91     Q3TextString text;
92 };
93 
94 class Q3TextEditPrivate
95 {
96 public:
Q3TextEditPrivate()97     Q3TextEditPrivate()
98         :preeditStart(-1),preeditLength(-1),numPreeditSelections(0),ensureCursorVisibleInShowEvent(false),
99          tabChangesFocus(false),
100 #ifndef QT_NO_CLIPBOARD
101          clipboard_mode(QClipboard::Clipboard),
102 #endif
103 #ifdef QT_TEXTEDIT_OPTIMIZATION
104          od(0), optimMode(false),
105          maxLogLines(-1),
106          logOffset(0),
107 #endif
108          autoFormatting((uint)Q3TextEdit::AutoAll),
109          cursorRepaintMode(false),
110          cursorBlinkActive(false)
111 
112     {
113         for (int i=0; i<7; i++)
114             id[i] = 0;
115     }
116     int id[7];
117     int preeditStart;
118     int preeditLength;
119     int numPreeditSelections;
120     uint ensureCursorVisibleInShowEvent : 1;
121     uint tabChangesFocus : 1;
122     QString scrollToAnchor; // used to deferr scrollToAnchor() until the show event when we are resized
123     QString pressedName;
124     QString onName;
125 #ifndef QT_NO_CLIPBOARD
126     QClipboard::Mode clipboard_mode;
127 #endif
128     QTimer *trippleClickTimer;
129     QPoint trippleClickPoint;
130 #ifdef QT_TEXTEDIT_OPTIMIZATION
131     Q3TextEditOptimPrivate * od;
132     bool optimMode : 1;
133     int maxLogLines;
134     int logOffset;
135 #endif
136     Q3TextEdit::AutoFormatting autoFormatting;
137     uint cursorRepaintMode : 1;
138     uint cursorBlinkActive : 1;
139 };
140 
141 #ifndef QT_NO_MIME
142 class Q3RichTextDrag : public Q3TextDrag
143 {
144 public:
145     Q3RichTextDrag(QWidget *dragSource = 0, const char *name = 0);
146 
setPlainText(const QString & txt)147     void setPlainText(const QString &txt) { setText(txt); }
setRichText(const QString & txt)148     void setRichText(const QString &txt) { richTxt = txt; }
149 
150     virtual QByteArray encodedData(const char *mime) const;
151     virtual const char* format(int i) const;
152 
153     static bool decode(QMimeSource *e, QString &str, const QString &mimetype,
154                         const QString &subtype);
155     static bool canDecode(QMimeSource* e);
156 
157 private:
158     QString richTxt;
159 
160 };
161 
Q3RichTextDrag(QWidget * dragSource,const char * name)162 Q3RichTextDrag::Q3RichTextDrag(QWidget *dragSource, const char *name)
163     : Q3TextDrag(dragSource, name)
164 {
165 }
166 
encodedData(const char * mime) const167 QByteArray Q3RichTextDrag::encodedData(const char *mime) const
168 {
169     if (qstrcmp("application/x-qrichtext", mime) == 0) {
170         return richTxt.toUtf8(); // #### perhaps we should use USC2 instead?
171     } else
172         return Q3TextDrag::encodedData(mime);
173 }
174 
decode(QMimeSource * e,QString & str,const QString & mimetype,const QString & subtype)175 bool Q3RichTextDrag::decode(QMimeSource *e, QString &str, const QString &mimetype,
176                             const QString &subtype)
177 {
178     if (mimetype == QLatin1String("application/x-qrichtext")) {
179         // do richtext decode
180         const char *mime;
181         int i;
182         for (i = 0; (mime = e->format(i)); ++i) {
183             if (qstrcmp("application/x-qrichtext", mime) != 0)
184                 continue;
185             str = QString::fromUtf8(e->encodedData(mime));
186             return true;
187         }
188         return false;
189     }
190 
191     // do a regular text decode
192     QString st = subtype;
193     return Q3TextDrag::decode(e, str, st);
194 }
195 
canDecode(QMimeSource * e)196 bool Q3RichTextDrag::canDecode(QMimeSource* e)
197 {
198     if (e->provides("application/x-qrichtext"))
199         return true;
200     return Q3TextDrag::canDecode(e);
201 }
202 
format(int i) const203 const char* Q3RichTextDrag::format(int i) const
204 {
205     if (Q3TextDrag::format(i))
206         return Q3TextDrag::format(i);
207     if (Q3TextDrag::format(i-1))
208         return "application/x-qrichtext";
209     return 0;
210 }
211 
212 #endif
213 
214 static bool block_set_alignment = false;
215 
216 /*!
217     \class Q3TextEdit
218     \brief The Q3TextEdit widget provides a powerful single-page rich text editor.
219 
220     \compat
221 
222     \tableofcontents
223 
224     \section1 Introduction and Concepts
225 
226     Q3TextEdit is an advanced WYSIWYG viewer/editor supporting rich
227     text formatting using HTML-style tags. It is optimized to handle
228     large documents and to respond quickly to user input.
229 
230     Q3TextEdit has four modes of operation:
231     \table
232     \header \i Mode \i Command \i Notes
233     \row \i Plain Text Editor \i setTextFormat(Qt::PlainText)
234          \i Set text with setText(); text() returns plain text. Text
235          attributes (e.g. colors) can be set, but plain text is always
236          returned.
237     \row \i Rich Text Editor \i setTextFormat(Qt::RichText)
238          \i Set text with setText(); text() returns rich text. Rich
239          text editing is fairly limited. You can't set margins or
240          insert images for example (although you can read and
241          correctly display files that have margins set and that
242          include images). This mode is mostly useful for editing small
243          amounts of rich text.
244     \row \i Text Viewer \i setReadOnly(true)
245          \i Set text with setText() or append() (which has no undo
246          history so is faster and uses less memory); text() returns
247          plain or rich text depending on the textFormat(). This mode
248          can correctly display a large subset of HTML tags.
249     \row \i Log Viewer \i setTextFormat(Qt::LogText)
250          \i Append text using append(). The widget is set to be read
251          only and rich text support is disabled although a few HTML
252          tags (for color, bold, italic and underline) may be used.
253          (See \link #logtextmode Qt::LogText mode\endlink for details.)
254     \endtable
255 
256     Q3TextEdit can be used as a syntax highlighting editor when used in
257     conjunction with QSyntaxHighlighter.
258 
259     We recommend that you always call setTextFormat() to set the mode
260     you want to use. If you use Qt::AutoText then setText() and
261     append() will try to determine whether the text they are given is
262     plain text or rich text. If you use Qt::RichText then setText() and
263     append() will assume that the text they are given is rich text.
264     insert() simply inserts the text it is given.
265 
266     Q3TextEdit works on paragraphs and characters. A paragraph is a
267     formatted string which is word-wrapped to fit into the width of
268     the widget. By default when reading plain text, one newline
269     signify a paragraph. A document consists of zero or more
270     paragraphs, indexed from 0. Characters are indexed on a
271     per-paragraph basis, also indexed from 0. The words in the
272     paragraph are aligned in accordance with the paragraph's
273     alignment(). Paragraphs are separated by hard line breaks. Each
274     character within a paragraph has its own attributes, for example,
275     font and color.
276 
277     The text edit documentation uses the following concepts:
278     \list
279     \i \e{current format} --
280     this is the format at the current cursor position, \e and it
281     is the format of the selected text if any.
282     \i \e{current paragraph} -- the paragraph which contains the
283     cursor.
284     \endlist
285 
286     Q3TextEdit can display images (using Q3MimeSourceFactory), lists and
287     tables. If the text is too large to view within the text edit's
288     viewport, scroll bars will appear. The text edit can load both
289     plain text and HTML files (a subset of HTML 3.2 and 4). The
290     rendering style and the set of valid tags are defined by a
291     styleSheet(). Custom tags can be created and placed in a custom
292     style sheet. Change the style sheet with \l{setStyleSheet()}; see
293     Q3StyleSheet for details. The images identified by image tags are
294     displayed if they can be interpreted using the text edit's
295     \l{Q3MimeSourceFactory}; see setMimeSourceFactory().
296 
297     If you want a text browser with more navigation use QTextBrowser.
298     If you just need to display a small piece of rich text use QLabel
299     or QSimpleRichText.
300 
301     If you create a new Q3TextEdit, and want to allow the user to edit
302     rich text, call setTextFormat(Qt::RichText) to ensure that the
303     text is treated as rich text. (Rich text uses HTML tags to set
304     text formatting attributes. See Q3StyleSheet for information on the
305     HTML tags that are supported.). If you don't call setTextFormat()
306     explicitly the text edit will guess from the text itself whether
307     it is rich text or plain text. This means that if the text looks
308     like HTML or XML it will probably be interpreted as rich text, so
309     you should call setTextFormat(Qt::PlainText) to preserve such
310     text.
311 
312     Note that we do not intend to add a full-featured web browser
313     widget to Qt (because that would easily double Qt's size and only
314     a few applications would benefit from it). The rich
315     text support in Qt is designed to provide a fast, portable and
316     efficient way to add reasonable online help facilities to
317     applications, and to provide a basis for rich text editors.
318 
319     \section1 Using Q3TextEdit as a Display Widget
320 
321     Q3TextEdit can display a large HTML subset, including tables and
322     images.
323 
324     The text is set or replaced using setText() which deletes any
325     existing text and replaces it with the text passed in the
326     setText() call. If you call setText() with legacy HTML (with
327     setTextFormat(Qt::RichText) in force), and then call text(), the text
328     that is returned may have different markup, but will render the
329     same. Text can be inserted with insert(), paste(), pasteSubType()
330     and append(). Text that is appended does not go into the undo
331     history; this makes append() faster and consumes less memory. Text
332     can also be cut(). The entire text is deleted with clear() and the
333     selected text is deleted with removeSelectedText(). Selected
334     (marked) text can also be deleted with del() (which will delete
335     the character to the right of the cursor if no text is selected).
336 
337     Loading and saving text is achieved using setText() and text(),
338     for example:
339     \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 0
340 
341     By default the text edit wraps words at whitespace to fit within
342     the text edit widget. The setWordWrap() function is used to
343     specify the kind of word wrap you want, or \c NoWrap if you don't
344     want any wrapping. Call setWordWrap() to set a fixed pixel width
345     \c FixedPixelWidth, or character column (e.g. 80 column) \c
346     FixedColumnWidth with the pixels or columns specified with
347     setWrapColumnOrWidth(). If you use word wrap to the widget's width
348     \c WidgetWidth, you can specify whether to break on whitespace or
349     anywhere with setWrapPolicy().
350 
351     The background color is set differently than other widgets, using
352     setPaper(). You specify a brush style which could be a plain color
353     or a complex pixmap.
354 
355     Hypertext links are automatically underlined; this can be changed
356     with setLinkUnderline(). The tab stop width is set with
357     setTabStopWidth().
358 
359     The zoomIn() and zoomOut() functions can be used to resize the
360     text by increasing (decreasing for zoomOut()) the point size used.
361     Images are not affected by the zoom functions.
362 
363     The lines() function returns the number of lines in the text and
364     paragraphs() returns the number of paragraphs. The number of lines
365     within a particular paragraph is returned by linesOfParagraph().
366     The length of the entire text in characters is returned by
367     length().
368 
369     You can scroll to an anchor in the text, e.g.
370     \c{<a name="anchor">} with scrollToAnchor(). The find() function
371     can be used to find and select a given string within the text.
372 
373     A read-only Q3TextEdit provides the same functionality as the
374     (obsolete) QTextView. (QTextView is still supplied for
375     compatibility with old code.)
376 
377     \section2 Read-only key bindings
378 
379     When Q3TextEdit is used read-only the key-bindings are limited to
380     navigation, and text may only be selected with the mouse:
381     \table
382     \header \i Keypresses \i Action
383     \row \i Up        \i Move one line up
384     \row \i Down        \i Move one line down
385     \row \i Left        \i Move one character left
386     \row \i Right        \i Move one character right
387     \row \i PageUp        \i Move one (viewport) page up
388     \row \i PageDown        \i Move one (viewport) page down
389     \row \i Home        \i Move to the beginning of the text
390     \row \i End                \i Move to the end of the text
391     \row \i Shift+Wheel
392          \i Scroll the page horizontally (the Wheel is the mouse wheel)
393     \row \i Ctrl+Wheel        \i Zoom the text
394     \endtable
395 
396     The text edit may be able to provide some meta-information. For
397     example, the documentTitle() function will return the text from
398     within HTML \c{<title>} tags.
399 
400     The text displayed in a text edit has a \e context. The context is
401     a path which the text edit's Q3MimeSourceFactory uses to resolve
402     the locations of files and images. It is passed to the
403     mimeSourceFactory() when quering data. (See Q3TextEdit() and
404     \l{context()}.)
405 
406     \target logtextmode
407     \section2 Using Q3TextEdit in Qt::LogText Mode
408 
409     Setting the text format to Qt::LogText puts the widget in a special
410     mode which is optimized for very large texts. In this mode editing
411     and rich text support are disabled (the widget is explicitly set
412     to read-only mode). This allows the text to be stored in a
413     different, more memory efficient manner. However, a certain degree
414     of text formatting is supported through the use of formatting
415     tags. A tag is delimited by \c < and \c {>}. The characters \c
416     {<}, \c > and \c & are escaped by using \c {&lt;}, \c {&gt;} and
417     \c {&amp;}. A tag pair consists of a left and a right tag (or
418     open/close tags). Left-tags mark the starting point for
419     formatting, while right-tags mark the ending point. A right-tag
420     always start with a \c / before the tag keyword. For example \c
421     <b> and \c </b> are a tag pair. Tags can be nested, but they
422     have to be closed in the same order as they are opened. For
423     example, \c <b><u></u></b> is valid, while \c
424     <b><u></b></u> will output an error message.
425 
426     By using tags it is possible to change the color, bold, italic and
427     underline settings for a piece of text. A color can be specified
428     by using the HTML font tag \c {<font color=colorname>}. The color
429     name can be one of the color names from the X11 color database, or
430     a RGB hex value (e.g \c {#00ff00}). Example of valid color tags:
431     \c {<font color=red>}, \c{<font color="light blue">},\c {<font
432     color="#223344">}. Bold, italic and underline settings can be
433     specified by the tags \c {<b>}, \c <i> and \c {<u>}. Note that a
434     tag does not necessarily have to be closed. A valid example:
435     \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 1
436 
437     Stylesheets can also be used in Qt::LogText mode. To create and use a
438     custom tag, you could do the following:
439     \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 2
440     Note that only the color, bold, underline and italic attributes of
441     a Q3StyleSheetItem is used in Qt::LogText mode.
442 
443     Note that you can use setMaxLogLines() to limit the number of
444     lines the widget can hold in Qt::LogText mode.
445 
446     There are a few things that you need to be aware of when the
447     widget is in this mode:
448     \list
449     \i Functions that deal with rich text formatting and cursor
450     movement will not work or return anything valid.
451     \i Lines are equivalent to paragraphs.
452     \endlist
453 
454     \section1 Using Q3TextEdit as an Editor
455 
456     All the information about using Q3TextEdit as a display widget also
457     applies here.
458 
459     The current format's attributes are set with setItalic(),
460     setBold(), setUnderline(), setFamily() (font family),
461     setPointSize(), setColor() and setCurrentFont(). The current
462     paragraph's alignment is set with setAlignment().
463 
464     Use setSelection() to select text. The setSelectionAttributes()
465     function is used to indicate how selected text should be
466     displayed. Use hasSelectedText() to find out if any text is
467     selected. The currently selected text's position is available
468     using getSelection() and the selected text itself is returned by
469     selectedText(). The selection can be copied to the clipboard with
470     copy(), or cut to the clipboard with cut(). It can be deleted with
471     removeSelectedText(). The entire text can be selected (or
472     unselected) using selectAll(). Q3TextEdit supports multiple
473     selections. Most of the selection functions operate on the default
474     selection, selection 0. If the user presses a non-selecting key,
475     e.g. a cursor key without also holding down Shift, all selections
476     are cleared.
477 
478     Set and get the position of the cursor with setCursorPosition()
479     and getCursorPosition() respectively. When the cursor is moved,
480     the signals currentFontChanged(), currentColorChanged() and
481     currentAlignmentChanged() are emitted to reflect the font, color
482     and alignment at the new cursor position.
483 
484     If the text changes, the textChanged() signal is emitted, and if
485     the user inserts a new line by pressing Return or Enter,
486     returnPressed() is emitted. The isModified() function will return
487     true if the text has been modified.
488 
489     Q3TextEdit provides command-based undo and redo. To set the depth
490     of the command history use setUndoDepth() which defaults to 100
491     steps. To undo or redo the last operation call undo() or redo().
492     The signals undoAvailable() and redoAvailable() indicate whether
493     the undo and redo operations can be executed.
494 
495     \section2 Editing key bindings
496 
497     The list of key-bindings which are implemented for editing:
498     \table
499     \header \i Keypresses \i Action
500     \row \i Backspace \i Delete the character to the left of the cursor
501     \row \i Delete \i Delete the character to the right of the cursor
502     \row \i Ctrl+A \i Move the cursor to the beginning of the line
503     \row \i Ctrl+B \i Move the cursor one character left
504     \row \i Ctrl+C \i Copy the marked text to the clipboard (also
505                       Ctrl+Insert under Windows)
506     \row \i Ctrl+D \i Delete the character to the right of the cursor
507     \row \i Ctrl+E \i Move the cursor to the end of the line
508     \row \i Ctrl+F \i Move the cursor one character right
509     \row \i Ctrl+H \i Delete the character to the left of the cursor
510     \row \i Ctrl+K \i Delete to end of line
511     \row \i Ctrl+N \i Move the cursor one line down
512     \row \i Ctrl+P \i Move the cursor one line up
513     \row \i Ctrl+V \i Paste the clipboard text into line edit
514                       (also Shift+Insert under Windows)
515     \row \i Ctrl+X \i Cut the marked text, copy to clipboard
516                       (also Shift+Delete under Windows)
517     \row \i Ctrl+Z \i Undo the last operation
518     \row \i Ctrl+Y \i Redo the last operation
519     \row \i Left            \i Move the cursor one character left
520     \row \i Ctrl+Left  \i Move the cursor one word left
521     \row \i Right            \i Move the cursor one character right
522     \row \i Ctrl+Right \i Move the cursor one word right
523     \row \i Up            \i Move the cursor one line up
524     \row \i Ctrl+Qt::Up    \i Move the cursor one word up
525     \row \i DownArrow            \i Move the cursor one line down
526     \row \i Ctrl+Down \i Move the cursor one word down
527     \row \i PageUp            \i Move the cursor one page up
528     \row \i PageDown            \i Move the cursor one page down
529     \row \i Home            \i Move the cursor to the beginning of the line
530     \row \i Ctrl+Home            \i Move the cursor to the beginning of the text
531     \row \i End                    \i Move the cursor to the end of the line
532     \row \i Ctrl+End            \i Move the cursor to the end of the text
533     \row \i Shift+Wheel            \i Scroll the page horizontally
534                             (the Wheel is the mouse wheel)
535     \row \i Ctrl+Wheel            \i Zoom the text
536     \endtable
537 
538     To select (mark) text hold down the Shift key whilst pressing one
539     of the movement keystrokes, for example, \e{Shift+Right}
540     will select the character to the right, and \e{Shift+Ctrl+Right} will select the word to the right, etc.
541 
542     By default the text edit widget operates in insert mode so all
543     text that the user enters is inserted into the text edit and any
544     text to the right of the cursor is moved out of the way. The mode
545     can be changed to overwrite, where new text overwrites any text to
546     the right of the cursor, using setOverwriteMode().
547 */
548 
549 /*!
550     \enum Q3TextEdit::AutoFormattingFlag
551 
552     \value AutoNone Do not perform any automatic formatting
553     \value AutoBulletList Only automatically format bulletted lists
554     \value AutoAll Apply all available autoformatting
555 */
556 
557 
558 /*!
559     \enum Q3TextEdit::KeyboardAction
560 
561     This enum is used by doKeyboardAction() to specify which action
562     should be executed:
563 
564     \value ActionBackspace  Delete the character to the left of the
565     cursor.
566 
567     \value ActionDelete  Delete the character to the right of the
568     cursor.
569 
570     \value ActionReturn  Split the paragraph at the cursor position.
571 
572     \value ActionKill If the cursor is not at the end of the
573     paragraph, delete the text from the cursor position until the end
574     of the paragraph. If the cursor is at the end of the paragraph,
575     delete the hard line break at the end of the paragraph: this will
576     cause this paragraph to be joined with the following paragraph.
577 
578     \value ActionWordBackspace Delete the word to the left of the
579     cursor position.
580 
581     \value ActionWordDelete Delete the word to the right of the
582     cursor position
583 
584 */
585 
586 /*!
587     \enum Q3TextEdit::VerticalAlignment
588 
589     This enum is used to set the vertical alignment of the text.
590 
591     \value AlignNormal Normal alignment
592     \value AlignSuperScript Superscript
593     \value AlignSubScript Subscript
594 */
595 
596 /*!
597     \enum Q3TextEdit::TextInsertionFlags
598 
599     \internal
600 
601     \value RedoIndentation
602     \value CheckNewLines
603     \value RemoveSelected
604 */
605 
606 
607 /*!
608     \fn void Q3TextEdit::copyAvailable(bool yes)
609 
610     This signal is emitted when text is selected or de-selected in the
611     text edit.
612 
613     When text is selected this signal will be emitted with \a yes set
614     to true. If no text has been selected or if the selected text is
615     de-selected this signal is emitted with \a yes set to false.
616 
617     If \a yes is true then copy() can be used to copy the selection to
618     the clipboard. If \a yes is false then copy() does nothing.
619 
620     \sa selectionChanged()
621 */
622 
623 
624 /*!
625     \fn void Q3TextEdit::textChanged()
626 
627     This signal is emitted whenever the text in the text edit changes.
628 
629     \sa setText() append()
630 */
631 
632 /*!
633     \fn void Q3TextEdit::selectionChanged()
634 
635     This signal is emitted whenever the selection changes.
636 
637     \sa setSelection() copyAvailable()
638 */
639 
640 /*!  \fn Q3TextDocument *Q3TextEdit::document() const
641 
642     \internal
643 
644   This function returns the Q3TextDocument which is used by the text
645   edit.
646 */
647 
648 /*!  \fn void Q3TextEdit::setDocument(Q3TextDocument *doc)
649 
650     \internal
651 
652   This function sets the Q3TextDocument which should be used by the text
653   edit to \a doc. This can be used, for example, if you want to
654   display a document using multiple views. You would create a
655   Q3TextDocument and set it to the text edits which should display it.
656   You would need to connect to the textChanged() and
657   selectionChanged() signals of all the text edits and update them all
658   accordingly (preferably with a slight delay for efficiency reasons).
659 */
660 
661 /*!
662     \enum Q3TextEdit::CursorAction
663 
664     This enum is used by moveCursor() to specify in which direction
665     the cursor should be moved:
666 
667     \value MoveBackward  Moves the cursor one character backward
668 
669     \value MoveWordBackward Moves the cursor one word backward
670 
671     \value MoveForward  Moves the cursor one character forward
672 
673     \value MoveWordForward Moves the cursor one word forward
674 
675     \value MoveUp  Moves the cursor up one line
676 
677     \value MoveDown  Moves the cursor down one line
678 
679     \value MoveLineStart  Moves the cursor to the beginning of the line
680 
681     \value MoveLineEnd Moves the cursor to the end of the line
682 
683     \value MoveHome  Moves the cursor to the beginning of the document
684 
685     \value MoveEnd Moves the cursor to the end of the document
686 
687     \value MovePgUp  Moves the cursor one viewport page up
688 
689     \value MovePgDown  Moves the cursor one viewport page down
690 */
691 
692 /*!
693     \property Q3TextEdit::overwriteMode
694     \brief the text edit's overwrite mode
695 
696     If false (the default) characters entered by the user are inserted
697     with any characters to the right being moved out of the way. If
698     true, the editor is in overwrite mode, i.e. characters entered by
699     the user overwrite any characters to the right of the cursor
700     position.
701 */
702 
703 /*!
704     \fn void Q3TextEdit::setCurrentFont(const QFont &f)
705 
706     Sets the font of the current format to \a f.
707 
708     If the widget is in Qt::LogText mode this function will do
709     nothing. Use setFont() instead.
710 
711     \sa currentFont() setPointSize() setFamily()
712 */
713 
714 /*!
715     \property Q3TextEdit::undoDepth
716     \brief the depth of the undo history
717 
718     The maximum number of steps in the undo/redo history. The default
719     is 100.
720 
721     \sa undo() redo()
722 */
723 
724 /*!
725     \fn void Q3TextEdit::undoAvailable(bool yes)
726 
727     This signal is emitted when the availability of undo changes. If
728     \a yes is true, then undo() will work until undoAvailable(false)
729     is next emitted.
730 
731     \sa undo() undoDepth()
732 */
733 
734 /*!
735     \fn void Q3TextEdit::modificationChanged(bool m)
736 
737     This signal is emitted when the modification status of the
738     document has changed. If \a m is true, the document was modified,
739     otherwise the modification state has been reset to unmodified.
740 
741     \sa modified
742 */
743 
744 /*!
745     \fn void Q3TextEdit::redoAvailable(bool yes)
746 
747     This signal is emitted when the availability of redo changes. If
748     \a yes is true, then redo() will work until redoAvailable(false)
749     is next emitted.
750 
751     \sa redo() undoDepth()
752 */
753 
754 /*!
755     \fn void Q3TextEdit::currentFontChanged(const QFont &f)
756 
757     This signal is emitted if the font of the current format has
758     changed.
759 
760     The new font is \a f.
761 
762     \sa setCurrentFont()
763 */
764 
765 /*!
766     \fn void Q3TextEdit::currentColorChanged(const QColor &c)
767 
768     This signal is emitted if the color of the current format has
769     changed.
770 
771     The new color is \a c.
772 
773     \sa setColor()
774 */
775 
776 /*!
777     \fn void Q3TextEdit::currentVerticalAlignmentChanged(Q3TextEdit::VerticalAlignment a)
778 
779     This signal is emitted if the vertical alignment of the current
780     format has changed.
781 
782     The new vertical alignment is \a a.
783 */
784 
785 /*!
786     \fn void Q3TextEdit::currentAlignmentChanged(int a)
787 
788     This signal is emitted if the alignment of the current paragraph
789     has changed.
790 
791     The new alignment is \a a.
792 
793     \sa setAlignment()
794 */
795 
796 /*!
797     \fn void Q3TextEdit::cursorPositionChanged(Q3TextCursor *c)
798 
799     \internal
800 */
801 
802 /*!
803     \fn void Q3TextEdit::cursorPositionChanged(int para, int pos)
804 
805     \overload
806 
807     This signal is emitted if the position of the cursor has changed.
808     \a para contains the paragraph index and \a pos contains the
809     character position within the paragraph.
810 
811     \sa setCursorPosition()
812 */
813 
814 /*!
815     \fn void Q3TextEdit::clicked(int para, int pos)
816 
817     This signal is emitted when the mouse is clicked on the paragraph
818     \a para at character position \a pos.
819 
820     \sa doubleClicked()
821 */
822 
823 /*! \fn void Q3TextEdit::doubleClicked(int para, int pos)
824 
825   This signal is emitted when the mouse is double-clicked on the
826   paragraph \a para at character position \a pos.
827 
828   \sa clicked()
829 */
830 
831 
832 /*!
833     \fn void Q3TextEdit::returnPressed()
834 
835     This signal is emitted if the user pressed the Return or the Enter
836     key.
837 */
838 
839 /*!
840     \fn Q3TextCursor *Q3TextEdit::textCursor() const
841 
842     Returns the text edit's text cursor.
843 
844     \warning Q3TextCursor is not in the public API, but in special
845     circumstances you might wish to use it.
846 */
847 
848 /*!
849     Constructs an empty Q3TextEdit called \a name, with parent \a
850     parent.
851 */
852 
Q3TextEdit(QWidget * parent,const char * name)853 Q3TextEdit::Q3TextEdit(QWidget *parent, const char *name)
854     : Q3ScrollView(parent, name, Qt::WStaticContents | Qt::WNoAutoErase),
855       doc(new Q3TextDocument(0)), undoRedoInfo(doc)
856 {
857     init();
858 }
859 
860 /*!
861     Constructs a Q3TextEdit called \a name, with parent \a parent. The
862     text edit will display the text \a text using context \a context.
863 
864     The \a context is a path which the text edit's Q3MimeSourceFactory
865     uses to resolve the locations of files and images. It is passed to
866     the mimeSourceFactory() when quering data.
867 
868     For example if the text contains an image tag,
869     \c{<img src="image.png">}, and the context is "path/to/look/in", the
870     Q3MimeSourceFactory will try to load the image from
871     "path/to/look/in/image.png". If the tag was
872     \c{<img src="/image.png">}, the context will not be used (because
873     Q3MimeSourceFactory recognizes that we have used an absolute path)
874     and will try to load "/image.png". The context is applied in exactly
875     the same way to \e hrefs, for example,
876     \c{<a href="target.html">Target</a>}, would resolve to
877     "path/to/look/in/target.html".
878 */
879 
Q3TextEdit(const QString & text,const QString & context,QWidget * parent,const char * name)880 Q3TextEdit::Q3TextEdit(const QString& text, const QString& context,
881                       QWidget *parent, const char *name)
882     : Q3ScrollView(parent, name, Qt::WStaticContents | Qt::WNoAutoErase),
883       doc(new Q3TextDocument(0)), undoRedoInfo(doc)
884 {
885     init();
886     setText(text, context);
887 }
888 
889 /*!
890     Destructor.
891 */
892 
~Q3TextEdit()893 Q3TextEdit::~Q3TextEdit()
894 {
895     delete undoRedoInfo.d;
896     undoRedoInfo.d = 0;
897     delete cursor;
898     delete doc;
899 #ifdef QT_TEXTEDIT_OPTIMIZATION
900     delete d->od;
901 #endif
902     delete d;
903 }
904 
init()905 void Q3TextEdit::init()
906 {
907     d = new Q3TextEditPrivate;
908     doc->formatCollection()->setPaintDevice(this);
909     undoEnabled = true;
910     readonly = true;
911     setReadOnly(false);
912     setFrameStyle(LineEditPanel | Sunken);
913     connect(doc, SIGNAL(minimumWidthChanged(int)),
914              this, SLOT(documentWidthChanged(int)));
915 
916     mousePressed = false;
917     inDoubleClick = false;
918     modified = false;
919     mightStartDrag = false;
920     onLink.clear();
921     d->onName.clear();
922     overWrite = false;
923     wrapMode = WidgetWidth;
924     wrapWidth = -1;
925     wPolicy = AtWhiteSpace;
926     inDnD = false;
927     doc->setFormatter(new Q3TextFormatterBreakWords);
928     QFont f = Q3ScrollView::font();
929     if (f.kerning())
930         f.setKerning(false);
931     doc->formatCollection()->defaultFormat()->setFont(f);
932     doc->formatCollection()->defaultFormat()->setColor(palette().color(QPalette::Text));
933     currentFormat = doc->formatCollection()->defaultFormat();
934     currentAlignment = Qt::AlignAuto;
935 
936     setBackgroundRole(QPalette::Base);
937     viewport()->setBackgroundRole(QPalette::Base);
938 
939     viewport()->setAcceptDrops(true);
940     resizeContents(0, doc->lastParagraph() ?
941                     (doc->lastParagraph()->paragId() + 1) * doc->formatCollection()->defaultFormat()->height() : 0);
942 
943     setAttribute(Qt::WA_KeyCompression, true);
944     viewport()->setMouseTracking(true);
945 #ifndef QT_NO_CURSOR
946     viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
947 #endif
948     cursor = new Q3TextCursor(doc);
949 
950     formatTimer = new QTimer(this);
951     connect(formatTimer, SIGNAL(timeout()),
952              this, SLOT(formatMore()));
953     lastFormatted = doc->firstParagraph();
954 
955     scrollTimer = new QTimer(this);
956     connect(scrollTimer, SIGNAL(timeout()),
957              this, SLOT(autoScrollTimerDone()));
958 
959     interval = 0;
960     changeIntervalTimer = new QTimer(this);
961     connect(changeIntervalTimer, SIGNAL(timeout()),
962              this, SLOT(doChangeInterval()));
963 
964     cursorVisible = true;
965     blinkTimer = new QTimer(this);
966     connect(blinkTimer, SIGNAL(timeout()),
967              this, SLOT(blinkCursor()));
968 
969 #ifndef QT_NO_DRAGANDDROP
970     dragStartTimer = new QTimer(this);
971     connect(dragStartTimer, SIGNAL(timeout()),
972              this, SLOT(startDrag()));
973 #endif
974 
975     d->trippleClickTimer = new QTimer(this);
976 
977     formatMore();
978 
979     blinkCursorVisible = false;
980 
981     viewport()->setFocusProxy(this);
982     viewport()->setFocusPolicy(Qt::WheelFocus);
983     setFocusPolicy(Qt::WheelFocus);
984     setInputMethodEnabled(true);
985     viewport()->installEventFilter(this);
986     connect(this, SIGNAL(horizontalSliderReleased()), this, SLOT(sliderReleased()));
987     connect(this, SIGNAL(verticalSliderReleased()), this, SLOT(sliderReleased()));
988     installEventFilter(this);
989 }
990 
paintDocument(bool drawAll,QPainter * p,int cx,int cy,int cw,int ch)991 void Q3TextEdit::paintDocument(bool drawAll, QPainter *p, int cx, int cy, int cw, int ch)
992 {
993 #ifdef QT_TEXTEDIT_OPTIMIZATION
994     Q_ASSERT(!d->optimMode);
995     if (d->optimMode)
996         return;
997 #endif
998 
999     bool drawCur = blinkCursorVisible && (hasFocus() || viewport()->hasFocus());
1000     if ((hasSelectedText() && !style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, 0, this)) ||
1001         isReadOnly() || !cursorVisible)
1002         drawCur = false;
1003     QPalette pal = palette();
1004     if (doc->paper())
1005         pal.setBrush(QPalette::Base, *doc->paper());
1006 
1007     if (contentsY() < doc->y()) {
1008         p->fillRect(contentsX(), contentsY(), visibleWidth(), doc->y(),
1009                      pal.base());
1010     }
1011     if (drawAll && doc->width() - contentsX() < cx + cw) {
1012         p->fillRect(doc->width() - contentsX(), cy, cx + cw - doc->width() + contentsX(), ch,
1013                      pal.base());
1014     }
1015 
1016     p->setBrushOrigin(-contentsX(), -contentsY());
1017 
1018     lastFormatted = doc->draw(p, cx, cy, cw, ch, pal, !drawAll, drawCur, cursor);
1019 
1020     if (lastFormatted == doc->lastParagraph())
1021         resizeContents(contentsWidth(), doc->height());
1022 
1023     if (contentsHeight() < visibleHeight() && (!doc->lastParagraph() || doc->lastParagraph()->isValid()) && drawAll)
1024         p->fillRect(0, contentsHeight(), visibleWidth(),
1025                      visibleHeight() - contentsHeight(), pal.base());
1026 }
1027 
1028 /*!
1029     \reimp
1030 */
1031 
drawContents(QPainter * p,int cx,int cy,int cw,int ch)1032 void Q3TextEdit::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
1033 {
1034 #ifdef QT_TEXTEDIT_OPTIMIZATION
1035     if (d->optimMode) {
1036         optimDrawContents(p, cx, cy, cw, ch);
1037         return;
1038     }
1039 #endif
1040     paintDocument(true, p, cx, cy, cw, ch);
1041     int v;
1042     p->setPen(palette().color(foregroundRole()));
1043     if (document()->isPageBreakEnabled() &&  (v = document()->flow()->pageSize()) > 0) {
1044         int l = int(cy / v) * v;
1045         while (l < cy + ch) {
1046             p->drawLine(cx, l, cx + cw - 1, l);
1047             l += v;
1048         }
1049     }
1050 }
1051 
1052 /*!
1053     \internal
1054 */
1055 
drawContents(QPainter * p)1056 void Q3TextEdit::drawContents(QPainter *p)
1057 {
1058     if (horizontalScrollBar()->isVisible() &&
1059          verticalScrollBar()->isVisible()) {
1060         const QRect verticalRect = verticalScrollBar()->geometry();
1061         const QRect horizontalRect = horizontalScrollBar()->geometry();
1062 
1063         QRect cornerRect;
1064         cornerRect.setTop(verticalRect.bottom());
1065         cornerRect.setBottom(horizontalRect.bottom());
1066         cornerRect.setLeft(verticalRect.left());
1067         cornerRect.setRight(verticalRect.right());
1068 
1069         p->fillRect(cornerRect, palette().background());
1070     }
1071 }
1072 
1073 /*!
1074     \reimp
1075 */
1076 
event(QEvent * e)1077 bool Q3TextEdit::event(QEvent *e)
1078 {
1079     if (e->type() == QEvent::AccelOverride && !isReadOnly()) {
1080         QKeyEvent* ke = (QKeyEvent*) e;
1081         switch(ke->state()) {
1082         case Qt::NoButton:
1083         case Qt::Keypad:
1084         case Qt::ShiftButton:
1085             if (ke->key() < Qt::Key_Escape) {
1086                 ke->accept();
1087             } else {
1088                 switch (ke->key()) {
1089                 case Qt::Key_Return:
1090                 case Qt::Key_Enter:
1091                 case Qt::Key_Delete:
1092                 case Qt::Key_Home:
1093                 case Qt::Key_End:
1094                 case Qt::Key_Backspace:
1095                 case Qt::Key_Left:
1096                 case Qt::Key_Right:
1097                     ke->accept();
1098                 default:
1099                     break;
1100                 }
1101             }
1102             break;
1103 
1104         case Qt::ControlButton:
1105         case Qt::ControlButton|Qt::ShiftButton:
1106         case Qt::ControlButton|Qt::Keypad:
1107         case Qt::ControlButton|Qt::ShiftButton|Qt::Keypad:
1108             switch (ke->key()) {
1109             case Qt::Key_Tab:
1110             case Qt::Key_Backtab:
1111                 ke->ignore();
1112                 break;
1113 // Those are too frequently used for application functionality
1114 /*            case Qt::Key_A:
1115             case Qt::Key_B:
1116             case Qt::Key_D:
1117             case Qt::Key_E:
1118             case Qt::Key_F:
1119             case Qt::Key_H:
1120             case Qt::Key_I:
1121             case Qt::Key_K:
1122             case Qt::Key_N:
1123             case Qt::Key_P:
1124             case Qt::Key_T:
1125 */
1126             case Qt::Key_C:
1127             case Qt::Key_V:
1128             case Qt::Key_X:
1129             case Qt::Key_Y:
1130             case Qt::Key_Z:
1131             case Qt::Key_Left:
1132             case Qt::Key_Right:
1133             case Qt::Key_Up:
1134             case Qt::Key_Down:
1135             case Qt::Key_Home:
1136             case Qt::Key_End:
1137 #if defined (Q_WS_WIN)
1138             case Qt::Key_Insert:
1139             case Qt::Key_Delete:
1140 #endif
1141                 ke->accept();
1142             default:
1143                 break;
1144             }
1145             break;
1146 
1147         default:
1148             switch (ke->key()) {
1149 #if defined (Q_WS_WIN)
1150             case Qt::Key_Insert:
1151                 ke->accept();
1152 #endif
1153             default:
1154                 break;
1155             }
1156             break;
1157         }
1158     }
1159 
1160     if (e->type() == QEvent::Show) {
1161         if (
1162 #ifdef QT_TEXTEDIT_OPTIMIZATION
1163              !d->optimMode &&
1164 #endif
1165              d->ensureCursorVisibleInShowEvent ) {
1166             ensureCursorVisible();
1167             d->ensureCursorVisibleInShowEvent = false;
1168         }
1169         if (!d->scrollToAnchor.isEmpty()) {
1170             scrollToAnchor(d->scrollToAnchor);
1171             d->scrollToAnchor.clear();
1172         }
1173     }
1174     return QWidget::event(e);
1175 }
1176 
1177 /*!
1178     Processes the key event, \a e. By default key events are used to
1179     provide keyboard navigation and text editing.
1180 */
1181 
keyPressEvent(QKeyEvent * e)1182 void Q3TextEdit::keyPressEvent(QKeyEvent *e)
1183 {
1184     changeIntervalTimer->stop();
1185     interval = 10;
1186     bool unknownKey = false;
1187     if (isReadOnly()) {
1188         if (!handleReadOnlyKeyEvent(e))
1189             Q3ScrollView::keyPressEvent(e);
1190         changeIntervalTimer->start(100, true);
1191         return;
1192     }
1193 
1194 
1195     bool selChanged = false;
1196     for (int i = 1; i < doc->numSelections(); ++i) // start with 1 as we don't want to remove the Standard-Selection
1197         selChanged = doc->removeSelection(i) || selChanged;
1198 
1199     if (selChanged) {
1200         cursor->paragraph()->document()->nextDoubleBuffered = true;
1201         repaintChanged();
1202     }
1203 
1204     bool clearUndoRedoInfo = true;
1205 
1206 
1207     switch (e->key()) {
1208     case Qt::Key_Left:
1209     case Qt::Key_Right: {
1210         // a bit hacky, but can't change this without introducing new enum values for move and keeping the
1211         // correct semantics and movement for BiDi and non BiDi text.
1212         CursorAction a;
1213         if (cursor->paragraph()->string()->isRightToLeft() == (e->key() == Qt::Key_Right))
1214             a = e->state() & Qt::ControlButton ? MoveWordBackward : MoveBackward;
1215         else
1216             a = e->state() & Qt::ControlButton ? MoveWordForward : MoveForward;
1217         moveCursor(a, e->state() & Qt::ShiftButton);
1218         break;
1219     }
1220     case Qt::Key_Up:
1221         moveCursor(e->state() & Qt::ControlButton ? MovePgUp : MoveUp, e->state() & Qt::ShiftButton);
1222         break;
1223     case Qt::Key_Down:
1224         moveCursor(e->state() & Qt::ControlButton ? MovePgDown : MoveDown, e->state() & Qt::ShiftButton);
1225         break;
1226     case Qt::Key_Home:
1227         moveCursor(e->state() & Qt::ControlButton ? MoveHome : MoveLineStart, e->state() & Qt::ShiftButton);
1228         break;
1229     case Qt::Key_End:
1230         moveCursor(e->state() & Qt::ControlButton ? MoveEnd : MoveLineEnd, e->state() & Qt::ShiftButton);
1231         break;
1232     case Qt::Key_Prior:
1233         moveCursor(MovePgUp, e->state() & Qt::ShiftButton);
1234         break;
1235     case Qt::Key_Next:
1236         moveCursor(MovePgDown, e->state() & Qt::ShiftButton);
1237         break;
1238     case Qt::Key_Return: case Qt::Key_Enter:
1239         if (doc->hasSelection(Q3TextDocument::Standard, false))
1240             removeSelectedText();
1241         if (textFormat() == Qt::RichText && (e->state() & Qt::ControlButton)) {
1242             // Ctrl-Enter inserts a line break in rich text mode
1243             insert(QString(QChar(QChar::LineSeparator)), true, false);
1244         } else {
1245 #ifndef QT_NO_CURSOR
1246             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
1247 #endif
1248             clearUndoRedoInfo = false;
1249             doKeyboardAction(ActionReturn);
1250             emit returnPressed();
1251         }
1252         break;
1253     case Qt::Key_Delete:
1254 #if defined (Q_WS_WIN)
1255         if (e->state() & Qt::ShiftButton) {
1256             cut();
1257             break;
1258         } else
1259 #endif
1260         if (doc->hasSelection(Q3TextDocument::Standard, true)) {
1261             removeSelectedText();
1262             break;
1263         }
1264         doKeyboardAction(e->state() & Qt::ControlButton ? ActionWordDelete
1265                           : ActionDelete);
1266         clearUndoRedoInfo = false;
1267 
1268         break;
1269     case Qt::Key_Insert:
1270         if (e->state() & Qt::ShiftButton)
1271             paste();
1272 #if defined (Q_WS_WIN)
1273         else if (e->state() & Qt::ControlButton)
1274             copy();
1275 #endif
1276         else
1277             setOverwriteMode(!isOverwriteMode());
1278         break;
1279     case Qt::Key_Backspace:
1280 #if defined (Q_WS_WIN)
1281         if (e->state() & Qt::AltButton) {
1282             if (e->state() & Qt::ControlButton) {
1283                 break;
1284             } else if (e->state() & Qt::ShiftButton) {
1285                 redo();
1286                 break;
1287             } else {
1288                 undo();
1289                 break;
1290             }
1291         } else
1292 #endif
1293         if (doc->hasSelection(Q3TextDocument::Standard, true)) {
1294             removeSelectedText();
1295             break;
1296         }
1297 
1298         doKeyboardAction(e->state() & Qt::ControlButton ? ActionWordBackspace
1299                           : ActionBackspace);
1300         clearUndoRedoInfo = false;
1301         break;
1302     case Qt::Key_F16: // Copy key on Sun keyboards
1303         copy();
1304         break;
1305     case Qt::Key_F18:  // Paste key on Sun keyboards
1306         paste();
1307         break;
1308     case Qt::Key_F20:  // Cut key on Sun keyboards
1309         cut();
1310         break;
1311     case Qt::Key_Direction_L:
1312         if (doc->textFormat() == Qt::PlainText) {
1313             // change the whole doc
1314             Q3TextParagraph *p = doc->firstParagraph();
1315             while (p) {
1316                 p->setDirection(QChar::DirL);
1317                 p->setAlignment(Qt::AlignLeft);
1318                 p->invalidate(0);
1319                 p = p->next();
1320             }
1321         } else {
1322             if (!cursor->paragraph() || cursor->paragraph()->direction() == QChar::DirL)
1323                 return;
1324             cursor->paragraph()->setDirection(QChar::DirL);
1325             if (cursor->paragraph()->length() <= 1&&
1326                  ((cursor->paragraph()->alignment() & (Qt::AlignLeft | Qt::AlignRight)) != 0))
1327                 setAlignment(Qt::AlignLeft);
1328         }
1329         repaintChanged();
1330         break;
1331     case Qt::Key_Direction_R:
1332         if (doc->textFormat() == Qt::PlainText) {
1333             // change the whole doc
1334             Q3TextParagraph *p = doc->firstParagraph();
1335             while (p) {
1336                 p->setDirection(QChar::DirR);
1337                 p->setAlignment(Qt::AlignRight);
1338                 p->invalidate(0);
1339                 p = p->next();
1340             }
1341         } else {
1342             if (!cursor->paragraph() || cursor->paragraph()->direction() == QChar::DirR)
1343                 return;
1344             cursor->paragraph()->setDirection(QChar::DirR);
1345             if (cursor->paragraph()->length() <= 1&&
1346                  ((cursor->paragraph()->alignment() & (Qt::AlignLeft | Qt::AlignRight)) != 0))
1347                 setAlignment(Qt::AlignRight);
1348         }
1349         repaintChanged();
1350         break;
1351     default: {
1352             unsigned char ascii = e->text().length() ? e->text().unicode()->latin1() : 0;
1353             if (e->text().length() &&
1354                 ((!(e->state() & Qt::ControlButton) &&
1355 #ifndef Q_OS_MAC
1356                   !(e->state() & Qt::AltButton) &&
1357 #endif
1358                   !(e->state() & Qt::MetaButton)) ||
1359                  (((e->state() & (Qt::ControlButton | Qt::AltButton))) == (Qt::ControlButton|Qt::AltButton))) &&
1360                  (!ascii || ascii >= 32 || e->text() == QString(QLatin1Char('\t')))) {
1361                 clearUndoRedoInfo = false;
1362                 if (e->key() == Qt::Key_Tab) {
1363                     if (d->tabChangesFocus) {
1364                         e->ignore();
1365                         break;
1366                     }
1367                     if (textFormat() == Qt::RichText && cursor->index() == 0
1368                          && (cursor->paragraph()->isListItem() || cursor->paragraph()->listDepth())) {
1369                         clearUndoRedo();
1370                         undoRedoInfo.type = UndoRedoInfo::Style;
1371                         undoRedoInfo.id = cursor->paragraph()->paragId();
1372                         undoRedoInfo.eid = undoRedoInfo.id;
1373                         undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid);
1374                         cursor->paragraph()->setListDepth(cursor->paragraph()->listDepth() +1);
1375                         clearUndoRedo();
1376                         drawCursor(false);
1377                         repaintChanged();
1378                         drawCursor(true);
1379                         break;
1380                     }
1381                 } else if (e->key() == Qt::Key_BackTab) {
1382                     if (d->tabChangesFocus) {
1383                         e->ignore();
1384                         break;
1385                     }
1386                 }
1387 
1388                 if ((autoFormatting() & AutoBulletList) &&
1389                      textFormat() == Qt::RichText && cursor->index() == 0
1390                      && !cursor->paragraph()->isListItem()
1391                      && (e->text()[0] == QLatin1Char('-') || e->text()[0] == QLatin1Char('*'))) {
1392                         clearUndoRedo();
1393                         undoRedoInfo.type = UndoRedoInfo::Style;
1394                         undoRedoInfo.id = cursor->paragraph()->paragId();
1395                         undoRedoInfo.eid = undoRedoInfo.id;
1396                         undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid);
1397                         setParagType(Q3StyleSheetItem::DisplayListItem, Q3StyleSheetItem::ListDisc);
1398                         clearUndoRedo();
1399                         drawCursor(false);
1400                         repaintChanged();
1401                         drawCursor(true);
1402                         break;
1403                 }
1404 		if (overWrite && !cursor->atParagEnd() && !doc->hasSelection(Q3TextDocument::Standard)) {
1405                     doKeyboardAction(ActionDelete);
1406                     clearUndoRedoInfo = false;
1407                 }
1408                 QString t = e->text();
1409                 insert(t, true, false);
1410                 break;
1411             } else if (e->state() & Qt::ControlButton) {
1412                 switch (e->key()) {
1413                 case Qt::Key_C: case Qt::Key_F16: // Copy key on Sun keyboards
1414                     copy();
1415                     break;
1416                 case Qt::Key_V:
1417                     paste();
1418                     break;
1419                 case Qt::Key_X:
1420                     cut();
1421                     break;
1422                 case Qt::Key_I: case Qt::Key_T: case Qt::Key_Tab:
1423                     if (!d->tabChangesFocus)
1424                         indent();
1425                     break;
1426                 case Qt::Key_A:
1427 #if defined(Q_WS_X11)
1428                     moveCursor(MoveLineStart, e->state() & Qt::ShiftButton);
1429 #else
1430                     selectAll(true);
1431 #endif
1432                     break;
1433                 case Qt::Key_B:
1434                     moveCursor(MoveBackward, e->state() & Qt::ShiftButton);
1435                     break;
1436                 case Qt::Key_F:
1437                     moveCursor(MoveForward, e->state() & Qt::ShiftButton);
1438                     break;
1439                 case Qt::Key_D:
1440                     if (doc->hasSelection(Q3TextDocument::Standard)) {
1441                         removeSelectedText();
1442                         break;
1443                     }
1444                     doKeyboardAction(ActionDelete);
1445                     clearUndoRedoInfo = false;
1446                     break;
1447                 case Qt::Key_H:
1448                     if (doc->hasSelection(Q3TextDocument::Standard)) {
1449                         removeSelectedText();
1450                         break;
1451                     }
1452                     if (!cursor->paragraph()->prev() &&
1453                          cursor->atParagStart())
1454                         break;
1455 
1456                     doKeyboardAction(ActionBackspace);
1457                     clearUndoRedoInfo = false;
1458                     break;
1459                 case Qt::Key_E:
1460                     moveCursor(MoveLineEnd, e->state() & Qt::ShiftButton);
1461                     break;
1462                 case Qt::Key_N:
1463                     moveCursor(MoveDown, e->state() & Qt::ShiftButton);
1464                     break;
1465                 case Qt::Key_P:
1466                     moveCursor(MoveUp, e->state() & Qt::ShiftButton);
1467                     break;
1468                 case Qt::Key_Z:
1469                     if(e->state() & Qt::ShiftButton)
1470                         redo();
1471                     else
1472                         undo();
1473                     break;
1474                 case Qt::Key_Y:
1475                     redo();
1476                     break;
1477                 case Qt::Key_K:
1478                     doKeyboardAction(ActionKill);
1479                     break;
1480 #if defined(Q_WS_WIN)
1481                 case Qt::Key_Insert:
1482                     copy();
1483                     break;
1484                 case Qt::Key_Delete:
1485                     del();
1486                     break;
1487 #endif
1488                 default:
1489                     unknownKey = false;
1490                     break;
1491                 }
1492             } else {
1493                 unknownKey = true;
1494             }
1495         }
1496     }
1497 
1498     emit cursorPositionChanged(cursor);
1499     emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
1500     if (clearUndoRedoInfo)
1501         clearUndoRedo();
1502     changeIntervalTimer->start(100, true);
1503     if (unknownKey)
1504         e->ignore();
1505 }
1506 
1507 /*!
1508     \reimp
1509 */
inputMethodEvent(QInputMethodEvent * e)1510 void Q3TextEdit::inputMethodEvent(QInputMethodEvent *e)
1511 {
1512     if (isReadOnly()) {
1513         e->ignore();
1514         return;
1515     }
1516 
1517     if (hasSelectedText())
1518         removeSelectedText();
1519     clearUndoRedo();
1520     undoRedoInfo.type = UndoRedoInfo::IME;
1521 
1522     bool oldupdate = updatesEnabled();
1523     if (oldupdate)
1524         setUpdatesEnabled(false);
1525     bool sigs_blocked = signalsBlocked();
1526     blockSignals(true);
1527     const int preeditSelectionBase = 31900;
1528     for (int i = 0; i < d->numPreeditSelections; ++i)
1529         doc->removeSelection(preeditSelectionBase + i);
1530     d->numPreeditSelections = 0;
1531 
1532     if (d->preeditLength > 0 && cursor->paragraph()) {
1533         cursor->setIndex(d->preeditStart);
1534         cursor->paragraph()->remove(d->preeditStart, d->preeditLength);
1535         d->preeditStart = d->preeditLength = -1;
1536     }
1537 
1538     if (!e->commitString().isEmpty() || e->replacementLength()) {
1539         int c = cursor->index(); // cursor position after insertion of commit string
1540         if (e->replacementStart() <= 0)
1541             c += e->commitString().length() + qMin(-e->replacementStart(), e->replacementLength());
1542         cursor->setIndex(cursor->index() + e->replacementStart());
1543         doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
1544         cursor->setIndex(cursor->index() + e->replacementLength());
1545         doc->setSelectionEnd(Q3TextDocument::Standard, *cursor);
1546         removeSelectedText();
1547 	if (undoRedoInfo.type == UndoRedoInfo::IME)
1548 	    undoRedoInfo.type = UndoRedoInfo::Invalid;
1549         insert(e->commitString());
1550 	undoRedoInfo.type = UndoRedoInfo::IME;
1551         cursor->setIndex(c);
1552     }
1553 
1554     if (!e->preeditString().isEmpty()) {
1555         d->preeditStart = cursor->index();
1556         d->preeditLength = e->preeditString().length();
1557 	insert(e->preeditString());
1558         cursor->setIndex(d->preeditStart);
1559 
1560         Q3TextCursor c = *cursor;
1561         for (int i = 0; i < e->attributes().size(); ++i) {
1562             const QInputMethodEvent::Attribute &a = e->attributes().at(i);
1563             if (a.type == QInputMethodEvent::Cursor)
1564                 cursor->setIndex(cursor->index() + a.start);
1565             else if (a.type != QInputMethodEvent::TextFormat)
1566                 continue;
1567             QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
1568             if (f.isValid()) {
1569                 Q3TextCursor c2 = c;
1570                 c2.setIndex(c.index() + a.start);
1571                 doc->setSelectionStart(preeditSelectionBase + d->numPreeditSelections, c2);
1572                 c2.setIndex(c.index() + a.start + a.length);
1573                 doc->setSelectionEnd(preeditSelectionBase + d->numPreeditSelections, c2);
1574 
1575                 QColor c = f.hasProperty(QTextFormat::BackgroundBrush) ? f.background().color() : QColor();
1576                 doc->setSelectionColor(preeditSelectionBase + d->numPreeditSelections, c);
1577                 c = f.hasProperty(QTextFormat::ForegroundBrush) ? f.foreground().color() : QColor();
1578                 doc->setSelectionTextColor(preeditSelectionBase + d->numPreeditSelections, c);
1579                 if (f.fontUnderline()) {
1580                     Q3TextParagraph *par = cursor->paragraph();
1581                     Q3TextFormat f(*par->string()->at(d->preeditStart).format());
1582                     f.setUnderline(true);
1583                     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
1584                     par->setFormat(d->preeditStart + a.start, a.length, f2);
1585                 }
1586                 ++d->numPreeditSelections;
1587             }
1588 	}
1589     } else {
1590 	undoRedoInfo.type = UndoRedoInfo::Invalid;
1591     }
1592     blockSignals(sigs_blocked);
1593     if (oldupdate)
1594         setUpdatesEnabled(true);
1595     if (!e->commitString().isEmpty())
1596         emit textChanged();
1597     repaintChanged();
1598 }
1599 
1600 
1601 static bool qtextedit_ignore_readonly = false;
1602 
1603 /*!
1604     Executes keyboard action \a action. This is normally called by a
1605     key event handler.
1606 */
1607 
doKeyboardAction(Q3TextEdit::KeyboardAction action)1608 void Q3TextEdit::doKeyboardAction(Q3TextEdit::KeyboardAction action)
1609 {
1610     if (isReadOnly() && !qtextedit_ignore_readonly)
1611         return;
1612 
1613     if (cursor->nestedDepth() != 0)
1614         return;
1615 
1616     lastFormatted = cursor->paragraph();
1617     drawCursor(false);
1618     bool doUpdateCurrentFormat = true;
1619 
1620     switch (action) {
1621     case ActionWordDelete:
1622     case ActionDelete:
1623         if (action == ActionDelete && !cursor->atParagEnd()) {
1624             if (undoEnabled) {
1625                 checkUndoRedoInfo(UndoRedoInfo::Delete);
1626                 if (!undoRedoInfo.valid()) {
1627                     undoRedoInfo.id = cursor->paragraph()->paragId();
1628                     undoRedoInfo.index = cursor->index();
1629                     undoRedoInfo.d->text.clear();
1630                 }
1631                 int idx = cursor->index();
1632                 do {
1633                     undoRedoInfo.d->text.insert(undoRedoInfo.d->text.length(), cursor->paragraph()->at(idx++), true);
1634                 } while (!cursor->paragraph()->string()->validCursorPosition(idx));
1635             }
1636             cursor->remove();
1637         } else {
1638             clearUndoRedo();
1639             doc->setSelectionStart(Q3TextDocument::Temp, *cursor);
1640             if (action == ActionWordDelete && !cursor->atParagEnd()) {
1641                 cursor->gotoNextWord();
1642             } else {
1643                 cursor->gotoNextLetter();
1644             }
1645             doc->setSelectionEnd(Q3TextDocument::Temp, *cursor);
1646             removeSelectedText(Q3TextDocument::Temp);
1647         }
1648         break;
1649     case ActionWordBackspace:
1650     case ActionBackspace:
1651         if (textFormat() == Qt::RichText
1652              && (cursor->paragraph()->isListItem()
1653                  || cursor->paragraph()->listDepth())
1654              && cursor->index() == 0) {
1655             if (undoEnabled) {
1656                 clearUndoRedo();
1657                 undoRedoInfo.type = UndoRedoInfo::Style;
1658                 undoRedoInfo.id = cursor->paragraph()->paragId();
1659                 undoRedoInfo.eid = undoRedoInfo.id;
1660                 undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid);
1661             }
1662             int ldepth = cursor->paragraph()->listDepth();
1663             if (cursor->paragraph()->isListItem() && ldepth == 1) {
1664                 cursor->paragraph()->setListItem(false);
1665             } else if (qMax(ldepth, 1) == 1) {
1666                 cursor->paragraph()->setListItem(false);
1667                 cursor->paragraph()->setListDepth(0);
1668             } else {
1669                 cursor->paragraph()->setListDepth(ldepth - 1);
1670             }
1671             clearUndoRedo();
1672             lastFormatted = cursor->paragraph();
1673             repaintChanged();
1674             drawCursor(true);
1675             return;
1676         }
1677 
1678         if (action == ActionBackspace && !cursor->atParagStart()) {
1679             if (undoEnabled) {
1680                 checkUndoRedoInfo(UndoRedoInfo::Delete);
1681                 if (!undoRedoInfo.valid()) {
1682                     undoRedoInfo.id = cursor->paragraph()->paragId();
1683                     undoRedoInfo.index = cursor->index();
1684                     undoRedoInfo.d->text.clear();
1685                 }
1686                 undoRedoInfo.d->text.insert(0, cursor->paragraph()->at(cursor->index()-1), true);
1687                 undoRedoInfo.index = cursor->index()-1;
1688             }
1689             cursor->removePreviousChar();
1690             lastFormatted = cursor->paragraph();
1691         } else if (cursor->paragraph()->prev()
1692                     || (action == ActionWordBackspace
1693                         && !cursor->atParagStart())) {
1694             clearUndoRedo();
1695             doc->setSelectionStart(Q3TextDocument::Temp, *cursor);
1696             if (action == ActionWordBackspace && !cursor->atParagStart()) {
1697                 cursor->gotoPreviousWord();
1698             } else {
1699                 cursor->gotoPreviousLetter();
1700             }
1701             doc->setSelectionEnd(Q3TextDocument::Temp, *cursor);
1702             removeSelectedText(Q3TextDocument::Temp);
1703         }
1704         break;
1705     case ActionReturn:
1706         if (undoEnabled) {
1707             checkUndoRedoInfo(UndoRedoInfo::Return);
1708             if (!undoRedoInfo.valid()) {
1709                 undoRedoInfo.id = cursor->paragraph()->paragId();
1710                 undoRedoInfo.index = cursor->index();
1711                 undoRedoInfo.d->text.clear();
1712             }
1713             undoRedoInfo.d->text += QString(QLatin1Char('\n'));
1714         }
1715         cursor->splitAndInsertEmptyParagraph();
1716         if (cursor->paragraph()->prev()) {
1717             lastFormatted = cursor->paragraph()->prev();
1718             lastFormatted->invalidate(0);
1719         }
1720         doUpdateCurrentFormat = false;
1721         break;
1722     case ActionKill:
1723         clearUndoRedo();
1724         doc->setSelectionStart(Q3TextDocument::Temp, *cursor);
1725         if (cursor->atParagEnd())
1726             cursor->gotoNextLetter();
1727         else
1728             cursor->setIndex(cursor->paragraph()->length() - 1);
1729         doc->setSelectionEnd(Q3TextDocument::Temp, *cursor);
1730         removeSelectedText(Q3TextDocument::Temp);
1731         break;
1732     }
1733 
1734     formatMore();
1735     repaintChanged();
1736     ensureCursorVisible();
1737     drawCursor(true);
1738     if (doUpdateCurrentFormat)
1739         updateCurrentFormat();
1740     setModified();
1741     emit textChanged();
1742 }
1743 
readFormats(Q3TextCursor & c1,Q3TextCursor & c2,Q3TextString & text,bool fillStyles)1744 void Q3TextEdit::readFormats(Q3TextCursor &c1, Q3TextCursor &c2, Q3TextString &text, bool fillStyles)
1745 {
1746 #ifndef QT_NO_DATASTREAM
1747     QDataStream styleStream(&undoRedoInfo.styleInformation, IO_WriteOnly);
1748 #endif
1749     c2.restoreState();
1750     c1.restoreState();
1751     int lastIndex = text.length();
1752     if (c1.paragraph() == c2.paragraph()) {
1753         for (int i = c1.index(); i < c2.index(); ++i)
1754             text.insert(lastIndex + i - c1.index(), c1.paragraph()->at(i), true);
1755 #ifndef QT_NO_DATASTREAM
1756         if (fillStyles) {
1757             styleStream << (int) 1;
1758             c1.paragraph()->writeStyleInformation(styleStream);
1759         }
1760 #endif
1761     } else {
1762         int i;
1763         for (i = c1.index(); i < c1.paragraph()->length()-1; ++i)
1764             text.insert(lastIndex++, c1.paragraph()->at(i), true);
1765         int num = 2; // start and end, being different
1766         text += QString(QLatin1Char('\n')); lastIndex++;
1767 
1768         if (c1.paragraph()->next() != c2.paragraph()) {
1769             num += text.appendParagraphs(c1.paragraph()->next(), c2.paragraph());
1770             lastIndex = text.length();
1771         }
1772 
1773         for (i = 0; i < c2.index(); ++i)
1774             text.insert(i + lastIndex, c2.paragraph()->at(i), true);
1775 #ifndef QT_NO_DATASTREAM
1776         if (fillStyles) {
1777             styleStream << num;
1778             for (Q3TextParagraph *p = c1.paragraph(); --num >= 0; p = p->next())
1779                 p->writeStyleInformation(styleStream);
1780         }
1781 #endif
1782     }
1783 }
1784 
1785 /*!
1786     Removes the selection \a selNum (by default 0). This does not
1787     remove the selected text.
1788 
1789     \sa removeSelectedText()
1790 */
1791 
removeSelection(int selNum)1792 void Q3TextEdit::removeSelection(int selNum)
1793 {
1794     doc->removeSelection(selNum);
1795     repaintChanged();
1796 }
1797 
1798 /*!
1799     Deletes the text of selection \a selNum (by default, the default
1800     selection, 0). If there is no selected text nothing happens.
1801 
1802     \sa selectedText removeSelection()
1803 */
1804 
removeSelectedText(int selNum)1805 void Q3TextEdit::removeSelectedText(int selNum)
1806 {
1807     Q3TextCursor c1 = doc->selectionStartCursor(selNum);
1808     c1.restoreState();
1809     Q3TextCursor c2 = doc->selectionEndCursor(selNum);
1810     c2.restoreState();
1811 
1812     // ### no support for editing tables yet, plus security for broken selections
1813     if (c1.nestedDepth() || c2.nestedDepth())
1814         return;
1815 
1816     for (int i = 0; i < (int)doc->numSelections(); ++i) {
1817         if (i == selNum)
1818             continue;
1819         doc->removeSelection(i);
1820     }
1821 
1822     drawCursor(false);
1823     if (undoEnabled) {
1824         checkUndoRedoInfo(UndoRedoInfo::RemoveSelected);
1825         if (!undoRedoInfo.valid()) {
1826             doc->selectionStart(selNum, undoRedoInfo.id, undoRedoInfo.index);
1827             undoRedoInfo.d->text.clear();
1828         }
1829         readFormats(c1, c2, undoRedoInfo.d->text, true);
1830     }
1831 
1832     doc->removeSelectedText(selNum, cursor);
1833     if (cursor->isValid()) {
1834         lastFormatted = 0; // make sync a noop
1835         ensureCursorVisible();
1836         lastFormatted = cursor->paragraph();
1837         formatMore();
1838         repaintContents();
1839         ensureCursorVisible();
1840         drawCursor(true);
1841         clearUndoRedo();
1842 #if defined(Q_WS_WIN)
1843         // there seems to be a problem with repainting or erasing the area
1844         // of the scrollview which is not the contents on windows
1845         if (contentsHeight() < visibleHeight())
1846             viewport()->repaint(0, contentsHeight(), visibleWidth(), visibleHeight() - contentsHeight());
1847 #endif
1848 #ifndef QT_NO_CURSOR
1849         viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
1850 #endif
1851     } else {
1852         lastFormatted = doc->firstParagraph();
1853         delete cursor;
1854         cursor = new Q3TextCursor(doc);
1855         drawCursor(true);
1856         repaintContents();
1857     }
1858     setModified();
1859     emit textChanged();
1860     emit selectionChanged();
1861     emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard));
1862 }
1863 
1864 /*!
1865     Moves the text cursor according to \a action. This is normally
1866     used by some key event handler. \a select specifies whether the
1867     text between the current cursor position and the new position
1868     should be selected.
1869 */
1870 
moveCursor(Q3TextEdit::CursorAction action,bool select)1871 void Q3TextEdit::moveCursor(Q3TextEdit::CursorAction action, bool select)
1872 {
1873 #ifdef QT_TEXTEDIT_OPTIMIZATION
1874     if (d->optimMode)
1875         return;
1876 #endif
1877 #ifdef Q_WS_MAC
1878     Q3TextCursor c1 = *cursor;
1879     Q3TextCursor c2;
1880 #endif
1881     drawCursor(false);
1882     if (select) {
1883         if (!doc->hasSelection(Q3TextDocument::Standard))
1884             doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
1885         moveCursor(action);
1886 #ifdef Q_WS_MAC
1887         c2 = *cursor;
1888         if (c1 == c2)
1889             if (action == MoveDown || action == MovePgDown)
1890                 moveCursor(MoveEnd);
1891             else if (action == MoveUp || action == MovePgUp)
1892                 moveCursor(MoveHome);
1893 #endif
1894         if (doc->setSelectionEnd(Q3TextDocument::Standard, *cursor)) {
1895             cursor->paragraph()->document()->nextDoubleBuffered = true;
1896             repaintChanged();
1897         } else {
1898             drawCursor(true);
1899         }
1900         ensureCursorVisible();
1901         emit selectionChanged();
1902         emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard));
1903     } else {
1904 #ifdef Q_WS_MAC
1905         Q3TextCursor cStart = doc->selectionStartCursor(Q3TextDocument::Standard);
1906         Q3TextCursor cEnd = doc->selectionEndCursor(Q3TextDocument::Standard);
1907         bool redraw = doc->removeSelection(Q3TextDocument::Standard);
1908         if (redraw && action == MoveDown)
1909             *cursor = cEnd;
1910         else if (redraw && action == MoveUp)
1911             *cursor = cStart;
1912         if (redraw && action == MoveForward)
1913             *cursor = cEnd;
1914         else if (redraw && action == MoveBackward)
1915             *cursor = cStart;
1916         else
1917             moveCursor(action);
1918         c2 = *cursor;
1919         if (c1 == c2)
1920             if (action == MoveDown)
1921                 moveCursor(MoveEnd);
1922             else if (action == MoveUp)
1923                 moveCursor(MoveHome);
1924 #else
1925         bool redraw = doc->removeSelection(Q3TextDocument::Standard);
1926         moveCursor(action);
1927 #endif
1928         if (!redraw) {
1929             ensureCursorVisible();
1930             drawCursor(true);
1931         } else {
1932             cursor->paragraph()->document()->nextDoubleBuffered = true;
1933             repaintChanged();
1934             ensureCursorVisible();
1935             drawCursor(true);
1936 #ifndef QT_NO_CURSOR
1937             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
1938 #endif
1939         }
1940         if (redraw) {
1941             emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard));
1942             emit selectionChanged();
1943         }
1944     }
1945 
1946     drawCursor(true);
1947     updateCurrentFormat();
1948 }
1949 
1950 /*!
1951     \overload
1952 */
1953 
moveCursor(Q3TextEdit::CursorAction action)1954 void Q3TextEdit::moveCursor(Q3TextEdit::CursorAction action)
1955 {
1956     resetInputContext();
1957     switch (action) {
1958     case MoveBackward:
1959         cursor->gotoPreviousLetter();
1960         break;
1961     case MoveWordBackward:
1962         cursor->gotoPreviousWord();
1963         break;
1964     case MoveForward:
1965         cursor->gotoNextLetter();
1966         break;
1967     case MoveWordForward:
1968         cursor->gotoNextWord();
1969         break;
1970     case MoveUp:
1971         cursor->gotoUp();
1972         break;
1973     case MovePgUp:
1974         cursor->gotoPageUp(visibleHeight());
1975         break;
1976     case MoveDown:
1977         cursor->gotoDown();
1978         break;
1979     case MovePgDown:
1980         cursor->gotoPageDown(visibleHeight());
1981         break;
1982     case MoveLineStart:
1983         cursor->gotoLineStart();
1984         break;
1985     case MoveHome:
1986         cursor->gotoHome();
1987         break;
1988     case MoveLineEnd:
1989         cursor->gotoLineEnd();
1990         break;
1991     case MoveEnd:
1992         ensureFormatted(doc->lastParagraph());
1993         cursor->gotoEnd();
1994         break;
1995     }
1996     updateCurrentFormat();
1997 }
1998 
1999 /*!
2000     \reimp
2001 */
2002 
resizeEvent(QResizeEvent * e)2003 void Q3TextEdit::resizeEvent(QResizeEvent *e)
2004 {
2005     Q3ScrollView::resizeEvent(e);
2006     if (doc->visibleWidth() == 0)
2007         doResize();
2008 }
2009 
2010 /*!
2011     \reimp
2012 */
2013 
viewportResizeEvent(QResizeEvent * e)2014 void Q3TextEdit::viewportResizeEvent(QResizeEvent *e)
2015 {
2016     Q3ScrollView::viewportResizeEvent(e);
2017     if (e->oldSize().width() != e->size().width()) {
2018         bool stayAtBottom = e->oldSize().height() != e->size().height() &&
2019                contentsY() > 0 && contentsY() >= doc->height() - e->oldSize().height();
2020         doResize();
2021         if (stayAtBottom)
2022             scrollToBottom();
2023     }
2024 }
2025 
2026 /*!
2027     Ensures that the cursor is visible by scrolling the text edit if
2028     necessary.
2029 
2030     \sa setCursorPosition()
2031 */
2032 
ensureCursorVisible()2033 void Q3TextEdit::ensureCursorVisible()
2034 {
2035     // Not visible or the user is dragging the window, so don't position to caret yet
2036     if (!updatesEnabled() || !isVisible() || isHorizontalSliderPressed() || isVerticalSliderPressed()) {
2037         d->ensureCursorVisibleInShowEvent = true;
2038         return;
2039     }
2040     sync();
2041     Q3TextStringChar *chr = cursor->paragraph()->at(cursor->index());
2042     int h = cursor->paragraph()->lineHeightOfChar(cursor->index());
2043     int x = cursor->paragraph()->rect().x() + chr->x + cursor->offsetX();
2044     int y = 0; int dummy;
2045     cursor->paragraph()->lineHeightOfChar(cursor->index(), &dummy, &y);
2046     y += cursor->paragraph()->rect().y() + cursor->offsetY();
2047     int w = 1;
2048     ensureVisible(x, y + h / 2, w, h / 2 + 2);
2049 }
2050 
2051 /*!
2052     \internal
2053 */
sliderReleased()2054 void Q3TextEdit::sliderReleased()
2055 {
2056     if (d->ensureCursorVisibleInShowEvent && isVisible()) {
2057         d->ensureCursorVisibleInShowEvent = false;
2058         ensureCursorVisible();
2059     }
2060 }
2061 
2062 /*!
2063     \internal
2064 
2065     If \a visible is true, the cursor is shown; otherwise it is
2066     hidden.
2067 */
drawCursor(bool visible)2068 void Q3TextEdit::drawCursor(bool visible)
2069 {
2070     d->cursorRepaintMode = true;
2071     blinkCursorVisible = visible;
2072     QRect r(cursor->topParagraph()->rect());
2073     if (!cursor->nestedDepth()) {
2074         int h = cursor->paragraph()->lineHeightOfChar(cursor->index());
2075         r = QRect(r.x(), r.y() + cursor->y(), r.width(), h);
2076     }
2077     r.moveBy(-contentsX(), -contentsY());
2078     viewport()->update(r);
2079 }
2080 
2081 enum {
2082     IdUndo = 0,
2083     IdRedo = 1,
2084     IdCut = 2,
2085     IdCopy = 3,
2086     IdPaste = 4,
2087     IdClear = 5,
2088     IdSelectAll = 6
2089 };
2090 
2091 /*!
2092     \reimp
2093 */
2094 #ifndef QT_NO_WHEELEVENT
contentsWheelEvent(QWheelEvent * e)2095 void Q3TextEdit::contentsWheelEvent(QWheelEvent *e)
2096 {
2097     if (isReadOnly()) {
2098         if (e->state() & Qt::ControlButton) {
2099             if (e->delta() > 0)
2100                 zoomOut();
2101             else if (e->delta() < 0)
2102                 zoomIn();
2103             return;
2104         }
2105     }
2106     Q3ScrollView::contentsWheelEvent(e);
2107 }
2108 #endif
2109 
2110 /*!
2111     \reimp
2112 */
2113 
contentsMousePressEvent(QMouseEvent * e)2114 void Q3TextEdit::contentsMousePressEvent(QMouseEvent *e)
2115 {
2116 #ifdef QT_TEXTEDIT_OPTIMIZATION
2117     if (d->optimMode) {
2118         optimMousePressEvent(e);
2119         return;
2120     }
2121 #endif
2122 
2123 #if !defined(QT_NO_IM)
2124     if (e->button() == Qt::LeftButton && d->preeditLength > 0 && cursor->paragraph()) {
2125         Q3TextCursor c = *cursor;
2126         placeCursor(e->pos(), &c, false);
2127         inputContext()->mouseHandler(c.index() - d->preeditStart, e);
2128         if (d->preeditLength > 0)
2129             return;
2130     }
2131 #endif
2132 
2133     if (d->trippleClickTimer->isActive() &&
2134          (e->globalPos() - d->trippleClickPoint).manhattanLength() <
2135          QApplication::startDragDistance()) {
2136         Q3TextCursor c1 = *cursor;
2137         Q3TextCursor c2 = *cursor;
2138         c1.gotoLineStart();
2139         c2.gotoLineEnd();
2140         doc->setSelectionStart(Q3TextDocument::Standard, c1);
2141         doc->setSelectionEnd(Q3TextDocument::Standard, c2);
2142         *cursor = c2;
2143         repaintChanged();
2144         mousePressed = true;
2145         return;
2146     }
2147 
2148     clearUndoRedo();
2149     Q3TextCursor oldCursor = *cursor;
2150     Q3TextCursor c = *cursor;
2151     mousePos = e->pos();
2152     mightStartDrag = false;
2153     pressedLink.clear();
2154     d->pressedName.clear();
2155 
2156     if (e->button() == Qt::LeftButton) {
2157         mousePressed = true;
2158         drawCursor(false);
2159         placeCursor(e->pos());
2160         ensureCursorVisible();
2161 
2162         if (isReadOnly() && linksEnabled()) {
2163             Q3TextCursor c = *cursor;
2164             placeCursor(e->pos(), &c, true);
2165             if (c.paragraph() && c.paragraph()->at(c.index()) &&
2166                  c.paragraph()->at(c.index())->isAnchor()) {
2167                 pressedLink = c.paragraph()->at(c.index())->anchorHref();
2168                 d->pressedName = c.paragraph()->at(c.index())->anchorName();
2169             }
2170         }
2171 
2172 #ifndef QT_NO_DRAGANDDROP
2173         if (doc->inSelection(Q3TextDocument::Standard, e->pos())) {
2174             mightStartDrag = true;
2175             drawCursor(true);
2176             dragStartTimer->start(QApplication::startDragTime(), true);
2177             dragStartPos = e->pos();
2178             return;
2179         }
2180 #endif
2181 
2182         bool redraw = false;
2183         if (doc->hasSelection(Q3TextDocument::Standard)) {
2184             if (!(e->state() & Qt::ShiftButton)) {
2185                 redraw = doc->removeSelection(Q3TextDocument::Standard);
2186                 doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
2187             } else {
2188                 redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw;
2189             }
2190         } else {
2191             if (isReadOnly() || !(e->state() & Qt::ShiftButton)) {
2192                 doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
2193             } else {
2194                 doc->setSelectionStart(Q3TextDocument::Standard, c);
2195                 redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw;
2196             }
2197         }
2198 
2199         for (int i = 1; i < doc->numSelections(); ++i) // start with 1 as we don't want to remove the Standard-Selection
2200             redraw = doc->removeSelection(i) || redraw;
2201 
2202         if (!redraw) {
2203             drawCursor(true);
2204         } else {
2205             repaintChanged();
2206 #ifndef QT_NO_CURSOR
2207             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
2208 #endif
2209         }
2210     } else if (e->button() == Qt::MidButton) {
2211         bool redraw = doc->removeSelection(Q3TextDocument::Standard);
2212         if (!redraw) {
2213             drawCursor(true);
2214         } else {
2215             repaintChanged();
2216 #ifndef QT_NO_CURSOR
2217             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
2218 #endif
2219         }
2220     }
2221 
2222     if (*cursor != oldCursor)
2223         updateCurrentFormat();
2224 }
2225 
2226 /*!
2227     \reimp
2228 */
2229 
contentsMouseMoveEvent(QMouseEvent * e)2230 void Q3TextEdit::contentsMouseMoveEvent(QMouseEvent *e)
2231 {
2232 #ifdef QT_TEXTEDIT_OPTIMIZATION
2233     if (d->optimMode) {
2234         optimMouseMoveEvent(e);
2235         return;
2236     }
2237 #endif
2238 
2239 #if !defined(QT_NO_IM)
2240     if (d->preeditLength > 0)
2241         return;
2242 #endif
2243 
2244     if (mousePressed) {
2245 #ifndef QT_NO_DRAGANDDROP
2246         if (mightStartDrag) {
2247             dragStartTimer->stop();
2248             if ((e->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance())
2249                 startDrag();
2250 #ifndef QT_NO_CURSOR
2251             if (!isReadOnly())
2252                 viewport()->setCursor(Qt::IBeamCursor);
2253 #endif
2254             return;
2255         }
2256 #endif
2257         mousePos = e->pos();
2258         handleMouseMove(mousePos);
2259         oldMousePos = mousePos;
2260     }
2261 
2262 #ifndef QT_NO_CURSOR
2263     if (!isReadOnly() && !mousePressed) {
2264         if (doc->hasSelection(Q3TextDocument::Standard) && doc->inSelection(Q3TextDocument::Standard, e->pos()))
2265             viewport()->setCursor(Qt::ArrowCursor);
2266         else
2267             viewport()->setCursor(Qt::IBeamCursor);
2268     }
2269 #endif
2270     updateCursor(e->pos());
2271 }
2272 
copyToClipboard()2273 void Q3TextEdit::copyToClipboard()
2274 {
2275 #ifndef QT_NO_CLIPBOARD
2276     if (QApplication::clipboard()->supportsSelection()) {
2277         d->clipboard_mode = QClipboard::Selection;
2278 
2279         // don't listen to selection changes
2280         disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
2281         copy();
2282         // listen to selection changes
2283         connect(QApplication::clipboard(), SIGNAL(selectionChanged()),
2284                  this, SLOT(clipboardChanged()));
2285 
2286         d->clipboard_mode = QClipboard::Clipboard;
2287     }
2288 #endif
2289 }
2290 
2291 /*!
2292     \reimp
2293 */
2294 
contentsMouseReleaseEvent(QMouseEvent * e)2295 void Q3TextEdit::contentsMouseReleaseEvent(QMouseEvent * e)
2296 {
2297     if (!inDoubleClick) { // could be the release of a dblclick
2298         int para = 0;
2299         int index = charAt(e->pos(), &para);
2300         emit clicked(para, index);
2301     }
2302 #ifdef QT_TEXTEDIT_OPTIMIZATION
2303     if (d->optimMode) {
2304         optimMouseReleaseEvent(e);
2305         return;
2306     }
2307 #endif
2308     Q3TextCursor oldCursor = *cursor;
2309     if (scrollTimer->isActive())
2310         scrollTimer->stop();
2311 #ifndef QT_NO_DRAGANDDROP
2312     if (dragStartTimer->isActive())
2313         dragStartTimer->stop();
2314     if (mightStartDrag) {
2315         selectAll(false);
2316         mousePressed = false;
2317     }
2318 #endif
2319     if (mousePressed) {
2320         mousePressed = false;
2321         copyToClipboard();
2322     }
2323 #ifndef QT_NO_CLIPBOARD
2324     else if (e->button() == Qt::MidButton && !isReadOnly()) {
2325         // only do middle-click pasting on systems that have selections (ie. X11)
2326         if (QApplication::clipboard()->supportsSelection()) {
2327             drawCursor(false);
2328             placeCursor(e->pos());
2329             ensureCursorVisible();
2330             doc->setSelectionStart(Q3TextDocument::Standard, oldCursor);
2331             bool redraw = false;
2332             if (doc->hasSelection(Q3TextDocument::Standard)) {
2333                 redraw = doc->removeSelection(Q3TextDocument::Standard);
2334                 doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
2335             } else {
2336                 doc->setSelectionStart(Q3TextDocument::Standard, *cursor);
2337             }
2338             // start with 1 as we don't want to remove the Standard-Selection
2339             for (int i = 1; i < doc->numSelections(); ++i)
2340                 redraw = doc->removeSelection(i) || redraw;
2341             if (!redraw) {
2342                 drawCursor(true);
2343             } else {
2344                 repaintChanged();
2345 #ifndef QT_NO_CURSOR
2346                 viewport()->setCursor(Qt::IBeamCursor);
2347 #endif
2348             }
2349             d->clipboard_mode = QClipboard::Selection;
2350             paste();
2351             d->clipboard_mode = QClipboard::Clipboard;
2352         }
2353     }
2354 #endif
2355     emit cursorPositionChanged(cursor);
2356     emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
2357     if (oldCursor != *cursor)
2358         updateCurrentFormat();
2359     inDoubleClick = false;
2360 
2361 #ifndef QT_NO_NETWORKPROTOCOL
2362     if ((  (!onLink.isEmpty() && onLink == pressedLink)
2363           || (!d->onName.isEmpty() && d->onName == d->pressedName))
2364          && linksEnabled()) {
2365         if (!onLink.isEmpty()) {
2366             QUrl u = QUrl(doc->context()).resolved(onLink);
2367             emitLinkClicked(u.toString(QUrl::None));
2368         }
2369         if (Q3TextBrowser *browser = qobject_cast<Q3TextBrowser*>(this))
2370             emit browser->anchorClicked(d->onName, onLink);
2371 
2372         // emitting linkClicked() may result in that the cursor winds
2373         // up hovering over a different valid link - check this and
2374         // set the appropriate cursor shape
2375         updateCursor(e->pos());
2376     }
2377 #endif
2378     drawCursor(true);
2379     if (!doc->hasSelection(Q3TextDocument::Standard, true))
2380         doc->removeSelection(Q3TextDocument::Standard);
2381 
2382     emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard));
2383     emit selectionChanged();
2384 }
2385 
2386 /*!
2387     \reimp
2388 */
2389 
contentsMouseDoubleClickEvent(QMouseEvent * e)2390 void Q3TextEdit::contentsMouseDoubleClickEvent(QMouseEvent * e)
2391 {
2392     if (e->button() != Qt::LeftButton) {
2393         e->ignore();
2394         return;
2395     }
2396 #if !defined(QT_NO_IM)
2397     if (d->preeditLength > 0)
2398         return;
2399 #endif
2400 
2401     int para = 0;
2402     int index = charAt(e->pos(), &para);
2403 #ifdef QT_TEXTEDIT_OPTIMIZATION
2404     if (d->optimMode) {
2405         QString str = d->od->lines[LOGOFFSET(para)];
2406         int startIdx = index, endIdx = index, i;
2407         if (!str[index].isSpace()) {
2408             i = startIdx;
2409             // find start of word
2410             while (i >= 0 && !str[i].isSpace()) {
2411                 startIdx = i--;
2412             }
2413             i = endIdx;
2414             // find end of word..
2415             while (i < str.length() && !str[i].isSpace()) {
2416                 endIdx = ++i;
2417             }
2418             // ..and start of next
2419             while (i < str.length() && str[i].isSpace()) {
2420                 endIdx = ++i;
2421             }
2422             optimSetSelection(para, startIdx, para, endIdx);
2423             repaintContents();
2424         }
2425     } else
2426 #endif
2427     {
2428         Q3TextCursor c1 = *cursor;
2429         Q3TextCursor c2 = *cursor;
2430 #if defined(Q_OS_MAC)
2431         Q3TextParagraph *para = cursor->paragraph();
2432         if (cursor->isValid()) {
2433             if (para->at(cursor->index())->c.isLetterOrNumber()) {
2434                 while (c1.index() > 0 &&
2435                         c1.paragraph()->at(c1.index()-1)->c.isLetterOrNumber())
2436                     c1.gotoPreviousLetter();
2437                 while (c2.paragraph()->at(c2.index())->c.isLetterOrNumber() &&
2438                         !c2.atParagEnd())
2439                     c2.gotoNextLetter();
2440             } else if (para->at(cursor->index())->c.isSpace()) {
2441                 while (c1.index() > 0 &&
2442                         c1.paragraph()->at(c1.index()-1)->c.isSpace())
2443                     c1.gotoPreviousLetter();
2444                 while (c2.paragraph()->at(c2.index())->c.isSpace() &&
2445                         !c2.atParagEnd())
2446                     c2.gotoNextLetter();
2447             } else if (!c2.atParagEnd()) {
2448                 c2.gotoNextLetter();
2449             }
2450         }
2451 #else
2452         if (cursor->index() > 0 && !cursor->paragraph()->at(cursor->index()-1)->c.isSpace())
2453             c1.gotoPreviousWord();
2454         if (!cursor->paragraph()->at(cursor->index())->c.isSpace() && !cursor->atParagEnd())
2455             c2.gotoNextWord();
2456 #endif
2457         doc->setSelectionStart(Q3TextDocument::Standard, c1);
2458         doc->setSelectionEnd(Q3TextDocument::Standard, c2);
2459 
2460         *cursor = c2;
2461 
2462         repaintChanged();
2463 
2464         d->trippleClickTimer->start(qApp->doubleClickInterval(), true);
2465         d->trippleClickPoint = e->globalPos();
2466     }
2467     inDoubleClick = true;
2468     mousePressed = true;
2469     emit doubleClicked(para, index);
2470 }
2471 
2472 #ifndef QT_NO_DRAGANDDROP
2473 
2474 /*!
2475     \reimp
2476 */
2477 
contentsDragEnterEvent(QDragEnterEvent * e)2478 void Q3TextEdit::contentsDragEnterEvent(QDragEnterEvent *e)
2479 {
2480     if (isReadOnly() || !Q3TextDrag::canDecode(e)) {
2481         e->ignore();
2482         return;
2483     }
2484     e->acceptAction();
2485     inDnD = true;
2486 }
2487 
2488 /*!
2489     \reimp
2490 */
2491 
contentsDragMoveEvent(QDragMoveEvent * e)2492 void Q3TextEdit::contentsDragMoveEvent(QDragMoveEvent *e)
2493 {
2494     if (isReadOnly() || !Q3TextDrag::canDecode(e)) {
2495         e->ignore();
2496         return;
2497     }
2498     drawCursor(false);
2499     placeCursor(e->pos(),  cursor);
2500     drawCursor(true);
2501     e->acceptAction();
2502 }
2503 
2504 /*!
2505     \reimp
2506 */
2507 
contentsDragLeaveEvent(QDragLeaveEvent *)2508 void Q3TextEdit::contentsDragLeaveEvent(QDragLeaveEvent *)
2509 {
2510     drawCursor(false);
2511     inDnD = false;
2512 }
2513 
2514 /*!
2515     \reimp
2516 */
2517 
contentsDropEvent(QDropEvent * e)2518 void Q3TextEdit::contentsDropEvent(QDropEvent *e)
2519 {
2520     if (isReadOnly())
2521         return;
2522     inDnD = false;
2523     e->acceptAction();
2524     bool intern = false;
2525     if (Q3RichTextDrag::canDecode(e)) {
2526         bool hasSel = doc->hasSelection(Q3TextDocument::Standard);
2527         bool internalDrag = e->source() == this || e->source() == viewport();
2528         int dropId, dropIndex;
2529         Q3TextCursor insertCursor = *cursor;
2530         dropId = cursor->paragraph()->paragId();
2531         dropIndex = cursor->index();
2532         if (hasSel && internalDrag) {
2533             Q3TextCursor c1, c2;
2534             int selStartId, selStartIndex;
2535             int selEndId, selEndIndex;
2536             c1 = doc->selectionStartCursor(Q3TextDocument::Standard);
2537             c1.restoreState();
2538             c2 = doc->selectionEndCursor(Q3TextDocument::Standard);
2539             c2.restoreState();
2540             selStartId = c1.paragraph()->paragId();
2541             selStartIndex = c1.index();
2542             selEndId = c2.paragraph()->paragId();
2543             selEndIndex = c2.index();
2544             if (((dropId > selStartId) ||
2545                    (dropId == selStartId && dropIndex > selStartIndex)) &&
2546                  ((dropId < selEndId) ||
2547                    (dropId == selEndId && dropIndex <= selEndIndex)))
2548                 insertCursor = c1;
2549             if (dropId == selEndId && dropIndex > selEndIndex) {
2550                 insertCursor = c1;
2551                 if (selStartId == selEndId) {
2552                     insertCursor.setIndex(dropIndex -
2553                                            (selEndIndex - selStartIndex));
2554                 } else {
2555                     insertCursor.setIndex(dropIndex - selEndIndex +
2556                                            selStartIndex);
2557                 }
2558             }
2559          }
2560 
2561         if (internalDrag && e->action() == QDropEvent::Move) {
2562             removeSelectedText();
2563             intern = true;
2564             doc->removeSelection(Q3TextDocument::Standard);
2565         } else {
2566             doc->removeSelection(Q3TextDocument::Standard);
2567 #ifndef QT_NO_CURSOR
2568             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
2569 #endif
2570         }
2571         drawCursor(false);
2572         cursor->setParagraph(insertCursor.paragraph());
2573         cursor->setIndex(insertCursor.index());
2574         drawCursor(true);
2575         if (!cursor->nestedDepth()) {
2576             QString subType = QLatin1String("plain");
2577             if (textFormat() != Qt::PlainText) {
2578                 if (e->provides("application/x-qrichtext"))
2579                     subType = QLatin1String("x-qrichtext");
2580             }
2581 #ifndef QT_NO_CLIPBOARD
2582             pasteSubType(subType.toLatin1(), e);
2583 #endif
2584             // emit appropriate signals.
2585             emit selectionChanged();
2586             emit cursorPositionChanged(cursor);
2587             emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
2588         } else {
2589             if (intern)
2590                 undo();
2591             e->ignore();
2592         }
2593     }
2594 }
2595 
2596 #endif
2597 
2598 /*!
2599     \reimp
2600 */
contentsContextMenuEvent(QContextMenuEvent * e)2601 void Q3TextEdit::contentsContextMenuEvent(QContextMenuEvent *e)
2602 {
2603     clearUndoRedo();
2604     mousePressed = false;
2605 
2606     e->accept();
2607 #ifndef QT_NO_POPUPMENU
2608     Q3PopupMenu *popup = createPopupMenu(e->pos());
2609     if (!popup)
2610         popup = createPopupMenu();
2611     if (!popup)
2612         return;
2613     int r = popup->exec(e->globalPos(), -1);
2614     delete popup;
2615 
2616     if (r == d->id[IdClear])
2617         clear();
2618     else if (r == d->id[IdSelectAll]) {
2619         selectAll();
2620 #ifndef QT_NO_CLIPBOARD
2621         // if the clipboard support selections, put the newly selected text into
2622         // the clipboard
2623         if (QApplication::clipboard()->supportsSelection()) {
2624             d->clipboard_mode = QClipboard::Selection;
2625 
2626             // don't listen to selection changes
2627             disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
2628             copy();
2629             // listen to selection changes
2630             connect(QApplication::clipboard(), SIGNAL(selectionChanged()),
2631                      this, SLOT(clipboardChanged()));
2632 
2633             d->clipboard_mode = QClipboard::Clipboard;
2634         }
2635 #endif
2636     } else if (r == d->id[IdUndo])
2637         undo();
2638     else if (r == d->id[IdRedo])
2639         redo();
2640 #ifndef QT_NO_CLIPBOARD
2641     else if (r == d->id[IdCut])
2642         cut();
2643     else if (r == d->id[IdCopy])
2644         copy();
2645     else if (r == d->id[IdPaste])
2646         paste();
2647 #endif
2648 #endif
2649 }
2650 
2651 
autoScrollTimerDone()2652 void Q3TextEdit::autoScrollTimerDone()
2653 {
2654     if (mousePressed)
2655         handleMouseMove( viewportToContents(viewport()->mapFromGlobal(QCursor::pos()) ));
2656 }
2657 
handleMouseMove(const QPoint & pos)2658 void Q3TextEdit::handleMouseMove(const QPoint& pos)
2659 {
2660     if (!mousePressed)
2661         return;
2662 
2663     if ((!scrollTimer->isActive() && pos.y() < contentsY()) || pos.y() > contentsY() + visibleHeight())
2664         scrollTimer->start(100, false);
2665     else if (scrollTimer->isActive() && pos.y() >= contentsY() && pos.y() <= contentsY() + visibleHeight())
2666         scrollTimer->stop();
2667 
2668     drawCursor(false);
2669     Q3TextCursor oldCursor = *cursor;
2670 
2671     placeCursor(pos);
2672 
2673     if (inDoubleClick) {
2674         Q3TextCursor cl = *cursor;
2675         cl.gotoPreviousWord();
2676         Q3TextCursor cr = *cursor;
2677         cr.gotoNextWord();
2678 
2679         int diff = QABS(oldCursor.paragraph()->at(oldCursor.index())->x - mousePos.x());
2680         int ldiff = QABS(cl.paragraph()->at(cl.index())->x - mousePos.x());
2681         int rdiff = QABS(cr.paragraph()->at(cr.index())->x - mousePos.x());
2682 
2683 
2684         if (cursor->paragraph()->lineStartOfChar(cursor->index()) !=
2685              oldCursor.paragraph()->lineStartOfChar(oldCursor.index()))
2686             diff = 0xFFFFFF;
2687 
2688         if (rdiff < diff && rdiff < ldiff)
2689             *cursor = cr;
2690         else if (ldiff < diff && ldiff < rdiff)
2691             *cursor = cl;
2692         else
2693             *cursor = oldCursor;
2694 
2695     }
2696     ensureCursorVisible();
2697 
2698     bool redraw = false;
2699     if (doc->hasSelection(Q3TextDocument::Standard)) {
2700         redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw;
2701     }
2702 
2703     if (!redraw) {
2704         drawCursor(true);
2705     } else {
2706         repaintChanged();
2707         drawCursor(true);
2708     }
2709 
2710     if (currentFormat && currentFormat->key() != cursor->paragraph()->at(cursor->index())->format()->key()) {
2711         currentFormat->removeRef();
2712         currentFormat = doc->formatCollection()->format(cursor->paragraph()->at(cursor->index())->format());
2713         if (currentFormat->isMisspelled()) {
2714             currentFormat->removeRef();
2715             currentFormat = doc->formatCollection()->format(currentFormat->font(), currentFormat->color());
2716         }
2717         emit currentFontChanged(currentFormat->font());
2718         emit currentColorChanged(currentFormat->color());
2719         emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign());
2720     }
2721 
2722     if (currentAlignment != cursor->paragraph()->alignment()) {
2723         currentAlignment = cursor->paragraph()->alignment();
2724         block_set_alignment = true;
2725         emit currentAlignmentChanged(currentAlignment);
2726         block_set_alignment = false;
2727     }
2728 }
2729 
2730 /*! \internal */
2731 
placeCursor(const QPoint & pos,Q3TextCursor * c,bool link)2732 void Q3TextEdit::placeCursor(const QPoint &pos, Q3TextCursor *c, bool link)
2733 {
2734 #ifdef QT_TEXTEDIT_OPTIMIZATION
2735     if (d->optimMode)
2736         return;
2737 #endif
2738     if (!c)
2739         c = cursor;
2740 
2741     if(c == cursor)
2742         resetInputContext();
2743     c->restoreState();
2744     Q3TextParagraph *s = doc->firstParagraph();
2745     c->place(pos, s, link);
2746 }
2747 
2748 
inputMethodQuery(Qt::InputMethodQuery query) const2749 QVariant Q3TextEdit::inputMethodQuery(Qt::InputMethodQuery query) const
2750 {
2751     Q3TextCursor c(*cursor);
2752 
2753     switch(query) {
2754     case Qt::ImMicroFocus: {
2755         int h = c.paragraph()->lineHeightOfChar(cursor->index());
2756         return QRect(c.x() - contentsX() + frameWidth(),
2757                      c.y() + cursor->paragraph()->rect().y() - contentsY() + frameWidth(), 1, h);
2758     }
2759     case Qt::ImFont:
2760             return c.paragraph()->at(c.index())->format()->font();
2761     default:
2762     // ##### fix the others!
2763         return QWidget::inputMethodQuery(query);
2764     }
2765 }
2766 
2767 
2768 
formatMore()2769 void Q3TextEdit::formatMore()
2770 {
2771     if (!lastFormatted)
2772         return;
2773 
2774     int bottom = contentsHeight();
2775     int lastTop = -1;
2776     int lastBottom = -1;
2777     int to = 20;
2778     bool firstVisible = false;
2779     QRect cr(contentsX(), contentsY(), visibleWidth(), visibleHeight());
2780     for (int i = 0; lastFormatted &&
2781           (i < to || (firstVisible && lastTop < contentsY()+height()));
2782           i++) {
2783         lastFormatted->format();
2784         lastTop = lastFormatted->rect().top();
2785         lastBottom = lastFormatted->rect().bottom();
2786         if (i == 0)
2787             firstVisible = lastBottom < cr.bottom();
2788         bottom = qMax(bottom, lastBottom);
2789         lastFormatted = lastFormatted->next();
2790     }
2791 
2792     if (bottom > contentsHeight()) {
2793         resizeContents(contentsWidth(), qMax(doc->height(), bottom));
2794     } else if (!lastFormatted && lastBottom < contentsHeight()) {
2795         resizeContents(contentsWidth(), qMax(doc->height(), lastBottom));
2796         if (contentsHeight() < visibleHeight())
2797             updateContents(0, contentsHeight(), visibleWidth(),
2798                             visibleHeight() - contentsHeight());
2799     }
2800 
2801     if (lastFormatted)
2802         formatTimer->start(interval, true);
2803     else
2804         interval = qMax(0, interval);
2805 }
2806 
doResize()2807 void Q3TextEdit::doResize()
2808 {
2809 #ifdef QT_TEXTEDIT_OPTIMIZATION
2810     if (!d->optimMode)
2811 #endif
2812     {
2813         if (wrapMode == FixedPixelWidth)
2814             return;
2815         doc->setMinimumWidth(-1);
2816         resizeContents(0, 0);
2817         doc->setWidth(visibleWidth());
2818         doc->invalidate();
2819         lastFormatted = doc->firstParagraph();
2820         interval = 0;
2821         formatMore();
2822     }
2823     repaintContents();
2824 }
2825 
2826 /*! \internal */
2827 
doChangeInterval()2828 void Q3TextEdit::doChangeInterval()
2829 {
2830     interval = 0;
2831 }
2832 
2833 /*!
2834     \reimp
2835 */
2836 
eventFilter(QObject * o,QEvent * e)2837 bool Q3TextEdit::eventFilter(QObject *o, QEvent *e)
2838 {
2839 #ifdef QT_TEXTEDIT_OPTIMIZATION
2840     if (!d->optimMode && (o == this || o == viewport())) {
2841 #else
2842     if (o == this || o == viewport()) {
2843 #endif
2844         if (d->cursorBlinkActive && e->type() == QEvent::FocusIn) {
2845             if (QApplication::cursorFlashTime() > 0)
2846                 blinkTimer->start(QApplication::cursorFlashTime() / 2);
2847             drawCursor(true);
2848         } else if (e->type() == QEvent::FocusOut) {
2849             blinkTimer->stop();
2850             drawCursor(false);
2851         }
2852     }
2853 
2854     if (o == this && e->type() == QEvent::PaletteChange) {
2855         QColor old(viewport()->palette().color(QPalette::Text));
2856         if (old != palette().color(QPalette::Text)) {
2857             QColor c(palette().color(QPalette::Text));
2858             doc->setMinimumWidth(-1);
2859             doc->setDefaultFormat(doc->formatCollection()->defaultFormat()->font(), c);
2860             lastFormatted = doc->firstParagraph();
2861             formatMore();
2862             repaintChanged();
2863         }
2864     }
2865 
2866     return Q3ScrollView::eventFilter(o, e);
2867 }
2868 
2869 /*!
2870     Inserts the given \a text. If \a indent is true the paragraph that
2871     contains the text is reindented; if \a checkNewLine is true the \a
2872     text is checked for newlines and relaid out. If \a removeSelected
2873     is true and there is a selection, the insertion replaces the
2874     selected text.
2875  */
2876 void Q3TextEdit::insert(const QString &text, bool indent,
2877                         bool checkNewLine, bool removeSelected)
2878 {
2879     uint f = 0;
2880     if (indent)
2881         f |= RedoIndentation;
2882     if (checkNewLine)
2883         f |= CheckNewLines;
2884     if (removeSelected)
2885         f |= RemoveSelected;
2886     insert(text, f);
2887 }
2888 
2889 /*!
2890     Inserts \a text at the current cursor position.
2891 
2892     The \a insertionFlags define how the text is inserted. If \c
2893     RedoIndentation is set, the paragraph is re-indented. If \c
2894     CheckNewLines is set, newline characters in \a text result in hard
2895     line breaks (i.e. new paragraphs). If \c checkNewLine is not set,
2896     the behavior of the editor is undefined if the \a text contains
2897     newlines. (It is not possible to change Q3TextEdit's newline handling
2898     behavior, but you can use QString::replace() to preprocess text
2899     before inserting it.) If \c RemoveSelected is set, any selected
2900     text (in selection 0) is removed before the text is inserted.
2901 
2902     The default flags are \c CheckNewLines | \c RemoveSelected.
2903 
2904     If the widget is in Qt::LogText mode this function will do nothing.
2905 
2906     \sa paste() pasteSubType()
2907 */
2908 
2909 
2910 void Q3TextEdit::insert(const QString &text, uint insertionFlags)
2911 {
2912 #ifdef QT_TEXTEDIT_OPTIMIZATION
2913     if (d->optimMode)
2914         return;
2915 #endif
2916 
2917     if (cursor->nestedDepth() != 0) // #### for 3.0, disable editing of tables as this is not advanced enough
2918         return;
2919 
2920     bool indent = insertionFlags & RedoIndentation;
2921     bool checkNewLine = insertionFlags & CheckNewLines;
2922     bool removeSelected = insertionFlags & RemoveSelected;
2923     QString txt(text);
2924     drawCursor(false);
2925     if (!isReadOnly() && doc->hasSelection(Q3TextDocument::Standard) && removeSelected)
2926         removeSelectedText();
2927     Q3TextCursor c2 = *cursor;
2928     int oldLen = 0;
2929 
2930     if ( undoEnabled && !isReadOnly() && undoRedoInfo.type != UndoRedoInfo::IME ) {
2931         checkUndoRedoInfo(UndoRedoInfo::Insert);
2932 
2933         // If we are inserting at the end of the previous insertion, we keep this in
2934         // the same undo/redo command. Otherwise, we separate them in two different commands.
2935         if (undoRedoInfo.valid() && undoRedoInfo.index + undoRedoInfo.d->text.length() != cursor->index()) {
2936             clearUndoRedo();
2937             undoRedoInfo.type = UndoRedoInfo::Insert;
2938         }
2939 
2940         if (!undoRedoInfo.valid()) {
2941             undoRedoInfo.id = cursor->paragraph()->paragId();
2942             undoRedoInfo.index = cursor->index();
2943             undoRedoInfo.d->text.clear();
2944         }
2945         oldLen = undoRedoInfo.d->text.length();
2946     }
2947 
2948     lastFormatted = checkNewLine && cursor->paragraph()->prev() ?
2949                     cursor->paragraph()->prev() : cursor->paragraph();
2950     Q3TextCursor oldCursor = *cursor;
2951     cursor->insert(txt, checkNewLine);
2952     if (doc->useFormatCollection() && !doc->preProcessor()) {
2953         doc->setSelectionStart(Q3TextDocument::Temp, oldCursor);
2954         doc->setSelectionEnd(Q3TextDocument::Temp, *cursor);
2955         doc->setFormat(Q3TextDocument::Temp, currentFormat, Q3TextFormat::Format);
2956         doc->removeSelection(Q3TextDocument::Temp);
2957     }
2958 
2959     if (indent && (txt == QString(QLatin1Char('{')) || txt == QString(QLatin1Char('}')) || txt == QString(QLatin1Char(':')) || txt == QString(QLatin1Char('#'))))
2960         cursor->indent();
2961     formatMore();
2962     repaintChanged();
2963     ensureCursorVisible();
2964     drawCursor(true);
2965 
2966     if ( undoEnabled && !isReadOnly() && undoRedoInfo.type != UndoRedoInfo::IME ) {
2967         undoRedoInfo.d->text += txt;
2968         if (!doc->preProcessor()) {
2969             for (int i = 0; i < (int)txt.length(); ++i) {
2970                 if (txt[i] != QLatin1Char('\n') && c2.paragraph()->at(c2.index())->format()) {
2971                     c2.paragraph()->at(c2.index())->format()->addRef();
2972                     undoRedoInfo.d->text.
2973                         setFormat(oldLen + i,
2974                                    c2.paragraph()->at(c2.index())->format(), true);
2975                 }
2976                 c2.gotoNextLetter();
2977             }
2978         }
2979     }
2980 
2981     if (!removeSelected) {
2982         doc->setSelectionStart(Q3TextDocument::Standard, oldCursor);
2983         doc->setSelectionEnd(Q3TextDocument::Standard, *cursor);
2984         repaintChanged();
2985     }
2986 
2987     setModified();
2988     emit textChanged();
2989 }
2990 
2991 /*!
2992     Inserts \a text in the paragraph \a para at position \a index.
2993 */
2994 
2995 void Q3TextEdit::insertAt(const QString &text, int para, int index)
2996 {
2997 #ifdef QT_TEXTEDIT_OPTIMIZATION
2998     if (d->optimMode) {
2999         optimInsert(text, para, index);
3000         return;
3001     }
3002 #endif
3003     Q3TextParagraph *p = doc->paragAt(para);
3004     if (!p)
3005         return;
3006     removeSelection(Q3TextDocument::Standard);
3007     Q3TextCursor tmp = *cursor;
3008     cursor->setParagraph(p);
3009     cursor->setIndex(index);
3010     insert(text, false, true, false);
3011     *cursor = tmp;
3012     removeSelection(Q3TextDocument::Standard);
3013 }
3014 
3015 /*!
3016     Inserts \a text as a new paragraph at position \a para. If \a para
3017     is -1, the text is appended. Use append() if the append operation
3018     is performance critical.
3019 */
3020 
3021 void Q3TextEdit::insertParagraph(const QString &text, int para)
3022 {
3023 #ifdef QT_TEXTEDIT_OPTIMIZATION
3024     if (d->optimMode) {
3025         optimInsert(text + QLatin1Char('\n'), para, 0);
3026         return;
3027     }
3028 #endif
3029     for (int i = 0; i < (int)doc->numSelections(); ++i)
3030         doc->removeSelection(i);
3031 
3032     Q3TextParagraph *p = doc->paragAt(para);
3033 
3034     bool append = !p;
3035     if (!p)
3036         p = doc->lastParagraph();
3037 
3038     Q3TextCursor old = *cursor;
3039     drawCursor(false);
3040 
3041     cursor->setParagraph(p);
3042     cursor->setIndex(0);
3043     clearUndoRedo();
3044     qtextedit_ignore_readonly = true;
3045     if (append && cursor->paragraph()->length() > 1) {
3046         cursor->setIndex(cursor->paragraph()->length() - 1);
3047         doKeyboardAction(ActionReturn);
3048     }
3049     insert(text, false, true, true);
3050     doKeyboardAction(ActionReturn);
3051     qtextedit_ignore_readonly = false;
3052 
3053     drawCursor(false);
3054     *cursor = old;
3055     drawCursor(true);
3056 
3057     repaintChanged();
3058 }
3059 
3060 /*!
3061     Removes the paragraph \a para.
3062 */
3063 
3064 void Q3TextEdit::removeParagraph(int para)
3065 {
3066 #ifdef QT_TEXTEDIT_OPTIMIZATION
3067     if (d->optimMode)
3068         return;
3069 #endif
3070     Q3TextParagraph *p = doc->paragAt(para);
3071     if (!p)
3072         return;
3073 
3074     for (int i = 0; i < doc->numSelections(); ++i)
3075         doc->removeSelection(i);
3076 
3077     Q3TextCursor start(doc);
3078     Q3TextCursor end(doc);
3079     start.setParagraph(p);
3080     start.setIndex(0);
3081     end.setParagraph(p);
3082     end.setIndex(p->length() - 1);
3083 
3084     if (!(p == doc->firstParagraph() && p == doc->lastParagraph())) {
3085         if (p->next()) {
3086             end.setParagraph(p->next());
3087             end.setIndex(0);
3088         } else if (p->prev()) {
3089             start.setParagraph(p->prev());
3090             start.setIndex(p->prev()->length() - 1);
3091         }
3092     }
3093 
3094     doc->setSelectionStart(Q3TextDocument::Temp, start);
3095     doc->setSelectionEnd(Q3TextDocument::Temp, end);
3096     removeSelectedText(Q3TextDocument::Temp);
3097 }
3098 
3099 /*!
3100     Undoes the last operation.
3101 
3102     If there is no operation to undo, i.e. there is no undo step in
3103     the undo/redo history, nothing happens.
3104 
3105     \sa undoAvailable() redo() undoDepth()
3106 */
3107 
3108 void Q3TextEdit::undo()
3109 {
3110     clearUndoRedo();
3111     if (isReadOnly() || !doc->commands()->isUndoAvailable() || !undoEnabled)
3112         return;
3113 
3114     for (int i = 0; i < (int)doc->numSelections(); ++i)
3115         doc->removeSelection(i);
3116 
3117 #ifndef QT_NO_CURSOR
3118     viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
3119 #endif
3120 
3121     clearUndoRedo();
3122     drawCursor(false);
3123     Q3TextCursor *c = doc->undo(cursor);
3124     if (!c) {
3125         drawCursor(true);
3126         return;
3127     }
3128     lastFormatted = 0;
3129     repaintChanged();
3130     ensureCursorVisible();
3131     drawCursor(true);
3132     setModified();
3133     // ### If we get back to a completely blank textedit, it
3134     // is possible that cursor is invalid and further actions
3135     // might not fix the problem, so reset the cursor here.
3136     // This is copied from removeSeletedText(), it might be
3137     // okay to just call that.
3138     if (!cursor->isValid()) {
3139         delete cursor;
3140         cursor = new Q3TextCursor(doc);
3141         drawCursor(true);
3142         repaintContents();
3143     }
3144     emit undoAvailable(isUndoAvailable());
3145     emit redoAvailable(isRedoAvailable());
3146     emit textChanged();
3147 }
3148 
3149 /*!
3150     Redoes the last operation.
3151 
3152     If there is no operation to redo, i.e. there is no redo step in
3153     the undo/redo history, nothing happens.
3154 
3155     \sa redoAvailable() undo() undoDepth()
3156 */
3157 
3158 void Q3TextEdit::redo()
3159 {
3160     if (isReadOnly() || !doc->commands()->isRedoAvailable() || !undoEnabled)
3161         return;
3162 
3163     for (int i = 0; i < (int)doc->numSelections(); ++i)
3164         doc->removeSelection(i);
3165 
3166 #ifndef QT_NO_CURSOR
3167     viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
3168 #endif
3169 
3170     clearUndoRedo();
3171     drawCursor(false);
3172     Q3TextCursor *c = doc->redo(cursor);
3173     if (!c) {
3174         drawCursor(true);
3175         return;
3176     }
3177     lastFormatted = 0;
3178     ensureCursorVisible();
3179     repaintChanged();
3180     ensureCursorVisible();
3181     drawCursor(true);
3182     setModified();
3183     emit undoAvailable(isUndoAvailable());
3184     emit redoAvailable(isRedoAvailable());
3185     emit textChanged();
3186 }
3187 
3188 /*!
3189     Pastes the text from the clipboard into the text edit at the
3190     current cursor position. Only plain text is pasted.
3191 
3192     If there is no text in the clipboard nothing happens.
3193 
3194     \sa pasteSubType() cut() Q3TextEdit::copy()
3195 */
3196 
3197 void Q3TextEdit::paste()
3198 {
3199 #ifndef QT_NO_MIMECLIPBOARD
3200     if (isReadOnly())
3201         return;
3202     QString subType = QLatin1String("plain");
3203     if (textFormat() != Qt::PlainText) {
3204         QMimeSource *m = QApplication::clipboard()->data(d->clipboard_mode);
3205         if (!m)
3206             return;
3207         if (m->provides("application/x-qrichtext"))
3208             subType = QLatin1String("x-qrichtext");
3209     }
3210 
3211     pasteSubType(subType.toLatin1());
3212 #endif
3213 }
3214 
3215 void Q3TextEdit::checkUndoRedoInfo(UndoRedoInfo::Type t)
3216 {
3217     if (undoRedoInfo.valid() && t != undoRedoInfo.type) {
3218         clearUndoRedo();
3219     }
3220     undoRedoInfo.type = t;
3221 }
3222 
3223 /*!
3224     Repaints any paragraphs that have changed.
3225 
3226     Although used extensively internally you shouldn't need to call
3227     this yourself.
3228 */
3229 void Q3TextEdit::repaintChanged()
3230 {
3231     if (!updatesEnabled() || !viewport()->updatesEnabled())
3232         return;
3233 
3234     if (doc->firstParagraph())
3235         lastFormatted = doc->firstParagraph();
3236     updateContents(); // good enough until this class is rewritten
3237 }
3238 
3239 #ifndef QT_NO_MIME
3240 Q3TextDrag *Q3TextEdit::dragObject(QWidget *parent) const
3241 {
3242     if (!doc->hasSelection(Q3TextDocument::Standard) ||
3243          doc->selectedText(Q3TextDocument::Standard).isEmpty())
3244         return 0;
3245     if (textFormat() != Qt::RichText)
3246         return new Q3TextDrag(doc->selectedText(Q3TextDocument::Standard), parent);
3247     Q3RichTextDrag *drag = new Q3RichTextDrag(parent);
3248     drag->setPlainText(doc->selectedText(Q3TextDocument::Standard));
3249     drag->setRichText(doc->selectedText(Q3TextDocument::Standard, true));
3250     return drag;
3251 }
3252 #endif
3253 
3254 /*!
3255     Copies the selected text (from selection 0) to the clipboard and
3256     deletes it from the text edit.
3257 
3258     If there is no selected text (in selection 0) nothing happens.
3259 
3260     \sa Q3TextEdit::copy() paste() pasteSubType()
3261 */
3262 
3263 void Q3TextEdit::cut()
3264 {
3265     if (isReadOnly())
3266         return;
3267     normalCopy();
3268     removeSelectedText();
3269 }
3270 
3271 void Q3TextEdit::normalCopy()
3272 {
3273 #ifndef QT_NO_MIME
3274     Q3TextDrag *drag = dragObject();
3275     if (!drag)
3276         return;
3277 #ifndef QT_NO_MIMECLIPBOARD
3278     QApplication::clipboard()->setData(drag, d->clipboard_mode);
3279 #endif // QT_NO_MIMECLIPBOARD
3280 #endif // QT_NO_MIME
3281 }
3282 
3283 /*!
3284     Copies any selected text (from selection 0) to the clipboard.
3285 
3286     \sa hasSelectedText() copyAvailable()
3287 */
3288 
3289 void Q3TextEdit::copy()
3290 {
3291 #ifndef QT_NO_CLIPBOARD
3292 # ifdef QT_TEXTEDIT_OPTIMIZATION
3293     if (d->optimMode && optimHasSelection())
3294         QApplication::clipboard()->setText(optimSelectedText(), d->clipboard_mode);
3295     else
3296         normalCopy();
3297 # else
3298     normalCopy();
3299 # endif
3300 #endif
3301 }
3302 
3303 /*!
3304     \internal
3305 
3306     Re-indents the current paragraph.
3307 */
3308 
3309 void Q3TextEdit::indent()
3310 {
3311     if (isReadOnly())
3312         return;
3313 
3314     drawCursor(false);
3315     if (!doc->hasSelection(Q3TextDocument::Standard))
3316         cursor->indent();
3317     else
3318         doc->indentSelection(Q3TextDocument::Standard);
3319     repaintChanged();
3320     drawCursor(true);
3321     setModified();
3322     emit textChanged();
3323 }
3324 
3325 /*!
3326     Reimplemented to allow tabbing through links. If \a n is true the
3327     tab moves the focus to the next child; if \a n is false the tab
3328     moves the focus to the previous child. Returns true if the focus
3329     was moved; otherwise returns false.
3330  */
3331 
3332 bool Q3TextEdit::focusNextPrevChild(bool n)
3333 {
3334     if (!isReadOnly() || !linksEnabled())
3335         return false;
3336     bool b = doc->focusNextPrevChild(n);
3337     repaintChanged();
3338     if (b) {
3339         Q3TextParagraph *p = doc->focusIndicator.parag;
3340         int start = doc->focusIndicator.start;
3341         int len = doc->focusIndicator.len;
3342 
3343         int y = p->rect().y();
3344         while (p
3345                && len == 0
3346                && p->at(start)->isCustom()
3347                && p->at(start)->customItem()->isNested()) {
3348 
3349             Q3TextTable *t = (Q3TextTable*)p->at(start)->customItem();
3350             QList<Q3TextTableCell *> cells = t->tableCells();
3351             for (int idx = 0; idx < cells.count(); ++idx) {
3352                 Q3TextTableCell *c = cells.at(idx);
3353                 Q3TextDocument *cellDoc = c->richText();
3354                 if ( cellDoc->hasFocusParagraph() ) {
3355                     y += c->geometry().y() + c->verticalAlignmentOffset();
3356 
3357                     p = cellDoc->focusIndicator.parag;
3358                     start = cellDoc->focusIndicator.start;
3359                     len = cellDoc->focusIndicator.len;
3360                     if ( p )
3361                         y += p->rect().y();
3362 
3363                     break;
3364                 }
3365             }
3366         }
3367         setContentsPos( contentsX(), QMIN( y, contentsHeight() - visibleHeight() ) );
3368     }
3369     return b;
3370 }
3371 
3372 /*!
3373     \internal
3374 
3375   This functions sets the current format to \a f. Only the fields of \a
3376   f which are specified by the \a flags are used.
3377 */
3378 
3379 void Q3TextEdit::setFormat(Q3TextFormat *f, int flags)
3380 {
3381     if (doc->hasSelection(Q3TextDocument::Standard)) {
3382         drawCursor(false);
3383         Q3TextCursor c1 = doc->selectionStartCursor(Q3TextDocument::Standard);
3384         c1.restoreState();
3385         Q3TextCursor c2 = doc->selectionEndCursor(Q3TextDocument::Standard);
3386         c2.restoreState();
3387         if (undoEnabled) {
3388             clearUndoRedo();
3389             undoRedoInfo.type = UndoRedoInfo::Format;
3390             undoRedoInfo.id = c1.paragraph()->paragId();
3391             undoRedoInfo.index = c1.index();
3392             undoRedoInfo.eid = c2.paragraph()->paragId();
3393             undoRedoInfo.eindex = c2.index();
3394             readFormats(c1, c2, undoRedoInfo.d->text);
3395             undoRedoInfo.format = f;
3396             undoRedoInfo.flags = flags;
3397             clearUndoRedo();
3398         }
3399         doc->setFormat(Q3TextDocument::Standard, f, flags);
3400         repaintChanged();
3401         formatMore();
3402         drawCursor(true);
3403         setModified();
3404         emit textChanged();
3405     }
3406     if (currentFormat && currentFormat->key() != f->key()) {
3407         currentFormat->removeRef();
3408         currentFormat = doc->formatCollection()->format(f);
3409         if (currentFormat->isMisspelled()) {
3410             currentFormat->removeRef();
3411             currentFormat = doc->formatCollection()->format(currentFormat->font(),
3412                                                              currentFormat->color());
3413         }
3414         emit currentFontChanged(currentFormat->font());
3415         emit currentColorChanged(currentFormat->color());
3416         emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign());
3417         if (cursor->index() == cursor->paragraph()->length() - 1) {
3418             currentFormat->addRef();
3419             cursor->paragraph()->string()->setFormat(cursor->index(), currentFormat, true);
3420             if (cursor->paragraph()->length() == 1) {
3421                 cursor->paragraph()->invalidate(0);
3422                 cursor->paragraph()->format();
3423                 repaintChanged();
3424             }
3425         }
3426     }
3427 }
3428 
3429 /*! \internal
3430   \warning In Qt 3.1 we will provide a cleaer API for the
3431   functionality which is provided by this function and in Qt 4.0 this
3432   function will go away.
3433 
3434   Sets the paragraph style of the current paragraph
3435   to \a dm. If \a dm is Q3StyleSheetItem::DisplayListItem, the
3436   type of the list item is set to \a listStyle.
3437 
3438   \sa setAlignment()
3439 */
3440 
3441 void Q3TextEdit::setParagType(Q3StyleSheetItem::DisplayMode dm,
3442                               Q3StyleSheetItem::ListStyle listStyle)
3443 {
3444     if (isReadOnly())
3445         return;
3446 
3447     drawCursor(false);
3448     Q3TextParagraph *start = cursor->paragraph();
3449     Q3TextParagraph *end = start;
3450     if (doc->hasSelection(Q3TextDocument::Standard)) {
3451         start = doc->selectionStartCursor(Q3TextDocument::Standard).topParagraph();
3452         end = doc->selectionEndCursor(Q3TextDocument::Standard).topParagraph();
3453         if (end->paragId() < start->paragId())
3454             return; // do not trust our selections
3455     }
3456 
3457     clearUndoRedo();
3458     undoRedoInfo.type = UndoRedoInfo::Style;
3459     undoRedoInfo.id = start->paragId();
3460     undoRedoInfo.eid = end->paragId();
3461     undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid);
3462 
3463     while (start != end->next()) {
3464         start->setListStyle(listStyle);
3465         if (dm == Q3StyleSheetItem::DisplayListItem) {
3466             start->setListItem(true);
3467             if(start->listDepth() == 0)
3468                 start->setListDepth(1);
3469         } else if (start->isListItem()) {
3470             start->setListItem(false);
3471             start->setListDepth(qMax(start->listDepth()-1, 0));
3472         }
3473         start = start->next();
3474     }
3475 
3476     clearUndoRedo();
3477     repaintChanged();
3478     formatMore();
3479     drawCursor(true);
3480     setModified();
3481     emit textChanged();
3482 }
3483 
3484 /*!
3485     Sets the alignment of the current paragraph to \a a. Valid
3486     alignments are Qt::AlignLeft, Qt::AlignRight,
3487     Qt::AlignJustify and Qt::AlignCenter (which centers
3488     horizontally).
3489 */
3490 
3491 void Q3TextEdit::setAlignment(int a)
3492 {
3493     if (isReadOnly() || block_set_alignment)
3494         return;
3495 
3496     drawCursor(false);
3497     Q3TextParagraph *start = cursor->paragraph();
3498     Q3TextParagraph *end = start;
3499     if (doc->hasSelection(Q3TextDocument::Standard)) {
3500         start = doc->selectionStartCursor(Q3TextDocument::Standard).topParagraph();
3501         end = doc->selectionEndCursor(Q3TextDocument::Standard).topParagraph();
3502         if (end->paragId() < start->paragId())
3503             return; // do not trust our selections
3504     }
3505 
3506     clearUndoRedo();
3507     undoRedoInfo.type = UndoRedoInfo::Style;
3508     undoRedoInfo.id = start->paragId();
3509     undoRedoInfo.eid = end->paragId();
3510     undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid);
3511 
3512     while (start != end->next()) {
3513         start->setAlignment(a);
3514         start = start->next();
3515     }
3516 
3517     clearUndoRedo();
3518     repaintChanged();
3519     formatMore();
3520     drawCursor(true);
3521     if (currentAlignment != a) {
3522         currentAlignment = a;
3523         emit currentAlignmentChanged(currentAlignment);
3524     }
3525     setModified();
3526     emit textChanged();
3527 }
3528 
3529 void Q3TextEdit::updateCurrentFormat()
3530 {
3531     int i = cursor->index();
3532     if (i > 0)
3533         --i;
3534     if (doc->useFormatCollection() &&
3535          (!currentFormat || currentFormat->key() != cursor->paragraph()->at(i)->format()->key())) {
3536         if (currentFormat)
3537             currentFormat->removeRef();
3538         currentFormat = doc->formatCollection()->format(cursor->paragraph()->at(i)->format());
3539         if (currentFormat->isMisspelled()) {
3540             currentFormat->removeRef();
3541             currentFormat = doc->formatCollection()->format(currentFormat->font(), currentFormat->color());
3542         }
3543         emit currentFontChanged(currentFormat->font());
3544         emit currentColorChanged(currentFormat->color());
3545         emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign());
3546     }
3547 
3548     if (currentAlignment != cursor->paragraph()->alignment()) {
3549         currentAlignment = cursor->paragraph()->alignment();
3550         block_set_alignment = true;
3551         emit currentAlignmentChanged(currentAlignment);
3552         block_set_alignment = false;
3553     }
3554 }
3555 
3556 /*!
3557     If \a b is true sets the current format to italic; otherwise sets
3558     the current format to non-italic.
3559 
3560     \sa italic()
3561 */
3562 
3563 void Q3TextEdit::setItalic(bool b)
3564 {
3565     Q3TextFormat f(*currentFormat);
3566     f.setItalic(b);
3567     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3568     setFormat(f2, Q3TextFormat::Italic);
3569 }
3570 
3571 /*!
3572     If \a b is true sets the current format to bold; otherwise sets
3573     the current format to non-bold.
3574 
3575     \sa bold()
3576 */
3577 
3578 void Q3TextEdit::setBold(bool b)
3579 {
3580     Q3TextFormat f(*currentFormat);
3581     f.setBold(b);
3582     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3583     setFormat(f2, Q3TextFormat::Bold);
3584 }
3585 
3586 /*!
3587     If \a b is true sets the current format to underline; otherwise
3588     sets the current format to non-underline.
3589 
3590     \sa underline()
3591 */
3592 
3593 void Q3TextEdit::setUnderline(bool b)
3594 {
3595     Q3TextFormat f(*currentFormat);
3596     f.setUnderline(b);
3597     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3598     setFormat(f2, Q3TextFormat::Underline);
3599 }
3600 
3601 /*!
3602     Sets the font family of the current format to \a fontFamily.
3603 
3604     \sa family() setCurrentFont()
3605 */
3606 
3607 void Q3TextEdit::setFamily(const QString &fontFamily)
3608 {
3609     Q3TextFormat f(*currentFormat);
3610     f.setFamily(fontFamily);
3611     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3612     setFormat(f2, Q3TextFormat::Family);
3613 }
3614 
3615 /*!
3616     Sets the point size of the current format to \a s.
3617 
3618     Note that if \a s is zero or negative, the behavior of this
3619     function is not defined.
3620 
3621     \sa pointSize() setCurrentFont() setFamily()
3622 */
3623 
3624 void Q3TextEdit::setPointSize(int s)
3625 {
3626     Q3TextFormat f(*currentFormat);
3627     f.setPointSize(s);
3628     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3629     setFormat(f2, Q3TextFormat::Size);
3630 }
3631 
3632 /*!
3633     Sets the color of the current format, i.e. of the text, to \a c.
3634 
3635     \sa color() setPaper()
3636 */
3637 
3638 void Q3TextEdit::setColor(const QColor &c)
3639 {
3640     Q3TextFormat f(*currentFormat);
3641     f.setColor(c);
3642     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3643     setFormat(f2, Q3TextFormat::Color);
3644 }
3645 
3646 /*!
3647     Sets the vertical alignment of the current format, i.e. of the
3648     text, to \a a.
3649 
3650     \sa color() setPaper()
3651 */
3652 
3653 void Q3TextEdit::setVerticalAlignment(Q3TextEdit::VerticalAlignment a)
3654 {
3655     Q3TextFormat f(*currentFormat);
3656     f.setVAlign((Q3TextFormat::VerticalAlignment)a);
3657     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3658     setFormat(f2, Q3TextFormat::VAlign);
3659 }
3660 
3661 void Q3TextEdit::setFontInternal(const QFont &f_)
3662 {
3663     QFont font = f_;
3664     if (font.kerning())
3665         font.setKerning(false);
3666     Q3TextFormat f(*currentFormat);
3667     f.setFont(font);
3668     Q3TextFormat *f2 = doc->formatCollection()->format(&f);
3669     setFormat(f2, Q3TextFormat::Font);
3670 }
3671 
3672 
3673 QString Q3TextEdit::text() const
3674 {
3675 #ifdef QT_TEXTEDIT_OPTIMIZATION
3676     if (d->optimMode)
3677         return optimText();
3678 #endif
3679 
3680     Q3TextParagraph *p = doc->firstParagraph();
3681     if (!p || (!p->next() && p->length() <= 1))
3682         return QString::fromLatin1("");
3683 
3684     if (isReadOnly())
3685         return doc->originalText();
3686     return doc->text();
3687 }
3688 
3689 /*!
3690     \overload
3691 
3692     Returns the text of paragraph \a para.
3693 
3694     If textFormat() is Qt::RichText the text will contain HTML
3695     formatting tags.
3696 */
3697 
3698 QString Q3TextEdit::text(int para) const
3699 {
3700 #ifdef QT_TEXTEDIT_OPTIMIZATION
3701     if (d->optimMode && (d->od->numLines >= para)) {
3702         QString paraStr = d->od->lines[LOGOFFSET(para)];
3703         if (paraStr.isEmpty())
3704             paraStr = QLatin1Char('\n');
3705         return paraStr;
3706     } else
3707 #endif
3708     return doc->text(para);
3709 }
3710 
3711 /*!
3712     \overload
3713 
3714     Changes the text of the text edit to the string \a text and the
3715     context to \a context. Any previous text is removed.
3716 
3717     \a text may be interpreted either as plain text or as rich text,
3718     depending on the textFormat(). The default setting is Qt::AutoText,
3719     i.e. the text edit auto-detects the format from \a text.
3720 
3721     For rich text the rendering style and available tags are defined
3722     by a styleSheet(); see Q3StyleSheet for details.
3723 
3724     The optional \a context is a path which the text edit's
3725     Q3MimeSourceFactory uses to resolve the locations of files and
3726     images. (See \l{Q3TextEdit::Q3TextEdit()}.) It is passed to the text
3727     edit's Q3MimeSourceFactory when quering data.
3728 
3729     Note that the undo/redo history is cleared by this function.
3730 
3731     \sa text(), setTextFormat()
3732 */
3733 
3734 void Q3TextEdit::setText(const QString &text, const QString &context)
3735 {
3736 #ifdef QT_TEXTEDIT_OPTIMIZATION
3737     if (d->optimMode) {
3738         optimSetText(text);
3739         return;
3740     }
3741 #endif
3742     if (!isModified() && isReadOnly() &&
3743          this->context() == context && this->text() == text)
3744         return;
3745 
3746     emit undoAvailable(false);
3747     emit redoAvailable(false);
3748     undoRedoInfo.clear();
3749     doc->commands()->clear();
3750 
3751     lastFormatted = 0;
3752     int oldCursorPos = cursor->index();
3753     int oldCursorPar = cursor->paragraph()->paragId();
3754     cursor->restoreState();
3755     delete cursor;
3756     doc->setText(text, context);
3757 
3758     if (wrapMode == FixedPixelWidth) {
3759         resizeContents(wrapWidth, 0);
3760         doc->setWidth(wrapWidth);
3761         doc->setMinimumWidth(wrapWidth);
3762     } else {
3763         doc->setMinimumWidth(-1);
3764         resizeContents(0, 0);
3765     }
3766 
3767     lastFormatted = doc->firstParagraph();
3768     cursor = new Q3TextCursor(doc);
3769     updateContents();
3770 
3771     if (isModified())
3772         setModified(false);
3773     emit textChanged();
3774     if (cursor->index() != oldCursorPos || cursor->paragraph()->paragId() != oldCursorPar) {
3775         emit cursorPositionChanged(cursor);
3776         emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
3777     }
3778     formatMore();
3779     updateCurrentFormat();
3780     d->scrollToAnchor.clear();
3781 }
3782 
3783 /*!
3784     \property Q3TextEdit::text
3785     \brief the text edit's text
3786 
3787     There is no default text.
3788 
3789     On setting, any previous text is deleted.
3790 
3791     The text may be interpreted either as plain text or as rich text,
3792     depending on the textFormat(). The default setting is Qt::AutoText,
3793     i.e. the text edit auto-detects the format of the text.
3794 
3795     For richtext, calling text() on an editable Q3TextEdit will cause
3796     the text to be regenerated from the textedit. This may mean that
3797     the QString returned may not be exactly the same as the one that
3798     was set.
3799 
3800     \sa textFormat
3801 */
3802 
3803 
3804 /*!
3805     \property Q3TextEdit::readOnly
3806     \brief whether the text edit is read-only
3807 
3808     In a read-only text edit the user can only navigate through the
3809     text and select text; modifying the text is not possible.
3810 
3811     This property's default is false.
3812 */
3813 
3814 /*!
3815     Finds the next occurrence of the string, \a expr. Returns true if
3816     \a expr was found; otherwise returns false.
3817 
3818     If \a para and \a index are both 0 the search begins from the
3819     current cursor position. If \a para and \a index are both not 0,
3820     the search begins from the \c{*}\a{index} character position in the
3821     \c{*}\a{para} paragraph.
3822 
3823     If \a cs is true the search is case sensitive, otherwise it is
3824     case insensitive. If \a wo is true the search looks for whole word
3825     matches only; otherwise it searches for any matching text. If \a
3826     forward is true (the default) the search works forward from the
3827     starting position to the end of the text, otherwise it works
3828     backwards to the beginning of the text.
3829 
3830     If \a expr is found the function returns true. If \a index and \a
3831     para are not 0, the number of the paragraph in which the first
3832     character of the match was found is put into \c{*}\a{para}, and the
3833     index position of that character within the paragraph is put into
3834     \c{*}\a{index}.
3835 
3836     If \a expr is not found the function returns false. If \a index
3837     and \a para are not 0 and \a expr is not found, \c{*}\a{index}
3838     and \c{*}\a{para} are undefined.
3839 
3840     Please note that this function will make the next occurrence of
3841     the string (if found) the current selection, and will thus
3842     modify the cursor position.
3843 
3844     Using the \a para and \a index parameters will not work correctly
3845     in case the document contains tables.
3846 */
3847 
3848 bool Q3TextEdit::find(const QString &expr, bool cs, bool wo, bool forward,
3849                       int *para, int *index)
3850 {
3851 #ifdef QT_TEXTEDIT_OPTIMIZATION
3852     if (d->optimMode)
3853         return optimFind(expr, cs, wo, forward, para, index);
3854 #endif
3855     drawCursor(false);
3856 #ifndef QT_NO_CURSOR
3857     viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
3858 #endif
3859     Q3TextCursor findcur = *cursor;
3860     if (para && index) {
3861         if (doc->paragAt(*para))
3862             findcur.gotoPosition(doc->paragAt(*para), *index);
3863         else
3864             findcur.gotoEnd();
3865     } else if (doc->hasSelection(Q3TextDocument::Standard)){
3866         // maks sure we do not find the same selection again
3867         if (forward)
3868             findcur.gotoNextLetter();
3869         else
3870             findcur.gotoPreviousLetter();
3871     } else if (!forward && findcur.index() == 0 && findcur.paragraph() == findcur.topParagraph()) {
3872         findcur.gotoEnd();
3873     }
3874     removeSelection(Q3TextDocument::Standard);
3875     bool found = doc->find(findcur, expr, cs, wo, forward);
3876     if (found) {
3877         if (para)
3878             *para = findcur.paragraph()->paragId();
3879         if (index)
3880             *index = findcur.index();
3881         *cursor = findcur;
3882         repaintChanged();
3883         ensureCursorVisible();
3884     }
3885     drawCursor(true);
3886     if (found) {
3887         emit cursorPositionChanged(cursor);
3888         emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
3889     }
3890     return found;
3891 }
3892 
3893 void Q3TextEdit::blinkCursor()
3894 {
3895     bool cv = cursorVisible;
3896     blinkCursorVisible = !blinkCursorVisible;
3897     drawCursor(blinkCursorVisible);
3898     cursorVisible = cv;
3899 }
3900 
3901 /*!
3902     Sets the cursor to position \a index in paragraph \a para.
3903 
3904     \sa getCursorPosition()
3905 */
3906 
3907 void Q3TextEdit::setCursorPosition(int para, int index)
3908 {
3909     Q3TextParagraph *p = doc->paragAt(para);
3910     if (!p)
3911         return;
3912 
3913     if (index > p->length() - 1)
3914         index = p->length() - 1;
3915 
3916     drawCursor(false);
3917     cursor->setParagraph(p);
3918     cursor->setIndex(index);
3919     ensureCursorVisible();
3920     drawCursor(true);
3921     updateCurrentFormat();
3922     emit cursorPositionChanged(cursor);
3923     emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
3924 }
3925 
3926 /*!
3927     This function sets the \c{*}\a{para} and \c{*}\a{index} parameters to the
3928     current cursor position. \a para and \a index must not be 0.
3929 
3930     \sa setCursorPosition()
3931 */
3932 
3933 void Q3TextEdit::getCursorPosition(int *para, int *index) const
3934 {
3935     if (!para || !index)
3936         return;
3937     *para = cursor->paragraph()->paragId();
3938     *index = cursor->index();
3939 }
3940 
3941 /*!
3942     Sets a selection which starts at position \a indexFrom in
3943     paragraph \a paraFrom and ends at position \a indexTo in paragraph
3944     \a paraTo.
3945 
3946     Any existing selections which have a different id (\a selNum) are
3947     left alone, but if an existing selection has the same id as \a
3948     selNum it is removed and replaced by this selection.
3949 
3950     Uses the selection settings of selection \a selNum. If \a selNum
3951     is 0, this is the default selection.
3952 
3953     The cursor is moved to the end of the selection if \a selNum is 0,
3954     otherwise the cursor position remains unchanged.
3955 
3956     \sa getSelection() selectedText
3957 */
3958 
3959 void Q3TextEdit::setSelection(int paraFrom, int indexFrom,
3960                               int paraTo, int indexTo, int selNum)
3961 {
3962 #ifdef QT_TEXTEDIT_OPTIMIZATION
3963     if (d->optimMode) {
3964         optimSetSelection(paraFrom, indexFrom, paraTo, indexTo);
3965         repaintContents();
3966         return;
3967     }
3968 #endif
3969     if (doc->hasSelection(selNum)) {
3970         doc->removeSelection(selNum);
3971         repaintChanged();
3972     }
3973     if (selNum > doc->numSelections() - 1)
3974         doc->addSelection(selNum);
3975     Q3TextParagraph *p1 = doc->paragAt(paraFrom);
3976     if (!p1)
3977         return;
3978     Q3TextParagraph *p2 = doc->paragAt(paraTo);
3979     if (!p2)
3980         return;
3981 
3982     if (indexFrom > p1->length() - 1)
3983         indexFrom = p1->length() - 1;
3984     if (indexTo > p2->length() - 1)
3985         indexTo = p2->length() - 1;
3986 
3987     drawCursor(false);
3988     Q3TextCursor c = *cursor;
3989     Q3TextCursor oldCursor = *cursor;
3990     c.setParagraph(p1);
3991     c.setIndex(indexFrom);
3992     cursor->setParagraph(p2);
3993     cursor->setIndex(indexTo);
3994     doc->setSelectionStart(selNum, c);
3995     doc->setSelectionEnd(selNum, *cursor);
3996     repaintChanged();
3997     ensureCursorVisible();
3998     if (selNum != Q3TextDocument::Standard)
3999         *cursor = oldCursor;
4000     drawCursor(true);
4001 }
4002 
4003 /*!
4004     If there is a selection, \c{*}\a{paraFrom} is set to the number of the
4005     paragraph in which the selection begins and \c{*}\a{paraTo} is set to
4006     the number of the paragraph in which the selection ends. (They
4007     could be the same.) \c{*}\a{indexFrom} is set to the index at which the
4008     selection begins within \c{*}\a{paraFrom}, and \c{*}\a{indexTo} is set to
4009     the index at which the selection ends within \c{*}\a{paraTo}.
4010 
4011     If there is no selection, \c{*}\a{paraFrom}, \c{*}\a{indexFrom},
4012     \c{*}\a{paraTo} and \c{*}\a{indexTo} are all set to -1.
4013 
4014     If \a paraFrom, \a indexFrom, \a paraTo or \a indexTo is 0 this
4015     function does nothing.
4016 
4017     The \a selNum is the number of the selection (multiple selections
4018     are supported). It defaults to 0 (the default selection).
4019 
4020     \sa setSelection() selectedText
4021 */
4022 
4023 void Q3TextEdit::getSelection(int *paraFrom, int *indexFrom,
4024                               int *paraTo, int *indexTo, int selNum) const
4025 {
4026     if (!paraFrom || !paraTo || !indexFrom || !indexTo)
4027         return;
4028 #ifdef QT_TEXTEDIT_OPTIMIZATION
4029     if (d->optimMode) {
4030         *paraFrom = d->od->selStart.line;
4031         *paraTo = d->od->selEnd.line;
4032         *indexFrom = d->od->selStart.index;
4033         *indexTo = d->od->selEnd.index;
4034         return;
4035     }
4036 #endif
4037     if (!doc->hasSelection(selNum)) {
4038         *paraFrom = -1;
4039         *indexFrom = -1;
4040         *paraTo = -1;
4041         *indexTo = -1;
4042         return;
4043     }
4044 
4045     doc->selectionStart(selNum, *paraFrom, *indexFrom);
4046     doc->selectionEnd(selNum, *paraTo, *indexTo);
4047 }
4048 
4049 /*!
4050     \property Q3TextEdit::textFormat
4051     \brief the text format: rich text, plain text, log text or auto text.
4052 
4053     The text format is one of the following:
4054     \list
4055     \i Qt::PlainText - all characters, except newlines, are displayed
4056     verbatim, including spaces. Whenever a newline appears in the text
4057     the text edit inserts a hard line break and begins a new
4058     paragraph.
4059     \i Qt::RichText - rich text rendering. The available styles are
4060     defined in the default stylesheet Q3StyleSheet::defaultSheet().
4061     \i Qt::LogText -  optimized mode for very large texts. Supports a very
4062     limited set of formatting tags (color, bold, underline and italic
4063     settings).
4064     \i Qt::AutoText - this is the default. The text edit autodetects which
4065     rendering style is best, Qt::PlainText or Qt::RichText. This is done
4066     by using the Q3StyleSheet::mightBeRichText() function.
4067     \endlist
4068 */
4069 
4070 void Q3TextEdit::setTextFormat(Qt::TextFormat format)
4071 {
4072     doc->setTextFormat(format);
4073 #ifdef QT_TEXTEDIT_OPTIMIZATION
4074     checkOptimMode();
4075 #endif
4076 }
4077 
4078 Qt::TextFormat Q3TextEdit::textFormat() const
4079 {
4080     return doc->textFormat();
4081 }
4082 
4083 /*!
4084     Returns the number of paragraphs in the text; an empty textedit is always
4085     considered to have one paragraph, so 1 is returned in this case.
4086 */
4087 
4088 int Q3TextEdit::paragraphs() const
4089 {
4090 #ifdef QT_TEXTEDIT_OPTIMIZATION
4091     if (d->optimMode) {
4092         return d->od->numLines;
4093     }
4094 #endif
4095     return doc->lastParagraph()->paragId() + 1;
4096 }
4097 
4098 /*!
4099     Returns the number of lines in paragraph \a para, or -1 if there
4100     is no paragraph with index \a para.
4101 */
4102 
4103 int Q3TextEdit::linesOfParagraph(int para) const
4104 {
4105 #ifdef QT_TEXTEDIT_OPTIMIZATION
4106     if (d->optimMode) {
4107         if (d->od->numLines >= para)
4108             return 1;
4109         else
4110             return -1;
4111     }
4112 #endif
4113     Q3TextParagraph *p = doc->paragAt(para);
4114     if (!p)
4115         return -1;
4116     return p->lines();
4117 }
4118 
4119 /*!
4120     Returns the length of the paragraph \a para (i.e. the number of
4121     characters), or -1 if there is no paragraph with index \a para.
4122 
4123     This function ignores newlines.
4124 */
4125 
4126 int Q3TextEdit::paragraphLength(int para) const
4127 {
4128 #ifdef QT_TEXTEDIT_OPTIMIZATION
4129     if (d->optimMode) {
4130         if (d->od->numLines >= para) {
4131             if (d->od->lines[LOGOFFSET(para)].isEmpty()) // CR
4132                 return 1;
4133             else
4134                 return d->od->lines[LOGOFFSET(para)].length();
4135         }
4136         return -1;
4137     }
4138 #endif
4139     Q3TextParagraph *p = doc->paragAt(para);
4140     if (!p)
4141         return -1;
4142     return p->length() - 1;
4143 }
4144 
4145 /*!
4146     Returns the number of lines in the text edit; this could be 0.
4147 
4148     \warning This function may be slow. Lines change all the time
4149     during word wrapping, so this function has to iterate over all the
4150     paragraphs and get the number of lines from each one individually.
4151 */
4152 
4153 int Q3TextEdit::lines() const
4154 {
4155 #ifdef QT_TEXTEDIT_OPTIMIZATION
4156     if (d->optimMode) {
4157         return d->od->numLines;
4158     }
4159 #endif
4160     Q3TextParagraph *p = doc->firstParagraph();
4161     int l = 0;
4162     while (p) {
4163         l += p->lines();
4164         p = p->next();
4165     }
4166 
4167     return l;
4168 }
4169 
4170 /*!
4171     Returns the line number of the line in paragraph \a para in which
4172     the character at position \a index appears. The \a index position is
4173     relative to the beginning of the paragraph. If there is no such
4174     paragraph or no such character at the \a index position (e.g. the
4175     index is out of range) -1 is returned.
4176 */
4177 
4178 int Q3TextEdit::lineOfChar(int para, int index)
4179 {
4180     Q3TextParagraph *p = doc->paragAt(para);
4181     if (!p)
4182         return -1;
4183 
4184     int idx, line;
4185     Q3TextStringChar *c = p->lineStartOfChar(index, &idx, &line);
4186     if (!c)
4187         return -1;
4188 
4189     return line;
4190 }
4191 
4192 void Q3TextEdit::setModified(bool m)
4193 {
4194     bool oldModified = modified;
4195     modified = m;
4196     if (modified && doc->oTextValid)
4197         doc->invalidateOriginalText();
4198     if (oldModified != modified)
4199         emit modificationChanged(modified);
4200 }
4201 
4202 /*!
4203     \property Q3TextEdit::modified
4204     \brief whether the document has been modified by the user
4205 */
4206 
4207 bool Q3TextEdit::isModified() const
4208 {
4209     return modified;
4210 }
4211 
4212 void Q3TextEdit::setModified()
4213 {
4214     if (!isModified())
4215         setModified(true);
4216 }
4217 
4218 /*!
4219     Returns true if the current format is italic; otherwise returns false.
4220 
4221     \sa setItalic()
4222 */
4223 
4224 bool Q3TextEdit::italic() const
4225 {
4226     return currentFormat->font().italic();
4227 }
4228 
4229 /*!
4230     Returns true if the current format is bold; otherwise returns false.
4231 
4232     \sa setBold()
4233 */
4234 
4235 bool Q3TextEdit::bold() const
4236 {
4237     return currentFormat->font().bold();
4238 }
4239 
4240 /*!
4241     Returns true if the current format is underlined; otherwise returns
4242     false.
4243 
4244     \sa setUnderline()
4245 */
4246 
4247 bool Q3TextEdit::underline() const
4248 {
4249     return currentFormat->font().underline();
4250 }
4251 
4252 /*!
4253     Returns the font family of the current format.
4254 
4255     \sa setFamily() setCurrentFont() setPointSize()
4256 */
4257 
4258 QString Q3TextEdit::family() const
4259 {
4260     return currentFormat->font().family();
4261 }
4262 
4263 /*!
4264     Returns the point size of the font of the current format.
4265 
4266     \sa setFamily() setCurrentFont() setPointSize()
4267 */
4268 
4269 int Q3TextEdit::pointSize() const
4270 {
4271     return currentFormat->font().pointSize();
4272 }
4273 
4274 /*!
4275     Returns the color of the current format.
4276 
4277     \sa setColor() setPaper()
4278 */
4279 
4280 QColor Q3TextEdit::color() const
4281 {
4282     return currentFormat->color();
4283 }
4284 
4285 /*!
4286     Returns Q3ScrollView::font()
4287 
4288     \warning In previous versions this function returned the font of
4289     the current format. This lead to confusion. Please use
4290     currentFont() instead.
4291 */
4292 
4293 QFont Q3TextEdit::font() const
4294 {
4295     return Q3ScrollView::font();
4296 }
4297 
4298 /*!
4299     Returns the font of the current format.
4300 
4301     \sa setCurrentFont() setFamily() setPointSize()
4302 */
4303 
4304 QFont Q3TextEdit::currentFont() const
4305 {
4306     return currentFormat->font();
4307 }
4308 
4309 
4310 /*!
4311     Returns the alignment of the current paragraph.
4312 
4313     \sa setAlignment()
4314 */
4315 
4316 int Q3TextEdit::alignment() const
4317 {
4318     return currentAlignment;
4319 }
4320 
4321 /*!
4322     Returns the vertical alignment of the current format.
4323 
4324     \sa setVerticalAlignment()
4325 */
4326 
4327 Q3TextEdit::VerticalAlignment Q3TextEdit::verticalAlignment() const
4328 {
4329     return (Q3TextEdit::VerticalAlignment) currentFormat->vAlign();
4330 }
4331 
4332 void Q3TextEdit::startDrag()
4333 {
4334 #ifndef QT_NO_DRAGANDDROP
4335     mousePressed = false;
4336     inDoubleClick = false;
4337     Q3DragObject *drag = dragObject(viewport());
4338     if (!drag)
4339         return;
4340     if (isReadOnly()) {
4341         drag->dragCopy();
4342     } else {
4343         if (drag->drag() && Q3DragObject::target() != this && Q3DragObject::target() != viewport())
4344             removeSelectedText();
4345     }
4346 #endif
4347 }
4348 
4349 /*!
4350     If \a select is true (the default), all the text is selected as
4351     selection 0. If \a select is false any selected text is
4352     unselected, i.e. the default selection (selection 0) is cleared.
4353 
4354     \sa selectedText
4355 */
4356 
4357 void Q3TextEdit::selectAll(bool select)
4358 {
4359 #ifdef QT_TEXTEDIT_OPTIMIZATION
4360     if (d->optimMode) {
4361         if (select)
4362             optimSelectAll();
4363         else
4364             optimRemoveSelection();
4365         return;
4366     }
4367 #endif
4368     if (!select)
4369         doc->removeSelection(Q3TextDocument::Standard);
4370     else
4371         doc->selectAll(Q3TextDocument::Standard);
4372     repaintChanged();
4373     emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard));
4374     emit selectionChanged();
4375 #ifndef QT_NO_CURSOR
4376     viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
4377 #endif
4378 }
4379 
4380 void Q3TextEdit::UndoRedoInfo::clear()
4381 {
4382     if (valid()) {
4383         if (type == Insert || type == Return)
4384             doc->addCommand(new Q3TextInsertCommand(doc, id, index, d->text.rawData(), styleInformation));
4385         else if (type == Format)
4386             doc->addCommand(new Q3TextFormatCommand(doc, id, index, eid, eindex, d->text.rawData(), format, flags));
4387         else if (type == Style)
4388             doc->addCommand(new Q3TextStyleCommand(doc, id, eid, styleInformation));
4389         else if (type != Invalid) {
4390             doc->addCommand(new Q3TextDeleteCommand(doc, id, index, d->text.rawData(), styleInformation));
4391         }
4392     }
4393     type = Invalid;
4394     d->text.clear();
4395     id = -1;
4396     index = -1;
4397     styleInformation = QByteArray();
4398 }
4399 
4400 
4401 /*!
4402     If there is some selected text (in selection 0) it is deleted. If
4403     there is no selected text (in selection 0) the character to the
4404     right of the text cursor is deleted.
4405 
4406     \sa removeSelectedText() cut()
4407 */
4408 
4409 void Q3TextEdit::del()
4410 {
4411     if (doc->hasSelection(Q3TextDocument::Standard)) {
4412         removeSelectedText();
4413         return;
4414     }
4415 
4416     doKeyboardAction(ActionDelete);
4417 }
4418 
4419 
4420 Q3TextEdit::UndoRedoInfo::UndoRedoInfo(Q3TextDocument *dc)
4421     : type(Invalid), doc(dc)
4422 {
4423     d = new QUndoRedoInfoPrivate;
4424     d->text.clear();
4425     id = -1;
4426     index = -1;
4427 }
4428 
4429 Q3TextEdit::UndoRedoInfo::~UndoRedoInfo()
4430 {
4431     delete d;
4432 }
4433 
4434 bool Q3TextEdit::UndoRedoInfo::valid() const
4435 {
4436     return id >= 0 &&  type != Invalid;
4437 }
4438 
4439 /*!
4440     \internal
4441 
4442   Resets the current format to the default format.
4443 */
4444 
4445 void Q3TextEdit::resetFormat()
4446 {
4447     setAlignment(Qt::AlignAuto);
4448     setParagType(Q3StyleSheetItem::DisplayBlock, Q3StyleSheetItem::ListDisc);
4449     setFormat(doc->formatCollection()->defaultFormat(), Q3TextFormat::Format);
4450 }
4451 
4452 /*!
4453     Returns the Q3StyleSheet which is being used by this text edit.
4454 
4455     \sa setStyleSheet()
4456 */
4457 
4458 Q3StyleSheet* Q3TextEdit::styleSheet() const
4459 {
4460     return doc->styleSheet();
4461 }
4462 
4463 /*!
4464     Sets the stylesheet to use with this text edit to \a styleSheet.
4465     Changes will only take effect for new text added with setText() or
4466     append().
4467 
4468     \sa styleSheet()
4469 */
4470 
4471 void Q3TextEdit::setStyleSheet(Q3StyleSheet* styleSheet)
4472 {
4473     doc->setStyleSheet(styleSheet);
4474 }
4475 
4476 /*!
4477     \property Q3TextEdit::paper
4478     \brief the background (paper) brush.
4479 
4480     The brush that is currently used to draw the background of the
4481     text edit. The initial setting is an empty brush.
4482 */
4483 
4484 void Q3TextEdit::setPaper(const QBrush& pap)
4485 {
4486     doc->setPaper(new QBrush(pap));
4487     if ( pap.pixmap() )
4488         viewport()->setBackgroundPixmap( *pap.pixmap() );
4489     QPalette pal = palette();
4490     pal.setColor(QPalette::Window, pap.color());
4491     setPalette(pal);
4492     pal = viewport()->palette();
4493     pal.setColor(QPalette::Window, pap.color());
4494     viewport()->setPalette(pal);
4495 #ifdef QT_TEXTEDIT_OPTIMIZATION
4496     // force a repaint of the entire viewport - using updateContents()
4497     // would clip the coords to the content size
4498     if (d->optimMode)
4499         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height());
4500     else
4501 #endif
4502         updateContents();
4503 }
4504 
4505 QBrush Q3TextEdit::paper() const
4506 {
4507     if (doc->paper())
4508         return *doc->paper();
4509     return QBrush(palette().base());
4510 }
4511 
4512 /*!
4513     \property Q3TextEdit::linkUnderline
4514     \brief whether hypertext links will be underlined
4515 
4516     If true (the default) hypertext links will be displayed
4517     underlined. If false links will not be displayed underlined.
4518 */
4519 
4520 void Q3TextEdit::setLinkUnderline(bool b)
4521 {
4522     if (doc->underlineLinks() == b)
4523         return;
4524     doc->setUnderlineLinks(b);
4525     repaintChanged();
4526 }
4527 
4528 bool Q3TextEdit::linkUnderline() const
4529 {
4530     return doc->underlineLinks();
4531 }
4532 
4533 /*!
4534     Sets the text edit's mimesource factory to \a factory. See
4535     Q3MimeSourceFactory for further details.
4536 
4537     \sa mimeSourceFactory()
4538  */
4539 
4540 #ifndef QT_NO_MIME
4541 void Q3TextEdit::setMimeSourceFactory(Q3MimeSourceFactory* factory)
4542 {
4543     doc->setMimeSourceFactory(factory);
4544 }
4545 
4546 /*!
4547     Returns the Q3MimeSourceFactory which is being used by this text
4548     edit.
4549 
4550     \sa setMimeSourceFactory()
4551 */
4552 
4553 Q3MimeSourceFactory* Q3TextEdit::mimeSourceFactory() const
4554 {
4555     return doc->mimeSourceFactory();
4556 }
4557 #endif
4558 
4559 /*!
4560     Returns how many pixels high the text edit needs to be to display
4561     all the text if the text edit is \a w pixels wide.
4562 */
4563 
4564 int Q3TextEdit::heightForWidth(int w) const
4565 {
4566     int oldw = doc->width();
4567     doc->doLayout(0, w);
4568     int h = doc->height();
4569     doc->setWidth(oldw);
4570     doc->invalidate();
4571     ((Q3TextEdit*)this)->formatMore();
4572     return h;
4573 }
4574 
4575 /*!
4576     Appends a new paragraph with \a text to the end of the text edit. Note that
4577     the undo/redo history is cleared by this function, and no undo
4578     history is kept for appends which makes them faster than
4579     insert()s. If you want to append text which is added to the
4580     undo/redo history as well, use insertParagraph().
4581 */
4582 
4583 void Q3TextEdit::append(const QString &text)
4584 {
4585 #ifdef QT_TEXTEDIT_OPTIMIZATION
4586     if (d->optimMode) {
4587         optimAppend(text);
4588         return;
4589     }
4590 #endif
4591     // flush and clear the undo/redo stack if necessary
4592     undoRedoInfo.clear();
4593     doc->commands()->clear();
4594 
4595     doc->removeSelection(Q3TextDocument::Standard);
4596     Qt::TextFormat f = doc->textFormat();
4597     if (f == Qt::AutoText) {
4598         if (Q3StyleSheet::mightBeRichText(text))
4599             f = Qt::RichText;
4600         else
4601             f = Qt::PlainText;
4602     }
4603 
4604     drawCursor(false);
4605     Q3TextCursor oldc(*cursor);
4606     ensureFormatted(doc->lastParagraph());
4607     bool atBottom = contentsY() >= contentsHeight() - visibleHeight();
4608     cursor->gotoEnd();
4609     if (cursor->index() > 0)
4610         cursor->splitAndInsertEmptyParagraph();
4611     Q3TextCursor oldCursor2 = *cursor;
4612 
4613     if (f == Qt::PlainText) {
4614         cursor->insert(text, true);
4615         if (doc->useFormatCollection() && !doc->preProcessor() &&
4616             currentFormat != cursor->paragraph()->at( cursor->index() )->format()) {
4617             doc->setSelectionStart( Q3TextDocument::Temp, oldCursor2 );
4618             doc->setSelectionEnd( Q3TextDocument::Temp, *cursor );
4619             doc->setFormat( Q3TextDocument::Temp, currentFormat, Q3TextFormat::Format );
4620             doc->removeSelection( Q3TextDocument::Temp );
4621         }
4622     } else {
4623         cursor->paragraph()->setListItem(false);
4624         cursor->paragraph()->setListDepth(0);
4625         if (cursor->paragraph()->prev())
4626             cursor->paragraph()->prev()->invalidate(0); // vertical margins might have to change
4627         doc->setRichTextInternal(text);
4628     }
4629     formatMore();
4630     repaintChanged();
4631     if (atBottom)
4632         scrollToBottom();
4633     *cursor = oldc;
4634     if (!isReadOnly())
4635         cursorVisible = true;
4636     setModified();
4637     emit textChanged();
4638 }
4639 
4640 /*!
4641     \property Q3TextEdit::hasSelectedText
4642     \brief whether some text is selected in selection 0
4643 */
4644 
4645 bool Q3TextEdit::hasSelectedText() const
4646 {
4647 #ifdef QT_TEXTEDIT_OPTIMIZATION
4648     if (d->optimMode)
4649         return optimHasSelection();
4650     else
4651 #endif
4652         return doc->hasSelection(Q3TextDocument::Standard);
4653 }
4654 
4655 /*!
4656     \property Q3TextEdit::selectedText
4657     \brief The selected text (from selection 0) or an empty string if
4658     there is no currently selected text (in selection 0).
4659 
4660     The text is always returned as Qt::PlainText if the textFormat() is
4661     Qt::PlainText or Qt::AutoText, otherwise it is returned as HTML.
4662 
4663     \sa hasSelectedText
4664 */
4665 
4666 QString Q3TextEdit::selectedText() const
4667 {
4668 #ifdef QT_TEXTEDIT_OPTIMIZATION
4669     if (d->optimMode)
4670         return optimSelectedText();
4671     else
4672 #endif
4673         return doc->selectedText(Q3TextDocument::Standard, textFormat() == Qt::RichText);
4674 }
4675 
4676 bool Q3TextEdit::handleReadOnlyKeyEvent(QKeyEvent *e)
4677 {
4678     switch(e->key()) {
4679     case Qt::Key_Down:
4680         setContentsPos(contentsX(), contentsY() + 10);
4681         break;
4682     case Qt::Key_Up:
4683         setContentsPos(contentsX(), contentsY() - 10);
4684         break;
4685     case Qt::Key_Left:
4686         setContentsPos(contentsX() - 10, contentsY());
4687         break;
4688     case Qt::Key_Right:
4689         setContentsPos(contentsX() + 10, contentsY());
4690         break;
4691     case Qt::Key_PageUp:
4692         setContentsPos(contentsX(), contentsY() - visibleHeight());
4693         break;
4694     case Qt::Key_PageDown:
4695         setContentsPos(contentsX(), contentsY() + visibleHeight());
4696         break;
4697     case Qt::Key_Home:
4698         setContentsPos(contentsX(), 0);
4699         break;
4700     case Qt::Key_End:
4701         setContentsPos(contentsX(), contentsHeight() - visibleHeight());
4702         break;
4703     case Qt::Key_F16: // Copy key on Sun keyboards
4704         copy();
4705         break;
4706 #ifndef QT_NO_NETWORKPROTOCOL
4707     case Qt::Key_Return:
4708     case Qt::Key_Enter:
4709     case Qt::Key_Space: {
4710         if (!doc->focusIndicator.href.isEmpty()
4711                 || !doc->focusIndicator.name.isEmpty()) {
4712             if (!doc->focusIndicator.href.isEmpty()) {
4713                 QUrl u = QUrl(doc->context()).resolved(doc->focusIndicator.href);
4714                 emitLinkClicked(u.toString(QUrl::None));
4715             }
4716             if (!doc->focusIndicator.name.isEmpty())
4717                 if (Q3TextBrowser *browser = qobject_cast<Q3TextBrowser*>(this))
4718                     emit browser->anchorClicked(doc->focusIndicator.name, doc->focusIndicator.href);
4719 
4720 #ifndef QT_NO_CURSOR
4721             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
4722 #endif
4723         }
4724     } break;
4725 #endif
4726     default:
4727         if (e->state() & Qt::ControlButton) {
4728             switch (e->key()) {
4729             case Qt::Key_C: case Qt::Key_F16: // Copy key on Sun keyboards
4730                 copy();
4731                 break;
4732 #ifdef Q_WS_WIN
4733             case Qt::Key_Insert:
4734                 copy();
4735                 break;
4736             case Qt::Key_A:
4737                 selectAll();
4738                 break;
4739 #endif
4740             }
4741 
4742         }
4743         return false;
4744     }
4745     return true;
4746 }
4747 
4748 /*!
4749     Returns the context of the text edit. The context is a path which
4750     the text edit's Q3MimeSourceFactory uses to resolve the locations
4751     of files and images.
4752 
4753     \sa text
4754 */
4755 
4756 QString Q3TextEdit::context() const
4757 {
4758     return doc->context();
4759 }
4760 
4761 /*!
4762     \property Q3TextEdit::documentTitle
4763     \brief the title of the document parsed from the text.
4764 
4765     For Qt::PlainText the title will be an empty string. For \c
4766     Qt::RichText the title will be the text between the \c{<title>} tags,
4767     if present, otherwise an empty string.
4768 */
4769 
4770 QString Q3TextEdit::documentTitle() const
4771 {
4772     return doc->attributes()[QLatin1String("title")];
4773 }
4774 
4775 void Q3TextEdit::makeParagVisible(Q3TextParagraph *p)
4776 {
4777     setContentsPos(contentsX(), qMin(p->rect().y(), contentsHeight() - visibleHeight()));
4778 }
4779 
4780 /*!
4781     Scrolls the text edit to make the text at the anchor called \a
4782     name visible, if it can be found in the document. If the anchor
4783     isn't found no scrolling will occur. An anchor is defined using
4784     the HTML anchor tag, e.g. \c{<a name="target">}.
4785 */
4786 
4787 void Q3TextEdit::scrollToAnchor(const QString& name)
4788 {
4789     if (!isVisible()) {
4790         d->scrollToAnchor = name;
4791         return;
4792     }
4793     if (name.isEmpty())
4794         return;
4795     sync();
4796     Q3TextCursor cursor(doc);
4797     Q3TextParagraph* last = doc->lastParagraph();
4798     for (;;) {
4799         Q3TextStringChar* c = cursor.paragraph()->at(cursor.index());
4800         if(c->isAnchor()) {
4801             QString a = c->anchorName();
4802             if (a == name ||
4803                  (a.contains(QLatin1Char('#')) && a.split(QLatin1Char('#')).contains(name))) {
4804                 setContentsPos(contentsX(), qMin(cursor.paragraph()->rect().top() + cursor.totalOffsetY(), contentsHeight() - visibleHeight()));
4805                 break;
4806             }
4807         }
4808         if (cursor.paragraph() == last && cursor.atParagEnd() )
4809             break;
4810         cursor.gotoNextLetter();
4811     }
4812 }
4813 
4814 /*!
4815     Returns the text for the attribute \a attr (Qt::AnchorHref by
4816     default) if there is an anchor at position \a pos (in contents
4817     coordinates); otherwise returns an empty string.
4818 */
4819 
4820 QString Q3TextEdit::anchorAt(const QPoint& pos, Qt::AnchorAttribute attr)
4821 {
4822     Q3TextCursor c(doc);
4823     placeCursor(pos, &c, true);
4824     switch(attr) {
4825         case Qt::AnchorName:
4826             return c.paragraph()->at(c.index())->anchorName();
4827         case Qt::AnchorHref:
4828             return c.paragraph()->at(c.index())->anchorHref();
4829     }
4830     // incase the compiler is really dumb about determining if a function
4831     // returns something :)
4832     return QString();
4833 }
4834 
4835 void Q3TextEdit::documentWidthChanged(int w)
4836 {
4837     resizeContents(qMax(visibleWidth(), w), contentsHeight());
4838 }
4839 
4840 /*! \internal
4841 
4842   This function does nothing
4843 */
4844 
4845 void Q3TextEdit::updateStyles()
4846 {
4847 }
4848 
4849 void Q3TextEdit::setDocument(Q3TextDocument *dc)
4850 {
4851     if (dc == 0) {
4852         qWarning("Q3TextEdit::setDocument() called with null Q3TextDocument pointer");
4853         return;
4854     }
4855     if (dc == doc)
4856         return;
4857     doc = dc;
4858     delete cursor;
4859     cursor = new Q3TextCursor(doc);
4860     clearUndoRedo();
4861     undoRedoInfo.doc = doc;
4862     lastFormatted = 0;
4863 }
4864 
4865 #ifndef QT_NO_CLIPBOARD
4866 
4867 /*!
4868     Pastes the text with format \a subtype from the clipboard into the
4869     text edit at the current cursor position. The \a subtype can be
4870     "plain" or "html".
4871 
4872     If there is no text with format \a subtype in the clipboard
4873     nothing happens.
4874 
4875     \sa paste() cut() Q3TextEdit::copy()
4876 */
4877 
4878 void Q3TextEdit::pasteSubType(const QByteArray &subtype)
4879 {
4880 #ifndef QT_NO_MIMECLIPBOARD
4881     QMimeSource *m = QApplication::clipboard()->data(d->clipboard_mode);
4882     pasteSubType(subtype, m);
4883 #endif
4884 }
4885 
4886 /*! \internal */
4887 
4888 void Q3TextEdit::pasteSubType(const QByteArray& subtype, QMimeSource *m)
4889 {
4890 #ifndef QT_NO_MIME
4891     QByteArray st = subtype;
4892 
4893     if (subtype != "x-qrichtext")
4894         st.prepend("text/");
4895     else
4896         st.prepend("application/");
4897     if (!m)
4898         return;
4899     if (doc->hasSelection(Q3TextDocument::Standard))
4900         removeSelectedText();
4901     if (!Q3RichTextDrag::canDecode(m))
4902         return;
4903     QString t;
4904     if (!Q3RichTextDrag::decode(m, t, QString::fromLatin1(st), QString::fromLatin1(subtype)))
4905         return;
4906     if (st == "application/x-qrichtext") {
4907         int start;
4908         if ((start = t.indexOf(QLatin1String("<!--StartFragment-->"))) != -1) {
4909             start += 20;
4910             int end = t.indexOf(QLatin1String("<!--EndFragment-->"));
4911             Q3TextCursor oldC = *cursor;
4912 
4913             // during the setRichTextInternal() call the cursors
4914             // paragraph might get joined with the provious one, so
4915             // the cursors one would get deleted and oldC.paragraph()
4916             // would be a dnagling pointer. To avoid that try to go
4917             // one letter back and later go one forward again.
4918             oldC.gotoPreviousLetter();
4919             bool couldGoBack = oldC != *cursor;
4920             // first para might get deleted, so remember to reset it
4921             bool wasAtFirst = oldC.paragraph() == doc->firstParagraph();
4922 
4923             if (start < end)
4924                 t = t.mid(start, end - start);
4925             else
4926                 t = t.mid(start);
4927             lastFormatted = cursor->paragraph();
4928             if (lastFormatted->prev())
4929                 lastFormatted = lastFormatted->prev();
4930             doc->setRichTextInternal(t, cursor);
4931 
4932             // the first para might have been deleted in
4933             // setRichTextInternal(). To be sure, reset it if
4934             // necessary.
4935             if (wasAtFirst) {
4936                 int index = oldC.index();
4937                 oldC.setParagraph(doc->firstParagraph());
4938                 oldC.setIndex(index);
4939             }
4940 
4941             // if we went back one letter before (see last comment),
4942             // go one forward to point to the right position
4943             if (couldGoBack)
4944                 oldC.gotoNextLetter();
4945 
4946             if (undoEnabled && !isReadOnly()) {
4947                 doc->setSelectionStart(Q3TextDocument::Temp, oldC);
4948                 doc->setSelectionEnd(Q3TextDocument::Temp, *cursor);
4949 
4950                 checkUndoRedoInfo(UndoRedoInfo::Insert);
4951                 if (!undoRedoInfo.valid()) {
4952                     undoRedoInfo.id = oldC.paragraph()->paragId();
4953                     undoRedoInfo.index = oldC.index();
4954                     undoRedoInfo.d->text.clear();
4955                 }
4956                 int oldLen = undoRedoInfo.d->text.length();
4957                 if (!doc->preProcessor()) {
4958                     QString txt = doc->selectedText(Q3TextDocument::Temp);
4959                     undoRedoInfo.d->text += txt;
4960                     for (int i = 0; i < (int)txt.length(); ++i) {
4961                         if (txt[i] != QLatin1Char('\n') && oldC.paragraph()->at(oldC.index())->format()) {
4962                             oldC.paragraph()->at(oldC.index())->format()->addRef();
4963                             undoRedoInfo.d->text.
4964                                 setFormat(oldLen + i, oldC.paragraph()->at(oldC.index())->format(), true);
4965                         }
4966                         oldC.gotoNextLetter();
4967                     }
4968                 }
4969                 undoRedoInfo.clear();
4970                 removeSelection(Q3TextDocument::Temp);
4971             }
4972 
4973             formatMore();
4974             setModified();
4975             emit textChanged();
4976             repaintChanged();
4977             ensureCursorVisible();
4978             return;
4979         }
4980     } else {
4981 #if defined(Q_OS_WIN32)
4982         // Need to convert CRLF to LF
4983         t.replace(QLatin1String("\r\n"), QLatin1String("\n"));
4984 #elif defined(Q_OS_MAC)
4985         //need to convert CR to LF
4986         t.replace(QLatin1Char('\r'), QLatin1Char('\n'));
4987 #endif
4988         QChar *uc = (QChar *)t.unicode();
4989         for (int i = 0; i < t.length(); i++) {
4990             if (uc[i] < QLatin1Char(' ') && uc[i] != QLatin1Char('\n') && uc[i] != QLatin1Char('\t'))
4991                 uc[i] = QLatin1Char(' ');
4992         }
4993         if (!t.isEmpty())
4994             insert(t, false, true);
4995     }
4996 #endif //QT_NO_MIME
4997 }
4998 
4999 #ifndef QT_NO_MIMECLIPBOARD
5000 /*!
5001     Prompts the user to choose a type from a list of text types
5002     available, then copies text from the clipboard (if there is any)
5003     into the text edit at the current text cursor position. Any
5004     selected text (in selection 0) is first deleted.
5005 */
5006 void Q3TextEdit::pasteSpecial(const QPoint& pt)
5007 {
5008     QByteArray st = pickSpecial(QApplication::clipboard()->data(d->clipboard_mode),
5009                                true, pt);
5010     if (!st.isEmpty())
5011         pasteSubType(st);
5012 }
5013 #endif
5014 #ifndef QT_NO_MIME
5015 QByteArray Q3TextEdit::pickSpecial(QMimeSource* ms, bool always_ask, const QPoint& pt)
5016 {
5017     if (ms)  {
5018 #ifndef QT_NO_MENU
5019         QMenu popup(this);
5020         QString fmt;
5021         int n = 0;
5022         QHash<QString, bool> done;
5023         for (int i = 0; !(fmt = QLatin1String(ms->format(i))).isNull(); i++) {
5024             int semi = fmt.indexOf(QLatin1Char(';'));
5025             if (semi >= 0)
5026                 fmt = fmt.left(semi);
5027             if (fmt.left(5) == QLatin1String("text/")) {
5028                 fmt = fmt.mid(5);
5029                 if (!done.contains(fmt)) {
5030                     done.insert(fmt,true);
5031                     popup.insertItem(fmt, i);
5032                     n++;
5033                 }
5034             }
5035         }
5036         if (n) {
5037             QAction *action = (n == 1 && !always_ask)
5038                 ? popup.actions().at(0)
5039                 : popup.exec(pt);
5040             if (action)
5041                 return action->text().toLatin1();
5042         }
5043 #else
5044         QString fmt;
5045         for (int i = 0; !(fmt = ms->format(i)).isNull(); i++) {
5046             int semi = fmt.indexOf(';');
5047             if (semi >= 0)
5048                 fmt = fmt.left(semi);
5049             if (fmt.left(5) == "text/") {
5050                 fmt = fmt.mid(5);
5051                 return fmt.latin1();
5052             }
5053         }
5054 #endif
5055     }
5056     return QByteArray();
5057 }
5058 #endif // QT_NO_MIME
5059 #endif // QT_NO_CLIPBOARD
5060 
5061 /*!
5062     \enum Q3TextEdit::WordWrap
5063 
5064     This enum defines the Q3TextEdit's word wrap modes.
5065 
5066     \value NoWrap Do not wrap the text.
5067 
5068     \value WidgetWidth Wrap the text at the current width of the
5069     widget (this is the default). Wrapping is at whitespace by
5070     default; this can be changed with setWrapPolicy().
5071 
5072     \value FixedPixelWidth Wrap the text at a fixed number of pixels
5073     from the widget's left side. The number of pixels is set with
5074     wrapColumnOrWidth().
5075 
5076     \value FixedColumnWidth Wrap the text at a fixed number of
5077     character columns from the widget's left side. The number of
5078     characters is set with wrapColumnOrWidth(). This is useful if you
5079     need formatted text that can also be displayed gracefully on
5080     devices with monospaced fonts, for example a standard VT100
5081     terminal, where you might set wrapColumnOrWidth() to 80.
5082 
5083     \sa setWordWrap() wordWrap()
5084 */
5085 
5086 /*!
5087     \property Q3TextEdit::wordWrap
5088     \brief the word wrap mode
5089 
5090     The default mode is \c WidgetWidth which causes words to be
5091     wrapped at the right edge of the text edit. Wrapping occurs at
5092     whitespace, keeping whole words intact. If you want wrapping to
5093     occur within words use setWrapPolicy(). If you set a wrap mode of
5094     \c FixedPixelWidth or \c FixedColumnWidth you should also call
5095     setWrapColumnOrWidth() with the width you want.
5096 
5097     \sa WordWrap, wrapColumnOrWidth, wrapPolicy,
5098 */
5099 
5100 void Q3TextEdit::setWordWrap(WordWrap mode)
5101 {
5102     if (wrapMode == mode)
5103         return;
5104     wrapMode = mode;
5105     switch (mode) {
5106     case NoWrap:
5107         document()->formatter()->setWrapEnabled(false);
5108         document()->formatter()->setWrapAtColumn(-1);
5109         doc->setWidth(visibleWidth());
5110         doc->setMinimumWidth(-1);
5111         doc->invalidate();
5112         updateContents();
5113         lastFormatted = doc->firstParagraph();
5114         interval = 0;
5115         formatMore();
5116         break;
5117     case WidgetWidth:
5118         document()->formatter()->setWrapEnabled(true);
5119         document()->formatter()->setWrapAtColumn(-1);
5120         doResize();
5121         break;
5122     case FixedPixelWidth:
5123         document()->formatter()->setWrapEnabled(true);
5124         document()->formatter()->setWrapAtColumn(-1);
5125         if (wrapWidth < 0)
5126             wrapWidth = 200;
5127         setWrapColumnOrWidth(wrapWidth);
5128         break;
5129     case FixedColumnWidth:
5130         if (wrapWidth < 0)
5131             wrapWidth = 80;
5132         document()->formatter()->setWrapEnabled(true);
5133         document()->formatter()->setWrapAtColumn(wrapWidth);
5134         setWrapColumnOrWidth(wrapWidth);
5135         break;
5136     }
5137 #ifdef QT_TEXTEDIT_OPTIMIZATION
5138     checkOptimMode();
5139 #endif
5140 }
5141 
5142 Q3TextEdit::WordWrap Q3TextEdit::wordWrap() const
5143 {
5144     return wrapMode;
5145 }
5146 
5147 /*!
5148     \property Q3TextEdit::wrapColumnOrWidth
5149     \brief the position (in pixels or columns depending on the wrap mode) where text will be wrapped
5150 
5151     If the wrap mode is \c FixedPixelWidth, the value is the number of
5152     pixels from the left edge of the text edit at which text should be
5153     wrapped. If the wrap mode is \c FixedColumnWidth, the value is the
5154     column number (in character columns) from the left edge of the
5155     text edit at which text should be wrapped.
5156 
5157     \sa wordWrap
5158 */
5159 void Q3TextEdit::setWrapColumnOrWidth(int value)
5160 {
5161     wrapWidth = value;
5162     if (wrapMode == FixedColumnWidth) {
5163         document()->formatter()->setWrapAtColumn(wrapWidth);
5164         resizeContents(0, 0);
5165         doc->setWidth(visibleWidth());
5166         doc->setMinimumWidth(-1);
5167     } else if (wrapMode == FixedPixelWidth) {
5168         document()->formatter()->setWrapAtColumn(-1);
5169         resizeContents(wrapWidth, 0);
5170         doc->setWidth(wrapWidth);
5171         doc->setMinimumWidth(wrapWidth);
5172     } else {
5173         return;
5174     }
5175     doc->invalidate();
5176     updateContents();
5177     lastFormatted = doc->firstParagraph();
5178     interval = 0;
5179     formatMore();
5180 }
5181 
5182 int Q3TextEdit::wrapColumnOrWidth() const
5183 {
5184     if (wrapMode == WidgetWidth)
5185         return visibleWidth();
5186     return wrapWidth;
5187 }
5188 
5189 
5190 /*!
5191     \enum Q3TextEdit::WrapPolicy
5192 
5193     This enum defines where text can be wrapped in word wrap mode.
5194 
5195     \value AtWhiteSpace Don't use this deprecated value (it is a
5196     synonym for \c AtWordBoundary which you should use instead).
5197     \value Anywhere  Break anywhere, including within words.
5198     \value AtWordBoundary Break lines at word boundaries, e.g. spaces or
5199     newlines
5200     \value AtWordOrDocumentBoundary Break lines at whitespace, e.g.
5201     spaces or newlines if possible. Break it anywhere otherwise.
5202 
5203     \sa setWrapPolicy()
5204 */
5205 
5206 /*!
5207     \property Q3TextEdit::wrapPolicy
5208     \brief the word wrap policy, at whitespace or anywhere
5209 
5210     Defines where text can be wrapped when word wrap mode is not \c
5211     NoWrap. The choices are \c AtWordBoundary (the default), \c
5212     Anywhere and \c AtWordOrDocumentBoundary
5213 
5214     \sa wordWrap
5215 */
5216 
5217 void Q3TextEdit::setWrapPolicy(WrapPolicy policy)
5218 {
5219     if (wPolicy == policy)
5220         return;
5221     wPolicy = policy;
5222     Q3TextFormatter *formatter;
5223     if (policy == AtWordBoundary || policy == AtWordOrDocumentBoundary) {
5224         formatter = new Q3TextFormatterBreakWords;
5225         formatter->setAllowBreakInWords(policy == AtWordOrDocumentBoundary);
5226     } else {
5227         formatter = new Q3TextFormatterBreakInWords;
5228     }
5229     formatter->setWrapAtColumn(document()->formatter()->wrapAtColumn());
5230     formatter->setWrapEnabled(document()->formatter()->isWrapEnabled(0));
5231     document()->setFormatter(formatter);
5232     doc->invalidate();
5233     updateContents();
5234     lastFormatted = doc->firstParagraph();
5235     interval = 0;
5236     formatMore();
5237 }
5238 
5239 Q3TextEdit::WrapPolicy Q3TextEdit::wrapPolicy() const
5240 {
5241     return wPolicy;
5242 }
5243 
5244 /*!
5245     Deletes all the text in the text edit.
5246 
5247     \sa cut() removeSelectedText() setText()
5248 */
5249 
5250 void Q3TextEdit::clear()
5251 {
5252 #ifdef QT_TEXTEDIT_OPTIMIZATION
5253     if (d->optimMode) {
5254         optimSetText(QLatin1String(""));
5255     } else
5256 #endif
5257     {
5258         // make clear undoable
5259         doc->selectAll(Q3TextDocument::Temp);
5260         removeSelectedText(Q3TextDocument::Temp);
5261         setContentsPos(0, 0);
5262         if (cursor->isValid())
5263             cursor->restoreState();
5264         doc->clear(true);
5265         delete cursor;
5266         cursor = new Q3TextCursor(doc);
5267         lastFormatted = 0;
5268     }
5269     updateContents();
5270 
5271     emit cursorPositionChanged(cursor);
5272     emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index());
5273 }
5274 
5275 int Q3TextEdit::undoDepth() const
5276 {
5277     return document()->undoDepth();
5278 }
5279 
5280 /*!
5281     \property Q3TextEdit::length
5282     \brief the number of characters in the text
5283 */
5284 
5285 int Q3TextEdit::length() const
5286 {
5287 #ifdef QT_TEXTEDIT_OPTIMIZATION
5288     if (d->optimMode)
5289         return d->od->len;
5290     else
5291 #endif
5292         return document()->length();
5293 }
5294 
5295 /*!
5296     \property Q3TextEdit::tabStopWidth
5297     \brief the tab stop width in pixels
5298 */
5299 
5300 int Q3TextEdit::tabStopWidth() const
5301 {
5302     return document()->tabStopWidth();
5303 }
5304 
5305 void Q3TextEdit::setUndoDepth(int d)
5306 {
5307     document()->setUndoDepth(d);
5308 }
5309 
5310 void Q3TextEdit::setTabStopWidth(int ts)
5311 {
5312     document()->setTabStops(ts);
5313     doc->invalidate();
5314     lastFormatted = doc->firstParagraph();
5315     interval = 0;
5316     formatMore();
5317     updateContents();
5318 }
5319 
5320 /*!
5321     \reimp
5322 */
5323 
5324 QSize Q3TextEdit::sizeHint() const
5325 {
5326     // cf. Q3ScrollView::sizeHint()
5327     ensurePolished();
5328     int f = 2 * frameWidth();
5329     int h = fontMetrics().height();
5330     QSize sz(f, f);
5331     return sz.expandedTo(QSize(12 * h, 8 * h));
5332 }
5333 
5334 void Q3TextEdit::clearUndoRedo()
5335 {
5336     if (!undoEnabled)
5337         return;
5338     undoRedoInfo.clear();
5339     emit undoAvailable(doc->commands()->isUndoAvailable());
5340     emit redoAvailable(doc->commands()->isRedoAvailable());
5341 }
5342 
5343 /*!  \internal
5344   \warning In Qt 3.1 we will provide a cleaer API for the
5345   functionality which is provided by this function and in Qt 4.0 this
5346   function will go away.
5347 
5348   This function gets the format of the character at position \a
5349   index in paragraph \a para. Sets \a font to the character's font, \a
5350   color to the character's color and \a verticalAlignment to the
5351   character's vertical alignment.
5352 
5353   Returns false if \a para or \a index is out of range otherwise
5354   returns true.
5355 */
5356 
5357 bool Q3TextEdit::getFormat(int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment)
5358 {
5359     if (!font || !color)
5360         return false;
5361     Q3TextParagraph *p = doc->paragAt(para);
5362     if (!p)
5363         return false;
5364     if (index < 0 || index >= p->length())
5365         return false;
5366     *font = p->at(index)->format()->font();
5367     *color = p->at(index)->format()->color();
5368     *verticalAlignment = (VerticalAlignment)p->at(index)->format()->vAlign();
5369     return true;
5370 }
5371 
5372 /*!  \internal
5373   \warning In Qt 3.1 we will provide a cleaer API for the
5374   functionality which is provided by this function and in Qt 4.0 this
5375   function will go away.
5376 
5377   This function gets the format of the paragraph \a para. Sets \a
5378   font to the paragraphs's font, \a color to the paragraph's color, \a
5379   verticalAlignment to the paragraph's vertical alignment, \a
5380   alignment to the paragraph's alignment, \a displayMode to the
5381   paragraph's display mode, \a listStyle to the paragraph's list style
5382   (if the display mode is Q3StyleSheetItem::DisplayListItem) and \a
5383   listDepth to the depth of the list (if the display mode is
5384   Q3StyleSheetItem::DisplayListItem).
5385 
5386   Returns false if \a para is out of range otherwise returns true.
5387 */
5388 
5389 bool Q3TextEdit::getParagraphFormat(int para, QFont *font, QColor *color,
5390                                     VerticalAlignment *verticalAlignment, int *alignment,
5391                                     Q3StyleSheetItem::DisplayMode *displayMode,
5392                                     Q3StyleSheetItem::ListStyle *listStyle,
5393                                     int *listDepth)
5394 {
5395     if (!font || !color || !alignment || !displayMode || !listStyle)
5396         return false;
5397     Q3TextParagraph *p = doc->paragAt(para);
5398     if (!p)
5399         return false;
5400     *font = p->at(0)->format()->font();
5401     *color = p->at(0)->format()->color();
5402     *verticalAlignment = (VerticalAlignment)p->at(0)->format()->vAlign();
5403     *alignment = p->alignment();
5404     *displayMode = p->isListItem() ? Q3StyleSheetItem::DisplayListItem : Q3StyleSheetItem::DisplayBlock;
5405     *listStyle = p->listStyle();
5406     *listDepth = p->listDepth();
5407     return true;
5408 }
5409 
5410 
5411 
5412 /*!
5413     This function is called to create a right mouse button popup menu
5414     at the document position \a pos. If you want to create a custom
5415     popup menu, reimplement this function and return the created popup
5416     menu. Ownership of the popup menu is transferred to the caller.
5417 
5418     \warning The QPopupMenu ID values 0-7 are reserved, and they map to the
5419     standard operations. When inserting items into your custom popup menu, be
5420     sure to specify ID values larger than 7.
5421 */
5422 
5423 Q3PopupMenu *Q3TextEdit::createPopupMenu(const QPoint& pos)
5424 {
5425     Q_UNUSED(pos)
5426 #ifndef QT_NO_POPUPMENU
5427     Q3PopupMenu *popup = new Q3PopupMenu(this, "qt_edit_menu");
5428     if (!isReadOnly()) {
5429         d->id[IdUndo] = popup->insertItem(tr("&Undo") + ACCEL_KEY(Z));
5430         d->id[IdRedo] = popup->insertItem(tr("&Redo") + ACCEL_KEY(Y));
5431         popup->addSeparator();
5432     }
5433 #ifndef QT_NO_CLIPBOARD
5434     if (!isReadOnly())
5435         d->id[IdCut] = popup->insertItem(tr("Cu&t") + ACCEL_KEY(X));
5436     d->id[IdCopy] = popup->insertItem(tr("&Copy") + ACCEL_KEY(C));
5437     if (!isReadOnly())
5438         d->id[IdPaste] = popup->insertItem(tr("&Paste") + ACCEL_KEY(V));
5439 #endif
5440     if (!isReadOnly()) {
5441         d->id[IdClear] = popup->insertItem(tr("Clear"));
5442         popup->addSeparator();
5443     }
5444 #if defined(Q_WS_X11)
5445     d->id[IdSelectAll] = popup->insertItem(tr("Select All"));
5446 #else
5447     d->id[IdSelectAll] = popup->insertItem(tr("Select All") + ACCEL_KEY(A));
5448 #endif
5449     popup->setItemEnabled(d->id[IdUndo], !isReadOnly() && doc->commands()->isUndoAvailable());
5450     popup->setItemEnabled(d->id[IdRedo], !isReadOnly() && doc->commands()->isRedoAvailable());
5451 #ifndef QT_NO_CLIPBOARD
5452     popup->setItemEnabled(d->id[IdCut], !isReadOnly() && doc->hasSelection(Q3TextDocument::Standard, true));
5453 #ifdef QT_TEXTEDIT_OPTIMIZATION
5454     popup->setItemEnabled(d->id[IdCopy], d->optimMode ? optimHasSelection() : doc->hasSelection(Q3TextDocument::Standard, true));
5455 #else
5456     popup->setItemEnabled(d->id[IdCopy], doc->hasSelection(Q3TextDocument::Standard, true));
5457 #endif
5458     popup->setItemEnabled(d->id[IdPaste], !isReadOnly() && !QApplication::clipboard()->text(d->clipboard_mode).isEmpty());
5459 #endif
5460     const bool isEmptyDocument = (length() == 0);
5461     popup->setItemEnabled(d->id[IdClear], !isReadOnly() && !isEmptyDocument);
5462     popup->setItemEnabled(d->id[IdSelectAll], !isEmptyDocument);
5463     return popup;
5464 #else
5465     return 0;
5466 #endif
5467 }
5468 
5469 /*! \overload
5470     This function is called to create a right mouse button popup menu.
5471     If you want to create a custom popup menu, reimplement this function
5472     and return the created popup menu. Ownership of the popup menu is
5473     transferred to the caller.
5474 
5475     This function is only called if createPopupMenu(const QPoint &)
5476     returns 0.
5477 */
5478 
5479 Q3PopupMenu *Q3TextEdit::createPopupMenu()
5480 {
5481     return 0;
5482 }
5483 
5484 /*!
5485     \fn Q3TextEdit::zoomIn()
5486 
5487     \overload
5488 
5489     Zooms in on the text by making the base font size one point
5490     larger and recalculating all font sizes to be the new size. This
5491     does not change the size of any images.
5492 
5493     \sa zoomOut()
5494 */
5495 
5496 /*!
5497     \fn Q3TextEdit::zoomOut()
5498 
5499     \overload
5500 
5501     Zooms out on the text by making the base font size one point
5502     smaller and recalculating all font sizes to be the new size. This
5503     does not change the size of any images.
5504 
5505     \sa zoomIn()
5506 */
5507 
5508 
5509 /*!
5510     Zooms in on the text by making the base font size \a range
5511     points larger and recalculating all font sizes to be the new size.
5512     This does not change the size of any images.
5513 
5514     \sa zoomOut()
5515 */
5516 
5517 void Q3TextEdit::zoomIn(int range)
5518 {
5519     QFont f(Q3ScrollView::font());
5520     f.setPointSize(f.pointSize() + range);
5521     setFont(f);
5522 }
5523 
5524 /*!
5525     Zooms out on the text by making the base font size \a range points
5526     smaller and recalculating all font sizes to be the new size. This
5527     does not change the size of any images.
5528 
5529     \sa zoomIn()
5530 */
5531 
5532 void Q3TextEdit::zoomOut(int range)
5533 {
5534     QFont f(Q3ScrollView::font());
5535     f.setPointSize(qMax(1, f.pointSize() - range));
5536     setFont(f);
5537 }
5538 
5539 /*!
5540     Zooms the text by making the base font size \a size points and
5541     recalculating all font sizes to be the new size. This does not
5542     change the size of any images.
5543 */
5544 
5545 void Q3TextEdit::zoomTo(int size)
5546 {
5547     QFont f(Q3ScrollView::font());
5548     f.setPointSize(size);
5549     setFont(f);
5550 }
5551 
5552 /*!
5553    Q3TextEdit is optimized for large amounts text. One of its
5554    optimizations is to format only the visible text, formatting the rest
5555    on demand, e.g. as the user scrolls, so you don't usually need to
5556    call this function.
5557 
5558     In some situations you may want to force the whole text
5559     to be formatted. For example, if after calling setText(), you wanted
5560     to know the height of the document (using contentsHeight()), you
5561     would call this function first.
5562 */
5563 
5564 void Q3TextEdit::sync()
5565 {
5566 #ifdef QT_TEXTEDIT_OPTIMIZATION
5567     if (d->optimMode) {
5568         QFontMetrics fm(Q3ScrollView::font());
5569         resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
5570     } else
5571 #endif
5572     {
5573         while (lastFormatted) {
5574             lastFormatted->format();
5575             lastFormatted = lastFormatted->next();
5576         }
5577         resizeContents(contentsWidth(), doc->height());
5578     }
5579     updateScrollBars();
5580 }
5581 
5582 /*!
5583     Sets the background color of selection number \a selNum to \a back
5584     and specifies whether the text of this selection should be
5585     inverted with \a invertText.
5586 
5587     This only works for \a selNum > 0. The default selection (\a
5588     selNum == 0) gets its attributes from the text edit's
5589     palette().
5590 */
5591 
5592 void Q3TextEdit::setSelectionAttributes(int selNum, const QColor &back, bool invertText)
5593 {
5594     if (selNum < 1)
5595         return;
5596     if (selNum > doc->numSelections())
5597         doc->addSelection(selNum);
5598     doc->setSelectionColor(selNum, back);
5599     if (invertText)
5600         doc->setSelectionTextColor(selNum, palette().color(QPalette::HighlightedText));
5601 }
5602 
5603 /*!
5604     \reimp
5605 */
5606 void Q3TextEdit::changeEvent(QEvent *ev)
5607 {
5608     if(ev->type() == QEvent::ActivationChange) {
5609         if (!isActiveWindow() && scrollTimer)
5610             scrollTimer->stop();
5611         if (!palette().isEqual(QPalette::Active, QPalette::Inactive))
5612             updateContents();
5613     }
5614 
5615 #ifdef QT_TEXTEDIT_OPTIMIZATION
5616     if (d->optimMode && (ev->type() == QEvent::ApplicationFontChange
5617                          || ev->type() == QEvent::FontChange)) {
5618         QFont f = font();
5619         if (f.kerning())
5620             f.setKerning(false);
5621 
5622         setFont(f);
5623 
5624         Q3ScrollView::setFont(f);
5625         doc->setDefaultFormat(f, doc->formatCollection()->defaultFormat()->color());
5626         // recalculate the max string width
5627         QFontMetrics fm(f);
5628         int i, sw;
5629         d->od->maxLineWidth = 0;
5630         for (i = 0; i < d->od->numLines; i++) {
5631             sw = fm.width(d->od->lines[LOGOFFSET(i)]);
5632             if (d->od->maxLineWidth < sw)
5633                 d->od->maxLineWidth = sw;
5634         }
5635         resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
5636         return;
5637     }
5638 #endif
5639 
5640     Q3ScrollView::changeEvent(ev);
5641 
5642     if (textFormat() == Qt::PlainText) {
5643         if (ev->type() == QEvent::ApplicationPaletteChange || ev->type() == QEvent::PaletteChange
5644             || ev->type() == QEvent::EnabledChange) {
5645             Q3TextFormat *f = doc->formatCollection()->defaultFormat();
5646             f->setColor(palette().text().color());
5647             updateContents();
5648         }
5649     }
5650 
5651     if (ev->type() == QEvent::ApplicationFontChange || ev->type() == QEvent::FontChange) {
5652         QFont f = font();
5653         if (f.kerning())
5654             f.setKerning(false);
5655         doc->setMinimumWidth(-1);
5656         doc->setDefaultFormat(f, doc->formatCollection()->defaultFormat()->color());
5657         lastFormatted = doc->firstParagraph();
5658         formatMore();
5659         repaintChanged();
5660     }
5661 }
5662 
5663 void Q3TextEdit::setReadOnly(bool b)
5664 {
5665     if (readonly == b)
5666         return;
5667     readonly = b;
5668     d->cursorBlinkActive = !b;
5669 #ifndef QT_NO_CURSOR
5670     if (readonly)
5671         viewport()->setCursor(Qt::ArrowCursor);
5672     else
5673         viewport()->setCursor(Qt::IBeamCursor);
5674     setInputMethodEnabled(!readonly);
5675 #endif
5676 #ifdef QT_TEXTEDIT_OPTIMIZATION
5677     checkOptimMode();
5678 #endif
5679 }
5680 
5681 /*!
5682     Scrolls to the bottom of the document and does formatting if
5683     required.
5684 */
5685 
5686 void Q3TextEdit::scrollToBottom()
5687 {
5688     sync();
5689     setContentsPos(contentsX(), contentsHeight() - visibleHeight());
5690 }
5691 
5692 /*!
5693     Returns the rectangle of the paragraph \a para in contents
5694     coordinates, or an invalid rectangle if \a para is out of range.
5695 */
5696 
5697 QRect Q3TextEdit::paragraphRect(int para) const
5698 {
5699     Q3TextEdit *that = (Q3TextEdit *)this;
5700     that->sync();
5701     Q3TextParagraph *p = doc->paragAt(para);
5702     if (!p)
5703         return QRect(-1, -1, -1, -1);
5704     return p->rect();
5705 }
5706 
5707 /*!
5708     Returns the paragraph which is at position \a pos (in contents
5709     coordinates).
5710 */
5711 
5712 int Q3TextEdit::paragraphAt(const QPoint &pos) const
5713 {
5714 #ifdef QT_TEXTEDIT_OPTIMIZATION
5715     if (d->optimMode) {
5716         QFontMetrics fm(Q3ScrollView::font());
5717         int parag = pos.y() / fm.lineSpacing();
5718         if (parag <= d->od->numLines)
5719             return parag;
5720         else
5721             return 0;
5722     }
5723 #endif
5724     Q3TextCursor c(doc);
5725     c.place(pos, doc->firstParagraph());
5726     if (c.paragraph())
5727         return c.paragraph()->paragId();
5728     return -1; // should never happen..
5729 }
5730 
5731 /*!
5732     Returns the index of the character (relative to its paragraph) at
5733     position \a pos (in contents coordinates). If \a para is not 0,
5734     \c{*}\a{para} is set to the character's paragraph.
5735 */
5736 
5737 int Q3TextEdit::charAt(const QPoint &pos, int *para) const
5738 {
5739 #ifdef QT_TEXTEDIT_OPTIMIZATION
5740     if (d->optimMode) {
5741         int par = paragraphAt(pos);
5742         if (para)
5743             *para = par;
5744         return optimCharIndex(d->od->lines[LOGOFFSET(par)], pos.x());
5745     }
5746 #endif
5747     Q3TextCursor c(doc);
5748     c.place(pos, doc->firstParagraph());
5749     if (c.paragraph()) {
5750         if (para)
5751             *para = c.paragraph()->paragId();
5752         return c.index();
5753     }
5754     return -1; // should never happen..
5755 }
5756 
5757 /*!
5758     Sets the background color of the paragraph \a para to \a bg.
5759 */
5760 
5761 void Q3TextEdit::setParagraphBackgroundColor(int para, const QColor &bg)
5762 {
5763     Q3TextParagraph *p = doc->paragAt(para);
5764     if (!p)
5765         return;
5766     p->setBackgroundColor(bg);
5767     repaintChanged();
5768 }
5769 
5770 /*!
5771     Clears the background color of the paragraph \a para, so that the
5772     default color is used again.
5773 */
5774 
5775 void Q3TextEdit::clearParagraphBackground(int para)
5776 {
5777     Q3TextParagraph *p = doc->paragAt(para);
5778     if (!p)
5779         return;
5780     p->clearBackgroundColor();
5781     repaintChanged();
5782 }
5783 
5784 /*!
5785     Returns the background color of the paragraph \a para or an
5786     invalid color if \a para is out of range or the paragraph has no
5787     background set
5788 */
5789 
5790 QColor Q3TextEdit::paragraphBackgroundColor(int para) const
5791 {
5792     Q3TextParagraph *p = doc->paragAt(para);
5793     if (!p)
5794         return QColor();
5795     QColor *c = p->backgroundColor();
5796     if (c)
5797         return *c;
5798     return QColor();
5799 }
5800 
5801 /*!
5802     \property Q3TextEdit::undoRedoEnabled
5803     \brief whether undo/redo is enabled
5804 
5805     When changing this property, the undo/redo history is cleared.
5806 
5807     The default is true.
5808 */
5809 
5810 void Q3TextEdit::setUndoRedoEnabled(bool b)
5811 {
5812     undoRedoInfo.clear();
5813     doc->commands()->clear();
5814 
5815     undoEnabled = b;
5816 }
5817 
5818 bool Q3TextEdit::isUndoRedoEnabled() const
5819 {
5820     return undoEnabled;
5821 }
5822 
5823 /*!
5824     Returns true if undo is available; otherwise returns false.
5825 */
5826 
5827 bool Q3TextEdit::isUndoAvailable() const
5828 {
5829     return undoEnabled && (doc->commands()->isUndoAvailable() || undoRedoInfo.valid());
5830 }
5831 
5832 /*!
5833     Returns true if redo is available; otherwise returns false.
5834 */
5835 
5836 bool Q3TextEdit::isRedoAvailable() const
5837 {
5838     return undoEnabled && doc->commands()->isRedoAvailable();
5839 }
5840 
5841 void Q3TextEdit::ensureFormatted(Q3TextParagraph *p)
5842 {
5843     while (!p->isValid()) {
5844         if (!lastFormatted)
5845             return;
5846         formatMore();
5847     }
5848 }
5849 
5850 /*! \internal */
5851 void Q3TextEdit::updateCursor(const QPoint & pos)
5852 {
5853     if (isReadOnly() && linksEnabled()) {
5854         Q3TextCursor c = *cursor;
5855         placeCursor(pos, &c, true);
5856 
5857 #ifndef QT_NO_NETWORKPROTOCOL
5858         bool insideParagRect = true;
5859         if (c.paragraph() == doc->lastParagraph()
5860             && c.paragraph()->rect().y() + c.paragraph()->rect().height() < pos.y())
5861             insideParagRect = false;
5862         if (insideParagRect && c.paragraph() && c.paragraph()->at(c.index()) &&
5863             c.paragraph()->at(c.index())->isAnchor()) {
5864             if (!c.paragraph()->at(c.index())->anchorHref().isEmpty()
5865                     && c.index() < c.paragraph()->length() - 1)
5866                 onLink = c.paragraph()->at(c.index())->anchorHref();
5867             else
5868                 onLink.clear();
5869 
5870             if (!c.paragraph()->at(c.index())->anchorName().isEmpty()
5871                     && c.index() < c.paragraph()->length() - 1)
5872                 d->onName = c.paragraph()->at(c.index())->anchorName();
5873             else
5874                 d->onName.clear();
5875 
5876             if (!c.paragraph()->at(c.index())->anchorHref().isEmpty()) {
5877 #ifndef QT_NO_CURSOR
5878                 viewport()->setCursor(onLink.isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor);
5879 #endif
5880                 QUrl u = QUrl(doc->context()).resolved(onLink);
5881                 emitHighlighted(u.toString(QUrl::None));
5882             }
5883         } else {
5884 #ifndef QT_NO_CURSOR
5885             viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor);
5886 #endif
5887             onLink.clear();
5888             emitHighlighted(QString());
5889         }
5890 #endif
5891     }
5892 }
5893 
5894 /*!
5895   Places the cursor \a c at the character which is closest to position
5896   \a pos (in contents coordinates). If \a c is 0, the default text
5897   cursor is used.
5898 
5899   \sa setCursorPosition()
5900 */
5901 void Q3TextEdit::placeCursor(const QPoint &pos, Q3TextCursor *c)
5902 {
5903     placeCursor(pos, c, false);
5904 }
5905 
5906 /*! \internal */
5907 void Q3TextEdit::clipboardChanged()
5908 {
5909 #ifndef QT_NO_CLIPBOARD
5910     // don't listen to selection changes
5911     disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
5912 #endif
5913     selectAll(false);
5914 }
5915 
5916 /*! \property Q3TextEdit::tabChangesFocus
5917   \brief whether TAB changes focus or is accepted as input
5918 
5919   In some occasions text edits should not allow the user to input
5920   tabulators or change indentation using the TAB key, as this breaks
5921   the focus chain. The default is false.
5922 
5923 */
5924 
5925 void Q3TextEdit::setTabChangesFocus(bool b)
5926 {
5927     d->tabChangesFocus = b;
5928 }
5929 
5930 bool Q3TextEdit::tabChangesFocus() const
5931 {
5932     return d->tabChangesFocus;
5933 }
5934 
5935 #ifdef QT_TEXTEDIT_OPTIMIZATION
5936 /* Implementation of optimized Qt::LogText mode follows */
5937 
5938 static void qSwap(int * a, int * b)
5939 {
5940     if (!a || !b)
5941         return;
5942     int tmp = *a;
5943     *a = *b;
5944     *b = tmp;
5945 }
5946 
5947 /*! \internal */
5948 bool Q3TextEdit::checkOptimMode()
5949 {
5950     bool oldMode = d->optimMode;
5951     if (textFormat() == Qt::LogText) {
5952         d->optimMode = true;
5953         setReadOnly(true);
5954     } else {
5955         d->optimMode = false;
5956     }
5957 
5958     // when changing mode - try to keep selections and text
5959     if (oldMode != d->optimMode) {
5960         if (d->optimMode) {
5961             d->od = new Q3TextEditOptimPrivate;
5962             connect(scrollTimer, SIGNAL(timeout()), this, SLOT(optimDoAutoScroll()));
5963             disconnect(doc, SIGNAL(minimumWidthChanged(int)), this, SLOT(documentWidthChanged(int)));
5964             disconnect(scrollTimer, SIGNAL(timeout()), this, SLOT(autoScrollTimerDone()));
5965             disconnect(formatTimer, SIGNAL(timeout()), this, SLOT(formatMore()));
5966             optimSetText(doc->originalText());
5967             doc->clear(true);
5968             delete cursor;
5969             cursor = new Q3TextCursor(doc);
5970         } else {
5971             disconnect(scrollTimer, SIGNAL(timeout()), this, SLOT(optimDoAutoScroll()));
5972             connect(doc, SIGNAL(minimumWidthChanged(int)), this, SLOT(documentWidthChanged(int)));
5973             connect(scrollTimer, SIGNAL(timeout()), this, SLOT(autoScrollTimerDone()));
5974             connect(formatTimer, SIGNAL(timeout()), this, SLOT(formatMore()));
5975             setText(optimText());
5976             delete d->od;
5977             d->od = 0;
5978         }
5979     }
5980     return d->optimMode;
5981 }
5982 
5983 /*! \internal */
5984 QString Q3TextEdit::optimText() const
5985 {
5986     QString str, tmp;
5987 
5988     if (d->od->len == 0)
5989         return str;
5990 
5991     // concatenate all strings
5992     int i;
5993     int offset;
5994     QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it;
5995     Q3TextEditOptimPrivate::Tag * ftag = 0;
5996     for (i = 0; i < d->od->numLines; i++) {
5997         if (d->od->lines[LOGOFFSET(i)].isEmpty()) { // CR lines are empty
5998             str += QLatin1Char('\n');
5999         } else {
6000             tmp = d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n');
6001             // inject the tags for this line
6002             if ((it = d->od->tagIndex.constFind(LOGOFFSET(i))) != d->od->tagIndex.constEnd())
6003                 ftag = it.value();
6004             offset = 0;
6005             while (ftag && ftag->line == i) {
6006                 tmp.insert(ftag->index + offset, QLatin1Char('<') + ftag->tag + QLatin1Char('>'));
6007                 offset += ftag->tag.length() + 2; // 2 -> the '<' and '>' chars
6008                 ftag = ftag->next;
6009             }
6010             str += tmp;
6011         }
6012     }
6013     return str;
6014 }
6015 
6016 /*! \internal */
6017 void Q3TextEdit::optimSetText(const QString &str)
6018 {
6019     optimRemoveSelection();
6020 // this is just too slow - but may have to go in due to compatibility reasons
6021 //     if (str == optimText())
6022 //         return;
6023     d->od->numLines = 0;
6024     d->od->lines.clear();
6025     d->od->maxLineWidth = 0;
6026     d->od->len = 0;
6027     d->od->clearTags();
6028     QFontMetrics fm(Q3ScrollView::font());
6029     if (!(str.isEmpty() || str.isNull() || d->maxLogLines == 0)) {
6030         QStringList strl = str.split(QLatin1Char('\n'));
6031         int lWidth = 0;
6032         for (QStringList::Iterator it = strl.begin(); it != strl.end(); ++it) {
6033             optimParseTags(&*it);
6034             optimCheckLimit(*it);
6035             lWidth = fm.width(*it);
6036             if (lWidth > d->od->maxLineWidth)
6037                 d->od->maxLineWidth = lWidth;
6038         }
6039     }
6040     resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
6041     repaintContents();
6042     emit textChanged();
6043 }
6044 
6045 /*! \internal
6046 
6047   Append \a tag to the tag list.
6048 */
6049 Q3TextEditOptimPrivate::Tag * Q3TextEdit::optimAppendTag(int index, const QString & tag)
6050 {
6051     Q3TextEditOptimPrivate::Tag * t = new Q3TextEditOptimPrivate::Tag, * tmp;
6052 
6053     if (d->od->tags == 0)
6054         d->od->tags = t;
6055     t->bold = t->italic = t->underline = false;
6056     t->line  = d->od->numLines;
6057     t->index = index;
6058     t->tag   = tag;
6059     t->leftTag = 0;
6060     t->parent  = 0;
6061     t->prev = d->od->lastTag;
6062     if (d->od->lastTag)
6063         d->od->lastTag->next = t;
6064     t->next = 0;
6065     d->od->lastTag = t;
6066     tmp = d->od->tagIndex[LOGOFFSET(t->line)];
6067     if (!tmp || (tmp && tmp->index > t->index)) {
6068         d->od->tagIndex.insert(LOGOFFSET(t->line), t);
6069     }
6070     return t;
6071 }
6072 
6073 /*! \internal
6074 
6075   Insert \a tag in the tag - according to line and index numbers
6076 */
6077 Q3TextEditOptimPrivate::Tag *Q3TextEdit::optimInsertTag(int line, int index, const QString &tag)
6078 {
6079     Q3TextEditOptimPrivate::Tag *t = new Q3TextEditOptimPrivate::Tag, *tmp;
6080 
6081     if (d->od->tags == 0)
6082         d->od->tags = t;
6083     t->bold = t->italic = t->underline = false;
6084     t->line  = line;
6085     t->index = index;
6086     t->tag   = tag;
6087     t->leftTag = 0;
6088     t->parent  = 0;
6089     t->next = 0;
6090     t->prev = 0;
6091 
6092     // find insertion pt. in tag struct.
6093     QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it;
6094     if ((it = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) {
6095         tmp = *it;
6096         if (tmp->index >= index) { // the existing tag may be placed AFTER the one we want to insert
6097             tmp = tmp->prev;
6098         } else {
6099             while (tmp && tmp->next && tmp->next->line == line && tmp->next->index <= index)
6100                 tmp = tmp->next;
6101         }
6102     } else {
6103         tmp = d->od->tags;
6104         while (tmp && tmp->next && tmp->next->line < line)
6105             tmp = tmp->next;
6106         if (tmp == d->od->tags)
6107             tmp = 0;
6108     }
6109 
6110     t->prev = tmp;
6111     t->next = tmp ? tmp->next : 0;
6112     if (t->next)
6113         t->next->prev = t;
6114     if (tmp)
6115         tmp->next = t;
6116 
6117     tmp = d->od->tagIndex[LOGOFFSET(t->line)];
6118     if (!tmp || (tmp && tmp->index >= t->index)) {
6119         d->od->tagIndex.insert(LOGOFFSET(t->line), t);
6120     }
6121     return t;
6122 }
6123 
6124 /*! \internal
6125 
6126   Find tags in \a line, remove them from \a line and put them in a
6127   structure.
6128 
6129   A tag is delimited by '<' and '>'. The characters '<', '>' and '&'
6130   are escaped by using '&lt;', '&gt;' and '&amp;'. Left-tags marks
6131   the starting point for formatting, while right-tags mark the ending
6132   point. A right-tag is the same as a left-tag, but with a '/'
6133   appearing before the tag keyword.  E.g a valid left-tag: <b>, and
6134   a valid right-tag: </b>.  Tags can be nested, but they have to be
6135   closed in the same order as they are opened. E.g:
6136   <font color=red><font color=blue>blue</font>red</font> - is valid, while:
6137   <font color=red><b>bold red</font> just bold</b> - is invalid since the font tag is
6138   closed before the bold tag. Note that a tag does not have to be
6139   closed: '<font color=blue>Lots of text - and then some..'  is perfectly valid for
6140   setting all text appearing after the tag to blue.  A tag can be used
6141   to change the color of a piece of text, or set one of the following
6142   formatting attributes: bold, italic and underline.  These attributes
6143   are set using the <b>, <i> and <u> tags.  Example of valid tags:
6144   <font color=red>, </font>, <b>, <u>, <i>, </i>.
6145   Example of valid text:
6146   This is some <font color=red>red text</font>, while this is some <font color=green>green
6147   text</font>. <font color=blue><font color=yellow>This is yellow</font>, while this is
6148   blue.</font>
6149 
6150   Note that only the color attribute of the HTML font tag is supported.
6151 
6152   Limitations:
6153   1. A tag cannot span several lines.
6154   2. Very limited error checking - mismatching left/right-tags is the
6155   only thing that is detected.
6156 
6157 */
6158 void Q3TextEdit::optimParseTags(QString * line, int lineNo, int indexOffset)
6159 {
6160     int len = line->length();
6161     int i, startIndex = -1, endIndex = -1, escIndex = -1;
6162     int state = 0; // 0 = outside tag, 1 = inside tag
6163     bool tagOpen, tagClose;
6164     int bold = 0, italic = 0, underline = 0;
6165     QString tagStr;
6166     QStack<Q3TextEditOptimPrivate::Tag *> tagStack;
6167 
6168     for (i = 0; i < len; i++) {
6169         tagOpen = (*line)[i] == QLatin1Char('<');
6170         tagClose = (*line)[i] == QLatin1Char('>');
6171 
6172         // handle '&lt;' and '&gt;' and '&amp;'
6173         if ((*line)[i] == QLatin1Char('&')) {
6174             escIndex = i;
6175             continue;
6176         } else if (escIndex != -1 && (*line)[i] == QLatin1Char(';')) {
6177             QString esc = line->mid(escIndex, i - escIndex + 1);
6178             QString c;
6179             if (esc == QLatin1String("&lt;"))
6180                 c = QLatin1Char('<');
6181             else if (esc == QLatin1String("&gt;"))
6182                 c = QLatin1Char('>');
6183             else if (esc == QLatin1String("&amp;"))
6184                 c = QLatin1Char('&');
6185             line->replace(escIndex, i - escIndex + 1, c);
6186             len = line->length();
6187             i -= i-escIndex;
6188             escIndex = -1;
6189             continue;
6190         }
6191 
6192         if (state == 0 && tagOpen) {
6193             state = 1;
6194             startIndex = i;
6195             continue;
6196         }
6197         if (state == 1 && tagClose) {
6198             state = 0;
6199             endIndex = i;
6200             if (!tagStr.isEmpty()) {
6201                 Q3TextEditOptimPrivate::Tag * tag, * cur, * tmp;
6202                 bool format = true;
6203 
6204                 if (tagStr == QLatin1String("b"))
6205                     bold++;
6206                 else if (tagStr == QLatin1String("/b"))
6207                     bold--;
6208                 else if (tagStr == QLatin1String("i"))
6209                     italic++;
6210                 else if (tagStr == QLatin1String("/i"))
6211                     italic--;
6212                 else if (tagStr == QLatin1String("u"))
6213                     underline++;
6214                 else if (tagStr == QLatin1String("/u"))
6215                     underline--;
6216                 else
6217                     format = false;
6218                 if (lineNo > -1)
6219                     tag = optimInsertTag(lineNo, startIndex + indexOffset, tagStr);
6220                 else
6221                     tag = optimAppendTag(startIndex, tagStr);
6222                 // everything that is not a b, u or i tag is considered
6223                 // to be a color tag.
6224                 tag->type = format ? Q3TextEditOptimPrivate::Format
6225                             : Q3TextEditOptimPrivate::Color;
6226                 if (tagStr[0] == QLatin1Char('/')) {
6227                     // this is a right-tag - search for the left-tag
6228                     // and possible parent tag
6229                     cur = tag->prev;
6230                     if (!cur) {
6231                         qWarning("Q3TextEdit::optimParseTags: no left-tag for '<%s>' in line %d.",
6232                                   tag->tag.latin1(), tag->line + 1);
6233                         return; // something is wrong - give up
6234                     }
6235                     while (cur) {
6236                         if (cur->leftTag) { // push right-tags encountered
6237                             tagStack.push(cur);
6238                         } else {
6239                             tmp = tagStack.isEmpty() ? 0 : tagStack.pop();
6240                             if (!tmp) {
6241                                 if ((QString(QLatin1Char('/') + cur->tag) == tag->tag) ||
6242                                      (tag->tag == QLatin1String("/font") && cur->tag.left(4) == QLatin1String("font"))) {
6243                                     // set up the left and parent of this tag
6244                                     tag->leftTag = cur;
6245                                     tmp = cur->prev;
6246                                     if (tmp && tmp->parent) {
6247                                         tag->parent = tmp->parent;
6248                                     } else if (tmp && !tmp->leftTag) {
6249                                         tag->parent = tmp;
6250                                     }
6251                                     break;
6252                                 } else if (!cur->leftTag) {
6253                                     qWarning("Q3TextEdit::optimParseTags: mismatching %s-tag for '<%s>' in line %d.",
6254                                               qPrintable(QString(cur->tag[0] == QLatin1Char('/') ? QLatin1String("left") : QLatin1String("right"))),
6255                                               cur->tag.latin1(), cur->line + 1);
6256                                     return; // something is amiss - give up
6257                                 }
6258                             }
6259                         }
6260                         cur = cur->prev;
6261                     }
6262                 } else {
6263                     tag->bold = bold > 0;
6264                     tag->italic = italic > 0;
6265                     tag->underline = underline > 0;
6266                     tmp = tag->prev;
6267                     while (tmp && tmp->leftTag) {
6268                         tmp = tmp->leftTag->parent;
6269                     }
6270                     if (tmp) {
6271                         tag->bold |= tmp->bold;
6272                         tag->italic |= tmp->italic;
6273                         tag->underline |= tmp->underline;
6274                     }
6275                 }
6276             }
6277             if (startIndex != -1) {
6278                 int l = (endIndex == -1) ?
6279                         line->length() - startIndex : endIndex - startIndex;
6280                 line->remove(startIndex, l+1);
6281                 len = line->length();
6282                 i -= l+1;
6283             }
6284             tagStr = QLatin1String("");
6285             continue;
6286         }
6287 
6288         if (state == 1) {
6289             tagStr += (*line)[i];
6290         }
6291     }
6292 }
6293 
6294 // calculate the width of a string in pixels inc. tabs
6295 static int qStrWidth(const QString& str, int tabWidth, const QFontMetrics& fm)
6296 {
6297     int tabs = str.count(QLatin1Char('\t'));
6298 
6299     if (!tabs)
6300         return fm.width(str);
6301 
6302     int newIdx = 0;
6303     int lastIdx = 0;
6304     int strWidth = 0;
6305     int tn;
6306     for (tn = 1; tn <= tabs; ++tn) {
6307         newIdx = str.indexOf(QLatin1Char('\t'), newIdx);
6308         strWidth += fm.width(str.mid(lastIdx, newIdx - lastIdx));
6309         if (strWidth >= tn * tabWidth) {
6310             int u = tn;
6311             while (strWidth >= u * tabWidth)
6312                 ++u;
6313             strWidth = u * tabWidth;
6314         } else {
6315             strWidth = tn * tabWidth;
6316         }
6317         lastIdx = ++newIdx;
6318     }
6319     if ((int)str.length() > newIdx)
6320         strWidth += fm.width(str.mid(newIdx));
6321     return strWidth;
6322 }
6323 
6324 bool Q3TextEdit::optimHasBoldMetrics(int line)
6325 {
6326     Q3TextEditOptimPrivate::Tag *t;
6327     QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it;
6328     if ((it = d->od->tagIndex.constFind(line)) != d->od->tagIndex.constEnd()) {
6329         t = *it;
6330         while (t && t->line == line) {
6331             if (t->bold)
6332                 return true;
6333             t = t->next;
6334         }
6335     } else if ((t = optimPreviousLeftTag(line)) && t->bold) {
6336         return true;
6337     }
6338     return false;
6339 }
6340 
6341 /*! \internal
6342 
6343   Append \a str to the current text buffer. Parses each line to find
6344   formatting tags.
6345 */
6346 void Q3TextEdit::optimAppend(const QString &str)
6347 {
6348     if (str.isEmpty() || str.isNull() || d->maxLogLines == 0)
6349         return;
6350 
6351     QStringList strl = str.split(QLatin1Char('\n'));
6352     QStringList::Iterator it = strl.begin();
6353 
6354     QFontMetrics fm(Q3ScrollView::font());
6355     int lWidth = 0;
6356     for (; it != strl.end(); ++it) {
6357         optimParseTags(&*it);
6358         optimCheckLimit(*it);
6359         if (optimHasBoldMetrics(d->od->numLines-1)) {
6360             QFont fnt = Q3ScrollView::font();
6361             fnt.setBold(true);
6362             fm = QFontMetrics(fnt);
6363         }
6364         lWidth = qStrWidth(*it, tabStopWidth(), fm) + 4;
6365         if (lWidth > d->od->maxLineWidth)
6366             d->od->maxLineWidth = lWidth;
6367     }
6368     bool scrollToEnd = contentsY() >= contentsHeight() - visibleHeight();
6369     resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
6370     if (scrollToEnd) {
6371         updateScrollBars();
6372         ensureVisible(contentsX(), contentsHeight(), 0, 0);
6373     }
6374     // when a max log size is set, the text may not be redrawn because
6375     // the size of the viewport may not have changed
6376     if (d->maxLogLines > -1)
6377         viewport()->update();
6378     emit textChanged();
6379 }
6380 
6381 static void qStripTags(QString *line)
6382 {
6383     int len = line->length();
6384     int i, startIndex = -1, endIndex = -1, escIndex = -1;
6385     int state = 0; // 0 = outside tag, 1 = inside tag
6386     bool tagOpen, tagClose;
6387 
6388     for (i = 0; i < len; i++) {
6389         tagOpen = (*line)[i] == QLatin1Char('<');
6390         tagClose = (*line)[i] == QLatin1Char('>');
6391 
6392         // handle '&lt;' and '&gt;' and '&amp;'
6393         if ((*line)[i] == QLatin1Char('&')) {
6394             escIndex = i;
6395             continue;
6396         } else if (escIndex != -1 && (*line)[i] == QLatin1Char(';')) {
6397             QString esc = line->mid(escIndex, i - escIndex + 1);
6398             QString c;
6399             if (esc == QLatin1String("&lt;"))
6400                 c = QLatin1Char('<');
6401             else if (esc == QLatin1String("&gt;"))
6402                 c = QLatin1Char('>');
6403             else if (esc == QLatin1String("&amp;"))
6404                 c = QLatin1Char('&');
6405             line->replace(escIndex, i - escIndex + 1, c);
6406             len = line->length();
6407             i -= i-escIndex;
6408             escIndex = -1;
6409             continue;
6410         }
6411 
6412         if (state == 0 && tagOpen) {
6413             state = 1;
6414             startIndex = i;
6415             continue;
6416         }
6417         if (state == 1 && tagClose) {
6418             state = 0;
6419             endIndex = i;
6420             if (startIndex != -1) {
6421                 int l = (endIndex == -1) ?
6422                         line->length() - startIndex : endIndex - startIndex;
6423                 line->remove(startIndex, l+1);
6424                 len = line->length();
6425                 i -= l+1;
6426             }
6427             continue;
6428         }
6429     }
6430 }
6431 
6432 /*! \internal
6433 
6434     Inserts the text into \a line at index \a index.
6435 */
6436 
6437 void Q3TextEdit::optimInsert(const QString& text, int line, int index)
6438 {
6439     if (text.isEmpty() || d->maxLogLines == 0)
6440         return;
6441     if (line < 0)
6442         line = 0;
6443     if (line > d->od->numLines-1)
6444         line = d->od->numLines-1;
6445     if (index < 0)
6446         index = 0;
6447     if (index > d->od->lines[line].length())
6448         index = d->od->lines[line].length();
6449 
6450     QStringList strl = text.split(QLatin1Char('\n'));
6451     int numNewLines = strl.count() - 1;
6452     Q3TextEditOptimPrivate::Tag *tag = 0;
6453     QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator ii;
6454     int x;
6455 
6456     if (numNewLines == 0) {
6457         // Case 1. Fast single line case - just inject it!
6458         QString stripped = text;
6459         qStripTags(&stripped);
6460         d->od->lines[LOGOFFSET(line)].insert(index, stripped);
6461         // move the tag indices following the insertion pt.
6462         if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) {
6463             tag = *ii;
6464             while (tag && (LOGOFFSET(tag->line) == line && tag->index < index))
6465                 tag = tag->next;
6466             while (tag && (LOGOFFSET(tag->line) == line)) {
6467                 tag->index += stripped.length();
6468                 tag = tag->next;
6469             }
6470         }
6471         stripped = text;
6472         optimParseTags(&stripped, line, index);
6473     } else if (numNewLines > 0) {
6474         // Case 2. We have at least 1 newline char - split at
6475         // insertion pt. and make room for new lines - complex and slow!
6476         QString left = d->od->lines[LOGOFFSET(line)].left(index);
6477         QString right = d->od->lines[LOGOFFSET(line)].mid(index);
6478 
6479         // rearrange lines for insertion
6480         for (x = d->od->numLines - 1; x > line; x--)
6481             d->od->lines[x + numNewLines] = d->od->lines[x];
6482         d->od->numLines += numNewLines;
6483 
6484         // fix the tag index and the tag line/index numbers - this
6485         // might take a while..
6486         for (x = line; x < d->od->numLines; x++) {
6487             if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) {
6488                 tag = ii.value();
6489                 if (LOGOFFSET(tag->line) == line)
6490                     while (tag && (LOGOFFSET(tag->line) == line && tag->index < index))
6491                         tag = tag->next;
6492             }
6493         }
6494 
6495         // relabel affected tags with new line numbers and new index
6496         // positions
6497         while (tag) {
6498             if (LOGOFFSET(tag->line) == line)
6499                 tag->index -= index;
6500             tag->line += numNewLines;
6501             tag = tag->next;
6502         }
6503 
6504         // generate a new tag index
6505         d->od->tagIndex.clear();
6506         tag = d->od->tags;
6507         while (tag) {
6508             if (!((ii = d->od->tagIndex.constFind(LOGOFFSET(tag->line))) != d->od->tagIndex.constEnd()))
6509                 d->od->tagIndex[LOGOFFSET(tag->line)] = tag;
6510             tag = tag->next;
6511         }
6512 
6513         // update the tag indices on the spliced line - needs to be done before new tags are added
6514         QString stripped = strl[strl.count() - 1];
6515         qStripTags(&stripped);
6516         if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line + numNewLines))) != d->od->tagIndex.constEnd()) {
6517             tag = *ii;
6518             while (tag && (LOGOFFSET(tag->line) == line + numNewLines)) {
6519                 tag->index += stripped.length();
6520                 tag = tag->next;
6521             }
6522         }
6523 
6524         // inject the new lines
6525         QStringList::Iterator it = strl.begin();
6526         x = line;
6527         int idx;
6528         for (; it != strl.end(); ++it) {
6529             stripped = *it;
6530             qStripTags(&stripped);
6531             if (x == line) {
6532                 stripped = left + stripped;
6533                 idx = index;
6534             } else {
6535                 idx = 0;
6536             }
6537             d->od->lines[LOGOFFSET(x)] = stripped;
6538             optimParseTags(&*it, x++, idx);
6539         }
6540         d->od->lines[LOGOFFSET(x - 1)] += right;
6541     }
6542     // recalculate the pixel width of the longest injected line -
6543     QFontMetrics fm(Q3ScrollView::font());
6544     int lWidth = 0;
6545     for (x = line; x < line + numNewLines; x++) {
6546         if (optimHasBoldMetrics(x)) {
6547             QFont fnt = Q3ScrollView::font();
6548             fnt.setBold(true);
6549             fm = QFontMetrics(fnt);
6550         }
6551         lWidth = fm.width(d->od->lines[x]) + 4;
6552         if (lWidth > d->od->maxLineWidth)
6553             d->od->maxLineWidth = lWidth;
6554     }
6555     resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
6556     repaintContents();
6557     emit textChanged();
6558 }
6559 
6560 
6561 /*! \internal
6562 
6563   Returns the first open left-tag appearing before line \a line.
6564  */
6565 Q3TextEditOptimPrivate::Tag * Q3TextEdit::optimPreviousLeftTag(int line)
6566 {
6567     Q3TextEditOptimPrivate::Tag * ftag = 0;
6568     QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it;
6569     if ((it = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd())
6570         ftag = it.value();
6571     if (!ftag) {
6572         // start searching for an open tag
6573         ftag = d->od->tags;
6574         while (ftag) {
6575             if (ftag->line > line || ftag->next == 0) {
6576                 if (ftag->line > line)
6577                     ftag = ftag->prev;
6578                 break;
6579             }
6580             ftag = ftag->next;
6581         }
6582     } else {
6583         ftag = ftag->prev;
6584     }
6585 
6586     if (ftag) {
6587         if (ftag && ftag->parent) // use the open parent tag
6588             ftag = ftag->parent;
6589         else if (ftag && ftag->leftTag) // this is a right-tag with no parent
6590             ftag = 0;
6591     }
6592     return ftag;
6593 }
6594 
6595 /*! \internal
6596 
6597   Set the format for the string starting at index \a start and ending
6598   at \a end according to \a tag. If \a tag is a Format tag, find the
6599   first open color tag appearing before \a tag and use that tag to
6600   color the string.
6601 */
6602 void Q3TextEdit::optimSetTextFormat(Q3TextDocument * td, Q3TextCursor * cur,
6603                                     Q3TextFormat * f, int start, int end,
6604                                     Q3TextEditOptimPrivate::Tag * tag)
6605 {
6606     int formatFlags = Q3TextFormat::Bold | Q3TextFormat::Italic |
6607                       Q3TextFormat::Underline;
6608     cur->setIndex(start);
6609     td->setSelectionStart(0, *cur);
6610     cur->setIndex(end);
6611     td->setSelectionEnd(0, *cur);
6612     Q3StyleSheetItem * ssItem = styleSheet()->item(tag->tag);
6613     if (!ssItem || tag->type == Q3TextEditOptimPrivate::Format) {
6614         f->setBold(tag->bold);
6615         f->setItalic(tag->italic);
6616         f->setUnderline(tag->underline);
6617         if (tag->type == Q3TextEditOptimPrivate::Format) {
6618             // check to see if there are any open color tags prior to
6619             // this format tag
6620             tag = tag->prev;
6621             while (tag && (tag->type == Q3TextEditOptimPrivate::Format ||
6622                             tag->leftTag)) {
6623                 tag = tag->leftTag ? tag->parent : tag->prev;
6624             }
6625         }
6626         if (tag) {
6627             QString col = tag->tag.simplified();
6628             if (col.startsWith(QLatin1String("font color"))) {
6629                 int i = col.indexOf(QLatin1Char('='), 10);
6630                 col = col.mid(i + 1).simplified();
6631                 if (col[0] == QLatin1Char('\"'))
6632                     col = col.mid(1, col.length() - 2);
6633             }
6634             QColor color = QColor(col);
6635             if (color.isValid()) {
6636                 formatFlags |= Q3TextFormat::Color;
6637                 f->setColor(color);
6638             }
6639         }
6640     } else { // use the stylesheet tag definition
6641         if (ssItem->color().isValid()) {
6642             formatFlags |= Q3TextFormat::Color;
6643             f->setColor(ssItem->color());
6644         }
6645         f->setBold(ssItem->fontWeight() == QFont::Bold);
6646         f->setItalic(ssItem->fontItalic());
6647         f->setUnderline(ssItem->fontUnderline());
6648     }
6649     td->setFormat(0, f, formatFlags);
6650     td->removeSelection(0);
6651 }
6652 
6653 /*! \internal */
6654 void Q3TextEdit::optimDrawContents(QPainter * p, int clipx, int clipy,
6655                                    int clipw, int cliph)
6656 {
6657     QFontMetrics fm(Q3ScrollView::font());
6658     int startLine = clipy / fm.lineSpacing();
6659 
6660     // we always have to fetch at least two lines for drawing because the
6661     // painter may be translated so that parts of two lines cover the area
6662     // of a single line
6663     int nLines = (cliph / fm.lineSpacing()) + 2;
6664     int endLine = startLine + nLines;
6665 
6666     if (startLine >= d->od->numLines)
6667         return;
6668     if ((startLine + nLines) > d->od->numLines)
6669         nLines = d->od->numLines - startLine;
6670 
6671     int i = 0;
6672     QString str;
6673     for (i = startLine; i < (startLine + nLines); i++)
6674         str.append(d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n'));
6675 
6676     Q3TextDocument * td = new Q3TextDocument(0);
6677     td->setDefaultFormat(Q3ScrollView::font(), QColor());
6678     td->setPlainText(str);
6679     td->setFormatter(new Q3TextFormatterBreakWords); // deleted by QTextDoc
6680     td->formatter()->setWrapEnabled(false);
6681     td->setTabStops(doc->tabStopWidth());
6682 
6683     // get the current text color from the current format
6684     td->selectAll(Q3TextDocument::Standard);
6685     Q3TextFormat f;
6686     f.setColor(palette().text().color());
6687     f.setFont(Q3ScrollView::font());
6688     td->setFormat(Q3TextDocument::Standard, &f,
6689                    Q3TextFormat::Color | Q3TextFormat::Font);
6690     td->removeSelection(Q3TextDocument::Standard);
6691 
6692     // add tag formatting
6693     if (d->od->tags) {
6694         int i = startLine;
6695         QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it;
6696         Q3TextEditOptimPrivate::Tag * tag = 0, * tmp = 0;
6697         Q3TextCursor cur(td);
6698         // Step 1 - find previous left-tag
6699         tmp = optimPreviousLeftTag(i);
6700         for (; i < startLine + nLines; i++) {
6701             if ((it = d->od->tagIndex.constFind(LOGOFFSET(i))) != d->od->tagIndex.constEnd())
6702                 tag = it.value();
6703             // Step 2 - iterate over tags on the current line
6704             int lastIndex = 0;
6705             while (tag && tag->line == i) {
6706                 tmp = 0;
6707                 if (tag->prev && !tag->prev->leftTag) {
6708                     tmp = tag->prev;
6709                 } else if (tag->prev && tag->prev->parent) {
6710                     tmp = tag->prev->parent;
6711                 }
6712                 if ((tag->index - lastIndex) > 0 && tmp) {
6713                     optimSetTextFormat(td, &cur, &f, lastIndex, tag->index, tmp);
6714                 }
6715                 lastIndex = tag->index;
6716                 tmp = tag;
6717                 tag = tag->next;
6718             }
6719             // Step 3 - color last part of the line - if necessary
6720             if (tmp && tmp->parent)
6721                 tmp = tmp->parent;
6722             if ((cur.paragraph()->length()-1 - lastIndex) > 0 && tmp && !tmp->leftTag) {
6723                 optimSetTextFormat(td, &cur, &f, lastIndex,
6724                                     cur.paragraph()->length() - 1, tmp);
6725             }
6726             cur.setParagraph(cur.paragraph()->next());
6727         }
6728         // useful debug info
6729         //
6730 //         tag = d->od->tags;
6731 //         qWarning("###");
6732 //         while (tag) {
6733 //             qWarning("Tag: %p, parent: %09p, leftTag: %09p, Name: %-15s, ParentName: %s, %d%d%d", tag,
6734 //                        tag->parent, tag->leftTag, tag->tag.latin1(), tag->parent ? tag->parent->tag.latin1():"<none>",
6735 //                       tag->bold, tag->italic, tag->underline);
6736 //             tag = tag->next;
6737 //         }
6738     }
6739 
6740     // if there is a selection, make sure that the selection in the
6741     // part we need to redraw is set correctly
6742     if (optimHasSelection()) {
6743         Q3TextCursor c1(td);
6744         Q3TextCursor c2(td);
6745         int selStart = d->od->selStart.line;
6746         int idxStart = d->od->selStart.index;
6747         int selEnd = d->od->selEnd.line;
6748         int idxEnd = d->od->selEnd.index;
6749         if (selEnd < selStart) {
6750             qSwap(&selStart, &selEnd);
6751             qSwap(&idxStart, &idxEnd);
6752         }
6753         if (selEnd > d->od->numLines-1) {
6754             selEnd = d->od->numLines-1;
6755         }
6756         if (startLine <= selStart && endLine >= selEnd) {
6757             // case 1: area to paint covers entire selection
6758             int paragS = selStart - startLine;
6759             int paragE = paragS + (selEnd - selStart);
6760             Q3TextParagraph * parag = td->paragAt(paragS);
6761             if (parag) {
6762                 c1.setParagraph(parag);
6763                 if (td->text(paragS).length() >= idxStart)
6764                     c1.setIndex(idxStart);
6765             }
6766             parag = td->paragAt(paragE);
6767             if (parag) {
6768                 c2.setParagraph(parag);
6769                 if (td->text(paragE).length() >= idxEnd)
6770                     c2.setIndex(idxEnd);
6771             }
6772         } else if (startLine > selStart && endLine < selEnd) {
6773             // case 2: area to paint is all part of the selection
6774             td->selectAll(Q3TextDocument::Standard);
6775         } else if (startLine > selStart && endLine >= selEnd &&
6776                     startLine <= selEnd) {
6777             // case 3: area to paint starts inside a selection, ends past it
6778             c1.setParagraph(td->firstParagraph());
6779             c1.setIndex(0);
6780             int paragE = selEnd - startLine;
6781             Q3TextParagraph * parag = td->paragAt(paragE);
6782             if (parag) {
6783                 c2.setParagraph(parag);
6784                 if (td->text(paragE).length() >= idxEnd)
6785                     c2.setIndex(idxEnd);
6786             }
6787         } else if (startLine <= selStart && endLine < selEnd &&
6788                     endLine > selStart) {
6789             // case 4: area to paint starts before a selection, ends inside it
6790             int paragS = selStart - startLine;
6791             Q3TextParagraph * parag = td->paragAt(paragS);
6792             if (parag) {
6793                 c1.setParagraph(parag);
6794                 c1.setIndex(idxStart);
6795             }
6796             c2.setParagraph(td->lastParagraph());
6797             c2.setIndex(td->lastParagraph()->string()->toString().length() - 1);
6798 
6799         }
6800         // previously selected?
6801         if (!td->hasSelection(Q3TextDocument::Standard)) {
6802             td->setSelectionStart(Q3TextDocument::Standard, c1);
6803             td->setSelectionEnd(Q3TextDocument::Standard, c2);
6804         }
6805     }
6806     td->doLayout(p, contentsWidth());
6807 
6808     // have to align the painter so that partly visible lines are
6809     // drawn at the correct position within the area that needs to be
6810     // painted
6811     int offset = clipy % fm.lineSpacing() + 2;
6812     QRect r(clipx, 0, clipw, cliph + offset);
6813     p->translate(0, clipy - offset);
6814     td->draw(p, r.x(), r.y(), r.width(), r.height(), palette());
6815     p->translate(0, -(clipy - offset));
6816     delete td;
6817 }
6818 
6819 /*! \internal */
6820 void Q3TextEdit::optimMousePressEvent(QMouseEvent * e)
6821 {
6822     if (e->button() != Qt::LeftButton)
6823         return;
6824 
6825     QFontMetrics fm(Q3ScrollView::font());
6826     mousePressed = true;
6827     mousePos = e->pos();
6828     d->od->selStart.line = e->y() / fm.lineSpacing();
6829     if (d->od->selStart.line > d->od->numLines-1) {
6830         d->od->selStart.line = d->od->numLines-1;
6831         d->od->selStart.index = d->od->lines[LOGOFFSET(d->od->numLines-1)].length();
6832     } else {
6833         QString str = d->od->lines[LOGOFFSET(d->od->selStart.line)];
6834         d->od->selStart.index = optimCharIndex(str, mousePos.x());
6835     }
6836     d->od->selEnd.line = d->od->selStart.line;
6837     d->od->selEnd.index = d->od->selStart.index;
6838     oldMousePos = e->pos();
6839     repaintContents();
6840 }
6841 
6842 /*! \internal */
6843 void Q3TextEdit::optimMouseReleaseEvent(QMouseEvent * e)
6844 {
6845     if (e->button() != Qt::LeftButton)
6846         return;
6847 
6848     if (scrollTimer->isActive())
6849         scrollTimer->stop();
6850     if (!inDoubleClick) {
6851         QFontMetrics fm(Q3ScrollView::font());
6852         d->od->selEnd.line = e->y() / fm.lineSpacing();
6853         if (d->od->selEnd.line > d->od->numLines-1) {
6854             d->od->selEnd.line = d->od->numLines-1;
6855         }
6856         QString str = d->od->lines[LOGOFFSET(d->od->selEnd.line)];
6857         mousePos = e->pos();
6858         d->od->selEnd.index = optimCharIndex(str, mousePos.x());
6859         if (d->od->selEnd.line < d->od->selStart.line) {
6860             qSwap(&d->od->selStart.line, &d->od->selEnd.line);
6861             qSwap(&d->od->selStart.index, &d->od->selEnd.index);
6862         } else if (d->od->selStart.line == d->od->selEnd.line &&
6863                     d->od->selStart.index > d->od->selEnd.index) {
6864             qSwap(&d->od->selStart.index, &d->od->selEnd.index);
6865         }
6866         oldMousePos = e->pos();
6867         repaintContents();
6868     }
6869     if (mousePressed) {
6870         mousePressed = false;
6871         copyToClipboard();
6872     }
6873 
6874     inDoubleClick = false;
6875     emit copyAvailable(optimHasSelection());
6876     emit selectionChanged();
6877 }
6878 
6879 /*! \internal */
6880 void Q3TextEdit::optimMouseMoveEvent(QMouseEvent * e)
6881 {
6882     mousePos = e->pos();
6883     optimDoAutoScroll();
6884     oldMousePos = mousePos;
6885 }
6886 
6887 /*! \internal */
6888 void Q3TextEdit::optimDoAutoScroll()
6889 {
6890     if (!mousePressed)
6891         return;
6892 
6893     QFontMetrics fm(Q3ScrollView::font());
6894     QPoint pos(mapFromGlobal(QCursor::pos()));
6895     bool doScroll = false;
6896     int xx = contentsX() + pos.x();
6897     int yy = contentsY() + pos.y();
6898 
6899     // find out how much we have to scroll in either dir.
6900     if (pos.x() < 0 || pos.x() > viewport()->width() ||
6901          pos.y() < 0 || pos.y() > viewport()->height()) {
6902         int my = yy;
6903         if (pos.x() < 0)
6904             xx = contentsX() - fm.width(QLatin1Char('w'));
6905         else if (pos.x() > viewport()->width())
6906             xx = contentsX() + viewport()->width() + fm.width(QLatin1Char('w'));
6907 
6908         if (pos.y() < 0) {
6909             my = contentsY() - 1;
6910             yy = (my / fm.lineSpacing()) * fm.lineSpacing() + 1;
6911         } else if (pos.y() > viewport()->height()) {
6912             my = contentsY() + viewport()->height() + 1;
6913             yy = (my / fm.lineSpacing() + 1) * fm.lineSpacing() - 1;
6914         }
6915         d->od->selEnd.line = my / fm.lineSpacing();
6916         mousePos.setX(xx);
6917         mousePos.setY(my);
6918         doScroll = true;
6919     } else {
6920         d->od->selEnd.line = mousePos.y() / fm.lineSpacing();
6921     }
6922 
6923     if (d->od->selEnd.line < 0) {
6924         d->od->selEnd.line = 0;
6925     } else if (d->od->selEnd.line > d->od->numLines-1) {
6926         d->od->selEnd.line = d->od->numLines-1;
6927     }
6928 
6929     QString str = d->od->lines[LOGOFFSET(d->od->selEnd.line)];
6930     d->od->selEnd.index = optimCharIndex(str, mousePos.x());
6931 
6932     // have to have a valid index before generating a paint event
6933     if (doScroll)
6934         ensureVisible(xx, yy, 1, 1);
6935 
6936     // if the text document is smaller than the height of the viewport
6937     // - redraw the whole thing otherwise calculate the rect that
6938     // needs drawing.
6939     if (d->od->numLines * fm.lineSpacing() < viewport()->height()) {
6940         repaintContents(contentsX(), contentsY(), width(), height());
6941     } else {
6942         int h = QABS(mousePos.y() - oldMousePos.y()) + fm.lineSpacing() * 2;
6943         int y;
6944         if (oldMousePos.y() < mousePos.y()) {
6945             y = oldMousePos.y() - fm.lineSpacing();
6946         } else {
6947             // expand paint area for a fully selected line
6948             h += fm.lineSpacing();
6949             y = mousePos.y() - fm.lineSpacing()*2;
6950         }
6951         if (y < 0)
6952             y = 0;
6953         repaintContents(contentsX(), y, width(), h);
6954     }
6955 
6956     if ((!scrollTimer->isActive() && pos.y() < 0) || pos.y() > height())
6957         scrollTimer->start(100, false);
6958     else if (scrollTimer->isActive() && pos.y() >= 0 && pos.y() <= height())
6959         scrollTimer->stop();
6960 }
6961 
6962 /*! \internal
6963 
6964   Returns the index of the character in the string \a str that is
6965   currently under the mouse pointer.
6966 */
6967 int Q3TextEdit::optimCharIndex(const QString &str, int mx) const
6968 {
6969     QFontMetrics fm(Q3ScrollView::font());
6970     int i = 0;
6971     int dd, dist = 10000000;
6972     int curpos = 0;
6973     int strWidth;
6974     mx = mx - 4; // ### get the real margin from somewhere
6975 
6976     if (!str.contains(QLatin1Char('\t')) && mx > fm.width(str))
6977         return str.length();
6978 
6979     while (i < str.length()) {
6980         strWidth = qStrWidth(str.left(i), tabStopWidth(), fm);
6981         dd = strWidth - mx;
6982         if (QABS(dd) <= dist) {
6983             dist = QABS(dd);
6984             if (mx >= strWidth)
6985                 curpos = i;
6986         }
6987         ++i;
6988     }
6989     return curpos;
6990 }
6991 
6992 /*! \internal */
6993 void Q3TextEdit::optimSelectAll()
6994 {
6995     d->od->selStart.line = d->od->selStart.index = 0;
6996     d->od->selEnd.line = d->od->numLines - 1;
6997     d->od->selEnd.index = d->od->lines[LOGOFFSET(d->od->selEnd.line)].length();
6998 
6999     repaintContents();
7000     emit copyAvailable(optimHasSelection());
7001     emit selectionChanged();
7002 }
7003 
7004 /*! \internal */
7005 void Q3TextEdit::optimRemoveSelection()
7006 {
7007     d->od->selStart.line = d->od->selEnd.line = -1;
7008     d->od->selStart.index = d->od->selEnd.index = -1;
7009     repaintContents();
7010 }
7011 
7012 /*! \internal */
7013 void Q3TextEdit::optimSetSelection(int startLine, int startIdx,
7014                                        int endLine, int endIdx)
7015 {
7016     d->od->selStart.line = startLine;
7017     d->od->selEnd.line = endLine;
7018     d->od->selStart.index = startIdx;
7019     d->od->selEnd.index = endIdx;
7020 }
7021 
7022 /*! \internal */
7023 bool Q3TextEdit::optimHasSelection() const
7024 {
7025     if (d->od->selStart.line != d->od->selEnd.line ||
7026          d->od->selStart.index != d->od->selEnd.index)
7027         return true;
7028     return false;
7029 }
7030 
7031 /*! \internal */
7032 QString Q3TextEdit::optimSelectedText() const
7033 {
7034     QString str;
7035 
7036     if (!optimHasSelection())
7037         return str;
7038 
7039     // concatenate all strings
7040     if (d->od->selStart.line == d->od->selEnd.line) {
7041         str = d->od->lines[LOGOFFSET(d->od->selEnd.line)].mid(d->od->selStart.index,
7042                            d->od->selEnd.index - d->od->selStart.index);
7043     } else {
7044         int i = d->od->selStart.line;
7045         str = d->od->lines[LOGOFFSET(i)].right(d->od->lines[LOGOFFSET(i)].length() -
7046                                   d->od->selStart.index) + QLatin1Char('\n');
7047         i++;
7048         for (; i < d->od->selEnd.line; i++) {
7049             if (d->od->lines[LOGOFFSET(i)].isEmpty()) // CR lines are empty
7050                 str += QLatin1Char('\n');
7051             else
7052                 str += d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n');
7053         }
7054         str += d->od->lines[LOGOFFSET(d->od->selEnd.line)].left(d->od->selEnd.index);
7055     }
7056     return str;
7057 }
7058 
7059 /*! \internal */
7060 bool Q3TextEdit::optimFind(const QString & expr, bool cs, bool /*wo*/,
7061                                bool fw, int * para, int * index)
7062 {
7063     bool found = false;
7064     int parag = para ? *para : d->od->search.line,
7065           idx = index ? *index : d->od->search.index, i;
7066 
7067     if (d->od->len == 0)
7068         return false;
7069 
7070     for (i = parag; fw ? i < d->od->numLines : i >= 0; fw ? i++ : i--) {
7071         idx = fw
7072               ? d->od->lines[LOGOFFSET(i)].indexOf(expr, idx,
7073                                                      cs ? Qt::CaseSensitive : Qt::CaseInsensitive)
7074               : d->od->lines[LOGOFFSET(i)].lastIndexOf(expr, idx,
7075                                                          cs ? Qt::CaseSensitive : Qt::CaseInsensitive);
7076         if (idx != -1) {
7077             found = true;
7078             break;
7079         } else if (fw)
7080             idx = 0;
7081     }
7082 
7083     if (found) {
7084         if (index)
7085             *index = idx;
7086         if (para)
7087             *para = i;
7088         d->od->search.index = idx;
7089         d->od->search.line = i;
7090         optimSetSelection(i, idx, i, idx + expr.length());
7091         QFontMetrics fm(Q3ScrollView::font());
7092         int h = fm.lineSpacing();
7093         int x = fm.width(d->od->lines[LOGOFFSET(i)].left(idx + expr.length())) + 4;
7094         ensureVisible(x, i * h + h / 2, 1, h / 2 + 2);
7095         repaintContents(); // could possibly be optimized
7096     }
7097     return found;
7098 }
7099 
7100 /*! \reimp */
7101 void Q3TextEdit::polishEvent(QEvent*)
7102 {
7103     // this will ensure that the last line is visible if text have
7104     // been added to the widget before it is shown
7105     if (d->optimMode)
7106         scrollToBottom();
7107 }
7108 
7109 /*!
7110     Sets the maximum number of lines a Q3TextEdit can hold in \c
7111     Qt::LogText mode to \a limit. If \a limit is -1 (the default), this
7112     signifies an unlimited number of lines.
7113 
7114     \warning Never use formatting tags that span more than one line
7115     when the maximum log lines is set. When lines are removed from the
7116     top of the buffer it could result in an unbalanced tag pair, i.e.
7117     the left formatting tag is removed before the right one.
7118  */
7119 void Q3TextEdit::setMaxLogLines(int limit)
7120 {
7121     d->maxLogLines = limit;
7122     if (d->maxLogLines < -1)
7123         d->maxLogLines = -1;
7124     if (d->maxLogLines == -1)
7125         d->logOffset = 0;
7126 }
7127 
7128 /*!
7129     Returns the maximum number of lines Q3TextEdit can hold in \c
7130     Qt::LogText mode. By default the number of lines is unlimited, which
7131     is signified by a value of -1.
7132  */
7133 int Q3TextEdit::maxLogLines() const
7134 {
7135     return d->maxLogLines;
7136 }
7137 
7138 /*!
7139     Check if the number of lines in the buffer is limited, and uphold
7140     that limit when appending new lines.
7141  */
7142 void Q3TextEdit::optimCheckLimit(const QString& str)
7143 {
7144     if (d->maxLogLines > -1 && d->maxLogLines <= d->od->numLines) {
7145         // NB! Removing the top line in the buffer will potentially
7146         // destroy the structure holding the formatting tags - if line
7147         // spanning tags are used.
7148         Q3TextEditOptimPrivate::Tag *t = d->od->tags, *tmp, *itr;
7149         QList<Q3TextEditOptimPrivate::Tag *> lst;
7150         while (t) {
7151             t->line -= 1;
7152             // unhook the ptr from the tag structure
7153             if (((uint) LOGOFFSET(t->line) < (uint) d->logOffset &&
7154                   (uint) LOGOFFSET(t->line) < (uint) LOGOFFSET(d->od->numLines) &&
7155                   (uint) LOGOFFSET(d->od->numLines) > (uint) d->logOffset))
7156             {
7157                 if (t->prev)
7158                     t->prev->next = t->next;
7159                 if (t->next)
7160                     t->next->prev = t->prev;
7161                 if (d->od->tags == t)
7162                     d->od->tags = t->next;
7163                 if (d->od->lastTag == t) {
7164                     if (t->prev)
7165                         d->od->lastTag = t->prev;
7166                     else
7167                         d->od->lastTag = d->od->tags;
7168                 }
7169                 tmp = t;
7170                 t = t->next;
7171                 lst.append(tmp);
7172                 delete tmp;
7173             } else {
7174                 t = t->next;
7175             }
7176         }
7177         // Remove all references to the ptrs we just deleted
7178         itr = d->od->tags;
7179         while (itr) {
7180             for (int i = 0; i < lst.size(); ++i) {
7181                 tmp = lst.at(i);
7182                 if (itr->parent == tmp)
7183                     itr->parent = 0;
7184                 if (itr->leftTag == tmp)
7185                     itr->leftTag = 0;
7186             }
7187             itr = itr->next;
7188         }
7189         // ...in the tag index as well
7190         QMap<int, Q3TextEditOptimPrivate::Tag *>::Iterator idx;
7191         if ((idx = d->od->tagIndex.find(d->logOffset)) != d->od->tagIndex.end())
7192             d->od->tagIndex.erase(idx);
7193 
7194         QMap<int,QString>::Iterator it;
7195         if ((it = d->od->lines.find(d->logOffset)) != d->od->lines.end()) {
7196             d->od->len -= (*it).length();
7197             d->od->lines.erase(it);
7198             d->od->numLines--;
7199             d->logOffset = LOGOFFSET(1);
7200         }
7201     }
7202     d->od->len += str.length();
7203     d->od->lines[LOGOFFSET(d->od->numLines++)] = str;
7204 }
7205 
7206 #endif // QT_TEXTEDIT_OPTIMIZATION
7207 
7208 /*!
7209     \property Q3TextEdit::autoFormatting
7210     \brief the enabled set of auto formatting features
7211 
7212     The value can be any combination of the values in the \c
7213     AutoFormattingFlag enum.  The default is \c AutoAll. Choose \c AutoNone
7214     to disable all automatic formatting.
7215 
7216     Currently, the only automatic formatting feature provided is \c
7217     AutoBulletList; future versions of Qt may offer more.
7218 */
7219 
7220 void Q3TextEdit::setAutoFormatting(AutoFormatting features)
7221 {
7222     d->autoFormatting = features;
7223 }
7224 
7225 Q3TextEdit::AutoFormatting Q3TextEdit::autoFormatting() const
7226 {
7227     return d->autoFormatting;
7228 }
7229 
7230 /*!
7231     Returns the QSyntaxHighlighter set on this Q3TextEdit. 0 is
7232     returned if no syntax highlighter is set.
7233  */
7234 Q3SyntaxHighlighter * Q3TextEdit::syntaxHighlighter() const
7235 {
7236     if (document()->preProcessor())
7237         return ((Q3SyntaxHighlighterInternal *) document()->preProcessor())->highlighter;
7238     else
7239         return 0;
7240 }
7241 
7242 QT_END_NAMESPACE
7243 
7244 #endif //QT_NO_TEXTEDIT
7245