source: trunk/src/widgets/qtextedit.cpp@ 8

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

Transferred Qt for OS/2 version 3.3.1-rc5 sources from the CVS

  • Property svn:keywords set to Id
File size: 209.3 KB
Line 
1/****************************************************************************
2** $Id: qtextedit.cpp 8 2005-11-16 19:36:46Z 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) || defined (Q_WS_PM)
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) || defined (Q_WS_PM)
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) || defined (Q_WS_PM)
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) || defined (Q_WS_PM)
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) || defined (Q_WS_PM)
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) || defined (Q_WS_PM)
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#ifndef QT_NO_TEXTBROWSER
2411 if (::qt_cast<QTextBrowser*>(this)) { // change for 4.0
2412 QConnectionList *clist = receivers(
2413 "anchorClicked(const QString&,const QString&)");
2414 if (!signalsBlocked() && clist) {
2415 QUObject o[3];
2416 static_QUType_QString.set(o+1, d->onName);
2417 static_QUType_QString.set(o+2, onLink);
2418 activate_signal( clist, o);
2419 }
2420 }
2421#endif
2422
2423 // emitting linkClicked() may result in that the cursor winds
2424 // up hovering over a different valid link - check this and
2425 // set the appropriate cursor shape
2426 updateCursor( e->pos() );
2427 }
2428#endif
2429 drawCursor( TRUE );
2430 if ( !doc->hasSelection( QTextDocument::Standard, TRUE ) )
2431 doc->removeSelection( QTextDocument::Standard );
2432
2433 emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) );
2434 emit selectionChanged();
2435}
2436
2437/*!
2438 \reimp
2439*/
2440
2441void QTextEdit::contentsMouseDoubleClickEvent( QMouseEvent * e )
2442{
2443 if ( e->button() != Qt::LeftButton ) {
2444 e->ignore();
2445 return;
2446 }
2447 int para = 0;
2448 int index = charAt( e->pos(), &para );
2449#ifdef QT_TEXTEDIT_OPTIMIZATION
2450 if ( d->optimMode ) {
2451 QString str = d->od->lines[ LOGOFFSET(para) ];
2452 int startIdx = index, endIdx = index, i;
2453 if ( !str[ index ].isSpace() ) {
2454 i = startIdx;
2455 // find start of word
2456 while ( i >= 0 && !str[ i ].isSpace() ) {
2457 startIdx = i--;
2458 }
2459 i = endIdx;
2460 // find end of word..
2461 while ( (uint) i < str.length() && !str[ i ].isSpace() ) {
2462 endIdx = ++i;
2463 }
2464 // ..and start of next
2465 while ( (uint) i < str.length() && str[ i ].isSpace() ) {
2466 endIdx = ++i;
2467 }
2468 optimSetSelection( para, startIdx, para, endIdx );
2469 repaintContents( FALSE );
2470 }
2471 } else
2472#endif
2473 {
2474 QTextCursor c1 = *cursor;
2475 QTextCursor c2 = *cursor;
2476#if defined(Q_OS_MAC)
2477 QTextParagraph *para = cursor->paragraph();
2478 if ( cursor->isValid() ) {
2479 if ( para->at( cursor->index() )->c.isLetterOrNumber() ) {
2480 while ( c1.index() > 0 &&
2481 c1.paragraph()->at( c1.index()-1 )->c.isLetterOrNumber() )
2482 c1.gotoPreviousLetter();
2483 while ( c2.paragraph()->at( c2.index() )->c.isLetterOrNumber() &&
2484 !c2.atParagEnd() )
2485 c2.gotoNextLetter();
2486 } else if ( para->at( cursor->index() )->c.isSpace() ) {
2487 while ( c1.index() > 0 &&
2488 c1.paragraph()->at( c1.index()-1 )->c.isSpace() )
2489 c1.gotoPreviousLetter();
2490 while ( c2.paragraph()->at( c2.index() )->c.isSpace() &&
2491 !c2.atParagEnd() )
2492 c2.gotoNextLetter();
2493 } else if ( !c2.atParagEnd() ) {
2494 c2.gotoNextLetter();
2495 }
2496 }
2497#else
2498 if ( cursor->index() > 0 && !cursor->paragraph()->at( cursor->index()-1 )->c.isSpace() )
2499 c1.gotoPreviousWord();
2500 if ( !cursor->paragraph()->at( cursor->index() )->c.isSpace() && !cursor->atParagEnd() )
2501 c2.gotoNextWord();
2502#endif
2503 doc->setSelectionStart( QTextDocument::Standard, c1 );
2504 doc->setSelectionEnd( QTextDocument::Standard, c2 );
2505
2506 *cursor = c2;
2507
2508 repaintChanged();
2509
2510 d->trippleClickTimer->start( qApp->doubleClickInterval(), TRUE );
2511 d->trippleClickPoint = e->globalPos();
2512 }
2513 inDoubleClick = TRUE;
2514 mousePressed = TRUE;
2515 emit doubleClicked( para, index );
2516}
2517
2518#ifndef QT_NO_DRAGANDDROP
2519
2520/*!
2521 \reimp
2522*/
2523
2524void QTextEdit::contentsDragEnterEvent( QDragEnterEvent *e )
2525{
2526 if ( isReadOnly() || !QTextDrag::canDecode( e ) ) {
2527 e->ignore();
2528 return;
2529 }
2530 e->acceptAction();
2531 inDnD = TRUE;
2532}
2533
2534/*!
2535 \reimp
2536*/
2537
2538void QTextEdit::contentsDragMoveEvent( QDragMoveEvent *e )
2539{
2540 if ( isReadOnly() || !QTextDrag::canDecode( e ) ) {
2541 e->ignore();
2542 return;
2543 }
2544 drawCursor( FALSE );
2545 placeCursor( e->pos(), cursor );
2546 drawCursor( TRUE );
2547 e->acceptAction();
2548}
2549
2550/*!
2551 \reimp
2552*/
2553
2554void QTextEdit::contentsDragLeaveEvent( QDragLeaveEvent * )
2555{
2556 inDnD = FALSE;
2557}
2558
2559/*!
2560 \reimp
2561*/
2562
2563void QTextEdit::contentsDropEvent( QDropEvent *e )
2564{
2565 if ( isReadOnly() )
2566 return;
2567 inDnD = FALSE;
2568 e->acceptAction();
2569 bool intern = FALSE;
2570 if ( QRichTextDrag::canDecode( e ) ) {
2571 bool hasSel = doc->hasSelection( QTextDocument::Standard );
2572 bool internalDrag = e->source() == this || e->source() == viewport();
2573 int dropId, dropIndex;
2574 QTextCursor insertCursor = *cursor;
2575 dropId = cursor->paragraph()->paragId();
2576 dropIndex = cursor->index();
2577 if ( hasSel && internalDrag ) {
2578 QTextCursor c1, c2;
2579 int selStartId, selStartIndex;
2580 int selEndId, selEndIndex;
2581 c1 = doc->selectionStartCursor( QTextDocument::Standard );
2582 c1.restoreState();
2583 c2 = doc->selectionEndCursor( QTextDocument::Standard );
2584 c2.restoreState();
2585 selStartId = c1.paragraph()->paragId();
2586 selStartIndex = c1.index();
2587 selEndId = c2.paragraph()->paragId();
2588 selEndIndex = c2.index();
2589 if ( ( ( dropId > selStartId ) ||
2590 ( dropId == selStartId && dropIndex > selStartIndex ) ) &&
2591 ( ( dropId < selEndId ) ||
2592 ( dropId == selEndId && dropIndex <= selEndIndex ) ) )
2593 insertCursor = c1;
2594 if ( dropId == selEndId && dropIndex > selEndIndex ) {
2595 insertCursor = c1;
2596 if ( selStartId == selEndId ) {
2597 insertCursor.setIndex( dropIndex -
2598 ( selEndIndex - selStartIndex ) );
2599 } else {
2600 insertCursor.setIndex( dropIndex - selEndIndex +
2601 selStartIndex );
2602 }
2603 }
2604 }
2605
2606 if ( internalDrag && e->action() == QDropEvent::Move ) {
2607 removeSelectedText();
2608 intern = TRUE;
2609 doc->removeSelection( QTextDocument::Standard );
2610 } else {
2611 doc->removeSelection( QTextDocument::Standard );
2612#ifndef QT_NO_CURSOR
2613 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
2614#endif
2615 }
2616 drawCursor( FALSE );
2617 cursor->setParagraph( insertCursor.paragraph() );
2618 cursor->setIndex( insertCursor.index() );
2619 drawCursor( TRUE );
2620 if ( !cursor->nestedDepth() ) {
2621 QString subType = "plain";
2622 if ( textFormat() != PlainText ) {
2623 if ( e->provides( "application/x-qrichtext" ) )
2624 subType = "x-qrichtext";
2625 }
2626#ifndef QT_NO_CLIPBOARD
2627 pasteSubType( subType.latin1(), e );
2628#endif
2629 // emit appropriate signals.
2630 emit selectionChanged();
2631 emit cursorPositionChanged( cursor );
2632 emit cursorPositionChanged( cursor->paragraph()->paragId(), cursor->index() );
2633 } else {
2634 if ( intern )
2635 undo();
2636 e->ignore();
2637 }
2638 }
2639}
2640
2641#endif
2642
2643/*!
2644 \reimp
2645*/
2646void QTextEdit::contentsContextMenuEvent( QContextMenuEvent *e )
2647{
2648 clearUndoRedo();
2649 mousePressed = FALSE;
2650
2651 e->accept();
2652#ifndef QT_NO_POPUPMENU
2653 QPopupMenu *popup = createPopupMenu( e->pos() );
2654 if ( !popup )
2655 popup = createPopupMenu();
2656 if ( !popup )
2657 return;
2658 int r = popup->exec( e->globalPos() );
2659 delete popup;
2660
2661 if ( r == d->id[ IdClear ] )
2662 clear();
2663 else if ( r == d->id[ IdSelectAll ] ) {
2664 selectAll();
2665#ifndef QT_NO_CLIPBOARD
2666 // if the clipboard support selections, put the newly selected text into
2667 // the clipboard
2668 if (QApplication::clipboard()->supportsSelection()) {
2669 d->clipboard_mode = QClipboard::Selection;
2670
2671 // don't listen to selection changes
2672 disconnect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
2673 copy();
2674 // listen to selection changes
2675 connect( QApplication::clipboard(), SIGNAL(selectionChanged()),
2676 this, SLOT(clipboardChanged()) );
2677
2678 d->clipboard_mode = QClipboard::Clipboard;
2679 }
2680#endif
2681 } else if ( r == d->id[ IdUndo ] )
2682 undo();
2683 else if ( r == d->id[ IdRedo ] )
2684 redo();
2685#ifndef QT_NO_CLIPBOARD
2686 else if ( r == d->id[ IdCut ] )
2687 cut();
2688 else if ( r == d->id[ IdCopy ] )
2689 copy();
2690 else if ( r == d->id[ IdPaste ] )
2691 paste();
2692#endif
2693#endif
2694}
2695
2696
2697void QTextEdit::autoScrollTimerDone()
2698{
2699 if ( mousePressed )
2700 handleMouseMove( viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ) );
2701}
2702
2703void QTextEdit::handleMouseMove( const QPoint& pos )
2704{
2705 if ( !mousePressed )
2706 return;
2707
2708 if ( !scrollTimer->isActive() && pos.y() < contentsY() || pos.y() > contentsY() + visibleHeight() )
2709 scrollTimer->start( 100, FALSE );
2710 else if ( scrollTimer->isActive() && pos.y() >= contentsY() && pos.y() <= contentsY() + visibleHeight() )
2711 scrollTimer->stop();
2712
2713 drawCursor( FALSE );
2714 QTextCursor oldCursor = *cursor;
2715
2716 placeCursor( pos );
2717
2718 if ( inDoubleClick ) {
2719 QTextCursor cl = *cursor;
2720 cl.gotoPreviousWord();
2721 QTextCursor cr = *cursor;
2722 cr.gotoNextWord();
2723
2724 int diff = QABS( oldCursor.paragraph()->at( oldCursor.index() )->x - mousePos.x() );
2725 int ldiff = QABS( cl.paragraph()->at( cl.index() )->x - mousePos.x() );
2726 int rdiff = QABS( cr.paragraph()->at( cr.index() )->x - mousePos.x() );
2727
2728
2729 if ( cursor->paragraph()->lineStartOfChar( cursor->index() ) !=
2730 oldCursor.paragraph()->lineStartOfChar( oldCursor.index() ) )
2731 diff = 0xFFFFFF;
2732
2733 if ( rdiff < diff && rdiff < ldiff )
2734 *cursor = cr;
2735 else if ( ldiff < diff && ldiff < rdiff )
2736 *cursor = cl;
2737 else
2738 *cursor = oldCursor;
2739
2740 }
2741 ensureCursorVisible();
2742
2743 bool redraw = FALSE;
2744 if ( doc->hasSelection( QTextDocument::Standard ) ) {
2745 redraw = doc->setSelectionEnd( QTextDocument::Standard, *cursor ) || redraw;
2746 }
2747
2748 if ( !redraw ) {
2749 drawCursor( TRUE );
2750 } else {
2751 repaintChanged();
2752 drawCursor( TRUE );
2753 }
2754
2755 if ( currentFormat && currentFormat->key() != cursor->paragraph()->at( cursor->index() )->format()->key() ) {
2756 currentFormat->removeRef();
2757 currentFormat = doc->formatCollection()->format( cursor->paragraph()->at( cursor->index() )->format() );
2758 if ( currentFormat->isMisspelled() ) {
2759 currentFormat->removeRef();
2760 currentFormat = doc->formatCollection()->format( currentFormat->font(), currentFormat->color() );
2761 }
2762 emit currentFontChanged( currentFormat->font() );
2763 emit currentColorChanged( currentFormat->color() );
2764 emit currentVerticalAlignmentChanged( (VerticalAlignment)currentFormat->vAlign() );
2765 }
2766
2767 if ( currentAlignment != cursor->paragraph()->alignment() ) {
2768 currentAlignment = cursor->paragraph()->alignment();
2769 block_set_alignment = TRUE;
2770 emit currentAlignmentChanged( currentAlignment );
2771 block_set_alignment = FALSE;
2772 }
2773}
2774
2775/*! \internal */
2776
2777void QTextEdit::placeCursor( const QPoint &pos, QTextCursor *c, bool link )
2778{
2779#ifdef QT_TEXTEDIT_OPTIMIZATION
2780 if ( d->optimMode )
2781 return;
2782#endif
2783 if ( !c )
2784 c = cursor;
2785
2786 resetInputContext();
2787 c->restoreState();
2788 QTextParagraph *s = doc->firstParagraph();
2789 c->place( pos, s, link );
2790 updateMicroFocusHint();
2791}
2792
2793
2794void QTextEdit::updateMicroFocusHint()
2795{
2796 QTextCursor c( *cursor );
2797 if ( d->preeditStart != -1 )
2798 c.setIndex( d->preeditStart );
2799
2800 if ( hasFocus() || viewport()->hasFocus() ) {
2801 int h = c.paragraph()->lineHeightOfChar( cursor->index() );
2802 if ( !readonly ) {
2803 QFont f = c.paragraph()->at( c.index() )->format()->font();
2804 setMicroFocusHint( c.x() - contentsX() + frameWidth(),
2805 c.y() + cursor->paragraph()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE, &f );
2806 }
2807 }
2808}
2809
2810
2811
2812void QTextEdit::formatMore()
2813{
2814 if ( !lastFormatted )
2815 return;
2816
2817 int bottom = contentsHeight();
2818 int lastTop = -1;
2819 int lastBottom = -1;
2820 int to = 20;
2821 bool firstVisible = FALSE;
2822 QRect cr( contentsX(), contentsY(), visibleWidth(), visibleHeight() );
2823 for ( int i = 0; lastFormatted &&
2824 ( i < to || ( firstVisible && lastTop < contentsY()+height() ) );
2825 i++ ) {
2826 lastFormatted->format();
2827 lastTop = lastFormatted->rect().top();
2828 lastBottom = lastFormatted->rect().bottom();
2829 if ( i == 0 )
2830 firstVisible = lastBottom < cr.bottom();
2831 bottom = QMAX( bottom, lastBottom );
2832 lastFormatted = lastFormatted->next();
2833 }
2834
2835 if ( bottom > contentsHeight() ) {
2836 resizeContents( contentsWidth(), QMAX( doc->height(), bottom ) );
2837 } else if ( !lastFormatted && lastBottom < contentsHeight() ) {
2838 resizeContents( contentsWidth(), QMAX( doc->height(), lastBottom ) );
2839 if ( contentsHeight() < visibleHeight() )
2840 updateContents( 0, contentsHeight(), visibleWidth(),
2841 visibleHeight() - contentsHeight() );
2842 }
2843
2844 if ( lastFormatted )
2845 formatTimer->start( interval, TRUE );
2846 else
2847 interval = QMAX( 0, interval );
2848}
2849
2850void QTextEdit::doResize()
2851{
2852#ifdef QT_TEXTEDIT_OPTIMIZATION
2853 if ( !d->optimMode )
2854#endif
2855 {
2856 if ( wrapMode == FixedPixelWidth )
2857 return;
2858 doc->setMinimumWidth( -1 );
2859 resizeContents( 0, 0 );
2860 doc->setWidth( visibleWidth() );
2861 doc->invalidate();
2862 lastFormatted = doc->firstParagraph();
2863 interval = 0;
2864 formatMore();
2865 }
2866 repaintContents( FALSE );
2867}
2868
2869/*! \internal */
2870
2871void QTextEdit::doChangeInterval()
2872{
2873 interval = 0;
2874}
2875
2876/*!
2877 \reimp
2878*/
2879
2880bool QTextEdit::eventFilter( QObject *o, QEvent *e )
2881{
2882#ifdef QT_TEXTEDIT_OPTIMIZATION
2883 if ( !d->optimMode && (o == this || o == viewport()) ) {
2884#else
2885 if ( o == this || o == viewport() ) {
2886#endif
2887 if ( e->type() == QEvent::FocusIn ) {
2888 if ( QApplication::cursorFlashTime() > 0 )
2889 blinkTimer->start( QApplication::cursorFlashTime() / 2 );
2890 drawCursor( TRUE );
2891 updateMicroFocusHint();
2892 } else if ( e->type() == QEvent::FocusOut ) {
2893 blinkTimer->stop();
2894 drawCursor( FALSE );
2895 }
2896 }
2897
2898 if ( o == this && e->type() == QEvent::PaletteChange ) {
2899 QColor old( viewport()->colorGroup().color( QColorGroup::Text ) );
2900 if ( old != colorGroup().color( QColorGroup::Text ) ) {
2901 QColor c( colorGroup().color( QColorGroup::Text ) );
2902 doc->setMinimumWidth( -1 );
2903 doc->setDefaultFormat( doc->formatCollection()->defaultFormat()->font(), c );
2904 lastFormatted = doc->firstParagraph();
2905 formatMore();
2906 repaintChanged();
2907 }
2908 }
2909
2910 return QScrollView::eventFilter( o, e );
2911}
2912
2913/*!
2914 \obsolete
2915 */
2916void QTextEdit::insert( const QString &text, bool indent,
2917 bool checkNewLine, bool removeSelected )
2918{
2919 uint f = 0;
2920 if ( indent )
2921 f |= RedoIndentation;
2922 if ( checkNewLine )
2923 f |= CheckNewLines;
2924 if ( removeSelected )
2925 f |= RemoveSelected;
2926 insert( text, f );
2927}
2928
2929/*!
2930 Inserts \a text at the current cursor position.
2931
2932 The \a insertionFlags define how the text is inserted. If \c
2933 RedoIndentation is set, the paragraph is re-indented. If \c
2934 CheckNewLines is set, newline characters in \a text result in hard
2935 line breaks (i.e. new paragraphs). If \c checkNewLine is not set,
2936 the behaviour of the editor is undefined if the \a text contains
2937 newlines. (It is not possible to change QTextEdit's newline handling
2938 behavior, but you can use QString::replace() to preprocess text
2939 before inserting it.) If \c RemoveSelected is set, any selected
2940 text (in selection 0) is removed before the text is inserted.
2941
2942 The default flags are \c CheckNewLines | \c RemoveSelected.
2943
2944 If the widget is in \c LogText mode this function will do nothing.
2945
2946 \sa paste() pasteSubType()
2947*/
2948
2949
2950void QTextEdit::insert( const QString &text, uint insertionFlags )
2951{
2952#ifdef QT_TEXTEDIT_OPTIMIZATION
2953 if ( d->optimMode )
2954 return;
2955#endif
2956
2957 if ( cursor->nestedDepth() != 0 ) // #### for 3.0, disable editing of tables as this is not advanced enough
2958 return;
2959
2960 bool indent = insertionFlags & RedoIndentation;
2961 bool checkNewLine = insertionFlags & CheckNewLines;
2962 bool removeSelected = insertionFlags & RemoveSelected;
2963 QString txt( text );
2964 drawCursor( FALSE );
2965 if ( !isReadOnly() && doc->hasSelection( QTextDocument::Standard ) && removeSelected )
2966 removeSelectedText();
2967 QTextCursor c2 = *cursor;
2968 int oldLen = 0;
2969
2970 if ( undoEnabled && !isReadOnly() ) {
2971 checkUndoRedoInfo( UndoRedoInfo::Insert );
2972 if ( !undoRedoInfo.valid() ) {
2973 undoRedoInfo.id = cursor->paragraph()->paragId();
2974 undoRedoInfo.index = cursor->index();
2975 undoRedoInfo.d->text = QString::null;
2976 }
2977 oldLen = undoRedoInfo.d->text.length();
2978 }
2979
2980 lastFormatted = checkNewLine && cursor->paragraph()->prev() ?
2981 cursor->paragraph()->prev() : cursor->paragraph();
2982 QTextCursor oldCursor = *cursor;
2983 cursor->insert( txt, checkNewLine );
2984 if ( doc->useFormatCollection() && !doc->preProcessor() ) {
2985 doc->setSelectionStart( QTextDocument::Temp, oldCursor );
2986 doc->setSelectionEnd( QTextDocument::Temp, *cursor );
2987 doc->setFormat( QTextDocument::Temp, currentFormat, QTextFormat::Format );
2988 doc->removeSelection( QTextDocument::Temp );
2989 }
2990
2991 if ( indent && ( txt == "{" || txt == "}" || txt == ":" || txt == "#" ) )
2992 cursor->indent();
2993 formatMore();
2994 repaintChanged();
2995 ensureCursorVisible();
2996 drawCursor( TRUE );
2997
2998 if ( undoEnabled && !isReadOnly() ) {
2999 undoRedoInfo.d->text += txt;
3000 if ( !doc->preProcessor() ) {
3001 for ( int i = 0; i < (int)txt.length(); ++i ) {
3002 if ( txt[ i ] != '\n' && c2.paragraph()->at( c2.index() )->format() ) {
3003 c2.paragraph()->at( c2.index() )->format()->addRef();
3004 undoRedoInfo.d->text.
3005 setFormat( oldLen + i,
3006 c2.paragraph()->at( c2.index() )->format(), TRUE );
3007 }
3008 c2.gotoNextLetter();
3009 }
3010 }
3011 }
3012
3013 if ( !removeSelected ) {
3014 doc->setSelectionStart( QTextDocument::Standard, oldCursor );
3015 doc->setSelectionEnd( QTextDocument::Standard, *cursor );
3016 repaintChanged();
3017 }
3018 updateMicroFocusHint();
3019 setModified();
3020 emit textChanged();
3021}
3022
3023/*!
3024 Inserts \a text in the paragraph \a para at position \a index.
3025*/
3026
3027void QTextEdit::insertAt( const QString &text, int para, int index )
3028{
3029#ifdef QT_TEXTEDIT_OPTIMIZATION
3030 if ( d->optimMode ) {
3031 optimInsert( text, para, index );
3032 return;
3033 }
3034#endif
3035 QTextParagraph *p = doc->paragAt( para );
3036 if ( !p )
3037 return;
3038 removeSelection( QTextDocument::Standard );
3039 QTextCursor tmp = *cursor;
3040 cursor->setParagraph( p );
3041 cursor->setIndex( index );
3042 insert( text, FALSE, TRUE, FALSE );
3043 *cursor = tmp;
3044 removeSelection( QTextDocument::Standard );
3045}
3046
3047/*!
3048 Inserts \a text as a new paragraph at position \a para. If \a para
3049 is -1, the text is appended. Use append() if the append operation
3050 is performance critical.
3051*/
3052
3053void QTextEdit::insertParagraph( const QString &text, int para )
3054{
3055#ifdef QT_TEXTEDIT_OPTIMIZATION
3056 if ( d->optimMode ) {
3057 optimInsert( text + "\n", para, 0 );
3058 return;
3059 }
3060#endif
3061 for ( int i = 0; i < (int)doc->numSelections(); ++i )
3062 doc->removeSelection( i );
3063
3064 QTextParagraph *p = doc->paragAt( para );
3065
3066 bool append = !p;
3067 if ( !p )
3068 p = doc->lastParagraph();
3069
3070 QTextCursor old = *cursor;
3071 drawCursor( FALSE );
3072
3073 cursor->setParagraph( p );
3074 cursor->setIndex( 0 );
3075 clearUndoRedo();
3076 qtextedit_ignore_readonly = TRUE;
3077 if ( append && cursor->paragraph()->length() > 1 ) {
3078 cursor->setIndex( cursor->paragraph()->length() - 1 );
3079 doKeyboardAction( ActionReturn );
3080 }
3081 insert( text, FALSE, TRUE, TRUE );
3082 doKeyboardAction( ActionReturn );
3083 qtextedit_ignore_readonly = FALSE;
3084
3085 drawCursor( FALSE );
3086 *cursor = old;
3087 drawCursor( TRUE );
3088
3089 repaintChanged();
3090}
3091
3092/*!
3093 Removes the paragraph \a para.
3094*/
3095
3096void QTextEdit::removeParagraph( int para )
3097{
3098#ifdef QT_TEXTEDIT_OPTIMIZATION
3099 if ( d->optimMode )
3100 return;
3101#endif
3102 QTextParagraph *p = doc->paragAt( para );
3103 if ( !p )
3104 return;
3105
3106 for ( int i = 0; i < doc->numSelections(); ++i )
3107 doc->removeSelection( i );
3108
3109 QTextCursor start( doc );
3110 QTextCursor end( doc );
3111 start.setParagraph( p );
3112 start.setIndex( 0 );
3113 end.setParagraph( p );
3114 end.setIndex( p->length() - 1 );
3115
3116 if ( !(p == doc->firstParagraph() && p == doc->lastParagraph()) ) {
3117 if ( p->next() ) {
3118 end.setParagraph( p->next() );
3119 end.setIndex( 0 );
3120 } else if ( p->prev() ) {
3121 start.setParagraph( p->prev() );
3122 start.setIndex( p->prev()->length() - 1 );
3123 }
3124 }
3125
3126 doc->setSelectionStart( QTextDocument::Temp, start );
3127 doc->setSelectionEnd( QTextDocument::Temp, end );
3128 removeSelectedText( QTextDocument::Temp );
3129}
3130
3131/*!
3132 Undoes the last operation.
3133
3134 If there is no operation to undo, i.e. there is no undo step in
3135 the undo/redo history, nothing happens.
3136
3137 \sa undoAvailable() redo() undoDepth()
3138*/
3139
3140void QTextEdit::undo()
3141{
3142 clearUndoRedo();
3143 if ( isReadOnly() || !doc->commands()->isUndoAvailable() || !undoEnabled )
3144 return;
3145
3146 for ( int i = 0; i < (int)doc->numSelections(); ++i )
3147 doc->removeSelection( i );
3148
3149#ifndef QT_NO_CURSOR
3150 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
3151#endif
3152
3153 clearUndoRedo();
3154 drawCursor( FALSE );
3155 QTextCursor *c = doc->undo( cursor );
3156 if ( !c ) {
3157 drawCursor( TRUE );
3158 return;
3159 }
3160 lastFormatted = 0;
3161 ensureCursorVisible();
3162 repaintChanged();
3163 drawCursor( TRUE );
3164 updateMicroFocusHint();
3165 setModified();
3166 // ### If we get back to a completely blank textedit, it
3167 // is possible that cursor is invalid and further actions
3168 // might not fix the problem, so reset the cursor here.
3169 // This is copied from removeSeletedText(), it might be
3170 // okay to just call that.
3171 if ( !cursor->isValid() ) {
3172 delete cursor;
3173 cursor = new QTextCursor( doc );
3174 drawCursor( TRUE );
3175 repaintContents( TRUE );
3176 }
3177 emit undoAvailable( isUndoAvailable() );
3178 emit redoAvailable( isRedoAvailable() );
3179 emit textChanged();
3180}
3181
3182/*!
3183 Redoes the last operation.
3184
3185 If there is no operation to redo, i.e. there is no redo step in
3186 the undo/redo history, nothing happens.
3187
3188 \sa redoAvailable() undo() undoDepth()
3189*/
3190
3191void QTextEdit::redo()
3192{
3193 if ( isReadOnly() || !doc->commands()->isRedoAvailable() || !undoEnabled )
3194 return;
3195
3196 for ( int i = 0; i < (int)doc->numSelections(); ++i )
3197 doc->removeSelection( i );
3198
3199#ifndef QT_NO_CURSOR
3200 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
3201#endif
3202
3203 clearUndoRedo();
3204 drawCursor( FALSE );
3205 QTextCursor *c = doc->redo( cursor );
3206 if ( !c ) {
3207 drawCursor( TRUE );
3208 return;
3209 }
3210 lastFormatted = 0;
3211 ensureCursorVisible();
3212 repaintChanged();
3213 ensureCursorVisible();
3214 drawCursor( TRUE );
3215 updateMicroFocusHint();
3216 setModified();
3217 emit undoAvailable( isUndoAvailable() );
3218 emit redoAvailable( isRedoAvailable() );
3219 emit textChanged();
3220}
3221
3222/*!
3223 Pastes the text from the clipboard into the text edit at the
3224 current cursor position. Only plain text is pasted.
3225
3226 If there is no text in the clipboard nothing happens.
3227
3228 \sa pasteSubType() cut() QTextEdit::copy()
3229*/
3230
3231void QTextEdit::paste()
3232{
3233#ifndef QT_NO_MIMECLIPBOARD
3234 if ( isReadOnly() )
3235 return;
3236 QString subType = "plain";
3237 if ( textFormat() != PlainText ) {
3238 QMimeSource *m = QApplication::clipboard()->data( d->clipboard_mode );
3239 if ( !m )
3240 return;
3241 if ( m->provides( "application/x-qrichtext" ) )
3242 subType = "x-qrichtext";
3243 }
3244
3245 pasteSubType( subType.latin1() );
3246 updateMicroFocusHint();
3247#endif
3248}
3249
3250void QTextEdit::checkUndoRedoInfo( UndoRedoInfo::Type t )
3251{
3252 if ( undoRedoInfo.valid() && t != undoRedoInfo.type ) {
3253 clearUndoRedo();
3254 }
3255 undoRedoInfo.type = t;
3256}
3257
3258/*!
3259 Repaints any paragraphs that have changed.
3260
3261 Although used extensively internally you shouldn't need to call
3262 this yourself.
3263*/
3264
3265void QTextEdit::repaintChanged()
3266{
3267 if ( !isUpdatesEnabled() || !viewport()->isUpdatesEnabled() )
3268 return;
3269
3270 QPainter p( viewport() );
3271#ifdef QT_TEXTEDIT_OPTIMIZATION
3272 if ( d->optimMode ) {
3273 optimDrawContents( &p, contentsX(), contentsY(), visibleWidth(), visibleHeight() );
3274 return;
3275 }
3276#endif
3277 p.translate( -contentsX(), -contentsY() );
3278 paintDocument( FALSE, &p, contentsX(), contentsY(), visibleWidth(), visibleHeight() );
3279}
3280
3281#ifndef QT_NO_MIME
3282QTextDrag *QTextEdit::dragObject( QWidget *parent ) const
3283{
3284 if ( !doc->hasSelection( QTextDocument::Standard ) ||
3285 doc->selectedText( QTextDocument::Standard ).isEmpty() )
3286 return 0;
3287 if ( textFormat() != RichText )
3288 return new QTextDrag( doc->selectedText( QTextDocument::Standard ), parent );
3289 QRichTextDrag *drag = new QRichTextDrag( parent );
3290 drag->setPlainText( doc->selectedText( QTextDocument::Standard ) );
3291 drag->setRichText( doc->selectedText( QTextDocument::Standard, TRUE ) );
3292 return drag;
3293}
3294#endif
3295
3296/*!
3297 Copies the selected text (from selection 0) to the clipboard and
3298 deletes it from the text edit.
3299
3300 If there is no selected text (in selection 0) nothing happens.
3301
3302 \sa QTextEdit::copy() paste() pasteSubType()
3303*/
3304
3305void QTextEdit::cut()
3306{
3307 if ( isReadOnly() )
3308 return;
3309 normalCopy();
3310 removeSelectedText();
3311 updateMicroFocusHint();
3312}
3313
3314void QTextEdit::normalCopy()
3315{
3316#ifndef QT_NO_MIME
3317 QTextDrag *drag = dragObject();
3318 if ( !drag )
3319 return;
3320#ifndef QT_NO_MIMECLIPBOARD
3321 QApplication::clipboard()->setData( drag, d->clipboard_mode );
3322#endif // QT_NO_MIMECLIPBOARD
3323#endif // QT_NO_MIME
3324}
3325
3326/*!
3327 Copies any selected text (from selection 0) to the clipboard.
3328
3329 \sa hasSelectedText() copyAvailable()
3330*/
3331
3332void QTextEdit::copy()
3333{
3334#ifndef QT_NO_CLIPBOARD
3335# ifdef QT_TEXTEDIT_OPTIMIZATION
3336 if ( d->optimMode && optimHasSelection() )
3337 QApplication::clipboard()->setText( optimSelectedText(), d->clipboard_mode );
3338 else
3339 normalCopy();
3340# else
3341 normalCopy();
3342# endif
3343#endif
3344}
3345
3346/*!
3347 \internal
3348
3349 Re-indents the current paragraph.
3350*/
3351
3352void QTextEdit::indent()
3353{
3354 if ( isReadOnly() )
3355 return;
3356
3357 drawCursor( FALSE );
3358 if ( !doc->hasSelection( QTextDocument::Standard ) )
3359 cursor->indent();
3360 else
3361 doc->indentSelection( QTextDocument::Standard );
3362 repaintChanged();
3363 drawCursor( TRUE );
3364 setModified();
3365 emit textChanged();
3366}
3367
3368/*!
3369 Reimplemented to allow tabbing through links. If \a n is TRUE the
3370 tab moves the focus to the next child; if \a n is FALSE the tab
3371 moves the focus to the previous child. Returns TRUE if the focus
3372 was moved; otherwise returns FALSE.
3373 */
3374
3375bool QTextEdit::focusNextPrevChild( bool n )
3376{
3377 if ( !isReadOnly() || !linksEnabled() )
3378 return FALSE;
3379 bool b = doc->focusNextPrevChild( n );
3380 repaintChanged();
3381 if ( b )
3382 //##### this does not work with tables. The focusIndicator
3383 //should really be a QTextCursor. Fix 3.1
3384 makeParagVisible( doc->focusIndicator.parag );
3385 return b;
3386}
3387
3388/*!
3389 \internal
3390
3391 This functions sets the current format to \a f. Only the fields of \a
3392 f which are specified by the \a flags are used.
3393*/
3394
3395void QTextEdit::setFormat( QTextFormat *f, int flags )
3396{
3397 if ( doc->hasSelection( QTextDocument::Standard ) ) {
3398 drawCursor( FALSE );
3399 QTextCursor c1 = doc->selectionStartCursor( QTextDocument::Standard );
3400 c1.restoreState();
3401 QTextCursor c2 = doc->selectionEndCursor( QTextDocument::Standard );
3402 c2.restoreState();
3403 if ( undoEnabled ) {
3404 clearUndoRedo();
3405 undoRedoInfo.type = UndoRedoInfo::Format;
3406 undoRedoInfo.id = c1.paragraph()->paragId();
3407 undoRedoInfo.index = c1.index();
3408 undoRedoInfo.eid = c2.paragraph()->paragId();
3409 undoRedoInfo.eindex = c2.index();
3410 readFormats( c1, c2, undoRedoInfo.d->text );
3411 undoRedoInfo.format = f;
3412 undoRedoInfo.flags = flags;
3413 clearUndoRedo();
3414 }
3415 doc->setFormat( QTextDocument::Standard, f, flags );
3416 repaintChanged();
3417 formatMore();
3418 drawCursor( TRUE );
3419 setModified();
3420 emit textChanged();
3421 }
3422 if ( currentFormat && currentFormat->key() != f->key() ) {
3423 currentFormat->removeRef();
3424 currentFormat = doc->formatCollection()->format( f );
3425 if ( currentFormat->isMisspelled() ) {
3426 currentFormat->removeRef();
3427 currentFormat = doc->formatCollection()->format( currentFormat->font(),
3428 currentFormat->color() );
3429 }
3430 emit currentFontChanged( currentFormat->font() );
3431 emit currentColorChanged( currentFormat->color() );
3432 emit currentVerticalAlignmentChanged( (VerticalAlignment)currentFormat->vAlign() );
3433 if ( cursor->index() == cursor->paragraph()->length() - 1 ) {
3434 currentFormat->addRef();
3435 cursor->paragraph()->string()->setFormat( cursor->index(), currentFormat, TRUE );
3436 if ( cursor->paragraph()->length() == 1 ) {
3437 cursor->paragraph()->invalidate( 0 );
3438 cursor->paragraph()->format();
3439 repaintChanged();
3440 }
3441 }
3442 }
3443}
3444
3445/*!
3446 \reimp
3447*/
3448
3449void QTextEdit::setPalette( const QPalette &p )
3450{
3451 QScrollView::setPalette( p );
3452 if ( textFormat() == PlainText ) {
3453 QTextFormat *f = doc->formatCollection()->defaultFormat();
3454 f->setColor( colorGroup().text() );
3455 updateContents();
3456 }
3457}
3458
3459/*! \internal
3460 \warning In Qt 3.1 we will provide a cleaer API for the
3461 functionality which is provided by this function and in Qt 4.0 this
3462 function will go away.
3463
3464 Sets the paragraph style of the current paragraph
3465 to \a dm. If \a dm is QStyleSheetItem::DisplayListItem, the
3466 type of the list item is set to \a listStyle.
3467
3468 \sa setAlignment()
3469*/
3470
3471void QTextEdit::setParagType( QStyleSheetItem::DisplayMode dm,
3472 QStyleSheetItem::ListStyle listStyle )
3473{
3474 if ( isReadOnly() )
3475 return;
3476
3477 drawCursor( FALSE );
3478 QTextParagraph *start = cursor->paragraph();
3479 QTextParagraph *end = start;
3480 if ( doc->hasSelection( QTextDocument::Standard ) ) {
3481 start = doc->selectionStartCursor( QTextDocument::Standard ).topParagraph();
3482 end = doc->selectionEndCursor( QTextDocument::Standard ).topParagraph();
3483 if ( end->paragId() < start->paragId() )
3484 return; // do not trust our selections
3485 }
3486
3487 clearUndoRedo();
3488 undoRedoInfo.type = UndoRedoInfo::Style;
3489 undoRedoInfo.id = start->paragId();
3490 undoRedoInfo.eid = end->paragId();
3491 undoRedoInfo.styleInformation = QTextStyleCommand::readStyleInformation( doc, undoRedoInfo.id, undoRedoInfo.eid );
3492
3493 while ( start != end->next() ) {
3494 start->setListStyle( listStyle );
3495 if ( dm == QStyleSheetItem::DisplayListItem ) {
3496 start->setListItem( TRUE );
3497 if( start->listDepth() == 0 )
3498 start->setListDepth( 1 );
3499 } else if ( start->isListItem() ) {
3500 start->setListItem( FALSE );
3501 start->setListDepth( QMAX( start->listDepth()-1, 0 ) );
3502 }
3503 start = start->next();
3504 }
3505
3506 clearUndoRedo();
3507 repaintChanged();
3508 formatMore();
3509 drawCursor( TRUE );
3510 setModified();
3511 emit textChanged();
3512}
3513
3514/*!
3515 Sets the alignment of the current paragraph to \a a. Valid
3516 alignments are \c Qt::AlignLeft, \c Qt::AlignRight,
3517 \c Qt::AlignJustify and \c Qt::AlignCenter (which centers
3518 horizontally).
3519*/
3520
3521void QTextEdit::setAlignment( int a )
3522{
3523 if ( isReadOnly() || block_set_alignment )
3524 return;
3525
3526 drawCursor( FALSE );
3527 QTextParagraph *start = cursor->paragraph();
3528 QTextParagraph *end = start;
3529 if ( doc->hasSelection( QTextDocument::Standard ) ) {
3530 start = doc->selectionStartCursor( QTextDocument::Standard ).topParagraph();
3531 end = doc->selectionEndCursor( QTextDocument::Standard ).topParagraph();
3532 if ( end->paragId() < start->paragId() )
3533 return; // do not trust our selections
3534 }
3535
3536 clearUndoRedo();
3537 undoRedoInfo.type = UndoRedoInfo::Style;
3538 undoRedoInfo.id = start->paragId();
3539 undoRedoInfo.eid = end->paragId();
3540 undoRedoInfo.styleInformation = QTextStyleCommand::readStyleInformation( doc, undoRedoInfo.id, undoRedoInfo.eid );
3541
3542 while ( start != end->next() ) {
3543 start->setAlignment( a );
3544 start = start->next();
3545 }
3546
3547 clearUndoRedo();
3548 repaintChanged();
3549 formatMore();
3550 drawCursor( TRUE );
3551 if ( currentAlignment != a ) {
3552 currentAlignment = a;
3553 emit currentAlignmentChanged( currentAlignment );
3554 }
3555 setModified();
3556 emit textChanged();
3557}
3558
3559void QTextEdit::updateCurrentFormat()
3560{
3561 int i = cursor->index();
3562 if ( i > 0 )
3563 --i;
3564 if ( doc->useFormatCollection() &&
3565 ( !currentFormat || currentFormat->key() != cursor->paragraph()->at( i )->format()->key() ) ) {
3566 if ( currentFormat )
3567 currentFormat->removeRef();
3568 currentFormat = doc->formatCollection()->format( cursor->paragraph()->at( i )->format() );
3569 if ( currentFormat->isMisspelled() ) {
3570 currentFormat->removeRef();
3571 currentFormat = doc->formatCollection()->format( currentFormat->font(), currentFormat->color() );
3572 }
3573 emit currentFontChanged( currentFormat->font() );
3574 emit currentColorChanged( currentFormat->color() );
3575 emit currentVerticalAlignmentChanged( (VerticalAlignment)currentFormat->vAlign() );
3576 }
3577
3578 if ( currentAlignment != cursor->paragraph()->alignment() ) {
3579 currentAlignment = cursor->paragraph()->alignment();
3580 block_set_alignment = TRUE;
3581 emit currentAlignmentChanged( currentAlignment );
3582 block_set_alignment = FALSE;
3583 }
3584}
3585
3586/*!
3587 If \a b is TRUE sets the current format to italic; otherwise sets
3588 the current format to non-italic.
3589
3590 \sa italic()
3591*/
3592
3593void QTextEdit::setItalic( bool b )
3594{
3595 QTextFormat f( *currentFormat );
3596 f.setItalic( b );
3597 QTextFormat *f2 = doc->formatCollection()->format( &f );
3598 setFormat( f2, QTextFormat::Italic );
3599}
3600
3601/*!
3602 If \a b is TRUE sets the current format to bold; otherwise sets
3603 the current format to non-bold.
3604
3605 \sa bold()
3606*/
3607
3608void QTextEdit::setBold( bool b )
3609{
3610 QTextFormat f( *currentFormat );
3611 f.setBold( b );
3612 QTextFormat *f2 = doc->formatCollection()->format( &f );
3613 setFormat( f2, QTextFormat::Bold );
3614}
3615
3616/*!
3617 If \a b is TRUE sets the current format to underline; otherwise
3618 sets the current format to non-underline.
3619
3620 \sa underline()
3621*/
3622
3623void QTextEdit::setUnderline( bool b )
3624{
3625 QTextFormat f( *currentFormat );
3626 f.setUnderline( b );
3627 QTextFormat *f2 = doc->formatCollection()->format( &f );
3628 setFormat( f2, QTextFormat::Underline );
3629}
3630
3631/*!
3632 Sets the font family of the current format to \a fontFamily.
3633
3634 \sa family() setCurrentFont()
3635*/
3636
3637void QTextEdit::setFamily( const QString &fontFamily )
3638{
3639 QTextFormat f( *currentFormat );
3640 f.setFamily( fontFamily );
3641 QTextFormat *f2 = doc->formatCollection()->format( &f );
3642 setFormat( f2, QTextFormat::Family );
3643}
3644
3645/*!
3646 Sets the point size of the current format to \a s.
3647
3648 Note that if \a s is zero or negative, the behaviour of this
3649 function is not defined.
3650
3651 \sa pointSize() setCurrentFont() setFamily()
3652*/
3653
3654void QTextEdit::setPointSize( int s )
3655{
3656 QTextFormat f( *currentFormat );
3657 f.setPointSize( s );
3658 QTextFormat *f2 = doc->formatCollection()->format( &f );
3659 setFormat( f2, QTextFormat::Size );
3660}
3661
3662/*!
3663 Sets the color of the current format, i.e. of the text, to \a c.
3664
3665 \sa color() setPaper()
3666*/
3667
3668void QTextEdit::setColor( const QColor &c )
3669{
3670 QTextFormat f( *currentFormat );
3671 f.setColor( c );
3672 QTextFormat *f2 = doc->formatCollection()->format( &f );
3673 setFormat( f2, QTextFormat::Color );
3674}
3675
3676/*!
3677 Sets the vertical alignment of the current format, i.e. of the
3678 text, to \a a.
3679
3680 \sa color() setPaper()
3681*/
3682
3683void QTextEdit::setVerticalAlignment( VerticalAlignment a )
3684{
3685 QTextFormat f( *currentFormat );
3686 f.setVAlign( (QTextFormat::VerticalAlignment)a );
3687 QTextFormat *f2 = doc->formatCollection()->format( &f );
3688 setFormat( f2, QTextFormat::VAlign );
3689}
3690
3691void QTextEdit::setFontInternal( const QFont &f_ )
3692{
3693 QTextFormat f( *currentFormat );
3694 f.setFont( f_ );
3695 QTextFormat *f2 = doc->formatCollection()->format( &f );
3696 setFormat( f2, QTextFormat::Font );
3697}
3698
3699
3700QString QTextEdit::text() const
3701{
3702#ifdef QT_TEXTEDIT_OPTIMIZATION
3703 if ( d->optimMode )
3704 return optimText();
3705#endif
3706
3707 QTextParagraph *p = doc->firstParagraph();
3708 if ( !p || (!p->next() && p->length() <= 1) )
3709 return QString::fromLatin1("");
3710
3711 if ( isReadOnly() )
3712 return doc->originalText();
3713 return doc->text();
3714}
3715
3716/*!
3717 \overload
3718
3719 Returns the text of paragraph \a para.
3720
3721 If textFormat() is \c RichText the text will contain HTML
3722 formatting tags.
3723*/
3724
3725QString QTextEdit::text( int para ) const
3726{
3727#ifdef QT_TEXTEDIT_OPTIMIZATION
3728 if ( d->optimMode && (d->od->numLines >= para) ) {
3729 QString paraStr = d->od->lines[ LOGOFFSET(para) ];
3730 if ( paraStr.isEmpty() )
3731 paraStr = "\n";
3732 return paraStr;
3733 } else
3734#endif
3735 return doc->text( para );
3736}
3737
3738/*!
3739 \overload
3740
3741 Changes the text of the text edit to the string \a text and the
3742 context to \a context. Any previous text is removed.
3743
3744 \a text may be interpreted either as plain text or as rich text,
3745 depending on the textFormat(). The default setting is \c AutoText,
3746 i.e. the text edit auto-detects the format from \a text.
3747
3748 For rich text the rendering style and available tags are defined
3749 by a styleSheet(); see QStyleSheet for details.
3750
3751 The optional \a context is a path which the text edit's
3752 QMimeSourceFactory uses to resolve the locations of files and
3753 images. (See \l{QTextEdit::QTextEdit()}.) It is passed to the text
3754 edit's QMimeSourceFactory when quering data.
3755
3756 Note that the undo/redo history is cleared by this function.
3757
3758 \sa text(), setTextFormat()
3759*/
3760
3761void QTextEdit::setText( const QString &text, const QString &context )
3762{
3763#ifdef QT_TEXTEDIT_OPTIMIZATION
3764 if ( d->optimMode ) {
3765 optimSetText( text );
3766 return;
3767 }
3768#endif
3769 if ( !isModified() && isReadOnly() &&
3770 this->context() == context && this->text() == text )
3771 return;
3772
3773 emit undoAvailable( FALSE );
3774 emit redoAvailable( FALSE );
3775 undoRedoInfo.clear();
3776 doc->commands()->clear();
3777
3778 lastFormatted = 0;
3779 int oldCursorPos = cursor->index();
3780 int oldCursorPar = cursor->paragraph()->paragId();
3781 cursor->restoreState();
3782 delete cursor;
3783 doc->setText( text, context );
3784
3785 if ( wrapMode == FixedPixelWidth ) {
3786 resizeContents( wrapWidth, 0 );
3787 doc->setWidth( wrapWidth );
3788 doc->setMinimumWidth( wrapWidth );
3789 } else {
3790 doc->setMinimumWidth( -1 );
3791 resizeContents( 0, 0 );
3792 }
3793
3794 lastFormatted = doc->firstParagraph();
3795 cursor = new QTextCursor( doc );
3796 updateContents();
3797
3798 if ( isModified() )
3799 setModified( FALSE );
3800 emit textChanged();
3801 if ( cursor->index() != oldCursorPos || cursor->paragraph()->paragId() != oldCursorPar ) {
3802 emit cursorPositionChanged( cursor );
3803 emit cursorPositionChanged( cursor->paragraph()->paragId(), cursor->index() );
3804 }
3805 formatMore();
3806 updateCurrentFormat();
3807 d->scrollToAnchor = QString::null;
3808}
3809
3810/*!
3811 \property QTextEdit::text
3812 \brief the text edit's text
3813
3814 There is no default text.
3815
3816 On setting, any previous text is deleted.
3817
3818 The text may be interpreted either as plain text or as rich text,
3819 depending on the textFormat(). The default setting is \c AutoText,
3820 i.e. the text edit auto-detects the format of the text.
3821
3822 For richtext, calling text() on an editable QTextEdit will cause
3823 the text to be regenerated from the textedit. This may mean that
3824 the QString returned may not be exactly the same as the one that
3825 was set.
3826
3827 \sa textFormat
3828*/
3829
3830
3831/*!
3832 \property QTextEdit::readOnly
3833 \brief whether the text edit is read-only
3834
3835 In a read-only text edit the user can only navigate through the
3836 text and select text; modifying the text is not possible.
3837
3838 This property's default is FALSE.
3839*/
3840
3841/*!
3842 Finds the next occurrence of the string, \a expr. Returns TRUE if
3843 \a expr was found; otherwise returns FALSE.
3844
3845 If \a para and \a index are both 0 the search begins from the
3846 current cursor position. If \a para and \a index are both not 0,
3847 the search begins from the \a *index character position in the
3848 \a *para paragraph.
3849
3850 If \a cs is TRUE the search is case sensitive, otherwise it is
3851 case insensitive. If \a wo is TRUE the search looks for whole word
3852 matches only; otherwise it searches for any matching text. If \a
3853 forward is TRUE (the default) the search works forward from the
3854 starting position to the end of the text, otherwise it works
3855 backwards to the beginning of the text.
3856
3857 If \a expr is found the function returns TRUE. If \a index and \a
3858 para are not 0, the number of the paragraph in which the first
3859 character of the match was found is put into \a *para, and the
3860 index position of that character within the paragraph is put into
3861 \a *index.
3862
3863 If \a expr is not found the function returns FALSE. If \a index
3864 and \a para are not 0 and \a expr is not found, \a *index
3865 and \a *para are undefined.
3866
3867 Please note that this function will make the next occurrence of
3868 the string (if found) the current selection, and will thus
3869 modify the cursor position.
3870*/
3871
3872bool QTextEdit::find( const QString &expr, bool cs, bool wo, bool forward,
3873 int *para, int *index )
3874{
3875#ifdef QT_TEXTEDIT_OPTIMIZATION
3876 if ( d->optimMode )
3877 return optimFind( expr, cs, wo, forward, para, index );
3878#endif
3879 drawCursor( FALSE );
3880#ifndef QT_NO_CURSOR
3881 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
3882#endif
3883 QTextCursor findcur = *cursor;
3884 if ( para && index ) {
3885 if ( doc->paragAt( *para ) )
3886 findcur.gotoPosition( doc->paragAt(*para), *index );
3887 else
3888 findcur.gotoEnd();
3889 } else if ( doc->hasSelection( QTextDocument::Standard ) ){
3890 // maks sure we do not find the same selection again
3891 if ( forward )
3892 findcur.gotoNextLetter();
3893 else
3894 findcur.gotoPreviousLetter();
3895 } else if (!forward && findcur.index() == 0 && findcur.paragraph() == findcur.topParagraph()) {
3896 findcur.gotoEnd();
3897 }
3898 removeSelection( QTextDocument::Standard );
3899 bool found = doc->find( findcur, expr, cs, wo, forward );
3900 if ( found ) {
3901 if ( para )
3902 *para = findcur.paragraph()->paragId();
3903 if ( index )
3904 *index = findcur.index();
3905 *cursor = findcur;
3906 repaintChanged();
3907 ensureCursorVisible();
3908 }
3909 drawCursor( TRUE );
3910 if (found) {
3911 emit cursorPositionChanged( cursor );
3912 emit cursorPositionChanged( cursor->paragraph()->paragId(), cursor->index() );
3913 }
3914 return found;
3915}
3916
3917void QTextEdit::blinkCursor()
3918{
3919 if ( !cursorVisible )
3920 return;
3921 bool cv = cursorVisible;
3922 blinkCursorVisible = !blinkCursorVisible;
3923 drawCursor( blinkCursorVisible );
3924 cursorVisible = cv;
3925}
3926
3927/*!
3928 Sets the cursor to position \a index in paragraph \a para.
3929
3930 \sa getCursorPosition()
3931*/
3932
3933void QTextEdit::setCursorPosition( int para, int index )
3934{
3935 QTextParagraph *p = doc->paragAt( para );
3936 if ( !p )
3937 return;
3938
3939 if ( index > p->length() - 1 )
3940 index = p->length() - 1;
3941
3942 drawCursor( FALSE );
3943 cursor->setParagraph( p );
3944 cursor->setIndex( index );
3945 ensureCursorVisible();
3946 drawCursor( TRUE );
3947 updateCurrentFormat();
3948 emit cursorPositionChanged( cursor );
3949 emit cursorPositionChanged( cursor->paragraph()->paragId(), cursor->index() );
3950}
3951
3952/*!
3953 This function sets the \a *para and \a *index parameters to the
3954 current cursor position. \a para and \a index must not be 0.
3955
3956 \sa setCursorPosition()
3957*/
3958
3959void QTextEdit::getCursorPosition( int *para, int *index ) const
3960{
3961 if ( !para || !index )
3962 return;
3963 *para = cursor->paragraph()->paragId();
3964 *index = cursor->index();
3965}
3966
3967/*!
3968 Sets a selection which starts at position \a indexFrom in
3969 paragraph \a paraFrom and ends at position \a indexTo in paragraph
3970 \a paraTo.
3971
3972 Any existing selections which have a different id (\a selNum) are
3973 left alone, but if an existing selection has the same id as \a
3974 selNum it is removed and replaced by this selection.
3975
3976 Uses the selection settings of selection \a selNum. If \a selNum
3977 is 0, this is the default selection.
3978
3979 The cursor is moved to the end of the selection if \a selNum is 0,
3980 otherwise the cursor position remains unchanged.
3981
3982 \sa getSelection() selectedText
3983*/
3984
3985void QTextEdit::setSelection( int paraFrom, int indexFrom,
3986 int paraTo, int indexTo, int selNum )
3987{
3988#ifdef QT_TEXTEDIT_OPTIMIZATION
3989 if (d->optimMode) {
3990 optimSetSelection(paraFrom, indexFrom, paraTo, indexTo);
3991 repaintContents(FALSE);
3992 return;
3993 }
3994#endif
3995 if ( doc->hasSelection( selNum ) ) {
3996 doc->removeSelection( selNum );
3997 repaintChanged();
3998 }
3999 if ( selNum > doc->numSelections() - 1 )
4000 doc->addSelection( selNum );
4001 QTextParagraph *p1 = doc->paragAt( paraFrom );
4002 if ( !p1 )
4003 return;
4004 QTextParagraph *p2 = doc->paragAt( paraTo );
4005 if ( !p2 )
4006 return;
4007
4008 if ( indexFrom > p1->length() - 1 )
4009 indexFrom = p1->length() - 1;
4010 if ( indexTo > p2->length() - 1 )
4011 indexTo = p2->length() - 1;
4012
4013 drawCursor( FALSE );
4014 QTextCursor c = *cursor;
4015 QTextCursor oldCursor = *cursor;
4016 c.setParagraph( p1 );
4017 c.setIndex( indexFrom );
4018 cursor->setParagraph( p2 );
4019 cursor->setIndex( indexTo );
4020 doc->setSelectionStart( selNum, c );
4021 doc->setSelectionEnd( selNum, *cursor );
4022 repaintChanged();
4023 ensureCursorVisible();
4024 if ( selNum != QTextDocument::Standard )
4025 *cursor = oldCursor;
4026 drawCursor( TRUE );
4027}
4028
4029/*!
4030 If there is a selection, \a *paraFrom is set to the number of the
4031 paragraph in which the selection begins and \a *paraTo is set to
4032 the number of the paragraph in which the selection ends. (They
4033 could be the same.) \a *indexFrom is set to the index at which the
4034 selection begins within \a *paraFrom, and \a *indexTo is set to
4035 the index at which the selection ends within \a *paraTo.
4036
4037 If there is no selection, \a *paraFrom, \a *indexFrom, \a *paraTo
4038 and \a *indexTo are all set to -1.
4039
4040 If \a paraFrom, \a indexFrom, \a paraTo or \a indexTo is 0 this
4041 function does nothing.
4042
4043 The \a selNum is the number of the selection (multiple selections
4044 are supported). It defaults to 0 (the default selection).
4045
4046 \sa setSelection() selectedText
4047*/
4048
4049void QTextEdit::getSelection( int *paraFrom, int *indexFrom,
4050 int *paraTo, int *indexTo, int selNum ) const
4051{
4052 if ( !paraFrom || !paraTo || !indexFrom || !indexTo )
4053 return;
4054#ifdef QT_TEXTEDIT_OPTIMIZATION
4055 if (d->optimMode) {
4056 *paraFrom = d->od->selStart.line;
4057 *paraTo = d->od->selEnd.line;
4058 *indexFrom = d->od->selStart.index;
4059 *indexTo = d->od->selEnd.index;
4060 return;
4061 }
4062#endif
4063 if ( !doc->hasSelection( selNum ) ) {
4064 *paraFrom = -1;
4065 *indexFrom = -1;
4066 *paraTo = -1;
4067 *indexTo = -1;
4068 return;
4069 }
4070
4071 doc->selectionStart( selNum, *paraFrom, *indexFrom );
4072 doc->selectionEnd( selNum, *paraTo, *indexTo );
4073}
4074
4075/*!
4076 \property QTextEdit::textFormat
4077 \brief the text format: rich text, plain text, log text or auto text.
4078
4079 The text format is one of the following:
4080 \list
4081 \i PlainText - all characters, except newlines, are displayed
4082 verbatim, including spaces. Whenever a newline appears in the text
4083 the text edit inserts a hard line break and begins a new
4084 paragraph.
4085 \i RichText - rich text rendering. The available styles are
4086 defined in the default stylesheet QStyleSheet::defaultSheet().
4087 \i LogText - optimized mode for very large texts. Supports a very
4088 limited set of formatting tags (color, bold, underline and italic
4089 settings).
4090 \i AutoText - this is the default. The text edit autodetects which
4091 rendering style is best, \c PlainText or \c RichText. This is done
4092 by using the QStyleSheet::mightBeRichText() function.
4093 \endlist
4094*/
4095
4096void QTextEdit::setTextFormat( TextFormat format )
4097{
4098 doc->setTextFormat( format );
4099#ifdef QT_TEXTEDIT_OPTIMIZATION
4100 checkOptimMode();
4101#endif
4102}
4103
4104Qt::TextFormat QTextEdit::textFormat() const
4105{
4106 return doc->textFormat();
4107}
4108
4109/*!
4110 Returns the number of paragraphs in the text; an empty textedit is always
4111 considered to have one paragraph, so 1 is returned in this case.
4112*/
4113
4114int QTextEdit::paragraphs() const
4115{
4116#ifdef QT_TEXTEDIT_OPTIMIZATION
4117 if ( d->optimMode ) {
4118 return d->od->numLines;
4119 }
4120#endif
4121 return doc->lastParagraph()->paragId() + 1;
4122}
4123
4124/*!
4125 Returns the number of lines in paragraph \a para, or -1 if there
4126 is no paragraph with index \a para.
4127*/
4128
4129int QTextEdit::linesOfParagraph( int para ) const
4130{
4131#ifdef QT_TEXTEDIT_OPTIMIZATION
4132 if ( d->optimMode ) {
4133 if ( d->od->numLines >= para )
4134 return 1;
4135 else
4136 return -1;
4137 }
4138#endif
4139 QTextParagraph *p = doc->paragAt( para );
4140 if ( !p )
4141 return -1;
4142 return p->lines();
4143}
4144
4145/*!
4146 Returns the length of the paragraph \a para (i.e. the number of
4147 characters), or -1 if there is no paragraph with index \a para.
4148
4149 This function ignores newlines.
4150*/
4151
4152int QTextEdit::paragraphLength( int para ) const
4153{
4154#ifdef QT_TEXTEDIT_OPTIMIZATION
4155 if ( d->optimMode ) {
4156 if ( d->od->numLines >= para ) {
4157 if ( d->od->lines[ LOGOFFSET(para) ].isEmpty() ) // CR
4158 return 1;
4159 else
4160 return d->od->lines[ LOGOFFSET(para) ].length();
4161 }
4162 return -1;
4163 }
4164#endif
4165 QTextParagraph *p = doc->paragAt( para );
4166 if ( !p )
4167 return -1;
4168 return p->length() - 1;
4169}
4170
4171/*!
4172 Returns the number of lines in the text edit; this could be 0.
4173
4174 \warning This function may be slow. Lines change all the time
4175 during word wrapping, so this function has to iterate over all the
4176 paragraphs and get the number of lines from each one individually.
4177*/
4178
4179int QTextEdit::lines() const
4180{
4181#ifdef QT_TEXTEDIT_OPTIMIZATION
4182 if ( d->optimMode ) {
4183 return d->od->numLines;
4184 }
4185#endif
4186 QTextParagraph *p = doc->firstParagraph();
4187 int l = 0;
4188 while ( p ) {
4189 l += p->lines();
4190 p = p->next();
4191 }
4192
4193 return l;
4194}
4195
4196/*!
4197 Returns the line number of the line in paragraph \a para in which
4198 the character at position \a index appears. The \a index position is
4199 relative to the beginning of the paragraph. If there is no such
4200 paragraph or no such character at the \a index position (e.g. the
4201 index is out of range) -1 is returned.
4202*/
4203
4204int QTextEdit::lineOfChar( int para, int index )
4205{
4206 QTextParagraph *p = doc->paragAt( para );
4207 if ( !p )
4208 return -1;
4209
4210 int idx, line;
4211 QTextStringChar *c = p->lineStartOfChar( index, &idx, &line );
4212 if ( !c )
4213 return -1;
4214
4215 return line;
4216}
4217
4218void QTextEdit::setModified( bool m )
4219{
4220 bool oldModified = modified;
4221 modified = m;
4222 if ( modified && doc->oTextValid )
4223 doc->invalidateOriginalText();
4224 if ( oldModified != modified )
4225 emit modificationChanged( modified );
4226}
4227
4228/*!
4229 \property QTextEdit::modified
4230 \brief whether the document has been modified by the user
4231*/
4232
4233bool QTextEdit::isModified() const
4234{
4235 return modified;
4236}
4237
4238void QTextEdit::setModified()
4239{
4240 if ( !isModified() )
4241 setModified( TRUE );
4242}
4243
4244/*!
4245 Returns TRUE if the current format is italic; otherwise returns FALSE.
4246
4247 \sa setItalic()
4248*/
4249
4250bool QTextEdit::italic() const
4251{
4252 return currentFormat->font().italic();
4253}
4254
4255/*!
4256 Returns TRUE if the current format is bold; otherwise returns FALSE.
4257
4258 \sa setBold()
4259*/
4260
4261bool QTextEdit::bold() const
4262{
4263 return currentFormat->font().bold();
4264}
4265
4266/*!
4267 Returns TRUE if the current format is underlined; otherwise returns
4268 FALSE.
4269
4270 \sa setUnderline()
4271*/
4272
4273bool QTextEdit::underline() const
4274{
4275 return currentFormat->font().underline();
4276}
4277
4278/*!
4279 Returns the font family of the current format.
4280
4281 \sa setFamily() setCurrentFont() setPointSize()
4282*/
4283
4284QString QTextEdit::family() const
4285{
4286 return currentFormat->font().family();
4287}
4288
4289/*!
4290 Returns the point size of the font of the current format.
4291
4292 \sa setFamily() setCurrentFont() setPointSize()
4293*/
4294
4295int QTextEdit::pointSize() const
4296{
4297 return currentFormat->font().pointSize();
4298}
4299
4300/*!
4301 Returns the color of the current format.
4302
4303 \sa setColor() setPaper()
4304*/
4305
4306QColor QTextEdit::color() const
4307{
4308 return currentFormat->color();
4309}
4310
4311/*!
4312 \obsolete
4313
4314 Returns QScrollView::font()
4315
4316 \warning In previous versions this function returned the font of
4317 the current format. This lead to confusion. Please use
4318 currentFont() instead.
4319*/
4320
4321QFont QTextEdit::font() const
4322{
4323 return QScrollView::font();
4324}
4325
4326/*!
4327 Returns the font of the current format.
4328
4329 \sa setCurrentFont() setFamily() setPointSize()
4330*/
4331
4332QFont QTextEdit::currentFont() const
4333{
4334 return currentFormat->font();
4335}
4336
4337
4338/*!
4339 Returns the alignment of the current paragraph.
4340
4341 \sa setAlignment()
4342*/
4343
4344int QTextEdit::alignment() const
4345{
4346 return currentAlignment;
4347}
4348
4349void QTextEdit::startDrag()
4350{
4351#ifndef QT_NO_DRAGANDDROP
4352 mousePressed = FALSE;
4353 inDoubleClick = FALSE;
4354 QDragObject *drag = dragObject( viewport() );
4355 if ( !drag )
4356 return;
4357 if ( isReadOnly() ) {
4358 drag->dragCopy();
4359 } else {
4360 if ( drag->drag() && QDragObject::target() != this && QDragObject::target() != viewport() )
4361 removeSelectedText();
4362 }
4363#endif
4364}
4365
4366/*!
4367 If \a select is TRUE (the default), all the text is selected as
4368 selection 0. If \a select is FALSE any selected text is
4369 unselected, i.e. the default selection (selection 0) is cleared.
4370
4371 \sa selectedText
4372*/
4373
4374void QTextEdit::selectAll( bool select )
4375{
4376#ifdef QT_TEXTEDIT_OPTIMIZATION
4377 if ( d->optimMode ) {
4378 if ( select )
4379 optimSelectAll();
4380 else
4381 optimRemoveSelection();
4382 return;
4383 }
4384#endif
4385 if ( !select )
4386 doc->removeSelection( QTextDocument::Standard );
4387 else
4388 doc->selectAll( QTextDocument::Standard );
4389 repaintChanged();
4390 emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) );
4391 emit selectionChanged();
4392#ifndef QT_NO_CURSOR
4393 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
4394#endif
4395}
4396
4397void QTextEdit::UndoRedoInfo::clear()
4398{
4399 if ( valid() ) {
4400 if ( type == Insert || type == Return )
4401 doc->addCommand( new QTextInsertCommand( doc, id, index, d->text.rawData(), styleInformation ) );
4402 else if ( type == Format )
4403 doc->addCommand( new QTextFormatCommand( doc, id, index, eid, eindex, d->text.rawData(), format, flags ) );
4404 else if ( type == Style )
4405 doc->addCommand( new QTextStyleCommand( doc, id, eid, styleInformation ) );
4406 else if ( type != Invalid ) {
4407 doc->addCommand( new QTextDeleteCommand( doc, id, index, d->text.rawData(), styleInformation ) );
4408 }
4409 }
4410 type = Invalid;
4411 d->text = QString::null;
4412 id = -1;
4413 index = -1;
4414 styleInformation = QByteArray();
4415}
4416
4417
4418/*!
4419 If there is some selected text (in selection 0) it is deleted. If
4420 there is no selected text (in selection 0) the character to the
4421 right of the text cursor is deleted.
4422
4423 \sa removeSelectedText() cut()
4424*/
4425
4426void QTextEdit::del()
4427{
4428 if ( doc->hasSelection( QTextDocument::Standard ) ) {
4429 removeSelectedText();
4430 return;
4431 }
4432
4433 doKeyboardAction( ActionDelete );
4434}
4435
4436
4437QTextEdit::UndoRedoInfo::UndoRedoInfo( QTextDocument *dc )
4438 : type( Invalid ), doc( dc )
4439{
4440 d = new QUndoRedoInfoPrivate;
4441 d->text = QString::null;
4442 id = -1;
4443 index = -1;
4444}
4445
4446QTextEdit::UndoRedoInfo::~UndoRedoInfo()
4447{
4448 delete d;
4449}
4450
4451bool QTextEdit::UndoRedoInfo::valid() const
4452{
4453 return id >= 0 && type != Invalid;
4454}
4455
4456/*!
4457 \internal
4458
4459 Resets the current format to the default format.
4460*/
4461
4462void QTextEdit::resetFormat()
4463{
4464 setAlignment( Qt::AlignAuto );
4465 setParagType( QStyleSheetItem::DisplayBlock, QStyleSheetItem::ListDisc );
4466 setFormat( doc->formatCollection()->defaultFormat(), QTextFormat::Format );
4467}
4468
4469/*!
4470 Returns the QStyleSheet which is being used by this text edit.
4471
4472 \sa setStyleSheet()
4473*/
4474
4475QStyleSheet* QTextEdit::styleSheet() const
4476{
4477 return doc->styleSheet();
4478}
4479
4480/*!
4481 Sets the stylesheet to use with this text edit to \a styleSheet.
4482 Changes will only take effect for new text added with setText() or
4483 append().
4484
4485 \sa styleSheet()
4486*/
4487
4488void QTextEdit::setStyleSheet( QStyleSheet* styleSheet )
4489{
4490 doc->setStyleSheet( styleSheet );
4491}
4492
4493/*!
4494 \property QTextEdit::paper
4495 \brief the background (paper) brush.
4496
4497 The brush that is currently used to draw the background of the
4498 text edit. The initial setting is an empty brush.
4499*/
4500
4501void QTextEdit::setPaper( const QBrush& pap )
4502{
4503 doc->setPaper( new QBrush( pap ) );
4504 setPaletteBackgroundColor( pap.color() );
4505 viewport()->setPaletteBackgroundColor( pap.color() );
4506#ifdef QT_TEXTEDIT_OPTIMIZATION
4507 // force a repaint of the entire viewport - using updateContents()
4508 // would clip the coords to the content size
4509 if (d->optimMode)
4510 repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height());
4511 else
4512#endif
4513 updateContents();
4514}
4515
4516QBrush QTextEdit::paper() const
4517{
4518 if ( doc->paper() )
4519 return *doc->paper();
4520 return QBrush( colorGroup().base() );
4521}
4522
4523/*!
4524 \property QTextEdit::linkUnderline
4525 \brief whether hypertext links will be underlined
4526
4527 If TRUE (the default) hypertext links will be displayed
4528 underlined. If FALSE links will not be displayed underlined.
4529*/
4530
4531void QTextEdit::setLinkUnderline( bool b )
4532{
4533 if ( doc->underlineLinks() == b )
4534 return;
4535 doc->setUnderlineLinks( b );
4536 repaintChanged();
4537}
4538
4539bool QTextEdit::linkUnderline() const
4540{
4541 return doc->underlineLinks();
4542}
4543
4544/*!
4545 Sets the text edit's mimesource factory to \a factory. See
4546 QMimeSourceFactory for further details.
4547
4548 \sa mimeSourceFactory()
4549 */
4550
4551#ifndef QT_NO_MIME
4552void QTextEdit::setMimeSourceFactory( QMimeSourceFactory* factory )
4553{
4554 doc->setMimeSourceFactory( factory );
4555}
4556
4557/*!
4558 Returns the QMimeSourceFactory which is being used by this text
4559 edit.
4560
4561 \sa setMimeSourceFactory()
4562*/
4563
4564QMimeSourceFactory* QTextEdit::mimeSourceFactory() const
4565{
4566 return doc->mimeSourceFactory();
4567}
4568#endif
4569
4570/*!
4571 Returns how many pixels high the text edit needs to be to display
4572 all the text if the text edit is \a w pixels wide.
4573*/
4574
4575int QTextEdit::heightForWidth( int w ) const
4576{
4577 int oldw = doc->width();
4578 doc->doLayout( 0, w );
4579 int h = doc->height();
4580 doc->setWidth( oldw );
4581 doc->invalidate();
4582 ( (QTextEdit*)this )->formatMore();
4583 return h;
4584}
4585
4586/*!
4587 Appends a new paragraph with \a text to the end of the text edit. Note that
4588 the undo/redo history is cleared by this function, and no undo
4589 history is kept for appends which makes them faster than
4590 insert()s. If you want to append text which is added to the
4591 undo/redo history as well, use insertParagraph().
4592*/
4593
4594void QTextEdit::append( const QString &text )
4595{
4596#ifdef QT_TEXTEDIT_OPTIMIZATION
4597 if ( d->optimMode ) {
4598 optimAppend( text );
4599 return;
4600 }
4601#endif
4602 // flush and clear the undo/redo stack if necessary
4603 undoRedoInfo.clear();
4604 doc->commands()->clear();
4605
4606 doc->removeSelection( QTextDocument::Standard );
4607 TextFormat f = doc->textFormat();
4608 if ( f == AutoText ) {
4609 if ( QStyleSheet::mightBeRichText( text ) )
4610 f = RichText;
4611 else
4612 f = PlainText;
4613 }
4614
4615 drawCursor( FALSE );
4616 QTextCursor oldc( *cursor );
4617 ensureFormatted( doc->lastParagraph() );
4618 bool atBottom = contentsY() >= contentsHeight() - visibleHeight();
4619 cursor->gotoEnd();
4620 if ( cursor->index() > 0 )
4621 cursor->splitAndInsertEmptyParagraph();
4622 QTextCursor oldCursor2 = *cursor;
4623
4624 if ( f == Qt::PlainText ) {
4625 cursor->insert( text, TRUE );
4626 if ( doc->useFormatCollection() && !doc->preProcessor() &&
4627 currentFormat != cursor->paragraph()->at( cursor->index() )->format() ) {
4628 doc->setSelectionStart( QTextDocument::Temp, oldCursor2 );
4629 doc->setSelectionEnd( QTextDocument::Temp, *cursor );
4630 doc->setFormat( QTextDocument::Temp, currentFormat, QTextFormat::Format );
4631 doc->removeSelection( QTextDocument::Temp );
4632 }
4633 } else {
4634 cursor->paragraph()->setListItem( FALSE );
4635 cursor->paragraph()->setListDepth( 0 );
4636 if ( cursor->paragraph()->prev() )
4637 cursor->paragraph()->prev()->invalidate(0); // vertical margins might have to change
4638 doc->setRichTextInternal( text );
4639 }
4640 formatMore();
4641 repaintChanged();
4642 if ( atBottom )
4643 scrollToBottom();
4644 *cursor = oldc;
4645 if ( !isReadOnly() )
4646 cursorVisible = TRUE;
4647 setModified();
4648 emit textChanged();
4649}
4650
4651/*!
4652 \property QTextEdit::hasSelectedText
4653 \brief whether some text is selected in selection 0
4654*/
4655
4656bool QTextEdit::hasSelectedText() const
4657{
4658#ifdef QT_TEXTEDIT_OPTIMIZATION
4659 if ( d->optimMode )
4660 return optimHasSelection();
4661 else
4662#endif
4663 return doc->hasSelection( QTextDocument::Standard );
4664}
4665
4666/*!
4667 \property QTextEdit::selectedText
4668 \brief The selected text (from selection 0) or an empty string if
4669 there is no currently selected text (in selection 0).
4670
4671 The text is always returned as \c PlainText if the textFormat() is
4672 \c PlainText or \c AutoText, otherwise it is returned as HTML.
4673
4674 \sa hasSelectedText
4675*/
4676
4677QString QTextEdit::selectedText() const
4678{
4679#ifdef QT_TEXTEDIT_OPTIMIZATION
4680 if ( d->optimMode )
4681 return optimSelectedText();
4682 else
4683#endif
4684 return doc->selectedText( QTextDocument::Standard, textFormat() == RichText );
4685}
4686
4687bool QTextEdit::handleReadOnlyKeyEvent( QKeyEvent *e )
4688{
4689 switch( e->key() ) {
4690 case Key_Down:
4691 setContentsPos( contentsX(), contentsY() + 10 );
4692 break;
4693 case Key_Up:
4694 setContentsPos( contentsX(), contentsY() - 10 );
4695 break;
4696 case Key_Left:
4697 setContentsPos( contentsX() - 10, contentsY() );
4698 break;
4699 case Key_Right:
4700 setContentsPos( contentsX() + 10, contentsY() );
4701 break;
4702 case Key_PageUp:
4703 setContentsPos( contentsX(), contentsY() - visibleHeight() );
4704 break;
4705 case Key_PageDown:
4706 setContentsPos( contentsX(), contentsY() + visibleHeight() );
4707 break;
4708 case Key_Home:
4709 setContentsPos( contentsX(), 0 );
4710 break;
4711 case Key_End:
4712 setContentsPos( contentsX(), contentsHeight() - visibleHeight() );
4713 break;
4714 case Key_F16: // Copy key on Sun keyboards
4715 copy();
4716 break;
4717#ifndef QT_NO_NETWORKPROTOCOL
4718 case Key_Return:
4719 case Key_Enter:
4720 case Key_Space: {
4721 if (!doc->focusIndicator.href.isEmpty()
4722 || !doc->focusIndicator.name.isEmpty()) {
4723 if (!doc->focusIndicator.href.isEmpty()) {
4724 QUrl u( doc->context(), doc->focusIndicator.href, TRUE );
4725 emitLinkClicked( u.toString( FALSE, FALSE ) );
4726 }
4727 if (!doc->focusIndicator.name.isEmpty()) {
4728#ifndef QT_NO_TEXTBROWSER
4729 if (::qt_cast<QTextBrowser*>(this)) { // change for 4.0
4730 QConnectionList *clist = receivers(
4731 "anchorClicked(const QString&,const QString&)");
4732 if (!signalsBlocked() && clist) {
4733 QUObject o[3];
4734 static_QUType_QString.set(o+1,
4735 doc->focusIndicator.name);
4736 static_QUType_QString.set(o+2,
4737 doc->focusIndicator.href);
4738 activate_signal( clist, o);
4739 }
4740 }
4741#endif
4742 }
4743#ifndef QT_NO_CURSOR
4744 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
4745#endif
4746 }
4747 } break;
4748#endif
4749 default:
4750 if ( e->state() & ControlButton ) {
4751 switch ( e->key() ) {
4752 case Key_C: case Key_F16: // Copy key on Sun keyboards
4753 copy();
4754 break;
4755#if defined (Q_WS_WIN) || defined (Q_WS_PM)
4756 case Key_Insert:
4757 copy();
4758 break;
4759 case Key_A:
4760 selectAll();
4761 break;
4762#endif
4763 }
4764
4765 }
4766 return FALSE;
4767 }
4768 return TRUE;
4769}
4770
4771/*!
4772 Returns the context of the text edit. The context is a path which
4773 the text edit's QMimeSourceFactory uses to resolve the locations
4774 of files and images.
4775
4776 \sa text
4777*/
4778
4779QString QTextEdit::context() const
4780{
4781 return doc->context();
4782}
4783
4784/*!
4785 \property QTextEdit::documentTitle
4786 \brief the title of the document parsed from the text.
4787
4788 For \c PlainText the title will be an empty string. For \c
4789 RichText the title will be the text between the \c{<title>} tags,
4790 if present, otherwise an empty string.
4791*/
4792
4793QString QTextEdit::documentTitle() const
4794{
4795 return doc->attributes()[ "title" ];
4796}
4797
4798void QTextEdit::makeParagVisible( QTextParagraph *p )
4799{
4800 setContentsPos( contentsX(), QMIN( p->rect().y(), contentsHeight() - visibleHeight() ) );
4801}
4802
4803/*!
4804 Scrolls the text edit to make the text at the anchor called \a
4805 name visible, if it can be found in the document. If the anchor
4806 isn't found no scrolling will occur. An anchor is defined using
4807 the HTML anchor tag, e.g. \c{<a name="target">}.
4808*/
4809
4810void QTextEdit::scrollToAnchor( const QString& name )
4811{
4812 if ( !isVisible() ) {
4813 d->scrollToAnchor = name;
4814 return;
4815 }
4816 if ( name.isEmpty() )
4817 return;
4818 sync();
4819 QTextCursor cursor( doc );
4820 QTextParagraph* last = doc->lastParagraph();
4821 for (;;) {
4822 QTextStringChar* c = cursor.paragraph()->at( cursor.index() );
4823 if( c->isAnchor() ) {
4824 QString a = c->anchorName();
4825 if ( a == name ||
4826 (a.contains( '#' ) && QStringList::split( '#', a ).contains( name ) ) ) {
4827 setContentsPos( contentsX(), QMIN( cursor.paragraph()->rect().top() + cursor.totalOffsetY(), contentsHeight() - visibleHeight() ) );
4828 break;
4829 }
4830 }
4831 if ( cursor.paragraph() == last && cursor.atParagEnd() )
4832 break;
4833 cursor.gotoNextLetter();
4834 }
4835}
4836
4837#if (QT_VERSION-0 >= 0x040000)
4838#error "function anchorAt(const QPoint& pos) should be merged into function anchorAt(const QPoint& pos, AnchorAttribute attr)"
4839#endif
4840
4841/*!
4842 \overload
4843
4844 If there is an anchor at position \a pos (in contents
4845 coordinates), its \c href is returned, otherwise QString::null is
4846 returned.
4847*/
4848
4849QString QTextEdit::anchorAt( const QPoint& pos )
4850{
4851 return anchorAt(pos, AnchorHref);
4852}
4853
4854/*!
4855 If there is an anchor at position \a pos (in contents
4856 coordinates), the text for attribute \a attr is returned,
4857 otherwise QString::null is returned.
4858*/
4859
4860QString QTextEdit::anchorAt( const QPoint& pos, AnchorAttribute attr )
4861{
4862 QTextCursor c( doc );
4863 placeCursor( pos, &c );
4864 switch(attr) {
4865 case AnchorName:
4866 return c.paragraph()->at( c.index() )->anchorName();
4867 case AnchorHref:
4868 return c.paragraph()->at( c.index() )->anchorHref();
4869 }
4870 // incase the compiler is really dumb about determining if a function
4871 // returns something :)
4872 return QString::null;
4873}
4874
4875void QTextEdit::documentWidthChanged( int w )
4876{
4877 resizeContents( QMAX( visibleWidth(), w), contentsHeight() );
4878}
4879
4880/*! \internal
4881
4882 This function does nothing
4883*/
4884
4885void QTextEdit::updateStyles()
4886{
4887}
4888
4889void QTextEdit::setDocument( QTextDocument *dc )
4890{
4891 if ( dc == doc )
4892 return;
4893 doc = dc;
4894 delete cursor;
4895 cursor = new QTextCursor( doc );
4896 clearUndoRedo();
4897 undoRedoInfo.doc = doc;
4898 lastFormatted = 0;
4899}
4900
4901#ifndef QT_NO_CLIPBOARD
4902
4903/*!
4904 Pastes the text with format \a subtype from the clipboard into the
4905 text edit at the current cursor position. The \a subtype can be
4906 "plain" or "html".
4907
4908 If there is no text with format \a subtype in the clipboard
4909 nothing happens.
4910
4911 \sa paste() cut() QTextEdit::copy()
4912*/
4913
4914void QTextEdit::pasteSubType( const QCString &subtype )
4915{
4916#ifndef QT_NO_MIMECLIPBOARD
4917 QMimeSource *m = QApplication::clipboard()->data( d->clipboard_mode );
4918 pasteSubType( subtype, m );
4919#endif
4920}
4921
4922/*! \internal */
4923
4924void QTextEdit::pasteSubType( const QCString& subtype, QMimeSource *m )
4925{
4926#ifndef QT_NO_MIME
4927 QCString st = subtype;
4928 if ( subtype != "x-qrichtext" )
4929 st.prepend( "text/" );
4930 else
4931 st.prepend( "application/" );
4932 if ( !m )
4933 return;
4934 if ( doc->hasSelection( QTextDocument::Standard ) )
4935 removeSelectedText();
4936 if ( !QRichTextDrag::canDecode( m ) )
4937 return;
4938 QString t;
4939 if ( !QRichTextDrag::decode( m, t, st.data(), subtype ) )
4940 return;
4941 if ( st == "application/x-qrichtext" ) {
4942 int start;
4943 if ( (start = t.find( "<!--StartFragment-->" )) != -1 ) {
4944 start += 20;
4945 int end = t.find( "<!--EndFragment-->" );
4946 QTextCursor oldC = *cursor;
4947
4948 // during the setRichTextInternal() call the cursors
4949 // paragraph might get joined with the provious one, so
4950 // the cursors one would get deleted and oldC.paragraph()
4951 // would be a dnagling pointer. To avoid that try to go
4952 // one letter back and later go one forward again.
4953 oldC.gotoPreviousLetter();
4954 bool couldGoBack = oldC != *cursor;
4955 // first para might get deleted, so remember to reset it
4956 bool wasAtFirst = oldC.paragraph() == doc->firstParagraph();
4957
4958 if ( start < end )
4959 t = t.mid( start, end - start );
4960 else
4961 t = t.mid( start );
4962 lastFormatted = cursor->paragraph();
4963 if ( lastFormatted->prev() )
4964 lastFormatted = lastFormatted->prev();
4965 doc->setRichTextInternal( t, cursor );
4966
4967 // the first para might have been deleted in
4968 // setRichTextInternal(). To be sure, reset it if
4969 // necessary.
4970 if ( wasAtFirst ) {
4971 int index = oldC.index();
4972 oldC.setParagraph( doc->firstParagraph() );
4973 oldC.setIndex( index );
4974 }
4975
4976 // if we went back one letter before (see last comment),
4977 // go one forward to point to the right position
4978 if ( couldGoBack )
4979 oldC.gotoNextLetter();
4980
4981 if ( undoEnabled && !isReadOnly() ) {
4982 doc->setSelectionStart( QTextDocument::Temp, oldC );
4983 doc->setSelectionEnd( QTextDocument::Temp, *cursor );
4984
4985 checkUndoRedoInfo( UndoRedoInfo::Insert );
4986 if ( !undoRedoInfo.valid() ) {
4987 undoRedoInfo.id = oldC.paragraph()->paragId();
4988 undoRedoInfo.index = oldC.index();
4989 undoRedoInfo.d->text = QString::null;
4990 }
4991 int oldLen = undoRedoInfo.d->text.length();
4992 if ( !doc->preProcessor() ) {
4993 QString txt = doc->selectedText( QTextDocument::Temp );
4994 undoRedoInfo.d->text += txt;
4995 for ( int i = 0; i < (int)txt.length(); ++i ) {
4996 if ( txt[ i ] != '\n' && oldC.paragraph()->at( oldC.index() )->format() ) {
4997 oldC.paragraph()->at( oldC.index() )->format()->addRef();
4998 undoRedoInfo.d->text.
4999 setFormat( oldLen + i, oldC.paragraph()->at( oldC.index() )->format(), TRUE );
5000 }
5001 oldC.gotoNextLetter();
5002 }
5003 }
5004 undoRedoInfo.clear();
5005 removeSelection( QTextDocument::Temp );
5006 }
5007
5008 formatMore();
5009 setModified();
5010 emit textChanged();
5011 repaintChanged();
5012 ensureCursorVisible();
5013 return;
5014 }
5015 } else {
5016#if defined(Q_OS_WIN32) || defined (Q_OS_OS2)
5017 // Need to convert CRLF to LF
5018 t.replace( "\r\n", "\n" );
5019#elif defined(Q_OS_MAC)
5020 //need to convert CR to LF
5021 t.replace( '\r', '\n' );
5022#endif
5023 QChar *uc = (QChar *)t.unicode();
5024 for ( int i=0; (uint) i<t.length(); i++ ) {
5025 if ( uc[ i ] < ' ' && uc[ i ] != '\n' && uc[ i ] != '\t' )
5026 uc[ i ] = ' ';
5027 }
5028 if ( !t.isEmpty() )
5029 insert( t, FALSE, TRUE );
5030 }
5031#endif //QT_NO_MIME
5032}
5033
5034#ifndef QT_NO_MIMECLIPBOARD
5035/*!
5036 Prompts the user to choose a type from a list of text types
5037 available, then copies text from the clipboard (if there is any)
5038 into the text edit at the current text cursor position. Any
5039 selected text (in selection 0) is first deleted.
5040*/
5041void QTextEdit::pasteSpecial( const QPoint& pt )
5042{
5043 QCString st = pickSpecial( QApplication::clipboard()->data( d->clipboard_mode ),
5044 TRUE, pt );
5045 if ( !st.isEmpty() )
5046 pasteSubType( st );
5047}
5048#endif
5049#ifndef QT_NO_MIME
5050QCString QTextEdit::pickSpecial( QMimeSource* ms, bool always_ask, const QPoint& pt )
5051{
5052 if ( ms ) {
5053#ifndef QT_NO_POPUPMENU
5054 QPopupMenu popup( this, "qt_pickspecial_menu" );
5055 QString fmt;
5056 int n = 0;
5057 QDict<void> done;
5058 for (int i = 0; !( fmt = ms->format( i ) ).isNull(); i++) {
5059 int semi = fmt.find( ";" );
5060 if ( semi >= 0 )
5061 fmt = fmt.left( semi );
5062 if ( fmt.left( 5 ) == "text/" ) {
5063 fmt = fmt.mid( 5 );
5064 if ( !done.find( fmt ) ) {
5065 done.insert( fmt,(void*)1 );
5066 popup.insertItem( fmt, i );
5067 n++;
5068 }
5069 }
5070 }
5071 if ( n ) {
5072 int i = n ==1 && !always_ask ? popup.idAt( 0 ) : popup.exec( pt );
5073 if ( i >= 0 )
5074 return popup.text(i).latin1();
5075 }
5076#else
5077 QString fmt;
5078 for (int i = 0; !( fmt = ms->format( i ) ).isNull(); i++) {
5079 int semi = fmt.find( ";" );
5080 if ( semi >= 0 )
5081 fmt = fmt.left( semi );
5082 if ( fmt.left( 5 ) == "text/" ) {
5083 fmt = fmt.mid( 5 );
5084 return fmt.latin1();
5085 }
5086 }
5087#endif
5088 }
5089 return QCString();
5090}
5091#endif // QT_NO_MIME
5092#endif // QT_NO_CLIPBOARD
5093
5094/*!
5095 \enum QTextEdit::WordWrap
5096
5097 This enum defines the QTextEdit's word wrap modes.
5098
5099 \value NoWrap Do not wrap the text.
5100
5101 \value WidgetWidth Wrap the text at the current width of the
5102 widget (this is the default). Wrapping is at whitespace by
5103 default; this can be changed with setWrapPolicy().
5104
5105 \value FixedPixelWidth Wrap the text at a fixed number of pixels
5106 from the widget's left side. The number of pixels is set with
5107 wrapColumnOrWidth().
5108
5109 \value FixedColumnWidth Wrap the text at a fixed number of
5110 character columns from the widget's left side. The number of
5111 characters is set with wrapColumnOrWidth(). This is useful if you
5112 need formatted text that can also be displayed gracefully on
5113 devices with monospaced fonts, for example a standard VT100
5114 terminal, where you might set wrapColumnOrWidth() to 80.
5115
5116 \sa setWordWrap() wordWrap()
5117*/
5118
5119/*!
5120 \property QTextEdit::wordWrap
5121 \brief the word wrap mode
5122
5123 The default mode is \c WidgetWidth which causes words to be
5124 wrapped at the right edge of the text edit. Wrapping occurs at
5125 whitespace, keeping whole words intact. If you want wrapping to
5126 occur within words use setWrapPolicy(). If you set a wrap mode of
5127 \c FixedPixelWidth or \c FixedColumnWidth you should also call
5128 setWrapColumnOrWidth() with the width you want.
5129
5130 \sa WordWrap, wrapColumnOrWidth, wrapPolicy,
5131*/
5132
5133void QTextEdit::setWordWrap( WordWrap mode )
5134{
5135 if ( wrapMode == mode )
5136 return;
5137 wrapMode = mode;
5138 switch ( mode ) {
5139 case NoWrap:
5140 document()->formatter()->setWrapEnabled( FALSE );
5141 document()->formatter()->setWrapAtColumn( -1 );
5142 doc->setWidth( visibleWidth() );
5143 doc->setMinimumWidth( -1 );
5144 doc->invalidate();
5145 updateContents();
5146 lastFormatted = doc->firstParagraph();
5147 interval = 0;
5148 formatMore();
5149 break;
5150 case WidgetWidth:
5151 document()->formatter()->setWrapEnabled( TRUE );
5152 document()->formatter()->setWrapAtColumn( -1 );
5153 doResize();
5154 break;
5155 case FixedPixelWidth:
5156 document()->formatter()->setWrapEnabled( TRUE );
5157 document()->formatter()->setWrapAtColumn( -1 );
5158 if ( wrapWidth < 0 )
5159 wrapWidth = 200;
5160 setWrapColumnOrWidth( wrapWidth );
5161 break;
5162 case FixedColumnWidth:
5163 if ( wrapWidth < 0 )
5164 wrapWidth = 80;
5165 document()->formatter()->setWrapEnabled( TRUE );
5166 document()->formatter()->setWrapAtColumn( wrapWidth );
5167 setWrapColumnOrWidth( wrapWidth );
5168 break;
5169 }
5170#ifdef QT_TEXTEDIT_OPTIMIZATION
5171 checkOptimMode();
5172#endif
5173}
5174
5175QTextEdit::WordWrap QTextEdit::wordWrap() const
5176{
5177 return wrapMode;
5178}
5179
5180/*!
5181 \property QTextEdit::wrapColumnOrWidth
5182 \brief the position (in pixels or columns depending on the wrap mode) where text will be wrapped
5183
5184 If the wrap mode is \c FixedPixelWidth, the value is the number of
5185 pixels from the left edge of the text edit at which text should be
5186 wrapped. If the wrap mode is \c FixedColumnWidth, the value is the
5187 column number (in character columns) from the left edge of the
5188 text edit at which text should be wrapped.
5189
5190 \sa wordWrap
5191*/
5192void QTextEdit::setWrapColumnOrWidth( int value )
5193{
5194 wrapWidth = value;
5195 if ( wrapMode == FixedColumnWidth ) {
5196 document()->formatter()->setWrapAtColumn( wrapWidth );
5197 resizeContents( 0, 0 );
5198 doc->setWidth( visibleWidth() );
5199 doc->setMinimumWidth( -1 );
5200 } else if (wrapMode == FixedPixelWidth ) {
5201 document()->formatter()->setWrapAtColumn( -1 );
5202 resizeContents( wrapWidth, 0 );
5203 doc->setWidth( wrapWidth );
5204 doc->setMinimumWidth( wrapWidth );
5205 } else {
5206 return;
5207 }
5208 doc->invalidate();
5209 updateContents();
5210 lastFormatted = doc->firstParagraph();
5211 interval = 0;
5212 formatMore();
5213}
5214
5215int QTextEdit::wrapColumnOrWidth() const
5216{
5217 if ( wrapMode == WidgetWidth )
5218 return visibleWidth();
5219 return wrapWidth;
5220}
5221
5222
5223/*!
5224 \enum QTextEdit::WrapPolicy
5225
5226 This enum defines where text can be wrapped in word wrap mode.
5227
5228 \value AtWhiteSpace Don't use this deprecated value (it is a
5229 synonym for \c AtWordBoundary which you should use instead).
5230 \value Anywhere Break anywhere, including within words.
5231 \value AtWordBoundary Break lines at word boundaries, e.g. spaces or
5232 newlines
5233 \value AtWordOrDocumentBoundary Break lines at whitespace, e.g.
5234 spaces or newlines if possible. Break it anywhere otherwise.
5235
5236 \sa setWrapPolicy()
5237*/
5238
5239/*!
5240 \property QTextEdit::wrapPolicy
5241 \brief the word wrap policy, at whitespace or anywhere
5242
5243 Defines where text can be wrapped when word wrap mode is not \c
5244 NoWrap. The choices are \c AtWordBoundary (the default), \c
5245 Anywhere and \c AtWordOrDocumentBoundary
5246
5247 \sa wordWrap
5248*/
5249
5250void QTextEdit::setWrapPolicy( WrapPolicy policy )
5251{
5252 if ( wPolicy == policy )
5253 return;
5254 wPolicy = policy;
5255 QTextFormatter *formatter;
5256 if ( policy == AtWordBoundary || policy == AtWordOrDocumentBoundary ) {
5257 formatter = new QTextFormatterBreakWords;
5258 formatter->setAllowBreakInWords( policy == AtWordOrDocumentBoundary );
5259 } else {
5260 formatter = new QTextFormatterBreakInWords;
5261 }
5262 formatter->setWrapAtColumn( document()->formatter()->wrapAtColumn() );
5263 formatter->setWrapEnabled( document()->formatter()->isWrapEnabled( 0 ) );
5264 document()->setFormatter( formatter );
5265 doc->invalidate();
5266 updateContents();
5267 lastFormatted = doc->firstParagraph();
5268 interval = 0;
5269 formatMore();
5270}
5271
5272QTextEdit::WrapPolicy QTextEdit::wrapPolicy() const
5273{
5274 return wPolicy;
5275}
5276
5277/*!
5278 Deletes all the text in the text edit.
5279
5280 \sa cut() removeSelectedText() setText()
5281*/
5282
5283void QTextEdit::clear()
5284{
5285#ifdef QT_TEXTEDIT_OPTIMIZATION
5286 if ( d->optimMode ) {
5287 optimSetText("");
5288 } else
5289#endif
5290 {
5291 // make clear undoable
5292 doc->selectAll( QTextDocument::Temp );
5293 removeSelectedText( QTextDocument::Temp );
5294 setContentsPos( 0, 0 );
5295 if ( cursor->isValid() )
5296 cursor->restoreState();
5297 doc->clear( TRUE );
5298 delete cursor;
5299 cursor = new QTextCursor( doc );
5300 lastFormatted = 0;
5301 }
5302 updateContents();
5303
5304 emit cursorPositionChanged( cursor );
5305 emit cursorPositionChanged( cursor->paragraph()->paragId(), cursor->index() );
5306}
5307
5308int QTextEdit::undoDepth() const
5309{
5310 return document()->undoDepth();
5311}
5312
5313/*!
5314 \property QTextEdit::length
5315 \brief the number of characters in the text
5316*/
5317
5318int QTextEdit::length() const
5319{
5320#ifdef QT_TEXTEDIT_OPTIMIZATION
5321 if ( d->optimMode )
5322 return d->od->len;
5323 else
5324#endif
5325 return document()->length();
5326}
5327
5328/*!
5329 \property QTextEdit::tabStopWidth
5330 \brief the tab stop width in pixels
5331*/
5332
5333int QTextEdit::tabStopWidth() const
5334{
5335 return document()->tabStopWidth();
5336}
5337
5338void QTextEdit::setUndoDepth( int d )
5339{
5340 document()->setUndoDepth( d );
5341}
5342
5343void QTextEdit::setTabStopWidth( int ts )
5344{
5345 document()->setTabStops( ts );
5346 doc->invalidate();
5347 lastFormatted = doc->firstParagraph();
5348 interval = 0;
5349 formatMore();
5350 updateContents();
5351}
5352
5353/*!
5354 \reimp
5355*/
5356
5357QSize QTextEdit::sizeHint() const
5358{
5359 // cf. QScrollView::sizeHint()
5360 constPolish();
5361 int f = 2 * frameWidth();
5362 int h = fontMetrics().height();
5363 QSize sz( f, f );
5364 return sz.expandedTo( QSize(12 * h, 8 * h) );
5365}
5366
5367void QTextEdit::clearUndoRedo()
5368{
5369 if ( !undoEnabled )
5370 return;
5371 undoRedoInfo.clear();
5372 emit undoAvailable( doc->commands()->isUndoAvailable() );
5373 emit redoAvailable( doc->commands()->isRedoAvailable() );
5374}
5375
5376/*! \internal
5377 \warning In Qt 3.1 we will provide a cleaer API for the
5378 functionality which is provided by this function and in Qt 4.0 this
5379 function will go away.
5380
5381 This function gets the format of the character at position \a
5382 index in paragraph \a para. Sets \a font to the character's font, \a
5383 color to the character's color and \a verticalAlignment to the
5384 character's vertical alignment.
5385
5386 Returns FALSE if \a para or \a index is out of range otherwise
5387 returns TRUE.
5388*/
5389
5390bool QTextEdit::getFormat( int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment )
5391{
5392 if ( !font || !color )
5393 return FALSE;
5394 QTextParagraph *p = doc->paragAt( para );
5395 if ( !p )
5396 return FALSE;
5397 if ( index < 0 || index >= p->length() )
5398 return FALSE;
5399 *font = p->at( index )->format()->font();
5400 *color = p->at( index )->format()->color();
5401 *verticalAlignment = (VerticalAlignment)p->at( index )->format()->vAlign();
5402 return TRUE;
5403}
5404
5405/*! \internal
5406 \warning In Qt 3.1 we will provide a cleaer API for the
5407 functionality which is provided by this function and in Qt 4.0 this
5408 function will go away.
5409
5410 This function gets the format of the paragraph \a para. Sets \a
5411 font to the paragraphs's font, \a color to the paragraph's color, \a
5412 verticalAlignment to the paragraph's vertical alignment, \a
5413 alignment to the paragraph's alignment, \a displayMode to the
5414 paragraph's display mode, \a listStyle to the paragraph's list style
5415 (if the display mode is QStyleSheetItem::DisplayListItem) and \a
5416 listDepth to the depth of the list (if the display mode is
5417 QStyleSheetItem::DisplayListItem).
5418
5419 Returns FALSE if \a para is out of range otherwise returns TRUE.
5420*/
5421
5422bool QTextEdit::getParagraphFormat( int para, QFont *font, QColor *color,
5423 VerticalAlignment *verticalAlignment, int *alignment,
5424 QStyleSheetItem::DisplayMode *displayMode,
5425 QStyleSheetItem::ListStyle *listStyle,
5426 int *listDepth )
5427{
5428 if ( !font || !color || !alignment || !displayMode || !listStyle )
5429 return FALSE;
5430 QTextParagraph *p = doc->paragAt( para );
5431 if ( !p )
5432 return FALSE;
5433 *font = p->at(0)->format()->font();
5434 *color = p->at(0)->format()->color();
5435 *verticalAlignment = (VerticalAlignment)p->at(0)->format()->vAlign();
5436 *alignment = p->alignment();
5437 *displayMode = p->isListItem() ? QStyleSheetItem::DisplayListItem : QStyleSheetItem::DisplayBlock;
5438 *listStyle = p->listStyle();
5439 *listDepth = p->listDepth();
5440 return TRUE;
5441}
5442
5443
5444
5445/*!
5446 This function is called to create a right mouse button popup menu
5447 at the document position \a pos. If you want to create a custom
5448 popup menu, reimplement this function and return the created popup
5449 menu. Ownership of the popup menu is transferred to the caller.
5450*/
5451
5452QPopupMenu *QTextEdit::createPopupMenu( const QPoint& pos )
5453{
5454 Q_UNUSED( pos )
5455#ifndef QT_NO_POPUPMENU
5456 QPopupMenu *popup = new QPopupMenu( this, "qt_edit_menu" );
5457 if ( !isReadOnly() ) {
5458 d->id[ IdUndo ] = popup->insertItem( tr( "&Undo" ) + ACCEL_KEY( Z ) );
5459 d->id[ IdRedo ] = popup->insertItem( tr( "&Redo" ) + ACCEL_KEY( Y ) );
5460 popup->insertSeparator();
5461 }
5462#ifndef QT_NO_CLIPBOARD
5463 if ( !isReadOnly() )
5464 d->id[ IdCut ] = popup->insertItem( tr( "Cu&t" ) + ACCEL_KEY( X ) );
5465 d->id[ IdCopy ] = popup->insertItem( tr( "&Copy" ) + ACCEL_KEY( C ) );
5466 if ( !isReadOnly() )
5467 d->id[ IdPaste ] = popup->insertItem( tr( "&Paste" ) + ACCEL_KEY( V ) );
5468#endif
5469 if ( !isReadOnly() ) {
5470 d->id[ IdClear ] = popup->insertItem( tr( "Clear" ) );
5471 popup->insertSeparator();
5472 }
5473#if defined(Q_WS_X11)
5474 d->id[ IdSelectAll ] = popup->insertItem( tr( "Select All" ) );
5475#else
5476 d->id[ IdSelectAll ] = popup->insertItem( tr( "Select All" ) + ACCEL_KEY( A ) );
5477#endif
5478 popup->setItemEnabled( d->id[ IdUndo ], !isReadOnly() && doc->commands()->isUndoAvailable() );
5479 popup->setItemEnabled( d->id[ IdRedo ], !isReadOnly() && doc->commands()->isRedoAvailable() );
5480#ifndef QT_NO_CLIPBOARD
5481 popup->setItemEnabled( d->id[ IdCut ], !isReadOnly() && doc->hasSelection( QTextDocument::Standard, TRUE ) );
5482#ifdef QT_TEXTEDIT_OPTIMIZATION
5483 popup->setItemEnabled( d->id[ IdCopy ], d->optimMode ? optimHasSelection() : doc->hasSelection( QTextDocument::Standard, TRUE ) );
5484#else
5485 popup->setItemEnabled( d->id[ IdCopy ], doc->hasSelection( QTextDocument::Standard, TRUE ) );
5486#endif
5487 popup->setItemEnabled( d->id[ IdPaste ], !isReadOnly() && !QApplication::clipboard()->text( d->clipboard_mode ).isEmpty() );
5488#endif
5489 const bool isEmptyDocument = (length() == 0);
5490 popup->setItemEnabled( d->id[ IdClear ], !isReadOnly() && !isEmptyDocument );
5491 popup->setItemEnabled( d->id[ IdSelectAll ], !isEmptyDocument );
5492 return popup;
5493#else
5494 return 0;
5495#endif
5496}
5497
5498/*! \overload
5499 \obsolete
5500 This function is called to create a right mouse button popup menu.
5501 If you want to create a custom popup menu, reimplement this function
5502 and return the created popup menu. Ownership of the popup menu is
5503 transferred to the caller.
5504
5505 This function is only called if createPopupMenu( const QPoint & )
5506 returns 0.
5507*/
5508
5509QPopupMenu *QTextEdit::createPopupMenu()
5510{
5511 return 0;
5512}
5513
5514/*!
5515 \reimp
5516*/
5517
5518void QTextEdit::setFont( const QFont &f )
5519{
5520#ifdef QT_TEXTEDIT_OPTIMIZATION
5521 if ( d->optimMode ) {
5522 QScrollView::setFont( f );
5523 doc->setDefaultFormat( f, doc->formatCollection()->defaultFormat()->color() );
5524 // recalculate the max string width
5525 QFontMetrics fm(f);
5526 int i, sw;
5527 d->od->maxLineWidth = 0;
5528 for ( i = 0; i < d->od->numLines; i++ ) {
5529 sw = fm.width(d->od->lines[LOGOFFSET(i)]);
5530 if (d->od->maxLineWidth < sw)
5531 d->od->maxLineWidth = sw;
5532 }
5533 resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
5534 return;
5535 }
5536#endif
5537 QScrollView::setFont( f );
5538 doc->setMinimumWidth( -1 );
5539 doc->setDefaultFormat( f, doc->formatCollection()->defaultFormat()->color() );
5540 lastFormatted = doc->firstParagraph();
5541 formatMore();
5542 repaintChanged();
5543}
5544
5545/*!
5546 \fn QTextEdit::zoomIn()
5547
5548 \overload
5549
5550 Zooms in on the text by by making the base font size one point
5551 larger and recalculating all font sizes to be the new size. This
5552 does not change the size of any images.
5553
5554 \sa zoomOut()
5555*/
5556
5557/*!
5558 \fn QTextEdit::zoomOut()
5559
5560 \overload
5561
5562 Zooms out on the text by by making the base font size one point
5563 smaller and recalculating all font sizes to be the new size. This
5564 does not change the size of any images.
5565
5566 \sa zoomIn()
5567*/
5568
5569
5570/*!
5571 Zooms in on the text by by making the base font size \a range
5572 points larger and recalculating all font sizes to be the new size.
5573 This does not change the size of any images.
5574
5575 \sa zoomOut()
5576*/
5577
5578void QTextEdit::zoomIn( int range )
5579{
5580 QFont f( QScrollView::font() );
5581 f.setPointSize( QFontInfo(f).pointSize() + range );
5582 setFont( f );
5583}
5584
5585/*!
5586 Zooms out on the text by making the base font size \a range points
5587 smaller and recalculating all font sizes to be the new size. This
5588 does not change the size of any images.
5589
5590 \sa zoomIn()
5591*/
5592
5593void QTextEdit::zoomOut( int range )
5594{
5595 QFont f( QScrollView::font() );
5596 f.setPointSize( QMAX( 1, QFontInfo(f).pointSize() - range ) );
5597 setFont( f );
5598}
5599
5600/*!
5601 Zooms the text by making the base font size \a size points and
5602 recalculating all font sizes to be the new size. This does not
5603 change the size of any images.
5604*/
5605
5606void QTextEdit::zoomTo( int size )
5607{
5608 QFont f( QScrollView::font() );
5609 f.setPointSize( size );
5610 setFont( f );
5611}
5612
5613/*!
5614 QTextEdit is optimized for large amounts text. One of its
5615 optimizations is to format only the visible text, formatting the rest
5616 on demand, e.g. as the user scrolls, so you don't usually need to
5617 call this function.
5618
5619 In some situations you may want to force the whole text
5620 to be formatted. For example, if after calling setText(), you wanted
5621 to know the height of the document (using contentsHeight()), you
5622 would call this function first.
5623*/
5624
5625void QTextEdit::sync()
5626{
5627#ifdef QT_TEXTEDIT_OPTIMIZATION
5628 if ( d->optimMode ) {
5629 QFontMetrics fm( QScrollView::font() );
5630 resizeContents( d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1 );
5631 } else
5632#endif
5633 {
5634 while ( lastFormatted ) {
5635 lastFormatted->format();
5636 lastFormatted = lastFormatted->next();
5637 }
5638 resizeContents( contentsWidth(), doc->height() );
5639 }
5640 updateScrollBars();
5641}
5642
5643/*!
5644 \reimp
5645*/
5646
5647void QTextEdit::setEnabled( bool b )
5648{
5649 QScrollView::setEnabled( b );
5650 if ( textFormat() == PlainText ) {
5651 QTextFormat *f = doc->formatCollection()->defaultFormat();
5652 f->setColor( colorGroup().text() );
5653 updateContents();
5654 }
5655}
5656
5657/*!
5658 Sets the background color of selection number \a selNum to \a back
5659 and specifies whether the text of this selection should be
5660 inverted with \a invertText.
5661
5662 This only works for \a selNum > 0. The default selection (\a
5663 selNum == 0) gets its attributes from the text edit's
5664 colorGroup().
5665*/
5666
5667void QTextEdit::setSelectionAttributes( int selNum, const QColor &back, bool invertText )
5668{
5669 if ( selNum < 1 )
5670 return;
5671 if ( selNum > doc->numSelections() )
5672 doc->addSelection( selNum );
5673 doc->setSelectionColor( selNum, back );
5674 doc->setInvertSelectionText( selNum, invertText );
5675}
5676
5677/*!
5678 \reimp
5679*/
5680void QTextEdit::windowActivationChange( bool oldActive )
5681{
5682 if ( oldActive && scrollTimer )
5683 scrollTimer->stop();
5684 if ( palette().active() != palette().inactive() )
5685 updateContents();
5686 QScrollView::windowActivationChange( oldActive );
5687}
5688
5689void QTextEdit::setReadOnly( bool b )
5690{
5691 if ( readonly == b )
5692 return;
5693 readonly = b;
5694#ifndef QT_NO_CURSOR
5695 if ( readonly )
5696 viewport()->setCursor( arrowCursor );
5697 else
5698 viewport()->setCursor( ibeamCursor );
5699 setInputMethodEnabled( !readonly );
5700#endif
5701#ifdef QT_TEXTEDIT_OPTIMIZATION
5702 checkOptimMode();
5703#endif
5704}
5705
5706/*!
5707 Scrolls to the bottom of the document and does formatting if
5708 required.
5709*/
5710
5711void QTextEdit::scrollToBottom()
5712{
5713 sync();
5714 setContentsPos( contentsX(), contentsHeight() - visibleHeight() );
5715}
5716
5717/*!
5718 Returns the rectangle of the paragraph \a para in contents
5719 coordinates, or an invalid rectangle if \a para is out of range.
5720*/
5721
5722QRect QTextEdit::paragraphRect( int para ) const
5723{
5724 QTextEdit *that = (QTextEdit *)this;
5725 that->sync();
5726 QTextParagraph *p = doc->paragAt( para );
5727 if ( !p )
5728 return QRect( -1, -1, -1, -1 );
5729 return p->rect();
5730}
5731
5732/*!
5733 Returns the paragraph which is at position \a pos (in contents
5734 coordinates).
5735*/
5736
5737int QTextEdit::paragraphAt( const QPoint &pos ) const
5738{
5739#ifdef QT_TEXTEDIT_OPTIMIZATION
5740 if ( d->optimMode ) {
5741 QFontMetrics fm( QScrollView::font() );
5742 int parag = pos.y() / fm.lineSpacing();
5743 if ( parag <= d->od->numLines )
5744 return parag;
5745 else
5746 return 0;
5747 }
5748#endif
5749 QTextCursor c( doc );
5750 c.place( pos, doc->firstParagraph() );
5751 if ( c.paragraph() )
5752 return c.paragraph()->paragId();
5753 return -1; // should never happen..
5754}
5755
5756/*!
5757 Returns the index of the character (relative to its paragraph) at
5758 position \a pos (in contents coordinates). If \a para is not 0,
5759 \a *para is set to the character's paragraph.
5760*/
5761
5762int QTextEdit::charAt( const QPoint &pos, int *para ) const
5763{
5764#ifdef QT_TEXTEDIT_OPTIMIZATION
5765 if ( d->optimMode ) {
5766 int par = paragraphAt( pos );
5767 if ( para )
5768 *para = par;
5769 return optimCharIndex( d->od->lines[ LOGOFFSET(par) ], pos.x() );
5770 }
5771#endif
5772 QTextCursor c( doc );
5773 c.place( pos, doc->firstParagraph() );
5774 if ( c.paragraph() ) {
5775 if ( para )
5776 *para = c.paragraph()->paragId();
5777 return c.index();
5778 }
5779 return -1; // should never happen..
5780}
5781
5782/*!
5783 Sets the background color of the paragraph \a para to \a bg.
5784*/
5785
5786void QTextEdit::setParagraphBackgroundColor( int para, const QColor &bg )
5787{
5788 QTextParagraph *p = doc->paragAt( para );
5789 if ( !p )
5790 return;
5791 p->setBackgroundColor( bg );
5792 repaintChanged();
5793}
5794
5795/*!
5796 Clears the background color of the paragraph \a para, so that the
5797 default color is used again.
5798*/
5799
5800void QTextEdit::clearParagraphBackground( int para )
5801{
5802 QTextParagraph *p = doc->paragAt( para );
5803 if ( !p )
5804 return;
5805 p->clearBackgroundColor();
5806 repaintChanged();
5807}
5808
5809/*!
5810 Returns the background color of the paragraph \a para or an
5811 invalid color if \a para is out of range or the paragraph has no
5812 background set
5813*/
5814
5815QColor QTextEdit::paragraphBackgroundColor( int para ) const
5816{
5817 QTextParagraph *p = doc->paragAt( para );
5818 if ( !p )
5819 return QColor();
5820 QColor *c = p->backgroundColor();
5821 if ( c )
5822 return *c;
5823 return QColor();
5824}
5825
5826/*!
5827 \property QTextEdit::undoRedoEnabled
5828 \brief whether undo/redo is enabled
5829
5830 When changing this property, the undo/redo history is cleared.
5831
5832 The default is TRUE.
5833*/
5834
5835void QTextEdit::setUndoRedoEnabled( bool b )
5836{
5837 undoRedoInfo.clear();
5838 doc->commands()->clear();
5839
5840 undoEnabled = b;
5841}
5842
5843bool QTextEdit::isUndoRedoEnabled() const
5844{
5845 return undoEnabled;
5846}
5847
5848/*!
5849 Returns TRUE if undo is available; otherwise returns FALSE.
5850*/
5851
5852bool QTextEdit::isUndoAvailable() const
5853{
5854 return undoEnabled && (doc->commands()->isUndoAvailable() || undoRedoInfo.valid());
5855}
5856
5857/*!
5858 Returns TRUE if redo is available; otherwise returns FALSE.
5859*/
5860
5861bool QTextEdit::isRedoAvailable() const
5862{
5863 return undoEnabled && doc->commands()->isRedoAvailable();
5864}
5865
5866void QTextEdit::ensureFormatted( QTextParagraph *p )
5867{
5868 while ( !p->isValid() ) {
5869 if ( !lastFormatted )
5870 return;
5871 formatMore();
5872 }
5873}
5874
5875/*! \internal */
5876void QTextEdit::updateCursor( const QPoint & pos )
5877{
5878 if ( isReadOnly() && linksEnabled() ) {
5879 QTextCursor c = *cursor;
5880 placeCursor( pos, &c, TRUE );
5881
5882#ifndef QT_NO_NETWORKPROTOCOL
5883 bool insideParagRect = TRUE;
5884 if (c.paragraph() == doc->lastParagraph()
5885 && c.paragraph()->rect().y() + c.paragraph()->rect().height() < pos.y())
5886 insideParagRect = FALSE;
5887 if (insideParagRect && c.paragraph() && c.paragraph()->at( c.index() ) &&
5888 c.paragraph()->at( c.index() )->isAnchor()) {
5889 if (!c.paragraph()->at( c.index() )->anchorHref().isEmpty()
5890 && c.index() < c.paragraph()->length() - 1 )
5891 onLink = c.paragraph()->at( c.index() )->anchorHref();
5892 else
5893 onLink = QString::null;
5894
5895 if (!c.paragraph()->at( c.index() )->anchorName().isEmpty()
5896 && c.index() < c.paragraph()->length() - 1 )
5897 d->onName = c.paragraph()->at( c.index() )->anchorName();
5898 else
5899 d->onName = QString::null;
5900
5901 if (!c.paragraph()->at( c.index() )->anchorHref().isEmpty() ) {
5902#ifndef QT_NO_CURSOR
5903 viewport()->setCursor( onLink.isEmpty() ? arrowCursor : pointingHandCursor );
5904#endif
5905 QUrl u( doc->context(), onLink, TRUE );
5906 emitHighlighted( u.toString( FALSE, FALSE ) );
5907 }
5908 } else {
5909#ifndef QT_NO_CURSOR
5910 viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor );
5911#endif
5912 onLink = QString::null;
5913 emitHighlighted( QString::null );
5914 }
5915#endif
5916 }
5917}
5918
5919/*!
5920 Places the cursor \a c at the character which is closest to position
5921 \a pos (in contents coordinates). If \a c is 0, the default text
5922 cursor is used.
5923
5924 \sa setCursorPosition()
5925*/
5926void QTextEdit::placeCursor( const QPoint &pos, QTextCursor *c )
5927{
5928 placeCursor( pos, c, FALSE );
5929}
5930
5931/*! \internal */
5932void QTextEdit::clipboardChanged()
5933{
5934#ifndef QT_NO_CLIPBOARD
5935 // don't listen to selection changes
5936 disconnect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
5937#endif
5938 selectAll(FALSE);
5939}
5940
5941/*! \property QTextEdit::tabChangesFocus
5942 \brief whether TAB changes focus or is accepted as input
5943
5944 In some occasions text edits should not allow the user to input
5945 tabulators or change indentation using the TAB key, as this breaks
5946 the focus chain. The default is FALSE.
5947
5948*/
5949
5950void QTextEdit::setTabChangesFocus( bool b )
5951{
5952 d->tabChangesFocus = b;
5953}
5954
5955bool QTextEdit::tabChangesFocus() const
5956{
5957 return d->tabChangesFocus;
5958}
5959
5960#ifdef QT_TEXTEDIT_OPTIMIZATION
5961/* Implementation of optimized LogText mode follows */
5962
5963static void qSwap( int * a, int * b )
5964{
5965 if ( !a || !b )
5966 return;
5967 int tmp = *a;
5968 *a = *b;
5969 *b = tmp;
5970}
5971
5972/*! \internal */
5973bool QTextEdit::checkOptimMode()
5974{
5975 bool oldMode = d->optimMode;
5976 if ( textFormat() == LogText ) {
5977 setReadOnly( TRUE );
5978 d->optimMode = TRUE;
5979 } else {
5980 d->optimMode = FALSE;
5981 }
5982
5983 // when changing mode - try to keep selections and text
5984 if ( oldMode != d->optimMode ) {
5985 if ( d->optimMode ) {
5986 d->od = new QTextEditOptimPrivate;
5987 connect( scrollTimer, SIGNAL( timeout() ), this, SLOT( optimDoAutoScroll() ) );
5988 disconnect( doc, SIGNAL( minimumWidthChanged(int) ), this, SLOT( documentWidthChanged(int) ) );
5989 disconnect( scrollTimer, SIGNAL( timeout() ), this, SLOT( autoScrollTimerDone() ) );
5990 disconnect( formatTimer, SIGNAL( timeout() ), this, SLOT( formatMore() ) );
5991 optimSetText( doc->originalText() );
5992 doc->clear(TRUE);
5993 delete cursor;
5994 cursor = new QTextCursor( doc );
5995 } else {
5996 disconnect( scrollTimer, SIGNAL( timeout() ), this, SLOT( optimDoAutoScroll() ) );
5997 connect( doc, SIGNAL( minimumWidthChanged(int) ), this, SLOT( documentWidthChanged(int) ) );
5998 connect( scrollTimer, SIGNAL( timeout() ), this, SLOT( autoScrollTimerDone() ) );
5999 connect( formatTimer, SIGNAL( timeout() ), this, SLOT( formatMore() ) );
6000 setText( optimText() );
6001 delete d->od;
6002 d->od = 0;
6003 }
6004 }
6005 return d->optimMode;
6006}
6007
6008/*! \internal */
6009QString QTextEdit::optimText() const
6010{
6011 QString str, tmp;
6012
6013 if ( d->od->len == 0 )
6014 return str;
6015
6016 // concatenate all strings
6017 int i;
6018 int offset;
6019 QMapConstIterator<int,QTextEditOptimPrivate::Tag *> it;
6020 QTextEditOptimPrivate::Tag * ftag = 0;
6021 for ( i = 0; i < d->od->numLines; i++ ) {
6022 if ( d->od->lines[ LOGOFFSET(i) ].isEmpty() ) { // CR lines are empty
6023 str += "\n";
6024 } else {
6025 tmp = d->od->lines[ LOGOFFSET(i) ] + "\n";
6026 // inject the tags for this line
6027 if ( (it = d->od->tagIndex.find( LOGOFFSET(i) )) != d->od->tagIndex.end() )
6028 ftag = it.data();
6029 offset = 0;
6030 while ( ftag && ftag->line == i ) {
6031 tmp.insert( ftag->index + offset, "<" + ftag->tag + ">" );
6032 offset += ftag->tag.length() + 2; // 2 -> the '<' and '>' chars
6033 ftag = ftag->next;
6034 }
6035 str += tmp;
6036 }
6037 }
6038 return str;
6039}
6040
6041/*! \internal */
6042void QTextEdit::optimSetText( const QString &str )
6043{
6044 optimRemoveSelection();
6045// this is just too slow - but may have to go in due to compatibility reasons
6046// if ( str == optimText() )
6047// return;
6048 d->od->numLines = 0;
6049 d->od->lines.clear();
6050 d->od->maxLineWidth = 0;
6051 d->od->len = 0;
6052 d->od->clearTags();
6053 QFontMetrics fm( QScrollView::font() );
6054 if ( !(str.isEmpty() || str.isNull() || d->maxLogLines == 0) ) {
6055 QStringList strl = QStringList::split( '\n', str, TRUE );
6056 int lWidth = 0;
6057 for ( QStringList::Iterator it = strl.begin(); it != strl.end(); ++it ) {
6058 optimParseTags( &*it );
6059 optimCheckLimit( *it );
6060 lWidth = fm.width( *it );
6061 if ( lWidth > d->od->maxLineWidth )
6062 d->od->maxLineWidth = lWidth;
6063 }
6064 }
6065 resizeContents( d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1 );
6066 repaintContents();
6067 emit textChanged();
6068}
6069
6070/*! \internal
6071
6072 Append \a tag to the tag list.
6073*/
6074QTextEditOptimPrivate::Tag * QTextEdit::optimAppendTag( int index, const QString & tag )
6075{
6076 QTextEditOptimPrivate::Tag * t = new QTextEditOptimPrivate::Tag, * tmp;
6077
6078 if ( d->od->tags == 0 )
6079 d->od->tags = t;
6080 t->bold = t->italic = t->underline = FALSE;
6081 t->line = d->od->numLines;
6082 t->index = index;
6083 t->tag = tag;
6084 t->leftTag = 0;
6085 t->parent = 0;
6086 t->prev = d->od->lastTag;
6087 if ( d->od->lastTag )
6088 d->od->lastTag->next = t;
6089 t->next = 0;
6090 d->od->lastTag = t;
6091 tmp = d->od->tagIndex[ LOGOFFSET(t->line) ];
6092 if ( !tmp || (tmp && tmp->index > t->index) ) {
6093 d->od->tagIndex.replace( LOGOFFSET(t->line), t );
6094 }
6095 return t;
6096}
6097
6098 /*! \internal
6099
6100 Insert \a tag in the tag - according to line and index numbers
6101*/
6102
6103QTextEditOptimPrivate::Tag *QTextEdit::optimInsertTag(int line, int index, const QString &tag)
6104{
6105 QTextEditOptimPrivate::Tag *t = new QTextEditOptimPrivate::Tag, *tmp;
6106
6107 if (d->od->tags == 0)
6108 d->od->tags = t;
6109 t->bold = t->italic = t->underline = FALSE;
6110 t->line = line;
6111 t->index = index;
6112 t->tag = tag;
6113 t->leftTag = 0;
6114 t->parent = 0;
6115 t->next = 0;
6116 t->prev = 0;
6117
6118 // find insertion pt. in tag struct.
6119 QMap<int,QTextEditOptimPrivate::Tag *>::ConstIterator it;
6120 if ((it = d->od->tagIndex.find(LOGOFFSET(line))) != d->od->tagIndex.end()) {
6121 tmp = *it;
6122 if (tmp->index >= index) { // the exisiting tag may be placed AFTER the one we want to insert
6123 tmp = tmp->prev;
6124 } else {
6125 while (tmp && tmp->next && tmp->next->line == line && tmp->next->index <= index)
6126 tmp = tmp->next;
6127 }
6128 } else {
6129 tmp = d->od->tags;
6130 while (tmp && tmp->next && tmp->next->line < line)
6131 tmp = tmp->next;
6132 if (tmp == d->od->tags)
6133 tmp = 0;
6134 }
6135
6136 t->prev = tmp;
6137 t->next = tmp ? tmp->next : 0;
6138 if (t->next)
6139 t->next->prev = t;
6140 if (tmp)
6141 tmp->next = t;
6142
6143 tmp = d->od->tagIndex[LOGOFFSET(t->line)];
6144 if (!tmp || (tmp && tmp->index >= t->index)) {
6145 d->od->tagIndex.replace(LOGOFFSET(t->line), t);
6146 }
6147 return t;
6148}
6149
6150
6151/*! \internal
6152
6153 Find tags in \a line, remove them from \a line and put them in a
6154 structure.
6155
6156 A tag is delimited by '<' and '>'. The characters '<', '>' and '&'
6157 are escaped by using '&lt;', '&gt;' and '&amp;'. Left-tags marks
6158 the starting point for formatting, while right-tags mark the ending
6159 point. A right-tag is the same as a left-tag, but with a '/'
6160 appearing before the tag keyword. E.g a valid left-tag: <b>, and
6161 a valid right-tag: </b>. Tags can be nested, but they have to be
6162 closed in the same order as they are opened. E.g:
6163 <font color=red><font color=blue>blue</font>red</font> - is valid, while:
6164 <font color=red><b>bold red</font> just bold</b> - is invalid since the font tag is
6165 closed before the bold tag. Note that a tag does not have to be
6166 closed: '<font color=blue>Lots of text - and then some..' is perfectly valid for
6167 setting all text appearing after the tag to blue. A tag can be used
6168 to change the color of a piece of text, or set one of the following
6169 formatting attributes: bold, italic and underline. These attributes
6170 are set using the <b>, <i> and <u> tags. Example of valid tags:
6171 <font color=red>, </font>, <b>, <u>, <i>, </i>.
6172 Example of valid text:
6173 This is some <font color=red>red text</font>, while this is some <font color=green>green
6174 text</font>. <font color=blue><font color=yellow>This is yellow</font>, while this is
6175 blue.</font>
6176
6177 Note that only the color attribute of the HTML font tag is supported.
6178
6179 Limitations:
6180 1. A tag cannot span several lines.
6181 2. Very limited error checking - mismatching left/right-tags is the
6182 only thing that is detected.
6183
6184*/
6185void QTextEdit::optimParseTags( QString * line, int lineNo, int indexOffset )
6186{
6187 int len = line->length();
6188 int i, startIndex = -1, endIndex = -1, escIndex = -1;
6189 int state = 0; // 0 = outside tag, 1 = inside tag
6190 bool tagOpen, tagClose;
6191 int bold = 0, italic = 0, underline = 0;
6192 QString tagStr;
6193 QPtrStack<QTextEditOptimPrivate::Tag> tagStack;
6194
6195 for ( i = 0; i < len; i++ ) {
6196 tagOpen = (*line)[i] == '<';
6197 tagClose = (*line)[i] == '>';
6198
6199 // handle '&lt;' and '&gt;' and '&amp;'
6200 if ( (*line)[i] == '&' ) {
6201 escIndex = i;
6202 continue;
6203 } else if ( escIndex != -1 && (*line)[i] == ';' ) {
6204 QString esc = line->mid( escIndex, i - escIndex + 1 );
6205 QString c;
6206 if ( esc == "&lt;" )
6207 c = '<';
6208 else if ( esc == "&gt;" )
6209 c = '>';
6210 else if ( esc == "&amp;" )
6211 c = '&';
6212 line->replace( escIndex, i - escIndex + 1, c );
6213 len = line->length();
6214 i -= i-escIndex;
6215 escIndex = -1;
6216 continue;
6217 }
6218
6219 if ( state == 0 && tagOpen ) {
6220 state = 1;
6221 startIndex = i;
6222 continue;
6223 }
6224 if ( state == 1 && tagClose ) {
6225 state = 0;
6226 endIndex = i;
6227 if ( !tagStr.isEmpty() ) {
6228 QTextEditOptimPrivate::Tag * tag, * cur, * tmp;
6229 bool format = TRUE;
6230
6231 if ( tagStr == "b" )
6232 bold++;
6233 else if ( tagStr == "/b" )
6234 bold--;
6235 else if ( tagStr == "i" )
6236 italic++;
6237 else if ( tagStr == "/i" )
6238 italic--;
6239 else if ( tagStr == "u" )
6240 underline++;
6241 else if ( tagStr == "/u" )
6242 underline--;
6243 else
6244 format = FALSE;
6245 if ( lineNo > -1 )
6246 tag = optimInsertTag( lineNo, startIndex + indexOffset, tagStr );
6247 else
6248 tag = optimAppendTag( startIndex, tagStr );
6249 // everything that is not a b, u or i tag is considered
6250 // to be a color tag.
6251 tag->type = format ? QTextEditOptimPrivate::Format
6252 : QTextEditOptimPrivate::Color;
6253 if ( tagStr[0] == '/' ) {
6254 // this is a right-tag - search for the left-tag
6255 // and possible parent tag
6256 cur = tag->prev;
6257 if ( !cur ) {
6258#ifdef QT_CHECK_RANGE
6259 qWarning( "QTextEdit::optimParseTags: no left-tag for '<" + tag->tag + ">' in line %d.", tag->line + 1 );
6260#endif
6261 return; // something is wrong - give up
6262 }
6263 while ( cur ) {
6264 if ( cur->leftTag ) { // push right-tags encountered
6265 tagStack.push( cur );
6266 } else {
6267 tmp = tagStack.pop();
6268 if ( !tmp ) {
6269 if ( (("/" + cur->tag) == tag->tag) ||
6270 (tag->tag == "/font" && cur->tag.left(4) == "font") ) {
6271 // set up the left and parent of this tag
6272 tag->leftTag = cur;
6273 tmp = cur->prev;
6274 if ( tmp && tmp->parent ) {
6275 tag->parent = tmp->parent;
6276 } else if ( tmp && !tmp->leftTag ) {
6277 tag->parent = tmp;
6278 }
6279 break;
6280 } else if ( !cur->leftTag ) {
6281#ifdef QT_CHECK_RANGE
6282 qWarning( "QTextEdit::optimParseTags: mismatching %s-tag for '<" + cur->tag + ">' in line %d.", cur->tag[0] == '/' ? "left" : "right", cur->line + 1 );
6283#endif
6284 return; // something is amiss - give up
6285 }
6286 }
6287 }
6288 cur = cur->prev;
6289 }
6290 } else {
6291 tag->bold = bold > 0;
6292 tag->italic = italic > 0;
6293 tag->underline = underline > 0;
6294 tmp = tag->prev;
6295 while ( tmp && tmp->leftTag ) {
6296 tmp = tmp->leftTag->parent;
6297 }
6298 if ( tmp ) {
6299 tag->bold |= tmp->bold;
6300 tag->italic |= tmp->italic;
6301 tag->underline |= tmp->underline;
6302 }
6303 }
6304 }
6305 if ( startIndex != -1 ) {
6306 int l = (endIndex == -1) ?
6307 line->length() - startIndex : endIndex - startIndex;
6308 line->remove( startIndex, l+1 );
6309 len = line->length();
6310 i -= l+1;
6311 }
6312 tagStr = "";
6313 continue;
6314 }
6315
6316 if ( state == 1 ) {
6317 tagStr += (*line)[i];
6318 }
6319 }
6320}
6321
6322// calculate the width of a string in pixels inc. tabs
6323static int qStrWidth(const QString& str, int tabWidth, const QFontMetrics& fm)
6324{
6325 int tabs = str.contains('\t');
6326
6327 if (!tabs)
6328 return fm.width(str);
6329
6330 int newIdx = 0;
6331 int lastIdx = 0;
6332 int strWidth = 0;
6333 int tn;
6334 for (tn = 1; tn <= tabs; ++tn) {
6335 newIdx = str.find('\t', newIdx);
6336 strWidth += fm.width(str.mid(lastIdx, newIdx - lastIdx));
6337 if (strWidth >= tn * tabWidth) {
6338 int u = tn;
6339 while (strWidth >= u * tabWidth)
6340 ++u;
6341 strWidth = u * tabWidth;
6342 } else {
6343 strWidth = tn * tabWidth;
6344 }
6345 lastIdx = ++newIdx;
6346 }
6347 if ((int)str.length() > newIdx)
6348 strWidth += fm.width(str.mid(newIdx));
6349 return strWidth;
6350}
6351
6352bool QTextEdit::optimHasBoldMetrics(int line)
6353{
6354 QTextEditOptimPrivate::Tag *t;
6355 QMapConstIterator<int,QTextEditOptimPrivate::Tag *> it;
6356 if ((it = d->od->tagIndex.find(line)) != d->od->tagIndex.end()) {
6357 t = *it;
6358 while (t && t->line == line) {
6359 if (t->bold)
6360 return TRUE;
6361 t = t->next;
6362 }
6363 } else if ((t = optimPreviousLeftTag(line)) && t->bold) {
6364 return TRUE;
6365 }
6366 return FALSE;
6367}
6368
6369/*! \internal
6370
6371 Append \a str to the current text buffer. Parses each line to find
6372 formatting tags.
6373*/
6374void QTextEdit::optimAppend( const QString &str )
6375{
6376 if ( str.isEmpty() || str.isNull() || d->maxLogLines == 0 )
6377 return;
6378
6379 QStringList strl = QStringList::split( '\n', str, TRUE );
6380 QStringList::Iterator it = strl.begin();
6381
6382 QFontMetrics fm(QScrollView::font());
6383 int lWidth = 0;
6384
6385 for ( ; it != strl.end(); ++it ) {
6386 optimParseTags( &*it );
6387 optimCheckLimit( *it );
6388 if (optimHasBoldMetrics(d->od->numLines-1)) {
6389 QFont fnt = QScrollView::font();
6390 fnt.setBold(TRUE);
6391 fm = QFontMetrics(fnt);
6392 }
6393 lWidth = qStrWidth(*it, tabStopWidth(), fm) + 4;
6394 if ( lWidth > d->od->maxLineWidth )
6395 d->od->maxLineWidth = lWidth;
6396 }
6397 bool scrollToEnd = contentsY() >= contentsHeight() - visibleHeight();
6398 resizeContents( d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1 );
6399 if ( scrollToEnd ) {
6400 updateScrollBars();
6401 ensureVisible( contentsX(), contentsHeight(), 0, 0 );
6402 }
6403 // when a max log size is set, the text may not be redrawn because
6404 // the size of the viewport may not have changed
6405 if ( d->maxLogLines > -1 )
6406 viewport()->update();
6407 emit textChanged();
6408}
6409
6410
6411static void qStripTags(QString *line)
6412{
6413 int len = line->length();
6414 int i, startIndex = -1, endIndex = -1, escIndex = -1;
6415 int state = 0; // 0 = outside tag, 1 = inside tag
6416 bool tagOpen, tagClose;
6417
6418 for ( i = 0; i < len; i++ ) {
6419 tagOpen = (*line)[i] == '<';
6420 tagClose = (*line)[i] == '>';
6421
6422 // handle '&lt;' and '&gt;' and '&amp;'
6423 if ( (*line)[i] == '&' ) {
6424 escIndex = i;
6425 continue;
6426 } else if ( escIndex != -1 && (*line)[i] == ';' ) {
6427 QString esc = line->mid( escIndex, i - escIndex + 1 );
6428 QString c;
6429 if ( esc == "&lt;" )
6430 c = '<';
6431 else if ( esc == "&gt;" )
6432 c = '>';
6433 else if ( esc == "&amp;" )
6434 c = '&';
6435 line->replace( escIndex, i - escIndex + 1, c );
6436 len = line->length();
6437 i -= i-escIndex;
6438 escIndex = -1;
6439 continue;
6440 }
6441
6442 if ( state == 0 && tagOpen ) {
6443 state = 1;
6444 startIndex = i;
6445 continue;
6446 }
6447 if ( state == 1 && tagClose ) {
6448 state = 0;
6449 endIndex = i;
6450 if ( startIndex != -1 ) {
6451 int l = (endIndex == -1) ?
6452 line->length() - startIndex : endIndex - startIndex;
6453 line->remove( startIndex, l+1 );
6454 len = line->length();
6455 i -= l+1;
6456 }
6457 continue;
6458 }
6459 }
6460}
6461
6462/*! \internal
6463
6464 Inserts the text into \a line at index \a index.
6465*/
6466
6467void QTextEdit::optimInsert(const QString& text, int line, int index)
6468{
6469 if (text.isEmpty() || d->maxLogLines == 0)
6470 return;
6471 if (line < 0)
6472 line = 0;
6473 if (line > d->od->numLines-1)
6474 line = d->od->numLines-1;
6475 if (index < 0)
6476 index = 0;
6477 if (index > (int) d->od->lines[line].length())
6478 index = d->od->lines[line].length();
6479
6480 QStringList strl = QStringList::split('\n', text, TRUE);
6481 int numNewLines = strl.count() - 1;
6482 QTextEditOptimPrivate::Tag *tag = 0;
6483 QMap<int,QTextEditOptimPrivate::Tag *>::ConstIterator ii;
6484 int x;
6485
6486 if (numNewLines == 0) {
6487 // Case 1. Fast single line case - just inject it!
6488 QString stripped = text;
6489 qStripTags( &stripped );
6490 d->od->lines[LOGOFFSET(line)].insert(index, stripped);
6491 // move the tag indices following the insertion pt.
6492 if ((ii = d->od->tagIndex.find(LOGOFFSET(line))) != d->od->tagIndex.end()) {
6493 tag = *ii;
6494 while (tag && (LOGOFFSET(tag->line) == line && tag->index < index))
6495 tag = tag->next;
6496 while (tag && (LOGOFFSET(tag->line) == line)) {
6497 tag->index += stripped.length();
6498 tag = tag->next;
6499 }
6500 }
6501 stripped = text;
6502 optimParseTags(&stripped, line, index);
6503 } else if (numNewLines > 0) {
6504 // Case 2. We have at least 1 newline char - split at
6505 // insertion pt. and make room for new lines - complex and slow!
6506 QString left = d->od->lines[LOGOFFSET(line)].left(index);
6507 QString right = d->od->lines[LOGOFFSET(line)].mid(index);
6508
6509 // rearrange lines for insertion
6510 for (x = d->od->numLines - 1; x > line; x--)
6511 d->od->lines[x + numNewLines] = d->od->lines[x];
6512 d->od->numLines += numNewLines;
6513
6514 // fix the tag index and the tag line/index numbers - this
6515 // might take a while..
6516 for (x = line; x < d->od->numLines; x++) {
6517 if ((ii = d->od->tagIndex.find(LOGOFFSET(line))) != d->od->tagIndex.end()) {
6518 tag = ii.data();
6519 if (LOGOFFSET(tag->line) == line)
6520 while (tag && (LOGOFFSET(tag->line) == line && tag->index < index))
6521 tag = tag->next;
6522 }
6523 }
6524
6525 // relabel affected tags with new line numbers and new index
6526 // positions
6527 while (tag) {
6528 if (LOGOFFSET(tag->line) == line)
6529 tag->index -= index;
6530 tag->line += numNewLines;
6531 tag = tag->next;
6532 }
6533
6534 // generate a new tag index
6535 d->od->tagIndex.clear();
6536 tag = d->od->tags;
6537 while (tag) {
6538 if (!((ii = d->od->tagIndex.find(LOGOFFSET(tag->line))) != d->od->tagIndex.end()))
6539 d->od->tagIndex[LOGOFFSET(tag->line)] = tag;
6540 tag = tag->next;
6541 }
6542
6543 // update the tag indices on the spliced line - needs to be done before new tags are added
6544 QString stripped = strl[strl.count() - 1];
6545 qStripTags(&stripped);
6546 if ((ii = d->od->tagIndex.find(LOGOFFSET(line + numNewLines))) != d->od->tagIndex.end()) {
6547 tag = *ii;
6548 while (tag && (LOGOFFSET(tag->line) == line + numNewLines)) {
6549 tag->index += stripped.length();
6550 tag = tag->next;
6551 }
6552 }
6553
6554 // inject the new lines
6555 QStringList::Iterator it = strl.begin();
6556 x = line;
6557 int idx;
6558 for (; it != strl.end(); ++it) {
6559 stripped = *it;
6560 qStripTags(&stripped);
6561 if (x == line) {
6562 stripped = left + stripped;
6563 idx = index;
6564 } else {
6565 idx = 0;
6566 }
6567 d->od->lines[LOGOFFSET(x)] = stripped;
6568 optimParseTags(&*it, x++, idx);
6569 }
6570 d->od->lines[LOGOFFSET(x - 1)] += right;
6571 }
6572 // recalculate the pixel width of the longest injected line -
6573 QFontMetrics fm(QScrollView::font());
6574 int lWidth = 0;
6575
6576 for (x = line; x < line + numNewLines; x++) {
6577 if (optimHasBoldMetrics(x)) {
6578 QFont fnt = QScrollView::font();
6579 fnt.setBold(TRUE);
6580 fm = QFontMetrics(fnt);
6581 }
6582 lWidth = fm.width(d->od->lines[x]) + 4;
6583 if (lWidth > d->od->maxLineWidth)
6584 d->od->maxLineWidth = lWidth;
6585 }
6586 resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1);
6587 repaintContents();
6588 emit textChanged();
6589}
6590
6591
6592
6593/*! \internal
6594
6595 Returns the first open left-tag appearing before line \a line.
6596 */
6597QTextEditOptimPrivate::Tag * QTextEdit::optimPreviousLeftTag( int line )
6598{
6599 QTextEditOptimPrivate::Tag * ftag = 0;
6600 QMapConstIterator<int,QTextEditOptimPrivate::Tag *> it;
6601 if ( (it = d->od->tagIndex.find( LOGOFFSET(line) )) != d->od->tagIndex.end() )
6602 ftag = it.data();
6603 if ( !ftag ) {
6604 // start searching for an open tag
6605 ftag = d->od->tags;
6606 while ( ftag ) {
6607 if ( ftag->line > line || ftag->next == 0 ) {
6608 if ( ftag->line > line )
6609 ftag = ftag->prev;
6610 break;
6611 }
6612 ftag = ftag->next;
6613 }
6614 } else {
6615 ftag = ftag->prev;
6616 }
6617
6618 if ( ftag ) {
6619 if ( ftag && ftag->parent ) // use the open parent tag
6620 ftag = ftag->parent;
6621 else if ( ftag && ftag->leftTag ) // this is a right-tag with no parent
6622 ftag = 0;
6623 }
6624 return ftag;
6625}
6626
6627/*! \internal
6628
6629 Set the format for the string starting at index \a start and ending
6630 at \a end according to \a tag. If \a tag is a Format tag, find the
6631 first open color tag appearing before \a tag and use that tag to
6632 color the string.
6633*/
6634void QTextEdit::optimSetTextFormat( QTextDocument * td, QTextCursor * cur,
6635 QTextFormat * f, int start, int end,
6636 QTextEditOptimPrivate::Tag * tag )
6637{
6638 int formatFlags = QTextFormat::Bold | QTextFormat::Italic |
6639 QTextFormat::Underline;
6640 cur->setIndex( start );
6641 td->setSelectionStart( 0, *cur );
6642 cur->setIndex( end );
6643 td->setSelectionEnd( 0, *cur );
6644 QStyleSheetItem * ssItem = styleSheet()->item( tag->tag );
6645 if ( !ssItem || tag->type == QTextEditOptimPrivate::Format ) {
6646 f->setBold( tag->bold );
6647 f->setItalic( tag->italic );
6648 f->setUnderline( tag->underline );
6649 if ( tag->type == QTextEditOptimPrivate::Format ) {
6650 // check to see if there are any open color tags prior to
6651 // this format tag
6652 tag = tag->prev;
6653 while ( tag && (tag->type == QTextEditOptimPrivate::Format ||
6654 tag->leftTag) ) {
6655 tag = tag->leftTag ? tag->parent : tag->prev;
6656 }
6657 }
6658 if ( tag ) {
6659 QString col = tag->tag.simplifyWhiteSpace();
6660 if ( col.left( 10 ) == "font color" ) {
6661 int i = col.find( '=', 10 );
6662 col = col.mid( i + 1 ).simplifyWhiteSpace();
6663 if ( col[0] == '\"' )
6664 col = col.mid( 1, col.length() - 2 );
6665 }
6666 QColor color = QColor( col );
6667 if ( color.isValid() ) {
6668 formatFlags |= QTextFormat::Color;
6669 f->setColor( color );
6670 }
6671 }
6672 } else { // use the stylesheet tag definition
6673 if ( ssItem->color().isValid() ) {
6674 formatFlags |= QTextFormat::Color;
6675 f->setColor( ssItem->color() );
6676 }
6677 f->setBold( ssItem->fontWeight() == QFont::Bold );
6678 f->setItalic( ssItem->fontItalic() );
6679 f->setUnderline( ssItem->fontUnderline() );
6680 }
6681 td->setFormat( 0, f, formatFlags );
6682 td->removeSelection( 0 );
6683}
6684
6685/*! \internal */
6686void QTextEdit::optimDrawContents( QPainter * p, int clipx, int clipy,
6687 int clipw, int cliph )
6688{
6689 QFontMetrics fm( QScrollView::font() );
6690 int startLine = clipy / fm.lineSpacing();
6691
6692 // we always have to fetch at least two lines for drawing because the
6693 // painter may be translated so that parts of two lines cover the area
6694 // of a single line
6695 int nLines = (cliph / fm.lineSpacing()) + 2;
6696 int endLine = startLine + nLines;
6697
6698 if ( startLine >= d->od->numLines ) {
6699#if defined (Q_WS_PM)
6700 // see below for more info
6701 p->eraseRect( clipx, clipy, clipw, cliph );
6702#endif
6703 return;
6704 }
6705
6706 if ( (startLine + nLines) > d->od->numLines )
6707 nLines = d->od->numLines - startLine;
6708
6709 int i = 0;
6710 QString str;
6711 for ( i = startLine; i < (startLine + nLines); i++ )
6712 str.append( d->od->lines[ LOGOFFSET(i) ] + "\n" );
6713
6714 QTextDocument * td = new QTextDocument( 0 );
6715 td->setDefaultFormat( QScrollView::font(), QColor() );
6716 td->setPlainText( str );
6717 td->setFormatter( new QTextFormatterBreakWords ); // deleted by QTextDoc
6718 td->formatter()->setWrapEnabled( FALSE );
6719 td->setTabStops(doc->tabStopWidth());
6720
6721 // get the current text color from the current format
6722 td->selectAll( QTextDocument::Standard );
6723 QTextFormat f;
6724 f.setColor( colorGroup().text() );
6725 f.setFont( QScrollView::font() );
6726 td->setFormat( QTextDocument::Standard, &f,
6727 QTextFormat::Color | QTextFormat::Font );
6728 td->removeSelection( QTextDocument::Standard );
6729
6730 // add tag formatting
6731 if ( d->od->tags ) {
6732 int i = startLine;
6733 QMapConstIterator<int,QTextEditOptimPrivate::Tag *> it;
6734 QTextEditOptimPrivate::Tag * tag = 0, * tmp = 0;
6735 QTextCursor cur( td );
6736 // Step 1 - find previous left-tag
6737 tmp = optimPreviousLeftTag( i );
6738 for ( ; i < startLine + nLines; i++ ) {
6739 if ( (it = d->od->tagIndex.find( LOGOFFSET(i) )) != d->od->tagIndex.end() )
6740 tag = it.data();
6741 // Step 2 - iterate over tags on the current line
6742 int lastIndex = 0;
6743 while ( tag && tag->line == i ) {
6744 tmp = 0;
6745 if ( tag->prev && !tag->prev->leftTag ) {
6746 tmp = tag->prev;
6747 } else if ( tag->prev && tag->prev->parent ) {
6748 tmp = tag->prev->parent;
6749 }
6750 if ( (tag->index - lastIndex) > 0 && tmp ) {
6751 optimSetTextFormat( td, &cur, &f, lastIndex, tag->index, tmp );
6752 }
6753 lastIndex = tag->index;
6754 tmp = tag;
6755 tag = tag->next;
6756 }
6757 // Step 3 - color last part of the line - if necessary
6758 if ( tmp && tmp->parent )
6759 tmp = tmp->parent;
6760 if ( (cur.paragraph()->length()-1 - lastIndex) > 0 && tmp && !tmp->leftTag ) {
6761 optimSetTextFormat( td, &cur, &f, lastIndex,
6762 cur.paragraph()->length() - 1, tmp );
6763 }
6764 cur.setParagraph( cur.paragraph()->next() );
6765 }
6766 // useful debug info
6767 //
6768// tag = d->od->tags;
6769// qWarning("###");
6770// while ( tag ) {
6771// qWarning( "Tag: %p, parent: %09p, leftTag: %09p, Name: %-15s, ParentName: %s, %d%d%d", tag,
6772// tag->parent, tag->leftTag, tag->tag.latin1(), tag->parent ? tag->parent->tag.latin1():"<none>",
6773// tag->bold, tag->italic, tag->underline );
6774// tag = tag->next;
6775// }
6776 }
6777
6778 // if there is a selection, make sure that the selection in the
6779 // part we need to redraw is set correctly
6780 if ( optimHasSelection() ) {
6781 QTextCursor c1( td );
6782 QTextCursor c2( td );
6783 int selStart = d->od->selStart.line;
6784 int idxStart = d->od->selStart.index;
6785 int selEnd = d->od->selEnd.line;
6786 int idxEnd = d->od->selEnd.index;
6787 if ( selEnd < selStart ) {
6788 qSwap( &selStart, &selEnd );
6789 qSwap( &idxStart, &idxEnd );
6790 }
6791 if ( selEnd > d->od->numLines-1 ) {
6792 selEnd = d->od->numLines-1;
6793 }
6794 if ( startLine <= selStart && endLine >= selEnd ) {
6795 // case 1: area to paint covers entire selection
6796 int paragS = selStart - startLine;
6797 int paragE = paragS + (selEnd - selStart);
6798 QTextParagraph * parag = td->paragAt( paragS );
6799 if ( parag ) {
6800 c1.setParagraph( parag );
6801 if ( td->text( paragS ).length() >= (uint) idxStart )
6802 c1.setIndex( idxStart );
6803 }
6804 parag = td->paragAt( paragE );
6805 if ( parag ) {
6806 c2.setParagraph( parag );
6807 if ( td->text( paragE ).length() >= (uint) idxEnd )
6808 c2.setIndex( idxEnd );
6809 }
6810 } else if ( startLine > selStart && endLine < selEnd ) {
6811 // case 2: area to paint is all part of the selection
6812 td->selectAll( QTextDocument::Standard );
6813 } else if ( startLine > selStart && endLine >= selEnd &&
6814 startLine <= selEnd ) {
6815 // case 3: area to paint starts inside a selection, ends past it
6816 c1.setParagraph( td->firstParagraph() );
6817 c1.setIndex( 0 );
6818 int paragE = selEnd - startLine;
6819 QTextParagraph * parag = td->paragAt( paragE );
6820 if ( parag ) {
6821 c2.setParagraph( parag );
6822 if ( td->text( paragE ).length() >= (uint) idxEnd )
6823 c2.setIndex( idxEnd );
6824 }
6825 } else if ( startLine <= selStart && endLine < selEnd &&
6826 endLine > selStart ) {
6827 // case 4: area to paint starts before a selection, ends inside it
6828 int paragS = selStart - startLine;
6829 QTextParagraph * parag = td->paragAt( paragS );
6830 if ( parag ) {
6831 c1.setParagraph( parag );
6832 c1.setIndex( idxStart );
6833 }
6834 c2.setParagraph( td->lastParagraph() );
6835 c2.setIndex( td->lastParagraph()->string()->toString().length() - 1 );
6836
6837 }
6838 // previously selected?
6839 if ( !td->hasSelection( QTextDocument::Standard ) ) {
6840 td->setSelectionStart( QTextDocument::Standard, c1 );
6841 td->setSelectionEnd( QTextDocument::Standard, c2 );
6842 }
6843 }
6844 td->doLayout( p, contentsWidth() );
6845
6846 // have to align the painter so that partly visible lines are
6847 // drawn at the correct position within the area that needs to be
6848 // painted
6849 int offset = clipy % fm.lineSpacing() + 2;
6850 QRect r( clipx, 0, clipw, cliph + offset );
6851 p->translate( 0, clipy - offset );
6852 td->draw( p, r.x(), r.y(), r.width(), r.height(), colorGroup() );
6853 p->translate( 0, -(clipy - offset) );
6854
6855#if defined (Q_WS_PM)
6856 // QTextEdit has WResizeNoErase and WRepaintNoErase flags (actually applied
6857 // to the viewport()), so we must erase areas not covered by the text doc.
6858 // [Win32 version feels ok without this, because it doesn't actually
6859 // fully obey WResizeNoErase and WRepaintNoErase: WM_ERASEBKGND always
6860 // erases the background before WM_PAINT and after every resize].
6861
6862 int tw = td->width();
6863 // calculate the height the text doc will occupy starting with clipy
6864 int th = td->height() - offset;
6865 if ( clipx + clipw > td->width() ) {
6866 // erase background to the left of every line
6867 p->eraseRect( tw, clipy, clipx + clipw - tw, th );
6868 }
6869 if ( cliph > th ) {
6870 // erase background under the text doc
6871 p->eraseRect( clipx, clipy + th, clipw, cliph - th );
6872 }
6873#endif
6874
6875 delete td;
6876}
6877
6878/*! \internal */
6879void QTextEdit::optimMousePressEvent( QMouseEvent * e )
6880{
6881 if ( e->button() != LeftButton )
6882 return;
6883
6884 QFontMetrics fm( QScrollView::font() );
6885 mousePressed = TRUE;
6886 mousePos = e->pos();
6887 d->od->selStart.line = e->y() / fm.lineSpacing();
6888 if ( d->od->selStart.line > d->od->numLines-1 ) {
6889 d->od->selStart.line = d->od->numLines-1;
6890 d->od->selStart.index = d->od->lines[ LOGOFFSET(d->od->numLines-1) ].length();
6891 } else {
6892 QString str = d->od->lines[ LOGOFFSET(d->od->selStart.line) ];
6893 d->od->selStart.index = optimCharIndex( str, mousePos.x() );
6894 }
6895 d->od->selEnd.line = d->od->selStart.line;
6896 d->od->selEnd.index = d->od->selStart.index;
6897 oldMousePos = e->pos();
6898 repaintContents( FALSE );
6899}
6900
6901/*! \internal */
6902void QTextEdit::optimMouseReleaseEvent( QMouseEvent * e )
6903{
6904 if ( e->button() != LeftButton )
6905 return;
6906
6907 if ( scrollTimer->isActive() )
6908 scrollTimer->stop();
6909 if ( !inDoubleClick ) {
6910 QFontMetrics fm( QScrollView::font() );
6911 d->od->selEnd.line = e->y() / fm.lineSpacing();
6912 if ( d->od->selEnd.line > d->od->numLines-1 ) {
6913 d->od->selEnd.line = d->od->numLines-1;
6914 }
6915 QString str = d->od->lines[ LOGOFFSET(d->od->selEnd.line) ];
6916 mousePos = e->pos();
6917 d->od->selEnd.index = optimCharIndex( str, mousePos.x() );
6918 if ( d->od->selEnd.line < d->od->selStart.line ) {
6919 qSwap( &d->od->selStart.line, &d->od->selEnd.line );
6920 qSwap( &d->od->selStart.index, &d->od->selEnd.index );
6921 } else if ( d->od->selStart.line == d->od->selEnd.line &&
6922 d->od->selStart.index > d->od->selEnd.index ) {
6923 qSwap( &d->od->selStart.index, &d->od->selEnd.index );
6924 }
6925 oldMousePos = e->pos();
6926 repaintContents( FALSE );
6927 }
6928 if ( mousePressed ) {
6929 mousePressed = FALSE;
6930 copyToClipboard();
6931 }
6932
6933 inDoubleClick = FALSE;
6934 emit copyAvailable( optimHasSelection() );
6935 emit selectionChanged();
6936}
6937
6938/*! \internal */
6939void QTextEdit::optimMouseMoveEvent( QMouseEvent * e )
6940{
6941 mousePos = e->pos();
6942 optimDoAutoScroll();
6943 oldMousePos = mousePos;
6944}
6945
6946/*! \internal */
6947void QTextEdit::optimDoAutoScroll()
6948{
6949 if ( !mousePressed )
6950 return;
6951
6952 QFontMetrics fm( QScrollView::font() );
6953 QPoint pos( mapFromGlobal( QCursor::pos() ) );
6954 bool doScroll = FALSE;
6955 int xx = contentsX() + pos.x();
6956 int yy = contentsY() + pos.y();
6957
6958 // find out how much we have to scroll in either dir.
6959 if ( pos.x() < 0 || pos.x() > viewport()->width() ||
6960 pos.y() < 0 || pos.y() > viewport()->height() ) {
6961 int my = yy;
6962 if ( pos.x() < 0 )
6963 xx = contentsX() - fm.width( 'w');
6964 else if ( pos.x() > viewport()->width() )
6965 xx = contentsX() + viewport()->width() + fm.width('w');
6966
6967 if ( pos.y() < 0 ) {
6968 my = contentsY() - 1;
6969 yy = (my / fm.lineSpacing()) * fm.lineSpacing() + 1;
6970 } else if ( pos.y() > viewport()->height() ) {
6971 my = contentsY() + viewport()->height() + 1;
6972 yy = (my / fm.lineSpacing() + 1) * fm.lineSpacing() - 1;
6973 }
6974 d->od->selEnd.line = my / fm.lineSpacing();
6975 mousePos.setX( xx );
6976 mousePos.setY( my );
6977 doScroll = TRUE;
6978 } else {
6979 d->od->selEnd.line = mousePos.y() / fm.lineSpacing();
6980 }
6981
6982 if ( d->od->selEnd.line < 0 ) {
6983 d->od->selEnd.line = 0;
6984 } else if ( d->od->selEnd.line > d->od->numLines-1 ) {
6985 d->od->selEnd.line = d->od->numLines-1;
6986 }
6987
6988 QString str = d->od->lines[ LOGOFFSET(d->od->selEnd.line) ];
6989 d->od->selEnd.index = optimCharIndex( str, mousePos.x() );
6990
6991 // have to have a valid index before generating a paint event
6992 if ( doScroll )
6993 ensureVisible( xx, yy, 1, 1 );
6994
6995 // if the text document is smaller than the heigth of the viewport
6996 // - redraw the whole thing otherwise calculate the rect that
6997 // needs drawing.
6998 if ( d->od->numLines * fm.lineSpacing() < viewport()->height() ) {
6999 repaintContents( contentsX(), contentsY(), width(), height(), FALSE );
7000 } else {
7001 int h = QABS(mousePos.y() - oldMousePos.y()) + fm.lineSpacing() * 2;
7002 int y;
7003 if ( oldMousePos.y() < mousePos.y() ) {
7004 y = oldMousePos.y() - fm.lineSpacing();
7005 } else {
7006 // expand paint area for a fully selected line
7007 h += fm.lineSpacing();
7008 y = mousePos.y() - fm.lineSpacing()*2;
7009 }
7010 if ( y < 0 )
7011 y = 0;
7012 repaintContents( contentsX(), y, width(), h, FALSE );
7013 }
7014
7015 if ( !scrollTimer->isActive() && pos.y() < 0 || pos.y() > height() )
7016 scrollTimer->start( 100, FALSE );
7017 else if ( scrollTimer->isActive() && pos.y() >= 0 && pos.y() <= height() )
7018 scrollTimer->stop();
7019}
7020
7021/*! \internal
7022
7023 Returns the index of the character in the string \a str that is
7024 currently under the mouse pointer.
7025*/
7026int QTextEdit::optimCharIndex( const QString &str, int mx ) const
7027{
7028 QFontMetrics fm(QScrollView::font());
7029 uint i = 0;
7030 int dd, dist = 10000000;
7031 int curpos = 0;
7032 int strWidth;
7033 mx = mx - 4; // ### get the real margin from somewhere
7034
7035 if (!str.contains('\t') && mx > fm.width(str))
7036 return str.length();
7037
7038 while (i < str.length()) {
7039 strWidth = qStrWidth(str.left(i), tabStopWidth(), fm);
7040 dd = strWidth - mx;
7041 if (QABS(dd) <= dist) {
7042 dist = QABS(dd);
7043 if (mx >= strWidth)
7044 curpos = i;
7045 }
7046 ++i;
7047 }
7048 return curpos;
7049}
7050
7051/*! \internal */
7052void QTextEdit::optimSelectAll()
7053{
7054 d->od->selStart.line = d->od->selStart.index = 0;
7055 d->od->selEnd.line = d->od->numLines - 1;
7056 d->od->selEnd.index = d->od->lines[ LOGOFFSET(d->od->selEnd.line) ].length();
7057
7058 repaintContents( FALSE );
7059 emit copyAvailable( optimHasSelection() );
7060 emit selectionChanged();
7061}
7062
7063/*! \internal */
7064void QTextEdit::optimRemoveSelection()
7065{
7066 d->od->selStart.line = d->od->selEnd.line = -1;
7067 d->od->selStart.index = d->od->selEnd.index = -1;
7068 repaintContents( FALSE );
7069}
7070
7071/*! \internal */
7072void QTextEdit::optimSetSelection( int startLine, int startIdx,
7073 int endLine, int endIdx )
7074{
7075 d->od->selStart.line = startLine;
7076 d->od->selEnd.line = endLine;
7077 d->od->selStart.index = startIdx;
7078 d->od->selEnd.index = endIdx;
7079}
7080
7081/*! \internal */
7082bool QTextEdit::optimHasSelection() const
7083{
7084 if ( d->od->selStart.line != d->od->selEnd.line ||
7085 d->od->selStart.index != d->od->selEnd.index )
7086 return TRUE;
7087 return FALSE;
7088}
7089
7090/*! \internal */
7091QString QTextEdit::optimSelectedText() const
7092{
7093 QString str;
7094
7095 if ( !optimHasSelection() )
7096 return str;
7097
7098 // concatenate all strings
7099 if ( d->od->selStart.line == d->od->selEnd.line ) {
7100 str = d->od->lines[ LOGOFFSET(d->od->selEnd.line) ].mid( d->od->selStart.index,
7101 d->od->selEnd.index - d->od->selStart.index );
7102 } else {
7103 int i = d->od->selStart.line;
7104 str = d->od->lines[ LOGOFFSET(i) ].right( d->od->lines[ LOGOFFSET(i) ].length() -
7105 d->od->selStart.index ) + "\n";
7106 i++;
7107 for ( ; i < d->od->selEnd.line; i++ ) {
7108 if ( d->od->lines[ LOGOFFSET(i) ].isEmpty() ) // CR lines are empty
7109 str += "\n";
7110 else
7111 str += d->od->lines[ LOGOFFSET(i) ] + "\n";
7112 }
7113 str += d->od->lines[ LOGOFFSET(d->od->selEnd.line) ].left( d->od->selEnd.index );
7114 }
7115 return str;
7116}
7117
7118/*! \internal */
7119bool QTextEdit::optimFind( const QString & expr, bool cs, bool /*wo*/,
7120 bool fw, int * para, int * index )
7121{
7122 bool found = FALSE;
7123 int parag = para ? *para : d->od->search.line,
7124 idx = index ? *index : d->od->search.index, i;
7125
7126 if ( d->od->len == 0 )
7127 return FALSE;
7128
7129 for ( i = parag; fw ? i < d->od->numLines : i >= 0; fw ? i++ : i-- ) {
7130 idx = fw ? d->od->lines[ LOGOFFSET(i) ].find( expr, idx, cs ) :
7131 d->od->lines[ LOGOFFSET(i) ].findRev( expr, idx, cs );
7132 if ( idx != -1 ) {
7133 found = TRUE;
7134 break;
7135 } else if ( fw )
7136 idx = 0;
7137 }
7138
7139 if ( found ) {
7140 if ( index )
7141 *index = idx;
7142 if ( para )
7143 *para = i;
7144 d->od->search.index = idx;
7145 d->od->search.line = i;
7146 optimSetSelection( i, idx, i, idx + expr.length() );
7147 QFontMetrics fm( QScrollView::font() );
7148 int h = fm.lineSpacing();
7149 int x = fm.width( d->od->lines[ LOGOFFSET(i) ].left( idx + expr.length()) ) + 4;
7150 ensureVisible( x, i * h + h / 2, 1, h / 2 + 2 );
7151 repaintContents(); // could possibly be optimized
7152 }
7153 return found;
7154}
7155
7156/*! \reimp */
7157void QTextEdit::polish()
7158{
7159 // this will ensure that the last line is visible if text have
7160 // been added to the widget before it is shown
7161 if ( d->optimMode )
7162 scrollToBottom();
7163 QWidget::polish();
7164}
7165
7166/*!
7167 Sets the maximum number of lines a QTextEdit can hold in \c
7168 LogText mode to \a limit. If \a limit is -1 (the default), this
7169 signifies an unlimited number of lines.
7170
7171 \warning Never use formatting tags that span more than one line
7172 when the maximum log lines is set. When lines are removed from the
7173 top of the buffer it could result in an unbalanced tag pair, i.e.
7174 the left formatting tag is removed before the right one.
7175 */
7176void QTextEdit::setMaxLogLines( int limit )
7177{
7178 d->maxLogLines = limit;
7179 if ( d->maxLogLines < -1 )
7180 d->maxLogLines = -1;
7181 if ( d->maxLogLines == -1 )
7182 d->logOffset = 0;
7183}
7184
7185/*!
7186 Returns the maximum number of lines QTextEdit can hold in \c
7187 LogText mode. By default the number of lines is unlimited, which
7188 is signified by a value of -1.
7189 */
7190int QTextEdit::maxLogLines()
7191{
7192 return d->maxLogLines;
7193}
7194
7195/*!
7196 Check if the number of lines in the buffer is limited, and uphold
7197 that limit when appending new lines.
7198 */
7199void QTextEdit::optimCheckLimit( const QString& str )
7200{
7201 if ( d->maxLogLines > -1 && d->maxLogLines == d->od->numLines ) {
7202 // NB! Removing the top line in the buffer will potentially
7203 // destroy the structure holding the formatting tags - if line
7204 // spanning tags are used.
7205 QTextEditOptimPrivate::Tag *t = d->od->tags, *tmp, *itr;
7206 QPtrList<QTextEditOptimPrivate::Tag> lst;
7207 while ( t ) {
7208 t->line -= 1;
7209 // unhook the ptr from the tag structure
7210 if ( ((uint) LOGOFFSET(t->line) < (uint) d->logOffset &&
7211 (uint) LOGOFFSET(t->line) < (uint) LOGOFFSET(d->od->numLines) &&
7212 (uint) LOGOFFSET(d->od->numLines) > (uint) d->logOffset) )
7213 {
7214 if ( t->prev )
7215 t->prev->next = t->next;
7216 if ( t->next )
7217 t->next->prev = t->prev;
7218 if ( d->od->tags == t )
7219 d->od->tags = t->next;
7220 if ( d->od->lastTag == t ) {
7221 if ( t->prev )
7222 d->od->lastTag = t->prev;
7223 else
7224 d->od->lastTag = d->od->tags;
7225 }
7226 tmp = t;
7227 t = t->next;
7228 lst.append( tmp );
7229 delete tmp;
7230 } else {
7231 t = t->next;
7232 }
7233 }
7234 // Remove all references to the ptrs we just deleted
7235 itr = d->od->tags;
7236 while ( itr ){
7237 for ( tmp = lst.first(); tmp; tmp = lst.next() ) {
7238 if ( itr->parent == tmp )
7239 itr->parent = 0;
7240 if ( itr->leftTag == tmp )
7241 itr->leftTag = 0;
7242 }
7243 itr = itr->next;
7244 }
7245 // ...in the tag index as well
7246 QMapIterator<int, QTextEditOptimPrivate::Tag *> idx;
7247 if ( (idx = d->od->tagIndex.find( d->logOffset )) != d->od->tagIndex.end() )
7248 d->od->tagIndex.remove( idx );
7249
7250 QMapIterator<int,QString> it;
7251 if ( (it = d->od->lines.find( d->logOffset )) != d->od->lines.end() ) {
7252 d->od->len -= (*it).length();
7253 d->od->lines.remove( it );
7254 d->od->numLines--;
7255 d->logOffset = LOGOFFSET(1);
7256 }
7257 }
7258 d->od->len += str.length();
7259 d->od->lines[ LOGOFFSET(d->od->numLines++) ] = str;
7260}
7261
7262#endif // QT_TEXTEDIT_OPTIMIZATION
7263
7264/*!
7265 \property QTextEdit::autoFormatting
7266 \brief the enabled set of auto formatting features
7267
7268 The value can be any combination of the values in the \c
7269 AutoFormatting enum. The default is \c AutoAll. Choose \c AutoNone
7270 to disable all automatic formatting.
7271
7272 Currently, the only automatic formatting feature provided is \c
7273 AutoBulletList; future versions of Qt may offer more.
7274*/
7275
7276void QTextEdit::setAutoFormatting( uint features )
7277{
7278 d->autoFormatting = features;
7279}
7280
7281uint QTextEdit::autoFormatting() const
7282{
7283 return d->autoFormatting;
7284}
7285
7286/*!
7287 Returns the QSyntaxHighlighter set on this QTextEdit. 0 is
7288 returned if no syntax highlighter is set.
7289 */
7290QSyntaxHighlighter * QTextEdit::syntaxHighlighter() const
7291{
7292 if (document()->preProcessor())
7293 return ((QSyntaxHighlighterInternal *) document()->preProcessor())->highlighter;
7294 else
7295 return 0;
7296}
7297
7298#endif //QT_NO_TEXTEDIT
Note: See TracBrowser for help on using the repository browser.