Merge lp:~zsombi/ubuntu-ui-toolkit/textinput-carets into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Zsombor Egri
Approved revision: 1036
Merged at revision: 1037
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/textinput-carets
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/textinputs-rightclick
Diff against target: 1322 lines (+808/-173)
16 files modified
components.api (+1/-0)
modules/Ubuntu/Components/InputHandler.qml (+107/-5)
modules/Ubuntu/Components/TextArea.qml (+10/-72)
modules/Ubuntu/Components/TextCursor.qml (+235/-26)
modules/Ubuntu/Components/TextField.qml (+11/-21)
modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml (+16/-1)
modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml (+58/-40)
modules/Ubuntu/Components/Themes/Ambiance/TextFieldStyle.qml (+1/-0)
modules/Ubuntu/Components/Themes/Ambiance/TextSelectionEndCursorStyle.qml (+66/-0)
modules/Ubuntu/Components/Themes/Ambiance/TextSelectionStartCursorStyle.qml (+66/-0)
modules/Ubuntu/Components/Themes/Ambiance/qmldir (+3/-0)
modules/Ubuntu/Test/UbuntuTestCase.qml (+25/-2)
tests/resources/inputs/TextInputs.qml (+4/-0)
tests/unit_x11/tst_components/tst_textarea.qml (+6/-5)
tests/unit_x11/tst_components/tst_textfield.qml (+1/-1)
tests/unit_x11/tst_components/tst_textinput_common.qml (+198/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/textinput-carets
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Cris Dywan Approve
Review via email: mp+216665@code.launchpad.net

Commit message

Text input caret handler visuals and functionality.

To post a comment you must log in.
Revision history for this message
Cris Dywan (kalikiana) wrote :

The carrots actually do a nice job on the desktop enabling me to extend selections with just a mouse - ordinarily it would require ⇧Arrow.

A single carrot is visible when there's no selection but can't be dragged.

If the selection is at the very start of the text field the first carrot is clipped.

505 - Ubuntu.Mouse.forwardTo: [inputHandler]
506 +// Ubuntu.Mouse.forwardTo: [inputHandler]

Accident?

526 +// FIXME: stabilize API

What does this mean? Could you make the comment more verbose or mention a bug?

Revision history for this message
Zsombor Egri (zsombi) wrote :

> The carrots actually do a nice job on the desktop enabling me to extend
> selections with just a mouse - ordinarily it would require ⇧Arrow.
>
> A single carrot is visible when there's no selection but can't be dragged.

The single caret is just to grab the cursor for easy positioning of it. It's not a selection caret. The selection cares get visible when there is selected text.
>
> If the selection is at the very start of the text field the first carrot is
> clipped.

The carets are clipped in the input area atm, so the selection beginning will be clipped yet. I'll try to get a similar image above the actual one, but unfortunately positioning the overlay caret is not that stable... And may require changes in the logic...

>
> 505 - Ubuntu.Mouse.forwardTo: [inputHandler]
> 506 +// Ubuntu.Mouse.forwardTo: [inputHandler]
>
> Accident?

Yes, good catch!

>
> 526 +// FIXME: stabilize API
>
> What does this mean? Could you make the comment more verbose or mention a bug?

Means that the API will be moved in Ubuntu.Components.Styles. Once I get everything done, and in a separate MR perhaps.

Revision history for this message
Cris Dywan (kalikiana) wrote :

> > The carrots actually do a nice job on the desktop enabling me to extend
> > selections with just a mouse - ordinarily it would require ⇧Arrow.
> >
> > A single carrot is visible when there's no selection but can't be dragged.
>
> The single caret is just to grab the cursor for easy positioning of it. It's
> not a selection caret. The selection cares get visible when there is selected
> text.

The problem with that is to start a selection you still have to aim freely, so there's no benefit in precisely moving the caret right now - unless there is going to be a way to long press/ right-click the carrot itself to start a selection or invoke the context menu.

> > If the selection is at the very start of the text field the first carrot is
> > clipped.
>
> The carets are clipped in the input area atm, so the selection beginning will
> be clipped yet. I'll try to get a similar image above the actual one, but
> unfortunately positioning the overlay caret is not that stable... And may
> require changes in the logic...

I could agree to filing a bug for it and leaving it as-is for now, it's not a deal breaker for typical use.

> >
> > 526 +// FIXME: stabilize API
> >
> > What does this mean? Could you make the comment more verbose or mention a
> bug?
>
> Means that the API will be moved in Ubuntu.Components.Styles. Once I get
> everything done, and in a separate MR perhaps.

Maybe just leave that out of this branch then? Since it's not actually a bug or anything, more like something that belongs in a bug or blueprint.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1032
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/146/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-trusty-touch/540
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/5168
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-amd64-ci/146
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/146
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/146/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-i386-ci/146
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/475
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4815
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4815/artifact/work/output/*zip*/output.zip
    FAILURE: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/6589/console
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/4441
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/5373
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/5373/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/146/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1034
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/156/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-trusty-touch/568
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/5194
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-amd64-ci/156
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/156
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/156/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-i386-ci/156
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/496
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4862
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4862/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/6697
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/4462
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/5417
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/5417/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/156/rebuild

review: Approve (continuous-integration)
Revision history for this message
Cris Dywan (kalikiana) wrote :

> > > A single carrot is visible when there's no selection but can't be dragged.
> > The single caret is just to grab the cursor for easy positioning of it. It's
> > not a selection caret.
> The problem with that is to start a selection you still have to aim freely, so
> there's no benefit in precisely moving the caret right now - unless there is
> going to be a way to long press/ right-click the carrot itself to start a
> selection or invoke the context menu.

?

> > > at the very start of the text field the first carrot is clipped.
> > The carets are clipped in the input area atm

It would seem to be fixed now. Nice.

> > > 526 +// FIXME: stabilize API
> > > What does this mean? Could you make the comment more verbose
> > Means that the API will be moved in Ubuntu.Components.Styles. Once I get
> > everything done, and in a separate MR perhaps.
> Maybe just leave that out of this branch then? Since it's not actually a bug
> or anything, more like something that belongs in a bug or blueprint.

?

From more testing I noticed, sometimes selecting text with the mouse gets ignored, even if you try a few times, and then it works again. Not sure what exactly causes it, it's hard to say if it's stuck or a timeout is catching it. I almost thought the last changes broke normal mouse selection :-(

review: Needs Information
Revision history for this message
Zsombor Egri (zsombi) wrote :

> > > > A single carrot is visible when there's no selection but can't be
> dragged.
> > > The single caret is just to grab the cursor for easy positioning of it.
> It's
> > > not a selection caret.
> > The problem with that is to start a selection you still have to aim freely,
> so
> > there's no benefit in precisely moving the caret right now - unless there is
> > going to be a way to long press/ right-click the carrot itself to start a
> > selection or invoke the context menu.
>
> ?

So, first of all, the caret is a handler which is used to grab the cursor (both in edit and select modes). We need them in all the modes, not only in selection mode.

You need to be able to position the cursor pretty precisely if you want to insert some text at a given position. When you have a single caret, you only have the cursor, you are not in the selection mode yet. That caret moves the cursor in a way so you can see where it actually gets positioned.

>
> > > > at the very start of the text field the first carrot is clipped.
> > > The carets are clipped in the input area atm
>
> It would seem to be fixed now. Nice.
>
> > > > 526 +// FIXME: stabilize API
> > > > What does this mean? Could you make the comment more verbose
> > > Means that the API will be moved in Ubuntu.Components.Styles. Once I get
> > > everything done, and in a separate MR perhaps.
> > Maybe just leave that out of this branch then? Since it's not actually a bug
> > or anything, more like something that belongs in a bug or blueprint.
>
> ?

FIXME comments we use not only to fix something, but to remember that there's more work to be done there. I can remove that of course, and I won't have the API stabilized in this MR for sure, but it is good to have extra reminders that these kind of tasks should also be done.

>
> From more testing I noticed, sometimes selecting text with the mouse gets
> ignored, even if you try a few times, and then it works again. Not sure what
> exactly causes it, it's hard to say if it's stuck or a timeout is catching it.
> I almost thought the last changes broke normal mouse selection :-(

Double tap? I noticed that because of the caret positioning, if you tap too close to the caret, the double tap will get stolen by the caret dragger... Perhaps I'd need to position the caret a bit closer to the bottom of the cursor so it doesn't interfere with the clicks...

Revision history for this message
Cris Dywan (kalikiana) wrote :

Very nice to have the cursor take events now. And I can actually open the menu more precisely through the carrot in tricky cases with small letters.

As I double-checked, the ignored selections are not new in this branch, just depend a lot on the exact mouse movement, so let's not worry about it here.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1035
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/169/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/13
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/7/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/1
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/1
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/1/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/1
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/528
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/88
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/88/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/6810
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/10/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/23
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/23/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/169/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Autolanding.
More details in the following jenkins job:
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-autolanding/57/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/14
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/9
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-autolanding/1
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-autolanding/1
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-autolanding/1/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-autolanding/1
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/529
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/90
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/90/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/6812
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/12
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/25
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/25/artifact/work/output/*zip*/output.zip

review: Needs Fixing (continuous-integration)
1036. By Zsombor Egri

staging merge

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1036
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/170/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/19
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/13
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/2
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/2
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/2/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/2
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/530
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/97
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/97/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/6815
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/14
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/29
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/29/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/170/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2014-05-02 11:09:58 +0000
3+++ components.api 2014-05-06 12:22:48 +0000
4@@ -605,6 +605,7 @@
5 TestCase
6 function findChild(obj,objectName)
7 function findInvisibleChild(obj,objectName)
8+ function findChildWithProperty(item, property, value)
9 function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay)
10 function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay)
11 function mouseLongPress(item, x, y, button, modifiers, delay)
12
13=== modified file 'modules/Ubuntu/Components/InputHandler.qml'
14--- modules/Ubuntu/Components/InputHandler.qml 2014-05-01 12:15:29 +0000
15+++ modules/Ubuntu/Components/InputHandler.qml 2014-05-06 12:22:48 +0000
16@@ -29,7 +29,7 @@
17 property Item main
18 // the input instance
19 property Item input
20- // the Flickable holdiong the input instance
21+ // the Flickable holding the input instance
22 property Flickable flickable
23 // selection cursor mode
24 property bool selectionCursor: input && input.selectedText !== ""
25@@ -38,7 +38,20 @@
26 // property holding the selection mode timeout
27 property int selectionModeTimeout: 200
28
29- // signal triggered when popup shoudl be opened
30+ // item filling the text input visible area, used to check teh caret handler
31+ // visibility
32+ property Item visibleArea: Item {
33+ parent: flickable
34+ anchors.fill: parent
35+ }
36+
37+ // line size and spacing
38+ property real lineSpacing: units.dp(3)
39+ property real lineSize: input.font.pixelSize + lineSpacing
40+ // input x/y distance from the frame
41+ property point frameDistance: Qt.point(0,0)
42+
43+ // signal triggered when popup should be opened
44 signal pressAndHold(int pos)
45
46 function activateInput() {
47@@ -80,9 +93,11 @@
48 readonly property Flickable scroller: (scrollingDisabled && grandScroller) ? grandScroller : flickable
49
50 // ensures the text cusrorRectangle is always in the internal Flickable's visible area
51- function ensureVisible()
52+ function ensureVisible(rect)
53 {
54- var rect = input.cursorRectangle;
55+ if (rect === undefined) {
56+ rect = input.cursorRectangle;
57+ }
58 if (flickable.moving || flickable.flicking)
59 return;
60 if (flickable.contentX >= rect.x)
61@@ -94,9 +109,14 @@
62 else if (flickable.contentY + flickable.height <= rect.y + rect.height)
63 flickable.contentY = rect.y + rect.height - flickable.height;
64 }
65+ // returns the cursor position from x,y pair
66+ function cursorPosition(x, y) {
67+ return singleLine ? input.positionAt(x, TextInput.CursorOnCharacter) : input.positionAt(x, y, TextInput.CursorOnCharacter);
68+ }
69+
70 // returns the mouse position
71 function mousePosition(mouse) {
72- return singleLine ? input.positionAt(mouse.x) : input.positionAt(mouse.x, mouse.y);
73+ return cursorPosition(mouse.x, mouse.y);
74 }
75 // checks whether the position is in the selected text
76 function positionInSelection(pos) {
77@@ -165,6 +185,30 @@
78 }
79 }
80
81+ // moves the specified position, called by the cursor handler
82+ // positioner = "currentPosition/selectionStart/selectionEnd"
83+ function positionCaret(positioner, x, y) {
84+ if (positioner === "cursorPosition") {
85+ input[positioner] = cursorPosition(x, y);
86+ } else {
87+ var pos = cursorPosition(x, y);
88+ if (positioner === "selectionStart" && (pos < input.selectionEnd)) {
89+ input.select(pos, input.selectionEnd);
90+ } else if (positioner === "selectionEnd" && (pos > input.selectionStart)) {
91+ input.select(input.selectionStart, pos);
92+ }
93+ }
94+ }
95+
96+ // returns the styles for the cursors depending on the position property given
97+ function textCursorStyle(positionProperty) {
98+ switch (positionProperty) {
99+ case "cursorPosition": return main.__styleInstance.mainCursorStyle;
100+ case "selectionStart": return main.__styleInstance.selectionStartCursorStyle;
101+ case "selectionEnd": return main.__styleInstance.selectionEndCursorStyle;
102+ }
103+ }
104+
105 Component.onCompleted: {
106 state = (main.focus) ? "" : "inactive";
107 }
108@@ -313,4 +357,62 @@
109 // trigger pressAndHold
110 onReleased: openContextMenu(mouse)
111 }
112+
113+ // cursors to use when text is selected
114+ Connections {
115+ property Item selectionStartCursor: null
116+ property Item selectionEndCursor: null
117+ target: input
118+ onSelectedTextChanged: {
119+ if (selectedText !== "" && input.cursorDelegate) {
120+ if (!selectionStartCursor) {
121+ selectionStartCursor = input.cursorDelegate.createObject(
122+ input, {
123+ "positionProperty": "selectionStart",
124+ "height": lineSize,
125+ "handler": inputHandler,
126+ }
127+ );
128+ moveSelectionCursor(selectionStartCursor);
129+ }
130+ if (!selectionEndCursor) {
131+ selectionEndCursor = input.cursorDelegate.createObject(
132+ input, {
133+ "positionProperty": "selectionEnd",
134+ "height": lineSize,
135+ "handler": inputHandler,
136+ }
137+ );
138+ moveSelectionCursor(selectionEndCursor);
139+ }
140+ } else {
141+ if (selectionStartCursor) {
142+ selectionStartCursor.destroy();
143+ selectionStartCursor = null;
144+ }
145+ if (selectionEndCursor) {
146+ selectionEndCursor.destroy();
147+ selectionEndCursor = null;
148+ }
149+ }
150+ }
151+ onSelectionStartChanged: moveSelectionCursor(selectionStartCursor, true);
152+ onSelectionEndChanged: moveSelectionCursor(selectionEndCursor, true);
153+
154+ function moveSelectionCursor(cursor, updateProperty) {
155+ if (!cursor) {
156+ return;
157+ }
158+ // workaround for https://bugreports.qt-project.org/browse/QTBUG-38704
159+ // selectedTextChanged signal is not emitted for TextEdit when selectByMouse is false
160+ if (updateProperty && QuickUtils.className(input) === "QQuickTextEdit") {
161+ input.selectedTextChanged();
162+ }
163+
164+ var pos = input.positionToRectangle(input[cursor.positionProperty]);
165+ cursor.x = pos.x;
166+ cursor.y = pos.y;
167+ ensureVisible(pos);
168+ }
169+ }
170 }
171
172=== modified file 'modules/Ubuntu/Components/TextArea.qml'
173--- modules/Ubuntu/Components/TextArea.qml 2014-04-28 06:48:28 +0000
174+++ modules/Ubuntu/Components/TextArea.qml 2014-05-06 12:22:48 +0000
175@@ -158,7 +158,7 @@
176 area defined in the current theme. The default value is the same as the visible
177 input area's width.
178 */
179- property real contentWidth: internal.inputAreaWidth
180+ property real contentWidth: control.width - 2 * internal.frameSpacing
181
182 /*!
183 The property folds the height of the text editing content. This can be equal or
184@@ -166,7 +166,7 @@
185 area defined in the current theme. The default value is the same as the visible
186 input area's height.
187 */
188- property real contentHeight: internal.inputAreaHeight
189+ property real contentHeight: control.height - 2 * internal.frameSpacing
190
191 /*!
192 The property overrides the default popover of the TextArea. When set, the
193@@ -233,10 +233,8 @@
194
195 Note that the root item of the delegate component must be a QQuickItem or
196 QQuickItem derived item.
197-
198- \qmlproperty Component cursorDelegate
199 */
200- property alias cursorDelegate: editor.cursorDelegate
201+ property Component cursorDelegate: null
202
203 /*!
204 The position of the cursor in the TextArea.
205@@ -731,7 +729,6 @@
206 /*!\internal - to remove warnings */
207 Component.onCompleted: {
208 editor.linkActivated.connect(control.linkActivated);
209- internal.prevShowCursor = control.cursorVisible;
210 }
211
212 // activation area on mouse click
213@@ -756,15 +753,6 @@
214 control.focus = false;
215 }
216
217- /*!\internal */
218- onContentWidthChanged: internal.inputAreaWidth = control.contentWidth
219- /*!\internal */
220- onContentHeightChanged: internal.inputAreaHeight = control.contentHeight
221- /*!\internal */
222- onWidthChanged: internal.inputAreaWidth = control.width - 2 * internal.frameSpacing
223- /*!\internal */
224- onHeightChanged: internal.inputAreaHeight = control.height - 2 * internal.frameSpacing
225-
226 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
227 LayoutMirroring.childrenInherit: true
228
229@@ -772,32 +760,12 @@
230 id: internal
231 // public property locals enabling aliasing
232 property string displayText: editor.getText(0, editor.length)
233- property real lineSpacing: units.dp(3)
234 property real frameSpacing: control.__styleInstance.frameSpacing
235- property real lineSize: editor.font.pixelSize + lineSpacing
236 property real minimumSize: units.gu(4)
237- property real inputAreaWidth: control.width - 2 * frameSpacing
238- property real inputAreaHeight: control.height - 2 * frameSpacing
239- //selection properties
240- property bool prevShowCursor
241-
242- function toggleSelectionCursors(show)
243- {
244- if (!show) {
245- leftCursorLoader.sourceComponent = undefined;
246- rightCursorLoader.sourceComponent = undefined;
247- editor.cursorVisible = prevShowCursor;
248- } else {
249- prevShowCursor = editor.cursorVisible;
250- editor.cursorVisible = false;
251- leftCursorLoader.sourceComponent = cursor;
252- rightCursorLoader.sourceComponent = cursor;
253- }
254- }
255
256 function linesHeight(lines)
257 {
258- var lineHeight = editor.font.pixelSize * lines + lineSpacing * lines
259+ var lineHeight = editor.font.pixelSize * lines + inputHandler.lineSpacing * lines
260 return lineHeight + 2 * frameSpacing;
261 }
262
263@@ -829,39 +797,6 @@
264 }
265 Keys.onReleased: event.accepted = (event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)
266
267- // cursor is FIXME: move in a separate element and align with TextField
268- Component {
269- id: cursor
270- TextCursor {
271- id: cursorItem
272- editorItem: control
273- height: internal.lineSize
274- popover: control.popover
275- visible: editor.cursorVisible
276-
277- Component.onCompleted: inputHandler.pressAndHold.connect(cursorItem.openPopover)
278- }
279- }
280- // selection cursor loader
281- Loader {
282- id: leftCursorLoader
283- onStatusChanged: {
284- if (status == Loader.Ready && item) {
285- item.positionProperty = "selectionStart";
286- item.parent = editor;
287- }
288- }
289- }
290- Loader {
291- id: rightCursorLoader
292- onStatusChanged: {
293- if (status == Loader.Ready && item) {
294- item.positionProperty = "selectionEnd";
295- item.parent = editor;
296- }
297- }
298- }
299-
300 // holding default values
301 Label { id: fontHolder }
302
303@@ -911,13 +846,15 @@
304 readOnly: false
305 id: editor
306 focus: true
307- width: internal.inputAreaWidth
308- height: Math.max(internal.inputAreaHeight, editor.contentHeight)
309+ width: control.contentWidth
310+ height: Math.max(control.contentHeight, editor.contentHeight)
311 wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
312 mouseSelectionMode: TextEdit.SelectWords
313 selectByMouse: false
314 activeFocusOnPress: false
315- cursorDelegate: cursor
316+ cursorDelegate: TextCursor {
317+ handler: inputHandler
318+ }
319 color: control.__styleInstance.color
320 selectedTextColor: Theme.palette.selected.foregroundText
321 selectionColor: Theme.palette.selected.foreground
322@@ -937,6 +874,7 @@
323 input: editor
324 flickable: flicker
325 selectionModeTimeout: control.__styleInstance.selectionModeTimeout
326+ frameDistance: Qt.point(internal.frameSpacing, internal.frameSpacing)
327 }
328 }
329 }
330
331=== modified file 'modules/Ubuntu/Components/TextCursor.qml'
332--- modules/Ubuntu/Components/TextCursor.qml 2014-04-28 06:48:28 +0000
333+++ modules/Ubuntu/Components/TextCursor.qml 2014-05-06 12:22:48 +0000
334@@ -15,48 +15,257 @@
335 */
336
337 import QtQuick 2.0
338+import Ubuntu.Components 1.1 as Ubuntu
339 import Ubuntu.Components.Popups 1.0
340
341-StyledItem {
342+Ubuntu.StyledItem {
343 id: cursorItem
344
345 width: units.dp(1)
346
347 /*
348- Property holding the text input item instance.
349- */
350- property var editorItem
351-
352- /*
353 Property holding the text input's custor position property. Can be one of
354 the following ones: cursorPosition, selectionStart and selectionEnd.
355 */
356 property string positionProperty: "cursorPosition"
357
358 /*
359- The property contains the custom popover to be shown.
360- */
361- property var popover
362-
363+ Input handler instance
364+ */
365+ property InputHandler handler
366+
367+ /*
368+ Cursor delegate used. This is the visual component from the main
369+ */
370+ readonly property Component cursorDelegate: handler.main.cursorDelegate ?
371+ handler.main.cursorDelegate :
372+ __styleInstance.cursorDelegate
373+
374+ // depending on the positionProperty, we chose different styles
375+ style: Theme.createStyleComponent(handler.textCursorStyle(positionProperty), cursorItem);
376+
377+ //Caret instance from the style.
378+ property Item caret: __styleInstance.caret
379+ property real caretX: caret ? caret.x : 0
380+ property real caretY: caret ? caret.y : 0
381+
382+ // returns the mapped cursor position to a position relative to the main component
383+ function mappedCursorPosition(pos) {
384+ return cursorItem[pos] + handler.frameDistance[pos] - handler.flickable["content"+pos.toUpperCase()];
385+ }
386 /*
387 The function opens the text input popover setting the text cursor as caller.
388 */
389+ Connections {
390+ target: inputHandler
391+ onPressAndHold: openPopover()
392+ }
393+
394 function openPopover() {
395- if (!visible)
396+ if (!visible || opacity === 0.0 || dragger.drag.active) {
397 return;
398- if (popover === undefined) {
399- // open the default one
400- PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem,
401- {
402- "target": editorItem
403- })
404- } else {
405- PopupUtils.open(popover, cursorItem,
406- {
407- "target": editorItem
408- })
409- }
410- }
411-
412- style: Theme.createStyleComponent("TextCursorStyle.qml", cursorItem)
413+ }
414+ // open context menu only for cursorPosition or selectionEnd
415+ if (positionProperty !== "selectionStart") {
416+ if (handler.main.popover === undefined) {
417+ // open the default one
418+ PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem,
419+ {
420+ "target": handler.main
421+ })
422+ } else {
423+ PopupUtils.open(handler.main.popover, cursorItem,
424+ {
425+ "target": handler.main
426+ })
427+ }
428+ }
429+ }
430+
431+ visible: handler.main.cursorVisible
432+ // change opacity to 0 if text is selected and the positionProperty is cursorPosition
433+ // note: we should not touch visibility as cursorVisible alters that!
434+ opacity: (positionProperty === "cursorPosition") && (handler.main.selectedText !== "") ? 0.0 : 1.0
435+
436+ // cursor visual loader
437+ Loader {
438+ id: cursorLoader
439+ sourceComponent: cursorDelegate
440+ height: parent.height
441+ onItemChanged: {
442+ if (item) {
443+ cursorItem.width = item.width;
444+ }
445+ }
446+ // bind the cursor height as it may change depending on the text size
447+ Binding {
448+ target: cursorLoader.item
449+ property: "height"
450+ value: cursorLoader.height
451+ when: cursorLoader.item
452+ }
453+ }
454+
455+ /*
456+ * Handle pressAndHold as well as right clicks when pressed around the
457+ */
458+ MouseArea {
459+ anchors {
460+ fill: parent
461+ margins: -units.dp(4)
462+ }
463+ acceptedButtons: Qt.LeftButon | Qt.RightButton
464+ preventStealing: false
465+ onPressAndHold: openPopover()
466+ onClicked: if (mouse.button === Qt.RightButton) openPopover()
467+ }
468+
469+ /*
470+ * Caret dragging handling. We need a separate item which is dragged along the
471+ * component's area, which can move freely and not attached to the caret itself.
472+ * This area will then be used to update the caret position.
473+ */
474+ onXChanged: if (draggedItem.state === "") draggedItem.moveToCaret()
475+ onYChanged: if (draggedItem.state === "") draggedItem.moveToCaret()
476+ Component.onCompleted: draggedItem.moveToCaret()
477+
478+ //dragged item
479+ Item {
480+ id: draggedItem
481+ objectName: cursorItem.positionProperty + "_draggeditem"
482+ width: caret ? caret.width : 0
483+ height: caret ? caret.height : 0
484+ parent: handler.main
485+ visible: cursorItem.visible && (cursorItem.opacity > 0.0)
486+
487+ // when the dragging ends, reposition the dragger back to caret
488+ onStateChanged: {
489+ if (state === "") {
490+ draggedItem.moveToCaret();
491+ }
492+ }
493+
494+ /*
495+ Mouse area to turn on dragging or selection mode when pressed
496+ on the handler area. The drag mode is turned off when the drag
497+ gets inactive or when the LeftButton is released.
498+ */
499+ MouseArea {
500+ objectName: cursorItem.positionProperty + "_activator"
501+ anchors.fill: parent
502+ acceptedButtons: Qt.LeftButton | Qt.RightButton
503+ preventStealing: true
504+ enabled: parent.width && parent.height
505+
506+ onPressed: {
507+ draggedItem.moveToCaret(mouse.x, mouse.y);
508+ draggedItem.state = "dragging";
509+ }
510+ onPressAndHold: openPopover()
511+ onClicked: if (mouse.button === Qt.RightButton) openPopover()
512+ Ubuntu.Mouse.forwardTo: [dragger]
513+ /*
514+ As we are forwarding the events to the upper mouse area, the release
515+ will not get into the normal MosueArea onRelease signal as the preventStealing
516+ will not have any effect on the handling. However due to the mouse
517+ filter's nature, we will still be able to grab mouse events and we
518+ can stop dragging. We only handle the release in case the drag hasn't
519+ been active at all, otherwise the drag will not be deactivated and we
520+ will end up in a binding loop on the moveToCaret() next time the caret
521+ handler is grabbed.
522+ */
523+ Ubuntu.Mouse.onReleased: {
524+ if (!dragger.drag.active) {
525+ draggedItem.state = "";
526+ }
527+ }
528+ }
529+
530+ // aligns the draggedItem to the caret and resets the dragger
531+ function moveToCaret(cx, cy) {
532+ if (cx === undefined && cy === undefined) {
533+ cx = mappedCursorPosition("x") + caretX;
534+ cy = mappedCursorPosition("y") + caretY;
535+ } else {
536+ // move mouse position to caret
537+ cx += draggedItem.x;
538+ cy += draggedItem.y;
539+ }
540+
541+ draggedItem.x = cx;
542+ draggedItem.y = cy;
543+ dragger.resetDrag();
544+ }
545+ // positions caret to the dragged position
546+ function positionCaret() {
547+ if (dragger.drag.active) {
548+ handler.positionCaret(positionProperty,
549+ dragger.thumbStartX + dragger.dragAmountX + handler.flickable.contentX,
550+ dragger.thumbStartY + dragger.dragAmountY + handler.flickable.contentY);
551+ }
552+ }
553+ }
554+ MouseArea {
555+ id: dragger
556+ objectName: cursorItem.positionProperty + "_dragger"
557+ // fill the entire component area
558+ parent: handler.main
559+ anchors.fill: parent
560+ hoverEnabled: true
561+ preventStealing: drag.active
562+ enabled: draggedItem.enabled && draggedItem.state === "dragging"
563+
564+ property int thumbStartX
565+ property int dragStartX
566+ property int dragAmountX: dragger.drag.target.x - dragStartX
567+ property int thumbStartY
568+ property int dragStartY
569+ property int dragAmountY: dragger.drag.target.y - dragStartY
570+
571+ function resetDrag() {
572+ thumbStartX = mappedCursorPosition("x");
573+ thumbStartY = mappedCursorPosition("y");
574+ dragStartX = drag.target.x;
575+ dragStartY = drag.target.y;
576+ }
577+
578+ // do not set minimum/maximum so we can drag outside of the Flickable area
579+ drag {
580+ target: draggedItem
581+ axis: handler.singleLine ? Drag.XAxis : Drag.XAndYAxis
582+ // deactivate dragging
583+ onActiveChanged: {
584+ if (!drag.active) {
585+ draggedItem.state = "";
586+ }
587+ }
588+ }
589+
590+ onDragAmountXChanged: draggedItem.positionCaret()
591+ onDragAmountYChanged: draggedItem.positionCaret()
592+ }
593+
594+ // fake cursor, caret is reparented to it to avoid caret clipping
595+ Item {
596+ id: fakeCursor
597+ parent: handler.main
598+ width: cursorItem.width
599+ height: cursorItem.height
600+ //reparent caret to it
601+ Component.onCompleted: if (caret) caret.parent = fakeCursor
602+
603+ x: mappedCursorPosition("x")
604+ y: mappedCursorPosition("y")
605+
606+ // manual clipping: the caret should be visible only while the cursor's
607+ // top/bottom falls into the text area
608+ visible: {
609+ if (!caret || !cursorItem.visible || cursorItem.opacity < 1.0)
610+ return false;
611+
612+ var leftTop = Qt.point(x - handler.frameDistance.x, y + handler.frameDistance.y + handler.lineSpacing);
613+ var rightBottom = Qt.point(x - handler.frameDistance.x, y + height - handler.frameDistance.y - handler.lineSpacing);
614+ return (handler.visibleArea.contains(leftTop) || handler.visibleArea.contains(rightBottom));
615+ }
616+ }
617 }
618
619=== modified file 'modules/Ubuntu/Components/TextField.qml'
620--- modules/Ubuntu/Components/TextField.qml 2014-04-28 06:48:28 +0000
621+++ modules/Ubuntu/Components/TextField.qml 2014-05-06 12:22:48 +0000
622@@ -244,10 +244,8 @@
623
624 Note that the root item of the delegate component must be a QQuickItem or
625 QQuickItem derived item.
626-
627- \qmlproperty Component cursorDelegate
628 */
629- property alias cursorDelegate: editor.cursorDelegate
630+ property Component cursorDelegate: null
631
632 /*!
633 The position of the cursor in the TextField.
634@@ -910,20 +908,6 @@
635 }
636 }
637
638- // cursor
639- Component {
640- id: cursor
641- TextCursor {
642- //FIXME: connect to root object once we have all TextInput properties exposed
643- editorItem: editor
644- height: internal.lineSize
645- popover: control.popover
646- visible: editor.cursorVisible
647-
648- Component.onCompleted: inputHandler.pressAndHold.connect(openPopover)
649- }
650- }
651-
652 AbstractButton {
653 id: clearButton
654 objectName: "clear_button"
655@@ -939,7 +923,6 @@
656 (control.activeFocus && ((editor.text != "") || editor.inputMethodComposing))
657
658 Image {
659- //anchors.fill: parent
660 anchors.verticalCenter: parent.verticalCenter
661 width: units.gu(3)
662 height: width
663@@ -997,9 +980,9 @@
664 // FocusScope will forward focus to this component
665 focus: true
666 anchors.verticalCenter: parent.verticalCenter
667- // get the control's style
668- clip: true
669- cursorDelegate: cursor
670+ cursorDelegate: TextCursor {
671+ handler: inputHandler
672+ }
673 color: control.__styleInstance.color
674 selectedTextColor: Theme.palette.selected.foregroundText
675 selectionColor: Theme.palette.selected.foreground
676@@ -1021,6 +1004,13 @@
677 input: editor
678 flickable: flicker
679 selectionModeTimeout: control.__styleInstance.selectionModeTimeout
680+ /*
681+ In x direction we use 2 times the configured spacing, as we have
682+ both the overlay and the Flickable aligned with margins. On y
683+ direction we only use the simple spacing, the Flickable moves the
684+ top downwards.
685+ */
686+ frameDistance: Qt.point(2 * internal.spacing, internal.spacing)
687 }
688 }
689 }
690
691=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml'
692--- modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml 2014-04-25 12:53:58 +0000
693+++ modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml 2014-05-06 12:22:48 +0000
694@@ -18,6 +18,7 @@
695 import Ubuntu.Components 1.1
696
697 // frame
698+// FIXME: stabilize API
699 Item {
700 id: visuals
701 // style properties
702@@ -36,17 +37,31 @@
703 Spacing between the frame and the text editor area
704 */
705 property real frameSpacing: units.gu(1)
706- property real overlaySpacing: units.gu(0.5)
707+ property real overlaySpacing: frameSpacing / 2
708
709 /*!
710 Property holding the timeout in milliseconds the component enters into selection mode.
711 */
712 property int selectionModeTimeout: 300
713
714+ /*!
715+ The following properties define the name of the style components declaring
716+ the styles for the main and the selection cursors. All cursors must defive
717+ from TextCursorStyle.
718+ */
719+ property string mainCursorStyle: "TextCursorStyle.qml"
720+ property string selectionStartCursorStyle: "TextSelectionStartCursorStyle.qml"
721+ property string selectionEndCursorStyle: "TextSelectionEndCursorStyle.qml"
722+
723+ // style body
724 anchors.fill: parent
725+ objectName: "textarea_style"
726
727 z: -1
728
729+ /*!
730+ Text input background
731+ */
732 property Component background: UbuntuShape {
733 property bool error: (styledItem.hasOwnProperty("errorHighlight") && styledItem.errorHighlight && !styledItem.acceptableInput)
734 onErrorChanged: (error) ? visuals.errorColor : visuals.backgroundColor;
735
736=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml'
737--- modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2013-07-01 06:33:24 +0000
738+++ modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2014-05-06 12:22:48 +0000
739@@ -16,48 +16,66 @@
740
741 import QtQuick 2.0
742
743+// FIXME : move the API into Ubuntu.Components.Style
744 Item {
745- id: visuals
746- /*!
747- Cursor color
748- */
749- property color color: Theme.palette.selected.foreground
750-
751- /*!
752- Properties driving cursor blinking. If either of these values are 0, no
753- blinking is provided.
754- */
755- property bool blinking: true
756- property int blinkTimeoutShown: 800
757- property int blinkTimeoutHidden: 400
758-
759- /*!
760- Selection mode pin styles
761- */
762- property var pinSize: units.gu(1.2)
763- property var pinSensingOffset: units.dp(4)
764- property color pinColor: Theme.palette.selected.foreground
765-
766- anchors.fill: parent
767- Rectangle {
768- id: cursor
769-
770- property bool showCursor: styledItem.visible
771- property bool timerShowCursor: true
772-
773- visible: showCursor && timerShowCursor
774- color: visuals.color
775- anchors.fill: parent
776-
777- Timer {
778- interval: visuals.blinkTimeoutShown
779- running: cursor.showCursor && (visuals.blinkTimeoutShown > 0) && (visuals.blinkTimeoutHidden > 0) && visuals.blinking
780- repeat: true
781- onTriggered: {
782- interval = (interval == visuals.blinkTimeoutShown) ?
783- visuals.blinkTimeoutHidden : visuals.blinkTimeoutShown;
784- cursor.timerShowCursor = !cursor.timerShowCursor;
785+ id: cursorStyle
786+ /*!
787+ Property specifying the visible timeout of the cursor. It is not mandatory
788+ for styles to define values for this property if the cursor blinking is not
789+ desired. A value of 0 turns off the cursor blinking.
790+ */
791+ property int cursorVisibleTimeout: 800
792+ /*!
793+ Property specifying the hidden timeout of the cursor. It is not mandatory
794+ for styles to define values for this property if the cursor blinking is not
795+ desired. A value of 0 turns off the cursor blinking.
796+ */
797+ property int cursorHiddenTimeout: 400
798+
799+ /*!
800+ Component defining the default cursor visuals.
801+ */
802+ property Component cursorDelegate: delegate
803+
804+ /*!
805+ The item pointing to the cursor handler. Styles should set to null if the
806+ cursor does not have handler at all.
807+ */
808+ property Item caret: caretItem
809+
810+ // style body
811+ Component {
812+ id: delegate
813+ Rectangle {
814+ width: units.dp(1)
815+ color: Theme.palette.selected.foreground
816+ visible: blinkTimer.timerShowCursor
817+ Timer {
818+ id: blinkTimer
819+ interval: cursorStyle.cursorVisibleTimeout
820+ running: (cursorStyle.cursorVisibleTimeout > 0) &&
821+ (cursorStyle.cursorHiddenTimeout > 0) &&
822+ styledItem.visible
823+ repeat: true
824+ property bool timerShowCursor: true
825+ onTriggered: {
826+ interval = (interval == cursorStyle.cursorVisibleTimeout) ?
827+ cursorStyle.cursorHiddenTimeout : cursorStyle.cursorVisibleTimeout;
828+ timerShowCursor = !timerShowCursor;
829+ }
830 }
831 }
832 }
833+
834+ // caretItem
835+ Image {
836+ id: caretItem
837+ source: "artwork/teardrop-left.png"
838+ anchors {
839+ top: parent.bottom
840+ horizontalCenter: parent.horizontalCenter
841+ topMargin: -units.gu(0.5)
842+ horizontalCenterOffset: units.gu(0.7)
843+ }
844+ }
845 }
846
847=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextFieldStyle.qml'
848--- modules/Ubuntu/Components/Themes/Ambiance/TextFieldStyle.qml 2013-06-27 15:20:12 +0000
849+++ modules/Ubuntu/Components/Themes/Ambiance/TextFieldStyle.qml 2014-05-06 12:22:48 +0000
850@@ -17,4 +17,5 @@
851 import QtQuick 2.0
852
853 TextAreaStyle {
854+ objectName: "textfield_style"
855 }
856
857=== added file 'modules/Ubuntu/Components/Themes/Ambiance/TextSelectionEndCursorStyle.qml'
858--- modules/Ubuntu/Components/Themes/Ambiance/TextSelectionEndCursorStyle.qml 1970-01-01 00:00:00 +0000
859+++ modules/Ubuntu/Components/Themes/Ambiance/TextSelectionEndCursorStyle.qml 2014-05-06 12:22:48 +0000
860@@ -0,0 +1,66 @@
861+/*
862+ * Copyright 2014 Canonical Ltd.
863+ *
864+ * This program is free software; you can redistribute it and/or modify
865+ * it under the terms of the GNU Lesser General Public License as published by
866+ * the Free Software Foundation; version 3.
867+ *
868+ * This program is distributed in the hope that it will be useful,
869+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
870+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
871+ * GNU Lesser General Public License for more details.
872+ *
873+ * You should have received a copy of the GNU Lesser General Public License
874+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
875+ */
876+
877+import QtQuick 2.0
878+
879+// FIXME : move the API into Ubuntu.Components.Style
880+Item {
881+ id: cursorStyle
882+ /*!
883+ Property specifying the visible timeout of the cursor. It is not mandatory
884+ for styles to define values for this property if the cursor blinking is not
885+ desired. A value of 0 turns off the cursor blinking.
886+ */
887+ property int cursorVisibleTimeout: 0
888+ /*!
889+ Property specifying the hidden timeout of the cursor. It is not mandatory
890+ for styles to define values for this property if the cursor blinking is not
891+ desired. A value of 0 turns off the cursor blinking.
892+ */
893+ property int cursorHiddenTimeout: 0
894+
895+ /*!
896+ Component defining the default cursor visuals.
897+ */
898+ property Component cursorDelegate: delegate
899+
900+ /*!
901+ The item pointing to the cursor handler. Styles should set to null if the
902+ cursor does not have handler at all.
903+ */
904+ property Item caret: caretItem
905+
906+ // style body
907+ Component {
908+ id: delegate
909+ Rectangle {
910+ width: units.dp(1)
911+ color: Theme.palette.selected.foreground
912+ }
913+ }
914+
915+ // caretItem
916+ Image {
917+ id: caretItem
918+ source: "artwork/teardrop-left.png"
919+ anchors {
920+ top: parent.bottom
921+ horizontalCenter: parent.horizontalCenter
922+ topMargin: -units.gu(0.5)
923+ horizontalCenterOffset: units.gu(0.7)
924+ }
925+ }
926+}
927
928=== added file 'modules/Ubuntu/Components/Themes/Ambiance/TextSelectionStartCursorStyle.qml'
929--- modules/Ubuntu/Components/Themes/Ambiance/TextSelectionStartCursorStyle.qml 1970-01-01 00:00:00 +0000
930+++ modules/Ubuntu/Components/Themes/Ambiance/TextSelectionStartCursorStyle.qml 2014-05-06 12:22:48 +0000
931@@ -0,0 +1,66 @@
932+/*
933+ * Copyright 2014 Canonical Ltd.
934+ *
935+ * This program is free software; you can redistribute it and/or modify
936+ * it under the terms of the GNU Lesser General Public License as published by
937+ * the Free Software Foundation; version 3.
938+ *
939+ * This program is distributed in the hope that it will be useful,
940+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
941+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
942+ * GNU Lesser General Public License for more details.
943+ *
944+ * You should have received a copy of the GNU Lesser General Public License
945+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
946+ */
947+
948+import QtQuick 2.0
949+
950+// FIXME : move the API into Ubuntu.Components.Style
951+Item {
952+ id: cursorStyle
953+ /*!
954+ Property specifying the visible timeout of the cursor. It is not mandatory
955+ for styles to define values for this property if the cursor blinking is not
956+ desired. A value of 0 turns off the cursor blinking.
957+ */
958+ property int cursorVisibleTimeout: 0
959+ /*!
960+ Property specifying the hidden timeout of the cursor. It is not mandatory
961+ for styles to define values for this property if the cursor blinking is not
962+ desired. A value of 0 turns off the cursor blinking.
963+ */
964+ property int cursorHiddenTimeout: 0
965+
966+ /*!
967+ Component defining the default cursor visuals.
968+ */
969+ property Component cursorDelegate: delegate
970+
971+ /*!
972+ The item pointing to the cursor handler. Styles should set to null if the
973+ cursor does not have handler at all.
974+ */
975+ property Item caret: caretItem
976+
977+ // style body
978+ Component {
979+ id: delegate
980+ Rectangle {
981+ width: units.dp(1)
982+ color: Theme.palette.selected.foreground
983+ }
984+ }
985+
986+ // caretItem
987+ Image {
988+ id: caretItem
989+ source: "artwork/teardrop-right.png"
990+ anchors {
991+ top: parent.bottom
992+ horizontalCenter: parent.horizontalCenter
993+ topMargin: -units.gu(0.5)
994+ horizontalCenterOffset: -units.gu(0.7)
995+ }
996+ }
997+}
998
999=== added file 'modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-left@20.png'
1000Binary files modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-left@20.png 1970-01-01 00:00:00 +0000 and modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-left@20.png 2014-05-06 12:22:48 +0000 differ
1001=== added file 'modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-right@20.png'
1002Binary files modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-right@20.png 1970-01-01 00:00:00 +0000 and modules/Ubuntu/Components/Themes/Ambiance/artwork/teardrop-right@20.png 2014-05-06 12:22:48 +0000 differ
1003=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/qmldir'
1004--- modules/Ubuntu/Components/Themes/Ambiance/qmldir 2014-04-28 14:04:29 +0000
1005+++ modules/Ubuntu/Components/Themes/Ambiance/qmldir 2014-05-06 12:22:48 +0000
1006@@ -46,3 +46,6 @@
1007
1008 #version 1.1
1009 ComboButtonStyle 1.1 ComboButtonStyle.qml
1010+
1011+internal TextSelectionStartCursorStyle TextSelectionStartCursorStyle.qml
1012+internal TextSelectionEndCursorStyle TextSelectionEndCursorStyle.qml
1013
1014=== modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml'
1015--- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-28 06:06:32 +0000
1016+++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-05-06 12:22:48 +0000
1017@@ -66,6 +66,28 @@
1018 return null;
1019 }
1020
1021+ /*!
1022+ Finds a visible child of an \a item having a given \a property with a given
1023+ \a value.
1024+ */
1025+ function findChildWithProperty(item, property, value) {
1026+ var childs = new Array(0);
1027+ childs.push(item)
1028+ while (childs.length > 0) {
1029+ var child = childs[0];
1030+ if (child.hasOwnProperty(property) && (child[property] === value)) {
1031+ return child;
1032+ }
1033+ for (var i in childs[0].children) {
1034+ childs.push(childs[0].children[i])
1035+ }
1036+ childs.splice(0, 1);
1037+ }
1038+ return null;
1039+ }
1040+
1041+
1042+
1043 /*!
1044 Move Mouse from x,y to distance of dx, dy divided to steps with a stepdelay (ms).
1045 */
1046@@ -152,8 +174,9 @@
1047 */
1048 function mouseLongPress(item, x, y, button, modifiers, delay) {
1049 mousePress(item, x, y, button, modifiers, delay);
1050- // the delay is taken from QQuickMouseArea
1051- wait(800);
1052+ // the delay is taken from QQuickMouseArea, add few miliseconds to it to make sure we have
1053+ // the long press triggered
1054+ wait(900);
1055 }
1056
1057 /*!
1058
1059=== modified file 'tests/resources/inputs/TextInputs.qml'
1060--- tests/resources/inputs/TextInputs.qml 2014-04-07 10:37:36 +0000
1061+++ tests/resources/inputs/TextInputs.qml 2014-05-06 12:22:48 +0000
1062@@ -74,6 +74,10 @@
1063 autoSize: true
1064 maximumLineCount: 0
1065 text: "Lorem Ipsum is simply dummy text\nof the printing and typesetting\nindustry.\n"
1066+ cursorDelegate: Rectangle {
1067+ width: units.dp(4)
1068+ color: "blue"
1069+ }
1070 }
1071 TextArea {
1072 autoSize: true
1073
1074=== modified file 'tests/unit_x11/tst_components/tst_textarea.qml'
1075--- tests/unit_x11/tst_components/tst_textarea.qml 2014-04-28 06:06:32 +0000
1076+++ tests/unit_x11/tst_components/tst_textarea.qml 2014-05-06 12:22:48 +0000
1077@@ -138,7 +138,7 @@
1078 }
1079
1080 function test_0_cursorDelegate() {
1081- compare((textArea.cursorDelegate!=null),true,"TextArea.cursorDelegate is not null")
1082+ verify(textArea.cursorDelegate === null, "TextArea.cursorDelegate is not null")
1083 }
1084
1085 function test_0_cursorPosition() {
1086@@ -242,15 +242,16 @@
1087 compare(textArea.highlighted, textArea.focus, "highlighted is the same as focused");
1088 }
1089
1090- function test_contentHeight() {
1091- compare(textArea.contentHeight>0,true,"contentHeight over 0 units on default")
1092+ function test_0_contentHeight() {
1093+ verify(textArea.contentHeight > 0, "contentHeight over 0 units on default")
1094 var newValue = 200;
1095 textArea.contentHeight = newValue;
1096 compare(textArea.contentHeight,newValue,"set/get");
1097 }
1098
1099- function test_contentWidth() {
1100- compare(textArea.contentWidth,units.gu(30),"contentWidth is 30 units on default")
1101+ function test_0_contentWidth() {
1102+ var style = findChild(textArea, "textarea_style");
1103+ compare(textArea.contentWidth, units.gu(30) - 2 * style.frameSpacing, "contentWidth is the implicitWidth - 2 times the frame size on default")
1104 var newValue = 200;
1105 textArea.contentWidth = newValue;
1106 compare(textArea.contentWidth,newValue,"set/get");
1107
1108=== modified file 'tests/unit_x11/tst_components/tst_textfield.qml'
1109--- tests/unit_x11/tst_components/tst_textfield.qml 2014-04-28 06:06:32 +0000
1110+++ tests/unit_x11/tst_components/tst_textfield.qml 2014-05-06 12:22:48 +0000
1111@@ -156,7 +156,7 @@
1112 }
1113
1114 function test_0_cursorDelegate() {
1115- verify(textField.cursorDelegate, "cursorDelegate set by default")
1116+ verify(textField.cursorDelegate === null, "cursorDelegate not set by default")
1117 }
1118
1119 function test_0_cursorPosition() {
1120
1121=== added file 'tests/unit_x11/tst_components/tst_textinput_common.qml'
1122--- tests/unit_x11/tst_components/tst_textinput_common.qml 1970-01-01 00:00:00 +0000
1123+++ tests/unit_x11/tst_components/tst_textinput_common.qml 2014-05-06 12:22:48 +0000
1124@@ -0,0 +1,198 @@
1125+/*
1126+ * Copyright 2014 Canonical Ltd.
1127+ *
1128+ * This program is free software; you can redistribute it and/or modify
1129+ * it under the terms of the GNU Lesser General Public License as published by
1130+ * the Free Software Foundation; version 3.
1131+ *
1132+ * This program is distributed in the hope that it will be useful,
1133+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1134+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1135+ * GNU Lesser General Public License for more details.
1136+ *
1137+ * You should have received a copy of the GNU Lesser General Public License
1138+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1139+ */
1140+
1141+import QtQuick 2.0
1142+import QtTest 1.0
1143+import Ubuntu.Test 1.0
1144+import Ubuntu.Components 1.1
1145+
1146+Item {
1147+ id: testMain
1148+ width: units.gu(40)
1149+ height: units.gu(71)
1150+
1151+ Column {
1152+ spacing: units.gu(1)
1153+ TextField {
1154+ id: textField
1155+ }
1156+ TextArea {
1157+ id: textArea
1158+ }
1159+ }
1160+
1161+ SignalSpy {
1162+ id: cursorPositionSpy
1163+ signalName: "onCursorPositionChanged"
1164+ }
1165+ SignalSpy {
1166+ id: selectionStartSpy
1167+ signalName: "onSelectionStartChanged"
1168+ }
1169+ SignalSpy {
1170+ id: selectionEndSpy
1171+ signalName: "onSelectionEndChanged"
1172+ }
1173+ SignalSpy {
1174+ id: selectedTextSpy
1175+ signalName: "onSelectedTextChanged"
1176+ }
1177+ SignalSpy {
1178+ id: popupSpy
1179+ signalName: "pressAndHold"
1180+ }
1181+ SignalSpy {
1182+ id: movementXSpy
1183+ signalName: "onContentXChanged"
1184+ }
1185+ SignalSpy {
1186+ id: movementYSpy
1187+ signalName: "contentYChanged"
1188+ }
1189+
1190+ UbuntuTestCase {
1191+ name: "TextInputCaretTest"
1192+ when: windowShown
1193+
1194+ function init() {
1195+ textField.text = "This is a single line text input called TextField.";
1196+ textArea.text = "This is a multiline text input component called TextArea. It supports fix size as well as auto-expanding behavior.";
1197+ textField.cursorPosition = 0;
1198+ textArea.cursorPosition = 0;
1199+ waitForRendering(textField, 500);
1200+ waitForRendering(textArea, 500);
1201+ }
1202+
1203+ function cleanup() {
1204+ textField.focus = false;
1205+ textArea.focus = false;
1206+ cursorPositionSpy.clear();
1207+ selectionStartSpy.clear();
1208+ selectionEndSpy.clear();
1209+ selectedTextSpy.clear();
1210+ popupSpy.clear();
1211+ movementXSpy.clear();
1212+ movementYSpy.clear();
1213+ }
1214+
1215+ function test_textfield_grab_caret_data() {
1216+ return [
1217+ {input: textField},
1218+ {input: textArea},
1219+ ];
1220+ }
1221+
1222+ function test_textfield_grab_caret(data) {
1223+ data.input.focus = true;
1224+
1225+ var draggedItem = findChild(data.input, "cursorPosition_draggeditem");
1226+ var drag_activator = findChild(data.input, "cursorPosition_activator");
1227+ var dragger = findChild(data.input, "cursorPosition_dragger");
1228+
1229+ // drag the cursor, observe cursorPositionChanged
1230+ cursorPositionSpy.target = data.input;
1231+ var x = drag_activator.width / 2;
1232+ var y = drag_activator.height / 2;
1233+ mousePress(drag_activator, x, y);
1234+ compare(draggedItem.state, "dragging", "the caret hasn't been activated");
1235+ // drag the mouse
1236+ mouseMoveSlowly(dragger, x, y, 100, 0, 10, 0);
1237+ mouseRelease(dragger, x + 100, y);
1238+ waitForRendering(data.input, 1000);
1239+ compare(draggedItem.state, "", "the caret hasn't been deactivated");
1240+ cursorPositionSpy.wait();
1241+ }
1242+
1243+ function test_textfield_grab_selection_cursors_data() {
1244+ return [
1245+ {input: textField, cursorSpy: selectionStartSpy, dragPrefix: "selectionStart", startDx: 0, startDy: 0, dragDx: 100, dragDy: 0, movingSpy: null},
1246+ {input: textArea, cursorSpy: selectionStartSpy, dragPrefix: "selectionStart", startDx: 0, startDy: 0, dragDx: 100, dragDy: 0, movingSpy: null},
1247+ {input: textField, cursorSpy: selectionEndSpy, dragPrefix: "selectionEnd", startDx: 0, startDy: 0, dragDx: 200, dragDy: 100, movingSpy: movementXSpy, flicker: "textfield_scroller"},
1248+ {input: textArea, cursorSpy: selectionEndSpy, dragPrefix: "selectionEnd", startDx: 0, startDy: 0, dragDx: 200, dragDy: 100, movingSpy: movementYSpy, flicker: "textarea_scroller"},
1249+ ];
1250+ }
1251+ function test_textfield_grab_selection_cursors(data) {
1252+ if (data.movingSpy) {
1253+ var flicker = findChild(data.input, data.flicker)
1254+ data.movingSpy.target = flicker;
1255+ }
1256+ data.input.focus = true;
1257+ // make sure the text is long enough
1258+ data.input.text += data.input.text;
1259+
1260+ // select text in front
1261+ data.input.select(0, 10);
1262+ var draggedItem = findChild(data.input, data.dragPrefix + "_draggeditem");
1263+ var drag_activator = findChild(data.input, data.dragPrefix + "_activator");
1264+ var dragger = findChild(data.input, data.dragPrefix + "_dragger");
1265+ data.cursorSpy.target = data.input;
1266+
1267+ // grab the selection and drag it
1268+ var x = drag_activator.width / 2 + data.startDx;
1269+ var y = drag_activator.height / 2 + data.startDy;
1270+ selectedTextSpy.target = data.input;
1271+ mousePress(drag_activator, x, y);
1272+ compare(draggedItem.state, "dragging", "the caret hasn't been activated");
1273+ // drag the mouse
1274+ mouseMoveSlowly(dragger, x, y, data.dragDx, data.dragDy, 15, 100);
1275+ mouseRelease(dragger, x + data.dragDx, y + data.dragDy);
1276+ waitForRendering(data.input, 500);
1277+ compare(draggedItem.state, "", "the caret hasn't been deactivated");
1278+ data.cursorSpy.wait();
1279+ selectedTextSpy.wait();
1280+ if (data.movingSpy) {
1281+ data.movingSpy.wait();
1282+ }
1283+ }
1284+
1285+ function test_clear_text_using_popover_data() {
1286+ return [
1287+ {input: textField},
1288+ {input: textArea},
1289+ ];
1290+ }
1291+
1292+ function test_clear_text_using_popover(data) {
1293+ var handler = findChild(data.input, "input_handler");
1294+ popupSpy.target = handler;
1295+ data.input.focus = true;
1296+
1297+ // invoke popover
1298+ var x = data.input.width / 2;
1299+ var y = data.input.height / 2;
1300+ mouseLongPress(data.input, x, y);
1301+ popupSpy.wait();
1302+ mouseRelease(data.input, x, y);
1303+ var popover = findChild(testMain, "text_input_popover");
1304+ verify(popover, "Cannot retrieve default TextInputPopover");
1305+ waitForRendering(popover);
1306+ // select all
1307+ var selectAll = findChildWithProperty(popover, "text", "Select All");
1308+ verify(selectAll, "Select All item not found");
1309+ mouseClick(selectAll, selectAll.width / 2, selectAll.height / 2);
1310+ waitForRendering(data.input, 1000);
1311+ compare(data.input.text, data.input.selectedText, "Not all the text is selected");
1312+ // delete with key press
1313+ keyClick(Qt.Key_Backspace);
1314+ waitForRendering(data.input, 1000);
1315+ compare(data.input.text, "", "The text has not been deleted");
1316+
1317+ // dismiss popover
1318+ mouseClick(testMain, testMain.width / 2, testMain.height / 2);
1319+ wait(200);
1320+ }
1321+ }
1322+}

Subscribers

People subscribed via source and target branches