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