// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qwidgetlinecontrol_p.h"

#if QT_CONFIG(itemviews)
#include "qabstractitemview.h"
#endif
#include "qclipboard.h"
#include <private/qguiapplication_p.h>
#if QT_CONFIG(completer)
#include <private/qcompleter_p.h>
#endif
#include <qpa/qplatformtheme.h>
#include <qstylehints.h>
#if QT_CONFIG(accessibility)
#include "qaccessible.h"
#endif

#include "qapplication.h"
#include "private/qapplication_p.h"
#if QT_CONFIG(graphicsview)
#include "qgraphicssceneevent.h"
#endif

#include "qvalidator.h"

QT_BEGIN_NAMESPACE


/*!
   \internal

   Updates the internal text layout. Returns the ascent of the
   created QTextLine.
*/
int QWidgetLineControl::redoTextLayout() const
{
    m_textLayout.clearLayout();

    m_textLayout.beginLayout();
    QTextLine l = m_textLayout.createLine();
    m_textLayout.endLayout();

    return qRound(l.ascent());
}

/*!
    \internal

    Updates the display text based of the current edit text
    If the text has changed will emit displayTextChanged()
*/
void QWidgetLineControl::updateDisplayText(bool forceUpdate)
{
    QString orig = m_textLayout.text();
    QString str;
    if (m_echoMode == QLineEdit::NoEcho)
        str = QString::fromLatin1("");
    else
        str = m_text;

    if (m_echoMode == QLineEdit::Password) {
        str.fill(m_passwordCharacter);
        if (m_passwordEchoTimer != 0 && m_cursor > 0 && m_cursor <= m_text.size()) {
            int cursor = m_cursor - 1;
            QChar uc = m_text.at(cursor);
            str[cursor] = uc;
            if (cursor > 0 && uc.isLowSurrogate()) {
                // second half of a surrogate, check if we have the first half as well,
                // if yes restore both at once
                uc = m_text.at(cursor - 1);
                if (uc.isHighSurrogate())
                    str[cursor - 1] = uc;
            }
        }
    } else if (m_echoMode == QLineEdit::PasswordEchoOnEdit && !m_passwordEchoEditing) {
        str.fill(m_passwordCharacter);
    }

    // replace certain non-printable characters with spaces (to avoid
    // drawing boxes when using fonts that don't have glyphs for such
    // characters)
    QChar* uc = str.data();
    for (int i = 0; i < (int)str.size(); ++i) {
        if ((uc[i].unicode() < 0x20 && uc[i].unicode() != 0x09)
            || uc[i] == QChar::LineSeparator
            || uc[i] == QChar::ParagraphSeparator)
            uc[i] = QChar(0x0020);
    }

    m_textLayout.setText(str);

    QTextOption option = m_textLayout.textOption();
    option.setTextDirection(m_layoutDirection);
    option.setFlags(QTextOption::IncludeTrailingSpaces);
    m_textLayout.setTextOption(option);

    m_ascent = redoTextLayout();

    if (str != orig || forceUpdate)
        emit displayTextChanged(str);
}

#ifndef QT_NO_CLIPBOARD
/*!
    \internal

    Copies the currently selected text into the clipboard using the given
    \a mode.

    \note If the echo mode is set to a mode other than Normal then copy
    will not work.  This is to prevent using copy as a method of bypassing
    password features of the line control.
*/
void QWidgetLineControl::copy(QClipboard::Mode mode) const
{
    QString t = selectedText();
    if (!t.isEmpty() && m_echoMode == QLineEdit::Normal) {
        QGuiApplication::clipboard()->setText(t, mode);
    }
}

/*!
    \internal

    Inserts the text stored in the application clipboard into the line
    control.

    \sa insert()
*/
void QWidgetLineControl::paste(QClipboard::Mode clipboardMode)
{
    QString clip = QGuiApplication::clipboard()->text(clipboardMode);
    if (!clip.isEmpty() || hasSelectedText()) {
        separate(); //make it a separate undo/redo command
        insert(clip);
        separate();
    }
}

#endif // !QT_NO_CLIPBOARD

/*!
    \internal
*/
void QWidgetLineControl::commitPreedit()
{
#ifndef QT_NO_IM
    if (!composeMode())
        return;

    QGuiApplication::inputMethod()->commit();
    if (!composeMode())
        return;

    m_preeditCursor = 0;
    setPreeditArea(-1, QString());
    m_textLayout.clearFormats();
    updateDisplayText(/*force*/ true);
#endif
}


/*!
    \internal

    Handles the behavior for the backspace key or function.
    Removes the current selection if there is a selection, otherwise
    removes the character prior to the cursor position.

    \sa del()
*/
void QWidgetLineControl::backspace()
{
    int priorState = m_undoState;
    if (hasSelectedText()) {
        removeSelectedText();
    } else if (m_cursor) {
            --m_cursor;
            if (m_maskData)
                m_cursor = prevMaskBlank(m_cursor);
            QChar uc = m_text.at(m_cursor);
            if (m_cursor > 0 && uc.isLowSurrogate()) {
                // second half of a surrogate, check if we have the first half as well,
                // if yes delete both at once
                uc = m_text.at(m_cursor - 1);
                if (uc.isHighSurrogate()) {
                    internalDelete(true);
                    --m_cursor;
                }
            }
            internalDelete(true);
    }
    finishChange(priorState);
}

/*!
    \internal

    Handles the behavior for the delete key or function.
    Removes the current selection if there is a selection, otherwise
    removes the character after the cursor position.

    \sa del()
*/
void QWidgetLineControl::del()
{
    int priorState = m_undoState;
    if (hasSelectedText()) {
        removeSelectedText();
    } else {
        int n = textLayout()->nextCursorPosition(m_cursor) - m_cursor;
        while (n--)
            internalDelete();
    }
    finishChange(priorState);
}

/*!
    \internal

    Inserts the given \a newText at the current cursor position.
    If there is any selected text it is removed prior to insertion of
    the new text.
*/
void QWidgetLineControl::insert(const QString &newText)
{
    int priorState = m_undoState;
    removeSelectedText();
    internalInsert(newText);
    finishChange(priorState);
}

/*!
    \internal

    Clears the line control text.
*/
void QWidgetLineControl::clear()
{
    int priorState = m_undoState;
    m_selstart = 0;
    m_selend = m_text.size();
    removeSelectedText();
    separate();
    finishChange(priorState, /*update*/false, /*edited*/false);
}
/*!
    \internal

    Undoes the previous operation.
*/

void QWidgetLineControl::undo()
{
    // Undo works only for clearing the line when in any of password the modes
    if (m_echoMode == QLineEdit::Normal) {
        internalUndo();
        finishChange(-1, true);
    } else {
        cancelPasswordEchoTimer();
        clear();
    }
}

