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