diff --git a/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgetlinecontrol.cpp b/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgetlinecontrol.cpp new file mode 100644 index 00000000000000..b91a924363379d --- /dev/null +++ b/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgetlinecontrol.cpp @@ -0,0 +1,1904 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidgetlinecontrol_p.h" + +#ifndef QT_NO_LINEEDIT + +#include "qabstractitemview.h" +#include "qclipboard.h" +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif + +#include "qapplication.h" +#ifndef QT_NO_GRAPHICSVIEW +#include "qgraphicssceneevent.h" +#endif + +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(); + +#if defined(Q_WS_MAC) + if (m_threadChecks) + m_textLayoutThread = QThread::currentThread(); +#endif + + 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.length()) { + 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.length(); ++i) { + if ((uc[i] < 0x20 && uc[i] != 0x09) + || uc[i] == QChar::LineSeparator + || uc[i] == QChar::ParagraphSeparator + || uc[i] == QChar::ObjectReplacementCharacter) + 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) { + disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); + QApplication::clipboard()->setText(t, mode); + connect(QApplication::clipboard(), SIGNAL(selectionChanged()), + this, SLOT(_q_clipboardChanged())); + } +} + +/*! + \internal + + Inserts the text stored in the application clipboard into the line + control. + + \sa insert() +*/ +void QWidgetLineControl::paste(QClipboard::Mode clipboardMode) +{ + QString clip = QApplication::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() +{ + if (!composeMode()) + return; + + qApp->inputMethod()->commit(); + if (!composeMode()) + return; + + m_preeditCursor = 0; + setPreeditArea(-1, QString()); + m_textLayout.clearAdditionalFormats(); + updateDisplayText(/*force*/ true); +} + + +/*! + \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.length(); + removeSelectedText(); + separate(); + finishChange(priorState, /*update*/false, /*edited*/false); +} + +/*! + \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(start < 0 || start > (int)m_text.length()){ + 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.length()); + 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_clipboardChanged() +{ +} + +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.length(); + if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) + m_keyboardScheme = theme->themeHint(QPlatformTheme::KeyboardScheme).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 current cursor, as defined as a + between characters cursor. +*/ +QRect QWidgetLineControl::cursorRect() const +{ + QTextLine l = textLayout()->lineAt(0); + int c = m_cursor; + if (m_preeditCursor != -1) + c += m_preeditCursor; + int cix = qRound(l.cursorToX(c)); + int w = m_cursorWidth; + int ch = l.height() + 1; + + return QRect(cix-5, 0, w+9, ch); +} + +/*! + \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.length(); + } + removeSelectedText(); + } + + int c = m_cursor; // cursor position after insertion of commit string + if (event->replacementStart() <= 0) + c += event->commitString().length() - 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; + } + + m_cursor = qBound(0, c, m_text.length()); + + 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.length()); + if (a.length) { + m_selstart = qMax(0, qMin(a.start, m_text.length())); + 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 + setPreeditArea(m_cursor, event->preeditString()); +#endif //QT_NO_IM + const int oldPreeditCursor = m_preeditCursor; + m_preeditCursor = event->preeditString().length(); + m_hideCursor = false; + QList formats; + 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(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.setAdditionalFormats(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) +{ + QVector 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_blinkPeriod || 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_blinkPeriod || 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, false); + return true; + } + m_cursor = cursorCopy; + } + } +#endif + if (validateFromState >= 0 && wasValidInput && !m_validInput) { + if (m_transactions.count()) + return false; + internalUndo(validateFromState); + m_history.resize(m_undoState); + 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) + 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.length(), m_maxLength - m_text.length()); + } 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.length()) ? m_text.length() : pos; + m_textDirty = (oldText != m_text); + const bool changed = finishChange(-1, true, edited); + +#ifndef QT_NO_ACCESSIBILITY + if (changed) { + if (oldText.isEmpty()) { + QAccessibleTextInsertEvent event(parent(), 0, txt); + event.setCursorPosition(m_cursor); + QAccessible::updateAccessibility(&event); + } else if (txt.isEmpty()) { + QAccessibleTextRemoveEvent event(parent(), 0, oldText); + event.setCursorPosition(m_cursor); + QAccessible::updateAccessibility(&event); + } else { + QAccessibleTextUpdateEvent event(parent(), 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) +{ + if (m_separator && m_undoState && m_history[m_undoState - 1].type != Separator) { + m_history.resize(m_undoState + 2); + m_history[m_undoState++] = Command(Separator, m_cursor, 0, m_selstart, m_selend); + } else { + m_history.resize(m_undoState + 1); + } + m_separator = false; + m_history[m_undoState++] = cmd; +} + +/*! + \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 = qGuiApp->styleHints()->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, 0, m_selstart, m_selend)); + if (m_maskData) { + QString ms = maskString(m_cursor, s); +#ifndef QT_NO_ACCESSIBILITY + QAccessibleTextInsertEvent insertEvent(parent(), m_cursor, ms); + QAccessible::updateAccessibility(&insertEvent); +#endif + for (int i = 0; i < (int) ms.length(); ++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.length(), ms); + m_cursor += ms.length(); + m_cursor = nextMaskBlank(m_cursor); + m_textDirty = true; +#ifndef QT_NO_ACCESSIBILITY + QAccessibleTextCursorEvent event(parent(), m_cursor); + QAccessible::updateAccessibility(&event); +#endif + } else { + int remaining = m_maxLength - m_text.length(); + if (remaining != 0) { +#ifndef QT_NO_ACCESSIBILITY + QAccessibleTextInsertEvent insertEvent(parent(), m_cursor, s); + QAccessible::updateAccessibility(&insertEvent); +#endif + m_text.insert(m_cursor, s.left(remaining)); + for (int i = 0; i < (int) s.left(remaining).length(); ++i) + addCommand(Command(Insert, m_cursor++, s.at(i), -1, -1)); + m_textDirty = true; + } + } +} + +/*! + \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.length()) { + cancelPasswordEchoTimer(); + if (hasSelectedText()) + addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); + addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)), + m_cursor, m_text.at(m_cursor), -1, -1)); +#ifndef QT_NO_ACCESSIBILITY + QAccessibleTextRemoveEvent event(parent(), 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.length()) { + cancelPasswordEchoTimer(); + separate(); + int i ; + addCommand(Command(SetSelection, m_cursor, 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)); + } +#ifndef QT_NO_ACCESSIBILITY + QAccessibleTextRemoveEvent event(parent(), 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) +{ + int delimiter = maskFields.indexOf(QLatin1Char(';')); + if (maskFields.isEmpty() || delimiter == 0) { + if (m_maskData) { + delete [] m_maskData; + m_maskData = 0; + m_maxLength = 32767; + internalSetText(QString()); + } + return; + } + + if (delimiter == -1) { + m_blank = QLatin1Char(' '); + m_inputMask = maskFields; + } else { + m_inputMask = maskFields.left(delimiter); + m_blank = (delimiter + 1 < maskFields.length()) ? maskFields[delimiter + 1] : QLatin1Char(' '); + } + + // calculate m_maxLength / m_maskData length + m_maxLength = 0; + QChar c = 0; + for (int i=0; i 0 && m_inputMask.at(i-1) == QLatin1Char('\\')) { + m_maxLength++; + continue; + } + if (c != QLatin1Char('\\') && c != QLatin1Char('!') && + c != QLatin1Char('<') && c != QLatin1Char('>') && + c != QLatin1Char('{') && c != QLatin1Char('}') && + c != QLatin1Char('[') && c != QLatin1Char(']')) + m_maxLength++; + } + + delete [] m_maskData; + m_maskData = new MaskInputData[m_maxLength]; + + MaskInputData::Casemode m = MaskInputData::NoCaseMode; + c = 0; + bool s; + bool escape = false; + int index = 0; + for (int i = 0; i < m_inputMask.length(); i++) { + 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 == QLatin1Char('<')) { + m = MaskInputData::Lower; + } else if (c == QLatin1Char('>')) { + m = MaskInputData::Upper; + } else if (c == QLatin1Char('!')) { + m = MaskInputData::NoCaseMode; + } else if (c != QLatin1Char('{') && c != QLatin1Char('}') && c != QLatin1Char('[') && c != QLatin1Char(']')) { + 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; + 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); +} + + +/*! + \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()) + 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 == QLatin1Char('+') || key == QLatin1Char('-') || key == m_blank) + return true; + break; + case 'B': + if (key == QLatin1Char('0') || key == QLatin1Char('1')) + return true; + break; + case 'b': + if (key == QLatin1Char('0') || key == QLatin1Char('1') || key == m_blank) + return true; + break; + case 'H': + if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F'))) + return true; + break; + case 'h': + if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('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.length() != 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(uint pos, const QString &str, bool clear) const +{ + if (pos >= (uint)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.length()) { + if (m_maskData[i].separator) { + s += m_maskData[i].maskChar; + if (str[(int)strIndex] == m_maskData[i].maskChar) + strIndex++; + ++i; + } else { + if (isValidInput(str[(int)strIndex], m_maskData[i].maskChar)) { + switch (m_maskData[i].caseMode) { + case MaskInputData::Upper: + s += str[(int)strIndex].toUpper(); + break; + case MaskInputData::Lower: + s += str[(int)strIndex].toLower(); + break; + default: + s += str[(int)strIndex]; + } + ++i; + } else { + // search for separator first + int n = findInMask(i, true, true, str[(int)strIndex]); + if (n != -1) { + if (str.length() != 1 || i == 0 || (i > 0 && (!m_maskData[i-1].separator || m_maskData[i-1].maskChar != str[(int)strIndex]))) { + s += 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[(int)strIndex]); + if (n != -1) { + s += fill.mid(i, n-i); + switch (m_maskData[n].caseMode) { + case MaskInputData::Upper: + s += str[(int)strIndex].toUpper(); + break; + case MaskInputData::Lower: + s += str[(int)strIndex].toLower(); + break; + default: + s += str[(int)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(uint pos, uint len) const +{ + if (pos >= (uint)m_maxLength) + return QString(); + + QString s; + int end = qMin((uint)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.length()); + 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(); + + // Undo works only for clearing the line when in any of password the modes + if (m_echoMode != QLineEdit::Normal) { + clear(); + return; + } + + 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; + cursorPositionChanged(oldLast, m_cursor); +#ifndef QT_NO_ACCESSIBILITY + // otherwise we send a selection update which includes the cursor + if (!hasSelectedText()) { + QAccessibleTextCursorEvent event(parent(), m_cursor); + QAccessible::updateAccessibility(&event); + } +#endif + } +} + +#ifndef QT_NO_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().length()) + 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()) { + m_completer->popup()->hide(); + return; + } +#endif + m_completer->setCompletionPrefix(text); + } + + m_completer->complete(); +} +#endif + +void QWidgetLineControl::setReadOnly(bool enable) +{ + m_readOnly = enable; + if (enable) + setCursorBlinkPeriod(0); + else + setCursorBlinkPeriod(QApplication::cursorFlashTime()); +} + +void QWidgetLineControl::setCursorBlinkPeriod(int msec) +{ + if (msec == m_blinkPeriod) + return; + if (m_blinkTimer) { + killTimer(m_blinkTimer); + } + if (msec && !m_readOnly) { + m_blinkTimer = startTimer(msec / 2); + m_blinkStatus = 1; + } else { + m_blinkTimer = 0; + if (m_blinkStatus == 1) + emit updateNeeded(inputMask().isEmpty() ? cursorRect() : QRect()); + } + m_blinkPeriod = msec; +} + +// This is still used by QDeclarativeTextInput in the qtquick1 repo +void QWidgetLineControl::resetCursorBlinkTimer() +{ + if (m_blinkPeriod == 0 || m_blinkTimer == 0) + return; + killTimer(m_blinkTimer); + m_blinkTimer = startTimer(m_blinkPeriod / 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 (isReadOnly()) + return; + + if (ke == QKeySequence::Copy + || ke == QKeySequence::Paste + || ke == QKeySequence::Cut + || ke == QKeySequence::Redo + || ke == QKeySequence::Undo + || ke == QKeySequence::MoveToNextWord + || ke == QKeySequence::MoveToPreviousWord + || 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 == QKeySequence::DeleteCompleteLine) { + ke->accept(); + } else if (ke->modifiers() == Qt::NoModifier || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + ke->accept(); + default: + break; + } + } + } +} +#endif + +void QWidgetLineControl::processKeyEvent(QKeyEvent* event) +{ + bool inlineCompletionAccepted = false; + +#ifndef QT_NO_COMPLETER + if (m_completer) { + QCompleter::CompletionMode completionMode = m_completer->completionMode(); + if ((completionMode == QCompleter::PopupCompletion + || completionMode == QCompleter::UnfilteredPopupCompletion) + && m_completer->popup() + && m_completer->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; + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F4: +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Select: + if (!QApplication::keypadNavigationEnabled()) + break; +#endif + m_completer->popup()->hide(); // just hide. will end up propagating to parent + 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 (!QApplication::keypadNavigationEnabled()) + break; +#endif + if (!m_completer->currentCompletion().isEmpty() && hasSelectedText() + && textAfterSelection().isEmpty()) { + setText(m_completer->currentCompletion()); + inlineCompletionAccepted = true; + } + default: + break; // normal key processing + } + } + } +#endif // QT_NO_COMPLETER + + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + if (hasAcceptableInput() || fixup()) { + 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; + bool visual = cursorMoveStyle() == Qt::VisualMoveStyle; + + 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()) { + 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 defined(QT_NO_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 defined(QT_NO_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()) { + cursorWordForward(true); + del(); + } + } + else if (event == QKeySequence::DeleteStartOfWord) { + if (!isReadOnly()) { + cursorWordBackward(true); + 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; +#ifndef QT_NO_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(); +#ifndef QT_NO_COMPLETER + complete(Qt::Key_Backspace); +#endif + } + break; +#ifdef QT_KEYPAD_NAVIGATION + case Qt::Key_Back: + if (QApplication::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()) { + QString t = event->text(); + if (!t.isEmpty() && (t.at(0).isPrint() || t.at(0).unicode() == 0x200C || t.at(0).unicode() == 0x200D)) { + insert(t); +#ifndef QT_NO_COMPLETER + complete(event->key()); +#endif + event->accept(); + return; + } + } + + if (unknown) + event->ignore(); + else + 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 < m_history.size(); +} + +QT_END_NAMESPACE + +#endif diff --git a/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgettextcontrol.cpp b/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgettextcontrol.cpp new file mode 100644 index 00000000000000..e6a8a6e4c60e64 --- /dev/null +++ b/Telegram/_qt_5_3_1_patch/qtbase/src/widgets/widgets/qwidgettextcontrol.cpp @@ -0,0 +1,3310 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidgettextcontrol_p.h" +#include "qwidgettextcontrol_p_p.h" + +#ifndef QT_NO_TEXTCONTROL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/qtextdocumentlayout_p.h" +#include "private/qabstracttextdocumentlayout_p.h" +#include "private/qtextedit_p.h" +#include "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qwidgettextcontrol_p.h" +#include "qgraphicssceneevent.h" +#include "qpagedpaintdevice.h" +#include "private/qpagedpaintdevice_p.h" +#include "qtextdocumentwriter.h" +#include "qstylehints.h" +#include "private/qtextcursor_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_SHORTCUT +#include "private/qapplication_p.h" +#include "private/qshortcutmap_p.h" +#include +#define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? \ + QLatin1Char('\t') + QKeySequence(k).toString(QKeySequence::NativeText) : QString()) + +#else +#define ACCEL_KEY(k) QString() +#endif + +#include + +QT_BEGIN_NAMESPACE + +// could go into QTextCursor... +static QTextLine currentTextLine(const QTextCursor &cursor) +{ + const QTextBlock block = cursor.block(); + if (!block.isValid()) + return QTextLine(); + + const QTextLayout *layout = block.layout(); + if (!layout) + return QTextLine(); + + const int relativePos = cursor.position() - block.position(); + return layout->lineForTextPosition(relativePos); +} + +QWidgetTextControlPrivate::QWidgetTextControlPrivate() + : doc(0), cursorOn(false), cursorIsFocusIndicator(false), +#ifndef Q_OS_ANDROID + interactionFlags(Qt::TextEditorInteraction), +#else + interactionFlags(Qt::TextEditable), +#endif + dragEnabled(true), +#ifndef QT_NO_DRAGANDDROP + mousePressed(false), mightStartDrag(false), +#endif + lastSelectionPosition(0), lastSelectionAnchor(0), + ignoreAutomaticScrollbarAdjustement(false), + overwriteMode(false), + acceptRichText(true), + preeditCursor(0), hideCursor(false), + hasFocus(false), +#ifdef QT_KEYPAD_NAVIGATION + hasEditFocus(false), +#endif + isEnabled(true), + hadSelectionOnMousePress(false), + ignoreUnusedNavigationEvents(false), + openExternalLinks(false), + wordSelectionEnabled(false) +{} + +bool QWidgetTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e) +{ +#ifdef QT_NO_SHORTCUT + Q_UNUSED(e); +#endif + + Q_Q(QWidgetTextControl); + if (cursor.isNull()) + return false; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + QTextCursor::MoveOperation op = QTextCursor::NoMove; + + if (false) { + } +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToNextChar) { + op = QTextCursor::Right; + } + else if (e == QKeySequence::MoveToPreviousChar) { + op = QTextCursor::Left; + } + else if (e == QKeySequence::SelectNextChar) { + op = QTextCursor::Right; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousChar) { + op = QTextCursor::Left; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextWord) { + op = QTextCursor::WordRight; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousWord) { + op = QTextCursor::WordLeft; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfLine) { + op = QTextCursor::StartOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfLine) { + op = QTextCursor::EndOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfBlock) { + op = QTextCursor::StartOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfBlock) { + op = QTextCursor::EndOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfDocument) { + op = QTextCursor::Start; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfDocument) { + op = QTextCursor::End; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousLine) { + op = QTextCursor::Up; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextLine) { + op = QTextCursor::Down; + mode = QTextCursor::KeepAnchor; + { + QTextBlock block = cursor.block(); + QTextLine line = currentTextLine(cursor); + if (!block.next().isValid() + && line.isValid() + && line.lineNumber() == block.layout()->lineCount() - 1) + op = QTextCursor::End; + } + } + else if (e == QKeySequence::MoveToNextWord) { + op = QTextCursor::WordRight; + } + else if (e == QKeySequence::MoveToPreviousWord) { + op = QTextCursor::WordLeft; + } + else if (e == QKeySequence::MoveToEndOfBlock) { + op = QTextCursor::EndOfBlock; + } + else if (e == QKeySequence::MoveToStartOfBlock) { + op = QTextCursor::StartOfBlock; + } + else if (e == QKeySequence::MoveToNextLine) { + op = QTextCursor::Down; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToStartOfLine) { + op = QTextCursor::StartOfLine; + } + else if (e == QKeySequence::MoveToEndOfLine) { + op = QTextCursor::EndOfLine; + } + else if (e == QKeySequence::MoveToStartOfDocument) { + op = QTextCursor::Start; + } + else if (e == QKeySequence::MoveToEndOfDocument) { + op = QTextCursor::End; + } +#endif // QT_NO_SHORTCUT + else { + return false; + } + +// Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but +// here's the breakdown: +// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command), +// Alt (Option), or Meta (Control). +// Command/Control + Left/Right -- Move to left or right of the line +// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor) +// Option + Left/Right -- Move one word Left/right. +// + Up/Down -- Begin/End of Paragraph. +// Home/End Top/Bottom of file. (usually don't move the cursor, but will select) + + bool visualNavigation = cursor.visualNavigation(); + cursor.setVisualNavigation(true); + const bool moved = cursor.movePosition(op, mode); + cursor.setVisualNavigation(visualNavigation); + q->ensureCursorVisible(); + + bool ignoreNavigationEvents = ignoreUnusedNavigationEvents; + bool isNavigationEvent = e->key() == Qt::Key_Up || e->key() == Qt::Key_Down; + +#ifdef QT_KEYPAD_NAVIGATION + ignoreNavigationEvents = ignoreNavigationEvents || QApplication::keypadNavigationEnabled(); + isNavigationEvent = isNavigationEvent || + (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional + && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)); +#else + isNavigationEvent = isNavigationEvent || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right; +#endif + + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } else if (ignoreNavigationEvents && isNavigationEvent && oldSelection.anchor() == cursor.anchor()) { + return false; + } + + selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor)); + + repaintOldAndNewSelection(oldSelection); + + return true; +} + +void QWidgetTextControlPrivate::updateCurrentCharFormat() +{ + Q_Q(QWidgetTextControl); + + QTextCharFormat fmt = cursor.charFormat(); + if (fmt == lastCharFormat) + return; + lastCharFormat = fmt; + + emit q->currentCharFormatChanged(fmt); + emit q->microFocusChanged(); +} + +void QWidgetTextControlPrivate::indent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() + 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat format = list->format(); + format.setIndent(format.indent() + 1); + + if (list->itemNumber(cursor.block()) == 1) + list->setFormat(format); + else + cursor.createList(format); + } +} + +void QWidgetTextControlPrivate::outdent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() - 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat listFmt = list->format(); + listFmt.setIndent(listFmt.indent() - 1); + list->setFormat(listFmt); + } +} + +void QWidgetTextControlPrivate::gotoNextTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() + cell.columnSpan(); + int newRow = cell.row(); + + if (newColumn >= table->columns()) { + newColumn = 0; + ++newRow; + if (newRow >= table->rows()) + table->insertRows(table->rows(), 1); + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QWidgetTextControlPrivate::gotoPreviousTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() - 1; + int newRow = cell.row(); + + if (newColumn < 0) { + newColumn = table->columns() - 1; + --newRow; + if (newRow < 0) + return; + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QWidgetTextControlPrivate::createAutoBulletList() +{ + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextListFormat listFmt; + listFmt.setStyle(QTextListFormat::ListDisc); + listFmt.setIndent(blockFmt.indent() + 1); + + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + + cursor.createList(listFmt); + + cursor.endEditBlock(); +} + +void QWidgetTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QWidgetTextControl); + setContent(format, text, document); + + doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable); + q->setCursorWidth(-1); +} + +void QWidgetTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QWidgetTextControl); + + // for use when called from setPlainText. we may want to re-use the currently + // set char format then. + const QTextCharFormat charFormatForInsertion = cursor.charFormat(); + + bool clearDocument = true; + if (!doc) { + if (document) { + doc = document; + clearDocument = false; + } else { + palette = QApplication::palette("QWidgetTextControl"); + doc = new QTextDocument(q); + } + _q_documentLayoutChanged(); + cursor = QTextCursor(doc); + +// #### doc->documentLayout()->setPaintDevice(viewport); + + QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection())); + QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor))); + QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged())); + + // convenience signal forwards + QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); + QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); + } + + bool previousUndoRedoState = doc->isUndoRedoEnabled(); + if (!document) + doc->setUndoRedoEnabled(false); + + //Saving the index save some time. + static int contentsChangedIndex = QMetaMethod::fromSignal(&QTextDocument::contentsChanged).methodIndex(); + static int textChangedIndex = QMetaMethod::fromSignal(&QWidgetTextControl::textChanged).methodIndex(); + // avoid multiple textChanged() signals being emitted + QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex); + + if (!text.isEmpty()) { + // clear 'our' cursor for insertion to prevent + // the emission of the cursorPositionChanged() signal. + // instead we emit it only once at the end instead of + // at the end of the document after loading and when + // positioning the cursor again to the start of the + // document. + cursor = QTextCursor(); + if (format == Qt::PlainText) { + QTextCursor formatCursor(doc); + // put the setPlainText and the setCharFormat into one edit block, + // so that the syntax highlight triggers only /once/ for the entire + // document, not twice. + formatCursor.beginEditBlock(); + doc->setPlainText(text); + doc->setUndoRedoEnabled(false); + formatCursor.select(QTextCursor::Document); + formatCursor.setCharFormat(charFormatForInsertion); + formatCursor.endEditBlock(); + } else { +#ifndef QT_NO_TEXTHTMLPARSER + doc->setHtml(text); +#else + doc->setPlainText(text); +#endif + doc->setUndoRedoEnabled(false); + } + cursor = QTextCursor(doc); + } else if (clearDocument) { + doc->clear(); + } + cursor.setCharFormat(charFormatForInsertion); + + QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex); + emit q->textChanged(); + if (!document) + doc->setUndoRedoEnabled(previousUndoRedoState); + _q_updateCurrentCharFormatAndSelection(); + if (!document) + doc->setModified(false); + + q->ensureCursorVisible(); + emit q->cursorPositionChanged(); + + QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q, SLOT(_q_contentsChanged(int,int,int)), Qt::UniqueConnection); +} + +void QWidgetTextControlPrivate::startDrag() +{ +#ifndef QT_NO_DRAGANDDROP + Q_Q(QWidgetTextControl); + mousePressed = false; + if (!contextWidget) + return; + QMimeData *data = q->createMimeDataFromSelection(); + + QDrag *drag = new QDrag(contextWidget); + drag->setMimeData(data); + + Qt::DropActions actions = Qt::CopyAction; + Qt::DropAction action; + if (interactionFlags & Qt::TextEditable) { + actions |= Qt::MoveAction; + action = drag->exec(actions, Qt::MoveAction); + } else { + action = drag->exec(actions, Qt::CopyAction); + } + + if (action == Qt::MoveAction && drag->target() != contextWidget) + cursor.removeSelectedText(); +#endif +} + +void QWidgetTextControlPrivate::setCursorPosition(const QPointF &pos) +{ + Q_Q(QWidgetTextControl); + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + return; + cursor.setPosition(cursorPos); +} + +void QWidgetTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode) +{ + cursor.setPosition(pos, mode); + + if (mode != QTextCursor::KeepAnchor) { + selectedWordOnDoubleClick = QTextCursor(); + selectedBlockOnTrippleClick = QTextCursor(); + } +} + +void QWidgetTextControlPrivate::repaintCursor() +{ + Q_Q(QWidgetTextControl); + emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor)); +} + +void QWidgetTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection) +{ + Q_Q(QWidgetTextControl); + if (cursor.hasSelection() + && oldSelection.hasSelection() + && cursor.currentFrame() == oldSelection.currentFrame() + && !cursor.hasComplexSelection() + && !oldSelection.hasComplexSelection() + && cursor.anchor() == oldSelection.anchor() + ) { + QTextCursor differenceSelection(doc); + differenceSelection.setPosition(oldSelection.position()); + differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor); + emit q->updateRequest(q->selectionRect(differenceSelection)); + } else { + if (!oldSelection.isNull()) + emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection)); + emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor)); + } +} + +void QWidgetTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/) +{ + Q_Q(QWidgetTextControl); + if (forceEmitSelectionChanged) { + emit q->selectionChanged(); +#ifndef QT_NO_ACCESSIBILITY + if (q->parent() && q->parent()->isWidgetType()) { + QAccessibleTextSelectionEvent ev(q->parent(), cursor.anchor(), cursor.position()); + QAccessible::updateAccessibility(&ev); + } +#endif + } + + if (cursor.position() == lastSelectionPosition + && cursor.anchor() == lastSelectionAnchor) + return; + + bool selectionStateChange = (cursor.hasSelection() + != (lastSelectionPosition != lastSelectionAnchor)); + if (selectionStateChange) + emit q->copyAvailable(cursor.hasSelection()); + + if (!forceEmitSelectionChanged + && (selectionStateChange + || (cursor.hasSelection() + && (cursor.position() != lastSelectionPosition + || cursor.anchor() != lastSelectionAnchor)))) { + emit q->selectionChanged(); +#ifndef QT_NO_ACCESSIBILITY + if (q->parent() && q->parent()->isWidgetType()) { + QAccessibleTextSelectionEvent ev(q->parent(), cursor.anchor(), cursor.position()); + QAccessible::updateAccessibility(&ev); + } +#endif + } + emit q->microFocusChanged(); + lastSelectionPosition = cursor.position(); + lastSelectionAnchor = cursor.anchor(); +} + +void QWidgetTextControlPrivate::_q_updateCurrentCharFormatAndSelection() +{ + updateCurrentCharFormat(); + selectionChanged(); +} + +#ifndef QT_NO_CLIPBOARD +void QWidgetTextControlPrivate::setClipboardSelection() +{ + QClipboard *clipboard = QApplication::clipboard(); + if (!cursor.hasSelection() || !clipboard->supportsSelection()) + return; + Q_Q(QWidgetTextControl); + QMimeData *data = q->createMimeDataFromSelection(); + clipboard->setMimeData(data, QClipboard::Selection); +} +#endif + +void QWidgetTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor) +{ + Q_Q(QWidgetTextControl); + if (someCursor.isCopyOf(cursor)) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } +} + +void QWidgetTextControlPrivate::_q_contentsChanged(int from, int charsRemoved, int charsAdded) +{ + Q_Q(QWidgetTextControl); +#ifndef QT_NO_ACCESSIBILITY + + if (QAccessible::isActive() && q->parent() && q->parent()->isWidgetType()) { + QTextCursor tmp(doc); + tmp.setPosition(from); + tmp.setPosition(from + charsAdded, QTextCursor::KeepAnchor); + QString newText = tmp.selectedText(); + + // always report the right number of removed chars, but in lack of the real string use spaces + QString oldText = QString(charsRemoved, QLatin1Char(' ')); + + QAccessibleEvent *ev = 0; + if (charsRemoved == 0) { + ev = new QAccessibleTextInsertEvent(q->parent(), from, newText); + } else if (charsAdded == 0) { + ev = new QAccessibleTextRemoveEvent(q->parent(), from, oldText); + } else { + ev = new QAccessibleTextUpdateEvent(q->parent(), from, oldText, newText); + } + QAccessible::updateAccessibility(ev); + delete ev; + } +#endif +} + +void QWidgetTextControlPrivate::_q_documentLayoutChanged() +{ + Q_Q(QWidgetTextControl); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QObject::connect(layout, SIGNAL(update(QRectF)), q, SIGNAL(updateRequest(QRectF))); + QObject::connect(layout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(_q_updateBlock(QTextBlock))); + QObject::connect(layout, SIGNAL(documentSizeChanged(QSizeF)), q, SIGNAL(documentSizeChanged(QSizeF))); + +} + +void QWidgetTextControlPrivate::setBlinkingCursorEnabled(bool enable) +{ + Q_Q(QWidgetTextControl); + + if (enable && QApplication::cursorFlashTime() > 0) + cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); + else + cursorBlinkTimer.stop(); + + cursorOn = enable; + + repaintCursor(); +} + +void QWidgetTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition) +{ + Q_Q(QWidgetTextControl); + + // if inside the initial selected word keep that + if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart() + && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) { + q->setTextCursor(selectedWordOnDoubleClick); + return; + } + + QTextCursor curs = selectedWordOnDoubleClick; + curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + + if (!curs.movePosition(QTextCursor::StartOfWord)) + return; + const int wordStartPos = curs.position(); + + const int blockPos = curs.block().position(); + const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft(); + + QTextLine line = currentTextLine(curs); + if (!line.isValid()) + return; + + const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (!curs.movePosition(QTextCursor::EndOfWord)) + return; + const int wordEndPos = curs.position(); + + const QTextLine otherLine = currentTextLine(curs); + if (otherLine.textStart() != line.textStart() + || wordEndPos == wordStartPos) + return; + + const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (!wordSelectionEnabled && (mouseXPosition < wordStartX || mouseXPosition > wordEndX)) + return; + + if (wordSelectionEnabled) { + if (suggestedNewPosition < selectedWordOnDoubleClick.position()) { + cursor.setPosition(selectedWordOnDoubleClick.selectionEnd()); + setCursorPosition(wordStartPos, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(selectedWordOnDoubleClick.selectionStart()); + setCursorPosition(wordEndPos, QTextCursor::KeepAnchor); + } + } else { + // keep the already selected word even when moving to the left + // (#39164) + if (suggestedNewPosition < selectedWordOnDoubleClick.position()) + cursor.setPosition(selectedWordOnDoubleClick.selectionEnd()); + else + cursor.setPosition(selectedWordOnDoubleClick.selectionStart()); + + const qreal differenceToStart = mouseXPosition - wordStartX; + const qreal differenceToEnd = wordEndX - mouseXPosition; + + if (differenceToStart < differenceToEnd) + setCursorPosition(wordStartPos, QTextCursor::KeepAnchor); + else + setCursorPosition(wordEndPos, QTextCursor::KeepAnchor); + } + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QWidgetTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition) +{ + Q_Q(QWidgetTextControl); + + // if inside the initial selected line keep that + if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart() + && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) { + q->setTextCursor(selectedBlockOnTrippleClick); + return; + } + + if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) { + cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(selectedBlockOnTrippleClick.selectionStart()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + } + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QWidgetTextControlPrivate::_q_deleteSelected() +{ + if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection()) + return; + cursor.removeSelectedText(); +} + +void QWidgetTextControl::undo() +{ + Q_D(QWidgetTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->undo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +void QWidgetTextControl::redo() +{ + Q_D(QWidgetTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->redo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +QWidgetTextControl::QWidgetTextControl(QObject *parent) + : QObject(*new QWidgetTextControlPrivate, parent) +{ + Q_D(QWidgetTextControl); + d->init(); +} + +QWidgetTextControl::QWidgetTextControl(const QString &text, QObject *parent) + : QObject(*new QWidgetTextControlPrivate, parent) +{ + Q_D(QWidgetTextControl); + d->init(Qt::RichText, text); +} + +QWidgetTextControl::QWidgetTextControl(QTextDocument *doc, QObject *parent) + : QObject(*new QWidgetTextControlPrivate, parent) +{ + Q_D(QWidgetTextControl); + d->init(Qt::RichText, QString(), doc); +} + +QWidgetTextControl::~QWidgetTextControl() +{ +} + +void QWidgetTextControl::setDocument(QTextDocument *document) +{ + Q_D(QWidgetTextControl); + if (d->doc == document) + return; + + d->doc->disconnect(this); + d->doc->documentLayout()->disconnect(this); + d->doc->documentLayout()->setPaintDevice(0); + + if (d->doc->parent() == this) + delete d->doc; + + d->doc = 0; + d->setContent(Qt::RichText, QString(), document); +} + +QTextDocument *QWidgetTextControl::document() const +{ + Q_D(const QWidgetTextControl); + return d->doc; +} + +void QWidgetTextControl::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QWidgetTextControl); + d->cursorIsFocusIndicator = false; + const bool posChanged = cursor.position() != d->cursor.position(); + const QTextCursor oldSelection = d->cursor; + d->cursor = cursor; + d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (posChanged) + emit cursorPositionChanged(); +} + +QTextCursor QWidgetTextControl::textCursor() const +{ + Q_D(const QWidgetTextControl); + return d->cursor; +} + +#ifndef QT_NO_CLIPBOARD + +void QWidgetTextControl::cut() +{ + Q_D(QWidgetTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection()) + return; + copy(); + d->cursor.removeSelectedText(); +} + +void QWidgetTextControl::copy() +{ + Q_D(QWidgetTextControl); + if (!d->cursor.hasSelection()) + return; + QMimeData *data = createMimeDataFromSelection(); + QApplication::clipboard()->setMimeData(data); +} + +void QWidgetTextControl::paste(QClipboard::Mode mode) +{ + const QMimeData *md = QApplication::clipboard()->mimeData(mode); + if (md) + insertFromMimeData(md); +} +#endif + +void QWidgetTextControl::clear() +{ + Q_D(QWidgetTextControl); + // clears and sets empty content + d->extraSelections.clear(); + d->setContent(); +} + + +void QWidgetTextControl::selectAll() +{ + Q_D(QWidgetTextControl); + const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor()); + d->cursor.select(QTextCursor::Document); + d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor())); + d->cursorIsFocusIndicator = false; + emit updateRequest(); +} + +void QWidgetTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset, QWidget *contextWidget) +{ + QMatrix m; + m.translate(coordinateOffset.x(), coordinateOffset.y()); + processEvent(e, m, contextWidget); +} + +void QWidgetTextControl::processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget) +{ + Q_D(QWidgetTextControl); + if (d->interactionFlags == Qt::NoTextInteraction) { + e->ignore(); + return; + } + + d->contextWidget = contextWidget; + + if (!d->contextWidget) { + switch (e->type()) { +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneContextMenu: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHelp: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneEvent *ev = static_cast(e); + d->contextWidget = ev->widget(); + break; + } +#endif // QT_NO_GRAPHICSVIEW + default: break; + }; + } + + switch (e->type()) { + case QEvent::KeyPress: + d->keyPressEvent(static_cast(e)); + break; + case QEvent::MouseButtonPress: { + QMouseEvent *ev = static_cast(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseMove: { + QMouseEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonRelease: { + QMouseEvent *ev = static_cast(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonDblClick: { + QMouseEvent *ev = static_cast(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::InputMethod: + d->inputMethodEvent(static_cast(e)); + break; +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: { + QContextMenuEvent *ev = static_cast(e); + d->contextMenuEvent(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; } +#endif // QT_NO_CONTEXTMENU + case QEvent::FocusIn: + case QEvent::FocusOut: + d->focusEvent(static_cast(e)); + break; + + case QEvent::EnabledChange: + d->isEnabled = e->isAccepted(); + break; + +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *ev = static_cast(e); + d->showToolTip(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; + } +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_DRAGANDDROP + case QEvent::DragEnter: { + QDragEnterEvent *ev = static_cast(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; + } + case QEvent::DragLeave: + d->dragLeaveEvent(); + break; + case QEvent::DragMove: { + QDragMoveEvent *ev = static_cast(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; + } + case QEvent::Drop: { + QDropEvent *ev = static_cast(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->acceptProposedAction(); + break; + } +#endif + +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMousePress: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseMove: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseRelease: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *ev = static_cast(e); + d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget); + break; } + + case QEvent::GraphicsSceneHoverMove: { + QGraphicsSceneHoverEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, Qt::NoButton, matrix.map(ev->pos()), ev->modifiers(),Qt::NoButton, + ev->screenPos()); + break; } + + case QEvent::GraphicsSceneDragEnter: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDragLeave: + d->dragLeaveEvent(); + break; + case QEvent::GraphicsSceneDragMove: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->accept(); + break; } +#endif // QT_NO_GRAPHICSVIEW +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + case QEvent::LeaveEditFocus: + if (QApplication::keypadNavigationEnabled()) + d->editFocusEvent(e); + break; +#endif + case QEvent::ShortcutOverride: + if (d->interactionFlags & Qt::TextEditable) { + QKeyEvent* ke = static_cast(e); + if (ke->modifiers() == Qt::NoModifier + || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Tab: + ke->accept(); + default: + break; + } + } +#ifndef QT_NO_SHORTCUT + } else if (ke == QKeySequence::Copy + || ke == QKeySequence::Paste + || ke == QKeySequence::Cut + || ke == QKeySequence::Redo + || ke == QKeySequence::Undo + || ke == QKeySequence::MoveToNextWord + || ke == QKeySequence::MoveToPreviousWord + || 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::SelectEndOfDocument + || ke == QKeySequence::SelectAll + ) { + ke->accept(); +#endif + } + } + break; + default: + break; + } +} + +bool QWidgetTextControl::event(QEvent *e) +{ + return QObject::event(e); +} + +void QWidgetTextControl::timerEvent(QTimerEvent *e) +{ + Q_D(QWidgetTextControl); + if (e->timerId() == d->cursorBlinkTimer.timerId()) { + d->cursorOn = !d->cursorOn; + + if (d->cursor.hasSelection()) + d->cursorOn &= (QApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected) + != 0); + + d->repaintCursor(); + } else if (e->timerId() == d->trippleClickTimer.timerId()) { + d->trippleClickTimer.stop(); + } +} + +void QWidgetTextControl::setPlainText(const QString &text) +{ + Q_D(QWidgetTextControl); + d->setContent(Qt::PlainText, text); +} + +void QWidgetTextControl::setHtml(const QString &text) +{ + Q_D(QWidgetTextControl); + d->setContent(Qt::RichText, text); +} + +void QWidgetTextControlPrivate::keyPressEvent(QKeyEvent *e) +{ + Q_Q(QWidgetTextControl); +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::SelectAll) { + e->accept(); + q->selectAll(); + return; + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Copy) { + e->accept(); + q->copy(); + return; + } +#endif +#endif // QT_NO_SHORTCUT + + if (interactionFlags & Qt::TextSelectableByKeyboard + && cursorMoveKeyEvent(e)) + goto accept; + + if (interactionFlags & Qt::LinksAccessibleByKeyboard) { + if ((e->key() == Qt::Key_Return + || e->key() == Qt::Key_Enter +#ifdef QT_KEYPAD_NAVIGATION + || e->key() == Qt::Key_Select +#endif + ) + && cursor.hasSelection()) { + + e->accept(); + activateLinkUnderCursor(); + return; + } + } + + if (!(interactionFlags & Qt::TextEditable)) { + e->ignore(); + return; + } + + if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) { + QTextBlockFormat fmt; + fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft); + cursor.mergeBlockFormat(fmt); + goto accept; + } + + // schedule a repaint of the region of the cursor, as when we move it we + // want to make sure the old cursor disappears (not noticeable when moving + // only a few pixels but noticeable when jumping between cells in tables for + // example) + repaintSelection(); + + if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) { + QTextBlockFormat blockFmt = cursor.blockFormat(); + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart() && !cursor.hasSelection()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + QTextCursor localCursor = cursor; + localCursor.deletePreviousChar(); + } + goto accept; + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::InsertParagraphSeparator) { + cursor.insertBlock(); + e->accept(); + goto accept; + } else if (e == QKeySequence::InsertLineSeparator) { + cursor.insertText(QString(QChar::LineSeparator)); + e->accept(); + goto accept; + } +#endif + if (false) { + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::Undo) { + q->undo(); + } + else if (e == QKeySequence::Redo) { + q->redo(); + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Cut) { + q->cut(); + } + else if (e == QKeySequence::Paste) { + QClipboard::Mode mode = QClipboard::Clipboard; + if (QGuiApplication::clipboard()->supportsSelection()) { + if (e->modifiers() == (Qt::CTRL | Qt::SHIFT) && e->key() == Qt::Key_Insert) + mode = QClipboard::Selection; + } + q->paste(mode); + } +#endif + else if (e == QKeySequence::Delete) { + QTextCursor localCursor = cursor; + localCursor.deleteChar(); + } + else if (e == QKeySequence::DeleteEndOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteStartOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteEndOfLine) { + QTextBlock block = cursor.block(); + if (cursor.position() == block.position() + block.length() - 2) + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + else + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } +#endif // QT_NO_SHORTCUT + else { + goto process; + } + goto accept; + +process: + { + QString text = e->text(); + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t') || text.at(0).unicode() == 0x200C || text.at(0).unicode() == 0x200D)) { + if (overwriteMode + // no need to call deleteChar() if we have a selection, insertText + // does it already + && !cursor.hasSelection() + && !cursor.atBlockEnd()) + cursor.deleteChar(); + + cursor.insertText(text); + selectionChanged(); + } else { + e->ignore(); + return; + } + } + + accept: + + e->accept(); + cursorOn = true; + + q->ensureCursorVisible(); + + updateCurrentCharFormat(); +} + +QVariant QWidgetTextControl::loadResource(int type, const QUrl &name) +{ +#ifdef QT_NO_TEXTEDIT + Q_UNUSED(type); + Q_UNUSED(name); +#else + if (QTextEdit *textEdit = qobject_cast(parent())) { + QUrl resolvedName = textEdit->d_func()->resolveUrl(name); + return textEdit->loadResource(type, resolvedName); + } +#endif + return QVariant(); +} + +void QWidgetTextControlPrivate::_q_updateBlock(const QTextBlock &block) +{ + Q_Q(QWidgetTextControl); + QRectF br = q->blockBoundingRect(block); + br.setRight(qreal(INT_MAX)); // the block might have shrunk + emit q->updateRequest(br); +} + +QRectF QWidgetTextControlPrivate::rectForPosition(int position) const +{ + Q_Q(const QWidgetTextControl); + const QTextBlock block = doc->findBlock(position); + if (!block.isValid()) + return QRectF(); + const QAbstractTextDocumentLayout *docLayout = doc->documentLayout(); + const QTextLayout *layout = block.layout(); + const QPointF layoutPos = q->blockBoundingRect(block).topLeft(); + int relativePos = position - block.position(); + if (preeditCursor != 0) { + int preeditPos = layout->preeditAreaPosition(); + if (relativePos == preeditPos) + relativePos += preeditCursor; + else if (relativePos > preeditPos) + relativePos += layout->preeditAreaText().length(); + } + QTextLine line = layout->lineForTextPosition(relativePos); + + int cursorWidth; + { + bool ok = false; +#ifndef QT_NO_PROPERTIES + cursorWidth = docLayout->property("cursorWidth").toInt(&ok); +#endif + if (!ok) + cursorWidth = 1; + } + + QRectF r; + + if (line.isValid()) { + qreal x = line.cursorToX(relativePos); + qreal w = 0; + if (overwriteMode) { + if (relativePos < line.textLength() - line.textStart()) + w = line.cursorToX(relativePos + 1) - x; + else + w = QFontMetrics(block.layout()->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw() + } + r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), + cursorWidth + w, line.height()); + } else { + r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height + } + + return r; +} + +namespace { +struct QTextFrameComparator { +#if defined(Q_CC_MSVC) && _MSC_VER < 1600 +//The STL implementation of MSVC 2008 requires the definition + bool operator()(QTextFrame *frame1, QTextFrame *frame2) { return frame1->firstPosition() < frame2->firstPosition(); } +#endif + bool operator()(QTextFrame *frame, int position) { return frame->firstPosition() < position; } + bool operator()(int position, QTextFrame *frame) { return position < frame->firstPosition(); } +}; +} + +static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor) +{ + QRectF r; + QTextFrame *frame = cursor.currentFrame(); + const QList children = frame->childFrames(); + + const QList::ConstIterator firstFrame = std::lower_bound(children.constBegin(), children.constEnd(), + cursor.selectionStart(), QTextFrameComparator()); + const QList::ConstIterator lastFrame = std::upper_bound(children.constBegin(), children.constEnd(), + cursor.selectionEnd(), QTextFrameComparator()); + for (QList::ConstIterator it = firstFrame; it != lastFrame; ++it) { + if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow) + r |= frame->document()->documentLayout()->frameBoundingRect(*it); + } + return r; +} + +QRectF QWidgetTextControl::selectionRect(const QTextCursor &cursor) const +{ + Q_D(const QWidgetTextControl); + + QRectF r = d->rectForPosition(cursor.selectionStart()); + + if (cursor.hasComplexSelection() && cursor.currentTable()) { + QTextTable *table = cursor.currentTable(); + + r = d->doc->documentLayout()->frameBoundingRect(table); + /* + int firstRow, numRows, firstColumn, numColumns; + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + + const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn); + const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1); + + const QAbstractTextDocumentLayout * const layout = doc->documentLayout(); + + QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block()); + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow, col); + const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top(); + + tableSelRect.setTop(qMin(tableSelRect.top(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn); + const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left(); + + tableSelRect.setLeft(qMin(tableSelRect.left(), x)); + } + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col); + const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom(); + + tableSelRect.setBottom(qMax(tableSelRect.bottom(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1); + const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right(); + + tableSelRect.setRight(qMax(tableSelRect.right(), x)); + } + + r = tableSelRect.toRect(); + */ + } else if (cursor.hasSelection()) { + const int position = cursor.selectionStart(); + const int anchor = cursor.selectionEnd(); + const QTextBlock posBlock = d->doc->findBlock(position); + const QTextBlock anchorBlock = d->doc->findBlock(anchor); + if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) { + const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position()); + const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position()); + + const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber()); + const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber()); + const QTextLayout *layout = posBlock.layout(); + r = QRectF(); + for (int i = firstLine; i <= lastLine; ++i) { + r |= layout->lineAt(i).rect(); + r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled + } + r.translate(blockBoundingRect(posBlock).topLeft()); + } else { + QRectF anchorRect = d->rectForPosition(cursor.selectionEnd()); + r |= anchorRect; + r |= boundingRectOfFloatsInSelection(cursor); + QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame())); + r.setLeft(frameRect.left()); + r.setRight(frameRect.right()); + } + if (r.isValid()) + r.adjust(-1, -1, 1, 1); + } + + return r; +} + +QRectF QWidgetTextControl::selectionRect() const +{ + Q_D(const QWidgetTextControl); + return selectionRect(d->cursor); +} + +void QWidgetTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QWidgetTextControl); + + mousePressPos = pos.toPoint(); + +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonPress, button, pos, modifiers, buttons, globalPos)) { + return; + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + anchorOnMousePress = q->anchorAt(pos); + + if (cursorIsFocusIndicator) { + cursorIsFocusIndicator = false; + repaintSelection(); + cursor.clearSelection(); + } + } + if (!(button & Qt::LeftButton) || + !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) { + e->ignore(); + return; + } + + cursorIsFocusIndicator = false; + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + mousePressed = (interactionFlags & Qt::TextSelectableByMouse); + + commitPreedit(); + + if (trippleClickTimer.isActive() + && ((pos - trippleClickPoint).toPoint().manhattanLength() < QApplication::startDragDistance())) { + + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selectedBlockOnTrippleClick = cursor; + + anchorOnMousePress = QString(); + + trippleClickTimer.stop(); + } else { + int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) { + e->ignore(); + return; + } + + if (modifiers == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) { + if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(cursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(cursorPos, pos.x()); + else if (!wordSelectionEnabled) + setCursorPosition(cursorPos, QTextCursor::KeepAnchor); + } else { + + if (dragEnabled + && cursor.hasSelection() + && !cursorIsFocusIndicator + && cursorPos >= cursor.selectionStart() + && cursorPos <= cursor.selectionEnd() + && q->hitTest(pos, Qt::ExactHit) != -1) { +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = true; +#endif + return; + } + + setCursorPosition(cursorPos); + } + } + + if (interactionFlags & Qt::TextEditable) { + q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); + } else { + if (cursor.position() != oldCursorPos) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + selectionChanged(); + } + repaintOldAndNewSelection(oldSelection); + hadSelectionOnMousePress = cursor.hasSelection(); +} + +void QWidgetTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &mousePos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QWidgetTextControl); + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + QString anchor = q->anchorAt(mousePos); + if (anchor != highlightedAnchor) { + highlightedAnchor = anchor; + emit q->linkHovered(anchor); + } + } + + if (buttons & Qt::LeftButton) { + const bool editable = interactionFlags & Qt::TextEditable; + + if (!(mousePressed + || editable + || mightStartDrag + || selectedWordOnDoubleClick.hasSelection() + || selectedBlockOnTrippleClick.hasSelection())) + return; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + if (mightStartDrag) { + if ((mousePos.toPoint() - mousePressPos).manhattanLength() > QApplication::startDragDistance()) + startDrag(); + return; + } + + const qreal mouseX = qreal(mousePos.x()); + + int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); + + if (isPreediting()) { + // note: oldCursorPos not including preedit + int selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit); + + if (newCursorPos != selectionStartPos) { + commitPreedit(); + // commit invalidates positions + newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); + selectionStartPos = q->hitTest(mousePressPos, Qt::FuzzyHit); + setCursorPosition(selectionStartPos); + } + } + + if (newCursorPos == -1) + return; + + if (mousePressed && wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(newCursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(newCursorPos, mouseX); + else if (mousePressed && !isPreediting()) + setCursorPosition(newCursorPos, QTextCursor::KeepAnchor); + + if (interactionFlags & Qt::TextEditable) { + // don't call ensureVisible for the visible cursor to avoid jumping + // scrollbars. the autoscrolling ensures smooth scrolling if necessary. + //q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); +#ifndef QT_NO_IM + if (contextWidget) + qApp->inputMethod()->update(Qt::ImQueryInput); +#endif //QT_NO_IM + } else { + //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1))); + if (cursor.position() != oldCursorPos) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + } + selectionChanged(true); + repaintOldAndNewSelection(oldSelection); + } + + sendMouseEventToInputContext(e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos); +} + +void QWidgetTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QWidgetTextControl); + + const QTextCursor oldSelection = cursor; + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonRelease, button, pos, modifiers, buttons, globalPos)) { + repaintOldAndNewSelection(oldSelection); + return; + } + + const int oldCursorPos = cursor.position(); + +#ifndef QT_NO_DRAGANDDROP + if (mightStartDrag && (button & Qt::LeftButton)) { + mousePressed = false; + setCursorPosition(pos); + cursor.clearSelection(); + selectionChanged(); + } +#endif + if (mousePressed) { + mousePressed = false; +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); + selectionChanged(true); + } else if (button == Qt::MidButton + && (interactionFlags & Qt::TextEditable) + && QApplication::clipboard()->supportsSelection()) { + setCursorPosition(pos); + const QMimeData *md = QApplication::clipboard()->mimeData(QClipboard::Selection); + if (md) + q->insertFromMimeData(md); +#endif + } + + repaintOldAndNewSelection(oldSelection); + + if (cursor.position() != oldCursorPos) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + if (!(button & Qt::LeftButton)) + return; + + const QString anchor = q->anchorAt(pos); + + if (anchor.isEmpty()) + return; + + if (!cursor.hasSelection() + || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) { + + const int anchorPos = q->hitTest(pos, Qt::ExactHit); + if (anchorPos != -1) { + cursor.setPosition(anchorPos); + + QString anchor = anchorOnMousePress; + anchorOnMousePress = QString(); + activateLinkUnderCursor(anchor); + } + } + } +} + +void QWidgetTextControlPrivate::mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, + const QPoint &globalPos) +{ + Q_Q(QWidgetTextControl); + + if (button == Qt::LeftButton + && (interactionFlags & Qt::TextSelectableByMouse)) { + +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + commitPreedit(); + + const QTextCursor oldSelection = cursor; + setCursorPosition(pos); + QTextLine line = currentTextLine(cursor); + bool doEmit = false; + if (line.isValid() && line.textLength()) { + cursor.select(QTextCursor::WordUnderCursor); + doEmit = true; + } + repaintOldAndNewSelection(oldSelection); + + cursorIsFocusIndicator = false; + selectedWordOnDoubleClick = cursor; + + trippleClickPoint = pos; + trippleClickTimer.start(QApplication::doubleClickInterval(), q); + if (doEmit) { + selectionChanged(); +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + emit q->cursorPositionChanged(); + } + } else if (!sendMouseEventToInputContext(e, QEvent::MouseButtonDblClick, button, pos, + modifiers, buttons, globalPos)) { + e->ignore(); + } +} + +bool QWidgetTextControlPrivate::sendMouseEventToInputContext( + QEvent *e, QEvent::Type eventType, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_UNUSED(eventType); + Q_UNUSED(button); + Q_UNUSED(pos); + Q_UNUSED(modifiers); + Q_UNUSED(buttons); + Q_UNUSED(globalPos); +#if !defined(QT_NO_IM) + Q_Q(QWidgetTextControl); + + if (isPreediting()) { + QTextLayout *layout = cursor.block().layout(); + int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position(); + + if (cursorPos < 0 || cursorPos > layout->preeditAreaText().length()) + cursorPos = -1; + + if (cursorPos >= 0) { + if (eventType == QEvent::MouseButtonRelease) + qApp->inputMethod()->invokeAction(QInputMethod::Click, cursorPos); + + e->setAccepted(true); + return true; + } + } +#else + Q_UNUSED(e); +#endif + return false; +} + +void QWidgetTextControlPrivate::contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget) +{ +#ifdef QT_NO_CONTEXTMENU + Q_UNUSED(screenPos); + Q_UNUSED(docPos); + Q_UNUSED(contextWidget); +#else + Q_Q(QWidgetTextControl); + if (!hasFocus) + return; + QMenu *menu = q->createStandardContextMenu(docPos, contextWidget); + if (!menu) + return; + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(screenPos); +#endif +} + +bool QWidgetTextControlPrivate::dragEnterEvent(QEvent *e, const QMimeData *mimeData) +{ + Q_Q(QWidgetTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + dndFeedbackCursor = QTextCursor(); + + return true; // accept proposed action +} + +void QWidgetTextControlPrivate::dragLeaveEvent() +{ + Q_Q(QWidgetTextControl); + + const QRectF crect = q->cursorRect(dndFeedbackCursor); + dndFeedbackCursor = QTextCursor(); + + if (crect.isValid()) + emit q->updateRequest(crect); +} + +bool QWidgetTextControlPrivate::dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos) +{ + Q_Q(QWidgetTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos != -1) { + QRectF crect = q->cursorRect(dndFeedbackCursor); + if (crect.isValid()) + emit q->updateRequest(crect); + + dndFeedbackCursor = cursor; + dndFeedbackCursor.setPosition(cursorPos); + + crect = q->cursorRect(dndFeedbackCursor); + emit q->updateRequest(crect); + } + + return true; // accept proposed action +} + +bool QWidgetTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QObject *source) +{ + Q_Q(QWidgetTextControl); + dndFeedbackCursor = QTextCursor(); + + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) + return false; + + repaintSelection(); + + QTextCursor insertionCursor = q->cursorForPosition(pos); + insertionCursor.beginEditBlock(); + + if (dropAction == Qt::MoveAction && source == contextWidget) + cursor.removeSelectedText(); + + cursor = insertionCursor; + q->insertFromMimeData(mimeData); + insertionCursor.endEditBlock(); + q->ensureCursorVisible(); + return true; // accept proposed action +} + +void QWidgetTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) +{ + Q_Q(QWidgetTextControl); + if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) { + e->ignore(); + return; + } + bool isGettingInput = !e->commitString().isEmpty() + || e->preeditString() != cursor.block().layout()->preeditAreaText() + || e->replacementLength() > 0; + + cursor.beginEditBlock(); + if (isGettingInput) { + cursor.removeSelectedText(); + } + + // insert commit string + if (!e->commitString().isEmpty() || e->replacementLength()) { + QTextCursor c = cursor; + c.setPosition(c.position() + e->replacementStart()); + c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor); + c.insertText(e->commitString()); + } + + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Selection) { + QTextCursor oldCursor = cursor; + int blockStart = a.start + cursor.block().position(); + cursor.setPosition(blockStart, QTextCursor::MoveAnchor); + cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor); + q->ensureCursorVisible(); + repaintOldAndNewSelection(oldCursor); + } + } + + QTextBlock block = cursor.block(); + QTextLayout *layout = block.layout(); + if (isGettingInput) + layout->setPreeditArea(cursor.position() - block.position(), e->preeditString()); + QList overrides; + const int oldPreeditCursor = preeditCursor; + preeditCursor = e->preeditString().length(); + hideCursor = false; + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Cursor) { + preeditCursor = a.start; + hideCursor = !a.length; + } else if (a.type == QInputMethodEvent::TextFormat) { + QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); + if (f.isValid()) { + QTextLayout::FormatRange o; + o.start = a.start + cursor.position() - block.position(); + o.length = a.length; + o.format = f; + overrides.append(o); + } + } + } + layout->setAdditionalFormats(overrides); + + cursor.endEditBlock(); + + if (cursor.d) + cursor.d->setX(); + if (oldPreeditCursor != preeditCursor) + emit q->microFocusChanged(); +} + +QVariant QWidgetTextControl::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const +{ + Q_D(const QWidgetTextControl); + QTextBlock block = d->cursor.block(); + switch(property) { + case Qt::ImCursorRectangle: + return cursorRect(); + case Qt::ImFont: + return QVariant(d->cursor.charFormat().font()); + case Qt::ImCursorPosition: + return QVariant(d->cursor.position() - block.position()); + case Qt::ImSurroundingText: + return QVariant(block.text()); + case Qt::ImCurrentSelection: + return QVariant(d->cursor.selectedText()); + case Qt::ImMaximumTextLength: + return QVariant(); // No limit. + case Qt::ImAnchorPosition: + return QVariant(d->cursor.anchor() - block.position()); + case Qt::ImAbsolutePosition: + return QVariant(d->cursor.position()); + case Qt::ImTextAfterCursor: + { + int maxLength = argument.isValid() ? argument.toInt() : 1024; + QTextCursor tmpCursor = d->cursor; + int localPos = d->cursor.position() - block.position(); + QString result = block.text().mid(localPos); + while (result.length() < maxLength) { + int currentBlock = tmpCursor.blockNumber(); + tmpCursor.movePosition(QTextCursor::NextBlock); + if (tmpCursor.blockNumber() == currentBlock) + break; + result += QLatin1Char('\n') + tmpCursor.block().text(); + } + return QVariant(result); + } + case Qt::ImTextBeforeCursor: + { + int maxLength = argument.isValid() ? argument.toInt() : 1024; + QTextCursor tmpCursor = d->cursor; + int localPos = d->cursor.position() - block.position(); + int numBlocks = 0; + int resultLen = localPos; + while (resultLen < maxLength) { + int currentBlock = tmpCursor.blockNumber(); + tmpCursor.movePosition(QTextCursor::PreviousBlock); + if (tmpCursor.blockNumber() == currentBlock) + break; + numBlocks++; + resultLen += tmpCursor.block().length(); + } + QString result; + while (numBlocks) { + result += tmpCursor.block().text() + QLatin1Char('\n'); + tmpCursor.movePosition(QTextCursor::NextBlock); + --numBlocks; + } + result += block.text().mid(0,localPos); + return QVariant(result); + } + default: + return QVariant(); + } +} + +void QWidgetTextControl::setFocus(bool focus, Qt::FocusReason reason) +{ + QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut, + reason); + processEvent(&ev); +} + +void QWidgetTextControlPrivate::focusEvent(QFocusEvent *e) +{ + Q_Q(QWidgetTextControl); + emit q->updateRequest(q->selectionRect()); + if (e->gotFocus()) { +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || (hasEditFocus && (e->reason() == Qt::PopupFocusReason))) { +#endif + cursorOn = (interactionFlags & Qt::TextSelectableByKeyboard); + if (interactionFlags & Qt::TextEditable) { + setBlinkingCursorEnabled(true); + } +#ifdef QT_KEYPAD_NAVIGATION + } +#endif + } else { + setBlinkingCursorEnabled(false); + + if (cursorIsFocusIndicator + && e->reason() != Qt::ActiveWindowFocusReason + && e->reason() != Qt::PopupFocusReason + && cursor.hasSelection()) { + cursor.clearSelection(); + } + } + hasFocus = e->gotFocus(); +} + +QString QWidgetTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const +{ + if (anchorCursor.hasSelection()) { + QTextCursor cursor = anchorCursor; + if (cursor.selectionStart() != cursor.position()) + cursor.setPosition(cursor.selectionStart()); + cursor.movePosition(QTextCursor::NextCharacter); + QTextCharFormat fmt = cursor.charFormat(); + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) + return fmt.stringProperty(QTextFormat::AnchorHref); + } + return QString(); +} + +#ifdef QT_KEYPAD_NAVIGATION +void QWidgetTextControlPrivate::editFocusEvent(QEvent *e) +{ + Q_Q(QWidgetTextControl); + + if (QApplication::keypadNavigationEnabled()) { + if (e->type() == QEvent::EnterEditFocus && interactionFlags & Qt::TextEditable) { + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + const bool moved = cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + q->ensureCursorVisible(); + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + selectionChanged(); + repaintOldAndNewSelection(oldSelection); + + setBlinkingCursorEnabled(true); + } else + setBlinkingCursorEnabled(false); + } + + hasEditFocus = e->type() == QEvent::EnterEditFocus ? true : false; +} +#endif + +#ifndef QT_NO_CONTEXTMENU +static inline void setActionIcon(QAction *action, const QString &name) +{ + const QIcon icon = QIcon::fromTheme(name); + if (!icon.isNull()) + action->setIcon(icon); +} + +QMenu *QWidgetTextControl::createStandardContextMenu(const QPointF &pos, QWidget *parent) +{ + Q_D(QWidgetTextControl); + + const bool showTextSelectionActions = d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + d->linkToCopy = QString(); + if (!pos.isNull()) + d->linkToCopy = anchorAt(pos); + + if (d->linkToCopy.isEmpty() && !showTextSelectionActions) + return 0; + + QMenu *menu = new QMenu(parent); + QAction *a; + + if (d->interactionFlags & Qt::TextEditable) { + a = menu->addAction(tr("&Undo") + ACCEL_KEY(QKeySequence::Undo), this, SLOT(undo())); + a->setEnabled(d->doc->isUndoAvailable()); + setActionIcon(a, QStringLiteral("edit-undo")); + a = menu->addAction(tr("&Redo") + ACCEL_KEY(QKeySequence::Redo), this, SLOT(redo())); + a->setEnabled(d->doc->isRedoAvailable()); + setActionIcon(a, QStringLiteral("edit-redo")); + menu->addSeparator(); + +#ifndef QT_NO_CLIPBOARD + a = menu->addAction(tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut), this, SLOT(cut())); + a->setEnabled(d->cursor.hasSelection()); + setActionIcon(a, QStringLiteral("edit-cut")); +#endif + } + +#ifndef QT_NO_CLIPBOARD + if (showTextSelectionActions) { + a = menu->addAction(tr("&Copy") + ACCEL_KEY(QKeySequence::Copy), this, SLOT(copy())); + a->setEnabled(d->cursor.hasSelection()); + setActionIcon(a, QStringLiteral("edit-copy")); + } + + if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard) + || (d->interactionFlags & Qt::LinksAccessibleByMouse)) { + + a = menu->addAction(tr("Copy &Link Location"), this, SLOT(_q_copyLink())); + a->setEnabled(!d->linkToCopy.isEmpty()); + } +#endif // QT_NO_CLIPBOARD + + if (d->interactionFlags & Qt::TextEditable) { +#ifndef QT_NO_CLIPBOARD + a = menu->addAction(tr("&Paste") + ACCEL_KEY(QKeySequence::Paste), this, SLOT(paste())); + a->setEnabled(canPaste()); + setActionIcon(a, QStringLiteral("edit-paste")); +#endif + a = menu->addAction(tr("Delete"), this, SLOT(_q_deleteSelected())); + a->setEnabled(d->cursor.hasSelection()); + setActionIcon(a, QStringLiteral("edit-delete")); + } + + + if (showTextSelectionActions) { + menu->addSeparator(); + a = menu->addAction(tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll), this, SLOT(selectAll())); + a->setEnabled(!d->doc->isEmpty()); + } + + if ((d->interactionFlags & Qt::TextEditable) && qApp->styleHints()->useRtlExtensions()) { + menu->addSeparator(); + QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, menu); + menu->addMenu(ctrlCharacterMenu); + } + + return menu; +} +#endif // QT_NO_CONTEXTMENU + +QTextCursor QWidgetTextControl::cursorForPosition(const QPointF &pos) const +{ + Q_D(const QWidgetTextControl); + int cursorPos = hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + cursorPos = 0; + QTextCursor c(d->doc); + c.setPosition(cursorPos); + return c; +} + +QRectF QWidgetTextControl::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QWidgetTextControl); + if (cursor.isNull()) + return QRectF(); + + return d->rectForPosition(cursor.position()); +} + +QRectF QWidgetTextControl::cursorRect() const +{ + Q_D(const QWidgetTextControl); + return cursorRect(d->cursor); +} + +QRectF QWidgetTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const +{ + if (cursor.isNull()) + return QRectF(); + + return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0); +} + +QString QWidgetTextControl::anchorAt(const QPointF &pos) const +{ + Q_D(const QWidgetTextControl); + return d->doc->documentLayout()->anchorAt(pos); +} + +QString QWidgetTextControl::anchorAtCursor() const +{ + Q_D(const QWidgetTextControl); + + return d->anchorForCursor(d->cursor); +} + +bool QWidgetTextControl::overwriteMode() const +{ + Q_D(const QWidgetTextControl); + return d->overwriteMode; +} + +void QWidgetTextControl::setOverwriteMode(bool overwrite) +{ + Q_D(QWidgetTextControl); + d->overwriteMode = overwrite; +} + +int QWidgetTextControl::cursorWidth() const +{ +#ifndef QT_NO_PROPERTIES + Q_D(const QWidgetTextControl); + return d->doc->documentLayout()->property("cursorWidth").toInt(); +#else + return 1; +#endif +} + +void QWidgetTextControl::setCursorWidth(int width) +{ + Q_D(QWidgetTextControl); +#ifdef QT_NO_PROPERTIES + Q_UNUSED(width); +#else + if (width == -1) + width = QApplication::style()->pixelMetric(QStyle::PM_TextCursorWidth); + d->doc->documentLayout()->setProperty("cursorWidth", width); +#endif + d->repaintCursor(); +} + +bool QWidgetTextControl::acceptRichText() const +{ + Q_D(const QWidgetTextControl); + return d->acceptRichText; +} + +void QWidgetTextControl::setAcceptRichText(bool accept) +{ + Q_D(QWidgetTextControl); + d->acceptRichText = accept; +} + +#ifndef QT_NO_TEXTEDIT + +void QWidgetTextControl::setExtraSelections(const QList &selections) +{ + Q_D(QWidgetTextControl); + + QHash hash; + for (int i = 0; i < d->extraSelections.count(); ++i) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(i); + hash.insertMulti(esel.cursor.anchor(), i); + } + + for (int i = 0; i < selections.count(); ++i) { + const QTextEdit::ExtraSelection &sel = selections.at(i); + QHash::iterator it = hash.find(sel.cursor.anchor()); + if (it != hash.end()) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + if (esel.cursor.position() == sel.cursor.position() + && esel.format == sel.format) { + hash.erase(it); + continue; + } + } + QRectF r = selectionRect(sel.cursor); + if (sel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + for (QHash::iterator it = hash.begin(); it != hash.end(); ++it) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + QRectF r = selectionRect(esel.cursor); + if (esel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + d->extraSelections.resize(selections.count()); + for (int i = 0; i < selections.count(); ++i) { + d->extraSelections[i].cursor = selections.at(i).cursor; + d->extraSelections[i].format = selections.at(i).format; + } +} + +QList QWidgetTextControl::extraSelections() const +{ + Q_D(const QWidgetTextControl); + QList selections; + for (int i = 0; i < d->extraSelections.count(); ++i) { + QTextEdit::ExtraSelection sel; + sel.cursor = d->extraSelections.at(i).cursor; + sel.format = d->extraSelections.at(i).format; + selections.append(sel); + } + return selections; +} + +#endif // QT_NO_TEXTEDIT + +void QWidgetTextControl::setTextWidth(qreal width) +{ + Q_D(QWidgetTextControl); + d->doc->setTextWidth(width); +} + +qreal QWidgetTextControl::textWidth() const +{ + Q_D(const QWidgetTextControl); + return d->doc->textWidth(); +} + +QSizeF QWidgetTextControl::size() const +{ + Q_D(const QWidgetTextControl); + return d->doc->size(); +} + +void QWidgetTextControl::setOpenExternalLinks(bool open) +{ + Q_D(QWidgetTextControl); + d->openExternalLinks = open; +} + +bool QWidgetTextControl::openExternalLinks() const +{ + Q_D(const QWidgetTextControl); + return d->openExternalLinks; +} + +bool QWidgetTextControl::ignoreUnusedNavigationEvents() const +{ + Q_D(const QWidgetTextControl); + return d->ignoreUnusedNavigationEvents; +} + +void QWidgetTextControl::setIgnoreUnusedNavigationEvents(bool ignore) +{ + Q_D(QWidgetTextControl); + d->ignoreUnusedNavigationEvents = ignore; +} + +void QWidgetTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) +{ + Q_D(QWidgetTextControl); + const QTextCursor oldSelection = d->cursor; + const bool moved = d->cursor.movePosition(op, mode); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (moved) + emit cursorPositionChanged(); +} + +bool QWidgetTextControl::canPaste() const +{ +#ifndef QT_NO_CLIPBOARD + Q_D(const QWidgetTextControl); + if (d->interactionFlags & Qt::TextEditable) { + const QMimeData *md = QApplication::clipboard()->mimeData(); + return md && canInsertFromMimeData(md); + } +#endif + return false; +} + +void QWidgetTextControl::setCursorIsFocusIndicator(bool b) +{ + Q_D(QWidgetTextControl); + d->cursorIsFocusIndicator = b; + d->repaintCursor(); +} + +bool QWidgetTextControl::cursorIsFocusIndicator() const +{ + Q_D(const QWidgetTextControl); + return d->cursorIsFocusIndicator; +} + + +void QWidgetTextControl::setDragEnabled(bool enabled) +{ + Q_D(QWidgetTextControl); + d->dragEnabled = enabled; +} + +bool QWidgetTextControl::isDragEnabled() const +{ + Q_D(const QWidgetTextControl); + return d->dragEnabled; +} + +void QWidgetTextControl::setWordSelectionEnabled(bool enabled) +{ + Q_D(QWidgetTextControl); + d->wordSelectionEnabled = enabled; +} + +bool QWidgetTextControl::isWordSelectionEnabled() const +{ + Q_D(const QWidgetTextControl); + return d->wordSelectionEnabled; +} + +#ifndef QT_NO_PRINTER +void QWidgetTextControl::print(QPagedPaintDevice *printer) const +{ + Q_D(const QWidgetTextControl); + if (!printer) + return; + QTextDocument *tempDoc = 0; + const QTextDocument *doc = d->doc; + if (QPagedPaintDevicePrivate::get(printer)->printSelectionOnly) { + if (!d->cursor.hasSelection()) + return; + tempDoc = new QTextDocument(const_cast(doc)); + tempDoc->setMetaInformation(QTextDocument::DocumentTitle, doc->metaInformation(QTextDocument::DocumentTitle)); + tempDoc->setPageSize(doc->pageSize()); + tempDoc->setDefaultFont(doc->defaultFont()); + tempDoc->setUseDesignMetrics(doc->useDesignMetrics()); + QTextCursor(tempDoc).insertFragment(d->cursor.selection()); + doc = tempDoc; + + // copy the custom object handlers + doc->documentLayout()->d_func()->handlers = d->doc->documentLayout()->d_func()->handlers; + } + doc->print(printer); + delete tempDoc; +} +#endif + +QMimeData *QWidgetTextControl::createMimeDataFromSelection() const +{ + Q_D(const QWidgetTextControl); + const QTextDocumentFragment fragment(d->cursor); + return new QTextEditMimeData(fragment); +} + +bool QWidgetTextControl::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QWidgetTextControl); + if (d->acceptRichText) + return (source->hasText() && !source->text().isEmpty()) + || source->hasHtml() + || source->hasFormat(QLatin1String("application/x-qrichtext")) + || source->hasFormat(QLatin1String("application/x-qt-richtext")); + else + return source->hasText() && !source->text().isEmpty(); +} + +void QWidgetTextControl::insertFromMimeData(const QMimeData *source) +{ + Q_D(QWidgetTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !source) + return; + + bool hasData = false; + QTextDocumentFragment fragment; +#ifndef QT_NO_TEXTHTMLPARSER + if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) { + // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore). + QString richtext = QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext"))); + richtext.prepend(QLatin1String("")); + fragment = QTextDocumentFragment::fromHtml(richtext, d->doc); + hasData = true; + } else if (source->hasHtml() && d->acceptRichText) { + fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc); + hasData = true; + } else { + QString text = source->text(); + if (!text.isNull()) { + fragment = QTextDocumentFragment::fromPlainText(text); + hasData = true; + } + } +#else + fragment = QTextDocumentFragment::fromPlainText(source->text()); +#endif // QT_NO_TEXTHTMLPARSER + + if (hasData) + d->cursor.insertFragment(fragment); + ensureCursorVisible(); +} + +bool QWidgetTextControl::findNextPrevAnchor(const QTextCursor &startCursor, bool next, QTextCursor &newAnchor) +{ + Q_D(QWidgetTextControl); + + int anchorStart = -1; + QString anchorHref; + int anchorEnd = -1; + + if (next) { + const int startPos = startCursor.selectionEnd(); + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator it = block.begin(); + + while (!it.atEnd() && it.fragment().position() < startPos) + ++it; + + while (block.isValid()) { + anchorStart = -1; + + // find next anchor + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position(); + anchorHref = fmt.anchorHref(); + break; + } + } + + if (anchorStart != -1) { + anchorEnd = -1; + + // find next non-anchor fragment + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position(); + break; + } + } + + if (anchorEnd == -1) + anchorEnd = block.position() + block.length() - 1; + + // make found selection + break; + } + + block = block.next(); + it = block.begin(); + } + } else { + int startPos = startCursor.selectionStart(); + if (startPos > 0) + --startPos; + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator blockStart = block.begin(); + QTextBlock::Iterator it = block.end(); + + if (startPos == block.position()) { + it = block.begin(); + } else { + do { + if (it == blockStart) { + it = QTextBlock::Iterator(); + block = QTextBlock(); + } else { + --it; + } + } while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos); + } + + while (block.isValid()) { + anchorStart = -1; + + if (!it.atEnd()) { + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position() + fragment.length(); + anchorHref = fmt.anchorHref(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + } + + if (anchorStart != -1 && !it.atEnd()) { + anchorEnd = -1; + + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position() + fragment.length(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + + if (anchorEnd == -1) + anchorEnd = qMax(0, block.position()); + + break; + } + + block = block.previous(); + it = block.end(); + if (it != block.begin()) + --it; + blockStart = block.begin(); + } + + } + + if (anchorStart != -1 && anchorEnd != -1) { + newAnchor = d->cursor; + newAnchor.setPosition(anchorStart); + newAnchor.setPosition(anchorEnd, QTextCursor::KeepAnchor); + return true; + } + + return false; +} + +void QWidgetTextControlPrivate::activateLinkUnderCursor(QString href) +{ + QTextCursor oldCursor = cursor; + + if (href.isEmpty()) { + QTextCursor tmp = cursor; + if (tmp.selectionStart() != tmp.position()) + tmp.setPosition(tmp.selectionStart()); + tmp.movePosition(QTextCursor::NextCharacter); + href = tmp.charFormat().anchorHref(); + } + if (href.isEmpty()) + return; + + if (!cursor.hasSelection()) { + QTextBlock block = cursor.block(); + const int cursorPos = cursor.position(); + + QTextBlock::Iterator it = block.begin(); + QTextBlock::Iterator linkFragment; + + for (; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + const int fragmentPos = fragment.position(); + if (fragmentPos <= cursorPos && + fragmentPos + fragment.length() > cursorPos) { + linkFragment = it; + break; + } + } + + if (!linkFragment.atEnd()) { + it = linkFragment; + cursor.setPosition(it.fragment().position()); + if (it != block.begin()) { + do { + --it; + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position()); + } while (it != block.begin()); + } + + for (it = linkFragment; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); + } + } + } + + if (hasFocus) { + cursorIsFocusIndicator = true; + } else { + cursorIsFocusIndicator = false; + cursor.clearSelection(); + } + repaintOldAndNewSelection(oldCursor); + +#ifndef QT_NO_DESKTOPSERVICES + if (openExternalLinks) + QDesktopServices::openUrl(href); + else +#endif + emit q_func()->linkActivated(href); +} + +#ifndef QT_NO_TOOLTIP +void QWidgetTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget) +{ + const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip(); + if (toolTip.isEmpty()) + return; + QToolTip::showText(globalPos, toolTip, contextWidget); +} +#endif // QT_NO_TOOLTIP + +bool QWidgetTextControlPrivate::isPreediting() const +{ + QTextLayout *layout = cursor.block().layout(); + if (layout && !layout->preeditAreaText().isEmpty()) + return true; + + return false; +} + +void QWidgetTextControlPrivate::commitPreedit() +{ + if (!isPreediting()) + return; + + qApp->inputMethod()->commit(); + + if (!isPreediting()) + return; + + cursor.beginEditBlock(); + preeditCursor = 0; + QTextBlock block = cursor.block(); + QTextLayout *layout = block.layout(); + layout->setPreeditArea(-1, QString()); + layout->clearAdditionalFormats(); + cursor.endEditBlock(); +} + +bool QWidgetTextControl::setFocusToNextOrPreviousAnchor(bool next) +{ + Q_D(QWidgetTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + QRectF crect = selectionRect(); + emit updateRequest(crect); + + // If we don't have a current anchor, we start from the start/end + if (!d->cursor.hasSelection()) { + d->cursor = QTextCursor(d->doc); + if (next) + d->cursor.movePosition(QTextCursor::Start); + else + d->cursor.movePosition(QTextCursor::End); + } + + QTextCursor newAnchor; + if (findNextPrevAnchor(d->cursor, next, newAnchor)) { + d->cursor = newAnchor; + d->cursorIsFocusIndicator = true; + } else { + d->cursor.clearSelection(); + } + + if (d->cursor.hasSelection()) { + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; + } else { + return false; + } +} + +bool QWidgetTextControl::setFocusToAnchor(const QTextCursor &newCursor) +{ + Q_D(QWidgetTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + // Verify that this is an anchor. + const QString anchorHref = d->anchorForCursor(newCursor); + if (anchorHref.isEmpty()) + return false; + + // and process it + QRectF crect = selectionRect(); + emit updateRequest(crect); + + d->cursor.setPosition(newCursor.selectionStart()); + d->cursor.setPosition(newCursor.selectionEnd(), QTextCursor::KeepAnchor); + d->cursorIsFocusIndicator = true; + + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; +} + +void QWidgetTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QWidgetTextControl); + if (flags == d->interactionFlags) + return; + d->interactionFlags = flags; + + if (d->hasFocus) + d->setBlinkingCursorEnabled(flags & Qt::TextEditable); +} + +Qt::TextInteractionFlags QWidgetTextControl::textInteractionFlags() const +{ + Q_D(const QWidgetTextControl); + return d->interactionFlags; +} + +void QWidgetTextControl::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QWidgetTextControl); + d->cursor.mergeCharFormat(modifier); + d->updateCurrentCharFormat(); +} + +void QWidgetTextControl::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QWidgetTextControl); + d->cursor.setCharFormat(format); + d->updateCurrentCharFormat(); +} + +QTextCharFormat QWidgetTextControl::currentCharFormat() const +{ + Q_D(const QWidgetTextControl); + return d->cursor.charFormat(); +} + +void QWidgetTextControl::insertPlainText(const QString &text) +{ + Q_D(QWidgetTextControl); + d->cursor.insertText(text); +} + +#ifndef QT_NO_TEXTHTMLPARSER +void QWidgetTextControl::insertHtml(const QString &text) +{ + Q_D(QWidgetTextControl); + d->cursor.insertHtml(text); +} +#endif // QT_NO_TEXTHTMLPARSER + +QPointF QWidgetTextControl::anchorPosition(const QString &name) const +{ + Q_D(const QWidgetTextControl); + if (name.isEmpty()) + return QPointF(); + + QRectF r; + for (QTextBlock block = d->doc->begin(); block.isValid(); block = block.next()) { + QTextCharFormat format = block.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(block.position()); + break; + } + + for (QTextBlock::Iterator it = block.begin(); !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + format = fragment.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(fragment.position()); + block = QTextBlock(); + break; + } + } + } + if (!r.isValid()) + return QPointF(); + return QPointF(0, r.top()); +} + +void QWidgetTextControl::adjustSize() +{ + Q_D(QWidgetTextControl); + d->doc->adjustSize(); +} + +bool QWidgetTextControl::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QWidgetTextControl); + QTextCursor search = d->doc->find(exp, d->cursor, options); + if (search.isNull()) + return false; + + setTextCursor(search); + return true; +} + +#ifndef QT_NO_REGEXP +bool QWidgetTextControl::find(const QRegExp &exp, QTextDocument::FindFlags options) +{ + Q_D(QWidgetTextControl); + QTextCursor search = d->doc->find(exp, d->cursor, options); + if (search.isNull()) + return false; + + setTextCursor(search); + return true; +} +#endif + +QString QWidgetTextControl::toPlainText() const +{ + return document()->toPlainText(); +} + +#ifndef QT_NO_TEXTHTMLPARSER +QString QWidgetTextControl::toHtml() const +{ + return document()->toHtml(); +} +#endif + +void QWidgetTextControlPrivate::append(const QString &text, Qt::TextFormat format) +{ + QTextCursor tmp(doc); + tmp.beginEditBlock(); + tmp.movePosition(QTextCursor::End); + + if (!doc->isEmpty()) + tmp.insertBlock(cursor.blockFormat(), cursor.charFormat()); + else + tmp.setCharFormat(cursor.charFormat()); + + // preserve the char format + QTextCharFormat oldCharFormat = cursor.charFormat(); + +#ifndef QT_NO_TEXTHTMLPARSER + if (format == Qt::RichText || (format == Qt::AutoText && Qt::mightBeRichText(text))) { + tmp.insertHtml(text); + } else { + tmp.insertText(text); + } +#else + tmp.insertText(text); +#endif // QT_NO_TEXTHTMLPARSER + if (!cursor.hasSelection()) + cursor.setCharFormat(oldCharFormat); + + tmp.endEditBlock(); +} + +void QWidgetTextControl::append(const QString &text) +{ + Q_D(QWidgetTextControl); + d->append(text, Qt::AutoText); +} + +void QWidgetTextControl::appendHtml(const QString &html) +{ + Q_D(QWidgetTextControl); + d->append(html, Qt::RichText); +} + +void QWidgetTextControl::appendPlainText(const QString &text) +{ + Q_D(QWidgetTextControl); + d->append(text, Qt::PlainText); +} + + +void QWidgetTextControl::ensureCursorVisible() +{ + Q_D(QWidgetTextControl); + QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0); + emit visibilityRequest(crect); + emit microFocusChanged(); +} + +QPalette QWidgetTextControl::palette() const +{ + Q_D(const QWidgetTextControl); + return d->palette; +} + +void QWidgetTextControl::setPalette(const QPalette &pal) +{ + Q_D(QWidgetTextControl); + d->palette = pal; +} + +QAbstractTextDocumentLayout::PaintContext QWidgetTextControl::getPaintContext(QWidget *widget) const +{ + Q_D(const QWidgetTextControl); + + QAbstractTextDocumentLayout::PaintContext ctx; + + ctx.selections = d->extraSelections; + ctx.palette = d->palette; + if (d->cursorOn && d->isEnabled) { + if (d->hideCursor) + ctx.cursorPosition = -1; + else if (d->preeditCursor != 0) + ctx.cursorPosition = - (d->preeditCursor + 2); + else + ctx.cursorPosition = d->cursor.position(); + } + + if (!d->dndFeedbackCursor.isNull()) + ctx.cursorPosition = d->dndFeedbackCursor.position(); +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || d->hasEditFocus) +#endif + if (d->cursor.hasSelection()) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = d->cursor; + if (d->cursorIsFocusIndicator) { + QStyleOption opt; + opt.palette = ctx.palette; + QStyleHintReturnVariant ret; + QStyle *style = QApplication::style(); + if (widget) + style = widget->style(); + style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret); + selection.format = qvariant_cast(ret.variant).toCharFormat(); + } else { + QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive; + selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight)); + selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText)); + QStyleOption opt; + QStyle *style = QApplication::style(); + if (widget) { + opt.initFrom(widget); + style = widget->style(); + } + if (style->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, widget)) + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + } + ctx.selections.append(selection); + } + + return ctx; +} + +void QWidgetTextControl::drawContents(QPainter *p, const QRectF &rect, QWidget *widget) +{ + Q_D(QWidgetTextControl); + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext(widget); + if (rect.isValid()) + p->setClipRect(rect, Qt::IntersectClip); + ctx.clip = rect; + + d->doc->documentLayout()->draw(p, ctx); + p->restore(); +} + +void QWidgetTextControlPrivate::_q_copyLink() +{ +#ifndef QT_NO_CLIPBOARD + QMimeData *md = new QMimeData; + md->setText(linkToCopy); + QApplication::clipboard()->setMimeData(md); +#endif +} + +int QWidgetTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const +{ + Q_D(const QWidgetTextControl); + return d->doc->documentLayout()->hitTest(point, accuracy); +} + +QRectF QWidgetTextControl::blockBoundingRect(const QTextBlock &block) const +{ + Q_D(const QWidgetTextControl); + return d->doc->documentLayout()->blockBoundingRect(block); +} + +#ifndef QT_NO_CONTEXTMENU +#define NUM_CONTROL_CHARACTERS 14 +const struct QUnicodeControlCharacter { + const char *text; + ushort character; +} qt_controlCharacters[NUM_CONTROL_CHARACTERS] = { + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWSP Zero width space"), 0x200b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRE Start of left-to-right embedding"), 0x202a }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLE Start of right-to-left embedding"), 0x202b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRO Start of left-to-right override"), 0x202d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLO Start of right-to-left override"), 0x202e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDF Pop directional formatting"), 0x202c }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRI Left-to-right isolate"), 0x2066 }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLI Right-to-left isolate"), 0x2067 }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "FSI First strong isolate"), 0x2068 }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDI Pop directional isolate"), 0x2069 } +}; + +QUnicodeControlCharacterMenu::QUnicodeControlCharacterMenu(QObject *_editWidget, QWidget *parent) + : QMenu(parent), editWidget(_editWidget) +{ + setTitle(tr("Insert Unicode control character")); + for (int i = 0; i < NUM_CONTROL_CHARACTERS; ++i) { + addAction(tr(qt_controlCharacters[i].text), this, SLOT(menuActionTriggered())); + } +} + +void QUnicodeControlCharacterMenu::menuActionTriggered() +{ + QAction *a = qobject_cast(sender()); + int idx = actions().indexOf(a); + if (idx < 0 || idx >= NUM_CONTROL_CHARACTERS) + return; + QChar c(qt_controlCharacters[idx].character); + QString str(c); + +#ifndef QT_NO_TEXTEDIT + if (QTextEdit *edit = qobject_cast(editWidget)) { + edit->insertPlainText(str); + return; + } +#endif + if (QWidgetTextControl *control = qobject_cast(editWidget)) { + control->insertPlainText(str); + } +#ifndef QT_NO_LINEEDIT + if (QLineEdit *edit = qobject_cast(editWidget)) { + edit->insert(str); + return; + } +#endif +} +#endif // QT_NO_CONTEXTMENU + +QStringList QTextEditMimeData::formats() const +{ + if (!fragment.isEmpty()) + return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html") +#ifndef QT_NO_TEXTODFWRITER + << QString::fromLatin1("application/vnd.oasis.opendocument.text") +#endif + ; + else + return QMimeData::formats(); +} + +QVariant QTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const +{ + if (!fragment.isEmpty()) + setup(); + return QMimeData::retrieveData(mimeType, type); +} + +void QTextEditMimeData::setup() const +{ + QTextEditMimeData *that = const_cast(this); +#ifndef QT_NO_TEXTHTMLPARSER + that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8()); +#endif +#ifndef QT_NO_TEXTODFWRITER + { + QBuffer buffer; + QTextDocumentWriter writer(&buffer, "ODF"); + writer.write(fragment); + buffer.close(); + that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data()); + } +#endif + that->setText(fragment.toPlainText()); + fragment = QTextDocumentFragment(); +} + +QT_END_NAMESPACE + +#include "moc_qwidgettextcontrol_p.cpp" + +#endif // QT_NO_TEXTCONTROL