/*!
    \internal

    Sets \a length characters from the given \a start position as selected.
    The given \a start position must be within the current text for
    the line control.  If \a length characters cannot be selected, then
    the selection will extend to the end of the current text.
*/
void QWidgetLineControl::setSelection(int start, int length)
{
    commitPreedit();

    if (Q_UNLIKELY(start < 0 || start > m_text.size())) {
        qWarning("QWidgetLineControl::setSelection: Invalid start position");
        return;
    }

    if (length > 0) {
        if (start == m_selstart && start + length == m_selend && m_cursor == m_selend)
            return;
        m_selstart = start;
        m_selend = qMin(start + length, (int)m_text.size());
        m_cursor = m_selend;
    } else if (length < 0){
        if (start == m_selend && start + length == m_selstart && m_cursor == m_selstart)
            return;
        m_selstart = qMax(start + length, 0);
        m_selend = start;
        m_cursor = m_selstart;
    } else if (m_selstart != m_selend) {
        m_selstart = 0;
        m_selend = 0;
        m_cursor = start;
    } else {
        m_cursor = start;
        emitCursorPositionChanged();
        return;
    }
    emit selectionChanged();
    emitCursorPositionChanged();
}

void QWidgetLineControl::_q_deleteSelected()
{
    if (!hasSelectedText())
        return;

    int priorState = m_undoState;
    emit resetInputContext();
    removeSelectedText();
    separate();
    finishChange(priorState);
}

/*!
    \internal

    Initializes the line control with a starting text value of \a txt.
*/
void QWidgetLineControl::init(const QString &txt)
{
    m_textLayout.setCacheEnabled(true);
    m_text = txt;
    updateDisplayText();
    m_cursor = m_text.size();
    if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
        m_keyboardScheme = theme->themeHint(QPlatformTheme::KeyboardScheme).toInt();
        m_passwordMaskDelay = theme->themeHint(QPlatformTheme::PasswordMaskDelay).toInt();
    }
    // Generalize for X11
    if (m_keyboardScheme == QPlatformTheme::KdeKeyboardScheme
        || m_keyboardScheme == QPlatformTheme::GnomeKeyboardScheme
        || m_keyboardScheme == QPlatformTheme::CdeKeyboardScheme) {
        m_keyboardScheme = QPlatformTheme::X11KeyboardScheme;
    }
}

/*!
    \internal

    Sets the password echo editing to \a editing.  If password echo editing
    is true, then the text of the password is displayed even if the echo
    mode is set to QLineEdit::PasswordEchoOnEdit.  Password echoing editing
    does not affect other echo modes.
*/
void QWidgetLineControl::updatePasswordEchoEditing(bool editing)
{
    cancelPasswordEchoTimer();
    m_passwordEchoEditing = editing;
    updateDisplayText();
}

/*!
    \internal

    Returns the cursor position of the given \a x pixel value in relation
    to the displayed text.  The given \a betweenOrOn specified what kind
    of cursor position is requested.
*/
int QWidgetLineControl::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
{
    return textLayout()->lineAt(0).xToCursor(x, betweenOrOn);
}

/*!
    \internal

    Returns the bounds of the given text position.
*/
QRect QWidgetLineControl::rectForPos(int pos) const
{
    QTextLine l = textLayout()->lineAt(0);
    if (m_preeditCursor != -1)
        pos += m_preeditCursor;
    int cix = qRound(l.cursorToX(pos));
    int w = m_cursorWidth;
    int ch = l.height() + 1;

    return QRect(cix - 5, 0, w + 9, ch);
}

/*!
    \internal

    Returns the bounds of the current cursor, as defined as a
    between characters cursor.
*/
QRect QWidgetLineControl::cursorRect() const
{
    return rectForPos(m_cursor);
}

/*!
    \internal

    Returns the bounds of the current anchor
*/
QRect QWidgetLineControl::anchorRect() const
{
    if (!hasSelectedText())
        return cursorRect();
    return rectForPos(m_cursor == m_selstart ? m_selend : m_selstart);
}

/*!
    \internal

    Fixes the current text so that it is valid given any set validators.

    Returns \c true if the text was changed.  Otherwise returns \c false.
*/
bool QWidgetLineControl::fixup() // this function assumes that validate currently returns != Acceptable
{
#ifndef QT_NO_VALIDATOR
    if (m_validator) {
        QString textCopy = m_text;
        int cursorCopy = m_cursor;
        m_validator->fixup(textCopy);
        if (m_validator->validate(textCopy, cursorCopy) == QValidator::Acceptable) {
            if (textCopy != m_text || cursorCopy != m_cursor)
                internalSetText(textCopy, cursorCopy, false);
            return true;
        }
    }
#endif
    return false;
}

/*!
    \internal

    Moves the cursor to the given position \a pos.   If \a mark is true will
    adjust the currently selected text.
*/
void QWidgetLineControl::moveCursor(int pos, bool mark)
{
    commitPreedit();

    if (pos != m_cursor) {
        separate();
        if (m_maskData)
            pos = pos > m_cursor ? nextMaskBlank(pos) : prevMaskBlank(pos);
    }
    if (mark) {
        int anchor;
        if (m_selend > m_selstart && m_cursor == m_selstart)
            anchor = m_selend;
        else if (m_selend > m_selstart && m_cursor == m_selend)
            anchor = m_selstart;
        else
            anchor = m_cursor;
        m_selstart = qMin(anchor, pos);
        m_selend = qMax(anchor, pos);
        updateDisplayText();
    } else {
        internalDeselect();
    }
    m_cursor = pos;
    if (mark || m_selDirty) {
        m_selDirty = false;
        emit selectionChanged();
    }
    emitCursorPositionChanged();
}

/*!
    \internal

    Applies the given input method event \a event to the text of the line
    control
*/
void QWidgetLineControl::processInputMethodEvent(QInputMethodEvent *event)
{
    int priorState = -1;
    bool isGettingInput = !event->commitString().isEmpty()
            || event->preeditString() != preeditAreaText()
            || event->replacementLength() > 0;
    bool cursorPositionChanged = false;
    bool selectionChange = false;

    if (isGettingInput) {
        // If any text is being input, remove selected text.
        priorState = m_undoState;
        if (echoMode() == QLineEdit::PasswordEchoOnEdit && !passwordEchoEditing()) {
            updatePasswordEchoEditing(true);
            m_selstart = 0;
            m_selend = m_text.size();
        }
        removeSelectedText();
    }

    int c = m_cursor; // cursor position after insertion of commit string
    if (event->replacementStart() <= 0)
        c += event->commitString().size() - qMin(-event->replacementStart(), event->replacementLength());

    m_cursor += event->replacementStart();
    if (m_cursor < 0)
        m_cursor = 0;

    // insert commit string
    if (event->replacementLength()) {
        m_selstart = m_cursor;
        m_selend = m_selstart + event->replacementLength();
        removeSelectedText();
    }
    if (!event->commitString().isEmpty()) {
        internalInsert(event->commitString());
        cursorPositionChanged = true;
    } else {
        m_cursor = qBound(0, c, m_text.size());
    }

    for (int i = 0; i < event->attributes().size(); ++i) {
        const QInputMethodEvent::Attribute &a = event->attributes().at(i);
        if (a.type == QInputMethodEvent::Selection) {
            m_cursor = qBound(0, a.start + a.length, m_text.size());
            if (a.length) {
                m_selstart = qMax(0, qMin(a.start, m_text.size()));
                m_selend = m_cursor;
                if (m_selend < m_selstart) {
                    qSwap(m_selstart, m_selend);
                }
                selectionChange = true;
            } else {
                if (m_selstart != m_selend)
                    selectionChange = true;
                m_selstart = m_selend = 0;
            }
            cursorPositionChanged = true;
        }
    }
#ifndef QT_NO_IM
    // in NoEcho mode, the cursor is always at the beginning of the lineedit
    switch (m_echoMode) {
    case QLineEdit::NoEcho:
        setPreeditArea(0, QString());
        break;
    case QLineEdit::Password: {
        QString preeditString = event->preeditString();
        preeditString.fill(m_passwordCharacter);
        setPreeditArea(m_cursor, preeditString);
        break;
    }
    default:
        setPreeditArea(m_cursor, event->preeditString());
        break;
    }
#endif //QT_NO_IM
    const int oldPreeditCursor = m_preeditCursor;
    m_preeditCursor = event->preeditString().size();
    m_hideCursor = false;
    QList<QTextLayout::FormatRange> formats;
    formats.reserve(event->attributes().size());
    for (int i = 0; i < event->attributes().size(); ++i) {
        const QInputMethodEvent::Attribute &a = event->attributes().at(i);
        if (a.type == QInputMethodEvent::Cursor) {
            m_preeditCursor = a.start;
            m_hideCursor = !a.length;
        } else if (a.type == QInputMethodEvent::TextFormat) {
            QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
            if (f.isValid()) {
                QTextLayout::FormatRange o;
                o.start = a.start + m_cursor;
                o.length = a.length;
                o.format = f;
                formats.append(o);
            }
        }
    }
    m_textLayout.setFormats(formats);
    updateDisplayText(/*force*/ true);
    if (cursorPositionChanged)
        emitCursorPositionChanged();
    else if (m_preeditCursor != oldPreeditCursor)
        emit updateMicroFocus();

    if (isGettingInput)
        finishChange(priorState);

    if (selectionChange)
        emit selectionChanged();
}

