Merge lp:~nick-dedekind/ubuntu-ui-toolkit/exclusiveGroup into lp:ubuntu-ui-toolkit/staging

Proposed by Nick Dedekind
Status: Merged
Approved by: Zoltan Balogh
Approved revision: 1353
Merged at revision: 2077
Proposed branch: lp:~nick-dedekind/ubuntu-ui-toolkit/exclusiveGroup
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 1025 lines (+720/-56)
13 files modified
components.api (+14/-3)
src/Ubuntu/Components/1.3/ActionList.qml (+0/-47)
src/Ubuntu/Components/ComponentModule.pro (+0/-1)
src/Ubuntu/Components/qmldir (+0/-1)
src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro (+6/-2)
src/Ubuntu/UbuntuToolkit/actionlist.cpp (+158/-0)
src/Ubuntu/UbuntuToolkit/actionlist_p.h (+58/-0)
src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp (+168/-0)
src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h (+62/-0)
src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp (+4/-0)
src/Ubuntu/UbuntuToolkit/ucaction.cpp (+124/-0)
src/Ubuntu/UbuntuToolkit/ucaction_p.h (+19/-1)
tests/unit/components/tst_action.qml (+107/-1)
To merge this branch: bzr merge lp:~nick-dedekind/ubuntu-ui-toolkit/exclusiveGroup
Reviewer Review Type Date Requested Status
ubuntu-sdk-build-bot continuous-integration Approve
Zoltan Balogh Approve
Zsombor Egri Approve
PS Jenkins bot continuous-integration Pending
Review via email: mp+297921@code.launchpad.net

This proposal supersedes a proposal from 2016-06-07.

Commit message

Introduced Action states & ExclusiveGroup action list

Description of the change

Added Action::state.
Introduced ExclusiveGroup action list.
Moved ActionList to cpp.

To post a comment you must log in.
Revision history for this message
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal

Please submit the MR against staging. And add unit tests pls.

review: Needs Fixing
Revision history for this message
Zoltan Balogh (bzoltan) wrote : Posted in a previous version of this proposal

Please retarget to the staging branch, thank you

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

There are few things to get sorted out still, the code looks good in general.

We are also planning to move all our code to UbuntuToolkit library and UbuntuTookit NS (see https://code.launchpad.net/~zsombi/ubuntu-ui-toolkit/moveStyledItem/+merge/296802), hopefully lands soon so you can move your stuff under the lib.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> There are few things to get sorted out still, the code looks good in general.
>
> We are also planning to move all our code to UbuntuToolkit library and
> UbuntuTookit NS (see https://code.launchpad.net/~zsombi/ubuntu-ui-
> toolkit/moveStyledItem/+merge/296802), hopefully lands soon so you can move
> your stuff under the lib.

I think I've addressed all the above and comments.
I've left the action::checked as a boolean rather than a tristate. On reflection, I believe that tristate should be handled in the implementer (ie Checkbox). Can't really get my head around tristate with Exclusive groups etc, as I don't think it makes much sense.
The mixed state doesn't really involve a "trigger", since it's not selectable.

Feel free to disagree :)

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

Sorry Nick, forgot to mention that UC prefix is no longer mandated. Let's get rid of it here too.

And with that change, we're all done! Thanks a lot!

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> Sorry Nick, forgot to mention that UC prefix is no longer mandated. Let's get
> rid of it here too.
>
> And with that change, we're all done! Thanks a lot!

I've removed the UC prefix.

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

All cool now!!! Thanks Nick!

review: Approve
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Timo Jyrinki (timo-jyrinki) wrote :

This MP seems to have a real failure, not failing only on the armhf where there was faulty phone device doing the CI.

Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

One more try...

