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 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | |
31 | /*! |
32 | \internal |
33 | |
34 | Updates the internal text layout. Returns the ascent of the |
35 | created QTextLine. |
36 | */ |
37 | int 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 | */ |
54 | void 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 | */ |
116 | void 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 | */ |
132 | void 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 | */ |
147 | void 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 | */ |
174 | void 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 | */ |
207 | void 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 | */ |
227 | void 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 | */ |
240 | void 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 | |
255 | void 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 | */ |
275 | void 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 | |
309 | void 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 | */ |
326 | void 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 | */ |
352 | void 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 | */ |
366 | int 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 | */ |
376 | QRect 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 | */ |
394 | QRect 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 | */ |
404 | QRect 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 | */ |
418 | bool 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 | */ |
441 | void 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 | */ |
478 | void 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 | */ |
605 | void 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 | */ |
646 | void 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 | */ |
672 | bool 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 | */ |
731 | void 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 | */ |
779 | void 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 | */ |
801 | void 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 | */ |
865 | void 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 | */ |
896 | void 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 | */ |
938 | void 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 | */ |
1044 | bool 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 | */ |
1121 | bool 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 | */ |
1157 | QString 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 | */ |
1232 | QString 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 | */ |
1254 | QString 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 | */ |
1275 | int 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 | |
1301 | void 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 | |
1344 | void 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 | */ |
1393 | void 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. |
1412 | bool 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 | |
1436 | void 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 * = 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 | |
1477 | void QWidgetLineControl::setReadOnly(bool enable) |
1478 | { |
1479 | if (m_readOnly == enable) |
1480 | return; |
1481 | |
1482 | m_readOnly = enable; |
1483 | updateCursorBlinking(); |
1484 | } |
1485 | |
1486 | void 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 | |
1501 | void 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 |
1519 | void 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 | |
1531 | void 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 |
1551 | void 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 | |
1605 | void 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 * = 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 | |
1934 | bool 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 | |
1942 | bool 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 | |
1950 | QT_END_NAMESPACE |
1951 | |
1952 | #include "moc_qwidgetlinecontrol_p.cpp" |
1953 | |