/*!
    \internal

    Draws the display text for the line control using the given
    \a painter, \a clip, and \a offset.  Which aspects of the display text
    are drawn is specified by the given \a flags.

    If the flags contain DrawSelections, then the selection or input mask
    backgrounds and foregrounds will be applied before drawing the text.

    If the flags contain DrawCursor a cursor of the current cursorWidth()
    will be drawn after drawing the text.

    The display text will only be drawn if the flags contain DrawText
*/
void QWidgetLineControl::draw(QPainter *painter, const QPoint &offset, const QRect &clip, int flags)
{
    QList<QTextLayout::FormatRange> selections;
    if (flags & DrawSelections) {
        QTextLayout::FormatRange o;
        if (m_selstart < m_selend) {
            o.start = m_selstart;
            o.length = m_selend - m_selstart;
            o.format.setBackground(m_palette.brush(QPalette::Highlight));
            o.format.setForeground(m_palette.brush(QPalette::HighlightedText));
        } else {
            // mask selection
            if (m_blinkStatus){
                o.start = m_cursor;
                o.length = 1;
                o.format.setBackground(m_palette.brush(QPalette::Text));
                o.format.setForeground(m_palette.brush(QPalette::Window));
            }
        }
        selections.append(o);
    }

    if (flags & DrawText)
        textLayout()->draw(painter, offset, selections, clip);

    if (flags & DrawCursor){
        int cursor = m_cursor;
        if (m_preeditCursor != -1)
            cursor += m_preeditCursor;
        if (!m_hideCursor && m_blinkStatus)
            textLayout()->drawCursor(painter, offset, cursor, m_cursorWidth);
    }
}

/*!
    \internal

    Sets the selection to cover the word at the given cursor position.
    The word boundaries are defined by the behavior of QTextLayout::SkipWords
    cursor mode.
*/
void QWidgetLineControl::selectWordAtPos(int cursor)
{
    int next = cursor + 1;
    if (next > end())
        --next;
    int c = textLayout()->previousCursorPosition(next, QTextLayout::SkipWords);
    moveCursor(c, false);
    // ## text layout should support end of words.
    int end = textLayout()->nextCursorPosition(c, QTextLayout::SkipWords);
    while (end > cursor && m_text[end-1].isSpace())
        --end;
    moveCursor(end, true);
}

/*!
    \internal

    Completes a change to the line control text.  If the change is not valid
    will undo the line control state back to the given \a validateFromState.

    If \a edited is true and the change is valid, will emit textEdited() in
    addition to textChanged().  Otherwise only emits textChanged() on a valid
    change.

    The \a update value is currently unused.
*/
bool QWidgetLineControl::finishChange(int validateFromState, bool update, bool edited)
{
    Q_UNUSED(update);

    if (m_textDirty) {
        // do validation
        bool wasValidInput = m_validInput;
        m_validInput = true;
#ifndef QT_NO_VALIDATOR
        if (m_validator) {
            QString textCopy = m_text;
            int cursorCopy = m_cursor;
            m_validInput = (m_validator->validate(textCopy, cursorCopy) != QValidator::Invalid);
            if (m_validInput) {
                if (m_text != textCopy) {
                    internalSetText(textCopy, cursorCopy, edited);
                    return true;
                }
                m_cursor = cursorCopy;
            } else {
                emit inputRejected();
            }
        }
#endif
        if (validateFromState >= 0 && wasValidInput && !m_validInput) {
            if (m_transactions.size())
                return false;
            internalUndo(validateFromState);
            m_history.erase(m_history.begin() + m_undoState, m_history.end());
            if (m_modifiedState > m_undoState)
                m_modifiedState = -1;
            m_validInput = true;
            m_textDirty = false;
        }
        updateDisplayText();

        if (m_textDirty) {
            m_textDirty = false;
            QString actualText = text();
            if (edited)
                emit textEdited(actualText);
            emit textChanged(actualText);
        }
    }
    if (m_selDirty) {
        m_selDirty = false;
        emit selectionChanged();
    }
    if (m_cursor == m_lastCursorPos)
        emit updateMicroFocus();
    emitCursorPositionChanged();
    return true;
}

/*!
    \internal

    An internal function for setting the text of the line control.
*/
void QWidgetLineControl::internalSetText(const QString &txt, int pos, bool edited)
{
    cancelPasswordEchoTimer();
    internalDeselect();
    emit resetInputContext();
    QString oldText = m_text;
    if (m_maskData) {
        m_text = maskString(0, txt, true);
        m_text += clearString(m_text.size(), m_maxLength - m_text.size());
        if (edited && oldText == m_text)
            emit inputRejected();
    } else {
        m_text = txt.isEmpty() ? txt : txt.left(m_maxLength);
    }
    m_history.clear();
    m_modifiedState =  m_undoState = 0;
    m_cursor = (pos < 0 || pos > m_text.size()) ? m_text.size() : pos;
    m_textDirty = (oldText != m_text);
    const bool changed = finishChange(-1, true, edited);

#if QT_CONFIG(accessibility)
    if (changed) {
        if (oldText.isEmpty()) {
            QAccessibleTextInsertEvent event(accessibleObject(), 0, txt);
            event.setCursorPosition(m_cursor);
            QAccessible::updateAccessibility(&event);
        } else if (txt.isEmpty()) {
            QAccessibleTextRemoveEvent event(accessibleObject(), 0, oldText);
            event.setCursorPosition(m_cursor);
            QAccessible::updateAccessibility(&event);
        } else {
            QAccessibleTextUpdateEvent event(accessibleObject(), 0, oldText, txt);
            event.setCursorPosition(m_cursor);
            QAccessible::updateAccessibility(&event);
        }
    }
#else
    Q_UNUSED(changed);
#endif
}


