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
=== modified file 'modules/Ubuntu/Components/InputHandler.qml'
--- modules/Ubuntu/Components/InputHandler.qml 2014-09-20 08:36:48 +0000
+++ modules/Ubuntu/Components/InputHandler.qml 2014-11-10 18:31:06 +0000
@@ -392,7 +392,7 @@
392 }392 }
393 function handleDblClick(event, touch) {393 function handleDblClick(event, touch) {
394 if (main.selectByMouse) {394 if (main.selectByMouse) {
395 input.selectWord();395 openContextMenu(event, false);
396 // turn selection state temporarily so the selection is not cleared on release396 // turn selection state temporarily so the selection is not cleared on release
397 state = "selection";397 state = "selection";
398 if (touch) {398 if (touch) {
@@ -448,13 +448,15 @@
448 }448 }
449449
450 // do not open context menu if this is scrolling450 // do not open context menu if this is scrolling
451 if (touchPoint.startY - touchPoint.y < -units.dp(2))451 if (touchPoint.startY - touchPoint.y < -units.gu(2))
452 return;452 return;
453453
454 openContextMenu(touchPoint, false);454 openContextMenu(touchPoint, false);
455 suppressReleaseEvent = true;455 suppressReleaseEvent = true;
456 }456 }
457 }457 }
458
459 property bool doubleTapInProgress: doubleTap.running
458 Timer {460 Timer {
459 id: doubleTap461 id: doubleTap
460 property int tapCount: 0462 property int tapCount: 0
461463
=== modified file 'modules/Ubuntu/Components/Popups/PopupBase.qml'
--- modules/Ubuntu/Components/Popups/PopupBase.qml 2014-08-22 15:02:28 +0000
+++ modules/Ubuntu/Components/Popups/PopupBase.qml 2014-11-10 18:31:06 +0000
@@ -240,7 +240,9 @@
240 ScriptAction {240 ScriptAction {
241 script: {241 script: {
242 popupBase.visible = false;242 popupBase.visible = false;
243 stateWrapper.restoreActiveFocus();243 if (eventGrabber.enabled) {
244 stateWrapper.restoreActiveFocus();
245 }
244 }246 }
245 }247 }
246 }248 }
247249
=== modified file 'modules/Ubuntu/Components/TextArea.qml'
--- modules/Ubuntu/Components/TextArea.qml 2014-10-01 18:04:10 +0000
+++ modules/Ubuntu/Components/TextArea.qml 2014-11-10 18:31:06 +0000
@@ -16,6 +16,7 @@
1616
17import QtQuick 2.017import QtQuick 2.0
18import Ubuntu.Components 1.1 as Ubuntu18import Ubuntu.Components 1.1 as Ubuntu
19import Ubuntu.Components.Popups 1.0
19import "mathUtils.js" as MathUtils20import "mathUtils.js" as MathUtils
2021
21/*!22/*!
@@ -847,6 +848,7 @@
847 mouseSelectionMode: TextEdit.SelectWords848 mouseSelectionMode: TextEdit.SelectWords
848 selectByMouse: true849 selectByMouse: true
849 activeFocusOnPress: true850 activeFocusOnPress: true
851 onActiveFocusChanged: if (!activeFocus && inputHandler.popover) PopupUtils.close(inputHandler.popover)
850 cursorDelegate: TextCursor {852 cursorDelegate: TextCursor {
851 handler: inputHandler853 handler: inputHandler
852 }854 }
853855
=== modified file 'modules/Ubuntu/Components/TextCursor.qml'
--- modules/Ubuntu/Components/TextCursor.qml 2014-10-07 15:21:11 +0000
+++ modules/Ubuntu/Components/TextCursor.qml 2014-11-10 18:31:06 +0000
@@ -62,33 +62,58 @@
62 The function opens the text input popover setting the text cursor as caller.62 The function opens the text input popover setting the text cursor as caller.
63 */63 */
64 Connections {64 Connections {
65 target: inputHandler65 target: handler
66 onPressAndHold: openPopover()66 onPressAndHold: {
67 // open context menu only for cursorPosition or selectionStart as to
68 // ensure that only one popover gets opened
69 if (positionProperty !== "selectionEnd") {
70 openPopover();
71 }
72 }
67 onTextModified: typing = true73 onTextModified: typing = true
68 onTap: typing = false74 onTap: {
75 typing = false
76 if (handler.popover != null) {
77 PopupUtils.close(handler.popover);
78 }
79 }
69 }80 }
7081
71 function openPopover() {82 function openPopover() {
72 if (!visible || opacity === 0.0 || dragger.drag.active) {83 if (!visible || opacity === 0.0 || dragger.dragActive) {
73 return;84 return;
74 }85 }
75 // open context menu only for cursorPosition or selectionEnd86
76 if (positionProperty !== "selectionStart") {87 if (contextMenuVisible) {
77 if (inputHandler.popover != null)88 return;
78 inputHandler.popover.hide();89 }
7990
80 var component = handler.main.popover;91 if (handler.popover != null) {
81 if (component === undefined)92 PopupUtils.close(handler.popover);
82 component = Qt.resolvedUrl("TextInputPopover.qml");93 }
83 var popup = PopupUtils.open(component, cursorItem, {94
84 "target": handler.main95 var component = handler.main.popover;
85 });96 if (component === undefined)
86 contextMenuVisible = true;97 component = Qt.resolvedUrl("TextInputPopover.qml");
87 popup.onVisibleChanged.connect(contextMenuHidden.bind(undefined, popup));98
88 // do not grab focus!99 var popup;
89 popup.__foreground.activeFocusOnPress = false;100 if (fakeCursor.visible) {
90 inputHandler.popover = popup;101 popup = PopupUtils.open(component, cursorItem, {
91 }102 "target": handler.main,
103 });
104 } else {
105 // if the cursor is out of the visible viewport, anchor the
106 // contextual menu to the input field
107 popup = PopupUtils.open(component, handler.main, {
108 "target": handler.main,
109 });
110 cursorItem.Component.onDestruction.connect(popup.__closePopup);
111 }
112 contextMenuVisible = true;
113 popup.onVisibleChanged.connect(contextMenuHidden.bind(undefined, popup));
114 // do not grab focus!
115 popup.__foreground.activeFocusOnPress = false;
116 handler.popover = popup;
92 }117 }
93118
94 visible: handler.main.cursorVisible119 visible: handler.main.cursorVisible
@@ -133,82 +158,62 @@
133 function contextMenuHidden(p) {158 function contextMenuHidden(p) {
134 contextMenuVisible = false159 contextMenuVisible = false
135 }160 }
136 onXChanged: if (draggedItem.state === "") draggedItem.moveToCaret()161
137 onYChanged: if (draggedItem.state === "") draggedItem.moveToCaret()162 onXChanged: if (!draggedItemMouseArea.pressed) draggedItem.moveToCaret()
163 onYChanged: if (!draggedItemMouseArea.pressed) draggedItem.moveToCaret()
138 Component.onCompleted: draggedItem.moveToCaret()164 Component.onCompleted: draggedItem.moveToCaret()
139165
140 //dragged item166 //dragged item
141 Item {167 Item {
142 id: draggedItem168 id: draggedItem
143 objectName: cursorItem.positionProperty + "_draggeditem"169 objectName: cursorItem.positionProperty + "_draggeditem"
144 width: caret ? Math.max(caret.width, units.gu(2)) : 0170 width: caret ? units.gu(4) : 0
145 height: caret ? Math.max(caret.height, units.gu(2)) : 0171 height: caret ? cursorItem.height : 0
146 parent: handler.main172 parent: handler.main
147 visible: cursorItem.visible && (cursorItem.opacity > 0.0) && QuickUtils.touchScreenAvailable173 visible: cursorItem.visible && (cursorItem.opacity > 0.0) && QuickUtils.touchScreenAvailable
148174
149 // when the dragging ends, reposition the dragger back to caret
150 onStateChanged: {
151 if (state === "") {
152 draggedItem.moveToCaret();
153 }
154 }
155
156 /*175 /*
157 Mouse area to turn on dragging or selection mode when pressed176 Mouse area to turn on dragging or selection mode when pressed
158 on the handler area. The drag mode is turned off when the drag177 on the handler area. The drag mode is turned off when the drag
159 gets inactive or when the LeftButton is released.178 gets inactive or when the LeftButton is released.
160 */179 */
161 MouseArea {180 MouseArea {
181 id: draggedItemMouseArea
162 objectName: cursorItem.positionProperty + "_activator"182 objectName: cursorItem.positionProperty + "_activator"
163 anchors.fill: parent183 anchors.fill: parent
164 acceptedButtons: Qt.LeftButton184 acceptedButtons: Qt.LeftButton
165 preventStealing: true185 preventStealing: true
166 enabled: parent.width && parent.height && parent.visible186 enabled: parent.width && parent.height && parent.visible && !handler.doubleTapInProgress
167187 onPressedChanged: {
168 onPressed: {188 if (!pressed) {
169 draggedItem.moveToCaret(mouse.x, mouse.y);189 // when the dragging ends, reposition the dragger back to caret
170 draggedItem.state = "dragging";190 draggedItem.moveToCaret();
191 }
171 }192 }
172 Ubuntu.Mouse.forwardTo: [dragger]193 Ubuntu.Mouse.forwardTo: [dragger]
173 /*194 Ubuntu.Mouse.onClicked: openPopover()
174 As we are forwarding the events to the upper mouse area, the release195 Ubuntu.Mouse.onPressAndHold: {
175 will not get into the normal MosueArea onRelease signal as the preventStealing196 handler.main.selectWord();
176 will not have any effect on the handling. However due to the mouse197 handler.pressAndHold(-1);
177 filter's nature, we will still be able to grab mouse events and we
178 can stop dragging. We only handle the release in case the drag hasn't
179 been active at all, otherwise the drag will not be deactivated and we
180 will end up in a binding loop on the moveToCaret() next time the caret
181 handler is grabbed.
182 */
183 Ubuntu.Mouse.onReleased: {
184 if (!dragger.drag.active) {
185 draggedItem.state = "";
186 }
187 }198 }
199 Ubuntu.Mouse.onDoubleClicked: handler.main.selectWord()
200 Ubuntu.Mouse.clickAndHoldThreshold: units.gu(2)
201 Ubuntu.Mouse.enabled: enabled
188 }202 }
189203
190 // aligns the draggedItem to the caret and resets the dragger204 // aligns the draggedItem to the caret and resets the dragger
191 function moveToCaret(cx, cy) {205 function moveToCaret() {
192 if (cx === undefined && cy === undefined) {206 draggedItem.x = mappedCursorPosition("x") - draggedItem.width / 2;
193 cx = mappedCursorPosition("x") + caretX;207 draggedItem.y = mappedCursorPosition("y");
194 cy = mappedCursorPosition("y") + caretY;
195 } else {
196 // move mouse position to caret
197 cx += draggedItem.x;
198 cy += draggedItem.y;
199 }
200
201 draggedItem.x = cx;
202 draggedItem.y = cy;
203 dragger.resetDrag();
204 }208 }
205 // positions caret to the dragged position209 // positions caret to the dragged posinotion
206 function positionCaret() {210 function positionCaret() {
207 if (dragger.drag.active) {211 if (dragger.dragActive) {
208 var dx = dragger.thumbStartX + dragger.dragAmountX + handler.flickable.contentX;212 var dx = dragger.dragStartX + dragger.dragAmountX + handler.flickable.contentX;
209 var dy = dragger.thumbStartY + dragger.dragAmountY + handler.flickable.contentY;213 var dy = dragger.dragStartY + dragger.dragAmountY + handler.flickable.contentY;
210 // consider only the x-distance because of the overlays214 // consider only the x-distance because of the overlays
211 dx -= handler.frameDistance.x;215 dx -= handler.frameDistance.x;
216 dy -= handler.frameDistance.y;
212 handler.positionCaret(positionProperty, dx, dy);217 handler.positionCaret(positionProperty, dx, dy);
213 }218 }
214 }219 }
@@ -219,38 +224,69 @@
219 // fill the entire component area224 // fill the entire component area
220 parent: handler.main225 parent: handler.main
221 anchors.fill: parent226 anchors.fill: parent
222 hoverEnabled: true227 enabled: draggedItemMouseArea.enabled && draggedItemMouseArea.pressed && QuickUtils.touchScreenAvailable
223 preventStealing: drag.active228 onEnabledChanged: {
224 enabled: draggedItem.enabled && draggedItem.state === "dragging" && QuickUtils.touchScreenAvailable229 if (enabled) {
230 dragAmountX = 0;
231 dragAmountY = 0;
232 firstMouseXChange = true;
233 firstMouseYChange = true;
234 } else {
235 dragActive = false;
236 }
237 }
225238
226 property int thumbStartX
227 property int dragStartX239 property int dragStartX
228 property int dragAmountX: dragger.drag.target.x - dragStartX240 property int dragAmountX
229 property int thumbStartY
230 property int dragStartY241 property int dragStartY
231 property int dragAmountY: dragger.drag.target.y - dragStartY242 property int dragAmountY
232243 property bool dragActive: false
233 function resetDrag() {244 property int dragThreshold: units.gu(2)
234 thumbStartX = mappedCursorPosition("x");245 property bool firstMouseXChange: true
235 thumbStartY = mappedCursorPosition("y");246 property bool firstMouseYChange: true
236 dragStartX = drag.target ? drag.target.x : 0;247
237 dragStartY = drag.target ? drag.target.y : 0;248 onMouseXChanged: {
238 }249 if (firstMouseXChange) {
239250 dragStartX = mouseX;
240 // do not set minimum/maximum so we can drag outside of the Flickable area251 firstMouseXChange = false;
241 drag {252 } else {
242 target: draggedItem253 var amount = mouseX - dragStartX;
243 axis: handler.singleLine ? Drag.XAxis : Drag.XAndYAxis254 if (Math.abs(amount) >= dragThreshold) {
244 // deactivate dragging255 dragActive = true;
245 onActiveChanged: {256 }
246 if (!drag.active) {257 if (dragActive) {
247 draggedItem.state = "";258 dragAmountX = amount;
248 }259 draggedItem.positionCaret();
249 }260 }
250 }261 }
251262 }
252 onDragAmountXChanged: draggedItem.positionCaret()263
253 onDragAmountYChanged: draggedItem.positionCaret()264 onMouseYChanged: {
265 if (firstMouseYChange) {
266 dragStartY = mouseY;
267 firstMouseYChange = false;
268 } else {
269 var amount = mouseY - dragStartY;
270 if (Math.abs(amount) >= dragThreshold) {
271 dragActive = true;
272 }
273 if (dragActive) {
274 dragAmountY = amount;
275 draggedItem.positionCaret()
276 }
277 }
278 }
279
280 onDragActiveChanged: {
281 // close contextual menu when dragging and reopen it at the end of the drag
282 if (dragActive) {
283 if (handler.popover != null) {
284 PopupUtils.close(handler.popover);
285 }
286 } else {
287 handler.pressAndHold(-1);
288 }
289 }
254 }290 }
255291
256 // fake cursor, caret is reparented to it to avoid caret clipping292 // fake cursor, caret is reparented to it to avoid caret clipping
257293
=== modified file 'modules/Ubuntu/Components/TextField.qml'
--- modules/Ubuntu/Components/TextField.qml 2014-10-02 20:08:52 +0000
+++ modules/Ubuntu/Components/TextField.qml 2014-11-10 18:31:06 +0000
@@ -16,6 +16,7 @@
1616
17import QtQuick 2.017import QtQuick 2.0
18import Ubuntu.Components 1.1 as Ubuntu18import Ubuntu.Components 1.1 as Ubuntu
19import Ubuntu.Components.Popups 1.0
1920
20/*!21/*!
21 \qmltype TextField22 \qmltype TextField
@@ -901,34 +902,6 @@
901 }902 }
902 }903 }
903904
904 AbstractButton {
905 id: clearButton
906 objectName: "clear_button"
907 activeFocusOnPress: false
908
909 anchors {
910 top: parent.top
911 right: rightPane.left
912 bottom: parent.bottom
913 margins: internal.spacing
914 }
915 width: visible ? icon.width : 0
916 visible: control.hasClearButton &&
917 !control.readOnly &&
918 (control.activeFocus && ((editor.text != "") || editor.inputMethodComposing))
919
920 Icon {
921 id: icon
922 anchors.verticalCenter: parent.verticalCenter
923 width: units.gu(2.5)
924 height: width
925 // use icon from icon-theme
926 name: control.hasClearButton && !control.readOnly ? "clear-search" : ""
927 }
928
929 onClicked: editor.text = ""
930 }
931
932 // hint text905 // hint text
933 Label {906 Label {
934 id: hint907 id: hint
@@ -991,6 +964,7 @@
991 // overrides964 // overrides
992 selectByMouse: true965 selectByMouse: true
993 activeFocusOnPress: true966 activeFocusOnPress: true
967 onActiveFocusChanged: if (!activeFocus && inputHandler.popover) PopupUtils.close(inputHandler.popover)
994968
995 // input selection and navigation handling969 // input selection and navigation handling
996 Ubuntu.Mouse.forwardTo: [inputHandler]970 Ubuntu.Mouse.forwardTo: [inputHandler]
@@ -1010,6 +984,34 @@
1010 }984 }
1011 }985 }
1012986
987 AbstractButton {
988 id: clearButton
989 objectName: "clear_button"
990 activeFocusOnPress: false
991
992 anchors {
993 top: parent.top
994 right: rightPane.left
995 bottom: parent.bottom
996 margins: internal.spacing
997 }
998 width: visible ? icon.width : 0
999 visible: control.hasClearButton &&
1000 !control.readOnly &&
1001 (control.activeFocus && ((editor.text != "") || editor.inputMethodComposing))
1002
1003 Icon {
1004 id: icon
1005 anchors.verticalCenter: parent.verticalCenter
1006 width: units.gu(2.5)
1007 height: width
1008 // use icon from icon-theme
1009 name: control.hasClearButton && !control.readOnly ? "clear-search" : ""
1010 }
1011
1012 onClicked: editor.text = ""
1013 }
1014
1013 Component.onCompleted: {1015 Component.onCompleted: {
1014 editor.accepted.connect(control.accepted);1016 editor.accepted.connect(control.accepted);
1015 cursorPosition = 0;1017 cursorPosition = 0;
10161018
=== modified file 'modules/Ubuntu/Components/TextInputPopover.qml'
--- modules/Ubuntu/Components/TextInputPopover.qml 2014-08-31 19:24:19 +0000
+++ modules/Ubuntu/Components/TextInputPopover.qml 2014-11-10 18:31:06 +0000
@@ -26,6 +26,7 @@
26 Action {26 Action {
27 text: i18n.dtr('ubuntu-ui-toolkit', "Select All")27 text: i18n.dtr('ubuntu-ui-toolkit', "Select All")
28 iconName: "edit-select-all"28 iconName: "edit-select-all"
29 enabled: target.text !== ""
29 visible: target && target.selectedText === ""30 visible: target && target.selectedText === ""
30 onTriggered: target.selectAll()31 onTriggered: target.selectAll()
31 },32 },
@@ -34,25 +35,40 @@
34 iconName: "edit-cut"35 iconName: "edit-cut"
35 // If paste/editing is not possible, then disable also "Cut" operation36 // If paste/editing is not possible, then disable also "Cut" operation
36 // It is applicable for ReadOnly's TextFields and TextAreas37 // It is applicable for ReadOnly's TextFields and TextAreas
37 enabled: target && target.selectedText !== "" && target.canPaste38 enabled: target && target.selectedText !== "" && !target.readOnly
38 visible: target.selectedText !== ""39 visible: target.selectedText !== ""
39 onTriggered: target.cut()40 onTriggered: {
41 PopupUtils.close(popover);
42 target.cut();
43 }
40 },44 },
41 Action {45 Action {
42 text: i18n.dtr('ubuntu-ui-toolkit', "Copy")46 text: i18n.dtr('ubuntu-ui-toolkit', "Copy")
43 iconName: "edit-copy"47 iconName: "edit-copy"
44 enabled: target && target.selectedText !== ""48 enabled: target && target.selectedText !== ""
45 visible: target.selectedText !== ""49 visible: target.selectedText !== ""
46 onTriggered: target.copy()50 onTriggered: {
51 PopupUtils.close(popover);
52 target.copy();
53 }
47 },54 },
48 Action {55 Action {
49 text: i18n.dtr('ubuntu-ui-toolkit', "Paste")56 text: i18n.dtr('ubuntu-ui-toolkit', "Paste")
50 iconName: "edit-paste"57 iconName: "edit-paste"
51 enabled: target && target.canPaste58 enabled: target && target.canPaste
52 onTriggered: target.paste()59 onTriggered: {
60 PopupUtils.close(popover);
61 target.paste();
62 }
53 }63 }
54 ]64 ]
5565
66 // removes hide animation
67 function hide() {
68 popover.visible = false;
69 }
70
71 autoClose: false
56 contentHeight: row.childrenRect.height72 contentHeight: row.childrenRect.height
57 contentWidth: row.childrenRect.width73 contentWidth: row.childrenRect.width
58 Row {74 Row {
@@ -73,7 +89,6 @@
73 height: units.gu(6)89 height: units.gu(6)
74 action: actions[modelData]90 action: actions[modelData]
75 style: Theme.createStyleComponent("ToolbarButtonStyle.qml", button)91 style: Theme.createStyleComponent("ToolbarButtonStyle.qml", button)
76 onClicked: popover.hide()
77 }92 }
78 }93 }
79 }94 }
8095
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2014-10-07 15:21:11 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/TextCursorStyle.qml 2014-11-10 18:31:06 +0000
@@ -53,10 +53,11 @@
53 Component {53 Component {
54 id: delegate54 id: delegate
55 Rectangle {55 Rectangle {
56 objectName: "text_cursor_style_" + styledItem.positionProperty
56 width: cursorWidth57 width: cursorWidth
57 // FIXME: Extend the palette and use palette values here58 // FIXME: Extend the palette and use palette values here
58 color: UbuntuColors.blue59 color: UbuntuColors.blue
59 visible: blinkTimer.timerShowCursor60 visible: blinkTimer.timerShowCursor || !blinkTimer.running
60 Timer {61 Timer {
61 id: blinkTimer62 id: blinkTimer
62 interval: cursorStyle.cursorVisibleTimeout63 interval: cursorStyle.cursorVisibleTimeout
6364
=== modified file 'modules/Ubuntu/Components/plugin/ucmousefilters.cpp'
--- modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-04-25 12:53:58 +0000
+++ modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-11-10 18:31:06 +0000
@@ -784,6 +784,7 @@
784 m_owner->installEventFilter(this);784 m_owner->installEventFilter(this);
785 } else {785 } else {
786 m_owner->removeEventFilter(this);786 m_owner->removeEventFilter(this);
787 m_pressAndHoldTimer.stop();
787 }788 }
788 Q_EMIT enabledChanged();789 Q_EMIT enabledChanged();
789 }790 }
790791
=== modified file 'tests/autopilot/ubuntuuitoolkit/base.py'
--- tests/autopilot/ubuntuuitoolkit/base.py 2014-09-17 16:06:35 +0000
+++ tests/autopilot/ubuntuuitoolkit/base.py 2014-11-10 18:31:06 +0000
@@ -21,6 +21,9 @@
21import os21import os
22import subprocess22import subprocess
23import ubuntuuitoolkit23import ubuntuuitoolkit
24from autopilot.introspection import dbus
25from autopilot.matchers import Eventually
26from testtools.matchers import Equals
2427
25from autopilot import (28from autopilot import (
26 input,29 input,
@@ -80,3 +83,16 @@
80 return input.Mouse83 return input.Mouse
81 else:84 else:
82 return input.Touch85 return input.Touch
86
87 def _assert_not_visible(self, type_name='*', **kwargs):
88 """Confirm that an object is hidden.
89
90 Internally this means asserting that selecting the object fails.
91 """
92 try:
93 object = self.main_view.select_single(type_name, **kwargs)
94 # object.visible is always True if the select succeeds
95 self.assertThat(object.visible, Eventually(Equals(False)))
96 except dbus.StateNotFoundError:
97 # Caret can't be selected because it's hidden
98 pass
8399
=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py'
--- tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py 2014-09-22 07:19:56 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/components/test_textinput.py 2014-11-10 18:31:06 +0000
@@ -17,8 +17,11 @@
17"""Tests for the Ubuntu UI Toolkit Header component."""17"""Tests for the Ubuntu UI Toolkit Header component."""
1818
19import os19import os
20import testtools
21from time import sleep
2022
21from autopilot.introspection import dbus23from autopilot.input._common import get_center_point
24from autopilot import platform
2225
23from ubuntuuitoolkit import tests26from ubuntuuitoolkit import tests
2427
@@ -57,19 +60,11 @@
57 self.assertFalse(self.textfield.focus)60 self.assertFalse(self.textfield.focus)
5861
59 def test_caret_visible_on_focus(self):62 def test_caret_visible_on_focus(self):
60 try:63 cursorName = 'text_cursor_style_caret_cursorPosition'
61 cursor = self.main_view.select_single(64 self._assert_not_visible(objectName=cursorName)
62 objectName='text_cursor_style_caret_cursorPosition')
63 # cursor.visible is always True if the select succeeds
64 self.assertFalse(cursor.visible)
65 except dbus.StateNotFoundError:
66 # Caret can't be selected because it's hidden
67 pass
68
69 self.pointing_device.click_object(self.textfield)65 self.pointing_device.click_object(self.textfield)
70 self.assertTrue(self.textfield.focus)66 self.assertTrue(self.textfield.focus)
71 cursor = self.main_view.select_single(67 cursor = self.main_view.select_single(objectName=cursorName)
72 objectName='text_cursor_style_caret_cursorPosition')
73 self.assertTrue(cursor.visible)68 self.assertTrue(cursor.visible)
7469
75 def test_caret_hide_while_typing(self):70 def test_caret_hide_while_typing(self):
@@ -96,3 +91,91 @@
96 cursor = self.main_view.select_single(91 cursor = self.main_view.select_single(
97 objectName='text_cursor_style_caret_selectionEnd')92 objectName='text_cursor_style_caret_selectionEnd')
98 self.assertTrue(cursor.visible)93 self.assertTrue(cursor.visible)
94
95
96class InsertModeTextInputTestCase(tests.QMLFileAppTestCase):
97
98 path = os.path.abspath(__file__)
99 dir_path = os.path.dirname(path)
100 textfield_qml_file_path = os.path.join(
101 dir_path, 'test_textinput.textfield.qml')
102 textarea_qml_file_path = os.path.join(
103 dir_path, 'test_textinput.textarea.qml')
104 customfield_qml_file_path = os.path.join(
105 dir_path, 'test_textinput.textfield_custom.qml')
106
107 scenarios = [
108 ('textfield',
109 dict(test_qml_file_path=textfield_qml_file_path,
110 objectName='textfield')),
111 ('textarea',
112 dict(test_qml_file_path=textarea_qml_file_path,
113 objectName='textarea')),
114 ('customfield',
115 dict(test_qml_file_path=customfield_qml_file_path,
116 objectName='textfield')),
117 ]
118
119 def get_command_line(self, command_line):
120 command_line.append('-touch')
121 return command_line
122
123 def setUp(self):
124 super(InsertModeTextInputTestCase, self).setUp()
125 self.textfield = self.main_view.select_single(
126 objectName=self.objectName)
127 self.assertFalse(self.textfield.focus)
128
129 def assert_buttons(self, texts):
130 popover = self.main_view.get_text_input_context_menu(
131 'text_input_contextmenu')
132 for text in texts:
133 button = popover._get_button(text)
134 self.assertTrue(button.visible)
135
136 def assert_discard_popover(self):
137 # Discard popover by tap
138 self.pointing_device.move(
139 self.textfield.globalRect.x + self.textfield.width * 0.7,
140 self.textfield.globalRect.y + self.textfield.height / 10)
141 self.pointing_device.click()
142
143 self._assert_not_visible(objectName='text_input_contextmenu')
144
145 @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
146 def test_popover_visible_after_tapping_caret(self):
147 # Insert Mode
148 self.pointing_device.click_object(self.textfield)
149 sleep(1)
150 cursor = self.main_view.select_single(
151 objectName='text_cursor_style_cursorPosition')
152 self.pointing_device.click_object(cursor)
153 self.assert_buttons(['Select All', 'Paste'])
154 self.assert_discard_popover()
155
156 @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
157 def test_popover_visible_after_dragging_caret(self):
158 # Insert Mode
159 self.pointing_device.click_object(self.textfield)
160 self.textfield.keyboard.type('Lorem ipsum')
161 #self.pointing_device.click_object(self.textfield)
162 cursor = self.main_view.select_single(
163 objectName='text_cursor_style_cursorPosition')
164 x, y = get_center_point(cursor)
165 self.pointing_device.drag(x, y, 0, y)
166 self.assert_buttons(['Select All', 'Paste'])
167 self.assert_discard_popover()
168
169 @testtools.skipIf(platform.model() == 'Desktop', 'Touch only')
170 def test_popover_visible_after_selecting(self):
171 # Select Mode
172 self.pointing_device.click_object(self.textfield)
173 self.textfield.keyboard.type('Lorem ipsum')
174 self.pointing_device.move(
175 self.textfield.globalRect.x + self.textfield.width / 8,
176 self.textfield.globalRect.y + self.textfield.height / 2)
177 # Long press to select a word
178 self.pointing_device.click()
179 self.pointing_device.click()
180 self.assert_buttons(['Cut', 'Copy', 'Paste'])
181 self.assert_discard_popover()
99182
=== modified file 'tests/unit_x11/tst_components/tst_textinput_common.qml'
--- tests/unit_x11/tst_components/tst_textinput_common.qml 2014-07-13 07:19:15 +0000
+++ tests/unit_x11/tst_components/tst_textinput_common.qml 2014-11-10 18:31:06 +0000
@@ -175,38 +175,6 @@
175 cursorRectSpy.target = null;175 cursorRectSpy.target = null;
176 }176 }
177177
178 function test_press_and_hold_does_nothing_data() {
179 return [
180 {tag: "TextField", input: textField, withTextSelected: false},
181 {tag: "TextArea", input: textArea, withTextSelected: false},
182 {tag: "TextField", input: textField, withTextSelected: true},
183 {tag: "TextArea", input: textArea, withTextSelected: true},
184 ];
185 }
186
187 function test_press_and_hold_does_nothing(data) {
188 var handler = findChild(data.input, "input_handler");
189 popupSpy.target = handler;
190
191 data.input.focus = true;
192 if (data.withTextSelected) {
193 // select the first 20 characters
194 data.input.select(0, 20);
195 }
196
197 // press and hold over selected text
198 mouseLongPress(data.input, units.gu(7), y);
199 waitForRendering(data.input);
200 expectFailContinue(data.tag, "Should not open popover");
201 popupSpy.wait(400);
202
203 if (data.withTextSelected) {
204 // text selection must not be cleared
205 verify(data.input.selectedText !== "", "Selected text cleared!");
206 }
207 mouseRelease(data.input, units.gu(7), y);
208 }
209
210 function test_scroll_when_not_focused_data() {178 function test_scroll_when_not_focused_data() {
211 return [179 return [
212 // dx and dy are in eights of a degree; see QWheelEvent::angleDelta() for more details.180 // dx and dy are in eights of a degree; see QWheelEvent::angleDelta() for more details.
@@ -274,7 +242,7 @@
274 {tag: "TextField click on selection", input: textField, selectChars: 10, clickPos: Qt.point(10, textField.height / 2)},242 {tag: "TextField click on selection", input: textField, selectChars: 10, clickPos: Qt.point(10, textField.height / 2)},
275 {tag: "TextArea click on selection", input: textArea, selectChars: 40, clickPos: Qt.point(20, 20)},243 {tag: "TextArea click on selection", input: textArea, selectChars: 40, clickPos: Qt.point(20, 20)},
276 {tag: "TextField click beside selection", input: textField, selectChars: 5, clickPos: Qt.point(textField.width / 2, textField.height / 2)},244 {tag: "TextField click beside selection", input: textField, selectChars: 5, clickPos: Qt.point(textField.width / 2, textField.height / 2)},
277 {tag: "TextArea click beside selection", input: textArea, selectChars: 5, clickPos: Qt.point(textArea.width / 2, textArea.height / 2)},245 {tag: "TextArea click beside selection", input: textArea, selectChars: 1, clickPos: Qt.point(textArea.width / 2, textArea.height / 2)},
278 ];246 ];
279 }247 }
280 function test_clear_selection_on_click(data) {248 function test_clear_selection_on_click(data) {
281249
=== modified file 'tests/unit_x11/tst_components/tst_textinput_touch.qml'
--- tests/unit_x11/tst_components/tst_textinput_touch.qml 2014-08-26 10:36:07 +0000
+++ tests/unit_x11/tst_components/tst_textinput_touch.qml 2014-11-10 18:31:06 +0000
@@ -223,7 +223,7 @@
223 return [223 return [
224 {tag: "TextField", input: textField, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 0)},224 {tag: "TextField", input: textField, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 0)},
225 {tag: "TextArea", input: textArea, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 5)},225 {tag: "TextArea", input: textArea, initialCursorPosition: 0, cursorName: "selectionEnd", delta: guPoint(10, 5)},
226 {tag: "TextField", input: textField, initialCursorPosition: 50, cursorName: "selectionStart", delta: guPoint(-10, 0)},226 {tag: "TextField", input: textField, initialCursorPosition: 48, cursorName: "selectionStart", delta: guPoint(-10, 0)},
227 {tag: "TextArea", input: textArea, initialCursorPosition: 50, cursorName: "selectionStart", delta: guPoint(-20, -5)},227 {tag: "TextArea", input: textArea, initialCursorPosition: 50, cursorName: "selectionStart", delta: guPoint(-20, -5)},
228 ];228 ];
229 }229 }
@@ -268,7 +268,6 @@
268 function test_0_drag_autosizing_textarea_drags_parent_flickable_data() {268 function test_0_drag_autosizing_textarea_drags_parent_flickable_data() {
269 return [269 return [
270 {tag: "when inactive", focused: false },270 {tag: "when inactive", focused: false },
271 {tag: "when active", focused: true },
272 ];271 ];
273 }272 }
274 function test_0_drag_autosizing_textarea_drags_parent_flickable(data) {273 function test_0_drag_autosizing_textarea_drags_parent_flickable(data) {

Subscribers

People subscribed via source and target branches