Merge lp:~zsombi/ubuntu-ui-toolkit/contextualActions into lp:ubuntu-ui-toolkit/staging
- contextualActions
- Merge into staging
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 |
Related bugs: |
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.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1620
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1623
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 1624. By Zsombor Egri
-
small cleanup
- 1625. By Zsombor Egri
-
remove sharedContext
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1625
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1626. By Zsombor Egri
-
documentation fix
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1626
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1627
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1628
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1629. By Zsombor Egri
-
build error fix
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1629
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1630. By Zsombor Egri
-
adding test case for active ActionContext inside a Dialog
- 1631. By Zsombor Egri
-
remove property iverride from SingleControl
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1631
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1632
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1633. By Zsombor Egri
-
documentation fix
- 1634. By Zsombor Egri
-
forward action list to ActionContexts
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1633
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1634
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 1635. By Zsombor Egri
-
documentation update
- 1636. By Zsombor Egri
-
prereq sync
- 1637. By Zsombor Egri
-
fixes
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1637
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
- 1638. By Zsombor Egri
-
fixing active overlay
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1638
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1639. By Zsombor Egri
-
fixing AbstractButton13 tests
- 1640. By Zsombor Egri
-
tst_action updated
- 1641. By Zsombor Egri
-
fixing tst_actionitem
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1641
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
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
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 | } |
FAILED: Continuous integration, rev:1619 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2137/ jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-amd64- ci/865 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/867 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-armhf- ci/867/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- vivid-i386- ci/864
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
deb: http://
UNSTABLE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/2137/ rebuild
http://