/*!
    \internal

    Adds the given \a command to the undo history
    of the line control.  Does not apply the command.
*/
void QWidgetLineControl::addCommand(const Command &cmd)
{
    m_history.erase(m_history.begin() + m_undoState, m_history.end());

    if (m_separator && m_undoState && m_history[m_undoState - 1].type != Separator)
        m_history.push_back(Command(Separator, m_cursor, u'\0', m_selstart, m_selend));

    m_separator = false;
    m_history.push_back(cmd);
    m_undoState = int(m_history.size());
}

/*!
    \internal

    Inserts the given string \a s into the line
    control.

    Also adds the appropriate commands into the undo history.
    This function does not call finishChange(), and may leave the text
    in an invalid state.
*/
void QWidgetLineControl::internalInsert(const QString &s)
{
    if (m_echoMode == QLineEdit::Password) {
        if (m_passwordEchoTimer != 0)
            killTimer(m_passwordEchoTimer);
        int delay = m_passwordMaskDelay;
#ifdef QT_BUILD_INTERNAL
        if (m_passwordMaskDelayOverride >= 0)
            delay = m_passwordMaskDelayOverride;
#endif

        if (delay > 0)
            m_passwordEchoTimer = startTimer(delay);
    }
    if (hasSelectedText())
        addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
    if (m_maskData) {
        QString ms = maskString(m_cursor, s);
        if (ms.isEmpty() && !s.isEmpty())
            emit inputRejected();
#if QT_CONFIG(accessibility)
        QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, ms);
        QAccessible::updateAccessibility(&insertEvent);
#endif
        for (int i = 0; i < (int) ms.size(); ++i) {
            addCommand (Command(DeleteSelection, m_cursor + i, m_text.at(m_cursor + i), -1, -1));
            addCommand(Command(Insert, m_cursor + i, ms.at(i), -1, -1));
        }
        m_text.replace(m_cursor, ms.size(), ms);
        m_cursor += ms.size();
        m_cursor = nextMaskBlank(m_cursor);
        m_textDirty = true;
#if QT_CONFIG(accessibility)
        QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
        QAccessible::updateAccessibility(&event);
#endif
    } else {
        int remaining = m_maxLength - m_text.size();
        if (remaining != 0) {
#if QT_CONFIG(accessibility)
            QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, s);
            QAccessible::updateAccessibility(&insertEvent);
#endif
            m_text.insert(m_cursor, s.left(remaining));
            for (int i = 0; i < (int) s.left(remaining).size(); ++i)
               addCommand(Command(Insert, m_cursor++, s.at(i), -1, -1));
            m_textDirty = true;
        }
        if (s.size() > remaining)
            emit inputRejected();
    }
}

/*!
    \internal

    deletes a single character from the current text.  If \a wasBackspace,
    the character prior to the cursor is removed.  Otherwise the character
    after the cursor is removed.

    Also adds the appropriate commands into the undo history.
    This function does not call finishChange(), and may leave the text
    in an invalid state.
*/
void QWidgetLineControl::internalDelete(bool wasBackspace)
{
    if (m_cursor < (int) m_text.size()) {
        cancelPasswordEchoTimer();
        if (hasSelectedText())
            addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
        addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
                   m_cursor, m_text.at(m_cursor), -1, -1));
#if QT_CONFIG(accessibility)
        QAccessibleTextRemoveEvent event(accessibleObject(), m_cursor, m_text.at(m_cursor));
        QAccessible::updateAccessibility(&event);
#endif
        if (m_maskData) {
            m_text.replace(m_cursor, 1, clearString(m_cursor, 1));
            addCommand(Command(Insert, m_cursor, m_text.at(m_cursor), -1, -1));
        } else {
            m_text.remove(m_cursor, 1);
        }
        m_textDirty = true;
    }
}

/*!
    \internal

    removes the currently selected text from the line control.

    Also adds the appropriate commands into the undo history.
    This function does not call finishChange(), and may leave the text
    in an invalid state.
*/
void QWidgetLineControl::removeSelectedText()
{
    if (m_selstart < m_selend && m_selend <= (int) m_text.size()) {
        cancelPasswordEchoTimer();
        separate();
        int i ;
        addCommand(Command(SetSelection, m_cursor, u'\0', m_selstart, m_selend));
        if (m_selstart <= m_cursor && m_cursor < m_selend) {
            // cursor is within the selection. Split up the commands
            // to be able to restore the correct cursor position
            for (i = m_cursor; i >= m_selstart; --i)
                addCommand (Command(DeleteSelection, i, m_text.at(i), -1, 1));
            for (i = m_selend - 1; i > m_cursor; --i)
                addCommand (Command(DeleteSelection, i - m_cursor + m_selstart - 1, m_text.at(i), -1, -1));
        } else {
            for (i = m_selend-1; i >= m_selstart; --i)
                addCommand (Command(RemoveSelection, i, m_text.at(i), -1, -1));
        }
#if QT_CONFIG(accessibility)
        QAccessibleTextRemoveEvent event(accessibleObject(), m_selstart, m_text.mid(m_selstart, m_selend - m_selstart));
        QAccessible::updateAccessibility(&event);
#endif
        if (m_maskData) {
            m_text.replace(m_selstart, m_selend - m_selstart,  clearString(m_selstart, m_selend - m_selstart));
            for (int i = 0; i < m_selend - m_selstart; ++i)
                addCommand(Command(Insert, m_selstart + i, m_text.at(m_selstart + i), -1, -1));
        } else {
            m_text.remove(m_selstart, m_selend - m_selstart);
        }
        if (m_cursor > m_selstart)
            m_cursor -= qMin(m_cursor, m_selend) - m_selstart;
        internalDeselect();
        m_textDirty = true;
    }
}