review: Approve
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (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: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (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: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (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: Needs Fixing (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: Needs Fixing (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: Needs Fixing (continuous-integration)
Revision history for this message
Zoltan Balogh (bzoltan) :
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: Needs Fixing (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: Needs Fixing (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)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'components.api'
2--- components.api 2016-08-09 03:48:53 +0000
3+++ components.api 2016-08-22 08:24:13 +0000
4@@ -10,13 +10,17 @@
5 readonly property bool pressed
6 readonly property UCMargins sensingMargins
7 Ubuntu.Components.Action 1.3 1.0 0.1 UCAction: QtObject
8+ property bool checkable 1.3
9+ property bool checked 1.3
10 property string description
11 property bool enabled
12+ property ExclusiveGroup exclusiveGroup 1.3
13 property string iconName
14 property url iconSource
15 property Component itemHint
16 property string keywords
17 signal triggered(var value)
18+ signal toggled(bool value) 1.3
19 function trigger(var value)
20 function trigger()
21 property string name
22@@ -59,9 +63,12 @@
23 Ubuntu.Components.ActionList 1.0 0.1: QtObject
24 property list<Action> actions
25 default property list<Action> children
26-Ubuntu.Components.ActionList 1.3: QtObject
27- property list<Action> actions
28- default property list<Action> children
29+Ubuntu.Components.ActionList 1.3 ActionList: QtObject
30+ default property list<Action> actions
31+ signal added(Action action)
32+ signal removed(Action action)
33+ function addAction(Action action)
34+ function removeAction(Action action)
35 Ubuntu.Components.ActionManager 1.0 0.1 UCActionManager: QtObject
36 default property list<Action> actions
37 readonly property ActionContext globalContext
38@@ -458,6 +465,10 @@
39 OperationPending
40 Ubuntu.Metrics.Event: Enum
41 UserInterfaceReady
42+Ubuntu.Components.ExclusiveGroup 1.3 ExclusiveGroup: ActionList
43+ readonly property QtObject current
44+ function bindCheckable(QtObject object)
45+ function unbindCheckable(QtObject object)
46 Ubuntu.Components.ListItems.Expandable 1.0 0.1: Empty
47 property bool collapseOnClick
48 property double collapsedHeight
49
50=== removed file 'src/Ubuntu/Components/1.3/ActionList.qml'
51--- src/Ubuntu/Components/1.3/ActionList.qml 2016-05-25 12:48:10 +0000
52+++ src/Ubuntu/Components/1.3/ActionList.qml 1970-01-01 00:00:00 +0000
53@@ -1,47 +0,0 @@
54-/*
55- * Copyright 2012 Canonical Ltd.
56- *
57- * This program is free software; you can redistribute it and/or modify
58- * it under the terms of the GNU Lesser General Public License as published by
59- * the Free Software Foundation; version 3.
60- *
61- * This program is distributed in the hope that it will be useful,
62- * but WITHOUT ANY WARRANTY; without even the implied warranty of
63- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64- * GNU Lesser General Public License for more details.
65- *
66- * You should have received a copy of the GNU Lesser General Public License
67- * along with this program. If not, see <http://www.gnu.org/licenses/>.
68- */
69-
70-import QtQuick 2.4
71-import Ubuntu.Components 1.3
72-
73-/*!
74- \qmltype ActionList
75- \inqmlmodule Ubuntu.Components
76- \ingroup ubuntu
77- \brief List of \l Action items
78-*/
79-
80-QtObject {
81- id: list
82- // internal objects using nested elements,
83- // which isn't allowed by QtObject; this fix makes this possible
84- /*!
85- Default property to allow adding of children.
86- \qmlproperty list<Action> children
87- \default
88- */
89- default property alias children: list.actions
90-
91- /*!
92- List of already defined actions when not defining them as children of the ActionList.
93- Note that when you set this property, the children of the ActionList will be ignored,
94- so do not set the list and define children.
95-
96- The advantage of setting actions over using the children is that the same
97- \l Action items can be used in several sets of actions.
98- */
99- property list<Action> actions
100-}
101
102=== modified file 'src/Ubuntu/Components/ComponentModule.pro'
103--- src/Ubuntu/Components/ComponentModule.pro 2016-07-29 13:21:05 +0000
104+++ src/Ubuntu/Components/ComponentModule.pro 2016-08-22 08:24:13 +0000
105@@ -78,7 +78,6 @@
106
107 #1.3
108 QML_FILES += 1.3/ActionBar.qml \
109- 1.3/ActionList.qml \
110 1.3/ActivityIndicator.qml \
111 1.3/AdaptivePageLayout.qml \
112 1.3/AnimatedItem.qml \
113
114=== modified file 'src/Ubuntu/Components/qmldir'
115--- src/Ubuntu/Components/qmldir 2016-07-29 13:21:05 +0000
116+++ src/Ubuntu/Components/qmldir 2016-08-22 08:24:13 +0000
117@@ -96,7 +96,6 @@
118 #################################################
119 #version 1.3
120 ActionBar 1.3 1.3/ActionBar.qml
121-ActionList 1.3 1.3/ActionList.qml
122 AdaptivePageLayout 1.3 1.3/AdaptivePageLayout.qml
123 PageColumnsLayout 1.3 1.3/PageColumnsLayout.qml
124 PageColumn 1.3 1.3/PageColumn.qml
125
126=== modified file 'src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro'
127--- src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-04 17:25:09 +0000
128+++ src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-22 08:24:13 +0000
129@@ -149,7 +149,9 @@
130 privates/appheaderbase_p.h \
131 label_p.h \
132 ucbottomedgeregion_p_p.h \
133- privates/ucscrollbarutils_p.h
134+ privates/ucscrollbarutils_p.h \
135+ actionlist_p.h \
136+ exclusivegroup_p.h
137
138 SOURCES += \
139 uctheme.cpp \
140@@ -226,7 +228,9 @@
141 privates/ucpagewrapper.cpp \
142 privates/ucpagewrapperincubator.cpp \
143 privates/appheaderbase.cpp \
144- privates/ucscrollbarutils.cpp
145+ privates/ucscrollbarutils.cpp \
146+ actionlist.cpp \
147+ exclusivegroup.cpp
148
149 # adapters
150 SOURCES += $$PWD/adapters/alarmsadapter_organizer.cpp
151
152=== added file 'src/Ubuntu/UbuntuToolkit/actionlist.cpp'
153--- src/Ubuntu/UbuntuToolkit/actionlist.cpp 1970-01-01 00:00:00 +0000
154+++ src/Ubuntu/UbuntuToolkit/actionlist.cpp 2016-08-22 08:24:13 +0000
155@@ -0,0 +1,158 @@
156+/*
157+ * Copyright 2016 Canonical Ltd.
158+ *
159+ * This program is free software; you can redistribute it and/or modify
160+ * it under the terms of the GNU Lesser General Public License as published by
161+ * the Free Software Foundation; version 3.
162+ *
163+ * This program is distributed in the hope that it will be useful,
164+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
165+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166+ * GNU Lesser General Public License for more details.
167+ *
168+ * You should have received a copy of the GNU Lesser General Public License
169+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
170+ */
171+
172+#include "actionlist_p.h"
173+#include "ucaction_p.h"
174+
175+UT_NAMESPACE_BEGIN
176+
177+/*!
178+ * \qmltype ActionList
179+ * \inqmlmodule Ubuntu.Components
180+ * \ingroup ubuntu
181+ * \brief List of \l Action items
182+ * An ActionList provies a way of grouping actions together.
183+ * \qml
184+ * ActionList {
185+ * Action {
186+ * id: action1
187+ * }
188+ * Action {
189+ * id: action2
190+ * }
191+ * }
192+ * \endqml
193+ */
194+/*!
195+ * \qmlsignal ActionList::added(Action action)
196+ * \since Ubuntu.Components 1.3
197+ * Signal called when an action is added to the list
198+ */
199+/*!
200+ * \qmlsignal ActionList::removed(Action action)
201+ * \since Ubuntu.Components 1.3
202+ * Signal called when an action is removed from the list
203+ */
204+ActionList::ActionList(QObject *parent)
205+ : QObject(parent)
206+{
207+}
208+
209+/*!
210+ * \qmlmethod ActionList::addAction(Action action)
211+ * \since Ubuntu.Components 1.3
212+ * Adds an Action to the list programatically.
213+ * \qml
214+ * Item {
215+ * Instantiator {
216+ * model: 4
217+ * onObjectAdded: actionList.addAction(object)
218+ * onObjectRemoved: actionList.removeAction(object)
219+ *
220+ * Action {}
221+ * }
222+ *
223+ * ActionList {
224+ * id: actionList
225+ * }
226+ * }
227+ * \endqml
228+ * \sa ActionList::removeAction
229+ */
230+void ActionList::addAction(UCAction *action)
231+{
232+ if (m_actions.contains(action)) {
233+ return;
234+ }
235+ m_actions.append(action);
236+ Q_EMIT added(action);
237+}
238+
239+/*!
240+ * \qmlmethod ActionList::removeAction(Action action)
241+ * \since Ubuntu.Components 1.3
242+ * Removes an action from the list programatically.
243+ * \sa ActionList::addAction
244+ */
245+void ActionList::removeAction(UCAction *action)
246+{
247+ if (!action) {
248+ return;
249+ }
250+ if (m_actions.removeOne(action)) {
251+ Q_EMIT removed(action);
252+ }
253+}
254+
255+/*!
256+ * \qmlproperty list<Action> ActionList::actions
257+ * \default
258+ * List of Actions in this ActionList
259+ * Note that when you set this property, the children of the ActionList will be ignored,
260+ * so do not set the list and define children.
261+ *
262+ * The advantage of setting actions over using the children is that the same
263+ * \l Action items can be used in several sets of actions.
264+ */
265+QQmlListProperty<UCAction> ActionList::actions()
266+{
267+ return QQmlListProperty<UCAction>(this, 0,
268+ ActionList::append,
269+ ActionList::count,
270+ ActionList::at,
271+ ActionList::clear);
272+}
273+
274+const QList<UCAction*> &ActionList::list() const
275+{
276+ return m_actions;
277+}
278+
279+void ActionList::append(QQmlListProperty<UCAction> *list, UCAction *action)
280+{
281+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
282+ if (actionList) {
283+ actionList->addAction(action);
284+ }
285+}
286+
287+void ActionList::clear(QQmlListProperty<UCAction> *list)
288+{
289+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
290+ if (actionList) {
291+ actionList->m_actions.clear();
292+ }
293+}
294+
295+UCAction* ActionList::at(QQmlListProperty<UCAction> *list, int index)
296+{
297+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
298+ if (actionList) {
299+ return actionList->m_actions.value(index, nullptr);
300+ }
301+ return Q_NULLPTR;
302+}
303+
304+int ActionList::count(QQmlListProperty<UCAction> *list)
305+{
306+ ActionList *actionList = qobject_cast<ActionList*>(list->object);
307+ if (actionList) {
308+ return actionList->m_actions.count();
309+ }
310+ return 0;
311+}
312+
313+UT_NAMESPACE_END
314
315=== added file 'src/Ubuntu/UbuntuToolkit/actionlist_p.h'
316--- src/Ubuntu/UbuntuToolkit/actionlist_p.h 1970-01-01 00:00:00 +0000
317+++ src/Ubuntu/UbuntuToolkit/actionlist_p.h 2016-08-22 08:24:13 +0000
318@@ -0,0 +1,58 @@
319+/*
320+ * Copyright 2016 Canonical Ltd.
321+ *
322+ * This program is free software; you can redistribute it and/or modify
323+ * it under the terms of the GNU Lesser General Public License as published by
324+ * the Free Software Foundation; version 3.
325+ *
326+ * This program is distributed in the hope that it will be useful,
327+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
328+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
329+ * GNU Lesser General Public License for more details.
330+ *
331+ * You should have received a copy of the GNU Lesser General Public License
332+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
333+ */
334+
335+#ifndef ACTIONLIST_H
336+#define ACTIONLIST_H
337+
338+#include <QObject>
339+#include <QtQml/QQmlListProperty>
340+#include "ubuntutoolkitglobal.h"
341+
342+UT_NAMESPACE_BEGIN
343+
344+class UCAction;
345+class UBUNTUTOOLKIT_EXPORT ActionList : public QObject
346+{
347+ Q_OBJECT
348+ Q_PROPERTY(QQmlListProperty<UT_PREPEND_NAMESPACE(UCAction)> actions READ actions)
349+ Q_CLASSINFO("DefaultProperty", "actions")
350+public:
351+ explicit ActionList(QObject *parent = 0);
352+
353+ QQmlListProperty<UCAction> actions();
354+
355+ const QList<UCAction*> &list() const;
356+
357+public Q_SLOTS:
358+ void addAction(UT_PREPEND_NAMESPACE(UCAction) *action);
359+ void removeAction(UT_PREPEND_NAMESPACE(UCAction) *action);
360+
361+Q_SIGNALS:
362+ void added(UCAction *action);
363+ void removed(UCAction *action);
364+
365+protected:
366+ QList<UCAction*> m_actions;
367+
368+ static void append(QQmlListProperty<UCAction> *list, UCAction *action);
369+ static void clear(QQmlListProperty<UCAction> *list);
370+ static UCAction* at(QQmlListProperty<UCAction> *list, int index);
371+ static int count(QQmlListProperty<UCAction> *list);
372+};
373+
374+UT_NAMESPACE_END
375+
376+#endif // ACTIONLIST_H
377
378=== added file 'src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp'
379--- src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp 1970-01-01 00:00:00 +0000
380+++ src/Ubuntu/UbuntuToolkit/exclusivegroup.cpp 2016-08-22 08:24:13 +0000
381@@ -0,0 +1,168 @@
382+/*
383+ * Copyright 2016 Canonical Ltd.
384+ *
385+ * This program is free software; you can redistribute it and/or modify
386+ * it under the terms of the GNU Lesser General Public License as published by
387+ * the Free Software Foundation; version 3.
388+ *
389+ * This program is distributed in the hope that it will be useful,
390+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
391+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
392+ * GNU Lesser General Public License for more details.
393+ *
394+ * You should have received a copy of the GNU Lesser General Public License
395+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
396+ *
397+ */
398+
399+#include "exclusivegroup_p.h"
400+#include "ucaction_p.h"
401+
402+#include <QSignalMapper>
403+
404+#define CHECKED_PROPERTY "checked"
405+
406+UT_NAMESPACE_BEGIN
407+
408+static const char *checkableSignals[] = {
409+ "toggled(bool)",
410+ 0
411+};
412+
413+static bool isChecked(const QObject *o)
414+{
415+ if (!o) return false;
416+ QVariant checkedVariant = o->property(CHECKED_PROPERTY);
417+ return checkedVariant.isValid() && checkedVariant.toBool();
418+}
419+
420+/*!
421+ * \qmltype ExclusiveGroup
422+ * \inqmlmodule Ubuntu.Components
423+ * \since Ubuntu.Components 1.3
424+ * \ingroup ubuntu
425+ * \inherits ActionList
426+ * \brief ExclusiveGroup provides a way to declare several checkable controls as mutually exclusive.
427+ *
428+ * The ExclusiveGroup will only allow a single object to have it's checkable property set to "true"
429+ * at any one time. The exclusive group accepts child Actions, but objects other than Actions can be
430+ * used by using the \l bindCheckable function as long as they support one of the required signals,
431+ * and a "checked" property.
432+ * \qml
433+ * ExclusiveGroup {
434+ * Action {
435+ * parameterType: Action.Bool
436+ * state: true
437+ * }
438+ * Action {
439+ * parameterType: Action.Bool
440+ * state: false
441+ * }
442+ * }
443+ * \endqml
444+ */
445+ExclusiveGroup::ExclusiveGroup(QObject *parent)
446+ : ActionList(parent)
447+ , m_signalMapper(new QSignalMapper(this))
448+ , m_entranceGuard(false)
449+{
450+ connect(this, &ActionList::added, this, &ExclusiveGroup::onActionAdded);
451+ connect(this, &ActionList::removed, this, &ExclusiveGroup::onActionRemoved);
452+
453+ int index = m_signalMapper->metaObject()->indexOfMethod("map()");
454+ m_updateCurrentMethod = m_signalMapper->metaObject()->method(index);
455+ connect(m_signalMapper, static_cast<void(QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, [this](QObject *object) {
456+ if (isChecked(object)) {
457+ setCurrent(object);
458+ }
459+ });
460+}
461+
462+void ExclusiveGroup::onActionAdded(UCAction *action)
463+{
464+ action->setExclusiveGroup(this);
465+}
466+
467+void ExclusiveGroup::onActionRemoved(UCAction *action)
468+{
469+ action->setExclusiveGroup(nullptr);
470+}
471+
472+/*!
473+ * \qmlproperty Action ExclusiveGroup::current
474+ * Returns the currently checked action
475+ */
476+void ExclusiveGroup::setCurrent(QObject *object)
477+{
478+ if (m_current == object)
479+ return;
480+
481+ if (m_current)
482+ m_current->setProperty(CHECKED_PROPERTY, QVariant(false));
483+ m_current = object;
484+ if (m_current)
485+ m_current->setProperty(CHECKED_PROPERTY, QVariant(true));
486+ Q_EMIT currentChanged();
487+}
488+
489+QObject *ExclusiveGroup::current() const
490+{
491+ return m_current.data();
492+}
493+
494+/*!
495+ * \qmlmethod void ExclusiveGroup::bindCheckable(object object)
496+ * Explicitly bind an objects checkability to this exclusive group.
497+ * \note This only works with objects which support the following signals signals:
498+ * \list
499+ * \li \b toggled(bool)
500+ * \endlist
501+ * \qml
502+ * Item {
503+ * ExclusiveGroup {
504+ * id: exclusiveGroup
505+ * }
506+ * Instantiator {
507+ * model: 4
508+ * onObjectAdded: exclusiveGroup.bindCheckable(object)
509+ * onObjectRemoved: exclusiveGroup.unbindCheckable(object)
510+ *
511+ * Action {
512+ * checkable: true
513+ * }
514+ * }
515+ * }
516+ * \endqml
517+ * \sa ExclusiveGroup::unbindCheckable
518+ */
519+void ExclusiveGroup::bindCheckable(QObject *object)
520+{
521+ for (const char **signalName = checkableSignals; *signalName; signalName++) {
522+ int signalIndex = object->metaObject()->indexOfSignal(*signalName);
523+ if (signalIndex != -1) {
524+ QMetaMethod signalMethod = object->metaObject()->method(signalIndex);
525+ connect(object, signalMethod, m_signalMapper, m_updateCurrentMethod, Qt::UniqueConnection);
526+ m_signalMapper->setMapping(object, object);
527+ connect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unbindCheckable(QObject*)), Qt::UniqueConnection);
528+ if (!m_current && isChecked(object))
529+ setCurrent(object);
530+ break;
531+ }
532+ }
533+}
534+
535+/*!
536+ * \qmlmethod void ExclusiveGroup::unbindCheckable(object object)
537+ * Explicitly unbind an objects checkability from this exclusive group.
538+ * \sa ExclusiveGroup::bindCheckable
539+ */
540+void ExclusiveGroup::unbindCheckable(QObject *object)
541+{
542+ if (m_current == object)
543+ setCurrent(0);
544+
545+ disconnect(object, 0, m_signalMapper, 0);
546+ disconnect(object, SIGNAL(destroyed(QObject*)), this, SLOT(unbindCheckable(QObject*)));
547+}
548+
549+UT_NAMESPACE_END
550
551=== added file 'src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h'
552--- src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h 1970-01-01 00:00:00 +0000
553+++ src/Ubuntu/UbuntuToolkit/exclusivegroup_p.h 2016-08-22 08:24:13 +0000
554@@ -0,0 +1,62 @@
555+/*
556+ * Copyright 2016 Canonical Ltd.
557+ *
558+ * This program is free software; you can redistribute it and/or modify
559+ * it under the terms of the GNU Lesser General Public License as published by
560+ * the Free Software Foundation; version 3.
561+ *
562+ * This program is distributed in the hope that it will be useful,
563+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
564+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
565+ * GNU Lesser General Public License for more details.
566+ *
567+ * You should have received a copy of the GNU Lesser General Public License
568+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
569+ *
570+ */
571+
572+#ifndef EXCLUSIVEGROUP_H
573+#define EXCLUSIVEGROUP_H
574+
575+#include "actionlist_p.h"
576+#include "ubuntutoolkitglobal.h"
577+
578+#include <QMetaMethod>
579+#include <QPointer>
580+
581+class QSignalMapper;
582+
583+UT_NAMESPACE_BEGIN
584+
585+class UBUNTUTOOLKIT_EXPORT ExclusiveGroup : public ActionList
586+{
587+ Q_OBJECT
588+ Q_PROPERTY(QObject* current READ current NOTIFY currentChanged)
589+
590+public:
591+ explicit ExclusiveGroup(QObject *parent = 0);
592+
593+ QObject* current() const;
594+
595+ Q_INVOKABLE void bindCheckable(QObject* object);
596+ Q_INVOKABLE void unbindCheckable(QObject* object);
597+
598+Q_SIGNALS:
599+ void currentChanged();
600+
601+protected Q_SLOTS:
602+ void onActionAdded(UCAction* action);
603+ void onActionRemoved(UCAction* action);
604+
605+private:
606+ void setCurrent(QObject* action);
607+
608+ QSignalMapper* m_signalMapper;
609+ QPointer<QObject> m_current;
610+ QMetaMethod m_updateCurrentMethod;
611+ bool m_entranceGuard;
612+};
613+
614+UT_NAMESPACE_END
615+
616+#endif // EXCLUSIVEGROUP_H
617
618=== modified file 'src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp'
619--- src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-06 00:55:32 +0000
620+++ src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-22 08:24:13 +0000
621@@ -89,6 +89,8 @@
622 #include <privates/ucpagewrapper_p.h>
623 #include <privates/appheaderbase_p.h>
624 #include <privates/ucscrollbarutils_p.h>
625+#include <actionlist_p.h>
626+#include <exclusivegroup_p.h>
627
628 // styles
629 #include <ucbottomedgestyle_p.h>
630@@ -374,6 +376,8 @@
631 qmlRegisterType<UCPageTreeNode>(uri, 1, 3, "PageTreeNode");
632 qmlRegisterType<UCPopupContext>(uri, 1, 3, "PopupContext");
633 qmlRegisterType<UCMainViewBase>(uri, 1, 3, "MainViewBase");
634+ qmlRegisterType<ActionList>(uri, 1, 3, "ActionList");
635+ qmlRegisterType<ExclusiveGroup>(uri, 1, 3, "ExclusiveGroup");
636 }
637
638 void UbuntuToolkitModule::undefineModule()
639
640=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction.cpp'
641--- src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-07-07 07:21:48 +0000
642+++ src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-08-22 08:24:13 +0000
643@@ -17,6 +17,7 @@
644 #include "ucaction_p.h"
645 #include "quickutils_p.h"
646 #include "ucactioncontext_p.h"
647+#include "exclusivegroup_p.h"
648
649 #include <QtDebug>
650 #include <QtQml/QQmlInfo>
651@@ -149,6 +150,18 @@
652 * text: "&Call"
653 * }
654 * \endqml
655+ *
656+ * \section2 Checkable property
657+ * Since Ubuntu.Components 1.3 Action supports the checkable/checked properties.
658+ * \qml
659+ * Button {
660+ * action: Action {
661+ * checkable: true
662+ * checked: false
663+ * }
664+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
665+ * }
666+ * \endqml
667 */
668
669 /*!
670@@ -160,6 +173,14 @@
671 */
672
673 /*!
674+ * \qmlsignal Action::toggled(bool value)
675+ * Signal called when the action's checked property changes.
676+ * \note The toggled signal should be used for checkable actions rather than the
677+ * triggered signal.
678+ * \sa Action::checkable, Action::checked, ExclusiveGroup
679+ */
680+
681+/*!
682 * \qmlproperty string Action::description
683 * User visible secondary description for the action. Description is more verbose
684 * than the \l text and should describe the Action with couple of words.
685@@ -289,12 +310,15 @@
686
687 UCAction::UCAction(QObject *parent)
688 : QObject(parent)
689+ , m_exclusiveGroup(Q_NULLPTR)
690 , m_itemHint(Q_NULLPTR)
691 , m_parameterType(None)
692 , m_factoryIconSource(true)
693 , m_enabled(true)
694 , m_visible(true)
695 , m_published(false)
696+ , m_checkable(false)
697+ , m_checked(false)
698 {
699 generateName();
700 // FIXME: we need QInputDeviceInfo to detect the keyboard attechment
701@@ -436,6 +460,101 @@
702 Q_EMIT shortcutChanged();
703 }
704
705+/*!
706+ * \qmlproperty bool Action::checkable
707+ * \since Ubuntu.Components 1.3
708+ * Whether the action can be checked. Defaults to false.
709+ * \sa Action::checked, Action::toggled, ExclusiveGroup
710+ */
711+void UCAction::setCheckable(bool checkable)
712+{
713+ if (m_checkable == checkable) {
714+ return;
715+ }
716+ m_checkable = checkable;
717+ Q_EMIT checkableChanged();
718+
719+ // If the Action is already checked, assert the check state.
720+ if (m_checked)
721+ Q_EMIT toggled(m_checkable);
722+}
723+
724+/*!
725+ * \qmlproperty bool Action::checked
726+ * \since Ubuntu.Components 1.3
727+ * If the action is checkable, this property reflects its checked state. Defaults to false.
728+ * Its value is also false while checkable is false.
729+ * \sa Action::checkable, Action::toggled, ExclusiveGroup
730+ */
731+void UCAction::setChecked(bool checked)
732+{
733+ if (m_checked == checked) {
734+ return;
735+ }
736+ m_checked = checked;
737+
738+ if (m_checkable) {
739+ Q_EMIT toggled(checked);
740+ }
741+}
742+
743+/*!
744+ * \qmlproperty ExclusiveGroup Action::exclusiveGroup
745+ * \since Ubuntu.Components 1.3
746+ * The \l ExclusiveGroup associated with this action.
747+ * An exclusive group allows the \l checked property to belinked to other actions,
748+ * as in radio controls.
749+ * \qml
750+ * Column {
751+ * ExclusiveGroup {
752+ * Action {
753+ * id: action1
754+ * checkable: true
755+ * checked: true
756+ * }
757+ * Action {
758+ * id: action2
759+ * checkable: true
760+ * }
761+ * Action {
762+ * id: action3
763+ * checkable: true
764+ * }
765+ * }
766+ *
767+ * Button {
768+ * action: action1
769+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
770+ * }
771+ * Button {
772+ * action: action2
773+ * color: action.checked ? UbuntuColor.green : UbuntuColor.red
774+ * }
775+ * Button {
776+ * action: action3
777+ * color: action.checked ? UbuntuColor.green : UbuntuColor.grey
778+ * }
779+ * }
780+ * \endqml
781+ */
782+void UCAction::setExclusiveGroup(ExclusiveGroup *exclusiveGroup)
783+{
784+ if (m_exclusiveGroup == exclusiveGroup) {
785+ return;
786+ }
787+
788+ if (m_exclusiveGroup) {
789+ m_exclusiveGroup->unbindCheckable(this);
790+ }
791+
792+ m_exclusiveGroup = exclusiveGroup;
793+
794+ if (m_exclusiveGroup) {
795+ m_exclusiveGroup->bindCheckable(this);
796+ }
797+ Q_EMIT exclusiveGroupChanged();
798+}
799+
800 bool UCAction::event(QEvent *event)
801 {
802 if (event->type() != QEvent::Shortcut)
803@@ -475,6 +594,11 @@
804 if (!m_enabled) {
805 return;
806 }
807+
808+ if (m_checkable && !(m_checked && m_exclusiveGroup)) {
809+ setChecked(!m_checked);
810+ }
811+
812 if (!isValidType(value.type())) {
813 Q_EMIT triggered(QVariant());
814 } else {
815
816=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction_p.h'
817--- src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-07-07 07:21:48 +0000
818+++ src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-08-22 08:24:13 +0000
819@@ -53,7 +53,7 @@
820 }
821 }
822
823-class UCActionAttached;
824+class ExclusiveGroup;
825 class UBUNTUTOOLKIT_EXPORT UCAction : public QObject
826 {
827 Q_OBJECT
828@@ -68,6 +68,10 @@
829 Q_PROPERTY(bool enabled MEMBER m_enabled NOTIFY enabledChanged)
830 Q_PROPERTY(Type parameterType MEMBER m_parameterType NOTIFY parameterTypeChanged)
831
832+ Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY checkableChanged REVISION 1)
833+ Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled REVISION 1)
834+ Q_PROPERTY(ExclusiveGroup* exclusiveGroup READ exclusiveGroup WRITE setExclusiveGroup NOTIFY exclusiveGroupChanged REVISION 1)
835+
836 // Toolkit Actions API
837 Q_PROPERTY(QUrl iconSource MEMBER m_iconSource WRITE setIconSource NOTIFY iconSourceChanged)
838 Q_PROPERTY(bool visible MEMBER m_visible NOTIFY visibleChanged)
839@@ -113,6 +117,13 @@
840 void setItemHint(QQmlComponent *);
841 void setShortcut(const QVariant&);
842 void resetShortcut();
843+ void setCheckable(bool checkable);
844+ bool isCheckable() const { return m_checkable; }
845+ void setChecked(bool checked);
846+ bool isChecked() const { return m_checkable && m_checked; }
847+
848+ ExclusiveGroup *exclusiveGroup() const { return m_exclusiveGroup; }
849+ void setExclusiveGroup(ExclusiveGroup *exclusiveGroup);
850
851 Q_SIGNALS:
852 void nameChanged();
853@@ -125,13 +136,18 @@
854 void iconSourceChanged();
855 void visibleChanged();
856 void shortcutChanged();
857+ Q_REVISION(1) void checkableChanged();
858+ Q_REVISION(1) void exclusiveGroupChanged();
859+
860 void triggered(const QVariant &value);
861+ Q_REVISION(1) void toggled(bool value);
862
863 public Q_SLOTS:
864 void trigger(const QVariant &value = QVariant());
865
866 private:
867 QPODVector<QQuickItem*, 4> m_owningItems;
868+ ExclusiveGroup *m_exclusiveGroup;
869 QString m_name;
870 QString m_text;
871 QString m_iconName;
872@@ -146,6 +162,8 @@
873 bool m_enabled:1;
874 bool m_visible:1;
875 bool m_published:1;
876+ bool m_checkable:1;
877+ bool m_checked:1;
878
879 friend class UCActionContext;
880 friend class UCActionItem;
881
882=== modified file 'tests/unit/components/tst_action.qml'
883--- tests/unit/components/tst_action.qml 2015-12-22 14:42:59 +0000
884+++ tests/unit/components/tst_action.qml 2016-08-22 08:24:13 +0000
885@@ -16,7 +16,7 @@
886
887 import QtQuick 2.0
888 import QtTest 1.0
889-import Ubuntu.Components 1.1
890+import Ubuntu.Components 1.3
891
892 TestCase {
893 name: "ActionAPI"
894@@ -39,6 +39,12 @@
895 triggeredSignalSpy.clear();
896 context1.active = false;
897 context2.active = false;
898+
899+ checkableAction.checkable = true;
900+ checkableAction.checked = false;
901+ action1.checked = true;
902+ currentActionSpy.clear();
903+ checkableSpy.clear();
904 }
905
906 function initTestCase() {
907@@ -166,6 +172,60 @@
908 compare(data.action.invoked, data.invoked);
909 }
910
911+ function test_checkable() {
912+ checkableAction.checkable = true;
913+ checkableAction.checked = true;
914+ checkableSpy.wait();
915+ compare(checkableAction.checked, true, "Checkable action should be checked");
916+ }
917+
918+ function test_not_checkable() {
919+ checkableAction.checkable = false;
920+ checkableAction.checked = true;
921+ compare(checkableAction.checked, false, "Non-checkable action should never be checked");
922+ }
923+
924+ function test_actionlist() {
925+ verify(actionList.actions.length, 2, "Default actions not added to actionList");
926+ }
927+
928+ function test_actionlist_dynamic_actions() {
929+ actionList.addAction(dynamicListAction);
930+ verify(actionList.actions.length, 3, "Dynamic action not added to actionList");
931+ actionList.removeAction(dynamicListAction);
932+ verify(actionList.actions.length, 2, "Dynamic action not remove from actionList");
933+ }
934+
935+ function test_exclusive_group() {
936+ compare(exclusiveGroup.actions.length, 3, "Incorrect number of actions");
937+ }
938+
939+ function test_exclusive_group_activation_data() {
940+ return [
941+ {tag: "Activate action2", active: [action2], inactive: [action1, action3], current: action2},
942+ {tag: "Activate action3", active: [action3], inactive: [action1, action2], current: action3},
943+ {tag: "Activate action2, action3", active: [action2, action3], inactive: [action1, action2], current: action3},
944+ ];
945+ }
946+ function test_exclusive_group_activation(data) {
947+ for (var i = 0; i < data.active.length; i++) {
948+ data.active[i].trigger();
949+ compare(data.active[i].checked, true, "Active action checked property should be 'true'");
950+ }
951+ for (var i = 0; i < data.inactive.length; i++) {
952+ compare(data.inactive[i].checked, false, "Inactive action checked property should be 'false'");
953+ }
954+ currentActionSpy.wait();
955+ compare(exclusiveGroup.current, data.current, "Current action in exclusiveGroup does not match");
956+ }
957+
958+ function test_always_one_action_selected() {
959+ action1.trigger();
960+ compare(action1.checked, true, "Triggering an exclusive group action should check the action");
961+ action1.trigger();
962+ compare(action1.checked, true, "Triggering an exclusive group action again will not uncheck the action.");
963+ }
964+
965 Action {
966 id: action
967 }
968@@ -197,6 +257,16 @@
969 target: action
970 signalName: "textChanged"
971 }
972+ SignalSpy {
973+ id: checkableSpy
974+ target: checkableAction
975+ signalName: "toggled"
976+ }
977+ SignalSpy {
978+ id: currentActionSpy
979+ target: exclusiveGroup
980+ signalName: "currentChanged"
981+ }
982
983 ActionManager {
984 id: manager
985@@ -236,4 +306,40 @@
986 id: testItem
987 }
988
989+ Action {
990+ id: checkableAction
991+ checkable: true
992+ }
993+
994+ ActionList {
995+ id: actionList
996+ Action {
997+ }
998+ Action {
999+ }
1000+ }
1001+
1002+ Action {
1003+ id: dynamicListAction
1004+ }
1005+
1006+ ExclusiveGroup {
1007+ id: exclusiveGroup
1008+ Action {
1009+ id: action1
1010+ checkable: true
1011+ checked: true
1012+ }
1013+ Action {
1014+ id: action2
1015+ checkable: true
1016+ checked: false
1017+ }
1018+ Action {
1019+ id: action3
1020+ checkable: true
1021+ checked: false
1022+ }
1023+ }
1024+
1025 }

Subscribers

People subscribed via source and target branches