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