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

Proposed by Zsombor Egri
Status: Rejected
Rejected by: Zsombor Egri
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/contextualActions
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~zsombi/ubuntu-ui-toolkit/cppAbstractButton
Diff against target: 2140 lines (+1070/-194)
32 files modified
components.api (+7/-2)
src/Ubuntu/Components/1.2/Page10.qml (+10/-15)
src/Ubuntu/Components/1.3/ActionBar.qml (+6/-0)
src/Ubuntu/Components/1.3/AdaptivePageLayout.qml (+8/-0)
src/Ubuntu/Components/1.3/Page.qml (+21/-3)
src/Ubuntu/Components/1.3/Sections.qml (+10/-1)
src/Ubuntu/Components/Popups/1.2/Popover.qml (+2/-1)
src/Ubuntu/Components/Popups/1.3/Dialog.qml (+56/-39)
src/Ubuntu/Components/Popups/1.3/Popover.qml (+12/-1)
src/Ubuntu/Components/plugin/adapters/actionsproxy_p.cpp (+43/-31)
src/Ubuntu/Components/plugin/adapters/actionsproxy_p.h (+5/-2)
src/Ubuntu/Components/plugin/plugin.cpp (+1/-0)
src/Ubuntu/Components/plugin/ucaction.cpp (+145/-19)
src/Ubuntu/Components/plugin/ucaction.h (+19/-2)
src/Ubuntu/Components/plugin/ucactioncontext.cpp (+171/-25)
src/Ubuntu/Components/plugin/ucactioncontext.h (+27/-5)
src/Ubuntu/Components/plugin/ucactionitem.cpp (+8/-0)
src/Ubuntu/Components/plugin/ucactionitem.h (+3/-2)
src/Ubuntu/Components/plugin/ucactionmanager.cpp (+8/-12)
src/Ubuntu/Components/plugin/uclistitem.cpp (+26/-3)
src/Ubuntu/Components/plugin/uclistitem.h (+1/-0)
src/Ubuntu/Components/plugin/uclistitem_p.h (+2/-0)
src/Ubuntu/Components/plugin/uclistitemactions.cpp (+14/-0)
src/Ubuntu/Components/plugin/uclistitemactions_p.h (+2/-0)
src/Ubuntu/Components/plugin/ucviewitemsattached.cpp (+20/-0)
tests/unit/tst_components/tst_action.qml (+17/-16)
tests/unit/tst_components/tst_actionitem.qml (+5/-0)
tests/unit_x11/tst_components/tst_abstractbutton13.qml (+2/-1)
tests/unit_x11/tst_components/tst_action13.qml (+215/-0)
tests/unit_x11/tst_components/tst_actioncontext.qml (+181/-0)
tests/unit_x11/tst_components/tst_sections.qml (+1/-1)
tests/unit_x11/tst_components/tst_shortcuts.qml (+22/-13)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/contextualActions
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Ubuntu SDK team Pending
Review via email: mp+268703@code.launchpad.net

This proposal supersedes a proposal from 2015-08-12.

Commit message

Actions to be activated by ActionContext. Introducing overlay ActionContexts. Actions can be triggered only if active.

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: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1621. By Zsombor Egri

add action to the closest ActionContext when set in ActionItem

1622. By Zsombor Egri

add ActionContext to Sections

1623. By Zsombor Egri

ViewItems attached object detects ancestor ActionContext, if none found, creates one. Needed to handle actions from leading/trailing and the main ListItem action

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

small cleanup

1625. By Zsombor Egri

remove sharedContext

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

documentation fix

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

ActiveContext derived from Item, brings extra API. Page, Popup, Dialog have ActionContext as containers, all Actions declared inside ActionContext can this way be added to the Context automatically; ActionContext is also a FocusScope

1628. By Zsombor Egri

small tweaks on tests and contexts

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

build error fix

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

adding test case for active ActionContext inside a Dialog

1631. By Zsombor Egri

remove property iverride from SingleControl

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

context is active only if there is no overlay context set, or when one of the ancestor context is the active overlay context

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

documentation fix

1634. By Zsombor Egri

forward action list to ActionContexts

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

documentation update

1636. By Zsombor Egri

prereq sync

1637. By Zsombor Egri

fixes

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

fixing active overlay

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

fixing AbstractButton13 tests

1640. By Zsombor Egri

tst_action updated

1641. By Zsombor Egri

fixing tst_actionitem

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

Unmerged revisions

1641. By Zsombor Egri

fixing tst_actionitem

1640. By Zsombor Egri

tst_action updated

1639. By Zsombor Egri

fixing AbstractButton13 tests

1638. By Zsombor Egri

fixing active overlay

1637. By Zsombor Egri

fixes

1636. By Zsombor Egri

prereq sync

1635. By Zsombor Egri

documentation update

1634. By Zsombor Egri

forward action list to ActionContexts

1633. By Zsombor Egri

documentation fix

1632. By Zsombor Egri

