Merge lp:~zsombi/ubuntu-ui-toolkit/list_item_focus into lp:ubuntu-ui-toolkit/staging
- list_item_focus
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Cris Dywan |
Approved revision: | 1785 |
Merged at revision: | 1878 |
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/list_item_focus |
Merge into: | lp:ubuntu-ui-toolkit/staging |
Diff against target: |
1287 lines (+897/-29) 16 files modified
src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml (+1/-0) src/Ubuntu/Components/plugin/plugin.pri (+2/-0) src/Ubuntu/Components/plugin/privates/listviewextensions.cpp (+151/-0) src/Ubuntu/Components/plugin/privates/listviewextensions.h (+60/-0) src/Ubuntu/Components/plugin/quickutils.cpp (+53/-0) src/Ubuntu/Components/plugin/quickutils.h (+3/-0) src/Ubuntu/Components/plugin/uclistitem.cpp (+110/-7) src/Ubuntu/Components/plugin/uclistitem.h (+7/-0) src/Ubuntu/Components/plugin/uclistitem_p.h (+5/-1) src/Ubuntu/Components/plugin/ucstyleditembase.cpp (+16/-1) src/Ubuntu/Components/plugin/ucstyleditembase.h (+2/-1) src/Ubuntu/Components/plugin/ucstyleditembase_p.h (+1/-0) src/Ubuntu/Components/plugin/ucviewitemsattached.cpp (+28/-10) tests/resources/listitems/ListItemTest.qml (+10/-5) tests/unit_x11/tst_components/tst_listitem_focus.qml (+417/-0) tests/unit_x11/tst_components/tst_quickutils.qml (+31/-4) |
To merge this branch: | bzr merge lp:~zsombi/ubuntu-ui-toolkit/list_item_focus |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
ubuntu-sdk-build-bot | continuous-integration | Approve | |
Cris Dywan | Approve | ||
PS Jenkins bot | continuous-integration | Pending | |
Tim Peeters | Pending | ||
Review via email: mp+287648@code.launchpad.net |
This proposal supersedes a proposal from 2016-02-29.
Commit message
ListItem focus navigation in ListView and other views.
Description of the change
- introduce internal isFocusScope into StyledItem to drive whether a StyledItem is a focus scope or not; FocusScopes can get the activeFocus true only the first time they are focused, in any other cases their last focused ascendant will be the activeFocus; ListItem's focus framing requires it to be activeFocus, therefore we need to turn off the focus scope in the StyledItem, and that cannot be removed once it is set.
- internal ListViewProxy added to wrap inaccessible QQuickListView API; it also overrides the default ListView navigation (up/down for vertical, left/right for horizontal orientations) to be able to notify the ListItem being focused by key events causing focus frame to be drawn.
Focus handling of ListItem:
- when in ListView, Tab/Backtab is used to enter/leave the focus of ListView. If no item is selected in ListView, it will focus and select the first available item in the ListView. When Tab/Backtab is pressed again, it will leave the ListView. ListItems are selected and focused the same time using the up/down or left/right arrows depending on the ListView direction. When a selected and focused ListItem has focusable child components (i.e. CheckBox, Switch, etc) this can be focused through the horizontal navigation keys (left/right) or vertical ones depending of the orientation.
- when a ListItem is out of the ListView, it will obey to the default Tab/Backtab navigation rules, and its child focusable elements can be focused either with horizontal keys or with the standard Tab/Backtab.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1768
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1770
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1770
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1770
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1772
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1772
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1773
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1773
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1775
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1775
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
I feel it should be good to have separate if basic unit tests for firstFocusableC
+ // first or the last focus child is reached, so we wrap around
Do we really want wrap-around? I'm double-checking because we planned to not have it for Tab focus in general, see bug 1523821, unless that decision has been re-considered in the meantime.
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
> I feel it should be good to have separate if basic unit tests for
> firstFocusableC
> on their behavior when looking at ListItem tests.
Good note, I'll add those, though that needs a separate unit test as those are private APIs.
>
> + // first or the last focus child is reached, so we wrap around
>
> Do we really want wrap-around? I'm double-checking because we planned to not
> have it for Tab focus in general, see bug 1523821, unless that decision has
> been re-considered in the meantime.
Hehe, that's interesting. Last time I've chatted with Femma was that this (and even others) will have to wrap around. Now, if we must forbid wrapping around, we will be in trouble, as Qt has no option to stop that. We could do that in our implementation, however even that wouldn't be straight forward, as next tabletop element may not necessarily be the sibling or the child of the current focus, so the tab order is not that clear there. And as said, Qt functions return the next tabletop, which in case it is to jump from the last one, it'll wrap around. As every UI in the OS worlds does that. So this would be again something we want to format the user's brain...
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
> > + // first or the last focus child is reached, so we wrap around
> >
> > Do we really want wrap-around? I'm double-checking because we planned to not
> > have it for Tab focus in general, see bug 1523821, unless that decision has
> > been re-considered in the meantime.
>
> Hehe, that's interesting. Last time I've chatted with Femma was that this (and
> even others) will have to wrap around. Now, if we must forbid wrapping around,
> we will be in trouble, as Qt has no option to stop that. We could do that in
> our implementation, however even that wouldn't be straight forward, as next
> tabletop element may not necessarily be the sibling or the child of the
> current focus, so the tab order is not that clear there. And as said, Qt
> functions return the next tabletop, which in case it is to jump from the last
> one, it'll wrap around. As every UI in the OS worlds does that. So this would
> be again something we want to format the user's brain...
The way I see it we wouldn't do something else but we would do nothing at all once you reach the bottom (or top), so I'm not sure most users even rely on that. I agree, though, it may be tricky to implement at all.
Let's consider it part of bug 1523821 then, since you've written the code anyway, so for the moment it's consistent.
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
>
> The way I see it we wouldn't do something else but we would do nothing at all
> once you reach the bottom (or top), so I'm not sure most users even rely on
> that. I agree, though, it may be tricky to implement at all.
As said, even GTK apps go around with the focus, they never stop in the bottom or top. OSX, Windows does that, why would we want to format brains again? Like the easiest way for a user - if he got used to that - to reach the first before the last element while the very first one is focused is to push the Backtab twice. In our case this would require him/her to walk along the tabletops and get there finally...
>
> Let's consider it part of bug 1523821 then, since you've written the code
> anyway, so for the moment it's consistent.
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1775
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1775
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1775
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Autolanding.
More details in the following jenkins job:
https:/
Executed test runs:
None: https:/
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
We need support for cursor keys in the ListView used in SectionsStyle in https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
I can, after using swipe to reveal left or right hand actions, use arrows to focus the actions and even activate them; however there's no visual indication of that.
I'm not sure this is wanted/ needed since the context menu would anyway achieve the same thing - maybe they should simply not take focus.
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
In the gallery I can reproduce a new bug: in the list view used to switch pages I can use the arrows to move the selection past the last item. The focus visual is still on the last item, and window.
examples/
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
In the debian control file we need to update the source dependency on a Qt that fixes this:
plugin/
plugin/
^
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
> I can, after using swipe to reveal left or right hand actions, use arrows to
> focus the actions and even activate them; however there's no visual indication
> of that.
> I'm not sure this is wanted/ needed since the context menu would anyway
> achieve the same thing - maybe they should simply not take focus.
Following up on this: Definitely not wanted, as per the spec. So disabling is the way to go.
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1778
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
- 1783. By Zsombor Egri
-
roll back palette changes
- 1784. By Zsombor Egri
-
origin merged
- 1785. By Zsombor Egri
-
API fixed
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1783
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Cris Dywan (kalikiana) wrote : | # |
Sweet, sweet fixes. Like the additional tests. Let's get this in!
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
PASSED: Continuous integration, rev:1785
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) : | # |
Preview Diff
1 | === modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml' | |||
2 | --- src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2016-01-27 16:42:47 +0000 | |||
3 | +++ src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2016-03-01 15:09:23 +0000 | |||
4 | @@ -79,6 +79,7 @@ | |||
5 | 79 | id: actionButton | 79 | id: actionButton |
6 | 80 | action: modelData | 80 | action: modelData |
7 | 81 | enabled: action.enabled | 81 | enabled: action.enabled |
8 | 82 | activeFocusOnTab: false | ||
9 | 82 | width: MathUtils.clamp(delegateLoader.item ? delegateLoader.item.width : 0, actionsRow.minItemWidth, actionsRow.maxItemWidth) | 83 | width: MathUtils.clamp(delegateLoader.item ? delegateLoader.item.width : 0, actionsRow.minItemWidth, actionsRow.maxItemWidth) |
10 | 83 | anchors { | 84 | anchors { |
11 | 84 | top: parent ? parent.top : undefined | 85 | top: parent ? parent.top : undefined |
12 | 85 | 86 | ||
13 | === modified file 'src/Ubuntu/Components/plugin/plugin.pri' | |||
14 | --- src/Ubuntu/Components/plugin/plugin.pri 2016-02-10 18:02:18 +0000 | |||
15 | +++ src/Ubuntu/Components/plugin/plugin.pri 2016-03-01 15:09:23 +0000 | |||
16 | @@ -110,6 +110,7 @@ | |||
17 | 110 | $$PWD/ucmainviewbase.h \ | 110 | $$PWD/ucmainviewbase.h \ |
18 | 111 | $$PWD/ucmainviewbase_p.h \ | 111 | $$PWD/ucmainviewbase_p.h \ |
19 | 112 | $$PWD/ucperformancemonitor.h \ | 112 | $$PWD/ucperformancemonitor.h \ |
20 | 113 | $$PWD/privates/listviewextensions.h \ | ||
21 | 113 | $$PWD/privates/frame.h \ | 114 | $$PWD/privates/frame.h \ |
22 | 114 | $$PWD/privates/ucpagewrapper.h \ | 115 | $$PWD/privates/ucpagewrapper.h \ |
23 | 115 | $$PWD/privates/ucpagewrapper_p.h \ | 116 | $$PWD/privates/ucpagewrapper_p.h \ |
24 | @@ -187,6 +188,7 @@ | |||
25 | 187 | $$PWD/ucpagetreenode.cpp \ | 188 | $$PWD/ucpagetreenode.cpp \ |
26 | 188 | $$PWD/ucmainviewbase.cpp \ | 189 | $$PWD/ucmainviewbase.cpp \ |
27 | 189 | $$PWD/ucperformancemonitor.cpp \ | 190 | $$PWD/ucperformancemonitor.cpp \ |
28 | 191 | $$PWD/privates/listviewextensions.cpp \ | ||
29 | 190 | $$PWD/privates/frame.cpp \ | 192 | $$PWD/privates/frame.cpp \ |
30 | 191 | $$PWD/privates/ucpagewrapper.cpp \ | 193 | $$PWD/privates/ucpagewrapper.cpp \ |
31 | 192 | $$PWD/privates/ucpagewrapperincubator.cpp \ | 194 | $$PWD/privates/ucpagewrapperincubator.cpp \ |
32 | 193 | 195 | ||
33 | === added file 'src/Ubuntu/Components/plugin/privates/listviewextensions.cpp' | |||
34 | --- src/Ubuntu/Components/plugin/privates/listviewextensions.cpp 1970-01-01 00:00:00 +0000 | |||
35 | +++ src/Ubuntu/Components/plugin/privates/listviewextensions.cpp 2016-03-01 15:09:23 +0000 | |||
36 | @@ -0,0 +1,151 @@ | |||
37 | 1 | /* | ||
38 | 2 | * Copyright 2016 Canonical Ltd. | ||
39 | 3 | * | ||
40 | 4 | * This program is free software; you can redistribute it and/or modify | ||
41 | 5 | * it under the terms of the GNU Lesser General Public License as published by | ||
42 | 6 | * the Free Software Foundation; version 3. | ||
43 | 7 | * | ||
44 | 8 | * This program is distributed in the hope that it will be useful, | ||
45 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
46 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
47 | 11 | * GNU Lesser General Public License for more details. | ||
48 | 12 | * | ||
49 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
50 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
51 | 15 | * | ||
52 | 16 | * Author Zsombor Egri <zsombor.egri@canonical.com> | ||
53 | 17 | */ | ||
54 | 18 | |||
55 | 19 | #include "listviewextensions.h" | ||
56 | 20 | #include "uclistitem_p.h" | ||
57 | 21 | #include "quickutils.h" | ||
58 | 22 | #include <QtQuick/QQuickItem> | ||
59 | 23 | #include <QtQuick/private/qquickflickable_p.h> | ||
60 | 24 | |||
61 | 25 | ListViewProxy::ListViewProxy(QQuickFlickable *listView, QObject *parent) | ||
62 | 26 | : QObject(parent) | ||
63 | 27 | , listView(listView) | ||
64 | 28 | { | ||
65 | 29 | } | ||
66 | 30 | ListViewProxy::~ListViewProxy() | ||
67 | 31 | { | ||
68 | 32 | if (isEventFilter) { | ||
69 | 33 | listView->removeEventFilter(this); | ||
70 | 34 | } | ||
71 | 35 | } | ||
72 | 36 | |||
73 | 37 | // proxy methods | ||
74 | 38 | |||
75 | 39 | Qt::Orientation ListViewProxy::orientation() | ||
76 | 40 | { | ||
77 | 41 | return (Qt::Orientation)listView->property("orientation").toInt(); | ||
78 | 42 | } | ||
79 | 43 | |||
80 | 44 | int ListViewProxy::count() | ||
81 | 45 | { | ||
82 | 46 | return listView->property("count").toInt(); | ||
83 | 47 | } | ||
84 | 48 | |||
85 | 49 | QQuickItem *ListViewProxy::currentItem() | ||
86 | 50 | { | ||
87 | 51 | return listView->property("currentItem").value<QQuickItem*>(); | ||
88 | 52 | } | ||
89 | 53 | |||
90 | 54 | int ListViewProxy::currentIndex() | ||
91 | 55 | { | ||
92 | 56 | return listView->property("currentIndex").toInt(); | ||
93 | 57 | } | ||
94 | 58 | |||
95 | 59 | void ListViewProxy::setCurrentIndex(int index) | ||
96 | 60 | { | ||
97 | 61 | listView->setProperty("currentIndex", index); | ||
98 | 62 | } | ||
99 | 63 | |||
100 | 64 | QVariant ListViewProxy::model() | ||
101 | 65 | { | ||
102 | 66 | return listView->property("model"); | ||
103 | 67 | } | ||
104 | 68 | |||
105 | 69 | /********************************************************************* | ||
106 | 70 | * Additional functionality used in different places in toolkit | ||
107 | 71 | *********************************************************************/ | ||
108 | 72 | |||
109 | 73 | // Navigation override used by ListItems | ||
110 | 74 | void ListViewProxy::overrideItemNavigation(bool override) | ||
111 | 75 | { | ||
112 | 76 | if (override) { | ||
113 | 77 | listView->installEventFilter(this); | ||
114 | 78 | } else { | ||
115 | 79 | listView->removeEventFilter(this); | ||
116 | 80 | } | ||
117 | 81 | isEventFilter = override; | ||
118 | 82 | } | ||
119 | 83 | |||
120 | 84 | bool ListViewProxy::eventFilter(QObject *, QEvent *event) | ||
121 | 85 | { | ||
122 | 86 | switch (event->type()) { | ||
123 | 87 | case QEvent::FocusIn: | ||
124 | 88 | return focusInEvent(static_cast<QFocusEvent*>(event)); | ||
125 | 89 | case QEvent::KeyPress: | ||
126 | 90 | return keyPressEvent(static_cast<QKeyEvent*>(event)); | ||
127 | 91 | default: | ||
128 | 92 | break; | ||
129 | 93 | } | ||
130 | 94 | |||
131 | 95 | return false; | ||
132 | 96 | } | ||
133 | 97 | |||
134 | 98 | void ListViewProxy::setKeyNavigationForListView(bool value) | ||
135 | 99 | { | ||
136 | 100 | UCListItem *listItem = qobject_cast<UCListItem*>(currentItem()); | ||
137 | 101 | if (listItem) { | ||
138 | 102 | UCListItemPrivate::get(listItem)->setListViewKeyNavigation(value); | ||
139 | 103 | listItem->update(); | ||
140 | 104 | } | ||
141 | 105 | } | ||
142 | 106 | |||
143 | 107 | // grab focusIn event | ||
144 | 108 | bool ListViewProxy::focusInEvent(QFocusEvent *event) | ||
145 | 109 | { | ||
146 | 110 | switch (event->reason()) { | ||
147 | 111 | case Qt::TabFocusReason: | ||
148 | 112 | case Qt::BacktabFocusReason: | ||
149 | 113 | { | ||
150 | 114 | QQuickItem *currentItem = this->currentItem(); | ||
151 | 115 | if (!currentItem && count() > 0) { | ||
152 | 116 | // set the first one to be the focus | ||
153 | 117 | setCurrentIndex(0); | ||
154 | 118 | setKeyNavigationForListView(true); | ||
155 | 119 | } | ||
156 | 120 | break; | ||
157 | 121 | } | ||
158 | 122 | default: | ||
159 | 123 | break; | ||
160 | 124 | } | ||
161 | 125 | return false; | ||
162 | 126 | } | ||
163 | 127 | |||
164 | 128 | // override up/down key presses for ListView | ||
165 | 129 | bool ListViewProxy::keyPressEvent(QKeyEvent *event) | ||
166 | 130 | { | ||
167 | 131 | int key = event->key(); | ||
168 | 132 | Qt::Orientation orientation = this->orientation(); | ||
169 | 133 | |||
170 | 134 | if ((orientation == Qt::Vertical && key != Qt::Key_Up && key != Qt::Key_Down) | ||
171 | 135 | || (orientation == Qt::Horizontal && key != Qt::Key_Left && key != Qt::Key_Right)) { | ||
172 | 136 | return false; | ||
173 | 137 | } | ||
174 | 138 | // for horizontal moves take into account the layout mirroring | ||
175 | 139 | bool isRtl = QQuickItemPrivate::get(listView)->effectiveLayoutMirror; | ||
176 | 140 | bool forwards = (key == Qt::Key_Up || (isRtl ? key == Qt::Key_Left : key == Qt::Key_Right)); | ||
177 | 141 | int currentIndex = this->currentIndex(); | ||
178 | 142 | int count = this->count(); | ||
179 | 143 | |||
180 | 144 | if (currentIndex >= 0 && count > 0) { | ||
181 | 145 | currentIndex = qBound<int>(0, forwards ? currentIndex - 1 : currentIndex + 1, count - 1); | ||
182 | 146 | setCurrentIndex(currentIndex); | ||
183 | 147 | setKeyNavigationForListView(true); | ||
184 | 148 | } | ||
185 | 149 | |||
186 | 150 | return true; | ||
187 | 151 | } | ||
188 | 0 | 152 | ||
189 | === added file 'src/Ubuntu/Components/plugin/privates/listviewextensions.h' | |||
190 | --- src/Ubuntu/Components/plugin/privates/listviewextensions.h 1970-01-01 00:00:00 +0000 | |||
191 | +++ src/Ubuntu/Components/plugin/privates/listviewextensions.h 2016-03-01 15:09:23 +0000 | |||
192 | @@ -0,0 +1,60 @@ | |||
193 | 1 | /* | ||
194 | 2 | * Copyright 2016 Canonical Ltd. | ||
195 | 3 | * | ||
196 | 4 | * This program is free software; you can redistribute it and/or modify | ||
197 | 5 | * it under the terms of the GNU Lesser General Public License as published by | ||
198 | 6 | * the Free Software Foundation; version 3. | ||
199 | 7 | * | ||
200 | 8 | * This program is distributed in the hope that it will be useful, | ||
201 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
202 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
203 | 11 | * GNU Lesser General Public License for more details. | ||
204 | 12 | * | ||
205 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
206 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
207 | 15 | * | ||
208 | 16 | * Author Zsombor Egri <zsombor.egri@canonical.com> | ||
209 | 17 | */ | ||
210 | 18 | |||
211 | 19 | #ifndef LISTVIEWEXTENSIONS_H | ||
212 | 20 | #define LISTVIEWEXTENSIONS_H | ||
213 | 21 | |||
214 | 22 | #include <QtCore/QObject> | ||
215 | 23 | |||
216 | 24 | class QQuickFlickable; | ||
217 | 25 | class QQuickItem; | ||
218 | 26 | class QFocusEvent; | ||
219 | 27 | class QKeyEvent; | ||
220 | 28 | class ListViewProxy : public QObject | ||
221 | 29 | { | ||
222 | 30 | Q_OBJECT | ||
223 | 31 | public: | ||
224 | 32 | explicit ListViewProxy(QQuickFlickable *listView, QObject *parent = 0); | ||
225 | 33 | virtual ~ListViewProxy(); | ||
226 | 34 | inline QQuickFlickable *view() const | ||
227 | 35 | { | ||
228 | 36 | return listView; | ||
229 | 37 | } | ||
230 | 38 | void overrideItemNavigation(bool override); | ||
231 | 39 | |||
232 | 40 | |||
233 | 41 | // proxied methods | ||
234 | 42 | Qt::Orientation orientation(); | ||
235 | 43 | int count(); | ||
236 | 44 | QQuickItem *currentItem(); | ||
237 | 45 | int currentIndex(); | ||
238 | 46 | void setCurrentIndex(int index); | ||
239 | 47 | QVariant model(); | ||
240 | 48 | |||
241 | 49 | protected: | ||
242 | 50 | bool eventFilter(QObject *, QEvent *) override; | ||
243 | 51 | |||
244 | 52 | bool focusInEvent(QFocusEvent *event); | ||
245 | 53 | bool keyPressEvent(QKeyEvent *event); | ||
246 | 54 | void setKeyNavigationForListView(bool value); | ||
247 | 55 | private: | ||
248 | 56 | QQuickFlickable *listView; | ||
249 | 57 | bool isEventFilter:1; | ||
250 | 58 | }; | ||
251 | 59 | |||
252 | 60 | #endif // LISTVIEWEXTENSIONS_H | ||
253 | 0 | 61 | ||
254 | === modified file 'src/Ubuntu/Components/plugin/quickutils.cpp' | |||
255 | --- src/Ubuntu/Components/plugin/quickutils.cpp 2016-02-01 18:57:26 +0000 | |||
256 | +++ src/Ubuntu/Components/plugin/quickutils.cpp 2016-03-01 15:09:23 +0000 | |||
257 | @@ -214,3 +214,56 @@ | |||
258 | 214 | } | 214 | } |
259 | 215 | return showWarnings == 2; | 215 | return showWarnings == 2; |
260 | 216 | } | 216 | } |
261 | 217 | |||
262 | 218 | // check whether an item is a descendant of parent | ||
263 | 219 | bool QuickUtils::descendantItemOf(QQuickItem *item, const QQuickItem *parent) | ||
264 | 220 | { | ||
265 | 221 | while (item && parent) { | ||
266 | 222 | if (item == parent) { | ||
267 | 223 | return true; | ||
268 | 224 | } | ||
269 | 225 | item = item->parentItem(); | ||
270 | 226 | } | ||
271 | 227 | return false; | ||
272 | 228 | } | ||
273 | 229 | |||
274 | 230 | // returns the first key-focusable child item | ||
275 | 231 | QQuickItem *QuickUtils::firstFocusableChild(QQuickItem *item) | ||
276 | 232 | { | ||
277 | 233 | if (!item) { | ||
278 | 234 | return Q_NULLPTR; | ||
279 | 235 | } | ||
280 | 236 | const QList<QQuickItem*> &list = item->childItems(); | ||
281 | 237 | for (int i = 0; i < list.count(); i++) { | ||
282 | 238 | QQuickItem *child = list.at(i); | ||
283 | 239 | if (child->activeFocusOnTab()) { | ||
284 | 240 | return child; | ||
285 | 241 | } | ||
286 | 242 | QQuickItem *focus = firstFocusableChild(child); | ||
287 | 243 | if (focus) { | ||
288 | 244 | return focus; | ||
289 | 245 | } | ||
290 | 246 | } | ||
291 | 247 | return Q_NULLPTR; | ||
292 | 248 | } | ||
293 | 249 | |||
294 | 250 | // returns the last key-focusable child item | ||
295 | 251 | QQuickItem *QuickUtils::lastFocusableChild(QQuickItem *item) | ||
296 | 252 | { | ||
297 | 253 | if (!item) { | ||
298 | 254 | return Q_NULLPTR; | ||
299 | 255 | } | ||
300 | 256 | const QList<QQuickItem*> &list = item->childItems(); | ||
301 | 257 | int i = list.count() - 1; | ||
302 | 258 | while (i >= 0) { | ||
303 | 259 | QQuickItem *child = list.at(i--); | ||
304 | 260 | if (child->activeFocusOnTab()) { | ||
305 | 261 | return child; | ||
306 | 262 | } | ||
307 | 263 | QQuickItem *focus = lastFocusableChild(child); | ||
308 | 264 | if (focus) { | ||
309 | 265 | return focus; | ||
310 | 266 | } | ||
311 | 267 | } | ||
312 | 268 | return Q_NULLPTR; | ||
313 | 269 | } | ||
314 | 217 | 270 | ||
315 | === modified file 'src/Ubuntu/Components/plugin/quickutils.h' | |||
316 | --- src/Ubuntu/Components/plugin/quickutils.h 2016-02-10 09:54:59 +0000 | |||
317 | +++ src/Ubuntu/Components/plugin/quickutils.h 2016-03-01 15:09:23 +0000 | |||
318 | @@ -55,6 +55,9 @@ | |||
319 | 55 | Q_REVISION(1) Q_INVOKABLE static bool inherits(QObject *object, const QString &fromClass); | 55 | Q_REVISION(1) Q_INVOKABLE static bool inherits(QObject *object, const QString &fromClass); |
320 | 56 | QObject* createQmlObject(const QUrl &url, QQmlEngine *engine); | 56 | QObject* createQmlObject(const QUrl &url, QQmlEngine *engine); |
321 | 57 | static bool showDeprecationWarnings(); | 57 | static bool showDeprecationWarnings(); |
322 | 58 | static bool descendantItemOf(QQuickItem *item, const QQuickItem *parent); | ||
323 | 59 | Q_INVOKABLE static QQuickItem *firstFocusableChild(QQuickItem *item); | ||
324 | 60 | Q_INVOKABLE static QQuickItem *lastFocusableChild(QQuickItem *item); | ||
325 | 58 | 61 | ||
326 | 59 | bool mouseAttached() | 62 | bool mouseAttached() |
327 | 60 | { | 63 | { |
328 | 61 | 64 | ||
329 | === modified file 'src/Ubuntu/Components/plugin/uclistitem.cpp' | |||
330 | --- src/Ubuntu/Components/plugin/uclistitem.cpp 2016-02-08 11:00:27 +0000 | |||
331 | +++ src/Ubuntu/Components/plugin/uclistitem.cpp 2016-03-01 15:09:23 +0000 | |||
332 | @@ -28,6 +28,7 @@ | |||
333 | 28 | #include "quickutils.h" | 28 | #include "quickutils.h" |
334 | 29 | #include "ucaction.h" | 29 | #include "ucaction.h" |
335 | 30 | #include "ucnamespace.h" | 30 | #include "ucnamespace.h" |
336 | 31 | #include "privates/listviewextensions.h" | ||
337 | 31 | #include <QtQml/QQmlInfo> | 32 | #include <QtQml/QQmlInfo> |
338 | 32 | #include <QtQuick/private/qquickitem_p.h> | 33 | #include <QtQuick/private/qquickitem_p.h> |
339 | 33 | #include <QtQuick/private/qquickflickable_p.h> | 34 | #include <QtQuick/private/qquickflickable_p.h> |
340 | @@ -210,7 +211,10 @@ | |||
341 | 210 | , suppressClick(false) | 211 | , suppressClick(false) |
342 | 211 | , ready(false) | 212 | , ready(false) |
343 | 212 | , customColor(false) | 213 | , customColor(false) |
344 | 214 | , listViewKeyNavigation(false) | ||
345 | 213 | { | 215 | { |
346 | 216 | // the ListItem is not a focus scope | ||
347 | 217 | isFocusScope = false; | ||
348 | 214 | } | 218 | } |
349 | 215 | UCListItemPrivate::~UCListItemPrivate() | 219 | UCListItemPrivate::~UCListItemPrivate() |
350 | 216 | { | 220 | { |
351 | @@ -220,10 +224,10 @@ | |||
352 | 220 | { | 224 | { |
353 | 221 | Q_Q(UCListItem); | 225 | Q_Q(UCListItem); |
354 | 222 | contentItem->setObjectName("ListItemHolder"); | 226 | contentItem->setObjectName("ListItemHolder"); |
355 | 227 | divider->init(q); | ||
356 | 223 | QQml_setParent_noEvent(contentItem, q); | 228 | QQml_setParent_noEvent(contentItem, q); |
357 | 224 | contentItem->setParentItem(q); | 229 | contentItem->setParentItem(q); |
358 | 225 | contentItem->setClip(true); | 230 | contentItem->setClip(true); |
359 | 226 | divider->init(q); | ||
360 | 227 | // content will be redirected to the contentItem, therefore we must report | 231 | // content will be redirected to the contentItem, therefore we must report |
361 | 228 | // children changes as it would come from the main component | 232 | // children changes as it would come from the main component |
362 | 229 | QObject::connect(contentItem, &QQuickItem::childrenChanged, | 233 | QObject::connect(contentItem, &QQuickItem::childrenChanged, |
363 | @@ -978,6 +982,23 @@ | |||
364 | 978 | { | 982 | { |
365 | 979 | } | 983 | } |
366 | 980 | 984 | ||
367 | 985 | // override keyNavigationFocus getter | ||
368 | 986 | bool UCListItem::keyNavigationFocus() const | ||
369 | 987 | { | ||
370 | 988 | Q_D(const UCListItem); | ||
371 | 989 | return d->keyNavigationFocus ||d->listViewKeyNavigation; | ||
372 | 990 | } | ||
373 | 991 | |||
374 | 992 | void UCListItemPrivate::setListViewKeyNavigation(bool value) | ||
375 | 993 | { | ||
376 | 994 | Q_Q(UCListItem); | ||
377 | 995 | bool prevKeyNav = q->keyNavigationFocus(); | ||
378 | 996 | listViewKeyNavigation = value; | ||
379 | 997 | if (prevKeyNav != q->keyNavigationFocus()) { | ||
380 | 998 | Q_EMIT q->keyNavigationFocusChanged(); | ||
381 | 999 | } | ||
382 | 1000 | } | ||
383 | 1001 | |||
384 | 981 | QObject *UCListItem::attachedViewItems(QObject *object, bool create) | 1002 | QObject *UCListItem::attachedViewItems(QObject *object, bool create) |
385 | 982 | { | 1003 | { |
386 | 983 | return qmlAttachedPropertiesObject<UCViewItemsAttached>(object, create); | 1004 | return qmlAttachedPropertiesObject<UCViewItemsAttached>(object, create); |
387 | @@ -1074,6 +1095,9 @@ | |||
388 | 1074 | d->selection->attachToViewItems(d->parentAttached.data()); | 1095 | d->selection->attachToViewItems(d->parentAttached.data()); |
389 | 1075 | connect(d->parentAttached.data(), SIGNAL(expandedIndicesChanged(QList<int>)), | 1096 | connect(d->parentAttached.data(), SIGNAL(expandedIndicesChanged(QList<int>)), |
390 | 1076 | this, SLOT(_q_updateExpansion(QList<int>)), Qt::DirectConnection); | 1097 | this, SLOT(_q_updateExpansion(QList<int>)), Qt::DirectConnection); |
391 | 1098 | // if the ViewItems is attached to a ListView, disable tab stops on the ListItem | ||
392 | 1099 | setActiveFocusOnTab(!d->parentAttached->isAttachedToListView()); | ||
393 | 1100 | d->isTabFence = d->parentAttached->isAttachedToListView(); | ||
394 | 1077 | } | 1101 | } |
395 | 1078 | 1102 | ||
396 | 1079 | if (parentAttachee) { | 1103 | if (parentAttachee) { |
397 | @@ -1101,17 +1125,36 @@ | |||
398 | 1101 | if (!rectNode) { | 1125 | if (!rectNode) { |
399 | 1102 | rectNode = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode(); | 1126 | rectNode = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode(); |
400 | 1103 | } | 1127 | } |
401 | 1128 | bool updateNode = false; | ||
402 | 1129 | |||
403 | 1130 | // focus frame | ||
404 | 1131 | bool paintFocus = hasActiveFocus() && keyNavigationFocus(); | ||
405 | 1132 | rectNode->setPenWidth(paintFocus ? UCUnits::instance()->dp(1) : 0); | ||
406 | 1133 | if (paintFocus) { | ||
407 | 1134 | QColor penColor; | ||
408 | 1135 | if (getTheme()) { | ||
409 | 1136 | penColor = getTheme()->getPaletteColor(isEnabled() ? "normal" : "disabled", "focus"); | ||
410 | 1137 | } | ||
411 | 1138 | rectNode->setPenColor(penColor); | ||
412 | 1139 | rectNode->setColor(Qt::transparent); | ||
413 | 1140 | updateNode = true; | ||
414 | 1141 | } | ||
415 | 1142 | QRectF rect(boundingRect()); | ||
416 | 1143 | rect -= QMarginsF(0, 0, UCUnits::instance()->dp(1), 0); | ||
417 | 1144 | d->divider->setOpacity(paintFocus ? 0.0 : 1.0); | ||
418 | 1145 | rectNode->setRect(rect); | ||
419 | 1146 | |||
420 | 1147 | // highlight color | ||
421 | 1104 | if (color.alphaF() >= (1.0f / 255.0f)) { | 1148 | if (color.alphaF() >= (1.0f / 255.0f)) { |
422 | 1105 | rectNode->setColor(color); | 1149 | rectNode->setColor(color); |
423 | 1106 | // cover only the area of the contentItem, removing divider's thickness | ||
424 | 1107 | QRectF rect(boundingRect()); | ||
425 | 1108 | if (d->divider->isVisible()) { | ||
426 | 1109 | rect -= QMarginsF(0, 0, 0, d->divider->height()); | ||
427 | 1110 | } | ||
428 | 1111 | rectNode->setRect(rect); | ||
429 | 1112 | rectNode->setGradientStops(QGradientStops()); | 1150 | rectNode->setGradientStops(QGradientStops()); |
430 | 1113 | rectNode->setAntialiasing(true); | 1151 | rectNode->setAntialiasing(true); |
431 | 1114 | rectNode->setAntialiasing(false); | 1152 | rectNode->setAntialiasing(false); |
432 | 1153 | updateNode = true; | ||
433 | 1154 | } | ||
434 | 1155 | |||
435 | 1156 | // update | ||
436 | 1157 | if (updateNode) { | ||
437 | 1115 | rectNode->update(); | 1158 | rectNode->update(); |
438 | 1116 | } else { | 1159 | } else { |
439 | 1117 | // delete node, this will delete the divider node as well | 1160 | // delete node, this will delete the divider node as well |
440 | @@ -1426,6 +1469,66 @@ | |||
441 | 1426 | } | 1469 | } |
442 | 1427 | } | 1470 | } |
443 | 1428 | 1471 | ||
444 | 1472 | void UCListItem::focusInEvent(QFocusEvent *event) | ||
445 | 1473 | { | ||
446 | 1474 | Q_D(UCListItem); | ||
447 | 1475 | UCStyledItemBase::focusInEvent(event); | ||
448 | 1476 | if (event->reason() == Qt::MouseFocusReason) { | ||
449 | 1477 | d_func()->setListViewKeyNavigation(false); | ||
450 | 1478 | } | ||
451 | 1479 | update(); | ||
452 | 1480 | } | ||
453 | 1481 | |||
454 | 1482 | void UCListItem::focusOutEvent(QFocusEvent *event) | ||
455 | 1483 | { | ||
456 | 1484 | UCStyledItemBase::focusOutEvent(event); | ||
457 | 1485 | d_func()->setListViewKeyNavigation(false); | ||
458 | 1486 | update(); | ||
459 | 1487 | } | ||
460 | 1488 | |||
461 | 1489 | // handle horizontal keys to navigate between focusable slots | ||
462 | 1490 | void UCListItem::keyPressEvent(QKeyEvent *event) | ||
463 | 1491 | { | ||
464 | 1492 | UCStyledItemBase::keyPressEvent(event); | ||
465 | 1493 | Q_D(UCListItem); | ||
466 | 1494 | int key = event->key(); | ||
467 | 1495 | if (key != Qt::Key_Left && key != Qt::Key_Right) { | ||
468 | 1496 | return; | ||
469 | 1497 | } | ||
470 | 1498 | |||
471 | 1499 | bool forwards = (d->effectiveLayoutMirror ? key == Qt::Key_Left : key == Qt::Key_Right); | ||
472 | 1500 | // we must check whether the ListItem has any key navigation focusable child | ||
473 | 1501 | // this is needed due to the Qt bug https://bugreports.qt.io/browse/QTBUG-50516 | ||
474 | 1502 | if (!QuickUtils::firstFocusableChild(this)) { | ||
475 | 1503 | return; | ||
476 | 1504 | } | ||
477 | 1505 | |||
478 | 1506 | // get the next focusable relative to the active focus item | ||
479 | 1507 | QQuickItem *activeFocus = isFocusScope() ? scopedFocusItem() : window()->activeFocusItem(); | ||
480 | 1508 | if (!activeFocus) { | ||
481 | 1509 | return; | ||
482 | 1510 | } | ||
483 | 1511 | |||
484 | 1512 | Qt::FocusReason reason = forwards ? Qt::TabFocusReason : Qt::BacktabFocusReason; | ||
485 | 1513 | if ((activeFocus == QuickUtils::firstFocusableChild(this) && !forwards) || | ||
486 | 1514 | (activeFocus == QuickUtils::lastFocusableChild(this) && forwards)) { | ||
487 | 1515 | // first or the last focus child is reached, so we wrap around | ||
488 | 1516 | // but for that we must set the activeFocus to false in order to | ||
489 | 1517 | // be able to focus the ListItem, especially when the ListItem is a Tab fence | ||
490 | 1518 | activeFocus->setFocus(false); | ||
491 | 1519 | forceActiveFocus(reason); | ||
492 | 1520 | } else if (activeFocus == this) { | ||
493 | 1521 | // get the first or last focusable item, depending on the direction | ||
494 | 1522 | QQuickItem *nextFocus = forwards | ||
495 | 1523 | ? QuickUtils::firstFocusableChild(this) | ||
496 | 1524 | : QuickUtils::lastFocusableChild(this); | ||
497 | 1525 | nextFocus->forceActiveFocus(reason); | ||
498 | 1526 | } else { | ||
499 | 1527 | // in case the ListItem is in ListView, we can freely proceed with the focusing | ||
500 | 1528 | QQuickItemPrivate::focusNextPrev(activeFocus, forwards); | ||
501 | 1529 | } | ||
502 | 1530 | } | ||
503 | 1531 | |||
504 | 1429 | /*! | 1532 | /*! |
505 | 1430 | * \qmlproperty ListItemActions ListItem::leadingActions | 1533 | * \qmlproperty ListItemActions ListItem::leadingActions |
506 | 1431 | * | 1534 | * |
507 | 1432 | 1535 | ||
508 | === modified file 'src/Ubuntu/Components/plugin/uclistitem.h' | |||
509 | --- src/Ubuntu/Components/plugin/uclistitem.h 2015-11-16 16:24:42 +0000 | |||
510 | +++ src/Ubuntu/Components/plugin/uclistitem.h 2016-03-01 15:09:23 +0000 | |||
511 | @@ -53,6 +53,9 @@ | |||
512 | 53 | explicit UCListItem(QQuickItem *parent = 0); | 53 | explicit UCListItem(QQuickItem *parent = 0); |
513 | 54 | ~UCListItem(); | 54 | ~UCListItem(); |
514 | 55 | 55 | ||
515 | 56 | // overrides | ||
516 | 57 | bool keyNavigationFocus() const override; | ||
517 | 58 | |||
518 | 56 | QQuickItem *contentItem() const; | 59 | QQuickItem *contentItem() const; |
519 | 57 | UCListItemDivider *divider() const; | 60 | UCListItemDivider *divider() const; |
520 | 58 | UCListItemActions *leadingActions() const; | 61 | UCListItemActions *leadingActions() const; |
521 | @@ -83,6 +86,9 @@ | |||
522 | 83 | bool childMouseEventFilter(QQuickItem *child, QEvent *event); | 86 | bool childMouseEventFilter(QQuickItem *child, QEvent *event); |
523 | 84 | bool eventFilter(QObject *, QEvent *); | 87 | bool eventFilter(QObject *, QEvent *); |
524 | 85 | void timerEvent(QTimerEvent *event); | 88 | void timerEvent(QTimerEvent *event); |
525 | 89 | void focusInEvent(QFocusEvent *event) override; | ||
526 | 90 | void focusOutEvent(QFocusEvent *event) override; | ||
527 | 91 | void keyPressEvent(QKeyEvent *event) override; | ||
528 | 86 | 92 | ||
529 | 87 | Q_SIGNALS: | 93 | Q_SIGNALS: |
530 | 88 | void leadingActionsChanged(); | 94 | void leadingActionsChanged(); |
531 | @@ -177,6 +183,7 @@ | |||
532 | 177 | static UCViewItemsAttached *qmlAttachedProperties(QObject *owner); | 183 | static UCViewItemsAttached *qmlAttachedProperties(QObject *owner); |
533 | 178 | 184 | ||
534 | 179 | bool listenToRebind(UCListItem *item, bool listen); | 185 | bool listenToRebind(UCListItem *item, bool listen); |
535 | 186 | bool isAttachedToListView(); | ||
536 | 180 | bool isMoving(); | 187 | bool isMoving(); |
537 | 181 | bool isBoundTo(UCListItem *item); | 188 | bool isBoundTo(UCListItem *item); |
538 | 182 | 189 | ||
539 | 183 | 190 | ||
540 | === modified file 'src/Ubuntu/Components/plugin/uclistitem_p.h' | |||
541 | --- src/Ubuntu/Components/plugin/uclistitem_p.h 2015-12-14 09:16:41 +0000 | |||
542 | +++ src/Ubuntu/Components/plugin/uclistitem_p.h 2016-03-01 15:09:23 +0000 | |||
543 | @@ -102,6 +102,7 @@ | |||
544 | 102 | bool suppressClick:1; | 102 | bool suppressClick:1; |
545 | 103 | bool ready:1; | 103 | bool ready:1; |
546 | 104 | bool customColor:1; | 104 | bool customColor:1; |
547 | 105 | bool listViewKeyNavigation:1; | ||
548 | 105 | 106 | ||
549 | 106 | // getters/setters | 107 | // getters/setters |
550 | 107 | QQmlListProperty<QObject> data(); | 108 | QQmlListProperty<QObject> data(); |
551 | @@ -119,6 +120,7 @@ | |||
552 | 119 | void setSelectMode(bool selectable); | 120 | void setSelectMode(bool selectable); |
553 | 120 | UCAction *action() const; | 121 | UCAction *action() const; |
554 | 121 | void setAction(UCAction *action); | 122 | void setAction(UCAction *action); |
555 | 123 | void setListViewKeyNavigation(bool value); | ||
556 | 122 | 124 | ||
557 | 123 | virtual void postThemeChanged(); | 125 | virtual void postThemeChanged(); |
558 | 124 | inline UCListItemStyle *listItemStyle() const; | 126 | inline UCListItemStyle *listItemStyle() const; |
559 | @@ -130,12 +132,14 @@ | |||
560 | 130 | 132 | ||
561 | 131 | class PropertyChange; | 133 | class PropertyChange; |
562 | 132 | class ListItemDragArea; | 134 | class ListItemDragArea; |
563 | 135 | class ListViewProxy; | ||
564 | 133 | class UCViewItemsAttachedPrivate : public QObjectPrivate | 136 | class UCViewItemsAttachedPrivate : public QObjectPrivate |
565 | 134 | { | 137 | { |
566 | 135 | Q_DECLARE_PUBLIC(UCViewItemsAttached) | 138 | Q_DECLARE_PUBLIC(UCViewItemsAttached) |
567 | 136 | public: | 139 | public: |
568 | 137 | UCViewItemsAttachedPrivate(); | 140 | UCViewItemsAttachedPrivate(); |
569 | 138 | ~UCViewItemsAttachedPrivate(); | 141 | ~UCViewItemsAttachedPrivate(); |
570 | 142 | void init(); | ||
571 | 139 | 143 | ||
572 | 140 | static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item) | 144 | static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item) |
573 | 141 | { | 145 | { |
574 | @@ -162,7 +166,7 @@ | |||
575 | 162 | QMap<int, QPointer<UCListItem> > expansionList; | 166 | QMap<int, QPointer<UCListItem> > expansionList; |
576 | 163 | QList< QPointer<QQuickFlickable> > flickables; | 167 | QList< QPointer<QQuickFlickable> > flickables; |
577 | 164 | QPointer<UCListItem> boundItem; | 168 | QPointer<UCListItem> boundItem; |
579 | 165 | QQuickFlickable *listView; | 169 | ListViewProxy *listView; |
580 | 166 | ListItemDragArea *dragArea; | 170 | ListItemDragArea *dragArea; |
581 | 167 | UCViewItemsAttached::ExpansionFlags expansionFlags; | 171 | UCViewItemsAttached::ExpansionFlags expansionFlags; |
582 | 168 | bool selectable:1; | 172 | bool selectable:1; |
583 | 169 | 173 | ||
584 | === modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.cpp' | |||
585 | --- src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2016-02-25 16:41:51 +0000 | |||
586 | +++ src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2016-03-01 15:09:23 +0000 | |||
587 | @@ -33,6 +33,7 @@ | |||
588 | 33 | , keyNavigationFocus(false) | 33 | , keyNavigationFocus(false) |
589 | 34 | , activeFocusOnPress(false) | 34 | , activeFocusOnPress(false) |
590 | 35 | , wasStyleLoaded(false) | 35 | , wasStyleLoaded(false) |
591 | 36 | , isFocusScope(true) | ||
592 | 36 | { | 37 | { |
593 | 37 | } | 38 | } |
594 | 38 | 39 | ||
595 | @@ -58,7 +59,6 @@ | |||
596 | 58 | void UCStyledItemBasePrivate::init() | 59 | void UCStyledItemBasePrivate::init() |
597 | 59 | { | 60 | { |
598 | 60 | Q_Q(UCStyledItemBase); | 61 | Q_Q(UCStyledItemBase); |
599 | 61 | q->setFlag(QQuickItem::ItemIsFocusScope); | ||
600 | 62 | QObject::connect(q, &QQuickItem::activeFocusOnTabChanged, q, &UCStyledItemBase::activeFocusOnTabChanged2); | 62 | QObject::connect(q, &QQuickItem::activeFocusOnTabChanged, q, &UCStyledItemBase::activeFocusOnTabChanged2); |
601 | 63 | } | 63 | } |
602 | 64 | 64 | ||
603 | @@ -501,6 +501,21 @@ | |||
604 | 501 | loadStyleItem(false); | 501 | loadStyleItem(false); |
605 | 502 | } | 502 | } |
606 | 503 | 503 | ||
607 | 504 | void UCStyledItemBase::classBegin() | ||
608 | 505 | { | ||
609 | 506 | /* Some items require not to be focus scopes (like ListItem), however for | ||
610 | 507 | * backwards compatibility we must keep setting the generic styled items | ||
611 | 508 | * as focus scopes. The flag once set cannot be cleared, therefore we must | ||
612 | 509 | * use an additional boolean member isFocusScope to drive this request. | ||
613 | 510 | * The member defaults to true. Additionally, the focus scope flag must | ||
614 | 511 | * be set before the parentItem is set and child items are added. | ||
615 | 512 | */ | ||
616 | 513 | if (d_func()->isFocusScope) { | ||
617 | 514 | setFlag(QQuickItem::ItemIsFocusScope); | ||
618 | 515 | } | ||
619 | 516 | QQuickItem::classBegin(); | ||
620 | 517 | } | ||
621 | 518 | |||
622 | 504 | void UCStyledItemBase::componentComplete() | 519 | void UCStyledItemBase::componentComplete() |
623 | 505 | { | 520 | { |
624 | 506 | QQuickItem::componentComplete(); | 521 | QQuickItem::componentComplete(); |
625 | 507 | 522 | ||
626 | === modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.h' | |||
627 | --- src/Ubuntu/Components/plugin/ucstyleditembase.h 2015-12-16 08:05:44 +0000 | |||
628 | +++ src/Ubuntu/Components/plugin/ucstyleditembase.h 2016-03-01 15:09:23 +0000 | |||
629 | @@ -48,7 +48,7 @@ | |||
630 | 48 | public: | 48 | public: |
631 | 49 | explicit UCStyledItemBase(QQuickItem *parent = 0); | 49 | explicit UCStyledItemBase(QQuickItem *parent = 0); |
632 | 50 | 50 | ||
634 | 51 | bool keyNavigationFocus() const; | 51 | virtual bool keyNavigationFocus() const; |
635 | 52 | bool activefocusOnPress() const; | 52 | bool activefocusOnPress() const; |
636 | 53 | void setActiveFocusOnPress(bool value); | 53 | void setActiveFocusOnPress(bool value); |
637 | 54 | bool activeFocusOnTab2() const; | 54 | bool activeFocusOnTab2() const; |
638 | @@ -73,6 +73,7 @@ | |||
639 | 73 | virtual void preThemeChanged(); | 73 | virtual void preThemeChanged(); |
640 | 74 | virtual void postThemeChanged(); | 74 | virtual void postThemeChanged(); |
641 | 75 | 75 | ||
642 | 76 | void classBegin(); | ||
643 | 76 | void componentComplete(); | 77 | void componentComplete(); |
644 | 77 | void itemChange(ItemChange change, const ItemChangeData &data); | 78 | void itemChange(ItemChange change, const ItemChangeData &data); |
645 | 78 | void focusInEvent(QFocusEvent *key); | 79 | void focusInEvent(QFocusEvent *key); |
646 | 79 | 80 | ||
647 | === modified file 'src/Ubuntu/Components/plugin/ucstyleditembase_p.h' | |||
648 | --- src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2015-12-15 19:17:11 +0000 | |||
649 | +++ src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2016-03-01 15:09:23 +0000 | |||
650 | @@ -72,6 +72,7 @@ | |||
651 | 72 | bool keyNavigationFocus:1; | 72 | bool keyNavigationFocus:1; |
652 | 73 | bool activeFocusOnPress:1; | 73 | bool activeFocusOnPress:1; |
653 | 74 | bool wasStyleLoaded:1; | 74 | bool wasStyleLoaded:1; |
654 | 75 | bool isFocusScope:1; | ||
655 | 75 | 76 | ||
656 | 76 | protected: | 77 | protected: |
657 | 77 | 78 | ||
658 | 78 | 79 | ||
659 | === modified file 'src/Ubuntu/Components/plugin/ucviewitemsattached.cpp' | |||
660 | --- src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2016-01-30 12:03:31 +0000 | |||
661 | +++ src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2016-03-01 15:09:23 +0000 | |||
662 | @@ -1,5 +1,5 @@ | |||
663 | 1 | /* | 1 | /* |
665 | 2 | * Copyright 2014-2015 Canonical Ltd. | 2 | * Copyright 2014-2016 Canonical Ltd. |
666 | 3 | * | 3 | * |
667 | 4 | * This program is free software; you can redistribute it and/or modify | 4 | * This program is free software; you can redistribute it and/or modify |
668 | 5 | * it under the terms of the GNU Lesser General Public License as published by | 5 | * it under the terms of the GNU Lesser General Public License as published by |
669 | @@ -23,6 +23,7 @@ | |||
670 | 23 | #include "i18n.h" | 23 | #include "i18n.h" |
671 | 24 | #include "uclistitemstyle.h" | 24 | #include "uclistitemstyle.h" |
672 | 25 | #include "privates/listitemdragarea.h" | 25 | #include "privates/listitemdragarea.h" |
673 | 26 | #include "privates/listviewextensions.h" | ||
674 | 26 | #include <QtQuick/private/qquickflickable_p.h> | 27 | #include <QtQuick/private/qquickflickable_p.h> |
675 | 27 | #include <QtQml/private/qqmlcomponentattached_p.h> | 28 | #include <QtQml/private/qqmlcomponentattached_p.h> |
676 | 28 | #include <QtQml/QQmlInfo> | 29 | #include <QtQml/QQmlInfo> |
677 | @@ -116,6 +117,22 @@ | |||
678 | 116 | clearFlickablesList(); | 117 | clearFlickablesList(); |
679 | 117 | } | 118 | } |
680 | 118 | 119 | ||
681 | 120 | void UCViewItemsAttachedPrivate::init() | ||
682 | 121 | { | ||
683 | 122 | Q_Q(UCViewItemsAttached); | ||
684 | 123 | if (parent->inherits("QQuickListView")) { | ||
685 | 124 | listView = new ListViewProxy(static_cast<QQuickFlickable*>(parent), q); | ||
686 | 125 | |||
687 | 126 | // ListView focus handling | ||
688 | 127 | listView->view()->setActiveFocusOnTab(true); | ||
689 | 128 | // filter ListView events to override up/down focus handling | ||
690 | 129 | listView->overrideItemNavigation(true); | ||
691 | 130 | } | ||
692 | 131 | // listen readyness | ||
693 | 132 | QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(parent); | ||
694 | 133 | QObject::connect(attached, &QQmlComponentAttached::completed, q, &UCViewItemsAttached::completed); | ||
695 | 134 | } | ||
696 | 135 | |||
697 | 119 | // disconnect all flickables | 136 | // disconnect all flickables |
698 | 120 | void UCViewItemsAttachedPrivate::clearFlickablesList() | 137 | void UCViewItemsAttachedPrivate::clearFlickablesList() |
699 | 121 | { | 138 | { |
700 | @@ -167,12 +184,7 @@ | |||
701 | 167 | UCViewItemsAttached::UCViewItemsAttached(QObject *owner) | 184 | UCViewItemsAttached::UCViewItemsAttached(QObject *owner) |
702 | 168 | : QObject(*(new UCViewItemsAttachedPrivate()), owner) | 185 | : QObject(*(new UCViewItemsAttachedPrivate()), owner) |
703 | 169 | { | 186 | { |
710 | 170 | if (owner->inherits("QQuickListView")) { | 187 | d_func()->init(); |
705 | 171 | d_func()->listView = static_cast<QQuickFlickable*>(owner); | ||
706 | 172 | } | ||
707 | 173 | // listen readyness | ||
708 | 174 | QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(owner); | ||
709 | 175 | connect(attached, &QQmlComponentAttached::completed, this, &UCViewItemsAttached::completed); | ||
711 | 176 | } | 188 | } |
712 | 177 | 189 | ||
713 | 178 | UCViewItemsAttached::~UCViewItemsAttached() | 190 | UCViewItemsAttached::~UCViewItemsAttached() |
714 | @@ -204,6 +216,12 @@ | |||
715 | 204 | return result; | 216 | return result; |
716 | 205 | } | 217 | } |
717 | 206 | 218 | ||
718 | 219 | // reports whether the ViewItems is attached to ListView | ||
719 | 220 | bool UCViewItemsAttached::isAttachedToListView() | ||
720 | 221 | { | ||
721 | 222 | return (d_func()->listView != Q_NULLPTR); | ||
722 | 223 | } | ||
723 | 224 | |||
724 | 207 | // reports true if any of the ascendant flickables is moving | 225 | // reports true if any of the ascendant flickables is moving |
725 | 208 | bool UCViewItemsAttached::isMoving() | 226 | bool UCViewItemsAttached::isMoving() |
726 | 209 | { | 227 | { |
727 | @@ -462,7 +480,7 @@ | |||
728 | 462 | qmlInfo(parent()) << QStringLiteral("Dragging mode requires ListView"); | 480 | qmlInfo(parent()) << QStringLiteral("Dragging mode requires ListView"); |
729 | 463 | return; | 481 | return; |
730 | 464 | } | 482 | } |
732 | 465 | QVariant model = d->listView->property("model"); | 483 | QVariant model = d->listView->model(); |
733 | 466 | // warn if the model is anything else but Instance model (ObjectModel or DelegateModel) | 484 | // warn if the model is anything else but Instance model (ObjectModel or DelegateModel) |
734 | 467 | // or a derivate of QAbstractItemModel | 485 | // or a derivate of QAbstractItemModel |
735 | 468 | QString warning = QStringLiteral("Dragging is only supported when using a QAbstractItemModel, ListModel or list."); | 486 | QString warning = QStringLiteral("Dragging is only supported when using a QAbstractItemModel, ListModel or list."); |
736 | @@ -492,7 +510,7 @@ | |||
737 | 492 | dragArea->reset(); | 510 | dragArea->reset(); |
738 | 493 | return; | 511 | return; |
739 | 494 | } | 512 | } |
741 | 495 | dragArea = new ListItemDragArea(listView); | 513 | dragArea = new ListItemDragArea(listView->view()); |
742 | 496 | dragArea->init(q_func()); | 514 | dragArea->init(q_func()); |
743 | 497 | } | 515 | } |
744 | 498 | 516 | ||
745 | @@ -515,7 +533,7 @@ | |||
746 | 515 | // updates the selected indices list in ViewAttached which is changed due to dragging | 533 | // updates the selected indices list in ViewAttached which is changed due to dragging |
747 | 516 | void UCViewItemsAttachedPrivate::updateSelectedIndices(int fromIndex, int toIndex) | 534 | void UCViewItemsAttachedPrivate::updateSelectedIndices(int fromIndex, int toIndex) |
748 | 517 | { | 535 | { |
750 | 518 | if (selectedList.count() == listView->property("count").toInt()) { | 536 | if (selectedList.count() == listView->count()) { |
751 | 519 | // all indices selected, no need to reorder | 537 | // all indices selected, no need to reorder |
752 | 520 | return; | 538 | return; |
753 | 521 | } | 539 | } |
754 | 522 | 540 | ||
755 | === modified file 'tests/resources/listitems/ListItemTest.qml' | |||
756 | --- tests/resources/listitems/ListItemTest.qml 2015-09-18 08:36:23 +0000 | |||
757 | +++ tests/resources/listitems/ListItemTest.qml 2016-03-01 15:09:23 +0000 | |||
758 | @@ -15,8 +15,8 @@ | |||
759 | 15 | */ | 15 | */ |
760 | 16 | 16 | ||
761 | 17 | import QtQuick 2.4 | 17 | import QtQuick 2.4 |
764 | 18 | import Ubuntu.Components 1.2 | 18 | import Ubuntu.Components 1.3 |
765 | 19 | import Ubuntu.Components.Styles 1.2 | 19 | import Ubuntu.Components.Styles 1.3 |
766 | 20 | import QtQuick.Layouts 1.1 | 20 | import QtQuick.Layouts 1.1 |
767 | 21 | 21 | ||
768 | 22 | MainView { | 22 | MainView { |
769 | @@ -207,6 +207,7 @@ | |||
770 | 207 | title.text: "This is one Label split in two lines.\n" + | 207 | title.text: "This is one Label split in two lines.\n" + |
771 | 208 | "The second line - item #" + modelData | 208 | "The second line - item #" + modelData |
772 | 209 | } | 209 | } |
773 | 210 | CheckBox {} | ||
774 | 210 | Button { | 211 | Button { |
775 | 211 | text: "Pressme..." | 212 | text: "Pressme..." |
776 | 212 | } | 213 | } |
777 | @@ -245,7 +246,7 @@ | |||
778 | 245 | model: 10 | 246 | model: 10 |
779 | 246 | ListItem { | 247 | ListItem { |
780 | 247 | objectName: "InFlickable"+index | 248 | objectName: "InFlickable"+index |
782 | 248 | color: UbuntuColors.red | 249 | color: UbuntuColors.silk |
783 | 249 | highlightColor: "lime" | 250 | highlightColor: "lime" |
784 | 250 | divider.colorFrom: UbuntuColors.green | 251 | divider.colorFrom: UbuntuColors.green |
785 | 251 | 252 | ||
786 | @@ -263,9 +264,13 @@ | |||
787 | 263 | Label { | 264 | Label { |
788 | 264 | text: modelData + " Flickable item" | 265 | text: modelData + " Flickable item" |
789 | 265 | } | 266 | } |
792 | 266 | Button { | 267 | Row { |
791 | 267 | text: "Pressme..." | ||
793 | 268 | anchors.centerIn: parent | 268 | anchors.centerIn: parent |
794 | 269 | spacing: units.dp(4) | ||
795 | 270 | Button { | ||
796 | 271 | text: "Pressme..." | ||
797 | 272 | } | ||
798 | 273 | Switch {} | ||
799 | 269 | } | 274 | } |
800 | 270 | 275 | ||
801 | 271 | onClicked: divider.visible = !divider.visible | 276 | onClicked: divider.visible = !divider.visible |
802 | 272 | 277 | ||
803 | === added file 'tests/unit_x11/tst_components/tst_listitem_focus.qml' | |||
804 | --- tests/unit_x11/tst_components/tst_listitem_focus.qml 1970-01-01 00:00:00 +0000 | |||
805 | +++ tests/unit_x11/tst_components/tst_listitem_focus.qml 2016-03-01 15:09:23 +0000 | |||
806 | @@ -0,0 +1,417 @@ | |||
807 | 1 | /* | ||
808 | 2 | * Copyright 2016 Canonical Ltd. | ||
809 | 3 | * | ||
810 | 4 | * This program is free software; you can redistribute it and/or modify | ||
811 | 5 | * it under the terms of the GNU Lesser General Public License as published by | ||
812 | 6 | * the Free Software Foundation; version 3. | ||
813 | 7 | * | ||
814 | 8 | * This program is distributed in the hope that it will be useful, | ||
815 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
816 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
817 | 11 | * GNU Lesser General Public License for more details. | ||
818 | 12 | * | ||
819 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
820 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
821 | 15 | */ | ||
822 | 16 | |||
823 | 17 | import QtQuick 2.4 | ||
824 | 18 | import QtTest 1.0 | ||
825 | 19 | import Ubuntu.Test 1.0 | ||
826 | 20 | import Ubuntu.Components 1.3 | ||
827 | 21 | import QtQuick.Window 2.2 | ||
828 | 22 | |||
829 | 23 | Item { | ||
830 | 24 | id: main | ||
831 | 25 | width: units.gu(40) | ||
832 | 26 | height: units.gu(71) | ||
833 | 27 | |||
834 | 28 | property Item activeFocusItem: Window.activeFocusItem | ||
835 | 29 | |||
836 | 30 | Rectangle { | ||
837 | 31 | id: topItem | ||
838 | 32 | objectName: "topItem" | ||
839 | 33 | activeFocusOnTab: true | ||
840 | 34 | width: parent.width | ||
841 | 35 | height: units.gu(2) | ||
842 | 36 | } | ||
843 | 37 | |||
844 | 38 | Loader { | ||
845 | 39 | id: testLoader | ||
846 | 40 | anchors { | ||
847 | 41 | fill: parent | ||
848 | 42 | topMargin: topItem.height | ||
849 | 43 | } | ||
850 | 44 | } | ||
851 | 45 | |||
852 | 46 | Component { | ||
853 | 47 | id: simpleListItem | ||
854 | 48 | ListItem { | ||
855 | 49 | objectName: "simple" + index | ||
856 | 50 | property int itemIndex: index | ||
857 | 51 | } | ||
858 | 52 | } | ||
859 | 53 | |||
860 | 54 | Component { | ||
861 | 55 | id: listItemWithContent | ||
862 | 56 | ListItem { | ||
863 | 57 | id: listItem | ||
864 | 58 | objectName: "withContent" + index | ||
865 | 59 | property int itemIndex: index | ||
866 | 60 | Row { | ||
867 | 61 | spacing: units.gu(1) | ||
868 | 62 | CheckBox { objectName: "checkbox" + listItem.itemIndex } | ||
869 | 63 | Switch { objectName: "switch" + listItem.itemIndex } | ||
870 | 64 | Button { objectName: "button" + listItem.itemIndex; text: "test" } | ||
871 | 65 | } | ||
872 | 66 | leadingActions: ListItemActions { | ||
873 | 67 | actions: Action { | ||
874 | 68 | iconName: "delete" | ||
875 | 69 | } | ||
876 | 70 | } | ||
877 | 71 | trailingActions: ListItemActions { | ||
878 | 72 | actions: Action { | ||
879 | 73 | iconName: "edit" | ||
880 | 74 | } | ||
881 | 75 | } | ||
882 | 76 | } | ||
883 | 77 | } | ||
884 | 78 | |||
885 | 79 | Component { | ||
886 | 80 | id: listView | ||
887 | 81 | ListView { | ||
888 | 82 | model: 10 | ||
889 | 83 | } | ||
890 | 84 | } | ||
891 | 85 | |||
892 | 86 | Component { | ||
893 | 87 | id: generic | ||
894 | 88 | Column { | ||
895 | 89 | spacing: units.gu(1) | ||
896 | 90 | Repeater { | ||
897 | 91 | model: 2 | ||
898 | 92 | delegate: listItemWithContent | ||
899 | 93 | } | ||
900 | 94 | Repeater { | ||
901 | 95 | model: 2 | ||
902 | 96 | delegate: simpleListItem | ||
903 | 97 | } | ||
904 | 98 | } | ||
905 | 99 | } | ||
906 | 100 | |||
907 | 101 | ListItemTestCase13 { | ||
908 | 102 | name: "ListItemFocus" | ||
909 | 103 | when: windowShown | ||
910 | 104 | |||
911 | 105 | function loadTest(component) { | ||
912 | 106 | testLoader.sourceComponent = component; | ||
913 | 107 | tryCompare(testLoader, "status", Loader.Ready); | ||
914 | 108 | return testLoader.item; | ||
915 | 109 | } | ||
916 | 110 | |||
917 | 111 | function cleanup() { | ||
918 | 112 | testLoader.sourceComponent = null; | ||
919 | 113 | wait(200); | ||
920 | 114 | } | ||
921 | 115 | function init() { | ||
922 | 116 | topItem.forceActiveFocus(Qt.TabFocusReason); | ||
923 | 117 | } | ||
924 | 118 | |||
925 | 119 | function initTestCase() { | ||
926 | 120 | TestExtras.registerTouchDevice(); | ||
927 | 121 | } | ||
928 | 122 | |||
929 | 123 | // Tab/Backtab focuses the First ListItem in a ListView | ||
930 | 124 | function test_focusing_listview_focuses_first_item_data() { | ||
931 | 125 | return [ | ||
932 | 126 | {tag: "Tab, no content", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Tab, focusItem: "simple0"}, | ||
933 | 127 | {tag: "Tab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Tab, focusItem: "withContent0"}, | ||
934 | 128 | {tag: "Backtab, no content", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Backtab, focusItem: "simple0"}, | ||
935 | 129 | {tag: "Backtab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Backtab, focusItem: "withContent0"}, | ||
936 | 130 | ]; | ||
937 | 131 | } | ||
938 | 132 | function test_focusing_listview_focuses_first_item(data) { | ||
939 | 133 | var test = loadTest(listView); | ||
940 | 134 | test.delegate = data.delegate; | ||
941 | 135 | waitForRendering(test, 500); | ||
942 | 136 | data.preFocus.forceActiveFocus(); | ||
943 | 137 | verify(data.preFocus.activeFocus); | ||
944 | 138 | keyClick(data.key); | ||
945 | 139 | var listItem = findChild(test, data.focusItem); | ||
946 | 140 | verify(listItem); | ||
947 | 141 | tryCompare(listItem, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
948 | 142 | verify(listItem.keyNavigationFocus); | ||
949 | 143 | } | ||
950 | 144 | |||
951 | 145 | // vertical key navigation in ListView between ListItems | ||
952 | 146 | function test_focus_and_navigate_in_listview_data() { | ||
953 | 147 | return [ | ||
954 | 148 | {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]}, | ||
955 | 149 | {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]}, | ||
956 | 150 | ]; | ||
957 | 151 | } | ||
958 | 152 | function test_focus_and_navigate_in_listview(data) { | ||
959 | 153 | var test = loadTest(listView); | ||
960 | 154 | test.delegate = data.delegate; | ||
961 | 155 | waitForRendering(test, 500); | ||
962 | 156 | keyClick(Qt.Key_Tab); | ||
963 | 157 | for (var i = 0; i < data.keyTimes; i++) { | ||
964 | 158 | keyClick(data.key); | ||
965 | 159 | var item = findChild(test, data.focusItems[i]); | ||
966 | 160 | verify(item); | ||
967 | 161 | tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
968 | 162 | verify(item.keyNavigationFocus, "failure on key navigation on " + data.focusItems[i]); | ||
969 | 163 | } | ||
970 | 164 | } | ||
971 | 165 | |||
972 | 166 | // vertical navigation updates ListView.currentItem (as well as currentIndex) | ||
973 | 167 | function test_focus_and_navigate_in_listview_updates_currentItem_data() { | ||
974 | 168 | return [ | ||
975 | 169 | {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]}, | ||
976 | 170 | {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]}, | ||
977 | 171 | ]; | ||
978 | 172 | } | ||
979 | 173 | function test_focus_and_navigate_in_listview_updates_currentItem(data) { | ||
980 | 174 | var test = loadTest(listView); | ||
981 | 175 | test.delegate = data.delegate; | ||
982 | 176 | waitForRendering(test, 500); | ||
983 | 177 | keyClick(Qt.Key_Tab); | ||
984 | 178 | for (var i = 0; i < data.keyTimes; i++) { | ||
985 | 179 | keyClick(data.key); | ||
986 | 180 | var item = findChild(test, data.focusItems[i]); | ||
987 | 181 | verify(item); | ||
988 | 182 | tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
989 | 183 | verify(item.keyNavigationFocus, "failure on key navigation on " + data.focusItems[i]); | ||
990 | 184 | compare(test.currentItem, item); | ||
991 | 185 | compare(test.currentIndex, item.itemIndex); | ||
992 | 186 | } | ||
993 | 187 | } | ||
994 | 188 | |||
995 | 189 | // re-focusing ListView will focus on the last focused item | ||
996 | 190 | function test_refocus_listview_on_last_focused_item_data() { | ||
997 | 191 | return [ | ||
998 | 192 | {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]}, | ||
999 | 193 | {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]}, | ||
1000 | 194 | ]; | ||
1001 | 195 | } | ||
1002 | 196 | function test_refocus_listview_on_last_focused_item(data) { | ||
1003 | 197 | var test = loadTest(listView); | ||
1004 | 198 | test.delegate = data.delegate; | ||
1005 | 199 | waitForRendering(test, 500); | ||
1006 | 200 | // focus on ListView and focus the 3rd item | ||
1007 | 201 | test.forceActiveFocus(); | ||
1008 | 202 | test.currentIndex = 2; | ||
1009 | 203 | waitForRendering(test, 400); | ||
1010 | 204 | verify(!test.currentItem.keyNavigationFocus, "Focus frame shown for the item"); | ||
1011 | 205 | // focus away | ||
1012 | 206 | keyClick(Qt.Key_Tab); | ||
1013 | 207 | waitForRendering(test, 400); | ||
1014 | 208 | // then focus back | ||
1015 | 209 | keyClick(Qt.Key_Backtab); | ||
1016 | 210 | waitForRendering(test, 400); | ||
1017 | 211 | tryCompare(test.currentItem, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
1018 | 212 | verify(test.currentItem.keyNavigationFocus, "Focus frame not shown for the item"); | ||
1019 | 213 | } | ||
1020 | 214 | |||
1021 | 215 | // Tab/Backtab focuses, next Tab/Backtab focuses out of ListItem in a ListView | ||
1022 | 216 | function test_tab_backtab_navigates_away_of_listview_data() { | ||
1023 | 217 | return [ | ||
1024 | 218 | {tag: "Tab, simple", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Tab}, | ||
1025 | 219 | {tag: "Tab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Tab}, | ||
1026 | 220 | {tag: "BackTab, simple", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Backtab}, | ||
1027 | 221 | {tag: "BackTab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Backtab}, | ||
1028 | 222 | ]; | ||
1029 | 223 | } | ||
1030 | 224 | function test_tab_backtab_navigates_away_of_listview(data) { | ||
1031 | 225 | var test = loadTest(listView); | ||
1032 | 226 | test.delegate = data.delegate; | ||
1033 | 227 | waitForRendering(test, 500); | ||
1034 | 228 | data.preFocus.forceActiveFocus(); | ||
1035 | 229 | verify(data.preFocus.activeFocus); | ||
1036 | 230 | // the first tab focuses the ListView and its first child | ||
1037 | 231 | keyClick(data.key); | ||
1038 | 232 | tryCompare(test, "activeFocus", true, 500, "Focus hasn't been gained bythe ListItem"); | ||
1039 | 233 | |||
1040 | 234 | // the second tab should leave the ListView | ||
1041 | 235 | keyClick(data.key); | ||
1042 | 236 | tryCompare(test, "activeFocus", false, 500, "Focus hasn't been lost by the ListItem"); | ||
1043 | 237 | } | ||
1044 | 238 | |||
1045 | 239 | // testing Tab/Backtab navigation when in a generic item | ||
1046 | 240 | function test_tab_navigation_when_not_in_listview_data() { | ||
1047 | 241 | return [ | ||
1048 | 242 | {tag: "Tabs", firstFocus: topItem, key: Qt.Key_Tab, | ||
1049 | 243 | focusItems: ["withContent0", "checkbox0", "switch0", "button0" | ||
1050 | 244 | , "withContent1", "checkbox1", "switch1", "button1"]}, | ||
1051 | 245 | {tag: "Backtabs", firstFocus: topItem, key: Qt.Key_Backtab, | ||
1052 | 246 | focusItems: ["simple1", "simple0" | ||
1053 | 247 | , "button1", "switch1", "checkbox1", "withContent1"]}, | ||
1054 | 248 | ]; | ||
1055 | 249 | } | ||
1056 | 250 | function test_tab_navigation_when_not_in_listview(data) { | ||
1057 | 251 | var test = loadTest(generic); | ||
1058 | 252 | data.firstFocus.forceActiveFocus(); | ||
1059 | 253 | for (var i = 0; i < data.focusItems.length; i++) { | ||
1060 | 254 | keyClick(data.key); | ||
1061 | 255 | compare(main.activeFocusItem.objectName, data.focusItems[i], "Unexpected focused item"); | ||
1062 | 256 | } | ||
1063 | 257 | } | ||
1064 | 258 | |||
1065 | 259 | // focus frame should not be shown | ||
1066 | 260 | function test_mouse_or_tap_focus_doesnt_show_focusframe_data() { | ||
1067 | 261 | return [ | ||
1068 | 262 | {tag: "Focus with mouse", mouse: true}, | ||
1069 | 263 | {tag: "Focus with touch", mouse: false}, | ||
1070 | 264 | ]; | ||
1071 | 265 | } | ||
1072 | 266 | function test_mouse_or_tap_focus_doesnt_show_focusframe(data) { | ||
1073 | 267 | var test = loadTest(listView); | ||
1074 | 268 | test.delegate = simpleListItem; | ||
1075 | 269 | waitForRendering(test, 500); | ||
1076 | 270 | var item = findChild(test, "simple3"); | ||
1077 | 271 | verify(item); | ||
1078 | 272 | if (data.mouse) { | ||
1079 | 273 | mouseClick(item, centerOf(item).x, centerOf(item).y); | ||
1080 | 274 | } else { | ||
1081 | 275 | TestExtras.touchClick(0, item, centerOf(item)); | ||
1082 | 276 | } | ||
1083 | 277 | verify(!item.keyNavigationFocus, "Focus frame must not be shown!"); | ||
1084 | 278 | } | ||
1085 | 279 | |||
1086 | 280 | // focus with mouse, then press Tab/Backtab, then mouse | ||
1087 | 281 | function test_focus_with_mouse_and_tab() { | ||
1088 | 282 | var test = loadTest(listView); | ||
1089 | 283 | test.delegate = simpleListItem; | ||
1090 | 284 | waitForRendering(test, 500); | ||
1091 | 285 | var listItem0 = findChild(test, "simple0"); | ||
1092 | 286 | verify(listItem0); | ||
1093 | 287 | var listItem1 = findChild(test, "simple1"); | ||
1094 | 288 | verify(listItem1); | ||
1095 | 289 | var listItem2 = findChild(test, "simple2"); | ||
1096 | 290 | verify(listItem2); | ||
1097 | 291 | |||
1098 | 292 | // click on first | ||
1099 | 293 | mouseClick(listItem0, centerOf(listItem0).x, centerOf(listItem0).y); | ||
1100 | 294 | verify(listItem0.activeFocus, "Not focused"); | ||
1101 | 295 | } | ||
1102 | 296 | |||
1103 | 297 | function test_horizontal_navigation_between_listitem_children_with_tabstop_data() { | ||
1104 | 298 | return [ | ||
1105 | 299 | {tag: "in ListView, rightwards", test: listView, focusItem: "withContent1", | ||
1106 | 300 | key: Qt.Key_Right, focus: ["checkbox1", "switch1", "button1", "withContent1"]}, | ||
1107 | 301 | {tag: "in ListView, leftwards", test: listView, focusItem: "withContent1", | ||
1108 | 302 | key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]}, | ||
1109 | 303 | {tag: "in generic, rightwards", test: generic, focusItem: "withContent1", | ||
1110 | 304 | key: Qt.Key_Right, focus: ["checkbox1", "switch1", "button1", "withContent1"]}, | ||
1111 | 305 | {tag: "in generic, leftwards", test: listView, focusItem: "withContent1", | ||
1112 | 306 | key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]}, | ||
1113 | 307 | ]; | ||
1114 | 308 | } | ||
1115 | 309 | function test_horizontal_navigation_between_listitem_children_with_tabstop(data) { | ||
1116 | 310 | var test = loadTest(data.test); | ||
1117 | 311 | if (test.hasOwnProperty("delegate")) { | ||
1118 | 312 | test.delegate = listItemWithContent; | ||
1119 | 313 | waitForRendering(test, 500); | ||
1120 | 314 | } | ||
1121 | 315 | var item = findChild(test, data.focusItem); | ||
1122 | 316 | verify(item); | ||
1123 | 317 | item.forceActiveFocus(); | ||
1124 | 318 | tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
1125 | 319 | for (var i = 0; i < data.focus.length; i++) { | ||
1126 | 320 | keyClick(data.key); | ||
1127 | 321 | tryCompare(main.activeFocusItem, "objectName", data.focus[i]); | ||
1128 | 322 | } | ||
1129 | 323 | } | ||
1130 | 324 | |||
1131 | 325 | // executes a combination of tab/navigation keys/backtab sequence | ||
1132 | 326 | function test_pattern_data() { | ||
1133 | 327 | return [ | ||
1134 | 328 | {tag: "Tabs in ListView", test: listView, delegate: listItemWithContent, testPlan: [ | ||
1135 | 329 | {key: Qt.Key_Tab, focus: "withContent0"}, | ||
1136 | 330 | {key: Qt.Key_Tab, focus: "topItem"}, | ||
1137 | 331 | {key: Qt.Key_Backtab, focus: "withContent0"}, | ||
1138 | 332 | ]}, | ||
1139 | 333 | {tag: "Tab and navigate in ListView", test: listView, delegate: listItemWithContent, testPlan: [ | ||
1140 | 334 | {key: Qt.Key_Tab, focus: "withContent0"}, | ||
1141 | 335 | {key: Qt.Key_Down, focus: "withContent1"}, | ||
1142 | 336 | {key: Qt.Key_Left, focus: "button1"}, | ||
1143 | 337 | {key: Qt.Key_Left, focus: "switch1"}, | ||
1144 | 338 | {key: Qt.Key_Down, focus: "withContent2"}, | ||
1145 | 339 | {key: Qt.Key_Right, focus: "checkbox2"}, | ||
1146 | 340 | {key: Qt.Key_Right, focus: "switch2"}, | ||
1147 | 341 | {key: Qt.Key_Down, focus: "withContent3"}, | ||
1148 | 342 | {key: Qt.Key_Down, focus: "withContent4"}, | ||
1149 | 343 | {key: Qt.Key_Backtab, focus: "topItem"}, | ||
1150 | 344 | {key: Qt.Key_Tab, focus: "withContent4"}, | ||
1151 | 345 | ]}, | ||
1152 | 346 | {tag: "Tab and navigate in generic", test: generic, testPlan: [ | ||
1153 | 347 | {key: Qt.Key_Tab, focus: "withContent0"}, | ||
1154 | 348 | {key: Qt.Key_Down, focus: "withContent0"}, | ||
1155 | 349 | {key: Qt.Key_Left, focus: "button0"}, | ||
1156 | 350 | {key: Qt.Key_Left, focus: "switch0"}, | ||
1157 | 351 | {key: Qt.Key_Down, focus: "switch0"}, | ||
1158 | 352 | {key: Qt.Key_Right, focus: "button0"}, | ||
1159 | 353 | {key: Qt.Key_Right, focus: "withContent0"}, | ||
1160 | 354 | {key: Qt.Key_Down, focus: "withContent0"}, | ||
1161 | 355 | {key: Qt.Key_Down, focus: "withContent0"}, | ||
1162 | 356 | {key: Qt.Key_Backtab, focus: "topItem"}, | ||
1163 | 357 | {key: Qt.Key_Tab, focus: "withContent0"}, | ||
1164 | 358 | ]}, | ||
1165 | 359 | {tag: "Mixed Tab and navigate keys in generic", test: generic, testPlan: [ | ||
1166 | 360 | {key: Qt.Key_Tab, focus: "withContent0"}, | ||
1167 | 361 | {key: Qt.Key_Tab, focus: "checkbox0"}, | ||
1168 | 362 | {key: Qt.Key_Tab, focus: "switch0"}, | ||
1169 | 363 | {key: Qt.Key_Right, focus: "button0"}, | ||
1170 | 364 | {key: Qt.Key_Right, focus: "withContent0"}, | ||
1171 | 365 | {key: Qt.Key_Tab, focus: "checkbox0"}, | ||
1172 | 366 | {key: Qt.Key_Left, focus: "withContent0"}, | ||
1173 | 367 | {key: Qt.Key_Left, focus: "button0"}, | ||
1174 | 368 | {key: Qt.Key_Tab, focus: "withContent1"}, | ||
1175 | 369 | ]}, | ||
1176 | 370 | ]; | ||
1177 | 371 | } | ||
1178 | 372 | function test_pattern(data) { | ||
1179 | 373 | var test = loadTest(data.test); | ||
1180 | 374 | if (test.hasOwnProperty("delegate") && data.delegate) { | ||
1181 | 375 | test.delegate = data.delegate; | ||
1182 | 376 | waitForRendering(test, 500); | ||
1183 | 377 | } | ||
1184 | 378 | for (var i = 0; i < data.testPlan.length; i++) { | ||
1185 | 379 | var plan = data.testPlan[i]; | ||
1186 | 380 | keyClick(plan.key); | ||
1187 | 381 | tryCompare(main.activeFocusItem, "activeFocus", true, 200, "Focus not set for " + plan.focus); | ||
1188 | 382 | compare(main.activeFocusItem.objectName, plan.focus); | ||
1189 | 383 | if (main.activeFocusItem.hasOwnProperty("keyNavigationFocus")) { | ||
1190 | 384 | verify(main.activeFocusItem.keyNavigationFocus); | ||
1191 | 385 | } | ||
1192 | 386 | } | ||
1193 | 387 | } | ||
1194 | 388 | |||
1195 | 389 | function test_do_not_focus_on_actions_data() { | ||
1196 | 390 | return [ | ||
1197 | 391 | {tag: "leading actions revealed", test: listView, focusItem: "withContent1", leading: true, swipeDx: units.gu(10), | ||
1198 | 392 | key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]}, | ||
1199 | 393 | {tag: "trailing actions revealed", test: listView, focusItem: "withContent1", leading: false, swipeDx: -units.gu(10), | ||
1200 | 394 | key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]}, | ||
1201 | 395 | ] | ||
1202 | 396 | } | ||
1203 | 397 | function test_do_not_focus_on_actions(data) { | ||
1204 | 398 | var test = loadTest(data.test); | ||
1205 | 399 | if (test.hasOwnProperty("delegate")) { | ||
1206 | 400 | test.delegate = listItemWithContent; | ||
1207 | 401 | waitForRendering(test, 500); | ||
1208 | 402 | } | ||
1209 | 403 | var item = findChild(test, data.focusItem); | ||
1210 | 404 | verify(item); | ||
1211 | 405 | item.forceActiveFocus(); | ||
1212 | 406 | tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem"); | ||
1213 | 407 | // swipe in | ||
1214 | 408 | swipe(item, data.leading ? 1 : item.width - 1, centerOf(item).y, data.swipeDx, 0); | ||
1215 | 409 | // compare | ||
1216 | 410 | for (var i = 0; i < data.focus.length; i++) { | ||
1217 | 411 | keyClick(data.key); | ||
1218 | 412 | tryCompare(main.activeFocusItem, "objectName", data.focus[i]); | ||
1219 | 413 | } | ||
1220 | 414 | } | ||
1221 | 415 | } | ||
1222 | 416 | } | ||
1223 | 417 | |||
1224 | 0 | 418 | ||
1225 | === modified file 'tests/unit_x11/tst_components/tst_quickutils.qml' | |||
1226 | --- tests/unit_x11/tst_components/tst_quickutils.qml 2015-03-03 13:20:06 +0000 | |||
1227 | +++ tests/unit_x11/tst_components/tst_quickutils.qml 2016-03-01 15:09:23 +0000 | |||
1228 | @@ -1,5 +1,5 @@ | |||
1229 | 1 | /* | 1 | /* |
1231 | 2 | * Copyright 2012 Canonical Ltd. | 2 | * Copyright 2012-2016 Canonical Ltd. |
1232 | 3 | * | 3 | * |
1233 | 4 | * This program is free software; you can redistribute it and/or modify | 4 | * This program is free software; you can redistribute it and/or modify |
1234 | 5 | * it under the terms of the GNU Lesser General Public License as published by | 5 | * it under the terms of the GNU Lesser General Public License as published by |
1235 | @@ -14,16 +14,33 @@ | |||
1236 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1237 | 15 | */ | 15 | */ |
1238 | 16 | 16 | ||
1240 | 17 | import QtQuick 2.0 | 17 | import QtQuick 2.4 |
1241 | 18 | import QtTest 1.0 | 18 | import QtTest 1.0 |
1244 | 19 | import Ubuntu.Components 1.1 | 19 | import Ubuntu.Components 1.3 |
1243 | 20 | import Ubuntu.Components.ListItems 1.0 | ||
1245 | 21 | 20 | ||
1246 | 22 | Item { | 21 | Item { |
1247 | 23 | id: root | 22 | id: root |
1248 | 24 | width: units.gu(40) | 23 | width: units.gu(40) |
1249 | 25 | height: units.gu(40) | 24 | height: units.gu(40) |
1250 | 26 | 25 | ||
1251 | 26 | Column { | ||
1252 | 27 | id: focusGroup | ||
1253 | 28 | Item { | ||
1254 | 29 | objectName: "unfocusableFirst" | ||
1255 | 30 | } | ||
1256 | 31 | |||
1257 | 32 | Repeater { | ||
1258 | 33 | model: 5 | ||
1259 | 34 | Item { | ||
1260 | 35 | objectName: index == 0 ? "first" : (index == 4 ? "last" : "item" + index) | ||
1261 | 36 | activeFocusOnTab: true | ||
1262 | 37 | } | ||
1263 | 38 | } | ||
1264 | 39 | Item { | ||
1265 | 40 | objectName: "unfocusableLast" | ||
1266 | 41 | } | ||
1267 | 42 | } | ||
1268 | 43 | |||
1269 | 27 | TestCase { | 44 | TestCase { |
1270 | 28 | id: test | 45 | id: test |
1271 | 29 | name: "QuickUtilsAPI" | 46 | name: "QuickUtilsAPI" |
1272 | @@ -39,5 +56,15 @@ | |||
1273 | 39 | compare(QuickUtils.className(test), "TestCase", "className for TestCase"); | 56 | compare(QuickUtils.className(test), "TestCase", "className for TestCase"); |
1274 | 40 | compare(QuickUtils.className(root), "QQuickItem", "className for Item"); | 57 | compare(QuickUtils.className(root), "QQuickItem", "className for Item"); |
1275 | 41 | } | 58 | } |
1276 | 59 | |||
1277 | 60 | function test_firstFocusableChild() | ||
1278 | 61 | { | ||
1279 | 62 | compare(QuickUtils.firstFocusableChild(focusGroup).objectName, "first"); | ||
1280 | 63 | } | ||
1281 | 64 | |||
1282 | 65 | function test_lastFocusableChild() | ||
1283 | 66 | { | ||
1284 | 67 | compare(QuickUtils.lastFocusableChild(focusGroup).objectName, "last"); | ||
1285 | 68 | } | ||
1286 | 42 | } | 69 | } |
1287 | 43 | } | 70 | } |
PASSED: Continuous integration, rev:1765 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2734/ jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/1457 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/1460 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/1460/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/1456
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2734/ rebuild
http://