/*!
    \internal

    Parses the input mask specified by \a maskFields to generate
    the mask data used to handle input masks.
*/
void QWidgetLineControl::parseInputMask(const QString &maskFields)
{
    qsizetype delimiter = maskFields.indexOf(u';');
    if (maskFields.isEmpty() || delimiter == 0) {
        if (m_maskData) {
            m_maskData.reset();
            m_maxLength = 32767;
            internalSetText(QString(), -1, false);
        }
        return;
    }

    if (delimiter == -1) {
        m_blank = u' ';
        m_inputMask = maskFields;
    } else {
        m_inputMask = maskFields.left(delimiter);
        m_blank = (delimiter + 1 < maskFields.size()) ? maskFields[delimiter + 1] : u' ';
    }

    // calculate m_maxLength / m_maskData length
    m_maxLength = 0;
    bool escaped = false;
    for (int i=0; i<m_inputMask.size(); i++) {
        const auto c = m_inputMask.at(i);
        if (escaped) {
           ++m_maxLength;
           escaped = false;
           continue;
        }

        if (c == u'\\') {
           escaped = true;
           continue;
        }

        if (c != u'\\' && c != u'!' && c != u'<' && c != u'>' &&
             c != u'{' && c != u'}' && c != u'[' && c != u']')
            m_maxLength++;
    }

    m_maskData = std::make_unique<MaskInputData[]>(m_maxLength);

    MaskInputData::Casemode m = MaskInputData::NoCaseMode;
    bool s;
    bool escape = false;
    int index = 0;
    for (int i = 0; i < m_inputMask.size(); i++) {
        const auto c = m_inputMask.at(i);
        if (escape) {
            s = true;
            m_maskData[index].maskChar = c;
            m_maskData[index].separator = s;
            m_maskData[index].caseMode = m;
            index++;
            escape = false;
        } else if (c == u'<') {
            m = MaskInputData::Lower;
        } else if (c == u'>') {
            m = MaskInputData::Upper;
        } else if (c == u'!') {
            m = MaskInputData::NoCaseMode;
        } else if (c != u'{' && c != u'}' && c != u'[' && c != u']') {
            switch (c.unicode()) {
            case 'A':
            case 'a':
            case 'N':
            case 'n':
            case 'X':
            case 'x':
            case '9':
            case '0':
            case 'D':
            case 'd':
            case '#':
            case 'H':
            case 'h':
            case 'B':
            case 'b':
                s = false;
                break;
            case '\\':
                escape = true;
                Q_FALLTHROUGH();
            default:
                s = true;
                break;
            }

            if (!escape) {
                m_maskData[index].maskChar = c;
                m_maskData[index].separator = s;
                m_maskData[index].caseMode = m;
                index++;
            }
        }
    }
    internalSetText(m_text, -1, false);
}


/*!
    \internal

    checks if the key is valid compared to the inputMask
*/
bool QWidgetLineControl::isValidInput(QChar key, QChar mask) const
{
    switch (mask.unicode()) {
    case 'A':
        if (key.isLetter())
            return true;
        break;
    case 'a':
        if (key.isLetter() || key == m_blank)
            return true;
        break;
    case 'N':
        if (key.isLetterOrNumber())
            return true;
        break;
    case 'n':
        if (key.isLetterOrNumber() || key == m_blank)
            return true;
        break;
    case 'X':
        if (key.isPrint() && key != m_blank)
            return true;
        break;
    case 'x':
        if (key.isPrint() || key == m_blank)
            return true;
        break;
    case '9':
        if (key.isNumber())
            return true;
        break;
    case '0':
        if (key.isNumber() || key == m_blank)
            return true;
        break;
    case 'D':
        if (key.isNumber() && key.digitValue() > 0)
            return true;
        break;
    case 'd':
        if ((key.isNumber() && key.digitValue() > 0) || key == m_blank)
            return true;
        break;
    case '#':
        if (key.isNumber() || key == u'+' || key == u'-' || key == m_blank)
            return true;
        break;
    case 'B':
        if (key == u'0' || key == u'1')
            return true;
        break;
    case 'b':
        if (key == u'0' || key == u'1' || key == m_blank)
            return true;
        break;
    case 'H':
        if (key.isNumber() || (key >= u'a' && key <= u'f') || (key >= u'A' && key <= u'F'))
            return true;
        break;
    case 'h':
        if (key.isNumber() || (key >= u'a' && key <= u'f') || (key >= u'A' && key <= u'F') || key == m_blank)
            return true;
        break;
    default:
        break;
    }
    return false;
}

/*!
    \internal

    Returns \c true if the given text \a str is valid for any
    validator or input mask set for the line control.

    Otherwise returns \c false
*/
bool QWidgetLineControl::hasAcceptableInput(const QString &str) const
{
#ifndef QT_NO_VALIDATOR
    QString textCopy = str;
    int cursorCopy = m_cursor;
    if (m_validator && m_validator->validate(textCopy, cursorCopy)
        != QValidator::Acceptable)
        return false;
#endif

    if (!m_maskData)
        return true;

    if (str.size() != m_maxLength)
        return false;

    for (int i=0; i < m_maxLength; ++i) {
        if (m_maskData[i].separator) {
            if (str.at(i) != m_maskData[i].maskChar)
                return false;
        } else {
            if (!isValidInput(str.at(i), m_maskData[i].maskChar))
                return false;
        }
    }
    return true;
}

/*!
    \internal

    Applies the inputMask on \a str starting from position \a pos in the mask. \a clear
    specifies from where characters should be gotten when a separator is met in \a str - true means
    that blanks will be used, false that previous input is used.
    Calling this when no inputMask is set is undefined.
*/
QString QWidgetLineControl::maskString(int pos, const QString &str, bool clear) const
{
    if (pos >= m_maxLength)
        return QString::fromLatin1("");

    QString fill;
    fill = clear ? clearString(0, m_maxLength) : m_text;

    int strIndex = 0;
    QString s = QString::fromLatin1("");
    int i = pos;
    while (i < m_maxLength) {
        if (strIndex < str.size()) {
            if (m_maskData[i].separator) {
                s += m_maskData[i].maskChar;
                if (str[strIndex] == m_maskData[i].maskChar)
                    strIndex++;
                ++i;
            } else {
                if (isValidInput(str[strIndex], m_maskData[i].maskChar)) {
                    switch (m_maskData[i].caseMode) {
                    case MaskInputData::Upper:
                        s += str[strIndex].toUpper();
                        break;
                    case MaskInputData::Lower:
                        s += str[strIndex].toLower();
                        break;
                    default:
                        s += str[strIndex];
                    }
                    ++i;
                } else {
                    // search for separator first
                    int n = findInMask(i, true, true, str[strIndex]);
                    if (n != -1) {
                        if (str.size() != 1 || i == 0 || (i > 0 && (!m_maskData[i-1].separator || m_maskData[i-1].maskChar != str[strIndex]))) {
                            s += QStringView{fill}.mid(i, n - i + 1);
                            i = n + 1; // update i to find + 1
                        }
                    } else {
                        // search for valid m_blank if not
                        n = findInMask(i, true, false, str[strIndex]);
                        if (n != -1) {
                            s += QStringView{fill}.mid(i, n - i);
                            switch (m_maskData[n].caseMode) {
                            case MaskInputData::Upper:
                                s += str[strIndex].toUpper();
                                break;
                            case MaskInputData::Lower:
                                s += str[strIndex].toLower();
                                break;
                            default:
                                s += str[strIndex];
                            }
                            i = n + 1; // updates i to find + 1
                        }
                    }
                }
                ++strIndex;
            }
        } else
            break;
    }

    return s;
}



/*!
    \internal

    Returns a "cleared" string with only separators and blank chars.
    Calling this when no inputMask is set is undefined.
*/
QString QWidgetLineControl::clearString(int pos, int len) const
{
    if (pos >= m_maxLength)
        return QString();

    QString s;
    int end = qMin(m_maxLength, pos + len);
    for (int i = pos; i < end; ++i)
        if (m_maskData[i].separator)
            s += m_maskData[i].maskChar;
        else
            s += m_blank;

    return s;
}