context is active only if there is no overlay context set, or when one of the ancestor context is the active overlay context

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 2015-08-24 16:02:50 +0000
3+++ components.api 2015-08-26 15:09:13 +0000
4@@ -11,6 +11,7 @@
5 Ubuntu.Components.Action 1.3 1.0 0.1: QtObject
6 property string description
7 property bool enabled
8+ readonly property bool global 1.3
9 property string iconName
10 property url iconSource
11 property Component itemHint
12@@ -33,11 +34,12 @@
13 Ubuntu.Components.ActionBar 1.3: StyledItem
14 readonly property Action actions
15 property int numberOfSlots
16-Ubuntu.Components.ActionContext 1.0 0.1: QtObject
17- default readonly property Action actions
18+Ubuntu.Components.ActionContext 1.3 1.0 0.1: Item
19+ readonly property Action actions
20 property bool active
21 function addAction(Action action)
22 function removeAction(Action action)
23+ property bool overlay 1.3
24 Ubuntu.Components.ActionItem 1.0 0.1: StyledItem
25 property Action action
26 property string iconName
27@@ -290,6 +292,7 @@
28 property string text
29 property string title
30 Ubuntu.Components.Popups.Dialog 1.3: PopupBase
31+ readonly property ActionContext actionContext
32 property Item caller
33 property double callerMargin
34 default readonly property QtObject contents
35@@ -597,6 +600,7 @@
36 Ubuntu.Components.Page 1.1: Page
37 readonly property PageHeadConfiguration head
38 Ubuntu.Components.Page 1.3: PageTreeNode
39+ readonly property ActionContext actionContext
40 property Flickable flickable
41 readonly property PageHeadConfiguration head
42 property string title
43@@ -738,6 +742,7 @@
44 function var hide()
45 property Item pointerTarget
46 Ubuntu.Components.Popups.Popover 1.3: PopupBase
47+ readonly property ActionContext actionContext
48 property bool autoClose
49 property Item caller
50 property double callerMargin
51
52=== modified file 'src/Ubuntu/Components/1.2/Page10.qml'
53--- src/Ubuntu/Components/1.2/Page10.qml 2015-05-06 10:32:20 +0000
54+++ src/Ubuntu/Components/1.2/Page10.qml 2015-08-26 15:09:13 +0000
55@@ -55,28 +55,23 @@
56
57 property Flickable flickable: internal.getFlickableChild(page)
58
59- /*! \internal */
60- onActiveChanged: {
61- internal.updateActions();
62- }
63-
64 /*!
65 \qmlproperty list<Action> actions
66 */
67- property alias actions: actionContext.actions
68+ property alias actions: context.actions
69+
70+ /*! \internal, redirect all data to ActionContext */
71+ default property alias data: context.data
72+
73+ Toolkit.ActionContext {
74+ id: context
75+ active: page.active
76+ anchors.fill: parent
77+ }
78
79 Object {
80 id: internal
81
82- // Toolkit ActionContext registers automatically to ActionManager
83- Toolkit.ActionContext {
84- id: actionContext
85- }
86-
87- function updateActions() {
88- actionContext.active = page.active;
89- }
90-
91 property Item header: page.__propagated && page.__propagated.header ? page.__propagated.header : null
92 // Used to position the Page when there is no flickable.
93 // When there is a flickable, the header will automatically position it.
94
95=== modified file 'src/Ubuntu/Components/1.3/ActionBar.qml'
96--- src/Ubuntu/Components/1.3/ActionBar.qml 2015-05-26 15:05:46 +0000
97+++ src/Ubuntu/Components/1.3/ActionBar.qml 2015-08-26 15:09:13 +0000
98@@ -53,6 +53,7 @@
99 styleName: "ActionBarStyle"
100
101 /*!
102+ \qmlproperty list<Action> actions
103 The actions to display in the bar.
104 If more actions are specified than there are slots, an overflow
105 button will be show which opens a popover with the actions that
106@@ -69,4 +70,9 @@
107 and no other action buttons. Default value is 3.
108 */
109 property int numberOfSlots: 3
110+
111+ ActionContext {
112+ id: context
113+ actions: bar.actions
114+ }
115 }
116
117=== modified file 'src/Ubuntu/Components/1.3/AdaptivePageLayout.qml'
118--- src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 2015-08-17 09:49:23 +0000
119+++ src/Ubuntu/Components/1.3/AdaptivePageLayout.qml 2015-08-26 15:09:13 +0000
120@@ -673,6 +673,14 @@
121 visible: false
122 // make sure nothing is shown eventually
123 clip: true
124+ // deactivate all children
125+ onChildrenChanged: {
126+ for (var i = 0; i < children.length; i++) {
127+ if (QuickUtils.inherits(children[i], "PageWrapper")) {
128+ children[i].active = false;
129+ }
130+ }
131+ }
132 }
133
134 // Holds the columns holding the pages visible. Each column has only one page
135
136=== modified file 'src/Ubuntu/Components/1.3/Page.qml'
137--- src/Ubuntu/Components/1.3/Page.qml 2015-07-03 14:53:08 +0000
138+++ src/Ubuntu/Components/1.3/Page.qml 2015-08-26 15:09:13 +0000
139@@ -15,7 +15,7 @@
140 */
141
142 import QtQuick 2.4
143-import Ubuntu.Components 1.3 as Toolkit13
144+import Ubuntu.Components 1.3
145 import "pageUtils.js" as Utils
146
147 /*!
148@@ -41,12 +41,30 @@
149 \qmlproperty PageHeadConfiguration head
150 */
151 readonly property alias head: headerConfig
152- Toolkit13.PageHeadConfiguration {
153+ PageHeadConfiguration {
154 id: headerConfig
155 title: page.title
156 }
157
158- Toolkit13.Object {
159+ /*
160+ \qmlproperty ActionContext actionContext
161+ \since Ubuntu.Components 1.3
162+ The property holding the ActionContext of the Page. The context is active
163+ while the Page is active. All actions declared in Page will belong to this
164+ context.
165+ */
166+ readonly property alias actionContext: context
167+
168+ /*! /internal - forward all data to ActionContext */
169+ default property alias data: context.data
170+ ActionContext {
171+ id: context
172+ objectName: "ActionContext+"+page.title
173+ active: page.active
174+ anchors.fill: parent
175+ }
176+
177+ Object {
178 id: internal
179
180 property AppHeader header: page.__propagated && page.__propagated.header ? page.__propagated.header : null
181
182=== modified file 'src/Ubuntu/Components/1.3/Sections.qml'
183--- src/Ubuntu/Components/1.3/Sections.qml 2015-07-14 12:49:58 +0000
184+++ src/Ubuntu/Components/1.3/Sections.qml 2015-08-26 15:09:13 +0000
185@@ -32,6 +32,7 @@
186 theme.version: Ubuntu.toolkitVersion
187
188 /*!
189+ \qmlproperty list<Action> actions
190 List of actions that represent the sections.
191 The text of each action is displayed as the section name and clicking
192 a section will update the \l selectedIndex.
193@@ -64,7 +65,7 @@
194 without setting the actions property. If both \l actions and \l model are set,
195 model overrides the actions.
196 */
197- property list<Action> actions
198+ property alias actions: context.actions
199
200 /*!
201 The input model for the sections. By default model takes the \l actions
202@@ -99,4 +100,12 @@
203 }
204 }
205 }
206+
207+ // add own action context and keep it active as long as the Sections is visible and enabled
208+ ActionContext {
209+ id: context
210+ // TODO: we may get rid of this, as an action context can serve actions if it is both active and enabled
211+ // enabled being bound to the parent's enabled state, but actions can be active also when invisible?
212+ active: sections.enabled && sections.visible
213+ }
214 }
215
216=== modified file 'src/Ubuntu/Components/Popups/1.2/Popover.qml'
217--- src/Ubuntu/Components/Popups/1.2/Popover.qml 2015-07-02 17:39:03 +0000
218+++ src/Ubuntu/Components/Popups/1.2/Popover.qml 2015-08-26 15:09:13 +0000
219@@ -217,8 +217,9 @@
220 width: Math.min(minimumWidth, maxWidth)
221 height: containerItem.height
222
223- Item {
224+ ActionContext {
225 id: containerItem
226+ active: popover.visible
227 parent: foreground.__styleInstance.contentItem
228 anchors {
229 left: parent.left
230
231=== modified file 'src/Ubuntu/Components/Popups/1.3/Dialog.qml'
232--- src/Ubuntu/Components/Popups/1.3/Dialog.qml 2015-08-11 02:03:59 +0000
233+++ src/Ubuntu/Components/Popups/1.3/Dialog.qml 2015-08-26 15:09:13 +0000
234@@ -132,6 +132,16 @@
235 */
236 property bool modal: true
237
238+ /*!
239+ \qmlproperty ActionContext actionContext
240+ \readonly
241+ \since Ubuntu.Components.Popups 1.3
242+ The property holds the ActionContext of the Dialog. This is an overlay action
243+ context blocking all active actions defined in the application, so only actions
244+ declared in the dialog will be active.
245+ */
246+ readonly property alias actionContext: context
247+
248 /*
249 QtObject {
250 id: internal
251@@ -157,7 +167,6 @@
252 focus: visible
253 width: Math.min(minimumWidth, dialog.width)
254 anchors.centerIn: parent
255-
256 // used in the style
257 property string title
258 property string text
259@@ -170,45 +179,53 @@
260
261 height: Math.min(contentsColumn.height + foreground.margins, dialog.height)
262
263- Flickable {
264+ ActionContext {
265+ id: context
266+ objectName: dialog.title + "ActionContext"
267+ overlay: true
268+ active: dialog.visible
269 anchors.fill: parent
270- anchors.margins: foreground.margins
271- contentWidth: contentsColumn.width
272- contentHeight: contentsColumn.height - foreground.margins
273- boundsBehavior: Flickable.StopAtBounds
274-
275- Column {
276- id: contentsColumn
277- spacing: foreground.itemSpacing
278- width: foreground.width - foreground.margins * 2
279- height: childrenRect.height + foreground.margins
280- onWidthChanged: updateChildrenWidths();
281-
282- Label {
283- horizontalAlignment: Text.AlignHCenter
284- text: dialog.title
285- wrapMode: Text.Wrap
286- maximumLineCount: 2
287- elide: Text.ElideRight
288- fontSize: "large"
289- color: UbuntuColors.darkGrey
290- visible: (text !== "")
291- }
292-
293- Label {
294- horizontalAlignment: Text.AlignHCenter
295- text: dialog.text
296- fontSize: "medium"
297- color: UbuntuColors.darkGrey
298- wrapMode: Text.Wrap
299- visible: (text !== "")
300- }
301-
302- onChildrenChanged: updateChildrenWidths()
303-
304- function updateChildrenWidths() {
305- for (var i = 0; i < children.length; i++) {
306- children[i].width = contentsColumn.width;
307+
308+ Flickable {
309+ anchors.fill: parent
310+ anchors.margins: foreground.margins
311+ contentWidth: contentsColumn.width
312+ contentHeight: contentsColumn.height - foreground.margins
313+ boundsBehavior: Flickable.StopAtBounds
314+
315+ Column {
316+ id: contentsColumn
317+ spacing: foreground.itemSpacing
318+ width: foreground.width - foreground.margins * 2
319+ height: childrenRect.height + foreground.margins
320+ onWidthChanged: updateChildrenWidths();
321+
322+ Label {
323+ horizontalAlignment: Text.AlignHCenter
324+ text: dialog.title
325+ wrapMode: Text.Wrap
326+ maximumLineCount: 2
327+ elide: Text.ElideRight
328+ fontSize: "large"
329+ color: UbuntuColors.darkGrey
330+ visible: (text !== "")
331+ }
332+
333+ Label {
334+ horizontalAlignment: Text.AlignHCenter
335+ text: dialog.text
336+ fontSize: "medium"
337+ color: UbuntuColors.darkGrey
338+ wrapMode: Text.Wrap
339+ visible: (text !== "")
340+ }
341+
342+ onChildrenChanged: updateChildrenWidths()
343+
344+ function updateChildrenWidths() {
345+ for (var i = 0; i < children.length; i++) {
346+ children[i].width = contentsColumn.width;
347+ }
348 }
349 }
350 }
351
352=== modified file 'src/Ubuntu/Components/Popups/1.3/Popover.qml'
353--- src/Ubuntu/Components/Popups/1.3/Popover.qml 2015-08-12 19:53:20 +0000
354+++ src/Ubuntu/Components/Popups/1.3/Popover.qml 2015-08-26 15:09:13 +0000
355@@ -153,6 +153,16 @@
356 property alias foregroundStyle: foreground.style
357
358 /*!
359+ \qmlproperty ActionContext actionContext
360+ \readonly
361+ \since Ubuntu.Components.Popups 1.3
362+ The property holds the ActionContext of the Dialog. This is an overlay action
363+ context blocking all active actions defined in the application, so only actions
364+ declared in the dialog will be active.
365+ */
366+ readonly property alias actionContext: containerItem
367+
368+ /*!
369 Make the popover visible. Reparent to the background area object first if needed.
370 Only use this function if you handle memory management. Otherwise use
371 PopupUtils.open() to do it automatically.
372@@ -217,8 +227,9 @@
373 width: Math.min(minimumWidth, maxWidth)
374 height: containerItem.height
375
376- Item {
377+ ActionContext {
378 id: containerItem
379+ active: popover.visible
380 parent: foreground.__styleInstance.contentItem
381 anchors {
382 left: parent.left
383
384=== modified file 'src/Ubuntu/Components/plugin/adapters/actionsproxy_p.cpp'
385--- src/Ubuntu/Components/plugin/adapters/actionsproxy_p.cpp 2014-09-01 06:56:39 +0000
386+++ src/Ubuntu/Components/plugin/adapters/actionsproxy_p.cpp 2015-08-26 15:09:13 +0000
387@@ -28,20 +28,11 @@
388 }
389 ActionProxy::~ActionProxy()
390 {
391- // if there is still an active context clear it
392- if (!m_activeContext.isNull()) {
393- m_activeContext->setActive(false);
394- }
395 // clear context explicitly, as global context is not connected to
396 clearContextActions(globalContext);
397 delete globalContext;
398 }
399
400-UCActionContext *ActionProxy::currentContext()
401-{
402- return instance().m_activeContext;
403-}
404-
405 const QSet<UCActionContext*> &ActionProxy::localContexts()
406 {
407 return instance().m_localContexts;
408@@ -90,48 +81,69 @@
409 }
410 if (watch) {
411 // connect to action proxy
412- QObject::connect(context, SIGNAL(activeChanged(bool)),
413- this, SLOT(handleContextActivation(bool)),
414+ QObject::connect(context, &UCActionContext::activeChanged,
415+ this, &ActionProxy::handleContextActivation,
416+ Qt::DirectConnection);
417+ QObject::connect(context, &UCActionContext::overlayChanged,
418+ this, &ActionProxy::handleContextOverlay,
419 Qt::DirectConnection);
420 } else {
421 // disconnect
422- QObject::disconnect(context, SIGNAL(activeChanged(bool)),
423- this, SLOT(handleContextActivation(bool)));
424+ QObject::disconnect(context, &UCActionContext::activeChanged,
425+ this, &ActionProxy::handleContextActivation);
426+ QObject::disconnect(context, &UCActionContext::overlayChanged,
427+ this, &ActionProxy::handleContextOverlay);
428 }
429 }
430-
431 // handles the local context activation
432 void ActionProxy::handleContextActivation(bool active)
433 {
434+ Q_UNUSED(active);
435 // sender is the context changing activation
436 UCActionContext *context = qobject_cast<UCActionContext*>(sender());
437 if (!context) {
438 return;
439 }
440- // deactivate the previous context if any
441- if (!m_activeContext.isNull()) {
442- if (!active) {
443- // the slot has been called due to the previous active deactivation,
444- // so perform system cleanup
445- clearContextActions(m_activeContext);
446- m_activeContext->markActionsPublished(false);
447- // finally clear the context and leave
448- m_activeContext.clear();
449- return;
450- } else {
451- // deactivate previous actiev context, this will cause the slot to
452- // be called with active = false within this call context
453- m_activeContext->setActive(false);
454+ activateContext(context);
455+}
456+// handles overlay state change of a context
457+void ActionProxy::handleContextOverlay(bool overlay)
458+{
459+ Q_UNUSED(overlay);
460+ UCActionContext *context = qobject_cast<UCActionContext*>(sender());
461+ if (!context) {
462+ return;
463+ }
464+ activateContext(context);
465+}
466+
467+void ActionProxy::activateContext(UCActionContext *context)
468+{
469+ // handle overlay change
470+ if (context->m_overlay) {
471+ UCActionContext *nextContext = (context->m_active) ? context : Q_NULLPTR;
472+ if (activeOverlay && nextContext != activeOverlay) {
473+ // deactivate any previous active overlay
474+ // TODO: block signals while we deactivate the overlay
475+ activeOverlay->setActive(false);
476 }
477+ activeOverlay = nextContext;
478 }
479- if (active) {
480+ // handle active change
481+ if (!context->m_active && m_activeContexts.contains(context)) {
482+ // perform system cleanup and remove the context
483+ clearContextActions(context);
484+ context->markActionsPublished(false);
485+ m_activeContexts.remove(context);
486+ } else if (context->m_active && !m_activeContexts.contains(context)) {
487 // publish the context's actions to the system
488 publishContextActions(context);
489 context->markActionsPublished(true);
490- // and finally set it as active
491- m_activeContext = context;
492+ // and finally add as active
493+ m_activeContexts.insert(context);
494 }
495 }
496+
497 // empty functions for context activation/deactivation, connect to HUD
498 void ActionProxy::clearContextActions(UCActionContext *context)
499 {
500
501=== modified file 'src/Ubuntu/Components/plugin/adapters/actionsproxy_p.h'
502--- src/Ubuntu/Components/plugin/adapters/actionsproxy_p.h 2014-09-01 06:56:39 +0000
503+++ src/Ubuntu/Components/plugin/adapters/actionsproxy_p.h 2015-08-26 15:09:13 +0000
504@@ -36,8 +36,8 @@
505 }
506
507 UCActionContext *globalContext;
508+ QPointer<UCActionContext> activeOverlay;
509
510- static UCActionContext *currentContext();
511 static const QSet<UCActionContext*> &localContexts();
512 static void publishGlobalContext();
513 static void addContext(UCActionContext *context);
514@@ -49,12 +49,15 @@
515 protected Q_SLOTS:
516 void watchContextActivation(UCActionContext *context, bool watch);
517 void handleContextActivation(bool active);
518+ void handleContextOverlay(bool overlay);
519 virtual void clearContextActions(UCActionContext *context);
520 virtual void publishContextActions(UCActionContext *context);
521
522 private:
523 QSet<UCActionContext*> m_localContexts;
524- QPointer<UCActionContext> m_activeContext;
525+ QSet<UCActionContext*> m_activeContexts;
526+
527+ void activateContext(UCActionContext* context);
528 };
529
530 #endif // ACTIONSPROXY_P_H
531
532=== modified file 'src/Ubuntu/Components/plugin/plugin.cpp'
533--- src/Ubuntu/Components/plugin/plugin.cpp 2015-08-24 16:02:50 +0000
534+++ src/Ubuntu/Components/plugin/plugin.cpp 2015-08-26 15:09:13 +0000
535@@ -230,6 +230,7 @@
536 qmlRegisterType<UCStyledItemBase, 2>(uri, 1, 3, "StyledItem");
537 qmlRegisterCustomType<UCStyleHints>(uri, 1, 3, "StyleHints", new UCStyleHintsParser);
538 qmlRegisterType<UCAction, 1>(uri, 1, 3, "Action");
539+ qmlRegisterType<UCActionContext, 1>(uri, 1, 3, "ActionContext");
540 qmlRegisterType<UCUbuntuShape, 2>(uri, 1, 3, "UbuntuShape");
541 qmlRegisterType<UCProportionalShape>(uri, 1, 3, "ProportionalShape");
542 qmlRegisterType<LiveTimer>(uri, 1, 3, "LiveTimer");
543
544=== modified file 'src/Ubuntu/Components/plugin/ucaction.cpp'
545--- src/Ubuntu/Components/plugin/ucaction.cpp 2015-08-25 11:31:29 +0000
546+++ src/Ubuntu/Components/plugin/ucaction.cpp 2015-08-26 15:09:13 +0000
547@@ -15,6 +15,9 @@
548 */
549
550 #include "ucaction.h"
551+#include "ucactioncontext.h"
552+#include "adapters/actionsproxy_p.h"
553+#include "quickutils.h"
554
555 #include <QtDebug>
556 #include <QtQml/QQmlInfo>
557@@ -22,6 +25,17 @@
558 #include <QtQuick/qquickwindow.h>
559 #include <private/qguiapplication_p.h>
560
561+// convert variant shortcut into a QKeySequence
562+QKeySequence sequenceFromVariant(const QVariant& variant) {
563+ if (variant.type() == QVariant::Int) {
564+ return static_cast<QKeySequence::StandardKey>(variant.toInt());
565+ }
566+ if (variant.type() == QVariant::String) {
567+ return QKeySequence::fromString(variant.toString());
568+ }
569+ return QKeySequence();
570+}
571+
572 /*!
573 * \qmltype Action
574 * \instantiates UCAction
575@@ -74,6 +88,51 @@
576 * as well as to define actions for pages, or when defining options in \c ListItemOptions.
577 *
578 * Examples: See \l Page
579+ *
580+ * \section2 Contextual action handling
581+ * Actions are typically declared in place they are used. However, the toolkit
582+ * differentiates them in global, shared and local actions, depending on how they
583+ * are declared or where are registered. Global actions are meant to handle those
584+ * type of actions which must be available also when the application is running
585+ * in background. Shared actions are those actions which are reused across application
586+ * views, and local actions are those which are declared in place of their use, like
587+ * Page header actions, Dialog actions, etc. The activation and lifetime of these
588+ * actions are controlled by the \l ActionContext component. This component is
589+ * built in all UI toolkit components which handle Actions. An action can be
590+ * present in many ActionContexts, however it will be active only when at least
591+ * one of these contexts is active. There can be several contexts active at a
592+ * time, except if those are overlay, in which case there can be only one overlay
593+ * context be active at a time. When organizing the application's actions, these
594+ * constraints must be taken into account.
595+ *
596+ * The Action registers itself to the closest ActionContext found in the component
597+ * hierarchy, however this can be achieved also programatically by adding them
598+ * explicitly to the context. Each Page and Dialog has a property called \l
599+ * Page::actionContext and \l Dialog::actionContext, which holds a local context
600+ * and can be used to register local or other actions. The following application
601+ * skeleton illustrates how to reuse shared actions and mix with local actions
602+ * in a context. Note that shared actions will only be activable once Page is
603+ * activated.
604+ *
605+ * \qml
606+ * import QtQuick 2.4
607+ * import Ubuntu.Components 1.3
608+ * MainView {
609+ * width: units.gu(40)
610+ * height: units.gu(71)
611+ *
612+ * Action {
613+ * id: sharedAction
614+ * }
615+ *
616+ * Page {
617+ * actionContext.actions: [ sharedAction, localAction ]
618+ * Action {
619+ * id: localAction
620+ * }
621+ * }
622+ * }
623+ * \endqml
624 */
625
626 /*!
627@@ -141,7 +200,44 @@
628 * If set to false the action can not be triggered. Components visualizing the
629 * action migth either hide the action or make it insensitive. However visibility
630 * can be controlled separately using the \l visible property.
631- */
632+ * \note Setting an action to enabled alone is not enough to trigget the action.
633+ * The action must be added to an active ActionContext.
634+ */
635+bool UCAction::isActive()
636+{
637+ bool activeContext = false;
638+ Q_FOREACH(UCActionContext *context, m_contexts) {
639+ activeContext = context->effectiveActive();
640+ if (activeContext) {
641+ break;
642+ }
643+ }
644+ return activeContext && m_enabled;
645+}
646+
647+/*!
648+ * \qmlproperty bool Action::global
649+ * \since Ubuntu.components 1.3
650+ * The property specifies whether the action is registerd in the global or local
651+ * ActionContext. Depending on the registration the shortcuts of the action may
652+ * be available while the application is running in background.
653+ */
654+bool UCAction::isGlobal()
655+{
656+ return m_global;
657+}
658+void UCAction::setGlobal(bool global)
659+{
660+ if (global == m_global) {
661+ return;
662+ }
663+ m_global = global;
664+ if (m_shortcut.isValid()) {
665+ // reset shortcut
666+ setShortcut(m_shortcut);
667+ }
668+ Q_EMIT globalChanged();
669+}
670
671 /*!
672 * \qmlproperty bool Action::visible
673@@ -156,9 +252,35 @@
674 , m_enabled(true)
675 , m_visible(true)
676 , m_published(false)
677+ , m_global(false)
678 {
679 generateName();
680 }
681+UCAction::~UCAction()
682+{
683+ // make sure the shortcut is removed from the app
684+ if (m_shortcut.isValid()) {
685+ QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, this, sequenceFromVariant(m_shortcut));
686+ }
687+ m_shortcut = QVariant();
688+ if (m_contexts.size() > 0) {
689+ // clear context list, the action is no longer available
690+ Q_FOREACH(UCActionContext *context, m_contexts) {
691+ context->removeAction(this);
692+ }
693+ }
694+}
695+
696+// detect action context
697+void UCAction::componentComplete()
698+{
699+ if (m_contexts.size() > 0) {
700+ // nothing to do, we have a context set by QML engine
701+ return;
702+ }
703+ // if the Action is not in an ActionContext, try to detect a context in between the parents
704+ UCActionContext::registerActionToAncestorContext(parent(), this);
705+}
706
707 bool UCAction::isValidType(QVariant::Type valueType)
708 {
709@@ -244,30 +366,29 @@
710 qWarning() << "Action.itemHint is a DEPRECATED property. Use ActionItems to specify the representation of an Action.";
711 }
712
713-bool shortcutContextMatcher(QObject* object, Qt::ShortcutContext)
714+bool shortcutContextMatcher(QObject* object, Qt::ShortcutContext context)
715 {
716 UCAction* action = static_cast<UCAction*>(object);
717- // Can't access member here because it's not public
718- if (!action->property("enabled").toBool())
719+ // is action in an active context?
720+ if (!action->isActive()) {
721 return false;
722+ }
723+
724+ if (action->isGlobal() && (context == Qt::ApplicationShortcut)) {
725+ // global action, no need to check the window it is registered to
726+ return true;
727+ }
728
729 QObject* window = object;
730 while (window && !window->isWindowType()) {
731 window = window->parent();
732- if (QQuickItem* item = qobject_cast<QQuickItem*>(window))
733+ if (QQuickItem* item = qobject_cast<QQuickItem*>(window)) {
734 window = item->window();
735+ }
736 }
737 return window && window == QGuiApplication::focusWindow();
738 }
739
740-QKeySequence sequenceFromVariant(const QVariant& variant) {
741- if (variant.type() == QVariant::Int)
742- return static_cast<QKeySequence::StandardKey>(variant.toInt());
743- if (variant.type() == QVariant::String)
744- return QKeySequence::fromString(variant.toString());
745- return QKeySequence();
746-}
747-
748 /*!
749 * \qmlproperty var Action::shortcut
750 * The keyboard shortcut that can be used to trigger the action.
751@@ -277,14 +398,18 @@
752 */
753 void UCAction::setShortcut(const QVariant& shortcut)
754 {
755- if (m_shortcut.isValid())
756+ if (m_shortcut.isValid()) {
757 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, this, sequenceFromVariant(m_shortcut));
758+ }
759
760 QKeySequence sequence(sequenceFromVariant(shortcut));
761- if (!sequence.toString().isEmpty())
762- QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence, Qt::WindowShortcut, shortcutContextMatcher);
763- else
764+ if (!sequence.toString().isEmpty()) {
765+ // FIXME: register the global shortcut using a different approach
766+ Qt::ShortcutContext context = m_global ? Qt::ApplicationShortcut : Qt::WindowShortcut;
767+ QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence, context, shortcutContextMatcher);
768+ } else {
769 qmlInfo(this) << "Invalid shortcut: " << shortcut.toString();
770+ }
771
772 m_shortcut = shortcut;
773 Q_EMIT shortcutChanged(shortcut);
774@@ -292,8 +417,9 @@
775
776 bool UCAction::event(QEvent *event)
777 {
778- if (event->type() != QEvent::Shortcut)
779+ if (event->type() != QEvent::Shortcut) {
780 return false;
781+ }
782
783 QShortcutEvent *shortcut_event(static_cast<QShortcutEvent*>(event));
784 if (shortcut_event->isAmbiguous()) {
785@@ -316,7 +442,7 @@
786 */
787 void UCAction::trigger(const QVariant &value)
788 {
789- if (!m_enabled) {
790+ if (!isActive()) {
791 return;
792 }
793 if (!isValidType(value.type())) {
794
795=== modified file 'src/Ubuntu/Components/plugin/ucaction.h'
796--- src/Ubuntu/Components/plugin/ucaction.h 2015-08-18 15:51:43 +0000
797+++ src/Ubuntu/Components/plugin/ucaction.h 2015-08-26 15:09:13 +0000
798@@ -20,11 +20,16 @@
799 #include <QtCore/QObject>
800 #include <QtCore/QVariant>
801 #include <QtCore/QUrl>
802+#include <QtCore/QPointer>
803+#include <QtCore/QSet>
804+#include <QtQml/QQmlParserStatus>
805
806 class QQmlComponent;
807-class UCAction : public QObject
808+class UCActionContext;
809+class UCAction : public QObject, public QQmlParserStatus
810 {
811 Q_OBJECT
812+ Q_INTERFACES(QQmlParserStatus)
813
814 // transferred from Unity Actions
815 Q_ENUMS(Type)
816@@ -43,6 +48,7 @@
817
818 // QtQuickControls.Action
819 Q_PROPERTY(QVariant shortcut MEMBER m_shortcut WRITE setShortcut NOTIFY shortcutChanged REVISION 1)
820+ Q_PROPERTY(bool global READ isGlobal NOTIFY globalChanged FINAL REVISION 1)
821 public:
822 enum Type {
823 None,
824@@ -54,11 +60,19 @@
825 };
826
827 explicit UCAction(QObject *parent = 0);
828+ ~UCAction();
829
830 inline bool isPublished() const
831 {
832 return m_published;
833 }
834+ bool isActive();
835+ bool isGlobal();
836+ void setGlobal(bool global);
837+
838+ // from QQmlParserStatus
839+ void classBegin() {}
840+ void componentComplete();
841
842 Q_SIGNALS:
843 void nameChanged();
844@@ -70,7 +84,8 @@
845 void parameterTypeChanged();
846 void iconSourceChanged();
847 void visibleChanged();
848- void shortcutChanged(const QVariant& shortcut);
849+ Q_REVISION(1) void shortcutChanged(const QVariant& shortcut);
850+ Q_REVISION(1) void globalChanged();
851 void triggered(const QVariant &value);
852
853 public Q_SLOTS:
854@@ -84,12 +99,14 @@
855 QString m_description;
856 QString m_keywords;
857 QVariant m_shortcut;
858+ QSet<UCActionContext*> m_contexts;
859 QQmlComponent *m_itemHint;
860 Type m_parameterType;
861 bool m_factoryIconSource:1;
862 bool m_enabled:1;
863 bool m_visible:1;
864 bool m_published:1;
865+ bool m_global:1;
866
867 friend class UCActionContext;
868 friend class UCActionItem;
869
870=== modified file 'src/Ubuntu/Components/plugin/ucactioncontext.cpp'
871--- src/Ubuntu/Components/plugin/ucactioncontext.cpp 2015-07-09 17:55:03 +0000
872+++ src/Ubuntu/Components/plugin/ucactioncontext.cpp 2015-08-26 15:09:13 +0000
873@@ -17,6 +17,8 @@
874 #include "ucactioncontext.h"
875 #include "ucaction.h"
876 #include "adapters/actionsproxy_p.h"
877+#include <QtQuick/QQuickItem>
878+#include <QtQuick/private/qquickitem_p.h>
879
880 /*!
881 * \qmltype ActionContext
882@@ -26,23 +28,90 @@
883 * \brief ActionContext groups actions together and by providing multiple contexts
884 * the developer is able to control the visibility of the actions. The \l ActionManager
885 * then exposes the actions from these different contexts.
886+ *
887+ * There can be two types of action contexts present in an application: local
888+ * and global context. \b Local contexts contains those actions which are active
889+ * and/or are available on a given Page or Dialog or other context. This can contain
890+ * \e shared actions. Shared actions are typical reusable actions across application
891+ * views. They are "dormant" until used in local contexts. \b Global actions are
892+ * those type of actions which are also available while the application is in
893+ * background.
894+ *
895+ * Actions declared in Dialog or Page, including the ones declared for the header,
896+ * are stored implicitly in the ActionContext associated to the component. When
897+ * the Page is activated, the Actions in the context will be activated as well.
898+ * Depending on the context \l overlay type, there can be only one or more action
899+ * contexts active at a time. When the ActionContext is destroyed, it is removed
900+ * from the action management, and all its actions declared in the same document
901+ * will be removed and destroyed, exception being the shared actions.
902+ *
903+ * Dialogs are a special case on action handling. When a dialog is shown, the main
904+ * action context of the Dialog is set to be overlay, meaning all action contexts
905+ * of the application are disabled, and only contexts declared in the Dialog will
906+ * be active. Actions registered in the disabled contexts will be suppressed,
907+ * excluding global actions, which will be also active. There can be only one overlay
908+ * action context active at a time. Any previously active overlay context will be
909+ * deactivated when a new overlay context is activated.
910 */
911-UCActionContext::UCActionContext(QObject *parent)
912- : QObject(parent)
913+UCActionContext::UCActionContext(QQuickItem *parent)
914+ : QQuickItem(parent)
915 , m_active(false)
916+ , m_overlay(false)
917 {
918+ setFlag(ItemIsFocusScope);
919 }
920 UCActionContext::~UCActionContext()
921 {
922+ // remove all actions from the context
923+ clear();
924 ActionProxy::removeContext(this);
925 }
926
927+// Returns an ancestor ActionContext declared either as actionContext or __actionContext (1.2)
928+// property. If none found, returns the sharedContext. Never returns NULL.
929+UCActionContext *UCActionContext::findAncestorContext(QObject *parent)
930+{
931+ UCActionContext *context = Q_NULLPTR;
932+ while (parent && !context) {
933+ context = qobject_cast<UCActionContext*>(parent);
934+ // if the parent is an Item, we go that way forward
935+ QQuickItem *parentItem = qobject_cast<QQuickItem*>(parent);
936+ parent = parentItem ? parentItem->parentItem() : parent->parent();
937+ }
938+ return context;
939+}
940+
941+bool UCActionContext::registerActionToAncestorContext(QObject *parent, UCAction *action)
942+{
943+ if (!parent || !action) {
944+ return false;
945+ }
946+ UCActionContext *context = findAncestorContext(parent);
947+ if (!context) {
948+ return false;
949+ }
950+ context->addAction(action);
951+ return true;
952+}
953+
954 void UCActionContext::componentComplete()
955 {
956 // add the context to the management
957 ActionProxy::addContext(this);
958 }
959
960+void UCActionContext::clear()
961+{
962+ // remove context from actions
963+ Q_FOREACH(UCAction *action, m_actions) {
964+ action->m_contexts.remove(this);
965+ // FIXME: shouldn't we emit action->activeChanged?
966+ // actions can be active until they are in an active context,
967+ // maybe we want to notify this
968+ }
969+ m_actions.clear();
970+}
971+
972 /*
973 * The function marks all context actions being (un)published.
974 */
975@@ -60,32 +129,31 @@
976 */
977 QQmlListProperty<UCAction> UCActionContext::actions()
978 {
979- return QQmlListProperty<UCAction>(this, 0, UCActionContext::append, UCActionContext::count, 0, UCActionContext::clear);
980+ return QQmlListProperty<UCAction>(this, 0, UCActionContext::append, UCActionContext::count, UCActionContext::at, UCActionContext::clear);
981 }
982
983 void UCActionContext::append(QQmlListProperty<UCAction> *list, UCAction *action)
984 {
985 UCActionContext *context = qobject_cast<UCActionContext*>(list->object);
986- if (context) {
987- context->m_actions.insert(action);
988- }
989+ context->addAction(action);
990 }
991
992 void UCActionContext::clear(QQmlListProperty<UCAction> *list)
993 {
994 UCActionContext *context = qobject_cast<UCActionContext*>(list->object);
995- if (context) {
996- context->m_actions.clear();
997- }
998+ context->clear();
999+}
1000+
1001+UCAction *UCActionContext::at(QQmlListProperty<UCAction> *list, int index)
1002+{
1003+ UCActionContext *context = qobject_cast<UCActionContext*>(list->object);
1004+ return context->m_actions.toList().at(index);
1005 }
1006
1007 int UCActionContext::count(QQmlListProperty<UCAction> *list)
1008 {
1009 UCActionContext *context = qobject_cast<UCActionContext*>(list->object);
1010- if (context) {
1011- return context->m_actions.count();
1012- }
1013- return 0;
1014+ return context->m_actions.count();
1015 }
1016
1017 /*!
1018@@ -97,20 +165,42 @@
1019 * whether or not the actions in a context are available to external components.
1020 *
1021 * The \l ActionManager monitors the active property of each of the local contexts
1022- * that has been added to it. There can be only one active local context at a time.
1023- * When one of the local contexts sets itself active the manager will notice this,
1024- * export the actions from that given context and set the previously active local
1025- * context as inactive. This way setting active to true on a local context is
1026- * sufficient to manage the active local context of the manager and no additional
1027- * calls are necessary to manually inactivate the other contexts.
1028+ * that has been added to it. There can be many local contexts active at the same
1029+ * time.
1030+ *
1031+ * The global context is the only context which cannot be deactivated.
1032 */
1033+bool UCActionContext::effectiveActive()
1034+{
1035+ if (!ActionProxy::instance().activeOverlay.isNull()) {
1036+ if (ActionProxy::instance().activeOverlay == this) {
1037+ return m_active && isEnabled();
1038+ }
1039+ // find out whether the context is in the active overlay one
1040+ UCActionContext *parentContext = findAncestorContext(parentItem());
1041+ if (parentContext) {
1042+ bool active = parentContext->effectiveActive();
1043+ return active;
1044+ }
1045+ // the context is not declared as child of the active overlay
1046+ // therefore report as inactive
1047+ return false;
1048+ }
1049+ // the action context may be embedded in an other context, so it will be active only if all parent contexts are active
1050+ UCActionContext *parentContext = findAncestorContext(parentItem());
1051+ if (parentContext) {
1052+ bool active = parentContext->effectiveActive();
1053+ return active;
1054+ }
1055+ return m_active && isEnabled();
1056+}
1057 void UCActionContext::setActive(bool active)
1058 {
1059 if (m_active == active) {
1060 return;
1061 }
1062- // skip deactivation for global context
1063- if (!active && (ActionProxy::instance().globalContext == this)) {
1064+ // skip deactivation for global context or activation of the shared context
1065+ if (!active && ActionProxy::instance().globalContext == this) {
1066 return;
1067 }
1068 m_active = active;
1069@@ -118,8 +208,58 @@
1070 }
1071
1072 /*!
1073- * \qmlmethod ActionContext::addAction(Action action)
1074- * \deprecated
1075+ * \qmlproperty bool ActionContext::overlay
1076+ * \since Ubuntu.Components 1.3
1077+ * The property specifies whether the action context is overlay or not. An active
1078+ * overlay ActionContext deactivates all previous active contexts, so only the
1079+ * active overlay context stays active. There can be only one active overlay
1080+ * ActionContext at a time in an application. When all overlay ActionContext
1081+ * elements are deactivated, the last state of local and global contexts will
1082+ * be restored.
1083+ *
1084+ * Defaults to false.
1085+ */
1086+void UCActionContext::setOverlay(bool overlay)
1087+{
1088+ if (m_overlay == overlay) {
1089+ return;
1090+ }
1091+ // global or shared context canot be overlay
1092+ if (ActionProxy::instance().globalContext == this) {
1093+ return;
1094+ }
1095+ m_overlay = overlay;
1096+ Q_EMIT overlayChanged(overlay);
1097+}
1098+
1099+// override data property's append and clear function to add/remove orphan shared actions
1100+// to/from context
1101+QQmlListProperty<QObject> UCActionContext::data()
1102+{
1103+ return QQmlListProperty<QObject>(this, 0, UCActionContext::data_append,
1104+ QQuickItemPrivate::data_count,
1105+ QQuickItemPrivate::data_at,
1106+ UCActionContext::data_clear);
1107+}
1108+void UCActionContext::data_append(QQmlListProperty<QObject> *list, QObject *obj)
1109+{
1110+ QQuickItemPrivate::data_append(list, obj);
1111+ // if obj is Action, add the action to the context.
1112+ UCAction *action = qobject_cast<UCAction*>(obj);
1113+ if (action) {
1114+ static_cast<UCActionContext*>(list->object)->addAction(action);
1115+ }
1116+}
1117+void UCActionContext::data_clear(QQmlListProperty<QObject> *list)
1118+{
1119+ // actions are registered as resources, so we must do cleanup here
1120+ UCActionContext *context = static_cast<UCActionContext*>(list->object);
1121+ context->clear();
1122+ QQuickItemPrivate::data_clear(list);
1123+}
1124+
1125+/*!
1126+ * \qmlmethod void ActionContext::addAction(Action action)
1127 * Adds an Action to the context programatically.
1128 */
1129 void UCActionContext::addAction(UCAction *action)
1130@@ -128,11 +268,12 @@
1131 return;
1132 }
1133 m_actions.insert(action);
1134+ action->m_contexts.insert(this);
1135+ action->setGlobal(this == ActionProxy::instance().globalContext);
1136 }
1137
1138 /*!
1139- * \qmlmethod ActionContext::removeAction(Action action)
1140- * \deprecated
1141+ * \qmlmethod void ActionContext::removeAction(Action action)
1142 * Removes an action from the context programatically.
1143 */
1144 void UCActionContext::removeAction(UCAction *action)
1145@@ -141,4 +282,9 @@
1146 return;
1147 }
1148 m_actions.remove(action);
1149+ action->m_contexts.remove(this);
1150+ // reset global behavior once the action is removed from the global list
1151+ if (!action->m_contexts.contains(ActionProxy::instance().globalContext)) {
1152+ action->setGlobal(false);
1153+ }
1154 }
1155
1156=== modified file 'src/Ubuntu/Components/plugin/ucactioncontext.h'
1157--- src/Ubuntu/Components/plugin/ucactioncontext.h 2015-07-09 17:55:03 +0000
1158+++ src/Ubuntu/Components/plugin/ucactioncontext.h 2015-08-26 15:09:13 +0000
1159@@ -20,43 +20,65 @@
1160 #include <QtCore/QObject>
1161 #include <QtQml/QQmlListProperty>
1162 #include <QtQml/QQmlParserStatus>
1163+#include <QtQuick/QQuickItem>
1164 #include <QtCore/QSet>
1165 #include <QtQml>
1166
1167 class UCAction;
1168-class UCActionContext : public QObject, public QQmlParserStatus
1169+class UCActionContext : public QQuickItem
1170 {
1171 Q_OBJECT
1172- Q_INTERFACES(QQmlParserStatus)
1173 Q_PROPERTY(QQmlListProperty<UCAction> actions READ actions)
1174 Q_PROPERTY(bool active MEMBER m_active WRITE setActive NOTIFY activeChanged)
1175- Q_CLASSINFO("DefaultProperty", "actions")
1176+ Q_PROPERTY(bool overlay READ isOverlay WRITE setOverlay NOTIFY overlayChanged FINAL REVISION 1)
1177+ Q_PROPERTY(bool effectiveActive READ effectiveActive CONSTANT)
1178+ Q_PROPERTY(QQmlListProperty<QObject> data READ data DESIGNABLE false)
1179+ Q_CLASSINFO("DefaultProperty", "data")
1180 public:
1181- explicit UCActionContext(QObject *parent = 0);
1182+ explicit UCActionContext(QQuickItem *parent = 0);
1183 ~UCActionContext();
1184
1185+ static UCActionContext *findAncestorContext(QObject *parent);
1186+ static bool registerActionToAncestorContext(QObject *parent, UCAction *action);
1187+
1188 void classBegin(){}
1189 void componentComplete();
1190+ void clear();
1191 void markActionsPublished(bool mark);
1192
1193 QQmlListProperty<UCAction> actions();
1194
1195+ bool effectiveActive();
1196 void setActive(bool active);
1197+ inline bool isOverlay()
1198+ {
1199+ return m_overlay;
1200+ }
1201+ void setOverlay(bool overlay);
1202+
1203+ // data property
1204+ static void data_append(QQmlListProperty<QObject> *, QObject *);
1205+ static void data_clear(QQmlListProperty<QObject> *);
1206+ QQmlListProperty<QObject> data();
1207
1208 Q_SIGNALS:
1209 void activeChanged(bool);
1210+ Q_REVISION(1) void overlayChanged(bool);
1211
1212 public Q_SLOTS:
1213 void addAction(UCAction *action);
1214 void removeAction(UCAction *action);
1215
1216 private:
1217- bool m_active;
1218 QSet<UCAction*> m_actions;
1219+ bool m_active:1;
1220+ bool m_overlay:1;
1221 friend class UCActionManager;
1222+ friend class ActionProxy;
1223
1224 static void append(QQmlListProperty<UCAction> *list, UCAction *action);
1225 static void clear(QQmlListProperty<UCAction> *list);
1226+ static UCAction *at(QQmlListProperty<UCAction> *list, int index);
1227 static int count(QQmlListProperty<UCAction> *list);
1228 };
1229
1230
1231=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.cpp'
1232--- src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-08-25 11:31:29 +0000
1233+++ src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-08-26 15:09:13 +0000
1234@@ -16,6 +16,8 @@
1235
1236 #include "ucactionitem.h"
1237 #include "ucaction.h"
1238+#include "ucactioncontext.h"
1239+#include <QtQuick/private/qquickitem_p.h>
1240
1241 /*!
1242 * \qmltype ActionItem
1243@@ -50,6 +52,7 @@
1244 // make sure we connect to the right signals, so we detach and re-attach actions
1245 // to make sure the SLOT macro picks up the custom trigger() slot
1246 if (m_action) {
1247+ UCActionContext::registerActionToAncestorContext(this, m_action);
1248 attachAction(false);
1249 attachAction(true);
1250 }
1251@@ -170,6 +173,11 @@
1252 _q_updateVisible();
1253 _q_updateEnabled();
1254 updateProperties();
1255+ // an action can be present in multiple contexts, therefore
1256+ // we only add this action to the context, no need to remove it
1257+ if (m_action && QQuickItemPrivate::get(this)->componentComplete) {
1258+ UCActionContext::registerActionToAncestorContext(this, m_action);
1259+ }
1260 }
1261
1262 /*!
1263
1264=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.h'
1265--- src/Ubuntu/Components/plugin/ucactionitem.h 2015-08-25 11:31:29 +0000
1266+++ src/Ubuntu/Components/plugin/ucactionitem.h 2015-08-26 15:09:13 +0000
1267@@ -40,6 +40,9 @@
1268 void setIconName(const QString &iconName);
1269 void resetIconName();
1270
1271+protected:
1272+ void componentComplete();
1273+
1274 Q_SIGNALS:
1275 void actionChanged();
1276 void textChanged();
1277@@ -70,8 +73,6 @@
1278 UCAction *m_action;
1279 quint8 m_flags;
1280
1281- void componentComplete();
1282-
1283 void updateProperties();
1284 void attachAction(bool attach);
1285 };
1286
1287=== modified file 'src/Ubuntu/Components/plugin/ucactionmanager.cpp'
1288--- src/Ubuntu/Components/plugin/ucactionmanager.cpp 2015-07-09 17:55:03 +0000
1289+++ src/Ubuntu/Components/plugin/ucactionmanager.cpp 2015-08-26 15:09:13 +0000
1290@@ -58,14 +58,14 @@
1291 void UCActionManager::actionAppend(QQmlListProperty<UCAction> *list, UCAction *action)
1292 {
1293 Q_UNUSED(list);
1294- ActionProxy::instance().globalContext->m_actions.insert(action);
1295+ ActionProxy::instance().globalContext->addAction(action);
1296 }
1297
1298 void UCActionManager::actionClear(QQmlListProperty<UCAction> *list)
1299 {
1300 Q_UNUSED(list);
1301 UCActionContext *context = ActionProxy::instance().globalContext;
1302- context->m_actions.clear();
1303+ context->clear();
1304 }
1305
1306 int UCActionManager::actionCount(QQmlListProperty<UCAction> *list)
1307@@ -114,8 +114,7 @@
1308 }
1309
1310 /*!
1311- * \qmlmethod ActionManager::addAction(Action action)
1312- * \deprecated
1313+ * \qmlmethod void ActionManager::addAction(Action action)
1314 * This is a shorthand for \c ActionManager.globalContext.addAction(action) call.
1315 */
1316 void UCActionManager::addAction(UCAction *action)
1317@@ -127,8 +126,7 @@
1318 }
1319
1320 /*!
1321- * \qmlmethod ActionManager::removeAction(Action action)
1322- * \deprecated
1323+ * \qmlmethod void ActionManager::removeAction(Action action)
1324 * This is a shorthand for \c ActionManager.globalContext.removeAction(action) call.
1325 */
1326 void UCActionManager::removeAction(UCAction *action)
1327@@ -140,11 +138,10 @@
1328 }
1329
1330 /*!
1331- * \qmlmethod ActionManager::addLocalContext(ActionContext context)
1332- * \deprecated
1333+ * \qmlmethod void ActionManager::addLocalContext(ActionContext context)
1334 * Adds the local context.
1335 *
1336- * This is deprecated. ActionContext instances are added autimatically to the
1337+ * ActionContext instances are added autimatically to the
1338 * action management stystem when declared and removed when destroyed.
1339 */
1340 void UCActionManager::addLocalContext(UCActionContext *context)
1341@@ -156,11 +153,10 @@
1342 }
1343
1344 /*!
1345- * \qmlmethod ActionManager::removeLocalContext(ActionContext context)
1346- * \deprecated
1347+ * \qmlmethod void ActionManager::removeLocalContext(ActionContext context)
1348 * Removes the local context.
1349 *
1350- * This is deprecated. ActionContext instances are added autimatically to the
1351+ * ActionContext instances are added autimatically to the
1352 * action management stystem when declared and removed when destroyed.
1353 */
1354 void UCActionManager::removeLocalContext(UCActionContext *context)
1355
1356=== modified file 'src/Ubuntu/Components/plugin/uclistitem.cpp'
1357--- src/Ubuntu/Components/plugin/uclistitem.cpp 2015-07-30 13:27:32 +0000
1358+++ src/Ubuntu/Components/plugin/uclistitem.cpp 2015-08-26 15:09:13 +0000
1359@@ -26,6 +26,7 @@
1360 #include "i18n.h"
1361 #include "quickutils.h"
1362 #include "ucaction.h"
1363+#include "ucactioncontext.h"
1364 #include "ucnamespace.h"
1365 #include <QtQml/QQmlInfo>
1366 #include <QtQuick/private/qquickitem_p.h>
1367@@ -339,6 +340,14 @@
1368 preStyleChanged();
1369 return false;
1370 }
1371+ // add leading/trailing actions to an active context!
1372+ Q_Q(UCListItem);
1373+ if (leadingActions) {
1374+ UCListItemActionsPrivate::get(leadingActions)->registerActionsToActionContext(q);
1375+ }
1376+ if (trailingActions) {
1377+ UCListItemActionsPrivate::get(trailingActions)->registerActionsToActionContext(q);
1378+ }
1379 // bring the panels foreground
1380 styleItem->setZ(0);
1381 listItemStyle()->setAnimatePanels(true);
1382@@ -955,6 +964,10 @@
1383 }
1384
1385 if (d->parentAttached) {
1386+ // make sure the action is added to the closest ActionContext
1387+ if (d->mainAction && UCViewItemsAttachedPrivate::get(d->parentAttached)->actionContext) {
1388+ UCViewItemsAttachedPrivate::get(d->parentAttached)->actionContext->addAction(d->mainAction);
1389+ }
1390 // connect selectedIndicesChanged
1391 connect(d->parentAttached.data(), &UCViewItemsAttached::selectedIndicesChanged,
1392 this, &UCListItem::selectedChanged);
1393@@ -1633,10 +1646,20 @@
1394 if (mainAction == action) {
1395 return;
1396 }
1397+ if (mainAction && parentAttached &&
1398+ UCViewItemsAttachedPrivate::get(parentAttached)->actionContext && componentComplete) {
1399+ UCViewItemsAttachedPrivate::get(parentAttached)->actionContext->removeAction(mainAction);
1400+ }
1401 mainAction = action;
1402- if (mainAction && (mainAction->m_parameterType == UCAction::None)) {
1403- // call setProperty to invoke notify signal
1404- mainAction->setProperty("parameterType", UCAction::Integer);
1405+ if (mainAction) {
1406+ if (mainAction->m_parameterType == UCAction::None) {
1407+ // call setProperty to invoke notify signal
1408+ mainAction->setProperty("parameterType", UCAction::Integer);
1409+ }
1410+ // add the action to the closest ActionContext
1411+ if (parentAttached && UCViewItemsAttachedPrivate::get(parentAttached)->actionContext && componentComplete) {
1412+ UCViewItemsAttachedPrivate::get(parentAttached)->actionContext->addAction(mainAction);
1413+ }
1414 }
1415 Q_EMIT q->actionChanged();
1416 }
1417
1418=== modified file 'src/Ubuntu/Components/plugin/uclistitem.h'
1419--- src/Ubuntu/Components/plugin/uclistitem.h 2015-07-28 19:29:19 +0000
1420+++ src/Ubuntu/Components/plugin/uclistitem.h 2015-08-26 15:09:13 +0000
1421@@ -180,6 +180,7 @@
1422 private Q_SLOTS:
1423 void unbindItem();
1424 void completed();
1425+ void updateActionContext();
1426
1427 Q_SIGNALS:
1428 void selectModeChanged();
1429
1430=== modified file 'src/Ubuntu/Components/plugin/uclistitem_p.h'
1431--- src/Ubuntu/Components/plugin/uclistitem_p.h 2015-07-30 13:27:32 +0000
1432+++ src/Ubuntu/Components/plugin/uclistitem_p.h 2015-08-26 15:09:13 +0000
1433@@ -123,6 +123,7 @@
1434
1435 class PropertyChange;
1436 class ListItemDragArea;
1437+class UCActionContext;
1438 class UCViewItemsAttachedPrivate : public QObjectPrivate
1439 {
1440 Q_DECLARE_PUBLIC(UCViewItemsAttached)
1441@@ -149,6 +150,7 @@
1442
1443 QQuickFlickable *listView;
1444 ListItemDragArea *dragArea;
1445+ UCActionContext *actionContext;
1446 bool globalDisabled:1;
1447 bool selectable:1;
1448 bool draggable:1;
1449
1450=== modified file 'src/Ubuntu/Components/plugin/uclistitemactions.cpp'
1451--- src/Ubuntu/Components/plugin/uclistitemactions.cpp 2015-03-03 13:47:48 +0000
1452+++ src/Ubuntu/Components/plugin/uclistitemactions.cpp 2015-08-26 15:09:13 +0000
1453@@ -24,6 +24,8 @@
1454 #include "ucaction.h"
1455 #include "ucunits.h"
1456 #include "uclistitemstyle.h"
1457+#include "ucactioncontext.h"
1458+#include "adapters/actionsproxy_p.h"
1459
1460 UCListItemActionsPrivate::UCListItemActionsPrivate()
1461 : QObjectPrivate()
1462@@ -34,6 +36,18 @@
1463 {
1464 }
1465
1466+void UCListItemActionsPrivate::registerActionsToActionContext(UCListItem *activeListItem)
1467+{
1468+ UCViewItemsAttached *viewItems = UCListItemPrivate::get(activeListItem)->parentAttached.data();
1469+ Q_ASSERT(viewItems);
1470+ UCActionContext *context = UCViewItemsAttachedPrivate::get(viewItems)->actionContext;
1471+ Q_ASSERT(context);
1472+ Q_FOREACH(UCAction *action, actions) {
1473+ context->addAction(action);
1474+ }
1475+}
1476+
1477+
1478 /*!
1479 * \qmltype ListItemActions
1480 * \instantiates UCListItemActions
1481
1482=== modified file 'src/Ubuntu/Components/plugin/uclistitemactions_p.h'
1483--- src/Ubuntu/Components/plugin/uclistitemactions_p.h 2015-01-07 10:38:04 +0000
1484+++ src/Ubuntu/Components/plugin/uclistitemactions_p.h 2015-08-26 15:09:13 +0000
1485@@ -36,6 +36,8 @@
1486 QList<UCAction*> actions;
1487 QList<QObject*> data;
1488
1489+ void registerActionsToActionContext(UCListItem *activeListItem);
1490+
1491 static int actions_count(QQmlListProperty<UCAction> *p);
1492 static void actions_append(QQmlListProperty<UCAction> *p, UCAction *v);
1493 static UCAction *actions_at(QQmlListProperty<UCAction>*, int);
1494
1495=== modified file 'src/Ubuntu/Components/plugin/ucviewitemsattached.cpp'
1496--- src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-03-17 16:48:59 +0000
1497+++ src/Ubuntu/Components/plugin/ucviewitemsattached.cpp 2015-08-26 15:09:13 +0000
1498@@ -23,6 +23,8 @@
1499 #include "i18n.h"
1500 #include "uclistitemstyle.h"
1501 #include "privates/listitemdragarea.h"
1502+#include "ucactioncontext.h"
1503+#include "adapters/actionsproxy_p.h"
1504 #include <QtQuick/private/qquickflickable_p.h>
1505 #include <QtQml/private/qqmlcomponentattached_p.h>
1506 #include <QtQml/QQmlInfo>
1507@@ -104,6 +106,7 @@
1508 : QObjectPrivate()
1509 , listView(0)
1510 , dragArea(0)
1511+ , actionContext(Q_NULLPTR)
1512 , globalDisabled(false)
1513 , selectable(false)
1514 , draggable(false)
1515@@ -309,6 +312,23 @@
1516 } else {
1517 d->leaveDragMode();
1518 }
1519+ // detect if we have any ancestor ActionContext, if none, create one
1520+ d->actionContext = UCActionContext::findAncestorContext(parent());
1521+ if (!d->actionContext) {
1522+ // create own context
1523+ d->actionContext = new UCActionContext(static_cast<QQuickItem*>(parent()));
1524+ connect(parent(), SIGNAL(enabledChanged()), this, SLOT(updateActionContext()));
1525+ connect(parent(), SIGNAL(visibleChanged()), this, SLOT(updateActionContext()));
1526+ updateActionContext();
1527+ }
1528+}
1529+
1530+// slot connected to owner's enabled and visible signals
1531+void UCViewItemsAttached::updateActionContext()
1532+{
1533+ Q_D(UCViewItemsAttached);
1534+ QQuickItem *owner = static_cast<QQuickItem*>(parent());
1535+ d->actionContext->setActive(owner->isEnabled() && owner->isVisible());
1536 }
1537
1538 /*!
1539
1540=== modified file 'tests/unit/tst_components/tst_action.qml'
1541--- tests/unit/tst_components/tst_action.qml 2015-08-25 11:31:29 +0000
1542+++ tests/unit/tst_components/tst_action.qml 2015-08-26 15:09:13 +0000
1543@@ -1,5 +1,5 @@
1544 /*
1545- * Copyright 2013 Canonical Ltd.
1546+ * Copyright 2015 Canonical Ltd.
1547 *
1548 * This program is free software; you can redistribute it and/or modify
1549 * it under the terms of the GNU Lesser General Public License as published by
1550@@ -21,10 +21,6 @@
1551 TestCase {
1552 name: "ActionAPI"
1553
1554- TestUtil {
1555- id: util
1556- }
1557-
1558 function contains(list, entry) {
1559 for (var i = 0; i < list.length; i++) {
1560 if (list[i] == entry) {
1561@@ -34,9 +30,15 @@
1562 return false;
1563 }
1564
1565+ function init() {
1566+ context1.addAction(valueType);
1567+ }
1568+
1569 function cleanup() {
1570 triggeredSignalSpy.target = action;
1571 triggeredSignalSpy.clear();
1572+ context1.removeAction(valueType);
1573+ context2.active = false;
1574 }
1575
1576 function initTestCase() {
1577@@ -120,21 +122,20 @@
1578 compare(manager.globalContext.actions.length, 3, "Global context action count must be a sum of all manager's actions' counts");
1579 }
1580
1581- function ignoreQMLWarning(message) {
1582- ignoreWarning(util.callerFile() + message);
1583- }
1584-
1585 function test_activate_contexts_data() {
1586 return [
1587- {tag: "Activate context1", active: context1, inactive: context2},
1588- {tag: "Activate context2", active: context2, inactive: context1},
1589- {tag: "Activate context1 again", active: context1, inactive: context2},
1590+ {tag: "Activate context1", activate: context1, active: [context1], inactive: [context2]},
1591+ {tag: "Activate context2", activate: context2, active: [context1, context2], inactive: []},
1592 ];
1593 }
1594 function test_activate_contexts(data) {
1595- data.active.active = true;
1596- verify(data.active.active, "Context activation error");
1597- verify(!data.inactive.active, "Context deactivation error");
1598+ data.activate.active = true;
1599+ for (var i = 0; i < data.active.length; i++) {
1600+ verify(data.active[i].active, "Context activation error");
1601+ }
1602+ for (var i = 0; i < data.inactive.length; i++) {
1603+ verify(!data.inactive[i].active, "Context deactivation error");
1604+ }
1605 }
1606
1607 function test_overloaded_action_trigger() {
1608@@ -191,6 +192,7 @@
1609
1610 ActionContext {
1611 id: context1
1612+ active: true
1613 }
1614 ActionContext {
1615 id: context2
1616@@ -200,5 +202,4 @@
1617 id: suppressTrigger
1618 function trigger() {}
1619 }
1620-
1621 }
1622
1623=== modified file 'tests/unit/tst_components/tst_actionitem.qml'
1624--- tests/unit/tst_components/tst_actionitem.qml 2015-08-18 16:58:41 +0000
1625+++ tests/unit/tst_components/tst_actionitem.qml 2015-08-26 15:09:13 +0000
1626@@ -100,4 +100,9 @@
1627 text: "actionText"
1628 iconSource: "imageURL"
1629 }
1630+
1631+ ActionContext {
1632+ active: true
1633+ actions: action1
1634+ }
1635 }
1636
1637=== modified file 'tests/unit_x11/tst_components/tst_abstractbutton13.qml'
1638--- tests/unit_x11/tst_components/tst_abstractbutton13.qml 2015-08-25 11:31:29 +0000
1639+++ tests/unit_x11/tst_components/tst_abstractbutton13.qml 2015-08-26 15:09:13 +0000
1640@@ -19,7 +19,8 @@
1641 import Ubuntu.Test 1.0
1642 import Ubuntu.Components 1.3
1643
1644-Item {
1645+ActionContext {
1646+ active: true
1647 width: units.gu(40)
1648 height: units.gu(71)
1649
1650
1651=== added file 'tests/unit_x11/tst_components/tst_action13.qml'
1652--- tests/unit_x11/tst_components/tst_action13.qml 1970-01-01 00:00:00 +0000
1653+++ tests/unit_x11/tst_components/tst_action13.qml 2015-08-26 15:09:13 +0000
1654@@ -0,0 +1,215 @@
1655+/*
1656+ * Copyright 2015 Canonical Ltd.
1657+ *
1658+ * This program is free software; you can redistribute it and/or modify
1659+ * it under the terms of the GNU Lesser General Public License as published by
1660+ * the Free Software Foundation; version 3.
1661+ *
1662+ * This program is distributed in the hope that it will be useful,
1663+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1664+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1665+ * GNU Lesser General Public License for more details.
1666+ *
1667+ * You should have received a copy of the GNU Lesser General Public License
1668+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1669+ */
1670+
1671+import QtQuick 2.0
1672+import QtTest 1.0
1673+import Ubuntu.Test 1.0
1674+import Ubuntu.Components 1.3
1675+
1676+MainView {
1677+ width: units.gu(40)
1678+ height: units.gu(71)
1679+
1680+ ActionManager {
1681+ id: manager
1682+ Action { id: global1 }
1683+ Action { id: global2 }
1684+ Action { id: global3 }
1685+ }
1686+ Action {
1687+ id: shared1
1688+ objectName: "shared1"
1689+ }
1690+ Action {
1691+ id: shared2
1692+ objectName: "shared2"
1693+ }
1694+ Action {
1695+ id: shared3
1696+ objectName: "shared3"
1697+ }
1698+ Item {
1699+ ActionContext {
1700+ id: context
1701+ objectName: "context"
1702+ Action { id: local }
1703+ }
1704+ }
1705+ ActionContext {
1706+ id: activeContext
1707+ objectName: "activeContext"
1708+ active: true
1709+ }
1710+ ActionContext {
1711+ id: overlayContext
1712+ objectName: "overlayContext"
1713+ overlay: true
1714+ }
1715+
1716+ UbuntuTestCase {
1717+ name: "ActionAPI13"
1718+ when: windowShown
1719+
1720+ SignalSpy {
1721+ id: triggeredSpy
1722+ signalName: "triggered"
1723+ }
1724+
1725+ function contains(list, entry) {
1726+ for (var i = 0; i < list.length; i++) {
1727+ if (list[i] == entry) {
1728+ return true;
1729+ }
1730+ }
1731+ return false;
1732+ }
1733+
1734+ function cleanup() {
1735+ var list = [shared1, shared2, shared3, global1, global2, global3];
1736+ for (var i = 0; i < list.length; i++) {
1737+ context.removeAction(list[i]);
1738+ activeContext.removeAction(list[i]);
1739+ overlayContext.removeAction(list[i]);
1740+ }
1741+
1742+ context.active = false;
1743+ activeContext.overlay = false;
1744+ context.active = false;
1745+ overlayContext.active = false;
1746+ activeContext.active = true;
1747+ triggeredSpy.target = null;
1748+ triggeredSpy.clear();
1749+ }
1750+
1751+ function initTestCase() {
1752+ compare(manager.globalContext.active, true, "globalContext is always active");
1753+ compare(manager.globalContext.overlay, false, "globalContext is not overlay");
1754+ compare(manager.globalContext.actions.length, 3, "global context action count mismatch");
1755+ compare(context.actions.length, 1, "Local context action count mismatch");
1756+ compare(context.active, false, "context should be inactive");
1757+ compare(activeContext.active, true, "activeContext should be active");
1758+ }
1759+
1760+ function test_actions_in_multiple_contexts() {
1761+ context.addAction(shared1);
1762+ context.addAction(global2);
1763+ verify(contains(context.actions, shared1), "shared Action missing from local");
1764+ verify(contains(manager.globalContext.actions, global2), "global Action missing from global");
1765+ verify(contains(context.actions, global2), "global Action missing from local");
1766+ }
1767+
1768+ function test_global_actions_always_active() {
1769+ context.addAction(global1);
1770+ triggeredSpy.target = global1;
1771+ global1.trigger();
1772+ triggeredSpy.wait(400);
1773+ }
1774+
1775+ function test_shared_actions_active_data() {
1776+ return [
1777+ {tag: "shared Action not in any local context", contexts: [], action: shared1, xfail: true},
1778+ {tag: "shared Action in inactive local context", contexts: [context], action: shared1, xfail: true},
1779+ {tag: "shared Action in active local context", contexts: [activeContext], action: shared1, xfail: false},
1780+ {tag: "shared Action in an active and inactive local context", contexts: [context, activeContext], action: shared1, xfail: false},
1781+ ];
1782+ }
1783+ function test_shared_actions_active(data) {
1784+ for (var i = 0; i < data.contexts.length; i++) {
1785+ data.contexts[i].addAction(data.action);
1786+ verify(contains(data.contexts[i].actions, data.action), "Action not found in context");
1787+ }
1788+ triggeredSpy.target = data.action;
1789+ data.action.trigger();
1790+ if (data.xfail) {
1791+ expectFail(data.tag, "Action should not trigger");
1792+ }
1793+ triggeredSpy.wait(400);
1794+ for (var i = 0; i < data.contexts.length; i++) {
1795+ data.contexts[i].removeAction(data.action);
1796+ verify(!contains(data.contexts[i].actions, data.action), "Action not removed from context");
1797+ }
1798+ }
1799+
1800+ function test_multiple_conytexts_active() {
1801+ context.active = true;
1802+ compare(activeContext.active, true);
1803+ compare(context.active, true);
1804+ }
1805+
1806+ function test_overlay_data() {
1807+ return [
1808+ {tag: "active overlay", active: true, xfail: true},
1809+ {tag: "inactive overlay", active: false, xfail: false},
1810+ ];
1811+ }
1812+ function test_overlay(data) {
1813+ activeContext.addAction(shared1);
1814+ context.overlay = true;
1815+ context.active = data.active;
1816+ verify(activeContext.active, "active ActionContext deactivated");
1817+ // try emit activeContext actions
1818+ triggeredSpy.target = shared1;
1819+ shared1.trigger();
1820+ if (data.xfail) {
1821+ expectFailContinue(data.tag, "Overlay should suppress other active context triggering");
1822+ }
1823+
1824+ triggeredSpy.wait(400);
1825+ }
1826+
1827+ function test_only_one_overlay_active_data() {
1828+ return [
1829+ {tag: "disable topmost first", actions: [shared1, shared2, shared3], contexts: [activeContext, context, overlayContext],
1830+ trigger: [shared1, shared2, shared3], triggerCount: [0, 0, 1], disableOrder: [overlayContext, context], disableTriggerCount: [[1, 0, 0], [1, 0, 0]] },
1831+ {tag: "disable second first", actions: [shared1, shared2, shared3], contexts: [activeContext, context, overlayContext],
1832+ trigger: [shared1, shared2, shared3], triggerCount: [0, 0, 1], disableOrder: [context, overlayContext], disableTriggerCount: [[0, 0, 1], [1, 0, 0]] },
1833+ ]
1834+ }
1835+ function test_only_one_overlay_active(data) {
1836+ for (var i = 0; i < data.contexts.length; i++) {
1837+ data.contexts[i].addAction(data.actions[i]);
1838+ }
1839+
1840+ context.overlay = true;
1841+ context.active = true;
1842+ // this should deactivate context!
1843+ overlayContext.active = true;
1844+ verify(!context.effectiveActive, "context active");
1845+ verify(!activeContext.effectiveActive, "activeContext deactivated");
1846+ verify(overlayContext.effectiveActive, "overlayContext deactivated");
1847+
1848+ // try to trigger actions
1849+ for (var i = 0; i < data.trigger.length; i++) {
1850+ triggeredSpy.clear();
1851+ triggeredSpy.target = data.trigger[i];
1852+ triggeredSpy.target.trigger();
1853+ compare(triggeredSpy.count, data.triggerCount[i], "action #" + data.trigger[i] + " should not be triggered");
1854+ }
1855+ // deactivate
1856+ for (var i = 0; i < data.disableOrder.length; i++) {
1857+ data.disableOrder[i].active = false;
1858+ var disableCount = data.disableTriggerCount[i]
1859+ for (var j = 0; j < data.trigger.length; j++) {
1860+ triggeredSpy.clear();
1861+ triggeredSpy.target = data.trigger[j];
1862+ triggeredSpy.target.trigger();
1863+ compare(triggeredSpy.count, disableCount[j], "action #" + data.trigger[j] + " should not be triggered");
1864+ }
1865+ }
1866+ }
1867+ }
1868+}
1869+
1870
1871=== added file 'tests/unit_x11/tst_components/tst_actioncontext.qml'
1872--- tests/unit_x11/tst_components/tst_actioncontext.qml 1970-01-01 00:00:00 +0000
1873+++ tests/unit_x11/tst_components/tst_actioncontext.qml 2015-08-26 15:09:13 +0000
1874@@ -0,0 +1,181 @@
1875+/*
1876+ * Copyright 2015 Canonical Ltd.
1877+ *
1878+ * This program is free software; you can redistribute it and/or modify
1879+ * it under the terms of the GNU Lesser General Public License as published by
1880+ * the Free Software Foundation; version 3.
1881+ *
1882+ * This program is distributed in the hope that it will be useful,
1883+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1884+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1885+ * GNU Lesser General Public License for more details.
1886+ *
1887+ * You should have received a copy of the GNU Lesser General Public License
1888+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1889+ */
1890+
1891+import QtQuick 2.0
1892+import QtTest 1.0
1893+import Ubuntu.Test 1.0
1894+import Ubuntu.Components 1.3
1895+import Ubuntu.Components.Popups 1.3
1896+
1897+MainView {
1898+ width: units.gu(40)
1899+ height: units.gu(71)
1900+
1901+ property Dialog testDialog: null
1902+
1903+ Action {
1904+ id: sharedAction
1905+ text: "pressme"
1906+ }
1907+
1908+ Component {
1909+ id: dialogComponent
1910+ Dialog {
1911+ id: dialog
1912+ title: "TestDialog"
1913+ Button {
1914+ objectName: "okButton"
1915+ action: Action {
1916+ text: "Ok"
1917+ onTriggered: {
1918+ testDialog = null;
1919+ PopupUtils.close(dialog);
1920+ }
1921+ }
1922+ }
1923+ // simulate Page
1924+ ActionContext {
1925+ active: true
1926+ Button {
1927+ anchors.bottom: parent.bottom
1928+ objectName: "nestedButton"
1929+ action: Action {
1930+ id: nestedAction
1931+ text: "Nested"
1932+ }
1933+ onClicked: action.trigger()
1934+ }
1935+ }
1936+ }
1937+ }
1938+
1939+ AdaptivePageLayout {
1940+ id: multiColumn
1941+ anchors.fill: parent
1942+ // only one column!
1943+ layouts: PageColumnsLayout {
1944+ when: true
1945+ PageColumn {
1946+ fillWidth: true
1947+ }
1948+ }
1949+
1950+ primaryPage: Page {
1951+ title: "MainPage"
1952+ ActionContext {
1953+ id: nestedContext
1954+ anchors.fill: parent
1955+ Column {
1956+ Button {
1957+ action: sharedAction
1958+ }
1959+ Button {
1960+ action: Action {
1961+ id: dialogOpen
1962+ text: "Open dialog"
1963+ onTriggered: testDialog = PopupUtils.open(dialogComponent);
1964+ }
1965+ }
1966+ }
1967+ }
1968+ }
1969+ Page {
1970+ id: secondPage
1971+ title: "SecondPage"
1972+ objectName: "secondPage"
1973+ Button {
1974+ action: Action {
1975+ id: secondAction
1976+ text: "once more"
1977+ }
1978+ }
1979+ }
1980+ }
1981+
1982+ UbuntuTestCase {
1983+ name: "PageAndDialogActions"
1984+ when: windowShown
1985+
1986+ SignalSpy {
1987+ id: triggerSpy
1988+ signalName: "triggered"
1989+ }
1990+ SignalSpy {
1991+ id: activeSpy
1992+ signalName: "activeChanged"
1993+ }
1994+
1995+ function cleanup() {
1996+ triggerSpy.target = null;
1997+ triggerSpy.clear();
1998+ activeSpy.target = null;
1999+ activeSpy.clear();
2000+ if (testDialog) {
2001+ PopupUtils.close(testDialog);
2002+ testDialog = null;
2003+ wait(500);
2004+ }
2005+ multiColumn.removePages(multiColumn.primaryPage);
2006+ waitForRendering(multiColumn, 400);
2007+ }
2008+
2009+ function test_secondPage_deactivates_first_actions() {
2010+ activeSpy.target = secondPage.actionContext;
2011+ multiColumn.addPageToCurrentColumn(multiColumn.primaryPage, secondPage);
2012+ activeSpy.wait();
2013+
2014+ triggerSpy.target = sharedAction;
2015+ triggerSpy.target.trigger();
2016+ compare(triggerSpy.count, 0, "sharedAction should not be triggered");
2017+ }
2018+
2019+ function test_dialog() {
2020+ triggerSpy.target = dialogOpen;
2021+ dialogOpen.trigger();
2022+ triggerSpy.wait();
2023+ wait(500);
2024+ // the active property doesn't change, only actions are not triggered from it
2025+ verify(multiColumn.primaryPage.actionContext.active, "primary page context should be active");
2026+ verify(testDialog.actionContext.active, "test dialog context should be active");
2027+ verify(testDialog.actionContext.overlay, "test dialog context should be overlay");
2028+
2029+ // test action triggering
2030+ triggerSpy.clear();
2031+ triggerSpy.target = sharedAction;
2032+ triggerSpy.target.trigger();
2033+ compare(triggerSpy.count, 0, "sharedAction should not be triggered");
2034+
2035+ // trigger dialog action
2036+ triggerSpy.target = findChild(testDialog, "okButton").action;
2037+ triggerSpy.clear();
2038+ triggerSpy.target.trigger();
2039+ triggerSpy.wait();
2040+ wait(500);
2041+ }
2042+
2043+ function test_nested_context_active_in_overlay() {
2044+ triggerSpy.target = dialogOpen;
2045+ dialogOpen.trigger();
2046+ triggerSpy.wait();
2047+ wait(500);
2048+ // trigger nested context
2049+ triggerSpy.clear();
2050+ triggerSpy.target = findChild(testDialog, "nestedButton");
2051+ triggerSpy.target.trigger();
2052+ triggerSpy.wait(400);
2053+ }
2054+ }
2055+}
2056
2057=== modified file 'tests/unit_x11/tst_components/tst_sections.qml'
2058--- tests/unit_x11/tst_components/tst_sections.qml 2015-06-17 12:17:09 +0000
2059+++ tests/unit_x11/tst_components/tst_sections.qml 2015-08-26 15:09:13 +0000
2060@@ -60,7 +60,7 @@
2061 Label {
2062 anchors.left: parent.left
2063 text: "actions in-line:"
2064- fontSize: small
2065+ fontSize: "small"
2066 }
2067 Sections {
2068 // Not used in the tests below, but added here to
2069
2070=== modified file 'tests/unit_x11/tst_components/tst_shortcuts.qml'
2071--- tests/unit_x11/tst_components/tst_shortcuts.qml 2015-07-03 19:16:56 +0000
2072+++ tests/unit_x11/tst_components/tst_shortcuts.qml 2015-08-26 15:09:13 +0000
2073@@ -32,8 +32,13 @@
2074 shortcut: 'Ctrl+G'
2075 }
2076
2077- TestUtil {
2078- id: util
2079+ // need a manager to activate global context
2080+ ActionManager{}
2081+ // shortcuts need an active action to trigger
2082+ ActionContext{
2083+ id: context
2084+ active: true
2085+ actions: [action, other]
2086 }
2087
2088 UbuntuTestCase {
2089@@ -53,23 +58,27 @@
2090 target: action
2091 }
2092
2093- function ignoreQMLWarning(message) {
2094- ignoreWarning(util.callerFile() + message);
2095- }
2096-
2097 function test_shortcut_triggered_data() {
2098 return [
2099- { tag: 'Multiple modifiers and letter', shortcut: 'Ctrl+Shift+Alt+A', key: Qt.Key_A, mod: Qt.ControlModifier + Qt.ShiftModifier + Qt.AltModifier },
2100- { tag: 'Modifier and letter', shortcut: 'Ctrl+A', key: Qt.Key_A, mod: Qt.ControlModifier },
2101- { tag: 'Single letter', shortcut: 'E', key: Qt.Key_E, mod: Qt.NoModifier },
2102- { tag: 'StandardKey', shortcut: StandardKey.Copy, key: Qt.Key_C, mod: Qt.ControlModifier }
2103+ { tag: 'Multiple modifiers and letter', shortcut: 'Ctrl+Shift+Alt+A', key: Qt.Key_A, mod: Qt.ControlModifier + Qt.ShiftModifier + Qt.AltModifier, active: true, xfail: false },
2104+ { tag: 'Multiple modifiers and letter, xfail', shortcut: 'Ctrl+Shift+Alt+A', key: Qt.Key_A, mod: Qt.ControlModifier + Qt.ShiftModifier + Qt.AltModifier, active: false, xfail: true },
2105+ { tag: 'Modifier and letter', shortcut: 'Ctrl+A', key: Qt.Key_A, mod: Qt.ControlModifier, active: true, xfail: false },
2106+ { tag: 'Modifier and letter, xfail', shortcut: 'Ctrl+A', key: Qt.Key_A, mod: Qt.ControlModifier, active: false, xfail: true },
2107+ { tag: 'Single letter', shortcut: 'E', key: Qt.Key_E, mod: Qt.NoModifier, active: true, xfail: false },
2108+ { tag: 'Single letter, xfail', shortcut: 'E', key: Qt.Key_E, mod: Qt.NoModifier, active: false, xfail: true },
2109+ { tag: 'StandardKey', shortcut: StandardKey.Copy, key: Qt.Key_C, mod: Qt.ControlModifier, active: true, xfail: false },
2110+ { tag: 'StandardKey, xfail', shortcut: StandardKey.Copy, key: Qt.Key_C, mod: Qt.ControlModifier, active: false, xfail: true }
2111 ];
2112 }
2113 function test_shortcut_triggered(data) {
2114+ context.active = data.active;
2115 action.shortcut = data.shortcut;
2116 spy.clear();
2117 keyClick(data.key, data.mod);
2118- spy.wait();
2119+ if (data.xfail) {
2120+ expectFailContinue(data.tag, "Signal should not be triggered");
2121+ }
2122+ spy.wait(400);
2123 }
2124
2125 function test_shortcut_invalid_data() {
2126@@ -79,12 +88,12 @@
2127 ];
2128 }
2129 function test_shortcut_invalid(data) {
2130- ignoreQMLWarning(':27:5: QML Action: Invalid shortcut: ');
2131+ ignoreWarning(warningFormat(27, 5, 'QML Action: Invalid shortcut: '));
2132 action.shortcut = data;
2133 }
2134
2135 function test_shortcut_duplicate() {
2136- ignoreQMLWarning(':30:5: QML Action: Ambiguous shortcut: Ctrl+G');
2137+ ignoreWarning(warningFormat(30, 5, 'QML Action: Ambiguous shortcut: Ctrl+G'));
2138 action.shortcut = other.shortcut;
2139 keyClick(Qt.Key_G, Qt.ControlModifier);
2140 }

Subscribers

People subscribed via source and target branches