Merge lp:~zsombi/ubuntu-ui-toolkit/textinput-text-selection into lp:ubuntu-ui-toolkit/staging
- textinput-text-selection
- Merge into staging
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Cris Dywan | ||||||||
Approved revision: | 1030 | ||||||||
Merged at revision: | 1020 | ||||||||
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/textinput-text-selection | ||||||||
Merge into: | lp:ubuntu-ui-toolkit/staging | ||||||||
Prerequisite: | lp:~zsombi/ubuntu-ui-toolkit/toolkit-version | ||||||||
Diff against target: |
2236 lines (+1257/-431) 12 files modified
components.api (+0/-2) modules/Ubuntu/Components/InputHandler.qml (+305/-0) modules/Ubuntu/Components/TextArea.qml (+45/-130) modules/Ubuntu/Components/TextField.qml (+81/-133) modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml (+5/-5) modules/Ubuntu/Components/qmldir (+1/-0) modules/Ubuntu/Test/UbuntuTestCase.qml (+1/-0) tests/resources/inputs/TextInputs.qml (+85/-0) tests/unit_x11/tst_components/tst_textarea.qml (+318/-60) tests/unit_x11/tst_components/tst_textarea_in_flickable.qml (+64/-38) tests/unit_x11/tst_components/tst_textfield.qml (+314/-63) tests/unit_x11/tst_mousefilters/HoverEvent.qml.moved (+38/-0) |
||||||||
To merge this branch: | bzr merge lp:~zsombi/ubuntu-ui-toolkit/textinput-text-selection | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Cris Dywan | Approve | ||
Review via email: mp+216851@code.launchpad.net |
This proposal supersedes a proposal from 2014-04-15.
Commit message
Fixes TextField and TextArea selection and scrolling behaviors.
Description of the change
Superseeded due to 1.0 version introduction.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:996
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:996
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:997
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:998
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1001
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1005
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
I see the same test failure here as J locally.
> tests/unit_
> tryCompare(moveSpy, "count", 1, 200);
Why not moveSpy.wait(); here? That seems to fix it for me.
When using select and context menu a few times it's possible to change the selection after the menu opens but before letting go of the mouse button. So I don't get what I selected or worst case nothing.
What I manually checked is that I can select, including scrolling, get a menu, copy, paste and so on, double click to select a word, all of these seem sold aside from the above issue.
FTR toggleFlickable
Should maybe selectionTimeou
test_press_
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
> I see the same test failure here as J locally.
> > tests/unit_
> > tryCompare(moveSpy, "count", 1, 200);
> Why not moveSpy.wait(); here? That seems to fix it for me.
Ahh, right :) I'll fix it.
>
> When using select and context menu a few times it's possible to change the
> selection after the menu opens but before letting go of the mouse button. So I
> don't get what I selected or worst case nothing.
Let's do this in a separate branch..
>
> What I manually checked is that I can select, including scrolling, get a menu,
> copy, paste and so on, double click to select a word, all of these seem sold
> aside from the above issue.
>
> FTR toggleFlickable
> support it, and PropertyChanges doesn't take a list either… but let's have it
> as-is for now.
Well, limitation of PropertyChange, maybe we propose some Change component upstream to deal with a list of targets :).
>
> Should maybe selectionTimeou
> modules/
I will put this in the TextFieldStyle/
>
> test_press_
> Especially given the above problem I found while using it, I think we need to
> test that it can be clicked and catches the right text. Possibly AP.
Yep, but let's get this in a separate branch as well.
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
Filed bug 1304952 for testing the context menu.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1008
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1012
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1014
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
The menu now stays in place the way it should. Nice!
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1015
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1019
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
Seems to work fine, albeit I get a weird error message now:
QIODevice::write: device not open
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1020
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1022
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1023
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1024
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
FAIL! : components:
Loc: [/tmp/buildd/
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
FAIL! : components:
Loc: [/tmp/buildd/
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1025
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1026
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1026
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1027
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 1028. By Zsombor Egri
-
tests reworked for tst_textarea_
in_flickable. qml
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1028
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 1029. By Zsombor Egri
-
staging merge
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1029
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Zsombor Egri (zsombi) wrote : | # |
The DatePicker failure must be a hickup of the CI - again... It has nothing to do with the changes from this MR.
- 1030. By Zsombor Egri
-
staging merge
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1030
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
(Still) working fine, and tests finally seem to pass. Nice.
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'components.api' |
2 | --- components.api 2014-04-24 18:39:13 +0000 |
3 | +++ components.api 2014-04-25 05:29:38 +0000 |
4 | @@ -483,7 +483,6 @@ |
5 | function remove(start, end) |
6 | function undo() |
7 | function forceActiveFocus() |
8 | - property internal __internal |
9 | TextField 0.1 1.0 |
10 | ActionItem |
11 | property bool highlighted |
12 | @@ -549,7 +548,6 @@ |
13 | function undo() |
14 | function remove(start, end) |
15 | function getText(start, end) |
16 | - property internal __internal |
17 | Palette 0.1 |
18 | QtObject |
19 | property PaletteValues normal |
20 | |
21 | === added file 'modules/Ubuntu/Components/InputHandler.qml' |
22 | --- modules/Ubuntu/Components/InputHandler.qml 1970-01-01 00:00:00 +0000 |
23 | +++ modules/Ubuntu/Components/InputHandler.qml 2014-04-25 05:29:38 +0000 |
24 | @@ -0,0 +1,305 @@ |
25 | +/* |
26 | + * Copyright 2014 Canonical Ltd. |
27 | + * |
28 | + * This program is free software; you can redistribute it and/or modify |
29 | + * it under the terms of the GNU Lesser General Public License as published by |
30 | + * the Free Software Foundation; version 3. |
31 | + * |
32 | + * This program is distributed in the hope that it will be useful, |
33 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
34 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
35 | + * GNU Lesser General Public License for more details. |
36 | + * |
37 | + * You should have received a copy of the GNU Lesser General Public License |
38 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
39 | + */ |
40 | + |
41 | +import QtQuick 2.0 |
42 | +import Ubuntu.Components 0.1 as Ubuntu |
43 | + |
44 | +/* |
45 | + This component is a unified text selection and scrolling handler for both |
46 | + TextField and TextArea components. |
47 | + */ |
48 | + |
49 | +Item { |
50 | + id: inputHandler |
51 | + objectName: "input_handler" |
52 | + // the root control |
53 | + property Item main |
54 | + // the input instance |
55 | + property Item input |
56 | + // the Flickable holdiong the input instance |
57 | + property Flickable flickable |
58 | + // selection cursor mode |
59 | + property bool selectionCursor: input && input.selectedText !== "" |
60 | + // True if mouse handlig is enabled, false if flicking mode is enabled |
61 | + readonly property bool mouseHandlingEnabled: !flickable.interactive |
62 | + // property holding the selection mode timeout |
63 | + property int selectionModeTimeout: 200 |
64 | + |
65 | + // signal triggered when popup shoudl be opened |
66 | + signal pressAndHold(int pos) |
67 | + |
68 | + function activateInput() { |
69 | + if (!input.activeFocus) { |
70 | + input.forceActiveFocus(); |
71 | + } else { |
72 | + showInputPanel(); |
73 | + } |
74 | + } |
75 | + |
76 | + function showInputPanel() { |
77 | + if (!Qt.inputMethod.visible) { |
78 | + Qt.inputMethod.show(); |
79 | + } |
80 | + textChanged = false; |
81 | + } |
82 | + function hideInputPanel() { |
83 | + Qt.inputMethod.hide(); |
84 | + // emit accepted signal if changed |
85 | + if (textChanged && input.hasOwnProperty("accepted")) { |
86 | + input.accepted(); |
87 | + } |
88 | + } |
89 | + |
90 | + // internal properties/functions |
91 | + readonly property bool singleLine: input && input.hasOwnProperty("validator") |
92 | + property var flickableList: new Array() |
93 | + property bool textChanged: false |
94 | + property int pressedPosition: -1 |
95 | + // move properties |
96 | + property int moveStarts: -1 |
97 | + property int moveEnds: -1 |
98 | + // set scroller to the first Flickable that scrolls the input |
99 | + // this can be the internal Flickable if the full autosize is disabled |
100 | + // or one of the input's parent Flickable |
101 | + readonly property bool scrollingDisabled: main && main.hasOwnProperty("autoSize") ? |
102 | + (main.autoSize && (main.maximumLineCount <= 0)) : false |
103 | + onScrollingDisabledChanged: if (state == "") flickable.interactive = !scrollingDisabled |
104 | + readonly property Flickable grandScroller: firstFlickableParent(main) |
105 | + readonly property Flickable scroller: (scrollingDisabled && grandScroller) ? grandScroller : flickable |
106 | + |
107 | + // ensures the text cusrorRectangle is always in the internal Flickable's visible area |
108 | + function ensureVisible() |
109 | + { |
110 | + var rect = input.cursorRectangle; |
111 | + if (flickable.moving || flickable.flicking) |
112 | + return; |
113 | + if (flickable.contentX >= rect.x) |
114 | + flickable.contentX = rect.x; |
115 | + else if (flickable.contentX + flickable.width <= rect.x + rect.width) |
116 | + flickable.contentX = rect.x + rect.width - flickable.width; |
117 | + if (flickable.contentY >= rect.y) |
118 | + flickable.contentY = rect.y; |
119 | + else if (flickable.contentY + flickable.height <= rect.y + rect.height) |
120 | + flickable.contentY = rect.y + rect.height - flickable.height; |
121 | + } |
122 | + // returns the mouse position |
123 | + function mousePosition(mouse) { |
124 | + return singleLine ? input.positionAt(mouse.x) : input.positionAt(mouse.x, mouse.y); |
125 | + } |
126 | + // checks whether the position is in the selected text |
127 | + function positionInSelection(pos) { |
128 | + return (input.selectionStart !== input.selectionEnd) |
129 | + && (pos >= Math.min(input.selectionStart, input.selectionEnd)) |
130 | + && (pos <= Math.max(input.selectionStart, input.selectionEnd)); |
131 | + } |
132 | + |
133 | + // check whether the mouse is inside a selected text area |
134 | + function mouseInSelection(mouse) { |
135 | + var pos = mousePosition(mouse); |
136 | + return positionInSelection(pos); |
137 | + } |
138 | + // selects text |
139 | + function selectText(mouse) { |
140 | + moveEnds = mousePosition(mouse); |
141 | + if (moveStarts < 0) { |
142 | + moveStarts = moveEnds; |
143 | + } |
144 | + input.select(moveStarts, moveEnds); |
145 | + } |
146 | + // returns the first Flickable parent of a given item |
147 | + function firstFlickableParent(item) { |
148 | + var p = item ? item.parent : null; |
149 | + while (p && !p.hasOwnProperty("flicking")) { |
150 | + p = p.parent; |
151 | + } |
152 | + return p; |
153 | + } |
154 | + // focuses the input if not yet focused, and shows the context menu |
155 | + function openContextMenu(mouse) { |
156 | + var pos = mousePosition(mouse); |
157 | + if (!main.focus || !mouseInSelection(mouse)) { |
158 | + activateInput(); |
159 | + input.cursorPosition = pressedPosition = mousePosition(mouse); |
160 | + } |
161 | + // open context menu at the cursor position |
162 | + inputHandler.pressAndHold(input.cursorPosition); |
163 | + // if opened with left press (touch falls into this criteria as well), we need to set state to inactive |
164 | + // so the mouse moves won't result in selected text loss/change |
165 | + if (mouse.button === Qt.LeftButton) { |
166 | + state = "inactive"; |
167 | + } |
168 | + } |
169 | + |
170 | + // disables interactive Flickable parents, stops at the first non-interactive flickable. |
171 | + function toggleFlickablesInteractive(turnOn) { |
172 | + var p; |
173 | + if (!turnOn) { |
174 | + // handle the scroller separately |
175 | + p = firstFlickableParent(scroller) |
176 | + while (p) { |
177 | + if (p.interactive) { |
178 | + flickableList.push(p); |
179 | + p.interactive = false; |
180 | + } else { |
181 | + break; |
182 | + } |
183 | + p = firstFlickableParent(p); |
184 | + } |
185 | + } else { |
186 | + while (flickableList.length > 0) { |
187 | + p = flickableList.pop(); |
188 | + p.interactive = true; |
189 | + } |
190 | + } |
191 | + } |
192 | + |
193 | + Component.onCompleted: { |
194 | + state = (main.focus) ? "" : "inactive"; |
195 | + } |
196 | + |
197 | + // states |
198 | + states: [ |
199 | + // override default state to turn on the saved Flickable interactive mode |
200 | + State { |
201 | + name: "" |
202 | + StateChangeScript { |
203 | + // restore interactive for all Flickable parents |
204 | + script: toggleFlickablesInteractive(true); |
205 | + } |
206 | + }, |
207 | + State { |
208 | + name: "inactive" |
209 | + // we do not disable scroller here as in case the internal scrolling |
210 | + // is disabled (scrollingDisabled = true) the outer scroller (grandScroller) |
211 | + // would be blocked as well, which we don't want to |
212 | + PropertyChanges { |
213 | + target: flickable |
214 | + interactive: false |
215 | + } |
216 | + }, |
217 | + State { |
218 | + name: "scrolling" |
219 | + StateChangeScript { |
220 | + script: { |
221 | + // stop scrolling all the parents |
222 | + toggleFlickablesInteractive(false); |
223 | + // stop selection timeout |
224 | + selectionTimeout.running = false; |
225 | + } |
226 | + } |
227 | + }, |
228 | + State { |
229 | + name: "select" |
230 | + // during select state all the flickables are blocked (interactive = false) |
231 | + // we can use scroller here as we need to disable the outer scroller too! |
232 | + PropertyChanges { |
233 | + target: scroller |
234 | + interactive: false |
235 | + } |
236 | + StateChangeScript { |
237 | + script: { |
238 | + // turn off interactive for all parent flickables |
239 | + toggleFlickablesInteractive(false); |
240 | + if (!positionInSelection(pressedPosition)) { |
241 | + input.cursorPosition = pressedPosition; |
242 | + } |
243 | + } |
244 | + } |
245 | + } |
246 | + ] |
247 | + |
248 | + // brings the state back to default when the component looses focuse |
249 | + Connections { |
250 | + target: main |
251 | + ignoreUnknownSignals: true |
252 | + onFocusChanged: { |
253 | + state = (main.focus) ? "" : "inactive"; |
254 | + } |
255 | + } |
256 | + |
257 | + // input specific signals |
258 | + Connections { |
259 | + target: input |
260 | + onCursorRectangleChanged: if (!scrollingDisabled) ensureVisible() |
261 | + onTextChanged: textChanged = true; |
262 | + } |
263 | + |
264 | + // inner or outer Flickable controlling |
265 | + Connections { |
266 | + target: scroller |
267 | + // turn scrolling state on |
268 | + onFlickStarted: if (!scrollingDisabled) state = "scrolling" |
269 | + onMovementStarted: if (!scrollingDisabled) state = "scrolling" |
270 | + // reset to default state |
271 | + onMovementEnded: state = "" |
272 | + } |
273 | + |
274 | + // switches the state to selection |
275 | + Timer { |
276 | + id: selectionTimeout |
277 | + interval: selectionModeTimeout |
278 | + onTriggered: { |
279 | + if (scroller && !scroller.moving) { |
280 | + state = "select"; |
281 | + } |
282 | + } |
283 | + } |
284 | + |
285 | + // Mouse handling |
286 | + Ubuntu.Mouse.forwardTo: [main] |
287 | + Ubuntu.Mouse.onPressed: { |
288 | + if (input.activeFocus) { |
289 | + // start selection timeout |
290 | + selectionTimeout.restart(); |
291 | + } |
292 | + // remember pressed position as we need it when entering into selection state |
293 | + pressedPosition = mousePosition(mouse); |
294 | + // consume event so it does not get forwarded to the input |
295 | + mouse.accepted = true; |
296 | + } |
297 | + Ubuntu.Mouse.onReleased: { |
298 | + if (!main.focus && !main.activeFocusOnPress) { |
299 | + return; |
300 | + } |
301 | + |
302 | + activateInput(); |
303 | + // stop text selection timer |
304 | + selectionTimeout.running = false; |
305 | + if (state === "") { |
306 | + input.cursorPosition = mousePosition(mouse); |
307 | + } |
308 | + moveStarts = moveEnds = -1; |
309 | + state = ""; |
310 | + } |
311 | + Ubuntu.Mouse.onPositionChanged: { |
312 | + // leave if not focus, not the left button or not in select state |
313 | + if (!input.activeFocus || (mouse.button !== Qt.LeftButton) || (state !== "select") || !main.selectByMouse) { |
314 | + return; |
315 | + } |
316 | + // stop text selection timer |
317 | + selectionTimeout.running = false; |
318 | + selectText(mouse); |
319 | + } |
320 | + Ubuntu.Mouse.onDoubleClicked: { |
321 | + if (main.selectByMouse) { |
322 | + input.selectWord(); |
323 | + // turn selection state temporarily so the selection is not cleared on release |
324 | + state = "selection"; |
325 | + } |
326 | + } |
327 | + Ubuntu.Mouse.onPressAndHold: openContextMenu(mouse) |
328 | + |
329 | +} |
330 | |
331 | === modified file 'modules/Ubuntu/Components/TextArea.qml' |
332 | --- modules/Ubuntu/Components/TextArea.qml 2014-04-20 19:25:12 +0000 |
333 | +++ modules/Ubuntu/Components/TextArea.qml 2014-04-25 05:29:38 +0000 |
334 | @@ -15,6 +15,7 @@ |
335 | */ |
336 | |
337 | import QtQuick 2.0 |
338 | +import Ubuntu.Components 0.1 as Ubuntu |
339 | import "mathUtils.js" as MathUtils |
340 | |
341 | /*! |
342 | @@ -60,19 +61,34 @@ |
343 | mode and 4 lines on fixed-mode. The line size is calculated from the font size and the |
344 | ovarlay and frame spacing specified in the style. |
345 | |
346 | + \section2 Scrolling and text selection |
347 | + The input is activated when the tap or mouse is released after being pressed |
348 | + over the component. |
349 | + |
350 | Scrolling the editing area can happen when the size is fixed or in auto-sizing mode when |
351 | the content size is bigger than the visible area. The scrolling is realized by swipe |
352 | gestures, or by navigating the cursor. |
353 | |
354 | - The item enters in selection mode when the user performs a long tap (or long mouse press) |
355 | - or a double tap/press on the text area. The mode is visualized by two selection cursors |
356 | - (pins) which can be used to select the desired text. The text can also be selected by |
357 | - moving the finger/mouse towards the desired area right after entering in selection mode. |
358 | - The way the text is selected is driven by the mouseSelectionMode value, which is either |
359 | - character or word. The editor leaves the selection mode by pressing/tapping again on it |
360 | - or by losing focus. |
361 | - |
362 | - \b{This component is under heavy development.} |
363 | + The content can be selected in the following ways: |
364 | + \list |
365 | + \li - double tapping/left mouse clicking over the content, when the word that |
366 | + had been tapped over will be selected |
367 | + \li - by pressing and dragging the selection cursor over the text input. Note |
368 | + that there has to be a delay of approx. 200 ms between the press and drag |
369 | + gesture, time when the input switches from scroll mode to selection mode |
370 | + \endlist |
371 | + |
372 | + The input is focused (activated) upon tap/left mouse button release. The cursor |
373 | + will be placed at the position the mouse/tap point at release time. If the click |
374 | + is happening on a selected area, the selection will be cleared. Long press above |
375 | + a selected area brings up the clipboard context menu. When the long press happens |
376 | + over a non-selected area, the cursor will be moved to the position and the component |
377 | + enters in selection mode. The selection mode can also be activated by tapping and |
378 | + keeping the tap/mouse over for approx 300 ms. If there is a move during this time, |
379 | + the component enters into scrolling mode. The mode is exited once the scrolling |
380 | + finishes. During the scrolling mode the selected text is preserved. |
381 | + |
382 | + \note During text selection all interactive parent Flickables are turned off. |
383 | */ |
384 | |
385 | StyledItem { |
386 | @@ -708,7 +724,7 @@ |
387 | */ |
388 | function forceActiveFocus() |
389 | { |
390 | - internal.activateEditor(); |
391 | + inputHandler.activateInput(); |
392 | } |
393 | |
394 | // logic |
395 | @@ -721,6 +737,13 @@ |
396 | // activation area on mouse click |
397 | // the editor activates automatically when pressed in the editor control, |
398 | // however that one can be slightly spaced to the main control area |
399 | + MouseArea { |
400 | + anchors.fill: parent |
401 | + enabled: internal.frameSpacing > 0 |
402 | + // activate input when pressed on the frame |
403 | + preventStealing: false |
404 | + Ubuntu.Mouse.forwardTo: [inputHandler] |
405 | + } |
406 | |
407 | //internals |
408 | |
409 | @@ -744,8 +767,6 @@ |
410 | LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft |
411 | LayoutMirroring.childrenInherit: true |
412 | |
413 | - /*!\internal */ |
414 | - property alias __internal: internal |
415 | QtObject { |
416 | id: internal |
417 | // public property locals enabling aliasing |
418 | @@ -757,23 +778,8 @@ |
419 | property real inputAreaWidth: control.width - 2 * frameSpacing |
420 | property real inputAreaHeight: control.height - 2 * frameSpacing |
421 | //selection properties |
422 | - property bool draggingMode: false |
423 | - property bool selectionMode: false |
424 | property bool prevShowCursor |
425 | |
426 | - signal popupTriggered(int pos) |
427 | - |
428 | - onDraggingModeChanged: { |
429 | - if (draggingMode) selectionMode = false; |
430 | - } |
431 | - onSelectionModeChanged: { |
432 | - if (selectionMode) |
433 | - draggingMode = false; |
434 | - else { |
435 | - toggleSelectionCursors(false); |
436 | - } |
437 | - } |
438 | - |
439 | function toggleSelectionCursors(show) |
440 | { |
441 | if (!show) { |
442 | @@ -788,25 +794,6 @@ |
443 | } |
444 | } |
445 | |
446 | - function activateEditor() |
447 | - { |
448 | - if (!editor.activeFocus) |
449 | - editor.forceActiveFocus(); |
450 | - else |
451 | - showInputPanel(); |
452 | - |
453 | - } |
454 | - |
455 | - function showInputPanel() |
456 | - { |
457 | - if (!Qt.inputMethod.visible) |
458 | - Qt.inputMethod.show(); |
459 | - } |
460 | - function hideInputPanel() |
461 | - { |
462 | - Qt.inputMethod.hide(); |
463 | - } |
464 | - |
465 | function linesHeight(lines) |
466 | { |
467 | var lineHeight = editor.font.pixelSize * lines + lineSpacing * lines |
468 | @@ -822,24 +809,6 @@ |
469 | control.height = linesHeight(MathUtils.clamp(control.lineCount, 1, max)); |
470 | } |
471 | } |
472 | - |
473 | - function enterSelectionMode(x, y) |
474 | - { |
475 | - if (undefined !== x && undefined !== y) { |
476 | - control.cursorPosition = control.positionAt(x, y); |
477 | - control.moveCursorSelection(control.cursorPosition + 1); |
478 | - } |
479 | - toggleSelectionCursors(true); |
480 | - } |
481 | - |
482 | - function positionCursor(x, y) { |
483 | - var cursorPos = control.positionAt(x, y); |
484 | - if (control.selectedText === "") |
485 | - control.cursorPosition = cursorPos; |
486 | - else if (control.selectionStart > cursorPos || control.selectionEnd < cursorPos) { |
487 | - control.cursorPosition = cursorPos; |
488 | - } |
489 | - } |
490 | } |
491 | |
492 | // grab Enter/Return keys which may be stolen from parent components of TextArea |
493 | @@ -869,7 +838,7 @@ |
494 | popover: control.popover |
495 | visible: editor.cursorVisible |
496 | |
497 | - Component.onCompleted: internal.popupTriggered.connect(cursorItem.openPopover) |
498 | + Component.onCompleted: inputHandler.pressAndHold.connect(cursorItem.openPopover) |
499 | } |
500 | } |
501 | // selection cursor loader |
502 | @@ -922,6 +891,7 @@ |
503 | } |
504 | Flickable { |
505 | id: flicker |
506 | + objectName: "textarea_scroller" |
507 | anchors { |
508 | fill: parent |
509 | margins: internal.frameSpacing |
510 | @@ -929,38 +899,23 @@ |
511 | clip: true |
512 | contentWidth: editor.paintedWidth |
513 | contentHeight: editor.paintedHeight |
514 | - interactive: !autoSize || (autoSize && maximumLineCount > 0) |
515 | // do not allow rebounding |
516 | boundsBehavior: Flickable.StopAtBounds |
517 | - pressDelay: 500 |
518 | - |
519 | - function ensureVisible(r) |
520 | - { |
521 | - if (moving || flicking) |
522 | - return; |
523 | - if (contentX >= r.x) |
524 | - contentX = r.x; |
525 | - else if (contentX+width <= r.x+r.width) |
526 | - contentX = r.x+r.width-width; |
527 | - if (contentY >= r.y) |
528 | - contentY = r.y; |
529 | - else if (contentY+height <= r.y+r.height) |
530 | - contentY = r.y+r.height-height; |
531 | - } |
532 | |
533 | // editor |
534 | // Images are not shown when text contains <img> tags |
535 | // bug to watch: https://bugreports.qt-project.org/browse/QTBUG-27071 |
536 | TextEdit { |
537 | + objectName: "textarea_input" |
538 | readOnly: false |
539 | id: editor |
540 | focus: true |
541 | - onCursorRectangleChanged: flicker.ensureVisible(cursorRectangle) |
542 | width: internal.inputAreaWidth |
543 | height: Math.max(internal.inputAreaHeight, editor.contentHeight) |
544 | wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere |
545 | mouseSelectionMode: TextEdit.SelectWords |
546 | selectByMouse: false |
547 | + activeFocusOnPress: false |
548 | cursorDelegate: cursor |
549 | color: control.__styleInstance.color |
550 | selectedTextColor: Theme.palette.selected.foregroundText |
551 | @@ -972,55 +927,15 @@ |
552 | // autosize handling |
553 | onLineCountChanged: internal.frameSize() |
554 | |
555 | - // remove selection when typing starts or input method start entering text |
556 | - onInputMethodComposingChanged: { |
557 | - if (inputMethodComposing) |
558 | - internal.selectionMode = false; |
559 | - } |
560 | - Keys.onPressed: { |
561 | - if ((event.text !== "")) |
562 | - internal.selectionMode = false; |
563 | - } |
564 | - Keys.onReleased: { |
565 | - // selection positioners are updated after the keypress |
566 | - if (selectionStart == selectionEnd) |
567 | - internal.selectionMode = false; |
568 | - } |
569 | - |
570 | - // handling text selection |
571 | - MouseArea { |
572 | - id: handler |
573 | - enabled: control.enabled && control.activeFocusOnPress |
574 | + // input selection and navigation handling |
575 | + Ubuntu.Mouse.forwardTo: [inputHandler] |
576 | + InputHandler { |
577 | + id: inputHandler |
578 | anchors.fill: parent |
579 | - propagateComposedEvents: true |
580 | - |
581 | - onPressed: { |
582 | - internal.activateEditor(); |
583 | - internal.draggingMode = true; |
584 | - } |
585 | - onPressAndHold: { |
586 | - // move mode gets false if there was a mouse move after the press; |
587 | - // this is needed as Flickable will send a pressAndHold in case of |
588 | - // press -> move-pressed ->stop-and-hold-pressed gesture is performed |
589 | - if (!internal.draggingMode) |
590 | - return; |
591 | - internal.draggingMode = false; |
592 | - // open popup |
593 | - internal.positionCursor(mouse.x, mouse.y); |
594 | - internal.popupTriggered(editor.cursorPosition); |
595 | - } |
596 | - onReleased: { |
597 | - internal.draggingMode = false; |
598 | - } |
599 | - onDoubleClicked: { |
600 | - internal.activateEditor(); |
601 | - if (control.selectByMouse) |
602 | - control.selectWord(); |
603 | - } |
604 | - onClicked: { |
605 | - internal.activateEditor(); |
606 | - internal.positionCursor(mouse.x, mouse.y); |
607 | - } |
608 | + main: control |
609 | + input: editor |
610 | + flickable: flicker |
611 | + selectionModeTimeout: control.__styleInstance.selectionModeTimeout |
612 | } |
613 | } |
614 | } |
615 | |
616 | === modified file 'modules/Ubuntu/Components/TextField.qml' |
617 | --- modules/Ubuntu/Components/TextField.qml 2014-04-20 19:25:12 +0000 |
618 | +++ modules/Ubuntu/Components/TextField.qml 2014-04-25 05:29:38 +0000 |
619 | @@ -16,6 +16,7 @@ |
620 | |
621 | import QtQuick 2.0 |
622 | import Ubuntu.Unity.Action 1.1 as UnityActions |
623 | +import Ubuntu.Components 0.1 as Ubuntu |
624 | |
625 | /*! |
626 | \qmltype TextField |
627 | @@ -70,6 +71,34 @@ |
628 | } |
629 | } |
630 | \endqml |
631 | + |
632 | + \section2 Scrolling and text selection |
633 | + The input is activated when the tap or mouse is released after being pressed |
634 | + over the component. |
635 | + |
636 | + The text can be scrolled horizontally by swiping over the content both when |
637 | + the component is active or inactive. |
638 | + |
639 | + The content can be selected in the following ways: |
640 | + \list |
641 | + \li - double tapping/left mouse clicking over the content, when the word that |
642 | + had been tapped over will be selected |
643 | + \li - by pressing and dragging the selection cursor over the text input. Note |
644 | + that there has to be a delay of approx. 200 ms between the press and drag |
645 | + gesture, time when the input switches from scroll mode to selection mode |
646 | + \endlist |
647 | + |
648 | + The input is focused (activated) upon tap/left mouse button release. The cursor |
649 | + will be placed at the position the mouse/tap point at release time. If the click |
650 | + is happening on a selected area, the selection will be cleared. Long press above |
651 | + a selected area brings up the clipboard context menu. When the long press happens |
652 | + over a non-selected area, the cursor will be moved to the position and the component |
653 | + enters in selection mode. The selection mode can also be activated by tapping and |
654 | + keeping the tap/mouse over for approx 300 ms. If there is a move during this time, |
655 | + the component enters into scrolling mode. The mode is exited once the scrolling |
656 | + finishes. During the scrolling mode the selected text is preserved. |
657 | + |
658 | + \note During text selection all interactive parent Flickables are turned off. |
659 | */ |
660 | |
661 | ActionItem { |
662 | @@ -98,10 +127,11 @@ |
663 | property bool hasClearButton: true |
664 | |
665 | /*! |
666 | - \preliminary |
667 | + \deprecated |
668 | Component to be shown and used instead of the default On Screen Keyboard. |
669 | */ |
670 | property Component customSoftwareInputPanel |
671 | + onCustomSoftwareInputPanelChanged: console.error("customSoftwareInputPanel property deprecated.") |
672 | |
673 | /*! |
674 | The property overrides the default popover of the TextField. When set, the TextField |
675 | @@ -146,10 +176,8 @@ |
676 | /*! |
677 | Whether the TextField should gain active focus on a mouse press. By default |
678 | this is set to true. |
679 | - |
680 | - \qmlproperty bool activeFocusOnPress |
681 | */ |
682 | - property alias activeFocusOnPress: editor.activeFocusOnPress |
683 | + property bool activeFocusOnPress: true |
684 | |
685 | /*! |
686 | Whether the TextField should scroll when the text is longer than the width. |
687 | @@ -446,11 +474,8 @@ |
688 | |
689 | If false, the user cannot use the mouse to select text, only can use it to |
690 | focus the input. |
691 | - |
692 | - \qmlproperty bool selectByMouse |
693 | - \preliminary |
694 | */ |
695 | - property alias selectByMouse: virtualKbdHandler.enabled |
696 | + property bool selectByMouse: true |
697 | |
698 | /*! |
699 | This read-only property provides the text currently selected in the text input. |
700 | @@ -719,7 +744,7 @@ |
701 | */ |
702 | function forceActiveFocus() |
703 | { |
704 | - internal.activateEditor(); |
705 | + inputHandler.activateInput(); |
706 | } |
707 | |
708 | /*! |
709 | @@ -817,25 +842,20 @@ |
710 | anchors.fill: parent |
711 | // us it only when there is space between the frame and input |
712 | enabled: internal.spacing > 0 |
713 | - onClicked: internal.activateEditor() |
714 | + preventStealing: false |
715 | + // forward mouse events to input so we can handle those uniformly |
716 | + Ubuntu.Mouse.forwardTo: [inputHandler] |
717 | } |
718 | |
719 | Text { id: fontHolder } |
720 | |
721 | //internals |
722 | - /*! \internal */ |
723 | - property alias __internal: internal |
724 | QtObject { |
725 | id: internal |
726 | // array of borders in left, top, right, bottom order |
727 | - property bool textChanged: false |
728 | property real spacing: control.__styleInstance.overlaySpacing |
729 | property real lineSpacing: units.dp(3) |
730 | property real lineSize: editor.font.pixelSize + lineSpacing |
731 | - //selection properties |
732 | - property bool selectionMode: false |
733 | - |
734 | - signal popupTriggered() |
735 | |
736 | property int type: action ? action.parameterType : 0 |
737 | onTypeChanged: { |
738 | @@ -847,54 +867,6 @@ |
739 | || type == UnityActions.Action.Real) |
740 | inputMethodHints = Qt.ImhDigitsOnly |
741 | } |
742 | - |
743 | - function activateEditor() |
744 | - { |
745 | - if (!editor.activeFocus) |
746 | - editor.forceActiveFocus(); |
747 | - else |
748 | - showInputPanel(); |
749 | - } |
750 | - |
751 | - function showInputPanel() |
752 | - { |
753 | - if (control.customSoftwareInputPanel != undefined) { |
754 | - // TODO implement once we have the SIP ready |
755 | - } else { |
756 | - if (!Qt.inputMethod.visible) |
757 | - Qt.inputMethod.show(); |
758 | - } |
759 | - textChanged = false; |
760 | - } |
761 | - function hideInputPanel() |
762 | - { |
763 | - if (control.customSoftwareInputPanel != undefined) { |
764 | - // TODO implement once we have the SIP ready |
765 | - } else { |
766 | - Qt.inputMethod.hide(); |
767 | - } |
768 | - // emit accepted signal if changed |
769 | - if (textChanged) |
770 | - control.accepted(); |
771 | - } |
772 | - // reset selection |
773 | - function resetEditorSelection(mouseX) |
774 | - { |
775 | - editor.cursorPosition = editor.positionAt(mouseX); |
776 | - } |
777 | - |
778 | - // positions the cursor depending on whether there is a selection active or not |
779 | - function positionCursor(x) { |
780 | - |
781 | - var cursorPos = control.positionAt(x); |
782 | - if (control.selectedText === "") { |
783 | - control.cursorPosition = cursorPos; |
784 | - } |
785 | - // If target cursor position is outside selection then cancel selection and move cursor |
786 | - else if (control.selectionStart > cursorPos || control.selectionEnd < cursorPos) { |
787 | - control.cursorPosition = cursorPos; |
788 | - } |
789 | - } |
790 | } |
791 | |
792 | //left pane |
793 | @@ -947,7 +919,7 @@ |
794 | popover: control.popover |
795 | visible: editor.cursorVisible |
796 | |
797 | - Component.onCompleted: internal.popupTriggered.connect(openPopover) |
798 | + Component.onCompleted: inputHandler.pressAndHold.connect(openPopover) |
799 | } |
800 | } |
801 | |
802 | @@ -999,79 +971,55 @@ |
803 | |
804 | |
805 | // text input |
806 | - TextInput { |
807 | - id: editor |
808 | - // FocusScope will forward focus to this component |
809 | - focus: true |
810 | + Flickable { |
811 | + id: flicker |
812 | + objectName: "textfield_scroller" |
813 | anchors { |
814 | left: leftPane.right |
815 | right: clearButton.left |
816 | + top: parent.top |
817 | + bottom: parent.bottom |
818 | margins: internal.spacing |
819 | - verticalCenter: parent.verticalCenter |
820 | } |
821 | - // get the control's style |
822 | + topMargin: internal.spacing |
823 | + // do not allow rebounding |
824 | + boundsBehavior: Flickable.StopAtBounds |
825 | + // need to forward events as events occurred on topMargin area are not grabbed by the MouseArea. |
826 | + Ubuntu.Mouse.forwardTo: [inputHandler] |
827 | + |
828 | clip: true |
829 | - onTextChanged: internal.textChanged = true |
830 | - cursorDelegate: cursor |
831 | - color: control.__styleInstance.color |
832 | - selectedTextColor: Theme.palette.selected.foregroundText |
833 | - selectionColor: Theme.palette.selected.foreground |
834 | - font.pixelSize: FontUtils.sizeToPixels("medium") |
835 | - passwordCharacter: "\u2022" |
836 | - // forward keys to the root element so it can be captured outside of it |
837 | - Keys.forwardTo: [control] |
838 | - |
839 | - // handle virtual keyboard and cursor positioning, as the MouseArea overrides |
840 | - // those functionalities of the TextInput |
841 | - MouseArea { |
842 | - id: virtualKbdHandler |
843 | - anchors.fill: parent |
844 | - hoverEnabled: true |
845 | - preventStealing: true |
846 | - |
847 | - onClicked: { |
848 | - // activate control |
849 | - if (!control.activeFocus) { |
850 | - internal.activateEditor(); |
851 | - // set cursor position if no selection was previously set |
852 | - internal.positionCursor(mouse.x) |
853 | - } else if (!internal.selectionMode){ |
854 | - // reset selection and move cursor unde mouse click |
855 | - internal.resetEditorSelection(mouse.x); |
856 | - } else if (internal.selectionMode) { |
857 | - // reset selection mode (onReleased is triggered prior to onClicked |
858 | - // and resetting selection mode there would cause to enter in the\ |
859 | - // previous if-clause |
860 | - internal.selectionMode = false; |
861 | - } |
862 | - } |
863 | - |
864 | - onPressAndHold: { |
865 | - internal.activateEditor(); |
866 | - internal.positionCursor(mouse.x); |
867 | - internal.popupTriggered(); |
868 | - } |
869 | - |
870 | - onDoubleClicked: { |
871 | - // select word under doubletap |
872 | - if (!control.activeFocus) |
873 | - return; |
874 | - editor.selectWord(); |
875 | - internal.selectionMode = false; |
876 | - } |
877 | - onPressed: { |
878 | - // don't do anything while the control is inactive |
879 | - if (!control.activeFocus || (pressedButtons != Qt.LeftButton)) |
880 | - return; |
881 | - internal.activateEditor(); |
882 | - if (control.selectedText === "") { |
883 | - internal.resetEditorSelection(mouse.x); |
884 | - internal.selectionMode = true; |
885 | - } |
886 | - } |
887 | - onReleased: { |
888 | - if (!containsMouse) |
889 | - internal.selectionMode = false; |
890 | + contentWidth: editor.contentWidth |
891 | + contentHeight: editor.contentHeight |
892 | + |
893 | + TextInput { |
894 | + id: editor |
895 | + // FocusScope will forward focus to this component |
896 | + focus: true |
897 | + anchors.verticalCenter: parent.verticalCenter |
898 | + // get the control's style |
899 | + clip: true |
900 | + cursorDelegate: cursor |
901 | + color: control.__styleInstance.color |
902 | + selectedTextColor: Theme.palette.selected.foregroundText |
903 | + selectionColor: Theme.palette.selected.foreground |
904 | + font.pixelSize: FontUtils.sizeToPixels("medium") |
905 | + passwordCharacter: "\u2022" |
906 | + // forward keys to the root element so it can be captured outside of it |
907 | + Keys.forwardTo: [control] |
908 | + |
909 | + // overrides |
910 | + selectByMouse: false |
911 | + activeFocusOnPress: false |
912 | + |
913 | + // input selection and navigation handling |
914 | + Ubuntu.Mouse.forwardTo: [inputHandler] |
915 | + InputHandler { |
916 | + id: inputHandler |
917 | + anchors.fill: parent |
918 | + main: control |
919 | + input: editor |
920 | + flickable: flicker |
921 | + selectionModeTimeout: control.__styleInstance.selectionModeTimeout |
922 | } |
923 | } |
924 | } |
925 | |
926 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml' |
927 | --- modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml 2014-04-20 19:25:12 +0000 |
928 | +++ modules/Ubuntu/Components/Themes/Ambiance/TextAreaStyle.qml 2014-04-25 05:29:38 +0000 |
929 | @@ -38,6 +38,11 @@ |
930 | property real frameSpacing: units.gu(1) |
931 | property real overlaySpacing: units.gu(0.5) |
932 | |
933 | + /*! |
934 | + Property holding the timeout in milliseconds the component enters into selection mode. |
935 | + */ |
936 | + property int selectionModeTimeout: 300 |
937 | + |
938 | anchors.fill: parent |
939 | |
940 | z: -1 |
941 | @@ -47,11 +52,6 @@ |
942 | onErrorChanged: (error) ? visuals.errorColor : visuals.backgroundColor; |
943 | color: visuals.backgroundColor; |
944 | anchors.fill: parent |
945 | - |
946 | - MouseArea { |
947 | - anchors.fill: parent |
948 | - onPressed: if (!styledItem.activeFocus && styledItem.activeFocusOnPress) styledItem.forceActiveFocus() |
949 | - } |
950 | } |
951 | |
952 | Loader { |
953 | |
954 | === modified file 'modules/Ubuntu/Components/qmldir' |
955 | --- modules/Ubuntu/Components/qmldir 2014-04-20 19:25:12 +0000 |
956 | +++ modules/Ubuntu/Components/qmldir 2014-04-25 05:29:38 +0000 |
957 | @@ -47,6 +47,7 @@ |
958 | |
959 | internal TextCursor TextCursor.qml |
960 | internal TextInputPopover TextInputPopover.qml |
961 | +internal InputHandler InputHandler.qml |
962 | |
963 | #version 1.0 |
964 | Action 1.0 Action.qml |
965 | |
966 | === modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml' |
967 | --- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-20 19:25:12 +0000 |
968 | +++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-25 05:29:38 +0000 |
969 | @@ -125,6 +125,7 @@ |
970 | if (pressTimeout !== undefined && pressTimeout > 0) { |
971 | wait(pressTimeout); |
972 | } |
973 | + mouseMove(item, x, y, button, modifiers, delay); |
974 | for (var i = 1; i <= steps; i++) { |
975 | // mouse moves are all processed immediately, without delay in between events |
976 | mouseMove(item, x + i * ddx, y + i * ddy, -1, button); |
977 | |
978 | === added directory 'tests/resources/inputs' |
979 | === added file 'tests/resources/inputs/TextInputs.qml' |
980 | --- tests/resources/inputs/TextInputs.qml 1970-01-01 00:00:00 +0000 |
981 | +++ tests/resources/inputs/TextInputs.qml 2014-04-25 05:29:38 +0000 |
982 | @@ -0,0 +1,85 @@ |
983 | +/* |
984 | + * Copyright 2014 Canonical Ltd. |
985 | + * |
986 | + * This program is free software; you can redistribute it and/or modify |
987 | + * it under the terms of the GNU Lesser General Public License as published by |
988 | + * the Free Software Foundation; version 3. |
989 | + * |
990 | + * This program is distributed in the hope that it will be useful, |
991 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
992 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
993 | + * GNU Lesser General Public License for more details. |
994 | + * |
995 | + * You should have received a copy of the GNU Lesser General Public License |
996 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
997 | + */ |
998 | + |
999 | +import QtQuick 2.0 |
1000 | +import Ubuntu.Components 0.1 |
1001 | +import Ubuntu.Components.ListItems 0.1 |
1002 | +import Ubuntu.Components.Popups 0.1 |
1003 | + |
1004 | +MainView { |
1005 | + id: main |
1006 | + width: units.gu(40) |
1007 | + height: units.gu(71) |
1008 | + |
1009 | + applicationName: "TextInputs" |
1010 | + |
1011 | + Column { |
1012 | + anchors.fill: parent |
1013 | + TextArea { |
1014 | + id: topLine |
1015 | + autoSize: true |
1016 | + maximumLineCount: 0 |
1017 | + text: "Lorem Ipsum is simply dummy text\nof the printing and typesetting\nindustry.\n" |
1018 | + } |
1019 | + Flickable { |
1020 | + width: parent.width |
1021 | + height: parent.height - topLine.height |
1022 | + objectName: "MainView_Flickable" |
1023 | + contentHeight: column.childrenRect.height |
1024 | + onMovingChanged: print(objectName, "moving") |
1025 | + Column { |
1026 | + id: column |
1027 | + anchors.horizontalCenter: parent.horizontalCenter |
1028 | + spacing: units.gu(1) |
1029 | + TextField { |
1030 | + id: field |
1031 | + objectName: "Standard" |
1032 | + width: units.gu(30) |
1033 | + text: "The orange (specifically, the sweet orange) is the fruit of the citrus species Citrus × sinensis in the family Rutaceae." |
1034 | + } |
1035 | + Button { |
1036 | + text: "home" |
1037 | + onClicked: field.cursorPosition = 0; |
1038 | + } |
1039 | + |
1040 | + TextField { |
1041 | + objectName: "Preserving" |
1042 | + width: units.gu(30) |
1043 | + text: "Second line." |
1044 | + selectByMouse: false |
1045 | + placeholderText: "yeeeeewww!" |
1046 | + persistentSelection: true |
1047 | + } |
1048 | + TextArea { |
1049 | + text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." |
1050 | + } |
1051 | + TextArea { |
1052 | + text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." |
1053 | + persistentSelection: true |
1054 | + } |
1055 | + TextArea { |
1056 | + autoSize: true |
1057 | + maximumLineCount: 0 |
1058 | + text: "Lorem Ipsum is simply dummy text\nof the printing and typesetting\nindustry.\n" |
1059 | + } |
1060 | + TextArea { |
1061 | + autoSize: true |
1062 | + maximumLineCount: 5 |
1063 | + } |
1064 | + } |
1065 | + } |
1066 | + } |
1067 | +} |
1068 | |
1069 | === modified file 'tests/unit_x11/tst_components/tst_textarea.qml' |
1070 | --- tests/unit_x11/tst_components/tst_textarea.qml 2014-04-20 19:25:12 +0000 |
1071 | +++ tests/unit_x11/tst_components/tst_textarea.qml 2014-04-25 05:29:38 +0000 |
1072 | @@ -16,56 +16,58 @@ |
1073 | |
1074 | import QtQuick 2.0 |
1075 | import QtTest 1.0 |
1076 | +import Ubuntu.Test 1.0 |
1077 | import Ubuntu.Components 1.0 |
1078 | import Ubuntu.Components.ListItems 1.0 as ListItem |
1079 | |
1080 | Item { |
1081 | - width: 200; height: 200 |
1082 | + id: main |
1083 | + width: units.gu(50); height: units.gu(100) |
1084 | |
1085 | property bool hasOSK: QuickUtils.inputMethodProvider !== "" |
1086 | |
1087 | - TextArea { |
1088 | - id: textArea |
1089 | - SignalSpy { |
1090 | - id: signalSpy |
1091 | - target: parent |
1092 | - } |
1093 | - |
1094 | - property int keyPressData |
1095 | - property int keyReleaseData |
1096 | - Keys.onPressed: keyPressData = event.key |
1097 | - Keys.onReleased: keyReleaseData = event.key |
1098 | - } |
1099 | - |
1100 | - TextArea { |
1101 | - id: colorTest |
1102 | - color: colorTest.text.length < 4 ? "#0000ff" : "#00ff00" |
1103 | - } |
1104 | - |
1105 | - TextEdit { |
1106 | - id: textEdit |
1107 | - } |
1108 | - |
1109 | - ListItem.Empty { |
1110 | - id: listItem |
1111 | - height: 200 |
1112 | - anchors.left: parent.left |
1113 | - |
1114 | - anchors.right: parent.right |
1115 | - SignalSpy { |
1116 | - id: listItemSpy |
1117 | - signalName: "clicked" |
1118 | - target: listItem |
1119 | - } |
1120 | - |
1121 | - TextArea { |
1122 | - id: input |
1123 | - anchors.fill: parent |
1124 | - Component.onCompleted: forceActiveFocus() |
1125 | - } |
1126 | - } |
1127 | - |
1128 | - Item { |
1129 | + Column { |
1130 | + TextArea { |
1131 | + id: textArea |
1132 | + SignalSpy { |
1133 | + id: signalSpy |
1134 | + target: parent |
1135 | + } |
1136 | + |
1137 | + property int keyPressData |
1138 | + property int keyReleaseData |
1139 | + Keys.onPressed: keyPressData = event.key |
1140 | + Keys.onReleased: keyReleaseData = event.key |
1141 | + } |
1142 | + |
1143 | + TextArea { |
1144 | + id: colorTest |
1145 | + color: colorTest.text.length < 4 ? "#0000ff" : "#00ff00" |
1146 | + } |
1147 | + |
1148 | + TextEdit { |
1149 | + id: textEdit |
1150 | + } |
1151 | + |
1152 | + ListItem.Empty { |
1153 | + id: listItem |
1154 | + height: 200 |
1155 | + anchors.left: parent.left |
1156 | + |
1157 | + anchors.right: parent.right |
1158 | + SignalSpy { |
1159 | + id: listItemSpy |
1160 | + signalName: "clicked" |
1161 | + target: listItem |
1162 | + } |
1163 | + |
1164 | + TextArea { |
1165 | + id: input |
1166 | + anchors.fill: parent |
1167 | + Component.onCompleted: forceActiveFocus() |
1168 | + } |
1169 | + } |
1170 | + |
1171 | TextArea { |
1172 | id: t1 |
1173 | objectName: "t1" |
1174 | @@ -74,13 +76,42 @@ |
1175 | id: t2 |
1176 | objectName: "t2" |
1177 | } |
1178 | + |
1179 | + TextArea { |
1180 | + id: longText |
1181 | + text: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book." |
1182 | + } |
1183 | } |
1184 | |
1185 | - TestCase { |
1186 | + UbuntuTestCase { |
1187 | name: "TextAreaAPI" |
1188 | when: windowShown |
1189 | |
1190 | - function test_1_activate() { |
1191 | + function cleanup() { |
1192 | + textArea.focus = |
1193 | + colorTest.focus = |
1194 | + textEdit.focus = |
1195 | + input.focus = |
1196 | + t1.focus = |
1197 | + t2.focus = |
1198 | + longText.focus = false; |
1199 | + longText.cursorPosition = 0; |
1200 | + var scroller = findChild(longText, "textarea_scroller"); |
1201 | + scroller.contentY = 0; |
1202 | + scroller.contentX = 0; |
1203 | + |
1204 | + textArea.text = ""; |
1205 | + textArea.textFormat = TextEdit.PlainText; |
1206 | + input.textFormat = TextEdit.PlainText; |
1207 | + input.text = ""; |
1208 | + input.cursorPosition = 0; |
1209 | + |
1210 | + // empty event buffer |
1211 | + wait(200); |
1212 | + } |
1213 | + |
1214 | + |
1215 | + function test_activate() { |
1216 | textArea.forceActiveFocus(); |
1217 | compare(textArea.activeFocus, true, "TextArea is active"); |
1218 | } |
1219 | @@ -150,7 +181,7 @@ |
1220 | compare(textArea.lineCount,textEdit.lineCount,"TextArea.lineCount is same as TextEdit.lineCount") |
1221 | } |
1222 | |
1223 | - function test_1_mouseSelectionMode() { |
1224 | + function test_0_mouseSelectionMode() { |
1225 | compare(textArea.mouseSelectionMode, TextEdit.SelectWords,"TextArea.mouseSelectionMode is SelectWords") |
1226 | } |
1227 | |
1228 | @@ -206,45 +237,45 @@ |
1229 | compare(textArea.wrapMode,TextEdit.Wrap,"TextArea.wrapMode is TextEdit.Wrap") |
1230 | } |
1231 | |
1232 | - // TextArea specific properties |
1233 | - function test_1_highlighted() { |
1234 | + // TextArea specific properties |
1235 | + function test_0_highlighted() { |
1236 | compare(textArea.highlighted, textArea.focus, "highlighted is the same as focused"); |
1237 | } |
1238 | |
1239 | - function test_1_contentHeight() { |
1240 | + function test_contentHeight() { |
1241 | compare(textArea.contentHeight>0,true,"contentHeight over 0 units on default") |
1242 | var newValue = 200; |
1243 | textArea.contentHeight = newValue; |
1244 | compare(textArea.contentHeight,newValue,"set/get"); |
1245 | } |
1246 | |
1247 | - function test_1_contentWidth() { |
1248 | + function test_contentWidth() { |
1249 | compare(textArea.contentWidth,units.gu(30),"contentWidth is 30 units on default") |
1250 | var newValue = 200; |
1251 | textArea.contentWidth = newValue; |
1252 | compare(textArea.contentWidth,newValue,"set/get"); |
1253 | } |
1254 | |
1255 | - function test_1_placeholderText() { |
1256 | + function test_placeholderText() { |
1257 | compare(textArea.placeholderText,"","placeholderText is '' on default") |
1258 | var newValue = "Hello Placeholder"; |
1259 | textArea.placeholderText = newValue; |
1260 | compare(textArea.placeholderText,newValue,"set/get"); |
1261 | } |
1262 | |
1263 | - function test_1_autoSize() { |
1264 | + function test_autoSize() { |
1265 | compare(textArea.autoSize,false,"TextArea.autoSize is set to false"); |
1266 | var newValue = true; |
1267 | textArea.autoSize = newValue; |
1268 | compare(textArea.autoSize, newValue,"set/get"); |
1269 | } |
1270 | |
1271 | - function test_1_baseUrl() { |
1272 | + function test_0_baseUrl() { |
1273 | expectFail("","TODO") |
1274 | compare(textArea.baseUrl,"tst_textarea.qml","baseUrl is QML file instantiating the TextArea item on default") |
1275 | } |
1276 | |
1277 | - function test_1_displayText() { |
1278 | + function test_displayText() { |
1279 | compare(textArea.displayText,"","displayText is '' on default") |
1280 | var newValue = "Hello Display Text"; |
1281 | try { |
1282 | @@ -256,11 +287,11 @@ |
1283 | |
1284 | } |
1285 | |
1286 | - function test_1_popover() { |
1287 | + function test_0_popover() { |
1288 | compare(textArea.popover, undefined, "Uses default popover"); |
1289 | } |
1290 | |
1291 | - function test_1_maximumLineCount() { |
1292 | + function test_maximumLineCount() { |
1293 | compare(textArea.maximumLineCount,5,"maximumLineCount is 0 on default") |
1294 | var newValue = 10; |
1295 | textArea.maximumLineCount = newValue; |
1296 | @@ -272,7 +303,7 @@ |
1297 | compare(textArea.activeFocus, false, "TextArea is inactive"); |
1298 | } |
1299 | |
1300 | - // functions |
1301 | + // functions |
1302 | function test_copy() { |
1303 | textArea.copy(); |
1304 | } |
1305 | @@ -360,13 +391,13 @@ |
1306 | } |
1307 | |
1308 | |
1309 | - // signals |
1310 | + // signals |
1311 | function test_linkActivated() { |
1312 | signalSpy.signalName = "linkActivated"; |
1313 | compare(signalSpy.valid,true,"linkActivated signal exists") |
1314 | } |
1315 | |
1316 | - // filters |
1317 | + // filters |
1318 | function test_keyPressAndReleaseFilter() { |
1319 | textArea.visible = true; |
1320 | textArea.forceActiveFocus(); |
1321 | @@ -451,7 +482,7 @@ |
1322 | } |
1323 | |
1324 | // make it to b ethe last test case executed |
1325 | - function test_zz_TextareaInListItem_RichTextEnterCaptured() { |
1326 | + function test_TextareaInListItem_RichTextEnterCaptured() { |
1327 | textArea.text = "a<br />b"; |
1328 | textArea.textFormat = TextEdit.RichText; |
1329 | input.forceActiveFocus(); |
1330 | @@ -461,5 +492,232 @@ |
1331 | keyClick(Qt.Key_Return); |
1332 | compare(input.text, textArea.text, "Formatted text split"); |
1333 | } |
1334 | + |
1335 | + // selection and scrolling |
1336 | + SignalSpy { |
1337 | + id: flickSpy |
1338 | + signalName: "onMovementEnded" |
1339 | + } |
1340 | + |
1341 | + function test_scroll_when_not_focused() { |
1342 | + var handler = findChild(longText, "input_handler"); |
1343 | + verify(handler); |
1344 | + |
1345 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1346 | + flickSpy.clear(); |
1347 | + verify(longText.focus == false); |
1348 | + var y = longText.height - units.gu(2); |
1349 | + var my = y / 2; |
1350 | + var x = longText.width / 2; |
1351 | + var dy = units.gu(3); |
1352 | + flick(longText, x, y, 0, -dy); |
1353 | + verify(longText.focus); |
1354 | + compare(flickSpy.count, 0, "The input had scrolled while inactive"); |
1355 | + } |
1356 | + |
1357 | + function test_scroll_when_focused() { |
1358 | + longText.focus = true; |
1359 | + var handler = findChild(longText, "input_handler"); |
1360 | + verify(handler); |
1361 | + |
1362 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1363 | + flickSpy.clear(); |
1364 | + var y = longText.height - units.gu(2); |
1365 | + var my = y / 2; |
1366 | + var x = longText.width / 2; |
1367 | + var dy = units.gu(3); |
1368 | + compare(handler.state, "", "The input is not in default state before selection"); |
1369 | + flick(longText, x, my, 0, -dy); |
1370 | + flickSpy.wait(); |
1371 | + compare(handler.state, "", "The input has not returned to default state."); |
1372 | + } |
1373 | + |
1374 | + function test_select_by_pressAndDrag() { |
1375 | + longText.focus = true; |
1376 | + var handler = findChild(longText, "input_handler"); |
1377 | + verify(handler); |
1378 | + var dx = longText.width / 4; |
1379 | + var x = units.gu(5); |
1380 | + var y = longText.height / 2; |
1381 | + compare(handler.state, "", "The input is not in default state before selection"); |
1382 | + flick(longText, x, y, 2*dx, 0, handler.selectionModeTimeout + 50, 10); |
1383 | + verify(longText.selectedText !== ""); |
1384 | + compare(handler.state, "", "The input has not returned to default state."); |
1385 | + } |
1386 | + |
1387 | + function test_select_text_doubletap() { |
1388 | + longText.focus = true; |
1389 | + var x = units.gu(2); |
1390 | + var y = longText.height / 4; |
1391 | + mouseDoubleClick(longText, x, y); |
1392 | + expectFail("", "mouseDoubleClick fails to trigger"); |
1393 | + verify(longText.selectedText !== ""); |
1394 | + } |
1395 | + |
1396 | + function test_scroll_with_selected_text() { |
1397 | + longText.focus = true; |
1398 | + var handler = findChild(longText, "input_handler"); |
1399 | + verify(handler); |
1400 | + var y = longText.height / 2; |
1401 | + var x = longText.width / 2; |
1402 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1403 | + flickSpy.clear(); |
1404 | + |
1405 | + // select text |
1406 | + compare(handler.state, "", "The input is not in default state before selection"); |
1407 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
1408 | + verify(longText.selectedText !== ""); |
1409 | + compare(handler.state, "", "The input has not returned to default state."); |
1410 | + |
1411 | + // flick upwards |
1412 | + flick(longText, x, y, 0, -units.gu(2)); |
1413 | + flickSpy.wait(); |
1414 | + compare(handler.state, "", "The input has not returned to default state."); |
1415 | + } |
1416 | + |
1417 | + SignalSpy { |
1418 | + id: popoverSpy |
1419 | + signalName: "onPressAndHold" |
1420 | + } |
1421 | + |
1422 | + function test_press_and_hold_moves_cursor_position_and_opens_context_menu() { |
1423 | + longText.focus = true; |
1424 | + var handler = findChild(longText, "input_handler"); |
1425 | + var y = longText.height / 2; |
1426 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1427 | + flickSpy.clear(); |
1428 | + popoverSpy.target = handler; |
1429 | + popoverSpy.clear(); |
1430 | + |
1431 | + // long press |
1432 | + compare(handler.state, "", "The input is not in default state before long press"); |
1433 | + mouseLongPress(longText, units.gu(8), y); |
1434 | + waitForRendering(longText); |
1435 | + popoverSpy.wait(); |
1436 | + verify(longText.cursorPosition != 0); |
1437 | + compare(handler.state, "inactive", "The input is not in inactive state while context menu is open"); |
1438 | + |
1439 | + // cleanup, release the mouse, that should bring the handler back to defautl state |
1440 | + mouseRelease(longText, units.gu(8), y); |
1441 | + compare(handler.state, "", "The input has not returned to default state."); |
1442 | + // dismiss popover |
1443 | + mouseClick(main, 10, 10); |
1444 | + } |
1445 | + |
1446 | + function test_press_and_hold_moves_cursor_position_and_opens_context_menu_when_not_focus() { |
1447 | + var handler = findChild(longText, "input_handler"); |
1448 | + var y = longText.height / 2; |
1449 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1450 | + flickSpy.clear(); |
1451 | + popoverSpy.target = handler; |
1452 | + popoverSpy.clear(); |
1453 | + |
1454 | + // long press |
1455 | + compare(handler.state, "inactive", "The input is not in inactive state before long press"); |
1456 | + mouseLongPress(longText, units.gu(8), y); |
1457 | + waitForRendering(longText); |
1458 | + popoverSpy.wait(); |
1459 | + verify(longText.focus, "The input is not focused"); |
1460 | + verify(longText.cursorPosition != 0, "The cursor wasn't moved"); |
1461 | + compare(handler.state, "inactive", "The input is not in inactive state while the context menu is open"); |
1462 | + |
1463 | + // cleanup, release the mouse, that should bring the handler back to defautl state |
1464 | + mouseRelease(longText, units.gu(8), y); |
1465 | + compare(handler.state, "", "The input has not returned to default state."); |
1466 | + // dismiss popover |
1467 | + mouseClick(main, 10, 10); |
1468 | + } |
1469 | + |
1470 | + function test_press_and_hold_over_selected_text() { |
1471 | + longText.focus = true; |
1472 | + var handler = findChild(longText, "input_handler"); |
1473 | + var y = longText.height / 2; |
1474 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1475 | + flickSpy.clear(); |
1476 | + popoverSpy.target = handler; |
1477 | + popoverSpy.clear(); |
1478 | + |
1479 | + // select text |
1480 | + compare(handler.state, "", "The input is not in default state before long press"); |
1481 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
1482 | + waitForRendering(longText); |
1483 | + compare(handler.state, "", "The input has not returned to default state."); |
1484 | + verify(longText.selectedText !== ""); |
1485 | + |
1486 | + mouseLongPress(longText, units.gu(4), y); |
1487 | + // wait till popover is shown |
1488 | + waitForRendering(longText); |
1489 | + popoverSpy.wait(); |
1490 | + // cleanup, release the mouse, that should bring the handler back to default state |
1491 | + mouseRelease(longText, units.gu(4), y); |
1492 | + compare(handler.state, "", "The input has not returned to default state."); |
1493 | + // dismiss popover |
1494 | + mouseClick(main, 10, 10); |
1495 | + } |
1496 | + |
1497 | + function test_move_mouse_while_context_menu_open_does_not_move_selection() { |
1498 | + longText.focus = true; |
1499 | + var handler = findChild(longText, "input_handler"); |
1500 | + var y = longText.height / 2; |
1501 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1502 | + flickSpy.clear(); |
1503 | + popoverSpy.target = handler; |
1504 | + popoverSpy.clear(); |
1505 | + |
1506 | + // select text |
1507 | + compare(handler.state, "", "The input is not in default state before long press"); |
1508 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
1509 | + verify(longText.selectedText !== ""); |
1510 | + var selection = longText.selectedText; |
1511 | + compare(handler.state, "", "The input has not returned to default state."); |
1512 | + |
1513 | + mouseLongPress(longText, units.gu(4), y); |
1514 | + // wait till popover is shown |
1515 | + waitForRendering(longText); |
1516 | + popoverSpy.wait(); |
1517 | + // do some mouse moves and compare whether we have the same selection |
1518 | + mouseMoveSlowly(longText, units.gu(4), y, units.gu(4), units.gu(1), 3, 100); |
1519 | + compare(selection, longText.selectedText, "Selection differs"); |
1520 | + // cleanup, release the mouse, that should bring the handler back to default state |
1521 | + mouseRelease(longText, units.gu(8), y + units.gu(1)); |
1522 | + compare(handler.state, "", "The input has not returned to default state."); |
1523 | + mouseClick(main, 10, 10); |
1524 | + } |
1525 | + |
1526 | + function test_clear_selection_by_click_on_selection() { |
1527 | + longText.focus = true; |
1528 | + var handler = findChild(longText, "input_handler"); |
1529 | + var y = longText.height / 2; |
1530 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1531 | + flickSpy.clear(); |
1532 | + |
1533 | + // select text |
1534 | + compare(handler.state, "", "The input is not in default state before long press"); |
1535 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
1536 | + compare(handler.state, "", "The input has not returned to default state."); |
1537 | + verify(longText.selectedText !== "", "There is no selected text"); |
1538 | + |
1539 | + // click on selection |
1540 | + mouseClick(longText, units.gu(4), y); |
1541 | + verify(longText.selectedText === "", "There is still selected text"); |
1542 | + } |
1543 | + |
1544 | + function test_clear_selection_by_click_beside_selection() { |
1545 | + longText.focus = true; |
1546 | + var handler = findChild(longText, "input_handler"); |
1547 | + var y = longText.height / 2; |
1548 | + flickSpy.target = findChild(longText, "textarea_scroller"); |
1549 | + flickSpy.clear(); |
1550 | + |
1551 | + // select text |
1552 | + compare(handler.state, "", "The input is not in default state before long press"); |
1553 | + flick(longText, 0, y, units.gu(8), units.gu(4), handler.selectionModeTimeout + 50); |
1554 | + compare(handler.state, "", "The input has not returned to default state."); |
1555 | + verify(longText.selectedText !== "", "There is no text selected"); |
1556 | + |
1557 | + // click on selection |
1558 | + mouseClick(longText, units.gu(10), y); |
1559 | + verify(longText.selectedText === "", "There is still selected text"); |
1560 | + } |
1561 | } |
1562 | } |
1563 | |
1564 | === modified file 'tests/unit_x11/tst_components/tst_textarea_in_flickable.qml' |
1565 | --- tests/unit_x11/tst_components/tst_textarea_in_flickable.qml 2014-04-20 19:25:12 +0000 |
1566 | +++ tests/unit_x11/tst_components/tst_textarea_in_flickable.qml 2014-04-25 05:29:38 +0000 |
1567 | @@ -16,68 +16,94 @@ |
1568 | |
1569 | import QtQuick 2.0 |
1570 | import QtTest 1.0 |
1571 | +import Ubuntu.Test 1.0 |
1572 | import Ubuntu.Components 1.0 |
1573 | |
1574 | Item { |
1575 | id: root |
1576 | - width: 200; height: 200 |
1577 | + width: units.gu(50); height: units.gu(100) |
1578 | |
1579 | property bool hasOSK: QuickUtils.inputMethodProvider !== "" |
1580 | |
1581 | Flickable { |
1582 | id: flickable |
1583 | anchors.fill: parent |
1584 | - contentWidth: inFlickable.width |
1585 | - contentHeight: inFlickable.height |
1586 | + contentHeight: column.childrenRect.height |
1587 | clip: true |
1588 | |
1589 | - TextArea { |
1590 | - id: inFlickable |
1591 | - width: flickable.width |
1592 | - autoSize: true |
1593 | - maximumLineCount: 0 |
1594 | - text: "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1" |
1595 | + Column { |
1596 | + id: column |
1597 | + Text { |
1598 | + text: "This is a simple label on top of the Flickable" |
1599 | + } |
1600 | + |
1601 | + TextArea { |
1602 | + id: inFlickable |
1603 | + width: flickable.width |
1604 | + autoSize: true |
1605 | + maximumLineCount: 0 |
1606 | + text: "1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1" |
1607 | + } |
1608 | } |
1609 | } |
1610 | |
1611 | SignalSpy { |
1612 | - id: flickableSpy |
1613 | + id: moveSpy |
1614 | target: flickable |
1615 | - signalName: "movingChanged" |
1616 | + signalName: "onMovementEnded" |
1617 | } |
1618 | |
1619 | - TestCase { |
1620 | + UbuntuTestCase { |
1621 | name: "TextAreaInFlickableAPI" |
1622 | when: windowShown |
1623 | - // simulates a flick event between \b from and \b to points both relative to the item |
1624 | - // with a given speed |
1625 | - function flick(item, from, to, speed) { |
1626 | - var pointCount = 5; |
1627 | - if (from === undefined) |
1628 | - qtest_fail("source point not defined", 2); |
1629 | - if (to === undefined) |
1630 | - qtest_fail("destination point not defined", 2); |
1631 | - if (speed === undefined) |
1632 | - speed = -1; |
1633 | - else |
1634 | - speed /= pointCount; |
1635 | - |
1636 | - var dx = to.x - from.x; |
1637 | - var dy = to.y - from.y; |
1638 | - |
1639 | - mousePress(item, from.x, from.y); |
1640 | - for (var i = 0; i < pointCount; i++) { |
1641 | - mouseMove(item, from.x + (i + 1) * dx / pointCount, from.y + (i + 1) * dy / pointCount, speed); |
1642 | - } |
1643 | - mouseRelease(item, to.x, to.y); |
1644 | - // empty event queues |
1645 | - wait(500); |
1646 | + |
1647 | + function init() { |
1648 | + waitForRendering(flickable, 1000); |
1649 | + } |
1650 | + |
1651 | + function cleanup() { |
1652 | + flickable.contentY = 0; |
1653 | + moveSpy.clear(); |
1654 | + inFlickable.focus = false; |
1655 | + inFlickable.cursorPosition = 0; |
1656 | + // empty event buffer caused by the flick() events |
1657 | + wait(400); |
1658 | } |
1659 | |
1660 | function test_DoNotStealFlickEvents() { |
1661 | - mouseClick(inFlickable, 10, 10); |
1662 | - flick(inFlickable, Qt.point(50, 20), Qt.point(50, 0), 100); |
1663 | - tryCompare(flickableSpy, "count", 2, 200); |
1664 | + inFlickable.focus = true; |
1665 | + flick(inFlickable, 50, 150, 0, -150); |
1666 | + moveSpy.wait(); |
1667 | + } |
1668 | + |
1669 | + function test_flicker_moves_when_inactive() { |
1670 | + flick(flickable, 50, 150, 0, -150); |
1671 | + moveSpy.wait(); |
1672 | + } |
1673 | + |
1674 | + function test_select_state_locks_outer_flickable() { |
1675 | + var handler = findChild(inFlickable, "input_handler"); |
1676 | + inFlickable.focus = true; |
1677 | + // select text |
1678 | + flick(inFlickable, 50, 50, -50, -50, handler.selectionModeTimeout+ 50); |
1679 | + compare(moveSpy.count, 0, "The Flickable has moved while the TextArea was in selection mode"); |
1680 | + verify(inFlickable.selectedText !== "", "No text selected"); |
1681 | + } |
1682 | + |
1683 | + function test_scrolling_input_with_selected_text() { |
1684 | + var handler = findChild(inFlickable, "input_handler"); |
1685 | + inFlickable.focus = true; |
1686 | + // select text |
1687 | + flick(inFlickable, 50, 50, -50, -50, handler.selectionModeTimeout + 100); |
1688 | + compare(moveSpy.count, 0, "The Flickable has moved while the TextArea was in selection mode"); |
1689 | + verify(inFlickable.selectedText !== "", "No text selected"); |
1690 | + |
1691 | + // scroll |
1692 | + moveSpy.clear(); |
1693 | + flick(inFlickable, 50, 100, 0, -100); |
1694 | + // wait till the move ends |
1695 | + moveSpy.wait(); |
1696 | + verify(inFlickable.selectedText !== "", "There is still text selected"); |
1697 | } |
1698 | } |
1699 | } |
1700 | |
1701 | === modified file 'tests/unit_x11/tst_components/tst_textfield.qml' |
1702 | --- tests/unit_x11/tst_components/tst_textfield.qml 2014-04-20 19:25:12 +0000 |
1703 | +++ tests/unit_x11/tst_components/tst_textfield.qml 2014-04-25 05:29:38 +0000 |
1704 | @@ -16,12 +16,13 @@ |
1705 | |
1706 | import QtQuick 2.0 |
1707 | import QtTest 1.0 |
1708 | +import Ubuntu.Test 1.0 |
1709 | import Ubuntu.Components 1.0 |
1710 | import Ubuntu.Unity.Action 1.1 as UnityActions |
1711 | |
1712 | Item { |
1713 | id: textItem |
1714 | - width: 200; height: 200 |
1715 | + width: units.gu(50); height: units.gu(70) |
1716 | |
1717 | property bool hasOSK: QuickUtils.inputMethodProvider !== "" |
1718 | |
1719 | @@ -32,52 +33,79 @@ |
1720 | t2.focus = false; |
1721 | } |
1722 | |
1723 | - TextField { |
1724 | - id: colorTest |
1725 | - color: colorTest.text.length < 4 ? "#0000ff" : "#00ff00" |
1726 | - } |
1727 | - |
1728 | - TextField { |
1729 | - id: textField |
1730 | - SignalSpy { |
1731 | - id: signalSpy |
1732 | - target: parent |
1733 | - } |
1734 | - |
1735 | - property int keyPressData |
1736 | - property int keyReleaseData |
1737 | - Keys.onPressed: keyPressData = event.key |
1738 | - Keys.onReleased: keyReleaseData = event.key |
1739 | - action: Action { |
1740 | - enabled: true |
1741 | - name: 'spam' |
1742 | - text: 'Spam' |
1743 | - } |
1744 | - } |
1745 | - |
1746 | - Item { |
1747 | + Column { |
1748 | + TextField { |
1749 | + id: colorTest |
1750 | + color: colorTest.text.length < 4 ? "#0000ff" : "#00ff00" |
1751 | + text: "colorTest" |
1752 | + } |
1753 | + |
1754 | + TextField { |
1755 | + id: textField |
1756 | + SignalSpy { |
1757 | + id: signalSpy |
1758 | + target: parent |
1759 | + } |
1760 | + |
1761 | + property int keyPressData |
1762 | + property int keyReleaseData |
1763 | + Keys.onPressed: keyPressData = event.key |
1764 | + Keys.onReleased: keyReleaseData = event.key |
1765 | + action: Action { |
1766 | + enabled: true |
1767 | + name: 'spam' |
1768 | + text: 'Spam' |
1769 | + } |
1770 | + } |
1771 | + |
1772 | TextField { |
1773 | id: t1 |
1774 | + text: "t1" |
1775 | } |
1776 | TextField { |
1777 | id: t2 |
1778 | - } |
1779 | - } |
1780 | - |
1781 | - TextField { |
1782 | - id: enabledTextField |
1783 | - enabled: true |
1784 | - } |
1785 | - |
1786 | - TextField { |
1787 | - id: disabledTextField |
1788 | - enabled: false |
1789 | - } |
1790 | - |
1791 | - TestCase { |
1792 | + text: "t2" |
1793 | + } |
1794 | + |
1795 | + TextField { |
1796 | + id: enabledTextField |
1797 | + enabled: true |
1798 | + text: "enabledTextField" |
1799 | + } |
1800 | + |
1801 | + TextField { |
1802 | + id: disabledTextField |
1803 | + enabled: false |
1804 | + text: "disabledTextField" |
1805 | + } |
1806 | + TextField { |
1807 | + id: longText |
1808 | + text: "The orange (specifically, the sweet orange) is the fruit of the citrus species Citrus × sinensis in the family Rutaceae." |
1809 | + } |
1810 | + } |
1811 | + |
1812 | + UbuntuTestCase { |
1813 | name: "TextFieldAPI" |
1814 | when: windowShown |
1815 | |
1816 | + // initialize test objects |
1817 | + function init() { |
1818 | + longText.cursorPosition = 0; |
1819 | + } |
1820 | + |
1821 | + // empty event buffer |
1822 | + function cleanup() { |
1823 | + colorTest.focus = |
1824 | + textField.focus = |
1825 | + t1.focus = |
1826 | + t2.focus = |
1827 | + enabledTextField.focus = |
1828 | + longText.focus = false; |
1829 | + var scroller = findChild(longText, "textfield_scroller"); |
1830 | + scroller.contentX = 0; |
1831 | + wait(200); |
1832 | + } |
1833 | + |
1834 | function initTestCase() { |
1835 | textField.forceActiveFocus(); |
1836 | compare(textField.focus, true, "TextField is focused"); |
1837 | @@ -140,7 +168,9 @@ |
1838 | } |
1839 | |
1840 | function test_0_cursorVisible() { |
1841 | - compare(textField.cursorVisible, true, "cursorVisible true by default") |
1842 | + compare(textField.cursorVisible, false, "cursorVisible false by default when inactive"); |
1843 | + textField.focus = true; |
1844 | + compare(textField.cursorVisible, true, "cursorVisible true by default when active"); |
1845 | } |
1846 | |
1847 | function test_0_customSoftwareInputPanel() { |
1848 | @@ -291,7 +321,7 @@ |
1849 | compare(textField.keyReleaseData, Qt.Key_Control, "Key release filtered"); |
1850 | } |
1851 | |
1852 | - function test_1_undo_redo() { |
1853 | + function test_undo_redo() { |
1854 | textField.readOnly = false; |
1855 | textField.text = ""; |
1856 | textField.focus = true; |
1857 | @@ -304,7 +334,7 @@ |
1858 | compare(textField.text, "test", "redone"); |
1859 | } |
1860 | |
1861 | - function test_1_getText() { |
1862 | + function test_getText() { |
1863 | textField.text = "this is a longer text"; |
1864 | compare(textField.getText(0, 10), "this is a ", "getText(0, 10)"); |
1865 | compare(textField.getText(10, 0), "this is a ", "getText(10, 0)"); |
1866 | @@ -312,7 +342,7 @@ |
1867 | compare(textField.getText(4, 0), "this", "getText(4, 0)"); |
1868 | } |
1869 | |
1870 | - function test_1_removeText() { |
1871 | + function test_removeText() { |
1872 | textField.text = "this is a longer text"; |
1873 | textField.remove(0, 10); |
1874 | compare(textField.text, "longer text", "remove(0, 10)"); |
1875 | @@ -335,14 +365,14 @@ |
1876 | compare(textField.text, "this is a longer text", "select(0, 4) && remove()"); |
1877 | } |
1878 | |
1879 | - function test_1_moveCursorSelection() { |
1880 | + function test_moveCursorSelection() { |
1881 | textField.text = "this is a longer text"; |
1882 | textField.cursorPosition = 5; |
1883 | textField.moveCursorSelection(9, TextInput.SelectCharacters); |
1884 | compare(textField.selectedText, "is a", "moveCursorSelection from 5 to 9, selecting the text"); |
1885 | } |
1886 | |
1887 | - function test_1_isRightToLeft() { |
1888 | + function test_isRightToLeft() { |
1889 | textField.text = "this is a longer text"; |
1890 | compare(textField.isRightToLeft(0), false, "isRightToLeft(0)"); |
1891 | compare(textField.isRightToLeft(0, 0), false, "isRightToLeft(0, 0)"); |
1892 | @@ -393,7 +423,7 @@ |
1893 | } |
1894 | |
1895 | // need to make the very first test case, otherwise OSK detection fails on phablet |
1896 | - function test_zz_OSK_ShownWhenNextTextFieldIsFocused() { |
1897 | + function test_OSK_ShownWhenNextTextFieldIsFocused() { |
1898 | if (!hasOSK) |
1899 | expectFail("", "OSK can be tested only when present"); |
1900 | t1.focus = true; |
1901 | @@ -402,7 +432,7 @@ |
1902 | compare(Qt.inputMethod.visible, true, "OSK is shown for the second TextField"); |
1903 | } |
1904 | |
1905 | - function test_zz_RemoveOSKWhenFocusLost() { |
1906 | + function test_RemoveOSKWhenFocusLost() { |
1907 | if (!hasOSK) |
1908 | expectFail("", "OSK can be tested only when present"); |
1909 | t1.focus = true; |
1910 | @@ -411,7 +441,7 @@ |
1911 | compare(Qt.inputMethod.visible, false, "OSK is hidden when TextField looses focus"); |
1912 | } |
1913 | |
1914 | - function test_zz_ReEnabledInput() { |
1915 | + function test_ReEnabledInput() { |
1916 | textField.forceActiveFocus(); |
1917 | textField.enabled = false; |
1918 | compare(textField.enabled, false, "textField is disabled"); |
1919 | @@ -428,7 +458,7 @@ |
1920 | compare(Qt.inputMethod.visible, true, "OSK shown"); |
1921 | } |
1922 | |
1923 | - function test_zz_Trigger() { |
1924 | + function test_Trigger() { |
1925 | signalSpy.signalName = 'accepted' |
1926 | textField.enabled = true |
1927 | textField.text = 'eggs' |
1928 | @@ -436,7 +466,7 @@ |
1929 | signalSpy.wait() |
1930 | } |
1931 | |
1932 | - function test_zz_ActionInputMethodHints() { |
1933 | + function test_ActionInputMethodHints() { |
1934 | // Preset digit only for numbers |
1935 | textField.inputMethodHints = Qt.ImhNone |
1936 | textField.action.parameterType = UnityActions.Action.Integer |
1937 | @@ -464,22 +494,243 @@ |
1938 | |
1939 | function test_click_enabled_textfield_must_give_focus() { |
1940 | textField.forceActiveFocus(); |
1941 | - compare( |
1942 | - enabledTextField.focus, false, |
1943 | - 'enabledTextField is not focused'); |
1944 | - mouseClick( |
1945 | - enabledTextField, enabledTextField.width/2, |
1946 | - enabledTextField.height/2) |
1947 | - compare( |
1948 | - enabledTextField.focus, true, 'enabledTextField is focused') |
1949 | + compare(enabledTextField.focus, false, 'enabledTextField is not focused'); |
1950 | + mouseClick(enabledTextField, enabledTextField.width/2, enabledTextField.height/2); |
1951 | + compare(enabledTextField.focus, true, 'enabledTextField is focused'); |
1952 | } |
1953 | |
1954 | function test_click_disabled_textfield_must_not_give_focus() { |
1955 | - mouseClick( |
1956 | - disabledTextField, disabledTextField.width/2, |
1957 | - disabledTextField.height/2) |
1958 | - compare( |
1959 | - textField.focus, false, 'disabledTextField is not focused'); |
1960 | + mouseClick(disabledTextField, disabledTextField.width/2, disabledTextField.height/2); |
1961 | + compare(textField.focus, false, 'disabledTextField is not focused'); |
1962 | + } |
1963 | + |
1964 | + |
1965 | + // text selection |
1966 | + SignalSpy { |
1967 | + id: flickSpy |
1968 | + signalName: "onMovementEnded" |
1969 | + } |
1970 | + |
1971 | + function test_scroll_when_not_focused() { |
1972 | + var handler = findChild(longText, "input_handler"); |
1973 | + verify(handler); |
1974 | + |
1975 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
1976 | + flickSpy.clear(); |
1977 | + // scroll when inactive |
1978 | + verify(longText.focus == false); |
1979 | + var x = longText.width / 2; |
1980 | + var y = longText.height / 2; |
1981 | + var dx = x; |
1982 | + flick(longText, x, y, -dx, 0); |
1983 | + verify(longText.focus); |
1984 | + compare(flickSpy.count, 0, "The input had scrolled while inactive"); |
1985 | + } |
1986 | + |
1987 | + function test_scroll_when_focused() { |
1988 | + longText.focus = true; |
1989 | + var handler = findChild(longText, "input_handler"); |
1990 | + verify(handler); |
1991 | + |
1992 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
1993 | + flickSpy.clear(); |
1994 | + var x = longText.width / 2; |
1995 | + var y = longText.height / 2; |
1996 | + |
1997 | + compare(handler.state, "", "The input is not in default state before selection"); |
1998 | + flick(longText, x, y, - x, 0); |
1999 | + flickSpy.wait(); |
2000 | + compare(handler.state, "", "The input has not returned to default state."); |
2001 | + } |
2002 | + |
2003 | + function test_scroll_with_selected_text() { |
2004 | + longText.focus = true; |
2005 | + var handler = findChild(longText, "input_handler"); |
2006 | + verify(handler); |
2007 | + var y = longText.height / 2; |
2008 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2009 | + flickSpy.clear(); |
2010 | + |
2011 | + // select text |
2012 | + compare(handler.state, "", "The input is not in default state before selection"); |
2013 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
2014 | + verify(longText.selectedText !== ""); |
2015 | + compare(handler.state, "", "The input has not returned to default state."); |
2016 | + |
2017 | + // flick |
2018 | + var dx = longText.width / 2; |
2019 | + flick(longText, dx, y, -dx, 0); |
2020 | + flickSpy.wait(); |
2021 | + compare(handler.state, "", "The input has not returned to default state."); |
2022 | + verify(longText.selectedText !== ""); |
2023 | + } |
2024 | + |
2025 | + function test_select_by_pressAndDrag() { |
2026 | + longText.focus = true; |
2027 | + var handler = findChild(longText, "input_handler"); |
2028 | + verify(handler); |
2029 | + var dx = longText.width / 4; |
2030 | + var x = units.gu(5); |
2031 | + var y = longText.height / 2; |
2032 | + compare(handler.state, "", "The input is not in default state before selection"); |
2033 | + flick(longText, x, y, 2*dx, 0, handler.selectionModeTimeout + 50); |
2034 | + verify(longText.selectedText !== ""); |
2035 | + compare(handler.state, "", "The input has not returned to default state."); |
2036 | + } |
2037 | + |
2038 | + function test_select_text_doubletap() { |
2039 | + longText.focus = true; |
2040 | + var x = units.gu(2); |
2041 | + var y = longText.height / 4; |
2042 | + mouseDoubleClick(longText, x, y); |
2043 | + expectFail("", "mouseDoubleClick fails to trigger"); |
2044 | + verify(longText.selectedText !== ""); |
2045 | + } |
2046 | + |
2047 | + SignalSpy { |
2048 | + id: popoverSpy |
2049 | + signalName: "onPressAndHold" |
2050 | + } |
2051 | + |
2052 | + function test_press_and_hold_moves_cursor_position_and_opens_context_menu() { |
2053 | + longText.focus = true; |
2054 | + var handler = findChild(longText, "input_handler"); |
2055 | + var y = longText.height / 2; |
2056 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2057 | + flickSpy.clear(); |
2058 | + popoverSpy.target = handler; |
2059 | + popoverSpy.clear(); |
2060 | + |
2061 | + // long press |
2062 | + compare(handler.state, "", "The input is not in default state before long press"); |
2063 | + mouseLongPress(longText, units.gu(8), y); |
2064 | + waitForRendering(longText); |
2065 | + popoverSpy.wait(); |
2066 | + verify(longText.cursorPosition != 0); |
2067 | + compare(handler.state, "inactive", "The input is not in inactive state while context menu is open"); |
2068 | + |
2069 | + // cleanup, release the mouse, that should bring the handler back to default state |
2070 | + mouseRelease(longText, units.gu(8), y); |
2071 | + compare(handler.state, "", "The input has not returned to default state."); |
2072 | + // dismiss popover |
2073 | + mouseClick(longText, 10, 10); |
2074 | + } |
2075 | + |
2076 | + function test_press_and_hold_moves_cursor_position_and_opens_context_menu_when_not_focus() { |
2077 | + var handler = findChild(longText, "input_handler"); |
2078 | + var y = longText.height / 2; |
2079 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2080 | + flickSpy.clear(); |
2081 | + popoverSpy.target = handler; |
2082 | + popoverSpy.clear(); |
2083 | + |
2084 | + // long press |
2085 | + compare(handler.state, "inactive", "The input is not in inactive state before long press"); |
2086 | + mouseLongPress(longText, units.gu(8), y); |
2087 | + waitForRendering(longText); |
2088 | + popoverSpy.wait(); |
2089 | + verify(longText.focus, "The input is not focused"); |
2090 | + verify(longText.cursorPosition != 0, "The cursor wasn't moved"); |
2091 | + compare(handler.state, "inactive", "The input is not in inactive state while the context menu is open"); |
2092 | + |
2093 | + // cleanup, release the mouse, that should bring the handler back to default state |
2094 | + mouseRelease(longText, units.gu(8), y); |
2095 | + compare(handler.state, "", "The input has not returned to default state."); |
2096 | + // dismiss popover |
2097 | + mouseClick(longText, 10, 10); |
2098 | + } |
2099 | + |
2100 | + function test_press_and_hold_over_selected_text() { |
2101 | + longText.focus = true; |
2102 | + var handler = findChild(longText, "input_handler"); |
2103 | + var y = longText.height / 2; |
2104 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2105 | + flickSpy.clear(); |
2106 | + popoverSpy.target = handler; |
2107 | + popoverSpy.clear(); |
2108 | + |
2109 | + // select text |
2110 | + compare(handler.state, "", "The input is not in default state before long press"); |
2111 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
2112 | + waitForRendering(longText); |
2113 | + compare(handler.state, "", "The input has not returned to default state."); |
2114 | + verify(longText.selectedText !== ""); |
2115 | + |
2116 | + mouseLongPress(longText, units.gu(7), y); |
2117 | + // wait till popover is shown |
2118 | + waitForRendering(longText); |
2119 | + popoverSpy.wait(); |
2120 | + // cleanup, release the mouse, that should bring the handler back to default state |
2121 | + mouseRelease(textItem, 0, 0); |
2122 | + compare(handler.state, "", "The input has not returned to default state."); |
2123 | + // dismiss popover |
2124 | + mouseClick(longText, 10, 10); |
2125 | + } |
2126 | + |
2127 | + function test_move_mouse_while_context_menu_open_does_not_move_selection() { |
2128 | + longText.focus = true; |
2129 | + var handler = findChild(longText, "input_handler"); |
2130 | + var y = longText.height / 2; |
2131 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2132 | + flickSpy.clear(); |
2133 | + popoverSpy.target = handler; |
2134 | + popoverSpy.clear(); |
2135 | + |
2136 | + // select text |
2137 | + compare(handler.state, "", "The input is not in default state before long press"); |
2138 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
2139 | + verify(longText.selectedText !== "", "Selected text differs"); |
2140 | + var selection = longText.selectedText; |
2141 | + compare(handler.state, "", "The input has not returned to default state."); |
2142 | + |
2143 | + mouseLongPress(longText, units.gu(4), y); |
2144 | + // wait till popover is shown |
2145 | + waitForRendering(longText); |
2146 | + popoverSpy.wait(); |
2147 | + // do some mouse moves and compare whether we have the same selection |
2148 | + mouseMoveSlowly(longText, units.gu(4), y, units.gu(4), 0, 3, 100); |
2149 | + compare(selection, longText.selectedText, "Selection differs"); |
2150 | + |
2151 | + // cleanup, release the mouse, that should bring the handler back to default state |
2152 | + mouseRelease(textItem, 0, 0); |
2153 | + compare(handler.state, "", "The input has not returned to default state."); |
2154 | + mouseClick(longText, 10, 10); |
2155 | + } |
2156 | + |
2157 | + function test_clear_selection_by_click_on_selection() { |
2158 | + longText.focus = true; |
2159 | + var handler = findChild(longText, "input_handler"); |
2160 | + var y = longText.height / 2; |
2161 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2162 | + flickSpy.clear(); |
2163 | + |
2164 | + // select text |
2165 | + compare(handler.state, "", "The input is not in default state before long press"); |
2166 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
2167 | + compare(handler.state, "", "The input has not returned to default state."); |
2168 | + verify(longText.selectedText !== ""); |
2169 | + |
2170 | + // click on selection |
2171 | + mouseClick(longText, units.gu(4), y); |
2172 | + verify(longText.selectedText === ""); |
2173 | + } |
2174 | + |
2175 | + function test_clear_selection_by_click_beside_selection() { |
2176 | + longText.focus = true; |
2177 | + var handler = findChild(longText, "input_handler"); |
2178 | + var y = longText.height / 2; |
2179 | + flickSpy.target = findChild(longText, "textfield_scroller"); |
2180 | + flickSpy.clear(); |
2181 | + |
2182 | + // select text |
2183 | + compare(handler.state, "", "The input is not in default state before long press"); |
2184 | + flick(longText, 0, y, units.gu(8), 0, handler.selectionModeTimeout + 50); |
2185 | + compare(handler.state, "", "The input has not returned to default state."); |
2186 | + verify(longText.selectedText !== ""); |
2187 | + |
2188 | + // click on selection |
2189 | + mouseClick(longText, units.gu(10), y); |
2190 | + verify(longText.selectedText === ""); |
2191 | } |
2192 | } |
2193 | } |
2194 | |
2195 | === added file 'tests/unit_x11/tst_mousefilters/HoverEvent.qml.moved' |
2196 | --- tests/unit_x11/tst_mousefilters/HoverEvent.qml.moved 1970-01-01 00:00:00 +0000 |
2197 | +++ tests/unit_x11/tst_mousefilters/HoverEvent.qml.moved 2014-04-25 05:29:38 +0000 |
2198 | @@ -0,0 +1,38 @@ |
2199 | +/* |
2200 | + * Copyright 2014 Canonical Ltd. |
2201 | + * |
2202 | + * This program is free software; you can redistribute it and/or modify |
2203 | + * it under the terms of the GNU Lesser General Public License as published by |
2204 | + * the Free Software Foundation; version 3. |
2205 | + * |
2206 | + * This program is distributed in the hope that it will be useful, |
2207 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2208 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2209 | + * GNU Lesser General Public License for more details. |
2210 | + * |
2211 | + * You should have received a copy of the GNU Lesser General Public License |
2212 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2213 | + */ |
2214 | + |
2215 | +import QtQuick 2.0 |
2216 | +import Ubuntu.Components 0.1 |
2217 | + |
2218 | +Item { |
2219 | + id: root |
2220 | + width: units.gu(40) |
2221 | + height: units.gu(71) |
2222 | + |
2223 | + Rectangle { |
2224 | + width: units.gu(30) |
2225 | + height: units.gu(30) |
2226 | + anchors.centerIn: parent |
2227 | + color: "blue" |
2228 | + |
2229 | + MouseArea { |
2230 | + objectName: "FilterOwner" |
2231 | + anchors.fill: parent |
2232 | + hoverEnabled: true |
2233 | + Mouse.enabled: true |
2234 | + } |
2235 | + } |
2236 | +} |
FAILED: Continuous integration, rev:994 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/1990/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- trusty- touch/47 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 4543/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- amd64-ci/ 938/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- armhf-ci/ 938 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- armhf-ci/ 938/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- mako/53 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/4148 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/4148/ artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 5667 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/4661/ console
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- ui-toolkit- ci/1990/ rebuild
http://