source: vendor/trolltech/current/src/widgets/qtextedit.cpp

Last change on this file was 2, checked in by dmik, 20 years ago

Imported xplatform parts of the official release 3.3.1 from Trolltech

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