diff options
Diffstat (limited to 'src/quick/items/qquicktextinput.cpp')
-rw-r--r-- | src/quick/items/qquicktextinput.cpp | 2007 |
1 files changed, 2007 insertions, 0 deletions
diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp new file mode 100644 index 0000000000..e9caaf2ee8 --- /dev/null +++ b/src/quick/items/qquicktextinput.cpp @@ -0,0 +1,2007 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktextinput_p.h" +#include "qquicktextinput_p_p.h" +#include "qquickcanvas.h" + +#include <private/qdeclarativeglobal_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qevent.h> +#include <QTextBoundaryFinder> +#include "qquicktextnode_p.h" +#include <QtQuick/qsgsimplerectnode.h> + +#include <QtGui/qstylehints.h> +#include <QtGui/qinputpanel.h> + +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) + +/*! + \qmlclass TextInput QQuickTextInput + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The TextInput item displays an editable line of text. + \inherits Item + + The TextInput element 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), + and setting \l echoMode to an appropriate value enables TextInput to be used for + a password input field. + + On Mac OS X, the Up/Down key bindings for Home/End are explicitly disabled. + If you want such bindings (on any platform), you will need to construct them in QML. + + \sa TextEdit, Text, {declarative/text/textselection}{Text Selection example} +*/ +QQuickTextInput::QQuickTextInput(QQuickItem* parent) +: QQuickImplicitSizeItem(*(new QQuickTextInputPrivate), parent) +{ + Q_D(QQuickTextInput); + d->init(); +} + +QQuickTextInput::~QQuickTextInput() +{ +} + +/*! + \qmlproperty string QtQuick2::TextInput::text + + The text in the TextInput. +*/ +QString QQuickTextInput::text() const +{ + Q_D(const QQuickTextInput); + return d->control->text(); +} + +void QQuickTextInput::setText(const QString &s) +{ + Q_D(QQuickTextInput); + if (s == text()) + return; + d->control->setText(s); +} + +/*! + \qmlproperty string QtQuick2::TextInput::font.family + + Sets the family name of the font. + + The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. + If the family isn't available a family will be set using the font matching algorithm. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.bold + + Sets whether the font weight is bold. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextInput::font.weight + + Sets the font's weight. + + The weight can be one of: + \list + \o Font.Light + \o Font.Normal - the default + \o Font.DemiBold + \o Font.Bold + \o Font.Black + \endlist + + \qml + TextInput { text: "Hello"; font.weight: Font.DemiBold } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.italic + + Sets whether the font has an italic style. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.underline + + Sets whether the text is underlined. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.strikeout + + Sets whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.pointSize + + Sets the font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int QtQuick2::TextInput::font.pixelSize + + Sets the font size in pixels. + + Using this function makes the font device dependent. + Use \c pointSize to set the size of the font in a device independent manner. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.letterSpacing + + Sets the letter spacing for the font. + + Letter spacing changes the default spacing between individual letters in the font. + A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.wordSpacing + + Sets the word spacing for the font. + + Word spacing changes the default spacing between individual words. + A positive value increases the word spacing by a corresponding amount of pixels, + while a negative value decreases the inter-word spacing accordingly. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextInput::font.capitalization + + Sets the capitalization for the text. + + \list + \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. + \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. + \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. + \o Font.SmallCaps - This alters the text to be rendered in small-caps type. + \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. + \endlist + + \qml + TextInput { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml +*/ + +QFont QQuickTextInput::font() const +{ + Q_D(const QQuickTextInput); + return d->sourceFont; +} + +void QQuickTextInput::setFont(const QFont &font) +{ + Q_D(QQuickTextInput); + if (d->sourceFont == font) + return; + + d->sourceFont = font; + QFont oldFont = d->font; + d->font = font; + if (d->font.pointSizeF() != -1) { + // 0.5pt resolution + qreal size = qRound(d->font.pointSizeF()*2.0); + d->font.setPointSizeF(size/2.0); + } + if (oldFont != d->font) { + d->control->setFont(d->font); + updateSize(); + updateCursorRectangle(); + if (d->cursorItem) { + d->cursorItem->setHeight(QFontMetrics(d->font).height()); + } + } + emit fontChanged(d->sourceFont); +} + +/*! + \qmlproperty color QtQuick2::TextInput::color + + The text color. +*/ +QColor QQuickTextInput::color() const +{ + Q_D(const QQuickTextInput); + return d->color; +} + +void QQuickTextInput::setColor(const QColor &c) +{ + Q_D(QQuickTextInput); + if (c != d->color) { + d->color = c; + update(); + emit colorChanged(c); + } +} + + +/*! + \qmlproperty color QtQuick2::TextInput::selectionColor + + The text highlight color, used behind selections. +*/ +QColor QQuickTextInput::selectionColor() const +{ + Q_D(const QQuickTextInput); + return d->selectionColor; +} + +void QQuickTextInput::setSelectionColor(const QColor &color) +{ + Q_D(QQuickTextInput); + if (d->selectionColor == color) + return; + + d->selectionColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::Highlight, d->selectionColor); + d->control->setPalette(p); + if (d->control->hasSelectedText()) + update(); + emit selectionColorChanged(color); +} +/*! + \qmlproperty color QtQuick2::TextInput::selectedTextColor + + The highlighted text color, used in selections. +*/ +QColor QQuickTextInput::selectedTextColor() const +{ + Q_D(const QQuickTextInput); + return d->selectedTextColor; +} + +void QQuickTextInput::setSelectedTextColor(const QColor &color) +{ + Q_D(QQuickTextInput); + if (d->selectedTextColor == color) + return; + + d->selectedTextColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::HighlightedText, d->selectedTextColor); + d->control->setPalette(p); + if (d->control->hasSelectedText()) + update(); + emit selectedTextColorChanged(color); +} + +/*! + \qmlproperty enumeration QtQuick2::TextInput::horizontalAlignment + \qmlproperty enumeration QtQuick2::TextInput::effectiveHorizontalAlignment + + Sets the horizontal alignment of the text within the TextInput item's + width and height. By default, the text alignment follows the natural alignment + of the text, for example text that is read from left to right will be aligned to + the left. + + TextInput does not have vertical alignment, as the natural height is + exactly the height of the single line of text. If you set the height + manually to something larger, TextInput will always be top aligned + vertically. You can use anchors to align it however you want within + another item. + + The valid values for \c horizontalAlignment are \c TextInput.AlignLeft, \c TextInput.AlignRight and + \c TextInput.AlignHCenter. + + When using the attached property LayoutMirroring::enabled to mirror application + layouts, the horizontal alignment of text will also be mirrored. However, the property + \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment + of TextInput, use the read-only property \c effectiveHorizontalAlignment. +*/ +QQuickTextInput::HAlignment QQuickTextInput::hAlign() const +{ + Q_D(const QQuickTextInput); + return d->hAlign; +} + +void QQuickTextInput::setHAlign(HAlignment align) +{ + Q_D(QQuickTextInput); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) { + updateCursorRectangle(); + } +} + +void QQuickTextInput::resetHAlign() +{ + Q_D(QQuickTextInput); + d->hAlignImplicit = true; + if (d->determineHorizontalAlignment() && isComponentComplete()) { + updateCursorRectangle(); + } +} + +QQuickTextInput::HAlignment QQuickTextInput::effectiveHAlign() const +{ + Q_D(const QQuickTextInput); + QQuickTextInput::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QQuickTextInput::AlignLeft: + effectiveAlignment = QQuickTextInput::AlignRight; + break; + case QQuickTextInput::AlignRight: + effectiveAlignment = QQuickTextInput::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QQuickTextInputPrivate::setHAlign(QQuickTextInput::HAlignment alignment, bool forceAlign) +{ + Q_Q(QQuickTextInput); + if ((hAlign != alignment || forceAlign) && alignment <= QQuickTextInput::AlignHCenter) { // justify not supported + QQuickTextInput::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + emit q->horizontalAlignmentChanged(alignment); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QQuickTextInputPrivate::determineHorizontalAlignment() +{ + if (hAlignImplicit) { + // if no explicit alignment has been set, follow the natural layout direction of the text + QString text = control->text(); + if (text.isEmpty()) + text = control->preeditAreaText(); + bool isRightToLeft = text.isEmpty() ? QGuiApplication::keyboardInputDirection() == Qt::RightToLeft : text.isRightToLeft(); + return setHAlign(isRightToLeft ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft); + } + return false; +} + +void QQuickTextInputPrivate::mirrorChange() +{ + Q_Q(QQuickTextInput); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QQuickTextInput::AlignRight || hAlign == QQuickTextInput::AlignLeft)) { + q->updateCursorRectangle(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +/*! + \qmlproperty bool QtQuick2::TextInput::readOnly + + Sets whether user input can modify the contents of the TextInput. + + If readOnly is set to true, then user input will not affect the text + property. Any bindings or attempts to set the text property will still + work. +*/ +bool QQuickTextInput::isReadOnly() const +{ + Q_D(const QQuickTextInput); + return d->control->isReadOnly(); +} + +void QQuickTextInput::setReadOnly(bool ro) +{ + Q_D(QQuickTextInput); + if (d->control->isReadOnly() == ro) + return; + + setFlag(QQuickItem::ItemAcceptsInputMethod, !ro); + d->control->setReadOnly(ro); + if (!ro) + d->control->setCursorPosition(d->control->end()); + + emit readOnlyChanged(ro); +} + +/*! + \qmlproperty int QtQuick2::TextInput::maximumLength + The maximum permitted length of the text in the TextInput. + + If the text is too long, it is truncated at the limit. + + By default, this property contains a value of 32767. +*/ +int QQuickTextInput::maxLength() const +{ + Q_D(const QQuickTextInput); + return d->control->maxLength(); +} + +void QQuickTextInput::setMaxLength(int ml) +{ + Q_D(QQuickTextInput); + if (d->control->maxLength() == ml) + return; + + d->control->setMaxLength(ml); + + emit maximumLengthChanged(ml); +} + +/*! + \qmlproperty bool QtQuick2::TextInput::cursorVisible + Set to true when the TextInput shows a cursor. + + This property is set and unset when the TextInput gets active focus, so that other + properties can be bound to whether the cursor is currently showing. As it + gets set and unset automatically, when you set the value yourself you must + keep in mind that your value may be overwritten. + + It can be set directly in script, for example if a KeyProxy might + 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, + as the specified value will be overridden an lost on focus changes. + + \code + TextInput { + text: "Text" + cursorVisible: false + } + \endcode + + In the above snippet the cursor will still become visible when the + TextInput gains active focus. +*/ +bool QQuickTextInput::isCursorVisible() const +{ + Q_D(const QQuickTextInput); + return d->cursorVisible; +} + +void QQuickTextInput::setCursorVisible(bool on) +{ + Q_D(QQuickTextInput); + if (d->cursorVisible == on) + return; + d->cursorVisible = on; + d->control->setCursorBlinkPeriod(on ? qApp->styleHints()->cursorFlashTime() : 0); + QRect r = d->control->cursorRect(); + if (d->control->inputMask().isEmpty()) + updateRect(r); + else + updateRect(); + emit cursorVisibleChanged(d->cursorVisible); +} + +/*! + \qmlproperty int QtQuick2::TextInput::cursorPosition + The position of the cursor in the TextInput. +*/ +int QQuickTextInput::cursorPosition() const +{ + Q_D(const QQuickTextInput); + return d->control->cursor(); +} +void QQuickTextInput::setCursorPosition(int cp) +{ + Q_D(QQuickTextInput); + if (cp < 0 || cp > d->control->text().length()) + return; + d->control->moveCursor(cp); +} + +/*! + Returns a Rect which encompasses the cursor, but which may be larger than is + required. Ignores custom cursor delegates. +*/ +QRect QQuickTextInput::cursorRectangle() const +{ + Q_D(const QQuickTextInput); + QRect r = d->control->cursorRect(); + // Scroll and make consistent with TextEdit + // QLineControl inexplicably adds 1 to the height and horizontal padding + // for unicode direction markers. + r.adjust(5 - d->hscroll, 0, -4 - d->hscroll, -1); + return r; +} +/*! + \qmlproperty int QtQuick2::TextInput::selectionStart + + The cursor position before the first character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + + \sa selectionEnd, cursorPosition, selectedText +*/ +int QQuickTextInput::selectionStart() const +{ + Q_D(const QQuickTextInput); + return d->lastSelectionStart; +} +/*! + \qmlproperty int QtQuick2::TextInput::selectionEnd + + The cursor position after the last character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + + \sa selectionStart, cursorPosition, selectedText +*/ +int QQuickTextInput::selectionEnd() const +{ + Q_D(const QQuickTextInput); + return d->lastSelectionEnd; +} +/*! + \qmlmethod void QtQuick2::TextInput::select(int start, int end) + + Causes the text from \a start to \a end to be selected. + + If either start or end is out of range, the selection is not changed. + + After calling this, selectionStart will become the lesser + and selectionEnd will become the greater (regardless of the order passed + to this method). + + \sa selectionStart, selectionEnd +*/ +void QQuickTextInput::select(int start, int end) +{ + Q_D(QQuickTextInput); + if (start < 0 || end < 0 || start > d->control->text().length() || end > d->control->text().length()) + return; + d->control->setSelection(start, end-start); +} + +/*! + \qmlproperty string QtQuick2::TextInput::selectedText + + This read-only property provides the text currently selected in the + text input. + + It is equivalent to the following snippet, but is faster and easier + to use. + + \js + myTextInput.text.toString().substring(myTextInput.selectionStart, + myTextInput.selectionEnd); + \endjs +*/ +QString QQuickTextInput::selectedText() const +{ + Q_D(const QQuickTextInput); + return d->control->selectedText(); +} + +/*! + \qmlproperty bool QtQuick2::TextInput::activeFocusOnPress + + Whether the TextInput should gain active focus on a mouse press. By default this is + set to true. +*/ +bool QQuickTextInput::focusOnPress() const +{ + Q_D(const QQuickTextInput); + return d->focusOnPress; +} + +void QQuickTextInput::setFocusOnPress(bool b) +{ + Q_D(QQuickTextInput); + if (d->focusOnPress == b) + return; + + d->focusOnPress = b; + + emit activeFocusOnPressChanged(d->focusOnPress); +} +/*! + \qmlproperty bool QtQuick2::TextInput::autoScroll + + Whether the TextInput should scroll when the text is longer than the width. By default this is + set to true. +*/ +bool QQuickTextInput::autoScroll() const +{ + Q_D(const QQuickTextInput); + return d->autoScroll; +} + +void QQuickTextInput::setAutoScroll(bool b) +{ + Q_D(QQuickTextInput); + if (d->autoScroll == b) + return; + + d->autoScroll = b; + //We need to repaint so that the scrolling is taking into account. + updateSize(true); + updateCursorRectangle(); + emit autoScrollChanged(d->autoScroll); +} + +#ifndef QT_NO_VALIDATOR + +/*! + \qmlclass IntValidator QIntValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator for integer values. + + IntValidator uses the \l {QLocale::setDefault()}{default locale} to interpret the number and + will accept locale specific digits, group separators, and positive and negative signs. In + addition, IntValidator is always guaranteed to accept a number formatted according to the "C" + locale. +*/ +/*! + \qmlproperty int QtQuick2::IntValidator::top + + This property holds the validator's highest acceptable value. + By default, this property's value is derived from the highest signed integer available (typically 2147483647). +*/ +/*! + \qmlproperty int QtQuick2::IntValidator::bottom + + This property holds the validator's lowest acceptable value. + By default, this property's value is derived from the lowest signed integer available (typically -2147483647). +*/ + +/*! + \qmlclass DoubleValidator QDoubleValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator for non-integer numbers. +*/ + +/*! + \qmlproperty real QtQuick2::DoubleValidator::top + + This property holds the validator's maximum acceptable value. + By default, this property contains a value of infinity. +*/ +/*! + \qmlproperty real QtQuick2::DoubleValidator::bottom + + This property holds the validator's minimum acceptable value. + By default, this property contains a value of -infinity. +*/ +/*! + \qmlproperty int QtQuick2::DoubleValidator::decimals + + This property holds the validator's maximum number of digits after the decimal point. + By default, this property contains a value of 1000. +*/ +/*! + \qmlproperty enumeration QtQuick2::DoubleValidator::notation + This property holds the notation of how a string can describe a number. + + The possible values for this property are: + + \list + \o DoubleValidator.StandardNotation + \o DoubleValidator.ScientificNotation (default) + \endlist + + If this property is set to DoubleValidator.ScientificNotation, the written number may have an exponent part (e.g. 1.5E-2). +*/ + +/*! + \qmlclass RegExpValidator QRegExpValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator, which counts as valid any string which + matches a specified regular expression. +*/ +/*! + \qmlproperty regExp QtQuick2::RegExpValidator::regExp + + This property holds the regular expression used for validation. + + Note that this property should be a regular expression in JS syntax, e.g /a/ for the regular expression + matching "a". + + By default, this property contains a regular expression with the pattern .* that matches any string. +*/ + +/*! + \qmlproperty Validator QtQuick2::TextInput::validator + + Allows you to set a validator on the TextInput. When a validator is set + the TextInput will only accept input which leaves the text property in + an acceptable or intermediate state. The accepted signal will only be sent + if the text is in an acceptable state when enter is pressed. + + Currently supported validators are IntValidator, DoubleValidator and + RegExpValidator. An example of using validators is shown below, which allows + input of integers between 11 and 31 into the text input: + + \code + import QtQuick 1.0 + TextInput{ + validator: IntValidator{bottom: 11; top: 31;} + focus: true + } + \endcode + + \sa acceptableInput, inputMask +*/ + +QValidator* QQuickTextInput::validator() const +{ + Q_D(const QQuickTextInput); + //###const cast isn't good, but needed for property system? + return const_cast<QValidator*>(d->control->validator()); +} + +void QQuickTextInput::setValidator(QValidator* v) +{ + Q_D(QQuickTextInput); + if (d->control->validator() == v) + return; + + d->control->setValidator(v); + if (!d->control->hasAcceptableInput()) { + d->oldValidity = false; + emit acceptableInputChanged(); + } + + emit validatorChanged(); +} +#endif // QT_NO_VALIDATOR + +/*! + \qmlproperty string QtQuick2::TextInput::inputMask + + Allows you to set an input mask on the TextInput, restricting the allowable + text inputs. See QLineEdit::inputMask for further details, as the exact + same mask strings are used by TextInput. + + \sa acceptableInput, validator +*/ +QString QQuickTextInput::inputMask() const +{ + Q_D(const QQuickTextInput); + return d->control->inputMask(); +} + +void QQuickTextInput::setInputMask(const QString &im) +{ + Q_D(QQuickTextInput); + if (d->control->inputMask() == im) + return; + + d->control->setInputMask(im); + emit inputMaskChanged(d->control->inputMask()); +} + +/*! + \qmlproperty bool QtQuick2::TextInput::acceptableInput + + This property is always true unless a validator or input mask has been set. + If a validator or input mask has been set, this property will only be true + if the current text is acceptable to the validator or input mask as a final + string (not as an intermediate string). +*/ +bool QQuickTextInput::hasAcceptableInput() const +{ + Q_D(const QQuickTextInput); + return d->control->hasAcceptableInput(); +} + +/*! + \qmlsignal QtQuick2::TextInput::onAccepted() + + This handler is called when the Return or Enter key is pressed. + Note that if there is a \l validator or \l inputMask set on the text + input, the handler will only be emitted if the input is in an acceptable + state. +*/ + +void QQuickTextInputPrivate::updateInputMethodHints() +{ + Q_Q(QQuickTextInput); + Qt::InputMethodHints hints = inputMethodHints; + uint echo = control->echoMode(); + if (echo == QQuickTextInput::Password || echo == QQuickTextInput::NoEcho) + hints |= Qt::ImhHiddenText; + else if (echo == QQuickTextInput::PasswordEchoOnEdit) + hints &= ~Qt::ImhHiddenText; + if (echo != QQuickTextInput::Normal) + hints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText); + q->setInputMethodHints(hints); +} +/*! + \qmlproperty enumeration QtQuick2::TextInput::echoMode + + Specifies how the text should be displayed in the TextInput. + \list + \o TextInput.Normal - Displays the text as it is. (Default) + \o TextInput.Password - Displays asterisks instead of characters. + \o TextInput.NoEcho - Displays nothing. + \o TextInput.PasswordEchoOnEdit - Displays characters as they are entered + while editing, otherwise displays asterisks. + \endlist +*/ +QQuickTextInput::EchoMode QQuickTextInput::echoMode() const +{ + Q_D(const QQuickTextInput); + return (QQuickTextInput::EchoMode)d->control->echoMode(); +} + +void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo) +{ + Q_D(QQuickTextInput); + if (echoMode() == echo) + return; + d->control->setEchoMode((QLineControl::EchoMode)echo); + d->updateInputMethodHints(); + q_textChanged(); + emit echoModeChanged(echoMode()); +} + +Qt::InputMethodHints QQuickTextInput::imHints() const +{ + Q_D(const QQuickTextInput); + return d->inputMethodHints; +} + +void QQuickTextInput::setIMHints(Qt::InputMethodHints hints) +{ + Q_D(QQuickTextInput); + if (d->inputMethodHints == hints) + return; + d->inputMethodHints = hints; + d->updateInputMethodHints(); +} + +/*! + \qmlproperty Component QtQuick2::TextInput::cursorDelegate + The delegate for the cursor in the TextInput. + + If you set a cursorDelegate for a TextInput, this delegate will be used for + drawing the cursor instead of the standard cursor. An instance of the + delegate will be created and managed by the TextInput when a cursor is + needed, and the x property of delegate instance will be set so as + to be one pixel before the top left of the current character. + + Note that the root item of the delegate component must be a QDeclarativeItem or + QDeclarativeItem derived item. +*/ +QDeclarativeComponent* QQuickTextInput::cursorDelegate() const +{ + Q_D(const QQuickTextInput); + return d->cursorComponent; +} + +void QQuickTextInput::setCursorDelegate(QDeclarativeComponent* 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; + } 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"); + } +} + +void QQuickTextInput::createCursor() +{ + Q_D(QQuickTextInput); + 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; + QDeclarativeContext *creationContext = d->cursorComponent->creationContext(); + QObject *object = d->cursorComponent->create(creationContext ? creationContext : qmlContext(this)); + d->cursorItem = qobject_cast<QQuickItem*>(object); + if (!d->cursorItem) { + delete object; + qmlInfo(this, d->cursorComponent->errors()) << tr("Could not instantiate cursor delegate"); + return; + } + + QDeclarative_setParent_noEvent(d->cursorItem, this); + d->cursorItem->setParentItem(this); + d->cursorItem->setX(d->control->cursorToX()); + d->cursorItem->setHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. +} + +/*! + \qmlmethod rect QtQuick2::TextInput::positionToRectangle(int pos) + + This function takes a character position and returns the rectangle that the + cursor would occupy, if it was placed at that character position. + + This is similar to setting the cursorPosition, and then querying the cursor + rectangle, but the cursorPosition is not changed. +*/ +QRectF QQuickTextInput::positionToRectangle(int pos) const +{ + Q_D(const QQuickTextInput); + if (pos > d->control->cursorPosition()) + pos += d->control->preeditAreaText().length(); + return QRectF(d->control->cursorToX(pos)-d->hscroll, + 0.0, + d->control->cursorWidth(), + cursorRectangle().height()); +} + +/*! + \qmlmethod int QtQuick2::TextInput::positionAt(int x, CursorPosition position = CursorBetweenCharacters) + + This function returns the character position at + x pixels from the left of the textInput. Position 0 is before the + first character, position 1 is after the first character but before the second, + and so on until position text.length, which is after all characters. + + This means that for all x values before the first character this function returns 0, + and for all x values after the last character this function returns text.length. + + The cursor position type specifies how the cursor position should be resolved. + + \list + \o TextInput.CursorBetweenCharacters - Returns the position between characters that is nearest x. + \o TextInput.CursorOnCharacter - Returns the position before the character that is nearest x. + \endlist +*/ +int QQuickTextInput::positionAt(int x) const +{ + return positionAt(x, CursorBetweenCharacters); +} + +int QQuickTextInput::positionAt(int x, CursorPosition position) const +{ + Q_D(const QQuickTextInput); + int pos = d->control->xToPos(x + d->hscroll, QTextLine::CursorPosition(position)); + const int cursor = d->control->cursor(); + if (pos > cursor) { + const int preeditLength = d->control->preeditAreaText().length(); + pos = pos > cursor + preeditLength + ? pos - preeditLength + : cursor; + } + return pos; +} + +void QQuickTextInput::keyPressEvent(QKeyEvent* ev) +{ + Q_D(QQuickTextInput); + // Don't allow MacOSX up/down support, and we don't allow a completer. + bool ignore = (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) && ev->modifiers() == Qt::NoModifier; + if (!ignore && (d->lastSelectionStart == d->lastSelectionEnd) && (ev->key() == Qt::Key_Right || ev->key() == Qt::Key_Left)) { + // Ignore when moving off the end unless there is a selection, + // because then moving will do something (deselect). + int cursorPosition = d->control->cursor(); + if (cursorPosition == 0) + ignore = ev->key() == (d->control->layoutDirection() == Qt::LeftToRight ? Qt::Key_Left : Qt::Key_Right); + if (cursorPosition == d->control->text().length()) + ignore = ev->key() == (d->control->layoutDirection() == Qt::LeftToRight ? Qt::Key_Right : Qt::Key_Left); + } + if (ignore) { + ev->ignore(); + } else { + d->control->processKeyEvent(ev); + } + if (!ev->isAccepted()) + QQuickImplicitSizeItem::keyPressEvent(ev); +} + +void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev) +{ + Q_D(QQuickTextInput); + const bool wasComposing = d->control->preeditAreaText().length() > 0; + if (d->control->isReadOnly()) { + ev->ignore(); + } else { + d->control->processInputMethodEvent(ev); + } + if (!ev->isAccepted()) + QQuickImplicitSizeItem::inputMethodEvent(ev); + + if (wasComposing != (d->control->preeditAreaText().length() > 0)) + emit inputMethodComposingChanged(); +} + +void QQuickTextInput::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QQuickTextInput); + + if (d->selectByMouse && event->button() == Qt::LeftButton) { + d->control->commitPreedit(); + int cursor = d->xToPos(event->localPos().x()); + d->control->selectWordAtPos(cursor); + event->setAccepted(true); + if (!d->hasPendingTripleClick()) { + d->tripleClickStartPoint = event->localPos().toPoint(); + d->tripleClickTimer.start(); + } + } else { + if (d->sendMouseEventToInputContext(event)) + return; + QQuickImplicitSizeItem::mouseDoubleClickEvent(event); + } +} + +void QQuickTextInput::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickTextInput); + + d->pressPos = event->localPos(); + + if (d->focusOnPress) { + bool hadActiveFocus = hasActiveFocus(); + forceActiveFocus(); + // re-open input panel on press if already focused + if (hasActiveFocus() && hadActiveFocus && !isReadOnly()) + openSoftwareInputPanel(); + } + if (d->selectByMouse) { + setKeepMouseGrab(false); + d->selectPressed = true; + QPoint distanceVector = d->pressPos.toPoint() - d->tripleClickStartPoint; + if (d->hasPendingTripleClick() + && distanceVector.manhattanLength() < qApp->styleHints()->startDragDistance()) { + event->setAccepted(true); + selectAll(); + return; + } + } + + if (d->sendMouseEventToInputContext(event)) + return; + + bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse; + int cursor = d->xToPos(event->localPos().x()); + d->control->moveCursor(cursor, mark); + event->setAccepted(true); +} + +void QQuickTextInput::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QQuickTextInput); + + if (d->selectPressed) { + if (qAbs(int(event->localPos().x() - d->pressPos.x())) > qApp->styleHints()->startDragDistance()) + setKeepMouseGrab(true); + + if (d->control->composeMode()) { + // start selection + int startPos = d->xToPos(d->pressPos.x()); + int currentPos = d->xToPos(event->localPos().x()); + if (startPos != currentPos) + d->control->setSelection(startPos, currentPos - startPos); + } else { + moveCursorSelection(d->xToPos(event->localPos().x()), d->mouseSelectionMode); + } + event->setAccepted(true); + } else { + QQuickImplicitSizeItem::mouseMoveEvent(event); + } +} + +void QQuickTextInput::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickTextInput); + if (d->sendMouseEventToInputContext(event)) + return; + if (d->selectPressed) { + d->selectPressed = false; + setKeepMouseGrab(false); + } + d->control->processEvent(event); + if (!event->isAccepted()) + QQuickImplicitSizeItem::mouseReleaseEvent(event); +} + +bool QQuickTextInputPrivate::sendMouseEventToInputContext(QMouseEvent *event) +{ +#if !defined QT_NO_IM + if (control->composeMode()) { + int tmp_cursor = xToPos(event->localPos().x()); + int mousePos = tmp_cursor - control->cursor(); + if (mousePos >= 0 && mousePos <= control->preeditAreaText().length()) { + if (event->type() == QEvent::MouseButtonRelease) { + qApp->inputPanel()->invokeAction(QInputPanel::Click, mousePos); + } + return true; + } + } +#else + Q_UNUSED(event); + Q_UNUSED(eventType) +#endif + + return false; +} + +void QQuickTextInput::mouseUngrabEvent() +{ + Q_D(QQuickTextInput); + d->selectPressed = false; + setKeepMouseGrab(false); +} + +bool QQuickTextInput::event(QEvent* ev) +{ + Q_D(QQuickTextInput); + //Anything we don't deal with ourselves, pass to the control + bool handled = false; + switch (ev->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease://###Should the control be doing anything with release? + case QEvent::InputMethod: + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + break; + default: + handled = d->control->processEvent(ev); + } + if (!handled) + handled = QQuickImplicitSizeItem::event(ev); + return handled; +} + +void QQuickTextInput::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.width() != oldGeometry.width()) { + updateSize(); + updateCursorRectangle(); + } + QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry); +} + +int QQuickTextInputPrivate::calculateTextWidth() +{ + return qRound(control->naturalTextWidth()); +} + +void QQuickTextInputPrivate::updateHorizontalScroll() +{ + Q_Q(QQuickTextInput); + const int preeditLength = control->preeditAreaText().length(); + const int width = q->width(); + int widthUsed = calculateTextWidth(); + + if (!autoScroll || widthUsed <= width) { + QQuickTextInput::HAlignment effectiveHAlign = q->effectiveHAlign(); + // text fits in br; use hscroll for alignment + switch (effectiveHAlign & ~(Qt::AlignAbsolute|Qt::AlignVertical_Mask)) { + case Qt::AlignRight: + hscroll = widthUsed - width; + break; + case Qt::AlignHCenter: + hscroll = (widthUsed - width) / 2; + break; + default: + // Left + hscroll = 0; + break; + } + } else { + int cix = qRound(control->cursorToX(control->cursor() + preeditLength)); + if (cix - hscroll >= width) { + // text doesn't fit, cursor is to the right of br (scroll right) + hscroll = cix - width; + } else if (cix - hscroll < 0 && hscroll < widthUsed) { + // text doesn't fit, cursor is to the left of br (scroll left) + hscroll = cix; + } else if (widthUsed - hscroll < width) { + // text doesn't fit, text document is to the left of br; align + // right + hscroll = widthUsed - width; + } + if (preeditLength > 0) { + // check to ensure long pre-edit text doesn't push the cursor + // off to the left + cix = qRound(control->cursorToX( + control->cursor() + qMax(0, control->preeditCursor() - 1))); + if (cix < hscroll) + hscroll = cix; + } + } +} + +QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + Q_D(QQuickTextInput); + + QQuickTextNode *node = static_cast<QQuickTextNode *>(oldNode); + if (node == 0) + node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext()); + d->textNode = node; + + if (!d->textLayoutDirty) { + QSGSimpleRectNode *cursorNode = node->cursorNode(); + if (cursorNode != 0 && !isReadOnly()) { + QFontMetrics fm = QFontMetrics(d->font); + // the y offset is there to keep the baseline constant in case we have script changes in the text. + QPoint offset(-d->hscroll, fm.ascent() - d->control->ascent()); + offset.rx() += d->control->cursorToX(); + + QRect br(boundingRect().toRect()); + cursorNode->setRect(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height()))); + + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } else { + d->showCursor(); + } + } + } else { + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + + QPoint offset = QPoint(0,0); + QFontMetrics fm = QFontMetrics(d->font); + QRect br(boundingRect().toRect()); + if (d->autoScroll) { + // the y offset is there to keep the baseline constant in case we have script changes in the text. + offset = br.topLeft() - QPoint(d->hscroll, d->control->ascent() - fm.ascent()); + } else { + offset = QPoint(d->hscroll, 0); + } + + QTextLayout *textLayout = d->control->textLayout(); + if (!textLayout->text().isEmpty()) { + node->addTextLayout(offset, textLayout, d->color, + QQuickText::Normal, QColor(), + d->selectionColor, d->selectedTextColor, + d->control->selectionStart(), + d->control->selectionEnd() - 1); // selectionEnd() returns first char after + // selection + } + + if (!isReadOnly() && d->cursorItem == 0) { + offset.rx() += d->control->cursorToX(); + node->setCursor(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height())), d->color); + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } else { + d->showCursor(); + } + } + + d->textLayoutDirty = false; + } + + return node; +} + +QVariant QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QQuickTextInput); + switch (property) { + case Qt::ImEnabled: + return QVariant((bool)(flags() & ItemAcceptsInputMethod)); + case Qt::ImHints: + return QVariant((int)inputMethodHints()); + case Qt::ImCursorRectangle: + return cursorRectangle(); + case Qt::ImFont: + return font(); + case Qt::ImCursorPosition: + return QVariant(d->control->cursor()); + case Qt::ImSurroundingText: + if (d->control->echoMode() == QLineControl::PasswordEchoOnEdit + && !d->control->passwordEchoEditing()) { + return QVariant(displayText()); + } else { + return QVariant(d->control->realText()); + } + case Qt::ImCurrentSelection: + return QVariant(selectedText()); + case Qt::ImMaximumTextLength: + return QVariant(maxLength()); + case Qt::ImAnchorPosition: + if (d->control->selectionStart() == d->control->selectionEnd()) + return QVariant(d->control->cursor()); + else if (d->control->selectionStart() == d->control->cursor()) + return QVariant(d->control->selectionEnd()); + else + return QVariant(d->control->selectionStart()); + default: + return QVariant(); + } +} + +/*! + \qmlmethod void QtQuick2::TextInput::deselect() + + Removes active text selection. +*/ +void QQuickTextInput::deselect() +{ + Q_D(QQuickTextInput); + d->control->deselect(); +} + +/*! + \qmlmethod void QtQuick2::TextInput::selectAll() + + Causes all text to be selected. +*/ +void QQuickTextInput::selectAll() +{ + Q_D(QQuickTextInput); + d->control->setSelection(0, d->control->text().length()); +} + +/*! + \qmlmethod void 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. +*/ +bool QQuickTextInput::isRightToLeft(int start, int end) +{ + Q_D(QQuickTextInput); + if (start > end) { + qmlInfo(this) << "isRightToLeft(start, end) called with the end property being smaller than the start."; + return false; + } else { + return d->control->text().mid(start, end - start).isRightToLeft(); + } +} + +#ifndef QT_NO_CLIPBOARD +/*! + \qmlmethod QtQuick2::TextInput::cut() + + Moves the currently selected text to the system clipboard. +*/ +void QQuickTextInput::cut() +{ + Q_D(QQuickTextInput); + d->control->copy(); + d->control->del(); +} + +/*! + \qmlmethod QtQuick2::TextInput::copy() + + Copies the currently selected text to the system clipboard. +*/ +void QQuickTextInput::copy() +{ + Q_D(QQuickTextInput); + d->control->copy(); +} + +/*! + \qmlmethod QtQuick2::TextInput::paste() + + Replaces the currently selected text by the contents of the system clipboard. +*/ +void QQuickTextInput::paste() +{ + Q_D(QQuickTextInput); + if (!d->control->isReadOnly()) + d->control->paste(); +} +#endif // QT_NO_CLIPBOARD + +/*! + \qmlmethod void QtQuick2::TextInput::selectWord() + + Causes the word closest to the current cursor position to be selected. +*/ +void QQuickTextInput::selectWord() +{ + Q_D(QQuickTextInput); + d->control->selectWordAtPos(d->control->cursor()); +} + +/*! + \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 + PasswordEchoOnEdit. By default it is an asterisk. + + If this property is set to a string with more than one character, + the first character is used. If the string is empty, the value + is ignored and the property is not set. +*/ +QString QQuickTextInput::passwordCharacter() const +{ + Q_D(const QQuickTextInput); + return QString(d->control->passwordCharacter()); +} + +void QQuickTextInput::setPasswordCharacter(const QString &str) +{ + Q_D(QQuickTextInput); + if (str.length() < 1) + return; + d->control->setPasswordCharacter(str.constData()[0]); + EchoMode echoMode_ = echoMode(); + if (echoMode_ == Password || echoMode_ == PasswordEchoOnEdit) { + updateSize(); + } + emit passwordCharacterChanged(); +} + +/*! + \qmlproperty string QtQuick2::TextInput::displayText + + This is the text displayed in the TextInput. + + If \l echoMode is set to TextInput::Normal, this holds the + same value as the TextInput::text property. Otherwise, + this property holds the text visible to the user, while + the \l text property holds the actual entered text. +*/ +QString QQuickTextInput::displayText() const +{ + Q_D(const QQuickTextInput); + return d->control->displayText(); +} + +/*! + \qmlproperty bool QtQuick2::TextInput::selectByMouse + + Defaults to false. + + If true, the user can use the mouse to select text in some + platform-specific way. Note that for some platforms this may + not be an appropriate interaction (eg. may conflict with how + the text needs to behave inside a Flickable. +*/ +bool QQuickTextInput::selectByMouse() const +{ + Q_D(const QQuickTextInput); + return d->selectByMouse; +} + +void QQuickTextInput::setSelectByMouse(bool on) +{ + Q_D(QQuickTextInput); + if (d->selectByMouse != on) { + d->selectByMouse = on; + emit selectByMouseChanged(on); + } +} + +/*! + \qmlproperty enum QtQuick2::TextInput::mouseSelectionMode + + Specifies how text should be selected using a mouse. + + \list + \o TextInput.SelectCharacters - The selection is updated with individual characters. (Default) + \o TextInput.SelectWords - The selection is updated with whole words. + \endlist + + This property only applies when \l selectByMouse is true. +*/ + +QQuickTextInput::SelectionMode QQuickTextInput::mouseSelectionMode() const +{ + Q_D(const QQuickTextInput); + return d->mouseSelectionMode; +} + +void QQuickTextInput::setMouseSelectionMode(SelectionMode mode) +{ + Q_D(QQuickTextInput); + if (d->mouseSelectionMode != mode) { + d->mouseSelectionMode = mode; + emit mouseSelectionModeChanged(mode); + } +} + +/*! + \qmlproperty bool QtQuick2::TextInput::canPaste + + Returns true if the TextInput is writable and the content of the clipboard is + suitable for pasting into the TextEdit. +*/ +bool QQuickTextInput::canPaste() const +{ + Q_D(const QQuickTextInput); + return d->canPaste; +} + +void QQuickTextInput::moveCursorSelection(int position) +{ + Q_D(QQuickTextInput); + d->control->moveCursor(position, true); +} + +/*! + \qmlmethod void 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.) + + When this method is called it additionally sets either the + selectionStart or the selectionEnd (whichever was at the previous cursor position) + to the specified position. This allows you to easily extend and contract the selected + text range. + + The selection mode specifies whether the selection is updated on a per character or a per word + basis. If not specified the selection mode will default to TextInput.SelectCharacters. + + \list + \o TextEdit.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at + the previous cursor position) to the specified position. + \o TextEdit.SelectWords - Sets the selectionStart and selectionEnd to include all + words between the specified position and the previous cursor position. Words partially in the + range are included. + \endlist + + For example, take this sequence of calls: + + \code + cursorPosition = 5 + moveCursorSelection(9, TextInput.SelectCharacters) + moveCursorSelection(7, TextInput.SelectCharacters) + \endcode + + This moves the cursor to position 5, extend the selection end from 5 to 9 + and then retract the selection end from 9 to 7, leaving the text from position 5 to 7 + selected (the 6th and 7th characters). + + The same sequence with TextInput.SelectWords will extend the selection start to a word boundary + before or on position 5 and extend the selection end to a word boundary on or past position 9. +*/ +void QQuickTextInput::moveCursorSelection(int pos, SelectionMode mode) +{ + Q_D(QQuickTextInput); + + if (mode == SelectCharacters) { + d->control->moveCursor(pos, true); + } else if (pos != d->control->cursor()){ + const int cursor = d->control->cursor(); + int anchor; + if (!d->control->hasSelectedText()) + anchor = d->control->cursor(); + else if (d->control->selectionStart() == d->control->cursor()) + anchor = d->control->selectionEnd(); + else + anchor = d->control->selectionStart(); + + if (anchor < pos || (anchor == pos && cursor < pos)) { + const QString text = d->control->text(); + QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text); + finder.setPosition(anchor); + + const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); + if (anchor < text.length() && (!(reasons & QTextBoundaryFinder::StartWord) + || ((reasons & QTextBoundaryFinder::EndWord) && anchor > cursor))) { + finder.toPreviousBoundary(); + } + anchor = finder.position() != -1 ? finder.position() : 0; + + finder.setPosition(pos); + if (pos > 0 && !finder.boundaryReasons()) + finder.toNextBoundary(); + const int cursor = finder.position() != -1 ? finder.position() : text.length(); + + d->control->setSelection(anchor, cursor - anchor); + } else if (anchor > pos || (anchor == pos && cursor > pos)) { + const QString text = d->control->text(); + QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text); + finder.setPosition(anchor); + + const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); + if (anchor > 0 && (!(reasons & QTextBoundaryFinder::EndWord) + || ((reasons & QTextBoundaryFinder::StartWord) && anchor < cursor))) { + finder.toNextBoundary(); + } + + anchor = finder.position() != -1 ? finder.position() : text.length(); + + finder.setPosition(pos); + if (pos < text.length() && !finder.boundaryReasons()) + finder.toPreviousBoundary(); + const int cursor = finder.position() != -1 ? finder.position() : 0; + + d->control->setSelection(anchor, cursor - anchor); + } + } +} + +/*! + \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 1.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->inputPanel()->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 1.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->inputPanel()->hide(); +} + +void QQuickTextInput::focusInEvent(QFocusEvent *event) +{ + Q_D(const QQuickTextInput); + if (d->focusOnPress && !isReadOnly()) + openSoftwareInputPanel(); + QQuickImplicitSizeItem::focusInEvent(event); +} + +void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QQuickTextInput); + if (change == ItemActiveFocusHasChanged) { + bool hasFocus = value.boolValue; + d->focused = hasFocus; + setCursorVisible(hasFocus); // ### refactor: && d->canvas && d->canvas->hasFocus() + if (echoMode() == QQuickTextInput::PasswordEchoOnEdit && !hasFocus) + d->control->updatePasswordEchoEditing(false);//QLineControl sets it on key events, but doesn't deal with focus events + if (!hasFocus) + d->control->deselect(); + } + QQuickItem::itemChange(change, value); +} + +/*! + \qmlproperty bool QtQuick2::TextInput::inputMethodComposing + + + This property holds whether the TextInput has partial text input from an + input method. + + While it is composing an input method may rely on mouse or key events from + the TextInput to edit or commit the partial text. This property can be + used to determine when to disable events handlers that may interfere with + the correct operation of an input method. +*/ +bool QQuickTextInput::isInputMethodComposing() const +{ + Q_D(const QQuickTextInput); + return d->control->preeditAreaText().length() > 0; +} + +void QQuickTextInputPrivate::init() +{ + Q_Q(QQuickTextInput); + control->setParent(q);//Now mandatory due to accessibility changes + control->setCursorWidth(1); + control->setPasswordCharacter(QLatin1Char('*')); + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QQuickItem::ItemAcceptsInputMethod); + q->setFlag(QQuickItem::ItemHasContents); + q->connect(control, SIGNAL(cursorPositionChanged(int,int)), + q, SLOT(cursorPosChanged())); + q->connect(control, SIGNAL(selectionChanged()), + q, SLOT(selectionChanged())); + q->connect(control, SIGNAL(textChanged(QString)), + q, SLOT(q_textChanged())); + q->connect(control, SIGNAL(accepted()), + q, SIGNAL(accepted())); + q->connect(control, SIGNAL(updateNeeded(QRect)), + q, SLOT(updateRect(QRect))); +#ifndef QT_NO_CLIPBOARD + q->connect(q, SIGNAL(readOnlyChanged(bool)), + q, SLOT(q_canPasteChanged())); + q->connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), + q, SLOT(q_canPasteChanged())); + canPaste = !control->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0; +#endif // QT_NO_CLIPBOARD + q->connect(control, SIGNAL(updateMicroFocus()), + q, SLOT(updateCursorRectangle())); + q->connect(control, SIGNAL(displayTextChanged(QString)), + q, SLOT(updateRect())); + q->updateSize(); + imHints &= ~Qt::ImhMultiLine; + oldValidity = control->hasAcceptableInput(); + lastSelectionStart = 0; + lastSelectionEnd = 0; + QPalette p = control->palette(); + selectedTextColor = p.color(QPalette::HighlightedText); + selectionColor = p.color(QPalette::Highlight); + determineHorizontalAlignment(); + + if (!qmlDisableDistanceField()) { + QTextOption option = control->textLayout()->textOption(); + option.setUseDesignMetrics(true); + control->textLayout()->setTextOption(option); + } +} + +void QQuickTextInput::cursorPosChanged() +{ + Q_D(QQuickTextInput); + updateCursorRectangle(); + emit cursorPositionChanged(); + // XXX todo - not in 4.8? +#if 0 + d->control->resetCursorBlinkTimer(); +#endif + + if (!d->control->hasSelectedText()) { + if (d->lastSelectionStart != d->control->cursor()) { + d->lastSelectionStart = d->control->cursor(); + emit selectionStartChanged(); + } + if (d->lastSelectionEnd != d->control->cursor()) { + d->lastSelectionEnd = d->control->cursor(); + emit selectionEndChanged(); + } + } +} + +void QQuickTextInput::updateCursorRectangle() +{ + Q_D(QQuickTextInput); + d->determineHorizontalAlignment(); + d->updateHorizontalScroll(); + updateRect();//TODO: Only update rect between pos's + updateMicroFocus(); + emit cursorRectangleChanged(); + if (d->cursorItem) + d->cursorItem->setX(d->control->cursorToX() - d->hscroll); +} + +void QQuickTextInput::selectionChanged() +{ + Q_D(QQuickTextInput); + updateRect();//TODO: Only update rect in selection + emit selectedTextChanged(); + + if (d->lastSelectionStart != d->control->selectionStart()) { + d->lastSelectionStart = d->control->selectionStart(); + if (d->lastSelectionStart == -1) + d->lastSelectionStart = d->control->cursor(); + emit selectionStartChanged(); + } + if (d->lastSelectionEnd != d->control->selectionEnd()) { + d->lastSelectionEnd = d->control->selectionEnd(); + if (d->lastSelectionEnd == -1) + d->lastSelectionEnd = d->control->cursor(); + emit selectionEndChanged(); + } +} + +void QQuickTextInput::q_textChanged() +{ + Q_D(QQuickTextInput); + emit textChanged(); + emit displayTextChanged(); + updateSize(); + d->determineHorizontalAlignment(); + d->updateHorizontalScroll(); + updateMicroFocus(); + if (hasAcceptableInput() != d->oldValidity) { + d->oldValidity = hasAcceptableInput(); + emit acceptableInputChanged(); + } +} + +void QQuickTextInputPrivate::showCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(color); +} + +void QQuickTextInputPrivate::hideCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(QColor(0, 0, 0, 0)); +} + +void QQuickTextInput::updateRect(const QRect &r) +{ + Q_D(QQuickTextInput); + if (!isComponentComplete()) + return; + + if (r.isEmpty()) { + d->textLayoutDirty = true; + } + + update(); +} + +QRectF QQuickTextInput::boundingRect() const +{ + Q_D(const QQuickTextInput); + QRectF r = QQuickImplicitSizeItem::boundingRect(); + + int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->control->cursorWidth(); + + // Could include font max left/right bearings to either side of rectangle. + + r.setRight(r.right() + cursorWidth); + return r; +} + +void QQuickTextInput::updateSize(bool needsRedraw) +{ + Q_D(QQuickTextInput); + int w = width(); + int h = height(); + setImplicitSize(d->calculateTextWidth(), d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. + if (w==width() && h==height() && needsRedraw) + update(); +} + +void QQuickTextInput::q_canPasteChanged() +{ + Q_D(QQuickTextInput); + bool old = d->canPaste; +#ifndef QT_NO_CLIPBOARD + d->canPaste = !d->control->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0; +#endif + if (d->canPaste != old) + emit canPasteChanged(); +} + +QT_END_NAMESPACE + |