Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwidgetlinecontrol.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#if QT_CONFIG(itemviews)
8#include "qabstractitemview.h"
9#endif
10#include "qclipboard.h"
11#include <private/qguiapplication_p.h>
12#if QT_CONFIG(completer)
13#include <private/qcompleter_p.h>
14#endif
15#include <qpa/qplatformtheme.h>
16#include <qstylehints.h>
17#if QT_CONFIG(accessibility)
18#include "qaccessible.h"
19#endif
20
21#include "qapplication.h"
22#include "private/qapplication_p.h"
23#if QT_CONFIG(graphicsview)
24#include "qgraphicssceneevent.h"
25#endif
26
27#include "qvalidator.h"
28
29using namespace std::chrono_literals;
30
31QT_BEGIN_NAMESPACE
32
33
34/*!
35 \internal
36
37 Updates the internal text layout. Returns the ascent of the
38 created QTextLine.
39*/
40int QWidgetLineControl::redoTextLayout() const
41{
42 m_textLayout.clearLayout();
43
44 m_textLayout.beginLayout();
45 QTextLine l = m_textLayout.createLine();
46 m_textLayout.endLayout();
47
48 return qRound(l.ascent());
49}
50
51/*!
52 \internal
53
54 Updates the display text based of the current edit text
55 If the text has changed will emit displayTextChanged()
56*/
57void QWidgetLineControl::updateDisplayText(bool forceUpdate)
58{
59 QString orig = m_textLayout.text();
60 QString str;
61 if (m_echoMode == QLineEdit::NoEcho)
62 str = QString::fromLatin1("");
63 else
64 str = m_text;
65
66 if (m_echoMode == QLineEdit::Password) {
67 str.fill(m_passwordCharacter);
68 if (m_passwordEchoTimer.isActive() && m_cursor > 0 && m_cursor <= m_text.size()) {
69 int cursor = m_cursor - 1;
70 QChar uc = m_text.at(cursor);
71 str[cursor] = uc;
72 if (cursor > 0 && uc.isLowSurrogate()) {
73 // second half of a surrogate, check if we have the first half as well,
74 // if yes restore both at once
75 uc = m_text.at(cursor - 1);
76 if (uc.isHighSurrogate())
77 str[cursor - 1] = uc;
78 }
79 }
80 } else if (m_echoMode == QLineEdit::PasswordEchoOnEdit && !m_passwordEchoEditing) {
81 str.fill(m_passwordCharacter);
82 }
83
84 // replace certain non-printable characters with spaces (to avoid
85 // drawing boxes when using fonts that don't have glyphs for such
86 // characters)
87 QChar* uc = str.data();
88 for (int i = 0; i < (int)str.size(); ++i) {
89 if ((uc[i].unicode() < 0x20 && uc[i].unicode() != 0x09)
90 || uc[i] == QChar::LineSeparator
91 || uc[i] == QChar::ParagraphSeparator)
92 uc[i] = QChar(0x0020);
93 }
94
95 m_textLayout.setText(str);
96
97 QTextOption option = m_textLayout.textOption();
98 option.setTextDirection(m_layoutDirection);
99 option.setFlags(QTextOption::IncludeTrailingSpaces);
100 m_textLayout.setTextOption(option);
101
102 m_ascent = redoTextLayout();
103
104 if (str != orig || forceUpdate)
105 emit displayTextChanged(str);
106}
107
108#ifndef QT_NO_CLIPBOARD
109/*!
110 \internal
111
112 Copies the currently selected text into the clipboard using the given
113 \a mode.
114
115 \note If the echo mode is set to a mode other than Normal then copy
116 will not work. This is to prevent using copy as a method of bypassing
117 password features of the line control.
118*/
119void QWidgetLineControl::copy(QClipboard::Mode mode) const
120{
121 QString t = selectedText();
122 if (!t.isEmpty() && m_echoMode == QLineEdit::Normal) {
123 QGuiApplication::clipboard()->setText(t, mode);
124 }
125}
126
127/*!
128 \internal
129
130 Inserts the text stored in the application clipboard into the line
131 control.
132
133 \sa insert()
134*/
135void QWidgetLineControl::paste(QClipboard::Mode clipboardMode)
136{
137 QString clip = QGuiApplication::clipboard()->text(clipboardMode);
138 if (!clip.isEmpty() || hasSelectedText()) {
139 separate(); //make it a separate undo/redo command
140 insert(clip);
141 separate();
142 }
143}
144
145#endif // !QT_NO_CLIPBOARD
146
147/*!
148 \internal
149*/
150void QWidgetLineControl::commitPreedit()
151{
152#ifndef QT_NO_IM
153 if (!composeMode())
154 return;
155
156 QGuiApplication::inputMethod()->commit();
157 if (!composeMode())
158 return;
159
160 m_preeditCursor = 0;
161 setPreeditArea(-1, QString());
162 m_textLayout.clearFormats();
163 updateDisplayText(/*force*/ true);
164#endif
165}
166
167
168/*!
169 \internal
170
171 Handles the behavior for the backspace key or function.
172 Removes the current selection if there is a selection, otherwise
173 removes the character prior to the cursor position.
174
175 \sa del()
176*/
177void QWidgetLineControl::backspace()
178{
179 int priorState = m_undoState;
180 if (hasSelectedText()) {
181 removeSelectedText();
182 } else if (m_cursor) {
183 --m_cursor;
184 if (m_maskData)
185 m_cursor = prevMaskBlank(m_cursor);
186 QChar uc = m_text.at(m_cursor);
187 if (m_cursor > 0 && uc.isLowSurrogate()) {
188 // second half of a surrogate, check if we have the first half as well,
189 // if yes delete both at once
190 uc = m_text.at(m_cursor - 1);
191 if (uc.isHighSurrogate()) {
192 internalDelete(true);
193 --m_cursor;
194 }
195 }
196 internalDelete(true);
197 }
198 finishChange(priorState);
199}
200
201/*!
202 \internal
203
204 Handles the behavior for the delete key or function.
205 Removes the current selection if there is a selection, otherwise
206 removes the character after the cursor position.
207
208 \sa del()
209*/
210void QWidgetLineControl::del()
211{
212 int priorState = m_undoState;
213 if (hasSelectedText()) {
214 removeSelectedText();
215 } else {
216 int n = textLayout()->nextCursorPosition(m_cursor) - m_cursor;
217 while (n--)
218 internalDelete();
219 }
220 finishChange(priorState);
221}
222
223/*!
224 \internal
225
226 Inserts the given \a newText at the current cursor position.
227 If there is any selected text it is removed prior to insertion of
228 the new text.
229*/
230void QWidgetLineControl::insert(const QString &newText)
231{
232 int priorState = m_undoState;
233 removeSelectedText();
234 internalInsert(newText);
235 finishChange(priorState);
236}
237
238/*!
239 \internal
240
241 Clears the line control text.
242*/
243void QWidgetLineControl::clear()
244{
245 int priorState = m_undoState;
246 m_selstart = 0;
247 m_selend = m_text.size();
248 removeSelectedText();
249 separate();
250 finishChange(priorState, /*update*/false, /*edited*/false);
251}
252/*!
253 \internal
254
255 Undoes the previous operation.
256*/
257
258void QWidgetLineControl::undo()
259{
260 // Undo works only for clearing the line when in any of password the modes
261 if (m_echoMode == QLineEdit::Normal) {
262 internalUndo();
263 finishChange(-1, true);
264 } else {
265 cancelPasswordEchoTimer();
266 clear();
267 }
268}
269
270/*!
271 \internal
272
273 Sets \a length characters from the given \a start position as selected.
274 The given \a start position must be within the current text for
275 the line control. If \a length characters cannot be selected, then
276 the selection will extend to the end of the current text.
277*/
278void QWidgetLineControl::setSelection(int start, int length)
279{
280 commitPreedit();
281
282 if (Q_UNLIKELY(start < 0 || start > m_text.size())) {
283 qWarning("QWidgetLineControl::setSelection: Invalid start position");
284 return;
285 }
286
287 if (length > 0) {
288 if (start == m_selstart && start + length == m_selend && m_cursor == m_selend)
289 return;
290 m_selstart = start;
291 m_selend = qMin(start + length, (int)m_text.size());
292 m_cursor = m_selend;
293 } else if (length < 0){
294 if (start == m_selend && start + length == m_selstart && m_cursor == m_selstart)
295 return;
296 m_selstart = qMax(start + length, 0);
297 m_selend = start;
298 m_cursor = m_selstart;
299 } else if (m_selstart != m_selend) {
300 m_selstart = 0;
301 m_selend = 0;
302 m_cursor = start;
303 } else {
304 m_cursor = start;
305 emitCursorPositionChanged();
306 return;
307 }
308 emit selectionChanged();
309 emitCursorPositionChanged();
310}
311
312void QWidgetLineControl::_q_deleteSelected()
313{
314 if (!hasSelectedText())
315 return;
316
317 int priorState = m_undoState;
318 emit resetInputContext();
319 removeSelectedText();
320 separate();
321 finishChange(priorState);
322}
323
324/*!
325 \internal
326
327 Initializes the line control with a starting text value of \a txt.
328*/
329void QWidgetLineControl::init(const QString &txt)
330{
331 m_textLayout.setCacheEnabled(true);
332 m_text = txt;
333 updateDisplayText();
334 m_cursor = m_text.size();
335 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
336 m_keyboardScheme = theme->themeHint(QPlatformTheme::KeyboardScheme).toInt();
337 m_passwordMaskDelay = theme->themeHint(QPlatformTheme::PasswordMaskDelay).toInt();
338 }
339 // Generalize for X11
340 if (m_keyboardScheme == QPlatformTheme::KdeKeyboardScheme
341 || m_keyboardScheme == QPlatformTheme::GnomeKeyboardScheme
342 || m_keyboardScheme == QPlatformTheme::CdeKeyboardScheme) {
343 m_keyboardScheme = QPlatformTheme::X11KeyboardScheme;
344 }
345}
346
347/*!
348 \internal
349
350 Sets the password echo editing to \a editing. If password echo editing
351 is true, then the text of the password is displayed even if the echo
352 mode is set to QLineEdit::PasswordEchoOnEdit. Password echoing editing
353 does not affect other echo modes.
354*/
355void QWidgetLineControl::updatePasswordEchoEditing(bool editing)
356{
357 cancelPasswordEchoTimer();
358 m_passwordEchoEditing = editing;
359 updateDisplayText();
360}
361
362/*!
363 \internal
364
365 Returns the cursor position of the given \a x pixel value in relation
366 to the displayed text. The given \a betweenOrOn specified what kind
367 of cursor position is requested.
368*/
369int QWidgetLineControl::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
370{
371 return textLayout()->lineAt(0).xToCursor(x, betweenOrOn);
372}
373
374/*!
375 \internal
376
377 Returns the bounds of the given text position.
378*/
379QRect QWidgetLineControl::rectForPos(int pos) const
380{
381 QTextLine l = textLayout()->lineAt(0);
382 if (m_preeditCursor != -1)
383 pos += m_preeditCursor;
384 int cix = qRound(l.cursorToX(pos));
385 int w = m_cursorWidth;
386 int ch = l.height() + 1;
387
388 return QRect(cix - 5, 0, w + 9, ch);
389}
390
391/*!
392 \internal
393
394 Returns the bounds of the current cursor, as defined as a
395 between characters cursor.
396*/
397QRect QWidgetLineControl::cursorRect() const
398{
399 return rectForPos(m_cursor);
400}
401
402/*!
403 \internal
404
405 Returns the bounds of the current anchor
406*/
407QRect QWidgetLineControl::anchorRect() const
408{
409 if (!hasSelectedText())
410 return cursorRect();
411 return rectForPos(m_cursor == m_selstart ? m_selend : m_selstart);
412}
413
414/*!
415 \internal
416
417 Fixes the current text so that it is valid given any set validators.
418
419 Returns \c true if the text was changed. Otherwise returns \c false.
420*/
421bool QWidgetLineControl::fixup() // this function assumes that validate currently returns != Acceptable
422{
423#ifndef QT_NO_VALIDATOR
424 if (m_validator) {
425 QString textCopy = m_text;
426 int cursorCopy = m_cursor;
427 m_validator->fixup(textCopy);
428 if (m_validator->validate(textCopy, cursorCopy) == QValidator::Acceptable) {
429 if (textCopy != m_text || cursorCopy != m_cursor)
430 internalSetText(textCopy, cursorCopy, false);
431 return true;
432 }
433 }
434#endif
435 return false;
436}
437
438/*!
439 \internal
440
441 Moves the cursor to the given position \a pos. If \a mark is true will
442 adjust the currently selected text.
443*/
444void QWidgetLineControl::moveCursor(int pos, bool mark)
445{
446 commitPreedit();
447
448 if (pos != m_cursor) {
449 separate();
450 if (m_maskData)
451 pos = pos > m_cursor ? nextMaskBlank(pos) : prevMaskBlank(pos);
452 }
453 if (mark) {
454 int anchor;
455 if (m_selend > m_selstart && m_cursor == m_selstart)
456 anchor = m_selend;
457 else if (m_selend > m_selstart && m_cursor == m_selend)
458 anchor = m_selstart;
459 else
460 anchor = m_cursor;
461 m_selstart = qMin(anchor, pos);
462 m_selend = qMax(anchor, pos);
463 updateDisplayText();
464 } else {
465 internalDeselect();
466 }
467 m_cursor = pos;
468 if (mark || m_selDirty) {
469 m_selDirty = false;
470 emit selectionChanged();
471 }
472 emitCursorPositionChanged();
473}
474
475/*!
476 \internal
477
478 Applies the given input method event \a event to the text of the line
479 control
480*/
481void QWidgetLineControl::processInputMethodEvent(QInputMethodEvent *event)
482{
483 int priorState = -1;
484 bool isGettingInput = !event->commitString().isEmpty()
485 || event->preeditString() != preeditAreaText()
486 || event->replacementLength() > 0;
487 bool cursorPositionChanged = false;
488 bool selectionChange = false;
489
490 if (isGettingInput) {
491 // If any text is being input, remove selected text.
492 priorState = m_undoState;
493 if (echoMode() == QLineEdit::PasswordEchoOnEdit && !passwordEchoEditing()) {
494 updatePasswordEchoEditing(true);
495 m_selstart = 0;
496 m_selend = m_text.size();
497 }
498 removeSelectedText();
499 }
500
501 int c = m_cursor; // cursor position after insertion of commit string
502 if (event->replacementStart() <= 0)
503 c += event->commitString().size() - qMin(-event->replacementStart(), event->replacementLength());
504
505 m_cursor += event->replacementStart();
506 if (m_cursor < 0)
507 m_cursor = 0;
508
509 // insert commit string
510 if (event->replacementLength()) {
511 m_selstart = m_cursor;
512 m_selend = m_selstart + event->replacementLength();
513 removeSelectedText();
514 }
515 if (!event->commitString().isEmpty()) {
516 internalInsert(event->commitString());
517 cursorPositionChanged = true;
518 } else {
519 m_cursor = qBound(0, c, m_text.size());
520 }
521
522 for (int i = 0; i < event->attributes().size(); ++i) {
523 const QInputMethodEvent::Attribute &a = event->attributes().at(i);
524 if (a.type == QInputMethodEvent::Selection) {
525 m_cursor = qBound(0, a.start + a.length, m_text.size());
526 if (a.length) {
527 m_selstart = qMax(0, qMin(a.start, m_text.size()));
528 m_selend = m_cursor;
529 if (m_selend < m_selstart) {
530 qSwap(m_selstart, m_selend);
531 }
532 selectionChange = true;
533 } else {
534 if (m_selstart != m_selend)
535 selectionChange = true;
536 m_selstart = m_selend = 0;
537 }
538 cursorPositionChanged = true;
539 }
540 }
541#ifndef QT_NO_IM
542 // in NoEcho mode, the cursor is always at the beginning of the lineedit
543 switch (m_echoMode) {
544 case QLineEdit::NoEcho:
545 setPreeditArea(0, QString());
546 break;
547 case QLineEdit::Password: {
548 QString preeditString = event->preeditString();
549 preeditString.fill(m_passwordCharacter);
550 setPreeditArea(m_cursor, preeditString);
551 break;
552 }
553 default:
554 setPreeditArea(m_cursor, event->preeditString());
555 break;
556 }
557#endif //QT_NO_IM
558 const int oldPreeditCursor = m_preeditCursor;
559 m_preeditCursor = event->preeditString().size();
560 m_hideCursor = false;
561 QList<QTextLayout::FormatRange> formats;
562 formats.reserve(event->attributes().size());
563 for (int i = 0; i < event->attributes().size(); ++i) {
564 const QInputMethodEvent::Attribute &a = event->attributes().at(i);
565 if (a.type == QInputMethodEvent::Cursor) {
566 m_preeditCursor = a.start;
567 m_hideCursor = !a.length;
568 } else if (a.type == QInputMethodEvent::TextFormat) {
569 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
570 if (f.isValid()) {
571 QTextLayout::FormatRange o;
572 o.start = a.start + m_cursor;
573 o.length = a.length;
574 o.format = f;
575 formats.append(o);
576 }
577 }
578 }
579 m_textLayout.setFormats(formats);
580 updateDisplayText(/*force*/ true);
581 if (cursorPositionChanged)
582 emitCursorPositionChanged();
583 else if (m_preeditCursor != oldPreeditCursor)
584 emit updateMicroFocus();
585
586 if (isGettingInput)
587 finishChange(priorState);
588
589 if (selectionChange)
590 emit selectionChanged();
591}
592
593/*!
594 \internal
595
596 Draws the display text for the line control using the given
597 \a painter, \a clip, and \a offset. Which aspects of the display text
598 are drawn is specified by the given \a flags.
599
600 If the flags contain DrawSelections, then the selection or input mask
601 backgrounds and foregrounds will be applied before drawing the text.
602
603 If the flags contain DrawCursor a cursor of the current cursorWidth()
604 will be drawn after drawing the text.
605
606 The display text will only be drawn if the flags contain DrawText
607*/
608void QWidgetLineControl::draw(QPainter *painter, const QPoint &offset, const QRect &clip, int flags)
609{
610 QList<QTextLayout::FormatRange> selections;
611 if (flags & DrawSelections) {
612 QTextLayout::FormatRange o;
613 if (m_selstart < m_selend) {
614 o.start = m_selstart;
615 o.length = m_selend - m_selstart;
616 o.format.setBackground(m_palette.brush(QPalette::Highlight));
617 o.format.setForeground(m_palette.brush(QPalette::HighlightedText));
618 } else {
619 // mask selection
620 if (m_blinkStatus){
621 o.start = m_cursor;
622 o.length = 1;
623 o.format.setBackground(m_palette.brush(QPalette::Text));
624 o.format.setForeground(m_palette.brush(QPalette::Window));
625 }
626 }
627 selections.append(o);
628 }
629
630 if (flags & DrawText)
631 textLayout()->draw(painter, offset, selections, clip);
632
633 if (flags & DrawCursor){
634 int cursor = m_cursor;
635 if (m_preeditCursor != -1)
636 cursor += m_preeditCursor;
637 if (!m_hideCursor && m_blinkStatus)
638 textLayout()->drawCursor(painter, offset, cursor, m_cursorWidth);
639 }
640}
641
642/*!
643 \internal
644
645 Sets the selection to cover the word at the given cursor position.
646 The word boundaries are defined by the behavior of QTextLayout::SkipWords
647 cursor mode.
648*/
649void QWidgetLineControl::selectWordAtPos(int cursor)
650{
651 int next = cursor + 1;
652 if (next > end())
653 --next;
654 int c = textLayout()->previousCursorPosition(next, QTextLayout::SkipWords);
655 moveCursor(c, false);
656 // ## text layout should support end of words.
657 int end = textLayout()->nextCursorPosition(c, QTextLayout::SkipWords);
658 while (end > cursor && m_text[end-1].isSpace())
659 --end;
660 moveCursor(end, true);
661}
662
663/*!
664 \internal
665
666 Completes a change to the line control text. If the change is not valid
667 will undo the line control state back to the given \a validateFromState.
668
669 If \a edited is true and the change is valid, will emit textEdited() in
670 addition to textChanged(). Otherwise only emits textChanged() on a valid
671 change.
672
673 The \a update value is currently unused.
674*/
675bool QWidgetLineControl::finishChange(int validateFromState, bool update, bool edited)
676{
677 Q_UNUSED(update);
678
679 if (m_textDirty) {
680 // do validation
681 bool wasValidInput = m_validInput;
682 m_validInput = true;
683#ifndef QT_NO_VALIDATOR
684 if (m_validator) {
685 QString textCopy = m_text;
686 int cursorCopy = m_cursor;
687 m_validInput = (m_validator->validate(textCopy, cursorCopy) != QValidator::Invalid);
688 if (m_validInput) {
689 if (m_text != textCopy) {
690 internalSetText(textCopy, cursorCopy, edited);
691 return true;
692 }
693 m_cursor = cursorCopy;
694 } else {
695 emit inputRejected();
696 }
697 }
698#endif
699 if (validateFromState >= 0 && wasValidInput && !m_validInput) {
700 if (m_transactions.size())
701 return false;
702 internalUndo(validateFromState);
703 m_history.erase(m_history.begin() + m_undoState, m_history.end());
704 if (m_modifiedState > m_undoState)
705 m_modifiedState = -1;
706 m_validInput = true;
707 m_textDirty = false;
708 }
709 updateDisplayText();
710
711 if (m_textDirty) {
712 m_textDirty = false;
713 QString actualText = text();
714 if (edited)
715 emit textEdited(actualText);
716 emit textChanged(actualText);
717 }
718 }
719 if (m_selDirty) {
720 m_selDirty = false;
721 emit selectionChanged();
722 }
723 if (m_cursor == m_lastCursorPos)
724 emit updateMicroFocus();
725 emitCursorPositionChanged();
726 return true;
727}
728
729/*!
730 \internal
731
732 An internal function for setting the text of the line control.
733*/
734void QWidgetLineControl::internalSetText(const QString &txt, int pos, bool edited)
735{
736 cancelPasswordEchoTimer();
737 internalDeselect();
738 emit resetInputContext();
739 QString oldText = m_text;
740 if (m_maskData) {
741 m_text = maskString(0, txt, true);
742 m_text += clearString(m_text.size(), m_maxLength - m_text.size());
743 if (edited && oldText == m_text)
744 emit inputRejected();
745 } else {
746 m_text = txt.isEmpty() ? txt : txt.left(m_maxLength);
747 }
748 m_history.clear();
749 m_modifiedState = m_undoState = 0;
750 m_cursor = (pos < 0 || pos > m_text.size()) ? m_text.size() : pos;
751 m_textDirty = (oldText != m_text);
752 const bool changed = finishChange(-1, true, edited);
753
754#if QT_CONFIG(accessibility)
755 if (changed) {
756 if (oldText.isEmpty()) {
757 QAccessibleTextInsertEvent event(accessibleObject(), 0, txt);
758 event.setCursorPosition(m_cursor);
759 QAccessible::updateAccessibility(&event);
760 } else if (txt.isEmpty()) {
761 QAccessibleTextRemoveEvent event(accessibleObject(), 0, oldText);
762 event.setCursorPosition(m_cursor);
763 QAccessible::updateAccessibility(&event);
764 } else {
765 QAccessibleTextUpdateEvent event(accessibleObject(), 0, oldText, txt);
766 event.setCursorPosition(m_cursor);
767 QAccessible::updateAccessibility(&event);
768 }
769 }
770#else
771 Q_UNUSED(changed);
772#endif
773}
774
775
776/*!
777 \internal
778
779 Adds the given \a command to the undo history
780 of the line control. Does not apply the command.
781*/
782void QWidgetLineControl::addCommand(const Command &cmd)
783{
784 m_history.erase(m_history.begin() + m_undoState, m_history.end());
785
786 if (m_separator && m_undoState && m_history[m_undoState - 1].type != Separator)
787 m_history.push_back(Command(Separator, m_cursor, u'\0', m_selstart, m_selend));
788
789 m_separator = false;
790 m_history.push_back(cmd);
791 m_undoState = int(m_history.size());
792}
793
794/*!
795 \internal
796
797 Inserts the given string \a s into the line
798 control.
799
800 Also adds the appropriate commands into the undo history.
801 This function does not call finishChange(), and may leave the text
802 in an invalid state.
803*/
804void QWidgetLineControl::internalInsert(const QString &s)
805{
806 if (m_echoMode == QLineEdit::Password) {
807 if (m_passwordEchoTimer.isActive())
808 m_passwordEchoTimer.stop();
809 int delay = m_passwordMaskDelay;
810#ifdef QT_BUILD_INTERNAL
811 if (m_passwordMaskDelayOverride >= 0)
812 delay = m_passwordMaskDelayOverride;
813#endif
814
815 if (delay > 0)
816 m_passwordEchoTimer.start(delay * 1ms, this);
817 }
818 if (hasSelectedText())
819 addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
820 if (m_maskData) {
821 QString ms = maskString(m_cursor, s);
822 if (ms.isEmpty() && !s.isEmpty())
823 emit inputRejected();
824#if QT_CONFIG(accessibility)
825 QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, ms);
826 QAccessible::updateAccessibility(&insertEvent);
827#endif
828 for (int i = 0; i < (int) ms.size(); ++i) {
829 addCommand (Command(DeleteSelection, m_cursor + i, m_text.at(m_cursor + i), -1, -1));
830 addCommand(Command(Insert, m_cursor + i, ms.at(i), -1, -1));
831 }
832 m_text.replace(m_cursor, ms.size(), ms);
833 m_cursor += ms.size();
834 m_cursor = nextMaskBlank(m_cursor);
835 m_textDirty = true;
836#if QT_CONFIG(accessibility)
837 QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
838 QAccessible::updateAccessibility(&event);
839#endif
840 } else {
841 int remaining = m_maxLength - m_text.size();
842 if (remaining != 0) {
843#if QT_CONFIG(accessibility)
844 QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, s);
845 QAccessible::updateAccessibility(&insertEvent);
846#endif
847 m_text.insert(m_cursor, s.left(remaining));
848 for (int i = 0; i < (int) s.left(remaining).size(); ++i)
849 addCommand(Command(Insert, m_cursor++, s.at(i), -1, -1));
850 m_textDirty = true;
851 }
852 if (s.size() > remaining)
853 emit inputRejected();
854 }
855}
856
857/*!
858 \internal
859
860 deletes a single character from the current text. If \a wasBackspace,
861 the character prior to the cursor is removed. Otherwise the character
862 after the cursor is removed.
863
864 Also adds the appropriate commands into the undo history.
865 This function does not call finishChange(), and may leave the text
866 in an invalid state.
867*/
868void QWidgetLineControl::internalDelete(bool wasBackspace)
869{
870 if (m_cursor < (int) m_text.size()) {
871 cancelPasswordEchoTimer();
872 if (hasSelectedText())
873 addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
874 addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
875 m_cursor, m_text.at(m_cursor), -1, -1));
876#if QT_CONFIG(accessibility)
877 QAccessibleTextRemoveEvent event(accessibleObject(), m_cursor, m_text.at(m_cursor));
878 QAccessible::updateAccessibility(&event);
879#endif
880 if (m_maskData) {
881 m_text.replace(m_cursor, 1, clearString(m_cursor, 1));
882 addCommand(Command(Insert, m_cursor, m_text.at(m_cursor), -1, -1));
883 } else {
884 m_text.remove(m_cursor, 1);
885 }
886 m_textDirty = true;
887 }
888}
889
890/*!
891 \internal
892
893 removes the currently selected text from the line control.
894
895 Also adds the appropriate commands into the undo history.
896 This function does not call finishChange(), and may leave the text
897 in an invalid state.
898*/
899void QWidgetLineControl::removeSelectedText()
900{
901 if (m_selstart < m_selend && m_selend <= (int) m_text.size()) {
902 cancelPasswordEchoTimer();
903 separate();
904 int i ;
905 addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
906 if (m_selstart <= m_cursor && m_cursor < m_selend) {
907 // cursor is within the selection. Split up the commands
908 // to be able to restore the correct cursor position
909 for (i = m_cursor; i >= m_selstart; --i)
910 addCommand (Command(DeleteSelection, i, m_text.at(i), -1, 1));
911 for (i = m_selend - 1; i > m_cursor; --i)
912 addCommand (Command(DeleteSelection, i - m_cursor + m_selstart - 1, m_text.at(i), -1, -1));
913 } else {
914 for (i = m_selend-1; i >= m_selstart; --i)
915 addCommand (Command(RemoveSelection, i, m_text.at(i), -1, -1));
916 }
917#if QT_CONFIG(accessibility)
918 QAccessibleTextRemoveEvent event(accessibleObject(), m_selstart, m_text.mid(m_selstart, m_selend - m_selstart));
919 QAccessible::updateAccessibility(&event);
920#endif
921 if (m_maskData) {
922 m_text.replace(m_selstart, m_selend - m_selstart, clearString(m_selstart, m_selend - m_selstart));
923 for (int i = 0; i < m_selend - m_selstart; ++i)
924 addCommand(Command(Insert, m_selstart + i, m_text.at(m_selstart + i), -1, -1));
925 } else {
926 m_text.remove(m_selstart, m_selend - m_selstart);
927 }
928 if (m_cursor > m_selstart)
929 m_cursor -= qMin(m_cursor, m_selend) - m_selstart;
930 internalDeselect();
931 m_textDirty = true;
932 }
933}
934
935/*!
936 \internal
937
938 Parses the input mask specified by \a maskFields to generate
939 the mask data used to handle input masks.
940*/
941void QWidgetLineControl::parseInputMask(const QString &maskFields)
942{
943 qsizetype delimiter = maskFields.indexOf(u';');
944 if (maskFields.isEmpty() || delimiter == 0) {
945 if (m_maskData) {
946 m_maskData.reset();
947 m_maxLength = 32767;
948 internalSetText(QString(), -1, false);
949 }
950 return;
951 }
952
953 if (delimiter == -1) {
954 m_blank = u' ';
955 m_inputMask = maskFields;
956 } else {
957 m_inputMask = maskFields.left(delimiter);
958 m_blank = (delimiter + 1 < maskFields.size()) ? maskFields[delimiter + 1] : u' ';
959 }
960
961 // calculate m_maxLength / m_maskData length
962 m_maxLength = 0;
963 bool escaped = false;
964 for (int i=0; i<m_inputMask.size(); i++) {
965 const auto c = m_inputMask.at(i);
966 if (escaped) {
967 ++m_maxLength;
968 escaped = false;
969 continue;
970 }
971
972 if (c == u'\\') {
973 escaped = true;
974 continue;
975 }
976
977 if (c != u'\\' && c != u'!' && c != u'<' && c != u'>' &&
978 c != u'{' && c != u'}' && c != u'[' && c != u']')
979 m_maxLength++;
980 }
981
982 m_maskData = std::make_unique<MaskInputData[]>(m_maxLength);
983
984 MaskInputData::Casemode m = MaskInputData::NoCaseMode;
985 bool s;
986 bool escape = false;
987 int index = 0;
988 for (int i = 0; i < m_inputMask.size(); i++) {
989 const auto c = m_inputMask.at(i);
990 if (escape) {
991 s = true;
992 m_maskData[index].maskChar = c;
993 m_maskData[index].separator = s;
994 m_maskData[index].caseMode = m;
995 index++;
996 escape = false;
997 } else if (c == u'<') {
998 m = MaskInputData::Lower;
999 } else if (c == u'>') {
1000 m = MaskInputData::Upper;
1001 } else if (c == u'!') {
1002 m = MaskInputData::NoCaseMode;
1003 } else if (c != u'{' && c != u'}' && c != u'[' && c != u']') {
1004 switch (c.unicode()) {
1005 case 'A':
1006 case 'a':
1007 case 'N':
1008 case 'n':
1009 case 'X':
1010 case 'x':
1011 case '9':
1012 case '0':
1013 case 'D':
1014 case 'd':
1015 case '#':
1016 case 'H':
1017 case 'h':
1018 case 'B':
1019 case 'b':
1020 s = false;
1021 break;
1022 case '\\':
1023 escape = true;
1024 Q_FALLTHROUGH();
1025 default:
1026 s = true;
1027 break;
1028 }
1029
1030 if (!escape) {
1031 m_maskData[index].maskChar = c;
1032 m_maskData[index].separator = s;
1033 m_maskData[index].caseMode = m;
1034 index++;
1035 }
1036 }
1037 }
1038 internalSetText(m_text, -1, false);
1039}
1040
1041
1042/*!
1043 \internal
1044
1045 checks if the key is valid compared to the inputMask
1046*/
1047bool QWidgetLineControl::isValidInput(QChar key, QChar mask) const
1048{
1049 switch (mask.unicode()) {
1050 case 'A':
1051 if (key.isLetter())
1052 return true;
1053 break;
1054 case 'a':
1055 if (key.isLetter() || key == m_blank)
1056 return true;
1057 break;
1058 case 'N':
1059 if (key.isLetterOrNumber())
1060 return true;
1061 break;
1062 case 'n':
1063 if (key.isLetterOrNumber() || key == m_blank)
1064 return true;
1065 break;
1066 case 'X':
1067 if (key.isPrint() && key != m_blank)
1068 return true;
1069 break;
1070 case 'x':
1071 if (key.isPrint() || key == m_blank)
1072 return true;
1073 break;
1074 case '9':
1075 if (key.isNumber())
1076 return true;
1077 break;
1078 case '0':
1079 if (key.isNumber() || key == m_blank)
1080 return true;
1081 break;
1082 case 'D':
1083 if (key.isNumber() && key.digitValue() > 0)
1084 return true;
1085 break;
1086 case 'd':
1087 if ((key.isNumber() && key.digitValue() > 0) || key == m_blank)
1088 return true;
1089 break;
1090 case '#':
1091 if (key.isNumber() || key == u'+' || key == u'-' || key == m_blank)
1092 return true;
1093 break;
1094 case 'B':
1095 if (key == u'0' || key == u'1')
1096 return true;
1097 break;
1098 case 'b':
1099 if (key == u'0' || key == u'1' || key == m_blank)
1100 return true;
1101 break;
1102 case 'H':
1103 if (key.isNumber() || (key >= u'a' && key <= u'f') || (key >= u'A' && key <= u'F'))
1104 return true;
1105 break;
1106 case 'h':
1107 if (key.isNumber() || (key >= u'a' && key <= u'f') || (key >= u'A' && key <= u'F') || key == m_blank)
1108 return true;
1109 break;
1110 default:
1111 break;
1112 }
1113 return false;
1114}
1115
1116/*!
1117 \internal
1118
1119 Returns \c true if the given text \a str is valid for any
1120 validator or input mask set for the line control.
1121
1122 Otherwise returns \c false
1123*/
1124bool QWidgetLineControl::hasAcceptableInput(const QString &str) const
1125{
1126#ifndef QT_NO_VALIDATOR
1127 QString textCopy = str;
1128 int cursorCopy = m_cursor;
1129 if (m_validator && m_validator->validate(textCopy, cursorCopy)
1130 != QValidator::Acceptable)
1131 return false;
1132#endif
1133
1134 if (!m_maskData)
1135 return true;
1136
1137 if (str.size() != m_maxLength)
1138 return false;
1139
1140 for (int i=0; i < m_maxLength; ++i) {
1141 if (m_maskData[i].separator) {
1142 if (str.at(i) != m_maskData[i].maskChar)
1143 return false;
1144 } else {
1145 if (!isValidInput(str.at(i), m_maskData[i].maskChar))
1146 return false;
1147 }
1148 }
1149 return true;
1150}
1151
1152/*!
1153 \internal
1154
1155 Applies the inputMask on \a str starting from position \a pos in the mask. \a clear
1156 specifies from where characters should be gotten when a separator is met in \a str - true means
1157 that blanks will be used, false that previous input is used.
1158 Calling this when no inputMask is set is undefined.
1159*/
1160QString QWidgetLineControl::maskString(int pos, const QString &str, bool clear) const
1161{
1162 if (pos >= m_maxLength)
1163 return QString::fromLatin1("");
1164
1165 QString fill;
1166 fill = clear ? clearString(0, m_maxLength) : m_text;
1167
1168 int strIndex = 0;
1169 QString s = QString::fromLatin1("");
1170 int i = pos;
1171 while (i < m_maxLength) {
1172 if (strIndex < str.size()) {
1173 if (m_maskData[i].separator) {
1174 s += m_maskData[i].maskChar;
1175 if (str[strIndex] == m_maskData[i].maskChar)
1176 strIndex++;
1177 ++i;
1178 } else {
1179 if (isValidInput(str[strIndex], m_maskData[i].maskChar)) {
1180 switch (m_maskData[i].caseMode) {
1181 case MaskInputData::Upper:
1182 s += str[strIndex].toUpper();
1183 break;
1184 case MaskInputData::Lower:
1185 s += str[strIndex].toLower();
1186 break;
1187 default:
1188 s += str[strIndex];
1189 }
1190 ++i;
1191 } else {
1192 // search for separator first
1193 int n = findInMask(i, true, true, str[strIndex]);
1194 if (n != -1) {
1195 if (str.size() != 1 || i == 0 || (i > 0 && (!m_maskData[i-1].separator || m_maskData[i-1].maskChar != str[strIndex]))) {
1196 s += QStringView{fill}.mid(i, n - i + 1);
1197 i = n + 1; // update i to find + 1
1198 }
1199 } else {
1200 // search for valid m_blank if not
1201 n = findInMask(i, true, false, str[strIndex]);
1202 if (n != -1) {
1203 s += QStringView{fill}.mid(i, n - i);
1204 switch (m_maskData[n].caseMode) {
1205 case MaskInputData::Upper:
1206 s += str[strIndex].toUpper();
1207 break;
1208 case MaskInputData::Lower:
1209 s += str[strIndex].toLower();
1210 break;
1211 default:
1212 s += str[strIndex];
1213 }
1214 i = n + 1; // updates i to find + 1
1215 }
1216 }
1217 }
1218 ++strIndex;
1219 }
1220 } else
1221 break;
1222 }
1223
1224 return s;
1225}
1226
1227
1228
1229/*!
1230 \internal
1231
1232 Returns a "cleared" string with only separators and blank chars.
1233 Calling this when no inputMask is set is undefined.
1234*/
1235QString QWidgetLineControl::clearString(int pos, int len) const
1236{
1237 if (pos >= m_maxLength)
1238 return QString();
1239
1240 QString s;
1241 int end = qMin(m_maxLength, pos + len);
1242 for (int i = pos; i < end; ++i)
1243 if (m_maskData[i].separator)
1244 s += m_maskData[i].maskChar;
1245 else
1246 s += m_blank;
1247
1248 return s;
1249}
1250
1251/*!
1252 \internal
1253
1254 Strips blank parts of the input in a QWidgetLineControl when an inputMask is set,
1255 separators are still included. Typically "127.0__.0__.1__" becomes "127.0.0.1".
1256*/
1257QString QWidgetLineControl::stripString(const QString &str) const
1258{
1259 if (!m_maskData)
1260 return str;
1261
1262 QString s;
1263 int end = qMin(m_maxLength, (int)str.size());
1264 for (int i = 0; i < end; ++i)
1265 if (m_maskData[i].separator)
1266 s += m_maskData[i].maskChar;
1267 else
1268 if (str[i] != m_blank)
1269 s += str[i];
1270
1271 return s;
1272}
1273
1274/*!
1275 \internal
1276 searches forward/backward in m_maskData for either a separator or a m_blank
1277*/
1278int QWidgetLineControl::findInMask(int pos, bool forward, bool findSeparator, QChar searchChar) const
1279{
1280 if (pos >= m_maxLength || pos < 0)
1281 return -1;
1282
1283 int end = forward ? m_maxLength : -1;
1284 int step = forward ? 1 : -1;
1285 int i = pos;
1286
1287 while (i != end) {
1288 if (findSeparator) {
1289 if (m_maskData[i].separator && m_maskData[i].maskChar == searchChar)
1290 return i;
1291 } else {
1292 if (!m_maskData[i].separator) {
1293 if (searchChar.isNull())
1294 return i;
1295 else if (isValidInput(searchChar, m_maskData[i].maskChar))
1296 return i;
1297 }
1298 }
1299 i += step;
1300 }
1301 return -1;
1302}
1303
1304void QWidgetLineControl::internalUndo(int until)
1305{
1306 if (!isUndoAvailable())
1307 return;
1308 cancelPasswordEchoTimer();
1309 internalDeselect();
1310
1311 while (m_undoState && m_undoState > until) {
1312 Command& cmd = m_history[--m_undoState];
1313 switch (cmd.type) {
1314 case Insert:
1315 m_text.remove(cmd.pos, 1);
1316 m_cursor = cmd.pos;
1317 break;
1318 case SetSelection:
1319 m_selstart = cmd.selStart;
1320 m_selend = cmd.selEnd;
1321 m_cursor = cmd.pos;
1322 break;
1323 case Remove:
1324 case RemoveSelection:
1325 m_text.insert(cmd.pos, cmd.uc);
1326 m_cursor = cmd.pos + 1;
1327 break;
1328 case Delete:
1329 case DeleteSelection:
1330 m_text.insert(cmd.pos, cmd.uc);
1331 m_cursor = cmd.pos;
1332 break;
1333 case Separator:
1334 continue;
1335 }
1336 if (until < 0 && m_undoState) {
1337 Command& next = m_history[m_undoState-1];
1338 if (next.type != cmd.type && next.type < RemoveSelection
1339 && (cmd.type < RemoveSelection || next.type == Separator))
1340 break;
1341 }
1342 }
1343 m_textDirty = true;
1344 emitCursorPositionChanged();
1345}
1346
1347void QWidgetLineControl::internalRedo()
1348{
1349 if (!isRedoAvailable())
1350 return;
1351 internalDeselect();
1352 while (m_undoState < (int)m_history.size()) {
1353 Command& cmd = m_history[m_undoState++];
1354 switch (cmd.type) {
1355 case Insert:
1356 m_text.insert(cmd.pos, cmd.uc);
1357 m_cursor = cmd.pos + 1;
1358 break;
1359 case SetSelection:
1360 m_selstart = cmd.selStart;
1361 m_selend = cmd.selEnd;
1362 m_cursor = cmd.pos;
1363 break;
1364 case Remove:
1365 case Delete:
1366 case RemoveSelection:
1367 case DeleteSelection:
1368 m_text.remove(cmd.pos, 1);
1369 m_selstart = cmd.selStart;
1370 m_selend = cmd.selEnd;
1371 m_cursor = cmd.pos;
1372 break;
1373 case Separator:
1374 m_selstart = cmd.selStart;
1375 m_selend = cmd.selEnd;
1376 m_cursor = cmd.pos;
1377 break;
1378 }
1379 if (m_undoState < (int)m_history.size()) {
1380 Command& next = m_history[m_undoState];
1381 if (next.type != cmd.type && cmd.type < RemoveSelection && next.type != Separator
1382 && (next.type < RemoveSelection || cmd.type == Separator))
1383 break;
1384 }
1385 }
1386 m_textDirty = true;
1387 emitCursorPositionChanged();
1388}
1389
1390/*!
1391 \internal
1392
1393 If the current cursor position differs from the last emitted cursor
1394 position, emits cursorPositionChanged().
1395*/
1396void QWidgetLineControl::emitCursorPositionChanged()
1397{
1398 if (m_cursor != m_lastCursorPos) {
1399 const int oldLast = m_lastCursorPos;
1400 m_lastCursorPos = m_cursor;
1401 emit cursorPositionChanged(oldLast, m_cursor);
1402#if QT_CONFIG(accessibility)
1403 // otherwise we send a selection update which includes the cursor
1404 if (!hasSelectedText()) {
1405 QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
1406 QAccessible::updateAccessibility(&event);
1407 }
1408#endif
1409 }
1410}
1411
1412#if QT_CONFIG(completer)
1413// iterating forward(dir=1)/backward(dir=-1) from the
1414// current row based. dir=0 indicates a new completion prefix was set.
1415bool QWidgetLineControl::advanceToEnabledItem(int dir)
1416{
1417 int start = m_completer->currentRow();
1418 if (start == -1)
1419 return false;
1420 int i = start + dir;
1421 if (dir == 0) dir = 1;
1422 do {
1423 if (!m_completer->setCurrentRow(i)) {
1424 if (!m_completer->wrapAround())
1425 break;
1426 i = i > 0 ? 0 : m_completer->completionCount() - 1;
1427 } else {
1428 QModelIndex currentIndex = m_completer->currentIndex();
1429 if (m_completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled)
1430 return true;
1431 i += dir;
1432 }
1433 } while (i != start);
1434
1435 m_completer->setCurrentRow(start); // restore
1436 return false;
1437}
1438
1439void QWidgetLineControl::complete(int key)
1440{
1441 if (!m_completer || isReadOnly() || echoMode() != QLineEdit::Normal)
1442 return;
1443
1444 QString text = this->text();
1445 if (m_completer->completionMode() == QCompleter::InlineCompletion) {
1446 if (key == Qt::Key_Backspace)
1447 return;
1448 int n = 0;
1449 if (key == Qt::Key_Up || key == Qt::Key_Down) {
1450 if (textAfterSelection().size())
1451 return;
1452 QString prefix = hasSelectedText() ? textBeforeSelection()
1453 : text;
1454 if (text.compare(m_completer->currentCompletion(), m_completer->caseSensitivity()) != 0
1455 || prefix.compare(m_completer->completionPrefix(), m_completer->caseSensitivity()) != 0) {
1456 m_completer->setCompletionPrefix(prefix);
1457 } else {
1458 n = (key == Qt::Key_Up) ? -1 : +1;
1459 }
1460 } else {
1461 m_completer->setCompletionPrefix(text);
1462 }
1463 if (!advanceToEnabledItem(n))
1464 return;
1465 } else {
1466#ifndef QT_KEYPAD_NAVIGATION
1467 if (text.isEmpty()) {
1468 if (auto *popup = QCompleterPrivate::get(m_completer)->popup)
1469 popup->hide();
1470 return;
1471 }
1472#endif
1473 m_completer->setCompletionPrefix(text);
1474 }
1475
1476 m_completer->complete();
1477}
1478#endif
1479
1480void QWidgetLineControl::setReadOnly(bool enable)
1481{
1482 if (m_readOnly == enable)
1483 return;
1484
1485 m_readOnly = enable;
1486 updateCursorBlinking();
1487}
1488
1489void QWidgetLineControl::setBlinkingCursorEnabled(bool enable)
1490{
1491 if (m_blinkEnabled == enable)
1492 return;
1493
1494 m_blinkEnabled = enable;
1495
1496 if (enable)
1497 connect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);
1498 else
1499 disconnect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);
1500
1501 updateCursorBlinking();
1502}
1503
1504void QWidgetLineControl::updateCursorBlinking()
1505{
1506 m_blinkTimer.stop();
1507
1508 if (m_blinkEnabled && !m_readOnly) {
1509 const auto flashTime = QGuiApplication::styleHints()->cursorFlashTime() * 1ms;
1510 if (flashTime >= 2ms)
1511 m_blinkTimer.start(flashTime / 2, this);
1512 }
1513
1514 m_blinkStatus = 1;
1515 emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
1516}
1517
1518// This is still used by QDeclarativeTextInput in the qtquick1 repo
1519void QWidgetLineControl::resetCursorBlinkTimer()
1520{
1521 if (!m_blinkEnabled || !m_blinkTimer.isActive())
1522 return;
1523 m_blinkTimer.stop();
1524 const auto flashTime = QGuiApplication::styleHints()->cursorFlashTime() * 1ms;
1525 if (flashTime >= 2ms)
1526 m_blinkTimer.start(flashTime / 2, this);
1527 m_blinkStatus = 1;
1528}
1529
1530void QWidgetLineControl::timerEvent(QTimerEvent *event)
1531{
1532 const auto eventId = event->id();
1533 if (eventId == m_blinkTimer.id()) {
1534 m_blinkStatus = !m_blinkStatus;
1535 emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
1536 } else if (eventId == m_deleteAllTimer.id()) {
1537 m_deleteAllTimer.stop();
1538 clear();
1539 } else if (eventId == m_tripleClickTimer.id()) {
1540 m_tripleClickTimer.stop();
1541 } else if (eventId == m_passwordEchoTimer.id()) {
1542 m_passwordEchoTimer.stop();
1543 updateDisplayText();
1544 }
1545}
1546
1547#ifndef QT_NO_SHORTCUT
1548void QWidgetLineControl::processShortcutOverrideEvent(QKeyEvent *ke)
1549{
1550 if (ke == QKeySequence::Copy
1551 || ke == QKeySequence::MoveToNextWord
1552 || ke == QKeySequence::MoveToPreviousWord
1553 || ke == QKeySequence::MoveToStartOfLine
1554 || ke == QKeySequence::MoveToEndOfLine
1555 || ke == QKeySequence::MoveToStartOfDocument
1556 || ke == QKeySequence::MoveToEndOfDocument
1557 || ke == QKeySequence::SelectNextWord
1558 || ke == QKeySequence::SelectPreviousWord
1559 || ke == QKeySequence::SelectStartOfLine
1560 || ke == QKeySequence::SelectEndOfLine
1561 || ke == QKeySequence::SelectStartOfBlock
1562 || ke == QKeySequence::SelectEndOfBlock
1563 || ke == QKeySequence::SelectStartOfDocument
1564 || ke == QKeySequence::SelectAll
1565 || ke == QKeySequence::SelectEndOfDocument) {
1566 ke->accept();
1567 } else if (ke == QKeySequence::Paste
1568 || ke == QKeySequence::Cut
1569 || ke == QKeySequence::Redo
1570 || ke == QKeySequence::Undo
1571 || ke == QKeySequence::DeleteCompleteLine) {
1572 if (!isReadOnly())
1573 ke->accept();
1574 } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
1575 || ke->modifiers() == Qt::KeypadModifier) {
1576 if (ke->key() < Qt::Key_Escape) {
1577 if (!isReadOnly())
1578 ke->accept();
1579 } else {
1580 switch (ke->key()) {
1581 case Qt::Key_Delete:
1582 case Qt::Key_Backspace:
1583 if (!isReadOnly())
1584 ke->accept();
1585 break;
1586
1587 case Qt::Key_Home:
1588 case Qt::Key_End:
1589 case Qt::Key_Left:
1590 case Qt::Key_Right:
1591 ke->accept();
1592 break;
1593
1594 default:
1595 break;
1596 }
1597 }
1598 }
1599}
1600#endif
1601
1602void QWidgetLineControl::processKeyEvent(QKeyEvent* event)
1603{
1604 bool inlineCompletionAccepted = false;
1605
1606#if QT_CONFIG(completer)
1607 if (m_completer) {
1608 QCompleter::CompletionMode completionMode = m_completer->completionMode();
1609 auto *popup = QCompleterPrivate::get(m_completer)->popup;
1610 if ((completionMode == QCompleter::PopupCompletion
1611 || completionMode == QCompleter::UnfilteredPopupCompletion)
1612 && popup && popup->isVisible()) {
1613 // The following keys are forwarded by the completer to the widget
1614 // Ignoring the events lets the completer provide suitable default behavior
1615 switch (event->key()) {
1616 case Qt::Key_Escape:
1617 event->ignore();
1618 return;
1619 default:
1620 break; // normal key processing
1621 }
1622 } else if (completionMode == QCompleter::InlineCompletion) {
1623 switch (event->key()) {
1624 case Qt::Key_Enter:
1625 case Qt::Key_Return:
1626 case Qt::Key_F4:
1627#ifdef QT_KEYPAD_NAVIGATION
1628 case Qt::Key_Select:
1629 if (!QApplicationPrivate::keypadNavigationEnabled())
1630 break;
1631#endif
1632 if (!m_completer->currentCompletion().isEmpty() && hasSelectedText()
1633 && !m_completer->completionPrefix().isEmpty()
1634 && textAfterSelection().isEmpty()) {
1635 setText(m_completer->currentCompletion());
1636 inlineCompletionAccepted = true;
1637 }
1638 break;
1639 default:
1640 break; // normal key processing
1641 }
1642 }
1643 }
1644#endif // QT_CONFIG(completer)
1645
1646 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
1647 if (hasAcceptableInput() || fixup()) {
1648
1649 QInputMethod *inputMethod = QGuiApplication::inputMethod();
1650 inputMethod->commit();
1651 QWidget *lineEdit = qobject_cast<QWidget *>(parent());
1652 if (!(lineEdit && lineEdit->inputMethodHints() & Qt::ImhMultiLine))
1653 inputMethod->hide();
1654
1655 emit accepted();
1656 emit editingFinished();
1657 }
1658 if (inlineCompletionAccepted)
1659 event->accept();
1660 else
1661 event->ignore();
1662 return;
1663 }
1664
1665 if (echoMode() == QLineEdit::PasswordEchoOnEdit
1666 && !passwordEchoEditing()
1667 && !isReadOnly()
1668 && !event->text().isEmpty()
1669#ifdef QT_KEYPAD_NAVIGATION
1670 && event->key() != Qt::Key_Select
1671 && event->key() != Qt::Key_Up
1672 && event->key() != Qt::Key_Down
1673 && event->key() != Qt::Key_Back
1674#endif
1675 && !(event->modifiers() & Qt::ControlModifier)) {
1676 // Clear the edit and reset to normal echo mode while editing; the
1677 // echo mode switches back when the edit loses focus
1678 // ### resets current content. dubious code; you can
1679 // navigate with keys up, down, back, and select(?), but if you press
1680 // "left" or "right" it clears?
1681 updatePasswordEchoEditing(true);
1682 clear();
1683 }
1684
1685 bool unknown = false;
1686#if QT_CONFIG(shortcut)
1687 bool visual = cursorMoveStyle() == Qt::VisualMoveStyle;
1688#endif
1689
1690 if (false) {
1691 }
1692#ifndef QT_NO_SHORTCUT
1693 else if (event == QKeySequence::Undo) {
1694 if (!isReadOnly())
1695 undo();
1696 }
1697 else if (event == QKeySequence::Redo) {
1698 if (!isReadOnly())
1699 redo();
1700 }
1701 else if (event == QKeySequence::SelectAll) {
1702 selectAll();
1703 }
1704#ifndef QT_NO_CLIPBOARD
1705 else if (event == QKeySequence::Copy) {
1706 copy();
1707 }
1708 else if (event == QKeySequence::Paste) {
1709 if (!isReadOnly()) {
1710 QClipboard::Mode mode = QClipboard::Clipboard;
1711 if (m_keyboardScheme == QPlatformTheme::X11KeyboardScheme
1712 && event->modifiers() == (Qt::CTRL | Qt::SHIFT)
1713 && event->key() == Qt::Key_Insert) {
1714 mode = QClipboard::Selection;
1715 }
1716 paste(mode);
1717 }
1718 }
1719 else if (event == QKeySequence::Cut) {
1720 if (!isReadOnly() && hasSelectedText()) {
1721 copy();
1722 del();
1723 }
1724 }
1725 else if (event == QKeySequence::DeleteEndOfLine) {
1726 if (!isReadOnly()) {
1727 setSelection(cursor(), end());
1728 copy();
1729 del();
1730 }
1731 }
1732#endif //QT_NO_CLIPBOARD
1733 else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
1734 home(0);
1735 }
1736 else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) {
1737 end(0);
1738 }
1739 else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) {
1740 home(1);
1741 }
1742 else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) {
1743 end(1);
1744 }
1745 else if (event == QKeySequence::MoveToNextChar) {
1746#if !QT_CONFIG(completer)
1747 const bool inlineCompletion = false;
1748#else
1749 const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
1750#endif
1751 if (hasSelectedText()
1752 && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
1753 || inlineCompletion)) {
1754 moveCursor(selectionEnd(), false);
1755 } else {
1756 cursorForward(0, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
1757 }
1758 }
1759 else if (event == QKeySequence::SelectNextChar) {
1760 cursorForward(1, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
1761 }
1762 else if (event == QKeySequence::MoveToPreviousChar) {
1763#if !QT_CONFIG(completer)
1764 const bool inlineCompletion = false;
1765#else
1766 const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
1767#endif
1768 if (hasSelectedText()
1769 && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
1770 || inlineCompletion)) {
1771 moveCursor(selectionStart(), false);
1772 } else {
1773 cursorForward(0, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
1774 }
1775 }
1776 else if (event == QKeySequence::SelectPreviousChar) {
1777 cursorForward(1, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
1778 }
1779 else if (event == QKeySequence::MoveToNextWord) {
1780 if (echoMode() == QLineEdit::Normal)
1781 layoutDirection() == Qt::LeftToRight ? cursorWordForward(0) : cursorWordBackward(0);
1782 else
1783 layoutDirection() == Qt::LeftToRight ? end(0) : home(0);
1784 }
1785 else if (event == QKeySequence::MoveToPreviousWord) {
1786 if (echoMode() == QLineEdit::Normal)
1787 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(0) : cursorWordForward(0);
1788 else if (!isReadOnly()) {
1789 layoutDirection() == Qt::LeftToRight ? home(0) : end(0);
1790 }
1791 }
1792 else if (event == QKeySequence::SelectNextWord) {
1793 if (echoMode() == QLineEdit::Normal)
1794 layoutDirection() == Qt::LeftToRight ? cursorWordForward(1) : cursorWordBackward(1);
1795 else
1796 layoutDirection() == Qt::LeftToRight ? end(1) : home(1);
1797 }
1798 else if (event == QKeySequence::SelectPreviousWord) {
1799 if (echoMode() == QLineEdit::Normal)
1800 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(1) : cursorWordForward(1);
1801 else
1802 layoutDirection() == Qt::LeftToRight ? home(1) : end(1);
1803 }
1804 else if (event == QKeySequence::Delete) {
1805 if (!isReadOnly())
1806 del();
1807 }
1808 else if (event == QKeySequence::DeleteEndOfWord) {
1809 if (!isReadOnly()) {
1810 if (!hasSelectedText())
1811 cursorWordForward(true);
1812
1813 if (hasSelectedText())
1814 del();
1815 }
1816 }
1817 else if (event == QKeySequence::DeleteStartOfWord) {
1818 if (!isReadOnly()) {
1819 if (!hasSelectedText())
1820 cursorWordBackward(true);
1821
1822 if (hasSelectedText())
1823 del();
1824 }
1825 } else if (event == QKeySequence::DeleteCompleteLine) {
1826 if (!isReadOnly()) {
1827 setSelection(0, text().size());
1828#ifndef QT_NO_CLIPBOARD
1829 copy();
1830#endif
1831 del();
1832 }
1833 }
1834#endif // QT_NO_SHORTCUT
1835 else {
1836 bool handled = false;
1837 if (m_keyboardScheme == QPlatformTheme::MacKeyboardScheme
1838 && (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)) {
1839 Qt::KeyboardModifiers myModifiers = (event->modifiers() & ~Qt::KeypadModifier);
1840 if (myModifiers & Qt::ShiftModifier) {
1841 if (myModifiers == (Qt::ControlModifier|Qt::ShiftModifier)
1842 || myModifiers == (Qt::AltModifier|Qt::ShiftModifier)
1843 || myModifiers == Qt::ShiftModifier) {
1844
1845 event->key() == Qt::Key_Up ? home(1) : end(1);
1846 }
1847 } else {
1848 if ((myModifiers == Qt::ControlModifier
1849 || myModifiers == Qt::AltModifier
1850 || myModifiers == Qt::NoModifier)) {
1851 event->key() == Qt::Key_Up ? home(0) : end(0);
1852 }
1853 }
1854 handled = true;
1855 }
1856 if (event->modifiers() & Qt::ControlModifier) {
1857 switch (event->key()) {
1858 case Qt::Key_Backspace:
1859 if (!isReadOnly()) {
1860 cursorWordBackward(true);
1861 del();
1862 }
1863 break;
1864#if QT_CONFIG(completer)
1865 case Qt::Key_Up:
1866 case Qt::Key_Down:
1867 complete(event->key());
1868 break;
1869#endif
1870 default:
1871 if (!handled)
1872 unknown = true;
1873 }
1874 } else { // ### check for *no* modifier
1875 switch (event->key()) {
1876 case Qt::Key_Backspace:
1877 if (!isReadOnly()) {
1878 backspace();
1879#if QT_CONFIG(completer)
1880 complete(Qt::Key_Backspace);
1881#endif
1882 }
1883 break;
1884#ifdef QT_KEYPAD_NAVIGATION
1885 case Qt::Key_Back:
1886 if (QApplicationPrivate::keypadNavigationEnabled() && !event->isAutoRepeat()
1887 && !isReadOnly()) {
1888 if (text().length() == 0) {
1889 setText(m_cancelText);
1890
1891 if (passwordEchoEditing())
1892 updatePasswordEchoEditing(false);
1893
1894 emit editFocusChange(false);
1895 } else if (!m_deleteAllTimer.isActive()) {
1896 m_deleteAllTimer.start(750ms, this);
1897 }
1898 } else {
1899 unknown = true;
1900 }
1901 break;
1902#endif
1903 default:
1904 if (!handled)
1905 unknown = true;
1906 }
1907 }
1908 }
1909
1910 if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) {
1911 setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
1912 unknown = false;
1913 }
1914
1915 if (unknown
1916 && !isReadOnly()
1917 && isAcceptableInput(event)) {
1918 insert(event->text());
1919#if QT_CONFIG(completer)
1920 complete(event->key());
1921#endif
1922 event->accept();
1923 return;
1924 }
1925
1926 if (unknown) {
1927 event->ignore();
1928 } else {
1929#ifndef QT_NO_CLIPBOARD
1930 if (QApplication::clipboard()->supportsSelection())
1931 copy(QClipboard::Selection);
1932#endif
1933 event->accept();
1934 }
1935}
1936
1937bool QWidgetLineControl::isUndoAvailable() const
1938{
1939 // For security reasons undo is not available in any password mode (NoEcho included)
1940 // with the exception that the user can clear the password with undo.
1941 return !m_readOnly && m_undoState
1942 && (m_echoMode == QLineEdit::Normal || m_history[m_undoState - 1].type == QWidgetLineControl::Insert);
1943}
1944
1945bool QWidgetLineControl::isRedoAvailable() const
1946{
1947 // Same as with undo. Disabled for password modes.
1948 return !m_readOnly
1949 && m_echoMode == QLineEdit::Normal
1950 && m_undoState < int(m_history.size());
1951}
1952
1953QT_END_NAMESPACE
1954
1955#include "moc_qwidgetlinecontrol_p.cpp"