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

Last change on this file since 168 was 106, checked in by dmik, 19 years ago

Widgets: QTextEdit: Fixed: AltGr+key can produce valid characters in some kbd layouts (i.e. the German one) which we must not ignore.

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