/*!
    \internal

    Strips blank parts of the input in a QWidgetLineControl when an inputMask is set,
    separators are still included. Typically "127.0__.0__.1__" becomes "127.0.0.1".
*/
QString QWidgetLineControl::stripString(const QString &str) const
{
    if (!m_maskData)
        return str;

    QString s;
    int end = qMin(m_maxLength, (int)str.size());
    for (int i = 0; i < end; ++i)
        if (m_maskData[i].separator)
            s += m_maskData[i].maskChar;
        else
            if (str[i] != m_blank)
                s += str[i];

    return s;
}

/*!
    \internal
    searches forward/backward in m_maskData for either a separator or a m_blank
*/
int QWidgetLineControl::findInMask(int pos, bool forward, bool findSeparator, QChar searchChar) const
{
    if (pos >= m_maxLength || pos < 0)
        return -1;

    int end = forward ? m_maxLength : -1;
    int step = forward ? 1 : -1;
    int i = pos;

    while (i != end) {
        if (findSeparator) {
            if (m_maskData[i].separator && m_maskData[i].maskChar == searchChar)
                return i;
        } else {
            if (!m_maskData[i].separator) {
                if (searchChar.isNull())
                    return i;
                else if (isValidInput(searchChar, m_maskData[i].maskChar))
                    return i;
            }
        }
        i += step;
    }
    return -1;
}

void QWidgetLineControl::internalUndo(int until)
{
    if (!isUndoAvailable())
        return;
    cancelPasswordEchoTimer();
    internalDeselect();

    while (m_undoState && m_undoState > until) {
        Command& cmd = m_history[--m_undoState];
        switch (cmd.type) {
        case Insert:
            m_text.remove(cmd.pos, 1);
            m_cursor = cmd.pos;
            break;
        case SetSelection:
            m_selstart = cmd.selStart;
            m_selend = cmd.selEnd;
            m_cursor = cmd.pos;
            break;
        case Remove:
        case RemoveSelection:
            m_text.insert(cmd.pos, cmd.uc);
            m_cursor = cmd.pos + 1;
            break;
        case Delete:
        case DeleteSelection:
            m_text.insert(cmd.pos, cmd.uc);
            m_cursor = cmd.pos;
            break;
        case Separator:
            continue;
        }
        if (until < 0 && m_undoState) {
            Command& next = m_history[m_undoState-1];
            if (next.type != cmd.type && next.type < RemoveSelection
                 && (cmd.type < RemoveSelection || next.type == Separator))
                break;
        }
    }
    m_textDirty = true;
    emitCursorPositionChanged();
}

void QWidgetLineControl::internalRedo()
{
    if (!isRedoAvailable())
        return;
    internalDeselect();
    while (m_undoState < (int)m_history.size()) {
        Command& cmd = m_history[m_undoState++];
        switch (cmd.type) {
        case Insert:
            m_text.insert(cmd.pos, cmd.uc);
            m_cursor = cmd.pos + 1;
            break;
        case SetSelection:
            m_selstart = cmd.selStart;
            m_selend = cmd.selEnd;
            m_cursor = cmd.pos;
            break;
        case Remove:
        case Delete:
        case RemoveSelection:
        case DeleteSelection:
            m_text.remove(cmd.pos, 1);
            m_selstart = cmd.selStart;
            m_selend = cmd.selEnd;
            m_cursor = cmd.pos;
            break;
        case Separator:
            m_selstart = cmd.selStart;
            m_selend = cmd.selEnd;
            m_cursor = cmd.pos;
            break;
        }
        if (m_undoState < (int)m_history.size()) {
            Command& next = m_history[m_undoState];
            if (next.type != cmd.type && cmd.type < RemoveSelection && next.type != Separator
                 && (next.type < RemoveSelection || cmd.type == Separator))
                break;
        }
    }
    m_textDirty = true;
    emitCursorPositionChanged();
}

/*!
    \internal

    If the current cursor position differs from the last emitted cursor
    position, emits cursorPositionChanged().
*/
void QWidgetLineControl::emitCursorPositionChanged()
{
    if (m_cursor != m_lastCursorPos) {
        const int oldLast = m_lastCursorPos;
        m_lastCursorPos = m_cursor;
        emit cursorPositionChanged(oldLast, m_cursor);
#if QT_CONFIG(accessibility)
        // otherwise we send a selection update which includes the cursor
        if (!hasSelectedText()) {
            QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
            QAccessible::updateAccessibility(&event);
        }
#endif
    }
}

#if QT_CONFIG(completer)
// iterating forward(dir=1)/backward(dir=-1) from the
// current row based. dir=0 indicates a new completion prefix was set.
bool QWidgetLineControl::advanceToEnabledItem(int dir)
{
    int start = m_completer->currentRow();
    if (start == -1)
        return false;
    int i = start + dir;
    if (dir == 0) dir = 1;
    do {
        if (!m_completer->setCurrentRow(i)) {
            if (!m_completer->wrapAround())
                break;
            i = i > 0 ? 0 : m_completer->completionCount() - 1;
        } else {
            QModelIndex currentIndex = m_completer->currentIndex();
            if (m_completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled)
                return true;
            i += dir;
        }
    } while (i != start);

    m_completer->setCurrentRow(start); // restore
    return false;
}

void QWidgetLineControl::complete(int key)
{
    if (!m_completer || isReadOnly() || echoMode() != QLineEdit::Normal)
        return;

    QString text = this->text();
    if (m_completer->completionMode() == QCompleter::InlineCompletion) {
        if (key == Qt::Key_Backspace)
            return;
        int n = 0;
        if (key == Qt::Key_Up || key == Qt::Key_Down) {
            if (textAfterSelection().size())
                return;
            QString prefix = hasSelectedText() ? textBeforeSelection()
                : text;
            if (text.compare(m_completer->currentCompletion(), m_completer->caseSensitivity()) != 0
                || prefix.compare(m_completer->completionPrefix(), m_completer->caseSensitivity()) != 0) {
                m_completer->setCompletionPrefix(prefix);
            } else {
                n = (key == Qt::Key_Up) ? -1 : +1;
            }
        } else {
            m_completer->setCompletionPrefix(text);
        }
        if (!advanceToEnabledItem(n))
            return;
    } else {
#ifndef QT_KEYPAD_NAVIGATION
        if (text.isEmpty()) {
            if (auto *popup = QCompleterPrivate::get(m_completer)->popup)
                popup->hide();
            return;
        }
#endif
        m_completer->setCompletionPrefix(text);
    }

    m_completer->complete();
}
#endif

void QWidgetLineControl::setReadOnly(bool enable)
{
    if (m_readOnly == enable)
        return;

    m_readOnly = enable;
    updateCursorBlinking();
}

void QWidgetLineControl::setBlinkingCursorEnabled(bool enable)
{
    if (m_blinkEnabled == enable)
        return;

    m_blinkEnabled = enable;

    if (enable)
        connect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);
    else
        disconnect(QGuiApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QWidgetLineControl::updateCursorBlinking);

    updateCursorBlinking();
}

void QWidgetLineControl::updateCursorBlinking()
{
    if (m_blinkTimer) {
        killTimer(m_blinkTimer);
        m_blinkTimer = 0;
    }

    if (m_blinkEnabled && !m_readOnly) {
        int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
        if (flashTime >= 2)
            m_blinkTimer = startTimer(flashTime / 2);
    }

    m_blinkStatus = 1;
    emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
}

