summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/controls/Private/TextHandle.qml91
-rw-r--r--src/controls/Private/private.pri1
-rw-r--r--src/controls/Private/qmldir1
-rw-r--r--src/controls/Styles/Base/TextAreaStyle.qml34
-rw-r--r--src/controls/Styles/Base/TextFieldStyle.qml34
-rw-r--r--src/controls/TextArea.qml133
-rw-r--r--src/controls/TextField.qml122
-rw-r--r--tests/manual/texthandles/main.cpp49
-rw-r--r--tests/manual/texthandles/main.qml169
-rw-r--r--tests/manual/texthandles/texthandles.pro11
-rw-r--r--tests/manual/texthandles/texthandles.qmlproject16
-rw-r--r--tests/manual/texthandles/texthandles.qrc5
12 files changed, 644 insertions, 22 deletions
diff --git a/src/controls/Private/TextHandle.qml b/src/controls/Private/TextHandle.qml
new file mode 100644
index 000000000..34daa4ab0
--- /dev/null
+++ b/src/controls/Private/TextHandle.qml
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.2
+
+Loader {
+ id: handle
+
+ property Item editor
+ property int minimum: -1
+ property int maximum: -1
+ property int position: -1
+ property alias delegate: handle.sourceComponent
+
+ readonly property alias pressed: mouse.pressed
+
+ readonly property real handleX: x + (item ? item.x : 0)
+ readonly property real handleY: y + (item ? item.y : 0)
+ readonly property real handleWidth: item ? item.width : 0
+ readonly property real handleHeight: item ? item.height : 0
+
+ property Item control
+ property QtObject styleData: QtObject {
+ readonly property alias pressed: mouse.pressed
+ readonly property alias position: handle.position
+ readonly property real lineHeight: position !== -1 ? editor.positionToRectangle(position).height
+ : editor.cursorRectangle.height
+ }
+
+ MouseArea {
+ id: mouse
+ anchors.fill: item
+ property point offset
+ onPressed: offset = Qt.point(x + mouse.x, y + mouse.y)
+ onPositionChanged: {
+ var pt = mapToItem(editor, mouse.x - offset.x, mouse.y - offset.y)
+
+ // limit vertically within mix/max coordinates or content bounds
+ var min = (minimum !== -1) ? minimum : 0
+ var max = (maximum !== -1) ? maximum : editor.length - 1
+ pt.y = Math.max(pt.y, editor.positionToRectangle(min).y)
+ pt.y = Math.min(pt.y, editor.positionToRectangle(max).y)
+
+ var pos = editor.positionAt(pt.x, pt.y)
+
+ // limit horizontally within min/max character positions
+ if (minimum !== -1)
+ pos = Math.max(pos, minimum)
+ if (maximum !== -1)
+ pos = Math.min(pos, maximum)
+
+ handle.position = pos
+ }
+ }
+}
diff --git a/src/controls/Private/private.pri b/src/controls/Private/private.pri
index f94468540..f2da1ec97 100644
--- a/src/controls/Private/private.pri
+++ b/src/controls/Private/private.pri
@@ -47,6 +47,7 @@ PRIVATE_QML_FILES += \
$$PWD/ScrollViewHelper.qml \
$$PWD/ScrollBar.qml \
$$PWD/TableViewSelection.qml \
+ $$PWD/TextHandle.qml \
$$PWD/TextSingleton.qml \
$$PWD/FocusFrame.qml \
$$PWD/ColumnMenuContent.qml \
diff --git a/src/controls/Private/qmldir b/src/controls/Private/qmldir
index d31483e31..589087bf4 100644
--- a/src/controls/Private/qmldir
+++ b/src/controls/Private/qmldir
@@ -25,3 +25,4 @@ ColumnMenuContent 1.0 ColumnMenuContent.qml
ContentItem 1.0 ContentItem.qml
HoverButton 1.0 HoverButton.qml
singleton TextSingleton 1.0 TextSingleton.qml
+TextHandle 1.0 TextHandle.qml
diff --git a/src/controls/Styles/Base/TextAreaStyle.qml b/src/controls/Styles/Base/TextAreaStyle.qml
index 7ad5686f9..4ffefe639 100644
--- a/src/controls/Styles/Base/TextAreaStyle.qml
+++ b/src/controls/Styles/Base/TextAreaStyle.qml
@@ -96,4 +96,38 @@ ScrollViewStyle {
\sa Text::renderType
*/
property int renderType: Text.NativeRendering
+
+ /*! The cursor handle.
+ \since QtQuick.Controls.Styles 1.3
+
+ The parent of the handle is positioned to the top left corner of
+ the cursor position. The interactive area is determined by the
+ geometry of the handle delegate.
+
+ The following read-only properties are available within the scope
+ of the handle delegate:
+ \table
+ \row \li \b {styleData.pressed} : bool \li Whether the handle is pressed.
+ \row \li \b {styleData.position} : int \li The character position of the handle.
+ \row \li \b {styleData.lineHeight} : real \li The height of the line the handle is on.
+ \endtable
+ */
+ property Component cursorHandle
+
+ /*! The selection handle.
+ \since QtQuick.Controls.Styles 1.3
+
+ The parent of the handle is positioned to the top left corner of
+ the first selected character. The interactive area is determined
+ by the geometry of the handle delegate.
+
+ The following read-only properties are available within the scope
+ of the handle delegate:
+ \table
+ \row \li \b {styleData.pressed} : bool \li Whether the handle is pressed.
+ \row \li \b {styleData.position} : int \li The character position of the handle.
+ \row \li \b {styleData.lineHeight} : real \li The height of the line the handle is on.
+ \endtable
+ */
+ property Component selectionHandle
}
diff --git a/src/controls/Styles/Base/TextFieldStyle.qml b/src/controls/Styles/Base/TextFieldStyle.qml
index d999551c0..dc25033aa 100644
--- a/src/controls/Styles/Base/TextFieldStyle.qml
+++ b/src/controls/Styles/Base/TextFieldStyle.qml
@@ -159,4 +159,38 @@ Style {
anchors.fill: parent
}
}
+
+ /*! The cursor handle.
+ \since QtQuick.Controls.Styles 1.3
+
+ The parent of the handle is positioned to the top left corner of
+ the cursor position. The interactive area is determined by the
+ geometry of the handle delegate.
+
+ The following read-only properties are available within the scope
+ of the handle delegate:
+ \table
+ \row \li \b {styleData.pressed} : bool \li Whether the handle is pressed.
+ \row \li \b {styleData.position} : int \li The character position of the handle.
+ \row \li \b {styleData.lineHeight} : real \li The height of the line the handle is on.
+ \endtable
+ */
+ property Component cursorHandle
+
+ /*! The selection handle.
+ \since QtQuick.Controls.Styles 1.3
+
+ The parent of the handle is positioned to the top left corner of
+ the first selected character. The interactive area is determined
+ by the geometry of the handle delegate.
+
+ The following read-only properties are available within the scope
+ of the handle delegate:
+ \table
+ \row \li \b {styleData.pressed} : bool \li Whether the handle is pressed.
+ \row \li \b {styleData.position} : int \li The character position of the handle.
+ \row \li \b {styleData.lineHeight} : real \li The height of the line the handle is on.
+ \endtable
+ */
+ property Component selectionHandle
}
diff --git a/src/controls/TextArea.qml b/src/controls/TextArea.qml
index 1ea265d69..b0c8882f6 100644
--- a/src/controls/TextArea.qml
+++ b/src/controls/TextArea.qml
@@ -683,7 +683,7 @@ ScrollView {
Flickable {
id: flickable
- interactive: false
+ interactive: !edit.selectByMouse
anchors.fill: parent
TextEdit {
@@ -739,7 +739,7 @@ ScrollView {
wrapMode: TextEdit.WordWrap
textMargin: 4
- selectByMouse: Qt.platform.os !== "android" // Workaround for QTBUG-36515
+ selectByMouse: !cursorHandle.delegate || !selectionHandle.delegate
readOnly: false
Keys.forwardTo: area
@@ -748,32 +748,139 @@ ScrollView {
KeyNavigation.tab: area.tabChangesFocus ? area.KeyNavigation.tab : null
KeyNavigation.backtab: area.tabChangesFocus ? area.KeyNavigation.backtab : null
- // keep textcursor within scroll view
+ property bool blockRecursion: false
+ property bool hasSelection: selectionStart !== selectionEnd
+ readonly property int selectionPosition: selectionStart !== cursorPosition ? selectionStart : selectionEnd
+
+ // force re-evaluation when contentWidth changes => text layout changes => selection moves
+ property rect selectionRectangle: contentWidth ? positionToRectangle(selectionPosition)
+ : positionToRectangle(selectionPosition)
+
+ onSelectionStartChanged: {
+ if (!blockRecursion && selectionHandle.delegate) {
+ blockRecursion = true
+ selectionHandle.position = selectionPosition
+ blockRecursion = false
+ }
+ }
+
onCursorPositionChanged: {
- if (cursorRectangle.y >= flickableItem.contentY + viewport.height - cursorRectangle.height - textMargin) {
+ if (!blockRecursion && cursorHandle.delegate) {
+ blockRecursion = true
+ cursorHandle.position = cursorPosition
+ blockRecursion = false
+ }
+ ensureVisible(cursorRectangle)
+ }
+
+ function ensureVisible(rect) {
+ if (rect.y >= flickableItem.contentY + viewport.height - rect.height - textMargin) {
// moving down
- flickableItem.contentY = cursorRectangle.y - viewport.height + cursorRectangle.height + textMargin
- } else if (cursorRectangle.y < flickableItem.contentY) {
+ flickableItem.contentY = rect.y - viewport.height + rect.height + textMargin
+ } else if (rect.y < flickableItem.contentY) {
// moving up
- flickableItem.contentY = cursorRectangle.y - textMargin
+ flickableItem.contentY = rect.y - textMargin
}
- if (cursorRectangle.x >= flickableItem.contentX + viewport.width - textMargin) {
+ if (rect.x >= flickableItem.contentX + viewport.width - textMargin) {
// moving right
- flickableItem.contentX = cursorRectangle.x - viewport.width + textMargin
- } else if (cursorRectangle.x < flickableItem.contentX) {
+ flickableItem.contentX = rect.x - viewport.width + textMargin
+ } else if (rect.x < flickableItem.contentX) {
// moving left
- flickableItem.contentX = cursorRectangle.x - textMargin
+ flickableItem.contentX = rect.x - textMargin
}
}
+
onLinkActivated: area.linkActivated(link)
onLinkHovered: area.linkHovered(link)
+ function activate() {
+ if (activeFocusOnPress) {
+ forceActiveFocus()
+ if (!readOnly)
+ Qt.inputMethod.show()
+ }
+ }
+
+ function moveHandles(cursor, selection) {
+ blockRecursion = true
+ Qt.inputMethod.commit()
+ cursorPosition = cursor
+ if (selection === -1) {
+ selectWord()
+ selection = selectionStart
+ }
+ selectionHandle.position = selection
+ cursorHandle.position = cursorPosition
+ blockRecursion = false
+ }
+
MouseArea {
- parent: area.viewport
anchors.fill: parent
cursorShape: edit.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
- acceptedButtons: Qt.NoButton
+ acceptedButtons: edit.selectByMouse ? Qt.NoButton : Qt.LeftButton
+ onClicked: {
+ var pos = edit.positionAt(mouse.x, mouse.y)
+ edit.moveHandles(pos, pos)
+ edit.activate()
+ }
+ onPressAndHold: {
+ var pos = edit.positionAt(mouse.x, mouse.y)
+ edit.moveHandles(pos, -1)
+ edit.activate()
+ }
+ }
+
+ TextHandle {
+ id: selectionHandle
+
+ editor: edit
+ control: area
+ z: 1 // above scrollbars
+ parent: __scroller // no clip
+ delegate: __style.selectionHandle
+ maximum: cursorHandle.position - 1
+ x: edit.selectionRectangle.x - flickableItem.contentX
+ y: edit.selectionRectangle.y - flickableItem.contentY
+ visible: pressed || (edit.hasSelection && handleY + handleHeight >= -1 && handleY <= viewport.height + 1
+ && handleX + handleWidth >= -1 && handleX <= viewport.width + 1)
+
+ onPositionChanged: {
+ if (!edit.blockRecursion) {
+ edit.blockRecursion = true
+ edit.select(selectionHandle.position, cursorHandle.position)
+ if (pressed)
+ edit.ensureVisible(edit.selectionRectangle)
+ edit.blockRecursion = false
+ }
+ }
+ }
+
+ TextHandle {
+ id: cursorHandle
+
+ editor: edit
+ control: area
+ z: 1 // above scrollbars
+ parent: __scroller // no clip
+ delegate: __style.cursorHandle
+ minimum: edit.hasSelection ? selectionHandle.position + 1 : -1
+ x: edit.cursorRectangle.x - flickableItem.contentX
+ y: edit.cursorRectangle.y - flickableItem.contentY
+ visible: pressed || ((edit.cursorVisible || edit.hasSelection)
+ && handleY + handleHeight >= -1 && handleY <= viewport.height + 1
+ && handleX + handleWidth >= -1 && handleX <= viewport.width + 1)
+
+ onPositionChanged: {
+ if (!edit.blockRecursion) {
+ edit.blockRecursion = true
+ if (!edit.hasSelection)
+ selectionHandle.position = cursorHandle.position
+ Qt.inputMethod.commit()
+ edit.select(selectionHandle.position, cursorHandle.position)
+ edit.blockRecursion = false
+ }
+ }
}
}
}
diff --git a/src/controls/TextField.qml b/src/controls/TextField.qml
index a20a5c40c..24f5e5e7f 100644
--- a/src/controls/TextField.qml
+++ b/src/controls/TextField.qml
@@ -572,14 +572,6 @@ Control {
Accessible.role: Accessible.EditableText
Accessible.description: placeholderText
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.IBeamCursor
- onClicked: textfield.forceActiveFocus()
- }
-
Text {
id: placeholderTextComponent
anchors.fill: textInput
@@ -597,7 +589,7 @@ Control {
TextInput {
id: textInput
focus: true
- selectByMouse: Qt.platform.os !== "android" // Workaround for QTBUG-36515
+ selectByMouse: !cursorHandle.delegate || !selectionHandle.delegate
selectionColor: __panel ? __panel.selectionColor : "darkred"
selectedTextColor: __panel ? __panel.selectedTextColor : "white"
@@ -624,5 +616,117 @@ Control {
}
onEditingFinished: textfield.editingFinished()
+
+ property bool blockRecursion: false
+ property bool hasSelection: selectionStart !== selectionEnd
+ readonly property int selectionPosition: selectionStart !== cursorPosition ? selectionStart : selectionEnd
+
+ // force re-evaluation when selection moves:
+ // - cyrsorRectangle changes => content scrolled
+ // - contentWidth changes => text layout changed
+ property rect selectionRectangle: cursorRectangle.x && contentWidth ? positionToRectangle(selectionPosition)
+ : positionToRectangle(selectionPosition)
+
+ onSelectionStartChanged: {
+ if (!blockRecursion && selectionHandle.delegate) {
+ blockRecursion = true
+ selectionHandle.position = selectionPosition
+ blockRecursion = false
+ }
+ }
+
+ onCursorPositionChanged: {
+ if (!blockRecursion && cursorHandle.delegate) {
+ blockRecursion = true
+ cursorHandle.position = cursorPosition
+ blockRecursion = false
+ }
+ }
+
+ function activate() {
+ if (activeFocusOnPress) {
+ forceActiveFocus()
+ if (!readOnly)
+ Qt.inputMethod.show()
+ }
+ }
+
+ function moveHandles(cursor, selection) {
+ blockRecursion = true
+ Qt.inputMethod.commit()
+ cursorPosition = cursor
+ if (selection === -1) {
+ selectWord()
+ selection = selectionStart
+ }
+ selectionHandle.position = selection
+ cursorHandle.position = cursorPosition
+ blockRecursion = false
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.IBeamCursor
+ acceptedButtons: textInput.selectByMouse ? Qt.NoButton : Qt.LeftButton
+ onClicked: {
+ var pos = textInput.positionAt(mouse.x, mouse.y)
+ textInput.moveHandles(pos, pos)
+ textInput.activate()
+ }
+ onPressAndHold: {
+ var pos = textInput.positionAt(mouse.x, mouse.y)
+ textInput.moveHandles(pos, -1)
+ textInput.activate()
+ }
+ }
+
+ TextHandle {
+ id: selectionHandle
+
+ editor: textInput
+ control: textfield
+ delegate: __style.selectionHandle
+ maximum: cursorHandle.position - 1
+ readonly property real selectionX: textInput.selectionRectangle.x
+ x: textInput.x + (pressed ? Math.max(0, selectionX) : selectionX)
+ y: textInput.selectionRectangle.y + textInput.y
+ visible: pressed || (textInput.hasSelection && handleX + handleWidth >= -1 && handleX <= textfield.width + 1)
+
+ onPositionChanged: {
+ if (!textInput.blockRecursion) {
+ textInput.blockRecursion = true
+ textInput.select(selectionHandle.position, cursorHandle.position)
+ if (pressed)
+ textInput.ensureVisible(position)
+ textInput.blockRecursion = false
+ }
+ }
+ }
+
+ TextHandle {
+ id: cursorHandle
+
+ editor: textInput
+ control: textfield
+ delegate: __style.cursorHandle
+ minimum: textInput.hasSelection ? selectionHandle.position + 1 : -1
+ x: textInput.cursorRectangle.x + textInput.x
+ y: textInput.cursorRectangle.y + textInput.y
+ visible: pressed || ((textInput.cursorVisible || textInput.hasSelection)
+ && handleX + handleWidth >= -1 && handleX <= textfield.width + 1)
+
+ onPositionChanged: {
+ if (!textInput.blockRecursion) {
+ textInput.blockRecursion = true
+ if (!textInput.hasSelection)
+ selectionHandle.position = cursorHandle.position
+ Qt.inputMethod.commit()
+ textInput.select(selectionHandle.position, cursorHandle.position)
+ textInput.blockRecursion = false
+ }
+ }
}
}
diff --git a/tests/manual/texthandles/main.cpp b/tests/manual/texthandles/main.cpp
new file mode 100644
index 000000000..210d777ec
--- /dev/null
+++ b/tests/manual/texthandles/main.cpp
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine(QUrl("qrc:/main.qml"));
+ return app.exec();
+}
diff --git a/tests/manual/texthandles/main.qml b/tests/manual/texthandles/main.qml
new file mode 100644
index 000000000..e5b8b3db1
--- /dev/null
+++ b/tests/manual/texthandles/main.qml
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Quick Controls module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.1
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+
+ApplicationWindow {
+ id: window
+
+ width: 800
+ height: 480
+ visible: true
+
+ toolBar: ToolBar {
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: window.spacing
+ CheckBox {
+ id: handleBox
+ text: "Handles"
+ checked: true
+ }
+ CheckBox {
+ id: outlineBox
+ text: "Outlines"
+ checked: false
+ enabled: handleBox.checked
+ }
+ Item { width: 1; height: 1; Layout.fillWidth: true }
+ CheckBox {
+ id: wrapBox
+ text: "Wrap"
+ checked: true
+ }
+ }
+ }
+
+ property int spacing: edit.font.pixelSize / 2
+
+ property string loremIpsum: "Lorem ipsum dolor sit amet, <a href='http://qt.digia.com'>consectetur</a> adipiscing elit. " +
+ "Morbi varius a lorem ac blandit. Donec eu nisl eu nisi consectetur commodo. " +
+ "Vestibulum tincidunt <img src='http://qt.digia.com/Static/Images/QtLogo.png'>ornare</img> tempor. " +
+ "Nulla dolor dui, vehicula quis tempor quis, ullamcorper vel dui. " +
+ "Integer semper suscipit ante, et luctus magna malesuada sed. " +
+ "Sed ipsum velit, pellentesque non aliquam eu, bibendum ac magna. " +
+ "Donec et luctus dolor. Nulla semper quis neque vitae cursus. " +
+ "Etiam auctor, ipsum vel varius tincidunt, erat lacus pulvinar sem, eu egestas leo nulla non felis. " +
+ "Maecenas hendrerit commodo turpis, ac convallis leo congue id. " +
+ "Donec et egestas ante, a dictum sapien."
+
+ ColumnLayout {
+ spacing: window.spacing
+ anchors.margins: window.spacing
+ anchors.fill: parent
+
+ TextField {
+ id: field
+ z: 1
+ text: loremIpsum
+ Layout.fillWidth: true
+
+ style: TextFieldStyle {
+ cursorHandle: handleBox.checked ? cursorDelegate : null
+ selectionHandle: handleBox.checked ? selectionDelegate : null
+ }
+ }
+
+ TextArea {
+ id: edit
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ textFormat: Qt.RichText
+ wrapMode: wrapBox.checked ? Text.Wrap : Text.NoWrap
+ text: loremIpsum + "<p>" + loremIpsum + "<p>" + loremIpsum + "<p>" + loremIpsum
+
+ style: TextAreaStyle {
+ cursorHandle: handleBox.checked ? cursorDelegate : null
+ selectionHandle: handleBox.checked ? selectionDelegate : null
+ }
+ }
+ }
+
+ Component {
+ id: selectionDelegate
+ Rectangle {
+ x: -width + edit.font.pixelSize / 2
+ y: (styleData.lineHeight - height) / 2
+ width: edit.font.pixelSize * 2.5
+ height: edit.font.pixelSize * 2.5
+ border.width: outlineBox.checked ? 1 : 0
+ radius: width / 4
+ color: "transparent"
+ Rectangle {
+ color: "white"
+ border.width: 1
+ radius: width / 2
+ width: height
+ height: edit.font.pixelSize / 2
+ anchors.right: parent.right
+ anchors.rightMargin: width / 2
+ anchors.verticalCenter: parent.verticalCenter
+ visible: control.activeFocus && control.selectionStart !== control.selectionEnd
+ }
+ }
+ }
+
+ Component {
+ id: cursorDelegate
+ Rectangle {
+ x: control.selectionStart !== control.selectionEnd ? -edit.font.pixelSize / 2 : -width / 2
+ y: (styleData.lineHeight - height) / 2
+ width: edit.font.pixelSize * 2.5
+ height: edit.font.pixelSize * 2.5
+ border.width: outlineBox.checked ? 1 : 0
+ radius: width / 4
+ color: "transparent"
+ Rectangle {
+ color: "white"
+ border.width: 1
+ radius: width / 2
+ width: height
+ height: edit.font.pixelSize / 2
+ anchors.left: parent.left
+ anchors.leftMargin: width / 2
+ anchors.verticalCenter: parent.verticalCenter
+ visible: control.activeFocus && control.selectionStart !== control.selectionEnd
+ }
+ }
+ }
+}
diff --git a/tests/manual/texthandles/texthandles.pro b/tests/manual/texthandles/texthandles.pro
new file mode 100644
index 000000000..0c7c00d84
--- /dev/null
+++ b/tests/manual/texthandles/texthandles.pro
@@ -0,0 +1,11 @@
+TARGET = texthandles
+QT += qml quick
+
+SOURCES += \
+ $$PWD/main.cpp
+
+OTHER_FILES += \
+ $$PWD/main.qml
+
+RESOURCES += \
+ texthandles.qrc
diff --git a/tests/manual/texthandles/texthandles.qmlproject b/tests/manual/texthandles/texthandles.qmlproject
new file mode 100644
index 000000000..e5a8bf02c
--- /dev/null
+++ b/tests/manual/texthandles/texthandles.qmlproject
@@ -0,0 +1,16 @@
+import QmlProject 1.1
+
+Project {
+ mainFile: "main.qml"
+
+ /* Include .qml, .js, and image files from current directory and subdirectories */
+ QmlFiles {
+ directory: "."
+ }
+ JavaScriptFiles {
+ directory: "."
+ }
+ ImageFiles {
+ directory: "."
+ }
+}
diff --git a/tests/manual/texthandles/texthandles.qrc b/tests/manual/texthandles/texthandles.qrc
new file mode 100644
index 000000000..5f6483ac3
--- /dev/null
+++ b/tests/manual/texthandles/texthandles.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>