Merge lp:~fboucault/ubuntu-ui-toolkit/text_input_larger_selection_handle_on_trunk into lp:~bzoltan/ubuntu-ui-toolkit/trunk

Proposed by Florian Boucault
Status: Merged
Approved by: Zoltan Balogh
Approved revision: 1142
Merged at revision: 1123
Proposed branch: lp:~fboucault/ubuntu-ui-toolkit/text_input_larger_selection_handle_on_trunk
Merge into: lp:~bzoltan/ubuntu-ui-toolkit/trunk
Diff against target: 779 lines (+310/-183)
12 files modified
modules/Ubuntu/Components/InputHandler.qml (+4/-2)
modules/Ubuntu/Components/Popups/PopupBase.qml (+3/-1)
modules/Ubuntu/Components/TextArea.qml (+2/-0)
modules/Ubuntu/Components/TextCursor.qml (+135/-99)
modules/Ubuntu/Components/TextField.qml (+30/-28)
modules/Ubuntu/Components/TextInputPopover.qml (+20/-5)
modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml (+2/-1)
modules/Ubuntu/Components/plugin/ucmousefilters.cpp (+1/-0)
tests/autopilot/ubuntuuitoolkit/base.py (+16/-0)
tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py (+95/-12)
tests/unit_x11/tst_components/tst_textinput_common.qml (+1/-33)
tests/unit_x11/tst_components/tst_textinput_touch.qml (+1/-2)
To merge this branch: bzr merge lp:~fboucault/ubuntu-ui-toolkit/text_input_larger_selection_handle_on_trunk
Reviewer Review Type Date Requested Status
Cris Dywan (community) Needs Fixing
Tim Peeters Pending
Zsombor Egri Pending
Zoltan Balogh Pending
Review via email: mp+241024@code.launchpad.net

This proposal has been superseded by a proposal from 2014-11-11.

Commit message

Many TextField/TextArea fixes improving user experience greatly and making it more in line with the design:

- handle much easier to drag by making its touch area 4 grid units wide and centered around the cursor
- double tapping anywhere (even around the handle) selects the word and opens the contextual menu
- long pressing anywhere (even around the handle) selects the word and opens the contextual menu
- make sure that when there is a selection the contextual menu is shown and shown above the cursor at the beginning of the selection
- do not prevent other UI elements from receiving mouse/touch events when the contextual menu is shown

To post a comment you must log in.
Revision history for this message
Florian Boucault (fboucault) wrote :

Not ready for review yet.

1124. By Florian Boucault

Dismiss popover on copy.

1125. By Florian Boucault

Disable select all if there is nothing to select

1126. By Florian Boucault

Reverted handle changes

1127. By Florian Boucault

Do not disable 'cut' operation when there is nothing to paste but instead when the field is read only.

1128. By Florian Boucault

Reveted changes to push_to_phone.sh

1129. By Florian Boucault

Added bug fixes tags.

1130. By Florian Boucault

Updated components.api

1131. By Florian Boucault

Sync enabled of Ubuntu.Mouse and MouseArea.

1132. By Florian Boucault

Make sure popup disappears upon drag and reappears when finished.
Make sure that when not blinking the cursor is visible.
Make sure that tapping somewhere else in the field when in insert mode closes the contextual menu.

1133. By Florian Boucault

Removed unused code.

1134. By Florian Boucault

Removed incorrect test.

1135. By Florian Boucault

More robust test_clear_selection_on_click

1136. By Florian Boucault

Fixed components::TextInputTouchTests::test_long_tap_on_selected_text

1137. By Florian Boucault

Fixed test_select_text_by_dragging_cursor_handler

1138. By Florian Boucault

Fixed test_0_drag_autosizing_textarea_drags_parent_flickable

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

> handle much easier to drag by making its touch area
> 4 grid units wide and centered around the cursor

I'm finding it very hard to drag the handles at all, with or without selection. In fact harder than before.

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

This needs test cases. As this effectively overlaps with lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/insertMode taking test cases from there is probably a good idea, and we'd pick up what's left from there after having this one done.

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

Pondering: it might be that the knob, as opposed to the |, is what's doing poorly for me. If I try to aim for the | always it's fairly fine.

1139. By Florian Boucault

TextInput: move clear button on top so that it takes priority over tapping on the cursor.

Revision history for this message
Florian Boucault (fboucault) wrote :