// This is still used by QDeclarativeTextInput in the qtquick1 repo
void QWidgetLineControl::resetCursorBlinkTimer()
{
    if (!m_blinkEnabled || m_blinkTimer == 0)
        return;
    killTimer(m_blinkTimer);
    m_blinkTimer = 0;
    int flashTime = QGuiApplication::styleHints()->cursorFlashTime();
    if (flashTime >= 2)
        m_blinkTimer = startTimer(flashTime / 2);
    m_blinkStatus = 1;
}

void QWidgetLineControl::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == m_blinkTimer) {
        m_blinkStatus = !m_blinkStatus;
        emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect());
    } else if (event->timerId() == m_deleteAllTimer) {
        killTimer(m_deleteAllTimer);
        m_deleteAllTimer = 0;
        clear();
    } else if (event->timerId() == m_tripleClickTimer) {
        killTimer(m_tripleClickTimer);
        m_tripleClickTimer = 0;
    } else if (event->timerId() == m_passwordEchoTimer) {
        killTimer(m_passwordEchoTimer);
        m_passwordEchoTimer = 0;
        updateDisplayText();
    }
}

#ifndef QT_NO_SHORTCUT
void QWidgetLineControl::processShortcutOverrideEvent(QKeyEvent *ke)
{
    if (ke == QKeySequence::Copy
        || ke == QKeySequence::MoveToNextWord
        || ke == QKeySequence::MoveToPreviousWord
        || ke == QKeySequence::MoveToStartOfLine
        || ke == QKeySequence::MoveToEndOfLine
        || ke == QKeySequence::MoveToStartOfDocument
        || ke == QKeySequence::MoveToEndOfDocument
        || ke == QKeySequence::SelectNextWord
        || ke == QKeySequence::SelectPreviousWord
        || ke == QKeySequence::SelectStartOfLine
        || ke == QKeySequence::SelectEndOfLine
        || ke == QKeySequence::SelectStartOfBlock
        || ke == QKeySequence::SelectEndOfBlock
        || ke == QKeySequence::SelectStartOfDocument
        || ke == QKeySequence::SelectAll
        || ke == QKeySequence::SelectEndOfDocument) {
        ke->accept();
    } else if (ke == QKeySequence::Paste
               || ke == QKeySequence::Cut
               || ke == QKeySequence::Redo
               || ke == QKeySequence::Undo
               || ke == QKeySequence::DeleteCompleteLine) {
        if (!isReadOnly())
            ke->accept();
    } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier
               || ke->modifiers() == Qt::KeypadModifier) {
        if (ke->key() < Qt::Key_Escape) {
            if (!isReadOnly())
                ke->accept();
        } else {
            switch (ke->key()) {
            case Qt::Key_Delete:
            case Qt::Key_Backspace:
                if (!isReadOnly())
                    ke->accept();
                break;

            case Qt::Key_Home:
            case Qt::Key_End:
            case Qt::Key_Left:
            case Qt::Key_Right:
                ke->accept();
                break;

            default:
                break;
            }
        }
    }
}
#endif

void QWidgetLineControl::processKeyEvent(QKeyEvent* event)
{
    bool inlineCompletionAccepted = false;

#if QT_CONFIG(completer)
    if (m_completer) {
        QCompleter::CompletionMode completionMode = m_completer->completionMode();
        auto *popup = QCompleterPrivate::get(m_completer)->popup;
        if ((completionMode == QCompleter::PopupCompletion
             || completionMode == QCompleter::UnfilteredPopupCompletion)
            && popup && popup->isVisible()) {
            // The following keys are forwarded by the completer to the widget
            // Ignoring the events lets the completer provide suitable default behavior
            switch (event->key()) {
            case Qt::Key_Escape:
                event->ignore();
                return;
            default:
                break; // normal key processing
            }
        } else if (completionMode == QCompleter::InlineCompletion) {
            switch (event->key()) {
            case Qt::Key_Enter:
            case Qt::Key_Return:
            case Qt::Key_F4:
#ifdef QT_KEYPAD_NAVIGATION
            case Qt::Key_Select:
                if (!QApplicationPrivate::keypadNavigationEnabled())
                    break;
#endif
                if (!m_completer->currentCompletion().isEmpty() && hasSelectedText()
                    && !m_completer->completionPrefix().isEmpty()
                    && textAfterSelection().isEmpty()) {
                    setText(m_completer->currentCompletion());
                    inlineCompletionAccepted = true;
                }
                break;
            default:
                break; // normal key processing
            }
        }
    }
#endif // QT_CONFIG(completer)

    if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
        if (hasAcceptableInput() || fixup()) {

            QInputMethod *inputMethod = QGuiApplication::inputMethod();
            inputMethod->commit();
            QWidget *lineEdit = qobject_cast<QWidget *>(parent());
            if (!(lineEdit && lineEdit->inputMethodHints() & Qt::ImhMultiLine))
                inputMethod->hide();

            emit accepted();
            emit editingFinished();
        }
        if (inlineCompletionAccepted)
            event->accept();
        else
            event->ignore();
        return;
    }

    if (echoMode() == QLineEdit::PasswordEchoOnEdit
        && !passwordEchoEditing()
        && !isReadOnly()
        && !event->text().isEmpty()
#ifdef QT_KEYPAD_NAVIGATION
        && event->key() != Qt::Key_Select
        && event->key() != Qt::Key_Up
        && event->key() != Qt::Key_Down
        && event->key() != Qt::Key_Back
#endif
        && !(event->modifiers() & Qt::ControlModifier)) {
        // Clear the edit and reset to normal echo mode while editing; the
        // echo mode switches back when the edit loses focus
        // ### resets current content.  dubious code; you can
        // navigate with keys up, down, back, and select(?), but if you press
        // "left" or "right" it clears?
        updatePasswordEchoEditing(true);
        clear();
    }

    bool unknown = false;
#if QT_CONFIG(shortcut)
    bool visual = cursorMoveStyle() == Qt::VisualMoveStyle;
#endif

    if (false) {
    }
#ifndef QT_NO_SHORTCUT
    else if (event == QKeySequence::Undo) {
        if (!isReadOnly())
            undo();
    }
    else if (event == QKeySequence::Redo) {
        if (!isReadOnly())
            redo();
    }
    else if (event == QKeySequence::SelectAll) {
        selectAll();
    }
#ifndef QT_NO_CLIPBOARD
    else if (event == QKeySequence::Copy) {
        copy();
    }
    else if (event == QKeySequence::Paste) {
        if (!isReadOnly()) {
            QClipboard::Mode mode = QClipboard::Clipboard;
            if (m_keyboardScheme == QPlatformTheme::X11KeyboardScheme
                && event->modifiers() == (Qt::CTRL | Qt::SHIFT)
                && event->key() == Qt::Key_Insert) {
                mode = QClipboard::Selection;
            }
            paste(mode);
        }
    }
    else if (event == QKeySequence::Cut) {
        if (!isReadOnly() && hasSelectedText()) {
            copy();
            del();
        }
    }
    else if (event == QKeySequence::DeleteEndOfLine) {
        if (!isReadOnly()) {
            setSelection(cursor(), end());
            copy();
            del();
        }
    }
