Merge lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Zsombor Egri
Approved revision: 1225
Merged at revision: 1211
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/ucfocusscope
Diff against target: 1373 lines (+712/-316)
23 files modified
modules/Ubuntu/Components/AbstractButton.qml (+4/-0)
modules/Ubuntu/Components/OptionSelector.qml (+5/-0)
modules/Ubuntu/Components/PageTreeNode.qml (+3/-0)
modules/Ubuntu/Components/Pickers/DatePicker.qml (+1/-0)
modules/Ubuntu/Components/Pickers/Dialer.qml (+1/-0)
modules/Ubuntu/Components/Pickers/DialerHand.qml (+2/-0)
modules/Ubuntu/Components/Pickers/Picker.qml (+8/-0)
modules/Ubuntu/Components/Pickers/PickerPanel.qml (+5/-0)
modules/Ubuntu/Components/Popups/Dialog.qml (+1/-0)
modules/Ubuntu/Components/Popups/Popover.qml (+1/-0)
modules/Ubuntu/Components/Popups/PopupBase.qml (+31/-1)
modules/Ubuntu/Components/Popups/SheetBase.qml (+1/-0)
modules/Ubuntu/Components/Slider.qml (+4/-0)
modules/Ubuntu/Components/TabBar.qml (+1/-0)
modules/Ubuntu/Components/TextCursor.qml (+5/-2)
modules/Ubuntu/Components/TextField.qml (+3/-0)
modules/Ubuntu/Components/UbuntuListView.qml (+18/-0)
modules/Ubuntu/Components/plugin/ucstyleditembase.cpp (+1/-2)
tests/unit_x11/tst_components/tst_datepicker.qml (+6/-2)
tests/unit_x11/tst_components/tst_focus.qml (+240/-0)
tests/unit_x11/tst_components/tst_tabs.qml (+96/-309)
tests/unit_x11/tst_components/tst_tabs_empty.qml (+41/-0)
tests/unit_x11/tst_components/tst_tabs_with_repeater.qml (+234/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Cris Dywan Approve
Review via email: mp+231356@code.launchpad.net

Commit message

Focus handling on components.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1214
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/841/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/3772/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/2901/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/673
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/673
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/673/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/673
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/3638/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5019
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5019/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/11738
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/2355/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3185
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3185/artifact/work/output/*zip*/output.zip

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

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

393 + // note: find Label, Picker's label uses medium font size. The lookup must be changed

Rogue fix sneaked in. Please make a separate MR for this.

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

> 393 + // note: find Label, Picker's label uses medium font size. The
> lookup must be changed
>
> Rogue fix sneaked in. Please make a separate MR for this.

As discussed, the order of children differs when focused. That's why it has been "sniffed" in here.

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

FAILED: Continuous integration, rev:1216
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/866/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/3883/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/2994/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/698
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/698
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/698/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/698
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/3740/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5130
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5130/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/11877
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/2435/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3278
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3278/artifact/work/output/*zip*/output.zip

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

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

FAILED: Continuous integration, rev:1217
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/869/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/3889/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/2997/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/701
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/701
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/701/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/701
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/3743/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5136
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5136/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/11882
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/2439/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3282
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3282/artifact/work/output/*zip*/output.zip

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

review: Needs Fixing (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

tim@trusty:~/dev/ubuntu-ui-toolkit/r/focus-handling-on-touch/tests/unit_x11/tst_components$ qmltestrunner -input tst_tabs_empty.qml -import ../../../modules/
unity::action::ActionManager::ActionManager(QObject*):
 Could not determine application identifier. HUD will not work properly.
 Provide your application identifier in $APP_ID environment variable.
file:///home/tim/dev/ubuntu-ui-toolkit/r/focus-handling-on-touch/tests/unit_x11/tst_components/tst_tabs_empty.qml:24: ReferenceError: uniys is not defined
Test '"/home/tim/dev/ubuntu-ui-toolkit/r/focus-handling-on-touch/tests/unit_x11/tst_components/tst_tabs_empty.qml"' has invalid size QSize(400, 0) , resizing.
file:///home/tim/dev/ubuntu-ui-toolkit/r/focus-handling-on-touch/tests/unit_x11/tst_components/tst_tabs_empty.qml:24: ReferenceError: uniys is not defined
********* Start testing of qmltestrunner *********
Config: Using QtTest library 5.3.0, Qt 5.3.0
PASS : qmltestrunner::EmptyTabs::initTestCase()
PASS : qmltestrunner::EmptyTabs::test_emptyTabs()
PASS : qmltestrunner::EmptyTabs::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of qmltestrunner *********
tim@trusty:~/dev/ubuntu-ui-toolkit/r/focus-handling-on-touch/tests/unit_x11/tst_components$

Revision history for this message
Tim Peeters (tpeeters) wrote :

^is just a typo in the code:

    height: uniys.gu(100)

Revision history for this message
Tim Peeters (tpeeters) wrote :

this program http://paste.ubuntu.com/8105902/ now gives a warning:

file:///home/tim/dev/ubuntu-ui-toolkit/m/07-tabsUnitTests/modules/Ubuntu/Components/Popups/PopupBase.qml:178: ReferenceError: window is not defined

it did not do that before. Please add a guard to check that window is valid.

Revision history for this message
Tim Peeters (tpeeters) wrote :

        function saveActiveFocus() {
            if (typeof window != 'undefined') prevFocus = window.activeFocusItem;
        }

fixes the issue in the previous comment

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

FAILED: Continuous integration, rev:1219
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/871/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/3902/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/3005/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/703
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/703
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/703/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/703
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/3757/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5149
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5149/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/11900
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/2446/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3290
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3290/artifact/work/output/*zip*/output.zip

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

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

FAILED: Continuous integration, rev:1221
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/874/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/3921/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/3016/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-amd64-ci/706
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/706
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-armhf-ci/706/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-utopic-i386-ci/706
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/3774/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5168
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/5168/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/11919
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/2457/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3301
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/3301/artifact/work/output/*zip*/output.zip

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

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

214 + if (typeof window != "undefined") {

This is still racy, it won't set if the window is available later. And it's the third instance where this came up and wasn't handled correctly on the first attempt.

We need something like Context.onWindowAvailable and fix this consistently.

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

> 214 + if (typeof window != "undefined") {
>
> This is still racy, it won't set if the window is available later. And it's
> the third instance where this came up and wasn't handled correctly on the
> first attempt.
>
> We need something like Context.onWindowAvailable and fix this consistently.

I'll do a local fix with a property in between, and we can return to it in a separate MR with a proper fix.

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

> > 214 + if (typeof window != "undefined") {
> >
> > This is still racy, it won't set if the window is available later. And it's
> > the third instance where this came up and wasn't handled correctly on the
> > first attempt.
> >
> > We need something like Context.onWindowAvailable and fix this consistently.
>
> I'll do a local fix with a property in between, and we can return to it in a
> separate MR with a proper fix.

Ack.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'modules/Ubuntu/Components/AbstractButton.qml'
2--- modules/Ubuntu/Components/AbstractButton.qml 2014-06-23 09:53:09 +0000
3+++ modules/Ubuntu/Components/AbstractButton.qml 2014-08-22 17:18:58 +0000
4@@ -16,6 +16,7 @@
5
6 import QtQuick 2.0
7 import QtFeedback 5.0
8+import Ubuntu.Components 1.1
9
10 /*!
11 \qmlabstract AbstractButton
12@@ -82,6 +83,8 @@
13 */
14 property alias __mouseArea: mouseArea
15
16+ activeFocusOnPress: true
17+
18 HapticsEffect {
19 id: pressEffect
20 attackIntensity: 0.0
21@@ -100,6 +103,7 @@
22 hoverEnabled: true
23
24 onClicked: {
25+ button.requestFocus();
26 if (button.__acceptEvents) {
27 pressEffect.start()
28 button.clicked()
29
30=== modified file 'modules/Ubuntu/Components/OptionSelector.qml'
31--- modules/Ubuntu/Components/OptionSelector.qml 2014-04-23 08:50:20 +0000
32+++ modules/Ubuntu/Components/OptionSelector.qml 2014-08-22 17:18:58 +0000
33@@ -220,6 +220,7 @@
34 StyledItem {
35 id: listContainer
36 objectName: "listContainer"
37+ activeFocusOnPress: true
38
39 readonly property url chevron: __styleInstance.chevron
40 readonly property url tick: __styleInstance.tick
41@@ -279,6 +280,10 @@
42 property real itemHeight
43 signal delegateClicked(int index)
44
45+ onMovementStarted: optionSelector.requestFocus(Qt.MouseFocusReason)
46+ onFlickStarted: optionSelector.requestFocus(Qt.MouseFocusReason)
47+ Toolkit.Mouse.onClicked: optionSelector.requestFocus(Qt.MouseFocusReason)
48+
49 onDelegateClicked: optionSelector.delegateClicked(index);
50 interactive: listContainer.height !== list.contentHeight && listContainer.currentlyExpanded ? true : false
51 clip: true
52
53=== modified file 'modules/Ubuntu/Components/PageTreeNode.qml'
54--- modules/Ubuntu/Components/PageTreeNode.qml 2014-06-19 12:45:37 +0000
55+++ modules/Ubuntu/Components/PageTreeNode.qml 2014-08-22 17:18:58 +0000
56@@ -100,6 +100,9 @@
57 */
58 property bool isLeaf: false
59
60+ // turn on focusing
61+ activeFocusOnPress: true
62+
63 Binding {
64 target: node.parentNode
65 property: "activeLeafNode"
66
67=== modified file 'modules/Ubuntu/Components/Pickers/DatePicker.qml'
68--- modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-07-18 20:12:42 +0000
69+++ modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-08-22 17:18:58 +0000
70@@ -302,6 +302,7 @@
71
72 implicitWidth: units.gu(36)
73 implicitHeight: units.gu(20)
74+ activeFocusOnPress: true
75
76 /*! \internal */
77 onMinimumChanged: {
78
79=== modified file 'modules/Ubuntu/Components/Pickers/Dialer.qml'
80--- modules/Ubuntu/Components/Pickers/Dialer.qml 2014-04-25 12:53:58 +0000
81+++ modules/Ubuntu/Components/Pickers/Dialer.qml 2014-08-22 17:18:58 +0000
82@@ -142,6 +142,7 @@
83 id: dialer
84 implicitWidth: size
85 implicitHeight: size
86+ activeFocusOnPress: true
87
88 style: Theme.createStyleComponent("DialerStyle.qml", dialer)
89
90
91=== modified file 'modules/Ubuntu/Components/Pickers/DialerHand.qml'
92--- modules/Ubuntu/Components/Pickers/DialerHand.qml 2014-04-25 12:53:58 +0000
93+++ modules/Ubuntu/Components/Pickers/DialerHand.qml 2014-08-22 17:18:58 +0000
94@@ -141,6 +141,7 @@
95 anchors.centerIn: parent
96 width: parent.width
97 height: parent.height
98+ activeFocusOnPress: true
99 style: Theme.createStyleComponent("DialerHandStyle.qml", dialerHand)
100
101 /*! \internal */
102@@ -193,6 +194,7 @@
103 onPositionChanged: {
104 if (internalChange) return;
105 internalChange = true;
106+ dialerHand.requestFocus(Qt.MouseFocusReason);
107 var point = mapToItem (dialerHand, mouse.x, mouse.y);
108 var diffX = (point.x - centerX);
109 var diffY = -1 * (point.y - centerY);
110
111=== modified file 'modules/Ubuntu/Components/Pickers/Picker.qml'
112--- modules/Ubuntu/Components/Pickers/Picker.qml 2014-04-23 08:50:20 +0000
113+++ modules/Ubuntu/Components/Pickers/Picker.qml 2014-08-22 17:18:58 +0000
114@@ -157,6 +157,7 @@
115
116 implicitWidth: units.gu(8)
117 implicitHeight: units.gu(20)
118+ activeFocusOnPress: true
119
120 style: Theme.createStyleComponent("PickerStyle.qml", picker)
121
122@@ -251,6 +252,9 @@
123 picker.selectedIndex = 0;
124 }
125 }
126+ // focus handling
127+ onMoveStarted: picker.requestFocus(Qt.MouseFocusReason)
128+ onFlickStarted: picker.requestFocus(Qt.MouseFocusReason)
129 }
130
131 function moveToIndex(toIndex) {
132@@ -289,6 +293,8 @@
133 }
134 width: parent ? parent.width : 0
135 clip: true
136+ focus: true
137+ Mouse.onClicked: picker.requestFocus()
138
139 model: picker.model
140 delegate: picker.delegate
141@@ -348,6 +354,8 @@
142 }
143 width: parent ? parent.width : 0
144 clip: true
145+ focus: true
146+ Mouse.onClicked: picker.requestFocus(Qt.MouseFocusReason)
147
148 model: picker.model
149 delegate: picker.delegate
150
151=== modified file 'modules/Ubuntu/Components/Pickers/PickerPanel.qml'
152--- modules/Ubuntu/Components/Pickers/PickerPanel.qml 2014-04-23 08:50:20 +0000
153+++ modules/Ubuntu/Components/Pickers/PickerPanel.qml 2014-08-22 17:18:58 +0000
154@@ -118,6 +118,11 @@
155 "callerProperty": property
156 }
157
158+ // hide OSK if eventually open
159+ if (Qt.inputMethod.visible) {
160+ Qt.inputMethod.hide();
161+ }
162+
163 if (!internal.isPhone) {
164 // we have no input panel defined, or the therefore we show the picker in a Popover
165 return internal.openPopover(caller, params);
166
167=== modified file 'modules/Ubuntu/Components/Popups/Dialog.qml'
168--- modules/Ubuntu/Components/Popups/Dialog.qml 2014-07-25 12:55:27 +0000
169+++ modules/Ubuntu/Components/Popups/Dialog.qml 2014-08-22 17:18:58 +0000
170@@ -157,6 +157,7 @@
171
172 StyledItem {
173 id: foreground
174+ activeFocusOnPress: true
175 width: Math.min(minimumWidth, dialog.width)
176 anchors.centerIn: parent
177
178
179=== modified file 'modules/Ubuntu/Components/Popups/Popover.qml'
180--- modules/Ubuntu/Components/Popups/Popover.qml 2014-07-16 06:31:50 +0000
181+++ modules/Ubuntu/Components/Popups/Popover.qml 2014-08-22 17:18:58 +0000
182@@ -204,6 +204,7 @@
183
184 StyledItem {
185 id: foreground
186+ activeFocusOnPress: true
187 objectName: "popover_foreground"
188
189 //styling properties
190
191=== modified file 'modules/Ubuntu/Components/Popups/PopupBase.qml'
192--- modules/Ubuntu/Components/Popups/PopupBase.qml 2014-07-10 11:50:13 +0000
193+++ modules/Ubuntu/Components/Popups/PopupBase.qml 2014-08-22 17:18:58 +0000
194@@ -163,12 +163,41 @@
195 onVisibleChanged: stateWrapper.state = (visible) ? 'opened' : 'closed'
196 /*! \internal */
197 onParentChanged: stateWrapper.rootItem = QuickUtils.rootItem(popupBase)
198- Component.onCompleted: stateWrapper.rootItem = QuickUtils.rootItem(popupBase);
199+ Component.onCompleted: {
200+ stateWrapper.saveActiveFocus();
201+ stateWrapper.rootItem = QuickUtils.rootItem(popupBase);
202+ }
203
204 Item {
205 id: stateWrapper
206 property Item rootItem: QuickUtils.rootItem(popupBase)
207
208+ property bool windowIsValid: typeof window != "undefined"
209+ property Item prevFocus
210+
211+ function saveActiveFocus() {
212+ // 'window' context property is exposed to QML after component completion
213+ // before rendering is complete, therefore a simple 'if (window)' check is
214+ // not enough.
215+ if (windowIsValid) {
216+ prevFocus = window.activeFocusItem;
217+ windowIsValidChanged.disconnect(saveActiveFocus);
218+ } else {
219+ // connect the function so we can save the original focus item
220+ windowIsValidChanged.connect(saveActiveFocus);
221+ }
222+ }
223+
224+ function restoreActiveFocus() {
225+ if (prevFocus) {
226+ if (prevFocus.hasOwnProperty("requestFocus")) {
227+ prevFocus.requestFocus(Qt.PopupFocusReason);
228+ } else {
229+ prevFocus.forceActiveFocus(Qt.PopupFocusReason);
230+ }
231+ }
232+ }
233+
234 states: [
235 State {
236 name: 'closed'
237@@ -211,6 +240,7 @@
238 ScriptAction {
239 script: {
240 popupBase.visible = false;
241+ stateWrapper.restoreActiveFocus();
242 }
243 }
244 }
245
246=== modified file 'modules/Ubuntu/Components/Popups/SheetBase.qml'
247--- modules/Ubuntu/Components/Popups/SheetBase.qml 2014-06-03 13:37:22 +0000
248+++ modules/Ubuntu/Components/Popups/SheetBase.qml 2014-08-22 17:18:58 +0000
249@@ -86,6 +86,7 @@
250
251 StyledItem {
252 id: foreground
253+ activeFocusOnPress: true
254
255 property string title
256 property real contentsWidth: units.gu(64)
257
258=== modified file 'modules/Ubuntu/Components/Slider.qml'
259--- modules/Ubuntu/Components/Slider.qml 2014-08-19 05:21:56 +0000
260+++ modules/Ubuntu/Components/Slider.qml 2014-08-22 17:18:58 +0000
261@@ -119,6 +119,8 @@
262 /*! \internal */
263 onValueChanged: mouseArea.liveValue = slider.value
264
265+ activeFocusOnPress: true
266+
267 Binding {
268 target: slider
269 property: "value"
270@@ -208,6 +210,8 @@
271 var normalizedOffsetX = (mouseArea.mouseX - dragInitMouseX) / barMinusThumb;
272 liveValue = valueFromNormalizedValue(dragInitNormalizedValue + normalizedOffsetX);
273 }
274+ onClicked: slider.requestFocus(Qt.MouseFocusReason)
275+ onLiveValueChanged: if (isPressed) slider.requestFocus(Qt.MouseFocusReason)
276 }
277
278 style: Theme.createStyleComponent("SliderStyle.qml", slider)
279
280=== modified file 'modules/Ubuntu/Components/TabBar.qml'
281--- modules/Ubuntu/Components/TabBar.qml 2014-05-09 11:22:07 +0000
282+++ modules/Ubuntu/Components/TabBar.qml 2014-08-22 17:18:58 +0000
283@@ -95,6 +95,7 @@
284 property bool animate: true
285
286 implicitHeight: units.gu(7.5)
287+ activeFocusOnPress: true
288
289 style: Theme.createStyleComponent("TabBarStyle.qml", tabBar)
290
291
292=== modified file 'modules/Ubuntu/Components/TextCursor.qml'
293--- modules/Ubuntu/Components/TextCursor.qml 2014-06-13 08:57:48 +0000
294+++ modules/Ubuntu/Components/TextCursor.qml 2014-08-22 17:18:58 +0000
295@@ -72,18 +72,21 @@
296 }
297 // open context menu only for cursorPosition or selectionEnd
298 if (positionProperty !== "selectionStart") {
299+ var popup;
300 if (handler.main.popover === undefined) {
301 // open the default one
302- PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem,
303+ popup = PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem,
304 {
305 "target": handler.main
306 })
307 } else {
308- PopupUtils.open(handler.main.popover, cursorItem,
309+ popup = PopupUtils.open(handler.main.popover, cursorItem,
310 {
311 "target": handler.main
312 })
313 }
314+ // do not grab focus!
315+ popup.__foreground.activeFocusOnPress = false;
316 }
317 }
318
319
320=== modified file 'modules/Ubuntu/Components/TextField.qml'
321--- modules/Ubuntu/Components/TextField.qml 2014-08-08 07:28:49 +0000
322+++ modules/Ubuntu/Components/TextField.qml 2014-08-22 17:18:58 +0000
323@@ -818,6 +818,7 @@
324 // internals
325
326 opacity: enabled ? 1.0 : 0.3
327+ activeFocusOnPress: true
328
329 /*! \internal */
330 onVisibleChanged: {
331@@ -904,6 +905,8 @@
332 AbstractButton {
333 id: clearButton
334 objectName: "clear_button"
335+ activeFocusOnPress: false
336+
337 anchors {
338 top: parent.top
339 right: rightPane.left
340
341=== modified file 'modules/Ubuntu/Components/UbuntuListView.qml'
342--- modules/Ubuntu/Components/UbuntuListView.qml 2014-04-23 08:50:20 +0000
343+++ modules/Ubuntu/Components/UbuntuListView.qml 2014-08-22 17:18:58 +0000
344@@ -92,8 +92,26 @@
345 }
346 animation.start();
347 }
348+
349+ function requestFocus(reason) {
350+ // lookup for the currentItem, and if it is a FocusScope, focus the view
351+ // this will also focus the currentItem
352+ if (root.currentItem && root.currentItem.hasOwnProperty("activeFocusOnPress")) {
353+ root.forceActiveFocus(reason);
354+ }
355+ }
356 }
357
358+ focus: true
359+
360+ /*!
361+ \internal
362+ Grab focus when moved, flicked or clicked
363+ */
364+ onMovementStarted: priv.requestFocus(Qt.MouseFocusReason)
365+ onFlickStarted: priv.requestFocus(Qt.MouseFocusReason)
366+ Toolkit.Mouse.onClicked: priv.requestFocus(Qt.MouseFocusReason)
367+
368 /*!
369 \preliminary
370 Expand the item at the given index.
371
372=== modified file 'modules/Ubuntu/Components/plugin/ucstyleditembase.cpp'
373--- modules/Ubuntu/Components/plugin/ucstyleditembase.cpp 2014-08-21 11:58:33 +0000
374+++ modules/Ubuntu/Components/plugin/ucstyleditembase.cpp 2014-08-22 17:18:58 +0000
375@@ -224,8 +224,7 @@
376 {
377 QQuickItem::mousePressEvent(event);
378 Q_D(UCStyledItemBase);
379- // accept the event if the focus was possible.
380- event->setAccepted(requestFocus(Qt::MouseFocusReason));
381+ requestFocus(Qt::MouseFocusReason);
382 }
383
384 #include "moc_ucstyleditembase.cpp"
385
386=== modified file 'tests/unit_x11/tst_components/tst_datepicker.qml'
387--- tests/unit_x11/tst_components/tst_datepicker.qml 2014-05-09 18:06:06 +0000
388+++ tests/unit_x11/tst_components/tst_datepicker.qml 2014-08-22 17:18:58 +0000
389@@ -59,7 +59,11 @@
390 function getPickerLabel(picker, name) {
391 var pickerItem = findChild(picker, name);
392 var pickerCurrent = findChild(pickerItem, "Picker_ViewLoader");
393- return pickerCurrent.item.currentItem.children[2];
394+ // note: find Label, Picker's label uses medium font size. The lookup must be changed
395+ // if the fontSize is changed!
396+ var pickerLabel = findChildWithProperty(pickerCurrent.item.currentItem, "fontSize", "medium");
397+ verify(pickerLabel, ("Label of %1 not accessible").arg(name));
398+ return pickerLabel;
399 }
400 function getPickerModel(picker, name) {
401 var pickerItem = findInvisibleChild(picker, name);
402@@ -318,7 +322,7 @@
403 picker.mode = "Years|Months|Days";
404 waitPickerMoving();
405
406- var yearLabel = getPickerLabel(picker, "PickerRow_YearPicker");
407+ var yearLabel = getPickerLabel(picker, "PickerRow_YearPicker", date.getFullYear());
408 var monthLabel = getPickerLabel(picker, "PickerRow_MonthPicker");
409 var monthModel = getPickerModel(picker, "PickerRow_MonthPicker");
410 var dayLabel = getPickerLabel(picker, "PickerRow_DayPicker");
411
412=== added file 'tests/unit_x11/tst_components/tst_focus.qml'
413--- tests/unit_x11/tst_components/tst_focus.qml 1970-01-01 00:00:00 +0000
414+++ tests/unit_x11/tst_components/tst_focus.qml 2014-08-22 17:18:58 +0000
415@@ -0,0 +1,240 @@
416+/*
417+ * Copyright 2014 Canonical Ltd.
418+ *
419+ * This program is free software; you can redistribute it and/or modify
420+ * it under the terms of the GNU Lesser General Public License as published by
421+ * the Free Software Foundation; version 3.
422+ *
423+ * This program is distributed in the hope that it will be useful,
424+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
425+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
426+ * GNU Lesser General Public License for more details.
427+ *
428+ * You should have received a copy of the GNU Lesser General Public License
429+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
430+ */
431+
432+import QtQuick 2.2
433+import QtTest 1.0
434+import Ubuntu.Test 1.0
435+import Ubuntu.Components 1.1
436+import Ubuntu.Components.Pickers 1.0
437+import Ubuntu.Components.ListItems 1.0 as ListItem
438+import Ubuntu.Components.Popups 1.0
439+
440+Item {
441+ id: main
442+ width: units.gu(50); height: units.gu(100)
443+
444+ property bool hasOSK: QuickUtils.inputMethodProvider !== ""
445+
446+ Flow {
447+ anchors {
448+ fill: parent
449+ margins: units.gu(0.5)
450+ }
451+ spacing: units.gu(1)
452+
453+ UbuntuListView {
454+ id: listView
455+ width: units.gu(50)
456+ height: units.gu(20)
457+ clip: true
458+ model: 10
459+ delegate: ListItem.Standard {
460+ text: "Whatever"
461+ }
462+ }
463+ TextField {
464+ id: textField
465+ text: "This is a text field with some text handling focus"
466+ }
467+ TextArea {
468+ id: textArea
469+ text: "This is a text area with some text handling focus"
470+ }
471+ Button {
472+ id: button
473+ text: "Press me"
474+ }
475+ CheckBox {
476+ id: checkbox
477+ text: "Check me"
478+ }
479+ Switch {
480+ id: switchBox
481+ }
482+ Picker {
483+ id: picker
484+ model: 10
485+ }
486+ Picker {
487+ id: roundPicker
488+ model: 10
489+ circular: true
490+ }
491+ Button {
492+ id: pickerPanel
493+ property date date: new Date()
494+ text: Qt.formatDateTime(date, "yyyy/MMMM")
495+ onClicked: PickerPanel.openDatePicker(pickerPanel, "date", "Years|Months")
496+ }
497+ Slider {
498+ id: slider
499+ }
500+ ComboButton {
501+ id: comboButton
502+ Rectangle {
503+ height: comboButton.comboListHeight
504+ color: "blue"
505+ }
506+ }
507+ Button {
508+ id: popoverTest
509+ text: "Popovers"
510+ property Item popover
511+ property Component popoverComponent
512+ onClicked: {
513+ popover = PopupUtils.open(popoverComponent)
514+ }
515+ }
516+ }
517+
518+ Component {
519+ id: dialogComponent
520+ Dialog {
521+ id: dialog
522+ title: "TestDialog"
523+ Button {
524+ text: "close"
525+ onClicked: PopupUtils.close(dialog)
526+ }
527+ }
528+ }
529+
530+ Component {
531+ id: popoverComponent
532+ Popover {
533+ id: popover
534+ contentWidth: units.gu(20)
535+ contentHeight: item.height
536+ ListItem.Standard {
537+ id: item
538+ text: "close"
539+ onClicked: PopupUtils.close(popover)
540+ }
541+ }
542+ }
543+
544+ UbuntuTestCase {
545+ name: "FocusingTests"
546+ when: windowShown
547+
548+ SignalSpy {
549+ id: popupCloseSpy
550+ signalName: "onDestruction"
551+ }
552+
553+ function initTestCase() {
554+ textField.forceActiveFocus();
555+ }
556+ function cleanup() {
557+ // empty event buffer
558+ wait(200);
559+ popupCloseSpy.clear();
560+ popupCloseSpy.target = null;
561+ }
562+
563+ // make this as the very first test executed
564+ function test_0_transfer_focus_data() {
565+ return [
566+ {tag: "TextArea", previousFocused: textField, focusOn: textArea, clickToDismiss: false},
567+ {tag: "Button", previousFocused: textArea, focusOn: button, clickToDismiss: false},
568+ {tag: "Checkbox", previousFocused: button, focusOn: checkbox, clickToDismiss: false},
569+ {tag: "Switch", previousFocused: checkbox, focusOn: switchBox, clickToDismiss: false},
570+ {tag: "Picker - linear", previousFocused: switchBox, focusOn: picker, clickToDismiss: false},
571+ {tag: "Picker - circular", previousFocused: picker, focusOn: roundPicker, clickToDismiss: false},
572+ {tag: "PickerPanel", previousFocused: roundPicker, focusOn: pickerPanel, clickToDismiss: true},
573+ {tag: "UbuntuListView", previousFocused: pickerPanel, focusOn: listView, clickToDismiss: false},
574+ {tag: "Slider", previousFocused: listView, focusOn: slider, clickToDismiss: false},
575+ {tag: "ComboButton", previousFocused: slider, focusOn: comboButton, clickToDismiss: false},
576+ ];
577+ }
578+ function test_0_transfer_focus(data) {
579+ // perform mouse press on
580+ mouseClick(data.focusOn, centerOf(data.focusOn).x, centerOf(data.focusOn).y);
581+ compare(data.previousFocused.focus, false, "Previous focus is still set!");
582+ compare(data.focusOn.focus, true, data.tag + " is not focused!");
583+ if (data.clickToDismiss) {
584+ mouseClick(main, 0, 0);
585+ }
586+ waitForRendering(data.focusOn, 200);
587+ }
588+
589+ function test_hide_osk_when_pickerpanel_opens() {
590+ if (!main.hasOSK) {
591+ skip("This functionality can be tested with OSK only.");
592+ }
593+
594+ textField.forceActiveFocus();
595+ verify(textField.focus, "TextField is not focused");
596+ verify(Qt.inputMethod.visible, "OSK is hidden");
597+ mouseClick(pickerPanel, centerOf(pickerPanel).x, centerOf(pickerPanel).y);
598+ verify(!Qt.inputMethod.visible, "OSK is visible still!");
599+ // remove panel/popover
600+ mouseClick(main, 0, 0);
601+ }
602+
603+ function test_textfield_clear_button_keeps_focus() {
604+ textField.forceActiveFocus();
605+ var text = textField.text;
606+
607+ var clearButton = findChild(textField, "clear_button");
608+ verify(clearButton, "clearButton is not accessible!");
609+ var center = centerOf(clearButton);
610+ mouseClick(clearButton, center.x, center.y);
611+ compare(textField.text, "", "Text had not been cleared?");
612+ compare(textField.focus, true, "Focus had been stolen from text input");
613+
614+ //restore
615+ textField.text = text;
616+ }
617+
618+ function test_combo_button_dropdown_focuses_component() {
619+ textField.forceActiveFocus();
620+ var dropdownButton = findChild(comboButton, "combobutton_dropdown");
621+ verify(dropdownButton, "dropdown button is not accessible?");
622+
623+ var center = centerOf(dropdownButton);
624+ mouseClick(dropdownButton, center.x, center.y);
625+ waitForRendering(comboButton);
626+ compare(dropdownButton.focus, true, "Dropdown button hasn't got focused!");
627+ compare(comboButton.focus, true, "ComboButton hasn't been focused!");
628+ comboButton.expanded = false;
629+ }
630+
631+ function test_popover_refocus_data() {
632+ return [
633+ {tag: "Dialog", component: dialogComponent},
634+ {tag: "Popover", component: popoverComponent},
635+ ];
636+ }
637+
638+ function test_popover_refocus(data) {
639+ popoverTest.popoverComponent = data.component;
640+ var center = centerOf(popoverTest);
641+ mouseClick(popoverTest, center.x, center.y);
642+ verify(popoverTest.focus, "Button focus not gained.");
643+ waitForRendering(popoverTest.popover);
644+ popupCloseSpy.target = popoverTest.popover.Component;
645+
646+ var closeButton = findChildWithProperty(popoverTest.popover, "text", "close");
647+ verify(closeButton, "Close button not accessible");
648+ center = centerOf(closeButton);
649+ mouseClick(closeButton, center.x, center.y);
650+ verify(!popoverTest.focus, "Button focus not lost.");
651+ popupCloseSpy.wait();
652+ verify(popoverTest.focus, "Button focus not restored.");
653+ }
654+ }
655+}
656
657=== modified file 'tests/unit_x11/tst_components/tst_tabs.qml'
658--- tests/unit_x11/tst_components/tst_tabs.qml 2014-04-25 05:28:37 +0000
659+++ tests/unit_x11/tst_components/tst_tabs.qml 2014-08-22 17:18:58 +0000
660@@ -16,323 +16,108 @@
661
662 import QtQuick 2.0
663 import QtTest 1.0
664+import Ubuntu.Test 1.0
665 import Ubuntu.Components 1.1
666
667-Item {
668- id: testCase
669+MainView {
670+ id: mainView
671 width: units.gu(50)
672 height: units.gu(80)
673
674 Tabs {
675- id: emptyTabs
676- }
677-
678- MainView {
679- id: mainView
680- anchors.fill: parent
681- Tabs {
682- id: tabs
683- Tab {
684- id: tab1
685- title: "tab 1"
686- page: Page {
687- id: page1
688- Button {
689- id: button
690- anchors.centerIn: parent
691- text: "click"
692- }
693- }
694- }
695- Tab {
696- id: tab2
697- title: "tab 2"
698- page: Page {
699- id: page2
700- }
701- }
702- Tab {
703- id: tab3
704- title: "tab 3"
705- page: Page {
706- id: page3
707- }
708- }
709- Tab {
710- id: tabFlick1
711- title: "flick"
712- page: Page {
713- Flickable {
714- id: flickable1
715- anchors.fill: parent
716- }
717- }
718- }
719- Tab {
720- id: tabFlick2
721- title: "flick 2"
722- page: Page {
723- Flickable {
724- id: flickable2
725- anchors.fill: parent
726- }
727- }
728- }
729- Tab {
730- id: tabFlickLoader
731- title: "load"
732- page: Loader {
733- id: loader
734- sourceComponent: tabs.selectedTabIndex != 5 ? null : pageComponent
735- }
736- }
737- Tab {
738- id: tabNoFlickLoader
739- title: "loadNoFlick"
740- page: Loader {
741- id: loaderNoFlick
742- anchors {
743- left: parent.left
744- right: parent.right
745- bottom: parent.bottom
746- }
747- // height compes from the loaded Page
748- sourceComponent: tabs.selectedTabIndex === 6 ? pageComponentNoFlick : null
749- }
750- }
751- }
752- Component {
753- id: pageComponent
754- Page {
755- title: "Loaded page"
756- property Flickable flick: loadedFlickable
757- Flickable {
758- id: loadedFlickable
759- anchors.fill: parent
760- contentHeight: 1000
761- }
762- }
763- }
764- Component {
765- id: pageComponentNoFlick
766- Page {
767- title: "Loaded page without flickable"
768- }
769- }
770- }
771-
772- ListModel {
773- id: inputModel
774- Component.onCompleted: {
775- append({ "name": "tab 1" });
776- insert(0, { "name": "tab 0" });
777- append({ "name": "tab 3" });
778- insert(2, { "name": "tab 2" });
779- }
780- }
781-
782- Tabs {
783- id: tabsWithRepeater
784- Repeater {
785- objectName: "tabsRepeater"
786- id: tabsRepeater
787- model: inputModel
788- Tab {
789- title: name
790- }
791- }
792- }
793-
794- Tabs {
795- id: repeaterTabs
796-
797- Repeater {
798- objectName: "repeater"
799- id: repeater
800- Tab {
801- title: modelData
802- }
803- }
804- }
805-
806- Tabs {
807- id: twoRepeaters
808-
809- Repeater {
810- objectName: "firstRepeater"
811- id: firstRepeater
812- model: inputModel
813- Tab {
814- title: name
815- }
816- }
817-
818- Repeater {
819- objectName: "secondRepeater"
820- id: secondRepeater
821- model: testCase.listModel
822- Tab {
823- title: modelData
824- }
825- }
826- }
827-
828- property var listModel: ["tab #0", "tab #1", "tab #2", "tab #3"];
829-
830- Tabs {
831- id: twinRepeaters
832- ListModel {
833- id: twinModel
834- Component.onCompleted: {
835- append({ "name": "twintab 1" });
836- insert(0, { "name": "twintab 0" });
837- append({ "name": "twintab 3" });
838- insert(2, { "name": "twintab 2" });
839- }
840- }
841- Repeater {
842- objectName: "tabsRepeater"
843- id: twinRepeater1
844- model: twinModel
845- Tab {
846- title: name
847- }
848- }
849- Repeater {
850- objectName: "tabsRepeater"
851- id: twinRepeater2
852- model: twinModel
853- Tab {
854- title: name
855- }
856- }
857- }
858-
859- TestCase {
860+ id: tabs
861+ Tab {
862+ id: tab1
863+ title: "tab 1"
864+ page: Page {
865+ id: page1
866+ Button {
867+ id: button
868+ anchors.centerIn: parent
869+ text: "click"
870+ }
871+ }
872+ }
873+ Tab {
874+ id: tab2
875+ title: "tab 2"
876+ page: Page {
877+ id: page2
878+ }
879+ }
880+ Tab {
881+ id: tab3
882+ title: "tab 3"
883+ page: Page {
884+ id: page3
885+ }
886+ }
887+ Tab {
888+ id: tabFlick1
889+ title: "flick"
890+ page: Page {
891+ Flickable {
892+ id: flickable1
893+ anchors.fill: parent
894+ }
895+ }
896+ }
897+ Tab {
898+ id: tabFlick2
899+ title: "flick 2"
900+ page: Page {
901+ Flickable {
902+ id: flickable2
903+ anchors.fill: parent
904+ }
905+ }
906+ }
907+ Tab {
908+ id: tabFlickLoader
909+ title: "load"
910+ page: Loader {
911+ id: loader
912+ sourceComponent: tabs.selectedTabIndex != 5 ? null : pageComponent
913+ }
914+ }
915+ Tab {
916+ id: tabNoFlickLoader
917+ title: "loadNoFlick"
918+ page: Loader {
919+ id: loaderNoFlick
920+ anchors {
921+ left: parent.left
922+ right: parent.right
923+ bottom: parent.bottom
924+ }
925+ // height compes from the loaded Page
926+ sourceComponent: tabs.selectedTabIndex === 6 ? pageComponentNoFlick : null
927+ }
928+ }
929+ }
930+ Component {
931+ id: pageComponent
932+ Page {
933+ title: "Loaded page"
934+ property Flickable flick: loadedFlickable
935+ Flickable {
936+ id: loadedFlickable
937+ anchors.fill: parent
938+ contentHeight: 1000
939+ }
940+ }
941+ }
942+ Component {
943+ id: pageComponentNoFlick
944+ Page {
945+ title: "Loaded page without flickable"
946+ }
947+ }
948+
949+ UbuntuTestCase {
950 name: "TabsAPI"
951 when: windowShown
952
953- /*
954- The following testcases are all related to bug #1253804
955- */
956- function test_tabOrder_bug1253804() {
957- var tabsModel = tabsWithRepeater.__model;
958-
959- compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs");
960- compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar");
961- for (var i=0; i < tabsModel.count; i++) {
962- compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
963- }
964-
965- //shufle
966- inputModel.move(1, 2, 1);
967- inputModel.move(3, 0, 1);
968- inputModel.move(1, 3, 1);
969- // wait few miliseconds
970- wait(50);
971- for (i=0; i < tabsModel.count; i++) {
972- compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles after shuffling don't match for index "+i);
973- }
974-
975- // set it to null
976- tabsRepeater.model = null;
977- compare(tabsWithRepeater.__model.count, 0, "There are still tabs left after repeater model is reset");
978- }
979-
980- function test_repeaterTabs() {
981- repeater.model = inputModel;
982- var tabsModel = repeaterTabs.__model;
983-
984- compare(repeater.count, inputModel.count, "Incorrect number of tabs in Tabs");
985- compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar");
986- for (var i=0; i < tabsModel.count; i++) {
987- compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
988- }
989-
990- // clear repeaterTabs
991- repeater.model = null;
992- compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset");
993- }
994-
995- function test_repeaterTabs_arrayAsModel() {
996- repeater.model = testCase.listModel;
997- var tabsModel = repeaterTabs.__model;
998-
999- compare(repeater.count, testCase.listModel.length, "Incorrect number of tabs in Tabs");
1000- compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar");
1001- for (var i=0; i < tabsModel.count; i++) {
1002- compare(tabsModel.get(i).title, testCase.listModel[i], "Tab titles don't match for index "+i);
1003- }
1004-
1005- // shuffling elements in an array is not detectable in Repeater
1006- var x = testCase.listModel[1];
1007- testCase.listModel[1] = testCase.listModel[0];
1008- testCase.listModel[0] = x;
1009- expectFailContinue("", "Array changes are not detected by repeaters");
1010- compare(tabsModel.get(0).title, testCase.listModel[0], "Tab titles don't match for index 0");
1011- expectFailContinue("", "Array changes are not detected by repeaters");
1012- compare(tabsModel.get(1).title, testCase.listModel[1], "Tab titles don't match for index 0");
1013-
1014- // clear repeaterTabs
1015- repeater.model = null;
1016- compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset");
1017-
1018- }
1019-
1020- function test_twoRepeaters() {
1021- var tabsModel = twoRepeaters.__model;
1022- var secondRepeaterModel = secondRepeater.model;
1023-
1024- compare(tabsModel.count, firstRepeater.count + secondRepeater.count, "Incorrect number of tabs in TabBar");
1025- for (var i = 0; i < firstRepeater.count; i++) {
1026- compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
1027- }
1028- for (i = firstRepeater.count; i < firstRepeater.count + secondRepeater.count; i++) {
1029- compare(tabsModel.get(i).title, secondRepeaterModel[i - firstRepeater.count], "Tab titles don't match for index "+i);
1030- }
1031- }
1032-
1033- function test_twinRepeaters() {
1034- var tabsModel = twinRepeaters.__model;
1035-
1036- compare(twinRepeater1.count, twinModel.count, "Incorrect number of tabs in the first repeater");
1037- compare(twinRepeater2.count, twinModel.count, "Incorrect number of tabs in the second repeater");
1038- compare(tabsModel.count, twinRepeater1.count + twinRepeater2.count, "Incorrect number of tabs in TabBar");
1039- for (var j = 0; j < 2; j++) {
1040- for (var i = 0; i < twinModel.count; i++) {
1041- var index = j * twinModel.count + i;
1042- compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index);
1043- }
1044- }
1045-
1046- //shuffle
1047- twinModel.move(1, 2, 1);
1048- twinModel.move(3, 0, 1);
1049- twinModel.move(1, 3, 1);
1050- // wait few miliseconds till Tabs update is realized
1051- wait(50);
1052-
1053- /* FIXME
1054- for (var j = 0; j < 2; j++) {
1055- for (var i = 0; i < twinModel.count; i++) {
1056- var index = j * twinModel.count + i;
1057- compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index);
1058- }
1059- }
1060- */
1061-
1062- // set it to null
1063- twinRepeater1.model = null;
1064- twinRepeater2.model = null;
1065- compare(twinRepeaters.__model.count, 0, "There are still tabs left after repeater model is reset");
1066- }
1067-
1068- function test_emptyTabs() {
1069- compare(emptyTabs.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs");
1070- compare(emptyTabs.selectedTab, null, "The default selected tab is null when there are no tabs");
1071- compare(emptyTabs.currentPage, null, "The default currentPage is null when there are no tabs");
1072- }
1073-
1074 /* FIXME
1075 function test_tabsDefaults() {
1076 compare(tabs.selectedTabIndex, 0, "The default selectedTabIndex is 0 when Tabs has contents");
1077@@ -412,8 +197,10 @@
1078
1079 function test_deactivateByAppInteraction() {
1080 tabs.tabBar.selectionMode = true;
1081+ tabs.selectedTabIndex = 0;
1082+ waitForRendering(tabs.tabBar, 400);
1083 compare(tabs.tabBar.selectionMode, true, "Tab bar can be put into selection mode");
1084- mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton);
1085+ mouseClick(button, centerOf(button).x, centerOf(button).y);
1086 compare(tabs.tabBar.selectionMode, false, "Tab bar deactivated by interacting with the page contents");
1087 }
1088
1089
1090=== added file 'tests/unit_x11/tst_components/tst_tabs_empty.qml'
1091--- tests/unit_x11/tst_components/tst_tabs_empty.qml 1970-01-01 00:00:00 +0000
1092+++ tests/unit_x11/tst_components/tst_tabs_empty.qml 2014-08-22 17:18:58 +0000
1093@@ -0,0 +1,41 @@
1094+/*
1095+ * Copyright 2014 Canonical Ltd.
1096+ *
1097+ * This program is free software; you can redistribute it and/or modify
1098+ * it under the terms of the GNU Lesser General Public License as published by
1099+ * the Free Software Foundation; version 3.
1100+ *
1101+ * This program is distributed in the hope that it will be useful,
1102+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1103+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1104+ * GNU Lesser General Public License for more details.
1105+ *
1106+ * You should have received a copy of the GNU Lesser General Public License
1107+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1108+ */
1109+
1110+import QtQuick 2.2
1111+import Ubuntu.Components 1.1
1112+import Ubuntu.Test 1.0
1113+
1114+MainView {
1115+ width: units.gu(50)
1116+ height: units.gu(100)
1117+
1118+ useDeprecatedToolbar: true
1119+
1120+ Tabs {
1121+ id: empty
1122+ }
1123+
1124+ UbuntuTestCase {
1125+ name: "EmptyTabs"
1126+ when: windowShown
1127+
1128+ function test_emptyTabs() {
1129+ compare(empty.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs");
1130+ compare(empty.selectedTab, null, "The default selected tab is null when there are no tabs");
1131+ compare(empty.currentPage, null, "The default currentPage is null when there are no tabs");
1132+ }
1133+ }
1134+}
1135
1136=== added file 'tests/unit_x11/tst_components/tst_tabs_with_repeater.qml'
1137--- tests/unit_x11/tst_components/tst_tabs_with_repeater.qml 1970-01-01 00:00:00 +0000
1138+++ tests/unit_x11/tst_components/tst_tabs_with_repeater.qml 2014-08-22 17:18:58 +0000
1139@@ -0,0 +1,234 @@
1140+/*
1141+ * Copyright 2014 Canonical Ltd.
1142+ *
1143+ * This program is free software; you can redistribute it and/or modify
1144+ * it under the terms of the GNU Lesser General Public License as published by
1145+ * the Free Software Foundation; version 3.
1146+ *
1147+ * This program is distributed in the hope that it will be useful,
1148+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1149+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1150+ * GNU Lesser General Public License for more details.
1151+ *
1152+ * You should have received a copy of the GNU Lesser General Public License
1153+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1154+ */
1155+
1156+import QtQuick 2.2
1157+import Ubuntu.Components 1.1
1158+import Ubuntu.Test 1.0
1159+
1160+MainView {
1161+ id: testCase
1162+ width: units.gu(50)
1163+ height: units.gu(100)
1164+
1165+ useDeprecatedToolbar: true
1166+
1167+ ListModel {
1168+ id: inputModel
1169+ Component.onCompleted: {
1170+ append({ "name": "tab 1" });
1171+ insert(0, { "name": "tab 0" });
1172+ append({ "name": "tab 3" });
1173+ insert(2, { "name": "tab 2" });
1174+ }
1175+ }
1176+
1177+ Tabs {
1178+ id: tabsWithRepeater
1179+ Repeater {
1180+ objectName: "tabsRepeater"
1181+ id: tabsRepeater
1182+ model: inputModel
1183+ Tab {
1184+ title: name
1185+ }
1186+ }
1187+ }
1188+
1189+ Tabs {
1190+ id: repeaterTabs
1191+
1192+ Repeater {
1193+ objectName: "repeater"
1194+ id: repeater
1195+ Tab {
1196+ title: modelData
1197+ }
1198+ }
1199+ }
1200+
1201+ Tabs {
1202+ id: twoRepeaters
1203+
1204+ Repeater {
1205+ objectName: "firstRepeater"
1206+ id: firstRepeater
1207+ model: inputModel
1208+ Tab {
1209+ title: name
1210+ }
1211+ }
1212+
1213+ Repeater {
1214+ objectName: "secondRepeater"
1215+ id: secondRepeater
1216+ model: testCase.listModel
1217+ Tab {
1218+ title: modelData
1219+ }
1220+ }
1221+ }
1222+
1223+ property var listModel: ["tab #0", "tab #1", "tab #2", "tab #3"];
1224+
1225+ Tabs {
1226+ id: twinRepeaters
1227+ ListModel {
1228+ id: twinModel
1229+ Component.onCompleted: {
1230+ append({ "name": "twintab 1" });
1231+ insert(0, { "name": "twintab 0" });
1232+ append({ "name": "twintab 3" });
1233+ insert(2, { "name": "twintab 2" });
1234+ }
1235+ }
1236+ Repeater {
1237+ objectName: "tabsRepeater"
1238+ id: twinRepeater1
1239+ model: twinModel
1240+ Tab {
1241+ title: name
1242+ }
1243+ }
1244+ Repeater {
1245+ objectName: "tabsRepeater"
1246+ id: twinRepeater2
1247+ model: twinModel
1248+ Tab {
1249+ title: name
1250+ }
1251+ }
1252+ }
1253+
1254+ UbuntuTestCase {
1255+ name: "TabsWithRepeater"
1256+ when: windowShown
1257+
1258+ /*
1259+ The following testcases are all related to bug #1253804
1260+ */
1261+ function test_tabOrder_bug1253804() {
1262+ var tabsModel = tabsWithRepeater.__model;
1263+
1264+ compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs");
1265+ compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar");
1266+ for (var i=0; i < tabsModel.count; i++) {
1267+ compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
1268+ }
1269+
1270+ //shufle
1271+ inputModel.move(1, 2, 1);
1272+ inputModel.move(3, 0, 1);
1273+ inputModel.move(1, 3, 1);
1274+ // wait few miliseconds
1275+ wait(50);
1276+ for (i=0; i < tabsModel.count; i++) {
1277+ compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles after shuffling don't match for index "+i);
1278+ }
1279+
1280+ // set it to null
1281+ tabsRepeater.model = null;
1282+ compare(tabsWithRepeater.__model.count, 0, "There are still tabs left after repeater model is reset");
1283+ }
1284+
1285+ function test_repeaterTabs() {
1286+ repeater.model = inputModel;
1287+ var tabsModel = repeaterTabs.__model;
1288+
1289+ compare(repeater.count, inputModel.count, "Incorrect number of tabs in Tabs");
1290+ compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar");
1291+ for (var i=0; i < tabsModel.count; i++) {
1292+ compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
1293+ }
1294+
1295+ // clear repeaterTabs
1296+ repeater.model = null;
1297+ compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset");
1298+ }
1299+
1300+ function test_repeaterTabs_arrayAsModel() {
1301+ repeater.model = testCase.listModel;
1302+ var tabsModel = repeaterTabs.__model;
1303+
1304+ compare(repeater.count, testCase.listModel.length, "Incorrect number of tabs in Tabs");
1305+ compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar");
1306+ for (var i=0; i < tabsModel.count; i++) {
1307+ compare(tabsModel.get(i).title, testCase.listModel[i], "Tab titles don't match for index "+i);
1308+ }
1309+
1310+ // shuffling elements in an array is not detectable in Repeater
1311+ var x = testCase.listModel[1];
1312+ testCase.listModel[1] = testCase.listModel[0];
1313+ testCase.listModel[0] = x;
1314+ expectFailContinue("", "Array changes are not detected by repeaters");
1315+ compare(tabsModel.get(0).title, testCase.listModel[0], "Tab titles don't match for index 0");
1316+ expectFailContinue("", "Array changes are not detected by repeaters");
1317+ compare(tabsModel.get(1).title, testCase.listModel[1], "Tab titles don't match for index 0");
1318+
1319+ // clear repeaterTabs
1320+ repeater.model = null;
1321+ compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset");
1322+
1323+ }
1324+
1325+ function test_twoRepeaters() {
1326+ var tabsModel = twoRepeaters.__model;
1327+ var secondRepeaterModel = secondRepeater.model;
1328+
1329+ compare(tabsModel.count, firstRepeater.count + secondRepeater.count, "Incorrect number of tabs in TabBar");
1330+ for (var i = 0; i < firstRepeater.count; i++) {
1331+ compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
1332+ }
1333+ for (i = firstRepeater.count; i < firstRepeater.count + secondRepeater.count; i++) {
1334+ compare(tabsModel.get(i).title, secondRepeaterModel[i - firstRepeater.count], "Tab titles don't match for index "+i);
1335+ }
1336+ }
1337+
1338+ function test_twinRepeaters() {
1339+ var tabsModel = twinRepeaters.__model;
1340+
1341+ compare(twinRepeater1.count, twinModel.count, "Incorrect number of tabs in the first repeater");
1342+ compare(twinRepeater2.count, twinModel.count, "Incorrect number of tabs in the second repeater");
1343+ compare(tabsModel.count, twinRepeater1.count + twinRepeater2.count, "Incorrect number of tabs in TabBar");
1344+ for (var j = 0; j < 2; j++) {
1345+ for (var i = 0; i < twinModel.count; i++) {
1346+ var index = j * twinModel.count + i;
1347+ compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index);
1348+ }
1349+ }
1350+
1351+ //shuffle
1352+ twinModel.move(1, 2, 1);
1353+ twinModel.move(3, 0, 1);
1354+ twinModel.move(1, 3, 1);
1355+ // wait few miliseconds till Tabs update is realized
1356+ wait(50);
1357+
1358+ /* FIXME
1359+ for (var j = 0; j < 2; j++) {
1360+ for (var i = 0; i < twinModel.count; i++) {
1361+ var index = j * twinModel.count + i;
1362+ compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index);
1363+ }
1364+ }
1365+ */
1366+
1367+ // set it to null
1368+ twinRepeater1.model = null;
1369+ twinRepeater2.model = null;
1370+ compare(twinRepeaters.__model.count, 0, "There are still tabs left after repeater model is reset");
1371+ }
1372+ }
1373+}

Subscribers

People subscribed via source and target branches