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

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