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

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