X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fquick%2Fitems%2Fqquicktextinput.cpp;h=22551c9d4c2e97c94fd907e968182991d5185eaf;hb=5c4108e2f0bcd8c116882eeb4aa96dc02cd22c06;hp=f2da67bae7e3c6d79c003ebe903af7ed5deef874;hpb=24fb8dc27eddfdd62bd2c3a6e863cbf433762cd6;p=profile%2Fivi%2Fqtdeclarative.git diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index f2da67b..22551c9 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -1,38 +1,38 @@ /**************************************************************************** ** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/ +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtQml 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 -** 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. +** 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, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception +** 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. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** +** 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$ @@ -41,10 +41,13 @@ #include "qquicktextinput_p.h" #include "qquicktextinput_p_p.h" -#include "qquickcanvas.h" +#include "qquickwindow.h" +#include "qquicktextutil_p.h" #include + +#include #include #include #include @@ -62,18 +65,16 @@ QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) -#ifdef QT_GUI_PASSWORD_ECHO_DELAY -static const int qt_passwordEchoDelay = QT_GUI_PASSWORD_ECHO_DELAY; -#endif - /*! - \qmlclass TextInput QQuickTextInput + \qmltype TextInput + \instantiates QQuickTextInput \inqmlmodule QtQuick 2 - \ingroup qml-basic-visual-elements - \brief The TextInput item displays an editable line of text. + \ingroup qtquick-visual + \ingroup qtquick-input \inherits Item + \brief Displays an editable line of text - The TextInput element displays a single line of editable plain text. + The TextInput type displays a single line of editable plain text. TextInput is used to accept a line of text input. Input constraints can be placed on a TextInput item (for example, through a \l validator or \l inputMask), @@ -105,8 +106,8 @@ void QQuickTextInput::componentComplete() d->checkIsValid(); d->updateLayout(); updateCursorRectangle(); - if (d->cursorComponent && d->cursorComponent->isReady()) - createCursor(); + if (d->cursorComponent && isCursorVisible()) + QQuickTextUtil::createCursor(d); } /*! @@ -128,11 +129,47 @@ void QQuickTextInput::setText(const QString &s) Q_D(QQuickTextInput); if (s == text()) return; - if (d->composeMode()) - qApp->inputMethod()->reset(); + + d->cancelPreedit(); d->internalSetText(s, -1, false); } + +/*! + \qmlproperty enumeration QtQuick2::TextInput::renderType + + Override the default rendering type for this component. + + Supported render types are: + \list + \li Text.QtRendering - the default + \li Text.NativeRendering + \endlist + + Select Text.NativeRendering if you prefer text to look native on the target platform and do + not require advanced features such as transformation of the text. Using such features in + combination with the NativeRendering render type will lend poor and sometimes pixelated + results. +*/ +QQuickTextInput::RenderType QQuickTextInput::renderType() const +{ + Q_D(const QQuickTextInput); + return d->renderType; +} + +void QQuickTextInput::setRenderType(QQuickTextInput::RenderType renderType) +{ + Q_D(QQuickTextInput); + if (d->renderType == renderType) + return; + + d->renderType = renderType; + emit renderTypeChanged(); + + if (isComponentComplete()) + d->updateLayout(); +} + /*! \qmlproperty int QtQuick2::TextInput::length @@ -470,16 +507,48 @@ bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment alignment, bo return false; } +Qt::LayoutDirection QQuickTextInputPrivate::textDirection() const +{ + QString text = m_text; + if (text.isEmpty()) + text = m_textLayout.preeditAreaText(); + + const QChar *character = text.constData(); + while (!character->isNull()) { + switch (character->direction()) { + case QChar::DirL: + return Qt::LeftToRight; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirAN: + return Qt::RightToLeft; + default: + break; + } + character++; + } + return Qt::LayoutDirectionAuto; +} + +Qt::LayoutDirection QQuickTextInputPrivate::layoutDirection() const +{ + Qt::LayoutDirection direction = m_layoutDirection; + if (direction == Qt::LayoutDirectionAuto) { + direction = textDirection(); + if (direction == Qt::LayoutDirectionAuto) + direction = qApp->inputMethod()->inputDirection(); + } + return (direction == Qt::LayoutDirectionAuto) ? Qt::LeftToRight : direction; +} + bool QQuickTextInputPrivate::determineHorizontalAlignment() { if (hAlignImplicit) { // if no explicit alignment has been set, follow the natural layout direction of the text - QString text = q_func()->text(); - if (text.isEmpty()) - text = m_textLayout.preeditAreaText(); - bool isRightToLeft = text.isEmpty() ? qApp->inputMethod()->inputDirection() == Qt::RightToLeft - : text.isRightToLeft(); - return setHAlign(isRightToLeft ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft); + Qt::LayoutDirection direction = textDirection(); + if (direction == Qt::LayoutDirectionAuto) + direction = qApp->inputMethod()->inputDirection(); + return setHAlign(direction == Qt::RightToLeft ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft); } return false; } @@ -615,7 +684,7 @@ void QQuickTextInput::setMaxLength(int ml) forward keys to it and you desire it to look active when this happens (but without actually giving it active focus). - It should not be set directly on the element, like in the below QML, + It should not be set directly on the item, like in the below QML, as the specified value will be overridden an lost on focus changes. \code @@ -640,9 +709,13 @@ void QQuickTextInput::setCursorVisible(bool on) if (d->cursorVisible == on) return; d->cursorVisible = on; - d->setCursorBlinkPeriod(on ? qApp->styleHints()->cursorFlashTime() : 0); - d->updateType = QQuickTextInputPrivate::UpdatePaintNode; - update(); + if (on && isComponentComplete()) + QQuickTextUtil::createCursor(d); + if (!d->cursorItem) { + d->setCursorBlinkPeriod(on ? qApp->styleHints()->cursorFlashTime() : 0); + d->updateType = QQuickTextInputPrivate::UpdatePaintNode; + update(); + } emit cursorVisibleChanged(d->cursorVisible); } @@ -678,9 +751,7 @@ QRectF QQuickTextInput::cursorRectangle() const { Q_D(const QQuickTextInput); - int c = d->m_cursor; - if (d->m_preeditCursor != -1) - c += d->m_preeditCursor; + int c = d->m_cursor + d->m_preeditCursor; if (d->m_echoMode == NoEcho) c = 0; QTextLine l = d->m_textLayout.lineForTextPosition(c); @@ -720,7 +791,7 @@ int QQuickTextInput::selectionEnd() const return d->lastSelectionEnd; } /*! - \qmlmethod void QtQuick2::TextInput::select(int start, int end) + \qmlmethod QtQuick2::TextInput::select(int start, int end) Causes the text from \a start to \a end to be selected. @@ -809,11 +880,13 @@ void QQuickTextInput::setAutoScroll(bool b) #ifndef QT_NO_VALIDATOR /*! - \qmlclass IntValidator QIntValidator + \qmltype IntValidator + \instantiates QIntValidator \inqmlmodule QtQuick 2 - \ingroup qml-basic-visual-elements + \ingroup qtquick-text-utility + \brief Defines a validator for integer values - This element provides a validator for integer values. + The IntValidator type provides a validator for integer values. If no \l locale is set IntValidator uses the \l {QLocale::setDefault()}{default locale} to interpret the number and will accept locale specific digits, group separators, and positive @@ -871,11 +944,13 @@ void QQuickIntValidator::resetLocaleName() */ /*! - \qmlclass DoubleValidator QDoubleValidator + \qmltype DoubleValidator + \instantiates QDoubleValidator \inqmlmodule QtQuick 2 - \ingroup qml-basic-visual-elements + \ingroup qtquick-text-utility + \brief Defines a validator for non-integer numbers - This element provides a validator for non-integer numbers. + The DoubleValidator type provides a validator for non-integer numbers. Input is accepted if it contains a double that is within the valid range and is in the correct format. @@ -963,11 +1038,13 @@ void QQuickDoubleValidator::resetLocaleName() */ /*! - \qmlclass RegExpValidator QRegExpValidator + \qmltype RegExpValidator + \instantiates QRegExpValidator \inqmlmodule QtQuick 2 - \ingroup qml-basic-visual-elements + \ingroup qtquick-text-utility + \brief Provides a string validator - This element provides a validator, which counts as valid any string which + The RegExpValidator type provides a validator, which counts as valid any string which matches a specified regular expression. */ /*! @@ -1016,14 +1093,32 @@ void QQuickTextInput::setValidator(QValidator* v) if (d->m_validator == v) return; + if (d->m_validator) { + qmlobject_disconnect( + d->m_validator, QValidator, SIGNAL(changed()), + this, QQuickTextInput, SLOT(q_validatorChanged())); + } + d->m_validator = v; + if (d->m_validator) { + qmlobject_connect( + d->m_validator, QValidator, SIGNAL(changed()), + this, QQuickTextInput, SLOT(q_validatorChanged())); + } + if (isComponentComplete()) d->checkIsValid(); emit validatorChanged(); } +void QQuickTextInput::q_validatorChanged() +{ + Q_D(QQuickTextInput); + d->checkIsValid(); +} + #endif // QT_NO_VALIDATOR void QQuickTextInputPrivate::checkIsValid() @@ -1220,65 +1315,14 @@ QQmlComponent* QQuickTextInput::cursorDelegate() const void QQuickTextInput::setCursorDelegate(QQmlComponent* c) { Q_D(QQuickTextInput); - if (d->cursorComponent == c) - return; - - d->cursorComponent = c; - if (!c) { - //note that the components are owned by something else - delete d->cursorItem; - d->cursorItem = 0; - } else { - d->startCreatingCursor(); - } - - emit cursorDelegateChanged(); -} - -void QQuickTextInputPrivate::startCreatingCursor() -{ - Q_Q(QQuickTextInput); - if (cursorComponent->isReady()) { - q->createCursor(); - } else if (cursorComponent->isLoading()) { - q->connect(cursorComponent, SIGNAL(statusChanged(int)), - q, SLOT(createCursor())); - } else { // isError - qmlInfo(q, cursorComponent->errors()) << QQuickTextInput::tr("Could not load cursor delegate"); - } + QQuickTextUtil::setCursorDelegate(d, c); } void QQuickTextInput::createCursor() { Q_D(QQuickTextInput); - if (!isComponentComplete()) - return; - - if (d->cursorComponent->isError()) { - qmlInfo(this, d->cursorComponent->errors()) << tr("Could not load cursor delegate"); - return; - } - - if (!d->cursorComponent->isReady()) - return; - - if (d->cursorItem) - delete d->cursorItem; - QQmlContext *creationContext = d->cursorComponent->creationContext(); - QObject *object = d->cursorComponent->create(creationContext ? creationContext : qmlContext(this)); - d->cursorItem = qobject_cast(object); - if (!d->cursorItem) { - delete object; - qmlInfo(this, d->cursorComponent->errors()) << tr("Could not instantiate cursor delegate"); - return; - } - - QRectF r = cursorRectangle(); - - QQml_setParent_noEvent(d->cursorItem, this); - d->cursorItem->setParentItem(this); - d->cursorItem->setPos(r.topLeft()); - d->cursorItem->setHeight(r.height()); + d->cursorPending = true; + QQuickTextUtil::createCursor(d); } /*! @@ -1387,7 +1431,7 @@ void QQuickTextInput::keyPressEvent(QKeyEvent* ev) int cursorPosition = d->m_cursor; if (cursorPosition == 0) ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Left : Qt::Key_Right); - if (cursorPosition == text().length()) + if (!ignore && cursorPosition == text().length()) ignore = ev->key() == (d->layoutDirection() == Qt::LeftToRight ? Qt::Key_Right : Qt::Key_Left); } if (ignore) { @@ -1402,7 +1446,7 @@ void QQuickTextInput::keyPressEvent(QKeyEvent* ev) void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev) { Q_D(QQuickTextInput); - const bool wasComposing = d->preeditAreaText().length() > 0; + const bool wasComposing = d->hasImState; if (d->m_readOnly) { ev->ignore(); } else { @@ -1411,7 +1455,7 @@ void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev) if (!ev->isAccepted()) QQuickImplicitSizeItem::inputMethodEvent(ev); - if (wasComposing != (d->m_textLayout.preeditAreaText().length() > 0)) + if (wasComposing != d->hasImState) emit inputMethodComposingChanged(); } @@ -1465,7 +1509,7 @@ void QQuickTextInput::mousePressEvent(QMouseEvent *event) forceActiveFocus(); // re-open input panel on press if already focused if (hasActiveFocus() && hadActiveFocus && !d->m_readOnly) - openSoftwareInputPanel(); + qGuiApp->inputMethod()->show(); } event->setAccepted(true); @@ -1602,7 +1646,7 @@ void QQuickTextInput::geometryChanged(const QRectF &newGeometry, { Q_D(QQuickTextInput); if (!d->inLayout) { - if (newGeometry.width() != oldGeometry.width() && d->wrapMode != NoWrap) + if (newGeometry.width() != oldGeometry.width()) d->updateLayout(); updateCursorRectangle(); } @@ -1660,23 +1704,13 @@ void QQuickTextInputPrivate::updateVerticalScroll() Q_Q(QQuickTextInput); const int preeditLength = m_textLayout.preeditAreaText().length(); const qreal height = qMax(0, q->height()); - qreal heightUsed = boundingRect.height(); + qreal heightUsed = contentSize.height(); qreal previousScroll = vscroll; if (!autoScroll || heightUsed <= height) { // text fits in br; use vscroll for alignment - switch (vAlign & ~(Qt::AlignAbsolute|Qt::AlignHorizontal_Mask)) { - case Qt::AlignBottom: - vscroll = heightUsed - height; - break; - case Qt::AlignVCenter: - vscroll = (heightUsed - height) / 2; - break; - default: - // Top - vscroll = 0; - break; - } + vscroll = -QQuickTextUtil::alignedY( + heightUsed, height, vAlign & ~(Qt::AlignAbsolute|Qt::AlignHorizontal_Mask)); } else { QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor + preeditLength); QRectF r = currentLine.isValid() ? currentLine.rect() : QRectF(); @@ -1733,18 +1767,19 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this); d->textNode = node; - if (!d->textLayoutDirty) { + if (!d->textLayoutDirty && oldNode != 0) { QSGSimpleRectNode *cursorNode = node->cursorNode(); if (cursorNode != 0 && !isReadOnly()) { cursorNode->setRect(cursorRectangle()); - if (!d->cursorVisible || (!d->m_blinkStatus && d->m_blinkPeriod > 0)) { + if (!d->cursorVisible || d->cursorItem || (!d->m_blinkStatus && d->m_blinkPeriod > 0)) { d->hideCursor(); } else { d->showCursor(); } } } else { + node->setUseNativeRenderer(d->renderType == QQuickTextInput::NativeRendering); node->deleteContent(); node->setMatrix(QMatrix4x4()); @@ -1818,7 +1853,7 @@ QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property) const } /*! - \qmlmethod void QtQuick2::TextInput::deselect() + \qmlmethod QtQuick2::TextInput::deselect() Removes active text selection. */ @@ -1829,7 +1864,7 @@ void QQuickTextInput::deselect() } /*! - \qmlmethod void QtQuick2::TextInput::selectAll() + \qmlmethod QtQuick2::TextInput::selectAll() Causes all text to be selected. */ @@ -1840,7 +1875,7 @@ void QQuickTextInput::selectAll() } /*! - \qmlmethod void QtQuick2::TextInput::isRightToLeft(int start, int end) + \qmlmethod QtQuick2::TextInput::isRightToLeft(int start, int end) Returns true if the natural reading direction of the editor text found between positions \a start and \a end is right to left. @@ -1864,8 +1899,10 @@ bool QQuickTextInput::isRightToLeft(int start, int end) void QQuickTextInput::cut() { Q_D(QQuickTextInput); - d->copy(); - d->del(); + if (!d->m_readOnly) { + d->copy(); + d->del(); + } } /*! @@ -1893,6 +1930,8 @@ void QQuickTextInput::paste() #endif // QT_NO_CLIPBOARD /*! + \qmlmethod QtQuick2::TextInput::undo() + Undoes the last operation if undo is \l {canUndo}{available}. Deselects any current selection, and updates the selection start to the current cursor position. @@ -1908,6 +1947,8 @@ void QQuickTextInput::undo() } /*! + \qmlmethod QtQuick2::TextInput::redo() + Redoes the last operation if redo is \l {canRedo}{available}. */ @@ -1921,7 +1962,7 @@ void QQuickTextInput::redo() } /*! - \qmlmethod void QtQuick2::TextInput::insert(int position, string text) + \qmlmethod QtQuick2::TextInput::insert(int position, string text) Inserts \a text into the TextInput at position. */ @@ -1929,11 +1970,11 @@ void QQuickTextInput::redo() void QQuickTextInput::insert(int position, const QString &text) { Q_D(QQuickTextInput); -#ifdef QT_GUI_PASSWORD_ECHO_DELAY - if (d->m_echoMode == QQuickTextInput::Password) - d->m_passwordEchoTimer.start(qt_passwordEchoDelay, this); -#endif - + if (d->m_echoMode == QQuickTextInput::Password) { + int delay = qGuiApp->styleHints()->passwordMaskDelay(); + if (delay > 0) + d->m_passwordEchoTimer.start(delay, this); + } if (position < 0 || position > d->m_text.length()) return; @@ -2073,7 +2114,7 @@ void QQuickTextInput::remove(int start, int end) /*! - \qmlmethod void QtQuick2::TextInput::selectWord() + \qmlmethod QtQuick2::TextInput::selectWord() Causes the word closest to the current cursor position to be selected. */ @@ -2084,20 +2125,6 @@ void QQuickTextInput::selectWord() } /*! - \qmlproperty bool QtQuick2::TextInput::smooth - - This property holds whether the text is smoothly scaled or transformed. - - Smooth filtering gives better visual quality, but is slower. If - the item is displayed at its natural size, this property has no visual or - performance effect. - - \note Generally scaling artifacts are only visible if the item is stationary on - the screen. A common pattern when animating an item is to disable smooth - filtering at the beginning of the animation and reenable it at the conclusion. -*/ - -/*! \qmlproperty string QtQuick2::TextInput::passwordCharacter This is the character displayed when echoMode is set to Password or @@ -2166,7 +2193,7 @@ void QQuickTextInput::setSelectByMouse(bool on) } /*! - \qmlproperty enum QtQuick2::TextInput::mouseSelectionMode + \qmlproperty enumeration QtQuick2::TextInput::mouseSelectionMode Specifies how text should be selected using a mouse. @@ -2215,6 +2242,7 @@ void QQuickTextInput::setPersistentSelection(bool on) emit persistentSelectionChanged(); } +#ifndef QT_NO_CLIPBOARD /*! \qmlproperty bool QtQuick2::TextInput::canPaste @@ -2231,6 +2259,7 @@ bool QQuickTextInput::canPaste() const } return d->canPaste; } +#endif /*! \qmlproperty bool QtQuick2::TextInput::canUndo @@ -2268,7 +2297,7 @@ bool QQuickTextInput::canRedo() const qreal QQuickTextInput::contentWidth() const { Q_D(const QQuickTextInput); - return d->boundingRect.width(); + return d->contentSize.width(); } /*! @@ -2281,7 +2310,7 @@ qreal QQuickTextInput::contentWidth() const qreal QQuickTextInput::contentHeight() const { Q_D(const QQuickTextInput); - return d->boundingRect.height(); + return d->contentSize.height(); } void QQuickTextInput::moveCursorSelection(int position) @@ -2291,7 +2320,7 @@ void QQuickTextInput::moveCursorSelection(int position) } /*! - \qmlmethod void QtQuick2::TextInput::moveCursorSelection(int position, SelectionMode mode = TextInput.SelectCharacters) + \qmlmethod QtQuick2::TextInput::moveCursorSelection(int position, SelectionMode mode = TextInput.SelectCharacters) Moves the cursor to \a position and updates the selection according to the optional \a mode parameter. (To only move the cursor, set the \l cursorPosition property.) @@ -2349,8 +2378,8 @@ void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode) finder.setPosition(anchor); const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); - if (anchor < text.length() && (!(reasons & QTextBoundaryFinder::StartWord) - || ((reasons & QTextBoundaryFinder::EndWord) && anchor > cursor))) { + if (anchor < text.length() && (reasons == QTextBoundaryFinder::NotAtBoundary + || (reasons & QTextBoundaryFinder::EndOfItem))) { finder.toPreviousBoundary(); } anchor = finder.position() != -1 ? finder.position() : 0; @@ -2367,11 +2396,10 @@ void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode) finder.setPosition(anchor); const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); - if (anchor > 0 && (!(reasons & QTextBoundaryFinder::EndWord) - || ((reasons & QTextBoundaryFinder::StartWord) && anchor < cursor))) { + if (anchor > 0 && (reasons == QTextBoundaryFinder::NotAtBoundary + || (reasons & QTextBoundaryFinder::StartOfItem))) { finder.toNextBoundary(); } - anchor = finder.position() != -1 ? finder.position() : text.length(); finder.setPosition(pos); @@ -2384,97 +2412,11 @@ void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode) } } -/*! - \qmlmethod void QtQuick2::TextInput::openSoftwareInputPanel() - - Opens software input panels like virtual keyboards for typing, useful for - customizing when you want the input keyboard to be shown and hidden in - your application. - - By default the opening of input panels follows the platform style. Input panels are - always closed if no editor has active focus. - - You can disable the automatic behavior by setting the property \c activeFocusOnPress to false - and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement - the behavior you want. - - Only relevant on platforms, which provide virtual keyboards. - - \qml - import QtQuick 2.0 - TextInput { - id: textInput - text: "Hello world!" - activeFocusOnPress: false - MouseArea { - anchors.fill: parent - onClicked: { - if (!textInput.activeFocus) { - textInput.forceActiveFocus() - textInput.openSoftwareInputPanel(); - } else { - textInput.focus = false; - } - } - onPressAndHold: textInput.closeSoftwareInputPanel(); - } - } - \endqml -*/ -void QQuickTextInput::openSoftwareInputPanel() -{ - if (qGuiApp) - qGuiApp->inputMethod()->show(); -} - -/*! - \qmlmethod void QtQuick2::TextInput::closeSoftwareInputPanel() - - Closes a software input panel like a virtual keyboard shown on the screen, useful - for customizing when you want the input keyboard to be shown and hidden in - your application. - - By default the opening of input panels follows the platform style. Input panels are - always closed if no editor has active focus. - - You can disable the automatic behavior by setting the property \c activeFocusOnPress to false - and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement - the behavior you want. - - Only relevant on platforms, which provide virtual keyboards. - - \qml - import QtQuick 2.0 - TextInput { - id: textInput - text: "Hello world!" - activeFocusOnPress: false - MouseArea { - anchors.fill: parent - onClicked: { - if (!textInput.activeFocus) { - textInput.forceActiveFocus(); - textInput.openSoftwareInputPanel(); - } else { - textInput.focus = false; - } - } - onPressAndHold: textInput.closeSoftwareInputPanel(); - } - } - \endqml -*/ -void QQuickTextInput::closeSoftwareInputPanel() -{ - if (qGuiApp) - qGuiApp->inputMethod()->hide(); -} - void QQuickTextInput::focusInEvent(QFocusEvent *event) { Q_D(const QQuickTextInput); if (d->focusOnPress && !d->m_readOnly) - openSoftwareInputPanel(); + qGuiApp->inputMethod()->show(); QQuickImplicitSizeItem::focusInEvent(event); } @@ -2483,12 +2425,8 @@ void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value) Q_D(QQuickTextInput); if (change == ItemActiveFocusHasChanged) { bool hasFocus = value.boolValue; - setCursorVisible(hasFocus); // ### refactor: && d->canvas && d->canvas->hasFocus() -#ifdef QT_GUI_PASSWORD_ECHO_DELAY + setCursorVisible(hasFocus); if (!hasFocus && (d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive())) { -#else - if (!hasFocus && d->m_passwordEchoEditing) { -#endif d->updatePasswordEchoEditing(false);//QQuickTextInputPrivate sets it on key events, but doesn't deal with focus events } @@ -2521,14 +2459,19 @@ void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value) bool QQuickTextInput::isInputMethodComposing() const { Q_D(const QQuickTextInput); - return d->preeditAreaText().length() > 0; + return d->hasImState; } void QQuickTextInputPrivate::init() { Q_Q(QQuickTextInput); - q->setSmooth(smooth); - q->setAcceptedMouseButtons(Qt::LeftButton); +#ifndef QT_NO_CLIPBOARD + if (QGuiApplication::clipboard()->supportsSelection()) + q->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton); + else +#endif + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QQuickItem::ItemAcceptsInputMethod); q->setFlag(QQuickItem::ItemHasContents); #ifndef QT_NO_CLIPBOARD @@ -2542,7 +2485,7 @@ void QQuickTextInputPrivate::init() if (!qmlDisableDistanceField()) { QTextOption option = m_textLayout.textOption(); - option.setUseDesignMetrics(true); + option.setUseDesignMetrics(renderType != QQuickTextInput::NativeRendering); m_textLayout.setTextOption(option); } } @@ -2604,10 +2547,26 @@ QRectF QQuickTextInput::boundingRect() const { Q_D(const QQuickTextInput); + int cursorWidth = d->cursorItem ? 0 : 1; + + qreal hscroll = d->hscroll; + if (!d->autoScroll || d->contentSize.width() < width()) + hscroll -= QQuickTextUtil::alignedX(d->contentSize.width(), width(), effectiveHAlign()); + + // Could include font max left/right bearings to either side of rectangle. + QRectF r(-hscroll, -d->vscroll, d->contentSize.width(), d->contentSize.height()); + r.setRight(r.right() + cursorWidth); + return r; +} + +QRectF QQuickTextInput::clipRect() const +{ + Q_D(const QQuickTextInput); + int cursorWidth = d->cursorItem ? d->cursorItem->width() : 1; // Could include font max left/right bearings to either side of rectangle. - QRectF r = QQuickImplicitSizeItem::boundingRect(); + QRectF r = QQuickImplicitSizeItem::clipRect(); r.setRight(r.right() + cursorWidth); return r; } @@ -2660,7 +2619,6 @@ void QQuickTextInputPrivate::updateDisplayText(bool forceUpdate) if (m_echoMode == QQuickTextInput::Password) { str.fill(m_passwordCharacter); -#ifdef QT_GUI_PASSWORD_ECHO_DELAY if (m_passwordEchoTimer.isActive() && m_cursor > 0 && m_cursor <= m_text.length()) { int cursor = m_cursor - 1; QChar uc = m_text.at(cursor); @@ -2673,7 +2631,6 @@ void QQuickTextInputPrivate::updateDisplayText(bool forceUpdate) str[cursor - 1] = uc; } } -#endif } else if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) { str.fill(m_passwordCharacter); } @@ -2736,7 +2693,6 @@ void QQuickTextInputPrivate::updateLayout() if (!q->isComponentComplete()) return; - const QRectF previousRect = boundingRect; QTextOption option = m_textLayout.textOption(); option.setTextDirection(layoutDirection()); @@ -2745,8 +2701,8 @@ void QQuickTextInputPrivate::updateLayout() m_textLayout.setTextOption(option); m_textLayout.setFont(font); - boundingRect = QRectF(); m_textLayout.beginLayout(); + QTextLine line = m_textLayout.createLine(); if (requireImplicitWidth) { line.setLineWidth(INT_MAX); @@ -2759,12 +2715,14 @@ void QQuickTextInputPrivate::updateLayout() } qreal lineWidth = q->widthValid() ? q->width() : INT_MAX; qreal height = 0; + qreal width = 0; do { line.setLineWidth(lineWidth); - line.setPosition(QPointF(line.position().x(), height)); - boundingRect = boundingRect.united(line.naturalTextRect()); + line.setPosition(QPointF(0, height)); height += line.height(); + width = qMax(width, line.naturalTextWidth()); + line = m_textLayout.createLine(); } while (line.isValid()); m_textLayout.endLayout(); @@ -2774,15 +2732,18 @@ void QQuickTextInputPrivate::updateLayout() textLayoutDirty = true; + const QSizeF previousSize = contentSize; + contentSize = QSizeF(width, height); + updateType = UpdatePaintNode; q->update(); if (!requireImplicitWidth && !q->widthValid()) - q->setImplicitSize(qCeil(boundingRect.width()), qCeil(boundingRect.height())); + q->setImplicitSize(width, height); else - q->setImplicitHeight(qCeil(boundingRect.height())); + q->setImplicitHeight(height); - if (previousRect != boundingRect) + if (previousSize != contentSize) emit q->contentSizeChanged(); } @@ -2830,18 +2791,31 @@ void QQuickTextInputPrivate::paste(QClipboard::Mode clipboardMode) */ void QQuickTextInputPrivate::commitPreedit() { - if (!composeMode()) + Q_Q(QQuickTextInput); + + if (!hasImState) return; qApp->inputMethod()->commit(); - if (!composeMode()) + if (!hasImState) + return; + + QInputMethodEvent ev; + QCoreApplication::sendEvent(q, &ev); +} + +void QQuickTextInputPrivate::cancelPreedit() +{ + Q_Q(QQuickTextInput); + + if (!hasImState) return; - m_preeditCursor = 0; - m_textLayout.setPreeditArea(-1, QString()); - m_textLayout.clearAdditionalFormats(); - updateLayout(); + qApp->inputMethod()->reset(); + + QInputMethodEvent ev; + QCoreApplication::sendEvent(q, &ev); } /*! @@ -2856,7 +2830,7 @@ void QQuickTextInputPrivate::commitPreedit() void QQuickTextInputPrivate::backspace() { int priorState = m_undoState; - if (hasSelectedText()) { + if (separateSelection()) { removeSelectedText(); } else if (m_cursor) { --m_cursor; @@ -2889,7 +2863,7 @@ void QQuickTextInputPrivate::backspace() void QQuickTextInputPrivate::del() { int priorState = m_undoState; - if (hasSelectedText()) { + if (separateSelection()) { removeSelectedText(); } else { int n = m_textLayout.nextCursorPosition(m_cursor) - m_cursor; @@ -2909,7 +2883,8 @@ void QQuickTextInputPrivate::del() void QQuickTextInputPrivate::insert(const QString &newText) { int priorState = m_undoState; - removeSelectedText(); + if (separateSelection()) + removeSelectedText(); internalInsert(newText); finishChange(priorState); } @@ -2922,6 +2897,7 @@ void QQuickTextInputPrivate::insert(const QString &newText) void QQuickTextInputPrivate::clear() { int priorState = m_undoState; + separateSelection(); m_selstart = 0; m_selend = m_text.length(); removeSelectedText(); @@ -3072,6 +3048,7 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event) if (isGettingInput) { // If any text is being input, remove selected text. priorState = m_undoState; + separateSelection(); if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) { updatePasswordEchoEditing(true); m_selstart = 0; @@ -3124,14 +3101,17 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event) #endif //QT_NO_IM const int oldPreeditCursor = m_preeditCursor; m_preeditCursor = event->preeditString().length(); - m_hideCursor = false; + hasImState = !event->preeditString().isEmpty(); + bool cursorVisible = true; QList formats; for (int i = 0; i < event->attributes().size(); ++i) { const QInputMethodEvent::Attribute &a = event->attributes().at(i); if (a.type == QInputMethodEvent::Cursor) { + hasImState = true; m_preeditCursor = a.start; - m_hideCursor = !a.length; + cursorVisible = a.length != 0; } else if (a.type == QInputMethodEvent::TextFormat) { + hasImState = true; QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); if (f.isValid()) { QTextLayout::FormatRange o; @@ -3145,15 +3125,17 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event) m_textLayout.setAdditionalFormats(formats); updateDisplayText(/*force*/ true); - if (cursorPositionChanged) { - emitCursorPositionChanged(); - } else if (m_preeditCursor != oldPreeditCursor) { + if ((cursorPositionChanged && !emitCursorPositionChanged()) + || m_preeditCursor != oldPreeditCursor + || isGettingInput) { q->updateCursorRectangle(); } if (isGettingInput) finishChange(priorState); + q->setCursorVisible(cursorVisible); + if (selectionChange) { emit q->selectionChanged(); q->updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorPosition @@ -3259,7 +3241,7 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo emit q->selectionChanged(); } - inputMethodAttributesChanged |= (m_cursor == m_lastCursorPos); + inputMethodAttributesChanged |= (m_cursor != m_lastCursorPos); if (inputMethodAttributesChanged) q->updateInputMethod(); emitUndoRedoChanged(); @@ -3333,13 +3315,13 @@ void QQuickTextInputPrivate::addCommand(const Command &cmd) */ void QQuickTextInputPrivate::internalInsert(const QString &s) { -#ifdef QT_GUI_PASSWORD_ECHO_DELAY Q_Q(QQuickTextInput); - if (m_echoMode == QQuickTextInput::Password) - m_passwordEchoTimer.start(qt_passwordEchoDelay, q); -#endif - if (hasSelectedText()) - addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); + if (m_echoMode == QQuickTextInput::Password) { + int delay = qGuiApp->styleHints()->passwordMaskDelay(); + if (delay > 0) + m_passwordEchoTimer.start(delay, q); + } + Q_ASSERT(!hasSelectedText()); // insert(), processInputMethodEvent() call removeSelectedText() first. if (m_maskData) { QString ms = maskString(m_cursor, s); for (int i = 0; i < (int) ms.length(); ++i) { @@ -3376,8 +3358,7 @@ void QQuickTextInputPrivate::internalDelete(bool wasBackspace) { if (m_cursor < (int) m_text.length()) { cancelPasswordEchoTimer(); - if (hasSelectedText()) - addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); + Q_ASSERT(!hasSelectedText()); // del(), backspace() call removeSelectedText() first. addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)), m_cursor, m_text.at(m_cursor), -1, -1)); if (m_maskData) { @@ -3403,9 +3384,7 @@ void QQuickTextInputPrivate::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 @@ -3434,6 +3413,25 @@ void QQuickTextInputPrivate::removeSelectedText() /*! \internal + Adds the current selection to the undo history. + + Returns true if there is a current selection and false otherwise. +*/ + +bool QQuickTextInputPrivate::separateSelection() +{ + if (hasSelectedText()) { + separate(); + addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); + return true; + } else { + return false; + } +} + +/*! + \internal + Parses the input mask specified by \a maskFields to generate the mask data used to handle input masks. */ @@ -3832,11 +3830,14 @@ void QQuickTextInputPrivate::internalUndo(int until) } 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)) + if (next.type != cmd.type + && next.type < RemoveSelection + && (cmd.type < RemoveSelection || next.type == Separator)) { break; + } } } + separate(); m_textDirty = true; } @@ -3874,9 +3875,12 @@ void QQuickTextInputPrivate::internalRedo() } 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)) + if (next.type != cmd.type + && cmd.type < RemoveSelection + && next.type != Separator + && (next.type < RemoveSelection || cmd.type == Separator)) { break; + } } } m_textDirty = true; @@ -3962,27 +3966,22 @@ void QQuickTextInput::timerEvent(QTimerEvent *event) d->m_blinkStatus = !d->m_blinkStatus; d->updateType = QQuickTextInputPrivate::UpdatePaintNode; update(); -#ifdef QT_GUI_PASSWORD_ECHO_DELAY } else if (event->timerId() == d->m_passwordEchoTimer.timerId()) { d->m_passwordEchoTimer.stop(); d->updateDisplayText(); -#endif + updateCursorRectangle(); } } void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) { Q_Q(QQuickTextInput); - bool inlineCompletionAccepted = false; if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { if (hasAcceptableInput(m_text) || fixup()) { emit q->accepted(); } - if (inlineCompletionAccepted) - event->accept(); - else - event->ignore(); + event->ignore(); return; } @@ -4032,11 +4031,8 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) } } else if (event == QKeySequence::DeleteEndOfLine) { - if (!m_readOnly) { - setSelection(m_cursor, end()); - copy(); - del(); - } + if (!m_readOnly) + deleteEndOfLine(); } #endif //QT_NO_CLIPBOARD else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) { @@ -4101,16 +4097,12 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) del(); } else if (event == QKeySequence::DeleteEndOfWord) { - if (!m_readOnly) { - cursorWordForward(true); - del(); - } + if (!m_readOnly) + deleteEndOfWord(); } else if (event == QKeySequence::DeleteStartOfWord) { - if (!m_readOnly) { - cursorWordBackward(true); - del(); - } + if (!m_readOnly) + deleteStartOfWord(); } #endif // QT_NO_SHORTCUT else { @@ -4118,10 +4110,8 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) if (event->modifiers() & Qt::ControlModifier) { switch (event->key()) { case Qt::Key_Backspace: - if (!m_readOnly) { - cursorWordBackward(true); - del(); - } + if (!m_readOnly) + deleteStartOfWord(); break; default: if (!handled) @@ -4161,6 +4151,58 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) event->accept(); } +/*! + \internal + + Deletes the portion of the word before the current cursor position. +*/ + +void QQuickTextInputPrivate::deleteStartOfWord() +{ + int priorState = m_undoState; + Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend); + separate(); + cursorWordBackward(true); + addCommand(cmd); + removeSelectedText(); + finishChange(priorState); +} + +/*! + \internal + + Deletes the portion of the word after the current cursor position. +*/ + +void QQuickTextInputPrivate::deleteEndOfWord() +{ + int priorState = m_undoState; + Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend); + separate(); + cursorWordForward(true); + // moveCursor (sometimes) calls separate() so we need to add the command after that so the + // cursor position and selection are restored in the same undo operation as the remove. + addCommand(cmd); + removeSelectedText(); + finishChange(priorState); +} + +/*! + \internal + + Deletes all text from the cursor position to the end of the line. +*/ + +void QQuickTextInputPrivate::deleteEndOfLine() +{ + int priorState = m_undoState; + Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend); + separate(); + setSelection(m_cursor, end()); + addCommand(cmd); + removeSelectedText(); + finishChange(priorState); +} QT_END_NAMESPACE