#endif //QT_NO_CLIPBOARD
    else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
        home(0);
    }
    else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) {
        end(0);
    }
    else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) {
        home(1);
    }
    else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) {
        end(1);
    }
    else if (event == QKeySequence::MoveToNextChar) {
#if !QT_CONFIG(completer)
        const bool inlineCompletion = false;
#else
        const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
#endif
        if (hasSelectedText()
           && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
               || inlineCompletion)) {
            moveCursor(selectionEnd(), false);
        } else {
            cursorForward(0, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
        }
    }
    else if (event == QKeySequence::SelectNextChar) {
        cursorForward(1, visual ? 1 : (layoutDirection() == Qt::LeftToRight ? 1 : -1));
    }
    else if (event == QKeySequence::MoveToPreviousChar) {
#if !QT_CONFIG(completer)
        const bool inlineCompletion = false;
#else
        const bool inlineCompletion = m_completer && m_completer->completionMode() == QCompleter::InlineCompletion;
#endif
        if (hasSelectedText()
            && (m_keyboardScheme != QPlatformTheme::WindowsKeyboardScheme
                || inlineCompletion)) {
            moveCursor(selectionStart(), false);
        } else {
            cursorForward(0, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
        }
    }
    else if (event == QKeySequence::SelectPreviousChar) {
        cursorForward(1, visual ? -1 : (layoutDirection() == Qt::LeftToRight ? -1 : 1));
    }
    else if (event == QKeySequence::MoveToNextWord) {
        if (echoMode() == QLineEdit::Normal)
            layoutDirection() == Qt::LeftToRight ? cursorWordForward(0) : cursorWordBackward(0);
        else
            layoutDirection() == Qt::LeftToRight ? end(0) : home(0);
    }
    else if (event == QKeySequence::MoveToPreviousWord) {
        if (echoMode() == QLineEdit::Normal)
            layoutDirection() == Qt::LeftToRight ? cursorWordBackward(0) : cursorWordForward(0);
        else if (!isReadOnly()) {
            layoutDirection() == Qt::LeftToRight ? home(0) : end(0);
        }
    }
    else if (event == QKeySequence::SelectNextWord) {
        if (echoMode() == QLineEdit::Normal)
            layoutDirection() == Qt::LeftToRight ? cursorWordForward(1) : cursorWordBackward(1);
        else
            layoutDirection() == Qt::LeftToRight ? end(1) : home(1);
    }
    else if (event == QKeySequence::SelectPreviousWord) {
        if (echoMode() == QLineEdit::Normal)
            layoutDirection() == Qt::LeftToRight ? cursorWordBackward(1) : cursorWordForward(1);
        else
            layoutDirection() == Qt::LeftToRight ? home(1) : end(1);
    }
    else if (event == QKeySequence::Delete) {
        if (!isReadOnly())
            del();
    }
    else if (event == QKeySequence::DeleteEndOfWord) {
        if (!isReadOnly()) {
            if (!hasSelectedText())
                cursorWordForward(true);

            if (hasSelectedText())
                del();
        }
    }
    else if (event == QKeySequence::DeleteStartOfWord) {
        if (!isReadOnly()) {
            if (!hasSelectedText())
                cursorWordBackward(true);

            if (hasSelectedText())
                del();
        }
    } else if (event == QKeySequence::DeleteCompleteLine) {
        if (!isReadOnly()) {
            setSelection(0, text().size());
#ifndef QT_NO_CLIPBOARD
            copy();
#endif
            del();
        }
    }
#endif // QT_NO_SHORTCUT
    else {
        bool handled = false;
        if (m_keyboardScheme == QPlatformTheme::MacKeyboardScheme
            && (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)) {
            Qt::KeyboardModifiers myModifiers = (event->modifiers() & ~Qt::KeypadModifier);
            if (myModifiers & Qt::ShiftModifier) {
                if (myModifiers == (Qt::ControlModifier|Qt::ShiftModifier)
                        || myModifiers == (Qt::AltModifier|Qt::ShiftModifier)
                        || myModifiers == Qt::ShiftModifier) {

                    event->key() == Qt::Key_Up ? home(1) : end(1);
                }
            } else {
                if ((myModifiers == Qt::ControlModifier
                     || myModifiers == Qt::AltModifier
                     || myModifiers == Qt::NoModifier)) {
                    event->key() == Qt::Key_Up ? home(0) : end(0);
                }
            }
            handled = true;
        }
        if (event->modifiers() & Qt::ControlModifier) {
            switch (event->key()) {
            case Qt::Key_Backspace:
                if (!isReadOnly()) {
                    cursorWordBackward(true);
                    del();
                }
                break;
#if QT_CONFIG(completer)
            case Qt::Key_Up:
            case Qt::Key_Down:
                complete(event->key());
                break;
#endif
            default:
                if (!handled)
                    unknown = true;
            }
        } else { // ### check for *no* modifier
            switch (event->key()) {
            case Qt::Key_Backspace:
                if (!isReadOnly()) {
                    backspace();
#if QT_CONFIG(completer)
                    complete(Qt::Key_Backspace);
#endif
                }
                break;
#ifdef QT_KEYPAD_NAVIGATION
            case Qt::Key_Back:
                if (QApplicationPrivate::keypadNavigationEnabled() && !event->isAutoRepeat()
                    && !isReadOnly()) {
                    if (text().length() == 0) {
                        setText(m_cancelText);

                        if (passwordEchoEditing())
                            updatePasswordEchoEditing(false);

                        emit editFocusChange(false);
                    } else if (!m_deleteAllTimer) {
                        m_deleteAllTimer = startTimer(750);
                    }
                } else {
                    unknown = true;
                }
                break;
#endif
            default:
                if (!handled)
                    unknown = true;
            }
        }
    }

    if (event->key() == Qt::Key_Direction_L || event->key() == Qt::Key_Direction_R) {
        setLayoutDirection((event->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft);
        unknown = false;
    }

    if (unknown
        && !isReadOnly()
        && isAcceptableInput(event)) {
        insert(event->text());
#if QT_CONFIG(completer)
        complete(event->key());
#endif
        event->accept();
        return;
    }

    if (unknown) {
        event->ignore();
    } else {
#ifndef QT_NO_CLIPBOARD
        if (QApplication::clipboard()->supportsSelection())
            copy(QClipboard::Selection);
#endif
        event->accept();
    }
}

bool QWidgetLineControl::isUndoAvailable() const
{
    // For security reasons undo is not available in any password mode (NoEcho included)
    // with the exception that the user can clear the password with undo.
    return !m_readOnly && m_undoState
            && (m_echoMode == QLineEdit::Normal || m_history[m_undoState - 1].type == QWidgetLineControl::Insert);
}

bool QWidgetLineControl::isRedoAvailable() const
{
    // Same as with undo. Disabled for password modes.
    return !m_readOnly
            && m_echoMode == QLineEdit::Normal
            && m_undoState < int(m_history.size());
}

QT_END_NAMESPACE

#include "moc_qwidgetlinecontrol_p.cpp"
