Merge lp:~zsombi/ubuntu-ui-toolkit/list_item_focus into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
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
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.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
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 firstFocusableChild, lasyFocusableChild, isDecendantOf, to ensure you can rely on their behavior when looking at ListItem tests.

+ // 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.

review: Needs Information
Revision history for this message
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
> firstFocusableChild, lasyFocusableChild, isDecendantOf, to ensure you can rely
> 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...

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
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://jenkins.ubuntu.com/ubuntu-sdk/job/ubuntu-ui-toolkit-autolanding/131/
Executed test runs:
    None: https://jenkins.ubuntu.com/ubuntu-sdk/job/generic-land-mp/132/console

review: Needs Fixing (continuous-integration)
Revision history for this message
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://code.launchpad.net/~tpeeters/ubuntu-ui-toolkit/60-scectionScrolling/+merge/286330 too. It is a simple horizontal ListView that can be selected using (back)tab, and navigation inside the sections should be done with left/right cursor keys.

Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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.activeFocusItem doesn't change (it remains on the last item), and I'm seeing this error message:
examples/ubuntu-ui-toolkit-gallery/MainPage.qml:110: TypeError: Cannot read property 'source' of undefined

review: Needs Fixing
Revision history for this message
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/uclistitem.cpp: In member function ‘virtual void UCListItem::itemChange(QQuickItem::ItemChange, const QQuickItem::ItemChangeData&)’:
plugin/uclistitem.cpp:1100:16: error: ‘class UCListItemPrivate’ has no member named ‘isTabFence’
             d->isTabFence = d->parentAttached->isAttachedToListView();
                ^

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
1783. By Zsombor Egri

roll back palette changes

1784. By Zsombor Egri

origin merged

1785. By Zsombor Egri

API fixed

Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Cris Dywan (kalikiana) wrote :

Sweet, sweet fixes. Like the additional tests. Let's get this in!

