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 m_completer->setCompletionPrefix(text);
1467 }
1468
1469 m_completer->complete();
1470}
1471#endif
1472
1473void QWidgetLineControl::setReadOnly(bool enable)
1474{
1475 if (m_readOnly == enable)
1476 return;
1477
1478 m_readOnly = enable;
1479 updateCursorBlinking();
1480}
1481
1482void QWidgetLineControl::setBlinkingCursorEnabled(bool enable)
1483{
1484 if (m_blinkEnabled == enable)
1485 return;
1486
1487 m_blinkEnabled = enable;
1488
1489 if (enable)
1490 connect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);
1491 else
1492 disconnect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);
1493
1494 updateCursorBlinking();
1495}
1496
1497void QWidgetLineControl::updateCursorBlinking()
1498{
1499 m_blinkTimer.stop();
1500
1501 if (m_blinkEnabled && !m_readOnly) {
1502 const auto flashTime = QGuiApplication::styleHints()->cursorFlashTime() * 1ms;
1503 if (flashTime >= 2ms)
1504 m_blinkTimer.start(flashTime / 2, this);
1505 }
1506
1507 m_blinkStatus = 1;
1508 emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
1509}
1510
1511// This is still used by QDeclarativeTextInput in the qtquick1 repo
1512void QWidgetLineControl::resetCursorBlinkTimer()
1513{
1514 if (!m_blinkEnabled || !m_blinkTimer.isActive())
1515 return;
1516 m_blinkTimer.stop();
1517 const auto flashTime = QGuiApplication::styleHints()->cursorFlashTime() * 1ms;
1518 if (flashTime >= 2ms)
1519 m_blinkTimer.start(flashTime / 2, this);
1520 m_blinkStatus = 1;
1521}
1522
1523void QWidgetLineControl::timerEvent(QTimerEvent *event)
1524{
1525 const auto eventId = event->id();
1526 if (eventId == m_blinkTimer.id()) {
1527 m_blinkStatus = !m_blinkStatus;
1528 emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
1529 } else if (eventId == m_deleteAllTimer.id()) {
1530 m_deleteAllTimer.stop();
1531 clear();
1532 } else if (eventId == m_tripleClickTimer.id()) {
1533 m_tripleClickTimer.stop();
1534 } else if (eventId == m_passwordEchoTimer.id()) {
1535 m_passwordEchoTimer.stop();
1536 updateDisplayText();
1537 }
1538}
1539
1540#ifndef QT_NO_SHORTCUT
1541void QWidgetLineControl::processShortcutOverrideEvent(QKeyEvent *ke)
1542{
1543 if (ke == QKeySequence::Copy
1544 || ke == QKeySequence::MoveToNextWord
1545 || ke == QKeySequence::MoveToPreviousWord
1546 || ke == QKeySequence::MoveToStartOfLine
1547 || ke == QKeySequence::MoveToEndOfLine
1548 || ke == QKeySequence::MoveToStartOfDocument
1549 || ke == QKeySequence::MoveToEndOfDocument
1550 || ke == QKeySequence::SelectNextWord
1551 || ke == QKeySequence::SelectPreviousWord
1552 || ke == QKeySequence::SelectStartOfLine
1553 || ke == QKeySequence::SelectEndOfLine
1554 || ke == QKeySequence::SelectStartOfBlock
1555 || ke == QKeySequence::SelectEndOfBlock
1556 || ke == QKeySequence::SelectStartOfDocument
1557 || ke == QKeySequence::SelectAll
1558 || ke == QKeySequence::SelectEndOfDocument) {
1559 ke->accept();
1560 } else if (ke == QKeySequence::Paste
1561 || ke == QKeySequence::Cut
1562 || ke == QKeySequence::Redo
1563 || ke == QKeySequence::Undo
1564 || ke == QKeySequence::DeleteCompleteLine) {
1565 if (!isReadOnly())
1566 ke->accept();
1567 } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
1568 || ke->modifiers() == Qt::KeypadModifier) {
1569 if (ke->key() < Qt::Key_Escape) {
1570 if (!isReadOnly())
1571 ke->accept();
1572 } else {
1573 switch (ke->key()) {
1574 case Qt::Key_Delete:
1575 case Qt::Key_Backspace:
1576 if (!isReadOnly())
1577 ke->accept();
1578 break;
1579
1580 case Qt::Key_Home:
1581 case Qt::Key_End:
1582 case Qt::Key_Left:
1583 case Qt::Key_Right:
1584 ke->accept();
1585 break;
1586
1587 default:
1588 break;
1589 }
1590 }
1591 }
1592}
1593#endif
1594
1595void QWidgetLineControl::processKeyEvent(QKeyEvent* event)
1596{
1597 bool inlineCompletionAccepted = false;
1598
1599#if QT_CONFIG(completer)
1600 if (m_completer) {
1601 QCompleter::CompletionMode completionMode = m_completer->completionMode();
1602 auto *popup = QCompleterPrivate::get(m_completer)->popup;
1603 if ((completionMode == QCompleter::PopupCompletion
1604 || completionMode == QCompleter::UnfilteredPopupCompletion)
1605 && popup && popup->isVisible()) {
1606 // The following keys are forwarded by the completer to the widget
1607 // Ignoring the events lets the completer provide suitable default behavior
1608 switch (event->key()) {
1609 case Qt::Key_Escape:
1610 event->ignore();
1611 return;
1612 default:
1613 break; // normal key processing
1614 }
1615 } else if (completionMode == QCompleter::InlineCompletion) {
1616 switch (event->key()) {
1617 case Qt::Key_Enter:
1618 case Qt::Key_Return:
1619 case Qt::Key_F4:
1620 if (!m_completer->currentCompletion().isEmpty() && hasSelectedText()
1621 && !m_completer->completionPrefix().isEmpty()
1622 && textAfterSelection().isEmpty()) {
1623 setText(m_completer->currentCompletion());
1624 inlineCompletionAccepted = true;
1625 }
1626 break;
1627 default:
1628 break; // normal key processing
1629 }
1630 }
1631 }
1632#endif // QT_CONFIG(completer)
1633
1634 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
1635 if (hasAcceptableInput() || fixup()) {
1636
1637 QInputMethod *inputMethod = QGuiApplication::inputMethod();
1638 inputMethod->commit();
1639 QWidget *lineEdit = qobject_cast<QWidget *>(parent());
1640 if (!(lineEdit && lineEdit->inputMethodHints() & Qt::ImhMultiLine))
1641 inputMethod->hide();
1642
1643 emit accepted();
1644 emit editingFinished();
1645 }
1646 if (inlineCompletionAccepted)
1647 event->accept();
1648 else
1649 event->ignore();
1650 return;
1651 }
1652
1653 if (echoMode() == QLineEdit::PasswordEchoOnEdit
1654 && !passwordEchoEditing()
1655 && !isReadOnly()
1656 && !event->text().isEmpty()
1657 && !(event->modifiers() & Qt::ControlModifier)) {
1658 // Clear the edit and reset to normal echo mode while editing; the
1659 // echo mode switches back when the edit loses focus
1660 // ### resets current content. dubious code; you can
1661 // navigate with keys up, down, back, and select(?), but if you press
1662 // "left" or "right" it clears?
1663 updatePasswordEchoEditing(true);
1664 clear();
1665 }
1666
1667 bool unknown = false;
1668#if QT_CONFIG(shortcut)
1669 bool visual = cursorMoveStyle() == Qt::VisualMoveStyle;
1670#endif
1671
1672 if (false) {
1673 }
1674#ifndef QT_NO_SHORTCUT
1675 else if (event == QKeySequence::Undo) {
1676 if (!isReadOnly())
1677 undo();
1678 }
1679 else if (event == QKeySequence::Redo) {
1680 if (!isReadOnly())
1681 redo();
1682 }
1683 else if (event == QKeySequence::SelectAll) {
1684 selectAll();
1685 }
1686#ifndef QT_NO_CLIPBOARD
1687 else if (event == QKeySequence::Copy) {
1688 copy();
1689 }
1690 else if (event == QKeySequence::Paste) {
1691 if (!isReadOnly()) {
1692 QClipboard::Mode mode = QClipboard::Clipboard;
1693 if (m_keyboardScheme == QPlatformTheme::X11KeyboardScheme
1694 && event->modifiers() == (Qt::CTRL | Qt::SHIFT)
1695 && event->key() == Qt::Key_Insert) {
1696 mode = QClipboard::Selection;
1697 }
1698 paste(mode);
1699 }
1700 }
1701 else if (event == QKeySequence::Cut) {
1702 if (!isReadOnly() && hasSelectedText()) {
1703 copy();
1704 del();
1705 }
1706 }
1707 else if (event == QKeySequence::DeleteEndOfLine) {
1708 if (!isReadOnly()) {
1709 setSelection(cursor(), end());
1710 copy();
1711 del();
1712 }
1713 }
1714#endif //QT_NO_CLIPBOARD
1715 else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
1716 home(0);
1717 }
1718 else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) {
1719 end(0);
1720 }
1721 else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) {
1722 home(1);
1723 }
1724 else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) {
1725 end(1);
1726 }
1727 else if (event == QKeySequence::MoveToNextChar) {
1728#if !QT_CONFIG(completer)
1729 const bool inlineCompletion = false;
1730#else
1731 const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
1732#endif
1733 if (hasSelectedText()
1734 && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
1735 || inlineCompletion)) {
1736 moveCursor(selectionEnd(), false);
1737 } else {
1738 cursorForward(0, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
1739 }
1740 }
1741 else if (event == QKeySequence::SelectNextChar) {
1742 cursorForward(1, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
1743 }
1744 else if (event == QKeySequence::MoveToPreviousChar) {
1745#if !QT_CONFIG(completer)
1746 const bool inlineCompletion = false;
1747#else
1748 const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
1749#endif
1750 if (hasSelectedText()
1751 && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
1752 || inlineCompletion)) {
1753 moveCursor(selectionStart(), false);
1754 } else {
1755 cursorForward(0, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
1756 }
1757 }
1758 else if (event == QKeySequence::SelectPreviousChar) {
1759 cursorForward(1, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
1760 }
1761 else if (event == QKeySequence::MoveToNextWord) {
1762 if (echoMode() == QLineEdit::Normal)
1763 layoutDirection() == Qt::LeftToRight ? cursorWordForward(0) : cursorWordBackward(0);
1764 else
1765 layoutDirection() == Qt::LeftToRight ? end(0) : home(0);
1766 }
1767 else if (event == QKeySequence::MoveToPreviousWord) {
1768 if (echoMode() == QLineEdit::Normal)
1769 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(0) : cursorWordForward(0);
1770 else if (!isReadOnly()) {
1771 layoutDirection() == Qt::LeftToRight ? home(0) : end(0);
1772 }
1773 }
1774 else if (event == QKeySequence::SelectNextWord) {
1775 if (echoMode() == QLineEdit::Normal)
1776 layoutDirection() == Qt::LeftToRight ? cursorWordForward(1) : cursorWordBackward(1);
1777 else
1778 layoutDirection() == Qt::LeftToRight ? end(1) : home(1);
1779 }
1780 else if (event == QKeySequence::SelectPreviousWord) {
1781 if (echoMode() == QLineEdit::Normal)
1782 layoutDirection() == Qt::LeftToRight ? cursorWordBackward(1) : cursorWordForward(1);
1783 else
1784 layoutDirection() == Qt::LeftToRight ? home(1) : end(1);
1785 }
1786 else if (event == QKeySequence::Delete) {
1787 if (!isReadOnly())
1788 del();
1789 }
1790 else if (event == QKeySequence::DeleteEndOfWord) {
1791 if (!isReadOnly()) {
1792 if (!hasSelectedText())
1793 cursorWordForward(true);
1794
1795 if (hasSelectedText())
1796 del();
1797 }
1798 }
1799 else if (event == QKeySequence::DeleteStartOfWord) {
1800 if (!isReadOnly()) {
1801 if (!hasSelectedText())
1802 cursorWordBackward(true);
1803
1804 if (hasSelectedText())
1805 del();
1806 }
1807 } else if (event == QKeySequence::DeleteCompleteLine) {
1808 if (!isReadOnly()) {
1809 setSelection(0, text().size());
1810#ifndef QT_NO_CLIPBOARD
1811 copy();
1812#endif
1813 del();
1814 }
1815 }
1816#endif // QT_NO_SHORTCUT
1817 else {
1818 bool handled = false;
1819 if (m_keyboardScheme == QPlatformTheme::MacKeyboardScheme
1820 && (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)) {
1821 Qt::KeyboardModifiers myModifiers = (event->modifiers() & ~Qt::KeypadModifier);
1822 if (myModifiers & Qt::ShiftModifier) {
1823 if (myModifiers == (Qt::ControlModifier|Qt::ShiftModifier)
1824 || myModifiers == (Qt::AltModifier|Qt::ShiftModifier)
1825 || myModifiers == Qt::ShiftModifier) {
1826
1827 event->key() == Qt::Key_Up ? home(1) : end(1);
1828 }
1829 } else {
1830 if ((myModifiers == Qt::ControlModifier
1831 || myModifiers == Qt::AltModifier
1832 || myModifiers == Qt::NoModifier)) {
1833 event->key() == Qt::Key_Up ? home(0) : end(0);
1834 }
1835 }
1836 handled = true;
1837 }
1838 if (event->modifiers() & Qt::ControlModifier) {
1839 switch (event->key()) {
1840 case Qt::Key_Backspace:
1841 if (!isReadOnly()) {
1842 cursorWordBackward(true);
1843 del();
1844 }
1845 break;
1846#if QT_CONFIG(completer)
1847 case Qt::Key_Up:
1848 case Qt::Key_Down:
1849 complete(event->key());
1850 break;
1851#endif
1852 default:
1853 if (!handled)
1854 unknown = true;
1855 }
1856 } else { // ### check for *no* modifier
1857 switch (event->key()) {
1858 case Qt::Key_Backspace:
1859 if (!isReadOnly()) {
1860 backspace();
1861#if QT_CONFIG(completer)
1862 complete(Qt::Key_Backspace);
1863#endif
1864 }
1865 break;
1866 default:
1867 if (!handled)
1868 unknown = true;
1869 }
1870 }
1871 }
1872
1873 if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) {
1874 setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
1875 unknown = false;
1876 }
1877
1878 if (unknown
1879 && !isReadOnly()
1880 && isAcceptableInput(event)) {
1881 insert(event->text());
1882#if QT_CONFIG(completer)
1883 complete(event->key());
1884#endif
1885 event->accept();
1886 return;
1887 }
1888
1889 if (unknown) {
1890 event->ignore();
1891 } else {
1892#ifndef QT_NO_CLIPBOARD
1893 if (QApplication::clipboard()->supportsSelection())
1894 copy(QClipboard::Selection);
1895#endif
1896 event->accept();
1897 }
1898}
1899
1900bool QWidgetLineControl::isUndoAvailable() const
1901{
1902 // For security reasons undo is not available in any password mode (NoEcho included)
1903 // with the exception that the user can clear the password with undo.
1904 return !m_readOnly && m_undoState
1905 && (m_echoMode == QLineEdit::Normal || m_history[m_undoState - 1].type == QWidgetLineControl::Insert);
1906}
1907
1908bool QWidgetLineControl::isRedoAvailable() const
1909{
1910 // Same as with undo. Disabled for password modes.
1911 return !m_readOnly
1912 && m_echoMode == QLineEdit::Normal
1913 && m_undoState < int(m_history.size());
1914}
1915
1916QT_END_NAMESPACE
1917
1918#include "moc_qwidgetlinecontrol_p.cpp"