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

source code of qtbase/src/widgets/widgets/qwidgettextcontrol.cpp