Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwidgettextcontrol.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#ifndef QT_NO_TEXTCONTROL
9
10#include <qfont.h>
11#include <qpainter.h>
12#include <qevent.h>
13#include <qdebug.h>
14#if QT_CONFIG(draganddrop)
15#include <qdrag.h>
16#endif
17#include <qclipboard.h>
18#include <qstyle.h>
19#include "private/qapplication_p.h"
20#include "private/qtextdocumentlayout_p.h"
21#include "private/qabstracttextdocumentlayout_p.h"
22#if QT_CONFIG(menu)
23#include "private/qmenu_p.h"
24#endif
25#include "qtextdocument.h"
26#include "private/qtextdocument_p.h"
27#include "private/qtextdocumentfragment_p.h"
28#include "qtextlist.h"
29#include "private/qwidgettextcontrol_p.h"
30#if QT_CONFIG(style_stylesheet)
31# include "private/qstylesheetstyle_p.h"
32#endif
33#if QT_CONFIG(graphicsview)
34#include "qgraphicssceneevent.h"
35#endif
37#include "private/qpagedpaintdevice_p.h"
39#include "qstylehints.h"
40#include "private/qtextcursor_p.h"
41
42#include <qtextformat.h>
43#include <qdatetime.h>
44#include <qbuffer.h>
45#include <qapplication.h>
46#include <limits.h>
47#include <qtexttable.h>
48#include <qvariant.h>
49#include <qurl.h>
50#include <qdesktopservices.h>
51#include <qinputmethod.h>
52#if QT_CONFIG(tooltip)
53#include <qtooltip.h>
54#endif
55#include <qstyleoption.h>
56#if QT_CONFIG(lineedit)
57#include <QtWidgets/qlineedit.h>
58#endif
59#include <QtGui/qaccessible.h>
60#include <QtCore/qmetaobject.h>
61#ifdef Q_OS_WASM
62#include <QtCore/private/qstdweb_p.h>
63#endif
64
65#include <private/qoffsetstringarray_p.h>
66
67#if QT_CONFIG(shortcut)
68#include "private/qapplication_p.h"
69#include "private/qshortcutmap_p.h"
70#include <qkeysequence.h>
71#define ACCEL_KEY(k) (!QCoreApplication::testAttribute(Qt::AA_DontShowShortcutsInContextMenus)
72 && !QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(k) ?
73 u'\t' + QKeySequence(k).toString(QKeySequence::NativeText) : QString())
74
75#else
76#define ACCEL_KEY(k) QString()
77#endif
78
79#include <algorithm>
80
81QT_BEGIN_NAMESPACE
82
83using namespace Qt::StringLiterals;
84
85// could go into QTextCursor...
86static QTextLine currentTextLine(const QTextCursor &cursor)
87{
88 const QTextBlock block = cursor.block();
89 if (!block.isValid())
90 return QTextLine();
91
92 const QTextLayout *layout = block.layout();
93 if (!layout)
94 return QTextLine();
95
96 const int relativePos = cursor.position() - block.position();
97 return layout->lineForTextPosition(relativePos);
98}
99
100QWidgetTextControlPrivate::QWidgetTextControlPrivate()
101 : doc(nullptr), cursorOn(false), cursorVisible(false), cursorIsFocusIndicator(false),
102#ifndef Q_OS_ANDROID
103 interactionFlags(Qt::TextEditorInteraction),
104#else
105 interactionFlags(Qt::TextEditable | Qt::TextSelectableByKeyboard),
106#endif
107 dragEnabled(true),
108#if QT_CONFIG(draganddrop)
109 mousePressed(false), mightStartDrag(false),
110#endif
113 overwriteMode(false),
114 acceptRichText(true),
115 preeditCursor(0), hideCursor(false),
116 hasFocus(false),
117 isEnabled(true),
120 openExternalLinks(false),
122{}
123
124bool QWidgetTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
125{
126#ifdef QT_NO_SHORTCUT
127 Q_UNUSED(e);
128#endif
129
130 Q_Q(QWidgetTextControl);
131 if (cursor.isNull())
132 return false;
133
134 const QTextCursor oldSelection = cursor;
135 const int oldCursorPos = cursor.position();
136
137 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
138 QTextCursor::MoveOperation op = QTextCursor::NoMove;
139
140 if (false) {
141 }
142#ifndef QT_NO_SHORTCUT
143 if (e == QKeySequence::MoveToNextChar) {
144 op = QTextCursor::Right;
145 }
146 else if (e == QKeySequence::MoveToPreviousChar) {
147 op = QTextCursor::Left;
148 }
149 else if (e == QKeySequence::SelectNextChar) {
150 op = QTextCursor::Right;
151 mode = QTextCursor::KeepAnchor;
152 }
153 else if (e == QKeySequence::SelectPreviousChar) {
154 op = QTextCursor::Left;
155 mode = QTextCursor::KeepAnchor;
156 }
157 else if (e == QKeySequence::SelectNextWord) {
158 op = QTextCursor::WordRight;
159 mode = QTextCursor::KeepAnchor;
160 }
161 else if (e == QKeySequence::SelectPreviousWord) {
162 op = QTextCursor::WordLeft;
163 mode = QTextCursor::KeepAnchor;
164 }
165 else if (e == QKeySequence::SelectStartOfLine) {
166 op = QTextCursor::StartOfLine;
167 mode = QTextCursor::KeepAnchor;
168 }
169 else if (e == QKeySequence::SelectEndOfLine) {
170 op = QTextCursor::EndOfLine;
171 mode = QTextCursor::KeepAnchor;
172 }
173 else if (e == QKeySequence::SelectStartOfBlock) {
174 op = QTextCursor::StartOfBlock;
175 mode = QTextCursor::KeepAnchor;
176 }
177 else if (e == QKeySequence::SelectEndOfBlock) {
178 op = QTextCursor::EndOfBlock;
179 mode = QTextCursor::KeepAnchor;
180 }
181 else if (e == QKeySequence::SelectStartOfDocument) {
182 op = QTextCursor::Start;
183 mode = QTextCursor::KeepAnchor;
184 }
185 else if (e == QKeySequence::SelectEndOfDocument) {
186 op = QTextCursor::End;
187 mode = QTextCursor::KeepAnchor;
188 }
189 else if (e == QKeySequence::SelectPreviousLine) {
190 op = QTextCursor::Up;
191 mode = QTextCursor::KeepAnchor;
192 {
193 QTextBlock block = cursor.block();
194 QTextLine line = currentTextLine(cursor);
195 if (!block.previous().isValid()
196 && line.isValid()
197 && line.lineNumber() == 0)
198 op = QTextCursor::Start;
199 }
200 }
201 else if (e == QKeySequence::SelectNextLine) {
202 op = QTextCursor::Down;
203 mode = QTextCursor::KeepAnchor;
204 {
205 QTextBlock block = cursor.block();
206 QTextLine line = currentTextLine(cursor);
207 if (!block.next().isValid()
208 && line.isValid()
209 && line.lineNumber() == block.layout()->lineCount() - 1)
210 op = QTextCursor::End;
211 }
212 }
213 else if (e == QKeySequence::MoveToNextWord) {
214 op = QTextCursor::WordRight;
215 }
216 else if (e == QKeySequence::MoveToPreviousWord) {
217 op = QTextCursor::WordLeft;
218 }
219 else if (e == QKeySequence::MoveToEndOfBlock) {
220 op = QTextCursor::EndOfBlock;
221 }
222 else if (e == QKeySequence::MoveToStartOfBlock) {
223 op = QTextCursor::StartOfBlock;
224 }
225 else if (e == QKeySequence::MoveToNextLine) {
226 op = QTextCursor::Down;
227 }
228 else if (e == QKeySequence::MoveToPreviousLine) {
229 op = QTextCursor::Up;
230 }
231 else if (e == QKeySequence::MoveToStartOfLine) {
232 op = QTextCursor::StartOfLine;
233 }
234 else if (e == QKeySequence::MoveToEndOfLine) {
235 op = QTextCursor::EndOfLine;
236 }
237 else if (e == QKeySequence::MoveToStartOfDocument) {
238 op = QTextCursor::Start;
239 }
240 else if (e == QKeySequence::MoveToEndOfDocument) {
241 op = QTextCursor::End;
242 }
243#endif // QT_NO_SHORTCUT
244 else {
245 return false;
246 }
247
248// Except for pageup and pagedown, OS X has very different behavior, we don't do it all, but
249// here's the breakdown:
250// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command),
251// Alt (Option), or Meta (Control).
252// Command/Control + Left/Right -- Move to left or right of the line
253// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor)
254// Option + Left/Right -- Move one word Left/right.
255// + Up/Down -- Begin/End of Paragraph.
256// Home/End Top/Bottom of file. (usually don't move the cursor, but will select)
257
258 bool visualNavigation = cursor.visualNavigation();
259 cursor.setVisualNavigation(true);
260 const bool moved = cursor.movePosition(op, mode);
261 cursor.setVisualNavigation(visualNavigation);
262 q->ensureCursorVisible();
263
264 bool ignoreNavigationEvents = ignoreUnusedNavigationEvents;
265 bool isNavigationEvent = e->key() == Qt::Key_Up || e->key() == Qt::Key_Down ||
266 e->key() == Qt::Key_Left || e->key() == Qt::Key_Right;
267
268 if (moved) {
269 if (cursor.position() != oldCursorPos)
270 emit q->cursorPositionChanged();
271 emit q->microFocusChanged();
272 } else if (ignoreNavigationEvents && isNavigationEvent && oldSelection.anchor() == cursor.anchor()) {
273 return false;
274 }
275
276 selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor));
277
278 repaintOldAndNewSelection(oldSelection);
279
280 return true;
281}
282
284{
285 Q_Q(QWidgetTextControl);
286
287 QTextCharFormat fmt = cursor.charFormat();
288 if (fmt == lastCharFormat)
289 return;
290 lastCharFormat = fmt;
291
292 emit q->currentCharFormatChanged(fmt);
293 emit q->microFocusChanged();
294}
295
297{
298 QTextBlockFormat blockFmt = cursor.blockFormat();
299
300 QTextList *list = cursor.currentList();
301 if (!list) {
302 QTextBlockFormat modifier;
303 modifier.setIndent(blockFmt.indent() + 1);
304 cursor.mergeBlockFormat(modifier);
305 } else {
306 QTextListFormat format = list->format();
307 format.setIndent(format.indent() + 1);
308
309 if (list->itemNumber(cursor.block()) == 1)
310 list->setFormat(format);
311 else
312 cursor.createList(format);
313 }
314}
315
317{
318 QTextBlockFormat blockFmt = cursor.blockFormat();
319
320 QTextList *list = cursor.currentList();
321
322 if (!list) {
323 QTextBlockFormat modifier;
324 modifier.setIndent(blockFmt.indent() - 1);
325 cursor.mergeBlockFormat(modifier);
326 } else {
327 QTextListFormat listFmt = list->format();
328 listFmt.setIndent(listFmt.indent() - 1);
329 list->setFormat(listFmt);
330 }
331}
332
334{
335 QTextTable *table = cursor.currentTable();
336 QTextTableCell cell = table->cellAt(cursor);
337
338 int newColumn = cell.column() + cell.columnSpan();
339 int newRow = cell.row();
340
341 if (newColumn >= table->columns()) {
342 newColumn = 0;
343 ++newRow;
344 if (newRow >= table->rows())
345 table->insertRows(table->rows(), 1);
346 }
347
348 cell = table->cellAt(newRow, newColumn);
349 cursor = cell.firstCursorPosition();
350}
351
353{
354 QTextTable *table = cursor.currentTable();
355 QTextTableCell cell = table->cellAt(cursor);
356
357 int newColumn = cell.column() - 1;
358 int newRow = cell.row();
359
360 if (newColumn < 0) {
361 newColumn = table->columns() - 1;
362 --newRow;
363 if (newRow < 0)
364 return;
365 }
366
367 cell = table->cellAt(newRow, newColumn);
368 cursor = cell.firstCursorPosition();
369}
370
372{
373 cursor.beginEditBlock();
374
375 QTextBlockFormat blockFmt = cursor.blockFormat();
376
377 QTextListFormat listFmt;
378 listFmt.setStyle(QTextListFormat::ListDisc);
379 listFmt.setIndent(blockFmt.indent() + 1);
380
381 blockFmt.setIndent(0);
382 cursor.setBlockFormat(blockFmt);
383
384 cursor.createList(listFmt);
385
386 cursor.endEditBlock();
387}
388
389void QWidgetTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document)
390{
391 Q_Q(QWidgetTextControl);
392 setContent(format, text, document);
393
394 doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable);
395 q->setCursorWidth(-1);
396}
397
398void QWidgetTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document)
399{
400 Q_Q(QWidgetTextControl);
401
402 // for use when called from setPlainText. we may want to re-use the currently
403 // set char format then.
404 const QTextCharFormat charFormatForInsertion = cursor.charFormat();
405
406 bool clearDocument = true;
407 if (!doc) {
408 if (document) {
409 doc = document;
410 } else {
411 palette = QApplication::palette("QWidgetTextControl");
412 doc = new QTextDocument(q);
413 }
414 clearDocument = false;
416 cursor = QTextCursor(doc);
417
418// #### doc->documentLayout()->setPaintDevice(viewport);
419
420 QObjectPrivate::connect(doc, &QTextDocument::contentsChanged, this,
421 &QWidgetTextControlPrivate::_q_updateCurrentCharFormatAndSelection);
422 QObjectPrivate::connect(doc, &QTextDocument::cursorPositionChanged, this,
423 &QWidgetTextControlPrivate::_q_emitCursorPosChanged);
424 QObjectPrivate::connect(doc, &QTextDocument::documentLayoutChanged, this,
425 &QWidgetTextControlPrivate::_q_documentLayoutChanged);
426
427 // convenience signal forwards
428 QObject::connect(doc, &QTextDocument::undoAvailable, q, &QWidgetTextControl::undoAvailable);
429 QObject::connect(doc, &QTextDocument::redoAvailable, q, &QWidgetTextControl::redoAvailable);
430 QObject::connect(doc, &QTextDocument::modificationChanged, q,
431 &QWidgetTextControl::modificationChanged);
432 QObject::connect(doc, &QTextDocument::blockCountChanged, q,
433 &QWidgetTextControl::blockCountChanged);
434 }
435
436 bool previousUndoRedoState = doc->isUndoRedoEnabled();
437 if (!document)
438 doc->setUndoRedoEnabled(false);
439
440 //Saving the index save some time.
441 static int contentsChangedIndex = QMetaMethod::fromSignal(&QTextDocument::contentsChanged).methodIndex();
442 static int textChangedIndex = QMetaMethod::fromSignal(&QWidgetTextControl::textChanged).methodIndex();
443 // avoid multiple textChanged() signals being emitted
444 QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex);
445
446 if (!text.isEmpty()) {
447 // clear 'our' cursor for insertion to prevent
448 // the emission of the cursorPositionChanged() signal.
449 // instead we emit it only once at the end instead of
450 // at the end of the document after loading and when
451 // positioning the cursor again to the start of the
452 // document.
453 cursor = QTextCursor();
454 if (format == Qt::PlainText) {
455 QTextCursor formatCursor(doc);
456 // put the setPlainText and the setCharFormat into one edit block,
457 // so that the syntax highlight triggers only /once/ for the entire
458 // document, not twice.
459 formatCursor.beginEditBlock();
460 doc->setPlainText(text);
461 doc->setUndoRedoEnabled(false);
462 formatCursor.select(QTextCursor::Document);
463 formatCursor.setCharFormat(charFormatForInsertion);
464 formatCursor.endEditBlock();
465#if QT_CONFIG(textmarkdownreader)
466 } else if (format == Qt::MarkdownText) {
467 doc->setMarkdown(text);
468 doc->setUndoRedoEnabled(false);
469#endif
470 } else {
471#ifndef QT_NO_TEXTHTMLPARSER
472 doc->setHtml(text);
473#else
474 doc->setPlainText(text);
475#endif
476 doc->setUndoRedoEnabled(false);
477 }
478 cursor = QTextCursor(doc);
479 } else if (clearDocument) {
480 doc->clear();
481 }
482 cursor.setCharFormat(charFormatForInsertion);
483
484 QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex);
485 emit q->textChanged();
486 if (!document)
487 doc->setUndoRedoEnabled(previousUndoRedoState);
489 if (!document)
490 doc->setModified(false);
491
492 q->ensureCursorVisible();
493 emit q->cursorPositionChanged();
494
495 QObjectPrivate::connect(doc, &QTextDocument::contentsChange, this,
496 &QWidgetTextControlPrivate::_q_contentsChanged, Qt::UniqueConnection);
497}
498
500{
501
502#ifdef Q_OS_WASM
503 // QDrag::exec() will crash without asyncify; disable drag instead.
504 if (!qstdweb::haveAsyncify())
505 return;
506#endif
507
508#if QT_CONFIG(draganddrop)
509 Q_Q(QWidgetTextControl);
510 mousePressed = false;
511 if (!contextWidget)
512 return;
513 QMimeData *data = q->createMimeDataFromSelection();
514
515 QDrag *drag = new QDrag(contextWidget);
516 drag->setMimeData(data);
517
518 Qt::DropActions actions = Qt::CopyAction;
519 Qt::DropAction action;
520 if (interactionFlags & Qt::TextEditable) {
521 actions |= Qt::MoveAction;
522 action = drag->exec(actions, Qt::MoveAction);
523 } else {
524 action = drag->exec(actions, Qt::CopyAction);
525 }
526
527 if (action == Qt::MoveAction && drag->target() != contextWidget)
528 cursor.removeSelectedText();
529#endif
530}
531
532void QWidgetTextControlPrivate::setCursorPosition(const QPointF &pos)
533{
534 Q_Q(QWidgetTextControl);
535 const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
536 if (cursorPos == -1)
537 return;
538 cursor.setPosition(cursorPos);
539}
540
541void QWidgetTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode)
542{
543 cursor.setPosition(pos, mode);
544
545 if (mode != QTextCursor::KeepAnchor) {
546 selectedWordOnDoubleClick = QTextCursor();
547 selectedBlockOnTrippleClick = QTextCursor();
548 }
549}
550
552{
553 Q_Q(QWidgetTextControl);
554 emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor));
555}
556
557void QWidgetTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection)
558{
559 Q_Q(QWidgetTextControl);
560 if (cursor.hasSelection()
561 && oldSelection.hasSelection()
562 && cursor.currentFrame() == oldSelection.currentFrame()
563 && !cursor.hasComplexSelection()
564 && !oldSelection.hasComplexSelection()
565 && cursor.anchor() == oldSelection.anchor()
566 ) {
567 QTextCursor differenceSelection(doc);
568 differenceSelection.setPosition(oldSelection.position());
569 differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor);
570 emit q->updateRequest(q->selectionRect(differenceSelection));
571 } else {
572 if (!oldSelection.isNull())
573 emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection));
574 emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor));
575 }
576}
577
578void QWidgetTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/)
579{
580 Q_Q(QWidgetTextControl);
581 if (forceEmitSelectionChanged) {
582 emit q->selectionChanged();
583#if QT_CONFIG(accessibility)
584 if (q->parent() && q->parent()->isWidgetType()) {
585 QAccessibleTextSelectionEvent ev(q->parent(), cursor.anchor(), cursor.position());
586 QAccessible::updateAccessibility(&ev);
587 }
588#endif
589 }
590
591 if (cursor.position() == lastSelectionPosition
592 && cursor.anchor() == lastSelectionAnchor)
593 return;
594
595 bool selectionStateChange = (cursor.hasSelection()
596 != (lastSelectionPosition != lastSelectionAnchor));
597 if (selectionStateChange)
598 emit q->copyAvailable(cursor.hasSelection());
599
600 if (!forceEmitSelectionChanged
601 && (selectionStateChange
602 || (cursor.hasSelection()
603 && (cursor.position() != lastSelectionPosition
604 || cursor.anchor() != lastSelectionAnchor)))) {
605 emit q->selectionChanged();
606#if QT_CONFIG(accessibility)
607 if (q->parent() && q->parent()->isWidgetType()) {
608 QAccessibleTextSelectionEvent ev(q->parent(), cursor.anchor(), cursor.position());
609 QAccessible::updateAccessibility(&ev);
610 }
611#endif
612 }
613 emit q->microFocusChanged();
614 lastSelectionPosition = cursor.position();
615 lastSelectionAnchor = cursor.anchor();
616}
617
623
624#ifndef QT_NO_CLIPBOARD
626{
627 QClipboard *clipboard = QGuiApplication::clipboard();
628 if (!cursor.hasSelection() || !clipboard->supportsSelection())
629 return;
630 Q_Q(QWidgetTextControl);
631 QMimeData *data = q->createMimeDataFromSelection();
632 clipboard->setMimeData(data, QClipboard::Selection);
633}
634#endif
635
636void QWidgetTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor)
637{
638 Q_Q(QWidgetTextControl);
639 if (someCursor.isCopyOf(cursor)) {
640 emit q->cursorPositionChanged();
641 emit q->microFocusChanged();
642 }
643}
644
645void QWidgetTextControlPrivate::_q_contentsChanged(int from, int charsRemoved, int charsAdded)
646{
647#if QT_CONFIG(accessibility)
648 Q_Q(QWidgetTextControl);
649
650 if (QAccessible::isActive() && q->parent() && q->parent()->isWidgetType()) {
651 QTextCursor tmp(doc);
652 tmp.setPosition(from);
653 // when setting a new text document the length is off
654 // QTBUG-32583 - characterCount is off by 1 requires the -1
655 tmp.setPosition(qMin(doc->characterCount() - 1, from + charsAdded), QTextCursor::KeepAnchor);
656 QString newText = tmp.selectedText();
657
658 // always report the right number of removed chars, but in lack of the real string use spaces
659 QString oldText = QString(charsRemoved, u' ');
660
661 QAccessibleEvent *ev = nullptr;
662 if (charsRemoved == 0) {
663 ev = new QAccessibleTextInsertEvent(q->parent(), from, newText);
664 } else if (charsAdded == 0) {
665 ev = new QAccessibleTextRemoveEvent(q->parent(), from, oldText);
666 } else {
667 ev = new QAccessibleTextUpdateEvent(q->parent(), from, oldText, newText);
668 }
669 QAccessible::updateAccessibility(ev);
670 delete ev;
671 }
672#else
673 Q_UNUSED(from);
674 Q_UNUSED(charsRemoved);
675 Q_UNUSED(charsAdded);
676#endif
677}
678
680{
681 Q_Q(QWidgetTextControl);
682 QAbstractTextDocumentLayout *layout = doc->documentLayout();
683 QObject::connect(layout, &QAbstractTextDocumentLayout::update, q,
684 &QWidgetTextControl::updateRequest);
685 QObjectPrivate::connect(layout, &QAbstractTextDocumentLayout::updateBlock, this,
686 &QWidgetTextControlPrivate::_q_updateBlock);
687 QObject::connect(layout, &QAbstractTextDocumentLayout::documentSizeChanged, q,
688 &QWidgetTextControl::documentSizeChanged);
689}
690
692{
693 if (cursorVisible == visible)
694 return;
695
696 cursorVisible = visible;
698
699 if (cursorVisible)
700 connect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetTextControlPrivate::updateCursorBlinking);
701 else
702 disconnect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetTextControlPrivate::updateCursorBlinking);
703}
704
706{
707 cursorBlinkTimer.stop();
708 if (cursorVisible) {
709 int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
710 if (flashTime >= 2)
711 cursorBlinkTimer.start(flashTime / 2, q_func());
712 }
713
716}
717
718void QWidgetTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
719{
720 Q_Q(QWidgetTextControl);
721
722 // if inside the initial selected word keep that
723 if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart()
724 && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) {
725 q->setTextCursor(selectedWordOnDoubleClick);
726 return;
727 }
728
729 QTextCursor curs = selectedWordOnDoubleClick;
730 curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
731
732 if (!curs.movePosition(QTextCursor::StartOfWord))
733 return;
734 const int wordStartPos = curs.position();
735
736 const int blockPos = curs.block().position();
737 const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft();
738
739 QTextLine line = currentTextLine(curs);
740 if (!line.isValid())
741 return;
742
743 const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
744
745 if (!curs.movePosition(QTextCursor::EndOfWord))
746 return;
747 const int wordEndPos = curs.position();
748
749 const QTextLine otherLine = currentTextLine(curs);
750 if (otherLine.textStart() != line.textStart()
751 || wordEndPos == wordStartPos)
752 return;
753
754 const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x();
755
756 if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX))
757 return;
758
760 if (suggestedNewPosition < selectedWordOnDoubleClick.position()) {
761 cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
762 setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
763 } else {
764 cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
765 setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
766 }
767 } else {
768 // keep the already selected word even when moving to the left
769 // (#39164)
770 if (suggestedNewPosition < selectedWordOnDoubleClick.position())
771 cursor.setPosition(selectedWordOnDoubleClick.selectionEnd());
772 else
773 cursor.setPosition(selectedWordOnDoubleClick.selectionStart());
774
775 const qreal differenceToStart = mouseXPosition - wordStartX;
776 const qreal differenceToEnd = wordEndX - mouseXPosition;
777
778 if (differenceToStart < differenceToEnd)
779 setCursorPosition(wordStartPos, QTextCursor::KeepAnchor);
780 else
781 setCursorPosition(wordEndPos, QTextCursor::KeepAnchor);
782 }
783
784 if (interactionFlags & Qt::TextSelectableByMouse) {
785#ifndef QT_NO_CLIPBOARD
787#endif
789 }
790}
791
793{
794 Q_Q(QWidgetTextControl);
795
796 // if inside the initial selected line keep that
797 if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart()
798 && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) {
799 q->setTextCursor(selectedBlockOnTrippleClick);
800 return;
801 }
802
803 if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) {
804 cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd());
805 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
806 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
807 } else {
808 cursor.setPosition(selectedBlockOnTrippleClick.selectionStart());
809 cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor);
810 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
811 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
812 }
813
814 if (interactionFlags & Qt::TextSelectableByMouse) {
815#ifndef QT_NO_CLIPBOARD
817#endif
819 }
820}
821
823{
824 if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection())
825 return;
826 cursor.removeSelectedText();
827}
828
829void QWidgetTextControl::undo()
830{
831 Q_D(QWidgetTextControl);
832 d->repaintSelection();
833 const int oldCursorPos = d->cursor.position();
834 d->doc->undo(&d->cursor);
835 if (d->cursor.position() != oldCursorPos)
836 emit cursorPositionChanged();
837 emit microFocusChanged();
838 ensureCursorVisible();
839}
840
841void QWidgetTextControl::redo()
842{
843 Q_D(QWidgetTextControl);
844 d->repaintSelection();
845 const int oldCursorPos = d->cursor.position();
846 d->doc->redo(&d->cursor);
847 if (d->cursor.position() != oldCursorPos)
848 emit cursorPositionChanged();
849 emit microFocusChanged();
850 ensureCursorVisible();
851}
852
853QWidgetTextControl::QWidgetTextControl(QObject *parent)
854 : QInputControl(QInputControl::TextEdit, *new QWidgetTextControlPrivate, parent)
855{
856 Q_D(QWidgetTextControl);
857 d->init();
858}
859
860QWidgetTextControl::QWidgetTextControl(const QString &text, QObject *parent)
861 : QInputControl(QInputControl::TextEdit, *new QWidgetTextControlPrivate, parent)
862{
863 Q_D(QWidgetTextControl);
864 d->init(Qt::RichText, text);
865}
866
867QWidgetTextControl::QWidgetTextControl(QTextDocument *doc, QObject *parent)
868 : QInputControl(QInputControl::TextEdit, *new QWidgetTextControlPrivate, parent)
869{
870 Q_D(QWidgetTextControl);
871 d->init(Qt::RichText, QString(), doc);
872}
873
874QWidgetTextControl::~QWidgetTextControl()
875{
876}
877
878void QWidgetTextControl::setDocument(QTextDocument *document)
879{
880 Q_D(QWidgetTextControl);
881 if (d->doc == document)
882 return;
883
884 d->doc->disconnect(this);
885 d->doc->documentLayout()->disconnect(this);
886 d->doc->documentLayout()->setPaintDevice(nullptr);
887
888 if (d->doc->parent() == this)
889 delete d->doc;
890
891 d->doc = nullptr;
892 d->setContent(Qt::RichText, QString(), document);
893}
894
895QTextDocument *QWidgetTextControl::document() const
896{
897 Q_D(const QWidgetTextControl);
898 return d->doc;
899}
900
901void QWidgetTextControl::setTextCursor(const QTextCursor &cursor, bool selectionClipboard)
902{
903 Q_D(QWidgetTextControl);
904 d->cursorIsFocusIndicator = false;
905 const bool posChanged = cursor.position() != d->cursor.position();
906 const QTextCursor oldSelection = d->cursor;
907 d->cursor = cursor;
908 d->cursorOn = d->hasFocus
909 && (d->interactionFlags & (Qt::TextSelectableByKeyboard | Qt::TextEditable));
910 d->_q_updateCurrentCharFormatAndSelection();
911 ensureCursorVisible();
912 d->repaintOldAndNewSelection(oldSelection);
913 if (posChanged)
914 emit cursorPositionChanged();
915
916#ifndef QT_NO_CLIPBOARD
917 if (selectionClipboard)
918 d->setClipboardSelection();
919#else
920 Q_UNUSED(selectionClipboard);
921#endif
922}
923
924QTextCursor QWidgetTextControl::textCursor() const
925{
926 Q_D(const QWidgetTextControl);
927 return d->cursor;
928}
929
930#ifndef QT_NO_CLIPBOARD
931
932void QWidgetTextControl::cut()
933{
934 Q_D(QWidgetTextControl);
935 if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection())
936 return;
937 copy();
938 d->cursor.removeSelectedText();
939}
940
941void QWidgetTextControl::copy()
942{
943 Q_D(QWidgetTextControl);
944 if (!d->cursor.hasSelection())
945 return;
946 QMimeData *data = createMimeDataFromSelection();
947 QGuiApplication::clipboard()->setMimeData(data);
948}
949
950void QWidgetTextControl::paste(QClipboard::Mode mode)
951{
952 const QMimeData *md = QGuiApplication::clipboard()->mimeData(mode);
953 if (md)
954 insertFromMimeData(md);
955}
956#endif
957
958void QWidgetTextControl::clear()
959{
960 Q_D(QWidgetTextControl);
961 // clears and sets empty content
962 d->extraSelections.clear();
963 d->setContent();
964}
965
966
967void QWidgetTextControl::selectAll()
968{
969 Q_D(QWidgetTextControl);
970 const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor());
971 const int oldCursorPos = d->cursor.position();
972 d->cursor.select(QTextCursor::Document);
973 d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor()));
974 d->cursorIsFocusIndicator = false;
975 if (d->cursor.position() != oldCursorPos)
976 emit cursorPositionChanged();
977 emit updateRequest();
978}
979
980void QWidgetTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset, QWidget *contextWidget)
981{
982 QTransform t;
983 t.translate(coordinateOffset.x(), coordinateOffset.y());
984 processEvent(e, t, contextWidget);
985}
986
987void QWidgetTextControl::processEvent(QEvent *e, const QTransform &transform, QWidget *contextWidget)
988{
989 Q_D(QWidgetTextControl);
990 if (d->interactionFlags == Qt::NoTextInteraction) {
991 e->ignore();
992 return;
993 }
994
995 d->contextWidget = contextWidget;
996
997 if (!d->contextWidget) {
998 switch (e->type()) {
999#if QT_CONFIG(graphicsview)
1000 case QEvent::GraphicsSceneMouseMove:
1001 case QEvent::GraphicsSceneMousePress:
1002 case QEvent::GraphicsSceneMouseRelease:
1003 case QEvent::GraphicsSceneMouseDoubleClick:
1004 case QEvent::GraphicsSceneContextMenu:
1005 case QEvent::GraphicsSceneHoverEnter:
1006 case QEvent::GraphicsSceneHoverMove:
1007 case QEvent::GraphicsSceneHoverLeave:
1008 case QEvent::GraphicsSceneHelp:
1009 case QEvent::GraphicsSceneDragEnter:
1010 case QEvent::GraphicsSceneDragMove:
1011 case QEvent::GraphicsSceneDragLeave:
1012 case QEvent::GraphicsSceneDrop: {
1013 QGraphicsSceneEvent *ev = static_cast<QGraphicsSceneEvent *>(e);
1014 d->contextWidget = ev->widget();
1015 break;
1016 }
1017#endif // QT_CONFIG(graphicsview)
1018 default: break;
1019 };
1020 }
1021
1022 switch (e->type()) {
1023 case QEvent::KeyPress:
1024 d->keyPressEvent(static_cast<QKeyEvent *>(e));
1025 break;
1026 case QEvent::MouseButtonPress: {
1027 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
1028 d->mousePressEvent(ev, ev->button(), transform.map(ev->position()), ev->modifiers(),
1029 ev->buttons(), ev->globalPosition());
1030 break; }
1031 case QEvent::Enter:
1032 d->updateHighlightedAnchor(transform.map(static_cast<QEnterEvent *>(e)->position()));
1033 break;
1034 case QEvent::Leave:
1035 d->resetHighlightedAnchor();
1036 break;
1037 case QEvent::MouseMove: {
1038 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
1039 d->mouseMoveEvent(ev, ev->button(), transform.map(ev->position()), ev->modifiers(),
1040 ev->buttons(), ev->globalPosition());
1041 break; }
1042 case QEvent::MouseButtonRelease: {
1043 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
1044 d->mouseReleaseEvent(ev, ev->button(), transform.map(ev->position()), ev->modifiers(),
1045 ev->buttons(), ev->globalPosition());
1046 break; }
1047 case QEvent::MouseButtonDblClick: {
1048 QMouseEvent *ev = static_cast<QMouseEvent *>(e);
1049 d->mouseDoubleClickEvent(ev, ev->button(), transform.map(ev->position()), ev->modifiers(),
1050 ev->buttons(), ev->globalPosition());
1051 break; }
1052 case QEvent::InputMethod:
1053 d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
1054 break;
1055#ifndef QT_NO_CONTEXTMENU
1056 case QEvent::ContextMenu: {
1057 QContextMenuEvent *ev = static_cast<QContextMenuEvent *>(e);
1058 d->contextMenuEvent(ev->globalPos(), transform.map(ev->pos()), contextWidget);
1059 break; }
1060#endif // QT_NO_CONTEXTMENU
1061 case QEvent::FocusIn:
1062 case QEvent::FocusOut:
1063 d->focusEvent(static_cast<QFocusEvent *>(e));
1064 break;
1065
1066 case QEvent::EnabledChange:
1067 d->isEnabled = e->isAccepted();
1068 break;
1069
1070#if QT_CONFIG(tooltip)
1071 case QEvent::ToolTip: {
1072 QHelpEvent *ev = static_cast<QHelpEvent *>(e);
1073 d->showToolTip(ev->globalPos(), transform.map(ev->pos()), contextWidget);
1074 break;
1075 }
1076#endif // QT_CONFIG(tooltip)
1077
1078#if QT_CONFIG(draganddrop)
1079 case QEvent::DragEnter: {
1080 QDragEnterEvent *ev = static_cast<QDragEnterEvent *>(e);
1081 if (d->dragEnterEvent(e, ev->mimeData()))
1082 ev->acceptProposedAction();
1083 break;
1084 }
1085 case QEvent::DragLeave:
1086 d->dragLeaveEvent();
1087 break;
1088 case QEvent::DragMove: {
1089 QDragMoveEvent *ev = static_cast<QDragMoveEvent *>(e);
1090 if (d->dragMoveEvent(e, ev->mimeData(), transform.map(ev->position())))
1091 ev->acceptProposedAction();
1092 break;
1093 }
1094 case QEvent::Drop: {
1095 QDropEvent *ev = static_cast<QDropEvent *>(e);
1096 if (d->dropEvent(ev->mimeData(), transform.map(ev->position()), ev->dropAction(), ev->source()))
1097 ev->acceptProposedAction();
1098 break;
1099 }
1100#endif
1101
1102#if QT_CONFIG(graphicsview)
1103 case QEvent::GraphicsSceneMousePress: {
1104 QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
1105 d->mousePressEvent(ev, ev->button(), transform.map(ev->pos()), ev->modifiers(), ev->buttons(),
1106 ev->screenPos());
1107 break; }
1108 case QEvent::GraphicsSceneMouseMove: {
1109 QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
1110 d->mouseMoveEvent(ev, ev->button(), transform.map(ev->pos()), ev->modifiers(), ev->buttons(),
1111 ev->screenPos());
1112 break; }
1113 case QEvent::GraphicsSceneMouseRelease: {
1114 QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
1115 d->mouseReleaseEvent(ev, ev->button(), transform.map(ev->pos()), ev->modifiers(), ev->buttons(),
1116 ev->screenPos());
1117 break; }
1118 case QEvent::GraphicsSceneMouseDoubleClick: {
1119 QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
1120 d->mouseDoubleClickEvent(ev, ev->button(), transform.map(ev->pos()), ev->modifiers(), ev->buttons(),
1121 ev->screenPos());
1122 break; }
1123 case QEvent::GraphicsSceneContextMenu: {
1124 QGraphicsSceneContextMenuEvent *ev = static_cast<QGraphicsSceneContextMenuEvent *>(e);
1125 d->contextMenuEvent(ev->screenPos(), transform.map(ev->pos()), contextWidget);
1126 break; }
1127
1128 case QEvent::GraphicsSceneHoverMove: {
1129 QGraphicsSceneHoverEvent *ev = static_cast<QGraphicsSceneHoverEvent *>(e);
1130 d->mouseMoveEvent(ev, Qt::NoButton, transform.map(ev->pos()), ev->modifiers(),Qt::NoButton,
1131 ev->screenPos());
1132 break; }
1133
1134 case QEvent::GraphicsSceneDragEnter: {
1135 QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e);
1136 if (d->dragEnterEvent(e, ev->mimeData()))
1137 ev->acceptProposedAction();
1138 break; }
1139 case QEvent::GraphicsSceneDragLeave:
1140 d->dragLeaveEvent();
1141 break;
1142 case QEvent::GraphicsSceneDragMove: {
1143 QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e);
1144 if (d->dragMoveEvent(e, ev->mimeData(), transform.map(ev->pos())))
1145 ev->acceptProposedAction();
1146 break; }
1147 case QEvent::GraphicsSceneDrop: {
1148 QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e);
1149 if (d->dropEvent(ev->mimeData(), transform.map(ev->pos()), ev->dropAction(), ev->source()))
1150 ev->accept();
1151 break; }
1152#endif // QT_CONFIG(graphicsview)
1153 case QEvent::ShortcutOverride:
1154 if (d->interactionFlags & Qt::TextEditable) {
1155 QKeyEvent* ke = static_cast<QKeyEvent *>(e);
1156 if (isCommonTextEditShortcut(ke))
1157 ke->accept();
1158 }
1159 break;
1160 default:
1161 break;
1162 }
1163}
1164
1165bool QWidgetTextControl::event(QEvent *e)
1166{
1167 return QObject::event(e);
1168}
1169
1170void QWidgetTextControl::timerEvent(QTimerEvent *e)
1171{
1172 Q_D(QWidgetTextControl);
1173 if (e->timerId() == d->cursorBlinkTimer.timerId()) {
1174 d->cursorOn = !d->cursorOn;
1175
1176 if (d->cursor.hasSelection())
1177 d->cursorOn &= (QApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected)
1178 != 0);
1179
1180 d->repaintCursor();
1181 } else if (e->timerId() == d->trippleClickTimer.timerId()) {
1182 d->trippleClickTimer.stop();
1183 }
1184}
1185
1186void QWidgetTextControl::setPlainText(const QString &text)
1187{
1188 Q_D(QWidgetTextControl);
1189 d->setContent(Qt::PlainText, text);
1190}
1191
1192#if QT_CONFIG(textmarkdownreader)
1193void QWidgetTextControl::setMarkdown(const QString &text)
1194{
1195 Q_D(QWidgetTextControl);
1196 d->setContent(Qt::MarkdownText, text);
1197}
1198#endif
1199
1200void QWidgetTextControl::setHtml(const QString &text)
1201{
1202 Q_D(QWidgetTextControl);
1203 d->setContent(Qt::RichText, text);
1204}
1205
1206void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e)
1207{
1208 Q_Q(QWidgetTextControl);
1209#ifndef QT_NO_SHORTCUT
1210 if (e == QKeySequence::SelectAll) {
1211 e->accept();
1212 q->selectAll();
1213#ifndef QT_NO_CLIPBOARD
1214 setClipboardSelection();
1215#endif
1216 return;
1217 }
1218#ifndef QT_NO_CLIPBOARD
1219 else if (e == QKeySequence::Copy) {
1220 e->accept();
1221 q->copy();
1222 return;
1223 }
1224#endif
1225#endif // QT_NO_SHORTCUT
1226
1227 if (interactionFlags & Qt::TextSelectableByKeyboard
1228 && cursorMoveKeyEvent(e))
1229 goto accept;
1230
1231 if (interactionFlags & Qt::LinksAccessibleByKeyboard) {
1232 if ((e->key() == Qt::Key_Return
1233 || e->key() == Qt::Key_Enter)
1234 && cursor.hasSelection()) {
1235
1236 e->accept();
1237 activateLinkUnderCursor();
1238 return;
1239 }
1240 }
1241
1242 if (!(interactionFlags & Qt::TextEditable)) {
1243 e->ignore();
1244 return;
1245 }
1246
1247 if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) {
1248 QTextBlockFormat fmt;
1249 fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
1250 cursor.mergeBlockFormat(fmt);
1251 goto accept;
1252 }
1253
1254 // schedule a repaint of the region of the cursor, as when we move it we
1255 // want to make sure the old cursor disappears (not noticeable when moving
1256 // only a few pixels but noticeable when jumping between cells in tables for
1257 // example)
1258 repaintSelection();
1259
1260 if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~(Qt::ShiftModifier | Qt::GroupSwitchModifier))) {
1261 QTextBlockFormat blockFmt = cursor.blockFormat();
1262 QTextList *list = cursor.currentList();
1263 if (list && cursor.atBlockStart() && !cursor.hasSelection()) {
1264 list->remove(cursor.block());
1265 } else if (cursor.atBlockStart() && blockFmt.indent() > 0) {
1266 blockFmt.setIndent(blockFmt.indent() - 1);
1267 cursor.setBlockFormat(blockFmt);
1268 } else {
1269 QTextCursor localCursor = cursor;
1270 localCursor.deletePreviousChar();
1271 if (cursor.d)
1272 cursor.d->setX();
1273 }
1274 goto accept;
1275 }
1276#ifndef QT_NO_SHORTCUT
1277 else if (e == QKeySequence::InsertParagraphSeparator) {
1278 insertParagraphSeparator();
1279 e->accept();
1280 goto accept;
1281 } else if (e == QKeySequence::InsertLineSeparator) {
1282 cursor.insertText(QString(QChar::LineSeparator));
1283 e->accept();
1284 goto accept;
1285 }
1286#endif
1287 if (false) {
1288 }
1289#ifndef QT_NO_SHORTCUT
1290 else if (e == QKeySequence::Undo) {
1291 q->undo();
1292 }
1293 else if (e == QKeySequence::Redo) {
1294 q->redo();
1295 }
1296#ifndef QT_NO_CLIPBOARD
1297 else if (e == QKeySequence::Cut) {
1298 q->cut();
1299 }
1300 else if (e == QKeySequence::Paste) {
1301 QClipboard::Mode mode = QClipboard::Clipboard;
1302 if (QGuiApplication::clipboard()->supportsSelection()) {
1303 if (e->modifiers() == (Qt::CTRL | Qt::SHIFT) && e->key() == Qt::Key_Insert)
1304 mode = QClipboard::Selection;
1305 }
1306 q->paste(mode);
1307 }
1308#endif
1309 else if (e == QKeySequence::Delete) {
1310 QTextCursor localCursor = cursor;
1311 localCursor.deleteChar();
1312 if (cursor.d)
1313 cursor.d->setX();
1314 } else if (e == QKeySequence::Backspace) {
1315 QTextCursor localCursor = cursor;
1316 localCursor.deletePreviousChar();
1317 if (cursor.d)
1318 cursor.d->setX();
1319 }else if (e == QKeySequence::DeleteEndOfWord) {
1320 if (!cursor.hasSelection())
1321 cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
1322 cursor.removeSelectedText();
1323 }
1324 else if (e == QKeySequence::DeleteStartOfWord) {
1325 if (!cursor.hasSelection())
1326 cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
1327 cursor.removeSelectedText();
1328 }
1329 else if (e == QKeySequence::DeleteEndOfLine) {
1330 QTextBlock block = cursor.block();
1331 if (cursor.position() == block.position() + block.length() - 2)
1332 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
1333 else
1334 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1335 cursor.removeSelectedText();
1336 }
1337#endif // QT_NO_SHORTCUT
1338 else {
1339 goto process;
1340 }
1341 goto accept;
1342
1343process:
1344 {
1345 if (q->isAcceptableInput(e)) {
1346 if (overwriteMode
1347 // no need to call deleteChar() if we have a selection, insertText
1348 // does it already
1349 && !cursor.hasSelection()
1350 && !cursor.atBlockEnd())
1351 cursor.deleteChar();
1352
1353 cursor.insertText(e->text());
1354 selectionChanged();
1355 } else {
1356 e->ignore();
1357 return;
1358 }
1359 }
1360
1361 accept:
1362
1363#ifndef QT_NO_CLIPBOARD
1364 setClipboardSelection();
1365#endif
1366
1367 e->accept();
1368 cursorOn = true;
1369
1370 q->ensureCursorVisible();
1371
1372 updateCurrentCharFormat();
1373}
1374
1375QVariant QWidgetTextControl::loadResource(int type, const QUrl &name)
1376{
1377 Q_UNUSED(type);
1378 Q_UNUSED(name);
1379 return QVariant();
1380}
1381
1382void QWidgetTextControlPrivate::_q_updateBlock(const QTextBlock &block)
1383{
1384 Q_Q(QWidgetTextControl);
1385 QRectF br = q->blockBoundingRect(block);
1386 br.setRight(qreal(INT_MAX)); // the block might have shrunk
1387 emit q->updateRequest(br);
1388}
1389
1391{
1392 Q_Q(const QWidgetTextControl);
1393 const QTextBlock block = doc->findBlock(position);
1394 if (!block.isValid())
1395 return QRectF();
1396 const QAbstractTextDocumentLayout *docLayout = doc->documentLayout();
1397 const QTextLayout *layout = block.layout();
1398 const QPointF layoutPos = q->blockBoundingRect(block).topLeft();
1399 int relativePos = position - block.position();
1400 if (preeditCursor != 0) {
1401 int preeditPos = layout->preeditAreaPosition();
1402 if (relativePos == preeditPos)
1403 relativePos += preeditCursor;
1404 else if (relativePos > preeditPos)
1405 relativePos += layout->preeditAreaText().size();
1406 }
1407 QTextLine line = layout->lineForTextPosition(relativePos);
1408
1409 int cursorWidth;
1410 {
1411 bool ok = false;
1412 cursorWidth = docLayout->property("cursorWidth").toInt(&ok);
1413 if (!ok)
1414 cursorWidth = 1;
1415 }
1416
1417 QRectF r;
1418
1419 if (line.isValid()) {
1420 qreal x = line.cursorToX(relativePos);
1421 qreal w = 0;
1422 if (overwriteMode) {
1423 if (relativePos < line.textLength() - line.textStart())
1424 w = line.cursorToX(relativePos + 1) - x;
1425 else
1426 w = QFontMetrics(block.layout()->font()).horizontalAdvance(u' '); // in sync with QTextLine::draw()
1427 }
1428 r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(),
1429 cursorWidth + w, line.height());
1430 } else {
1431 r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height
1432 }
1433
1434 return r;
1435}
1436
1437namespace {
1438struct QTextFrameComparator {
1439 bool operator()(QTextFrame *frame, int position) { return frame->firstPosition() < position; }
1440 bool operator()(int position, QTextFrame *frame) { return position < frame->firstPosition(); }
1441};
1442}
1443
1444static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor)
1445{
1446 QRectF r;
1447 QTextFrame *frame = cursor.currentFrame();
1448 const QList<QTextFrame *> children = frame->childFrames();
1449
1450 const QList<QTextFrame *>::ConstIterator firstFrame = std::lower_bound(children.constBegin(), children.constEnd(),
1451 cursor.selectionStart(), QTextFrameComparator());
1452 const QList<QTextFrame *>::ConstIterator lastFrame = std::upper_bound(children.constBegin(), children.constEnd(),
1453 cursor.selectionEnd(), QTextFrameComparator());
1454 for (QList<QTextFrame *>::ConstIterator it = firstFrame; it != lastFrame; ++it) {
1455 if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow)
1456 r |= frame->document()->documentLayout()->frameBoundingRect(*it);
1457 }
1458 return r;
1459}
1460
1461QRectF QWidgetTextControl::selectionRect(const QTextCursor &cursor) const
1462{
1463 Q_D(const QWidgetTextControl);
1464
1465 QRectF r = d->rectForPosition(cursor.selectionStart());
1466
1467 if (cursor.hasComplexSelection() && cursor.currentTable()) {
1468 QTextTable *table = cursor.currentTable();
1469
1470 r = d->doc->documentLayout()->frameBoundingRect(table);
1471 /*
1472 int firstRow, numRows, firstColumn, numColumns;
1473 cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
1474
1475 const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn);
1476 const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1);
1477
1478 const QAbstractTextDocumentLayout * const layout = doc->documentLayout();
1479
1480 QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block());
1481
1482 for (int col = firstColumn; col < firstColumn + numColumns; ++col) {
1483 const QTextTableCell cell = table->cellAt(firstRow, col);
1484 const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top();
1485
1486 tableSelRect.setTop(qMin(tableSelRect.top(), y));
1487 }
1488
1489 for (int row = firstRow; row < firstRow + numRows; ++row) {
1490 const QTextTableCell cell = table->cellAt(row, firstColumn);
1491 const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left();
1492
1493 tableSelRect.setLeft(qMin(tableSelRect.left(), x));
1494 }
1495
1496 for (int col = firstColumn; col < firstColumn + numColumns; ++col) {
1497 const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col);
1498 const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom();
1499
1500 tableSelRect.setBottom(qMax(tableSelRect.bottom(), y));
1501 }
1502
1503 for (int row = firstRow; row < firstRow + numRows; ++row) {
1504 const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1);
1505 const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right();
1506
1507 tableSelRect.setRight(qMax(tableSelRect.right(), x));
1508 }
1509
1510 r = tableSelRect.toRect();
1511 */
1512 } else if (cursor.hasSelection()) {
1513 const int position = cursor.selectionStart();
1514 const int anchor = cursor.selectionEnd();
1515 const QTextBlock posBlock = d->doc->findBlock(position);
1516 const QTextBlock anchorBlock = d->doc->findBlock(anchor);
1517 if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) {
1518 const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position());
1519 const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position());
1520
1521 const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber());
1522 const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber());
1523 const QTextLayout *layout = posBlock.layout();
1524 r = QRectF();
1525 for (int i = firstLine; i <= lastLine; ++i) {
1526 r |= layout->lineAt(i).rect();
1527 r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled
1528 }
1529 r.translate(blockBoundingRect(posBlock).topLeft());
1530 } else {
1531 QRectF anchorRect = d->rectForPosition(cursor.selectionEnd());
1532 r |= anchorRect;
1533 r |= boundingRectOfFloatsInSelection(cursor);
1534 QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame()));
1535 r.setLeft(frameRect.left());
1536 r.setRight(frameRect.right());
1537 }
1538 if (r.isValid())
1539 r.adjust(-1, -1, 1, 1);
1540 }
1541
1542 return r;
1543}
1544
1545QRectF QWidgetTextControl::selectionRect() const
1546{
1547 Q_D(const QWidgetTextControl);
1548 return selectionRect(d->cursor);
1549}
1550
1551void QWidgetTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers,
1552 Qt::MouseButtons buttons, const QPointF &globalPos)
1553{
1554 Q_Q(QWidgetTextControl);
1555
1556 mousePressPos = pos;
1557
1558#if QT_CONFIG(draganddrop)
1559 mightStartDrag = false;
1560#endif
1561
1562 if (sendMouseEventToInputContext(
1563 e, QEvent::MouseButtonPress, button, pos, modifiers, buttons, globalPos)) {
1564 return;
1565 }
1566
1567 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1568 anchorOnMousePress = q->anchorAt(pos);
1569
1571 cursorIsFocusIndicator = false;
1573 cursor.clearSelection();
1574 }
1575 }
1576 if (!(button & Qt::LeftButton) ||
1577 !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) {
1578 e->ignore();
1579 return;
1580 }
1581 bool wasValid = blockWithMarkerUnderMouse.isValid();
1582 blockWithMarkerUnderMouse = q->blockWithMarkerAt(pos);
1583 if (wasValid != blockWithMarkerUnderMouse.isValid())
1584 emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
1585
1586
1587 cursorIsFocusIndicator = false;
1588 const QTextCursor oldSelection = cursor;
1589 const int oldCursorPos = cursor.position();
1590
1591 mousePressed = (interactionFlags & Qt::TextSelectableByMouse);
1592
1594
1595 if (trippleClickTimer.isActive()
1596 && ((pos - trippleClickPoint).manhattanLength() < QApplication::startDragDistance())) {
1597
1598 cursor.movePosition(QTextCursor::StartOfBlock);
1599 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1600 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1601 selectedBlockOnTrippleClick = cursor;
1602
1603 anchorOnMousePress = QString();
1604 blockWithMarkerUnderMouse = QTextBlock();
1605 emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
1606
1607 trippleClickTimer.stop();
1608 } else {
1609 int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
1610 if (cursorPos == -1) {
1611 e->ignore();
1612 return;
1613 }
1614
1615 if (modifiers == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) {
1616 if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1617 selectedWordOnDoubleClick = cursor;
1618 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1619 }
1620
1621 if (selectedBlockOnTrippleClick.hasSelection())
1623 else if (selectedWordOnDoubleClick.hasSelection())
1624 extendWordwiseSelection(cursorPos, pos.x());
1625 else if (!wordSelectionEnabled)
1626 setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
1627 } else {
1628
1629 if (dragEnabled
1630 && cursor.hasSelection()
1631 && !cursorIsFocusIndicator
1632 && cursorPos >= cursor.selectionStart()
1633 && cursorPos <= cursor.selectionEnd()
1634 && q->hitTest(pos, Qt::ExactHit) != -1) {
1635#if QT_CONFIG(draganddrop)
1636 mightStartDrag = true;
1637#endif
1638 return;
1639 }
1640
1641 setCursorPosition(cursorPos);
1642 }
1643 }
1644
1645 if (interactionFlags & Qt::TextEditable) {
1646 q->ensureCursorVisible();
1647 if (cursor.position() != oldCursorPos)
1648 emit q->cursorPositionChanged();
1650 } else {
1651 if (cursor.position() != oldCursorPos) {
1652 emit q->cursorPositionChanged();
1653 emit q->microFocusChanged();
1654 }
1656 }
1657 repaintOldAndNewSelection(oldSelection);
1658 hadSelectionOnMousePress = cursor.hasSelection();
1659}
1660
1661void QWidgetTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &mousePos, Qt::KeyboardModifiers modifiers,
1662 Qt::MouseButtons buttons, const QPointF &globalPos)
1663{
1664 Q_Q(QWidgetTextControl);
1665
1666 if (interactionFlags & Qt::LinksAccessibleByMouse)
1667 updateHighlightedAnchor(mousePos);
1668
1669 if (buttons & Qt::LeftButton) {
1670 const bool editable = interactionFlags & Qt::TextEditable;
1671
1672 if (!(mousePressed
1673 || editable
1674 || mightStartDrag
1675 || selectedWordOnDoubleClick.hasSelection()
1676 || selectedBlockOnTrippleClick.hasSelection()))
1677 return;
1678
1679 const QTextCursor oldSelection = cursor;
1680 const int oldCursorPos = cursor.position();
1681
1682 if (mightStartDrag) {
1683 if ((mousePos - mousePressPos).manhattanLength() > QApplication::startDragDistance())
1685 return;
1686 }
1687
1688 const qreal mouseX = qreal(mousePos.x());
1689
1690 int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1691
1692 if (isPreediting()) {
1693 // note: oldCursorPos not including preedit
1694 int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1695
1696 if (newCursorPos != selectionStartPos) {
1698 // commit invalidates positions
1699 newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
1700 selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit);
1701 setCursorPosition(selectionStartPos);
1702 }
1703 }
1704
1705 if (newCursorPos == -1)
1706 return;
1707
1708 if (mousePressed && wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) {
1709 selectedWordOnDoubleClick = cursor;
1710 selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor);
1711 }
1712
1713 if (selectedBlockOnTrippleClick.hasSelection())
1714 extendBlockwiseSelection(newCursorPos);
1715 else if (selectedWordOnDoubleClick.hasSelection())
1716 extendWordwiseSelection(newCursorPos, mouseX);
1717 else if (mousePressed && !isPreediting())
1718 setCursorPosition(newCursorPos, QTextCursor::KeepAnchor);
1719
1720 if (interactionFlags & Qt::TextEditable) {
1721 // don't call ensureVisible for the visible cursor to avoid jumping
1722 // scrollbars. the autoscrolling ensures smooth scrolling if necessary.
1723 //q->ensureCursorVisible();
1724 if (cursor.position() != oldCursorPos)
1725 emit q->cursorPositionChanged();
1727#ifndef QT_NO_IM
1728 if (contextWidget)
1729 QGuiApplication::inputMethod()->update(Qt::ImQueryInput);
1730#endif //QT_NO_IM
1731 } else {
1732 //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1)));
1733 if (cursor.position() != oldCursorPos) {
1734 emit q->cursorPositionChanged();
1735 emit q->microFocusChanged();
1736 }
1737 }
1739 repaintOldAndNewSelection(oldSelection);
1740 } else {
1741 bool wasValid = blockWithMarkerUnderMouse.isValid();
1742 blockWithMarkerUnderMouse = q->blockWithMarkerAt(mousePos);
1743 if (wasValid != blockWithMarkerUnderMouse.isValid())
1744 emit q->blockMarkerHovered(blockWithMarkerUnderMouse);
1745 }
1746
1747 sendMouseEventToInputContext(e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos);
1748}
1749
1750void QWidgetTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers,
1751 Qt::MouseButtons buttons, const QPointF &globalPos)
1752{
1753 Q_Q(QWidgetTextControl);
1754
1755 const QTextCursor oldSelection = cursor;
1756 if (sendMouseEventToInputContext(
1757 e, QEvent::MouseButtonRelease, button, pos, modifiers, buttons, globalPos)) {
1758 repaintOldAndNewSelection(oldSelection);
1759 return;
1760 }
1761
1762 const int oldCursorPos = cursor.position();
1763
1764#if QT_CONFIG(draganddrop)
1765 if (mightStartDrag && (button & Qt::LeftButton)) {
1766 mousePressed = false;
1767 setCursorPosition(pos);
1768 cursor.clearSelection();
1769 selectionChanged();
1770 }
1771#endif
1772 if (mousePressed) {
1773 mousePressed = false;
1774#ifndef QT_NO_CLIPBOARD
1777 } else if (button == Qt::MiddleButton
1778 && (interactionFlags & Qt::TextEditable)
1779 && QGuiApplication::clipboard()->supportsSelection()) {
1780 setCursorPosition(pos);
1781 const QMimeData *md = QGuiApplication::clipboard()->mimeData(QClipboard::Selection);
1782 if (md)
1783 q->insertFromMimeData(md);
1784#endif
1785 }
1786
1787 repaintOldAndNewSelection(oldSelection);
1788
1789 if (cursor.position() != oldCursorPos) {
1790 emit q->cursorPositionChanged();
1791 emit q->microFocusChanged();
1792 }
1793
1794 // toggle any checkbox that the user clicks
1795 if ((interactionFlags & Qt::TextEditable) && (button & Qt::LeftButton) &&
1796 (blockWithMarkerUnderMouse.isValid()) && !cursor.hasSelection()) {
1797 QTextBlock markerBlock = q->blockWithMarkerAt(pos);
1798 if (markerBlock == blockWithMarkerUnderMouse) {
1799 auto fmt = blockWithMarkerUnderMouse.blockFormat();
1800 switch (fmt.marker()) {
1801 case QTextBlockFormat::MarkerType::Unchecked :
1802 fmt.setMarker(QTextBlockFormat::MarkerType::Checked);
1803 break;
1804 case QTextBlockFormat::MarkerType::Checked:
1805 fmt.setMarker(QTextBlockFormat::MarkerType::Unchecked);
1806 break;
1807 default:
1808 break;
1809 }
1810 cursor.setBlockFormat(fmt);
1811 }
1812 }
1813
1814 if (interactionFlags & Qt::LinksAccessibleByMouse) {
1815
1816 // Ignore event unless left button has been pressed
1817 if (!(button & Qt::LeftButton)) {
1818 e->ignore();
1819 return;
1820 }
1821
1822 const QString anchor = q->anchorAt(pos);
1823
1824 // Ignore event without selection anchor
1825 if (anchor.isEmpty()) {
1826 e->ignore();
1827 return;
1828 }
1829
1830 if (!cursor.hasSelection()
1831 || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) {
1832
1833 const int anchorPos = q->hitTest(pos, Qt::ExactHit);
1834
1835 // Ignore event without valid anchor position
1836 if (anchorPos < 0) {
1837 e->ignore();
1838 return;
1839 }
1840
1841 cursor.setPosition(anchorPos);
1842 activateLinkUnderCursor(std::exchange(anchorOnMousePress, QString()));
1843 }
1844 }
1845}
1846
1847void QWidgetTextControlPrivate::mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos,
1848 Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons,
1849 const QPointF &globalPos)
1850{
1851 Q_Q(QWidgetTextControl);
1852
1853 if (button == Qt::LeftButton
1854 && (interactionFlags & Qt::TextSelectableByMouse)) {
1855
1856#if QT_CONFIG(draganddrop)
1857 mightStartDrag = false;
1858#endif
1860
1861 const QTextCursor oldSelection = cursor;
1862 setCursorPosition(pos);
1863 QTextLine line = currentTextLine(cursor);
1864 bool doEmit = false;
1865 if (line.isValid() && line.textLength()) {
1866 cursor.select(QTextCursor::WordUnderCursor);
1867 doEmit = true;
1868 }
1870 repaintOldAndNewSelection(oldSelection);
1871 else {
1872 // When the focus indicator is active (link selected with outline style),
1873 // we need to repaint the entire old and new selection areas rather than
1874 // just the difference. The visual style changes from outline to background
1875 // highlight, but repaintOldAndNewSelection's optimization would skip the
1876 // repaint when the text range is unchanged.
1877 emit q->updateRequest(q->selectionRect(oldSelection));
1879 }
1880
1881 cursorIsFocusIndicator = false;
1882 selectedWordOnDoubleClick = cursor;
1883
1884 trippleClickPoint = pos;
1885 trippleClickTimer.start(QApplication::doubleClickInterval(), q);
1886 if (doEmit) {
1888#ifndef QT_NO_CLIPBOARD
1890#endif
1891 emit q->cursorPositionChanged();
1892 }
1893 } else if (!sendMouseEventToInputContext(e, QEvent::MouseButtonDblClick, button, pos,
1894 modifiers, buttons, globalPos)) {
1895 e->ignore();
1896 }
1897}
1898
1899bool QWidgetTextControlPrivate::sendMouseEventToInputContext(
1900 QEvent *e, QEvent::Type eventType, Qt::MouseButton button, const QPointF &pos,
1901 Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPointF &globalPos)
1902{
1903 Q_UNUSED(eventType);
1904 Q_UNUSED(button);
1905 Q_UNUSED(pos);
1906 Q_UNUSED(modifiers);
1907 Q_UNUSED(buttons);
1908 Q_UNUSED(globalPos);
1909#if !defined(QT_NO_IM)
1910 Q_Q(QWidgetTextControl);
1911
1912 if (isPreediting()) {
1913 QTextLayout *layout = cursor.block().layout();
1914 int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position();
1915
1916 if (cursorPos < 0 || cursorPos > layout->preeditAreaText().size())
1917 cursorPos = -1;
1918
1919 if (cursorPos >= 0) {
1920 if (eventType == QEvent::MouseButtonRelease)
1921 QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos);
1922
1923 e->setAccepted(true);
1924 return true;
1925 }
1926 }
1927#else
1928 Q_UNUSED(e);
1929#endif
1930 return false;
1931}
1932
1933void QWidgetTextControlPrivate::contextMenuEvent(const QPointF &screenPos, const QPointF &docPos, QWidget *contextWidget)
1934{
1935#ifdef QT_NO_CONTEXTMENU
1936 Q_UNUSED(screenPos);
1937 Q_UNUSED(docPos);
1938 Q_UNUSED(contextWidget);
1939#else
1940 Q_Q(QWidgetTextControl);
1941 QMenu *menu = q->createStandardContextMenu(docPos, contextWidget);
1942 if (!menu)
1943 return;
1944 menu->setAttribute(Qt::WA_DeleteOnClose);
1945
1946 if (auto *widget = qobject_cast<QWidget *>(parent)) {
1947 if (auto *window = widget->window()->windowHandle())
1948 QMenuPrivate::get(menu)->topData()->initialScreen = window->screen();
1949 }
1950
1951 menu->popup(screenPos.toPoint());
1952#endif
1953}
1954
1955bool QWidgetTextControlPrivate::dragEnterEvent(QEvent *e, const QMimeData *mimeData)
1956{
1957 Q_Q(QWidgetTextControl);
1958 if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) {
1959 e->ignore();
1960 return false;
1961 }
1962
1963 dndFeedbackCursor = QTextCursor();
1964
1965 return true; // accept proposed action
1966}
1967
1969{
1970 Q_Q(QWidgetTextControl);
1971
1972 const QRectF crect = q->cursorRect(dndFeedbackCursor);
1973 dndFeedbackCursor = QTextCursor();
1974
1975 if (crect.isValid())
1976 emit q->updateRequest(crect);
1977}
1978
1979bool QWidgetTextControlPrivate::dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos)
1980{
1981 Q_Q(QWidgetTextControl);
1982 if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) {
1983 e->ignore();
1984 return false;
1985 }
1986
1987 const int cursorPos = q->hitTest(pos, Qt::FuzzyHit);
1988 if (cursorPos != -1) {
1989 QRectF crect = q->cursorRect(dndFeedbackCursor);
1990 if (crect.isValid())
1991 emit q->updateRequest(crect);
1992
1993 dndFeedbackCursor = cursor;
1994 dndFeedbackCursor.setPosition(cursorPos);
1995
1996 crect = q->cursorRect(dndFeedbackCursor);
1997 emit q->updateRequest(crect);
1998 }
1999
2000 return true; // accept proposed action
2001}
2002
2003bool QWidgetTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QObject *source)
2004{
2005 Q_Q(QWidgetTextControl);
2006 dndFeedbackCursor = QTextCursor();
2007
2008 if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData))
2009 return false;
2010
2012
2013 QTextCursor insertionCursor = q->cursorForPosition(pos);
2014 insertionCursor.beginEditBlock();
2015
2016 if (dropAction == Qt::MoveAction && source == contextWidget)
2017 cursor.removeSelectedText();
2018
2019 cursor = insertionCursor;
2020 q->insertFromMimeData(mimeData);
2021 insertionCursor.endEditBlock();
2022 q->ensureCursorVisible();
2023 return true; // accept proposed action
2024}
2025
2026void QWidgetTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
2027{
2028 Q_Q(QWidgetTextControl);
2029 if (!(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByMouse)) || cursor.isNull()) {
2030 e->ignore();
2031 return;
2032 }
2033 bool isGettingInput = !e->commitString().isEmpty()
2034 || e->preeditString() != cursor.block().layout()->preeditAreaText()
2035 || e->replacementLength() > 0;
2036
2037 if (!isGettingInput && e->attributes().isEmpty()) {
2038 e->ignore();
2039 return;
2040 }
2041
2042 int oldCursorPos = cursor.position();
2043
2044 cursor.beginEditBlock();
2045 if (isGettingInput) {
2046 cursor.removeSelectedText();
2047 }
2048
2049 QTextBlock block;
2050
2051 // insert commit string
2052 if (!e->commitString().isEmpty() || e->replacementLength()) {
2053 auto *mimeData = QInputControl::mimeDataForInputEvent(e);
2054 if (mimeData && q->canInsertFromMimeData(mimeData)) {
2055 q->insertFromMimeData(mimeData);
2056 } else {
2057 if (e->commitString().endsWith(QChar::LineFeed))
2058 block = cursor.block(); // Remember the block where the preedit text is
2059 QTextCursor c = cursor;
2060 c.setPosition(c.position() + e->replacementStart());
2061 c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor);
2062 c.insertText(e->commitString());
2063 }
2064 }
2065
2066 for (int i = 0; i < e->attributes().size(); ++i) {
2067 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
2068 if (a.type == QInputMethodEvent::Selection) {
2069 QTextCursor oldCursor = cursor;
2070 int blockStart = a.start + cursor.block().position();
2071 cursor.setPosition(blockStart, QTextCursor::MoveAnchor);
2072 cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor);
2073 q->ensureCursorVisible();
2074 repaintOldAndNewSelection(oldCursor);
2075 }
2076 }
2077
2078 if (!block.isValid())
2079 block = cursor.block();
2080 QTextLayout *layout = block.layout();
2081 if (isGettingInput)
2082 layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
2083 QList<QTextLayout::FormatRange> overrides;
2084 overrides.reserve(e->attributes().size());
2085 const int oldPreeditCursor = preeditCursor;
2086 preeditCursor = e->preeditString().size();
2087 hideCursor = false;
2088 for (int i = 0; i < e->attributes().size(); ++i) {
2089 const QInputMethodEvent::Attribute &a = e->attributes().at(i);
2090 if (a.type == QInputMethodEvent::Cursor) {
2091 preeditCursor = a.start;
2092 hideCursor = !a.length;
2093 } else if (a.type == QInputMethodEvent::TextFormat) {
2094 QTextCharFormat f = cursor.charFormat();
2095 f.merge(qvariant_cast<QTextFormat>(a.value).toCharFormat());
2096 if (f.isValid()) {
2097 QTextLayout::FormatRange o;
2098 o.start = a.start + cursor.position() - block.position();
2099 o.length = a.length;
2100 o.format = f;
2101
2102 // Make sure list is sorted by start index
2103 QList<QTextLayout::FormatRange>::iterator it = overrides.end();
2104 while (it != overrides.begin()) {
2105 QList<QTextLayout::FormatRange>::iterator previous = it - 1;
2106 if (o.start >= previous->start) {
2107 overrides.insert(it, o);
2108 break;
2109 }
2110 it = previous;
2111 }
2112
2113 if (it == overrides.begin())
2114 overrides.prepend(o);
2115 }
2116 }
2117 }
2118
2119 if (cursor.charFormat().isValid()) {
2120 int start = cursor.position() - block.position();
2121 int end = start + e->preeditString().size();
2122
2123 QList<QTextLayout::FormatRange>::iterator it = overrides.begin();
2124 while (it != overrides.end()) {
2125 QTextLayout::FormatRange range = *it;
2126 int rangeStart = range.start;
2127 if (rangeStart > start) {
2128 QTextLayout::FormatRange o;
2129 o.start = start;
2130 o.length = rangeStart - start;
2131 o.format = cursor.charFormat();
2132 it = overrides.insert(it, o) + 1;
2133 }
2134
2135 ++it;
2136 start = range.start + range.length;
2137 }
2138
2139 if (start < end) {
2140 QTextLayout::FormatRange o;
2141 o.start = start;
2142 o.length = end - start;
2143 o.format = cursor.charFormat();
2144 overrides.append(o);
2145 }
2146 }
2147 layout->setFormats(overrides);
2148
2149 cursor.endEditBlock();
2150
2151 if (cursor.d)
2152 cursor.d->setX();
2153 if (oldCursorPos != cursor.position())
2154 emit q->cursorPositionChanged();
2155 if (oldPreeditCursor != preeditCursor)
2156 emit q->microFocusChanged();
2157}
2158
2159QVariant QWidgetTextControl::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
2160{
2161 Q_D(const QWidgetTextControl);
2162 QTextBlock block = d->cursor.block();
2163 switch(property) {
2164 case Qt::ImCursorRectangle:
2165 return cursorRect();
2166 case Qt::ImAnchorRectangle:
2167 return d->rectForPosition(d->cursor.anchor());
2168 case Qt::ImFont:
2169 return QVariant(d->cursor.charFormat().font());
2170 case Qt::ImCursorPosition: {
2171 const QPointF pt = argument.toPointF();
2172 if (!pt.isNull())
2173 return QVariant(cursorForPosition(pt).position() - block.position());
2174 return QVariant(d->cursor.position() - block.position()); }
2175 case Qt::ImSurroundingText:
2176 return QVariant(block.text());
2177 case Qt::ImCurrentSelection: {
2178 QMimeData *mimeData = createMimeDataFromSelection();
2179 mimeData->deleteLater();
2180 return QInputControl::selectionWrapper(mimeData);
2181 }
2182 case Qt::ImMaximumTextLength:
2183 return QVariant(); // No limit.
2184 case Qt::ImAnchorPosition:
2185 return QVariant(d->cursor.anchor() - block.position());
2186 case Qt::ImAbsolutePosition: {
2187 const QPointF pt = argument.toPointF();
2188 if (!pt.isNull())
2189 return QVariant(cursorForPosition(pt).position());
2190 return QVariant(d->cursor.position()); }
2191 case Qt::ImTextAfterCursor:
2192 {
2193 int maxLength = argument.isValid() ? argument.toInt() : 1024;
2194 QTextCursor tmpCursor = d->cursor;
2195 int localPos = d->cursor.position() - block.position();
2196 QString result = block.text().mid(localPos);
2197 while (result.size() < maxLength) {
2198 int currentBlock = tmpCursor.blockNumber();
2199 tmpCursor.movePosition(QTextCursor::NextBlock);
2200 if (tmpCursor.blockNumber() == currentBlock)
2201 break;
2202 result += u'\n' + tmpCursor.block().text();
2203 }
2204 return QVariant(result);
2205 }
2206 case Qt::ImTextBeforeCursor:
2207 {
2208 int maxLength = argument.isValid() ? argument.toInt() : 1024;
2209 QTextCursor tmpCursor = d->cursor;
2210 int localPos = d->cursor.position() - block.position();
2211 int numBlocks = 0;
2212 int resultLen = localPos;
2213 while (resultLen < maxLength) {
2214 int currentBlock = tmpCursor.blockNumber();
2215 tmpCursor.movePosition(QTextCursor::PreviousBlock);
2216 if (tmpCursor.blockNumber() == currentBlock)
2217 break;
2218 numBlocks++;
2219 resultLen += tmpCursor.block().length();
2220 }
2221 QString result;
2222 while (numBlocks) {
2223 result += tmpCursor.block().text() + u'\n';
2224 tmpCursor.movePosition(QTextCursor::NextBlock);
2225 --numBlocks;
2226 }
2227 result += QStringView{block.text()}.mid(0, localPos);
2228 return QVariant(result);
2229 }
2230 default:
2231 return QVariant();
2232 }
2233}
2234
2235void QWidgetTextControl::setFocus(bool focus, Qt::FocusReason reason)
2236{
2237 QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut,
2238 reason);
2239 processEvent(&ev);
2240}
2241
2242void QWidgetTextControlPrivate::focusEvent(QFocusEvent *e)
2243{
2244 Q_Q(QWidgetTextControl);
2245 emit q->updateRequest(q->selectionRect());
2246 if (e->gotFocus()) {
2247 cursorOn = (interactionFlags & (Qt::TextSelectableByKeyboard | Qt::TextEditable));
2248 if (interactionFlags & Qt::TextEditable) {
2249 setCursorVisible(true);
2250 }
2251 } else {
2252 setCursorVisible(false);
2253 cursorOn = false;
2254
2255 if (cursorIsFocusIndicator
2256 && e->reason() != Qt::ActiveWindowFocusReason
2257 && e->reason() != Qt::PopupFocusReason
2258 && cursor.hasSelection()) {
2259 cursor.clearSelection();
2260 }
2261 }
2262 hasFocus = e->gotFocus();
2263}
2264
2265QString QWidgetTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const
2266{
2267 if (anchorCursor.hasSelection()) {
2268 QTextCursor cursor = anchorCursor;
2269 if (cursor.selectionStart() != cursor.position())
2270 cursor.setPosition(cursor.selectionStart());
2271 cursor.movePosition(QTextCursor::NextCharacter);
2272 QTextCharFormat fmt = cursor.charFormat();
2273 if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref))
2274 return fmt.stringProperty(QTextFormat::AnchorHref);
2275 }
2276 return QString();
2277}
2278
2279
2280#ifndef QT_NO_CONTEXTMENU
2281void setActionIcon(QAction *action, const QString &name)
2282{
2283 const QIcon icon = QIcon::fromTheme(name);
2284 if (!icon.isNull())
2285 action->setIcon(icon);
2286}
2287
2288QMenu *QWidgetTextControl::createStandardContextMenu(const QPointF &pos, QWidget *parent)
2289{
2290 Q_D(QWidgetTextControl);
2291
2292 const bool showTextSelectionActions = d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
2293
2294 d->linkToCopy = QString();
2295 if (!pos.isNull())
2296 d->linkToCopy = anchorAt(pos);
2297
2298 if (d->linkToCopy.isEmpty() && !showTextSelectionActions)
2299 return nullptr;
2300
2301 QMenu *menu = new QMenu(parent);
2302 QAction *a;
2303
2304 if (d->interactionFlags & Qt::TextEditable) {
2305 a = menu->addAction(tr("&Undo") + ACCEL_KEY(QKeySequence::Undo), this, SLOT(undo()));
2306 a->setEnabled(d->doc->isUndoAvailable());
2307 a->setObjectName(QStringLiteral("edit-undo"));
2308 setActionIcon(a, QStringLiteral("edit-undo"));
2309 a = menu->addAction(tr("&Redo") + ACCEL_KEY(QKeySequence::Redo), this, SLOT(redo()));
2310 a->setEnabled(d->doc->isRedoAvailable());
2311 a->setObjectName(QStringLiteral("edit-redo"));
2312 setActionIcon(a, QStringLiteral("edit-redo"));
2313 menu->addSeparator();
2314
2315#ifndef QT_NO_CLIPBOARD
2316 a = menu->addAction(tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut), this, SLOT(cut()));
2317 a->setEnabled(d->cursor.hasSelection());
2318 a->setObjectName(QStringLiteral("edit-cut"));
2319 setActionIcon(a, QStringLiteral("edit-cut"));
2320#endif
2321 }
2322
2323#ifndef QT_NO_CLIPBOARD
2324 if (showTextSelectionActions) {
2325 a = menu->addAction(tr("&Copy") + ACCEL_KEY(QKeySequence::Copy), this, SLOT(copy()));
2326 a->setEnabled(d->cursor.hasSelection());
2327 a->setObjectName(QStringLiteral("edit-copy"));
2328 setActionIcon(a, QStringLiteral("edit-copy"));
2329 }
2330
2331 if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard)
2332 || (d->interactionFlags & Qt::LinksAccessibleByMouse)) {
2333
2334 a = menu->addAction(tr("Copy &Link Location"), this, SLOT(_q_copyLink()));
2335 a->setEnabled(!d->linkToCopy.isEmpty());
2336 a->setObjectName(QStringLiteral("link-copy"));
2337 }
2338#endif // QT_NO_CLIPBOARD
2339
2340 if (d->interactionFlags & Qt::TextEditable) {
2341#ifndef QT_NO_CLIPBOARD
2342 a = menu->addAction(tr("&Paste") + ACCEL_KEY(QKeySequence::Paste), this, SLOT(paste()));
2343 a->setEnabled(canPaste());
2344 a->setObjectName(QStringLiteral("edit-paste"));
2345 setActionIcon(a, QStringLiteral("edit-paste"));
2346#endif
2347 a = menu->addAction(tr("Delete"), this, SLOT(_q_deleteSelected()));
2348 a->setEnabled(d->cursor.hasSelection());
2349 a->setObjectName(QStringLiteral("edit-delete"));
2350 setActionIcon(a, QStringLiteral("edit-delete"));
2351 }
2352
2353
2354 if (showTextSelectionActions) {
2355 menu->addSeparator();
2356 a = menu->addAction(tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll), this, SLOT(selectAll()));
2357 a->setEnabled(!d->doc->isEmpty());
2358 a->setObjectName(QStringLiteral("select-all"));
2359 setActionIcon(a, QStringLiteral("edit-select-all"));
2360 }
2361
2362 if ((d->interactionFlags & Qt::TextEditable) && QGuiApplication::styleHints()->useRtlExtensions()) {
2363 menu->addSeparator();
2364 QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, menu);
2365 menu->addMenu(ctrlCharacterMenu);
2366 }
2367
2368 return menu;
2369}
2370#endif // QT_NO_CONTEXTMENU
2371
2372QTextCursor QWidgetTextControl::cursorForPosition(const QPointF &pos) const
2373{
2374 Q_D(const QWidgetTextControl);
2375 int cursorPos = hitTest(pos, Qt::FuzzyHit);
2376 if (cursorPos == -1)
2377 cursorPos = 0;
2378 QTextCursor c(d->doc);
2379 c.setPosition(cursorPos);
2380 return c;
2381}
2382
2383QRectF QWidgetTextControl::cursorRect(const QTextCursor &cursor) const
2384{
2385 Q_D(const QWidgetTextControl);
2386 if (cursor.isNull())
2387 return QRectF();
2388
2389 return d->rectForPosition(cursor.position());
2390}
2391
2392QRectF QWidgetTextControl::cursorRect() const
2393{
2394 Q_D(const QWidgetTextControl);
2395 return cursorRect(d->cursor);
2396}
2397
2398QRectF QWidgetTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const
2399{
2400 if (cursor.isNull())
2401 return QRectF();
2402
2403 return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0);
2404}
2405
2406QString QWidgetTextControl::anchorAt(const QPointF &pos) const
2407{
2408 Q_D(const QWidgetTextControl);
2409 return d->doc->documentLayout()->anchorAt(pos);
2410}
2411
2412QString QWidgetTextControl::anchorAtCursor() const
2413{
2414 Q_D(const QWidgetTextControl);
2415
2416 return d->anchorForCursor(d->cursor);
2417}
2418
2419QTextBlock QWidgetTextControl::blockWithMarkerAt(const QPointF &pos) const
2420{
2421 Q_D(const QWidgetTextControl);
2422 return d->doc->documentLayout()->blockWithMarkerAt(pos);
2423}
2424
2425bool QWidgetTextControl::overwriteMode() const
2426{
2427 Q_D(const QWidgetTextControl);
2428 return d->overwriteMode;
2429}
2430
2431void QWidgetTextControl::setOverwriteMode(bool overwrite)
2432{
2433 Q_D(QWidgetTextControl);
2434 d->overwriteMode = overwrite;
2435}
2436
2437int QWidgetTextControl::cursorWidth() const
2438{
2439 Q_D(const QWidgetTextControl);
2440 return d->doc->documentLayout()->property("cursorWidth").toInt();
2441}
2442
2443void QWidgetTextControl::setCursorWidth(int width)
2444{
2445 Q_D(QWidgetTextControl);
2446 if (width == -1)
2447 width = QApplication::style()->pixelMetric(QStyle::PM_TextCursorWidth, nullptr, qobject_cast<QWidget *>(parent()));
2448 d->doc->documentLayout()->setProperty("cursorWidth", width);
2449 d->repaintCursor();
2450}
2451
2452bool QWidgetTextControl::acceptRichText() const
2453{
2454 Q_D(const QWidgetTextControl);
2455 return d->acceptRichText;
2456}
2457
2458void QWidgetTextControl::setAcceptRichText(bool accept)
2459{
2460 Q_D(QWidgetTextControl);
2461 d->acceptRichText = accept;
2462}
2463
2464#if QT_CONFIG(textedit)
2465
2466void QWidgetTextControl::setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections)
2467{
2468 Q_D(QWidgetTextControl);
2469
2470 QMultiHash<int, int> hash;
2471 for (int i = 0; i < d->extraSelections.size(); ++i) {
2472 const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(i);
2473 hash.insert(esel.cursor.anchor(), i);
2474 }
2475
2476 for (int i = 0; i < selections.size(); ++i) {
2477 const QTextEdit::ExtraSelection &sel = selections.at(i);
2478 const auto it = hash.constFind(sel.cursor.anchor());
2479 if (it != hash.cend()) {
2480 const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value());
2481 if (esel.cursor.position() == sel.cursor.position()
2482 && esel.format == sel.format) {
2483 hash.erase(it);
2484 continue;
2485 }
2486 }
2487 QRectF r = selectionRect(sel.cursor);
2488 if (sel.format.boolProperty(QTextFormat::FullWidthSelection)) {
2489 r.setLeft(0);
2490 r.setWidth(qreal(INT_MAX));
2491 }
2492 emit updateRequest(r);
2493 }
2494
2495 for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
2496 const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value());
2497 QRectF r = selectionRect(esel.cursor);
2498 if (esel.format.boolProperty(QTextFormat::FullWidthSelection)) {
2499 r.setLeft(0);
2500 r.setWidth(qreal(INT_MAX));
2501 }
2502 emit updateRequest(r);
2503 }
2504
2505 d->extraSelections.resize(selections.size());
2506 for (int i = 0; i < selections.size(); ++i) {
2507 d->extraSelections[i].cursor = selections.at(i).cursor;
2508 d->extraSelections[i].format = selections.at(i).format;
2509 }
2510}
2511
2512QList<QTextEdit::ExtraSelection> QWidgetTextControl::extraSelections() const
2513{
2514 Q_D(const QWidgetTextControl);
2515 QList<QTextEdit::ExtraSelection> selections;
2516 const int numExtraSelections = d->extraSelections.size();
2517 selections.reserve(numExtraSelections);
2518 for (int i = 0; i < numExtraSelections; ++i) {
2519 QTextEdit::ExtraSelection sel;
2520 const QAbstractTextDocumentLayout::Selection &sel2 = d->extraSelections.at(i);
2521 sel.cursor = sel2.cursor;
2522 sel.format = sel2.format;
2523 selections.append(sel);
2524 }
2525 return selections;
2526}
2527
2528#endif // QT_CONFIG(textedit)
2529
2530void QWidgetTextControl::setTextWidth(qreal width)
2531{
2532 Q_D(QWidgetTextControl);
2533 d->doc->setTextWidth(width);
2534}
2535
2536qreal QWidgetTextControl::textWidth() const
2537{
2538 Q_D(const QWidgetTextControl);
2539 return d->doc->textWidth();
2540}
2541
2542QSizeF QWidgetTextControl::size() const
2543{
2544 Q_D(const QWidgetTextControl);
2545 return d->doc->size();
2546}
2547
2548void QWidgetTextControl::setOpenExternalLinks(bool open)
2549{
2550 Q_D(QWidgetTextControl);
2551 d->openExternalLinks = open;
2552}
2553
2554bool QWidgetTextControl::openExternalLinks() const
2555{
2556 Q_D(const QWidgetTextControl);
2557 return d->openExternalLinks;
2558}
2559
2560bool QWidgetTextControl::ignoreUnusedNavigationEvents() const
2561{
2562 Q_D(const QWidgetTextControl);
2563 return d->ignoreUnusedNavigationEvents;
2564}
2565
2566void QWidgetTextControl::setIgnoreUnusedNavigationEvents(bool ignore)
2567{
2568 Q_D(QWidgetTextControl);
2569 d->ignoreUnusedNavigationEvents = ignore;
2570}
2571
2572void QWidgetTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
2573{
2574 Q_D(QWidgetTextControl);
2575 const QTextCursor oldSelection = d->cursor;
2576 const bool moved = d->cursor.movePosition(op, mode);
2577 d->_q_updateCurrentCharFormatAndSelection();
2578 ensureCursorVisible();
2579 d->repaintOldAndNewSelection(oldSelection);
2580 if (moved)
2581 emit cursorPositionChanged();
2582}
2583
2584bool QWidgetTextControl::canPaste() const
2585{
2586#ifndef QT_NO_CLIPBOARD
2587 Q_D(const QWidgetTextControl);
2588 if (d->interactionFlags & Qt::TextEditable) {
2589 const QMimeData *md = QGuiApplication::clipboard()->mimeData();
2590 return md && canInsertFromMimeData(md);
2591 }
2592#endif
2593 return false;
2594}
2595
2596void QWidgetTextControl::setCursorIsFocusIndicator(bool b)
2597{
2598 Q_D(QWidgetTextControl);
2599 d->cursorIsFocusIndicator = b;
2600 d->repaintCursor();
2601}
2602
2603bool QWidgetTextControl::cursorIsFocusIndicator() const
2604{
2605 Q_D(const QWidgetTextControl);
2606 return d->cursorIsFocusIndicator;
2607}
2608
2609
2610void QWidgetTextControl::setDragEnabled(bool enabled)
2611{
2612 Q_D(QWidgetTextControl);
2613 d->dragEnabled = enabled;
2614}
2615
2616bool QWidgetTextControl::isDragEnabled() const
2617{
2618 Q_D(const QWidgetTextControl);
2619 return d->dragEnabled;
2620}
2621
2622void QWidgetTextControl::setWordSelectionEnabled(bool enabled)
2623{
2624 Q_D(QWidgetTextControl);
2625 d->wordSelectionEnabled = enabled;
2626}
2627
2628bool QWidgetTextControl::isWordSelectionEnabled() const
2629{
2630 Q_D(const QWidgetTextControl);
2631 return d->wordSelectionEnabled;
2632}
2633
2634bool QWidgetTextControl::isPreediting()
2635{
2636 return d_func()->isPreediting();
2637}
2638
2639#ifndef QT_NO_PRINTER
2640void QWidgetTextControl::print(QPagedPaintDevice *printer) const
2641{
2642 Q_D(const QWidgetTextControl);
2643 if (!printer)
2644 return;
2645 QTextDocument *tempDoc = nullptr;
2646 const QTextDocument *doc = d->doc;
2647 if (QPagedPaintDevicePrivate::get(printer)->printSelectionOnly) {
2648 if (!d->cursor.hasSelection())
2649 return;
2650 tempDoc = new QTextDocument(const_cast<QTextDocument *>(doc));
2651 tempDoc->setResourceProvider(doc->resourceProvider());
2652 tempDoc->setMetaInformation(QTextDocument::DocumentTitle, doc->metaInformation(QTextDocument::DocumentTitle));
2653 tempDoc->setPageSize(doc->pageSize());
2654 tempDoc->setDefaultFont(doc->defaultFont());
2655 tempDoc->setUseDesignMetrics(doc->useDesignMetrics());
2656 QTextCursor(tempDoc).insertFragment(d->cursor.selection());
2657 doc = tempDoc;
2658
2659 // copy the custom object handlers
2660 doc->documentLayout()->d_func()->handlers = d->doc->documentLayout()->d_func()->handlers;
2661 }
2662 doc->print(printer);
2663 delete tempDoc;
2664}
2665#endif
2666
2667QMimeData *QWidgetTextControl::createMimeDataFromSelection() const
2668{
2669 Q_D(const QWidgetTextControl);
2670 const QTextDocumentFragment fragment(d->cursor);
2671 return new QTextEditMimeData(fragment);
2672}
2673
2674bool QWidgetTextControl::canInsertFromMimeData(const QMimeData *source) const
2675{
2676 Q_D(const QWidgetTextControl);
2677 if (d->acceptRichText)
2678 return (source->hasText() && !source->text().isEmpty())
2679 || source->hasHtml()
2680 || source->hasFormat("application/x-qrichtext"_L1)
2681 || source->hasFormat("application/x-qt-richtext"_L1);
2682 else
2683 return source->hasText() && !source->text().isEmpty();
2684}
2685
2686void QWidgetTextControl::insertFromMimeData(const QMimeData *source)
2687{
2688 Q_D(QWidgetTextControl);
2689 if (!(d->interactionFlags & Qt::TextEditable) || !source)
2690 return;
2691
2692 bool hasData = false;
2693 QTextDocumentFragment fragment;
2694#if QT_CONFIG(textmarkdownreader)
2695 const auto formats = source->formats();
2696 if (formats.size() && formats.first() == "text/markdown"_L1) {
2697 auto s = QString::fromUtf8(source->data("text/markdown"_L1));
2698 fragment = QTextDocumentFragment::fromMarkdown(s);
2699 hasData = true;
2700 } else
2701#endif
2702#ifndef QT_NO_TEXTHTMLPARSER
2703 if (source->hasFormat("application/x-qrichtext"_L1) && d->acceptRichText) {
2704 // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore).
2705 const QString richtext = "<meta name=\"qrichtext\" content=\"1\" />"_L1
2706 + QString::fromUtf8(source->data("application/x-qrichtext"_L1));
2707 fragment = QTextDocumentFragment::fromHtml(richtext, d->doc);
2708 hasData = true;
2709 } else if (source->hasHtml() && d->acceptRichText) {
2710 fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc);
2711 hasData = true;
2712 }
2713#endif // QT_NO_TEXTHTMLPARSER
2714 if (!hasData) {
2715 const QString text = source->text();
2716 if (!text.isNull()) {
2717 fragment = QTextDocumentFragment::fromPlainText(text);
2718 hasData = true;
2719 }
2720 }
2721
2722 if (hasData)
2723 d->cursor.insertFragment(fragment);
2724 ensureCursorVisible();
2725}
2726
2727bool QWidgetTextControl::findNextPrevAnchor(const QTextCursor &startCursor, bool next, QTextCursor &newAnchor)
2728{
2729 Q_D(QWidgetTextControl);
2730
2731 int anchorStart = -1;
2732 QString anchorHref;
2733 int anchorEnd = -1;
2734
2735 if (next) {
2736 const int startPos = startCursor.selectionEnd();
2737
2738 QTextBlock block = d->doc->findBlock(startPos);
2739 QTextBlock::Iterator it = block.begin();
2740
2741 while (!it.atEnd() && it.fragment().position() < startPos)
2742 ++it;
2743
2744 while (block.isValid()) {
2745 anchorStart = -1;
2746
2747 // find next anchor
2748 for (; !it.atEnd(); ++it) {
2749 const QTextFragment fragment = it.fragment();
2750 const QTextCharFormat fmt = fragment.charFormat();
2751
2752 if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) {
2753 anchorStart = fragment.position();
2754 anchorHref = fmt.anchorHref();
2755 break;
2756 }
2757 }
2758
2759 if (anchorStart != -1) {
2760 anchorEnd = -1;
2761
2762 // find next non-anchor fragment
2763 for (; !it.atEnd(); ++it) {
2764 const QTextFragment fragment = it.fragment();
2765 const QTextCharFormat fmt = fragment.charFormat();
2766
2767 if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) {
2768 anchorEnd = fragment.position();
2769 break;
2770 }
2771 }
2772
2773 if (anchorEnd == -1)
2774 anchorEnd = block.position() + block.length() - 1;
2775
2776 // make found selection
2777 break;
2778 }
2779
2780 block = block.next();
2781 it = block.begin();
2782 }
2783 } else {
2784 int startPos = startCursor.selectionStart();
2785 if (startPos > 0)
2786 --startPos;
2787
2788 QTextBlock block = d->doc->findBlock(startPos);
2789 QTextBlock::Iterator blockStart = block.begin();
2790 QTextBlock::Iterator it = block.end();
2791
2792 if (startPos == block.position()) {
2793 it = block.begin();
2794 } else {
2795 do {
2796 if (it == blockStart) {
2797 it = QTextBlock::Iterator();
2798 block = QTextBlock();
2799 } else {
2800 --it;
2801 }
2802 } while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos);
2803 }
2804
2805 while (block.isValid()) {
2806 anchorStart = -1;
2807
2808 if (!it.atEnd()) {
2809 do {
2810 const QTextFragment fragment = it.fragment();
2811 const QTextCharFormat fmt = fragment.charFormat();
2812
2813 if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) {
2814 anchorStart = fragment.position() + fragment.length();
2815 anchorHref = fmt.anchorHref();
2816 break;
2817 }
2818
2819 if (it == blockStart)
2820 it = QTextBlock::Iterator();
2821 else
2822 --it;
2823 } while (!it.atEnd());
2824 }
2825
2826 if (anchorStart != -1 && !it.atEnd()) {
2827 anchorEnd = -1;
2828
2829 do {
2830 const QTextFragment fragment = it.fragment();
2831 const QTextCharFormat fmt = fragment.charFormat();
2832
2833 if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) {
2834 anchorEnd = fragment.position() + fragment.length();
2835 break;
2836 }
2837
2838 if (it == blockStart)
2839 it = QTextBlock::Iterator();
2840 else
2841 --it;
2842 } while (!it.atEnd());
2843
2844 if (anchorEnd == -1)
2845 anchorEnd = qMax(0, block.position());
2846
2847 break;
2848 }
2849
2850 block = block.previous();
2851 it = block.end();
2852 if (it != block.begin())
2853 --it;
2854 blockStart = block.begin();
2855 }
2856
2857 }
2858
2859 if (anchorStart != -1 && anchorEnd != -1) {
2860 newAnchor = d->cursor;
2861 newAnchor.setPosition(anchorStart);
2862 newAnchor.setPosition(anchorEnd, QTextCursor::KeepAnchor);
2863 return true;
2864 }
2865
2866 return false;
2867}
2868
2869void QWidgetTextControlPrivate::activateLinkUnderCursor(QString href)
2870{
2871 QTextCursor oldCursor = cursor;
2872
2873 if (href.isEmpty()) {
2874 QTextCursor tmp = cursor;
2875 if (tmp.selectionStart() != tmp.position())
2876 tmp.setPosition(tmp.selectionStart());
2877 tmp.movePosition(QTextCursor::NextCharacter);
2878 href = tmp.charFormat().anchorHref();
2879 }
2880 if (href.isEmpty())
2881 return;
2882
2883 if (!cursor.hasSelection()) {
2884 QTextBlock block = cursor.block();
2885 const int cursorPos = cursor.position();
2886
2887 QTextBlock::Iterator it = block.begin();
2888 QTextBlock::Iterator linkFragment;
2889
2890 for (; !it.atEnd(); ++it) {
2891 QTextFragment fragment = it.fragment();
2892 const int fragmentPos = fragment.position();
2893 if (fragmentPos <= cursorPos &&
2894 fragmentPos + fragment.length() > cursorPos) {
2895 linkFragment = it;
2896 break;
2897 }
2898 }
2899
2900 if (!linkFragment.atEnd()) {
2901 it = linkFragment;
2902 cursor.setPosition(it.fragment().position());
2903 if (it != block.begin()) {
2904 do {
2905 --it;
2906 QTextFragment fragment = it.fragment();
2907 if (fragment.charFormat().anchorHref() != href)
2908 break;
2909 cursor.setPosition(fragment.position());
2910 } while (it != block.begin());
2911 }
2912
2913 for (it = linkFragment; !it.atEnd(); ++it) {
2914 QTextFragment fragment = it.fragment();
2915 if (fragment.charFormat().anchorHref() != href)
2916 break;
2917 cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
2918 }
2919 }
2920 }
2921
2922 if (hasFocus) {
2924 } else {
2925 cursorIsFocusIndicator = false;
2926 cursor.clearSelection();
2927 }
2928 repaintOldAndNewSelection(oldCursor);
2929
2930#ifndef QT_NO_DESKTOPSERVICES
2932 QDesktopServices::openUrl(QUrl{href});
2933 else
2934#endif
2935 emit q_func()->linkActivated(href);
2936}
2937
2938void QWidgetTextControlPrivate::updateHighlightedAnchor(QPointF mousePos)
2939{
2940 Q_Q(QWidgetTextControl);
2941 const QString anchor = q->anchorAt(mousePos);
2942 if (anchor != highlightedAnchor) {
2943 highlightedAnchor = anchor;
2944 emit q->linkHovered(anchor);
2945 }
2946}
2947
2949{
2950 Q_Q(QWidgetTextControl);
2951 if (!highlightedAnchor.isEmpty()) {
2952 highlightedAnchor.clear();
2953 emit q->linkHovered(QString());
2954 }
2955}
2956
2957#if QT_CONFIG(tooltip)
2958void QWidgetTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget)
2959{
2960 const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip();
2961 if (toolTip.isEmpty())
2962 return;
2963 QToolTip::showText(globalPos, toolTip, contextWidget);
2964}
2965#endif // QT_CONFIG(tooltip)
2966
2968{
2969 QTextLayout *layout = cursor.block().layout();
2970 if (layout && !layout->preeditAreaText().isEmpty())
2971 return true;
2972
2973 return false;
2974}
2975
2977{
2978 if (!isPreediting())
2979 return;
2980
2981 QGuiApplication::inputMethod()->commit();
2982
2983 if (!isPreediting())
2984 return;
2985
2986 cursor.beginEditBlock();
2987 preeditCursor = 0;
2988 QTextBlock block = cursor.block();
2989 QTextLayout *layout = block.layout();
2990 layout->setPreeditArea(-1, QString());
2991 layout->clearFormats();
2992 cursor.endEditBlock();
2993}
2994
2995bool QWidgetTextControl::setFocusToNextOrPreviousAnchor(bool next)
2996{
2997 Q_D(QWidgetTextControl);
2998
2999 if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard))
3000 return false;
3001
3002 QRectF crect = selectionRect();
3003 emit updateRequest(crect);
3004
3005 // If we don't have a current anchor, we start from the start/end
3006 if (!d->cursor.hasSelection()) {
3007 d->cursor = QTextCursor(d->doc);
3008 if (next)
3009 d->cursor.movePosition(QTextCursor::Start);
3010 else
3011 d->cursor.movePosition(QTextCursor::End);
3012 }
3013
3014 QTextCursor newAnchor;
3015 if (findNextPrevAnchor(d->cursor, next, newAnchor)) {
3016 d->cursor = newAnchor;
3017 d->cursorIsFocusIndicator = true;
3018 } else {
3019 d->cursor.clearSelection();
3020 }
3021
3022 if (d->cursor.hasSelection()) {
3023 crect = selectionRect();
3024 emit updateRequest(crect);
3025 emit visibilityRequest(crect);
3026 return true;
3027 } else {
3028 return false;
3029 }
3030}
3031
3032bool QWidgetTextControl::setFocusToAnchor(const QTextCursor &newCursor)
3033{
3034 Q_D(QWidgetTextControl);
3035
3036 if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard))
3037 return false;
3038
3039 // Verify that this is an anchor.
3040 const QString anchorHref = d->anchorForCursor(newCursor);
3041 if (anchorHref.isEmpty())
3042 return false;
3043
3044 // and process it
3045 QRectF crect = selectionRect();
3046 emit updateRequest(crect);
3047
3048 d->cursor.setPosition(newCursor.selectionStart());
3049 d->cursor.setPosition(newCursor.selectionEnd(), QTextCursor::KeepAnchor);
3050 d->cursorIsFocusIndicator = true;
3051
3052 crect = selectionRect();
3053 emit updateRequest(crect);
3054 emit visibilityRequest(crect);
3055 return true;
3056}
3057
3058void QWidgetTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
3059{
3060 Q_D(QWidgetTextControl);
3061 if (flags == d->interactionFlags)
3062 return;
3063 d->interactionFlags = flags;
3064
3065 if (d->hasFocus)
3066 d->setCursorVisible(flags & Qt::TextEditable);
3067}
3068
3069Qt::TextInteractionFlags QWidgetTextControl::textInteractionFlags() const
3070{
3071 Q_D(const QWidgetTextControl);
3072 return d->interactionFlags;
3073}
3074
3075void QWidgetTextControl::mergeCurrentCharFormat(const QTextCharFormat &modifier)
3076{
3077 Q_D(QWidgetTextControl);
3078 d->cursor.mergeCharFormat(modifier);
3079 d->updateCurrentCharFormat();
3080}
3081
3082void QWidgetTextControl::setCurrentCharFormat(const QTextCharFormat &format)
3083{
3084 Q_D(QWidgetTextControl);
3085 d->cursor.setCharFormat(format);
3086 d->updateCurrentCharFormat();
3087}
3088
3089QTextCharFormat QWidgetTextControl::currentCharFormat() const
3090{
3091 Q_D(const QWidgetTextControl);
3092 return d->cursor.charFormat();
3093}
3094
3095void QWidgetTextControl::insertPlainText(const QString &text)
3096{
3097 Q_D(QWidgetTextControl);
3098 d->cursor.insertText(text);
3099}
3100
3101#ifndef QT_NO_TEXTHTMLPARSER
3102void QWidgetTextControl::insertHtml(const QString &text)
3103{
3104 Q_D(QWidgetTextControl);
3105 d->cursor.insertHtml(text);
3106}
3107#endif // QT_NO_TEXTHTMLPARSER
3108
3109QPointF QWidgetTextControl::anchorPosition(const QString &name) const
3110{
3111 Q_D(const QWidgetTextControl);
3112 if (name.isEmpty())
3113 return QPointF();
3114
3115 QRectF r;
3116 for (QTextBlock block = d->doc->begin(); block.isValid(); block = block.next()) {
3117 QTextCharFormat format = block.charFormat();
3118 if (format.isAnchor() && format.anchorNames().contains(name)) {
3119 r = d->rectForPosition(block.position());
3120 break;
3121 }
3122
3123 for (QTextBlock::Iterator it = block.begin(); !it.atEnd(); ++it) {
3124 QTextFragment fragment = it.fragment();
3125 format = fragment.charFormat();
3126 if (format.isAnchor() && format.anchorNames().contains(name)) {
3127 r = d->rectForPosition(fragment.position());
3128 block = QTextBlock();
3129 break;
3130 }
3131 }
3132 }
3133 if (!r.isValid())
3134 return QPointF();
3135 return QPointF(0, r.top());
3136}
3137
3138void QWidgetTextControl::adjustSize()
3139{
3140 Q_D(QWidgetTextControl);
3141 d->doc->adjustSize();
3142}
3143
3144bool QWidgetTextControl::find(const QString &exp, QTextDocument::FindFlags options)
3145{
3146 Q_D(QWidgetTextControl);
3147 QTextCursor search = d->doc->find(exp, d->cursor, options);
3148 if (search.isNull())
3149 return false;
3150
3151 setTextCursor(search);
3152 return true;
3153}
3154
3155#if QT_CONFIG(regularexpression)
3156bool QWidgetTextControl::find(const QRegularExpression &exp, QTextDocument::FindFlags options)
3157{
3158 Q_D(QWidgetTextControl);
3159 QTextCursor search = d->doc->find(exp, d->cursor, options);
3160 if (search.isNull())
3161 return false;
3162
3163 setTextCursor(search);
3164 return true;
3165}
3166#endif
3167
3168QString QWidgetTextControl::toPlainText() const
3169{
3170 return document()->toPlainText();
3171}
3172
3173#ifndef QT_NO_TEXTHTMLPARSER
3174QString QWidgetTextControl::toHtml() const
3175{
3176 return document()->toHtml();
3177}
3178#endif
3179
3180#if QT_CONFIG(textmarkdownwriter)
3181QString QWidgetTextControl::toMarkdown(QTextDocument::MarkdownFeatures features) const
3182{
3183 return document()->toMarkdown(features);
3184}
3185#endif
3186
3188{
3189 // clear blockFormat properties that the user is unlikely to want duplicated:
3190 // - don't insert <hr/> automatically
3191 // - the next paragraph after a heading should be a normal paragraph
3192 // - remove the bottom margin from the last list item before appending
3193 // - the next checklist item after a checked item should be unchecked
3194 auto blockFmt = cursor.blockFormat();
3195 auto charFmt = cursor.charFormat();
3196 blockFmt.clearProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
3197 if (blockFmt.hasProperty(QTextFormat::HeadingLevel)) {
3198 blockFmt.clearProperty(QTextFormat::HeadingLevel);
3199 charFmt = QTextCharFormat();
3200 }
3201 if (cursor.currentList()) {
3202 auto existingFmt = cursor.blockFormat();
3203 existingFmt.clearProperty(QTextBlockFormat::BlockBottomMargin);
3204 cursor.setBlockFormat(existingFmt);
3205 if (blockFmt.marker() == QTextBlockFormat::MarkerType::Checked)
3206 blockFmt.setMarker(QTextBlockFormat::MarkerType::Unchecked);
3207 }
3208
3209 // After a blank line, reset block and char formats. I.e. you can end a list,
3210 // block quote, etc. by hitting enter twice, and get back to normal paragraph style.
3211 if (cursor.block().text().isEmpty() &&
3212 !cursor.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth) &&
3213 !cursor.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
3214 blockFmt = QTextBlockFormat();
3215 const bool blockFmtChanged = (cursor.blockFormat() != blockFmt);
3216 charFmt = QTextCharFormat();
3217 cursor.setBlockFormat(blockFmt);
3218 cursor.setCharFormat(charFmt);
3219 // If the user hit enter twice just to get back to default format,
3220 // don't actually insert a new block. But if the user then hits enter
3221 // yet again, the block format will not change, so we will insert a block.
3222 // This is what many word processors do.
3223 if (blockFmtChanged)
3224 return;
3225 }
3226
3227 cursor.insertBlock(blockFmt, charFmt);
3228}
3229
3230void QWidgetTextControlPrivate::append(const QString &text, Qt::TextFormat format)
3231{
3232 QTextCursor tmp(doc);
3233 tmp.beginEditBlock();
3234 tmp.movePosition(QTextCursor::End);
3235
3236 if (!doc->isEmpty())
3237 tmp.insertBlock(cursor.blockFormat(), cursor.charFormat());
3238 else
3239 tmp.setCharFormat(cursor.charFormat());
3240
3241 // preserve the char format
3242 QTextCharFormat oldCharFormat = cursor.charFormat();
3243
3244#ifndef QT_NO_TEXTHTMLPARSER
3245 if (format == Qt::RichText || (format == Qt::AutoText && Qt::mightBeRichText(text))) {
3246 tmp.insertHtml(text);
3247 } else {
3248 tmp.insertText(text);
3249 }
3250#else
3251 Q_UNUSED(format);
3252 tmp.insertText(text);
3253#endif // QT_NO_TEXTHTMLPARSER
3254 if (!cursor.hasSelection())
3255 cursor.setCharFormat(oldCharFormat);
3256
3257 tmp.endEditBlock();
3258}
3259
3260void QWidgetTextControl::append(const QString &text)
3261{
3262 Q_D(QWidgetTextControl);
3263 d->append(text, Qt::AutoText);
3264}
3265
3266void QWidgetTextControl::appendHtml(const QString &html)
3267{
3268 Q_D(QWidgetTextControl);
3269 d->append(html, Qt::RichText);
3270}
3271
3272void QWidgetTextControl::appendPlainText(const QString &text)
3273{
3274 Q_D(QWidgetTextControl);
3275 d->append(text, Qt::PlainText);
3276}
3277
3278
3279void QWidgetTextControl::ensureCursorVisible()
3280{
3281 Q_D(QWidgetTextControl);
3282 QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0);
3283 emit visibilityRequest(crect);
3284 emit microFocusChanged();
3285}
3286
3287QPalette QWidgetTextControl::palette() const
3288{
3289 Q_D(const QWidgetTextControl);
3290 return d->palette;
3291}
3292
3293void QWidgetTextControl::setPalette(const QPalette &pal)
3294{
3295 Q_D(QWidgetTextControl);
3296 d->palette = pal;
3297}
3298
3299QAbstractTextDocumentLayout::PaintContext QWidgetTextControl::getPaintContext(QWidget *widget) const
3300{
3301 Q_D(const QWidgetTextControl);
3302
3303 QAbstractTextDocumentLayout::PaintContext ctx;
3304
3305 ctx.selections = d->extraSelections;
3306 ctx.palette = d->palette;
3307#if QT_CONFIG(style_stylesheet)
3308 if (widget) {
3309 if (auto cssStyle = qt_styleSheet(widget->style())) {
3310 QStyleOption option;
3311 option.initFrom(widget);
3312 cssStyle->styleSheetPalette(widget, &option, &ctx.palette);
3313 }
3314 }
3315#endif // style_stylesheet
3316 if (d->cursorOn && d->isEnabled) {
3317 if (d->hideCursor)
3318 ctx.cursorPosition = -1;
3319 else if (d->preeditCursor != 0)
3320 ctx.cursorPosition = - (d->preeditCursor + 2);
3321 else
3322 ctx.cursorPosition = d->cursor.position();
3323 }
3324
3325 if (!d->dndFeedbackCursor.isNull())
3326 ctx.cursorPosition = d->dndFeedbackCursor.position();
3327 if (d->cursor.hasSelection()) {
3328 QAbstractTextDocumentLayout::Selection selection;
3329 selection.cursor = d->cursor;
3330 if (d->cursorIsFocusIndicator) {
3331 QStyleOption opt;
3332 opt.palette = ctx.palette;
3333 QStyleHintReturnVariant ret;
3334 QStyle *style = QApplication::style();
3335 if (widget)
3336 style = widget->style();
3337 style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret);
3338 selection.format = qvariant_cast<QTextFormat>(ret.variant).toCharFormat();
3339 } else {
3340 QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive;
3341 selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight));
3342 selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText));
3343 QStyleOption opt;
3344 QStyle *style = QApplication::style();
3345 if (widget) {
3346 opt.initFrom(widget);
3347 style = widget->style();
3348 }
3349 if (style->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, widget))
3350 selection.format.setProperty(QTextFormat::FullWidthSelection, true);
3351 }
3352 ctx.selections.append(selection);
3353 }
3354
3355 return ctx;
3356}
3357
3358void QWidgetTextControl::drawContents(QPainter *p, const QRectF &rect, QWidget *widget)
3359{
3360 Q_D(QWidgetTextControl);
3361 p->save();
3362 QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext(widget);
3363 if (rect.isValid())
3364 p->setClipRect(rect, Qt::IntersectClip);
3365 ctx.clip = rect;
3366
3367 d->doc->documentLayout()->draw(p, ctx);
3368 p->restore();
3369}
3370
3372{
3373#ifndef QT_NO_CLIPBOARD
3374 QMimeData *md = new QMimeData;
3375 md->setText(linkToCopy);
3376 QGuiApplication::clipboard()->setMimeData(md);
3377#endif
3378}
3379
3380int QWidgetTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
3381{
3382 Q_D(const QWidgetTextControl);
3383 return d->doc->documentLayout()->hitTest(point, accuracy);
3384}
3385
3386QRectF QWidgetTextControl::blockBoundingRect(const QTextBlock &block) const
3387{
3388 Q_D(const QWidgetTextControl);
3389 return d->doc->documentLayout()->blockBoundingRect(block);
3390}
3391
3392#ifndef QT_NO_CONTEXTMENU
3393#define NUM_CONTROL_CHARACTERS 14
3395 const char *text;
3397} qt_controlCharacters[NUM_CONTROL_CHARACTERS] = {
3398 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e },
3399 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f },
3400 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d },
3401 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c },
3402 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWSP Zero width space"), 0x200b },
3403 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRE Start of left-to-right embedding"), 0x202a },
3404 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLE Start of right-to-left embedding"), 0x202b },
3405 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRO Start of left-to-right override"), 0x202d },
3406 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLO Start of right-to-left override"), 0x202e },
3407 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDF Pop directional formatting"), 0x202c },
3408 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRI Left-to-right isolate"), 0x2066 },
3409 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLI Right-to-left isolate"), 0x2067 },
3410 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "FSI First strong isolate"), 0x2068 },
3411 { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDI Pop directional isolate"), 0x2069 }
3413
3414QUnicodeControlCharacterMenu::QUnicodeControlCharacterMenu(QObject *_editWidget, QWidget *parent)
3415 : QMenu(parent), editWidget(_editWidget)
3416{
3417 setTitle(tr("Insert Unicode control character"));
3418 for (int i = 0; i < NUM_CONTROL_CHARACTERS; ++i) {
3419 addAction(tr(qt_controlCharacters[i].text), this, SLOT(menuActionTriggered()));
3420 }
3421}
3422
3423void QUnicodeControlCharacterMenu::menuActionTriggered()
3424{
3425 QAction *a = qobject_cast<QAction *>(sender());
3426 int idx = actions().indexOf(a);
3427 if (idx < 0 || idx >= NUM_CONTROL_CHARACTERS)
3428 return;
3429 QChar c(qt_controlCharacters[idx].character);
3430 QString str(c);
3431
3432#if QT_CONFIG(textedit)
3433 if (QTextEdit *edit = qobject_cast<QTextEdit *>(editWidget)) {
3434 edit->insertPlainText(str);
3435 return;
3436 }
3437#endif
3438 if (QWidgetTextControl *control = qobject_cast<QWidgetTextControl *>(editWidget)) {
3439 control->insertPlainText(str);
3440 }
3441#if QT_CONFIG(lineedit)
3442 if (QLineEdit *edit = qobject_cast<QLineEdit *>(editWidget)) {
3443 edit->insert(str);
3444 return;
3445 }
3446#endif
3447}
3448#endif // QT_NO_CONTEXTMENU
3449
3450static constexpr auto supportedMimeTypes = qOffsetStringArray(
3451 "text/plain",
3452 "text/html"
3453#if QT_CONFIG(textmarkdownwriter)
3454 , "text/markdown"
3455#endif
3456#if QT_CONFIG(textodfwriter)
3457 , "application/vnd.oasis.opendocument.text"
3458#endif
3459);
3460
3461/*! \internal
3462 \reimp
3463*/
3465{
3466 if (!fragment.isEmpty()) {
3467 constexpr auto size = supportedMimeTypes.count();
3468 QStringList ret;
3469 ret.reserve(size);
3470 for (int i = 0; i < size; ++i)
3471 ret.emplace_back(QLatin1StringView(supportedMimeTypes.at(i)));
3472
3473 return ret;
3474 }
3475
3476 return QMimeData::formats();
3477}
3478
3479/*! \internal
3480 \reimp
3481*/
3482bool QTextEditMimeData::hasFormat(const QString &format) const
3483{
3484 if (!fragment.isEmpty()) {
3485 constexpr auto size = supportedMimeTypes.count();
3486 for (int i = 0; i < size; ++i) {
3487 if (format == QLatin1StringView(supportedMimeTypes.at(i)))
3488 return true;
3489 }
3490 return false;
3491 }
3492
3493 return QMimeData::hasFormat(format);
3494}
3495
3496QVariant QTextEditMimeData::retrieveData(const QString &mimeType, QMetaType type) const
3497{
3498 if (!fragment.isEmpty())
3499 setup();
3500 return QMimeData::retrieveData(mimeType, type);
3501}
3502
3503void QTextEditMimeData::setup() const
3504{
3505 QTextEditMimeData *that = const_cast<QTextEditMimeData *>(this);
3506#ifndef QT_NO_TEXTHTMLPARSER
3507 that->setData("text/html"_L1, fragment.toHtml().toUtf8());
3508#endif
3509#if QT_CONFIG(textmarkdownwriter)
3510 that->setData("text/markdown"_L1, fragment.toMarkdown().toUtf8());
3511#endif
3512#ifndef QT_NO_TEXTODFWRITER
3513 {
3514 QBuffer buffer;
3515 QTextDocumentWriter writer(&buffer, "ODF");
3516 writer.write(fragment);
3517 buffer.close();
3518 that->setData("application/vnd.oasis.opendocument.text"_L1, buffer.data());
3519 }
3520#endif
3521 that->setText(fragment.toPlainText());
3522 fragment = QTextDocumentFragment();
3523}
3524
3525QT_END_NAMESPACE
3526
3527#include "moc_qwidgettextcontrol_p.cpp"
3528
3529#endif // QT_NO_TEXTCONTROL
\inmodule QtCore \reentrant
Definition qbuffer.h:17
friend class QWidget
Definition qpainter.h:432
\inmodule QtCore\reentrant
Definition qpoint.h:232
The QTextDocumentWriter class provides a format-independent interface for writing a QTextDocument to ...
virtual QStringList formats() const override
\reentrant
Definition qtexttable.h:19
void setCursorVisible(bool visible)
void _q_contentsChanged(int from, int charsRemoved, int charsAdded)
void selectionChanged(bool forceEmitSelectionChanged=false)
void extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition)
void setCursorPosition(int pos, QTextCursor::MoveMode mode=QTextCursor::MoveAnchor)
bool dragEnterEvent(QEvent *e, const QMimeData *mimeData)
QRectF rectForPosition(int position) const
void extendBlockwiseSelection(int suggestedNewPosition)
#define ACCEL_KEY(k)
Definition qlineedit.cpp:56
static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor)
static constexpr auto supportedMimeTypes
#define NUM_CONTROL_CHARACTERS
static QTextLine currentTextLine(const QTextCursor &cursor)
void setActionIcon(QAction *action, const QString &name)