review: Approve
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml'
--- src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2016-01-27 16:42:47 +0000
+++ src/Ubuntu/Components/Themes/Ambiance/1.3/ListItemStyle.qml 2016-03-01 15:09:23 +0000
@@ -79,6 +79,7 @@
79 id: actionButton79 id: actionButton
80 action: modelData80 action: modelData
81 enabled: action.enabled81 enabled: action.enabled
82 activeFocusOnTab: false
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)
83 anchors {84 anchors {
84 top: parent ? parent.top : undefined85 top: parent ? parent.top : undefined
8586
=== modified file 'src/Ubuntu/Components/plugin/plugin.pri'
--- src/Ubuntu/Components/plugin/plugin.pri 2016-02-10 18:02:18 +0000
+++ src/Ubuntu/Components/plugin/plugin.pri 2016-03-01 15:09:23 +0000
@@ -110,6 +110,7 @@
110 $$PWD/ucmainviewbase.h \110 $$PWD/ucmainviewbase.h \
111 $$PWD/ucmainviewbase_p.h \111 $$PWD/ucmainviewbase_p.h \
112 $$PWD/ucperformancemonitor.h \112 $$PWD/ucperformancemonitor.h \
113 $$PWD/privates/listviewextensions.h \
113 $$PWD/privates/frame.h \114 $$PWD/privates/frame.h \
114 $$PWD/privates/ucpagewrapper.h \115 $$PWD/privates/ucpagewrapper.h \
115 $$PWD/privates/ucpagewrapper_p.h \116 $$PWD/privates/ucpagewrapper_p.h \
@@ -187,6 +188,7 @@
187 $$PWD/ucpagetreenode.cpp \188 $$PWD/ucpagetreenode.cpp \
188 $$PWD/ucmainviewbase.cpp \189 $$PWD/ucmainviewbase.cpp \
189 $$PWD/ucperformancemonitor.cpp \190 $$PWD/ucperformancemonitor.cpp \
191 $$PWD/privates/listviewextensions.cpp \
190 $$PWD/privates/frame.cpp \192 $$PWD/privates/frame.cpp \
191 $$PWD/privates/ucpagewrapper.cpp \193 $$PWD/privates/ucpagewrapper.cpp \
192 $$PWD/privates/ucpagewrapperincubator.cpp \194 $$PWD/privates/ucpagewrapperincubator.cpp \
193195
=== added file 'src/Ubuntu/Components/plugin/privates/listviewextensions.cpp'
--- src/Ubuntu/Components/plugin/privates/listviewextensions.cpp 1970-01-01 00:00:00 +0000
+++ src/Ubuntu/Components/plugin/privates/listviewextensions.cpp 2016-03-01 15:09:23 +0000
@@ -0,0 +1,151 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author Zsombor Egri <zsombor.egri@canonical.com>
17 */
18
19#include "listviewextensions.h"
20#include "uclistitem_p.h"
21#include "quickutils.h"
22#include <QtQuick/QQuickItem>
23#include <QtQuick/private/qquickflickable_p.h>
24
25ListViewProxy::ListViewProxy(QQuickFlickable *listView, QObject *parent)
26 : QObject(parent)
27 , listView(listView)
28{
29}
30ListViewProxy::~ListViewProxy()
31{
32 if (isEventFilter) {
33 listView->removeEventFilter(this);
34 }
35}
36
37// proxy methods
38
39Qt::Orientation ListViewProxy::orientation()
40{
41 return (Qt::Orientation)listView->property("orientation").toInt();
42}
43
44int ListViewProxy::count()
45{
46 return listView->property("count").toInt();
47}
48
49QQuickItem *ListViewProxy::currentItem()
50{
51 return listView->property("currentItem").value<QQuickItem*>();
52}
53
54int ListViewProxy::currentIndex()
55{
56 return listView->property("currentIndex").toInt();
57}
58
59void ListViewProxy::setCurrentIndex(int index)
60{
61 listView->setProperty("currentIndex", index);
62}
63
64QVariant ListViewProxy::model()
65{
66 return listView->property("model");
67}
68
69/*********************************************************************
70 * Additional functionality used in different places in toolkit
71 *********************************************************************/
72
73// Navigation override used by ListItems
74void ListViewProxy::overrideItemNavigation(bool override)
75{
76 if (override) {
77 listView->installEventFilter(this);
78 } else {
79 listView->removeEventFilter(this);
80 }
81 isEventFilter = override;
82}
83
84bool ListViewProxy::eventFilter(QObject *, QEvent *event)
85{
86 switch (event->type()) {
87 case QEvent::FocusIn:
88 return focusInEvent(static_cast<QFocusEvent*>(event));
89 case QEvent::KeyPress:
90 return keyPressEvent(static_cast<QKeyEvent*>(event));
91 default:
92 break;
93 }
94
95 return false;
96}
97
98void ListViewProxy::setKeyNavigationForListView(bool value)
99{
100 UCListItem *listItem = qobject_cast<UCListItem*>(currentItem());
101 if (listItem) {
102 UCListItemPrivate::get(listItem)->setListViewKeyNavigation(value);
103 listItem->update();
104 }
105}
106
107// grab focusIn event
108bool ListViewProxy::focusInEvent(QFocusEvent *event)
109{
110 switch (event->reason()) {
111 case Qt::TabFocusReason:
112 case Qt::BacktabFocusReason:
113 {
114 QQuickItem *currentItem = this->currentItem();
115 if (!currentItem && count() > 0) {
116 // set the first one to be the focus
117 setCurrentIndex(0);
118 setKeyNavigationForListView(true);
119 }
120 break;
121 }
122 default:
123 break;
124 }
125 return false;
126}
127
128// override up/down key presses for ListView
129bool ListViewProxy::keyPressEvent(QKeyEvent *event)
130{
131 int key = event->key();
132 Qt::Orientation orientation = this->orientation();
133
134 if ((orientation == Qt::Vertical && key != Qt::Key_Up && key != Qt::Key_Down)
135 || (orientation == Qt::Horizontal && key != Qt::Key_Left && key != Qt::Key_Right)) {
136 return false;
137 }
138 // for horizontal moves take into account the layout mirroring
139 bool isRtl = QQuickItemPrivate::get(listView)->effectiveLayoutMirror;
140 bool forwards = (key == Qt::Key_Up || (isRtl ? key == Qt::Key_Left : key == Qt::Key_Right));
141 int currentIndex = this->currentIndex();
142 int count = this->count();
143
144 if (currentIndex >= 0 && count > 0) {
145 currentIndex = qBound<int>(0, forwards ? currentIndex - 1 : currentIndex + 1, count - 1);
146 setCurrentIndex(currentIndex);
147 setKeyNavigationForListView(true);
148 }
149
150 return true;
151}
0152
=== added file 'src/Ubuntu/Components/plugin/privates/listviewextensions.h'
--- src/Ubuntu/Components/plugin/privates/listviewextensions.h 1970-01-01 00:00:00 +0000
+++ src/Ubuntu/Components/plugin/privates/listviewextensions.h 2016-03-01 15:09:23 +0000
@@ -0,0 +1,60 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Author Zsombor Egri <zsombor.egri@canonical.com>
17 */
18
19#ifndef LISTVIEWEXTENSIONS_H
20#define LISTVIEWEXTENSIONS_H
21
22#include <QtCore/QObject>
23
24class QQuickFlickable;
25class QQuickItem;
26class QFocusEvent;
27class QKeyEvent;
28class ListViewProxy : public QObject
29{
30 Q_OBJECT
31public:
32 explicit ListViewProxy(QQuickFlickable *listView, QObject *parent = 0);
33 virtual ~ListViewProxy();
34 inline QQuickFlickable *view() const
35 {
36 return listView;
37 }
38 void overrideItemNavigation(bool override);
39
40
41 // proxied methods
42 Qt::Orientation orientation();
43 int count();
44 QQuickItem *currentItem();
45 int currentIndex();
46 void setCurrentIndex(int index);
47 QVariant model();
48
49protected:
50 bool eventFilter(QObject *, QEvent *) override;
51
52 bool focusInEvent(QFocusEvent *event);
53 bool keyPressEvent(QKeyEvent *event);
54 void setKeyNavigationForListView(bool value);
55private:
56 QQuickFlickable *listView;
57 bool isEventFilter:1;
58};
59
60#endif // LISTVIEWEXTENSIONS_H
061
=== modified file 'src/Ubuntu/Components/plugin/quickutils.cpp'
--- src/Ubuntu/Components/plugin/quickutils.cpp 2016-02-01 18:57:26 +0000
+++ src/Ubuntu/Components/plugin/quickutils.cpp 2016-03-01 15:09:23 +0000
@@ -214,3 +214,56 @@
214 }214 }
215 return showWarnings == 2;215 return showWarnings == 2;
216}216}
217
218// check whether an item is a descendant of parent
219bool QuickUtils::descendantItemOf(QQuickItem *item, const QQuickItem *parent)
220{
221 while (item && parent) {
222 if (item == parent) {
223 return true;
224 }
225 item = item->parentItem();
226 }
227 return false;
228}
229
230// returns the first key-focusable child item
231QQuickItem *QuickUtils::firstFocusableChild(QQuickItem *item)
232{
233 if (!item) {
234 return Q_NULLPTR;
235 }
236 const QList<QQuickItem*> &list = item->childItems();
237 for (int i = 0; i < list.count(); i++) {
238 QQuickItem *child = list.at(i);
239 if (child->activeFocusOnTab()) {
240 return child;
241 }
242 QQuickItem *focus = firstFocusableChild(child);
243 if (focus) {
244 return focus;
245 }
246 }
247 return Q_NULLPTR;
248}
249
250// returns the last key-focusable child item
251QQuickItem *QuickUtils::lastFocusableChild(QQuickItem *item)
252{
253 if (!item) {
254 return Q_NULLPTR;
255 }
256 const QList<QQuickItem*> &list = item->childItems();
257 int i = list.count() - 1;
258 while (i >= 0) {
259 QQuickItem *child = list.at(i--);
260 if (child->activeFocusOnTab()) {
261 return child;
262 }
263 QQuickItem *focus = lastFocusableChild(child);
264 if (focus) {
265 return focus;
266 }
267 }
268 return Q_NULLPTR;
269}
217270
=== modified file 'src/Ubuntu/Components/plugin/quickutils.h'
--- src/Ubuntu/Components/plugin/quickutils.h 2016-02-10 09:54:59 +0000
+++ src/Ubuntu/Components/plugin/quickutils.h 2016-03-01 15:09:23 +0000
@@ -55,6 +55,9 @@
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);
56 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);56 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);
57 static bool showDeprecationWarnings();57 static bool showDeprecationWarnings();
58 static bool descendantItemOf(QQuickItem *item, const QQuickItem *parent);
59 Q_INVOKABLE static QQuickItem *firstFocusableChild(QQuickItem *item);
60 Q_INVOKABLE static QQuickItem *lastFocusableChild(QQuickItem *item);
5861
59 bool mouseAttached()62 bool mouseAttached()
60 {63 {
6164
=== modified file 'src/Ubuntu/Components/plugin/uclistitem.cpp'
--- src/Ubuntu/Components/plugin/uclistitem.cpp 2016-02-08 11:00:27 +0000
+++ src/Ubuntu/Components/plugin/uclistitem.cpp 2016-03-01 15:09:23 +0000
@@ -28,6 +28,7 @@
28#include "quickutils.h"28#include "quickutils.h"
29#include "ucaction.h"29#include "ucaction.h"
30#include "ucnamespace.h"30#include "ucnamespace.h"
31#include "privates/listviewextensions.h"
31#include <QtQml/QQmlInfo>32#include <QtQml/QQmlInfo>
32#include <QtQuick/private/qquickitem_p.h>33#include <QtQuick/private/qquickitem_p.h>
33#include <QtQuick/private/qquickflickable_p.h>34#include <QtQuick/private/qquickflickable_p.h>
@@ -210,7 +211,10 @@
210 , suppressClick(false)211 , suppressClick(false)
211 , ready(false)212 , ready(false)
212 , customColor(false)213 , customColor(false)
214 , listViewKeyNavigation(false)
213{215{
216 // the ListItem is not a focus scope
217 isFocusScope = false;
214}218}
215UCListItemPrivate::~UCListItemPrivate()219UCListItemPrivate::~UCListItemPrivate()
216{220{
@@ -220,10 +224,10 @@
220{224{
221 Q_Q(UCListItem);225 Q_Q(UCListItem);
222 contentItem->setObjectName("ListItemHolder");226 contentItem->setObjectName("ListItemHolder");
227 divider->init(q);
223 QQml_setParent_noEvent(contentItem, q);228 QQml_setParent_noEvent(contentItem, q);
224 contentItem->setParentItem(q);229 contentItem->setParentItem(q);
225 contentItem->setClip(true);230 contentItem->setClip(true);
226 divider->init(q);
227 // content will be redirected to the contentItem, therefore we must report231 // content will be redirected to the contentItem, therefore we must report
228 // children changes as it would come from the main component232 // children changes as it would come from the main component
229 QObject::connect(contentItem, &QQuickItem::childrenChanged,233 QObject::connect(contentItem, &QQuickItem::childrenChanged,
@@ -978,6 +982,23 @@
978{982{
979}983}
980984
985// override keyNavigationFocus getter
986bool UCListItem::keyNavigationFocus() const
987{
988 Q_D(const UCListItem);
989 return d->keyNavigationFocus ||d->listViewKeyNavigation;
990}
991
992void UCListItemPrivate::setListViewKeyNavigation(bool value)
993{
994 Q_Q(UCListItem);
995 bool prevKeyNav = q->keyNavigationFocus();
996 listViewKeyNavigation = value;
997 if (prevKeyNav != q->keyNavigationFocus()) {
998 Q_EMIT q->keyNavigationFocusChanged();
999 }
1000}
1001
981QObject *UCListItem::attachedViewItems(QObject *object, bool create)1002QObject *UCListItem::attachedViewItems(QObject *object, bool create)
982{1003{
983 return qmlAttachedPropertiesObject<UCViewItemsAttached>(object, create);1004 return qmlAttachedPropertiesObject<UCViewItemsAttached>(object, create);
@@ -1074,6 +1095,9 @@
1074 d->selection->attachToViewItems(d->parentAttached.data());1095 d->selection->attachToViewItems(d->parentAttached.data());
1075 connect(d->parentAttached.data(), SIGNAL(expandedIndicesChanged(QList<int>)),1096 connect(d->parentAttached.data(), SIGNAL(expandedIndicesChanged(QList<int>)),
1076 this, SLOT(_q_updateExpansion(QList<int>)), Qt::DirectConnection);1097 this, SLOT(_q_updateExpansion(QList<int>)), Qt::DirectConnection);
1098 // if the ViewItems is attached to a ListView, disable tab stops on the ListItem
1099 setActiveFocusOnTab(!d->parentAttached->isAttachedToListView());
1100 d->isTabFence = d->parentAttached->isAttachedToListView();
1077 }1101 }
10781102
1079 if (parentAttachee) {1103 if (parentAttachee) {
@@ -1101,17 +1125,36 @@
1101 if (!rectNode) {1125 if (!rectNode) {
1102 rectNode = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode();1126 rectNode = QQuickItemPrivate::get(this)->sceneGraphContext()->createRectangleNode();
1103 }1127 }
1128 bool updateNode = false;
1129
1130 // focus frame
1131 bool paintFocus = hasActiveFocus() && keyNavigationFocus();
1132 rectNode->setPenWidth(paintFocus ? UCUnits::instance()->dp(1) : 0);
1133 if (paintFocus) {
1134 QColor penColor;
1135 if (getTheme()) {
1136 penColor = getTheme()->getPaletteColor(isEnabled() ? "normal" : "disabled", "focus");
1137 }
1138 rectNode->setPenColor(penColor);
1139 rectNode->setColor(Qt::transparent);
1140 updateNode = true;
1141 }
1142 QRectF rect(boundingRect());
1143 rect -= QMarginsF(0, 0, UCUnits::instance()->dp(1), 0);
1144 d->divider->setOpacity(paintFocus ? 0.0 : 1.0);
1145 rectNode->setRect(rect);
1146
1147 // highlight color
1104 if (color.alphaF() >= (1.0f / 255.0f)) {1148 if (color.alphaF() >= (1.0f / 255.0f)) {
1105 rectNode->setColor(color);1149 rectNode->setColor(color);
1106 // cover only the area of the contentItem, removing divider's thickness
1107 QRectF rect(boundingRect());
1108 if (d->divider->isVisible()) {
1109 rect -= QMarginsF(0, 0, 0, d->divider->height());
1110 }
1111 rectNode->setRect(rect);
1112 rectNode->setGradientStops(QGradientStops());1150 rectNode->setGradientStops(QGradientStops());
1113 rectNode->setAntialiasing(true);1151 rectNode->setAntialiasing(true);
1114 rectNode->setAntialiasing(false);1152 rectNode->setAntialiasing(false);
1153 updateNode = true;
1154 }
1155
1156 // update
1157 if (updateNode) {
1115 rectNode->update();1158 rectNode->update();
1116 } else {1159 } else {
1117 // delete node, this will delete the divider node as well1160 // delete node, this will delete the divider node as well
@@ -1426,6 +1469,66 @@
1426 }1469 }
1427}1470}
14281471
1472void UCListItem::focusInEvent(QFocusEvent *event)
1473{
1474 Q_D(UCListItem);
1475 UCStyledItemBase::focusInEvent(event);
1476 if (event->reason() == Qt::MouseFocusReason) {
1477 d_func()->setListViewKeyNavigation(false);
1478 }
1479 update();
1480}
1481
1482void UCListItem::focusOutEvent(QFocusEvent *event)
1483{
1484 UCStyledItemBase::focusOutEvent(event);
1485 d_func()->setListViewKeyNavigation(false);
1486 update();
1487}
1488
1489// handle horizontal keys to navigate between focusable slots
1490void UCListItem::keyPressEvent(QKeyEvent *event)
1491{
1492 UCStyledItemBase::keyPressEvent(event);
1493 Q_D(UCListItem);
1494 int key = event->key();
1495 if (key != Qt::Key_Left && key != Qt::Key_Right) {
1496 return;
1497 }
1498
1499 bool forwards = (d->effectiveLayoutMirror ? key == Qt::Key_Left : key == Qt::Key_Right);
1500 // we must check whether the ListItem has any key navigation focusable child
1501 // this is needed due to the Qt bug https://bugreports.qt.io/browse/QTBUG-50516
1502 if (!QuickUtils::firstFocusableChild(this)) {
1503 return;
1504 }
1505
1506 // get the next focusable relative to the active focus item
1507 QQuickItem *activeFocus = isFocusScope() ? scopedFocusItem() : window()->activeFocusItem();
1508 if (!activeFocus) {
1509 return;
1510 }
1511
1512 Qt::FocusReason reason = forwards ? Qt::TabFocusReason : Qt::BacktabFocusReason;
1513 if ((activeFocus == QuickUtils::firstFocusableChild(this) && !forwards) ||
1514 (activeFocus == QuickUtils::lastFocusableChild(this) && forwards)) {
1515 // first or the last focus child is reached, so we wrap around
1516 // but for that we must set the activeFocus to false in order to
1517 // be able to focus the ListItem, especially when the ListItem is a Tab fence
1518 activeFocus->setFocus(false);
1519 forceActiveFocus(reason);
1520 } else if (activeFocus == this) {
1521 // get the first or last focusable item, depending on the direction
1522 QQuickItem *nextFocus = forwards
1523 ? QuickUtils::firstFocusableChild(this)
1524 : QuickUtils::lastFocusableChild(this);
1525 nextFocus->forceActiveFocus(reason);
1526 } else {
1527 // in case the ListItem is in ListView, we can freely proceed with the focusing
1528 QQuickItemPrivate::focusNextPrev(activeFocus, forwards);
1529 }
1530}
1531
1429/*!1532/*!
1430 * \qmlproperty ListItemActions ListItem::leadingActions1533 * \qmlproperty ListItemActions ListItem::leadingActions
1431 *1534 *
14321535
=== modified file 'src/Ubuntu/Components/plugin/uclistitem.h'
--- src/Ubuntu/Components/plugin/uclistitem.h 2015-11-16 16:24:42 +0000
+++ src/Ubuntu/Components/plugin/uclistitem.h 2016-03-01 15:09:23 +0000
@@ -53,6 +53,9 @@
53 explicit UCListItem(QQuickItem *parent = 0);53 explicit UCListItem(QQuickItem *parent = 0);
54 ~UCListItem();54 ~UCListItem();
5555
56 // overrides
57 bool keyNavigationFocus() const override;
58
56 QQuickItem *contentItem() const;59 QQuickItem *contentItem() const;
57 UCListItemDivider *divider() const;60 UCListItemDivider *divider() const;
58 UCListItemActions *leadingActions() const;61 UCListItemActions *leadingActions() const;
@@ -83,6 +86,9 @@
83 bool childMouseEventFilter(QQuickItem *child, QEvent *event);86 bool childMouseEventFilter(QQuickItem *child, QEvent *event);
84 bool eventFilter(QObject *, QEvent *);87 bool eventFilter(QObject *, QEvent *);
85 void timerEvent(QTimerEvent *event);88 void timerEvent(QTimerEvent *event);
89 void focusInEvent(QFocusEvent *event) override;
90 void focusOutEvent(QFocusEvent *event) override;
91 void keyPressEvent(QKeyEvent *event) override;
8692
87Q_SIGNALS:93Q_SIGNALS:
88 void leadingActionsChanged();94 void leadingActionsChanged();
@@ -177,6 +183,7 @@
177 static UCViewItemsAttached *qmlAttachedProperties(QObject *owner);183 static UCViewItemsAttached *qmlAttachedProperties(QObject *owner);
178184
179 bool listenToRebind(UCListItem *item, bool listen);185 bool listenToRebind(UCListItem *item, bool listen);
186 bool isAttachedToListView();
180 bool isMoving();187 bool isMoving();
181 bool isBoundTo(UCListItem *item);188 bool isBoundTo(UCListItem *item);
182189
183190
=== modified file 'src/Ubuntu/Components/plugin/uclistitem_p.h'
--- src/Ubuntu/Components/plugin/uclistitem_p.h 2015-12-14 09:16:41 +0000
+++ src/Ubuntu/Components/plugin/uclistitem_p.h 2016-03-01 15:09:23 +0000
@@ -102,6 +102,7 @@
102 bool suppressClick:1;102 bool suppressClick:1;
103 bool ready:1;103 bool ready:1;
104 bool customColor:1;104 bool customColor:1;
105 bool listViewKeyNavigation:1;
105106
106 // getters/setters107 // getters/setters
107 QQmlListProperty<QObject> data();108 QQmlListProperty<QObject> data();
@@ -119,6 +120,7 @@
119 void setSelectMode(bool selectable);120 void setSelectMode(bool selectable);
120 UCAction *action() const;121 UCAction *action() const;
121 void setAction(UCAction *action);122 void setAction(UCAction *action);
123 void setListViewKeyNavigation(bool value);
122124
123 virtual void postThemeChanged();125 virtual void postThemeChanged();
124 inline UCListItemStyle *listItemStyle() const;126 inline UCListItemStyle *listItemStyle() const;
@@ -130,12 +132,14 @@
130132
131class PropertyChange;133class PropertyChange;
132class ListItemDragArea;134class ListItemDragArea;
135class ListViewProxy;
133class UCViewItemsAttachedPrivate : public QObjectPrivate136class UCViewItemsAttachedPrivate : public QObjectPrivate
134{137{
135 Q_DECLARE_PUBLIC(UCViewItemsAttached)138 Q_DECLARE_PUBLIC(UCViewItemsAttached)
136public:139public:
137 UCViewItemsAttachedPrivate();140 UCViewItemsAttachedPrivate();
138 ~UCViewItemsAttachedPrivate();141 ~UCViewItemsAttachedPrivate();
142 void init();
139143
140 static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item)144 static UCViewItemsAttachedPrivate *get(UCViewItemsAttached *item)
141 {145 {
@@ -162,7 +166,7 @@
162 QMap<int, QPointer<UCListItem> > expansionList;166 QMap<int, QPointer<UCListItem> > expansionList;
163 QList< QPointer<QQuickFlickable> > flickables;167 QList< QPointer<QQuickFlickable> > flickables;
164 QPointer<UCListItem> boundItem;168 QPointer<UCListItem> boundItem;
165 QQuickFlickable *listView;169 ListViewProxy *listView;
166 ListItemDragArea *dragArea;170 ListItemDragArea *dragArea;
167 UCViewItemsAttached::ExpansionFlags expansionFlags;171 UCViewItemsAttached::ExpansionFlags expansionFlags;
168 bool selectable:1;172 bool selectable:1;
169173
=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.cpp'
--- src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2016-02-25 16:41:51 +0000
+++ src/Ubuntu/Components/plugin/ucstyleditembase.cpp 2016-03-01 15:09:23 +0000
@@ -33,6 +33,7 @@
33 , keyNavigationFocus(false)33 , keyNavigationFocus(false)
34 , activeFocusOnPress(false)34 , activeFocusOnPress(false)
35 , wasStyleLoaded(false)35 , wasStyleLoaded(false)
36 , isFocusScope(true)
36{37{
37}38}
3839
@@ -58,7 +59,6 @@
58void UCStyledItemBasePrivate::init()59void UCStyledItemBasePrivate::init()
59{60{
60 Q_Q(UCStyledItemBase);61 Q_Q(UCStyledItemBase);
61 q->setFlag(QQuickItem::ItemIsFocusScope);
62 QObject::connect(q, &QQuickItem::activeFocusOnTabChanged, q, &UCStyledItemBase::activeFocusOnTabChanged2);62 QObject::connect(q, &QQuickItem::activeFocusOnTabChanged, q, &UCStyledItemBase::activeFocusOnTabChanged2);
63}63}
6464
@@ -501,6 +501,21 @@
501 loadStyleItem(false);501 loadStyleItem(false);
502}502}
503503
504void UCStyledItemBase::classBegin()
505{
506 /* Some items require not to be focus scopes (like ListItem), however for
507 * backwards compatibility we must keep setting the generic styled items
508 * as focus scopes. The flag once set cannot be cleared, therefore we must
509 * use an additional boolean member isFocusScope to drive this request.
510 * The member defaults to true. Additionally, the focus scope flag must
511 * be set before the parentItem is set and child items are added.
512 */
513 if (d_func()->isFocusScope) {
514 setFlag(QQuickItem::ItemIsFocusScope);
515 }
516 QQuickItem::classBegin();
517}
518
504void UCStyledItemBase::componentComplete()519void UCStyledItemBase::componentComplete()
505{520{
506 QQuickItem::componentComplete();521 QQuickItem::componentComplete();
507522
=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase.h'
--- src/Ubuntu/Components/plugin/ucstyleditembase.h 2015-12-16 08:05:44 +0000
+++ src/Ubuntu/Components/plugin/ucstyleditembase.h 2016-03-01 15:09:23 +0000
@@ -48,7 +48,7 @@
48public:48public:
49 explicit UCStyledItemBase(QQuickItem *parent = 0);49 explicit UCStyledItemBase(QQuickItem *parent = 0);
5050
51 bool keyNavigationFocus() const;51 virtual bool keyNavigationFocus() const;
52 bool activefocusOnPress() const;52 bool activefocusOnPress() const;
53 void setActiveFocusOnPress(bool value);53 void setActiveFocusOnPress(bool value);
54 bool activeFocusOnTab2() const;54 bool activeFocusOnTab2() const;
@@ -73,6 +73,7 @@
73 virtual void preThemeChanged();73 virtual void preThemeChanged();
74 virtual void postThemeChanged();74 virtual void postThemeChanged();
7575
76 void classBegin();
76 void componentComplete();77 void componentComplete();
77 void itemChange(ItemChange change, const ItemChangeData &data);78 void itemChange(ItemChange change, const ItemChangeData &data);
78 void focusInEvent(QFocusEvent *key);79 void focusInEvent(QFocusEvent *key);
7980
=== modified file 'src/Ubuntu/Components/plugin/ucstyleditembase_p.h'
--- src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2015-12-15 19:17:11 +0000
+++ src/Ubuntu/Components/plugin/ucstyleditembase_p.h 2016-03-01 15:09:23 +0000
@@ -72,6 +72,7 @@
72 bool keyNavigationFocus:1;72 bool keyNavigationFocus:1;
73 bool activeFocusOnPress:1;73 bool activeFocusOnPress:1;
74 bool wasStyleLoaded:1;74 bool wasStyleLoaded:1;
75 bool isFocusScope:1;
7576
76protected:77protected:
7778
7879
=== modified file 'src/Ubuntu/Components/plugin/ucviewitemsattached.cpp'
--- src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2016-01-30 12:03:31 +0000
+++ src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2016-03-01 15:09:23 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2014-2015 Canonical Ltd.2 * Copyright 2014-2016 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -23,6 +23,7 @@
23#include "i18n.h"23#include "i18n.h"
24#include "uclistitemstyle.h"24#include "uclistitemstyle.h"
25#include "privates/listitemdragarea.h"25#include "privates/listitemdragarea.h"
26#include "privates/listviewextensions.h"
26#include <QtQuick/private/qquickflickable_p.h>27#include <QtQuick/private/qquickflickable_p.h>
27#include <QtQml/private/qqmlcomponentattached_p.h>28#include <QtQml/private/qqmlcomponentattached_p.h>
28#include <QtQml/QQmlInfo>29#include <QtQml/QQmlInfo>
@@ -116,6 +117,22 @@
116 clearFlickablesList();117 clearFlickablesList();
117}118}
118119
120void UCViewItemsAttachedPrivate::init()
121{
122 Q_Q(UCViewItemsAttached);
123 if (parent->inherits("QQuickListView")) {
124 listView = new ListViewProxy(static_cast<QQuickFlickable*>(parent), q);
125
126 // ListView focus handling
127 listView->view()->setActiveFocusOnTab(true);
128 // filter ListView events to override up/down focus handling
129 listView->overrideItemNavigation(true);
130 }
131 // listen readyness
132 QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(parent);
133 QObject::connect(attached, &QQmlComponentAttached::completed, q, &UCViewItemsAttached::completed);
134}
135
119// disconnect all flickables136// disconnect all flickables
120void UCViewItemsAttachedPrivate::clearFlickablesList()137void UCViewItemsAttachedPrivate::clearFlickablesList()
121{138{
@@ -167,12 +184,7 @@
167UCViewItemsAttached::UCViewItemsAttached(QObject *owner)184UCViewItemsAttached::UCViewItemsAttached(QObject *owner)
168 : QObject(*(new UCViewItemsAttachedPrivate()), owner)185 : QObject(*(new UCViewItemsAttachedPrivate()), owner)
169{186{
170 if (owner->inherits("QQuickListView")) {187 d_func()->init();
171 d_func()->listView = static_cast<QQuickFlickable*>(owner);
172 }
173 // listen readyness
174 QQmlComponentAttached *attached = QQmlComponent::qmlAttachedProperties(owner);
175 connect(attached, &QQmlComponentAttached::completed, this, &UCViewItemsAttached::completed);
176}188}
177189
178UCViewItemsAttached::~UCViewItemsAttached()190UCViewItemsAttached::~UCViewItemsAttached()
@@ -204,6 +216,12 @@
204 return result;216 return result;
205}217}
206218
219// reports whether the ViewItems is attached to ListView
220bool UCViewItemsAttached::isAttachedToListView()
221{
222 return (d_func()->listView != Q_NULLPTR);
223}
224
207// reports true if any of the ascendant flickables is moving225// reports true if any of the ascendant flickables is moving
208bool UCViewItemsAttached::isMoving()226bool UCViewItemsAttached::isMoving()
209{227{
@@ -462,7 +480,7 @@
462 qmlInfo(parent()) << QStringLiteral("Dragging mode requires ListView");480 qmlInfo(parent()) << QStringLiteral("Dragging mode requires ListView");
463 return;481 return;
464 }482 }
465 QVariant model = d->listView->property("model");483 QVariant model = d->listView->model();
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)
467 // or a derivate of QAbstractItemModel485 // or a derivate of QAbstractItemModel
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.");
@@ -492,7 +510,7 @@
492 dragArea->reset();510 dragArea->reset();
493 return;511 return;
494 }512 }
495 dragArea = new ListItemDragArea(listView);513 dragArea = new ListItemDragArea(listView->view());
496 dragArea->init(q_func());514 dragArea->init(q_func());
497}515}
498516
@@ -515,7 +533,7 @@
515// updates the selected indices list in ViewAttached which is changed due to dragging533// updates the selected indices list in ViewAttached which is changed due to dragging
516void UCViewItemsAttachedPrivate::updateSelectedIndices(int fromIndex, int toIndex)534void UCViewItemsAttachedPrivate::updateSelectedIndices(int fromIndex, int toIndex)
517{535{
518 if (selectedList.count() == listView->property("count").toInt()) {536 if (selectedList.count() == listView->count()) {
519 // all indices selected, no need to reorder537 // all indices selected, no need to reorder
520 return;538 return;
521 }539 }
522540
=== modified file 'tests/resources/listitems/ListItemTest.qml'
--- tests/resources/listitems/ListItemTest.qml 2015-09-18 08:36:23 +0000
+++ tests/resources/listitems/ListItemTest.qml 2016-03-01 15:09:23 +0000
@@ -15,8 +15,8 @@
15 */15 */
1616
17import QtQuick 2.417import QtQuick 2.4
18import Ubuntu.Components 1.218import Ubuntu.Components 1.3
19import Ubuntu.Components.Styles 1.219import Ubuntu.Components.Styles 1.3
20import QtQuick.Layouts 1.120import QtQuick.Layouts 1.1
2121
22MainView {22MainView {
@@ -207,6 +207,7 @@
207 title.text: "This is one Label split in two lines.\n" +207 title.text: "This is one Label split in two lines.\n" +
208 "The second line - item #" + modelData208 "The second line - item #" + modelData
209 }209 }
210 CheckBox {}
210 Button {211 Button {
211 text: "Pressme..."212 text: "Pressme..."
212 }213 }
@@ -245,7 +246,7 @@
245 model: 10246 model: 10
246 ListItem {247 ListItem {
247 objectName: "InFlickable"+index248 objectName: "InFlickable"+index
248 color: UbuntuColors.red249 color: UbuntuColors.silk
249 highlightColor: "lime"250 highlightColor: "lime"
250 divider.colorFrom: UbuntuColors.green251 divider.colorFrom: UbuntuColors.green
251252
@@ -263,9 +264,13 @@
263 Label {264 Label {
264 text: modelData + " Flickable item"265 text: modelData + " Flickable item"
265 }266 }
266 Button {267 Row {
267 text: "Pressme..."
268 anchors.centerIn: parent268 anchors.centerIn: parent
269 spacing: units.dp(4)
270 Button {
271 text: "Pressme..."
272 }
273 Switch {}
269 }274 }
270275
271 onClicked: divider.visible = !divider.visible276 onClicked: divider.visible = !divider.visible
272277
=== added file 'tests/unit_x11/tst_components/tst_listitem_focus.qml'
--- tests/unit_x11/tst_components/tst_listitem_focus.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/tst_listitem_focus.qml 2016-03-01 15:09:23 +0000
@@ -0,0 +1,417 @@
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtTest 1.0
19import Ubuntu.Test 1.0
20import Ubuntu.Components 1.3
21import QtQuick.Window 2.2
22
23Item {
24 id: main
25 width: units.gu(40)
26 height: units.gu(71)
27
28 property Item activeFocusItem: Window.activeFocusItem
29
30 Rectangle {
31 id: topItem
32 objectName: "topItem"
33 activeFocusOnTab: true
34 width: parent.width
35 height: units.gu(2)
36 }
37
38 Loader {
39 id: testLoader
40 anchors {
41 fill: parent
42 topMargin: topItem.height
43 }
44 }
45
46 Component {
47 id: simpleListItem
48 ListItem {
49 objectName: "simple" + index
50 property int itemIndex: index
51 }
52 }
53
54 Component {
55 id: listItemWithContent
56 ListItem {
57 id: listItem
58 objectName: "withContent" + index
59 property int itemIndex: index
60 Row {
61 spacing: units.gu(1)
62 CheckBox { objectName: "checkbox" + listItem.itemIndex }
63 Switch { objectName: "switch" + listItem.itemIndex }
64 Button { objectName: "button" + listItem.itemIndex; text: "test" }
65 }
66 leadingActions: ListItemActions {
67 actions: Action {
68 iconName: "delete"
69 }
70 }
71 trailingActions: ListItemActions {
72 actions: Action {
73 iconName: "edit"
74 }
75 }
76 }
77 }
78
79 Component {
80 id: listView
81 ListView {
82 model: 10
83 }
84 }
85
86 Component {
87 id: generic
88 Column {
89 spacing: units.gu(1)
90 Repeater {
91 model: 2
92 delegate: listItemWithContent
93 }
94 Repeater {
95 model: 2
96 delegate: simpleListItem
97 }
98 }
99 }
100
101 ListItemTestCase13 {
102 name: "ListItemFocus"
103 when: windowShown
104
105 function loadTest(component) {
106 testLoader.sourceComponent = component;
107 tryCompare(testLoader, "status", Loader.Ready);
108 return testLoader.item;
109 }
110
111 function cleanup() {
112 testLoader.sourceComponent = null;
113 wait(200);
114 }
115 function init() {
116 topItem.forceActiveFocus(Qt.TabFocusReason);
117 }
118
119 function initTestCase() {
120 TestExtras.registerTouchDevice();
121 }
122
123 // Tab/Backtab focuses the First ListItem in a ListView
124 function test_focusing_listview_focuses_first_item_data() {
125 return [
126 {tag: "Tab, no content", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Tab, focusItem: "simple0"},
127 {tag: "Tab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Tab, focusItem: "withContent0"},
128 {tag: "Backtab, no content", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Backtab, focusItem: "simple0"},
129 {tag: "Backtab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Backtab, focusItem: "withContent0"},
130 ];
131 }
132 function test_focusing_listview_focuses_first_item(data) {
133 var test = loadTest(listView);
134 test.delegate = data.delegate;
135 waitForRendering(test, 500);
136 data.preFocus.forceActiveFocus();
137 verify(data.preFocus.activeFocus);
138 keyClick(data.key);
139 var listItem = findChild(test, data.focusItem);
140 verify(listItem);
141 tryCompare(listItem, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
142 verify(listItem.keyNavigationFocus);
143 }
144
145 // vertical key navigation in ListView between ListItems
146 function test_focus_and_navigate_in_listview_data() {
147 return [
148 {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]},
149 {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]},
150 ];
151 }
152 function test_focus_and_navigate_in_listview(data) {
153 var test = loadTest(listView);
154 test.delegate = data.delegate;
155 waitForRendering(test, 500);
156 keyClick(Qt.Key_Tab);
157 for (var i = 0; i < data.keyTimes; i++) {
158 keyClick(data.key);
159 var item = findChild(test, data.focusItems[i]);
160 verify(item);
161 tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
162 verify(item.keyNavigationFocus, "failure on key navigation on " + data.focusItems[i]);
163 }
164 }
165
166 // vertical navigation updates ListView.currentItem (as well as currentIndex)
167 function test_focus_and_navigate_in_listview_updates_currentItem_data() {
168 return [
169 {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]},
170 {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]},
171 ];
172 }
173 function test_focus_and_navigate_in_listview_updates_currentItem(data) {
174 var test = loadTest(listView);
175 test.delegate = data.delegate;
176 waitForRendering(test, 500);
177 keyClick(Qt.Key_Tab);
178 for (var i = 0; i < data.keyTimes; i++) {
179 keyClick(data.key);
180 var item = findChild(test, data.focusItems[i]);
181 verify(item);
182 tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
183 verify(item.keyNavigationFocus, "failure on key navigation on " + data.focusItems[i]);
184 compare(test.currentItem, item);
185 compare(test.currentIndex, item.itemIndex);
186 }
187 }
188
189 // re-focusing ListView will focus on the last focused item
190 function test_refocus_listview_on_last_focused_item_data() {
191 return [
192 {tag: "No content", delegate: simpleListItem, key: Qt.Key_Down, keyTimes: 3, focusItems: ["simple1", "simple2", "simple3"]},
193 {tag: "With content", delegate: listItemWithContent, key: Qt.Key_Down, keyTimes: 3, focusItems: ["withContent1", "withContent2", "withContent3"]},
194 ];
195 }
196 function test_refocus_listview_on_last_focused_item(data) {
197 var test = loadTest(listView);
198 test.delegate = data.delegate;
199 waitForRendering(test, 500);
200 // focus on ListView and focus the 3rd item
201 test.forceActiveFocus();
202 test.currentIndex = 2;
203 waitForRendering(test, 400);
204 verify(!test.currentItem.keyNavigationFocus, "Focus frame shown for the item");
205 // focus away
206 keyClick(Qt.Key_Tab);
207 waitForRendering(test, 400);
208 // then focus back
209 keyClick(Qt.Key_Backtab);
210 waitForRendering(test, 400);
211 tryCompare(test.currentItem, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
212 verify(test.currentItem.keyNavigationFocus, "Focus frame not shown for the item");
213 }
214
215 // Tab/Backtab focuses, next Tab/Backtab focuses out of ListItem in a ListView
216 function test_tab_backtab_navigates_away_of_listview_data() {
217 return [
218 {tag: "Tab, simple", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Tab},
219 {tag: "Tab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Tab},
220 {tag: "BackTab, simple", preFocus: topItem, delegate: simpleListItem, key: Qt.Key_Backtab},
221 {tag: "BackTab, with content", preFocus: topItem, delegate: listItemWithContent, key: Qt.Key_Backtab},
222 ];
223 }
224 function test_tab_backtab_navigates_away_of_listview(data) {
225 var test = loadTest(listView);
226 test.delegate = data.delegate;
227 waitForRendering(test, 500);
228 data.preFocus.forceActiveFocus();
229 verify(data.preFocus.activeFocus);
230 // the first tab focuses the ListView and its first child
231 keyClick(data.key);
232 tryCompare(test, "activeFocus", true, 500, "Focus hasn't been gained bythe ListItem");
233
234 // the second tab should leave the ListView
235 keyClick(data.key);
236 tryCompare(test, "activeFocus", false, 500, "Focus hasn't been lost by the ListItem");
237 }
238
239 // testing Tab/Backtab navigation when in a generic item
240 function test_tab_navigation_when_not_in_listview_data() {
241 return [
242 {tag: "Tabs", firstFocus: topItem, key: Qt.Key_Tab,
243 focusItems: ["withContent0", "checkbox0", "switch0", "button0"
244 , "withContent1", "checkbox1", "switch1", "button1"]},
245 {tag: "Backtabs", firstFocus: topItem, key: Qt.Key_Backtab,
246 focusItems: ["simple1", "simple0"
247 , "button1", "switch1", "checkbox1", "withContent1"]},
248 ];
249 }
250 function test_tab_navigation_when_not_in_listview(data) {
251 var test = loadTest(generic);
252 data.firstFocus.forceActiveFocus();
253 for (var i = 0; i < data.focusItems.length; i++) {
254 keyClick(data.key);
255 compare(main.activeFocusItem.objectName, data.focusItems[i], "Unexpected focused item");
256 }
257 }
258
259 // focus frame should not be shown
260 function test_mouse_or_tap_focus_doesnt_show_focusframe_data() {
261 return [
262 {tag: "Focus with mouse", mouse: true},
263 {tag: "Focus with touch", mouse: false},
264 ];
265 }
266 function test_mouse_or_tap_focus_doesnt_show_focusframe(data) {
267 var test = loadTest(listView);
268 test.delegate = simpleListItem;
269 waitForRendering(test, 500);
270 var item = findChild(test, "simple3");
271 verify(item);
272 if (data.mouse) {
273 mouseClick(item, centerOf(item).x, centerOf(item).y);
274 } else {
275 TestExtras.touchClick(0, item, centerOf(item));
276 }
277 verify(!item.keyNavigationFocus, "Focus frame must not be shown!");
278 }
279
280 // focus with mouse, then press Tab/Backtab, then mouse
281 function test_focus_with_mouse_and_tab() {
282 var test = loadTest(listView);
283 test.delegate = simpleListItem;
284 waitForRendering(test, 500);
285 var listItem0 = findChild(test, "simple0");
286 verify(listItem0);
287 var listItem1 = findChild(test, "simple1");
288 verify(listItem1);
289 var listItem2 = findChild(test, "simple2");
290 verify(listItem2);
291
292 // click on first
293 mouseClick(listItem0, centerOf(listItem0).x, centerOf(listItem0).y);
294 verify(listItem0.activeFocus, "Not focused");
295 }
296
297 function test_horizontal_navigation_between_listitem_children_with_tabstop_data() {
298 return [
299 {tag: "in ListView, rightwards", test: listView, focusItem: "withContent1",
300 key: Qt.Key_Right, focus: ["checkbox1", "switch1", "button1", "withContent1"]},
301 {tag: "in ListView, leftwards", test: listView, focusItem: "withContent1",
302 key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]},
303 {tag: "in generic, rightwards", test: generic, focusItem: "withContent1",
304 key: Qt.Key_Right, focus: ["checkbox1", "switch1", "button1", "withContent1"]},
305 {tag: "in generic, leftwards", test: listView, focusItem: "withContent1",
306 key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]},
307 ];
308 }
309 function test_horizontal_navigation_between_listitem_children_with_tabstop(data) {
310 var test = loadTest(data.test);
311 if (test.hasOwnProperty("delegate")) {
312 test.delegate = listItemWithContent;
313 waitForRendering(test, 500);
314 }
315 var item = findChild(test, data.focusItem);
316 verify(item);
317 item.forceActiveFocus();
318 tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
319 for (var i = 0; i < data.focus.length; i++) {
320 keyClick(data.key);
321 tryCompare(main.activeFocusItem, "objectName", data.focus[i]);
322 }
323 }
324
325 // executes a combination of tab/navigation keys/backtab sequence
326 function test_pattern_data() {
327 return [
328 {tag: "Tabs in ListView", test: listView, delegate: listItemWithContent, testPlan: [
329 {key: Qt.Key_Tab, focus: "withContent0"},
330 {key: Qt.Key_Tab, focus: "topItem"},
331 {key: Qt.Key_Backtab, focus: "withContent0"},
332 ]},
333 {tag: "Tab and navigate in ListView", test: listView, delegate: listItemWithContent, testPlan: [
334 {key: Qt.Key_Tab, focus: "withContent0"},
335 {key: Qt.Key_Down, focus: "withContent1"},
336 {key: Qt.Key_Left, focus: "button1"},
337 {key: Qt.Key_Left, focus: "switch1"},
338 {key: Qt.Key_Down, focus: "withContent2"},
339 {key: Qt.Key_Right, focus: "checkbox2"},
340 {key: Qt.Key_Right, focus: "switch2"},
341 {key: Qt.Key_Down, focus: "withContent3"},
342 {key: Qt.Key_Down, focus: "withContent4"},
343 {key: Qt.Key_Backtab, focus: "topItem"},
344 {key: Qt.Key_Tab, focus: "withContent4"},
345 ]},
346 {tag: "Tab and navigate in generic", test: generic, testPlan: [
347 {key: Qt.Key_Tab, focus: "withContent0"},
348 {key: Qt.Key_Down, focus: "withContent0"},
349 {key: Qt.Key_Left, focus: "button0"},
350 {key: Qt.Key_Left, focus: "switch0"},
351 {key: Qt.Key_Down, focus: "switch0"},
352 {key: Qt.Key_Right, focus: "button0"},
353 {key: Qt.Key_Right, focus: "withContent0"},
354 {key: Qt.Key_Down, focus: "withContent0"},
355 {key: Qt.Key_Down, focus: "withContent0"},
356 {key: Qt.Key_Backtab, focus: "topItem"},
357 {key: Qt.Key_Tab, focus: "withContent0"},
358 ]},
359 {tag: "Mixed Tab and navigate keys in generic", test: generic, testPlan: [
360 {key: Qt.Key_Tab, focus: "withContent0"},
361 {key: Qt.Key_Tab, focus: "checkbox0"},
362 {key: Qt.Key_Tab, focus: "switch0"},
363 {key: Qt.Key_Right, focus: "button0"},
364 {key: Qt.Key_Right, focus: "withContent0"},
365 {key: Qt.Key_Tab, focus: "checkbox0"},
366 {key: Qt.Key_Left, focus: "withContent0"},
367 {key: Qt.Key_Left, focus: "button0"},
368 {key: Qt.Key_Tab, focus: "withContent1"},
369 ]},
370 ];
371 }
372 function test_pattern(data) {
373 var test = loadTest(data.test);
374 if (test.hasOwnProperty("delegate") && data.delegate) {
375 test.delegate = data.delegate;
376 waitForRendering(test, 500);
377 }
378 for (var i = 0; i < data.testPlan.length; i++) {
379 var plan = data.testPlan[i];
380 keyClick(plan.key);
381 tryCompare(main.activeFocusItem, "activeFocus", true, 200, "Focus not set for " + plan.focus);
382 compare(main.activeFocusItem.objectName, plan.focus);
383 if (main.activeFocusItem.hasOwnProperty("keyNavigationFocus")) {
384 verify(main.activeFocusItem.keyNavigationFocus);
385 }
386 }
387 }
388
389 function test_do_not_focus_on_actions_data() {
390 return [
391 {tag: "leading actions revealed", test: listView, focusItem: "withContent1", leading: true, swipeDx: units.gu(10),
392 key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]},
393 {tag: "trailing actions revealed", test: listView, focusItem: "withContent1", leading: false, swipeDx: -units.gu(10),
394 key: Qt.Key_Left, focus: ["button1", "switch1", "checkbox1", "withContent1"]},
395 ]
396 }
397 function test_do_not_focus_on_actions(data) {
398 var test = loadTest(data.test);
399 if (test.hasOwnProperty("delegate")) {
400 test.delegate = listItemWithContent;
401 waitForRendering(test, 500);
402 }
403 var item = findChild(test, data.focusItem);
404 verify(item);
405 item.forceActiveFocus();
406 tryCompare(item, "activeFocus", true, 500, "Focus hasn't been gained by the ListItem");
407 // swipe in
408 swipe(item, data.leading ? 1 : item.width - 1, centerOf(item).y, data.swipeDx, 0);
409 // compare
410 for (var i = 0; i < data.focus.length; i++) {
411 keyClick(data.key);
412 tryCompare(main.activeFocusItem, "objectName", data.focus[i]);
413 }
414 }
415 }
416}
417
0418
=== modified file 'tests/unit_x11/tst_components/tst_quickutils.qml'
--- tests/unit_x11/tst_components/tst_quickutils.qml 2015-03-03 13:20:06 +0000
+++ tests/unit_x11/tst_components/tst_quickutils.qml 2016-03-01 15:09:23 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright 2012 Canonical Ltd.2 * Copyright 2012-2016 Canonical Ltd.
3 *3 *
4 * This program is free software; you can redistribute it and/or modify4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by5 * it under the terms of the GNU Lesser General Public License as published by
@@ -14,16 +14,33 @@
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/>.
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.4
18import QtTest 1.018import QtTest 1.0
19import Ubuntu.Components 1.119import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.0
2120
22Item {21Item {
23 id: root22 id: root
24 width: units.gu(40)23 width: units.gu(40)
25 height: units.gu(40)24 height: units.gu(40)
2625
26 Column {
27 id: focusGroup
28 Item {
29 objectName: "unfocusableFirst"
30 }
31
32 Repeater {
33 model: 5
34 Item {
35 objectName: index == 0 ? "first" : (index == 4 ? "last" : "item" + index)
36 activeFocusOnTab: true
37 }
38 }
39 Item {
40 objectName: "unfocusableLast"
41 }
42 }
43
27 TestCase {44 TestCase {
28 id: test45 id: test
29 name: "QuickUtilsAPI"46 name: "QuickUtilsAPI"
@@ -39,5 +56,15 @@
39 compare(QuickUtils.className(test), "TestCase", "className for TestCase");56 compare(QuickUtils.className(test), "TestCase", "className for TestCase");
40 compare(QuickUtils.className(root), "QQuickItem", "className for Item");57 compare(QuickUtils.className(root), "QQuickItem", "className for Item");
41 }58 }
59
60 function test_firstFocusableChild()
61 {
62 compare(QuickUtils.firstFocusableChild(focusGroup).objectName, "first");
63 }
64
65 function test_lastFocusableChild()
66 {
67 compare(QuickUtils.lastFocusableChild(focusGroup).objectName, "last");
68 }
42 }69 }
43}70}

Subscribers

People subscribed via source and target branches