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