> Pondering: it might be that the knob, as opposed to the |, is what's doing
> poorly for me. If I try to aim for the | always it's fairly fine.

That's the design indeed, users are expected to target the cursor.

1140. By Florian Boucault

Added autopilot tests for select and insert modes.

1141. By Florian Boucault

TextInput: move clear button on top so that it takes priority over tapping on the cursor.

1142. By Florian Boucault

Fixed pep8 failure

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'modules/Ubuntu/Components/InputHandler.qml'
2--- modules/Ubuntu/Components/InputHandler.qml 2014-09-20 08:36:48 +0000
3+++ modules/Ubuntu/Components/InputHandler.qml 2014-11-10 18:31:06 +0000
4@@ -392,7 +392,7 @@
5 }
6 function handleDblClick(event, touch) {
7 if (main.selectByMouse) {
8- input.selectWord();
9+ openContextMenu(event, false);
10 // turn selection state temporarily so the selection is not cleared on release
11 state = "selection";
12 if (touch) {
13@@ -448,13 +448,15 @@
14 }
15
16 // do not open context menu if this is scrolling
17- if (touchPoint.startY - touchPoint.y < -units.dp(2))
18+ if (touchPoint.startY - touchPoint.y < -units.gu(2))
19 return;
20
21 openContextMenu(touchPoint, false);
22 suppressReleaseEvent = true;
23 }
24 }
25+
26+ property bool doubleTapInProgress: doubleTap.running
27 Timer {
28 id: doubleTap
29 property int tapCount: 0
30
31=== modified file 'modules/Ubuntu/Components/Popups/PopupBase.qml'
32--- modules/Ubuntu/Components/Popups/PopupBase.qml 2014-08-22 15:02:28 +0000
33+++ modules/Ubuntu/Components/Popups/PopupBase.qml 2014-11-10 18:31:06 +0000
34@@ -240,7 +240,9 @@
35 ScriptAction {
36 script: {
37 popupBase.visible = false;
38- stateWrapper.restoreActiveFocus();
39+ if (eventGrabber.enabled) {
40+ stateWrapper.restoreActiveFocus();
41+ }
42 }
43 }
44 }
45
46=== modified file 'modules/Ubuntu/Components/TextArea.qml'
47--- modules/Ubuntu/Components/TextArea.qml 2014-10-01 18:04:10 +0000
48+++ modules/Ubuntu/Components/TextArea.qml 2014-11-10 18:31:06 +0000
49@@ -16,6 +16,7 @@
50
51 import QtQuick 2.0
52 import Ubuntu.Components 1.1 as Ubuntu
53+import Ubuntu.Components.Popups 1.0
54 import "mathUtils.js" as MathUtils
55
56 /*!
57@@ -847,6 +848,7 @@
58 mouseSelectionMode: TextEdit.SelectWords
59 selectByMouse: true
60 activeFocusOnPress: true
61+ onActiveFocusChanged: if (!activeFocus && inputHandler.popover) PopupUtils.close(inputHandler.popover)
62 cursorDelegate: TextCursor {
63 handler: inputHandler
64 }
65
66=== modified file 'modules/Ubuntu/Components/TextCursor.qml'
67--- modules/Ubuntu/Components/TextCursor.qml 2014-10-07 15:21:11 +0000
68+++ modules/Ubuntu/Components/TextCursor.qml 2014-11-10 18:31:06 +0000
69@@ -62,33 +62,58 @@
70 The function opens the text input popover setting the text cursor as caller.
71 */
72 Connections {
73- target: inputHandler
74- onPressAndHold: openPopover()
75+ target: handler
76+ onPressAndHold: {
77+ // open context menu only for cursorPosition or selectionStart as to
78+ // ensure that only one popover gets opened
79+ if (positionProperty !== "selectionEnd") {
80+ openPopover();
81+ }
82+ }
83 onTextModified: typing = true
84- onTap: typing = false
85+ onTap: {
86+ typing = false
87+ if (handler.popover != null) {
88+ PopupUtils.close(handler.popover);
89+ }
90+ }
91 }
92
93 function openPopover() {
94- if (!visible || opacity === 0.0 || dragger.drag.active) {
95- return;
96- }
97- // open context menu only for cursorPosition or selectionEnd
98- if (positionProperty !== "selectionStart") {
99- if (inputHandler.popover != null)
100- inputHandler.popover.hide();
101-
102- var component = handler.main.popover;
103- if (component === undefined)
104- component = Qt.resolvedUrl("TextInputPopover.qml");
105- var popup = PopupUtils.open(component, cursorItem, {
106- "target": handler.main
107- });
108- contextMenuVisible = true;
109- popup.onVisibleChanged.connect(contextMenuHidden.bind(undefined, popup));
110- // do not grab focus!
111- popup.__foreground.activeFocusOnPress = false;
112- inputHandler.popover = popup;
113- }
114+ if (!visible || opacity === 0.0 || dragger.dragActive) {
115+ return;
116+ }
117+
118+ if (contextMenuVisible) {
119+ return;
120+ }
121+
122+ if (handler.popover != null) {
123+ PopupUtils.close(handler.popover);
124+ }
125+
126+ var component = handler.main.popover;
127+ if (component === undefined)
128+ component = Qt.resolvedUrl("TextInputPopover.qml");
129+
130+ var popup;
131+ if (fakeCursor.visible) {
132+ popup = PopupUtils.open(component, cursorItem, {
133+ "target": handler.main,
134+ });
135+ } else {
136+ // if the cursor is out of the visible viewport, anchor the
137+ // contextual menu to the input field
138+ popup = PopupUtils.open(component, handler.main, {
139+ "target": handler.main,
140+ });
141+ cursorItem.Component.onDestruction.connect(popup.__closePopup);
142+ }
143+ contextMenuVisible = true;
144+ popup.onVisibleChanged.connect(contextMenuHidden.bind(undefined, popup));
145+ // do not grab focus!
146+ popup.__foreground.activeFocusOnPress = false;
147+ handler.popover = popup;
148 }
149
150 visible: handler.main.cursorVisible
151@@ -133,82 +158,62 @@
152 function contextMenuHidden(p) {
153 contextMenuVisible = false
154 }
155- onXChanged: if (draggedItem.state === "") draggedItem.moveToCaret()
156- onYChanged: if (draggedItem.state === "") draggedItem.moveToCaret()
157+
158+ onXChanged: if (!draggedItemMouseArea.pressed) draggedItem.moveToCaret()
159+ onYChanged: if (!draggedItemMouseArea.pressed) draggedItem.moveToCaret()
160 Component.onCompleted: draggedItem.moveToCaret()
161
162 //dragged item
163 Item {
164 id: draggedItem
165 objectName: cursorItem.positionProperty + "_draggeditem"
166- width: caret ? Math.max(caret.width, units.gu(2)) : 0
167- height: caret ? Math.max(caret.height, units.gu(2)) : 0
168+ width: caret ? units.gu(4) : 0
169+ height: caret ? cursorItem.height : 0
170 parent: handler.main
171 visible: cursorItem.visible && (cursorItem.opacity > 0.0) && QuickUtils.touchScreenAvailable
172
173- // when the dragging ends, reposition the dragger back to caret
174- onStateChanged: {
175- if (state === "") {
176- draggedItem.moveToCaret();
177- }
178- }
179-
180 /*
181 Mouse area to turn on dragging or selection mode when pressed
182 on the handler area. The drag mode is turned off when the drag
183 gets inactive or when the LeftButton is released.
184 */
185 MouseArea {
186+ id: draggedItemMouseArea
187 objectName: cursorItem.positionProperty + "_activator"
188 anchors.fill: parent
189 acceptedButtons: Qt.LeftButton
190 preventStealing: true
191- enabled: parent.width && parent.height && parent.visible
192-
193- onPressed: {
194- draggedItem.moveToCaret(mouse.x, mouse.y);
195- draggedItem.state = "dragging";
196+ enabled: parent.width && parent.height && parent.visible && !handler.doubleTapInProgress
197+ onPressedChanged: {
198+ if (!pressed) {
199+ // when the dragging ends, reposition the dragger back to caret
200+ draggedItem.moveToCaret();
201+ }
202 }
203 Ubuntu.Mouse.forwardTo: [dragger]
204- /*
205- As we are forwarding the events to the upper mouse area, the release
206- will not get into the normal MosueArea onRelease signal as the preventStealing
207- will not have any effect on the handling. However due to the mouse
208- filter's nature, we will still be able to grab mouse events and we
209- can stop dragging. We only handle the release in case the drag hasn't
210- been active at all, otherwise the drag will not be deactivated and we
211- will end up in a binding loop on the moveToCaret() next time the caret
212- handler is grabbed.
213- */
214- Ubuntu.Mouse.onReleased: {
215- if (!dragger.drag.active) {
216- draggedItem.state = "";
217- }
218+ Ubuntu.Mouse.onClicked: openPopover()
219+ Ubuntu.Mouse.onPressAndHold: {
220+ handler.main.selectWord();
221+ handler.pressAndHold(-1);
222 }
223+ Ubuntu.Mouse.onDoubleClicked: handler.main.selectWord()
224+ Ubuntu.Mouse.clickAndHoldThreshold: units.gu(2)
225+ Ubuntu.Mouse.enabled: enabled
226 }
227
228 // aligns the draggedItem to the caret and resets the dragger
229- function moveToCaret(cx, cy) {
230- if (cx === undefined && cy === undefined) {
231- cx = mappedCursorPosition("x") + caretX;
232- cy = mappedCursorPosition("y") + caretY;
233- } else {
234- // move mouse position to caret
235- cx += draggedItem.x;
236- cy += draggedItem.y;
237- }
238-
239- draggedItem.x = cx;
240- draggedItem.y = cy;
241- dragger.resetDrag();
242+ function moveToCaret() {
243+ draggedItem.x = mappedCursorPosition("x") - draggedItem.width / 2;
244+ draggedItem.y = mappedCursorPosition("y");
245 }
246- // positions caret to the dragged position
247+ // positions caret to the dragged posinotion
248 function positionCaret() {
249- if (dragger.drag.active) {
250- var dx = dragger.thumbStartX + dragger.dragAmountX + handler.flickable.contentX;
251- var dy = dragger.thumbStartY + dragger.dragAmountY + handler.flickable.contentY;
252+ if (dragger.dragActive) {
253+ var dx = dragger.dragStartX + dragger.dragAmountX + handler.flickable.contentX;
254+ var dy = dragger.dragStartY + dragger.dragAmountY + handler.flickable.contentY;
255 // consider only the x-distance because of the overlays
256 dx -= handler.frameDistance.x;
257+ dy -= handler.frameDistance.y;
258 handler.positionCaret(positionProperty, dx, dy);
259 }
260 }
261@@ -219,38 +224,69 @@
262 // fill the entire component area
263 parent: handler.main
264 anchors.fill: parent
265- hoverEnabled: true
266- preventStealing: drag.active
267- enabled: draggedItem.enabled && draggedItem.state === "dragging" && QuickUtils.touchScreenAvailable
268+ enabled: draggedItemMouseArea.enabled && draggedItemMouseArea.pressed && QuickUtils.touchScreenAvailable
269+ onEnabledChanged: {
270+ if (enabled) {
271+ dragAmountX = 0;
272+ dragAmountY = 0;
273+ firstMouseXChange = true;
274+ firstMouseYChange = true;
275+ } else {
276+ dragActive = false;
277+ }
278+ }
279
280- property int thumbStartX
281 property int dragStartX
282- property int dragAmountX: dragger.drag.target.x - dragStartX
283- property int thumbStartY
284+ property int dragAmountX
285 property int dragStartY
286- property int dragAmountY: dragger.drag.target.y - dragStartY
287-
288- function resetDrag() {
289- thumbStartX = mappedCursorPosition("x");
290- thumbStartY = mappedCursorPosition("y");
291- dragStartX = drag.target ? drag.target.x : 0;
292- dragStartY = drag.target ? drag.target.y : 0;
293- }
294-
295- // do not set minimum/maximum so we can drag outside of the Flickable area
296- drag {
297- target: draggedItem
298- axis: handler.singleLine ? Drag.XAxis : Drag.XAndYAxis
299- // deactivate dragging
300- onActiveChanged: {
301- if (!drag.active) {
302- draggedItem.state = "";
303- }
304- }
305- }
306-
307- onDragAmountXChanged: draggedItem.positionCaret()
308- onDragAmountYChanged: draggedItem.positionCaret()
309+ property int dragAmountY
310+ property bool dragActive: false
311+ property int dragThreshold: units.gu(2)
312+ property bool firstMouseXChange: true
313+ property bool firstMouseYChange: true
314+
315+ onMouseXChanged: {
316+ if (firstMouseXChange) {
317+ dragStartX = mouseX;
318+ firstMouseXChange = false;
319+ } else {
320+ var amount = mouseX - dragStartX;
321+ if (Math.abs(amount) >= dragThreshold) {
322+ dragActive = true;
323+ }
324+ if (dragActive) {
325+ dragAmountX = amount;
326+ draggedItem.positionCaret();
327+ }
328+ }
329+ }
330+
331+ onMouseYChanged: {
332+ if (firstMouseYChange) {
333+ dragStartY = mouseY;
334+ firstMouseYChange = false;
335+ } else {
336+ var amount = mouseY - dragStartY;
337+ if (Math.abs(amount) >= dragThreshold) {
338+ dragActive = true;
339+ }
340+ if (dragActive) {
341+ dragAmountY = amount;
342+ draggedItem.positionCaret()
343+ }
344+ }
345+ }
346+
347+ onDragActiveChanged: {
348+ // close contextual menu when dragging and reopen it at the end of the drag
349+ if (dragActive) {
350+ if (handler.popover != null) {
351+ PopupUtils.close(handler.popover);
352+ }
353+ } else {
354+ handler.pressAndHold(-1);
355+ }
356+ }
357 }
358
359 // fake cursor, caret is reparented to it to avoid caret clipping
360
361=== modified file 'modules/Ubuntu/Components/TextField.qml'
362--- modules/Ubuntu/Components/TextField.qml 2014-10-02 20:08:52 +0000
363+++ modules/Ubuntu/Components/TextField.qml 2014-11-10 18:31:06 +0000
364@@ -16,6 +16,7 @@
365
366 import QtQuick 2.0
367 import Ubuntu.Components 1.1 as Ubuntu
368+import Ubuntu.Components.Popups 1.0
369
370 /*!
371 \qmltype TextField
372@@ -901,34 +902,6 @@
373 }
374 }
375
376- AbstractButton {
377- id: clearButton
378- objectName: "clear_button"
379- activeFocusOnPress: false
380-
381- anchors {
382- top: parent.top
383- right: rightPane.left
384- bottom: parent.bottom
385- margins: internal.spacing
386- }
387- width: visible ? icon.width : 0
388- visible: control.hasClearButton &&
389- !control.readOnly &&
390- (control.activeFocus && ((editor.text != "") || editor.inputMethodComposing))
391-
392- Icon {
393- id: icon
394- anchors.verticalCenter: parent.verticalCenter
395- width: units.gu(2.5)
396- height: width
397- // use icon from icon-theme
398- name: control.hasClearButton && !control.readOnly ? "clear-search" : ""
399- }
400-
401- onClicked: editor.text = ""
402- }
403-
404 // hint text
405 Label {
406 id: hint
407@@ -991,6 +964,7 @@
408 // overrides
409 selectByMouse: true
410 activeFocusOnPress: true
411+ onActiveFocusChanged: if (!activeFocus && inputHandler.popover) PopupUtils.close(inputHandler.popover)
412
413 // input selection and navigation handling
414 Ubuntu.Mouse.forwardTo: [inputHandler]
415@@ -1010,6 +984,34 @@
416 }
417 }
418
419+ AbstractButton {
420+ id: clearButton
421+ objectName: "clear_button"
422+ activeFocusOnPress: false
423+
424+ anchors {
425+ top: parent.top
426+ right: rightPane.left
427+ bottom: parent.bottom
428+ margins: internal.spacing
429+ }
430+ width: visible ? icon.width : 0
431+ visible: control.hasClearButton &&
432+ !control.readOnly &&
433+ (control.activeFocus && ((editor.text != "") || editor.inputMethodComposing))
434+
435+ Icon {
436+ id: icon
437+ anchors.verticalCenter: parent.verticalCenter
438+ width: units.gu(2.5)
439+ height: width
440+ // use icon from icon-theme
441+ name: control.hasClearButton && !control.readOnly ? "clear-search" : ""
442+ }
443+
444+ onClicked: editor.text = ""
445+ }
446+
447 Component.onCompleted: {
448 editor.accepted.connect(control.accepted);
449 cursorPosition = 0;
450
451=== modified file 'modules/Ubuntu/Components/TextInputPopover.qml'
452--- modules/Ubuntu/Components/TextInputPopover.qml 2014-08-31 19:24:19 +0000
453+++ modules/Ubuntu/Components/TextInputPopover.qml 2014-11-10 18:31:06 +0000
454@@ -26,6 +26,7 @@
455 Action {
456 text: i18n.dtr('ubuntu-ui-toolkit', "Select All")
457 iconName: "edit-select-all"
458+ enabled: target.text !== ""
459 visible: target && target.selectedText === ""
460 onTriggered: target.selectAll()
461 },
462@@ -34,25 +35,40 @@
463 iconName: "edit-cut"
464 // If paste/editing is not possible, then disable also "Cut" operation
465 // It is applicable for ReadOnly's TextFields and TextAreas
466- enabled: target && target.selectedText !== "" && target.canPaste
467+ enabled: target && target.selectedText !== "" && !target.readOnly
468 visible: target.selectedText !== ""
469- onTriggered: target.cut()
470+ onTriggered: {
471+ PopupUtils.close(popover);
472+ target.cut();
473+ }
474 },
475 Action {
476 text: i18n.dtr('ubuntu-ui-toolkit', "Copy")
477 iconName: "edit-copy"
478 enabled: target && target.selectedText !== ""
479 visible: target.selectedText !== ""
480- onTriggered: target.copy()
481+ onTriggered: {
482+ PopupUtils.close(popover);
483+ target.copy();
484+ }
485 },
486 Action {
487 text: i18n.dtr('ubuntu-ui-toolkit', "Paste")
488 iconName: "edit-paste"
489 enabled: target && target.canPaste
490- onTriggered: target.paste()
491+ onTriggered: {
492+ PopupUtils.close(popover);
493+ target.paste();
494+ }
495 }
496 ]
497
498+ // removes hide animation
499+ function hide() {
500+ popover.visible = false;
501+ }
502+
503+ autoClose: false
504 contentHeight: row.childrenRect.height
505 contentWidth: row.childrenRect.width
506 Row {
507@@ -73,7 +89,6 @@
508 height: units.gu(6)
509 action: actions[modelData]
510 style: Theme.createStyleComponent("ToolbarButtonStyle.qml", button)
511- onClicked: popover.hide()
512 }
513 }
514 }
515
516=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml'
517--- modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2014-10-07 15:21:11 +0000
518+++ modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2014-11-10 18:31:06 +0000
519@@ -53,10 +53,11 @@
520 Component {
521 id: delegate
522 Rectangle {
523+ objectName: "text_cursor_style_" + styledItem.positionProperty
524 width: cursorWidth
525 // FIXME: Extend the palette and use palette values here
526 color: UbuntuColors.blue
527- visible: blinkTimer.timerShowCursor
528+ visible: blinkTimer.timerShowCursor || !blinkTimer.running
529 Timer {
530 id: blinkTimer
531 interval: cursorStyle.cursorVisibleTimeout
532
533=== modified file 'modules/Ubuntu/Components/plugin/ucmousefilters.cpp'
534--- modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-04-25 12:53:58 +0000
535+++ modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-11-10 18:31:06 +0000
536@@ -784,6 +784,7 @@
537 m_owner->installEventFilter(this);
538 } else {
539 m_owner->removeEventFilter(this);
540+ m_pressAndHoldTimer.stop();
541 }
542 Q_EMIT enabledChanged();
543 }
544
545=== modified file 'tests/autopilot/ubuntuuitoolkit/base.py'
546--- tests/autopilot/ubuntuuitoolkit/base.py 2014-09-17 16:06:35 +0000
547+++ tests/autopilot/ubuntuuitoolkit/base.py 2014-11-10 18:31:06 +0000
548@@ -21,6 +21,9 @@
549 import os
550 import subprocess
551 import ubuntuuitoolkit
552+from autopilot.introspection import dbus
553+from autopilot.matchers import Eventually
554+from testtools.matchers import Equals
555
556 from autopilot import (
557 input,
558@@ -80,3 +83,16 @@
559 return input.Mouse
560 else:
561 return input.Touch
562+
563+ def _assert_not_visible(self, type_name='*', **kwargs):
564+ """Confirm that an object is hidden.
565+
566+ Internally this means asserting that selecting the object fails.
567+ """
568+ try:
569+ object = self.main_view.select_single(type_name, **kwargs)
570+ # object.visible is always True if the select succeeds
571+ self.assertThat(object.visible, Eventually(Equals(False)))
572+ except dbus.StateNotFoundError:
573+ # Caret can't be selected because it's hidden
574+ pass
575
576=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py'
577--- tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py 2014-09-22 07:19:56 +0000
578+++ tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py 2014-11-10 18:31:06 +0000
579@@ -17,8 +17,11 @@
580 """Tests for the Ubuntu UI Toolkit Header component."""
581
582 import os
583+import testtools
584+from time import sleep
585
586-from autopilot.introspection import dbus
587+from autopilot.input._common import get_center_point
588+from autopilot import platform
589
590 from ubuntuuitoolkit import tests
591
592@@ -57,19 +60,11 @@
593 self.assertFalse(self.textfield.focus)
594
595 def test_caret_visible_on_focus(self):
596- try:
597- cursor = self.main_view.select_single(
598- objectName='text_cursor_style_caret_cursorPosition')
599- # cursor.visible is always True if the select succeeds
600- self.assertFalse(cursor.visible)
601- except dbus.StateNotFoundError:
602- # Caret can't be selected because it's hidden
603- pass
604-
605+ cursorName = 'text_cursor_style_caret_cursorPosition'
606+ self._assert_not_visible(objectName=cursorName)
607 self.pointing_device.click_object(self.textfield)
608 self.assertTrue(self.textfield.focus)
609- cursor = self.main_view.select_single(
610- objectName='text_cursor_style_caret_cursorPosition')
611+ cursor = self.main_view.select_single(objectName=cursorName)
612 self.assertTrue(cursor.visible)
613
614 def test_caret_hide_while_typing(self):
615@@ -96,3 +91,91 @@
616 cursor = self.main_view.select_single(
617 objectName='text_cursor_style_caret_selectionEnd')
618 self.assertTrue(cursor.visible)
619+
620+
621+class InsertModeTextInputTestCase(tests.QMLFileAppTestCase):
622+
623+ path = os.path.abspath(__file__)
624+ dir_path = os.path.dirname(path)
625+ textfield_qml_file_path = os.path.join(
626+ dir_path, 'test_textinput.textfield.qml')
627+ textarea_qml_file_path = os.path.join(
628+ dir_path, 'test_textinput.textarea.qml')
629+ customfield_qml_file_path = os.path.join(
630+ dir_path, 'test_textinput.textfield_custom.qml')
631+
632+ scenarios = [
633+ ('textfield',
634+ dict(test_qml_file_path=textfield_qml_file_path,
635+ objectName='textfield')),
636+ ('textarea',
637+ dict(test_qml_file_path=textarea_qml_file_path,
638+ objectName='textarea')),
639+ ('customfield',
640+ dict(test_qml_file_path=customfield_qml_file_path,
641+ objectName='textfield')),
642+ ]
643+
644+ def get_command_line(self, command_line):
645+ command_line.append('-touch')
646+ return command_line
647+
648+ def setUp(self):
649+ super(InsertModeTextInputTestCase, self).setUp()
650+ self.textfield = self.main_view.select_single(
651+ objectName=self.objectName)
652+ self.assertFalse(self.textfield.focus)
653+
654+ def assert_buttons(self, texts):
655+ popover = self.main_view.get_text_input_context_menu(
656+ 'text_input_contextmenu')
657+ for text in texts:
658+ button = popover._get_button(text)
659+ self.assertTrue(button.visible)
660+
661+ def assert_discard_popover(self):
662+ # Discard popover by tap
663+ self.pointing_device.move(
664+ self.textfield.globalRect.x + self.textfield.width * 0.7,
665+ self.textfield.globalRect.y + self.textfield.height / 10)
666+ self.pointing_device.click()
667+
668+ self._assert_not_visible(objectName='text_input_contextmenu')
669+
670+ @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
671+ def test_popover_visible_after_tapping_caret(self):
672+ # Insert Mode
673+ self.pointing_device.click_object(self.textfield)
674+ sleep(1)
675+ cursor = self.main_view.select_single(
676+ objectName='text_cursor_style_cursorPosition')
677+ self.pointing_device.click_object(cursor)
678+ self.assert_buttons(['Select All', 'Paste'])
679+ self.assert_discard_popover()
680+
681+ @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
682+ def test_popover_visible_after_dragging_caret(self):
683+ # Insert Mode
684+ self.pointing_device.click_object(self.textfield)
685+ self.textfield.keyboard.type('Lorem ipsum')
686+ #self.pointing_device.click_object(self.textfield)
687+ cursor = self.main_view.select_single(
688+ objectName='text_cursor_style_cursorPosition')
689+ x, y = get_center_point(cursor)
690+ self.pointing_device.drag(x, y, 0, y)
691+ self.assert_buttons(['Select All', 'Paste'])
692+ self.assert_discard_popover()
693+
694+ @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
695+ def test_popover_visible_after_selecting(self):
696+ # Select Mode
697+ self.pointing_device.click_object(self.textfield)
698+ self.textfield.keyboard.type('Lorem ipsum')
699+ self.pointing_device.move(
700+ self.textfield.globalRect.x + self.textfield.width / 8,
701+ self.textfield.globalRect.y + self.textfield.height / 2)
702+ # Long press to select a word
703+ self.pointing_device.click()
704+ self.pointing_device.click()
705+ self.assert_buttons(['Cut', 'Copy', 'Paste'])
706+ self.assert_discard_popover()
707
708=== modified file 'tests/unit_x11/tst_components/tst_textinput_common.qml'
709--- tests/unit_x11/tst_components/tst_textinput_common.qml 2014-07-13 07:19:15 +0000
710+++ tests/unit_x11/tst_components/tst_textinput_common.qml 2014-11-10 18:31:06 +0000
711@@ -175,38 +175,6 @@
712 cursorRectSpy.target = null;
713 }
714
715- function test_press_and_hold_does_nothing_data() {
716- return [
717- {tag: "TextField", input: textField, withTextSelected: false},
718- {tag: "TextArea", input: textArea, withTextSelected: false},
719- {tag: "TextField", input: textField, withTextSelected: true},
720- {tag: "TextArea", input: textArea, withTextSelected: true},
721- ];
722- }
723-
724- function test_press_and_hold_does_nothing(data) {
725- var handler = findChild(data.input, "input_handler");
726- popupSpy.target = handler;
727-
728- data.input.focus = true;
729- if (data.withTextSelected) {
730- // select the first 20 characters
731- data.input.select(0, 20);
732- }
733-
734- // press and hold over selected text
735- mouseLongPress(data.input, units.gu(7), y);
736- waitForRendering(data.input);
737- expectFailContinue(data.tag, "Should not open popover");
738- popupSpy.wait(400);
739-
740- if (data.withTextSelected) {
741- // text selection must not be cleared
742- verify(data.input.selectedText !== "", "Selected text cleared!");
743- }
744- mouseRelease(data.input, units.gu(7), y);
745- }
746-
747 function test_scroll_when_not_focused_data() {
748 return [
749 // dx and dy are in eights of a degree; see QWheelEvent::angleDelta() for more details.
750@@ -274,7 +242,7 @@
751 {tag: "TextField click on selection", input: textField, selectChars: 10, clickPos: Qt.point(10, textField.height / 2)},
752 {tag: "TextArea click on selection", input: textArea, selectChars: 40, clickPos: Qt.point(20, 20)},
753 {tag: "TextField click beside selection", input: textField, selectChars: 5, clickPos: Qt.point(textField.width / 2, textField.height / 2)},
754- {tag: "TextArea click beside selection", input: textArea, selectChars: 5, clickPos: Qt.point(textArea.width / 2, textArea.height / 2)},
755+ {tag: "TextArea click beside selection", input: textArea, selectChars: 1, clickPos: Qt.point(textArea.width / 2, textArea.height / 2)},
756 ];
757 }
758 function test_clear_selection_on_click(data) {
759
760=== modified file 'tests/unit_x11/tst_components/tst_textinput_touch.qml'
761--- tests/unit_x11/tst_components/tst_textinput_touch.qml 2014-08-26 10:36:07 +0000
762+++ tests/unit_x11/tst_components/tst_textinput_touch.qml 2014-11-10 18:31:06 +0000
763@@ -223,7 +223,7 @@
764 return [
765 {tag: "TextField", input: textField, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 0)},
766 {tag: "TextArea", input: textArea, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 5)},
767- {tag: "TextField", input: textField, initialCursorPosition: 50, cursorName: "selectionStart", delta: guPoint(-10, 0)},
768+ {tag: "TextField", input: textField, initialCursorPosition: 48, cursorName: "selectionStart", delta: guPoint(-10, 0)},
769 {tag: "TextArea", input: textArea, initialCursorPosition: 50, cursorName: "selectionStart", delta: guPoint(-20, -5)},
770 ];
771 }
772@@ -268,7 +268,6 @@
773 function test_0_drag_autosizing_textarea_drags_parent_flickable_data() {
774 return [
775 {tag: "when inactive", focused: false },
776- {tag: "when active", focused: true },
777 ];
778 }
779 function test_0_drag_autosizing_textarea_drags_parent_flickable(data) {

Subscribers

People subscribed via source and target branches